/* Défis énergie — surcouche projet, direction « cadrans ».
 *
 * La base est Pico.css v2 (classless, MIT), vendorisée dans vendor/ : le HTML
 * reste sémantique et nu, sans classes utilitaires. Ce fichier porte l'identité
 * visuelle par-dessus.
 *
 * L'objet central du site est le cadran : un anneau de compteur dont la charge
 * suit la fraction écoulée d'un créneau. Un fournisseur sans annonce n'est pas
 * absent de la page — il y occupe un cadran à zéro, qui appelle une
 * contribution. Le vide est une information, pas un blanc.
 *
 * Contraintes qui expliquent la plupart des choix ci-dessous :
 *
 *   - CSP `script-src 'none'` : aucune animation pilotée en JavaScript, et les
 *     anneaux sont des SVG construits côté serveur (`View::anneau`) — un
 *     attribut `stroke-dasharray`, jamais un `style=`.
 *   - CSP `default-src 'self'` : aucune police, image ou feuille distante. Tout
 *     l'ornement est fait de dégradés CSS et de SVG inline.
 *   - Pas de style inline : tout passe par des classes ou des attributs.
 *
 * Le mode sombre est celui de Pico : `prefers-color-scheme` est géré
 * nativement, aucun `data-theme` n'est forcé. Chaque jeton est défini une fois
 * en clair, redéfini une fois en sombre.
 *
 * La couleur d'accent de chaque fournisseur arrive par `--pico-primary`,
 * surchargée dans /assets/accents.css (généré depuis la base).
 */

@import url("vendor/pico.classless.min.css");

/* ==========================================================================
   1. Jetons
   ========================================================================== */

