// ========== ДАННЫЕ О ЛЕКАРСТВАХ ========== const MEDICATIONS = [ // УТРО (7:00) { name: "Эутирокс", dose: "1 таблетка", time: "07:00", period: "утро", instruction: "утром" }, { name: "Йодомарин", dose: "1 таблетка", time: "07:00", period: "утро", instruction: "после завтрака" }, { name: "Биовит", dose: "1 ст. ложка", time: "07:00", period: "утро", instruction: "после еды" }, // ПЕРЕД ОБЕДОМ (11:30) { name: "Одестон 200", dose: "1 таблетка", time: "11:30", period: "обед", instruction: "за 20 мин до еды" }, { name: "Аминосорб", dose: "1 ст. ложка", time: "11:30", period: "обед", instruction: "за 30 мин до еды" }, // ОБЕД (12:00) { name: "Креон", dose: "3 таблетки", time: "12:00", period: "обед", instruction: "во время еды" }, // ПОСЛЕ ОБЕДА (14:00) { name: "Урсосан 250", dose: "2 таблетки", time: "14:00", period: "обед", instruction: "после обеда" }, { name: "Биовит", dose: "1 ст. ложка", time: "14:00", period: "обед", instruction: "после еды" }, // ПЕРЕД УЖИНОМ (17:30) { name: "Одестон 200", dose: "1 таблетка", time: "17:30", period: "ужин", instruction: "за 20 мин до еды" }, { name: "Аминосорб", dose: "1 ст. ложка", time: "17:30", period: "ужин", instruction: "за 30 мин до еды" }, // УЖИН (18:00) { name: "Креон", dose: "3 таблетки", time: "18:00", period: "ужин", instruction: "во время еды" }, // ПОСЛЕ УЖИНА (20:00) { name: "Урсосан 250", dose: "2 таблетки", time: "20:00", period: "ужин", instruction: "после ужина" }, { name: "Биовит", dose: "1 ст. ложка", time: "20:00", period: "ужин", instruction: "после еды" } ]; // Уникальные времена const TIMES = ["07:00", "11:30", "12:00", "14:00", "17:30", "18:00", "20:00"]; // ========== СОСТОЯНИЕ ========== let takenMeds = JSON.parse(localStorage.getItem('meds_taken') || '{}'); let notificationPermission = 'default'; let audioCtx = null; // ========== ИНИЦИАЛИЗАЦИЯ ========== document.addEventListener('DOMContentLoaded', () => { checkNotificationPermission(); renderSchedule(); updateClock(); setInterval(updateClock, 1000); setInterval(checkReminders, 10000); updateTakenCount(); }); // ========== ЧАСЫ ========== function updateClock() { const now = new Date(); document.getElementById('current-time').textContent = now.toLocaleTimeString('ru-RU', { hour: '2-digit', minute: '2-digit', second: '2-digit' }); updateNextReminder(); updateStatus(); } // ========== УВЕДОМЛЕНИЯ ========== function checkNotificationPermission() { if (!('Notification' in window)) { document.getElementById('notif-status').textContent = 'Браузер не поддерживает уведомления'; document.getElementById('notif-btn').classList.add('hidden'); return; } notificationPermission = Notification.permission; updateNotificationUI(); } function updateNotificationUI() { const status = document.getElementById('notif-status'); const btn = document.getElementById('notif-btn'); if (notificationPermission === 'granted') { status.textContent = '✅ Уведомления включены'; btn.classList.add('hidden'); } else if (notificationPermission === 'denied') { status.textContent = '❌ Уведомления заблокированы. Включите в настройках браузера'; btn.classList.add('hidden'); } else { status.textContent = 'Уведомления не включены'; btn.classList.remove('hidden'); } } function requestNotificationPermission() { if (!('Notification' in window)) return; Notification.requestPermission().then(permission => { notificationPermission = permission; updateNotificationUI(); if (permission === 'granted') { sendNotification('Готово! 🔔', 'Уведомления о приёме лекарств включены'); } }); } function sendNotification(title, body) { if (notificationPermission === 'granted') { new Notification(title, { body: body, icon: '💊', badge: '💊', tag: 'med-reminder', requireInteraction: true }); } playSound(); } // ========== ЗВУК ========== function playSound() { try { if (!audioCtx) { audioCtx = new (window.AudioContext || window.webkitAudioContext)(); } const oscillator = audioCtx.createOscillator(); const gainNode = audioCtx.createGain(); oscillator.connect(gainNode); gainNode.connect(audioCtx.destination); oscillator.frequency.value = 800; oscillator.type = 'sine'; gainNode.gain.setValueAtTime(0.3, audioCtx.currentTime); gainNode.gain.exponentialRampToValueAtTime(0.01, audioCtx.currentTime + 0.5); oscillator.start(audioCtx.currentTime); oscillator.stop(audioCtx.currentTime + 0.5); } catch (e) { console.log('Audio not supported'); } } // ========== РАСПИСАНИЕ ========== function renderSchedule() { const container = document.getElementById('schedule'); const now = new Date(); const currentTime = now.getHours().toString().padStart(2, '0') + ':' + now.getMinutes().toString().padStart(2, '0'); let html = ''; for (const time of TIMES) { const medsAtTime = MEDICATIONS.filter(m => m.time === time); const isPast = time < currentTime; const isCurrent = isTimeNear(time, currentTime); let blockClass = 'time-block'; if (isCurrent) blockClass += ' active'; else if (isPast) blockClass += ' past'; html += `
`; html += `
`; html += `${time}`; if (isCurrent) html += `СЕЙЧАС`; html += `
`; for (const med of medsAtTime) { const key = `${time}_${med.name}`; const isTaken = takenMeds[key]; html += `
`; html += `${isTaken ? '✅' : '💊'}`; html += `${med.name} — ${med.dose}`; html += `(${med.instruction})`; html += `
`; } html += `
`; } container.innerHTML = html; } function isTimeNear(targetTime, currentTime) { const [tH, tM] = targetTime.split(':').map(Number); const [cH, cM] = currentTime.split(':').map(Number); const targetMin = tH * 60 + tM; const currentMin = cH * 60 + cM; return Math.abs(targetMin - currentMin) <= 15; } // ========== ОТМЕТА ПРИНЯТИЯ ========== function toggleMed(key) { takenMeds[key] = !takenMeds[key]; localStorage.setItem('meds_taken', JSON.stringify(takenMeds)); renderSchedule(); updateTakenCount(); if (takenMeds[key]) { playSound(); } } function markAllCurrentAsTaken() { const now = new Date(); const currentTime = now.getHours().toString().padStart(2, '0') + ':' + now.getMinutes().toString().padStart(2, '0'); for (const time of TIMES) { if (isTimeNear(time, currentTime) || time <= currentTime) { const medsAtTime = MEDICATIONS.filter(m => m.time === time); for (const med of medsAtTime) { const key = `${time}_${med.name}`; takenMeds[key] = true; } } } localStorage.setItem('meds_taken', JSON.stringify(takenMeds)); renderSchedule(); updateTakenCount(); playSound(); } function resetDay() { if (confirm('Сбросить все отметки за сегодня?')) { takenMeds = {}; localStorage.setItem('meds_taken', JSON.stringify(takenMeds)); renderSchedule(); updateTakenCount(); } } // ========== СТАТИСТИКА ========== function updateTakenCount() { const total = MEDICATIONS.length; const taken = Object.values(takenMeds).filter(v => v).length; document.getElementById('taken-count').textContent = `${taken} / ${total}`; const pct = total > 0 ? (taken / total * 100) : 0; document.getElementById('progress-bar').style.width = pct + '%'; } // ========== СЛЕДУЮЩЕЕ НАПОМИНАНИЕ ========== function updateNextReminder() { const now = new Date(); const currentTime = now.getHours().toString().padStart(2, '0') + ':' + now.getMinutes().toString().padStart(2, '0'); let nextTime = null; let nextMeds = []; for (const time of TIMES) { if (time > currentTime) { nextTime = time; nextMeds = MEDICATIONS.filter(m => m.time === time); break; } } // Если все прошли сегодня - берём первое завтра if (!nextTime) { nextTime = TIMES[0]; nextMeds = MEDICATIONS.filter(m => m.time === TIMES[0]); } const medNames = [...new Set(nextMeds.map(m => m.name))].join(', '); document.getElementById('next-med-name').textContent = medNames; document.getElementById('next-med-time').textContent = `В ${nextTime} (${nextMeds[0].instruction})`; } function updateStatus() { const now = new Date(); const currentTime = now.getHours().toString().padStart(2, '0') + ':' + now.getMinutes().toString().padStart(2, '0'); let nextTime = null; for (const time of TIMES) { if (time > currentTime) { nextTime = time; break; } } if (!nextTime) nextTime = TIMES[0] + ' (завтра)'; document.getElementById('status-text').textContent = `Следующий приём: ${nextTime}`; // Countdown if (nextTime && !nextTime.includes('завтра')) { const [nH, nM] = nextTime.split(':').map(Number); const [cH, cM] = currentTime.split(':').map(Number); const diff = (nH * 60 + nM) - (cH * 60 + cM); if (diff > 0) { const h = Math.floor(diff / 60); const m = diff % 60; document.getElementById('next-in').textContent = `через ${h > 0 ? h + ' ч ' : ''}${m} мин`; } else { document.getElementById('next-in').textContent = 'время приёма!'; } } } // ========== НАПОМИНАНИЯ ========== function checkReminders() { const now = new Date(); const currentTime = now.getHours().toString().padStart(2, '0') + ':' + now.getMinutes().toString().padStart(2, '0'); for (const time of TIMES) { if (isTimeNear(time, currentTime)) { const key = `notified_${time}_${now.toDateString()}`; if (!sessionStorage.getItem(key)) { const meds = MEDICATIONS.filter(m => m.time === time); const names = [...new Set(meds.map(m => m.name))].join(', '); sendNotification( '💊 Время принимать лекарства!', `${names} — ${meds[0].instruction}` ); sessionStorage.setItem(key, 'true'); } } } } // Сохраняем при закрытии window.addEventListener('beforeunload', () => { localStorage.setItem('meds_taken', JSON.stringify(takenMeds)); });