<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:base="https://www.pandoraburo.ru/" xml:lang="ru">
  <id>https://www.pandoraburo.ru/trenirovka-vnimaniya</id>
  <link href="https://www.pandoraburo.ru/trenirovka-vnimaniya.xml" rel="self"/>
  <title/>
  <subtitle type="html">
&lt;style&gt;
    .target-tracker * { box-sizing: border-box; user-select: none; }
    .target-tracker {
        font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
        background: #1e1e2f; padding: 20px; border-radius: 20px;
        color: #eee; max-width: 1300px; margin: 0 auto;
    }
    .target-tracker .screen { display: none; width: 100%; }
    .target-tracker .screen.active { display: block; }
    .target-tracker .disclaimer-box {
        background: #2a1a1a; border: 2px solid #ff4444; border-radius: 16px; padding: 30px; text-align: center;
    }
    .target-tracker .disclaimer-box h2 { color: #ff6b6b; margin-top: 0; }
    .target-tracker .disclaimer-box p { font-size: 1.1rem; line-height: 1.6; margin-bottom: 20px; }
    .target-tracker .warning-text { color: #ffaa66; font-weight: bold; }
    .target-tracker .game-layout { display: flex; flex-wrap: wrap; gap: 20px; width: 100%; }
    .target-tracker .canvas-wrapper {
        flex: 3; min-width: 500px; background: #0b0b16; border-radius: 16px; padding: 10px;
        box-shadow: inset 0 0 10px #00000066; position: relative;
    }
    .target-tracker canvas {
        display: block; width: 100%; height: auto; background: #12121f; border-radius: 12px;
        cursor: crosshair; touch-action: none;
    }
    .target-tracker .panel {
        flex: 1.2; min-width: 250px; background: #22223b; border-radius: 16px; padding: 20px;
        display: flex; flex-direction: column; gap: 18px; box-shadow: inset 0 0 8px #00000088;
    }
    .target-tracker .panel h3 { margin: 0 0 5px 0; font-weight: 300; letter-spacing: 1px; color: #aab; border-bottom: 1px solid #444466; padding-bottom: 8px; }
    .target-tracker .param { display: flex; justify-content: space-between; align-items: center; font-size: 1rem; }
    .target-tracker .param span:first-child { color: #99a; }
    .target-tracker .param .value { font-weight: 600; font-size: 1.2rem; color: #8cf; }
    .target-tracker .slider-group { display: flex; flex-direction: column; gap: 4px; margin-top: 5px; }
    .target-tracker .slider-group label { font-size: 0.9rem; color: #aab; display: flex; justify-content: space-between; }
    .target-tracker input[type="range"] {
        width: 100%; height: 6px; background: #3a3a5a; border-radius: 10px; -webkit-appearance: none; appearance: none;
    }
    .target-tracker input[type="range"]::-webkit-slider-thumb {
        -webkit-appearance: none; appearance: none; width: 18px; height: 18px; border-radius: 50%;
        background: #6a8cff; cursor: pointer; border: 2px solid #aac;
    }
    .target-tracker .btn {
        background: #4a4a7a; border: none; padding: 12px 0; border-radius: 40px; font-weight: 600;
        font-size: 1.1rem; color: #fff; cursor: pointer; transition: 0.2s; margin-top: 5px; letter-spacing: 0.5px; width: 100%;
    }
    .target-tracker .btn:hover { background: #5f5f9f; transform: scale(1.02); }
    .target-tracker .btn:active { transform: scale(0.97); }
    .target-tracker .btn-primary { background: #2a7a4a; }
    .target-tracker .btn-primary:hover { background: #3f9f5f; }
    .target-tracker .btn-danger { background: #7a2a2a; }
    .target-tracker .btn-danger:hover { background: #9f3f3f; }
    .target-tracker .btn:disabled { background: #3a3a5a; color: #778; cursor: not-allowed; transform: none; }
    .target-tracker .footer-note { font-size: 0.7rem; color: #556; text-align: center; margin-top: 10px; border-top: 1px solid #333355; padding-top: 12px; }
    .target-tracker .badge { display: inline-block; padding: 4px 12px; border-radius: 30px; background: #2a2a4a; font-size: 0.8rem; color: #bcd; }
    .target-tracker .stats-box { margin-top: auto; font-size:0.85rem; background:#1a1a2e; padding:10px; border-radius:10px; }
    .target-tracker .stats-box div { margin-bottom: 5px; }
    .target-tracker .level-badge { 
        background: #3a2a4a; color: #d8c; padding: 4px 8px; border-radius: 8px; font-size: 0.9rem; text-align: center; margin-bottom: 10px;
    }
    @media (max-width: 800px) {
        .target-tracker .canvas-wrapper { min-width: unset; }
        .target-tracker .game-layout { flex-direction: column; }
    }
&lt;/style&gt;

&lt;div class="target-tracker"&gt;
    
    &lt;div id="ttScreenDisclaimer" class="screen active"&gt;
        &lt;div class="disclaimer-box"&gt;
            &lt;h2&gt;⚠️ ВНИМАНИЕ: ИССЛЕДОВАТЕЛЬСКИЙ ПРОТОТИП&lt;/h2&gt;
            &lt;p&gt;
                Данная программа &lt;strong&gt;не является сертифицированным медицинским устройством&lt;/strong&gt;.&lt;br /&gt;
                Использование пациентами с когнитивными или моторными нарушениями допускается &lt;span class="warning-text"&gt;исключительно под контролем врача&lt;/span&gt;.&lt;br /&gt;
                Рекомендуемый возраст: 18+. Данные хранятся только локально в вашем браузере.
            &lt;/p&gt;
            &lt;p&gt;Выберите вашу роль для продолжения:&lt;/p&gt;
            &lt;div style="display: flex; gap: 10px; justify-content: center; flex-wrap: wrap;"&gt;
                &lt;button class="btn" onclick="ttApp.setRole('patient')"&gt;? Пациент / Испытуемый&lt;/button&gt;
                &lt;button class="btn" onclick="ttApp.setRole('doctor')"&gt;? Врач / Тестировщик&lt;/button&gt;
                &lt;button class="btn" onclick="ttApp.setRole('scientist')"&gt;? Учёный / Исследователь&lt;/button&gt;
            &lt;/div&gt;
        &lt;/div&gt;
    &lt;/div&gt;

    
    &lt;div id="ttScreenTest" class="screen"&gt;
        &lt;div class="game-layout"&gt;
            &lt;div class="canvas-wrapper"&gt;
                &lt;canvas id="ttGameCanvas" width="800" height="600"&gt;&lt;/canvas&gt;
                &lt;div id="ttPauseOverlay" style="display:none; position:absolute; top:0; left:0; width:100%; height:100%; background:rgba(0,0,0,0.8); border-radius:16px; justify-content:center; align-items:center; color:white; font-size:1.5rem; flex-direction:column; z-index: 10;"&gt;
                    &lt;div&gt;⏸ ТЕСТ ПРИОСТАНОВЛЕН&lt;/div&gt;
                    &lt;div style="font-size:1rem; color:#aab; margin-top:10px;"&gt;Верните курсор в область теста&lt;/div&gt;
                &lt;/div&gt;
                &lt;div id="ttIdleOverlay" style="position:absolute; top:0; left:0; width:100%; height:100%; background:rgba(0,0,0,0.6); border-radius:16px; justify-content:center; align-items:center; color:white; font-size:1.2rem; flex-direction:column; z-index: 5;"&gt;
                    &lt;div&gt;Нажмите "Начать тест" для запуска&lt;/div&gt;
                &lt;/div&gt;
            &lt;/div&gt;

            &lt;div class="panel"&gt;
                &lt;div style="display: flex; justify-content: space-between; align-items: center;"&gt;
                    &lt;h3 style="border: none; margin:0;"&gt;? Телеметрия&lt;/h3&gt;
                    &lt;span class="badge" id="ttRoleBadge"&gt;Роль&lt;/span&gt;
                &lt;/div&gt;

                &lt;div class="level-badge"&gt;Уровень адаптивной сложности: &lt;strong id="ttLevelDisplay"&gt;5&lt;/strong&gt; / 10&lt;/div&gt;

                &lt;div class="param"&gt;
                    &lt;span&gt;⏱ Время сессии&lt;/span&gt;
                    &lt;span class="value" id="ttSessionTime"&gt;00:00&lt;/span&gt;
                &lt;/div&gt;
                &lt;div class="param"&gt;
                    &lt;span&gt;? Расстояние до центра&lt;/span&gt;
                    &lt;span class="value" id="ttDistanceDisplay"&gt;0 px&lt;/span&gt;
                &lt;/div&gt;
                &lt;div class="param"&gt;
                    &lt;span&gt;⏱ Время в цели (сек)&lt;/span&gt;
                    &lt;span class="value" id="ttTimeOnTarget"&gt;0.00&lt;/span&gt;
                &lt;/div&gt;
                
                &lt;div id="ttDoctorControls" style="display: none;"&gt;
                    &lt;div class="slider-group"&gt;
                        &lt;label&gt;? Базовая скорость &lt;span id="ttSpeedValue"&gt;100&lt;/span&gt; px/с&lt;/label&gt;
                        &lt;input type="range" id="ttSpeedSlider" min="50" max="300" value="100" step="10" /&gt;
                    &lt;/div&gt;
                    &lt;div class="slider-group"&gt;
                        &lt;label&gt;? Базовый радиус &lt;span id="ttRadiusValue"&gt;40&lt;/span&gt; px&lt;/label&gt;
                        &lt;input type="range" id="ttRadiusSlider" min="20" max="80" value="40" step="5" /&gt;
                    &lt;/div&gt;
                &lt;/div&gt;

                &lt;button class="btn btn-primary" id="ttStartBtn"&gt;▶ Начать тест&lt;/button&gt;
                &lt;button class="btn btn-danger" id="ttStopBtn" disabled="disabled"&gt;⏹ Завершить и сохранить&lt;/button&gt;
                &lt;button class="btn" onclick="ttApp.goToDisclaimer()" style="margin-top: 15px;"&gt;? Сменить роль&lt;/button&gt;

                &lt;div class="stats-box"&gt;
                    &lt;div&gt;? Входов в цель: &lt;strong id="ttHitCount"&gt;0&lt;/strong&gt;&lt;/div&gt;
                    &lt;div&gt;? Выходов из цели: &lt;strong id="ttMissCount"&gt;0&lt;/strong&gt;&lt;/div&gt;
                    &lt;div style="margin-top:5px; color:#8af; border-top: 1px solid #333; padding-top:5px;"&gt;
                        Точность удержания: &lt;strong id="ttAccuracyDisplay"&gt;0%&lt;/strong&gt;
                    &lt;/div&gt;
                &lt;/div&gt;

                &lt;div class="footer-note"&gt;⚠️ Данные сохраняются локально. Прототип v0.3&lt;/div&gt;
            &lt;/div&gt;
        &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;

&lt;script&gt;
    const ttApp = {
        role: 'patient',
        setRole: function(selectedRole) {
            this.role = selectedRole;
            document.getElementById('ttRoleBadge').textContent = 
                selectedRole === 'patient' ? 'Пациент' : selectedRole === 'doctor' ? 'Врач' : 'Учёный';
            
            document.getElementById('ttDoctorControls').style.display = 
                (selectedRole === 'doctor' || selectedRole === 'scientist') ? 'block' : 'none';

            document.getElementById('ttScreenDisclaimer').classList.remove('active');
            document.getElementById('ttScreenTest').classList.add('active');
            
            ttGame.init();
        },
        goToDisclaimer: function() {
            ttGame.stop();
            document.getElementById('ttScreenTest').classList.remove('active');
            document.getElementById('ttScreenDisclaimer').classList.add('active');
        }
    };

    const ttGame = {
        canvas: null, ctx: null, W: 800, H: 600,
        target: { x: 400, y: 300, radius: 40, angle: 0, baseSpeed: 100, currentSpeed: 100, orbitRadius: 220 },
        mouse: { x: 400, y: 300 },
        stats: { timeOnTarget: 0, hits: 0, misses: 0, totalTime: 0 },
        session: { isActive: false, startTime: 0, microCycleStart: 0, microCycleHits: 0, microCycleTotal: 0, level: 5 },
        lastTimestamp: 0, isInsideTarget: false, animationId: null, isPaused: false,

        init: function() {
            this.canvas = document.getElementById('ttGameCanvas');
            this.ctx = this.canvas.getContext('2d');
            this.bindEvents();
            this.resetSession();
            this.draw(); // Отрисовать начальный кадр
        },

        bindEvents: function() {
            const handleMove = (e) =&gt; {
                if (this.isPaused || !this.session.isActive) return;
                const rect = this.canvas.getBoundingClientRect();
                const scaleX = this.canvas.width / rect.width;
                const scaleY = this.canvas.height / rect.height;
                const clientX = e.touches ? e.touches[0].clientX : e.clientX;
                const clientY = e.touches ? e.touches[0].clientY : e.clientY;
                
                this.mouse.x = Math.min(Math.max((clientX - rect.left) * scaleX, 0), this.W);
                this.mouse.y = Math.min(Math.max((clientY - rect.top) * scaleY, 0), this.H);
            };

            this.canvas.addEventListener('mousemove', handleMove);
            this.canvas.addEventListener('touchmove', handleMove, { passive: false });
            this.canvas.addEventListener('touchstart', handleMove, { passive: false });

            this.canvas.addEventListener('mouseleave', () =&gt; this.pause(true));
            this.canvas.addEventListener('mouseenter', () =&gt; this.pause(false));
            window.addEventListener('blur', () =&gt; this.pause(true));
            window.addEventListener('focus', () =&gt; this.pause(false));

            document.getElementById('ttSpeedSlider').addEventListener('input', (e) =&gt; {
                this.target.baseSpeed = parseFloat(e.target.value);
                this.target.currentSpeed = this.target.baseSpeed;
                document.getElementById('ttSpeedValue').textContent = this.target.baseSpeed;
            });
            document.getElementById('ttRadiusSlider').addEventListener('input', (e) =&gt; {
                this.target.radius = parseFloat(e.target.value);
                document.getElementById('ttRadiusValue').textContent = this.target.radius;
            });

            document.getElementById('ttStartBtn').addEventListener('click', () =&gt; this.startSession());
            document.getElementById('ttStopBtn').addEventListener('click', () =&gt; this.stopSession());
        },

        resetSession: function() {
            this.session = { isActive: false, startTime: 0, microCycleStart: 0, microCycleHits: 0, microCycleTotal: 0, level: 5 };
            this.stats = { timeOnTarget: 0, hits: 0, misses: 0, totalTime: 0 };
            this.isInsideTarget = false;
            this.target.currentSpeed = this.target.baseSpeed;
            this.updateUI();
            document.getElementById('ttIdleOverlay').style.display = 'flex';
            document.getElementById('ttStartBtn').disabled = false;
            document.getElementById('ttStopBtn').disabled = true;
            document.getElementById('ttLevelDisplay').textContent = '5';
        },

        startSession: function() {
            this.session.isActive = true;
            this.session.startTime = performance.now();
            this.session.microCycleStart = performance.now();
            this.session.microCycleHits = 0;
            this.session.microCycleTotal = 0;
            
            document.getElementById('ttIdleOverlay').style.display = 'none';
            document.getElementById('ttStartBtn').disabled = true;
            document.getElementById('ttStopBtn').disabled = false;
            
            this.lastTimestamp = performance.now();
            this.isPaused = false;
            this.loop(this.lastTimestamp);
        },

        stopSession: function() {
            this.session.isActive = false;
            this.isPaused = true;
            if (this.animationId) cancelAnimationFrame(this.animationId);
            
            // Формируем протокол
            const protocol = {
                id: 'SES-' + Date.now(),
                date: new Date().toISOString(),
                role: ttApp.role,
                durationSec: this.stats.totalTime.toFixed(2),
                timeOnTargetSec: this.stats.timeOnTarget.toFixed(2),
                accuracyPercent: (this.stats.totalTime &gt; 0 ? (this.stats.timeOnTarget / this.stats.totalTime * 100) : 0).toFixed(1),
                hits: this.stats.hits,
                misses: this.stats.misses,
                finalLevel: this.session.level,
                baseSpeed: this.target.baseSpeed,
                baseRadius: parseFloat(document.getElementById('ttRadiusSlider').value)
            };

            // Сохраняем в localStorage
            let history = JSON.parse(localStorage.getItem('ttProtocols') || '[]');
            history.push(protocol);
            localStorage.setItem('ttProtocols', JSON.stringify(history));

            alert(`✅ Тест завершен!\nТочность: ${protocol.accuracyPercent}%\nВремя в цели: ${protocol.timeOnTargetSec} сек.\nПротокол сохранен локально (ID: ${protocol.id})`);
            
            this.resetSession();
        },

        pause: function(state) {
            if (!this.session.isActive) return;
            this.isPaused = state;
            document.getElementById('ttPauseOverlay').style.display = state ? 'flex' : 'none';
            if (!state) this.lastTimestamp = performance.now();
        },

        getDistance: function(x1, y1, x2, y2) { return Math.hypot(x2 - x1, y2 - y1); },

        checkAdaptiveDifficulty: function(now) {
            const cycleDuration = (now - this.session.microCycleStart) / 1000; // в секундах
            if (cycleDuration &gt;= 5.0) { // Микро-цикл 5 секунд
                const cycleAccuracy = this.session.microCycleTotal &gt; 0 
                    ? (this.session.microCycleHits / this.session.microCycleTotal) 
                    : 0;

                // Логика адаптации (пороги 40% и 80%)
                if (cycleAccuracy &lt; 0.40 &amp;&amp; this.session.level &gt; 1) {
                    this.session.level--;
                    this.target.currentSpeed = Math.max(50, this.target.currentSpeed * 0.9); // -10% скорости
                    this.target.radius = Math.min(80, this.target.radius * 1.1); // +10% размера
                } else if (cycleAccuracy &gt; 0.80 &amp;&amp; this.session.level &lt; 10) {
                    this.session.level++;
                    this.target.currentSpeed = Math.min(300, this.target.currentSpeed * 1.1); // +10% скорости
                    this.target.radius = Math.max(20, this.target.radius * 0.9); // -10% размера
                }

                // Сброс счетчиков микро-цикла
                this.session.microCycleStart = now;
                this.session.microCycleHits = 0;
                this.session.microCycleTotal = 0;
                
                document.getElementById('ttLevelDisplay').textContent = this.session.level;
                
                // Обновляем ползунки визуально, если они есть
                document.getElementById('ttSpeedValue').textContent = Math.round(this.target.currentSpeed);
                document.getElementById('ttRadiusValue').textContent = Math.round(this.target.radius);
            }
        },

        update: function(deltaSec, now) {
            if (this.isPaused || !this.session.isActive) return;

            // Движение мишени
            this.target.angle += deltaSec * (this.target.currentSpeed / this.target.orbitRadius) * 0.8;
            this.target.x = this.W/2 + Math.cos(this.target.angle) * this.target.orbitRadius;
            this.target.y = this.H/2 + Math.sin(this.target.angle) * this.target.orbitRadius * 0.6;

            const dist = this.getDistance(this.mouse.x, this.mouse.y, this.target.x, this.target.y);
            const isInside = dist &lt;= this.target.radius;
            
            this.stats.totalTime += deltaSec;
            this.session.microCycleTotal += deltaSec;

            if (isInside &amp;&amp; !this.isInsideTarget) this.stats.hits++;
            else if (!isInside &amp;&amp; this.isInsideTarget) this.stats.misses++;
            
            this.isInsideTarget = isInside;
            if (isInside) {
                this.stats.timeOnTarget += deltaSec;
                this.session.microCycleHits += deltaSec;
            }

            this.checkAdaptiveDifficulty(now);
            this.updateUI(dist);
        },

        draw: function() {
            this.ctx.clearRect(0, 0, this.W, this.H);
            // Сетка
            this.ctx.strokeStyle = '#2a2a44'; this.ctx.lineWidth = 0.5;
            for (let i = 0; i &lt; this.W; i += 40) {
                this.ctx.beginPath(); this.ctx.moveTo(i, 0); this.ctx.lineTo(i, this.H); this.ctx.stroke();
            }
            for (let i = 0; i &lt; this.H; i += 40) {
                this.ctx.beginPath(); this.ctx.moveTo(0, i); this.ctx.lineTo(this.W, i); this.ctx.stroke();
            }
            // Мишень
            this.ctx.beginPath();
            this.ctx.arc(this.target.x, this.target.y, this.target.radius, 0, Math.PI * 2);
            this.ctx.fillStyle = 'rgba(40, 80, 180, 0.2)'; this.ctx.fill();
            this.ctx.strokeStyle = '#4a8cff'; this.ctx.lineWidth = 2; this.ctx.stroke();
            this.ctx.beginPath();
            this.ctx.arc(this.target.x, this.target.y, 4, 0, Math.PI * 2);
            this.ctx.fillStyle = '#ff6b6b'; this.ctx.fill();

            // Курсор
            const dist = this.getDistance(this.mouse.x, this.mouse.y, this.target.x, this.target.y);
            const isOnTarget = dist &lt;= this.target.radius;
            this.ctx.strokeStyle = isOnTarget ? '#5f5' : '#fff'; this.ctx.lineWidth = 2;
            const size = 10;
            this.ctx.beginPath();
            this.ctx.moveTo(this.mouse.x - size, this.mouse.y); this.ctx.lineTo(this.mouse.x + size, this.mouse.y);
            this.ctx.moveTo(this.mouse.x, this.mouse.y - size); this.ctx.lineTo(this.mouse.x, this.mouse.y + size);
            this.ctx.stroke();

            // Линия до центра
            this.ctx.strokeStyle = 'rgba(255,255,255,0.15)'; this.ctx.lineWidth = 1;
            this.ctx.setLineDash([4, 6]);
            this.ctx.beginPath(); this.ctx.moveTo(this.mouse.x, this.mouse.y); this.ctx.lineTo(this.target.x, this.target.y);
            this.ctx.stroke(); this.ctx.setLineDash([]);
        },

        updateUI: function(currentDist = 0) {
            document.getElementById('ttDistanceDisplay').textContent = Math.round(currentDist) + ' px';
            document.getElementById('ttTimeOnTarget').textContent = this.stats.timeOnTarget.toFixed(2);
            document.getElementById('ttHitCount').textContent = this.stats.hits;
            document.getElementById('ttMissCount').textContent = this.stats.misses;
            const accuracy = this.stats.totalTime &gt; 0 ? (this.stats.timeOnTarget / this.stats.totalTime * 100) : 0;
            document.getElementById('ttAccuracyDisplay').textContent = accuracy.toFixed(1) + '%';
            
            // Форматирование времени сессии
            if (this.session.isActive &amp;&amp; !this.isPaused) {
                const elapsed = Math.floor((performance.now() - this.session.startTime) / 1000);
                const m = Math.floor(elapsed / 60).toString().padStart(2, '0');
                const s = (elapsed % 60).toString().padStart(2, '0');
                document.getElementById('ttSessionTime').textContent = `${m}:${s}`;
            }
        },

        loop: function(timestamp) {
            if (!this.session.isActive) return;
            const deltaMs = timestamp - this.lastTimestamp;
            this.lastTimestamp = timestamp;
            const deltaSec = Math.min(deltaMs / 1000, 0.05);
            
            this.update(deltaSec, timestamp);
            this.draw();
            
            this.animationId = requestAnimationFrame((t) =&gt; this.loop(t));
        }
    };
&lt;/script&gt;</subtitle>
  <updated>2026-08-22T11:41:37Z</updated>
  <logo>https://pandoraburo.ru/ssl/u/pic/4b/bcd80a9d3e11f18b98ed4e8ec7cd6a/-/1787302250.png</logo>
  <author>
    <name>1592962</name>
    <uri>https://www.pandoraburo.ru/</uri>
  </author>
</feed>
