prology-landingpage/assets/js/footer.js

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) => ({
"&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;",
})[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);
});
})();