/* ==========================================================
   root.css — Album Sport
   Variables CSS, Reset, Debug
   ========================================================== */

:root {
    /* -- Couleurs -- */
    --app-primary: #00BE3D;
    --app-primary-hover: #009E33;
    --app-primary-light: #E3FAEB;
    --app-secondary: #0046FF;
    --app-secondary-hover: #0036CC;
    --app-secondary-light: #E8EEFF;

    --app-success: #00BE3D;
    --app-success-light: #E3FAEB;
    --app-warning: #F77F00;
    --app-warning-light: #FFF3E0;
    --app-danger: #D62828;
    --app-danger-light: #FDEAEA;
    --app-info: #0046FF;
    --app-info-light: #E8EEFF;

    /* Couleur sémantique « Récompense ».
       Mapping sémantique global de l'app :
         Carte      → vert    (--app-success / --app-primary)
         Événement  → orange  (--app-warning)
         Récompense → violet  (--app-purple) */
    /* ENCRES DE TEXTE (audit du 15/10, WCAG AA 4,5:1). Le vert et l'orange de marque
       restent pour les aplats, les icônes et les filets ; pour du TEXTE sur fond clair
       (blanc, --app-*-light), ces deux encres. Sur un aplat vert ou orange (boutons,
       pastilles), l'encre reste BLANCHE : choix de Julien le 16/10, l'encre sombre posée
       au lot 2 de l'audit a été retirée (« le contraste est mauvais »). */
    --app-success-text: #007F29;
    --app-warning-text: #B45309;
    --app-danger-text: #B71C1C;
    /* Gris de texte secondaire : #767676 tombe sous 4,5:1 sur les fonds teintés de l'admin
       (#F5F5F5, #F8F9FA, --app-*-light). --app-gray-500 reste tel quel : le rendu des cartes s'en sert. */
    --app-gray-text: #616161;
    --app-purple: #7C3AED;
    --app-purple-hover: #6D28D9;
    --app-purple-light: #EDE9FE;
    /* Bornes du gradient « achat récompense » — du purple-300 au purple-800. */
    --app-purple-gradient-from: #A78BFA;
    --app-purple-gradient-to:   #5B21B6;

    /* -- Bordures teintées « light » (GUIDELINE) --
       Une boîte de couleur (fond --*-light) posée sur un fond GRIS reçoit une
       border discrète de sa propre teinte (~18 % d'opacité, cf. .app-link-body).
       Sur fond BLANC, on ne met PAS de border. */
    --app-primary-border:   color-mix(in srgb, var(--app-primary) 18%, transparent);
    --app-secondary-border: color-mix(in srgb, var(--app-secondary) 18%, transparent);
    --app-success-border:   color-mix(in srgb, var(--app-success) 18%, transparent);
    --app-warning-border:   color-mix(in srgb, var(--app-warning) 18%, transparent);
    --app-danger-border:    color-mix(in srgb, var(--app-danger) 18%, transparent);
    --app-info-border:      color-mix(in srgb, var(--app-info) 18%, transparent);
    --app-purple-border:    color-mix(in srgb, var(--app-purple) 18%, transparent);

    --app-white: #FFFFFF;
    --app-black: #000000;
    --app-light: #F8F9FA;
    --app-gray-100: #F5F5F5;
    --app-gray-200: #E0E0E0;
    --app-gray-300: #BDBDBD;
    --app-gray-400: #9E9E9E;
    --app-gray-500: #767676;
    --app-gray-600: #555555;
    --app-gray-700: #333333;
    --app-dark: #1A1A1A;
    /* Couleurs « magiques » des cartes (tokenisées — valeurs inchangées). */
    --app-card-text-dark: #1A1A2E; /* texte sombre par défaut sur les cartes (fallback de --card-text-color) */
    --app-background-dark: #2A2A28; /* fond sombre (vue ballon publique) */

    /* -- Typographie -- */
    --app-font-family: 'neulis-neue', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
    --app-font-size-xs: 0.75rem;
    --app-font-size-sm: 0.875rem;
    --app-font-size-base: 1rem;
    --app-font-size-md: 1.125rem;
    --app-font-size-lg: 1.25rem;
    --app-font-size-xl: 1.5rem;
    --app-font-size-xxl: 2rem;
    --app-font-size-xxxl: 2.5rem;
    --app-line-height: 1.2;

    /* -- Espacement -- */
    --app-spacer: 1rem;
    --app-spacer-xs: 0.25rem;
    --app-spacer-sm: 0.5rem;
    --app-spacer-md: 1.5rem;
    --app-spacer-lg: 2rem;
    --app-spacer-xl: 3rem;
    --app-spacer-xxl: 4rem;

    /* -- Layout -- */
    --app-container: 1200px;
    --app-sidebar-width: 300px;
    --app-header-height: 64px;
    /* Barre de titre de page (.app-app-page-header) : sticky sous le header global.
       Sert d'offset aux éléments sticky placés dessous (ex : menu settings). */
    --app-page-header-height: 72px;
    --app-radius: 8px;
    --app-radius-sm: 4px;
    --app-radius-lg: 12px;
    --app-radius-xl: 16px;
    /* Radius « pill » (full rounded) — utilisé pour tous les boutons. */
    --app-radius-pill: 9999px;

    /* -- Ombres -- */
    --app-shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.05);
    --app-shadow: 0 1px 3px rgba(0, 0, 0, 0.1), 0 1px 2px rgba(0, 0, 0, 0.06);
    --app-shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
    --app-shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);

    /* -- Transitions -- */
    --app-transition: 200ms ease;
    --app-transition-slow: 400ms ease;

    /* -- Surcharge Tabler — harmonisation avec le design system PB -- */
    --tblr-font-sans-serif: 'neulis-neue', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
    --tblr-body-font-family: var(--tblr-font-sans-serif);
    --tblr-border-radius: var(--app-radius);
    --tblr-border-radius-sm: var(--app-radius-sm);
    --tblr-border-radius-lg: var(--app-radius-lg);
    --tblr-border-radius-xl: var(--app-radius-xl);
    --tblr-border-color: var(--app-gray-200);
    /* Force la primaire Tabler en vert PB (sinon Tabler par défaut = bleu #066fd1) */
    --tblr-primary: #00BE3D;
    --tblr-primary-rgb: 0, 190, 61;
    --tblr-primary-fg: #1A1A1A;
    --tblr-link-color: #00BE3D;
    --tblr-link-color-rgb: 0, 190, 61;
    --tblr-link-hover-color: #009E33;
    --tblr-link-hover-color-rgb: 0, 158, 51;

    /* -- Typographie mono -- */
    --app-font-mono: 'SF Mono', 'Fira Code', 'Fira Mono', 'Roboto Mono', monospace;

    /* -- Gris étendus -- */
    --app-gray-50: #FAFAFA;
    --app-gray-800: #212121;

    /* -- Primary RGB (pour rgba()) -- */
    --app-primary-rgb: 0, 190, 61;

    /* -- Hover boutons -- */
    --app-danger-hover: #B71C1C;
    --app-warning-hover: #D96E00;
    --app-success-hover: #009E33;

    /* -- Alert colors -- */
    --app-alert-warning-bg: #fff8e1;
    --app-alert-warning-border: #ffe082;
    --app-alert-warning-text: #8d6e00;
    --app-alert-danger-bg: #fef2f2;
    --app-alert-danger-border: #fecaca;
    --app-alert-danger-text: #991b1b;

    /* -- Accent -- */
    --app-accent: #7b1fa2;
    --app-accent-light: #f3e5f5;

    /* -- Header banner (variante info — palette beige chaud, distincte des alertes) -- */
    --app-banner-info-bg: #FFF3CD;
    --app-banner-info-border: #FFECB5;
    --app-banner-info-text: #664D03;
    --app-banner-info-text-hover: #503C02;
}

/* -- Reset -- */
*, *::before, *::after {
    box-sizing: border-box;
    margin: 0;
    padding: 0;
}

html {
    font-size: 16px;
    -webkit-text-size-adjust: 100%;
    scroll-behavior: smooth;
}

body {
    font-family: var(--app-font-family);
    font-size: var(--app-font-size-base);
    line-height: var(--app-line-height);
    color: var(--app-dark);
    background: var(--app-white);
    margin: 0;
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
}

/* -- Avatar : empêcher la déformation -- */
.avatar {
    flex-shrink: 0;
    aspect-ratio: 1;
}

/* Liens par défaut — noir sans underline. Les classes spécifiques
   (.app-btn--*, .app-list__club-link, .app-form-link, …) peuvent surcharger
   ce défaut pour les besoins métier. */
a {
    color: var(--app-dark);
    text-decoration: none;
    transition: color var(--app-transition);
}

/* Override explicite de Tabler qui pose `text-decoration: underline` sur a:hover. */
a:hover {
    color: var(--app-dark);
    text-decoration: none;
}

img {
    max-width: 100%;
    height: auto;
    display: block;
}

button, input, select, textarea {
    font-family: inherit;
    font-size: inherit;
}

button {
    cursor: pointer;
    border: none;
    background: none;
}

ul, ol {
    list-style: none;
}

h1, h2, h3, h4, h5, h6 {
    line-height: 1.3;
    font-weight: 600;
}

/* -- Utilitaires -- */
.app-container {
    max-width: var(--app-container);
    margin: 0 auto;
    padding: 0 var(--app-spacer);
}

.app-sr-only {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
}

.app-text-center { text-align: center; }
.app-text-right { text-align: right; }
.app-text-muted { color: var(--app-gray-text); }
.app-hidden { display: none !important; }
.app-text-light { color: var(--app-gray-text); }
.app-text-dark { color: var(--app-dark); }
.app-text-success { color: var(--app-success-text); }
.app-text-danger { color: var(--app-danger-text); }
.app-text-warning { color: var(--app-warning-text); }
.app-text-xs { font-size: var(--app-font-size-xs); }
.app-text-sm { font-size: var(--app-font-size-sm); }
.app-text-bold { font-weight: 600; }
.app-d-block { display: block; }

.app-mt-xs { margin-top: var(--app-spacer-xs); }
.app-mt-sm { margin-top: var(--app-spacer-sm); }
.app-mt { margin-top: var(--app-spacer); }
.app-mt-md { margin-top: var(--app-spacer-md); }
.app-mt-lg { margin-top: var(--app-spacer-lg); }
.app-mb-0 { margin-bottom: 0; }
.app-mb-xs { margin-bottom: var(--app-spacer-xs); }
.app-mb-sm { margin-bottom: var(--app-spacer-sm); }
.app-mb { margin-bottom: var(--app-spacer); }
.app-mb-md { margin-bottom: var(--app-spacer-md); }
.app-mb-lg { margin-bottom: var(--app-spacer-lg); }
.app-ml-auto { margin-left: auto; }
.app-ml-xs { margin-left: 0.25rem; }
.app-ml-sm { margin-left: 0.5rem; }

/* -- Field box : generic grey container -- */
.app-field-box {
    background: var(--app-gray-50);
    border: 1px solid var(--app-gray-200);
    border-radius: var(--app-radius);
    padding: var(--app-spacer);
}

/* -- Separator -- */
.app-separator {
    border: none;
    border-top: 1px solid var(--app-gray-100);
    opacity: 1;
    margin: var(--app-spacer-md) 0;
}

/* -- Background pattern -- */
.app-bg-pattern {
    background-image: url('../img/img-background-pattern-1.svg');
    background-repeat: repeat;
    background-size: 400px;
    background-position: center;
}

/* -- Media object (image + body side by side) -- */
.app-media { display: flex; gap: var(--app-spacer); align-items: flex-start; }
.app-media--center { align-items: center; }
/* `min-width: 0` : sans lui, un corps de flex refuse de passer sous la largeur
   de son contenu et se fait pousser hors de la boîte par une vignette large. */
.app-media__body { flex: 1; min-width: 0; }

