  :root{
    --emerald:#146356;
    --emerald-light:#1c8a76;
    --marigold:#e8a23b;
    --paper:#f7f3e8;
    --paper-2:#efe8d6;
    --charcoal:#26221d;
    --charcoal-soft:#5b564c;
    --alert:#c0392b;
    --alert-bg:#fbeae7;
    --ok-bg:#e9f3ee;
    --card-bg:#ffffff;
    --slate:#5a7268;
    --radius:14px;
    --shadow:0 2px 10px rgba(38,34,29,0.08);
    --marigold-bg:#fbf1de;

    /* ---------- Relief 3D, deux niveaux (demande du 12/11/2026) ----------
       STATIQUE : les 4 tuiles du tableau de bord et les 2 cartes « Mon palier »
       / « Mes connexions » sont bombées, mais ne bougent pas quand on les
       touche. On ne donne pas l'illusion d'un bouton à des chiffres qu'on lit.
       PRESSABLE : seules les 3 cartes « Mes produits » / « Catégories » /
       « Favoris » s'enfoncent au toucher. C'est le seul relief qui réagit.
       Trois valeurs par niveau, dans l'ordre où l'œil les lit :
         tranche  — l'ombre basse et nette : l'épaisseur de la carte ;
         bevel    — le liseré clair en haut : l'arête éclairée ;
         socle    — l'ombre diffuse : la carte posée sur le papier.
       Elles sont ici et pas en dur dans chaque règle : sans ça, la tuile du
       tableau de bord et la carte du palier finiraient par ne plus avoir la
       même épaisseur au premier ajustement. */
    --relief-tranche:0 3px 0 rgba(38,34,29,.10);
    --relief-bevel:inset 0 1px 0 rgba(255,255,255,.9);
    --relief-socle:0 5px 12px rgba(38,34,29,.10);
    --press-tranche:0 5px 0 rgba(38,34,29,.13);
    --press-bevel:inset 0 1px 0 rgba(255,255,255,.95);
    --press-socle:0 7px 15px rgba(38,34,29,.13);
  }
  body.dark-mode{
    --paper:#151a17;
    --paper-2:#20271f;
    --charcoal:#eae7de;
    --charcoal-soft:#a39d90;
    --alert-bg:#3a1f1c;
    --ok-bg:#16332a;
    --card-bg:#1e2521;
    --slate:#6b8a7e;
    --shadow:0 2px 10px rgba(0,0,0,0.35);
    --marigold-bg:#2e2410;
    /* Relief en thème sombre : mêmes rôles, valeurs différentes. Un liseré
       blanc à 90 % sur du charbon ferait une arête qui brille comme du métal ;
       20 % donne le fil de lumière attendu sur une surface sombre. Les tranches
       sont plus opaques parce qu'une ombre à 10 % sur fond noir est invisible. */
    --relief-tranche:0 3px 0 rgba(0,0,0,.34);
    --relief-bevel:inset 0 1px 0 rgba(255,255,255,.20);
    --relief-socle:0 5px 12px rgba(0,0,0,.38);
    --press-tranche:0 5px 0 rgba(0,0,0,.42);
    --press-bevel:inset 0 1px 0 rgba(255,255,255,.28);
    --press-socle:0 7px 15px rgba(0,0,0,.45);
  }
  /* La poubelle des tuiles a son propre réglage (voir .stat-reset plus bas) :
     la forcer à .65 ici l'aurait rendue plus pâle en sombre qu'en clair, alors
     que c'est exactement l'inverse qu'on veut. */
  body.dark-mode .del-entry, body.dark-mode button.del, body.dark-mode button.edit{
    opacity:.65;
  }
  /* ---------- Icônes SVG (src/icons.js, chantier émojis du 12/11/2026) ----------
     `currentColor` : l'icône prend la couleur du texte qui l'entoure, donc elle
     s'accorde avec la carte, le bouton et le thème sombre sans règle dédiée.
     `flex-shrink:0` : dans une ligne de texte, une icône ne doit jamais être
     écrasée quand le mot voisin est long.
     `vertical-align:-0.14em` : sans ça, l'icône s'aligne sur la ligne de base et
     paraît posée trop haut à côté d'un mot en minuscules. */
  .mb-i{ flex-shrink:0; vertical-align:-0.14em; display:inline-block; }
  .mb-i-text{ display:inline-flex; align-items:center; gap:5px; }
  .mb-i-text > .mb-i{ vertical-align:0; }
  .mb-i-wrap{ display:inline-flex; align-items:center; }

  *{box-sizing:border-box;}
  body{
    transition:background-color .2s ease, color .2s ease;
    margin:0; background:var(--paper); color:var(--charcoal);
    font-family:'Inter',sans-serif; -webkit-tap-highlight-color:transparent;
  }
  /* Écran de chargement au démarrage/rechargement — voir hideBootLoading() dans
     stores-devices.js. Volontairement en dehors de #app (z-index le plus haut de toute
     l'app) pour couvrir tout le reste, y compris une fiche déjà ouverte lors d'un
     rechargement. */
  #boot-loading-overlay{
    position:fixed; inset:0; z-index:99998; background:var(--emerald);
    display:flex; flex-direction:column; align-items:center; justify-content:center; gap:16px;
    color:var(--paper); font-family:'Inter',sans-serif;
  }
  .boot-spinner{
    width:40px; height:40px; border-radius:50%;
    border:4px solid rgba(255,255,255,0.25); border-top-color:#fff;
    animation:bootSpin .8s linear infinite;
  }
  @keyframes bootSpin{ to{ transform:rotate(360deg); } }
  /* Même z-index que #boot-loading-overlay (au-dessus de tout, y compris une fiche VIP
     déjà ouverte) — masqué par défaut, affiché en JS via .open (voir
     updateEmployeeDowngradeBlock(), plans.js). display:none ET .open{display:flex} plutôt
     qu'une simple opacité : doit aussi couper les clics/taps derrière lui. */
  #employee-plan-block-overlay{
    position:fixed; inset:0; z-index:99997; background:var(--emerald);
    display:none; flex-direction:column; align-items:center; justify-content:center; gap:14px;
    color:var(--paper); font-family:'Inter',sans-serif; padding:24px; text-align:center;
  }
  #employee-plan-block-overlay.open{ display:flex; }
  /* Même logique/z-index que les 2 overlays plein écran ci-dessus — voir app-lock.js.
     Masqué par défaut (display:none), affiché via .open (maybeShowAppLockScreen()). */
  #app-lock-overlay{
    position:fixed; inset:0; z-index:99999; background:var(--emerald);
    display:none; flex-direction:column; align-items:center; justify-content:center; gap:14px;
    color:var(--paper); font-family:'Inter',sans-serif; padding:24px; text-align:center;
  }
  #app-lock-overlay.open{ display:flex; }
  .app-lock-pin-input{
    font-size:28px; letter-spacing:14px; text-align:center; width:150px; padding:10px 8px;
    border-radius:10px; border:none; font-family:'Sora',sans-serif;
  }
  /* Fenêtre obligatoire d'inscription/connexion — voir maybeScheduleAuthGate()
     dans phone-auth.js. Z-index juste sous #boot-loading-overlay/#employee-plan-block-overlay
     (ne doit jamais s'afficher tant que l'un des deux couvre encore l'écran), mais
     largement au-dessus des .sheet-overlay classiques (30). */
  #auth-gate-overlay{
    position:fixed; inset:0; z-index:99996; background:rgba(18,16,12,0.82);
    backdrop-filter:blur(3px); -webkit-backdrop-filter:blur(3px);
    display:none; flex-direction:column; align-items:center;
    /* PAS de justify-content:center : quand le formulaire est plus haut que
       l'écran (clavier ouvert), un contenu centré dans une boîte qui défile
       devient inatteignable par le HAUT — le premier champ restait coupé. Le
       centrage est fait par les marges auto de la carte juste en dessous, qui
       se replient à 0 dès qu'il n'y a plus la place. */
    justify-content:flex-start;
    padding:20px; overflow-y:auto; overscroll-behavior:contain;
  }
  #auth-gate-overlay.open{ display:flex; }
  /* S'ouvre PAR-DESSUS #auth-gate-overlay (voir openSelfResetSheet(), phone-auth.js) —
     doit donc dépasser son z-index (99996), pas juste celui des .sheet-overlay
     classiques (30) comme les autres cas similaires plus bas dans ce fichier. */
  #self-reset-overlay{ z-index:99997; }
  @keyframes authGatePop{
    from{ opacity:0; transform:translateY(10px) scale(.96); }
    to{ opacity:1; transform:translateY(0) scale(1); }
  }
  .auth-gate-card{
    background:var(--paper); width:100%; max-width:400px; border-radius:22px;
    padding:26px 22px 22px; box-shadow:0 12px 40px rgba(0,0,0,0.4);
    animation:authGatePop .32s ease;
    /* Centrée quand il reste de la place, collée en haut quand le clavier
       réduit la fenêtre : tout le formulaire reste alors atteignable en
       défilant, du premier champ au bouton (voir src/keyboard.js). */
    margin-top:auto; margin-bottom:auto;
  }
  .auth-gate-brand{
    display:flex; align-items:center; justify-content:center; gap:9px; margin-bottom:18px;
  }
  .auth-gate-logo{ width:34px; height:34px; border-radius:9px; box-shadow:0 2px 8px rgba(0,0,0,.15); }
  .auth-gate-brand-name{ font-family:'Sora',sans-serif; font-size:19px; font-weight:800; color:var(--emerald); }
  .auth-gate-card h3{ text-align:center; margin:0 0 4px; }
  .auth-gate-subtitle{ font-size:12.5px; color:var(--charcoal-soft); text-align:center; margin:0 0 18px; line-height:1.5; }
  .auth-gate-error{
    background:var(--alert-bg); color:var(--alert); border-radius:10px; padding:9px 12px;
    font-size:12px; text-align:center; margin:0 0 16px; line-height:1.5; font-weight:600;
  }
  .auth-gate-input-wrap{ position:relative; }
  .auth-gate-input-wrap input{ padding-right:42px !important; letter-spacing:1px; }
  .auth-gate-eye{
    position:absolute; right:4px; top:50%; transform:translateY(-50%);
    background:none; border:none; font-size:16px; padding:8px; cursor:pointer;
    line-height:1; opacity:.65;
  }
  .auth-gate-eye:active{ opacity:1; }
  .auth-gate-photo-row{
    display:flex; flex-direction:column; align-items:center; gap:8px; margin:0 0 18px;
  }
  .auth-gate-photo-picker{
    width:64px; height:64px; border-radius:50%; background:var(--paper-2);
    border:2px dashed var(--charcoal-soft); display:flex; align-items:center; justify-content:center;
    cursor:pointer; overflow:hidden; flex-shrink:0;
  }
  .auth-gate-photo-placeholder{ font-size:24px; opacity:.55; }
  .auth-gate-photo-preview-img{ width:100%; height:100%; object-fit:cover; border-radius:50%; }
  .auth-gate-photo-label{
    font-size:11.5px; color:var(--charcoal-soft); cursor:pointer; text-decoration:underline;
  }
  .auth-gate-forgot{
    display:block; text-align:center; font-size:11.5px; margin-top:12px;
    color:var(--charcoal-soft); text-decoration:underline; cursor:pointer;
  }
  .auth-gate-switch{
    text-align:center; font-size:13px; margin:16px 0 0; padding-top:14px;
    border-top:1px solid var(--paper-2); color:var(--charcoal-soft);
  }
  .auth-gate-switch a{ color:var(--emerald-light); font-weight:700; cursor:pointer; text-decoration:underline; margin-left:4px; }
  .auth-gate-secondary-wrap{ width:100%; max-width:400px; margin-top:2px; }
  .auth-gate-or{
    display:flex; align-items:center; gap:10px;
    text-align:center; font-size:11px; color:rgba(247,243,232,0.6);
    margin:16px 0 12px;
  }
  .auth-gate-or::before, .auth-gate-or::after{
    content:""; flex:1; height:1px; background:rgba(247,243,232,0.25);
  }
  .auth-gate-join-btn{
    width:100%; background:var(--marigold-bg); color:var(--charcoal); border:1px solid var(--marigold);
    border-radius:12px; padding:14px; font-size:14.5px; font-weight:700; font-family:'Sora',sans-serif; cursor:pointer;
    display:flex; align-items:center; justify-content:center; gap:8px;
  }
  .auth-gate-join-hint{
    color:rgba(247,243,232,0.75); font-size:11px; text-align:center; margin:10px 0 0; line-height:1.5;
  }
  .auth-gate-note{
    color:rgba(247,243,232,0.75); font-size:11px; text-align:center;
    margin-top:16px; max-width:340px; line-height:1.5;
  }
  h1,h2,h3,.display{ font-family:'Sora',sans-serif; }
  #app{
    max-width:480px; margin:0 auto; min-height:100vh;
    background:var(--paper); display:flex; flex-direction:column;
  }
  header.top{
    background:var(--emerald); color:var(--paper);
    padding:20px 20px 28px; position:relative; overflow:hidden;
  }
  header.top::after{
    content:""; position:absolute; right:-30px; top:-30px;
    width:140px; height:140px; border-radius:50%;
    background:var(--emerald-light); opacity:.5;
  }
  header.top .row{display:flex; justify-content:space-between; align-items:flex-start; position:relative; z-index:1;}
  header.top h1{font-size:22px; font-weight:800; margin:0 0 4px;}
  header.top p{margin:0; font-size:13px; opacity:.85;}
  .toggles{display:flex; flex-direction:column; align-items:flex-end; gap:6px;}
  .mini-toggle{display:flex; background:rgba(0,0,0,0.18); border-radius:999px; padding:3px;}
  .mini-toggle button{
    border:none; background:transparent; color:var(--paper);
    font-family:'Inter'; font-size:12px; font-weight:600;
    padding:6px 10px; border-radius:999px; cursor:pointer;
  }
  .mini-toggle button.active{background:var(--paper); color:var(--emerald);}
  .field-currency-toggle{display:inline-flex; background:var(--paper-2); border-radius:999px; padding:2px; margin-left:6px; vertical-align:middle;}
  .field-currency-toggle button{border:none; background:transparent; color:var(--charcoal-soft); font-family:'Inter'; font-size:10.5px; font-weight:700; padding:3px 8px; border-radius:999px; cursor:pointer;}
  .field-currency-toggle button.active{background:var(--emerald); color:#fff;}
  #rate-row{
    display:flex; align-items:center; justify-content:space-between; gap:8px;
    margin-top:12px; position:relative; z-index:1; flex-wrap:wrap;
  }
  #supplier-header-actions{
    display:flex; align-items:center; gap:6px; margin-left:auto;
    min-width:0; max-width:100%; flex-shrink:0;
  }
  #rate-row #suppliers-header-btn{ margin-left:0; min-width:0; }
  #rate-row #suppliers-header-btn span:last-child{ overflow-wrap:anywhere; }
  #rate-fields-wrap{display:none;}
  #rate-fields-wrap label{font-size:11px; opacity:.85; display:block; margin-bottom:4px;}
  #rate-fields-wrap input{
    width:110px; padding:7px 9px; border-radius:8px; border:none;
    font-size:13px; font-family:'Inter'; font-weight:600;
  }
  #rate-fields-wrap span{font-size:12px; margin-left:6px;}
  /* Message d'erreur du taux de change : la saisie reste libre (les chiffres
     doivent pouvoir être effacés), c'est la VALIDATION qui refuse une
     valeur finale vide ou impossible — et elle doit le dire clairement. */
  #rate-fields-wrap #in-rate-error{
    display:none; margin-left:8px; font-size:11.5px; font-weight:600;
    color:#ffd7d2; background:rgba(0,0,0,0.28); padding:3px 8px;
    border-radius:8px; max-width:260px; line-height:1.35;
  }
  /* Bande « prix arrondis au multiple de 50 FC » : masquée par défaut, affichée
     par updateCatalogRoundingHint() (src/render.js) seulement quand AU MOINS UN
     prix du catalogue a vraiment été arrondi — et jamais en USD. */
  #rate-fields-wrap #t-catalog-rounding-hint{
    display:none; margin-top:5px; max-width:360px; font-size:11px; line-height:1.35; opacity:.82;
  }
  #rate-fields-wrap input[aria-invalid="true"]{ outline:2px solid #ffb3ab; }
  #account-row{margin-top:10px; position:relative; z-index:1; flex-wrap:wrap; row-gap:6px;}
  #account-btn, #history-btn, #discover-btn, #suppliers-header-btn{
    display:flex; align-items:center; gap:6px;
    background:rgba(0,0,0,0.18); border:none; border-radius:999px;
    color:var(--paper); font-family:'Inter'; font-size:12px; font-weight:600;
    padding:7px 13px; cursor:pointer; flex-shrink:0;
  }
  /* 👁 immédiatement avant « Fournisseurs » : les deux boutons restent groupés,
     sans jamais comprimer ni déformer le champ du taux sur petit écran. */
  #dashboard-privacy-btn{
    display:flex; align-items:center; justify-content:center;
    background:rgba(0,0,0,0.18); border:none; border-radius:999px;
    color:var(--paper); font-size:14px; line-height:1;
    width:31px; height:31px; padding:0; cursor:pointer; flex-shrink:0;
  }
  #dashboard-privacy-btn:active{ transform:translateY(1px); }
  /* Cases à cocher de la petite fenêtre 👁 — une ligne par chiffre. */
  .privacy-check{
    display:flex; align-items:center; gap:10px;
    background:var(--paper-2); border-radius:12px;
    padding:12px 14px; margin-bottom:8px;
    font-size:13.5px; font-weight:600; color:var(--charcoal); cursor:pointer;
  }
  .privacy-check input[type="checkbox"]{
    width:18px; height:18px; flex-shrink:0; accent-color:var(--green-deep); cursor:pointer;
  }
  #login-cta-btn{
    display:none; align-items:center; justify-content:center; gap:8px;
    background:var(--marigold); color:#1a1408; border:2px solid #fff3d6;
    border-radius:999px; font-family:'Sora',sans-serif; font-size:14px; font-weight:800;
    padding:10px 22px; cursor:pointer; flex-shrink:0; letter-spacing:.02em;
    box-shadow:0 2px 0 #c4841f, 0 4px 14px rgba(0,0,0,.28);
    min-width:158px;
  }
  #login-cta-btn:active{ transform:translateY(1px); box-shadow:0 1px 0 #c4841f; }
  body.dark-mode #login-cta-btn{
    background:#f0b445; color:#1a1408; border-color:#ffe9b0;
    box-shadow:0 2px 0 #c4841f, 0 4px 16px rgba(0,0,0,.45);
  }
  #login-cta-btn .login-cta-warn{
    display:inline-flex; align-items:center; justify-content:center;
    width:22px; height:22px; border-radius:50%;
    background:rgba(26,20,8,0.84);
    border:1.5px solid rgba(255,243,214,0.92);
    box-shadow:0 1px 4px rgba(0,0,0,0.32);
    font-size:13px; line-height:1; flex-shrink:0;
    animation:loginCtaWarnBlink 1s ease-in-out infinite;
  }
  body.dark-mode #login-cta-btn .login-cta-warn{
    background:#141816;
    border-color:#fff3d6;
    box-shadow:0 1px 5px rgba(0,0,0,0.55);
  }
  @keyframes loginCtaWarnBlink{
    0%, 100%{ opacity:1; transform:scale(1); }
    50%{ opacity:0.15; transform:scale(0.92); }
  }
  .auth-gate-skip{
    margin-top:10px; background:transparent; border:none; color:rgba(247,243,232,.9);
    font-family:'Inter'; font-size:13.5px; font-weight:700; cursor:pointer;
    text-decoration:underline; padding:8px 12px;
  }
  #theme-btn{
    display:flex; align-items:center; justify-content:center;
    background:rgba(0,0,0,0.18); border:none; border-radius:50%;
    color:var(--paper); font-size:22px; width:48px; height:48px; cursor:pointer; flex-shrink:0;
  }
  .history-item{
    background:var(--card-bg); border-radius:12px; padding:12px 14px; margin-bottom:8px;
    display:flex; justify-content:space-between; align-items:center; gap:10px;
  }
  .history-item .info{flex:1; min-width:0;}
  .history-item .info .name{font-weight:700; font-size:13.5px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;}
  .history-item .info .meta{font-size:11.5px; color:var(--charcoal-soft); margin-top:2px;}
  .history-item .amounts{text-align:right; white-space:nowrap;}
  .history-item .amounts .total{font-family:'Sora'; font-weight:700; font-size:13.5px; color:var(--emerald);}
  .history-item .amounts .total.negative{color:var(--alert);}
  .history-item .amounts .profit{font-size:11px; color:var(--charcoal-soft);}
  .history-item button.del-entry{
    background:none; border:none; color:var(--charcoal-soft); font-size:14px; cursor:pointer; padding:4px; flex-shrink:0;
  }
  .history-item button.del-entry:active{color:var(--alert);}
  .history-load-more{ margin:4px 0 10px; }
  .history-load-more button{
    width:100%; padding:10px; border-radius:12px; font-size:13px; font-weight:700;
    font-family:'Inter'; cursor:pointer; background:var(--card-bg); border:1.5px solid var(--paper-2);
    color:var(--charcoal);
  }
  .btn-danger-outline{
    width:100%; background:var(--card-bg); border:1.5px solid var(--alert); color:var(--alert);
    border-radius:12px; padding:12px; font-size:13.5px; font-weight:700; font-family:'Inter';
    cursor:pointer; margin-top:6px; margin-bottom:6px;
  }
  .account-status{
    display:flex; align-items:center; gap:10px; background:var(--ok-bg);
    border-radius:12px; padding:12px 14px; margin-bottom:14px;
  }
  .account-status img{width:36px; height:36px; border-radius:50%;}
  .account-photo-wrap{ width:36px; height:36px; flex-shrink:0; }
  /* touch-action:manipulation réduit le risque que Chrome interprète un tap ici comme
     le début d'un double-tap-zoom plutôt qu'un tap simple, immédiat — voir le
     commentaire au-dessus de #account-photo dans index.html : c'est ce qui rend un
     onclick="openAccountPhotoViewer()" direct sur la photo fiable. */
  .account-photo-wrap img{
    width:100%; height:100%; border-radius:50%; object-fit:cover; display:block;
    touch-action:manipulation; cursor:pointer;
  }
  .account-photo-menu{
    display:none; position:absolute; top:26px; left:0; z-index:20; min-width:150px;
    background:var(--card-bg); border-radius:10px; box-shadow:0 4px 16px rgba(0,0,0,0.22);
    padding:6px; flex-direction:column; gap:2px;
  }
  .account-photo-menu.open{ display:flex; }
  .account-photo-menu button{
    background:none; border:none; text-align:left; padding:8px 10px; border-radius:7px;
    font-size:13px; font-weight:600; color:var(--charcoal); cursor:pointer; font-family:'Inter';
  }
  .account-photo-menu button:active{ background:var(--paper-2); }
  .account-photo-menu button.danger{ color:var(--alert); }
  .store-menu-wrap{
    position:relative; display:inline-flex; align-items:center;
  }
  .store-menu-btn{
    width:auto; padding:6px 10px; background:var(--paper-2); border-radius:8px;
    margin:0 0 0 6px; font-size:16px; line-height:1; font-weight:700;
  }
  .store-actions-menu{
    display:none; position:absolute; top:calc(100% + 4px); right:0; z-index:25;
    min-width:210px; background:var(--card-bg); border:1px solid var(--paper-2);
    border-radius:10px; box-shadow:0 6px 20px rgba(0,0,0,0.22);
    padding:6px; flex-direction:column; gap:2px;
  }
  .store-actions-menu.open{ display:flex; }
  .store-menu-item{
    background:none; border:none; text-align:left; padding:8px 10px; border-radius:7px;
    font-size:13px; font-weight:600; color:var(--charcoal); cursor:pointer; font-family:'Inter';
    display:flex; align-items:center; justify-content:space-between; gap:8px; width:100%;
  }
  .store-menu-item:active{ background:var(--paper-2); }
  .store-menu-item.danger{ color:var(--alert); }
  .store-menu-edit-sub{
    display:none; flex-direction:column; gap:2px; padding:4px 0 4px 10px;
    margin:2px 0 4px 8px; border-left:2px solid var(--paper-2);
  }
  .store-menu-edit-sub.open{ display:flex; }
  .store-menu-subitem{
    background:none; border:none; text-align:left; padding:7px 9px; border-radius:7px;
    font-size:12.5px; font-weight:600; color:var(--charcoal); cursor:pointer; font-family:'Inter';
    display:flex; align-items:center; gap:6px; width:100%;
  }
  .store-menu-subitem:active{ background:var(--paper-2); }
  #delete-store-confirm-overlay{ z-index:40; }
  .account-status .name{font-weight:700; font-size:13.5px;}
  .account-status .email{font-size:11.5px; color:var(--charcoal-soft);}

  /* ---------- Visionneuse plein écran de la photo de profil ----------
     Voir #account-photo-viewer dans index.html, openAccountPhotoViewer()/
     closeAccountPhotoViewer() dans account-cloud.js. z-index:9000 : largement
     au-dessus des .sheet-overlay classiques (30, elle s'ouvre PAR-DESSUS
     account-overlay déjà ouvert) mais sous les blocages critiques de l'app
     (#auth-gate-overlay/#app-lock-overlay et ce qui les entoure, 99996+) — sans
     rapport avec cette visionneuse, jamais besoin de passer au-dessus. */
  .photo-viewer-overlay{
    position:fixed; inset:0; z-index:9000; background:#000;
    display:none; align-items:center; justify-content:center; overflow:hidden;
  }
  .photo-viewer-overlay.open{ display:flex; }
  .photo-viewer-overlay img{
    display:block; flex:none; width:100vw; height:100vw; aspect-ratio:1 / 1;
    object-fit:cover; border-radius:0; box-shadow:none;
  }
  /* En mode paysage, la hauteur est la dimension limitante : garder le carré
     entièrement visible plutôt que de le rogner hors de l'écran. En portrait
     (le cas téléphone demandé), l'image fait exactement 100vw × 100vw et les
     bandes noires restent visibles en haut et en bas. */
  @media (orientation:landscape){
    .photo-viewer-overlay img{ width:100vh; height:100vh; }
  }
  .photo-viewer-header{
    position:absolute; top:0; left:0; right:0;
    display:flex; align-items:flex-start; justify-content:space-between; padding:16px;
  }
  .photo-viewer-icon-btn{
    background:rgba(255,255,255,0.14); border:none; color:#fff; width:38px; height:38px;
    border-radius:50%; font-size:19px; font-weight:700; cursor:pointer; line-height:1;
    display:flex; align-items:center; justify-content:center; touch-action:manipulation;
    padding:0;
  }
  .photo-viewer-menu-wrap{ position:relative; }
  /* Même menu ⋮ (.account-photo-menu) que partout ailleurs, juste repositionné :
     sous le bouton ⋮ de la visionneuse (right:0) plutôt que sous la vignette de
     36px (left:0, règle de base ci-dessus). */
  .photo-viewer-menu-wrap .account-photo-menu{ top:46px; left:auto; right:0; }

  .stats-row{
    display:grid; grid-template-columns:1fr 1fr; gap:10px;
    padding:0 16px; margin-top:-18px; position:relative; z-index:2;
  }
  /* Les 4 tuiles du tableau de bord (ventes du jour, bénéfice du jour, bénéfice
     total, alertes) sont en RELIEF 3D, mais SANS mouvement au toucher (demande
     du 12/11/2026). Le relief vient de trois couches : le liseré clair de
     l'arête haute, la tranche basse et nette, l'ombre diffuse du socle.
     Pas de `:active` ici, volontairement : ces tuiles affichent des chiffres
     qu'on lit, et deux d'entre elles ne s'ouvrent même pas. Faire bouger un
     chiffre sous le doigt laisse croire qu'on a modifié la donnée. */
  .stat-card{
    background:var(--paper); border-radius:var(--radius); padding:14px;
    position:relative;
    box-shadow:var(--relief-bevel), var(--relief-tranche), var(--relief-socle);
  }
  /* Poubelle des tuiles du tableau de bord (« supprimer les 3/4 cartes »).
     AVANT le chantier des icônes, c'était un glyphe 🗑 de 13 px : l'émoji
     apporte sa propre couleur, donc il restait visible même à 45 % d'opacité.
     Le SVG, lui, prend `currentColor` — et à 45 % d'opacité sur du papier
     sombre, un tracé de 1,9 d'épaisseur disparaît. D'où trois réglages :
       • taille explicite, sinon 1em = 13 px, trop fin pour être vu ;
       • opacité remontée (0,6 en clair, 0,75 en sombre) ;
       • couleur explicite, `--charcoal-soft`, qui contraste sur LES DEUX fonds.
     On ne descend pas plus bas en opacité : ce bouton efface des ventes, il doit
     se voir pour qu'on ne l'atteigne pas par accident. */
  .stat-reset{
    position:absolute; top:6px; right:6px; background:none; border:none;
    color:var(--charcoal-soft); opacity:.6; cursor:pointer; padding:6px; line-height:0;
  }
  .stat-reset svg.mb-i{ width:15px; height:15px; stroke-width:2.1; }
  body.dark-mode .stat-reset{ color:var(--charcoal-soft); opacity:.75; }
  .stat-reset:active{opacity:.9;}
  .stat-card .label{font-size:10.5px; color:var(--charcoal-soft); font-weight:600; text-transform:uppercase; letter-spacing:.04em;}
  /* « Bénéfice aujourd'hui » est le libellé le plus long des trois cartes : il
     glissait sous la poubelle de reset (absolute top/right). On réserve la
     place de l'icône sur CETTE carte seulement — ni « Ventes aujourd'hui »,
     ni « Bénéfice total » ne bougent (13/11, demande de Vincent). Le padding
     fait revenir le texte d'un cheveu à gauche ; la largeur du libellé reste
     suffisante en ln/sw (applyTranslations remplace le textContent, le CSS par
     id reste valable). */
  #t-profit-today{padding-right:26px; font-size:10px;}
  .stat-card .value{font-family:'Sora'; font-size:17px; font-weight:700; margin-top:4px;}
  .stat-card .value.money{color:var(--emerald);}
  .stat-card .value.alert{color:var(--alert);}
  .stat-card.clickable{cursor:pointer;}
  .sub-expense{
    margin-top:8px; padding-top:8px; border-top:1px solid var(--paper-2);
    font-size:11px; color:var(--alert); display:flex; align-items:center; gap:5px; cursor:pointer;
  }
  .sub-expense:active{opacity:.7;}
  /* .expense-reset (le 🗑 qui vidait toutes les dépenses) a été RETIRÉ le
     12/11/2026 avec son bouton : les dépenses s'effacent une à une. La règle
     restait sans élément — code mort qui donnait à croire que le bouton existe
     encore. */
  .sub-debt{cursor:pointer;}
  .sub-debt:active{opacity:.7;}
  .cash-recon-diff-ok{color:var(--emerald);}
  .cash-recon-diff-over{color:var(--emerald-light);}
  .cash-recon-diff-under{color:var(--alert);}
  .cash-recon-note-display{
    font-size:12px; color:var(--charcoal-soft); font-style:italic;
    margin-top:6px; padding-top:6px; border-top:1px solid var(--paper-2);
  }
  .cash-recon-history-row{
    background:var(--paper-2); border-radius:10px; padding:10px 12px; margin-bottom:8px;
    position:relative;
  }
  .cash-recon-edit-btn{
    position:absolute; top:8px; right:8px;
    background:none; border:none; font-size:16px; cursor:pointer;
    padding:4px 6px; line-height:1;
  }
  .cash-recon-history-date{font-weight:700; font-size:12.5px; margin-bottom:3px;}
  .cash-recon-history-amounts{font-size:12.5px; color:var(--charcoal-soft);}

  .toggle-row{
    display:flex; align-items:center; justify-content:space-between;
    background:var(--paper-2); border-radius:12px; padding:12px 14px; margin-bottom:14px;
  }
  .toggle-row span{font-size:13.5px; font-weight:600;}
  .switch{position:relative; width:44px; height:25px; flex-shrink:0;}
  .switch input{opacity:0; width:0; height:0;}
  .switch-track{
    position:absolute; inset:0; background:var(--charcoal-soft); opacity:.35;
    border-radius:999px; cursor:pointer; transition:background .2s ease;
  }
  .switch-track::before{
    content:""; position:absolute; width:19px; height:19px; left:3px; top:3px;
    background:#fff; border-radius:50%; transition:transform .2s ease;
  }
  .switch input:checked + .switch-track{background:var(--emerald); opacity:1;}
  .switch input:checked + .switch-track::before{transform:translateX(19px);}

  .debt-item{
    background:var(--card-bg); border-radius:12px; padding:13px 14px; margin-bottom:10px;
  }
  .debt-item .top-row{display:flex; justify-content:space-between; align-items:flex-start; gap:8px;}
  .debt-item .name{font-weight:700; font-size:14px;}
  .client-call-link{color:var(--emerald); text-decoration:none;}
  .client-call-link:active{opacity:.7;}
  .debt-item .phone{font-size:11.5px; color:var(--charcoal-soft); margin-top:1px;}
  .debt-item .owed{font-family:'Sora'; font-weight:800; font-size:15px; color:var(--alert); white-space:nowrap;}
  .debt-item .items-list{font-size:11.5px; color:var(--charcoal-soft); margin-top:6px; line-height:1.5;}
  /* Zone tapable qui ouvre la fiche détaillée d'une dette (openDebtStatementSheet,
     voir debt-statement.js) — le nom/téléphone en haut, et le résumé des produits
     juste en dessous ; le montant dû et les boutons ✏️/🗑/Régler restent en dehors. */
  .debt-tap-zone{cursor:pointer;}
  .detail-chevron{color:var(--emerald); font-weight:700; margin-left:2px;}
  .debt-item .due{font-size:11px; color:var(--marigold); margin-top:4px; font-weight:600;}
  .debt-item .pay-btn{
    width:100%; margin-top:10px; background:var(--emerald); color:#fff; border:none;
    border-radius:9px; padding:9px; font-size:12.5px; font-weight:700; font-family:'Inter'; cursor:pointer;
  }

  main{flex:1; padding:20px 16px 100px;}
  section{margin-bottom:22px;}
  section h2{font-size:14px; text-transform:uppercase; letter-spacing:.05em; color:var(--charcoal-soft); margin:0 0 10px; font-weight:700;}

  /* `flex-wrap` : sur un téléphone étroit (320 px), trois cartes qui épousent
     leur texte peuvent dépasser la largeur. Elles passent alors à la ligne au
     lieu de déborder hors de l'écran — la mise en page se dégrade, elle ne
     casse pas. `min-width:0` sur les cartes permet au libellé de se replier
     DANS sa carte si besoin, sans élargir la rangée. */
  /* RANGÉE DE CLASSEMENT (corrigée le 12/11/2026, capture de Vincent à l'appui).
     Historique de la règle, parce qu'on a déjà tourné autour du problème :
       • origine : space-between → trois onglets larges bien répartis, mais du
         texte nu, sans carte ;
       • 12/11 : cartes à leur taille + space-between retiré → les cartes se
         sont collées à gauche et les vides entre elles sont devenus inégaux
         (« c'est mal classé »). C'est ce que montraient les deux captures.
     LA BONNE RÉPONSE n'est ni space-between (écarts commandés par la largeur de
     la fenêtre, donc jamais deux fois les mêmes) ni « collés à gauche ».
     C'est `grid` à trois colonnes ÉGALES, avec les cartes centrées dans leur
     colonne :
       • largeur identique quel que soit le texte → les trois centres tombent
         exactement au tiers, à la moitié et aux deux tiers de la ligne. C'est
         ce qui « classe » la rangée, et ça ne dépend pas du téléphone ;
       • la rangée occupe toute la largeur, donc aucune place perdue d'un côté ;
       • `justify-self:center` : la carte est centrée DANS sa colonne, elle ne
         s'étire pas — on garde des cartes à la taille de leur texte, ce que
         Vincent voulait au départ.
     `repeat(3, minmax(0, 1fr))` et non `1fr` seul : `minmax(0, …)` autorise une
     colonne à rétrécir sous la largeur de son contenu, donc plus de débordement
     sur un écran étroit (un `1fr` seul garde un minimum `auto` qui déborde).
     Il n'y a plus de `flex-wrap` : avec des colonnes égales, un retour à la
     ligne casserait précisément l'alignement qu'on cherche. */
  .products-header-row{
    display:grid;
    grid-template-columns:repeat(3, minmax(0, 1fr));
    align-items:center; justify-items:center;
    column-gap:6px;
    margin:0 0 12px;
  }
  /* « MES PRODUITS » / « CATÉGORIES » / « FAVORIS » : chacun est enveloppé dans
     une CARTE, centrée dans sa colonne.
     RELIEF : un cran au-dessus des tuiles du tableau de bord (tranche de 4 px
     contre 3 px), pour que ces cartes se lisent comme des boutons.
     APPUI : c'est ICI, et nulle part ailleurs dans l'application, que le
     mouvement est franc — la carte descend de 3 px, sa tranche disparaît, son
     ombre se resserre. Plus marqué que sur les cartes du compte (2 px, effet
     retiré le 12/11/2026 à la demande de Vincent) : ces trois-là sont les seules
     à devoir donner l'impression d'un vrai bouton qu'on presse. */
  .products-tab{
    /* TAILLE FLUIDE, mesurée sur la vraie police et non estimée.
       Inter gras, capitales, mesuré au fichier de police : « MES PRODUITS »
       fait 7,56 px par caractère à 12,5 px. Avec 10 px de marge de chaque côté
       et 2 px de bordure, la carte demande 116 px — pour une colonne de 105 px
       sur un téléphone de 360 px. Elle débordait de 11 px.
       D'où `clamp()` plutôt qu'une taille fixe : la police suit la largeur de
       l'écran. À 320 px → 10 px ; à 360 px → 11,2 px ; à 390 px → 12,1 px ;
       à 412 px et au-delà → 12,5 px. Dans les quatre cas le libellé tient dans
       son tiers de ligne, avec le padding qui suit la même règle. C'est ce que
       veut dire « s'adapter à n'importe quel écran » : une seule mise en page
       qui se met à l'échelle, pas trois cas particuliers.
       `min-width:0` + `overflow-wrap:anywhere` restent en filet : si un jour un
       libellé est plus long que sa colonne, il se replie dans sa carte au lieu
       de sortir de l'écran. */
    display:inline-flex; align-items:center; justify-content:center; gap:3px;
    padding:8px clamp(4px, 1.4vw, 9px); margin:0; cursor:pointer; font-family:'Inter';
    min-width:0; max-width:100%;
    font-size:clamp(10px, 3.1vw, 12.5px); text-transform:uppercase; letter-spacing:0; font-weight:700;
    overflow-wrap:anywhere; text-align:center;
    color:var(--charcoal-soft); opacity:.55;
    background:var(--card-bg); border:1px solid rgba(255,255,255,.85);
    border-radius:12px;
    /* Tranche plus épaisse que le relief statique : 4 px. */
    box-shadow:0 4px 0 rgba(38,34,29,.12),
               inset 0 1px 0 rgba(255,255,255,.9),
               0 6px 13px rgba(38,34,29,.10);
    transition:transform .08s ease-out, box-shadow .08s ease-out,
               color .15s, opacity .15s;
    -webkit-tap-highlight-color:transparent;
  }
  body.dark-mode .products-tab{
    border-color:rgba(255,255,255,.14);
    box-shadow:0 4px 0 rgba(0,0,0,.40),
               inset 0 1px 0 rgba(255,255,255,.20),
               0 6px 13px rgba(0,0,0,.38);
  }
  /* L'appui : 3 px vers le bas (contre 2 px sur les cartes du compte), et la
     tranche de 4 px s'efface d'autant — la carte est plaquée au papier. */
  .products-tab:active{
    transform:translateY(3px);
    box-shadow:0 1px 0 rgba(38,34,29,.12),
               inset 0 1px 0 rgba(255,255,255,.6),
               0 2px 5px rgba(38,34,29,.08);
  }
  body.dark-mode .products-tab:active{
    box-shadow:0 1px 0 rgba(0,0,0,.40),
               inset 0 1px 0 rgba(255,255,255,.14),
               0 2px 5px rgba(0,0,0,.30);
  }
  .products-tab:focus-visible{ outline:2px solid var(--emerald); outline-offset:2px; }
  /* Actif : couleur d'accent propre (pas juste l'opacité remontée à 1 sur la même
     couleur que les 2 autres) — repérable au premier coup d'œil sur les deux thèmes.
     emerald-light plutôt que emerald : même choix que le reste de l'app pour tout ce
     qui doit rester lisible aussi bien sur le fond clair que sur le fond sombre (voir
     .dot.green, le lien "s'inscrire" de l'écran de connexion...). */
  /* Onglet actif : couleur d'accent ET carte légèrement plus haute (tranche de
     5 px). Deux signaux valent mieux qu'un : sur un écran de téléphone en plein
     soleil, une différence de couleur seule se perd. */
  .products-tab.active{
    color:var(--emerald-light); opacity:1;
    box-shadow:0 5px 0 rgba(38,34,29,.14),
               inset 0 1px 0 rgba(255,255,255,.95),
               0 7px 15px rgba(38,34,29,.12);
  }
  body.dark-mode .products-tab.active{
    box-shadow:0 5px 0 rgba(0,0,0,.46),
               inset 0 1px 0 rgba(255,255,255,.24),
               0 7px 15px rgba(0,0,0,.42);
  }
  /* Actif ET pressé : il doit s'enfoncer comme les autres, sinon l'onglet
     courant serait le seul à ne pas réagir au doigt. */
  .products-tab.active:active{
    box-shadow:0 1px 0 rgba(38,34,29,.12),
               inset 0 1px 0 rgba(255,255,255,.6),
               0 2px 5px rgba(38,34,29,.08);
  }

  .alert-banner{
    background:var(--alert-bg); border-left:4px solid var(--alert);
    border-radius:10px; padding:10px 12px; margin-bottom:10px; font-size:13.5px;
  }
  .alert-banner b{color:var(--alert);}
  /* Alerte de stock bas : texte à gauche, bouton « Réappro » à DROITE du produit
     (demande d'origine) — la ligne est flex pour que le bouton reste collé au bord
     droit même quand le nom du produit est long et passe sur deux lignes. */
  .alert-reorder-row{display:flex; align-items:center; justify-content:space-between; gap:10px;}
  .alert-reorder-row .alert-reorder-text{min-width:0;}
  .alert-reorder-btn{
    flex-shrink:0; white-space:nowrap; background:var(--emerald); color:#fff; border:none;
    border-radius:8px; padding:6px 10px; font-size:12px; font-weight:700; cursor:pointer;
  }
  .alert-reorder-btn:active{opacity:.85;}
  /* Variante verrouillée : pas de Fournisseurs (palier non Pro ou interrupteur éteint).
     Le cadenas + la teinte atténuée signalent en un coup d'œil que le geste n'est pas
     disponible sur ce palier ; le tap ouvre l'écran « palier requis » standard (voir
     reorderFromAlerts(), suppliers.js) — le signalement part de la fiche Alertes. */
  .alert-reorder-btn.locked{
    background:var(--paper-2); color:var(--charcoal-soft);
  }
  .alert-activity-row{display:flex; justify-content:space-between; align-items:center; gap:8px;}
  .alert-activity-row button.del-entry{
    background:none; border:none; color:var(--charcoal-soft); font-size:14px; cursor:pointer; padding:4px; flex-shrink:0;
  }
  .alert-activity-row button.del-entry:active{color:var(--alert);}

  /* ---------- Bande d'alerte défilante (sous les statistiques) ----------
     Icône fixe à gauche, puis les alertes qui défilent de droite à gauche, séparées par
     un carreau ◆. Le texte « Vérifie les alertes… » reste dans le DOM (lecteurs d'écran)
     mais n'est plus affiché. Les deux bords s'estompent pour que le défilement ne coupe
     pas brutalement le texte. */
  .alert-ticker{ display:flex; align-items:center; gap:8px; padding:9px 12px; min-width:0; }
  .alert-ticker-icon{ flex-shrink:0; line-height:1; }
  .alert-ticker-prompt{
    position:absolute; width:1px; height:1px; overflow:hidden; clip:rect(0 0 0 0); white-space:nowrap;
  }
  .alert-ticker-viewport{
    flex:1; min-width:0; overflow:hidden; position:relative;
    -webkit-mask-image:linear-gradient(to right, transparent 0, #000 14px, #000 calc(100% - 14px), transparent 100%);
            mask-image:linear-gradient(to right, transparent 0, #000 14px, #000 calc(100% - 14px), transparent 100%);
  }
  .alert-ticker-track{
    display:flex; width:max-content; will-change:transform;
    animation:alert-ticker-scroll var(--alert-ticker-duration, 30s) linear infinite;
  }
  .alert-ticker-block{ display:flex; flex-shrink:0; white-space:nowrap; font-size:13.5px; }
  .alert-ticker-item{ color:var(--charcoal); font-weight:600; }
  .alert-ticker-sep{ color:var(--alert); margin:0 12px; font-size:11px; line-height:1.9; }
  @keyframes alert-ticker-scroll{
    from{ transform:translate3d(0,0,0); }
    to{ transform:translate3d(-50%,0,0); }
  }
  @media(prefers-reduced-motion:reduce){
    .alert-ticker-track{ animation:none; }
  }
  .alert-clear-all{margin-bottom:10px;}
  .empty{
    text-align:center; color:var(--charcoal-soft); font-size:13.5px;
    padding:26px 10px; border:1.5px dashed var(--paper-2); border-radius:var(--radius);
  }

  .search-wrap{position:relative; margin-bottom:12px;}
  /* Recherche + tri : le champ occupe 90 % et le bouton de tri 10 % de l'espace
     disponible (après l'écart), sur les deux listes concernées uniquement. */
  .product-search-toolbar{display:flex; align-items:stretch; gap:4px; width:100%; margin-bottom:12px;}
  .product-search-toolbar .search-wrap{flex:9 1 0; min-width:0; margin:0;}
  .product-search-toolbar .search-wrap input{height:48px; margin:0;}
  .product-sort-trigger{
    flex:1 1 0; min-width:0; padding:0 2px; height:48px;
    display:flex; align-items:center; justify-content:center; border-radius:12px;
    border:1.5px solid var(--paper-2); background:var(--card-bg); color:var(--emerald);
    cursor:pointer; touch-action:manipulation;
  }
  .product-sort-trigger:active{background:var(--ok-bg);}
  .product-sort-trigger svg{width:18px; height:18px; fill:none; stroke:currentColor; stroke-width:1.8; stroke-linecap:round; stroke-linejoin:round;}
  .multi-search.product-search-toolbar{margin-bottom:14px;}
  .multi-search.product-search-toolbar .search-wrap input{margin:0;}

  /* Barre de défilement rapide (src/fast-scrollbar.js) : large pour le doigt (piste de
     48 px), poignée qui suit la position, bulle latérale qui suit la poignée. Elle
     n'est visible qu'en défilant ou en la touchant, et en OPACITÉ RÉDUITE (.85) :
     elle se superpose au bord droit de la liste, elle doit guider l'œil sans manger
     les fiches produit. */
  .fast-scrollbar{
    position:absolute; display:flex; opacity:0; visibility:hidden; pointer-events:none;
    transition:opacity .18s ease, visibility .18s ease;
  }
  .fast-scrollbar.visible{ opacity:.85; visibility:visible; pointer-events:auto; }
  .fast-scrollbar-track{
    position:relative; width:48px; height:100%;
    background:rgba(0,0,0,0.08); border-radius:24px;
    cursor:pointer; touch-action:none; -webkit-tap-highlight-color:transparent;
  }
  .fast-scrollbar-thumb{
    position:absolute; left:4px; right:4px; top:0; height:56px;
    background:var(--emerald); border-radius:20px;
    box-shadow:0 2px 8px rgba(0,0,0,0.15); transition:background .15s ease;
  }
  .fast-scrollbar-thumb.dragging{ background:var(--emerald-dark, #059669); }
  .fast-scrollbar-bubble{
    position:absolute; right:56px; transform:translateY(-50%);
    background:var(--card-bg); color:var(--emerald); border:1.5px solid var(--paper-2);
    border-radius:12px; padding:8px 12px; font-size:13px; font-weight:600;
    box-shadow:var(--shadow); white-space:nowrap; pointer-events:none;
    opacity:0; transition:opacity .15s ease;
  }
  .fast-scrollbar.visible .fast-scrollbar-bubble{ opacity:1; }
  .product-fast-scrollbar{
    position:fixed; z-index:24; top:50%; transform:translate3d(8px,-50%,0);
    right:max(8px, calc((100vw - 480px)/2 + 8px)); height:min(70vh, 520px);
    transition:opacity .18s ease, visibility .18s ease, transform .18s ease;
  }
  .product-fast-scrollbar.visible{ transform:translate3d(0,-50%,0); }
  @media(prefers-reduced-motion:reduce){
    .fast-scrollbar, .fast-scrollbar-thumb, .fast-scrollbar-bubble{ transition:none; }
  }

  /* ---------- Compteur de produits (X/Y selon le palier) ---------- */
  .products-counter{
    font-size:12.5px; font-weight:600; margin:-4px 0 10px; display:flex; align-items:center; gap:6px;
  }
  .products-counter .bar-track{
    flex:1; height:6px; border-radius:4px; background:var(--paper-2); overflow:hidden; max-width:120px;
  }
  .products-counter .bar-fill{ height:100%; border-radius:4px; background:var(--emerald); transition:width .2s; }
  .products-counter.near-limit .bar-fill{ background:var(--marigold); }
  .products-counter.at-limit{ color:var(--alert); }
  .products-counter.at-limit .bar-fill{ background:var(--alert); }
  .search-wrap input{
    width:100%; padding:12px 14px 12px 38px; border-radius:12px;
    border:1.5px solid var(--paper-2); background:var(--card-bg); font-size:14px;
    font-family:'Inter'; color:var(--charcoal);
  }
  .search-wrap input:focus{outline:2px solid var(--emerald-light); border-color:transparent;}
  .search-wrap .icon{
    position:absolute; left:12px; top:50%; transform:translateY(-50%);
    font-size:15px; opacity:.5; pointer-events:none;
  }

  .load-more-btn{
    width:100%; margin-top:4px; margin-bottom:10px; padding:12px;
    border:1px dashed var(--charcoal-soft); color:var(--emerald);
    background:var(--paper-2); border-radius:var(--radius);
    font-family:'Inter'; font-size:13.5px; font-weight:700; cursor:pointer;
  }
  .load-more-btn:active{opacity:.7;}

  .product-card{
    background:var(--card-bg); border-radius:var(--radius); box-shadow:var(--shadow);
    padding:14px; margin-bottom:10px; display:flex; flex-direction:column; gap:10px;
  }
  /* ---------- Ligne du haut : point de stock + nom (lisible, plusieurs lignes si besoin) + prix ---------- */
  .product-card-top{display:flex; align-items:flex-start; gap:10px;}
  .product-card .dot{width:10px; height:10px; border-radius:50%; flex-shrink:0; margin-top:5px;}
  .dot.green{background:var(--emerald-light);}
  .dot.amber{background:var(--marigold);}
  .dot.red{background:var(--alert);}
  .product-card .info{flex:1; min-width:0;}
  /* Le nom n'est plus tronqué en une seule ligne : avec les boutons d'action passés sur
     leur propre ligne (.product-card-bottom), il a toute la largeur de la carte (moins le
     prix) pour s'afficher en entier, quitte à passer sur 2 lignes pour un nom long. */
  .product-card .info .name{font-weight:700; font-size:14.5px; line-height:1.3; word-break:break-word;}
  .product-card .info .meta{font-size:12px; color:var(--charcoal-soft); margin-top:3px;}
  .product-card .price{font-family:'Sora'; font-weight:700; font-size:14px; color:var(--emerald); white-space:nowrap; text-align:right; flex-shrink:0; margin-top:1px;}
  /* ---------- Ligne du bas : "Vendre" élargi + icônes bien espacées ----------
     Avant, les 4-5 boutons (vendre/modifier/dupliquer/transférer/supprimer) se
     partageaient la même ligne que le nom et le prix, serrés les uns contre les
     autres — un appui pouvait facilement tomber sur la mauvaise icône. Ici, ils ont
     leur propre ligne, avec un espacement (gap) et une zone de tap par icône plus
     grands, exprès pour réduire ce risque. */
  .product-card-bottom{display:flex; align-items:center; gap:18px;}
  /* « Vendre », sur chaque produit (demande du 12/11/2026) : en RELIEF 3D, sans
     mouvement au toucher.
     Le relief d'un bouton sombre ne se voit pas avec l'ombre claire des cartes
     (elle disparaîtrait sur du vert) : ici c'est une TRANSLUCIDE qui fait
     l'arête — un fil de lumière sur le dessus du bouton — et une ombre colorée
     sous lui, de la couleur du bouton. Une ombre noire sur du vert foncé donne
     un halo sale ; une ombre verte donne l'impression que le bouton est posé.
     Pas de `:active` : Vincent veut le relief, pas le clic (il l'a dit pour les
     cartes du tableau de bord, et ces trois-là sont de la même famille). */
  .product-card button.sell{
    flex:1; background:var(--emerald); color:#fff; border:none; border-radius:10px;
    padding:12px; font-size:13.5px; font-weight:700; font-family:'Inter'; cursor:pointer; white-space:nowrap;
    box-shadow:inset 0 1px 0 rgba(255,255,255,.30),
               0 3px 0 rgba(9,54,46,.55),
               0 5px 11px rgba(20,99,86,.34);
  }
  /* ---------- Les 4 icônes qui suivent le bouton « Vendre » ----------
     Modifier, dupliquer, transférer, supprimer (demande de Vincent,
     13/11/2026 : « les 4 icônes qui accompagnent chaque carte doivent être en
     gras aussi »). Le bouton « Vendre » n'est PAS concerné : il porte du texte.
     Ce qui a changé avec le passage aux SVG : `font-size` ne grossit plus une
     icône. Un glyphe 🗑 de 17 px était épais ; le tracé SVG de 1,9 d'épaisseur
     à la même taille paraît maigre. On agit donc sur TROIS leviers :
       • `stroke-width` explicite, 2,6 au lieu de 1,9 ;
       • une taille qui ne dépend plus de la police du bouton ;
       • une couleur pleine. En mode sombre, `--charcoal-soft` seul donne un
         tracé gris qui se perd sur le fond charbon : on remonte à la couleur du
         texte principal, avec un léger halo clair qui détache le tracé.
     La couleur rouge de « supprimer » et verte des autres au toucher est
     conservée : c'est le repère de danger, on n'y touche pas. */
  .product-card button.del,
  .product-card button.edit{
    background:transparent; border:none; color:var(--charcoal-soft);
    cursor:pointer; padding:11px; flex-shrink:0; line-height:0;
  }
  .product-card button.del svg.mb-i,
  .product-card button.edit svg.mb-i{
    width:19px; height:19px; stroke-width:2.6;
  }
  .product-card button.del:active{color:var(--alert);}
  .product-card button.edit:active{color:var(--emerald);}
  /* En sombre : tracé plus clair et plus franc. Sans ça, les quatre icônes
     disparaissent dans le fond — même symptôme que la poubelle des tuiles. */
  body.dark-mode .product-card button.del,
  body.dark-mode .product-card button.edit{
    color:var(--charcoal); opacity:.85;
  }
  body.dark-mode .product-card button.del svg.mb-i,
  body.dark-mode .product-card button.edit svg.mb-i{
    stroke-width:2.7;
  }
  body.dark-mode .product-card button.del:active{color:var(--alert);}
  body.dark-mode .product-card button.edit:active{color:var(--emerald-light);}

  /* ---------- Produit gelé (au-delà de la limite du palier) ----------
     Grisé + non cliquable : le commerçant voit encore ce qu'il a ajouté
     (rien n'est perdu) mais comprend immédiatement qu'il doit passer au
     palier supérieur pour le réactiver. Le badge cadenas remplace les
     boutons d'action habituels (vendre/modifier/dupliquer/supprimer), sur
     toute la largeur de la ligne du bas (flex:1) plutôt qu'un simple bouton
     perdu à gauche. */
  .product-card.frozen{ opacity:.5; filter:grayscale(60%); }
  .product-card.frozen .info .name{ color:var(--charcoal-soft); }
  .product-card.frozen .price{ color:var(--charcoal-soft); }
  /* Ne laisse pas le filtre gris du palier effacer le signal rouge de prix manquant. */
  .product-card.frozen.has-missing-buy-price{filter:none;}
  /* Le prix de vente reste rouge tant que le coût d'achat n'est pas renseigné
     (notamment les produits du catalogue rapide créés avec buy=0). */
  .product-card .price.missing-buy-price{color:var(--alert);}
  .product-card .frozen-badge{
    background:transparent; border:none; color:var(--charcoal-soft);
    font-size:13px; cursor:pointer; padding:10px 8px; flex:1;
    white-space:nowrap; font-family:'Inter'; font-weight:700;
  }

  .fab-row{
    position:fixed; left:50%; transform:translateX(-50%); bottom:82px;
    width:calc(100% - 32px); max-width:448px; display:flex; gap:10px; z-index:20;
  }
  .fab{
    flex:1; border:none; border-radius:14px; padding:15px; font-size:14.5px; font-weight:800;
    font-family:'Sora'; cursor:pointer;
  }
  /* « + Produit » et « Dépense » : RELIEF 3D, sans mouvement (12/11/2026).
     L'ombre diffuse d'origine est conservée — c'est elle qui décolle la barre du
     contenu qui défile dessous — et on ajoute par-dessus l'arête claire et la
     tranche, pour que le bouton ait une épaisseur et pas seulement un halo.
     L'arête est blanche à 34 % et non à 90 % comme sur les cartes claires : sur
     un aplat saturé (marigold, slate), un liseré franc ferait un reflet de
     plastique. */
  .fab-row{ perspective:600px; }
  .fab-left{
    background:var(--marigold); color:var(--charcoal);
    box-shadow:inset 0 1px 0 rgba(255,255,255,.34),
               0 3px 0 rgba(150,96,18,.45),
               0 6px 18px rgba(232,162,59,0.4);
  }
  .fab-right{
    background:var(--slate); color:#fff;
    box-shadow:inset 0 1px 0 rgba(255,255,255,.26),
               0 3px 0 rgba(41,54,49,.55),
               0 6px 18px rgba(90,107,107,0.35);
  }
  /* ---------- Les trois boutons ronds du bas ----------
     Micro (vente vocale), appareil photo (scan de code-barres), panier (vente
     plusieurs). Chacun EXPLIQUE une fonctionnalité à lui seul (demande de
     Vincent, 13/11/2026) : l'icône doit donc se lire sans légende.
     CE QUI N'ALLAIT PAS : l'icône vaut 1em, soit la taille de la police du
     bouton — 20 px pour l'appareil photo et le panier dans un rond de 48 px,
     posée en haut à gauche et non centrée. Résultat : trois ronds vides à
     l'écran, alors que le SVG était bien là. La taille est maintenant posée
     explicitement, et le conteneur centre son contenu.
     Le tracé est aussi plus épais que dans le texte (2,6 contre 1,9) : à 26 px
     dans un rond blanc sur vert, un trait fin se perd. */
  .voice-fab{
    position:fixed; left:50%; transform:translateX(-50%); bottom:16px;
    width:64px; height:64px; border-radius:50%; border:none;
    background:var(--emerald); color:#fff;
    display:flex; align-items:center; justify-content:center;
    box-shadow:inset 0 1px 0 rgba(255,255,255,.28),
               0 3px 0 rgba(9,54,46,.5),
               0 6px 18px rgba(20,99,86,0.45);
    z-index:22; cursor:pointer;
    transition:background .15s ease, transform .15s ease; -webkit-tap-highlight-color:transparent;
    user-select:none;
  }
  /* Taille ET centrage de l'icône dans les trois boutons ronds. */
  .voice-fab > .mb-i-slot{ display:flex; align-items:center; justify-content:center; line-height:0; }
  .voice-fab svg.mb-i{ width:30px; height:30px; stroke-width:2.2; }
  .barcode-fab svg.mb-i,
  .held-carts-fab svg.mb-i{ width:24px; height:24px; stroke-width:2.4; }
  .voice-fab.listening svg.mb-i{ stroke-width:2.6; }
  /* Libellé lu par les lecteurs d'écran seulement : les trois boutons n'ont
     aucun texte visible, et un rond sans nom est un rond muet pour qui ne voit
     pas l'icône. */
  .voice-fab .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;
  }
  .voice-fab.pressed{
    transform:translateX(-50%) scale(1.1);
  }
  .voice-fab.listening{
    background:var(--alert); transform:translateX(-50%) scale(1.1);
    animation:voiceWave 1.2s ease-out infinite;
  }
  /* Le bouton scanner se place juste à côté du micro (plus petit, non centré) plutôt que de
     lui superposer — les deux restent accessibles au pouce sans se gêner. */
  .barcode-fab{
    left:calc(50% + 62px); transform:none; width:48px; height:48px; font-size:20px;
    background:var(--slate); box-shadow:0 6px 18px rgba(90,107,107,0.35);
  }
  /* En miroir de .barcode-fab, côté gauche du micro. Le barcode-fab est positionné par son
     bord GAUCHE (left:50%+62px, largeur 48px) ; pour un espacement identique de l'autre
     côté, on doit ancrer held-carts-fab par son bord DROIT au même decalage — d'où
     50% - 62px - 48px = 50% - 110px, et non 50% - 62px (qui alignait les bords gauches
     des deux boutons et faisait chevaucher son bord droit sur le micro). */
  .held-carts-fab{
    left:calc(50% - 110px); transform:none; width:48px; height:48px; font-size:20px;
    background:var(--slate); box-shadow:0 6px 18px rgba(90,107,107,0.35);
    position:fixed;
  }
  .held-cart-row{
    display:flex; align-items:center; gap:8px; padding:12px 0;
    border-bottom:1px solid var(--paper-2);
  }
  .held-cart-row:last-child{border-bottom:none;}
  .held-cart-row .info{flex:1; min-width:0;}
  .held-cart-row .info .name{font-weight:700; font-size:14px;}
  .held-cart-row .info .meta{font-size:12px; color:var(--charcoal-soft); margin-top:2px;}
  .held-cart-row .held-cart-drop{width:40px; padding:8px; flex:none;}
  .held-cart-row .held-cart-resume{width:auto; padding:8px 14px; flex:none; font-size:13px;}
  .account-bottom-links{display:flex; justify-content:space-between; gap:14px; margin-top:16px;}
  .account-bottom-col{display:flex; flex-direction:column; gap:8px; flex:1; min-width:0;}
  .account-bottom-links a{font-size:12.5px; color:var(--emerald); text-decoration:underline; font-weight:600;}
  .account-bottom-col .account-privacy-link{
    display:inline; width:auto; background:none; border:none; padding:0; margin:0;
    text-align:left; font-size:11.5px; color:var(--charcoal-soft); text-decoration:underline;
    cursor:pointer; font-weight:600;
  }
  .account-privacy-link{
    display:block; width:100%; text-align:center; background:none; border:none;
    font-size:12px; color:var(--charcoal-soft); text-decoration:underline; margin:10px 0 14px;
    font-family:'Inter'; cursor:pointer;
  }
  @keyframes voiceWave{
    0%{ box-shadow:0 0 0 0 rgba(192,57,43,0.55), 0 0 0 0 rgba(192,57,43,0.35); }
    60%{ box-shadow:0 0 0 18px rgba(192,57,43,0), 0 0 0 34px rgba(192,57,43,0); }
    100%{ box-shadow:0 0 0 18px rgba(192,57,43,0), 0 0 0 34px rgba(192,57,43,0); }
  }

  .sheet-overlay{
    position:fixed; inset:0; background:rgba(38,34,29,0.45);
    display:none; align-items:flex-end; z-index:30;
  }
  .sheet-overlay.open{display:flex;}
  /* Popups qui doivent apparaître au milieu de l'écran (comme une vraie fenêtre modale)
     plutôt qu'en fiche remontant du bas — ex : l'écran de choix du palier. */
  .sheet-overlay.center-modal{
    align-items:flex-start; padding:20px;
    overflow-y:auto; overscroll-behavior:contain;
  }
  .sheet-overlay.center-modal .sheet{
    border-radius:20px; max-height:none; margin:auto;
  }
  /* Fiches de scan code-barres (voir barcode.js) : ombre plus marquée pour se
     détacher de l'écran de visée qu'elles recouvrent. */
  .scan-card{ box-shadow:0 8px 30px rgba(38,34,29,0.3); }
  .bulk-catalog-row{
    display:flex; align-items:center; gap:10px; padding:9px 2px;
    border-bottom:1px solid var(--paper-2); font-size:14px; cursor:pointer;
  }
  .bulk-catalog-row:last-child{ border-bottom:none; }
  .bulk-catalog-row input[type=checkbox]{ width:18px; height:18px; flex-shrink:0; }
  .bulk-catalog-row.disabled{ opacity:0.4; cursor:not-allowed; }
  /* Contrôle unique "Tout cocher/décocher" au-dessus de la liste (voir index.html,
     bulk-catalog-overlay) : centré plutôt que deux boutons côte à côte, une vraie case
     à cocher qui montre l'état réel au lieu d'un texte seul. */
  .bulk-select-all-toggle{
    display:flex; align-items:center; justify-content:center; gap:8px;
    width:fit-content; padding:8px 18px; border:1.5px solid var(--paper-2); border-radius:999px;
    font-size:12.5px; font-weight:700; color:var(--charcoal-soft); cursor:pointer;
  }
  .bulk-select-all-toggle input[type=checkbox]{ width:17px; height:17px; margin:0; accent-color:var(--emerald); cursor:pointer; }
  /* Onglet "💡 Découvrir" : FAQ à accordéon */
  .discover-item{ border-bottom:1px solid var(--paper-2); }
  .discover-item:last-child{ border-bottom:none; }
  .discover-item-header{
    width:100%; display:flex; align-items:center; gap:10px; background:none; border:none;
    padding:14px 2px; font-family:'Inter'; font-size:14px; font-weight:700; color:var(--charcoal);
    cursor:pointer; text-align:left; -webkit-tap-highlight-color:transparent;
  }
  .discover-item-icon{ font-size:18px; flex-shrink:0; }
  .discover-item-title{ flex:1; }
  .discover-chevron{
    font-size:20px; color:var(--charcoal-soft); flex-shrink:0;
    transition:transform .2s ease;
  }
  .discover-item.open .discover-chevron{ transform:rotate(90deg); color:var(--emerald); }
  .discover-item-body{
    max-height:0; overflow:hidden; transition:max-height .25s ease;
    font-size:13.3px; color:var(--charcoal-soft); line-height:1.65;
  }
  .discover-item-body p{ margin:0 0 14px 2px; }
  .discover-item.open .discover-item-body{ max-height:600px; }

  /* Onglet "Catégories" (voir product-categories.js) : même mécanique d'accordéon que
     "💡 Découvrir" ci-dessus (transition sur max-height), mais un plafond bien plus
     haut à l'ouverture — une catégorie peut contenir jusqu'à 60 produits affichés
     d'un coup (+ le bouton "Afficher plus" + "+ Ajouter un produit"), pas juste un
     paragraphe d'explication. */
  .category-item{ border-bottom:1px solid var(--paper-2); }
  .category-item:last-child{ border-bottom:none; }
  .category-item-header{
    width:100%; display:flex; align-items:center; gap:8px; background:none; border:none;
    padding:14px 2px; font-family:'Inter'; font-size:14.5px; font-weight:700; color:var(--charcoal);
    cursor:pointer; text-align:left; -webkit-tap-highlight-color:transparent;
  }
  .category-item-title{ flex:1; }
  .category-item-count{ font-weight:500; color:var(--charcoal-soft); margin-left:4px; }
  .category-edit-btn{ font-size:15px; padding:4px; flex-shrink:0; cursor:pointer; }
  .category-chevron{
    font-size:20px; color:var(--charcoal-soft); flex-shrink:0;
    transition:transform .2s ease;
  }
  .category-item.open .category-chevron{ transform:rotate(90deg); color:var(--emerald-light); }
  .category-item-body{
    max-height:0; overflow:hidden; transition:max-height .3s ease;
  }
  /* Plafond volontairement très large : les fiches produit complètes (buildProductCardElement(),
     render.js) sont bien plus hautes que les anciennes lignes compactes nom+stock — jusqu'à
     60 produits affichés d'un coup dans une catégorie (+ "Afficher plus" qui peut encore
     accumuler plusieurs pages). Un plafond trop juste couperait le bas d'une catégorie bien
     fournie une fois dépliée (overflow:hidden ci-dessus). Un max-height élevé ne ralentit pas
     la transition (toujours .3s ci-dessus) ni ne coûte rien tant que la catégorie est fermée. */
  .category-item.open .category-item-body{ max-height:200000px; }
  .category-item-body-inner{ padding:0 2px 14px; }
  .category-empty-hint{ font-size:12.5px; color:var(--charcoal-soft); margin:0 0 10px; }
  /* Saisie rapide en tableau */
  .grid-add-header{
    display:grid; grid-template-columns:2.1fr 1fr 1fr 0.85fr 22px; gap:6px;
    font-size:11px; font-weight:700; color:var(--charcoal-soft); text-transform:uppercase;
    padding:0 2px 6px; letter-spacing:.02em;
  }
  .grid-add-row{
    display:grid; grid-template-columns:2.1fr 1fr 1fr 0.85fr 22px; gap:6px;
    align-items:center; margin-bottom:8px;
  }
  .grid-add-row input{
    width:100%; padding:9px 8px; border-radius:8px; border:1.5px solid var(--paper-2);
    background:var(--card-bg); font-size:13px; font-family:'Inter'; color:var(--charcoal); min-width:0;
  }
  .grid-add-row input:focus{ outline:2px solid var(--emerald-light); border-color:transparent; }
  .grid-add-row .grid-row-del{
    background:none; border:none; color:var(--alert); font-size:16px; padding:2px; cursor:pointer;
  }
  /* Lignes d'articles achetés dans "Enregistrer un achat" (voir suppliers.js) : produit
     (menu déroulant), quantité, coût unitaire, suppression — même esprit visuel que
     .grid-add-row ci-dessus, mais avec un <select> à la place du champ nom libre. */
  .purchase-item-row{
    display:grid; grid-template-columns:2fr 0.8fr 1fr 22px; gap:6px;
    align-items:center; margin-bottom:8px;
  }
  .purchase-item-row select, .purchase-item-row input{
    width:100%; padding:9px 8px; border-radius:8px; border:1.5px solid var(--paper-2);
    background:var(--card-bg); font-size:13px; font-family:'Inter'; color:var(--charcoal); min-width:0;
  }
  .purchase-item-row select:focus, .purchase-item-row input:focus{ outline:2px solid var(--emerald-light); border-color:transparent; }
  /* Le scanner de code-barres doit toujours apparaître AU-DESSUS de n'importe quelle autre
     fiche déjà ouverte (ex : le formulaire d'ajout de produit) — sinon la caméra reste
     invisible derrière. On force un z-index plus haut que les .sheet-overlay classiques
     (30) plutôt que de dépendre de l'ordre d'apparition dans le HTML, qui n'est pas fiable
     dès que deux fiches sont ouvertes en même temps. */
  #barcode-scan-overlay, #barcode-confirm-overlay{ z-index:45; }
  #barcode-scan-region video{ width:100%; border-radius:12px; }
  .bulk-catalog-row.disabled{ opacity:.4; }
  /* Ces fenêtres s'ouvrent par-dessus une autre déjà ouverte (Historique / Crédits & Dettes) :
     elles doivent toujours passer au premier plan, peu importe leur ordre dans le fichier. */
  #delete-sale-reason-overlay, #delete-expense-reason-overlay, #delete-debt-reason-overlay, #edit-debt-overlay{ z-index:40; }
  /* La boîte de confirmation Mombongo (#mb-confirm-overlay) remplace toutes les boîtes
     natives du navigateur : elle doit recouvrir n'importe quelle fiche ouverte, y compris
     l'aperçu plein écran de la photo de profil (#account-photo-viewer, z-index:9000). */
  #mb-confirm-overlay{ z-index:9500; }
  /* La fiche qui explique le palier requis s'ouvre depuis Alertes (ex. « Réappro »
     en stock faible) sans fermer #alerts-overlay. Elle doit donc la recouvrir même
     si elle apparaît plus tôt dans le HTML. */
  #limit-overlay{ z-index:40; }
  /* Même raisonnement : "Nouvelle commande" s'ouvre depuis le bouton "🛒 Commander" de la
     fiche Alertes (stock faible), voir openNewOrderSheetFromSuggestion() dans suppliers.js.
     CORRECTIF : les deux partageaient le même z-index par défaut (30), donc lequel des deux
     apparaissait au-dessus dépendait de l'ORDRE dans le HTML — alerts-overlay étant plus bas
     dans le fichier, c'est LUI qui gagnait, laissant la fiche de commande invisible derrière
     alors qu'elle était bien ouverte. Une fois la commande enregistrée, seul ce panneau se
     ferme (closeNewOrderSheet(), voir confirmNewOrder()) — la fiche Alertes, jamais fermée
     entre-temps, redevient donc visible automatiquement pour enchaîner une autre commande. */
  #new-order-overlay{ z-index:40; }
  .sheet{
    background:var(--paper); width:100%; max-width:480px; margin:0 auto;
    border-radius:20px 20px 0 0; padding:20px 20px 26px;
    /* 88vh au repos ; quand le clavier du téléphone réduit la zone visible, la
       fiche se limite à cette zone (--mb-vvh, publié par src/keyboard.js) pour
       que le champ en cours de saisie reste atteignable en défilant. */
    max-height:88vh; max-height:min(88vh, calc(var(--mb-vvh, 100vh) - 24px));
    overflow-y:auto;
    overscroll-behavior:contain; /* voir body.overlay-locked plus bas — empêche le "scroll
      chaining" : sans ça, arriver en haut/bas de la liste À L'INTÉRIEUR d'une fenêtre
      ouverte (ex. la longue liste "Ajout rapide depuis le catalogue") et continuer à
      swiper faisait défiler la page D'ARRIÈRE-PLAN en dessous, invisible mais bien réelle. */
  }
  /* Verrou d'arrière-plan pendant qu'une fenêtre (.sheet-overlay) est ouverte — posé/retiré
     automatiquement par updateBackgroundLock() (navigation.js) dès qu'une .sheet-overlay
     gagne/perd sa classe .open, sans avoir à modifier un par un les dizaines de
     open.../close... déjà présents dans le code. Deux effets : la page ne peut plus défiler
     derrière la fenêtre (position:fixed + restauration exacte du scroll au retrait), et le
     contenu visible mais recouvert (#app) devient inerte — aucun tap, aucun raccourci
     clavier ne peut plus l'atteindre tant que la fenêtre reste ouverte. */
  body.overlay-locked{ position:fixed; left:0; right:0; overflow:hidden; }
  body.overlay-locked #app{ pointer-events:none; }
  body.overlay-locked .sheet-overlay.open{ pointer-events:auto; }
  body.overlay-locked .sheet-overlay.open .sheet{ pointer-events:auto; }
  .sheet h3{margin:0 0 16px; font-size:17px; font-weight:700;}
  /* En-tête de la fiche de vente (index.html) : le titre « Vendre » garde sa place à
     gauche, l'avertissement « ce produit n'a pas de prix d'achat » s'aligne à droite
     sur la même ligne. Petit (11,5 px) mais jamais discret au point d'être ignoré :
     fond rouge pâle + texte rouge gras, lisible aussi en mode sombre. */
  .sell-sheet-head{display:flex; align-items:flex-start; justify-content:space-between; gap:8px; margin-bottom:16px;}
  .sell-sheet-head h3{margin:0; flex:0 0 auto;}
  .sell-missing-buy-price-notice{
    flex:1 1 auto; min-width:0; text-align:right; font-size:11.5px; font-weight:700; line-height:1.3;
    color:var(--alert); background:var(--alert-bg); border:1px solid var(--alert);
    border-radius:8px; padding:3px 7px;
  }
  .field{margin-bottom:14px;}
  .field label{display:block; font-size:12.5px; font-weight:600; color:var(--charcoal-soft); margin-bottom:6px;}
  .field input, .field select{
    width:100%; padding:12px; border-radius:10px; border:1.5px solid var(--paper-2);
    background:var(--card-bg); font-size:15px; font-family:'Inter'; color:var(--charcoal);
  }
  .field input:focus, .field select:focus{outline:2px solid var(--emerald-light); border-color:transparent;}
  .two-col{display:grid; grid-template-columns:1fr 1fr; gap:10px;}
  .date-group{display:flex; gap:6px;}
  .date-part, .date-part-year{
    text-align:center; padding:12px 4px; border-radius:10px; border:1.5px solid var(--paper-2);
    background:var(--card-bg); font-size:15px; font-family:'Inter'; color:var(--charcoal);
  }
  .date-part{width:0; flex:1;}
  .date-part-year{width:0; flex:1.4;}
  .btn-primary{
    width:100%; background:var(--emerald); color:#fff; border:none; border-radius:12px;
    padding:14px; font-size:15px; font-weight:700; font-family:'Sora'; cursor:pointer; margin-top:6px;
  }
  .btn-primary:disabled{
    background:var(--slate); opacity:0.55; cursor:not-allowed;
  }
  .btn-secondary{
    width:100%; background:transparent; color:var(--charcoal-soft); border:none;
    padding:10px; font-size:13.5px; font-weight:600; cursor:pointer;
  }
  /* Pilule colorée du bouton « Appliquer » de la période personnalisée (Historique & Consolidé) :
     zone tactile élargie, contraste doux mais bien visible dans les thèmes clair et sombre. */
  .btn-secondary.period-apply-pill{
    display:flex; align-items:center; justify-content:center;
    width:100%; min-height:46px;
    margin:2px 0 14px; padding:12px 24px;
    border-radius:999px;
    background:#d6ede4; color:#146356;
    border:1.5px solid rgba(31,122,107,0.34);
    font-family:'Sora'; font-size:14.5px; font-weight:700; letter-spacing:0.2px;
    cursor:pointer;
    transition:transform 0.12s ease, filter 0.15s ease, background 0.15s ease;
    -webkit-tap-highlight-color:transparent;
  }
  .btn-secondary.period-apply-pill:active{ transform:scale(0.985); }
  body.dark-mode .btn-secondary.period-apply-pill{
    background:#1b4338; color:#8ee3ca;
    border-color:rgba(142,227,202,0.34);
  }

  /* ---------- Vente rapide de tout le carton/sac (Business/Pro) ----------
     Même langage visuel que les autres éléments "premium" de l'app (bordure +
     fond marigold, voir .auth-gate-join-btn) — pas un bouton d'action banal, un
     raccourci qui mérite de se distinguer visuellement du reste de la fiche. */
  .sell-full-batch-btn{
    width:100%; background:var(--marigold-bg); color:var(--charcoal); border:1px solid var(--marigold);
    border-radius:10px; padding:11px; font-size:13px; font-weight:700; font-family:'Inter';
    cursor:pointer; margin:-6px 0 16px; text-align:center;
  }

  /* ---------- Bande de confirmation scan/vocale coupée en 2 (Panier / Confirmer) ----------
     "Panier" (marigold, même langage visuel "action secondaire notable" que
     .sell-full-batch-btn) à gauche, "Confirmer" (vert, inchangé) à droite — même
     largeur chacun, côte à côte plutôt qu'empilés comme avant. Partagée par
     barcode-confirm-overlay ET voice-confirm-overlay (index.html). */
  .barcode-confirm-actions{ display:flex; gap:10px; margin-bottom:8px; }
  .barcode-confirm-actions .barcode-confirm-half{ flex:1; width:auto; margin-top:0; }
  .barcode-cart-btn{
    flex:1; background:var(--marigold-bg); color:var(--charcoal); border:1px solid var(--marigold);
    border-radius:12px; padding:14px; font-size:14px; font-weight:700; font-family:'Sora';
    cursor:pointer;
  }
  .barcode-cart-btn:disabled{ opacity:0.5; cursor:not-allowed; }

  /* ---------- Écran de choix du palier (onboarding) ----------
     Un badge en gros caractères par carte ("Gratuit à vie" / "Essai gratuit" /
     "Payant dès l'inscription") pour qu'aucun commerçant ne puisse se tromper de
     palier sans le voir écrit noir sur blanc — voir plans.js / navigation.js.
     Fenêtre volontairement encadrée (jamais bord à bord) : 10% de marge libre en haut
     ET en bas, 5% à gauche ET à droite — donc 80% de hauteur et 90% de largeur d'écran
     au maximum pour le contenu, quelle que soit la taille de l'écran. */
  #plan-onboarding-overlay.sheet-overlay.center-modal{ padding:10vh 5vw; }
  /* CORRECTIF : cette règle était déjà là pour permettre au sélecteur de palier de
     défiler quand son contenu dépasse l'écran (3 cartes + titre), mais elle perdait
     TOUJOURS face à ".sheet-overlay.center-modal .sheet{max-height:none}" plus haut —
     deux classes battent une classe en spécificité CSS, peu importe qui est écrit en
     dernier dans le fichier. Résultat : "max-height:none" gagnait systématiquement, la
     carte Pro (la 3e, tout en bas) se retrouvait hors écran SANS aucun moyen de
     l'atteindre, sur tout téléphone où les 3 cartes ne tiennent pas d'un coup. Qualifiée
     par l'ID du conteneur ici pour être sûre de gagner, et en dur en vh (pas en %,
     peu fiable dans un conteneur flex comme .sheet-overlay) : quelle que soit la taille
     de l'écran, le contenu qui dépasse est désormais toujours atteignable par défilement. */
  #plan-onboarding-overlay .plan-onboarding-sheet{
    max-height:80vh; width:100%; max-width:460px; overflow-y:auto; margin:auto;
  }
  .plan-card{
    background:var(--card-bg); border-radius:var(--radius); box-shadow:var(--shadow);
    padding:16px; margin-bottom:12px; border:1.5px solid var(--paper-2);
  }
  .plan-card.current-plan{ border-color:var(--emerald); }
  .plan-card.current-plan .btn-primary, .plan-card.current-plan .btn-secondary{
    background:var(--ok-bg); color:var(--emerald); cursor:default;
  }
  .plan-card-badge{
    display:inline-block; font-size:12px; font-weight:700; padding:5px 10px;
    border-radius:8px; margin-bottom:8px;
  }
  .plan-badge-free{ background:var(--ok-bg); color:var(--emerald); }
  .plan-badge-trial{ background:var(--marigold-bg); color:var(--marigold); }
  .plan-badge-paid{ background:var(--alert-bg); color:var(--alert); }
  .plan-card-title{ font-family:'Sora'; font-weight:700; font-size:15px; margin-bottom:4px; }
  .plan-card-desc{ font-size:13px; color:var(--charcoal-soft); line-height:1.5; margin-bottom:12px; }
  /* Ligne « 🔒 Pour activer ou prolonger un palier, entrez votre code premium… »
     affichée à la place du badge d'essai quand celui-ci n'est plus disponible. Cadre
     discret pour se lire comme une CONSIGNE à recopier, pas comme un bouton cliquable
     (c'est volontairement du texte brut : aucun lien, aucun prix dans l'application). */
  .plan-card-note{
    font-size:12.5px; line-height:1.6; color:var(--charcoal); margin:-4px 0 12px;
    background:var(--paper); border:1.5px dashed var(--paper-2); border-radius:10px;
    padding:8px 10px; word-break:break-word;
  }

  /* ---------- Carte "Mon palier" (dans le compte) ---------- */
  .plan-current-card{
    background:var(--card-bg); border-radius:var(--radius); box-shadow:var(--shadow);
    padding:12px 14px; margin-bottom:8px;
  }
  .plan-current-name{ font-family:'Sora'; font-weight:700; font-size:14px; }
  .plan-current-status{ font-size:12px; color:var(--charcoal-soft); margin-top:2px; }
  .plan-current-warning{ font-size:12px; font-weight:700; color:var(--alert); margin-top:6px; }
  /* ---------- Paire « Mon palier » / « Mes connexions » ----------
     Géométrie mesurée sur la capture de Vincent (12/11/2026).
     ATTENTION au repère : sur son image, les boutons font 44,2 % de la LARGEUR
     DU TÉLÉPHONE. Mais une grille CSS compte dans SON CONTENEUR, qui est déjà
     en retrait de 4,5 % de chaque côté. Écrire 4,4fr / 44,2fr dans la grille
     aurait donc doublé les marges et rétréci les boutons à 62 % de la largeur
     voulue. Les bonnes valeurs, ramenées au conteneur (626 px sur sa capture) :
       48,56 % bouton | 3,35 % d'écart | 48,56 % bouton
     Ce qui redonne bien 44,2 % de l'écran à chaque bouton, écart de 21 px.
     En fr, donc proportions identiques sur 360 px comme sur 420 px : aucune
     largeur fixe. Les deux boutons ont la même hauteur (align-items:stretch)
     et le même texte (14 px, Sora, centré).
     RELIEF : la carte n'est pas un rectangle plat. Elle a un contour clair et
     une ombre portée basse et nette → elle semble posée au-dessus du papier.
     PAS D'APPUI : le mouvement de 2 px à chaque toucher a été RETIRÉ le
     12/11/2026, à la demande de Vincent. Ces cartes restent bombées mais ne
     bougent plus. Le mouvement d'appui ne vit plus que sur les trois onglets
     « Mes produits » / « Catégories » / « Favoris » (voir .products-tab), qui
     sont les seuls à devoir se comporter comme des boutons. */
  .account-duo-row{
    display:grid;
    /* 48,56 | 3,35 | 48,56 : ramené au conteneur, pas à l'écran (voir ci-dessus). */
    grid-template-columns:48.56fr 3.35fr 48.56fr;
    align-items:stretch;
    margin-bottom:8px;
  }
  .account-duo-row > .account-duo-card:first-child{ grid-column:1; }
  .account-duo-row > .account-duo-card:last-child{ grid-column:3; }
  .account-duo-card{
    /* min-width:0 obligatoire : sans ça, un titre long refuserait de se
       replier dans une grille (min-width:auto) et déborderait du bouton. */
    min-width:0;
    display:flex; align-items:center; justify-content:center;
    /* Hauteur minimale : le bouton est plus large que haut, sinon il se lit
       comme une simple étiquette et non comme un bouton. */
    min-height:46px; padding:10px 8px; text-align:center; cursor:pointer;
    font:inherit; color:inherit; -webkit-tap-highlight-color:transparent;
    background:var(--card-bg);
    border:1px solid rgba(255,255,255,.85);
    border-radius:16px;
    /* Le relief : l'arête claire, la tranche basse et nette, le socle diffus.
       Mêmes variables que les tuiles du tableau de bord — une seule épaisseur
       dans toute l'application. Aucune transition : la carte ne bouge plus. */
    box-shadow:var(--relief-bevel), var(--relief-tranche), var(--relief-socle);
    position:relative;
  }
  /* Accessibilité clavier : même relief, sans translation (pas de mouvement
     imposé à qui navigue au clavier). */
  .account-duo-card:focus-visible{
    outline:2px solid var(--emerald); outline-offset:2px;
  }
  .account-duo-name{
    font-family:'Sora'; font-weight:700; font-size:14px; line-height:1.2;
    /* Un titre qui ne tient pas passe à la ligne : on ne tronque jamais le
       nom du palier, c'est l'information qui justifie d'ouvrir la fiche. */
    overflow-wrap:anywhere;
  }
  /* Pastille d'alerte : un appareil inconnu attend une décision. Même rouge
     que les autres alertes de l'app, jamais une couleur inventée ici. */
  .connections-card-dot{
    position:absolute; top:8px; right:10px; width:9px; height:9px;
    border-radius:50%; background:var(--alert);
  }
  /* ---------- Détail du palier (carte pleine largeur sous les boutons) ----------
     Sur la capture, le nom du palier et sa date ne sont pas dans le bouton
     « Mon palier » mais dans leur propre carte, en dessous. Elle n'est pas
     cliquable : c'est une information, l'action est le bouton au-dessus. */
  .plan-detail-card{
    background:var(--card-bg); border-radius:var(--radius);
    box-shadow:var(--shadow); padding:12px 14px; margin-bottom:4px;
  }
  .plan-detail-name{
    font-family:'Sora'; font-weight:700; font-size:14px; line-height:1.2;
    overflow-wrap:anywhere;
  }
  .plan-detail-status{
    font-size:12px; color:var(--charcoal-soft); line-height:1.25;
    margin-top:2px; overflow-wrap:anywhere;
  }
  .plan-detail-card .plan-current-warning{ font-size:12px; font-weight:700; color:var(--alert); margin-top:6px; }
  /* ---------- Panneau « Mes connexions » ----------
     Demande du 13/11/2026 (capture de Vincent) : la carte fait la PLEINE
     largeur du bloc — les mêmes bords que les deux boutons et que la carte
     de détail du palier — et sa hauteur va du bas des deux cartes jusqu'à
     « Mes boutiques ». Elle disparaît au tap n'importe où HORS d'elle, et la
     touche retour Android la ferme ELLE D'ABORD (avant le menu Compte) :
     voir src/connections.js (écouteur hors-carte + closeConnectionsCardIfOpen)
     et src/back-navigation.js.
     La hauteur est calculée à l'ouverture (voir openConnectionsPanel()
     dans src/connections.js, qui mesure la distance réelle entre les deux
     points) et publiée dans --connections-panel-h. On ne peut pas l'exprimer
     en CSS pur : les deux repères sont des titres, leur position dépend de
     la longueur des textes et du palier affiché.
     Si le calcul échoue (mesure à 0), le repli ci-dessous garde une fenêtre
     lisible (46 vh) au lieu d'une carte écrasée. */
  .connections-panel{
    width:100%; margin-left:0; box-sizing:border-box;
    /* FENÊTRE, pas bloc inséré. Consigne du 12/11/2026, confirmée le 13/11 :
       le panneau PASSE PAR-DESSUS la carte qui détaille le palier (et le
       bouton « Changer de palier »), et descend jusqu'au titre
       « Mes boutiques ».
       En position absolue dans son conteneur (le bloc du palier, qui est en
       position:relative) :
         • `top` est posé à l'ouverture sur la distance exacte entre le bas des
           deux boutons et le haut du conteneur (applyPanelHeight) ;
         • le panneau ne pousse donc RIEN : ouvrir et fermer ne fait pas
           remonter « Mes achats » d'un pixel ;
         • à hauteur fixe, la carte garde LES DIMENSIONS VALIDÉES par la
           capture du 13/11/2026, même avec une seule ligne : le contenu
           défile quand la liste dépasse.
       Surtout PAS `position:fixed` : le panneau se détacherait du contenu au
       défilement de la page et flotterait au-dessus de tout.
       `z-index` : indispensable, sinon le texte de la carte de détail se
       dessinerait par-dessus le fond du panneau (un frère précédent peint son
       contenu en ligne après les fonds des frères suivants). */
    position:absolute; left:0; z-index:2;
    background:var(--paper-2); border:1px solid rgba(38,34,29,.10);
    border-radius:var(--radius);
    /* Deux ombres : une tranche fine (comme les boutons, pour la même
       matière) et une ombre large qui détache la fenêtre de ce qu'elle
       recouvre. Une fenêtre sans ombre se lit comme un aplat collé. */
    box-shadow:0 1px 0 rgba(255,255,255,.55) inset,
               0 6px 18px rgba(38,34,29,.14);
    padding:10px 10px 12px;
    height:min(var(--connections-panel-h, 46vh), 46vh);
    overflow-y:auto; overscroll-behavior:contain;
    animation:connectionsPanelIn .18s ease;
  }
  @keyframes connectionsPanelIn{
    from{ opacity:0; transform:translateY(-4px); }
    to{ opacity:1; transform:translateY(0); }
  }
  /* Chaque ligne du panneau occupe toute la largeur disponible — soit la
     moitié de l'écran. Les textes se replient, les noms d'appareils sont longs
     (« Samsung Galaxy S21 Ultra »). Aucune troncature : l'utilisateur doit
     reconnaître son téléphone dans la liste. */
  .conn-row{
    display:flex; align-items:flex-start; gap:8px;
    padding:8px 6px; border-radius:10px; cursor:pointer;
    background:transparent; border:0; width:100%; text-align:left;
    font:inherit; color:inherit; -webkit-tap-highlight-color:transparent;
  }
  .conn-row + .conn-row{ border-top:1px solid rgba(38,34,29,.10); }
  .conn-row:active{ background:var(--card-bg); }
  .conn-icon{ flex:0 0 auto; font-size:14px; line-height:1.3; }
  .conn-body{ flex:1 1 auto; min-width:0; }
  .conn-name{
    font-weight:700; font-size:12.5px; line-height:1.25; overflow-wrap:anywhere;
  }
  .conn-meta{ font-size:11px; color:var(--charcoal-soft); line-height:1.3; margin-top:1px; }
  .conn-flag{
    display:inline-block; font-size:10px; font-weight:700; padding:1px 5px;
    border-radius:6px; margin-top:3px;
  }
  .conn-flag.pending{ background:var(--alert-bg); color:var(--alert); }
  .conn-flag.approved{ background:rgba(16,110,80,.12); color:var(--emerald); }
  .conn-section-title{
    font-size:11px; font-weight:700; text-transform:none; color:var(--charcoal-soft);
    margin:8px 0 2px; padding:0 6px;
  }
  .conn-empty{ font-size:11.5px; color:var(--charcoal-soft); padding:6px; line-height:1.35; }
  .conn-badge-count{
    font-size:11px; color:var(--charcoal-soft);
  }
  .method-row{display:flex; gap:8px;}
  .method-row button{
    flex:1; padding:10px; border-radius:10px; border:1.5px solid var(--paper-2);
    background:var(--card-bg); font-size:13px; font-weight:600; cursor:pointer; font-family:'Inter';
  }
  .method-row button.active{background:var(--emerald); border-color:var(--emerald); color:#fff;}

  #add-sheet-inner{ min-height:75vh; }
  .mode-row{display:flex; gap:8px; margin-bottom:18px;}
  .period-row{display:flex; gap:6px; margin-bottom:14px; flex-wrap:wrap;}
  .period-btn{
    flex:1; min-width:70px; padding:9px 4px; border-radius:10px; border:1.5px solid var(--paper-2);
    background:var(--card-bg); font-size:11.5px; font-weight:700; cursor:pointer; font-family:'Inter';
    color:var(--charcoal-soft);
  }
  .period-btn.active{background:var(--emerald); border-color:var(--emerald); color:#fff;}
  .summary-card{
    display:flex; background:var(--ok-bg); border-radius:12px; padding:13px 8px; margin-bottom:16px;
  }
  .summary-col{flex:1; text-align:center; border-left:1px solid var(--paper-2);}
  .summary-col:first-child{border-left:none;}
  .summary-lbl{font-size:10px; text-transform:uppercase; color:var(--charcoal-soft); font-weight:700; letter-spacing:.03em;}
  .summary-val{font-family:'Sora'; font-size:14px; font-weight:800; margin-top:4px;}
  .summary-val.money{color:var(--emerald);}
  .summary-val.alert{color:var(--alert);}
  .mode-btn{
    flex:1; padding:11px 6px; border-radius:11px; border:1.5px solid var(--paper-2);
    background:var(--card-bg); font-size:12.5px; font-weight:700; cursor:pointer; font-family:'Inter';
    color:var(--charcoal-soft);
    /* Les trois pastilles du formulaire d'ajout contiennent une icône (Carton,
       Sac, appareil photo). Même piège que les boutons ronds : sans taille
       explicite ni centrage, le SVG de 1em flotte en haut à gauche. */
    display:inline-flex; align-items:center; justify-content:center; gap:5px;
  }
  .mode-btn svg.mb-i{ width:16px; height:16px; stroke-width:2.2; }
  .mode-btn.active{background:var(--emerald); border-color:var(--emerald); color:#fff;}
  .mesurette-row{
    background:var(--ok-bg); border-radius:12px; padding:12px 12px 4px; margin-bottom:12px;
  }
  .btn-add-mesurette{
    width:100%; background:var(--card-bg); border:1.5px dashed var(--paper-2); color:var(--emerald);
    border-radius:10px; padding:12px; font-size:13.5px; font-weight:700; font-family:'Inter';
    cursor:pointer; margin-bottom:6px;
  }
  .remove-mesurette{
    background:none; border:none; color:var(--alert); font-size:12px; font-weight:600;
    cursor:pointer; padding:0 0 10px; font-family:'Inter';
  }

  .sell-preview{
    background:var(--ok-bg); border-radius:12px; padding:14px; margin-bottom:14px;
    display:flex; justify-content:space-between; align-items:center;
  }
  .sell-preview .col{text-align:center; flex:1;}
  .sell-preview .col .lbl{font-size:10.5px; text-transform:uppercase; color:var(--charcoal-soft); font-weight:700; letter-spacing:.03em;}
  .sell-preview .col .val{font-family:'Sora'; font-size:16px; font-weight:800; margin-top:3px; color:var(--emerald);}
  .sell-preview .divider{width:1px; height:30px; background:var(--paper-2);}

  .toggle-row-pair{display:flex; gap:10px; margin-bottom:14px;}
  .toggle-row-pair .toggle-row{flex:1; margin-bottom:0;}
  .multi-search input{margin-bottom:10px;}
  .multi-product-row{
    background:var(--card-bg); border-radius:12px; padding:10px 12px; margin-bottom:8px;
    display:flex; justify-content:space-between; align-items:center; gap:10px;
  }
  .multi-product-row .info{flex:1; min-width:0;}
  .multi-product-row .info .name{font-weight:700; font-size:13.5px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;}
  .multi-product-row .info .meta{font-size:11.5px; color:var(--charcoal-soft); margin-top:2px;}
  .multi-product-row.out-of-stock{opacity:.5;}
  .qty-stepper{display:flex; align-items:center; gap:10px; flex-shrink:0;}
  .qty-stepper button{
    width:28px; height:28px; border-radius:50%; border:1.5px solid var(--paper-2);
    background:var(--card-bg); color:var(--charcoal); font-size:16px; line-height:1; cursor:pointer;
  }
  .qty-stepper button:disabled{opacity:.4; cursor:not-allowed;}
  .qty-stepper button:active:not(:disabled){background:var(--paper-2);}
  .qty-stepper span{min-width:16px; text-align:center; font-size:13.5px; font-weight:700;}
  /* Saisie directe de la quantité en tapant SUR le chiffre, au milieu des boutons +/- —
     évite d'avoir à appuyer +1 des dizaines de fois pour une grosse quantité (ex : 100
     bouteilles vendues à un client). Un simple <input type="number"> plutôt qu'un <span>
     cliquable-qui-se-transforme : une case en moins dans le flux, et le clavier numérique
     s'ouvre directement au tap, comme n'importe quel autre champ de saisie de l'app. Les
     flèches natives du navigateur sont masquées (elles feraient doublon moche avec nos
     propres boutons ronds − / +) ; le style reste volontairement neutre (pas de bordure
     visible au repos) pour ne pas alourdir visuellement le stepper. */
  .qty-stepper-input{
    width:44px; flex-shrink:0; padding:4px 2px; border-radius:6px; border:1.5px solid transparent;
    background:transparent; font-size:13.5px; font-weight:700; font-family:'Inter'; color:var(--charcoal);
    text-align:center; -moz-appearance:textfield;
  }
  .qty-stepper-input::-webkit-outer-spin-button, .qty-stepper-input::-webkit-inner-spin-button{
    -webkit-appearance:none; margin:0;
  }
  .qty-stepper-input:focus{outline:none; border-color:var(--emerald-light); background:var(--card-bg);}
  .qty-stepper-unit{font-size:11px; color:var(--charcoal-soft); margin-left:-6px; flex-shrink:0;}
  /* Champ de saisie directe pour les unités décimales (kg/L/m) dans le panier "vente
     plusieurs" — un +/- de 1 n'a pas de sens pour du vrac, donc pas de qty-stepper ici. */
  .multi-qty-input{
    width:64px; flex-shrink:0; padding:8px 6px; border-radius:8px; border:1.5px solid var(--paper-2);
    background:var(--card-bg); font-size:14px; font-family:'Inter'; color:var(--charcoal); text-align:center;
  }
  .multi-qty-input:focus{outline:2px solid var(--emerald-light); border-color:transparent;}
  .multi-total-divider{height:1px; background:var(--paper-2); margin:12px 0;}
  .multi-list-separator{height:1px; background:var(--paper-2); margin:8px 0;}
  .multi-total-row{display:flex; justify-content:space-between; align-items:baseline; margin-bottom:2px;}
  .multi-total-row .lbl{font-size:13.5px; font-weight:700;}
  .multi-total-row .val{font-family:'Sora'; font-size:19px; font-weight:800; color:var(--emerald);}

  /* ---- Remises (vente simple + panier "vente plusieurs") ---- */
  .discount-preview-line{
    text-align:right; font-size:11.5px; font-weight:700; color:var(--alert);
    margin:-6px 0 14px;
  }
  /* Ligne de produit du panier repliée en colonne dès qu'elle porte ses propres
     contrôles de remise (uniquement les lignes déjà sélectionnées, voir
     renderMultiProductRow() dans sales.js) — les autres gardent la mise en page
     horizontale d'origine. */
  .multi-product-row.has-discount-row{flex-direction:column; align-items:stretch; gap:8px;}
  /* CORRECTIF : ".info{min-width:0}" (plus haut) ne suffit pas à lui seul quand
     l'élément est niché dans un DEUXIÈME conteneur flex (.mpr-top, entre .info et
     .multi-product-row) — un conteneur flex sans min-width:0 explicite hérite par
     défaut d'une "taille minimale automatique" calculée sur le contenu de SES PROPRES
     enfants (donc sur le texte non coupé du nom de produit), et la répercute à SON
     TOUR sur son propre parent. Résultat concret : un nom de produit assez long
     poussait le bouton "+" hors de l'écran, alors que le texte lui-même semblait
     pourtant se couper correctement avec des "…". Il faut min-width:0 à CHAQUE niveau
     de conteneur flex imbriqué entre le texte et l'endroit où on veut vraiment
     limiter la largeur — pas seulement sur celui qui contient le texte. */
  .mpr-top{display:flex; justify-content:space-between; align-items:center; gap:10px; flex:1; min-width:0;}
  .mpr-discount{display:flex; align-items:center; gap:8px;}
  .disc-type-toggle{display:flex; gap:4px; flex-shrink:0;}
  .disc-type-btn{
    padding:7px 9px; border-radius:8px; border:1.5px solid var(--paper-2);
    background:var(--card-bg); font-size:11px; font-weight:700; cursor:pointer; font-family:'Inter';
    color:var(--charcoal-soft);
  }
  .disc-type-btn.active{background:var(--emerald); border-color:var(--emerald); color:#fff;}
  .mpr-discount-input{
    flex:1; min-width:0; padding:8px 10px; border-radius:8px; border:1.5px solid var(--paper-2);
    background:var(--card-bg); font-size:13px; font-family:'Inter'; color:var(--charcoal);
  }
  .mpr-discount-input:focus{outline:2px solid var(--emerald-light); border-color:transparent;}

  /* ---- Tableau de bord consolidé (toutes boutiques, palier Pro) ---- */
  .consolidated-status-msg{text-align:center; color:var(--charcoal-soft); font-size:13px; padding:20px 0;}
  .store-compare-row{
    background:var(--card-bg); border:1.5px solid var(--paper-2); border-radius:12px;
    padding:12px 14px; margin-bottom:10px;
  }
  .store-compare-row.loss{border-color:var(--alert); background:rgba(220,53,69,0.05);}
  .scr-top{display:flex; justify-content:space-between; align-items:baseline; gap:10px; margin-bottom:6px;}
  .scr-name{font-size:14px; font-weight:700; color:var(--charcoal);}
  .scr-net{font-family:'Sora'; font-size:15px; font-weight:800; color:var(--emerald);}
  .store-compare-row.loss .scr-net{color:var(--alert);}
  .scr-detail{display:flex; flex-wrap:wrap; gap:4px 12px; font-size:11.5px; color:var(--charcoal-soft);}

  /* ---- Valeur du stock de la boutique active et tendance ---- */
  #stock-valuation-overlay{z-index:40;}
  .stock-valuation-sheet{padding-top:14px;}
  .stock-value-sheet-header{display:flex; justify-content:space-between; align-items:center; gap:12px; margin-bottom:5px;}
  .stock-value-sheet-header h3{margin:0; font-size:18px;}
  .stock-value-active-store{font-size:12px; font-weight:600; color:var(--charcoal-soft); margin:0 0 12px;}
  .stock-value-active-store:empty{display:none;}
  .stock-value-close{
    display:flex; align-items:center; justify-content:center; flex:none; width:36px; height:36px;
    padding:0; border:1px solid var(--paper-2); border-radius:50%; background:var(--card-bg);
    color:var(--charcoal-soft); font-size:17px; cursor:pointer;
  }
  .stock-value-metrics{display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); gap:10px;}
  .stock-value-card{
    min-width:0; min-height:112px; display:flex; flex-direction:column; justify-content:center; align-items:center;
    gap:8px; padding:12px 9px; border:1.5px solid var(--paper-2); border-radius:14px;
    background:var(--card-bg); box-shadow:var(--shadow); text-align:center;
  }
  .stock-value-buy-card{border-top:3px solid #5875d1;}
  .stock-value-sell-card{border-top:3px solid #1f8a5f;}
  .stock-value-label{font-size:12px; line-height:1.4; font-weight:700; color:var(--charcoal-soft);}
  .stock-value-amount{
    max-width:100%; font-family:'Sora',sans-serif; font-size:clamp(16px,4.7vw,22px); line-height:1.2;
    font-weight:800; color:var(--charcoal); overflow-wrap:anywhere; text-align:center;
  }
  .stock-value-potential-card{
    grid-column:1 / -1; display:flex; justify-content:space-between; align-items:center; gap:12px;
    padding:12px 14px; border-radius:12px; background:var(--ok-bg); border:1px solid rgba(31,138,95,0.18);
  }
  .stock-value-potential-card .stock-value-label{font-size:12.5px; color:var(--emerald);}
  .stock-value-potential-card .stock-value-amount{color:var(--emerald); font-size:clamp(17px,4.8vw,23px);}
  .stock-value-count{text-align:right; font-size:11.5px; color:var(--charcoal-soft); margin:8px 2px 12px;}
  .stock-value-status{text-align:center; color:var(--charcoal-soft); font-size:12.5px; padding:11px 8px; line-height:1.5;}
  .stock-value-status.error{color:var(--alert);}
  .stock-trend-section{margin-top:12px; padding-top:14px; border-top:1px solid var(--paper-2);}
  .stock-trend-heading-row{display:flex; justify-content:space-between; align-items:baseline; gap:8px; margin-bottom:11px;}
  .stock-trend-heading-row h4{margin:0; font:700 16px 'Sora',sans-serif;}
  #stock-value-trend-period-summary{font-size:11px; color:var(--charcoal-soft); text-align:right;}
  .stock-trend-period-row{gap:6px; margin-bottom:10px;}
  .stock-trend-period-row .period-btn{min-width:58px; padding:9px 3px; font-size:11px;}
  .stock-trend-custom-fields{display:grid; grid-template-columns:1fr 1fr; gap:0 10px; align-items:end;}
  .stock-trend-custom-fields .field{min-width:0;}
  .stock-trend-custom-fields .date-group{gap:4px;}
  .stock-trend-custom-fields .date-part,
  .stock-trend-custom-fields .date-part-year{min-width:0; padding:10px 2px; font-size:13px;}
  .stock-trend-apply-btn{grid-column:1 / -1; margin:-4px 0 12px; border-radius:9px; background:var(--paper-2);}
  .stock-trend-series-grid{
    display:grid; grid-template-columns:repeat(2,minmax(0,1fr));
    grid-template-areas:"sales expenses" "profit debts"; gap:8px 10px; margin:2px 0 6px;
  }
  .stock-trend-series{
    display:flex; align-items:center; gap:7px; min-width:0; padding:8px 9px;
    border:1px solid var(--paper-2); border-radius:9px; background:var(--card-bg);
    font-size:12px; font-weight:600; cursor:pointer;
  }
  .stock-trend-series[data-series="sales"]{grid-area:sales;}
  .stock-trend-series[data-series="expenses"]{grid-area:expenses;}
  .stock-trend-series[data-series="profit"]{grid-area:profit;}
  .stock-trend-series[data-series="debts"]{grid-area:debts;}
  .stock-trend-series input{width:16px; height:16px; flex:none; margin:0; accent-color:var(--emerald);}
  .stock-trend-series-dot{width:9px; height:9px; flex:none; border-radius:50%; background:#1f8a5f;}
  .stock-trend-series[data-series="profit"] .stock-trend-series-dot{background:#5875d1;}
  .stock-trend-series[data-series="expenses"] .stock-trend-series-dot{background:#df4759;}
  .stock-trend-series[data-series="debts"] .stock-trend-series-dot{background:#d99424;}
  .stock-trend-debt-hint{font-size:10.5px; line-height:1.4; color:var(--charcoal-soft); margin:2px 2px 8px;}
  .stock-value-chart-wrap{
    position:relative; width:100%; height:230px; margin-top:4px; padding:8px 2px;
    border:1px solid var(--paper-2); border-radius:12px; background:var(--card-bg);
  }
  .stock-value-chart-wrap canvas{display:block; width:100% !important; height:100% !important;}
  .consolidated-actions{display:flex; gap:8px; width:100%;}
  .consolidated-action-btn{
    flex:1 1 0; min-width:0; width:auto; min-height:46px; padding:9px 7px;
    border:1px solid var(--paper-2); border-radius:10px; background:var(--paper-2);
    color:var(--charcoal); font-size:12px; line-height:1.25; font-weight:700;
  }
  .consolidated-action-btn:active{background:var(--ok-bg);}

  /* ---- Sélecteur du mode de tri des recherches produits ---- */
  #product-sort-overlay{z-index:50;}
  .product-sort-sheet{padding-top:20px;}
  .product-sort-heading{font:700 17px 'Sora',sans-serif; margin:0 0 10px;}
  .product-sort-option{
    display:flex; align-items:center; gap:12px; width:100%; padding:13px 10px;
    border:0; border-bottom:1px solid var(--paper-2); background:transparent;
    color:var(--charcoal); text-align:left; font:600 14px 'Inter',sans-serif; cursor:pointer;
  }
  .product-sort-option:last-child{border-bottom:none;}
  .product-sort-option.active{color:var(--emerald);}
  .product-sort-symbol{
    display:flex; align-items:center; justify-content:center; flex:none; width:34px; height:34px;
    border-radius:10px; background:var(--paper-2); color:var(--emerald); font-size:17px; font-weight:700;
  }
  .product-sort-symbol small{font-size:10px; margin-left:1px;}
  .product-sort-label{flex:1;}
  .product-sort-check{width:20px; color:var(--emerald); font-size:18px; text-align:center; font-weight:800;}

  .toast{
    position:fixed; top:16px; left:50%; transform:translateX(-50%);
    background:var(--charcoal); color:var(--paper); padding:11px 18px; border-radius:999px;
    font-size:13px; font-weight:600; z-index:999999; box-shadow:0 4px 14px rgba(0,0,0,0.25);
    opacity:0; pointer-events:none; transition:opacity .25s ease;
  }
  .toast.show{opacity:1;}

  .catalog-field-wrap{ position:relative; }
  .catalog-field-wrap input{ padding-right:38px !important; }
  .catalog-arrow-btn{
    position:absolute; top:0; right:0; bottom:0; width:36px;
    display:none; align-items:center; justify-content:center;
    background:transparent; border:none; cursor:pointer; padding:0;
    color:var(--charcoal-soft); font-size:12px;
  }
  .catalog-panel{
    display:none; position:absolute; top:0; right:0; width:50%; max-height:280px;
    overflow-y:auto; background:var(--paper); border:1px solid var(--emerald);
    border-radius:10px; box-shadow:0 8px 20px rgba(0,0,0,0.18); z-index:40;
  }
  .catalog-panel-item{
    padding:9px 10px; font-size:13px; border-bottom:1px solid var(--paper-2);
    cursor:pointer; line-height:1.3;
    display:flex; align-items:center; gap:8px;
  }
  .catalog-panel-name{ flex:1; min-width:0; }
  /* Drapeau de signalement : discret, mais assez large pour le doigt (32px). */
  .catalog-panel-flag{
    flex:none; border:1.5px solid var(--paper-2); background:var(--paper);
    border-radius:8px; font-size:12px; line-height:1; padding:7px 8px; cursor:pointer;
  }
  .catalog-panel-flag:active{ background:var(--paper-2); }
  /* Feuille « Signaler ce nom » : le motif retenu passe en vert, comme les
     options de type de commerce de la fiche voisine. */
  .catalog-report-reason.cp-opt-selected{
    border-color:var(--emerald); color:var(--emerald); font-weight:700;
  }
  .catalog-panel-item:last-child{ border-bottom:none; }
  .catalog-panel-item:active{ background:var(--paper-2); }
  .catalog-panel-empty{
    padding:14px 10px; font-size:12.5px; color:var(--charcoal-soft); text-align:center;
  }
  /* Panneau de produits d'une ligne de commande : sous le champ, jamais plus étroit
     que 240 px pour que les noms restent lisibles. */
  .catalog-panel.order-product-panel{
    top:100%; left:0; right:auto; margin-top:4px; width:max(100%, 240px); max-height:260px;
  }

  /* ---------- Popup liste de produits dans la vente (multi-search) ---------- */
  .multi-search-wrap{ position:relative; flex:1; }
  .multi-search-wrap input{ padding-right:38px !important; }
  .multi-search-toggle-btn{
    position:absolute; top:0; right:0; bottom:0; width:36px;
    display:flex; align-items:center; justify-content:center;
    background:transparent; border:none; cursor:pointer; padding:0;
    color:var(--charcoal-soft); font-size:12px;
    touch-action:manipulation; -webkit-tap-highlight-color:transparent;
  }
  .multi-search-toggle-btn:active{ color:var(--emerald); }
  .multi-search-toggle-btn.open{ transform:rotate(180deg); }
  .multi-product-list-popup-wrap{
    position:relative; width:100%; background:var(--paper);
    border:1px solid var(--emerald); border-radius:10px;
    box-shadow:0 8px 20px rgba(0,0,0,0.18); z-index:40; margin-top:4px;
  }
  .multi-product-list-scroll{
    position:relative; max-height:280px; overflow-y:auto; border-radius:10px;
  }
  .multi-product-fast-scrollbar{
    right:4px; top:6px; bottom:6px; z-index:41;
  }
  .multi-product-fast-scrollbar .fast-scrollbar-track{ width:40px; }
  .multi-product-fast-scrollbar .fast-scrollbar-bubble{ right:50px; }

  /* =========================================================================
     VERSION ORDINATEUR — détection automatique, voir src/device-mode.js.
     La classe `device-desktop` est posée sur <html> ; sans elle (téléphone),
     TOUT ce qui suit ne s'applique pas et la mise en page historique reste
     exactement celle d'avant. Aucune règle ci-dessous ne change le
     fonctionnement de l'app : seulement la façon dont elle s'étale.
     ========================================================================= */

  /* ÉCRAN PLEIN — la colonne du milieu occupait 1080 px, tout le reste étant du
     vide : sur un écran de bureau, c'était la mise en page d'un téléphone posé
     dans un cadre. Elle prend maintenant TOUTE la largeur de la fenêtre, comme
     un site de bureau classique, et se contente de respirer autrement selon la
     place disponible : les listes de produits s'ajoutent des colonnes, les
     marges latérales grandissent avec la fenêtre (clamp(), plus bas), les
     fenêtres modales s'élargissent (déjà fait plus haut dans ce bloc).

     Rien n'est figé ici : 1366, 1440, 1920, 2560 px… ou une simple fenêtre
     réduite, c'est la même mise en page qui remplit exactement l'espace offert.
     Un ordinateur à fenêtre étroite reste en version ordinateur (voir
     detectDeviceMode() dans src/device-mode.js — souris + clavier = PC, quelle
     que soit la largeur), simplement avec moins de colonnes.

     `body` reprend le fond de l'app (--paper) : la colonne n'a plus de bord à
     montrer, donc plus d'ombre ni de liseré — sinon on verrait un cadre au
     milieu de lui-même. La VERSION TÉLÉPHONE n'est pas concernée : elle garde
     sa colonne de 480 px (règle #app de base, plus haut) et son fond, à
     l'identique. */
  html.device-desktop body{ background:var(--paper); }
  html.device-desktop #app{
    max-width:none; width:100%;
    box-shadow:none;
  }
  /* Marges latérales : elles suivent la largeur de la fenêtre — 20 px au
     minimum dans une petite fenêtre, ~2,6 % de la largeur ensuite, 44 px sur un
     très grand écran — au lieu de 32 px fixes. Le minimum de 20 px garantit que
     la place restante pour le contenu est toujours positive : aucun
     débordement horizontal possible, même dans une fenêtre minuscule. */
  html.device-desktop header.top{ padding:26px clamp(20px,2.6vw,44px) 36px; }
  html.device-desktop header.top h1{ font-size:26px; }
  html.device-desktop header.top .toggles{ flex-direction:row; align-items:center; gap:10px; }
  html.device-desktop #t-seo-subtitle{ max-width:none !important; }
  /* auto-fit + minmax(0,…) : 4 cartes côte à côte quand la place existe, et
     repli automatique en 2 colonnes dans une fenêtre étroite — sans jamais
     pouvoir élargir la page (un `1fr` nu refuserait de descendre sous la
     largeur du texte, ce qui pousse la page entière à déborder). Sur un très
     grand écran les 4 tuiles s'étirent sur toute la largeur (voir le bloc
     « très grand écran » plus bas, qui leur redonne un peu de corps). */
  html.device-desktop .stats-row{
    grid-template-columns:repeat(auto-fit, minmax(150px, 1fr));
    gap:14px; padding:0 clamp(20px,2.6vw,44px); margin-top:-22px;
  }
  html.device-desktop main{ padding:26px clamp(20px,2.6vw,44px) 130px; }
  /* 14 px et non 28 px : avec des cartes, 28 px d'écart creusait un vide qui
     séparait trois onglets censés former un groupe. */
  /* Sur ordinateur, la rangée garde son classement (trois colonnes égales),
     mais elle ne prend pas toute la largeur d'un grand écran : étalée sur
     1400 px, les trois cartes flotteraient loin les unes des autres. Elle est
     donc centrée et bornée. `justify-content` et `gap` de l'ancienne règle
     flex sont retirés : sur une grille ils ne centrent rien, ils décalent. */
  html.device-desktop .products-header-row{
    max-width:560px; margin:0 auto 14px;
    column-gap:10px;
    border-bottom:1.5px solid var(--paper-2); padding-bottom:10px;
  }
  /* Le champ de recherche peut s'étendre sur un grand écran (jamais au-delà de
     560 px sur une taille de fenêtre courante : la limite d'avant). */
  html.device-desktop #products-search-toolbar,
  html.device-desktop .product-search-toolbar{
    max-width:min(100%, clamp(560px, 38vw, 820px)); gap:10px;
  }
  html.device-desktop .product-sort-trigger{ flex:0 0 48px; width:48px; }

  /* Catalogue desktop : chaque rangée regroupe plusieurs cartes. La liste
     virtualisée garde la grille sur toute sa longueur sans construire chaque
     carte; les spacers restent visibles pour conserver le défilement complet. */
  html.device-desktop #products-list{
    display:flex; flex-direction:column; align-items:stretch;
  }
  html.device-desktop #products-list > .vl-spacer{
    display:block; flex:0 0 auto; width:100%; grid-column:auto;
  }
  html.device-desktop #products-list > .product-grid-row{
    display:grid;
    grid-template-columns:repeat(var(--product-grid-columns, 1), minmax(0, 1fr));
    column-gap:clamp(10px, 1vw, 16px);
    padding-bottom:clamp(10px, 1vw, 16px);
    align-items:start;
  }
  html.device-desktop .product-card{ margin-bottom:0; }
  html.device-desktop #delete-all-products-btn{ max-width:360px; margin:18px auto 0 !important; }
  html.device-desktop .category-item-body-inner{
    display:grid; grid-template-columns:repeat(auto-fill, minmax(320px, 1fr));
    gap:clamp(10px, 1vw, 16px); align-items:start; padding:8px 2px 16px;
  }
  html.device-desktop .category-item-body-inner > .category-empty-hint,
  html.device-desktop .category-item-body-inner > .load-more-btn,
  html.device-desktop .category-item-body-inner > .btn-secondary{
    grid-column:1 / -1;
  }

  /* Barre de défilement rapide d'ordinateur : rail fin le long du bord droit de
     l'écran (8 px) sur toute la hauteur utile, au lieu de la grosse capsule
     tactile de 48 px prévue pour le pouce sur téléphone. */
  html.device-desktop .product-fast-scrollbar{
    right:8px; top:16px; bottom:16px; height:auto;
    transform:translate3d(6px, 0, 0);
  }
  html.device-desktop .product-fast-scrollbar.visible{
    transform:translate3d(0, 0, 0);
  }
  html.device-desktop .fast-scrollbar-track{
    width:14px; border-radius:7px;
    background:rgba(38,34,29,0.10);
    border:1px solid rgba(38,34,29,0.08);
    transition:background .14s ease;
  }
  html.device-desktop .fast-scrollbar-track:hover{
    background:rgba(38,34,29,0.18);
  }
  html.device-desktop body.dark-mode .fast-scrollbar-track{
    background:rgba(255,255,255,0.10);
    border-color:rgba(255,255,255,0.12);
  }
  html.device-desktop .fast-scrollbar-thumb{
    left:2px; right:2px; border-radius:5px;
    box-shadow:0 2px 6px rgba(20,99,86,0.28);
  }
  html.device-desktop .fast-scrollbar-bubble{
    right:22px; padding:6px 10px; border-radius:8px; font-size:12px;
  }
  html.device-desktop .multi-product-fast-scrollbar{
    right:4px; top:6px; bottom:6px;
  }
  html.device-desktop .multi-product-fast-scrollbar .fast-scrollbar-track{
    width:12px; border-radius:6px;
  }
  html.device-desktop .multi-product-fast-scrollbar .fast-scrollbar-bubble{
    right:18px;
  }

  /* Boutons d'action principaux (« + Produit » et « 💸 Dépense ») : bien espacés
     de part et d'autre sur ordinateur au lieu de rester collés à 10 px comme
     sur l'écran étroit d'un téléphone. */
  html.device-desktop .fab-row{
    width:calc(100% - 2 * clamp(20px, 2.6vw, 44px));
    max-width:none;
    justify-content:space-between;
    gap:clamp(64px, 16vw, 320px);
  }
  html.device-desktop .fab{
    flex:0 1 280px;
    max-width:280px;
  }
  html.device-desktop .fab-right{
    margin-left:auto;
  }

  /* Fenêtres (fiches) : au milieu de l'écran, comme une vraie fenêtre, au lieu
     de remonter du bas — et plus larges, puisque la place existe. */
  html.device-desktop .sheet-overlay{ align-items:center; padding:28px; }
  html.device-desktop .sheet-overlay.center-modal{ padding:28px; }
  html.device-desktop .sheet{
    max-width:600px; border-radius:20px; padding:24px 28px 28px;
    max-height:88vh; max-height:min(88vh, calc(var(--mb-vvh, 100vh) - 48px));
    box-shadow:0 18px 46px rgba(38,34,29,0.24);
  }
  html.device-desktop #receipt-sheet-overlay,
  html.device-desktop #debt-statement-overlay,
  html.device-desktop #category-prompt-overlay{
    align-items:center; padding:28px;
  }
  html.device-desktop #receipt-sheet-card{
    max-width:540px; border-radius:20px; padding:26px 28px;
    box-shadow:0 18px 46px rgba(0,0,0,0.28);
  }
  html.device-desktop #debt-statement-card{
    max-width:640px; border-radius:20px; padding:26px 28px;
    box-shadow:0 18px 46px rgba(0,0,0,0.28);
  }
  html.device-desktop #category-prompt-card{
    max-width:560px; border-radius:20px; padding:26px 28px;
    box-shadow:0 18px 46px rgba(0,0,0,0.28);
  }
  html.device-desktop #sell-overlay .sheet{ max-width:920px; }
  html.device-desktop #add-overlay .sheet,
  html.device-desktop #add-sheet-inner{ max-width:720px; min-height:auto; }
  html.device-desktop #mesurettes-list{
    display:grid; grid-template-columns:repeat(auto-fill, minmax(260px, 1fr)); gap:12px;
  }
  html.device-desktop #mesurettes-list .mesurette-row{ margin-bottom:0; }
  html.device-desktop #bulk-catalog-overlay .sheet{ max-width:860px; min-height:auto !important; }
  html.device-desktop #bulk-catalog-list{
    display:grid; grid-template-columns:repeat(auto-fill, minmax(240px, 1fr));
    gap:0 16px; max-height:46vh !important;
  }
  html.device-desktop #grid-add-overlay .sheet{ max-width:860px; min-height:auto !important; }
  html.device-desktop .grid-add-header,
  html.device-desktop .grid-add-row{ gap:10px; }
  html.device-desktop #discover-overlay .sheet{ max-width:760px; min-height:auto !important; }
  html.device-desktop #history-overlay .sheet,
  html.device-desktop #consolidated-overlay .sheet,
  html.device-desktop #stock-valuation-overlay .sheet{ max-width:840px; }
  html.device-desktop #account-overlay .sheet,
  html.device-desktop #debts-overlay .sheet,
  html.device-desktop #expenses-history-overlay .sheet,
  html.device-desktop #alerts-overlay .sheet,
  html.device-desktop #suppliers-overlay .sheet,
  html.device-desktop #orders-overlay .sheet,
  html.device-desktop #new-order-overlay .sheet,
  html.device-desktop #record-purchase-overlay .sheet,
  html.device-desktop #purchase-history-overlay .sheet,
  html.device-desktop #ai-reports-overlay .sheet,
  html.device-desktop #cash-reconciliation-overlay .sheet,
  html.device-desktop #cash-reconciliation-history-overlay .sheet,
  html.device-desktop #activity-journal-overlay .sheet,
  html.device-desktop #transfer-history-overlay .sheet,
  html.device-desktop #devices-overlay .sheet,
  html.device-desktop #held-carts-overlay .sheet,
  html.device-desktop #privacy-overlay .sheet,
  html.device-desktop #terms-overlay .sheet{ max-width:740px; }
  html.device-desktop #plan-onboarding-overlay .plan-onboarding-sheet{
    max-width:960px; padding:28px 32px;
    display:grid; grid-template-columns:repeat(3, minmax(0, 1fr));
    gap:16px; align-items:stretch;
  }
  html.device-desktop #plan-onboarding-overlay .plan-onboarding-sheet > h3,
  html.device-desktop #plan-onboarding-overlay .plan-onboarding-sheet > p,
  html.device-desktop #plan-onboarding-overlay .plan-onboarding-sheet > #t-plan-onb-skip{
    grid-column:1 / -1;
  }
  html.device-desktop #plan-onboarding-overlay .plan-card{
    margin-bottom:0; display:flex; flex-direction:column;
  }
  html.device-desktop #plan-onboarding-overlay .plan-card > button{
    margin-top:auto;
  }
  html.device-desktop #auth-gate-overlay .auth-gate-card{
    max-width:500px; padding:30px 28px 24px;
  }
  html.device-desktop #auth-gate-overlay .auth-gate-secondary-wrap{
    max-width:500px;
  }
  html.device-desktop .stock-value-metrics{
    grid-template-columns:repeat(3, minmax(0, 1fr)); gap:14px;
  }
  html.device-desktop .stock-value-potential-card{
    grid-column:auto; flex-direction:column; justify-content:center;
  }
  html.device-desktop .stock-trend-series-grid{
    grid-template-columns:repeat(4, minmax(0, 1fr));
    grid-template-areas:"sales expenses profit debts";
  }
  html.device-desktop .stock-value-chart-wrap{ height:270px; }
  html.device-desktop #consolidated-stores-list{
    display:grid; grid-template-columns:repeat(auto-fill, minmax(300px, 1fr)); gap:12px;
  }
  html.device-desktop .store-compare-row{ margin-bottom:0; }
  html.device-desktop #debts-list{
    display:grid; grid-template-columns:repeat(auto-fill, minmax(290px, 1fr)); gap:12px; align-items:start;
  }
  html.device-desktop #debts-list > p{ grid-column:1 / -1; }
  html.device-desktop .debt-item{ margin-bottom:0; }
  html.device-desktop #alerts-sheet-list{
    display:grid; grid-template-columns:repeat(auto-fill, minmax(290px, 1fr)); gap:10px; align-items:start;
  }
  html.device-desktop #alerts-sheet-list > p,
  html.device-desktop #alerts-sheet-list > .alert-clear-all{ grid-column:1 / -1; }
  html.device-desktop #alerts-sheet-list .alert-banner{ margin-bottom:0; }

  /* « Vente plusieurs » sur ordinateur : la liste des produits à GAUCHE, le
     panier (remise globale, total, dettes) à DROITE — tout est visible en même
     temps, sans faire défiler la fiche. C'est le JS qui pose display:grid sur
     #multi-fields (voir multiFieldsDisplayValue(), sales.js) ; ici, seulement
     la répartition des colonnes. Sur téléphone : rien de tout ça, la liste est
     simplement au-dessus du panier, comme avant. */
  html.device-desktop #multi-fields{
    grid-template-columns:minmax(0, 1.15fr) minmax(0, 0.85fr);
    column-gap:22px; align-items:start;
  }
  html.device-desktop #multi-fields > .multi-search{ grid-column:1 / -1; }
  html.device-desktop #multi-product-list-popup-wrap{ grid-column:1; }
  html.device-desktop #multi-summary{ grid-column:2; }
  html.device-desktop #multi-summary .multi-total-divider{ margin-top:0; }
  html.device-desktop .multi-product-list-scroll{ max-height:min(58vh, 540px); }
  html.device-desktop #multi-product-list{
    display:grid; grid-template-columns:repeat(2, minmax(0, 1fr)); gap:4px 10px;
  }
  html.device-desktop .multi-product-row{ padding:10px 8px; }
  html.device-desktop #multi-product-list .multi-list-separator{ grid-column:1 / -1; }
  html.device-desktop #multi-product-list .multi-list-empty{ grid-column:1 / -1; }

  /* -------------------------------------------------------------------------
     TRÈS GRAND ÉCRAN — au-delà de 1700 px de fenêtre. Même mise en page, mais
     elle respire davantage au lieu de s'étirer : les cartes produits gardent
     une largeur confortable (minimum 340 px au lieu de 320, sinon on finit avec
     6 ou 7 colonnes serrées sur un 2560 px), l'écart entre elles grandit, le
     bandeau du haut et les tuiles de statistiques prennent une taille en
     rapport avec la place, et le champ de recherche reste proportionné.

     Volontairement au-dessus de 1700 px : sur un ordinateur de taille courante
     (1280, 1366, 1440, 1600), la version ordinateur reste EXACTEMENT celle
     d'avant ce bloc. Aucune règle ci-dessous ne touche le téléphone
     (`html.device-desktop` en tête de chaque sélecteur).
     ------------------------------------------------------------------------- */
  @media (min-width:1700px){
    html.device-desktop header.top{ padding:30px clamp(28px,2.4vw,56px) 40px; }
    html.device-desktop header.top h1{ font-size:30px; }
    html.device-desktop .stats-row{ gap:16px; padding:0 clamp(28px,2.4vw,56px); }
    /* Les 4 tuiles s'étirent sur toute la largeur : le chiffre et le libellé
       grandissent un peu, sinon une tuile de 600 px ne contiendrait qu'un texte
       de 17 px perdu en haut à gauche. */
    html.device-desktop .stat-card .value{ font-size:19px; }
    html.device-desktop .stat-card .label{ font-size:11.5px; }
    html.device-desktop main{ padding:28px clamp(28px,2.4vw,56px) 130px; }
    html.device-desktop #products-list > .product-grid-row{
      column-gap:16px; padding-bottom:16px;
    }
    html.device-desktop #products-search-toolbar,
    html.device-desktop .product-search-toolbar{ max-width:min(100%, 820px); }
    html.device-desktop .fab-row{
      width:calc(100% - 2 * clamp(28px, 2.4vw, 56px));
      gap:clamp(120px, 20vw, 480px);
    }
    html.device-desktop .fab{
      flex:0 1 320px;
      max-width:320px;
    }
  }

  /* Survol à la souris : uniquement là où il y a VRAIMENT une souris (écrans
     tactiles jamais concernés). Simple retour visuel, aucun changement de
     comportement. */
  @media (hover:hover) and (pointer:fine){
    .product-card:hover{ box-shadow:0 6px 18px rgba(38,34,29,0.14); }
    html.device-desktop .product-card:hover{ box-shadow:0 6px 18px rgba(38,34,29,0.16); }
    .multi-product-row:hover{ background:var(--paper-2); border-radius:8px; }
    .discover-item-header:hover .discover-item-title{ color:var(--emerald); }
    .domain-hover-none:not(.x):hover{ background:inherit; }
    .fab:hover, .btn-primary:hover, .btn-secondary:hover{ filter:brightness(1.04); }
    .catalog-panel-item:hover{ background:var(--paper-2); }
  }

  /* ---------- Choix de version (fiche « 💡 Découvrir ») ---------- */
  .device-mode-chooser{ display:flex; flex-wrap:wrap; gap:8px; margin:2px 0 12px; }
  .device-mode-btn{
    flex:1 1 30%; min-width:104px; padding:9px 10px; border-radius:10px; cursor:pointer;
    border:1.5px solid var(--paper-2); background:var(--paper); color:var(--charcoal);
    font-family:'Inter'; font-size:12.5px; font-weight:700;
    -webkit-tap-highlight-color:transparent;
  }
  .device-mode-btn.active{ border-color:var(--emerald); color:var(--emerald); }
  .device-mode-status{ font-size:12px; color:var(--charcoal-soft); margin:0 0 10px 2px; }

  /* Liste « vente plusieurs » vide (magasin sans produit, ou recherche sans
     résultat) : un mot d'explication plutôt qu'un cadre vide. */
  .multi-list-empty{
    padding:18px 10px; font-size:12.5px; color:var(--charcoal-soft); text-align:center;
  }


  /* Rapport IA : un plan lisible par boutique, pas une deuxième liste de chiffres. */
  .ai-report-store{ border:1px solid var(--paper-2); border-radius:14px; padding:14px; margin:16px 0; overflow-wrap:anywhere; }
  .ai-report-store-header{ display:flex; align-items:center; justify-content:space-between; flex-wrap:wrap; gap:8px; margin-bottom:6px; }
  .ai-report-store h4{ font-size:15px; margin:0; }
  .ai-report-store h5{ font-size:13px; margin:18px 0 8px; }
  .ai-report-store h6{ font-size:13px; margin:0 0 6px; }
  .ai-report-source{ font-size:10.5px; font-weight:700; border-radius:20px; padding:4px 8px; background:var(--paper-2); color:var(--charcoal-soft); }
  .ai-report-source.is-ai{ color:var(--emerald); background:rgba(31,138,95,.09); }
  .ai-report-note{ font-size:12px; color:var(--charcoal-soft); line-height:1.5; margin:6px 0; }
  .ai-report-diagnosis{ font-size:13px; line-height:1.6; margin:0; }
  .ai-report-status{ font-size:12px; line-height:1.5; background:var(--paper-2); border-radius:8px; padding:10px; margin:12px 0; }
  .ai-report-actions{ margin:0; padding-left:22px; }
  .ai-report-actions li{ margin:0 0 14px; padding-left:3px; }
  .ai-report-actions li::marker{ font-weight:700; color:var(--emerald); }
  .ai-report-actions p, .ai-report-products p{ font-size:12.5px; line-height:1.6; margin:5px 0; }
  .ai-report-fact{ color:var(--charcoal-soft); }
  .ai-report-option{ border-left:2px solid var(--paper-2); padding-left:9px; }
  .ai-report-follow-up{ color:var(--emerald); }
  .ai-report-products{ list-style:none; padding:0; margin:8px 0; }
  .ai-report-products li{ background:var(--paper-2); border-radius:10px; padding:12px; margin:8px 0; }
  .ai-report-snapshot{ border-top:1px solid var(--paper-2); padding-top:10px; margin-top:14px; }
  .ai-report-chart-details{ margin:16px 0 8px; font-size:12.5px; }
  .ai-report-chart-details summary{ cursor:pointer; color:var(--charcoal-soft); font-weight:600; padding:8px 0; }

