/* ===========================================================================
   Aurora — MENU « + » (joindre) : passage de la LISTE a la GRILLE
   ---------------------------------------------------------------------------
   POURQUOI cette feuille existe.
   `#attach-menu` porte 13 options. En liste verticale (une pastille 34x34 +
   un libelle par ligne) cela faisait 716 px de contenu enferme dans un
   panneau haut de ~291 px : une colonne interminable, une grosse barre de
   defilement claire en travers du verre, et six options invisibles tant
   qu'on n'avait pas fait defiler. C'etait le defaut signale.

   La reponse est une GRILLE facon feuille de partage (WhatsApp / Telegram) :
   pastille d'icone au-dessus, libelle centre en dessous, 5 colonnes en
   largeur normale. Les 13 options tiennent alors en 4 rangees (454 px
   mesures), donc TOUT est visible d'un coup, sans defilement, sur un
   ecran normal — et jusqu'a un telephone en paysage (§8).

   Trois choix structurants, expliques a leur place plus bas :
     1. les groupes (Envoyer / Creer / Jouer / Options) sont decrits par
        `[data-att]`, JAMAIS par `:nth-child` : le balisage peut etre
        reordonne sans rien casser ;
     2. les intitules de groupe sont des `::before` HORS FLUX loges dans
        l'ecart de rangee — ils ne decalent donc pas les tuiles voisines et
        les icones restent alignees a la rangee pres ;
     3. « Vue unique » n'est pas une tuile mais une BARRE pleine largeur
        avec un temoin en forme d'interrupteur : ce n'est pas une action,
        c'est un etat qui persiste.

   Chargee EN DERNIER : elle surcharge style.css (l.605-616), polish.css
   (l.570-590) et anim.css (l.912-920) sans que ces fichiers bougent.
   Aucune couleur en dur, aucun script, uniquement des jetons de theme.
   =========================================================================== */

/* ---------------------------------------------------------------------------
   1. LE PANNEAU
   ---------------------------------------------------------------------------
   `container-type: inline-size` n'est pas decoratif : il rend l'unite `cqw`
   disponible aux descendants. C'est elle qui permet aux filets de groupe de
   faire EXACTEMENT la largeur utile du panneau alors qu'ils sont dessines
   depuis une tuile large d'un cinquieme (voir §4). Sans conteneur declare
   les navigateurs qui connaissent `cqw` retomberaient sur le viewport ;
   ceux qui l'ignorent tombent sur le repli `width: 100%` ecrit juste avant.
   La largeur est imposee ici parce que `.dock-panel` la laisse a 268 px
   (style.css l.605) et la passe a `auto` sous 820 px (style.css l.464) :
   une grille a besoin d'une largeur connue et bornee.
   --------------------------------------------------------------------------- */
