{# =======================================================
JULICO Home Page — with Search & Filter
✅ Place at: templates/produit/index.html.twig
======================================================= #}
<!DOCTYPE html>
<html lang="{{ _locale ?? 'en' }}">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Julico — Where Trust Meets Success</title>
<link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700;800&display=swap" rel="stylesheet">
<link rel="stylesheet" href="/css/julico-home.css">
<style>
/* Global defensive resets to avoid horizontal scroll on mobile */
*, *::before, *::after { box-sizing: border-box; }
html, body { max-width: 100%; overflow-x: hidden; }
img, video, iframe { max-width: 100%; height: auto; }
.prod-card { display:flex; flex-direction:column; }
.prod-card-link { text-decoration:none; color:inherit; display:flex; flex-direction:column; flex:1; }
.prod-card-link:hover { color:inherit; }
.prod-img.out-of-stock img { opacity:0.35; filter:grayscale(60%); }
.out-of-stock-overlay { position:absolute; inset:0; display:flex; align-items:center; justify-content:center; pointer-events:none; }
.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; }
.add-btn { touch-action:manipulation; -webkit-tap-highlight-color:transparent; cursor:pointer; }
.card-qty-row { display:none; align-items:center; border:1.5px solid var(--brand); border-radius:var(--rsm); overflow:hidden; height:34px; }
.card-qty-row.visible { display:flex; }
.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; }
.cq-btn:hover { background:var(--brand); color:white; }
.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; }
/* ── SHOP LOGO IN SUBNAV (replaces the 🏪 emoji when a logo is set) ── */
.snav-logo { width:20px; height:20px; border-radius:5px; object-fit:cover; vertical-align:middle; border:1px solid var(--gray-100); }
/* ── SHOP HEADER BANNER (shown when viewing a single shop) ── */
.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); }
.shop-hero-logo { width:64px; height:64px; border-radius:14px; object-fit:cover; border:1.5px solid var(--gray-100); flex-shrink:0; }
.shop-hero-logo.blank { display:flex; align-items:center; justify-content:center; background:var(--gray-50); font-size:30px; color:var(--gray-300); }
.shop-hero-name { font-size:22px; font-weight:800; color:var(--gray-900); letter-spacing:-0.02em; }
.shop-hero-sub { font-size:13px; color:var(--gray-500); margin-top:2px; }
@media (max-width:480px){
.shop-hero { padding:14px 16px; gap:12px; margin:16px 0; }
.shop-hero-logo { width:52px; height:52px; border-radius:12px; }
.shop-hero-name { font-size:18px; }
}
/* ── ADMIN PILL IN TOP NAV (visible only to ROLE_ADMIN) ── */
.nav-admin-btn {
display: inline-flex; align-items: center; gap: 4px;
background: #fef3c7; color: #92400e;
border: 1.5px solid #fcd34d;
font-size: 13px; font-weight: 800;
padding: 7px 12px; border-radius: var(--rsm);
text-decoration: none; transition: all .2s;
min-height: 38px; white-space: nowrap;
}
.nav-admin-btn:hover { background: #fde68a; color: #78350f; border-color: #f59e0b; }
@media (max-width: 480px) {
.nav-admin-btn { font-size: 11px; padding: 5px 9px; min-height: 36px; gap: 3px; }
}
/* ── VENDOR PILL IN TOP NAV (visible to users linked to a shop) ── */
.nav-vendor-btn {
display: inline-flex; align-items: center; gap: 4px;
background: var(--brand-light); color: var(--brand-dark);
border: 1.5px solid var(--brand-mid);
font-size: 13px; font-weight: 800;
padding: 7px 12px; border-radius: var(--rsm);
text-decoration: none; transition: all .2s;
min-height: 38px; white-space: nowrap;
position: relative;
}
.nav-vendor-btn:hover { background: var(--brand); color: #fff; border-color: var(--brand); }
/* ── NEW-ORDERS BADGE on the "My Shop" button (filled by JS) ── */
.nav-vendor-badge {
display: none;
position: absolute;
top: -7px; right: -7px;
min-width: 18px; height: 18px;
padding: 0 5px;
background: #ef4444; color: #fff;
font-size: 11px; font-weight: 800;
line-height: 18px; text-align: center;
border-radius: 100px;
border: 2px solid #fff;
box-shadow: 0 1px 4px rgba(0,0,0,0.25);
}
.nav-vendor-badge.show { display: inline-block; }
@media (max-width: 480px) {
.nav-vendor-btn { font-size: 11px; padding: 5px 9px; min-height: 36px; gap: 3px; }
}
/* FILTER SIDEBAR */
.shop-layout { display:grid; grid-template-columns:250px 1fr; gap:24px; align-items:start; margin-top:24px; }
.filter-sidebar { position:sticky; top:80px; }
.filter-card { background:var(--white); border:1.5px solid var(--gray-100); border-radius:var(--rlg); padding:20px; box-shadow:var(--shadow-sm); }
.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; }
.filter-section { margin-bottom:18px; padding-bottom:18px; border-bottom:1px solid var(--gray-100); }
.filter-section:last-child { margin-bottom:0; padding-bottom:0; border-bottom:none; }
.filter-lbl { font-size:11px; font-weight:700; color:var(--gray-400); text-transform:uppercase; letter-spacing:0.07em; margin-bottom:10px; }
.cat-check { display:flex; align-items:center; gap:8px; margin-bottom:7px; cursor:pointer; }
.cat-check input { width:15px; height:15px; accent-color:var(--brand); cursor:pointer; flex-shrink:0; }
.cat-check span { font-size:13px; font-weight:500; color:var(--gray-700); cursor:pointer; }
.price-row { display:grid; grid-template-columns:1fr auto 1fr; gap:6px; align-items:center; }
.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; }
.price-input:focus { border-color:var(--brand); background:var(--white); }
.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; }
.promo-check:hover { border-color:var(--brand); background:var(--brand-light); }
.promo-check input { width:16px; height:16px; accent-color:var(--brand); cursor:pointer; }
.promo-check span { font-size:13px; font-weight:700; color:var(--gray-700); }
.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; }
.btn-apply:hover { background:var(--brand-dark); }
.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; }
.btn-clear:hover { border-color:var(--brand); color:var(--brand); }
.sort-bar { display:flex; align-items:center; justify-content:space-between; margin-bottom:16px; flex-wrap:wrap; gap:10px; }
.results-count { font-size:13px; color:var(--gray-500); }
.results-count strong { color:var(--gray-900); font-weight:800; }
.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; }
.sort-select:focus { border-color:var(--brand); }
.active-pills { display:flex; gap:8px; flex-wrap:wrap; margin-bottom:14px; }
.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; }
.filter-badge { background:var(--brand); color:white; font-size:10px; font-weight:800; padding:2px 7px; border-radius:100px; margin-left:6px; }
.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%; }
.filter-toggle-btn:hover { border-color:var(--brand); color:var(--brand); }
.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; }
.store-link:hover { color:var(--brand); }
@media(max-width:900px) {
.shop-layout { grid-template-columns:1fr; }
.filter-sidebar { position:static; display:none; }
.filter-sidebar.open { display:block; }
.filter-toggle-btn { display:flex; }
}
/* ═══════════════════════════════════════════════════════
TABLET / SMALL DESKTOP (≤ 1024px) — soften padding
═══════════════════════════════════════════════════════ */
@media (max-width: 1024px) {
.page { padding-left: 16px !important; padding-right: 16px !important; }
}
/* ═══════════════════════════════════════════════════════
MOBILE (≤ 768px)
═══════════════════════════════════════════════════════ */
@media (max-width: 768px) {
/* PAGE container */
.page { padding: 0 12px !important; }
/* NAVBAR */
.navbar { padding: 8px 12px !important; }
.navbar-inner {
flex-wrap: wrap !important;
gap: 6px !important;
align-items: center !important;
}
.navbar-inner .logo { order: 1; flex-shrink: 0; }
.navbar-inner .logo img { height: 38px !important; }
.navbar-inner .location-btn { display: none !important; }
.navbar-inner .nav-actions {
order: 2;
margin-left: auto;
gap: 6px !important;
}
.navbar-inner .search-wrap {
order: 3;
flex: 1 1 100% !important;
width: 100% !important;
max-width: 100% !important;
margin: 4px 0 0 !important;
}
.navbar-inner .search-wrap input { font-size: 14px !important; }
.nav-actions > .btn-signin,
.nav-actions > .nav-icon-btn { display: inline-flex !important; }
.nav-actions > .btn-signin,
.nav-actions > .btn-register {
font-size: 12px !important;
padding: 6px 10px !important;
min-height: 36px !important;
white-space: nowrap;
}
.nav-actions > .nav-icon-btn {
width: 36px !important;
height: 36px !important;
}
/* SUBNAV — horizontal scroll */
.subnav-inner {
flex-wrap: nowrap !important;
overflow-x: auto !important;
-webkit-overflow-scrolling: touch !important;
scroll-snap-type: x mandatory;
padding: 8px 12px !important;
gap: 6px !important;
}
.subnav-inner::-webkit-scrollbar { display: none; }
.snav-item {
flex-shrink: 0 !important;
scroll-snap-align: start;
font-size: 12px !important;
padding: 7px 12px !important;
white-space: nowrap;
}
/* B2B vendor strip — single column, smaller padding */
.b2b-strip {
grid-template-columns: 1fr !important;
padding: 24px 18px !important;
gap: 24px !important;
border-radius: 16px !important;
}
.b2b-tag { font-size: 11px !important; }
.b2b-h { font-size: 22px !important; line-height: 1.2 !important; }
.b2b-p { font-size: 13px !important; line-height: 1.6 !important; }
.b2b-feats {
grid-template-columns: 1fr 1fr !important;
gap: 12px !important;
}
.b2b-feat { padding: 14px !important; }
.b2b-ficon { font-size: 22px !important; }
.b2b-ft { font-size: 13px !important; }
.b2b-fd { font-size: 11px !important; line-height: 1.45 !important; }
.b2b-btn {
width: 100% !important;
justify-content: center !important;
font-size: 14px !important;
padding: 12px 18px !important;
}
/* PROMO cards strip — single column */
.promo-strip {
grid-template-columns: 1fr !important;
gap: 10px !important;
margin-bottom: 18px !important;
}
.promo-card { padding: 14px !important; }
.p-em { font-size: 28px !important; }
.p-title { font-size: 15px !important; }
.p-sub { font-size: 12px !important; }
/* PWA install banner — stack */
.pwa-banner {
grid-template-columns: 1fr !important;
padding: 24px 18px !important;
gap: 22px !important;
border-radius: 16px !important;
}
.pwa-h { font-size: 22px !important; line-height: 1.2 !important; }
.pwa-p { font-size: 13px !important; }
.pwa-features {
grid-template-columns: 1fr 1fr !important;
gap: 8px !important;
}
.pwa-feat { font-size: 12px !important; padding: 8px 10px !important; }
.pwa-install-btn {
width: 100% !important;
justify-content: center !important;
font-size: 14px !important;
}
.pwa-device-card { padding: 16px !important; }
.pwa-device-name { font-size: 14px !important; }
.pwa-device-browser { font-size: 11px !important; }
/* FOOTER — 2 cols on tablet, content stacked */
.footer-top {
grid-template-columns: 1fr 1fr !important;
gap: 24px !important;
padding: 28px 18px !important;
}
.footer-bottom {
flex-direction: column !important;
gap: 10px !important;
text-align: center !important;
padding: 16px 18px !important;
}
.footer-bname { font-size: 22px !important; }
/* Hero results header */
.page > div[style*="font-size:22px"] { font-size: 18px !important; }
/* CART BAR — override external julico-home.css transform centering */
.cart-bar {
position: fixed !important;
bottom: 16px !important;
left: 16px !important;
right: 16px !important;
width: auto !important;
max-width: calc(100vw - 32px) !important;
transform: none !important;
margin: 0 !important;
box-sizing: border-box !important;
justify-content: center !important;
}
}
/* ═══════════════════════════════════════════════════════
SMALL PHONES (≤ 480px)
═══════════════════════════════════════════════════════ */
@media (max-width: 480px) {
.page { padding: 0 10px !important; }
.cart-bar {
position: fixed !important;
bottom: 12px !important;
left: 12px !important;
right: 12px !important;
width: auto !important;
max-width: calc(100vw - 24px) !important;
transform: none !important;
margin: 0 !important;
padding: 10px 14px !important;
font-size: 12px !important;
box-sizing: border-box !important;
justify-content: center !important;
}
.cart-label { display: none; }
.cart-total { font-size: 14px !important; }
.card-qty-row { height: 32px !important; }
.cq-btn {
width: 30px !important;
height: 30px !important;
min-width: 30px !important;
min-height: 30px !important;
font-size: 16px !important;
}
.cq-num {
min-width: 22px !important;
font-size: 12px !important;
padding: 0 3px !important;
}
.prod-price { font-size: 18px !important; }
.prod-price sup { font-size: 11px !important; }
.prod-card > div:last-child { padding: 0 10px 12px !important; }
.prod-name { font-size: 13px !important; }
.prod-brand { font-size: 10px !important; }
.prod-unit { font-size: 11px !important; }
.prod-grid {
grid-template-columns: 1fr 1fr !important;
gap: 10px !important;
}
.b2b-strip { padding: 20px 14px !important; }
.b2b-h { font-size: 20px !important; }
.b2b-feats { grid-template-columns: 1fr !important; }
.pwa-banner { padding: 20px 14px !important; }
.pwa-h { font-size: 20px !important; }
.footer-top {
grid-template-columns: 1fr !important;
gap: 22px !important;
padding: 24px 16px !important;
}
.footer-socials { justify-content: flex-start !important; }
.filter-toggle-btn { font-size: 13px !important; padding: 10px 14px !important; }
.sort-bar { flex-direction: column !important; align-items: stretch !important; }
.sort-select { width: 100% !important; }
}
/* ═══════════════════════════════════════════════════════
TINY PHONES (≤ 380px)
═══════════════════════════════════════════════════════ */
@media (max-width: 380px) {
.page { padding: 0 8px !important; }
.prod-grid { gap: 8px !important; }
.prod-card > div:last-child { padding: 0 8px 10px !important; }
.b2b-h { font-size: 18px !important; }
.pwa-h { font-size: 18px !important; }
.navbar-inner .logo img { height: 32px !important; }
.nav-actions > .btn-signin,
.nav-actions > .btn-register {
font-size: 11px !important;
padding: 5px 8px !important;
}
}
</style>
<!-- ═════════════ PWA (Progressive Web App) ═════════════ -->
<link rel="manifest" href="/manifest.json">
<meta name="theme-color" content="#00a7b5">
<meta name="mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="default">
<meta name="apple-mobile-web-app-title" content="Julico">
<link rel="apple-touch-icon" href="/images/julico-icon-192.png">
<link rel="apple-touch-icon" sizes="192x192" href="/images/julico-icon-192.png">
<link rel="apple-touch-icon" sizes="512x512" href="/images/julico-icon-512.png">
<!-- ════════════════════════════════════════════════════ -->
</head>
<body>
{# ── Cart session ── #}
{% set panier = app.session.get('panier') ?? [] %}
{% set cartCount = 0 %}
{% set cartTotal = 0 %}
{% for pan in panier %}
{% set cartCount = cartCount + pan.qtt %}
{% for p in produits %}
{% if p.id == pan.id %}
{% set unitPrice = p.prix %}
{% if p.salePrice is defined and p.salePrice and p.salePrice > 0 and p.salePrice < p.prix %}
{% set unitPrice = p.salePrice %}
{% endif %}
{% set cartTotal = cartTotal + (unitPrice * pan.qtt) %}
{% endif %}
{% endfor %}
{% endfor %}
{# ── Safe defaults for filter vars ── #}
{% set currentQ = currentQ ?? '' %}
{% set currentMin = currentMin ?? '' %}
{% set currentMax = currentMax ?? '' %}
{% set currentPromo = currentPromo ?? '' %}
{% set currentCategories = currentCategories ?? [] %}
{% set currentSort = currentSort ?? '' %}
{% set activeFilters = activeFilters ?? 0 %}
{% set allCategories = allCategories ?? [] %}
{# ── Which shop is being viewed (when browsing /home/{shopName}) ── #}
{% set selectedShopName = app.request.attributes.get('shopName') ?: app.request.query.get('shopName') %}
{% set selectedShop = null %}
{% if selectedShopName %}
{% for m in magasins %}{% if m.nom == selectedShopName %}{% set selectedShop = m %}{% endif %}{% endfor %}
{% endif %}
{# ── Is any filter active? ── #}
{% 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) %}
{# ── Is the logged-in user a vendor (linked to ≥1 shop)? ── #}
{% set isVendor = false %}
{% if app.user %}
{% for mu in app.user.magasinsuser %}
{% if mu.linked and mu.magasin %}{% set isVendor = true %}{% endif %}
{% endfor %}
{% endif %}
<!-- NAVBAR -->
<nav class="navbar">
<div class="navbar-inner">
<a href="{{ path('app_home') }}" class="logo">
<img src="/images/julico-logo.png" alt="Julico" style="height:48px;object-fit:contain">
</a>
{# ── PHASE 3 Step 3: Dynamic country pill (replaces hardcoded Beirut, LB) ── #}
<button type="button" class="location-btn" onclick="if(window.openCountryModal){window.openCountryModal();}">
<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>
{% if selectedCountry is defined and selectedCountry %}
{{ selectedCountry.name }}{% if selectedCountry.isoCode %} ({{ selectedCountry.isoCode }}){% endif %}
{% else %}
Choose country
{% endif %}
<span style="color:var(--gray-400);font-size:10px;margin-left:4px">▾</span>
</button>
<div class="search-wrap">
<form action="{{ path('app_home') }}" method="GET">
<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>
<input type="text" name="q" value="{{ currentQ }}" placeholder="Search products, categories…">
<button type="submit" class="search-btn">
<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>
</button>
</form>
</div>
<div class="nav-actions">
<a href="{{ path('app_panier') }}" class="nav-icon-btn" style="position:relative" title="My Cart">
<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>
{% if cartCount > 0 %}<span class="nbadge" id="nav-cart-badge">{{ cartCount }}</span>{% endif %}
</a>
{% if app.user %}
<a href="{{ path('app_orders') }}" class="nav-icon-btn" title="My Orders">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2"/>
<rect x="9" y="3" width="6" height="4" rx="1"/>
<path d="M9 12h6M9 16h4"/>
</svg>
</a>
{# ── Vendor shortcut: visible to users linked to a shop ── #}
{% if isVendor %}
<a href="{{ path('vendor_dashboard') }}" class="nav-vendor-btn" title="My Shop">🏪 My Shop<span class="nav-vendor-badge" id="vendorNewBadge"></span></a>
{% endif %}
{# ── Admin shortcut: visible only to ROLE_ADMIN ── #}
{% if is_granted('ROLE_ADMIN') %}
<a href="{{ path('app_super_admin') }}" class="nav-admin-btn" title="Admin Tools">🛡️ Admin</a>
{% endif %}
<a href="{{ path('app_profile') }}" class="btn-signin">{{ app.user.pseudo }}</a>
<a href="/logout" class="btn-register">Logout</a>
{% else %}
<a href="/login" class="btn-signin">Sign In</a>
<a href="/register" class="btn-register">Register Free</a>
{% endif %}
</div>
</div>
</nav>
<!-- SUBNAV -->
<div class="subnav">
<div class="subnav-inner">
<a href="{{ path('app_home') }}" class="snav-item {% if not filtersActive and selectedShop is null %}active{% endif %}"><span>🏠</span>All</a>
{% for magasin in magasins %}
<a href="{{ path('app_home', {'shopName': magasin.nom}) }}" class="snav-item {% if selectedShop and selectedShop.nom == magasin.nom %}active{% endif %}">
{% if magasin.logoPath %}<img class="snav-logo" src="{{ asset('assets/uploads/logos/' ~ magasin.logoPath) }}" alt="">{% else %}<span>🏪</span>{% endif %}{{ magasin.nom }}
</a>
{% endfor %}
<a href="{{ path('app_home') }}?promo=1" class="snav-item {% if currentPromo %}active{% endif %}"><span>🔥</span>Promotions</a>
</div>
</div>
<div class="page">
{# Shop header when viewing a single shop #}
{% if selectedShop %}
<div class="shop-hero">
{% if selectedShop.logoPath %}
<img class="shop-hero-logo" src="{{ asset('assets/uploads/logos/' ~ selectedShop.logoPath) }}" alt="{{ selectedShop.nom }}">
{% else %}
<div class="shop-hero-logo blank">🏪</div>
{% endif %}
<div>
<div class="shop-hero-name">{{ selectedShop.nom }}</div>
<div class="shop-hero-sub">{{ produits|length }} product{% if produits|length != 1 %}s{% endif %}</div>
</div>
</div>
{% endif %}
{# Hero only when no filters active and not viewing a single shop #}
{% if not filtersActive and selectedShop is null %}
<!-- VENDOR SUBSCRIPTION (at top of page) -->
<div class="b2b-strip" style="margin-bottom:24px">
<div>
<div class="b2b-tag">💼 Sell on Julico</div>
<div class="b2b-h">List your products<br>for <span>$15/month</span></div>
<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>
<div style="margin:14px 0 20px">
<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>
<div style="display:flex;gap:8px;flex-wrap:wrap">
<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>
<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>
<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>
</div>
</div>
<a href="{{ path('seller_apply') }}" class="b2b-btn">{{ isVendor ? 'Add another shop →' : 'Become a Seller →' }}</a>
</div>
<div class="b2b-feats">
<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>
<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>
<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>
<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>
</div>
</div>
<div class="promo-strip">
<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>
<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>
<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>
</div>
{% endif %}
{# Cart session for JS #}
<script>
window.CART_SESSION = {
{% for pan in panier %}
"{{ pan.id }}": {{ pan.qtt }}{% if not loop.last %},{% endif %}
{% endfor %}
};
</script>
{# Results header when filtering #}
{% if filtersActive %}
<div style="margin:24px 0 16px">
<div style="font-size:11px;font-weight:700;color:var(--brand);text-transform:uppercase;letter-spacing:0.08em;margin-bottom:3px">Search Results</div>
<div style="font-size:22px;font-weight:800;color:var(--gray-900);letter-spacing:-0.02em">
{% if currentQ is not empty %}Results for "{{ currentQ }}"
{% elseif currentPromo %}🔥 Active Promotions
{% else %}Filtered Products{% endif %}
<span style="font-size:15px;font-weight:500;color:var(--gray-400)">({{ produits|length }})</span>
</div>
</div>
{% endif %}
{# Mobile filter toggle #}
<button class="filter-toggle-btn" onclick="toggleFilters()" style="margin-top:{{ filtersActive ? '0' : '24px' }}">
🔧 Filters {% if activeFilters > 0 %}<span class="filter-badge">{{ activeFilters }}</span>{% endif %}
</button>
<div class="shop-layout" style="margin-top:{{ filtersActive ? '0' : '24px' }}">
{# ── FILTER SIDEBAR ── #}
<div class="filter-sidebar" id="filter-sidebar">
<form action="{{ path('app_home') }}" method="GET" id="filter-form">
{# Preserve search query in filters #}
{% if currentQ is not empty %}
<input type="hidden" name="q" value="{{ currentQ }}">
{% endif %}
<div class="filter-card">
<div class="filter-title">
🔧 Filters
{% if activeFilters > 0 %}
<a href="{{ path('app_home') }}" style="font-size:11px;font-weight:600;color:var(--brand);text-decoration:none">Clear all</a>
{% endif %}
</div>
{# PROMO #}
<div class="filter-section">
<div class="filter-lbl">Deals</div>
<label class="promo-check" style="{{ currentPromo ? 'border-color:var(--brand);background:var(--brand-light)' : '' }}">
<input type="checkbox" name="promo" value="1" {{ currentPromo ? 'checked' : '' }} onchange="this.form.submit()">
<span>🔥 Promotions Only</span>
</label>
</div>
{# CATEGORIES #}
{% if allCategories|length > 0 %}
<div class="filter-section">
<div class="filter-lbl">Category</div>
{% for cat in allCategories %}
<label class="cat-check">
<input type="checkbox" name="categories[]" value="{{ cat.id }}"
{% if currentCategories is iterable %}
{% for cid in currentCategories %}
{% if cid == cat.id %}checked{% endif %}
{% endfor %}
{% endif %}
>
<span>{{ cat.nom }}</span>
</label>
{% endfor %}
</div>
{% endif %}
{# PRICE #}
<div class="filter-section">
<div class="filter-lbl">Price Range ($)</div>
<div class="price-row">
<input type="number" name="min" class="price-input" placeholder="Min" value="{{ currentMin }}" min="0">
<span style="font-size:12px;color:var(--gray-400);text-align:center">—</span>
<input type="number" name="max" class="price-input" placeholder="Max" value="{{ currentMax }}" min="0">
</div>
</div>
{# STORES #}
<div class="filter-section">
<div class="filter-lbl">Store</div>
{% for magasin in magasins %}
<label class="cat-check">
<input type="checkbox" name="shopName" value="{{ magasin.nom }}"
{% if app.request.query.get('shopName') == magasin.nom %}checked{% endif %}>
<span>🏪 {{ magasin.nom }}</span>
</label>
{% endfor %}
</div>
<button type="submit" class="btn-apply">Apply Filters</button>
<a href="{{ path('app_home') }}" class="btn-clear">Reset</a>
</div>
</form>
</div>
{# ── PRODUCTS AREA ── #}
<div>
{# Active filter pills #}
{% if activeFilters > 0 %}
<div class="active-pills">
{% if currentQ is not empty %}
<a href="{{ path('app_home') }}?promo={{ currentPromo }}&min={{ currentMin }}&max={{ currentMax }}" class="pill">🔍 "{{ currentQ }}" ×</a>
{% endif %}
{% if currentPromo %}
<a href="{{ path('app_home') }}?q={{ currentQ }}&min={{ currentMin }}&max={{ currentMax }}" class="pill">🔥 Promo ×</a>
{% endif %}
{% if currentMin is not empty %}
<a href="{{ path('app_home') }}?q={{ currentQ }}&promo={{ currentPromo }}&max={{ currentMax }}" class="pill">Min ${{ currentMin }} ×</a>
{% endif %}
{% if currentMax is not empty %}
<a href="{{ path('app_home') }}?q={{ currentQ }}&promo={{ currentPromo }}&min={{ currentMin }}" class="pill">Max ${{ currentMax }} ×</a>
{% endif %}
</div>
{% endif %}
{# Sort + count bar #}
<div class="sort-bar">
<span class="results-count"><strong>{{ produits|length }}</strong> product{% if produits|length != 1 %}s{% endif %} found</span>
<select class="sort-select" onchange="applySort(this.value)">
<option value="">Sort: Default</option>
<option value="price_asc" {% if currentSort == 'price_asc' %}selected{% endif %}>Price: Low to High</option>
<option value="price_desc" {% if currentSort == 'price_desc' %}selected{% endif %}>Price: High to Low</option>
<option value="newest" {% if currentSort == 'newest' %}selected{% endif %}>Newest First</option>
<option value="name_asc" {% if currentSort == 'name_asc' %}selected{% endif %}>Name: A–Z</option>
</select>
</div>
{# Products #}
{% if produits|length > 0 %}
<div class="prod-grid">
{% for produit in produits %}
{% set qttValue = produit.qtt is defined ? produit.qtt : null %}
{% set isOutOfStock = (qttValue is null) or (qttValue <= 0) %}
{% set unitPrice = produit.prix %}
{% if produit.salePrice is defined and produit.salePrice and produit.salePrice > 0 and produit.salePrice < produit.prix %}
{% set unitPrice = produit.salePrice %}
{% endif %}
<div class="prod-card" style="{% if isOutOfStock %}opacity:0.85{% endif %}" data-product-id="{{ produit.id }}">
<a href="{{ path('show_produit', {'id': produit.id}) }}" class="prod-card-link">
<div class="prod-img {% if isOutOfStock %}out-of-stock{% endif %}">
{% set imgs = produit.images %}
{% if imgs is not empty %}
<img src="{{ asset('assets/uploads/products/' ~ imgs|first.fileName) }}"
alt="{{ produit.productName }}"
loading="lazy"
onerror="this.onerror=null;this.src='{{ asset('uploads/products/' ~ imgs|first.fileName) }}'">
{% else %}
<div style="font-size:40px;opacity:0.2{% if isOutOfStock %};filter:grayscale(80%){% endif %}">📦</div>
{% endif %}
{% if produit.promo and not isOutOfStock %}<span class="pbadge badge-sale">PROMO</span>{% endif %}
{% if isOutOfStock %}<div class="out-of-stock-overlay"><span class="out-of-stock-label">Out of Stock</span></div>{% endif %}
</div>
<div class="prod-body">
<div class="prod-brand">
{% if produit.category is defined and produit.category %}{{ produit.category.nom }}{% endif %}
{% if produit.magasin is defined and produit.magasin %} · {{ produit.magasin.nom }}{% endif %}
</div>
<div class="prod-name" style="{% if isOutOfStock %}color:var(--gray-400){% endif %}">{{ produit.productName }}</div>
<div class="prod-unit">{{ produit.description|slice(0,40) }}{% if produit.description|length > 40 %}…{% endif %}</div>
</div>
</a>
<div style="padding:0 14px 14px;display:flex;align-items:center;justify-content:space-between;margin-top:auto;position:relative;z-index:5;">
<div>
{% if produit.salePrice is defined and produit.salePrice and produit.salePrice > 0 and produit.salePrice < produit.prix %}
<div class="prod-price" style="{% if isOutOfStock %}color:var(--gray-400){% endif %}"><sup>$</sup>{{ produit.salePrice|number_format(2) }}</div>
<div class="prod-old">${{ produit.prix|number_format(2) }}</div>
{% else %}
<div class="prod-price" style="{% if isOutOfStock %}color:var(--gray-400){% endif %}"><sup>$</sup>{{ produit.prix|number_format(2) }}</div>
{% endif %}
</div>
{% if isOutOfStock %}
<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>
{% else %}
<div class="card-qty-row" id="qty-row-{{ produit.id }}">
<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>
<span class="cq-num" id="qty-num-{{ produit.id }}">0</span>
<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>
</div>
<button type="button" class="add-btn" id="add-btn-{{ produit.id }}"
data-action="{{ path('panier_add', {'id': produit.id, 'idVariante': 'notdefined', 'option': 'home'}) }}"
data-csrf="{{ csrf_token('authenticate') }}"
data-id="{{ produit.id }}"
data-price="{{ unitPrice }}"
title="Add to cart">+</button>
{% endif %}
</div>
</div>
{% endfor %}
</div>
{# Pagination #}
{% if previous is defined and next is defined %}
{% if previous >= 0 or next < produits|length %}
<div style="display:flex;justify-content:center;gap:12px;margin-top:32px">
{% if previous >= 0 %}<a href="{{ path('app_home') }}?offset={{ previous }}&q={{ currentQ }}&promo={{ currentPromo }}&min={{ currentMin }}&max={{ currentMax }}" class="see-all">← Previous</a>{% endif %}
{% 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 %}
</div>
{% endif %}
{% endif %}
{% else %}
<div style="text-align:center;padding:60px 20px;color:var(--gray-400)">
<div style="font-size:48px;margin-bottom:16px">🔍</div>
<div style="font-size:18px;font-weight:700;color:var(--gray-600)">No products found</div>
<div style="margin-top:8px;margin-bottom:20px">Try adjusting your search or filters</div>
<a href="{{ path('app_home') }}" class="see-all" style="display:inline-flex">Clear all filters</a>
</div>
{% endif %}
</div>{# /products area #}
</div>{# /shop-layout #}
{% if not filtersActive and selectedShop is null %}
<!-- PWA -->
<div class="pwa-banner">
<div class="pwa-left">
<div class="pwa-chip"><span class="pwa-chip-dot"></span> <span id="pwa-chip-text">Web App — No App Store Needed</span></div>
<div class="pwa-h" id="pwa-headline">Install Julico on<br>Your Device, Instantly</div>
<div class="pwa-p" id="pwa-desc">Get the full Julico experience right from your browser.</div>
<div class="pwa-features">
<div class="pwa-feat"><span class="pwa-feat-icon">⚡</span> Instant Load</div>
<div class="pwa-feat"><span class="pwa-feat-icon">📴</span> Works Offline</div>
<div class="pwa-feat"><span class="pwa-feat-icon">🔔</span> Push Alerts</div>
<div class="pwa-feat"><span class="pwa-feat-icon">🔒</span> Secure</div>
</div>
<button class="pwa-install-btn" id="pwa-btn" onclick="handleInstall()">
<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>
<span id="pwa-btn-text">Add to Home Screen</span>
</button>
</div>
<div class="pwa-right">
<div class="pwa-device-card">
<div class="pwa-device-header">
<div class="pwa-device-icon" id="pwa-device-icon">📱</div>
<div>
<div class="pwa-device-name" id="pwa-device-name">Detecting device…</div>
<div class="pwa-device-browser" id="pwa-device-browser">Checking browser…</div>
</div>
<div class="pwa-device-status ready" id="pwa-device-status">Ready</div>
</div>
<div class="pwa-steps-wrap" id="pwa-steps"></div>
</div>
</div>
</div>
<div class="install-toast" id="install-toast"><div class="toast-icon">✓</div><span id="toast-msg">Julico added!</span></div>
{% endif %}
</div>{# /page #}
<!-- FOOTER -->
<footer>
<div class="footer-top">
<div>
<div class="footer-bname">JULI<span>CO</span></div>
<div class="footer-btag">Where Trust Meets Success</div>
<div class="footer-bdesc">Lebanon's leading B2B wholesale platform.</div>
<div class="footer-socials">
<div class="social-btn">f</div><div class="social-btn">in</div>
<div class="social-btn">tw</div><div class="social-btn">ig</div>
</div>
</div>
<div>
<div class="footer-ct">Shop</div>
<a href="{{ path('app_home') }}" class="footer-link">All Products</a>
<a href="{{ path('app_home') }}?promo=1" class="footer-link">Promotions</a>
{% for magasin in magasins %}<a href="{{ path('app_home', {'shopName': magasin.nom}) }}" class="footer-link">{{ magasin.nom }}</a>{% endfor %}
</div>
<div>
<div class="footer-ct">Categories</div>
{% for cat in allCategories|slice(0,6) %}
<a href="{{ path('app_home') }}?categories[]={{ cat.id }}" class="footer-link">{{ cat.nom }}</a>
{% endfor %}
</div>
<div>
<div class="footer-ct">Account</div>
<a href="/register" class="footer-link">Register</a>
<a href="/login" class="footer-link">Sign In</a>
<a href="{{ path('app_panier') }}" class="footer-link">My Cart</a>
{% if app.user %}<a href="{{ path('app_orders') }}" class="footer-link">My Orders</a>{% endif %}
</div>
<div>
<div class="footer-ct">Support</div>
<a href="#" class="footer-link">Help Center</a>
<a href="#" class="footer-link">Contact Us</a>
<a href="{{ path('app_locale', {'_locale': 'en'}) }}" class="footer-link">English</a>
<a href="{{ path('app_locale', {'_locale': 'fr'}) }}" class="footer-link">Français</a>
</div>
</div>
<div class="footer-bottom">
<div class="footer-copy">© {{ "now"|date("Y") }} Julico S.A.R.L. All rights reserved.</div>
<div class="footer-legal"><a href="#">Privacy</a><a href="#">Terms</a></div>
</div>
</footer>
<a href="{{ path('app_panier') }}" class="cart-bar" id="cart-bar"
style="{% if cartCount == 0 %}display:none;{% endif %}text-decoration:none">
<span>🛒</span>
<div class="cart-cnt">{{ cartCount }}</div>
<span class="cart-label">{{ cartCount }} item{% if cartCount != 1 %}s{% endif %} in cart</span>
<span style="color:rgba(255,255,255,0.4)">·</span>
<span class="cart-total" id="cart-total-price">${{ cartTotal|number_format(2) }}</span>
<span style="opacity:0.6">→</span>
</a>
<script src="/js/julico-app.js"></script>
<script>
function toggleFilters() {
document.getElementById('filter-sidebar').classList.toggle('open');
}
function applySort(val) {
var url = new URL(window.location.href);
if (val) { url.searchParams.set('sort', val); }
else { url.searchParams.delete('sort'); }
window.location.href = url.toString();
}
</script>
{# ── NEW-ORDERS BADGE on "My Shop": fetch count, show when > 0 ── #}
{% if isVendor %}
<script>
(function () {
var badge = document.getElementById('vendorNewBadge');
if (!badge) return;
fetch('{{ path('vendor_new_orders_count') }}?_=' + Date.now(), {
credentials: 'same-origin',
cache: 'no-store',
headers: { 'X-Requested-With': 'XMLHttpRequest' }
})
.then(function (r) { return r.json(); })
.then(function (d) {
if (d && d.ok && d.count > 0) {
badge.textContent = d.count > 99 ? '99+' : d.count;
badge.classList.add('show');
var btn = badge.closest('.nav-vendor-btn');
if (btn) { btn.setAttribute('title', d.count + ' new order(s) waiting'); }
} else {
badge.classList.remove('show');
badge.textContent = '';
}
})
.catch(function () {});
})();
</script>
{% endif %}
<!-- ═════════════ PWA: Service Worker + Install Handler ═════════════ -->
<script>
if ('serviceWorker' in navigator) {
window.addEventListener('load', function() {
navigator.serviceWorker.register('/sw.js', { scope: '/' })
.then(function(reg) { console.log('[PWA] SW registered:', reg.scope); })
.catch(function(err) { console.warn('[PWA] SW failed:', err); });
});
}
let __deferredPrompt = null;
window.addEventListener('beforeinstallprompt', function(e) {
e.preventDefault();
__deferredPrompt = e;
console.log('[PWA] Install prompt ready — button is live');
var btn = document.getElementById('pwa-btn');
if (btn) {
btn.disabled = false;
btn.style.opacity = '1';
}
});
function handleInstall() {
if (!__deferredPrompt) {
if (window.matchMedia && window.matchMedia('(display-mode: standalone)').matches) {
alert('Julico is already installed! Look for it in your taskbar or applications.');
return;
}
if (/iPhone|iPad|iPod/i.test(navigator.userAgent)) {
alert('To install on iOS:\n1. Tap the Share button (square with up arrow)\n2. Tap "Add to Home Screen"');
return;
}
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…"');
return;
}
__deferredPrompt.prompt();
__deferredPrompt.userChoice.then(function(choice) {
console.log('[PWA] User choice:', choice.outcome);
if (choice.outcome === 'accepted') {
var toast = document.getElementById('install-toast');
if (toast) {
toast.style.display = 'flex';
setTimeout(function() { toast.style.display = 'none'; }, 2500);
}
}
__deferredPrompt = null;
});
}
window.addEventListener('appinstalled', function() {
console.log('[PWA] App installed successfully');
var btn = document.getElementById('pwa-btn');
if (btn) btn.style.display = 'none';
});
</script>
<!-- ════════════════════════════════════════════════════════════════ -->
{# ── PHASE 3 Step 3: Country picker modal — shows on first visit when needsCountryPicker is true ── #}
{% include '_partials/country_picker.html.twig' %}
</body>
</html>