:root {
  --pico-border-radius: 16px;
  --defi-radius-sm: 10px;
  --defi-radius-lg: 20px;

  --defi-ombre: 0 1px 2px rgb(16 24 40 / .05), 0 12px 28px -14px rgb(16 24 40 / .28);
  --defi-ombre-haute: 0 2px 6px rgb(16 24 40 / .07), 0 26px 52px -20px rgb(16 24 40 / .38);
  /* Un très léger rebond : c'est la seule chose qui distingue une carte-cadran
     d'une carte de formulaire au survol. */
  --defi-transition: 200ms cubic-bezier(.34, 1.36, .64, 1);

  --defi-fond: #eef2f8;
  --defi-surface: #ffffff;
  --defi-trait: rgb(16 24 40 / .10);
  --defi-trait-fort: rgb(16 24 40 / .20);
  /* Piste vide des anneaux : ni fond ni bordure, une troisième valeur. */
  --defi-creux: #e4eaf3;

  /* Jaune électrique : la couleur de l'éclair. Réservée à l'énergie elle-même
     et à l'appel à contribution — jamais à un état, qui a ses propres couleurs
     de sévérité. */
  --defi-eclair: #d99400;
  --defi-eclair-doux: rgb(217 148 0 / .12);
  --defi-eclair-encre: #1a1200;   /* texte posé sur un aplat d'éclair */

  --defi-degrade: linear-gradient(115deg, #d99400 0%, #10b981 100%);
  --defi-degrade-doux: linear-gradient(115deg, rgb(217 148 0 / .10), rgb(16 185 129 / .12));

  /* ---- Sévérité des annonces ----
     Indépendante de --pico-primary (couleur du fournisseur) : le repérage doit
     rester lisible quelle que soit la marque. */
  --defi-success-bg: rgb(16 185 129 / .13);
  --defi-success-text: #0f7a52;
  --defi-success-line: rgb(16 185 129 / .38);
  --defi-warn-bg: rgb(217 148 0 / .15);
  --defi-warn-text: #9a6400;
  --defi-warn-line: rgb(217 148 0 / .42);
  --defi-danger-bg: rgb(239 68 68 / .13);
  --defi-danger-text: #b02525;
  --defi-danger-line: rgb(239 68 68 / .38);

  /* ---- Redéfinitions Pico ---- */
  --pico-font-family-sans-serif:
    "Inter", system-ui, "SF Pro Text", -apple-system, BlinkMacSystemFont,
    "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif,
    var(--pico-font-family-emoji);
  /* Pico fait grossir la taille de base avec la largeur de l'écran, jusqu'à
     118,75 % au-delà de 1536 px — un corps de 19 px sur un grand moniteur.
     C'est ce qui donnait au site son air de brochure. On la fige : la mesure
     est déjà limitée à 1100 px, la ligne n'a pas besoin d'un texte plus gros
     pour rester lisible. */
  --pico-font-size: 100%;
  --pico-line-height: 1.6;
  --pico-typography-spacing-vertical: 1.1rem;
  --pico-block-spacing-vertical: 1.9rem;
  --pico-background-color: var(--defi-fond);
  --pico-card-background-color: var(--defi-surface);
  --pico-card-border-color: var(--defi-trait);
  --pico-card-sectioning-background-color: transparent;
  --pico-card-box-shadow: none;   /* remplacée par --defi-ombre, animable */
  --pico-muted-border-color: var(--defi-trait);
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --defi-fond: #070b14;
    --defi-surface: #101827;
    --defi-trait: rgb(255 255 255 / .10);
    --defi-trait-fort: rgb(255 255 255 / .22);
    --defi-creux: rgb(255 255 255 / .07);

    --defi-eclair: #fbbf24;
    --defi-eclair-doux: rgb(251 191 36 / .13);
    --defi-eclair-encre: #1a1200;

    --defi-degrade: linear-gradient(115deg, #fbbf24 0%, #34d399 100%);
    --defi-degrade-doux: linear-gradient(115deg, rgb(251 191 36 / .10), rgb(52 211 153 / .12));

    --defi-success-bg: rgb(52 211 153 / .14);
    --defi-success-text: #6ee7b7;
    --defi-success-line: rgb(52 211 153 / .34);
    --defi-warn-bg: rgb(251 191 36 / .14);
    --defi-warn-text: #fcd34d;
    --defi-warn-line: rgb(251 191 36 / .34);
    --defi-danger-bg: rgb(248 113 113 / .15);
    --defi-danger-text: #fca5a5;
    --defi-danger-line: rgb(248 113 113 / .34);

    --defi-ombre: 0 1px 3px rgb(0 0 0 / .4), 0 14px 34px -16px rgb(0 0 0 / .8);
    --defi-ombre-haute: 0 2px 8px rgb(0 0 0 / .5), 0 30px 60px -22px rgb(0 0 0 / .95);

    --pico-background-color: var(--defi-fond);
    --pico-card-background-color: var(--defi-surface);
  }
}

/* ==========================================================================
   2. Page
   ========================================================================== */

html { background-color: var(--defi-fond); }

/* Deux halos fixes plutôt qu'une image : rien à charger, net à toutes les
   résolutions. Ils reprennent les deux pôles du site — l'énergie et le vert. */
body {
  min-height: 100vh;
  background-color: var(--defi-fond);
  background-image:
    radial-gradient(46rem 30rem at 6% -8%, var(--defi-eclair-doux), transparent 62%),
    radial-gradient(40rem 28rem at 98% 2%, var(--defi-success-bg), transparent 58%);
  background-attachment: fixed;
  -webkit-font-smoothing: antialiased;
}

body > main,
body > header,
body > footer { max-width: 1100px; }

body > main { padding-bottom: 4.5rem; }

/* ==========================================================================
   3. Typographie
   ========================================================================== */

h1, h2, h3 { letter-spacing: -.025em; font-weight: 750; }

h1 {
  display: flex;
  align-items: center;
  gap: .5rem;
  /* Une taille de lecture, pas une affiche : le titre nomme la page, il ne la
     remplit pas. Le contenu — les cadrans, les prix — doit primer. */
  font-size: clamp(1.5rem, 1.25rem + 1vw, 1.95rem);
  font-weight: 800;
  line-height: 1.15;
  margin-block-end: .35rem;
}

/* L'éclair suit la taille du titre, et porte la couleur de l'énergie — pas
   celle du fournisseur : c'est le logo du site, pas un état. */
h1 > svg {
  width: .95em;
  height: .95em;
  color: var(--defi-eclair);
  filter: drop-shadow(0 2px 8px rgb(217 148 0 / .4));
  vertical-align: baseline;
}

h2 { font-size: clamp(1.15rem, 1.05rem + .5vw, 1.4rem); }
h3 { font-size: clamp(1.02rem, .98rem + .25vw, 1.15rem); }

hgroup > p {
  color: var(--pico-muted-color);
  font-size: .98rem;
  max-width: 62ch;
  margin-block-end: 0;
}

/* ==========================================================================
   4. En-tête de page
   ========================================================================== */

body > main > header {
  margin-block-end: 2.2rem;
  padding-block-end: 1.5rem;
  border-block-end: 1px solid var(--defi-trait);
}

/* Trois compteurs : ce qui court, ce qui arrive, ce qui manque. Le troisième
   est en jaune parce qu'il appelle une action, pas parce qu'il alerte. */
.compteurs {
  display: flex;
  flex-wrap: wrap;
  gap: .6rem;
  margin-block: 1.2rem 0;
  padding: 0;
  list-style: none;
}

.compteurs > li {
  display: flex;
  align-items: baseline;
  gap: .45rem;
  padding: .5rem .9rem;
  border: 1px solid var(--defi-trait);
  border-radius: 999px;
  background: var(--defi-surface);
  font-size: .88rem;
  box-shadow: var(--defi-ombre);
}

.compteurs .valeur {
  font-size: 1.15rem;
  font-weight: 800;
  font-variant-numeric: tabular-nums;
  letter-spacing: -.02em;
}

.compteurs li.vif .valeur { color: var(--defi-success-text); }
.compteurs li.manque .valeur { color: var(--defi-eclair); }

/* La barre de liens du haut. `font-size: 0` efface les séparateurs textuels du
   balisage entre deux pastilles ; les enfants retrouvent leur taille juste
   après. */
.nav {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: .5rem;
  margin-block: 1rem 0;
  font-size: 0;
}
.nav > * { font-size: 1rem; }
.nav > .badge { font-size: .78rem; }

.nav > a {
  display: inline-flex;
  align-items: center;
  gap: .4rem;
  padding: .45rem .95rem;
  border: 1px solid var(--defi-trait-fort);
  border-radius: 999px;
  background: transparent;
  font-size: .9rem;
  font-weight: 600;
  text-decoration: none;
  transition: transform var(--defi-transition), border-color var(--defi-transition),
              background-color var(--defi-transition);
}

.nav > a:hover {
  transform: translateY(-2px);
  border-color: var(--defi-eclair);
  background: var(--defi-eclair-doux);
}

/* ==========================================================================
   5. Sections
   ========================================================================== */

/* Les titres de section sont des étiquettes, pas des titres de chapitre : ils
   nomment un lot de cartes et doivent s'effacer devant elles. */
body > main > section > h2 {
  display: flex;
  align-items: baseline;
  gap: .6rem;
  font-size: .82rem;
  font-weight: 700;
  letter-spacing: .09em;
  text-transform: uppercase;
  color: var(--pico-muted-color);
  margin-block-end: 1rem;
}

body > main > section > h2 small { text-transform: none; letter-spacing: 0; }

body > main > section > h2 > .compte {
  font-size: .8rem;
  font-weight: 700;
  letter-spacing: 0;
  padding: .1rem .55rem;
  border-radius: 999px;
  background: var(--defi-creux);
  color: var(--pico-muted-color);
}

/* Les listes de cartes passent en colonnes dès que la place le permet : une
   liste verticale oblige à faire défiler pour comparer, alors que la question
   posée à ces pages est « qu'est-ce qui se passe en ce moment ? ». */
.grille {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 20rem), 1fr));
  gap: 1.1rem;
  align-items: start;
}

