# Creating project files and zipping them for download.
from pathlib import Path, PurePosixPath
import json, zipfile, os, textwrap
OUT_DIR = Path('/mnt/data/medicycle_app')
OUT_DIR.mkdir(parents=True, exist_ok=True)
# index.html
index_html = """
MediCycle — Medicine Reminder
Welcome to MediCycle
No profiles found. Create a profile to start adding medicine routines.
Select profile
Made by © VKY Soft
"""
# css/style.css
css = """:root { --bg: #ffffff; --text: #111827; --card: #f8fafc; }
.dark { --bg: #0b1220; --text: #e6eef8; --card: #0f1724; }
body { background: var(--bg); color: var(--text); }
.card { background: var(--card); }
.app-shell { min-height: 100vh; display:flex; flex-direction:column; }
.logo { font-weight:700; letter-spacing:0.6px; }
.small-muted { font-size:0.85rem; color:rgba(0,0,0,0.55); }
.time-pill { font-weight:600; }
.cursor-pointer { cursor:pointer; }
@media (max-width:576px){ .desktop-only{ display:none } }
/* Dark-mode tweaks */
:root.dark .small-muted { color: rgba(255,255,255,0.6); }
"""
# js/app.js - improved from user's code with passkey check and delete profile
js = r"""// ---------- Data model and localStorage helpers ----------
const STORAGE_KEY = 'medicycle_data_v1';
function defaultData(){
return {
profiles: [], // {id, name, age, passkey, medicines: [med objects]}
history: [], // {profileId, medId, medName, timeTakenISO, dosage}
currentProfileId: null
};
}
function loadData(){
try{
const raw = localStorage.getItem(STORAGE_KEY);
if(!raw) return defaultData();
return JSON.parse(raw);
}catch(e){ console.error('loadData', e); return defaultData(); }
}
function saveData(data){ localStorage.setItem(STORAGE_KEY, JSON.stringify(data)); }
// Unique id helper
function uid(prefix='id'){ return prefix + '_' + Math.random().toString(36).slice(2,9); }
// ---------- DOM refs ----------
const screenProfile = document.getElementById('screen-profile');
const screenApp = document.getElementById('screen-app');
const screenAdd = document.getElementById('screen-add');
const createProfileBtn = document.getElementById('createProfileBtn');
const addProfileBtn = document.getElementById('addProfileBtn');
const profilesList = document.getElementById('profilesList');
const profileTitle = document.getElementById('profileTitle');
const profileAgeEl = document.getElementById('profileAge');
const profileIdEl = document.getElementById('profileId');
const switchProfileBtn = document.getElementById('switchProfileBtn');
const deleteProfileBtn = document.getElementById('deleteProfileBtn');
const addMedicineBtn = document.getElementById('addMedicineBtn');
const medListAll = document.getElementById('medListAll');
const medListToday = document.getElementById('medListToday');
const historyList = document.getElementById('historyList');
const medForm = document.getElementById('medForm');
const medName = document.getElementById('medName');
const medDosage = document.getElementById('medDosage');
const medTime = document.getElementById('medTime');
const medCycle = document.getElementById('medCycle');
const cycleDetails = document.getElementById('cycleDetails');
const saveMedBtn = document.getElementById('saveMedBtn');
const cancelMedBtn = document.getElementById('cancelMedBtn');
const addTitle = document.getElementById('addTitle');
const toggleTheme = document.getElementById('toggleTheme');
const exportBtn = document.getElementById('exportBtn');
const importFile = document.getElementById('importFile');
// Tabs
document.querySelectorAll('#mainTabs .nav-link').forEach(a => {
a.addEventListener('click', ()=>{
document.querySelectorAll('#mainTabs .nav-link').forEach(x=>x.classList.remove('active'));
a.classList.add('active');
document.querySelectorAll('.tab-pane').forEach(p=>p.style.display='none');
document.getElementById('tab-'+a.dataset.tab).style.display='block';
if(a.dataset.tab === 'history') renderHistory();
if(a.dataset.tab === 'today') renderToday();
if(a.dataset.tab === 'all') renderAll();
});
});
// ---------- Profile creation / selection UI ----------
function showCreateProfileForm(){
// Use simple built-in prompts for quick UX
const name = prompt('Name:'); if(!name) return;
const age = prompt('Age:'); if(!age) return;
const passkey = prompt('Passkey (used to protect profile):'); if(!passkey) return;
const data = loadData();
const id = uid('profile');
data.profiles.push({ id, name, age, passkey, medicines: [] });
data.currentProfileId = id;
saveData(data);
initApp();
}
function renderProfiles(){
const data = loadData();
profilesList.innerHTML = '';
if(data.profiles.length===0){
document.getElementById('no-data-screen').style.display='block';
document.getElementById('select-profile-screen').style.display='none';
} else {
document.getElementById('no-data-screen').style.display='none';
document.getElementById('select-profile-screen').style.display='block';
data.profiles.forEach(p=>{
const div = document.createElement('div');
div.className = 'd-flex align-items-center justify-content-between mb-2 card p-2';
div.innerHTML = `${escapeHtml(p.name)}Age ${p.age}
`;
profilesList.appendChild(div);
});
document.querySelectorAll('.select-profile').forEach(b=> b.addEventListener('click', async ()=>{
const id = b.dataset.id; const data = loadData(); const profile = data.profiles.find(x=>x.id===id);
if(!profile) return alert('Profile not found');
// ask passkey
const pass = prompt('Enter passkey for profile "'+profile.name+'":');
if(pass === null) return;
if(pass === profile.passkey){
data.currentProfileId = id; saveData(data); initApp();
} else {
alert('Wrong passkey');
}
}));
document.querySelectorAll('.delete-profile').forEach(b=> b.addEventListener('click', (e)=> {
const id = b.dataset.id; const data = loadData(); const profile = data.profiles.find(x=>x.id===id);
if(!profile) return;
if(confirm('Delete profile "'+profile.name+'"? This will remove all medicines and history for this profile.')) {
// remove profile and related history
data.profiles = data.profiles.filter(p=> p.id !== id);
data.history = data.history.filter(h=> h.profileId !== id);
if(data.currentProfileId === id) data.currentProfileId = null;
saveData(data); initApp();
}
}));
}
}
// ---------- App init / rendering ----------
let data = loadData();
let currentProfile = null;
let editingMedId = null;
function initApp(){
data = loadData();
if(!data || !data.profiles || data.profiles.length===0){
// show create profile screen
screenProfile.style.display='block';
screenApp.style.display='none';
screenAdd.style.display='none';
renderProfiles();
return;
}
// if a currentProfileId exists, require passkey before opening automatically
if(data.currentProfileId){
const profile = data.profiles.find(p=> p.id === data.currentProfileId);
if(profile){
const pass = prompt('Enter passkey to open profile "'+profile.name+'": (cancel to choose another profile)');
if(pass === profile.passkey){
currentProfile = profile;
openAppForProfile();
return;
} else {
// wrong or cancelled -> clear currentProfileId and show selector
data.currentProfileId = null; saveData(data);
screenProfile.style.display='block'; screenApp.style.display='none'; screenAdd.style.display='none';
renderProfiles();
return;
}
} else {
data.currentProfileId = null; saveData(data);
initApp(); return;
}
} else {
// no current profile set -> show selector
screenProfile.style.display='block'; screenApp.style.display='none'; screenAdd.style.display='none';
renderProfiles();
return;
}
}
function openAppForProfile(){
// show dashboard
screenProfile.style.display='none'; screenApp.style.display='block'; screenAdd.style.display='none';
profileTitle.textContent = currentProfile.name;
profileAgeEl.textContent = currentProfile.age;
profileIdEl.textContent = currentProfile.id;
renderAll(); renderToday();
}
// ---------- Render lists ----------
function renderAll(){
medListAll.innerHTML = '';
if(!currentProfile || currentProfile.medicines.length===0){ medListAll.innerHTML = 'No medicines added yet
'; return; }
currentProfile.medicines.forEach(m=>{
const card = document.createElement('div'); card.className='card p-2 mb-2';
card.innerHTML = `
${escapeHtml(m.name)}
${escapeHtml(m.dosage)} • ${m.food==='after'? 'After Food':'Before Food'}
Time: ${m.time} • Cycle: ${cycleLabel(m)}
`;
medListAll.appendChild(card);
});
document.querySelectorAll('.edit-med').forEach(b=> b.addEventListener('click', ()=> openEditMedicine(b.dataset.id)));
document.querySelectorAll('.del-med').forEach(b=> b.addEventListener('click', ()=> deleteMed(b.dataset.id)));
}
function renderToday(){
medListToday.innerHTML = '';
if(!currentProfile) return;
const today = new Date();
const todays = currentProfile.medicines.filter(m=> isMedicineForDate(m, today));
if(todays.length===0){ medListToday.innerHTML = 'No medicines scheduled for today
'; return; }
todays.forEach(m=>{
const card = document.createElement('div'); card.className='card p-2 mb-2 d-flex justify-content-between align-items-center';
// check if already taken today
const taken = wasTakenToday(currentProfile.id, m.id);
card.innerHTML = `
${escapeHtml(m.name)} ${taken? 'Taken': ''}
${escapeHtml(m.dosage)} • ${m.time} • ${m.food==='after'?'After Food':'Before Food'}
`;
medListToday.appendChild(card);
});
document.querySelectorAll('.mark-taken').forEach(b=> b.addEventListener('click', ()=> markTaken(b.dataset.id)));
}
function renderHistory(){
historyList.innerHTML = '';
const data = loadData();
const list = data.history.filter(h=> h.profileId === currentProfile.id).sort((a,b)=> new Date(b.timeTakenISO)-new Date(a.timeTakenISO));
if(list.length===0) { historyList.innerHTML = 'No history yet
'; return; }
list.forEach(h=>{
const el = document.createElement('div'); el.className='card p-2 mb-2';
el.innerHTML = `${escapeHtml(h.medName)}${escapeHtml(h.dosage)} • ${new Date(h.timeTakenISO).toLocaleString()}
`;
historyList.appendChild(el);
});
document.querySelectorAll('.btn-copy').forEach(b=> b.addEventListener('click', ()=> navigator.clipboard.writeText(b.dataset.iso)));
}
// ---------- Helpers: cycle label, isMedicineForDate, taken checks ----------
function cycleLabel(m){
if(m.cycle==='daily') return 'Daily';
if(m.cycle==='monthly') return 'Monthly on ' + (m.monthDay||'?');
if(m.cycle==='weekly') return 'Weekly on ' + (m.weekDays? m.weekDays.join(', '):'?');
return '';
}
function isMedicineForDate(m, date){
if(!m) return false;
if(m.cycle==='daily') return true;
if(m.cycle==='monthly'){
const d = Number(m.monthDay);
return d === date.getDate();
}
if(m.cycle==='weekly'){
const names = ['Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday'];
return m.weekDays && m.weekDays.includes(names[date.getDay()]);
}
return false;
}
function wasTakenToday(profileId, medId){
const d = loadData();
const today = (new Date()).toDateString();
return d.history.some(h=> h.profileId===profileId && h.medId===medId && new Date(h.timeTakenISO).toDateString()===today);
}
// ---------- Add/Edit/Delete medicine ----------
function openAddMedicine(){
editingMedId = null; addTitle.textContent = 'Add Medicine'; medForm.reset(); cycleDetails.innerHTML=''; medCycle.value='daily'; renderCycleDetails();
screenProfile.style.display='none'; screenApp.style.display='none'; screenAdd.style.display='block';
}
function openEditMedicine(id){
const med = currentProfile.medicines.find(x=> x.id===id); if(!med) return;
editingMedId = id; addTitle.textContent = 'Edit Medicine';
medName.value = med.name; medDosage.value = med.dosage; medTime.value = med.time; document.querySelectorAll('input[name="food"]').forEach(r=> r.checked = (r.value === med.food));
medCycle.value = med.cycle; renderCycleDetails(med);
screenProfile.style.display='none'; screenApp.style.display='none'; screenAdd.style.display='block';
}
function deleteMed(id){ if(!confirm('Delete this medicine?')) return; const data = loadData(); const profile = data.profiles.find(p=> p.id===data.currentProfileId); profile.medicines = profile.medicines.filter(m=> m.id!==id); saveData(data); initApp(); }
medCycle.addEventListener('change', ()=> renderCycleDetails());
function renderCycleDetails(med=null){
const v = medCycle.value; if(v==='daily'){ cycleDetails.innerHTML = 'Will repeat every day
'; return; }
if(v==='monthly'){
const val = med? (med.monthDay||'') : '';
cycleDetails.innerHTML = ``; return;
}
if(v==='weekly'){
const names = ['Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday'];
const selected = med && med.weekDays ? med.weekDays : [];
cycleDetails.innerHTML = '' + names.map(n=> `
`).join('');
return;
}
}
medForm.addEventListener('submit', (e)=>{ e.preventDefault(); saveMedicine(); });
cancelMedBtn.addEventListener('click', ()=> { screenAdd.style.display='none'; screenApp.style.display='block'; });
function saveMedicine(){
const name = medName.value.trim(); const dosage = medDosage.value.trim(); const time = medTime.value; const food = document.querySelector('input[name="food"]:checked').value;
const cycle = medCycle.value;
const data = loadData(); const profile = data.profiles.find(p=> p.id === data.currentProfileId);
let medObj = editingMedId ? profile.medicines.find(m=> m.id===editingMedId) : null;
if(!medObj){ medObj = { id: uid('med'), name, dosage, time, food, cycle }; profile.medicines.push(medObj); }
else { medObj.name = name; medObj.dosage = dosage; medObj.time = time; medObj.food = food; medObj.cycle = cycle; }
if(cycle==='monthly'){ medObj.monthDay = Number(document.getElementById('monthDay').value); }
if(cycle==='weekly'){ medObj.weekDays = Array.from(document.querySelectorAll('.week-day:checked')).map(x=>x.value); }
saveData(data); screenAdd.style.display='none'; screenApp.style.display='block'; initApp();
}
// ---------- Mark taken ----------
function markTaken(medId){
const data = loadData(); const profile = data.profiles.find(p=> p.id===data.currentProfileId); const med = profile.medicines.find(m=> m.id===medId);
data.history.push({ profileId: profile.id, medId: med.id, medName: med.name, dosage: med.dosage, timeTakenISO: new Date().toISOString() });
saveData(data); renderToday();
}
// ---------- Export / Import ----------
exportBtn.addEventListener('click', ()=>{
const data = loadData(); const blob = new Blob([JSON.stringify(data, null, 2)], {type:'application/json'});
const url = URL.createObjectURL(blob);
const a = document.createElement('a'); a.href = url; a.download = 'medicycle_data.json'; document.body.appendChild(a); a.click(); a.remove(); URL.revokeObjectURL(url);
});
importFile.addEventListener('change', async (e)=>{
const f = e.target.files[0]; if(!f) return; try{
const txt = await f.text(); const imported = JSON.parse(txt);
if(!imported.profiles) throw new Error('Invalid file');
if(confirm('This will replace current data. Proceed?')){ localStorage.setItem(STORAGE_KEY, JSON.stringify(imported)); alert('Imported. Reloading view.'); initApp(); }
}catch(err){ alert('Failed to import: '+err.message); }
importFile.value='';
});
// ---------- Notifications ----------
let notifiedSet = new Set(); // in-memory, resets on reload
function requestNotifPermission(){ if('Notification' in window) Notification.requestPermission().then(perm=> console.log('notification permission', perm)); }
function checkReminders(){
const now = new Date(); const hhmm = now.toTimeString().slice(0,5);
const data = loadData();
if(!data.currentProfileId) return;
const profile = data.profiles.find(p=> p.id===data.currentProfileId); if(!profile) return;
profile.medicines.forEach(m=>{
if(m.time === hhmm && isMedicineForDate(m, now)){
const key = profile.id + '|' + m.id + '|' + now.toDateString() + '|' + hhmm;
if(notifiedSet.has(key)) return; notifiedSet.add(key);
// show notification
if('Notification' in window && Notification.permission === 'granted'){
new Notification('MediCycle: ' + m.name, { body: `${m.dosage} • ${m.food==='after'? 'After Food':'Before Food'}` });
} else {
// fallback: console log
console.log('Reminder:', m.name, m.dosage);
}
}
});
}
// check every 20 seconds
setInterval(checkReminders, 20000);
// ---------- Theme toggle ----------
function applyTheme(isDark){ if(isDark) document.documentElement.classList.add('dark'); else document.documentElement.classList.remove('dark'); localStorage.setItem('medicycle_theme', isDark? 'dark':'light'); }
toggleTheme.addEventListener('click', ()=>{ const isDark = document.documentElement.classList.toggle('dark'); localStorage.setItem('medicycle_theme', isDark? 'dark':'light'); });
// apply persisted theme
(function(){ const t = localStorage.getItem('medicycle_theme'); if(t==='dark') applyTheme(true); else applyTheme(false); })();
// ---------- Utility: escapeHtml ----------
function escapeHtml(s){ return String(s).replace(/&/g,'&').replace(//g,'>'); }
// ---------- Buttons wiring ----------
createProfileBtn.addEventListener('click', showCreateProfileForm);
addProfileBtn.addEventListener('click', showCreateProfileForm);
switchProfileBtn.addEventListener('click', ()=>{ data = loadData(); data.currentProfileId=null; saveData(data); initApp(); });
addMedicineBtn.addEventListener('click', openAddMedicine);
deleteProfileBtn.addEventListener('click', ()=> {
if(!currentProfile) return;
if(!confirm('Delete current profile "'+currentProfile.name+'"? This removes all medicines and history for this profile.')) return;
const data = loadData();
data.profiles = data.profiles.filter(p=> p.id !== currentProfile.id);
data.history = data.history.filter(h=> h.profileId !== currentProfile.id);
data.currentProfileId = null;
saveData(data);
currentProfile = null;
initApp();
});
// Request notification permissions on first interaction (polite)
document.addEventListener('click', ()=>{ if(window.Notification && Notification.permission === 'default') requestNotifPermission(); }, { once:true });
// Initialize
initApp();
"""
# README
readme = """MediCycle — Medicine Reminder (Local-only webapp)
Files:
- index.html
- css/style.css
- js/app.js
Features implemented:
- Multiple profiles with passkey protection (prompt on open).
- Create/Edit/Delete medicine routines (daily / monthly / weekly).
- "Today" tab shows only medicines scheduled for today; you can mark them as taken.
- History tab shows taken records per profile.
- Export / Import JSON (local file).
- Dark / Light theme toggle (persisted).
- Browser notifications (requests permission once).
- All data stored in browser localStorage, no backend.
How to use:
1. Open index.html in a browser (supports mobile & desktop).
2. Create a profile (name, age, passkey).
3. Add medicines and schedules.
4. Use Export to backup, Import to restore (replaces current data).
"""
# write files
(OUT_DIR / 'index.html').write_text(index_html, encoding='utf-8')
css_dir = OUT_DIR / 'css'
css_dir.mkdir(exist_ok=True)
(css_dir / 'style.css').write_text(css, encoding='utf-8')
js_dir = OUT_DIR / 'js'
js_dir.mkdir(exist_ok=True)
(js_dir / 'app.js').write_text(js, encoding='utf-8')
(OUT_DIR / 'README.txt').write_text(readme, encoding='utf-8')
# create zip
zip_path = Path('/mnt/data/medicycle_app.zip')
with zipfile.ZipFile(zip_path, 'w', compression=zipfile.ZIP_DEFLATED) as z:
for p in OUT_DIR.rglob('*'):
z.write(p, arcname=str(p.relative_to(OUT_DIR.parent)))
print("Created:", zip_path)
print("Files included:")
for p in OUT_DIR.rglob('*'):
print("-", p.relative_to(OUT_DIR))