/* Système d'animations partagé pour les pages statiques de l'app mobile (frontend/www).
   Volontairement en CSS pur hors pipeline Tailwind : aucune dépendance de build, il suffit
   de le lier après tailwind-output.css sur chaque page. */

/* Courbe "spring" douce (ease-out-expo) : montée rapide puis amortie en fin de course,
   c'est ce qui donne la sensation "app native" plutôt qu'un simple fondu web plat. */
:root {
    --emarkt-ease-out: cubic-bezier(0.22, 1, 0.36, 1);
    --emarkt-ease-in: cubic-bezier(0.55, 0, 0.85, 0.25);
}

/* ------------------------------------------------------------------ */
/* Marges des barres système Android                                   */
/* ------------------------------------------------------------------ */
/* --emarkt-inset-top / --emarkt-inset-bottom sont injectées par le code natif
   (MainActivity.applyInsetsToWebView) avec les VRAIES mesures de l'appareil. C'est ce
   qui permet à l'app de s'adapter toute seule aux deux modes de navigation Android :
   navigation gestuelle (fine barre d'environ 24dp) et navigation à 3 boutons (barre
   d'environ 48dp). env(safe-area-inset-*) ne peut pas servir seul ici : dans une WebView
   Android il ne renseigne que les encoches d'écran, jamais la barre de navigation du bas
   — il ne reste donc qu'en repli pour la version web/navigateur. */
.safe-pt {
    padding-top: max(1rem, calc(var(--emarkt-inset-top, env(safe-area-inset-top)) + 0.7rem));
}

.safe-pb {
    padding-bottom: max(0.5rem, calc(var(--emarkt-inset-bottom, env(safe-area-inset-bottom)) + 0.35rem));
}

/* Pages plein écran au contenu centré (écrans d'authentification). Remplace le py-*
   Tailwind d'origine plutôt que de s'y ajouter : deux règles de padding sur le même
   élément se seraient écrasées l'une l'autre au lieu de se cumuler. */
.safe-main {
    padding-top: calc(2rem + var(--emarkt-inset-top, env(safe-area-inset-top)));
    padding-bottom: calc(2rem + var(--emarkt-inset-bottom, env(safe-area-inset-bottom)));
}

/* ⚠ LE PADDING NE SUFFIT PAS DÈS QUE L'ÉCRAN DÉFILE (recette du 2026-09-28). L'application est
   bord à bord : la barre d'état est transparente, et le texte qui remonte (« Saisissez le code
   reçu », clavier ouvert) passait sous l'heure et les icônes. Ces écrans n'ont pas d'en-tête
   collant qui la couvre : un voile de la couleur du fond la tient propre. Hauteur nulle sur le
   site (aucune marge système dans un navigateur).

   ⚠ IL N'APPARAÎT QU'EN DÉFILANT. Posé en permanence, il traçait une bande crème sur le dégradé
   rose du haut de l'écran. Il suit donc le défilement de la page (24 premiers pixels) ; une
   WebView trop ancienne pour le suivre n'a pas de voile, comme avant. */
@supports (animation-timeline: scroll()) {
    body:has(.safe-main)::before {
        content: '';
        position: fixed;
        top: 0;
        left: 0;
        right: 0;
        height: var(--emarkt-inset-top, env(safe-area-inset-top, 0px));
        background: #FFF8F0;
        z-index: 70;
        pointer-events: none;
        animation: voile-barre-etat linear both;
        animation-timeline: scroll(root);
        animation-range: 0 24px;
    }

    @keyframes voile-barre-etat {
        from { opacity: 0; }
        to { opacity: 1; }
    }
}

/* ------------------------------------------------------------------ */
/* Retour tactile à l'appui                                            */
/* ------------------------------------------------------------------ */
/* Piloté par le JS (ui-feedback.js), qui pose .is-pressed dès touchstart, et NON par la
   pseudo-classe CSS :active. Raison : sur Android, le navigateur retarde volontairement
   :active de 100 à 300ms, le temps de décider si le doigt est en train d'appuyer ou de
   commencer un défilement. Sur un appui bref — c'est-à-dire l'appui normal — l'effet
   n'apparaissait donc jamais, ce qui donnait des boutons "morts" au toucher.
   :active reste en repli pour la souris sur navigateur, où ce délai n'existe pas. */