.grille > article { margin: 0; height: 100%; }

/* ==========================================================================
   6. Le cadran
   ========================================================================== */

article.cadran {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 1rem;
  align-items: center;
  padding: 1.15rem 1.25rem;
  border: 1px solid var(--defi-trait);
  border-radius: var(--pico-border-radius);
  background: var(--defi-surface);
  box-shadow: var(--defi-ombre);
  transition: transform var(--defi-transition), box-shadow var(--defi-transition),
              border-color var(--defi-transition);
}

article.cadran:hover {
  transform: translateY(-4px);
  box-shadow: var(--defi-ombre-haute);
}

/* La tuile entière mène à la page du fournisseur.
 *
 * La carte se soulevait déjà au survol sans être cliquable ailleurs que sur le
 * nom : elle promettait un geste qu'elle ne tenait pas. Le lien du nom s'étire
 * donc par un `::after` qui couvre la carte — pas de JavaScript (la CSP
 * l'interdit), pas de lien imbriqué, et un seul lien par tuile pour le clavier
 * et les lecteurs d'écran, étiqueté par le nom du fournisseur.
 *
 * Conséquence assumée : sélectionner le texte d'une tuile devient malaisé, la
 * nappe passant devant. C'est le prix connu de ce procédé, et une tuile de
 * quatre lignes n'est pas un texte qu'on copie. */
article.cadran { position: relative; }

/* Restreint aux cadrans : ailleurs, `.fournisseur` vit dans un conteneur non
   positionné, et la nappe s'étirerait jusqu'au premier ancêtre positionné —
   c'est-à-dire, en pratique, sur toute la page. */
article.cadran .fournisseur a::after {
  content: "";
  position: absolute;
  inset: 0;
}

/* « Signaler un défi » reste un lien à part : il remonte au-dessus de la nappe,
   sinon la nappe le recouvrirait et la seule action du cadran vide serait
   perdue. */
article.cadran .signaler {
  position: relative;
  z-index: 1;
}

/* Au clavier, le contour doit cerner ce qui sera activé — la tuile — et non le
   seul nom. Les deux règles sont conditionnées à `:has()` : là où il manque,
   elles sont ignorées ensemble et le lien garde son propre contour, jamais
   aucun. */
article.cadran:has(.fournisseur a:focus-visible) {
  outline: 2px solid var(--defi-eclair);
  outline-offset: 3px;
  border-radius: var(--pico-border-radius);
}
article.cadran:has(.fournisseur a:focus-visible) .fournisseur a { outline: none; }

.anneau { line-height: 0; }
.anneau svg { display: block; }

.corps { min-width: 0; display: flex; flex-direction: column; gap: .3rem; align-items: flex-start; }

.fournisseur { font-size: 1rem; font-weight: 750; letter-spacing: -.015em; }
.fournisseur a { text-decoration: none; color: inherit; }
.fournisseur a:hover { text-decoration: underline; }