/* VIGNETTE PAR DÉFAUT de shared/media-file-preview — et elle vit ICI, dans la
   feuille GLOBALE, à côté du composant qu'elle habille.
   Le défaut du partial était `app-album-cover-thumb`, une classe définie dans
   albums.css : sur toute page qui ne charge pas cette feuille, l'image sortait
   à sa taille NATURELLE (900 px de large pour une photo redimensionnée), la
   rangée flex explosait et le bloc « Télécharger / Supprimer » se retrouvait
   poussé hors du cadre. Aucune erreur — juste une mise en page qui part.
   Format paysage 3/2 : c'est celui d'une photo. Les vignettes carrées ou
   portrait restent nommées explicitement par l'appelant. */
.app-media-thumb {
    width: 160px;
    aspect-ratio: 3 / 2;
    object-fit: cover;
    border-radius: var(--app-radius);
    border: 1px solid var(--app-gray-200);
    background: var(--app-gray-100);
    flex: none;
}

/* -- Owner card : bloc d'identité réutilisable (utilisateur + club + sport +
      date de création), posé en tête des écrans d'édition. Style par défaut,
      à affiner ; partagé → un seul endroit à ajuster. -- */
.app-owner-card {
    position: relative;
    overflow: hidden; /* rogne le ruban en coin */
    background: var(--app-white);
    border: 1px solid var(--app-gray-200);
    border-radius: var(--app-radius-lg); /* aligné sur .app-card */
    padding: var(--app-spacer);
    margin-bottom: var(--app-spacer-md);
}
/* Ruban « CLUB » en coin haut-droit, à 45°, gris. */
.app-owner-card__ribbon {
    position: absolute;
    top: 13px;
    right: -32px;
    transform: rotate(45deg);
    background: var(--app-gray-400);
    color: var(--app-white);
    font-size: 0.6rem;
    font-weight: 700;
    letter-spacing: 0.08em;
    padding: 2px 36px;
    text-align: center;
    pointer-events: none;
}
.app-owner-card__main {
    display: flex;
    align-items: flex-start;
    gap: var(--app-spacer);
    min-width: 0;
}
.app-owner-card__avatar {
    flex-shrink: 0;
    width: 48px;
    height: 48px;
    display: flex;
    align-items: center;
    justify-content: center;
    color: var(--app-gray-text);
    font-size: 1.6rem;
}
/* Logo affiché EN ENTIER (pas de cadre/zone, pas de recadrage). */
.app-owner-card__avatar img { max-width: 100%; max-height: 100%; object-fit: contain; }
.app-owner-card__info {
    display: flex;
    flex-direction: column;
    gap: 0.15rem;
    min-width: 0;
}
.app-owner-card__name { font-weight: 600; color: var(--app-dark); }
.app-owner-card__email {
    display: flex;
    align-items: center;
    gap: 0.3rem;
    font-size: var(--app-font-size-sm);
    color: var(--app-gray-600);
}
.app-owner-card__club {
    display: flex;
    align-items: center;
    gap: 0.25rem;
    font-size: var(--app-font-size-sm);
    color: var(--app-gray-700);
}
.app-owner-card__club-name { font-weight: 600; color: var(--app-dark); }

/* -- File / upload info patterns -- */
.app-file-meta { font-size: 0.8125rem; color: var(--app-gray-600); line-height: 1.6; }
.app-file-actions { display: flex; gap: var(--app-spacer-sm); margin-top: var(--app-spacer-sm); }
/* Mobile : boutons empilés (les uns sous les autres), pleine largeur. */
@media (max-width: 768px) {
    .app-file-actions { flex-direction: column; }
    .app-file-actions > * { width: 100%; }
}
/* Reprend le style « post-it » de .form-hint (gris clair, radius, chevron)
   pour cohérence visuelle des hints d'upload. */
.app-upload-hint {
    position: relative;
    display: block;
    width: 100%;
    background: var(--app-gray-100);
    color: var(--app-gray-700);
    padding: calc(var(--app-spacer) / 2);
    font-size: var(--app-font-size-xs);
    line-height: 1.4;
    border-radius: var(--app-radius);
    margin-top: calc(var(--app-spacer) / 2);
    margin-bottom: var(--app-spacer-sm);
}

.app-upload-hint::before {
    content: '';
    position: absolute;
    top: -6px;
    left: 12px;
    width: 0;
    height: 0;
    border-left: 6px solid transparent;
    border-right: 6px solid transparent;
    border-bottom: 6px solid var(--app-gray-100);
}

/* -- Feedback box (success / warning / error states) -- */
.app-feedback-box { text-align: center; padding: var(--app-spacer); }
.app-feedback-box__icon { font-size: 2.5rem; display: block; margin-bottom: var(--app-spacer-sm); }
.app-feedback-box__title { font-weight: 600; color: var(--app-dark); margin-bottom: var(--app-spacer-xs); }

/* -- Inline code display (API tokens, hashes) -- */
.app-code-inline {
    padding: 0.4rem 0.75rem;
    background: var(--app-gray-100);
    border-radius: var(--app-radius);
    font-size: var(--app-font-size-sm);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* -- Inline form row (align items in a row) -- */
.app-inline-form { display: flex; gap: var(--app-spacer-sm); align-items: center; margin-top: var(--app-spacer-sm); }

/* -- Description paragraph (common pattern for form descriptions) -- */
.app-text-description { color: var(--app-gray-text); font-size: var(--app-font-size-sm); margin-bottom: var(--app-spacer); }

/* -- Sport icon thumbnail (admin form) — image en object-fit:contain pour
   garder les marges autour d'un logo / icône. */
.app-sport-icon-thumb {
    width: 64px;
    height: 64px;
    object-fit: contain;
    border-radius: var(--app-radius-sm);
    background: var(--app-gray-100);
    padding: 4px;
}

/* -- Sport back-image thumbnail (admin form) — pattern standard app-media :
   bordure grise, fond gris, radius. Aspect 2/3 (portrait carte) pour
   représenter fidèlement le rendu sur la carte. Calqué sur .app-og-thumb. */
.app-sport-back-image-thumb {
    width: 120px;
    height: auto;
    aspect-ratio: 2 / 3;
    object-fit: cover;
    border-radius: var(--app-radius);
    border: 1px solid var(--app-gray-200);
    background: var(--app-gray-100);
    flex-shrink: 0;
    display: block;
}

/* ────────────────────────────────────────────────────────────
   .app-list-thumb — Pattern générique pour mini-aperçus dans
   les listings (tableaux app-list).
   ────────────────────────────────────────────────────────────
   Carré 36×36 avec bordure grise subtile. S'utilise au choix :

   1. Directement sur un <img> (preview d'une image attachée)
      <img class="app-list-thumb" src="...">

   2. Comme wrapper d'une icône (ri-*) ou placeholder
      <span class="app-list-thumb"><i class="ri-play-fill"></i></span>

   3. État vide / placeholder (gris atténué)
      <span class="app-list-thumb app-list-thumb--empty"><i class="ri-image-line"></i></span>

   4. Variante `cover` (pour les images full-bleed comme un fond de carte)
      <img class="app-list-thumb app-list-thumb--cover">

   5. Variante `accent` (fond vert light + couleur primaire — pour mettre
      en valeur la présence d'une ressource, ex: vidéo dispo)
      <span class="app-list-thumb app-list-thumb--accent"><i class="ri-play-fill"></i></span>
   ──────────────────────────────────────────────────────────── */
.app-list-thumb {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 36px;
    height: 36px;
    border-radius: var(--app-radius-sm);
    border: 1px solid var(--app-gray-200);
    background: var(--app-gray-100);
    overflow: hidden;
    flex-shrink: 0;
    object-fit: contain;
    font-size: 1rem;
}

/* Quand utilisé en wrapper, l'image enfant remplit le carré */
.app-list-thumb img {
    width: 100%;
    height: 100%;
    object-fit: contain;
    display: block;
}

/* État vide : placeholder gris avec icône au centre */
.app-list-thumb--empty {
    color: var(--app-gray-text);
}

/* Variante cover (image en plein cadre, croppée) */
.app-list-thumb--cover,
.app-list-thumb--cover img {
    object-fit: cover;
}

/* Variante accent (fond vert light, couleur primaire) — pour indiquer
   la présence d'une ressource sans afficher de preview directe (ex : vidéo).
   Pas de bordure pour rester cohérent avec le pattern `.app-cart-product__icon`. */
.app-list-thumb--purple {
    background: var(--app-purple-light);
    color: var(--app-purple);
}

.app-list-thumb--accent {
    background: var(--app-primary-light);
    color: var(--app-success-text);
    border-color: transparent;
}

/* Variante violette — sémantique « Récompense ». */
.app-list-thumb--purple {
    background: var(--app-purple-light);
    color: var(--app-purple);
    border-color: transparent;
}

/* -- Video preview -- */
.app-video-preview {
    width: 100%;
    max-height: 400px;
    border-radius: var(--app-radius-sm);
    background: var(--app-black);
}

/* -- Toast container -- */
#app-toasts { z-index: 9999; }

/* -- Card body flush (no padding, for tables) -- */
.app-card__body--flush { padding: 0; }

/* -- Form select narrow -- */
.app-form-select--narrow { max-width: 250px; }

/* -- Boutons de base -- */
.app-btn {
    display: inline-flex;
    align-items: center;
    gap: var(--app-spacer-sm);
    padding: 0.5rem 1.25rem;
    border-radius: var(--app-radius-pill);
    font-size: var(--app-font-size-sm);
    font-weight: 500;
    line-height: 1.5;
    text-align: center;
    transition: all 300ms cubic-bezier(0.4, 0, 0.2, 1);
    border: 1px solid transparent;
    cursor: pointer;
    text-decoration: none;
}

.app-btn i { font-size: 1.1em; }

.app-btn--primary {
    background: var(--app-primary);
    color: var(--app-white);
}
.app-btn--primary:hover {
    background: var(--app-primary-hover);
    color: var(--app-white);
}

.app-btn--secondary {
    background: var(--app-secondary);
    color: var(--app-white);
}
.app-btn--secondary:hover {
    background: var(--app-secondary-hover);
    color: var(--app-white);
}

.app-btn--danger {
    background: var(--app-danger);
    color: var(--app-white);
}
.app-btn--danger:hover {
    background: var(--app-danger-hover);
    color: var(--app-white);
}
.app-btn--danger-outline {
    color: var(--app-danger-text);
    border-color: var(--app-danger);
}
.app-btn--danger-outline:hover {
    background: var(--app-danger);
    color: var(--app-white);
}

.app-btn--warning {
    background: var(--app-warning);
    color: var(--app-white);
}
.app-btn--warning:hover {
    background: var(--app-warning-hover);
    color: var(--app-white);
}

/* Bouton « Carte » — couleur sémantique vert (aplat plein). */
.app-btn--success {
    background: var(--app-success);
    color: var(--app-white);
}
.app-btn--success:hover {
    background: var(--app-success-hover);
    color: var(--app-white);
}

/* Bouton « Récompense » — gradient violet pour les CTAs d'achat. */
.app-btn--purple {
    background: linear-gradient(
        205deg,
        var(--app-purple-gradient-from) 0%,
        var(--app-purple-gradient-to) 100%
    );
    color: var(--app-white);
    border-color: transparent;
}
.app-btn--purple:hover {
    background: linear-gradient(
        205deg,
        var(--app-purple) 0%,
        var(--app-purple-gradient-to) 100%
    );
    color: var(--app-white);
}
.app-btn--purple-light {
    background: var(--app-purple-light);
    color: var(--app-purple);
    border-color: transparent;
    font-weight: 600;
}
.app-btn--purple-light:hover {
    background: var(--app-purple);
    color: var(--app-white);
}

/* Animation « pulse » subtile — pour CTAs principaux (ex. créer vos cartes).
   Désactivée si `prefers-reduced-motion`. */
@keyframes app-pulse {
    0% {
        box-shadow: 0 0 0 0 rgba(0, 190, 61, 0.45);
    }
    100% {
        box-shadow: 0 0 0 6px rgba(0, 190, 61, 0);
    }
}

.app-btn--pulse {
    animation: app-pulse 2s ease-out infinite;
}

@media (prefers-reduced-motion: reduce) {
    .app-btn--pulse {
        animation: none;
    }
}

/* Variante "light green" : fond vert très clair, texte vert primaire — utilisé pour
   les actions secondaires/contextuelles dans la zone haute (ex : Encodage NFC). */
.app-btn--primary-light {
    background: var(--app-primary-light);
    color: var(--app-success-text);
    border-color: transparent;
    font-weight: 600;
}
.app-btn--primary-light:hover {
    background: var(--app-primary);
    color: var(--app-white);
}

/* Variante "light warning" : fond orange clair, texte orange — pour CTAs
   événement secondaires. */
.app-btn--warning-light {
    background: var(--app-warning-light);
    color: var(--app-warning-text);
    border-color: transparent;
    font-weight: 600;
}
.app-btn--warning-light:hover {
    background: var(--app-warning);
    color: var(--app-white);
}

.app-btn--outline {
    background: transparent;
    color: var(--app-gray-700);
    border-color: var(--app-gray-200);
}
.app-btn--outline:hover {
    background: var(--app-gray-100);
    color: var(--app-dark);
}

/* Bouton « blanc » — aplat blanc plein, AUCUNE bordure (border = couleur du fond). */
.app-btn--white {
    background: var(--app-white);
    color: var(--app-gray-700);
    border-color: var(--app-white);
}
.app-btn--white:hover {
    background: var(--app-gray-100);
    color: var(--app-dark);
    border-color: var(--app-gray-100);
}

/* Bouton « gris » — aplat gris neutre plein, sans bordure. */
.app-btn--gray {
    background: var(--app-gray-100);
    color: var(--app-gray-700);
    border-color: var(--app-gray-100);
}
.app-btn--gray:hover {
    background: var(--app-gray-200);
    color: var(--app-dark);
    border-color: var(--app-gray-200);
}

/* Bouton « dark » — CTA neutre plein.
   Utilisé sur les zones SANS couleur sémantique attribuée (clubs, sports,
   utilisateurs, rangs, sous-comptes…) à la place de `--primary`
   qui est réservé au vert sémantique « Carte ». */
.app-btn--dark {
    background: var(--app-dark);
    color: var(--app-white);
    border-color: var(--app-dark);
}
.app-btn--dark:hover {
    background: var(--app-gray-700);
    color: var(--app-white);
    border-color: var(--app-gray-700);
}

/* Badges (count) à l'intérieur d'un bouton outline : fond blanc neutre. */
.app-btn--outline .app-badge {
    background: var(--app-white);
    color: var(--app-gray-700);
    border: 1px solid var(--app-gray-200);
}

.app-btn:disabled,
.app-btn[disabled] {
    opacity: 0.45;
    cursor: not-allowed;
    pointer-events: none;
}

.app-btn--sm {
    padding: 0.25rem 0.75rem;
    font-size: var(--app-font-size-xs);
}

.app-btn--lg {
    padding: 0.75rem 1.75rem;
    font-size: var(--app-font-size-base);
}

.app-btn--icon {
    padding: 0.5rem;
    line-height: 1;
}

.app-btn--icon.app-btn--sm {
    padding: 0.25rem;
}

.app-btn--icon.app-btn--xs {
    padding: 0.125rem;
    font-size: var(--app-font-size-xs);
    margin-left: var(--app-spacer-xs);
}

/* -- Action buttons (Tabler .btn-action override) -- */
.btn-action {
    width: 32px;
    height: 32px;
    padding: 0;
    border-radius: 50%;
    background: var(--app-gray-100);
    border: none;
    box-shadow: none !important;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    color: var(--app-gray-600);
    transition: background var(--app-transition), color var(--app-transition);
    text-decoration: none;
}

.btn-action:hover {
    background: var(--app-gray-200);
    color: var(--app-dark);
}

/* -- Color field (preview + native picker + text input) -- */
.app-color-field {
    display: flex;
    align-items: center;
    gap: var(--app-spacer-xs);
    flex-shrink: 0;
}

.app-color-field__native {
    position: absolute;
    width: 0;
    height: 0;
    padding: 0;
    border: 0;
    opacity: 0;
    pointer-events: none;
}

.app-color-field__preview {
    width: 38px;
    height: 38px;
    border-radius: var(--app-radius);
    flex-shrink: 0;
    border: 1px solid rgba(0, 0, 0, 0.12);
    cursor: pointer;
}

.app-color-field__input {
    flex: 1 1 auto;
    min-width: 70px;
    font-family: var(--app-font-mono);
    font-size: 0.8125rem;
    letter-spacing: 0.02em;
}

.app-btn--block {
    display: flex;
    width: 100%;
    justify-content: center;
}

/* -- Formulaires de base -- */
.app-form-group {
    margin-bottom: var(--app-spacer);
}

/* Liens à l'intérieur d'un form (label, hint, erreur, footer auth…) :
   neutralisés en dark + souligné gris.
   Aussi exposé via la classe explicite `.app-form-link`. */
.app-form-group a,
.app-form-error a,
.app-form-label a,
.app-form-link {
    color: var(--app-dark);
    text-decoration: underline;
    text-decoration-color: var(--app-gray-300);
    text-decoration-thickness: 1px;
    text-underline-offset: 3px;
    transition: text-decoration-color var(--app-transition);
}

.app-form-group a:hover,
.app-form-error a:hover,
.app-form-label a:hover,
.app-form-link:hover {
    color: var(--app-dark);
    text-decoration-color: var(--app-dark);
}

.app-form-label {
    display: block;
    margin-bottom: var(--app-spacer-xs);
    font-size: var(--app-font-size-sm);
    font-weight: 500;
    color: var(--app-gray-700);
}

/* Label + bouton d'action sur la même ligne (label à gauche, bouton à droite) */
.app-form-label-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--app-spacer-sm);
    margin-bottom: var(--app-spacer-xs);
}

.app-form-label-row .app-form-label {
    margin-bottom: 0;
}

.app-form-input,
.app-form-select,
.app-form-textarea {
    display: block;
    width: 100%;
    padding: 0.5rem 0.75rem;
    font-size: var(--app-font-size-base);
    line-height: 1.5;
    color: var(--app-dark);
    background: var(--app-white);
    border: 1px solid var(--app-gray-200);
    border-radius: var(--app-radius);
    transition: border-color var(--app-transition), box-shadow var(--app-transition);
}

/* Override Tabler : espacement réduit entre les items d'un form-selectgroup.
   Tabler définit `.form-selectgroup .form-selectgroup-item { margin: 0 .5rem .5rem 0 }`
   (spécificité 0,2,0). On reprend la même spécificité, root.css étant chargé
   APRÈS Tabler → notre valeur (.15rem) gagne. */
.form-selectgroup .form-selectgroup-item {
    margin: 0 .15rem .15rem 0;
}
/* Le conteneur rend exactement la marge de ses items (16/10) : il gardait le
   -.5rem de Tabler, et la colonne en overflow:hidden des formulaires mobiles
   rognait le bas des pastilles de 5 à 6 px (audit du 15/10). */
.form-selectgroup {
    margin: 0 -.15rem -.15rem 0;
}

.app-form-input:focus,
.app-form-select:focus,
.app-form-textarea:focus {
    outline: none;
    border-color: var(--app-gray-700);
    box-shadow: 0 0 0 3px var(--app-gray-200);
}

.app-form-input.is-invalid,
.app-form-select.is-invalid {
    border-color: var(--app-danger);
}

.app-form-error {
    font-size: var(--app-font-size-xs);
    color: var(--app-danger-text);
    margin-top: var(--app-spacer-xs);
}

.app-form-textarea {
    resize: vertical;
    min-height: 100px;
}

/* Form hint — petite bulle d'aide gris clair sous un champ form.
   Style « post-it » avec un mini chevron en haut à gauche qui pointe
   vers le champ au-dessus. La classe `form-hint` (convention Tabler)
   est utilisée directement dans les vues. */
.form-hint {
    position: relative;
    display: block;
    width: 100%;
    background: var(--app-gray-100);
    color: var(--app-gray-700);
    padding: calc(var(--app-spacer) / 2);
    font-size: var(--app-font-size-xs);
    line-height: 1.4;
    border-radius: var(--app-radius);
    margin-top: calc(var(--app-spacer) / 2);
}

/* Chevron pointu en haut — triangle CSS qui pointe vers le champ au-dessus.
   Placé à 12px du bord gauche pour aligner avec un libellé/icône typique. */
.form-hint::before {
    content: '';
    position: absolute;
    top: -6px;
    left: 12px;
    width: 0;
    height: 0;
    border-left: 6px solid transparent;
    border-right: 6px solid transparent;
    border-bottom: 6px solid var(--app-gray-100);
}

.app-form-select {
    appearance: none;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath d='M6 8L1 3h10z' fill='%23767676'/%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-position: right 0.75rem center;
    padding-right: 2rem;
}

/* -- Password toggle -- */
.app-password-wrapper {
    position: relative;
}

.app-password-wrapper > .app-form-input,
.app-password-wrapper > .form-control {
    padding-right: 2.5rem;
}

.app-password-toggle {
    position: absolute;
    right: 0.625rem;
    top: 50%;
    transform: translateY(-50%);
    background: none;
    border: none;
    cursor: pointer;
    color: var(--app-gray-text);
    padding: 0.25rem;
    font-size: 1.125rem;
    line-height: 1;
    display: flex;
    align-items: center;
}

.app-password-toggle:hover {
    color: var(--app-gray-600);
}

/* -- Password generator button -- */
.app-password-generate {
    position: absolute;
    right: 2.5rem;
    top: 50%;
    transform: translateY(-50%);
    background: none;
    border: none;
    padding: 0.25rem;
    cursor: pointer;
    color: var(--app-gray-text);
    font-size: 1.1rem;
    line-height: 1;
    display: flex;
    align-items: center;
    transition: color 0.15s ease;
}

.app-password-generate:hover {
    color: var(--app-success-text);
}

.app-password-wrapper:has(.app-password-generate) > .app-form-input,
.app-password-wrapper:has(.app-password-generate) > .form-control {
    padding-right: 4.5rem;
}

.app-password-generate.is-copied::after {
    content: 'Copié !';
    position: absolute;
    bottom: calc(100% + 4px);
    left: 50%;
    transform: translateX(-50%);
    background: var(--app-gray-800);
    color: var(--app-white);
    font-size: 0.7rem;
    padding: 2px 8px;
    border-radius: var(--app-radius-sm);
    white-space: nowrap;
    animation: app-fade-in-up 0.2s ease;
    pointer-events: none;
}

@keyframes app-fade-in-up {
    from { opacity: 0; transform: translateX(-50%) translateY(4px); }
    to   { opacity: 1; transform: translateX(-50%) translateY(0); }
}

/* -- Password strength indicator -- */
.app-password-strength {
    margin-top: var(--app-spacer-xs);
}

.app-password-strength__track {
    height: 4px;
    background: var(--app-gray-100);
    border-radius: 2px;
    overflow: hidden;
}

.app-password-strength__bar {
    height: 100%;
    width: 0;
    border-radius: 2px;
    transition: width 0.3s ease, background 0.3s ease;
}

.app-password-strength__label {
    font-size: var(--app-font-size-xs);
    color: var(--app-gray-text);
    margin-top: var(--app-spacer-xs);
}

/* ── Jauge « barre combinée » d'événement (refonte 2026-06-24) ──
   UNE seule barre bi-ton : segment vert (cartes validées) + segment violet
   (récompenses attribuées) sur fond gris « en attente ». Remplace l'ancienne
   paire de jauges séparées (cartes / récompenses) côté listing events
   + booster. Cf. partial shared/event-progress + la formule unique
   EventUserTarget::buildProgress. */
.app-event-progress {
    display: flex;
    flex-direction: column;
    gap: 6px;
}

.app-event-progress__head {
    display: flex;
    align-items: center;
    gap: var(--app-spacer-xs);
}

