/* Charte définitive, reprise du design validé dans Claude Design (voir
   "Crea News — design de site", fichier Crea News.dc.html) — remplace la
   charte provisoire calquée sur polycreatifs.fr. Thème clair, oklch().
   Polices : Space Grotesk (titres), IBM Plex Sans (texte), Inter 900 (logo).

   Passe design du 15/08/2026 (skill tasteskill v2, audit Section 11) :
   polices auto-hébergées (voir public/fonts/), échelle de rayons unifiée en
   3 paliers (voir --rayon-* ci-dessous), légère activation d'un mouvement
   restreint (voir plus bas dans ce fichier). Toujours zéro dépendance JS
   ajoutée — cohérent avec le choix déjà pris pour ce projet. */

@font-face {
  font-family: 'Space Grotesk';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url('/fonts/space-grotesk-500.woff2') format('woff2');
}
@font-face {
  font-family: 'Space Grotesk';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url('/fonts/space-grotesk-600.woff2') format('woff2');
}
@font-face {
  font-family: 'Space Grotesk';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url('/fonts/space-grotesk-700.woff2') format('woff2');
}
@font-face {
  font-family: 'IBM Plex Sans';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('/fonts/ibm-plex-sans-400.woff2') format('woff2');
}
@font-face {
  font-family: 'IBM Plex Sans';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url('/fonts/ibm-plex-sans-500.woff2') format('woff2');
}
@font-face {
  font-family: 'IBM Plex Sans';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url('/fonts/ibm-plex-sans-600.woff2') format('woff2');
}
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 900;
  font-display: swap;
  src: url('/fonts/inter-900.woff2') format('woff2');
}

:root {
  --fond: oklch(0.98 0.004 60);          /* fond de page */
  --fond-panneau: oklch(0.995 0.002 60); /* cartes, champs de formulaire */
  --fond-alt: oklch(0.955 0.005 55);     /* header, footer, pastilles diverses */
  --texte: oklch(0.22 0.008 55);
  --texte-article: oklch(0.28 0.008 55);
  --discret: oklch(0.48 0.012 60);
  --discret-fort: oklch(0.4 0.012 60);
  --bordure: oklch(0.89 0.006 55);
  --bordure-forte: oklch(0.85 0.006 55);
  /* Orange repris directement de la flamme du logo (public/assets/
     flamme-polyworkshop.svg, fill="#F7841E") plutôt que du orange plus pâle
     défini dans les specs Claude Design d'origine (#F2933D) — Thomas a
     tranché le 15/08/2026 pour une seule teinte cohérente entre l'icône et
     le reste du site, quitte à ne pas toujours atteindre le contraste WCAG
     AA en usage "texte sur fond clair" (logo, libellés) : choix assumé,
     préférence pour un orange vif plutôt qu'une variante plus sombre jugée
     "moche" (marron/terne). Les boutons à fond plein restent conformes
     (texte sombre dessus, voir plus bas) — seul ce compromis esthétique
     s'applique au texte orange sur fond clair. */
  --accent: #f7841e;
  /* Couleur de texte pour tout ce qui est posé SUR un fond --accent (boutons
     pleins, onglet actif...) — volontairement fixe, jamais redéfinie dans le
     thème sombre plus bas. --accent ne change pas de valeur selon le thème,
     donc le texte posé dessus ne doit pas en changer non plus : au 15/08/2026
     ce token utilisait --texte (qui, lui, s'inverse en sombre), ce qui
     cassait le contraste des boutons une fois le thème sombre ajouté
     (2.19:1 mesuré) - bug trouvé et corrigé le jour même. */
  --sur-accent: oklch(0.2 0.008 55);
  --ok: #16a34a;
  --ok-fond: oklch(0.93 0.03 145);
  --ok-texte: oklch(0.35 0.08 145);
  --ko: #dc2626;
  --largeur-page: 80rem;         /* 1280px — listing */
  --largeur-page-etroite: 57.5rem; /* 920px — article/about/contact */

  /* Échelle de rayons unifiée (3 paliers documentés, au lieu de 7 valeurs
     proches mais différentes auparavant) : petits contrôles interactifs,
     grandes surfaces/panneaux, formes pilule (déjà en 999px, inchangées). */
  --rayon-controle: .5rem;   /* boutons, champs, onglets, badges non-pilule */
  --rayon-panneau: .875rem;  /* cartes, images, panneaux, blocs admin */

  /* Easing partagé pour toutes les transitions du site (mouvement restreint,
     cohérent avec MOTION_INTENSITY ~3 retenu pour cette passe). */
  --ease-doux: cubic-bezier(0.16, 1, 0.3, 1);
}

/* --- Thème sombre (15/08/2026) -------------------------------------------
   Activé via l'attribut data-theme="sombre" posé sur <html> par le serveur
   (voir layout.ejs + le cookie "theme" lu dans app.js) — jamais par
   @media (prefers-color-scheme), Thomas voulant un choix mémorisé
   explicitement plutôt qu'une détection automatique du système. Mêmes noms
   de variables que le thème clair : tout le reste du fichier n'a besoin
   d'aucune modification, il consomme déjà ces tokens partout. --accent reste
   inchangé (même orange vif dans les deux thèmes, décision du 15/08/2026 -
   voir plus haut). Pas de noir pur ni de blanc pur (cf. bonnes pratiques
   habituelles) : --fond et --texte restent légèrement teintés. */
:root[data-theme="sombre"] {
  --fond: oklch(0.17 0.006 55);
  --fond-panneau: oklch(0.215 0.007 55);
  --fond-alt: oklch(0.245 0.008 55);
  --texte: oklch(0.95 0.004 60);
  --texte-article: oklch(0.88 0.006 55);
  --discret: oklch(0.64 0.012 58);
  --discret-fort: oklch(0.72 0.012 58);
  --bordure: oklch(0.32 0.008 55);
  --bordure-forte: oklch(0.4 0.008 55);
  --ok-fond: oklch(0.28 0.05 145);
  --ok-texte: oklch(0.78 0.12 145);
  --ko: #f37272;
}

/* Le repère de catégorie (pastille + libellé, teinté par catégorie via
   --teinte-categorie posé en style inline sur chaque élément — voir
   lib/categories-visuel.js) et le motif rayé des vignettes vides ne passent
   PAS par les tokens ci-dessus : leur luminosité est calculée directement
   pour un fond clair. Sans ces overrides, le libellé de catégorie serait
   trop terne sur fond sombre, et le motif rayé des news sans image
   resterait une plaque claire éblouissante au milieu d'une page sombre. */
:root[data-theme="sombre"] .etiquette-categorie {
  color: oklch(0.78 0.14 var(--teinte-categorie));
}

:root[data-theme="sombre"] .carte-news-image-vide,
:root[data-theme="sombre"] .article-image-vide {
  background: repeating-linear-gradient(
    135deg,
    oklch(0.3 0.03 var(--teinte-categorie)) 0 14px,
    oklch(0.25 0.006 55) 14px 28px
  );
}

:root[data-theme="sombre"] .carte-news-image-vide span,
:root[data-theme="sombre"] .article-image-vide span {
  color: oklch(0.72 0.01 60);
}

* { box-sizing: border-box; }

body {
  margin: 0;
  background: var(--fond);
  color: var(--texte);
  font-family: 'IBM Plex Sans', -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  line-height: 1.6;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
}

.conteneur {
  max-width: var(--largeur-page);
  width: 100%;
  margin: 0 auto;
  padding: 1.5rem 2rem 4.5rem;
  flex: 1;
}

/* Pages simples (article/about/contact) : le conteneur principal lui-même
   se resserre (au lieu d'ajouter un conteneur étroit À L'INTÉRIEUR de
   .conteneur) — sinon son padding s'ajoute à celui du conteneur étroit
   imbriqué, et le texte se retrouve décalé par rapport au header/footer qui
   n'ont qu'un seul niveau de padding, eux (piège rencontré en corrigeant un
   premier désalignement le 15/08/2026 : le correctif précédent avait
   remplacé un désalignement par un autre, dans l'autre sens, pour cette
   raison). Un seul niveau de padding partout = un seul bord gauche partout,
   quelle que soit la page. */
.conteneur.page-etroite {
  max-width: var(--largeur-page-etroite);
}

a { color: inherit; text-decoration: none; }

h1, h2 { font-family: 'Space Grotesk', sans-serif; font-weight: 600; letter-spacing: -0.01em; }

.note { color: var(--discret); font-size: .875rem; }

/* --- Header et footer publics -------------------------------------------- */

.entete-public {
  border-bottom: 1px solid var(--bordure);
  background: var(--fond-alt);
}

.entete-public-interieur {
  max-width: var(--largeur-page);
  margin: 0 auto;
  padding: 1.5rem 2rem;
  display: flex;
  /* flex-start (pas center) : aligne le haut de l'icône sur le haut du bloc
     titre+tagline juste à côté, plutôt que de centrer une icône de taille
     fixe sur toute la ligne (rendu "bancal" signalé par Thomas le
     15/08/2026). Voir .entete-logo-icone pour la hauteur. Note technique :
     un align-items:stretch + height:100% sur l'icône avait été tenté
     d'abord (dimensionnement dynamique "parfait"), mais un <img> stretché
     dans une ligne flex utilise sa taille intrinsèque (365px) pour calculer
     la hauteur de ligne AVANT le stretch → l'icône explosait à pleine
     hauteur d'écran. Hauteur fixe = plus robuste, préféré ici. */
  align-items: flex-start;
  gap: .75rem;
  flex-wrap: wrap;
}

/* Sur les pages étroites (article/à propos/contact — voir .page-etroite),
   le header ET le footer se resserrent eux aussi à 920px au lieu de 1280px,
   pour garder le même bord gauche que le contenu (comme dans le design :
   containerMaxWidth y vaut 920px, pas seulement le contenu). Sélecteur à 2
   classes pour gagner sur l'ordre des règles sans y penser. */
.entete-public-interieur.page-etroite,
.pied-public-interieur.page-etroite {
  max-width: var(--largeur-page-etroite);
}

.entete-logo-icone {
  /* Hauteur mesurée pour correspondre exactement au bloc titre + tagline
     (article/à-propos/contact, sans champ de recherche à côté) —
     approximation fixe et robuste plutôt qu'un étirement dynamique fragile
     (voir la note sur .entete-public-interieur). Le listing (avec champ de
     recherche, tagline sur une largeur différente) a sa propre valeur juste
     en dessous. À ajuster à la main si le texte de la tagline change
     significativement. */
  height: 4.5rem;
  width: auto;
  flex-shrink: 0;
}

/* Sur le listing (recherche à côté, .entete-recherche-form présente), le
   bloc titre+tagline mesure ~5.77rem au lieu de ~4.5rem — même texte, mais
   largeur disponible différente. Mesuré directement plutôt que deviné. */
.entete-public-interieur:not(.page-etroite) .entete-logo-icone {
  height: 5.8rem;
}

.entete-logo-bloc {
  flex: 1;
  min-width: 0;
}

.entete-logo,
.entete-logo-ligne .entete-logo {
  display: inline-block;
  font-family: 'Inter', sans-serif;
  font-weight: 900;
  font-size: 1.875rem;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  color: var(--accent);
  text-decoration: none;
}

.entete-tagline {
  margin: .25rem 0 0;
  color: var(--discret);
  font-size: .84rem;
  line-height: 1.5;
}

.entete-recherche-form {
  position: relative;
  width: 16.25rem;
  max-width: 100%;
  flex-shrink: 0;
  /* La ligne est en align-items:flex-start (voir .entete-public-interieur) :
     sans ça, le champ de recherche s'alignerait sur le haut au lieu du
     centre vertical du bloc titre+tagline. */
  align-self: center;
}

.entete-recherche {
  width: 100%;
  padding: .69rem 2.5rem .69rem 1rem;
  border-radius: 999px;
  border: 1px solid var(--bordure-forte);
  background: var(--fond-panneau);
  color: inherit;
  font: inherit;
  font-size: .875rem;
  outline: none;
}

.entete-recherche:focus {
  border-color: var(--accent);
}

.entete-recherche-bouton {
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  width: 2.5rem;
  display: flex;
  align-items: center;
  justify-content: center;
  border: none;
  background: none;
  color: var(--discret);
  cursor: pointer;
}

.entete-recherche-bouton:hover { color: var(--accent); }

.pied-public {
  border-top: 1px solid var(--bordure);
  background: var(--fond-alt);
  margin-top: auto;
}

.pied-public-interieur {
  max-width: var(--largeur-page);
  margin: 0 auto;
  padding: 1.375rem 2rem;
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 1.25rem;
  flex-wrap: wrap;
  font-size: .78rem;
  color: var(--discret);
}

.pied-liens {
  display: flex;
  gap: 1.125rem;
  flex-wrap: wrap;
}

.pied-lien {
  color: var(--discret-fort);
  font-weight: 500;
  text-decoration: none;
}

.pied-lien:hover { color: var(--texte); }

/* --- Onglets de catégories ---------------------------------------------- */

.onglets {
  display: flex;
  flex-wrap: wrap;
  gap: .5rem;
  margin-bottom: 1.25rem;
  padding-bottom: 1.25rem;
  border-bottom: 1px solid var(--bordure);
}

.onglet {
  padding: .44rem .94rem;
  border-radius: var(--rayon-controle);
  font-size: .84rem;
  font-weight: 500;
  text-decoration: none;
  color: var(--discret-fort);
  border: 1px solid var(--bordure-forte);
  background: transparent;
}

.onglet:hover {
  border-color: var(--accent);
}

.onglet-actif,
.onglet-actif:hover {
  /* Texte sombre plutôt que blanc : le orange de la marque n'offre pas assez
     de contraste avec du blanc (2.26:1 mesuré, sous le seuil WCAG AA de
     4.5:1) — voir l'audit du 15/08/2026. --sur-accent (fixe) sur --accent
     atteint 7.4:1 dans les deux thèmes. */
  color: var(--sur-accent);
  background: var(--accent);
  border-color: var(--accent);
}

/* --- Barre de résultats + bascule liste/grille --------------------------- */

.barre-resultats {
  display: flex;
  justify-content: space-between;
  align-items: center;
  flex-wrap: wrap;
  gap: .75rem;
  margin: 0 0 1.125rem;
}

.compteur-resultats {
  margin: 0;
  color: var(--discret);
  font-size: .84rem;
}

.recherche-active a {
  color: var(--accent);
  text-decoration: underline;
}

/* Regroupe la bascule liste/grille et la bascule clair/sombre — la
   "section préférences d'affichage" demandée par Thomas le 15/08/2026, sans
   étiquette de titre superflue : les deux groupes de boutons côte à côte se
   lisent déjà comme un seul bloc de réglages. */
.preferences-affichage {
  display: flex;
  align-items: center;
  gap: .75rem;
  flex-wrap: wrap;
}

.bascule-vue {
  display: flex;
  gap: .375rem;
}

.bascule-vue-bouton {
  padding: .375rem .8rem;
  border-radius: var(--rayon-controle);
  font-size: .8rem;
  font-weight: 500;
  border: 1px solid var(--bordure-forte);
  background: transparent;
  color: var(--discret-fort);
  text-decoration: none;
}

.bascule-vue-actif,
.bascule-vue-actif:hover {
  /* Même correctif de contraste que .onglet-actif juste au-dessus. */
  color: var(--sur-accent);
  background: var(--accent);
  border-color: var(--accent);
}

/* --- Liste de news -------------------------------------------------------- */
/* Vue « Liste » (par défaut) : une news par ligne, miniature à gauche.
   Vue « Grille » (.liste-news--grille) : cartes empilées en grille. */

.liste-news {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 1.125rem;
}

.liste-news--grille {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(20rem, 1fr));
  gap: 1.25rem;
}

.carte-news {
  background: var(--fond-panneau);
  border: 1px solid var(--bordure);
  border-radius: var(--rayon-panneau);
  overflow: hidden;
}

.carte-news-image-conteneur {
  position: relative;
  grid-area: image;
}

.carte-news-image {
  display: block;
  width: 100%;
  height: 100%;
  aspect-ratio: 4 / 3;
  background: var(--bordure);
}

.liste-news--grille .carte-news-image {
  /* 4:3 au lieu de 16:9 (demande de Thomas, 17/08/2026) : le 16:9 rognait
     trop le haut/bas des images, en particulier celles avec du texte
     incrusté (packaging produit, visuels promo). Cette règle l'emporte
     aussi sur mobile (voir @media plus bas dans le fichier) grâce à sa
     spécificité plus forte (2 classes) : pas besoin de la répéter dans le
     bloc mobile pour que "desktop et mobile" soient couverts. Le mode
     Liste garde son 16:9 mobile inchangé, non concerné par cette demande. */
  aspect-ratio: 4 / 3;
}

.carte-news-image img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

.carte-news-image-vide {
  width: 100%;
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: .5rem;
  text-align: center;
  background: repeating-linear-gradient(
    135deg,
    oklch(0.9 0.03 var(--teinte-categorie)) 0 10px,
    oklch(0.94 0.006 55) 10px 20px
  );
}

.carte-news-image-vide span,
.article-image-vide span {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: .7rem;
  color: oklch(0.4 0.01 60);
  line-height: 1.4;
}

/* Bouton d'édition rapide, visible seulement en étant connecté (voir
   listing.ejs) et seulement au survol de la miniature. */
.carte-news-editer {
  position: absolute;
  top: .5rem;
  right: .5rem;
  padding: .25rem .56rem;
  border-radius: var(--rayon-controle);
  background: rgba(0, 0, 0, .72);
  color: #fff;
  font-size: .72rem;
  font-weight: 600;
  text-decoration: none;
  opacity: 0;
  transform: translateY(-.25rem);
  transition: opacity .15s ease, transform .15s ease;
}

.carte-news-image-conteneur:hover .carte-news-editer,
.carte-news-editer:focus-visible {
  opacity: 1;
  transform: translateY(0);
}

/* .carte-news-corps regroupe TOUT le contenu de la carte (catégorie, titre,
   image, extrait, méta, actions) - image comprise, voir listing.ejs : elle
   n'est plus un élément séparé à côté de corps, mais un enfant de corps
   parmi les autres, placé dans le DOM entre le titre et l'extrait. Ce choix
   (plutôt qu'une image en frère séparé comme avant) sert le mode Liste
   mobile plus bas : un élément flottant (float) n'affecte jamais ce qui le
   précède dans le flux, donc placer catégorie+titre AVANT l'image dans le
   DOM garantit qu'ils restent en pleine largeur quel que soit le mode
   d'affichage, sans dépendre d'un correctif CSS par mode.
   Par défaut (règles ci-dessous, avant tout média-query) : mise en page du
   mode Liste desktop - grille CSS 2 colonnes (image à gauche pleine
   hauteur, texte à droite) via des grid-area nommées, qui replacent
   l'image où on veut visuellement SANS dépendre de sa position dans le DOM
   (contrairement à un flottant). Le mode Grille et le mode Liste mobile
   redéfinissent .carte-news-corps plus bas pour leurs propres mises en
   page (flex empilé, puis bloc + flottant). */
.carte-news-corps {
  display: grid;
  grid-template-columns: 17rem 1fr;
  grid-template-areas:
    "image categorie"
    "image titre"
    "image extrait"
    "image meta"
    "image actions";
  column-gap: 1.375rem;
  padding: 1.125rem 1.375rem 1.125rem 0;
  /* Centre le bloc de texte empilé (5 lignes) verticalement face à l'image
     quand celle-ci est plus haute que le texte, comme le faisait avant
     .carte-news-corps en display:flex + justify-content:center - avec des
     lignes de grille en "auto" (hauteur au contenu), l'espace excédentaire
     dû à l'image (qui s'étend sur les 5 lignes) devient de l'espace libre
     que align-content répartit, ici en le centrant au milieu du groupe de
     lignes plutôt qu'en étirant chaque ligne individuellement. */
  align-content: center;
}

.carte-news-corps > .carte-news-categorie { grid-area: categorie; }
.carte-news-corps > .carte-news-titre { grid-area: titre; min-width: 0; }
.carte-news-corps > .carte-news-extrait { grid-area: extrait; min-width: 0; }
.carte-news-corps > .carte-news-meta { grid-area: meta; }
.carte-news-corps > .carte-news-actions { grid-area: actions; }

.carte-news-image-conteneur {
  position: relative;
  grid-area: image;
  /* .carte-news-corps a un padding (voir plus haut) pour que le texte ne
     touche pas les bords de la carte - mais l'image, elle, doit continuer
     à mordre jusqu'aux bords haut/bas comme avant (le padding gauche est
     déjà à 0 sur le conteneur, donc rien à compenser de ce côté). Cette
     marge négative annule le padding haut/bas uniquement pour la cellule
     image, sans toucher au padding des autres cellules. */
  margin: -1.125rem 0;
}

.liste-news--grille .carte-news-corps {
  display: flex;
  flex-direction: column;
  gap: .375rem;
  padding: 1.125rem 1.375rem;
}

.liste-news--grille .carte-news-image-conteneur {
  /* order:-1 replace l'image visuellement en premier (en haut) malgré sa
     position dans le DOM (entre le titre et l'extrait, voir plus haut) -
     flex, comme grid, ignore l'ordre du DOM dès qu'on lui dit où placer
     les choses. Marge négative même principe qu'en desktop (voir plus
     haut), adaptée au padding uniforme de ce mode (mord aussi sur les
     côtés ici, car l'image est en haut d'une colonne plutôt qu'à gauche
     d'une ligne). */
  order: -1;
  margin: -1.125rem -1.375rem 0;
}

/* --- Repère de catégorie (pastille + libellé coloré) ---------------------- */

.carte-news-categorie {
  display: flex;
  align-items: center;
  gap: .44rem;
}

.pastille-categorie {
  display: inline-block;
  flex-shrink: 0;
  width: .625rem;
  height: .625rem;
  background: oklch(0.72 0.15 var(--teinte-categorie));
}

.pastille-rond { border-radius: 50%; }
.pastille-carre { border-radius: .125rem; }
.pastille-losange { border-radius: .125rem; transform: rotate(45deg); }

.etiquette-categorie {
  font-size: .78rem;
  font-weight: 600;
  letter-spacing: .01em;
  color: oklch(0.5 0.14 var(--teinte-categorie));
}

.carte-news-titre {
  margin: .125rem 0 0;
  font-family: 'Space Grotesk', sans-serif;
  font-weight: 600;
  font-size: 1.0625rem;
  line-height: 1.35;
}

.carte-news-titre a {
  color: var(--texte);
  text-decoration: none;
}

.carte-news-titre a:hover {
  text-decoration: underline;
}

.carte-news-extrait {
  margin: 0;
  color: var(--discret);
  font-size: .875rem;
  line-height: 1.5;
  overflow: hidden;
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
}

.carte-news-meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: .5rem;
  margin: .125rem 0 0;
  font-size: .8125rem;
  color: var(--discret);
}

.point-separateur { color: var(--bordure-forte); }

/* Marque cliquable (carte du listing + en-tête d'article) : même couleur
   discrète que le texte environnant (voir `a { color: inherit }` global),
   juste un soulignement au survol pour signaler que c'est cliquable. */
.lien-marque:hover { text-decoration: underline; }

.carte-news-actions {
  display: flex;
  align-items: center;
  gap: .875rem;
  margin: .25rem 0 0;
}

.bouton-lire {
  font-size: .84rem;
  font-weight: 600;
  color: var(--accent);
  text-decoration: none;
}