.creneau {
  font-size: .95rem;
  font-variant-numeric: tabular-nums;
  color: var(--pico-muted-color);
}

/* La ligne qu'on vient chercher : « reste 52 minutes », « dans 2 jours ». */
.tempo {
  font-size: .96rem;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  letter-spacing: -.01em;
}

/* La sévérité colore la carte entière : anneau, badge et liseré parlent d'une
   seule voix. */
article.cadran.surplus_vert { --teinte: var(--defi-success-text); }
article.cadran.effacement   { --teinte: var(--defi-warn-text); }
article.cadran.abstention   { --teinte: var(--defi-danger-text); }

article.cadran.en-cours {
  border-color: color-mix(in srgb, var(--teinte) 45%, transparent);
  box-shadow: var(--defi-ombre), 0 0 0 3px color-mix(in srgb, var(--teinte) 12%, transparent);
}

article.cadran.en-cours .tempo { color: var(--teinte); }

/* ------------------------------------------------------ Le cadran vide */
/* Un fournisseur sans annonce garde sa place, avec son cadran à zéro. Un blanc
   ne se remarque pas ; un cadran vide, si. */
article.cadran.vide {
  border-style: dashed;
  border-color: var(--defi-trait-fort);
  background: transparent;
  box-shadow: none;
}

article.cadran.vide .fournisseur { color: var(--pico-muted-color); }

article.cadran.vide:hover {
  border-color: var(--defi-eclair);
  background: var(--defi-eclair-doux);
  box-shadow: var(--defi-ombre);
}

.silence { font-size: .92rem; color: var(--pico-muted-color); }

.signaler {
  display: inline-flex;
  align-items: center;
  gap: .35rem;
  margin-block-start: .15rem;
  padding: .3rem .75rem;
  border: 1px solid var(--defi-eclair);
  border-radius: 999px;
  background: transparent;
  color: var(--defi-eclair);
  font-size: .82rem;
  font-weight: 700;
  text-decoration: none;
  transition: background-color var(--defi-transition);
}
.signaler:hover { background: var(--defi-eclair-doux); }

/* ==========================================================================
   6 bis. La case prix du marché
   ========================================================================== */

/* Un aperçu, pas un tableau de bord : deux quarts d'heure, celui qui court et
   celui qui arrive. Le prix est converti en centimes par kilowattheure —
   l'unité de la facture — parce que personne n'achète un mégawattheure. La
   carte entière est le lien vers /prix : c'est là qu'est la journée.
   La mention « hors taxes » n'est pas une note de bas de page : sans elle,
   1,83 c€/kWh se lit comme le prix payé, qui est dix fois plus élevé. */
.prix-marche {
  display: flex;
  flex-direction: column;
  gap: .7rem;
  margin-block: 0 2.2rem;
  padding: 1.1rem 1.25rem;
  border: 1px solid var(--defi-trait);
  border-radius: var(--pico-border-radius);
  background: var(--defi-surface);
  background-image: var(--defi-degrade-doux);
  box-shadow: var(--defi-ombre);
  /* `inherit`, pas `var(--pico-color)` : sur un <a>, Pico redéfinit
     `--pico-color` en `--pico-primary`, et les chiffres viraient au bleu de
     lien au milieu de la carte. */
  color: inherit;
  text-decoration: none;
  transition: transform var(--defi-transition), box-shadow var(--defi-transition),
              border-color var(--defi-transition);
}

.prix-marche:hover {
  transform: translateY(-3px);
  border-color: var(--defi-trait-fort);
  box-shadow: var(--defi-ombre-haute);
}

.prix-marche .entete {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: .5rem .9rem;
}

.prix-marche .titre {
  display: inline-flex;
  align-items: center;
  gap: .4rem;
  font-size: .82rem;
  font-weight: 700;
  letter-spacing: .09em;
  text-transform: uppercase;
  color: var(--pico-muted-color);
}

.prix-marche .moments {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: .5rem 1.4rem;
}

.moment { display: flex; flex-direction: column; gap: .1rem; }

.moment .quand {
  font-size: .78rem;
  font-variant-numeric: tabular-nums;
  color: var(--pico-muted-color);
}

/* Le chiffre est le sujet de la carte, mais il reste un chiffre de lecture :
   assez grand pour être lu de loin, pas assez pour crier. */
.moment .valeur {
  font-size: 1.35rem;
  font-weight: 800;
  letter-spacing: -.02em;
  font-variant-numeric: tabular-nums;
}

/* La flèche seule serait muette pour qui ne la voit pas : le mot l'accompagne
   toujours, et porte la couleur. */
.tendance {
  display: flex;
  align-items: center;
  gap: .3rem;
  font-size: .8rem;
  font-weight: 700;
  color: var(--pico-muted-color);
}
.tendance > svg { flex: none; }
.tendance.monte  { color: var(--defi-danger-text); }
.tendance.baisse { color: var(--defi-success-text); }