/* =========================================================================
   FICHE DE PAIEMENT MOBILE MONEY (saveur web / APK hors Play)
   -------------------------------------------------------------------------
   Le point vert clignote DOUCEMENT (2,2 s, opacité seulement) : c'est un
   indicateur d'état, pas une alarme. Ceux qui ne clignotent pas restent fixes.
   `prefers-reduced-motion` coupe complètement l'animation : l'information reste
   lisible par la couleur et la mention « indisponible ».
   ========================================================================= */
.mm-step-title{ letter-spacing:.01em; }
#mm-operators{ display:flex; flex-direction:column; gap:8px; }

/* -------------------------------------------------------------------------
   TABLEAU TARIFAIRE — GRILLE DE CARTES (référence visuelle du 27/10/2026)
   2 colonnes ; la dernière carte, seule sur sa ligne, occupe toute la largeur
   avec le PRIX à gauche et la DURÉE à droite. Fond crème, filet vert, et la
   carte choisie passe en vert d'eau très clair.
   ------------------------------------------------------------------------- */
#mm-durations{
  display:grid; grid-template-columns:1fr 1fr; gap:10px;
}
.mm-card{
  display:flex; flex-direction:column; align-items:center; justify-content:center;
  gap:2px; box-sizing:border-box; width:100%; min-height:66px; padding:10px 8px;
  background:#f6f1e4;                       /* crème comme sur la référence */
  border:2px solid #2f6b62; border-radius:14px;
  font-family:inherit; color:#2b2b2b; text-align:center; cursor:pointer;
  transition:background .15s ease, border-color .15s ease;
}
.mm-card:active{ transform:scale(.992); }
.mm-card-name{ font-size:16px; font-weight:800; line-height:1.15; }
.mm-card-price{ font-size:17px; font-weight:800; color:#1f6b4f; line-height:1.15; }
.mm-card--on{ background:#e4f2f2; border-color:#1f6b4f; }

/* Un palier qui n'a qu'une seule durée (Simple étendu : 1 mois) affiche une
   seule carte, pleine largeur — jamais une demi-carte orpheline. */
#mm-durations .mm-card:only-child{ grid-column:1 / -1; }

/* Carte pleine largeur : prix à gauche, durée à droite. */
.mm-card--wide{
  grid-column:1 / -1; flex-direction:row; justify-content:space-between;
  align-items:center; padding:14px 18px; min-height:60px;
}
.mm-card--wide .mm-card-price{ font-size:17px; }
.mm-card--wide .mm-card-name{ font-size:17px; }

/* Le bouton de l'étape et la sortie : pleine largeur, comme la référence. */
#mm-next, #mm-confirm{ width:100%; margin-top:14px; }
#t-mm-close{
  background:none; border:0; box-shadow:none; padding:12px 0;
  font-family:inherit; font-size:16px; color:#2b2b2b; width:100%;
}
@media (max-width:360px){
  .mm-card-name, .mm-card-price{ font-size:15px; }
}

.mm-choice{
  display:flex; align-items:center; justify-content:space-between; gap:10px;
  width:100%; box-sizing:border-box; padding:12px 14px;
  background:var(--paper-2); border:2px solid transparent; border-radius:12px;
  font-family:inherit; font-size:14px; color:var(--charcoal); text-align:left;
  cursor:pointer;
}
.mm-choice:active{ transform:scale(.995); }
.mm-choice--on{ border-color:var(--emerald); background:#eaf5f1; }
.mm-choice--off{ opacity:.55; cursor:not-allowed; }
.mm-choice-label{ font-weight:700; }
.mm-choice-price{ font-weight:800; color:var(--emerald); white-space:nowrap; }

.mm-op{ justify-content:flex-start; }
.mm-op-name{ font-weight:700; }
.mm-op-off{ font-weight:500; font-size:12.5px; color:var(--charcoal-soft); }

/* Pastille d'état d'un réseau mobile money. */
.mm-dot{
  display:inline-block; flex:0 0 auto;
  width:10px; height:10px; border-radius:50%;
  background:#b8b3a8; margin-right:10px;
}
.mm-dot--on{
  background:#22b573; box-shadow:0 0 0 3px rgba(34,181,115,.18);
  animation:mm-dot-pulse 2.2s ease-in-out infinite;
}
@keyframes mm-dot-pulse{
  0%, 100%{ opacity:1; box-shadow:0 0 0 3px rgba(34,181,115,.18); }
  50%{ opacity:.55; box-shadow:0 0 0 5px rgba(34,181,115,.10); }
}
@media (prefers-reduced-motion: reduce){
  .mm-dot--on{ animation:none; }
}

/* Trois points tournent pendant les opérations en cours. */
.loading-orbit{display:inline-block;position:relative;width:1.2em;height:1.2em;vertical-align:middle;margin-inline-start:.35em;animation:loadingOrbit .9s linear infinite}
.loading-orbit i{position:absolute;left:calc(50% - .12em);top:calc(50% - .12em);width:.24em;height:.24em;border-radius:50%;background:currentColor}
.loading-orbit i:nth-child(1){transform:translateY(-.43em)}
.loading-orbit i:nth-child(2){transform:rotate(120deg) translateY(-.43em);opacity:.7}
.loading-orbit i:nth-child(3){transform:rotate(240deg) translateY(-.43em);opacity:.4}
@keyframes loadingOrbit{to{transform:rotate(360deg)}}
@media (prefers-reduced-motion: reduce){.loading-orbit{animation-duration:2.5s}}

/* ---------- Première visite : tour guidé et checklist ---------- */
.home-tour-overlay{
  position:fixed; inset:0; z-index:100000; display:none;
  background:rgba(13,25,21,.46); backdrop-filter:blur(5px); -webkit-backdrop-filter:blur(5px);
  cursor:pointer; touch-action:manipulation;
}
.home-tour-overlay.open{display:block;}
.home-tour-spotlight{position:fixed; pointer-events:none; filter:none; z-index:1;}
.home-tour-tooltip{
  position:fixed; z-index:3; width:min(310px,calc(100vw - 24px)); padding:16px 17px 14px;
  border-radius:16px; background:var(--paper); color:var(--charcoal);
  box-shadow:0 12px 38px rgba(0,0,0,.35); font-size:14px; line-height:1.5;
}
.home-tour-step{font-size:11px; font-weight:800; color:var(--emerald-light); text-transform:uppercase; letter-spacing:.05em;}
.home-tour-text{margin:6px 0 8px; font-weight:650;}
.home-tour-hint{margin:0; color:var(--charcoal-soft); font-size:11.5px;}
/* Flèche de l'infobulle : une courbe qui va du bord de l'infobulle jusqu'à
   l'élément expliqué. Position et taille du SVG sont calculées par
   tourLayout() (src/home-onboarding.js), qui fournit aussi les tracés.
   Couche posée entre le clone surligné (z-index 1) et l'infobulle (z-index 3). */
.home-tour-arrow{position:fixed; z-index:2; color:var(--marigold); overflow:visible; pointer-events:none;}
.home-tour-arrow path{fill:none;}
/* Contour clair : la flèche reste lisible sur le bouton orange (marigold). */
.home-tour-arrow-outline{stroke:var(--paper); opacity:.92;}
/* Liste des quatre premières tâches. `overflow-y:auto` + `max-height` : sur un
   petit téléphone (ou clavier ouvert), la carte reste entièrement lisible et
   défilable au lieu d'être coupée en haut et en bas. */
.home-checklist-overlay{
  position:fixed; inset:0; z-index:100001; display:none; align-items:center; justify-content:center;
  padding:20px; background:rgba(13,25,21,.52); backdrop-filter:blur(3px); -webkit-backdrop-filter:blur(3px);
  overflow-y:auto; overscroll-behavior:contain;
}
.home-checklist-overlay.open{display:flex;}
.home-checklist-card{
  width:100%; max-width:360px; max-height:calc(100vh - 40px); overflow-y:auto;
  padding:24px 22px; border-radius:20px;
  background:var(--paper); color:var(--charcoal); box-shadow:0 16px 48px rgba(0,0,0,.35);
}
.home-checklist-card h2{font-size:20px; line-height:1.3; margin:0 0 18px; color:var(--emerald);}
.home-checklist-list{list-style:none; padding:0; margin:0; display:grid; gap:10px;}
.home-checklist-item{display:flex; align-items:flex-start; gap:10px; font-size:14px; line-height:1.45;}
.home-checklist-box{
  display:inline-flex; align-items:center; justify-content:center; flex:0 0 22px; width:22px; height:22px;
  border:2px solid var(--charcoal-soft); border-radius:6px; color:transparent; font-size:15px; font-weight:900;
}
.home-checklist-item.done .home-checklist-box{background:#21875f; border-color:#21875f; color:#fff;}
.home-checklist-item.done .home-checklist-label{color:var(--emerald);}
.home-checklist-progress{margin:18px 0 0; padding-top:14px; border-top:1px solid var(--paper-2); font-weight:800; color:var(--emerald);}
.home-checklist-dismiss{margin:8px 0 0; font-size:11.5px; color:var(--charcoal-soft);}
.home-congratulations{text-align:center;}
.home-congratulations .home-celebration-icon{font-size:42px;}
.home-congratulations h2{margin-top:8px;}
.home-confetti{position:fixed; z-index:100002; top:-36px; pointer-events:none; display:block; line-height:0; color:var(--emerald); animation:homeConfettiFall 2.4s ease-in forwards;}
  /* Confettis en tracés SVG (13/11/2026) : plus d'émojis 🎉🎊. Ils doivent
     être VISIBLES en couleur pleine sur le fond de la page, donc dimension
     explicite — un .mb-i resté à 1em dans un span vide mesurerait 0. */
  .home-confetti svg.mb-i{width:22px; height:22px; stroke-width:1.7;}
  .home-celebration-icon{display:flex; justify-content:center; color:var(--emerald); line-height:0; margin-bottom:6px;}
  .home-celebration-icon svg.mb-i{width:44px; height:44px; stroke-width:1.6;}
@keyframes homeConfettiFall{to{transform:translate(var(--confetti-drift),110vh) rotate(620deg); opacity:.15;}}
@media (max-width:360px){
  #rate-row{align-items:flex-start;}
  #supplier-header-actions{margin-left:0;}
  #account-row{gap:6px !important;}
  #account-btn,#history-btn,#discover-btn{padding-inline:10px;}
  .home-tour-tooltip{font-size:13px;}
}
@media (prefers-reduced-motion:reduce){.home-confetti{animation:none; display:none;}}