.app-event-progress__bar {
    flex: 1;
    height: 10px;
    background: var(--app-gray-200);
    border-radius: 999px;
    overflow: hidden;
    display: flex;
}

.app-event-progress__seg {
    height: 100%;
    transition: width 400ms cubic-bezier(0.4, 0, 0.2, 1);
}

.app-event-progress__seg--cards   { background: var(--app-primary); }
.app-event-progress__seg--rewards { background: var(--app-purple); }

.app-event-progress__percent {
    font-size: var(--app-font-size-sm);
    font-weight: 600;
    color: var(--app-dark);
    font-variant-numeric: tabular-nums;
    min-width: 2.6em;
    text-align: right;
}

.app-event-progress__counts {
    display: flex;
    align-items: center;
    gap: var(--app-spacer);
    font-size: var(--app-font-size-xs);
    color: var(--app-gray-700);
}

.app-event-progress__count {
    display: inline-flex;
    align-items: center;
    gap: 5px;
}

.app-event-progress__count strong {
    color: var(--app-dark);
    font-weight: 600;
    font-variant-numeric: tabular-nums;
}

.app-event-progress__count--cards i   { color: var(--app-success-text); }
.app-event-progress__count--rewards i { color: var(--app-purple); }

.app-event-progress__pending {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    margin-left: auto;
    color: var(--app-gray-text);
}

/* Compact (cellule de tableau events) : barre plus fine, compteurs resserrés
   et sans libellé texte (icône + chiffre seuls — géré côté partial). */
.app-event-progress--compact { gap: 5px; }
.app-event-progress--compact .app-event-progress__bar { height: 7px; }
.app-event-progress--compact .app-event-progress__percent {
    font-size: var(--app-font-size-xs);
    min-width: 2.4em;
}
.app-event-progress--compact .app-event-progress__counts { gap: var(--app-spacer-sm); }

/* Mobile : les compteurs de progression (cartes / LINK / en attente) wrappent
   dans les cartes événement étroites (grille 2 col) au lieu de déborder à
   droite — le « en attente » (margin-left:auto) passe à la ligne au besoin. */
@media (max-width: 768px) {
    .app-event-progress__counts {
        flex-wrap: wrap;
        gap: var(--app-spacer-xs) var(--app-spacer-sm);
        min-width: 0;
    }
    .app-event-progress__pending {
        margin-left: 0;
    }
}

/* -- Badges -- */
.app-badge {
    display: inline-flex;
    align-items: center;
    /* L'icône ne colle plus au mot (« ⊗Rejetée », audit du 15/10). */
    gap: 0.25em;
    padding: 0.15rem 0.5rem;
    font-size: var(--app-font-size-xs);
    font-weight: 500;
    border-radius: 50px;
    line-height: 1.5;
    white-space: nowrap;
    /* Badge utilisé en `<a>` (lien cliquable) : pas d'underline. */
    text-decoration: none;
}

.app-badge--primary { background: var(--app-primary-light); color: var(--app-success-text); }
.app-badge--secondary { background: var(--app-secondary-light); color: var(--app-secondary); }
.app-badge--success { background: var(--app-success-light); color: var(--app-success-text); }
.app-badge--danger { background: var(--app-danger-light); color: var(--app-danger-text); }
.app-badge--warning { background: var(--app-warning-light); color: var(--app-warning-text); }
/* Orange PLEIN (fond orange, texte blanc) — réservé au certifié ADMIN (template
   figé). Le certifié CLIENT (instance perso capturée) garde `--warning` (light)
   pour les distinguer d'un coup d'œil. */
.app-badge--warning-solid { background: var(--app-warning); color: var(--app-white); }
.app-badge--info { background: var(--app-info-light); color: var(--app-info); }
.app-badge--neutral { background: var(--app-gray-100); color: var(--app-gray-700); }
/* Badge count "neutre" utilisé partout dans les listings.
   Pour un badge réellement vert, utiliser `--success` explicitement. */
.app-badge--light { background: var(--app-gray-100); color: var(--app-gray-700); }
/* Badge récompense — violet plein, texte blanc.
   Identifie fortement la sémantique « Récompense » dans les listings. */
.app-badge--purple { background: var(--app-purple); color: var(--app-white); }

/* Variante HOLO — reprend le gradient holographique de la carte shiny
   (cf. card-default.css → .tc-card--shiny-cosmos). Utilisé partout où on
   indique qu'un rang a un effet shiny (listing admin rangs,
   picker carte, etc.) pour rendre visuellement reconnaissable la propriété
   « ce ranking imprime un effet brillant sur la carte ». */
.app-badge--holo {
    color: var(--app-white);
    background: linear-gradient(
        135deg,
        #ff5e5b 0%,
        #ffd166 16%,
        #06d6a0 33%,
        #118ab2 50%,
        #b388eb 66%,
        #f72585 83%,
        #ff5e5b 100%
    );
    background-size: 200% 200%;
    animation: app-badge-holo-shift 6s ease-in-out infinite;
    padding: 0 0.5rem;
    text-shadow: 0 1px 1px rgba(0, 0, 0, 0.25);
    font-weight: 600;
}

@keyframes app-badge-holo-shift {
    0%, 100% { background-position:   0% 50%; }
    50%      { background-position: 100% 50%; }
}

@media (prefers-reduced-motion: reduce) {
    .app-badge--holo { animation: none; }
}

/* Soft variants — fond blanc, couleur texte conservée (utile dans les inner-cards sur fond gris) */
.app-badge--soft-primary { background: var(--app-white); color: var(--app-success-text); }
.app-badge--soft-secondary { background: var(--app-white); color: var(--app-secondary); }
.app-badge--soft-success { background: var(--app-white); color: var(--app-success-text); }
.app-badge--soft-danger { background: var(--app-white); color: var(--app-danger-text); }
.app-badge--soft-warning { background: var(--app-white); color: var(--app-warning-text); }
.app-badge--soft-info { background: var(--app-white); color: var(--app-info); }
.app-badge--soft-neutral { background: var(--app-white); color: var(--app-gray-600); }
/* Variant zone Récompense. */
.app-badge--soft-purple { background: var(--app-white); color: var(--app-purple); }

/* Light variants — fond teinté clair + couleur sémantique (cohérent avec app-badge--light). */
.app-badge--primary-light { background: var(--app-primary-light); color: var(--app-success-text); }
.app-badge--info-light    { background: var(--app-info-light);    color: var(--app-info); }
.app-badge--warning-light { background: var(--app-warning-light); color: var(--app-warning-text); }
.app-badge--purple-light  { background: var(--app-purple-light);  color: var(--app-purple); }

/* Size variants */
.app-badge--xs {
    padding: 0.1rem 0.35rem;
    font-size: 0.625rem;
    line-height: 1.3;
}

a.app-badge:hover { opacity: 0.8; }

/* -- Cartes -- */
.app-card {
    background: var(--app-white);
    border: 1px solid var(--app-gray-200);
    border-radius: var(--app-radius-lg);
    overflow: hidden;
}

.app-card__header {
    padding: var(--app-spacer) var(--app-spacer-md);
    border-bottom: 1px solid var(--app-gray-200);
    display: flex;
    align-items: center;
    justify-content: space-between;
}

.app-card__title {
    font-size: var(--app-font-size-md);
    font-weight: 600;
    margin: 0;
}

.app-card__body {
    padding: var(--app-spacer-md);
}

.app-card__footer {
    padding: var(--app-spacer) var(--app-spacer-md);
    border-top: 1px solid var(--app-gray-100);
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: var(--app-spacer-sm);
}

.app-card__footer--flat {
    border-top: 1px solid var(--app-gray-200);
    padding: var(--app-spacer) 0 0;
    margin-top: var(--app-spacer);
}

.app-card__subtitle {
    font-size: var(--app-font-size-base);
    font-weight: 600;
    margin: 0 0 var(--app-spacer-sm) 0;
    color: var(--app-gray-700);
}

/* -- Divider (visible globalement, contraste augmenté) -- */
.app-divider {
    border: none;
    border-top: 1px solid var(--app-gray-300);
    margin: var(--app-spacer-md) 0;
}

/* -- Selectgroup overrides (Tabler) -- */
.form-selectgroup-label {
    border-radius: var(--app-radius) !important;
    /* Alignement du contenu (texte + cercle holo / icône) avec un petit
       espace. inline-flex pour garder l'alignement horizontal des items
       du selectgroup. Présent ici (root, chargé partout) ET dans
       public-ball.css pour couvrir les forms sans ce dernier (events). */
    display: inline-flex;
    align-items: center;
    gap: var(--app-spacer-xs);
}

/* Selectgroup actif — fond dark plein + texte blanc.
   Distinct du disabled (opacity 0.45). */
.form-selectgroup-input:checked + .form-selectgroup-label {
    background-color: var(--app-dark);
    border-color: var(--app-dark);
    color: var(--app-white);
    font-weight: 600;
    z-index: 1;
}

.form-selectgroup-input:disabled + .form-selectgroup-label {
    opacity: 0.45;
    cursor: not-allowed;
    background-color: var(--app-gray-100);
}

.form-selectgroup-input:disabled:checked + .form-selectgroup-label {
    opacity: 1;
    background-color: var(--app-dark);
    border-color: var(--app-dark);
    color: var(--app-white);
    cursor: default;
}

/* ==========================================================
   Pagination (shared admin + client)
   ========================================================== */

.app-pagination {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: var(--app-spacer-sm);
    margin-top: var(--app-spacer-md);
    font-size: var(--app-font-size-sm);
}

.app-pagination__pages {
    display: flex;
    align-items: center;
    gap: var(--app-spacer-xs);
}

.app-pagination__link,
.app-pagination__page {
    display: inline-flex;
    align-items: center;
    gap: 0.25rem;
    padding: 0.375rem 0.75rem;
    /* border/background/font/cursor neutralisés pour que le pager client-side
       (list-paginate.js) puisse réutiliser ces classes sur des <button>. Les
       états hover/--active (règles suivantes) restent prioritaires. */
    border: 0;
    background: transparent;
    border-radius: var(--app-radius);
    color: var(--app-gray-600);
    font: inherit;
    cursor: pointer;
    text-decoration: none;
    transition: all var(--app-transition);
}

/* Pagination : couleur neutre (dark) pour les états hover/active.
   La sémantique couleur reste réservée aux CTAs et entités métier ;
   la pagination est un outil de navigation neutre. */
.app-pagination__link:hover,
.app-pagination__page:hover {
    background: var(--app-gray-100);
    color: var(--app-dark);
}

.app-pagination__page--active {
    background: var(--app-dark);
    color: var(--app-white);
}

.app-pagination__page--active:hover {
    background: var(--app-gray-700);
    color: var(--app-white);
}

.app-pagination__link--disabled {
    opacity: 0.4;
    pointer-events: none;
}

.app-pagination__ellipsis {
    padding: 0.375rem;
    color: var(--app-gray-text);
}

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

.app-pagination-wrapper .app-pagination {
    margin-top: 0;
}

.app-pagination-per-page {
    position: absolute;
    right: 0;
    display: flex;
    align-items: center;
    gap: 0.375rem;
    font-size: var(--app-font-size-sm);
    color: var(--app-gray-text);
}

.app-pagination-per-page__select {
    padding: 0.25rem 1.75rem 0.25rem 0.5rem;
    border: 1px solid var(--app-gray-300);
    border-radius: var(--app-radius);
    font-size: var(--app-font-size-sm);
    color: var(--app-gray-600);
    background: var(--app-white) url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath fill='none' stroke='%23666' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m4 6 4 4 4-4'/%3E%3C/svg%3E") no-repeat right 0.4rem center / 0.75rem;
    appearance: none;
    cursor: pointer;
    transition: border-color var(--app-transition);
}

.app-pagination-per-page__select:hover {
    border-color: var(--app-gray-400);
}

.app-pagination-per-page__select:focus {
    outline: none;
    border-color: var(--app-gray-700);
    box-shadow: 0 0 0 2px var(--app-gray-200);
}

