/* ============================================================================
   merchant.css — Espace commerçant Quaantum
   Dashboard dense assumé : le commerçant vient chercher un chiffre, pas une
   expérience. Palette et typographie reprises du reste de l'app pour ne pas
   dépareiller (#0c0d17 / #1c1e24, ambre #ffc52a, Inter).

   Deux postures, deux coquilles :
    - au bureau (> 900px), sidebar fixe, densité maximale ;
    - au comptoir (<= 900px), barre de titre en haut + barre d'onglets en bas,
      atteignable au pouce. Les tableaux deviennent des fiches, les KPI passent
      sur deux colonnes, l'action principale d'un formulaire colle au bas de
      l'écran. Tout est calé sur env(safe-area-inset-*) pour les iPhone à
      encoche et barre gestuelle.
   ========================================================================== */

:root {
    --bg: #08090f;
    --surface: #12131c;
    --surface-2: #1a1c26;
    --line: #2a2d3a;
    --line-strong: #3a3f47;

    --text: #f2f3f7;
    --muted: #a2a8bd;

    --accent: #ffc52a;
    --accent-dim: #e0a600;
    --accent-ink: #1a1400;

    --ok: #34d399;
    --ok-dim: #0f2e23;
    --danger: #f87171;
    --danger-dim: #34161a;

    --radius: 14px;
    --radius-sm: 10px;
    --gap: 20px;
    --card-pad: 22px;

    /* Hauteurs des barres mobiles : le contenu s'en sert pour ne pas passer
       dessous, la barre d'action collante pour se poser juste au-dessus. */
    --appbar-h: 56px;
    --tabbar-h: 64px;

    /* Échelle de z-index explicite pour éviter les surenchères de 9999. */
    --z-sidebar: 30;
    --z-overlay: 40;
    --z-toast: 50;
}

* { box-sizing: border-box; }

html, body {
    margin: 0;
    padding: 0;
    background: var(--bg);
    color: var(--text);
    font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
    font-size: 16px;
    line-height: 1.6;
    -webkit-font-smoothing: antialiased;
}

a { color: var(--accent); text-decoration: none; }
a:hover { color: var(--accent-dim); }

:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
    border-radius: 4px;
}

/* ---------------------------------------------------------------- Squelette */

.layout { display: flex; min-height: 100vh; }

.sidebar {
    position: fixed;
    inset: 0 auto 0 0;
    width: 248px;
    background: var(--surface);
    border-right: 1px solid var(--line);
    padding: 22px 16px;
    display: flex;
    flex-direction: column;
    gap: 26px;
    z-index: var(--z-sidebar);
    overflow-y: auto;
}

.sidebar-brand { display: flex; align-items: center; gap: 10px; }
.sidebar-brand img { height: 26px; width: auto; }
.sidebar-brand span { font-weight: 600; letter-spacing: -0.01em; }

.sidebar-shop {
    background: var(--surface-2);
    border: 1px solid var(--line);
    border-radius: var(--radius-sm);
    padding: 12px 14px;
}
.sidebar-shop strong { display: block; font-size: 0.95rem; }
.sidebar-shop small { color: var(--muted); font-size: 0.8rem; }

.nav-group { margin-bottom: 18px; }
.nav-group h4 {
    margin: 0 0 8px 10px;
    font-size: 0.72rem;
    font-weight: 600;
    letter-spacing: 0.09em;
    text-transform: uppercase;
    color: var(--muted);
}
.nav-group a {
    display: flex;
    align-items: center;
    gap: 10px;
    min-height: 44px;
    padding: 0 10px;
    border-radius: var(--radius-sm);
    color: var(--text);
    font-size: 0.94rem;
    transition: background-color 200ms ease, color 200ms ease;
}
.nav-group a:hover { background: var(--surface-2); color: var(--text); }
.nav-group a[aria-current="page"] {
    background: var(--surface-2);
    color: var(--accent);
    box-shadow: inset 2px 0 0 var(--accent);
}
.nav-group svg { width: 18px; height: 18px; flex: none; }

.sidebar-footer { margin-top: auto; border-top: 1px solid var(--line); padding-top: 14px; }

.content {
    flex: 1;
    margin-left: 248px;
    padding: 32px 28px 64px;
    max-width: 1200px;
}

