Project structure:

LitGeiger/
├── static/
│   ├── css/
│   │   └── styles.css
│   ├── images/
│   │   └── geiger.webp
│   ├── js/
│   │   └── app.js
├── templates/
│   └── index2.html
└── main.py


Project files:

main.py
```py
from flask import Flask, render_template

app = Flask(__name__)

@app.route('/')
def main():
    return render_template("index2.html")

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=8081)
```


static/css/styles.css
```css
body {
    font-family: Arial, sans-serif;
    text-align: center;
    padding: 0;
    background-color: #f0f0f0;
    margin: 0;
}

h1 {
    color: #333;
}

.main-screen {
    position: fixed;
    width: 100%;
    height: 100vh;
    display: flex;
}

#radiationLevel {
    font-weight: bold;
    color: #d9534f;
}

#radiationSlider {
    width: 100%;
    position: absolute;
    bottom: 0;
    left: 0;
    right: 0;
    opacity: 0.1;
}

#startButton {
    position: absolute;
    z-index: 2;
    left: 21%;
    top: 57%;
    width: 12%;
    height: 6%;
    background: none;
    border: none;
    outline: none;
}

#startButton:hover {
    background-color: #4cae4c;
}

.controls {
    /* margin-top: 20px; */
    z-index: 2;
    position: absolute;
    top: 94%;
    left: 10%;
}

.controls label {
    font-size: 16px;
    margin-left: 10px;
}

.background-img {
    width: 100%;
    height: 100vh;
    position: absolute;
    left: 0;
    right: 0;
    top: 0;
    bottom: 0;
}

.screen {
    display: flex;
    position: absolute;
    width: 61%;
    height: 26%;
    background: none;
    left: 20%;
    top: 15%;
    flex-direction: column;
    align-items: center;
    justify-content: center;
}

.radiationLevel {
    color: #ffffff;
    font-size: 90px;
    opacity: 0.5;
    filter: blur(0.7px);
}
```