#attach-menu {
  /* Jetons locaux : un seul endroit a toucher dans les requetes media. */
  --ao-ic-size: 44px;      /* cote de la pastille d'icone */
  --ao-gap-x: 6px;         /* ecart entre colonnes */
  --ao-gap-y: 22px;        /* ecart entre rangees — loge aussi les intitules */
  --ao-pad-x: 12px;        /* marge interieure horizontale du panneau */
  --ao-pad-t: 30px;        /* marge haute : logement du 1er intitule */
  --ao-pad-b: 14px;
  --ao-label: .655rem;     /* corps du libelle */
  --ao-lines: 2.35em;      /* hauteur reservee au libelle : 2 lignes pleines */

  /* Gabarit des intitules de groupe (§4). Ils sont HORS FLUX et logent dans
     l'ecart de rangee : si cet ecart devient plus petit qu'eux, l'intitule
     recouvre le libelle de la rangee du dessus. `--ao-lab-h` mesure donc la
     place qu'ils reclament reellement — corps x interlignage, plus le filet
     (1px), sa marge basse et le retrait qui l'ecarte de la tuile — et sert de
     PLANCHER a `row-gap` et a la marge haute juste en dessous. Le calcul est
     en `rem` : si l'utilisateur grossit la police de son navigateur, l'ecart
     grossit avec les intitules et le chevauchement reste impossible. */
  --ao-lab-fs: .58rem;
  --ao-lab-pb: 3px;
  --ao-lab-mb: 6px;
  --ao-lab-h: calc(var(--ao-lab-fs) * 1.05 + var(--ao-lab-pb) + var(--ao-lab-mb) + 1px);

  /* Filet / bordure DERIVE DU TEXTE et non de `--border` : dans la palette
     « Clair » `--border` vaut du blanc a 75 % (themes.css l.103, qui gagne sur
     style.css l.994 puisqu'il est charge apres), donc un trait blanc sur du
     verre clair — invisible. Un melange de la couleur du texte s'inverse tout
     seul : clair sur fond sombre, sombre sur fond clair, sur les 7 palettes. */
  --ao-line: color-mix(in srgb, var(--text) 18%, transparent);

  container-type: inline-size;

  width: min(444px, calc(100vw - 20px));
  padding: max(var(--ao-pad-t), var(--ao-lab-h)) var(--ao-pad-x) var(--ao-pad-b);

  display: grid;
  grid-template-columns: repeat(5, minmax(0, 1fr));
  column-gap: var(--ao-gap-x);
  row-gap: max(var(--ao-gap-y), var(--ao-lab-h));
  align-content: start;   /* les rangees ne s'etirent pas si le panneau est haut */

  /* Le panneau se cale sur la hauteur reellement disponible au-dessus du
     composeur (`.dock-panel { bottom: 84px }`), et ne depasse jamais 560 px.
     `dvh` d'abord en repli `vh` : sur mobile la barre d'URL fait varier vh.
     `env(safe-area-inset-bottom)` est RETRANCHE parce que `.dock-panel` ajoute
     ce meme retrait a son `bottom` (style.css l.734) : sans lui, sur un
     telephone a encoche le panneau depassait par le HAUT de la hauteur de la
     barre de gestes (jusqu'a ~34 px), et son premier intitule sortait
     de l'ecran. */
  max-height: min(calc(100vh - 104px - env(safe-area-inset-bottom, 0px)), 660px);
  max-height: min(calc(100dvh - 104px - env(safe-area-inset-bottom, 0px)), 660px);

  /* Si malgre tout il faut defiler (fenetre tres basse), ca defile ici et
     nulle part ailleurs. `hidden` en X : rien n'a le droit de deborder
     lateralement, et surtout aucune barre horizontale parasite. */
  overflow: hidden auto;
  overscroll-behavior: contain;
}

/* Barre de defilement discrete et TEINTEE PAR LE THEME : l'ancienne barre
   claire du navigateur trouait le verre. `--text` melange a du transparent
   reste lisible en clair comme en sombre, sur les 7 palettes. */
#attach-menu {
  scrollbar-width: thin;
  scrollbar-color: color-mix(in srgb, var(--text) 28%, transparent) transparent;
}
#attach-menu::-webkit-scrollbar { width: 6px; }
#attach-menu::-webkit-scrollbar-track { background: transparent; }
#attach-menu::-webkit-scrollbar-thumb {
  background: color-mix(in srgb, var(--text) 26%, transparent);
  border-radius: 999px;
}
#attach-menu::-webkit-scrollbar-thumb:hover {
  background: color-mix(in srgb, var(--text) 42%, transparent);
}

/* ---------------------------------------------------------------------------
   2. LES GROUPES
   ---------------------------------------------------------------------------
   `--ao-g` porte le numero de groupe. Il sert DEUX fois : a l'ordre visuel
   (`order`) et au decalage d'entree (§8). Ce sont des selecteurs d'attribut
   et non `:nth-child` parce que « Message programme » est le 12e enfant dans
   le balisage alors qu'il appartient au groupe « Creer » : il faut pouvoir
   le remonter sans que le reste des regles se decale d'un cran. Meme raison
   pour le cas ou quelqu'un reordonnerait index.html plus tard.

   RESERVE ASSUMEE : `order` deplace « Message programme » a l'ecran mais pas
   dans l'ordre de tabulation, qui suit le DOM. Le correctif propre est de le
   remonter dans index.html — il est propose en integration ; ces regles
   restent justes une fois qu'il est applique (order devient un non-evenement).
   --------------------------------------------------------------------------- */
#attach-menu .attach-opt[data-att="file"],
#attach-menu .attach-opt[data-att="folder"],
#attach-menu .attach-opt[data-att="photo"],
#attach-menu .attach-opt[data-att="album"]     { --ao-g: 1; }   /* Envoyer */