.prix-marche .mention {
  font-size: .8rem;
  line-height: 1.45;
  color: var(--pico-muted-color);
  max-width: 62ch;
}

/* Jauge « le marché rapporté à la part fourniture du tarif réglementé ».
 *
 * L'échelle va de zéro à ce repère, pas du minimum au maximum du jour :
 * personne n'a d'intuition de l'amplitude d'une journée de marché, tout le
 * monde a en tête le prix de sa facture. Le SVG est construit côté serveur
 * (`View::jaugeTarif`) — la CSP interdit autant le script que le `style=`, et
 * une largeur dynamique ne peut donc pas passer par une variable CSS inline. */
.prix-marche .jauge-tarif {
  display: flex;
  flex-direction: column;
  gap: .32rem;
}

/* La hauteur est fixée ici, la largeur suit la carte : le SVG s'étire
   (`preserveAspectRatio="none"`) au lieu de grandir en proportion. */
.prix-marche .jauge {
  display: block;
  width: 100%;
  height: 22px;
}

/* Le remplissage pousse depuis la gauche à l'arrivée sur la page. C'est la
   seule animation de l'encart, et elle porte l'information : on voit la barre
   s'arrêter, donc on regarde où elle s'arrête. L'état par défaut est la barre
   pleine — `prefers-reduced-motion` coupe l'animation sans laisser une jauge
   vide.

   `transform-box: fill-box` cale l'origine sur le rectangle lui-même et non
   sur le viewport du SVG : sans lui, le comportement diffère d'un moteur à
   l'autre. */
.prix-marche .jauge .rempli {
  transform-box: fill-box;
  transform-origin: left center;
  animation: jauge-pousse 900ms cubic-bezier(.22, 1, .36, 1) both;
}

/* Le curseur ne se met pas à l'échelle — il s'écraserait sur lui-même au lieu
   d'avancer, faute de pouvoir animer une translation dont la distance dépend
   de la valeur (largeur dynamique = pas de `style=`, la CSP l'interdit). Il
   apparaît donc là où la barre s'arrête, une fois qu'elle s'est arrêtée. */
.prix-marche .jauge .curseur {
  animation: jauge-curseur 260ms ease-out 780ms both;
}

@keyframes jauge-pousse {
  from { transform: scaleX(0); }
  to   { transform: scaleX(1); }
}

@keyframes jauge-curseur {
  from { opacity: 0; }
  to   { opacity: 1; }
}

.prix-marche .bornes {
  display: flex;
  justify-content: space-between;
  gap: .5rem;
  font-size: .72rem;
  font-variant-numeric: tabular-nums;
  color: var(--pico-muted-color);
}

/* ------------------------------------------- La journée, en aperçu */
/* La même courbe que /prix, sans ses zones de survol : ici elle situe le
   moment dans la journée d'un coup d'œil, elle ne se lit pas point par point.
   Le cadre est plus discret que sur /prix — c'est une vignette, pas le sujet
   de la page. */
.prix-marche .courbe-apercu {
  display: block;
  margin-block: .2rem;
  border: 1px solid var(--defi-trait);
  border-radius: var(--defi-radius-sm);
  overflow: hidden;
  /* Le fond du cadre reste neutre : les bandes de tiers de la courbe portent
     déjà la couleur, et un dégradé de plus les brouillerait. */
  background: color-mix(in srgb, var(--pico-muted-color) 4%, transparent);
}

.prix-marche .courbe-apercu svg {
  display: block;
  width: 100%;
  height: auto;
}

/* La phrase qui donne l'ordre de grandeur : c'est elle qu'on retient, pas le
   chiffre brut. */
.prix-marche .comparaison {
  font-size: .95rem;
  font-weight: 700;
  line-height: 1.35;
}

.prix-marche .conseil {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: .45rem .9rem;
}

.prix-marche .phrase { font-size: .88rem; }

.jalons {
  display: flex;
  flex-wrap: wrap;
  gap: .35rem;
}

/* Mêmes couleurs que les badges de tiers : un lecteur qui a vu « tiers bas »
   en vert retrouve le vert sur le creux du jour. */
.jalon {
  padding: .16rem .55rem;
  border: 1px solid transparent;
  border-radius: 999px;
  font-size: .73rem;
  font-weight: 700;
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}
.jalon.bas {
  background: var(--defi-success-bg);
  color: var(--defi-success-text);
  border-color: var(--defi-success-line);
}
.jalon.eleve {
  background: var(--defi-danger-bg);
  color: var(--defi-danger-text);
  border-color: var(--defi-danger-line);
}

/* Sous 480 px, les deux moments s'empilent et la flèche se couche. */
@media (max-width: 480px) {
  .prix-marche .moments { flex-direction: column; align-items: flex-start; gap: .45rem; }
  .tendance > svg { transform: rotate(45deg); }
}

/* ==========================================================================
   7. Contribuer
   ========================================================================== */

/* Quelqu'un qui reçoit une annonce par courriel n'a aucune raison de connaître
   /admin. Ce bloc explique d'abord pourquoi le site existe, puis ce qui est
   utile — trois choses, pas un formulaire de douze champs. */