.page-head { margin-bottom: 28px; }
.page-head h1 {
    margin: 0 0 6px;
    font-size: 1.6rem;
    font-weight: 650;
    letter-spacing: -0.02em;
}
.page-head p { margin: 0; color: var(--muted); max-width: 68ch; }

/* --------------------------------------------------------------------- KPIs */

.kpi-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
    gap: var(--gap);
    margin-bottom: var(--gap);
}

.kpi {
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--radius);
    padding: 18px;
}
.kpi h3 {
    margin: 0 0 10px;
    font-size: 0.78rem;
    font-weight: 600;
    letter-spacing: 0.07em;
    text-transform: uppercase;
    color: var(--muted);
}
.kpi .value {
    font-size: 1.9rem;
    font-weight: 650;
    line-height: 1.1;
    letter-spacing: -0.02em;
    /* Réserve la hauteur : la valeur arrive en asynchrone, la carte ne doit
       pas sauter quand elle se remplit. */
    min-height: 2.1rem;
}
.kpi .trend { font-size: 0.85rem; color: var(--muted); }
.kpi .trend.up { color: var(--ok); }
.kpi .trend.down { color: var(--danger); }

/* -------------------------------------------------------------------- Cartes */

.card {
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--radius);
    padding: var(--card-pad);
    margin-bottom: var(--gap);
}
.card > h2 {
    margin: 0 0 4px;
    font-size: 1.05rem;
    font-weight: 620;
}
.card > h2 + p { margin: 0 0 18px; color: var(--muted); font-size: 0.9rem; }

.grid-2 {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
    gap: var(--gap);
}

.chart-box { position: relative; height: 260px; }

/* -------------------------------------------------------------- Formulaires */

.field { margin-bottom: 16px; }
.field label {
    display: block;
    margin-bottom: 6px;
    font-size: 0.85rem;
    font-weight: 550;
    color: var(--muted);
}
.field input,
.field select,
.field textarea {
    width: 100%;
    min-height: 44px;
    padding: 10px 12px;
    background: var(--surface-2);
    border: 1px solid var(--line-strong);
    border-radius: var(--radius-sm);
    color: var(--text);
    font: inherit;
    font-size: 1rem; /* 16px : évite le zoom automatique sur iOS */
}
.field input[type="color"] { padding: 4px; height: 44px; cursor: pointer; }
.field .hint { margin-top: 6px; font-size: 0.82rem; color: var(--muted); }
.field-error { margin-top: 6px; font-size: 0.85rem; color: var(--danger); }

.radio-row { display: flex; flex-wrap: wrap; gap: 12px; }
.radio-card {
    flex: 1 1 180px;
    display: flex;
    gap: 10px;
    padding: 14px;
    background: var(--surface-2);
    border: 1px solid var(--line-strong);
    border-radius: var(--radius-sm);
    cursor: pointer;
    transition: border-color 200ms ease, background-color 200ms ease;
}
.radio-card:hover { border-color: var(--accent-dim); }
/* flex: none — sans ça l'input s'étire à toute la hauteur de la carte et
   devient un disque géant au milieu du texte. */
.radio-card input {
    flex: none;
    align-self: flex-start;
    width: 18px;
    height: 18px;
    margin-top: 3px;
    accent-color: var(--accent);
}
.radio-card strong { display: block; font-size: 0.95rem; }
.radio-card small { color: var(--muted); }
.radio-card:has(input:checked) {
    border-color: var(--accent);
    background: color-mix(in srgb, var(--accent) 8%, var(--surface-2));
}

/* ------------------------------------------------------------------ Boutons */

.btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
    min-height: 44px;
    padding: 0 18px;
    border: 1px solid var(--line-strong);
    border-radius: var(--radius-sm);
    background: var(--surface-2);
    color: var(--text);
    font: inherit;
    font-weight: 550;
    cursor: pointer;
    transition: background-color 200ms ease, border-color 200ms ease, color 200ms ease;
}
.btn:hover { border-color: var(--accent-dim); }
.btn:disabled { opacity: 0.55; cursor: not-allowed; }

.btn-primary {
    background: var(--accent);
    border-color: var(--accent);
    color: var(--accent-ink);
}
.btn-primary:hover { background: var(--accent-dim); border-color: var(--accent-dim); }

.btn-danger { color: var(--danger); }
.btn-lg { min-height: 56px; font-size: 1.05rem; padding: 0 24px; }
.btn-block { width: 100%; }

