/* Apparence reprise de l'ancien site (styles.css + Bootstrap 5.3.3), mais
   sans Bootstrap et sans aucune ressource distante : la CSP de
   app/web/security.py impose style-src/font-src 'self'. Une @import vers
   fonts.googleapis.com, comme le faisait l'ancien fichier, serait bloquee
   par le navigateur en silence -- la page s'afficherait simplement sans la
   police. Verrouille par tests/unit/test_static_assets.py. */

/* Poppins vendorisee (SIL Open Font License, voir ../fonts/OFL.txt).
   Sous-ensemble latin, 3 graisses : les 400/500/600/700/800/900 que
   declarait l'ancien @import n'etaient pas toutes utilisees, et chaque
   graisse est un fichier a telecharger. */
@font-face {
  font-family: "Poppins"; font-style: normal; font-weight: 400;
  font-display: swap; src: url("../fonts/poppins-400.woff2") format("woff2");
}
@font-face {
  font-family: "Poppins"; font-style: normal; font-weight: 600;
  font-display: swap; src: url("../fonts/poppins-600.woff2") format("woff2");
}
@font-face {
  font-family: "Poppins"; font-style: normal; font-weight: 700;
  font-display: swap; src: url("../fonts/poppins-700.woff2") format("woff2");
}

:root {
  --bg: #212529;          /* fond de l'ancien site */
  --fg: #e8eaed; --muted: #9aa0a6;
  --accent: #4ea1ff; --warn: #ffb44e; --line: #2a2e35;
  /* Ligne du visiteur dans le tableau : le violet que l'ancien site
     appliquait via .table>tbody>tr.table-active. */
  --me: rgba(110, 36, 175, 0.87);
  /* Degrades des deux tuiles de rang, repris tels quels. */
  --tile-promo: linear-gradient(62deg, #FBAB7E 0%, #F7CE68 100%);
  --tile-piscine: linear-gradient(160deg, #0093E9 0%, #80D0C7 100%);
  /* Texte pose sur ces degrades clairs : noir, comme l'ancien
     .custom-rank-text. */
  --on-tile: #000;
  /* Alerte lisible sur un degrade clair : --warn (orange) sur la tuile
     promo (orange) serait illisible. */
  --warn-on-tile: #8a1c00;
}
* { box-sizing: border-box; }
body {
  margin: 0; background: var(--bg); color: var(--fg);
  font: 16px/1.5 "Poppins", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
}
.wrap { max-width: 62rem; margin: 0 auto; padding: 1.5rem 1rem 4rem; }
.head { display: flex; align-items: center; justify-content: space-between;
  gap: 1rem; flex-wrap: wrap; }
/* Photo + titre restent solidaires a gauche, le bouton de deconnexion a
   droite : sans ce groupe, space-between repartirait les trois enfants et
   le titre atterrirait au milieu. */
.ident { display: flex; align-items: center; gap: 1rem; }
h1 { font-size: 1.6rem; margin: 0.5rem 0; color: #fff;
  text-transform: uppercase; font-weight: 700; }
/* #pp de l'ancien site. width/height sont aussi poses en attributs dans
   ranking.html.j2 pour reserver la place avant le chargement. */
.pp { width: 110px; height: 110px; border-radius: 50%; object-fit: cover;
  display: block; }
/* Ecran de connexion : l'ancien site centrait son unique bouton dans la
   fenetre (d-flex justify-content-center align-items-center + mt-5).
   60vh laisse le pied de page visible sans le pousser hors de l'ecran. */
.login-centre { display: flex; justify-content: center; align-items: center;
  min-height: 60vh; }
/* #loginBtn de l'ancien site : pilule blanche a bordure noire epaisse,
   texte noir, largement rembourree (px-5 de Bootstrap). Le logo est un
   SVG monochrome deja present dans app/static/icon. */
.btn-login {
  display: inline-flex; align-items: center; gap: 0.75rem;
  background: #fff; color: #000; border: 4px solid #000;
  border-radius: 25px; padding: 0.75rem 3rem;
  font-size: 1.25rem; font-weight: 500; text-decoration: none;
}
.btn-login:hover, .btn-login:focus-visible { background: #f1f1f1; }
.btn-login img { display: block; }
.lede { color: var(--muted); }
.note { color: var(--muted); font-size: 0.9rem; }
.warn { color: var(--warn); }
.btn, .btn-ghost {
  display: inline-block; padding: 0.55rem 1rem;
  text-decoration: none; cursor: pointer; font: inherit;
}
/* #loginBtn de l'ancien site : pilule a bordure noire epaisse. */
.btn { background: var(--accent); color: #06101c; border: 4px solid #000;
  border-radius: 25px; font-weight: 600; }
.btn-ghost { background: transparent; color: var(--fg);
  border: 1px solid var(--line); border-radius: 25px; }
.tiles { display: grid; gap: 1rem; grid-template-columns: 1fr; margin: 1.5rem 0; }
@media (min-width: 40rem) { .tiles { grid-template-columns: 1fr 1fr; } }
/* .custom-rank de l'ancien site : radius 15px, padding 20px, centre. */
.tile { border-radius: 15px; padding: 20px; text-align: center;
  color: var(--on-tile); }
.tile-promo { background-image: var(--tile-promo); }
.tile-piscine { background-image: var(--tile-piscine); }
.tile h2 { margin: 0 0 0.5rem; font-size: 0.85rem; letter-spacing: 0.08em;
  text-transform: uppercase; font-weight: 600; color: var(--on-tile); }
.big { font-size: 2.4rem; font-weight: 700; margin: 0.25rem 0;
  text-transform: uppercase; }
.sub { margin: 0.25rem 0; font-weight: 500; text-transform: uppercase; }
.cohort { font-size: 0.9rem; margin: 0.5rem 0 0; }
/* Dans une tuile, .warn se pose sur un degrade clair : l'orange du theme
   sombre y disparaitrait. */
.tile .warn { color: var(--warn-on-tile); }
/* Select de cohorte dans une carte, comme #promoSelect de l'ancien site.
   Fond blanc translucide sur le degrade clair : un select sombre y
   trancherait, un select transparent rendrait le texte illisible sur les
   parties claires du degrade. */
.choix-cohorte { margin: 0.75rem 0 0; }
.choix-cohorte select {
  background: rgba(255, 255, 255, 0.85); color: var(--on-tile);
  border: 1px solid rgba(0, 0, 0, 0.35); border-radius: 0.5rem;
  padding: 0.4rem 0.6rem; font: inherit; max-width: 100%;
}
/* Le libelle reste lu par les lecteurs d'ecran mais n'encombre pas la
   carte : le titre de celle-ci dit deja de quelle cohorte il s'agit. */
.sr-only {
  position: absolute; width: 1px; height: 1px; overflow: hidden;
  clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
}
/* Lien « Voir ma position » : reprend .go-to-position et
   .position-click-btn de l'ancien site -- noir sur le degrade clair de la
   tuile, blanc au survol. Rendu uniquement quand la cohorte a produit un
   rang (voir _tiles.html.j2), donc jamais de lien mort a survoler. */
.voir-position {
  display: inline-block; margin-top: 0.75rem;
  color: var(--on-tile); font-weight: 500; text-transform: uppercase;
  text-decoration: underline; cursor: pointer;
}
.voir-position:hover, .voir-position:focus-visible { color: #fff; }
.picker { display: flex; flex-wrap: wrap; gap: 0.75rem; align-items: center;
  margin: 1.5rem 0; }
.picker select { background: #1b1f26; color: var(--fg);
  border: 1px solid var(--line); border-radius: 0.5rem; padding: 0.45rem 0.6rem;
  font: inherit; }
table.rank { width: 100%; border-collapse: collapse; margin-top: 0.75rem; }
table.rank th, table.rank td { text-align: left; padding: 0.5rem 0.6rem;
  border-bottom: 1px solid var(--line); }
table.rank tr.is-me { background: var(--me); font-weight: 600; color: #fff; }
.avatar { width: 28px; height: 28px; border-radius: 50%; object-fit: cover;
  display: block; }
/* Sur fond sombre : l'ancien .position-click-btn etait noir parce qu'il
   vivait sur un degrade clair. Le reprendre ici le rendrait invisible. */
.pager { display: flex; align-items: center; gap: 1rem; margin: 1.25rem 0;
  color: var(--fg); }
.pager a, .pager button { color: var(--fg); cursor: pointer; }
/* ---- Etats d'attente ----------------------------------------------
   Le principe : un squelette doit annoncer la FORME de ce qui arrive,
   sinon l'arrivee du contenu est un saut. Les cartes en attente gardent
   donc le degrade de leur tuile -- orange pour la promo, bleu pour la
   piscine -- et n'y posent que des blocs translucides. Avant, elles
   etaient grises : le passage gris -> degrade colore etait brutal, et
   rien ne laissait deviner laquelle des deux cartes se remplissait.

   Le balayage passe par un pseudo-element en overlay et non par un
   background-position sur l'element lui-meme : c'est ce qui permet de le
   superposer au degrade au lieu de le remplacer. */
.skeleton { padding: 0.5rem 0 0; }

.skel-shine { position: relative; overflow: hidden; }
.skel-shine::after {
  content: ""; position: absolute; inset: 0;
  background: linear-gradient(
    100deg,
    transparent 20%,
    rgba(255, 255, 255, 0.28) 48%,
    transparent 76%
  );
  transform: translateX(-100%);
  animation: sweep 1.6s cubic-bezier(0.4, 0, 0.2, 1) infinite;
}
@keyframes sweep { to { transform: translateX(100%); } }

/* Blocs d'une carte en attente : blanc translucide sur le degrade, comme
   le sera le texte noir -- meme emplacement, meme hauteur, donc aucune
   ligne ne bouge a l'arrivee du rang. */
/* display:block est load-bearing : un <span> reste inline par defaut, et
   height/width n'y produisent aucun effet -- les cartes en attente
   s'affichaient vides et plates. */
.skel-block {
  display: block; border-radius: 0.5rem;
  background: rgba(255, 255, 255, 0.45); margin: 0 auto;
}
.skel-cell, .skel-avatar { display: block; flex: none; }
.skel-login { flex: 1 1 auto; }
.tile-attente h2 .skel-block { height: 0.8rem; width: 4rem; }
/* Hauteur = la hauteur de LIGNE de .big (2.4rem x 1.5), pas la taille
   de police : c'est elle qui occupe la place. */
.skel-rank { height: 2.6rem; width: 7.5rem; margin: 0.5rem auto 0.45rem; }
.skel-sub { height: 1rem; width: 9.5rem; margin: 0.6rem auto 0;
  background: rgba(255, 255, 255, 0.32); }
.skel-cohort { height: 0.8rem; width: 7.5rem; margin: 0.7rem auto 0;
  background: rgba(255, 255, 255, 0.24); }
/* Le selecteur et le lien « Voir ma position » comptent dans la hauteur
   d'une vraie tuile : sans eux, la carte en attente mesurait 148 px
   contre 275 px, et le tableau remontait de 120 px a l'arrivee des
   rangs. Les deux cartes sont alignees par la grille, donc le squelette
   vise la plus haute -- celle qui porte le selecteur. */
.skel-select { height: 2.3rem; width: 11.5rem; margin: 0.75rem auto 0;
  border-radius: 0.5rem; background: rgba(255, 255, 255, 0.55); }
.skel-lien { height: 0.9rem; width: 8.5rem; margin: 1.45rem auto 0.75rem;
  background: rgba(255, 255, 255, 0.3); }

/* Lignes du tableau en attente : la structure reelle -- un rond
   d'avatar, un rang court, un login large, un level court -- plutot que
   des barres pleine largeur, qui ne ressemblaient a rien de ce qui
   arrive. Les largeurs varient d'une ligne a l'autre (nth-child) : des
   barres toutes identiques se lisent comme un motif, pas comme des
   donnees. */
.skel-row { display: flex; align-items: center; gap: 0.6rem;
  padding: 0.5rem 0.6rem; border-bottom: 1px solid var(--line); }
.skel-cell { height: 0.85rem; border-radius: 0.3rem;
  background: linear-gradient(90deg, #262b33, #303643); }
.skel-num { width: 1.4rem; }
.skel-avatar { width: 28px; height: 28px; border-radius: 50%; flex: none;
  background: linear-gradient(135deg, #262b33, #333a47); }
.skel-login { flex: 1 1 auto; max-width: 12rem; }
.skel-level { width: 3rem; margin-left: auto; }
.skel-row:nth-child(even) .skel-login { max-width: 9rem; }
.skel-row:nth-child(3n) .skel-login { max-width: 14rem; }
.skel-row:nth-child(3n + 2) .skel-level { width: 2.2rem; }
/* Cascade : chaque ligne demarre son balayage un cran plus tard, ce qui
   fait descendre une onde au lieu de faire clignoter huit lignes a
   l'unisson. */
.skel-row:nth-child(1) .skel-shine::after { animation-delay: 0s; }
.skel-row:nth-child(2) .skel-shine::after { animation-delay: 0.08s; }
.skel-row:nth-child(3) .skel-shine::after { animation-delay: 0.16s; }
.skel-row:nth-child(4) .skel-shine::after { animation-delay: 0.24s; }
.skel-row:nth-child(5) .skel-shine::after { animation-delay: 0.32s; }
.skel-row:nth-child(6) .skel-shine::after { animation-delay: 0.4s; }
.skel-row:nth-child(7) .skel-shine::after { animation-delay: 0.48s; }
.skel-row:nth-child(8) .skel-shine::after { animation-delay: 0.56s; }

/* Le contenu reel entre en fondu, sans deplacement : un glissement
   ferait sauter la page au moment ou le visiteur commence a lire. */
.tiles, #table-slot table.rank, #table-slot .pager { animation: apparait 0.28s ease-out; }
@keyframes apparait { from { opacity: 0; } to { opacity: 1; } }

/* Une animation en boucle sur toute la largeur de l'ecran est
   exactement ce que ce reglage systeme demande d'eviter. Le squelette
   reste lisible, simplement immobile. */
@media (prefers-reduced-motion: reduce) {
  .skel-shine::after { animation: none; opacity: 0.5; }
  .tiles, #table-slot table.rank, #table-slot .pager { animation: none; }
}

.foot { border-top: 1px solid var(--line); color: var(--muted);
  font-size: 0.85rem; display: flex; gap: 1rem; justify-content: center;
  padding: 1rem; }
.foot a { color: var(--accent); }
