first commit
This commit is contained in:
commit
4c7542bd7f
Binary file not shown.
|
After Width: | Height: | Size: 253 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 16 KiB |
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,169 @@
|
||||||
|
{
|
||||||
|
"_comment": "Schema for Magento Custom Variable code 'prology_footer_config'. Paste WITHOUT this _comment field into Stores > Custom Variables > prology_footer_config > Plain Value. JSON invalid sẽ tự fallback về defaults trong footer.phtml.",
|
||||||
|
|
||||||
|
"company_name": "Prology Pty Ltd Trading as Prology",
|
||||||
|
|
||||||
|
"addresses": [
|
||||||
|
{
|
||||||
|
"label": "AU Warehouse",
|
||||||
|
"lines": ["Unit 8/4A Bachell Ave,", "Lidcombe NSW 2141, Australia"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "US Warehouse",
|
||||||
|
"lines": ["17150 Newhope St Ste 308,", "Fountain Valley, CA 92708, U.S.A"]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
|
||||||
|
"phones": [
|
||||||
|
{
|
||||||
|
"label": "AU",
|
||||||
|
"numbers": [{ "display": "+612 8061 6886", "href": "tel:+61280616886" }]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "US",
|
||||||
|
"numbers": [{ "display": "+1 4689 3350", "href": "tel:+146893350" }]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
|
||||||
|
"emails": [
|
||||||
|
{
|
||||||
|
"label": "Sales",
|
||||||
|
"addresses": ["sales@prology.net"]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
|
||||||
|
"links": {
|
||||||
|
"information": [
|
||||||
|
{ "label": "About Us", "url": "about-us", "external": false },
|
||||||
|
{ "label": "News", "url": "https://prology.net/blog", "external": true },
|
||||||
|
{ "label": "Contact Us", "url": "contact", "external": false },
|
||||||
|
{ "label": "Terms of Use & Sale", "url": "terms", "external": false },
|
||||||
|
{
|
||||||
|
"label": "Privacy & Cookie Policy",
|
||||||
|
"url": "privacy-policy-cookie-restriction-mode",
|
||||||
|
"external": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"your_orders": [
|
||||||
|
{ "label": "My Account", "url": "customer/account", "external": false },
|
||||||
|
{ "label": "My Order", "url": "sales/order/history", "external": false },
|
||||||
|
{ "label": "My Wish List", "url": "wishlist", "external": false },
|
||||||
|
{
|
||||||
|
"label": "Manufacturers List",
|
||||||
|
"url": "manufacturers",
|
||||||
|
"external": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"support": [
|
||||||
|
{
|
||||||
|
"label": "Track Your Order",
|
||||||
|
"url": "sales/guest/form",
|
||||||
|
"external": false
|
||||||
|
},
|
||||||
|
{ "label": "Returns & Exchanges", "url": "returns", "external": false },
|
||||||
|
{ "label": "FAQ", "url": "faq", "external": false }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
"payment_methods": [
|
||||||
|
{
|
||||||
|
"name": "PayPal",
|
||||||
|
"image": "images/icons/payment/paypal.png",
|
||||||
|
"width": 48,
|
||||||
|
"height": 48
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Visa",
|
||||||
|
"image": "images/icons/payment/visa.png",
|
||||||
|
"width": 48,
|
||||||
|
"height": 48
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Mastercard",
|
||||||
|
"image": "images/icons/payment/master.png",
|
||||||
|
"width": 48,
|
||||||
|
"height": 48
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "American Express",
|
||||||
|
"image": "images/icons/payment/amex.png",
|
||||||
|
"width": 48,
|
||||||
|
"height": 48
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Afterpay",
|
||||||
|
"image": "images/icons/payment/after.png",
|
||||||
|
"width": 48,
|
||||||
|
"height": 48
|
||||||
|
}
|
||||||
|
],
|
||||||
|
|
||||||
|
"shipping_partners": [
|
||||||
|
{ "name": "DHL Express", "image": "images/icons/shipping/dhl.png" },
|
||||||
|
{
|
||||||
|
"name": "UPS",
|
||||||
|
"image": "images/icons/shipping/ups.png",
|
||||||
|
"width": 48,
|
||||||
|
"height": 48
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Fedex",
|
||||||
|
"image": "images/icons/shipping/fedex.png",
|
||||||
|
"width": 68,
|
||||||
|
"height": 68
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "TNT",
|
||||||
|
"image": "images/icons/shipping/tnt.png",
|
||||||
|
"width": 68,
|
||||||
|
"height": 68
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Couriers Please",
|
||||||
|
"image": "images/icons/shipping/couriers.png",
|
||||||
|
"width": 68,
|
||||||
|
"height": 68
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Startrack",
|
||||||
|
"image": "images/icons/shipping/star.png",
|
||||||
|
"width": 68,
|
||||||
|
"height": 68
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Australia Post",
|
||||||
|
"image": "images/icons/shipping/aupost.png",
|
||||||
|
"width": 68,
|
||||||
|
"height": 68
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Sendle",
|
||||||
|
"image": "images/icons/shipping/sendle.png",
|
||||||
|
"width": 68,
|
||||||
|
"height": 68
|
||||||
|
}
|
||||||
|
],
|
||||||
|
|
||||||
|
"social": [
|
||||||
|
{
|
||||||
|
"name": "Facebook",
|
||||||
|
"url": "https://www.facebook.com/prology.net/",
|
||||||
|
"icon": "facebook"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "LinkedIn",
|
||||||
|
"url": "https://www.linkedin.com/company/unavailable/",
|
||||||
|
"icon": "linkedin"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "eBay",
|
||||||
|
"url": "https://www.ebay.com.au/str/prologyptyltd",
|
||||||
|
"icon": "ebay"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "YouTube",
|
||||||
|
"url": "https://www.youtube.com/@PrologyPtyLtd",
|
||||||
|
"icon": "youtube"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
window.PROLOGY_CONFIG = {
|
||||||
|
API_URL: "https://prologyms.nswteam.net/chat-plugin/api/support",
|
||||||
|
LOGO_SRC: "assets/Prology_logo.png",
|
||||||
|
|
||||||
|
// ── Footer ──
|
||||||
|
FOOTER_DATA_URL: "assets/data-footer.json",
|
||||||
|
FOOTER_BASE_URL: "http://localhost:8080/",
|
||||||
|
FOOTER_STATIC_URL:
|
||||||
|
"http://localhost:8080/static/version1778227322/frontend/Netstore/default/en_US/",
|
||||||
|
FOOTER_LOGO_URL:
|
||||||
|
"http://localhost:8080/media/logo/stores/1/Prology-2-e1665964900953_6_.png",
|
||||||
|
FOOTER_CATEGORIES: [
|
||||||
|
{ label: "Compute", url: "compute.html" },
|
||||||
|
{ label: "Storage", url: "storage.html" },
|
||||||
|
{ label: "Networking", url: "networking.html" },
|
||||||
|
{ label: "Security", url: "security.html" },
|
||||||
|
{ label: "End User", url: "end-user.html" },
|
||||||
|
{ label: "Data Center", url: "data-center.html" },
|
||||||
|
{ label: "Components", url: "components.html" },
|
||||||
|
],
|
||||||
|
FOOTER_LEGAL_LINKS: [
|
||||||
|
{ label: "Privacy Policy", url: "privacy-policy-cookie-restriction-mode" },
|
||||||
|
{ label: "Terms of Use & Sale", url: "terms" },
|
||||||
|
{ label: "Cookie Policy", url: "enable-cookies" },
|
||||||
|
],
|
||||||
|
FOOTER_COPYRIGHT:
|
||||||
|
"Copyright © 2013-present Magento, Inc. All rights reserved.",
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,162 @@
|
||||||
|
(() => {
|
||||||
|
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;
|
||||||
|
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="" 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>`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderCol = (title, items) => {
|
||||||
|
if (!items || !items.length) return "";
|
||||||
|
const lis = items
|
||||||
|
.map(
|
||||||
|
(it) =>
|
||||||
|
`<li><a href="${escape(linkUrl(it))}"${it.external ? ' target="_blank" rel="noopener"' : ""}>${escape(it.label)}</a></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(
|
||||||
|
(l) => `<a href="${escape(linkUrl(l))}">${escape(l.label)}</a>`,
|
||||||
|
).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);
|
||||||
|
});
|
||||||
|
})();
|
||||||
|
|
@ -0,0 +1,337 @@
|
||||||
|
document.addEventListener("DOMContentLoaded", () => {
|
||||||
|
// Fix double H1: demote hidden Magento page-title h1 to <p>
|
||||||
|
const magH1 = document.querySelector(".page-title-wrapper h1");
|
||||||
|
if (magH1) {
|
||||||
|
const p = document.createElement("p");
|
||||||
|
p.className = magH1.className;
|
||||||
|
p.innerHTML = magH1.innerHTML;
|
||||||
|
magH1.parentNode.replaceChild(p, magH1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hamburger menu toggle
|
||||||
|
const hamburgerBtn = document.querySelector(".hamburger-btn");
|
||||||
|
const navMenu = document.querySelector(".nav-links");
|
||||||
|
const navBackdrop = document.getElementById("nav-backdrop");
|
||||||
|
|
||||||
|
function closeNavMenu() {
|
||||||
|
navMenu.classList.remove("open");
|
||||||
|
hamburgerBtn.classList.remove("open");
|
||||||
|
hamburgerBtn.setAttribute("aria-expanded", "false");
|
||||||
|
if (navBackdrop) navBackdrop.classList.remove("open");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hamburgerBtn && navMenu) {
|
||||||
|
hamburgerBtn.addEventListener("click", () => {
|
||||||
|
const isOpen = navMenu.classList.toggle("open");
|
||||||
|
hamburgerBtn.classList.toggle("open", isOpen);
|
||||||
|
hamburgerBtn.setAttribute("aria-expanded", String(isOpen));
|
||||||
|
if (navBackdrop) navBackdrop.classList.toggle("open", isOpen);
|
||||||
|
});
|
||||||
|
navMenu.querySelectorAll("a").forEach((a) => {
|
||||||
|
a.addEventListener("click", closeNavMenu);
|
||||||
|
});
|
||||||
|
if (navBackdrop) navBackdrop.addEventListener("click", closeNavMenu);
|
||||||
|
}
|
||||||
|
|
||||||
|
const sections = document.querySelectorAll(".section");
|
||||||
|
const navLinks = document.querySelectorAll(".nav-links a");
|
||||||
|
const scrollIndicator = document.querySelector(".scroll-indicator");
|
||||||
|
const heroInput = document.getElementById("hero-input");
|
||||||
|
const typewriterEl = document.getElementById("typewriter-title");
|
||||||
|
|
||||||
|
// Multi-stage Typewriter Sequence
|
||||||
|
const typeStrings = [
|
||||||
|
"Network hardware sourcing made simple",
|
||||||
|
"for every site",
|
||||||
|
"for every need",
|
||||||
|
"for every environment",
|
||||||
|
];
|
||||||
|
|
||||||
|
async function runTypewriter() {
|
||||||
|
if (!typewriterEl) return;
|
||||||
|
|
||||||
|
async function typeInto(element, text, speed) {
|
||||||
|
element.classList.add("typing-cursor");
|
||||||
|
for (const char of text) {
|
||||||
|
element.textContent += char;
|
||||||
|
await new Promise((r) => setTimeout(r, speed));
|
||||||
|
}
|
||||||
|
element.classList.remove("typing-cursor");
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fadeStep() {
|
||||||
|
typewriterEl.style.opacity = "0";
|
||||||
|
await new Promise((r) => setTimeout(r, 600));
|
||||||
|
typewriterEl.innerHTML = "";
|
||||||
|
typewriterEl.style.opacity = "1";
|
||||||
|
await new Promise((r) => setTimeout(r, 200));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delay for server animation
|
||||||
|
await new Promise((r) => setTimeout(r, 3500));
|
||||||
|
|
||||||
|
// -- Text Phases --
|
||||||
|
for (let i = 0; i < typeStrings.length; i++) {
|
||||||
|
const span = document.createElement("span");
|
||||||
|
typewriterEl.appendChild(span);
|
||||||
|
await typeInto(span, typeStrings[i], 45);
|
||||||
|
|
||||||
|
await new Promise((r) => setTimeout(r, 1200));
|
||||||
|
await fadeStep();
|
||||||
|
}
|
||||||
|
|
||||||
|
// -- FINAL PHASE: BRAND LOGO --
|
||||||
|
const brandBox = document.createElement("div");
|
||||||
|
brandBox.style.display = "flex";
|
||||||
|
brandBox.style.flexDirection = "column";
|
||||||
|
brandBox.style.alignItems = "center";
|
||||||
|
brandBox.style.gap = "15px";
|
||||||
|
brandBox.style.opacity = "0"; // Start invisible
|
||||||
|
brandBox.style.transition = "opacity 0.6s ease-out";
|
||||||
|
|
||||||
|
const brandImg = document.createElement("img");
|
||||||
|
brandImg.src = window.PROLOGY_CONFIG.LOGO_SRC;
|
||||||
|
brandImg.style.height = "80px";
|
||||||
|
brandImg.style.width = "auto";
|
||||||
|
brandImg.style.filter = "none"; // Logo full màu
|
||||||
|
|
||||||
|
const finalTag = document.createElement("div");
|
||||||
|
finalTag.style.fontSize = "1.6rem";
|
||||||
|
finalTag.style.fontWeight = "600";
|
||||||
|
finalTag.style.color = "var(--primary)";
|
||||||
|
finalTag.style.letterSpacing = "0.15em";
|
||||||
|
finalTag.style.textTransform = "uppercase";
|
||||||
|
|
||||||
|
brandBox.appendChild(brandImg);
|
||||||
|
brandBox.appendChild(finalTag);
|
||||||
|
typewriterEl.appendChild(brandBox);
|
||||||
|
|
||||||
|
// 1. Kick off Logo Fade-in
|
||||||
|
setTimeout(() => {
|
||||||
|
brandBox.style.opacity = "1";
|
||||||
|
}, 200);
|
||||||
|
await new Promise((r) => setTimeout(r, 1000));
|
||||||
|
|
||||||
|
// 2. Type the final tagline
|
||||||
|
await typeInto(finalTag, "Your Network Hardware Partner", 50);
|
||||||
|
|
||||||
|
// 3. Wait a moment so user can read it, then fade out the tagline/logo
|
||||||
|
await new Promise((r) => setTimeout(r, 1500));
|
||||||
|
brandBox.style.opacity = "0";
|
||||||
|
|
||||||
|
// 4. Wait for fade out, hide it, and show the eBay Banner
|
||||||
|
await new Promise((r) => setTimeout(r, 600));
|
||||||
|
brandBox.style.display = "none";
|
||||||
|
|
||||||
|
const ebayBanner = document.getElementById("hero-ebay-integrated");
|
||||||
|
if (ebayBanner) {
|
||||||
|
// Typewriter xong hẳn — giờ mới hide wrapper và show banner
|
||||||
|
const wrapper = document.querySelector(".hero-animation-wrapper");
|
||||||
|
if (wrapper) wrapper.style.display = "none";
|
||||||
|
ebayBanner.classList.add("visible");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
runTypewriter();
|
||||||
|
|
||||||
|
// Đã hủy bỏ bộ hẹn giờ auto-focus ở đây.
|
||||||
|
// Việc auto-focus sau 19s khiến trình duyệt tự động cuộn giật ngược lên đầu trang nếu user đang đọc ở dưới.
|
||||||
|
|
||||||
|
// Switch between Source (Buy) and Sell Modes
|
||||||
|
window.switchTab = function (mode) {
|
||||||
|
const buySection = document.getElementById("buy-section");
|
||||||
|
const sellSection = document.getElementById("sell-section");
|
||||||
|
const tabs = document.querySelectorAll(".search-tab");
|
||||||
|
|
||||||
|
// Toggle tabs
|
||||||
|
tabs.forEach((tab) => {
|
||||||
|
tab.classList.toggle(
|
||||||
|
"active",
|
||||||
|
tab.textContent.toLowerCase().includes(mode),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Toggle sections with a small fade
|
||||||
|
if (mode === "buy") {
|
||||||
|
sellSection.style.display = "none";
|
||||||
|
buySection.style.display = "block";
|
||||||
|
document.getElementById("hero-input").focus();
|
||||||
|
} else {
|
||||||
|
buySection.style.display = "none";
|
||||||
|
sellSection.style.display = "block";
|
||||||
|
document.getElementById("sell-email-init").focus();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let isScrolling = false;
|
||||||
|
let currentSectionIndex = 0;
|
||||||
|
|
||||||
|
function updateUI(index) {
|
||||||
|
currentSectionIndex = index;
|
||||||
|
const currentSection = sections[index];
|
||||||
|
const id = currentSection.getAttribute("id");
|
||||||
|
|
||||||
|
// Update Top Nav based on href matches, not raw index parity
|
||||||
|
navLinks.forEach((link) => {
|
||||||
|
link.classList.remove("active", "past");
|
||||||
|
const targetId = link.getAttribute("href");
|
||||||
|
|
||||||
|
if (targetId === `#${id}`) {
|
||||||
|
link.classList.add("active");
|
||||||
|
} else if (id === "warehouse" && targetId === "#quality") {
|
||||||
|
link.classList.add("active");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Toggle light color for scroll indicator on dark sections (1, 3, 5)
|
||||||
|
if (scrollIndicator) {
|
||||||
|
if (index % 2 !== 0) {
|
||||||
|
scrollIndicator.classList.add("light");
|
||||||
|
} else {
|
||||||
|
scrollIndicator.classList.remove("light");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hide Scroll Indicator on last page
|
||||||
|
if (scrollIndicator) {
|
||||||
|
if (index === sections.length - 1) {
|
||||||
|
scrollIndicator.style.opacity = "0";
|
||||||
|
} else {
|
||||||
|
scrollIndicator.style.opacity = "1";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Logic cuộn tự động đã được chuyển qua CSS scroll-snap để tối ưu hiệu năng và tương thích màn hình
|
||||||
|
|
||||||
|
function scrollToSection(index) {
|
||||||
|
isScrolling = true;
|
||||||
|
updateUI(index);
|
||||||
|
sections[index].scrollIntoView({ behavior: "smooth" });
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
isScrolling = false;
|
||||||
|
}, 800); // Wait for animation to finish
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============ SECTION OBSERVER ============
|
||||||
|
const observerOptions = {
|
||||||
|
threshold: 0.5,
|
||||||
|
};
|
||||||
|
|
||||||
|
const observer = new IntersectionObserver((entries) => {
|
||||||
|
entries.forEach((entry) => {
|
||||||
|
if (entry.isIntersecting) {
|
||||||
|
const id = entry.target.getAttribute("id");
|
||||||
|
const index = Array.from(sections).indexOf(entry.target);
|
||||||
|
|
||||||
|
// Update internal index for wheel/space navigation
|
||||||
|
if (!isScrolling) currentSectionIndex = index;
|
||||||
|
|
||||||
|
// Update Top Nav
|
||||||
|
navLinks.forEach((link) => {
|
||||||
|
link.classList.remove("active");
|
||||||
|
const targetId = link.getAttribute("href");
|
||||||
|
|
||||||
|
if (targetId === `#${id}`) {
|
||||||
|
link.classList.add("active");
|
||||||
|
}
|
||||||
|
// Special case: Keep 'Why Us' highlighted when inside Global Logistics
|
||||||
|
if (id === "warehouse" && targetId === "#quality") {
|
||||||
|
link.classList.add("active");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
entry.target.classList.add("visible");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, observerOptions);
|
||||||
|
|
||||||
|
sections.forEach((section) => observer.observe(section));
|
||||||
|
|
||||||
|
// ============ SMOOTH SCROLL (Navbar) ============
|
||||||
|
document.querySelectorAll('a[href^="#"]').forEach((anchor) => {
|
||||||
|
anchor.addEventListener("click", function (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
const targetId = this.getAttribute("href");
|
||||||
|
const target = document.querySelector(targetId);
|
||||||
|
if (target) {
|
||||||
|
const index = Array.from(sections).indexOf(target);
|
||||||
|
if (index !== -1) scrollToSection(index);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// ============ ANIMATE PORT SEQUENCE ============
|
||||||
|
const switchPanel = document.getElementById("animated-switch-panel");
|
||||||
|
if (switchPanel) {
|
||||||
|
const totalColumns = 24;
|
||||||
|
const totalRows = 2;
|
||||||
|
|
||||||
|
for (let r = 0; r < totalRows; r++) {
|
||||||
|
const rowDiv = document.createElement("div");
|
||||||
|
rowDiv.className = "port-row";
|
||||||
|
|
||||||
|
for (let c = 0; c < totalColumns; c++) {
|
||||||
|
const portDiv = document.createElement("div");
|
||||||
|
|
||||||
|
// Randomly decide if this port gets a cable ( ~40% chance )
|
||||||
|
// EXCLUDE Port 22 of Bottom Row (r=1, c=21) for the master cable
|
||||||
|
if (Math.random() < 0.4 && !(r === 1 && c === 21)) {
|
||||||
|
portDiv.className = "port-front active-port";
|
||||||
|
// Random drop-in delay between 0 and 2.5 seconds
|
||||||
|
const plugDelay = Math.random() * 2.5;
|
||||||
|
portDiv.style.setProperty("--delay", `${plugDelay}s`);
|
||||||
|
|
||||||
|
portDiv.innerHTML = `
|
||||||
|
<div class="port-led"></div>
|
||||||
|
<div class="cable-front">
|
||||||
|
<div class="cable-head-front"></div>
|
||||||
|
<div class="cable-wire-front"></div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
} else {
|
||||||
|
// Inactive port just has the base shell and an unlit LED
|
||||||
|
portDiv.className = "port-front";
|
||||||
|
portDiv.innerHTML = `<div class="port-led"></div>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
rowDiv.appendChild(portDiv);
|
||||||
|
}
|
||||||
|
switchPanel.appendChild(rowDiv);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============ QUALITY SLIDER LOGIC ============
|
||||||
|
let currentQSlide = 0;
|
||||||
|
const qSlides = document.querySelectorAll(".q-slide");
|
||||||
|
const qDots = document.querySelectorAll(".dot");
|
||||||
|
let qInterval;
|
||||||
|
|
||||||
|
window.setQSlide = function (index) {
|
||||||
|
if (index === currentQSlide) return;
|
||||||
|
|
||||||
|
qSlides[currentQSlide].classList.remove("active");
|
||||||
|
qDots[currentQSlide].classList.remove("active");
|
||||||
|
|
||||||
|
currentQSlide = index;
|
||||||
|
|
||||||
|
qSlides[currentQSlide].classList.add("active");
|
||||||
|
qDots[currentQSlide].classList.add("active");
|
||||||
|
|
||||||
|
// Reset interval on manual click
|
||||||
|
clearInterval(qInterval);
|
||||||
|
startQAutoPlay();
|
||||||
|
};
|
||||||
|
|
||||||
|
function startQAutoPlay() {
|
||||||
|
qInterval = setInterval(() => {
|
||||||
|
let next = (currentQSlide + 1) % qSlides.length;
|
||||||
|
setQSlide(next);
|
||||||
|
}, 5000);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (qSlides.length > 0) {
|
||||||
|
startQAutoPlay();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,507 @@
|
||||||
|
(() => {
|
||||||
|
// ── API Config ──
|
||||||
|
const API_ENDPOINT = window.PROLOGY_CONFIG.API_URL;
|
||||||
|
|
||||||
|
// ── Shared submit function ──
|
||||||
|
async function submitToAPI(payload, btnEl, onSuccess) {
|
||||||
|
btnEl.disabled = true;
|
||||||
|
const origHTML = btnEl.innerHTML;
|
||||||
|
btnEl.innerHTML = "<span>Sending...</span>";
|
||||||
|
|
||||||
|
const restore = () => {
|
||||||
|
btnEl.innerHTML = origHTML;
|
||||||
|
btnEl.disabled = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await fetch(API_ENDPOINT, {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
"X-Requested-With": "XMLHttpRequest",
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
...payload,
|
||||||
|
source: window.location.href,
|
||||||
|
ts: Date.now(),
|
||||||
|
nonce: Math.random().toString(36).slice(2),
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = await res.json();
|
||||||
|
|
||||||
|
if (res.ok && data.ok) {
|
||||||
|
onSuccess(restore);
|
||||||
|
} else {
|
||||||
|
const msg =
|
||||||
|
data.error || "Something went wrong. Please try again.";
|
||||||
|
btnEl.innerHTML = "<span>⚠ " + msg + "</span>";
|
||||||
|
setTimeout(restore, 3000);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
btnEl.innerHTML =
|
||||||
|
"<span>⚠ Network error. Please try again.</span>";
|
||||||
|
setTimeout(restore, 3000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Shared Shadow DOM CSS ──
|
||||||
|
const MODAL_CSS = `
|
||||||
|
:host { display: none; }
|
||||||
|
:host(.open) { display: block; }
|
||||||
|
|
||||||
|
.overlay {
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
background: rgba(0,0,0,0.75);
|
||||||
|
backdrop-filter: blur(12px);
|
||||||
|
z-index: 9999;
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 20px;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
.box {
|
||||||
|
background: linear-gradient(145deg, #2A3B54, #1E293B);
|
||||||
|
border: 1px solid rgba(255,255,255,0.15);
|
||||||
|
border-radius: 24px;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 580px;
|
||||||
|
max-height: calc(100vh - 40px);
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: 36px;
|
||||||
|
margin: auto;
|
||||||
|
position: relative;
|
||||||
|
box-shadow: 0 40px 100px rgba(0,0,0,0.8);
|
||||||
|
font-family: 'Plus Jakarta Sans', sans-serif;
|
||||||
|
color: white;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
.badge {
|
||||||
|
display: inline-block;
|
||||||
|
font-size: 0.7rem;
|
||||||
|
font-weight: 700;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.12em;
|
||||||
|
color: #1A73E8;
|
||||||
|
background: rgba(26,115,232,0.15);
|
||||||
|
padding: 4px 12px;
|
||||||
|
border-radius: 4px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
h3 { font-size: 1.75rem; font-weight: 800; margin: 0 0 8px; color: white; }
|
||||||
|
.intro { font-size: 0.95rem; color: #94A3B8; line-height: 1.6; margin-bottom: 28px; }
|
||||||
|
.close {
|
||||||
|
position: absolute; top: 20px; right: 20px;
|
||||||
|
background: none; border: none; color: #94A3B8;
|
||||||
|
font-size: 1.4rem; cursor: pointer; line-height: 1;
|
||||||
|
padding: 4px 8px; border-radius: 6px; transition: color 0.2s;
|
||||||
|
}
|
||||||
|
.close:hover { color: white; }
|
||||||
|
.field { margin-bottom: 20px; }
|
||||||
|
.field-row { display: flex; gap: 16px; margin-bottom: 20px; }
|
||||||
|
.field-row .field { flex: 1; margin-bottom: 0; }
|
||||||
|
label {
|
||||||
|
display: block; font-size: 0.85rem; font-weight: 600;
|
||||||
|
color: #E2E8F0; margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
.req { color: #f87171; font-size: 0.8em; margin-left: 2px; font-weight: 700; }
|
||||||
|
input, textarea, select {
|
||||||
|
width: 100%; box-sizing: border-box;
|
||||||
|
background: rgba(255,255,255,0.06);
|
||||||
|
border: 1px solid rgba(255,255,255,0.12);
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 12px 16px;
|
||||||
|
color: white;
|
||||||
|
font-family: 'Plus Jakarta Sans', sans-serif;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
outline: none;
|
||||||
|
transition: border-color 0.2s;
|
||||||
|
-webkit-appearance: none;
|
||||||
|
appearance: none;
|
||||||
|
}
|
||||||
|
input:focus, textarea:focus, select:focus {
|
||||||
|
border-color: #1A73E8;
|
||||||
|
background: rgba(255,255,255,0.09);
|
||||||
|
}
|
||||||
|
input::placeholder, textarea::placeholder { color: #64748B; }
|
||||||
|
textarea { min-height: 120px; resize: vertical; }
|
||||||
|
select {
|
||||||
|
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath fill='%2394A3B8' d='M1 1l5 5 5-5'/%3E%3C/svg%3E");
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: right 14px center;
|
||||||
|
padding-right: 36px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
select option { background: #1E293B; color: white; }
|
||||||
|
.btn-submit {
|
||||||
|
width: 100%; padding: 16px;
|
||||||
|
background: #1A73E8; color: white;
|
||||||
|
border: none; border-radius: 14px;
|
||||||
|
font-size: 1rem; font-weight: 700;
|
||||||
|
cursor: pointer; display: flex;
|
||||||
|
align-items: center; justify-content: center;
|
||||||
|
gap: 10px; transition: background 0.2s, transform 0.2s;
|
||||||
|
font-family: 'Plus Jakarta Sans', sans-serif;
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
.btn-submit:hover { background: #174ea6; transform: translateY(-2px); }
|
||||||
|
.btn-submit svg { flex-shrink: 0; }
|
||||||
|
.btn-submit:disabled {
|
||||||
|
opacity: 0.7;
|
||||||
|
cursor: not-allowed;
|
||||||
|
transform: none !important;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const ARROW_SVG = `<svg viewBox="0 0 24 24" width="18" height="18"><path fill="currentColor" d="M12 4l-1.41 1.41L16.17 11H4v2h12.17l-5.58 5.59L12 20l8-8z"/></svg>`;
|
||||||
|
|
||||||
|
// ── Base class ──
|
||||||
|
class ProloModal extends HTMLElement {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this.attachShadow({ mode: "open" });
|
||||||
|
}
|
||||||
|
|
||||||
|
open(prefillEmail = "") {
|
||||||
|
this.classList.add("open");
|
||||||
|
document.body.style.overflow = "hidden";
|
||||||
|
if (prefillEmail) {
|
||||||
|
const emailInput = this.shadowRoot.querySelector(
|
||||||
|
'input[type="email"]',
|
||||||
|
);
|
||||||
|
if (emailInput) emailInput.value = prefillEmail;
|
||||||
|
}
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
const first = this.shadowRoot.querySelector("input, textarea");
|
||||||
|
if (first) first.focus();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
close() {
|
||||||
|
this.classList.remove("open");
|
||||||
|
document.body.style.overflow = "";
|
||||||
|
const form = this.shadowRoot.querySelector("form");
|
||||||
|
if (form) form.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
_bindClose() {
|
||||||
|
this.shadowRoot
|
||||||
|
.querySelector(".close")
|
||||||
|
.addEventListener("click", () => this.close());
|
||||||
|
this.shadowRoot
|
||||||
|
.querySelector(".overlay")
|
||||||
|
.addEventListener("click", (e) => {
|
||||||
|
if (e.target.classList.contains("overlay")) this.close();
|
||||||
|
});
|
||||||
|
document.addEventListener("keydown", (e) => {
|
||||||
|
if (e.key === "Escape" && this.classList.contains("open"))
|
||||||
|
this.close();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── 1. Sell / Valuation Modal ──
|
||||||
|
class PrologySellModal extends ProloModal {
|
||||||
|
connectedCallback() {
|
||||||
|
this.shadowRoot.innerHTML = `
|
||||||
|
<style>${MODAL_CSS}</style>
|
||||||
|
<div class="overlay">
|
||||||
|
<div class="box">
|
||||||
|
<button class="close">✕</button>
|
||||||
|
<span class="badge">Asset Recovery</span>
|
||||||
|
<h3>Get a Valuation</h3>
|
||||||
|
<p class="intro">Tell us what hardware you have. Our team will provide a competitive buy-back offer within 4 business hours.</p>
|
||||||
|
<form id="f">
|
||||||
|
<div class="field">
|
||||||
|
<label>Contact Email <span class="req">*</span></label>
|
||||||
|
<input type="email" name="email" placeholder="name@company.com" required>
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<label>Inventory List / Part Numbers <span class="req">*</span></label>
|
||||||
|
<textarea name="inventory" placeholder="e.g. 20x WS-C3850-24P-S, 10x Nexus 9k..." required></textarea>
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<label>Condition</label>
|
||||||
|
<select name="condition">
|
||||||
|
<option value="used">Used / Working</option>
|
||||||
|
<option value="nib">New In Box</option>
|
||||||
|
<option value="no-power">Decommissioned / Faulty</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn-submit">
|
||||||
|
<span>Request Buy-Back Quote</span>${ARROW_SVG}
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>`;
|
||||||
|
this._bindClose();
|
||||||
|
this.shadowRoot
|
||||||
|
.querySelector("form")
|
||||||
|
.addEventListener("submit", (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
const sr = this.shadowRoot;
|
||||||
|
const email = sr.querySelector('[name="email"]').value;
|
||||||
|
if (!email) return;
|
||||||
|
const inventory = sr.querySelector('[name="inventory"]').value;
|
||||||
|
const condition = sr.querySelector('[name="condition"]').value;
|
||||||
|
const btn = sr.querySelector(".btn-submit");
|
||||||
|
submitToAPI(
|
||||||
|
{
|
||||||
|
email,
|
||||||
|
message: `[Asset Recovery]\nCondition: ${condition}\n\n${inventory || "(no inventory listed)"}`,
|
||||||
|
channel: "sell",
|
||||||
|
},
|
||||||
|
btn,
|
||||||
|
(restore) => {
|
||||||
|
btn.innerHTML = "<span>✓ Request Sent!</span>";
|
||||||
|
setTimeout(() => {
|
||||||
|
this.close();
|
||||||
|
restore();
|
||||||
|
}, 1800);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── 2. Source / Quote Modal ──
|
||||||
|
class PrologySourceModal extends ProloModal {
|
||||||
|
connectedCallback() {
|
||||||
|
this.shadowRoot.innerHTML = `
|
||||||
|
<style>${MODAL_CSS}</style>
|
||||||
|
<div class="overlay">
|
||||||
|
<div class="box">
|
||||||
|
<button class="close">✕</button>
|
||||||
|
<span class="badge">Hardware Sourcing</span>
|
||||||
|
<h3>Request a Quote</h3>
|
||||||
|
<p class="intro">Looking for specific equipment? We source authorized and refurbished Cisco, HP, and more.</p>
|
||||||
|
<form id="f">
|
||||||
|
<div class="field">
|
||||||
|
<label>Contact Email <span class="req">*</span></label>
|
||||||
|
<input type="email" name="email" placeholder="name@company.com" required>
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<label>Specific Models / Requirements</label>
|
||||||
|
<textarea name="parts" placeholder="e.g. 5x C9200L-24T-4G, 2x ASA5506-X..."></textarea>
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<label>Required Timing</label>
|
||||||
|
<select name="timing">
|
||||||
|
<option value="urgent">Urgent (ASAP)</option>
|
||||||
|
<option value="1week">Within 1 Week</option>
|
||||||
|
<option value="budgeting">Budgeting / Future Project</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn-submit">
|
||||||
|
<span>Check Availability & Price</span>${ARROW_SVG}
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>`;
|
||||||
|
this._bindClose();
|
||||||
|
this.shadowRoot
|
||||||
|
.querySelector("form")
|
||||||
|
.addEventListener("submit", (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
const sr = this.shadowRoot;
|
||||||
|
const email = sr.querySelector('[name="email"]').value;
|
||||||
|
if (!email) return;
|
||||||
|
const parts = sr.querySelector('[name="parts"]').value;
|
||||||
|
const timing = sr.querySelector('[name="timing"]').value;
|
||||||
|
const btn = sr.querySelector(".btn-submit");
|
||||||
|
submitToAPI(
|
||||||
|
{
|
||||||
|
email,
|
||||||
|
message: `[Hardware Sourcing]\nTiming: ${timing}\n\n${parts || "(no parts listed)"}`,
|
||||||
|
channel: "source",
|
||||||
|
},
|
||||||
|
btn,
|
||||||
|
(restore) => {
|
||||||
|
btn.innerHTML = "<span>✓ Request Sent!</span>";
|
||||||
|
setTimeout(() => {
|
||||||
|
this.close();
|
||||||
|
restore();
|
||||||
|
}, 1800);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── 3. Quote Modal (Request a Quote from Who We Serve) ──
|
||||||
|
class PrologyQuoteModal extends ProloModal {
|
||||||
|
connectedCallback() {
|
||||||
|
this.shadowRoot.innerHTML = `
|
||||||
|
<style>${MODAL_CSS}</style>
|
||||||
|
<div class="overlay">
|
||||||
|
<div class="box">
|
||||||
|
<button class="close">✕</button>
|
||||||
|
<span class="badge">IT Procurement</span>
|
||||||
|
<h3>Request a Quote</h3>
|
||||||
|
<p class="intro">Please provide details about your project or hardware requirements. Our team will verify stock and get back to you within 24 hours.</p>
|
||||||
|
<form id="f">
|
||||||
|
<div class="field-row">
|
||||||
|
<div class="field"><label>Name <span class="req">*</span></label><input type="text" name="name" placeholder="John Doe" required></div>
|
||||||
|
<div class="field"><label>Email <span class="req">*</span></label><input type="email" name="email" placeholder="john@company.com" required></div>
|
||||||
|
</div>
|
||||||
|
<div class="field-row">
|
||||||
|
<div class="field"><label>Company (Optional)</label><input type="text" name="company" placeholder="Company Ltd."></div>
|
||||||
|
<div class="field"><label>Phone Number <span class="req">*</span></label><input type="tel" name="phone" placeholder="+1 234 567 8900" required></div>
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<label>Equipment Requirements <span class="req">*</span></label>
|
||||||
|
<textarea name="requirements" placeholder="e.g. 10x Cisco C9200L-48P-4G..." required></textarea>
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<label>Shipping Destination</label>
|
||||||
|
<input type="text" name="destination" placeholder="City, Country (Optional)">
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn-submit"><span>Submit Request</span>${ARROW_SVG}</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>`;
|
||||||
|
this._bindClose();
|
||||||
|
this.shadowRoot
|
||||||
|
.querySelector("form")
|
||||||
|
.addEventListener("submit", (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
const sr = this.shadowRoot;
|
||||||
|
const name = sr.querySelector('[name="name"]').value;
|
||||||
|
const email = sr.querySelector('[name="email"]').value;
|
||||||
|
const phone = sr.querySelector('[name="phone"]').value;
|
||||||
|
const company = sr.querySelector('[name="company"]').value;
|
||||||
|
const requirements = sr.querySelector(
|
||||||
|
'[name="requirements"]',
|
||||||
|
).value;
|
||||||
|
const destination = sr.querySelector(
|
||||||
|
'[name="destination"]',
|
||||||
|
).value;
|
||||||
|
const btn = sr.querySelector(".btn-submit");
|
||||||
|
submitToAPI(
|
||||||
|
{
|
||||||
|
name,
|
||||||
|
email,
|
||||||
|
phone,
|
||||||
|
message: `[Quote Request]\nCompany: ${company || "N/A"}\nDestination: ${destination || "N/A"}\n\n${requirements}`,
|
||||||
|
channel: "quote",
|
||||||
|
},
|
||||||
|
btn,
|
||||||
|
(restore) => {
|
||||||
|
btn.innerHTML = "<span>✓ Request Sent!</span>";
|
||||||
|
setTimeout(() => {
|
||||||
|
this.close();
|
||||||
|
restore();
|
||||||
|
}, 1800);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── 4. Enquire Modal (Gov & Corp) ──
|
||||||
|
class PrologyEnquireModal extends ProloModal {
|
||||||
|
connectedCallback() {
|
||||||
|
this.shadowRoot.innerHTML = `
|
||||||
|
<style>${MODAL_CSS}</style>
|
||||||
|
<div class="overlay">
|
||||||
|
<div class="box">
|
||||||
|
<button class="close">✕</button>
|
||||||
|
<span class="badge">Government & Corporate</span>
|
||||||
|
<h3>Corporate Enquiry</h3>
|
||||||
|
<p class="intro">Get in touch with our specialized team to establish customized procurement workflows for your organization.</p>
|
||||||
|
<form id="f">
|
||||||
|
<div class="field-row">
|
||||||
|
<div class="field"><label>Name <span class="req">*</span></label><input type="text" name="name" placeholder="Jane Doe" required></div>
|
||||||
|
<div class="field"><label>Work Email <span class="req">*</span></label><input type="email" name="email" placeholder="jane@agency.gov" required></div>
|
||||||
|
</div>
|
||||||
|
<div class="field-row">
|
||||||
|
<div class="field"><label>Organization / Agency <span class="req">*</span></label><input type="text" name="org" placeholder="Organization Name" required></div>
|
||||||
|
<div class="field"><label>Contact Number <span class="req">*</span></label><input type="tel" name="phone" placeholder="+1 234 567 8900" required></div>
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<label>Enquiry Details <span class="req">*</span></label>
|
||||||
|
<textarea name="details" placeholder="Please describe your procurement needs..." required></textarea>
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn-submit"><span>Submit Enquiry</span>${ARROW_SVG}</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>`;
|
||||||
|
this._bindClose();
|
||||||
|
this.shadowRoot
|
||||||
|
.querySelector("form")
|
||||||
|
.addEventListener("submit", (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
const sr = this.shadowRoot;
|
||||||
|
const name = sr.querySelector('[name="name"]').value;
|
||||||
|
const email = sr.querySelector('[name="email"]').value;
|
||||||
|
const phone = sr.querySelector('[name="phone"]').value;
|
||||||
|
const org = sr.querySelector('[name="org"]').value;
|
||||||
|
const details = sr.querySelector('[name="details"]').value;
|
||||||
|
const btn = sr.querySelector(".btn-submit");
|
||||||
|
submitToAPI(
|
||||||
|
{
|
||||||
|
name,
|
||||||
|
email,
|
||||||
|
phone,
|
||||||
|
message: `[Corporate Enquiry]\nOrganization: ${org}\n\n${details}`,
|
||||||
|
channel: "enquire",
|
||||||
|
},
|
||||||
|
btn,
|
||||||
|
(restore) => {
|
||||||
|
btn.innerHTML = "<span>✓ Enquiry Sent!</span>";
|
||||||
|
setTimeout(() => {
|
||||||
|
this.close();
|
||||||
|
restore();
|
||||||
|
}, 1800);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
customElements.define("prology-sell-modal", PrologySellModal);
|
||||||
|
customElements.define("prology-source-modal", PrologySourceModal);
|
||||||
|
customElements.define("prology-quote-modal", PrologyQuoteModal);
|
||||||
|
customElements.define("prology-enquire-modal", PrologyEnquireModal);
|
||||||
|
|
||||||
|
// ── Global API (backward compatible with onclick calls) ──
|
||||||
|
window.openValuationModal = (email = "") =>
|
||||||
|
document.getElementById("wc-sell-modal").open(email);
|
||||||
|
window.openSourcingModal = () =>
|
||||||
|
document.getElementById("wc-source-modal").open();
|
||||||
|
window.openQuoteModal = () =>
|
||||||
|
document.getElementById("wc-quote-modal").open();
|
||||||
|
window.openEnquireModal = () =>
|
||||||
|
document.getElementById("wc-enquire-modal").open();
|
||||||
|
|
||||||
|
// goToSellStep2: pass email from capsule input into sell modal
|
||||||
|
// ── Store picker modal ──
|
||||||
|
window.openStoreModal = function () {
|
||||||
|
const overlay = document.getElementById("store-modal-overlay");
|
||||||
|
if (overlay) overlay.classList.add("open");
|
||||||
|
};
|
||||||
|
window.closeStoreModal = function () {
|
||||||
|
const overlay = document.getElementById("store-modal-overlay");
|
||||||
|
if (overlay) overlay.classList.remove("open");
|
||||||
|
};
|
||||||
|
document
|
||||||
|
.getElementById("store-modal-overlay")
|
||||||
|
.addEventListener("click", function (e) {
|
||||||
|
if (e.target === this) closeStoreModal();
|
||||||
|
});
|
||||||
|
document.addEventListener("keydown", function (e) {
|
||||||
|
if (e.key === "Escape") closeStoreModal();
|
||||||
|
});
|
||||||
|
|
||||||
|
window.goToSellStep2 = function () {
|
||||||
|
const emailInput = document.getElementById("sell-email-init");
|
||||||
|
const email = emailInput ? emailInput.value : "";
|
||||||
|
if (!email || !email.includes("@")) {
|
||||||
|
alert("Please input a valid email address.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
window.openValuationModal(email);
|
||||||
|
};
|
||||||
|
})();
|
||||||
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue