/* ==========================================================================
   ESPACE CLIENT — 4. COMPOSANTS
   ==========================================================================
   Bibliothèque de composants de l'espace client. Elle remplace, pour cet
   espace, ce que Tabler + root.css fournissent à l'admin.

   On garde la MÊME GRAMMAIRE que l'admin (bloc + modificateurs `--variante`,
   états en `is-`, mêmes suffixes de dimension) mais on n'en reprend aucune
   règle : le client est autonome.

   Dépend de : client/tokens.css, client/base.css
   ========================================================================== */

/* ---------- Boutons ------------------------------------------------------ */
/* Gabarit REPRIS DE L'ADMIN (.app-btn, root.css) : mêmes padding (8px 20px),
   même taille de texte, même interligne (1.5) et même forme en pilule.
   La hauteur RENDUE dépend du contenu (présence d'une icône, longueur du
   libellé) : on aligne les règles, pas un nombre de pixels. */
.c-btn {
    display: inline-flex; align-items: center; justify-content: center;
    gap: var(--c-spacer-sm);
    padding: 8px 20px;
    border-radius: var(--c-radius-pill);
    border: 1px solid transparent;

    /* LE DÉGRADÉ DOIT COUVRIR LA BORDURE, PAS S'ARRÊTER AVANT.
       Par défaut un fond est CALÉ sur la boîte intérieure (background-origin:
       padding-box) mais PEINT jusqu'au bord extérieur (background-clip:
       border-box). Un dégradé se prolonge donc dans la bordure en s'aplatissant
       sur sa dernière couleur : le bouton LINK™ se retrouvait cerné d'un anneau
       de 1 px du violet le plus sombre (mesuré : rgb(102,49,192) contre
       rgb(155,122,239) juste à côté). D'où la bordure « étrange ».
       En calant l'origine sur la boîte extérieure, le dégradé couvre le bouton
       entier et l'anneau disparaît. Sans effet sur les fonds unis. */
    background-origin: border-box;
    font-family: inherit; font-size: var(--c-font-size-sm); font-weight: 500;
    line-height: 1.5; cursor: pointer; white-space: nowrap;
    transition: background var(--c-transition), border-color var(--c-transition), color var(--c-transition);
}
.c-btn--primary { background: var(--c-accent); color: var(--c-on-accent); }
.c-btn--primary:hover { background: var(--c-accent-hover); }

.c-btn--outline { background: transparent; border-color: var(--c-line); color: var(--c-ink); }
.c-btn--outline:hover { background: var(--c-elev-2); border-color: var(--c-elev-3); }

/* ---------- CROCHET : les boutons venus du code partagé -------------------
   `app-btn` est écrit en dur dans les vues partagées, dans les partials, et —
   c'est le cas qui oblige — par du JavaScript : searchable-select.js construit
   son bouton « Créer … » avec `app-btn app-btn--primary`. Il n'y a donc aucun
   markup à renommer.
   Une règle, deux sélecteurs : le bouton natif et le bouton plaqué sont le
   MÊME objet, ils ne peuvent pas diverger. Cf. l'en-tête de client/forms.css.
   Les variantes reprennent les rôles de l'administration, en valeurs sombres :
   success et primary y désignent la même action principale. */
