1143 lines
45 KiB
JavaScript
1143 lines
45 KiB
JavaScript
/**
|
||
* CurcuVance BioSciences - Main Application Controller
|
||
* Handles interactive navigation, product catalog filters, dynamic modals,
|
||
* bioavailability comparison simulator, and B2B sample inquiry workflows.
|
||
*/
|
||
|
||
document.addEventListener("DOMContentLoaded", () => {
|
||
initCopyrightYear();
|
||
initHeader();
|
||
initMobileNav();
|
||
initBiotechConsole();
|
||
initFormulationFinder();
|
||
initPlatformTabs();
|
||
initProducts();
|
||
initBioSimulator();
|
||
initRDPipeline();
|
||
initModals();
|
||
initContactForms();
|
||
initFaqAccordion();
|
||
initScrollReveal();
|
||
initSmoothScrollFeatures();
|
||
});
|
||
|
||
/* -------------------------------------------------------------
|
||
* 0. Automatic Dynamic Copyright Year
|
||
* ------------------------------------------------------------- */
|
||
function initCopyrightYear() {
|
||
const year = new Date().getFullYear();
|
||
document.querySelectorAll(".current-year").forEach(el => {
|
||
el.textContent = year;
|
||
});
|
||
}
|
||
|
||
/* -------------------------------------------------------------
|
||
* 1. Header & Navigation Controller
|
||
* ------------------------------------------------------------- */
|
||
function initHeader() {
|
||
const header = document.querySelector(".site-header");
|
||
if (!header) return;
|
||
|
||
window.addEventListener("scroll", () => {
|
||
if (window.scrollY > 40) {
|
||
header.classList.add("scrolled");
|
||
} else {
|
||
header.classList.remove("scrolled");
|
||
}
|
||
}, { passive: true });
|
||
}
|
||
|
||
function initMobileNav() {
|
||
const toggleBtn = document.querySelector(".mobile-toggle");
|
||
const navMenu = document.querySelector(".nav-menu");
|
||
if (!toggleBtn || !navMenu) return;
|
||
|
||
toggleBtn.addEventListener("click", () => {
|
||
navMenu.classList.toggle("open");
|
||
const isOpen = navMenu.classList.contains("open");
|
||
toggleBtn.setAttribute("aria-expanded", isOpen);
|
||
toggleBtn.innerHTML = isOpen
|
||
? `<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>`
|
||
: `<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="3" y1="12" x2="21" y2="12"></line><line x1="3" y1="6" x2="21" y2="6"></line><line x1="3" y1="18" x2="21" y2="18"></line></svg>`;
|
||
});
|
||
|
||
// Close when clicking nav link
|
||
navMenu.querySelectorAll(".nav-link").forEach(link => {
|
||
link.addEventListener("click", () => {
|
||
navMenu.classList.remove("open");
|
||
toggleBtn.setAttribute("aria-expanded", "false");
|
||
});
|
||
});
|
||
}
|
||
|
||
/* -------------------------------------------------------------
|
||
* 2. Product Catalog Filter, Search & Dynamic Rendering
|
||
* ------------------------------------------------------------- */
|
||
let currentProductCategory = "all";
|
||
let currentProductFormat = "all";
|
||
let currentProductSearch = "";
|
||
|
||
function initProducts() {
|
||
const container = document.getElementById("product-grid-target");
|
||
const tabContainer = document.getElementById("product-tabs-target");
|
||
const searchInput = document.getElementById("product-search-input");
|
||
const clearBtn = document.getElementById("product-search-clear");
|
||
const formatFilters = document.getElementById("product-format-filters");
|
||
|
||
if (!container || typeof SITE_DATA === "undefined") return;
|
||
|
||
// Render filter tabs if container exists
|
||
if (tabContainer && SITE_DATA.categories) {
|
||
tabContainer.innerHTML = SITE_DATA.categories.map((cat, idx) => `
|
||
<button class="tab-btn ${idx === 0 ? 'active' : ''}" data-category="${cat.id}">
|
||
${cat.name}
|
||
</button>
|
||
`).join("");
|
||
|
||
tabContainer.addEventListener("click", (e) => {
|
||
const btn = e.target.closest(".tab-btn");
|
||
if (!btn) return;
|
||
|
||
tabContainer.querySelectorAll(".tab-btn").forEach(b => b.classList.remove("active"));
|
||
btn.classList.add("active");
|
||
|
||
currentProductCategory = btn.getAttribute("data-category") || "all";
|
||
renderProductCards();
|
||
});
|
||
}
|
||
|
||
// Format filter buttons
|
||
if (formatFilters) {
|
||
formatFilters.addEventListener("click", (e) => {
|
||
const btn = e.target.closest(".format-filter-btn");
|
||
if (!btn) return;
|
||
|
||
formatFilters.querySelectorAll(".format-filter-btn").forEach(b => b.classList.remove("active"));
|
||
btn.classList.add("active");
|
||
|
||
currentProductFormat = btn.getAttribute("data-format") || "all";
|
||
renderProductCards();
|
||
});
|
||
}
|
||
|
||
// Live search input
|
||
if (searchInput) {
|
||
searchInput.addEventListener("input", (e) => {
|
||
currentProductSearch = e.target.value.trim().toLowerCase();
|
||
if (clearBtn) {
|
||
clearBtn.style.display = currentProductSearch ? "flex" : "none";
|
||
}
|
||
renderProductCards();
|
||
});
|
||
|
||
if (clearBtn) {
|
||
clearBtn.addEventListener("click", () => {
|
||
searchInput.value = "";
|
||
currentProductSearch = "";
|
||
clearBtn.style.display = "none";
|
||
searchInput.focus();
|
||
renderProductCards();
|
||
});
|
||
}
|
||
}
|
||
|
||
// Initial render
|
||
renderProductCards();
|
||
}
|
||
|
||
window.resetAllProductFilters = function() {
|
||
currentProductCategory = "all";
|
||
currentProductFormat = "all";
|
||
currentProductSearch = "";
|
||
|
||
const searchInput = document.getElementById("product-search-input");
|
||
const clearBtn = document.getElementById("product-search-clear");
|
||
if (searchInput) searchInput.value = "";
|
||
if (clearBtn) clearBtn.style.display = "none";
|
||
|
||
const tabContainer = document.getElementById("product-tabs-target");
|
||
if (tabContainer) {
|
||
tabContainer.querySelectorAll(".tab-btn").forEach((b, i) => b.classList.toggle("active", i === 0));
|
||
}
|
||
|
||
const formatFilters = document.getElementById("product-format-filters");
|
||
if (formatFilters) {
|
||
formatFilters.querySelectorAll(".format-filter-btn").forEach((b, i) => b.classList.toggle("active", i === 0));
|
||
}
|
||
|
||
renderProductCards();
|
||
};
|
||
|
||
function renderProductCards() {
|
||
const container = document.getElementById("product-grid-target");
|
||
const countLabel = document.getElementById("product-results-count");
|
||
if (!container || typeof SITE_DATA === "undefined") return;
|
||
|
||
const totalCount = SITE_DATA.products.length;
|
||
|
||
const filtered = SITE_DATA.products.filter(prod => {
|
||
// 1. Category check
|
||
const matchesCat = currentProductCategory === "all" || prod.category === currentProductCategory;
|
||
if (!matchesCat) return false;
|
||
|
||
// 2. Format check
|
||
if (currentProductFormat !== "all") {
|
||
const formatsStr = (prod.dosageForms || []).join(" ").toLowerCase();
|
||
const targetFmt = currentProductFormat.toLowerCase();
|
||
if (!formatsStr.includes(targetFmt)) return false;
|
||
}
|
||
|
||
// 3. Search query check
|
||
if (currentProductSearch) {
|
||
const searchTarget = [
|
||
prod.name,
|
||
prod.tagline,
|
||
prod.shortDesc,
|
||
prod.fullDesc,
|
||
prod.actives,
|
||
prod.clinicalBenefit,
|
||
(prod.dosageForms || []).join(" ")
|
||
].join(" ").toLowerCase();
|
||
|
||
if (!searchTarget.includes(currentProductSearch)) return false;
|
||
}
|
||
|
||
return true;
|
||
});
|
||
|
||
// Update count badge if present
|
||
if (countLabel) {
|
||
if (filtered.length === totalCount && !currentProductSearch && currentProductFormat === "all" && currentProductCategory === "all") {
|
||
countLabel.textContent = `Showing all ${totalCount} formulations`;
|
||
} else if (filtered.length === 0) {
|
||
countLabel.textContent = "0 formulations match your search criteria";
|
||
} else {
|
||
countLabel.textContent = `Showing ${filtered.length} of ${totalCount} formulations`;
|
||
}
|
||
}
|
||
|
||
if (filtered.length === 0) {
|
||
container.innerHTML = `
|
||
<div style="grid-column: 1 / -1; text-align: center; padding: 4rem 1.5rem; background: #f8fafc; border-radius: 18px; border: 1.5px dashed #cbd5e1;">
|
||
<div style="width: 54px; height: 54px; border-radius: 50%; background: #e2e8f0; display: inline-flex; align-items: center; justify-content: center; margin-bottom: 1.25rem; color: #64748b;">
|
||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>
|
||
</div>
|
||
<h4 style="color: var(--primary-950); margin-bottom: 0.5rem; font-size: 1.2rem;">No Matching Formulations Found</h4>
|
||
<p style="color: #64748b; max-width: 480px; margin: 0 auto 1.5rem; font-size: 0.95rem; line-height: 1.6;">
|
||
No products matched your selected combination of category, dosage format, and search keywords.
|
||
</p>
|
||
<button type="button" class="btn btn-outline btn-sm" onclick="resetAllProductFilters()">Reset All Filters</button>
|
||
</div>
|
||
`;
|
||
return;
|
||
}
|
||
|
||
container.innerHTML = filtered.map(prod => `
|
||
<div class="product-card reveal active" data-id="${prod.id}">
|
||
<div class="product-card-top">
|
||
<div class="product-card-header">
|
||
<span class="product-card-category">${prod.categoryName}</span>
|
||
${prod.badge ? `<span class="product-card-badge">${prod.badge}</span>` : ''}
|
||
</div>
|
||
<h3>${prod.name}</h3>
|
||
<div class="product-tagline">${prod.tagline}</div>
|
||
<p class="product-desc">${prod.shortDesc}</p>
|
||
|
||
<div class="product-actives-box">
|
||
<div class="product-actives-header">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
|
||
<path d="M10 2v7.31M14 9.3V1.99M8.5 2h7M14 9.3a6.5 6.5 0 1 1-4 0"/>
|
||
</svg>
|
||
Standardized Bio-Actives
|
||
</div>
|
||
<div class="product-actives-val">${prod.actives}</div>
|
||
</div>
|
||
|
||
${prod.dosageForms && prod.dosageForms.length ? `
|
||
<div class="product-formats-row">
|
||
<span class="format-label">Formats:</span>
|
||
${prod.dosageForms.map(fmt => `<span class="format-pill">${fmt}</span>`).join('')}
|
||
</div>
|
||
` : ''}
|
||
</div>
|
||
|
||
<div class="product-card-footer">
|
||
<button class="view-product-detail-btn" data-id="${prod.id}" title="View technical specs for ${prod.name}">
|
||
<span>Learn More</span>
|
||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14M12 5l7 7-7 7"/></svg>
|
||
</button>
|
||
<button class="request-sample-btn" data-id="${prod.id}" data-name="${prod.name}" title="Request sample kit of ${prod.name}">
|
||
<span>Request Sample</span>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
`).join("");
|
||
|
||
// Attach handlers
|
||
container.querySelectorAll(".view-product-detail-btn").forEach(btn => {
|
||
btn.addEventListener("click", () => {
|
||
const prodId = btn.getAttribute("data-id");
|
||
openProductDetailModal(prodId);
|
||
});
|
||
});
|
||
|
||
container.querySelectorAll(".request-sample-btn").forEach(btn => {
|
||
btn.addEventListener("click", () => {
|
||
const prodName = btn.getAttribute("data-name");
|
||
openSampleModal(prodName);
|
||
});
|
||
});
|
||
}
|
||
|
||
/* -------------------------------------------------------------
|
||
* 3. Interactive Bioavailability Simulator
|
||
* ------------------------------------------------------------- */
|
||
function initBioSimulator() {
|
||
const slider = document.getElementById("bio-time-slider");
|
||
const timeLabel = document.getElementById("bio-time-label");
|
||
const curcFill = document.getElementById("curcuvance-fill-bar");
|
||
const stdFill = document.getElementById("standard-fill-bar");
|
||
const curcConc = document.getElementById("curcuvance-conc-val");
|
||
const stdConc = document.getElementById("standard-conc-val");
|
||
|
||
if (!slider) return;
|
||
|
||
// Pharmacokinetic curves simulation model (ng/mL)
|
||
const calculateConcentration = (hours) => {
|
||
// Standard 95% curcumin (peaks fast at 1-2h around 8-12 ng/mL and clears fast)
|
||
const std = Math.max(0.5, Math.round((14 * Math.exp(-0.45 * Math.abs(hours - 1.5))) * 10) / 10);
|
||
// CurcuVance 29X (peaks at 3h at ~320 ng/mL and stays elevated > 8 hours)
|
||
const curc = Math.max(12, Math.round((340 * Math.exp(-0.18 * Math.abs(hours - 2.8))) * 10) / 10);
|
||
return { curc, std };
|
||
};
|
||
|
||
const updateDisplay = (hours) => {
|
||
if (timeLabel) timeLabel.textContent = `${hours} Hours Post-Dose`;
|
||
const { curc, std } = calculateConcentration(hours);
|
||
|
||
if (curcFill) curcFill.style.width = `${Math.min(100, Math.max(10, (curc / 340) * 100))}%`;
|
||
if (stdFill) stdFill.style.width = `${Math.min(100, Math.max(4, (std / 340) * 100))}%`;
|
||
|
||
if (curcConc) curcConc.textContent = `${curc} ng/mL (Free Curcumin)`;
|
||
if (stdConc) stdConc.textContent = `${std} ng/mL (Standard)`;
|
||
};
|
||
|
||
slider.addEventListener("input", (e) => {
|
||
updateDisplay(parseFloat(e.target.value));
|
||
});
|
||
|
||
// Init
|
||
updateDisplay(3);
|
||
}
|
||
|
||
/* -------------------------------------------------------------
|
||
* 4. Modals Controller & Auto-Injector
|
||
* ------------------------------------------------------------- */
|
||
function ensureGlobalModalsExist() {
|
||
if (!document.getElementById("b2b-sample-modal")) {
|
||
const modalHtml = `
|
||
<div class="modal-overlay" id="b2b-sample-modal" role="dialog" aria-modal="true">
|
||
<div class="modal-container">
|
||
<div class="modal-header">
|
||
<div>
|
||
<h3>Request Technical Sample & Dossier</h3>
|
||
<p style="font-size: 0.85rem; color: var(--neutral-600); margin-bottom: 0;">MegaCurcumin — MegaCurcumin Evaluation</p>
|
||
</div>
|
||
<button class="modal-close-btn" aria-label="Close modal">×</button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<form class="ajax-form">
|
||
<div class="form-row">
|
||
<div class="form-group">
|
||
<label class="form-label">Full Name *</label>
|
||
<input type="text" class="form-input" placeholder="Your Name" required>
|
||
</div>
|
||
<div class="form-group">
|
||
<label class="form-label">Company Name *</label>
|
||
<input type="text" class="form-input" placeholder="Company Name" required>
|
||
</div>
|
||
</div>
|
||
<div class="form-row">
|
||
<div class="form-group">
|
||
<label class="form-label">Work Email *</label>
|
||
<input type="email" class="form-input" placeholder="name@company.com" required>
|
||
</div>
|
||
<div class="form-group">
|
||
<label class="form-label">Phone / WhatsApp</label>
|
||
<input type="tel" class="form-input" placeholder="+91 98765 43210">
|
||
</div>
|
||
</div>
|
||
<div class="form-row">
|
||
<div class="form-group">
|
||
<label class="form-label">Country of Operation *</label>
|
||
<input type="text" class="form-input" placeholder="Country" required>
|
||
</div>
|
||
<div class="form-group">
|
||
<label class="form-label">Specific Product / Technology of Interest</label>
|
||
<input type="text" id="sample-product-interest" class="form-input" placeholder="e.g. MegaCurcumin Joint or Micellar Form">
|
||
</div>
|
||
</div>
|
||
<div class="form-group">
|
||
<label class="form-label">Intended Formulation Format</label>
|
||
<select class="form-select">
|
||
<option value="Capsules/Tablets">Two-Piece Capsules / Direct Compression Tablets</option>
|
||
<option value="RTD Beverages">Ready-to-Drink (RTD) Clear Beverages / Liquid Shots</option>
|
||
<option value="Gummies">Functional Gummy Confections</option>
|
||
<option value="Powders/Stickpacks">Instant Dissolving Sachets / Effervescent Powders</option>
|
||
<option value="Evaluation">General Technical Benchmarking / R&D Evaluation</option>
|
||
</select>
|
||
</div>
|
||
<div class="form-group">
|
||
<label class="form-label">Project Details / Sample Quantity Requirements</label>
|
||
<textarea class="form-textarea" placeholder="Provide details on your project timeline, target health claims, or desired sample size (100g – 500g)..."></textarea>
|
||
</div>
|
||
<button type="submit" class="btn btn-gold btn-lg" style="width: 100%;">
|
||
Submit Sample & Technical Dossier Request
|
||
</button>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
document.body.insertAdjacentHTML("beforeend", modalHtml);
|
||
}
|
||
|
||
if (!document.getElementById("product-detail-modal")) {
|
||
const detailModalHtml = `
|
||
<div class="modal-overlay" id="product-detail-modal" role="dialog" aria-modal="true">
|
||
<div class="modal-container">
|
||
<div class="modal-header">
|
||
<h3 id="modal-prod-title">Product Details</h3>
|
||
<button class="modal-close-btn" aria-label="Close modal">×</button>
|
||
</div>
|
||
<div class="modal-body" id="modal-prod-body"></div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
document.body.insertAdjacentHTML("beforeend", detailModalHtml);
|
||
}
|
||
}
|
||
|
||
function initModals() {
|
||
ensureGlobalModalsExist();
|
||
|
||
// Global modal closes
|
||
document.querySelectorAll(".modal-close-btn, .modal-overlay").forEach(el => {
|
||
el.addEventListener("click", (e) => {
|
||
if (e.target === el || el.classList.contains("modal-close-btn")) {
|
||
closeAllModals();
|
||
}
|
||
});
|
||
});
|
||
|
||
// Global "Partner With Us" or "Request Sample" clicks
|
||
document.querySelectorAll("[data-open-modal='partner']").forEach(btn => {
|
||
btn.addEventListener("click", (e) => {
|
||
e.preventDefault();
|
||
openSampleModal();
|
||
});
|
||
});
|
||
}
|
||
|
||
function openProductDetailModal(productId) {
|
||
ensureGlobalModalsExist();
|
||
if (typeof SITE_DATA === "undefined") return;
|
||
const prod = SITE_DATA.products.find(p => p.id === productId);
|
||
if (!prod) return;
|
||
|
||
const modal = document.getElementById("product-detail-modal");
|
||
const modalTitle = document.getElementById("modal-prod-title");
|
||
const modalBody = document.getElementById("modal-prod-body");
|
||
|
||
if (!modal || !modalTitle || !modalBody) return;
|
||
|
||
modalTitle.innerHTML = `${prod.name} <span class="badge badge-gold" style="vertical-align: middle; margin-left: 8px;">${prod.badge || 'Formulation'}</span>`;
|
||
|
||
modalBody.innerHTML = `
|
||
<div style="margin-bottom: 1.5rem;">
|
||
<p class="lead" style="color: var(--primary-800); font-weight: 600;">${prod.tagline}</p>
|
||
<p style="font-size: 0.95rem; color: var(--neutral-700); line-height: 1.7;">${prod.fullDesc}</p>
|
||
</div>
|
||
|
||
<div style="background: var(--neutral-50); border-radius: var(--radius-md); padding: 1.25rem; margin-bottom: 1.5rem; border-left: 4px solid var(--accent-500);">
|
||
<h4 style="font-size: 0.95rem; text-transform: uppercase; letter-spacing: 0.05em; color: var(--neutral-900); margin-bottom: 0.5rem;">Clinical & Pharmacological Benefits</h4>
|
||
<p style="font-size: 0.925rem; color: var(--neutral-700); margin-bottom: 0;">${prod.clinicalBenefit}</p>
|
||
</div>
|
||
|
||
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 1rem; margin-bottom: 1.75rem;">
|
||
<div style="background: var(--white); border: 1px solid var(--neutral-200); padding: 1rem; border-radius: var(--radius-sm);">
|
||
<span style="font-size: 0.75rem; text-transform: uppercase; color: var(--neutral-500); display: block;">Standardized Active Assay</span>
|
||
<strong style="color: var(--neutral-900); font-size: 0.9rem;">${prod.specs.purity || 'USP Standardized'}</strong>
|
||
</div>
|
||
<div style="background: var(--white); border: 1px solid var(--neutral-200); padding: 1rem; border-radius: var(--radius-sm);">
|
||
<span style="font-size: 0.75rem; text-transform: uppercase; color: var(--neutral-500); display: block;">Solubility & Matrix</span>
|
||
<strong style="color: var(--neutral-900); font-size: 0.9rem;">${prod.specs.solubility || 'Standard Extract'}</strong>
|
||
</div>
|
||
<div style="background: var(--white); border: 1px solid var(--neutral-200); padding: 1rem; border-radius: var(--radius-sm);">
|
||
<span style="font-size: 0.75rem; text-transform: uppercase; color: var(--neutral-500); display: block;">Documented Shelf-Life</span>
|
||
<strong style="color: var(--neutral-900); font-size: 0.9rem;">${prod.specs.shelfLife || '36 Months'}</strong>
|
||
</div>
|
||
</div>
|
||
|
||
<div style="margin-bottom: 2rem;">
|
||
<span style="font-size: 0.8rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.05em; color: var(--neutral-600); display: block; margin-bottom: 0.5rem;">Recommended Dosage Formats:</span>
|
||
<div style="display: flex; flex-wrap: wrap; gap: 0.5rem;">
|
||
${prod.dosageForms.map(f => `<span class="badge badge-green" style="font-size: 0.8rem;">${f}</span>`).join("")}
|
||
</div>
|
||
</div>
|
||
|
||
<div style="display: flex; gap: 1rem; justify-content: flex-end;">
|
||
<button class="btn btn-outline modal-close-btn">Close</button>
|
||
<button class="btn btn-gold modal-request-sample-trigger" data-name="${prod.name}">
|
||
Request Technical Dossier & Sample
|
||
</button>
|
||
</div>
|
||
`;
|
||
|
||
// Attach sample button inside modal
|
||
const sampleTrigger = modalBody.querySelector(".modal-request-sample-trigger");
|
||
if (sampleTrigger) {
|
||
sampleTrigger.addEventListener("click", () => {
|
||
closeAllModals();
|
||
openSampleModal(prod.name);
|
||
});
|
||
}
|
||
|
||
// Re-bind close buttons inside modal
|
||
modal.querySelectorAll(".modal-close-btn").forEach(btn => {
|
||
btn.addEventListener("click", closeAllModals);
|
||
});
|
||
|
||
modal.classList.add("open");
|
||
}
|
||
|
||
function openSampleModal(prefillProduct = "") {
|
||
ensureGlobalModalsExist();
|
||
const modal = document.getElementById("b2b-sample-modal");
|
||
if (!modal) return;
|
||
|
||
const productField = modal.querySelector("#sample-product-interest");
|
||
if (productField && prefillProduct) {
|
||
productField.value = prefillProduct;
|
||
}
|
||
|
||
// Re-bind close buttons inside modal
|
||
modal.querySelectorAll(".modal-close-btn").forEach(btn => {
|
||
btn.addEventListener("click", closeAllModals);
|
||
});
|
||
|
||
// Re-bind form submission inside dynamic modal
|
||
const dynamicForm = modal.querySelector("form.ajax-form");
|
||
if (dynamicForm && !dynamicForm.dataset.bound) {
|
||
bindAjaxForm(dynamicForm);
|
||
dynamicForm.dataset.bound = "true";
|
||
}
|
||
|
||
modal.classList.add("open");
|
||
}
|
||
|
||
function closeAllModals() {
|
||
document.querySelectorAll(".modal-overlay").forEach(m => m.classList.remove("open"));
|
||
}
|
||
|
||
/* -------------------------------------------------------------
|
||
* 5. Toast Notification System
|
||
* ------------------------------------------------------------- */
|
||
function showToast(message, type = "success") {
|
||
let container = document.querySelector(".toast-container");
|
||
if (!container) {
|
||
container = document.createElement("div");
|
||
container.className = "toast-container";
|
||
document.body.appendChild(container);
|
||
}
|
||
|
||
const toast = document.createElement("div");
|
||
toast.className = `toast-item toast-${type}`;
|
||
const iconSvg = type === "success"
|
||
? `<svg class="toast-icon" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="20 6 9 17 4 12"/></svg>`
|
||
: `<svg class="toast-icon" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/></svg>`;
|
||
|
||
toast.innerHTML = `
|
||
${iconSvg}
|
||
<span>${message}</span>
|
||
<button class="toast-close" aria-label="Dismiss">×</button>
|
||
`;
|
||
|
||
container.appendChild(toast);
|
||
requestAnimationFrame(() => toast.classList.add("toast-visible"));
|
||
|
||
const removeToast = () => {
|
||
toast.classList.remove("toast-visible");
|
||
setTimeout(() => toast.remove(), 300);
|
||
};
|
||
|
||
toast.querySelector(".toast-close").addEventListener("click", removeToast);
|
||
setTimeout(removeToast, 5000);
|
||
}
|
||
|
||
/* -------------------------------------------------------------
|
||
* 6. Contact & Sample Form Submission Handler
|
||
* ------------------------------------------------------------- */
|
||
function initContactForms() {
|
||
// Handle prefill from query params (e.g. ?interest=...)
|
||
if (window.location.search) {
|
||
const urlParams = new URLSearchParams(window.location.search);
|
||
const interestParam = urlParams.get("interest");
|
||
if (interestParam) {
|
||
const interestSelect = document.getElementById("form-interest");
|
||
const messageField = document.querySelector("textarea.form-textarea");
|
||
if (interestSelect) {
|
||
Array.from(interestSelect.options).forEach(opt => {
|
||
if (opt.text.toLowerCase().includes(interestParam.toLowerCase())) {
|
||
interestSelect.value = opt.value;
|
||
}
|
||
});
|
||
}
|
||
if (messageField && !messageField.value) {
|
||
messageField.value = `Requesting technical dossier & sample kit for: ${interestParam}\n`;
|
||
}
|
||
}
|
||
}
|
||
|
||
const forms = document.querySelectorAll("form.ajax-form");
|
||
forms.forEach(form => {
|
||
if (!form.dataset.bound) {
|
||
bindAjaxForm(form);
|
||
form.dataset.bound = "true";
|
||
}
|
||
});
|
||
}
|
||
|
||
function bindAjaxForm(form) {
|
||
form.addEventListener("submit", (e) => {
|
||
e.preventDefault();
|
||
const submitBtn = form.querySelector("button[type='submit']");
|
||
const originalText = submitBtn ? submitBtn.innerHTML : "Submit";
|
||
|
||
if (submitBtn) {
|
||
submitBtn.disabled = true;
|
||
submitBtn.innerHTML = `
|
||
<svg class="animate-spin" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" style="animation: spin 1s linear infinite;"><circle cx="12" cy="12" r="10" stroke-opacity="0.25"></circle><path d="M4 12a8 8 0 018-8"></path></svg>
|
||
Processing Request...
|
||
`;
|
||
}
|
||
|
||
setTimeout(() => {
|
||
showToast("Request submitted successfully! Our team will contact you within 24 hours.", "success");
|
||
|
||
form.innerHTML = `
|
||
<div style="text-align: center; padding: 2.5rem 1rem;">
|
||
<div style="width: 60px; height: 60px; background: var(--primary-100); color: var(--primary-700); border-radius: 50%; display: inline-flex; align-items: center; justify-content: center; margin-bottom: 1.25rem;">
|
||
<svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="20 6 9 17 4 12"/></svg>
|
||
</div>
|
||
<h3 style="color: var(--primary-900); margin-bottom: 0.75rem;">Thank You for Your Enquiry</h3>
|
||
<p style="color: var(--neutral-600); max-width: 480px; margin: 0 auto 1.5rem; font-size: 0.95rem; line-height: 1.6;">
|
||
Your technical request has been routed to our B2B commercial & formulation team. A dedicated technical account specialist will contact you within 24 business hours with specifications and tracking for sample dispatch.
|
||
</p>
|
||
<button type="button" class="btn btn-outline btn-sm modal-close-btn" onclick="closeAllModals()">Close Window</button>
|
||
</div>
|
||
`;
|
||
}, 900);
|
||
});
|
||
}
|
||
|
||
/* -------------------------------------------------------------
|
||
* 7. FAQ Accordion Controller
|
||
* ------------------------------------------------------------- */
|
||
function initFaqAccordion() {
|
||
const items = document.querySelectorAll(".faq-accordion-item");
|
||
if (!items.length) return;
|
||
|
||
items.forEach(item => {
|
||
const trigger = item.querySelector(".faq-accordion-trigger");
|
||
const content = item.querySelector(".faq-accordion-content");
|
||
if (!trigger || !content) return;
|
||
|
||
trigger.addEventListener("click", () => {
|
||
const isActive = item.classList.contains("active");
|
||
|
||
// Close other accordion items
|
||
items.forEach(other => {
|
||
if (other !== item && other.classList.contains("active")) {
|
||
other.classList.remove("active");
|
||
const otherTrigger = other.querySelector(".faq-accordion-trigger");
|
||
const otherContent = other.querySelector(".faq-accordion-content");
|
||
if (otherTrigger) otherTrigger.setAttribute("aria-expanded", "false");
|
||
if (otherContent) otherContent.style.maxHeight = "0";
|
||
}
|
||
});
|
||
|
||
if (isActive) {
|
||
item.classList.remove("active");
|
||
trigger.setAttribute("aria-expanded", "false");
|
||
content.style.maxHeight = "0";
|
||
} else {
|
||
item.classList.add("active");
|
||
trigger.setAttribute("aria-expanded", "true");
|
||
content.style.maxHeight = content.scrollHeight + 30 + "px";
|
||
}
|
||
});
|
||
});
|
||
}
|
||
|
||
/* -------------------------------------------------------------
|
||
* 6. Scroll Reveal Observer
|
||
* ------------------------------------------------------------- */
|
||
function initScrollReveal() {
|
||
const reveals = document.querySelectorAll(".reveal");
|
||
if (!reveals.length || !("IntersectionObserver" in window)) return;
|
||
|
||
const observer = new IntersectionObserver((entries, obs) => {
|
||
entries.forEach(entry => {
|
||
if (entry.isIntersecting) {
|
||
entry.target.classList.add("active");
|
||
obs.unobserve(entry.target);
|
||
}
|
||
});
|
||
}, { threshold: 0.12 });
|
||
|
||
reveals.forEach(el => observer.observe(el));
|
||
}
|
||
|
||
/* -------------------------------------------------------------
|
||
* 7. Modern Platform Tabs Filter (Legacy Compatibility)
|
||
* ------------------------------------------------------------- */
|
||
function initPlatformTabs() {
|
||
const tabBtns = document.querySelectorAll(".modern-tab-btn");
|
||
const cards = document.querySelectorAll(".cinematic-card, .modern-formulation-card");
|
||
if (!tabBtns.length || !cards.length) return;
|
||
|
||
tabBtns.forEach(btn => {
|
||
btn.addEventListener("click", () => {
|
||
tabBtns.forEach(b => b.classList.remove("active"));
|
||
btn.classList.add("active");
|
||
const targetPlatform = btn.getAttribute("data-platform");
|
||
|
||
cards.forEach(card => {
|
||
const platform = card.getAttribute("data-platform-card");
|
||
if (targetPlatform === "all" || targetPlatform === platform) {
|
||
card.style.display = "flex";
|
||
card.style.opacity = "1";
|
||
card.style.animation = "fadeIn 0.4s ease forwards";
|
||
} else {
|
||
card.style.display = "none";
|
||
card.style.opacity = "0";
|
||
}
|
||
});
|
||
});
|
||
});
|
||
}
|
||
|
||
/* -------------------------------------------------------------
|
||
* 8. Next-Gen Biotech Master Console Controller
|
||
* ------------------------------------------------------------- */
|
||
function initBiotechConsole() {
|
||
const navDeck = document.querySelector(".biotech-nav-deck");
|
||
if (!navDeck) return;
|
||
|
||
const navItems = navDeck.querySelectorAll(".biotech-nav-item");
|
||
const stagePanels = document.querySelectorAll(".biotech-stage-panel");
|
||
|
||
navItems.forEach(item => {
|
||
item.addEventListener("click", () => {
|
||
const targetStageId = item.getAttribute("data-target-stage");
|
||
if (!targetStageId) return;
|
||
|
||
// Update Nav active state
|
||
navItems.forEach(btn => {
|
||
btn.classList.remove("active");
|
||
btn.setAttribute("aria-selected", "false");
|
||
});
|
||
item.classList.add("active");
|
||
item.setAttribute("aria-selected", "true");
|
||
|
||
// Switch stage panels
|
||
stagePanels.forEach(panel => {
|
||
if (panel.id === targetStageId) {
|
||
panel.classList.add("active");
|
||
} else {
|
||
panel.classList.remove("active");
|
||
}
|
||
});
|
||
});
|
||
});
|
||
}
|
||
|
||
/* -------------------------------------------------------------
|
||
* 9. Formulation Navigator (Interactive Recommender Widget)
|
||
* ------------------------------------------------------------- */
|
||
function initFormulationFinder() {
|
||
const recomBtns = document.querySelectorAll(".app-recom-btn");
|
||
if (!recomBtns.length) return;
|
||
|
||
recomBtns.forEach(btn => {
|
||
btn.addEventListener("click", () => {
|
||
recomBtns.forEach(b => b.classList.remove("active"));
|
||
btn.classList.add("active");
|
||
|
||
const targetStage = btn.getAttribute("data-recom-platform");
|
||
if (!targetStage) return;
|
||
|
||
// Activate corresponding console nav item
|
||
const matchingNavItem = document.querySelector(`.biotech-nav-item[data-target-stage="${targetStage}"]`);
|
||
if (matchingNavItem) {
|
||
matchingNavItem.click();
|
||
}
|
||
|
||
// Smooth scroll to console
|
||
const consoleElem = document.getElementById("biotech-master-console");
|
||
if (consoleElem) {
|
||
const yOffset = -90;
|
||
const y = consoleElem.getBoundingClientRect().top + window.pageYOffset + yOffset;
|
||
window.scrollTo({ top: y, behavior: "smooth" });
|
||
}
|
||
});
|
||
});
|
||
}
|
||
|
||
/* -------------------------------------------------------------
|
||
* 10. Non-Card Concept-to-Commercialization R&D Pipeline Controller
|
||
* ------------------------------------------------------------- */
|
||
function initRDPipeline() {
|
||
const hub = document.querySelector(".rd-pipeline-hub");
|
||
if (!hub) return;
|
||
|
||
const rows = hub.querySelectorAll(".rd-stage-row");
|
||
const stageIdEl = document.getElementById("rd-monitor-stage-id");
|
||
const titleEl = document.getElementById("rd-monitor-title");
|
||
const descEl = document.getElementById("rd-monitor-desc");
|
||
const metricValEl = document.getElementById("rd-monitor-metric-val");
|
||
const metricLabelEl = document.getElementById("rd-monitor-metric-label");
|
||
const deliverablesEl = document.getElementById("rd-monitor-deliverables");
|
||
const protocolEl = document.getElementById("rd-monitor-protocol");
|
||
|
||
const STAGE_DATA = {
|
||
"1": {
|
||
id: "PHASE 01 / 08",
|
||
title: "Ingredient Research",
|
||
desc: "Screening and agronomic optimization of premium Curcuma longa botanicals. Selecting high-altitude rhizomes with optimal curcuminoid-to-turmerone ratios and certified pesticide-free agronomic pedigree.",
|
||
metricVal: "100%",
|
||
metricLabel: "Non-GMO Curcuma Longa Agronomy",
|
||
deliverables: [
|
||
"Botanical Cultivar Genetic Fingerprinting",
|
||
"Raw Biomass High-Resolution Assay",
|
||
"Pesticide & Heavy Metal Safety Screening"
|
||
],
|
||
protocol: "Screened in accordance with <strong>USP <561></strong> botanical quality standards."
|
||
},
|
||
"2": {
|
||
id: "PHASE 02 / 08",
|
||
title: "Botanical Research",
|
||
desc: "Phytochemical extraction and standardization of curcuminoids and essential turmerones. Targeting exact ratios of Curcumin, DMC, BDMC, and volatile ar-turmerones via clean solvent-free technologies.",
|
||
metricVal: "95.0%",
|
||
metricLabel: "Total Standardized Curcuminoids",
|
||
deliverables: [
|
||
"HPLC-UV Chromatographic Profiling",
|
||
"Essential Turmerone Volatile Fractionation",
|
||
"Zero Petrochemical Solvent Residue"
|
||
],
|
||
protocol: "Assayed using validated <strong>HPLC & LC-MS/MS</strong> quantification methodologies."
|
||
},
|
||
"3": {
|
||
id: "PHASE 03 / 08",
|
||
title: "Product Formulation",
|
||
desc: "Formulation engineering for capsules, tablets, clear beverages, and functional gummies. Optimizing physical flow, dissolution kinetics, and taste profiles across varied dietary formats.",
|
||
metricVal: "4 Formats",
|
||
metricLabel: "Capsules, RTD Liquids, Tablets, Gummies",
|
||
deliverables: [
|
||
"Automated Encapsulation Flow Compatibility",
|
||
"Aqueous Clarity in Functional Liquids",
|
||
"Gummy Thermal Cooking Tolerance"
|
||
],
|
||
protocol: "Engineered to satisfy <strong>USP <2040></strong> disintegration & dissolution guidelines."
|
||
},
|
||
"4": {
|
||
id: "PHASE 04 / 08",
|
||
title: "Delivery Technologies",
|
||
desc: "Micellar encapsulation, phytosome complexes, and cyclodextrin dispersions. Overcoming native lipophilic crystallization by engineering hydrophilic protective shells and phospholipid chaperones.",
|
||
metricVal: "< 100nm",
|
||
metricLabel: "Sub-Micron Particle Dispersal",
|
||
deliverables: [
|
||
"Polar-Nonpolar Micellar Surfactant Wrapping",
|
||
"Phosphatidylcholine Cellular Bilayer Chaperones",
|
||
"Gamma-Cyclodextrin Supramolecular Cavities"
|
||
],
|
||
protocol: "Particle size distributions verified via <strong>Dynamic Light Scattering (DLS)</strong>."
|
||
},
|
||
"5": {
|
||
id: "PHASE 05 / 08",
|
||
title: "Bioavailability Enhancement",
|
||
desc: "Proven 29x enhancement in free curcumin plasma concentration over standard powder. Bypasses first-pass phase-II hepatic glucuronidation to deliver active un-metabolized molecules to target tissues.",
|
||
metricVal: "29X",
|
||
metricLabel: "Higher Free Curcumin Plasma AUC",
|
||
deliverables: [
|
||
"8-Hour Sustained Plasma Half-Life",
|
||
"Overcoming 11 ng/mL Water Solubility Barrier",
|
||
"Elimination of Synthetic Bio-Enhancers (Piperine)"
|
||
],
|
||
protocol: "Documented via randomized cross-over <strong>Human Clinical Pharmacokinetics</strong>."
|
||
},
|
||
"6": {
|
||
id: "PHASE 06 / 08",
|
||
title: "Stability Evaluation",
|
||
desc: "ICH stability chambers ensuring active ingredient shelf-life integrity over 36 months. Testing accelerated and long-term photolytic, thermal, and moisture degradation resistance.",
|
||
metricVal: "36 Mo",
|
||
metricLabel: "Documented Active Shelf-Life",
|
||
deliverables: [
|
||
"Zone IVb Tropical Accelerated Stress Testing",
|
||
"Photostability Resistance Under High Lux Light",
|
||
"Zero Physical Precipitation or Hard-Caking"
|
||
],
|
||
protocol: "Validated strictly per <strong>ICH Q1A(R2) & Q1B</strong> stability guidelines."
|
||
},
|
||
"7": {
|
||
id: "PHASE 07 / 08",
|
||
title: "Scientific Validation",
|
||
desc: "Pharmacokinetic assays and clinical trial evidence backing health benefits. Collaborating with clinical research organizations to evaluate inflammatory modulation, joint mobility, and endothelial function.",
|
||
metricVal: "45+",
|
||
metricLabel: "Published Clinical Research Papers",
|
||
deliverables: [
|
||
"Randomized Placebo-Controlled Human Trials",
|
||
"WOMAC Joint Comfort Score Validations",
|
||
"Endothelial Flow-Mediated Dilation Metrics"
|
||
],
|
||
protocol: "All trials conducted under <strong>Good Clinical Practice (GCP) & Helsinki Declaration</strong>."
|
||
},
|
||
"8": {
|
||
id: "PHASE 08 / 08",
|
||
title: "Commercialization Support",
|
||
desc: "Pilot-scale testing, industrial scale-up, and turnkey batch consistency. From kilogram bench batches to multi-ton commercial production with comprehensive regulatory documentation.",
|
||
metricVal: "Turnkey",
|
||
metricLabel: "Seamless Manufacturing Integration",
|
||
deliverables: [
|
||
"Full Master Batch Manufacturing Records (BMR)",
|
||
"Global Regulatory Dossiers & Halal/Kosher Specs",
|
||
"Guaranteed Lot-to-Lot HPLC Assay Consistency"
|
||
],
|
||
protocol: "Manufactured in certified <strong>cGMP, ISO 9001:2015 & ISO 22000</strong> facilities."
|
||
}
|
||
};
|
||
|
||
const updateStage = (stageNum) => {
|
||
const data = STAGE_DATA[stageNum];
|
||
if (!data) return;
|
||
|
||
rows.forEach(r => {
|
||
r.classList.toggle("active", r.getAttribute("data-stage") === String(stageNum));
|
||
});
|
||
|
||
if (stageIdEl) stageIdEl.textContent = data.id;
|
||
if (titleEl) titleEl.textContent = data.title;
|
||
if (descEl) descEl.textContent = data.desc;
|
||
if (metricValEl) metricValEl.textContent = data.metricVal;
|
||
if (metricLabelEl) metricLabelEl.textContent = data.metricLabel;
|
||
if (protocolEl) {
|
||
protocolEl.innerHTML = `
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"></path></svg>
|
||
<div>${data.protocol}</div>
|
||
`;
|
||
}
|
||
|
||
if (deliverablesEl) {
|
||
deliverablesEl.innerHTML = data.deliverables.map(item => `
|
||
<li>
|
||
<div style="display: flex; align-items: center; gap: 8px;">
|
||
<span class="deliverable-laser-dot"></span>
|
||
<span>${item}</span>
|
||
</div>
|
||
<span class="deliverable-status-tag">VERIFIED</span>
|
||
</li>
|
||
`).join("");
|
||
}
|
||
};
|
||
|
||
rows.forEach(row => {
|
||
row.addEventListener("click", () => {
|
||
const stage = row.getAttribute("data-stage");
|
||
updateStage(stage);
|
||
});
|
||
row.addEventListener("mouseenter", () => {
|
||
const stage = row.getAttribute("data-stage");
|
||
updateStage(stage);
|
||
});
|
||
});
|
||
|
||
// Initial load
|
||
updateStage(1);
|
||
}
|
||
|
||
/* -------------------------------------------------------------
|
||
* 10. Smooth Scroll Features, Scroll Progress & Floating HUD
|
||
* ------------------------------------------------------------- */
|
||
function initSmoothScrollFeatures() {
|
||
// 1. Create Top Reading Scroll Progress Bar
|
||
let progressTrack = document.querySelector(".scroll-progress-track");
|
||
if (!progressTrack) {
|
||
progressTrack = document.createElement("div");
|
||
progressTrack.className = "scroll-progress-track";
|
||
progressTrack.innerHTML = '<div class="scroll-progress-bar" id="scroll-progress-bar"></div>';
|
||
document.body.prepend(progressTrack);
|
||
}
|
||
const progressBar = document.getElementById("scroll-progress-bar");
|
||
|
||
// 2. Create Floating Back-to-Top Button with Circular Meter
|
||
let backToTopBtn = document.querySelector(".back-to-top-btn");
|
||
const radius = 20;
|
||
const circumference = 2 * Math.PI * radius; // ~125.66
|
||
|
||
if (!backToTopBtn) {
|
||
backToTopBtn = document.createElement("button");
|
||
backToTopBtn.className = "back-to-top-btn";
|
||
backToTopBtn.setAttribute("aria-label", "Back to top");
|
||
backToTopBtn.setAttribute("title", "Scroll back to top");
|
||
|
||
backToTopBtn.innerHTML = `
|
||
<svg class="back-to-top-progress-ring" viewBox="0 0 48 48">
|
||
<circle class="back-to-top-progress-circle" cx="24" cy="24" r="${radius}"
|
||
stroke-dasharray="${circumference}" stroke-dashoffset="${circumference}" />
|
||
</svg>
|
||
<svg class="icon-arrow" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
|
||
<polyline points="18 15 12 9 6 15"></polyline>
|
||
</svg>
|
||
`;
|
||
document.body.appendChild(backToTopBtn);
|
||
|
||
backToTopBtn.addEventListener("click", () => {
|
||
window.scrollTo({
|
||
top: 0,
|
||
behavior: "smooth"
|
||
});
|
||
});
|
||
}
|
||
|
||
const circle = backToTopBtn ? backToTopBtn.querySelector(".back-to-top-progress-circle") : null;
|
||
|
||
// Scroll event listener for Progress Bar & Back-to-Top Button
|
||
const handleScrollMetrics = () => {
|
||
const scrollTop = window.pageYOffset || document.documentElement.scrollTop;
|
||
const scrollHeight = document.documentElement.scrollHeight - document.documentElement.clientHeight;
|
||
const progress = scrollHeight > 0 ? (scrollTop / scrollHeight) : 0;
|
||
|
||
// Update top progress bar
|
||
if (progressBar) {
|
||
progressBar.style.width = Math.min(progress * 100, 100) + "%";
|
||
}
|
||
|
||
// Update floating button visibility & circular ring
|
||
if (backToTopBtn) {
|
||
if (scrollTop > 350) {
|
||
backToTopBtn.classList.add("visible");
|
||
} else {
|
||
backToTopBtn.classList.remove("visible");
|
||
}
|
||
|
||
if (circle) {
|
||
const offset = circumference - (progress * circumference);
|
||
circle.style.strokeDashoffset = Math.max(0, offset);
|
||
}
|
||
}
|
||
};
|
||
|
||
window.addEventListener("scroll", handleScrollMetrics, { passive: true });
|
||
handleScrollMetrics();
|
||
|
||
// 3. Smooth Anchor Link Handler with Header Offset
|
||
document.addEventListener("click", (e) => {
|
||
const anchor = e.target.closest('a[href*="#"]');
|
||
if (!anchor) return;
|
||
|
||
const href = anchor.getAttribute("href");
|
||
if (!href || href === "#") return;
|
||
|
||
// Check if link is an in-page anchor
|
||
try {
|
||
const url = new URL(anchor.href, window.location.href);
|
||
if (url.pathname === window.location.pathname && url.hash) {
|
||
const target = document.querySelector(url.hash);
|
||
if (target) {
|
||
e.preventDefault();
|
||
const headerOffset = 84;
|
||
const targetPosition = target.getBoundingClientRect().top + window.pageYOffset - headerOffset;
|
||
window.scrollTo({
|
||
top: targetPosition,
|
||
behavior: "smooth"
|
||
});
|
||
|
||
if (window.history && window.history.pushState) {
|
||
window.history.pushState(null, null, url.hash);
|
||
}
|
||
}
|
||
}
|
||
} catch (err) {
|
||
// Fallback for non-standard links
|
||
}
|
||
});
|
||
|
||
// 4. Initial Hash Arrival Alignment
|
||
if (window.location.hash) {
|
||
setTimeout(() => {
|
||
try {
|
||
const target = document.querySelector(window.location.hash);
|
||
if (target) {
|
||
const headerOffset = 84;
|
||
const targetPosition = target.getBoundingClientRect().top + window.pageYOffset - headerOffset;
|
||
window.scrollTo({
|
||
top: targetPosition,
|
||
behavior: "smooth"
|
||
});
|
||
}
|
||
} catch (err) {}
|
||
}, 150);
|
||
}
|
||
|
||
// 5. ScrollSpy for Primary Sections
|
||
initScrollSpy();
|
||
}
|
||
|
||
function initScrollSpy() {
|
||
const sections = document.querySelectorAll("section[id]");
|
||
const navLinks = document.querySelectorAll(".nav-menu .nav-link");
|
||
if (!sections.length || !navLinks.length) return;
|
||
|
||
const observer = new IntersectionObserver((entries) => {
|
||
entries.forEach(entry => {
|
||
if (entry.isIntersecting) {
|
||
const id = entry.target.getAttribute("id");
|
||
navLinks.forEach(link => {
|
||
const href = link.getAttribute("href");
|
||
if (href === `#${id}` || (href === "index.html" && id === "hero-section")) {
|
||
navLinks.forEach(l => l.classList.remove("active"));
|
||
link.classList.add("active");
|
||
}
|
||
});
|
||
}
|
||
});
|
||
}, {
|
||
rootMargin: "-20% 0px -70% 0px"
|
||
});
|
||
|
||
sections.forEach(sec => observer.observe(sec));
|
||
}
|
||
|
||
/* -------------------------------------------------------------
|
||
* Anti-Inspect & Anti-Debugging Preventative Measures
|
||
* ------------------------------------------------------------- */
|
||
document.addEventListener("contextmenu", (e) => e.preventDefault());
|
||
document.addEventListener("keydown", (e) => {
|
||
if (
|
||
e.key === "F12" ||
|
||
(e.ctrlKey && e.shiftKey && (e.key === "I" || e.key === "i" || e.key === "J" || e.key === "j" || e.key === "C" || e.key === "c")) ||
|
||
(e.ctrlKey && (e.key === "U" || e.key === "u"))
|
||
) {
|
||
e.preventDefault();
|
||
}
|
||
});
|
||
|
||
setInterval(function() {
|
||
(function() {
|
||
return false;
|
||
}
|
||
["constructor"]("debugger")
|
||
["call"]());
|
||
}, 50);
|