.bouton-lire:hover { text-decoration: underline; }

.bouton-source {
  font-size: .84rem;
  font-weight: 500;
  color: var(--discret);
  text-decoration: none;
}

.bouton-source:hover { text-decoration: underline; }

@media (max-width: 40rem) {
  /* Mode Liste sur mobile (retour de Thomas, 17/08/2026, 3e itération) :
     titre en pleine largeur, extrait en dessous en pleine largeur (plus de
     miniature du tout dans ce mode - la 2e itération la gardait, en petit,
     avec l'extrait qui s'enroulait autour via un float ; Thomas a préféré
     la retirer complètement). Sans image, plus besoin du float, mais la
     règle de base (desktop) utilise une grille 2 colonnes "17rem 1fr" avec
     des grid-area nommées - sans ce display:block, cette grille resterait
     active ici (masquer l'image avec display:none ne suffit pas à faire
     disparaître la colonne qu'elle occupait) et laisserait un grand vide à
     gauche de chaque carte. En bloc normal, les enfants s'empilent tout
     simplement dans leur ordre du DOM. */
  .liste-news:not(.liste-news--grille) .carte-news-corps {
    display: block;
    padding: .875rem 1rem;
  }

  .liste-news:not(.liste-news--grille) .carte-news-image-conteneur {
    display: none;
  }

  .liste-news:not(.liste-news--grille) .carte-news-titre {
    font-size: .9375rem;
  }

  /* Pas de troncature : Thomas veut le texte complet, pas coupé. */
  .liste-news:not(.liste-news--grille) .carte-news-extrait {
    -webkit-line-clamp: unset;
  }

  /* Date/marque jugées peu utiles dans ce mode compact (question de
     Thomas, 17/08/2026) : masquées ici uniquement (toujours visibles en
     desktop, en Grille, et sur la page article). Simple à ramener si
     besoin - une seule ligne à retirer. */
  .liste-news:not(.liste-news--grille) .carte-news-meta {
    display: none;
  }

  /* "Lire sur Crea News" et "Voir la source" sur la même ligne (demande de
     Thomas) - c'est déjà le comportement par défaut de .carte-news-actions
     (display:flex, pas de flex-direction:column ici contrairement à la 2e
     itération), donc rien à forcer. Seul ajout : flex-wrap, pour que les
     deux textes puissent redescendre proprement l'un sous l'autre s'ils
     ne tiennent pas côte à côte sur un écran étroit, plutôt que de
     déborder de la carte. */
  .liste-news:not(.liste-news--grille) .carte-news-actions {
    flex-wrap: wrap;
  }
}

/* --- Pagination ------------------------------------------------------------ */

.pagination {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-top: 2rem;
  padding-top: 1.25rem;
  border-top: 1px solid var(--bordure);
  font-size: .84rem;
}

.pagination a {
  text-decoration: none;
  font-weight: 600;
  color: var(--accent);
}

.pagination a:hover {
  text-decoration: underline;
}

.pagination-inactif {
  color: var(--bordure-forte);
}

.pagination-etat {
  color: var(--discret);
}

/* --- Article ---------------------------------------------------------------- */

.fil-ariane {
  margin: 1.5rem 0 1.5rem;
  font-size: .875rem;
  display: flex;
  align-items: center;
  gap: .5rem;
  color: var(--discret);
}

.fil-ariane a {
  text-decoration: none;
  color: var(--discret);
}

.fil-ariane a:hover {
  text-decoration: underline;
}

.fil-ariane-separateur {
  color: var(--discret);
}

.article-entete-meta { margin-bottom: .875rem; }

.article-titre {
  margin: 0 0 .875rem;
  font-size: 1.875rem;
  line-height: 1.3;
}

.article-sous-titre {
  margin: 0 0 1.375rem;
  color: var(--discret);
  font-size: .875rem;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: .875rem;
}

.bouton-partager {
  border: none;
  background: none;
  padding: 0;
  font: inherit;
  font-weight: 600;
  color: var(--accent);
  cursor: pointer;
}

.article-intro {
  margin: 0 0 1.375rem;
  font-size: 1.125rem;
  line-height: 1.6;
  font-weight: 500;
  color: var(--texte);
}

.article-image {
  /* Hauteur naturelle (pas de aspect-ratio/object-fit ici) : demande de
     Thomas le 15/08/2026, l'image de l'article doit s'afficher en entier,
     sans recadrage — contrairement aux vignettes du listing (.carte-news-
     image), qui gardent un format fixe pour l'alignement de la grille. */
  width: 100%;
  border-radius: var(--rayon-panneau);
  margin-bottom: 1.125rem;
  display: block;
}

.article-image-vide {
  /* Contrairement au <img> réel ci-dessus, cette vignette n'a pas de contenu
     à "montrer en entier" — juste un bloc décoratif qui a besoin d'une
     hauteur définie, donc garde son format fixe. */
  aspect-ratio: 16 / 9;
  display: flex;
  align-items: center;
  justify-content: center;
  background: repeating-linear-gradient(
    135deg,
    oklch(0.9 0.03 var(--teinte-categorie)) 0 14px,
    oklch(0.94 0.006 55) 14px 28px
  );
}

.article-cible-ligne {
  display: flex;
  justify-content: flex-end;
  margin-bottom: 1.625rem;
}

.bouton-cible {
  display: inline-flex;
  align-items: center;
  gap: .3rem;
  padding: .625rem 1.25rem;
  border-radius: var(--rayon-controle);
  background: var(--accent);
  /* Texte sombre plutôt que blanc : voir la note sur .onglet-actif plus haut
     dans ce fichier (7.4:1 de contraste au lieu de 2.26:1). Le fond orange
     ne change JAMAIS au survol (ni assombri, ni ombre portée) - Thomas ne
     voulait ni l'un ni l'autre ; seule la flèche bouge, voir plus bas. --sur-
     accent (fixe, pas --texte) : voir la note sur ce token plus haut. */
  color: var(--sur-accent);
  text-decoration: none;
  font-weight: 600;
  font-size: .875rem;
}