button:not(:disabled),
input[type="submit"]:not(:disabled),
input[type="button"]:not(:disabled),
a.tap-shrink,
.tap-shrink {
    transition: transform 0.11s ease-out, filter 0.11s ease-out;
    -webkit-tap-highlight-color: transparent;
}

/* Règle autonome, sans liste de sélecteurs : c'est le JS qui décide déjà quels éléments
   sont "appuyables" (boutons, liens-cartes, liens-boutons...). L'ancienne version listait
   les sélecteurs ici AUSSI, et tout élément posé par le JS mais absent de la liste — les
   grandes cartes-liens de la page de choix d'inscription, par exemple — recevait bien la
   classe sans qu'aucun style ne s'applique : un retour tactile silencieusement mort.
   !important : prime sur les transforms de survol (hover-lift, etc.). */
.is-pressed {
    /* ⚠ L'ECHELLE EST POSEE PAR `ui-feedback.js`, EN FONCTION DE LA TAILLE (2026-09-18).
       Un pourcentage fixe ne produit pas le meme geste selon la surface : a 0,95, le bord
       d'un bouton de 52 px bouge de 1 px, celui d'une carte de banniere de 950 px bouge de
       24 px -- l'un se lit comme un appui, l'autre comme une carte qui saute.
       Le repli de 0,95 est le comportement d'avant : sans la variable, rien ne casse. */
    transform: scale(var(--presse-echelle, 0.95)) !important;
    filter: brightness(0.92);
    transition: transform 0.1s ease-out, filter 0.1s ease-out;
}

button:not(:disabled):active,
input[type="submit"]:not(:disabled):active,
input[type="button"]:not(:disabled):active,
.tap-shrink:active {
    transform: scale(0.95);
    filter: brightness(0.92);
}

/* Entrée de page : montée douce + fondu (convention Android native pour l'ouverture d'un
   écran) — ni le simple fondu "page web classique", ni l'ancien glissement horizontal.

   Deux choix techniques importants ici :
   1. Le déplacement est COURT (16px) et rapide : c'est le grand trajet de l'ancien
      glissement (28px sur toute la durée) qui rendait texte et images visiblement flous,
      la WebView rééchantillonnant la page à des positions non entières pendant tout le
      mouvement. Sur 16px en 0,22s, ce rééchantillonnage est imperceptible.
   2. L'animation s'applique aux ENFANTS de <body>, jamais à <body> lui-même : un
      transform sur <body> change le repère de tous ses descendants position:fixed (barre
      de navigation du bas, overlay hors-ligne), qui se retrouvaient positionnés par
      rapport au document au lieu de l'écran pendant — et après — l'animation. Les
      éléments fixed, eux, entrent en fondu sur place. Dernière étape transform:none :
      une fois l'animation finie, plus aucun transform ne reste appliqué. */
@keyframes emarkt-page-enter {
    from { opacity: 0; transform: translate3d(0, 16px, 0); }
    to { opacity: 1; transform: none; }
}

@keyframes emarkt-fade-in {
    from { opacity: 0; }
    to { opacity: 1; }
}

/*  ⚠ `backwards` ET NON `both` (2026-09-16). Avec `both`, l'animation garde son
    DERNIER état pour toujours -- donc `opacity: 1` -- et une valeur animée l'emporte
    sur toute déclaration normale de la feuille de style. Un élément enfant de <body>
    qui gère lui-même sa visibilité par `opacity: 0` ne pouvait donc plus jamais se
    cacher.

    C'est ce qui rendait « Ma boutique » et « Mon compte » flous dans
    l'application : `phone-input.js` pose en fin de <body> son voile
    `#phone-dropdown-overlay` (`opacity: 0`, `backdrop-filter: blur(2px)`) et son
    infobulle `#custom-tooltip`. Les deux restaient affichés en permanence -- le voile
    sombre et flouté couvrait l'écran entier. Le défaut était indolore au toucher
    (`pointer-events: none` n'est pas animé, lui), donc rien ne le trahissait sinon
    l'image.

    `backwards` suffit : il tient l'état de départ AVANT le départ (c'est ce dont
    `html.fonts-pending` a besoin), et à l'arrivée chaque élément reprend SA propre
    déclaration. Pour tout le contenu de page, cette déclaration vaut déjà
    `opacity: 1` / `transform: none` : rien ne change à l'œil.

    ⚠ `.fixed` est la CLASSE Tailwind, pas `position: fixed`. Un élément fixé par une
    règle d'identifiant -- les deux ci-dessus -- ne la porte pas et tombe dans la
    première règle. Ne pas s'y fier pour exclure quoi que ce soit. */
