{"id":153231,"date":"2026-10-19T10:41:27","date_gmt":"2026-10-19T08:41:27","guid":{"rendered":"https:\/\/builders24.net\/?page_id=153231"},"modified":"2026-10-20T07:49:27","modified_gmt":"2026-10-20T07:49:27","slug":"shop-online","status":"publish","type":"page","link":"https:\/\/builders24.net\/zw\/shop-online\/","title":{"rendered":"Shop Online"},"content":{"rendered":"\t\t<div data-elementor-type=\"wp-page\" data-elementor-id=\"153231\" class=\"elementor elementor-153231\">\n\t\t\t\t<div class=\"elementor-element elementor-element-6ee5eff e-con e-atomic-element e-flexbox-base e-default-div e-cb0ceb1 \" data-id=\"6ee5eff\" data-element_type=\"e-flexbox\" data-e-type=\"e-flexbox\" data-interaction-id=\"6ee5eff\">\n    \t\t<div class=\"elementor-element elementor-element-f6db27f elementor-widget elementor-widget-html\" data-id=\"f6db27f\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"html.default\">\n\t\t\t\t\t<!DOCTYPE html>\r\n<html lang=\"en-ZW\">\r\n<head>\r\n<meta charset=\"UTF-8\">\r\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\r\n<title>Shop Online in Zimbabwe | Building Materials, Equipment, Tools, Home Goods &amp; More | Builders24 Zimbabwe<\/title>\r\n<meta name=\"description\" content=\"Builders24 Zimbabwe sells building materials, equipment and machinery, tools, home goods, appliances, electronics and more online in Zimbabwe. Builders24 supplies shoppers, contractors and businesses in Zimbabwe from Zimbabwean and international suppliers and delivers to every province in Zimbabwe.\">\r\n<link rel=\"stylesheet\" href=\"https:\/\/app.builders24.net\/b24app.css\">\r\n<meta name=\"robots\" content=\"index, follow\">\r\n\r\n<!-- Open Graph -->\r\n<meta property=\"og:type\" content=\"website\">\r\n<meta property=\"og:site_name\" content=\"Builders24\">\r\n<meta property=\"og:title\" content=\"Shop Online in Zimbabwe | Builders24 Zimbabwe\">\r\n<meta property=\"og:description\" content=\"Builders24 Zimbabwe sells building materials, equipment and machinery, tools, home goods, appliances, electronics and more online in Zimbabwe. Builders24 supplies shoppers, contractors and businesses in Zimbabwe from Zimbabwean and international suppliers and delivers to every province in Zimbabwe.\">\r\n<meta property=\"og:image\" content=\"https:\/\/app.builders24.net\/assets\/og-image.jpg\">\r\n<meta property=\"og:locale\" content=\"en_ZA\">\r\n\r\n<!-- Twitter Card -->\r\n<meta name=\"twitter:card\" content=\"summary_large_image\">\r\n<meta name=\"twitter:title\" content=\"Shop Online in Zimbabwe | Builders24 Zimbabwe\">\r\n<meta name=\"twitter:description\" content=\"Builders24 Zimbabwe sells building materials, equipment and machinery, tools, home goods, appliances, electronics and more online in Zimbabwe. Builders24 supplies shoppers, contractors and businesses in Zimbabwe from Zimbabwean and international suppliers and delivers to every province in Zimbabwe.\">\r\n<meta name=\"twitter:image\" content=\"https:\/\/app.builders24.net\/assets\/og-image.jpg\">\r\n\r\n<link rel=\"preconnect\" href=\"https:\/\/fonts.googleapis.com\">\r\n<link href=\"https:\/\/fonts.googleapis.com\/css2?family=Archivo:wght@400;500;600;700;800&family=Archivo+Narrow:wght@400;500;600;700&display=swap\" rel=\"stylesheet\">\r\n\r\n<script type=\"application\/ld+json\">\r\n{\r\n  \"@context\": \"https:\/\/schema.org\",\r\n  \"@type\": \"CollectionPage\",\r\n  \"name\": \"Shop Online in Zimbabwe \u2014 Builders24 Zimbabwe\",\r\n  \"description\": \"Builders24 Zimbabwe sells building materials, equipment and machinery, tools, home goods, appliances, electronics and more online in Zimbabwe. Builders24 supplies shoppers, contractors and businesses in Zimbabwe from Zimbabwean and international suppliers and delivers to every province in Zimbabwe.\",\r\n  \"isPartOf\": {\r\n    \"@type\": \"Organization\",\r\n    \"name\": \"Builders24\",\r\n    \"url\": \"https:\/\/app.builders24.net\/\"\r\n  }\r\n}\r\n<\/script>\r\n\r\n<style>\r\n  :root{\r\n    --navy:#0D1B3E;\r\n    --orange:#F0A500;\r\n    --grey-bg:#EEF0F3;\r\n    --grey-text:#6B7280;\r\n    --grey-light:#9CA3AF;\r\n    --border:#E7E9EC;\r\n    --white:#FFFFFF;\r\n    --text:#0D1220;\r\n    --pad:16px;\r\n    --amber:#FBBF24;\r\n    --gold:#F2B705;\r\n  }\r\n  *{box-sizing:border-box; margin:0; padding:0;}\r\n  html,body{height:100%;}\r\n  body{ font-family:'Archivo Narrow', Helvetica, Arial, sans-serif; background:#F7F8FA; color:var(--text); -webkit-font-smoothing:antialiased; }\r\n  a{ text-decoration:none; color:inherit; }\r\n  button{ font-family:inherit; border:none; cursor:pointer; background:none; }\r\n  input,select{ font-family:inherit; }\r\n\r\n  \/* ===== Header\/hero \u2014 real replica of Builders24_Retail_Listing design ===== *\/\r\n  .bc-hero{ position:relative; background:var(--navy); overflow:hidden; font-family:'Archivo Narrow',sans-serif; }\r\n  .bc-hero-bg{ position:absolute; inset:0; z-index:0; }\r\n  .bc-hero-bg img{ width:100%; height:100%; object-fit:cover; }\r\n  .bc-hero-gradient{ position:absolute; inset:0; background:linear-gradient(100deg, rgba(13,27,62,0.96) 0%, rgba(13,27,62,0.78) 38%, rgba(13,27,62,0.15) 68%); }\r\n\r\n  .bc-topbar{ position:relative; z-index:1; display:flex; align-items:center; justify-content:space-between; padding:14px var(--pad); border-bottom:1px solid rgba(255,255,255,0.15); flex-wrap:nowrap; gap:8px; }\r\n  .bc-brand{ display:flex; flex-direction:column; min-width:0; flex-shrink:0; }\r\n  .bc-brand-main{ font-family:'Archivo',sans-serif; font-weight:800; font-size:17px; color:#fff; line-height:1.1; white-space:nowrap; }\r\n  .bc-brand-sub{ font-size:9px; font-weight:700; color:#F0A500; letter-spacing:.5px; text-transform:uppercase; text-align:justify; text-align-last:justify; white-space:nowrap; width:100%; }\r\n  .bc-nav{ display:none; align-items:center; gap:26px; }\r\n  .bc-nav a{ color:#fff; font-size:13px; }\r\n  .bc-nav a.bc-nav-active{ color:#F0A500; font-weight:600; }\r\n  .bc-topbar-right{ display:flex; align-items:center; gap:8px; flex-shrink:0; }\r\n  .bc-saved{ display:none; align-items:center; gap:6px; color:#fff; font-size:13px; }\r\n  .bc-login-link{ color:#fff; font-size:13px; font-weight:600; }\r\n  .bc-avatar{ width:30px; height:30px; border-radius:50%; background:#EDEFF3; display:flex; align-items:center; justify-content:center; overflow:hidden; flex-shrink:0; }\r\n  .bc-avatar img{ width:100%; height:100%; object-fit:cover; }\r\n\r\n  .bc-hero-inner{ position:relative; z-index:1; padding:32px var(--pad) 28px; }\r\n  .bc-hero-title{ margin:0; font-family:'Archivo',sans-serif; font-weight:800; font-size:23px; line-height:1.15; color:#fff; }\r\n  .bc-hero-title .accent{ color:#F0A500; }\r\n  .bc-hero-sub{ margin:14px 0 0; font-size:13.5px; color:#D7DEEC; max-width:420px; }\r\n  .bc-trust-row{ display:flex; gap:18px; flex-wrap:wrap; margin-top:22px; }\r\n  .bc-trust-item{ display:flex; align-items:center; gap:8px; }\r\n  .bc-trust-item .diamond{ width:24px; height:24px; border:1.5px solid #ffffff; border-radius:6px; transform:rotate(45deg); display:flex; align-items:center; justify-content:center; flex:none; }\r\n  .bc-trust-item .diamond svg{ transform:rotate(-45deg); width:11px; height:11px; }\r\n  .bc-trust-item span{ font-size:11.5px; color:#D7DEEC; line-height:1.3; }\r\n\r\n  .bc-tabbar{ position:relative; z-index:1; margin-top:26px; padding:0 var(--pad); display:flex; gap:2px; overflow-x:auto; }\r\n  .bc-tab{ background:rgba(255,255,255,0.1); color:rgba(255,255,255,0.85); font-size:12.5px; font-weight:600; padding:12px 18px; border-radius:9px 9px 0 0; cursor:pointer; white-space:nowrap; }\r\n  .bc-tab.active{ background:var(--navy); color:#fff; }\r\n\r\n  .filter-card{ background:#ffffff; border-radius:12px; padding:18px var(--pad); margin:0 var(--pad) 20px; box-shadow:0 8px 24px rgba(13,27,62,0.15); position:relative; z-index:2; }\r\n  .filter-fields{ display:grid; grid-template-columns:1fr; gap:10px; margin-bottom:12px; }\r\n  .filter-fields label{ display:block; font-size:11.5px; color:var(--grey-text); margin-bottom:5px; }\r\n  .filter-fields select, .filter-fields input{ width:100%; box-sizing:border-box; border:1px solid var(--border); border-radius:9px; padding:11px 12px; font-size:13px; font-family:'Archivo Narrow',sans-serif; color:var(--text); background:var(--grey-bg); }\r\n  .filter-submit-btn{ width:100%; background:#F0A500; color:var(--navy); border:none; border-radius:9px; padding:13px; font-size:13.5px; font-weight:700; font-family:'Archivo',sans-serif; }\r\n\r\n  .tab-results{ padding:6px var(--pad) 40px; }\r\n  .tab-results-head h2{ font-size:16px; font-weight:800; color:var(--navy); margin-bottom:14px; }\r\n  .tr-empty{ grid-column:1\/-1; text-align:center; padding:30px 10px; color:var(--grey-text); font-size:13px; }\r\n\r\n  \/* ===== Currency + Delivery selector \u2014 real, standard on this header ===== *\/\r\n  .bc-util{ display:flex; align-items:center; gap:4px; }\r\n  .bc-util-btn{ display:flex; align-items:center; gap:4px; color:#fff; font-size:12px; background:rgba(255,255,255,0.1); border-radius:7px; padding:6px 8px; cursor:pointer; position:relative; }\r\n  .bc-util-dropdown{ display:none; position:absolute; top:calc(100% + 6px); right:0; background:#fff; border-radius:10px; box-shadow:0 12px 28px rgba(13,27,62,0.25); padding:8px; min-width:180px; max-height:280px; overflow-y:auto; z-index:20; }\r\n  .bc-util-dropdown.open{ display:block; }\r\n  .bc-util-item{ padding:8px 10px; font-size:13px; color:var(--navy); border-radius:7px; cursor:pointer; display:flex; justify-content:space-between; gap:8px; }\r\n  .bc-util-item:hover{ background:var(--grey-bg); }\r\n  .bc-util-item.selected{ font-weight:700; color:var(--orange); }\r\n\r\n  \/* ===== Pan-African delivery flag banner \u2014 real, kept from live shop.html ===== *\/\r\n  .brands-section{ padding:16px var(--pad) 6px; }\r\n  @media(min-width:900px){ .brands-section{ padding:24px 40px 12px; } }\r\n  .brands-banner{ position:relative; border-radius:18px; overflow:hidden; min-height:170px; background-image:url('https:\/\/app.builders24.net\/assets\/hero-africa-delivery.png'); background-size:cover; background-position:center; display:flex; align-items:center; padding:20px; flex-wrap:wrap; gap:14px; }\r\n  .brands-overlay{ position:absolute; inset:0; background:linear-gradient(100deg, rgba(9,15,32,0.92) 0%, rgba(9,15,32,0.78) 45%, rgba(9,15,32,0.55) 75%, rgba(9,15,32,0.3) 100%); }\r\n  .brands-text{ position:relative; z-index:2; flex:1; max-width:56%; }\r\n  .sponsored-tag-dark{ font-size:8.5px; font-weight:800; letter-spacing:1.2px; color:#F0A500; margin-bottom:6px; }\r\n  .brands-title{ font-size:16px; font-weight:800; color:#ffffff; line-height:1.28; }\r\n  .brands-link{ font-size:11px; font-weight:700; color:#ffffff; margin-top:10px; display:inline-flex; align-items:center; gap:4px; }\r\n  .brands-card{ position:relative; z-index:2; flex-shrink:0; background:rgba(9,15,32,0.55); backdrop-filter:blur(3px); border-radius:12px; padding:10px; display:flex; flex-wrap:wrap; align-items:center; gap:5px; max-width:40%; max-height:120px; overflow:hidden; border:1px solid rgba(255,255,255,0.15); }\r\n  .flag-item{ width:20px; height:15px; border-radius:2px; object-fit:cover; box-shadow:0 0 0 1px rgba(255,255,255,0.2); }\r\n\r\n  @media(min-width:900px){\r\n    .bc-nav{ display:flex; }\r\n    .bc-saved{ display:flex; }\r\n    .bc-util{ gap:6px; }\r\n    .bc-util-btn{ gap:5px; font-size:12.5px; padding:6px 10px; }\r\n    .bc-topbar{ padding:18px 40px; }\r\n    .bc-hero-inner{ padding:56px 40px 40px; max-width:720px; }\r\n    .bc-hero-title{ font-size:34px; }\r\n    .bc-hero-sub{ font-size:16px; max-width:600px; }\r\n    .bc-tabbar{ padding:0 40px; margin-top:48px; }\r\n    .bc-tab{ font-size:14px; padding:14px 26px; }\r\n    .filter-card{ position:relative; margin:0 40px 20px; width:auto; max-width:none; border-radius:12px; z-index:3; display:flex; align-items:flex-end; gap:16px; padding:24px 32px; }\r\n    .filter-fields{ flex:1; grid-template-columns:repeat(4,1fr); margin-bottom:0; }\r\n    .filter-submit-btn{ width:auto; white-space:nowrap; padding:12px 28px; flex:none; }\r\n    .tab-results{ padding:6px 40px 40px; }\r\n  }\r\n\r\n  \/* ===== Product card \u2014 real, kept verbatim from the live shop.html ===== *\/\r\n  .product-grid-5{ display:grid; grid-template-columns:repeat(2,1fr); gap:14px; }\r\n  @media(min-width:640px){ .product-grid-5{ grid-template-columns:repeat(3,1fr); } }\r\n  @media(min-width:1100px){ .product-grid-5{ grid-template-columns:repeat(5,1fr); } }\r\n  .product-card{\r\n    width:auto; flex:none; border:1px solid var(--border); border-radius:14px; padding:12px;\r\n    display:flex; flex-direction:column; position:relative;\r\n    transition:box-shadow .25s ease, transform .25s ease;\r\n  }\r\n  .product-card:hover{ box-shadow:0 10px 22px rgba(13,27,61,0.1); transform:translateY(-2px); }\r\n  .card-top-row{ display:flex; align-items:flex-start; justify-content:space-between; margin-bottom:6px; }\r\n  .wish-btn{ width:22px; height:22px; display:flex; align-items:center; justify-content:center; }\r\n  .wish-btn.wished svg{ fill:#E63946; stroke:#E63946; }\r\n  .discount-badge{ position:absolute; top:8px; left:8px; z-index:2; background:var(--orange); color:var(--white); font-size:9px; font-weight:800; padding:3px 7px; border-radius:8px; }\r\n  .featured-badge{ position:absolute; top:8px; left:8px; z-index:2; background:var(--navy); color:#ffffff; font-size:9px; font-weight:800; padding:3px 7px; border-radius:8px; }\r\n  .card-image{ width:100%; aspect-ratio:1; border-radius:10px; overflow:hidden; background:var(--grey-bg); margin-bottom:9px; }\r\n  .card-image svg{ width:100%; height:100%; }\r\n  .card-seller{ display:flex; align-items:center; gap:5px; font-size:11px; font-weight:600; color:var(--text); margin-bottom:4px; }\r\n  .card-name{ font-size:12.5px; font-weight:700; line-height:1.32; height:33px; overflow:hidden; display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical; margin-bottom:6px; }\r\n  .card-rating{ display:flex; align-items:center; gap:5px; margin-bottom:6px; }\r\n  .card-rating .stars{ display:inline-flex; gap:1px; }\r\n  .card-rating-count{ font-size:10.5px; color:#8A96B0; }\r\n  .card-price-row{ display:flex; align-items:baseline; gap:5px; margin-bottom:2px; flex-wrap:wrap; }\r\n  .card-price{ font-size:15px; font-weight:800; }\r\n  .card-price sup{ font-size:8.5px; }\r\n  .card-price-strike{ font-size:9px; color:var(--grey-light); text-decoration:line-through; }\r\n  .card-secondary-prices{ font-size:9.5px; color:var(--grey-text); margin-bottom:6px; }\r\n  .card-stock{ font-size:10.5px; font-weight:600; margin-bottom:9px; }\r\n  .card-stock.in{ color:#1F9D55; }\r\n  .card-stock.out{ color:#D64545; }\r\n  .card-bottom-row{ display:flex; gap:6px; margin-top:auto; }\r\n  .qty-stepper{ display:flex; align-items:center; border:1px solid var(--border); border-radius:9px; flex:none; }\r\n  .qty-stepper button{ width:24px; height:32px; background:none; font-size:14px; font-weight:700; color:var(--navy); }\r\n  .qty-stepper span{ width:22px; text-align:center; font-size:12px; font-weight:600; }\r\n  .card-cart-btn{\r\n    background:var(--orange); color:var(--white); font-size:10.5px; font-weight:700; padding:9px 6px;\r\n    border-radius:9px; display:flex; align-items:center; justify-content:center; gap:5px; flex:1;\r\n  }\r\n\r\n  \/* ===================== SHOP: toolbar, filters, results, pagination ===================== *\/\r\n  body{ overflow-x:hidden; }\r\n  .shop-wrap{ padding:16px var(--pad) 8px; max-width:1500px; margin:0 auto; }\r\n  @media(min-width:900px){ .shop-wrap{ padding:28px 40px 8px; } }\r\n\r\n  .shop-toolbar{ display:flex; flex-wrap:wrap; align-items:center; gap:10px; margin-bottom:14px; }\r\n  .shop-search{ flex:1 1 100%; display:flex; align-items:center; gap:8px; background:#fff; border:1.5px solid var(--border); border-radius:12px; padding:0 12px; min-height:46px; min-width:0; }\r\n  .shop-search:focus-within{ border-color:var(--navy); }\r\n  .shop-search input{ flex:1; min-width:0; border:none; outline:none; background:transparent; font-size:14px; color:var(--text); padding:12px 0; }\r\n  .shop-search .s-clear{ width:28px; height:28px; border-radius:50%; display:none; align-items:center; justify-content:center; color:var(--navy); font-size:18px; line-height:1; }\r\n  .shop-search .s-clear.show{ display:flex; }\r\n  .shop-ctl{ min-height:44px; display:flex; align-items:center; gap:8px; background:#fff; border:1.5px solid var(--border); border-radius:12px; padding:0 12px; font-size:13px; font-weight:700; color:var(--navy); min-width:0; }\r\n  .shop-filters-btn{ flex:none; }\r\n  .shop-filters-btn .fcount{ background:var(--orange); color:var(--navy); border-radius:999px; min-width:20px; height:20px; padding:0 6px; font-size:11.5px; display:none; align-items:center; justify-content:center; }\r\n  .shop-filters-btn .fcount.show{ display:inline-flex; }\r\n  .shop-sort{ flex:1 1 140px; }\r\n  .shop-sort select{ flex:1; min-width:0; border:none; background:transparent; font:inherit; font-weight:700; color:var(--navy); outline:none; padding:10px 0; cursor:pointer; }\r\n  .shop-viewtoggle{ display:flex; flex:none; border:1.5px solid var(--border); border-radius:12px; overflow:hidden; background:#fff; }\r\n  .shop-viewtoggle button{ width:44px; height:41px; display:flex; align-items:center; justify-content:center; color:var(--navy); }\r\n  .shop-viewtoggle button.on{ background:var(--navy); color:#fff; }\r\n  .shop-viewtoggle svg{ stroke:currentColor; }\r\n  @media(min-width:900px){\r\n    .shop-search{ flex:1 1 380px; }\r\n    .shop-filters-btn{ display:none; }\r\n    .shop-sort{ flex:0 0 250px; }\r\n  }\r\n  button:focus-visible, select:focus-visible, input:focus-visible, summary:focus-visible{ outline:2px solid var(--navy); outline-offset:2px; }\r\n\r\n  .dept-row{ display:flex; gap:8px; overflow-x:auto; padding:2px 0 10px; margin-bottom:4px; -webkit-overflow-scrolling:touch; }\r\n  .chip{ flex:none; border:1.5px solid var(--border); background:#fff; border-radius:999px; padding:9px 14px; font-size:12.5px; font-weight:700; color:var(--navy); white-space:nowrap; min-height:38px; }\r\n  .chip .n{ font-weight:500; margin-left:6px; }\r\n  .chip.on{ background:var(--navy); color:#fff; border-color:var(--navy); }\r\n  .chip:active, .btn-primary:active, .btn-ghost:active, .pg:active{ transform:translateY(1px); }\r\n\r\n  .active-bar{ display:flex; flex-wrap:wrap; gap:8px; align-items:center; margin-bottom:12px; }\r\n  .active-bar:empty{ display:none; }\r\n  .af-chip{ background:#FFF4D6; border:1px solid var(--orange); color:var(--navy); border-radius:999px; padding:5px 6px 5px 12px; font-size:12px; font-weight:700; display:inline-flex; gap:6px; align-items:center; }\r\n  .af-chip button{ width:22px; height:22px; border-radius:50%; background:#fff; color:var(--navy); font-size:15px; line-height:1; display:flex; align-items:center; justify-content:center; }\r\n  .af-clear{ color:var(--navy); text-decoration:underline; font-weight:700; font-size:12.5px; padding:6px 4px; }\r\n\r\n  .shop-body{ display:block; }\r\n  @media(min-width:900px){ .shop-body{ display:grid; grid-template-columns:280px minmax(0,1fr); gap:28px; align-items:start; } }\r\n  .shop-main{ min-width:0; }\r\n\r\n  \/* Filters: sticky sidebar on desktop, bottom sheet on mobile *\/\r\n  .sheet-overlay{ position:fixed; inset:0; background:rgba(9,15,32,0.55); z-index:300; opacity:0; pointer-events:none; transition:opacity .25s ease; }\r\n  .sheet-overlay.open{ opacity:1; pointer-events:auto; }\r\n  .shop-side{ position:fixed; left:0; right:0; bottom:0; z-index:301; background:#fff; border-radius:18px 18px 0 0; max-height:88vh; display:flex; flex-direction:column; transform:translateY(100%); transition:transform .28s ease; }\r\n  .shop-side.open{ transform:translateY(0); }\r\n  .side-head{ display:flex; justify-content:space-between; align-items:center; padding:14px 16px; border-bottom:1px solid var(--border); font-size:15px; font-weight:800; color:var(--navy); }\r\n  .side-head .close{ width:34px; height:34px; border-radius:50%; background:var(--grey-bg); color:var(--navy); font-size:20px; line-height:1; }\r\n  .side-scroll{ overflow-y:auto; padding:0 16px 12px; flex:1; min-height:0; }\r\n  .side-foot{ display:flex; gap:10px; padding:12px 16px calc(12px + env(safe-area-inset-bottom)); border-top:1px solid var(--border); }\r\n  .side-foot .btn-primary{ flex:1; }\r\n  @media(min-width:900px){\r\n    .sheet-overlay{ display:none; }\r\n    .shop-side{ position:sticky; top:12px; z-index:1; transform:none; border-radius:14px; border:1px solid var(--border); max-height:calc(100vh - 24px); }\r\n    .side-head .close, .side-foot{ display:none; }\r\n    .side-head{ border-bottom:none; padding-bottom:2px; }\r\n  }\r\n  .fs{ border-bottom:1px solid var(--border); padding:4px 0 14px; }\r\n  .fs:last-of-type{ border-bottom:none; }\r\n  .fs summary{ list-style:none; cursor:pointer; display:flex; justify-content:space-between; align-items:center; padding:12px 0 8px; font-size:14px; font-weight:800; color:var(--navy); min-height:44px; }\r\n  .fs summary::-webkit-details-marker{ display:none; }\r\n  .fs summary::after{ content:'+'; font-size:18px; font-weight:700; }\r\n  .fs[open] summary::after{ content:'\\2212'; }\r\n  .fs-input{ width:100%; border:1px solid var(--border); border-radius:10px; padding:10px 12px; font-size:13px; color:var(--text); background:#fff; margin-bottom:8px; }\r\n  .cat-line{ display:flex; align-items:center; gap:2px; }\r\n  .cat-tog{ width:26px; height:30px; flex:none; display:flex; align-items:center; justify-content:center; color:var(--navy); }\r\n  .cat-tog svg{ stroke:currentColor; transition:transform .15s ease; }\r\n  .cat-tog.open svg{ transform:rotate(90deg); }\r\n  .cat-row{ flex:1; min-width:0; display:flex; align-items:center; gap:8px; text-align:left; padding:7px 8px; border-radius:8px; font-size:13px; color:var(--navy); min-height:34px; }\r\n  .cat-row:hover{ background:#F3F5F9; }\r\n  .cat-row.sel{ background:#FFF4D6; font-weight:800; }\r\n  .cat-row > span:first-child{ flex:1; min-width:0; overflow-wrap:anywhere; }\r\n  .cat-count{ margin-left:auto; font-size:11.5px; font-weight:600; color:var(--navy); flex:none; }\r\n  .cat-kids{ margin-left:12px; padding-left:6px; border-left:1px solid var(--border); }\r\n  .cat-node > .cat-line > .cat-row{ font-weight:600; }\r\n  #catTree{ max-height:360px; overflow-y:auto; }\r\n  .opt{ display:flex; align-items:center; gap:10px; padding:6px 4px; font-size:13px; color:var(--navy); cursor:pointer; min-height:36px; }\r\n  .opt input{ width:18px; height:18px; accent-color:#F0A500; flex:none; }\r\n  .opt .lbl{ flex:1; min-width:0; overflow-wrap:anywhere; }\r\n  .fs-more{ color:var(--navy); text-decoration:underline; font-weight:700; font-size:12.5px; padding:8px 4px; }\r\n  .price-inputs{ display:grid; grid-template-columns:minmax(0,1fr) minmax(0,1fr) auto; gap:8px; align-items:end; }\r\n  .price-inputs label{ display:block; font-size:11.5px; font-weight:700; color:var(--navy); margin-bottom:4px; }\r\n  .price-inputs input{ width:100%; border:1px solid var(--border); border-radius:10px; padding:10px 10px; font-size:13px; color:var(--text); }\r\n  .btn-go{ background:var(--navy); color:#fff; border-radius:10px; padding:0 16px; height:40px; font-size:13px; font-weight:800; }\r\n  .budget-chips{ display:flex; flex-wrap:wrap; gap:8px; margin-top:10px; }\r\n  .budget-chips .chip{ padding:7px 11px; min-height:34px; font-size:12px; }\r\n  .btn-primary{ background:var(--orange); color:var(--navy); font-weight:800; border-radius:10px; padding:12px 16px; font-size:13.5px; min-height:44px; }\r\n  .btn-ghost{ border:1.5px solid var(--navy); color:var(--navy); font-weight:800; border-radius:10px; padding:12px 16px; font-size:13.5px; background:#fff; min-height:44px; }\r\n\r\n  .shop-resulthead{ display:flex; flex-wrap:wrap; justify-content:space-between; align-items:center; gap:10px; margin-bottom:14px; }\r\n  #resultCount{ font-size:14px; font-weight:700; color:var(--navy); }\r\n  .per-wrap{ display:flex; align-items:center; gap:8px; font-size:12.5px; font-weight:700; color:var(--navy); }\r\n  .per-wrap select{ border:1px solid var(--border); border-radius:8px; padding:7px 8px; font:inherit; color:var(--navy); background:#fff; }\r\n\r\n  .shop-grid{ display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); gap:12px; }\r\n  @media(min-width:700px){ .shop-grid{ grid-template-columns:repeat(3,minmax(0,1fr)); gap:14px; } }\r\n  @media(min-width:1280px){ .shop-grid{ grid-template-columns:repeat(4,minmax(0,1fr)); } }\r\n  @media(min-width:1560px){ .shop-grid{ grid-template-columns:repeat(5,minmax(0,1fr)); } }\r\n  .shop-grid .product-card, .shop-list .list-card{ background:#fff; }\r\n  .shop-grid.busy, .shop-list.busy{ opacity:.55; pointer-events:none; }\r\n\r\n  .shop-list{ display:flex; flex-direction:column; gap:12px; }\r\n  .list-card{ position:relative; display:grid; grid-template-columns:112px minmax(0,1fr); gap:12px; border:1px solid var(--border); border-radius:14px; padding:12px; transition:box-shadow .25s ease; }\r\n  .list-card:hover{ box-shadow:0 10px 22px rgba(13,27,61,0.1); }\r\n  .list-card .discount-badge, .list-card .featured-badge{ top:10px; left:10px; }\r\n  .lc-img{ display:block; width:112px; aspect-ratio:1; border-radius:10px; overflow:hidden; background:var(--grey-bg); }\r\n  .lc-img img{ width:100%; height:100%; object-fit:cover; display:block; }\r\n  .lc-img svg{ width:100%; height:100%; }\r\n  .lc-body{ min-width:0; padding-right:30px; }\r\n  .lc-name{ display:block; font-size:14px; font-weight:700; line-height:1.3; color:var(--text); margin-bottom:6px; overflow-wrap:anywhere; }\r\n  .lc-meta{ display:flex; flex-wrap:wrap; gap:4px 14px; font-size:12px; color:var(--navy); margin-bottom:6px; }\r\n  .lc-buy{ grid-column:1 \/ -1; display:flex; flex-wrap:wrap; align-items:center; justify-content:space-between; gap:10px; border-top:1px solid var(--border); padding-top:10px; }\r\n  .lc-buy .card-price-row{ margin:0; }\r\n  .lc-buy .card-secondary-prices{ margin:0; }\r\n  .lc-buy .card-bottom-row{ margin:0; }\r\n  .lc-buy .card-cart-btn{ padding:10px 14px; flex:none; }\r\n  .lc-wish{ position:absolute; top:10px; right:10px; }\r\n  @media(min-width:720px){\r\n    .list-card{ grid-template-columns:170px minmax(0,1fr) 230px; gap:18px; padding:14px; }\r\n    .lc-img{ width:170px; }\r\n    .lc-buy{ grid-column:auto; flex-direction:column; align-items:stretch; border-top:none; border-left:1px solid var(--border); padding:0 0 0 16px; justify-content:center; }\r\n    .lc-body{ padding-right:0; }\r\n    .lc-wish{ top:12px; right:12px; }\r\n    .lc-buy .card-cart-btn{ flex:1; }\r\n  }\r\n\r\n  .pager{ display:flex; flex-wrap:wrap; justify-content:center; align-items:center; gap:8px; margin:30px 0 6px; }\r\n  .pg{ min-width:42px; height:42px; padding:0 12px; border:1.5px solid var(--border); background:#fff; border-radius:10px; font-size:14px; font-weight:800; color:var(--navy); display:inline-flex; align-items:center; justify-content:center; gap:6px; }\r\n  .pg svg{ stroke:currentColor; }\r\n  .pg.cur{ background:var(--navy); color:#fff; border-color:var(--navy); }\r\n  .pg[disabled]{ background:var(--grey-bg); cursor:not-allowed; }\r\n  .pg[disabled] svg{ stroke:#B8BFCC; }\r\n  .pg-ell{ min-width:24px; text-align:center; font-weight:800; color:var(--navy); }\r\n  .pg-info{ flex-basis:100%; text-align:center; font-size:12.5px; font-weight:600; color:var(--navy); margin-top:4px; }\r\n  @media(max-width:480px){ .pg{ min-width:38px; height:40px; padding:0 8px; } .pg .pg-lbl{ display:none; } }\r\n  .empty-state{ grid-column:1 \/ -1; text-align:center; padding:48px 16px; color:var(--navy); }\r\n  .empty-state h3{ font-size:17px; font-weight:800; margin-bottom:6px; }\r\n  .empty-state p{ font-size:13.5px; margin-bottom:16px; }\r\n\r\n  \/* ---- shop v2 ---- *\/\r\n  .crumbs{ font-size:12.5px; font-weight:600; color:#0D1B3E; display:flex; flex-wrap:wrap; gap:6px; align-items:center; margin-bottom:6px; }\r\n  .crumbs a{ color:#0D1B3E; text-decoration:underline; }\r\n  .crumbs .sep{ color:#0D1B3E; }\r\n  .shop-h1{ font-size:22px; font-weight:800; color:#0D1B3E; margin-bottom:12px; line-height:1.15; }\r\n  @media(min-width:900px){ .shop-h1{ font-size:28px; } }\r\n  .banner-slot .brands-section{ padding-left:0; padding-right:0; }\r\n  .shop-search{ position:relative; }\r\n  .s-suggest{ display:none; position:absolute; left:-1.5px; right:-1.5px; top:calc(100% + 4px); background:#fff; border:1.5px solid var(--border); border-radius:12px; box-shadow:0 12px 28px rgba(13,27,61,.16); z-index:60; max-height:380px; overflow-y:auto; }\r\n  .s-suggest.open{ display:block; }\r\n  .s-suggest .sg-h{ font-size:11px; font-weight:800; color:#0D1B3E; padding:10px 12px 4px; text-transform:uppercase; letter-spacing:.04em; }\r\n  .s-suggest button{ display:flex; width:100%; align-items:center; gap:10px; text-align:left; padding:9px 12px; font-size:13.5px; font-weight:600; color:#0D1B3E; background:#fff; }\r\n  .s-suggest button:active, .s-suggest button:hover, .s-suggest button.hl{ background:#EEF0F3; }\r\n  .s-suggest img{ width:34px; height:34px; object-fit:cover; border-radius:6px; flex:none; background:#EEF0F3; }\r\n  .s-suggest .sg-t{ flex:1; min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }\r\n  .s-suggest .sg-m{ font-size:11.5px; font-weight:600; flex:none; }\r\n  .s-suggest .sg-all{ border-top:1px solid var(--border); font-weight:800; }\r\n  .product-card{ display:flex; flex-direction:column; }\r\n  .card-ship{ font-size:11px; font-weight:600; color:#0D1B3E; margin-bottom:8px; }\r\n  .card-ship:empty{ display:none; }\r\n  .card-save{ font-size:11px; font-weight:800; color:#1F9D55; min-height:15px; margin-bottom:2px; }\r\n  .product-card .card-price{ font-size:18px; }\r\n  .product-card .card-price-strike{ font-size:11px; }\r\n  .card-secondary-prices{ min-height:14px; }\r\n  .discount-badge{ background:#fff; color:var(--orange); border:1px solid var(--orange); font-weight:800; }\r\n  .card-stock.low{ color:#B26A00; }\r\n  .landing-sec{ margin-bottom:22px; }\r\n  .landing-sec h3{ font-size:17px; font-weight:800; color:#0D1B3E; margin-bottom:10px; display:flex; justify-content:space-between; align-items:baseline; }\r\n  .landing-sec h3 a{ font-size:12.5px; font-weight:700; color:#0D1B3E; text-decoration:underline; }\r\n  .cat-tiles{ display:grid; gap:10px; grid-template-columns:repeat(2,minmax(0,1fr)); }\r\n  @media(min-width:760px){ .cat-tiles{ grid-template-columns:repeat(auto-fill,minmax(210px,1fr)); } }\r\n  .cat-tile{ background:#fff; border:1.5px solid var(--border); border-radius:12px; padding:10px; display:grid; grid-template-columns:44px minmax(0,1fr); grid-template-areas:\"im nm\" \"im mt\"; column-gap:10px; row-gap:2px; align-items:center; text-align:left; min-height:64px; min-width:0; color:#0D1B3E; transition:transform .12s ease, border-color .2s; }\r\n  .cat-tile:active{ transform:scale(.98); }\r\n  .cat-tile:hover{ border-color:#F0A500; }\r\n  .cat-tile .ct-img{ grid-area:im; display:block; width:44px; height:44px; border-radius:10px; background:linear-gradient(135deg,#e9edf3,#dfe5ee); overflow:hidden; }\r\n  .cat-tile .ct-img img{ width:100%; height:100%; object-fit:cover; display:block; }\r\n  .cat-tile b{ grid-area:nm; display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical; overflow:hidden; font-size:13px; font-weight:700; line-height:1.2; padding:0; min-height:0; align-self:end; }\r\n  .cat-tile .ct-n{ grid-area:mt; font-size:12px; font-weight:600; padding:0; align-self:start; }\r\n  .lrow{ display:flex; gap:12px; overflow-x:auto; padding-bottom:6px; -webkit-overflow-scrolling:touch; }\r\n  .lrow > .product-card{ flex:none; width:172px; }\r\n  @media(min-width:900px){ .lrow > .product-card{ width:210px; } }\r\n  .grid-title{ font-size:17px; font-weight:800; color:#0D1B3E; margin:4px 0 10px; }\r\n  .skel{ pointer-events:none; }\r\n  .sk{ background:linear-gradient(90deg,#E6E9EE 25%,#F3F5F8 37%,#E6E9EE 63%); background-size:400% 100%; animation:skl 1.3s ease infinite; border-radius:8px; }\r\n  .sk-img{ aspect-ratio:1; margin-bottom:10px; } .sk-line{ height:12px; margin-bottom:8px; } .sk-line.short{ width:55%; } .sk-btn{ height:34px; margin-top:auto; }\r\n  @keyframes skl{ 0%{background-position:100% 50%} 100%{background-position:0 50%} }\r\n  .cart-toast{ position:fixed; left:12px; right:12px; bottom:14px; z-index:300; background:#0D1B3E; color:#fff; border-radius:14px; padding:12px 14px; display:flex; align-items:center; gap:12px; box-shadow:0 14px 34px rgba(13,27,61,.35); transform:translateY(140%); transition:transform .25s ease; max-width:460px; margin:0 auto; }\r\n  .cart-toast.show{ transform:translateY(0); }\r\n  .cart-toast .ct-msg{ flex:1; min-width:0; font-size:13px; font-weight:700; line-height:1.3; }\r\n  .cart-toast .ct-msg small{ display:block; font-size:12px; font-weight:600; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }\r\n  .cart-toast a{ background:#F0A500; color:#0D1B3E; font-weight:800; font-size:13px; padding:10px 14px; border-radius:10px; flex:none; }\r\n  .cart-toast a:active{ transform:scale(.96); }\r\n  .cart-toast button{ color:#fff; font-size:20px; width:28px; height:28px; flex:none; }\r\n<\/style>\r\n<\/head>\r\n<body data-auto-page=\"shop\">\r\n\r\n<section class=\"bc-hero\">\r\n  <div class=\"bc-hero-bg\">\r\n    <img decoding=\"async\" src=\"https:\/\/app.builders24.net\/assets\/Builders24_Distribution_handoff.png\" alt=\"\">\r\n    <div class=\"bc-hero-gradient\"><\/div>\r\n  <\/div>\r\n\r\n  <div class=\"bc-hero-inner\">\r\n    <h1 class=\"bc-hero-title\">Shop Online in Zimbabwe, <span class=\"accent\">Sold and Delivered by Builders24<\/span><\/h1>\r\n    <p class=\"bc-hero-sub\">Builders24 Zimbabwe sells building materials, equipment and machinery, tools, home goods, appliances, electronics and more in Zimbabwe. We supply shoppers, contractors and businesses in Zimbabwe from Zimbabwean and international suppliers, and deliver to every province in Zimbabwe. Search by product, brand or category and order online with secure checkout.<\/p>\r\n    <div class=\"bc-trust-row\">\r\n      <div class=\"bc-trust-item\"><span class=\"diamond\"><svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#F0A500\" stroke-width=\"2.5\"><path d=\"M12 2l7 3v6c0 5-3 8-7 9-4-1-7-4-7-9V5z\"\/><\/svg><\/span><span>Quality<br>Guaranteed<\/span><\/div>\r\n      <div class=\"bc-trust-item\"><span class=\"diamond\"><svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#F0A500\" stroke-width=\"2.5\"><path d=\"M12 2l7 3v6c0 5-3 8-7 9-4-1-7-4-7-9V5z\"\/><\/svg><\/span><span>Nationwide<br>Delivery<\/span><\/div>\r\n      <div class=\"bc-trust-item\"><span class=\"diamond\"><svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#F0A500\" stroke-width=\"2.5\"><path d=\"M12 2l7 3v6c0 5-3 8-7 9-4-1-7-4-7-9V5z\"\/><\/svg><\/span><span>Trusted by<br>Pros<\/span><\/div>\r\n      <div class=\"bc-trust-item\"><span class=\"diamond\"><svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#F0A500\" stroke-width=\"2.5\"><path d=\"M12 2l7 3v6c0 5-3 8-7 9-4-1-7-4-7-9V5z\"\/><\/svg><\/span><span>Secure<br>Payments<\/span><\/div>\r\n    <\/div>\r\n  <\/div>\r\n<\/section>\r\n\r\n<main class=\"shop-wrap\" id=\"shopTop\">\r\n  <div class=\"shop-toolbar\">\r\n    <div class=\"shop-search\">\r\n      <svg width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#0D1B3E\" stroke-width=\"2\" stroke-linecap=\"round\" aria-hidden=\"true\"><circle cx=\"11\" cy=\"11\" r=\"7\"\/><path d=\"M21 21l-4.3-4.3\"\/><\/svg>\r\n      <input type=\"search\" id=\"sSearch\" placeholder=\"Search products, brands, categories or SKU\" autocomplete=\"off\" aria-label=\"Search products, brands, categories or SKU\">\r\n      <button type=\"button\" class=\"s-clear\" id=\"sClear\" aria-label=\"Clear search\">&times;<\/button>\r\n      <div class=\"s-suggest\" id=\"sSuggest\" role=\"listbox\"><\/div>\r\n    <\/div>\r\n    <button type=\"button\" class=\"shop-ctl shop-filters-btn\" id=\"openFilters\" aria-haspopup=\"dialog\">\r\n      <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#0D1B3E\" stroke-width=\"2\" stroke-linecap=\"round\" aria-hidden=\"true\"><path d=\"M4 6h16M7 12h10M10 18h4\"\/><\/svg>\r\n      Filters <span class=\"fcount\" id=\"fCount\"><\/span>\r\n    <\/button>\r\n    <label class=\"shop-ctl shop-sort\" for=\"sSort\">\r\n      <span>Sort by<\/span>\r\n      <select id=\"sSort\"><\/select>\r\n    <\/label>\r\n    <div class=\"shop-viewtoggle\" role=\"group\" aria-label=\"Product view\">\r\n      <button type=\"button\" id=\"vGrid\" class=\"on\" aria-pressed=\"true\" aria-label=\"Grid view\"><svg width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke-width=\"2\" stroke-linejoin=\"round\"><rect x=\"3\" y=\"3\" width=\"7\" height=\"7\" rx=\"1\"\/><rect x=\"14\" y=\"3\" width=\"7\" height=\"7\" rx=\"1\"\/><rect x=\"3\" y=\"14\" width=\"7\" height=\"7\" rx=\"1\"\/><rect x=\"14\" y=\"14\" width=\"7\" height=\"7\" rx=\"1\"\/><\/svg><\/button>\r\n      <button type=\"button\" id=\"vList\" aria-pressed=\"false\" aria-label=\"List view\"><svg width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke-width=\"2\" stroke-linecap=\"round\"><path d=\"M8 6h13M8 12h13M8 18h13M3 6h.01M3 12h.01M3 18h.01\"\/><\/svg><\/button>\r\n    <\/div>\r\n  <\/div>\r\n\r\n  <div class=\"dept-row\" id=\"deptRow\" aria-label=\"Departments\"><\/div>\r\n  <div class=\"active-bar\" id=\"activeBar\"><\/div>\r\n\r\n  <div class=\"banner-slot\">\r\n<!-- ================= TRUSTED BRANDS \u2014 real, kept from live shop.html ================= -->\r\n<section class=\"brands-section\">\r\n  <div class=\"brands-banner\">\r\n    <div class=\"brands-overlay\"><\/div>\r\n    <div class=\"brands-text\">\r\n      <div class=\"sponsored-tag-dark\">SHOP IN SOUTH AFRICA<\/div>\r\n      <div class=\"brands-title\">BUILDERS24 SELLS AND DELIVERS<br>EVERYTHING YOU NEED IN SOUTH AFRICA.<\/div>\r\n      <a class=\"brands-link\" href=\"#productsGrid\">Start shopping\r\n        <svg width=\"11\" height=\"11\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#ffffff\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M5 12h14M13 6l6 6-6 6\"\/><\/svg>\r\n      <\/a>\r\n    <\/div>\r\n    <div class=\"brands-card\">\r\n      <img decoding=\"async\" class=\"flag-item\" src=\"https:\/\/flagcdn.com\/40x30\/zw.png\" alt=\"Zimbabwe\" title=\"Zimbabwe\" width=\"20\">\r\n    <\/div>\r\n  <\/div>\r\n<\/section>\r\n  <\/div>\r\n  <div class=\"shop-body\">\r\n    <div class=\"sheet-overlay\" id=\"sheetOverlay\"><\/div>\r\n    <aside class=\"shop-side\" id=\"shopSide\" aria-label=\"Filters\">\r\n      <div class=\"side-head\"><span>Filters<\/span><button type=\"button\" class=\"close\" id=\"sideClose\" aria-label=\"Close filters\">&times;<\/button><\/div>\r\n      <div class=\"side-scroll\">\r\n        <details class=\"fs\" open id=\"fsCat\"><summary>Categories<\/summary>\r\n          <input type=\"search\" id=\"catSearch\" class=\"fs-input\" placeholder=\"Find a category\" aria-label=\"Find a category\" autocomplete=\"off\">\r\n          <div id=\"catTree\"><\/div>\r\n        <\/details>\r\n        <details class=\"fs\" open id=\"fsPrice\"><summary>Price \/ Budget <span id=\"priceCur\"><\/span><\/summary>\r\n          <div class=\"price-inputs\">\r\n            <div><label for=\"pMin\">Min<\/label><input type=\"number\" id=\"pMin\" inputmode=\"decimal\" min=\"0\" placeholder=\"0\"><\/div>\r\n            <div><label for=\"pMax\">Max<\/label><input type=\"number\" id=\"pMax\" inputmode=\"decimal\" min=\"0\" placeholder=\"Any\"><\/div>\r\n            <button type=\"button\" class=\"btn-go\" id=\"pGo\">Go<\/button>\r\n          <\/div>\r\n          <div class=\"budget-chips\" id=\"priceChips\"><\/div>\r\n        <\/details>\r\n        <details class=\"fs\" open id=\"fsBrand\" hidden><summary>Brand<\/summary>\r\n          <input type=\"search\" id=\"brandSearch\" class=\"fs-input\" placeholder=\"Find a brand\" aria-label=\"Find a brand\" autocomplete=\"off\">\r\n          <div id=\"brandList\"><\/div>\r\n        <\/details>\r\n        <details class=\"fs\" open id=\"fsShip\" hidden><summary>Shipped from<\/summary>\r\n          <div id=\"shipList\"><\/div>\r\n        <\/details>\r\n        <details class=\"fs\" open id=\"fsRating\" hidden><summary>Customer rating<\/summary>\r\n          <div id=\"ratingList\"><\/div>\r\n        <\/details>\r\n        <details class=\"fs\" open id=\"fsMore\" hidden><summary>Availability &amp; deals<\/summary>\r\n          <div id=\"moreList\"><\/div>\r\n        <\/details>\r\n        <details class=\"fs\" id=\"fsLoc\" hidden><summary>Seller location<\/summary>\r\n          <input type=\"search\" id=\"locInput\" class=\"fs-input\" placeholder=\"City or country\" aria-label=\"Seller location\" autocomplete=\"off\">\r\n        <\/details>\r\n      <\/div>\r\n      <div class=\"side-foot\">\r\n        <button type=\"button\" class=\"btn-ghost\" id=\"sideClear\">Clear all<\/button>\r\n        <button type=\"button\" class=\"btn-primary\" id=\"sideShow\">Show results<\/button>\r\n      <\/div>\r\n    <\/aside>\r\n\r\n    <section class=\"shop-main\" aria-label=\"Products\">\r\n      <div class=\"shop-resulthead\">\r\n        <div id=\"resultCount\" aria-live=\"polite\">Loading&hellip;<\/div>\r\n        <label class=\"per-wrap\" for=\"sPer\">Show <select id=\"sPer\"><\/select> per page<\/label>\r\n      <\/div>\r\n      <div id=\"landing\"><\/div>\r\n      <h3 class=\"grid-title\" id=\"gridTitle\" hidden>All products<\/h3>\r\n      <div id=\"productsGrid\" class=\"shop-grid\" aria-busy=\"true\"><div class=\"product-card skel\"><div class=\"sk sk-img\"><\/div><div class=\"sk sk-line\"><\/div><div class=\"sk sk-line short\"><\/div><div class=\"sk sk-btn\"><\/div><\/div><div class=\"product-card skel\"><div class=\"sk sk-img\"><\/div><div class=\"sk sk-line\"><\/div><div class=\"sk sk-line short\"><\/div><div class=\"sk sk-btn\"><\/div><\/div><div class=\"product-card skel\"><div class=\"sk sk-img\"><\/div><div class=\"sk sk-line\"><\/div><div class=\"sk sk-line short\"><\/div><div class=\"sk sk-btn\"><\/div><\/div><div class=\"product-card skel\"><div class=\"sk sk-img\"><\/div><div class=\"sk sk-line\"><\/div><div class=\"sk sk-line short\"><\/div><div class=\"sk sk-btn\"><\/div><\/div><div class=\"product-card skel\"><div class=\"sk sk-img\"><\/div><div class=\"sk sk-line\"><\/div><div class=\"sk sk-line short\"><\/div><div class=\"sk sk-btn\"><\/div><\/div><div class=\"product-card skel\"><div class=\"sk sk-img\"><\/div><div class=\"sk sk-line\"><\/div><div class=\"sk sk-line short\"><\/div><div class=\"sk sk-btn\"><\/div><\/div><div class=\"product-card skel\"><div class=\"sk sk-img\"><\/div><div class=\"sk sk-line\"><\/div><div class=\"sk sk-line short\"><\/div><div class=\"sk sk-btn\"><\/div><\/div><div class=\"product-card skel\"><div class=\"sk sk-img\"><\/div><div class=\"sk sk-line\"><\/div><div class=\"sk sk-line short\"><\/div><div class=\"sk sk-btn\"><\/div><\/div><\/div>\r\n      <nav class=\"pager\" id=\"pager\" aria-label=\"Pagination\"><\/nav>\r\n    <\/section>\r\n  <\/div>\r\n<\/main>\r\n<div class=\"cart-toast\" id=\"cartToast\" role=\"status\" aria-live=\"polite\"><\/div>\r\n\r\n\r\n<script src=\"https:\/\/cdn.jsdelivr.net\/npm\/@supabase\/supabase-js@2\"><\/script>\r\n<script src=\"https:\/\/app.builders24.net\/guest-cart.js\"><\/script>\r\n<script src=\"https:\/\/app.builders24.net\/b24app.js\"><\/script>\r\n<script>\r\n\/* Zimbabwe localisation: all prices are shown in US dollars (USD). *\/\r\nwindow.getCustomerCurrency = async function(){ return 'USD'; };\r\nwindow.setCustomerCurrency = async function(){};\r\n<\/script>\r\n<script>\r\n(function(){\r\n\r\n  let appState = { currency: 'USD' };\r\n\r\n  \/\/ ===== Real product card \u2014 kept verbatim from the live shop.html =====\r\n  const _cardQty = {};\r\n  function productUrl(p){ return 'https:\/\/app.builders24.net\/retail\/product\/' + p.slug; }\r\n\r\n  \/\/ Real star rating \u2014 full\/half\/empty stars from a real 0-5 avg_rating.\r\n  function starsHtml(avg) {\r\n    const rating = Number(avg) || 0;\r\n    let out = '';\r\n    for (let i = 1; i <= 5; i++) {\r\n      if (rating >= i) out += '<svg width=\"11\" height=\"11\" viewBox=\"0 0 24 24\" fill=\"#F0A500\"><path d=\"M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z\"\/><\/svg>';\r\n      else if (rating >= i - 0.5) out += '<svg width=\"11\" height=\"11\" viewBox=\"0 0 24 24\"><defs><linearGradient id=\"half' + i + '\"><stop offset=\"50%\" stop-color=\"#F0A500\"\/><stop offset=\"50%\" stop-color=\"#E3E8F0\"\/><\/linearGradient><\/defs><path fill=\"url(#half' + i + ')\" d=\"M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z\"\/><\/svg>';\r\n      else out += '<svg width=\"11\" height=\"11\" viewBox=\"0 0 24 24\" fill=\"#E3E8F0\"><path d=\"M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z\"\/><\/svg>';\r\n    }\r\n    return '<span class=\"stars\">' + out + '<\/span>';\r\n  }\r\n\r\n  \/\/ Real rating, from whichever source actually has data: real customer\r\n  \/\/ reviews first, then the rating imported from the original source\r\n  \/\/ site. If neither exists, show nothing rather than a fake 0-star row.\r\n  function ratingRowHtml(p) {\r\n    if (p.review_count > 0) {\r\n      return `<div class=\"card-rating\">${starsHtml(p.avg_rating)}<span class=\"card-rating-count\">(${p.review_count})<\/span><\/div>`;\r\n    }\r\n    if (p.source_rating != null && p.source_rating_scale) {\r\n      const normalized = (Number(p.source_rating) \/ Number(p.source_rating_scale)) * 5;\r\n      const countLabel = p.source_review_count > 0 ? `(${p.source_review_count})` : '';\r\n      return `<div class=\"card-rating\">${starsHtml(normalized)}<span class=\"card-rating-count\">${countLabel}<\/span><\/div>`;\r\n    }\r\n    return `<div class=\"card-rating\">${starsHtml(0)}<span class=\"card-rating-count\">No reviews yet<\/span><\/div>`;\r\n  }\r\n\r\n  function productCardHtml(p, myCurrency, sellerNames){\r\n    const img = (Array.isArray(p.images) && p.images.length) ? p.images[0] : null;\r\n    const showEstimate = p.currency !== myCurrency;\r\n    const hasSale = p.sale_price && p.price && p.sale_price < p.price;\r\n    const discountPct = hasSale ? Math.round((1 - p.sale_price \/ p.price) * 100) : 0;\r\n    const displayPrice = hasSale ? p.sale_price : p.price;\r\n    const outOfStock = p.track_inventory && (p.stock_quantity || 0) <= 0;\r\n    const lowStock = p.track_inventory && !outOfStock && (p.stock_quantity || 0) <= 5;\r\n    const shipTxt = shipLabel(p.shipped_from);\r\n    const sellerName = sellerNames && p.org_id && sellerNames[p.org_id] ? sellerNames[p.org_id] : null;\r\n    _cardQty[p.id] = _cardQty[p.id] || 1;\r\n    return `\r\n      <div class=\"product-card\" data-product-id=\"${p.id}\">\r\n        ${hasSale ? `<span class=\"discount-badge\">-${discountPct}%<\/span>` : (p.is_featured ? `<span class=\"featured-badge\">Featured<\/span>` : '')}\r\n        <div class=\"card-top-row\" style=\"justify-content:flex-end;\"><button class=\"wish-btn\"><svg width=\"15\" height=\"15\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#0D1220\" stroke-width=\"1.8\"><path d=\"M20.8 4.6a5.5 5.5 0 0 0-7.8 0L12 5.6l-1-1a5.5 5.5 0 0 0-7.8 7.8l1 1L12 21l7.8-7.6 1-1a5.5 5.5 0 0 0 0-7.8Z\"\/><\/svg><\/button><\/div>\r\n        <a href=\"${productUrl(p)}\" style=\"display:block;\">\r\n          <div class=\"card-image\">\r\n            ${img\r\n              ? `<img src=\"${esc(img)}\" alt=\"${esc(p.name)}\" loading=\"lazy\" decoding=\"async\" style=\"width:100%;height:100%;object-fit:cover;\">`\r\n              : `<svg viewBox=\"0 0 150 150\"><rect width=\"150\" height=\"150\" fill=\"#EEF0F3\"\/><\/svg>`}\r\n          <\/div>\r\n          ${sellerName ? `<div class=\"card-seller\">${esc(sellerName)}<\/div>` : ''}\r\n          <p class=\"card-name\">${esc(p.name)}<\/p>\r\n          ${ratingRowHtml(p)}\r\n        <\/a>\r\n        <div class=\"card-price-row\">\r\n          <span class=\"card-price\">${fmtConverted(displayPrice, p.currency, myCurrency)}<\/span>\r\n          ${hasSale ? `<span class=\"card-price-strike\">${fmtConverted(p.price, p.currency, myCurrency)}<\/span>` : ''}\r\n        <\/div>\r\n        <div class=\"card-save\">${hasSale ? 'Save ' + fmtConverted(p.price - p.sale_price, p.currency, myCurrency) : ''}<\/div>\r\n        ${showEstimate ? `<div class=\"card-secondary-prices\">Listed at ${fmtNative(displayPrice, p.currency)}<\/div>` : ''}\r\n        <div class=\"card-ship\">${outOfStock ? '<span class=\"card-stock out\" style=\"margin:0\">Out of Stock<\/span>' : lowStock ? `<span class=\"card-stock low\" style=\"margin:0\">Only ${p.stock_quantity} left<\/span>` : ''}<\/div>\r\n        <div class=\"card-bottom-row\">\r\n          <div class=\"qty-stepper\">\r\n            <button type=\"button\" onclick=\"changeCardQty('${p.id}',-1,this)\">&minus;<\/button>\r\n            <span id=\"qty-${p.id}\">${_cardQty[p.id]}<\/span>\r\n            <button type=\"button\" onclick=\"changeCardQty('${p.id}',1,this)\">+<\/button>\r\n          <\/div>\r\n          <button class=\"card-cart-btn\" ${outOfStock ? 'disabled style=\"opacity:0.5;\"' : ''} onclick=\"event.preventDefault();handleAddToCart(event,'${p.id}',this);\">\r\n            <svg width=\"11\" height=\"11\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#ffffff\" stroke-width=\"2.2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><circle cx=\"9\" cy=\"21\" r=\"1\"\/><circle cx=\"20\" cy=\"21\" r=\"1\"\/><path d=\"M1 1h4l2.7 13.4a2 2 0 0 0 2 1.6h9.7a2 2 0 0 0 2-1.6L23 6H6\"\/><\/svg>\r\n            Add to Cart\r\n          <\/button>\r\n        <\/div>\r\n      <\/div>`;\r\n  }\r\n\r\n  function changeCardQty(productId, delta, btn){\r\n    const newQty = Math.max(1, (_cardQty[productId] || 1) + delta);\r\n    _cardQty[productId] = newQty;\r\n    const span = document.getElementById('qty-' + productId);\r\n    if (span) span.textContent = newQty;\r\n  }\r\n  window.changeCardQty = changeCardQty;\r\n\r\n  function handleAddToCart(event, productId, btn){\r\n    const qty = _cardQty[productId] || 1;\r\n    addToGuestCart(productId, qty);\r\n    if (typeof updateCartBadge === 'function') updateCartBadge();\r\n    const original = btn.innerHTML;\r\n    btn.textContent = 'Added \\u2713';\r\n    setTimeout(() => { btn.innerHTML = original; }, 1500);\r\n    const fp = (typeof FULL !== 'undefined' && FULL[productId]) || {};\r\n    showCartToast(fp.name || 'Item', qty);\r\n  }\r\n  window.handleAddToCart = handleAddToCart;\r\n  let _toastT;\r\n  function showCartToast(name, qty){\r\n    const t = document.getElementById('cartToast'); if (!t) return;\r\n    t.innerHTML = `<div class=\"ct-msg\">Added to cart<small>${qty > 1 ? qty + ' \\u00d7 ' : ''}${esc(name)}<\/small><\/div><a href=\"https:\/\/app.builders24.net\/retail\/cart.html\">View cart<\/a><button type=\"button\" aria-label=\"Dismiss\" onclick=\"this.parentNode.classList.remove('show')\">&times;<\/button>`;\r\n    t.classList.add('show'); clearTimeout(_toastT); _toastT = setTimeout(() => t.classList.remove('show'), 4500);\r\n  }\r\n\r\n  const WISH_KEY = 'b24_wishlist';\r\n  function getWishlist(){ try { return JSON.parse(localStorage.getItem(WISH_KEY) || '[]'); } catch(e) { return []; } }\r\n  function setWishlist(ids){ try { localStorage.setItem(WISH_KEY, JSON.stringify(ids)); } catch(e) {} }\r\n  function toggleWish(productId, btn){\r\n    let ids = getWishlist();\r\n    const isWished = ids.includes(productId);\r\n    ids = isWished ? ids.filter(id => id !== productId) : [...ids, productId];\r\n    setWishlist(ids);\r\n    btn.classList.toggle('wished', !isWished);\r\n  }\r\n  function wireWishButtons(scopeEl){\r\n    const ids = getWishlist();\r\n    scopeEl.querySelectorAll('.product-card, .list-card').forEach(card => {\r\n      const pid = card.dataset.productId;\r\n      const btn = card.querySelector('.wish-btn');\r\n      if (!btn) return;\r\n      btn.classList.toggle('wished', ids.includes(pid));\r\n      btn.onclick = (e) => { e.preventDefault(); toggleWish(pid, btn); };\r\n    });\r\n  }\r\n\r\n  \/\/ ===== Shop: live catalogue, client-side filtering, sorting, pagination =====\r\n  \/\/ The light list of every published product (plus the full category tree)\r\n  \/\/ loads once; facets, counts, sorting and paging then work instantly in the\r\n  \/\/ browser. Full card data is only fetched for the page being shown. Nothing\r\n  \/\/ here is hard-coded: categories, brands and ship-from options all come from\r\n  \/\/ the database, so new ones appear without a code change.\r\n  const PER_OPTIONS = [24, 48, 96];\r\n  const SORTS = { newest:'Latest', recommended:'Recommended', price_asc:'Price: Low to High', price_desc:'Price: High to Low', bestselling:'Best Selling', rating:'Top Rated', name:'Name: A to Z' };\r\n  \/\/ This shop only shows these top-level categories (and everything under them), read live from the categories table by slug.\r\n  \/\/ To show more departments, add their slugs here. An empty list shows everything.\r\n  const SHOP_ROOT_SLUGS = []; \/\/ full shop: all categories\r\n  const LIGHT_COLS = 'id, name, slug, price, sale_price, currency, brand, category_id, shipped_from, seller_location, is_featured, units_sold, created_at, avg_rating, review_count, source_rating, source_rating_scale, track_inventory, stock_quantity, org_id, b24_sku, sku';\r\n  const FULL_COLS = 'id, name, slug, price, sale_price, currency, images, brand, shipped_from, seller_location, org_id, stock_quantity, track_inventory, is_featured, units_sold, created_at, avg_rating, review_count, source_rating, source_rating_scale, source_review_count';\r\n\r\n  const state = { q:'', cat:'', brands:new Set(), ship:new Set(), minP:null, maxP:null, rating:0, onSale:false, inStock:false, loc:'', sort:'newest', view:'grid', per:24, page:1 };\r\n  let ALL = [], CATS = [], CAT_BY_ID = {}, CAT_BY_SLUG = {}, CAT_KIDS = {};\r\n  const FULL = {}, SELLERS = {};\r\n  const expanded = new Set();\r\n  let _seq = 0, _showAllBrands = false, _ready = false;\r\n  const $ = id => document.getElementById(id);\r\n  const debounce = (fn, ms) => { let t; return (...a) => { clearTimeout(t); t = setTimeout(() => fn(...a), ms); }; };\r\n  const store = { get(k){ try { return localStorage.getItem(k); } catch(e) { return null; } }, set(k,v){ try { localStorage.setItem(k, v); } catch(e) {} } };\r\n\r\n  \/\/ ---------- currency helpers (use the shared rates from b24app.js) ----------\r\n  function curSym(c){ try { if (typeof _currencySymbols !== 'undefined' && _currencySymbols && _currencySymbols[c]) return _currencySymbols[c]; } catch(e) {} return ({ USD:'$' }[c]) || (c ? c + ' ' : '$'); }\r\n  function money0(v){ return curSym(appState.currency) + Math.round(v).toLocaleString('en'); }\r\n  function ratesMap(){ try { return (typeof _ratesToUsd !== 'undefined' && _ratesToUsd) ? _ratesToUsd : null; } catch(e) { return null; } }\r\n  function conv(a, from, to){ if (!from || !to || from === to) return a; const r = ratesMap(); if (!r || !r[from] || !r[to]) return a; return (a \/ r[from]) * r[to]; }\r\n  const isOnSale = p => !!(p.sale_price && p.price && Number(p.sale_price) < Number(p.price));\r\n  const effNative = p => isOnSale(p) ? Number(p.sale_price) : (Number(p.price) || 0);\r\n  const inStock = p => !(p.track_inventory && (p.stock_quantity || 0) <= 0);\r\n  function ratingOf(p){\r\n    if (p.review_count > 0) return Number(p.avg_rating) || 0;\r\n    if (p.source_rating != null && p.source_rating_scale) return (Number(p.source_rating) \/ Number(p.source_rating_scale)) * 5;\r\n    return 0;\r\n  }\r\n  function recomputePrices(){ ALL.forEach(p => { p._px = conv(effNative(p), p.currency, appState.currency); }); }\r\n\r\n  const slugKey = s => String(s || '').toLowerCase().replace(\/[^a-z0-9]+\/g, '-').replace(\/^-|-$\/g, '');\r\n  function shipLabel(v){\r\n    if (!v) return '';\r\n    const s = String(v).trim();\r\n    if (\/south africa\/i.test(s)) return 'Zimbabwe';\r\n    const t = s.replace(\/^builders24\\s+(warehouse\\s+)?\/i, '').trim();\r\n    return t || s;\r\n  }\r\n\r\n  \/\/ ---------- data loading ----------\r\n  async function pagedSelect(build){\r\n    \/\/ PostgREST caps a response at 1000 rows, so page until exhausted.\r\n    let out = [], from = 0; const size = 1000;\r\n    while (true) {\r\n      const { data, error } = await build().range(from, from + size - 1);\r\n      if (error) throw error;\r\n      out = out.concat(data || []);\r\n      if (!data || data.length < size || out.length >= 20000) break;\r\n      from += size;\r\n    }\r\n    return out;\r\n  }\r\n  async function loadCategories(){\r\n    CATS = await pagedSelect(() => sb.from('categories').select('id,name,slug,parent_id,sort_order').eq('is_active', true).order('sort_order').order('id'));\r\n    if (SHOP_ROOT_SLUGS.length) {\r\n      const keep = new Set(CATS.filter(c => !c.parent_id && SHOP_ROOT_SLUGS.indexOf(c.slug) !== -1).map(c => c.id));\r\n      let grew = true;\r\n      while (grew) { grew = false; CATS.forEach(c => { if (c.parent_id && keep.has(c.parent_id) && !keep.has(c.id)) { keep.add(c.id); grew = true; } }); }\r\n      CATS = CATS.filter(c => keep.has(c.id));\r\n    }\r\n    CAT_BY_ID = {}; CAT_BY_SLUG = {}; CAT_KIDS = {};\r\n    CATS.forEach(c => { CAT_BY_ID[c.id] = c; CAT_BY_SLUG[c.slug] = c; });\r\n    CATS.forEach(c => { const k = (c.parent_id && CAT_BY_ID[c.parent_id]) ? c.parent_id : (c.parent_id ? '__orphan' : '__root'); (CAT_KIDS[k] = CAT_KIDS[k] || []).push(c); });\r\n    CAT_KIDS['__root'] = CAT_KIDS['__root'] || [];\r\n  }\r\n  function catPath(id){\r\n    const path = []; let cur = CAT_BY_ID[id], guard = 0;\r\n    while (cur && guard++ < 10) { path.unshift(cur.id); cur = cur.parent_id ? CAT_BY_ID[cur.parent_id] : null; }\r\n    return path.length ? path : (id ? [id] : []);\r\n  }\r\n  async function loadCatalogue(){\r\n    ALL = await pagedSelect(() => sb.from('products').select(LIGHT_COLS).eq('is_published', true).eq('is_archived', false).order('created_at', { ascending:false }).order('id'));\r\n    ALL.forEach(p => {\r\n      p._cats = catPath(p.category_id);\r\n      const b = (p.brand || '').trim();\r\n      p._bk = b.toLowerCase(); p._bl = b;\r\n      p._sl = shipLabel(p.shipped_from); p._sk = slugKey(p._sl);\r\n      p._q = ((p.name || '') + ' ' + b).toLowerCase();\r\n    });\r\n  }\r\n\r\n  function buildSearchIndex(){\r\n    ALL.forEach(p => {\r\n      p._cats = catPath(p.category_id);\r\n      const cn = (p._cats || []).map(id => CAT_BY_ID[id] ? CAT_BY_ID[id].name : '').join(' ');\r\n      p._q = [p.name, p.brand, p.sku, p.b24_sku, SELLERS[p.org_id], cn].filter(Boolean).join(' ').toLowerCase();\r\n    });\r\n  }\r\n  async function loadSellers(){\r\n    const ids = [...new Set(ALL.map(p => p.org_id).filter(Boolean))];\r\n    if (!ids.length) return;\r\n    try { const { data } = await sb.from('orgs').select('id,name').in('id', ids); (data || []).forEach(o => { SELLERS[o.id] = o.name; }); } catch (e) {}\r\n  }\r\n  function renderSuggest(){\r\n    const box = $('sSuggest'); const q = $('sSearch').value.trim().toLowerCase();\r\n    if (q.length < 2) { box.classList.remove('open'); box.innerHTML = ''; return; }\r\n    const terms = q.split(\/\\s+\/);\r\n    const cats = CATS.filter(c => terms.every(t => c.name.toLowerCase().includes(t))).slice(0, 3);\r\n    const prods = ALL.filter(p => terms.every(t => p._q.includes(t))).slice(0, 5);\r\n    const count = ALL.reduce((n, p) => n + (terms.every(t => p._q.includes(t)) ? 1 : 0), 0);\r\n    if (!cats.length && !prods.length) { box.innerHTML = '<div class=\"sg-h\">No matches<\/div>'; box.classList.add('open'); return; }\r\n    let h = '';\r\n    if (cats.length) h += '<div class=\"sg-h\">Categories<\/div>' + cats.map(c => `<button type=\"button\" data-sg-cat=\"${c.id}\"><span class=\"sg-t\">${esc(c.name)}<\/span><span class=\"sg-m\">Category<\/span><\/button>`).join('');\r\n    if (prods.length) h += '<div class=\"sg-h\">Products<\/div>' + prods.map(p => `<button type=\"button\" data-sg-prod=\"${esc(p.slug)}\"><span class=\"sg-t\">${esc(p.name)}<\/span>${p.brand ? `<span class=\"sg-m\">${esc(p.brand)}<\/span>` : ''}<\/button>`).join('');\r\n    h += `<button type=\"button\" class=\"sg-all\" data-sg-all=\"1\"><span class=\"sg-t\">See all ${count.toLocaleString('en')} result${count === 1 ? '' : 's'} for \u201c${esc($('sSearch').value.trim())}\u201d<\/span><\/button>`;\r\n    box.innerHTML = h; box.classList.add('open');\r\n  }\r\n\r\n  \/\/ ---------- filtering \/ sorting ----------\r\n  function matches(p, skip){\r\n    const s = state;\r\n    if (skip !== 'q' && s.q) { for (const t of s.q.toLowerCase().split(\/\\s+\/).filter(Boolean)) if (!p._q.includes(t)) return false; }\r\n    if (skip !== 'cat' && s.cat && !p._cats.includes(s.cat)) return false;\r\n    if (skip !== 'brand' && s.brands.size && !s.brands.has(p._bk)) return false;\r\n    if (skip !== 'ship' && s.ship.size && !s.ship.has(p._sk)) return false;\r\n    if (skip !== 'price' && (s.minP != null || s.maxP != null)) {\r\n      if (!(p._px > 0)) return false;\r\n      if (s.minP != null && p._px < s.minP) return false;\r\n      if (s.maxP != null && p._px > s.maxP) return false;\r\n    }\r\n    if (skip !== 'rating' && s.rating && ratingOf(p) < s.rating) return false;\r\n    if (skip !== 'sale' && s.onSale && !isOnSale(p)) return false;\r\n    if (skip !== 'stock' && s.inStock && !inStock(p)) return false;\r\n    if (skip !== 'loc' && s.loc && !(p.seller_location || '').toLowerCase().includes(s.loc.toLowerCase())) return false;\r\n    return true;\r\n  }\r\n  const byNewest = (a, b) => (a.created_at < b.created_at ? 1 : a.created_at > b.created_at ? -1 : 0);\r\n  function sortList(list){\r\n    const out = list.slice();\r\n    const priced = (dir) => (a, b) => { const x = a._px > 0, y = b._px > 0; if (x !== y) return x ? -1 : 1; return dir * (a._px - b._px) || byNewest(a, b); };\r\n    switch (state.sort) {\r\n      case 'price_asc': out.sort(priced(1)); break;\r\n      case 'price_desc': out.sort(priced(-1)); break;\r\n      case 'bestselling': out.sort((a, b) => (b.units_sold || 0) - (a.units_sold || 0) || byNewest(a, b)); break;\r\n      case 'rating': out.sort((a, b) => ratingOf(b) - ratingOf(a) || (b.review_count || 0) - (a.review_count || 0) || byNewest(a, b)); break;\r\n      case 'name': out.sort((a, b) => String(a.name || '').localeCompare(String(b.name || ''))); break;\r\n      case 'recommended': out.sort((a, b) => (b.is_featured ? 1 : 0) - (a.is_featured ? 1 : 0) || (b.units_sold || 0) - (a.units_sold || 0) || byNewest(a, b)); break;\r\n      default: out.sort(byNewest);\r\n    }\r\n    return out;\r\n  }\r\n\r\n  \/\/ ---------- facets ----------\r\n  const CHEV = '<svg width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\" stroke-width=\"2.6\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"M9 6l6 6-6 6\"\/><\/svg>';\r\n  function buildCatNode(c, term, counts){\r\n    const n = counts[c.id] || 0; if (!n) return '';\r\n    const kidsHtml = (CAT_KIDS[c.id] || []).map(k => buildCatNode(k, term, counts)).join('');\r\n    const selfMatch = !term || c.name.toLowerCase().includes(term);\r\n    if (term && !selfMatch && !kidsHtml) return '';\r\n    const hasKids = !!kidsHtml;\r\n    const isOpen = hasKids && (!!term || expanded.has(c.id));\r\n    return `<div class=\"cat-node\"><div class=\"cat-line\">\r\n      <button type=\"button\" class=\"cat-tog${isOpen ? ' open' : ''}\" data-tog=\"${c.id}\" aria-label=\"Expand ${esc(c.name)}\" ${hasKids ? '' : 'style=\"visibility:hidden\" tabindex=\"-1\"'}>${CHEV}<\/button>\r\n      <button type=\"button\" class=\"cat-row${state.cat === c.id ? ' sel' : ''}\" data-cat=\"${c.id}\"><span>${esc(c.name)}<\/span><span class=\"cat-count\">${n}<\/span><\/button>\r\n    <\/div>${isOpen ? `<div class=\"cat-kids\">${kidsHtml}<\/div>` : ''}<\/div>`;\r\n  }\r\n  function categoryCounts(){\r\n    const counts = {}; ALL.forEach(p => { if (matches(p, 'cat')) p._cats.forEach(id => { counts[id] = (counts[id] || 0) + 1; }); });\r\n    return counts;\r\n  }\r\n  function renderCategories(counts){\r\n    const term = $('catSearch').value.trim().toLowerCase();\r\n    const html = (CAT_KIDS['__root'] || []).map(c => buildCatNode(c, term, counts)).join('');\r\n    $('catTree').innerHTML = html || '<p style=\"font-size:13px;color:#0D1B3E;padding:6px 4px;\">No categories match.<\/p>';\r\n  }\r\n  function renderDepartments(counts){\r\n    const total = ALL.filter(p => matches(p, 'cat')).length;\r\n    const selRoot = state.cat ? catPath(state.cat)[0] : '';\r\n    const chips = (CAT_KIDS['__root'] || []).filter(c => counts[c.id]).map(c =>\r\n      `<button type=\"button\" class=\"chip${selRoot === c.id ? ' on' : ''}\" data-dept=\"${c.id}\">${esc(c.name)}<span class=\"n\">${counts[c.id]}<\/span><\/button>`).join('');\r\n    $('deptRow').innerHTML = `<button type=\"button\" class=\"chip${!state.cat ? ' on' : ''}\" data-dept=\"\">All<span class=\"n\">${total}<\/span><\/button>` + chips;\r\n  }\r\n  function facetOptions(skip, keyFn, labelFn){\r\n    const map = new Map();\r\n    ALL.forEach(p => { if (!matches(p, skip)) return; const k = keyFn(p); if (!k) return; const e = map.get(k) || { label: labelFn(p), n: 0 }; e.n++; map.set(k, e); });\r\n    return map;\r\n  }\r\n  function renderBrands(){\r\n    const map = facetOptions('brand', p => p._bk, p => p._bl);\r\n    state.brands.forEach(k => { if (!map.has(k)) { const any = ALL.find(p => p._bk === k); map.set(k, { label: any ? any._bl : k, n: 0 }); } });\r\n    const sec = $('fsBrand');\r\n    const everyBrand = new Set(ALL.map(p => p._bk).filter(Boolean));\r\n    sec.hidden = everyBrand.size < 2;\r\n    if (sec.hidden) return;\r\n    const term = $('brandSearch').value.trim().toLowerCase();\r\n    let rows = [...map.entries()].filter(([k, e]) => !term || e.label.toLowerCase().includes(term))\r\n      .sort((a, b) => (state.brands.has(b[0]) - state.brands.has(a[0])) || b[1].n - a[1].n || a[1].label.localeCompare(b[1].label));\r\n    const limited = !term && !_showAllBrands && rows.length > 8;\r\n    const shown = limited ? rows.slice(0, 8) : rows;\r\n    $('brandList').innerHTML = shown.map(([k, e]) => `<label class=\"opt\"><input type=\"checkbox\" data-brand=\"${esc(k)}\" ${state.brands.has(k) ? 'checked' : ''}><span class=\"lbl\">${esc(e.label)}<\/span><span class=\"cat-count\">${e.n}<\/span><\/label>`).join('')\r\n      + (rows.length > 8 && !term ? `<button type=\"button\" class=\"fs-more\" id=\"brandMore\">${_showAllBrands ? 'Show fewer brands' : 'Show all ' + rows.length + ' brands'}<\/button>` : '')\r\n      + (!rows.length ? '<p style=\"font-size:13px;color:#0D1B3E;padding:6px 4px;\">No brands match.<\/p>' : '');\r\n  }\r\n  function renderShip(){\r\n    const map = facetOptions('ship', p => p._sk, p => p._sl);\r\n    state.ship.forEach(k => { if (!map.has(k)) { const any = ALL.find(p => p._sk === k); map.set(k, { label: any ? any._sl : k, n: 0 }); } });\r\n    const sec = $('fsShip');\r\n    sec.hidden = new Set(ALL.map(p => p._sk).filter(Boolean)).size < 2;\r\n    if (sec.hidden) return;\r\n    $('shipList').innerHTML = [...map.entries()].sort((a, b) => b[1].n - a[1].n || a[1].label.localeCompare(b[1].label))\r\n      .map(([k, e]) => `<label class=\"opt\"><input type=\"checkbox\" data-ship=\"${esc(k)}\" ${state.ship.has(k) ? 'checked' : ''}><span class=\"lbl\">${esc(e.label)}<\/span><span class=\"cat-count\">${e.n}<\/span><\/label>`).join('');\r\n  }\r\n  function renderRating(){\r\n    const sec = $('fsRating');\r\n    sec.hidden = !ALL.some(p => ratingOf(p) > 0);\r\n    if (sec.hidden) return;\r\n    const base = ALL.filter(p => matches(p, 'rating'));\r\n    const star = '<svg width=\"13\" height=\"13\" viewBox=\"0 0 24 24\" fill=\"#F0A500\" aria-hidden=\"true\"><path d=\"M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z\"\/><\/svg>';\r\n    $('ratingList').innerHTML = [4, 3].map(r => `<label class=\"opt\"><input type=\"radio\" name=\"fRating\" data-rating=\"${r}\" ${state.rating === r ? 'checked' : ''}><span class=\"lbl\">${star.repeat(r)} &amp; up<\/span><span class=\"cat-count\">${base.filter(p => ratingOf(p) >= r).length}<\/span><\/label>`).join('')\r\n      + (state.rating ? '<button type=\"button\" class=\"fs-more\" id=\"ratingClear\">Clear rating<\/button>' : '');\r\n  }\r\n  function renderMore(){\r\n    const sec = $('fsMore');\r\n    const hasSale = ALL.some(isOnSale), hasStock = ALL.some(p => p.track_inventory);\r\n    sec.hidden = !(hasSale || hasStock);\r\n    if (sec.hidden) return;\r\n    let h = '';\r\n    if (hasSale) h += `<label class=\"opt\"><input type=\"checkbox\" id=\"fOnSale\" ${state.onSale ? 'checked' : ''}><span class=\"lbl\">On sale<\/span><span class=\"cat-count\">${ALL.filter(p => matches(p, 'sale') && isOnSale(p)).length}<\/span><\/label>`;\r\n    if (hasStock) h += `<label class=\"opt\"><input type=\"checkbox\" id=\"fInStock\" ${state.inStock ? 'checked' : ''}><span class=\"lbl\">In stock only<\/span><span class=\"cat-count\">${ALL.filter(p => matches(p, 'stock') && inStock(p)).length}<\/span><\/label>`;\r\n    $('moreList').innerHTML = h;\r\n  }\r\n  function budgetBands(){\r\n    const px = ALL.map(p => p._px).filter(v => v > 0).sort((a, b) => a - b);\r\n    if (px.length < 8) return [];\r\n    const q = f => Number(px[Math.floor(px.length * f)].toPrecision(2));\r\n    const a = q(0.25), b = q(0.5), c = q(0.75);\r\n    if (!(a < b && b < c)) return [];\r\n    return [\r\n      { label: 'Under ' + money0(a), min: null, max: a },\r\n      { label: money0(a) + ' \u2013 ' + money0(b), min: a, max: b },\r\n      { label: money0(b) + ' \u2013 ' + money0(c), min: b, max: c },\r\n      { label: 'Over ' + money0(c), min: c, max: null },\r\n    ];\r\n  }\r\n  let _bands = [];\r\n  function renderPrice(){\r\n    $('priceCur').textContent = '(' + appState.currency + ')';\r\n    _bands = budgetBands();\r\n    $('priceChips').innerHTML = _bands.map((b, i) => `<button type=\"button\" class=\"chip${(state.minP === b.min && state.maxP === b.max) ? ' on' : ''}\" data-band=\"${i}\">${esc(b.label)}<\/button>`).join('');\r\n    const setVal = (el, v) => { if (document.activeElement !== el) el.value = v == null ? '' : String(Math.round(v * 100) \/ 100); };\r\n    setVal($('pMin'), state.minP); setVal($('pMax'), state.maxP);\r\n    $('fsLoc').hidden = !ALL.some(p => p.seller_location);\r\n  }\r\n\r\n  \/\/ ---------- active filter chips ----------\r\n  function activeFilters(){\r\n    const out = [];\r\n    if (state.q) out.push({ label: 'Search: ' + state.q, off: () => { state.q = ''; $('sSearch').value = ''; } });\r\n    if (state.cat) out.push({ label: catPath(state.cat).map(id => CAT_BY_ID[id] ? CAT_BY_ID[id].name : '').filter(Boolean).join(' \u203a '), off: () => { state.cat = ''; } });\r\n    state.brands.forEach(k => { const any = ALL.find(p => p._bk === k); out.push({ label: 'Brand: ' + (any ? any._bl : k), off: () => state.brands.delete(k) }); });\r\n    state.ship.forEach(k => { const any = ALL.find(p => p._sk === k); out.push({ label: 'Ships from ' + (any ? any._sl : k), off: () => state.ship.delete(k) }); });\r\n    if (state.minP != null || state.maxP != null) {\r\n      const lbl = state.minP != null && state.maxP != null ? money0(state.minP) + ' \u2013 ' + money0(state.maxP) : state.minP != null ? 'Over ' + money0(state.minP) : 'Under ' + money0(state.maxP);\r\n      out.push({ label: 'Price: ' + lbl, off: () => { state.minP = null; state.maxP = null; } });\r\n    }\r\n    if (state.rating) out.push({ label: state.rating + '\u2605 & up', off: () => { state.rating = 0; } });\r\n    if (state.onSale) out.push({ label: 'On sale', off: () => { state.onSale = false; } });\r\n    if (state.inStock) out.push({ label: 'In stock only', off: () => { state.inStock = false; } });\r\n    if (state.loc) out.push({ label: 'Seller: ' + state.loc, off: () => { state.loc = ''; $('locInput').value = ''; } });\r\n    return out;\r\n  }\r\n  let _chipActions = [];\r\n  function renderActiveBar(){\r\n    const list = activeFilters();\r\n    _chipActions = list.map(f => f.off);\r\n    $('activeBar').innerHTML = list.map((f, i) => `<span class=\"af-chip\">${esc(f.label)}<button type=\"button\" data-rm=\"${i}\" aria-label=\"Remove filter\">&times;<\/button><\/span>`).join('')\r\n      + (list.length ? '<button type=\"button\" class=\"af-clear\" id=\"afClear\">Clear all<\/button>' : '');\r\n    const n = list.filter(f => !f.label.startsWith('Search:')).length;\r\n    const fc = $('fCount'); fc.textContent = n; fc.classList.toggle('show', n > 0);\r\n  }\r\n  function clearAll(){\r\n    state.q = ''; state.cat = ''; state.brands.clear(); state.ship.clear(); state.minP = null; state.maxP = null;\r\n    state.rating = 0; state.onSale = false; state.inStock = false; state.loc = '';\r\n    $('sSearch').value = ''; $('locInput').value = ''; $('catSearch').value = ''; $('brandSearch').value = '';\r\n    state.page = 1; render();\r\n  }\r\n\r\n  \/\/ ---------- pager ----------\r\n  const ARROW_L = '<svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke-width=\"2.6\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"M15 6l-6 6 6 6\"\/><\/svg>';\r\n  const ARROW_R = '<svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke-width=\"2.6\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"M9 6l6 6-6 6\"\/><\/svg>';\r\n  function pageItems(cur, total){\r\n    if (total <= 7) return Array.from({ length: total }, (_, i) => i + 1);\r\n    const set = new Set([1, total, cur - 1, cur, cur + 1]);\r\n    if (cur <= 3) { set.add(2); set.add(3); set.add(4); }\r\n    if (cur >= total - 2) { set.add(total - 1); set.add(total - 2); set.add(total - 3); }\r\n    const nums = [...set].filter(n => n >= 1 && n <= total).sort((a, b) => a - b);\r\n    const out = []; nums.forEach((n, i) => { if (i && n - nums[i - 1] > 1) out.push('\u2026'); out.push(n); });\r\n    return out;\r\n  }\r\n  function renderPager(total){\r\n    const pages = Math.max(1, Math.ceil(total \/ state.per));\r\n    const el = $('pager');\r\n    if (total === 0 || pages <= 1) { el.innerHTML = total ? `<div class=\"pg-info\">Showing all ${total} result${total === 1 ? '' : 's'}<\/div>` : ''; return; }\r\n    const cur = state.page;\r\n    el.innerHTML =\r\n      `<button type=\"button\" class=\"pg\" data-pg=\"${cur - 1}\" ${cur === 1 ? 'disabled' : ''} aria-label=\"Previous page\">${ARROW_L}<span class=\"pg-lbl\">Prev<\/span><\/button>` +\r\n      pageItems(cur, pages).map(n => n === '\u2026' ? '<span class=\"pg-ell\" aria-hidden=\"true\">\u2026<\/span>' : `<button type=\"button\" class=\"pg${n === cur ? ' cur' : ''}\" data-pg=\"${n}\" ${n === cur ? 'aria-current=\"page\"' : ''} aria-label=\"Page ${n}\">${n}<\/button>`).join('') +\r\n      `<button type=\"button\" class=\"pg\" data-pg=\"${cur + 1}\" ${cur === pages ? 'disabled' : ''} aria-label=\"Next page\"><span class=\"pg-lbl\">Next<\/span>${ARROW_R}<\/button>` +\r\n      `<div class=\"pg-info\">Page ${cur} of ${pages}<\/div>`;\r\n  }\r\n\r\n  \/\/ ---------- list-view card ----------\r\n  function listCardHtml(p, myCurrency, sellerNames){\r\n    const img = (Array.isArray(p.images) && p.images.length) ? p.images[0] : null;\r\n    const showEstimate = p.currency !== myCurrency;\r\n    const hasSale = isOnSale(p);\r\n    const discountPct = hasSale ? Math.round((1 - p.sale_price \/ p.price) * 100) : 0;\r\n    const displayPrice = hasSale ? p.sale_price : p.price;\r\n    const outOfStock = p.track_inventory && (p.stock_quantity || 0) <= 0;\r\n    const sellerName = sellerNames && p.org_id && sellerNames[p.org_id] ? sellerNames[p.org_id] : null;\r\n    const ship = shipLabel(p.shipped_from);\r\n    _cardQty[p.id] = _cardQty[p.id] || 1;\r\n    return `\r\n      <div class=\"list-card\" data-product-id=\"${p.id}\">\r\n        ${hasSale ? `<span class=\"discount-badge\">-${discountPct}%<\/span>` : (p.is_featured ? `<span class=\"featured-badge\">Featured<\/span>` : '')}\r\n        <button class=\"wish-btn lc-wish\" aria-label=\"Save to wishlist\"><svg width=\"15\" height=\"15\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#0D1220\" stroke-width=\"1.8\"><path d=\"M20.8 4.6a5.5 5.5 0 0 0-7.8 0L12 5.6l-1-1a5.5 5.5 0 0 0-7.8 7.8l1 1L12 21l7.8-7.6 1-1a5.5 5.5 0 0 0 0-7.8Z\"\/><\/svg><\/button>\r\n        <a class=\"lc-img\" href=\"${productUrl(p)}\">${img ? `<img src=\"${esc(img)}\" alt=\"${esc(p.name)}\" loading=\"lazy\" decoding=\"async\">` : `<svg viewBox=\"0 0 150 150\"><rect width=\"150\" height=\"150\" fill=\"#EEF0F3\"\/><\/svg>`}<\/a>\r\n        <div class=\"lc-body\">\r\n          ${sellerName ? `<div class=\"card-seller\">${esc(sellerName)}<\/div>` : ''}\r\n          <a class=\"lc-name\" href=\"${productUrl(p)}\">${esc(p.name)}<\/a>\r\n          ${ratingRowHtml(p)}\r\n          <div class=\"lc-meta\">${p.brand ? `<span>Brand: ${esc(p.brand)}<\/span>` : ''}${ship ? `<span>Ships from ${esc(ship)}<\/span>` : ''}<\/div>\r\n          <div class=\"card-stock ${outOfStock ? 'out' : 'in'}\" style=\"margin-bottom:0;\">${outOfStock ? 'Out of Stock' : 'In Stock'}<\/div>\r\n        <\/div>\r\n        <div class=\"lc-buy\">\r\n          <div>\r\n            <div class=\"card-price-row\">\r\n              <span class=\"card-price\">${fmtConverted(displayPrice, p.currency, myCurrency)}<\/span>\r\n              ${hasSale ? `<span class=\"card-price-strike\">${fmtConverted(p.price, p.currency, myCurrency)}<\/span>` : ''}\r\n            <\/div>\r\n            ${showEstimate ? `<div class=\"card-secondary-prices\">Listed at ${fmtNative(displayPrice, p.currency)}<\/div>` : ''}\r\n          <\/div>\r\n          <div class=\"card-bottom-row\">\r\n            <div class=\"qty-stepper\">\r\n              <button type=\"button\" onclick=\"changeCardQty('${p.id}',-1,this)\">&minus;<\/button>\r\n              <span id=\"qty-${p.id}\">${_cardQty[p.id]}<\/span>\r\n              <button type=\"button\" onclick=\"changeCardQty('${p.id}',1,this)\">+<\/button>\r\n            <\/div>\r\n            <button class=\"card-cart-btn\" ${outOfStock ? 'disabled style=\"opacity:0.5;\"' : ''} onclick=\"event.preventDefault();handleAddToCart(event,'${p.id}',this);\">\r\n              <svg width=\"11\" height=\"11\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#ffffff\" stroke-width=\"2.2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><circle cx=\"9\" cy=\"21\" r=\"1\"\/><circle cx=\"20\" cy=\"21\" r=\"1\"\/><path d=\"M1 1h4l2.7 13.4a2 2 0 0 0 2 1.6h9.7a2 2 0 0 0 2-1.6L23 6H6\"\/><\/svg>\r\n              Add to Cart\r\n            <\/button>\r\n          <\/div>\r\n        <\/div>\r\n      <\/div>`;\r\n  }\r\n\r\n  \/\/ ---------- URL <-> state ----------\r\n  function syncUrl(){\r\n    const sp = new URLSearchParams();\r\n    if (state.q) sp.set('q', state.q);\r\n    if (state.cat) sp.set('category', state.cat);\r\n    if (state.brands.size) sp.set('brand', [...state.brands].map(k => (ALL.find(p => p._bk === k) || {})._bl || k).join(','));\r\n    if (state.ship.size) sp.set('ship', [...state.ship].join(','));\r\n    if (state.minP != null) sp.set('min_price', String(Math.round(state.minP * 100) \/ 100));\r\n    if (state.maxP != null) sp.set('max_price', String(Math.round(state.maxP * 100) \/ 100));\r\n    if (state.minP != null || state.maxP != null) sp.set('currency', appState.currency);\r\n    if (state.rating) sp.set('rating', String(state.rating));\r\n    if (state.onSale) sp.set('sale', '1');\r\n    if (state.inStock) sp.set('instock', '1');\r\n    if (state.loc) sp.set('location', state.loc);\r\n    if (state.sort !== 'newest') sp.set('sort', state.sort);\r\n    if (state.view !== 'grid') sp.set('view', state.view);\r\n    if (state.per !== 24) sp.set('per', String(state.per));\r\n    if (state.page > 1) sp.set('page', String(state.page));\r\n    const qs = sp.toString();\r\n    try { history.replaceState(null, '', location.pathname + (qs ? '?' + qs : '')); } catch (e) {}\r\n  }\r\n  function applyUrl(){\r\n    const sp = new URLSearchParams(location.search);\r\n    const num = k => { const v = sp.get(k); if (v == null || v === '') return null; const n = Number(v); return isFinite(n) && n >= 0 ? n : null; };\r\n    state.q = (sp.get('q') || '').trim();\r\n    const cat = sp.get('category');\r\n    if (cat) { const c = CAT_BY_ID[cat] || CAT_BY_SLUG[cat]; if (c) { state.cat = c.id; catPath(c.id).forEach(id => expanded.add(id)); } }\r\n    const brandParam = (sp.get('brand') || '').split(',').map(s => s.trim()).filter(Boolean);\r\n    const brandKeys = new Set(ALL.map(p => p._bk).filter(Boolean));\r\n    brandParam.forEach(b => { const k = b.toLowerCase(); if (brandKeys.has(k)) state.brands.add(k); else state.q = (state.q + ' ' + b).trim(); });\r\n    (sp.get('ship') || sp.get('shipped_from') || '').split(',').map(s => slugKey(s)).filter(Boolean).forEach(k => state.ship.add(k));\r\n    let minP = num('min_price'), maxP = num('max_price');\r\n    const pc = sp.get('currency');\r\n    if (pc && pc !== appState.currency) { if (minP != null) minP = conv(minP, pc, appState.currency); if (maxP != null) maxP = conv(maxP, pc, appState.currency); }\r\n    state.minP = minP; state.maxP = maxP;\r\n    const r = Number(sp.get('rating')); if (r === 3 || r === 4) state.rating = r;\r\n    const filter = sp.get('filter');\r\n    if (sp.get('sale') === '1' || (filter === 'deals' && ALL.some(isOnSale))) state.onSale = true;\r\n    if (sp.get('instock') === '1') state.inStock = true;\r\n    state.loc = (sp.get('location') || '').trim();\r\n    const sort = sp.get('sort');\r\n    if (sort === 'newest' || SORTS[sort]) state.sort = sort;\r\n    else if (filter === 'bestsellers' || filter === 'popular') state.sort = 'bestselling';\r\n    const view = sp.get('view') || store.get('b24_shop_view'); if (view === 'list' || view === 'grid') state.view = view;\r\n    const per = Number(sp.get('per') || store.get('b24_shop_per')); if (PER_OPTIONS.includes(per)) state.per = per;\r\n    state.page = Math.max(1, parseInt(sp.get('page'), 10) || 1);\r\n    if (state.q) $('sSearch').value = state.q;\r\n    if (state.loc) $('locInput').value = state.loc;\r\n  }\r\n\r\n  \/\/ ---------- render ----------\r\n  async function renderPage(list){\r\n    const seq = ++_seq;\r\n    const grid = $('productsGrid');\r\n    const start = (state.page - 1) * state.per;\r\n    const slice = list.slice(start, start + state.per);\r\n    grid.className = state.view === 'list' ? 'shop-list' : 'shop-grid';\r\n    if (!slice.length) {\r\n      grid.removeAttribute('aria-busy');\r\n      grid.innerHTML = `<div class=\"empty-state\"><h3>No products match your filters<\/h3><p>Try removing a filter or widening your price range.<\/p><button type=\"button\" class=\"btn-primary\" id=\"emptyClear\">Clear all filters<\/button><\/div>`;\r\n      return;\r\n    }\r\n    grid.setAttribute('aria-busy', 'true');\r\n    try {\r\n      const missing = slice.map(p => p.id).filter(id => !FULL[id]);\r\n      if (missing.length) {\r\n        grid.innerHTML = Array(Math.min(slice.length, 8)).fill('<div class=\"product-card skel\"><div class=\"sk sk-img\"><\/div><div class=\"sk sk-line\"><\/div><div class=\"sk sk-line short\"><\/div><div class=\"sk sk-btn\"><\/div><\/div>').join('');\r\n        const { data, error } = await sb.from('products').select(FULL_COLS).in('id', missing);\r\n        if (error) throw error;\r\n        (data || []).forEach(r => { FULL[r.id] = r; });\r\n        const orgIds = [...new Set((data || []).map(r => r.org_id).filter(id => id && !(id in SELLERS)))];\r\n        if (orgIds.length) {\r\n          try { const { data: orgs } = await sb.from('orgs').select('id,name').in('id', orgIds); (orgs || []).forEach(o => { SELLERS[o.id] = o.name; }); } catch (e) {}\r\n          orgIds.forEach(id => { if (!(id in SELLERS)) SELLERS[id] = null; });\r\n        }\r\n      }\r\n      if (seq !== _seq) return;\r\n      const myCurrency = appState.currency;\r\n      const rows = slice.map(p => FULL[p.id]).filter(Boolean);\r\n      grid.classList.remove('busy');\r\n      grid.innerHTML = rows.map(p => state.view === 'list' ? listCardHtml(p, myCurrency, SELLERS) : productCardHtml(p, myCurrency, SELLERS)).join('');\r\n      grid.removeAttribute('aria-busy');\r\n      wireWishButtons(grid);\r\n    } catch (e) {\r\n      console.error('[shop] page load failed:', e);\r\n      if (seq !== _seq) return;\r\n      grid.classList.remove('busy');\r\n      grid.innerHTML = '<div class=\"empty-state\"><h3>Could not load products<\/h3><p>Please check your connection and try again.<\/p><button type=\"button\" class=\"btn-primary\" id=\"emptyRetry\">Try again<\/button><\/div>';\r\n    }\r\n  }\r\n\r\n  function render(){\r\n    if (!_ready) return;\r\n    const filtered = sortList(ALL.filter(p => matches(p)));\r\n    const pages = Math.max(1, Math.ceil(filtered.length \/ state.per));\r\n    if (state.page > pages) state.page = pages;\r\n    const counts = categoryCounts();\r\n    renderCategories(counts); renderDepartments(counts);\r\n    renderBrands(); renderShip(); renderRating(); renderMore(); renderPrice();\r\n    renderActiveBar();\r\n    const from = filtered.length ? (state.page - 1) * state.per + 1 : 0;\r\n    const to = Math.min(state.page * state.per, filtered.length);\r\n    $('resultCount').textContent = filtered.length\r\n      ? `Showing ${from.toLocaleString('en')}\u2013${to.toLocaleString('en')} of ${filtered.length.toLocaleString('en')} result${filtered.length === 1 ? '' : 's'}${state.q ? ' for \u201c' + state.q + '\u201d' : ''}`\r\n      : '0 results';\r\n    $('sideShow').textContent = `Show ${filtered.length.toLocaleString('en')} result${filtered.length === 1 ? '' : 's'}`;\r\n    $('sSort').value = state.sort; $('sPer').value = String(state.per);\r\n    $('vGrid').classList.toggle('on', state.view === 'grid'); $('vGrid').setAttribute('aria-pressed', state.view === 'grid');\r\n    $('vList').classList.toggle('on', state.view === 'list'); $('vList').setAttribute('aria-pressed', state.view === 'list');\r\n    $('sClear').classList.toggle('show', !!$('sSearch').value);\r\n    renderPager(filtered.length);\r\n    renderHeading();\r\n    renderLanding(!activeFilters().length && state.page === 1 && state.sort === 'newest', counts);\r\n    syncUrl();\r\n    return renderPage(filtered);\r\n  }\r\n  function renderHeading(){\r\n    const names = (state.cat ? catPath(state.cat) : []).map(id => CAT_BY_ID[id]).filter(Boolean);\r\n    const t = state.q ? 'Results for \\u201c' + state.q + '\\u201d' : names.length ? names[names.length - 1].name : '';\r\n    document.title = (t ? t + ' | ' : '') + 'Shop | Builders24';\r\n  }\r\n  async function ensureFull(list){\r\n    const missing = list.map(p => p.id).filter(id => !FULL[id]); if (!missing.length) return;\r\n    const { data } = await sb.from('products').select(FULL_COLS).in('id', missing);\r\n    (data || []).forEach(r => { FULL[r.id] = r; });\r\n  }\r\n  let _lseq = 0, VEH = null;\r\n  async function loadVehicleTiles(){\r\n    if (VEH) return VEH;\r\n    VEH = [];\r\n    if (SHOP_ROOT_SLUGS.length) return VEH; \/\/ building materials only: no vehicle tiles\r\n    try {\r\n      const [{ data: cats }, { data: models }, { data: ls }] = await Promise.all([\r\n        sb.from('vehicle_categories').select('id,parent_id,slug').eq('is_active', true).range(0, 999),\r\n        sb.from('vehicle_models').select('id,category_id').range(0, 9999),\r\n        sb.from('vehicle_listings').select('id,vehicle_model_id,images').eq('is_published', true).order('created_at', { ascending:false }).range(0, 999)\r\n      ]);\r\n      const catOf = {}; (models || []).forEach(m => { catOf[m.id] = m.category_id; });\r\n      const trucks = (cats || []).find(c => c.slug === 'trucks');\r\n      const tIds = new Set(trucks ? [trucks.id, ...(cats || []).filter(c => c.parent_id === trucks.id).map(c => c.id)] : []);\r\n      const img = l => (Array.isArray(l.images) && l.images[0]) || '';\r\n      const pick = list => { const h = list.find(l => \/\\\/storage\\\/v1\\\/object\\\/public\\\/\/.test(img(l))) || list.find(img); return h ? img(h) : ''; };\r\n      const all = ls || [], tr = all.filter(l => tIds.has(catOf[l.vehicle_model_id]));\r\n      if (all.length) VEH.push({ name:'Vehicles', n:all.length, img:pick(all) });\r\n      if (tr.length) VEH.push({ name:'Trucks', n:tr.length, img:pick(tr) });\r\n    } catch (e) { console.error('[shop] vehicle tiles failed', e); }\r\n    return VEH;\r\n  }\r\n  async function renderLanding(show, counts){\r\n    const el = $('landing'), seq = ++_lseq;\r\n    $('gridTitle').hidden = !show;\r\n    if (!show) { el.innerHTML = ''; return; }\r\n    const roots = (CAT_KIDS['__root'] || []).filter(c => counts[c.id]);\r\n    const cands = {}; roots.forEach(c => { cands[c.id] = ALL.filter(p => p._cats[0] === c.id).slice(0, 5); });\r\n    const vehTile = v => `<button type=\"button\" class=\"cat-tile\" data-href=\"https:\/\/app.builders24.net\/auto\/vehicles\/\"><span class=\"ct-img\">${v.img ? `<img src=\"${esc(v.img)}\" alt=\"\" loading=\"lazy\" decoding=\"async\">` : ''}<\/span><b>${esc(v.name)}<\/b><span class=\"ct-n\">${v.n.toLocaleString('en')} listed<\/span><\/button>`;\r\n    const tileHtml = () => roots.map(c => {\r\n      const hit = (cands[c.id] || []).map(p => FULL[p.id]).filter(f => f && Array.isArray(f.images) && f.images.length);\r\n      const f = hit.find(f => \/\\\/storage\\\/v1\\\/object\\\/public\\\/\/.test(f.images[0])) || hit[0];\r\n      return `<button type=\"button\" class=\"cat-tile\" data-dept=\"${c.id}\"><span class=\"ct-img\">${f ? `<img src=\"${esc(f.images[0])}\" alt=\"\" loading=\"lazy\" decoding=\"async\">` : ''}<\/span><b>${esc(c.name)}<\/b><span class=\"ct-n\">${counts[c.id].toLocaleString('en')} items<\/span><\/button>`;\r\n    }).join('') + (VEH || []).map(vehTile).join('');\r\n    const tiles = tileHtml();\r\n    const rows = [\r\n      ['Deals', ALL.filter(isOnSale).sort((a, b) => (b.price - b.sale_price) \/ b.price - (a.price - a.sale_price) \/ a.price).slice(0, 10)],\r\n      ['Best sellers', ALL.filter(p => (p.units_sold || 0) > 0).sort((a, b) => b.units_sold - a.units_sold).slice(0, 10)],\r\n      ['Featured', ALL.filter(p => p.is_featured).slice(0, 10)]\r\n    ].filter(r => r[1].length);\r\n    let html = tiles ? `<div class=\"landing-sec\"><h3>Shop by category<\/h3><div class=\"cat-tiles\">${tiles}<\/div><\/div>` : '';\r\n    el.innerHTML = html;\r\n    try {\r\n      await Promise.all([ensureFull(Object.values(cands).flat().concat(rows.flatMap(r => r[1]))), loadVehicleTiles()]);\r\n      if (seq !== _lseq) return;\r\n      html = `<div class=\"landing-sec\"><h3>Shop by category<\/h3><div class=\"cat-tiles\">${tileHtml()}<\/div><\/div>`;\r\n      rows.forEach(([title, list]) => { html += `<div class=\"landing-sec\"><h3>${title}<\/h3><div class=\"lrow\">${list.map(p => FULL[p.id]).filter(Boolean).map(p => productCardHtml(p, appState.currency, SELLERS)).join('')}<\/div><\/div>`; });\r\n      el.innerHTML = html; wireWishButtons(el);\r\n    } catch (e) { console.error('[shop] landing failed', e); }\r\n  }\r\n  const goTop = () => { const t = $('shopTop'); if (t) t.scrollIntoView({ behavior: 'smooth', block: 'start' }); };\r\n  const reset = () => { state.page = 1; render(); };\r\n\r\n  \/\/ ---------- events ----------\r\n  function openSheet(open){\r\n    $('shopSide').classList.toggle('open', open); $('sheetOverlay').classList.toggle('open', open);\r\n    document.body.style.overflow = open ? 'hidden' : '';\r\n  }\r\n  function wireEvents(){\r\n    $('sSort').innerHTML = Object.entries(SORTS).map(([k, v]) => `<option value=\"${k}\">${v}<\/option>`).join('');\r\n    $('sPer').innerHTML = PER_OPTIONS.map(n => `<option value=\"${n}\">${n}<\/option>`).join('');\r\n    $('sSort').addEventListener('change', e => { state.sort = e.target.value; reset(); });\r\n    $('sPer').addEventListener('change', e => { state.per = Number(e.target.value); store.set('b24_shop_per', String(state.per)); reset(); });\r\n    $('vGrid').addEventListener('click', () => { state.view = 'grid'; store.set('b24_shop_view', 'grid'); render(); });\r\n    $('vList').addEventListener('click', () => { state.view = 'list'; store.set('b24_shop_view', 'list'); render(); });\r\n    const onSearch = debounce(() => { state.q = $('sSearch').value.trim(); reset(); }, 220);\r\n    $('sSearch').addEventListener('input', () => { $('sClear').classList.toggle('show', !!$('sSearch').value); onSearch(); });\r\n    $('sClear').addEventListener('click', () => { $('sSearch').value = ''; state.q = ''; $('sSuggest').classList.remove('open'); reset(); $('sSearch').focus(); });\r\n    $('sSearch').addEventListener('input', debounce(renderSuggest, 120));\r\n    $('sSearch').addEventListener('focus', renderSuggest);\r\n    $('sSearch').addEventListener('keydown', e => { if (e.key === 'Enter') { state.q = $('sSearch').value.trim(); $('sSuggest').classList.remove('open'); reset(); } else if (e.key === 'Escape') $('sSuggest').classList.remove('open'); });\r\n    $('sSuggest').addEventListener('click', e => {\r\n      const b = e.target.closest('button'); if (!b) return;\r\n      if (b.dataset.sgProd) { location.href = 'https:\/\/app.builders24.net\/retail\/product\/' + b.dataset.sgProd; return; }\r\n      if (b.dataset.sgCat) { state.cat = b.dataset.sgCat; state.q = ''; $('sSearch').value = ''; }\r\n      else state.q = $('sSearch').value.trim();\r\n      $('sSuggest').classList.remove('open'); reset(); goTop();\r\n    });\r\n    document.addEventListener('click', e => { if (!e.target.closest('.shop-search')) $('sSuggest').classList.remove('open'); });\r\n    $('catSearch').addEventListener('input', debounce(() => render(), 150));\r\n    $('brandSearch').addEventListener('input', debounce(() => render(), 150));\r\n    $('locInput').addEventListener('input', debounce(() => { state.loc = $('locInput').value.trim(); reset(); }, 250));\r\n    const applyPrice = () => {\r\n      const mn = $('pMin').value.trim(), mx = $('pMax').value.trim();\r\n      let a = mn === '' ? null : Math.max(0, Number(mn)), b = mx === '' ? null : Math.max(0, Number(mx));\r\n      if (a != null && b != null && a > b) { const t = a; a = b; b = t; }\r\n      state.minP = (a != null && isFinite(a)) ? a : null; state.maxP = (b != null && isFinite(b)) ? b : null; reset();\r\n    };\r\n    $('pGo').addEventListener('click', applyPrice);\r\n    ['pMin', 'pMax'].forEach(id => { $(id).addEventListener('keydown', e => { if (e.key === 'Enter') { e.preventDefault(); applyPrice(); } }); $(id).addEventListener('change', applyPrice); });\r\n    $('priceChips').addEventListener('click', e => {\r\n      const b = e.target.closest('[data-band]'); if (!b) return; const band = _bands[Number(b.dataset.band)]; if (!band) return;\r\n      const same = state.minP === band.min && state.maxP === band.max;\r\n      state.minP = same ? null : band.min; state.maxP = same ? null : band.max; reset();\r\n    });\r\n    $('catTree').addEventListener('click', e => {\r\n      const tog = e.target.closest('[data-tog]');\r\n      if (tog) { const id = tog.dataset.tog; expanded.has(id) ? expanded.delete(id) : expanded.add(id); render(); return; }\r\n      const row = e.target.closest('[data-cat]'); if (!row) return;\r\n      const id = row.dataset.cat; state.cat = state.cat === id ? '' : id;\r\n      if (state.cat) catPath(state.cat).forEach(x => expanded.add(x));\r\n      reset(); if (state.cat && window.innerWidth < 900) { \/* keep sheet open so buyers can keep refining *\/ }\r\n    });\r\n    $('landing').addEventListener('click', e => { const h = e.target.closest('[data-href]'); if (h) { location.href = h.dataset.href; return; } const b = e.target.closest('[data-dept]'); if (!b) return; state.cat = b.dataset.dept; reset(); goTop(); });\r\n    $('deptRow').addEventListener('click', e => {\r\n      const b = e.target.closest('[data-dept]'); if (!b) return; const id = b.dataset.dept;\r\n      state.cat = (id && state.cat === id) ? '' : id;\r\n      if (state.cat) catPath(state.cat).forEach(x => expanded.add(x));\r\n      reset();\r\n    });\r\n    $('brandList').addEventListener('change', e => { const k = e.target.dataset.brand; if (k == null) return; e.target.checked ? state.brands.add(k) : state.brands.delete(k); reset(); });\r\n    $('brandList').addEventListener('click', e => { if (e.target.id === 'brandMore') { _showAllBrands = !_showAllBrands; render(); } });\r\n    $('shipList').addEventListener('change', e => { const k = e.target.dataset.ship; if (k == null) return; e.target.checked ? state.ship.add(k) : state.ship.delete(k); reset(); });\r\n    $('ratingList').addEventListener('change', e => { const r = Number(e.target.dataset.rating); if (r) { state.rating = r; reset(); } });\r\n    $('ratingList').addEventListener('click', e => { if (e.target.id === 'ratingClear') { state.rating = 0; reset(); } });\r\n    $('moreList').addEventListener('change', e => { if (e.target.id === 'fOnSale') state.onSale = e.target.checked; if (e.target.id === 'fInStock') state.inStock = e.target.checked; reset(); });\r\n    $('activeBar').addEventListener('click', e => {\r\n      if (e.target.id === 'afClear') { clearAll(); return; }\r\n      const rm = e.target.closest('[data-rm]'); if (!rm) return; const fn = _chipActions[Number(rm.dataset.rm)]; if (fn) { fn(); reset(); }\r\n    });\r\n    $('pager').addEventListener('click', e => {\r\n      const b = e.target.closest('[data-pg]'); if (!b || b.disabled) return;\r\n      const n = Number(b.dataset.pg); if (!n || n === state.page) return; state.page = n; render(); goTop();\r\n    });\r\n    $('productsGrid').addEventListener('click', e => {\r\n      if (e.target.id === 'emptyClear') clearAll();\r\n      if (e.target.id === 'emptyRetry') render();\r\n    });\r\n    $('openFilters').addEventListener('click', () => openSheet(true));\r\n    $('sideClose').addEventListener('click', () => openSheet(false));\r\n    $('sideShow').addEventListener('click', () => { openSheet(false); goTop(); });\r\n    $('sideClear').addEventListener('click', clearAll);\r\n    $('sheetOverlay').addEventListener('click', () => openSheet(false));\r\n    document.addEventListener('keydown', e => { if (e.key === 'Escape') openSheet(false); });\r\n    window.addEventListener('resize', debounce(() => { if (window.innerWidth >= 900) openSheet(false); }, 150));\r\n  }\r\n\r\n  async function initShop(){\r\n    wireEvents();\r\n    try {\r\n      try { await loadExchangeRates(); } catch (e) {}\r\n      appState.currency = await getCustomerCurrency();\r\n      await Promise.all([loadCategories(), loadCatalogue()]);\r\n      \/\/ Categories and products load in parallel, so restrict the catalogue only once both are in.\r\n      if (SHOP_ROOT_SLUGS.length) ALL = ALL.filter(p => CAT_BY_ID[p.category_id]);\r\n      await loadSellers();\r\n      buildSearchIndex();\r\n      recomputePrices();\r\n      applyUrl();\r\n      _ready = true;\r\n      await render();\r\n    } catch (e) {\r\n      console.error('[shop] init failed:', e);\r\n      $('resultCount').textContent = 'Could not load products';\r\n      $('productsGrid').removeAttribute('aria-busy');\r\n      $('productsGrid').innerHTML = '<div class=\"empty-state\"><h3>Could not load products<\/h3><p>Please check your connection and try again.<\/p><button type=\"button\" class=\"btn-primary\" onclick=\"location.reload()\">Reload<\/button><\/div>';\r\n    }\r\n  }\r\n\r\n  \/\/ ===== Currency + Delivery selector \u2014 real, standard on this header.\r\n  \/\/ Real LOCATIONS list (kept from the live shop.html) and the real\r\n  \/\/ getCustomerCurrency\/setCustomerCurrency system \u2014 a lightweight\r\n  \/\/ dropdown rather than the full-screen modal, same underlying data. =====\r\n  const LOCATIONS = window.B24_LOCATIONS || [];\r\n  \/\/ Single real source: header.js sets window.B24_LOCATIONS (all 54\r\n  \/\/ African countries), loaded before this script now runs. Local\r\n  \/\/ duplicate array removed \u2014 this page no longer keeps its own copy.\r\n  appState.locationIndex = 0;\r\n\r\n  \/\/ ===== Real delivery-location detection \u2014 ported from index.html, not\r\n  \/\/ reinvented. Logged-in customer: real saved address (customer_addresses).\r\n  \/\/ Guest: IP geolocation with a timezone fallback, matched against the\r\n  \/\/ real LOCATIONS list. Shows just the city (fits the compact header). =====\r\n  let _customerDefaultAddressLabel = null; \/\/ null = not checked, false = none found\r\n  async function getCustomerDefaultAddress() {\r\n    if (_customerDefaultAddressLabel !== null) return _customerDefaultAddressLabel;\r\n    try {\r\n      const { data: { session } } = await sb.auth.getSession();\r\n      if (!session) { _customerDefaultAddressLabel = false; return false; }\r\n      const { data } = await sb.from('customer_addresses').select('suburb, city').eq('customer_id', session.user.id).order('is_default', { ascending: false }).limit(1).maybeSingle();\r\n      _customerDefaultAddressLabel = (data && data.city) ? data.city : false;\r\n    } catch (e) { _customerDefaultAddressLabel = false; }\r\n    return _customerDefaultAddressLabel;\r\n  }\r\n\r\n  const TZ_CITY_MAP = {\r\n    'Africa\/Johannesburg':'Johannesburg', 'Africa\/Lusaka':'Lusaka', 'Africa\/Luanda':'Luanda',\r\n    'Africa\/Harare':'Harare', 'Africa\/Maputo':'Maputo', 'Africa\/Gaborone':'Gaborone',\r\n    'Africa\/Kinshasa':'Kinshasa', 'Africa\/Windhoek':'Windhoek', 'Africa\/Dar_es_Salaam':'Dar es Salaam',\r\n    'Africa\/Kampala':'Kampala', 'Africa\/Lagos':'Lagos', 'Africa\/Nairobi':'Nairobi'\r\n  };\r\n  async function detectGuestLocation(){\r\n    let session = null;\r\n    try { const { data } = await sb.auth.getSession(); session = data.session; } catch (e) {}\r\n    const isGuest = !session;\r\n    let matchIdx = -1;\r\n    try {\r\n      const res = await fetch('https:\/\/ipapi.co\/json\/');\r\n      const data = await res.json();\r\n      if (data && data.country_name) {\r\n        matchIdx = LOCATIONS.findIndex(l => l.country === data.country_name && data.city && l.city.toLowerCase() === String(data.city).toLowerCase());\r\n        if (matchIdx === -1) matchIdx = LOCATIONS.findIndex(l => l.country === data.country_name);\r\n      }\r\n    } catch (e) { \/* IP lookup can fail\/be rate-limited \u2014 timezone fallback below covers it *\/ }\r\n    if (matchIdx === -1) {\r\n      try {\r\n        const tz = Intl.DateTimeFormat().resolvedOptions().timeZone;\r\n        const cityGuess = TZ_CITY_MAP[tz];\r\n        if (cityGuess) matchIdx = LOCATIONS.findIndex(l => l.city === cityGuess);\r\n      } catch (e) {}\r\n    }\r\n    if (matchIdx !== -1) {\r\n      appState.locationIndex = matchIdx;\r\n      if (isGuest) appState.currency = LOCATIONS[matchIdx].currency;\r\n    }\r\n  }\r\n\r\n  function updateDeliverLabel(){\r\n    const loc = LOCATIONS[appState.locationIndex];\r\n    const label = _customerDefaultAddressLabel || (loc && loc.city);\r\n    if (!label) return;\r\n    document.querySelectorAll('.deliver-city-text').forEach(el => el.textContent = label);\r\n  }\r\n\r\n  \/\/ header.js calls window.renderPrices() after the customer changes\r\n  \/\/ currency or delivery location in the shared picker \u2014 this is what\r\n  \/\/ keeps displayed prices in sync with that choice on this page.\r\n  window.renderPrices = async () => {\r\n    try { appState.currency = await getCustomerCurrency(); } catch (e) {}\r\n    recomputePrices();\r\n    render();\r\n  };\r\n\r\n  \/\/ Exposed so the B24Nav.init() sequencing block (separate script tag\r\n  \/\/ below, since it needs to run after header.js injects the header) can\r\n  \/\/ re-sync the delivery label once .deliver-city-text actually exists in\r\n  \/\/ the DOM. This script runs immediately (not gated on DOMContentLoaded),\r\n  \/\/ which is before that injection happens \u2014 so the call inside boot()\r\n  \/\/ below is a harmless best-effort attempt that likely finds nothing yet.\r\n  window.__updateDeliverLabel = updateDeliverLabel;\r\n\r\n  (async () => {\r\n    initShop(); \/\/ catalogue, categories and rates load in parallel; first paint as soon as they arrive\r\n    if (typeof updateCartBadge === 'function') updateCartBadge();\r\n\r\n    await getCustomerDefaultAddress();\r\n    \/\/ The app header (delivery label) is not loaded on this page, so no delivery-location detection is needed here.\r\n\r\n    \/\/ Location detection can nudge appState.currency for guests; settle on the\r\n    \/\/ shopper's real selected currency and re-render only if it differs.\r\n    const before = appState.currency;\r\n    try { appState.currency = await getCustomerCurrency(); } catch (e) {}\r\n    if (_ready && before !== appState.currency) { recomputePrices(); render(); }\r\n  })();\r\n\r\n})();\r\n<\/script>\r\n<\/body>\r\n<\/html>\t\t\t\t<\/div>\n\t\t\n<\/div>\n\t\t<\/div>\n\t\t","protected":false},"excerpt":{"rendered":"<p>Shop Online in Zimbabwe | Building Materials, Equipment, Tools, Home Goods &amp; More | Builders24 Zimbabwe Shop Online in Zimbabwe, Sold and Delivered by Builders24 Builders24 Zimbabwe sells building materials, equipment and machinery, tools, home goods, appliances, electronics and more in Zimbabwe. We supply shoppers, contractors and businesses in Zimbabwe from Zimbabwean and international suppliers, and deliver to every province in Zimbabwe. Search by product, brand or category and order online with secure checkout. QualityGuaranteed NationwideDelivery Trusted byPros SecurePayments &times; Filters Sort by SHOP IN SOUTH AFRICA BUILDERS24 SELLS AND DELIVERSEVERYTHING YOU NEED IN SOUTH AFRICA. Start shopping Filters&times; Categories Price \/ Budget Min Max Go Brand Shipped from Customer rating Availability &amp; deals Seller location Clear all Show results Loading&hellip; Show per page All products<\/p>\n","protected":false},"author":1,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"elementor_header_footer","meta":{"footnotes":""},"class_list":["post-153231","page","type-page","status-publish","hentry"],"_links":{"self":[{"href":"https:\/\/builders24.net\/zw\/wp-json\/wp\/v2\/pages\/153231","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/builders24.net\/zw\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/builders24.net\/zw\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/builders24.net\/zw\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/builders24.net\/zw\/wp-json\/wp\/v2\/comments?post=153231"}],"version-history":[{"count":3,"href":"https:\/\/builders24.net\/zw\/wp-json\/wp\/v2\/pages\/153231\/revisions"}],"predecessor-version":[{"id":153430,"href":"https:\/\/builders24.net\/zw\/wp-json\/wp\/v2\/pages\/153231\/revisions\/153430"}],"wp:attachment":[{"href":"https:\/\/builders24.net\/zw\/wp-json\/wp\/v2\/media?parent=153231"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}