/* ============================================================================
   rayz-components.css — CATALOGUE DE COMPOSANTS commun aux dashboards INTERNES
   de monitoring.cloud.rayz.io. Source unique, comme _header.html et
   rayz-responsive.css : une correction de charte propage à toutes les pages au
   lieu d'être recopiée quinze fois, ce qui est le mécanisme même de la dérive.

   Le CSS est RELEVÉ sur le catalogue du skill `dashboard-rayz`
   (references/composants-interne.md), valeur pour valeur. Ne rien retoucher ici
   sans le retoucher dans le skill, et réciproquement.

   ORDRE DE CHARGEMENT dans le <head>, il est signifiant :
     1. le <style> de la page      (spécificités locales)
     2. rayz-components.css        (ce fichier : impose la charte)
     3. rayz-responsive.css        (garde-fou responsive, prime sur tout)
   ============================================================================ */

/* === SECTION ============================================================== */
.section { margin-bottom: 36px; transition: padding 0.2s ease; }
.section-head {
  display: flex; justify-content: space-between; align-items: flex-end; gap: 16px;
  margin-bottom: 16px; flex-wrap: wrap; cursor: pointer; user-select: none;
  padding: 4px 0; border-radius: 6px; transition: background 0.15s; position: relative;
}
.section-head:hover { background: rgba(255,255,255,0.02); }
.section-head:hover .section-chevron { color: var(--text-primary); }
.section-title { font-size: 20px; font-weight: 600; letter-spacing: 0.2px; }
/* Le sous-titre n'est pas décoratif : il dit ce que la section montre ET ce
   qu'elle ne montre pas. Une section à titre nu se lit de travers. */
.section-subtitle { font-size: 13px; color: var(--text-muted); margin-top: 4px; }
.section-actions { display: flex; gap: 10px; align-items: center; }

/* Accordéon : chevron injecté en JS, état persisté, replié par défaut sur mobile. */
.section-chevron {
  width: 30px; height: 30px; border-radius: 6px; border: 1px solid var(--border);
  background: transparent; color: var(--text-secondary); font-size: 14px;
  display: inline-flex; align-items: center; justify-content: center;
  cursor: pointer; transition: all 0.18s; font-family: var(--mono); line-height: 1;
  flex-shrink: 0; margin-left: 12px;
}
.section.is-collapsed .section-chevron { transform: rotate(-90deg); }
.section.is-collapsed .section-head { margin-bottom: 0; padding-bottom: 4px; }
.section.is-collapsed > *:not(.section-head) { display: none; }
.section-head-meta { font-family: var(--mono); font-size: 11px; color: var(--text-muted); margin-left: 8px; }
.section.is-collapsed .section-head-meta { display: inline-block; }
.section:not(.is-collapsed) .section-head-meta { display: none; }

@media (max-width: 768px) {
  .section { margin-bottom: 18px; }
  .section-head { padding: 10px 0; }
  .section-title { font-size: 17px; }
  .section-subtitle { font-size: 12px; }
  .section.is-collapsed { margin-bottom: 6px; }
  .section.is-collapsed .section-head { padding: 8px 0; border-bottom: 1px dashed var(--border); }
  .section.is-collapsed .section-actions { display: none; }
}
@media (max-width: 480px) {
  .section-title { font-size: 16px; }
  .section-subtitle { font-size: 11px; line-height: 1.45; }
}

/* === BANDEAU DE FRAÎCHEUR ================================================= */
/* Toujours en tête de main, avant les KPI : un chiffre sans sa date de relevé
   n'engage rien. Trois états, jamais deux : frais / périmé / je ne sais pas. */
.freshness {
  display: flex; flex-wrap: wrap; gap: 10px 18px; align-items: center;
  background: var(--bg-card); border: 1px solid var(--border);
  border-radius: var(--radius); padding: 12px 16px; font-size: 13px; margin-bottom: 26px;
}
.freshness.stale { border-color: var(--degraded); background: var(--degraded-bg); }
.freshness.unknown { border-color: var(--unknown); background: var(--unknown-bg); }
.dot { width: 8px; height: 8px; border-radius: 50%; display: inline-block; margin-right: 6px; }
.dot.ok { background: var(--ok); }
.dot.warn { background: var(--degraded); }
.dot.na { background: var(--unknown); }

/* === TUILES KPI =========================================================== */
/* Cinq sur UNE ligne : on les lit d'un coup d'œil, et un indicateur relégué en
   seconde ligne cesse d'être regardé. La couleur dit si la CIBLE est atteinte,
   jamais si le capteur va bien : ça, c'est le bandeau de fraîcheur. */