body > *:not(.fixed) {
    animation: emarkt-page-enter 0.22s var(--emarkt-ease-out) backwards;
}

body > .fixed {
    animation: emarkt-fade-in 0.18s ease-out backwards;
}

/*  ⚠ ET PAS DE FONDU DU TOUT POUR CE QUI GÈRE SA PROPRE VISIBILITÉ (2026-09-28). Même avec
    `backwards`, l'opacité animée l'emporte PENDANT l'animation : le voile sombre et flouté de
    `phone-input.js` passait de 0 à 1 pendant 220 ms à chaque arrivée sur un écran à champ
    téléphone -- le « film noir » signalé en recette sur « Ma boutique » et « Mon compte »
    (mesuré image par image : luminosité de 240 à 167 pendant un tiers de seconde).
    Un élément posé dans <body> et invisible au repos (voile, infobulle, indicateur de
    défilement) porte `data-sans-entree`. */
body > [data-sans-entree] {
    animation: none;
}

/* Tant que les polices ne sont pas prêtes (classe posée puis retirée par ui-feedback.js,
   quelques millisecondes — elles sont locales) : rien n'est montré et les animations
   d'entrée restent en pause à leur première image. Au retrait de la classe, la page
   apparaît en une fois avec ses dimensions définitives — c'est ce qui élimine le flash
   d'élargissement du texte au changement de page. */
html.fonts-pending body > * {
    visibility: hidden;
    animation-play-state: paused;
}

/* ------------------------------------------------------------------ */
/* LA BARRE D'ONGLETS DU BAS NE DISPARAIT JAMAIS                       */
/* ------------------------------------------------------------------ */
/*  ⚠ ELLE EST EXEMPTEE DES DEUX REGLES CI-DESSUS, ET C'EST DELIBERE.
    Le proprietaire a signale qu'elle « disparait un petit instant » a chaque changement
    de page. Ce n'etait pas un defaut de chargement : mesure faite dans la WebView, le
    document est pret a 275 ms mais rien n'est peint avant 817 ms. La cause est ici :
    `html.fonts-pending body > *` met TOUTE la page en `visibility: hidden` jusqu'a ce que
    les polices soient pretes, puis `body > .fixed` la fait entrer en fondu. La barre,
    etant un enfant de <body>, y passait comme le reste -- filmee image par image, on la
    voit bien s'effacer puis revenir en translucide.

    Or la barre n'est pas du contenu de page : c'est le CADRE de l'application, le seul
    element commun aux onze ecrans. Un cadre qui clignote a chaque navigation fait douter
    de l'appui qu'on vient de faire.

    Elle apparait donc des la premiere image, avec sa carte, son ombre et sa pastille. Les
    icones, elles, restent tributaires de leur police (`font-display: block` dans
    `fonts/remixicon/remixicon.css`) : elles arrivent quelques dizaines de millisecondes
    plus tard, sur une barre deja en place -- au lieu que la barre entiere s'absente.

    Specificite : `html body [data-barre-onglets]` (0,2,1) l'emporte sur
    `html.fonts-pending body > *` (0,1,1) comme sur `body > .fixed` (0,1,1). */
html body [data-barre-onglets] {
    visibility: visible;
    animation: none;
}

/*  L'ONGLET EVEILLE : l'icone se souleve de 2 px.
    C'est le troisieme etage de l'etat actif, avec le faisceau lumineux du bord haut et le halo
    qui descend derriere l'icone (tous deux poses par `barre-onglets.js`). Un seul de ces trois
    signes suffirait a designer l'onglet ; les trois ensemble le font EXISTER -- c'est la
    difference entre un onglet marque et un onglet vivant.

    ⚠ LE SOULEVEMENT EST DE 2 px, PAS DAVANTAGE. Au-dela, le libelle se rapproche de l'icone au
    point de paraitre colle, et la WebView reechantillonne le glyphe a une position non entiere :
    l'icone devient floue pendant tout le mouvement. */