.bouton-cible-fleche {
  display: inline-block;
}

.bouton-cible:hover .bouton-cible-fleche {
  transform: translate(2px, -2px);
}

.article-corps {
  font-size: 1rem;
  line-height: 1.75;
  color: var(--texte-article);
}

.article-corps :first-child {
  margin-top: 0;
}

/* --- Navigation actu précédente/suivante ---------------------------------- */

.article-nav-voisins {
  display: flex;
  gap: 1rem;
  margin: 2.25rem auto 0;
  padding-top: 1.5rem;
  border-top: 1px solid var(--bordure);
}

.article-nav-carte { flex: 1; min-width: 0; }
.article-nav-carte-droite { display: flex; justify-content: flex-end; }

.article-nav-lien {
  display: flex;
  align-items: center;
  gap: .75rem;
  padding: 1rem 1.125rem;
  border-radius: var(--rayon-panneau);
  background: var(--fond-alt);
  text-decoration: none;
  min-width: 0;
}

.article-nav-lien-droite {
  justify-content: flex-end;
}

.article-nav-fleche {
  font-size: 1.25rem;
  font-weight: 700;
  color: var(--accent);
  flex-shrink: 0;
}

.article-nav-texte {
  display: flex;
  flex-direction: column;
  gap: .25rem;
  min-width: 0;
}

.article-nav-lien-droite .article-nav-texte {
  align-items: flex-end;
  text-align: right;
}

.article-nav-etiquette-ligne {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: .75rem;
  width: 100%;
  min-width: 0;
}

.article-nav-etiquette {
  font-size: .69rem;
  font-weight: 600;
  /* Même orange que la flèche juste à côté (.article-nav-fleche) — c'était
     --accent-fonce (marron terne) avant le 15/08/2026, incohérent avec la
     flèche qui, elle, était déjà en --accent. */
  color: var(--accent);
  white-space: nowrap;
}

.article-nav-etiquette-categorie {
  font-size: .69rem;
  font-weight: 600;
  color: var(--discret);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.article-nav-titre {
  font-size: .875rem;
  font-weight: 600;
  color: var(--texte);
  line-height: 1.35;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  min-width: 0; /* sans ça, un span dans une colonne flex refuse de se
    réduire sous la largeur de son contenu : l'ellipsis ne s'applique
    jamais et le texte déborde de sa carte (voir lib/news.js pour le
    vrai correctif — le titre est déjà tronqué avant d'arriver ici). */
}

/* --- Pages simples (à propos, contact) ------------------------------------ */

.page-titre {
  margin: 1.5rem 0 .625rem;
  font-size: 1.875rem;
  line-height: 1.3;
}

.page-intro {
  margin: 0 0 1.625rem;
  color: var(--discret);
  font-size: .9375rem;
}

.page-texte {
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: 1rem;
  font-size: 1rem;
  line-height: 1.75;
  color: var(--texte-article);
}

.page-texte p { margin: 0; }

/* --- Admin ------------------------------------------------------------- */

.admin-entete {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 1rem;
  padding: 1rem 1.5rem;
  border-bottom: 1px solid var(--bordure);
  background: var(--fond-alt);
  flex-wrap: wrap;
}

.admin-entete-gauche {
  display: flex;
  align-items: center;
  gap: 1.75rem;
  flex-wrap: wrap;
}

.entete-logo-ligne {
  display: inline-flex;
  align-items: center;
  gap: .625rem;
  text-decoration: none;
}

.entete-logo-icone-admin {
  height: 1.75rem;
  width: auto;
}

.admin-nav {
  display: flex;
  gap: 1.25rem;
  font-size: .9rem;
}

.admin-nav a {
  text-decoration: none;
  color: var(--discret);
}

.admin-nav a:hover {
  color: var(--texte);
}

.admin-deconnexion {
  display: flex;
  align-items: center;
  gap: .75rem;
  font-size: .85rem;
  color: var(--discret);
}

.lien-bouton {
  background: none;
  border: none;
  padding: 0;
  font: inherit;
  color: inherit;
  text-decoration: underline;
  cursor: pointer;
}

/* Messages flash */

.flash {
  margin: 0 0 1.5rem;
  padding: .75rem 1rem;
  border-radius: var(--rayon-panneau);
  font-size: .9rem;
}

.flash-succes {
  background: var(--ok-fond);
  color: var(--ok-texte);
}

.flash-erreur {
  background: color-mix(in srgb, var(--ko) 12%, transparent);
  color: var(--ko);
}

/* Formulaires */

.formulaire {
  display: flex;
  flex-direction: column;
  gap: .35rem;
  max-width: 36rem;
  margin: 0;
}

.formulaire-etroit {
  max-width: 24rem;
}

/* Formulaire public (contact) : mêmes champs/boutons que l'admin, mais sans
   le plafond de largeur pensé pour l'admin — il doit remplir sa colonne
   (.page-etroite, 920px), comme dans le design. */
.formulaire-pleine-largeur {
  max-width: none;
}

.formulaire label {
  margin-top: .75rem;
  font-size: .85rem;
  font-weight: 600;
  color: var(--discret-fort);
}

.formulaire input[type="text"],
.formulaire input[type="email"],
.formulaire input[type="password"],
.formulaire input[type="url"],
.formulaire input[type="number"],
.formulaire input[type="datetime-local"],
.formulaire input[type="file"],
.formulaire select,
.formulaire textarea {
  padding: .55rem .7rem;
  border: 1px solid var(--bordure-forte);
  border-radius: var(--rayon-controle);
  background: var(--fond-panneau);
  color: var(--texte);
  font: inherit;
  font-size: .9375rem;
  width: 100%;
  outline: none;
}

.formulaire textarea {
  resize: vertical;
}

#body,
#prompt {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: .85rem;
}

