/* =========================================================================
   ESDI MANAGER — 04-patterns.css
   Composants & patterns ESDI (stub initial — sera rempli par stories Phase 2-4)
   Spec : _bmad-output/planning-artifacts/ux-design-specification.md
          section "Component Strategy" (lignes 1034-1170)
   ========================================================================= */

/* ---------------------------------------------------------------------------
   STUB — Ce fichier accueillera les 24 composants custom ESDI :

   Phase 1 (Sprint 1-2) — viendront avec Stories Epic 2 / Epic 7 :
     <ai-panel>, <kpi-card>, <employee-card>, <status-badge>,
     <status-dot>, <eyebrow>, <pulse-icon>, <undo-toast>

   Phase 2 (Sprint 3) — Stories Epic 5 / Epic 7 :
     <validation-card>, <empty-state>, <confidence-badge>,
     <mission-cascade>, <budget-tracker>, <rh-button-cluster>

   Phase 3 (Sprint 4) — Stories Epic 2 / Epic 4 :
     <employee-hero>, <schedule-timeline-24h>, <activity-log>,
     <oauth-connect>, <tier-badge>

   Phase 4 (Sprint 5) — Stories Epic 6 / Epic 7 :
     <command-palette>, <employee-catalog-card>, <without-with-compare>

   Phase 5 (hors V1) — Console agence V2.5 :
     <org-chart-compact>, <company-switcher>

   Chaque composant utilisera EXCLUSIVEMENT les tokens de 01-tokens.css.
   Aucun hex hard-coded autorisé ici.
   --------------------------------------------------------------------------- */

/* ===========================================================================
   Story 27-4 — LA FRISE DES PLANNINGS (.pl-*)
   ---------------------------------------------------------------------------
   Une grille de jours en colonnes, une ressource (ou une cible) par ligne, et
   des barres positionnées en pourcentage de la piste — donc responsive sans
   calcul JS au redimensionnement.

   Deux choix qui ne sont pas cosmétiques :
   — une barre qui DÉBORDE de la fenêtre le dit par une encoche (.pl-deborde-*) :
     sans ça, un bail de douze mois ressemble à une semaine et l'utilisateur
     croit que son planning s'est raccourci ;
   — les indisponibilités sont un FOND rayé, jamais une barre : elles expliquent
     un refus avant qu'il n'arrive, elles ne se manipulent pas.

   Aucun hex en dur : tokens de 01-tokens.css, et le dark passe par eux.
   =========================================================================== */

.pl-frise { overflow-x: auto; }
.pl-grille { min-width: 720px; }

.pl-ligne { display: flex; align-items: stretch; border-bottom: 1px solid var(--border-subtle, rgba(0,0,0,.07)); }
.pl-ligne:last-child { border-bottom: 0; }

.pl-cellule-titre {
    flex: 0 0 190px; padding: .5rem .75rem; min-width: 190px;
    border-right: 1px solid var(--border-subtle, rgba(0,0,0,.07));
    background: var(--bg-card);
    position: sticky; left: 0; z-index: 2;
}
.pl-titre { font-size: .875rem; font-weight: 600; color: var(--ink); }
.pl-sous  { font-size: .75rem; color: var(--ink-faded); }

.pl-piste { position: relative; flex: 1 1 auto; display: flex; min-height: 46px; }
.pl-jour, .pl-case { flex: 1 1 0; min-width: 0; border-right: 1px solid var(--border-subtle, rgba(0,0,0,.05)); }
.pl-jour { padding: .35rem .25rem; font-size: .7rem; text-align: center; color: var(--ink-faded); }
.pl-weekend { background: var(--bg-section-warm); }

.pl-entete { position: sticky; top: 0; z-index: 3; background: var(--bg-card); }

.pl-barre {
    position: absolute; top: 6px; bottom: 6px;
    background: var(--gradient-brand); color: #fff;
    border-radius: 6px; padding: 0 .5rem;
    display: flex; align-items: center; overflow: hidden;
    cursor: grab; user-select: none;
    box-shadow: 0 1px 3px rgba(0,0,0,.18);
}