#attach-menu .attach-opt[data-att="poll"],
#attach-menu .attach-opt[data-att="draw"],
#attach-menu .attach-opt[data-att="schedule"]  { --ao-g: 2; }   /* Creer   */

/* Groupe « Jouer ». On cible l'ATTRIBUT `data-jeu`, pose sur chaque bouton de
   jeu dans index.html, plutot que la liste de leurs identifiants : ajouter un
   jeu ne demande alors plus de revenir ici. L'intitule de groupe (§5) reste
   accroche a `tictactoe`, qui ouvre la categorie dans l'ordre du DOM. */
#attach-menu .attach-opt[data-jeu]             { --ao-g: 3; }   /* Jouer   */

#attach-menu .attach-opt[data-att="once"]      { --ao-g: 4; }   /* Options */

/* ---------------------------------------------------------------------------
   3. LES TUILES
   ---------------------------------------------------------------------------
   Le balisage n'entoure PAS le libelle d'une balise : `.attach-opt` contient
   une pastille puis un simple nœud texte. En grille, ce texte devient un
   « element de grille anonyme » : il occupe bien une cellule, mais on ne peut
   le viser en CSS. D'ou la construction retenue —
     • la 2e rangee du bouton est dimensionnee (`minmax(var(--ao-lines), auto)`)
       pour reserver deux lignes a TOUTES les tuiles : les pastilles restent
       alignees meme quand un libelle tient sur une seule ligne ;
     • tout ce qui concerne le texte (corps, interlignage, centrage, cesure)
       est pose sur le BOUTON et descend par heritage.
   `align-content: start` (et non `end`) : si un libelle deborde sur trois
   lignes, il pousse vers le BAS et les pastilles de la rangee restent alignees.
   --------------------------------------------------------------------------- */
#attach-menu .attach-opt {
  order: var(--ao-g, 1);
  position: relative;      /* repere des intitules de groupe (§4) */

  display: grid;
  grid-template-columns: minmax(0, 1fr);
  grid-template-rows: auto minmax(var(--ao-lines), auto);
  justify-items: center;
  align-content: start;
  gap: 7px 0;

  width: auto;
  padding: 7px 4px 8px;
  border: 1px solid transparent;   /* reserve le trait du survol : rien ne bouge */
  border-radius: 15px;
  background: transparent;
  color: var(--text);

  font-size: var(--ao-label);
  font-weight: 650;
  line-height: 1.18;
  letter-spacing: .1px;
  text-align: center;              /* herite par le libelle anonyme */
  /* « Pierre-papier-ciseaux » se coupe tout seul apres ses traits d'union ;
     `break-word` n'est que le filet de securite pour un mot plus large que
     la colonne, et `manual` interdit une cesure automatique qui inventerait
     des traits d'union absents du libelle. */
  overflow-wrap: break-word;
  hyphens: manual;
}

/* La pastille grandit et gagne un lisere clair en haut : elle se detache du
   verre sans recourir a une ombre noire en dur (illisible en theme clair).
   Les degrades `.grad1`..`.grad5` restent ceux de style.css, deja assombris
   par l'audit de contraste (style.css l.952-954) pour porter l'icone blanche. */
#attach-menu .ao-ic {
  width: var(--ao-ic-size);
  height: var(--ao-ic-size);
  border-radius: 15px;
  box-shadow: inset 0 1px 0 var(--glass-3);
  transition: transform var(--an-fast, 160ms) var(--an-ease, cubic-bezier(.22, 1, .36, 1));
}
/* Icone proportionnelle : une seule valeur de pastille a changer plus bas. */
#attach-menu .ao-ic .ic {
  width: 46%;
  height: 46%;
}