static/js/app.js
```js
$(document).ready(function () {
    let radiationLevel = 0;
    let beepTimeout;
    let audioCtx;
    let masterGain;
    let lfo;
    let isAudioInitialized = false;

    // Гироскоп
    let useGyroscope = false;
    let initialOrientation = null;
    let deviceOrientationListener = null;

    const beepDuration = 0.05; // in seconds
    const attackTime = 0.005;
    const decayTime = 0.015;

    // Функция для инициализации аудио
    function initAudio() {
        audioCtx = new (window.AudioContext || window.webkitAudioContext)();

        // Создаем мастер-узел громкости
        masterGain = audioCtx.createGain();
        masterGain.gain.setValueAtTime(0.8, audioCtx.currentTime); // Основной уровень громкости
        masterGain.connect(audioCtx.destination);

        // Создаем LFO для глобальной модуляции громкости
        lfo = audioCtx.createOscillator();
        const lfoGain = audioCtx.createGain();
        lfo.frequency.setValueAtTime(0.2, audioCtx.currentTime); // Частота модуляции: 0.2 Гц
        lfoGain.gain.setValueAtTime(0.05, audioCtx.currentTime); // Амплитуда модуляции
        lfo.connect(lfoGain);
        lfoGain.connect(masterGain.gain);
        lfo.start();

        isAudioInitialized = true;
    }

    // Функция для начала писка с плавным нарастанием громкости
    function startBeep() {
        if (!isAudioInitialized) return;

        const oscillator = audioCtx.createOscillator();
        const gainNode = audioCtx.createGain();

        // Установка частоты с небольшим случайным отклонением
        const baseFreq = 880;
        const variation = (Math.random() - 0.5) * 20; // +/-10 Гц
        oscillator.frequency.setValueAtTime(baseFreq + variation, audioCtx.currentTime);
        oscillator.type = 'sine';

        // Настройка огибающей ADSR для плавного звучания
        const now = audioCtx.currentTime;
        gainNode.gain.setValueAtTime(0, now);
        gainNode.gain.linearRampToValueAtTime(0.5, now + attackTime);
        gainNode.gain.linearRampToValueAtTime(0, now + attackTime + decayTime);

        oscillator.connect(gainNode);
        gainNode.connect(masterGain);
        oscillator.start();
        oscillator.stop(now + beepDuration + decayTime + 0.1); // Остановка немного позже окончания огибающей

        // Очистка после завершения
        oscillator.onended = function () {
            oscillator.disconnect();
            gainNode.disconnect();
        };
    }

    // Функция для планирования следующего писка с учетом радиационного уровня
    function scheduleBeep() {
        if (radiationLevel === 0) return;

        // Вычисляем средний интервал между писками
        const averageInterval = 600 / (radiationLevel + 1); // Чем выше уровень, тем чаще писки

        // Добавляем случайность к интервалу
        const jitter = averageInterval * 0.3;
        const interval = averageInterval + (Math.random() * jitter * 2 - jitter);

        beepTimeout = setTimeout(() => {
            // При высоких уровнях радиации увеличиваем вероятность наложения писков
            const overlapChance = radiationLevel > 80 ? 0.3 :
                radiationLevel > 60 ? 0.2 :
                    radiationLevel > 40 ? 0.1 : 0.05;

            if (Math.random() < overlapChance) {
                // Иногда запускаем несколько писков одновременно
                const beepCount = Math.floor(Math.random() * 3) + 1; // 1, 2 или 3 писка
                for (let i = 0; i < beepCount; i++) {
                    startBeep();
                }
            } else {
                startBeep();
            }

            scheduleBeep();
        }, interval);
    }

    // Обновление писков при изменении уровня радиации
    function updateBeep() {
        clearTimeout(beepTimeout);
        if (radiationLevel > 0) {
            scheduleBeep();
        }
    }

    // Обработчик изменения слайдера
    $('#radiationSlider').on('input', function () {
        if (useGyroscope) return; // Если гироскоп используется, игнорируем слайдер

        const newRadiationLevel = parseInt($(this).val());
        if (newRadiationLevel !== radiationLevel) {
            radiationLevel = newRadiationLevel;
            $('#radiationLevel').text(radiationLevel);
            if (isAudioInitialized) {
                updateBeep();
            }
        }
    });

    // Обработчик нажатия кнопки "Включить"
    $('#startButton').click(function () {
        if (!isAudioInitialized) {
            initAudio();
            updateBeep();
            $(this).fadeOut();
        }
    });

    // Обработчик галочки "Гироскоп"
    $('#gyroscopeCheckbox').on('change', function () {
        useGyroscope = $(this).is(':checked');

        if (useGyroscope) {
            // Инициализация гироскопа
            if (window.DeviceOrientationEvent) {
                // Запрос разрешения (для iOS 13+)
                if (typeof DeviceOrientationEvent.requestPermission === 'function') {
                    DeviceOrientationEvent.requestPermission()
                        .then(response => {
                            if (response === 'granted') {
                                startGyroscope();
                            } else {
                                alert('Доступ к гироскопу отклонен.');
                                $('#gyroscopeCheckbox').prop('checked', false);
                            }
                        })
                        .catch(() => {
                            alert('Не удалось получить доступ к гироскопу.');
                            $('#gyroscopeCheckbox').prop('checked', false);
                        });
                } else {
                    // Для браузеров, где не требуется разрешение
                    startGyroscope();
                }
            } else {
                alert('Гироскоп не поддерживается вашим устройством или браузером.');
                $('#gyroscopeCheckbox').prop('checked', false);
            }
        } else {
            // Остановка гироскопа
            if (deviceOrientationListener) {
                window.removeEventListener('deviceorientation', deviceOrientationListener);
                deviceOrientationListener = null;
            }
            initialOrientation = null;
            // Возвращаем управление слайдеру
            $('#radiationSlider').prop('disabled', false);
        }
    });

    // Функция для начала прослушивания гироскопа
    function startGyroscope() {
        initialOrientation = null;

        deviceOrientationListener = function (event) {
            if (!initialOrientation) {
                initialOrientation = {
                    alpha: event.alpha,
                    beta: event.beta,
                    gamma: event.gamma
                };
                return;
            }

            // Вычисляем отклонения
            let deltaAlpha = Math.abs(event.alpha - initialOrientation.alpha);
            let deltaBeta = Math.abs(event.beta - initialOrientation.beta);
            let deltaGamma = Math.abs(event.gamma - initialOrientation.gamma);

            // Обработка перехода через 0 градусов
            deltaAlpha = deltaAlpha > 180 ? 360 - deltaAlpha : deltaAlpha;
            deltaBeta = deltaBeta > 180 ? 360 - deltaBeta : deltaBeta;
            deltaGamma = deltaGamma > 180 ? 360 - deltaGamma : deltaGamma;

            // Игнорируем поворот вокруг оси Y (gamma)
            deltaAlpha = 0;

            // Общая величина отклонения без учета поворота вокруг Y
            const deviation = Math.sqrt(deltaAlpha * deltaAlpha + deltaBeta * deltaBeta);

            // Масштабирование отклонения до 0-100
            const scaledDeviation = Math.min(Math.floor((deviation / 90) * 100), 100); // Предполагая, что максимальное отклонение 90 градусов

            // Обновляем уровень радиации
            if (scaledDeviation !== radiationLevel) {
                radiationLevel = scaledDeviation;
                $('#radiationLevel').text(radiationLevel);
                $('#radiationSlider').val(radiationLevel);
                if (isAudioInitialized) {
                    updateBeep();
                }
            }
        };

        window.addEventListener('deviceorientation', deviceOrientationListener);
        // Отключаем слайдер, так как управление теперь через гироскоп
        $('#radiationSlider').prop('disabled', true);
    }

    // Остановка звука и очистка при закрытии страницы
    $(window).on('beforeunload', function () {
        if (audioCtx) {
            audioCtx.close();
        }
        if (deviceOrientationListener) {
            window.removeEventListener('deviceorientation', deviceOrientationListener);
        }
    });
});
```


templates/index2.html
```html
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Счетчик Гейгера</title>
    <meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
    <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
    <link rel="stylesheet" href="../static/css/styles.css">
</head>
<body>

<div class="main-screen">
    <img class="background-img" src="../static/images/geiger.webp">
    <div class="screen">
        <div class="radiationLevel" id="radiationLevel">50</div>
        <input type="range" id="radiationSlider" min="0" max="100" value="0">
    </div>

    <br>
    <button id="startButton"></button>
    <div class="controls">
        <input type="checkbox" id="gyroscopeCheckbox">
        <label for="gyroscopeCheckbox">Гироскоп</label>
    </div>

</div>
<script src="../static/js/app.js"></script>

</body>
</html>
```


Сделай так, чтобы там в тексте писалось - когда 0 - 50, когда 100 - 1000. И сделай так, чтобы когда больше 200, то текст становится красным, а когда нет - белым.
Также, добавь в нижнюю часть .screen на всю ширину типо график, который будет постоянно что-то показывать. Его цвет тоже должен меняться