.btn-row { display: flex; flex-wrap: wrap; gap: 12px; }

/* ------------------------------------------------------------------ Tableau */

.table-wrap { overflow-x: auto; border-radius: var(--radius-sm); }
table { width: 100%; border-collapse: collapse; font-size: 0.92rem; }
thead th {
    position: sticky;
    top: 0;
    background: var(--surface-2);
    text-align: left;
    padding: 11px 14px;
    font-size: 0.76rem;
    font-weight: 600;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--muted);
    white-space: nowrap;
}
tbody td { padding: 12px 14px; border-top: 1px solid var(--line); }
tbody tr { transition: background-color 150ms ease; }
tbody tr:hover { background: var(--surface-2); }
td.num, th.num { text-align: right; font-variant-numeric: tabular-nums; }

.badge {
    display: inline-block;
    padding: 2px 9px;
    border-radius: 999px;
    font-size: 0.78rem;
    font-weight: 600;
    border: 1px solid var(--line-strong);
    color: var(--muted);
}
.badge-ok { background: var(--ok-dim); border-color: var(--ok); color: var(--ok); }
.badge-accent {
    background: color-mix(in srgb, var(--accent) 14%, transparent);
    border-color: var(--accent);
    color: var(--accent);
}

/* ------------------------------------------------------- États vides / chargement */

.empty {
    text-align: center;
    padding: 48px 24px;
    border: 1px dashed var(--line-strong);
    border-radius: var(--radius);
    color: var(--muted);
}
.empty h3 { margin: 0 0 8px; color: var(--text); font-size: 1.1rem; }
.empty p { margin: 0 auto 20px; max-width: 46ch; }

.skeleton {
    background: linear-gradient(90deg, var(--surface-2) 25%, var(--line) 50%, var(--surface-2) 75%);
    background-size: 200% 100%;
    animation: shimmer 1.4s linear infinite;
    border-radius: 6px;
    color: transparent;
}
@keyframes shimmer {
    from { background-position: 200% 0; }
    to { background-position: -200% 0; }
}

/* -------------------------------------------------------------------- Toast */

.toast {
    position: fixed;
    left: 50%;
    bottom: 26px;
    transform: translateX(-50%) translateY(140%);
    z-index: var(--z-toast);
    display: flex;
    align-items: center;
    gap: 10px;
    max-width: min(92vw, 520px);
    padding: 13px 18px;
    border-radius: var(--radius-sm);
    border: 1px solid var(--line-strong);
    background: var(--surface-2);
    box-shadow: 0 18px 40px rgba(0, 0, 0, 0.5);
    transition: transform 260ms cubic-bezier(0.22, 1, 0.36, 1);
}
.toast.show { transform: translateX(-50%) translateY(0); }
.toast.ok { border-color: var(--ok); }
.toast.error { border-color: var(--danger); }
.toast svg { width: 18px; height: 18px; flex: none; }

/* --------------------------------------------- Aperçu de la carte wallet */

.card-preview-col { position: sticky; top: 32px; }

.wallet-card {
    /* Les dimensions RÉELLES d'un pass, pas une vignette : 375 pt de large —
       la largeur d'un pass sur iPhone — et le rapport relevé sur un vrai pass
       installé (354 x 495 pt à l'écran, soit 375 x 524 à cette largeur). Un
       aperçu plus petit que la vraie carte rend le QR et les tampons plus
       serrés qu'ils ne le seront, et c'est l'écran sur lequel le commerçant dit
       oui. `max-width` pour que le téléphone, plus étroit que 375 px, le
       réduise proportionnellement au lieu de déborder.
       `min-height: max-content` laisse la carte s'allonger si le contenu
       déborde (20 tampons, promesse longue) plutôt que de le rogner —
       `max-content` et pas `fit-content` : ce dernier sous-mesurait un
       conteneur flex en colonne, et la légende sous le QR sortait du cadre. */
    width: 375px;
    max-width: 100%;
    aspect-ratio: 375 / 524;
    min-height: max-content;
    display: flex;
    flex-direction: column;
    border-radius: 16px;
    padding: 20px;
    color: #fff;
    background: #0c0d17;
    /* Un pass posé sur une table, pas un rectangle à plat : l'ombre portée
       creuse le fond, le liseré intérieur attrape la lumière sur l'arête, et
       ::before dessine une vraie bordure en dégradé (impossible en `border`,
       la couleur de fond est posée en style inline par le JS). */
    box-shadow:
        0 26px 60px rgba(0, 0, 0, 0.60),
        0 2px 6px rgba(0, 0, 0, 0.35),
        inset 0 1px 0 rgba(255, 255, 255, 0.12);
    /* La bande de tampons déborde le padding : les angles la recoupent. */
    overflow: hidden;
    position: relative;
    /* La colonne d'aperçu est plus large que 375 px : sans ça la carte pendait
       à gauche de son bloc au lieu d'en tenir le milieu. */
    margin-inline: auto;
}
/* Bordure en dégradé : un cadre d'1 px rempli d'un dégradé, évidé au centre par
   un masque. `border-radius` hérité pour qu'il suive les angles. */
