+
+
+
+
+
+
+
+
+ Initializing Realtime Engine
+
+
+
+ Connecting MQTT Broker...
+
+
+
+
+
-
-
-
🔌 Device Status
-
-
-
-
⚡ Power
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Total Power
+
+ 0
+
+
Live
+
+
+
+
+
+
Active Devices
+
+ 0
+
+
Active
+
+
+
+
+
+
Occupancy
+
+ 0
+
+
+12.5%
+
+
+
+
+
+
System Health
+
Healthy
+
MQTT Connected
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 72W
+ ACTIVE
+
+
+
+
+
+
+
+
+
+ 72W
+ ACTIVE
+
+
+
+
+
+
+
+
+
+ 65W
+ ACTIVE
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Temperature
+
26.8°C
+
Normal
+
+
+
+
+
+
+
+
+
+
+
+
+
Humidity
+
58%
+
Stable
+
+
+
+
+
+
+
+
+
+
+
+
+
Air Quality
+
82 AQI
+
Good
+
+
+
+
+
+
+
+
+
+
+
+
+
CO₂ Level
+
620 ppm
+
Normal
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Monitoring stable
+
+
+
+
+ Environment conditions are
+ currently operating within
+ optimal threshold.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
-
\ No newline at end of file
+
+
diff --git a/docs/script.js b/docs/script.js
index 5827381..793f258 100644
--- a/docs/script.js
+++ b/docs/script.js
@@ -1,390 +1,2619 @@
-console.log("JS LOADED");
-
-// =======================
-// GLOBAL STATE
-// =======================
-let generatedData = [];
-let charts = {};
-let streamInterval = null;
-let currentIndex = 0;
-let startTime = null;
-
-let deviceState = {
- fan: { power: 0 },
- lamp: { power: 0 },
- charger: { power: 0 }
+console.log("Realtime Engine Started 🚀");
+
+/* ========================= */
+/* MAIN CHART */
+/* ========================= */
+
+const ctx = document
+ .getElementById("mainChart")
+ .getContext("2d");
+
+/* ========================= */
+/* INITIAL DATA */
+/* ========================= */
+
+let labels = [];
+let powerData = [];
+let occupancyData = [];
+
+/* ========================= */
+/* GENERATE INITIAL */
+/* ========================= */
+
+for (let i = 0; i < 24; i++) {
+
+ labels.push(`${i}:00`);
+
+ powerData.push(
+ 1.2 +
+ Math.random() * 1.5
+ );
+
+ occupancyData.push(
+ Math.floor(
+ 2 + Math.random() * 6
+ )
+ );
+}
+
+/* ========================= */
+/* CHART */
+/* ========================= */
+
+/* ========================= */
+/* CHART DATASETS */
+/* ========================= */
+
+const chartData = {
+
+ "24H": {
+
+ labels: [
+ "0:00", "1:00", "2:00", "3:00",
+ "4:00", "5:00", "6:00", "7:00",
+ "8:00", "9:00", "10:00", "11:00",
+ "12:00", "13:00", "14:00", "15:00",
+ "16:00", "17:00", "18:00", "19:00",
+ "20:00", "21:00", "22:00", "23:00"
+ ],
+
+ power: [
+ 2.0, 2.4, 2.5, 2.1,
+ 1.2, 1.7, 2.3, 1.5,
+ 2.0, 1.9, 1.7, 2.6,
+ 1.3, 2.2, 1.2, 1.1,
+ 1.2, 1.4, 1.2, 1.0,
+ 0.9, 0.7, 0.8, 0.6
+ ],
+
+ occupancy: [
+ 7, 4, 6, 5,
+ 2, 5, 7, 7,
+ 3, 3, 6, 5,
+ 7, 7, 2, 5,
+ 6, 3, 4, 5,
+ 6, 6, 7, 7
+ ]
+ },
+
+ "7D": {
+
+ labels: [
+ "Mon", "Tue", "Wed",
+ "Thu", "Fri", "Sat", "Sun"
+ ],
+
+ power: [
+ 18, 22, 25, 20, 26, 17, 15
+ ],
+
+ occupancy: [
+ 5, 6, 7, 5, 8, 4, 3
+ ]
+ },
+
+ "30D": {
+
+ labels: [
+ "W1", "W2", "W3", "W4"
+ ],
+
+ power: [
+ 120, 140, 110, 170
+ ],
+
+ occupancy: [
+ 4, 6, 5, 7
+ ]
+ }
};
-let currentOccupancy = 0;
-let weatherState = "clear";
+const mainChart = new Chart(ctx, {
-// =======================
-// MQTT SETUP
-// =======================
-let client = mqtt.connect("wss://broker.hivemq.com:8884/mqtt");
+ type: "line",
-client.on("connect", () => {
- console.log("✅ MQTT Connected");
-});
+ data: {
-client.on("reconnect", () => {
- console.log("🔄 Reconnecting MQTT...");
-});
+ labels: chartData["24H"].labels,
+
+ datasets: [
+
+ {
+ label: "Power (kW)",
+
+ data: chartData["24H"].power,
+
+ borderColor: "#00c2ff",
+
+ backgroundColor:
+ "rgba(0,194,255,0.15)",
+
+ borderWidth: 3,
+
+ tension: 0.45,
+
+ fill: true,
+
+ pointRadius: 0,
+
+ pointHoverRadius: 6,
+
+ pointHoverBackgroundColor:
+ "#00c2ff",
+
+ yAxisID: "y",
+ },
+
+ {
+ label: "Occupancy",
+
+ data: chartData["24H"].occupancy,
+
+ borderColor: "#9b5cff",
+
+ backgroundColor:
+ "rgba(155,92,255,0.08)",
+
+ borderWidth: 3,
+
+ tension: 0.45,
+
+ fill: false,
+
+ pointRadius: 0,
+
+ pointHoverRadius: 6,
+
+ pointHoverBackgroundColor:
+ "#9b5cff",
+
+ yAxisID: "y1",
+ }
+ ]
+ },
+
+ options: {
+
+ responsive: true,
+
+ maintainAspectRatio: false,
+
+ interaction: {
+ mode: "index",
+ intersect: false
+ },
+
+ plugins: {
+
+ legend: {
+
+ labels: {
+ color: "#8fa3b8",
+ usePointStyle: true,
+ pointStyle: "circle",
+ padding: 20
+ }
+ },
+
+ tooltip: {
+
+ backgroundColor: "#111c29",
+
+ borderColor:
+ "rgba(255,255,255,0.08)",
+
+ borderWidth: 1,
+
+ padding: 14,
+
+ titleColor: "#ffffff",
+
+ bodyColor: "#8fa3b8",
+
+ displayColors: true
+ }
+ },
+
+ scales: {
+
+ x: {
+
+ grid: {
+ color:
+ "rgba(255,255,255,0.03)"
+ },
+
+ ticks: {
+ color: "#6f8193"
+ }
+ },
+
+ y: {
+
+ position: "left",
+
+ grid: {
+ color:
+ "rgba(255,255,255,0.04)"
+ },
+
+ ticks: {
+ color: "#00c2ff"
+ }
+ },
+
+ y1: {
+
+ position: "right",
-client.on("error", (err) => {
- console.error("❌ MQTT Error:", err);
+ grid: {
+ drawOnChartArea: false
+ },
+
+ ticks: {
+ color: "#9b5cff"
+ }
+ }
+ }
+ }
});
-// =======================
-// CONFIG
-// =======================
-function getConfig() {
- return {
- intervalMs: parseInt(document.getElementById("intervalSelect").value || 1000)
- };
-}
+/* ========================= */
+/* REALTIME UPDATE */
+/* ========================= */
-// =======================
-// TIME
-// =======================
-function getSimulatedTime(idx) {
- return new Date(startTime.getTime() + idx * 1000);
-}
+setInterval(() => {
-// =======================
-// WEATHER (MARKOV)
-// =======================
-function nextWeather() {
- let r = Math.random();
-
- if (weatherState === "clear") {
- if (r < 0.1) weatherState = "cloudy";
- else if (r < 0.15) weatherState = "rain";
- } else if (weatherState === "cloudy") {
- if (r < 0.3) weatherState = "clear";
- else if (r < 0.5) weatherState = "rain";
- } else {
- if (r < 0.4) weatherState = "cloudy";
- }
-
- return weatherState;
-}
+ powerData.shift();
+ occupancyData.shift();
-// =======================
-// OCCUPANCY
-// =======================
-function updateOccupancy(hour) {
- let pEnter = 0.02;
- let pLeave = 0.02;
+ const lastPower =
+ powerData[powerData.length - 1];
- if (hour >= 18) pEnter = 0.05;
- if (hour < 6) pEnter = 0.03;
+ const nextPower =
+ Math.max(
+ 0.5,
+ lastPower +
+ (Math.random() - 0.5) * 0.5
+ );
- if (Math.random() < pEnter) {
- currentOccupancy = Math.min(7, currentOccupancy + 1);
- }
+ const nextOcc =
+ Math.max(
+ 0,
+ Math.min(
+ 10,
+ occupancyData[
+ occupancyData.length - 1
+ ] + Math.floor(Math.random() * 3 - 1)
+ )
+ );
- if (Math.random() < pLeave) {
- currentOccupancy = Math.max(0, currentOccupancy - 1);
- }
+ powerData.push(nextPower);
+ occupancyData.push(nextOcc);
- return currentOccupancy;
-}
+ mainChart.update();
-// =======================
-// PIR
-// =======================
-function generatePIR(occ) {
- if (occ === 0) return 0;
- return Math.random() < (0.2 + occ * 0.1) ? 1 : 0;
-}
+}, 2000);
-// =======================
-// LIGHT
-// =======================
-function getNaturalLight(hour) {
- if (hour < 6 || hour > 18) return 5;
+/* ========================= */
+/* LIVE CLOCK */
+/* ========================= */
- let base = 80;
- if (weatherState === "cloudy") base = 60;
- if (weatherState === "rain") base = 40;
+function updateClock() {
- return base;
-}
+ const now = new Date();
-// =======================
-// TEMP & HUM
-// =======================
-function generateTemp(hour, occ) {
- let base = 27 + 3 * Math.sin((2 * Math.PI * hour) / 24);
- let occHeat = occ * 0.4;
+ const hours =
+ String(now.getHours())
+ .padStart(2, "0");
- if (weatherState === "rain") base -= 2;
+ const minutes =
+ String(now.getMinutes())
+ .padStart(2, "0");
- return base + occHeat + (Math.random() - 0.5);
+ document.getElementById(
+ "liveClock"
+ ).textContent =
+ `${hours}:${minutes}`;
}
-function generateHum(temp) {
- let h = 80 - (temp - 25) * 2;
+updateClock();
+
+setInterval(updateClock, 1000);
+
+const presetSelect =
+ document.getElementById("presetSelect");
+
+let activePreset = "Normal Day";
+
+/* ========================= */
+/* LIVE ACTIVITY */
+/* ========================= */
+
+const presetActivities = {
+
+ "Normal Day": [
+
+ "Occupancy stabilized within normal threshold",
+
+ "Environment telemetry operating normally",
+
+ "Power usage remained consistent",
+
+ "Realtime sensor synchronization active",
+
+ "Ambient conditions stable"
+ ],
+
+ "High Occupancy": [
+
+ "Occupancy surge detected in monitored zone",
+
+ "Cooling demand increased significantly",
+
+ "Fan speed escalated automatically",
+
+ "Realtime consumption trending upward",
+
+ "Environmental load balancing active"
+ ],
+
+ "Energy Saving": [
+
+ "Eco mode enabled across devices",
+
+ "Non-critical systems switched to standby",
+
+ "Smart power optimization active",
+
+ "Low-energy telemetry profile detected",
+
+ "Consumption reduced successfully"
+ ],
+
+ "Power Spike": [
- if (weatherState === "rain") h += 10;
+ "Voltage fluctuation detected",
- return Math.max(60, Math.min(95, h));
+ "Realtime power spike identified",
+
+ "Electrical load redistribution active",
+
+ "High consumption anomaly detected",
+
+ "Smart charger operating above baseline"
+ ],
+
+ "Rainy Environment": [
+
+ "Humidity compensation activated",
+
+ "Moisture stabilization process running",
+
+ "Condensation prevention enabled",
+
+ "Environmental humidity elevated",
+
+ "Realtime climate adjustment active"
+ ]
+};
+
+const activityList =
+ document.getElementById(
+ "activityList"
+ );
+
+function renderActivities() {
+
+ activityList.innerHTML = "";
+
+ const activeActivities =
+ presetActivities[activePreset];
+
+ const shuffled =
+ [...activeActivities]
+ .sort(() => 0.5 - Math.random());
+
+ shuffled
+ .slice(0, 3)
+ .forEach(message => {
+
+ const item =
+ document.createElement("div");
+
+ item.className =
+ "feed-item";
+
+ item.textContent =
+ message;
+
+ activityList.appendChild(item);
+
+ });
}
-// =======================
-// POWER ENGINE 🔥
-// =======================
-function simulatePower(target, prev) {
+renderActivities();
+
+setInterval(renderActivities, 5000);
+
+/* ========================= */
+/* ENVIRONMENT REALTIME */
+/* ========================= */
+
+const tempValue =
+ document.getElementById(
+ "tempValue"
+ );
+
+const tempStatus =
+ document.getElementById(
+ "tempStatus"
+ );
+
+const humidityValue =
+ document.getElementById(
+ "humidityValue"
+ );
+
+const humidityStatus =
+ document.getElementById(
+ "humidityStatus"
+ );
+
+const aqiValue =
+ document.getElementById(
+ "aqiValue"
+ );
+
+const aqiStatus =
+ document.getElementById(
+ "aqiStatus"
+ );
+
+const co2Value =
+ document.getElementById(
+ "co2Value"
+ );
+
+const co2Status =
+ document.getElementById(
+ "co2Status"
+ );
+
+function updateEnvironment() {
- if (target === 0) {
- return Math.max(0, prev - Math.random() * 5);
- }
+ /* TEMP */
+ let baseTemp = 25;
- let diff = target - prev;
- let ramp = diff * 0.2;
+ if (activePreset === "High Occupancy") {
+ baseTemp = 29;
+ }
+
+ if (activePreset === "Energy Saving") {
+ baseTemp = 23;
+ }
+
+ if (activePreset === "Rainy Environment") {
+ baseTemp = 24;
+ }
+
+ const temp =
+ (baseTemp + Math.random() * 2)
+ .toFixed(1);
+
+ tempValue.textContent =
+ `${temp}°C`;
+
+ tempStatus.textContent =
+ temp > 28
+ ? "Warning"
+ : "Normal";
- let noise = (Math.random() - 0.5) * target * 0.05;
- let spike = Math.random() < 0.05 ? target * 0.1 : 0;
+ /* HUMIDITY */
+ let humidityBase = 45;
- return Math.max(0, prev + ramp + noise + spike);
+ if (activePreset === "Rainy Environment") {
+ humidityBase = 70;
+ }
+
+ if (activePreset === "Energy Saving") {
+ humidityBase = 40;
+ }
+
+ const humidity =
+ Math.floor(
+ humidityBase + Math.random() * 15
+ );
+
+ humidityValue.textContent =
+ `${humidity}%`;
+
+ humidityStatus.textContent =
+ humidity > 65
+ ? "High"
+ : "Stable";
+
+ /* AQI */
+ const aqi =
+ Math.floor(
+ 60 + Math.random() * 40
+ );
+
+ aqiValue.textContent =
+ `${aqi} AQI`;
+
+ aqiStatus.textContent =
+ aqi > 100
+ ? "Moderate"
+ : "Good";
+
+ /* CO2 */
+ const co2 =
+ Math.floor(
+ 500 + Math.random() * 300
+ );
+
+ co2Value.textContent =
+ `${co2} ppm`;
+
+ co2Status.textContent =
+ co2 > 750
+ ? "High"
+ : "Normal";
}
-// =======================
-// GENERATE POINT
-// =======================
-function generatePoint(i) {
- let t = getSimulatedTime(i);
- let hour = t.getHours();
+updateEnvironment();
+
+setInterval(
+ updateEnvironment,
+ 4000
+);
+
+/* ========================= */
+/* DEVICE REALTIME */
+/* ========================= */
+
+const fanPower =
+ document.getElementById(
+ "fanPower"
+ );
+
+const lampPower =
+ document.getElementById(
+ "lampPower"
+ );
- nextWeather();
+const chargerPower =
+ document.getElementById(
+ "chargerPower"
+ );
- let occ = updateOccupancy(hour);
- let pir = generatePIR(occ);
+const fanStatus =
+ document.getElementById(
+ "fanStatus"
+ );
- let light1 = getNaturalLight(hour);
- let lampStatus = (light1 < 40 && occ > 0) ? 1 : 0;
- let light2 = light1 + 20;
+const lampStatus =
+ document.getElementById(
+ "lampStatus"
+ );
- let temp = generateTemp(hour, occ);
- let hum = generateHum(temp);
+const chargerStatus =
+ document.getElementById(
+ "chargerStatus"
+ );
- let fanStatus = (temp > 30 || occ >= 3) ? 1 : 0;
- let chargerStatus = Math.random() < 0.1 ? 1 : 0;
+function updateDevices() {
- const W = { fan: 80, lamp: 12, charger: 65 };
+ let fanMin = 40;
+ let fanMax = 90;
- deviceState.fan.power = simulatePower(fanStatus ? W.fan : 0, deviceState.fan.power);
- deviceState.lamp.power = simulatePower(lampStatus ? W.lamp : 0, deviceState.lamp.power);
- deviceState.charger.power = simulatePower(chargerStatus ? W.charger : 0, deviceState.charger.power);
+ let lampMin = 5;
+ let lampMax = 20;
- let fanPower = deviceState.fan.power;
- let lampPower = deviceState.lamp.power;
- let chargerPower = deviceState.charger.power;
+ let chargerMin = 20;
+ let chargerMax = 80;
- let roomPower = fanPower + lampPower + chargerPower;
+ /* ========================= */
+ /* PRESET LOGIC */
+ /* ========================= */
- let baseLoad = 150 + Math.sin(i / 50) * 50;
- let noise = (Math.random() - 0.5) * 20;
+ if (activePreset === "High Occupancy") {
- let totalPower = roomPower + baseLoad + noise;
+ fanMin = 70;
+ fanMax = 120;
- return {
- timestamp: t.toISOString(),
+ lampMin = 15;
+ lampMax = 35;
- temperature: temp,
- humidity: hum,
+ chargerMin = 60;
+ chargerMax = 100;
+ }
+
+ if (activePreset === "Energy Saving") {
+
+ fanMin = 20;
+ fanMax = 50;
+
+ lampMin = 3;
+ lampMax = 10;
- light: light1,
- light_out: light2,
+ chargerMin = 10;
+ chargerMax = 40;
+ }
+
+ if (activePreset === "Power Spike") {
- pir: pir,
- occupancy: occ,
+ chargerMin = 90;
+ chargerMax = 160;
+ }
- fan_status: fanStatus,
- lamp_status: lampStatus,
- charger_status: chargerStatus,
+ if (activePreset === "Rainy Environment") {
- fan_power: fanPower,
- lamp_power: lampPower,
- charger_power: chargerPower,
+ lampMin = 20;
+ lampMax = 45;
+ }
- total_power: totalPower
- };
+ updateSingleDevice(
+ fanPower,
+ fanStatus,
+ fanMin,
+ fanMax
+ );
+
+ updateSingleDevice(
+ lampPower,
+ lampStatus,
+ lampMin,
+ lampMax
+ );
+
+ updateSingleDevice(
+ chargerPower,
+ chargerStatus,
+ chargerMin,
+ chargerMax
+ );
}
-// =======================
-// AGGREGATION
-// =======================
-function aggregateData(data, type) {
- if (type === "second") return data;
+function updateSingleDevice(
+ powerElement,
+ statusElement,
+ min,
+ max
+) {
- let map = {};
+ const active =
+ Math.random() > 0.2;
- data.forEach(d => {
- let date = new Date(d.timestamp);
+ if (active) {
- let key = type === "minute"
- ? date.toISOString().slice(0, 16)
- : date.toISOString().slice(0, 13);
+ const watt =
+ Math.floor(
+ min +
+ Math.random() *
+ (max - min)
+ );
+
+ powerElement.textContent =
+ `${watt}W`;
+
+ statusElement.textContent =
+ "ACTIVE";
+
+ statusElement.style.color =
+ "#00ff99";
- if (!map[key]) {
- map[key] = { ...d, count: 1 };
} else {
- map[key].temperature += d.temperature;
- map[key].humidity += d.humidity;
- map[key].total_power += d.total_power;
- map[key].count++;
- }
- });
-
- return Object.values(map).map(d => ({
- ...d,
- temperature: d.temperature / d.count,
- humidity: d.humidity / d.count,
- total_power: d.total_power / d.count
- }));
-}
-// =======================
-// CHART
-// =======================
-function buildMultiChart(id, datasets) {
- let ctx = document.getElementById(id).getContext("2d");
+ powerElement.textContent =
+ "0W";
- if (charts[id]) charts[id].destroy();
+ statusElement.textContent =
+ "STANDBY";
- charts[id] = new Chart(ctx, {
- type: "line",
- data: {
- labels: datasets[0].data.map((_, i) => i),
- datasets: datasets
+ statusElement.style.color =
+ "#777";
}
- });
}
-function drawCharts(data) {
- let d = data.slice(-100);
-
- buildMultiChart("envChart", [
- { label: "Temperature (T)", data: d.map(x => x.temperature) },
- { label: "Humidity (H)", data: d.map(x => x.humidity) },
- { label: "Light1", data: d.map(x => x.light) },
- { label: "Light2", data: d.map(x => x.light_out) }
- ]);
-
- buildMultiChart("occChart", [
- { label: "Motion (PIR)", data: d.map(x => x.pir) },
- { label: "Counter Visitor", data: d.map(x => x.occupancy) }
- ]);
-
- buildMultiChart("statusChart", [
- { label: "S-K (Fan)", data: d.map(x => x.fan_status) },
- { label: "S-C (Charger)", data: d.map(x => x.charger_status) },
- { label: "S-L (Lamp)", data: d.map(x => x.lamp_status) }
- ]);
-
- buildMultiChart("powerChart", [
- { label: "P-K (Fan)", data: d.map(x => x.fan_power) },
- { label: "P-C (Charger)", data: d.map(x => x.charger_power) },
- { label: "P-L (Lamp)", data: d.map(x => x.lamp_power) },
- { label: "Total Power (TP)", data: d.map(x => x.total_power) }
- ]);
-}
+updateDevices();
-// =======================
-// GENERATE
-// =======================
-function generateData() {
- generatedData = [];
- currentIndex = 0;
- startTime = new Date();
+setInterval(
+ updateDevices,
+ 5000
+);
- deviceState = { fan: { power: 0 }, lamp: { power: 0 }, charger: { power: 0 } };
- currentOccupancy = 0;
- weatherState = "clear";
+/* ========================= */
+/* CHART FILTER */
+/* ========================= */
- let count = parseInt(document.getElementById("generateCount").value || 200);
+const filterButtons =
+ document.querySelectorAll(
+ ".filter-btn"
+ );
- for (let i = 0; i < count; i++) {
- generatedData.push(generatePoint(i));
- }
+filterButtons.forEach(button => {
- let res = document.querySelector('input[name="resolution"]:checked').value;
+ button.addEventListener(
+ "click",
+ () => {
- drawCharts(aggregateData(generatedData, res));
- document.getElementById("liveCount").textContent = generatedData.length;
-}
+ filterButtons.forEach(btn =>
+ btn.classList.remove(
+ "active"
+ )
+ );
-// =======================
-// STREAMING
-// =======================
-function startStreaming() {
- if (streamInterval) return;
+ button.classList.add(
+ "active"
+ );
- startTime = new Date();
- currentIndex = 0;
- generatedData = [];
+ const range =
+ button.innerText;
- deviceState = { fan: { power: 0 }, lamp: { power: 0 }, charger: { power: 0 } };
- currentOccupancy = 0;
- weatherState = "clear";
+ mainChart.data.labels =
+ chartData[range].labels;
- updateStatus("Streaming...");
+ mainChart.data.datasets[0].data =
+ chartData[range].power;
- let config = getConfig();
+ mainChart.data.datasets[1].data =
+ chartData[range].occupancy;
- streamInterval = setInterval(() => {
- let p = generatePoint(currentIndex);
- generatedData.push(p);
+ mainChart.update();
- // 🔥 MQTT PUBLISH
- if (client && client.connected) {
- client.publish("iot/smartroom/data", JSON.stringify(p));
- console.log("📡 Sent:", new Date().toLocaleTimeString(), p);
+ }
+ );
+});
+
+/* ========================= */
+/* SIDEBAR NAVIGATION */
+/* ========================= */
+
+const navItems =
+ document.querySelectorAll(
+ ".nav-item"
+ );
+
+navItems.forEach(item => {
+
+ item.addEventListener(
+ "click",
+ () => {
+
+ navItems.forEach(nav =>
+ nav.classList.remove(
+ "active"
+ )
+ );
+
+ item.classList.add(
+ "active"
+ );
+
+ const targetId =
+ item.dataset.target;
+
+ if (!targetId) return;
+
+ const section =
+ document.getElementById(
+ targetId
+ );
+
+ if (!section) return;
+
+ section.scrollIntoView({
+ behavior: "smooth",
+ block: "start"
+ });
+
+ }
+ );
+});
+
+/* ========================= */
+/* LIVE COUNTER ANIMATION */
+/* ========================= */
+
+const counters =
+ document.querySelectorAll(
+ ".counter"
+ );
+
+counters.forEach(counter => {
+
+ const target =
+ Number(
+ counter.dataset.target
+ );
+
+ const suffix =
+ counter.dataset.suffix || "";
+
+ let current = 0;
+
+ const increment =
+ target / 80;
+
+ function updateCounter() {
+
+ current += increment;
+
+ if (current >= target) {
+
+ counter.innerText =
+ target + suffix;
+
+ return;
+ }
+
+ if (target % 1 !== 0) {
+
+ counter.innerText =
+ current.toFixed(2)
+ + suffix;
+
+ } else {
+
+ counter.innerText =
+ Math.floor(current)
+ + suffix;
+ }
+
+ requestAnimationFrame(
+ updateCounter
+ );
}
- let res = document.querySelector('input[name="resolution"]:checked').value;
+ updateCounter();
+
+});
+
+const speedSlider =
+ document.getElementById(
+ "speedRange"
+ );
+
+let simulationSpeed = 3000;
+let datasetMemory = [];
+
+/* ========================= */
+/* REALTIME SIMULATION */
+/* ========================= */
- drawCharts(aggregateData(generatedData, res));
- document.getElementById("liveCount").textContent = generatedData.length;
+function randomFloat(min, max) {
- currentIndex++;
+ return (
+ Math.random() * (max - min)
+ + min
+ ).toFixed(2);
- }, config.intervalMs);
}
-function stopStreaming() {
- clearInterval(streamInterval);
- streamInterval = null;
- updateStatus("Stopped");
+function randomInt(min, max) {
+
+ return Math.floor(
+ Math.random() * (max - min + 1)
+ ) + min;
+
}
-// =======================
-// STATUS
-// =======================
-function updateStatus(text) {
- let el = document.getElementById("status");
- el.textContent = text;
- el.style.color = text.includes("Streaming") ? "green" : "red";
+function realtimeSimulationLoop() {
+
+ /* ========================= */
+ /* UPDATE METRICS */
+ /* ========================= */
+
+ let powerMin = 1.8;
+ let powerMax = 3.2;
+
+ let occupancyMin = 2;
+ let occupancyMax = 10;
+
+ let devicesMin = 20;
+ let devicesMax = 30;
+
+ /* ========================= */
+ /* PRESET LOGIC */
+ /* ========================= */
+
+ if (activePreset === "High Occupancy") {
+
+ powerMin = 2.8;
+ powerMax = 4.2;
+
+ occupancyMin = 7;
+ occupancyMax = 12;
+
+ devicesMin = 28;
+ devicesMax = 40;
+ }
+
+ if (activePreset === "Energy Saving") {
+
+ powerMin = 0.8;
+ powerMax = 1.6;
+
+ occupancyMin = 2;
+ occupancyMax = 5;
+
+ devicesMin = 10;
+ devicesMax = 20;
+ }
+
+ if (activePreset === "Power Spike") {
+
+ powerMin = 3.5;
+ powerMax = 5.5;
+
+ occupancyMin = 4;
+ occupancyMax = 8;
+
+ devicesMin = 25;
+ devicesMax = 38;
+ }
+
+ if (activePreset === "Rainy Environment") {
+
+ powerMin = 1.5;
+ powerMax = 2.5;
+
+ occupancyMin = 1;
+ occupancyMax = 4;
+
+ devicesMin = 18;
+ devicesMax = 26;
+ }
+
+ const power =
+ randomFloat(
+ powerMin,
+ powerMax
+ );
+
+ const occupancy =
+ randomInt(
+ occupancyMin,
+ occupancyMax
+ );
+
+ const devices =
+ randomInt(
+ devicesMin,
+ devicesMax
+ );
+
+ document.getElementById(
+ "powerValue"
+ ).innerText =
+ power + " kW";
+
+ document.getElementById(
+ "occupancyValue"
+ ).innerText =
+ occupancy + " People";
+
+ document.getElementById(
+ "deviceValue"
+ ).innerText =
+ devices;
+
+ /* ========================= */
+ /* DATASET MEMORY */
+ /* ========================= */
+
+ datasetMemory.push({
+
+ timestamp:
+ new Date().toISOString(),
+
+ preset:
+ activePreset,
+
+ power:
+ Number(power),
+
+ occupancy:
+ occupancy,
+
+ devices:
+ devices,
+
+ temperature:
+ tempValue.textContent,
+
+ humidity:
+ humidityValue.textContent,
+
+ aqi:
+ aqiValue.textContent,
+
+ co2:
+ co2Value.textContent
+ });
+
+ /* LIMIT MEMORY */
+
+ if (datasetMemory.length > 500) {
+
+ datasetMemory.shift();
+
+ }
+
+ /* ========================= */
+ /* UPDATE DEVICE POWER */
+ /* ========================= */
+
+ document.getElementById(
+ "fanPower"
+ ).innerText =
+ randomInt(60, 90) + "W";
+
+ document.getElementById(
+ "lampPower"
+ ).innerText =
+ randomInt(8, 20) + "W";
+
+ document.getElementById(
+ "chargerPower"
+ ).innerText =
+ randomInt(40, 80) + "W";
+
+ /* ========================= */
+ /* UPDATE CHART */
+ /* ========================= */
+
+ mainChart.data.datasets[0]
+ .data.shift();
+
+ mainChart.data.datasets[0]
+ .data.push(
+ Number(
+ randomFloat(1.0, 3.0)
+ )
+ );
+
+ mainChart.data.datasets[1]
+ .data.shift();
+
+ mainChart.data.datasets[1]
+ .data.push(
+ randomInt(2, 10)
+ );
+
+ mainChart.update();
+
+ setTimeout(
+ realtimeSimulationLoop,
+ simulationSpeed
+ );
}
-// =======================
-// DOWNLOAD
-// =======================
-function downloadCSV() {
- let res = document.querySelector('input[name="resolution"]:checked').value;
- let data = aggregateData(generatedData, res);
-
- let csv = Object.keys(data[0]).join(",") + "\n";
-
- data.forEach(r => {
- csv += Object.values(r).join(",") + "\n";
- });
-
- let blob = new Blob([csv]);
- let link = document.createElement("a");
- link.href = URL.createObjectURL(blob);
- link.download = "iot_dataset.csv";
- link.click();
-}
\ No newline at end of file
+realtimeSimulationLoop();
+
+/* ========================= */
+/* CONNECTION STATUS */
+/* ========================= */
+
+const connectionStatus =
+ document.getElementById(
+ "connectionStatus"
+ );
+
+const statusMessages = [
+
+ "MQTT Connected",
+
+ "Realtime Stream Active",
+
+ "Broker Synced",
+
+ "Receiving Sensor Data"
+
+];
+
+let statusIndex = 0;
+
+setInterval(() => {
+
+ statusIndex++;
+
+ if (
+ statusIndex >=
+ statusMessages.length
+ ) {
+ statusIndex = 0;
+ }
+
+ connectionStatus.textContent =
+ statusMessages[statusIndex];
+
+}, 4000);
+
+/* ========================= */
+/* TOAST NOTIFICATION */
+/* ========================= */
+
+const toastContainer =
+ document.getElementById(
+ "toastContainer"
+ ) || document.body;
+
+const presetToasts = {
+
+ "Normal Day": [
+
+ {
+ title: "Telemetry Stable",
+ message:
+ "Realtime monitoring operating normally"
+ },
+
+ {
+ title: "MQTT Connected",
+ message:
+ "Broker synchronization successful"
+ },
+
+ {
+ title: "Environment Stable",
+ message:
+ "Temperature and humidity within threshold"
+ }
+ ],
+
+ "High Occupancy": [
+
+ {
+ title: "Occupancy Surge",
+ message:
+ "Realtime occupancy increased significantly"
+ },
+
+ {
+ title: "Cooling Demand",
+ message:
+ "Fan system escalated to high performance"
+ },
+
+ {
+ title: "Power Usage Rising",
+ message:
+ "Energy consumption exceeded baseline"
+ }
+ ],
+
+ "Energy Saving": [
+
+ {
+ title: "Eco Mode Enabled",
+ message:
+ "Non-critical systems switched to standby"
+ },
+
+ {
+ title: "Optimization Active",
+ message:
+ "Smart energy-saving policy applied"
+ },
+
+ {
+ title: "Low Power State",
+ message:
+ "Consumption reduced successfully"
+ }
+ ],
+
+ "Power Spike": [
+
+ {
+ title: "Voltage Instability",
+ message:
+ "Power fluctuation detected on charger node"
+ },
+
+ {
+ title: "Critical Spike",
+ message:
+ "Realtime consumption exceeded safe threshold"
+ },
+
+ {
+ title: "Load Redistribution",
+ message:
+ "Emergency balancing process activated"
+ }
+ ],
+
+ "Rainy Environment": [
+
+ {
+ title: "Humidity Elevated",
+ message:
+ "Ambient moisture increased significantly"
+ },
+
+ {
+ title: "Climate Adjustment",
+ message:
+ "Realtime humidity compensation enabled"
+ },
+
+ {
+ title: "Condensation Prevention",
+ message:
+ "Moisture stabilization system active"
+ }
+ ]
+};
+
+function createToast() {
+
+ const activeToastSet =
+ presetToasts[activePreset];
+
+ const data =
+ activeToastSet[
+ Math.floor(
+ Math.random() *
+ activeToastSet.length
+ )
+ ];
+
+ const toast =
+ document.createElement(
+ "div"
+ );
+
+ toast.className = "toast";
+
+ toast.innerHTML = `
+
+ ${data.title}
+
+
+
+ ${data.message}
+
+ `;
+
+ toastContainer.appendChild(
+ toast
+ );
+
+ setTimeout(() => {
+
+ toast.style.opacity = "0";
+
+ toast.style.transform =
+ "translateX(40px)";
+
+ toast.style.transition =
+ "0.4s ease";
+
+ setTimeout(() => {
+
+ toast.remove();
+
+ }, 400);
+
+ }, 4500);
+}
+
+/* AUTO RANDOM TOAST */
+
+setInterval(() => {
+
+ if (Math.random() > 0.4) {
+
+ createToast();
+
+ }
+
+}, 8000);
+
+const presetChartData = {
+
+ "Normal Day": {
+ power: [
+ 2.0, 2.4, 2.5, 2.1,
+ 1.2, 1.7, 2.3, 1.5,
+ 2.0, 1.9, 1.7, 2.6,
+ 1.3, 2.2, 1.2, 1.1,
+ 1.2, 1.4, 1.2, 1.0,
+ 0.9, 0.7, 0.8, 0.6
+ ],
+
+ occupancy: [
+ 7, 4, 6, 5,
+ 2, 5, 7, 7,
+ 3, 3, 6, 5,
+ 7, 7, 2, 5,
+ 6, 3, 4, 5,
+ 6, 6, 7, 7
+ ]
+ },
+
+ "High Occupancy": {
+ power: [
+ 2.8, 2.9, 3.0, 2.7,
+ 2.6, 3.1, 3.2, 2.9,
+ 3.0, 3.1, 2.8, 3.2,
+ 3.0, 2.9, 3.1, 3.0,
+ 2.8, 2.9, 3.0, 3.2,
+ 3.1, 3.0, 2.9, 3.2
+ ],
+
+ occupancy: [
+ 8, 9, 10, 9,
+ 8, 10, 10, 9,
+ 8, 9, 10, 10,
+ 9, 8, 10, 9,
+ 8, 9, 10, 10,
+ 9, 8, 10, 10
+ ]
+ },
+
+ "Energy Saving": {
+ power: [
+ 1.0, 1.1, 1.2, 1.0,
+ 0.9, 1.0, 1.1, 1.0,
+ 1.2, 1.1, 1.0, 1.2,
+ 1.1, 1.0, 1.1, 1.0,
+ 0.9, 1.0, 1.1, 1.0,
+ 0.8, 0.9, 1.0, 0.9
+ ],
+
+ occupancy: [
+ 3, 4, 3, 4,
+ 3, 4, 3, 4,
+ 3, 4, 3, 4,
+ 3, 4, 3, 4,
+ 3, 4, 3, 4,
+ 3, 4, 3, 4
+ ]
+ },
+
+ "Power Spike": {
+ power: [
+ 1.5, 2.0, 3.5, 2.8,
+ 3.6, 2.9, 3.4, 2.7,
+ 3.8, 2.9, 3.7, 3.2,
+ 2.8, 3.5, 3.1, 3.0,
+ 2.9, 3.8, 3.3, 2.9,
+ 3.6, 3.2, 3.7, 3.5
+ ],
+
+ occupancy: [
+ 4, 5, 4, 5,
+ 4, 5, 4, 5,
+ 4, 5, 4, 5,
+ 4, 5, 4, 5,
+ 4, 5, 4, 5,
+ 4, 5, 4, 5
+ ]
+ },
+
+ "Rainy Environment": {
+ power: [
+ 1.7, 1.8, 1.9, 1.7,
+ 1.8, 1.9, 2.0, 1.8,
+ 1.9, 2.0, 1.8, 1.9,
+ 2.0, 1.8, 1.9, 1.8,
+ 1.7, 1.8, 1.9, 2.0,
+ 1.8, 1.9, 2.0, 1.8
+ ],
+
+ occupancy: [
+ 2, 3, 2, 3,
+ 2, 3, 2, 3,
+ 2, 3, 2, 3,
+ 2, 3, 2, 3,
+ 2, 3, 2, 3,
+ 2, 3, 2, 3
+ ]
+ }
+};
+
+/* ========================= */
+/* AI INSIGHT */
+/* ========================= */
+
+const presetInsights = {
+
+ "Normal Day": {
+ title: "Environment stable",
+ description: "Temperature and humidity currently operating within optimal threshold range."
+ },
+
+ "High Occupancy": {
+ title: "Occupancy spike detected",
+ description: "Power usage increased due to elevated occupancy and cooling demand."
+ },
+
+ "Energy Saving": {
+ title: "Energy optimization active",
+ description: "System reduced non-critical consumption and switched devices into eco mode."
+ },
+
+ "Power Spike": {
+ title: "Abnormal energy surge",
+ description: "Sudden increase detected on power distribution line."
+ },
+
+ "Rainy Environment": {
+ title: "Humidity level elevated",
+ description: "Moisture and ambient humidity increased due to weather simulation."
+ }
+};
+
+const aiTitle =
+ document.getElementById(
+ "aiTitle"
+ );
+
+const aiDescription =
+ document.getElementById(
+ "aiDescription"
+ );
+
+const aiInsights = [
+
+ {
+ title:
+ "Power usage increasing",
+
+ description:
+ "Energy consumption increased during high occupancy period. Cooling devices contributing 28% additional load."
+ },
+
+ {
+ title:
+ "Environment stable",
+
+ description:
+ "Temperature and humidity currently operating within optimal threshold range."
+ },
+
+ {
+ title:
+ "Occupancy pattern detected",
+
+ description:
+ "Peak occupancy detected during evening cycle with consistent environmental fluctuation."
+ },
+
+ {
+ title:
+ "Anomaly probability low",
+
+ description:
+ "Sensor activity currently aligned with expected realtime operational behavior."
+ },
+
+ {
+ title:
+ "Smart cooling recommended",
+
+ description:
+ "High temperature trend detected. Eco cooling optimization recommended."
+ }
+];
+
+function updateAIInsight() {
+
+ const randomInsight =
+ aiInsights[
+ Math.floor(
+ Math.random() *
+ aiInsights.length
+ )
+ ];
+
+ aiTitle.textContent =
+ randomInsight.title;
+
+ aiDescription.textContent =
+ randomInsight.description;
+}
+
+updateAIInsight();
+
+setInterval(
+ updateAIInsight,
+ 7000
+);
+
+/* ========================= */
+/* ANOMALY DETECTION */
+/* ========================= */
+
+const presetAnomalies = {
+
+ "Normal Day": [
+ {
+ title: "Occupancy fluctuation",
+ description:
+ "Minor occupancy variation detected during monitoring cycle.",
+ severity: "LOW"
+ },
+
+ {
+ title: "Sensor latency detected",
+ description:
+ "Telemetry response delay detected from environment node.",
+ severity: "LOW"
+ }
+ ],
+
+ "High Occupancy": [
+ {
+ title: "Cooling overload risk",
+ description:
+ "Fan system operating near maximum thermal threshold.",
+ severity: "HIGH"
+ },
+
+ {
+ title: "Occupancy surge detected",
+ description:
+ "Unexpected occupancy increase detected within short interval.",
+ severity: "MEDIUM"
+ }
+ ],
+
+ "Energy Saving": [
+ {
+ title: "Eco mode stabilization",
+ description:
+ "Low power operational mode active across multiple devices.",
+ severity: "LOW"
+ }
+ ],
+
+ "Power Spike": [
+ {
+ title: "Critical power spike",
+ description:
+ "Abnormal voltage fluctuation detected on smart charger node.",
+ severity: "HIGH"
+ },
+
+ {
+ title: "Power instability",
+ description:
+ "Realtime consumption variance exceeded safe threshold.",
+ severity: "MEDIUM"
+ }
+ ],
+
+ "Rainy Environment": [
+ {
+ title: "Humidity threshold exceeded",
+ description:
+ "Ambient humidity crossed operational comfort threshold.",
+ severity: "HIGH"
+ },
+
+ {
+ title: "Condensation risk",
+ description:
+ "Moisture accumulation probability increased significantly.",
+ severity: "MEDIUM"
+ }
+ ]
+};
+
+const anomalyList =
+ document.getElementById(
+ "anomalyList"
+ );
+
+const anomalyData = [
+
+ {
+ title:
+ "Sudden power spike",
+
+ description:
+ "Unexpected increase detected on smart charger consumption pattern.",
+
+ severity:
+ "MEDIUM"
+ },
+
+ {
+ title:
+ "Occupancy fluctuation",
+
+ description:
+ "Rapid occupancy transition detected within short interval.",
+
+ severity:
+ "LOW"
+ },
+
+ {
+ title:
+ "Humidity threshold exceeded",
+
+ description:
+ "Humidity crossed recommended operational threshold.",
+
+ severity:
+ "HIGH"
+ },
+
+ {
+ title:
+ "Sensor latency detected",
+
+ description:
+ "Delayed telemetry response detected from environment node.",
+
+ severity:
+ "LOW"
+ },
+
+ {
+ title:
+ "Temperature instability",
+
+ description:
+ "Temperature variance exceeded expected baseline behavior.",
+
+ severity:
+ "MEDIUM"
+ }
+];
+
+function renderAnomalies() {
+
+ anomalyList.innerHTML = "";
+
+ const activeAnomalySet =
+ presetAnomalies[activePreset];
+
+ activeAnomalySet.forEach(item => {
+
+ const div =
+ document.createElement(
+ "div"
+ );
+
+ div.className =
+ "anomaly-item";
+
+ div.innerHTML = `
+
+
+ ${item.title}
+
+
+
+ ${item.description}
+
+
+
+ ${item.severity}
+
+ `;
+
+ anomalyList.appendChild(div);
+
+ });
+}
+
+renderAnomalies();
+
+setInterval(
+ renderAnomalies,
+ 7000
+);
+
+/* ========================= */
+/* THEME SWITCHER */
+/* ========================= */
+
+const themeToggle =
+ document.getElementById(
+ "themeToggle"
+ );
+
+/* LOAD SAVED THEME */
+
+const savedTheme =
+ localStorage.getItem(
+ "dashboard-theme"
+ );
+
+if (savedTheme === "light") {
+
+ document.body.classList.add(
+ "light-theme"
+ );
+
+ themeToggle.querySelector("i")
+ .className =
+ "fa-solid fa-sun";
+}
+
+/* TOGGLE */
+
+themeToggle.addEventListener(
+ "click",
+ () => {
+
+ document.body.classList.toggle(
+ "light-theme"
+ );
+
+ const isLight =
+ document.body.classList.contains(
+ "light-theme"
+ );
+
+ const icon =
+ themeToggle.querySelector("i");
+
+ if (isLight) {
+
+ icon.className =
+ "fa-solid fa-sun";
+
+ localStorage.setItem(
+ "dashboard-theme",
+ "light"
+ );
+
+ } else {
+
+ icon.className =
+ "fa-solid fa-moon";
+
+ localStorage.setItem(
+ "dashboard-theme",
+ "dark"
+ );
+ }
+ }
+);
+
+/* ========================= */
+/* AUTO ACTIVE SIDEBAR */
+/* ========================= */
+
+const sections =
+ document.querySelectorAll("section");
+
+window.addEventListener(
+ "scroll",
+ () => {
+
+ let current = "";
+
+ sections.forEach(section => {
+
+ const sectionTop =
+ section.offsetTop;
+
+ const sectionHeight =
+ section.clientHeight;
+
+ if (
+ scrollY >=
+ sectionTop - 200
+ ) {
+ current =
+ section.getAttribute("id");
+ }
+ });
+
+ navItems.forEach(item => {
+
+ item.classList.remove(
+ "active"
+ );
+
+ if (
+ item.dataset.target ===
+ current
+ ) {
+
+ item.classList.add(
+ "active"
+ );
+ }
+ });
+ }
+);
+
+/* ========================= */
+/* SYSTEM LOGS */
+/* ========================= */
+
+const logsContainer =
+ document.getElementById(
+ "logsContainer"
+ );
+
+const presetLogs = {
+
+ "Normal Day": [
+
+ "MQTT broker synchronized",
+
+ "Realtime telemetry received",
+
+ "Environment node connected",
+
+ "Sensor packet acknowledged",
+
+ "Operational threshold stabilized"
+ ],
+
+ "High Occupancy": [
+
+ "Cooling system escalated to high load",
+
+ "Occupancy surge detected in monitored zone",
+
+ "Power demand increased significantly",
+
+ "Realtime balancing process activated",
+
+ "Environmental load threshold exceeded"
+ ],
+
+ "Energy Saving": [
+
+ "Eco standby mode enabled",
+
+ "Non-critical devices switched to low power",
+
+ "Smart optimization policy applied",
+
+ "Reduced telemetry consumption profile active",
+
+ "Energy stabilization completed"
+ ],
+
+ "Power Spike": [
+
+ "Voltage instability detected",
+
+ "Critical power spike identified",
+
+ "Emergency load redistribution active",
+
+ "Electrical threshold exceeded",
+
+ "High variance consumption pattern detected"
+ ],
+
+ "Rainy Environment": [
+
+ "Humidity compensation enabled",
+
+ "Climate stabilization active",
+
+ "Condensation prevention running",
+
+ "Environmental moisture threshold elevated",
+
+ "Realtime atmospheric adjustment synchronized"
+ ]
+};
+
+function generateLog() {
+
+ const now =
+ new Date();
+
+ const time =
+ now.toLocaleTimeString();
+
+ const activeLogs =
+ presetLogs[activePreset];
+
+ const randomMessage =
+ activeLogs[
+ Math.floor(
+ Math.random() *
+ activeLogs.length
+ )
+ ];
+
+ const log =
+ document.createElement(
+ "div"
+ );
+
+ log.className =
+ "log-item";
+
+ log.innerHTML = `
+
+ [${time}]
+
+
+ ${randomMessage}
+ `;
+
+ logsContainer.prepend(log);
+
+ if (
+ logsContainer.children.length > 12
+ ) {
+
+ logsContainer.lastChild.remove();
+ }
+}
+
+generateLog();
+
+setInterval(
+ generateLog,
+ 4000
+);
+
+/* ========================= */
+/* MINI SPARKLINE CHARTS */
+/* ========================= */
+
+function createMiniChart(
+ id,
+ color
+) {
+
+ const ctx =
+ document
+ .getElementById(id)
+ .getContext("2d");
+
+ const data =
+ Array.from(
+ { length: 12 },
+ () =>
+ Math.random() * 100
+ );
+
+ return new Chart(ctx, {
+
+ type: "line",
+
+ data: {
+
+ labels:
+ data.map((_, i) => i),
+
+ datasets: [
+
+ {
+ data,
+
+ borderColor:
+ color,
+
+ borderWidth: 2,
+
+ tension: 0.5,
+
+ fill: false,
+
+ pointRadius: 0
+ }
+ ]
+ },
+
+ options: {
+
+ responsive: true,
+
+ maintainAspectRatio: false,
+
+ plugins: {
+ legend: {
+ display: false
+ },
+ tooltip: {
+ enabled: false
+ }
+ },
+
+ scales: {
+
+ x: {
+ display: false
+ },
+
+ y: {
+ display: false
+ }
+ }
+ }
+ });
+}
+
+const powerMiniChart =
+ createMiniChart(
+ "powerMiniChart",
+ "#00c2ff"
+ );
+
+const deviceMiniChart =
+ createMiniChart(
+ "deviceMiniChart",
+ "#9b5cff"
+ );
+
+const occupancyMiniChart =
+ createMiniChart(
+ "occupancyMiniChart",
+ "#00ff99"
+ );
+
+function updateMiniChart(chart) {
+
+ chart.data.datasets[0]
+ .data.shift();
+
+ chart.data.datasets[0]
+ .data.push(
+ Math.random() * 100
+ );
+
+ chart.update();
+}
+
+setInterval(() => {
+
+ updateMiniChart(
+ powerMiniChart
+ );
+
+ updateMiniChart(
+ deviceMiniChart
+ );
+
+ updateMiniChart(
+ occupancyMiniChart
+ );
+
+}, 3000);
+
+const exportCsvBtn =
+ document.getElementById(
+ "exportCsvBtn"
+ );
+
+const exportJsonBtn =
+ document.getElementById(
+ "exportJsonBtn"
+ );
+
+const durationSelect =
+ document.getElementById(
+ "durationSelect"
+ );
+
+const datasetStatus =
+ document.getElementById(
+ "datasetStatus"
+ );
+
+speedSlider.addEventListener(
+ "input",
+ () => {
+
+ const value =
+ Number(
+ speedSlider.value
+ );
+
+ /* SLOW */
+
+ if (value <= 33) {
+
+ simulationSpeed = 5000;
+ }
+
+ /* NORMAL */
+
+ else if (value <= 66) {
+
+ simulationSpeed = 3000;
+ }
+
+ /* FAST */
+
+ else {
+
+ simulationSpeed = 1200;
+ }
+
+ datasetStatus.textContent =
+ `Simulation speed updated (${value}%)`;
+
+ }
+);
+
+function generateDataset() {
+
+ const duration =
+ durationSelect.value;
+
+ let rows = 24;
+
+ if (duration === "Last 7 Days") {
+ rows = 7;
+ }
+
+ if (duration === "Last 30 Days") {
+ rows = 30;
+ }
+
+ if (duration === "Last 1 Year") {
+ rows = 365;
+ }
+
+ const dataset = [];
+
+ for (let i = 0; i < rows; i++) {
+
+ let powerMin = 1.0;
+ let powerMax = 3.0;
+
+ let occupancyMin = 2;
+ let occupancyMax = 7;
+
+ let tempMin = 23;
+ let tempMax = 28;
+
+ let humidityMin = 45;
+ let humidityMax = 60;
+
+ let anomalyChance = 0.1;
+
+ /* ========================= */
+ /* PRESET LOGIC */
+ /* ========================= */
+
+ if (activePreset === "High Occupancy") {
+
+ powerMin = 2.8;
+ powerMax = 4.5;
+
+ occupancyMin = 7;
+ occupancyMax = 12;
+
+ tempMin = 28;
+ tempMax = 32;
+
+ anomalyChance = 0.35;
+ }
+
+ if (activePreset === "Energy Saving") {
+
+ powerMin = 0.8;
+ powerMax = 1.5;
+
+ occupancyMin = 1;
+ occupancyMax = 4;
+
+ tempMin = 22;
+ tempMax = 26;
+
+ anomalyChance = 0.03;
+ }
+
+ if (activePreset === "Power Spike") {
+
+ powerMin = 3.5;
+ powerMax = 6.0;
+
+ occupancyMin = 4;
+ occupancyMax = 8;
+
+ tempMin = 26;
+ tempMax = 30;
+
+ anomalyChance = 0.55;
+ }
+
+ if (activePreset === "Rainy Environment") {
+
+ powerMin = 1.5;
+ powerMax = 2.5;
+
+ occupancyMin = 1;
+ occupancyMax = 4;
+
+ humidityMin = 70;
+ humidityMax = 90;
+
+ anomalyChance = 0.25;
+ }
+
+ const anomalyDetected =
+ Math.random() < anomalyChance;
+
+ dataset.push({
+
+ timestamp:
+ new Date(
+ Date.now() -
+ i * 3600000
+ ).toISOString(),
+
+ preset:
+ activePreset,
+
+ power:
+ randomFloat(
+ powerMin,
+ powerMax
+ ),
+
+ occupancy:
+ randomInt(
+ occupancyMin,
+ occupancyMax
+ ),
+
+ temperature:
+ randomFloat(
+ tempMin,
+ tempMax
+ ),
+
+ humidity:
+ randomInt(
+ humidityMin,
+ humidityMax
+ ),
+
+ co2:
+ randomInt(500, 900),
+
+ anomaly:
+ anomalyDetected
+ ? "TRUE"
+ : "FALSE",
+
+ device_status:
+ Math.random() > 0.2
+ ? "ACTIVE"
+ : "STANDBY"
+ });
+ }
+
+ return dataset;
+}
+
+exportCsvBtn.addEventListener(
+ "click",
+ () => {
+
+ const dataset =
+ datasetMemory.length
+ ? datasetMemory
+ : generateDataset();
+
+ const headers =
+ Object.keys(dataset[0]);
+
+ const csvRows = [
+
+ headers.join(",")
+ ];
+
+ dataset.forEach(row => {
+
+ const values =
+ headers.map(
+ header =>
+ row[header]
+ );
+
+ csvRows.push(
+ values.join(",")
+ );
+ });
+
+ const csvContent =
+ csvRows.join("\n");
+
+ const blob =
+ new Blob(
+ [csvContent],
+ { type: "text/csv" }
+ );
+
+ const url =
+ URL.createObjectURL(blob);
+
+ const link =
+ document.createElement("a");
+
+ link.href = url;
+
+ link.download =
+ "iot_dataset.csv";
+
+ link.click();
+
+ URL.revokeObjectURL(url);
+
+ datasetStatus.textContent =
+ "CSV dataset exported successfully";
+
+ }
+);
+
+exportJsonBtn.addEventListener(
+ "click",
+ () => {
+
+ const dataset =
+ datasetMemory.length
+ ? datasetMemory
+ : generateDataset();
+
+ const blob =
+ new Blob(
+ [
+ JSON.stringify(
+ dataset,
+ null,
+ 2
+ )
+ ],
+ {
+ type:
+ "application/json"
+ }
+ );
+
+ const url =
+ URL.createObjectURL(blob);
+
+ const link =
+ document.createElement("a");
+
+ link.href = url;
+
+ link.download =
+ "iot_dataset.json";
+
+ link.click();
+
+ URL.revokeObjectURL(url);
+
+ datasetStatus.textContent =
+ `${dataset.length} realtime records exported`;
+
+ }
+);
+
+/* ========================= */
+/* LOADING SCREEN */
+/* ========================= */
+
+const loadingScreen =
+ document.getElementById(
+ "loadingScreen"
+ );
+
+const loadingProgress =
+ document.getElementById(
+ "loadingProgress"
+ );
+
+const loadingText =
+ document.getElementById(
+ "loadingText"
+ );
+
+const loadingMessages = [
+
+ "Connecting MQTT Broker...",
+
+ "Loading Realtime Telemetry...",
+
+ "Preparing Analytics Engine...",
+
+ "Synchronizing Environment Data...",
+
+ "Launching Dashboard..."
+];
+
+let progress = 0;
+
+const loadingInterval =
+ setInterval(() => {
+
+ progress += 20;
+
+ loadingProgress.style.width =
+ `${progress}%`;
+
+ const index =
+ Math.min(
+ loadingMessages.length - 1,
+ progress / 20 - 1
+ );
+
+ loadingText.textContent =
+ loadingMessages[index];
+
+ if (progress >= 100) {
+
+ clearInterval(
+ loadingInterval
+ );
+
+ setTimeout(() => {
+
+ loadingScreen.classList.add(
+ "hidden"
+ );
+
+ }, 500);
+ }
+
+ }, 700);
+
+presetSelect.addEventListener("change", () => {
+
+ const selectedPreset =
+ presetSelect.value;
+
+ activePreset = selectedPreset;
+
+ /* AI INSIGHT */
+
+ const insight =
+ presetInsights[selectedPreset];
+
+ aiTitle.textContent =
+ insight.title;
+
+ aiDescription.textContent =
+ insight.description;
+
+ /* CHART UPDATE */
+
+ const selectedData =
+ presetChartData[selectedPreset];
+
+ mainChart.data.datasets[0].data =
+ selectedData.power;
+
+ mainChart.data.datasets[1].data =
+ selectedData.occupancy;
+
+ mainChart.update();
+
+});
+
+presetSelect.addEventListener("change", () => {
+
+ datasetStatus.textContent =
+ `Generating ${presetSelect.value} dataset...`;
+
+});
+
+exportJsonBtn.addEventListener(
+ "click",
+ () => {
+
+ const dataStr =
+ JSON.stringify(
+ datasetMemory,
+ null,
+ 2
+ );
+
+ const blob =
+ new Blob(
+ [dataStr],
+ {
+ type:
+ "application/json"
+ }
+ );
+
+ const url =
+ URL.createObjectURL(
+ blob
+ );
+
+ const link =
+ document.createElement(
+ "a"
+ );
+
+ link.href = url;
+
+ link.download =
+ `iot-dataset-${Date.now()}.json`;
+
+ link.click();
+
+ URL.revokeObjectURL(
+ url
+ );
+
+ createToastCustom(
+ "Dataset Exported",
+ "JSON dataset generated successfully"
+ );
+
+ }
+);
+
+/* ========================= */
+/* CUSTOM TOAST */
+/* ========================= */
+
+function createToastCustom(
+ title,
+ message
+) {
+
+ if (!toastContainer) return;
+
+ const toast =
+ document.createElement(
+ "div"
+ );
+
+ toast.className = "toast";
+
+ toast.innerHTML = `
+
+ ${title}
+
+
+
+ ${message}
+
+ `;
+
+ toastContainer.appendChild(
+ toast
+ );
+
+ setTimeout(() => {
+
+ toast.style.opacity = "0";
+
+ toast.style.transform =
+ "translateX(40px)";
+
+ setTimeout(() => {
+
+ toast.remove();
+
+ }, 400);
+
+ }, 3500);
+}
+
+window.addEventListener(
+ "load",
+ () => {
+
+ setTimeout(() => {
+
+ loadingScreen.classList.add(
+ "hidden"
+ );
+
+ }, 1800);
+
+ }
+);
\ No newline at end of file
diff --git a/docs/style.css b/docs/style.css
index 212a7a9..34c3a4e 100644
--- a/docs/style.css
+++ b/docs/style.css
@@ -1,38 +1,1616 @@
+/* ========================= */
+/* RESET */
+/* ========================= */
+
+* {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+}
+
+/* ========================= */
+/* ROOT */
+/* ========================= */
+
+:root {
+
+ --bg: #081018;
+ --card: #0f1722;
+ --card-2: #111c29;
+
+ --border: rgba(255, 255, 255, 0.05);
+
+ --text: #f5f7fa;
+ --text-soft: #8fa3b8;
+
+ --primary: #00c2ff;
+ --primary-soft: rgba(0, 194, 255, 0.15);
+ --surface: rgba(255, 255, 255, 0.03);
+
+ --shadow: 0 10px 30px rgba(0, 0, 0, 0.25);
+
+ --green: #2ecc71;
+ --yellow: #f1c40f;
+ --red: #ff5c5c;
+
+ --radius-lg: 24px;
+ --radius-md: 18px;
+ --radius-sm: 14px;
+
+ --transition: 0.25s ease;
+}
+
+/* ========================= */
+/* BODY */
+/* ========================= */
+
body {
- font-family: Arial;
- padding: 20px;
- background: #f4f6f9;
+ background:
+ radial-gradient(circle at top left, rgba(0, 194, 255, 0.08), transparent 30%),
+ radial-gradient(circle at top right, rgba(0, 100, 255, 0.06), transparent 25%),
+ var(--bg);
+
+ color: var(--text);
+
+ font-family:
+ Inter,
+ Arial,
+ sans-serif;
+
+ min-height: 100vh;
+
+ padding: 24px;
+}
+
+/* ========================= */
+/* APP LAYOUT */
+/* ========================= */
+
+.app-layout {
+ display: flex;
+ gap: 24px;
+}
+
+/* ========================= */
+/* SIDEBAR */
+/* ========================= */
+
+.sidebar {
+
+ width: 68px;
+ min-height: calc(100vh - 48px);
+
+ background: rgba(15, 23, 34, 0.8);
+
+ border: 1px solid var(--border);
+
+ border-radius: 28px;
+
+ backdrop-filter: blur(20px);
+
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+
+ padding: 20px 0;
+
+ position: sticky;
+ top: 24px;
+}
+
+.sidebar-logo {
+
+ width: 46px;
+ height: 46px;
+
+ border-radius: 18px;
+
+ background: var(--primary-soft);
+
+ display: flex;
+ align-items: center;
+ justify-content: center;
+
+ color: var(--primary);
+
+ font-size: 20px;
+
+ margin-bottom: 40px;
+}
+
+.sidebar-menu {
+ display: flex;
+ flex-direction: column;
+ gap: 14px;
+}
+
+.nav-item {
+
+ width: 52px;
+ height: 52px;
+
+ border: none;
+
+ border-radius: 18px;
+
+ background: transparent;
+
+ color: var(--text-soft);
+
+ cursor: pointer;
+
+ transition: var(--transition);
+
+ font-size: 18px;
+}
+
+.nav-item:hover {
+ background: rgba(255, 255, 255, 0.04);
+ color: var(--text);
+}
+
+.nav-item.active {
+ background: var(--primary-soft);
+ color: var(--primary);
+}
+
+/* ========================= */
+/* MAIN CONTENT */
+/* ========================= */
+
+.main-content {
+ flex: 1;
+
+ display: flex;
+ flex-direction: column;
+ gap: 24px;
+}
+
+/* ========================= */
+/* TOP HEADER */
+/* ========================= */
+
+.top-header {
+
+ display: flex;
+ justify-content: space-between;
+ align-items: flex-start;
+
+ gap: 20px;
+}
+
+.header-left h1 {
+
+ font-size: 30px;
+ font-weight: 700;
+
+ margin-bottom: 8px;
+}
+
+.header-left p {
+
+ color: var(--text-soft);
+
+ font-size: 15px;
+}
+
+.header-right {
+
+ display: flex;
+ gap: 12px;
+ flex-wrap: wrap;
+}
+
+.header-status {
+
+ background: var(--card);
+
+ border: 1px solid var(--border);
+
+ border-radius: 16px;
+
+ padding: 14px 18px;
+
+ color: var(--text-soft);
+
+ font-size: 14px;
+}
+
+/* ========================= */
+/* HERO METRICS */
+/* ========================= */
+
+.hero-metrics {
+
+ display: grid;
+
+ grid-template-columns:
+ repeat(auto-fit, minmax(220px, 1fr));
+
+ gap: 20px;
+}
+
+.metric-card {
+
+ background:
+ linear-gradient(180deg,
+ rgba(255, 255, 255, 0.02),
+ rgba(255, 255, 255, 0.01));
+
+ border: 1px solid rgba(255, 255, 255, 0.05);
+
+ border-radius: var(--radius-lg);
+
+ padding: 20px;
+
+ transition: var(--transition);
+
+ position: relative;
+
+ overflow: hidden;
+
+ box-shadow:
+ inset 0 1px 0 rgba(255, 255, 255, 0.03),
+ var(--shadow);
+}
+
+.metric-card::before {
+
+ content: "";
+
+ position: absolute;
+
+ top: -60px;
+ right: -60px;
+
+ width: 140px;
+ height: 140px;
+
+ background:
+ radial-gradient(circle,
+ rgba(0, 194, 255, 0.12),
+ transparent 70%);
+
+ pointer-events: none;
+}
+
+.metric-card:hover {
+ transform: translateY(-4px);
+}
+
+.metric-card h3 {
+
+ color: var(--text-soft);
+
+ font-size: 15px;
+ font-weight: 500;
+
+ margin-bottom: 16px;
+}
+
+.metric-card h2 {
+
+ font-size: 24px;
+ margin-bottom: 12px;
+}
+
+.metric-card p {
+ color: var(--primary);
+ font-size: 14px;
+}
+
+/* ========================= */
+/* MAIN ANALYTICS */
+/* ========================= */
+
+.main-analytics {
+
+ display: grid;
+
+ grid-template-columns:
+ 2fr 1fr;
+
+ gap: 20px;
}
-.panel {
- background: white;
- padding: 15px;
- border-radius: 8px;
- margin-bottom: 15px;
+.analytics-card {
+
+ background:
+ linear-gradient(180deg,
+ rgba(255, 255, 255, 0.02),
+ rgba(255, 255, 255, 0.01));
+
+ border: 1px solid rgba(255, 255, 255, 0.05);
+
+ border-radius: var(--radius-lg);
+
+ padding: 24px;
+
+ position: relative;
+
+ overflow: hidden;
+
+ box-shadow:
+ inset 0 1px 0 rgba(255, 255, 255, 0.03),
+ var(--shadow);
+}
+
+.analytics-card::before {
+
+ content: "";
+
+ position: absolute;
+
+ inset: 0;
+
+ background:
+ linear-gradient(120deg,
+ transparent,
+ rgba(255, 255, 255, 0.015),
+ transparent);
+
+ pointer-events: none;
+}
+
+.large-card {
+ min-height: 390px;
+}
+
+/* ========================= */
+/* CARD HEADER */
+/* ========================= */
+
+.card-header {
+
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+
+ margin-bottom: 20px;
+}
+
+.card-header h3 {
+ font-size: 18px;
+}
+
+/* ========================= */
+/* DEVICE GRID */
+/* ========================= */
+
+.device-grid {
+
+ display: flex;
+ flex-direction: column;
+
+ gap: 18px;
+
+ margin-top: 20px;
+}
+
+.device-item {
+
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+
+ padding: 20px 22px;
+
+ border-radius: 18px;
+
+ background: var(--surface);
+
+ border:
+ 1px solid var(--border);
+
+ transition:
+ 0.3s ease;
+}
+
+.device-item:hover {
+
+ transform: translateY(-2px);
+
+ border-color:
+ rgba(0, 200, 255, 0.18);
+}
+
+.device-left {
+ display: flex;
+ align-items: center;
+ gap: 14px;
+}
+
+.device-dot {
+
+ width: 10px;
+ height: 10px;
+
+ border-radius: 50%;
+
+ background: #00ff99;
+
+ box-shadow:
+ 0 0 10px #00ff99,
+ 0 0 20px #00ff99;
+}
+
+.device-item.off {
+ opacity: 0.45;
+}
+
+.device-item.off .device-dot {
+
+ background: #334155;
+
+ box-shadow: none;
+}
+
+/* ========================= */
+/* FEED */
+/* ========================= */
+
+.feed-card,
+.tools-card {
+
+ background:
+ linear-gradient(180deg,
+ rgba(255, 255, 255, 0.02),
+ rgba(255, 255, 255, 0.01));
+
+ border: 1px solid rgba(255, 255, 255, 0.05);
+
+ border-radius: var(--radius-lg);
+
+ padding: 24px;
+
+ box-shadow:
+ inset 0 1px 0 rgba(255, 255, 255, 0.03),
+ var(--shadow);
+}
+
+.feed-list {
+
+ display: flex;
+ flex-direction: column;
+
+ gap: 14px;
+}
+
+.feed-item {
+
+ background: var(--card-2);
+
+ border-radius: 16px;
+
+ padding: 16px;
+
+ color: var(--text-soft);
+
+ border-left: 3px solid var(--primary);
+
+ animation: fadeSlide 0.4s ease;
+}
+
+/* ========================= */
+/* DATASET TOOLS */
+/* ========================= */
+
+.tools-card p {
+ color: var(--text-soft);
+}
+
+/* ========================= */
+/* RESPONSIVE */
+/* ========================= */
+
+@media (max-width: 1100px) {
+
+ .main-analytics {
+ grid-template-columns: 1fr;
+ }
+}
+
+@media (max-width: 768px) {
+
+ body {
+ padding: 16px;
+ }
+
+ .app-layout {
+ flex-direction: column;
+ }
+
+ .sidebar {
+
+ width: 100%;
+ min-height: auto;
+
+ flex-direction: row;
+
+ justify-content: space-between;
+
+ padding: 14px 20px;
+ }
+
+ .sidebar-menu {
+ flex-direction: row;
+ }
+
+ .top-header {
+ flex-direction: column;
+ }
+
+ .header-left h1 {
+ font-size: 30px;
+ }
+}
+
+canvas {
+ width: 100% !important;
}
-.controls {
- margin-bottom: 15px;
+.large-card {
+ height: 390px;
}
-button {
- margin: 5px;
- padding: 10px;
- border: none;
- background: #007bff;
- color: white;
- border-radius: 5px;
- cursor: pointer;
+#mainChart {
+ height: 300px !important;
}
-.grid {
- display: grid;
- grid-template-columns: repeat(auto-fit, minmax(300px,1fr));
- gap: 15px;
+.large-card canvas {
+ margin-top: 10px;
}
-.card {
- background: white;
- padding: 10px;
- border-radius: 8px;
+/* ========================= */
+/* DEVICE STATUS PULSE */
+/* ========================= */
+
+.device-item {
+
+ position: relative;
+
+ overflow: hidden;
+}
+
+.device-item::before {
+
+ content: "";
+
+ width: 10px;
+ height: 10px;
+
+ border-radius: 50%;
+
+ background: #00ff9d;
+
+ position: absolute;
+
+ left: 18px;
+ top: 50%;
+
+ transform: translateY(-50%);
+
+ box-shadow:
+ 0 0 10px #00ff9d,
+ 0 0 20px #00ff9d;
+
+ animation: pulse 2s infinite;
+}
+
+.device-item span {
+ margin-left: 20px;
+}
+
+@keyframes pulse {
+
+ 0% {
+ opacity: 0.4;
+ transform: translateY(-50%) scale(1);
+ }
+
+ 50% {
+ opacity: 1;
+ transform: translateY(-50%) scale(1.3);
+ }
+
+ 100% {
+ opacity: 0.4;
+ transform: translateY(-50%) scale(1);
+ }
+}
+
+/* ========================= */
+/* ENVIRONMENT SECTION */
+/* ========================= */
+
+.environment-section {
+
+ display: grid;
+
+ grid-template-columns:
+ repeat(auto-fit, minmax(220px, 1fr));
+
+ gap: 20px;
+}
+
+.env-card {
+
+ background:
+ linear-gradient(180deg,
+ rgba(255, 255, 255, 0.02),
+ rgba(255, 255, 255, 0.01));
+
+ border: 1px solid rgba(255, 255, 255, 0.05);
+
+ border-radius: var(--radius-lg);
+
+ padding: 22px;
+
+ display: flex;
+ align-items: center;
+
+ gap: 18px;
+
+ transition: var(--transition);
+
+ box-shadow:
+ inset 0 1px 0 rgba(255, 255, 255, 0.03),
+ var(--shadow);
+}
+
+.env-card:hover {
+ transform: translateY(-4px);
+}
+
+.env-icon {
+
+ width: 58px;
+ height: 58px;
+
+ border-radius: 18px;
+
+ background:
+ rgba(0, 194, 255, 0.12);
+
+ color: var(--primary);
+
+ display: flex;
+ align-items: center;
+ justify-content: center;
+
+ font-size: 22px;
+
+ flex-shrink: 0;
+}
+
+.env-info span {
+
+ color: var(--text-soft);
+
+ font-size: 14px;
+}
+
+.env-info h3 {
+
+ margin-top: 6px;
+
+ font-size: 20px;
+}
+
+.env-info p {
+
+ margin-top: 6px;
+
+ color: var(--green);
+
+ font-size: 14px;
+}
+
+@keyframes fadeSlide {
+
+ from {
+ opacity: 0;
+ transform: translateY(10px);
+ }
+
+ to {
+ opacity: 1;
+ transform: translateY(0);
+ }
+}
+
+.device-item small {
+
+ display: block;
+
+ margin-top: 4px;
+
+ font-size: 11px;
+
+ letter-spacing: 1px;
+
+ color: #00ff99;
+}
+
+/* ========================= */
+/* CHART FILTERS */
+/* ========================= */
+
+.chart-filters {
+
+ display: flex;
+
+ gap: 10px;
+}
+
+.filter-btn {
+
+ border: none;
+
+ background:
+ rgba(255, 255, 255, 0.04);
+
+ color: var(--text-soft);
+
+ padding: 10px 14px;
+
+ border-radius: 12px;
+
+ cursor: pointer;
+
+ transition: 0.25s ease;
+
+ font-size: 13px;
+}
+
+.filter-btn:hover {
+
+ background:
+ rgba(255, 255, 255, 0.08);
+
+ color: white;
+}
+
+.filter-btn.active {
+
+ background:
+ rgba(0, 194, 255, 0.16);
+
+ color: var(--primary);
+
+ border:
+ 1px solid rgba(0, 194, 255, 0.25);
+}
+
+/* ========================= */
+/* LIVE CONNECTION STATUS */
+/* ========================= */
+
+.live-status {
+
+ display: flex;
+
+ align-items: center;
+
+ gap: 10px;
+
+ color: #00ff99;
+}
+
+.status-dot {
+
+ width: 10px;
+ height: 10px;
+
+ border-radius: 50%;
+
+ background: #00ff99;
+
+ box-shadow:
+ 0 0 10px #00ff99,
+ 0 0 20px #00ff99;
+
+ animation: pulseStatus 2s infinite;
+}
+
+@keyframes pulseStatus {
+
+ 0% {
+ opacity: 0.5;
+ transform: scale(1);
+ }
+
+ 50% {
+ opacity: 1;
+ transform: scale(1.25);
+ }
+
+ 100% {
+ opacity: 0.5;
+ transform: scale(1);
+ }
+}
+
+/* ========================= */
+/* TOAST NOTIFICATION */
+/* ========================= */
+
+#toastContainer {
+
+ position: fixed;
+
+ top: 24px;
+ right: 24px;
+
+ display: flex;
+ flex-direction: column;
+
+ gap: 14px;
+
+ z-index: 9999;
+}
+
+.toast {
+
+ min-width: 280px;
+
+ padding: 18px 20px;
+
+ border-radius: 18px;
+
+ background: var(--card);
+
+ border:
+ 1px solid rgba(255, 255, 255, 0.06);
+
+ backdrop-filter: blur(16px);
+
+ color: var(--text);
+
+ box-shadow:
+ var(--shadow);
+
+ animation:
+ toastSlide 0.4s ease;
+
+ position: relative;
+
+ overflow: hidden;
+}
+
+.toast::before {
+
+ content: "";
+
+ position: absolute;
+
+ top: 0;
+ left: 0;
+
+ width: 4px;
+ height: 100%;
+
+ background: #00c2ff;
+}
+
+.toast-title {
+
+ font-size: 14px;
+
+ font-weight: 600;
+
+ margin-bottom: 6px;
+}
+
+.toast-message {
+
+ font-size: 13px;
+
+ color: var(--text-soft);
+
+ line-height: 1.5;
+}
+
+@keyframes toastSlide {
+
+ from {
+ opacity: 0;
+ transform:
+ translateX(40px);
+ }
+
+ to {
+ opacity: 1;
+ transform:
+ translateX(0);
+ }
+}
+
+/* ========================= */
+/* AI INSIGHT */
+/* ========================= */
+
+.ai-card {
+
+ background:
+ linear-gradient(135deg,
+ rgba(0, 194, 255, 0.10),
+ rgba(155, 92, 255, 0.08));
+
+ border:
+ 1px solid rgba(255, 255, 255, 0.06);
+
+ border-radius: 24px;
+
+ padding: 28px;
+
+ position: relative;
+
+ overflow: hidden;
+
+ box-shadow:
+ var(--shadow);
+}
+
+.ai-card::before {
+
+ content: "";
+
+ position: absolute;
+
+ top: -80px;
+ right: -80px;
+
+ width: 180px;
+ height: 180px;
+
+ background:
+ radial-gradient(circle,
+ rgba(255, 255, 255, 0.08),
+ transparent 70%);
+}
+
+.ai-badge {
+
+ background:
+ rgba(0, 255, 153, 0.14);
+
+ color: #00ff99;
+
+ padding: 8px 12px;
+
+ border-radius: 999px;
+
+ font-size: 12px;
+
+ letter-spacing: 1px;
+}
+
+.ai-content {
+
+ margin-top: 20px;
+}
+
+.ai-content h2 {
+
+ font-size: 26px;
+
+ margin-bottom: 14px;
+}
+
+.ai-content p {
+
+ color: var(--text-soft);
+
+ line-height: 1.7;
+
+ max-width: 700px;
+}
+
+/* ========================= */
+/* ANOMALY PANEL */
+/* ========================= */
+
+.anomaly-card {
+
+ background:
+ linear-gradient(180deg,
+ rgba(255, 255, 255, 0.02),
+ rgba(255, 255, 255, 0.01));
+
+ border:
+ 1px solid rgba(255, 255, 255, 0.06);
+
+ border-radius: 24px;
+
+ padding: 24px;
+
+ box-shadow:
+ var(--shadow);
+}
+
+.anomaly-live {
+
+ background:
+ rgba(255, 92, 92, 0.12);
+
+ color: #ff5c5c;
+
+ padding: 8px 12px;
+
+ border-radius: 999px;
+
+ font-size: 12px;
+}
+
+.anomaly-list {
+
+ display: flex;
+
+ flex-direction: column;
+
+ gap: 16px;
+
+ margin-top: 20px;
+}
+
+.anomaly-item {
+
+ background:
+ rgba(255, 255, 255, 0.02);
+
+ border:
+ 1px solid rgba(255, 255, 255, 0.05);
+
+ border-left:
+ 4px solid #ff5c5c;
+
+ border-radius: 18px;
+
+ padding: 18px;
+
+ animation:
+ fadeSlide 0.4s ease;
+}
+
+.anomaly-item h4 {
+
+ font-size: 15px;
+
+ margin-bottom: 8px;
+}
+
+.anomaly-item p {
+
+ color: var(--text-soft);
+
+ font-size: 13px;
+
+ line-height: 1.6;
+}
+
+.anomaly-severity {
+
+ display: inline-block;
+
+ margin-top: 12px;
+
+ padding: 6px 10px;
+
+ border-radius: 999px;
+
+ font-size: 11px;
+
+ letter-spacing: 1px;
+
+ background:
+ rgba(255, 92, 92, 0.12);
+
+ color: #ff5c5c;
+}
+
+/* ========================= */
+/* LIGHT THEME */
+/* ========================= */
+
+body.light-theme {
+
+ --bg: #eef3f8;
+
+ --card: #ffffff;
+ --card-2: #f7fafc;
+
+ --border:
+ rgba(15, 23, 42, 0.06);
+
+ --text: #081018;
+
+ --text-soft: #5b6b7c;
+
+ --primary: #009dff;
+
+ --primary-soft:
+ rgba(0, 157, 255, 0.12);
+
+ --surface:
+ rgba(15, 23, 42, 0.04);
+
+ --shadow:
+ 0 10px 30px rgba(15, 23, 42, 0.08);
+}
+
+/* ========================= */
+/* THEME TOGGLE */
+/* ========================= */
+
+.theme-toggle {
+
+ width: 48px;
+ height: 48px;
+
+ border: none;
+
+ border-radius: 16px;
+
+ background: var(--card);
+
+ border:
+ 1px solid var(--border);
+
+ color: var(--text);
+
+ cursor: pointer;
+
+ transition: 0.25s ease;
+
+ font-size: 16px;
+}
+
+.theme-toggle:hover {
+
+ transform: translateY(-2px);
+
+ background:
+ rgba(255, 255, 255, 0.04);
+}
+
+/* ========================= */
+/* SYSTEM LOGS */
+/* ========================= */
+
+.logs-card {
+
+ background:
+ linear-gradient(180deg,
+ rgba(255, 255, 255, 0.02),
+ rgba(255, 255, 255, 0.01));
+
+ border:
+ 1px solid rgba(255, 255, 255, 0.05);
+
+ border-radius: 24px;
+
+ padding: 24px;
+
+ box-shadow:
+ var(--shadow);
+}
+
+.logs-container {
+
+ margin-top: 20px;
+
+ display: flex;
+
+ flex-direction: column;
+
+ gap: 12px;
+
+ max-height: 320px;
+
+ overflow-y: auto;
+
+ padding-right: 6px;
+}
+
+.logs-container::-webkit-scrollbar {
+
+ width: 6px;
+}
+
+.logs-container::-webkit-scrollbar-thumb {
+
+ background:
+ rgba(255, 255, 255, 0.08);
+
+ border-radius: 999px;
+}
+
+.log-item {
+
+ font-family:
+ monospace;
+
+ font-size: 13px;
+
+ background:
+ var(--surface);
+
+ border:
+ 1px solid var(--border);
+
+ border-radius: 14px;
+
+ padding: 14px 16px;
+
+ color: var(--text-soft);
+
+ animation:
+ fadeSlide 0.35s ease;
+}
+
+.log-time {
+
+ color: var(--primary);
+
+ margin-right: 10px;
+}
+
+/* ========================= */
+/* MINI CHART */
+/* ========================= */
+
+.mini-chart {
+
+ width: 100%;
+
+ height: 60px !important;
+
+ margin-top: 16px;
+
+ opacity: 0.9;
+}
+
+/* ========================= */
+/* LOADING SCREEN */
+/* ========================= */
+
+.loading-screen {
+
+ position: fixed;
+
+ inset: 0;
+
+ background:
+ radial-gradient(circle at top,
+ rgba(0, 194, 255, 0.08),
+ transparent 30%),
+ var(--bg);
+
+ display: flex;
+
+ align-items: center;
+ justify-content: center;
+
+ z-index: 999999;
+
+ transition:
+ opacity 0.8s ease,
+ visibility 0.8s ease;
+}
+
+.loading-screen.hidden {
+
+ opacity: 0;
+
+ visibility: hidden;
+}
+
+.loading-content {
+
+ width: 320px;
+
+ text-align: center;
+}
+
+.loading-logo {
+
+ width: 72px;
+ height: 72px;
+
+ border-radius: 24px;
+
+ background:
+ var(--primary-soft);
+
+ color: var(--primary);
+
+ display: flex;
+
+ align-items: center;
+ justify-content: center;
+
+ font-size: 28px;
+
+ margin: 0 auto 24px;
+
+ animation:
+ pulseLogo 2s infinite;
+}
+
+.loading-content h2 {
+
+ margin-bottom: 12px;
+
+ font-size: 24px;
+}
+
+.loading-content p {
+
+ color: var(--text-soft);
+
+ margin-bottom: 28px;
+}
+
+.loading-bar {
+
+ width: 100%;
+ height: 10px;
+
+ border-radius: 999px;
+
+ background:
+ rgba(255, 255, 255, 0.06);
+
+ overflow: hidden;
+}
+
+.loading-progress {
+
+ width: 0%;
+
+ height: 100%;
+
+ background:
+ linear-gradient(90deg,
+ #00c2ff,
+ #9b5cff);
+
+ border-radius: 999px;
+
+ transition:
+ width 0.4s ease;
+}
+
+@keyframes pulseLogo {
+
+ 0% {
+ transform: scale(1);
+ opacity: 0.7;
+ }
+
+ 50% {
+ transform: scale(1.08);
+ opacity: 1;
+ }
+
+ 100% {
+ transform: scale(1);
+ opacity: 0.7;
+ }
+}
+
+/* ========================= */
+/* DATASET GENERATOR */
+/* ========================= */
+
+.dataset-controls {
+
+ display: grid;
+
+ grid-template-columns:
+ repeat(auto-fit, minmax(220px, 1fr));
+
+ gap: 20px;
+
+ margin-top: 24px;
+}
+
+.control-group {
+
+ display: flex;
+
+ flex-direction: column;
+
+ gap: 12px;
+}
+
+.control-group label {
+
+ font-size: 14px;
+
+ color: var(--text-soft);
+}
+
+.control-group select,
+.control-group input:not([type="range"]) {
+
+ appearance: none;
+ -webkit-appearance: none;
+ -moz-appearance: none;
+
+ background: var(--surface);
+
+ border: 1px solid var(--border);
+
+ border-radius: 14px;
+
+ padding: 14px 16px;
+
+ color: var(--text);
+
+ outline: none;
+
+ transition: 0.25s ease;
+}
+
+.control-group select option {
+
+ background: var(--card);
+
+ color: var(--text);
+}
+
+.control-group select:focus,
+.control-group input:focus {
+
+ border-color:
+ rgba(0, 194, 255, 0.35);
+
+ box-shadow:
+ 0 0 0 4px rgba(0, 194, 255, 0.08);
+}
+
+/* ACTION BUTTONS */
+
+.dataset-actions {
+
+ display: flex;
+
+ gap: 14px;
+
+ margin-top: 26px;
+
+ flex-wrap: wrap;
+}
+
+.dataset-btn {
+
+ border: none;
+
+ background:
+ rgba(0, 194, 255, 0.14);
+
+ color: var(--primary);
+
+ padding: 14px 20px;
+
+ border-radius: 14px;
+
+ cursor: pointer;
+
+ transition: 0.25s ease;
+
+ font-weight: 600;
+}
+
+.dataset-btn:hover {
+
+ transform: translateY(-2px);
+
+ background:
+ rgba(0, 194, 255, 0.22);
+}
+
+.dataset-btn.secondary {
+
+ background:
+ rgba(255, 255, 255, 0.04);
+
+ color: var(--text-soft);
+}
+
+/* STATUS */
+
+.dataset-status {
+
+ margin-top: 24px;
+
+ display: flex;
+
+ align-items: center;
+
+ gap: 12px;
+
+ color: #00ff99;
+
+ font-size: 14px;
+}
+
+.status-indicator {
+
+ width: 10px;
+ height: 10px;
+
+ border-radius: 50%;
+
+ background: #00ff99;
+
+ box-shadow:
+ 0 0 10px #00ff99,
+ 0 0 20px #00ff99;
+
+ animation:
+ pulseStatus 2s infinite;
+}
+
+/* ========================= */
+/* RANGE SLIDER */
+/* ========================= */
+
+input[type="range"] {
+
+ width: 100%;
+
+ appearance: none;
+
+ -webkit-appearance: none;
+
+ background: transparent;
+
+ cursor: pointer;
+
+ margin-top: 10px;
+}
+
+/* TRACK */
+
+input[type="range"]::-webkit-slider-runnable-track {
+
+ height: 8px;
+
+ border-radius: 999px;
+
+ background: var(--surface);
+
+ border: 1px solid var(--border);
+}
+
+/* THUMB */
+
+input[type="range"]::-webkit-slider-thumb {
+
+ -webkit-appearance: none;
+
+ width: 18px;
+ height: 18px;
+
+ border-radius: 50%;
+
+ background: var(--primary);
+
+ border: 2px solid rgba(255,255,255,0.15);
+
+ margin-top: -6px;
+
+ box-shadow:
+ 0 0 12px rgba(0,194,255,0.45);
+
+ transition: 0.2s ease;
+}
+
+
+input[type="range"]::-webkit-slider-thumb:hover {
+
+ transform: scale(1.08);
}
\ No newline at end of file