.kpis { display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); gap: 12px; }
@media (max-width: 1200px) { .kpis { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
@media (max-width: 760px)  { .kpis { grid-template-columns: 1fr; } }
.kpi {
  background: var(--bg-card); border: 1px solid var(--border); border-radius: var(--radius);
  padding: 16px; position: relative; overflow: hidden; min-width: 0;
}
/* Signature §4bis : filet 3px + HALO de la même couleur. Les deux, toujours. */
.kpi::before {
  content: ''; position: absolute; top: 0; left: 0; right: 0; height: 3px;
  background: var(--kpi-color, var(--unknown));
  box-shadow: 0 0 12px var(--kpi-color, var(--unknown));
}
.kpi.ok { --kpi-color: var(--ok); }
.kpi.degraded { --kpi-color: var(--degraded); }
.kpi.down { --kpi-color: var(--down); }
.kpi.unknown { --kpi-color: var(--unknown); }
.kpi .label { font-size: 11px; font-weight: 600; color: var(--text-muted); text-transform: uppercase; letter-spacing: .5px; }
.kpi .value { font-size: 26px; font-weight: 600; margin: 6px 0 2px; color: var(--kpi-color, var(--text-primary)); }
.kpi.unknown .value { color: var(--text-primary); }
.kpi .meta { font-size: 12px; color: var(--text-muted); }
.kpi .target { font-size: 12px; margin-top: 12px; padding-top: 10px; border-top: 1px solid var(--border); }
.kpi .target.reached { color: var(--ok); }
.kpi .target.todo { color: var(--degraded); }
.kpi .target.none { color: var(--text-muted); }

/* === ESPACEMENT DES BLOCS ================================================= */
/* 🔴 Le template canonique place TOUJOURS une rangee de KPI, une grille ou une
   table DANS une <section>, dont le `margin-bottom: 36px` fournit l'air. Hors de
   ce cas — et plusieurs pages du parc emettent ces blocs directement dans `main`
   — plus rien ne le fournissait : 0 px mesure entre le bas des cartes et la
   section suivante sur claap, notion, slack et vps-health le 23/08/2026.
   Le bloc porte donc son propre espace des qu'il est SUIVI de quelque chose.
   `:not(:last-child)` est ce qui evite d'ajouter une marge inutile quand la
   section, elle, fournit deja l'air en dessous. */
.kpis:not(:last-child),
.cols:not(:last-child),
.scroll:not(:last-child),
.chart-wrap:not(:last-child),
.freshness:not(:last-child) { margin-bottom: 26px; }

/* === TABLEAUX ============================================================= */
/* `min-width: 0` est indispensable : un enfant de grille ou de flex a
   `min-width: auto` par defaut, donc sa piste s'elargit jusqu'au min-content de
   son contenu. Avec `.scroll > table.tbl { display: table }`, ce min-content est
   la largeur NATURELLE du tableau : sans cette ligne, une table large pousse sa
   colonne de grille et la page deborde en mobile (24px mesures sur cost.html).
   Le scroll doit rester porte par .scroll, pas par la page. */
.scroll { overflow-x: auto; min-width: 0; }
table.tbl { width: 100%; border-collapse: collapse; background: var(--bg-card); border: 1px solid var(--border); border-radius: var(--radius); overflow: hidden; }
table.tbl th { padding: 10px 12px; text-align: left; font-size: 11px; font-weight: 600; color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.5px; border-bottom: 1px solid var(--border); background: var(--bg-secondary); }
table.tbl td { padding: 10px 12px; font-size: 13px; border-bottom: 1px solid var(--border); vertical-align: top; }
table.tbl tr:last-child td { border-bottom: none; }
table.tbl a { color: var(--text-primary); text-decoration: none; }
table.tbl a:hover { color: var(--accent); }
/* nowrap sur toute cellule de chiffres : sans lui « 10.0 % » se coupe entre le
   nombre et son unité dès que la colonne serre, et la 2e ligne paraît vide. */
table.tbl td.num { text-align: right; font-family: var(--mono); font-variant-numeric: tabular-nums; color: var(--text-secondary); white-space: nowrap; }
table.tbl th.num { text-align: right; }
table.tbl td.nw { white-space: nowrap; }

/* Tri : la flèche est TOUJOURS présente, en gris très clair au repos, pour que
   le tableau annonce qu'il est triable sans qu'on ait à le survoler. */
table.tbl th.sortable { cursor: pointer; user-select: none; white-space: nowrap; }
table.tbl th.sortable:hover { color: var(--text-primary); background: var(--bg-card-hover); }
table.tbl th.sortable.sorted { color: var(--text-primary); }
.sort-arrow { margin-left: 6px; font-size: 9px; opacity: 0.35; }
th.sorted .sort-arrow { opacity: 1; color: var(--accent); }

/* La boîte de la table épouse ses colonnes. Cf rayz-responsive.css : au-delà de
   680px `display:table; width:auto` est déjà rendu à toutes les tables ; cette
   règle le garantit aussi pour une table placée dans un `.scroll` sous 680px,
   où le scroll est porté par l'enveloppe et non par la table. */
.scroll > table.tbl { display: table; width: auto; }

.bar-wrap { background: var(--bg-secondary); border-radius: 3px; height: 8px; width: 100%; min-width: 90px; display: block; }
.bar { background: var(--text-muted); height: 8px; border-radius: 3px; }
tr.self td { color: var(--text-primary); font-weight: 600; }
tr.self .bar { background: var(--accent); }

/* Intitulé d'un bloc secondaire : dès qu'une section porte un SECOND tableau,
   il le porte, sinon il se lit comme la suite du premier et son sujet disparaît. */
.block-title { font-size: 13px; font-weight: 600; color: var(--text-secondary); text-transform: uppercase; letter-spacing: 0.5px; margin: 20px 0 8px; }

/* === PASTILLES, VIDES ===================================================== */
.pill { display: inline-block; padding: 2px 8px; border-radius: 4px; font-family: var(--mono); font-size: 10px; font-weight: 600; text-transform: uppercase; letter-spacing: .4px; }
.pill.ok { background: var(--ok-bg); color: var(--ok); }
.pill.warn { background: var(--degraded-bg); color: var(--degraded); }
.pill.bad { background: var(--down-bg); color: var(--down); }
.pill.na { background: var(--unknown-bg); color: var(--unknown); }
/* Une pastille de CATÉGORIE n'est ni bonne ni mauvaise : elle ne prend jamais un
   ton d'alerte, et `.na` ne convient pas non plus (on sait de quoi il s'agit). */
.pill.neutre { background: var(--bg-secondary); color: var(--text-secondary); border: 1px solid var(--border); }

/* Un bloc vide DIT pourquoi il est vide : un vide muet est indiscernable d'une panne. */
.empty { color: var(--text-muted); padding: 24px 0; text-align: center; }

/* === COLONNES ET GRAPHES ================================================== */
.chart-wrap { background: var(--bg-card); border: 1px solid var(--border); border-radius: var(--radius); padding: 16px 18px 10px; }
.cols { display: grid; grid-template-columns: repeat(auto-fit, minmax(330px, 1fr)); gap: 16px; }

/* 🔴 Tout enfant direct d'une grille declaree dans main. Un enfant de grille ou
   de flex a `min-width: auto` par defaut : sa piste s'elargit jusqu'au
   min-content de son contenu, et il ne peut PAS retrecir. Une table large y
   pousse donc la page, en mobile comme ailleurs (24px mesures sur cost.html,
   grille .grid-2). Le scroll doit rester porte par `.scroll`, jamais par la page. */
main .cols > *, main [class*="grid-"] > *, main [class*="grid"] > .scroll { min-width: 0; }

/* ============================================================
   Infobulle (.tip) — composant partagé depuis le 26/08/2026
   ============================================================
   🔴 Elle est en `position: fixed`, et ce n'est pas un détail de style.
   `table.tbl` porte `overflow: hidden` (pour ses coins arrondis) et `.scroll` porte
   `overflow-x: auto`, qui rend le débordement vertical clippé lui aussi. Une infobulle
   en `position: absolute` dans un tableau est donc COUPÉE dès qu'elle déborde, ce qui
   se voit sur les dernières lignes : le contenu s'affiche sous le cadre et devient
   illisible. Défaut relevé par Damien sur geo.html le 26/08 ; le piège vit dans cette
   feuille, donc il menaçait les 15 pages.
   `fixed` sort l'infobulle de tout conteneur clippant ; sa position est calculée au
   survol par rayz-components.js, qui la bascule au-dessus quand le bas manque de place. */
.tip { position: relative; border-bottom: 1px dotted var(--text-muted); cursor: help; }
.tip .tip-box {
  visibility: hidden; opacity: 0; transition: opacity .12s;
  position: fixed; left: 0; top: 0; z-index: 60;
  width: 290px; max-width: calc(100vw - 24px); padding: 10px 12px; border-radius: 8px;
  background: #05050a; border: 1px solid var(--border);
  color: var(--text-secondary); font-size: 12px; line-height: 1.5; font-weight: 400;
  box-shadow: 0 8px 26px rgba(0,0,0,.55); white-space: normal; text-transform: none;
  letter-spacing: 0; pointer-events: none;
}
.tip .tip-box strong { color: var(--text-primary); display: block; margin-bottom: 3px; }
.tip:hover .tip-box, .tip:focus .tip-box, .tip:focus-within .tip-box { visibility: visible; opacity: 1; }
