templates/produit/index.html.twig line 1

Open in your IDE?
  1. {# =======================================================
  2.    JULICO Home Page — with Search & Filter
  3.    ✅ Place at: templates/produit/index.html.twig
  4.    ======================================================= #}
  5. <!DOCTYPE html>
  6. <html lang="{{ _locale ?? 'en' }}">
  7. <head>
  8. <meta charset="UTF-8">
  9. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  10. <title>Julico — Where Trust Meets Success</title>
  11. <link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700;800&display=swap" rel="stylesheet">
  12. <link rel="stylesheet" href="/css/julico-home.css">
  13. <style>
  14.   /* Global defensive resets to avoid horizontal scroll on mobile */
  15.   *, *::before, *::after { box-sizing: border-box; }
  16.   html, body { max-width: 100%; overflow-x: hidden; }
  17.   img, video, iframe { max-width: 100%; height: auto; }
  18.   .prod-card { display:flex; flex-direction:column; }
  19.   .prod-card-link { text-decoration:none; color:inherit; display:flex; flex-direction:column; flex:1; }
  20.   .prod-card-link:hover { color:inherit; }
  21.   .prod-img.out-of-stock img { opacity:0.35; filter:grayscale(60%); }
  22.   .out-of-stock-overlay { position:absolute; inset:0; display:flex; align-items:center; justify-content:center; pointer-events:none; }
  23.   .out-of-stock-label { background:#1f2937; color:white; font-size:11px; font-weight:800; letter-spacing:0.08em; text-transform:uppercase; padding:6px 14px; border-radius:100px; }
  24.   .add-btn { touch-action:manipulation; -webkit-tap-highlight-color:transparent; cursor:pointer; }
  25.   .card-qty-row { display:none; align-items:center; border:1.5px solid var(--brand); border-radius:var(--rsm); overflow:hidden; height:34px; }
  26.   .card-qty-row.visible { display:flex; }
  27.   .cq-btn { width:34px; height:34px; background:var(--brand-light); color:var(--brand); border:none; font-size:18px; font-weight:700; cursor:pointer; font-family:var(--font); display:flex; align-items:center; justify-content:center; transition:background .15s; touch-action:manipulation; flex-shrink:0; }
  28.   .cq-btn:hover { background:var(--brand); color:white; }
  29.   .cq-num { min-width:28px; text-align:center; font-size:13px; font-weight:800; color:var(--gray-900); border-left:1px solid var(--brand-mid); border-right:1px solid var(--brand-mid); padding:0 4px; }
  30.   /* ── SHOP LOGO IN SUBNAV (replaces the 🏪 emoji when a logo is set) ── */
  31.   .snav-logo { width:20px; height:20px; border-radius:5px; object-fit:cover; vertical-align:middle; border:1px solid var(--gray-100); }
  32.   /* ── SHOP HEADER BANNER (shown when viewing a single shop) ── */
  33.   .shop-hero { display:flex; align-items:center; gap:16px; background:var(--white); border:1.5px solid var(--gray-100); border-radius:var(--rlg); padding:18px 22px; margin:24px 0; box-shadow:var(--shadow-sm); }
  34.   .shop-hero-logo { width:64px; height:64px; border-radius:14px; object-fit:cover; border:1.5px solid var(--gray-100); flex-shrink:0; }
  35.   .shop-hero-logo.blank { display:flex; align-items:center; justify-content:center; background:var(--gray-50); font-size:30px; color:var(--gray-300); }
  36.   .shop-hero-name { font-size:22px; font-weight:800; color:var(--gray-900); letter-spacing:-0.02em; }
  37.   .shop-hero-sub { font-size:13px; color:var(--gray-500); margin-top:2px; }
  38.   @media (max-width:480px){
  39.     .shop-hero { padding:14px 16px; gap:12px; margin:16px 0; }
  40.     .shop-hero-logo { width:52px; height:52px; border-radius:12px; }
  41.     .shop-hero-name { font-size:18px; }
  42.   }
  43.   /* ── ADMIN PILL IN TOP NAV (visible only to ROLE_ADMIN) ── */
  44.   .nav-admin-btn {
  45.     display: inline-flex; align-items: center; gap: 4px;
  46.     background: #fef3c7; color: #92400e;
  47.     border: 1.5px solid #fcd34d;
  48.     font-size: 13px; font-weight: 800;
  49.     padding: 7px 12px; border-radius: var(--rsm);
  50.     text-decoration: none; transition: all .2s;
  51.     min-height: 38px; white-space: nowrap;
  52.   }
  53.   .nav-admin-btn:hover { background: #fde68a; color: #78350f; border-color: #f59e0b; }
  54.   @media (max-width: 480px) {
  55.     .nav-admin-btn { font-size: 11px; padding: 5px 9px; min-height: 36px; gap: 3px; }
  56.   }
  57.   /* ── VENDOR PILL IN TOP NAV (visible to users linked to a shop) ── */
  58.   .nav-vendor-btn {
  59.     display: inline-flex; align-items: center; gap: 4px;
  60.     background: var(--brand-light); color: var(--brand-dark);
  61.     border: 1.5px solid var(--brand-mid);
  62.     font-size: 13px; font-weight: 800;
  63.     padding: 7px 12px; border-radius: var(--rsm);
  64.     text-decoration: none; transition: all .2s;
  65.     min-height: 38px; white-space: nowrap;
  66.     position: relative;
  67.   }
  68.   .nav-vendor-btn:hover { background: var(--brand); color: #fff; border-color: var(--brand); }
  69.   /* ── NEW-ORDERS BADGE on the "My Shop" button (filled by JS) ── */
  70.   .nav-vendor-badge {
  71.     display: none;
  72.     position: absolute;
  73.     top: -7px; right: -7px;
  74.     min-width: 18px; height: 18px;
  75.     padding: 0 5px;
  76.     background: #ef4444; color: #fff;
  77.     font-size: 11px; font-weight: 800;
  78.     line-height: 18px; text-align: center;
  79.     border-radius: 100px;
  80.     border: 2px solid #fff;
  81.     box-shadow: 0 1px 4px rgba(0,0,0,0.25);
  82.   }
  83.   .nav-vendor-badge.show { display: inline-block; }
  84.   @media (max-width: 480px) {
  85.     .nav-vendor-btn { font-size: 11px; padding: 5px 9px; min-height: 36px; gap: 3px; }
  86.   }
  87.   /* FILTER SIDEBAR */
  88.   .shop-layout { display:grid; grid-template-columns:250px 1fr; gap:24px; align-items:start; margin-top:24px; }
  89.   .filter-sidebar { position:sticky; top:80px; }
  90.   .filter-card { background:var(--white); border:1.5px solid var(--gray-100); border-radius:var(--rlg); padding:20px; box-shadow:var(--shadow-sm); }
  91.   .filter-title { font-size:14px; font-weight:800; color:var(--gray-900); margin-bottom:16px; padding-bottom:12px; border-bottom:1px solid var(--gray-100); display:flex; align-items:center; justify-content:space-between; }
  92.   .filter-section { margin-bottom:18px; padding-bottom:18px; border-bottom:1px solid var(--gray-100); }
  93.   .filter-section:last-child { margin-bottom:0; padding-bottom:0; border-bottom:none; }
  94.   .filter-lbl { font-size:11px; font-weight:700; color:var(--gray-400); text-transform:uppercase; letter-spacing:0.07em; margin-bottom:10px; }
  95.   .cat-check { display:flex; align-items:center; gap:8px; margin-bottom:7px; cursor:pointer; }
  96.   .cat-check input { width:15px; height:15px; accent-color:var(--brand); cursor:pointer; flex-shrink:0; }
  97.   .cat-check span { font-size:13px; font-weight:500; color:var(--gray-700); cursor:pointer; }
  98.   .price-row { display:grid; grid-template-columns:1fr auto 1fr; gap:6px; align-items:center; }
  99.   .price-input { height:36px; border:1.5px solid var(--gray-200); border-radius:var(--rsm); padding:0 10px; font-family:var(--font); font-size:13px; color:var(--gray-800); background:var(--gray-50); outline:none; width:100%; box-sizing:border-box; }
  100.   .price-input:focus { border-color:var(--brand); background:var(--white); }
  101.   .promo-check { display:flex; align-items:center; gap:8px; padding:10px 12px; border:1.5px solid var(--gray-200); border-radius:var(--rsm); cursor:pointer; transition:all .2s; }
  102.   .promo-check:hover { border-color:var(--brand); background:var(--brand-light); }
  103.   .promo-check input { width:16px; height:16px; accent-color:var(--brand); cursor:pointer; }
  104.   .promo-check span { font-size:13px; font-weight:700; color:var(--gray-700); }
  105.   .btn-apply { width:100%; height:42px; background:var(--brand); color:white; border:none; border-radius:var(--rsm); font-family:var(--font); font-size:13px; font-weight:700; cursor:pointer; transition:background .2s; margin-top:16px; }
  106.   .btn-apply:hover { background:var(--brand-dark); }
  107.   .btn-clear { width:100%; height:38px; background:var(--white); color:var(--gray-500); border:1.5px solid var(--gray-200); border-radius:var(--rsm); font-family:var(--font); font-size:12px; font-weight:600; cursor:pointer; transition:all .2s; margin-top:8px; text-decoration:none; display:flex; align-items:center; justify-content:center; }
  108.   .btn-clear:hover { border-color:var(--brand); color:var(--brand); }
  109.   .sort-bar { display:flex; align-items:center; justify-content:space-between; margin-bottom:16px; flex-wrap:wrap; gap:10px; }
  110.   .results-count { font-size:13px; color:var(--gray-500); }
  111.   .results-count strong { color:var(--gray-900); font-weight:800; }
  112.   .sort-select { height:36px; border:1.5px solid var(--gray-200); border-radius:var(--rsm); padding:0 12px; font-family:var(--font); font-size:13px; color:var(--gray-700); background:var(--white); outline:none; cursor:pointer; }
  113.   .sort-select:focus { border-color:var(--brand); }
  114.   .active-pills { display:flex; gap:8px; flex-wrap:wrap; margin-bottom:14px; }
  115.   .pill { display:inline-flex; align-items:center; gap:5px; background:var(--brand-light); border:1px solid var(--brand-mid); color:var(--brand-dark); font-size:11px; font-weight:700; padding:4px 10px; border-radius:100px; text-decoration:none; }
  116.   .filter-badge { background:var(--brand); color:white; font-size:10px; font-weight:800; padding:2px 7px; border-radius:100px; margin-left:6px; }
  117.   .filter-toggle-btn { display:none; align-items:center; gap:8px; background:var(--white); border:1.5px solid var(--gray-200); border-radius:var(--rsm); padding:9px 16px; font-family:var(--font); font-size:13px; font-weight:700; cursor:pointer; color:var(--gray-700); margin-bottom:14px; width:100%; }
  118.   .filter-toggle-btn:hover { border-color:var(--brand); color:var(--brand); }
  119.   .store-link { display:flex; align-items:center; gap:6px; font-size:13px; font-weight:600; color:var(--gray-600); text-decoration:none; padding:6px 0; transition:color .2s; }
  120.   .store-link:hover { color:var(--brand); }
  121.   @media(max-width:900px) {
  122.     .shop-layout { grid-template-columns:1fr; }
  123.     .filter-sidebar { position:static; display:none; }
  124.     .filter-sidebar.open { display:block; }
  125.     .filter-toggle-btn { display:flex; }
  126.   }
  127.   /* ═══════════════════════════════════════════════════════
  128.      TABLET / SMALL DESKTOP (≤ 1024px) — soften padding
  129.      ═══════════════════════════════════════════════════════ */
  130.   @media (max-width: 1024px) {
  131.     .page { padding-left: 16px !important; padding-right: 16px !important; }
  132.   }
  133.   /* ═══════════════════════════════════════════════════════
  134.      MOBILE (≤ 768px)
  135.      ═══════════════════════════════════════════════════════ */
  136.   @media (max-width: 768px) {
  137.     /* PAGE container */
  138.     .page { padding: 0 12px !important; }
  139.     /* NAVBAR */
  140.     .navbar { padding: 8px 12px !important; }
  141.     .navbar-inner {
  142.       flex-wrap: wrap !important;
  143.       gap: 6px !important;
  144.       align-items: center !important;
  145.     }
  146.     .navbar-inner .logo { order: 1; flex-shrink: 0; }
  147.     .navbar-inner .logo img { height: 38px !important; }
  148.     .navbar-inner .location-btn { display: none !important; }
  149.     .navbar-inner .nav-actions {
  150.       order: 2;
  151.       margin-left: auto;
  152.       gap: 6px !important;
  153.     }
  154.     .navbar-inner .search-wrap {
  155.       order: 3;
  156.       flex: 1 1 100% !important;
  157.       width: 100% !important;
  158.       max-width: 100% !important;
  159.       margin: 4px 0 0 !important;
  160.     }
  161.     .navbar-inner .search-wrap input { font-size: 14px !important; }
  162.     .nav-actions > .btn-signin,
  163.     .nav-actions > .nav-icon-btn { display: inline-flex !important; }
  164.     .nav-actions > .btn-signin,
  165.     .nav-actions > .btn-register {
  166.       font-size: 12px !important;
  167.       padding: 6px 10px !important;
  168.       min-height: 36px !important;
  169.       white-space: nowrap;
  170.     }
  171.     .nav-actions > .nav-icon-btn {
  172.       width: 36px !important;
  173.       height: 36px !important;
  174.     }
  175.     /* SUBNAV — horizontal scroll */
  176.     .subnav-inner {
  177.       flex-wrap: nowrap !important;
  178.       overflow-x: auto !important;
  179.       -webkit-overflow-scrolling: touch !important;
  180.       scroll-snap-type: x mandatory;
  181.       padding: 8px 12px !important;
  182.       gap: 6px !important;
  183.     }
  184.     .subnav-inner::-webkit-scrollbar { display: none; }
  185.     .snav-item {
  186.       flex-shrink: 0 !important;
  187.       scroll-snap-align: start;
  188.       font-size: 12px !important;
  189.       padding: 7px 12px !important;
  190.       white-space: nowrap;
  191.     }
  192.     /* B2B vendor strip — single column, smaller padding */
  193.     .b2b-strip {
  194.       grid-template-columns: 1fr !important;
  195.       padding: 24px 18px !important;
  196.       gap: 24px !important;
  197.       border-radius: 16px !important;
  198.     }
  199.     .b2b-tag { font-size: 11px !important; }
  200.     .b2b-h { font-size: 22px !important; line-height: 1.2 !important; }
  201.     .b2b-p { font-size: 13px !important; line-height: 1.6 !important; }
  202.     .b2b-feats {
  203.       grid-template-columns: 1fr 1fr !important;
  204.       gap: 12px !important;
  205.     }
  206.     .b2b-feat { padding: 14px !important; }
  207.     .b2b-ficon { font-size: 22px !important; }
  208.     .b2b-ft { font-size: 13px !important; }
  209.     .b2b-fd { font-size: 11px !important; line-height: 1.45 !important; }
  210.     .b2b-btn {
  211.       width: 100% !important;
  212.       justify-content: center !important;
  213.       font-size: 14px !important;
  214.       padding: 12px 18px !important;
  215.     }
  216.     /* PROMO cards strip — single column */
  217.     .promo-strip {
  218.       grid-template-columns: 1fr !important;
  219.       gap: 10px !important;
  220.       margin-bottom: 18px !important;
  221.     }
  222.     .promo-card { padding: 14px !important; }
  223.     .p-em { font-size: 28px !important; }
  224.     .p-title { font-size: 15px !important; }
  225.     .p-sub { font-size: 12px !important; }
  226.     /* PWA install banner — stack */
  227.     .pwa-banner {
  228.       grid-template-columns: 1fr !important;
  229.       padding: 24px 18px !important;
  230.       gap: 22px !important;
  231.       border-radius: 16px !important;
  232.     }
  233.     .pwa-h { font-size: 22px !important; line-height: 1.2 !important; }
  234.     .pwa-p { font-size: 13px !important; }
  235.     .pwa-features {
  236.       grid-template-columns: 1fr 1fr !important;
  237.       gap: 8px !important;
  238.     }
  239.     .pwa-feat { font-size: 12px !important; padding: 8px 10px !important; }
  240.     .pwa-install-btn {
  241.       width: 100% !important;
  242.       justify-content: center !important;
  243.       font-size: 14px !important;
  244.     }
  245.     .pwa-device-card { padding: 16px !important; }
  246.     .pwa-device-name { font-size: 14px !important; }
  247.     .pwa-device-browser { font-size: 11px !important; }
  248.     /* FOOTER — 2 cols on tablet, content stacked */
  249.     .footer-top {
  250.       grid-template-columns: 1fr 1fr !important;
  251.       gap: 24px !important;
  252.       padding: 28px 18px !important;
  253.     }
  254.     .footer-bottom {
  255.       flex-direction: column !important;
  256.       gap: 10px !important;
  257.       text-align: center !important;
  258.       padding: 16px 18px !important;
  259.     }
  260.     .footer-bname { font-size: 22px !important; }
  261.     /* Hero results header */
  262.     .page > div[style*="font-size:22px"] { font-size: 18px !important; }
  263.     /* CART BAR — override external julico-home.css transform centering */
  264.     .cart-bar {
  265.       position: fixed !important;
  266.       bottom: 16px !important;
  267.       left: 16px !important;
  268.       right: 16px !important;
  269.       width: auto !important;
  270.       max-width: calc(100vw - 32px) !important;
  271.       transform: none !important;
  272.       margin: 0 !important;
  273.       box-sizing: border-box !important;
  274.       justify-content: center !important;
  275.     }
  276.   }
  277.   /* ═══════════════════════════════════════════════════════
  278.      SMALL PHONES (≤ 480px)
  279.      ═══════════════════════════════════════════════════════ */
  280.   @media (max-width: 480px) {
  281.     .page { padding: 0 10px !important; }
  282.     .cart-bar {
  283.       position: fixed !important;
  284.       bottom: 12px !important;
  285.       left: 12px !important;
  286.       right: 12px !important;
  287.       width: auto !important;
  288.       max-width: calc(100vw - 24px) !important;
  289.       transform: none !important;
  290.       margin: 0 !important;
  291.       padding: 10px 14px !important;
  292.       font-size: 12px !important;
  293.       box-sizing: border-box !important;
  294.       justify-content: center !important;
  295.     }
  296.     .cart-label { display: none; }
  297.     .cart-total { font-size: 14px !important; }
  298.     .card-qty-row { height: 32px !important; }
  299.     .cq-btn {
  300.       width: 30px !important;
  301.       height: 30px !important;
  302.       min-width: 30px !important;
  303.       min-height: 30px !important;
  304.       font-size: 16px !important;
  305.     }
  306.     .cq-num {
  307.       min-width: 22px !important;
  308.       font-size: 12px !important;
  309.       padding: 0 3px !important;
  310.     }
  311.     .prod-price { font-size: 18px !important; }
  312.     .prod-price sup { font-size: 11px !important; }
  313.     .prod-card > div:last-child { padding: 0 10px 12px !important; }
  314.     .prod-name { font-size: 13px !important; }
  315.     .prod-brand { font-size: 10px !important; }
  316.     .prod-unit { font-size: 11px !important; }
  317.     .prod-grid {
  318.       grid-template-columns: 1fr 1fr !important;
  319.       gap: 10px !important;
  320.     }
  321.     .b2b-strip { padding: 20px 14px !important; }
  322.     .b2b-h { font-size: 20px !important; }
  323.     .b2b-feats { grid-template-columns: 1fr !important; }
  324.     .pwa-banner { padding: 20px 14px !important; }
  325.     .pwa-h { font-size: 20px !important; }
  326.     .footer-top {
  327.       grid-template-columns: 1fr !important;
  328.       gap: 22px !important;
  329.       padding: 24px 16px !important;
  330.     }
  331.     .footer-socials { justify-content: flex-start !important; }
  332.     .filter-toggle-btn { font-size: 13px !important; padding: 10px 14px !important; }
  333.     .sort-bar { flex-direction: column !important; align-items: stretch !important; }
  334.     .sort-select { width: 100% !important; }
  335.   }
  336.   /* ═══════════════════════════════════════════════════════
  337.      TINY PHONES (≤ 380px)
  338.      ═══════════════════════════════════════════════════════ */
  339.   @media (max-width: 380px) {
  340.     .page { padding: 0 8px !important; }
  341.     .prod-grid { gap: 8px !important; }
  342.     .prod-card > div:last-child { padding: 0 8px 10px !important; }
  343.     .b2b-h { font-size: 18px !important; }
  344.     .pwa-h { font-size: 18px !important; }
  345.     .navbar-inner .logo img { height: 32px !important; }
  346.     .nav-actions > .btn-signin,
  347.     .nav-actions > .btn-register {
  348.       font-size: 11px !important;
  349.       padding: 5px 8px !important;
  350.     }
  351.   }
  352. </style>
  353. <!-- ═════════════ PWA (Progressive Web App) ═════════════ -->
  354. <link rel="manifest" href="/manifest.json">
  355. <meta name="theme-color" content="#00a7b5">
  356. <meta name="mobile-web-app-capable" content="yes">
  357. <meta name="apple-mobile-web-app-capable" content="yes">
  358. <meta name="apple-mobile-web-app-status-bar-style" content="default">
  359. <meta name="apple-mobile-web-app-title" content="Julico">
  360. <link rel="apple-touch-icon" href="/images/julico-icon-192.png">
  361. <link rel="apple-touch-icon" sizes="192x192" href="/images/julico-icon-192.png">
  362. <link rel="apple-touch-icon" sizes="512x512" href="/images/julico-icon-512.png">
  363. <!-- ════════════════════════════════════════════════════ -->
  364. </head>
  365. <body>
  366. {# ── Cart session ── #}
  367. {% set panier = app.session.get('panier') ?? [] %}
  368. {% set cartCount = 0 %}
  369. {% set cartTotal = 0 %}
  370. {% for pan in panier %}
  371.   {% set cartCount = cartCount + pan.qtt %}
  372.   {% for p in produits %}
  373.     {% if p.id == pan.id %}
  374.       {% set unitPrice = p.prix %}
  375.       {% if p.salePrice is defined and p.salePrice and p.salePrice > 0 and p.salePrice < p.prix %}
  376.         {% set unitPrice = p.salePrice %}
  377.       {% endif %}
  378.       {% set cartTotal = cartTotal + (unitPrice * pan.qtt) %}
  379.     {% endif %}
  380.   {% endfor %}
  381. {% endfor %}
  382. {# ── Safe defaults for filter vars ── #}
  383. {% set currentQ          = currentQ ?? '' %}
  384. {% set currentMin        = currentMin ?? '' %}
  385. {% set currentMax        = currentMax ?? '' %}
  386. {% set currentPromo      = currentPromo ?? '' %}
  387. {% set currentCategories = currentCategories ?? [] %}
  388. {% set currentSort       = currentSort ?? '' %}
  389. {% set activeFilters     = activeFilters ?? 0 %}
  390. {% set allCategories     = allCategories ?? [] %}
  391. {# ── Which shop is being viewed (when browsing /home/{shopName}) ── #}
  392. {% set selectedShopName = app.request.attributes.get('shopName') ?: app.request.query.get('shopName') %}
  393. {% set selectedShop = null %}
  394. {% if selectedShopName %}
  395.   {% for m in magasins %}{% if m.nom == selectedShopName %}{% set selectedShop = m %}{% endif %}{% endfor %}
  396. {% endif %}
  397. {# ── Is any filter active? ── #}
  398. {% set filtersActive = (currentQ is not empty) or (currentPromo is not empty) or (currentMin is not empty) or (currentMax is not empty) or (currentCategories is not empty) %}
  399. {# ── Is the logged-in user a vendor (linked to ≥1 shop)? ── #}
  400. {% set isVendor = false %}
  401. {% if app.user %}
  402.   {% for mu in app.user.magasinsuser %}
  403.     {% if mu.linked and mu.magasin %}{% set isVendor = true %}{% endif %}
  404.   {% endfor %}
  405. {% endif %}
  406. <!-- NAVBAR -->
  407. <nav class="navbar">
  408.   <div class="navbar-inner">
  409.     <a href="{{ path('app_home') }}" class="logo">
  410.       <img src="/images/julico-logo.png" alt="Julico" style="height:48px;object-fit:contain">
  411.     </a>
  412.     {# ── PHASE 3 Step 3: Dynamic country pill (replaces hardcoded Beirut, LB) ── #}
  413.     <button type="button" class="location-btn" onclick="if(window.openCountryModal){window.openCountryModal();}">
  414.       <svg viewBox="0 0 20 20" fill="currentColor" style="width:14px;height:14px;color:var(--brand)"><path fill-rule="evenodd" d="M5.05 4.05a7 7 0 119.9 9.9L10 18.9l-4.95-4.95a7 7 0 010-9.9zM10 11a2 2 0 100-4 2 2 0 000 4z" clip-rule="evenodd"/></svg>
  415.       {% if selectedCountry is defined and selectedCountry %}
  416.         {{ selectedCountry.name }}{% if selectedCountry.isoCode %} ({{ selectedCountry.isoCode }}){% endif %}
  417.       {% else %}
  418.         Choose country
  419.       {% endif %}
  420.       <span style="color:var(--gray-400);font-size:10px;margin-left:4px">▾</span>
  421.     </button>
  422.     <div class="search-wrap">
  423.       <form action="{{ path('app_home') }}" method="GET">
  424.         <svg class="search-icon" viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M8 4a4 4 0 100 8 4 4 0 000-8zM2 8a6 6 0 1110.89 3.476l4.817 4.817a1 1 0 01-1.414 1.414l-4.816-4.816A6 6 0 012 8z" clip-rule="evenodd"/></svg>
  425.         <input type="text" name="q" value="{{ currentQ }}" placeholder="Search products, categories…">
  426.         <button type="submit" class="search-btn">
  427.           <svg viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M8 4a4 4 0 100 8 4 4 0 000-8zM2 8a6 6 0 1110.89 3.476l4.817 4.817a1 1 0 01-1.414 1.414l-4.816-4.816A6 6 0 012 8z" clip-rule="evenodd"/></svg>
  428.         </button>
  429.       </form>
  430.     </div>
  431.     <div class="nav-actions">
  432.       <a href="{{ path('app_panier') }}" class="nav-icon-btn" style="position:relative" title="My Cart">
  433.         <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="9" cy="21" r="1"/><circle cx="20" cy="21" r="1"/><path d="M1 1h4l2.68 13.39a2 2 0 0 0 2 1.61h9.72a2 2 0 0 0 2-1.61L23 6H6"/></svg>
  434.         {% if cartCount > 0 %}<span class="nbadge" id="nav-cart-badge">{{ cartCount }}</span>{% endif %}
  435.       </a>
  436.       {% if app.user %}
  437.         <a href="{{ path('app_orders') }}" class="nav-icon-btn" title="My Orders">
  438.           <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  439.             <path d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2"/>
  440.             <rect x="9" y="3" width="6" height="4" rx="1"/>
  441.             <path d="M9 12h6M9 16h4"/>
  442.           </svg>
  443.         </a>
  444.         {# ── Vendor shortcut: visible to users linked to a shop ── #}
  445.         {% if isVendor %}
  446.           <a href="{{ path('vendor_dashboard') }}" class="nav-vendor-btn" title="My Shop">🏪 My Shop<span class="nav-vendor-badge" id="vendorNewBadge"></span></a>
  447.         {% endif %}
  448.         {# ── Admin shortcut: visible only to ROLE_ADMIN ── #}
  449.         {% if is_granted('ROLE_ADMIN') %}
  450.           <a href="{{ path('app_super_admin') }}" class="nav-admin-btn" title="Admin Tools">🛡️ Admin</a>
  451.         {% endif %}
  452.         <a href="{{ path('app_profile') }}" class="btn-signin">{{ app.user.pseudo }}</a>
  453.         <a href="/logout" class="btn-register">Logout</a>
  454.       {% else %}
  455.         <a href="/login" class="btn-signin">Sign In</a>
  456.         <a href="/register" class="btn-register">Register Free</a>
  457.       {% endif %}
  458.     </div>
  459.   </div>
  460. </nav>
  461. <!-- SUBNAV -->
  462. <div class="subnav">
  463.   <div class="subnav-inner">
  464.     <a href="{{ path('app_home') }}" class="snav-item {% if not filtersActive and selectedShop is null %}active{% endif %}"><span>🏠</span>All</a>
  465.     {% for magasin in magasins %}
  466.       <a href="{{ path('app_home', {'shopName': magasin.nom}) }}" class="snav-item {% if selectedShop and selectedShop.nom == magasin.nom %}active{% endif %}">
  467.         {% if magasin.logoPath %}<img class="snav-logo" src="{{ asset('assets/uploads/logos/' ~ magasin.logoPath) }}" alt="">{% else %}<span>🏪</span>{% endif %}{{ magasin.nom }}
  468.       </a>
  469.     {% endfor %}
  470.     <a href="{{ path('app_home') }}?promo=1" class="snav-item {% if currentPromo %}active{% endif %}"><span>🔥</span>Promotions</a>
  471.   </div>
  472. </div>
  473. <div class="page">
  474.   {# Shop header when viewing a single shop #}
  475.   {% if selectedShop %}
  476.   <div class="shop-hero">
  477.     {% if selectedShop.logoPath %}
  478.       <img class="shop-hero-logo" src="{{ asset('assets/uploads/logos/' ~ selectedShop.logoPath) }}" alt="{{ selectedShop.nom }}">
  479.     {% else %}
  480.       <div class="shop-hero-logo blank">🏪</div>
  481.     {% endif %}
  482.     <div>
  483.       <div class="shop-hero-name">{{ selectedShop.nom }}</div>
  484.       <div class="shop-hero-sub">{{ produits|length }} product{% if produits|length != 1 %}s{% endif %}</div>
  485.     </div>
  486.   </div>
  487.   {% endif %}
  488.   {# Hero only when no filters active and not viewing a single shop #}
  489.   {% if not filtersActive and selectedShop is null %}
  490.   <!-- VENDOR SUBSCRIPTION (at top of page) -->
  491.   <div class="b2b-strip" style="margin-bottom:24px">
  492.     <div>
  493.       <div class="b2b-tag">💼 Sell on Julico</div>
  494.       <div class="b2b-h">List your products<br>for <span>$15/month</span></div>
  495.       <div class="b2b-p">Julico is a marketplace platform — we simply <strong>connect buyers with sellers</strong>. You handle your own pricing, transactions, and delivery directly. Flat $15/month. No commission, ever.</div>
  496.       <div style="margin:14px 0 20px">
  497.         <div style="font-size:12px;font-weight:800;color:var(--brand);text-transform:uppercase;letter-spacing:0.08em;margin-bottom:12px">💳 Pay your subscription via</div>
  498.         <div style="display:flex;gap:8px;flex-wrap:wrap">
  499.           <span style="background:#ffffff;color:#0b3640;font-size:13px;font-weight:700;padding:8px 16px;border-radius:100px;white-space:nowrap;box-shadow:0 2px 10px rgba(0,167,181,0.35)">💸 Wish Money</span>
  500.           <span style="background:#ffffff;color:#0b3640;font-size:13px;font-weight:700;padding:8px 16px;border-radius:100px;white-space:nowrap;box-shadow:0 2px 10px rgba(0,167,181,0.35)">🌐 Western Union</span>
  501.           <span style="background:#ffffff;color:#0b3640;font-size:13px;font-weight:700;padding:8px 16px;border-radius:100px;white-space:nowrap;box-shadow:0 2px 10px rgba(0,167,181,0.35)">🏦 Bank Transfer</span>
  502.         </div>
  503.       </div>
  504.       <a href="{{ path('seller_apply') }}" class="b2b-btn">{{ isVendor ? 'Add another shop →' : 'Become a Seller →' }}</a>
  505.     </div>
  506.     <div class="b2b-feats">
  507.       <div class="b2b-feat"><div class="b2b-ficon">💰</div><div class="b2b-ft">Flat $15/month</div><div class="b2b-fd">No commission, no hidden fees. One simple subscription.</div></div>
  508.       <div class="b2b-feat"><div class="b2b-ficon">📦</div><div class="b2b-ft">Unlimited Products</div><div class="b2b-fd">List as many products as you want. Scale freely.</div></div>
  509.       <div class="b2b-feat"><div class="b2b-ficon">🤝</div><div class="b2b-ft">Direct Connection</div><div class="b2b-fd">Buyers contact you directly. You sell, you ship, you keep 100%.</div></div>
  510.       <div class="b2b-feat"><div class="b2b-ficon">🚪</div><div class="b2b-ft">Cancel Anytime</div><div class="b2b-fd">No long-term contracts. Total flexibility.</div></div>
  511.     </div>
  512.   </div>
  513.   <div class="promo-strip">
  514.     <a href="{{ path('app_home') }}?promo=1" class="promo-card pc1"><div class="p-em">🔥</div><div><div class="p-lbl">Limited Time</div><div class="p-title">Active Promotions</div><div class="p-sub">Set by sellers</div></div></a>
  515.     <a href="{{ path('app_home') }}" class="promo-card pc2"><div class="p-em">📦</div><div><div class="p-lbl">New Arrivals</div><div class="p-title">{{ produitsNouveaute|length }} New Products</div><div class="p-sub">Just added by sellers</div></div></a>
  516.     <a href="{{ path('app_home') }}" class="promo-card pc3"><div class="p-em">🌍</div><div><div class="p-lbl">B2B Marketplace</div><div class="p-title">Julico Platform</div><div class="p-sub">Direct buyer-seller</div></div></a>
  517.   </div>
  518.   {% endif %}
  519.   {# Cart session for JS #}
  520.   <script>
  521.     window.CART_SESSION = {
  522.       {% for pan in panier %}
  523.         "{{ pan.id }}": {{ pan.qtt }}{% if not loop.last %},{% endif %}
  524.       {% endfor %}
  525.     };
  526.   </script>
  527.   {# Results header when filtering #}
  528.   {% if filtersActive %}
  529.   <div style="margin:24px 0 16px">
  530.     <div style="font-size:11px;font-weight:700;color:var(--brand);text-transform:uppercase;letter-spacing:0.08em;margin-bottom:3px">Search Results</div>
  531.     <div style="font-size:22px;font-weight:800;color:var(--gray-900);letter-spacing:-0.02em">
  532.       {% if currentQ is not empty %}Results for "{{ currentQ }}"
  533.       {% elseif currentPromo %}🔥 Active Promotions
  534.       {% else %}Filtered Products{% endif %}
  535.       <span style="font-size:15px;font-weight:500;color:var(--gray-400)">({{ produits|length }})</span>
  536.     </div>
  537.   </div>
  538.   {% endif %}
  539.   {# Mobile filter toggle #}
  540.   <button class="filter-toggle-btn" onclick="toggleFilters()" style="margin-top:{{ filtersActive ? '0' : '24px' }}">
  541.     🔧 Filters {% if activeFilters > 0 %}<span class="filter-badge">{{ activeFilters }}</span>{% endif %}
  542.   </button>
  543.   <div class="shop-layout" style="margin-top:{{ filtersActive ? '0' : '24px' }}">
  544.     {# ── FILTER SIDEBAR ── #}
  545.     <div class="filter-sidebar" id="filter-sidebar">
  546.       <form action="{{ path('app_home') }}" method="GET" id="filter-form">
  547.         {# Preserve search query in filters #}
  548.         {% if currentQ is not empty %}
  549.           <input type="hidden" name="q" value="{{ currentQ }}">
  550.         {% endif %}
  551.         <div class="filter-card">
  552.           <div class="filter-title">
  553.             🔧 Filters
  554.             {% if activeFilters > 0 %}
  555.               <a href="{{ path('app_home') }}" style="font-size:11px;font-weight:600;color:var(--brand);text-decoration:none">Clear all</a>
  556.             {% endif %}
  557.           </div>
  558.           {# PROMO #}
  559.           <div class="filter-section">
  560.             <div class="filter-lbl">Deals</div>
  561.             <label class="promo-check" style="{{ currentPromo ? 'border-color:var(--brand);background:var(--brand-light)' : '' }}">
  562.               <input type="checkbox" name="promo" value="1" {{ currentPromo ? 'checked' : '' }} onchange="this.form.submit()">
  563.               <span>🔥 Promotions Only</span>
  564.             </label>
  565.           </div>
  566.           {# CATEGORIES #}
  567.           {% if allCategories|length > 0 %}
  568.           <div class="filter-section">
  569.             <div class="filter-lbl">Category</div>
  570.             {% for cat in allCategories %}
  571.             <label class="cat-check">
  572.               <input type="checkbox" name="categories[]" value="{{ cat.id }}"
  573.                 {% if currentCategories is iterable %}
  574.                   {% for cid in currentCategories %}
  575.                     {% if cid == cat.id %}checked{% endif %}
  576.                   {% endfor %}
  577.                 {% endif %}
  578.               >
  579.               <span>{{ cat.nom }}</span>
  580.             </label>
  581.             {% endfor %}
  582.           </div>
  583.           {% endif %}
  584.           {# PRICE #}
  585.           <div class="filter-section">
  586.             <div class="filter-lbl">Price Range ($)</div>
  587.             <div class="price-row">
  588.               <input type="number" name="min" class="price-input" placeholder="Min" value="{{ currentMin }}" min="0">
  589.               <span style="font-size:12px;color:var(--gray-400);text-align:center">—</span>
  590.               <input type="number" name="max" class="price-input" placeholder="Max" value="{{ currentMax }}" min="0">
  591.             </div>
  592.           </div>
  593.           {# STORES #}
  594.           <div class="filter-section">
  595.             <div class="filter-lbl">Store</div>
  596.             {% for magasin in magasins %}
  597.             <label class="cat-check">
  598.               <input type="checkbox" name="shopName" value="{{ magasin.nom }}"
  599.                 {% if app.request.query.get('shopName') == magasin.nom %}checked{% endif %}>
  600.               <span>🏪 {{ magasin.nom }}</span>
  601.             </label>
  602.             {% endfor %}
  603.           </div>
  604.           <button type="submit" class="btn-apply">Apply Filters</button>
  605.           <a href="{{ path('app_home') }}" class="btn-clear">Reset</a>
  606.         </div>
  607.       </form>
  608.     </div>
  609.     {# ── PRODUCTS AREA ── #}
  610.     <div>
  611.       {# Active filter pills #}
  612.       {% if activeFilters > 0 %}
  613.       <div class="active-pills">
  614.         {% if currentQ is not empty %}
  615.           <a href="{{ path('app_home') }}?promo={{ currentPromo }}&min={{ currentMin }}&max={{ currentMax }}" class="pill">🔍 "{{ currentQ }}" ×</a>
  616.         {% endif %}
  617.         {% if currentPromo %}
  618.           <a href="{{ path('app_home') }}?q={{ currentQ }}&min={{ currentMin }}&max={{ currentMax }}" class="pill">🔥 Promo ×</a>
  619.         {% endif %}
  620.         {% if currentMin is not empty %}
  621.           <a href="{{ path('app_home') }}?q={{ currentQ }}&promo={{ currentPromo }}&max={{ currentMax }}" class="pill">Min ${{ currentMin }} ×</a>
  622.         {% endif %}
  623.         {% if currentMax is not empty %}
  624.           <a href="{{ path('app_home') }}?q={{ currentQ }}&promo={{ currentPromo }}&min={{ currentMin }}" class="pill">Max ${{ currentMax }} ×</a>
  625.         {% endif %}
  626.       </div>
  627.       {% endif %}
  628.       {# Sort + count bar #}
  629.       <div class="sort-bar">
  630.         <span class="results-count"><strong>{{ produits|length }}</strong> product{% if produits|length != 1 %}s{% endif %} found</span>
  631.         <select class="sort-select" onchange="applySort(this.value)">
  632.           <option value="">Sort: Default</option>
  633.           <option value="price_asc"  {% if currentSort == 'price_asc' %}selected{% endif %}>Price: Low to High</option>
  634.           <option value="price_desc" {% if currentSort == 'price_desc' %}selected{% endif %}>Price: High to Low</option>
  635.           <option value="newest"     {% if currentSort == 'newest' %}selected{% endif %}>Newest First</option>
  636.           <option value="name_asc"   {% if currentSort == 'name_asc' %}selected{% endif %}>Name: A–Z</option>
  637.         </select>
  638.       </div>
  639.       {# Products #}
  640.       {% if produits|length > 0 %}
  641.       <div class="prod-grid">
  642.         {% for produit in produits %}
  643.         {% set qttValue = produit.qtt is defined ? produit.qtt : null %}
  644.         {% set isOutOfStock = (qttValue is null) or (qttValue <= 0) %}
  645.         {% set unitPrice = produit.prix %}
  646.         {% if produit.salePrice is defined and produit.salePrice and produit.salePrice > 0 and produit.salePrice < produit.prix %}
  647.           {% set unitPrice = produit.salePrice %}
  648.         {% endif %}
  649.         <div class="prod-card" style="{% if isOutOfStock %}opacity:0.85{% endif %}" data-product-id="{{ produit.id }}">
  650.           <a href="{{ path('show_produit', {'id': produit.id}) }}" class="prod-card-link">
  651.             <div class="prod-img {% if isOutOfStock %}out-of-stock{% endif %}">
  652.               {% set imgs = produit.images %}
  653.               {% if imgs is not empty %}
  654.                 <img src="{{ asset('assets/uploads/products/' ~ imgs|first.fileName) }}"
  655.                      alt="{{ produit.productName }}"
  656.                      loading="lazy"
  657.                      onerror="this.onerror=null;this.src='{{ asset('uploads/products/' ~ imgs|first.fileName) }}'">
  658.               {% else %}
  659.                 <div style="font-size:40px;opacity:0.2{% if isOutOfStock %};filter:grayscale(80%){% endif %}">📦</div>
  660.               {% endif %}
  661.               {% if produit.promo and not isOutOfStock %}<span class="pbadge badge-sale">PROMO</span>{% endif %}
  662.               {% if isOutOfStock %}<div class="out-of-stock-overlay"><span class="out-of-stock-label">Out of Stock</span></div>{% endif %}
  663.             </div>
  664.             <div class="prod-body">
  665.               <div class="prod-brand">
  666.                 {% if produit.category is defined and produit.category %}{{ produit.category.nom }}{% endif %}
  667.                 {% if produit.magasin is defined and produit.magasin %} · {{ produit.magasin.nom }}{% endif %}
  668.               </div>
  669.               <div class="prod-name" style="{% if isOutOfStock %}color:var(--gray-400){% endif %}">{{ produit.productName }}</div>
  670.               <div class="prod-unit">{{ produit.description|slice(0,40) }}{% if produit.description|length > 40 %}…{% endif %}</div>
  671.             </div>
  672.           </a>
  673.           <div style="padding:0 14px 14px;display:flex;align-items:center;justify-content:space-between;margin-top:auto;position:relative;z-index:5;">
  674.             <div>
  675.               {% if produit.salePrice is defined and produit.salePrice and produit.salePrice > 0 and produit.salePrice < produit.prix %}
  676.                 <div class="prod-price" style="{% if isOutOfStock %}color:var(--gray-400){% endif %}"><sup>$</sup>{{ produit.salePrice|number_format(2) }}</div>
  677.                 <div class="prod-old">${{ produit.prix|number_format(2) }}</div>
  678.               {% else %}
  679.                 <div class="prod-price" style="{% if isOutOfStock %}color:var(--gray-400){% endif %}"><sup>$</sup>{{ produit.prix|number_format(2) }}</div>
  680.               {% endif %}
  681.             </div>
  682.             {% if isOutOfStock %}
  683.               <button class="add-btn" disabled style="background:var(--gray-200);color:var(--gray-400);cursor:not-allowed;font-size:11px;width:auto;padding:0 10px">Sold Out</button>
  684.             {% else %}
  685.               <div class="card-qty-row" id="qty-row-{{ produit.id }}">
  686.                 <button type="button" class="cq-btn" data-action="{{ path('panier_minus', {'id': produit.id, 'idVariante': 'notdefined', 'option': 'home'}) }}" data-csrf="{{ csrf_token('authenticate') }}" data-id="{{ produit.id }}" data-price="{{ unitPrice }}" data-type="minus">−</button>
  687.                 <span class="cq-num" id="qty-num-{{ produit.id }}">0</span>
  688.                 <button type="button" class="cq-btn" data-action="{{ path('panier_add', {'id': produit.id, 'idVariante': 'notdefined', 'option': 'home'}) }}" data-csrf="{{ csrf_token('authenticate') }}" data-id="{{ produit.id }}" data-price="{{ unitPrice }}" data-type="plus">+</button>
  689.               </div>
  690.               <button type="button" class="add-btn" id="add-btn-{{ produit.id }}"
  691.                       data-action="{{ path('panier_add', {'id': produit.id, 'idVariante': 'notdefined', 'option': 'home'}) }}"
  692.                       data-csrf="{{ csrf_token('authenticate') }}"
  693.                       data-id="{{ produit.id }}"
  694.                       data-price="{{ unitPrice }}"
  695.                       title="Add to cart">+</button>
  696.             {% endif %}
  697.           </div>
  698.         </div>
  699.         {% endfor %}
  700.       </div>
  701.       {# Pagination #}
  702.       {% if previous is defined and next is defined %}
  703.       {% if previous >= 0 or next < produits|length %}
  704.       <div style="display:flex;justify-content:center;gap:12px;margin-top:32px">
  705.         {% if previous >= 0 %}<a href="{{ path('app_home') }}?offset={{ previous }}&q={{ currentQ }}&promo={{ currentPromo }}&min={{ currentMin }}&max={{ currentMax }}" class="see-all">← Previous</a>{% endif %}
  706.         {% if next < produits|length %}<a href="{{ path('app_home') }}?offset={{ next }}&q={{ currentQ }}&promo={{ currentPromo }}&min={{ currentMin }}&max={{ currentMax }}" class="see-all">Next →</a>{% endif %}
  707.       </div>
  708.       {% endif %}
  709.       {% endif %}
  710.       {% else %}
  711.       <div style="text-align:center;padding:60px 20px;color:var(--gray-400)">
  712.         <div style="font-size:48px;margin-bottom:16px">🔍</div>
  713.         <div style="font-size:18px;font-weight:700;color:var(--gray-600)">No products found</div>
  714.         <div style="margin-top:8px;margin-bottom:20px">Try adjusting your search or filters</div>
  715.         <a href="{{ path('app_home') }}" class="see-all" style="display:inline-flex">Clear all filters</a>
  716.       </div>
  717.       {% endif %}
  718.     </div>{# /products area #}
  719.   </div>{# /shop-layout #}
  720.   {% if not filtersActive and selectedShop is null %}
  721.   <!-- PWA -->
  722.   <div class="pwa-banner">
  723.     <div class="pwa-left">
  724.       <div class="pwa-chip"><span class="pwa-chip-dot"></span> <span id="pwa-chip-text">Web App — No App Store Needed</span></div>
  725.       <div class="pwa-h" id="pwa-headline">Install Julico on<br>Your Device, Instantly</div>
  726.       <div class="pwa-p" id="pwa-desc">Get the full Julico experience right from your browser.</div>
  727.       <div class="pwa-features">
  728.         <div class="pwa-feat"><span class="pwa-feat-icon">⚡</span> Instant Load</div>
  729.         <div class="pwa-feat"><span class="pwa-feat-icon">📴</span> Works Offline</div>
  730.         <div class="pwa-feat"><span class="pwa-feat-icon">🔔</span> Push Alerts</div>
  731.         <div class="pwa-feat"><span class="pwa-feat-icon">🔒</span> Secure</div>
  732.       </div>
  733.       <button class="pwa-install-btn" id="pwa-btn" onclick="handleInstall()">
  734.         <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M12 2v12m0 0l-4-4m4 4l4-4M2 17l.621 2.485A2 2 0 004.561 21h14.878a2 2 0 001.94-1.515L22 17"/></svg>
  735.         <span id="pwa-btn-text">Add to Home Screen</span>
  736.       </button>
  737.     </div>
  738.     <div class="pwa-right">
  739.       <div class="pwa-device-card">
  740.         <div class="pwa-device-header">
  741.           <div class="pwa-device-icon" id="pwa-device-icon">📱</div>
  742.           <div>
  743.             <div class="pwa-device-name" id="pwa-device-name">Detecting device…</div>
  744.             <div class="pwa-device-browser" id="pwa-device-browser">Checking browser…</div>
  745.           </div>
  746.           <div class="pwa-device-status ready" id="pwa-device-status">Ready</div>
  747.         </div>
  748.         <div class="pwa-steps-wrap" id="pwa-steps"></div>
  749.       </div>
  750.     </div>
  751.   </div>
  752.   <div class="install-toast" id="install-toast"><div class="toast-icon">✓</div><span id="toast-msg">Julico added!</span></div>
  753.   {% endif %}
  754. </div>{# /page #}
  755. <!-- FOOTER -->
  756. <footer>
  757.   <div class="footer-top">
  758.     <div>
  759.       <div class="footer-bname">JULI<span>CO</span></div>
  760.       <div class="footer-btag">Where Trust Meets Success</div>
  761.       <div class="footer-bdesc">Lebanon's leading B2B wholesale platform.</div>
  762.       <div class="footer-socials">
  763.         <div class="social-btn">f</div><div class="social-btn">in</div>
  764.         <div class="social-btn">tw</div><div class="social-btn">ig</div>
  765.       </div>
  766.     </div>
  767.     <div>
  768.       <div class="footer-ct">Shop</div>
  769.       <a href="{{ path('app_home') }}" class="footer-link">All Products</a>
  770.       <a href="{{ path('app_home') }}?promo=1" class="footer-link">Promotions</a>
  771.       {% for magasin in magasins %}<a href="{{ path('app_home', {'shopName': magasin.nom}) }}" class="footer-link">{{ magasin.nom }}</a>{% endfor %}
  772.     </div>
  773.     <div>
  774.       <div class="footer-ct">Categories</div>
  775.       {% for cat in allCategories|slice(0,6) %}
  776.         <a href="{{ path('app_home') }}?categories[]={{ cat.id }}" class="footer-link">{{ cat.nom }}</a>
  777.       {% endfor %}
  778.     </div>
  779.     <div>
  780.       <div class="footer-ct">Account</div>
  781.       <a href="/register" class="footer-link">Register</a>
  782.       <a href="/login" class="footer-link">Sign In</a>
  783.       <a href="{{ path('app_panier') }}" class="footer-link">My Cart</a>
  784.       {% if app.user %}<a href="{{ path('app_orders') }}" class="footer-link">My Orders</a>{% endif %}
  785.     </div>
  786.     <div>
  787.       <div class="footer-ct">Support</div>
  788.       <a href="#" class="footer-link">Help Center</a>
  789.       <a href="#" class="footer-link">Contact Us</a>
  790.       <a href="{{ path('app_locale', {'_locale': 'en'}) }}" class="footer-link">English</a>
  791.       <a href="{{ path('app_locale', {'_locale': 'fr'}) }}" class="footer-link">Français</a>
  792.     </div>
  793.   </div>
  794.   <div class="footer-bottom">
  795.     <div class="footer-copy">© {{ "now"|date("Y") }} Julico S.A.R.L. All rights reserved.</div>
  796.     <div class="footer-legal"><a href="#">Privacy</a><a href="#">Terms</a></div>
  797.   </div>
  798. </footer>
  799. <a href="{{ path('app_panier') }}" class="cart-bar" id="cart-bar"
  800.    style="{% if cartCount == 0 %}display:none;{% endif %}text-decoration:none">
  801.   <span>🛒</span>
  802.   <div class="cart-cnt">{{ cartCount }}</div>
  803.   <span class="cart-label">{{ cartCount }} item{% if cartCount != 1 %}s{% endif %} in cart</span>
  804.   <span style="color:rgba(255,255,255,0.4)">·</span>
  805.   <span class="cart-total" id="cart-total-price">${{ cartTotal|number_format(2) }}</span>
  806.   <span style="opacity:0.6">→</span>
  807. </a>
  808. <script src="/js/julico-app.js"></script>
  809. <script>
  810. function toggleFilters() {
  811.   document.getElementById('filter-sidebar').classList.toggle('open');
  812. }
  813. function applySort(val) {
  814.   var url = new URL(window.location.href);
  815.   if (val) { url.searchParams.set('sort', val); }
  816.   else { url.searchParams.delete('sort'); }
  817.   window.location.href = url.toString();
  818. }
  819. </script>
  820. {# ── NEW-ORDERS BADGE on "My Shop": fetch count, show when > 0 ── #}
  821. {% if isVendor %}
  822. <script>
  823. (function () {
  824.   var badge = document.getElementById('vendorNewBadge');
  825.   if (!badge) return;
  826.   fetch('{{ path('vendor_new_orders_count') }}?_=' + Date.now(), {
  827.     credentials: 'same-origin',
  828.     cache: 'no-store',
  829.     headers: { 'X-Requested-With': 'XMLHttpRequest' }
  830.   })
  831.   .then(function (r) { return r.json(); })
  832.   .then(function (d) {
  833.     if (d && d.ok && d.count > 0) {
  834.       badge.textContent = d.count > 99 ? '99+' : d.count;
  835.       badge.classList.add('show');
  836.       var btn = badge.closest('.nav-vendor-btn');
  837.       if (btn) { btn.setAttribute('title', d.count + ' new order(s) waiting'); }
  838.     } else {
  839.       badge.classList.remove('show');
  840.       badge.textContent = '';
  841.     }
  842.   })
  843.   .catch(function () {});
  844. })();
  845. </script>
  846. {% endif %}
  847. <!-- ═════════════ PWA: Service Worker + Install Handler ═════════════ -->
  848. <script>
  849. if ('serviceWorker' in navigator) {
  850.   window.addEventListener('load', function() {
  851.     navigator.serviceWorker.register('/sw.js', { scope: '/' })
  852.       .then(function(reg) { console.log('[PWA] SW registered:', reg.scope); })
  853.       .catch(function(err) { console.warn('[PWA] SW failed:', err); });
  854.   });
  855. }
  856. let __deferredPrompt = null;
  857. window.addEventListener('beforeinstallprompt', function(e) {
  858.   e.preventDefault();
  859.   __deferredPrompt = e;
  860.   console.log('[PWA] Install prompt ready — button is live');
  861.   var btn = document.getElementById('pwa-btn');
  862.   if (btn) {
  863.     btn.disabled = false;
  864.     btn.style.opacity = '1';
  865.   }
  866. });
  867. function handleInstall() {
  868.   if (!__deferredPrompt) {
  869.     if (window.matchMedia && window.matchMedia('(display-mode: standalone)').matches) {
  870.       alert('Julico is already installed! Look for it in your taskbar or applications.');
  871.       return;
  872.     }
  873.     if (/iPhone|iPad|iPod/i.test(navigator.userAgent)) {
  874.       alert('To install on iOS:\n1. Tap the Share button (square with up arrow)\n2. Tap "Add to Home Screen"');
  875.       return;
  876.     }
  877.     alert('To install Julico:\n• Use Chrome or Edge on desktop\n• Look for the install icon in the address bar\n• Or open browser menu → "Install Julico…"');
  878.     return;
  879.   }
  880.   __deferredPrompt.prompt();
  881.   __deferredPrompt.userChoice.then(function(choice) {
  882.     console.log('[PWA] User choice:', choice.outcome);
  883.     if (choice.outcome === 'accepted') {
  884.       var toast = document.getElementById('install-toast');
  885.       if (toast) {
  886.         toast.style.display = 'flex';
  887.         setTimeout(function() { toast.style.display = 'none'; }, 2500);
  888.       }
  889.     }
  890.     __deferredPrompt = null;
  891.   });
  892. }
  893. window.addEventListener('appinstalled', function() {
  894.   console.log('[PWA] App installed successfully');
  895.   var btn = document.getElementById('pwa-btn');
  896.   if (btn) btn.style.display = 'none';
  897. });
  898. </script>
  899. <!-- ════════════════════════════════════════════════════════════════ -->
  900. {# ── PHASE 3 Step 3: Country picker modal — shows on first visit when needsCountryPicker is true ── #}
  901. {% include '_partials/country_picker.html.twig' %}
  902. </body>
  903. </html>