[data-onglet] i {
    transition: transform .22s var(--emarkt-ease-out), color .18s ease-out;
}
[data-onglet-eveille] i {
    transform: translateY(-2px);
}


/* ------------------------------------------------------------------ */
/* Bouton en cours de traitement (posé par ui-feedback.js)             */
/* ------------------------------------------------------------------ */
/* Grisé + curseur d'attente + plus aucun effet d'appui : l'état doit se lire d'un coup
   d'œil comme "en cours, ne ré-appuyez pas". Le contenu (icône qui tourne + texte) est
   géré en JS, pas ici. */
.is-loading {
    opacity: 0.65;
    cursor: wait !important;
    pointer-events: none;
    transform: none !important;
    filter: none !important;
}

@keyframes emarkt-step-enter {
    from { opacity: 0; transform: translateX(16px); }
    to { opacity: 1; transform: translateX(0); }
}

/* Ajouté/retiré par window.eMarktAnimateStep() à chaque changement d'étape des assistants
   d'inscription (register/client.html, register/shop.html). */
.step-enter {
    animation: emarkt-step-enter 0.2s var(--emarkt-ease-out) both;
}

/* ------------------------------------------------------------------ */
/* Skeleton screens — effet "shimmer" (balayage lumineux diagonal),   */
/* remplace l'ancien animate-pulse (opacité plate) de Tailwind.       */
/* Usage : mettre .skeleton-shimmer sur chaque bloc au lieu de bg-*.  */
/* ------------------------------------------------------------------ */
.skeleton-shimmer {
    position: relative;
    overflow: hidden;
    background: rgba(39, 27, 18, 0.08);
}

.skeleton-shimmer::after {
    content: '';
    position: absolute;
    inset: 0;
    transform: translateX(-100%);
    background: linear-gradient(100deg, transparent 30%, rgba(255, 255, 255, 0.65) 50%, transparent 70%);
    animation: emarkt-shimmer 1.6s ease-in-out infinite;
}

@keyframes emarkt-shimmer {
    100% { transform: translateX(100%); }
}

/* Le splash est désormais entièrement natif (écran de démarrage du système, maintenu
   affiché par MainActivity jusqu'à ce que la page soit prête) : plus aucun splash HTML,
   donc plus aucun style de splash ici. */

/* Grain photographique décoratif du site web (fine texture de bruit posée par-dessus
   toute la page) : désactivé dans l'app. Sur l'écran du téléphone il ne se lit pas
   comme une texture subtile mais comme un filet de petits points à travers lequel on
   voit le contenu — signalé comme gênant sur toutes les pages. Style vitrine web,
   pas application mobile. */
.grain-overlay {
    display: none !important;
}

/* Vidéo de bannière : fondu doux entre le fond placeholder (couleur/LQIP) et la vidéo une
   fois chargée -- évite le "pop" brutal au moment où la vidéo apparaît par-dessus. */
/* ═══ LE BOUTON DE LECTURE QUI APPARAISSAIT SUR LES BANNIÈRES VIDÉO ══════════════════════
   Signalé le 2026-09-05 : « il arrive souvent qu'il y ait un bouton start en triangle entouré
   d'un cercle, avec un fond blanc, que ce soit dans la modale ou sur la carte de la bannière ».

   Ce n'est pas un élément du projet : c'est le contrôle de lecture NATIF que Chrome et la
   WebView Android posent d'eux-mêmes sur toute vidéo qui n'est pas en train de jouer. Il
   apparaît donc dans trois cas parfaitement ordinaires ici :

     · pendant que la source se charge — le carrousel ne garde qu'UNE vidéo en mémoire à la
       fois (mesure sur appareil : douze diapositives = 719 Mo, application tuée par Android),
       donc chaque changement de bannière retire une source et en pose une autre ;
     · quand `play()` est refusé par le navigateur, ce qui arrive et que le code absorbe en
       silence — à raison : une vidéo de décor qui ne démarre pas ne doit pas casser la page ;
     · à l'ouverture de la fiche, le temps que la vidéo de la scène s'amorce.

   ⚠ CES VIDÉOS SONT UN DÉCOR, PAS UN LECTEUR. Elles n'ont ni `controls`, ni son, ni barre de
   progression : personne n'est censé les mettre en pause. Un bouton de lecture posé dessus par
   le système promet une commande qui n'existe pas, et masque la bannière au passage.

   ⚠ ET SEULEMENT CELLES-LÀ. La visionneuse plein écran (`#mediaViewerVideo`, Video.js) garde
   son gros bouton central : là, on regarde vraiment une vidéo, et le bouton est voulu. */
