171 lines
7.8 KiB
JavaScript
171 lines
7.8 KiB
JavaScript
(() => {
|
|
const CFG = window.PROLOGY_CONFIG;
|
|
|
|
// ── Icons ──
|
|
const ICON_PHONE = `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" aria-hidden="true"><path d="M22 16.92v3a2 2 0 01-2.18 2 19.79 19.79 0 01-8.63-3.07 19.5 19.5 0 01-6-6 19.79 19.79 0 01-3.07-8.67A2 2 0 014.11 2h3a2 2 0 012 1.72 12.84 12.84 0 00.7 2.81 2 2 0 01-.45 2.11L8.09 9.91a16 16 0 006 6l1.27-1.27a2 2 0 012.11-.45 12.84 12.84 0 002.81.7A2 2 0 0122 16.92z" stroke="rgba(255,255,255,.5)" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>`;
|
|
const ICON_EMAIL = `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" aria-hidden="true"><path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z" stroke="rgba(255,255,255,.5)" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/><polyline points="22,6 12,13 2,6" stroke="rgba(255,255,255,.5)" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>`;
|
|
const SOCIAL_SVG = {
|
|
facebook: `<svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M18 2h-3a5 5 0 00-5 5v3H7v4h3v8h4v-8h3l1-4h-4V7a1 1 0 011-1h3z"/></svg>`,
|
|
linkedin: `<svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M20.45 20.45h-3.55v-5.57c0-1.33-.03-3.03-1.85-3.03-1.85 0-2.13 1.45-2.13 2.93v5.67H9.37V9h3.41v1.56h.05c.48-.9 1.64-1.85 3.37-1.85 3.6 0 4.27 2.37 4.27 5.45v6.29zM5.34 7.43a2.06 2.06 0 1 1 0-4.12 2.06 2.06 0 0 1 0 4.12zM7.12 20.45H3.56V9h3.56v11.45z"/></svg>`,
|
|
youtube: `<svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M23.5 6.2s-.23-1.63-.95-2.35c-.91-.95-1.93-.96-2.4-1.01C17.05 2.67 12 2.67 12 2.67s-5.05 0-8.15.17c-.47.05-1.49.06-2.4 1.01C.73 4.57.5 6.2.5 6.2S.27 8.1.27 10v1.8c0 1.9.23 3.8.23 3.8s.23 1.63.95 2.35c.91.95 2.14.92 2.68 1.02C5.64 19.15 12 19.2 12 19.2s5.05-.01 8.15-.18c.47-.05 1.49-.07 2.4-1.02.72-.72.95-2.35.95-2.35s.23-1.9.23-3.8V10c0-1.9-.23-3.8-.23-3.8zM9.73 14.18V8.45l6.48 2.88-6.48 2.85z"/></svg>`,
|
|
ebay: `<span aria-hidden="true">eBay</span>`,
|
|
};
|
|
|
|
// ── Helpers ──
|
|
const escape = (s) =>
|
|
String(s ?? "").replace(/[&<>"']/g, (c) => ({
|
|
"&": "&", "<": "<", ">": ">", '"': """, "'": "'",
|
|
})[c]);
|
|
|
|
const linkUrl = (item) => {
|
|
const url = item.url || "";
|
|
if (item.external || /^https?:\/\//i.test(url)) return url;
|
|
if (url.startsWith("/")) return url;
|
|
const trimmed = url.replace(/^\/+/, "");
|
|
const suffix = /\.[a-z0-9]+$/i.test(trimmed) ? "" : "/";
|
|
return CFG.FOOTER_BASE_URL + trimmed + suffix;
|
|
};
|
|
|
|
const staticUrl = (rel) =>
|
|
/^https?:\/\//i.test(rel) ? rel : CFG.FOOTER_STATIC_URL + rel.replace(/^\/+/, "");
|
|
|
|
// ── Renderers ──
|
|
const renderBrand = (data) => {
|
|
const phoneItems = (data.phones || [])
|
|
.flatMap((g) =>
|
|
(g.numbers || []).map(
|
|
(n) => `<li class="ns-footer__trust-item">
|
|
${ICON_PHONE}
|
|
<span class="ns-footer__region-label">${escape(g.label)}:</span>
|
|
<a href="${escape(n.href)}">${escape(n.display)}</a>
|
|
</li>`,
|
|
),
|
|
)
|
|
.join("");
|
|
|
|
const emailItems = (data.emails || [])
|
|
.flatMap((g) =>
|
|
(g.addresses || []).map(
|
|
(addr) => `<li class="ns-footer__trust-item">
|
|
${ICON_EMAIL}
|
|
<a href="mailto:${escape(addr)}">${escape(addr)}</a>
|
|
</li>`,
|
|
),
|
|
)
|
|
.join("");
|
|
|
|
const addressBlocks = (data.addresses || [])
|
|
.map(
|
|
(a) => `<p class="ns-footer__company ns-footer__company--address">
|
|
<strong class="ns-footer__region-label">${escape(a.label)}:</strong><br>
|
|
${(a.lines || []).map(escape).join("<br>")}
|
|
</p>`,
|
|
)
|
|
.join("");
|
|
|
|
return `
|
|
<div class="ns-footer__brand">
|
|
<a class="ns-footer__logo" href="${escape(CFG.FOOTER_BASE_URL)}" aria-label="Home">
|
|
<img src="${escape(CFG.FOOTER_LOGO_URL)}" alt="Prology" width="170" height="35" loading="lazy">
|
|
</a>
|
|
<p class="ns-footer__company ns-footer__company--top">${escape(data.company_name || "")}</p>
|
|
${addressBlocks}
|
|
<ul class="ns-footer__trust-list">${phoneItems}${emailItems}</ul>
|
|
</div>`;
|
|
};
|
|
|
|
// Internal items render as <span> that opens the store picker — the picked
|
|
// store then redirects to {STORE_URL}/{slug-or-url}. External items render
|
|
// as a regular <a target="_blank">.
|
|
const renderLinkInner = (item) => {
|
|
const label = escape(item.label);
|
|
const url = item.url || "";
|
|
if (item.external || /^https?:\/\//i.test(url)) {
|
|
return `<a href="${escape(linkUrl(item))}" target="_blank" rel="noopener">${label}</a>`;
|
|
}
|
|
const slug = escape(item.slug || url || "");
|
|
const call = `openStoreModal('${slug}')`;
|
|
return `<span class="ns-footer__category" role="button" tabindex="0" onclick="${call}" onkeydown="if(event.key==='Enter'||event.key===' '){event.preventDefault();${call};}">${label}</span>`;
|
|
};
|
|
|
|
const renderCol = (title, items) => {
|
|
if (!items || !items.length) return "";
|
|
const lis = items.map((it) => `<li>${renderLinkInner(it)}</li>`).join("");
|
|
return `<nav class="ns-footer__col" aria-label="${escape(title)}">
|
|
<h3 class="ns-footer__col-title">${escape(title)}</h3>
|
|
<ul>${lis}</ul>
|
|
</nav>`;
|
|
};
|
|
|
|
const renderPartnerRow = (label, items, ariaLabel) => {
|
|
if (!items || !items.length) return "";
|
|
const imgs = items
|
|
.map((p) => {
|
|
const w = p.width ? ` width="${p.width}"` : "";
|
|
const h = p.height ? ` height="${p.height}"` : "";
|
|
return `<img src="${escape(staticUrl(p.image))}" alt="${escape(p.name)}" title="${escape(p.name)}" loading="lazy"${w}${h} role="listitem">`;
|
|
})
|
|
.join("");
|
|
return `<div class="ns-footer__partner-row">
|
|
<span class="ns-footer__partner-label">${escape(label)}</span>
|
|
<div class="ns-footer__partner-logos" role="list" aria-label="${escape(ariaLabel)}">${imgs}</div>
|
|
</div>`;
|
|
};
|
|
|
|
const renderSocial = (items) => {
|
|
if (!items || !items.length) return "";
|
|
return items
|
|
.map((s) => {
|
|
const icon = SOCIAL_SVG[s.icon] || "";
|
|
const cls =
|
|
"ns-footer__social-link" +
|
|
(s.icon === "ebay" ? " ns-footer__social-link--ebay" : "");
|
|
return `<a class="${cls}" href="${escape(s.url)}" target="_blank" rel="noopener noreferrer" aria-label="${escape(s.name)}">${icon}</a>`;
|
|
})
|
|
.join("");
|
|
};
|
|
|
|
const renderFooter = (data) => {
|
|
const links = data.links || {};
|
|
return `
|
|
<div class="footer content">
|
|
<div class="ns-footer__top">
|
|
${renderBrand(data)}
|
|
${renderCol("Categories", CFG.FOOTER_CATEGORIES)}
|
|
${renderCol("Information", links.information)}
|
|
${renderCol("Your Orders", links.your_orders)}
|
|
${renderCol("Support", links.support)}
|
|
</div>
|
|
<div class="ns-footer__partners">
|
|
${renderPartnerRow("Payment Methods", data.payment_methods, "We accept")}
|
|
${renderPartnerRow("Shipping Partners", data.shipping_partners, "We ship with")}
|
|
</div>
|
|
<div class="ns-footer__bottom">
|
|
<p class="ns-footer__copyright">${escape(CFG.FOOTER_COPYRIGHT)}</p>
|
|
<nav class="ns-footer__bottom-links" aria-label="Legal">
|
|
${CFG.FOOTER_LEGAL_LINKS.map(renderLinkInner).join("")}
|
|
</nav>
|
|
<div class="ns-footer__social" aria-label="Follow us">
|
|
${renderSocial(data.social)}
|
|
</div>
|
|
</div>
|
|
</div>`;
|
|
};
|
|
|
|
// ── Boot ──
|
|
const mount = document.querySelector("footer.page-footer");
|
|
if (!mount) return;
|
|
|
|
fetch(CFG.FOOTER_DATA_URL)
|
|
.then((r) => {
|
|
if (!r.ok) throw new Error("HTTP " + r.status);
|
|
return r.json();
|
|
})
|
|
.then((data) => {
|
|
mount.innerHTML = renderFooter(data);
|
|
})
|
|
.catch((err) => {
|
|
console.error("[footer] failed to load", CFG.FOOTER_DATA_URL, err);
|
|
});
|
|
})();
|