.contribuer {
  margin-block: 2.6rem 0;
  padding: 1.6rem 1.7rem;
  border: 1px solid var(--defi-trait);
  border-radius: var(--pico-border-radius);
  background: var(--defi-surface);
  box-shadow: var(--defi-ombre);
}

.contribuer > h2 {
  display: flex;
  align-items: center;
  gap: .5rem;
  font-size: 1.12rem;
  font-weight: 800;
  letter-spacing: -.02em;
  text-transform: none;
  color: var(--pico-color);
  margin-block: 0 .5rem;
}
.contribuer > h2 > svg { color: var(--defi-eclair); }

.contribuer > p { max-width: 62ch; color: var(--pico-muted-color); }

.recette {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(13rem, 1fr));
  gap: .9rem;
  margin-block: 1.2rem;
  padding: 0;
  list-style: none;
  counter-reset: etape;
}

/* Les numéros sont ici légitimes : c'est une marche à suivre, l'ordre compte.
   Le compteur remplace le marqueur natif, qu'il faut éteindre sur le `li`
   lui-même — `list-style` posé sur le `ul` ne suffit pas face à Pico. */
.recette > li {
  list-style: none;
  counter-increment: etape;
  position: relative;
  padding: .9rem 1rem .9rem 2.6rem;
  border: 1px solid var(--defi-trait);
  border-radius: var(--defi-radius-sm);
  background: var(--defi-fond);
  font-size: .93rem;
  line-height: 1.5;
}

.recette > li::marker { content: ""; }

.recette > li::before {
  content: counter(etape);
  position: absolute;
  inset-inline-start: .85rem;
  inset-block-start: .85rem;
  width: 1.35rem;
  height: 1.35rem;
  display: grid;
  place-items: center;
  border-radius: 50%;
  background: var(--defi-eclair);
  color: var(--defi-eclair-encre);
  font-size: .78rem;
  font-weight: 800;
}

.recette strong { display: block; margin-block-end: .15rem; color: var(--pico-color); }

.contribuer .canal {
  display: inline-flex;
  align-items: center;
  gap: .45rem;
  padding: .6rem 1.2rem;
  border-radius: 999px;
  background: var(--defi-eclair);
  color: var(--defi-eclair-encre);
  font-weight: 750;
  text-decoration: none;
  box-shadow: 0 6px 18px -8px var(--defi-eclair);
  transition: transform var(--defi-transition);
}
.contribuer .canal:hover { transform: translateY(-2px); }

.contribuer footer {
  margin-block-start: 1.2rem;
  padding-block-start: 1rem;
  border-block-start: 1px solid var(--defi-trait);
  background: transparent;
  font-size: .88rem;
  color: var(--pico-muted-color);
}

/* ==========================================================================
   8. Cartes ordinaires — /prix, /glossaire, page fournisseur
   ========================================================================== */

article {
  --pico-block-spacing-vertical: 1.35rem;
  position: relative;
  border: 1px solid var(--defi-trait);
  border-radius: var(--pico-border-radius);
  box-shadow: var(--defi-ombre);
  transition: transform var(--defi-transition), box-shadow var(--defi-transition),
              border-color var(--defi-transition);
}

article:hover { box-shadow: var(--defi-ombre-haute); }

article > header {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: .55rem .75rem;
  background: transparent;
  border-block-end: 1px solid var(--defi-trait);
}

article > header > h2,
article > header > h3 { margin: 0; flex-basis: 100%; }

article > footer {
  background: transparent;
  border-block-start: 1px solid var(--defi-trait);
  color: var(--pico-muted-color);
}

/* --------------------------------------------- Carte mise en avant (accent) */
/* Le filet en dégradé porte la couleur du fournisseur : c'est la carte qu'on
   doit remarquer en premier. */
article.avant {
  overflow: hidden;
  border-color: color-mix(in srgb, var(--pico-primary) 32%, var(--defi-trait));
}

article.avant::before {
  content: "";
  position: absolute;
  inset-inline: 0;
  inset-block-start: 0;
  height: 3px;
  background-image: linear-gradient(
    90deg,
    var(--pico-primary),
    color-mix(in srgb, var(--pico-primary) 45%, transparent)
  );
}

article.avant:hover {
  border-color: var(--pico-primary);
  box-shadow: var(--defi-ombre-haute),
              0 0 0 1px color-mix(in srgb, var(--pico-primary) 22%, transparent);
}

/* ------------------------------------------- État d'une source nationale */
article.etat-source { border-inline-start: 4px solid var(--pico-muted-border-color); }
article.etat-source.vert   { border-inline-start-color: var(--defi-success-text); }
article.etat-source.orange { border-inline-start-color: var(--defi-warn-text); }
article.etat-source.rouge  { border-inline-start-color: var(--defi-danger-text); }

/* ==========================================================================
   9. Badges
   ========================================================================== */