.c-body .app-btn {
    display: inline-flex; align-items: center; justify-content: center;
    gap: var(--c-spacer-sm);
    padding: 8px 20px;
    border-radius: var(--c-radius-pill);
    border: 1px solid transparent;
    background-origin: border-box;
    font-family: inherit; font-size: var(--c-font-size-sm); font-weight: 500;
    line-height: 1.5; cursor: pointer; white-space: nowrap;
    color: var(--c-ink);
    transition: background var(--c-transition), border-color var(--c-transition), color var(--c-transition);
}
.c-body .app-btn--primary,
.c-body .app-btn--success {
    background: var(--c-accent);
    color: var(--c-on-accent);
    font-weight: 600;
}
.c-body .app-btn--primary:hover,
.c-body .app-btn--success:hover { background: var(--c-accent-hover); }
.c-body .app-btn--outline { background: transparent; border-color: var(--c-line); }
.c-body .app-btn--outline:hover { background: var(--c-elev-2); border-color: var(--c-elev-3); }
.c-body .app-btn--danger { background: var(--c-danger); color: #1A0A0D; font-weight: 600; }
.c-body .app-btn--danger:hover { background: var(--c-danger-hover); }
/* Deux variantes de plus, pour les pages annexes passées en sombre (23/09) :
   l'ORANGE des événements (« Demander un événement certifié ») et le VIOLET des
   LINK™ (« Acheter des LINK™ ») — les couleurs de la charte, là où l'admin les
   posait en app-btn--warning et app-btn--purple. */
.c-body .app-btn--warning { background: var(--c-warning); color: #26170A; font-weight: 600; }
.c-body .app-btn--warning:hover { background: color-mix(in srgb, var(--c-warning) 88%, white); }
.c-body .app-btn--purple { background: var(--c-link-strong); color: var(--c-on-link); font-weight: 600; }
.c-body .app-btn--purple:hover { background: var(--c-link); color: #1A1033; }
.c-body .app-btn--sm { padding: 4px 12px; font-size: var(--c-font-size-xs); }
.c-body .app-btn:disabled,
.c-body .app-btn.is-disabled { opacity: .45; cursor: not-allowed; pointer-events: none; }

/* ---------- CROCHET : l'avatar d'un compte ---------------------------------
   `User::avatarHtml` rend `<span class="avatar avatar-sm rounded-circle">` —
   une classe de Tabler, le socle de l'administration, que le gabarit client ne
   charge pas : sans règle, l'avatar n'était qu'un bout de texte. Ici, une
   pastille de surface relevée avec les initiales, ou la photo en fond. Les
   initiales arrivent déjà en capitales du modèle : jamais de `text-transform`. */
.c-body .avatar {
    flex: none;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 2.25rem;
    height: 2.25rem;
    aspect-ratio: 1;
    border-radius: var(--c-radius);
    background: var(--c-elev-3) center / cover no-repeat;
    color: var(--c-ink-dim);
    font-size: .75rem;
    font-weight: 600;
    letter-spacing: .02em;
}
.c-body .avatar.rounded-circle { border-radius: 50%; }
.c-body .avatar-sm { width: 1.75rem; height: 1.75rem; font-size: .625rem; }
.c-body .avatar-lg { width: 2.75rem; height: 2.75rem; font-size: .8125rem; }
.c-body .avatar-xl { width: 3.5rem; height: 3.5rem; font-size: 1rem; }

/* Les deux tailles de texte des vues partagées, et le bouton réduit à son
   icône (envoyer un email depuis une liste, par exemple). */
.c-body .app-text-xs { font-size: var(--c-font-size-xs); }
.c-body .app-text-sm { font-size: var(--c-font-size-sm); }
.c-body .app-btn--icon {
    width: 2rem;
    height: 2rem;
    padding: 0;
    border-radius: 50%;
    background: transparent;
    color: var(--c-ink-dim);
}
.c-body .app-btn--icon:hover { background: var(--c-elev-2); color: var(--c-ink); }
.c-body .app-btn--icon.app-btn--sm { width: 1.75rem; height: 1.75rem; font-size: var(--c-font-size-sm); }

/* ---------- CROCHET : la carte des vues partagées (.app-card) --------------
   Une boîte à en-tête, posée par une quinzaine de vues partagées (détail d'une
   commande, fiches récap…). En clair c'est du blanc sur du gris ; ici, une
   surface relevée, filet blanc transparent, coins arrondis — comme toutes les
   boîtes du socle. (23/09, avec les pages annexes.) */
.c-body .app-card {
    position: relative;                     /* le reflet de coin s'y accroche */
    border: 1px solid var(--c-glass-line);
    border-radius: var(--c-radius-lg);
    /* DU VERRE, COMME LES AUTRES BOÎTES DU CLIENT (23/09, Julien : « tu as souvent
       oublié dans les boîtes le flou et les bordures avec le coin lumineux ») : le
       fond de page transparaît, adouci. Le reflet de coin est posé plus bas, par
       crochet sur la recette commune (.c-lueur). */
    background-color: var(--c-glass-bg);
    -webkit-backdrop-filter: blur(var(--c-glass-blur)) saturate(140%);
    backdrop-filter: blur(var(--c-glass-blur)) saturate(140%);
    box-shadow: var(--c-shadow-md);
    overflow: hidden;
}
.c-body .app-card__header {
    display: flex;
    align-items: center;
    gap: var(--c-spacer-sm);
    padding: var(--c-spacer-sm) var(--c-spacer);
    border-bottom: 1px solid var(--c-line);
    background-color: var(--c-elev-2);
}
.c-body .app-card__title {
    display: flex;
    align-items: center;
    gap: var(--c-spacer-sm);
    margin: 0;
    font-size: var(--c-font-size-base);
    font-weight: 600;
}
.c-body .app-card__title i { color: var(--c-ink-faint); }
.c-body .app-card__body { padding: var(--c-spacer); }
.c-body .app-card__footer {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: var(--c-spacer-sm);
    padding: var(--c-spacer-sm) var(--c-spacer);
    border-top: 1px solid var(--c-line);
    background-color: var(--c-elev-2);
}
/* Variante « à plat » des fiches de compte : le pied se fond dans le corps. */
.c-body .app-card__footer--flat { background: transparent; }

/* ---------- CROCHET : les quelques utilitaires de grille des vues partagées --
   `row` / `col` / `col-3` / `d-flex` / `mb-3`… viennent du socle CSS de
   l'administration. Les vues partagées les écrivent en dur (shared/detail-row
   et les fiches récap) : sans eux, le client empile tout à plat. On n'importe
   pas une grille à douze colonnes pour autant — juste ces quelques classes,
   dans leur usage réel : un libellé à gauche, sa valeur à droite, et au
   téléphone l'un sous l'autre. */
.c-body .row { display: flex; flex-wrap: wrap; gap: 4px var(--c-spacer); }
.c-body .col { flex: 1 1 0; min-width: 0; }
.c-body .col-3 { flex: 0 0 auto; width: 22%; min-width: 0; color: var(--c-ink-dim); }
@media (max-width: 768px) {
    .c-body .col-3 { width: 100%; }
}
.c-body .d-flex { display: flex; }
.c-body .d-block { display: block; }
.c-body .align-items-center { align-items: center; }
.c-body .gap-2 { gap: var(--c-spacer-sm); }
/* `mb-0` et `mb-3` sont déjà posés par client/forms.css, chargée par tous les
   écrans à formulaire : ne pas les redéclarer ici. */
.c-body .mt-1 { margin-top: 4px; }
.c-body .ms-2 { margin-left: var(--c-spacer-sm); }
.c-body .app-text-bold { font-weight: 600; }
.c-body .app-mb { margin-bottom: var(--c-spacer); }

/* ---------- CROCHET : le filet horizontal ---------------------------------
   `<hr class="app-divider">` est injecté à l'exécution par card-create.js
   entre deux catégories de classification. Un <hr> sans style prend la bordure
   grise CLAIRE du navigateur : sur fond sombre, il ressort en blanc et ne
   ressemble à aucun autre filet de la page. Défaut signalé à l'œil, pas au
   code — c'est exactement ce qu'une règle par défaut de navigateur produit. */
.c-hr,
.c-body .app-divider,
.c-body hr {
    height: 0;
    border: 0;
    border-top: 1px solid var(--c-line-soft);
}

/* Variante LINK™ — LE BOUTON D'ACHAT.
   ⚠ Son habillage complet (dégradé, relief, halo, comète de bordure,
   projecteur, balayage, étincelles) vit dans assets/css/client/action-fx.css,
   chargée par le gabarit juste après cette feuille, avec son script
   assets/js/client/action-fx.js.
   Rien n'en est écrit ici : ce bouton est le seul objet de l'interface à avoir
   une mécanique propre, et la répartir sur trois fichiers obligerait à les
   recoller pour comprendre une seule surface. */

/* Icône calibrée comme celle du panier : les deux éléments de la barre 2
   se retrouvent ainsi à la même hauteur exacte. */
.c-btn i { font-size: 1.05em; }  /* relatif, comme .app-btn i */

.c-btn--sm { padding: 4px 12px; font-size: var(--c-font-size-xs); }  /* = .app-btn--sm */
/* :disabled ne s'applique pas aux <a> — or la plupart des boutons du socle
   en sont. L'état passe donc aussi par une classe, comme côté admin. */
.c-btn:disabled,
.c-btn[disabled],
.c-btn.is-disabled { opacity: .45; cursor: not-allowed; pointer-events: none; }

/* ---------- Panier ------------------------------------------------------- */
/* Bouton nommé plutôt qu'icône seule : le mot « Panier » lève l'ambiguïté,
   surtout à côté d'un bouton d'achat. Le compteur reste en vert primaire. */
.c-cart {
    display: inline-flex; align-items: center; gap: var(--c-spacer-sm);
    padding: 8px var(--c-spacer) 8px 20px;
    border-radius: var(--c-radius-pill);
    border: 1px solid var(--c-line);
    background: transparent;
    color: var(--c-ink);
    font-family: inherit; font-size: var(--c-font-size-sm); font-weight: 500;
    line-height: 1.5; white-space: nowrap; cursor: pointer;   /* = .c-btn */
    transition: background var(--c-transition), border-color var(--c-transition);
}
/* Le panier est la seule action de la barre sans fond plein : c'est son
   CONTOUR qui répond. Deux intensités, et c'est ce qui rend le geste doux :
   le survol effleure, l'ouverture affirme.

   Le fond reste TRANSLUCIDE. Un aplat opaque posé dans une barre en verre
   apparaissait comme un bloc qui se pose d'un coup — c'était là, et non dans
   la durée, que le survol paraissait brutal.

   Le liseré blanc est à 40 % : à pleine intensité il tranchait beaucoup trop
   sur une barre dont tous les autres filets sont à 12 %. */
.c-cart:hover {
    background: color-mix(in srgb, var(--c-elev-2) 45%, transparent);
    border-color: color-mix(in srgb, #fff 40%, transparent);
}

.c-cart.is-open {
    background: color-mix(in srgb, var(--c-elev-2) 65%, transparent);
    border-color: color-mix(in srgb, #fff 60%, transparent);
}

/* Un peu plus lent que le reste, et en sortie douce : sur un contour, l'œil
   suit la transition — 200 ms linéaires s'y lisent comme un basculement. */
.c-cart { transition: background 280ms ease-out, border-color 280ms ease-out; }
.c-cart i { font-size: 1.05em; }

/* Sur téléphone ET SUR TABLETTE (jusqu'à 1024 px depuis le 21/09, même seuil
   que les formes courtes de action-fx.css), le panier se réduit à son icône et
   son compteur : la place va aux onglets et aux deux boutons d'action, qui
   gardent un libellé court.

   Le mot « Panier » n'est masqué qu'À L'ŒIL (recette de .c-sr, base.css) :
   `display: none` le retirait aussi du nom accessible, et le bouton ne
   s'annonçait plus que « 3 articles », voire « vide ».

   PANIER VIDE (21/09). Le rembourrage 16 / 8 équilibre la pastille du compteur,
   posée à droite ; sans elle, l'icône seule était décentrée de 4 px. Même
   largeur totale, répartie également : la pastille ronde ne change pas de
   taille, seule l'icône revient au centre. */
@media (max-width: 1024px) {
    .c-cart { padding: 8px var(--c-spacer-sm) 8px var(--c-spacer); }
    .c-cart:not(:has(.c-cart__count)) { padding-inline: calc((var(--c-spacer) + var(--c-spacer-sm)) / 2); }
    .c-cart__label {
        position: absolute; width: 1px; height: 1px;
        padding: 0; margin: -1px; overflow: hidden;
        clip: rect(0 0 0 0); white-space: nowrap; border: 0;
    }
}
.c-cart__count {
    min-width: 18px; height: 18px; padding: 0 5px;
    border-radius: var(--c-radius-pill);
    background: var(--c-accent); color: var(--c-on-accent);
    font-size: 10px; font-weight: 700; line-height: 18px; text-align: center;
}

/* ---------- Le panier tressaille quand on y met quelque chose ---------------
   (29/09, Julien : « quand on ajoute au panier, qu'il y ait une animation sur le
   panier dans la nav »). La classe est posée le temps de l'animation par
   client/boutique.js, où que l'ajout ait eu lieu — vitrine, fiche ou modale.
   Le bouton se soulève, la pastille tape une fois : de quoi suivre des yeux ce
   qu'on vient de faire, sans déplacer quoi que ce soit autour. */
.c-cart.is-ajout { animation: c-cart-saut 520ms cubic-bezier(.34, 1.56, .64, 1); }
.c-cart.is-ajout .c-cart__count { animation: c-cart-pastille 520ms cubic-bezier(.34, 1.56, .64, 1); }
.c-cart.is-ajout i { animation: c-cart-panier 520ms ease-out; }

@keyframes c-cart-saut {
    30% { transform: translateY(-4px); }
    60% { transform: translateY(1px); }
}
@keyframes c-cart-pastille {
    30% { transform: scale(1.45); background: var(--c-link); color: var(--c-on-link); }
    70% { transform: scale(.95); }
}
@keyframes c-cart-panier {
    25% { transform: rotate(-12deg); }
    55% { transform: rotate(8deg); }
    80% { transform: rotate(-3deg); }
}

@media (prefers-reduced-motion: reduce) {
    /* Mouvement réduit : la pastille change de teinte le temps de l'animation,
       rien ne bouge. L'information passe quand même. */
    .c-cart.is-ajout,
    .c-cart.is-ajout i { animation: none; }
    .c-cart.is-ajout .c-cart__count { animation: c-cart-pastille-douce 520ms ease-out; }
    @keyframes c-cart-pastille-douce {
        0%, 70% { background: var(--c-link); color: var(--c-on-link); }
    }
}

/* ---------- Surface ------------------------------------------------------ */
.c-surface {
    background: var(--c-elev-1);
    border: 1px solid var(--c-line);
    border-radius: var(--c-radius-xl);
    padding: var(--c-spacer-md);
}

/* ---------- Étiquette ---------------------------------------------------- */
.c-badge {
    display: inline-flex; align-items: center; gap: var(--c-spacer-xs);
    padding: 3px 10px; border-radius: var(--c-radius-pill);
    font-size: var(--c-font-size-xs); font-weight: 600;
    background: var(--c-elev-2); color: var(--c-ink-dim);
    border: 1px solid var(--c-line);
}

/* ---------- Alerte -------------------------------------------------------
   LE MESSAGE FIXE DANS LA PAGE — à distinguer de ses deux voisins : le
   `.c-toast` passe et s'efface, le `.c-banner` traverse toute la largeur de
   l'écran. L'alerte, elle, se pose là où le problème se trouve.

   Portée de l'admin (assets/css/alerts.css) à l'identique : icône alignée sur
   la première ligne, fond teinté doux, texte à la teinte, liens soulignés. Le
   même vocabulaire des deux côtés, en valeurs sombres.

   La bordure est ce que l'aplat clair de l'admin n'a pas besoin d'avoir : sur
   un fond sombre, un fond teinté à 14 % n'a pas d'arête franche et la boîte
   s'évapore. Le liseré à 24 % lui rend son contour sans l'alourdir (même
   raisonnement que la boîte de couleur sur fond gris, côté admin).

   DEUX CROCHETS : `app-alert` est écrit dans 14 vues partagées avec
   l'administration — le placage les habille toutes d'un coup, sans en
   renommer une seule. */
.c-alert,
.c-body .app-alert {
    display: flex;
    align-items: flex-start;
    gap: var(--c-spacer-sm);
    padding: var(--c-spacer-sm) var(--c-spacer);
    border-radius: var(--c-radius);
    background: var(--c-neutral-soft);
    background-clip: padding-box;
    border: 1px solid var(--c-line);
    color: var(--c-ink-dim);
    font-size: var(--c-font-size-sm);
    line-height: var(--c-line-height-prose);
}
.c-alert > i,
.c-body .app-alert > i {
    flex: none;
    margin-top: 0.1rem;
    font-size: 1.1rem;
    line-height: 1.35;
}
/* Variante à deux niveaux : un intitulé, puis le détail. */
.c-alert > div,
.c-body .app-alert > div { flex: 1; min-width: 0; }
.c-alert > div strong,
.c-body .app-alert > div strong { display: block; font-weight: 600; color: var(--c-ink); }
.c-alert a:not(.c-btn):not(.app-btn),
.c-body .app-alert a:not(.c-btn):not(.app-btn) {
    color: inherit;
    text-decoration: underline;
    font-weight: 550;
}

.c-alert--warning, .c-body .app-alert--warning {
    background: var(--c-warning-soft);
    border-color: color-mix(in srgb, var(--c-warning) 24%, transparent);
    color: var(--c-warning);
}
.c-alert--danger, .c-body .app-alert--danger {
    background: var(--c-danger-soft);
    border-color: color-mix(in srgb, var(--c-danger) 24%, transparent);
    color: var(--c-danger);
}
.c-alert--info, .c-body .app-alert--info {
    background: var(--c-info-soft);
    border-color: color-mix(in srgb, var(--c-info) 24%, transparent);
    color: var(--c-info);
}
.c-alert--success, .c-body .app-alert--success {
    background: var(--c-success-soft);
    border-color: color-mix(in srgb, var(--c-success) 24%, transparent);
    color: var(--c-success);
}
.c-alert--purple, .c-body .app-alert--purple {
    background: var(--c-link-soft);
    border-color: color-mix(in srgb, var(--c-link) 24%, transparent);
    color: var(--c-link);
}
/* `neutral` et `white` de l'admin retombent sur le défaut : sur fond sombre,
   il n'y a pas de « blanc » à opposer au gris. */
.c-alert--neutral, .c-body .app-alert--neutral,
.c-body .app-alert--white { background: var(--c-neutral-soft); color: var(--c-ink-dim); }

/* ---------- La boîte CONNECTÉE ------------------------------------------
   Écrite dans le SOCLE et non dans la feuille d'un écran : « cette boîte parle
   de LINK™ » n'est pas une notion de l'écran de création, c'en est une de
   l'espace entier. La page d'annonce publique la porte aussi.
   Chaque écran reste libre d'accélérer le rythme au survol — cela dépend de
   son propre conteneur, pas de la classe.
   Un LINK™ est un objet à puce : on approche le téléphone, la carte s'ouvre.
   La boîte qui en compte doit le DIRE, sans un mot de plus.

   DEUX GESTES, ET C'EST LA BOÎTE ENTIÈRE QUI LES FAIT — pas son icône. Une
   pastille qui clignote se lit comme un badge de notification ; c'est la
   surface complète qui doit avoir l'air sous tension.

     • LE POINT FAIT LE TOUR du contour, sans s'arrêter : le courant dans un
       fil. C'est la même géométrie que la comète du bouton d'achat, et c'est
       voulu — les deux objets parlent du même produit. Elle est ici deux fois
       plus lente et bien plus discrète : la boîte est un endroit où l'on règle
       un nombre, pas un appel à l'action.
     • LE CONTOUR GRÉSILLE, par salves courtes et inégales : l'arc électrique
       du contact. Une première version faisait partir une ONDE régulière du
       contour — juste, mais calme : elle disait « sans contact » là où il faut
       entendre « sous tension ». Ce qui fait lire « électrique » n'est ni la
       couleur ni la lueur, c'est l'IRRÉGULARITÉ : quatre décharges d'intensités
       inégales en 550 ms, puis quatre secondes de silence, puis deux décharges
       plus faibles. Une pulsation régulière, si vive soit-elle, se lit comme un
       battement de cœur.

   AU SURVOL, LE RYTHME SE RESSERRE — le tour passe de 4,6 s à 2,2 s, le
   grésillement de 5,4 à 2,6. Ce n'est pas la décharge qui s'allonge, c'est le
   silence qui raccourcit : l'objet devient nerveux quand la main approche. On
   change de NOM d'animation plutôt que de durée : changer la durée en cours de
   route provoquerait un saut, un nom neuf redémarre proprement à zéro.

   `--c-lien-tour` est TYPÉE : sans type, le navigateur ne sait pas interpoler
   entre deux angles et l'animation saute. `inherits: false` lui évite
   d'invalider le style de tout le sous-arbre à chaque trame. */
@property --c-lien-tour { syntax: '<angle>'; inherits: false; initial-value: 0deg; }

.c-lien {
    position: absolute;
    inset: 0;
    border-radius: inherit;
    pointer-events: none;
}

/* 1. LE POINT QUI FAIT LE TOUR.
   Le dégradé conique est peint sur toute la boîte puis ÉVIDÉ de son contenu
   par deux masques qui se soustraient : il n'en reste qu'un liseré d'un pixel,
   d'épaisseur constante jusque dans les angles. */
@keyframes c-lien-tour { to { --c-lien-tour: 360deg; } }
.c-lien::before {
    content: '';
    position: absolute;
    inset: 0;
    border-radius: inherit;
    padding: 1px;
    background: conic-gradient(
        from var(--c-lien-tour),
        transparent                                        0%,
        transparent                                       66%,
        color-mix(in srgb, var(--c-link) 40%, transparent) 86%,
        #FFFFFF                                           96%,
        color-mix(in srgb, var(--c-link) 40%, transparent) 99%,
        transparent                                      100%);
    -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
    -webkit-mask-composite: xor;
            mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
            mask-composite: exclude;
    animation: c-lien-tour 4.6s linear infinite;
}

/* 2. LE GRÉSILLEMENT — l'arc électrique du contact.
   L'onde disait « sans contact » ; elle disait aussi « calme ». Ce qu'il faut
   entendre ici, c'est le COURANT : une puce qu'on approche, un contact qui
   s'établit. Or ce qui fait lire « électrique » plutôt que « respiration »,
   ce n'est ni la couleur ni la lueur — c'est l'IRRÉGULARITÉ. Une pulsation
   régulière, si vive soit-elle, se lit comme un battement de cœur.

   D'où ces images-clés par PAIRES très rapprochées : chaque valeur est tenue
   quelques centièmes puis saute à la suivante, ce qui donne des transitions
   quasi carrées sans avoir à changer de fonction de progression. Le premier
   crépitement dure ~550 ms et compte quatre décharges d'intensités inégales ;
   le second, à mi-cycle, n'en compte que deux et plus faibles. Entre les deux,
   du SILENCE — c'est lui qui rend le crépitement remarquable. Une boîte qui
   grésille sans répit, à côté d'un compteur, deviendrait insupportable.

   Trois ombres pour une seule décharge : un filet clair POSÉ SUR la bordure
   (`inset`), un liseré violet juste à l'extérieur, et une nappe diffuse de
   12 px. La nappe déborde volontairement — une décharge sans halo n'a pas
   l'air d'éclairer — mais elle reste sous la gouttière de 16 px, et une lueur
   floue qui effleure la boîte voisine ne la salit pas, contrairement à un
   trait net. */
@keyframes c-lien-arc {
    0%,    1.4%  { opacity: 0; }
    1.5%,  2.6%  { opacity: .95; }
    2.7%,  3.8%  { opacity: 0; }
    3.9%,  4.6%  { opacity: .45; }
    4.7%,  7.2%  { opacity: 0; }
    7.3%,  9.4%  { opacity: 1; }
    9.5%, 10.2%  { opacity: .12; }
    10.3%,11.6%  { opacity: .8; }
    11.7%,55.9%  { opacity: 0; }
    56%,  57.1%  { opacity: .7; }
    57.2%,58.3%  { opacity: 0; }
    58.4%,59.1%  { opacity: .35; }
    59.2%,100%   { opacity: 0; }
}
.c-lien::after {
    content: '';
    position: absolute;
    inset: 0;
    border-radius: inherit;
    box-shadow:
        inset 0 0 0 1px color-mix(in srgb, #FFFFFF 72%, var(--c-link)),
        0 0 0 1px var(--c-link),
        0 0 12px 1px color-mix(in srgb, var(--c-link) 65%, transparent);
    opacity: 0;
    animation: c-lien-arc 5.4s linear infinite;
}

@keyframes c-lien-tour-vite { to { --c-lien-tour: 360deg; } }
/* Au survol, le crépitement REVIENT PLUS SOUVENT — même partition, jouée en
   moitié moins de temps. Ce n'est pas la décharge qui s'allonge, c'est le
   silence qui raccourcit : l'objet devient nerveux quand la main approche. */
@keyframes c-lien-arc-vite {
    0%,    1.4%  { opacity: 0; }
    1.5%,  2.6%  { opacity: 1; }
    2.7%,  3.8%  { opacity: 0; }
    3.9%,  4.6%  { opacity: .6; }
    4.7%,  7.2%  { opacity: 0; }
    7.3%,  9.4%  { opacity: 1; }
    9.5%, 10.2%  { opacity: .18; }
    10.3%,11.6%  { opacity: .9; }
    11.7%,49.9%  { opacity: 0; }
    50%,  51.4%  { opacity: .85; }
    51.5%,52.6%  { opacity: 0; }
    52.7%,53.6%  { opacity: .5; }
    53.7%,100%   { opacity: 0; }
}

/* La boîte connectée S'ARRÊTE, elle ne disparaît pas : le liseré violet reste,
   figé sur un point du contour, et le grésillement s'éteint. Ce qui doit
   cesser, c'est le mouvement — pas le signe. */
@media (prefers-reduced-motion: reduce) {
    .c-lien::before { animation: none; }
    .c-lien::after  { animation: none; opacity: 0; }
}

/* ---------- Notifications ------------------------------------------------ */
.c-toasts {
    position: fixed; z-index: var(--c-z-toast);
    left: var(--c-spacer); right: var(--c-spacer);
    /* remontées au-dessus de la barre d'onglets sur téléphone */
    bottom: calc(var(--c-tabbar-space) + var(--c-spacer-sm));
    display: flex; flex-direction: column; gap: var(--c-spacer-sm);
    pointer-events: none;
}
.c-toast {
    display: flex; align-items: flex-start; gap: var(--c-spacer-sm);
    padding: var(--c-spacer-sm) var(--c-spacer);
    border-radius: var(--c-radius-lg);
    background: var(--c-elev-3);
    border: 1px solid var(--c-line);
    box-shadow: var(--c-shadow-lg);
    font-size: var(--c-font-size-sm);
    pointer-events: auto;
    transition: opacity var(--c-transition);
}
.c-toast.is-leaving { opacity: 0; }
.c-toast i { flex: none; font-size: 1.15rem; line-height: 1.35; }
.c-toast__msg { flex: 1; min-width: 0; line-height: var(--c-line-height-prose); }
.c-toast__close {
    flex: none; background: none; border: 0; padding: 0;
    color: var(--c-ink-faint); cursor: pointer; font-size: 1.05rem; line-height: 1.35;
}
.c-toast__close:hover { color: var(--c-ink); }
.c-toast--success i { color: var(--c-accent); }
.c-toast--danger  i { color: var(--c-danger); }
.c-toast--warning i { color: var(--c-warning); }
.c-toast--info    i { color: var(--c-info); }

@media (min-width: 769px) {
    .c-toasts {
        left: auto; right: var(--c-spacer-md); bottom: var(--c-spacer-md);
        width: min(380px, calc(100vw - var(--c-spacer-lg)));
    }
}

/* ---------- Boîte du panier ---------------------------------------------- */
/* Aperçu déroulant, ancré sous le bouton « Panier ». Permet de vérifier son
   contenu sans quitter l'écran en cours. */
.c-cartwrap { position: relative; }

/* Boîte du panier en VERRE, même recette que les widgets — elle flotte
   au-dessus de la page, elle doit en laisser deviner le fond.
   Elle est un peu plus fermée que les widgets (var(--c-glass-bg-bar), 50 %
   contre 55 %) parce qu'elle porte des prix et des libellés fins.
   `background-clip: padding-box` : sinon le fond translucide passe sous la
   bordure et le liseré n'est plus un trait net. */
.c-cartbox {
    position: absolute; top: calc(100% + var(--c-spacer-sm)); right: 0; z-index: var(--c-z-pop);
    width: min(340px, calc(100vw - var(--c-spacer-lg)));
    background: var(--c-glass-bg);
    background-clip: padding-box;
    -webkit-backdrop-filter: blur(var(--c-glass-blur)) saturate(140%);
    backdrop-filter: blur(var(--c-glass-blur)) saturate(140%);
    border: 1px solid var(--c-glass-line);
    border-radius: var(--c-radius-xl);
    box-shadow: var(--c-shadow-lg);
    padding: var(--c-spacer);
    white-space: normal;
}
.c-cartbox[hidden] { display: none; }

/* La boîte étant translucide, son texte le plus pâle repose sur ce qui passe
   derrière. Sur une photo claire il tombe à 3,1:1 ; d'un cran plus clair il
   remonte à 5,7:1 tout en restant sous le texte principal — la hiérarchie est
   préservée. Même traitement que la baseline de page et le compteur de la
   vitrine face au même problème. */
.c-body:has(.c-page-bg) .c-cartbox__count,
.c-body:has(.c-page-bg) .c-cartbox__more,
.c-body:has(.c-page-bg) .c-cartbox__label small,
.c-body:has(.c-page-bg) .c-cartbox__item > i { color: var(--c-ink-dim); }

.c-cartbox__head {
    display: flex; align-items: baseline; justify-content: space-between;
    gap: var(--c-spacer-sm);
    padding-bottom: var(--c-spacer-sm);
    border-bottom: 1px solid var(--c-line);
}
.c-cartbox__title { font-size: var(--c-font-size-sm); font-weight: 600; }
.c-cartbox__count { font-size: var(--c-font-size-xs); color: var(--c-ink-faint); }

.c-cartbox__empty {
    padding: var(--c-spacer) 0;
    font-size: var(--c-font-size-sm); color: var(--c-ink-dim); text-align: center;
}

.c-cartbox__list {
    list-style: none; margin: 0; padding: var(--c-spacer-sm) 0;
    display: flex; flex-direction: column; gap: var(--c-spacer-sm);
}
.c-cartbox__item {
    display: flex; align-items: flex-start; gap: var(--c-spacer-sm);
    font-size: var(--c-font-size-sm);
}
.c-cartbox__item > i { flex: none; color: var(--c-ink-faint); font-size: 1.05rem; line-height: 1.4; }
.c-cartbox__label { flex: 1; min-width: 0; line-height: 1.4; }
.c-cartbox__label small { display: block; color: var(--c-ink-faint); font-size: var(--c-font-size-xs); }
.c-cartbox__price { flex: none; color: var(--c-ink); font-weight: 600; line-height: 1.4; }
.c-cartbox__more { font-size: var(--c-font-size-xs); color: var(--c-ink-faint); }

.c-cartbox__total {
    display: flex; align-items: baseline; justify-content: space-between;
    padding-top: var(--c-spacer-sm);
    border-top: 1px solid var(--c-line);
    font-size: var(--c-font-size-sm);
}
.c-cartbox__total strong { font-size: var(--c-font-size-md); }

.c-cartbox__actions {
    display: flex; flex-direction: column; gap: var(--c-spacer-sm);
    margin-top: var(--c-spacer);
}
.c-btn--block { width: 100%; }

/* ---------- Zone 2 : widgets --------------------------------------------- */
/* Format CONDENSÉ : une seule ligne. À gauche l'icône puis les libellés
   alignés, à droite le chiffre. Un widget tient ainsi en ~70px de haut au
   lieu de ~150px — on en voit plus d'un coup d'œil, ce qui est tout l'intérêt
   d'un tableau de bord.

   DIMENSIONNEMENT AUTOMATIQUE : chaque widget prend la largeur de son contenu
   et les widgets se rangent à GAUCHE, sans s'étirer. En pleine largeur, une
   grille à colonnes égales éloignerait le chiffre des libellés de plusieurs
   centaines de pixels — le regard ne ferait plus le lien entre les deux.
   Ils passent à la ligne quand la place manque, et occupent toute la largeur
   sur téléphone. */
.c-widgets {
    display: flex;
    flex-wrap: wrap;
    justify-content: flex-start;
    align-items: stretch;          /* même hauteur sur une même ligne */
    gap: var(--c-spacer);
}

/* VERRE DÉPOLI.
   Le fond du widget est translucide et floute ce qui passe derrière : sur un
   club qui a posé un fond de page, la photo transparaît sans jamais gêner la
   lecture. Sans fond, le flou n'a rien à flouter et le widget garde
   exactement l'aspect d'avant — la translucidité se fait alors sur --c-bg,
   qui est la couleur de --c-elev-1 en plus sombre.

   La BORDURE DE LUMIÈRE est ce qui fait tenir l'illusion : un liseré clair sur
   l'arête haute (l'ombre portée interne, en blanc très faible) donne au verre
   une épaisseur, comme une tranche éclairée par le haut. Sans elle, le widget
   n'est qu'un rectangle plus pâle.

   `backdrop-filter` ne coûte que lorsqu'il y a du mouvement DERRIÈRE : sur
   ordinateur la couche de fond est fixe et les widgets ne bougent pas, le flou
   est calculé une fois.

   SUR TÉLÉPHONE il ne coûte rien du tout, et pour une raison qu'il faut
   connaître : la rangée porte un masque de bord (client/canvas.css, bloc
   téléphone), et un MASQUE fait de son élément une racine d'arrière-plan — le
   flou des widgets qu'il contient n'a plus rien à échantillonner. Le verre y
   est donc inerte. On le désactive explicitement plus bas, sinon il se
   rallumerait d'un coup en fin de course, quand le script retire le masque.
   Règle générale à retenir : ne jamais poser masque, filtre, opacité ou mode
   de fusion sur un ANCÊTRE d'une surface de verre. */
.c-widget {
    position: relative;            /* ancre le reflet d'arête (::before) */
    flex: 0 0 auto;                /* largeur du contenu, jamais étirée */
    min-width: 240px;              /* plancher : reste lisible même si court */
    display: flex; align-items: center; gap: var(--c-spacer);
    background: var(--c-glass-bg);

    /* `padding-box` : SANS LUI LE LISERÉ BAVE.
       Par défaut un fond s'étend SOUS la bordure. Avec un fond translucide,
       la bordure n'est donc plus une ligne nette : c'est un mélange de fond,
       d'arrière-plan flouté et de sa propre couleur. En arrêtant le fond au
       bord intérieur, la bordure retrouve sa couleur pure et redevient un
       trait de 1 px franc. */
    background-clip: padding-box;

    -webkit-backdrop-filter: blur(var(--c-glass-blur)) saturate(140%);
    backdrop-filter: blur(var(--c-glass-blur)) saturate(140%);

    /* LE LISERÉ EST LA LUMIÈRE — il n'y a pas de seconde ligne.
       La version précédente cumulait une bordure claire ET une ombre interne
       blanche sur l'arête haute. Or une ombre `inset` se dessine JUSTE À
       L'INTÉRIEUR de la bordure : les deux se suivaient, et le haut du widget
       montrait 2 px de lumière au lieu d'un trait. D'où l'aspect baveux.
       Une seule bordure, un peu plus franche, donne l'arête de verre nette. */
    border: 1px solid var(--c-glass-line);
    box-shadow: var(--c-shadow-md);
    border-radius: var(--c-radius-xl);
    padding: var(--c-spacer);
    transition: border-color var(--c-transition), background var(--c-transition);
}
/* REFLET D'ARÊTE — le coin haut-droit accroche la lumière.
   Une plaque de verre n'est pas éclairée uniformément : sa tranche s'allume là
   où la source la frappe et s'éteint en s'en éloignant. La bordure unie du
   widget, elle, avait partout la même valeur — d'où un contour dessiné plutôt
   qu'une arête.

   COMMENT. Un anneau de 1 px posé PAR-DESSUS la bordure existante, rempli d'un
   dégradé radial dont le foyer est au coin haut-droit. On ne peut pas peindre
   un dégradé dans une `border` (border-image ne suit pas border-radius) : on
   peint donc un rectangle plein aux dimensions de la boîte, puis on l'ÉVIDE de
   son propre contenu. Les deux couches de masque — l'une calée sur la boîte de
   contenu, l'autre sur la boîte de bordure — se soustraient (`exclude`) et il
   ne reste que le liseré. Les deux boîtes héritent du border-radius, ce qui
   donne un trait d'épaisseur constante jusque dans les angles.

   Le foyer déborde volontairement du widget (à 100 % 0 %, donc pile sur le
   coin) : on n'en voit que le versant descendant, ce qui évite le point chaud
   trop net d'un reflet centré dans le cadre.

   `pointer-events: none` : l'anneau couvre tout le widget, il ne doit pas
   intercepter le clic du widget-lien.
   Un pseudo ABSOLU n'est pas un élément de flex — il ne perturbe pas la
   rangée icône / libellés / chiffre. */
/* LA MÊME LUEUR, PARTOUT OÙ IL Y A UNE BORDURE.
   `.c-lueur` est la classe à poser sur n'importe quelle boîte bordée pour
   qu'elle reçoive ce liseré : les boîtes de l'écran de création s'en servent.
   Une seule écriture de la recette (masque, foyer, épaisseur) — si le reflet
   change un jour, il change partout du même geste. La classe fournit aussi le
   `position: relative` dont le pseudo a besoin : sans lui, il se calerait sur
   un ancêtre positionné quelconque et couvrirait la moitié de l'écran. */
.c-lueur { position: relative; }

/* Le panneau du filtre déroulant (partial partagé) reçoit le même reflet par
   crochet, sans classe ajoutée au balisage commun. Il est déjà positionné
   (absolu, fixe sur téléphone) : pas de `position: relative` à lui donner —
   il casserait son placement. */
.c-widget::before,
.c-lueur::before,
.c-body .app-card::before,
.c-body .app-account-action::before,
.c-body .app-info-cards__card::before,
.c-body .app-list::before,
.c-body .app-filter-dropdown__panel::before {
    content: '';
    position: absolute;
    inset: 0;
    z-index: 1;
    border-radius: inherit;
    padding: 1px;
    background: radial-gradient(120px circle at 100% 0%,
        color-mix(in srgb, #fff 62%, transparent)  0%,
        color-mix(in srgb, #fff 22%, transparent) 34%,
        transparent                               72%);
    -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
    -webkit-mask-composite: xor;
            mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
            mask-composite: exclude;
    pointer-events: none;
    transition: opacity var(--c-transition);
}

/* Widget cliquable : il réagit au survol, contrairement à un widget d'affichage. */
a.c-widget:hover {
    border-color: color-mix(in srgb, var(--c-ink) 28%, transparent);
    background: color-mix(in srgb, var(--c-elev-2) 66%, transparent);
}
/* Au survol la source se rapproche : le reflet gagne en intensité sans changer
   de place. C'est le même geste que sur une vitre qu'on incline. */
a.c-widget:hover::before {
    background: radial-gradient(150px circle at 100% 0%,
        color-mix(in srgb, #fff 88%, transparent)  0%,
        color-mix(in srgb, #fff 34%, transparent) 34%,
        transparent                               72%);
}

/* La pastille garde un fond PLEIN : c'est elle qui ancre le widget. Tout
   rendre translucide donnerait une carte qui flotte sans point d'appui. */
.c-widget__icon {
    width: 38px; height: 38px; border-radius: var(--c-radius-lg);
    display: grid; place-items: center; flex: none;
    background: var(--c-elev-2); color: var(--c-ink-dim); font-size: 1.15rem;
}

/* Libellés : occupent la place restante, tronqués plutôt que de pousser le
   chiffre hors du widget. */
.c-widget__body { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
/* PAS DE CAPITALES — règle générale de l'application. Sans elles, l'étiquette
   n'a plus besoin d'être minuscule pour rester discrète : c'est la couleur
   faible qui la met en retrait, pas la taille. */
.c-widget__label {
    font-size: var(--c-font-size-sm); font-weight: 600;
    color: var(--c-ink-faint);
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.c-widget__hint {
    font-size: var(--c-font-size-sm); color: var(--c-ink-dim);
    line-height: var(--c-line-height-prose);
}

.c-widget__value {
    flex: none;
    font-size: var(--c-font-size-xxl); font-weight: 650;
    line-height: 1; letter-spacing: -.02em;
    font-variant-numeric: tabular-nums;   /* les chiffres restent alignés */
}

/* Accentuation : réservée au widget qui demande une action. */
.c-widget--accent .c-widget__value { color: var(--c-accent); }
.c-widget--accent .c-widget__icon  { background: var(--c-accent-soft); color: var(--c-accent); }
.c-widget--link   .c-widget__icon  { background: var(--c-link-soft);   color: var(--c-link); }

/* ---------- Widget à deux entrées ---------------------------------------- */
/* Un même compteur, deux façons de l'ouvrir. Les pastilles occupent la ligne
   qui, dans les autres widgets, porte l'explication : la rangée garde donc
   exactement la même hauteur, et l'accueil ne perd pas de vitrine.

   Le bloc n'est pas un lien — ce sont ses pastilles. Il ne prend donc AUCUN
   effet de survol d'ensemble : la seule chose qui réagit sous le curseur est
   ce sur quoi on peut cliquer. C'est la règle qui évite le pire des cas, une
   surface qui s'allume mais ne fait rien. */
.c-widget--split .c-widget__body { gap: 5px; }

.c-widget__parts {
    display: flex;
    align-items: center;
    gap: var(--c-spacer-xs);
    flex-wrap: wrap;
}

/* `baseline` et non `center` : le libellé et le nombre n'ont pas le même
   corps ; alignés sur leur milieu, le nombre paraît glisser vers le haut. */
.c-widget__part {
    display: inline-flex; align-items: baseline; gap: 6px;
    padding: 2px 10px 3px;
    border-radius: var(--c-radius-pill);
    border: 1px solid var(--c-line);
    background: color-mix(in srgb, var(--c-elev-2) 45%, transparent);
    background-clip: padding-box;   /* même motif que le widget : liseré net */
    color: var(--c-ink-dim);
    font-size: var(--c-font-size-xs);
    line-height: var(--c-line-height);
    white-space: nowrap;
    transition: background var(--c-transition), border-color var(--c-transition), color var(--c-transition);
}
.c-widget__part:hover {
    background: color-mix(in srgb, var(--c-elev-3) 75%, transparent);
    border-color: color-mix(in srgb, var(--c-ink) 30%, transparent);
    color: var(--c-ink);
}
.c-widget__part-value {
    font-size: var(--c-font-size-sm); font-weight: 700;
    color: var(--c-ink);
    font-variant-numeric: tabular-nums;
}

/* LA CHARTE DES COULEURS PAR ÉLÉMENT (retour client du 14/09). Une carte est
   VERTE, un LINK™ est VIOLET : c'est la règle de toute l'application
   (root.css : Carte → vert, Récompense → violet). Les deux parts du widget la
   suivent — « sans LINK™ » parle d'une carte seule, « avec LINK™ » d'une carte
   munie de son objet. Même construction pour les deux : liseré, fond doux et
   chiffre à la teinte, survol plus franc. */
.c-widget__part--carte {
    border-color: color-mix(in srgb, var(--c-accent) 38%, transparent);
    background: var(--c-accent-soft);
    color: color-mix(in srgb, var(--c-accent) 80%, var(--c-ink));
}
.c-widget__part--carte .c-widget__part-value { color: var(--c-accent); }
.c-widget__part--carte:hover {
    background: color-mix(in srgb, var(--c-accent) 22%, transparent);
    border-color: color-mix(in srgb, var(--c-accent) 60%, transparent);
    color: var(--c-ink);
}

.c-widget__part--link {
    border-color: color-mix(in srgb, var(--c-link) 38%, transparent);
    background: var(--c-link-soft);
    color: color-mix(in srgb, var(--c-link) 80%, var(--c-ink));
}
.c-widget__part--link .c-widget__part-value { color: var(--c-link); }
.c-widget__part--link:hover {
    background: color-mix(in srgb, var(--c-link) 22%, transparent);
    border-color: color-mix(in srgb, var(--c-link) 60%, transparent);
    color: var(--c-ink);
}

/* ---------- Rail de widgets ----------------------------------------------- */
/* Conteneur transparent sur ordinateur. Sur téléphone, il porte l'indice de
   défilement latéral (cf. client/canvas.css, bloc téléphone). */
.c-widgets-rail {
    position: relative;
    min-width: 0;
}

/* Chevron posé au bord droit : il dit que la rangée continue. Le script le
   masque dès que l'on a fait glisser, et quand il n'y a rien à faire glisser.
   Hors du masque de la rangée, sinon il s'effacerait avec elle. */
.c-widgets-rail__hint {
    position: absolute;
    top: 50%;
    right: 0;
    transform: translateY(-50%);
    display: none;                       /* activé sur téléphone seulement */
    align-items: center;
    justify-content: center;
    width: 24px; height: 24px;
    border-radius: var(--c-radius-pill);
    background: var(--c-elev-2);
    color: var(--c-ink-dim);
    font-size: 1rem;
    pointer-events: none;
    transition: opacity var(--c-transition);
}

.c-widgets-rail.is-scrolled .c-widgets-rail__hint { opacity: 0; }

/* Pleine largeur : le widget occupe la rangée entière (fil d'activité,
   graphique…). En flex, cela se traduit par une base de 100 %. */
.c-widget--wide { flex: 1 1 100%; min-width: 0; }

/* Sur téléphone, chaque widget occupe toute la largeur : côte à côte, ils
   seraient illisibles. */
@media (max-width: 768px) {
    .c-widget { flex: 1 1 100%; min-width: 0; }

    /* Le masque de la rangée rend ce flou inerte (cf. plus haut) : on le
       retire pour que l'aspect soit CONSTANT pendant tout le glissement,
       plutôt que de le voir s'allumer brutalement en fin de course. */
    .c-body--fixed .c-widget {
        -webkit-backdrop-filter: none;
        backdrop-filter: none;
    }
}

/* ==========================================================================
   SOCLE DES LISTES — composants transverses
   ==========================================================================
   Portés à l'occasion de « Mes cartes », mais écrits pour tout l'espace client :
   le comptage fait le 11/09 montre que la pagination sert 7 écrans client, les
   cartes d'information 7, le bandeau d'appel 4, les badges une quinzaine. Ils
   vivent donc ici, dans le socle chargé partout, et non dans une feuille d'écran.
   Tous en « une règle, deux crochets » : le préfixe `.c-body` les rend
   incapables d'atteindre l'administration.
   ========================================================================== */

/* ---------- Badges ---------------------------------------------------------
   DÉPLACÉ depuis client/skin-classif.css, où il ne servait qu'à l'écran de
   création alors qu'une quinzaine de vues partagées posent `app-badge`. Mêmes
   règles, à l'identique : l'écran de création charge components.css AVANT
   skin-classif.css, le rendu n'y change donc pas. */
.c-badge,
.c-body .app-badge {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    padding: 2px 9px;
    border-radius: var(--c-radius-pill);
    background: var(--c-neutral-soft);
    color: var(--c-ink);
    font-size: var(--c-font-size-xs);
    font-weight: 600;
    line-height: 1.5;
    white-space: nowrap;
}
.c-body .app-badge--xs { padding: 1px 7px; font-size: 0.7rem; }
.c-body .app-badge--sm { padding: 1px 8px; font-size: var(--c-font-size-xs); }
.c-badge--success, .c-body .app-badge--success { background: var(--c-success-soft); color: var(--c-success); }
.c-badge--warning, .c-body .app-badge--warning { background: var(--c-warning-soft); color: var(--c-warning); }
.c-badge--danger,  .c-body .app-badge--danger  { background: var(--c-danger-soft);  color: var(--c-danger); }
.c-badge--info,    .c-body .app-badge--info    { background: var(--c-info-soft);    color: var(--c-info); }
.c-badge--purple,  .c-body .app-badge--purple  { background: var(--c-link-soft);    color: var(--c-link); }
.c-badge--neutral, .c-body .app-badge--neutral { background: var(--c-neutral-soft); color: var(--c-ink-dim); }
/* « Doux » : c'est déjà ce que sont toutes les variantes en sombre — un fond
   teinté à 14 %. Le nom vient de l'admin, on lui donne le même rendu. */
.c-body .app-badge--soft-danger { background: var(--c-danger-soft); color: var(--c-danger); }
/* « Clair » : en admin, une pastille blanche posée sur un fond coloré (le
   bouton de création, la ligne fantôme « En attente »). En sombre, elle se
   relève d'un cran au lieu de s'éclaircir en blanc. */
.c-body .app-badge--light { background: var(--c-elev-3); color: var(--c-ink-dim); }

/* ---------- Pastille de catégorie d'événement ------------------------------
   (retour client du 15/09) Le badge image de la catégorie, à la hauteur du
   texte, juste avant son nom : tableau et corbeille de « Mes cartes »
   (shared/event-cell), booster de l'événement filtré (shared/event-pack),
   note de la saisie du rang de l'aperçu. Décorative — le nom suit, alt vide.
   Ni fond ni bordure : l'image seule. Dans un `.app-badge` (inline-flex,
   écart de 4 px), elle s'aligne d'elle-même ; `vertical-align` sert en ligne
   de texte. Les attributs width/height réservent la place avant chargement.
   `.c-body` : (0,2,0) contre le `.c-body img { display: block }` du socle
   (client/base.css), qui la mettrait seule sur sa ligne au milieu d'une phrase. */
.c-body .c-cat-pastille {
    display: inline-block;
    flex: none;
    width: 1em;
    height: 1em;
    object-fit: contain;
    vertical-align: -0.125em;
}

/* ---------- Boutons : variantes manquantes ------------------------------- */
.c-body .app-btn--dark { background: var(--c-elev-3); color: var(--c-ink); }
.c-body .app-btn--dark:hover { background: color-mix(in srgb, var(--c-ink) 12%, var(--c-elev-3)); }

/* Le violet PLEIN : le dégradé LINK™, sans l'animation — celle-ci suppose
   l'enveloppe `.c-actionfx`, qu'aucune vue partagée ne pose. `app-btn--purple`
   sert à seize fichiers, dont le panier et le catalogue où c'est un vrai bouton
   d'achat : c'est donc ce rendu-là qui fait foi. Là où un violet plein serait
   de trop (le tuto des emplacements fantômes), l'écran le radoucit lui-même. */
.c-body .app-btn--purple {
    background-color: var(--c-link-strong);
    background-image: linear-gradient(205deg, var(--c-link-from) 0%, var(--c-link-to) 100%);
    color: var(--c-on-link);
    font-weight: 600;
}
.c-body .app-btn--purple:hover { filter: brightness(1.1); }

/* ---------- Bouton d'action rond (Tabler `.btn-action`) --------------------
   Écrit en dur dans les listes partagées (crayon, œil, corbeille) et sur le
   booster d'événement. Le client ne charge ni Tabler ni root.css : sans ce
   crochet, c'est un <button> natif gris clair, carré, avec sa bordure. */
.c-body .btn-action {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 32px;
    height: 32px;
    padding: 0;
    border: 0;
    border-radius: 50%;
    appearance: none;
    background-color: var(--c-elev-2);
    color: var(--c-ink-dim);
    font-size: 1rem;
    line-height: 1;
    text-decoration: none;
    cursor: pointer;
    transition: background-color var(--c-transition), color var(--c-transition);
}
.c-body .btn-action:hover { background-color: var(--c-elev-3); color: var(--c-ink); }

/* (0,3,0) : doit battre `.c-body .text-danger` (client/forms.css), qui n'a que
   (0,2,0) et teindrait sinon toute l'icône sans le fond qui va avec. */
.c-body .btn-action.text-danger { color: var(--c-danger); }
.c-body .btn-action.text-danger:hover { background-color: var(--c-danger-soft); color: var(--c-danger); }
/* Vert : l'action qui REND quelque chose — restaurer une carte de la corbeille. */
.c-body .btn-action.text-success { color: var(--c-success); }
.c-body .btn-action.text-success:hover { background-color: var(--c-success-soft); color: var(--c-success); }

/* Le formulaire qui ne porte qu'un bouton d'action, dans une cellule : il ne
   crée pas de bloc, le bouton s'aligne avec ses voisins (root.css : inline). */
.c-body .app-form-inline { display: inline-flex; margin: 0; }

.c-body .app-btn-action--disabled,
.c-body .app-btn-action--disabled:hover {
    background-color: var(--c-elev-2);
    color: var(--c-ink-dim);
    opacity: .35;
    cursor: not-allowed;
}

/* Le crayon posé SUR le booster d'événement. Le booster appartient au visuel de
   carte — un objet clair, identique partout — et son bouton reste donc clair.
   Fond ET couleur d'icône explicites, et c'est le point important : event-pack
   pose un fond blanc (via le pont de jetons), et l'icône d'un <a> hérite de
   l'encre claire du client — blanc sur blanc, invisible. Écrit APRÈS
   `.btn-action` : même spécificité, c'est l'ordre qui tranche. */
.c-body .app-event-pack__edit { background-color: #FFFFFF; color: #1A1A1A; }
.c-body .app-event-pack__edit:hover { background-color: #EDEDED; color: #1A1A1A; }

/* ---------- Hash monospace --------------------------------------------------
   La pastille d'identifiant d'une carte, d'un LINK™ : dans les listes, dans le
   message des modales de suppression. La couleur dit à quoi l'identifiant
   renvoie — vert la carte, violet le LINK™. */
.c-body .app-hash,
.c-body .app-ball-hash {
    display: inline-block;
    padding: .1rem .4rem;
    border-radius: var(--c-radius-sm);
    background-color: var(--c-neutral-soft);
    color: var(--c-ink-dim);
    font-family: var(--c-font-mono);
    font-size: var(--c-font-size-xs);
    letter-spacing: .03em;
    user-select: all;
}
.c-body .app-hash--card   { background-color: var(--c-accent-soft); color: var(--c-accent); }
.c-body .app-hash--reward { background-color: var(--c-link-soft);   color: var(--c-link); }
.c-body a.app-hash { text-decoration: none; }
.c-body a.app-hash:hover { filter: brightness(1.2); }

/* ---------- Pagination ------------------------------------------------------
   Outil de navigation NEUTRE : aucune couleur de rôle, la page courante se
   distingue par l'inversion encre / fond — le même geste que l'admin, retourné. */
.c-body .app-pagination {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: var(--c-spacer-sm);
    margin-top: var(--c-spacer-md);
    font-size: var(--c-font-size-sm);
}
.c-body .app-pagination__pages { display: flex; align-items: center; gap: var(--c-spacer-xs); }

/* Bordure, fond, police et curseur neutralisés : list-paginate.js réemploie
   ces classes sur des <button>. */
.c-body .app-pagination__link,
.c-body .app-pagination__page {
    display: inline-flex;
    align-items: center;
    gap: .25rem;
    padding: .375rem .75rem;
    border: 0;
    border-radius: var(--c-radius);
    background-color: transparent;
    color: var(--c-ink-dim);
    font: inherit;
    text-decoration: none;
    cursor: pointer;
    transition: background-color var(--c-transition), color var(--c-transition);
}
.c-body .app-pagination__link:hover,
.c-body .app-pagination__page:hover { background-color: var(--c-elev-2); color: var(--c-ink); }

.c-body .app-pagination__page--active,
.c-body .app-pagination__page--active:hover { background-color: var(--c-ink); color: var(--c-bg); font-weight: 600; }

.c-body .app-pagination__link--disabled { opacity: .4; pointer-events: none; }
.c-body .app-pagination__ellipsis { padding: .375rem; color: var(--c-ink-faint); }

.c-body .app-pagination-wrapper {
    position: relative;
    display: flex;
    align-items: center;
    justify-content: center;
    min-height: 36px;
    margin-top: var(--c-spacer-md);
}
.c-body .app-pagination-wrapper .app-pagination { margin-top: 0; }

.c-body .app-pagination-per-page {
    position: absolute;
    right: 0;
    display: flex;
    align-items: center;
    gap: .375rem;
    color: var(--c-ink-dim);
    font-size: var(--c-font-size-sm);
}

/* Le sélecteur « par page » : PAS branché sur `.c-select`, qui prend toute la
   largeur et casserait la ligne « Afficher [x] par page ». Chevron redessiné en
   #98A2A8, la valeur de --c-ink-dim (une donnée d'URL ne lit pas les jetons).
   Propriétés de fond séparées : le raccourci réinitialiserait background-clip. */
.c-body .app-pagination-per-page__select {
    padding: .25rem 1.75rem .25rem .5rem;
    border: 1px solid var(--c-field-border);
    border-radius: var(--c-radius);
    appearance: none;
    background-color: var(--c-field-bg);
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath fill='none' stroke='%2398A2A8' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m4 6 4 4 4-4'/%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-position: right .4rem center;
    background-size: .75rem;
    color: var(--c-ink);
    font-size: var(--c-font-size-sm);
    cursor: pointer;
    transition: border-color var(--c-transition), box-shadow var(--c-transition);
}
.c-body .app-pagination-per-page__select:hover { border-color: var(--c-field-border-hover); }
.c-body .app-pagination-per-page__select:focus-visible {
    outline: none;
    border-color: var(--c-accent);
    box-shadow: 0 0 0 3px var(--c-field-ring);
}

@media (max-width: 768px) {
    .c-body .app-pagination-wrapper { flex-direction: column; gap: var(--c-spacer-sm); }
    .c-body .app-pagination { flex-wrap: wrap; gap: var(--c-spacer-xs); }
    .c-body .app-pagination__pages { flex-wrap: wrap; justify-content: center; }
    .c-body .app-pagination__link,
    .c-body .app-pagination__page { padding: .3rem .5rem; font-size: var(--c-font-size-xs); }
    .c-body .app-pagination-per-page { position: static; }
}

/* ---------- Bandeau d'appel à l'action ------------------------------------
   Un lien pleine largeur qui pousse vers une action (acheter des LINK™, faire
   une demande). En sombre, un fond teinté à 14 % sans bordure se lit mal : on
   lui donne un liseré de sa couleur. Le titre porte la couleur du rôle, la
   phrase reste dans le gris de lecture. */
.c-body .app-cta-banner {
    position: relative;   /* au-dessus de la pile de cartes des boosters, qui remonte */
    z-index: 1;
    display: flex;
    align-items: center;
    gap: var(--c-spacer);
    margin: var(--c-spacer) 0;
    padding: var(--c-spacer);
    border: 1px solid var(--c-line);
    border-radius: var(--c-radius-lg);
    background-color: var(--c-elev-1);
    color: var(--c-ink);
    text-decoration: none;
    transition: transform .15s ease, border-color .15s ease;
}
.c-body .app-cta-banner:hover { transform: translateX(2px); }
.c-body .app-cta-banner > i:first-child { flex-shrink: 0; font-size: 1.6rem; }
.c-body .app-cta-banner__body { display: flex; flex: 1; flex-direction: column; gap: 2px; }
.c-body .app-cta-banner__body strong { font-size: var(--c-font-size-base); font-weight: 600; }
.c-body .app-cta-banner__body span { color: var(--c-ink-dim); font-size: var(--c-font-size-sm); }

.c-body .app-cta-banner--purple  { background-color: var(--c-link-soft);    border-color: color-mix(in srgb, var(--c-link) 30%, transparent);    color: var(--c-link); }
.c-body .app-cta-banner--primary { background-color: var(--c-accent-soft);  border-color: color-mix(in srgb, var(--c-accent) 30%, transparent);  color: var(--c-accent); }
.c-body .app-cta-banner--warning { background-color: var(--c-warning-soft); border-color: color-mix(in srgb, var(--c-warning) 30%, transparent); color: var(--c-warning); }
.c-body .app-cta-banner--info    { background-color: var(--c-info-soft);    border-color: color-mix(in srgb, var(--c-info) 30%, transparent);    color: var(--c-info); }
.c-body .app-cta-banner--neutral { background-color: var(--c-neutral-soft); color: var(--c-ink); }
.c-body .app-cta-banner--purple:hover  { border-color: color-mix(in srgb, var(--c-link) 55%, transparent); }
.c-body .app-cta-banner--primary:hover { border-color: color-mix(in srgb, var(--c-accent) 55%, transparent); }

@media (max-width: 768px) {
    .c-body .app-cta-banner { margin: var(--c-spacer-sm) 0; }
}

/* ---------- Cartes d'information ------------------------------------------
   Les encarts explicatifs posés sous une liste (« Cycle de vie »…). Même
   logique que le bandeau : le fond teinté seul ne tient pas sur le noir, on
   l'appuie d'un liseré. `h4` est remis à zéro ici : client/base.css ne le fait
   que pour h1 à h3. */
.c-body .app-info-cards { display: grid; gap: var(--c-spacer); margin-top: var(--c-spacer-lg); }
.c-body .app-info-cards--cols-1 { grid-template-columns: 1fr; }
.c-body .app-info-cards--cols-2 { grid-template-columns: 1fr 1fr; }
.c-body .app-info-cards--cols-3 { grid-template-columns: repeat(3, 1fr); }

.c-body .app-info-cards__card {
    position: relative;   /* le reflet de coin s'y accroche */
    padding: var(--c-spacer);
    border: 1px solid var(--c-line-soft);
    border-radius: var(--c-radius-lg);
    background-color: var(--c-elev-1);
}
/* L'ORANGE NE FAIT PAS DE VOILE (23/09, règle du projet : posé à faible opacité
   sur le sombre, il vire au marron). La note « certifié » garde donc la surface
   neutre des autres, et c'est sa pastille qui porte la couleur. */
.c-body .app-info-cards__card--warning { background-color: var(--c-elev-1); border-color: var(--c-line-soft); }
.c-body .app-info-cards__card--info    { background-color: var(--c-info-soft);    border-color: color-mix(in srgb, var(--c-info) 24%, transparent); }
.c-body .app-info-cards__card--success,
.c-body .app-info-cards__card--primary { background-color: var(--c-accent-soft);  border-color: color-mix(in srgb, var(--c-accent) 24%, transparent); }
.c-body .app-info-cards__card--danger  { background-color: var(--c-danger-soft);  border-color: color-mix(in srgb, var(--c-danger) 24%, transparent); }
.c-body .app-info-cards__card--purple  { background-color: var(--c-link-soft);    border-color: color-mix(in srgb, var(--c-link) 24%, transparent); }

.c-body .app-info-cards__header {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--c-spacer-sm);
    margin-bottom: var(--c-spacer-sm);
}
.c-body .app-info-cards__header h4 {
    margin: 0;
    color: var(--c-ink);
    font-size: var(--c-font-size-base);
    font-weight: 600;
    line-height: var(--c-line-height);
}
.c-body .app-info-cards__card p {
    margin: 0;
    color: var(--c-ink-dim);
    font-size: var(--c-font-size-sm);
    line-height: var(--c-line-height-prose);
}
.c-body .app-info-cards__card p strong { color: var(--c-ink); font-weight: 600; }
.c-body .app-info-cards__cta { margin-top: var(--c-spacer-sm); }

@media (max-width: 768px) {
    .c-body .app-info-cards--cols-2,
    .c-body .app-info-cards--cols-3 { grid-template-columns: 1fr; }
}

/* ---------- État vide -------------------------------------------------------
   Le message « aucune … trouvée » (partial shared/empty-state), rendu par
   quatorze écrans client. Deux variantes : dans une liste (`.app-list__empty`)
   ou en bloc autonome sur une page sans tableau (`.app-empty-state.app-card`). */
.c-body .app-list__empty,
.c-body .app-empty-state {
    padding: var(--c-spacer-xl) var(--c-spacer);
    color: var(--c-ink-faint);
    text-align: center;
}
.c-body .app-list__empty i   { display: block; margin-bottom: var(--c-spacer-xs); font-size: 2rem; }
.c-body .app-empty-state i   { display: block; margin-bottom: var(--c-spacer-sm); font-size: 3rem; }
.c-body .app-list__empty p,
.c-body .app-empty-state p   { margin: 0 0 var(--c-spacer-xs); color: var(--c-ink-dim); }
.c-body .app-list__empty .app-text-muted,
.c-body .app-empty-state .app-text-muted { margin-top: 0; }
.c-body .app-empty-state.app-card {
    border: 1px solid var(--c-line);
    border-radius: var(--c-radius-lg);
    background-color: var(--c-elev-1);
}

/* ---------- Le prix empilé, HT au-dessus de TTC ---------------------------
   La boîte du panier de la barre du haut le montre sur TOUTES les pages : hors
   de la boutique et des demandes, faute de règle, on lisait « 2,00 € HT2,40 € TTC »
   (audit du 15/10). Les écrans qui le montrent en grand gardent leurs tailles
   (client/boutique.css, client/demandes.css). */
.c-body .app-price-stack {
    display: inline-flex;
    flex-direction: column;
    align-items: flex-start;
    gap: .05rem;
    line-height: 1.15;
}
.c-body .app-price-stack__ht { color: var(--c-ink); font-size: 1rem; font-variant-numeric: tabular-nums; white-space: nowrap; }
.c-body .app-price-stack__ttc { color: var(--c-ink-dim); font-size: .75rem; white-space: nowrap; }

/* Les paragraphes d'introduction d'une section (Couleurs, Facturation, fenêtres
   de demande) et l'aide d'un envoi d'image ne collent plus au champ qui suit
   (audit du 15/10 : 0 px d'écart). */
.c-body .app-text-description:not(:last-child),
.c-body .app-upload-hint:not(:last-child) { margin-bottom: var(--c-spacer); }
.c-body .app-upload-hint { display: block; }

/* Un numéro (facture, commande) ne se coupe pas en fin de ligne. */
.c-body .c-nowrap { white-space: nowrap; }

/* ---------- Une rangée qui défile le dit ----------------------------------
   Onglets de statut, liste des albums : la rangée défile de côté et la barre est
   masquée. client/ui.js pose `is-defile-gauche` / `is-defile-droite` quand il
   reste du contenu de ce côté ; le bord s'estompe alors (audit du 15/10). */
.c-body .is-defile-droite { -webkit-mask-image: linear-gradient(to right, #000 calc(100% - 48px), transparent); mask-image: linear-gradient(to right, #000 calc(100% - 48px), transparent); }
.c-body .is-defile-gauche { -webkit-mask-image: linear-gradient(to left, #000 calc(100% - 48px), transparent); mask-image: linear-gradient(to left, #000 calc(100% - 48px), transparent); }
.c-body .is-defile-gauche.is-defile-droite { -webkit-mask-image: linear-gradient(to right, transparent, #000 48px, #000 calc(100% - 48px), transparent); mask-image: linear-gradient(to right, transparent, #000 48px, #000 calc(100% - 48px), transparent); }

/* ---------- Au doigt, 40 px au moins (audit du 15/10) ----------------------
   Sur téléphone et tablette, les petits boutons (« Vider le panier », actions de
   l'aperçu), les boutons d'action ronds et le choix « par page » passaient sous
   la cible tactile de 40 px. */
@media (max-width: 768px) {
    .c-body .c-btn--sm { min-height: 40px; }
    .c-body .btn-action { width: 40px; height: 40px; }
    .c-body .app-pagination-per-page__select { min-height: 40px; }
}

/* Les métadonnées d'un fichier envoyé (format, dimensions, poids) : même rendu sur
   « Mon club » et sur « Profil » (audit du 15/10 : 13 px d'un côté, 16 de l'autre). */
.c-body .app-file-meta { color: var(--c-ink-dim); font-size: .8125rem; line-height: 1.6; }
.c-body .app-file-meta strong { color: var(--c-ink); }