.wallet-card::before {
    content: '';
    position: absolute;
    inset: 0;
    border-radius: inherit;
    padding: 1px;
    background: linear-gradient(
        155deg,
        rgba(255, 255, 255, 0.50),
        rgba(255, 255, 255, 0.06) 34%,
        rgba(255, 255, 255, 0) 60%,
        rgba(255, 197, 42, 0.32)
    );
    -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
            mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
    -webkit-mask-composite: xor;
            mask-composite: exclude;
    pointer-events: none;
    z-index: 2;
}
/* Reflet du haut : ce qui distingue une carte d'un aplat. Très bas en opacité,
   il passe par-dessus l'en-tête sans en changer la lecture. */
.wallet-card::after {
    content: '';
    position: absolute;
    inset: 0;
    border-radius: inherit;
    background: linear-gradient(
        180deg,
        rgba(255, 255, 255, 0.10),
        rgba(255, 255, 255, 0) 38%
    );
    pointer-events: none;
    z-index: 1;
}
/* Le contenu passe devant les deux voiles décoratifs. */
.wallet-card > * { position: relative; z-index: 3; }
/* En-tête d'un vrai pass : la marque et le logo tiennent la gauche, le nom de
   la carte et son porteur la droite (les `headerFields` du pass). */
.wallet-card .wc-head {
    display: flex;
    align-items: flex-start;
    gap: 12px;
    margin-bottom: 8px;
}
/* La signature au-dessus du logo — ou de l'initiale quand aucun logo n'a été
   trouvé. En masque et pas en <img> : la marque prend `currentColor`, donc la
   couleur du texte de la carte, et reste lisible sur n'importe quel fond. */
.wallet-card .wc-brand {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 8px;
    min-width: 0;
}
.wallet-card .wc-by-mark {
    display: block;
    width: 76px;
    height: 10px;
    background: currentColor;
    opacity: 0.45;
    -webkit-mask: url('/images/by-quaantum.svg') no-repeat left center / contain;
            mask: url('/images/by-quaantum.svg') no-repeat left center / contain;
}
/* Le nom de la carte et son porteur, calés à droite : c'est le seul bloc qui
   reste lisible quand le pass est empilé dans le Wallet. */