@media (max-width: 768px) {
    .app-pagination-wrapper {
        flex-direction: column;
        gap: var(--app-spacer-sm);
    }

    .app-pagination {
        flex-wrap: wrap;
        gap: var(--app-spacer-xs);
    }

    .app-pagination__pages {
        flex-wrap: wrap;
        justify-content: center;
    }

    .app-pagination__link,
    .app-pagination__page {
        padding: 0.3rem 0.5rem;
        font-size: var(--app-font-size-xs);
    }

    .app-pagination-per-page {
        position: static;
    }
}

/* -- Club sports inline display (shared admin + client) -- */
.app-club-sports {
    display: flex;
    align-items: center;
    gap: var(--app-spacer-xs);
    font-size: var(--app-font-size-xs);
    color: var(--app-gray-text);
    margin-top: 2px;
}

.app-club-sports i {
    font-size: 0.875rem;
    flex-shrink: 0;
}

.app-club-sports--alert {
    color: var(--app-danger-text);
    font-weight: 500;
}

/* -- Event cell (partial shared/event-cell.php) ---------------------- */
/* Bloc « cellule événement » utilisé dans toutes les colonnes Événement
   des tableaux cartes (admin/client). Format :
     Nom de l'event   [Badge Certifié]
     › Classif   Date(bold)   · Catégorie(light) */
.app-event-cell {
    display: flex;
    flex-direction: column;
    gap: 2px;
    min-width: 0;
}

.app-event-cell__name-row {
    display: flex;
    align-items: center;
    gap: var(--app-spacer-xs);
    flex-wrap: wrap;
}

.app-event-cell__name {
    font-weight: 600;
    color: var(--app-dark);
}

/* La précision d'un tournoi (01/10) : deux tournois du même nom, le même jour.
   Coupable n'importe où : 80 caractères sans espace ne doivent pas élargir la cellule. */
.app-event-cell__distinction {
    font-weight: 500;
    color: var(--app-gray-600);
    overflow-wrap: anywhere;
    min-width: 0;
}

/* ── Indicateurs événement réutilisés partout (listing events + event-cell
   des pages cartes) : flèche « instance perso » + pastille image de fond. ── */
.app-list__child-arrow,
.app-event-cell__child-arrow {
    color: var(--app-gray-text);
    margin-right: 0.15rem;
    vertical-align: -1px;
}
.app-event-bg-dot {
    display: inline-flex;
    width: 10px;
    height: 10px;
    border-radius: 50%;
    overflow: hidden;
    flex: 0 0 auto;
    border: 1px solid rgba(0, 0, 0, 0.08);
    vertical-align: -1px;
}
.app-event-bg-dot img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
}

.app-event-cell__meta {
    display: flex;
    align-items: center;
    gap: 4px;
    font-size: var(--app-font-size-xs);
    color: var(--app-gray-text);
    flex-wrap: wrap;
}

.app-event-cell__date {
    color: var(--app-dark);
    font-weight: 600;
}

/* Classification & catégorie : rendues en petits badges (app-badge--neutral
   app-badge--xs) — l'apparence est portée par les classes badge. */

/* Indicateur holo générique : pastille ronde (15px, taille d'un badge),
   gradient holo animé, sans icône ni texte. Réutilisable partout où un
   effet shiny doit être signalé discrètement (event-cell, ranking-picker…).
   Le libellé passe en attribut title. */
.app-holo-dot,
.app-event-cell__holo {
    display: inline-block;
    width: 15px;
    height: 15px;
    border-radius: 50%;
    flex-shrink: 0;
    vertical-align: middle;
    background: linear-gradient(
        135deg,
        #ff5e5b 0%,
        #ffd166 16%,
        #06d6a0 33%,
        #118ab2 50%,
        #b388eb 66%,
        #f72585 83%,
        #ff5e5b 100%
    );
    background-size: 200% 200%;
    animation: app-badge-holo-shift 6s ease-in-out infinite;
    text-shadow: 0 1px 1px rgba(0, 0, 0, 0.25);
}

/* Pastille holo de l'event-cell : 10×10 comme les autres dots (couleur, image). */
.app-event-cell__holo {
    width: 10px;
    height: 10px;
}

@media (prefers-reduced-motion: reduce) {
    .app-holo-dot,
    .app-event-cell__holo { animation: none; }
}

/* Mobile : pastille holo réduite à 12px. */
@media (max-width: 768px) {
    .app-holo-dot,
    .app-event-cell__holo {
        width: 12px;
        height: 12px;
    }
}

/* -- Sport cell (partial shared/sport-cell.php) --------------------- */
.app-sport-cell {
    display: flex;
    align-items: center;
    gap: var(--app-spacer-xs);
    min-width: 0;
}

.app-sport-cell__icon {
    width: 24px;
    height: 24px;
    object-fit: contain;
    flex-shrink: 0;
}

@media (max-width: 768px) {
    .app-sport-cell__icon {
        width: 16px;
        height: 16px;
    }
}

.app-sport-cell__icon--empty {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    color: var(--app-gray-text);
}

.app-sport-cell__info {
    display: flex;
    flex-direction: column;
    min-width: 0;
}

.app-sport-cell__name {
    color: var(--app-dark);
}

/* -- Reward cell (partial shared/reward-cell.php) ------------------- */
.app-reward-cell {
    display: flex;
    align-items: center;
    gap: var(--app-spacer-xs);
    flex-wrap: wrap;
    min-width: 0;
}

.app-reward-cell--stacked {
    flex-direction: column;
    align-items: flex-start;
    gap: 2px;
}

/* Pastille circulaire « encodée NFC » (partial shared/reward-encoded-dot.php) :
   icône seule dans un cercle décoratif neutre, SANS bordure. Encodée = couleur
   texte ; en attente = grise. (Pas de vert.) */
