Spaces:
Running
Running
Upload index.html with huggingface_hub
Browse files- index.html +479 -18
index.html
CHANGED
|
@@ -1,19 +1,480 @@
|
|
| 1 |
-
<!
|
| 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="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>
|