Initial commit: AIFO official portal web.
Add static site, shared layout, and Node dev server with standard .gitignore. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,85 @@
|
||||
/**
|
||||
* Footer newsletter validation (client-side).
|
||||
* Loaded by layout.js after footer inject.
|
||||
*/
|
||||
(function () {
|
||||
var EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||
|
||||
function initNewsletter(root) {
|
||||
var form = root.querySelector("#aifo-footer-newsletter");
|
||||
if (!form) return;
|
||||
|
||||
var field = form.querySelector("#aifo-footer-newsletter-field");
|
||||
var input = form.querySelector('[name="email"]');
|
||||
var errorEl = form.querySelector("#aifo-footer-newsletter-error");
|
||||
var errorTextEl = errorEl
|
||||
? errorEl.querySelector(".aifo-foot__form-tooltip-text")
|
||||
: null;
|
||||
var successEl = form.querySelector("#aifo-footer-newsletter-success");
|
||||
|
||||
function hideMessages() {
|
||||
if (errorEl) errorEl.hidden = true;
|
||||
if (successEl) successEl.hidden = true;
|
||||
if (field) field.classList.remove("is-invalid");
|
||||
}
|
||||
|
||||
function showError(message) {
|
||||
if (errorEl) {
|
||||
if (errorTextEl) {
|
||||
errorTextEl.textContent = message;
|
||||
} else {
|
||||
errorEl.textContent = message;
|
||||
}
|
||||
errorEl.hidden = false;
|
||||
}
|
||||
if (successEl) successEl.hidden = true;
|
||||
if (field) field.classList.add("is-invalid");
|
||||
}
|
||||
|
||||
function showSuccess(message) {
|
||||
if (successEl) {
|
||||
successEl.textContent = message;
|
||||
successEl.hidden = false;
|
||||
}
|
||||
if (errorEl) errorEl.hidden = true;
|
||||
if (field) field.classList.remove("is-invalid");
|
||||
}
|
||||
|
||||
form.addEventListener("submit", function (e) {
|
||||
e.preventDefault();
|
||||
var value = (input.value || "").trim();
|
||||
|
||||
if (!value) {
|
||||
showError("Please enter your email address.");
|
||||
input.focus();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!EMAIL_RE.test(value)) {
|
||||
showError("Please enter a valid email address.");
|
||||
input.focus();
|
||||
return;
|
||||
}
|
||||
|
||||
showSuccess("Thank you for subscribing!");
|
||||
form.reset();
|
||||
});
|
||||
|
||||
input.addEventListener("input", hideMessages);
|
||||
input.addEventListener("blur", function () {
|
||||
var value = (input.value || "").trim();
|
||||
if (!value) {
|
||||
hideMessages();
|
||||
return;
|
||||
}
|
||||
if (!EMAIL_RE.test(value)) {
|
||||
showError("Please enter a valid email address.");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
window.initAifoFooter = function (root) {
|
||||
if (!root) return;
|
||||
initNewsletter(root);
|
||||
};
|
||||
})();
|
||||
+191
@@ -0,0 +1,191 @@
|
||||
/**
|
||||
* Loads shared nav/footer and applies active menu + copyright year.
|
||||
* Requires pages to be served over HTTP (see server.js).
|
||||
*/
|
||||
(function () {
|
||||
var NAV_URL = "/components/nav.html";
|
||||
var FOOTER_URL = "/components/footer.html";
|
||||
var FOOT_CSS_ID = "aifo-foot-styles";
|
||||
var FLAG_ICONS_CSS_ID = "aifo-flag-icons-styles";
|
||||
var FLAG_ICONS_CSS_URL = "/assets/flagicon/css/flag-icons.min.css";
|
||||
var FOOT_JS_URL = "/js/footer.js";
|
||||
var footJsLoading = false;
|
||||
var footJsLoaded = false;
|
||||
|
||||
function normalizePath(pathname) {
|
||||
if (!pathname) return "/";
|
||||
var p = pathname.split("?")[0].split("#")[0];
|
||||
try {
|
||||
p = decodeURIComponent(p);
|
||||
} catch (e) {
|
||||
/* ignore */
|
||||
}
|
||||
if (p.endsWith("/index.html")) {
|
||||
p = p.slice(0, -"/index.html".length) || "/";
|
||||
}
|
||||
if (p.length > 1 && p.endsWith("/")) {
|
||||
p = p.slice(0, -1);
|
||||
}
|
||||
return p.toLowerCase();
|
||||
}
|
||||
|
||||
function hrefToPath(href) {
|
||||
if (!href || href === "#" || href.indexOf("javascript:") === 0) return null;
|
||||
try {
|
||||
var url = new URL(href, window.location.origin);
|
||||
if (url.origin !== window.location.origin) return null;
|
||||
return normalizePath(url.pathname);
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function pathsMatch(current, target) {
|
||||
if (!target) return false;
|
||||
if (current === target) return true;
|
||||
if (
|
||||
(current === "/" || current === "/index.html") &&
|
||||
(target === "/" || target === "/index.html")
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function highlightActiveNav(container) {
|
||||
var current = normalizePath(window.location.pathname);
|
||||
var links = container.querySelectorAll("a[href]");
|
||||
|
||||
links.forEach(function (anchor) {
|
||||
var target = hrefToPath(anchor.getAttribute("href"));
|
||||
if (!pathsMatch(current, target)) return;
|
||||
|
||||
anchor.classList.add("active");
|
||||
var item = anchor.closest(".nav-item");
|
||||
if (item) item.classList.add("active");
|
||||
|
||||
if (anchor.classList.contains("dropdown-item")) {
|
||||
var parentItem = anchor.closest(".nav-item.dropdown");
|
||||
if (parentItem) {
|
||||
parentItem.classList.add("active");
|
||||
var toggle = parentItem.querySelector(".nav-link.dropdown-toggle");
|
||||
if (toggle) toggle.classList.add("active");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function updateCopyrightYear(root) {
|
||||
var year = String(new Date().getFullYear());
|
||||
root.querySelectorAll("#current-year").forEach(function (el) {
|
||||
el.textContent = year;
|
||||
});
|
||||
}
|
||||
|
||||
function fetchText(url) {
|
||||
return fetch(url, { credentials: "same-origin" }).then(function (res) {
|
||||
if (!res.ok) throw new Error("Failed to load " + url + " (" + res.status + ")");
|
||||
return res.text();
|
||||
});
|
||||
}
|
||||
|
||||
function injectHtml(containerId, html) {
|
||||
var el = document.getElementById(containerId);
|
||||
if (!el) return null;
|
||||
el.innerHTML = html;
|
||||
return el;
|
||||
}
|
||||
|
||||
function ensureFooterStyles() {
|
||||
if (document.getElementById(FOOT_CSS_ID)) return;
|
||||
var link = document.createElement("link");
|
||||
link.id = FOOT_CSS_ID;
|
||||
link.rel = "stylesheet";
|
||||
link.href = "/assets/css/foot.css";
|
||||
document.head.appendChild(link);
|
||||
}
|
||||
|
||||
function ensureFlagIconsStyles() {
|
||||
if (document.getElementById(FLAG_ICONS_CSS_ID)) return;
|
||||
var link = document.createElement("link");
|
||||
link.id = FLAG_ICONS_CSS_ID;
|
||||
link.rel = "stylesheet";
|
||||
link.href = FLAG_ICONS_CSS_URL;
|
||||
document.head.appendChild(link);
|
||||
}
|
||||
|
||||
function loadFooterScript(callback) {
|
||||
if (footJsLoaded) {
|
||||
callback();
|
||||
return;
|
||||
}
|
||||
if (footJsLoading) {
|
||||
document.addEventListener("aifo:footer-js-ready", callback, { once: true });
|
||||
return;
|
||||
}
|
||||
footJsLoading = true;
|
||||
var script = document.createElement("script");
|
||||
script.src = FOOT_JS_URL;
|
||||
script.onload = function () {
|
||||
footJsLoaded = true;
|
||||
footJsLoading = false;
|
||||
document.dispatchEvent(new CustomEvent("aifo:footer-js-ready"));
|
||||
callback();
|
||||
};
|
||||
script.onerror = function () {
|
||||
footJsLoading = false;
|
||||
console.error("[layout.js] Failed to load " + FOOT_JS_URL);
|
||||
};
|
||||
document.body.appendChild(script);
|
||||
}
|
||||
|
||||
function initFooterModule(root) {
|
||||
ensureFooterStyles();
|
||||
updateCopyrightYear(root);
|
||||
loadFooterScript(function () {
|
||||
if (typeof window.initAifoFooter === "function") {
|
||||
window.initAifoFooter(root);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function initLayout() {
|
||||
var navContainer = document.getElementById("nav-container");
|
||||
var footerContainer = document.getElementById("footer-container");
|
||||
if (!navContainer && !footerContainer) return;
|
||||
|
||||
var tasks = [];
|
||||
if (navContainer) tasks.push(fetchText(NAV_URL));
|
||||
else tasks.push(Promise.resolve(null));
|
||||
if (footerContainer) tasks.push(fetchText(FOOTER_URL));
|
||||
else tasks.push(Promise.resolve(null));
|
||||
|
||||
Promise.all(tasks)
|
||||
.then(function (parts) {
|
||||
var navHtml = parts[0];
|
||||
var footerHtml = parts[1];
|
||||
if (navHtml) {
|
||||
ensureFlagIconsStyles();
|
||||
var navRoot = injectHtml("nav-container", navHtml);
|
||||
if (navRoot) highlightActiveNav(navRoot);
|
||||
}
|
||||
if (footerHtml) {
|
||||
var footerRoot = injectHtml("footer-container", footerHtml);
|
||||
if (footerRoot) initFooterModule(footerRoot);
|
||||
}
|
||||
if (navHtml && window.jQuery && window.jQuery.fn.dropdown) {
|
||||
window.jQuery("#nav-container [data-toggle=\"dropdown\"]").dropdown();
|
||||
}
|
||||
document.dispatchEvent(new CustomEvent("aifo:layout-ready"));
|
||||
})
|
||||
.catch(function (err) {
|
||||
console.error("[layout.js]", err);
|
||||
});
|
||||
}
|
||||
|
||||
if (document.readyState === "loading") {
|
||||
document.addEventListener("DOMContentLoaded", initLayout);
|
||||
} else {
|
||||
initLayout();
|
||||
}
|
||||
})();
|
||||
@@ -0,0 +1,59 @@
|
||||
/**
|
||||
* Injects Payment Methods module from pages/modules/payment-methods.html
|
||||
* into #payment-methods-container (style + section + script).
|
||||
*/
|
||||
(function () {
|
||||
var MODULE_URL = "/pages/modules/payment-methods.html";
|
||||
var MOUNT_ID = "payment-methods-container";
|
||||
var STYLE_ID = "payment-methods-module-styles";
|
||||
|
||||
function injectModule(html) {
|
||||
var doc = new DOMParser().parseFromString(html, "text/html");
|
||||
var mount = document.getElementById(MOUNT_ID);
|
||||
if (!mount) return;
|
||||
|
||||
var style = doc.querySelector("style");
|
||||
if (style && !document.getElementById(STYLE_ID)) {
|
||||
var styleEl = document.createElement("style");
|
||||
styleEl.id = STYLE_ID;
|
||||
styleEl.textContent = style.textContent;
|
||||
document.head.appendChild(styleEl);
|
||||
}
|
||||
|
||||
var section = doc.querySelector("[data-payment-methods-module]");
|
||||
if (section) {
|
||||
mount.innerHTML = "";
|
||||
mount.appendChild(document.importNode(section, true));
|
||||
}
|
||||
|
||||
var inlineScript = doc.querySelector("body script");
|
||||
if (inlineScript && inlineScript.textContent) {
|
||||
var run = new Function(inlineScript.textContent);
|
||||
run();
|
||||
}
|
||||
|
||||
if (typeof window.initPaymentMethodsModule === "function") {
|
||||
mount.querySelectorAll("[data-payment-methods-module]").forEach(function (el) {
|
||||
window.initPaymentMethodsModule(el);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function load() {
|
||||
fetch(MODULE_URL, { credentials: "same-origin" })
|
||||
.then(function (res) {
|
||||
if (!res.ok) throw new Error("Failed to load " + MODULE_URL);
|
||||
return res.text();
|
||||
})
|
||||
.then(injectModule)
|
||||
.catch(function (err) {
|
||||
console.error("[load-payment-methods]", err);
|
||||
});
|
||||
}
|
||||
|
||||
if (document.readyState === "loading") {
|
||||
document.addEventListener("DOMContentLoaded", load);
|
||||
} else {
|
||||
load();
|
||||
}
|
||||
})();
|
||||
Reference in New Issue
Block a user