/* ---------------------------------------------------------------------------
   4. LES SEPARATEURS DE GROUPE
   ---------------------------------------------------------------------------
   Chaque groupe commence en colonne 1 : `grid-column-start: 1` force le
   placement automatique a passer a la rangee suivante, ce qui donne les
   quatre blocs sans une seule balise supplementaire.

   L'intitule est un `::before` ABSOLU cale sur le haut de la tuile
   (`bottom: 100%`). Hors flux, il n'ajoute aucune rangee au bouton : il se
   loge dans l'ecart de rangee (`--ao-gap-y`), donc il ne decale ni sa propre
   tuile ni ses voisines. Sa largeur `100cqw` est celle de la boite de contenu
   du panneau (§1) : le filet traverse toute la grille alors qu'il est dessine
   depuis une tuile large d'un cinquieme.
   Le texte est ecrit en casse normale et mis en capitales par le CSS, et la
   seconde declaration `content` ajoute un texte de remplacement VIDE pour que
   l'intitule ne vienne pas polluer le nom accessible du bouton (« Envoyer
   Fichier »). Les navigateurs qui ignorent cette syntaxe gardent la premiere
   declaration, donc l'intitule reste visible partout.
   --------------------------------------------------------------------------- */
#attach-menu .attach-opt[data-att="file"],
#attach-menu .attach-opt[data-att="poll"],
#attach-menu .attach-opt[data-att="tictactoe"],
#attach-menu .attach-opt[data-att="once"] {
  grid-column-start: 1;
}

#attach-menu .attach-opt[data-att="file"]::before,
#attach-menu .attach-opt[data-att="poll"]::before,
#attach-menu .attach-opt[data-att="tictactoe"]::before,
#attach-menu .attach-opt[data-att="once"]::before {
  position: absolute;
  left: 0;
  bottom: 100%;
  /* Ces trois mesures sont celles qui composent `--ao-lab-h` (§1) : elles
     doivent rester solidaires, sinon le plancher de `row-gap` ment. */
  margin-bottom: var(--ao-lab-mb);

  /* `left: 0` se mesure depuis la boite de PADDING de la tuile, qui commence
     1 px a droite de la boite de contenu du panneau (la tuile porte un
     `border: 1px` transparent, §3). Sans ce retrait le filet debordait d'un
     pixel a droite — rogne par l'`overflow-x: hidden` du panneau. */
  margin-left: -1px;
  width: 100%;        /* repli si les unites de conteneur manquent */
  width: 100cqw;
  padding-bottom: var(--ao-lab-pb);
  border-bottom: 1px solid var(--ao-line);

  font-size: var(--ao-lab-fs);
  font-weight: 800;
  line-height: 1.05;
  letter-spacing: .12em;
  text-transform: uppercase;
  text-align: left;
  color: var(--muted);
  pointer-events: none;   /* un intitule ne se clique pas */
}

#attach-menu .attach-opt[data-att="file"]::before      { content: "Envoyer";  content: "Envoyer" / ""; }
#attach-menu .attach-opt[data-att="poll"]::before      { content: "Créer";    content: "Créer" / ""; }
#attach-menu .attach-opt[data-att="tictactoe"]::before { content: "Jouer";    content: "Jouer" / ""; }
#attach-menu .attach-opt[data-att="once"]::before      { content: "Options";  content: "Options" / ""; }

/* ---------------------------------------------------------------------------
   5. SURVOL, APPUI, FOCUS CLAVIER
   ---------------------------------------------------------------------------
   `:active` est REDECLARE ici alors que style.css (l.831-836) et anim.css
   (l.814-820) le font deja : les deux fichiers sont charges AVANT nous, donc
   notre `:hover` — de meme specificite qu'eux mais plus tardif — gagnerait
   au moment de l'appui et la tuile ne s'enfoncerait plus. On reprend donc le
   meme geste (scale .94) a une specificite superieure pour rester coherent
   avec le reste de l'application.
   Seuls `transform` et `opacity` sont animes ; `background` et `border-color`
   changent en transition (pur dessin, aucun recalcul de mise en page).
   --------------------------------------------------------------------------- */
#attach-menu .attach-opt {
  cursor: pointer;
  transition:
    transform var(--an-fast, 160ms) var(--an-ease, cubic-bezier(.22, 1, .36, 1)),
    background-color var(--an-fast, 160ms) linear,
    border-color var(--an-fast, 160ms) linear;
}
#attach-menu .attach-opt:hover {
  background: var(--glass-2);
  border-color: var(--ao-line);   /* pas `--border` : blanc invisible en clair */
  transform: translateY(-2px);
}
#attach-menu .attach-opt:hover .ao-ic { transform: scale(1.06); }

#attach-menu .attach-opt:active {
  transform: scale(.94);
  transition-duration: 60ms;
}

