/* ==========================================================================
   home.css — Tableau de bord d'accueil (grille bento)
   Dépend de theme.css pour les tokens et le reset.
   ========================================================================== */

.home-body { position: relative; overflow-x: hidden; }

/* --------------------------------------------------------------
   Pas de curseur texte sur le tableau de bord

   Rien ici n'est fait pour être sélectionné : ce sont des tuiles, pas un
   document. Sans ces règles, survoler un titre affiche la barre en I,
   un clic laisse un curseur clignotant et un double-clic surligne le texte
   en bleu — sur une tablette accrochée au mur, ça passe pour une panne.
   -------------------------------------------------------------- */
.home-body {
  cursor: default;
  caret-color: transparent;
  -webkit-user-select: none;
  user-select: none;
  -webkit-tap-highlight-color: transparent;   /* pas de flash gris au doigt */
}

/* Les tuiles cliquables gardent la main, elles. */
.home-body a[href] { cursor: pointer; }

/* Seuls les champs du panneau de réglages redeviennent de vrais champs :
   recherche de ville, cases à cocher, listes déroulantes. */
.home-body input,
.home-body textarea,
.home-body select {
  cursor: auto;
  caret-color: auto;
  -webkit-user-select: text;
  user-select: text;
}

.home-body input[type="checkbox"],
.home-body input[type="radio"],
.home-body select { cursor: pointer; }

/* --------------------------------------------------------------
   Fond : trois halos colorés en dérive lente
   -------------------------------------------------------------- */
.aurora {
  position: fixed;
  inset: 0;
  z-index: -1;
  overflow: hidden;
  pointer-events: none;
}

.aurora-blob {
  position: absolute;
  border-radius: 50%;
  filter: blur(90px);
  opacity: .38;
  will-change: transform;
}

.a1 {
  width: 520px; height: 520px;
  top: -160px; left: -120px;
  background: #3b82f6;
  animation: drift-a 26s ease-in-out infinite alternate;
}

.a2 {
  width: 460px; height: 460px;
  top: -80px; right: -140px;
  background: #a855f7;
  animation: drift-b 32s ease-in-out infinite alternate;
}

.a3 {
  width: 400px; height: 400px;
  top: 340px; left: 42%;
  background: #10b981;
  opacity: .22;
  animation: drift-c 38s ease-in-out infinite alternate;
}

@keyframes drift-a { to { transform: translate(90px, 70px) scale(1.12); } }
@keyframes drift-b { to { transform: translate(-70px, 110px) scale(1.08); } }
@keyframes drift-c { to { transform: translate(60px, -80px) scale(1.15); } }

:root[data-theme="dark"] .aurora-blob { opacity: .26; }
:root[data-theme="dark"] .a3 { opacity: .16; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .aurora-blob { opacity: .26; }
  :root:not([data-theme="light"]) .a3 { opacity: .16; }
}

/* --------------------------------------------------------------
   En-tête éditorial
   -------------------------------------------------------------- */
.home {
  max-width: 1040px;
  margin: 0 auto;
  padding: 52px 20px 80px;
}

.hero { margin-bottom: 38px; }

.hero-line {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 14px;
}

.hero-dot {
  width: 7px; height: 7px;
  border-radius: 50%;
  background: var(--success);
  box-shadow: 0 0 0 4px color-mix(in srgb, var(--success) 20%, transparent);
  animation: pulse-dot 2.4s ease-in-out infinite;
}

@keyframes pulse-dot {
  50% { box-shadow: 0 0 0 7px color-mix(in srgb, var(--success) 6%, transparent); }
}

.hero-date {
  font-size: .78rem;
  font-weight: 620;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--text-3);
}

.hero-title {
  margin: 0 0 10px;
  font-size: clamp(2.2rem, 6vw, 3.4rem);
  font-weight: 700;
  letter-spacing: -0.05em;
  line-height: 1.02;
}

.hero-accent {
  background: linear-gradient(115deg, var(--primary), #a855f7 60%, #10b981);
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
}

.hero-sub {
  margin: 0;
  max-width: 520px;
  font-size: .98rem;
  color: var(--text-2);
}

/* --------------------------------------------------------------
   Grille bento
   -------------------------------------------------------------- */
/* 12 colonnes : c'est ce qui donne de la finesse au redimensionnement.
   Les rangées ont une hauteur fixe pour que la hauteur des tuiles soit
   pilotable — `auto` rendrait --tile-h sans effet visible. */
.bento {
  display: grid;
  /* Le nombre de colonnes est réglable par disposition (home.js pose
     --bento-cols en ligne). Sans réglage, on garde 12 : c'est ce qui donne
     la finesse au redimensionnement des tuiles.
     minmax(0, 1fr) et non 1fr : sinon un contenu large (titre long, affiche)
     impose sa largeur minimale à la colonne et fait déborder la grille. */
  grid-template-columns: repeat(var(--bento-cols, 12), minmax(0, 1fr));
  grid-auto-rows: 78px;
  gap: 14px;
}

/* Repère de colonnes pendant l'édition — un simple fond visuel, pas une
   grille pixel-parfaite (il ignore le fossé entre colonnes). Sert de papier
   quadrillé pour « dessiner » les emplacements. */
body.is-editing .bento {
  background-image: repeating-linear-gradient(
    to right,
    color-mix(in srgb, var(--text) 10%, transparent) 0,
    color-mix(in srgb, var(--text) 10%, transparent) 1px,
    transparent 1px,
    transparent calc(100% / var(--bento-cols, 12))
  );
}

.tile {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 14px;
  min-width: 0;
  padding: 20px;
  color: inherit;
  overflow: hidden;
  border: 1px solid var(--border);
  border-radius: var(--r-xl);
  background: color-mix(in srgb, var(--surface) 72%, transparent);
  backdrop-filter: blur(16px) saturate(150%);
  -webkit-backdrop-filter: blur(16px) saturate(150%);
  box-shadow: var(--shadow-sm);
  transition: transform .28s var(--ease), box-shadow .28s var(--ease), border-color .28s var(--ease);
}

/* Voile coloré propre à chaque app */
.tile::after {
  content: '';
  position: absolute;
  inset: 0;
  background: radial-gradient(120% 90% at 100% 0%, color-mix(in srgb, var(--accent) 16%, transparent), transparent 62%);
  opacity: .55;
  pointer-events: none;
  transition: opacity .28s var(--ease);
}

.tile:hover {
  transform: translateY(-4px);
  border-color: color-mix(in srgb, var(--accent) 42%, transparent);
  box-shadow: 0 20px 44px color-mix(in srgb, var(--accent) 18%, transparent), var(--shadow-md);
}

.tile:hover::after { opacity: 1; }

/* Position ET taille pilotées par home.js — plus un simple empaquetage
   automatique par ordre : chaque tuile occupe un rectangle précis de la
   grille (--tile-x/y = coin haut-gauche, --tile-w/h = largeur/hauteur en
   cellules), posé une fois pour toutes par layoutTiles(). C'est ce qui
   permet de laisser un trou volontaire à côté d'une tuile haute au lieu de
   le voir comblé par la suivante. */
.tile {
  grid-column: calc(var(--tile-x, 0) + 1) / span var(--tile-w, 3);
  grid-row: calc(var(--tile-y, 0) + 1) / span var(--tile-h, 2);
  min-height: 0;

  /* Chaque tuile devient un conteneur de requête : son contenu s'adapte à
     SA taille, pas à celle de la fenêtre. C'est ce qui permet à une tuile
     rétrécie de se réorganiser au lieu de laisser déborder ses infos.
     `size` (et pas `inline-size`) car on a besoin de la hauteur : la tuile
     la tient de la grille, son contenu ne la détermine pas. */
  container: tile / size;
}

/* Sans ça, un enfant flex refuse de descendre sous sa hauteur de contenu
   et pousse tout le reste hors du cadre. */
.tile > * { min-height: 0; min-width: 0; }

/* --- En-tête de tuile --- */
.tile-head {
  display: flex;
  align-items: center;
  gap: 10px;
  position: relative;
  z-index: 1;
}

.tile-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 38px; height: 38px;
  flex-shrink: 0;
  border-radius: var(--r-sm);
  background: color-mix(in srgb, var(--accent) 16%, transparent);
  font-size: 1.15rem;
  line-height: 1;
  transition: transform .28s var(--ease);
}

.tile:hover .tile-icon { transform: scale(1.08) rotate(-4deg); }

.tile-name {
  font-size: .95rem;
  font-weight: 650;
  letter-spacing: -0.02em;
}

.tile-tag {
  margin-left: auto;
  padding: 3px 9px;
  border-radius: var(--r-full);
  background: color-mix(in srgb, var(--accent) 18%, transparent);
  color: var(--accent);
  font-size: .64rem;
  font-weight: 700;
  letter-spacing: .05em;
  text-transform: uppercase;
}

/* --- Chiffre principal --- */
.tile-metric {
  display: flex;
  align-items: baseline;
  gap: 8px;
  margin-top: auto;
  position: relative;
  z-index: 1;
}

.tile-value {
  font-size: clamp(2rem, 5vw, 3rem);
  font-weight: 700;
  letter-spacing: -0.045em;
  line-height: 1;
  font-variant-numeric: tabular-nums;
  color: var(--text);
}

.tile-value.sm { font-size: 1.5rem; }

.tile-unit {
  font-size: .8rem;
  font-weight: 560;
  color: var(--text-3);
}

.tile-foot {
  font-size: .81rem;
  line-height: 1.45;
  color: var(--text-2);
  position: relative;
  z-index: 1;
}

.tile-foot.grow { margin-top: auto; }

/* --- Podomètre, fusionné dans la tuile Sport --- une ligne "Moi", une ligne
   "ami" (masquée sans ami choisi en réglages ⚙️ → Podomètre). Chaque ligne :
   étiquette au-dessus, chiffre centré (ex. "156 / 10 000") en dessous, barre
   pleine largeur tout en bas. Empilé en colonne plutôt qu'étiquette+chiffre
   côte à côte : sur mobile (tuile étroite), un `flex:1` à côté d'une
   étiquette de largeur variable ("Moi" vs un pseudo plus long) centre le
   chiffre dans l'espace RESTANT après l'étiquette, pas dans la ligne entière
   — lu comme "pas centré du tout" une fois l'étiquette assez large. Empiler
   centre chaque ligne indépendamment, donc toujours vraiment au milieu. */
.pedo-block {
  display: flex;
  flex-direction: column;
  flex-shrink: 0;
  gap: clamp(4px, 1.6cqh, 10px);
  margin-top: clamp(4px, 1.4cqh, 10px);
  position: relative;
  z-index: 1;
}
.pedo-row {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
}
.pedo-row.hidden { display: none; }
.pedo-row__label {
  font-size: .72rem;
  font-weight: 700;
  letter-spacing: .02em;
  color: var(--text-3);
  text-transform: uppercase;
}
.pedo-row__count {
  text-align: center;
  font-size: clamp(.95rem, 3.6cqh, 1.3rem);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  color: var(--text);
}
.pedo-row__goal {
  margin-left: 4px;
  font-size: .72em;
  font-weight: 560;
  color: var(--text-3);
}
.pedo-row__fresh {
  text-align: center;
  font-size: .66rem;
  color: var(--text-3);
}
.pedo-row__fresh:empty { display: none; }

/* clamp() en cqh : reste fine à hauteur de tuile 1, s'épaissit un peu à
   hauteur 2, sans logique JS par palier (cf. .tile { container: tile / size }
   plus haut, même mécanisme que le reste de la tuile). */
.pedo-bar {
  width: 100%;
  height: clamp(9px, 5.5cqh, 16px);
  margin: clamp(2px, 1cqh, 6px) 0 0;
  border-radius: var(--r-full);
  background: color-mix(in srgb, var(--accent) 14%, transparent);
  border: 1px solid color-mix(in srgb, var(--accent) 28%, transparent);
  overflow: hidden;
  position: relative;
  z-index: 1;
  flex-shrink: 0;
}
.pedo-bar.hidden { display: none; }
.pedo-bar__fill {
  height: 100%;
  width: 0%;
  min-width: 2px;
  border-radius: var(--r-full);
  background: linear-gradient(90deg, color-mix(in srgb, var(--accent) 55%, transparent), var(--accent));
  box-shadow: 0 0 10px color-mix(in srgb, var(--accent) 60%, transparent);
  transition: width 1s var(--ease);
  position: relative;
  overflow: hidden;
}
/* Filet lumineux qui glisse en continu sur le remplissage — le "animé et
   moderne" demandé, sans dépendance : un dégradé translucide qui traverse
   la barre en boucle. */
