ArthurZ HF Staff commited on
Commit
82de106
·
verified ·
1 Parent(s): a556895

Upload index.html with huggingface_hub

Browse files
Files changed (1) hide show
  1. index.html +479 -18
index.html CHANGED
@@ -1,19 +1,480 @@
1
- <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
  </html>
 
1
+ <!DOCTYPE html>
2
+ <html lang="fr">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
6
+ <meta name="theme-color" content="#0b1220">
7
+ <title>Gouttes PKR 💧</title>
8
+ <style>
9
+ :root{
10
+ --bg:#0b1220; --card:#141d2e; --card2:#1b2740; --line:#26334d;
11
+ --txt:#e8eefc; --muted:#8da0c4; --accent:#4f8cff; --ok:#39d98a;
12
+ --warn:#ffb020; --danger:#ff5d6c; --chip:#22304d;
13
+ --shadow:0 6px 22px rgba(0,0,0,.35);
14
+ }
15
+ *{box-sizing:border-box;-webkit-tap-highlight-color:transparent}
16
+ body{margin:0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;
17
+ background:var(--bg);color:var(--txt);padding:14px 12px 90px;max-width:560px;margin:0 auto}
18
+ h1{font-size:20px;margin:0 0 2px;display:flex;align-items:center;gap:8px}
19
+ .sub{color:var(--muted);font-size:13px;margin-bottom:14px}
20
+ .clock{font-variant-numeric:tabular-nums;font-weight:700}
21
+
22
+ .topbar{display:flex;justify-content:space-between;align-items:center;gap:10px;
23
+ background:var(--card);border:1px solid var(--line);border-radius:16px;padding:12px 14px;margin-bottom:12px}
24
+ .topbar .now{font-size:30px;font-weight:800;letter-spacing:.5px}
25
+ .topbar .day{font-size:12px;color:var(--muted);text-align:right}
26
+
27
+ .modebar{display:flex;gap:6px;margin-bottom:10px;flex-wrap:wrap}
28
+ .modebar button{flex:1;min-width:110px;background:var(--card);color:var(--muted);border:1px solid var(--line);padding:10px;font-size:13px;border-radius:12px}
29
+ .modebar button.active{background:rgba(79,140,255,.2);color:#a9c6ff;border-color:var(--accent)}
30
+ .modebar .notifbtn{flex:0 0 auto;min-width:0}
31
+ .modebar .notifbtn.on{background:rgba(57,217,138,.2);color:#7af0b6;border-color:var(--ok)}
32
+ .lasiknote{display:none;background:rgba(255,176,32,.1);border:1px solid rgba(255,176,32,.35);color:#ffd27a;
33
+ font-size:12px;border-radius:12px;padding:10px 12px;margin-bottom:12px;line-height:1.45}
34
+ .lasiknote.show{display:block}
35
+ .banner{border-radius:14px;padding:12px 14px;margin-bottom:12px;font-weight:600;font-size:14px;
36
+ display:none;align-items:center;gap:10px;border:1px solid}
37
+ .banner.wait{display:flex;background:rgba(255,176,32,.12);border-color:rgba(255,176,32,.4);color:#ffd27a}
38
+ .banner.go{display:flex;background:rgba(57,217,138,.12);border-color:rgba(57,217,138,.4);color:#7af0b6}
39
+
40
+ .next{background:var(--card);border:1px solid var(--line);border-radius:16px;padding:12px 14px;margin-bottom:16px}
41
+ .next h2{font-size:13px;text-transform:uppercase;letter-spacing:1px;color:var(--muted);margin:0 0 8px}
42
+ .next .row{display:flex;justify-content:space-between;align-items:center;padding:7px 0;border-top:1px solid var(--line);font-size:14px}
43
+ .next .row:first-of-type{border-top:none}
44
+ .next .dot{width:10px;height:10px;border-radius:50%;display:inline-block;margin-right:8px}
45
+ .next .t{font-weight:700;font-variant-numeric:tabular-nums}
46
+ .next .late{color:var(--danger)}
47
+ .next .soon{color:var(--warn)}
48
+ .next .ok{color:var(--muted)}
49
+ .next .empty{color:var(--muted);font-size:13px;padding:4px 0}
50
+
51
+ .card{background:var(--card);border:1px solid var(--line);border-radius:18px;padding:14px;margin-bottom:14px;box-shadow:var(--shadow);border-left:5px solid var(--c)}
52
+ .card .head{display:flex;justify-content:space-between;align-items:flex-start;gap:10px}
53
+ .card .name{font-size:17px;font-weight:800;display:flex;align-items:center;gap:8px}
54
+ .card .desc{font-size:12.5px;color:var(--muted);margin-top:2px;line-height:1.4}
55
+ .pill{font-size:11px;font-weight:700;padding:4px 9px;border-radius:999px;background:var(--chip);color:var(--txt);white-space:nowrap}
56
+ .pill.warnpill{background:rgba(255,176,32,.18);color:#ffd27a}
57
+
58
+ .progress{display:flex;align-items:center;gap:8px;margin:12px 0 4px;font-size:13px;color:var(--muted)}
59
+ .progress b{color:var(--txt)}
60
+
61
+ .slots{display:flex;flex-wrap:wrap;gap:6px;margin:8px 0 12px}
62
+ .slot{font-size:12px;font-variant-numeric:tabular-nums;padding:5px 9px;border-radius:9px;background:var(--card2);color:var(--muted);border:1px solid var(--line)}
63
+ .slot.done{background:rgba(57,217,138,.16);color:#7af0b6;border-color:rgba(57,217,138,.4)}
64
+ .slot.next{background:rgba(79,140,255,.18);color:#a9c6ff;border-color:rgba(79,140,255,.5);font-weight:700}
65
+ .slot.late{background:rgba(255,93,108,.16);color:#ff9aa4;border-color:rgba(255,93,108,.45);font-weight:700}
66
+
67
+ .actions{display:flex;gap:8px;align-items:center;flex-wrap:wrap}
68
+ button{font-family:inherit;cursor:pointer;border:none;border-radius:12px;font-weight:700}
69
+ .give{flex:1;min-width:150px;background:var(--accent);color:#fff;padding:13px;font-size:15px}
70
+ .give:active{transform:scale(.98)}
71
+ .give.go{background:var(--ok)}
72
+ .give.blocked{background:transparent;color:var(--danger);border:1.5px dashed var(--danger);cursor:not-allowed;opacity:.85}
73
+ .give.notyet{background:var(--card2);color:var(--muted);border:1px solid var(--line)}
74
+ .mini{background:var(--card2);color:var(--txt);border:1px solid var(--line);padding:11px 12px;font-size:13px}
75
+ .mini.undo{color:#ff9aa4}
76
+ input[type=time]{background:var(--card2);color:var(--txt);border:1px solid var(--line);border-radius:10px;padding:9px;font-family:inherit;font-size:13px}
77
+
78
+ .phase{display:flex;gap:6px;flex-wrap:wrap;margin:6px 0 10px}
79
+ .phase button{font-size:11.5px;padding:6px 9px;background:var(--card2);color:var(--muted);border:1px solid var(--line)}
80
+ .phase button.active{background:rgba(79,140,255,.2);color:#a9c6ff;border-color:var(--accent)}
81
+
82
+ .footer{margin-top:18px;text-align:center}
83
+ .footer button{background:transparent;color:var(--muted);border:1px solid var(--line);padding:9px 14px;font-size:12px;margin:3px}
84
+ .note{font-size:11px;color:var(--muted);text-align:center;margin-top:14px;line-height:1.5}
85
+ details{background:var(--card);border:1px solid var(--line);border-radius:14px;padding:10px 14px;margin-bottom:14px}
86
+ summary{cursor:pointer;font-size:13px;font-weight:600;color:var(--muted)}
87
+ details ol{font-size:12.5px;color:var(--muted);line-height:1.6;padding-left:18px;margin:8px 0 0}
88
+ .howto{background:var(--card2);border:1px solid var(--line);border-radius:10px;padding:7px 11px;margin:2px 0 10px}
89
+ .howto summary{font-size:12px;color:var(--muted)}
90
+ .howto ol{font-size:12px;color:var(--muted);line-height:1.55;padding-left:16px;margin:7px 0 2px}
91
+ .howto li{margin-bottom:3px}
92
+ </style>
93
+ </head>
94
+ <body>
95
+
96
+ <h1>💧 Mes gouttes — yeux</h1>
97
+ <div class="sub">Clique « Donné » dès que tu mets une goutte → les prochaines heures se calculent toutes seules.</div>
98
+
99
+ <div class="topbar">
100
+ <div>
101
+ <div class="now clock" id="clock">--:--</div>
102
+ <div class="day" id="dayinfo"></div>
103
+ </div>
104
+ <div class="day" id="startbox"></div>
105
+ </div>
106
+
107
+ <div class="modebar">
108
+ <button id="mode_pkr" onclick="setMode('pkr')">PKR · mon traitement</button>
109
+ <button id="mode_lasik" onclick="setMode('lasik')">LASIK · type</button>
110
+ <button id="notifbtn" class="notifbtn" onclick="toggleNotif()">🔔 Rappels</button>
111
+ </div>
112
+ <div class="lasiknote" id="lasiknote"></div>
113
+
114
+ <div class="banner" id="banner"></div>
115
+
116
+ <details>
117
+ <summary>📋 Comment espacer (lis-moi)</summary>
118
+ <ol>
119
+ <li><b>5 min minimum entre deux produits différents</b> sinon la 1ère goutte fait déborder la 2ème.</li>
120
+ <li>1 seule goutte suffit (l'œil ne contient pas plus).</li>
121
+ <li>Ordre conseillé : antibiotique → corticoïde fort → cicatrisant/lubrifiant → rinçage en dernier. (Suis l'ordre de ton ophtalmo.)</li>
122
+ <li>Dacudose se garde au frais ❄️.</li>
123
+ </ol>
124
+ </details>
125
+
126
+ <div class="next">
127
+ <h2>⏭️ Prochaines doses</h2>
128
+ <div id="nextlist"></div>
129
+ </div>
130
+
131
+ <div id="cards"></div>
132
+
133
+ <div class="footer">
134
+ <button onclick="resetDay()">↺ Réinitialiser la journée</button>
135
+ <button onclick="resetAll()">🗑️ Tout effacer</button>
136
+ </div>
137
+
138
+ <div class="note">
139
+ Outil personnel d'aide-mémoire, sans valeur médicale. En cas de douleur intense, baisse de vision ou doute → ophtalmo.
140
+ </div>
141
+
142
+ <script>
143
+ /* ---------- Données des collyres ---------- */
144
+ const PKR_MEDS = [
145
+ {
146
+ id:'maxidrol', name:'Maxidrol', emoji:'🔴', color:'#ff5d6c',
147
+ desc:'Tobramycine + corticoïde — TRÈS fort. À donner en premier.',
148
+ taper:[
149
+ {label:'4×/j (début)', freq:4, intervalH:4},
150
+ {label:'3×/j · 7 jours', freq:3, intervalH:5},
151
+ {label:'2×/j · 7 jours', freq:2, intervalH:7},
152
+ {label:'1×/j · 7 jours', freq:1, intervalH:24},
153
+ ],
154
+ howto:[
155
+ 'Agiter le flacon avant usage 🫨',
156
+ 'Se laver les mains',
157
+ 'Ne pas toucher l\'œil ni les cils avec l\'embout',
158
+ 'Regarder vers le haut, tirer la paupière du bas, déposer 1 goutte dans le cul-de-sac conjonctival inférieur',
159
+ 'Fermer l\'œil et appuyer 2 min sur le coin interne (près du nez) 👃',
160
+ ],
161
+ },
162
+ { id:'quinofree', name:'Quinofree', emoji:'🟡', color:'#ffb020',
163
+ desc:'Ofloxacine (antibiotique) — 1 goutte, 4×/j pendant 7 jours.',
164
+ freq:4, intervalH:4, durationDays:7,
165
+ howto:[
166
+ 'Se laver les mains',
167
+ 'Ne pas toucher l\'œil avec l\'embout',
168
+ 'Regarder en haut, tirer la paupière du bas, 1 goutte dans le cul-de-sac inférieur',
169
+ 'Fermer l\'œil doucement quelques secondes',
170
+ ] },
171
+ { id:'nebraya', name:'Nebraya', emoji:'🟢', color:'#39d98a',
172
+ desc:'Cicatrisant — 1 goutte, 6×/j pendant 30 jours. + à volonté si douleur.',
173
+ freq:6, intervalH:2.5, durationDays:30, asNeeded:true,
174
+ howto:[
175
+ 'Se laver les mains',
176
+ '1 goutte dans le cul-de-sac inférieur (paupière du bas)',
177
+ 'Une goutte de plus possible si douleur',
178
+ ] },
179
+ { id:'vitadrop', name:'Vitadrop', emoji:'🔵', color:'#4f8cff',
180
+ desc:'Vitamine (solution ophtalmique) — 1 goutte, 4×/j pendant 15 jours.',
181
+ freq:4, intervalH:4, durationDays:15,
182
+ howto:[
183
+ 'Se laver les mains',
184
+ 'Ne pas toucher l\'œil avec l\'embout',
185
+ '1 goutte dans le cul-de-sac inférieur',
186
+ 'Fermer l\'œil doucement',
187
+ ] },
188
+ { id:'dacudose', name:'Dacudose', emoji:'❄️', color:'#9aa6c4',
189
+ desc:'Acide borique (rinçage) — à volonté. Garder au frais.',
190
+ unlimited:true, intervalH:2,
191
+ howto:[
192
+ 'Se laver les mains',
193
+ 'Garder au frais ❄️',
194
+ 'Rincer l\'œil, puis jeter l\'unidose après usage',
195
+ ] },
196
+ ];
197
+
198
+ /* Protocole LASIK — TYPE / GÉNÉRIQUE (cicatrisation plus rapide qu'en PKR).
199
+ Les marques et durées exactes varient selon le chirurgien : à adapter à ton ordonnance. */
200
+ const LASIK_MEDS = [
201
+ { id:'lasik_antibio', name:'Antibiotique', emoji:'🟡', color:'#ffb020',
202
+ desc:'Ofloxacine / moxifloxacine — 1 goutte, 4×/j pendant ~7 jours.',
203
+ freq:4, intervalH:4, durationDays:7,
204
+ howto:[
205
+ 'Se laver les mains',
206
+ 'Ne pas toucher l\'œil avec l\'embout',
207
+ 'Regarder en haut, tirer la paupière du bas, 1 goutte dans le cul-de-sac inférieur',
208
+ 'Appuyer 1–2 min sur le coin interne (près du nez), fermer l\'œil',
209
+ ] },
210
+ { id:'lasik_cortico', name:'Corticoïde', emoji:'🔴', color:'#ff5d6c',
211
+ desc:'Dexaméthasone (anti-inflammatoire) — 1 goutte, 4×/j pendant ~7 jours (parfois toutes les 2h le jour J).',
212
+ freq:4, intervalH:4, durationDays:7,
213
+ howto:[
214
+ 'Agiter si suspension 🫨',
215
+ 'Se laver les mains',
216
+ 'Ne pas toucher l\'œil avec l\'embout',
217
+ '1 goutte dans le cul-de-sac inférieur',
218
+ 'Appuyer 1–2 min sur le coin interne (près du nez), fermer l\'œil',
219
+ ] },
220
+ { id:'lasik_larmes', name:'Larmes artificielles', emoji:'🟢', color:'#39d98a',
221
+ desc:'Sans conservateur — généreusement, jusqu\'à 1×/h la 1ère semaine, puis ≥4×/j pendant ~1 mois. À volonté.',
222
+ freq:6, intervalH:2, durationDays:30, asNeeded:true,
223
+ howto:[
224
+ 'Se laver les mains',
225
+ '1 goutte dans le cul-de-sac inférieur',
226
+ 'À renouveler dès que l\'œil est sec ou irrité',
227
+ ] },
228
+ ];
229
+
230
+ let MEDS = PKR_MEDS;
231
+
232
+ /* ---------- État ---------- */
233
+ const KEY='pkr_state_v2';
234
+ function todayKey(){const d=new Date();return d.getFullYear()+'-'+String(d.getMonth()+1).padStart(2,'0')+'-'+String(d.getDate()).padStart(2,'0');}
235
+ function loadState(){
236
+ let s=null; try{s=JSON.parse(localStorage.getItem(KEY));}catch(e){}
237
+ if(!s){ s={startDate:todayKey(), maxidrolPhase:0, logs:{}, lastDrop:null, mode:'pkr', notif:false}; }
238
+ if(!s.logs) s.logs={};
239
+ if(!s.mode) s.mode='pkr';
240
+ // pré-remplissage : Maxidrol déjà donné à 12h10 aujourd'hui
241
+ const tk=todayKey();
242
+ if(!s.logs[tk]){ s.logs[tk]={}; if(s.startDate===tk){ s.logs[tk].maxidrol=['12:10']; } }
243
+ return s;
244
+ }
245
+ let S=loadState();
246
+ MEDS = (S.mode==='lasik') ? LASIK_MEDS : PKR_MEDS;
247
+ function save(){localStorage.setItem(KEY,JSON.stringify(S));}
248
+ function setMode(m){ S.mode=m; MEDS=(m==='lasik')?LASIK_MEDS:PKR_MEDS; firedNotif={}; save(); render(); }
249
+ function today(){const tk=todayKey(); if(!S.logs[tk])S.logs[tk]={}; return S.logs[tk];}
250
+
251
+ /* ---------- Helpers temps ---------- */
252
+ function nowStr(){const d=new Date();return String(d.getHours()).padStart(2,'0')+':'+String(d.getMinutes()).padStart(2,'0');}
253
+ function hm(str){const[a,b]=str.split(':').map(Number);return a*60+b;}
254
+ function fromMin(m){m=((m%1440)+1440)%1440;return String(Math.floor(m/60)).padStart(2,'0')+':'+String(m%60).padStart(2,'0');}
255
+ function nowMin(){const d=new Date();return d.getHours()*60+d.getMinutes();}
256
+ function daysSinceStart(){
257
+ const a=new Date(S.startDate+'T00:00:00'), b=new Date(todayKey()+'T00:00:00');
258
+ return Math.round((b-a)/86400000);
259
+ }
260
+
261
+ /* ---------- Logique d'un collyre ---------- */
262
+ function medCfg(m){
263
+ if(m.taper) return {...m, ...m.taper[S.maxidrolPhase]};
264
+ return m;
265
+ }
266
+ function give(id,time){
267
+ const t=today(); if(!t[id])t[id]=[];
268
+ t[id].push(time||nowStr());
269
+ t[id].sort((a,b)=>hm(a)-hm(b));
270
+ if(!time){ S.lastDrop={id, ts:Date.now()}; }
271
+ save(); render();
272
+ }
273
+ function undo(id){ const t=today(); if(t[id]&&t[id].length){t[id].pop(); save(); render();} }
274
+
275
+ /* planned times: étalées de la 1ère goutte du jour → minuit max */
276
+ function planned(cfg){
277
+ const t=today(); const given=t[cfg.id]||[];
278
+ if(cfg.unlimited||!cfg.freq) return null;
279
+ const anchor = given.length? hm(given[0]) : nowMin();
280
+ const END = 1440; // minuit
281
+ if(cfg.freq===1) return [anchor];
282
+ const step = Math.max(0,(END-anchor))/(cfg.freq-1);
283
+ const arr=[];
284
+ for(let k=0;k<cfg.freq;k++) arr.push(Math.round(anchor + k*step));
285
+ return arr;
286
+ }
287
+
288
+ /* ---------- Calcul par collyre (partagé render + tick) ---------- */
289
+ function blockState(){
290
+ if(!S.lastDrop) return {blocked:false,elapsed:1e9};
291
+ const elapsed=Math.floor((Date.now()-S.lastDrop.ts)/1000);
292
+ return {blocked:elapsed<300, elapsed, rem:300-elapsed, fromId:S.lastDrop.id};
293
+ }
294
+ function computeMed(m){
295
+ const cfg=medCfg(m);
296
+ const given=(today()[m.id]||[]);
297
+ const plan=planned(cfg);
298
+ const nm=nowMin();
299
+ let nextMin=null,status='';
300
+ if(plan){
301
+ const idx=given.length;
302
+ if(idx<plan.length){ nextMin=plan[idx]; status=(nm>=nextMin)?'late':(nextMin-nm<=30?'soon':'ok'); }
303
+ }
304
+ // slots
305
+ let slotsHtml='';
306
+ if(plan){
307
+ plan.forEach((p,i)=>{
308
+ let cls='slot';
309
+ if(i<given.length) cls+=' done';
310
+ else if(i===given.length) cls+=(nm>=p?' late':' next');
311
+ slotsHtml+='<span class="'+cls+'">'+(i<given.length?'✓ '+given[i]:fromMin(p))+'</span>';
312
+ });
313
+ } else if(m.unlimited){
314
+ given.forEach(g=>slotsHtml+='<span class="slot done">✓ '+g+'</span>');
315
+ if(!given.length) slotsHtml='<span class="slot">à volonté</span>';
316
+ }
317
+ // progress
318
+ let progress='';
319
+ if(cfg.freq){ progress='Aujourd\'hui : <b>'+given.length+' / '+cfg.freq+'</b>'+(nextMin!=null?' · prochaine <b>'+fromMin(nextMin)+'</b>':' ✅ terminé'); }
320
+ else if(m.unlimited){ progress='<b>'+given.length+'</b> aujourd\'hui'; }
321
+ return {cfg,given,plan,nextMin,status,slotsHtml,progress,goNow:(nextMin!=null&&nm>=nextMin)};
322
+ }
323
+
324
+ /* ---------- Rendu ---------- */
325
+ function render(){
326
+ document.getElementById('clock').textContent=nowStr();
327
+ document.getElementById('dayinfo').textContent='Jour '+(daysSinceStart()+1)+' · '+todayKey().split('-').reverse().join('/');
328
+ document.getElementById('startbox').innerHTML='Début traitement<br><input type="date" value="'+S.startDate+'" onchange="S.startDate=this.value;save();render()" style="background:var(--card2);color:var(--txt);border:1px solid var(--line);border-radius:8px;padding:4px;font-size:11px;margin-top:3px">';
329
+
330
+ /* mode + notif */
331
+ document.getElementById('mode_pkr').className = (S.mode==='pkr'?'active':'');
332
+ document.getElementById('mode_lasik').className = (S.mode==='lasik'?'active':'');
333
+ const ln=document.getElementById('lasiknote');
334
+ if(S.mode==='lasik'){ ln.className='lasiknote show'; ln.innerHTML='ℹ️ Protocole LASIK <b>générique/type</b> (cicatrisation plus rapide). Marques et durées varient selon le chirurgien — adapte à ton ordonnance.'; }
335
+ else ln.className='lasiknote';
336
+ const nb=document.getElementById('notifbtn');
337
+ const granted = ('Notification' in window) && Notification.permission==='granted' && S.notif;
338
+ nb.className = 'notifbtn'+(granted?' on':''); nb.textContent = granted?'🔔 Rappels ON':'🔕 Rappels';
339
+
340
+ /* cartes */
341
+ const cardsEl=document.getElementById('cards'); cardsEl.innerHTML='';
342
+ MEDS.forEach(m=>{
343
+ const d=computeMed(m);
344
+
345
+ /* pill */
346
+ let pill='';
347
+ if(m.durationDays!==undefined){
348
+ const rem=m.durationDays-daysSinceStart();
349
+ pill='<span class="pill">'+(rem>0?'reste '+rem+' j':'fini ?')+'</span>';
350
+ } else if(m.unlimited){ pill='<span class="pill warnpill">à volonté ❄️</span>'; }
351
+
352
+ /* phases maxidrol */
353
+ let phaseHtml='';
354
+ if(m.taper){
355
+ phaseHtml='<div class="phase">';
356
+ m.taper.forEach((p,i)=>{ phaseHtml+='<button class="'+(i===S.maxidrolPhase?'active':'')+'" onclick="S.maxidrolPhase='+i+';save();render()">'+p.label+'</button>'; });
357
+ phaseHtml+='</div>';
358
+ }
359
+
360
+ /* comment l'utiliser */
361
+ let howtoHtml='';
362
+ if(m.howto){
363
+ howtoHtml='<details class="howto"><summary>💧 Comment l\'utiliser</summary><ol>'
364
+ +m.howto.map(s=>'<li>'+s+'</li>').join('')+'</ol></details>';
365
+ }
366
+
367
+ const card=document.createElement('div');
368
+ card.className='card'; card.style.setProperty('--c',m.color);
369
+ card.innerHTML=
370
+ '<div class="head"><div><div class="name">'+m.emoji+' '+m.name+'</div><div class="desc">'+m.desc+'</div></div>'+pill+'</div>'
371
+ +phaseHtml
372
+ +'<div class="progress" id="prog_'+m.id+'">'+d.progress+'</div>'
373
+ +howtoHtml
374
+ +'<div class="slots" id="slots_'+m.id+'">'+d.slotsHtml+'</div>'
375
+ +'<div class="actions">'
376
+ +'<button class="give" id="give_'+m.id+'" onclick="give(\''+m.id+'\')"></button>'
377
+ +'<input type="time" id="t_'+m.id+'" value="'+nowStr()+'">'
378
+ +'<button class="mini" onclick="give(\''+m.id+'\',document.getElementById(\'t_'+m.id+'\').value)">+ heure</button>'
379
+ +'<button class="mini undo" onclick="undo(\''+m.id+'\')">↶</button>'
380
+ +'</div>';
381
+ cardsEl.appendChild(card);
382
+ });
383
+
384
+ tick(); // remplit boutons, banner, liste, compteur
385
+ }
386
+
387
+ /* ---------- Ticker à la seconde (sans casser les inputs) ---------- */
388
+ function tick(){
389
+ document.getElementById('clock').textContent=nowStr();
390
+ const b=blockState();
391
+ const banner=document.getElementById('banner'); banner.className='banner';
392
+ if(b.blocked){
393
+ const mm=Math.floor(b.rem/60), ss=b.rem%60;
394
+ banner.className='banner wait';
395
+ banner.innerHTML='⏳ Bloqué encore <b style="margin:0 4px">'+mm+':'+String(ss).padStart(2,'0')+'</b> — 5 min entre 2 produits.';
396
+ } else if(b.elapsed<900){
397
+ banner.className='banner go';
398
+ banner.innerHTML='✅ OK pour le produit suivant.';
399
+ }
400
+
401
+ const upcoming=[];
402
+ MEDS.forEach(m=>{
403
+ const d=computeMed(m);
404
+ const sl=document.getElementById('slots_'+m.id); if(sl) sl.innerHTML=d.slotsHtml;
405
+ const pg=document.getElementById('prog_'+m.id); if(pg) pg.innerHTML=d.progress;
406
+ const btn=document.getElementById('give_'+m.id);
407
+ if(btn){
408
+ if(b.blocked){
409
+ btn.disabled=true; btn.className='give blocked'; btn.style.opacity=1;
410
+ const mm=Math.floor(b.rem/60), ss=b.rem%60;
411
+ btn.textContent='⛔ Attends '+mm+':'+String(ss).padStart(2,'0');
412
+ } else {
413
+ btn.disabled=false; btn.style.opacity=1;
414
+ if(d.cfg.unlimited){
415
+ btn.className='give'; btn.textContent='✓ Donné maintenant';
416
+ } else if(d.goNow || d.status==='soon'){
417
+ btn.className='give'+(d.goNow?' go':''); btn.textContent='✓ Donné'+(d.goNow?' (maintenant !)':' maintenant');
418
+ } else if(d.nextMin!=null){
419
+ btn.className='give notyet'; btn.textContent='⏰ Pas l\'heure · '+fromMin(d.nextMin);
420
+ } else {
421
+ btn.className='give notyet'; btn.textContent='✓ Fini aujourd\'hui';
422
+ }
423
+ }
424
+ }
425
+ // rappel "tu peux donner X" (une fois par créneau, quand c'est l'heure et non bloqué)
426
+ if(S.notif && d.nextMin!=null && d.goNow && !b.blocked){
427
+ const k=S.mode+':'+m.id+':'+d.given.length+':'+todayKey();
428
+ if(!firedNotif[k]){ firedNotif[k]=1; notify('💧 '+m.name,'Tu peux donner '+m.name+' maintenant.'); }
429
+ }
430
+ if(d.nextMin!=null) upcoming.push({m,min:d.nextMin,status:d.status});
431
+ });
432
+
433
+ // rappel fin d'espacement 5 min
434
+ if(S.notif && wasBlocked && !b.blocked){ notify('✅ Espacement fini','Tu peux donner la goutte suivante (5 min écoulées).'); }
435
+ wasBlocked=b.blocked;
436
+
437
+ upcoming.sort((a,b)=>a.min-b.min);
438
+ const nl=document.getElementById('nextlist');
439
+ if(!upcoming.length){ nl.innerHTML='<div class="empty">Tout est donné pour les créneaux planifiés 🎉</div>'; }
440
+ else{
441
+ nl.innerHTML=upcoming.map(u=>{
442
+ const cls=u.status==='late'?'late':u.status==='soon'?'soon':'ok';
443
+ const txt=u.status==='late'?'à donner !':u.status==='soon'?'bientôt':'';
444
+ return '<div class="row"><div><span class="dot" style="background:'+u.m.color+'"></span>'+u.m.emoji+' '+u.m.name+'</div>'
445
+ +'<div class="t '+cls+'">'+fromMin(u.min)+(txt?' · '+txt:'')+'</div></div>';
446
+ }).join('');
447
+ }
448
+ }
449
+
450
+ /* ---------- Notifications ---------- */
451
+ let firedNotif={}; // clés déjà notifiées (en mémoire, par jour)
452
+ let wasBlocked=false;
453
+ function toggleNotif(){
454
+ if(!('Notification' in window)){ alert('Les notifications ne sont pas supportées par ce navigateur.'); return; }
455
+ if(S.notif && Notification.permission==='granted'){ S.notif=false; save(); render(); return; }
456
+ Notification.requestPermission().then(p=>{
457
+ S.notif=(p==='granted'); save(); render();
458
+ if(p==='granted') notify('🔔 Rappels activés','Tu seras prévenu quand une goutte est à donner.');
459
+ else alert('Autorise les notifications dans les réglages du navigateur pour activer les rappels.');
460
+ });
461
+ }
462
+ function notify(title,body){
463
+ try{
464
+ if(('Notification' in window) && Notification.permission==='granted'){ new Notification(title,{body,tag:title}); }
465
+ if(navigator.vibrate) navigator.vibrate(200);
466
+ }catch(e){}
467
+ }
468
+
469
+ function resetDay(){ if(confirm('Effacer les gouttes données aujourd\'hui ?')){ S.logs[todayKey()]={}; S.lastDrop=null; firedNotif={}; save(); render(); } }
470
+ function resetAll(){ if(confirm('Tout remettre à zéro (historique + date de début) ?')){ localStorage.removeItem(KEY); S=loadState(); save(); render(); } }
471
+
472
+ render();
473
+ setInterval(tick,1000); // compteur 5 min + horloge + statuts (sans casser les champs heure)
474
+ // réveille l'horloge quand on revient sur la page (les navigateurs gèlent les timers en arrière-plan)
475
+ document.addEventListener('visibilitychange',()=>{ if(!document.hidden) tick(); });
476
+ window.addEventListener('focus',tick);
477
+ window.addEventListener('pageshow',tick);
478
+ </script>
479
+ </body>
480
  </html>