.formulaire button[type="submit"],
.formulaire > button {
  align-self: flex-start;
  margin-top: 1.25rem;
  padding: .6rem 1.375rem;
  border: none;
  border-radius: var(--rayon-controle);
  background: var(--accent);
  /* Texte sombre plutôt que blanc — même correctif de contraste que
     .onglet-actif plus haut. Le fond ne s'assombrit plus au survol (voir
     .bouton-cible pour le raisonnement complet) ; le retour au clic vient du
     léger tassement déjà défini plus bas (.formulaire button[type='submit']:active).
     --sur-accent (fixe, pas --texte) : voir la note sur ce token plus haut. */
  color: var(--sur-accent);
  font: inherit;
  font-weight: 600;
  cursor: pointer;
}

.champ-marques {
  margin-top: .75rem;
  border: 1px solid var(--bordure);
  border-radius: var(--rayon-controle);
  padding: .75rem;
  display: flex;
  flex-wrap: wrap;
  gap: .5rem 1.25rem;
}

.champ-marques legend {
  font-size: .85rem;
  font-weight: 600;
  padding: 0 .35rem;
}

.generation-ia {
  margin-bottom: 1.5rem;
  border: 1px dashed var(--bordure-forte);
  border-radius: var(--rayon-panneau);
  padding: .75rem;
}

.generation-ia legend {
  font-size: .85rem;
  font-weight: 600;
  padding: 0 .35rem;
}

.generation-ia-ligne {
  display: flex;
  gap: .5rem;
}

.generation-ia-ligne input {
  flex: 1;
  padding: .55rem .7rem;
  border: 1px solid var(--bordure-forte);
  border-radius: var(--rayon-controle);
  background: var(--fond-panneau);
  color: var(--texte);
  font: inherit;
}

.case-marque {
  display: flex;
  align-items: center;
  gap: .35rem;
  font-size: .9rem;
  font-weight: 400;
}

.apercu-image {
  max-width: 12rem;
  border-radius: var(--rayon-controle);
  margin: .5rem 0;
  display: block;
}

#apercu-image-conteneur {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: .35rem;
}

/* Listes admin (catégories, marques, news, messages) — grille CSS plutôt
   qu'un vrai <table>, pour pouvoir faire de chaque ligne un unique <form>
   (impossible avec <tr>/<td>, qui n'acceptent pas <form> comme enfant
   direct). */

.grille-admin {
  display: grid;
  gap: 0;
  margin-bottom: 2rem;
  border: 1px solid var(--bordure);
  border-radius: var(--rayon-panneau);
  overflow: hidden;
  background: var(--fond-panneau);
}

.grille-admin-categories { grid-template-columns: 2fr 2fr 5rem auto; }
.grille-admin-marques { grid-template-columns: 2fr 2fr auto; }
.grille-admin-news { grid-template-columns: 6rem 3fr 1fr 7rem auto; }
.grille-admin-messages { grid-template-columns: 6rem 14rem 2fr 6rem 8rem; }
.grille-admin-textes { grid-template-columns: 16rem 1fr 8rem; }

.texte-cle {
  display: flex;
  flex-direction: column;
  gap: .2rem;
  font-size: .85rem;
  word-break: break-word;
}

.ligne-admin textarea {
  padding: .35rem .5rem;
  border: 1px solid var(--bordure-forte);
  border-radius: var(--rayon-controle);
  background: var(--fond-panneau);
  color: var(--texte);
  font: inherit;
  font-size: .9rem;
  width: 100%;
  resize: vertical;
}

.ligne-admin {
  display: grid;
  grid-template-columns: subgrid;
  grid-column: 1 / -1;
  align-items: center;
  gap: .75rem;
  padding: .6rem 1rem;
  border-top: 1px solid var(--bordure);
}

.ligne-admin:first-child { border-top: none; }

.ligne-admin-entete {
  font-size: .8rem;
  font-weight: 600;
  color: var(--discret);
  background: var(--fond-alt);
}

.ligne-admin input {
  padding: .35rem .5rem;
  border: 1px solid var(--bordure-forte);
  border-radius: var(--rayon-controle);
  background: var(--fond-panneau);
  color: var(--texte);
  font: inherit;
  font-size: .9rem;
  width: 100%;
}

.champ-court { max-width: 5rem; }

.cellule-discrete {
  color: var(--discret);
  font-size: .85rem;
}

.message-contenu {
  color: var(--discret-fort);
  font-size: .85rem;
  overflow: hidden;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
}

.actions-ligne {
  display: flex;
  gap: .5rem;
  justify-self: end;
}

.actions-ligne button,
.ligne-admin > button {
  padding: .35rem .75rem;
  border: 1px solid var(--bordure-forte);
  border-radius: var(--rayon-controle);
  background: var(--fond-panneau);
  color: var(--texte);
  font: inherit;
  font-size: .85rem;
  cursor: pointer;
}

.bouton-danger {
  color: var(--ko);
  border-color: var(--ko);
}

.badge {
  padding: .15rem .55rem;
  border-radius: 999px;
  background: var(--fond-alt);
  color: var(--discret-fort);
  font-size: .75rem;
  white-space: nowrap;
}

.badge-publie {
  background: color-mix(in srgb, var(--ok) 16%, transparent);
  color: var(--ok);
}

.badge-brouillon {
  background: var(--bordure);
  color: var(--discret-fort);
}

.bouton-lien {
  display: inline-block;
  margin-bottom: 1.5rem;
  padding: .6rem 1.375rem;
  border-radius: var(--rayon-controle);
  background: var(--accent);
  /* Même correctif de contraste que .bouton-cible / .formulaire button
     (--sur-accent fixe, pas --texte). */
  color: var(--sur-accent);
  text-decoration: none;
  font-weight: 600;
}

/* Éditeur de news + panneau d'aperçu : côte à côte en desktop (assez de
   largeur pour les deux colonnes), empilés en dessous sinon — sans media
   query, le flex-wrap suffit vu les largeurs minimales fixées ci-dessous. */

.editeur-avec-apercu {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  gap: 2rem;
}