.wallet-card .wc-id {
    min-width: 0;
    margin-left: auto;
    text-align: right;
}
.wallet-card .wc-member {
    display: block;
    margin-top: 2px;
    font-weight: 600;
    font-size: 1.02rem;
    line-height: 1.2;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
/* « ZUZUTTOMO PASS » : l'étiquette du champ d'en-tête du vrai pass, donc en
   ambre et en capitales comme toutes les étiquettes. Elle ne porte que
   l'enseigne — pas « New Saigon - Restaurant Vietnamien Lognes » (voir
   shortName(), services/loyaltyWalletService.js). Une seule ligne, tronquée :
   un pass ne revient jamais à la ligne ici. */
.wallet-card .wc-shop {
    display: block;
    min-width: 0;
    font-size: 0.62rem;
    font-weight: 600;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--accent);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.wallet-card .wc-logo {
    height: 34px;
    max-width: 96px;
    object-fit: contain;
}
/* Le commerce n'a pas de site, ou son logo est introuvable : la carte porte son
   initiale au lieu d'apparaître nue. C'est l'écran qui fait basculer le
   commerçant en boutique, il ne doit jamais avoir l'air inachevé. */
.wallet-card .wc-mono {
    display: grid;
    place-items: center;
    width: 34px;
    height: 34px;
    flex: 0 0 auto;
    border-radius: 50%;
    background: rgba(255, 255, 255, 0.16);
    color: #fff;
    font-size: 1rem;
    font-weight: 700;
    line-height: 1;
}
/* Logo affiché : le monogramme s'efface. Fait en CSS et pas en JS parce que
   trois endroits basculent `pvLogo.hidden` (rendu, chargement Places, création)
   et qu'un seul remettait le monogramme à jour — d'où les deux affichés côte à
   côte. Le sélecteur ne peut pas se désynchroniser. */
.wallet-card .wc-logo:not([hidden]) + .wc-mono { display: none; }

/* Carte à points : pas de grille à remplir, le compteur EST la carte. Il prend
   la place que la bande occupe en mode tampons. */
.wallet-card .wc-points { margin-top: 18px; }
.wallet-card .wc-label {
    font-size: 0.68rem;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--accent);
    margin-bottom: 2px;
}
.wallet-card .wc-balance {
    font-size: 2rem;
    font-weight: 650;
    line-height: 1;
    font-variant-numeric: tabular-nums;
}
/* En points il n'y a pas de grille à remplir : le solde se lit comme un chiffre
   de compteur, calé à droite — c'est là que l'œil le cherche sur un pass. */
.wallet-card.is-points .wc-label,
.wallet-card.is-points .wc-balance { text-align: right; }

/* Sous la bande, un seul champ étiqueté : la récompense, alignée à gauche —
   la rangée `secondaryFields` du vrai pass. Le porteur est remonté en en-tête,
   là où le pass le met. */
.wallet-card .wc-fields { margin-top: 18px; }
.wallet-card .wc-field { min-width: 0; }
.wallet-card .wc-flabel {
    margin: 0 0 3px;
    font-size: 0.62rem;
    font-weight: 600;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--accent);
}
.wallet-card .wc-fvalue {
    margin: 0;
    font-size: 0.9rem;
    line-height: 1.3;
    color: #fff;
}
.wallet-card .wc-code {
    margin-top: auto;
    padding: 14px;
    border-radius: 10px;
    background: #fff;
    display: flex;
    justify-content: center;
}
.wallet-card .wc-code canvas,
.wallet-card .wc-code img { display: block; }
/* Aucune carte n'existe encore : l'emplacement est montré vide plutôt qu'absent.
   Sans lui le commerçant ne voit pas que le client aura un code à présenter en
   caisse — c'est le geste que toute la mécanique attend.
   Blanc, exactement comme la pastille du vrai pass : un rectangle translucide
   sur fond sombre laissait croire que le QR se fondrait dans la couleur de la
   carte, alors qu'il arrive sur un carré blanc. Même taille que le QR livré
   (canvas 120 + 14 px de marge de chaque côté). */
.wallet-card .wc-code.is-empty {
    /* Hauteur explicite et pas `aspect-ratio` : une hauteur déduite d'une largeur
       inconnue ne compte pas dans la mesure intrinsèque du parent, et la carte se
       fermait 20px trop haut — la légende sortait du cadre. */
    width: 148px;
    height: 148px;
    margin-left: auto;
    margin-right: auto;
    padding: 10px;
    align-items: center;
    text-align: center;
    background: #fff;
    border: 1px dashed rgba(12, 13, 23, 0.28);
    font-size: 0.7rem;
    line-height: 1.35;
    color: rgba(12, 13, 23, 0.5);
}
.wallet-card .wc-code-note {
    margin-top: 8px;
    text-align: center;
    font-size: 0.72rem;
    color: rgba(255, 255, 255, 0.55);
}

/* La bande de tampons du VRAI pass est une image pleine largeur de 375 x 144 pt
   (services/stampStrip.js) : cases centrées horizontalement ET verticalement,
   pas de 11 pt, marge de 16. L'aperçu rejoue la même géométrie en proportions
   du conteneur — `container-type: inline-size` fait de la bande sa propre unité
   de mesure, et le JS pose `--cell` en `cqw` avec la formule du service. Sans
   ça l'aperçu montrait des ronds deux fois trop petits collés à gauche, et le
   commerçant validait une grille que son client ne verrait jamais. */