/*
  Les jours CHÔMÉS d'une affectation récurrente (2026-08-11) : ils sont creusés DANS la barre par un
  dégradé calculé en JS, pas découpés en éléments — le glisser-déposer manipule une barre unique, et une
  année en jours ouvrés produirait sinon 52 nœuds par ligne.

  Un voile sombre translucide plutôt qu'une couleur pleine : il fonctionne au-dessus de n'importe quelle
  couleur de type de ressource, sans avoir à en calculer une variante par type.
*/
:root { --pl-jour-chome: rgba(0, 0, 0, .34); }
[data-theme="dark"] { --pl-jour-chome: rgba(0, 0, 0, .48); }

/*
  Échelles larges (trimestre, année) — 2026-08-11.

  Une colonne y vaut deux ou trois pixels. Un libellé écrit DANS la colonne la force à la largeur de son
  texte : les mots se replient caractère par caractère (« J A N » à la verticale) et la piste passe de
  1 000 à 3 500 px, qu'il faut alors faire défiler pour voir une année — précisément ce qu'on voulait
  éviter. L'étiquette est donc posée HORS DU FLUX, par-dessus la grille : elle déborde librement sur les
  colonnes voisines, jusqu'au repère suivant, et ne contraint plus rien.
*/
.pl-echelle-large .pl-jour { padding: 0; min-width: 0; position: relative; overflow: visible; }
/*
  ⚠️ Le z-index n'est pas cosmétique. Une étiquette sortie du flux déborde sur les colonnes SUIVANTES,
  qui sont plus loin dans le DOM : à z-index égal, ce sont elles qui se dessinent par-dessus, et leurs
  filets verticaux tranchent le mot (« FÉ / », « A /R »). Constaté par Kevin sur la vue annuelle.
  Le fond opaque évite le même écrasement par les bandes de week-end.
*/
.pl-echelle-large .pl-jour .pl-jour-lettre,
.pl-echelle-large .pl-jour .pl-jour-chiffre {
    position: absolute; top: 2px; left: 3px; z-index: 4;
    white-space: nowrap; pointer-events: none;
    font-size: .68rem; line-height: 1;
    padding: 0 .25rem 0 .15rem;
    background: var(--bg-card);
    border-radius: 3px;
}
/* Un filet vertical sur le repère : sans lui, l'étiquette flotte sans qu'on sache où commence le mois. */
.pl-echelle-large .pl-jour-repere { border-left: 1px solid var(--border, rgba(0,0,0,.18)); z-index: 3; }
.pl-barre:hover { filter: brightness(1.06); }
.pl-glisse { cursor: grabbing; opacity: .82; }
.pl-barre-texte { font-size: .75rem; white-space: nowrap; text-overflow: ellipsis; overflow: hidden; }

/* La poignée de redimensionnement est un ENFANT de la barre : le mousedown la
   distingue par sa classe, ce qui évite un second écouteur et un état de plus. */
.pl-poignee { position: absolute; right: 0; top: 0; bottom: 0; width: 8px; cursor: ew-resize; }

.pl-deborde-gauche  { border-top-left-radius: 0; border-bottom-left-radius: 0;
                      border-left: 3px dashed rgba(255,255,255,.75); }
.pl-deborde-droite  { border-top-right-radius: 0; border-bottom-right-radius: 0;
                      border-right: 3px dashed rgba(255,255,255,.75); }

/* Les PHASES d'un chantier (2026-08-23) — « 3 jours de démolition, puis 2 jours de placo ».
   Elles occupent le BAS de la piste et restent DERRIÈRE les affectations : une phase est le décor,
   les gens sont au premier plan. Une phase à laquelle personne n'est affecté se hachure — c'est
   l'information qu'un conducteur de travaux cherche du regard, elle ne doit pas se lire. */

/* La ligne des PHASES, en tete de frise (2026-08-23) — elle cadence tout le monde, elle se lit donc
   au-dessus de tout le monde, et dans les DEUX lectures (par ressource comme par chantier). */
.pl-ligne-phases { background: rgba(63,190,204,.05); }
.pl-ligne-phases .pl-cellule-titre { font-size: .78rem; }
.pl-ligne-phases .pl-piste { min-height: 34px; }
.pl-ligne-phases .pl-tache { top: 4px; bottom: 4px; height: auto; }
.pl-ligne-phases .pl-tache-texte { font-size: 10px; line-height: 24px; }

