import { translations, defaultLocale, storageKey, type Locale, type Translations, } from "./translations"; type ScenarioMode = "normal" | "dry" | "hot" | "low"; interface ScenarioData { moisture: string; temperature: string; rover: string; battery: string; moistureText: string; temperatureText: string; roverText: string; batteryText: string; alerts: Array<[string, string]>; moistureHistory: number[]; temperatureHistory: number[]; } const scenarios: Record = { normal: { moisture: "42%", temperature: "29°C", rover: "Patrol", battery: "76%", moistureText: "Example range", temperatureText: "Demo reading", roverText: "Route P-01", batteryText: "Demo status", moistureHistory: [40, 42, 41, 43, 42, 44, 42], temperatureHistory: [27, 28, 29, 28, 29, 30, 29], alerts: [ ["✓ Routine patrol logged", "Example route P-01 was checked in the demo."], ["☀ Solar charging concept", "Docking and charging still require testing."], ], }, dry: { moisture: "18%", temperature: "30°C", rover: "Inspect", battery: "71%", moistureText: "Example low reading", temperatureText: "Demo reading", roverText: "Review zone P-01", batteryText: "Demo status", moistureHistory: [22, 20, 19, 18, 17, 18, 18], temperatureHistory: [28, 29, 30, 29, 30, 31, 30], alerts: [ [ "⚠ Low moisture warning", "Check the soil and crop needs before deciding whether to irrigate.", ], ["⌖ Zone flagged", "Example location P-01 added to the review list."], ], }, hot: { moisture: "35%", temperature: "39°C", rover: "Observe", battery: "68%", moistureText: "Monitor trend", temperatureText: "Example high reading", roverText: "Capture observation", batteryText: "Demo status", moistureHistory: [38, 37, 36, 35, 34, 35, 35], temperatureHistory: [36, 37, 38, 39, 38, 39, 39], alerts: [ [ "⚠ High temperature warning", "Check field conditions and local crop-care guidance.", ], ["◉ Follow-up suggested", "Confirm conditions with real measurements."], ], }, low: { moisture: "42%", temperature: "29°C", rover: "Return", battery: "14%", moistureText: "Example range", temperatureText: "Demo reading", roverText: "Return-to-dock concept", batteryText: "Low battery example", moistureHistory: [43, 42, 42, 42, 41, 42, 42], temperatureHistory: [28, 29, 29, 28, 29, 29, 29], alerts: [ [ "↩ Return-to-dock mode", "A real rover needs tested navigation and a safe fallback.", ], ["⚡ Charging required", "Battery protection and charging must be tested."], ], }, }; let currentLocale: Locale = defaultLocale; let conditionsWidth = 0; let conditionsHeight = 0; let batteryWidth = 0; let batteryHeight = 0; function getStoredLocale(): Locale { if (typeof localStorage === "undefined") return defaultLocale; const stored = localStorage.getItem(storageKey); if (stored === "en" || stored === "ml") return stored; return defaultLocale; } function setLocale(locale: Locale): void { currentLocale = locale; document.documentElement.lang = locale; if (typeof localStorage !== "undefined") { localStorage.setItem(storageKey, locale); } render(); } function t(section: keyof Translations, key: string): string { const dict = translations[currentLocale]; const sectionDict = dict[section]; if (sectionDict && typeof sectionDict[key] === "string") { return sectionDict[key]; } const enDict = translations.en[section]; if (enDict && typeof enDict[key] === "string") { return enDict[key]; } return key; } function getScenarioTranslation(mode: ScenarioMode): ScenarioData { const translated = translations[currentLocale].scenarios[mode]; if (translated) { return { moisture: translated.moisture, temperature: translated.temperature, rover: translated.rover, battery: translated.battery, moistureText: translated.moistureText, temperatureText: translated.temperatureText, roverText: translated.roverText, batteryText: translated.batteryText, alerts: translated.alerts.map((item) => [item[0], item[1]]), moistureHistory: translated.moistureHistory, temperatureHistory: translated.temperatureHistory, }; } return scenarios[mode]; } function applyTranslations(): void { document.querySelectorAll("[data-i18n]").forEach((el) => { const key = el.getAttribute("data-i18n")!; const parts = key.split("."); const section = parts[0]; const field = parts[1]; if (!section || !field) return; el.textContent = t(section as keyof Translations, field); }); document.querySelectorAll("[data-i18n-html]").forEach((el) => { const key = el.getAttribute("data-i18n-html")!; const parts = key.split("."); const section = parts[0]; const field = parts[1]; if (!section || !field) return; el.innerHTML = t(section as keyof Translations, field); }); const langToggle = document.getElementById("lang-toggle"); if (langToggle) { langToggle.textContent = t("nav", "lang"); } } function parsePercent(value: string): number { return Number(value.replace("%", "")); } function drawConditionsChart(data: ScenarioData): void { const canvas = document.getElementById("conditionsChart") as HTMLCanvasElement | null; if (!canvas) return; const ctx = canvas.getContext("2d"); if (!ctx) return; conditionsWidth = canvas.clientWidth * window.devicePixelRatio; conditionsHeight = canvas.clientHeight * window.devicePixelRatio; canvas.width = conditionsWidth; canvas.height = conditionsHeight; ctx.scale(window.devicePixelRatio, window.devicePixelRatio); const w = canvas.clientWidth; const h = canvas.clientHeight; const padding = 30; const plotW = w - padding * 2; const plotH = h - padding * 2; const points = 7; const moisture = data.moistureHistory; const temperature = data.temperatureHistory; const allValues = [...moisture, ...temperature]; const minVal = Math.min(...allValues); const maxVal = Math.max(...allValues); const range = maxVal - minVal || 10; ctx.clearRect(0, 0, w, h); ctx.font = "11px Arial, sans-serif"; ctx.fillStyle = "#afc5b3"; ctx.textAlign = "right"; ctx.fillText(Math.round(maxVal).toString(), padding - 6, padding + 4); ctx.fillText(Math.round(minVal).toString(), padding - 6, h - padding + 4); const days = ["D1", "D2", "D3", "D4", "D5", "D6", "D7"]; ctx.textAlign = "center"; for (let i = 0; i < days.length; i++) { const day = days[i]!; const x = padding + (plotW / (points - 1)) * i; ctx.fillText(day, x, h - padding + 16); } const getY = (val: number) => h - padding - ((val - minVal) / range) * plotH; const getX = (i: number) => padding + (plotW / (points - 1)) * i; ctx.beginPath(); ctx.strokeStyle = "#c9ff75"; ctx.lineWidth = 2; ctx.lineJoin = "round"; moisture.forEach((v, i) => { if (i === 0) ctx.moveTo(getX(i), getY(v)); else ctx.lineTo(getX(i), getY(v)); }); ctx.stroke(); ctx.beginPath(); ctx.strokeStyle = "#ff6b6b"; ctx.lineWidth = 2; ctx.lineJoin = "round"; temperature.forEach((v, i) => { if (i === 0) ctx.moveTo(getX(i), getY(v)); else ctx.lineTo(getX(i), getY(v)); }); ctx.stroke(); moisture.forEach((v, i) => { ctx.beginPath(); ctx.fillStyle = "#c9ff75"; ctx.arc(getX(i), getY(v), 3, 0, Math.PI * 2); ctx.fill(); }); temperature.forEach((v, i) => { ctx.beginPath(); ctx.fillStyle = "#ff6b6b"; ctx.arc(getX(i), getY(v), 3, 0, Math.PI * 2); ctx.fill(); }); } function drawBatteryChart(data: ScenarioData): void { const canvas = document.getElementById("batteryChart") as HTMLCanvasElement | null; if (!canvas) return; const ctx = canvas.getContext("2d"); if (!ctx) return; batteryWidth = canvas.clientWidth * window.devicePixelRatio; batteryHeight = canvas.clientHeight * window.devicePixelRatio; canvas.width = batteryWidth; canvas.height = batteryHeight; ctx.scale(window.devicePixelRatio, window.devicePixelRatio); const w = canvas.clientWidth; const h = canvas.clientHeight; const centerX = w / 2; const centerY = h / 2; const radius = Math.min(w, h) / 2 - 10; const innerRadius = radius * 0.7; ctx.clearRect(0, 0, w, h); const battery = parsePercent(data.battery); const endAngle = -Math.PI / 2 + (battery / 100) * 2 * Math.PI; ctx.beginPath(); ctx.arc(centerX, centerY, radius, -Math.PI / 2, endAngle, false); ctx.lineTo(centerX, centerY); ctx.closePath(); ctx.fillStyle = "#c9ff75"; ctx.fill(); ctx.beginPath(); ctx.arc(centerX, centerY, innerRadius, 0, Math.PI * 2, false); ctx.fillStyle = "rgba(7, 28, 21, 1)"; ctx.fill(); ctx.fillStyle = "#c9ff75"; ctx.font = "bold 20px Arial, sans-serif"; ctx.textAlign = "center"; ctx.textBaseline = "middle"; ctx.fillText(`${battery}%`, centerX, centerY); } function updateCharts(data: ScenarioData): void { drawConditionsChart(data); drawBatteryChart(data); } function updateScenarioDisplay(mode: ScenarioMode): void { const data = getScenarioTranslation(mode); const moistureEl = document.getElementById("moisture"); const temperatureEl = document.getElementById("temperature"); const roverEl = document.getElementById("rover"); const batteryEl = document.getElementById("battery"); const moistureTextEl = document.getElementById("moistureText"); const temperatureTextEl = document.getElementById("temperatureText"); const roverTextEl = document.getElementById("roverText"); const batteryTextEl = document.getElementById("batteryText"); const alertsEl = document.getElementById("alerts"); if (moistureEl) moistureEl.textContent = data.moisture; if (temperatureEl) temperatureEl.textContent = data.temperature; if (roverEl) roverEl.textContent = data.rover; if (batteryEl) batteryEl.textContent = data.battery; if (moistureTextEl) moistureTextEl.textContent = data.moistureText; if (temperatureTextEl) temperatureTextEl.textContent = data.temperatureText; if (roverTextEl) roverTextEl.textContent = data.roverText; if (batteryTextEl) batteryTextEl.textContent = data.batteryText; if (alertsEl) { alertsEl.innerHTML = data.alerts .map( (item) => `
${item[0]}

${item[1]}

` ) .join(""); } document.querySelectorAll(".controls button").forEach( (button) => { button.classList.toggle("active", button.dataset.mode === mode); } ); updateCharts(data); } function render(): void { applyTranslations(); } const menuButton = document.getElementById("menu")!; const navlinks = document.getElementById("navlinks")!; menuButton.addEventListener("click", () => { const open = navlinks.classList.toggle("open"); menuButton.textContent = open ? "×" : "☰"; menuButton.setAttribute("aria-expanded", String(open)); }); navlinks.querySelectorAll("a").forEach((link) => { link.addEventListener("click", () => { navlinks.classList.remove("open"); menuButton.textContent = "☰"; menuButton.setAttribute("aria-expanded", "false"); }); }); const langToggle = document.getElementById("lang-toggle"); if (langToggle) { langToggle.addEventListener("click", () => { const next: Locale = currentLocale === "en" ? "ml" : "en"; setLocale(next); }); } const yearElement = document.getElementById("year"); if (yearElement) { yearElement.textContent = String(new Date().getFullYear()); } const savedLocale = getStoredLocale(); setLocale(savedLocale); const hero = document.getElementById("hero"); const modelViewer = document.getElementById("rover") as HTMLElement | null; if (hero && modelViewer) { const observer = new IntersectionObserver( (entries) => { entries.forEach((entry) => { if (entry.isIntersecting) { const src = modelViewer.getAttribute("data-src"); if (src) { modelViewer.setAttribute("src", src); modelViewer.removeAttribute("data-src"); } observer.unobserve(hero); } }); }, { threshold: 0.1 } ); observer.observe(hero); } document.querySelectorAll(".controls button").forEach( (button) => { button.addEventListener("click", () => { const mode = button.dataset.mode; if ( mode === "normal" || mode === "dry" || mode === "hot" || mode === "low" ) { updateScenarioDisplay(mode as ScenarioMode); } }); } ); updateScenarioDisplay("normal"); window.addEventListener("resize", () => { const mode = document.querySelector(".controls button.active")?.dataset.mode; if (mode === "normal" || mode === "dry" || mode === "hot" || mode === "low") { updateCharts(getScenarioTranslation(mode as ScenarioMode)); } });