.stamp-band {
    container-type: inline-size;
    /* Pleine largeur : la bande déborde le padding de 20 px de la carte, comme
       l'image du pass qui va d'un bord à l'autre. */
    margin: 14px -20px 0;
    aspect-ratio: 375 / 144;
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    /* Valeurs de repli avant le premier rendu du JS (10 tampons, 5 par rangée). */
    --cell: 13.4cqw;
    --gap: 2.93cqw;
}
/* `display: flex` bat la règle par défaut du navigateur : sans ça, la bande
   restait visible en mode points. */
.stamp-band[hidden] { display: none; }
.stamp-row { display: flex; justify-content: center; gap: var(--gap); }
.stamp-row + .stamp-row { margin-top: var(--gap); }
.stamp {
    width: var(--cell);
    height: var(--cell);
    border-radius: 50%;
    /* Mêmes épaisseur et opacité que le tracé du pass (ring(..., WHITE, 0.42)). */
    border: max(1px, calc(var(--cell) * 0.04)) solid rgba(255, 255, 255, 0.42);
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: calc(var(--cell) * 0.5);
    color: #fff;
}
/* La coche est là dès l'emplacement vide, en filigrane : c'est ce qui fait
   comprendre d'un coup d'œil que les cases se cochent au fil des passages.
   0.22 = l'opacité du tracé sur le vrai pass. */
.stamp::after { content: '✓'; opacity: 0.22; line-height: 1; }
.stamp.filled::after { opacity: 0.92; }
.stamp.filled {
    background: var(--accent);
    border-color: var(--accent);
    color: var(--accent-ink);
    font-weight: 700;
}

/* --------------------------------------------------- Révélation de la carte */

/* La carte n'apparaît pas, elle se dévoile : floutée sous un voile jusqu'au
   premier geste. C'est l'écran où le commerçant découvre SA carte, et un objet
   qu'on découvre vaut mieux qu'un objet déjà là. Le voile lâche aussi à la
   première frappe dans le formulaire — jamais il ne doit bloquer l'aperçu
   vivant, qui est ce qui fait dire oui. */