/* Anneau de focus : `outline` (et non un `box-shadow`) pour rester visible en
   mode contraste force. `offset` negatif pour ne pas mordre sur les voisines. */
#attach-menu .attach-opt:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: -1px;
  background: var(--glass-2);
  border-color: transparent;
}

/* Sur ecran tactile `:hover` reste « colle » apres un appui : on le neutralise,
   l'enfoncement suffit comme retour. */
@media (hover: none) {
  #attach-menu .attach-opt:hover {
    background: transparent;
    border-color: transparent;
    transform: none;
  }
  #attach-menu .attach-opt:hover .ao-ic { transform: none; }
}

/* ---------------------------------------------------------------------------
   6. « VUE UNIQUE » — un INTERRUPTEUR, pas une option
   ---------------------------------------------------------------------------
   C'est la seule entree du menu qui ne declenche rien : elle arme un etat
   (`state.once`) que `updateOnceState()` reflete dans le temoin `.pill`
   (`off` / `ON` + classe `.on`). La faire ressembler aux douze autres tuiles
   etait trompeur. Elle devient donc une barre pleine largeur, posee sur une
   surface creusee, avec le temoin a droite : la forme dit « reglage ».

   Le bouton contient pastille + texte + temoin ; en trois colonnes le
   placement automatique les range exactement dans cet ordre.
   --------------------------------------------------------------------------- */
#attach-menu .attach-opt[data-att="once"] {
  --ao-ic-size: 32px;

  grid-column: 1 / -1;
  grid-template-columns: auto minmax(0, 1fr) auto;
  grid-template-rows: auto;
  align-items: center;
  justify-items: start;
  gap: 0 11px;

  margin-top: 2px;
  padding: 8px 11px;
  border-color: var(--ao-line);   /* pas `--border` : blanc invisible en clair */
  border-radius: 16px;
  background: var(--sunken);

  font-size: .82rem;
  font-weight: 600;
  text-align: left;
}
#attach-menu .attach-opt[data-att="once"] .ao-ic { border-radius: 11px; }
#attach-menu .attach-opt[data-att="once"]:hover {
  background: var(--sunken-2);
  transform: none;          /* une barre de reglage ne se souleve pas */
}

/* Le temoin : une piste arrondie et une bille. En position « off » la bille
   est a gauche et le libelle a droite ; `order` les permute quand `.on`
   arrive, la bille passe donc a droite — la lecture est celle d'un
   interrupteur. `space-between` maintient la bille collee au bord, sinon
   l'echange de deux mots de largeurs differentes la ferait flotter. */
#attach-menu .attach-opt[data-att="once"] .pill {
  margin: 0;
  display: inline-flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  min-width: 58px;

  padding: 3px 7px;
  border: 1px solid var(--ao-line);   /* pas `--border` : blanc invisible en clair */
  border-radius: 999px;
  background: var(--sunken-2);
  color: var(--muted);

  font-size: .58rem;
  font-weight: 800;
  line-height: 1.6;
  letter-spacing: .1em;
  text-transform: uppercase;
  transition:
    background-color var(--an-fast, 160ms) linear,
    color var(--an-fast, 160ms) linear,
    border-color var(--an-fast, 160ms) linear;
}
#attach-menu .attach-opt[data-att="once"] .pill::before {
  content: "";
  flex: none;
  width: 13px;
  height: 13px;
  border-radius: 50%;
  background: currentColor;   /* suit la couleur du temoin, donc du theme */
  opacity: .5;
}

/* Etat arme. `--degrade-txt` est la variante assombrie de l'accent, celle qui
   est faite pour porter du texte blanc (style.css l.939-942) : le vif
   `linear-gradient(--accent, --accent-2)` de `.pill.on` tombait sous 2:1 sur
   les palettes claires. `--sur-accent` est declare sur `:root` (style.css
   l.941) et n'existe que pour ca — donc pas de repli en dur ici. */
#attach-menu .attach-opt[data-att="once"] .pill.on {
  background: var(--degrade-txt);
  border-color: transparent;
  color: var(--sur-accent);
}
#attach-menu .attach-opt[data-att="once"] .pill.on::before {
  order: 1;      /* la bille passe a droite : geste d'interrupteur */
  opacity: 1;
}

