medtrack / index.html
ruslan312's picture
Add 1 files
9ba306b verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>MedTrack - Premium Medication Reminder</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700&display=swap" rel="stylesheet">
<style>
:root {
--primary: #6c5ce7;
--primary-light: #a29bfe;
--primary-dark: #5649c2;
--secondary: #00cec9;
--success: #00b894;
--danger: #d63031;
--warning: #fdcb6e;
--light: #f5f6fa;
--dark: #2d3436;
--gray: #636e72;
--gray-light: #dfe6e9;
--white: #ffffff;
--glass: rgba(255, 255, 255, 0.25);
--glass-border: rgba(255, 255, 255, 0.4);
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: 'Poppins', sans-serif;
}
body {
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
color: var(--dark);
line-height: 1.6;
min-height: 100vh;
}
/* Floating background elements */
.bg-shapes {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
overflow: hidden;
z-index: -1;
}
.bg-shape {
position: absolute;
border-radius: 50%;
opacity: 0.1;
filter: blur(50px);
}
.shape-1 {
width: 400px;
height: 400px;
background: var(--primary);
top: -100px;
left: -100px;
}
.shape-2 {
width: 600px;
height: 600px;
background: var(--secondary);
bottom: -200px;
right: -200px;
}
.container {
max-width: 900px;
margin: 0 auto;
padding: 30px;
animation: fadeIn 0.8s ease-out;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(20px); }
to { opacity: 1; transform: translateY(0); }
}
header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 40px;
padding-bottom: 20px;
border-bottom: 1px solid rgba(255, 255, 255, 0.5);
}
.logo {
display: flex;
align-items: center;
gap: 15px;
}
.logo-icon {
width: 50px;
height: 50px;
background: linear-gradient(135deg, var(--primary) 0%, var(--primary-dark) 100%);
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 8px 20px rgba(108, 92, 231, 0.3);
}
.logo-icon i {
color: var(--white);
font-size: 24px;
}
.logo-text h1 {
font-size: 28px;
font-weight: 700;
background: linear-gradient(135deg, var(--primary) 0%, var(--primary-dark) 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
margin-bottom: 3px;
}
.logo-text p {
font-size: 12px;
color: var(--gray);
letter-spacing: 1px;
text-transform: uppercase;
}
/* Buttons */
.btn {
padding: 12px 20px;
border: none;
border-radius: 12px;
cursor: pointer;
font-weight: 600;
transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1);
display: inline-flex;
align-items: center;
gap: 10px;
font-size: 14px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
}
.btn i {
font-size: 16px;
}
.btn-primary {
background: linear-gradient(135deg, var(--primary) 0%, var(--primary-dark) 100%);
color: var(--white);
}
.btn-primary:hover {
box-shadow: 0 8px 20px rgba(108, 92, 231, 0.4);
transform: translateY(-2px);
}
.btn-secondary {
background: linear-gradient(135deg, var(--secondary) 0%, #00a6b3 100%);
color: var(--white);
}
.btn-secondary:hover {
box-shadow: 0 8px 20px rgba(0, 206, 201, 0.4);
transform: translateY(-2px);
}
.btn-success {
background: linear-gradient(135deg, var(--success) 0%, #00967e 100%);
color: var(--white);
}
.btn-success:hover {
box-shadow: 0 8px 20px rgba(0, 184, 148, 0.4);
transform: translateY(-2px);
}
.btn-danger {
background: linear-gradient(135deg, var(--danger) 0%, #c02929 100%);
color: var(--white);
}
.btn-danger:hover {
box-shadow: 0 8px 20px rgba(214, 48, 49, 0.4);
transform: translateY(-2px);
}
.btn-outline {
background-color: transparent;
border: 2px solid var(--glass-border);
color: var(--dark);
backdrop-filter: blur(5px);
}
.btn-outline:hover {
background-color: var(--glass);
transform: translateY(-2px);
}
/* Dashboard Section */
.dashboard {
margin-bottom: 40px;
}
.stats {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 20px;
margin-bottom: 30px;
}
.stat-card {
background: var(--glass);
backdrop-filter: blur(10px);
border: 1px solid var(--glass-border);
padding: 25px;
border-radius: 16px;
box-shadow: 0 8px 32px 0 rgba(31, 38, 135, 0.1);
transition: all 0.3s ease;
}
.stat-card:hover {
transform: translateY(-5px);
box-shadow: 0 8px 20px rgba(108, 92, 231, 0.2);
}
.stat-card h3 {
font-size: 14px;
font-weight: 500;
color: var(--gray);
margin-bottom: 10px;
}
.stat-card p {
font-size: 28px;
font-weight: 700;
}
/* Upcoming Medications */
.upcoming {
background: var(--glass);
backdrop-filter: blur(10px);
border: 1px solid var(--glass-border);
border-radius: 16px;
box-shadow: 0 8px 32px 0 rgba(31, 38, 135, 0.1);
padding: 25px;
transition: all 0.3s ease;
}
.upcoming:hover {
box-shadow: 0 8px 20px rgba(108, 92, 231, 0.2);
}
.section-title {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 25px;
}
.section-title h2 {
font-size: 20px;
font-weight: 600;
display: flex;
align-items: center;
gap: 10px;
}
.section-title i {
color: var(--primary);
}
.medication-list {
display: flex;
flex-direction: column;
gap: 15px;
}
.medication-item {
display: flex;
align-items: center;
padding: 20px;
border-radius: 12px;
background: var(--white);
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.05);
transition: all 0.4s cubic-bezier(0.25, 0.8, 0.25, 1);
position: relative;
overflow: hidden;
}
.medication-item:hover {
transform: translateY(-3px);
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.1);
}
.medication-item::before {
content: '';
position: absolute;
top: 0;
left: 0;
width: 5px;
height: 100%;
background: linear-gradient(to bottom, var(--primary), var(--secondary));
}
.med-icon {
width: 55px;
height: 55px;
background: linear-gradient(135deg, var(--primary) 0%, var(--secondary) 100%);
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
margin-right: 20px;
color: var(--white);
font-size: 22px;
box-shadow: 0 4px 15px rgba(108, 92, 231, 0.3);
}
.med-details {
flex: 1;
}
.med-details h3 {
font-size: 18px;
margin-bottom: 8px;
font-weight: 600;
}
.med-details p {
font-size: 14px;
color: var(--gray);
}
.med-note {
font-size: 13px;
color: var(--gray);
margin-top: 5px;
display: flex;
align-items: center;
gap: 5px;
}
.med-time {
display: flex;
flex-direction: column;
align-items: flex-end;
}
.med-time p {
font-weight: 600;
margin-bottom: 10px;
font-size: 15px;
background: linear-gradient(135deg, var(--primary) 0%, var(--secondary) 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
/* Modal */
.modal {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(45, 52, 54, 0.8);
z-index: 1000;
justify-content: center;
align-items: center;
backdrop-filter: blur(5px);
animation: fadeIn 0.3s ease-out;
}
.modal-content {
background: var(--white);
padding: 30px;
border-radius: 20px;
width: 90%;
max-width: 500px;
max-height: 90vh;
overflow-y: auto;
box-shadow: 0 20px 50px rgba(0, 0, 0, 0.3);
transform: translateY(0);
transition: all 0.4s cubic-bezier(0.25, 0.8, 0.25, 1);
}
.modal.show .modal-content {
animation: modalSlideIn 0.4s ease-out;
}
@keyframes modalSlideIn {
from { transform: translateY(50px); opacity: 0; }
to { transform: translateY(0); opacity: 1; }
}
.modal-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 25px;
}
.modal-header h2 {
font-size: 24px;
font-weight: 600;
color: var(--primary);
}
.close-btn {
background: none;
border: none;
font-size: 28px;
cursor: pointer;
color: var(--gray);
transition: all 0.3s ease;
}
.close-btn:hover {
color: var(--danger);
transform: rotate(90deg);
}
.form-group {
margin-bottom: 25px;
}
.form-group label {
display: block;
margin-bottom: 10px;
font-weight: 500;
color: var(--dark);
}
.form-control {
width: 100%;
padding: 14px 20px;
border: 2px solid var(--gray-light);
border-radius: 12px;
font-size: 16px;
transition: all 0.3s ease;
background-color: var(--light);
}
.form-control:focus {
outline: none;
border-color: var(--primary);
box-shadow: 0 0 0 3px rgba(108, 92, 231, 0.2);
}
.time-inputs {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 15px;
}
textarea.form-control {
min-height: 100px;
resize: vertical;
}
.modal-footer {
display: flex;
justify-content: flex-end;
gap: 15px;
margin-top: 30px;
}
/* Notification */
.notification {
position: fixed;
bottom: 30px;
right: 30px;
background: linear-gradient(135deg, var(--success) 0%, #00967e 100%);
color: var(--white);
padding: 18px 30px;
border-radius: 12px;
box-shadow: 0 10px 30px rgba(0, 184, 148, 0.3);
display: flex;
align-items: center;
gap: 15px;
transform: translateX(150%);
opacity: 0;
transition: all 0.4s cubic-bezier(0.25, 0.8, 0.25, 1);
z-index: 100;
}
.notification.show {
transform: translateX(0);
opacity: 1;
}
.notification i {
font-size: 24px;
}
.notification.success {
background: linear-gradient(135deg, var(--success) 0%, #00967e 100%);
}
.notification.warning {
background: linear-gradient(135deg, var(--warning) 0%, #f8a84a 100%);
}
.notification.error {
background: linear-gradient(135deg, var(--danger) 0%, #c02929 100%);
}
/* Empty state */
.empty-state {
text-align: center;
padding: 40px 0;
}
.empty-state i {
font-size: 60px;
color: var(--gray-light);
margin-bottom: 20px;
}
.empty-state h3 {
font-size: 18px;
color: var(--gray);
margin-bottom: 10px;
}
.empty-state p {
font-size: 14px;
color: var(--gray-light);
margin-bottom: 20px;
}
/* Notification permissions section */
.notification-permission {
background: var(--glass);
backdrop-filter: blur(10px);
border: 1px solid var(--glass-border);
border-radius: 16px;
padding: 20px;
margin-bottom: 30px;
display: flex;
justify-content: space-between;
align-items: center;
animation: fadeIn 0.6s ease-out;
}
.notification-permission p {
font-size: 14px;
color: var(--gray);
display: flex;
align-items: center;
gap: 10px;
}
.notification-permission i {
color: var(--warning);
font-size: 18px;
}
/* Responsive */
@media (max-width: 768px) {
.container {
padding: 20px;
}
.stats {
grid-template-columns: 1fr;
}
.medication-item {
flex-direction: column;
text-align: center;
}
.med-icon {
margin-right: 0;
margin-bottom: 20px;
}
.med-time {
align-items: center;
margin-top: 15px;
}
.time-inputs {
grid-template-columns: 1fr;
}
}
/* Animation on scroll */
.animate-on-scroll {
opacity: 0;
transform: translateY(30px);
transition: all 0.6s ease-out;
}
.animate-on-scroll.show {
opacity: 1;
transform: translateY(0);
}
/* Loading spinner */
.spinner {
display: inline-block;
width: 20px;
height: 20px;
border: 3px solid rgba(255,255,255,.3);
border-radius: 50%;
border-top-color: var(--white);
animation: spin 1s ease-in-out infinite;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
</style>
</head>
<body>
<div class="bg-shapes">
<div class="bg-shape shape-1"></div>
<div class="bg-shape shape-2"></div>
</div>
<div class="container">
<header>
<div class="logo">
<div class="logo-icon">
<i class="fas fa-pills"></i>
</div>
<div class="logo-text">
<h1>MedTrack</h1>
<p>Premium Medication Manager</p>
</div>
</div>
<button class="btn btn-primary" id="addMedBtn">
<i class="fas fa-plus"></i> Add Medication
</button>
</header>
<!-- Notification Permission Banner -->
<div class="notification-permission animate-on-scroll" id="permissionBanner" style="display: none;">
<p><i class="fas fa-bell"></i> Enable notifications to get reminders for your medications</p>
<button class="btn btn-warning" id="enableNotifications">
<span id="enableNotificationsText">Enable Notifications</span>
<span class="spinner" id="notificationSpinner" style="display: none;"></span>
</button>
</div>
<section class="dashboard">
<div class="stats">
<div class="stat-card animate-on-scroll">
<h3><i class="fas fa-clock"></i> Upcoming Today</h3>
<p id="upcomingCount">3</p>
</div>
<div class="stat-card animate-on-scroll" style="transition-delay: 0.1s;">
<h3><i class="fas fa-prescription-bottle-alt"></i> Medications</h3>
<p id="totalMeds">5</p>
</div>
<div class="stat-card animate-on-scroll" style="transition-delay: 0.2s;">
<h3><i class="fas fa-check-circle"></i> Adherence</h3>
<p id="adherenceRate">92%</p>
</div>
</div>
</section>
<section class="upcoming animate-on-scroll">
<div class="section-title">
<h2><i class="far fa-clock"></i> Upcoming Medications</h2>
<div class="dropdown">
<button class="btn btn-outline">
<i class="fas fa-ellipsis-v"></i>
</button>
</div>
</div>
<div class="medication-list" id="medicationList">
<!-- Medication items will be added here dynamically -->
</div>
<div class="empty-state" id="emptyState" style="display: none;">
<i class="fas fa-cloud"></i>
<h3>No medications scheduled</h3>
<p>Add your first medication to get started</p>
<button class="btn btn-primary" id="addFirstMed">
<i class="fas fa-plus"></i> Add Medication
</button>
</div>
</section>
</div>
<!-- Add Medication Modal -->
<div class="modal" id="addMedModal">
<div class="modal-content">
<div class="modal-header">
<h2><i class="fas fa-plus-circle"></i> Add New Medication</h2>
<button class="close-btn" id="closeModal">&times;</button>
</div>
<form id="medicationForm">
<div class="form-group">
<label for="medName"><i class="fas fa-capsules"></i> Medication Name</label>
<input type="text" id="medName" class="form-control" placeholder="e.g. Ibuprofen" required>
</div>
<div class="form-group">
<label for="medDosage"><i class="fas fa-prescription-bottle-alt"></i> Dosage</label>
<input type="text" id="medDosage" class="form-control" placeholder="e.g. 200mg" required>
</div>
<div class="form-group">
<label for="medFrequency"><i class="fas fa-history"></i> Frequency</label>
<select id="medFrequency" class="form-control" required>
<option value="">Select frequency</option>
<option value="once">Once daily</option>
<option value="twice">Twice daily</option>
<option value="thrice">Three times daily</option>
<option value="as_needed">As needed</option>
</select>
</div>
<div class="form-group">
<label><i class="far fa-clock"></i> Times</label>
<div class="time-inputs" id="timeInputs">
<div>
<input type="time" id="time1" class="form-control" required>
</div>
</div>
</div>
<div class="form-group">
<label for="medNotes"><i class="far fa-sticky-note"></i> Notes (Optional)</label>
<textarea id="medNotes" class="form-control" rows="3" placeholder="Special instructions..."></textarea>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-outline" id="cancelBtn">Cancel</button>
<button type="submit" class="btn btn-primary">Add Medication</button>
</div>
</form>
</div>
</div>
<!-- Notification -->
<div class="notification success" id="notification">
<i class="fas fa-check-circle"></i>
<span id="notificationText">Medication added successfully!</span>
</div>
<script>
// Enhanced sample data for medications with more details
const sampleMedications = [
{
id: 1,
name: "Ibuprofen",
dosage: "200mg",
time: "08:00",
isTaken: false,
note: "Take with breakfast",
category: "Pain Relief",
lastTaken: null
},
{
id: 2,
name: "Vitamin D",
dosage: "1000IU",
time: "12:00",
isTaken: false,
note: "With lunch for better absorption",
category: "Supplement",
lastTaken: null
},
{
id: 3,
name: "Multivitamin",
dosage: "1 tablet",
time: "18:00",
isTaken: false,
note: "",
category: "Supplement",
lastTaken: null
},
{
id: 4,
name: "Propranolol",
dosage: "40mg",
time: "10:00",
isTaken: true,
note: "For blood pressure",
category: "Cardiac",
lastTaken: "2023-07-20T08:00:00"
},
{
id: 5,
name: "Omeprazole",
dosage: "20mg",
time: "07:30",
isTaken: true,
note: "Before breakfast",
category: "Gastrointestinal",
lastTaken: "2023-07-20T07:30:00"
}
];
// DOM Elements
const medicationList = document.getElementById('medicationList');
const addMedBtn = document.getElementById('addMedBtn');
const addFirstMed = document.getElementById('addFirstMed');
const addMedModal = document.getElementById('addMedModal');
const closeModal = document.getElementById('closeModal');
const cancelBtn = document.getElementById('cancelBtn');
const medicationForm = document.getElementById('medicationForm');
const medFrequency = document.getElementById('medFrequency');
const timeInputs = document.getElementById('timeInputs');
const notification = document.getElementById('notification');
const notificationText = document.getElementById('notificationText');
const upcomingCount = document.getElementById('upcomingCount');
const totalMeds = document.getElementById('totalMeds');
const adherenceRate = document.getElementById('adherenceRate');
const emptyState = document.getElementById('emptyState');
const permissionBanner = document.getElementById('permissionBanner');
const enableNotificationsBtn = document.getElementById('enableNotifications');
const enableNotificationsText = document.getElementById('enableNotificationsText');
const notificationSpinner = document.getElementById('notificationSpinner');
// Current medications
let medications = [...sampleMedications];
let nextId = 6;
let notificationPermissionGranted = false;
let notificationCheckInterval;
let scheduledNotifications = {};
// Initialize the app
function init() {
renderMedications();
updateStats();
checkNotificationPermission();
setupIntersectionObserver();
// Request permission when clicking the notification button
enableNotificationsBtn.addEventListener('click', requestNotificationPermission);
// Check every minute for due medications
notificationCheckInterval = setInterval(checkNotifications, 60000);
// Initial check
checkNotifications();
}
// Check and request notification permission
function checkNotificationPermission() {
if (!('Notification' in window)) {
console.log('This browser does not support desktop notification');
return;
}
if (Notification.permission === 'granted') {
notificationPermissionGranted = true;
permissionBanner.style.display = 'none';
} else if (Notification.permission !== 'denied') {
// Show the permission banner
permissionBanner.style.display = 'flex';
setTimeout(() => permissionBanner.classList.add('show'), 100);
}
}
// Request notification permission
function requestNotificationPermission() {
enableNotificationsText.textContent = 'Loading...';
notificationSpinner.style.display = 'inline-block';
Notification.requestPermission().then(permission => {
enableNotificationsText.textContent = 'Enable Notifications';
notificationSpinner.style.display = 'none';
if (permission === 'granted') {
notificationPermissionGranted = true;
showNotification('Notifications enabled! You will now receive reminders for your medications.', 'success');
permissionBanner.style.display = 'none';
// Schedule all existing medication notifications
medications.forEach(med => {
if (!med.isTaken) {
scheduleSystemNotification(med);
}
});
} else {
showNotification('Notifications are disabled. You can enable them later in your browser settings.', 'warning');
}
}).catch(err => {
enableNotificationsText.textContent = 'Enable Notifications';
notificationSpinner.style.display = 'none';
console.error('Error requesting notification permission:', err);
showNotification('Failed to enable notifications. Please try again later.', 'error');
});
}
// Show system notification
function showSystemNotification(title, options) {
if (!notificationPermissionGranted) return;
try {
// Close any existing notification with the same tag
if (options.tag) {
const existingNotifications = Notification.notifications || [];
existingNotifications.forEach(notification => {
if (notification.tag === options.tag) {
notification.close();
}
});
}
// Show new notification
const notification = new Notification(title, options);
// Add click handler if supported
if ('onshow' in notification) {
notification.onshow = function() {
console.log('Notification shown:', title);
};
}
if ('onclick' in notification) {
notification.onclick = function() {
console.log('Notification clicked:', title);
window.focus();
// You could focus on the specific medication item
};
}
if ('onclose' in notification) {
notification.onclose = function() {
console.log('Notification closed:', title);
};
}
return notification;
} catch (error) {
console.error('Error showing notification:', error);
return null;
}
}
// Schedule system notification for medication
function scheduleSystemNotification(medication) {
if (!notificationPermissionGranted) return;
// Clear any existing notification for this medication
if (scheduledNotifications[medication.id]) {
clearTimeout(scheduledNotifications[medication.id]);
delete scheduledNotifications[medication.id];
}
// Parse the medication time
const [hours, minutes] = medication.time.split(':').map(Number);
const now = new Date();
const notificationTime = new Date();
notificationTime.setHours(hours);
notificationTime.setMinutes(minutes);
notificationTime.setSeconds(0);
// If the time is in the past, schedule for tomorrow
if (notificationTime < now) {
notificationTime.setDate(notificationTime.getDate() + 1);
}
const timeUntilNotification = notificationTime.getTime() - now.getTime();
if (timeUntilNotification > 0 && timeUntilNotification < 24 * 60 * 60 * 1000) {
scheduledNotifications[medication.id] = setTimeout(() => {
const title = `Time to take ${medication.name}`;
const options = {
body: `Dosage: ${medication.dosage}\n${medication.note || ''}`,
icon: 'https://cdn-icons-png.flaticon.com/512/3161/3161837.png',
tag: `medication-${medication.id}`,
requireInteraction: true,
vibrate: [200, 100, 200],
actions: [
{
action: 'mark-taken',
title: 'Mark as Taken',
icon: 'https://cdn-icons-png.flaticon.com/512/1828/1828640.png'
}
]
};
const notif = showSystemNotification(title, options);
if (notif) {
notif.onclick = (event) => {
// If a specific action was clicked
if (event.action === 'mark-taken') {
takeMedication(medication.id);
} else {
// Otherwise, focus the window
window.focus();
}
notif.close();
};
}
// Auto-fade notification after 5 minutes
setTimeout(() => {
if (notif) notif.close();
}, 5 * 60 * 1000);
}, timeUntilNotification);
}
}
// Render medications to the list
function renderMedications() {
medicationList.innerHTML = '';
// Sort medications by time
const sortedMeds = [...medications].sort((a, b) => {
return a.time.localeCompare(b.time);
});
const upcomingMeds = sortedMeds.filter(med => !med.isTaken);
if (upcomingMeds.length === 0) {
emptyState.style.display = 'block';
medicationList.style.display = 'none';
} else {
emptyState.style.display = 'none';
medicationList.style.display = 'flex';
upcomingMeds.forEach((med, index) => {
const medItem = document.createElement('div');
medItem.className = 'medication-item animate-on-scroll';
medItem.dataset.id = med.id;
medItem.style.transitionDelay = `${index * 0.1}s`;
const [hours, minutes] = med.time.split(':');
const hour = parseInt(hours);
const amPm = hour >= 12 ? 'PM' : 'AM';
const hour12 = hour % 12 || 12;
const timeDisplay = `${hour12}:${minutes} ${amPm}`;
// Determine urgency color
const now = new Date();
const medTime = new Date();
medTime.setHours(hour);
medTime.setMinutes(minutes);
const timeDiff = (medTime - now) / (1000 * 60 * 60); // hours difference
let urgencyClass = '';
if (timeDiff < 0) {
// Past due
urgencyClass = 'past-due';
} else if (timeDiff < 1) {
// Due soon (within 1 hour)
urgencyClass = 'due-soon';
}
medItem.innerHTML = `
<div class="med-icon">
<i class="fas fa-pills"></i>
</div>
<div class="med-details">
<h3>${med.name}</h3>
<p>${med.dosage}${med.category || 'General'}</p>
${med.note ? `<div class="med-note"><i class="fas fa-info-circle"></i> ${med.note}</div>` : ''}
</div>
<div class="med-time ${urgencyClass}">
<p>${timeDisplay} ${urgencyClass === 'past-due' ? ' (Past Due)' : urgencyClass === 'due-soon' ? ' (Soon!)' : ''}</p>
<button class="btn btn-success take-btn">
<i class="fas fa-check"></i> Mark Taken
</button>
</div>
`;
if (urgencyClass === 'due-soon' || urgencyClass === 'past-due') {
medItem.style.animation = urgencyClass === 'past-due' ? 'pulse 2s infinite' : 'pulse 3s infinite';
}
medicationList.appendChild(medItem);
// Add event listener to the take button
medItem.querySelector('.take-btn').addEventListener('click', () => takeMedication(med.id));
});
// Trigger animation when elements are in view
setTimeout(() => {
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
entry.target.classList.add('show');
}
});
}, { threshold: 0.1 });
document.querySelectorAll('.animate-on-scroll').forEach(el => {
observer.observe(el);
});
}, 100);
}
}
// Mark medication as taken
function takeMedication(id) {
const index = medications.findIndex(med => med.id === id);
if (index !== -1) {
medications[index].isTaken = true;
medications[index].lastTaken = new Date().toISOString();
renderMedications();
updateStats();
showNotification(`Marked ${medications[index].name} as taken.`, 'success');
// Cancel any scheduled notification for this medication
if (scheduledNotifications[id]) {
clearTimeout(scheduledNotifications[id]);
delete scheduledNotifications[id];
}
// Schedule notification for next dose if recurring
if (notificationPermissionGranted) {
scheduleSystemNotification(medications[index]);
}
}
}
// Show notification with different types
function showNotification(message, type = 'success') {
// Show in-page notification
notification.className = `notification ${type}`;
notificationText.textContent = message;
notification.classList.add('show');
setTimeout(() => {
notification.classList.remove('show');
}, 3000);
// If permission granted and it's a warning/important notification, show system notification
if (notificationPermissionGranted && (type === 'warning' || type === 'error')) {
try {
const notification = new Notification('MedTrack Alert', {
body: message,
icon: 'https://cdn-icons-png.flaticon.com/512/3161/3161837.png',
});
setTimeout(() => notification.close(), 5000);
} catch (e) {
console.warn('Could not show system notification', e);
}
}
}
// Check if any medications are due
function checkNotifications() {
const now = new Date();
const currentHours = now.getHours().toString().padStart(2, '0');
const currentMinutes = now.getMinutes().toString().padStart(2, '0');
const currentTime = `${currentHours}:${currentMinutes}`;
medications.forEach(med => {
if (!med.isTaken && med.time === currentTime) {
const message = `Time to take ${med.name} (${med.dosage})${med.note ? ': ' + med.note : ''}`;
showNotification(message, 'warning');
// If notifications are enabled, also show system notification
if (notificationPermissionGranted) {
try {
const notification = new Notification(`Time to take ${med.name}`, {
body: message,
icon: 'https://cdn-icons-png.flaticon.com/512/3161/3161837.png',
requireInteraction: true,
vibrate: [200, 100, 200]
});
// Auto-close after 5 minutes
setTimeout(() => notification.close(), 5 * 60 * 1000);
} catch (e) {
console.warn('Could not show system notification', e);
}
}
}
});
}
// Schedule next notification for recurring medications
function scheduleNextNotification(medication) {
// In a real app, this would schedule an actual notification
if (notificationPermissionGranted) {
scheduleSystemNotification(medication);
}
}
// Update statistics with animation
function updateStats() {
const total = medications.length;
const upcoming = medications.filter(med => !med.isTaken).length;
const taken = medications.filter(med => med.isTaken).length;
const adherence = total > 0 ? Math.round((taken / total) * 100) : 0;
// Animate count changes
animateValue('upcomingCount', parseInt(upcomingCount.textContent), upcoming, 500);
animateValue('totalMeds', parseInt(totalMeds.textContent), total, 500);
animateValue('adherenceRate', parseInt(adherenceRate.textContent.replace('%', '')), adherence, 500);
}
// Animate numeric value changes
function animateValue(id, start, end, duration) {
const obj = document.getElementById(id);
let startTimestamp = null;
const step = (timestamp) => {
if (!startTimestamp) startTimestamp = timestamp;
const progress = Math.min((timestamp - startTimestamp) / duration, 1);
const val = Math.floor(progress * (end - start) + start);
obj.textContent = id === 'adherenceRate' ? `${val}%` : val;
if (progress < 1) {
window.requestAnimationFrame(step);
}
};
window.requestAnimationFrame(step);
}
// Show modal with animation
function showModal() {
addMedModal.style.display = 'flex';
document.body.style.overflow = 'hidden';
setTimeout(() => addMedModal.classList.add('show'), 10);
}
// Hide modal with animation
function hideModal() {
addMedModal.classList.remove('show');
setTimeout(() => {
addMedModal.style.display = 'none';
document.body.style.overflow = 'auto';
medicationForm.reset();
while (timeInputs.children.length > 1) {
timeInputs.removeChild(timeInputs.lastChild);
}
}, 300);
}
// Add new medication with additional fields
function addMedication(name, dosage, frequency, times, note) {
const categories = ["Pain Relief", "Supplement", "Antibiotic", "Cardiac", "Gastrointestinal", "Other"];
const randomCategory = categories[Math.floor(Math.random() * categories.length)];
times.forEach(time => {
const newMed = {
id: nextId++,
name,
dosage,
time,
isTaken: false,
note,
category: randomCategory,
lastTaken: null
};
medications.push(newMed);
// Schedule notification for this medication if permission granted
if (notificationPermissionGranted) {
scheduleSystemNotification(newMed);
}
});
renderMedications();
updateStats();
showNotification(`Added ${name} to your medications.`, 'success');
hideModal();
}
// Setup intersection observer for scroll animations
function setupIntersectionObserver() {
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
entry.target.classList.add('show');
}
});
}, { threshold: 0.1 });
document.querySelectorAll('.animate-on-scroll').forEach(el => {
observer.observe(el);
});
}
// Event Listeners
addMedBtn.addEventListener('click', showModal);
addFirstMed.addEventListener('click', showModal);
closeModal.addEventListener('click', hideModal);
cancelBtn.addEventListener('click', hideModal);
// Handle frequency change
medFrequency.addEventListener('change', function() {
const count = this.value === 'once' ? 1 :
this.value === 'twice' ? 2 :
this.value === 'thrice' ? 3 : 1;
// Clear existing time inputs except the first one
while (timeInputs.children.length > 1) {
timeInputs.removeChild(timeInputs.lastChild);
}
// Add additional time inputs as needed
for (let i = 1; i < count; i++) {
const div = document.createElement('div');
div.innerHTML = `<input type="time" id="time${i+1}" class="form-control" required>`;
timeInputs.appendChild(div);
}
});
// Handle form submission
medicationForm.addEventListener('submit', function(e) {
e.preventDefault();
const name = document.getElementById('medName').value;
const dosage = document.getElementById('medDosage').value;
const frequency = document.getElementById('medFrequency').value;
const note = document.getElementById('medNotes').value;
// Get all time values
const times = [];
const timeInputElements = timeInputs.querySelectorAll('input[type="time"]');
timeInputElements.forEach(input => {
if (input.value) times.push(input.value);
});
if (times.length === 0) {
showNotification('Please enter at least one time', 'error');
return;
}
addMedication(name, dosage, frequency, times, note);
});
// Close modal when clicking outside
addMedModal.addEventListener('click', function(e) {
if (e.target === addMedModal) {
hideModal();
}
});
// Empty state button
addFirstMed.addEventListener('click', showModal);
// Initialize the app
init();
// Add pulse animation for emergencies
const style = document.createElement('style');
style.textContent = `
@keyframes pulse {
0% { box-shadow: 0 0 0 0 rgba(231, 76, 60, 0.4); }
70% { box-shadow: 0 0 0 10px rgba(231, 76, 60, 0); }
100% { box-shadow: 0 0 0 0 rgba(231, 76, 60, 0); }
}
.due-soon p, .past-due p {
color: #e74c3c !important;
}
`;
document.head.appendChild(style);
</script>
</body>
</html>