.banner-bg-video::-webkit-media-controls,
.banner-bg-video::-webkit-media-controls-start-playback-button,
.banner-bg-video::-webkit-media-controls-overlay-play-button,
.banner-bg-video::-webkit-media-controls-panel,
.banner-stage-media::-webkit-media-controls,
.banner-stage-media::-webkit-media-controls-start-playback-button,
.banner-stage-media::-webkit-media-controls-overlay-play-button,
.banner-stage-media::-webkit-media-controls-panel {
    display: none !important;
    -webkit-appearance: none;
}

.banner-bg-video {
    opacity: 0;
    transition: opacity 0.6s ease;
}

.banner-bg-video.is-loaded {
    opacity: 1;
}

/* ------------------------------------------------------------------ */
/* Fiche détail de bannière (bottom sheet) : ouverture/fermeture réelles */
/* ------------------------------------------------------------------ */
/* La sheet part hors-écran (translateY 100%) et le fond est transparent ;
   #bannerDetailModal.is-open fait glisser/apparaître les deux -- posé et retiré en JS pour
   que la fermeture rejoue la même transition en sens inverse au lieu de disparaître d'un coup.
   Fond animé via background-color (pas opacity) + couche de composition dédiée
   (translateZ(0)) : faire varier l'opacité d'un élément qui a aussi backdrop-filter force le
   navigateur à recomposer le flou à chaque frame en même temps que la sheet voisine anime son
   transform -- sur Chrome/WebView ça produit des artefacts visuels (lignes/traînées) le temps de
   la transition. Idem côté sheet : sa propre couche isolée évite que les deux calques interfèrent. */
.banner-modal-backdrop {
    background-color: rgba(39, 27, 18, 0);
    transition: background-color 0.3s ease;
    transform: translateZ(0);
}
.banner-modal-sheet {
    transform: translateY(100%) translateZ(0);
    transition: transform 0.36s var(--emarkt-ease-out, cubic-bezier(0.22, 1, 0.36, 1));
}
#bannerDetailModal.is-open .banner-modal-backdrop {
    background-color: rgba(39, 27, 18, 0.6);
}
#bannerDetailModal.is-open .banner-modal-sheet {
    transform: translateY(0) translateZ(0);
}

/* ------------------------------------------------------------------ */
/* Fiche bannière : géométrie FIXE (scène média + panneau remontant)   */
/* ------------------------------------------------------------------ */
/* Règle non négociable de cette fiche : sa hauteur ne dépend JAMAIS du contenu. Une bannière
   sans sous-titre, sans stats et sans bouton occupe exactement la même place qu'une bannière
   complète -- c'est ce qui donne la sensation d'écran natif au lieu d'une modale qui saute de
   taille à chaque changement. Concrètement : hauteur de sheet figée, scène média figée en
   pourcentage de la sheet, barre d'actions figée, et seule la colonne de texte défile.
   Les couleurs viennent de la bannière elle-même, injectées en variables par le JS
   (applyBannerSheetTheme) : --bs-tint (fond configuré), --bs-accent (couleur d'urgence ou
   orange de marque), --bs-on-tint (encre ou crème, choisi par luminance pour rester lisible). */
.banner-sheet {
    --bs-tint: #271B12;
    --bs-tint-rgb: 39, 27, 18;
    --bs-accent: #FF6B35;
    --bs-accent-rgb: 255, 107, 53;
    --bs-on-tint: #FFF8F0;

    height: 88vh;
    height: min(88svh, 780px);
    max-height: 780px;
}
@media (min-width: 640px) {
    .banner-sheet {
        height: min(84svh, 760px);
        border-radius: 32px;
    }
}

