<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
<title>La Place des Artisans — Click &amp; collect chez vos artisans locaux</title>
<meta name="description" id="metaDescription" content="Commandez en ligne chez les boulangers, bouchers, fromagers et artisans de votre région, puis récupérez vos produits sur place.">
<meta name="robots" id="metaRobots" content="index, follow">
<link rel="canonical" id="canonicalLink" href="https://laplacedesartisans.fr/">
<meta property="og:type" content="website">
<meta property="og:site_name" content="La Place des Artisans">
<meta property="og:locale" content="fr_FR">
<meta property="og:title" id="ogTitle" content="La Place des Artisans — Click &amp; collect chez vos artisans locaux">
<meta property="og:description" id="ogDescription" content="Commandez en ligne chez les boulangers, bouchers, fromagers et artisans de votre région, puis récupérez vos produits sur place.">
<meta property="og:url" id="ogUrl" content="https://laplacedesartisans.fr/">
<meta property="og:image" content="https://laplacedesartisans.fr/og-image.jpg">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta property="og:image:alt" content="La Place des Artisans — Le local a du talent">
<meta name="twitter:card" content="summary_large_image">
<link rel="manifest" href="/manifest.json">
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32.png">
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16.png">
<link rel="shortcut icon" href="/favicon.ico">
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png">
<meta name="theme-color" content="#15301f">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<meta name="apple-mobile-web-app-title" content="La Place des Artisans">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Fraunces:opsz,wght@9..144,400;9..144,500;9..144,600;9..144,700&family=Work+Sans:wght@400;500;600;700&display=swap" rel="stylesheet">
<style>
  :root{
    --forest-900:#0f2419; --forest-800:#15301f; --forest-700:#1c3a29; --forest-500:#2f5940;
    --oak-300:#e6bd85; --oak-500:#c98d4a; --oak-600:#a8712f; --oak-700:#8a5a2b;
    --cream:#faf5ec; --cream-2:#ffffff; --ink:#1d1c19; --ink-soft:#6b6559;
    --line:rgba(168,113,47,0.22); --oak-soft:rgba(201,141,74,0.10); --oak-soft-2:rgba(201,141,74,0.18);
    --danger:#a4402e; --radius:12px;
    --shadow:0 1px 2px rgba(29,28,25,0.06);
    color-scheme: light;
  }
  *{box-sizing:border-box;}
  html,body{margin:0;padding:0;}
  body{ background:var(--cream); color:var(--ink); font-family:"Work Sans",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif; min-height:100vh; -webkit-font-smoothing:antialiased; }
  .app{ max-width:480px; margin:0 auto; min-height:100vh; background:var(--cream); display:flex; flex-direction:column; position:relative; }

  header.top{ background:var(--forest-800); color:var(--cream); padding:env(safe-area-inset-top) 16px 0; height:calc(54px + env(safe-area-inset-top)); display:flex; align-items:center; position:sticky; top:0; z-index:20; border-bottom:2px solid var(--oak-500); }
  header.top .brand{ width:100%; }
  .brand{ display:flex; align-items:center; gap:10px; justify-content:space-between; }
  .brand-left{display:flex;align-items:center;gap:10px;min-width:0;}
  .brand-logo{ width:30px;height:30px;border-radius:50%; background:#fff;flex-shrink:0; background:var(--oak-500); display:flex;align-items:center;justify-content:center; font-size:16px;overflow:hidden; }
  .brand-logo img{width:100%;height:100%;object-fit:cover;}
  .brand-text{min-width:0;}
  .brand .mark{ font-family:"Fraunces",serif; font-optical-sizing:auto; font-weight:600; font-size:16.5px; letter-spacing:0.2px; color:var(--oak-300); white-space:nowrap;overflow:hidden;text-overflow:ellipsis;display:block; }
  .brand .sub{ display:none; }
  .gear-btn{ background:rgba(230,189,133,0.18); border:none;color:var(--oak-300); width:34px;height:34px;border-radius:50%; font-size:17px;cursor:pointer;flex-shrink:0; display:flex;align-items:center;justify-content:center;line-height:1;padding:0; }

  main{ flex:1; padding:18px 16px 96px; }
  h1.page-title{ font-family:"Fraunces",serif; font-weight:600; font-size:25px; line-height:1.15; margin:0 0 6px; color:var(--forest-900); letter-spacing:-0.2px; }
  p.page-sub{ font-size:14px; color:var(--ink-soft); margin:0 0 18px; line-height:1.45; }
  .intro{ margin:2px 0 20px; }
  .intro .steps{ display:flex; flex-wrap:wrap; gap:8px 16px; margin:12px 0 0; padding:0; list-style:none; font-size:13px; font-weight:600; color:var(--ink); }
  .intro .steps li{ display:flex; align-items:center; gap:7px; }
  .intro .steps .n{ display:inline-flex; align-items:center; justify-content:center; width:21px; height:21px; border-radius:50%; background:var(--forest-700); color:var(--cream); font-size:11.5px; font-weight:700; flex:0 0 auto; }
  h2.list-title{ font-family:"Fraunces",serif; font-weight:600; font-size:17px; margin:22px 0 2px; color:var(--forest-900); }
  .centered{ max-width:640px; margin-left:auto; margin-right:auto; }
  .shop-list{ border-top:2px solid var(--oak-soft-2); }
  a.shop-item{ display:flex; align-items:center; gap:13px; padding:14px 2px; border-bottom:1px solid var(--line);
               color:inherit; text-decoration:none; }
  a.shop-item:first-child{ border-top:0; }
  a.shop-item:hover{ background:var(--oak-soft); }
  .shop-item .avatar{ width:46px;height:46px;border-radius:50%;flex-shrink:0;overflow:hidden;
                      background:var(--oak-soft); border:1.5px solid var(--oak-soft-2);
                      display:flex;align-items:center;justify-content:center;font-size:19px;color:var(--oak-700); }
  .shop-item .avatar img{ width:100%;height:100%;object-fit:cover; }
  .shop-item .meta{ flex:1; min-width:0; }
  .shop-item .nm{ font-weight:600; font-size:16px; display:block; letter-spacing:-0.1px; }
  .shop-item .sub{ font-size:12.5px; color:var(--ink-soft); margin-top:2px; display:block;
                   white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
  .shop-item .dist{ font-size:12.5px; font-weight:700; color:var(--oak-700); background:var(--oak-soft); padding:3px 8px; border-radius:999px; white-space:nowrap; }
  .shop-item .chev{ color:var(--oak-600); flex-shrink:0; display:flex; align-items:center; justify-content:center;
                    width:26px; height:26px; border-radius:50%; background:rgba(201,141,74,0.14); }
  .toolbar{ display:flex; gap:8px; flex-wrap:wrap; margin:0 0 14px; }
  .toolbar .btn{ padding:7px 13px; font-size:13px; }
  .toolbar .btn svg{ color:var(--oak-600); }
  .foot-links{ display:flex; flex-direction:column; gap:10px; margin:26px 0 0; padding-top:16px; border-top:2px solid var(--oak-soft-2); }
  .foot-links a{ display:flex; align-items:center; justify-content:space-between; gap:12px;
                 color:var(--ink); text-decoration:none; font-size:13.5px; line-height:1.4; }
  .foot-links a strong{ color:var(--oak-700); white-space:nowrap; font-size:13px; }
  a.pro-cta{ display:flex; align-items:center; justify-content:space-between; gap:12px; margin:24px 0 6px; padding:14px 16px; border:1px dashed rgba(33,27,18,0.25); border-radius:var(--radius); color:var(--ink); text-decoration:none; font-size:13.5px; line-height:1.45; background:rgba(255,255,255,0.35); }
  a.pro-cta strong{ color:var(--forest-700); white-space:nowrap; }

  .btn{ display:inline-flex; align-items:center; justify-content:center; gap:8px; font-family:"Work Sans",sans-serif; font-weight:600; font-size:14.5px; border-radius:9px; border:1px solid transparent; padding:12px 16px; cursor:pointer; transition:transform .12s ease, opacity .12s ease; }
  .btn:active{transform:scale(0.98);}
  .btn-primary{ background:var(--forest-800); color:var(--cream); }
  .btn-oak{ background:var(--oak-500); color:var(--forest-900); }
  .btn-ghost{ background:var(--oak-soft); color:var(--ink); border-color:var(--line); }
  .btn-ghost:hover{ background:var(--oak-soft-2); }
  .btn-block{width:100%;}
  .link-btn{background:none;border:none;color:var(--forest-700);font-weight:600;font-size:13px;cursor:pointer;padding:0;}
  :root[data-theme="dark"] .link-btn{color:var(--oak-300);}
  .pro-link{ text-align:center; font-size:12.5px; color:var(--ink-soft); text-decoration:underline dotted; cursor:pointer; margin-top:18px; }

  .onboarding{ text-align:center; padding:14px 4px 10px; }
  .logo-picker{ width:88px;height:88px;border-radius:50%; border:2px dashed var(--oak-600); margin:0 auto 14px; display:flex;align-items:center;justify-content:center; flex-direction:column;gap:2px; cursor:pointer;color:var(--oak-700); background:var(--cream-2); overflow:hidden; }
  :root[data-theme="dark"] .logo-picker{color:var(--oak-300);}
  .logo-picker img{width:100%;height:100%;object-fit:cover;}
  .logo-picker .plus{font-size:22px;line-height:1;}
  .logo-picker .lbl{font-size:9.5px;font-weight:600;letter-spacing:.3px;}
  .shopname-input{ text-align:center;font-family:"Fraunces",serif;font-size:18px;font-weight:600; border:none;background:none;color:var(--ink); border-bottom:1px solid var(--line); padding:4px 8px 8px;margin-bottom:22px;width:80%;max-width:280px; }
  .shopname-input:focus{outline:none;border-color:var(--oak-600);}
  .dashed-cta{ border:2px dashed var(--oak-600); border-radius:var(--radius); padding:30px 20px; color:var(--oak-700); font-weight:600; font-size:15px; cursor:pointer; margin-top:6px; background:var(--cream-2); }
  :root[data-theme="dark"] .dashed-cta{color:var(--oak-300);}
  .dashed-cta .plus{font-size:26px;display:block;margin-bottom:6px;}

  .product-row{ display:flex; gap:13px; padding:13px 2px; background:none; border:0; border-bottom:1px solid var(--line); border-radius:0; margin:0; align-items:flex-start; }
  .thumb{ width:52px;height:52px;border-radius:8px;flex-shrink:0; display:flex;align-items:center;justify-content:center; font-size:19px; background:var(--oak-soft); border:1px solid var(--line); color:var(--oak-700); overflow:hidden; }
  .thumb.empty{ background:var(--cream); border-style:dashed; color:var(--line); }
  .app[data-noimg="1"] .pro-products .thumb.empty{ display:none; }
  .thumb img{width:100%;height:100%;object-fit:cover;}
  .row-body{flex:1;min-width:0;}
  .row-top{display:flex;justify-content:space-between;gap:8px;align-items:baseline;}
  .row-name{font-weight:700;font-size:15px;}
  .row-price{font-family:"Fraunces",serif;font-weight:600;color:var(--oak-700);font-size:15px;white-space:nowrap;}
  .row-cat{font-size:12px;color:var(--ink-soft);margin-top:1px;}
  .row-opts{font-size:11.5px;color:var(--ink-soft);margin-top:4px;}
  .chip-list{display:flex;flex-wrap:wrap;gap:6px;margin-top:8px;}
  .chip{ font-size:11.5px; padding:4px 9px; border-radius:999px; background:rgba(47,89,64,0.1); color:var(--forest-700); border:1px solid rgba(47,89,64,0.18); }
  .chip.tag{ background:rgba(201,141,74,0.15); color:#6b421d; border-color:rgba(201,141,74,0.3); }
  :root[data-theme="dark"] .chip{ color:var(--oak-300); background:rgba(230,189,133,0.1); border-color:rgba(230,189,133,0.22);}
  :root[data-theme="dark"] .chip.tag{ color:var(--oak-300); }
  .row-actions{display:flex;gap:14px;margin-top:9px;flex-wrap:wrap;}
  .icon-btn{ border:1px solid rgba(201,141,74,0.35); background:rgba(201,141,74,0.08); color:var(--oak-700); font-size:12.5px; font-weight:600; padding:6px 10px; border-radius:8px; cursor:pointer; }
  .icon-btn:hover{ background:rgba(201,141,74,0.18); }

  .empty{ text-align:center; padding:46px 20px; color:var(--ink-soft); border:1px dashed var(--line); border-radius:var(--radius); }
  .empty .glyph{font-size:30px;margin-bottom:8px;}
  .empty p{margin:4px 0 0;font-size:13.5px;line-height:1.5;}

  .fab{ position:fixed; left:50%; transform:translateX(-50%); bottom:18px; width:100%; max-width:480px; padding:0 18px; z-index:15; }

  .settings-section{margin-bottom:26px;}
  .settings-section h3{ font-family:"Fraunces",serif;font-size:16px;font-weight:600;margin:0 0 10px; }
  .settings-section .hint{font-size:12.5px;color:var(--ink-soft);margin:-4px 0 12px;line-height:1.5;}
  .manage-add{display:flex;gap:8px;}
  .manage-add input{flex:1;}
  .manage-chip-list{display:flex;flex-wrap:wrap;gap:8px;margin-top:6px;}
  .manage-chip{ display:flex;align-items:center;gap:8px; padding:7px 8px 7px 13px; border-radius:999px; background:var(--cream-2); border:1px solid var(--line); font-size:13px;font-weight:600; }
  .manage-chip button{ border:none;background:rgba(164,64,46,0.12);color:var(--danger); width:19px;height:19px;border-radius:50%;font-size:11px;cursor:pointer; display:flex;align-items:center;justify-content:center;padding:0; }
  .settings-logo-row{display:flex;align-items:center;gap:14px;margin-bottom:8px;}
  .multi-image-row{display:flex;gap:10px;}
  .img-slot{width:78px;height:78px;border-radius:10px;position:relative;overflow:hidden;background:var(--cream-2);border:1px solid var(--line);display:flex;align-items:center;justify-content:center;flex-shrink:0;}
  .img-slot img{width:100%;height:100%;object-fit:cover;}
  .img-slot.add{cursor:pointer;border-style:dashed;color:var(--oak-700);font-size:26px;}
  :root[data-theme="dark"] .img-slot.add{color:var(--oak-300);}
  .img-remove{position:absolute;top:3px;right:3px;width:20px;height:20px;border-radius:50%;border:none;background:rgba(0,0,0,0.55);color:#fff;font-size:11px;cursor:pointer;line-height:1;}
  .img-dots{position:absolute;bottom:9px;left:0;right:0;display:flex;justify-content:center;gap:6px;}
  .img-dot{width:7px;height:7px;border-radius:50%;background:rgba(255,255,255,0.55);cursor:pointer;}
  .img-dot.active{background:#fff;}
  .color-row{display:flex;justify-content:space-between;align-items:center;padding:9px 0;font-size:14px;font-weight:600;}
  input[type=color]{width:46px;height:32px;border:1px solid var(--line);border-radius:8px;padding:2px;background:var(--cream-2);cursor:pointer;}
  .hex-input{width:90px;font-family:monospace;font-size:13px;padding:8px 9px;text-transform:lowercase;}
  .hours-row{display:flex;align-items:center;justify-content:space-between;gap:10px;padding:10px 0;border-top:1px solid var(--line);font-size:13.5px;}
  .hours-row:first-of-type{margin-top:8px;}
  .day-toggle{display:flex;align-items:center;gap:8px;font-weight:600;min-width:100px;}
  .hours-times{display:flex;align-items:center;gap:8px;}
  input[type=time]{width:auto;flex:none;padding:8px;font-size:13px;}
  .closed-label{color:var(--ink-soft);font-size:12.5px;}
  .shop-info-block{font-size:12.5px;color:var(--ink-soft);margin:-10px 0 16px;line-height:1.6;}
  .notif-badge{ position:absolute; top:-7px; right:-7px; background:var(--danger); color:#fff; font-size:10.5px; font-weight:700; min-width:17px; height:17px; border-radius:9px; display:flex; align-items:center; justify-content:center; padding:0 4px; line-height:1; }
  .devis-badge{ display:inline-flex; align-items:center; justify-content:center; background:var(--danger); color:#fff; font-size:10.5px; font-weight:700; padding:1px 7px; border-radius:9px; margin-left:6px; vertical-align:middle; }
  .devis-alert{ background:rgba(164,64,46,0.08); border:1px solid var(--danger); border-radius:10px; padding:12px 14px; margin:0 0 16px; font-size:13px; line-height:1.5; }
  .practical-info-toggle{font-size:13px;font-weight:600;color:var(--oak-700);cursor:pointer;margin:0 0 18px;}
  :root[data-theme="dark"] .practical-info-toggle{color:var(--oak-300);}
  .shop-description{font-size:13.5px;color:var(--ink);line-height:1.55;margin:-2px 0 12px;font-style:italic;}

  .sheet-backdrop{ position:fixed; inset:0; background:rgba(15,36,25,0.55); z-index:40; display:none; align-items:flex-end; justify-content:center; }
  .sheet-backdrop.open{display:flex;}
  .sheet{ background:var(--cream); width:100%; max-width:480px; border-radius:20px 20px 0 0; padding:20px 20px calc(20px + env(safe-area-inset-bottom)); max-height:88vh; overflow-y:auto; }
  .sheet h2{ font-family:"Fraunces",serif; font-size:19px; font-weight:600; margin:2px 0 16px; }
  label.field-label{ display:block; font-size:12.5px; font-weight:600; color:var(--ink-soft); margin:14px 0 6px; }
  label.field-label:first-of-type{margin-top:0;}
  input[type=text], input[type=number], input[type=email], input[type=password], select, textarea{
    width:100%; font-family:"Work Sans",sans-serif; font-size:15px; padding:11px 12px;
    border:1px solid var(--line); border-radius:9px; background:var(--cream-2); color:var(--ink);
    box-shadow:0 1px 0 var(--oak-soft);
  }
  textarea{resize:vertical;min-height:64px;font-family:"Work Sans",sans-serif;}
  input:focus, select:focus, textarea:focus{ outline:2px solid var(--oak-500); outline-offset:1px; }
  .price-input{display:flex;align-items:center;gap:8px;}
  .price-input span{color:var(--ink-soft);font-weight:600;}

  .image-picker{ display:flex; align-items:center; gap:12px; }
  .image-picker .prev{ width:64px;height:64px;border-radius:10px; background:linear-gradient(155deg,var(--oak-300),var(--oak-600)); display:flex;align-items:center;justify-content:center; font-size:26px;color:var(--forest-900);overflow:hidden;flex-shrink:0; }
  .image-picker .prev img{width:100%;height:100%;object-fit:cover;}
  .image-picker label.upload{ border:1px solid var(--line); border-radius:10px; padding:9px 13px; font-size:13px; font-weight:600; cursor:pointer; color:var(--forest-700); }
  :root[data-theme="dark"] .image-picker label.upload{ color:var(--oak-300); }
  .image-picker input[type=file]{display:none;}

  .pay-banner{ display:flex; gap:12px; align-items:center; justify-content:space-between; background:rgba(201,141,74,0.12); border:1px solid rgba(201,141,74,0.5); border-radius:12px; padding:12px 14px; margin:0 0 16px; font-size:13px; line-height:1.45; }
  .pay-banner .btn{ white-space:nowrap; padding:9px 13px; font-size:13px; }
  @media (max-width:560px){ .pay-banner{ flex-direction:column; align-items:stretch; } }
  .pay-card{ background:var(--cream-2); border:1px solid var(--line); border-radius:12px; padding:14px 15px; margin:0 0 18px; }
  .pay-title{ font-family:"Fraunces",serif; font-size:16px; font-weight:600; margin:0 0 6px; }
  .pay-sub{ font-size:13px; color:var(--ink-soft); margin:0 0 10px; line-height:1.5; }
  .pay-ok{ font-size:13.5px; color:#2e6b32; font-weight:600; margin:0 0 8px; }
  .pay-row{ display:flex; gap:14px; align-items:center; flex-wrap:wrap; margin:8px 0; font-size:14px; }
  .pay-opt{ display:flex; gap:9px; align-items:flex-start; margin:8px 0; font-size:13.5px; line-height:1.45; cursor:pointer; }
  .pay-opt input{ margin-top:3px; }
  .pay-list{ margin:8px 0 14px; padding-left:18px; font-size:13.5px; line-height:1.6; }
  .deposit-note{ background:rgba(201,141,74,0.10); border:1px solid rgba(201,141,74,0.4); border-radius:10px; padding:11px 13px; margin:12px 0 0; font-size:13px; line-height:1.5; }
  .chip.dep{ font-size:11.5px; font-weight:600; padding:3px 9px; border-radius:999px; border:1px solid var(--line); background:var(--cream); color:var(--ink-soft); }
  .chip.dep-ok{ background:rgba(46,125,50,0.10); border-color:rgba(46,125,50,0.35); color:#2e6b32; }
  .chip.dep-wait{ background:rgba(201,141,74,0.14); border-color:rgba(201,141,74,0.45); color:var(--oak-700); }
  .chip.dep-late{ background:rgba(164,64,46,0.08); border-color:rgba(164,64,46,0.4); color:var(--danger); }
  .row-badges{ display:flex; gap:6px; flex-wrap:wrap; margin-top:6px; }
  .track-box{ text-align:left; background:var(--cream-2); border:1px solid var(--line); border-radius:12px; padding:14px 15px; margin:16px 0 0; font-size:13.5px; line-height:1.55; }
  .track-box .cancel-rule{ color:var(--ink-soft); font-size:12.5px; margin-top:8px; }
  .order-alert{ background:rgba(201,141,74,0.12); border:1px solid rgba(201,141,74,0.5); border-radius:10px; padding:12px 14px; margin:0 0 16px; font-size:13px; line-height:1.5; }
  .allerg-grid{ display:grid; grid-template-columns:repeat(2,1fr); gap:6px 12px; margin-bottom:10px; }
  @media (min-width:560px){ .allerg-grid{ grid-template-columns:repeat(3,1fr); } }
  .allerg-grid label{ display:flex; align-items:center; gap:7px; font-size:13.5px; cursor:pointer; }
  .allerg-none{ display:flex; align-items:center; gap:7px; font-size:13.5px; font-weight:600; color:var(--ink-soft); cursor:pointer; margin-bottom:4px; }
  .two-cols{ display:grid; grid-template-columns:1fr 1fr; gap:12px; }
  .allerg-list{ font-size:12.5px; color:var(--ink-soft); margin-top:6px; }
  .allerg-list strong{ color:var(--ink); font-weight:600; }
  .comptoir-log{ border-top:1px solid var(--line); margin-bottom:18px; }
  .log-line{ display:flex; align-items:center; gap:10px; padding:9px 2px; border-bottom:1px solid var(--line); font-size:13px; color:var(--ink-soft); }
  .log-code{ font-weight:700; color:var(--ink); letter-spacing:.5px; min-width:52px; }
  .log-line span:nth-child(2){ flex:1; }
  .log-par{ font-size:11.5px; font-weight:600; color:var(--oak-700); background:rgba(201,141,74,0.14); padding:2px 8px; border-radius:999px; }
  .prod-head{ display:flex; justify-content:space-between; align-items:flex-start; gap:12px;
              padding:12px 0 14px; border-bottom:2px solid var(--forest-800); margin-bottom:4px; font-size:14px; }
  .prod-table{ width:100%; border-collapse:collapse; }
  .prod-table td{ padding:11px 4px; border-bottom:1px solid var(--line); vertical-align:top; font-size:15px; }
  .prod-table td.q{ width:76px; font-weight:700; font-size:17px; color:var(--forest-900); white-space:nowrap; }
  .prod-table .opt{ display:block; font-size:12.5px; color:var(--ink-soft); font-weight:400; }
  @media print{
    header.top, .cat-scroll, .legal-footer, .foot-note, .cartbar, .icon-btn, .tab-bar, .pro-topbar{ display:none !important; }
    main{ padding:0 !important; }
    .prod-table td{ font-size:13pt; }
  }
  .closed-banner{ background:rgba(164,64,46,0.08); border:1px solid var(--danger); border-radius:10px;
                  padding:12px 14px; margin:0 0 14px; font-size:13.5px; line-height:1.5; }
  .opt-add-row{display:flex;gap:8px;}
  .opt-add-row input{flex:1;}
  .opt-editing-list{display:flex;flex-wrap:wrap;gap:7px;margin-top:10px;}
  .opt-editing-list .chip{display:flex;align-items:center;gap:6px;}
  .opt-editing-list .chip button{ border:none;background:none;color:inherit;font-size:13px;line-height:1; cursor:pointer;padding:0;opacity:.7; }

  .select-chip-list{display:flex;flex-wrap:wrap;gap:8px;}
  .select-chip{ padding:8px 14px;border-radius:999px; border:1px solid var(--line); background:var(--cream-2); font-size:13px;font-weight:600;color:var(--ink-soft); cursor:pointer; }
  .select-chip.active{ background:var(--forest-800); border-color:var(--forest-800); color:var(--cream); }
  .select-chip.tag-active{ background:var(--oak-500); border-color:var(--oak-500); color:var(--forest-900); }
  .select-chip.addnew{ border-style:dashed; color:var(--oak-700); }
  :root[data-theme="dark"] .select-chip.addnew{ color:var(--oak-300); }
  .inline-add{display:flex;gap:8px;margin-top:8px;}
  .inline-add input{flex:1;}
  .sheet-actions{display:flex;gap:10px;margin-top:20px;}
  .thread{display:flex;flex-direction:column;gap:8px;margin:14px 0;}
  .msg{max-width:82%;padding:9px 12px;border-radius:12px;font-size:13.5px;line-height:1.45;white-space:pre-wrap;word-break:break-word;}
  .msg .who{display:block;font-size:10.5px;font-weight:700;text-transform:uppercase;letter-spacing:.4px;opacity:.65;margin-bottom:3px;}
  .msg.client{align-self:flex-end;background:var(--forest-700);color:var(--cream);border-bottom-right-radius:3px;}
  .msg.artisan{align-self:flex-start;background:var(--cream-2);color:var(--ink);border:1px solid var(--line);border-bottom-left-radius:3px;}
  .msg .quote{display:block;margin-top:6px;font-family:"Fraunces",serif;font-size:15px;font-weight:600;}
  .devis-code{font-family:"Fraunces",serif;font-size:22px;font-weight:600;letter-spacing:1px;color:var(--oak-700);}
  .devis-head{background:var(--cream-2);border:1px solid var(--line);border-radius:10px;padding:12px 14px;margin-bottom:14px;}
  .sheet-actions .btn{flex:1;}

  .cat-scroll{ display:flex;gap:4px;overflow-x:auto;padding-bottom:0;margin-bottom:14px; border-bottom:1px solid var(--oak-soft-2); -ms-overflow-style:none; scrollbar-width:none; }
  .cat-scroll::-webkit-scrollbar{display:none;}
  .cat-chip{ flex-shrink:0; padding:9px 14px 10px; border:0; border-bottom:2px solid transparent; background:none; border-radius:0; font-size:14px; font-weight:600; color:var(--ink-soft); cursor:pointer; white-space:nowrap; margin-bottom:-1px; }
  .cat-chip.active{ color:var(--forest-900); border-bottom-color:var(--oak-500); border-bottom-width:3px; }
  .cat-chip.tagchip{ padding:7px 13px; border:1px solid var(--line); border-radius:999px; font-size:13px; margin-bottom:0; background:var(--oak-soft); }
  .cat-chip.tagchip.active{ background:var(--oak-500); border-color:var(--oak-500); color:var(--forest-900); }
  .filter-label{font-size:11px;font-weight:700;color:var(--oak-700);text-transform:uppercase;letter-spacing:.5px;margin:4px 0 6px;}
  .img-toggle{ background:none; border:0; padding:4px 0; margin:0 0 10px; font-family:"Work Sans",sans-serif;
               font-size:12.5px; font-weight:600; color:var(--ink-soft); cursor:pointer; display:inline-flex; align-items:center; gap:6px; }
  .img-toggle svg{ color:var(--oak-600); }
  .img-toggle:hover{ color:var(--forest-700); }
  .app[data-noimg="1"] .p-thumb,
  .app[data-noimg="1"] .card-media,
  .app[data-noimg="1"] .di-thumb,
  .app[data-noimg="1"] .pro-products .thumb{ display:none; }


  .card{ background:var(--cream-2); border:1px solid var(--line); border-radius:12px; overflow:hidden; margin-bottom:10px; box-shadow:0 1px 2px rgba(168,113,47,0.10); }
  .p-main{ display:flex; gap:13px; align-items:flex-start; }
  .p-text{ flex:1; min-width:0; }
  .p-thumb{ width:78px; height:78px; border-radius:9px; overflow:hidden; flex-shrink:0; position:relative; background:var(--oak-soft); }
  .di-thumb{ width:46px;height:46px;border-radius:8px;overflow:hidden;flex-shrink:0;background:var(--oak-soft);border:1px solid var(--line); }
  .di-thumb img{ width:100%;height:100%;object-fit:cover;display:block; }
  .drawer-item .di-main{ display:flex; gap:11px; align-items:flex-start; flex:1 1 auto; min-width:0; }
  .drawer-item .di-body{ flex:1 1 auto; min-width:0; }
  .drawer-item .di-name{ overflow-wrap:anywhere; }
  .p-thumb img{ width:100%; height:100%; object-fit:cover; display:block; }
  .p-thumb .img-dots{ bottom:5px; }
  .card-media{ height:128px; background:linear-gradient(155deg,var(--oak-300),var(--oak-600)); display:flex;align-items:center;justify-content:center; font-size:44px;color:var(--forest-900); position:relative; }
  .card-media img{width:100%;height:100%;object-fit:cover;}
  .card-body{padding:12px 13px 13px;}
  .card-top{display:flex;justify-content:space-between;align-items:baseline;gap:10px;}
  .card-name{font-weight:600;font-size:15.5px;line-height:1.3;}
  .card-price{font-weight:700;font-size:15px;color:var(--ink);white-space:nowrap;}
  .card-cat{font-size:12.5px;color:var(--ink-soft);margin-top:3px;}
  .card-desc{font-size:13px;color:var(--ink-soft);margin-top:6px;line-height:1.45;}
  .card-tags{display:flex;flex-wrap:wrap;gap:6px;margin-top:9px;}

  .opt-check{ display:flex;align-items:center;gap:10px; padding:9px 0; border-top:1px solid var(--line); font-size:14px; font-style:italic; cursor:pointer; }
  .opt-check:first-of-type{margin-top:10px;}
  .opt-check input{ width:19px;height:19px;flex-shrink:0; accent-color:var(--oak-600); }
  .opt-check.disabled{ opacity:.45; cursor:not-allowed; }
  .opt-check.disabled input{ cursor:not-allowed; }
  .conflict-row{display:flex;gap:8px;align-items:center;}
  .conflict-row select{flex:1;}

  .qty-add{ display:flex;align-items:center;justify-content:space-between; margin-top:14px;gap:10px; }
  .qty-controls{ display:flex;align-items:center;gap:10px; border:1px solid var(--line); border-radius:10px; padding:5px 8px; }
  .qty-controls button{ border:none;background:rgba(201,141,74,0.16);font-size:16px;font-weight:700; color:var(--oak-700);cursor:pointer;
                        width:26px;height:26px;border-radius:50%;line-height:1;display:flex;align-items:center;justify-content:center;padding:0; }
  .qty-controls button:hover{ background:rgba(201,141,74,0.26); }
  :root[data-theme="dark"] .qty-controls button{ color:var(--oak-300); }
  .qty-controls span{font-weight:600;min-width:14px;text-align:center;}

  .cartbar{ position:fixed; left:50%;transform:translateX(-50%); bottom:0; width:100%; max-width:480px; background:var(--forest-800); color:var(--cream); border-radius:0; padding:15px 18px calc(15px + env(safe-area-inset-bottom,0px)); display:flex; align-items:center; justify-content:space-between; box-shadow:0 14px 30px -12px rgba(15,36,25,0.55); cursor:pointer; z-index:25; }
  .cartbar .left{display:flex;align-items:center;gap:10px;}
  .cartbar .count{ background:var(--oak-500);color:var(--forest-900); font-weight:700;font-size:12.5px; width:22px;height:22px;border-radius:50%; display:flex;align-items:center;justify-content:center; }
  .cartbar .total{font-family:"Fraunces",serif;font-weight:600;}
  .cartbar .cta{font-size:13.5px;font-weight:600;color:var(--oak-300);}

  .drawer-item{ display:flex;justify-content:space-between;gap:10px; padding:12px 0;border-bottom:1px solid var(--line); }
  .drawer-item .di-name{font-weight:600;font-size:14.5px;}
  .drawer-item .di-opts{font-size:12px;color:var(--ink-soft);margin-top:3px;}
  .drawer-item .di-price{font-family:"Fraunces",serif;font-weight:600;font-size:14.5px;white-space:nowrap;}
  .drawer-item .di-remove{font-size:11.5px;color:var(--danger);background:none;border:none;padding:0;margin-top:6px;cursor:pointer;font-weight:600;}
  .drawer-total{ display:flex;justify-content:space-between; padding:14px 0 6px; font-family:"Fraunces",serif;font-weight:600;font-size:17px; }

  .confirm{ text-align:center; padding:30px 10px 10px; }
  .confirm .stamp{ width:74px;height:74px;border-radius:50%; background:var(--oak-500);color:var(--forest-900); display:flex;align-items:center;justify-content:center; font-size:32px;margin:0 auto 18px; }
  .confirm h2{font-family:"Fraunces",serif;font-size:21px;margin:0 0 8px;}
  .confirm p{color:var(--ink-soft);font-size:14px;line-height:1.6;margin:0 0 4px;}
  .confirm .code{ display:inline-block;margin:18px 0; font-family:"Fraunces",serif;font-weight:600;font-size:26px; letter-spacing:2px; background:var(--cream-2); border:1px dashed var(--oak-600); padding:10px 22px;border-radius:12px; }

  .foot-note{ text-align:center; font-size:11.5px; color:var(--ink-soft); padding:10px 18px 0; }
  .legal-footer{ text-align:center; font-size:11.5px; color:var(--ink-soft); padding:8px 18px calc(14px + env(safe-area-inset-bottom,0px)); }
  html.sys-inset header.top{ padding-top:30px; height:calc(54px + 30px); }
  html.sys-inset .legal-footer{ padding-bottom:40px; }
  html.sys-inset .cartbar{ padding-bottom:38px; }
  .legal-footer a{ color:inherit; cursor:pointer; text-decoration:underline dotted; }
  a.product-row{ color:inherit; text-decoration:none; }
  a.brand-left{ color:inherit; text-decoration:none; }
  .toast{ position:fixed; left:50%; bottom:88px; transform:translateX(-50%); background:var(--forest-800); color:var(--cream); padding:11px 16px; border-radius:12px; font-size:13.5px; box-shadow:var(--shadow); z-index:200; max-width:calc(100% - 36px); text-align:center; }

  .rule-list{margin:8px 0 4px;}
  .rule-item{font-size:12px;color:var(--ink-soft);padding:2px 0;}
  .rule-item:before{content:"• ";}
  .rule-item.ok{color:var(--forest-700);}
  :root[data-theme="dark"] .rule-item.ok{color:var(--oak-300);}
  .rule-item.ok:before{content:"✓ ";}
  .auth-error{color:var(--danger);font-size:13px;margin-bottom:10px;min-height:14px;}
  .demo-note{ background:var(--cream-2); border:1px dashed var(--oak-600); border-radius:10px; padding:12px; font-size:12.5px; color:var(--ink-soft); margin-bottom:16px; line-height:1.5; }

  @media (min-width:481px){ .app{ border-left:1px solid var(--line); border-right:1px solid var(--line); } }
  @media (min-width:820px){
    .app{ max-width:820px; }
    main{ padding:24px 36px 100px; }
  }
  @media (min-width:1180px){
    .app{ max-width:980px; }
    main{ padding:28px 40px 100px; }
  }
  .grid-wrap{ display:grid; grid-template-columns:1fr; gap:14px; align-items:start; }
  .grid-wrap .card, .grid-wrap .product-row{ margin-bottom:0; }
  @media (min-width:640px){ .grid-wrap.cards{ grid-template-columns:repeat(2,1fr); } }
  @media (min-width:960px){ .grid-wrap.cards{ grid-template-columns:repeat(3,1fr); } }
  .grid-wrap.rows{ gap:0; max-width:660px; border-top:1px solid var(--line); }
</style>
</head>
<body>
<div class="app">
  <header class="top">
    <div class="brand">
      <a class="brand-left" href="/" onclick="return onBrandClick(event)">
        <div class="brand-logo" id="headerLogo"><img alt="" src="data:image/png;base64,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9J033tTc3Hjf409mUpkV69ZNvvXGk447JpOMM8aISEphhCpmfjentLLq6COOOvTAA35esmzl2rX77VOTMa17pz7/xoefXHPxxelEcuqLL1x64YUjBvRftW1bVbfup1985fkTzjRd+dBTT595+vgXH360dtfm6++ZPKj/gHm//dajuuv7Lz7XUL/tuv95QFPUnS2tjW3xT156fnC/vpOnTHnupddOPW28IumL73/IALz99JTTTzwxmUjccM89lWVlK9eslQTffviGcNNFlbuCqNLx+sBPW/59nfeTaK0lSj/6zJMHnzxu957te+q37Hv8scdfcjERvf+v98aeehqR3L59U3TwPstX/XHX5AfOu/pqInr57TdfeO0VkmaiZaf3hcmWnVJkLrr+uh/n/khEH82Yht27TX7yf4nidqZp1erfy4bt2/uAUc+/9OLRZ51xy93/k0w0DT5u7GHnnp1MtlqZ1vXrV6o9e15ywzVEdOv99x5y2hlEtHtP7QOPP0Lk3HjbDd2H7ENEyUQLDhx804P3E5lLFs+B6m5Hjv/L+g2/b9u6odfRRx1w5plE9PfX/tl9nyFE5NjJyY8+kiqSQO4nk6hPx+uV9gNbMDD7AQxdq6+tnfLSK+++8Eynqi4Aol91dVtrC5D77D9eHTpk3x/n/tyWiINw5//6azwenzN37sKli6+68GJpNqXjTQpXPftrhIxt2zfP+mme69gffTYtbplUUvbFnHm3X3mFbhi9u1crQo4YMnji1Vdfft54XQ+hEtIMQ+E8Egk76dauVaUlhkGuAwCdO3VatGzpHU88fv35f73/jputZMPF555x88RrAcSWbdtKOE+1tgCI7t16qCXRY444aED//gD64fuPWLhsOYATC6m7Nm8+77pr7r3hhvvuujsTr8vXB9unGqx94Y98hQ4iUnRj9cZtiYzZqSwizBRJkUmnNEXNpONbd9SqHHfW7mxuaX7t8YfOGHvYjZefHykvP2zc6addcnFtfQNXuAQCYETA9fIPPptxwPBh55x+2tGHjjr/9NMOHbLP78tXLF+zgSmhTCbh2k5VeYkQrY4gyyUAxhiS65CVEMJxHEdyroVjAHTNBedfMuG0KVOfqjnk6KdeeFFTtf33G/7Dzwsvue76jz7/0komGDCAEICCuuEAI0kkM3Yqobiuk26ZcMrYiTdc/cGnXww9/sSH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"></div>
        <div class="brand-text">
          <span class="mark" id="headerName">La Place des Artisans</span>
          <span class="sub" id="headerSub">Le local a du talent</span>
        </div>
      </a>
      <button class="gear-btn" id="gearBtn" onclick="openSettings()" title="Paramètres" aria-label="Paramètres de la boutique" style="display:none;">⚙︎</button>
      <button class="gear-btn" id="loginBtn" onclick="onLoginButton()" title="Espace artisan" aria-label="Espace artisan : connexion" style="display:none;">👤</button>
    </div>
  </header>

  <main id="main"></main>
  <div class="foot-note" id="footNote"></div>
  <div class="legal-footer">
    <a href="/mentions-legales" onclick="return navLegal(event,'mentions')">Mentions légales</a> · <a href="/cgu" onclick="return navLegal(event,'cgu')">CGU</a> · <a href="/confidentialite" onclick="return navLegal(event,'confidentialite')">Confidentialité</a>
  </div>
</div>

<!-- Sheet: shop settings -->
<div class="sheet-backdrop" id="settingsBackdrop">
  <div class="sheet">
    <h2>Paramètres de la boutique</h2>

    <div class="settings-section">
      <h3>Identité</h3>
      <div style="text-align:center;margin-bottom:14px;">
        <div class="logo-picker" style="width:84px;height:84px;margin:0 auto;" onclick="document.getElementById('shopLogoInput').click()">
          <div id="settingsLogoPreview" style="width:100%;height:100%;display:flex;align-items:center;justify-content:center;">🥖</div>
        </div>
        <input type="file" id="shopLogoInput" accept="image/*" style="display:none">
        <p class="hint" style="margin-top:8px;margin-bottom:0;">Cliquez sur le cercle pour changer le logo.</p>
      </div>
      <label class="field-label">Nom de la boutique</label>
      <input type="text" spellcheck="true" lang="fr" id="shopNameInput" placeholder="Nom de votre boutique">
    </div>

    <div class="settings-section">
      <h3>Couleurs</h3>
      <p class="hint">Personnalisez l'apparence de votre boutique.</p>
      <div class="color-row"><span>Fond</span><div style="display:flex;align-items:center;gap:8px;"><input type="text" id="colorFondHex" class="hex-input" maxlength="7" placeholder="#f6f1e6" oninput="syncColorFromHex('Fond')"><input type="color" id="colorFond" oninput="syncColorFromPicker('Fond')"></div></div>
      <div class="color-row"><span>Bandeau</span><div style="display:flex;align-items:center;gap:8px;"><input type="text" id="colorBandeauHex" class="hex-input" maxlength="7" placeholder="#15301f" oninput="syncColorFromHex('Bandeau')"><input type="color" id="colorBandeau" oninput="syncColorFromPicker('Bandeau')"></div></div>
      <div class="color-row"><span>Icône produits</span><div style="display:flex;align-items:center;gap:8px;"><input type="text" id="colorIconeHex" class="hex-input" maxlength="7" placeholder="#c98d4a" oninput="syncColorFromHex('Icone')"><input type="color" id="colorIcone" oninput="syncColorFromPicker('Icone')"></div></div>
      <div class="color-row"><span>Texte</span><div style="display:flex;align-items:center;gap:8px;"><input type="text" id="colorTexteHex" class="hex-input" maxlength="7" placeholder="#211b12" oninput="syncColorFromHex('Texte')"><input type="color" id="colorTexte" oninput="syncColorFromPicker('Texte')"></div></div>
      <div id="colorWarning"></div>
      <button class="link-btn" style="margin-top:6px;" onclick="resetColors()">Réinitialiser les couleurs</button>
    </div>

    <div class="settings-section">
      <h3>Catégories</h3>
      <p class="hint">Organisez vos produits comme vous le souhaitez.</p>
      <div class="manage-chip-list" id="catManageList"></div>
      <div class="manage-add" style="margin-top:12px;">
        <input type="text" spellcheck="true" lang="fr" id="newCatInput" placeholder="Nouvelle catégorie" onkeydown="if(event.key==='Enter'){event.preventDefault();addCategory();}">
        <button class="btn btn-ghost" onclick="addCategory()">Ajouter</button>
      </div>
    </div>

    <div class="settings-section">
      <h3>Tags &amp; filtres</h3>
      <p class="hint">Ces tags apparaissent sur vos fiches produits et servent de filtres pour vos clients.</p>
      <div class="manage-chip-list" id="tagManageList"></div>
      <div class="manage-add" style="margin-top:12px;">
        <input type="text" spellcheck="true" lang="fr" id="newTagInput" placeholder="Nouveau tag" onkeydown="if(event.key==='Enter'){event.preventDefault();addTag();}">
        <button class="btn btn-ghost" onclick="addTag()">Ajouter</button>
      </div>
    </div>

    <div class="sheet-actions">
      <button class="btn btn-oak btn-block" onclick="closeSettings()">Terminé</button>
    </div>
  </div>
</div>

<!-- Sheet: add/edit product -->
<div class="sheet-backdrop" id="sheetBackdrop">
  <div class="sheet">
    <h2 id="sheetTitle">Créer une fiche produit</h2>
    <label class="field-label">Nom du produit</label>
    <input type="text" spellcheck="true" lang="fr" id="fNom" placeholder="Ex. Pain de campagne">
    <label class="field-label">Description (optionnel)</label>
    <textarea spellcheck="true" lang="fr" id="fDesc" placeholder="Ex. Pain au levain naturel, cuit sur pierre."></textarea>
    <button type="button" class="link-btn" style="margin-top:6px;" onclick="checkGrammar('fDesc','fDescGrammar')">Vérifier l'orthographe et la grammaire</button>
    <div id="fDescGrammar" style="margin-top:8px;"></div>
    <label class="field-label">Prix</label>
    <div class="price-input"><input type="text" spellcheck="true" lang="fr" inputmode="decimal" id="fPrix" placeholder="0,00"><span>€</span></div>
    <label class="field-label">Unité</label>
    <select id="fUnit">
      <option value="unité">Unité</option>
      <option value="kg">Kg</option>
      <option value="100g">100 g</option>
      <option value="litre">Litre</option>
      <option value="cl">Cl</option>
      <option value="pièce">Pièce</option>
      <option value="lot">Lot</option>
      <option value="portion">Portion</option>
    </select>
    <label class="field-label">Photos (jusqu'à 3)</label>
    <div class="multi-image-row" id="fImagesRow"></div>
    <input type="file" id="fImageInput" accept="image/*" style="display:none">
    <label class="field-label">Catégorie</label>
    <div class="select-chip-list" id="fCatChips"></div>
    <div class="inline-add" id="fCatAddRow">
      <input type="text" spellcheck="true" lang="fr" id="fCatNewInput" placeholder="+ Nouvelle catégorie" onkeydown="if(event.key==='Enter'){event.preventDefault();confirmNewCatDraft();}">
      <button class="btn btn-ghost" onclick="confirmNewCatDraft()">Ajouter</button>
    </div>
    <label class="field-label">Tags</label>
    <div class="select-chip-list" id="fTagChips"></div>
    <div class="inline-add" id="fTagAddRow">
      <input type="text" spellcheck="true" lang="fr" id="fTagNewInput" placeholder="+ Nouveau tag" onkeydown="if(event.key==='Enter'){event.preventDefault();confirmNewTagDraft();}">
      <button class="btn btn-ghost" onclick="confirmNewTagDraft()">Ajouter</button>
    </div>
    <label class="field-label">Délai minimum avant retrait</label>
    <select id="fLeadDays">
      <option value="0">Aucun</option>
      <option value="1">1 jour</option>
      <option value="2">2 jours</option>
      <option value="3">3 jours</option>
      <option value="4">4 jours</option>
      <option value="5">5 jours</option>
      <option value="7">1 semaine</option>
      <option value="14">2 semaines</option>
    </select>
    <p style="font-size:12px;color:var(--ink-soft);margin:4px 0 0;">Ex. une pièce montée peut nécessiter 2 jours, pour éviter une commande la veille pour le lendemain.</p>

    <label class="field-label">Quantité maximum par commande (optionnel)</label>
    <input type="text" inputmode="decimal" id="fMaxQty" placeholder="Laisser vide = pas de limite">
    <p style="font-size:12px;color:var(--ink-soft);margin:4px 0 0;">Au-delà, le client devra vous laisser un message avec ses coordonnées au lieu de commander directement.</p>

    <label class="field-label">Stock disponible (optionnel)</label>
    <input type="text" inputmode="decimal" id="fStock" placeholder="Laisser vide = stock non suivi">
    <p style="font-size:12px;color:var(--ink-soft);margin:4px 0 0;">Si renseigné : tant qu'il reste du stock, la commande est possible sans délai minimum. Décompté automatiquement à chaque commande.</p>
    <label class="field-label">Allergènes présents</label>
    <p style="font-size:12px;color:var(--ink-soft);margin:-2px 0 8px;">Obligatoire pour la vente de denrées alimentaires : le client doit pouvoir les consulter avant de commander. Cochez tout ce qui entre dans la recette, y compris les traces éventuelles.</p>
    <div class="allerg-grid" id="fAllergenes"></div>
    <label class="allerg-none"><input type="checkbox" id="fNoAllerg" onchange="setNoAllergenes(this.checked)"> Aucun des 14 allergènes à déclarer</label>

    <div class="two-cols">
      <div>
        <label class="field-label">Poids (g)</label>
        <input type="text" inputmode="numeric" id="fPoids" placeholder="Ex. 250">
      </div>
      <div>
        <label class="field-label">Portions</label>
        <input type="text" inputmode="numeric" id="fPortions" placeholder="Ex. 8">
      </div>
    </div>

    <label class="field-label">Options proposées au client (à cocher lors de la commande)</label>
    <div class="opt-add-row">
      <input type="text" spellcheck="true" lang="fr" id="fOptInput" placeholder="Ex. Trancher le pain" onkeydown="if(event.key==='Enter'){event.preventDefault();addOptionDraft();}">
      <input type="text" spellcheck="true" lang="fr" inputmode="decimal" id="fOptPriceInput" placeholder="+0,00 €" style="max-width:96px;" onkeydown="if(event.key==='Enter'){event.preventDefault();addOptionDraft();}">
      <button class="btn btn-ghost" onclick="addOptionDraft()">Ajouter</button>
    </div>
    <div class="opt-editing-list" id="fOptList"></div>

    <label class="field-label">Options incompatibles entre elles (optionnel)</label>
    <p style="font-size:12.5px;color:var(--ink-soft);margin:-2px 0 8px;line-height:1.5;">Ex. « Pain tranché » et « Pain entier » ne peuvent pas être cochées ensemble.</p>
    <div class="conflict-row">
      <select id="fConflictA"></select>
      <select id="fConflictB"></select>
      <button class="btn btn-ghost" onclick="addConflictDraft()">Lier</button>
    </div>
    <div class="opt-editing-list" id="fConflictList"></div>
    <div class="sheet-actions">
      <button class="btn btn-ghost" onclick="closeSheet()">Annuler</button>
      <button class="btn btn-oak" onclick="saveProduct()">Enregistrer</button>
    </div>
  </div>
</div>

<!-- Sheet: cart -->
<div class="sheet-backdrop" id="cartBackdrop">
  <div class="sheet">
    <h2>Votre panier</h2>
    <div id="cartItems"></div>
    <div class="drawer-total" id="cartTotal"></div>
    <div id="pickupPointSelectWrap" style="display:none;">
      <label class="field-label">Lieu de retrait</label>
      <select id="pickupPointSelect" onchange="onPickupPointChange()"></select>
    </div>
    <label class="field-label">Jour de retrait</label>
    <select id="pickupDay" onchange="populateTimesForSelectedDay()"></select>
    <label class="field-label">Heure de retrait</label>
    <select id="pickupTime"></select>
    <p id="leadTimeNote" style="font-size:12px;color:var(--oak-700);margin:6px 0 0;"></p>
    <div id="validationEmailWrap" style="margin-top:10px;">
      <label class="field-label">Votre e-mail (facultatif)</label>
      <input type="email" id="cartValidationEmail" placeholder="vous@exemple.fr">
      <p id="cartEmailHint" style="font-size:12px;color:var(--ink-soft);margin:4px 0 0;"></p>
    </div>
    <div id="cartDepositNote" class="deposit-note" style="display:none;"></div>
    <div class="sheet-actions">
      <button class="btn btn-ghost" style="flex:0 0 auto;" onclick="closeCart()">Fermer</button>
      <button class="btn btn-oak" id="cartSubmitBtn" style="flex:1 1 auto;" onclick="validateOrder()">Valider la commande (réglée sur place)</button>
    </div>
    <p id="cartPayNote" style="font-size:11.5px;color:var(--ink-soft);text-align:center;margin:8px 0 0;">Le règlement se fait au moment du retrait.</p>
  </div>
</div>

<!-- Sheet: création d'une boutique (admin) -->
<div class="sheet-backdrop" id="newShopBackdrop">
  <div class="sheet">
    <h2>Créer une boutique</h2>
    <p class="page-sub">La boutique est créée privée : elle n'apparaîtra dans l'annuaire qu'une fois publiée avec un SIRET vérifié.</p>
    <label class="field-label">Nom de la boutique</label>
    <input type="text" spellcheck="true" lang="fr" id="newShopName" placeholder="Ex. Boulangerie du Centre">
    <label class="field-label">E-mail du compte artisan (facultatif)</label>
    <input type="email" id="newShopOwner" placeholder="artisan@exemple.fr">
    <p style="font-size:12px;color:var(--ink-soft);margin:-2px 0 0;">Laissez vide pour garder la boutique sous votre compte administrateur. Vous pourrez l'attribuer plus tard.</p>
    <div class="auth-error" id="newShopError"></div>
    <div class="sheet-actions">
      <button class="btn btn-ghost" onclick="closeNewShop()">Annuler</button>
      <button class="btn btn-oak" onclick="createShopAsAdmin()">Créer</button>
    </div>
  </div>
</div>

<!-- Sheet: discussion d'un devis (côté artisan) -->
<div class="sheet-backdrop" id="shopDevisBackdrop">
  <div class="sheet">
    <h2 id="shopDevisTitle">Devis</h2>
    <p class="page-sub" id="shopDevisIntro"></p>
    <div id="shopDevisThread"></div>
    <label class="field-label">Votre réponse</label>
    <textarea spellcheck="true" lang="fr" id="shopDevisBody" placeholder="Répondez au client…"></textarea>
    <label class="field-label">Montant total proposé (facultatif)</label>
    <input type="text" inputmode="decimal" id="shopDevisQuote" placeholder="Ex. 240,00">
    <p style="font-size:12px;color:var(--ink-soft);margin:-2px 0 0;">Si vous indiquez un montant, le client pourra l'accepter ou le refuser d'un clic.</p>
    <label class="field-label">Acompte demandé (facultatif)</label>
    <div style="display:flex;gap:8px;align-items:center;">
      <input type="text" inputmode="decimal" id="shopDevisDeposit" placeholder="Ex. 72,00" style="flex:1;">
      <button type="button" class="btn btn-ghost" style="padding:10px 12px;font-size:12.5px;white-space:nowrap;" onclick="setDepositPercent(30)">30 %</button>
      <button type="button" class="btn btn-ghost" style="padding:10px 12px;font-size:12.5px;white-space:nowrap;" onclick="setDepositPercent(50)">50 %</button>
    </div>
    <p style="font-size:12px;color:var(--ink-soft);margin:-2px 0 0;">Montant à verser pour bloquer la commande. Le paiement en ligne n'étant pas encore actif, l'acompte se règle en boutique et le solde au retrait.</p>
    <div class="auth-error" id="shopDevisError"></div>
    <div class="sheet-actions">
      <button class="btn btn-ghost" onclick="closeShopDevis()">Fermer</button>
      <button class="btn btn-oak" onclick="sendShopDevisReply()">Envoyer</button>
    </div>
  </div>
</div>

<!-- Sheet: demande spéciale (quantité au-delà du maximum autorisé) -->
<div class="sheet-backdrop" id="specialRequestBackdrop">
  <div class="sheet">
    <h2>Quantité importante</h2>
    <p class="page-sub" id="specialRequestIntro"></p>
    <div class="auth-error" id="specialRequestError"></div>
    <label class="field-label">Quantité souhaitée <span id="srQtyUnit" style="font-weight:400;color:var(--ink-soft);"></span></label>
    <input type="text" spellcheck="true" lang="fr" inputmode="decimal" id="srQty" placeholder="Ex. 8">
    <label class="field-label">E-mail (facultatif)</label>
    <input type="email" id="srEmail" placeholder="vous@exemple.fr">
    <p style="font-size:12px;color:var(--ink-soft);margin:-2px 0 0;">Uniquement pour vous prévenir d'une réponse. Il n'est pas communiqué à l'artisan : tout l'échange se fait dans la messagerie de votre devis.</p>
    <label class="field-label">Votre demande</label>
    <textarea spellcheck="true" lang="fr" id="srMessage" placeholder="Précisions sur votre demande…"></textarea>
    <div class="sheet-actions">
      <button class="btn btn-ghost" onclick="closeSpecialRequest()">Annuler</button>
      <button class="btn btn-oak" onclick="submitSpecialRequest()">Demander un devis</button>
    </div>
  </div>
</div>

<!-- Sheet: fermeture exceptionnelle -->
<div class="sheet-backdrop" id="fermetureBackdrop">
  <div class="sheet">
    <h2>Fermeture exceptionnelle</h2>
    <p class="page-sub">Vos clients ne pourront pas choisir de créneau de retrait pendant cette période.</p>
    <div class="two-cols">
      <div><label class="field-label">Du</label><input type="date" id="fermDebut"></div>
      <div><label class="field-label">Au (inclus)</label><input type="date" id="fermFin"></div>
    </div>
    <label class="field-label">Message affiché aux clients (facultatif)</label>
    <input type="text" spellcheck="true" lang="fr" id="fermMessage" placeholder="Ex. Congés annuels, retour le 18 août">
    <div class="auth-error" id="fermError"></div>
    <div class="sheet-actions">
      <button class="btn btn-ghost" onclick="closeFermeture()">Annuler</button>
      <button class="btn btn-oak" onclick="saveFermeture()">Enregistrer</button>
    </div>
  </div>
</div>

<!-- Sheet: rappel activation des paiements -->
<div class="sheet-backdrop" id="payPopupBackdrop">
  <div class="sheet">
    <h2>Activez vos paiements en ligne</h2>
    <p class="page-sub">Gratuit, sécurisé par Stripe, quelques minutes.</p>
    <ul class="pay-list">
      <li>Vos clients versent un <strong>acompte</strong> dès que vous acceptez leur commande (30 % par défaut, vous le réglez).</li>
      <li>L'acompte arrive <strong>sur votre compte</strong> : La Place des Artisans n'en prélève rien.</li>
      <li>Vous gardez la main : <strong>annulation et remboursement</strong> en un bouton.</li>
    </ul>
    <p class="pay-sub">Obligatoire pour publier une nouvelle boutique.</p>
    <div class="sheet-actions">
      <button class="btn btn-ghost" onclick="closePayPopup()">Plus tard</button>
      <button class="btn btn-oak" onclick="goToPayments()">Activer maintenant</button>
    </div>
  </div>
</div>

<!-- Sheet: retrait d'un devis accepté -->
<div class="sheet-backdrop" id="devisPickupBackdrop">
  <div class="sheet">
    <h2>Retrait de votre commande</h2>
    <p class="page-sub" id="dvIntro"></p>
    <div id="dvPlaceWrap" style="display:none;">
      <label class="field-label">Lieu de retrait</label>
      <select id="dvPlace" onchange="dvPlaceChanged()"></select>
    </div>
    <label class="field-label">Jour de retrait</label>
    <select id="dvDay" onchange="dvFillTimes()"></select>
    <label class="field-label">Heure de retrait</label>
    <select id="dvTime"></select>
    <label class="field-label">Votre e-mail (facultatif)</label>
    <input type="email" id="dvEmail" placeholder="vous@exemple.fr">
    <p id="dvHint" style="font-size:12px;color:var(--ink-soft);margin:4px 0 0;"></p>
    <div class="auth-error" id="dvError"></div>
    <div class="sheet-actions">
      <button class="btn btn-ghost" onclick="closeDevisPickup()">Annuler</button>
      <button class="btn btn-oak" onclick="confirmDevisPickup()">Accepter et confirmer</button>
    </div>
  </div>
</div>

<!-- Sheet: point de retrait -->
<div class="sheet-backdrop" id="pickupPointBackdrop">
  <div class="sheet">
    <h2 id="ppSheetTitle">Point de retrait</h2>
    <label class="field-label">Nom du point de retrait</label>
    <input type="text" spellcheck="true" lang="fr" id="ppLabel" placeholder="Ex. Marché de Ribemont">
    <label class="field-label">Adresse</label>
    <input type="text" spellcheck="true" lang="fr" id="ppAddress" placeholder="Ex. Place du marché, 02240 Ribemont">
    <label class="field-label">Horaires</label>
    <select id="ppHoursMode" onchange="renderPPHoursEditor()">
      <option value="shop">Mêmes horaires que la boutique</option>
      <option value="custom">Horaires spécifiques à ce point</option>
    </select>
    <div id="ppHoursEditor"></div>
    <label class="field-label">Délai minimum propre à ce point</label>
    <select id="ppLeadDays">
      <option value="0">Aucun</option>
      <option value="1">1 jour</option>
      <option value="2">2 jours</option>
      <option value="3">3 jours</option>
      <option value="4">4 jours</option>
      <option value="5">5 jours</option>
      <option value="7">1 semaine</option>
      <option value="14">2 semaines</option>
    </select>
    <p style="font-size:12px;color:var(--ink-soft);margin:4px 0 0;">S'ajoute au délai du produit si celui-ci en a un (le plus long des deux s'applique).</p>
    <div class="sheet-actions">
      <button class="btn btn-ghost" onclick="closePickupPointSheet()">Annuler</button>
      <button class="btn btn-oak" onclick="savePickupPoint()">Enregistrer</button>
    </div>
  </div>
</div>

<script>

window.addEventListener('error', function(e){
  var main = document.getElementById('main');
  if(main && main.innerHTML.trim() === ''){
    main.innerHTML = '<div class="empty"><div class="glyph">\u26A0\uFE0F</div>'
      + '<p>Une erreur technique emp\u00eache l\'application de d\u00e9marrer.<br>'
      + 'Merci d\'envoyer une capture de ce message :</p>'
      + '<p style="font-size:11px;color:var(--ink-soft);margin-top:10px;word-break:break-word;">'
      + (e.message || 'erreur inconnue') + ' \u2014 ' + (e.filename||'?') + ':' + (e.lineno||'?')
      + '</p></div>';
  }
});
</script>
<script>

/* ================= Minimal Supabase REST client (no SDK, max browser compatibility) ================= */
var DEFAULT_LOGO_SRC = (document.querySelector('#headerLogo img') || {}).src || '';
var SB_URL = 'https://jtddksiztplykivbsqlb.supabase.co';
var SB_ANON_KEY = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Imp0ZGRrc2l6dHBseWtpdmJzcWxiIiwicm9sZSI6ImFub24iLCJpYXQiOjE3ODk1NzU4MDcsImV4cCI6MjEwNTE1MTgwN30.1MqeXZy9A-8P6xQBnR8pS8pZBGLNdMThAjseNf5C5EM';
var sbSession = null;
function loadStoredSession(){
  try{
    var raw = localStorage.getItem('sb_session');
    if(raw) sbSession = JSON.parse(raw);
  }catch(e){}
}
function saveStoredSession(s){
  sbSession = s;
  try{
    if(s) localStorage.setItem('sb_session', JSON.stringify(s));
    else localStorage.removeItem('sb_session');
  }catch(e){}
}
/* Touche Entrée : valide la saisie et déclenche l'action principale du bloc courant */
function primaryActionFor(input){
  const openSheet = input.closest('.sheet-backdrop.open');
  if(openSheet) return openSheet.querySelector('.sheet-actions .btn-oak');
  if(input.dataset && input.dataset.pid) return document.querySelector('[data-add="' + input.dataset.pid + '"]');
  return document.querySelector('#main .sheet-actions .btn-oak');
}
document.addEventListener('keydown', function(e){
  if(e.key !== 'Enter' || e.defaultPrevented || e.isComposing) return;
  const t = e.target;
  if(!t || t.tagName !== 'INPUT') return;
  const type = (t.getAttribute('type') || 'text').toLowerCase();
  if(['checkbox','radio','button','submit','file','range'].indexOf(type) !== -1) return;
  e.preventDefault();
  const pid = (t.dataset && t.dataset.pid) || null;
  const btn = primaryActionFor(t);
  t.blur();                                    // valide la saisie (onchange) et referme le clavier
  if(!btn) return;
  setTimeout(function(){
    const again = pid ? document.querySelector('[data-add="' + pid + '"]') : (document.contains(btn) ? btn : null);
    if(again && !again.disabled) again.click();
  }, 0);
});
function authHeaders(){
  return {
    'apikey': SB_ANON_KEY,
    'Authorization': 'Bearer ' + ((sbSession && sbSession.access_token) ? sbSession.access_token : SB_ANON_KEY),
    'Content-Type': 'application/json'
  };
}
/* La session Supabase expire au bout d'une heure : on la renouvelle automatiquement (refresh token). */
var refreshPromise = null;
function tokenExp(t){
  try{ var p = String(t).split('.')[1].replace(/-/g,'+').replace(/_/g,'/'); return JSON.parse(atob(p)).exp || 0; }catch(e){ return 0; }
}
async function refreshSession(){
  if(!sbSession || !sbSession.refresh_token) return false;
  if(!refreshPromise){
    var rt = sbSession.refresh_token;
    refreshPromise = (async function(){
      try{
        var res = await fetch(SB_URL + '/auth/v1/token?grant_type=refresh_token', {
          method: 'POST', headers: { apikey: SB_ANON_KEY, 'Content-Type': 'application/json' },
          body: JSON.stringify({ refresh_token: rt })
        });
        if(!res.ok) return false;
        var d = await res.json();
        if(!d || !d.access_token) return false;
        saveStoredSession({ access_token: d.access_token, refresh_token: d.refresh_token || rt, user: d.user || (sbSession && sbSession.user) });
        return true;
      }catch(e){ return false; }
    })();
    refreshPromise.then(function(){ refreshPromise = null; }, function(){ refreshPromise = null; });
  }
  return refreshPromise;
}
async function ensureFreshSession(){
  if(!sbSession || !sbSession.access_token) return;
  var exp = tokenExp(sbSession.access_token);
  if(exp && (exp * 1000 - Date.now()) < 60000) await refreshSession();
}
async function sbFetch(path, options, retried){
  options = options || {};
  try{
    var usesSession = !!(sbSession && sbSession.access_token && options.headers && options.headers.Authorization === 'Bearer ' + sbSession.access_token);
    if(usesSession && path.indexOf('/auth/v1/token') !== 0){
      var before = sbSession.access_token;
      await ensureFreshSession();
      if(sbSession && sbSession.access_token !== before) options.headers.Authorization = 'Bearer ' + sbSession.access_token;
    }
    var res = await fetch(SB_URL + path, options);
    var text = await res.text();
    var data = null;
    if(text){ try{ data = JSON.parse(text); }catch(e){ data = text; } }
    if(!res.ok){
      var message = (data && (data.msg || data.error_description || data.message || data.error)) || res.statusText || ('HTTP ' + res.status);
      if(usesSession && !retried && (res.status === 401 || res.status === 403) && /jwt|expired|token/i.test(String(message))){
        if(await refreshSession()){
          options.headers.Authorization = 'Bearer ' + sbSession.access_token;
          return sbFetch(path, options, true);
        }
      }
      return { data: null, error: { message: message, status: res.status } };
    }
    return { data: data, error: null };
  }catch(networkErr){
    return { data: null, error: { message: 'Erreur réseau : ' + networkErr.message } };
  }
}
async function authSignUp(email, password){
  return sbFetch('/auth/v1/signup', {
    method: 'POST',
    headers: { apikey: SB_ANON_KEY, 'Content-Type': 'application/json' },
    body: JSON.stringify({ email: email, password: password })
  });
}
async function authSignIn(email, password){
  var res = await sbFetch('/auth/v1/token?grant_type=password', {
    method: 'POST',
    headers: { apikey: SB_ANON_KEY, 'Content-Type': 'application/json' },
    body: JSON.stringify({ email: email, password: password })
  });
  if(!res.error && res.data && res.data.access_token){
    saveStoredSession({ access_token: res.data.access_token, refresh_token: res.data.refresh_token, user: res.data.user });
  }
  return res;
}
async function authSignOut(){ saveStoredSession(null); }
async function authRequestPasswordReset(email){
  return sbFetch('/auth/v1/recover', {
    method: 'POST',
    headers: { apikey: SB_ANON_KEY, 'Content-Type': 'application/json' },
    body: JSON.stringify({ email: email })
  });
}
async function authSetNewPassword(recoveryToken, newPassword){
  return sbFetch('/auth/v1/user', {
    method: 'PUT',
    headers: { apikey: SB_ANON_KEY, 'Content-Type': 'application/json', Authorization: 'Bearer ' + recoveryToken },
    body: JSON.stringify({ password: newPassword })
  });
}
async function authGetUser(){
  if(!sbSession || !sbSession.access_token) return null;
  var res = await sbFetch('/auth/v1/user', { headers: { apikey: SB_ANON_KEY, Authorization: 'Bearer ' + sbSession.access_token } });
  if(res.error) return null;
  return res.data;
}
function qs(params){
  return Object.keys(params).map(function(k){ return encodeURIComponent(k) + '=' + encodeURIComponent(params[k]); }).join('&');
}
async function dbSelect(table, params){
  params = params || {};
  var query = qs(params);
  return sbFetch('/rest/v1/' + table + (query ? ('?' + query) : ''), { headers: authHeaders() });
}
async function dbInsert(table, payload, returning){
  var h = authHeaders(); h['Prefer'] = returning ? 'return=representation' : 'return=minimal';
  return sbFetch('/rest/v1/' + table, { method: 'POST', headers: h, body: JSON.stringify(payload) });
}
async function dbRpc(fn, payload){
  return sbFetch('/rest/v1/rpc/' + fn, { method: 'POST', headers: authHeaders(), body: JSON.stringify(payload || {}) });
}
async function dbUpdate(table, match, payload){
  var h = authHeaders(); h['Prefer'] = 'return=representation';
  var query = qs(match);
  return sbFetch('/rest/v1/' + table + '?' + query, { method: 'PATCH', headers: h, body: JSON.stringify(payload) });
}
async function dbDelete(table, match){
  var query = qs(match);
  return sbFetch('/rest/v1/' + table + '?' + query, { method: 'DELETE', headers: authHeaders() });
}

/* ================= Helpers ================= */
async function checkGrammar(textareaId, resultId){
  const textarea = document.getElementById(textareaId);
  const resultEl = document.getElementById(resultId);
  const text = textarea.value.trim();
  if(!text){ resultEl.innerHTML = ''; return; }
  resultEl.innerHTML = '<p style="font-size:12px;color:var(--ink-soft);">Vérification en cours…</p>';
  try{
    const res = await fetch('https://api.languagetool.org/v2/check', {
      method: 'POST',
      headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
      body: 'text=' + encodeURIComponent(text) + '&language=fr'
    });
    const data = await res.json();
    const matches = data.matches || [];
    if(matches.length === 0){
      resultEl.innerHTML = '<p style="font-size:12px;color:var(--forest-700);">✓ Aucune erreur détectée.</p>';
      return;
    }
    resultEl.innerHTML = `<div style="background:var(--cream-2);border:1px solid var(--line);border-radius:10px;padding:10px 12px;">
      <p style="font-size:12px;font-weight:600;margin:0 0 8px;">${matches.length} suggestion${matches.length>1?'s':''} :</p>
      ${matches.slice(0,10).map((m,i)=>{
        const bad = text.substring(m.offset, m.offset+m.length);
        const suggestions = (m.replacements||[]).slice(0,3);
        return `<div style="font-size:12px;margin-bottom:8px;line-height:1.5;">
          <span style="color:var(--danger);text-decoration:underline wavy;">${escapeHtml(bad)}</span> — ${escapeHtml(m.message)}
          ${suggestions.length ? `<div style="margin-top:4px;">${suggestions.map(s=>`<button type="button" class="chip" style="cursor:pointer;border:none;margin-right:4px;" onclick="applyGrammarFix('${textareaId}','${resultId}',${m.offset},${m.length},'${escapeHtml(s.value).replace(/'/g,"\\'")}')">${escapeHtml(s.value)}</button>`).join('')}</div>` : ''}
        </div>`;
      }).join('')}
    </div>`;
  }catch(e){
    resultEl.innerHTML = '<p style="font-size:12px;color:var(--danger);">Vérification indisponible pour le moment.</p>';
  }
}
function applyGrammarFix(textareaId, resultId, offset, length, replacement){
  const textarea = document.getElementById(textareaId);
  const v = textarea.value;
  textarea.value = v.substring(0,offset) + replacement + v.substring(offset+length);
  document.getElementById(resultId).innerHTML = '<p style="font-size:12px;color:var(--ink-soft);">Correction appliquée. Vérifiez à nouveau si besoin.</p>';
}
function escapeHtml(s){ return String(s).replace(/[&<>"']/g, c=>({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c])); }
function val(id){ const el = document.getElementById(id); return el ? el.value : ''; }
function validEmail(email){ return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email); }
function normalizeOption(o){ return (typeof o === 'string') ? {label:o, price:0} : {label:o.label, price:o.price||0}; }
function productImages(p){ return (p.images && p.images.length) ? p.images : (p.image ? [p.image] : []); }
function fmtEuro(n){ return Number(n||0).toFixed(2).replace('.', ','); }
function priceLabel(p){ return `${fmtEuro(p.prix)} €${p.unit && p.unit!=='unité' ? '/'+p.unit : ''}`; }
function parsePrice(str){ const n = parseFloat(String(str).replace(',', '.').trim()); return isNaN(n) ? 0 : n; }
function checkPasswordRules(pw){
  return [
    {key:'len', label:'8 caractères minimum', ok: pw.length>=8},
    {key:'upper', label:'Une majuscule', ok:/[A-Z]/.test(pw)},
    {key:'lower', label:'Une minuscule', ok:/[a-z]/.test(pw)},
    {key:'special', label:'Un caractère spécial (ex. ! ? # @)', ok:/[^A-Za-z0-9]/.test(pw)}
  ];
}
function resizeImageFile(file, maxDim, quality){
  maxDim = maxDim || 900; quality = quality || 0.72;
  return new Promise(function(resolve, reject){
    const reader = new FileReader();
    reader.onload = function(e){
      const img = new Image();
      img.onload = function(){
        let w = img.width, h = img.height;
        if(w > h && w > maxDim){ h = Math.round(h*maxDim/w); w = maxDim; }
        else if(h >= w && h > maxDim){ w = Math.round(w*maxDim/h); h = maxDim; }
        const canvas = document.createElement('canvas');
        canvas.width = w; canvas.height = h;
        canvas.getContext('2d').drawImage(img, 0, 0, w, h);
        resolve(canvas.toDataURL('image/jpeg', quality));
      };
      img.onerror = reject;
      img.src = e.target.result;
    };
    reader.onerror = reject;
    reader.readAsDataURL(file);
  });
}

/* ================= State ================= */
let screen = 'directory'; // directory | auth | client | pro | admin
let authMode = 'login'; // login | signup | checkEmail
let clientReturnScreen = 'directory';
let currentDataId = null; // shop id (uuid)
let currentUser = null;   // supabase auth user
let currentRole = null;   // 'artisan' | 'admin'

let shop = {name:'',logo:null,colors:{},hours:{},categories:[],tags:[]};
let categories = [];
let tags = [];
let products = [];
let orders = [];
let specialRequests = [];
let cart = [];
let lastOrder = null;

function hexToRgba(hex, alpha){
  hex = String(hex||'#211b12').replace('#','');
  if(hex.length===3) hex = hex.split('').map(c=>c+c).join('');
  const r = parseInt(hex.substring(0,2),16)||33;
  const g = parseInt(hex.substring(2,4),16)||27;
  const b = parseInt(hex.substring(4,6),16)||18;
  return `rgba(${r},${g},${b},${alpha})`;
}
function applyColors(){
  const c = shop.colors || {};
  document.documentElement.style.setProperty('--cream', c.fond || '#f6f1e6');
  document.documentElement.style.setProperty('--forest-800', c.bandeau || '#15301f');
  document.documentElement.style.setProperty('--oak-500', c.icone || '#c98d4a');
  const ink = c.texte || '#211b12';
  document.documentElement.style.setProperty('--ink', ink);
  document.documentElement.style.setProperty('--ink-soft', hexToRgba(ink, 0.72));
}
function applyDefaultColors(){
  document.documentElement.style.setProperty('--cream', '#f6f1e6');
  document.documentElement.style.setProperty('--forest-800', '#15301f');
  document.documentElement.style.setProperty('--oak-500', '#c98d4a');
  document.documentElement.style.setProperty('--ink', '#211b12');
  document.documentElement.style.setProperty('--ink-soft', '#5a5142');
}

function mapProduct(row){
  return { id:row.id, nom:row.nom, description:row.description||'', prix:Number(row.prix),
    images: row.images||[], categorie: row.categorie, tags: row.tags||[],
    options: row.options||[], conflicts: row.conflicts||[], leadDays: row.lead_days||0, unit: row.unit||'unité',
    maxQty: (row.max_qty===null||row.max_qty===undefined) ? null : Number(row.max_qty),
    stock: (row.stock===null||row.stock===undefined) ? null : Number(row.stock),
    allergenes: Array.isArray(row.allergenes) ? row.allergenes : [],
    poidsG: (row.poids_g===null||row.poids_g===undefined) ? null : Number(row.poids_g),
    portions: (row.portions===null||row.portions===undefined) ? null : Number(row.portions) };
}

async function loadShopFor(shopId){
  currentDataId = shopId;
  const { data: shopRows } = await dbSelect('shops', { id: 'eq.'+shopId, select: '*' });
  const shopRow = shopRows && shopRows[0];
  shop = shopRow || {name:'',logo:null,colors:{},hours:{},categories:[],tags:[]};
  if(!shop.colors) shop.colors = {};
  if(!shop.categories) shop.categories = [];
  if(!shop.tags) shop.tags = [];
  if(!shop.metiers) shop.metiers = [];
  shop.emailReport = shop.email_report || {enabled:false, hour:'08:00'};
  if(!shop.slug && shop.name){
    shop.slug = await ensureUniqueSlug(slugify(shop.name), currentDataId);
    dbUpdate('shops', { id: 'eq.'+currentDataId }, { slug: shop.slug });
  }
  if(!shop.pickupPoints) shop.pickupPoints = shop.pickup_points || [];
  if(!Array.isArray(shop.fermetures)) shop.fermetures = [];
  if(shop.adresse && !shop.addr_city){
    geocodeAddress(shop.adresse).then(function(geo){
      if(!geo || !geo.city) return;
      shop.addr_street = geo.street; shop.addr_postcode = geo.postcode; shop.addr_city = geo.city;
      if(shop.lat == null){ shop.lat = geo.lat; shop.lng = geo.lng; }
      dbUpdate('shops', { id: 'eq.'+shopId }, { addr_street: geo.street, addr_postcode: geo.postcode,
                                               addr_city: geo.city, lat: shop.lat, lng: shop.lng });
    });
  }
  categories = shop.categories;
  tags = shop.tags;
  const { data: prodRows } = await dbSelect('products', { shop_id: 'eq.'+shopId, select: '*', order: 'created_at.asc' });
  products = (prodRows||[]).map(mapProduct);
  const { data: orderRows } = await dbSelect('orders', { shop_id: 'eq.'+shopId, select: '*', order: 'created_at.desc' });
  orders = orderRows || [];
  const { data: srRows } = await dbSelect('special_requests', { shop_id: 'eq.'+shopId, select: '*', order: 'created_at.desc' });
  specialRequests = srRows || [];
  await loadBilling();
  applyColors();
}
async function saveShop(){
  const { error } = await dbUpdate('shops', { id: 'eq.'+currentDataId }, {
    name: shop.name, logo: shop.logo, adresse: shop.adresse, telephone: shop.telephone,
    colors: shop.colors, hours: shop.hours, categories: shop.categories, tags: shop.tags,
    email_report: shop.emailReport, description: shop.description, metiers: shop.metiers, slug: shop.slug,
    pickup_points: shop.pickupPoints, lat: shop.lat, lng: shop.lng, order_cutoff: shop.order_cutoff || null,
    fermetures: shop.fermetures || [],
    addr_street: shop.addr_street, addr_postcode: shop.addr_postcode, addr_city: shop.addr_city
  });
  return !error;
}

/* ================= Boot / session ================= */
let recoveryToken = null;
function checkRecoveryLink(){
  const hash = window.location.hash || '';
  if(hash.indexOf('type=recovery') !== -1){
    const params = new URLSearchParams(hash.replace(/^#/, ''));
    const token = params.get('access_token');
    if(token){
      recoveryToken = token;
      history.replaceState(null, '', window.location.pathname + window.location.search);
      return true;
    }
  }
  return false;
}
async function geocodeAddress(q){
  if(!q || !q.trim()) return null;
  try{
    const res = await fetch('https://api-adresse.data.gouv.fr/search/?q=' + encodeURIComponent(q) + '&limit=1');
    const data = await res.json();
    const f = data.features && data.features[0];
    if(f) return { lat: f.geometry.coordinates[1], lng: f.geometry.coordinates[0], label: f.properties.label,
                   street: f.properties.name || '', postcode: f.properties.postcode || '', city: f.properties.city || '' };
  }catch(e){}
  return null;
}
function haversineKm(lat1,lon1,lat2,lon2){
  const R = 6371;
  const dLat = (lat2-lat1)*Math.PI/180;
  const dLon = (lon2-lon1)*Math.PI/180;
  const a = Math.sin(dLat/2)**2 + Math.cos(lat1*Math.PI/180)*Math.cos(lat2*Math.PI/180)*Math.sin(dLon/2)**2;
  return R*2*Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
}
function callPaiements(payload, withAuth){
  const go = async function(){
    const headers = { apikey: SB_ANON_KEY, 'Content-Type': 'application/json' };
    if(withAuth){ await ensureFreshSession(); headers.Authorization = 'Bearer ' + (sbSession && sbSession.access_token); }
    const res = await fetch(SB_URL + '/functions/v1/paiements', { method: 'POST', headers, body: JSON.stringify(payload) });
    const data = await res.json().catch(function(){ return {}; });
    return { ok: res.ok && data.ok !== false, status: res.status, data };
  };
  return go().catch(function(){ return { ok: false, status: 0, data: { error: 'Erreur réseau.' } }; });
}
function paymentsActive(){ return !!(shop && shop.stripe_charges_enabled); }
function previewDeposit(total){   /* même règle que le serveur */
  if(!(total >= 1)) return null;
  const fixed = (shop && shop.deposit_mode) === 'fixed';
  const v = Number(shop && shop.deposit_value);
  let d = fixed ? (v || 0) : total * ((v > 0 ? v : 30)) / 100;
  if(!(d > 0)) d = total * 0.3;
  d = Math.round(d * 100) / 100;
  return Math.max(0.5, Math.min(d, total));
}
const ALLERGENES = [
  ['gluten','Gluten'], ['crustaces','Crustacés'], ['oeufs','Œufs'], ['poissons','Poissons'],
  ['arachides','Arachides'], ['soja','Soja'], ['lait','Lait'], ['fruits_a_coque','Fruits à coque'],
  ['celeri','Céleri'], ['moutarde','Moutarde'], ['sesame','Sésame'], ['sulfites','Sulfites'],
  ['lupin','Lupin'], ['mollusques','Mollusques']
];
function allergeneLabel(code){ const a = ALLERGENES.find(x=>x[0]===code); return a ? a[1] : code; }
const ICON_EYE = '<svg viewBox="0 0 16 16" width="14" height="14" fill="currentColor" aria-hidden="true"><path d="M8 3C4.4 3 1.5 7.3 1.5 8S4.4 13 8 13s6.5-4.3 6.5-5S11.6 3 8 3Zm0 8a3 3 0 1 1 0-6 3 3 0 0 1 0 6Zm0-4.5a1.5 1.5 0 1 0 0 3 1.5 1.5 0 0 0 0-3Z"/></svg>';
const ICON_EYE_OFF = '<svg viewBox="0 0 16 16" width="14" height="14" fill="currentColor" aria-hidden="true"><path d="M2.2 1.5 1.5 2.2l2.1 2.1C2.2 5.5 1.5 7.4 1.5 8c0 .7 2.9 5 6.5 5 1.3 0 2.5-.6 3.4-1.2l2.4 2.4.7-.7L2.2 1.5Zm5.8 9.6c-2.6 0-4.8-2.6-5.3-3.1.3-.4 1.1-1.3 2.2-2l1.4 1.4a3 3 0 0 0 4 4l1 1c-.7.4-1.5.7-2.3.7ZM8 3c-.8 0-1.5.2-2.2.4l1 1A4 4 0 0 1 8 4.2c2.6 0 4.8 2.6 5.3 3.1-.2.3-.7.9-1.5 1.5l.9.9c1.2-1 1.8-2.1 1.8-2.4C14.5 6.7 11.6 3 8 3Z"/></svg>';
const ICON_PIN = '<svg viewBox="0 0 16 16" width="13" height="13" fill="currentColor" aria-hidden="true" style="vertical-align:-1px;margin-right:5px;"><path d="M8 0a5 5 0 0 0-5 5c0 3.6 4.4 10.4 4.6 10.7a.5.5 0 0 0 .8 0C8.6 15.4 13 8.6 13 5a5 5 0 0 0-5-5Zm0 7a2 2 0 1 1 0-4 2 2 0 0 1 0 4Z"/></svg>';
const ICON_CHEVRON = '<svg viewBox="0 0 16 16" width="15" height="15" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m6 3 5 5-5 5"/></svg>';
/* ================= Comptoir (personnel de vente) ================= */
let comptoirSession = (function(){
  try{ return JSON.parse(sessionStorage.getItem('lpda_comptoir') || 'null'); }catch(e){ return null; }
})();
let comptoirData = null, comptoirError = '', comptoirFilter = '', comptoirTimer = null;
function comptoirSave(sess){
  comptoirSession = sess;
  try{
    if(sess) sessionStorage.setItem('lpda_comptoir', JSON.stringify(sess));
    else sessionStorage.removeItem('lpda_comptoir');
  }catch(e){}
}
async function comptoirLogin(){
  const slug = val('cpSlug').trim();
  const pin = val('cpPin').trim();
  if(!slug){ comptoirError = 'Indiquez le nom de la boutique.'; render(); return; }
  if(!pin && !currentUser){ comptoirError = 'Saisissez le code comptoir.'; render(); return; }
  const { data, error } = await dbRpc('comptoir_orders', { p_slug: slug, p_pin: pin });
  if(error || !data){ comptoirError = 'Boutique ou code incorrect.'; render(); return; }
  comptoirError = ''; comptoirData = data;
  comptoirSave({ slug: (data.slug || slug), pin: pin, artisan: (!pin && !!currentUser) });
  render();
}
async function comptoirLogout(){
  const etaitArtisan = comptoirSession && comptoirSession.artisan;
  comptoirSave(null); comptoirData = null; comptoirFilter = '';
  if(comptoirTimer){ clearInterval(comptoirTimer); comptoirTimer = null; }
  if(etaitArtisan && currentUser && currentDataId){
    screen = 'pro'; proTab = 'commandes';
    await loadShopFor(currentDataId);   // les remises faites au comptoir doivent apparaître
  }
  render();
}
async function openComptoirAsArtisan(){
  if(!shop.slug){ alert("Enregistrez d'abord le nom de votre boutique."); return; }
  comptoirSave({ slug: shop.slug, pin: '', artisan: true });
  comptoirData = null; comptoirFilter = ''; comptoirError = '';
  await comptoirRefresh(true);
  screen = 'comptoir';
  render();
}
async function comptoirRefresh(silent){
  if(!comptoirSession) return;
  const { data, error } = await dbRpc('comptoir_orders', { p_slug: comptoirSession.slug, p_pin: comptoirSession.pin });
  if(error){ if(!silent){ comptoirError = 'Session expirée, reconnectez-vous.'; comptoirSave(null); render(); } return; }
  comptoirData = data;
  if(!silent) render();
}
async function comptoirMark(code){
  if(!comptoirSession) return;
  const { error } = await dbRpc('comptoir_mark', { p_slug: comptoirSession.slug, p_pin: comptoirSession.pin, p_code: code });
  if(error){ alert(error.message); return; }
  await comptoirRefresh();
}
function comptoirSearch(v){ comptoirFilter = v; renderComptoirList(); }
function renderComptoir(main){
  let fab = document.getElementById('fabWrap'); if(fab) fab.style.display='none';
  if(!comptoirSession){
    main.innerHTML = `<div class="centered">
      <h1 class="page-title">Comptoir</h1>
      <p class="page-sub">Accès réservé au personnel de vente : les commandes du jour, rien d'autre.</p>
      <label class="field-label">Nom de la boutique</label>
      <input type="text" id="cpSlug" placeholder="Ex. Boulangerie du Village">
      <p style="font-size:12px;color:var(--ink-soft);margin:4px 0 0;">Le nom tel qu'il apparaît en haut de votre boutique. Accents et majuscules sans importance.</p>
      <label class="field-label">Code comptoir${currentUser ? ' (inutile pour votre propre boutique)' : ''}</label>
      <input type="password" inputmode="numeric" id="cpPin" placeholder="4 chiffres">
      <div class="auth-error">${escapeHtml(comptoirError)}</div>
      <div class="sheet-actions"><button class="btn btn-oak btn-block" onclick="comptoirLogin()">Ouvrir le comptoir</button></div>
      <p class="pro-link" onclick="screen='directory';render();">← Retour aux boutiques</p>
    </div>`;
    return;
  }
  main.innerHTML = `<div class="centered">
    <div class="prod-head">
      <div><strong>${escapeHtml((comptoirData && comptoirData.shop) || comptoirSession.slug)}</strong><br>
      <span style="color:var(--ink-soft);font-size:12.5px;">Commandes à retirer aujourd'hui${comptoirSession.artisan ? ' · vue artisan' : ''}</span></div>
      <button class="icon-btn" onclick="comptoirLogout()">${comptoirSession.artisan ? 'Mon espace' : 'Quitter'}</button>
    </div>
    <input type="text" id="cpSearch" placeholder="Chercher un code de retrait…" value="${escapeHtml(comptoirFilter)}" oninput="comptoirSearch(this.value)" style="margin-top:14px;" autocapitalize="characters">
    <div id="cpList"></div>
  </div>`;
  renderComptoirList();
  if(!comptoirTimer){
    comptoirTimer = setInterval(function(){ if(screen==='comptoir') comptoirRefresh(true).then(renderComptoirList); }, 60000);
  }
}
function renderComptoirList(){
  const el = document.getElementById('cpList');
  if(!el) return;
  const all = (comptoirData && comptoirData.orders) || [];
  const q = comptoirFilter.trim().toUpperCase();
  const list = q ? all.filter(o => (o.code||'').toUpperCase().includes(q)) : all;
  const attente = list.filter(o => o.status === 'nouvelle');
  const faites = list.filter(o => o.status === 'recuperee');
  if(list.length === 0){
    el.innerHTML = `<div class="empty"><p>${q ? 'Aucune commande avec ce code.' : 'Aucune commande à retirer aujourd\'hui.'}</p></div>`;
    return;
  }
  function ligne(o, done){
    const arts = (o.items||[]).map(it=>`${it.qty} × ${escapeHtml(it.nom)}`).join(', ');
    return `<div class="product-row" style="align-items:flex-start;${done?'opacity:.5;':''}">
      <div class="thumb" style="font-weight:700;font-size:13px;letter-spacing:.5px;">${escapeHtml(o.code)}</div>
      <div class="row-body">
        <div class="row-top"><span class="row-name">${escapeHtml(o.slot||'')}</span><span class="row-price">${fmtEuro(o.total)} €</span></div>
        <div class="row-cat">${arts}</div>
        ${o.pickup_location?`<div class="row-opts">${escapeHtml(o.pickup_location)}</div>`:''}
        ${(o.deposit_status === 'payee' || o.deposit_status === 'litige') ? `<div class="row-badges"><span class="chip dep dep-ok">Acompte payé — reste ${fmtEuro(Number(o.total) - Number(o.deposit_amount || 0))} € à encaisser</span></div>`
          : ((o.deposit_status === 'lien_envoye' || o.deposit_status === 'expiree' || (o.deposit_required && o.deposit_status === 'aucun')) ? `<div class="row-badges"><span class="chip dep dep-late">Acompte non réglé</span></div>` : '')}
        <div class="row-actions">${done ? `<span class="chip">✓ Récupérée</span>` : `<button class="icon-btn" onclick="comptoirMark('${o.code}')">Marquer récupérée</button>`}</div>
      </div>
    </div>`;
  }
  let html = '';
  if(attente.length) html += `<h2 class="list-title">À remettre (${attente.length})</h2><div class="grid-wrap rows">${attente.map(o=>ligne(o,false)).join('')}</div>`;
  if(faites.length) html += `<h2 class="list-title">Déjà récupérées (${faites.length})</h2><div class="grid-wrap rows">${faites.map(o=>ligne(o,true)).join('')}</div>`;
  el.innerHTML = html;
}
let hideImages = (function(){ try{ return localStorage.getItem('lpda_hide_images') === '1'; }catch(e){ return false; } })();
/* Dans l'application Android récente, le contenu s'affiche sous les barres système.
   Si le système ne transmet pas les marges de sécurité, on les compense nous-mêmes. */
function checkSystemInsets(){
  try{
    const ua = navigator.userAgent || '';
    const inApp = /;\s*wv\)/.test(ua) || !!window.Capacitor;
    if(!inApp) return;
    const probe = document.createElement('div');
    probe.style.cssText = 'position:fixed;top:0;left:0;width:1px;height:env(safe-area-inset-top,0px);pointer-events:none;opacity:0;';
    document.body.appendChild(probe);
    const top = probe.getBoundingClientRect().height;
    probe.remove();
    if(top < 1) document.documentElement.classList.add('sys-inset');
  }catch(e){}
}
function applyImagePreference(){
  const app = document.querySelector('.app');
  if(app) app.setAttribute('data-noimg', hideImages ? '1' : '0');
}
function toggleImages(){
  hideImages = !hideImages;
  try{ localStorage.setItem('lpda_hide_images', hideImages ? '1' : '0'); }catch(e){}
  applyImagePreference();
  render();
}
function imagesToggleBtn(){
  return `<button type="button" class="img-toggle" onclick="toggleImages()">${hideImages ? ICON_EYE : ICON_EYE_OFF}${hideImages ? 'Afficher les photos' : 'Masquer les photos'}</button>`;
}
function cityOf(adresse){
  const m = /(\d{5})\s+(.+)$/.exec(String(adresse||'').trim());
  return m ? m[2].trim() : '';
}
function slugify(str){
  return String(str||'').toLowerCase()
    .normalize('NFD').replace(/[\u0300-\u036f]/g,'')
    .replace(/[^a-z0-9]+/g,'-')
    .replace(/^-+|-+$/g,'')
    .slice(0,60) || 'boutique';
}
async function ensureUniqueSlug(base, excludeId){
  if(RESERVED_SLUGS.indexOf(base) !== -1) base = base + '-boutique';
  let slug = base, i = 2;
  while(true){
    const { data } = await dbSelect('shops', { slug: 'eq.'+slug, select: 'id' });
    const conflict = (data||[]).find(s=>s.id !== excludeId);
    if(!conflict) return slug;
    slug = base + '-' + i;
    i++;
  }
}
/* ================= Adresses distinctes par page (routage) ================= */
const ROUTING_ENABLED = /(^|\.)laplacedesartisans\.fr$|\.netlify\.app$|^localhost$|^127\.0\.0\.1$/.test(window.location.hostname);
const SITE_ORIGIN = 'https://laplacedesartisans.fr';
const LEGAL_PATH_TO_TAB = { 'mentions-legales':'mentions', 'cgu':'cgu', 'confidentialite':'confidentialite', 'suppression-compte':'suppression-compte', 'suppression-donnees':'suppression-donnees' };
const LEGAL_TAB_TO_PATH = { 'mentions':'mentions-legales', 'cgu':'cgu', 'confidentialite':'confidentialite', 'suppression-compte':'suppression-compte', 'suppression-donnees':'suppression-donnees' };
const LEGAL_TITLES = { 'mentions':'Mentions légales', 'cgu':"Conditions générales d'utilisation", 'confidentialite':'Politique de confidentialité', 'suppression-compte':'Suppression de compte', 'suppression-donnees':'Suppression de données' };
const AUTH_PATH_TO_MODE = { 'connexion':'login', 'inscription':'signup', 'mot-de-passe-oublie':'forgot' };
const RESERVED_SLUGS = ['connexion','inscription','mot-de-passe-oublie','nouveau-mot-de-passe','espace-pro','admin','devis','comptoir','mentions-legales','cgu','confidentialite','suppression-compte','suppression-donnees','panier','commande','apercu','index-html','sitemap-xml','robots-txt','manifest-json','og-image-jpg'];
const DEFAULT_TITLE = 'La Place des Artisans — Click & collect chez vos artisans locaux';
const DEFAULT_DESC = 'Commandez en ligne chez les boulangers, bouchers, fromagers et artisans de votre région, puis récupérez vos produits sur place.';
let urlSyncReplace = false;
let pendingRoute = null;
let cartIsOpen = false;
let pendingAuthNotice = '';
let myShopId = null;

function currentPath(){
  let p = window.location.pathname || '/';
  try{ p = decodeURIComponent(p); }catch(e){}
  p = p.replace(/\/+$/, '');
  return p || '/';
}
function proBasePath(){ return adminManaging ? ('/admin/boutique/' + currentDataId) : '/espace-pro'; }
function pathForState(){
  if(screen==='auth'){
    if(authMode==='signup' || authMode==='checkEmail') return '/inscription';
    if(authMode==='forgot' || authMode==='forgotSent') return '/mot-de-passe-oublie';
    if(authMode==='resetPassword') return '/nouveau-mot-de-passe';
    return '/connexion';
  }
  if(screen==='legal') return '/' + (LEGAL_TAB_TO_PATH[legalTab] || 'mentions-legales');
  if(screen==='admin') return '/admin';
  if(screen==='pro') return proBasePath() + (proTab==='commandes' ? '/commandes' : (proTab==='production' ? '/production' : (proTab==='devis' ? '/devis' : (proTab==='infos' ? '/infos' : ''))));
  if(screen==='client'){
    if(clientReturnScreen==='pro') return proBasePath() + '/apercu';
    return (shop && shop.slug) ? ('/' + shop.slug + (cartIsOpen ? '/panier' : '')) : '/';
  }
  if(screen==='confirm') return (shop && shop.slug && lastOrder) ? ('/' + shop.slug + '/commande/' + lastOrder.code) : '/';
  if(screen==='devis') return currentDevis ? ('/devis/' + currentDevis.code) : (devisNotFound ? ('/devis/' + devisNotFound) : '/devis');
  if(screen==='comptoir') return '/comptoir';
  return '/';
}
function syncUrl(){
  if(!ROUTING_ENABLED){ urlSyncReplace = false; return; }
  const target = pathForState();
  const replace = urlSyncReplace; urlSyncReplace = false;
  if(target === currentPath() && !window.location.hash) return;
  try{
    const state = { app:1, cart: cartIsOpen ? 1 : 0 };
    if(replace) history.replaceState(state, '', target);
    else { history.pushState(state, '', target); if(!cartIsOpen) window.scrollTo(0, 0); }
  }catch(e){}
}
function setMeta(id, attr, value){ const el = document.getElementById(id); if(el) el.setAttribute(attr, value); }
function jsonLdForShop(url){
  const ld = { '@context':'https://schema.org', '@type':'LocalBusiness', 'name': shop.name, 'url': url };
  if(shop.description) ld.description = String(shop.description).slice(0, 300);
  if(shop.telephone) ld.telephone = shop.telephone;
  if(shop.adresse){
    if(shop.addr_city && shop.addr_postcode){
      ld.address = { '@type':'PostalAddress', 'streetAddress': shop.addr_street || shop.adresse,
                     'postalCode': shop.addr_postcode, 'addressLocality': shop.addr_city, 'addressCountry':'FR' };
    } else {
      const m = /^(.*?)[,\s]*(\d{5})\s+(.+)$/.exec(String(shop.adresse).trim());
      ld.address = m ? { '@type':'PostalAddress', 'streetAddress': m[1].trim(), 'postalCode': m[2], 'addressLocality': m[3].trim(), 'addressCountry':'FR' }
                     : { '@type':'PostalAddress', 'streetAddress': shop.adresse, 'addressCountry':'FR' };
    }
  }
  if(shop.lat != null && shop.lng != null) ld.geo = { '@type':'GeoCoordinates', 'latitude': Number(shop.lat), 'longitude': Number(shop.lng) };
  const dayNames = { lun:'Monday', mar:'Tuesday', mer:'Wednesday', jeu:'Thursday', ven:'Friday', sam:'Saturday', dim:'Sunday' };
  const specs = [];
  Object.keys(dayNames).forEach(function(k){
    const h = normalizeDayHours((shop.hours||{})[k]);
    if(h.ouvert) h.ranges.forEach(function(r){ specs.push({ '@type':'OpeningHoursSpecification', 'dayOfWeek':'https://schema.org/' + dayNames[k], 'opens': r.de, 'closes': r.a }); });
  });
  if(specs.length) ld.openingHoursSpecification = specs;
  return ld;
}
function updateHeadForState(){
  let title = DEFAULT_TITLE, desc = DEFAULT_DESC, index = true, ld = null;
  const path = pathForState();
  const url = SITE_ORIGIN + (path === '/' ? '/' : path);
  if(screen==='client' && clientReturnScreen!=='pro' && shop && shop.name){
    title = shop.name + ' — Commande en click & collect | La Place des Artisans';
    const d = String(shop.description || '').trim();
    desc = d ? (d.length > 155 ? d.slice(0, 152) + '…' : d) : ('Commandez en ligne chez ' + shop.name + (shop.adresse ? (', ' + shop.adresse) : '') + ', puis récupérez votre commande sur place.');
    ld = jsonLdForShop(SITE_ORIGIN + '/' + shop.slug);
  } else if(screen==='client'){ title = 'Aperçu de ma boutique — La Place des Artisans'; index = false; }
  else if(screen==='legal'){ title = (LEGAL_TITLES[legalTab] || 'Informations légales') + ' — La Place des Artisans'; }
  else if(screen==='auth'){
    title = ({ signup:'Créer un compte artisan', checkEmail:'Créer un compte artisan', forgot:'Mot de passe oublié', forgotSent:'Mot de passe oublié', resetPassword:'Nouveau mot de passe' }[authMode] || 'Connexion artisan') + ' — La Place des Artisans';
    index = false;
  }
  else if(screen==='pro'){ title = 'Espace pro — La Place des Artisans'; index = false; }
  else if(screen==='admin'){ title = 'Administration — La Place des Artisans'; index = false; }
  else if(screen==='confirm'){ title = 'Commande enregistrée — La Place des Artisans'; index = false; }
  else if(screen==='devis'){ title = 'Votre devis — La Place des Artisans'; index = false; }
  else if(screen==='comptoir'){ title = 'Comptoir — La Place des Artisans'; index = false; }
  else { ld = { '@context':'https://schema.org', '@type':'WebSite', 'name':'La Place des Artisans', 'url': SITE_ORIGIN + '/' }; }
  if(cartIsOpen) index = false;
  document.title = title;
  setMeta('metaDescription', 'content', desc);
  setMeta('ogTitle', 'content', title);
  setMeta('ogDescription', 'content', desc);
  setMeta('metaRobots', 'content', index ? 'index, follow' : 'noindex, nofollow');
  setMeta('canonicalLink', 'href', url);
  setMeta('ogUrl', 'content', url);
  let ldEl = document.getElementById('ldJson');
  if(ld){
    if(!ldEl){ ldEl = document.createElement('script'); ldEl.type = 'application/ld+json'; ldEl.id = 'ldJson'; document.head.appendChild(ldEl); }
    ldEl.textContent = JSON.stringify(ld);
  } else if(ldEl){ ldEl.remove(); }
}
function showToast(msg){
  const t = document.createElement('div');
  t.className = 'toast'; t.setAttribute('role','status'); t.textContent = msg;
  document.body.appendChild(t);
  setTimeout(function(){ t.remove(); }, 4000);
}
function newCaptcha(){ captchaA = 1+Math.floor(Math.random()*8); captchaB = 1+Math.floor(Math.random()*8); }
function navLegal(e, tab){ if(e && (e.ctrlKey || e.metaKey || e.shiftKey)) return true; if(e) e.preventDefault(); goLegal(tab); return false; }
function navShop(e, id){ if(e && (e.ctrlKey || e.metaKey || e.shiftKey)) return true; if(e) e.preventDefault(); openShop(id); return false; }
function onBrandClick(e){
  if(e && (e.ctrlKey || e.metaKey || e.shiftKey)) return true;
  if(e) e.preventDefault();
  if(screen==='pro' || screen==='admin') return false;
  closeCartSilently();
  screen = 'directory'; render();
  return false;
}
async function onLoginButton(){ if(currentUser){ await goToMySpace(); } else { goAuth('login'); } }

/* Liens envoyés par e-mail (confirmation de compte, erreurs) et anciens liens « #confidentialite » */
function checkSignupConfirmLink(){
  const hash = window.location.hash || '';
  if(hash.indexOf('access_token=') === -1 || hash.indexOf('type=recovery') !== -1) return false;
  const params = new URLSearchParams(hash.replace(/^#/, ''));
  const at = params.get('access_token');
  if(!at) return false;
  saveStoredSession({ access_token: at, refresh_token: params.get('refresh_token') });
  try{ history.replaceState(null, '', window.location.pathname + window.location.search); }catch(e){}
  return true;
}
function checkAuthErrorLink(){
  const hash = window.location.hash || '';
  if(hash.indexOf('error_description=') === -1 && hash.indexOf('error_code=') === -1) return '';
  const params = new URLSearchParams(hash.replace(/^#/, ''));
  const code = params.get('error_code') || '';
  try{ history.replaceState(null, '', window.location.pathname + window.location.search); }catch(e){}
  return code === 'otp_expired'
    ? "Ce lien a expiré ou a déjà été utilisé. Connectez-vous, ou refaites une demande."
    : "Ce lien n'est plus valide. Connectez-vous, ou refaites une demande.";
}
function legacyLegalTabFromHash(){
  const hash = (window.location.hash || '').replace(/^#/, '');
  return LEGAL_TAB_TO_PATH[hash] ? hash : '';
}

/* Récapitulatif de commande conservé le temps de la session (rechargement de la page de confirmation) */
function saveLastOrder(){
  try{ sessionStorage.setItem('lpda_last_order', JSON.stringify(Object.assign({}, lastOrder, { shopSlug: shop.slug }))); }catch(e){}
}
function loadSavedOrder(code){
  try{
    const o = JSON.parse(sessionStorage.getItem('lpda_last_order') || 'null');
    if(o && o.code === code && o.shopSlug === shop.slug) return o;
  }catch(e){}
  return null;
}

/* Session et espace personnel */
async function loadCurrentUser(user){
  currentUser = user;
  const { data: profileRows } = await dbSelect('profiles', { id: 'eq.'+user.id, select: 'role' });
  const profile = profileRows && profileRows[0];
  currentRole = profile ? profile.role : 'artisan';
  myShopId = null;
}
async function goToMySpace(){
  cart = []; closeCartSilently();
  clientReturnScreen = 'directory';
  if(currentRole === 'admin'){
    if(adminManaging){ stopOrderPolling(); adminManaging = false; currentDataId = null; }
    screen = 'admin'; render(); return;
  }
  if(!myShopId){
    const { data } = await dbSelect('shops', { owner_id: 'eq.'+currentUser.id, select: 'id' });
    myShopId = (data && data[0]) ? data[0].id : null;
  }
  if(myShopId && currentDataId !== myShopId) await loadShopFor(myShopId);
  proTab = 'produits';
  screen = 'pro';
  startOrderPolling();
  render();
}
function applyProSub(sub){
  if(sub === 'apercu'){ clientReturnScreen = 'pro'; screen = 'client'; render(); return; }
  clientReturnScreen = 'directory';
  proTab = (sub === 'commandes' || sub === 'infos' || sub === 'production' || sub === 'devis') ? sub : 'produits';
  screen = 'pro';
  render();
}
async function applyPrivateRoute(parts){
  if(currentRole === 'admin'){
    if(parts[0] === 'admin' && parts[1] === 'boutique' && parts[2]){
      const id = parts[2];
      if(!(adminManaging && currentDataId === id)){
        adminManaging = true;
        document.getElementById('main').innerHTML = `<p class="page-sub">Chargement…</p>`;
        await loadShopFor(id);
        startOrderPolling();
      }
      applyProSub(parts[3]);
      return;
    }
    if(adminManaging){ stopOrderPolling(); adminManaging = false; currentDataId = null; }
    clientReturnScreen = 'directory';
    screen = 'admin'; render(); return;
  }
  if(parts[0] !== 'espace-pro'){ await goToMySpace(); return; }
  if(!myShopId){
    const { data } = await dbSelect('shops', { owner_id: 'eq.'+currentUser.id, select: 'id' });
    myShopId = (data && data[0]) ? data[0].id : null;
  }
  if(myShopId && currentDataId !== myShopId) await loadShopFor(myShopId);
  if(!orderPollInterval) startOrderPolling();
  applyProSub(parts[1]);
}
async function findPublishedShopBySlug(slug){
  const { data, error } = await dbSelect('shops', { slug: 'eq.'+slug, published: 'eq.true', select: 'id' });
  if(error) throw new Error(error.message);
  return (data && data[0]) ? data[0].id : null;
}
async function routeFromUrl(){
  urlSyncReplace = true;
  try{
    const parts = currentPath().split('/').filter(Boolean);
    const first = parts[0] || '';
    if(!first || first === 'index.html'){ closeCartSilently(); screen = 'directory'; render(); return; }
    if(parts.length === 1 && LEGAL_PATH_TO_TAB[first]){ closeCartSilently(); legalTab = LEGAL_PATH_TO_TAB[first]; screen = 'legal'; render(); return; }
    if(parts.length === 1 && AUTH_PATH_TO_MODE[first]){
      closeCartSilently();
      if(currentUser){ await goToMySpace(); return; }
      authMode = AUTH_PATH_TO_MODE[first];
      if(authMode === 'signup') newCaptcha();
      screen = 'auth'; render(); return;
    }
    if(first === 'comptoir'){
      closeCartSilently();
      if(comptoirSession) await comptoirRefresh(true);
      screen = 'comptoir'; render(); return;
    }
    if(first === 'devis'){
      closeCartSilently();
      if(parts[1]){ await openDevis(parts[1], true); return; }
      currentDevis = null; devisNotFound = ''; screen = 'devis'; render(); return;
    }
    if(first === 'nouveau-mot-de-passe'){ closeCartSilently(); authMode = recoveryToken ? 'resetPassword' : 'login'; screen = 'auth'; render(); return; }
    if(first === 'espace-pro' || first === 'admin'){
      closeCartSilently();
      if(!currentUser){ pendingRoute = currentPath(); authMode = 'login'; screen = 'auth'; render(); return; }
      await applyPrivateRoute(parts);
      return;
    }
    // Boutique : /boutique, /boutique/panier, /boutique/commande/CODE
    const slug = first, sub = parts[1] || '';
    const sameShop = (screen === 'client' || screen === 'confirm') && clientReturnScreen !== 'pro' && shop && shop.slug === slug;
    if(sameShop && screen === 'client' && !sub){ closeCartSilently(); updateHeadForState(); return; }
    if(!sameShop){
      const id = await findPublishedShopBySlug(slug);
      if(!id){ closeCartSilently(); screen = 'directory'; render(); return; }
      closeCartSilently();
      cart = []; showPracticalInfo = false; selectedPickupPointId = '';
      document.getElementById('main').innerHTML = `<p class="page-sub">Chargement…</p>`;
      await loadShopFor(id);
    }
    clientReturnScreen = 'directory';
    if(sub === 'commande' && parts[2]){
      const code = String(parts[2]).toUpperCase();
      const tok = new URLSearchParams(window.location.search).get('t') || orderTokenFor(code);
      if(tok){
        const tracked = await loadTrackedOrder(code, tok);
        if(tracked){ lastOrder = tracked; rememberOrder({ code, token: tok, slug: shop.slug, shop: shop.name }); closeCartSilently(); screen = 'confirm'; render(); return; }
      }
      const saved = loadSavedOrder(parts[2]);
      if(saved){ lastOrder = saved; closeCartSilently(); screen = 'confirm'; render(); return; }
    }
    closeCartSilently();
    screen = 'client';
    render();
    if(sub === 'panier' && cart.length > 0) openCart(true);
  } finally {
    urlSyncReplace = false;
  }
}
window.addEventListener('popstate', function(){
  if(!ROUTING_ENABLED) return;
  routeFromUrl().catch(showBootError);
});
async function boot(){
  try{
    checkSystemInsets();
    loadStoredSession();
    const isRecovery = checkRecoveryLink();
    const authError = isRecovery ? '' : checkAuthErrorLink();
    const confirmed = (!isRecovery && !authError) ? checkSignupConfirmLink() : false;
    const legacyLegal = legacyLegalTabFromHash();
    if(sbSession && sbSession.access_token && !isRecovery){
      const user = await authGetUser();
      if(user) await loadCurrentUser(user); else saveStoredSession(null);
    }
    urlSyncReplace = true;
    if(isRecovery){ authMode = 'resetPassword'; screen = 'auth'; render(); return; }
    if(authError){ authMode = 'login'; screen = 'auth'; pendingAuthNotice = authError; render(); return; }
    if(legacyLegal){ legalTab = legacyLegal; screen = 'legal'; render(); return; }
    if(confirmed && currentUser){ await goToMySpace(); showToast('Adresse e-mail confirmée : bienvenue !'); return; }
    const path = currentPath();
    if(!ROUTING_ENABLED || path === '/' || path === '/index.html'){
      if(currentUser){ await goToMySpace(); return; }
      screen = 'directory'; render(); return;
    }
    await routeFromUrl();
  }catch(err){
    showBootError(err);
  }
}
function showBootError(err){
  document.getElementById('main').innerHTML = `
    <div class="empty">
      <div class="glyph">⚠️</div>
      <p>Impossible de contacter le service pour le moment.<br>Vérifiez votre connexion et réessayez.</p>
      <p style="font-size:11px;color:var(--ink-soft);margin-top:10px;">${escapeHtml(err && err.message ? err.message : String(err))}</p>
      <div style="margin-top:16px;"><button class="btn btn-oak" onclick="boot()">Réessayer</button></div>
    </div>`;
}
async function afterLogin(user){
  await loadCurrentUser(user);
  urlSyncReplace = true; // la page de connexion est remplacée dans l'historique
  if(pendingRoute && ROUTING_ENABLED){
    const target = pendingRoute; pendingRoute = null;
    try{ history.replaceState({ app:1 }, '', target); }catch(e){}
    await applyPrivateRoute(target.split('/').filter(Boolean));
    urlSyncReplace = false;
    return;
  }
  await goToMySpace();
  urlSyncReplace = false;
}
async function logout(){
  await authSignOut();
  stopOrderPolling();
  cart = []; closeCartSilently();
  currentUser = null; currentRole = null; currentDataId = null; adminManaging = false; myShopId = null;
  clientReturnScreen = 'directory';
  screen = 'directory';
  render();
}

/* ================= Header / dispatch ================= */
function renderHeader(){
  document.getElementById('gearBtn').style.display = screen==='pro' ? 'flex' : 'none';
  const lb = document.getElementById('loginBtn');
  lb.style.display = screen==='directory' ? 'flex' : 'none';
  lb.setAttribute('aria-label', currentUser ? 'Mon espace artisan' : 'Espace artisan : connexion');
  const showShop = (screen==='pro' || screen==='client' || screen==='confirm');
  document.getElementById('headerName').textContent = (showShop && shop.name) ? shop.name : 'La Place des Artisans';
  document.getElementById('headerLogo').innerHTML = (showShop && shop.logo) ? `<img src="${shop.logo}" alt="">` : '<img alt="" src="' + DEFAULT_LOGO_SRC + '">';
  document.getElementById('headerSub').textContent = screen==='admin' ? 'espace admin' : (screen==='pro' ? 'espace pro' : ((screen==='client'||screen==='confirm') ? 'sur La Place des Artisans' : 'Le local a du talent'));
}
function render(){
  const main = document.getElementById('main');
  const foot = document.getElementById('footNote');
  applyImagePreference();
  syncUrl();
  updateHeadForState();
  renderHeader();
  if(screen==='directory'){ applyDefaultColors(); foot.textContent = "Chaque boutique est gérée en toute indépendance par son artisan."; return renderDirectory(main); }
  if(screen==='auth'){ applyDefaultColors(); foot.textContent = "Vos identifiants restent propres à votre boutique."; renderAuth(main); if(pendingAuthNotice){ showAuthError(pendingAuthNotice); pendingAuthNotice = ''; } return; }
  if(screen==='confirm'){ applyColors(); foot.textContent = ""; return renderConfirmation(); }
  if(screen==='client'){ applyColors(); foot.textContent = "Aperçu de ce que voient vos clients au moment de la commande."; renderClient(main); refreshDevisBadges(); refreshOrderStates(); return; }
  if(screen==='pro'){ applyColors(); foot.textContent = "Catégories, tags, couleurs et options sont personnalisables depuis les paramètres (⚙︎)."; return renderArtisan(main); }
  if(screen==='admin'){ applyDefaultColors(); foot.textContent = "Vue d'ensemble réservée à l'administrateur."; return renderAdminDash(main); }
  if(screen==='comptoir'){ applyDefaultColors(); foot.textContent = "Accès réservé au personnel de vente."; return renderComptoir(main); }
  if(screen==='devis'){ applyDefaultColors(); foot.textContent = "Les échanges de devis restent dans l'application."; return renderDevis(main); }
  if(screen==='legal'){ applyDefaultColors(); foot.textContent = ""; return renderLegal(main); }
}

/* ================= Directory (public landing) ================= */
const FRENCH_DEPARTMENTS = {"01":"Ain","02":"Aisne","03":"Allier","04":"Alpes-de-Haute-Provence","05":"Hautes-Alpes","06":"Alpes-Maritimes","07":"Ardèche","08":"Ardennes","09":"Ariège","10":"Aube","11":"Aude","12":"Aveyron","13":"Bouches-du-Rhône","14":"Calvados","15":"Cantal","16":"Charente","17":"Charente-Maritime","18":"Cher","19":"Corrèze","2A":"Corse-du-Sud","2B":"Haute-Corse","21":"Côte-d'Or","22":"Côtes-d'Armor","23":"Creuse","24":"Dordogne","25":"Doubs","26":"Drôme","27":"Eure","28":"Eure-et-Loir","29":"Finistère","30":"Gard","31":"Haute-Garonne","32":"Gers","33":"Gironde","34":"Hérault","35":"Ille-et-Vilaine","36":"Indre","37":"Indre-et-Loire","38":"Isère","39":"Jura","40":"Landes","41":"Loir-et-Cher","42":"Loire","43":"Haute-Loire","44":"Loire-Atlantique","45":"Loiret","46":"Lot","47":"Lot-et-Garonne","48":"Lozère","49":"Maine-et-Loire","50":"Manche","51":"Marne","52":"Haute-Marne","53":"Mayenne","54":"Meurthe-et-Moselle","55":"Meuse","56":"Morbihan","57":"Moselle","58":"Nièvre","59":"Nord","60":"Oise","61":"Orne","62":"Pas-de-Calais","63":"Puy-de-Dôme","64":"Pyrénées-Atlantiques","65":"Hautes-Pyrénées","66":"Pyrénées-Orientales","67":"Bas-Rhin","68":"Haut-Rhin","69":"Rhône","70":"Haute-Saône","71":"Saône-et-Loire","72":"Sarthe","73":"Savoie","74":"Haute-Savoie","75":"Paris","76":"Seine-Maritime","77":"Seine-et-Marne","78":"Yvelines","79":"Deux-Sèvres","80":"Somme","81":"Tarn","82":"Tarn-et-Garonne","83":"Var","84":"Vaucluse","85":"Vendée","86":"Vienne","87":"Haute-Vienne","88":"Vosges","89":"Yonne","90":"Territoire de Belfort","91":"Essonne","92":"Hauts-de-Seine","93":"Seine-Saint-Denis","94":"Val-de-Marne","95":"Val-d'Oise","971":"Guadeloupe","972":"Martinique","973":"Guyane","974":"La Réunion","976":"Mayotte"};
function resolveDeptCode(q){
  const trimmed = q.trim().toUpperCase();
  if(/^\d{1,2}$/.test(trimmed)) return trimmed.padStart(2,'0');
  if(/^(2A|2B)$/.test(trimmed)) return trimmed;
  if(/^\d{3}$/.test(trimmed) && FRENCH_DEPARTMENTS[trimmed]) return trimmed;
  for(const code in FRENCH_DEPARTMENTS){
    if(FRENCH_DEPARTMENTS[code].toLowerCase() === q.trim().toLowerCase()) return code;
  }
  for(const code in FRENCH_DEPARTMENTS){
    if(FRENCH_DEPARTMENTS[code].toLowerCase().includes(q.trim().toLowerCase()) && q.trim().length>=3) return code;
  }
  return null;
}
function shopMatchesQuery(shop, q){
  const ql = q.toLowerCase();
  if((shop.name||'').toLowerCase().includes(ql)) return true;
  const addr = (shop.adresse||'').toLowerCase();
  if(addr.includes(ql)) return true;
  const deptCode = resolveDeptCode(q);
  if(deptCode){
    const m = addr.match(/\b(\d{5})\b/);
    if(m){
      const cp = m[1];
      const prefix = (cp.slice(0,2)==='97') ? cp.slice(0,3) : cp.slice(0,2);
      if(prefix === deptCode) return true;
    }
  }
  return false;
}
let directorySearch = '';
let directoryShops = [];
async function renderDirectory(main){
  let fab = document.getElementById('fabWrap'); if(fab) fab.style.display='none';
  main.innerHTML = `<h1 class="page-title">Vos artisans en click &amp; collect</h1><p class="page-sub">Chargement…</p>`;
  try{
    const { data, error } = await dbSelect('shops', { name: 'neq.', published: 'eq.true', select: 'id,name,slug,logo,adresse,metiers,lat,lng', order: 'name.asc' });
    if(error) throw error;
    directoryShops = data||[];
    renderDirectoryList(main);
  }catch(err){
    showBootError(err);
  }
}
let distanceOrigin = null;
async function sortByMyLocation(){
  if(!navigator.geolocation){ alert("La géolocalisation n'est pas disponible sur cet appareil."); return; }
  navigator.geolocation.getCurrentPosition(function(pos){
    distanceOrigin = { lat: pos.coords.latitude, lng: pos.coords.longitude, label: 'Votre position' };
    renderDirectoryList(document.getElementById('main'));
  }, function(){
    alert("Impossible d'accéder à votre position. Vérifiez l'autorisation de localisation.");
  });
}
async function sortBySearchLocation(){
  const geo = await geocodeAddress(directorySearch);
  if(!geo){ alert("Adresse introuvable, réessayez avec un nom de ville plus précis."); return; }
  distanceOrigin = geo;
  renderDirectoryList(document.getElementById('main'));
}
function clearDistanceSort(){ distanceOrigin = null; renderDirectoryList(document.getElementById('main')); }
let activeMetierFilters = [];
function toggleMetierFilter(m){
  const i = activeMetierFilters.indexOf(m);
  if(i>-1) activeMetierFilters.splice(i,1); else activeMetierFilters.push(m);
  renderDirectoryList(document.getElementById('main'));
}
function renderDirectoryList(main, keepFocus){
  const q = directorySearch.trim();
  const usedMetiers = METIERS.filter(m => directoryShops.some(s => (s.metiers||[]).includes(m)));
  let filtered = q ? directoryShops.filter(s=>shopMatchesQuery(s, q)) : directoryShops;
  if(activeMetierFilters.length>0){
    filtered = filtered.filter(s => (s.metiers||[]).some(m => activeMetierFilters.includes(m)));
  }
  if(distanceOrigin){
    filtered = filtered.map(s=>({ ...s, _distance: (s.lat!=null && s.lng!=null) ? haversineKm(distanceOrigin.lat, distanceOrigin.lng, s.lat, s.lng) : null }));
    filtered.sort((a,b)=>{
      if(a._distance==null && b._distance==null) return 0;
      if(a._distance==null) return 1;
      if(b._distance==null) return -1;
      return a._distance - b._distance;
    });
  }
  let html = `<div class="centered">
    <h1 class="page-title">Vos artisans en click &amp; collect</h1>
    <p class="page-sub">Commandez chez un artisan près de chez vous, retirez au créneau choisi, réglez sur place.</p>`;
  const unreadCodes = Object.keys(devisUnread);
  if(unreadCodes.length > 0){
    html += (`<div class="devis-alert"><strong>Vous avez une réponse à votre devis.</strong><br>${unreadCodes.map(c=>`<span class="pro-link" style="display:inline;font-weight:600;" onclick="openDevis('${c}')">${escapeHtml(c)}<span class="devis-badge">${devisUnread[c]} réponse${devisUnread[c]>1?'s':''}</span></span>`).join(' · ')}</div>`);
  }
  html += ordersBanner();
  if(directoryShops.length>0){
    html += `<input type="text" enterkeyhint="search" spellcheck="true" lang="fr" id="directorySearchInput" aria-label="Rechercher un artisan par nom, ville ou département" placeholder="Nom, ville, département (ex. Aisne ou 02)…" value="${escapeHtml(directorySearch)}" oninput="onDirectorySearch(this.value)" style="margin-bottom:10px;">
    <div class="toolbar">
      <button type="button" class="btn btn-ghost" onclick="sortByMyLocation()">${ICON_PIN} Autour de moi</button>
      ${q ? `<button type="button" class="btn btn-ghost" onclick="sortBySearchLocation()">Trier depuis « ${escapeHtml(q)} »</button>` : ''}
      ${distanceOrigin ? `<button type="button" class="btn btn-ghost" style="color:var(--danger);" onclick="clearDistanceSort()">✕ ${escapeHtml(distanceOrigin.label||'')}</button>` : ''}
    </div>`;
  }
  if(usedMetiers.length>0){
    html += `<div class="filter-label">Filtrer par métier</div>
    <div class="cat-scroll">${usedMetiers.map(m=>`<div class="cat-chip tagchip ${activeMetierFilters.includes(m)?'active':''}" onclick="toggleMetierFilter('${m.replace(/'/g,"\\'")}')">${escapeHtml(m)}</div>`).join('')}</div>`;
  }
  if(directoryShops.length===0){
    html += `<div class="empty"><p>Aucune boutique publiée pour le moment.</p></div>`;
  } else if(filtered.length===0){
    html += `<div class="empty"><p>Aucun artisan ne correspond à ces critères.</p></div>`;
  } else {
    html += `<h2 class="list-title">${filtered.length} boutique${filtered.length>1?'s':''}</h2><div class="shop-list">`;
    filtered.forEach(s=>{
      const sub = [(s.metiers||[]).join(' · '), cityOf(s.adresse)].filter(Boolean).join(' — ');
      html += `<a class="shop-item" ${s.slug ? `href="/${encodeURI(s.slug)}"` : 'href="/"'} onclick="return navShop(event,'${s.id}')">
        <span class="avatar">${s.logo?`<img src="${s.logo}" alt="" loading="lazy">`:'<span aria-hidden="true">🥖</span>'}</span>
        <span class="meta"><span class="nm">${escapeHtml(s.name||'Boutique sans nom')}</span>${sub?`<span class="sub">${escapeHtml(sub)}</span>`:''}</span>
        ${s._distance!=null?`<span class="dist">${s._distance.toFixed(1).replace('.', ',')} km</span>`:''}
        <span class="chev" aria-hidden="true">${ICON_CHEVRON}</span>
      </a>`;
    });
    html += `</div>`;
  }
  html += `<div class="foot-links">
    ${!currentUser ? `<a href="/inscription" onclick="goAuth('signup');return false;"><span>Vous êtes artisan ?</span><strong>Créer ma boutique →</strong></a>` : ''}
    <a href="/devis" onclick="openDevisLookup();return false;"><span>Vous avez demandé un devis ?</span><strong>Ouvrir mon devis →</strong></a>
  </div></div>`;
  main.innerHTML = html;
  const inp = document.getElementById('directorySearchInput');
  if(inp && keepFocus){ inp.focus(); inp.setSelectionRange(inp.value.length, inp.value.length); }
  refreshDevisBadges(); refreshOrderStates();
}
function onDirectorySearch(v){ directorySearch = v; renderDirectoryList(document.getElementById('main'), true); }

/* ================= Legal pages ================= */
let legalTab = 'mentions';
function goLegal(tab){ legalTab = tab; closeCartSilently(); screen = 'legal'; render(); }
/* À COMPLÉTER AVANT LE LANCEMENT COMMERCIAL : obligatoire dès que la plateforme perçoit une rémunération.
   Exemple : statut: 'entrepreneur individuel', siret: '123 456 789 00012', adresse: '1 rue Exemple, 02240 Ribemont' */
const EDITOR = { statut: '', siret: '', adresse: '' };
function renderLegal(main){
  let fab = document.getElementById('fabWrap'); if(fab) fab.style.display='none';
  const tabs = [['mentions','Mentions légales'],['cgu','CGU'],['confidentialite','Confidentialité'],['suppression-compte','Suppression de compte'],['suppression-donnees','Suppression de données']];
  let html = `<h1 class="page-title">Informations légales</h1>
  <div class="cat-scroll" style="flex-wrap:wrap;overflow:visible;">${tabs.map(([k,label])=>`<div class="cat-chip ${legalTab===k?'active':''}" onclick="goLegal('${k}')">${label}</div>`).join('')}</div>`;

  if(legalTab==='mentions'){
    html += `
    <h2 style="font-family:'Fraunces',serif;font-size:1.17em;margin:18px 0 8px;">Éditeur du site</h2>
    <p style="font-size:14px;line-height:1.7;">
      Le site La Place des Artisans est édité par :<br>
      <strong>Ronan Bisson</strong>${EDITOR.statut ? ', ' + escapeHtml(EDITOR.statut) : ''}<br>
      ${EDITOR.adresse ? escapeHtml(EDITOR.adresse) + '<br>' : ''}${EDITOR.siret ? 'SIRET : ' + escapeHtml(EDITOR.siret) + '<br>' : ''}
      Contact : <a href="mailto:laplacedesartisans02@gmail.com" style="color:var(--forest-700);">laplacedesartisans02@gmail.com</a><br>
      ${EDITOR.siret ? '' : 'Le service est en phase de développement et de test ; ces mentions seront complétées (statut, SIRET, adresse) lors du lancement commercial.'}
    </p>
    <h2 style="font-family:'Fraunces',serif;font-size:1.17em;margin:18px 0 8px;">Directeur de la publication</h2>
    <p style="font-size:14px;line-height:1.7;">Ronan Bisson</p>
    <h2 style="font-family:'Fraunces',serif;font-size:1.17em;margin:18px 0 8px;">Hébergement</h2>
    <p style="font-size:14px;line-height:1.7;">
      Le site est hébergé par <strong>Netlify, Inc.</strong> — 512 2nd Street, Suite 200, San Francisco, CA 94107, États-Unis.<br>
      La base de données est hébergée par <strong>Supabase</strong> (région Europe — Paris).
    </p>
    <h2 style="font-family:'Fraunces',serif;font-size:1.17em;margin:18px 0 8px;">Paiements en ligne</h2>
    <p style="font-size:14px;line-height:1.7;">Les paiements en ligne (acomptes) sont traités par <strong>Stripe Payments Europe, Ltd.</strong>, 1 Grand Canal Street Lower, Grand Canal Dock, Dublin, D02 H210, Irlande. La plateforme n'a jamais accès aux numéros de carte bancaire.</p>`;
  } else if(legalTab==='cgu'){
    html += `
    <div style="font-size:14px;line-height:1.7;">
    <h2 style="font-family:'Fraunces',serif;font-size:1.17em;margin:18px 0 8px;">1. Objet</h2>
    <p>La Place des Artisans est une plateforme de mise en relation entre des artisans commerçants et des clients, permettant la consultation de catalogues de produits et la commande en click&amp;collect (retrait en boutique).</p>
    <h2 style="font-family:'Fraunces',serif;font-size:1.17em;margin:18px 0 8px;">2. Fonctionnement des boutiques</h2>
    <p>Chaque boutique visible sur la plateforme est créée et gérée de façon indépendante par l'artisan qui en est propriétaire : contenu, prix, disponibilité, préparation et qualité des produits relèvent de sa seule responsabilité.</p>
    <h2 style="font-family:'Fraunces',serif;font-size:1.17em;margin:18px 0 8px;">3. Commandes et règlement</h2>
    <p>Les commandes sont passées auprès de l'artisan, qui est le vendeur. Selon la boutique, le règlement a lieu sur place, au retrait, ou — lorsque l'artisan a activé le paiement en ligne — par un acompte versé en ligne après acceptation de la commande (voir l'article 7), le solde se réglant au retrait. Lorsque le bouton de validation indique « Commande avec obligation de paiement », le client s'engage à régler l'acompte demandé, puis le solde au retrait. La plateforme n'est pas partie à la vente : elle fournit l'outil de commande.</p>
    <h2 style="font-family:'Fraunces',serif;font-size:1.17em;margin:18px 0 8px;">4. Droit de rétractation</h2>
    <p>Conformément à l'article L221-28 du Code de la consommation, le droit de rétractation de 14 jours ne s'applique pas aux biens périssables ou confectionnés selon les spécifications du client (produits alimentaires frais, pièces montées, commandes personnalisées, etc.). Une fois la commande validée, elle ne peut donc pas être annulée de plein droit, sauf accord de l'artisan concerné.</p>
    <h2 style="font-family:'Fraunces',serif;font-size:1.17em;margin:18px 0 8px;">5. Responsabilité</h2>
    <p>L'éditeur de la plateforme agit en simple intermédiaire technique et n'intervient pas dans la relation commerciale entre le client et l'artisan. Il ne saurait être tenu responsable de la qualité, de la conformité ou de la disponibilité des produits proposés, ni des litiges pouvant survenir entre un client et un artisan.</p>
    <h2 style="font-family:'Fraunces',serif;font-size:1.17em;margin:18px 0 8px;">6. Facturation</h2>
    <p>Chaque artisan est seul responsable de l'émission de ses factures ou tickets de caisse, conformément aux obligations comptables et fiscales qui lui incombent en tant que professionnel. Les acomptes encaissés en ligne sont déduits du total de la commande : l'artisan émet le ticket ou la facture pour la totalité de la vente.</p>
    <h2 style="font-family:'Fraunces',serif;font-size:1.17em;margin:18px 0 8px;">7. Paiement en ligne de l'acompte et annulation</h2>
    <p>Lorsque l'artisan a activé le paiement en ligne, sa boutique fonctionne ainsi :</p>
    <ul style="padding-left:20px;margin:6px 0 10px;">
      <li>le client envoie sa commande ; <strong>aucun paiement n'est demandé à ce stade</strong> ;</li>
      <li>l'artisan accepte (ou refuse) la commande. En cas d'acceptation, un <strong>lien de paiement sécurisé</strong> est mis à disposition du client sur la page de suivi de sa commande, et par e-mail s'il en a indiqué un ; ce lien est valable 24 heures et peut être renouvelé par l'artisan ;</li>
      <li>l'acompte (par défaut 30 % du total, fixé par l'artisan) est <strong>encaissé directement par l'artisan</strong>, par l'intermédiaire de Stripe ; la plateforme n'en prélève aucune part ;</li>
      <li>le solde se règle au retrait. La commande n'est préparée qu'une fois l'acompte réglé.</li>
    </ul>
    <p><strong>Annulation et remboursement.</strong> Le client peut annuler sa commande depuis la page de suivi, tant qu'elle n'est pas récupérée. L'acompte lui est <strong>remboursé intégralement s'il annule dans l'heure qui suit son paiement</strong> ; passé ce délai, il reste acquis à l'artisan. Si c'est l'artisan qui annule (rupture, panne, imprévu), l'acompte est <strong>intégralement remboursé</strong>. Les remboursements sont effectués par l'artisan via Stripe et créditent le moyen de paiement utilisé sous quelques jours ouvrés. Ces stipulations s'ajoutent aux droits que le client tient de la loi.</p>
    <p><strong>Conservation du lien de suivi.</strong> L'e-mail étant facultatif, le client est invité à conserver le lien de suivi de sa commande : il lui permet de retrouver son état et le lien de règlement. La plateforme ne peut pas le lui renvoyer si aucun e-mail n'a été indiqué.</p>
    <h2 style="font-family:'Fraunces',serif;font-size:1.17em;margin:18px 0 8px;">8. Conditions applicables aux artisans</h2>
    <p><strong>Compte de paiement.</strong> Pour recevoir des acomptes, l'artisan ouvre un compte auprès de <strong>Stripe</strong>, dans le cadre du contrat conclu directement avec Stripe, qui vérifie son identité et son activité. L'artisan reste le vendeur : il encaisse l'acompte sur son propre compte, supporte les frais de transaction de Stripe (selon les tarifs de Stripe) et reste responsable de l'exécution de ses commandes, des remboursements et des litiges avec ses clients. L'activation du paiement en ligne est obligatoire pour publier une nouvelle boutique.</p>
    <p><strong>Rémunération de la plateforme.</strong> Au choix de l'artisan, modifiable à tout moment :</p>
    <ul style="padding-left:20px;margin:6px 0 10px;">
      <li>une <strong>commission de 8 %</strong> du montant total des commandes <strong>récupérées</strong> au cours du mois civil, calculée sur l'ensemble du mois et facturée le 1er du mois suivant ; aucune commission sur les commandes refusées, annulées ou non récupérées, ni sur l'acompte lui-même ;</li>
      <li>ou un <strong>abonnement de 24,99 € par mois</strong>, sans commission.</li>
    </ul>
    <p>Le taux applicable à une commande est celui en vigueur au moment où elle est marquée comme récupérée.</p>
    <p><strong>Facturation et règlement.</strong> Les commissions sont facturées mensuellement ; l'artisan choisit de les régler par <strong>prélèvement SEPA automatique</strong> ou par <strong>virement sous 30 jours</strong>. TVA non applicable, article 293 B du Code général des impôts. En cas de retard de paiement : pénalités au taux de trois fois le taux d'intérêt légal et indemnité forfaitaire de recouvrement de 40 € (articles L441-10 et D441-5 du Code de commerce) ; aucun escompte n'est accordé pour paiement anticipé.</p>
    <p><strong>Obligations de l'artisan.</strong> Honorer les commandes acceptées ; renseigner avec exactitude les allergènes et les informations de ses produits ; rembourser les clients dans les cas prévus à l'article 7 ; déclarer ses propres revenus et émettre ses propres factures.</p>
    <p><strong>Fin de la relation.</strong> L'artisan peut supprimer sa boutique à tout moment, sauf commande en cours ou facture à régler. Les factures émises et les pièces comptables associées sont conservées pendant dix ans (article L123-22 du Code de commerce).</p>
    <h2 style="font-family:'Fraunces',serif;font-size:1.17em;margin:18px 0 8px;">9. Médiation de la consommation</h2>
    <p>En cas de litige avec un artisan, le client est invité à le contacter directement en priorité. À défaut de résolution amiable, et conformément aux articles L616-1 et suivants du Code de la consommation, le client peut recourir gratuitement à un médiateur de la consommation ; les coordonnées du médiateur compétent sont celles communiquées par l'artisan concerné, en sa qualité de vendeur professionnel.</p>
    <h2 style="font-family:'Fraunces',serif;font-size:1.17em;margin:18px 0 8px;">10. Comptes utilisateurs</h2>
    <p>Chaque artisan est responsable de la confidentialité de ses identifiants de connexion. L'éditeur se réserve le droit de suspendre un compte en cas d'usage frauduleux ou de non-respect des présentes conditions.</p>
    <h2 style="font-family:'Fraunces',serif;font-size:1.17em;margin:18px 0 8px;">11. Évolution des CGU</h2>
    <p>Ces conditions, propres à un service en cours de développement, sont susceptibles d'évoluer. La version en vigueur est celle publiée sur cette page.</p>
    </div>`;
  } else if(legalTab==='confidentialite'){
    html += `
    <div style="font-size:14px;line-height:1.7;">
    <h2 style="font-family:'Fraunces',serif;font-size:1.17em;margin:18px 0 8px;">Données collectées</h2>
    <p>Selon votre usage du service, nous collectons : votre adresse e-mail et un mot de passe (stocké de façon sécurisée, jamais en clair) ; votre numéro SIRET, vérifié auprès du registre officiel des entreprises françaises pour vous permettre de créer un compte artisan ; les informations de votre boutique si vous êtes artisan (nom, logo, adresse, téléphone, horaires) ; les fiches produits que vous créez ; le contenu des commandes passées et des devis, y compris les messages échangés dans la messagerie d'un devis (articles, options, créneau de retrait).</p>
    <h2 style="font-family:'Fraunces',serif;font-size:1.17em;margin:18px 0 8px;">Finalité</h2>
    <p>Ces données servent uniquement à la création et à la gestion de votre compte, au fonctionnement du service de commande en click&amp;collect, et à l'affichage de votre tableau de bord.</p>
    <h2 style="font-family:'Fraunces',serif;font-size:1.17em;margin:18px 0 8px;">Destinataires</h2>
    <p>Les données d'une boutique (produits, commandes) ne sont visibles que par l'artisan concerné, et par l'administrateur de la plateforme à des fins de support technique.</p>
    <h2 style="font-family:'Fraunces',serif;font-size:1.17em;margin:18px 0 8px;">Sous-traitants techniques</h2>
    <p><strong>Supabase</strong> (hébergement de la base de données, région Europe — Paris) et <strong>Netlify</strong> (hébergement du site) traitent des données en tant que sous-traitants techniques. <strong>Resend</strong> (États-Unis), utilisé pour l'envoi des e-mails (confirmation de compte, récapitulatifs de commandes), adhère au cadre de protection des données UE-États-Unis (Data Privacy Framework) encadrant ce transfert.</p>
    <h2 style="font-family:'Fraunces',serif;font-size:1.17em;margin:18px 0 8px;">Paiements en ligne (Stripe)</h2>
    <p>Les acomptes et l'activation du compte de paiement des artisans sont assurés par <strong>Stripe Payments Europe, Ltd.</strong> <strong>Clients :</strong> vos coordonnées bancaires sont saisies directement sur la page sécurisée de Stripe ; la plateforme n'y a jamais accès et ne conserve que le montant et l'état du paiement (réglé, remboursé). <strong>Artisans :</strong> votre identité, les informations de votre entreprise et votre compte bancaire sont collectés par Stripe pour la vérification réglementaire ; la plateforme reçoit seulement l'état de votre compte (activé ou non) et ses références techniques. Stripe agit comme responsable de traitement pour ses propres obligations légales (lutte contre la fraude et le blanchiment). Sa politique de confidentialité : <span style="color:var(--forest-700);">stripe.com/fr/privacy</span>.</p>
    <h2 style="font-family:'Fraunces',serif;font-size:1.17em;margin:18px 0 8px;">Durées de conservation</h2>
    <p>Les commandes et leur suivi sont conservés pendant la durée nécessaire à leur exécution et aux éventuelles réclamations. Les factures émises aux artisans et les pièces comptables sont conservées dix ans (article L123-22 du Code de commerce). Le client qui n'a pas indiqué d'e-mail n'est pas identifiable : sa commande n'est rattachée à aucune personne.</p>
    <h2 style="font-family:'Fraunces',serif;font-size:1.17em;margin:18px 0 8px;">Cookies et stockage local</h2>
    <p>Le site utilise le stockage local de votre navigateur pour maintenir votre session de connexion et pour retrouver sur votre appareil vos commandes et devis en cours (codes de suivi), ainsi que vos préférences d'affichage. Le composant d'activation des paiements proposé aux artisans (Stripe) peut déposer ses propres traceurs de sécurité et de prévention de la fraude. Aucun cookie publicitaire ni traceur tiers n'est utilisé.</p>
    <h2 style="font-family:'Fraunces',serif;font-size:1.17em;margin:18px 0 8px;">Vos droits</h2>
    <p>Conformément au RGPD, vous disposez d'un droit d'accès, de rectification, de portabilité et d'effacement de vos données. Un artisan peut exporter ses données ou supprimer son compte directement depuis l'onglet « Infos boutique ». Pour toute autre demande, écrivez à <a href="mailto:laplacedesartisans02@gmail.com" style="color:var(--forest-700);">laplacedesartisans02@gmail.com</a>.</p>
    <h2 style="font-family:'Fraunces',serif;font-size:1.17em;margin:18px 0 8px;">Signalement</h2>
    <p>Tout contenu que vous estimez illicite ou inapproprié peut être signalé à <a href="mailto:laplacedesartisans02@gmail.com" style="color:var(--forest-700);">laplacedesartisans02@gmail.com</a>, ou directement depuis la page d'une boutique via le lien « Signaler cette boutique ».</p>
    </div>`;
  } else if(legalTab==='suppression-compte'){
    html += `
    <div style="font-size:14px;line-height:1.7;">
    <h2 style="font-family:'Fraunces',serif;font-size:1.17em;margin:18px 0 8px;">Supprimer votre compte La Place des Artisans</h2>
    <p>Si vous avez un compte artisan sur <strong>La Place des Artisans</strong>, vous pouvez le supprimer vous-même, à tout moment, en quelques secondes, directement depuis l'application :</p>
    <ol style="padding-left:20px;">
      <li>Connectez-vous à votre compte</li>
      <li>Ouvrez l'onglet <strong>« Infos boutique »</strong></li>
      <li>Faites défiler jusqu'à la section <strong>« Zone dangereuse »</strong></li>
      <li>Appuyez sur <strong>« Supprimer mon compte »</strong>, puis confirmez</li>
    </ol>
    <p>La suppression est <strong>immédiate et définitive</strong> : votre compte, votre boutique, vos fiches produits et l'historique de vos commandes sont effacés. Cette action est irréversible. Elle n'est possible que lorsqu'<strong>aucune commande n'est en cours</strong> et qu'<strong>aucune facture ne vous a été émise</strong> ; dans le cas contraire, écrivez-nous (voir ci-dessous) pour clôturer votre compte.</p>
    <h2 style="font-family:'Fraunces',serif;font-size:1.17em;margin:18px 0 8px;">Sans accès à l'application</h2>
    <p>Si vous ne pouvez pas vous connecter à l'application, vous pouvez demander la suppression de votre compte et de vos données par e-mail à <a href="mailto:laplacedesartisans02@gmail.com?subject=Suppression%20de%20compte" style="color:var(--forest-700);">laplacedesartisans02@gmail.com</a>, en précisant l'adresse e-mail associée à votre compte. Votre demande sera traitée sous 30 jours maximum, conformément au RGPD.</p>
    <h2 style="font-family:'Fraunces',serif;font-size:1.17em;margin:18px 0 8px;">Données supprimées</h2>
    <p>La suppression efface intégralement : votre adresse e-mail et mot de passe, votre boutique (nom, adresse, horaires, description, SIRET), vos fiches produits, ainsi que l'historique de vos commandes.</p>
    <h2 style="font-family:'Fraunces',serif;font-size:1.17em;margin:18px 0 8px;">Données conservées</h2>
    <p>Après la suppression de votre compte, <strong>aucune donnée n'est conservée par La Place des Artisans, à l'exception de celles que la loi nous oblige à garder</strong> : les factures émises à votre intention et leurs pièces comptables (dix ans, article L123-22 du Code de commerce). <strong>Stripe</strong> conserve de son côté les données de votre compte de paiement pour ses propres obligations légales. Voir aussi notre <span class="pro-link" style="display:inline;" onclick="goLegal('confidentialite')">politique de confidentialité</span>.</p>
    </div>`;
  } else if(legalTab==='suppression-donnees'){
    html += `
    <div style="font-size:14px;line-height:1.7;">
    <h2 style="font-family:'Fraunces',serif;font-size:1.17em;margin:18px 0 8px;">Supprimer une partie de vos données, sans fermer votre compte</h2>
    <p>Sur <strong>La Place des Artisans</strong>, un artisan peut supprimer les données de sa boutique (produits, commandes, réglages) sans supprimer son compte de connexion :</p>
    <ol style="padding-left:20px;">
      <li>Connectez-vous à votre compte</li>
      <li>Ouvrez l'onglet <strong>« Infos boutique »</strong></li>
      <li>Faites défiler jusqu'à la section <strong>« Zone dangereuse »</strong></li>
      <li>Appuyez sur <strong>« Supprimer cette boutique »</strong>, puis confirmez</li>
    </ol>
    <p>Cette action efface <strong>immédiatement et définitivement</strong> vos fiches produits, l'historique de vos commandes, et les réglages de votre boutique (horaires, couleurs, description, points de retrait). <strong>Votre compte de connexion (e-mail et mot de passe) est conservé</strong> : vous pouvez immédiatement recommencer avec une nouvelle boutique vide.</p>
    <h2 style="font-family:'Fraunces',serif;font-size:1.17em;margin:18px 0 8px;">Sans accès à l'application</h2>
    <p>Vous pouvez aussi demander la suppression de données précises par e-mail à <a href="mailto:laplacedesartisans02@gmail.com?subject=Suppression%20partielle%20de%20donn%C3%A9es" style="color:var(--forest-700);">laplacedesartisans02@gmail.com</a>, en précisant l'adresse e-mail de votre compte et les données concernées. Traitement sous 30 jours maximum.</p>
    <h2 style="font-family:'Fraunces',serif;font-size:1.17em;margin:18px 0 8px;">Données supprimées et conservées</h2>
    <p>Sont effacés : produits, commandes, horaires, description, points de retrait de la boutique. <strong>Sont conservés</strong> : votre e-mail et votre mot de passe (votre compte reste actif). Aucun délai de conservation supplémentaire ne s'applique aux données supprimées.</p>
    </div>`;
  }
  html += `<p class="pro-link" onclick="screen='directory';render();">← Retour aux boutiques</p><div style="height:20px"></div>`;
  main.innerHTML = html;
}

async function openShop(id){
  const sameShop = currentDataId === id && (screen === 'client' || screen === 'confirm') && clientReturnScreen !== 'pro';
  closeCartSilently();
  clientReturnScreen = 'directory';
  if(!sameShop){
    cart = []; showPracticalInfo = false; selectedPickupPointId = '';
    document.getElementById('main').innerHTML = `<p class="page-sub">Chargement…</p>`;
    await loadShopFor(id);
  }
  screen = 'client';
  render();
}
function backFromClient(){
  closeCartSilently();
  screen = clientReturnScreen === 'pro' ? 'pro' : 'directory';
  clientReturnScreen = 'directory';
  render();
}
function previewOwnShop(){
  clientReturnScreen = 'pro';
  screen = 'client';
  render();
}

/* ================= Auth ================= */
let captchaA = 0, captchaB = 0;
function goAuth(m){ authMode=m; if(m==='signup'){ newCaptcha(); } closeCartSilently(); screen='auth'; render(); }
function showAuthError(msg){ const el = document.getElementById('authError'); if(el) el.textContent = msg; }

function renderAuth(main){
  let fab = document.getElementById('fabWrap'); if(fab) fab.style.display='none';
  const titles = { signup:'Créer un compte pro', checkEmail:'Vérifiez vos e-mails', forgot:'Mot de passe oublié', forgotSent:'Vérifiez vos e-mails', resetPassword:'Nouveau mot de passe' };
  let html = `<h1 class="page-title">${titles[authMode] || 'Connexion'}</h1>
  <div class="auth-error" id="authError"></div>`;

  if(authMode==='checkEmail'){
    html += `<div class="demo-note">Un e-mail de confirmation vient de vous être envoyé. Cliquez sur le lien qu'il contient, puis revenez ici pour vous connecter.</div>
    <div class="sheet-actions"><button class="btn btn-oak btn-block" onclick="goAuth('login')">Aller à la connexion</button></div>`;
  } else if(authMode==='forgot'){
    html += `<p class="page-sub">Indiquez votre e-mail, nous vous enverrons un lien pour choisir un nouveau mot de passe.</p>
    <label class="field-label">E-mail</label>
    <input type="email" id="authEmail" placeholder="vous@boutique.fr">
    <div class="sheet-actions"><button class="btn btn-oak btn-block" onclick="handleForgotPassword()">Envoyer le lien</button></div>
    <p class="pro-link" onclick="goAuth('login')">← Retour à la connexion</p>`;
  } else if(authMode==='forgotSent'){
    html += `<div class="demo-note">Un e-mail vient de vous être envoyé avec un lien pour choisir un nouveau mot de passe. Cliquez dessus, puis revenez ici.</div>
    <div class="sheet-actions"><button class="btn btn-oak btn-block" onclick="goAuth('login')">Aller à la connexion</button></div>`;
  } else if(authMode==='resetPassword'){
    html += `<p class="page-sub">Choisissez votre nouveau mot de passe.</p>
    <label class="field-label">Nouveau mot de passe</label>
    <input type="password" id="authPassword" placeholder="••••••••" oninput="updatePwRules()">
    <div class="rule-list" id="pwRules">
      <div class="rule-item" data-rule="len">8 caractères minimum</div>
      <div class="rule-item" data-rule="upper">Une majuscule</div>
      <div class="rule-item" data-rule="lower">Une minuscule</div>
      <div class="rule-item" data-rule="special">Un caractère spécial (ex. ! ? # @)</div>
    </div>
    <label class="field-label">Confirmer le mot de passe</label>
    <input type="password" id="authPassword2" placeholder="••••••••">
    <div class="sheet-actions"><button class="btn btn-oak btn-block" onclick="handleResetPassword()">Enregistrer le mot de passe</button></div>`;
  } else {
    html += `<label class="field-label">E-mail</label>
    <input type="email" id="authEmail" placeholder="vous@boutique.fr">
    <label class="field-label">Mot de passe</label>
    <input type="password" id="authPassword" placeholder="••••••••" oninput="updatePwRules()">`;
    if(authMode==='signup'){
      html += `<div class="rule-list" id="pwRules">
        <div class="rule-item" data-rule="len">8 caractères minimum</div>
        <div class="rule-item" data-rule="upper">Une majuscule</div>
        <div class="rule-item" data-rule="lower">Une minuscule</div>
        <div class="rule-item" data-rule="special">Un caractère spécial (ex. ! ? # @)</div>
      </div>
      <label class="field-label">Confirmer le mot de passe</label>
      <input type="password" id="authPassword2" placeholder="••••••••">
      <label class="field-label">Vérification anti-robot — combien font ${captchaA} + ${captchaB} ?</label>
      <input type="text" inputmode="numeric" id="authCaptcha" placeholder="Votre réponse">
      <input type="text" id="hpField" name="hpField" autocomplete="off" tabindex="-1" style="position:absolute;left:-9999px;top:-9999px;width:1px;height:1px;opacity:0;" aria-hidden="true">
      <div class="sheet-actions"><button class="btn btn-oak btn-block" onclick="handleSignup()">Créer mon compte</button></div>
      <p class="pro-link" onclick="goAuth('login')">Déjà un compte ? Se connecter</p>`;
    } else {
      html += `<div class="sheet-actions"><button class="btn btn-oak btn-block" onclick="handleLogin()">Se connecter</button></div>
      <p class="pro-link" onclick="goAuth('forgot')">Mot de passe oublié ?</p>
      <p class="pro-link" onclick="goAuth('signup')">Pas encore de compte ? En créer un</p>`;
    }
  }
  if(authMode!=='resetPassword'){
    html += `<p class="pro-link" onclick="screen='directory';render();">← Retour aux boutiques</p>`;
  }
  main.innerHTML = html;
}

function updatePwRules(){
  const pw = val('authPassword');
  checkPasswordRules(pw).forEach(r=>{
    const el = document.querySelector(`#pwRules .rule-item[data-rule="${r.key}"]`);
    if(el) el.classList.toggle('ok', r.ok);
  });
}

async function handleSignup(){
  const email = val('authEmail').trim().toLowerCase();
  const pw = val('authPassword');
  const pw2 = val('authPassword2');
  if(val('hpField').trim()!==''){ showAuthError('Une erreur est survenue.'); return; }
  const captchaAns = parseInt(val('authCaptcha').trim(), 10);
  if(captchaAns !== captchaA+captchaB){
    captchaA = 1+Math.floor(Math.random()*8); captchaB = 1+Math.floor(Math.random()*8);
    showAuthError('Réponse anti-robot incorrecte, nouvelle question générée.');
    const capInput = document.getElementById('authCaptcha');
    if(capInput){ capInput.value=''; const p = capInput.previousElementSibling; if(p) p.textContent = `Vérification anti-robot — combien font ${captchaA} + ${captchaB} ?`; }
    return;
  }
  if(!validEmail(email)){ showAuthError('Adresse e-mail invalide.'); return; }
  if(!checkPasswordRules(pw).every(r=>r.ok)){ showAuthError('Le mot de passe ne respecte pas toutes les règles ci-dessus.'); return; }
  if(pw!==pw2){ showAuthError('Les mots de passe ne correspondent pas.'); return; }
  const { error } = await authSignUp(email, pw);
  if(error){ showAuthError(error.message); return; }
  authMode = 'checkEmail'; render();
}
let verifiedSiretForPublish = null;
function onShopSiretInput(){ verifiedSiretForPublish = null; const el = document.getElementById('shopSiretResult'); if(el) el.innerHTML = ''; }
async function verifyShopSiret(){
  const raw = val('shopSiretInput').replace(/\s+/g,'');
  const resultEl = document.getElementById('shopSiretResult');
  if(!/^\d{14}$/.test(raw)){
    resultEl.innerHTML = '<span style="color:var(--danger);">Le SIRET doit contenir exactement 14 chiffres.</span>';
    verifiedSiretForPublish = null;
    return;
  }
  resultEl.textContent = 'Vérification en cours…';
  try{
    const res = await fetch('https://recherche-entreprises.api.gouv.fr/search?q=' + raw);
    const data = await res.json();
    const results = data.results || [];
    const match = results.find(r => (r.matching_etablissements||[]).some(e=>e.siret===raw)) || results[0];
    if(match){
      verifiedSiretForPublish = raw;
      resultEl.innerHTML = '<span style="color:var(--forest-700);">✓ Entreprise trouvée : ' + escapeHtml(match.nom_complet || match.nom_raison_sociale || '') + '</span>';
    } else {
      verifiedSiretForPublish = null;
      resultEl.innerHTML = '<span style="color:var(--danger);">Aucune entreprise trouvée avec ce SIRET.</span>';
    }
  }catch(e){
    verifiedSiretForPublish = null;
    resultEl.innerHTML = '<span style="color:var(--danger);">Impossible de vérifier pour le moment. Réessayez.</span>';
  }
}
async function publishShop(){
  if(!(shop && shop.stripe_charges_enabled)){ alert("Activez d'abord vos paiements en ligne (section « Paiements en ligne », plus haut) : c'est obligatoire pour publier votre boutique."); return; }
  const raw = val('shopSiretInput').replace(/\s+/g,'');
  if(!verifiedSiretForPublish || verifiedSiretForPublish !== raw){ alert('Merci de vérifier un SIRET valide avant de publier.'); return; }
  const { error } = await dbUpdate('shops', { id: 'eq.'+currentDataId }, { siret: raw, published: true });
  if(error){ alert(error.message.includes('duplicate') || error.message.includes('unique') ? 'Ce SIRET est déjà associé à une autre boutique.' : error.message); return; }
  shop.siret = raw; shop.published = true;
  render();
}
async function adminForcePublish(){
  if(!confirm('Rendre cette boutique visible publiquement sans vérification SIRET ? (usage test / exception admin)')) return;
  const { error } = await dbUpdate('shops', { id: 'eq.'+currentDataId }, { published: true });
  if(error){ alert(error.message); return; }
  shop.published = true;
  render();
}
async function unpublishShop(){
  if(!confirm('Rendre cette boutique privée ? Elle disparaîtra de l\'annuaire client.')) return;
  const { error } = await dbUpdate('shops', { id: 'eq.'+currentDataId }, { published: false });
  if(error){ alert(error.message); return; }
  shop.published = false;
  render();
}

async function handleLogin(){
  const email = val('authEmail').trim().toLowerCase();
  const pw = val('authPassword');
  const { data, error } = await authSignIn(email, pw);
  if(error){ showAuthError(error.message.includes('Invalid') ? 'E-mail ou mot de passe incorrect (ou compte pas encore confirmé par e-mail).' : error.message); return; }
  await afterLogin(data.user);
}
async function handleForgotPassword(){
  const email = val('authEmail').trim().toLowerCase();
  if(!validEmail(email)){ showAuthError('Adresse e-mail invalide.'); return; }
  const { error } = await authRequestPasswordReset(email);
  if(error){ showAuthError(error.message); return; }
  authMode = 'forgotSent'; render();
}
async function handleResetPassword(){
  const pw = val('authPassword');
  const pw2 = val('authPassword2');
  if(!checkPasswordRules(pw).every(r=>r.ok)){ showAuthError('Le mot de passe ne respecte pas toutes les règles ci-dessus.'); return; }
  if(pw!==pw2){ showAuthError('Les mots de passe ne correspondent pas.'); return; }
  if(!recoveryToken){ showAuthError('Lien invalide ou expiré. Refaites une demande.'); return; }
  const { error } = await authSetNewPassword(recoveryToken, pw);
  if(error){ showAuthError(error.message); return; }
  recoveryToken = null;
  authMode = 'login';
  screen = 'auth';
  render();
  showAuthError('');
  const el = document.getElementById('authError');
  if(el){ el.style.color = 'var(--forest-700)'; el.textContent = 'Mot de passe mis à jour. Connectez-vous.'; }
}

/* ================= Admin dashboard ================= */
let adminManaging = false;
function openNewShop(){
  document.getElementById('newShopName').value = '';
  document.getElementById('newShopOwner').value = '';
  document.getElementById('newShopError').textContent = '';
  document.getElementById('newShopBackdrop').classList.add('open');
}
function closeNewShop(){ document.getElementById('newShopBackdrop').classList.remove('open'); }
async function findProfileByEmail(email, errEl){
  const { data, error } = await dbSelect('profiles', { email: 'eq.'+email.toLowerCase(), select: 'id,email' });
  if(error){ errEl.textContent = error.message; return null; }
  const prof = data && data[0];
  if(!prof){ errEl.textContent = "Aucun compte n'existe avec cet e-mail. L'artisan doit d'abord créer son compte."; return null; }
  const { data: owned } = await dbSelect('shops', { owner_id: 'eq.'+prof.id, select: 'id' });
  if(owned && owned.length){ errEl.textContent = "Cet artisan possède déjà une boutique."; return null; }
  return prof;
}
async function createShopAsAdmin(){
  const errEl = document.getElementById('newShopError');
  const name = val('newShopName').trim();
  const ownerEmail = val('newShopOwner').trim();
  if(!name){ errEl.textContent = 'Indiquez un nom de boutique.'; return; }
  let ownerId = currentUser.id;
  if(ownerEmail){
    if(!validEmail(ownerEmail)){ errEl.textContent = 'Adresse e-mail invalide.'; return; }
    const prof = await findProfileByEmail(ownerEmail, errEl);
    if(!prof) return;
    ownerId = prof.id;
  }
  const slug = await ensureUniqueSlug(slugify(name), null);
  const { data, error } = await dbInsert('shops', { owner_id: ownerId, name: name, slug: slug }, true);
  if(error){ errEl.textContent = error.message; return; }
  const created = Array.isArray(data) ? data[0] : data;
  closeNewShop();
  if(created && created.id) await manageShop(created.id);
  else render();
}
async function assignShop(id){
  const email = prompt("E-mail du compte artisan à qui attribuer cette boutique :");
  if(!email) return;
  const fake = { textContent: '' };
  const prof = await findProfileByEmail(email.trim(), fake);
  if(!prof){ alert(fake.textContent); return; }
  const { error } = await dbUpdate('shops', { id: 'eq.'+id }, { owner_id: prof.id });
  if(error){ alert(error.message); return; }
  alert('Boutique attribuée à ' + prof.email + '.');
  render();
}
async function manageShop(id){
  adminManaging = true;
  document.getElementById('main').innerHTML = `<p class="page-sub">Chargement…</p>`;
  await loadShopFor(id);
  proTab = 'produits';
  clientReturnScreen = 'directory';
  screen = 'pro';
  startOrderPolling();
  render();
}
function backToAdminDash(){
  stopOrderPolling();
  adminManaging = false; currentDataId = null;
  screen = 'admin';
  render();
}
async function renderAdminDash(main){
  let fab = document.getElementById('fabWrap'); if(fab) fab.style.display='none';
  main.innerHTML = `<h1 class="page-title">Tableau de bord</h1><p class="page-sub">Chargement…</p>`;
  const { data: shopsData } = await dbSelect('shops', { select: 'id,name,logo,owner_id,created_at,profiles(email)' });
  let html = `<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:6px;">
    <h1 class="page-title" style="margin:0;">Tableau de bord</h1>
    <button class="link-btn" onclick="logout()" style="color:var(--danger);">Déconnexion</button>
  </div>
  <p class="page-sub">Vue d'ensemble de toutes les boutiques.</p>`;
  html += `<button class="btn btn-oak btn-block" style="margin-bottom:16px;" onclick="openNewShop()">+ Créer une boutique</button>`;
  const list = shopsData || [];
  if(list.length===0){
    html += `<div class="empty"><div class="glyph">📊</div><p>Aucune boutique pour l'instant.</p></div>`;
  } else {
    for(const s of list){
      const { data: countRows } = await dbSelect('products', { shop_id: 'eq.'+s.id, select: 'id' });
      const count = countRows ? countRows.length : 0;
      html += `<div class="product-row" style="align-items:center;">
        <div class="thumb">${s.logo?`<img src="${s.logo}" alt="Logo ${escapeHtml(s.name||'')}">`:'<span aria-hidden="true">🥖</span>'}</div>
        <div class="row-body">
          <div class="row-name">${escapeHtml(s.name||'Boutique sans nom')}</div>
          <div class="row-cat">${escapeHtml(s.profiles ? s.profiles.email : '')}</div>
          <div class="row-opts">${count||0} produit(s) · créé le ${new Date(s.created_at).toLocaleDateString('fr-FR')}</div>
          <div class="row-actions">
            <button class="icon-btn" onclick="manageShop('${s.id}')">Gérer cette boutique</button>
            <button class="icon-btn" onclick="assignShop('${s.id}')">Attribuer à un artisan</button>
          </div>
        </div>
      </div>`;
    }
  }
  main.innerHTML = html;
}

/* ================= Artisan (pro) view ================= */
/* ================= Order polling / notifications ================= */
let orderPollInterval = null;
let lastKnownPendingCount = null;
function startOrderPolling(){
  stopOrderPolling();
  lastKnownPendingCount = orders.filter(o=>o.status==='nouvelle'||o.status==='en_attente').length + specialRequests.filter(r=>r.status!=='traitee').length;
  orderPollInterval = setInterval(pollOrders, 120000);
}
function stopOrderPolling(){ if(orderPollInterval){ clearInterval(orderPollInterval); orderPollInterval = null; } lastKnownPendingCount = null; }
async function pollOrders(){
  if(screen!=='pro' || !currentDataId) return;
  const { data } = await dbSelect('orders', { shop_id: 'eq.'+currentDataId, select: '*', order: 'created_at.desc' });
  const { data: srData } = await dbSelect('special_requests', { shop_id: 'eq.'+currentDataId, select: '*', order: 'created_at.desc' });
  if(!data) return;
  orders = data;
  specialRequests = srData || [];
  const pendingCount = orders.filter(o=>o.status==='nouvelle'||o.status==='en_attente').length + specialRequests.filter(r=>r.status!=='traitee').length;
  if(lastKnownPendingCount !== null && pendingCount > lastKnownPendingCount){
    notifyNewOrder(pendingCount - lastKnownPendingCount);
  }
  lastKnownPendingCount = pendingCount;
  const active = document.activeElement;
  const typing = !!(active && /^(INPUT|TEXTAREA|SELECT)$/.test(active.tagName));
  const sheetOpen = !!document.querySelector('.sheet-backdrop.open');
  if(proTab==='commandes' && !typing && !sheetOpen){ render(); } else { refreshProBadge(); }
}
function refreshProBadge(){
  const chip = document.getElementById('proTab-commandes');
  if(chip){
    const n = orders.filter(o=>o.status==='nouvelle'||o.status==='en_attente').length;
    chip.innerHTML = 'Commandes' + (n>0 ? `<span class="notif-badge">${n}</span>` : '');
  }
  const chipD = document.getElementById('proTab-devis');
  if(chipD){
    const d = specialRequests.filter(r => r.status !== 'traitee' && (r.last_from === 'client' || r.status === 'nouvelle')).length;
    chipD.innerHTML = 'Devis' + (d>0 ? `<span class="notif-badge">${d}</span>` : '');
  }
}
function enableNotifications(){
  try{
    const p = Notification.requestPermission(function(){ render(); });
    if(p && p.then) p.then(function(){ render(); });
  }catch(e){}
}
function notifyNewOrder(n){
  if(window.Notification && Notification.permission === 'granted'){
    try{
      const notif = new Notification(n>1 ? n+' nouvelles commandes' : 'Nouvelle commande', {
        body: shop && shop.name ? shop.name : 'La Place des Artisans',
        icon: shop && shop.logo ? shop.logo : undefined
      });
      notif.onclick = function(){ window.focus(); };
    }catch(e){}
  }
}

function renderProTopBarBase(){
  if(adminManaging){
    return `<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:16px;font-size:12px;color:var(--ink-soft);">
      <span>Gestion admin : ${escapeHtml(shop.name||'boutique sans nom')}</span>
      <span style="display:flex;gap:12px;">
        <button class="link-btn" onclick="previewOwnShop()">Voir la boutique</button>
        <button class="link-btn" onclick="backToAdminDash()" style="color:var(--danger);">← Tableau de bord</button>
      </span>
    </div>`;
  }
  return `<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:16px;font-size:12px;color:var(--ink-soft);">
    <span>Connecté : ${escapeHtml(currentUser?currentUser.email:'')}</span>
    <span style="display:flex;gap:12px;">
      <button class="link-btn" onclick="previewOwnShop()">Voir ma boutique</button>
      <button class="link-btn" onclick="logout()" style="color:var(--danger);">Déconnexion</button>
    </span>
  </div>`;
}
/* ================= Paiements en ligne (espace artisan) ================= */
let shopBilling = null, shopInvoices = [];
let connectInstance = null, connectNote = '', onboardingOpen = false, paymentsSyncedAt = 0, billingNote = '';

async function loadBilling(){
  shopBilling = null; shopInvoices = [];
  if(!currentUser || !currentDataId) return;
  const { data: b } = await dbSelect('shop_billing', { shop_id: 'eq.' + currentDataId, select: '*' });
  shopBilling = (b && b[0]) || null;
  if(shopBilling){
    const { data: inv } = await dbSelect('artisan_invoices', { shop_id: 'eq.' + currentDataId, select: '*', order: 'month.desc' });
    shopInvoices = inv || [];
  }
}
function payState(){
  if(shop && shop.stripe_charges_enabled) return 'active';
  return (shopBilling && shopBilling.stripe_account_id) ? 'pending' : 'none';
}
function billingReady(){ return !!(shopBilling && shopBilling.billing_ready); }
function renderProTopBar(){ return renderProTopBarBase() + paymentsBanner(); }
function paymentsBanner(){
  if(adminManaging) return '';
  const st = payState();
  if(st === 'active' && billingReady()) return '';
  let title, text, cta;
  if(st === 'none'){ title = 'Activez vos paiements en ligne'; text = "Vos clients versent un acompte à chaque commande, directement sur votre compte. Obligatoire pour publier une nouvelle boutique ; quelques minutes suffisent."; cta = 'Activer mes paiements'; }
  else if(st === 'pending'){ title = "Votre activation n'est pas terminée"; text = "Stripe attend encore quelques informations pour ouvrir vos paiements."; cta = "Terminer l'activation"; }
  else { title = 'Choisissez votre formule'; text = "Commission de 8 % sur les commandes récupérées, ou abonnement de 24,99 € par mois : indiquez comment vous réglez La Place des Artisans."; cta = 'Choisir ma formule'; }
  return `<div class="pay-banner"><div><strong>${title}</strong><br>${text}</div><button class="btn btn-oak" onclick="goToPayments()">${cta}</button></div>`;
}
function goToPayments(){
  closePayPopup();
  proTab = 'infos'; render();
  setTimeout(function(){ const el = document.getElementById('paymentsSection'); if(el) el.scrollIntoView({ behavior: 'smooth', block: 'start' }); }, 120);
}
function maybeShowPayPopup(){
  if(adminManaging || !currentUser || screen !== 'pro') return;
  if(payState() === 'active') return;
  try{ if(sessionStorage.getItem('lpda_pay_popup')) return; sessionStorage.setItem('lpda_pay_popup', '1'); }catch(e){}
  const el = document.getElementById('payPopupBackdrop'); if(el) el.classList.add('open');
}
function closePayPopup(){ const el = document.getElementById('payPopupBackdrop'); if(el) el.classList.remove('open'); }

function loadConnectJs(){
  return new Promise(function(resolve, reject){
    if(window.StripeConnect && window.StripeConnect.init) return resolve();
    window.StripeConnect = window.StripeConnect || {};
    window.StripeConnect.onLoad = resolve;
    if(document.getElementById('connectJsScript')) return;
    const sc = document.createElement('script');
    sc.id = 'connectJsScript'; sc.src = 'https://connect-js.stripe.com/v1.0/connect.js'; sc.async = true;
    sc.onerror = function(){ reject(new Error('Chargement de Stripe impossible')); };
    document.head.appendChild(sc);
  });
}
async function ensureConnect(){
  if(connectInstance) return connectInstance;
  const first = await callPaiements({ action: 'onboarding_session', shop_id: currentDataId }, true);
  if(!first.ok){
    connectNote = (first.data && first.data.code === 'cle_publique_manquante')
      ? "L'activation n'est pas encore disponible : la configuration Stripe du site est incomplète. Contactez l'administrateur."
      : ((first.data && first.data.error) || 'Activation indisponible pour le moment.');
    return null;
  }
  try{ await loadConnectJs(); }catch(e){ connectNote = e.message; return null; }
  let pending = first.data.client_secret;
  connectInstance = window.StripeConnect.init({
    publishableKey: first.data.publishable_key,
    fetchClientSecret: async function(){
      if(pending){ const v = pending; pending = null; return v; }
      const r = await callPaiements({ action: 'onboarding_session', shop_id: currentDataId }, true);
      return r.ok ? r.data.client_secret : undefined;
    },
    locale: 'fr-FR',
    appearance: { variables: { colorPrimary: '#c98d4a', colorBackground: '#ffffff', fontFamily: 'Work Sans, sans-serif', borderRadius: '9px' } }
  });
  await loadBilling();
  return connectInstance;
}
async function syncPayments(forceRender){
  const before = payState();
  const r = await callPaiements({ action: 'sync_status', shop_id: currentDataId }, true);
  if(r.ok) shop.stripe_charges_enabled = !!r.data.charges_enabled;
  await loadBilling();
  if(forceRender || payState() !== before) render();
}
function startOnboarding(){ connectNote = ''; onboardingOpen = true; render(); }
async function mountPaymentsUi(){
  if(proTab !== 'infos' || !currentUser || screen !== 'pro') return;
  const qs = new URLSearchParams(window.location.search).get('facturation');
  if(qs){
    billingNote = qs === 'ok' ? "Merci : votre moyen de règlement est en cours de validation chez Stripe, il sera actif dans un instant." : "Vous avez quitté la page de règlement : rien n'a été enregistré.";
    try{ history.replaceState(history.state, '', window.location.pathname); }catch(e){}
    if(qs === 'ok') setTimeout(async function(){ await loadBilling(); if(screen === 'pro' && proTab === 'infos') render(); }, 4500);
    render(); return;
  }
  const acct = shopBilling && shopBilling.stripe_account_id;
  if(acct && !(shop && shop.stripe_charges_enabled) && Date.now() - paymentsSyncedAt > 20000){ paymentsSyncedAt = Date.now(); syncPayments(); }
  const banner = document.getElementById('connectBanner'), onb = document.getElementById('connectOnboarding');
  if((!acct && !onboardingOpen) || (!banner && !onb)) return;
  const inst = await ensureConnect();
  if(!inst){ if(onb) onb.innerHTML = `<p class="auth-error">${escapeHtml(connectNote)}</p>`; return; }
  if(banner && !banner.hasChildNodes()) banner.appendChild(inst.create('notification-banner'));
  if(onb && !onb.hasChildNodes()){
    const comp = inst.create('account-onboarding');
    comp.setOnExit(async function(){ onboardingOpen = false; await syncPayments(true); });
    onb.appendChild(comp);
  }
}
async function openAccountManagement(){
  const box = document.getElementById('connectManage'); if(!box || box.hasChildNodes()) return;
  const inst = await ensureConnect();
  if(!inst){ box.innerHTML = `<p class="auth-error">${escapeHtml(connectNote)}</p>`; return; }
  box.appendChild(inst.create('account-management'));
}

function depositExampleText(mode, value){
  const total = 20;
  let d = mode === 'fixed' ? value : total * value / 100;
  if(!(d > 0)) return '';
  d = Math.max(0.5, Math.min(Math.round(d * 100) / 100, total));
  return `Exemple : pour une commande de ${fmtEuro(total)} €, l'acompte sera de ${fmtEuro(d)} € et le solde de ${fmtEuro(total - d)} € se règle au retrait.`;
}
function updateDepositExample(){
  const mode = (document.querySelector('input[name=depMode]:checked') || {}).value || 'percent';
  const v = parsePrice(val('depValue').trim());
  const u = document.getElementById('depUnit'); if(u) u.textContent = mode === 'fixed' ? '€' : '%';
  const ex = document.getElementById('depExample'); if(ex) ex.textContent = depositExampleText(mode, v);
}
async function saveDepositSettings(){
  const mode = (document.querySelector('input[name=depMode]:checked') || {}).value || 'percent';
  const raw = parsePrice(val('depValue').trim());
  const err = document.getElementById('depError');
  if(!(raw > 0)){ err.textContent = 'Indiquez une valeur supérieure à 0.'; return; }
  if(mode === 'percent' && (raw < 5 || raw > 100)){ err.textContent = 'Le pourcentage doit être compris entre 5 et 100.'; return; }
  if(mode === 'fixed' && raw < 0.5){ err.textContent = 'Le montant minimum est de 0,50 €.'; return; }
  const { error } = await dbUpdate('shops', { id: 'eq.' + currentDataId }, { deposit_mode: mode, deposit_value: raw });
  if(error){ err.textContent = error.message; return; }
  shop.deposit_mode = mode; shop.deposit_value = raw;
  err.style.color = '#2e6b32'; err.textContent = 'Enregistré.';
  setTimeout(function(){ if(err){ err.textContent = ''; err.style.color = ''; } }, 2500);
}
function depositSettingsHtml(){
  const mode = (shop && shop.deposit_mode) || 'percent';
  const v = (shop && shop.deposit_value != null) ? shop.deposit_value : 30;
  return `<div class="pay-card">
    <h3 class="pay-title">Acompte demandé à vos clients</h3>
    <p class="pay-sub">Demandé quand vous acceptez une commande d'au moins 1 €. Il est pour vous : La Place des Artisans n'en prélève rien.</p>
    <div class="pay-row">
      <label><input type="radio" name="depMode" value="percent" ${mode === 'percent' ? 'checked' : ''} onchange="updateDepositExample()"> Un pourcentage du total</label>
      <label><input type="radio" name="depMode" value="fixed" ${mode === 'fixed' ? 'checked' : ''} onchange="updateDepositExample()"> Un montant fixe</label>
    </div>
    <div class="pay-row"><input type="text" inputmode="decimal" id="depValue" value="${String(v).replace('.', ',')}" oninput="updateDepositExample()" style="max-width:110px;"><span id="depUnit">${mode === 'fixed' ? '€' : '%'}</span></div>
    <p id="depExample" class="pay-sub">${escapeHtml(depositExampleText(mode, Number(v)))}</p>
    <div class="auth-error" id="depError"></div>
    <button class="btn btn-oak" onclick="saveDepositSettings()">Enregistrer</button>
  </div>`;
}
async function savePlan(){
  const plan = (document.querySelector('input[name=planKind]:checked') || {}).value;
  const method = (document.querySelector('input[name=planMethod]:checked') || {}).value;
  const err = document.getElementById('planError');
  if(!plan || !method){ err.textContent = 'Choisissez une formule et un mode de règlement.'; return; }
  err.textContent = ''; err.style.color = '';
  const r = await callPaiements({ action: 'choose_plan', shop_id: currentDataId, plan, method }, true);
  if(!r.ok){ err.textContent = (r.data && r.data.error) || "Impossible d'enregistrer votre formule pour le moment."; return; }
  if(r.data.url){ window.location.href = r.data.url; return; }
  await loadBilling(); billingNote = 'Formule enregistrée.'; render();
}
function planSectionHtml(){
  const b = shopBilling || {};
  const plan = b.billing_plan || 'commission', method = b.billing_method || 'virement';
  const etat = b.billing_ready
    ? `<p class="pay-ok">Formule active : ${plan === 'abonnement' ? 'abonnement à 24,99 € par mois' : 'commission de 8 %'}, règlement par ${method === 'prelevement' ? 'prélèvement automatique' : 'virement'}.</p>`
    : `<p class="pay-sub" style="color:var(--oak-700);font-weight:600;">Votre formule n'est pas encore confirmée : enregistrez votre choix ci-dessous.</p>`;
  return `<div class="pay-card">
    <h3 class="pay-title">Votre formule</h3>
    <p class="pay-sub">Comment réglez-vous La Place des Artisans ? Vous pouvez changer à tout moment.</p>
    ${etat}
    ${billingNote ? `<p class="pay-ok">${escapeHtml(billingNote)}</p>` : ''}
    <label class="pay-opt"><input type="radio" name="planKind" value="commission" ${plan === 'commission' ? 'checked' : ''}><span><strong>Commission : 8 %</strong> du total des commandes récupérées dans le mois, facturée le 1er du mois suivant. Rien si aucune commande n'est récupérée.</span></label>
    <label class="pay-opt"><input type="radio" name="planKind" value="abonnement" ${plan === 'abonnement' ? 'checked' : ''}><span><strong>Abonnement : 24,99 € par mois</strong>, sans commission. Plus avantageux au-delà d'environ 312 € de commandes récupérées par mois.</span></label>
    <p class="pay-sub" style="margin-top:12px;">Mode de règlement</p>
    <label class="pay-opt"><input type="radio" name="planMethod" value="prelevement" ${method === 'prelevement' ? 'checked' : ''}><span><strong>Prélèvement automatique</strong> (SEPA) : rien à faire chaque mois.</span></label>
    <label class="pay-opt"><input type="radio" name="planMethod" value="virement" ${method === 'virement' ? 'checked' : ''}><span><strong>Virement</strong> sous 30 jours, à réception de la facture.</span></label>
    <div class="auth-error" id="planError"></div>
    <button class="btn btn-oak" onclick="savePlan()">Enregistrer ma formule</button>
  </div>`;
}
function invoicesHtml(){
  if(!shopInvoices.length) return '';
  const st = { emise: ['À régler', 'dep-wait'], payee: ['Payée', 'dep-ok'], impayee: ['Impayée', 'dep-late'], annulee: ['Annulée', 'dep-off'] };
  return `<div class="pay-card"><h3 class="pay-title">Vos factures</h3>
    ${shopInvoices.map(i => {
      const m = new Date(String(i.month).slice(0, 10) + 'T12:00:00').toLocaleDateString('fr-FR', { month: 'long', year: 'numeric' });
      const t = st[i.status] || [i.status, ''];
      return `<div class="log-line"><span style="font-weight:600;color:var(--ink);text-transform:capitalize;min-width:110px;">${escapeHtml(m)}</span><span style="flex:1;">${fmtEuro(Number(i.amount))} € · ${i.orders_count} commande(s)</span><span class="chip dep ${t[1]}">${t[0]}</span>${i.hosted_invoice_url ? `<a class="link-btn" target="_blank" rel="noopener" href="${escapeHtml(i.hosted_invoice_url)}">Voir</a>` : ''}</div>`;
    }).join('')}</div>`;
}
function paymentsSectionHtml(){
  const st = payState();
  const acct = shopBilling && shopBilling.stripe_account_id;
  let html = `<div id="paymentsSection" class="pay-card"><h3 class="pay-title">Paiements en ligne</h3><div id="connectBanner"></div>`;
  if(st === 'active'){
    html += `<p class="pay-ok">Paiements activés : vos clients règlent un acompte à chaque commande.</p>
      <div style="font-size:13px;"><a class="link-btn" target="_blank" rel="noopener" href="https://dashboard.stripe.com/${encodeURIComponent(acct || '')}">Ouvrir mon tableau de bord Stripe</a> · <button class="link-btn" onclick="openAccountManagement()">Gérer mes informations de paiement</button></div>
      <div id="connectManage" style="margin-top:10px;"></div>`;
  } else {
    html += `<p class="pay-sub">${st === 'pending' ? "Votre activation est en cours : Stripe attend encore quelques informations." : "Pour recevoir des acomptes et publier votre boutique, ouvrez votre compte de paiement. Gratuit, sécurisé par Stripe, quelques minutes."}</p>`;
    html += onboardingOpen ? `<div id="connectOnboarding"></div>` : `<button class="btn btn-oak" onclick="startOnboarding()">${st === 'pending' ? "Terminer l'activation" : 'Activer mes paiements'}</button>`;
    if(connectNote) html += `<p class="auth-error">${escapeHtml(connectNote)}</p>`;
  }
  html += `</div>` + depositSettingsHtml() + (acct ? planSectionHtml() : '') + invoicesHtml();
  return html;
}
let proTab = 'produits';
function setProTab(t){ proTab=t; render(); }
function renderProTabBar(){
  const pendingCount = orders.filter(o=>o.status==='nouvelle'||o.status==='en_attente').length;
  const devisCount = specialRequests.filter(r => r.status !== 'traitee' && (r.last_from === 'client' || r.status === 'nouvelle')).length;
  const tabs = [['produits','Produits'],['commandes','Commandes'],['devis','Devis'],['production','Production'],['infos','Infos boutique']];
  return `<div class="cat-scroll" style="margin-bottom:18px;">${tabs.map(([k,label])=>`<div class="cat-chip ${proTab===k?'active':''}" id="proTab-${k}" role="button" tabindex="0" style="position:relative;" onclick="setProTab('${k}')">${label}${(k==='commandes'&&pendingCount>0)?`<span class="notif-badge">${pendingCount}</span>`:''}${(k==='devis'&&devisCount>0)?`<span class="notif-badge">${devisCount}</span>`:''}</div>`).join('')}</div>`;
}

function renderArtisan(main){
  setTimeout(maybeShowPayPopup, 500);
  let fab = document.getElementById('fabWrap');
  const topBar = renderProTopBar();

  if(products.length===0 && proTab==='produits'){
    main.innerHTML = topBar + `
      <div class="onboarding">
        <div class="logo-picker" onclick="document.getElementById('onboardLogoInput').click()">
          ${shop.logo ? `<img src="${shop.logo}" alt="Logo de la boutique">` : `<span class="plus">+</span><span class="lbl">LOGO</span>`}
        </div>
        <input type="file" id="onboardLogoInput" accept="image/*" style="display:none">
        <input type="text" spellcheck="true" lang="fr" class="shopname-input" id="onboardNameInput" placeholder="Nom de votre boutique" value="${escapeHtml(shop.name||'')}">
        <label class="field-label" style="text-align:left;">Présentez votre boutique à vos clients</label>
        <p style="font-size:12px;color:var(--ink-soft);margin:-2px 0 8px;text-align:left;">Ex. « L'Arbre à Pain fait depuis 2013 du pain BIO au four à bois et au levain, avec des farines locales. Retrouvez nos produits en ligne et venez les récupérer sur place. »</p>
        <textarea spellcheck="true" lang="fr" id="onboardDescInput" placeholder="Votre histoire, vos valeurs, votre savoir-faire…" style="min-height:90px;">${escapeHtml(shop.description||'')}</textarea>
        <button type="button" class="link-btn" style="margin:6px 0 0;" onclick="checkGrammar('onboardDescInput','onboardDescGrammar')">Vérifier l'orthographe et la grammaire</button>
        <div id="onboardDescGrammar" style="margin-top:6px;text-align:left;"></div>
        <h1 class="page-title" style="margin-top:18px;">Bienvenue</h1>
        <p class="page-sub">Votre catalogue est vide. Créez votre première fiche produit pour commencer à vendre en click&amp;collect.</p>
        <div class="dashed-cta" onclick="openAdd()"><span class="plus">+</span>Créer votre première fiche produit</div>
      </div>`;
    document.getElementById('onboardLogoInput').addEventListener('change', async function(e){
      const file = e.target.files[0]; if(!file) return;
      shop.logo = await resizeImageFile(file, 400, 0.75);
      await saveShop(); render();
    });
    document.getElementById('onboardNameInput').addEventListener('change', async function(e){ await updateShopField('name', e.target.value); renderHeader(); });
    document.getElementById('onboardDescInput').addEventListener('change', async function(e){ shop.description = e.target.value.trim(); await saveShop(); });
    if(fab) fab.style.display='none';
    return;
  }

  const tabBar = renderProTabBar();
  if(proTab==='devis'){ if(fab) fab.style.display='none'; return renderProDevis(main, topBar, tabBar); }
  if(proTab==='production'){ if(fab) fab.style.display='none'; return renderProduction(main, topBar, tabBar); }
  if(proTab==='commandes'){ if(fab) fab.style.display='none'; return renderProOrders(main, topBar, tabBar); }
  if(proTab==='infos'){ if(fab) fab.style.display='none'; return renderProInfos(main, topBar, tabBar); }

  let html = topBar + tabBar + `<h1 class="page-title">Vos produits</h1>
  <p class="page-sub">Gérez vos fiches, vos catégories, vos tags et vos couleurs depuis les paramètres (⚙︎ en haut à droite).</p>
  ${products.some(p=>productImages(p)[0]) ? imagesToggleBtn() : ''}
  <div class="grid-wrap rows pro-products">`;
  products.forEach(p=>{
    html += `<div class="product-row">
      ${productImages(p)[0] ? `<div class="thumb"><img src="${productImages(p)[0]}" alt="${escapeHtml(p.nom)}"></div>` : (products.some(x=>productImages(x)[0]) ? `<div class="thumb empty" aria-hidden="true"></div>` : '')}
      <div class="row-body">
        <div class="row-top"><span class="row-name">${escapeHtml(p.nom)}</span><span class="row-price">${priceLabel(p)}</span></div>
        <div class="row-cat">${escapeHtml(p.categorie || 'Sans catégorie')}</div>
        ${(p.tags && p.tags.length) ? `<div class="chip-list">${p.tags.map(t=>`<span class="chip tag">${escapeHtml(t)}</span>`).join('')}</div>` : ''}
        ${(p.options && p.options.length) ? `<div class="row-opts">Options : ${p.options.map(normalizeOption).map(o=> o.price ? `${escapeHtml(o.label)} (+${fmtEuro(o.price)} €)` : escapeHtml(o.label)).join(', ')}</div>` : ''}
        <div class="row-actions">
          <button class="icon-btn" onclick="openEdit('${p.id}')">Modifier</button>
          <button class="icon-btn" onclick="deleteProduct('${p.id}')">Supprimer</button>
        </div>
      </div>
    </div>`;
  });
  html += `</div><div style="height:70px"></div>`;
  main.innerHTML = html;

  if(!fab){ fab = document.createElement('div'); fab.id='fabWrap'; fab.className='fab'; document.querySelector('.app').appendChild(fab); }
  fab.style.display='block';
  fab.innerHTML = `<button class="btn btn-primary btn-block" onclick="openAdd()">+ Ajouter un produit</button>`;
}

function renderProOrders(main, topBar, tabBar){
  let html = topBar + tabBar + `<h1 class="page-title">Commandes</h1>
  <p class="page-sub">Commandes passées par vos clients, les plus récentes en premier.</p>`;
  if(window.Notification && Notification.permission === 'default'){
    html += `<button type="button" class="btn btn-ghost btn-block" style="margin-bottom:16px;" onclick="enableNotifications()">🔔 Être prévenu des nouvelles commandes sur cet appareil</button>`;
  }
  const remises = orders.filter(o => o.picked_up_at)
                        .sort((a,b) => (a.picked_up_at < b.picked_up_at ? 1 : -1))
                        .slice(0, 12);
  html += `<div class="prod-head" style="margin-bottom:12px;">
    <div><strong>Comptoir</strong><br><span style="color:var(--ink-soft);font-size:12.5px;">${shop.counter_pin_set ? 'Code actif pour votre équipe' : 'Aucun code défini — voir Infos boutique'}</span></div>
    <button class="icon-btn" onclick="openComptoirAsArtisan()">Ouvrir le comptoir</button>
  </div>`;
  if(remises.length){
    html += `<div class="comptoir-log">${remises.map(o=>{
      const q = new Date(o.picked_up_at);
      const par = o.picked_up_via === 'artisan' ? 'vous' : 'comptoir';
      return `<div class="log-line"><span class="log-code">${escapeHtml(o.code)}</span><span>remise ${escapeHtml(q.toLocaleString('fr-FR', {weekday:'short', day:'numeric', month:'short', hour:'2-digit', minute:'2-digit'}))}</span><span class="log-par">${par}</span></div>`;
    }).join('')}</div>`;
  } else {
    html += `<p style="font-size:12.5px;color:var(--ink-soft);margin:-4px 0 16px;">Aucune remise enregistrée pour l'instant. Chaque commande marquée récupérée apparaîtra ici, avec son heure.</p>`;
  }

  if(orders.length===0){
    html += `<div class="empty"><div class="glyph">🧾</div><p>Aucune commande pour l'instant.</p></div>`;
  } else {
    html += `<div class="grid-wrap rows">`;
    orders.forEach(o=>{ html += orderRowHtml(o); });
    html += `</div>`;
  }
  main.innerHTML = html;
}
function depositBadge(o){
  const amt = fmtEuro(Number(o.deposit_amount) || 0) + ' €';
  if(o.status === 'annulee') return o.deposit_status === 'remboursee' ? '<span class="chip dep dep-off">Annulée — acompte remboursé</span>'
    : (o.deposit_status === 'payee' ? '<span class="chip dep dep-off">Annulée — acompte conservé</span>' : '<span class="chip dep dep-off">Annulée</span>');
  if(o.status === 'refusee') return '<span class="chip dep dep-off">Refusée</span>';
  switch(o.deposit_status){
    case 'lien_envoye': return (Date.parse(o.payment_expires_at || 0) > Date.now()) ? `<span class="chip dep dep-wait">Acompte ${amt} à régler</span>` : '<span class="chip dep dep-late">Lien expiré</span>';
    case 'expiree': return '<span class="chip dep dep-late">Lien expiré</span>';
    case 'payee': return `<span class="chip dep dep-ok">Acompte ${amt} payé</span>`;
    case 'remboursee': return '<span class="chip dep dep-off">Acompte remboursé</span>';
    case 'litige': return '<span class="chip dep dep-late">Litige en cours</span>';
    default: return (o.deposit_required && o.status === 'nouvelle') ? '<span class="chip dep dep-late">Lien d\'acompte à envoyer</span>' : '';
  }
}
function orderRowHtml(o){
  const done = o.status === 'recuperee', pending = o.status === 'en_attente', refused = o.status === 'refusee', cancelled = o.status === 'annulee';
  const ds = o.deposit_status || 'aucun';
  const open = !done && !refused && !cancelled;
  const linkLive = ds === 'lien_envoye' && !!o.payment_url && Date.parse(o.payment_expires_at || 0) > Date.now();
  const needsLink = open && !pending && (ds === 'expiree' || (ds === 'lien_envoye' && !linkLive) || (!!o.deposit_required && ds === 'aucun'));
  const canDelete = ['payee', 'lien_envoye', 'litige'].indexOf(ds) === -1;
  return `<div class="product-row" style="align-items:flex-start;${(done||refused||cancelled)?'opacity:.55;':''}${pending?`border-color:var(--oak-500);`:''}">
    <div class="thumb" style="font-size:13px;font-weight:700;">${escapeHtml(o.code)}</div>
    <div class="row-body">
      <div class="row-top"><span class="row-name">${o.items.reduce((s,i)=>s+i.qty,0)} article(s)${pending?' — à valider':''}</span><span class="row-price">${fmtEuro(o.total)} €</span></div>
      <div class="row-cat">${escapeHtml(o.slot||'')}${o.pickup_location?` · ${escapeHtml(o.pickup_location)}`:''} · ${new Date(o.created_at).toLocaleString('fr-FR')}${o.client_email?` · ${escapeHtml(o.client_email)}`:''}</div>
      <div class="row-opts">${o.items.map(it=>`${it.qty}× ${escapeHtml(it.nom)}${(it.options||[]).length?` (${it.options.map(escapeHtml).join(', ')})`:''}${it.needsValidation?' ⚠️':''}`).join(' — ')}</div>
      <div class="row-badges">${depositBadge(o)}${o.source === 'devis' ? '<span class="chip dep">Issue d\'un devis</span>' : ''}</div>
      <div class="row-actions">
        ${pending ? `<button class="icon-btn" onclick="respondToOrder('${o.id}','approve')">Valider</button><button class="icon-btn" style="color:var(--danger);" onclick="respondToOrder('${o.id}','refuse')">Refuser</button>` : ''}
        ${linkLive ? `<button class="icon-btn" onclick="copyPaymentLink('${o.id}')">Copier le lien de paiement</button>` : ''}
        ${(linkLive || needsLink) ? `<button class="icon-btn" onclick="resendDeposit('${o.id}')">${needsLink ? (ds === 'aucun' ? 'Envoyer le lien d\'acompte' : 'Envoyer un nouveau lien') : 'Renvoyer le lien'}</button>` : ''}
        ${(open && !pending) ? `<button class="icon-btn" onclick="markOrderDone('${o.id}')">Marquer récupérée</button>` : ''}
        ${(open && !pending) ? `<button class="icon-btn" style="color:var(--danger);" onclick="cancelOrderArtisan('${o.id}')">Annuler</button>` : ''}
        ${done ? `<span class="chip">✓ Récupérée</span>` : ''}
        ${canDelete ? `<button class="icon-btn" style="color:var(--danger);" onclick="deleteOrder('${o.id}')">Supprimer</button>` : ''}
      </div>
    </div>
  </div>`;
}
async function copyPaymentLink(id){
  const o = orders.find(x => x.id === id);
  if(!o || !o.payment_url) return;
  try{ await navigator.clipboard.writeText(o.payment_url); alert("Lien de paiement copié. Vous pouvez l'envoyer à votre client par SMS ou message : il le retrouve aussi sur sa page de suivi."); }
  catch(e){ prompt('Copiez ce lien de paiement :', o.payment_url); }
}
async function resendDeposit(id){
  const r = await callPaiements({ action: 'resend_deposit', order_id: id }, true);
  if(!r.ok){ alert("Le lien n'a pas pu être créé : " + ((r.data && r.data.error) || 'erreur inconnue')); }
  else if(r.data && r.data.deposit === false){ alert("Aucun acompte n'est nécessaire pour cette commande."); }
  else alert(r.data.emailed ? 'Nouveau lien créé et envoyé au client par e-mail.'
                            : "Nouveau lien créé. Le client n'a pas d'e-mail (ou il n'a pas pu être envoyé) : copiez le lien et envoyez-le-lui ; il le retrouve aussi sur sa page de suivi.");
  await loadShopFor(currentDataId); render();
}
async function cancelOrderArtisan(id){
  const o = orders.find(x => x.id === id); if(!o) return;
  const paid = o.deposit_status === 'payee';
  const msg = paid ? `Annuler cette commande ? L'acompte de ${fmtEuro(Number(o.deposit_amount) || 0)} € sera intégralement remboursé au client (les frais de transaction Stripe ne sont en général pas restitués) et le stock sera remis en vente.`
                   : 'Annuler cette commande ? Le client en sera informé et le stock sera remis en vente.';
  if(!confirm(msg)) return;
  const r = await callPaiements({ action: 'cancel_order', order_id: id }, true);
  if(!r.ok) alert('Annulation impossible : ' + ((r.data && r.data.error) || 'erreur inconnue'));
  await loadShopFor(currentDataId); render();
}
async function respondToOrder(orderId, action){
  if(action==='refuse' && !confirm("Refuser cette commande ? Le client en sera informé (par e-mail s'il en a donné un, sinon sur sa page de suivi) et le stock sera restitué.")) return;
  try{
    await ensureFreshSession();
    const res = await fetch(SB_URL + '/functions/v1/respond-to-order', {
      method: 'POST',
      headers: { apikey: SB_ANON_KEY, Authorization: 'Bearer ' + (sbSession && sbSession.access_token), 'Content-Type': 'application/json' },
      body: JSON.stringify({ order_id: orderId, action: action })
    });
    const data = await res.json().catch(()=>({}));
    if(!res.ok || !data.ok){ alert('Erreur : ' + (data.error||res.statusText)); return; }
    if(action === 'approve'){
      if(data.deposit_error) alert("Commande acceptée, mais le lien d'acompte n'a pas pu être créé : " + data.deposit_error + "\nUtilisez « Envoyer un nouveau lien » depuis la commande.");
      else if(data.deposit) alert(data.deposit.emailed ? "Commande acceptée. Le client a reçu le lien d'acompte par e-mail." : "Commande acceptée. Lien d'acompte créé : copiez-le depuis la commande pour l'envoyer à votre client ; il le retrouve aussi sur sa page de suivi.");
    }
  }catch(e){ alert('Erreur réseau.'); return; }
  await loadShopFor(currentDataId); render();
}
async function markOrderDone(orderId){
  const o = orders.find(x => x.id === orderId);
  if(o && (o.deposit_status === 'lien_envoye' || o.deposit_status === 'expiree') && !confirm("L'acompte de cette commande n'est pas réglé. La marquer quand même comme récupérée ?")) return;
  const { error } = await dbUpdate('orders', { id: 'eq.'+orderId }, { status: 'recuperee' });
  if(!error){ await loadShopFor(currentDataId); render(); }
}
async function deleteOrder(orderId){
  if(!confirm('Supprimer définitivement cette commande ? (annulation ou doublon)')) return;
  const { error } = await dbDelete('orders', { id: 'eq.'+orderId });
  if(error){ alert(error.message); return; }
  await loadShopFor(currentDataId); render();
}
let shopDevisId = null;
function openShopDevis(id){
  const r = specialRequests.find(x=>x.id===id);
  if(!r) return;
  shopDevisId = id;
  const unit = (r.unit && r.unit!=='unité') ? ' ' + r.unit : '';
  document.getElementById('shopDevisTitle').textContent = `Devis ${r.code || ''}`;
  document.getElementById('shopDevisIntro').innerHTML =
    `${escapeHtml(r.product_nom)} — <strong>${String(r.qty).replace('.',',')}${escapeHtml(unit)}</strong> · ${escapeHtml(DEVIS_STATUS[r.status] || r.status)}` +
    (r.message ? `<br><span style="color:var(--ink-soft);">Demande : ${escapeHtml(r.message)}</span>` : '');
  document.getElementById('shopDevisThread').innerHTML = threadHtml(r.messages, 'artisan');
  document.getElementById('shopDevisBody').value = '';
  document.getElementById('shopDevisQuote').value = (r.quoted_total!=null) ? fmtEuro(r.quoted_total) : '';
  document.getElementById('shopDevisDeposit').value = (r.deposit!=null) ? fmtEuro(r.deposit) : '';
  document.getElementById('shopDevisError').textContent = '';
  document.getElementById('shopDevisBackdrop').classList.add('open');
}
function closeShopDevis(){ document.getElementById('shopDevisBackdrop').classList.remove('open'); }
function setDepositPercent(pct){
  const quote = parsePrice(val('shopDevisQuote').trim());
  if(!quote || quote<=0){ document.getElementById('shopDevisError').textContent = 'Indiquez d\'abord le montant total.'; return; }
  document.getElementById('shopDevisError').textContent = '';
  document.getElementById('shopDevisDeposit').value = fmtEuro(Math.round(quote*pct)/100);
}
async function sendShopDevisReply(){
  const r = specialRequests.find(x=>x.id===shopDevisId);
  const errEl = document.getElementById('shopDevisError');
  if(!r) return;
  const body = val('shopDevisBody').trim();
  const quoteRaw = val('shopDevisQuote').trim();
  const quote = quoteRaw ? parsePrice(quoteRaw) : null;
  const depositRaw = val('shopDevisDeposit').trim();
  const deposit = depositRaw ? parsePrice(depositRaw) : null;
  if(!body && quote===null){ errEl.textContent = 'Écrivez un message ou indiquez un montant.'; return; }
  if(quoteRaw && (!quote || quote<=0)){ errEl.textContent = 'Montant invalide.'; return; }
  if(depositRaw && (!deposit || deposit<=0)){ errEl.textContent = 'Acompte invalide.'; return; }
  if(deposit!==null && quote===null){ errEl.textContent = 'Indiquez le montant total avant l\'acompte.'; return; }
  if(deposit!==null && deposit > quote){ errEl.textContent = 'L\'acompte ne peut pas dépasser le montant total.'; return; }
  const { error } = await dbRpc('devis_shop_reply', { p_code: r.code, p_body: body, p_quote: quote, p_deposit: deposit });
  if(error){ errEl.textContent = error.message; return; }
  closeShopDevis();
  await loadShopFor(currentDataId);
  render();
}
async function markRequestDone(id){
  const { error } = await dbUpdate('special_requests', { id: 'eq.'+id }, { status: 'traitee' });
  if(!error){ await loadShopFor(currentDataId); render(); }
}
async function deleteRequest(id){
  if(!confirm('Supprimer cette demande ?')) return;
  const { error } = await dbDelete('special_requests', { id: 'eq.'+id });
  if(error){ alert(error.message); return; }
  await loadShopFor(currentDataId); render();
}

const WEEKDAYS = [['lun','Lundi'],['mar','Mardi'],['mer','Mercredi'],['jeu','Jeudi'],['ven','Vendredi'],['sam','Samedi'],['dim','Dimanche']];
function normalizeDayHours(h){
  if(!h) return {ouvert:false, ranges:[{de:'08:00',a:'19:00'}]};
  if(h.ranges) return h;
  return {ouvert: !!h.ouvert, ranges:[{de:h.de||'08:00', a:h.a||'19:00'}]};
}
const METIERS = ['Boulangerie','Pâtisserie','Boucherie','Charcuterie','Fromagerie','Poissonnerie','Primeur','Épicerie fine','Traiteur','Fleuriste','Vigneron / Caviste','Brasserie','Confiserie','Autre'];
async function toggleMetier(m){
  shop.metiers = shop.metiers || [];
  const i = shop.metiers.indexOf(m);
  if(i>-1) shop.metiers.splice(i,1); else shop.metiers.push(m);
  await saveShop(); render();
}
let productionDate = null;
function orderPickupDate(o){
  if(o.pickup_date) return o.pickup_date;
  return null;
}
function renderProDevis(main, topBar, tabBar){
  let html = topBar + tabBar + `<h1 class="page-title">Devis</h1>
  <p class="page-sub">Les demandes de commande exceptionnelle de vos clients : gros volumes, pièces sur mesure, événements. Répondez dans la discussion, le client reçoit votre réponse dans l'application.</p>`;
  if(specialRequests.length>0){
    html += `<div class="grid-wrap rows">`;
    specialRequests.forEach(r=>{
      const done = r.status==='traitee';
      const waiting = r.last_from === 'client' || (r.status === 'nouvelle');
      const msgs = Array.isArray(r.messages) ? r.messages : [];
      const last = msgs.length ? msgs[msgs.length-1] : null;
      html += `<div class="product-row" style="align-items:flex-start;${done?'opacity:.55;':''}">
        <div class="thumb" style="font-size:20px;">${waiting && !done ? '💬' : '✉️'}</div>
        <div class="row-body">
          <div class="row-top"><span class="row-name">${escapeHtml(r.product_nom)}${waiting && !done ? ' — à répondre' : ''}</span><span class="row-price">${String(r.qty).replace('.',',')}${r.unit&&r.unit!=='unité'?' '+escapeHtml(r.unit):''}</span></div>
          <div class="row-cat">${escapeHtml(r.code || '')} · ${escapeHtml(DEVIS_STATUS[r.status] || r.status)}${r.quoted_total!=null?` · ${fmtEuro(r.quoted_total)} €`:''}${r.deposit!=null?` · acompte ${fmtEuro(r.deposit)} €`:''} · ${new Date(r.created_at).toLocaleString('fr-FR')}</div>
          ${r.message?`<div class="row-opts">Demande : ${escapeHtml(r.message)}</div>`:''}
          ${last?`<div class="row-opts">${last.from==='client'?'Client':'Vous'} : ${escapeHtml(String(last.body||'').slice(0,90))}</div>`:''}
          <div class="row-actions">
            <button class="icon-btn" onclick="openShopDevis('${r.id}')">Ouvrir la discussion</button>
            ${done ? `<span class="chip">✓ Clôturée</span>` : `<button class="icon-btn" onclick="markRequestDone('${r.id}')">Clôturer</button>`}
            <button class="icon-btn" style="color:var(--danger);" onclick="deleteRequest('${r.id}')">Supprimer</button>
          </div>
        </div>
      </div>`;
    });
    html += `</div><div style="height:10px"></div>`;
  }
  if(specialRequests.length===0){
    html += `<div class="empty"><p>Aucune demande pour l'instant.</p><p style="font-size:12.5px;margin-top:8px;">Un client qui demande une quantité supérieure à votre stock verra apparaître un lien pour vous demander un devis.</p></div>`;
  }
  main.innerHTML = html;
}
function renderProduction(main, topBar, tabBar){
  const today = ymd(new Date());
  const aProduire = o => (o.status === 'nouvelle' || o.status === 'recuperee') && !(o.status === 'nouvelle' && o.deposit_required && (!o.deposit_status || o.deposit_status === 'aucun'))
                         && (!o.deposit_status || o.deposit_status === 'aucun' || o.deposit_status === 'payee' || o.deposit_status === 'litige');
  const sansAcompte = o => o.status === 'nouvelle' && (o.deposit_status === 'lien_envoye' || o.deposit_status === 'expiree' || (o.deposit_required && (!o.deposit_status || o.deposit_status === 'aucun')));
  const retenues = orders.filter(o => aProduire(o) && orderPickupDate(o));
  const jours = [...new Set(orders.filter(o => (aProduire(o) || sansAcompte(o)) && orderPickupDate(o)).map(orderPickupDate))].filter(d => d >= today).sort();
  if(productionDate === null || jours.indexOf(productionDate) === -1) productionDate = jours[0] || today;

  let html = topBar + tabBar + `<h1 class="page-title">Feuille de production</h1>
  <p class="page-sub">Le total à préparer pour un jour de retrait donné, toutes commandes confondues.</p>`;

  if(jours.length === 0){
    html += `<div class="empty"><p>Aucune commande à préparer pour l'instant.</p></div>`;
    main.innerHTML = html;
    return;
  }

  html += `<div class="cat-scroll">${jours.map(d=>{
    const dd = new Date(d + 'T12:00:00');
    const lib = d === today ? "Aujourd'hui" : dd.toLocaleDateString('fr-FR', {weekday:'short', day:'numeric', month:'short'});
    return `<button class="cat-chip ${d===productionDate?'active':''}" onclick="setProductionDate('${d}')">${escapeHtml(lib)}</button>`;
  }).join('')}</div>`;

  const duJour = retenues.filter(o => orderPickupDate(o) === productionDate);
  const totaux = {};
  const enAttente = orders.filter(o => sansAcompte(o) && orderPickupDate(o) === productionDate).length;
  duJour.forEach(o=>{
    (o.items||[]).forEach(it=>{
      const cle = it.nom + '||' + (Array.isArray(it.options) ? it.options.map(x=>x.label||x).join(', ') : '');
      if(!totaux[cle]) totaux[cle] = { nom: it.nom, options: (Array.isArray(it.options) ? it.options.map(x=>x.label||x).join(', ') : ''), qty: 0, unit: '' };
      totaux[cle].qty += Number(it.qty) || 0;
      const p = products.find(pp=>pp.id===it.productId);
      if(p && p.unit && p.unit !== 'unité') totaux[cle].unit = p.unit;
    });
  });
  const lignes = Object.values(totaux).sort((a,b)=> a.nom.localeCompare(b.nom, 'fr'));
  const dd = new Date(productionDate + 'T12:00:00');
  const titreJour = dd.toLocaleDateString('fr-FR', {weekday:'long', day:'numeric', month:'long'});

  html += `<div class="prod-head">
    <div><strong>${escapeHtml(titreJour.charAt(0).toUpperCase() + titreJour.slice(1))}</strong><br>
    <span style="color:var(--ink-soft);font-size:12.5px;">${duJour.length} commande${duJour.length>1?'s':''}${enAttente ? ` · ${enAttente} en attente d'acompte (non comptée${enAttente > 1 ? 's' : ''})` : ''}</span></div>
    <button class="icon-btn" onclick="window.print()">Imprimer</button>
  </div>`;

  if(lignes.length === 0){
    html += `<div class="empty"><p>Rien à préparer pour ce jour.</p></div>`;
  } else {
    html += `<table class="prod-table"><tbody>`;
    lignes.forEach(l=>{
      const q = String(l.qty).replace('.', ',') + (l.unit ? ' ' + l.unit : '');
      html += `<tr><td class="q">${escapeHtml(q)}</td><td>${escapeHtml(l.nom)}${l.options?`<span class="opt">${escapeHtml(l.options)}</span>`:''}</td></tr>`;
    });
    html += `</tbody></table>`;
    if(enAttente) html += `<p style="font-size:12.5px;color:var(--oak-700);margin-top:10px;">Les commandes dont l'acompte n'est pas encore réglé ne sont pas comptées dans ce total.</p>`;
  }
  main.innerHTML = html;
}
function setProductionDate(d){ productionDate = d; render(); }
function renderProInfos(main, topBar, tabBar){
  const hours = shop.hours || {};
  let html = topBar + tabBar + `<h1 class="page-title">Infos boutique</h1>
  <p class="page-sub">Adresse, téléphone et horaires : visibles par vos clients, enregistrés automatiquement.</p>
  ${paymentsSectionHtml()}

  <div style="background:${shop.published?'rgba(47,89,64,0.08)':'var(--cream-2)'};border:1px solid var(--line);border-radius:10px;padding:12px 14px;margin-bottom:22px;">
    <h3 style="font-family:'Fraunces',serif;font-size:15px;font-weight:600;margin:0 0 6px;">Visibilité de la boutique</h3>
    ${shop.published ? `
      <p style="font-size:13px;color:var(--forest-700);margin:0 0 8px;">✓ Boutique publique — visible par les clients (SIRET ${escapeHtml(shop.siret||'')}).</p>
      ${shop.slug ? `<p style="font-size:12.5px;color:var(--ink-soft);margin:0 0 8px;">Votre adresse à partager : <strong>laplacedesartisans.fr/${escapeHtml(shop.slug)}</strong></p>` : ''}
      <button type="button" class="link-btn" style="color:var(--danger);" onclick="unpublishShop()">Rendre la boutique privée</button>
    ` : `
      <p style="font-size:13px;color:var(--ink-soft);margin:0 0 10px;">Votre boutique est privée : seuls vous et l'administrateur pouvez la voir. Vérifiez un numéro SIRET pour la rendre publique.${(shop && shop.stripe_charges_enabled) ? '' : ' Il faut aussi avoir activé vos paiements en ligne (section ci-dessus).'}</p>
      <div style="display:flex;gap:8px;">
        <input type="text" inputmode="numeric" id="shopSiretInput" placeholder="14 chiffres" maxlength="14" oninput="onShopSiretInput()">
        <button type="button" class="btn btn-ghost" onclick="verifyShopSiret()">Vérifier</button>
      </div>
      <div id="shopSiretResult" style="font-size:12.5px;margin:8px 0;line-height:1.5;"></div>
      <button type="button" class="btn btn-oak btn-block" onclick="publishShop()">Publier ma boutique</button>
      ${adminManaging ? `<button type="button" class="btn btn-ghost btn-block" style="margin-top:10px;border-color:var(--danger);color:var(--danger);" onclick="adminForcePublish()">Rendre visible sans SIRET (admin)</button>` : ''}
    `}
  </div>

  <label class="field-label">Votre métier</label>
  <p style="font-size:12px;color:var(--ink-soft);margin:-2px 0 8px;">Permet aux clients de vous trouver par filtre lors d'une recherche.</p>
  <div class="select-chip-list">${METIERS.map(m=>`<span class="select-chip ${(shop.metiers||[]).includes(m)?'tag-active':''}" onclick="toggleMetier('${m.replace(/'/g,"\\'")}')">${(shop.metiers||[]).includes(m)?'✓ ':''}${escapeHtml(m)}</span>`).join('')}</div>
  <label class="field-label">Présentation de la boutique</label>
  <p style="font-size:12px;color:var(--ink-soft);margin:-2px 0 8px;">Visible en avant sur votre page boutique, pour rassurer et promouvoir vos valeurs.</p>
  <textarea spellcheck="true" lang="fr" id="infoDescription" style="min-height:90px;" placeholder="Votre histoire, vos valeurs, votre savoir-faire…" onchange="updateShopField('description', this.value)">${escapeHtml(shop.description||'')}</textarea>
  <button type="button" class="link-btn" style="margin:6px 0 0;" onclick="checkGrammar('infoDescription','infoDescGrammar')">Vérifier l'orthographe et la grammaire</button>
  <div id="infoDescGrammar" style="margin-top:6px;"></div>
  <label class="field-label">Adresse</label>
  <input type="text" spellcheck="true" lang="fr" id="infoAdresse" value="${escapeHtml(shop.adresse||'')}" placeholder="12 rue des Artisans, 02240 Ribemont" onchange="updateShopField('adresse', this.value)">
  <label class="field-label">Téléphone</label>
  <input type="text" spellcheck="true" lang="fr" id="infoTelephone" value="${escapeHtml(shop.telephone||'')}" placeholder="03 23 XX XX XX" onchange="updateShopField('telephone', this.value)">
  <label class="field-label">Horaires d'ouverture</label>
  <p style="font-size:12.5px;color:var(--ink-soft);margin:-2px 0 8px;">Cochez un jour pour l'ouvrir. « + Ajouter une coupure » permet par exemple une fermeture le midi.</p>`;
  WEEKDAYS.forEach(([k,label])=>{
    const h = normalizeDayHours(hours[k]);
    html += `<div class="hours-row" style="flex-direction:column;align-items:stretch;">
      <div style="display:flex;justify-content:space-between;align-items:center;">
        <label class="day-toggle"><input type="checkbox" ${h.ouvert?'checked':''} onchange="toggleDayOpen('${k}', this.checked)">${label}</label>
        ${!h.ouvert?`<span class="closed-label">Fermé</span>`:''}
      </div>
      ${h.ouvert ? h.ranges.map((r,i)=>`
        <div class="hours-times" style="margin-top:6px;">
          <input type="time" value="${r.de}" onchange="updateRangeTime('${k}',${i},'de',this.value)">
          <span>–</span>
          <input type="time" value="${r.a}" onchange="updateRangeTime('${k}',${i},'a',this.value)">
          ${h.ranges.length>1 ? `<button class="icon-btn" onclick="removeRange('${k}',${i})">✕</button>` : ''}
        </div>`).join('') : ''}
      ${h.ouvert ? `<button class="link-btn" style="margin-top:6px;align-self:flex-start;" onclick="addRange('${k}')">+ Ajouter une coupure</button>` : ''}
    </div>`;
  });
  html += `<button class="link-btn" style="margin-top:14px;" onclick="copyMondayToAll()">Appliquer les horaires du lundi à toute la semaine</button>`;
  html += `<label class="field-label" for="infoCutoff" style="margin-top:28px;">Heure limite de commande</label>
  <p style="font-size:12.5px;color:var(--ink-soft);margin:-2px 0 8px;">Pour les produits qui demandent un délai de préparation : une commande passée après cette heure compte comme passée le lendemain. Exemple avec un délai d'1 jour et une heure limite à 12h00 : commandé lundi à 10h → retrait dès mardi ; commandé lundi à 19h → retrait dès mercredi. Les produits encore en stock ne sont pas concernés. Laissez vide pour ne pas appliquer d'heure limite.</p>
  <input type="time" id="infoCutoff" value="${escapeHtml(shop.order_cutoff||'')}" onchange="updateCutoff(this.value)" style="max-width:150px;">`;

  html += `<label class="field-label" style="margin-top:30px;">Accès comptoir</label>
  <p style="font-size:12.5px;color:var(--ink-soft);margin:-2px 0 10px;">Un code à 4 chiffres pour votre personnel de vente. Il donne accès uniquement aux commandes du jour : chercher un code de retrait et marquer une commande récupérée. Ni vos produits, ni vos réglages, ni votre compte.</p>
  <div style="display:flex;gap:8px;align-items:flex-start;">
    <input type="text" inputmode="numeric" id="counterPin" placeholder="${shop.counter_pin_set ? 'Code défini — saisir pour le remplacer' : 'Ex. 4821'}" style="flex:1;">
    <button class="btn btn-oak" style="padding:11px 14px;white-space:nowrap;" onclick="saveCounterPin()">Enregistrer</button>
  </div>
  <div class="auth-error" id="counterPinError"></div>
  ${shop.counter_pin_set ? `<p style="font-size:12.5px;color:var(--ink-soft);margin:6px 0 0;">Adresse à donner à votre équipe : <strong>laplacedesartisans.fr/comptoir</strong> — boutique « ${escapeHtml(shop.slug||'')} ».<br><span class="pro-link" style="display:inline;" onclick="removeCounterPin()">Désactiver l'accès comptoir</span></p>` : ''}

  <label class="field-label" style="margin-top:30px;">Fermetures exceptionnelles</label>
  <p style="font-size:12.5px;color:var(--ink-soft);margin:-2px 0 10px;">Congés, jour férié, imprévu : aucun retrait ne sera proposé sur ces dates, et vos clients verront le message.</p>`;
  const ferms = (shop.fermetures||[]).map(fermetureRange);
  ferms.forEach((f,i)=>{
    html += `<div class="product-row" style="align-items:flex-start;">
      <div class="row-body">
        <div class="row-top"><span class="row-name">Du ${escapeHtml(frDate(f.debut))} au ${escapeHtml(frDate(f.fin))}</span></div>
        ${f.message?`<div class="row-cat">${escapeHtml(f.message)}</div>`:''}
        <div class="row-actions"><button class="icon-btn" style="color:var(--danger);" onclick="deleteFermeture(${i})">Supprimer</button></div>
      </div>
    </div>`;
  });
  html += `<button class="link-btn" style="margin-top:6px;" onclick="openAddFermeture()">+ Ajouter une fermeture</button>

  <label class="field-label" style="margin-top:30px;">Points de retrait supplémentaires</label>
  <p style="font-size:12.5px;color:var(--ink-soft);margin:-2px 0 10px;">Par défaut, vos clients retirent leur commande à l'adresse de votre boutique. Si vous livrez aussi sur d'autres points (marché, point relais...), ajoutez-les ici — le client devra alors obligatoirement choisir son lieu de retrait.</p>`;
  (shop.pickupPoints||[]).forEach((pt,i)=>{
    html += `<div class="product-row" style="align-items:flex-start;">
      <div class="thumb" style="font-size:18px;">📍</div>
      <div class="row-body">
        <div class="row-top"><span class="row-name">${escapeHtml(pt.label)}</span></div>
        <div class="row-cat">${escapeHtml(pt.address)}</div>
        <div class="row-opts">${pt.hoursMode==='custom' ? 'Horaires spécifiques' : 'Horaires de la boutique'}${pt.leadDays>0 ? ` · Délai ${pt.leadDays} jour${pt.leadDays>1?'s':''}` : ' · Aucun délai propre'}</div>
        <div class="row-actions">
          <button class="icon-btn" onclick="openEditPickupPoint(${i})">Modifier</button>
          <button class="icon-btn" style="color:var(--danger);" onclick="deletePickupPoint(${i})">Supprimer</button>
        </div>
      </div>
    </div>`;
  });
  html += `<button class="link-btn" style="margin-top:6px;" onclick="openAddPickupPoint()">+ Ajouter un point de retrait</button>`;

  const report = shop.emailReport || {enabled:false, hour:'08:00'};
  html += `
  <label class="field-label" style="margin-top:26px;">Récapitulatif quotidien par e-mail</label>
  <p style="font-size:12.5px;color:var(--ink-soft);margin:-2px 0 8px;">Recevez chaque jour, à l'heure choisie, un résumé des commandes de la veille.</p>
  <div class="hours-row" style="border-top:none;padding-top:0;">
    <label class="day-toggle"><input type="checkbox" ${report.enabled?'checked':''} onchange="toggleEmailReport(this.checked)">Activer</label>
    <input type="time" value="${report.hour}" ${report.enabled?'':'disabled'} onchange="updateEmailReportHour(this.value)" style="max-width:110px;">
  </div>
  <label class="field-label">E-mail de réception</label>
  <input type="email" id="infoNotifyEmail" value="${escapeHtml(report.notifyEmail||'')}" placeholder="${escapeHtml(currentUser?currentUser.email:'')} (par défaut)" onchange="updateNotifyEmail(this.value)">
  <p style="font-size:12px;color:var(--ink-soft);margin:4px 0 0;">Laissez vide pour utiliser l'e-mail de votre compte.</p>

  <div style="margin-top:30px;border-top:1px solid var(--line);padding-top:18px;">
    <h3 style="font-family:'Fraunces',serif;font-size:16px;font-weight:600;margin:0 0 10px;">Vos données</h3>
    <button class="btn btn-ghost btn-block" onclick="exportMyData()">Exporter mes données (JSON)</button>
    <p style="font-size:11.5px;color:var(--ink-soft);margin:6px 0 0;">Télécharge une copie de votre compte, votre boutique, vos produits et vos commandes.</p>
  </div>

  <div style="margin-top:34px;border-top:1px solid var(--line);padding-top:18px;">
    <h3 style="font-family:'Fraunces',serif;font-size:16px;font-weight:600;margin:0 0 10px;color:var(--danger);">Zone dangereuse</h3>
    <button class="btn btn-ghost btn-block" style="border-color:var(--danger);color:var(--danger);" onclick="deleteShop()">Supprimer cette boutique</button>
    <p style="font-size:11.5px;color:var(--ink-soft);margin:6px 0 16px;">Produits, commandes et réglages seront définitivement effacés.${adminManaging?' Le compte artisan restera actif.':''}</p>
    ${!adminManaging ? `
    <button class="btn btn-ghost btn-block" style="border-color:var(--danger);color:var(--danger);" onclick="deleteMyAccount()">Supprimer mon compte</button>
    <p style="font-size:11.5px;color:var(--ink-soft);margin:6px 0 0;">Supprime définitivement votre compte, votre boutique et toutes ses données. Action irréversible.</p>` : ''}
  </div>
  <div style="height:30px"></div>`;
  main.innerHTML = html;
  setTimeout(mountPaymentsUi, 0);
}
async function exportMyData(){
  const { data: profileRows } = await dbSelect('profiles', { id: 'eq.'+currentUser.id, select: '*' });
  const profile = (profileRows && profileRows[0]) || {};
  const exportObj = {
    exporte_le: new Date().toISOString(),
    compte: { email: profile.email, siret: profile.siret, role: profile.role, cree_le: profile.created_at },
    boutique: shop,
    produits: products,
    commandes: orders
  };
  const blob = new Blob([JSON.stringify(exportObj, null, 2)], {type:'application/json'});
  const url = URL.createObjectURL(blob);
  const a = document.createElement('a');
  a.href = url; a.download = 'mes-donnees-la-place-des-artisans.json';
  a.click();
  URL.revokeObjectURL(url);
}
async function deleteShop(){
  if(!confirm('Supprimer définitivement cette boutique (produits, commandes, réglages) ? Cette action est irréversible.')) return;
  const ownerId = shop.owner_id;
  const oldId = currentDataId;
  const { error } = await dbDelete('shops', { id: 'eq.'+oldId });
  if(error){ alert(error.message); return; }
  await dbInsert('shops', { owner_id: ownerId, name:'', colors:{}, hours:{}, categories:[], tags:[] });
  const { data: rows } = await dbSelect('shops', { owner_id: 'eq.'+ownerId, select: 'id' });
  const newId = rows && rows[0] && rows[0].id;
  if(newId) await loadShopFor(newId);
  proTab = 'produits';
  render();
}
async function deleteMyAccount(){
  const enCours = orders.filter(o => ['recuperee', 'annulee', 'refusee'].indexOf(o.status) === -1).length;
  if(enCours){
    alert("Votre compte ne peut pas être supprimé pour l'instant : " + enCours + " commande(s) en cours. Terminez ou annulez-les, puis réessayez.");
    return;
  }
  if(shopInvoices.length){
    alert("Des factures vous ont été émises : la loi impose de les conserver dix ans, la suppression en ligne n'est donc pas possible. Écrivez à laplacedesartisans02@gmail.com pour clôturer votre compte.");
    return;
  }
  if(!confirm('Supprimer définitivement votre compte, votre boutique, vos produits et vos commandes ? Cette action est IRRÉVERSIBLE.')) return;
  if(!confirm('Dernière confirmation : voulez-vous vraiment tout supprimer ?')) return;
  try{
    await ensureFreshSession();
    const res = await fetch(SB_URL + '/functions/v1/delete-account', {
      method: 'POST',
      headers: { apikey: SB_ANON_KEY, Authorization: 'Bearer ' + (sbSession && sbSession.access_token) }
    });
    const data = await res.json().catch(()=>({}));
    if(!res.ok || !data.ok){ alert('Erreur lors de la suppression : ' + (data.error||res.statusText)); return; }
  }catch(e){ alert('Erreur réseau lors de la suppression.'); return; }
  stopOrderPolling();
  saveStoredSession(null);
  currentUser = null; currentRole = null; currentDataId = null; adminManaging = false;
  screen = 'directory';
  render();
  alert('Votre compte a été supprimé.');
}
async function updateNotifyEmail(value){
  shop.emailReport = shop.emailReport || {enabled:false, hour:'08:00'};
  shop.emailReport.notifyEmail = value.trim();
  await saveShop();
}
async function toggleEmailReport(checked){
  shop.emailReport = shop.emailReport || {enabled:false, hour:'08:00'};
  shop.emailReport.enabled = checked;
  await saveShop(); render();
}
async function updateEmailReportHour(value){
  shop.emailReport = shop.emailReport || {enabled:true, hour:'08:00'};
  shop.emailReport.hour = value;
  await saveShop();
}
async function updateCutoff(value){ shop.order_cutoff = value || null; await saveShop(); }
async function updateShopField(field, value){
  shop[field] = value.trim();
  if(field==='name'){
    shop.slug = await ensureUniqueSlug(slugify(shop.name), currentDataId);
  }
  if(field==='adresse'){
    const geo = await geocodeAddress(shop.adresse);
    shop.lat = geo ? geo.lat : null;
    shop.lng = geo ? geo.lng : null;
    shop.addr_street = geo ? geo.street : null;
    shop.addr_postcode = geo ? geo.postcode : null;
    shop.addr_city = geo ? geo.city : null;
  }
  await saveShop();
}
async function toggleDayOpen(day, checked){
  shop.hours = shop.hours || {};
  const cur = normalizeDayHours(shop.hours[day]);
  cur.ouvert = checked;
  shop.hours[day] = cur;
  await saveShop(); render();
}
async function updateRangeTime(day, idx, field, value){
  shop.hours = shop.hours || {};
  const cur = normalizeDayHours(shop.hours[day]);
  cur.ranges[idx][field] = value;
  shop.hours[day] = cur;
  await saveShop();
}
async function addRange(day){
  shop.hours = shop.hours || {};
  const cur = normalizeDayHours(shop.hours[day]);
  const last = cur.ranges[cur.ranges.length-1];
  cur.ranges.push({ de: (last && last.a) || '14:00', a: '19:00' });
  shop.hours[day] = cur;
  await saveShop(); render();
}
async function removeRange(day, idx){
  shop.hours = shop.hours || {};
  const cur = normalizeDayHours(shop.hours[day]);
  cur.ranges.splice(idx, 1);
  shop.hours[day] = cur;
  await saveShop(); render();
}
async function copyMondayToAll(){
  const mon = normalizeDayHours(shop.hours && shop.hours['lun']);
  shop.hours = shop.hours || {};
  WEEKDAYS.forEach(([k])=>{ shop.hours[k] = { ouvert: mon.ouvert, ranges: mon.ranges.map(r=>({de:r.de, a:r.a})) }; });
  await saveShop(); render();
}
function todayHoursLabel(hours){
  if(!hours) return '';
  const days = ['dim','lun','mar','mer','jeu','ven','sam'];
  const h = normalizeDayHours(hours[days[new Date().getDay()]]);
  if(!h.ouvert) return 'Fermé aujourd\'hui';
  return `Aujourd'hui : ${h.ranges.map(r=>`${r.de} – ${r.a}`).join(', ')}`;
}

/* ---------------- Settings ---------------- */
function openSettings(){
  document.getElementById('shopNameInput').value = shop.name || '';
  document.getElementById('settingsLogoPreview').innerHTML = shop.logo ? `<img src="${shop.logo}" alt="Logo de la boutique" style="width:100%;height:100%;object-fit:cover;border-radius:50%;">` : '🥖';
  document.getElementById('colorFond').value = (shop.colors&&shop.colors.fond) || '#f6f1e6';
  document.getElementById('colorBandeau').value = (shop.colors&&shop.colors.bandeau) || '#15301f';
  document.getElementById('colorIcone').value = (shop.colors&&shop.colors.icone) || '#c98d4a';
  document.getElementById('colorTexte').value = (shop.colors&&shop.colors.texte) || '#211b12';
  document.getElementById('colorFondHex').value = document.getElementById('colorFond').value;
  document.getElementById('colorBandeauHex').value = document.getElementById('colorBandeau').value;
  document.getElementById('colorIconeHex').value = document.getElementById('colorIcone').value;
  document.getElementById('colorTexteHex').value = document.getElementById('colorTexte').value;
  renderCatManageList();
  updateColorWarnings();
  renderTagManageList();
  document.getElementById('settingsBackdrop').classList.add('open');
}
async function closeSettings(){
  shop.name = document.getElementById('shopNameInput').value.trim();
  await saveShop();
  document.getElementById('settingsBackdrop').classList.remove('open');
  render();
}
document.getElementById('shopLogoInput').addEventListener('change', async function(e){
  const file = e.target.files[0]; if(!file) return;
  shop.logo = await resizeImageFile(file, 400, 0.75); await saveShop();
  document.getElementById('settingsLogoPreview').innerHTML = `<img src="${shop.logo}" alt="Logo de la boutique" style="width:100%;height:100%;object-fit:cover;border-radius:50%;">`;
  renderHeader();
});
function relLuminance(hex){
  hex = String(hex||'#000000').replace('#','');
  if(hex.length===3) hex = hex.split('').map(c=>c+c).join('');
  const r = parseInt(hex.substring(0,2),16)/255;
  const g = parseInt(hex.substring(2,4),16)/255;
  const b = parseInt(hex.substring(4,6),16)/255;
  const lin = (c)=> c<=0.03928 ? c/12.92 : Math.pow((c+0.055)/1.055, 2.4);
  return 0.2126*lin(r) + 0.7152*lin(g) + 0.0722*lin(b);
}
function contrastRatio(hex1, hex2){
  const l1 = relLuminance(hex1), l2 = relLuminance(hex2);
  const lighter = Math.max(l1,l2), darker = Math.min(l1,l2);
  return (lighter+0.05)/(darker+0.05);
}
function updateColorWarnings(){
  const el = document.getElementById('colorWarning');
  if(!el) return;
  const fond = val('colorFond'), texte = val('colorTexte'), bandeau = val('colorBandeau');
  const warnings = [];
  if(contrastRatio(texte, fond) < 4.5){
    warnings.push('⚠️ Le texte risque d\'être difficile à lire sur ce fond.');
  }
  if(contrastRatio('#e6bd85', bandeau) < 3){
    warnings.push('⚠️ Le nom de la boutique (en haut) risque d\'être peu lisible sur ce bandeau.');
  }
  if(contrastRatio('#f6f1e6', bandeau) < 2.2){
    warnings.push('⚠️ Le sous-titre du bandeau risque d\'être peu lisible.');
  }
  el.innerHTML = warnings.length
    ? `<div style="background:rgba(164,64,46,0.1);border:1px solid rgba(164,64,46,0.3);border-radius:10px;padding:10px 12px;margin:10px 0;font-size:12.5px;color:var(--danger);line-height:1.5;">${warnings.join('<br>')}</div>`
    : `<div style="font-size:12px;color:var(--forest-700);margin:10px 0;">✓ Bon contraste, tout est lisible.</div>`;
}
function syncColorFromPicker(key){
  document.getElementById('color'+key+'Hex').value = document.getElementById('color'+key).value;
  updateColorLive();
}
function syncColorFromHex(key){
  const hexInput = document.getElementById('color'+key+'Hex');
  let v = hexInput.value.trim();
  if(v && !v.startsWith('#')) v = '#'+v;
  if(/^#[0-9A-Fa-f]{6}$/.test(v)){
    document.getElementById('color'+key).value = v;
    updateColorLive();
  }
}
async function updateColorLive(){
  shop.colors = shop.colors || {};
  shop.colors.fond = val('colorFond');
  shop.colors.bandeau = val('colorBandeau');
  shop.colors.icone = val('colorIcone');
  shop.colors.texte = val('colorTexte');
  applyColors();
  updateColorWarnings();
  await saveShop();
}
async function resetColors(){
  shop.colors = {fond:'#f6f1e6', bandeau:'#15301f', icone:'#c98d4a', texte:'#211b12'};
  document.getElementById('colorFond').value = shop.colors.fond;
  document.getElementById('colorBandeau').value = shop.colors.bandeau;
  document.getElementById('colorIcone').value = shop.colors.icone;
  document.getElementById('colorTexte').value = shop.colors.texte;
  document.getElementById('colorFondHex').value = shop.colors.fond;
  document.getElementById('colorBandeauHex').value = shop.colors.bandeau;
  document.getElementById('colorIconeHex').value = shop.colors.icone;
  document.getElementById('colorTexteHex').value = shop.colors.texte;
  applyColors(); updateColorWarnings(); await saveShop();
}
function renderCatManageList(){
  const el = document.getElementById('catManageList');
  if(categories.length===0){ el.innerHTML = `<p class="hint" style="margin:0;">Aucune catégorie pour l'instant.</p>`; return; }
  el.innerHTML = categories.map((c,i)=>`<div class="manage-chip">${escapeHtml(c)}<button onclick="deleteCategory(${i})">✕</button></div>`).join('');
}
async function addCategory(){
  const inp = document.getElementById('newCatInput'); const v = inp.value.trim();
  if(!v || categories.includes(v)){ inp.value=''; return; }
  categories.push(v); await saveShop(); inp.value=''; renderCatManageList();
}
async function deleteCategory(i){
  if(!confirm(`Supprimer la catégorie "${categories[i]}" ?`)) return;
  categories.splice(i,1); await saveShop(); renderCatManageList();
}
function renderTagManageList(){
  const el = document.getElementById('tagManageList');
  if(tags.length===0){ el.innerHTML = `<p class="hint" style="margin:0;">Aucun tag pour l'instant.</p>`; return; }
  el.innerHTML = tags.map((t,i)=>`<div class="manage-chip">${escapeHtml(t)}<button onclick="deleteTag(${i})">✕</button></div>`).join('');
}
async function addTag(){
  const inp = document.getElementById('newTagInput'); const v = inp.value.trim();
  if(!v || tags.includes(v)){ inp.value=''; return; }
  tags.push(v); await saveShop(); inp.value=''; renderTagManageList();
}
async function deleteTag(i){
  if(!confirm(`Supprimer le tag "${tags[i]}" ?`)) return;
  tags.splice(i,1); await saveShop(); renderTagManageList();
}

/* ---------------- Product sheet ---------------- */
let editingId = null, draftOptions = [], draftCategory = null, draftTags = [], draftConflicts = [], draftImages = [], draftAllergenes = [];
function renderAllergenesDraft(){
  const el = document.getElementById('fAllergenes');
  if(!el) return;
  el.innerHTML = ALLERGENES.map(([code,label])=>
    `<label><input type="checkbox" ${draftAllergenes.includes(code)?'checked':''} onchange="toggleAllergene('${code}', this.checked)"> ${escapeHtml(label)}</label>`).join('');
  const none = document.getElementById('fNoAllerg');
  if(none) none.checked = (draftAllergenes.length === 0);
}
function toggleAllergene(code, on){
  const i = draftAllergenes.indexOf(code);
  if(on && i === -1) draftAllergenes.push(code);
  if(!on && i > -1) draftAllergenes.splice(i,1);
  renderAllergenesDraft();
}
function setNoAllergenes(on){
  if(on) draftAllergenes = [];
  renderAllergenesDraft();
}
function openAdd(){
  editingId = null; draftOptions = []; draftCategory = categories[0] || null; draftTags = []; draftConflicts = []; draftImages = []; draftAllergenes = [];
  document.getElementById('sheetTitle').textContent = 'Créer une fiche produit';
  document.getElementById('fNom').value=''; document.getElementById('fDesc').value=''; document.getElementById('fPrix').value=''; document.getElementById('fLeadDays').value='0'; document.getElementById('fUnit').value='unité'; document.getElementById('fMaxQty').value=''; document.getElementById('fStock').value='';
  document.getElementById('fPoids').value = ''; document.getElementById('fPortions').value = '';
  renderImagesDraft(); renderAllergenesDraft();
  renderOptDraft(); renderCatDraft(); renderTagDraft(); renderConflictDraft();
  document.getElementById('sheetBackdrop').classList.add('open');
}
function openEdit(id){
  const p = products.find(x=>x.id===id); if(!p) return;
  editingId = id; draftOptions = (p.options||[]).map(normalizeOption); draftCategory = p.categorie || (categories[0]||null); draftTags = [...(p.tags||[])]; draftConflicts = (p.conflicts||[]).map(pair=>[...pair]); draftImages = productImages(p).slice(0,3);
  document.getElementById('sheetTitle').textContent = 'Modifier la fiche produit';
  document.getElementById('fNom').value = p.nom; document.getElementById('fDesc').value = p.description || ''; document.getElementById('fPrix').value = fmtEuro(p.prix); document.getElementById('fLeadDays').value = String(p.leadDays||0); document.getElementById('fUnit').value = p.unit||'unité';
  document.getElementById('fMaxQty').value = (p.maxQty===null||p.maxQty===undefined) ? '' : String(p.maxQty).replace('.',',');
  document.getElementById('fStock').value = (p.stock===null||p.stock===undefined) ? '' : String(p.stock).replace('.',',');
  draftAllergenes = [...(p.allergenes||[])];
  document.getElementById('fPoids').value = (p.poidsG===null||p.poidsG===undefined) ? '' : String(p.poidsG);
  document.getElementById('fPortions').value = (p.portions===null||p.portions===undefined) ? '' : String(p.portions);
  renderImagesDraft(); renderAllergenesDraft();
  renderOptDraft(); renderCatDraft(); renderTagDraft(); renderConflictDraft();
  document.getElementById('sheetBackdrop').classList.add('open');
}
function closeSheet(){ document.getElementById('sheetBackdrop').classList.remove('open'); }
function renderImagesDraft(){
  let html = draftImages.map((img,i)=>`<div class="img-slot"><img src="${img}" alt="Photo ${i+1}"><button class="img-remove" onclick="removeDraftImage(${i})">✕</button></div>`).join('');
  if(draftImages.length<3){ html += `<div class="img-slot add" onclick="document.getElementById('fImageInput').click()">+</div>`; }
  document.getElementById('fImagesRow').innerHTML = html;
}
function removeDraftImage(i){ draftImages.splice(i,1); renderImagesDraft(); }
document.getElementById('fImageInput').addEventListener('change', async function(e){
  const file = e.target.files[0]; if(!file) return;
  if(draftImages.length < 3){
    const dataUrl = await resizeImageFile(file, 900, 0.72);
    draftImages.push(dataUrl);
    renderImagesDraft();
  }
  e.target.value = '';
});

function renderCatDraft(){
  document.getElementById('fCatChips').innerHTML = categories.length===0
    ? `<p style="font-size:12px;color:var(--ink-soft);margin:0;">Aucune catégorie pour l'instant — créez-en une ci-dessous.</p>`
    : categories.map(c=>
        `<span class="select-chip ${draftCategory===c?'active':''}" onclick="pickCategory('${c.replace(/'/g,"\\'")}')">${draftCategory===c?'✓ ':''}${escapeHtml(c)}</span>`
      ).join('');
}
function pickCategory(c){ draftCategory=c; renderCatDraft(); }
async function confirmNewCatDraft(){
  const inp = document.getElementById('fCatNewInput'); const v = inp.value.trim(); if(!v) return;
  if(!categories.includes(v)){ categories.push(v); await saveShop(); }
  draftCategory=v; inp.value=''; renderCatDraft(); inp.focus();
}
function renderTagDraft(){
  document.getElementById('fTagChips').innerHTML = tags.length===0
    ? `<p style="font-size:12px;color:var(--ink-soft);margin:0;">Aucun tag pour l'instant — créez-en un ci-dessous.</p>`
    : tags.map(t=>
        `<span class="select-chip ${draftTags.includes(t)?'tag-active':''}" onclick="toggleTagDraft('${t.replace(/'/g,"\\'")}')">${draftTags.includes(t)?'✓ ':''}${escapeHtml(t)}</span>`
      ).join('');
}
function toggleTagDraft(t){ const i=draftTags.indexOf(t); if(i>-1) draftTags.splice(i,1); else draftTags.push(t); renderTagDraft(); }
async function confirmNewTagDraft(){
  const inp = document.getElementById('fTagNewInput'); const v = inp.value.trim(); if(!v) return;
  if(!tags.includes(v)){ tags.push(v); await saveShop(); }
  if(!draftTags.includes(v)) draftTags.push(v);
  inp.value=''; renderTagDraft(); inp.focus();
}
function addOptionDraft(){
  const inp = document.getElementById('fOptInput'); const label = inp.value.trim(); if(!label) return;
  const priceInp = document.getElementById('fOptPriceInput'); const price = parsePrice(priceInp.value);
  draftOptions.push({label, price}); inp.value=''; priceInp.value=''; renderOptDraft(); inp.focus();
}
function removeOptionDraft(idx){ draftOptions.splice(idx,1); renderOptDraft(); }
function renderOptDraft(){
  document.getElementById('fOptList').innerHTML = draftOptions.length===0
    ? `<p style="font-size:12px;color:var(--ink-soft);margin:8px 0 0;">Aucune option ajoutée pour l'instant.</p>`
    : draftOptions.map((o,i)=>`<span class="chip">✓ ${escapeHtml(o.label)}${o.price?` (+${fmtEuro(o.price)} €)`:' (gratuit)'} <button onclick="removeOptionDraft(${i})">✕</button></span>`).join('');
  populateConflictSelects();
}
function populateConflictSelects(){
  const optionsHtml = draftOptions.map(o=>`<option value="${escapeHtml(o.label)}">${escapeHtml(o.label)}</option>`).join('');
  const selA = document.getElementById('fConflictA'), selB = document.getElementById('fConflictB');
  if(!selA || !selB) return;
  const prevA = selA.value, prevB = selB.value;
  selA.innerHTML = `<option value="">Option A…</option>` + optionsHtml;
  selB.innerHTML = `<option value="">Option B…</option>` + optionsHtml;
  if(draftOptions.some(o=>o.label===prevA)) selA.value = prevA;
  if(draftOptions.some(o=>o.label===prevB)) selB.value = prevB;
}
function addConflictDraft(){
  const a = val('fConflictA'), b = val('fConflictB');
  if(!a || !b || a===b) return;
  const exists = draftConflicts.some(pair => (pair[0]===a&&pair[1]===b) || (pair[0]===b&&pair[1]===a));
  if(exists) return;
  draftConflicts.push([a,b]);
  renderConflictDraft();
}
function removeConflictDraft(idx){ draftConflicts.splice(idx,1); renderConflictDraft(); }
function renderConflictDraft(){
  populateConflictSelects();
  document.getElementById('fConflictList').innerHTML = draftConflicts.map((pair,i)=>
    `<span class="chip">${escapeHtml(pair[0])} ⇔ ${escapeHtml(pair[1])} <button onclick="removeConflictDraft(${i})">✕</button></span>`
  ).join('');
}
async function saveProduct(){
  const nom = val('fNom').trim(); const description = val('fDesc').trim(); const prixRaw = val('fPrix').trim(); const prix = parsePrice(prixRaw);
  const images = [...draftImages];
  if(!nom || prixRaw===''){ alert('Merci de renseigner au moins le nom et le prix.'); return; }
  const labels = draftOptions.map(o=>o.label);
  const cleanConflicts = draftConflicts.filter(pair=>labels.includes(pair[0]) && labels.includes(pair[1]));
  const maxQtyRaw = val('fMaxQty').trim();
  const stockRaw = val('fStock').trim();
  const payload = { nom, description, prix, images, categorie: draftCategory, tags: [...draftTags], options: [...draftOptions], conflicts: cleanConflicts, lead_days: parseInt(val('fLeadDays'),10)||0, unit: val('fUnit'),
    max_qty: maxQtyRaw==='' ? null : parsePrice(maxQtyRaw),
    stock: stockRaw==='' ? null : parsePrice(stockRaw),
    allergenes: [...draftAllergenes],
    poids_g: val('fPoids').trim()==='' ? null : parseInt(val('fPoids'),10),
    portions: val('fPortions').trim()==='' ? null : parseInt(val('fPortions'),10) };
  let error;
  if(editingId){
    ({ error } = await dbUpdate('products', { id: 'eq.'+editingId }, payload));
  } else {
    ({ error } = await dbInsert('products', Object.assign({}, payload, { shop_id: currentDataId })));
  }
  if(error){ alert("La sauvegarde a échoué : " + error.message); return; }
  await loadShopFor(currentDataId);
  closeSheet(); render();
}
async function deleteProduct(id){
  if(!confirm('Supprimer ce produit ?')) return;
  const { error } = await dbDelete('products', { id: 'eq.'+id });
  if(error){ alert(error.message); return; }
  await loadShopFor(currentDataId); render();
}

/* ---------------- Client view ---------------- */
let activeCat = 'Tous', activeTagFilters = [], clientOptState = {}, clientQty = {}, showPracticalInfo = false, clientImageIndex = {};
function setClientImage(pid, i){ clientImageIndex[pid] = i; render(); }
function toggleServiceInfo(){ showPracticalInfo = !showPracticalInfo; render(); }
function renderPracticalInfoBlock(){
  let html = `<div class="shop-info-block" style="margin-top:-6px;">`;
  if(shop.adresse) html += `📍 ${escapeHtml(shop.adresse)}<br>`;
  if(shop.telephone) html += `📞 ${escapeHtml(shop.telephone)}<br>`;
  if(shop.hours){
    html += `<div style="margin-top:8px;">`;
    WEEKDAYS.forEach(([k,label])=>{
      const h = normalizeDayHours(shop.hours[k]);
      html += `${label} : ${h.ouvert ? h.ranges.map(r=>`${r.de} – ${r.a}`).join(', ') : 'Fermé'}<br>`;
    });
    html += `</div>`;
  }
  html += `<p class="pro-link" style="text-align:left;margin-top:12px;" onclick="reportShop()">Signaler cette boutique</p>`;
  html += `</div>`;
  return html;
}
function reportShop(){
  const subject = encodeURIComponent('Signalement — boutique ' + (shop.name||''));
  const body = encodeURIComponent('Bonjour,\n\nJe souhaite signaler la boutique « ' + (shop.name||'') + ' » pour la raison suivante :\n\n[décrivez le problème ici]');
  window.location.href = `mailto:laplacedesartisans02@gmail.com?subject=${subject}&body=${body}`;
}
function renderClient(main){
  let fab = document.getElementById('fabWrap'); if(fab) fab.style.display='none';
  const mine = savedDevis().filter(d => d.shop === shop.name).slice(0, 3);
  const mineUnread = mine.reduce((n, d) => n + (devisUnread[d.code] || 0), 0);
  const devisBanner = (mine.length && clientReturnScreen!=='pro') ? `<div class="${mineUnread ? 'devis-alert' : 'demo-note'}" style="margin-bottom:14px;">${mineUnread ? '<strong>L\'artisan vous a répondu.</strong><br>' : ''}Vos devis en cours : ${mine.map(d=>`<span class="pro-link" style="display:inline;font-weight:600;" onclick="openDevis('${d.code}')">${escapeHtml(d.code)}${devisUnread[d.code] ? `<span class="devis-badge">${devisUnread[d.code]} réponse${devisUnread[d.code]>1?'s':''}</span>` : ''}</span>`).join(' · ')}</div>` : '';
  const backLabel = clientReturnScreen==='pro' ? 'Retour à mon espace pro' : 'Toutes les boutiques';
  const backBtn = `<button class="link-btn" style="margin-bottom:14px;" onclick="backFromClient()">← ${backLabel}</button>`;

  if(products.length===0){
    main.innerHTML = backBtn + `<h1 class="page-title">${escapeHtml(shop.name||'Notre boutique')}</h1>
      <div class="empty"><div class="glyph">🧺</div><p>Aucun produit disponible pour le moment.<br>Revenez bientôt !</p></div>`;
    const bar = document.getElementById('cartBar'); if(bar) bar.remove();
    return;
  }

  let html = backBtn + `<h1 class="page-title">${escapeHtml(shop.name||'Notre boutique')}</h1>
  ${shop.description ? `<p class="shop-description">${escapeHtml(shop.description)}</p>` : ''}
  ${(shop.adresse||shop.telephone||shop.hours) ? `<div class="practical-info-toggle" onclick="toggleServiceInfo()">${showPracticalInfo?'▾':'▸'} Informations pratiques</div>${showPracticalInfo?renderPracticalInfoBlock():''}` : ''}
  ` + (function(){
    const f = upcomingClosure();
    if(!f) return '';
    const today = ymd(new Date());
    const enCours = (f.debut <= today);
    return `<div class="closed-banner"><strong>${enCours ? 'Boutique fermée' : 'Fermeture à venir'} — du ${escapeHtml(frDate(f.debut))} au ${escapeHtml(frDate(f.fin))}.</strong>${f.message ? '<br>' + escapeHtml(f.message) : ''}<br>Aucun retrait n'est proposé sur cette période.</div>`;
  })() + ordersBanner(shop.slug) + devisBanner;

  if(categories.length>0){
    const catsInUse = ['Tous', ...categories];
    html += `<div class="cat-scroll">${catsInUse.map(c=>`<div class="cat-chip ${c===activeCat?'active':''}" onclick="setCat('${c.replace(/'/g,"\\'")}')">${escapeHtml(c)}</div>`).join('')}</div>`;
  }
  if(tags.length>0){
    html += `<div class="filter-label">Filtres</div>
    <div class="cat-scroll">${tags.map(t=>`<div class="cat-chip tagchip ${activeTagFilters.includes(t)?'active':''}" onclick="toggleTagFilter('${t.replace(/'/g,"\\'")}')">${escapeHtml(t)}</div>`).join('')}</div>`;
  }

  const list = products.filter(p=> (activeCat==='Tous' || p.categorie===activeCat) && (activeTagFilters.length===0 || (p.tags||[]).some(t=>activeTagFilters.includes(t))) );
  if(list.some(p=>productImages(p)[0])) html += imagesToggleBtn();

  if(list.length===0){
    html += `<div class="empty"><p>Aucun produit ne correspond à ces filtres.</p></div>`;
  } else {
    html += `<div class="grid-wrap cards">`;
    list.forEach(p=>{
      if(!clientOptState[p.id]) clientOptState[p.id] = {};
      if(!clientQty[p.id]) clientQty[p.id] = 1;
      if(FRACTIONABLE_UNITS[p.unit] && clientFraction[p.id]===undefined) clientFraction[p.id] = 1;
      const imgs = productImages(p);
      const activeImg = clientImageIndex[p.id] || 0;
      html += `<div class="card">
        <div class="card-body">
          <div class="p-main">
          <div class="p-text">
          <div class="card-top"><span class="card-name">${escapeHtml(p.nom)}</span><span class="card-price">${FRACTIONABLE_UNITS[p.unit] ? fmtEuro(p.prix*(clientFraction[p.id]||1))+' €' : priceLabel(p)}</span></div>
          ${FRACTIONABLE_UNITS[p.unit] ? `<div style="font-size:11.5px;color:var(--ink-soft);margin-top:-4px;">${priceLabel(p)}</div>` : ''}
          <div class="card-cat">${escapeHtml(p.categorie || '')}${(p.stock!==null&&p.stock!==undefined) ? (p.stock>0 ? ` · <span style="color:var(--forest-700);font-weight:600;">En stock (${p.stock})</span>` : ` · <span style="color:var(--danger);font-weight:600;">Rupture de stock${p.leadDays>0?` — délai ${p.leadDays} jour${p.leadDays>1?'s':''}`:''}</span>`) : (p.leadDays>0 ? ` · <span style="color:var(--oak-700);font-weight:600;">Délai ${p.leadDays} jour${p.leadDays>1?'s':''}</span>` : '')}</div>
          ${p.description ? `<div class="card-desc">${escapeHtml(p.description)}</div>` : ''}
          ${(p.tags && p.tags.length) ? `<div class="card-tags">${p.tags.map(t=>`<span class="chip tag">${escapeHtml(t)}</span>`).join('')}</div>` : ''}
          ${(function(){
            const det = [];
            if(p.poidsG) det.push(p.poidsG + ' g');
            if(p.portions) det.push(p.portions + ' portion' + (p.portions>1?'s':''));
            const bits = det.length ? `<div class="allerg-list">${escapeHtml(det.join(' · '))}</div>` : '';
            const all = (p.allergenes && p.allergenes.length)
              ? `<div class="allerg-list"><strong>Allergènes :</strong> ${escapeHtml(p.allergenes.map(allergeneLabel).join(', '))}</div>`
              : `<div class="allerg-list">Aucun des 14 allergènes à déclarer</div>`;
            return bits + all;
          })()}
          </div>
          ${imgs.length?`<div class="p-thumb"><img src="${imgs[Math.min(activeImg,imgs.length-1)]}" alt="${escapeHtml(p.nom)}" loading="lazy">${imgs.length>1?`<div class="img-dots">${imgs.map((_,i)=>`<span class="img-dot ${i===activeImg?'active':''}" onclick="setClientImage('${p.id}',${i})"></span>`).join('')}</div>`:''}</div>`:''}
          </div>
          ${(function(){
            const opts = (p.options||[]).map(normalizeOption);
            const conflicts = p.conflicts||[];
            const checkedLabels = Object.entries(clientOptState[p.id]||{}).filter(([k,v])=>v).map(([k])=>k);
            const isBlocked = (label)=> !clientOptState[p.id][label] && checkedLabels.some(cl => conflicts.some(pair => (pair[0]===cl&&pair[1]===label)||(pair[0]===label&&pair[1]===cl)));
            return opts.map(o=>{
              const disabled = isBlocked(o.label);
              return `<label class="opt-check ${disabled?'disabled':''}"><input type="checkbox" ${clientOptState[p.id][o.label]?'checked':''} ${disabled?'disabled':''} onchange="toggleOpt('${p.id}','${o.label.replace(/'/g,"\\'")}')">${escapeHtml(o.label)}${o.price?` <span style="color:var(--oak-700);font-weight:700;">(+${fmtEuro(o.price)} €)</span>`:''}</label>`;
            }).join('');
          })()}
          <div class="qty-add" style="flex-wrap:wrap;">
            ${FRACTIONABLE_UNITS[p.unit] ? `<div style="flex-basis:100%;margin-bottom:8px;">
              <input type="text" spellcheck="true" lang="fr" inputmode="decimal" value="${String(clientFraction[p.id]||1).replace('.',',')}" data-pid="${p.id}" onchange="setClientFraction('${p.id}', this.value)" placeholder="Quantité en ${p.unit==='kg'?'kg':'L'}">
              <p style="font-size:11px;color:var(--ink-soft);margin:4px 0 0;">Ex. 0,2 pour ${p.unit==='kg'?'200 g':'20 cl'}. Minimum 0,1 ${p.unit==='kg'?'kg':'L'}.</p>
            </div>` : ''}
            <div class="qty-controls"><button onclick="changeQty('${p.id}',-1)">−</button><span>${clientQty[p.id]}</span><button onclick="changeQty('${p.id}',1)">+</button></div>
            <button class="btn btn-oak" data-add="${p.id}" onclick="addToCart('${p.id}')">Ajouter</button>
          </div>
          ${(p.stock!==null&&p.stock!==undefined&&p.stock>0) ? `<p class="pro-link" style="text-align:left;margin-top:8px;" onclick="openSpecialRequest('${p.id}')">Besoin d'une quantité plus importante que le stock disponible ?</p>` : ''}
        </div>
      </div>`;
    });
    html += `</div>`;
  }
  html += `<div style="height:90px"></div>`;
  main.innerHTML = html;
  renderCartBar();
}
const FRACTIONABLE_UNITS = { kg: true, litre: true };
let clientFraction = {};
function setClientFraction(pid, value){
  const p = products.find(x=>x.id===pid);
  let n = parsePrice(value);
  if(isNaN(n) || n < 0.1) n = 0.1;
  const stockCap = (p && p.stock!==null && p.stock!==undefined && p.stock>0) ? p.stock : Infinity;
  if(n > stockCap){ n = stockCap; }
  clientFraction[pid] = n;
  render();
}

function setCat(c){ activeCat=c; render(); }
function toggleTagFilter(t){ const i=activeTagFilters.indexOf(t); if(i>-1) activeTagFilters.splice(i,1); else activeTagFilters.push(t); render(); }
function toggleOpt(pid, opt){ clientOptState[pid][opt] = !clientOptState[pid][opt]; render(); }
async function saveCounterPin(){
  const errEl = document.getElementById('counterPinError');
  const pin = val('counterPin').trim();
  if(!/^[0-9]{4,8}$/.test(pin)){ errEl.textContent = 'Le code doit comporter de 4 à 8 chiffres.'; return; }
  const { error } = await dbRpc('comptoir_set_pin', { p_shop_id: currentDataId, p_pin: pin });
  if(error){ errEl.textContent = error.message; return; }
  shop.counter_pin_set = 'defini';
  errEl.textContent = '';
  alert('Code comptoir enregistré. Communiquez-le à votre équipe avec l\'adresse laplacedesartisans.fr/comptoir');
  render();
}
async function removeCounterPin(){
  if(!confirm("Désactiver l'accès comptoir ? Votre équipe ne pourra plus consulter les commandes.")) return;
  const { error } = await dbRpc('comptoir_set_pin', { p_shop_id: currentDataId, p_pin: null });
  if(error){ alert(error.message); return; }
  shop.counter_pin_set = null;
  render();
}
function openAddFermeture(){
  const t = new Date();
  document.getElementById('fermDebut').value = ymd(t);
  document.getElementById('fermFin').value = ymd(t);
  document.getElementById('fermMessage').value = '';
  document.getElementById('fermError').textContent = '';
  document.getElementById('fermetureBackdrop').classList.add('open');
}
function closeFermeture(){ document.getElementById('fermetureBackdrop').classList.remove('open'); }
async function saveFermeture(){
  const errEl = document.getElementById('fermError');
  const debut = val('fermDebut'), fin = val('fermFin'), message = val('fermMessage').trim();
  if(!debut || !fin){ errEl.textContent = 'Indiquez les deux dates.'; return; }
  if(fin < debut){ errEl.textContent = 'La date de fin doit suivre la date de début.'; return; }
  shop.fermetures = shop.fermetures || [];
  shop.fermetures.push({ debut: debut, fin: fin, message: message });
  shop.fermetures.sort((a,b)=> (a.debut < b.debut ? -1 : 1));
  const okSave = await saveShop();
  if(!okSave){ errEl.textContent = "L'enregistrement a échoué."; return; }
  closeFermeture();
  render();
}
async function deleteFermeture(i){
  if(!confirm('Supprimer cette fermeture ?')) return;
  shop.fermetures.splice(i,1);
  await saveShop();
  render();
}
let draftPickupPoint = null, editingPickupPointIndex = null;
function openAddPickupPoint(){
  editingPickupPointIndex = null;
  draftPickupPoint = { label:'', address:'', hoursMode:'shop', hours:{}, leadDays:0 };
  document.getElementById('ppSheetTitle').textContent = 'Ajouter un point de retrait';
  document.getElementById('ppLabel').value = '';
  document.getElementById('ppAddress').value = '';
  document.getElementById('ppHoursMode').value = 'shop';
  document.getElementById('ppLeadDays').value = '0';
  renderPPHoursEditor();
  document.getElementById('pickupPointBackdrop').classList.add('open');
}
function openEditPickupPoint(i){
  editingPickupPointIndex = i;
  const pt = shop.pickupPoints[i];
  draftPickupPoint = JSON.parse(JSON.stringify(pt));
  document.getElementById('ppSheetTitle').textContent = 'Modifier ce point de retrait';
  document.getElementById('ppLabel').value = pt.label;
  document.getElementById('ppAddress').value = pt.address;
  document.getElementById('ppHoursMode').value = pt.hoursMode || 'shop';
  document.getElementById('ppLeadDays').value = String(pt.leadDays||0);
  renderPPHoursEditor();
  document.getElementById('pickupPointBackdrop').classList.add('open');
}
function closePickupPointSheet(){ document.getElementById('pickupPointBackdrop').classList.remove('open'); }
function renderPPHoursEditor(){
  const mode = val('ppHoursMode');
  const el = document.getElementById('ppHoursEditor');
  if(mode!=='custom'){ el.innerHTML = ''; return; }
  if(!draftPickupPoint.hours || Object.keys(draftPickupPoint.hours).length===0) draftPickupPoint.hours = {};
  let html = '';
  WEEKDAYS.forEach(([k,label])=>{
    const h = normalizeDayHours(draftPickupPoint.hours[k]);
    html += `<div class="hours-row" style="flex-direction:column;align-items:stretch;">
      <div style="display:flex;justify-content:space-between;align-items:center;">
        <label class="day-toggle"><input type="checkbox" ${h.ouvert?'checked':''} onchange="togglePPDay('${k}', this.checked)">${label}</label>
        ${!h.ouvert?`<span class="closed-label">Fermé</span>`:''}
      </div>
      ${h.ouvert ? h.ranges.map((r,i)=>`
        <div class="hours-times" style="margin-top:6px;">
          <input type="time" value="${r.de}" onchange="updatePPRangeTime('${k}',${i},'de',this.value)">
          <span>–</span>
          <input type="time" value="${r.a}" onchange="updatePPRangeTime('${k}',${i},'a',this.value)">
          ${h.ranges.length>1 ? `<button class="icon-btn" onclick="removePPRange('${k}',${i})">✕</button>` : ''}
        </div>`).join('') : ''}
      ${h.ouvert ? `<button class="link-btn" style="margin-top:6px;align-self:flex-start;" onclick="addPPRange('${k}')">+ Ajouter une coupure</button>` : ''}
    </div>`;
  });
  el.innerHTML = html;
}
function togglePPDay(day, checked){
  draftPickupPoint.hours[day] = normalizeDayHours(draftPickupPoint.hours[day]);
  draftPickupPoint.hours[day].ouvert = checked;
  renderPPHoursEditor();
}
function updatePPRangeTime(day, idx, field, value){
  draftPickupPoint.hours[day].ranges[idx][field] = value;
}
function addPPRange(day){
  const cur = draftPickupPoint.hours[day];
  const last = cur.ranges[cur.ranges.length-1];
  cur.ranges.push({ de:(last&&last.a)||'14:00', a:'19:00' });
  renderPPHoursEditor();
}
function removePPRange(day, idx){
  draftPickupPoint.hours[day].ranges.splice(idx,1);
  renderPPHoursEditor();
}
async function savePickupPoint(){
  const label = val('ppLabel').trim();
  const address = val('ppAddress').trim();
  if(!label || !address){ alert('Merci de renseigner un nom et une adresse.'); return; }
  const point = {
    id: (draftPickupPoint && draftPickupPoint.id) || (Date.now()+'-'+Math.random().toString(36).slice(2)),
    label, address,
    hoursMode: val('ppHoursMode'),
    hours: draftPickupPoint.hours || {},
    leadDays: parseInt(val('ppLeadDays'),10)||0
  };
  shop.pickupPoints = shop.pickupPoints || [];
  if(editingPickupPointIndex!==null){ shop.pickupPoints[editingPickupPointIndex] = point; }
  else { shop.pickupPoints.push(point); }
  await saveShop();
  closePickupPointSheet();
  render();
}
async function deletePickupPoint(i){
  if(!confirm('Supprimer ce point de retrait ?')) return;
  shop.pickupPoints.splice(i,1);
  await saveShop();
  render();
}
function openSpecialRequest(pid){
  const p = products.find(x=>x.id===pid);
  specialRequestProductId = pid;
  const unitLabel = FRACTIONABLE_UNITS[p.unit] ? (p.unit==='kg'?'kg':'L') : (p.unit!=='unité' ? p.unit : '');
  document.getElementById('specialRequestIntro').textContent = `« ${p.nom} » — il reste ${String(p.stock).replace('.',',')}${unitLabel?' '+unitLabel:''} en stock. Pour une quantité plus importante, indiquez ce dont vous avez besoin : vous recevrez un numéro de devis et l'artisan vous répondra dans la messagerie de l'application.`;
  document.getElementById('srQty').value = '';
  document.getElementById('srQtyUnit').textContent = unitLabel ? `en ${unitLabel}` : '';
  document.getElementById('srEmail').value = '';
  document.getElementById('srMessage').value = '';
  document.getElementById('specialRequestError').textContent = '';
  document.getElementById('specialRequestBackdrop').classList.add('open');
}
function closeSpecialRequest(){ document.getElementById('specialRequestBackdrop').classList.remove('open'); }
async function submitSpecialRequest(){
  const qty = parsePrice(val('srQty'));
  const email = val('srEmail').trim();
  const message = val('srMessage').trim();
  const errEl = document.getElementById('specialRequestError');
  if(!qty || qty<=0){ errEl.textContent = 'Merci d\'indiquer la quantité souhaitée.'; return; }
  if(email && !validEmail(email)){ errEl.textContent = 'Adresse e-mail invalide.'; return; }
  const p = products.find(x=>x.id===specialRequestProductId);
  const { data, error } = await dbRpc('devis_create', {
    p_shop_id: currentDataId, p_product_nom: p ? p.nom : '', p_qty: qty,
    p_unit: p ? p.unit : null, p_email: email || null, p_message: message || null
  });
  if(error){ errEl.textContent = error.message; return; }
  const created = Array.isArray(data) ? data[0] : data;
  if(!created || !created.code){ errEl.textContent = "Le devis n'a pas pu être créé, réessayez."; return; }
  rememberDevis(created.code, shop.name, p ? p.nom : '');
  closeSpecialRequest();
  await openDevis(created.code);
}

/* ================= Devis (messagerie interne) ================= */
let currentDevis = null;
const DEVIS_STATUS = {
  nouvelle: 'En attente de réponse', en_discussion: 'Discussion en cours',
  devis_propose: 'Proposition reçue', acceptee: 'Acceptée', refusee: 'Refusée', traitee: 'Clôturée'
};
function savedDevis(){
  try{ return JSON.parse(localStorage.getItem('lpda_devis') || '[]'); }catch(e){ return []; }
}
function rememberDevis(code, shopName, productName){
  try{
    const prev = savedDevis().find(d => d.code === code);
    const list = savedDevis().filter(d => d.code !== code);
    list.unshift({ code: code, shop: shopName || (prev && prev.shop) || '', product: productName || (prev && prev.product) || '',
                   at: (prev && prev.at) || new Date().toISOString(), seen: (prev && prev.seen) || 0 });
    localStorage.setItem('lpda_devis', JSON.stringify(list.slice(0, 20)));
  }catch(e){}
}
function markDevisSeen(code, count){
  try{
    const list = savedDevis().map(d => d.code === code ? Object.assign({}, d, { seen: count }) : d);
    localStorage.setItem('lpda_devis', JSON.stringify(list));
  }catch(e){}
  delete devisUnread[code];
}
/* Réponses non lues : consultées au chargement, sans e-mail ni compte */
let orderStates = {}, orderStatesCheckedAt = 0;
async function refreshOrderStates(force){
  const list = savedOrders().filter(o => Date.now() - (o.at || 0) < 14 * 86400000).slice(0, 6);
  if(list.length === 0) return;
  if(!force && (Date.now() - orderStatesCheckedAt) < 45000) return;
  orderStatesCheckedAt = Date.now();
  const before = JSON.stringify(orderStates);
  const next = {};
  for(const o of list){
    const { data, error } = await dbRpc('commande_client', { p_code: o.code, p_token: o.token });
    if(error) continue;
    if(!data){ forgetOrder(o.code); continue; }
    next[o.code] = { status: data.status, dep: data.deposit_status, url: data.payment_url, exp: data.payment_expires_at, shopName: data.shop_name, slug: data.shop_slug };
  }
  orderStates = next;
  if(JSON.stringify(orderStates) !== before && (screen === 'directory' || screen === 'client')) render();
}
function ordersBanner(onlySlug){
  const rows = Object.keys(orderStates).map(c => ({ code: c, st: orderStates[c] }))
    .filter(r => !onlySlug || r.st.slug === onlySlug)
    .filter(r => r.st.status === 'en_attente' || (r.st.status === 'nouvelle' && r.st.dep === 'lien_envoye' && r.st.url && Date.parse(r.st.exp || 0) > Date.now()));
  if(!rows.length) return '';
  return `<div class="order-alert">${rows.map(r => {
    const pay = r.st.status === 'nouvelle';
    return `<div><strong>${pay ? 'Commande ' + escapeHtml(r.code) + ' acceptée : réglez votre acompte.' : 'Commande ' + escapeHtml(r.code) + ' en attente de l\'artisan.'}</strong> ` +
           `<span class="pro-link" style="display:inline;font-weight:600;" onclick="openTrackedOrder('${r.code}')">${pay ? 'Régler l\'acompte' : 'Voir la commande'}</span> <span style="color:var(--ink-soft);">${escapeHtml(r.st.shopName || '')}</span></div>`;
  }).join('')}</div>`;
}
async function openTrackedOrder(code){
  const tok = orderTokenFor(code); if(!tok) return;
  const st = orderStates[code];
  window.location.href = '/' + ((st && st.slug) || '') + '/commande/' + code + '?t=' + tok;
}
let devisUnread = {};
let devisBadgesCheckedAt = 0;
function devisUnreadTotal(){
  return Object.keys(devisUnread).reduce((n, k) => n + devisUnread[k], 0);
}
async function refreshDevisBadges(force){
  const list = savedDevis().slice(0, 6);
  if(list.length === 0) return;
  const now = Date.now();
  if(!force && (now - devisBadgesCheckedAt) < 60000) return;
  devisBadgesCheckedAt = now;
  const before = JSON.stringify(devisUnread);
  const avant = savedDevis().length;
  const next = {};
  const introuvables = [];
  for(const d of list){
    const { data, error } = await dbRpc('devis_get', { p_code: d.code });
    if(error) continue;                    // problème réseau : on ne touche à rien
    if(!data){ introuvables.push(d.code); continue; }   // devis supprimé : on l'oublie
    const total = Array.isArray(data.messages) ? data.messages.length : 0;
    const unread = Math.max(0, total - (d.seen || 0));
    if(unread > 0 && data.last_from === 'artisan') next[d.code] = unread;
  }
  if(introuvables.length){
    try{
      localStorage.setItem('lpda_devis', JSON.stringify(savedDevis().filter(d => introuvables.indexOf(d.code) === -1)));
    }catch(e){}
    introuvables.forEach(c => { delete devisUnread[c]; });
  }
  devisUnread = next;
  if(introuvables.length && (screen === 'directory' || screen === 'client')){ render(); return; }
  if(JSON.stringify(devisUnread) !== before && (screen === 'directory' || screen === 'client')) render();
}
async function openDevis(code, skipRemember){
  document.getElementById('main').innerHTML = `<p class="page-sub">Chargement…</p>`;
  const { data, error } = await dbRpc('devis_get', { p_code: code });
  if(error || !data){
    currentDevis = null;
    screen = 'devis'; devisNotFound = String(code || '').toUpperCase(); render(); return;
  }
  devisNotFound = '';
  currentDevis = data;
  rememberDevis(data.code, data.shop_name, data.product_nom);
  markDevisSeen(data.code, Array.isArray(data.messages) ? data.messages.length : 0);
  closeCartSilently();
  screen = 'devis';
  render();
}
let devisNotFound = '';
function threadHtml(messages, viewer){
  viewer = viewer || 'client';
  const list = Array.isArray(messages) ? messages : [];
  if(list.length === 0) return `<p style="font-size:13px;color:var(--ink-soft);">Aucun message pour l'instant.</p>`;
  return `<div class="thread">` + list.map(m=>{
    const own = (m.from === viewer);
    const who = own ? 'Vous' : (m.from === 'client' ? 'Client' : 'Artisan');
    const quote = (m.quote !== null && m.quote !== undefined && m.quote !== '')
      ? `<span class="quote">Proposition : ${fmtEuro(m.quote)} €${(m.deposit !== null && m.deposit !== undefined && m.deposit !== '') ? ` · acompte ${fmtEuro(m.deposit)} €` : ''}</span>` : '';
    const when = m.at ? new Date(m.at).toLocaleString('fr-FR') : '';
    return `<div class="msg ${own ? 'client' : 'artisan'}"><span class="who">${who}${when ? ' · ' + escapeHtml(when) : ''}</span>${escapeHtml(m.body || '')}${quote}</div>`;
  }).join('') + `</div>`;
}
function renderDevis(main){
  let fab = document.getElementById('fabWrap'); if(fab) fab.style.display='none';
  if(!currentDevis){
    const mine = savedDevis();
    main.innerHTML = `<h1 class="page-title">Retrouver mon devis</h1>
      <p class="page-sub">${devisNotFound ? `Aucun devis ne correspond au numéro <strong>${escapeHtml(devisNotFound)}</strong>. Vérifiez la saisie.` : 'Saisissez le numéro reçu lors de votre demande pour rouvrir la discussion avec l\'artisan.'}</p>
      <label class="field-label">Numéro de devis</label>
      <input type="text" id="devisLookup" placeholder="DV-XXXXXXX" value="${escapeHtml(devisNotFound)}" autocapitalize="characters">
      <div class="auth-error" id="devisLookupError"></div>
      <div class="sheet-actions"><button class="btn btn-oak btn-block" onclick="lookupDevis()">Ouvrir mon devis</button></div>
      ${mine.length ? `<label class="field-label" style="margin-top:22px;">Sur cet appareil</label>
        <div class="grid-wrap rows">${mine.map(d=>`<div class="product-row" style="cursor:pointer;align-items:center;" onclick="openDevis('${d.code}')">
          <div class="thumb" style="font-size:18px;">🧾</div>
          <div class="row-body"><div class="row-top"><span class="row-name">${escapeHtml(d.code)}</span>${devisUnread[d.code]?`<span class="devis-badge">${devisUnread[d.code]} réponse${devisUnread[d.code]>1?'s':''}</span>`:''}</div>
          <div class="row-cat">${escapeHtml(d.product || '')}${d.shop?' · '+escapeHtml(d.shop):''}</div></div></div>`).join('')}</div>` : ''}
      <p class="pro-link" onclick="screen='directory';render();">← Retour aux boutiques</p>`;
    refreshDevisBadges(); refreshOrderStates();
    return;
  }
  const d = currentDevis;
  const closed = (d.status === 'acceptee' || d.status === 'refusee' || d.status === 'traitee');
  const unit = (d.unit && d.unit !== 'unité') ? ' ' + d.unit : '';
  let html = `<h1 class="page-title">Votre devis</h1>
  <p class="page-sub">Échangez ici avec l'artisan : tout se passe dans cette messagerie, vous n'avez rien d'autre à faire.</p>
  <div class="devis-head">
    <div class="devis-code">${escapeHtml(d.code)}</div>
    <div style="font-size:13px;margin-top:4px;">${escapeHtml(d.product_nom)} — <strong>${String(d.qty).replace('.', ',')}${escapeHtml(unit)}</strong></div>
    <div style="font-size:12.5px;color:var(--ink-soft);margin-top:2px;">${escapeHtml(d.shop_name || '')} · ${escapeHtml(DEVIS_STATUS[d.status] || d.status)}</div>
  </div>`;
  if(d.message) html += `<p style="font-size:13px;color:var(--ink-soft);margin:-4px 0 10px;">Votre demande initiale : « ${escapeHtml(d.message)} »</p>`;
  html += threadHtml(d.messages);
  if(d.status === 'devis_propose' && d.quoted_total !== null && d.quoted_total !== undefined){
    html += `<div class="demo-note" style="margin-top:14px;">L'artisan vous propose cette commande à <strong>${fmtEuro(d.quoted_total)} €</strong>.${(d.deposit!=null) ? ` Un acompte de <strong>${fmtEuro(d.deposit)} €</strong> est demandé pour bloquer la commande ; le solde de ${fmtEuro(d.quoted_total - d.deposit)} € se règle au retrait.` : ' Le règlement se fera au retrait, en boutique.'} En acceptant, vous choisissez votre jour de retrait.</div>
    <div class="sheet-actions">
      <button class="btn btn-ghost" onclick="decideDevis(false)">Refuser</button>
      <button class="btn btn-oak" onclick="decideDevis(true)">Accepter la proposition</button>
    </div>`;
  }
  if(d.status === 'acceptee' && d.order_code && d.order_token){
    html += `<div class="demo-note" style="margin-top:14px;">Proposition acceptée : votre commande <strong>${escapeHtml(d.order_code)}</strong> est créée. Suivez-la, et réglez l'acompte s'il vous est demandé, sur sa page de suivi.<div style="margin-top:10px;"><a class="btn btn-oak" style="text-decoration:none;display:inline-block;" href="/${escapeHtml(d.shop_slug || '')}/commande/${escapeHtml(d.order_code)}?t=${escapeHtml(d.order_token)}">Suivre ma commande</a></div></div>`;
  } else if(d.status === 'acceptee') html += `<div class="demo-note" style="margin-top:14px;">Proposition acceptée${(d.deposit!=null) ? ` — pensez à verser l'acompte de <strong>${fmtEuro(d.deposit)} €</strong> en boutique pour bloquer la commande` : ''}. L'artisan prépare votre commande et vous confirmera les détails ici.</div>`;
  if(d.status === 'refusee') html += `<div class="demo-note" style="margin-top:14px;">Proposition refusée. Vous pouvez toujours écrire à l'artisan ci-dessous.</div>`;
  html += `<label class="field-label" style="margin-top:18px;">Votre message</label>
  <textarea spellcheck="true" lang="fr" id="devisReply" placeholder="Écrivez à l'artisan…"></textarea>
  <div class="auth-error" id="devisError"></div>
  <div class="sheet-actions"><button class="btn btn-oak btn-block" onclick="sendDevisMessage()">Envoyer</button></div>
  <button class="btn btn-ghost btn-block" style="margin-top:10px;" onclick="downloadDevisRecap()">Télécharger le récap (image)</button>
  <p style="font-size:12px;color:var(--ink-soft);margin:10px 0 0;">Conservez ce numéro : <strong>${escapeHtml(d.code)}</strong>. Il vous permet de rouvrir cette discussion à tout moment, depuis la page de la boutique ou l'accueil.</p>
  <p class="pro-link" onclick="screen='directory';render();">← Retour aux boutiques</p>`;
  main.innerHTML = html;
}
function openDevisLookup(){
  currentDevis = null; devisNotFound = '';
  closeCartSilently();
  screen = 'devis';
  render();
}
async function lookupDevis(){
  let code = val('devisLookup').trim().toUpperCase().replace(/\s+/g, '');
  const errEl = document.getElementById('devisLookupError');
  if(!code){ if(errEl) errEl.textContent = 'Saisissez votre numéro de devis.'; return; }
  if(!/^DV-/.test(code)) code = 'DV-' + code.replace(/^DV/, '');
  await openDevis(code);
}
async function sendDevisMessage(){
  const body = val('devisReply').trim();
  const errEl = document.getElementById('devisError');
  if(!body){ if(errEl) errEl.textContent = 'Écrivez un message avant d\'envoyer.'; return; }
  const { data, error } = await dbRpc('devis_client_message', { p_code: currentDevis.code, p_body: body });
  if(error){ if(errEl) errEl.textContent = error.message; return; }
  currentDevis = data;
  markDevisSeen(data.code, Array.isArray(data.messages) ? data.messages.length : 0);
  render();
}
async function decideDevis(accept){
  if(accept){ openDevisPickup(); return; }
  if(!confirm('Refuser cette proposition ?')) return;
  const { data, error } = await dbRpc('devis_client_decision', { p_code: currentDevis.code, p_accept: false });
  if(error){ alert(error.message); return; }
  currentDevis = data;
  markDevisSeen(data.code, Array.isArray(data.messages) ? data.messages.length : 0);
  render();
}
async function openDevisPickup(){
  const d = currentDevis; if(!d || !d.shop_id) return;
  const el = document.getElementById('devisPickupBackdrop');
  if(!shop || shop.id !== d.shop_id){ await loadShopFor(d.shop_id); }
  cart = []; selectedPickupPointId = '';
  document.getElementById('dvIntro').textContent = `${d.product_nom} — ${fmtEuro(d.quoted_total)} €. Choisissez quand et où vous la récupérez.`;
  document.getElementById('dvError').textContent = '';
  document.getElementById('dvEmail').value = '';
  const pts = shop.pickupPoints || [];
  const wrap = document.getElementById('dvPlaceWrap');
  if(pts.length){
    wrap.style.display = 'block';
    document.getElementById('dvPlace').innerHTML = '<option value="">— Choisissez votre lieu de retrait —</option>' +
      `<option value="__shop__">Boutique — ${escapeHtml(shop.adresse || shop.name || 'adresse principale')}</option>` +
      pts.map(p => `<option value="${escapeHtml(p.id)}">${escapeHtml(p.label)} — ${escapeHtml(p.address)}</option>`).join('');
  } else { wrap.style.display = 'none'; selectedPickupPointId = '__shop__'; }
  document.getElementById('dvHint').textContent = paymentsActive()
    ? "Si la boutique demande un acompte, le lien de règlement apparaît sur la page de suivi de votre commande (et par e-mail si vous en indiquez un)."
    : "Pour recevoir la confirmation de votre commande.";
  dvFillDays();
  el.classList.add('open');
}
function closeDevisPickup(){ document.getElementById('devisPickupBackdrop').classList.remove('open'); }
function dvPlaceChanged(){ selectedPickupPointId = val('dvPlace') || ''; dvFillDays(); }
function dvFillDays(){
  const daySel = document.getElementById('dvDay'), timeSel = document.getElementById('dvTime');
  if(!selectedPickupPointId){
    daySel.innerHTML = '<option value="">Choisissez d\'abord le lieu de retrait</option>'; daySel.disabled = true;
    timeSel.innerHTML = '<option value="">—</option>'; timeSel.disabled = true; return;
  }
  daySel.disabled = false; timeSel.disabled = false;
  const days = getOpenDayOffsets(cutoffPassed() ? 2 : 1, 8);
  if(!days.length){
    daySel.innerHTML = '<option value="">Aucun créneau disponible</option>'; daySel.disabled = true;
    timeSel.innerHTML = '<option value="">—</option>'; timeSel.disabled = true; return;
  }
  daySel.innerHTML = days.map(x => `<option value="${x.offset}">${escapeHtml(x.label)}</option>`).join('');
  dvFillTimes();
}
function dvFillTimes(){
  const off = parseInt(val('dvDay'), 10);
  const times = isNaN(off) ? [] : getTimesForDayOffset(off);
  document.getElementById('dvTime').innerHTML = times.length ? times.map(t => `<option>${escapeHtml(t)}</option>`).join('') : '<option value="">—</option>';
}
async function confirmDevisPickup(){
  const err = document.getElementById('dvError'); err.textContent = '';
  if(val('dvDay') === '' || val('dvTime') === ''){ err.textContent = "Choisissez un jour et une heure de retrait."; return; }
  const email = val('dvEmail').trim() || null;
  if(email && !validEmail(email)){ err.textContent = 'Cette adresse e-mail ne semble pas valide.'; return; }
  const dt = new Date(); dt.setDate(dt.getDate() + (parseInt(val('dvDay'), 10) || 0));
  const lbl = dt.toLocaleDateString('fr-FR', { weekday: 'long', day: 'numeric', month: 'long' });
  const slot = lbl.charAt(0).toUpperCase() + lbl.slice(1) + ' — ' + val('dvTime');
  const pt = getSelectedPickupPoint();
  const loc = pt ? `${pt.label} — ${pt.address}` : `Boutique — ${shop.adresse || shop.name || ''}`;
  const { data, error } = await dbRpc('devis_client_decision', { p_code: currentDevis.code, p_accept: true, p_pickup_date: ymd(dt), p_slot: slot, p_pickup_location: loc, p_email: email });
  if(error){ err.textContent = error.message; return; }
  currentDevis = data;
  if(data.order_code && data.order_token) rememberOrder({ code: data.order_code, token: data.order_token, slug: data.shop_slug, shop: data.shop_name });
  markDevisSeen(data.code, Array.isArray(data.messages) ? data.messages.length : 0);
  closeDevisPickup();
  render();
}
function changeQty(pid, delta){
  const p = products.find(x=>x.id===pid);
  const stockCap = (p && p.stock!==null && p.stock!==undefined && p.stock>0) ? p.stock : Infinity;
  let next = Math.max(1, (clientQty[pid]||1)+delta);
  if(next > stockCap) next = Math.max(1, stockCap);
  clientQty[pid] = next;
  render();
}
function addToCart(pid){
  const p = products.find(x=>x.id===pid);
  const opts = (p.options||[]).map(normalizeOption);
  const chosenLabels = Object.entries(clientOptState[pid]||{}).filter(([k,v])=>v).map(([k])=>k);
  const chosenOpts = opts.filter(o=>chosenLabels.includes(o.label));
  const optSum = chosenOpts.reduce((s,o)=>s+(o.price||0),0);
  const displayOpts = chosenOpts.map(o=> o.price ? `${o.label} (+${fmtEuro(o.price)} €)` : o.label);
  let fraction = null, fractionLabel = null;
  if(FRACTIONABLE_UNITS[p.unit]){
    fraction = clientFraction[pid]||1;
    if(fraction < 0.1){ alert('La quantité minimum est de 0,1 ' + (p.unit==='kg'?'kg':'L') + '.'); return; }
    fractionLabel = String(fraction).replace('.',',') + ' ' + (p.unit==='kg'?'kg':'L');
  }
  const unitPrice = fraction ? p.prix*fraction : p.prix;
  const nom = fractionLabel ? `${p.nom} (${fractionLabel})` : p.nom;
  const qty = clientQty[pid]||1;
  const effectiveQty = fraction ? fraction : qty;
  const needsValidation = !!(p.maxQty && p.maxQty>0 && effectiveQty > p.maxQty);
  // Deux articles ne se regroupent que s'ils portent exactement les mêmes options
  const empreinte = pid + '|' + nom + '|' + [...displayOpts].sort().join('§');
  const dejaAuPanier = cart
    .filter(it => it.productId === pid)
    .reduce((n, it) => n + (FRACTIONABLE_UNITS[p.unit] ? it.qty * (it.fraction || 1) : it.qty), 0);
  const stockTracked = (p.stock !== null && p.stock !== undefined);
  if(stockTracked && p.stock > 0 && (dejaAuPanier + effectiveQty) > p.stock){
    const reste = Math.max(0, p.stock - dejaAuPanier);
    alert(reste > 0
      ? `Il ne reste que ${String(reste).replace('.', ',')} en stock, et votre panier en contient déjà ${String(dejaAuPanier).replace('.', ',')}.`
      : `Votre panier contient déjà tout le stock disponible pour « ${p.nom} ».`);
    return;
  }
  const existante = cart.find(it => it.empreinte === empreinte);
  if(existante){
    existante.qty += qty;
    const cumul = FRACTIONABLE_UNITS[p.unit] ? existante.qty * (existante.fraction || 1) : existante.qty;
    existante.needsValidation = !!(p.maxQty && p.maxQty > 0 && cumul > p.maxQty);
  } else {
    cart.push({ productId:pid, nom, prix:unitPrice+optSum, qty, options:displayOpts,
                needsValidation, empreinte, fraction });
  }
  clientQty[pid]=1; render();
}
function renderCartBar(){
  let bar = document.getElementById('cartBar');
  if(cart.length===0){ if(bar) bar.remove(); return; }
  const count = cart.reduce((s,i)=>s+i.qty,0); const total = cart.reduce((s,i)=>s+i.qty*i.prix,0);
  if(!bar){ bar = document.createElement('div'); bar.id='cartBar'; bar.className='cartbar'; bar.onclick=function(){ openCart(); }; document.querySelector('.app').appendChild(bar); }
  bar.innerHTML = `<div class="left"><span class="count">${count}</span><span class="total">${fmtEuro(total)} €</span></div><span class="cta">Voir le panier →</span>`;
}
function openCart(fromRoute){
  renderCartDrawer(); populatePickupSlots();
  document.getElementById('cartBackdrop').classList.add('open');
  cartIsOpen = true;
  if(fromRoute === true) urlSyncReplace = true;
  syncUrl(); updateHeadForState();
}
const WEEKDAY_ORDER = ['dim','lun','mar','mer','jeu','ven','sam'];
function pad2(n){ return String(n).padStart(2,'0'); }
let selectedPickupPointId = null;
function getSelectedPickupPoint(){
  if(!selectedPickupPointId || selectedPickupPointId === '__shop__') return null;
  return (shop.pickupPoints||[]).find(p=>p.id===selectedPickupPointId) || null;
}
function getActiveHours(){
  const pt = getSelectedPickupPoint();
  if(pt && pt.hoursMode==='custom') return pt.hours || {};
  return shop.hours || {};
}
function cutoffPassed(){
  const m = /^(\d{1,2}):(\d{2})/.exec(String((shop && shop.order_cutoff) || ''));
  if(!m) return false;
  const now = new Date();
  return (now.getHours()*60 + now.getMinutes()) >= (parseInt(m[1],10)*60 + parseInt(m[2],10));
}
function cutoffLabel(){ return String(shop.order_cutoff || '').replace(':', 'h'); }
function cartMinLeadDays(){
  const base = cartBaseLeadDays();
  return (base > 0 && cutoffPassed()) ? base + 1 : base;
}
function cartBaseLeadDays(){
  let max = 0;
  cart.forEach(it=>{
    const p = products.find(pp=>pp.id===it.productId);
    if(!p) return;
    const stockTracked = (p.stock !== null && p.stock !== undefined);
    if(stockTracked && p.stock > 0) return; // en stock : pas de délai
    if(p.leadDays) max = Math.max(max, p.leadDays);
  });
  const pt = getSelectedPickupPoint();
  if(pt && pt.leadDays) max = Math.max(max, pt.leadDays);
  return max;
}
function fermetureRange(f){
  return { debut: f.debut || f.start || '', fin: f.fin || f.end || f.debut || f.start || '', message: f.message || f.msg || '' };
}
function ymd(d){
  return d.getFullYear() + '-' + pad2(d.getMonth()+1) + '-' + pad2(d.getDate());
}
function closureOn(dateStr){
  const list = Array.isArray(shop.fermetures) ? shop.fermetures : [];
  for(const raw of list){
    const f = fermetureRange(raw);
    if(f.debut && f.fin && dateStr >= f.debut && dateStr <= f.fin) return f;
  }
  return null;
}
function upcomingClosure(){
  const today = ymd(new Date());
  const list = (Array.isArray(shop.fermetures) ? shop.fermetures : []).map(fermetureRange)
    .filter(f => f.fin && f.fin >= today)
    .sort((a,b) => a.debut < b.debut ? -1 : 1);
  return list[0] || null;
}
function frDate(dateStr){
  const p = String(dateStr||'').split('-');
  if(p.length !== 3) return dateStr || '';
  return new Date(Number(p[0]), Number(p[1])-1, Number(p[2])).toLocaleDateString('fr-FR', {day:'numeric', month:'long'});
}
function getOpenDayOffsets(minDayOffset, maxCount){
  minDayOffset = minDayOffset || 0;
  const hours = getActiveHours();
  const now = new Date();
  const result = [];
  for(let dayOffset=minDayOffset; dayOffset<minDayOffset+30 && result.length<maxCount; dayOffset++){
    const d = new Date(now); d.setDate(now.getDate()+dayOffset);
    const key = WEEKDAY_ORDER[d.getDay()];
    const h = normalizeDayHours(hours[key]);
    if(!h.ouvert) continue;
    if(closureOn(ymd(d))) continue;
    if(getTimesForDayOffset(dayOffset).length === 0) continue;
    const label = dayOffset===0 ? "Aujourd'hui" : dayOffset===1 ? "Demain" : d.toLocaleDateString('fr-FR', {weekday:'long', day:'numeric', month:'short'});
    result.push({ offset: dayOffset, label: label });
  }
  return result;
}
function getTimesForDayOffset(dayOffset){
  const hours = getActiveHours();
  const now = new Date();
  const d = new Date(now); d.setDate(now.getDate()+dayOffset);
  const key = WEEKDAY_ORDER[d.getDay()];
  const h = normalizeDayHours(hours[key]);
  const times = [];
  if(!h.ouvert) return times;
  h.ranges.forEach(function(r){
    const [openH,openM] = r.de.split(':').map(Number);
    const [closeH,closeM] = r.a.split(':').map(Number);
    if(isNaN(openH)||isNaN(closeH)) return;
    let cursor = new Date(d); cursor.setHours(openH, openM, 0, 0);
    const slotEnd = new Date(d); slotEnd.setHours(closeH, closeM, 0, 0);
    if(dayOffset===0){
      const rounded = new Date(now);
      const add = rounded.getMinutes() < 30 ? (30-rounded.getMinutes()) : (60-rounded.getMinutes());
      rounded.setMinutes(rounded.getMinutes()+add, 0, 0);
      if(rounded > cursor) cursor = rounded;
    }
    while(cursor < slotEnd){
      const next = new Date(cursor.getTime()+30*60000);
      const end = next > slotEnd ? slotEnd : next;
      times.push(`${pad2(cursor.getHours())}h${pad2(cursor.getMinutes())} à ${pad2(end.getHours())}h${pad2(end.getMinutes())}`);
      cursor = next;
    }
  });
  return times;
}
function onPickupPointChange(){
  selectedPickupPointId = val('pickupPointSelect') || '';
  populatePickupSlots();
}
function populateTimesForSelectedDay(){
  const daySel = document.getElementById('pickupDay');
  if(daySel.value === ''){ document.getElementById('pickupTime').innerHTML = '<option value="">—</option>'; return; }
  const dayOffset = parseInt(daySel.value, 10) || 0;
  const times = getTimesForDayOffset(dayOffset);
  document.getElementById('pickupTime').innerHTML = times.length ? times.map(t=>`<option>${escapeHtml(t)}</option>`).join('') : '<option value="">—</option>';
}
function populatePickupSlots(){
  const wrap = document.getElementById('pickupPointSelectWrap');
  const pts = shop.pickupPoints || [];
  const daySel = document.getElementById('pickupDay');
  const timeSel = document.getElementById('pickupTime');
  const noteEl = document.getElementById('leadTimeNote');
  if(pts.length>0){
    wrap.style.display = 'block';
    const sel = document.getElementById('pickupPointSelect');
    sel.innerHTML = `<option value="">— Choisissez votre lieu de retrait —</option>` +
      `<option value="__shop__">Boutique — ${escapeHtml(shop.adresse||shop.name||'adresse principale')}</option>` +
      pts.map(p=>`<option value="${escapeHtml(p.id)}">${escapeHtml(p.label)} — ${escapeHtml(p.address)}</option>`).join('');
    sel.value = selectedPickupPointId || '';
    if(!selectedPickupPointId){
      daySel.innerHTML = `<option value="">Choisissez d'abord le lieu de retrait</option>`; daySel.disabled = true;
      timeSel.innerHTML = `<option value="">—</option>`; timeSel.disabled = true;
      if(noteEl) noteEl.textContent = '';
      return;
    }
  } else {
    wrap.style.display = 'none';
    selectedPickupPointId = '__shop__';
  }
  daySel.disabled = false; timeSel.disabled = false;
  const base = cartBaseLeadDays();
  const late = base > 0 && cutoffPassed();
  const minDays = base + (late ? 1 : 0);
  const days = getOpenDayOffsets(minDays, 8);
  if(days.length===0){
    daySel.innerHTML = `<option value="">Aucun créneau disponible</option>`; daySel.disabled = true;
    timeSel.innerHTML = `<option value="">—</option>`; timeSel.disabled = true;
    if(noteEl) noteEl.textContent = "Aucun créneau de retrait n'est disponible pour le moment (horaires non renseignés). Contactez directement la boutique.";
    return;
  }
  daySel.innerHTML = days.map(d=>`<option value="${d.offset}">${escapeHtml(d.label)}</option>`).join('');
  populateTimesForSelectedDay();
  if(noteEl){
    let note = '';
    if(base>0) note = `Délai de préparation : ${base} jour${base>1?'s':''} minimum pour un article de votre panier ou le lieu de retrait choisi.`;
    if(late) note += ` Commande passée après l'heure limite de la boutique (${cutoffLabel()}) : un jour supplémentaire s'applique.`;
    noteEl.textContent = note.trim();
  }
}
function closeCartSilently(){
  const bd = document.getElementById('cartBackdrop'); if(bd) bd.classList.remove('open');
  cartIsOpen = false;
}
function closeCart(){
  const wasOpen = cartIsOpen;
  closeCartSilently();
  if(!ROUTING_ENABLED || !wasOpen) return;
  if(history.state && history.state.cart){ history.back(); return; }
  urlSyncReplace = true; syncUrl(); updateHeadForState();
}
function renderCartDrawer(){
  const wrap = document.getElementById('cartItems');
  if(cart.length===0){ wrap.innerHTML = `<div class="empty"><div class="glyph">🧺</div><p>Votre panier est vide.</p></div>`; document.getElementById('cartTotal').textContent=''; toggleValidationEmailField(); return; }
  wrap.innerHTML = cart.map((it,i)=>`
    <div class="drawer-item">
      <div class="di-main">
        ${(function(){ const im = products.find(pp=>pp.id===it.productId) || {}; const src = productImages(im)[0]; return src ? `<span class="di-thumb"><img src="${src}" alt="" loading="lazy"></span>` : ''; })()}
        <div class="di-body">
          <div class="di-name">${it.qty} × ${escapeHtml(it.nom)}</div>
          ${it.options.length ? `<div class="di-opts">${it.options.map(escapeHtml).join(', ')}</div>` : ''}
          ${it.needsValidation ? `<div class="di-opts" style="color:var(--oak-700);">Nécessite la validation de l'artisan</div>` : ''}
          <button class="di-remove" onclick="removeFromCart(${i})">Retirer</button>
        </div>
      </div>
      <div class="di-price">${fmtEuro(it.qty*it.prix)} €</div>
    </div>`).join('');
  const total = cart.reduce((s,i)=>s+i.qty*i.prix,0);
  document.getElementById('cartTotal').innerHTML = `<span>Total</span><span>${fmtEuro(total)} €</span>`;
  toggleValidationEmailField();
}
function toggleValidationEmailField(){
  const total = cart.reduce((s,i)=>s+i.qty*i.prix,0);
  const needsVal = cart.some(it=>it.needsValidation);
  const dep = (typeof paymentsActive === 'function' && paymentsActive() && total >= 1) ? previewDeposit(total) : null;
  const hint = document.getElementById('cartEmailHint');
  if(hint) hint.textContent = (dep !== null || needsVal)
    ? "Pour recevoir le lien de règlement et la réponse de l'artisan. Sans e-mail, vous les retrouverez sur la page de suivi de votre commande : gardez-en le lien."
    : "Pour recevoir la confirmation de votre commande.";
  const note = document.getElementById('cartDepositNote');
  if(note){
    if(dep !== null && cart.length){
      note.style.display = 'block';
      note.innerHTML = `<strong>Acompte de ${fmtEuro(dep)} €</strong> à régler une fois votre commande acceptée par l'artisan, par un lien de paiement sécurisé. Le solde (${fmtEuro(total - dep)} €) se règle au retrait.`;
    } else if(needsVal && cart.length){
      note.style.display = 'block';
      note.innerHTML = "L'artisan doit valider cette commande avant confirmation (au moins un article dépasse la quantité habituelle).";
    } else note.style.display = 'none';
  }
  const btn = document.getElementById('cartSubmitBtn');
  if(btn) btn.textContent = dep !== null ? 'Commande avec obligation de paiement' : 'Valider la commande (réglée sur place)';
  const pn = document.getElementById('cartPayNote');
  if(pn) pn.textContent = dep !== null ? "Aucun paiement n'est demandé maintenant : l'acompte se règle après acceptation de la commande." : 'Le règlement se fait au moment du retrait.';
}
function removeFromCart(i){ cart.splice(i,1); renderCartDrawer(); renderCartBar(); }
async function validateOrder(){
  if(cart.length===0) return;
  if((shop.pickupPoints||[]).length>0 && !selectedPickupPointId){
    alert('Merci de choisir votre lieu de retrait.');
    const s = document.getElementById('pickupPointSelect'); if(s) s.focus();
    return;
  }
  if(val('pickupDay')==='' || val('pickupTime')===''){
    alert("Aucun créneau de retrait n'est disponible pour le moment : contactez directement la boutique.");
    return;
  }
  const needsValidation = cart.some(it=>it.needsValidation);
  let clientEmail = val('cartValidationEmail').trim() || null;
  if(clientEmail && !validEmail(clientEmail)){ alert("Cette adresse e-mail ne semble pas valide. Corrigez-la ou laissez le champ vide."); return; }
  const daySel = document.getElementById('pickupDay');
  const pickupDate = new Date(); pickupDate.setDate(pickupDate.getDate() + (parseInt(daySel.value, 10) || 0));
  const dayLabel = pickupDate.toLocaleDateString('fr-FR', {weekday:'long', day:'numeric', month:'long'});
  const slot = `${dayLabel.charAt(0).toUpperCase() + dayLabel.slice(1)} — ${val('pickupTime')}`; const total = cart.reduce((s,i)=>s+i.qty*i.prix,0);
  const pt = getSelectedPickupPoint();
  const pickupLocation = pt ? `${pt.label} — ${pt.address}` : `Boutique — ${shop.adresse||shop.name||''}`;
  const { data: made, error } = await dbRpc('commande_create', {
    p_shop_id: currentDataId, p_slot: slot, p_total: total, p_items: cart, p_client_email: clientEmail,
    p_pickup_location: pickupLocation, p_pickup_date: ymd(pickupDate), p_needs_validation: needsValidation });
  if(error || !made){ alert("Erreur lors de l'envoi de la commande : " + ((error && error.message) || 'réponse vide')); return; }
  lastOrder = { code: made.code, token: made.token, slot, total, items:[...cart], status: made.status, pending: made.status === 'en_attente',
                acompte: !!made.acompte, depositStatus: 'aucun', depositAmount: 0, pickupLocation, shopName: shop.name, shopSlug: shop.slug };
  rememberOrder({ code: made.code, token: made.token, slug: shop.slug, shop: shop.name });
  cart = []; closeCartSilently(); saveLastOrder();
  screen = 'confirm'; urlSyncReplace = true; render();
}
/* ================= Suivi de commande côté client ================= */
function savedOrders(){ try{ return JSON.parse(localStorage.getItem('lpda_orders') || '[]'); }catch(e){ return []; } }
function rememberOrder(o){
  try{
    const list = savedOrders().filter(x => x.code !== o.code);
    list.unshift({ code: o.code, token: o.token, slug: o.slug, shop: o.shop, at: Date.now() });
    localStorage.setItem('lpda_orders', JSON.stringify(list.slice(0, 15)));
  }catch(e){}
}
function forgetOrder(code){ try{ localStorage.setItem('lpda_orders', JSON.stringify(savedOrders().filter(x => x.code !== code))); }catch(e){} }
function orderTokenFor(code){ const o = savedOrders().find(x => x.code === code); return o ? o.token : null; }
function trackedToLast(d, token){
  return { code: d.code, token, slot: d.slot, total: Number(d.total), items: d.items || [], pickupLocation: d.pickup_location || '',
    status: d.status, pending: d.status === 'en_attente', depositStatus: d.deposit_status, depositAmount: Number(d.deposit_amount) || 0,
    paymentUrl: d.payment_url, paymentExpiresAt: d.payment_expires_at, depositPaidAt: d.deposit_paid_at, cancelledBy: d.cancelled_by,
    source: d.source, shopName: d.shop_name, shopSlug: d.shop_slug, depositRequired: !!d.deposit_required, acompte: !!d.deposit_required || d.deposit_status !== 'aucun' };
}
async function loadTrackedOrder(code, token){
  const { data, error } = await dbRpc('commande_client', { p_code: code, p_token: token });
  if(error || !data) return null;
  return trackedToLast(data, token);
}
function trackingUrl(o){ return window.location.origin + '/' + (o.shopSlug || (shop && shop.slug) || '') + '/commande/' + o.code + '?t=' + o.token; }
async function copyTrackingLink(){
  const url = trackingUrl(lastOrder);
  try{ await navigator.clipboard.writeText(url); alert('Lien de suivi copié. Gardez-le : vous y retrouverez votre commande et le lien de règlement.'); }
  catch(e){ prompt('Copiez ce lien de suivi :', url); }
}
let trackTimer = null;
function stopTracking(){ if(trackTimer){ clearInterval(trackTimer); trackTimer = null; } }
function startTracking(){
  if(trackTimer) return;
  trackTimer = setInterval(async function(){
    if(screen !== 'confirm' || !lastOrder || !lastOrder.token){ stopTracking(); return; }
    const t = await loadTrackedOrder(lastOrder.code, lastOrder.token);
    if(t){ const merged = Object.assign({}, lastOrder, t); if(JSON.stringify(merged) !== JSON.stringify(lastOrder)){ lastOrder = merged; saveLastOrder(); render(); } }
  }, 12000);
}
function cancelRuleText(o){
  if(o.depositStatus === 'payee'){
    const until = o.depositPaidAt ? Date.parse(o.depositPaidAt) + 3600000 : 0;
    if(until && Date.now() <= until){
      const hh = new Date(until).toLocaleTimeString('fr-FR', { hour: '2-digit', minute: '2-digit' });
      return `Vous pouvez annuler : votre acompte de ${fmtEuro(o.depositAmount)} € vous sera remboursé (possible jusqu'à ${hh}, soit une heure après votre paiement).`;
    }
    return `Vous pouvez annuler, mais plus d'une heure s'est écoulée depuis votre paiement : l'acompte de ${fmtEuro(o.depositAmount)} € reste acquis à l'artisan.`;
  }
  return "Vous pouvez annuler cette commande tant qu'elle n'est pas récupérée : aucun paiement n'a été prélevé.";
}
async function cancelMyOrder(){
  const o = lastOrder; if(!o || !o.token) return;
  if(!confirm(cancelRuleText(o) + "\n\nConfirmer l'annulation de la commande " + o.code + " ?")) return;
  const r = await callPaiements({ action: 'cancel_order', code: o.code, token: o.token }, false);
  if(!r.ok){ alert((r.data && r.data.error) || "L'annulation a échoué. Réessayez dans un instant."); return; }
  const t = await loadTrackedOrder(o.code, o.token);
  if(t){ lastOrder = Object.assign({}, lastOrder, t); saveLastOrder(); }
  render();
}
function renderConfirmation(){
  const main = document.getElementById('main');
  let fab = document.getElementById('fabWrap'); if(fab) fab.style.display='none';
  let bar = document.getElementById('cartBar'); if(bar) bar.remove();
  const o = lastOrder;
  const dep = o.depositAmount || 0;
  const exp = o.paymentExpiresAt ? Date.parse(o.paymentExpiresAt) : 0;
  const linkOpen = o.depositStatus === 'lien_envoye' && !!o.paymentUrl && exp > Date.now();
  const justPaid = new URLSearchParams(window.location.search).get('paye') === '1';
  let stamp = '✓', title = 'Commande enregistrée', body = '', extra = '', polling = false, canCancel = false;
  if(o.status === 'annulee'){
    stamp = '✕'; title = 'Commande annulée';
    body = o.depositStatus === 'remboursee' ? `Votre acompte de ${fmtEuro(dep)} € vous est remboursé (quelques jours ouvrés selon votre banque).`
         : o.depositStatus === 'payee' ? `L'acompte de ${fmtEuro(dep)} € reste acquis à l'artisan.`
         : o.cancelledBy === 'artisan' ? "L'artisan a dû annuler votre commande. Aucun paiement n'a été prélevé." : "Aucun paiement n'a été prélevé.";
  } else if(o.status === 'refusee'){
    stamp = '✕'; title = 'Commande non retenue';
    body = "L'artisan n'a pas pu honorer cette commande. Aucun paiement n'a été prélevé.";
  } else if(o.status === 'recuperee'){
    title = 'Commande récupérée'; body = 'Merci pour votre commande !';
  } else if(o.status === 'en_attente'){
    stamp = '⏳'; title = "Commande envoyée, en attente de l'artisan"; polling = true; canCancel = true;
    body = o.acompte
      ? "L'artisan doit accepter votre commande. Une fois acceptée, le lien pour régler l'acompte apparaîtra ici : revenez sur cette page ou gardez son lien."
      : "L'artisan doit valider votre commande. Sa réponse apparaîtra ici : revenez sur cette page ou gardez son lien.";
  } else if(o.depositRequired && o.depositStatus === 'aucun'){
    stamp = '⏳'; title = 'Commande acceptée'; polling = true; canCancel = true;
    body = "L'artisan a accepté votre commande. Le lien de règlement de l'acompte va apparaître ici dans un instant : revenez sur cette page ou gardez son lien.";
  } else if(o.depositStatus === 'lien_envoye' || o.depositStatus === 'expiree'){
    polling = true; canCancel = true;
    if(linkOpen){
      title = "Commande acceptée — réglez l'acompte";
      body = `L'artisan a accepté votre commande. Pour la confirmer, réglez l'acompte de <strong>${fmtEuro(dep)} €</strong> ; le solde (${fmtEuro(o.total - dep)} €) se règle au retrait.`;
      extra = `<a class="btn btn-oak btn-block" style="margin-top:14px;text-align:center;text-decoration:none;display:block;" href="${escapeHtml(o.paymentUrl)}">Régler l'acompte de ${fmtEuro(dep)} €</a>
        <p style="font-size:12.5px;color:var(--ink-soft);margin:8px 0 0;">Lien valable jusqu'au ${escapeHtml(new Date(exp).toLocaleString('fr-FR', { weekday:'long', day:'numeric', month:'long', hour:'2-digit', minute:'2-digit' }))}.</p>`;
    } else {
      stamp = '⏳'; title = 'Lien de paiement expiré';
      body = "Le lien de règlement de l'acompte n'est plus valable. L'artisan peut vous en envoyer un nouveau : revenez sur cette page, il y apparaîtra.";
    }
  } else if(o.depositStatus === 'payee' || o.depositStatus === 'litige'){
    title = 'Acompte reçu — commande confirmée'; canCancel = true;
    body = `Votre acompte de ${fmtEuro(dep)} € est bien reçu. Il reste ${fmtEuro(o.total - dep)} € à régler au retrait. Présentez ce code à l'artisan.`;
  } else {
    canCancel = true;
    body = o.acompte === false || o.depositStatus === 'aucun' ? 'Présentez ce code au retrait de votre commande. Le règlement se fait sur place.' : '';
  }
  if(justPaid && o.depositStatus !== 'payee' && o.status === 'nouvelle'){
    stamp = '⏳'; title = 'Paiement reçu, vérification en cours'; polling = true;
    body = "Merci ! Nous attendons la confirmation de votre banque : cette page se met à jour toute seule.";
    extra = ''; 
  }
  const showCode = !['annulee','refusee'].includes(o.status);
  main.innerHTML = `<div class="confirm">
    <div class="stamp">${stamp}</div><h2>${escapeHtml(title)}</h2>
    ${body ? `<p>${body}</p>` : ''}
    ${extra}
    ${showCode ? `<div class="code">${escapeHtml(o.code)}</div>` : ''}
    <p><strong>Retrait souhaité :</strong> ${escapeHtml(o.slot || '')}</p>
    <p><strong>Lieu :</strong> ${escapeHtml(o.pickupLocation || '')}</p>
    <p><strong>Total :</strong> ${fmtEuro(o.total)} €</p>
    ${o.token ? `<div class="track-box">
      <strong>Retrouvez cette commande à tout moment</strong> avec le lien de suivi : l'état de la commande et, le cas échéant, le lien de règlement y sont toujours à jour.
      <div style="margin-top:10px;"><button class="icon-btn" onclick="copyTrackingLink()">Copier le lien de suivi</button></div>
      ${canCancel ? `<div class="cancel-rule">${escapeHtml(cancelRuleText(o))}</div>
      <div style="margin-top:8px;"><button class="icon-btn" style="color:var(--danger);" onclick="cancelMyOrder()">Annuler ma commande</button></div>` : ''}
    </div>` : ''}
    <div style="margin-top:26px;">
      <button class="btn btn-oak btn-block" onclick="downloadReceipt()">Télécharger le récap (image)</button>
      <button class="btn btn-ghost btn-block" style="margin-top:10px;" onclick="screen='client';clientReturnScreen='directory';render();">Nouvelle commande</button>
    </div>
  </div>`;
  if(polling && o.token) startTracking(); else stopTracking();
}

function downloadDevisRecap(){
  const d = currentDevis;
  if(!d) return;
  const unit = (d.unit && d.unit !== 'unité') ? ' ' + d.unit : '';
  const lines = [];
  lines.push(['Demande', `${d.product_nom} — ${String(d.qty).replace('.', ',')}${unit}`]);
  lines.push(['État', DEVIS_STATUS[d.status] || d.status]);
  if(d.quoted_total != null) lines.push(['Montant proposé', fmtEuro(d.quoted_total) + ' €']);
  if(d.deposit != null) lines.push(['Acompte demandé', fmtEuro(d.deposit) + ' €']);
  if(d.created_at) lines.push(['Demande du', new Date(d.created_at).toLocaleDateString('fr-FR')]);
  const w = 620, headerH = 96;
  const h = headerH + 252 + lines.length*28;
  const canvas = document.createElement('canvas');
  canvas.width = w; canvas.height = h;
  const ctx = canvas.getContext('2d');
  ctx.fillStyle = '#f6f1e6'; ctx.fillRect(0,0,w,h);
  ctx.fillStyle = '#15301f'; ctx.fillRect(0,0,w,headerH);
  ctx.fillStyle = '#e6bd85'; ctx.font = '600 26px Georgia, "Times New Roman", serif';
  ctx.fillText(d.shop_name || 'La Place des Artisans', 26, 58);
  let y = headerH + 46;
  ctx.fillStyle = '#211b12'; ctx.font = '600 21px Georgia, serif';
  ctx.fillText('Récapitulatif de devis', 26, y); y += 42;
  ctx.font = '700 30px Georgia, serif'; ctx.fillStyle = '#a8712f';
  ctx.fillText(d.code, 26, y); y += 34;
  ctx.strokeStyle = 'rgba(33,27,18,0.15)'; ctx.beginPath(); ctx.moveTo(26,y); ctx.lineTo(w-26,y); ctx.stroke(); y += 30;
  lines.forEach(function(L){
    ctx.font = '15px Arial'; ctx.fillStyle = '#5a5142'; ctx.textAlign = 'left';
    ctx.fillText(L[0], 26, y);
    ctx.font = '600 15px Arial'; ctx.fillStyle = '#211b12'; ctx.textAlign = 'right';
    ctx.fillText(L[1], w-26, y); ctx.textAlign = 'left';
    y += 28;
  });
  y += 6;
  ctx.strokeStyle = 'rgba(33,27,18,0.15)'; ctx.beginPath(); ctx.moveTo(26,y); ctx.lineTo(w-26,y); ctx.stroke(); y += 30;
  ctx.font = '13px Arial'; ctx.fillStyle = '#5a5142';
  ctx.fillText('Rouvrez la discussion sur laplacedesartisans.fr', 26, y); y += 22;
  ctx.fillText('avec ce numéro : ' + d.code, 26, y);
  const link = document.createElement('a');
  link.download = 'devis-' + d.code + '.png';
  link.href = canvas.toDataURL('image/png');
  link.click();
}
function downloadReceipt(){
  const o = lastOrder;
  let itemLines = 0;
  o.items.forEach(it=>{ itemLines += 1 + (it.options.length?1:0); });
  const w = 620, headerH = 96;
  const h = headerH + 236 + itemLines*30;
  const canvas = document.createElement('canvas');
  canvas.width = w; canvas.height = h;
  const ctx = canvas.getContext('2d');
  ctx.fillStyle = '#f6f1e6'; ctx.fillRect(0,0,w,h);
  ctx.fillStyle = '#15301f'; ctx.fillRect(0,0,w,headerH);
  ctx.fillStyle = '#e6bd85'; ctx.font = '600 26px Georgia, "Times New Roman", serif';
  ctx.fillText(shop.name || 'La Place des Artisans', 26, 58);
  let y = headerH + 46;
  ctx.fillStyle = '#211b12'; ctx.font = '600 21px Georgia, serif';
  ctx.fillText('Récapitulatif de commande', 26, y); y += 42;
  ctx.font = '700 30px Georgia, serif'; ctx.fillStyle = '#a8712f';
  ctx.fillText('Code : ' + o.code, 26, y); y += 38;
  ctx.font = '15px Arial'; ctx.fillStyle = '#5a5142';
  ctx.fillText('Retrait : ' + o.slot, 26, y); y += 26;
  if(o.pickupLocation){ ctx.fillText('Lieu : ' + o.pickupLocation, 26, y); y += 26; }
  ctx.strokeStyle = 'rgba(33,27,18,0.15)'; ctx.beginPath(); ctx.moveTo(26,y); ctx.lineTo(w-26,y); ctx.stroke(); y += 30;
  o.items.forEach(it=>{
    ctx.font = '600 15px Arial'; ctx.fillStyle = '#211b12'; ctx.textAlign = 'left';
    ctx.fillText(`${it.qty} × ${it.nom}`, 26, y);
    ctx.textAlign = 'right'; ctx.fillText(fmtEuro(it.qty*it.prix)+' €', w-26, y); ctx.textAlign = 'left';
    y += 28;
    if(it.options.length){
      ctx.font = '13px Arial'; ctx.fillStyle = '#5a5142';
      ctx.fillText(it.options.join(', '), 36, y); y += 28;
    }
  });
  ctx.strokeStyle = 'rgba(33,27,18,0.15)'; ctx.beginPath(); ctx.moveTo(26,y); ctx.lineTo(w-26,y); ctx.stroke(); y += 34;
  ctx.font = '700 20px Georgia, serif'; ctx.fillStyle = '#211b12'; ctx.textAlign = 'left';
  ctx.fillText('Total', 26, y);
  ctx.textAlign = 'right'; ctx.fillText(fmtEuro(o.total)+' €', w-26, y); ctx.textAlign = 'left';
  y += 34;
  ctx.font = '13px Arial'; ctx.fillStyle = '#5a5142';
  ctx.fillText('Réglé sur place, au retrait.', 26, y);
  const link = document.createElement('a');
  link.download = 'commande-' + o.code + '.png';
  link.href = canvas.toDataURL('image/png');
  link.click();
}

boot();
</script>

</body>
</html>
