ieeeColors = ({
navy: "#2C3E50",
teal: "#16A085",
orange: "#E67E22",
gray: "#7F8C8D",
red: "#c0392b",
green: "#27ae60"
})
// Comprehensive sensor database with full specifications
sensors = [
{
id: "dht22",
name: "DHT22 (Temperature & Humidity)",
shortName: "DHT22",
description: "Low-cost digital temperature and humidity sensor. Popular in hobbyist projects. I2C/1-Wire interface, moderate accuracy.",
icon: "🌡️",
category: "Environment",
characteristics: {
accuracy: 3,
cost: 5,
power: 4,
range: 3,
reliability: 3
},
specs: {
tempRange: "-40 to 80°C",
tempAccuracy: "±0.5°C",
humidityRange: "0-100% RH",
humidityAccuracy: "±2% RH",
powerConsumption: "1.5 mA active",
responseTime: "2 seconds",
interface: "1-Wire digital",
lifespan: "2-3 years",
calibrationNeeded: "Factory calibrated"
},
pricing: {
unitCost: 5,
maintenanceYearly: 0,
replacementYears: 3,
calibrationYearly: 0
},
bestFor: ["Hobbyist projects", "Indoor monitoring", "Non-critical applications", "Low budget"],
limitations: ["Slow response time", "Limited lifespan", "Drift over time", "Not industrial-grade"],
tco5Year: 10
},
{
id: "sht85",
name: "Sensirion SHT85 (High-Precision)",
shortName: "SHT85",
description: "Industrial-grade temperature and humidity sensor. Factory-calibrated, excellent long-term stability. I2C interface, high accuracy.",
icon: "🌡️",
category: "Environment",
characteristics: {
accuracy: 5,
cost: 3,
power: 4,
range: 4,
reliability: 5
},
specs: {
tempRange: "-40 to 125°C",
tempAccuracy: "±0.1°C",
humidityRange: "0-100% RH",
humidityAccuracy: "±1.5% RH",
powerConsumption: "0.6 mA active",
responseTime: "8 seconds",
interface: "I2C digital",
lifespan: "10+ years",
calibrationNeeded: "Factory calibrated, stable"
},
pricing: {
unitCost: 25,
maintenanceYearly: 0,
replacementYears: 10,
calibrationYearly: 0
},
bestFor: ["Industrial monitoring", "HVAC control", "Long-term deployment", "High accuracy required"],
limitations: ["Higher cost", "Slower response than thermocouples", "Requires I2C"],
tco5Year: 25
},
{
id: "bmp388",
name: "Bosch BMP388 (Barometric Pressure)",
shortName: "BMP388",
description: "High-precision MEMS barometric pressure sensor with temperature compensation. Low power, small footprint. SPI/I2C interface.",
icon: "💨",
category: "Environment",
characteristics: {
accuracy: 5,
cost: 4,
power: 5,
range: 4,
reliability: 4
},
specs: {
pressureRange: "300-1250 hPa",
pressureAccuracy: "±0.33 hPa",
tempRange: "-40 to 85°C",
tempAccuracy: "±0.5°C",
powerConsumption: "3.4 µA @ 1Hz",
responseTime: "Instant",
interface: "I2C/SPI",
lifespan: "10 years",
calibrationNeeded: "Factory calibrated"
},
pricing: {
unitCost: 8,
maintenanceYearly: 0,
replacementYears: 10,
calibrationYearly: 0
},
bestFor: ["Weather stations", "Altitude measurement", "IoT devices", "Battery-powered"],
limitations: ["Temperature drift needs compensation", "MEMS sensitivity to shock", "Limited pressure range"],
tco5Year: 8
},
{
id: "mq135",
name: "MQ-135 (Air Quality - Gas)",
shortName: "MQ-135",
description: "Analog gas sensor detecting CO2, NH3, NOx, benzene. Requires preheating, analog output needs calibration. Low cost but high power.",
icon: "💨",
category: "Gas",
characteristics: {
accuracy: 2,
cost: 5,
power: 2,
range: 3,
reliability: 2
},
specs: {
detects: "CO2, NH3, NOx, Benzene",
sensitivity: "10-1000 ppm (varies)",
accuracy: "±20-50% (uncalibrated)",
powerConsumption: "150 mA (heater)",
responseTime: "30 seconds",
interface: "Analog voltage",
lifespan: "1-2 years",
calibrationNeeded: "Regular calibration required"
},
pricing: {
unitCost: 3,
maintenanceYearly: 20,
replacementYears: 2,
calibrationYearly: 50
},
bestFor: ["General air quality indication", "Cost-sensitive projects", "Relative measurements"],
limitations: ["Poor accuracy", "High power consumption", "Frequent calibration", "Cross-sensitivity"],
tco5Year: 406
},
{
id: "ccs811",
name: "CCS811 (eCO2/TVOC Sensor)",
shortName: "CCS811",
description: "Digital gas sensor measuring equivalent CO2 and total VOCs. Low power, I2C interface. Calibrated algorithm, but limited accuracy.",
icon: "💨",
category: "Gas",
characteristics: {
accuracy: 3,
cost: 4,
power: 4,
range: 3,
reliability: 3
},
specs: {
detects: "eCO2, TVOC",
eCO2Range: "400-8192 ppm",
TVOCRange: "0-1187 ppb",
accuracy: "±50 ppm (eCO2)",
powerConsumption: "1.2 mA active",
responseTime: "60 seconds",
interface: "I2C digital",
lifespan: "3-5 years",
calibrationNeeded: "Baseline calibration"
},
pricing: {
unitCost: 12,
maintenanceYearly: 0,
replacementYears: 4,
calibrationYearly: 0
},
bestFor: ["Indoor air quality", "Smart HVAC", "Energy-efficient monitoring"],
limitations: ["Not suitable for safety-critical", "Baseline drift", "Indirect measurement"],
tco5Year: 24
},
{
id: "scd40",
name: "Sensirion SCD40 (True CO2)",
shortName: "SCD40",
description: "NDIR CO2 sensor with photoacoustic measurement. Factory-calibrated, high accuracy, long-term stability. I2C interface.",
icon: "💨",
category: "Gas",
characteristics: {
accuracy: 5,
cost: 2,
power: 3,
range: 4,
reliability: 5
},
specs: {
detects: "CO2 (NDIR)",
co2Range: "400-2000 ppm",
accuracy: "±40 ppm ±5%",
powerConsumption: "18 mA active",
responseTime: "60 seconds",
interface: "I2C digital",
lifespan: "10+ years",
calibrationNeeded: "Auto-calibration"
},
pricing: {
unitCost: 50,
maintenanceYearly: 0,
replacementYears: 10,
calibrationYearly: 0
},
bestFor: ["Accurate CO2 monitoring", "Ventilation control", "Research-grade", "Regulatory compliance"],
limitations: ["Higher cost", "Slower response", "Requires periodic baseline"],
tco5Year: 50
},
{
id: "bh1750",
name: "BH1750 (Ambient Light)",
shortName: "BH1750",
description: "Digital ambient light sensor with spectral response close to human eye. I2C interface, low power, wide dynamic range.",
icon: "💡",
category: "Light",
characteristics: {
accuracy: 4,
cost: 5,
power: 5,
range: 5,
reliability: 4
},
specs: {
range: "1-65535 lux",
accuracy: "±20% typical",
spectralResponse: "Close to human eye",
powerConsumption: "120 µA active",
responseTime: "120 ms",
interface: "I2C digital",
lifespan: "10 years",
calibrationNeeded: "Factory calibrated"
},
pricing: {
unitCost: 2,
maintenanceYearly: 0,
replacementYears: 10,
calibrationYearly: 0
},
bestFor: ["Automatic lighting control", "Display brightness", "Energy saving", "General lighting"],
limitations: ["Not suitable for UV/IR", "Temperature drift", "Saturation in direct sunlight"],
tco5Year: 2
},
{
id: "tsl2591",
name: "TSL2591 (High-Dynamic Light)",
shortName: "TSL2591",
description: "Advanced light sensor with ultra-wide dynamic range. Separate IR and visible channels. I2C interface, programmable gain.",
icon: "💡",
category: "Light",
characteristics: {
accuracy: 5,
cost: 4,
power: 4,
range: 5,
reliability: 5
},
specs: {
range: "0.000188-88000 lux",
accuracy: "±10% typical",
channels: "Visible + IR",
powerConsumption: "400 µA active",
responseTime: "100 ms",
interface: "I2C digital",
lifespan: "10+ years",
calibrationNeeded: "Factory calibrated"
},
pricing: {
unitCost: 8,
maintenanceYearly: 0,
replacementYears: 10,
calibrationYearly: 0
},
bestFor: ["Outdoor monitoring", "Solar tracking", "Research applications", "Wide range needed"],
limitations: ["Higher cost than BH1750", "Complex configuration", "IR compensation needed"],
tco5Year: 8
},
{
id: "hcsr04",
name: "HC-SR04 (Ultrasonic Distance)",
shortName: "HC-SR04",
description: "Low-cost ultrasonic ranging sensor. Digital interface, simple operation. Affected by temperature and obstacles.",
icon: "📏",
category: "Motion/Distance",
characteristics: {
accuracy: 2,
cost: 5,
power: 3,
range: 3,
reliability: 2
},
specs: {
range: "2-400 cm",
accuracy: "±3 mm",
beamAngle: "15°",
powerConsumption: "15 mA active",
responseTime: "Trigger-based",
interface: "Digital pulse",
lifespan: "3-5 years",
calibrationNeeded: "Temperature compensation"
},
pricing: {
unitCost: 2,
maintenanceYearly: 0,
replacementYears: 4,
calibrationYearly: 10
},
bestFor: ["Object detection", "Parking sensors", "Tank level", "Robotics"],
limitations: ["Temperature sensitive", "Soft materials absorb ultrasound", "Narrow beam"],
tco5Year: 52
},
{
id: "vl53l1x",
name: "VL53L1X (ToF Distance)",
shortName: "VL53L1X",
description: "Time-of-flight laser ranging sensor. I2C interface, immune to ambient light. Precise, fast, and temperature-stable.",
icon: "📏",
category: "Motion/Distance",
characteristics: {
accuracy: 5,
cost: 3,
power: 4,
range: 4,
reliability: 5
},
specs: {
range: "40-4000 mm",
accuracy: "±25 mm @ 3m",
fieldOfView: "27°",
powerConsumption: "19 mA active",
responseTime: "20-50 ms",
interface: "I2C digital",
lifespan: "10+ years",
calibrationNeeded: "Factory calibrated"
},
pricing: {
unitCost: 15,
maintenanceYearly: 0,
replacementYears: 10,
calibrationYearly: 0
},
bestFor: ["Precise ranging", "Gesture detection", "Robotics", "Industrial automation"],
limitations: ["Shorter range than ultrasonic", "Affected by reflectivity", "Higher cost"],
tco5Year: 15
},
{
id: "mpu6050",
name: "MPU-6050 (6-Axis IMU)",
shortName: "MPU-6050",
description: "6-axis accelerometer and gyroscope. I2C interface, integrated DMP. Low cost, popular in consumer electronics.",
icon: "🎯",
category: "Motion/Distance",
characteristics: {
accuracy: 3,
cost: 5,
power: 4,
range: 4,
reliability: 3
},
specs: {
accelerometer: "±2/4/8/16g",
gyroscope: "±250/500/1000/2000°/s",
accuracy: "±1% (accel), ±3% (gyro)",
powerConsumption: "3.9 mA active",
responseTime: "1 kHz output",
interface: "I2C digital",
lifespan: "5-7 years",
calibrationNeeded: "Gyro drift compensation"
},
pricing: {
unitCost: 5,
maintenanceYearly: 0,
replacementYears: 6,
calibrationYearly: 0
},
bestFor: ["Motion tracking", "Gaming", "Drones", "Wearables"],
limitations: ["Gyro drift over time", "Temperature sensitivity", "Consumer-grade accuracy"],
tco5Year: 5
},
{
id: "bno055",
name: "BNO055 (9-Axis Fusion)",
shortName: "BNO055",
description: "9-axis absolute orientation sensor with sensor fusion. Integrated ARM processor. I2C/UART, auto-calibration.",
icon: "🎯",
category: "Motion/Distance",
characteristics: {
accuracy: 5,
cost: 3,
power: 3,
range: 5,
reliability: 5
},
specs: {
sensors: "Accel + Gyro + Mag",
orientation: "Absolute (Euler/Quaternion)",
accuracy: "±1° heading",
powerConsumption: "12.3 mA active",
responseTime: "100 Hz fusion",
interface: "I2C/UART",
lifespan: "10 years",
calibrationNeeded: "Auto-calibration"
},
pricing: {
unitCost: 30,
maintenanceYearly: 0,
replacementYears: 10,
calibrationYearly: 0
},
bestFor: ["Robotics", "AR/VR", "Navigation", "Industrial automation"],
limitations: ["Higher cost", "Magnetic interference", "Complex setup"],
tco5Year: 30
}
]
// ----------------------------------------------------------------------------
// SECTION 2: SCENARIOS BY DIFFICULTY (INCLUDING 5 ADVANCED)
// ----------------------------------------------------------------------------
scenarios = ({
beginner: [
{
id: 1,
title: "Smart Home Temperature Monitor",
description: "You're building a WiFi-enabled temperature and humidity monitor for a living room. The device will log data every 5 minutes and display it on a mobile app. Budget is tight ($20 per unit), and it needs to last 2+ years.",
requirements: {
measurement: "Temperature (-10 to 50°C) and Humidity (20-80% RH)",
accuracy: "±1°C and ±5% RH acceptable",
environment: "Indoor, stable conditions",
power: "USB powered (always on)",
budget: "Low cost priority",
lifespan: "2+ years"
},
optimalSensor: "dht22",
explanation: "DHT22 is optimal because: (1) Integrated temp + humidity in one sensor saves cost and complexity, (2) ±0.5°C accuracy exceeds ±1°C requirement, (3) $5 cost fits tight budget easily, (4) 1-Wire interface simple to integrate with WiFi MCU, (5) 2-3 year lifespan meets requirement, (6) USB power makes 1.5mA consumption negligible. **Why not SHT85?** While more accurate, the $25 cost is 5x higher with no benefit for this non-critical home application.",
alternativeSensors: ["sht85"],
scores: {
dht22: { overall: 95, accuracy: 90, cost: 100, power: 95, reliability: 80 },
sht85: { overall: 75, accuracy: 100, cost: 40, power: 95, reliability: 100 },
bmp388: { overall: 40, accuracy: 60, cost: 70, power: 100, reliability: 90 }
}
},
{
id: 2,
title: "Automatic Room Lighting Control",
description: "Office building wants automatic lights that turn on when people enter dark rooms. Sensor must detect low light (<50 lux) reliably and adjust brightness. 200 units needed, battery backup power.",
requirements: {
measurement: "Ambient light (1-10000 lux range)",
accuracy: "±20% acceptable",
environment: "Indoor office, fluorescent lights",
power: "Low power critical (battery backup)",
budget: "Moderate ($500 total for 200 sensors)",
lifespan: "10 years"
},
optimalSensor: "bh1750",
explanation: "BH1750 is optimal because: (1) Wide 1-65535 lux range covers dark (1 lux) to bright office (1000 lux), (2) $2 cost allows 200 sensors within $500 budget ($400 total), (3) 120µA power consumption critical for battery backup, (4) I2C interface standard on building automation controllers, (5) 10-year lifespan avoids frequent maintenance. **Why not TSL2591?** While more capable, the $8 cost (4x) exceeds budget, and wide dynamic range is overkill for indoor lighting.",
alternativeSensors: ["tsl2591"],
scores: {
bh1750: { overall: 98, accuracy: 90, cost: 100, power: 100, reliability: 95 },
tsl2591: { overall: 70, accuracy: 100, cost: 60, power: 95, reliability: 100 }
}
},
{
id: 3,
title: "Parking Spot Occupancy Detector",
description: "Shopping mall needs 50 ultrasonic sensors to detect car presence in parking spots (2-3m range). Display available spots on LED board. Outdoor installation, temperature varies 0-40°C.",
requirements: {
measurement: "Distance 0.5-3 meters",
accuracy: "±5 cm acceptable",
environment: "Outdoor parking, temp swings",
power: "Wired power available",
budget: "Low cost ($150 total)",
lifespan: "3+ years"
},
optimalSensor: "hcsr04",
explanation: "HC-SR04 is optimal because: (1) 2-400cm range perfectly covers parking spot detection (2-3m), (2) $2 cost allows 50 sensors within $150 budget ($100 total), (3) Simple trigger/echo interface easy to multiplex, (4) 15mA power acceptable with wired supply, (5) ±3mm accuracy far exceeds ±5cm requirement. **Why not VL53L1X?** ToF laser is 7x more expensive ($15 vs $2) and provides excessive precision for simple car/no-car detection. Temperature compensation is doable with software.",
alternativeSensors: ["vl53l1x"],
scores: {
hcsr04: { overall: 95, accuracy: 100, cost: 100, power: 85, reliability: 75 },
vl53l1x: { overall: 65, accuracy: 100, cost: 45, power: 90, reliability: 100 }
}
},
{
id: 4,
title: "Fitness Tracker Motion Sensor",
description: "Wearable fitness band needs to count steps, detect running vs. walking, and track sleep. Must be ultra-low power (coin cell battery, 1 year life). Small form factor required.",
requirements: {
measurement: "Acceleration and rotation",
accuracy: "±5% step counting",
environment: "Body temperature, low vibration",
power: "Ultra-low power (<1mA average)",
budget: "Moderate ($10-15 per unit)",
lifespan: "Device lifetime (3 years)"
},
optimalSensor: "mpu6050",
explanation: "MPU-6050 is optimal because: (1) 6-axis (accel + gyro) enables step counting, activity classification, and sleep tracking with one sensor, (2) $5 cost fits moderate budget with margin for other components, (3) 3.9mA active power acceptable when duty-cycled (sleep 90% of time → <0.5mA average), (4) Integrated DMP offloads processing from MCU, saving more power, (5) Small package size critical for wearables. **Why not BNO055?** Magnetometer adds cost ($30 vs $5) with no benefit for fitness tracking, and power is higher (12.3mA).",
alternativeSensors: ["bno055"],
scores: {
mpu6050: { overall: 95, accuracy: 85, cost: 95, power: 90, reliability: 80 },
bno055: { overall: 70, accuracy: 100, cost: 50, power: 75, reliability: 100 }
}
}
],
intermediate: [
{
id: 5,
title: "Industrial HVAC CO2 Monitoring",
description: "Factory HVAC system needs to maintain indoor CO2 <1000 ppm for worker safety and productivity. 10 sensors across factory floor. Must be accurate for regulatory compliance and energy-efficient ventilation control.",
requirements: {
measurement: "CO2 concentration (400-2000 ppm)",
accuracy: "±50 ppm required",
environment: "Industrial, temp 15-35°C",
power: "24V DC available",
budget: "Good budget ($1000 for 10 sensors)",
lifespan: "10 years, low maintenance"
},
optimalSensor: "scd40",
explanation: "SCD40 is optimal because: (1) True NDIR CO2 measurement (not estimated like CCS811), (2) ±40ppm ±5% accuracy meets ±50ppm regulatory requirement, (3) 10+ year lifespan with auto-calibration minimizes maintenance, (4) $50 cost allows 10 sensors within $1000 budget ($500 total), (5) Factory calibration and long-term stability critical for compliance. **Why not CCS811?** eCO2 estimation from TVOC is inaccurate (±50ppm spec, but real error often ±100ppm), and baseline drift requires frequent recalibration. MQ-135 is even worse (±20-50% error).",
alternativeSensors: ["ccs811", "mq135"],
scores: {
scd40: { overall: 98, accuracy: 100, cost: 85, power: 80, reliability: 100 },
ccs811: { overall: 65, accuracy: 60, cost: 90, power: 95, reliability: 70 },
mq135: { overall: 40, accuracy: 30, cost: 100, power: 50, reliability: 40 }
}
},
{
id: 6,
title: "Weather Station Network",
description: "City deploys 50 outdoor weather stations measuring temperature, humidity, and barometric pressure. Data used for weather forecasting. Must handle -20 to +50°C, rain/snow exposure. Solar powered.",
requirements: {
measurement: "Temp, Humidity, Pressure",
accuracy: "±0.2°C, ±2% RH, ±1 hPa",
environment: "Outdoor, extreme conditions",
power: "Solar (low power critical)",
budget: "Moderate ($75 per station)",
lifespan: "10 years outdoor"
},
optimalSensor: "sht85",
explanation: "SHT85 + BMP388 combination is optimal because: (1) SHT85 provides ±0.1°C temp and ±1.5% humidity (exceeds spec), (2) BMP388 provides ±0.33 hPa pressure (exceeds ±1 hPa), (3) Combined cost $25 + $8 = $33, well under $75 budget, (4) Both are ultra-low power (600µA + 3.4µA = <1mA average) critical for solar, (5) Both rated -40 to +85°C and 10+ year lifespan for outdoor deployment. **Why not DHT22?** Fails outdoor: poor accuracy (±0.5°C, ±2%), 2-year lifespan, and -40°C operation not guaranteed.",
alternativeSensors: ["dht22", "bmp388"],
scores: {
sht85: { overall: 97, accuracy: 100, cost: 90, power: 100, reliability: 100 },
dht22: { overall: 50, accuracy: 60, cost: 100, power: 95, reliability: 40 },
bmp388: { overall: 75, accuracy: 90, cost: 95, power: 100, reliability: 95 }
}
},
{
id: 7,
title: "Drone Obstacle Avoidance",
description: "Racing drone needs forward-facing distance sensor to avoid trees/walls at 20 mph (9 m/s). Must measure 0.5-4m range, update 20Hz+, and work in bright sunlight and darkness. Weight critical (<20g).",
requirements: {
measurement: "Distance 0.5-4 meters",
accuracy: "±5 cm",
environment: "Outdoor, all lighting conditions",
power: "Battery powered (efficiency matters)",
budget: "Moderate ($30 per drone)",
responseTime: "<50ms (20Hz update)"
},
optimalSensor: "vl53l1x",
explanation: "VL53L1X is optimal because: (1) ToF laser works in full sunlight (unlike ultrasonic which fails outdoors), (2) 40-4000mm range perfectly covers obstacle detection at speed, (3) 20-50ms response time achieves 20Hz update rate, (4) Immune to ambient light (critical for racing drones), (5) $15 cost fits budget, (6) 19mA power acceptable for 5-10 minute drone flights, (7) Lightweight sensor (<2g). **Why not HC-SR04?** Ultrasonic fails outdoors (wind interference), too slow (trigger-based, not continuous), and temperature-sensitive.",
alternativeSensors: ["hcsr04"],
scores: {
vl53l1x: { overall: 98, accuracy: 100, cost: 85, power: 90, reliability: 100 },
hcsr04: { overall: 45, accuracy: 70, cost: 100, power: 85, reliability: 40 }
}
},
{
id: 8,
title: "Robot Arm Orientation Tracking",
description: "6-axis robot arm needs precise orientation feedback for each joint (6 sensors total). Industrial application requires ±1° accuracy, 100Hz update rate, and immune to magnetic interference from motors.",
requirements: {
measurement: "3D orientation (Euler angles)",
accuracy: "±1° absolute heading",
environment: "Factory, strong magnetic fields",
power: "Wired power available",
budget: "Good ($300 for 6 sensors)",
responseTime: "100 Hz fusion output"
},
optimalSensor: "bno055",
explanation: "BNO055 is optimal because: (1) 9-axis fusion provides absolute orientation (not just relative motion like MPU-6050), (2) ±1° heading accuracy meets spec exactly, (3) 100Hz fusion output matches required update rate, (4) Integrated ARM processor offloads sensor fusion from robot controller, (5) $30 cost allows 6 sensors within $300 budget ($180 total), (6) Auto-calibration critical for industrial reliability. **Why not MPU-6050?** Only provides relative motion (gyro integrates to drift), no absolute heading, and 6-axis can't determine yaw without magnetometer.",
alternativeSensors: ["mpu6050"],
scores: {
bno055: { overall: 97, accuracy: 100, cost: 80, power: 85, reliability: 100 },
mpu6050: { overall: 55, accuracy: 60, cost: 95, power: 95, reliability: 70 }
}
}
],
advanced: [
{
id: 9,
title: "Semiconductor Cleanroom Monitoring",
description: "ISO Class 3 cleanroom for chip fabrication requires ultra-precise temp (±0.05°C) and humidity (±1% RH) control. Even 0.1°C drift causes wafer defects ($100k+ loss). 20 sensors, critical reliability.",
requirements: {
measurement: "Temperature and Humidity",
accuracy: "±0.05°C, ±1% RH (critical)",
environment: "Cleanroom, 20-22°C stable",
power: "Wired power",
budget: "High ($1500 for 20 sensors)",
lifespan: "10 years, zero drift"
},
optimalSensor: "sht85",
explanation: "SHT85 is optimal (even mandatory) because: (1) ±0.1°C accuracy is the ONLY sensor that meets ±0.05°C after calibration, (2) ±1.5% RH achieves ±1% spec, (3) Factory-calibrated with 10-year stability ensures zero drift (critical for $100k wafer batches), (4) $25 cost allows 20 sensors within $1500 budget ($500 total), (5) I2C allows networked monitoring with central control. **Why not DHT22?** Completely inadequate: ±0.5°C is 10x worse than spec, and 2-year lifespan with drift is unacceptable. This is the rare case where only one sensor works.",
alternativeSensors: ["dht22"],
scores: {
sht85: { overall: 100, accuracy: 100, cost: 90, power: 100, reliability: 100 },
dht22: { overall: 20, accuracy: 20, cost: 100, power: 95, reliability: 30 }
}
},
{
id: 10,
title: "Solar Panel Sun Tracking System",
description: "Utility-scale solar farm needs light sensors to track sun position across 0.01-100,000 lux range (pre-dawn to midday). Must measure both visible and IR. Outdoor -30 to +60°C. 100 dual-axis trackers.",
requirements: {
measurement: "Light intensity (wide dynamic range)",
accuracy: "±10% across 0.01-100k lux",
environment: "Outdoor, extreme temp/sun",
power: "Solar powered (low power)",
budget: "Moderate ($2000 for 100 sensors)",
lifespan: "15 years outdoor"
},
optimalSensor: "tsl2591",
explanation: "TSL2591 is optimal because: (1) Ultra-wide 0.000188-88000 lux range captures pre-dawn to direct sun (BH1750 saturates >65k lux), (2) Separate IR and visible channels enable true sun tracking (compensate for clouds), (3) $8 cost allows 100 sensors within $2000 budget ($800 total), (4) 400µA power acceptable with solar, (5) 10+ year lifespan for outdoor. **Why not BH1750?** Saturates in direct sunlight (>65k lux), can't distinguish IR, and less accurate tracking (±20% vs ±10%). For solar farms, TSL2591's extra cost is justified by 5-10% energy gain.",
alternativeSensors: ["bh1750"],
scores: {
tsl2591: { overall: 98, accuracy: 100, cost: 85, power: 95, reliability: 100 },
bh1750: { overall: 60, accuracy: 70, cost: 100, power: 100, reliability: 90 }
}
},
{
id: 11,
title: "Autonomous Warehouse Robot Navigation",
description: "100 warehouse robots need precise 3D orientation for SLAM (simultaneous localization and mapping). ±0.5° heading accuracy required. Magnetic interference from metal shelves and forklifts. 50Hz update minimum.",
requirements: {
measurement: "9-axis orientation (SLAM)",
accuracy: "±0.5° heading",
environment: "Indoor, strong magnetic interference",
power: "Battery powered (8 hour shifts)",
budget: "High ($5000 for 100 robots)",
responseTime: "50 Hz minimum"
},
optimalSensor: "bno055",
explanation: "BNO055 is optimal because: (1) 9-axis fusion with magnetometer provides absolute heading for SLAM (MPU-6050 drifts in minutes), (2) ±1° heading accuracy achievable with calibration (close to ±0.5° spec), (3) 100Hz fusion output exceeds 50Hz requirement with margin, (4) $30 cost allows 100 sensors within $5000 budget ($3000 total), (5) Auto-calibration compensates for warehouse magnetic interference, (6) 12.3mA power acceptable for 8-hour battery operation with sleep modes. **Why not MPU-6050?** Gyro drift makes absolute heading impossible for long-duration SLAM - robots would lose position within 10 minutes.",
alternativeSensors: ["mpu6050"],
scores: {
bno055: { overall: 97, accuracy: 95, cost: 80, power: 85, reliability: 100 },
mpu6050: { overall: 50, accuracy: 60, cost: 95, power: 95, reliability: 60 }
}
},
{
id: 12,
title: "Research-Grade Air Quality Station",
description: "University atmospheric research station needs ppb-level gas detection (CO2, VOCs) for climate studies. Data published in peer-reviewed papers. Outdoor -40 to +50°C. Grant funding available. 10-year deployment.",
requirements: {
measurement: "CO2 (±10 ppm) and VOC",
accuracy: "Research-grade (±1%)",
environment: "Outdoor research station",
power: "Solar + battery backup",
budget: "Research grant ($2000 per station)",
lifespan: "10 years, minimal maintenance"
},
optimalSensor: "scd40",
explanation: "SCD40 is optimal because: (1) True NDIR CO2 with ±40ppm accuracy far exceeds research-grade needs, (2) 10+ year lifespan with auto-calibration minimizes maintenance at remote station, (3) $50 cost is negligible with research grant budget, (4) Factory calibration and long-term stability critical for publishable data, (5) 18mA power acceptable with solar + battery backup. **Pair with CCS811 for TVOC** to get both CO2 and VOC (total $62). **Why not MQ-135?** Completely inadequate for research: ±20-50% error, cross-sensitivity, frequent calibration, and short lifespan make data unusable for publications.",
alternativeSensors: ["ccs811", "mq135"],
scores: {
scd40: { overall: 98, accuracy: 100, cost: 95, power: 85, reliability: 100 },
ccs811: { overall: 70, accuracy: 70, cost: 95, power: 95, reliability: 80 },
mq135: { overall: 25, accuracy: 20, cost: 100, power: 60, reliability: 30 }
}
},
{
id: 13,
title: "High-Speed Production Line Inspection",
description: "Automotive parts move at 2 m/s on conveyor belt. ToF sensor must measure part height (5-50mm range) with ±0.5mm accuracy to detect defects. 100 measurements/second. Industrial environment with vibration and dust.",
requirements: {
measurement: "Distance 5-50 mm",
accuracy: "±0.5 mm (high precision)",
environment: "Industrial, vibration, dust",
power: "24V industrial power",
budget: "Industrial budget ($100 per sensor)",
responseTime: "100 Hz (10 ms)"
},
optimalSensor: "vl53l1x",
explanation: "VL53L1X is optimal because: (1) ToF laser achieves ±25mm @ 3m accuracy, which scales to <±0.5mm at 50mm range, (2) 20-50ms response time allows 100Hz measurement rate for fast-moving parts, (3) Laser-based measurement immune to ambient light and vibration (unlike ultrasonic), (4) $15 cost fits industrial budget well, (5) I2C interface standard in industrial PLCs, (6) No moving parts for reliability in dusty environments. **Why not HC-SR04?** Ultrasonic too slow (trigger-based), insufficient accuracy, and sensitive to vibration/temperature in factory. This application demands laser precision.",
alternativeSensors: ["hcsr04"],
scores: {
vl53l1x: { overall: 98, accuracy: 100, cost: 90, power: 95, reliability: 100 },
hcsr04: { overall: 40, accuracy: 50, cost: 100, power: 90, reliability: 40 }
}
}
]
})
// ----------------------------------------------------------------------------
// SECTION 3: GAME STATE MANAGEMENT
// ----------------------------------------------------------------------------
mutable gameState = ({
difficulty: "beginner",
currentScenarioIndex: 0,
score: 0,
answered: false,
selectedSensor: null,
correctAnswers: 0,
totalQuestions: 0,
gameComplete: false,
showFeedback: false,
showComparison: false,
showDatasheet: false,
showCostCalculator: false,
showPerformanceCharts: false,
comparisonSensors: []
})
// Get current scenario
currentScenario = scenarios[gameState.difficulty][gameState.currentScenarioIndex]
// Calculate total scenarios for current difficulty
totalScenarios = scenarios[gameState.difficulty].length
// Get relevant sensors for current scenario
relevantSensors = currentScenario ?
sensors.filter(s =>
s.id === currentScenario.optimalSensor ||
currentScenario.alternativeSensors.includes(s.id)
) : []
// ----------------------------------------------------------------------------
// SECTION 4: GAME LOGIC FUNCTIONS
// ----------------------------------------------------------------------------
function selectSensor(sensorId) {
if (gameState.answered) return;
mutable gameState = { ...gameState, selectedSensor: sensorId };
}
function submitAnswer() {
if (!gameState.selectedSensor || gameState.answered) return;
const isCorrect = gameState.selectedSensor === currentScenario.optimalSensor;
const scores = currentScenario.scores[gameState.selectedSensor];
const points = Math.round(scores.overall);
mutable gameState = {
...gameState,
answered: true,
showFeedback: true,
score: gameState.score + points,
correctAnswers: isCorrect ? gameState.correctAnswers + 1 : gameState.correctAnswers,
totalQuestions: gameState.totalQuestions + 1
};
}
function toggleComparison() {
if (!gameState.answered) return;
const allSensors = [currentScenario.optimalSensor, ...currentScenario.alternativeSensors];
mutable gameState = {
...gameState,
showComparison: !gameState.showComparison,
comparisonSensors: allSensors
};
}
function toggleDatasheet() {
if (!gameState.selectedSensor) return;
mutable gameState = { ...gameState, showDatasheet: !gameState.showDatasheet };
}
function toggleCostCalculator() {
if (!gameState.selectedSensor) return;
mutable gameState = { ...gameState, showCostCalculator: !gameState.showCostCalculator };
}
function togglePerformanceCharts() {
if (!gameState.answered) return;
mutable gameState = { ...gameState, showPerformanceCharts: !gameState.showPerformanceCharts };
}
function nextScenario() {
const nextIndex = gameState.currentScenarioIndex + 1;
if (nextIndex >= scenarios[gameState.difficulty].length) {
mutable gameState = { ...gameState, gameComplete: true };
} else {
mutable gameState = {
...gameState,
currentScenarioIndex: nextIndex,
answered: false,
selectedSensor: null,
showFeedback: false,
showComparison: false,
showDatasheet: false,
showCostCalculator: false,
showPerformanceCharts: false,
comparisonSensors: []
};
}
}
function changeDifficulty(difficulty) {
mutable gameState = {
difficulty: difficulty,
currentScenarioIndex: 0,
score: 0,
answered: false,
selectedSensor: null,
correctAnswers: 0,
totalQuestions: 0,
gameComplete: false,
showFeedback: false,
showComparison: false,
showDatasheet: false,
showCostCalculator: false,
showPerformanceCharts: false,
comparisonSensors: []
};
}
function restartGame() {
mutable gameState = {
...gameState,
currentScenarioIndex: 0,
score: 0,
answered: false,
selectedSensor: null,
correctAnswers: 0,
totalQuestions: 0,
gameComplete: false,
showFeedback: false,
showComparison: false,
showDatasheet: false,
showCostCalculator: false,
showPerformanceCharts: false,
comparisonSensors: []
};
}
function calculateTCO(sensor, years = 5, quantity = 1) {
const replacements = Math.floor(years / sensor.pricing.replacementYears);
const unitCosts = (replacements + 1) * sensor.pricing.unitCost;
const maintenance = years * sensor.pricing.maintenanceYearly;
const calibration = years * sensor.pricing.calibrationYearly;
const total = (unitCosts + maintenance + calibration) * quantity;
return {
unitCosts,
maintenance,
calibration,
total,
perYear: total / years
};
}
// ----------------------------------------------------------------------------
// SECTION 5: UI RENDERING
// ----------------------------------------------------------------------------
viewof sensorGame = {
const container = html`<div class="sensor-game-container">
<div class="game-header">
<h2>🔬 Sensor Selection Challenge</h2>
<p>Choose the optimal sensor for real-world IoT scenarios</p>
</div>
<div class="game-controls">
<button class="difficulty-btn beginner ${gameState.difficulty === 'beginner' ? 'active' : ''}"
onclick=${() => changeDifficulty('beginner')}>
🌱 Beginner
</button>
<button class="difficulty-btn intermediate ${gameState.difficulty === 'intermediate' ? 'active' : ''}"
onclick=${() => changeDifficulty('intermediate')}>
🔥 Intermediate
</button>
<button class="difficulty-btn advanced ${gameState.difficulty === 'advanced' ? 'active' : ''}"
onclick=${() => changeDifficulty('advanced')}>
💎 Advanced
</button>
</div>
<div class="progress-bar">
<div class="progress-fill" style="width: ${(gameState.currentScenarioIndex / totalScenarios) * 100}%"></div>
</div>
<div class="game-stats">
<div class="stat-card">
<div class="stat-value">${gameState.score}</div>
<div class="stat-label">Score</div>
</div>
<div class="stat-card">
<div class="stat-value">${gameState.currentScenarioIndex + 1}/${totalScenarios}</div>
<div class="stat-label">Scenario</div>
</div>
<div class="stat-card">
<div class="stat-value">${gameState.correctAnswers}/${gameState.totalQuestions}</div>
<div class="stat-label">Optimal</div>
</div>
<div class="stat-card">
<div class="stat-value">${gameState.totalQuestions > 0 ? Math.round((gameState.correctAnswers / gameState.totalQuestions) * 100) : 0}%</div>
<div class="stat-label">Accuracy</div>
</div>
<div class="stat-card">
<div class="stat-value">${gameState.totalQuestions > 0 ? Math.round(gameState.score / gameState.totalQuestions) : 0}</div>
<div class="stat-label">Avg Score</div>
</div>
</div>
<div id="game-content"></div>
</div>`;
const gameContent = container.querySelector("#game-content");
if (gameState.gameComplete) {
renderGameComplete(gameContent);
} else {
renderScenario(gameContent);
}
return container;
}
function renderScenario(container) {
const scenario = currentScenario;
container.innerHTML = `
<div class="scenario-card">
<div class="scenario-header">
<div class="scenario-title">${scenario.title}</div>
<div class="scenario-badge">Scenario ${scenario.id}</div>
</div>
<div class="scenario-description">
${scenario.description}
</div>
<div class="requirements-grid">
${Object.entries(scenario.requirements).map(([key, value]) => `
<div class="requirement-item">
<div class="requirement-label">${key.charAt(0).toUpperCase() + key.slice(1)}</div>
<div class="requirement-value">${value}</div>
</div>
`).join('')}
</div>
<div style="margin-bottom: 12px;">
<strong>Select the best sensor for this scenario:</strong>
</div>
<div class="sensor-options">
${relevantSensors.map(sensor => {
let btnClass = 'sensor-btn';
if (gameState.selectedSensor === sensor.id && !gameState.answered) {
btnClass += ' selected';
} else if (gameState.answered && sensor.id === scenario.optimalSensor) {
btnClass += ' correct';
} else if (gameState.answered && gameState.selectedSensor === sensor.id && sensor.id !== scenario.optimalSensor) {
btnClass += ' incorrect';
}
const scores = scenario.scores[sensor.id];
return `
<button class="${btnClass}"
onclick="selectSensor('${sensor.id}')"
${gameState.answered ? 'disabled' : ''}>
<div class="sensor-info">
<div class="sensor-details">
<div class="sensor-name">${sensor.icon} ${sensor.name}</div>
<div class="sensor-description">${sensor.description}</div>
</div>
<div class="sensor-specs-grid">
<div class="spec-mini">
<div class="spec-mini-label">Accuracy</div>
<div class="spec-mini-value">${'⭐'.repeat(sensor.characteristics.accuracy)}</div>
</div>
<div class="spec-mini">
<div class="spec-mini-label">Cost</div>
<div class="spec-mini-value">${'💰'.repeat(sensor.characteristics.cost)}</div>
</div>
<div class="spec-mini">
<div class="spec-mini-label">Power</div>
<div class="spec-mini-value">${'🔋'.repeat(sensor.characteristics.power)}</div>
</div>
<div class="spec-mini">
<div class="spec-mini-label">Reliability</div>
<div class="spec-mini-value">${'✓'.repeat(sensor.characteristics.reliability)}</div>
</div>
</div>
</div>
</button>
`;
}).join('')}
</div>
<div class="mode-toggle">
<button class="mode-btn ${gameState.showDatasheet ? 'active' : ''}"
onclick="toggleDatasheet()"
${!gameState.selectedSensor ? 'disabled' : ''}>
📖 Datasheet
</button>
<button class="mode-btn ${gameState.showCostCalculator ? 'active' : ''}"
onclick="toggleCostCalculator()"
${!gameState.selectedSensor ? 'disabled' : ''}>
💰 Cost (5yr)
</button>
<button class="mode-btn ${gameState.showComparison ? 'active' : ''}"
onclick="toggleComparison()"
${!gameState.answered ? 'disabled' : ''}>
⚖️ Compare
</button>
<button class="mode-btn ${gameState.showPerformanceCharts ? 'active' : ''}"
onclick="togglePerformanceCharts()"
${!gameState.answered ? 'disabled' : ''}>
📊 Charts
</button>
</div>
${gameState.showDatasheet ? renderDatasheet() : ''}
${gameState.showCostCalculator ? renderCostCalculator() : ''}
${gameState.showComparison ? renderComparison() : ''}
${gameState.showPerformanceCharts ? renderPerformanceCharts() : ''}
<div class="action-buttons">
<button class="action-btn submit"
onclick="submitAnswer()"
${!gameState.selectedSensor || gameState.answered ? 'disabled' : ''}>
✓ Submit Answer
</button>
<button class="action-btn next"
onclick="nextScenario()"
${!gameState.answered ? 'disabled' : ''}>
→ Next Scenario
</button>
</div>
<div class="feedback-panel ${gameState.showFeedback ? 'show' : ''} ${gameState.selectedSensor === scenario.optimalSensor ? 'correct' : 'incorrect'}">
${gameState.showFeedback ? renderFeedback() : ''}
</div>
</div>
`;
}
function renderDatasheet() {
const sensor = sensors.find(s => s.id === gameState.selectedSensor);
if (!sensor) return '';
return `
<div class="datasheet-viewer show">
<div class="datasheet-title">📖 ${sensor.name} - Technical Specifications</div>
<div class="datasheet-content">
<div class="datasheet-section">
<div class="datasheet-section-title">Measurement Specifications</div>
${Object.entries(sensor.specs).slice(0, 5).map(([key, value]) => `
<div class="datasheet-item">
<span style="color: #555;">${key.replace(/([A-Z])/g, ' $1').trim()}:</span>
<span style="font-weight: 600;">${value}</span>
</div>
`).join('')}
</div>
<div class="datasheet-section">
<div class="datasheet-section-title">Operational Details</div>
${Object.entries(sensor.specs).slice(5).map(([key, value]) => `
<div class="datasheet-item">
<span style="color: #555;">${key.replace(/([A-Z])/g, ' $1').trim()}:</span>
<span style="font-weight: 600;">${value}</span>
</div>
`).join('')}
</div>
<div class="datasheet-section">
<div class="datasheet-section-title">Best For</div>
${sensor.bestFor.map(item => `
<div style="padding: 4px 0; font-size: 0.85em;">✓ ${item}</div>
`).join('')}
</div>
<div class="datasheet-section">
<div class="datasheet-section-title">Limitations</div>
${sensor.limitations.map(item => `
<div style="padding: 4px 0; font-size: 0.85em; color: #c0392b;">⚠ ${item}</div>
`).join('')}
</div>
</div>
</div>
`;
}
function renderCostCalculator() {
const sensor = sensors.find(s => s.id === gameState.selectedSensor);
if (!sensor) return '';
const tco = calculateTCO(sensor, 5, 1);
return `
<div class="cost-calculator show">
<div class="cost-title">💰 Total Cost of Ownership (5 Years, 1 Unit)</div>
<div class="cost-breakdown">
<div class="cost-item">
<div class="cost-item-label">Initial + Replacements</div>
<div class="cost-item-value">$${tco.unitCosts.toFixed(2)}</div>
<div style="font-size: 0.75em; color: #666; margin-top: 4px;">
Replace every ${sensor.pricing.replacementYears} years
</div>
</div>
<div class="cost-item">
<div class="cost-item-label">Maintenance (Yearly)</div>
<div class="cost-item-value">$${tco.maintenance.toFixed(2)}</div>
<div style="font-size: 0.75em; color: #666; margin-top: 4px;">
$${sensor.pricing.maintenanceYearly}/year × 5 years
</div>
</div>
<div class="cost-item">
<div class="cost-item-label">Calibration (Yearly)</div>
<div class="cost-item-value">$${tco.calibration.toFixed(2)}</div>
<div style="font-size: 0.75em; color: #666; margin-top: 4px;">
$${sensor.pricing.calibrationYearly}/year × 5 years
</div>
</div>
<div class="cost-item">
<div class="cost-item-label">Cost per Year</div>
<div class="cost-item-value">$${tco.perYear.toFixed(2)}</div>
<div style="font-size: 0.75em; color: #666; margin-top: 4px;">
Average annual cost
</div>
</div>
</div>
<div class="cost-total">
<div class="cost-total-label">5-Year Total Cost</div>
<div class="cost-total-value">$${tco.total.toFixed(2)}</div>
</div>
<div style="margin-top: 12px; padding: 12px; background: #f8f9fa; border-radius: 6px; font-size: 0.9em; color: #555;">
<strong>Note:</strong> TCO includes initial purchase, replacements based on lifespan, annual maintenance, and calibration costs. Lower TCO doesn't always mean better choice - consider accuracy and reliability requirements.
</div>
</div>
`;
}
function renderComparison() {
const comparisonData = gameState.comparisonSensors.map(sensorId =>
sensors.find(s => s.id === sensorId)
).filter(s => s);
return `
<div class="comparison-panel show">
<div class="comparison-title">⚖️ Side-by-Side Sensor Comparison</div>
<div class="comparison-grid">
${comparisonData.map(sensor => {
const isOptimal = sensor.id === currentScenario.optimalSensor;
const tco = calculateTCO(sensor, 5, 1);
return `
<div class="comparison-card ${isOptimal ? 'highlight' : ''}">
<div class="comparison-card-title">
${sensor.icon} ${sensor.shortName}
${isOptimal ? ' ✅ OPTIMAL' : ''}
</div>
<div class="comparison-specs">
<div class="comparison-spec-item">
<span>Unit Cost</span>
<span style="font-weight: 700;">$${sensor.pricing.unitCost}</span>
</div>
<div class="comparison-spec-item">
<span>5-Year TCO</span>
<span style="font-weight: 700;">$${tco.total.toFixed(0)}</span>
</div>
<div class="comparison-spec-item">
<span>Accuracy</span>
<span>${'⭐'.repeat(sensor.characteristics.accuracy)}</span>
</div>
<div class="comparison-spec-item">
<span>Power Efficiency</span>
<span>${'🔋'.repeat(sensor.characteristics.power)}</span>
</div>
<div class="comparison-spec-item">
<span>Reliability</span>
<span>${'✓'.repeat(sensor.characteristics.reliability)}</span>
</div>
<div class="comparison-spec-item">
<span>Lifespan</span>
<span style="font-weight: 700;">${sensor.specs.lifespan}</span>
</div>
<div class="comparison-spec-item">
<span>Interface</span>
<span style="font-weight: 700;">${sensor.specs.interface}</span>
</div>
</div>
</div>
`;
}).join('')}
</div>
</div>
`;
}
function renderPerformanceCharts() {
const comparisonData = gameState.comparisonSensors.map(sensorId => {
const sensor = sensors.find(s => s.id === sensorId);
const scores = currentScenario.scores[sensorId];
return { sensor, scores };
}).filter(d => d.sensor && d.scores);
return `
<div class="performance-charts show">
<div class="charts-title">📊 Performance Comparison Charts</div>
<div class="chart-row">
<div class="chart-label">
<span>Accuracy Score</span>
<span>Higher is better</span>
</div>
<div class="chart-bars">
${comparisonData.map(({sensor, scores}) => `
<div class="chart-bar">
<div class="chart-bar-label">${sensor.shortName}</div>
<div class="chart-bar-track">
<div class="chart-bar-fill" style="height: ${scores.accuracy}%">
${scores.accuracy}%
</div>
</div>
<div class="chart-bar-value">${scores.accuracy}/100</div>
</div>
`).join('')}
</div>
</div>
<div class="chart-row">
<div class="chart-label">
<span>Cost Efficiency</span>
<span>Higher is better (lower cost)</span>
</div>
<div class="chart-bars">
${comparisonData.map(({sensor, scores}) => `
<div class="chart-bar">
<div class="chart-bar-label">${sensor.shortName}</div>
<div class="chart-bar-track">
<div class="chart-bar-fill" style="height: ${scores.cost}%">
${scores.cost}%
</div>
</div>
<div class="chart-bar-value">${scores.cost}/100</div>
</div>
`).join('')}
</div>
</div>
<div class="chart-row">
<div class="chart-label">
<span>Power Efficiency</span>
<span>Higher is better (lower power)</span>
</div>
<div class="chart-bars">
${comparisonData.map(({sensor, scores}) => `
<div class="chart-bar">
<div class="chart-bar-label">${sensor.shortName}</div>
<div class="chart-bar-track">
<div class="chart-bar-fill" style="height: ${scores.power}%">
${scores.power}%
</div>
</div>
<div class="chart-bar-value">${scores.power}/100</div>
</div>
`).join('')}
</div>
</div>
<div class="chart-row">
<div class="chart-label">
<span>Overall Score</span>
<span>Composite score</span>
</div>
<div class="chart-bars">
${comparisonData.map(({sensor, scores}) => `
<div class="chart-bar">
<div class="chart-bar-label">${sensor.shortName}</div>
<div class="chart-bar-track">
<div class="chart-bar-fill" style="height: ${scores.overall}%">
${scores.overall}%
</div>
</div>
<div class="chart-bar-value">${scores.overall}/100</div>
</div>
`).join('')}
</div>
</div>
</div>
`;
}
function renderFeedback() {
const scenario = currentScenario;
const selectedSensor = sensors.find(s => s.id === gameState.selectedSensor);
const optimalSensor = sensors.find(s => s.id === scenario.optimalSensor);
const isCorrect = gameState.selectedSensor === scenario.optimalSensor;
const scores = scenario.scores[gameState.selectedSensor];
return `
<div class="feedback-title">
${isCorrect ? '✅ Optimal Choice!' : '⚠️ Suboptimal Choice'}
${` +${Math.round(scores.overall)} points`}
</div>
<div class="feedback-content">
<p><strong>Optimal Sensor: ${optimalSensor.name}</strong></p>
<p>${scenario.explanation}</p>
${!isCorrect ? `
<p style="margin-top: 16px;">
<strong>Your Selection (${selectedSensor.name}):</strong><br/>
While this sensor could work, it's not optimal for this scenario. You scored ${Math.round(scores.overall)}/100.
</p>
<ul style="margin: 12px 0; padding-left: 20px; line-height: 1.7;">
<li><strong>Accuracy:</strong> ${scores.accuracy}/100 (Optimal: ${scenario.scores[scenario.optimalSensor].accuracy}/100)</li>
<li><strong>Cost Efficiency:</strong> ${scores.cost}/100 (Optimal: ${scenario.scores[scenario.optimalSensor].cost}/100)</li>
<li><strong>Power Efficiency:</strong> ${scores.power}/100 (Optimal: ${scenario.scores[scenario.optimalSensor].power}/100)</li>
<li><strong>Reliability:</strong> ${scores.reliability || 'N/A'}/100</li>
</ul>
` : ''}
<p style="margin-top: 16px;">
<strong>Key Lesson:</strong> Sensor selection requires balancing accuracy, cost, power consumption, reliability, and application-specific requirements. The "best" sensor depends entirely on the use case context.
</p>
</div>
`;
}
function renderGameComplete(container) {
const accuracy = gameState.totalQuestions > 0 ? Math.round((gameState.correctAnswers / gameState.totalQuestions) * 100) : 0;
const maxScore = scenarios[gameState.difficulty].length * 95;
const scorePercentage = Math.round((gameState.score / maxScore) * 100);
let feedback, emoji;
if (accuracy >= 90 && scorePercentage >= 90) {
feedback = "Outstanding! You're a sensor selection expert! You understand how to balance technical requirements with practical constraints.";
emoji = "🏆";
} else if (accuracy >= 75 && scorePercentage >= 75) {
feedback = "Great job! You have strong sensor selection skills. Review the feedback to refine your decision-making.";
emoji = "🎉";
} else if (accuracy >= 50 && scorePercentage >= 60) {
feedback = "Good effort! You're learning the trade-offs. Study the explanations to understand why certain sensors are optimal.";
emoji = "👍";
} else {
feedback = "Keep learning! Sensor selection is complex. Review the scenarios and try again to master the concepts.";
emoji = "📚";
}
container.innerHTML = `
<div class="game-complete">
<div class="complete-icon">${emoji}</div>
<div class="complete-title">Challenge Complete!</div>
<div class="complete-score">
Final Score: ${gameState.score} / ${maxScore} (${scorePercentage}%)
</div>
<div class="complete-score">
Optimal Choices: ${gameState.correctAnswers} / ${gameState.totalQuestions} (${accuracy}%)
</div>
<div class="complete-feedback">${feedback}</div>
<div style="margin: 24px 0; text-align: left; max-width: 800px; margin-left: auto; margin-right: auto; background: #f8f9fa; padding: 20px; border-radius: 10px;">
<h3 style="color: #2C3E50; margin-bottom: 16px;">What You've Learned</h3>
<ul style="line-height: 1.8; color: #333;">
<li><strong>Accuracy vs Cost:</strong> Higher accuracy sensors cost more. Match sensor precision to application requirements - don't over-specify.</li>
<li><strong>Total Cost of Ownership:</strong> Consider maintenance, calibration, and replacement costs over 5+ years, not just initial price.</li>
<li><strong>Power Consumption:</strong> Battery-powered applications demand low-power sensors. Wired applications have more flexibility.</li>
<li><strong>Environmental Factors:</strong> Temperature range, humidity, dust, and vibration affect sensor choice and longevity.</li>
<li><strong>Interface Compatibility:</strong> Digital sensors (I2C, SPI) easier to integrate than analog, but may cost more.</li>
<li><strong>Lifespan and Drift:</strong> Some sensors (pH, gas) drift and require frequent calibration. Others (ToF, IMU) are stable for years.</li>
</ul>
</div>
<button class="restart-btn" onclick="restartGame()">
🔄 Play Again
</button>
<div style="margin-top: 24px;">
<p style="font-size: 0.9em; color: #666;">
Try different difficulty levels to master sensor selection across various applications!
</p>
</div>
</div>
`;
}
// Make functions globally accessible
window.selectSensor = selectSensor;