Spaces:
Running
Running
Mona commited on
Commit ·
090e374
1
Parent(s): af1d617
Upload DailyPal Interactive Prototype
Browse files- README.md +16 -12
- index.html +283 -17
README.md
CHANGED
|
@@ -1,12 +1,16 @@
|
|
| 1 |
-
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# DailyPal • Interactive Prototype (Static HTML)
|
| 2 |
+
|
| 3 |
+
This folder contains a **single-page interactive prototype** (no backend) that runs in any modern browser.
|
| 4 |
+
|
| 5 |
+
## Files
|
| 6 |
+
- `index.html` — the interactive prototype (clickable toggles, draggable sliders, Apple-style home screen)
|
| 7 |
+
- `README.md` — this file
|
| 8 |
+
|
| 9 |
+
## Deploy to Hugging Face Spaces (Static HTML Site)
|
| 10 |
+
1. Create a new Space at https://huggingface.co/spaces → **Static HTML Site**
|
| 11 |
+
2. Upload `index.html` (and `README.md` optional). Make sure `index.html` is at the **root**.
|
| 12 |
+
3. Once the build finishes, your live link will look like:
|
| 13 |
+
`https://huggingface.co/spaces/<YourName>/DailyPal-Interactive`
|
| 14 |
+
|
| 15 |
+
## Local run
|
| 16 |
+
Just open `index.html` in your browser.
|
index.html
CHANGED
|
@@ -1,19 +1,285 @@
|
|
| 1 |
<!doctype html>
|
| 2 |
-
<html>
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 19 |
</html>
|
|
|
|
| 1 |
<!doctype html>
|
| 2 |
+
<html lang='en'>
|
| 3 |
+
<meta charset='utf-8'>
|
| 4 |
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
| 5 |
+
<title>DailyPal Interactive Prototype</title>
|
| 6 |
+
<style>
|
| 7 |
+
:root { --w:430px; --h:520px; --r:30px; --stroke:#E5E5EA; --bg:#F2F2F7;
|
| 8 |
+
--green:#34C759; --blue:#007AFF; --dark:#000; --sub:#8E8E93; }
|
| 9 |
+
body { background:#111; font-family:SF Pro Display, Helvetica, Arial, sans-serif; display:flex; flex-direction:column; align-items:center; gap:16px; min-height:100vh; margin:0; justify-content:center; padding:16px; }
|
| 10 |
+
.watch svg { border-radius: var(--r); box-shadow:0 20px 60px rgba(0,0,0,.5); max-width:100%; height:auto; }
|
| 11 |
+
#bar { color:#aaa; font-size:12px; text-align:center; }
|
| 12 |
+
.btnlike { cursor:pointer; }
|
| 13 |
+
</style>
|
| 14 |
+
<body>
|
| 15 |
+
<div id='screen' class='watch'></div>
|
| 16 |
+
<div id='bar'>Press 0:Home, 1:Dashboard, 2:Popup, 3:Scheduler, 4:Weekly, 5:Settings</div>
|
| 17 |
+
|
| 18 |
+
<script>
|
| 19 |
+
const W=430, H=520, R=30;
|
| 20 |
+
const homeSVG = `<svg xmlns='http://www.w3.org/2000/svg' width='430' height='520' viewBox='0 0 430 520'>
|
| 21 |
+
<defs>
|
| 22 |
+
<style>
|
| 23 |
+
.time { font: 700 22px "SF Pro Display, Helvetica, Arial, sans-serif"; fill: #FFFFFF; }
|
| 24 |
+
.label { font: 600 12px "SF Pro Display, Helvetica, Arial, sans-serif"; fill: #FFFFFF; }
|
| 25 |
+
</style>
|
| 26 |
+
</defs>
|
| 27 |
+
<rect x='0' y='0' rx='30' ry='30' width='430' height='520' fill='#000000'/>
|
| 28 |
+
<text class='time' x='20' y='38'>9:41</text>
|
| 29 |
+
<g id='icon-dailypal' cursor='pointer'>
|
| 30 |
+
<circle cx='115' cy='210' r='38' fill='#34C759'/>
|
| 31 |
+
<rect x='100' y='195' width='30' height='24' rx='6' fill='#FFFFFF'/>
|
| 32 |
+
<circle cx='132' cy='205' r='5' fill='#34C759'/>
|
| 33 |
+
<text class='label' x='115' y='260' text-anchor='middle'>DailyPal</text>
|
| 34 |
+
</g>
|
| 35 |
+
<g>
|
| 36 |
+
<circle cx='215' cy='180' r='28' fill='#FF3B30'/>
|
| 37 |
+
<circle cx='280' cy='230' r='32' fill='#FF9500'/>
|
| 38 |
+
<circle cx='190' cy='270' r='26' fill='#0A84FF'/>
|
| 39 |
+
<circle cx='260' cy='305' r='24' fill='#BF5AF2'/>
|
| 40 |
+
<circle cx='158' cy='330' r='22' fill='#64D2FF'/>
|
| 41 |
+
<circle cx='320' cy='190' r='22' fill='#FFD60A'/>
|
| 42 |
+
</g>
|
| 43 |
+
</svg>`;
|
| 44 |
+
|
| 45 |
+
function frameStart(title){
|
| 46 |
+
return `
|
| 47 |
+
<svg xmlns='http://www.w3.org/2000/svg' width='${W}' height='${H}' viewBox='0 0 ${W} ${H}'>
|
| 48 |
+
<defs>
|
| 49 |
+
<style>
|
| 50 |
+
.title { font: 700 22px "SF Pro Display, Helvetica, Arial, sans-serif"; fill: #000; }
|
| 51 |
+
.section { font: 600 17px "SF Pro Display, Helvetica, Arial, sans-serif"; fill: #000; }
|
| 52 |
+
.body { font: 500 14px "SF Pro Display, Helvetica, Arial, sans-serif"; fill: #000; }
|
| 53 |
+
.note { font: 500 12px "SF Pro Display, Helvetica, Arial, sans-serif"; fill: #8E8E93; }
|
| 54 |
+
.btn { font: 700 16px "SF Pro Display, Helvetica, Arial, sans-serif"; fill: #000; }
|
| 55 |
+
</style>
|
| 56 |
+
</defs>
|
| 57 |
+
<rect x='0' y='0' rx='${R}' ry='${R}' width='${W}' height='${H}' fill='#F2F2F7' stroke='#E5E5EA'/>
|
| 58 |
+
<text class='note' x='22' y='24'>9:41</text>
|
| 59 |
+
<text class='title' x='22' y='44'>${title}</text>
|
| 60 |
+
`;
|
| 61 |
+
}
|
| 62 |
+
function frameEnd(){ return "</svg>"; }
|
| 63 |
+
|
| 64 |
+
function pill(x,y,w,h,label,id,fill='#FFFFFF',stroke='#E5E5EA'){
|
| 65 |
+
return `<g id='${id}' class='btnlike'><rect x='${x}' y='${y}' width='${w}' height='${h}' rx='18' fill='${fill}' stroke='${stroke}'/><text class='btn' x='${x+w/2}' y='${y+h/2+6}' text-anchor='middle'>${label}</text></g>`;
|
| 66 |
+
}
|
| 67 |
+
|
| 68 |
+
function toggle(x,y,on,id){
|
| 69 |
+
const fill = on ? '#34C759' : '#E9E9ED';
|
| 70 |
+
const knob = x + (on ? 48 : 18);
|
| 71 |
+
return `<g id='${id}' class='btnlike' data-on='${on?1:0}'>
|
| 72 |
+
<rect x='${x}' y='${y}' width='66' height='32' rx='16' fill='${fill}' stroke='#E5E5EA'/>
|
| 73 |
+
<circle cx='${knob}' cy='${y+16}' r='13' fill='#FFFFFF' stroke='#E5E5EA'/>
|
| 74 |
+
</g>`;
|
| 75 |
+
}
|
| 76 |
+
|
| 77 |
+
function checkbox(x,y,checked,label,id){
|
| 78 |
+
const mark = checked ? `<path d='M ${x+4} ${y+12} l 6 6 l 10 -12' stroke='#007AFF' stroke-width='2' fill='none'/>` : '';
|
| 79 |
+
return `<g id='${id}' class='btnlike' data-on='${checked?1:0}'>
|
| 80 |
+
<rect x='${x}' y='${y}' width='24' height='24' rx='6' fill='#FFFFFF' stroke='#E5E5EA'/>
|
| 81 |
+
${mark}
|
| 82 |
+
<text class='body' x='${x+32}' y='${y+17}'>${label}</text>
|
| 83 |
+
</g>`;
|
| 84 |
+
}
|
| 85 |
+
|
| 86 |
+
function slider(x,y,w,label,value,id) {
|
| 87 |
+
const pos = 0.65;
|
| 88 |
+
const knobX = x + w*pos;
|
| 89 |
+
return `<g id='${id}' class='slider' data-x='${x}' data-w='${w}' data-pos='${pos}'>
|
| 90 |
+
<text class='section' x='${x}' y='${y-12}'>${label}</text>
|
| 91 |
+
<rect x='${x}' y='${y}' width='${w}' height='6' rx='3' fill='#E5E7EB'/>
|
| 92 |
+
<circle cx='${knobX}' cy='${y+3}' r='10' fill='#FFFFFF' stroke='#E5E5EA'/>
|
| 93 |
+
<text class='note' id='${id}-val' x='${x+w}' y='${y-12}' text-anchor='end'>${value}</text>
|
| 94 |
+
</g>`;
|
| 95 |
+
}
|
| 96 |
+
|
| 97 |
+
function weeklyBars(x,y,w,h,vals){
|
| 98 |
+
let out='<g>';
|
| 99 |
+
const bw = w/(vals.length*1.6); const gap=bw*0.6;
|
| 100 |
+
for(let i=0;i<vals.length;i++){
|
| 101 |
+
const v=vals[i];
|
| 102 |
+
const color = v>0.75 ? '#B8E6C1' : (v>0.45 ? '#FFDDA6' : '#FFB3A8');
|
| 103 |
+
const bx = x + i*(bw+gap); const bh=v*h; const by=y+(h-bh);
|
| 104 |
+
out += `<rect x='${bx}' y='${by}' width='${bw}' height='${bh}' rx='4' fill='${color}' stroke='#E5E5EA'/>`;
|
| 105 |
+
}
|
| 106 |
+
out+='</g>'; return out;
|
| 107 |
+
}
|
| 108 |
+
|
| 109 |
+
function dashboard(){
|
| 110 |
+
let s=frameStart('Next dose • 9:00 AM');
|
| 111 |
+
s += `<text class='note' x='22' y='66'>2 of 3 done today — Doing well!</text>`;
|
| 112 |
+
function row(y,label,right){
|
| 113 |
+
return `<g>
|
| 114 |
+
<rect x='18' y='${y}' width='${W-36}' height='64' rx='16' fill='#FFFFFF' stroke='#E5E5EA'/>
|
| 115 |
+
<circle cx='40' cy='${y+32}' r='10' fill='#E9F8EF' stroke='#E5E5EA'/>
|
| 116 |
+
<path d='M 36 ${y+32} l 6 6 l 10 -12' stroke='#34C759' stroke-width='2' fill='none'/>
|
| 117 |
+
<text class='section' x='58' y='${y+37}'>${label}</text>
|
| 118 |
+
<text class='note' x='${W-28}' y='${y+37}' text-anchor='end'>${right}</text>
|
| 119 |
+
</g>`;
|
| 120 |
+
}
|
| 121 |
+
s += row(82,'Taken','8:00 AM');
|
| 122 |
+
s += row(154,'Snoozed','12:00 PM');
|
| 123 |
+
s += row(226,'Skipped','6:00 PM');
|
| 124 |
+
s += pill(18,308,W-36,60,'I took it','btn_dashboard_took','#E9F8EF');
|
| 125 |
+
s += `<text class='note' x='22' y='392'>Weekly summary</text>`;
|
| 126 |
+
s += pill(18,402,W-36,44,'Open Settings','btn_dashboard_settings','#FFFFFF');
|
| 127 |
+
s += frameEnd();
|
| 128 |
+
return s;
|
| 129 |
+
}
|
| 130 |
+
|
| 131 |
+
function popup(){
|
| 132 |
+
let s=frameStart('Time to take medication');
|
| 133 |
+
s += `<rect x='22' y='54' width='${W-44}' height='96' rx='16' fill='#FFFFFF' stroke='#E5E5EA'/>
|
| 134 |
+
<circle cx='46' cy='102' r='12' fill='#F2F2F5' stroke='#E5E5EA'/>
|
| 135 |
+
<text class='section' x='70' y='96'>Aspirin</text><text class='note' x='70' y='116'>1 tablet</text>`;
|
| 136 |
+
s += pill(22,162,120,52,'Snooze','btn_popup_snooze','#FFF7CC');
|
| 137 |
+
s += pill(154,162,120,52,'Skip','btn_popup_skip','#FFE9E7','#F0C2C2');
|
| 138 |
+
s += pill(286,162,120,52,'Taken','btn_popup_taken','#E9F8EF','#B9E6C6');
|
| 139 |
+
s += frameEnd(); return s;
|
| 140 |
+
}
|
| 141 |
+
|
| 142 |
+
function scheduler(){
|
| 143 |
+
let s=frameStart('Adaptive Scheduler');
|
| 144 |
+
s += slider(22,78,W-44,'Morning','8:00 AM','sld_morning');
|
| 145 |
+
s += slider(22,132,W-44,'Noon','12:30 PM','sld_noon');
|
| 146 |
+
s += slider(22,186,W-44,'Evening','9:00 PM','sld_evening');
|
| 147 |
+
s += `<text class='section' x='22' y='244'>Quiet Hours 10:00 PM–7:00 AM</text>`;
|
| 148 |
+
s += toggle(W-22-66,224,true,'tgl_quiet');
|
| 149 |
+
s += `<text class='section' x='22' y='292'>Auto-learn from your responses</text>`;
|
| 150 |
+
s += toggle(W-22-66,272,true,'tgl_autolearn');
|
| 151 |
+
s += pill(22,324,180,56,'Save','btn_scheduler_save','#E9F8EF');
|
| 152 |
+
s += pill(W-22-180,324,180,56,'Cancel','btn_scheduler_cancel');
|
| 153 |
+
s += frameEnd(); return s;
|
| 154 |
+
}
|
| 155 |
+
|
| 156 |
+
function weekly(){
|
| 157 |
+
let s=frameStart('Weekly Summary');
|
| 158 |
+
s += weeklyBars(56,70,W-112,150,[0.92,0.6,0.84,0.72,0.35,0.52,0.64]);
|
| 159 |
+
const days=['M','T','W','T','F','S','S'];
|
| 160 |
+
const bw = (W-112)/(days.length*1.6); const gap=bw*0.6;
|
| 161 |
+
for(let i=0;i<days.length;i++){ const x=56+i*(bw+gap)+bw/2; s+=`<text class='note' x='${x}' y='230' text-anchor='middle'>${days[i]}</text>`; }
|
| 162 |
+
s += `<text class='body' x='22' y='260'>You missed 2 doses this week — mostly at night.</text>`;
|
| 163 |
+
s += pill(22,288,W-44,56,'View Details','btn_weekly_view');
|
| 164 |
+
s += frameEnd(); return s;
|
| 165 |
+
}
|
| 166 |
+
|
| 167 |
+
function settings(){
|
| 168 |
+
let s=frameStart('Reminder Settings');
|
| 169 |
+
s += `<text class='section' x='22' y='80'>Alert Style</text>`;
|
| 170 |
+
s += checkbox(22,92,true,'Vibration','chk_vib');
|
| 171 |
+
s += checkbox(22,128,false,'Sound','chk_sound');
|
| 172 |
+
s += checkbox(22,164,false,'Light','chk_light');
|
| 173 |
+
s += `<line x1='22' y1='200' x2='${W-22}' y2='200' stroke='#E5E5EA'/>`;
|
| 174 |
+
s += `<text class='section' x='22' y='232'>Show pop-ups</text>`;
|
| 175 |
+
s += toggle(W-22-66,212,false,'tgl_popups');
|
| 176 |
+
s += `<text class='note' x='22' y='256'>Disable sounds and pop-ups during presentations, etc.</text>`;
|
| 177 |
+
s += `<line x1='22' y1='284' x2='${W-22}' y2='284' stroke='#E5E5EA'/>`;
|
| 178 |
+
s += `<text class='section' x='22' y='316'>Quiet Mode</text>`;
|
| 179 |
+
s += toggle(W-22-66,296,false,'tgl_quietmode');
|
| 180 |
+
s += pill(22,348,W-44,56,'Done','btn_settings_done');
|
| 181 |
+
s += frameEnd(); return s;
|
| 182 |
+
}
|
| 183 |
+
|
| 184 |
+
const container = document.getElementById('screen');
|
| 185 |
+
function render(name){
|
| 186 |
+
const map = {home: homeSVG, dashboard: dashboard(), popup: popup(), scheduler: scheduler(), weekly: weekly(), settings: settings()};
|
| 187 |
+
container.innerHTML = map[name];
|
| 188 |
+
const svg = container.querySelector('svg');
|
| 189 |
+
svg.style.width = '430px'; svg.style.height = '520px';
|
| 190 |
+
|
| 191 |
+
const nav = [
|
| 192 |
+
['#btn_dashboard_took','weekly'],
|
| 193 |
+
['#btn_dashboard_settings','settings'],
|
| 194 |
+
['#btn_popup_taken','dashboard'],
|
| 195 |
+
['#btn_popup_snooze','dashboard'],
|
| 196 |
+
['#btn_popup_skip','dashboard'],
|
| 197 |
+
['#btn_scheduler_save','dashboard'],
|
| 198 |
+
['#btn_scheduler_cancel','dashboard'],
|
| 199 |
+
['#btn_weekly_view','dashboard'],
|
| 200 |
+
['#btn_settings_done','dashboard'],
|
| 201 |
+
['#icon-dailypal','dashboard']
|
| 202 |
+
];
|
| 203 |
+
nav.forEach(([sel,target])=>{ const el=svg.querySelector(sel); if(el) el.addEventListener('click',()=>render(target)); });
|
| 204 |
+
|
| 205 |
+
// toggles
|
| 206 |
+
svg.querySelectorAll('[id^=tgl_]').forEach(t=>{
|
| 207 |
+
t.style.cursor='pointer';
|
| 208 |
+
t.addEventListener('click',()=>{
|
| 209 |
+
const rect=t.querySelector('rect');
|
| 210 |
+
const circ=t.querySelector('circle');
|
| 211 |
+
const on = rect.getAttribute('fill')!=='#E9E9ED';
|
| 212 |
+
if(on) {
|
| 213 |
+
rect.setAttribute('fill','#E9E9ED'); circ.setAttribute('cx', parseFloat(rect.getAttribute('x'))+18);
|
| 214 |
+
} else {
|
| 215 |
+
rect.setAttribute('fill','#34C759'); circ.setAttribute('cx', parseFloat(rect.getAttribute('x'))+48);
|
| 216 |
+
}
|
| 217 |
+
});
|
| 218 |
+
});
|
| 219 |
+
|
| 220 |
+
// checkboxes
|
| 221 |
+
[['#chk_vib'],['#chk_sound'],['#chk_light']].forEach(([sel])=>{
|
| 222 |
+
const g = svg.querySelector(sel);
|
| 223 |
+
if(!g) return;
|
| 224 |
+
g.addEventListener('click',()=>{
|
| 225 |
+
const path = g.querySelector('path');
|
| 226 |
+
if(path) path.remove();
|
| 227 |
+
else {
|
| 228 |
+
const x = parseFloat(g.querySelector('rect').getAttribute('x'));
|
| 229 |
+
const y = parseFloat(g.querySelector('rect').getAttribute('y'));
|
| 230 |
+
const p = document.createElementNS('http://www.w3.org/2000/svg','path');
|
| 231 |
+
p.setAttribute('d',`M ${x+4} ${y+12} l 6 6 l 10 -12`);
|
| 232 |
+
p.setAttribute('stroke','#007AFF'); p.setAttribute('stroke-width','2'); p.setAttribute('fill','none');
|
| 233 |
+
g.appendChild(p);
|
| 234 |
+
}
|
| 235 |
+
});
|
| 236 |
+
});
|
| 237 |
+
|
| 238 |
+
// sliders drag
|
| 239 |
+
svg.querySelectorAll('.slider').forEach(g=>{
|
| 240 |
+
const x = parseFloat(g.getAttribute('data-x'));
|
| 241 |
+
const w = parseFloat(g.getAttribute('data-w'));
|
| 242 |
+
const circle = g.querySelector('circle');
|
| 243 |
+
const label = g.querySelector('text.note[id$="-val"]');
|
| 244 |
+
let dragging=false;
|
| 245 |
+
|
| 246 |
+
function updateByClientX(clientX){
|
| 247 |
+
const pt = svg.createSVGPoint(); pt.x = clientX; pt.y = 0;
|
| 248 |
+
const ctm = svg.getScreenCTM().inverse(); const loc = pt.matrixTransform(ctm);
|
| 249 |
+
let nx = Math.max(x, Math.min(x+w, loc.x));
|
| 250 |
+
circle.setAttribute('cx', nx);
|
| 251 |
+
const ratio = (nx - x)/w;
|
| 252 |
+
const minutes = Math.round(7*60 + ratio*(15*60));
|
| 253 |
+
const hr = Math.floor(minutes/60); const min = minutes%60;
|
| 254 |
+
const ampm = hr>=12? 'PM':'AM'; const hr12 = ((hr+11)%12)+1;
|
| 255 |
+
const mm = String(min).padStart(2,'0');
|
| 256 |
+
if(label) label.textContent = `${hr12}:${mm} ${ampm}`;
|
| 257 |
+
}
|
| 258 |
+
|
| 259 |
+
circle.addEventListener('mousedown', (e)=>{ dragging=true; e.preventDefault(); });
|
| 260 |
+
svg.addEventListener('mousemove',(e)=>{ if(dragging) updateByClientX(e.clientX); });
|
| 261 |
+
window.addEventListener('mouseup',()=> dragging=false);
|
| 262 |
+
|
| 263 |
+
// touch support
|
| 264 |
+
circle.addEventListener('touchstart', (e)=>{ dragging=true; e.preventDefault(); });
|
| 265 |
+
svg.addEventListener('touchmove',(e)=>{ if(dragging) updateByClientX(e.touches[0].clientX); });
|
| 266 |
+
window.addEventListener('touchend',()=> dragging=false);
|
| 267 |
+
|
| 268 |
+
// tap track
|
| 269 |
+
g.addEventListener('click',(e)=>{ updateByClientX((e.touches? e.touches[0].clientX : e.clientX)); });
|
| 270 |
+
});
|
| 271 |
+
}
|
| 272 |
+
|
| 273 |
+
render('home');
|
| 274 |
+
|
| 275 |
+
document.addEventListener('keydown',(e)=>{
|
| 276 |
+
if(e.key==='0') render('home');
|
| 277 |
+
if(e.key==='1') render('dashboard');
|
| 278 |
+
if(e.key==='2') render('popup');
|
| 279 |
+
if(e.key==='3') render('scheduler');
|
| 280 |
+
if(e.key==='4') render('weekly');
|
| 281 |
+
if(e.key==='5') render('settings');
|
| 282 |
+
});
|
| 283 |
+
</script>
|
| 284 |
+
</body>
|
| 285 |
</html>
|