<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Online Noise Meter</title>
    <link rel="icon" href="favicon.ico" />
    <style>
        /* Manually extracted and optimized Tailwind CSS */
        @font-face {
            font-family: 'Inter';
            src: url('/fonts/Inter-Regular.woff2') format('woff2');
            font-weight: 400;
            font-style: normal;
            font-display: swap;
        }
        @font-face {
            font-family: 'Inter';
            src: url('/fonts/Inter-SemiBold.woff2') format('woff2');
            font-weight: 600;
            font-style: normal;
            font-display: swap;
        }
        @font-face {
            font-family: 'Inter';
            src: url('/fonts/Inter-Bold.woff2') format('woff2');
            font-weight: 700;
            font-style: normal;
            font-display: swap;
        }
         @font-face {
            font-family: 'Inter';
            src: url('/fonts/Inter-Black.woff2') format('woff2');
            font-weight: 900;
            font-style: normal;
            font-display: swap;
        }

        body {
            font-family: 'Inter', sans-serif;
            background-color: #0f172a; /* slate-900 */
            color: #f8fafc; /* slate-50 */
        }
        .btn-start {
            background-color: #22c55e; /* green-500 */
            transition: background-color 0.3s ease, opacity 0.3s ease;
            min-width: 150px;
            padding: 1.25rem 2.5rem; /* Increased padding */
            font-size: 1.25rem; /* Increased font size */
        }
        .btn-start:hover {
            background-color: #16a34a; /* green-600 */
        }
        .btn-stop {
            background-color: #ef4444; /* red-500 */
            transition: background-color 0.3s ease;
            min-width: 150px;
            padding: 1.25rem 2.5rem; /* Increased padding */
            font-size: 1.25rem; /* Increased font size */
        }
        .btn-stop:hover {
            background-color: #dc2626; /* red-600 */
        }
        #gauge, #historyChart {
            transition: opacity 0.5s ease-in-out;
        }
        canvas {
            max-width: 100%;
            height: auto;
        }

        /* Hardcoded Tailwind CSS classes used in your HTML */
        .flex { display: flex; }
        .justify-center { justify-content: center; }
        .py-4 { padding-top: 1rem; padding-bottom: 1rem; }
        .px-2 { padding-left: 0.5rem; padding-right: 0.5rem; }
        .sm\:px-4 { padding-left: 1rem; padding-right: 1rem; }
        .w-full { width: 100%; }
        .max-w-4xl { max-width: 56rem; }
        .mx-auto { margin-left: auto; margin-right: auto; }
        .bg-slate-800 { background-color: #1e293b; }
        .rounded-2xl { border-radius: 1rem; }
        .shadow-2xl { box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25); }
        .p-3 { padding: 0.75rem; }
        .sm\:p-6 { padding: 1.5rem; }
        .md\:p-8 { padding: 2rem; }
        .space-y-3 > :not([hidden]) ~ :not([hidden]) { --tw-space-y-reverse: 0; margin-top: calc(0.75rem * calc(1 - var(--tw-space-y-reverse))); margin-bottom: calc(0.75rem * var(--tw-space-y-reverse)); }
        .text-center { text-align: center; }
        .text-3xl { font-size: 1.875rem; line-height: 2.25rem; }
        .md\:text-4xl { font-size: 2.25rem; line-height: 2.5rem; }
        .font-bold { font-weight: 700; }
        .text-white { color: #fff; }
        .text-slate-400 { color: #94a3b8; }
        .mt-2 { margin-top: 0.5rem; }
        .relative { position: relative; }
        .items-center { align-items: center; }
        .justify-center { justify-content: center; }
        .h-\[240px\] { height: 240px; }
        .sm\:h-\[250px\] { height: 250px; }
        .absolute { position: absolute; }
        .flex-col { flex-direction: column; }
        .font-black { font-weight: 900; }
        .tracking-tighter { letter-spacing: -0.05em; }
        .text-6xl { font-size: 3.75rem; line-height: 1; }
        .sm\:text-7xl { font-size: 4.5rem; line-height: 1; }
        .md\:text-8xl { font-size: 6rem; line-height: 1; }
        .text-xl { font-size: 1.25rem; line-height: 1.75rem; }
        .font-medium { font-weight: 500; }
        .text-slate-300 { color: #cbd5e1; }
        .hidden { display: none; }
        .flex { display: flex; }
        .grid { display: grid; }
        .grid-cols-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
        .md\:grid-cols-4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
        .gap-3 { gap: 0.75rem; }
        .sm\:gap-4 { gap: 1rem; }
        .bg-slate-700\/50 { background-color: rgba(51, 65, 85, 0.5); }
        .p-4 { padding: 1rem; }
        .rounded-lg { border-radius: 0.5rem; }
        .text-sm { font-size: 0.875rem; line-height: 1.25rem; }
        .text-2xl { font-size: 1.5rem; line-height: 2rem; }
        .h-24 { height: 6rem; }
        .bg-slate-900 { background-color: #0f172a; }
        .p-2 { padding: 0.5rem; }
        .overflow-hidden { overflow: hidden; }
        .gap-4 { gap: 1rem; }
        .pt-4 { padding-top: 1rem; }
        .text-xs { font-size: 0.75rem; line-height: 1rem; }
        .text-slate-500 { color: #64748b; }
        .max-w-md { max-width: 28rem; }
        .mt-4 { margin-top: 1rem; }
        .mt-8 { margin-top: 2rem; }
        .prose { color: #e2e8f0; font-family: 'Inter', sans-serif; font-size: 1rem; line-height: 1.75; }
        .prose :where(p, li) { margin-top: 1.25em; margin-bottom: 1.25em; }
        .prose :where(h1, h2, h3, h4, h5, h6) { color: inherit; font-weight: 600; }
        .prose :where(h2) { font-size: 1.5rem; line-height: 2rem; margin-top: 2rem; margin-bottom: 1rem; }
        .prose :where(h3) { font-size: 1.25rem; line-height: 1.75rem; margin-top: 1.5rem; margin-bottom: 0.5rem; }
        .prose :where(ul) { list-style-type: disc; }
        .prose :where(ol) { list-style-type: decimal; }
        .prose :where(ul, ol) { padding-left: 1.5rem; }
        .prose :where(li) { padding-left: 0.5em; }
        .prose-invert :where(h1, h2, h3, h4, h5, h6) { color: #f8fafc; }
        .prose-invert :where(p) { color: #cbd5e1; }
        .max-w-none { max-width: none; }

        .prose-p\:text-slate-300 p { color: #cbd5e1; }
        .prose-headings\:text-white h1, .prose-headings\:text-white h2, .prose-headings\:text-white h3, .prose-headings\:text-white h4, .prose-headings\:text-white h5, .prose-headings\:text-white h6 { color: #ffffff; }

    </style>
</head>
<body class="flex justify-center py-4 px-2 sm:px-4">
    <div class="w-full max-w-4xl mx-auto">
        <div class="bg-slate-800 rounded-2xl shadow-2xl p-3 sm:p-6 md:p-8 space-y-3">
            
            <!-- Header -->
            <header class="text-center">
                <h1 class="text-3xl md:text-4xl font-bold text-white">Noise Level Meter</h1>
                <p id="status" class="text-slate-400 mt-2">Click the button to begin monitoring</p>
            </header>

            <!-- Main Display -->
            <div class="relative flex items-center justify-center h-[240px] sm:h-[250px]">
                <canvas id="gauge" width="500" height="250"></canvas>
                <div class="absolute flex flex-col items-center justify-center">
                    <button id="start-btn" class="btn-start text-white font-bold rounded-lg shadow-lg">Start Meter</button>
                    <div id="readout" class="hidden flex-col items-center justify-center">
                        <span id="db-value" class="text-6xl sm:text-7xl md:text-8xl font-black text-white tracking-tighter">--</span>
                        <span class="text-xl font-medium text-slate-300">dBA</span>
                    </div>
                </div>
            </div>
            
            <!-- Data Cards -->
            <div id="data-cards" class="grid grid-cols-2 md:grid-cols-4 gap-3 sm:gap-4 text-center opacity-0 transition-opacity duration-500">
                <div class="bg-slate-700/50 p-4 rounded-lg">
                    <p class="text-sm text-slate-400">Min</p>
                    <p id="min-db" class="text-2xl font-bold text-white">--</p>
                </div>
                <div class="bg-slate-700/50 p-4 rounded-lg">
                    <p class="text-sm text-slate-400">Avg</p>
                    <p id="avg-db" class="text-2xl font-bold text-white">--</p>
                </div>
                <div class="bg-slate-700/50 p-4 rounded-lg">
                    <p class="text-sm text-slate-400">Max</p>
                    <p id="max-db" class="text-2xl font-bold text-white">--</p>
                </div>
                <div class="bg-slate-700/50 p-4 rounded-lg">
                    <p class="text-sm text-slate-400">Level</p>
                    <p id="level-text" class="text-2xl font-bold text-white">--</p>
                </div>
            </div>

            <!-- History Chart -->
            <div class="w-full h-24 bg-slate-900 rounded-lg p-2 overflow-hidden">
                <canvas id="historyChart"></canvas>
            </div>

            <!-- Controls and Disclaimer -->
            <div class="flex flex-col items-center justify-center gap-4 pt-4">
                 <div class="flex gap-4">
                    <button id="stop-btn" class="btn-stop text-white font-bold rounded-lg shadow-lg hidden">Stop Meter</button>
                </div>
                <p class="text-xs text-slate-500 text-center max-w-md mt-4">
                    <span class="font-bold">Disclaimer:</span> This tool provides an uncalibrated noise level estimate. For professional use, a calibrated physical sound level meter is required.
                </p>
            </div>
        </div>

        <!-- How-To and FAQ Section -->
        <div id="info-section" class="w-full max-w-4xl mx-auto mt-8 p-6 md:p-8 bg-slate-800 rounded-2xl">
            <div class="prose prose-invert max-w-none prose-p:text-slate-300 prose-headings:text-white">
                <h2 class="text-2xl font-bold">How to Use This Online Noise Meter</h2>
                <p>Getting a read on your environment's sound levels is simple. Here’s how to get started:</p>
                <ol>
                    <li><strong>Start the Meter:</strong> Click the green "Start Meter" button.</li>
                    <li><strong>Grant Permission:</strong> Your browser will ask for permission to use your microphone. This is essential for the meter to work. We don't record or store any audio, so your privacy is assured.</li>
                    <li><strong>Read the Levels:</strong> The large gauge will immediately show you the current sound level in dBA. You can also see the minimum, maximum, and average readings in the cards below.</li>
                </ol>
                
                <h2 class="text-2xl font-bold mt-8">Frequently Asked Questions (FAQ)</h2>
                
                <h3 class="text-xl font-semibold">Is this online decibel meter accurate?</h3>
                <p>This tool provides a good general estimate of noise levels for everyday use. It uses an A-weighting filter (dBA) to better match how the human ear perceives sound. However, it's not a professionally calibrated device. The quality of your microphone and your computer's audio settings can affect the readings. For official measurements (like for workplace safety), you should always use a dedicated, calibrated sound level meter.</p>
                
                <h3 class="text-xl font-semibold">What do the dBA values mean?</h3>
                <p>dBA is a measure of sound pressure level, adjusted to be more sensitive to the frequencies our ears hear best. Here’s a quick guide to what different levels sound like:</p>
                <ul>
                    <li><strong>20 dBA:</strong> A quiet whisper or rustling leaves.</li>
                    <li><strong>50 dBA:</strong> A quiet library or a refrigerator humming.</li>
                    <li><strong>70 dBA:</strong> A normal conversation or a running dishwasher.</li>
                    <li><strong>90 dBA:</strong> A noisy restaurant, a lawnmower, or heavy city traffic.</li>
                    <li><strong>110 dBA:</strong> A rock concert or a chainsaw from 3 feet away.</li>
                    <li><strong>130 dBA:</strong> A jet engine taking off from 100 feet away.</li>
                </ul>

                <h3 class="text-xl font-semibold">Why is the live value moving so much?</h3>
                <p>Sound in the real world fluctuates constantly. The history chart at the bottom shows these rapid, real-time changes. To make the main display easier to read, we apply a smoothing algorithm. This gives you a stable, averaged-out value on the gauge, which is more useful for understanding the overall noise level at any given moment.</p>

                <h3 class="text-xl font-semibold">Is my audio being recorded or stored?</h3>
                <p>Absolutely not. Your privacy is paramount. All audio processing is done directly in your browser on your own device. The audio data is analyzed in real-time and then immediately discarded. Nothing is ever sent to or stored on our servers.</p>

                <h3 class="text-xl font-semibold">What are considered safe noise levels?</h3>
                <p>According to health organizations, prolonged exposure to noise levels above <strong>85 dBA</strong> can lead to permanent hearing damage. The louder the sound, the less time it takes for damage to occur. For example, damage can occur after just 15 minutes of exposure to 100 dBA. It's always a good idea to use hearing protection in loud environments.</p>
            </div>
        </div>
    </div>


    <script>
        // --- AUDIO WORKLET PROCESSOR ---
        // This code runs in a separate thread to process audio efficiently.
        const aWeightingProcessorString = `
            // Implements a cascade of filters to apply A-weighting to the audio signal.
            // This makes the measurement more representative of human hearing.
            class AWeightingFilter {
                constructor(sampleRate) {
                    // Pre-calculated coefficients for the A-weighting filter for different sample rates.
                    const coeffs = {
                        48000: [
                            [1, -1.9913, 0.99135, 1, -2, 1],
                            [1, -1.9749, 0.97517, 1, -2, 1],
                            [1, -1.8126, 0.81661, 1, -1.8087, 0.81323],
                            [1, -0.4913, 0.06313, 1, 0.5095, 0.06766]
                        ],
                        44100: [
                            [1, -1.9905, 0.99052, 1, -2, 1],
                            [1, -1.9727, 0.97295, 1, -2, 1],
                            [1, -1.7951, 0.80058, 1, -1.7912, 0.79719],
                            [1, -0.4246, 0.05054, 1, 0.4435, 0.05575]
                        ]
                    };
                    
                    this.filters = (coeffs[sampleRate] || coeffs[48000]).map(c => new BiquadFilter(c));
                    this.gain = 1.2589; // A-weighting gain compensation
                }

                process(input) {
                    let output = new Float32Array(input.length);
                    for (let i = 0; i < input.length; i++) {
                        let s = input[i];
                        for (const filter of this.filters) {
                            s = filter.process(s);
                        }
                        output[i] = s * this.gain;
                    }
                    return output;
                }
            }

            // Standard Biquad Filter implementation used by the A-weighting filter.
            class BiquadFilter {
                constructor(c) {
                    [this.a1, this.a2, this.b0, this.b1, this.b2] = c.slice(1);
                    this.x1 = this.x2 = this.y1 = this.y2 = 0;
                }

                process(x0) {
                    const y0 = this.b0 * x0 + this.b1 * this.x1 + this.b2 * this.x2 - this.a1 * this.y1 - this.a2 * this.y2;
                    this.x2 = this.x1;
                    this.x1 = x0;
                    this.y2 = this.y1;
                    this.y1 = y0;
                    return y0;
                }
            }

            class AWeightingProcessor extends AudioWorkletProcessor {
                constructor(options) {
                    super();
                    this.sampleRate = options.processorOptions.sampleRate;
                    this.filter = new AWeightingFilter(this.sampleRate);
                }

                process(inputs, outputs, parameters) {
                    const input = inputs[0][0];
                    if (!input) return true;

                    const weighted = this.filter.process(input);
                    
                    let sum = 0;
                    for (let i = 0; i < weighted.length; i++) {
                        sum += weighted[i] * weighted[i];
                    }
                    
                    const rms = Math.sqrt(sum / weighted.length);
                    this.port.postMessage({ rms });

                    return true;
                }
            }

            registerProcessor('a-weighting-processor', AWeightingProcessor);
        `;

        // --- MAIN APPLICATION LOGIC ---
        document.addEventListener('DOMContentLoaded', () => {
            // UI Elements
            const statusEl = document.getElementById('status');
            const dbValueEl = document.getElementById('db-value');
            const minDbEl = document.getElementById('min-db');
            const avgDbEl = document.getElementById('avg-db');
            const maxDbEl = document.getElementById('max-db');
            const levelTextEl = document.getElementById('level-text');
            const startBtn = document.getElementById('start-btn');
            const stopBtn = document.getElementById('stop-btn');
            const readoutEl = document.getElementById('readout');
            const dataCardsEl = document.getElementById('data-cards');
            
            // Gauge Canvas
            const gaugeCanvas = document.getElementById('gauge');
            const gaugeCtx = gaugeCanvas.getContext('2d');

            // History Chart Canvas
            const chartCanvas = document.getElementById('historyChart');
            const chartCtx = chartCanvas.getContext('2d');

            // Audio variables
            let audioContext;
            let micSource;
            let workletNode;
            let animationFrameId;

            // State variables
            let db = 0;
            let smoothedDb = 0;
            const SMOOTHING_FACTOR = 0.05; // Lower value = slower, smoother updates
            let minDb = 140;
            let maxDb = 0;
            let avgDb = 0;
            let dbHistory = [];
            let avgBuffer = [];
            const historySize = 200; // Number of data points for the chart
            const avgBufferSize = 50; // Number of data points for rolling average

            const soundLevels = [
                { db: 30, label: "Silent", color: "#22c55e" },
                { db: 50, label: "Quiet Library", color: "#84cc16" },
                { db: 70, label: "Conversation", color: "#facc15" },
                { db: 90, label: "Noisy Restaurant", color: "#f97316" },
                { db: 110, label: "Rock Concert", color: "#ef4444" },
                { db: 130, label: "Power Tools", color: "#dc2626" },
                { db: 140, label: "Jet Engine", color: "#b91c1c" }
            ];

            // --- EVENT LISTENERS ---
            startBtn.addEventListener('click', startMeter);
            stopBtn.addEventListener('click', stopMeter);
            
            // --- AUDIO PROCESSING ---
            async function startMeter() {
                try {
                    statusEl.textContent = 'Initializing...';
                    const stream = await navigator.mediaDevices.getUserMedia({
                        audio: {
                            echoCancellation: false,
                            noiseSuppression: false,
                            autoGainControl: false
                        }
                    });

                    audioContext = new (window.AudioContext || window.webkitAudioContext)();
                    
                    const blob = new Blob([aWeightingProcessorString], { type: 'application/javascript' });
                    const workletURL = URL.createObjectURL(blob);
                    
                    await audioContext.audioWorklet.addModule(workletURL);
                    
                    workletNode = new AudioWorkletNode(audioContext, 'a-weighting-processor', {
                        processorOptions: { sampleRate: audioContext.sampleRate }
                    });

                    micSource = audioContext.createMediaStreamSource(stream);
                    micSource.connect(workletNode);
                    workletNode.connect(audioContext.destination);

                    workletNode.port.onmessage = (event) => {
                        const rms = event.data.rms;
                        if (rms > 0) {
                           db = 20 * Math.log10(rms) + 94;
                           updateReadings(db);
                        }
                    };

                    resetState();
                    statusEl.textContent = 'Monitoring...';
                    startBtn.classList.add('hidden');
                    readoutEl.classList.remove('hidden');
                    readoutEl.classList.add('flex');
                    stopBtn.classList.remove('hidden');
                    dataCardsEl.classList.remove('opacity-0');

                    animationFrameId = requestAnimationFrame(updateUI);

                } catch (err) {
                    statusEl.textContent = `Error: ${err.message}. Please grant microphone access.`;
                    console.error('Error starting meter:', err);
                }
            }

            function stopMeter() {
                if (micSource) {
                    micSource.mediaStream.getTracks().forEach(track => track.stop());
                    micSource.disconnect();
                }
                if (workletNode) workletNode.disconnect();
                if (audioContext) audioContext.close();
                if (animationFrameId) cancelAnimationFrame(animationFrameId);

                statusEl.textContent = 'Click the button to begin monitoring';
                startBtn.classList.remove('hidden');
                readoutEl.classList.add('hidden');
                readoutEl.classList.remove('flex');
                stopBtn.classList.add('hidden');
            }
            
            function resetState() {
                db = 0;
                smoothedDb = 0;
                minDb = 140;
                maxDb = 0;
                avgDb = 0;
                dbHistory = Array(historySize).fill(0);
                avgBuffer = [];
            }

            // --- UI UPDATES ---
            function updateReadings(currentDb) {
                // Apply exponential smoothing to the raw dB value
                smoothedDb = SMOOTHING_FACTOR * currentDb + (1 - SMOOTHING_FACTOR) * smoothedDb;

                minDb = Math.min(minDb, currentDb);
                maxDb = Math.max(maxDb, currentDb);

                avgBuffer.push(currentDb);
                if (avgBuffer.length > avgBufferSize) {
                    avgBuffer.shift();
                }
                avgDb = avgBuffer.reduce((a, b) => a + b, 0) / avgBuffer.length;
                
                // History chart uses the raw, unsmoothed value
                dbHistory.push(currentDb);
                if (dbHistory.length > historySize) {
                    dbHistory.shift();
                }
            }

            function updateUI() {
                const displayDb = Math.max(0, smoothedDb);
                const level = soundLevels.find(l => displayDb < l.db) || soundLevels[soundLevels.length - 1];

                dbValueEl.textContent = displayDb.toFixed(1);
                minDbEl.textContent = minDb === 140 ? '--' : minDb.toFixed(1);
                maxDbEl.textContent = maxDb === 0 ? '--' : maxDb.toFixed(1);
                avgDbEl.textContent = avgDb.toFixed(1);
                levelTextEl.textContent = level.label;
                levelTextEl.style.color = level.color;

                drawGauge(displayDb);
                drawHistoryChart();

                animationFrameId = requestAnimationFrame(updateUI);
            }

            // --- CANVAS DRAWING ---
            function drawGauge(value) {
                const width = gaugeCanvas.width;
                const height = gaugeCanvas.height;
                const centerX = width / 2;
                const centerY = height - 15;
                const radius = Math.min(width / 2 - 45, height - 55);
                const minDbValue = 0;
                const maxDbValue = 140;

                gaugeCtx.clearRect(0, 0, width, height);

                // Draw the gauge arc background
                gaugeCtx.beginPath();
                gaugeCtx.arc(centerX, centerY, radius, Math.PI, 0);
                gaugeCtx.lineWidth = 30;
                gaugeCtx.strokeStyle = '#1e293b'; // slate-800
                gaugeCtx.stroke();

                // Draw the colored gauge arc
                const gradient = gaugeCtx.createLinearGradient(centerX - radius, 0, centerX + radius, 0);
                gradient.addColorStop(0, '#22c55e'); // green-500
                gradient.addColorStop(0.5, '#facc15'); // yellow-400
                gradient.addColorStop(1, '#ef4444'); // red-500

                const angle = Math.PI + (Math.min(value, maxDbValue) / maxDbValue) * Math.PI;
                gaugeCtx.beginPath();
                gaugeCtx.arc(centerX, centerY, radius, Math.PI, angle);
                gaugeCtx.strokeStyle = gradient;
                gaugeCtx.stroke();

                // Draw ticks and labels
                for (let i = 0; i <= maxDbValue; i += 10) {
                    const tickAngle = Math.PI + (i / maxDbValue) * Math.PI;
                    const isMajorTick = i % 20 === 0;

                    const tickLength = isMajorTick ? 20 : 10;
                    const tickXStart = centerX + (radius - tickLength / 2) * Math.cos(tickAngle);
                    const tickYStart = centerY + (radius - tickLength / 2) * Math.sin(tickAngle);
                    const tickXEnd = centerX + (radius + tickLength / 2) * Math.cos(tickAngle);
                    const tickYEnd = centerY + (radius + tickLength / 2) * Math.sin(tickAngle);
                    
                    gaugeCtx.beginPath();
                    gaugeCtx.moveTo(tickXStart, tickYStart);
                    gaugeCtx.lineTo(tickXEnd, tickYEnd);
                    gaugeCtx.strokeStyle = isMajorTick ? '#64748b' : '#334155'; // slate-500 : slate-700
                    gaugeCtx.lineWidth = isMajorTick ? 2 : 1;
                    gaugeCtx.stroke();
                    
                    if (isMajorTick) {
                        const labelX = centerX + (radius + 28) * Math.cos(tickAngle);
                        const labelY = centerY + (radius + 28) * Math.sin(tickAngle);
                        gaugeCtx.font = '700 14px Inter';
                        gaugeCtx.fillStyle = '#94a3b8'; // slate-400
                        gaugeCtx.textAlign = 'center';
                        gaugeCtx.textBaseline = 'middle';
                        gaugeCtx.fillText(i, labelX, labelY);
                    }
                }
            }

            function drawHistoryChart() {
                const width = chartCanvas.clientWidth;
                const height = chartCanvas.clientHeight;
                chartCanvas.width = width;
                chartCanvas.height = height;
                
                chartCtx.clearRect(0, 0, width, height);

                const maxVal = 140;
                const stepX = width / (historySize - 1);
                
                const graphHeight = height * 0.9; // Use 90% of the canvas height for the graph
                const bottomPadding = height * 0.05; // Leave 5% padding at the bottom

                chartCtx.beginPath();
                chartCtx.moveTo(0, height);

                for (let i = 0; i < dbHistory.length; i++) {
                    const val = dbHistory[i];
                    const x = i * stepX;
                    const y = height - ((val / maxVal) * graphHeight + bottomPadding);
                    chartCtx.lineTo(x, y);
                }
                
                chartCtx.lineTo(width, height);
                chartCtx.closePath();

                const gradient = chartCtx.createLinearGradient(0, 0, 0, height);
                gradient.addColorStop(0, 'rgba(34, 197, 94, 0.4)');
                gradient.addColorStop(1, 'rgba(15, 23, 42, 0.1)');

                chartCtx.fillStyle = gradient;
                chartCtx.fill();

                chartCtx.beginPath();
                for (let i = 0; i < dbHistory.length; i++) {
                    const val = dbHistory[i];
                    const x = i * stepX;
                    const y = height - ((val / maxVal) * graphHeight + bottomPadding);
                    if (i === 0) {
                        chartCtx.moveTo(x, y);
                    } else {
                        chartCtx.lineTo(x, y);
                    }
                }
                chartCtx.lineWidth = 2;
                chartCtx.strokeStyle = '#22c55e'; // green-500
                chartCtx.stroke();
            }
            
            // Initial draw
            drawGauge(0);
            drawHistoryChart();
        });
    </script>
</body>
</html>
