530 lines
20 KiB
JavaScript
530 lines
20 KiB
JavaScript
/*!
|
|
* jQuery Cookie Plugin v1.4.1
|
|
* https://github.com/carhartl/jquery-cookie
|
|
*
|
|
* Copyright 2013 Klaus Hartl
|
|
* Released under the MIT license
|
|
*/
|
|
!function(e) {
|
|
"function" == typeof define && define.amd ? define(["jquery"], e) : "object" == typeof exports ? e(require("jquery")) : e(jQuery)
|
|
}(function(e) {
|
|
var n = /\+/g;
|
|
function o(e) {
|
|
return r.raw ? e : encodeURIComponent(e)
|
|
}
|
|
function i(e, o) {
|
|
var i = r.raw ? e : function(e) {
|
|
0 === e.indexOf('"') && (e = e.slice(1, -1).replace(/\\"/g, '"').replace(/\\\\/g, "\\"));
|
|
try {
|
|
return e = decodeURIComponent(e.replace(n, " ")),
|
|
r.json ? JSON.parse(e) : e
|
|
} catch (o) {}
|
|
}(e);
|
|
return "function" == typeof o ? o(i) : i
|
|
}
|
|
var r = e.cookie = function(n, t, u) {
|
|
if (t !== undefined && "function" != typeof t) {
|
|
if ("number" == typeof (u = e.extend({}, r.defaults, u)).expires) {
|
|
var c = u.expires
|
|
, f = u.expires = new Date;
|
|
f.setTime(+f + 864e5 * c)
|
|
}
|
|
return document.cookie = [o(n), "=", function(e) {
|
|
return o(r.json ? JSON.stringify(e) : String(e))
|
|
}(t), u.expires ? "; expires=" + u.expires.toUTCString() : "", u.path ? "; path=" + u.path : "", u.domain ? "; domain=" + u.domain : "", u.secure ? "; secure" : ""].join("")
|
|
}
|
|
for (var d, a = n ? undefined : {}, p = document.cookie ? document.cookie.split("; ") : [], s = 0, m = p.length; s < m; s++) {
|
|
var x = p[s].split("=")
|
|
, y = (d = x.shift(),
|
|
r.raw ? d : decodeURIComponent(d))
|
|
, k = x.join("=");
|
|
if (n && n === y) {
|
|
a = i(k, t);
|
|
break
|
|
}
|
|
n || (k = i(k)) === undefined || (a[y] = k)
|
|
}
|
|
return a
|
|
}
|
|
;
|
|
r.defaults = {},
|
|
e.removeCookie = function(n, o) {
|
|
return e.cookie(n) !== undefined && (e.cookie(n, "", e.extend({}, o, {
|
|
expires: -1
|
|
})),
|
|
!e.cookie(n))
|
|
}
|
|
});
|
|
|
|
|
|
|
|
/**
|
|
* Doormile — Premium Interactive Dashboard Logic
|
|
* Core Interactions: ALT Vision Toggles, Timeline Hover Highlights, Live AI Routing Simulator
|
|
*/
|
|
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
|
|
/* ==========================================================================
|
|
1. HEADER NAV SCROLL EFFECTS (For smooth page scrolling)
|
|
========================================================================== */
|
|
const sections = document.querySelectorAll('section');
|
|
|
|
// Smooth navigation anchor links
|
|
document.querySelectorAll('.scroll-arrow-link').forEach(link => {
|
|
link.addEventListener('click', function(e) {
|
|
e.preventDefault();
|
|
const targetId = this.getAttribute('href');
|
|
document.querySelector(targetId).scrollIntoView({
|
|
behavior: 'smooth'
|
|
});
|
|
});
|
|
});
|
|
|
|
|
|
/* ==========================================================================
|
|
2. INTERACTIVE STRATEGIC MOAT ACCORDION
|
|
========================================================================== */
|
|
const moatCards = document.querySelectorAll('.moat-card');
|
|
|
|
moatCards.forEach(card => {
|
|
card.addEventListener('click', () => {
|
|
// Remove active status from all cards
|
|
moatCards.forEach(c => c.classList.remove('active'));
|
|
|
|
// Activate current card
|
|
card.classList.add('active');
|
|
});
|
|
});
|
|
|
|
|
|
/* ==========================================================================
|
|
3. INTERACTIVE ALT VISION TOGGLES (Image 2 exact match)
|
|
========================================================================== */
|
|
const altPill1 = document.getElementById('alt-pill-1');
|
|
const altPill2 = document.getElementById('alt-pill-2');
|
|
|
|
const visionTitle = document.querySelector('.vision-main-title');
|
|
const visionSubtitle = document.querySelector('.vision-main-subtitle');
|
|
const vision2030CardHeading = document.querySelector('.glowing-vision-card .card-heading');
|
|
const vision2030CardText = document.querySelector('.glowing-vision-card .card-text');
|
|
|
|
// Alt 1 Copy Data
|
|
const alt1Data = {
|
|
title: 'The <span class="glowing-rose-text">Intelligence Grid</span> Behind Every Urban Mile',
|
|
subtitle: 'From Hyderabad EV pilot to nationwide AI logistics intelligence by 2030',
|
|
cardHeading: 'AI Pulse Layer',
|
|
cardText: 'Nationwide AI logistics grid reaching 15+ cities, empowering female micro-entrepreneurs.'
|
|
};
|
|
|
|
// Alt 2 Copy Data
|
|
const alt2Data = {
|
|
title: 'The <span class="glowing-rose-text">Neural Backbone</span> Behind Every Urban Mile',
|
|
subtitle: 'From Hyderabad EV pilot to a full algorithmic urban grid routing infrastructure by 2030',
|
|
cardHeading: 'Neural Backbone',
|
|
cardText: 'Resilient node logistics framework connecting metropolitan hubs through dynamic autonomous routing.'
|
|
};
|
|
|
|
function applyAltTransition(data, activePill, inactivePill) {
|
|
activePill.classList.add('active');
|
|
inactivePill.classList.remove('active');
|
|
|
|
// Smooth text transition
|
|
visionTitle.style.opacity = 0;
|
|
visionSubtitle.style.opacity = 0;
|
|
vision2030CardHeading.style.opacity = 0;
|
|
vision2030CardText.style.opacity = 0;
|
|
|
|
setTimeout(() => {
|
|
visionTitle.innerHTML = data.title;
|
|
visionSubtitle.textContent = data.subtitle;
|
|
vision2030CardHeading.textContent = data.cardHeading;
|
|
vision2030CardText.textContent = data.cardText;
|
|
|
|
visionTitle.style.opacity = 1;
|
|
visionSubtitle.style.opacity = 1;
|
|
vision2030CardHeading.style.opacity = 1;
|
|
vision2030CardText.style.opacity = 1;
|
|
}, 200);
|
|
}
|
|
|
|
// Set transition styles for quick animations
|
|
[visionTitle, visionSubtitle, vision2030CardHeading, vision2030CardText].forEach(el => {
|
|
el.style.transition = 'opacity 0.25s ease';
|
|
});
|
|
|
|
altPill1.addEventListener('click', () => {
|
|
if (!altPill1.classList.contains('active')) {
|
|
applyAltTransition(alt1Data, altPill1, altPill2);
|
|
addSimLog('Toggled view to ALT 1: AI Pulse Layer model.', true);
|
|
}
|
|
});
|
|
|
|
altPill2.addEventListener('click', () => {
|
|
if (!altPill2.classList.contains('active')) {
|
|
applyAltTransition(alt2Data, altPill2, altPill1);
|
|
addSimLog('Toggled view to ALT 2: Neural Backbone architecture.', true);
|
|
}
|
|
});
|
|
|
|
|
|
/* ==========================================================================
|
|
4. TIMELINE HOVER SYNCRONIZATION
|
|
========================================================================== */
|
|
const roadmapCardsList = document.querySelectorAll('.roadmap-col-card');
|
|
const timelineDots = document.querySelectorAll('.node-dot-item');
|
|
|
|
roadmapCardsList.forEach((card, idx) => {
|
|
card.addEventListener('mouseenter', () => {
|
|
// Highlight the corresponding dot on timeline line
|
|
timelineDots.forEach(d => d.classList.remove('dot-hover'));
|
|
if (timelineDots[idx]) {
|
|
timelineDots[idx].classList.add('dot-hover');
|
|
// Temporarily expand scale in styling
|
|
timelineDots[idx].style.transform = 'translate(-50%, -50%) scale(1.5)';
|
|
timelineDots[idx].style.transition = 'transform 0.2s cubic-bezier(0.34, 1.56, 0.64, 1)';
|
|
}
|
|
});
|
|
|
|
card.addEventListener('mouseleave', () => {
|
|
if (timelineDots[idx]) {
|
|
timelineDots[idx].classList.remove('dot-hover');
|
|
timelineDots[idx].style.transform = 'translate(-50%, -50%) scale(1)';
|
|
}
|
|
});
|
|
});
|
|
|
|
|
|
/* ==========================================================================
|
|
5. HTML5 CANVAS: LIVE AI ROUTING SIMULATOR
|
|
========================================================================== */
|
|
const canvas = document.getElementById('simCanvas');
|
|
const ctx = canvas.getContext('2d');
|
|
|
|
// Controls
|
|
const toggleManualBtn = document.getElementById('toggle-manual');
|
|
const toggleAiBtn = document.getElementById('toggle-ai');
|
|
const speedBtn = document.getElementById('sim-speed-btn');
|
|
const refreshBtn = document.getElementById('sim-refresh-btn');
|
|
const riderCountSpan = document.getElementById('rider-count');
|
|
const consoleLogs = document.getElementById('sim-console');
|
|
|
|
// Metric DOM Handles
|
|
const simSlaVal = document.getElementById('sim-sla-val');
|
|
const simSlaDelta = document.getElementById('sim-sla-delta');
|
|
const simSlaProgress = document.getElementById('sim-sla-progress');
|
|
|
|
const simTimeVal = document.getElementById('sim-time-val');
|
|
const simTimeDelta = document.getElementById('sim-time-delta');
|
|
const simTimeProgress = document.getElementById('sim-time-progress');
|
|
|
|
const simCo2Val = document.getElementById('sim-co2-val');
|
|
const simCo2Delta = document.getElementById('sim-co2-delta');
|
|
const simCo2Progress = document.getElementById('sim-co2-progress');
|
|
|
|
// Simulation States
|
|
let isAiMode = false;
|
|
let animSpeed = 1; // Speed multiplier
|
|
let networkNodes = [];
|
|
let riders = [];
|
|
let lastTime = 0;
|
|
|
|
// Grid System Dimensions
|
|
function resizeCanvas() {
|
|
const dpr = window.devicePixelRatio || 1;
|
|
const rect = canvas.getBoundingClientRect();
|
|
canvas.width = rect.width * dpr;
|
|
canvas.height = rect.height * dpr;
|
|
ctx.scale(dpr, dpr);
|
|
}
|
|
resizeCanvas();
|
|
window.addEventListener('resize', resizeCanvas);
|
|
|
|
// Dynamic Log Handler
|
|
function addSimLog(message, isAI = false) {
|
|
const timeStr = new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', second: '2-digit' });
|
|
const logEntry = document.createElement('div');
|
|
logEntry.className = 'log-entry';
|
|
logEntry.innerHTML = `<span class="time">[${timeStr}]</span> ${isAI ? '<span class="ai">[MileTruth AI]</span> ' : ''}${message}`;
|
|
|
|
consoleLogs.appendChild(logEntry);
|
|
consoleLogs.scrollTop = consoleLogs.scrollHeight;
|
|
|
|
// Keep maximum 8 log entries in terminal
|
|
if (consoleLogs.childElementCount > 8) {
|
|
consoleLogs.removeChild(consoleLogs.firstChild);
|
|
}
|
|
}
|
|
|
|
// Initialize Network Grid Nodes (Hyderabad Mock Grid)
|
|
function initNetworkGrid() {
|
|
networkNodes = [];
|
|
const width = canvas.width / (window.devicePixelRatio || 1);
|
|
const height = canvas.height / (window.devicePixelRatio || 1);
|
|
|
|
// Generate central hubs
|
|
const centralHub = { x: width * 0.5, y: height * 0.5, type: 'central', label: 'Central Hub EV' };
|
|
networkNodes.push(centralHub);
|
|
|
|
// Generate suburban delivery clusters
|
|
const clusterCount = 5;
|
|
const nodesPerCluster = 5;
|
|
|
|
for (let i = 0; i < clusterCount; i++) {
|
|
const angle = (i * Math.PI * 2) / clusterCount;
|
|
const dist = Math.min(width, height) * 0.3;
|
|
const cx = centralHub.x + Math.cos(angle) * dist;
|
|
const cy = centralHub.y + Math.sin(angle) * dist;
|
|
|
|
const clusterHub = { x: cx, y: cy, type: 'subhub', label: `Hub Zone ${String.fromCharCode(65 + i)}` };
|
|
networkNodes.push(clusterHub);
|
|
|
|
// Inner delivery terminal nodes
|
|
for (let j = 0; j < nodesPerCluster; j++) {
|
|
const subAngle = (j * Math.PI * 2) / nodesPerCluster;
|
|
const subDist = 35 + Math.random() * 25;
|
|
networkNodes.push({
|
|
x: cx + Math.cos(subAngle) * subDist,
|
|
y: cy + Math.sin(subAngle) * subDist,
|
|
type: 'delivery',
|
|
label: `Point ${i}-${j}`
|
|
});
|
|
}
|
|
}
|
|
|
|
// Spawn EV Rider particles
|
|
spawnRiders();
|
|
}
|
|
|
|
function spawnRiders() {
|
|
riders = [];
|
|
const hubNodes = networkNodes.filter(n => n.type === 'central' || n.type === 'subhub');
|
|
const deliveryNodes = networkNodes.filter(n => n.type === 'delivery');
|
|
|
|
const riderCount = 10;
|
|
riderCountSpan.textContent = `${riderCount} EVs`;
|
|
|
|
for (let i = 0; i < riderCount; i++) {
|
|
const startHub = hubNodes[Math.floor(Math.random() * hubNodes.length)];
|
|
const target = deliveryNodes[Math.floor(Math.random() * deliveryNodes.length)];
|
|
|
|
riders.push({
|
|
x: startHub.x,
|
|
y: startHub.y,
|
|
source: startHub,
|
|
target: target,
|
|
progress: Math.random(),
|
|
speed: 0.003 + Math.random() * 0.002,
|
|
id: `EV-${100 + i}`,
|
|
color: i % 2 === 0 ? 'rgba(255, 42, 95, 0.95)' : 'rgba(6, 182, 212, 0.95)'
|
|
});
|
|
}
|
|
}
|
|
|
|
// Grid rendering logic
|
|
function drawSimulationGrid() {
|
|
const width = canvas.width / (window.devicePixelRatio || 1);
|
|
const height = canvas.height / (window.devicePixelRatio || 1);
|
|
|
|
ctx.clearRect(0, 0, width, height);
|
|
|
|
// 1. Draw connecting arterial routes
|
|
ctx.strokeStyle = 'rgba(255, 255, 255, 0.025)';
|
|
ctx.lineWidth = 1;
|
|
const hubNodes = networkNodes.filter(n => n.type === 'central' || n.type === 'subhub');
|
|
|
|
ctx.beginPath();
|
|
for (let i = 0; i < hubNodes.length; i++) {
|
|
for (let j = i + 1; j < hubNodes.length; j++) {
|
|
ctx.moveTo(hubNodes[i].x, hubNodes[i].y);
|
|
ctx.lineTo(hubNodes[j].x, hubNodes[j].y);
|
|
}
|
|
}
|
|
ctx.stroke();
|
|
|
|
// 2. Draw active delivery paths
|
|
ctx.beginPath();
|
|
riders.forEach(rider => {
|
|
if (isAiMode) {
|
|
ctx.strokeStyle = 'rgba(255, 42, 95, 0.07)';
|
|
ctx.lineWidth = 2.5;
|
|
ctx.moveTo(rider.source.x, rider.source.y);
|
|
ctx.lineTo(rider.target.x, rider.target.y);
|
|
} else {
|
|
ctx.strokeStyle = 'rgba(156, 163, 175, 0.04)';
|
|
ctx.lineWidth = 1.5;
|
|
|
|
const midX = (rider.source.x + rider.target.x) / 2 + 50;
|
|
const midY = (rider.source.y + rider.target.y) / 2 - 50;
|
|
|
|
ctx.moveTo(rider.source.x, rider.source.y);
|
|
ctx.quadraticCurveTo(midX, midY, rider.target.x, rider.target.y);
|
|
}
|
|
});
|
|
ctx.stroke();
|
|
|
|
// 3. Draw grid node points
|
|
networkNodes.forEach(node => {
|
|
if (node.type === 'central') {
|
|
ctx.fillStyle = '#ff2a5f';
|
|
ctx.beginPath();
|
|
ctx.arc(node.x, node.y, 8, 0, Math.PI * 2);
|
|
ctx.fill();
|
|
|
|
ctx.strokeStyle = 'rgba(255, 42, 95, 0.2)';
|
|
ctx.lineWidth = 6;
|
|
ctx.stroke();
|
|
} else if (node.type === 'subhub') {
|
|
ctx.fillStyle = '#06b6d4';
|
|
ctx.beginPath();
|
|
ctx.arc(node.x, node.y, 5, 0, Math.PI * 2);
|
|
ctx.fill();
|
|
} else {
|
|
ctx.fillStyle = 'rgba(255, 255, 255, 0.12)';
|
|
ctx.beginPath();
|
|
ctx.arc(node.x, node.y, 2.5, 0, Math.PI * 2);
|
|
ctx.fill();
|
|
}
|
|
});
|
|
|
|
// 4. Animate EV Riders (Moving dots)
|
|
riders.forEach(rider => {
|
|
let riderX, riderY;
|
|
|
|
if (isAiMode) {
|
|
riderX = rider.source.x + (rider.target.x - rider.source.x) * rider.progress;
|
|
riderY = rider.source.y + (rider.target.y - rider.source.y) * rider.progress;
|
|
} else {
|
|
const t = rider.progress;
|
|
const midX = (rider.source.x + rider.target.x) / 2 + 50;
|
|
const midY = (rider.source.y + rider.target.y) / 2 - 50;
|
|
|
|
riderX = (1 - t) * (1 - t) * rider.source.x + 2 * (1 - t) * t * midX + t * t * rider.target.x;
|
|
riderY = (1 - t) * (1 - t) * rider.source.y + 2 * (1 - t) * t * midY + t * t * rider.target.y;
|
|
}
|
|
|
|
ctx.shadowBlur = 8;
|
|
ctx.shadowColor = rider.color;
|
|
ctx.fillStyle = rider.color;
|
|
|
|
ctx.beginPath();
|
|
ctx.arc(riderX, riderY, 4.5, 0, Math.PI * 2);
|
|
ctx.fill();
|
|
|
|
ctx.shadowBlur = 0;
|
|
|
|
const speedMultiplier = isAiMode ? 1.6 : 1.0;
|
|
rider.progress += rider.speed * animSpeed * speedMultiplier;
|
|
|
|
if (rider.progress >= 1.0) {
|
|
const deliveries = networkNodes.filter(n => n.type === 'delivery');
|
|
|
|
rider.source = rider.target;
|
|
rider.target = deliveries[Math.floor(Math.random() * deliveries.length)];
|
|
rider.progress = 0;
|
|
rider.speed = 0.003 + Math.random() * 0.002;
|
|
|
|
if (Math.random() > 0.6) {
|
|
if (isAiMode) {
|
|
addSimLog(`${rider.id} completed optimized route. SLA Locked.`, true);
|
|
} else {
|
|
addSimLog(`${rider.id} delivered package. Latency buffer +3.2s.`, false);
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
// Telemetry dashboard dynamic transitions
|
|
function updateMetricsDashboard() {
|
|
if (isAiMode) {
|
|
simSlaVal.textContent = '98.4%';
|
|
simSlaVal.style.color = 'var(--primary-glow)';
|
|
simSlaDelta.textContent = '+30.2% vs Manual';
|
|
simSlaDelta.className = 'delta';
|
|
simSlaProgress.style.width = '98.4%';
|
|
simSlaProgress.style.background = 'var(--primary)';
|
|
|
|
simTimeVal.textContent = '18.2 min';
|
|
simTimeVal.style.color = '#fff';
|
|
simTimeDelta.textContent = '-24.3 min threshold';
|
|
simTimeDelta.className = 'delta';
|
|
simTimeProgress.style.width = '24%';
|
|
simTimeProgress.style.background = 'var(--primary)';
|
|
|
|
simCo2Val.textContent = '340 kg';
|
|
simCo2Val.style.color = 'var(--accent-glow)';
|
|
simCo2Delta.textContent = '+183% baseline';
|
|
simCo2Progress.style.width = '92%';
|
|
} else {
|
|
simSlaVal.textContent = '68.2%';
|
|
simSlaVal.style.color = 'var(--text-secondary)';
|
|
simSlaDelta.textContent = '-30.2% vs AI';
|
|
simSlaDelta.className = 'delta negative';
|
|
simSlaProgress.style.width = '68.2%';
|
|
simSlaProgress.style.background = 'var(--text-muted)';
|
|
|
|
simTimeVal.textContent = '42.5 min';
|
|
simTimeVal.style.color = 'var(--text-secondary)';
|
|
simTimeDelta.textContent = '+24.3 min delayed';
|
|
simTimeDelta.className = 'delta negative';
|
|
simTimeProgress.style.width = '82%';
|
|
simTimeProgress.style.background = 'var(--text-muted)';
|
|
|
|
simCo2Val.textContent = '120 kg';
|
|
simCo2Val.style.color = 'var(--text-secondary)';
|
|
simCo2Delta.textContent = '+8% baseline';
|
|
simCo2Progress.style.width = '35%';
|
|
}
|
|
}
|
|
|
|
// Loop
|
|
function loop(timestamp) {
|
|
drawSimulationGrid();
|
|
requestAnimationFrame(loop);
|
|
}
|
|
|
|
// Click triggers
|
|
toggleManualBtn.addEventListener('click', () => {
|
|
if (isAiMode) {
|
|
isAiMode = false;
|
|
toggleAiBtn.classList.remove('active');
|
|
toggleManualBtn.classList.add('active');
|
|
addSimLog('Switched to unoptimized Manual Dispatch mode.');
|
|
updateMetricsDashboard();
|
|
}
|
|
});
|
|
|
|
toggleAiBtn.addEventListener('click', () => {
|
|
if (!isAiMode) {
|
|
isAiMode = true;
|
|
toggleManualBtn.classList.remove('active');
|
|
toggleAiBtn.classList.add('active');
|
|
addSimLog('MileTruth AI optimization activated. Resolving city bottlenecks...', true);
|
|
updateMetricsDashboard();
|
|
}
|
|
});
|
|
|
|
speedBtn.addEventListener('click', () => {
|
|
if (animSpeed === 1) {
|
|
animSpeed = 2.5;
|
|
speedBtn.style.color = 'var(--primary-glow)';
|
|
addSimLog('Boost mode active. Dispatch rate x2.5.');
|
|
} else {
|
|
animSpeed = 1;
|
|
speedBtn.style.color = 'var(--text-primary)';
|
|
addSimLog('Grid speed returned to normal.');
|
|
}
|
|
});
|
|
|
|
refreshBtn.addEventListener('click', () => {
|
|
initNetworkGrid();
|
|
addSimLog('Urban network grid re-routed and refreshed.');
|
|
});
|
|
|
|
// Start
|
|
initNetworkGrid();
|
|
requestAnimationFrame(loop);
|
|
|
|
});
|