.pl-tache {
    position: absolute; bottom: 2px; height: 10px;
    border-radius: 4px; pointer-events: none; overflow: hidden;
    background: linear-gradient(135deg, #d6f1f5, #a9e2ea);
    border: 1px solid rgba(63, 190, 204, .55);
}
.pl-tache-texte {
    display: block; font-size: 8px; line-height: 10px; padding: 0 5px;
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
    color: #0d5560; font-weight: 600;
}
.pl-tache-vide {
    background: repeating-linear-gradient(45deg,
        #fff4d6 0, #fff4d6 5px, transparent 5px, transparent 10px);
    border-color: rgba(234, 179, 8, .6);
}
[data-theme="dark"] .pl-tache { background: linear-gradient(135deg, #0e3f48, #12525e); border-color: rgba(63,190,204,.4); }
[data-theme="dark"] .pl-tache-texte { color: #bfeef5; }
[data-theme="dark"] .pl-tache-vide { background: repeating-linear-gradient(45deg, #4a3a12 0, #4a3a12 5px, transparent 5px, transparent 10px); }

.pl-absence {
    position: absolute; top: 0; bottom: 0;
    background: repeating-linear-gradient(45deg,
        var(--sig-warn-bg) 0, var(--sig-warn-bg) 6px,
        transparent 6px, transparent 12px);
    pointer-events: none;
}

/* ===========================================================================
   PLANNINGS — REFONTE 27-15 (Kevin, 2026-08-02) : « qu'en un coup d'œil il
   puisse tout de suite le comprendre ».

   Trois partis pris, aucun cosmétique :

   — LA FRISE RESPIRE. Les lignes passent de 46 à 62 px et les barres de 34 à
     42 px. Ce n'est pas du confort : à 34 px, le nom du chantier et les heures
     ne tenaient pas ensemble dans la barre, et l'heure disparaissait — donc
     l'information qu'on cherche le plus souvent.

   — AUJOURD'HUI EST UNE COLONNE, pas une couleur de texte. On lit un planning
     en cherchant d'abord « on en est où » ; si la réponse demande de compter
     les colonnes depuis le lundi, l'écran a échoué.

   — LES TUILES SONT DES BOUTONS et en ont l'air. Une tuile qui ne filtre rien
     (l'avancement moyen) est explicitement inerte : sinon on clique, il ne se
     passe rien, et on cesse de faire confiance aux quatre autres.

   Aucun hex en dur : tokens de 01-tokens.css, et le dark passe par eux.
   =========================================================================== */

/* ── Le coup d'œil ─────────────────────────────────────────────────────── */

.pl-coupdoeil {
    display: grid; gap: .75rem;
    grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
}
.pl-tuile {
    text-align: left; border: 1px solid var(--border-subtle, rgba(0,0,0,.08));
    border-radius: 12px; background: var(--bg-card); padding: .7rem .9rem;
    transition: border-color .15s ease, transform .15s ease, box-shadow .15s ease;
    cursor: pointer;
}
.pl-tuile:hover:not(:disabled) { border-color: var(--accent-400); transform: translateY(-1px); box-shadow: 0 3px 10px rgba(0,0,0,.07); }
.pl-tuile:focus-visible { outline: 2px solid var(--accent-500); outline-offset: 2px; }
.pl-tuile-valeur { font-family: var(--font-mono, monospace); font-size: 1.6rem; line-height: 1.1; font-weight: 600; color: var(--ink); }
.pl-tuile-titre  { font-size: .8rem; font-weight: 600; color: var(--ink); margin-top: .15rem; }
.pl-tuile-detail { font-size: .72rem; color: var(--ink-faded); margin-top: .1rem; }

/* La tuile active porte la couleur de marque : on doit voir SANS chercher que
   la frise en dessous est filtrée — un filtre invisible fait croire à des
   données disparues. */
.pl-tuile-active { border-color: var(--accent-500); box-shadow: inset 0 0 0 1px var(--accent-500); }
.pl-tuile-ok .pl-tuile-valeur    { color: var(--sig-ok); }
.pl-tuile-alerte                  { border-color: var(--sig-warn); }
.pl-tuile-alerte .pl-tuile-valeur { color: var(--sig-warn); }
.pl-tuile-inerte { cursor: default; opacity: .9; }
.pl-tuile-inerte:hover { transform: none; box-shadow: none; }

/* ── L'encart assistant ───────────────────────────────────────────────── */

/* DISCRET par construction (consigne Kevin, 2026-08-02). Une carte pleine
   largeur avec un bouton d'appel à l'action posée entre le dirigeant et sa
   frise, c'est une bannière publicitaire : le sujet de cette page est le
   chantier, pas la boutique. Donc UNE LIGNE — pas de fond, pas de bordure, pas
   de bouton. Elle informe, elle ne réclame pas. */
.pl-assistant {
    display: flex; align-items: center; gap: .4rem; flex-wrap: wrap;
    font-size: .78rem; color: var(--ink-faded); padding: 0 .15rem;
}
/* La pastille garde son doré plein : c'est un repère de marque, pas une décoration —
   l'atténuer la ferait passer pour une icône générique. */
.pl-assistant-icone { flex: 0 0 auto; vertical-align: -1px; }
.pl-assistant-lien {
    color: var(--accent-600); text-decoration: none;
    border-bottom: 1px solid currentColor; padding-bottom: 1px;
}
.pl-assistant-lien:hover { color: var(--accent-700); }
.pl-assistant-lien:focus-visible { outline: 2px solid var(--accent-500); outline-offset: 2px; }

/* ── La frise, en plus grand ──────────────────────────────────────────── */

.pl-grille .pl-piste { min-height: 62px; }
.pl-grille .pl-barre { top: 10px; bottom: 10px; }

/* L'en-tête de jour porte la LETTRE du jour au-dessus du quantième : sans
   elle, on compte les colonnes pour savoir si le 14 est un samedi. */
.pl-jour-lettre  { font-size: .62rem; letter-spacing: .06em; text-transform: uppercase; opacity: .75; }
.pl-jour-chiffre { font-size: .82rem; font-weight: 600; color: var(--ink); }

.pl-aujourdhui { background: var(--accent-50); box-shadow: inset 1px 0 0 var(--accent-300), inset -1px 0 0 var(--accent-300); }
.pl-jour.pl-aujourdhui .pl-jour-chiffre { color: var(--accent-700); }

/* Le titre de ligne est un BOUTON (clavier + lecteurs d'écran), qui ne doit
   pas ressembler à un bouton tant qu'on ne le survole pas. */
.pl-titre-bouton {
    display: block; width: 100%; text-align: left; background: none; border: 0;
    padding: .1rem .15rem; border-radius: 6px; cursor: pointer;
}
.pl-titre-bouton:hover:not(:disabled) { background: var(--bg-section-cool); }
.pl-titre-bouton:focus-visible { outline: 2px solid var(--accent-500); outline-offset: 1px; }
.pl-titre-bouton:disabled { cursor: default; }

/* La jauge d'avancement, sous le nom de la cible. Elle répond à « où j'en
   suis » sans ouvrir quoi que ce soit. */
.pl-jauge { height: 4px; border-radius: 2px; background: var(--border-subtle, rgba(0,0,0,.1)); margin-top: .3rem; overflow: hidden; }
.pl-jauge-remplie { height: 100%; background: var(--gradient-brand); }

/* Deux poignées, une par bord : un chantier qui commence trois jours plus tôt
   se corrige d'un seul geste. Avec la seule poignée droite d'avant, il fallait
   déplacer PUIS rallonger — deux écritures pour un geste. */
.pl-poignee-g { right: auto; left: 0; }
.pl-poignee-d { left: auto; right: 0; }
.pl-barre:hover .pl-poignee { box-shadow: inset 0 0 0 2px rgba(255,255,255,.55); border-radius: 6px; }

/* L'infobulle de glissement : elle annonce le résultat AVANT de relâcher.
   Sans elle, on relâche, on lit, et si c'est faux on recommence. */
.pl-infobulle {
    position: fixed; z-index: 1080; pointer-events: none;
    background: var(--ink); color: var(--bg-card);
    font-size: .75rem; padding: .3rem .55rem; border-radius: 6px;
    box-shadow: 0 2px 8px rgba(0,0,0,.25); white-space: nowrap;
}

/* Une ligne d'inventaire est cliquable sur toute sa largeur : viser un petit
   crayon à droite est un geste de précision inutile. */
.pl-inventaire-ligne {
    display: flex; width: 100%; align-items: center; justify-content: space-between; gap: .75rem;
    background: none; border: 0; padding: .35rem .25rem; border-radius: 6px; cursor: pointer;
}
.pl-inventaire-ligne:hover { background: var(--bg-section-cool); }
.pl-inventaire-ligne:focus-visible { outline: 2px solid var(--accent-500); outline-offset: 1px; }

/* ── Les types : les repérer, les filtrer ─────────────────────────────
   Demande Kevin (2026-08-02) : « les compagnons et le matériel sont dans la
   même catégorie, il faudrait les séparer » puis « il faut qu'on puisse les
   filtrer et les repérer / différencier correctement dans la frise ».

   La COULEUR est calculée en JS à partir du code du type, jamais déclarée ici
   ni dans le registre : un pack qu'on n'a pas encore écrit aurait sinon des
   barres toutes grises, et le défaut reviendrait au premier métier ajouté. */

.pl-groupe { background: var(--bg-section-cool); }
.pl-groupe-titre {
    display: flex; align-items: center; gap: .4rem;
    font-size: .72rem; font-weight: 700; text-transform: uppercase; letter-spacing: .04em;
    color: var(--ink-faded); padding-top: .35rem; padding-bottom: .35rem;
    background: var(--bg-section-cool);
}
.pl-groupe .pl-piste { min-height: 0; background: var(--bg-section-cool); }
.pl-groupe-compte {
    margin-left: auto; font-family: var(--font-mono, monospace);
    font-size: .7rem; opacity: .8;
}

/* La pastille de couleur : devant le nom ET dans la légende, pour qu'on relie
   les deux sans effort. */
.pl-point {
    display: inline-block; width: 8px; height: 8px; border-radius: 50%;
    margin-right: .4rem; flex-shrink: 0; vertical-align: 1px;
}

.pl-types { display: flex; flex-wrap: wrap; gap: .35rem; }
.pl-puce {
    display: inline-flex; align-items: center; gap: .15rem;
    font-size: .72rem; padding: .18rem .5rem; border-radius: 999px;
    border: 1px solid var(--border-subtle, rgba(0,0,0,.12));
    background: var(--bg-card); color: var(--ink); cursor: pointer;
}
.pl-puce:hover { border-color: var(--accent-400); }
.pl-puce:focus-visible { outline: 2px solid var(--accent-500); outline-offset: 2px; }
.pl-puce-compte {
    font-family: var(--font-mono, monospace); font-size: .68rem;
    margin-left: .3rem; opacity: .7;
}
/* Un type masqué reste VISIBLE, barré : un filtre invisible fait croire à des
   données perdues — même règle que pour les tuiles du coup d'œil. */
.pl-puce-off { opacity: .5; text-decoration: line-through; }
.pl-puce-off .pl-point { filter: grayscale(1); }

/* ═══════════════════════════════════════════════════════════════════════════════════════════════════
   ÉPIQUE 30 — LA GRILLE DU TABLEAU DE BORD, ARRANGEABLE À LA SOURIS
   Pages /dashboard/societe et /dashboard/groupe.

   Le modèle est volontairement pauvre : une grille de 12 colonnes, chaque carte occupe `--w` colonnes et
   réclame `--h` crans de hauteur MINIMALE. Pas de x, pas de y — l'ordre du tableau EST la position. C'est
   ce qui rend le téléphone gratuit (tout en pleine largeur, dans l'ordre) et ce qui interdit la classe de
   bug la plus détestée d'une grille libre : « mon widget a bougé tout seul ».

   `--h` agit en hauteur MINIMALE et non en nombre de lignes de grille : chaque rangée se dimensionne alors
   sur sa carte la plus haute, exactement comme le faisait la rangée Bootstrap + `h-100` d'avant, et aucun
   trou n'est représentable — donc aucun n'est à compacter.
   ═══════════════════════════════════════════════════════════════════════════════════════════════════ */

.esdi-dash-grid {
    display: grid;
    grid-template-columns: repeat(12, minmax(0, 1fr));
    gap: 1rem;
    align-items: stretch;
}
.esdi-dash-cell {
    display: flex;
    min-width: 0;                                   /* sans ça, un tableau large fait déborder sa colonne */
    grid-column: span var(--w, 4);
    min-height: calc(var(--h, 1) * 148px);
}
.esdi-dash-cell > .card { flex: 1 1 auto; min-width: 0; }

/* Paliers hérités de l'écran d'avant : pleine largeur sur téléphone, et sur tablette un tableau prend toute
   la largeur pendant que le reste se met par deux (`--wmd`, posé par la vue). */
@media (max-width: 767.98px) {
    .esdi-dash-cell { grid-column: 1 / -1; min-height: 0; }
}
@media (min-width: 768px) and (max-width: 1199.98px) {
    .esdi-dash-cell { grid-column: span var(--wmd, 6); }
}

/* ── Le bouton « ? » ────────────────────────────────────────────────────────────────────────────────
   Il existait depuis 17-6 sous forme d'un point d'interrogation nu, sans aucune règle de style : à peine
   visible, donc à peine cliqué. Il reste HORS DU FLUX (position absolue) — en version texte dans le flux,
   il consommait de la hauteur dans chaque carte et décalait le contenu. */
.esdi-demander {
    display: inline-flex; align-items: center; justify-content: center;
    width: 22px; height: 22px; border-radius: 999px;
    font-size: .78rem; font-weight: 600; line-height: 1;
    color: var(--ink-faded); background: var(--bg-section-cool);
    border: 1px solid var(--line);
    opacity: .55; transition: opacity .15s, color .15s, border-color .15s, background .15s;
}
.esdi-dash-cell:hover .esdi-demander, .esdi-demander:focus-visible { opacity: 1; }
.esdi-demander:hover { color: var(--accent-700, #26737c); border-color: var(--accent-500); background: var(--bg-card); }
.esdi-demander:focus-visible { outline: 2px solid var(--accent-500); outline-offset: 2px; }

/* ── Attraper une carte ─────────────────────────────────────────────────────────────────────────────
   Pas de mode, pas de bouton (demande Kevin du 2026-08-02) : on appuie, on TIENT un court instant, la
   carte se soulève et suit le curseur ; on la pose où on veut. Ce qui remplace le mode, c'est le maintien
   — l'intention se prouve par le geste. Un simple clic, un défilement ou une sélection de texte ne
   déclenchent donc jamais un déplacement, sur un tableau de bord pourtant partagé par toute la société.

   La transition sur `transform` est ce qui fait l'animation : les voisines s'écartent en glissant (le JS
   les repositionne par la technique FLIP), et la carte relâchée revient dans sa case au lieu d'y claquer. */
.esdi-dash-cell {
    transition: transform .18s cubic-bezier(.2, .8, .3, 1), box-shadow .18s ease;
}
.esdi-dash-grid[data-peut-arranger="true"] .esdi-dash-cell { cursor: grab; }

/* Le maintien est en train d'être reconnu : la carte s'enfonce très légèrement. Sans ce retour, on ne sait
   pas si l'appui « a pris » et on relâche trop tôt. */
.esdi-dash-cell--attente { transform: scale(.985); }

/* La carte tenue. `transition: none` — elle doit coller au curseur, une interpolation la ferait flotter en
   retard d'un dixième de seconde, ce qui se ressent immédiatement comme « ça rame ». */
.esdi-dash-cell--drag {
    transition: none;
    cursor: grabbing;
    z-index: 5;
    will-change: transform;
    /* 🔴 INDISPENSABLE, et ça ne se devine pas : la carte tenue suit le curseur, elle est donc toujours
       l'élément le plus haut sous lui. Sans cette ligne, `elementFromPoint` renvoie la carte elle-même,
       jamais la voisine survolée, et RIEN ne se réordonne — la carte bouge à l'écran et le tableau de bord
       ne change pas. Elle continue de recevoir ses propres événements : ils arrivent par la capture de
       pointeur, qui ne dépend pas du survol. */
    pointer-events: none;
}
.esdi-dash-cell--drag > .card {
    box-shadow: 0 12px 32px rgba(0, 0, 0, .22);
    border-color: var(--accent-500);
}
.esdi-dash-cell--taille > .card { border-color: var(--accent-500); }

/* Pendant le geste, plus rien ne se sélectionne : sans ça, traverser un titre en tirant surligne du texte
   au milieu du tableau de bord. */
body.esdi-dash-en-cours { user-select: none; cursor: grabbing; }

/* La marque « ça s'attrape » : trois points en haut à gauche, au survol seulement. Une poignée permanente
   salirait un tableau de bord qu'on regarde surtout sans y toucher. Purement indicative — c'est toute la
   carte qui s'attrape. */
.esdi-dash-grid[data-peut-arranger="true"] .esdi-dash-cell::before {
    content: '';
    position: absolute; top: 10px; left: 8px; z-index: 3;
    width: 10px; height: 14px;
    background-image: radial-gradient(currentColor 1.1px, transparent 1.2px);
    background-size: 5px 5px;
    color: var(--ink-faded);
    opacity: 0; transition: opacity .15s ease;
    pointer-events: none;
}
.esdi-dash-grid[data-peut-arranger="true"] .esdi-dash-cell:hover::before { opacity: .45; }
.esdi-dash-cell--drag::before { opacity: 0 !important; }

/* Le coin de redimensionnement : visible au survol de sa carte, et toujours pendant le geste. */
.esdi-dash-poignee {
    position: absolute; right: 3px; bottom: 3px;
    width: 20px; height: 20px;
    display: inline-flex; align-items: center; justify-content: center;
    cursor: nwse-resize; z-index: 4;
    color: var(--ink-faded); background: var(--bg-card);
    border: 1px solid var(--line); border-radius: 6px;
    opacity: 0; transition: opacity .15s ease, color .15s, border-color .15s;
}
.esdi-dash-grid[data-peut-arranger="true"] .esdi-dash-cell:hover .esdi-dash-poignee { opacity: .8; }
.esdi-dash-cell--taille .esdi-dash-poignee { opacity: 1; }
.esdi-dash-poignee::before {
    content: ''; width: 8px; height: 8px;
    border-right: 2px solid currentColor; border-bottom: 2px solid currentColor;
    opacity: .7;
}
.esdi-dash-poignee:hover { color: var(--accent-700, #26737c); border-color: var(--accent-500); opacity: 1; }
/* Un tableau de bord qu'on ne peut pas modifier n'affiche aucune poignée : proposer un geste qui finira en
   403 est pire que ne rien proposer. */
.esdi-dash-grid:not([data-peut-arranger="true"]) .esdi-dash-poignee { display: none; }

/* L'infobulle qui annonce la position ou la taille PENDANT le geste (patron éprouvé sur la frise du
   planning) : sans elle on relâche, puis on découvre, et si c'est faux on recommence. */
.esdi-dash-bulle {
    position: fixed; z-index: 1080; pointer-events: none;
    font-size: .75rem; font-family: var(--font-mono, monospace);
    padding: .2rem .5rem; border-radius: 6px;
    color: #fff; background: rgba(17, 24, 39, .92);
    white-space: nowrap;
}

/* L'état de l'enregistrement — discret, à côté du titre. « Modifié… » puis « Disposition enregistrée ».
   Sans ce mot, une sauvegarde automatique est indiscernable d'une sauvegarde qui n'a pas eu lieu. */
.esdi-dash-etat {
    display: inline-flex; align-items: center; gap: .3rem;
    font-size: .78rem; color: var(--accent-700, #26737c);
    background: var(--bg-section-cool); border: 1px solid var(--line);
    border-radius: 999px; padding: .1rem .6rem;
}

@media (prefers-reduced-motion: reduce) {
    .esdi-dash-cell { transition: none; }
    .esdi-dash-cell--attente { transform: none; }
}

[data-theme="dark"] .esdi-dash-cell--drag > .card { box-shadow: 0 12px 32px rgba(0, 0, 0, .6); }
[data-theme="dark"] .esdi-dash-bulle { background: rgba(2, 6, 23, .95); border: 1px solid var(--line); }