.pedo-bar__fill::after {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(90deg, transparent, color-mix(in srgb, white 55%, transparent), transparent);
  transform: translateX(-100%);
  animation: pedo-shimmer 2.4s ease-in-out infinite;
}
.pedo-bar__fill.is-full {
  background: linear-gradient(90deg, var(--accent), color-mix(in srgb, var(--accent) 60%, #22c55e));
}
@keyframes pedo-shimmer {
  0%   { transform: translateX(-100%); }
  55%, 100% { transform: translateX(220%); }
}
@media (prefers-reduced-motion: reduce) {
  .pedo-bar__fill::after { animation: none; }
}

.tile-arrow {
  position: absolute;
  right: 20px; bottom: 18px;
  z-index: 1;
  font-size: 1rem;
  color: var(--accent);
  opacity: 0;
  transform: translateX(-6px);
  transition: opacity .28s var(--ease), transform .28s var(--ease);
}

.tile:hover .tile-arrow { opacity: 1; transform: none; }

/* --- Prochaine sortie --- */
.tile-next {
  display: flex;
  flex-direction: column;
  gap: 5px;
  margin-top: auto;
  position: relative;
  z-index: 1;
}

.tile-next-label {
  font-size: .7rem;
  font-weight: 660;
  letter-spacing: .09em;
  text-transform: uppercase;
  color: var(--accent);
}

.tile-next-title {
  font-size: 1.35rem;
  font-weight: 680;
  letter-spacing: -0.032em;
  line-height: 1.18;
  color: var(--text);
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.tile-next-when { font-size: .84rem; color: var(--text-2); }

/* --- Barres de dépenses (12 mois) --- */
.tile-spark {
  display: flex;
  align-items: flex-end;
  gap: 4px;
  height: 46px;
  position: relative;
  z-index: 1;
}

.spark-bar {
  flex: 1;
  min-height: 3px;
  border-radius: 3px 3px 0 0;
  background: color-mix(in srgb, var(--accent) 26%, transparent);
  transition: height .6s var(--ease), background .3s var(--ease);
}

.spark-bar.now { background: var(--accent); }

/* --- Anneau calories --- */
.tile-ring-wrap {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-top: auto;
  position: relative;
  z-index: 1;
}

.tile-ring { width: 54px; height: 54px; flex-shrink: 0; transform: rotate(-90deg); }

.ring-bg, .ring-fill {
  fill: none;
  stroke-width: 4.5;
  stroke-linecap: round;
}

.ring-bg { stroke: color-mix(in srgb, var(--accent) 16%, transparent); }

.ring-fill {
  stroke: var(--accent);
  stroke-dasharray: 113;
  stroke-dashoffset: 113;
  transition: stroke-dashoffset .9s var(--ease);
}

/* --- Tuile Sport : HUD duo (groupe mis en avant, réglages ⚙️ → Sport) ----
   Remplace ENTIÈREMENT le HUD perso (`.sport-hud__solo`) dès qu'un groupe est actif — demande
   explicite : "il ne devrait y avoir QUE le dual screen [...] et pas juste ma barre perso à moi".
   Chaque colonne reprend le même niveau d'information que la vue solo (niveau, XP, 3 anneaux),
   juste à une échelle réduite pour tenir à deux de front. `#sportSolo`/`#sportDuo` sont pilotés en
   classList('hidden') par home.js (setSportDuoActif) — jamais tous les deux visibles à la fois.
   Grid plutôt que flex : avec flex, `flex:1` par colonne pouvait se faire comprimer/déborder hors
   du cadre (`.tile{overflow:hidden}`) selon la largeur réelle de la tuile, donnant l'impression
   qu'un seul membre du duo s'affichait (bug corrigé passe 17, à ne pas réintroduire ici). */
.sport-hud__duo {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
  gap: 10px;
  position: relative;
  z-index: 1;
  flex: 1 0 auto;
  /* Sans ça, `#pedoBlock` juste en dessous (passe podomètre fusionné) lui
     dispute l'espace via flex-shrink et l'écrase à quelques px de haut — les
     cartes duo, elles, gardent leur hauteur naturelle et débordent par-dessus
     ce qui vient après (chevauchement visuel constaté en prod). `flex-shrink:0`
     force le calcul à se baser sur la hauteur réelle du contenu (1 ou 2
     colonnes selon la largeur de tuile) plutôt qu'un chiffre fixe devine ;
     si ça dépasse quand même, `.tile{overflow:hidden}` coupe proprement en
     bas plutôt que de superposer. */
  min-height: 145px;
}
.sport-hud__duo-col {
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 10px;
  border-radius: var(--r-sm);
  background: color-mix(in srgb, var(--accent) 10%, transparent);
  border: 1px solid color-mix(in srgb, var(--accent) 22%, transparent);
}
.sport-hud__duo-col.is-me { border-color: var(--accent); }
/* Couleur de thème personnelle du joueur (Profil Sport > Thème, republiée via cacheStats.couleurAccent) */
.sport-hud__duo-col[style*="--accent-membre"] { border-color: var(--accent-membre); background: color-mix(in srgb, var(--accent-membre) 10%, transparent); }
.sport-hud__duo-col[style*="--accent-membre"] .sport-hud__duo-niveau { color: var(--accent-membre); }
.sport-hud__duo-head { display: flex; align-items: center; gap: 8px; min-width: 0; }
.sport-hud__duo-head-info { min-width: 0; display: flex; flex-direction: column; gap: 1px; }
/* Enveloppe positionnée : sert d'ancre à .rank-medal (composant générique, voir plus haut), qui
   se pose en coin de l'avatar sans avoir besoin de connaître sa taille exacte. */
.sport-hud__duo-avatar-wrap { position: relative; flex-shrink: 0; }
.sport-hud__duo-avatar {
  width: 28px; height: 28px; border-radius: 50%; flex-shrink: 0;
  background: var(--surface-2) center/cover no-repeat;
  border: 1px solid var(--border);
  display: flex; align-items: center; justify-content: center;
  font-size: .85rem;
}
.sport-hud__duo-avatar--vide { color: var(--text-3); }
.sport-hud__duo-nom { font-size: .76rem; font-weight: 700; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.sport-hud__duo-niveau { font-size: .82rem; font-weight: 800; color: var(--accent); }
.sport-hud__xpbar--mini { height: 5px; }
.sport-hud__duo-rings { display: flex; gap: 4px; }
.sport-hud__duo-rings .sport-hud__ring { gap: 0; }
.sport-hud__duo-rings .sport-hud__ring-svgwrap { width: 34px; height: 34px; }
.sport-hud__duo-rings .sport-hud__ring-icon { font-size: .72rem; }
.sport-hud__duo-values {
  display: flex; gap: 4px;
  font-size: .58rem; font-weight: 700; color: var(--text-3);
  font-variant-numeric: tabular-nums;
}
/* Une valeur par anneau, même largeur/mêmes marges que .sport-hud__duo-rings juste au-dessus —
   centrée sous son icône plutôt qu'étalée sur toute la largeur (space-between les envoyait aux
   extrémités, désaligné avec les 3 anneaux). */
.sport-hud__duo-values span { flex: 1; min-width: 0; text-align: center; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
@container tile (max-width: 170px) { .sport-hud__duo-values { display: none; } }
@container tile (max-height: 150px) { .sport-hud__duo-rings { display: none; } }

/* ==========================================================================
   COMPOSANTS RÉUTILISABLES STYLE « HUD DE JEU » (badges, médailles, icônes)

   Trois briques génériques utilisées par les widgets Sport (HUD duo,
   Dernières séances, en-tête de tuile), pensées pour un look jeu vidéo façon
   mockup de référence (badge en biseau, icônes dans un badge coloré, médaille
   de rang façon ruban) SANS aucune image externe — tout est fait en CSS pur
   (dégradés, clip-path, ombres). Objectif explicite : pouvoir re-styliser
   facilement plus tard, donc chaque brique se pilote par variable CSS et
   n'impose aucune structure HTML particulière :

     .sport-badge-pill  { --badge-color: <couleur> }   badge biseauté (rang, niveau...)
     .sport-icon-badge  { --icon-bg:    <couleur> }   pastille ronde derrière une icône
     .rank-medal        { --medal-color: <couleur> }  médaille ruban en coin d'avatar

   Pour remplacer un emoji par une vraie illustration plus tard : le
   conteneur (span) reste le même, il suffit d'y poser un background-image
   (ou d'y glisser un <img>) — aucune des règles ci-dessous n'a besoin de
   changer. Pareil pour changer une couleur : une seule variable à toucher,
   pas de sélecteur à traquer dans tout le fichier.
   ========================================================================== */

/* --- Badge biseauté (rang, niveau, en-tête de tuile) ------------------ */
.sport-badge-pill {
  --badge-color: var(--accent);
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 4px 16px 4px 10px;
  font-size: .78rem;
  font-weight: 800;
  letter-spacing: .02em;
  color: var(--badge-color);
  background: color-mix(in srgb, var(--badge-color) 16%, transparent);
  border: 1px solid color-mix(in srgb, var(--badge-color) 55%, transparent);
  /* Biseau sur le bord droit — la « pointe » façon étiquette du mockup. */
  clip-path: polygon(0 0, calc(100% - 9px) 0, 100% 50%, calc(100% - 9px) 100%, 0 100%);
  text-shadow: 0 0 10px color-mix(in srgb, var(--badge-color) 55%, transparent);
  white-space: nowrap;
  line-height: 1.3;
}

/* --- Pastille ronde derrière une icône (anneaux Cal/Activité/Hydro...) - */
.sport-icon-badge {
  --icon-bg: var(--accent);
  border-radius: 50%;
  background: radial-gradient(circle at 35% 30%, color-mix(in srgb, var(--icon-bg) 88%, white), var(--icon-bg) 75%);
  box-shadow:
    0 0 0 1px color-mix(in srgb, var(--icon-bg) 50%, transparent) inset,
    0 2px 6px color-mix(in srgb, var(--icon-bg) 45%, transparent);
}

/* --- Médaille de rang façon ruban, en coin d'un avatar ----------------- */
.rank-medal {
  --medal-color: #eab308; /* palier « or » par défaut */
  position: absolute;
  right: -3px; bottom: -3px;
  min-width: 20px; height: 20px;
  padding: 0 4px;
  border-radius: var(--r-full);
  background: linear-gradient(155deg, color-mix(in srgb, var(--medal-color) 88%, white), var(--medal-color));
  border: 2px solid var(--surface);
  box-shadow: 0 2px 5px rgba(0, 0, 0, .35);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: .58rem;
  font-weight: 800;
  color: color-mix(in srgb, var(--medal-color) 25%, black);
  z-index: 1;
}

.tile-ring-text { display: flex; align-items: baseline; gap: 5px; }

/* --------------------------------------------------------------
   Widget séparé « Sport — Dernières séances » : dernière séance/entraînement de chaque membre
   du groupe mis en avant, côte à côte — pensé pour être vu en grand (mode affichage), donc plus
   spacieux que les mini-colonnes du HUD duo (.sport-hud__duo-col ci-dessus, dont il reprend le
   même langage visuel : accent de thème perso par joueur, colonne mise en avant pour "moi").
   -------------------------------------------------------------- */
.sport-seances {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
  gap: 12px;
  flex: 1;
}
.sport-seances__empty {
  grid-column: 1 / -1;
  margin: 0;
  color: var(--text-3);
  font-size: .85rem;
  align-self: center;
}
.sport-seances__col {
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: clamp(10px, 2cqw, 18px);
  border-radius: var(--r-md);
  background: color-mix(in srgb, var(--accent) 10%, transparent);
  border: 1px solid color-mix(in srgb, var(--accent) 22%, transparent);
}
.sport-seances__col.is-me { border-color: var(--accent); }
.sport-seances__col[style*="--accent-membre"] { border-color: var(--accent-membre); background: color-mix(in srgb, var(--accent-membre) 10%, transparent); }
.sport-seances__col[style*="--accent-membre"] .sport-seances__type { color: var(--accent-membre); }
.sport-seances__head { display: flex; align-items: center; gap: 10px; min-width: 0; }
/* Enveloppe positionnée : sert d'ancre à .rank-medal (composant générique, voir plus haut). */
.sport-seances__avatar-wrap { position: relative; flex-shrink: 0; }
.sport-seances__avatar {
  width: clamp(28px, 6cqw, 52px); height: clamp(28px, 6cqw, 52px);
  border-radius: 50%; flex-shrink: 0;
  background: var(--surface-2) center/cover no-repeat;
  border: 2px solid var(--border);
  display: flex; align-items: center; justify-content: center;
  font-size: 1.1rem;
}
.sport-seances__avatar--vide { color: var(--text-3); }
/* La médaille grandit avec l'avatar (mêmes points de repère cqw/cqh que .sport-seances__avatar
   et son équivalent en mode affichage, plus bas). */
.sport-seances__avatar-wrap .rank-medal,
.sport-hud__duo-avatar-wrap .rank-medal {
  min-width: clamp(18px, 4.5cqw, 30px); height: clamp(18px, 4.5cqw, 30px);
  font-size: clamp(.58rem, 1.6cqw, .82rem);
}
body.is-display .sport-seances__avatar-wrap .rank-medal,
body.is-display-preview .sport-seances__avatar-wrap .rank-medal {
  min-width: clamp(22px, min(16cqh, 5cqw), 40px); height: clamp(22px, min(16cqh, 5cqw), 40px);
  font-size: clamp(.68rem, min(7cqh, 2.4cqw), 1.05rem);
}
.sport-seances__nom { font-size: clamp(.82rem, 2.2cqw, 1.05rem); font-weight: 700; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.sport-seances__type { font-size: clamp(.9rem, 2.6cqw, 1.25rem); font-weight: 800; color: var(--accent); }
.sport-seances__meta {
  display: flex; gap: 10px; flex-wrap: wrap;
  font-size: clamp(.72rem, 1.8cqw, .92rem); font-weight: 600; color: var(--text-2);
  font-variant-numeric: tabular-nums;
}
/* État vide : icône + invite, distincte pour « moi » (➕, invitation) et les autres (constat neutre)
   — voir seanceMemberHtml (home.js). */
.sport-seances__vide {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 6px;
  text-align: center;
  color: var(--text-3);
  min-height: 60px;
}
.sport-seances__vide p { margin: 0; font-size: .8rem; line-height: 1.4; }
.sport-seances__vide-icon {
  width: 30px; height: 30px;
  border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  font-size: .95rem;
  background: color-mix(in srgb, var(--text-3) 14%, transparent);
  border: 1px dashed color-mix(in srgb, var(--text-3) 40%, transparent);
}
/* « Moi » sans séance : invite plus engageante, dans la couleur d'accent plutôt que neutre. */
.sport-seances__vide--moi { color: var(--accent); }
.sport-seances__vide--moi .sport-seances__vide-icon {
  background: color-mix(in srgb, var(--accent) 16%, transparent);
  border: 1px dashed color-mix(in srgb, var(--accent) 55%, transparent);
  color: var(--accent);
}
.sport-seances__col[style*="--accent-membre"] .sport-seances__vide--moi,
.sport-seances__col[style*="--accent-membre"] .sport-seances__vide--moi .sport-seances__vide-icon {
  color: var(--accent-membre);
  border-color: color-mix(in srgb, var(--accent-membre) 55%, transparent);
}

/* Mode affichage : le widget est fait pour être vu de loin, on prolonge le même agrandissement
   que le reste de la tuile Sport (cf. section « LISIBILITÉ EN MODE AFFICHAGE » plus bas). */
body.is-display .sport-seances__type,
body.is-display-preview .sport-seances__type { font-size: clamp(1.3rem, min(24cqh, 6cqw), 2.2rem); }
body.is-display .sport-seances__meta,
body.is-display-preview .sport-seances__meta { font-size: clamp(.95rem, min(14cqh, 4cqw), 1.3rem); }
body.is-display .sport-seances__nom,
body.is-display-preview .sport-seances__nom { font-size: clamp(1rem, min(16cqh, 4.4cqw), 1.4rem); }
body.is-display .sport-seances__avatar,
body.is-display-preview .sport-seances__avatar { width: clamp(40px, min(30cqh, 9cqw), 72px); height: clamp(40px, min(30cqh, 9cqw), 72px); }

/* --------------------------------------------------------------
   Widget séparé « Sport — Semaine Résumé » : volume d'entraînement (kcal brûlées) des 7 derniers
   jours, en barres — même langage visuel que .tile-spark (tuile Abonnements) mais avec un libellé
   de jour visible sous chaque barre (Lu, Ma, Me...), comme le mockup de référence.
   -------------------------------------------------------------- */
.sport-semaine {
  display: flex;
  align-items: flex-end;
  gap: clamp(4px, 1.4cqw, 10px);
  height: clamp(50px, min(30cqh, 34cqw), 130px);
  flex: 1;
}
.sport-semaine__col {
  flex: 1;
  min-width: 0;
  height: 100%;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  align-items: center;
  gap: 4px;
}
.sport-semaine__bar {
  width: 100%;
  min-height: 3px;
  border-radius: 4px 4px 0 0;
  background: color-mix(in srgb, var(--accent) 28%, transparent);
  transition: height .6s var(--ease), background .3s var(--ease);
}
.sport-semaine__col.is-today .sport-semaine__bar {
  background: var(--accent);
  box-shadow: 0 0 10px color-mix(in srgb, var(--accent) 55%, transparent);
}
.sport-semaine__label {
  font-size: .6rem; font-weight: 700; letter-spacing: .03em;
  text-transform: uppercase; color: var(--text-3);
}
.sport-semaine__col.is-today .sport-semaine__label { color: var(--accent); font-weight: 800; }
.sport-semaine__empty { margin: auto; color: var(--text-3); font-size: .82rem; text-align: center; }

body.is-display .sport-semaine,
body.is-display-preview .sport-semaine { height: clamp(70px, min(38cqh, 30cqw), 220px); }
body.is-display .sport-semaine__label,
body.is-display-preview .sport-semaine__label { font-size: clamp(.7rem, min(9cqh, 2.6cqw), 1.05rem); }

/* --------------------------------------------------------------
   Tuile Sport — refonte HUD RPG (niveau géant, barre d'XP, anneaux
   Calories/Activité/Hydratation, fond à particules). Demandé en référence à un
   HUD de jeu type "Solo Leveling" : niveau bien visible, compteurs circulaires.
   -------------------------------------------------------------- */
.sport-hud { isolation: isolate; } /* le fond à particules ne doit jamais passer devant le contenu */

.sport-hud__particles {
  position: absolute;
  inset: 0;
  z-index: 0;
  overflow: hidden;
  pointer-events: none;
  opacity: .55;
  background-repeat: no-repeat;
  background-image:
    radial-gradient(1.6px 1.6px at 12% 22%, color-mix(in srgb, var(--accent) 95%, white), transparent),
    radial-gradient(1.6px 1.6px at 78% 12%, #fff, transparent),
    radial-gradient(1.2px 1.2px at 45% 38%, color-mix(in srgb, var(--accent) 95%, white), transparent),
    radial-gradient(1.6px 1.6px at 88% 48%, #fff, transparent),
    radial-gradient(1.2px 1.2px at 8% 62%, color-mix(in srgb, var(--accent) 95%, white), transparent),
    radial-gradient(1.6px 1.6px at 60% 78%, #fff, transparent),
    radial-gradient(1.2px 1.2px at 32% 88%, color-mix(in srgb, var(--accent) 95%, white), transparent),
    radial-gradient(1.6px 1.6px at 95% 82%, #fff, transparent);
  animation: sport-hud-drift 16s linear infinite;
}
@keyframes sport-hud-drift {
  0%   { background-position: 0 4px, 0 0, 0 8px, 0 2px, 0 6px, 0 0, 0 10px, 0 3px; opacity: .3; }
  50%  { background-position: 0 -10px, 0 -14px, 0 -6px, 0 -12px, 0 -8px, 0 -16px, 0 -4px, 0 -11px; opacity: .65; }
  100% { background-position: 0 -24px, 0 -28px, 0 -20px, 0 -26px, 0 -22px, 0 -32px, 0 -18px, 0 -25px; opacity: .3; }
}
@media (prefers-reduced-motion: reduce) {
  .sport-hud__particles { animation: none; }
}

/* Wrapper de la vue solo (niveau + anneaux perso) — la tuile (`.tile`) espace ses enfants directs
   via `gap:14px` ; ce wrapper reproduit le même espacement en interne puisqu'il s'intercale
   maintenant entre `.tile` et `.sport-hud__level`/`.sport-hud__rings` (passe 21 : HUD duo). */
.sport-hud__solo { display: flex; flex-direction: column; gap: 14px; flex: 1 0 auto; min-height: 145px; }
.sport-hud__solo > * { min-height: 0; min-width: 0; }

/* --- Niveau géant + barre d'XP --- */
.sport-hud__level {
  display: flex;
  align-items: center;
  gap: 14px;
  position: relative;
  z-index: 1;
}
.sport-hud__level-num {
  font-size: clamp(1.8rem, min(28cqh, 18cqw), 3.2rem);
  font-weight: 800;
  line-height: 1;
  letter-spacing: -0.04em;
  font-variant-numeric: tabular-nums;
  color: var(--text);
  text-shadow: 0 0 20px color-mix(in srgb, var(--accent) 65%, transparent);
  flex-shrink: 0;
}
.sport-hud__level-info { display: flex; flex-direction: column; gap: 5px; min-width: 0; flex: 1; }
/* Pseudo de la personne affichée (avant, seul le badge de rang apparaissait dans l'en-tête,
   jamais de nom — impossible de savoir "qui" est affiché d'un coup d'œil). */
.sport-hud__level-pseudo {
  font-size: .74rem; font-weight: 700; letter-spacing: .04em;
  color: var(--text-2); overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.sport-hud__xpbar {
  height: 9px;
  border-radius: var(--r-full);
  background: color-mix(in srgb, var(--accent) 14%, transparent);
  border: 1px solid color-mix(in srgb, var(--accent) 30%, transparent);
  overflow: hidden;
}
.sport-hud__xpbar-fill {
  height: 100%;
  width: 0%;
  border-radius: var(--r-full);
  background: linear-gradient(90deg, color-mix(in srgb, var(--accent) 55%, transparent), var(--accent));
  box-shadow: 0 0 10px color-mix(in srgb, var(--accent) 70%, transparent);
  transition: width .9s var(--ease);
}
.sport-hud__xptext { font-size: .64rem; color: var(--text-3); font-variant-numeric: tabular-nums; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* --- Rangée des 3 anneaux circulaires --- */
.sport-hud__rings {
  display: flex;
  gap: 10px;
  margin-top: auto;
  position: relative;
  z-index: 1;
}
.sport-hud__ring {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  text-align: center;
}
.sport-hud__ring-svgwrap { position: relative; width: 52px; height: 52px; flex-shrink: 0; }
.sport-hud__ring-svgwrap .tile-ring { position: absolute; inset: 0; width: 100%; height: 100%; }
/* Pastille colorée derrière l'icône (voir .sport-icon-badge, section « COMPOSANTS RÉUTILISABLES »
   plus haut) — un peu en retrait du bord (inset 15%) pour qu'un anneau de progression bien visible
   reste tout autour, comme sur le mockup de référence. --icon-bg posé par variante ci-dessous. */
.sport-hud__ring-icon {
  position: absolute; inset: 15%;
  display: flex; align-items: center; justify-content: center;
  font-size: 1.05rem;
}
.sport-hud__ring-label { font-size: .6rem; font-weight: 700; letter-spacing: .04em; text-transform: uppercase; color: var(--text-3); }
.sport-hud__ring-value { font-size: .64rem; font-weight: 700; color: var(--text-2); font-variant-numeric: tabular-nums; white-space: nowrap; }

.sport-hud__ring--cal .ring-fill      { stroke: #f97316; }
.sport-hud__ring--cal .ring-bg        { stroke: color-mix(in srgb, #f97316 18%, transparent); }
.sport-hud__ring--cal .sport-hud__ring-icon      { --icon-bg: #f97316; }
.sport-hud__ring--activite .ring-fill { stroke: #22c55e; }
.sport-hud__ring--activite .ring-bg   { stroke: color-mix(in srgb, #22c55e 18%, transparent); }
.sport-hud__ring--activite .sport-hud__ring-icon { --icon-bg: #22c55e; }
.sport-hud__ring--hydro .ring-fill    { stroke: #38bdf8; }
.sport-hud__ring--hydro .ring-bg      { stroke: color-mix(in srgb, #38bdf8 18%, transparent); }
.sport-hud__ring--hydro .sport-hud__ring-icon    { --icon-bg: #38bdf8; }
/* Reprend le patron générique .sport-icon-badge (voir plus haut) directement sur le sélecteur
   existant, sans avoir besoin d'ajouter la classe en HTML — même rendu, un seul point à modifier
   si on veut changer le style de toutes les pastilles d'icône d'un coup (.sport-icon-badge). */
.sport-hud__ring-icon {
  border-radius: 50%;
  background: radial-gradient(circle at 35% 30%, color-mix(in srgb, var(--icon-bg, var(--accent)) 88%, white), var(--icon-bg, var(--accent)) 75%);
  box-shadow:
    0 0 0 1px color-mix(in srgb, var(--icon-bg, var(--accent)) 50%, transparent) inset,
    0 2px 6px color-mix(in srgb, var(--icon-bg, var(--accent)) 45%, transparent);
}

/* Tuile étroite : les libellés/valeurs des anneaux débordent avant la tuile elle-même */
@container tile (max-width: 210px) {
  .sport-hud__ring-label { display: none; }
}

/* Mode affichage (tablette murale) : tout est déjà agrandi via les règles `.tile-value`
   existantes (cf. plus bas) — on prolonge le même traitement au niveau géant. */
body.is-display .sport-hud__level-num,
body.is-display-preview .sport-hud__level-num { font-size: clamp(2.4rem, min(36cqh, 22cqw), 4.6rem); }

/* --- Tuile à venir --- */
.tile-soon {
  cursor: default;
  border-style: dashed;
  background: color-mix(in srgb, var(--surface) 48%, transparent);
  box-shadow: none;
}

.tile-soon:hover {
  transform: none;
  box-shadow: none;
  border-color: var(--border);
}

.tile-soon::after { opacity: .3; }
.tile-soon:hover::after { opacity: .3; }
.tile-soon .tile-icon { filter: grayscale(.35); opacity: .8; }
.tile-soon:hover .tile-icon { transform: none; }
.tile-soon .tile-name { color: var(--text-2); }
.tile-soon .tile-foot { color: var(--text-3); }

/* --------------------------------------------------------------
   Responsive
   -------------------------------------------------------------- */
/* Écran moyen : 6 colonnes par défaut. Un réglage explicite (--bento-cols
   posé en ligne par home.js) reste prioritaire, c'est le principe même de
   la variable. */
@media (max-width: 900px) {
  .bento { grid-template-columns: repeat(var(--bento-cols, 6), minmax(0, 1fr)); grid-auto-rows: 74px; }
}

/* Téléphone.
   La grille garde des rangées de hauteur fixe, exactement comme sur grand
   écran : c'est ce qui rend les tuiles redimensionnables au doigt et ce qui
   permet aux requêtes de conteneur de fonctionner en hauteur. Une hauteur
   dictée par le contenu (l'ancien `grid-row: auto`) rendait `--tile-h`
   sans effet et faisait se chevaucher les textes, faute de pouvoir masquer
   quoi que ce soit. */
@media (max-width: 560px) {
  .home { padding: 30px 14px 60px; }
  .hero { margin-bottom: 26px; }

  .bento {
    grid-template-columns: repeat(var(--bento-cols, 1), minmax(0, 1fr));
    grid-auto-rows: 78px;
    gap: 11px;
  }

  /* Padding réduit : les tuiles font ~345 px de large, chaque pixel compte. */
  .tile { padding: 17px; gap: 11px; border-radius: var(--r-lg); }

  /* Pas de survol au doigt : la flèche reste lisible. */
  .tile .tile-arrow { opacity: .4; transform: none; }
  .tile-soon .tile-arrow { display: none; }
  .aurora-blob { filter: blur(70px); }
}

/* Les halos animés sont coûteux : on les fige si l'utilisateur le demande. */
@media (prefers-reduced-motion: reduce) {
  .aurora-blob, .hero-dot { animation: none; }
}

/* --------------------------------------------------------------
   Tuile Trophées : streak + dernier trophée obtenu
   -------------------------------------------------------------- */

/* Regroupe la date d'obtention et le badge de série sur la droite de
   l'en-tête, sur la même ligne que « Trophées ». Un seul élément porte
   `margin-left: auto` (lui, pas ses enfants) : avec deux marges auto
   indépendantes, l'espace libre se répartit entre les deux badges au lieu
   de les coller l'un à l'autre. */
.trophy-head-right {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-left: auto;
}

/* Date d'obtention du platine affiché dans le carrousel (vide — donc
   masquée — pour un trophée du jour, voir paintTrophyCarousel). */
.trophy-date {
  padding: 4px 10px;
  border-radius: var(--r-full);
  background: color-mix(in srgb, var(--accent) 16%, transparent);
  color: var(--accent);
  font-size: .76rem;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}

.trophy-date:empty { display: none; }

.streak-pill {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 4px 10px;
  border-radius: var(--r-full);
  background: color-mix(in srgb, var(--accent) 18%, transparent);
  color: var(--accent);
  font-size: .76rem;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}

.streak-pill.hidden { display: none; }
.streak-pill-label { font-weight: 600; opacity: .8; }

/* --- Série en cours : la flamme s'emballe avec le compteur ---------------
   Le nombre de jours d'affilée est la statistique qui se perd le plus vite du
   regard, alors qu'elle est la plus motivante. On l'agrandit, et cinq paliers
   font monter la mise : amplitude du battement, vitesse, halo, puis la
   couleur de la pastille elle-même au-delà d'une semaine.

   Tout passe par des variables lues par UNE seule animation : ajouter un
   palier revient à écrire une ligne, pas un cinquième bloc de keyframes.
   Les paliers sont posés par home.js (`streakLevel`).                     */
.streak-pill {
  --fa: 1.02;    /* amplitude du battement */
  --fr: -1deg;   /* bascule de la flamme */
  --fd: 3.4s;    /* durée d'un cycle */
  --fg: 0;       /* intensité du halo */
  gap: 6px;
  padding: 4px 11px;
}

/* Contrairement au reste de la tuile (icône, chiffres…), la pastille avait
   une taille fixe (.76rem) plutôt qu'à l'échelle du conteneur — c'est ce qui
   l'obligeait à disparaître d'un coup sous `fit-lg` dès que la tuile se
   resserrait un peu (l'aperçu de l'onboarding, par exemple), faute de pouvoir
   rétrécir avec elle. En la faisant suivre `cqh`/`cqw` comme ses voisines,
   elle reste lisible et visible à toutes les tailles, y compris les plus
   petites — c'est la statistique la plus motivante de la tuile, elle ne
   devrait jamais être celle qui disparaît en premier. */
.streak-pill {
  font-size: clamp(.62rem, min(6.5cqh, 7cqw), .76rem);
  padding: clamp(2px, .8cqh, 4px) clamp(6px, 2.4cqw, 11px);
}

.streak-count {
  font-size: 1.34em;
  font-weight: 800;
  line-height: 1;
  letter-spacing: -.02em;
}

.streak-flame {
  display: inline-block;
  transform-origin: 50% 85%;
  filter: drop-shadow(0 0 calc(var(--fg) * 7px) rgba(249, 115, 22, .9));
  animation: streak-flame var(--fd) ease-in-out infinite;
}

/* Deux temps inégaux : une poussée franche puis un rebond plus court, ce qui
   évite le va-et-vient mécanique d'un aller-retour symétrique. */
@keyframes streak-flame {
  0%, 100% { transform: none; }
  35%      { transform: scale(var(--fa)) rotate(var(--fr)); }
  70%      { transform: scale(calc(1 + (var(--fa) - 1) * .55)) rotate(calc(var(--fr) * -.6)); }
}

.streak-pill[data-level="1"] { --fa: 1.09; --fr: -3deg; --fd: 2.7s;  --fg: .35; }
.streak-pill[data-level="2"] { --fa: 1.15; --fr: -5deg; --fd: 2.1s;  --fg: .6;  }
.streak-pill[data-level="3"] { --fa: 1.22; --fr: -7deg; --fd: 1.6s;  --fg: .85; }
.streak-pill[data-level="4"] { --fa: 1.30; --fr: -9deg; --fd: 1.15s; --fg: 1;   }

/* À partir de deux semaines, la pastille chauffe aussi : l'orange de la tuile
   ne suffit plus à distinguer une belle série d'une série ordinaire. */
.streak-pill[data-level="3"],
.streak-pill[data-level="4"] {
  background: linear-gradient(135deg,
    color-mix(in srgb, #f97316 26%, transparent),
    color-mix(in srgb, #ef4444 20%, transparent));
  color: #fb923c;
}

.streak-pill[data-level="4"] { box-shadow: 0 0 14px -2px rgba(249, 115, 22, .55); }

.trophy-last {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-top: auto;
  position: relative;
  z-index: 1;
  min-width: 0;
}

.trophy-icon {
  width: 46px;
  height: 46px;
  flex-shrink: 0;
  object-fit: cover;
  border-radius: var(--r-sm);
  border: 2px solid var(--accent);
  background: var(--surface-3);
}

.trophy-text {
  display: flex;
  flex-direction: column;
  gap: 1px;
  flex: 1;
  min-width: 0;
}

/* `.trophy-text` est en colonne flex, dans l'ordre du DOM (label, nom,
   descriptif, jeu) — `order` réordonne l'affichage sans toucher au balisage :
   le jeu remonte juste sous le libellé, au-dessus du titre du trophée,
   classique ou platine. */
.trophy-label {
  order: -2;
  font-size: .68rem;
  font-weight: 660;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--accent);
}

.trophy-name {
  font-size: 1.24rem;
  font-weight: 650;
  letter-spacing: -0.02em;
  color: var(--text);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.trophy-game {
  order: -1;
  font-size: .78rem;
  font-weight: 600;
  color: var(--text-3);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Le descriptif d'obtention — contrairement au nom du jeu (fit-lg), il n'a
   PAS de palier d'abandon : il doit rester lisible même dans la plus petite
   tuile. Deux lignes avec coupure propre plutôt qu'un débordement, et une
   police qui suit la taille de la tuile (voir plus bas, ADAPTATION). */
.trophy-detail {
  font-size: .8rem;
  line-height: 1.28;
  color: var(--text-3);
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.trophy-detail:empty { display: none; }

@media (max-width: 560px) {
  .trophy-icon { width: 40px; height: 40px; }
  .trophy-name { font-size: 1.1rem; }
}

/* ==========================================================================
   Visuels enrichis : affiches, vignettes et carrousels
   ========================================================================== */

/* --- Affiche en arrière-plan de tuile --- */
.tile.has-bg { isolation: isolate; }

.tile-bg {
  position: absolute;
  inset: 0;
  z-index: 0;
  opacity: 0;
  transform: scale(1.06);
  transition: opacity 1.1s var(--ease-soft), transform 12s linear;
  pointer-events: none;
}

/* Deux calques empilés : c'est le croisement de leurs opacités qui produit
   le fondu enchaîné, sans passage par le vide. */
.xf-layer {
  position: absolute;
  inset: 0;
  background-size: cover;
  background-position: center 22%;
  opacity: 0;
  transition: opacity 1.2s var(--ease-soft);
  will-change: opacity;
}

.xf-layer.is-on { opacity: 1; }

.tile-bg.is-visible { opacity: .3; }
.tile:hover .tile-bg.is-visible { opacity: .42; transform: scale(1.12); }

/* Voile de lisibilité : sans lui, le texte se perd sur l'affiche.
   Volontairement resserré sur le bas — il ne doit protéger que la zone de
   texte. L'ancien réglage montait à 55 % d'opacité jusqu'en haut du cadre et
   noyait l'image, surtout sur une tuile de deux rangées où il n'y a presque
   que du « bas ». Le texte, lui, gagne une ombre portée un peu plus loin. */
.tile.has-bg::before {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 0;
  background: linear-gradient(
    to top,
    color-mix(in srgb, var(--surface) 88%, transparent) 0%,
    color-mix(in srgb, var(--surface) 52%, transparent) 38%,
    transparent 68%
  );
  pointer-events: none;
}

/* Tuile basse : le dégradé occuperait toute la hauteur. On le comprime pour
   laisser l'image respirer. */
@container tile (max-height: 210px) {
  .tile.has-bg::before,
  .tile.has-photo::before {
    background: linear-gradient(
      to top,
      color-mix(in srgb, var(--surface) 80%, transparent) 0%,
      color-mix(in srgb, var(--surface) 28%, transparent) 30%,
      transparent 52%
    );
  }
}

/* Apparition douce d'une image ponctuelle (vignette, icône de trophée). */
.xf-in { animation: xf-in .75s var(--ease-soft); }

@keyframes xf-in {
  from { opacity: 0; transform: scale(.96); }
}

/* --- Prochaine échéance (abonnements) --- */
.next-due {
  display: flex;
  align-items: center;
  gap: 11px;
  padding: 10px 12px;
  border-radius: var(--r-md);
  background: color-mix(in srgb, var(--accent) 9%, transparent);
  border: 1px solid color-mix(in srgb, var(--accent) 20%, transparent);
  position: relative;
  z-index: 1;
  min-width: 0;
}

.next-due-img {
  width: 38px;
  height: 38px;
  flex-shrink: 0;
  object-fit: cover;
  border-radius: var(--r-xs);
  background: var(--surface-3);
}

.next-due-text {
  display: flex;
  flex-direction: column;
  gap: 1px;
  min-width: 0;
  flex: 1;
}

.next-due-label {
  font-size: .64rem;
  font-weight: 680;
  letter-spacing: .09em;
  text-transform: uppercase;
  color: var(--accent);
}

.next-due-name {
  font-size: .89rem;
  font-weight: 640;
  color: var(--text);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.next-due-when { font-size: .74rem; color: var(--text-3); }

.next-due-price {
  flex-shrink: 0;
  font-size: .95rem;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  color: var(--accent);
}

/* --- Puces de carrousel --- */
.carousel-dots {
  display: flex;
  align-items: center;
  gap: 5px;
  margin-left: auto;
}

.carousel-dots.vertical {
  flex-direction: column;
  margin-left: auto;
  align-self: center;
  /* Une grosse journée de trophées peut donner des dizaines de puces —
     sans plafond, la colonne s'étire à perte de vue au lieu de rester à
     hauteur de l'icône. Défilement interne plutôt que tout afficher. */
  max-height: 46px;
  overflow-y: auto;
  scrollbar-width: none;
}
.carousel-dots.vertical::-webkit-scrollbar { display: none; }

/* Sur tuile étroite, les puces prennent une largeur fixe que le texte
   (`.trophy-text`, flex:1) n'a déjà pas à revendre — mieux vaut les cacher
   que perdre encore un peu plus de place utile pour le nom/descriptif. */
@container tile (max-width: 260px) {
  .trophy-last .carousel-dots.vertical { display: none; }
}

.carousel-dot {
  width: 6px;
  height: 6px;
  border-radius: var(--r-full);
  background: color-mix(in srgb, var(--accent) 28%, transparent);
  transition: all .55s var(--ease-soft);
}

.carousel-dot.is-active {
  background: var(--accent);
  width: 16px;
}

.carousel-dots.vertical .carousel-dot.is-active { width: 6px; height: 16px; }

/* --- Trophée : pastille lumineuse + apparition --- */
.trophy-icon-wrap {
  position: relative;
  display: inline-flex;
  flex-shrink: 0;
}

.trophy-icon {
  position: relative;
  z-index: 1;
  border-color: var(--trophy-tint, var(--accent));
}

.trophy-glow {
  position: absolute;
  inset: -6px;
  z-index: 0;
  border-radius: var(--r-md);
  filter: blur(14px);
  opacity: .45;
  transition: opacity .4s var(--ease);
}

.tile:hover .trophy-glow { opacity: .7; }

.trophy-icon-wrap.trophy-pop { animation: trophy-pop .8s var(--ease-soft); }

@keyframes trophy-pop {
  0%   { transform: scale(.88) rotate(-5deg); opacity: 0; }
  55%  { transform: scale(1.04) rotate(1.5deg); opacity: 1; }
  100% { transform: none; }
}

/* --- Entrée du texte au changement de slide --- */
.slide-up { animation: slide-up .7s var(--ease-soft); }

@keyframes slide-up {
  from { opacity: 0; transform: translateY(7px); }
  40%  { opacity: .55; }
}

@media (max-width: 560px) {
  .next-due { padding: 8px 10px; gap: 9px; }
  .next-due-img { width: 32px; height: 32px; }
  .next-due-price { font-size: .86rem; }
  .tile-bg.is-visible { opacity: .24; }
  /* En ligne sur mobile (pas en colonne) : le plafond de hauteur mis plus
     haut ne freine plus rien, il faut le même principe mais en largeur. */
  .carousel-dots.vertical {
    flex-direction: row;
    max-height: none;
    max-width: 46px;
    overflow-x: auto;
    overflow-y: visible;
  }
  .carousel-dots.vertical .carousel-dot.is-active { width: 16px; height: 6px; }
}

/* Les carrousels ne défilent pas tout seuls : on fige aussi les transitions. */
@media (prefers-reduced-motion: reduce) {
  .tile-bg, .xf-layer { transition: opacity .01ms; transform: none; }
  .trophy-icon-wrap.trophy-pop, .slide-up, .xf-in { animation: none; }

  /* La flamme et le reflet du platine sont décoratifs : ils tombent aussi.
     Le cadre argenté, lui, reste — c'est de l'information, pas du mouvement. */
  .streak-flame, .trophy-last.plat-enter { animation: none; }
  .trophy-last.is-platinum::after { display: none; }
}

/* --- Pastille du profil (carrousel multi-comptes) --- */
.profile-chip {
  display: inline-flex;
  align-items: center;
  max-width: 110px;
  padding: 3px 10px;
  border-radius: var(--r-full);
  background: color-mix(in srgb, var(--accent) 18%, transparent);
  color: var(--accent);
  font-size: .68rem;
  font-weight: 700;
  letter-spacing: .02em;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  transition: background .4s var(--ease), color .4s var(--ease);
}

.profile-chip.hidden { display: none; }

/* #sportRank cumule .profile-chip (masquage/troncature générique) et .sport-badge-pill (le badge
   biseauté façon HUD, voir composants réutilisables plus haut) — .profile-chip étant défini après
   dans ce fichier, ses propriétés (padding, fond, bord arrondi) gagneraient sinon sur celles du
   badge à spécificité égale. Un sélecteur par ID force le badge à gagner sans dépendre de l'ordre
   des règles dans le fichier. */
#sportRank.sport-badge-pill {
  padding: 4px 16px 4px 10px;
  border-radius: 0;
  background: color-mix(in srgb, var(--badge-color) 16%, transparent);
  color: var(--badge-color);
  max-width: none;
}

/* La tuile change d'accent d'un profil à l'autre : on adoucit la transition. */
.tile[data-widget="calendar"] {
  transition: transform .28s var(--ease), box-shadow .28s var(--ease),
              border-color .5s var(--ease);
}

.tile[data-widget="calendar"]::after { transition: opacity .28s var(--ease), background .5s var(--ease); }

@media (max-width: 560px) {
  .profile-chip { max-width: 88px; font-size: .63rem; }
}

/* --------------------------------------------------------------
   Chargement : flou léger le temps que les données arrivent
   -------------------------------------------------------------- */
.tile.is-loading > *:not(.tile-bg) {
  filter: blur(7px);
  opacity: .55;
  pointer-events: none;
  transition: filter .45s var(--ease), opacity .45s var(--ease);
}

.tile:not(.is-loading) > * {
  filter: none;
  opacity: 1;
  transition: filter .45s var(--ease), opacity .45s var(--ease);
}

/* Balayage discret pour signaler que ça travaille. */
.tile.is-loading::before {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 2;
  background: linear-gradient(
    100deg,
    transparent 30%,
    color-mix(in srgb, var(--accent) 12%, transparent) 50%,
    transparent 70%
  );
  background-size: 220% 100%;
  animation: shimmer 1.4s ease-in-out infinite;
  pointer-events: none;
}

@keyframes shimmer {
  from { background-position: 140% 0; }
  to   { background-position: -40% 0; }
}

/* --------------------------------------------------------------
   « Plus rien à payer »
   -------------------------------------------------------------- */
.all-clear {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px 14px;
  border-radius: var(--r-md);
  background: color-mix(in srgb, var(--success) 11%, transparent);
  border: 1px solid color-mix(in srgb, var(--success) 26%, transparent);
  position: relative;
  z-index: 1;
  min-width: 0;
}

.all-clear-icon {
  font-size: 1.35rem;
  line-height: 1;
  flex-shrink: 0;
  animation: sparkle 2.6s ease-in-out infinite;
}

@keyframes sparkle {
  0%, 100% { transform: none; opacity: 1; }
  50%      { transform: scale(1.15) rotate(8deg); opacity: .85; }
}

.all-clear-text { display: flex; flex-direction: column; gap: 1px; min-width: 0; }

.all-clear-title {
  font-size: .95rem;
  font-weight: 660;
  letter-spacing: -0.02em;
  color: var(--success);
}

.all-clear-sub {
  font-size: .78rem;
  color: var(--text-2);
  overflow: hidden;
  text-overflow: ellipsis;
}

@media (prefers-reduced-motion: reduce) {
  .tile.is-loading::before, .all-clear-icon { animation: none; }
}

/* --------------------------------------------------------------
   Tuile météo
   -------------------------------------------------------------- */
.weather-main {
  display: flex;
  align-items: center;
  gap: 14px;
  margin-top: auto;
  position: relative;
  z-index: 1;
  min-width: 0;
}

.weather-glyph {
  font-size: 2.6rem;
  line-height: 1;
  flex-shrink: 0;
}

/* « Aujourd'hui » et la description partagent la même ligne (grille, pas
   flux) : ça libère toute une rangée par rapport à un empilement en trois
   lignes, et c'est ce qui laisse au bloc météo la place de tout garder
   affiché même dans une tuile bien plus petite. */
.weather-block {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  grid-template-areas: "day desc" "temp temp";
  column-gap: 6px;
  row-gap: 1px;
  align-items: baseline;
  min-width: 0;
}

.weather-day {
  grid-area: day;
  font-size: .68rem;
  font-weight: 680;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--accent);
}

.weather-temp {
  grid-area: temp;
  font-size: 1.9rem;
  font-weight: 700;
  letter-spacing: -0.04em;
  line-height: 1.05;
  font-variant-numeric: tabular-nums;
  color: var(--text);
}

.weather-desc {
  grid-area: desc;
  min-width: 0;
  font-size: .78rem;
  color: var(--text-2);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.weather-desc:not(:empty)::before { content: '· '; opacity: .6; }

/* Attribution obligatoire (données CC BY 4.0). */
.weather-credit {
  position: absolute;
  right: 14px;
  bottom: 10px;
  z-index: 1;
  font-size: .6rem;
  letter-spacing: .04em;
  color: var(--text-3);
  opacity: .65;
}

@media (max-width: 560px) {
  .weather-glyph { font-size: 2.1rem; }
  .weather-temp { font-size: 1.6rem; }
}

/* ==========================================================================
   MODE ÉDITION — redimensionner, déplacer, masquer les tuiles
   ========================================================================== */

/* Pendant l'édition, le contenu ne doit pas capter les clics ni les
   gestes tactiles : seuls les boutons de la barre d'outils répondent. */
/* En édition, aucun contenu ne capte le clic : la tuile entière devient
   une poignée de sélection et de déplacement. */
.is-editing .tile > * { pointer-events: none; }

.is-editing .tile {
  cursor: default;
  border-style: dashed;
  border-color: color-mix(in srgb, var(--accent) 55%, transparent);
  animation: edit-wobble 3.4s ease-in-out infinite;
}

.is-editing .tile:hover { transform: none; box-shadow: var(--shadow-sm); }

/* Décalage des phases pour que les tuiles n'oscillent pas à l'unisson. */
.is-editing .tile:nth-child(2n)  { animation-delay: -.9s; }
.is-editing .tile:nth-child(3n)  { animation-delay: -1.8s; }

@keyframes edit-wobble {
  0%, 100% { transform: rotate(-.22deg); }
  50%      { transform: rotate(.22deg); }
}

/* La tuile quitte le flux et suit le pointeur. pointer-events:none est
   indispensable : sinon elementFromPoint ne verrait qu'elle. */
.tile.is-dragging {
  position: fixed !important;
  z-index: 1000;
  margin: 0;
  pointer-events: none;
  animation: none;
  opacity: .92;
  transform: scale(1.02) rotate(1deg);
  box-shadow: var(--shadow-lg), 0 0 0 3px color-mix(in srgb, var(--accent) 45%, transparent) !important;
  cursor: grabbing;
}

/* Emplacement fantôme : montre où la tuile va s'ancrer pendant qu'on la
   déplace ou la redimensionne. Rouge quand l'endroit visé chevauche une
   autre tuile ou déborde de la grille — la dépose est alors refusée et la
   tuile revient d'où elle vient. */
.drop-slot {
  grid-column: calc(var(--tile-x, 0) + 1) / span var(--tile-w, 3);
  grid-row: calc(var(--tile-y, 0) + 1) / span var(--tile-h, 2);
  border: 2px dashed color-mix(in srgb, var(--primary) 55%, transparent);
  border-radius: var(--r-xl);
  background: color-mix(in srgb, var(--primary) 8%, transparent);
  animation: slot-pulse 1.1s ease-in-out infinite;
  pointer-events: none;
}

.drop-slot.is-blocked {
  border-color: color-mix(in srgb, #ef4444 65%, transparent);
  background: color-mix(in srgb, #ef4444 14%, transparent);
  animation: none;
}

@keyframes slot-pulse {
  50% { background: color-mix(in srgb, var(--primary) 16%, transparent); }
}

/* --- Poignée de redimensionnement --- */
.tile .resize-handle { display: none; }

.is-editing .tile > .resize-handle {
  display: block;
  position: absolute;
  right: 6px;
  bottom: 6px;
  width: 24px;
  height: 24px;
  border-radius: 7px;
  background: color-mix(in srgb, var(--accent) 75%, transparent);
  box-shadow: var(--shadow-sm);
  cursor: nwse-resize;
  touch-action: none;
  pointer-events: auto;
  z-index: 5;
}

.is-editing .tile > .resize-handle::after {
  content: '';
  position: absolute;
  inset: 6px;
  border-right: 2px solid #fff;
  border-bottom: 2px solid #fff;
  opacity: .85;
}


/* --- Barre flottante --- */
.edit-bar {
  position: fixed;
  left: 50%;
  bottom: calc(18px + env(safe-area-inset-bottom, 0px));
  transform: translateX(-50%);
  z-index: 940;
  display: flex;
  align-items: center;
  gap: 10px;
  max-width: calc(100vw - 24px);
  padding: 10px 12px;
  border-radius: var(--r-full);
  background: color-mix(in srgb, var(--surface) 94%, transparent);
  border: 1px solid var(--border);
  box-shadow: var(--shadow-lg);
  backdrop-filter: blur(14px);
  animation: edit-bar-in .3s var(--ease);
}

/* Une fois déplacée à la main (JS pose left/top en ligne), la barre quitte
   son ancrage bas-centré : plus de transform de centrage, sinon la poignée
   ne suivrait pas le doigt. */
.edit-bar.is-placed {
  left: 0;
  bottom: auto;
  transform: none;
}

.edit-bar-handle {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  width: 30px;
  height: 30px;
  margin: -10px 0 -10px -12px;   /* touche les bords de la pilule, comme une vraie poignée */
  border-radius: var(--r-full) 0 0 var(--r-full);
  background: color-mix(in srgb, var(--accent) 14%, transparent);
  color: var(--text-2);
  font-size: 1.05rem;
  line-height: 1;
  cursor: grab;
  touch-action: none;
  user-select: none;
}

.edit-bar-handle:active { cursor: grabbing; }

@keyframes edit-bar-in {
  from { opacity: 0; transform: translate(-50%, 14px); }
}

.edit-bar-hint {
  font-size: .78rem;
  color: var(--text-3);
  white-space: nowrap;
}

.edit-bar .btn-primary,
.edit-bar .btn-secondary { padding: 8px 14px; font-size: .82rem; }

@media (max-width: 640px) {
  .edit-bar { flex-wrap: wrap; justify-content: center; }
  .edit-bar-hint { width: 100%; text-align: center; white-space: normal; font-size: .72rem; }
}

@media (prefers-reduced-motion: reduce) {
  .is-editing .tile { animation: none; }
  .edit-bar { animation: none; }
}

/* ==========================================================================
   AMBIANCES MÉTÉO
   Tout est fait en CSS : aucune image à héberger, aucune licence à gérer,
   et le rendu s'adapte au thème clair/sombre.
   ========================================================================== */
.tile.has-wx { isolation: isolate; }

.wx-sky {
  position: absolute;
  inset: 0;
  z-index: 0;
  overflow: hidden;
  pointer-events: none;
  opacity: .8;
  transition: background 1.4s var(--ease-soft);
}

/* Voile de lisibilité : le texte doit rester net sur n'importe quel ciel. */
.tile.has-wx::before {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 0;
  /* Ne descend jamais sous 62 % d'opacité, même en haut : c'est ce voile qui
     garantit le contraste du texte par-dessus n'importe quel ciel. */
  background: linear-gradient(
    to top,
    color-mix(in srgb, var(--surface) 96%, transparent) 0%,
    color-mix(in srgb, var(--surface) 88%, transparent) 45%,
    color-mix(in srgb, var(--surface) 62%, transparent) 100%
  );
  pointer-events: none;
}

/* --- Astre (soleil ou lune) --- */
.wx-orb {
  position: absolute;
  top: -26px;
  right: -20px;
  width: 120px;
  height: 120px;
  border-radius: 50%;
  opacity: 0;
  transition: opacity .8s var(--ease);
}

.wx-clear .wx-orb,
.wx-partly .wx-orb {
  opacity: 1;
  background: radial-gradient(circle, #fde68a 0%, #fbbf24 42%, transparent 68%);
  animation: orb-pulse 7s ease-in-out infinite;
}

.wx-clear-night .wx-orb,
.wx-partly-night .wx-orb {
  opacity: 1;
  background: radial-gradient(circle, #e0e7ff 0%, #a5b4fc 40%, transparent 66%);
}

@keyframes orb-pulse {
  50% { transform: scale(1.08); filter: brightness(1.1); }
}

/* --- Ciels --- */
.wx-clear        { background: linear-gradient(165deg, #7dd3fc 0%, #bae6fd 45%, transparent 85%); }
.wx-clear-night  { background: linear-gradient(165deg, #1e1b4b 0%, #312e81 45%, transparent 85%); }
.wx-partly       { background: linear-gradient(165deg, #93c5fd 0%, #dbeafe 48%, transparent 85%); }
.wx-partly-night { background: linear-gradient(165deg, #1e293b 0%, #334155 48%, transparent 85%); }
.wx-cloudy       { background: linear-gradient(165deg, #94a3b8 0%, #cbd5e1 48%, transparent 85%); }
.wx-fog          { background: linear-gradient(165deg, #cbd5e1 0%, #e2e8f0 50%, transparent 88%); }
.wx-rain         { background: linear-gradient(165deg, #475569 0%, #64748b 45%, transparent 85%); }
.wx-snow         { background: linear-gradient(165deg, #bfdbfe 0%, #eff6ff 48%, transparent 85%); }
.wx-storm        { background: linear-gradient(165deg, #312e81 0%, #4c1d95 45%, transparent 85%); }

/* --- Effets animés (pluie, neige, brume, éclair) --- */
.wx-fx {
  position: absolute;
  inset: -20% -10%;
  opacity: 0;
  transition: opacity .6s var(--ease);
}

/* Pluie : deux couches de traits obliques à vitesses différentes. */
.wx-rain .wx-fx,
.wx-storm .wx-fx {
  opacity: .5;
  background-image:
    repeating-linear-gradient(74deg, rgba(255,255,255,.55) 0 1px, transparent 1px 9px),
    repeating-linear-gradient(74deg, rgba(255,255,255,.3) 0 1px, transparent 1px 15px);
  background-size: 100% 90px, 100% 130px;
  animation: rain-fall .55s linear infinite, rain-fall-slow .9s linear infinite;
}

@keyframes rain-fall      { to { background-position-y: 90px, 0; } }
@keyframes rain-fall-slow { to { background-position-y: 0, 130px; } }

/* Neige : points blancs qui descendent lentement. */
.wx-snow .wx-fx {
  opacity: .8;
  background-image:
    radial-gradient(circle at 20% 20%, #fff 1.6px, transparent 2px),
    radial-gradient(circle at 65% 45%, #fff 1.3px, transparent 2px),
    radial-gradient(circle at 40% 75%, #fff 1.8px, transparent 2px);
  background-size: 90px 90px, 130px 130px, 70px 70px;
  animation: snow-fall 9s linear infinite;
}

@keyframes snow-fall {
  to { background-position: 12px 90px, -16px 130px, 8px 70px; }
}

/* Brouillard : nappes horizontales qui dérivent. */
.wx-fog .wx-fx {
  opacity: .75;
  background: repeating-linear-gradient(
    to bottom,
    rgba(255,255,255,.5) 0 10px,
    transparent 10px 26px
  );
  filter: blur(4px);
  animation: fog-drift 11s ease-in-out infinite alternate;
}

@keyframes fog-drift { to { transform: translateX(22px); } }

/* Orage : éclat bref et espacé.
   Le ::after porte sur .wx-sky lui-même — pas sur un descendant. */
.wx-storm::after {
  content: '';
  position: absolute;
  inset: 0;
  background: #fff;
  opacity: 0;
  animation: lightning 6s ease-out infinite;
  pointer-events: none;
}

@keyframes lightning {
  0%, 92%, 100% { opacity: 0; }
  93%           { opacity: .5; }
  94%           { opacity: 0; }
  95%           { opacity: .32; }
  96%           { opacity: 0; }
}

/* Nuages : deux masses floues qui traversent lentement. */
.wx-cloudy .wx-fx,
.wx-partly .wx-fx,
.wx-partly-night .wx-fx {
  opacity: .55;
  background-image:
    radial-gradient(ellipse 70px 26px at 25% 30%, rgba(255,255,255,.9), transparent 70%),
    radial-gradient(ellipse 90px 30px at 70% 55%, rgba(255,255,255,.75), transparent 72%);
  animation: cloud-drift 26s ease-in-out infinite alternate;
}

@keyframes cloud-drift { to { transform: translateX(26px); } }

/* En sombre, les ciels clairs seraient éblouissants. */
/* En sombre, un ciel bleu clair derrière du texte clair serait illisible. */
:root[data-theme="dark"] .wx-sky { opacity: .4; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .wx-sky { opacity: .4; }
}

/* --- Fond flouté du dernier trophée --- */
.trophy-bg {
  background-size: 150%;
  background-position: center;
  filter: blur(26px) saturate(1.5);
  transform: scale(1.25);
}

.trophy-bg.is-visible { opacity: .5; }
.tile:hover .trophy-bg.is-visible { opacity: .68; transform: scale(1.32); }

:root[data-theme="dark"] .trophy-bg.is-visible { opacity: .38; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .trophy-bg.is-visible { opacity: .38; }
}

@media (prefers-reduced-motion: reduce) {
  .wx-fx, .wx-orb, .wx-storm::after { animation: none; }
}

/* --- Matin / après-midi --- */
.weather-parts {
  display: flex;
  gap: 8px;
  position: relative;
  z-index: 1;
}

.weather-part {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 1px;
  padding: 7px 10px;
  border-radius: var(--r-sm);
  background: color-mix(in srgb, var(--surface) 72%, transparent);
  border: 1px solid var(--border);
}

.weather-part-label {
  font-size: .62rem;
  font-weight: 680;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--text-3);
}

.weather-part-val {
  font-size: .92rem;
  font-weight: 660;
  color: var(--text);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

@media (max-width: 560px) {
  .weather-part { padding: 6px 8px; }
  .weather-part-val { font-size: .86rem; }
}


/* Une tuile 3×1 est étroite : la barre doit rester utilisable. */
@media (max-width: 900px) {
}

/* --- Tuile sélectionnée en édition ---

   `touch-action: pan-y` et non `none`. Avec `none`, le navigateur cédait
   tous les gestes au script : comme la grille n'est faite QUE de tuiles,
   plus aucun endroit ne permettait de faire défiler la page au doigt, et
   les tuiles du bas devenaient inatteignables.

   Le défilement vertical revient donc au navigateur par défaut. Le
   déplacement d'une tuile, lui, démarre sur un appui long (voir startDrag) :
   c'est à ce moment-là seulement que le script réclame le geste, via la
   classe `is-grabbing` posée en JS. */
.is-editing .tile {
  cursor: grab;
  touch-action: pan-y;
}

/* Appui long confirmé : le doigt pilote la tuile, plus la page. */
.is-editing .tile.is-grabbing { touch-action: none; }

/* La barre d'édition flotte au-dessus du bas de page : sans cette réserve,
   la dernière rangée de tuiles reste cachée dessous, quel que soit le
   défilement. */
body.is-editing .home { padding-bottom: 150px; }

.is-editing .tile:active { cursor: grabbing; }

.tile.is-selected {
  border-style: solid;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 30%, transparent), var(--shadow-md);
  animation: none;
}

/* --- Barre d'édition : contrôles de la tuile sélectionnée --- */
.edit-preset {
  flex-shrink: 0;
  padding: 4px 10px;
  border-radius: var(--r-full);
  background: color-mix(in srgb, var(--primary) 16%, transparent);
  color: var(--primary);
  font-size: .68rem;
  font-weight: 700;
  letter-spacing: .03em;
  white-space: nowrap;
}

.edit-target {
  font-size: .82rem;
  font-weight: 660;
  color: var(--text);
  white-space: nowrap;
  max-width: 130px;
  overflow: hidden;
  text-overflow: ellipsis;
}

.edit-group {
  display: inline-flex;
  align-items: center;
  gap: 3px;
  padding: 3px 5px;
  border-radius: var(--r-full);
  background: var(--surface-2);
}

.edit-axis { font-size: .78rem; color: var(--text-3); }

.edit-btn {
  width: 26px;
  height: 26px;
  padding: 0;
  border: none;
  border-radius: 50%;
  background: var(--surface);
  color: var(--text-2);
  font-family: inherit;
  font-size: .88rem;
  font-weight: 700;
  line-height: 1;
  cursor: pointer;
  transition: background .15s var(--ease), color .15s var(--ease);
}

.edit-btn:hover:not(:disabled) { background: var(--primary); color: var(--primary-contrast); }
.edit-btn.danger:hover:not(:disabled) { background: var(--danger); color: #fff; }
.edit-btn.wide { width: 30px; background: var(--surface-2); }

.edit-btn:disabled { opacity: .35; cursor: not-allowed; }

/* « 12 col » est plus large que « 4×3 » : sans ça, la barre sautille d'un
   palier à l'autre. */
#editCols { min-width: 50px; }

.edit-size {
  min-width: 38px;
  text-align: center;
  font-size: .78rem;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  color: var(--text);
}

.edit-sep {
  width: 1px;
  height: 22px;
  background: var(--border);
  margin: 0 2px;
}

@media (max-width: 760px) {
  .edit-bar { flex-wrap: wrap; justify-content: center; gap: 6px; }
  .edit-target { width: 100%; text-align: center; max-width: none; }
  .edit-sep { display: none; }
  .edit-bar .btn-primary,
  .edit-bar .btn-secondary { flex: 1; }
}

/* --- Pas de sélection de texte pendant l'édition ---
   Sans ça, glisser une tuile surlignait tout le texte au passage. */
body.is-editing,
body.is-editing .tile,
body.is-editing .edit-bar {
  user-select: none;
  -webkit-user-select: none;
}

/* Le curseur « texte » sur les libellés trahissait aussi la sélection. */
body.is-editing .tile * { cursor: inherit; }

/* ==========================================================================
   LISIBILITÉ SUR LES TUILES À FOND VISUEL
   Sur une affiche, un trophée doré ou un ciel, les gris clairs (--text-2,
   --text-3) passent sous le seuil de contraste. Deux réponses :
   un support translucide pour les blocs, et un cran de contraste en plus
   pour le texte isolé.
   ========================================================================== */

/* --- Bloc « dernier trophée » : même traitement que « prochaine échéance » --- */
.trophy-last {
  padding: 10px 12px;
  border-radius: var(--r-md);
  background: color-mix(in srgb, var(--surface) 82%, transparent);
  border: 1px solid color-mix(in srgb, var(--accent) 22%, transparent);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
}

.trophy-game { color: var(--text-2); }
.trophy-detail { color: var(--text-2); }

/* --- Platine : traitement d'exception ------------------------------------
   Les platines de rappel passent APRÈS les trophées du jour et peuvent dater
   de plusieurs semaines. Avec le style commun, ils ressembleraient à un
   trophée ancien de plus — alors que ce sont les seuls qui se méritent sur
   des dizaines d'heures. Cadre métal, reflet qui balaie le bloc en boucle,
   et une entrée appuyée au moment où ils apparaissent.

   La pastille photo reste À GAUCHE du texte, comme pour tout trophée — ce
   qui change, c'est le bloc de texte à droite : titre en grand (quitte à
   courir sur deux lignes), descriptif puis jeu en dessous, et un numéro de
   rang épinglé en haut à droite façon panneau de position Formule 1.

   Les teintes --plat-* restent fixes, volontairement : un platine est
   argenté, pas orange. Elles ne dérivent donc pas de --accent.             */
.trophy-last.is-platinum {
  --plat-hi:  #eaf2fb;
  --plat-mid: #b9c6d6;
  --plat-lo:  #7d93ab;

  position: relative;
  overflow: hidden;
  border-color: color-mix(in srgb, var(--plat-mid) 62%, transparent);
  background:
    linear-gradient(135deg,
      color-mix(in srgb, var(--plat-hi) 20%, transparent),
      color-mix(in srgb, var(--surface) 86%, transparent) 46%,
      color-mix(in srgb, var(--plat-lo) 18%, transparent));
  box-shadow:
    inset 0 1px 0 color-mix(in srgb, var(--plat-hi) 45%, transparent),
    0 8px 26px -10px color-mix(in srgb, var(--plat-lo) 70%, transparent);
}

/* Reflet balayant. `pointer-events: none` est indispensable : la tuile
   entière est un lien, un calque au-dessus avalerait le clic. */
.trophy-last.is-platinum::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: linear-gradient(105deg,
    transparent 38%,
    color-mix(in srgb, var(--plat-hi) 55%, transparent) 48%,
    color-mix(in srgb, #fff 70%, transparent) 51%,
    transparent 62%);
  transform: translateX(-120%);
  animation: plat-sheen 4.6s var(--ease-soft) .5s infinite;
}

/* Le reflet reste hors champ les deux tiers du temps : un balayage qui
   repasse sans arrêt devient un clignotant. */
@keyframes plat-sheen {
  0%, 62% { transform: translateX(-120%); }
  100%    { transform: translateX(120%); }
}

.trophy-last.is-platinum .trophy-label {
  color: var(--plat-hi);
  text-shadow: 0 0 12px color-mix(in srgb, var(--plat-mid) 70%, transparent);
}

.trophy-last.is-platinum .trophy-game {
  font-size: .72rem;
}

/* La taille de la pastille vient de la règle générique .trophy-icon (même
   gabarit que pour un trophée classique désormais) — ici, juste l'habillage
   métal. */
.trophy-last.is-platinum .trophy-icon {
  border-color: var(--plat-hi);
  box-shadow:
    0 0 0 1px color-mix(in srgb, var(--plat-lo) 60%, transparent),
    0 0 18px -2px color-mix(in srgb, var(--plat-mid) 75%, transparent);
}

.trophy-last.is-platinum .trophy-glow { opacity: .75; }

/* Colonne de texte : de la marge à droite pour laisser sa place au numéro
   de rang (désormais très large), et assez de largeur (flex: 1) pour que
   le titre ait la place de courir sur deux lignes plutôt que de rester
   coincé sur la largeur du contenu d'un trophée classique. */
.trophy-last.is-platinum .trophy-text {
  flex: 1 1 auto;
  min-width: 0;
  padding-right: clamp(64px, 36cqw, 150px);
}

/* Titre du platine : en grand, quitte à courir sur deux lignes —
   contrairement au nom d'un trophée classique, il ne s'agit pas de le
   confirmer d'un coup d'œil mais de le mettre en avant. */
.trophy-last.is-platinum .trophy-name {
  font-size: clamp(1.06rem, min(15cqh, 3.7cqw), 1.42rem);
  font-weight: 800;
  line-height: 1.15;
  white-space: normal;
  overflow: visible;
  text-overflow: clip;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
}

/* Numéro de rang : plaqué à droite sur toute la hauteur de la case, comme
   un dossard — traitement volontairement graphique et engagé, à la manière
   d'un panneau de position en Formule 1 (chiffre italique très gras, liseré
   de couleur en dessous), plutôt qu'une simple étiquette de plus dans le
   bloc de texte. N'existe que pour les platines, et seulement une fois que
   la fonction serveur (psnSync) écrit le champ `rank` dans Firestore : tant
   qu'il est absent, l'élément reste vide et donc masqué (:empty, règle
   générique plus bas). */
.trophy-last.is-platinum .trophy-rank:not(:empty) {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  justify-content: center;
  position: absolute;
  inset: 0 8px 0 auto;
  font-size: clamp(1.9rem, min(74cqh, 22cqw), 5.4rem);
  font-weight: 900;
  font-style: italic;
  letter-spacing: -0.04em;
  line-height: .82;
  color: var(--text);
  text-shadow: 0 1px 2px color-mix(in srgb, var(--plat-hi) 55%, transparent);
}

.trophy-last.is-platinum .trophy-rank:not(:empty)::after {
  content: "";
  display: block;
  margin-top: .08em;
  width: 100%;
  height: .06em;
  border-radius: var(--r-full);
  background: linear-gradient(90deg, var(--plat-lo), var(--plat-hi));
  box-shadow: 0 0 8px color-mix(in srgb, var(--plat-hi) 65%, transparent);
}

/* Puces de carrousel : le numéro occupant maintenant toute la hauteur à
   droite, elles n'ont plus leur place habituelle en bout de rangée — on les
   pose en bande basse, centrées, hors du passage du texte comme du chiffre. */
.trophy-last.is-platinum .carousel-dots.vertical {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 4px;
  z-index: 2;
  flex-direction: row;
  justify-content: center;
  margin: 0;
}

/* Rangée, donc puce active large plutôt que haute — même bascule que la
   variante mobile (voir plus bas @media max-width: 560px). */
.trophy-last.is-platinum .carousel-dots.vertical .carousel-dot.is-active {
  width: 16px;
  height: 6px;
}

/* Rang à vie du platine, générique : masqué tant qu'aucune donnée (voir
   plus haut pour l'habillage spécifique aux platines). */
.trophy-rank {
  display: none;
  font-size: clamp(1.3rem, min(18cqh, 7cqw), 2.1rem);
  font-weight: 800;
  letter-spacing: -0.03em;
  line-height: 1;
  font-variant-numeric: tabular-nums;
}

.trophy-rank:empty { display: none; }

/* Entrée : le bloc se pose et s'allume une fois. */
.trophy-last.plat-enter { animation: plat-enter .9s var(--ease-soft); }

@keyframes plat-enter {
  0%   { transform: scale(.985); box-shadow: 0 0 0 0 color-mix(in srgb, var(--plat-hi) 60%, transparent); }
  45%  { transform: scale(1.012); box-shadow: 0 0 26px 3px color-mix(in srgb, var(--plat-hi) 45%, transparent); }
  100% { transform: none; }
}

/* --- Texte isolé posé directement sur un fond --- */
.tile.has-bg .tile-foot,
.tile.has-wx .tile-foot {
  color: var(--text);
  font-weight: 560;
}

.tile.has-bg .tile-next-when,
.tile.has-wx .weather-desc {
  color: var(--text-2);
  font-weight: 550;
}

/* Le sous-titre de la carte film était le plus exposé : plein cadre sur
   l'affiche, en gris le plus clair. */
.tile.has-bg .poster-meta { color: var(--text-2); }

/* Mention légale : discrète, mais elle doit rester lisible. */
.weather-credit {
  color: var(--text-2);
  opacity: .8;
  padding: 2px 6px;
  border-radius: var(--r-full);
  background: color-mix(in srgb, var(--surface) 62%, transparent);
}

/* Le fond du trophée reste une ambiance, pas une image de fond. */
.trophy-bg.is-visible { opacity: .38; }
.tile:hover .trophy-bg.is-visible { opacity: .52; }

:root[data-theme="dark"] .trophy-bg.is-visible { opacity: .3; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .trophy-bg.is-visible { opacity: .3; }
}

@media (max-width: 560px) {
  .trophy-last { padding: 8px 10px; }
}

/* ==========================================================================
   ADAPTATION DU CONTENU À LA TAILLE DE LA TUILE

   Deux mécanismes complémentaires :

   1. Des tailles exprimées en unités de conteneur (cqh / cqw). Le chiffre
      principal, l'icône ou l'affiche se mettent à l'échelle en continu.
      Chaque cqh est bridé par un cqw dans un min() : en colonne unique
      (mobile), la tuile n'est conteneur qu'en largeur et cqh retomberait
      sur la hauteur de la fenêtre.

   2. Trois paliers d'abandon. Un élément marqué `fit-xl` s'efface dès que la
      tuile se resserre, `fit-lg` un cran plus tard, `fit-md` en dernier.
      L'information disparaît proprement au lieu d'être coupée en deux.
   ========================================================================== */

/* --- Paliers d'abandon --- */
@container tile (max-height: 245px) { .fit-xl { display: none !important; } }
@container tile (max-width: 235px)  { .fit-xl { display: none !important; } }

@container tile (max-height: 185px) { .fit-lg { display: none !important; } }
@container tile (max-width: 172px)  { .fit-lg { display: none !important; } }

@container tile (max-height: 132px) { .fit-md { display: none !important; } }
@container tile (max-width: 124px)  { .fit-md { display: none !important; } }

/* --- Mise à l'échelle continue --- */
.tile-icon {
  width:  clamp(26px, min(15cqh, 22cqw), 38px);
  height: clamp(26px, min(15cqh, 22cqw), 38px);
  font-size: clamp(.75rem, min(7cqh, 10cqw), 1.15rem);
}

.tile-name { font-size: clamp(.72rem, min(6cqh, 7cqw), .95rem); }

.tile-value { font-size: clamp(1.25rem, min(22cqh, 26cqw), 3rem); }
.tile-value.sm { font-size: clamp(1rem, min(13cqh, 16cqw), 1.5rem); }

.tile-unit { font-size: clamp(.62rem, min(5cqh, 6cqw), .8rem); }
.tile-foot { font-size: clamp(.68rem, min(5cqh, 6cqw), .81rem); }

.tile-next-title { font-size: clamp(.92rem, min(11cqh, 12cqw), 1.35rem); }
.tile-next-when  { font-size: clamp(.68rem, min(5cqh, 6cqw), .84rem); }

.tile-spark { height: clamp(16px, min(18cqh, 20cqw), 46px); }
.tile-ring  { width: clamp(34px, min(26cqh, 30cqw), 54px); height: clamp(34px, min(26cqh, 30cqw), 54px); }

.weather-glyph { font-size: clamp(1.3rem, min(24cqh, 24cqw), 2.6rem); }
.weather-temp  { font-size: clamp(1rem, min(17cqh, 20cqw), 1.9rem); }

.trophy-icon   { width: clamp(52px, min(80cqh, 32cqw), 132px); height: clamp(52px, min(80cqh, 32cqw), 132px); }
.trophy-detail { font-size: clamp(.66rem, min(9cqh, 9cqw), .86rem); }
.next-due-img  { width: clamp(26px, min(18cqh, 20cqw), 38px); height: clamp(26px, min(18cqh, 20cqw), 38px); }

/* Le bandeau météo se serrait contre la mention Open-Meteo, en bas à droite. */
.tile.has-wx .tile-foot { padding-right: 76px; }

/* Matin / après-midi : seuils propres à la tuile météo, pas le palier
   `fit-xl` générique — il les aurait fait disparaître bien trop tôt. Le
   bloc se resserre d'abord (padding, police) avant de disparaître en tout
   dernier recours : « Aujourd'hui »/description tenant maintenant sur une
   seule ligne (voir .weather-block), il reste de la place pour le garder
   affiché dans une tuile bien plus petite qu'avant. */
@container tile (max-height: 195px) {
  .weather-part      { padding: 4px 7px; }
  .weather-part-label { font-size: .56rem; }
  .weather-part-val   { font-size: .78rem; }
}
@container tile (max-height: 145px) { .weather-parts { display: none !important; } }
@container tile (max-width: 150px)  { .weather-parts { display: none !important; } }

/* Ville personnalisée, « Aujourd'hui »/description, plage de températures et
   mention Open-Meteo : seuil propre à la météo (pas le `fit-lg` générique),
   nettement plus tardif — ce contenu est plus léger que celui des autres
   tuiles et tient dans un bien plus petit carré. */
@container tile (max-height: 130px) { .tile.has-wx .wx-fit { display: none !important; } }
@container tile (max-width: 140px)  { .tile.has-wx .wx-fit { display: none !important; } }

/* --- Resserrage général : padding et espacement.
   Une requête de conteneur ne peut pas styler son propre conteneur ; ces
   classes sont donc posées par home.js (observeTileFit). --- */
.tile.is-tight { padding: 14px; gap: 9px; }
.tile.is-tiny  { padding: 10px; gap: 6px; border-radius: var(--r-lg); }
.tile.is-tiny .tile-head { gap: 7px; }
.tile.is-tiny .tile-arrow,
.tile.is-tight .tile-arrow { right: 12px; bottom: 10px; }

/* Une tuile très basse n'a plus la place d'un bloc encadré : on retire le
   cadre plutôt que d'écraser le texte. */
@container tile (max-height: 160px) {
  .next-due, .all-clear, .trophy-last { padding: 0; background: none; border: none; backdrop-filter: none; }

  /* Le cadre platine tombe avec les autres : sa spécificité plus forte
     (.trophy-last.is-platinum) l'aurait laissé seul debout, cadre et ombre
     collés au texte dans une tuile qui n'a déjà plus la place de respirer. */
  .trophy-last.is-platinum {
    background: none;
    border: none;
    box-shadow: none;
    overflow: visible;
  }
  .trophy-last.is-platinum::after { display: none; }
}

/* En dessous, plus rien ne tient sur deux lignes : on force une seule ligne
   lisible et on coupe proprement. */
@container tile (max-height: 120px) {
  .tile-next-title { -webkit-line-clamp: 1; }
  .weather-main { gap: 8px; }
}

/* ==========================================================================
   AGENDA
   ========================================================================== */
/* --------------------------------------------------------------
   Fond décoratif de l'agenda

   Même principe que les ciels de la météo : tout est en CSS, donc aucune
   image à héberger, aucune licence à gérer, et le rendu suit le thème.
   Le dégradé et le pictogramme changent selon le type de rendez-vous —
   c'est ce qui remplace le fond gris quand un événement n'a pas d'image.
   -------------------------------------------------------------- */
.agenda-sky {
  position: absolute;
  inset: 0;
  z-index: 0;
  overflow: hidden;
  pointer-events: none;
  opacity: .85;
  transition: background 1.2s var(--ease-soft), opacity .8s var(--ease);
}

/* Grille décorative : suggère une page de calendrier — une texture, pas une
   vraie date. Deux jeux de lignes fines superposés au dégradé de couleur,
   en `overlay` pour rester lisibles aussi bien sur un ton clair que sombre. */
.agenda-sky::before {
  content: '';
  position: absolute;
  inset: 0;
  background-image:
    repeating-linear-gradient(90deg,
      rgba(255,255,255,.24) 0, rgba(255,255,255,.24) 1px,
      transparent 1px, transparent 14.2857%),
    repeating-linear-gradient(180deg,
      rgba(255,255,255,.24) 0, rgba(255,255,255,.24) 1px,
      transparent 1px, transparent 20%);
  mix-blend-mode: overlay;
}

/* Le vrai jour du mois, énorme et flouté derrière le contenu — seul élément
   de ce faux calendrier qui ne soit pas inventé. `scale` compense le flou :
   sans lui, `overflow:hidden` coupe net le dégradé sur les bords (même
   technique que `.trophy-bg`). */
.agenda-day-ghost {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  font-weight: 800;
  font-size: clamp(4rem, min(64cqh, 54cqw), 10rem);
  line-height: 1;
  color: rgba(255,255,255,.55);
  filter: blur(8px);
  transform: scale(1.15);
  pointer-events: none;
}

/* Pictogramme en filigrane, débordant volontairement du cadre. */
.agenda-glyph {
  position: absolute;
  right: -8px;
  bottom: -14px;
  font-size: clamp(3rem, min(52cqh, 46cqw), 7rem);
  line-height: 1;
  opacity: .16;
  transform: rotate(-8deg);
  transition: opacity .8s var(--ease), transform 1s var(--ease-soft);
}

.tile:hover .agenda-glyph { opacity: .22; transform: rotate(-4deg) scale(1.04); }

/* Ambiances par type d'événement.
   Le dégradé va jusqu'au bord (100%, pas 85%) : arrêté trop tôt, il laissait
   une bande plate et vide sur le reste de la tuile — c'est ce qui donnait
   l'impression de fond raté. La dernière teinte est volontairement très
   légère plutôt que transparente : la couleur couvre tout, sans écraser
   le texte qui passe par-dessus. */
.ag-default   { background: linear-gradient(160deg, #f9a8d4 0%, #fbcfe8 45%, color-mix(in srgb, #fbcfe8 22%, transparent) 100%); }
.ag-birthday  { background: linear-gradient(160deg, #fbbf24 0%, #fda4af 45%, color-mix(in srgb, #fda4af 22%, transparent) 100%); }
.ag-health    { background: linear-gradient(160deg, #7dd3fc 0%, #bae6fd 48%, color-mix(in srgb, #bae6fd 22%, transparent) 100%); }
.ag-food      { background: linear-gradient(160deg, #fdba74 0%, #fed7aa 48%, color-mix(in srgb, #fed7aa 22%, transparent) 100%); }
.ag-travel    { background: linear-gradient(160deg, #67e8f9 0%, #a5f3fc 48%, color-mix(in srgb, #a5f3fc 22%, transparent) 100%); }
.ag-sport     { background: linear-gradient(160deg, #86efac 0%, #bbf7d0 48%, color-mix(in srgb, #bbf7d0 22%, transparent) 100%); }
.ag-school    { background: linear-gradient(160deg, #c4b5fd 0%, #ddd6fe 48%, color-mix(in srgb, #ddd6fe 22%, transparent) 100%); }
.ag-work      { background: linear-gradient(160deg, #94a3b8 0%, #cbd5e1 48%, color-mix(in srgb, #cbd5e1 22%, transparent) 100%); }
.ag-fun       { background: linear-gradient(160deg, #f0abfc 0%, #f5d0fe 48%, color-mix(in srgb, #f5d0fe 22%, transparent) 100%); }
.ag-home      { background: linear-gradient(160deg, #fcd34d 0%, #fef3c7 48%, color-mix(in srgb, #fef3c7 22%, transparent) 100%); }
.ag-admin     { background: linear-gradient(160deg, #a8a29e 0%, #e7e5e4 48%, color-mix(in srgb, #e7e5e4 22%, transparent) 100%); }

/* Journée libre : une ambiance à elle, calme, plutôt qu'un fond générique
   laissé tel quel. Injoignable (calendrier hors-ligne) reste dans les gris. */
.ag-empty     { background: linear-gradient(160deg, #a5f3fc 0%, #bbf7d0 48%, color-mix(in srgb, #bbf7d0 22%, transparent) 100%); }
.ag-offline   { background: linear-gradient(160deg, #cbd5e1 0%, #e2e8f0 48%, color-mix(in srgb, #e2e8f0 22%, transparent) 100%); }

/* En thème sombre, ces pastels éblouiraient et mangeraient le texte. */
:root[data-theme="dark"] .agenda-sky { opacity: .38; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .agenda-sky { opacity: .38; }
}

/* L'illustration de l'événement passe par-dessus le dégradé. */
.agenda-photo { z-index: 0; }
.agenda-photo.is-visible { opacity: .42; }
.tile:hover .agenda-photo.is-visible { opacity: .55; }

:root[data-theme="dark"] .agenda-photo.is-visible { opacity: .3; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .agenda-photo.is-visible { opacity: .3; }
}

/* Quand une vraie image s'affiche, le filigrane n'a plus lieu d'être. */
.tile.has-photo-ev .agenda-glyph { opacity: 0; }

/* --------------------------------------------------------------
   Feux d'artifice — uniquement les jours d'anniversaire

   Pas de canvas ni de bibliothèque : trois éléments, chacun portant une
   couronne d'étincelles en box-shadow. Animer leur échelle propulse toute
   la couronne vers l'extérieur, ce qui suffit à faire une gerbe. Une
   tablette allumée en permanence n'a pas à faire tourner un canvas pour ça.
   -------------------------------------------------------------- */
.agenda-fx {
  position: absolute;
  inset: 0;
  z-index: 2;
  overflow: hidden;
  pointer-events: none;
  opacity: 0;
  transition: opacity .6s var(--ease);
}

.tile.is-party .agenda-fx { opacity: 1; }

.fw {
  position: absolute;
  width: 3px;
  height: 3px;
  border-radius: 50%;
  opacity: 0;
  /* Couronne de 12 étincelles, réparties tous les 30°, rayon 26 px. */
  box-shadow:
     26px    0px 0 0 #fbbf24,   22px   13px 0 0 #f472b6,
     13px   22px 0 0 #38bdf8,    0px   26px 0 0 #a78bfa,
    -13px   22px 0 0 #fbbf24,  -22px   13px 0 0 #34d399,
    -26px    0px 0 0 #f472b6,  -22px  -13px 0 0 #38bdf8,
    -13px  -22px 0 0 #fde047,    0px  -26px 0 0 #a78bfa,
     13px  -22px 0 0 #34d399,   22px  -13px 0 0 #fb7185;
}

.tile.is-party .fw { animation: fw-burst 2.4s ease-out infinite; }

.fw1 { left: 22%; top: 30%; }
.fw2 { left: 68%; top: 22%; animation-delay: .8s !important; }
.fw3 { left: 46%; top: 52%; animation-delay: 1.6s !important; }

@keyframes fw-burst {
  0%   { transform: scale(.15); opacity: 0; }
  12%  { opacity: 1; }
  55%  { transform: scale(1.5); opacity: .85; }
  100% { transform: scale(2.1); opacity: 0; }
}

/* La pastille d'anniversaire pulse doucement le jour J. */
.tile.is-party .agenda-bday {
  animation: bday-glow 2.6s ease-in-out infinite;
  border-color: color-mix(in srgb, #f59e0b 55%, transparent);
}

@keyframes bday-glow {
  50% { background: color-mix(in srgb, #f59e0b 24%, transparent); }
}

@media (prefers-reduced-motion: reduce) {
  .tile.is-party .fw { animation: none; opacity: .5; transform: scale(1.2); }
  .tile.is-party .agenda-bday { animation: none; }
}

/* Jusqu'à 3 rendez-vous empilés en même temps, plutôt qu'un seul affiché à
   la fois : sur un mur, mieux vaut voir d'un coup d'œil ce qui arrive dans
   la semaine qu'attendre le tour de chaque événement.

   `flex: 1` (plutôt que l'ancien `margin-top: auto`, qui ne faisait que
   pousser un bloc à sa taille naturelle vers le bas) : ce bloc s'étire pour
   occuper tout l'espace vertical restant sous l'en-tête, jusqu'à
   l'anniversaire/pied de tuile. Avec un ou deux rendez-vous seulement, les
   cartes se répartissent alors sur toute cette hauteur au lieu de laisser
   un vide entre elles. */
.agenda-main {
  display: flex;
  flex-direction: column;
  flex: 1;
  min-height: 0;
  position: relative;
  z-index: 1;
  min-width: 0;
}

.agenda-list {
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  flex: 1;
  gap: clamp(4px, 1.6cqh, 9px);
  min-height: 0;
  min-width: 0;
}

/* Chaque rendez-vous dans son propre cadre — comme une mini-carte plutôt
   qu'une ligne de texte nue, pour que la liste remplisse vraiment le bloc
   au lieu de flotter dedans. */
.agenda-row {
  display: flex;
  align-items: center;
  flex: 1;
  gap: clamp(6px, 2cqw, 10px);
  padding: clamp(3px, 1.4cqh, 7px) clamp(6px, 2cqw, 10px);
  border-radius: var(--r-sm);
  background: color-mix(in srgb, var(--ev-color, var(--accent)) 9%, transparent);
  border: 1px solid color-mix(in srgb, var(--ev-color, var(--accent)) 24%, transparent);
  min-width: 0;
  min-height: 0;
}

/* Le chiffre du jour, dans son petit carré — repris de l'ancienne pastille
   unique, juste redimensionné pour tenir trois fois dans la même tuile. */
.agenda-row-badge {
  flex-shrink: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  min-width: clamp(24px, min(15cqh, 15cqw), 38px);
  padding: clamp(2px, 1cqh, 5px) clamp(3px, 1.1cqw, 6px);
  border-radius: var(--r-xs);
  background: color-mix(in srgb, var(--ev-color, var(--accent)) 16%, transparent);
  line-height: 1;
}

.agenda-row-day {
  font-size: clamp(.7rem, min(8.5cqh, 9.5cqw), 1.15rem);
  font-weight: 700;
  letter-spacing: -0.03em;
  font-variant-numeric: tabular-nums;
  color: var(--ev-color, var(--accent));
}

.agenda-row-month {
  margin-top: 1px;
  font-size: clamp(.42rem, min(3cqh, 3.4cqw), .55rem);
  font-weight: 700;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--text-3);
}

/* Aujourd'hui se distingue par un carré plein plutôt qu'un mot en plus :
   la couleur suffit, pas besoin d'une ligne de texte supplémentaire. */
.agenda-row.is-today .agenda-row-badge {
  background: var(--ev-color, var(--accent));
}
.agenda-row.is-today .agenda-row-day,
.agenda-row.is-today .agenda-row-month {
  color: #fff;
}

.agenda-row-block {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 1px;
  line-height: 1.2;
}

.agenda-row-title {
  min-width: 0;
  font-size: clamp(.68rem, min(6cqh, 6.4cqw), .92rem);
  font-weight: 640;
  color: var(--text);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.agenda-row-hour {
  min-width: 0;
  font-size: clamp(.58rem, min(4.8cqh, 5.2cqw), .78rem);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  color: var(--text-3);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Paliers propres à la liste empilée : tout rétrécit d'abord en continu
   (cqh/cqw ci-dessus) ; seulement si la tuile devient vraiment minuscule,
   l'heure cède la place en premier (le jour + le titre suffisent), puis la
   3e ligne, puis la 2e — il reste toujours au moins UN rendez-vous visible,
   quelle que soit la taille de la tuile (contrairement à l'ancien
   `fit-lg`/`fit-xl`, qui aurait tout fait disparaître d'un coup). */
@container tile (max-height: 190px) {
  .agenda-row-hour { display: none; }
}
@container tile (max-height: 150px) {
  .agenda-row:nth-child(3) { display: none; }
}
@container tile (max-height: 112px) {
  .agenda-row:nth-child(2) { display: none; }
}

/* --- Prochain anniversaire, en permanence sous le carrousel --- */
.agenda-bday {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 6px 10px;
  border-radius: var(--r-full);
  background: color-mix(in srgb, #f59e0b 13%, transparent);
  border: 1px solid color-mix(in srgb, #f59e0b 30%, transparent);
  position: relative;
  z-index: 1;
  min-width: 0;
}

.agenda-bday-icon { font-size: clamp(.8rem, min(6cqh, 7cqw), 1rem); flex-shrink: 0; line-height: 1; }

.agenda-bday-name {
  flex: 1;
  min-width: 0;
  font-size: clamp(.7rem, min(5.5cqh, 6cqw), .88rem);
  font-weight: 650;
  color: var(--text);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.agenda-bday-when {
  flex-shrink: 0;
  font-size: clamp(.62rem, min(4.8cqh, 5.4cqw), .76rem);
  font-weight: 700;
  color: #b45309;
  white-space: nowrap;
}

:root[data-theme="dark"] .agenda-bday-when { color: #fbbf24; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .agenda-bday-when { color: #fbbf24; }
}

/* Journée libre : ce n'est pas une anomalie à signaler, c'est une bonne
   nouvelle. D'où le ton posé plutôt que le bandeau d'alerte. */
.agenda-empty {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-top: auto;
  padding: 12px 14px;
  border-radius: var(--r-md);
  background: color-mix(in srgb, var(--accent) 8%, transparent);
  border: 1px dashed color-mix(in srgb, var(--accent) 28%, transparent);
  position: relative;
  z-index: 1;
  min-width: 0;
}

.agenda-empty-icon {
  font-size: clamp(1.1rem, min(15cqh, 16cqw), 1.6rem);
  line-height: 1;
  flex-shrink: 0;
}

.agenda-empty-text {
  font-size: clamp(.8rem, min(6.5cqh, 7cqw), 1rem);
  font-weight: 640;
  letter-spacing: -0.015em;
  color: var(--text);
  overflow: hidden;
  text-overflow: ellipsis;
}

/* ==========================================================================
   PHOTOS
   ========================================================================== */
/* Le fondu enchaîné est celui de crossfadeBg() : deux calques .xf-layer
   empilés dont on croise l'opacité. Ici l'image est le sujet, pas une
   ambiance : elle monte donc à pleine opacité. */
.photo-stage {
  position: absolute;
  inset: 0;
  z-index: 0;
  overflow: hidden;
  border-radius: inherit;
  opacity: 0;
  transition: opacity 1s var(--ease-soft);
}

.photo-stage.is-visible { opacity: 1; }
.photo-stage .xf-layer { background-position: center; }

/* Affiche de film (tuile Divertissements) : le cadrage ambiant partagé
   (« center 22% ») est pensé pour des tuiles larges et basses. Une affiche
   est verticale — visage/titre dans le tiers haut — et la tuile peut, elle,
   devenir étroite et haute (ex. 2 de large sur 4 de haut). On remonte donc
   le point de cadrage pour garder cette zone visible quel que soit le
   rognage que `cover` applique sur les bords. */
[data-widget="entertain"] .xf-layer { background-position: center 12%; }

/* Voile bas : le nom de l'auteur et la légende doivent rester lisibles sur
   n'importe quelle photo — sans pour autant recouvrir la photo elle-même.
   C'est l'ombre portée du texte qui fait le gros du travail, pas le voile. */
.tile.has-photo::before {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 0;
  background: linear-gradient(
    to top,
    color-mix(in srgb, var(--surface) 84%, transparent) 0%,
    color-mix(in srgb, var(--surface) 32%, transparent) 30%,
    transparent 56%
  );
  pointer-events: none;
}

.tile.has-photo .tile-head,
.tile.has-photo .photo-caption {
  text-shadow:
    0 1px 3px color-mix(in srgb, var(--surface) 90%, transparent),
    0 2px 12px color-mix(in srgb, var(--surface) 70%, transparent);
}

/* Les voiles de lisibilité doivent passer AU-DESSUS du visuel.
   Un ::before est peint comme le premier enfant : à z-index égal, l'affiche
   ou la photo, qui vient après dans le DOM, le recouvrait. Un cran suffit —
   le texte, lui aussi en z-index 1 mais plus bas dans le DOM, reste au-dessus. */
.tile.has-photo::before,
.tile.has-bg::before,
.tile.has-wx::before { z-index: 1; }

.photo-caption {
  margin-top: auto;
  display: flex;
  flex-direction: column;
  gap: 2px;
  position: relative;
  z-index: 1;
  min-width: 0;
}

.photo-by {
  font-size: clamp(.58rem, min(4.5cqh, 5.5cqw), .68rem);
  font-weight: 700;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--accent);
}

.photo-text {
  font-size: clamp(.76rem, min(6cqh, 7cqw), .95rem);
  font-weight: 600;
  color: var(--text);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.photo-empty {
  margin-top: auto;
  display: flex;
  align-items: center;
  gap: 11px;
  position: relative;
  z-index: 1;
  min-width: 0;
}

.photo-empty-icon {
  font-size: clamp(1.1rem, min(14cqh, 15cqw), 1.7rem);
  flex-shrink: 0;
  opacity: .85;
}

/* Titre + sous-titre des états vides : une accroche, puis l'explication en
   plus discret — qui s'efface la première quand la tuile rétrécit. */
.photo-empty .tile-foot { display: flex; flex-direction: column; gap: 1px; min-width: 0; }

.empty-title {
  font-size: clamp(.8rem, min(6.5cqh, 7cqw), .99rem);
  font-weight: 640;
  letter-spacing: -0.015em;
  color: var(--text);
}

.empty-sub {
  font-size: clamp(.66rem, min(4.8cqh, 5.6cqw), .79rem);
  font-weight: 500;
  color: var(--text-2);
  overflow: hidden;
  text-overflow: ellipsis;
}

/* ==========================================================================
   NOTES
   ========================================================================== */
/* Une note occupe toute la place disponible : un mot laissé sur le frigo se
   lit de loin, il n'a pas à se serrer dans un coin. Au-delà d'une note, elles
   défilent — empiler trois papiers minuscules ne se lisait de nulle part. */
.note-stack {
  display: flex;
  flex: 1;
  min-height: 0;
  padding: 2px 3px;        /* laisse respirer l'ombre et la légère inclinaison */
  position: relative;
  z-index: 1;
  overflow: hidden;
}

/* Le post-it EST le widget, pas une carte posée dans un cadre : aucun
   bandeau flottant, aucune marge de tuile pour le distinguer des autres
   widgets — le papier et sa couleur suffisent à le reconnaître. */
[data-widget="notes"] { padding: 0; }

/* Le bandeau d'invitation (aucune note configurée) retrouve la marge que le
   post-it n'a plus besoin de garder, puisqu'il est alors seul contenu visible
   de la tuile. */
[data-widget="notes"] .hub-cta { margin: 20px; }

/* Un vrai papier collé, pas une ligne de tableau : le fond reste clair même
   en thème sombre — un Post-it noir n'existe pas — et c'est l'encre qui
   assure le contraste. Coins presque francs (la tuile n'a plus de cadre à
   habiller) et légèrement irréguliers pour casser l'effet « rectangle ». */
.note-card {
  position: relative;
  flex: 1;
  min-width: 0;
  min-height: 0;
  display: flex;
  flex-direction: column;
  padding: clamp(14px, 5cqh, 22px) clamp(16px, 5cqw, 24px);
  border: none;
  border-radius: var(--r-xl);
  background: linear-gradient(
    158deg,
    color-mix(in srgb, var(--note-color, #fde68a) 88%, #fff) 0%,
    var(--note-color, #fde68a) 100%
  );
  box-shadow:
    0 1px 1px rgba(0, 0, 0, .07),
    0 8px 16px -10px color-mix(in srgb, var(--note-color, #fde68a) 85%, #000 25%);
}


/* Coin corné */
.note-card::after {
  content: '';
  position: absolute;
  right: 0; bottom: 0;
  width: 15px; height: 15px;
  border-bottom-right-radius: 4px;
  background: linear-gradient(
    135deg,
    transparent 50%,
    color-mix(in srgb, var(--note-color, #fde68a) 72%, #000 18%) 50%
  );
}

/* Centre le mot verticalement dans l'espace laissé par la signature — séparé
   de `.note-text` car `display:-webkit-box` (nécessaire au troncage multi-
   ligne juste en dessous) ne peut pas aussi servir de conteneur flex. */
.note-body {
  flex: 1;
  min-height: 0;
  display: flex;
  align-items: center;
}

.note-text {
  font-family: ui-serif, Georgia, 'Iowan Old Style', 'Times New Roman', serif;
  /* `--note-len-scale` (posé par home.js selon la longueur du mot) réduit la
     taille pour qu'un message long tienne, plutôt que d'être tronqué. */
  font-size: calc(clamp(.9rem, min(11cqh, 10cqw), 1.9rem) * var(--note-len-scale, 1));
  font-weight: 400;
  line-height: 1.38;
  color: #3f3729;                     /* encre, indépendante du thème */
  display: -webkit-box;
  -webkit-line-clamp: 6;
  -webkit-box-orient: vertical;
  overflow: hidden;
  white-space: pre-line;
}

/* Toujours en pied de post-it, quelle que soit la longueur du mot au-dessus :
   `.note-body{flex:1}` pousse ce bloc en bas. */
.note-foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  margin-top: 8px;
}

.note-by {
  display: block;
  font-size: clamp(.6rem, min(4.5cqh, 5.5cqw), .72rem);
  font-weight: 600;
  font-style: italic;
  letter-spacing: .01em;
  color: rgba(63, 55, 41, .62);
}

.note-by::before { content: '— '; }

/* Note anonyme : pas de tiret orphelin. */
.note-by:empty { display: none; }

/* En thème sombre, un jaune vif au mur éblouit : on baisse d'un ton. */
:root[data-theme="dark"] .note-card { filter: brightness(.88) saturate(.92); }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .note-card { filter: brightness(.88) saturate(.92); }
}

/* --- Note fantôme : la tuile n'est jamais vraiment vide --- */
.note-card.is-ghost {
  background: color-mix(in srgb, var(--note-color, #fde68a) 12%, var(--surface));
  border: 1px dashed color-mix(in srgb, var(--note-color, #fde68a) 55%, transparent);
  box-shadow: none;
  transform: rotate(-.35deg);
  text-align: center;
  filter: none;
}

.note-card.is-ghost::after { display: none; }

.note-card.is-ghost .note-text {
  color: var(--text-2);
  font-style: italic;
}

:root[data-theme="dark"] .note-card.is-ghost { filter: none; }

/* Le nombre de lignes lisibles suit la hauteur du papier. Le pied (signature
   + points) ne libère plus de bandeau de tête à soustraire : la tuile a plus
   de lignes disponibles qu'avant à taille égale. */
@container tile (max-height: 220px) { .note-text { -webkit-line-clamp: 5; } }
@container tile (max-height: 170px) { .note-text { -webkit-line-clamp: 3; } }
@container tile (max-height: 130px) { .note-text { -webkit-line-clamp: 2; } }
@container tile (max-height: 100px) { .note-text { -webkit-line-clamp: 1; } }

/* ==========================================================================
   ESPACE PARTAGÉ — bandeau d'invitation quand rien n'est configuré
   ========================================================================== */
.hub-cta {
  margin-top: auto;
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 12px;
  border-radius: var(--r-md);
  background: color-mix(in srgb, var(--accent) 10%, transparent);
  border: 1px dashed color-mix(in srgb, var(--accent) 38%, transparent);
  position: relative;
  z-index: 1;
  font-size: .8rem;
  color: var(--text-2);
}

.hub-chip {
  margin-left: auto;
  padding: 3px 9px;
  border-radius: var(--r-full);
  background: color-mix(in srgb, var(--accent) 16%, transparent);
  color: var(--accent);
  font-size: .64rem;
  font-weight: 700;
  letter-spacing: .04em;
  max-width: 40%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.hub-chip.hidden { display: none; }

/* ==========================================================================
   MODE AFFICHAGE (tablette murale)

   Objectif : une page qui se regarde de loin. La navigation disparaît,
   l'horloge prend la place du titre, les tuiles gagnent en contraste, et
   le moindre geste rend la main à l'utilisateur.
   ========================================================================== */
/* Le curseur doit disparaître PARTOUT, pas seulement sur le fond : chaque
   lien, bouton ou tuile impose sinon le sien (main, flèche, barre en I) et
   il resurgit dès qu'on le survole. D'où le sélecteur universel. */
body.is-display,
body.is-display * { cursor: none !important; }

body.is-display,
body.is-display-preview { overflow: hidden; }

/* Aperçu hors plein écran (édition de la disposition « Mode affichage ») :
   reprend la mise en page du vrai plein écran, SAUF le curseur, la barre de
   réglages (qui contient le sélecteur de disposition lui-même — la masquer
   rendrait impossible de revenir en arrière) et la barre d'édition — sans
   eux, impossible de rien régler. */
/* La salutation personnelle n'a rien à faire sur un mur : l'écran s'adresse
   à la maison, pas à celui qui l'a configuré. Le nom de l'espace partagé
   prend sa place, à côté de l'horloge. */
body.is-display .appnav,
body.is-display .hero-sub,
body.is-display .hero-line,
body.is-display .hero-title,
body.is-display-preview .hero-sub,
body.is-display-preview .hero-line,
body.is-display-preview .hero-title { display: none !important; }

body.is-display .edit-bar { display: none !important; }

body.is-display .home,
body.is-display-preview .home {
  max-width: none;
  padding: clamp(14px, 2.2vw, 30px);
  display: flex;
  flex-direction: column;
  gap: clamp(10px, 1.6vh, 22px);
}

body.is-display .home { height: 100dvh; }

/* En aperçu, la barre de réglages reste visible (il faut pouvoir cliquer
   dessus) : elle garde donc sa hauteur en haut de page, contrairement au
   vrai plein écran où elle disparaît. On en tient compte ici pour que
   l'aperçu tienne pile dans l'écran, sans bas de grille coupé. */
body.is-display-preview .home {
  height: calc(100dvh - var(--nav-h) - env(safe-area-inset-top, 0px));
}

body.is-display .hero,
body.is-display-preview .hero { margin: 0; flex-shrink: 0; }

/* La grille occupe tout ce qui reste : les rangées s'étirent au lieu de
   laisser un vide en bas d'écran. */
/* Les rangées implicites passent en 1fr : dans un conteneur de hauteur fixe,
   elles se partagent l'espace et la grille remplit l'écran, quel que soit le
   nombre de tuiles. */
body.is-display .bento,
body.is-display-preview .bento {
  flex: 1;
  min-height: 0;
  grid-auto-rows: minmax(0, 1fr);
}

/* --------------------------------------------------------------------
   Transition « shuffle » entre deux pages du mode affichage (demande
   explicite : "un effet de shuffle vers la gauche"). Voir shuffleToNextPage
   dans home.js : la page qui s'en va glisse vers la gauche en s'estompant,
   puis la nouvelle est dessinée hors champ à droite et glisse jusqu'à sa
   place. Scopé au mode affichage réel/aperçu : n'affecte jamais la
   disposition « classique ». Durée synchronisée avec SHUFFLE_OUT_MS (home.js).
   -------------------------------------------------------------------- */
body.is-display .bento,
body.is-display-preview .bento {
  transition: transform 420ms var(--ease), opacity 420ms var(--ease);
}
body.is-display .bento.is-page-shuffle-out,
body.is-display-preview .bento.is-page-shuffle-out { transform: translateX(-48px); opacity: 0; }

/* Positionnement instantané (SANS transition) juste avant l'animation retour — sinon la nouvelle
   page glisserait en diagonale depuis la position -48px de l'ancienne au lieu d'apparaître déjà à
   +48px, prête à revenir de la droite. */
body.is-display .bento.is-page-shuffle-in,
body.is-display-preview .bento.is-page-shuffle-in {
  transition: none;
  transform: translateX(48px);
  opacity: 0;
}

@media (prefers-reduced-motion: reduce) {
  body.is-display .bento,
  body.is-display-preview .bento { transition-duration: .001ms !important; }
}

/* ==========================================================================
   ÉDITION DANS L'APERÇU — l'aperçu et l'édition sont incompatibles

   L'aperçu enferme la grille dans la hauteur de l'écran et répartit les
   rangées en 1fr. Excellent pour juger du rendu final, catastrophique pour
   modifier quoi que ce soit :

     • agrandir une tuile ne change RIEN à l'écran, puisque toutes les
       rangées se repartagent la même hauteur totale ;
     • ajouter une tuile écrase toutes les autres, jusqu'à les faire
       paraître superposées ;
     • la page ne peut plus défiler (overflow: hidden), donc une tuile
       poussée vers le bas devient inatteignable.

   Pendant l'édition, on rend donc la main à la mise en page normale :
   rangées de 78 px et page qui défile. L'aperçu reprend ses droits dès
   qu'on appuie sur Terminer.
   ========================================================================== */
body.is-editing.is-display-preview { overflow: auto; }

body.is-editing.is-display-preview .home {
  height: auto;
  display: block;
  padding: 52px 20px 120px;   /* place pour la barre d'édition flottante */
}

body.is-editing.is-display-preview .bento {
  flex: none;
  grid-auto-rows: 78px;
}

/* L'horloge géante mangerait la moitié de l'écran de travail. */
body.is-editing.is-display-preview .display-clock { display: none; }

body.is-editing.is-display-preview .hero-line,
body.is-editing.is-display-preview .hero-title { display: block !important; }

body.is-display .tile:hover,
body.is-display-preview .tile:hover { transform: none; box-shadow: var(--shadow-sm); }
body.is-display .tile-arrow,
body.is-display-preview .tile-arrow { display: none; }
body.is-display .aurora-blob,
body.is-display-preview .aurora-blob { opacity: .5; }

/* --- Horloge --- */
.display-clock {
  display: none;
  align-items: baseline;
  gap: clamp(10px, 1.6vw, 22px);
  flex-wrap: wrap;
}

body.is-display .display-clock,
body.is-display-preview .display-clock { display: flex; }

.display-time {
  font-size: clamp(2.6rem, 8vw, 6rem);
  font-weight: 700;
  letter-spacing: -0.05em;
  line-height: .95;
  font-variant-numeric: tabular-nums;
  background: linear-gradient(115deg, var(--primary), #a855f7 60%, #10b981);
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
}

.display-block { display: flex; flex-direction: column; gap: 3px; min-width: 0; }

/* Nom de l'espace partagé — l'identité du mur.
   Même traitement que l'horloge, mais dégradé inversé : les deux se
   répondent sans se copier.

   La taille est délibérément calée sous celle de l'heure (3,4vw contre 8vw,
   et 2,9rem contre 6rem en plafond) : le ratio tient à toutes les largeurs
   d'écran, donc c'est toujours l'heure qui fixe la hauteur de l'en-tête.
   Agrandir le nom ne repousse jamais la grille de tuiles. */
.display-space {
  font-size: clamp(1.35rem, 3.4vw, 2.9rem);
  font-weight: 700;
  letter-spacing: -0.035em;
  line-height: 1.05;
  background: linear-gradient(115deg, #10b981, #a855f7 40%, var(--primary));
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  /* Sans padding, les jambages des g/p/y sont rognés par le background-clip. */
  padding-bottom: .08em;
}

.display-space:empty { display: none; }

.display-date {
  font-size: clamp(.85rem, 1.6vw, 1.35rem);
  font-weight: 620;
  letter-spacing: .04em;
  text-transform: capitalize;
  color: var(--text-2);
}

/* --- Diaporama plein écran --- */
.display-slideshow {
  position: fixed;
  inset: 0;
  z-index: 900;
  display: none;
  background: #000;
}

body.is-display.display-photos .display-slideshow { display: block; }
body.is-display.display-photos .home { visibility: hidden; }

.display-slideshow .xf-layer { z-index: 0; background-position: center; }

.slideshow-overlay {
  z-index: 2;
  position: absolute;
  left: 0; right: 0; bottom: 0;
  padding: clamp(20px, 4vw, 52px);
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 24px;
  color: #fff;
  background: linear-gradient(to top, rgba(0,0,0,.72), transparent);
  text-shadow: 0 2px 14px rgba(0,0,0,.6);
}

.slideshow-clock {
  font-size: clamp(2.4rem, 7vw, 5.4rem);
  font-weight: 700;
  letter-spacing: -0.05em;
  line-height: 1;
  font-variant-numeric: tabular-nums;
}

.slideshow-meta { text-align: right; font-size: clamp(.8rem, 1.5vw, 1.15rem); opacity: .92; }
.slideshow-meta strong { display: block; font-size: clamp(.95rem, 1.8vw, 1.4rem); font-weight: 660; }

/* --- Voile d'annonce à l'entrée du mode --- */
.display-hint {
  position: fixed;
  left: 50%;
  bottom: 24px;
  transform: translateX(-50%);
  z-index: 950;
  padding: 9px 18px;
  border-radius: var(--r-full);
  background: color-mix(in srgb, var(--surface) 88%, transparent);
  border: 1px solid var(--border);
  font-size: .8rem;
  color: var(--text-2);
  animation: hint-out 4s var(--ease) forwards;
  pointer-events: none;
}

@keyframes hint-out {
  0%, 70% { opacity: 1; }
  100%    { opacity: 0; visibility: hidden; }
}

@media (prefers-reduced-motion: reduce) {
  .photo-stage { transition: opacity .01ms; }
  .display-hint { animation: none; opacity: .9; }
}

/* ==========================================================================
   RÉGLAGES — sections « Mode affichage » et « Espace partagé »
   ========================================================================== */
/* Métriques communes à tous les contrôles du panneau : voir la section
   HARMONISATION DU PANNEAU DE RÉGLAGES dans theme.css. */
.settings-select {
  border-radius: var(--r-sm);
  border: 1px solid var(--border);
  background: var(--surface-2);
  color: var(--text);
  font-family: inherit;
}

.settings-note {
  margin: 8px 0 0;
  font-size: .72rem;
  line-height: 1.45;
  color: var(--text-3);
}

.settings-link {
  display: block;
  width: 100%;
  margin-top: 8px;
  padding: 9px 12px;
  border-radius: var(--r-sm);
  border: 1px solid var(--border);
  background: var(--surface-2);
  color: var(--text);
  font-family: inherit;
  font-size: .82rem;
  font-weight: 600;
  text-align: center;
  text-decoration: none;
  cursor: pointer;
  box-sizing: border-box;
}

.settings-link:hover { background: var(--primary); color: var(--primary-contrast); border-color: transparent; }

/* --- Pastilles de page (mode affichage à pages multiples) ------------ */
.settings-pages { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 8px; }
.settings-page-pill,
.settings-page-add,
.settings-page-del {
  padding: 6px 12px;
  border-radius: var(--r-full);
  border: 1px solid var(--border);
  background: var(--surface-2);
  color: var(--text-2);
  font-family: inherit;
  font-size: .78rem;
  font-weight: 600;
  cursor: pointer;
}
.settings-page-pill.is-active { background: var(--primary); color: var(--primary-contrast); border-color: transparent; }
.settings-page-add, .settings-page-del { padding: 6px 10px; }
.settings-page-pill:disabled, .settings-page-add:disabled, .settings-page-del:disabled {
  opacity: .5; cursor: default;
}

/* ==========================================================================
   LISIBILITÉ EN MODE AFFICHAGE

   Contre-intuitif mais logique : en plein écran, une tuile est souvent plus
   BASSE qu'en navigation normale. La grille doit faire tenir toutes les
   rangées dans un seul écran, alors qu'en mode classique la page défile —
   une tuile de 3 rangées passe ainsi de ~260 px à ~150 px de haut.

   Les tailles exprimées en `cqh` tombaient donc sur leur plancher, et le mur
   se retrouvait avec du texte de 11 px. Ici, on rééquilibre : le poids passe
   sur la largeur (les tuiles murales sont larges et courtes) et les planchers
   remontent — rien ne doit descendre sous ~15 px à trois mètres.
   ========================================================================== */

/* --- Trophées : c'est là que le contraste était le plus criant --- */
body.is-display .trophy-name,
body.is-display-preview .trophy-name {
  font-size: clamp(1.05rem, min(22cqh, 6cqw), 2rem);
  line-height: 1.15;
}

body.is-display .trophy-game,
body.is-display-preview .trophy-game {
  font-size: clamp(.85rem, min(14cqh, 4.2cqw), 1.25rem);
}

body.is-display .trophy-detail,
body.is-display-preview .trophy-detail {
  font-size: clamp(.78rem, min(12cqh, 3.6cqw), 1.1rem);
  line-height: 1.3;
}

body.is-display .trophy-label,
body.is-display-preview .trophy-label {
  font-size: clamp(.72rem, min(10cqh, 3cqw), 1rem);
}

body.is-display .trophy-icon,
body.is-display-preview .trophy-icon {
  width:  clamp(44px, min(34cqh, 11cqw), 84px);
  height: clamp(44px, min(34cqh, 11cqw), 84px);
}

body.is-display .streak-pill,
body.is-display-preview .streak-pill { font-size: clamp(.8rem, min(11cqh, 3.4cqw), 1.15rem); }

/* --- En-tête commun à toutes les tuiles --- */
body.is-display .tile-name,
body.is-display-preview .tile-name {
  font-size: clamp(.92rem, min(13cqh, 4cqw), 1.5rem);
}

body.is-display .tile-icon,
body.is-display-preview .tile-icon {
  width:  clamp(34px, min(26cqh, 8cqw), 62px);
  height: clamp(34px, min(26cqh, 8cqw), 62px);
  font-size: clamp(1rem, min(13cqh, 4cqw), 1.8rem);
}

body.is-display .tile-foot, body.is-display-preview .tile-foot { font-size: clamp(.82rem, min(11cqh, 3.4cqw), 1.2rem); }
body.is-display .tile-unit, body.is-display-preview .tile-unit { font-size: clamp(.78rem, min(10cqh, 3.2cqw), 1.1rem); }

/* --- Chiffres et titres principaux --- */
body.is-display .tile-value,      body.is-display-preview .tile-value      { font-size: clamp(1.8rem, min(38cqh, 11cqw), 4.2rem); }
body.is-display .tile-value.sm,   body.is-display-preview .tile-value.sm   { font-size: clamp(1.3rem, min(24cqh, 7cqw), 2.4rem); }
body.is-display .tile-next-title, body.is-display-preview .tile-next-title { font-size: clamp(1.05rem, min(20cqh, 5.5cqw), 1.9rem); }
body.is-display .tile-next-when,  body.is-display-preview .tile-next-when  { font-size: clamp(.82rem, min(11cqh, 3.4cqw), 1.2rem); }
body.is-display .tile-next-label, body.is-display-preview .tile-next-label { font-size: clamp(.72rem, min(9cqh, 2.8cqw), 1rem); }

/* --- Agenda, notes, météo, abonnements --- */
body.is-display .agenda-row-day,   body.is-display-preview .agenda-row-day   { font-size: clamp(1rem, min(11cqh, 4cqw), 1.8rem); }
body.is-display .agenda-row-month, body.is-display-preview .agenda-row-month { font-size: clamp(.55rem, min(4cqh, 1.6cqw), .8rem); }
body.is-display .agenda-row-title, body.is-display-preview .agenda-row-title { font-size: clamp(.85rem, min(8cqh, 3.1cqw), 1.35rem); }
body.is-display .agenda-row-hour,  body.is-display-preview .agenda-row-hour  { font-size: clamp(.75rem, min(6.5cqh, 2.6cqw), 1.1rem); }

body.is-display .note-text,       body.is-display-preview .note-text       { font-size: clamp(1.05rem, min(20cqh, 5.5cqw), 2.1rem); }
body.is-display .weather-temp,    body.is-display-preview .weather-temp    { font-size: clamp(1.3rem, min(28cqh, 8cqw), 2.6rem); }
body.is-display .weather-glyph,   body.is-display-preview .weather-glyph   { font-size: clamp(1.7rem, min(38cqh, 10cqw), 3.4rem); }
body.is-display .next-due-name,   body.is-display-preview .next-due-name   { font-size: clamp(.92rem, min(14cqh, 4cqw), 1.4rem); }
body.is-display .next-due-price,  body.is-display-preview .next-due-price  { font-size: clamp(1rem, min(16cqh, 4.5cqw), 1.6rem); }

/* --------------------------------------------------------------
   Mode affichage : « Sport — Dernières séances » et « Sport — Semaine
   Résumé » débordaient de leur tuile à une hauteur de 2 rangées. Les
   planchers ci-dessus (avatar 40px, graphique 70px de haut...) sont
   pensés pour rester lisibles à trois mètres sur une GRANDE tuile murale
   — mais rien ne les réduisait pour les petites tailles, alors que ces
   deux widgets peuvent aussi être posés en 2 rangées en mode affichage
   (demande explicite : « sport récap et sport semaine déborde [...] il
   faut que tout rentre même en *2 de hauteur »). Scopé à ces deux widgets
   uniquement (repérage par `data-widget`, spécificité volontairement plus
   élevée que les règles génériques ci-dessus pour être sûr de les
   emporter) et à un seuil sous l'équivalent 3 rangées (~150px d'après le
   repère donné plus haut) pour ne resserrer QUE les tuiles vraiment
   petites, pas la taille par défaut (3 rangées) qui, elle, tient déjà. */
@container tile (max-height: 120px) {
  body.is-display [data-widget="sportSeances"] .tile-icon,
  body.is-display-preview [data-widget="sportSeances"] .tile-icon,
  body.is-display [data-widget="sportSemaine"] .tile-icon,
  body.is-display-preview [data-widget="sportSemaine"] .tile-icon {
    width: 24px; height: 24px; font-size: .8rem;
  }
  body.is-display [data-widget="sportSeances"] .tile-head,
  body.is-display-preview [data-widget="sportSeances"] .tile-head,
  body.is-display [data-widget="sportSemaine"] .tile-head,
  body.is-display-preview [data-widget="sportSemaine"] .tile-head {
    gap: 6px;
  }
  body.is-display [data-widget="sportSeances"] .tile-name,
  body.is-display-preview [data-widget="sportSeances"] .tile-name,
  body.is-display [data-widget="sportSemaine"] .tile-name,
  body.is-display-preview [data-widget="sportSemaine"] .tile-name {
    font-size: .78rem;
  }

  /* Sport — Dernières séances : avatar réduit, ligne meta (durée/kcal/date)
     masquée — le type de séance reste la seule ligne de texte affichée. */
  body.is-display [data-widget="sportSeances"] .sport-seances__avatar,
  body.is-display-preview [data-widget="sportSeances"] .sport-seances__avatar {
    width: 30px; height: 30px;
  }
  body.is-display [data-widget="sportSeances"] .sport-seances__col,
  body.is-display-preview [data-widget="sportSeances"] .sport-seances__col {
    padding: 8px 10px; gap: 4px;
  }
  body.is-display [data-widget="sportSeances"] .sport-seances__meta,
  body.is-display-preview [data-widget="sportSeances"] .sport-seances__meta {
    display: none;
  }

  /* Sport — Semaine Résumé : le graphique retombe sur un plancher bas
     plutôt que les 70px pensés pour une grande tuile murale ; les libellés
     de jour (Lu/Ma/Me...) cèdent la place en premier, les barres restent
     seules à parler. */
  body.is-display [data-widget="sportSemaine"] .sport-semaine,
  body.is-display-preview [data-widget="sportSemaine"] .sport-semaine {
    height: 34px;
  }
  body.is-display [data-widget="sportSemaine"] .sport-semaine__label,
  body.is-display-preview [data-widget="sportSemaine"] .sport-semaine__label {
    display: none;
  }
}

/* ==========================================================================
   CONSOMMATION ÉLECTRIQUE

   Plusieurs vues qui défilent (hier/semaine/mois, ou leur équivalent
   historique). Le prix est ce qui parle le plus fort : gros chiffre en
   `.tile-value` (même typo que les autres métriques de la grille), la
   conso en kWh redescend en ligne secondaire — voir `paintEnergySlides`
   (home.js), qui inverse ce choix quand aucun prix n'est connu.
   ========================================================================== */
.energy-main {
  display: flex;
  flex-direction: column;
  gap: 2px;
  margin-top: auto;
  position: relative;
  z-index: 1;
  min-width: 0;
}

.energy-period {
  font-size: clamp(.62rem, min(5cqh, 6cqw), .74rem);
  font-weight: 700;
  letter-spacing: .09em;
  text-transform: uppercase;
  color: var(--accent);
}

.energy-main .tile-metric { margin-top: 0; }

/* Tendance par rapport à la veille : le signe compte plus que la précision. */
.energy-trend {
  font-size: clamp(.66rem, min(5cqh, 6cqw), .8rem);
  font-weight: 700;
  padding: 2px 8px;
  border-radius: var(--r-full);
  white-space: nowrap;
}

.energy-trend.up   { background: color-mix(in srgb, var(--danger) 15%, transparent);  color: var(--danger); }
.energy-trend.down  { background: color-mix(in srgb, var(--success) 16%, transparent); color: var(--success); }
.energy-trend.flat { background: var(--surface-2); color: var(--text-3); }
.energy-trend:empty { display: none; }

/* Conso en kWh, redescendue en ligne secondaire (voir le commentaire
   d'en-tête) — a repris exactement le style qu'avait le prix avant
   l'inversion. */
.energy-kwh {
  font-size: clamp(.7rem, min(5.5cqh, 6.5cqw), .88rem);
  font-weight: 620;
  color: var(--text-2);
  font-variant-numeric: tabular-nums;
}

.energy-kwh:empty { display: none; }

/* --- Fond décoratif : éclair + circuit imprimé ---
   Le voile flou générique (`.tile::after`, partagé par toutes les tuiles,
   voir plus haut) est coupé ici et remplacé par un éclair filigrane sur des
   pistes de circuit imprimé — plus parlant pour l'électricité qu'un dégradé
   flou anonyme. Couche z-index:0, sous tout le contenu (z-index:1). */
[data-widget="energy"]::after { opacity: 0; }

.energy-fx {
  position: absolute;
  inset: 0;
  z-index: 0;
  overflow: hidden;
  pointer-events: none;
  border-radius: inherit;
}

/* Pistes de circuit imprimé, estompées vers le centre de la tuile. */
.energy-circuit {
  position: absolute;
  inset: 0;
  background-image:
    repeating-linear-gradient(90deg,  color-mix(in srgb, var(--accent) 26%, transparent) 0 1.5px, transparent 1.5px 32px),
    repeating-linear-gradient(180deg, color-mix(in srgb, var(--accent) 26%, transparent) 0 1.5px, transparent 1.5px 32px);
  mask-image: radial-gradient(75% 65% at 100% 0%, black, transparent 72%);
  -webkit-mask-image: radial-gradient(75% 65% at 100% 0%, black, transparent 72%);
  opacity: .6;
}

/* L'éclair lui-même : un polygone en zigzag, pas une image — se redimensionne
   sans jamais pixelliser, et prend nativement la couleur d'accent du thème. */
.energy-bolt {
  position: absolute;
  top: -8%;
  right: -10%;
  width: 60%;
  height: 82%;
  background: linear-gradient(155deg, var(--accent), color-mix(in srgb, var(--accent) 45%, white));
  clip-path: polygon(58% 0%, 22% 52%, 46% 52%, 34% 100%, 82% 40%, 54% 40%);
  opacity: .24;
  filter: drop-shadow(0 0 20px color-mix(in srgb, var(--accent) 60%, transparent));
  transform: rotate(6deg);
}

/* Quelques étincelles, pour que le coin ne soit pas qu'un aplat. */
.energy-spark {
  position: absolute;
  border-radius: 50%;
  background: var(--accent);
  filter: blur(.5px);
}
.energy-spark-a { width: 5px; height: 5px; top: 20%; right: 34%; opacity: .55; }
.energy-spark-b { width: 3px; height: 3px; top: 56%; right: 14%; opacity: .4; }
.energy-spark-c { width: 4px; height: 4px; top: 74%; right: 42%; opacity: .35; }

/* Barres des sept derniers jours — même langage que le graphe des abonnements. */
.energy-bars {
  display: flex;
  align-items: flex-end;
  gap: 4px;
  height: clamp(18px, min(20cqh, 22cqw), 52px);
  position: relative;
  z-index: 1;
}

.energy-bar {
  flex: 1;
  min-height: 3px;
  border-radius: 3px 3px 0 0;
  background: color-mix(in srgb, var(--accent) 30%, transparent);
  transition: height .6s var(--ease);
}

/* Hier : la barre qui compte. */
.energy-bar.is-last { background: var(--accent); }

/* Journée pas encore remontée par le compteur : creux, pas zéro. */
.energy-bar.is-missing {
  background: repeating-linear-gradient(
    45deg,
    color-mix(in srgb, var(--text-3) 22%, transparent) 0 3px,
    transparent 3px 6px
  );
}

/* --- Couleurs Tempo (offre EDF Bleu / Blanc / Rouge) ---
   Une ligne fixe sous le carrousel : la couleur du jour se lit d'un coup
   d'œil depuis la cuisine, elle n'a pas à attendre son tour de diapositive. */
.tempo-row {
  display: flex;
  gap: 6px;
  position: relative;
  z-index: 1;
  min-width: 0;
}

.tempo-chip {
  flex: 1;
  display: flex;
  align-items: center;
  gap: 6px;
  min-width: 0;
  padding: 5px 9px;
  border-radius: var(--r-full);
  background: color-mix(in srgb, var(--tempo-color, var(--text-3)) 14%, transparent);
  border: 1px solid color-mix(in srgb, var(--tempo-color, var(--text-3)) 38%, transparent);
}

.tempo-when {
  font-size: clamp(.56rem, min(4.2cqh, 5cqw), .66rem);
  font-weight: 700;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--text-3);
  flex-shrink: 0;
}

.tempo-dot {
  width: clamp(8px, min(6cqh, 7cqw), 12px);
  height: clamp(8px, min(6cqh, 7cqw), 12px);
  border-radius: 50%;
  flex-shrink: 0;
  background: var(--tempo-color, var(--text-3));
  /* Le jour Blanc serait invisible sur fond clair sans ce cerne. */
  box-shadow: 0 0 0 1px color-mix(in srgb, var(--text) 22%, transparent);
}

.tempo-label {
  font-size: clamp(.66rem, min(5cqh, 5.8cqw), .82rem);
  font-weight: 700;
  color: var(--text);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Couleur pas encore publiée : RTE n'annonce le lendemain que vers 11 h. */
.tempo-chip.is-unknown { border-style: dashed; }
.tempo-chip.is-unknown .tempo-label { color: var(--text-3); font-weight: 600; }

/* Un jour Rouge mérite d'être vu de loin. */
.tempo-chip.is-rouge {
  background: color-mix(in srgb, #ef4444 20%, transparent);
  border-color: color-mix(in srgb, #ef4444 55%, transparent);
}

/* --- Cadre d'une seule rangée (~78px) ---
   Bien en dessous des trois paliers d'abandon génériques : à ce stade,
   `energy-trend`, `energy-kwh`, `energy-bars`, `tempo-row` et `tile-foot`
   sont déjà tous masqués (fit-xl/fit-lg). Il ne reste que l'en-tête et le
   prix — cette requête leur fait gagner la place qu'occupaient encore le
   nom de la tuile et l'étiquette de période, qui n'apportent rien d'utile
   à cette taille. Scopée à cette tuile : les autres widgets gardent leur
   comportement d'abandon générique. */
@container tile (max-height: 95px) {
  [data-widget="energy"] .tile-icon {
    width: 20px;
    height: 20px;
    font-size: .62rem;
  }
  [data-widget="energy"] .tile-head    { gap: 6px; }
  [data-widget="energy"] .tile-name    { display: none; }
  [data-widget="energy"] .energy-period { display: none; }
  [data-widget="energy"] .energy-main  { gap: 0; margin-top: 0; }
}

/* --- Même traitement d'en-tête, généralisé à TOUTES les tuiles (pas
   seulement Électricité ci-dessus) ---
   Demande explicite : "il faut aussi resize les widget pour que toute les
   info rentre même avec une hauteur de 1". En dessous des trois paliers
   d'abandon génériques (fit-md/fit-lg/fit-xl, déjà tous masqués à cette
   taille), l'en-tête (icône + nom de la tuile) restait pourtant à taille
   quasi normale sur TOUTES les tuiles sauf Électricité — la seule à avoir
   reçu ce resserrage. Étendu ici à toutes, pour gagner la même quinzaine de
   pixels partout : à une hauteur de tuile pareille, savoir qu'on est sur
   « Sport » ou « Trophées » se voit déjà à l'icône et au contenu, le nom
   écrit en toutes lettres n'apporte plus rien d'utile. */
@container tile (max-height: 95px) {
  .tile-icon { width: 20px; height: 20px; font-size: .62rem; }
  .tile-head { gap: 6px; }
  .tile-name { display: none; }
}

/* --- Débordements propres à certains widgets, dont le contenu principal
   n'était protégé par AUCUN palier fit-md/fit-lg/fit-xl (contrairement aux
   tuiles Abonnements/Divertissements/Météo/Trophées, qui masquent déjà tout
   le superflu à cette taille) : sans ces règles, le contenu dépassait
   silencieusement du cadre (`.tile { overflow: hidden }` masque le
   débordement visuellement, mais le chiffre/l'anneau essentiel devenait
   alors invisible plutôt que simplement plus petit). */
@container tile (max-height: 95px) {
  /* Trophées : la pastille du dernier trophée avait un plancher de 52px,
     à elle seule plus haute que la tuile entière une fois l'en-tête et le
     padding comptés. */
  .trophy-icon { width: 28px; height: 28px; }
  .trophy-last { gap: 8px; }

  /* Sport (vue solo) : le niveau géant + la barre d'XP n'avaient aucun
     palier d'abandon (seuls les anneaux, déjà masqués à ≤132px, en
     avaient un) — plancher du chiffre de niveau réduit et espacement
     resserré pour que la ligne tienne sous l'en-tête compact ci-dessus. */
  .sport-hud__level      { gap: 8px; }
  .sport-hud__level-num  { font-size: 1.3rem; }
  .sport-hud__xptext     { display: none; }

  /* Sport — Dernières séances : avatar + colonne resserrés (le plancher de
     l'avatar, 28px, plus le padding de la carte, 10px de chaque côté,
     dépassaient déjà à eux seuls une tuile de 78px). */
  .sport-seances__col    { padding: 6px 8px; gap: 4px; }
  .sport-seances__avatar { width: 20px; height: 20px; font-size: .7rem; }
  .sport-seances__meta   { display: none; }

  /* Sport — Semaine Résumé : le graphique en barres avait un plancher de
     hauteur fixe (50px) qui, ajouté à l'en-tête et au padding, dépassait
     systématiquement une tuile de 78px. */
  .sport-semaine        { height: 26px; }
  .sport-semaine__label { display: none; }
}