/* La barre entiere signale l'etat arme, pas seulement le temoin : sans cela,
   une pastille de 58 px etait le seul indice que le prochain media partira en
   vue unique. `:has()` est une amelioration — la ou il manque, le temoin
   reste parfaitement explicite. */
#attach-menu .attach-opt[data-att="once"]:has(.pill.on) {
  background: color-mix(in srgb, var(--accent) 14%, var(--sunken));
  border-color: color-mix(in srgb, var(--accent) 45%, transparent);
}
#attach-menu .attach-opt[data-att="once"]:has(.pill.on):hover {
  background: color-mix(in srgb, var(--accent) 20%, var(--sunken));
}

/* Meme raison qu'au §5, mais il faut le redire APRES : les regles de la barre
   sont plus specifiques que le `@media (hover: none)` generique ecrit plus
   haut, donc sans ce rappel un survol « colle » sur mobile laissait la barre
   dans son fond de survol — et l'etat arme devenait illisible. */
@media (hover: none) {
  #attach-menu .attach-opt[data-att="once"]:hover { background: var(--sunken); }
  #attach-menu .attach-opt[data-att="once"]:has(.pill.on):hover {
    background: color-mix(in srgb, var(--accent) 14%, var(--sunken));
  }
}

/* ---------------------------------------------------------------------------
   7. CAS ETROIT
   ---------------------------------------------------------------------------
   Sous 420 px le panneau passe en pleine largeur moins les marges : cinq
   colonnes donneraient des libelles de six caracteres. On descend a quatre.
   La grille se REORGANISE, elle ne deborde pas : « Jouer » passe simplement
   sur deux rangees et l'ensemble tient toujours dans les 560 px du panneau
   (mesure : 510 px sur un 360x740, 475 px sur un 320x700).
   On ne descend PAS a trois colonnes sous 340 px : trois colonnes coutent
   deux rangees de plus que quatre — le menu se mettait alors a defiler, ce
   qui est exactement le defaut qu'on corrige. On resserre le gabarit a la
   place, les colonnes restent quatre.
   --------------------------------------------------------------------------- */
@media (max-width: 420px) {
  #attach-menu {
    --ao-ic-size: 38px;
    --ao-gap-y: 20px;
    --ao-label: .625rem;
    --ao-pad-x: 10px;
    --ao-pad-t: 28px;
    --ao-lab-mb: 5px;
    width: calc(100vw - 16px);
    grid-template-columns: repeat(4, minmax(0, 1fr));
  }
  #attach-menu .attach-opt { padding: 6px 3px 7px; }
}
@media (max-width: 340px) {
  #attach-menu {
    --ao-ic-size: 34px;
    --ao-gap-y: 19px;
    --ao-label: .58rem;
    --ao-pad-t: 27px;
    --ao-lab-fs: .55rem;
    --ao-lab-pb: 2px;
    --ao-lab-mb: 5px;
  }
  #attach-menu .attach-opt { padding: 5px 2px 6px; gap: 6px 0; }
  #attach-menu .attach-opt[data-att="once"] { --ao-ic-size: 26px; font-size: .78rem; }
}

/* ---------------------------------------------------------------------------
   8. CAS DE LA FENETRE BASSE
   ---------------------------------------------------------------------------
   `.dock-panel` remonte a `bottom: 78px` sous 620 px de haut (style.css
   l.739-741) : la hauteur disponible est recalculee en consequence. On
   resserre la grille par TROIS paliers plutot que de laisser apparaitre une
   barre de defilement. Les valeurs ne sont pas au juge : elles ont ete
   reglees en mesurant la grille rendue a chaque palier contre la hauteur
   reellement disponible. Mesures (hauteur de grille / hauteur permise) —
   761 px de fenetre : 454 / 560 · 620 : 375 / 528 · 530 : 375 / 438 ·
   470 : 330 / 378 · 390 (telephone en paysage) : 278 / 298. Aucun
   defilement nulle part. En deca, la barre discrete et teintee du §1
   prend le relais.
   --------------------------------------------------------------------------- */