/* Scène média : part fixe de la sheet. Le média y est affiché ENTIER (object-contain) sur un
   fond composé du même média, agrandi et flouté -- aucune image n'est recadrée, et un 21:9
   comme un 4:5 tiennent dans le même cadre sans changer la hauteur de la fiche. */
/* Répartition scène / panneau : STRICTEMENT FIXE. Ni la hauteur de la fiche, ni celle de la
   scène média, ni celle du panneau ne dépendent de la bannière affichée -- une photo 21:9, une
   vidéo verticale, un fond uni ou un dégradé occupent tous exactement le même cadre. Faire
   varier la scène selon le remplissage du texte (essai précédent) rendait bien sur une fiche
   isolée mais cassait l'immersion dès qu'on enchaînait les bannières : le cadre "respirait" à
   chaque changement. Seule la colonne de texte défile. */
.banner-sheet-stage {
    flex: 0 0 40%;
    background-color: var(--bs-tint);
    /* Modelé permanent sous le fond flouté : garantit que la scène reste une composition même
       pour une bannière à fond uni sans média, au lieu d'un rectangle de couleur plate. */
    background-image: radial-gradient(120% 95% at 50% 0%, rgba(255, 255, 255, 0.12) 0%, rgba(0, 0, 0, 0.26) 100%);
}
.banner-stage-backdrop {
    background-size: cover;
    background-position: center;
    transform: scale(1.35);
    filter: blur(30px) saturate(1.45) brightness(0.82);
    opacity: 0.95;
}
.banner-stage-media {
    max-width: 100%;
    max-height: 100%;
    width: auto;
    height: auto;
    object-fit: contain;
    transition: opacity 0.28s ease;
}
.banner-stage-media.is-swapping { opacity: 0; }
/* Halos d'ambiance : rendu du réglage « Flou » sur les bannières à fond uni (sans image ni
   vidéo). C'est le même traitement que sur la carte du carrousel -- la fiche l'ignorait
   totalement, un fond uni flouté y apparaissait donc comme un aplat mort. */
.banner-stage-glow {
    position: absolute;
    width: 62%;
    padding-bottom: 62%;
    border-radius: 9999px;
    pointer-events: none;
    opacity: 0.55;
}
.banner-stage-vignette {
    height: 45%;
    background-image: linear-gradient(to top, rgba(var(--bs-tint-rgb), 0.85), rgba(var(--bs-tint-rgb), 0));
}

/* Panneau : remonte par-dessus la scène média (marge négative) et porte le lavis de teinte. */
.banner-sheet-panel {
    flex: 1 1 auto;
    min-height: 0;
    margin-top: -26px;
    border-radius: 28px 28px 0 0;
    background-color: #FFF8F0;
    background-image: linear-gradient(180deg, rgba(var(--bs-tint-rgb), 0.09) 0%, rgba(var(--bs-tint-rgb), 0) 190px);
    box-shadow: 0 -14px 34px -18px rgba(39, 27, 18, 0.45);
}

/* Rail de statistiques : le seul bloc plein de la fiche, dans la teinte de la bannière. C'est
   la signature colorée qui rend chaque fiche différente sans casser la lisibilité du texte. */
.banner-stat-rail {
    background-color: var(--bs-tint);
    color: var(--bs-on-tint);
}
.banner-stat-rail .banner-stat-value { color: var(--bs-accent); }
.banner-stat-rail .banner-stat-sep { background-color: currentColor; opacity: 0.18; }

/* Ticket code promo : encoche gauche/droite façon coupon, pas une simple carte arrondie. */
.banner-promo-ticket {
    background-color: rgba(var(--bs-accent-rgb), 0.10);
    border: 1px dashed rgba(var(--bs-accent-rgb), 0.45);
}
.banner-promo-ticket::before,
.banner-promo-ticket::after {
    content: '';
    position: absolute;
    top: 50%;
    width: 16px;
    height: 16px;
    border-radius: 9999px;
    background-color: #FFF8F0;
    transform: translateY(-50%);
}
.banner-promo-ticket::before { left: -9px; }
.banner-promo-ticket::after { right: -9px; }