.app-encoded-dot {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 1.3rem;
    height: 1.3rem;
    border-radius: 50%;
    background: var(--app-gray-100, #f5f5f5);
    font-size: 0.72rem;
    line-height: 1;
    flex-shrink: 0;
}

.app-encoded-dot--on {
    color: var(--app-gray-900, #212121);
}

.app-encoded-dot--off {
    color: var(--app-gray-400, #bdbdbd);
}

/* -- Player cell (partial shared/player-cell.php) ------------------- */
.app-player-cell {
    display: flex;
    flex-direction: column;
    gap: 4px;
    min-width: 0;
}

.app-player-cell__name {
    display: flex;
    align-items: center;
    gap: var(--app-spacer-xs);
    color: var(--app-dark);
}

.app-player-cell__flag {
    display: inline-flex;
    flex-shrink: 0;
}

.app-player-cell__flag .fi {
    width: 1.1em;
    height: 0.8em;
    border-radius: 2px;
}

/* -- Ranking cell (partial shared/ranking-cell.php) ---------------- */
.app-ranking-cell {
    display: flex;
    align-items: center;
    gap: var(--app-spacer-xs);
    flex-wrap: wrap;
    min-width: 0;
}

.app-ranking-cell__name {
    color: var(--app-dark);
}

/* -- Date cell (partial shared/date-cell.php) ---------------------- */
.app-date-cell {
    display: flex;
    flex-direction: column;
    min-width: 0;
}

/* -- Empty state -- */
.app-empty-state {
    text-align: center;
    padding: var(--app-spacer-xl) var(--app-spacer);
    color: var(--app-gray-text);
}

.app-empty-state i {
    font-size: 3rem;
    display: block;
    margin-bottom: var(--app-spacer-sm);
}

/* -- List empty (icon) -- */
.app-list__empty i {
    font-size: 2rem;
    display: block;
    margin-bottom: var(--app-spacer-xs);
}

/* -- Inline forms (delete forms in modals, etc.) -- */
.app-form-inline {
    display: inline;
}

/* -- Required asterisk -- */
.app-required {
    color: var(--app-danger-text);
}

/* -- Player row (inline flag + name) -- */
.app-player-row {
    display: flex;
    gap: var(--app-spacer-sm);
    align-items: center;
}

.app-player-row__flag {
    width: 70px;
    flex-shrink: 0;
    font-size: 1.25rem;
    text-align: center;
}

/* Searchable select wrapper inside player row */
.app-player-row .app-search-select {
    width: 110px;
    flex-shrink: 0;
}

.app-player-row .app-search-select__trigger {
    padding: 0.5rem 0.5rem;
    justify-content: center;
    gap: 0.25rem;
}

.app-player-row .app-search-select__trigger-text {
    flex: 1;
    min-width: 0;
    font-size: var(--app-font-size-base);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.app-player-row .app-search-select__trigger-icon {
    font-size: 0.85rem;
    margin-left: 0;
}

/* Flag icons inside searchable select options + trigger */
.app-search-select__option .fi,
.app-search-select__trigger-text .fi {
    display: inline-block;
    width: 1.3em;
    line-height: 1em;
    border-radius: 2px;
    vertical-align: middle;
    margin-right: 0.3em;
}

.app-player-row .app-search-select__dropdown {
    min-width: 260px;
}

.app-player-row .form-control,
.app-player-row .app-form-input {
    flex: 1;
    min-width: 0;
}

.app-player-row {
    margin-bottom: var(--app-spacer-xs);
}

/* -- Variante "stacked" : flag, name et position empilés en colonne dans une boîte
      (utilisée dans la modale d'upload public pour clarifier la saisie) -- */
.app-player-row--stacked {
    flex-direction: column;
    align-items: stretch;
    gap: var(--app-spacer-sm);
    padding: var(--app-spacer);
    background: var(--app-gray-100);
    border: 1px solid var(--app-gray-200);
    border-radius: var(--app-radius);
}

.app-player-row--stacked .app-player-row__flag,
.app-player-row--stacked .app-search-select {
    width: 100%;
    flex: 0 0 auto;
}

.app-player-row--stacked .form-control,
.app-player-row--stacked .app-form-input {
    width: 100%;
    flex: 0 0 auto;
}

/* Mobile : flag / nom / position empilés (les uns sous les autres), pleine
   largeur — sans le fond/boîte de la variante --stacked. */
@media (max-width: 768px) {
    .app-player-row {
        flex-direction: column;
        align-items: stretch;
    }

    .app-player-row__flag,
    .app-player-row .app-search-select,
    .app-player-row .form-control,
    .app-player-row .app-form-input {
        width: 100%;
        flex: 0 0 auto;
    }
}


/* ==========================================================
   Responsive global — Réduction titres, boutons, cards
   ========================================================== */
@media (max-width: 768px) {
    /* -- Variables de taille réduites -- */
    :root {
        --app-font-size-xxl:  1.5rem;
        --app-font-size-xl:   1.25rem;
        --app-font-size-lg:   1.1rem;
        --app-font-size-md:   0.9375rem;
        --app-font-size-base: 0.875rem;
        --app-font-size-sm:   0.8125rem;
        --app-font-size-xs:   0.6875rem;
    }

    /* -- Boutons : plus compacts (gain horizontal + vertical en mobile), lisibles : 13 px
          et non 11 (audit du 15/10) -- */
    .app-btn {
        padding: 0.35rem 0.75rem;
        gap: var(--app-spacer-xs);
        font-size: var(--app-font-size-sm);
    }

    .app-btn--lg {
        padding: 0.45rem 1rem;
        font-size: var(--app-font-size-sm);
    }

    .app-btn--sm {
        padding: 0.2rem 0.55rem;
    }

    .app-btn--icon {
        padding: 0.35rem;
    }

    .app-btn--icon.app-btn--sm {
        padding: 0.2rem;
    }

    /* -- Badges : plus compacts -- */
    .app-badge {
        padding: 0.1rem 0.4rem;
        font-size: 0.625rem;
    }

    /* -- Cards : padding réduit -- */
    .app-card__header {
        padding: var(--app-spacer-sm) var(--app-spacer);
    }

    .app-card__body {
        padding: var(--app-spacer);
    }

    .app-card__footer {
        padding: var(--app-spacer-sm) var(--app-spacer);
    }

    .app-card__footer--flat {
        padding: var(--app-spacer-sm) 0 0;
    }

    .app-card__footer .app-btn {
        flex: 1;
        justify-content: center;
    }

    /* -- Formulaires : plus compacts en mobile, mais jamais sous 16 px : en dessous,
          l'iPhone agrandit la page dès qu'on touche un champ (audit du 15/10, le zoom
          n'est plus bloqué par la balise viewport) -- */
    .form-control,
    .form-select,
    .app-form-input,
    .app-form-select,
    .app-form-textarea {
        font-size: 1rem;
        padding: 0.375rem 0.625rem;
    }

    /* -- Labels formulaire -- */
    .col-form-label {
        font-size: var(--app-font-size-xs);
        padding-bottom: 0;
    }

    /* -- Selectgroup : plus compact -- */
    .form-selectgroup-label {
        font-size: var(--app-font-size-xs);
        padding: 0.25rem 0.5rem;
    }

    /* -- Avatars grand format : réduire -- */
    .avatar-xl {
        width: 3rem !important;
        height: 3rem !important;
        font-size: 0.875rem !important;
    }

    .avatar-lg {
        width: 2.5rem !important;
        height: 2.5rem !important;
        font-size: 0.75rem !important;
    }

    /* -- Rows Bootstrap : empiler label + input -- */
    .app-card__body .row.mb-3 {
        margin-bottom: var(--app-spacer-sm) !important;
    }

    .app-card__body .row .col-3 {
        flex: 0 0 100%;
        max-width: 100%;
        margin-bottom: 0.125rem;
    }

    .app-card__body .row .col {
        min-width: 0;
        overflow: hidden;
    }

    /* -- Inline form : wrap en mobile -- */
    .app-inline-form {
        flex-wrap: wrap;
    }

    /* -- Color pickers : empiler verticalement -- */
    .row.g-3:has(.app-color-field) {
        flex-wrap: wrap;
    }

    .row.g-3:has(.app-color-field) > .col-md-4 {
        flex: 0 0 100%;
        max-width: 100%;
    }

    .row.g-3:has(.app-color-field) .form-label {
        font-size: var(--app-font-size-xs);
        margin-bottom: 0.125rem;
    }

    .app-color-field__preview {
        width: 32px;
        height: 32px;
    }

    /* -- Boutons télécharger/supprimer : empiler -- */
    .app-card__body div[style*="display: flex; gap: 0.5rem; margin-top"] {
        flex-wrap: wrap !important;
    }

    .app-card__body div[style*="display: flex; gap: 0.5rem; margin-top"] .app-btn {
        width: 100%;
        justify-content: center;
    }
}

/* ==========================================================
   Shared list component (admin + client)
   ========================================================== */

.app-list {
    background: var(--app-white);
    border: 1px solid var(--app-gray-200);
    border-radius: var(--app-radius-lg);
    overflow: hidden;
}

.app-list__header {
    display: grid;
    gap: var(--app-spacer-sm);
    padding: var(--app-spacer-sm) var(--app-spacer);
    background: var(--app-gray-100);
    border-bottom: 1px solid var(--app-gray-200);
    font-size: var(--app-font-size-xs);
    font-weight: 600;
    color: var(--app-gray-text);
    text-transform: uppercase;
    letter-spacing: 0.05em;
}

.app-list__row {
    display: grid;
    gap: var(--app-spacer-sm);
    padding: var(--app-spacer-sm) var(--app-spacer);
    border-bottom: 1px solid var(--app-gray-200);
    align-items: center;
    font-size: var(--app-font-size-sm);
    transition: background var(--app-transition);
}

.app-list__row:last-child {
    border-bottom: none;
}

.app-list__row:hover {
    background: var(--app-gray-100);
}

.app-list__col--actions {
    display: flex;
    gap: var(--app-spacer-xs);
    justify-content: flex-end;
}

.app-list__empty {
    padding: var(--app-spacer-xl);
    text-align: center;
    color: var(--app-gray-text);
}

/* -- Responsive: chaque ligne = carte indépendante -- */
@media (max-width: 768px) {
    .app-list {
        background: none;
        border: none;
        border-radius: 0;
    }

    .app-list .app-list__header {
        display: none;
    }

    .app-list .app-list__row {
        background: var(--app-white);
        border: 1px solid var(--app-gray-200);
        border-radius: var(--app-radius-lg);
        margin-bottom: var(--app-spacer-sm);
        grid-template-columns: 1fr;
        gap: 0;
        padding: 0;
        position: relative;
    }

    .app-list .app-list__row:last-child {
        margin-bottom: 0;
    }

    .app-list .app-list__row:hover {
        background: var(--app-white);
    }

    .app-list .app-list__col {
        grid-column: 1;
        min-width: 0;
        display: flex;
        align-items: center;
        gap: var(--app-spacer-sm);
        padding: var(--app-spacer-sm) var(--app-spacer);
        border-bottom: 1px solid var(--app-gray-100);
    }

    .app-list .app-list__col:last-child {
        border-bottom: none;
    }

    .app-list .app-list__col::before {
        content: attr(data-label);
        flex-shrink: 0;
        width: 5.25rem;
        font-size: 0.625rem;
        font-weight: 600;
        color: var(--app-gray-text);
        text-transform: uppercase;
        letter-spacing: 0.05em;
        /* Labels affichés EN ENTIER (wrap sur 2 lignes au besoin) au lieu d'être
           tronqués « EVENE… » / « UTILISA… ». Colonne un peu plus large. */
        white-space: normal;
        /* Césure du dictionnaire, pas au milieu d'un mot (« CLASSIFICATIO / NS », 16/10). */
        overflow-wrap: normal;
        hyphens: auto;
        line-height: 1.2;
        align-self: flex-start;
    }

    /* LES ACTIONS EN PIED DE CARTE (16/10, audit du 15/10). Posées en absolu en haut à
       droite, sans place réservée, elles recouvraient le nom, l'e-mail ou l'état, et la
       quatrième passait sous la carte suivante. Une rangée dans le flux, à droite. */
    .app-list .app-list__col--actions {
        position: static;
        order: 99;
        flex-direction: row;
        flex-wrap: wrap;
        justify-content: flex-end;
        align-items: center;
        gap: var(--app-spacer-sm);
        padding: var(--app-spacer-sm) var(--app-spacer);
        border-top: 1px solid var(--app-gray-100);
        border-bottom: none;
    }

    .app-list .app-list__col--actions::before {
        display: none;
    }

    /* Zones tactiles : boutons d'action de liste agrandis (~40px min) — ils
       étaient trop petits et collés pour le doigt en mobile. */
    .app-list .app-list__col--actions .app-btn,
    .app-list .app-list__col--actions .btn-action {
        min-width: 2.5rem;
        min-height: 2.5rem;
    }

    /* Colonne « Événement » : on garde le label à gauche, et on empile
       verticalement à droite le badge officiel/custom puis le lien vers
       l'événement. Sinon en mobile c'est trop dense sur 1 ligne. */
    .app-list .app-list__col[data-label="Événement"] {
        display: grid;
        grid-template-columns: 5.25rem 1fr;
        grid-auto-rows: auto;
        column-gap: var(--app-spacer-sm);
        row-gap: var(--app-spacer-xs);
        align-items: center;
        justify-items: start;
    }
    .app-list .app-list__col[data-label="Événement"]::before {
        grid-column: 1;
        grid-row: 1 / span 2;
        align-self: center;
    }
}

/* ==========================================================
   Shared item patterns (balls/cards — admin + client)
   ========================================================== */

/* -- Hash monospace : pastilles distinctes carte / récompense / NFC -- */
.app-hash,
.app-ball-hash {
    font-family: var(--app-font-mono);
    font-size: var(--app-font-size-xs);
    padding: 0.1rem 0.4rem;
    border-radius: var(--app-radius-sm);
    letter-spacing: 0.03em;
    user-select: all;
    display: inline-block;
}

/* Hash neutres (par défaut) — gris.
   Toute classe inconnue ou sans modificateur tombe sur ce style. */
.app-hash,
.app-ball-hash {
    background: var(--app-gray-100);
    color: var(--app-gray-700);
}

/* Hash carte — vert (sémantique « Carte »). */
.app-hash--card {
    background: var(--app-primary-light);
    color: var(--app-success-text);
}

/* Hash récompense — violet (sémantique « Récompense »). */
.app-hash--reward {
    background: var(--app-purple-light);
    color: var(--app-purple);
}

/* UID NFC — violet plein, texte blanc (inverse du hash récompense classique).
   NFC reste dans la famille « récompense » mais inversé pour le distinguer
   visuellement du hash récompense classique. */
.app-hash--nfc {
    background: var(--app-purple);
    color: var(--app-white);
}

/* Hash/code cliquable pour copy-to-clipboard : feedback visuel discret au
   hover (l'utilisateur sait que c'est cliquable). Combiné avec data-app-copy
   pour déclencher la copie + toast. */
.app-hash--clickable {
    cursor: pointer;
    transition: opacity var(--app-transition), filter var(--app-transition);
    user-select: all;
}

.app-hash--clickable:hover {
    filter: brightness(0.95);
    opacity: 0.95;
}

.app-hash--clickable:active {
    filter: brightness(0.88);
}

.app-ball-hash-display {
    display: flex;
    align-items: center;
    gap: var(--app-spacer-sm);
}

.app-ball-hash-row {
    display: flex;
    align-items: center;
    gap: var(--app-spacer-sm);
}

/* -- NFC indicators -- */
.app-ball-nfc {
    display: flex;
    align-items: center;
    gap: 0.35rem;
    margin-top: 0.2rem;
    padding-left: 1.35rem;
}

.app-ball-nfc__dot {
    display: inline-block;
    width: 7px;
    height: 7px;
    border-radius: 50%;
    flex-shrink: 0;
}

.app-ball-nfc__dot--ok {
    background-color: var(--app-success);
    box-shadow: 0 0 0 2px rgba(0, 190, 61, 0.15);
}

.app-ball-nfc__dot--pending {
    background-color: var(--app-danger);
    box-shadow: 0 0 0 2px rgba(220, 53, 69, 0.15);
}

.app-ball-nfc__uid {
    font-family: var(--app-font-mono);
    font-size: var(--app-font-size-sm);
    color: var(--app-white);
    background: var(--app-gray-700);
    padding: 0.1rem 0.4rem;
    border-radius: var(--app-radius-sm);
    letter-spacing: 0.03em;
    user-select: all;
    display: inline-block;
}

.app-ball-nfc__label {
    font-size: 0.68rem;
    color: var(--app-gray-text);
    font-style: italic;
}

.app-ball-nfc__encode {
    font-size: 0.68rem;
    color: var(--app-gray-text);
    text-decoration: none;
    font-style: italic;
    transition: color var(--app-transition);
}

.app-ball-nfc__encode:hover {
    color: var(--app-gray-600);
}

/* -- Détail LINK™ (édition carte) : la STEP BODY elle-même EST la boîte (violet
      clair + bordure), pas de boîte imbriquée. Contenu compact — une seule ligne
      de chips (sans label), actions alignées à gauche dessous. -- */
/* Double classe pour passer devant `.app-step__body` (fond blanc) quel que soit
   l'ordre de chargement des feuilles. */
.app-step__body.app-link-body {
    background: var(--app-purple-light);
    border-color: var(--app-purple-border);
}

/* Actions (Encoder / Modifier / Supprimer, ou boutons d'ajout) : alignées à
   gauche, sous les badges. */
.app-link-body__actions {
    display: flex;
    flex-wrap: wrap;
    gap: var(--app-spacer-sm);
    /* Séparateur entre le contenu (badges / « Aucun LINK™ ») et les boutons :
       filet teinté violet du thème + respiration au-dessus. */
    margin-top: var(--app-spacer);
    padding-top: var(--app-spacer);
    border-top: 1px solid var(--app-purple-border);
}

/* État vide : pastille + libellé sur une ligne. */
.app-link-body__empty {
    display: flex;
    align-items: center;
    gap: var(--app-spacer-sm);
}

.app-link-body__pending {
    font-size: var(--app-font-size-sm);
    color: var(--app-purple);
    font-style: italic;
    opacity: 0.8;
}

/* ══════════════════════════════════════════════════════════════════
   Badges LINK™ (partial shared/link-badges) — réutilisable.
   Badge 1 : pilule dégradée violet (type + statut d'encodage), concept
   repris du badge de la grille. Badge 2 : boîte blanche listant les deux
   codes, une icône remix par code pour les distinguer.
   ══════════════════════════════════════════════════════════════════ */
.app-link-badges {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--app-spacer-sm);
}

/* Badge 1 — pilule dégradée : type (icône + libellé blanc) + pilule statut. */
.app-link-badge {
    display: inline-flex;
    align-items: center;
    gap: var(--app-spacer);
    padding: 0.35rem 0.4rem 0.35rem 0.85rem;
    border-radius: 999px;
    background: linear-gradient(
        205deg,
        var(--app-purple-gradient-from) 0%,
        var(--app-purple-gradient-to) 100%
    );
}

.app-link-badge__type {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    font-weight: 700;
    color: var(--app-white);
    white-space: nowrap;
}
.app-link-badge__type > i {
    font-size: 1.15rem;
}

.app-link-badge__status {
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
    padding: 0.2rem 0.6rem;
    border-radius: 999px;
    background: var(--app-white);
    color: var(--app-purple);
    font-size: var(--app-font-size-sm);
    font-weight: 600;
    white-space: nowrap;
}
/* Statut « en attente » (pas encore encodé) : gris atténué. */
.app-link-badge__status--pending {
    color: var(--app-gray-text);
}

/* Badge 2 — boîte blanche, les deux codes CÔTE À CÔTE séparés par une flèche
   grise ; une icône remix + code mono par code. */
.app-link-codes {
    display: inline-flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 0.5rem;
    padding: 0.5rem 0.85rem;
    /* Full radius (pilule) pour être cohérent avec le badge voisin. */
    border-radius: 999px;
    background: var(--app-white);
    border: 1px solid var(--app-purple-border);
    min-width: 0;
}

.app-link-codes__item {
    display: flex;
    align-items: center;
    gap: 0.4rem;
    min-width: 0;
}

/* Flèche de liaison identifiant → code NFC : grise, discrète. */
.app-link-codes__arrow {
    flex-shrink: 0;
    font-size: 1rem;
    color: var(--app-gray-text);
}

.app-link-codes__icon {
    flex-shrink: 0;
    font-size: 1rem;
    color: var(--app-purple);
}

.app-link-codes__code {
    font-family: var(--app-font-mono);
    font-size: var(--app-font-size-xs);
    letter-spacing: 0.03em;
    color: var(--app-gray-700);
    word-break: break-all;
    min-width: 0;
    user-select: all;
}

.app-link-codes__pending {
    font-size: var(--app-font-size-xs);
    font-style: italic;
    color: var(--app-gray-text);
}

/* -- Lock indicators -- */
.app-ball-lock { color: var(--app-danger-text); font-size: 0.9rem; }
.app-ball-unlock { color: var(--app-success-text); font-size: 0.9rem; }

/* -- Locked form: fields disabled overlay -- */
.app-locked-section {
    opacity: 0.55;
    pointer-events: none;
    user-select: none;
}

/* -- Photo -- */
.app-ball-photo-thumb {
    width: 120px;
    height: 120px;
    border-radius: var(--app-radius);
    border: 1px solid var(--app-gray-200);
    object-fit: cover;
    background: var(--app-gray-100);
    flex-shrink: 0;
}

.app-ball-current-photo {
    display: flex;
    flex-direction: column;
    gap: var(--app-spacer-xs);
}

@media (max-width: 768px) {
    .app-ball-photo-thumb {
        width: 90px;
        height: 90px;
    }

    /* -- Ball / Card detail : wrap en mobile -- */
    .app-ball-nfc-detail,
    .app-ball-hash-display {
        flex-wrap: wrap;
    }

    .app-ball-nfc-detail .app-ball-hash,
    .app-ball-hash-display .app-ball-hash {
        min-width: 0;
        overflow: hidden;
        text-overflow: ellipsis;
        white-space: nowrap;
    }

    .app-ball-nfc-detail .app-text-muted {
        flex-basis: 100%;
    }

    .app-ball-nfc-detail .app-inline-form {
        flex-basis: 100%;
        margin-left: 0;
    }

    .app-ball-hash-display a {
        min-width: 0;
        overflow: hidden;
        text-overflow: ellipsis;
        white-space: nowrap;
    }
}

/* ==========================================================
   CTA banner — bandeau d'appel à l'action en pleine largeur
   Utilisé pour orienter l'utilisateur vers une action liée
   (ex. faire une demande, créer une ressource, etc.)
   ========================================================== */
.app-cta-banner {
    display: flex;
    align-items: center;
    gap: var(--app-spacer);
    margin: var(--app-spacer) 0;
    padding: var(--app-spacer);
    border-radius: var(--app-radius);
    text-decoration: none;
    transition: opacity 0.15s ease, transform 0.15s ease;
    /* Au-dessus des éléments qui débordent vers le haut (ex : pile de cartes
       des boosters événements, remontée en marge négative). */
    position: relative;
    z-index: 1;
}

/* Mobile : marges verticales réduites pour gagner de la place. */
@media (max-width: 768px) {
    .app-cta-banner {
        margin: var(--app-spacer-sm) 0;
    }
}

.app-cta-banner:hover {
    opacity: 0.88;
    transform: translateX(2px);
    text-decoration: none;
}

.app-cta-banner > i:first-child {
    font-size: 1.6rem;
    flex-shrink: 0;
}

.app-cta-banner__body {
    flex: 1;
    display: flex;
    flex-direction: column;
    gap: 2px;
}

.app-cta-banner__body strong {
    font-size: var(--app-font-size-base);
    font-weight: 600;
}

.app-cta-banner__body span {
    font-size: var(--app-font-size-sm);
    opacity: 0.85;
}

.app-cta-banner--warning {
    background: var(--app-warning-light);
    color: var(--app-warning-text);
}
.app-cta-banner--warning:hover { color: var(--app-warning-text); }

.app-cta-banner--primary {
    background: var(--app-primary-light);
    color: var(--app-success-text);
}
.app-cta-banner--primary:hover { color: var(--app-success-text); }

.app-cta-banner--info {
    background: var(--app-info-light);
    color: var(--app-info);
}
.app-cta-banner--info:hover { color: var(--app-info); }

.app-cta-banner--neutral {
    background: var(--app-gray-100);
    color: var(--app-gray-700);
}
.app-cta-banner--neutral:hover { color: var(--app-gray-700); }

/* Variante violet — couleur sémantique « Récompense ». Pour les CTAs banner
   liés à l'achat / catalogue. */
.app-cta-banner--purple {
    background: var(--app-purple-light);
    color: var(--app-purple);
}
.app-cta-banner--purple:hover { color: var(--app-purple); }

/* Variante blanche — notice neutre (fond blanc + bordure), pour les états
   vides type « carte » qui doivent rester sobres. */
.app-cta-banner--white {
    background: var(--app-white);
    color: var(--app-gray-700);
}
.app-cta-banner--white:hover { color: var(--app-gray-700); }

/* Bannière servant de déclencheur (div/bouton) : curseur pointeur. */
.app-cta-banner[data-app-modal-open] { cursor: pointer; }

/* ==========================================================
   Info cards — pédagogie utilisateur (zone d'explication)
   Pattern : 1 à 3 cartes côte à côte avec badge + titre + texte.
   Rendu via le partial shared/info-cards.php.
   Variants : neutral (gris), warning (jaune), info (bleu),
              success (vert), danger (rouge).
   ========================================================== */
.app-info-cards {
    display: grid;
    gap: var(--app-spacer);
    margin-top: var(--app-spacer-lg);
}

.app-info-cards--cols-1 { grid-template-columns: 1fr; }
.app-info-cards--cols-2 { grid-template-columns: 1fr 1fr; }
.app-info-cards--cols-3 { grid-template-columns: repeat(3, 1fr); }

.app-info-cards__card {
    background: var(--app-gray-100);
    border-radius: var(--app-radius);
    padding: var(--app-spacer);
}

.app-info-cards__card--neutral { background: var(--app-gray-100); }
.app-info-cards__card--warning { background: var(--app-warning-light); }
.app-info-cards__card--info    { background: var(--app-info-light); }
.app-info-cards__card--success { background: var(--app-success-light); }
.app-info-cards__card--danger  { background: var(--app-danger-light); }
/* Variants sémantiques zone Récompense (violet).
   Primary alias success pour l'ergonomie côté PHP. */
.app-info-cards__card--purple  { background: var(--app-purple-light); }
.app-info-cards__card--primary { background: var(--app-primary-light); }

.app-info-cards__header {
    display: flex;
    align-items: center;
    gap: var(--app-spacer-sm);
    margin-bottom: var(--app-spacer-sm);
    flex-wrap: wrap;
}

.app-info-cards__header h4 {
    margin: 0;
    font-size: var(--app-font-size-md);
    font-weight: 600;
}

.app-info-cards__card p {
    margin: 0;
    color: var(--app-gray-600);
    font-size: var(--app-font-size-sm);
    line-height: 1.5;
}

/* CTA optionnel placé sous le texte (ex : « Demander un officiel »,
   « Acheter des récompenses »). */
.app-info-cards__cta {
    margin-top: var(--app-spacer-sm);
}

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

/* -- Club link in lists -- */
.app-list__club-link {
    color: var(--app-dark);
    text-decoration: none;
}

/* Hover neutre : pas d'underline (ni vert).
   L'affordance cliquable est portée par le cursor:pointer (hérité de <a>)
   et par le contexte tableau/listing (rangées hoverables). */
.app-list__club-link:hover {
    color: var(--app-dark);
    text-decoration: none;
}

/* -- Tabs -- */
.app-tabs {
    display: flex;
    gap: 0;
    border-bottom: 2px solid var(--app-gray-200);
    /* Une rangée régulière (16/10) : les libellés ne passent plus à la ligne (« En
       préparation » sur trois lignes à 1440), la rangée défile si elle déborde. */
    max-width: 100%;
    overflow-x: auto;
    overflow-y: hidden;
    scrollbar-width: none;
}
.app-tabs::-webkit-scrollbar { display: none; }

.app-tabs__btn {
    flex: 1;
    white-space: nowrap;
    padding: 0.625rem 1rem;
    font-size: var(--app-font-size-sm);
    font-weight: 500;
    color: var(--app-gray-text);
    background: none;
    border: none;
    border-bottom: 2px solid transparent;
    margin-bottom: -2px;
    cursor: pointer;
    transition: all var(--app-transition);
    text-align: center;
    /* Pas d'underline héritée du défaut <a>. */
    text-decoration: none;
}

/* Icône d'onglet : espacement canonique (source unique — remplace les règles
   scopées events/cards/rewards). S'applique à TOUS les groupes d'onglets. */
.app-tabs__btn i {
    margin-right: 0.4rem;
}

/* Onglet « Certifié » : pastille orange pleine + glyphe blanc sur l'icône de
   tête. Canonique pour tous les contextes d'onglets (events, rangs…). */
.app-tabs__btn i.app-events-tabs__certif-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 1.5em;
    height: 1.5em;
    border-radius: 50%;
    background: var(--app-warning);
    color: var(--app-white);
    font-size: 0.85em;
}

/* Tabs : couleur neutre (dark) pour l'état actif.
   La sémantique couleur (vert/violet/orange) reste réservée aux CTAs et
   éléments d'identité ; un onglet sélectionné ne doit pas "attribuer" une
   couleur métier à la liste en dessous. */
.app-tabs__btn:hover {
    color: var(--app-dark);
}

.app-tabs__btn.is-active {
    color: var(--app-dark);
    border-bottom-color: var(--app-dark);
}

/* Badges (count) à l'intérieur d'un tab → fond blanc neutre pour ne pas
   attribuer une couleur métier à l'onglet.
   Override les classes app-badge--light / --warning / etc. qui auraient
   pu être passées en PHP. */
.app-tabs__btn .app-badge {
    background: var(--app-white);
    color: var(--app-gray-700);
    border: 1px solid var(--app-gray-200);
}

/* Responsive : sur mobile, on bascule en scroll horizontal — chaque tab
   garde sa largeur naturelle pour rester lisible, et l'utilisateur swipe
   pour accéder aux tabs en débord. Scrollbar cachée visuellement.
   `max-width: 100%` + `min-width: 0` empêchent le conteneur de pousser
   la page en largeur quand son contenu déborde. */
@media (max-width: 768px) {
    .app-tabs {
        flex-wrap: nowrap;
        overflow-x: auto;
        overflow-y: hidden;
        -webkit-overflow-scrolling: touch;
        scrollbar-width: none;
        scroll-snap-type: x proximity;
        max-width: 100%;
        min-width: 0;
    }
    .app-tabs::-webkit-scrollbar {
        display: none;
    }
    .app-tabs__btn {
        flex: 0 0 auto;
        white-space: nowrap;
        scroll-snap-align: start;
        padding: 0.5rem 0.75rem;
    }
}

/* Tablette : on garde les onglets pleine largeur (flex:1), mais on resserre le
   padding horizontal pour éviter que des libellés longs forcent un débordement. */
@media (min-width: 769px) and (max-width: 1024px) {
    .app-tabs__btn {
        padding: 0.6rem 0.85rem;
    }
}

.app-tab-panel {
    display: none;
}

.app-tab-panel.is-active {
    display: block;
}

/* -- Switch row (toggle + label + hint) -- */
.app-switch-row .form-check.form-switch {
    display: flex;
    align-items: center;
    gap: var(--app-spacer-sm);
}

.app-switch-row .form-check-label {
    display: flex;
    flex-direction: column;
    gap: 0.125rem;
}

/* -- Bootstrap switch/checkbox color overrides — dark.
   Sélecteur multi-classes utilisé pour forcer la spécificité au-dessus
   de la règle Tabler `.form-switch .form-check-input:checked`. -- */
.form-check-input:checked,
.form-switch .form-check-input:checked,
.form-check .form-check-input:checked {
    background-color: var(--app-dark);
    border-color: var(--app-dark);
}

/* Focus form-check neutre — pas de vert sur les forms. */
.form-check-input:focus {
    border-color: var(--app-gray-700);
    box-shadow: 0 0 0 0.25rem rgba(26, 26, 26, 0.12);
}

.app-switch-row__hint {
    display: block;
    font-weight: 400;
}

/* -- Scan empty & load more -- */
.app-scan-empty {
    text-align: center;
    padding: var(--app-spacer-xl) var(--app-spacer);
    color: var(--app-gray-text);
}

.app-scan-empty i {
    font-size: 2.5rem;
    display: block;
    margin-bottom: var(--app-spacer-sm);
}

.app-scan-empty p {
    font-size: var(--app-font-size-sm);
    margin: 0;
}

.app-scan-more {
    text-align: center;
    font-size: var(--app-font-size-xs);
    color: var(--app-gray-text);
    padding: var(--app-spacer-sm);
    border-top: 1px solid var(--app-gray-100);
}

/* ==========================================================
   Shared club patterns (admin + client)
   ========================================================== */

/* -- Color dots -- */
.app-club-colors {
    display: inline-flex;
    align-items: center;
    gap: var(--app-spacer-xs);
}

.app-color-dot {
    display: inline-block;
    width: 10px;
    height: 10px;
    border-radius: 50%;
    border: 1px solid rgba(0, 0, 0, 0.08);
    flex-shrink: 0;
    /* Permet de passer la couleur via la CSS variable --event-color
       (l'attribut style="background: ..." inline reste prioritaire) */
    background: var(--event-color, transparent);
}

/* Cellule vide de tableau : petite croix rouge atténuée — remplace le « — »
   (partial shared/empty-cell). « light » = rouge danger à opacité réduite. */
.app-empty-cell {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    color: var(--app-danger-text);
    opacity: 0.45;
    font-size: 0.95rem;
    line-height: 1;
}

/* Variante inline (un peu plus grosse) — utilisée avant OU après un label
   dans les listes d'items, le tableau de variations, etc. Déplacée ici
   depuis rewards.css pour être disponible sur toutes les pages. */
.app-color-dot--inline {
    display: inline-block;
    width: 14px;
    height: 14px;
    border-radius: 50%;
    border: 1px solid var(--app-gray-300);
    margin-inline: 0 0.4rem;
    vertical-align: middle;
}

/* ==========================================================
   Avatar list — avatars empilés façon Tabler (partial shared/avatar-list)
   ========================================================== */
.app-avatar-list {
    display: inline-flex;
    align-items: center;
}

.app-avatar-list__item {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 36px;
    height: 36px;
    border-radius: var(--app-radius-sm);
    background: var(--app-gray-100);
    border: 1px solid var(--app-gray-200);
    overflow: hidden;
    flex-shrink: 0;
    font-size: 0.8rem;
    font-weight: 600;
    color: var(--app-gray-600);
    line-height: 1;
    transition: transform 0.12s ease;
}

/* Chevauchement : chaque carré mord sur le précédent + ombre portée vers la
   GAUCHE (il « flotte » au-dessus du précédent). Pas d'ombre sur le 1er. */
.app-avatar-list__item:not(:first-child) {
    margin-left: -10px;
    box-shadow: -3px 0 5px -2px rgba(0, 0, 0, 0.22);
}

/* Au survol, l'avatar passe devant et se décolle légèrement. */
.app-avatar-list__item:hover {
    transform: translateY(-2px);
    z-index: 2;
}

.app-avatar-list__item img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
}

/* Pastille « +N » : juste bordure + fond blanc (pas de remplissage gris). */
.app-avatar-list__more {
    background: var(--app-white);
    color: var(--app-gray-700);
}

/* Variante compacte. */
.app-avatar-list--sm .app-avatar-list__item {
    width: 28px;
    height: 28px;
    font-size: 0.65rem;
}
.app-avatar-list--sm .app-avatar-list__item:not(:first-child) {
    margin-left: -8px;
}

/* ==========================================================
   Cellule « Utilisation » — badges icone+chiffre (partial shared/usage-cell)
   ========================================================== */
.app-usage-cell {
    display: inline-flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 6px;
}
.app-usage-cell .app-badge i {
    margin-right: 2px;
}

/* Empilement generique de badges dans une cellule de liste (ex. role + statut). */
.app-cell-badges {
    display: inline-flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 4px;
}

/* -- Logo thumbnail -- */
.app-club-logo-thumb {
    width: 120px;
    height: 120px;
    border-radius: var(--app-radius);
    border: 1px solid var(--app-gray-200);
    /* Le logo entier, sur fond neutre (16/10) : recadré en carré, un logo paysage
       ne montrait plus que son milieu (audit du 15/10). */
    object-fit: contain;
    background: var(--app-gray-100);
    flex-shrink: 0;
}

.app-club-current-logo {
    display: flex;
    flex-direction: column;
    gap: var(--app-spacer-xs);
}

@media (max-width: 768px) {
    .app-club-logo-thumb {
        width: 90px;
        height: 90px;
    }
}

/* ==========================================================
   Reset font-weight sur icônes & SVG
   ----------------------------------------------------------
   Remix Icons est une icon-font livrée en weight 400 uniquement.
   Lorsqu'elle hérite d'un parent en `font-weight: 600/700` (titre,
   bouton primary, badge…), le navigateur faux-bold le glyphe ce
   qui produit un rendu épaissi inesthétique.
   On force `font-weight: 400` sur :
     - tout <i> dont la classe commence par `ri-` (Remix Icons)
     - tout <svg> au cas où certains hériteraient (sécurité belt-and-braces,
       même si `font-weight` n'affecte que les <text> internes du SVG)
   `!important` est nécessaire pour battre l'héritage des composants
   comme `.app-step__title { font-weight: 600 }` ou `.app-btn`.
   ========================================================== */
i[class^="ri-"],
i[class*=" ri-"],
svg {
    font-weight: 400 !important;
}

/* -- Debug (decommenter en developpement) --
* { outline: 1px solid rgba(255, 0, 0, 0.15); }
*:hover { outline: 1px solid rgba(255, 0, 0, 0.4); }
*/

/* ─── Panneaux d'onglets JS — réutilisent la barre existante .app-tabs +
   .app-tabs__btn (ci-dessus). Markup : wrapper [data-app-tabs], boutons
   .app-tabs__btn[data-tab], panneaux .app-tabs__panel[data-tab-panel].
   Toggle .is-active géré dans app.js. ─── */
.app-tabs__panel { display: none; padding-top: 16px; }
.app-tabs__panel.is-active { display: block; }

.app-badge__icon-img {
    width: 14px;
    height: 14px;
    object-fit: contain;
    vertical-align: -2px;
}

/* ─── Formulaire de déconnexion (POST + CSRF) — `display: contents` efface
   la boîte du <form> pour que le <button> se comporte exactement comme
   l'ancien lien dans son conteneur (sidebar, footer de modale). ─── */
.app-logout-form { display: contents; }

/* Un tableau large défile dans sa carte au lieu d'être rogné (16/10, audit du 15/10). */
.app-table-defile { max-width: 100%; overflow-x: auto; -webkit-overflow-scrolling: touch; }

/* Le focus clavier se voit sur les boutons (16/10, audit du 15/10) : Tabler et la règle
   des boutons d'action le retiraient (outline 0, box-shadow none !important). */
.btn-action:focus-visible,
.app-btn:focus-visible,
.app-btn-action:focus-visible {
    outline: 2px solid var(--app-dark);
    outline-offset: 2px;
}

/* Une rangée qui défile le dit : app.js pose `is-defile-gauche` / `is-defile-droite`
   quand il reste du contenu de ce côté, le bord s'estompe (16/10, audit du 15/10). */
.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); }
.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); }
.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); }