@media (max-height: 620px) {
  #attach-menu {
    --ao-ic-size: 36px;
    --ao-gap-y: 19px;
    --ao-label: .62rem;
    --ao-pad-t: 27px;
    --ao-pad-b: 10px;
    --ao-lab-fs: .55rem;
    --ao-lab-pb: 2px;
    --ao-lab-mb: 5px;
    max-height: calc(100vh - 92px - env(safe-area-inset-bottom, 0px));
    max-height: calc(100dvh - 92px - env(safe-area-inset-bottom, 0px));
  }
  #attach-menu .attach-opt { padding: 5px 3px 6px; gap: 6px 0; }
  #attach-menu .attach-opt[data-att="once"] { --ao-ic-size: 28px; padding: 6px 10px; }
}
@media (max-height: 520px) {
  #attach-menu {
    --ao-ic-size: 32px;
    --ao-gap-y: 16px;
    --ao-lines: 2.25em;
    --ao-label: .6rem;
    --ao-pad-t: 24px;
    --ao-pad-b: 9px;
    --ao-lab-fs: .53rem;
    --ao-lab-pb: 2px;
    --ao-lab-mb: 4px;
  }
  #attach-menu .ao-ic { border-radius: 12px; }
  #attach-menu .attach-opt { padding: 4px 3px 5px; gap: 5px 0; }
  #attach-menu .attach-opt[data-att="once"] {
    --ao-ic-size: 24px;
    margin-top: 0;
    padding: 5px 9px;
    font-size: .78rem;
  }
}
@media (max-height: 430px) {
  #attach-menu {
    --ao-ic-size: 26px;
    --ao-gap-y: 13px;
    --ao-lines: 2.2em;
    --ao-label: .56rem;
    --ao-pad-t: 21px;
    --ao-pad-b: 6px;
    --ao-lab-fs: .5rem;
    --ao-lab-pb: 1px;
    --ao-lab-mb: 2px;
  }
  #attach-menu .ao-ic { border-radius: 9px; }
  #attach-menu .attach-opt { padding: 3px 2px 4px; gap: 4px 0; }
  #attach-menu .attach-opt[data-att="once"] {
    --ao-ic-size: 20px;
    padding: 4px 8px;
    font-size: .72rem;
  }
  #attach-menu .attach-opt[data-att="once"] .pill { min-width: 52px; line-height: 1.4; }
  #attach-menu .attach-opt[data-att="once"] .pill::before { width: 11px; height: 11px; }
}

/* ETROIT **ET** BAS — les deux contraintes se cumulent et les paliers
   ci-dessus ne suffisent plus. La raison est arithmetique : sous 420 px on
   passe a quatre colonnes (§7), donc « Jouer » occupe DEUX rangees au lieu
   d'une — quatre rangees de tuiles a caser au lieu de trois, alors que les
   paliers de hauteur sont calibres sur cinq colonnes. Un cran de plus est
   donc necessaire, sans quoi un telephone dont le clavier virtuel vient de
   s'ouvrir se remettait a defiler.
   Mesures : 320x480 -> 376 pour 388 · 414x420 -> 304 pour 328 ·
   280x420 -> 318 pour 328. Toujours aucun defilement. */
@media (max-width: 420px) and (max-height: 500px) {
  #attach-menu {
    --ao-ic-size: 26px;
    --ao-gap-y: 15px;
    --ao-lines: 2.2em;
    --ao-label: .57rem;
    --ao-pad-t: 22px;
    --ao-pad-b: 8px;
    --ao-lab-fs: .52rem;
    --ao-lab-pb: 2px;
    --ao-lab-mb: 3px;
  }
  #attach-menu .ao-ic { border-radius: 9px; }
  #attach-menu .attach-opt { padding: 3px 2px 4px; gap: 4px 0; }
  #attach-menu .attach-opt[data-att="once"] {
    --ao-ic-size: 22px;
    margin-top: 0;
    padding: 5px 9px;
    font-size: .76rem;
  }
}
@media (max-width: 420px) and (max-height: 450px) {
  #attach-menu {
    --ao-ic-size: 22px;
    --ao-gap-y: 11px;
    --ao-lines: 2.15em;
    --ao-label: .54rem;
    --ao-pad-t: 19px;
    --ao-pad-b: 5px;
    --ao-lab-fs: .48rem;
    --ao-lab-pb: 1px;
    --ao-lab-mb: 1px;
  }
  #attach-menu .attach-opt { padding: 2px 2px 3px; gap: 3px 0; }
  #attach-menu .attach-opt[data-att="once"] {
    --ao-ic-size: 20px;
    padding: 4px 8px;
    font-size: .7rem;
  }
  #attach-menu .attach-opt[data-att="once"] .pill { min-width: 50px; line-height: 1.35; }
  #attach-menu .attach-opt[data-att="once"] .pill::before { width: 10px; height: 10px; }
}