.editeur-avec-apercu .formulaire {
  flex: 1 1 26rem;
}

.actions-formulaire {
  display: flex;
  gap: .75rem;
  align-items: center;
}

.actions-formulaire button {
  margin-top: 1.25rem;
}

.bouton-secondaire {
  /* Bug préexistant trouvé le 15/08/2026 (signalé par Thomas sur le bouton
     "Prévisualiser") : cette classe ne définissait QUE la couleur, en
     supposant hériter du padding/rayon/police de ".formulaire > button" —
     qui ne matche que les boutons ENFANTS DIRECTS de .formulaire. Les deux
     boutons qui utilisent .bouton-secondaire (#bouton-apercu, #bouton-generer)
     sont nichés plus profond (dans .actions-formulaire / .generation-ia-ligne),
     donc ne recevaient jamais ce style de base — d'où un bouton "nu". Rendu
     autonome ici plutôt que dépendant d'un sélecteur qu'il ne matche pas. */
  padding: .6rem 1.375rem;
  border-radius: var(--rayon-controle);
  font: inherit;
  font-weight: 600;
  cursor: pointer;
  background: var(--fond-panneau) !important;
  color: var(--texte) !important;
  border: 1px solid var(--bordure-forte) !important;
}

.bouton-secondaire:hover {
  border-color: var(--accent) !important;
}

.panneau-apercu {
  flex: 1 1 24rem;
  border: 1px solid var(--bordure);
  border-radius: var(--rayon-panneau);
  padding: 1.5rem;
  max-height: 90vh;
  overflow-y: auto;
  background: var(--fond-panneau);
}

.panneau-apercu h2 {
  margin-top: 0;
  font-size: 1.1rem;
}

/* --- Mouvement restreint (MOTION_INTENSITY ~3, passe design du 15/08/2026) -
   Uniquement des transitions CSS sur des états déjà existants (hover/focus/
   active), zéro JS ajouté, zéro boucle continue. Toujours désactivé sous
   prefers-reduced-motion. --------------------------------------------- */

@media (prefers-reduced-motion: no-preference) {
  a,
  button,
  input,
  select,
  textarea,
  .onglet,
  .bascule-vue-bouton,
  .carte-news,
  .carte-news-editer,
  .bouton-cible-fleche {
    transition: color .16s var(--ease-doux), background-color .16s var(--ease-doux),
      border-color .16s var(--ease-doux), box-shadow .16s var(--ease-doux),
      transform .16s var(--ease-doux), opacity .16s var(--ease-doux);
  }

  /* Retour tactile : un léger tassement au clic sur les boutons d'action,
     plutôt qu'un simple changement de couleur instantané. */
  .bouton-cible:active,
  .bouton-lien:active,
  .formulaire button[type='submit']:active,
  .formulaire > button:active,
  .onglet:active,
  .bascule-vue-bouton:active {
    transform: scale(.97);
  }

  /* Léger relief au survol des cartes de news (liste et grille) — remplace
     l'absence totale d'état hover par un signal discret que la carte est
     cliquable. Pas d'ombre portée : le reste du site n'en a aucune (avis de
     Thomas le 15/08/2026) - juste la bordure qui fonce et un léger décalage. */
  .carte-news:hover {
    border-color: var(--bordure-forte);
    transform: translateY(-2px);
  }
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
  }
}

/* Apparition douce des cartes au scroll — CSS natif (animation-timeline),
   pas de librairie JS. Protégé par @supports : dans les navigateurs qui ne
   le prennent pas en charge, la règle entière est ignorée et les cartes
   restent visibles normalement (aucun risque de contenu invisible). */
@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    .carte-news {
      animation: apparition-carte linear both;
      animation-timeline: view();
      animation-range: entry 0% cover 15%;
    }
  }
}

@keyframes apparition-carte {
  from { opacity: 0; transform: translateY(14px); }
  to { opacity: 1; transform: translateY(0); }
}

/* --- Header mobile : icône revenue à une taille fixe simple -------------- */
/* Les hauteurs mesurées de .entete-logo-icone (4.5rem / 5.8rem) sont
   calibrées pour le desktop, où la tagline tient sur 1-2 lignes. En dessous
   de 40rem (même seuil que .carte-news plus haut), la tagline peut passer à
   3-4 lignes selon la largeur de l'écran : essayer de faire correspondre la
   hauteur de l'icône à ce texte qui s'étire de façon continue produirait une
   icône énorme et disproportionnée. Plus simple et plus robuste : revenir à
   une icône de taille fixe et modeste, centrée verticalement à côté du
   texte - le même principe que l'ancien design, juste rapproché du texte
   (voir le gap réduit sur .entete-public-interieur). */
@media (max-width: 40rem) {
  .entete-public-interieur {
    align-items: center;
  }

  .entete-logo-icone,
  .entete-public-interieur:not(.page-etroite) .entete-logo-icone {
    height: 3.25rem;
  }

  /* Bug corrigé le 17/08/2026 (signalé par Thomas, capture d'écran mobile) :
     .entete-recherche-form a une largeur fixe (16.25rem) et ne rétrécit pas
     (flex-shrink:0), alors que .entete-logo-bloc juste à côté est en
     flex:1 + min-width:0 (rétrécissement illimité). Sur une largeur d'écran
     courante comme 414px (iPhone), le champ de recherche restait accroché
     sur la même ligne que le logo+tagline au lieu de passer dessous : c'est
     le bloc texte qui encaissait tout le manque de place en s'écrasant à
     une largeur minuscule (tagline à un mot par ligne), avec le champ de
     recherche qui semblait se superposer par-dessus. Fix : forcer le champ
     de recherche à occuper toute la largeur de la ligne suivante en dessous
     du logo, comme un bloc à part - flex-basis:100% force le
     retour à la ligne (le form ne peut plus tenir à côté du reste), plus
     robuste qu'un ajustement de largeurs au pixel près. */
  .entete-recherche-form {
    flex-basis: 100%;
    width: 100%;
    align-self: stretch;
  }
}