.wallet-card > *:not(.wc-veil) { transition: filter 0.55s ease, opacity 0.55s ease; }
.wallet-card.is-veiled > *:not(.wc-veil) { filter: blur(11px); opacity: 0.55; }
.wc-veil {
    position: absolute;
    inset: 0;
    z-index: 4;
    display: grid;
    place-items: center;
    border: 0;
    border-radius: inherit;
    background: rgba(8, 9, 16, 0.25);
    color: #fff;
    font: inherit;
    font-size: 0.82rem;
    letter-spacing: 0.05em;
    cursor: pointer;
    animation: veil-pulse 2.4s ease-in-out infinite;
}
.wallet-card:not(.is-veiled) .wc-veil { display: none; }
@keyframes veil-pulse {
    0%, 100% { opacity: 0.85; }
    50% { opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
    .wc-veil { animation: none; }
    .wallet-card > *:not(.wc-veil) { transition: none; }
}

/* ------------------------------------------------- Coquille mobile (masquée) */

/* Les trois éléments n'existent qu'en dessous de 900px. Déclarés ici pour que
   la règle [hidden] plus bas les batte en spécificité quel que soit l'ordre. */
.appbar,
.tabbar,
.sheet,
.sheet-backdrop { display: none; }

.appbar-logo { height: 22px; width: auto; flex: none; }
.appbar-shop { min-width: 0; line-height: 1.28; }
.appbar-shop strong,
.appbar-shop small {
    display: block;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.appbar-shop strong { font-size: 0.95rem; font-weight: 600; }
.appbar-shop small { color: var(--muted); font-size: 0.76rem; }

.tabbar a,
.tabbar button {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: flex-end;
    gap: 4px;
    min-height: 52px;
    padding: 6px 2px;
    border: 0;
    border-radius: 12px;
    background: none;
    color: var(--muted);
    font: inherit;
    font-size: 0.68rem;
    font-weight: 550;
    text-decoration: none;
    cursor: pointer;
    transition: color 200ms ease;
}
.tabbar svg { width: 22px; height: 22px; flex: none; }
.tabbar a:hover, .tabbar button:hover { color: var(--text); }
.tabbar [aria-current="page"] { color: var(--accent); }

/* Le geste quotidien mérite la cible la plus grosse et la seule couleur pleine
   de la barre : on le trouve sans regarder. */
.tab-primary-disc {
    display: grid;
    place-items: center;
    width: 46px;
    height: 46px;
    margin-top: -20px;
    border-radius: 50%;
    background: var(--accent);
    color: var(--accent-ink);
    box-shadow: 0 8px 22px color-mix(in srgb, var(--accent) 38%, transparent);
    transition: background-color 200ms ease;
}
.tab-primary-disc svg { width: 24px; height: 24px; }
.tabbar .tab-primary { color: var(--text); }
.tabbar .tab-primary:hover .tab-primary-disc { background: var(--accent-dim); }

.sheet-grip {
    width: 40px;
    height: 4px;
    margin: 0 auto 12px;
    border-radius: 999px;
    background: var(--line-strong);
}
.sheet-title {
    margin: 0 0 10px;
    font-size: 0.72rem;
    font-weight: 600;
    letter-spacing: 0.09em;
    text-transform: uppercase;
    color: var(--muted);
}
.sheet-link {
    display: flex;
    align-items: center;
    gap: 13px;
    min-height: 60px;
    padding: 10px 12px;
    border-radius: var(--radius-sm);
    color: var(--text);
    transition: background-color 200ms ease;
}
.sheet-link:hover, .sheet-link:focus-visible { background: var(--surface-2); color: var(--text); }
.sheet-link svg { width: 20px; height: 20px; flex: none; color: var(--muted); }
.sheet-link strong { display: block; font-size: 0.96rem; font-weight: 550; }
.sheet-link small { color: var(--muted); font-size: 0.82rem; }
.sheet-link[aria-current="page"] { background: var(--surface-2); }
.sheet-link[aria-current="page"] svg { color: var(--accent); }
.sheet-actions {
    display: grid;
    gap: 10px;
    margin-top: 14px;
    padding-top: 14px;
    border-top: 1px solid var(--line);
}

/* La feuille est modale : la page derrière ne doit pas défiler sous le doigt. */
body.sheet-open { overflow: hidden; }

/* -------------------------------------------------- Barre d'action collante */

/* Pour les formulaires longs (la carte de fidélité en premier) : le bouton
   d'enregistrement reste sous le pouce au lieu d'attendre en bas de page.
   Sur grand écran il redevient un simple pied de formulaire. */
.sticky-actions { display: flex; flex-wrap: wrap; gap: 12px; }

/* --------------------------------------------------------------- Responsive */

@media (max-width: 900px) {
    :root { --card-pad: 16px; --gap: 14px; }

    .sidebar { display: none; }

    .appbar {
        display: flex;
        align-items: center;
        gap: 11px;
        position: fixed;
        inset: 0 0 auto 0;
        z-index: var(--z-sidebar);
        height: calc(var(--appbar-h) + env(safe-area-inset-top, 0px));
        padding: env(safe-area-inset-top, 0px) 16px 0;
        background: color-mix(in srgb, var(--surface) 92%, transparent);
        -webkit-backdrop-filter: blur(14px);
        backdrop-filter: blur(14px);
        border-bottom: 1px solid var(--line);
    }

    .tabbar {
        display: grid;
        grid-template-columns: repeat(5, 1fr);
        align-items: end;
        gap: 2px;
        position: fixed;
        inset: auto 0 0 0;
        z-index: var(--z-sidebar);
        padding: 6px 6px calc(6px + env(safe-area-inset-bottom, 0px));
        background: color-mix(in srgb, var(--surface) 92%, transparent);
        -webkit-backdrop-filter: blur(14px);
        backdrop-filter: blur(14px);
        border-top: 1px solid var(--line);
    }

    .sheet-backdrop {
        display: block;
        position: fixed;
        inset: 0;
        z-index: var(--z-overlay);
        background: rgba(3, 4, 8, 0.66);
    }

    .sheet {
        display: block;
        position: fixed;
        inset: auto 0 0 0;
        z-index: calc(var(--z-overlay) + 1);
        max-height: 86vh;
        overflow-y: auto;
        padding: 12px 16px calc(18px + env(safe-area-inset-bottom, 0px));
        background: var(--surface);
        border-top: 1px solid var(--line);
        border-radius: 20px 20px 0 0;
        box-shadow: 0 -18px 50px rgba(0, 0, 0, 0.55);
        animation: sheet-up 240ms cubic-bezier(0.22, 1, 0.36, 1);
    }
    @keyframes sheet-up { from { transform: translateY(100%); } }

    .content {
        margin-left: 0;
        padding: calc(var(--appbar-h) + env(safe-area-inset-top, 0px) + 18px) 16px
                 calc(var(--tabbar-h) + env(safe-area-inset-bottom, 0px) + 28px);
    }

    .page-head { margin-bottom: 18px; }
    .page-head h1 { font-size: 1.35rem; }
    .page-head p { font-size: 0.92rem; }

    /* Deux colonnes plutôt qu'une : quatre KPI empilés, c'est un écran entier
       de défilement avant d'atteindre le contenu utile. */
    .kpi-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; }
    .kpi { padding: 13px; }
    .kpi h3 { margin-bottom: 5px; font-size: 0.66rem; letter-spacing: 0.05em; }
    .kpi .value { font-size: 1.45rem; min-height: 1.6rem; }
    .kpi .trend { font-size: 0.76rem; }

    .grid-2 { grid-template-columns: 1fr; }
    .chart-box { height: 220px; }

    .card-preview-col { position: static; }

    /* Le toast se pose au-dessus de la barre d'onglets, jamais dessous. */
    .toast { bottom: calc(var(--tabbar-h) + env(safe-area-inset-bottom, 0px) + 14px); }

    .sticky-actions {
        position: sticky;
        /* Se pose juste au-dessus de la barre d'onglets, pas dessous. */
        bottom: calc(var(--tabbar-h) + env(safe-area-inset-bottom, 0px));
        z-index: 2;
        /* Pleine largeur de la carte : la barre doit lire comme un socle, pas
           comme un bouton flottant au milieu du formulaire. */
        margin: 18px calc(-1 * var(--card-pad)) 0;
        padding: 12px var(--card-pad);
        background: color-mix(in srgb, var(--surface) 95%, transparent);
        -webkit-backdrop-filter: blur(12px);
        backdrop-filter: blur(12px);
        border-top: 1px solid var(--line);
    }
    .sticky-actions .btn { flex: 1 1 auto; }
}

/* Un tableau de sept colonnes sur 375px, c'est un défilement horizontal et
   un en-tête qu'on ne voit plus : chaque ligne devient une fiche, l'intitulé
   de colonne revient en libellé via data-label. */
@media (max-width: 760px) {
    .table-wrap { overflow: visible; }
    table, tbody, tbody tr, tbody td { display: block; }

    thead {
        position: absolute;
        width: 1px;
        height: 1px;
        overflow: hidden;
        clip-path: inset(50%);
        white-space: nowrap;
    }

    tbody tr {
        margin-bottom: 12px;
        padding: 14px;
        background: var(--surface-2);
        border: 1px solid var(--line);
        border-radius: var(--radius-sm);
    }
    tbody tr:last-child { margin-bottom: 0; }
    tbody tr:hover { background: var(--surface-2); }

    tbody td {
        display: flex;
        align-items: baseline;
        justify-content: space-between;
        gap: 16px;
        padding: 5px 0;
        border-top: 0;
        text-align: right;
    }
    tbody td::before {
        content: attr(data-label);
        flex: none;
        color: var(--muted);
        font-size: 0.74rem;
        font-weight: 600;
        letter-spacing: 0.05em;
        text-transform: uppercase;
        text-align: left;
    }
    tbody td:not([data-label])::before { content: none; }

    /* Première cellule : le titre de la fiche, pas une ligne parmi d'autres. */
    tbody td.cell-head {
        display: block;
        margin-bottom: 8px;
        padding: 0 0 10px;
        border-bottom: 1px solid var(--line);
        text-align: left;
    }
    /* Un commentaire libre se lit à gauche, sur toute la largeur : aligné à
       droite sous son libellé, c'est illisible. */
    tbody td.cell-text {
        display: block;
        text-align: left;
        padding-top: 10px;
    }
    tbody td.cell-text::before { display: block; margin-bottom: 4px; }

    tbody td.cell-action { display: block; padding-top: 12px; }
    tbody td.cell-action:empty { display: none; }
    tbody td.cell-action .btn { width: 100%; }

    .empty { padding: 34px 18px; }
}

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

/* [hidden] doit gagner sur les display: du bloc mobile. */
.appbar[hidden],
.tabbar[hidden],
.sheet[hidden],
.sheet-backdrop[hidden] { display: none; }