.badge {
  display: inline-flex;
  align-items: center;
  gap: .38rem;
  padding: .22rem .65rem;
  border: 1px solid transparent;
  border-radius: 999px;
  font-size: .78rem;
  font-weight: 700;
  letter-spacing: .01em;
  white-space: nowrap;
  line-height: 1.35;
}

.badge > svg { flex: none; }

.badge-surplus_vert {
  background: var(--defi-success-bg);
  color: var(--defi-success-text);
  border-color: var(--defi-success-line);
}
.badge-effacement {
  background: var(--defi-warn-bg);
  color: var(--defi-warn-text);
  border-color: var(--defi-warn-line);
}
.badge-abstention {
  background: var(--defi-danger-bg);
  color: var(--defi-danger-text);
  border-color: var(--defi-danger-line);
}

/* Niveaux de prix — vert / gris / rouge, aucune couleur nouvelle. Le tiers
   médian reste neutre : le réserver à l'ambre lui donnerait le poids d'un
   avertissement, alors qu'il ne dit rien d'autre que « ni l'un ni l'autre ». */
.badge-bas {
  background: var(--defi-success-bg);
  color: var(--defi-success-text);
  border-color: var(--defi-success-line);
}
.badge-eleve {
  background: var(--defi-danger-bg);
  color: var(--defi-danger-text);
  border-color: var(--defi-danger-line);
}
.badge-normal,
.badge-neutre {
  background: color-mix(in srgb, var(--pico-muted-color) 12%, transparent);
  color: var(--pico-muted-color);
  border-color: var(--defi-trait-fort);
}

/* « Défi actif maintenant » sur une page fournisseur : le seul badge animé,
   un halo qui respire lentement. Les badges du glossaire, qui nomment un type
   sans rien signaler d'actif, ne bougent pas. */
.nav > .badge-surplus_vert,
.nav > .badge-effacement,
.nav > .badge-abstention {
  animation: defi-pulsation 2.8s ease-in-out infinite;
}

@keyframes defi-pulsation {
  0%, 100% { box-shadow: 0 0 0 0 color-mix(in srgb, currentColor 45%, transparent); }
  70%      { box-shadow: 0 0 0 7px transparent; }
}

/* ==========================================================================
   10. Contenu courant
   ========================================================================== */

/* Liste des créneaux d'une annonce : chaque ligne devient une puce lisible en
   diagonale, sans le point de liste par défaut. */
ul.creneaux {
  list-style: none;
  padding-inline-start: 0;
  margin-block: 0;
}

ul.creneaux > li {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: .3rem .6rem;
  padding: .6rem .85rem;
  margin-block: .4rem;
  border: 1px solid var(--defi-trait);
  border-radius: var(--defi-radius-sm);
  background: color-mix(in srgb, var(--pico-muted-color) 5%, transparent);
}

ul.creneaux > li > svg { color: var(--pico-primary); }

table { font-size: .95rem; }

thead th {
  font-size: .78rem;
  text-transform: uppercase;
  letter-spacing: .06em;
  color: var(--pico-muted-color);
  border-block-end: 1px solid var(--defi-trait-fort);
}

tbody tr { transition: background-color var(--defi-transition); }
tbody tr:hover { background: color-mix(in srgb, var(--pico-primary) 6%, transparent); }

details {
  border: 1px solid var(--defi-trait);
  border-radius: var(--pico-border-radius);
  padding: .9rem 1.1rem;
  margin-block: .7rem;
  background: var(--defi-surface);
  transition: border-color var(--defi-transition), box-shadow var(--defi-transition);
}

details:hover { border-color: var(--defi-trait-fort); }
details[open] { box-shadow: var(--defi-ombre); }
details > summary { font-weight: 600; }

code { border-radius: var(--defi-radius-sm); font-size: .86em; }

hr {
  border: 0;
  height: 1px;
  margin-block: 2.5rem;
  background-image: linear-gradient(90deg, transparent, var(--defi-trait-fort), transparent);
}

/* Boutons de flux : Pico classless ne fournit pas les variantes `.outline` ni
   `.secondary` que le balisage demande — deux boutons pleins à la couleur du
   fournisseur pour deux liens techniques, c'est l'inverse de la hiérarchie
   voulue. On les rend ici, en contour. */
[role="button"].outline,
[role="button"].secondary {
  background: transparent;
  border: 1px solid var(--defi-trait-fort);
  color: var(--pico-muted-color);
  font-size: .9rem;
  font-weight: 600;
  padding: .5rem 1.1rem;
  margin-inline-end: .5rem;
  transition: border-color var(--defi-transition), color var(--defi-transition);
}

[role="button"].outline:hover,
[role="button"].secondary:hover {
  background: transparent;
  border-color: var(--pico-primary);
  color: var(--pico-primary);
}

body > main > footer {
  margin-block-start: 3rem;
  padding-block-start: 1.4rem;
  border-block-start: 1px solid var(--defi-trait);
  font-size: .88rem;
  color: var(--pico-muted-color);
}

