/* KARDTRCKR — Tableau de bord d'accueil, indice de marché et courbe.

   Trois principes, tenus partout dans ce fichier :

   1. La couleur ne sert qu'au sens. Vert et rouge portent la variation et
      les deux pôles de l'indice ; le reste est de l'encre. Un accent mint
      court signale les têtes de section, rien d'autre.
   2. Les légendes appartiennent aux COLONNES, pas aux lignes. Elles sont
      écrites une fois en tête de panneau, sur la même grille — c'est ce qui
      garantit que « prix » et « prévision » tombent exactement l'un sous
      l'autre au lieu de flotter sous la première ligne.
   3. Dense mais respirant : les lignes se touchent et un filet les sépare,
      l'air est mis entre les blocs, pas à l'intérieur.                      */

/* ═════════════  Têtes de section  ═════════════
   Un titre posé sur une grille ne fait pas une section. Ce gabarit en
   fabrique une : surtitre qui situe, titre, sous-titre qui dit ce qu'on
   regarde, repère chiffré à droite, filet de fermeture et accent court.
   Scellé et Cartes le partagent à l'identique — c'est ce qui les fait
   appartenir au même système plutôt qu'au même fichier. */
.sec {
  position: relative;
  display: flex; align-items: flex-end; justify-content: space-between;
  gap: 28px; padding-bottom: 14px;
  border-bottom: 1px solid var(--ui-border);
}
.sec::after {
  content: ''; position: absolute; left: 0; bottom: -1px;
  width: 72px; height: 2px; border-radius: 2px;
  background: linear-gradient(90deg, #8cddbf, #8cddbf00);
}
.sec-main { min-width: 0; display: flex; flex-direction: column; gap: 7px; }
.sec-eyebrow {
  display: block; font-size: 10px; font-weight: 600;
  letter-spacing: .16em; color: #7abaac; margin: 0;
}
.sec-titre { display: flex; align-items: center; gap: 10px; min-width: 0; }
.sec-titre h2 { font-size: clamp(22px, 2.3vw, 27px); font-weight: 600; letter-spacing: -.035em; margin: 0; }
.sec-sous { font-size: 12.5px; line-height: 1.55; color: var(--text-tertiary); max-width: 74ch; margin: 0; }

.dash-bloc { display: flex; flex-direction: column; gap: 16px; }
.dash-duo { display: grid; grid-template-columns: repeat(auto-fit, minmax(420px, 1fr)); gap: 16px; align-items: start; }

/* ═════════════  Panneaux et lignes  ═════════════ */
.dash-panel { gap: 0; padding: 20px 22px 8px; }
.dash-panel-head {
  display: flex; align-items: flex-start; justify-content: space-between;
  gap: 14px; padding-bottom: 12px;
}
.dash-panel-titre { min-width: 0; }
.dash-panel-head h3 {
  font-size: 15.5px; font-weight: 600; letter-spacing: -.02em; margin: 0;
  display: flex; align-items: center; gap: 8px;
}
.dash-panel-head small { display: block; margin-top: 3px; font-size: 11px; color: var(--text-tertiary); }
.dash-panel-head .r-chip, .dash-panel-head .quiet-link { flex: none; margin-top: 2px; font-size: 10px; }

.rows { display: flex; flex-direction: column; }

/* Une seule grille pour toutes les lignes du tableau de bord, intitulés
   compris. Les colonnes de nombres sont à largeur FIXE : c'est la condition
   pour que deux panneaux côte à côte s'alignent aussi entre eux. */
.row {
  display: grid;
  grid-template-columns: 18px 42px minmax(0, 1fr) repeat(var(--cols, 2), var(--colw, 118px));
  align-items: center; gap: 14px;
  padding: 11px 0;
  border-top: 1px solid var(--ui-border);
  text-decoration: none; color: inherit;
  transition: background .12s ease;
}
[data-cols="1"] .row { --cols: 1; }
[data-cols="2"] .row { --cols: 2; }
[data-cols="3"] .row { --cols: 3; --colw: 104px; }
.row:hover { background: var(--app-card); }
.row-cols {
  border-top: 0; padding: 0 0 8px; pointer-events: none;
  font-size: 9.5px; text-transform: uppercase; letter-spacing: .08em;
  color: var(--text-muted);
}
.row-cols > span { text-align: right; }
.row-cols + .row { border-top: 1px solid var(--ui-border); }
.rows > .row:first-child { border-top: 0; }

.row-rank { font-size: 11px; color: var(--text-muted); text-align: center; font-variant-numeric: tabular-nums; }
.row-thumb {
  width: 42px; height: 52px; border-radius: var(--radius-sm);
  overflow: hidden; display: grid; place-items: center; background: var(--app-card);
}
.row-thumb img { width: 100%; height: 100%; object-fit: cover; }
/* Les visuels produits de TCGplayer sont des JPEG sur fond blanc : une tuile
   quasi blanche et `multiply` les intègrent sans détourage. */
.row-thumb-art { background: radial-gradient(ellipse at 50% 45%, #fff 0, #f2f5f3 60%, #e6ebe8 100%); }
.row-thumb-art img { object-fit: contain; padding: 3px; mix-blend-mode: multiply; }

.row-id { min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.row-id strong { font-size: 13.5px; font-weight: 550; letter-spacing: -.01em; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.row-id small { font-size: 11px; color: var(--text-tertiary); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* La phrase qui justifie le rang. Elle est produite par le code, pas rédigée
   — et elle est ce qui distingue un classement d'un ordre arbitraire, donc
   elle reste sur la ligne plutôt que dans une infobulle. */
.row-id em {
  font-style: normal; font-size: 11.5px; line-height: 1.5;
  color: var(--text-secondary); max-width: 82ch; margin-top: 3px;
}

.row-num { text-align: right; font-variant-numeric: tabular-nums; display: flex; flex-direction: column; gap: 2px; align-items: flex-end; }
.row-num strong { font-size: 13px; font-weight: 500; letter-spacing: -.01em; }
.row-num-fort strong { font-size: 16px; font-weight: 600; letter-spacing: -.03em; }
/* La note se replie. En `nowrap` elle débordait sur la colonne voisine dès
   qu'un prix passait à quatre chiffres — le défaut exact que cette grille est
   censée supprimer. */
.row-num small { font-size: 9.5px; line-height: 1.35; color: var(--text-muted); }
.row-num.r-up strong { color: #83d9b1; }
.row-num.r-down strong { color: #edaa90; }

@media (max-width: 900px) {
  .row { grid-template-columns: 18px 38px minmax(0, 1fr) repeat(var(--cols, 2), minmax(0, auto)); gap: 10px; }
  [data-cols="3"] .row { --cols: 2; }
  [data-cols="3"] .row > .row-num:last-child, [data-cols="3"] .row-cols > span:last-child { display: none; }
  .row-thumb { width: 38px; height: 48px; }
  .row-id em { display: none; }
}

/* ═════════════  Indice de marché  ═════════════ */
/* La carte du haut est la plus dense de la page : elle porte un repère, une
   courbe, trois scénarios et six lectures. Chaque point d'air y coûte donc
   plus cher qu'ailleurs, et l'air est mis ENTRE les blocs, jamais dedans. */
/* Le dimensionnement de la carte vit ICI, et nulle part ailleurs. Elle portait
   autrefois un `max-width: 1200px; margin: 0 auto` hérité de l'ancienne page
   marché : sur un enfant de conteneur flex, une marge automatique sur l'axe
   transversal ANNULE l'étirement, et la carte se dimensionnait sur son contenu
   (~900 px) au lieu de suivre la colonne de ses voisines. La règle fautive a
   été retirée de `kard-theme.css` ; les deux lignes ci-dessous restent comme
   garde-fou explicite. */
.mkt { max-width: none; margin: 0; padding: 20px 24px 10px; gap: 0; }

/* Le tableau de bord occupe toute la largeur disponible : c'est la page qu'on
   lit en premier, et ses blocs sont des grilles qui savent se répartir. Les
   autres pages gardent la largeur de lecture confortable posée par
   `research.css`. */
.research-page.dash { max-width: 100%; }

/* Deux colonnes de force presque égale : l'état du marché aujourd'hui à
   gauche, sa trajectoire à droite. La droite prend un peu plus (0,88 / 1,12)
   parce qu'elle porte un dessin, qui gagne à chaque pixel de largeur, face à
   six lignes de texte, qui n'en demandent pas. La version précédente
   réservait une colonne étroite (230–292 px) au repère, ce qui laissait la
   courbe décider seule de la hauteur du bloc et déséquilibrait l'ensemble. */
.mkt-grid { display: grid; grid-template-columns: minmax(0, 0.88fr) minmax(0, 1.12fr); gap: 46px; align-items: stretch; }
.mkt-left { display: flex; flex-direction: column; gap: 10px; }
.mkt-left .kt-gauge-main { flex: none; }
.mkt-left .sec-eyebrow { margin-bottom: 0; }
/* Les six lectures prennent la hauteur qui reste sous la jauge et se
   répartissent dedans : c'est ce qui remplace le demi-écran de vide que
   laissait la colonne de gauche quand elles vivaient sous les deux colonnes. */
.mkt-left .kt-readings { flex: 1; align-content: space-between; }

.kt-gauge-main { display: flex; flex-direction: column; gap: 9px; }
.kt-gauge-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
/* Le mot, pas le nombre : une valeur sur 100 dont l'échelle n'est pas connue
   n'apprend rien de plus que la position du repère. Le chiffre exact reste
   dans le `title` de la barre et dans l'infobulle de méthode. */
.kt-gauge-label { font-size: 20px; font-weight: 600; letter-spacing: -.03em; }
.kt-gauge-track {
  position: relative; height: 10px; border-radius: var(--radius-full);
  background: linear-gradient(90deg, var(--negative) 0%, #6e6e78 50%, var(--positive) 100%);
}
/* Une aiguille, pas une pastille : elle désigne un point de l'échelle au
   lieu de couvrir dix points de large. */
.kt-gauge-marker {
  position: absolute; top: 50%; width: 4px; height: 22px;
  transform: translate(-50%, -50%); border-radius: 3px;
  background: var(--text-primary);
  box-shadow: 0 0 0 3px var(--app-surface, var(--app-card));
}
.kt-gauge-scale {
  display: flex; justify-content: space-between;
  font-size: 9.5px; color: var(--text-muted);
  text-transform: uppercase; letter-spacing: .06em;
}
.kt-gauge-scale [data-on="1"] { color: var(--text-secondary); font-weight: 600; }
.kt-gauge-meta { font-size: 11px; color: var(--text-tertiary); line-height: 1.6; margin: 2px 0 0; }
.kt-gauge-empty { padding: 20px; border: 1px solid var(--ui-border); border-radius: var(--radius-xl); background: var(--app-card); }
.kt-gauge-empty strong { display: block; font-size: 15px; }
.kt-gauge-empty p { font-size: 12px; color: var(--text-tertiary); margin-top: 4px; }

/* Les six composantes, pleine largeur sous les deux colonnes : elles
   expliquent le repère de gauche autant que la pente de droite. */
/* Les six composantes, pleine largeur sous les deux colonnes. Elles tiennent
   maintenant sur une ligne chacune : intitulé, « ? », barre. La phrase de
   commentaire a quitté la page — elle multipliait la hauteur du bloc par
   trois pour une information qu'on ne lit qu'une seule fois. */
.kt-readings {
  display: grid; grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 0 26px; margin: 16px 0 0; padding-top: 2px;
  border-top: 1px solid var(--ui-border);
}
/* En colonne, une lecture par ligne et un filet entre elles : c'est la même
   grammaire que les lignes de classement du reste de la page. */
.mkt-left .kt-readings { grid-template-columns: 1fr; gap: 0; margin-top: 12px; }
.mkt-left .kt-reading + .kt-reading { border-top: 1px solid var(--ui-border); }
/* `display:block` est nécessaire : research.css pose une grille en deux
   colonnes sur `.kt-reading` pour la fiche produit, qui mettrait ici la note
   à droite du libellé au lieu de dessous. */
.kt-reading { display: block; padding: 13px 0; min-width: 0; border: 0; }
.kt-reading dt { font-size: 12px; color: var(--text-primary); font-weight: 500; display: flex; align-items: center; gap: 8px; }
.kt-reading dd { margin: 5px 0 0; font-size: 11.5px; line-height: 1.5; color: var(--text-tertiary); }

/* Variante compacte : la ligne est intitulé — « ? » — barre, la barre poussée
   à droite pour que les six barres s'alignent en colonne d'un bloc à l'autre. */
.kt-readings-compact .kt-reading { padding: 9px 0; }
.kt-readings-compact .kt-reading dt { gap: 7px; }
.kt-readings-compact .kt-reading-nom {
  min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.kt-readings-compact .kt-reading-bar { margin-left: auto; }
/* Sur toute la largeur de la carte, une barre de 52 px laissait un couloir
   vide de 400 px entre l'intitulé et elle. Elle prend maintenant une part de
   la ligne : même lecture, plus de trajet de l'œil à vide. */
.mkt-left .kt-reading-bar { width: clamp(52px, 26%, 132px); height: 4px; }
.kt-readings-compact .dash-info { width: 16px; height: 16px; font-size: 10px; }
/* Barre de composante : la valeur 0-100 d'un coup d'œil, sans chiffre de
   plus à lire. Elle complète la note, elle ne la remplace pas. */
.kt-reading-bar {
  flex: none; width: 52px; height: 3px; border-radius: 3px;
  background: var(--ui-border-strong); position: relative; overflow: hidden;
}
.kt-reading-bar::after {
  content: ''; position: absolute; inset: 0 auto 0 0; width: var(--v, 0%);
  background: #8cddbf; border-radius: inherit;
}

/* ── Courbe et scénarios ────────────────────────────────────────────────
   Trois trajectoires, trois couleurs, et la même couleur partout : dans la
   légende, sur le trait, sur le point d'échéance, sur la pastille du tableau.
   C'est cette répétition qui permet de lire le tableau sans revenir au
   graphique, et le graphique sans revenir à la légende.

   Le vert et le rouge ne sont pas décoratifs : ce sont les deux pôles déjà
   utilisés par l'indice et par les variations de prix sur toute la page.
   Le scénario central est volontairement neutre — il ne promet rien. */
.mkt-right { display: flex; flex-direction: column; gap: 8px; min-width: 0; }
.mkt-right-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap; }
.mkt-sous { font-size: 11.5px; color: var(--text-tertiary); margin: 4px 0 0; }
.mkt-right-vide { justify-content: center; }

:root {
  --scn-haut: #83d9b1;
  --scn-centre: #9ba3ae;
  --scn-bas: #edaa90;
}

.mkt-legend { display: flex; align-items: center; gap: 14px; margin-left: auto; flex-wrap: wrap; }
.mkt-legend-item {
  display: inline-flex; align-items: center; gap: 6px;
  font-size: 10.5px; letter-spacing: .02em; color: var(--text-tertiary); white-space: nowrap;
}
.mkt-legend-item i { width: 14px; height: 2px; border-radius: 2px; background: var(--scn-centre); }
.mkt-legend-item[data-scn="haut"] i { background: var(--scn-haut); }
.mkt-legend-item[data-scn="bas"] i { background: var(--scn-bas); }

.mkt-curve { width: 100%; height: auto; display: block; overflow: visible; }
.mkt-line { fill: none; stroke: #8cddbf; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
/* Le passé est plein, l'avenir est pointillé. La césure est portée par le
   TRAIT, jamais par une légende qu'il faudrait aller lire. */
.mkt-proj {
  fill: none; stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round;
  stroke-dasharray: 6 5; stroke: var(--scn-centre);
}
.mkt-proj[data-scn="haut"] { stroke: var(--scn-haut); }
.mkt-proj[data-scn="bas"] { stroke: var(--scn-bas); }
.mkt-proj[data-scn="centre"] { stroke-dasharray: 2 4; }

.mkt-area { fill: url(#mktFill); stroke: none; }
/* Le fuseau garde son épaisseur à l'éventail, sans concurrencer les trois
   traits qui le bordent : c'est un volume, pas une courbe de plus. */
.mkt-band { fill: #8cddbf; opacity: .055; stroke: none; }
/* L'horizontale du niveau actuel. Sans elle, une trajectoire en baisse se lit
   comme « un peu plus bas que les autres » au lieu de « en dessous
   d'aujourd'hui » — ce qui n'est pas la même information. */
.mkt-base { stroke: rgba(255, 255, 255, .14); stroke-width: 1; stroke-dasharray: 1 4; }
.mkt-cut { stroke: var(--ui-border-strong); stroke-width: 1; stroke-dasharray: 2 4; }
.mkt-now { fill: #8cddbf; }

.mkt-dot { fill: var(--app-surface, var(--app-card)); stroke: var(--scn-centre); stroke-width: 1.6; }
.mkt-dot[data-scn="haut"] { stroke: var(--scn-haut); }
.mkt-dot[data-scn="bas"] { stroke: var(--scn-bas); }

.mkt-jalon line { stroke: var(--ui-border); stroke-width: 1; stroke-dasharray: 2 4; }
.mkt-jalon text { fill: var(--text-muted); font-size: 9.5px; letter-spacing: .06em; text-transform: uppercase; }

.mkt-fin circle { fill: var(--scn-centre); }
.mkt-fin text { fill: var(--scn-centre); font-size: 10.5px; font-weight: 600; font-variant-numeric: tabular-nums; }
.mkt-fin[data-scn="haut"] circle, .mkt-fin[data-scn="haut"] text { fill: var(--scn-haut); }
.mkt-fin[data-scn="bas"] circle, .mkt-fin[data-scn="bas"] text { fill: var(--scn-bas); }

.mkt-axis { fill: var(--text-muted); font-size: 10px; }

/* ── Le tableau des scénarios ───────────────────────────────────────────
   Une ligne par scénario, une colonne par échéance. C'est ce croisement que
   le dessin ne sait pas écrire : « pessimiste à neuf mois » se lit d'un seul
   coup, là où trois pavés séparés obligeaient à recomposer soi-même. */
.mkt-scn {
  display: flex; flex-direction: column;
  border: 1px solid var(--ui-border); border-radius: 12px;
  overflow: hidden; margin-top: 2px;
}
.mkt-scn-row {
  display: grid; grid-template-columns: minmax(96px, 1.1fr) repeat(3, minmax(0, 1fr));
  align-items: center; gap: 10px;
  padding: 8px 14px;
  border-top: 1px solid var(--ui-border);
}
.mkt-scn-row:first-child { border-top: 0; }
.mkt-scn-head {
  padding-top: 8px; padding-bottom: 6px;
  font-size: 9.5px; text-transform: uppercase; letter-spacing: .08em; color: var(--text-muted);
}
.mkt-scn-head > span + span { text-align: right; }
.mkt-scn-nom {
  display: inline-flex; align-items: center; gap: 7px;
  font-size: 11.5px; color: var(--text-secondary); white-space: nowrap;
}
.mkt-scn-nom i { flex: none; width: 14px; height: 2px; border-radius: 2px; background: var(--scn-centre); }
.mkt-scn-row[data-scn="haut"] .mkt-scn-nom i { background: var(--scn-haut); }
.mkt-scn-row[data-scn="bas"] .mkt-scn-nom i { background: var(--scn-bas); }
.mkt-scn-cell {
  display: flex; flex-direction: column; align-items: flex-end; gap: 1px;
  text-align: right; font-variant-numeric: tabular-nums; min-width: 0;
}
.mkt-scn-cell strong { font-size: 14.5px; font-weight: 600; letter-spacing: -.03em; }
.mkt-scn-cell small { font-size: 9.5px; color: var(--text-muted); }
.mkt-scn-cell.r-up strong { color: var(--scn-haut); }
.mkt-scn-cell.r-down strong { color: var(--scn-bas); }
.mkt-scn-note {
  margin: 0; padding: 7px 14px 8px;
  border-top: 1px solid var(--ui-border);
  font-size: 10px; color: var(--text-muted);
}

@media (max-width: 1080px) {
  .mkt-grid { grid-template-columns: 1fr; gap: 26px; }
  .kt-readings { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 640px) {
  .kt-readings { grid-template-columns: 1fr; }
  .mkt-scn-row { grid-template-columns: minmax(78px, 1fr) repeat(3, minmax(0, 1fr)); gap: 6px; padding: 8px 10px; }
  .mkt-scn-cell strong { font-size: 12.5px; }
  .mkt-scn-cell small { display: none; }
  .mkt-legend { margin-left: 0; gap: 10px; }
  .sec { flex-direction: column; align-items: flex-start; gap: 14px; }
}

/* ═════════════  Le « ? » des sections  ═════════════
   La méthode a sa place sur la page, pas dans le flux de lecture. Un pavé de
   six lignes sous un titre se saute ; sous un « ? » il se consulte. */
.dash-info {
  position: relative; flex: none; display: inline-grid; place-items: center;
  width: 19px; height: 19px; padding: 0; border-radius: 50%;
  border: 1px solid var(--ui-border-strong); background: none;
  color: var(--text-tertiary); font: inherit; font-size: 11px; line-height: 1; cursor: help;
  transition: color .15s ease, border-color .15s ease;
}
.dash-info:hover, .dash-info:focus-visible { color: var(--text-primary); border-color: var(--text-tertiary); outline: none; }
.dash-info-pop {
  position: absolute; top: calc(100% + 9px); left: 0; z-index: 40;
  /* Bornée par la fenêtre et non par une valeur fixe : à 380 px elle
     dépassait l'écran sur les petites largeurs, même invisible. */
  width: max-content; max-width: min(380px, calc(100vw - 32px)); padding: 13px 15px;
  border-radius: 11px; border: 1px solid var(--ui-border-strong);
  background: var(--app-surface-elevated); box-shadow: 0 18px 44px rgba(0, 0, 0, .55);
  font-size: 11.5px; line-height: 1.6; color: var(--text-secondary);
  text-align: left; white-space: normal; font-weight: 400;
  opacity: 0; visibility: hidden; transform: translateY(4px);
  transition: opacity .15s ease, transform .15s ease, visibility .15s ease;
}
/* Près du bord droit, l'infobulle s'ancre à droite pour ne pas déborder.
   Les deux premiers cas sont connus d'avance ; le troisième est posé par
   frame.js au survol, pour tous les « ? » dont la place manque réellement. */
.mkt-right-head .dash-info-pop,
.dash-info[data-cote="droite"] .dash-info-pop { left: auto; right: 0; }
.dash-info:hover .dash-info-pop, .dash-info:focus-visible .dash-info-pop { opacity: 1; visibility: visible; transform: translateY(0); }

/* ═════════════  Courbes résumées  ═════════════ */
.kt-spark { flex-shrink: 0; }
.kt-spark-empty { display: inline-block; width: 88px; height: 26px; }

/* ═════════════  Tension de l'offre  ═════════════
   Une mesure de pression vendeuse, pas un volume. Le libellé le dit et
   l'infobulle le répète. */
.kt-tension {
  display: inline-flex; align-items: center; gap: 5px;
  font-size: 11px; padding: 2px 8px;
  border-radius: var(--radius-full);
  border: 1px solid var(--ui-border-strong);
  color: var(--text-secondary);
  white-space: nowrap;
}
.kt-tension::before { content: ''; width: 6px; height: 6px; border-radius: 50%; background: currentColor; opacity: .8; }
.kt-tension[data-level="serrée"] { color: var(--positive); }
.kt-tension[data-level="relâchée"] { color: var(--warning, #f59e0b); }
.kt-tension[data-level="forecast"] { color: var(--text-secondary); }

.r-flat { color: var(--text-muted); }

/* ═════════════  Barres de chargement  ═════════════
   Deux états. Déterminé quand le total est connu — la barre avance et le
   compteur dit où on en est. Indéterminé sinon : un balayage, jamais une
   fausse progression qui mentirait sur l'attente restante. */
.kt-progress { display: flex; flex-direction: column; gap: 7px; width: 100%; max-width: 460px; }
.kt-progress-head { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; font-size: 12px; color: var(--text-secondary); }
.kt-progress-head .tnum { color: var(--text-tertiary); font-variant-numeric: tabular-nums; }
.kt-progress small { font-size: 11px; color: var(--text-muted); }
.kt-progress-track {
  position: relative; height: 4px; overflow: hidden;
  border-radius: var(--radius-full);
  background: var(--ui-border-strong);
}
.kt-progress-track > span {
  display: block; height: 100%; width: 0;
  border-radius: inherit;
  background: var(--text-secondary);
  transition: width .35s ease;
}
.kt-progress-track.is-indeterminate > span {
  width: 34%;
  animation: kt-sweep 1.15s ease-in-out infinite;
}
@keyframes kt-sweep {
  0%   { transform: translateX(-110%); }
  100% { transform: translateX(400%); }
}
@media (prefers-reduced-motion: reduce) {
  .kt-progress-track.is-indeterminate > span { animation: none; width: 100%; opacity: .45; }
  .kt-progress-track > span { transition: none; }
}

.r-loading { display: flex; flex-direction: column; gap: 14px; padding: 48px 0; align-items: flex-start; }
.kt-loading-skeletons { display: grid; gap: 12px; width: 100%; margin-top: 8px; }

/* La page démarre directement sur le contenu, et l'air est mis ENTRE les
   blocs : c'est ce qui rend une page dense respirable. */
.dash { gap: 34px; }
.dash > section:first-of-type { margin-top: 2px; }