/* Compte à rebours : même parti pris que le carrousel web, un effet verre -- transposé sur fond
   clair. Le verre est obtenu par un dégradé blanc translucide + bordure claire + reflet interne,
   et NON par le seul backdrop-filter : la convention du projet (.glassmorphism dans input.css)
   coupe backdrop-filter sous 768px, et un WebView Android le rend mal quand un ancêtre anime son
   opacité. Le filtre n'est ajouté qu'au-dessus de cette largeur, en bonus. */
.banner-countdown-cell {
    background-image: linear-gradient(160deg, rgba(255, 255, 255, 0.92) 0%, rgba(255, 255, 255, 0.5) 100%);
    border: 1px solid rgba(255, 255, 255, 0.9);
    box-shadow: 0 10px 22px -14px rgba(39, 27, 18, 0.5), inset 0 1px 0 rgba(255, 255, 255, 0.95);
}
@media (min-width: 769px) {
    .banner-countdown-cell {
        backdrop-filter: blur(12px) saturate(1.4);
        -webkit-backdrop-filter: blur(12px) saturate(1.4);
    }
}

/* Apparition en cascade des lignes de contenu : rejouée à chaque bannière affichée. */
@keyframes bannerRowIn {
    from { opacity: 0; transform: translateY(12px); }
    to   { opacity: 1; transform: translateY(0); }
}
@keyframes bannerRowSlideIn {
    from { transform: translateY(12px); }
    to   { transform: translateY(0); }
}
.banner-row {
    opacity: 0;
    animation: bannerRowIn 0.4s var(--emarkt-ease-out, cubic-bezier(0.22, 1, 0.36, 1)) forwards;
    animation-delay: calc(var(--row-i, 0) * 45ms);
}
/* Variante sans fondu, réservée aux blocs qui contiennent du verre : animer l'opacité d'un
   ancêtre isole la couche et fait rendre le backdrop-filter de travers sur WebView. */
.banner-row.is-glass {
    opacity: 1;
    animation-name: bannerRowSlideIn;
}
@media (prefers-reduced-motion: reduce) {
    .banner-row { animation: none; opacity: 1; }
    .banner-stage-media { transition: none; }
}

/* Visionneuse plein écran (image/vidéo agrandie) */
.banner-media-viewer {
    opacity: 0;
    transition: opacity 0.28s ease;
}
.banner-media-viewer.is-open {
    opacity: 1;
}
.banner-media-viewer-stage {
    transform: scale(0.94);
    transition: transform 0.28s var(--emarkt-ease-out, cubic-bezier(0.22, 1, 0.36, 1));
}
.banner-media-viewer.is-open .banner-media-viewer-stage {
    transform: scale(1);
}

/* ------------------------------------------------------------------ */
/* Thème Video.js aux couleurs eMarkt (surcharge du skin par défaut)   */
/* ------------------------------------------------------------------ */
.vjs-emarkt-theme .vjs-big-play-button {
    background-color: rgba(255, 107, 53, 0.9);
    border-color: #fff;
    border-radius: 50%;
    width: 2.4em;
    height: 2.4em;
    line-height: 2.4em;
}
.vjs-emarkt-theme:hover .vjs-big-play-button,
.vjs-emarkt-theme .vjs-big-play-button:focus {
    background-color: #FF6B35;
}
.vjs-emarkt-theme .vjs-control-bar {
    background: linear-gradient(to top, rgba(20, 14, 9, 0.85), rgba(20, 14, 9, 0));
}
.vjs-emarkt-theme .vjs-play-progress,
.vjs-emarkt-theme .vjs-volume-level {
    background-color: #FF6B35;
}
.vjs-emarkt-theme .vjs-slider {
    background-color: rgba(255, 255, 255, 0.25);
}
.vjs-emarkt-theme .vjs-play-progress::before {
    color: #FF6B35;
}

@media (prefers-reduced-motion: reduce) {
    body > *,
    .step-enter {
        animation: none !important;
    }
    .skeleton-shimmer::after {
        animation-duration: 2.4s;
    }
}