body > main > footer > p { margin-block-start: 1.1rem; margin-block-end: 0; }

a:focus-visible,
button:focus-visible,
summary:focus-visible,
[role="button"]:focus-visible {
  outline: 2px solid var(--defi-eclair);
  outline-offset: 3px;
  border-radius: var(--defi-radius-sm);
}

/* ==========================================================================
   11. Détails
   ========================================================================== */

/* Icônes SVG inline : alignement sur la ligne de base du texte voisin. */
svg { vertical-align: -.15em; }

/* L'icône qui ouvre le titre d'une annonce prend la couleur de sa sévérité,
   celle que porte le badge de la même carte. Sans cela elle reste grise à côté
   d'un badge vert ou rouge, et le titre — la première chose lue — ne dit plus
   rien du niveau. `:has()` va chercher la sévérité là où elle est déjà. */
article:has(.badge-surplus_vert) :is(h2, h3) > svg:first-child { color: var(--defi-success-text); }
article:has(.badge-effacement)   :is(h2, h3) > svg:first-child { color: var(--defi-warn-text); }
article:has(.badge-abstention)   :is(h2, h3) > svg:first-child { color: var(--defi-danger-text); }

/* Messages d'état, portés par les rôles ARIA plutôt que par des classes. */
[role="alert"],
[role="status"] {
  display: block;
  padding: .85rem 1.1rem;
  border: 1px solid;
  border-radius: var(--pico-border-radius);
  font-weight: 600;
}
[role="alert"]  { background: var(--defi-danger-bg);  color: var(--defi-danger-text);  border-color: var(--defi-danger-line); }
[role="status"] { background: var(--defi-success-bg); color: var(--defi-success-text); border-color: var(--defi-success-line); }

/* ==========================================================================
   12. Mouvement
   ========================================================================== */

@keyframes defi-apparition {
  from { opacity: 0; transform: translateY(10px); }
  to   { opacity: 1; transform: none; }
}

body > main > header,
body > main > section,
body > main > article { animation: defi-apparition .5s cubic-bezier(.2, .7, .3, 1) both; }

/* Décalage en cascade sur les six premières cartes ; au-delà l'attente
   deviendrait perceptible, donc le délai s'arrête là. */
.grille > article { animation: defi-apparition .45s cubic-bezier(.2, .7, .3, 1) both; }
.grille > article:nth-child(2) { animation-delay: .04s; }
.grille > article:nth-child(3) { animation-delay: .08s; }
.grille > article:nth-child(4) { animation-delay: .12s; }
.grille > article:nth-child(5) { animation-delay: .16s; }
.grille > article:nth-child(6) { animation-delay: .20s; }
.grille > article:nth-child(n+7) { animation-delay: .24s; }

/* L'anneau du créneau en cours respire — le seul mouvement continu du site, sur
   le seul élément qui change vraiment de minute en minute. */
article.cadran.en-cours .anneau .marqueur {
  animation: defi-souffle 2.4s ease-in-out infinite;
}

@keyframes defi-souffle {
  0%, 100% { opacity: 1; }
  50%      { opacity: .72; }
}

/* Tout ce qui bouge s'arrête ici. Les états de survol restent : ils ne se
   déclenchent pas tout seuls. */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation: none !important;
    transition-duration: .01ms !important;
  }
  article.cadran:hover { transform: none; }
}

/* ---------------------------------------- Le texte d'annonce du fournisseur */
/* Ce que le fournisseur a écrit, mot pour mot — souvent le seul endroit où
 * figurent les conditions concrètes de l'offre : le volume attendu, la
 * rémunération. L'intitulé et les créneaux résument, ce paragraphe détaille.
 *
 * Présenté comme une citation, pas comme du contenu du site : le liseré et le
 * léger retrait disent que ces mots ne sont pas les nôtres. C'est aussi le seul
 * endroit où l'on rend une saisie libre, échappée par `View::message()`. */
.annonce {
  border-inline-start: 3px solid color-mix(in srgb, var(--pico-primary) 45%, transparent);
  padding: .1rem 0 .1rem .9rem;
  margin-block: .2rem 1rem;
  color: var(--pico-color);
  font-size: .96rem;
  line-height: 1.55;
  max-width: 70ch;
}

/* ------------------------------------------ Le repère de l'instant présent */
/* Le seul halo du graphique, et le seul mouvement : il désigne le point où
 * l'on se trouve dans la journée, que rien d'autre ne distingue d'un point de
 * mesure ordinaire. La respiration est lente et de faible amplitude — un
 * clignotement sur une courbe déjà chargée en repères serait pénible. */
.halo-present { animation: defi-halo 2.6s ease-in-out infinite; }

@keyframes defi-halo {
  0%, 100% { r: 7; opacity: .22; }
  50%      { r: 10; opacity: .07; }
}

@media (prefers-reduced-motion: reduce) {
  .halo-present { animation: none; }
}