/* ---------------------------------------------------------------------------
   9. ENTREE EN SCENE
   ---------------------------------------------------------------------------
   anim.css (l.912-920) fait glisser chaque LIGNE depuis la gauche avec un
   decalage par `:nth-child`. Sur une grille ce glissement lateral n'a plus de
   sens, et les rangs `:nth-child` ne correspondent plus a l'ordre visuel
   depuis que `order` regroupe les options. On remplace donc par une montee
   tres courte, cadencee PAR GROUPE : quatre vagues, une par bloc, dans
   l'ordre ou l'œil les lit.
   `backwards` et surtout pas `both` — un remplissage vers l'avant figerait
   `transform` sur chaque tuile et l'enfoncement a l'appui (§5) ne
   s'appliquerait plus jamais, l'origine « animation » battant toute
   declaration d'auteur. C'est exactement le piege documente dans anim.css.
   Seuls `opacity` et `transform` bougent.

   La regle `.closing` qui suit n'est PAS cosmetique. `fermerAnime()`
   (app.js l.1170-1184) pose `.closing` puis attend UN evenement `animationend`
   SUR LE PANNEAU pour reposer `.hidden` — or `animationend` REMONTE. Si
   l'utilisateur referme le menu pendant les ~240 ms d'entree des tuiles, c'est
   la fin d'animation d'une TUILE qui remonte et declenche la fermeture,
   coupant net le glissement de sortie du panneau (`.dock-panel.closing`,
   anim.css l.893). On coupe donc l'animation des tuiles des l'arrivee de
   `.closing` : elle est ANNULEE, elle emet `animationcancel` — qui n'est pas
   ecoute — et seule la sortie du panneau conclut la fermeture.

   Il faut bien ecrire `animation: none` et NON retirer la regle par un
   `:not(.closing)` : `.closing` n'est pas `.hidden`, donc la regle d'anim.css
   (l.912, `.attach-menu:not(.hidden) .attach-opt { animation: an-right }`)
   reprendrait aussitot la main et relancerait une animation — verifie au
   navigateur, elle emettait bien `animationend` sur la tuile « once ».
   --------------------------------------------------------------------------- */
@keyframes ao-in {
  from { opacity: 0; transform: translateY(9px) scale(.94); }
}
#attach-menu:not(.hidden) .attach-opt {
  animation: ao-in var(--an-fast, 160ms) var(--an-ease, cubic-bezier(.22, 1, .36, 1)) backwards;
  animation-delay: calc(var(--an-step, 26ms) * (var(--ao-g, 1) - 1));
}
/* Meme specificite que la regle ci-dessus, ecrite APRES : elle l'emporte, et
   l'identifiant lui fait battre celle d'anim.css. */
#attach-menu.closing .attach-opt { animation: none; }

/* ---------------------------------------------------------------------------
   10. MOUVEMENT REDUIT
   ---------------------------------------------------------------------------
   anim.css neutralise `.attach-opt:active` mais pas la cascade d'entree du
   menu ni le soulevement au survol : on complete ici. Tout reste LISIBLE,
   plus rien ne bouge. Les changements de fond, eux, sont conserves : ce sont
   des etats, pas des mouvements.
   --------------------------------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
  /* Meme specificite que le §9, mais ecrit APRES : c'est bien cette regle-ci
     qui l'emporte et l'entree en scene est neutralisee. */
  #attach-menu:not(.hidden) .attach-opt {
    animation: none;
    opacity: 1;
    transform: none;
  }
  #attach-menu .attach-opt,
  #attach-menu .ao-ic,
  #attach-menu .attach-opt[data-att="once"] .pill { transition: none; }

  #attach-menu .attach-opt:hover,
  #attach-menu .attach-opt:active,
  #attach-menu .attach-opt:focus-visible { transform: none; }
  #attach-menu .attach-opt:hover .ao-ic { transform: none; }
}