/* LES LISTES LARGES DÉFILENT AU LIEU DE SE CHEVAUCHER (16/10, audit du 15/10).
   Entre 769 et 1200 px, la barre latérale reste ouverte : la zone de contenu tombe
   vers 700 px et une liste de 7 à 9 colonnes se chevauchait, sa colonne d'actions
   sortant de la boîte (invisible). La liste garde sa largeur utile et défile de côté. */
@media (min-width: 769px) and (max-width: 1200px) {
    .app-list.app-list--cards,
    .app-list.app-list--rewards-admin,
    .app-list.app-list--admin-reward-requests,
    .app-list.app-list--event-requests,
    .app-list.app-list--unlock-requests {
        overflow-x: auto;
    }
    .app-list.app-list--cards > .app-list__header,
    .app-list.app-list--cards > .app-list__row,
    .app-list.app-list--rewards-admin > .app-list__header,
    .app-list.app-list--rewards-admin > .app-list__row,
    .app-list.app-list--admin-reward-requests > .app-list__header,
    .app-list.app-list--admin-reward-requests > .app-list__row,
    .app-list.app-list--event-requests > .app-list__header,
    .app-list.app-list--event-requests > .app-list__row,
    .app-list.app-list--unlock-requests > .app-list__header,
    .app-list.app-list--unlock-requests > .app-list__row {
        min-width: 980px;
    }
}

/* Bannières d'appel teintées : le filet de leur teinte hors d'une carte blanche, c'est-à-dire
   sur le fond gris de la page (règle du 25/06, audit du 15/10). Dans une carte, pas de filet. */
.app-cta-banner { border: 1px solid transparent; }
.app-cta-banner--warning { border-color: var(--app-warning-border); }
.app-cta-banner--primary { border-color: var(--app-primary-border); }
.app-cta-banner--info    { border-color: var(--app-info-border); }
.app-cta-banner--purple  { border-color: var(--app-purple-border); }
.app-card .app-cta-banner { border-color: transparent; }
