-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathindex.html
541 lines (471 loc) · 32.4 KB
/
index.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
<!DOCTYPE html>
<html lang="en" data-theme="dark">
<head>
<title>Beacon: Emergency Tools, Training & Strategy</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no, interactive-widget=resizes-content">
<meta name="description" content="Beacon combines emergency survival tools with skill-building and resource management. Quickly access life-saving features like an SOS signal, compass, pedometer, Morse code translator, and signal mirror, while also tracking your survival training, managing resources, and completing essential checklists. Whether you're in an emergency or sharpening your survival knowledge, Beacon keeps you prepared.">
<meta name="author" content="Michael Schwartz">
<meta name="mobile-web-app-capable" content="yes">
<meta name="application-name" content="Beacon: Emergency Tools, Training & Strategy">
<meta name="theme-color" content="#13171f">
<meta name="apple-mobile-web-app-title" content="Beacon: Emergency Tools, Training & Strategy">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<meta name="msapplication-starturl" content="./index.html">
<meta name="msapplication-navbutton-color" content="#13171f">
<meta property="og:url" content="https://michaelsboost.com/Beacon" />
<meta property="og:type" content="website" />
<meta property="og:title" content="Beacon: Emergency Tools, Training & Strategy" />
<meta property="og:description" content="Beacon combines emergency survival tools with skill-building and resource management. Quickly access life-saving features like an SOS signal, compass, pedometer, Morse code translator, and signal mirror, while also tracking your survival training, managing resources, and completing essential checklists. Whether you're in an emergency or sharpening your survival knowledge, Beacon keeps you prepared." />
<link rel="manifest" href="manifest.json">
<link rel="shortcut icon" type="image/x-icon" href="imgs/logo.svg">
<link rel="icon" type="image/svg+xml" href="imgs/logo.svg" />
<link rel="apple-touch-icon" href="imgs/logo.svg">
<link rel="stylesheet" href="dist/bundle.css">
<script src="https://cdn.jsdelivr.net/npm/alpinejs@3.14.1/dist/cdn.min.js" defer></script>
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
</head>
<body>
<div class="absolute inset-0 overflow-auto bg-gray-900 text-gray-100 font-sans" x-data="app">
<!-- Main Container -->
<div class="container mx-auto p-4">
<!-- Header -->
<header class="text-center mb-8">
<a href="https://github.com/michaelsboost/Beacon" target="_blank" class="text-blue-500 hover:underline">
<h1 class="text-4xl font-bold" x-text="appData.appName"></h1>
</a>
<p class="text-gray-400" x-text="appData.appDescription"></p>
</header>
<!-- Tabs Navigation -->
<nav class="flex justify-center overflow-x-auto gap-2 pb-2 whitespace-nowrap">
<div class="flex gap-2 overflow-x-auto max-w-full py-2 mb-2 px-4">
<button @click="setActiveTab('training')" :class="{ 'bg-blue-500 text-white': activeTab === 'training' }" class="px-4 py-2 rounded-lg">Training</button>
<button @click="setActiveTab('scenarios')" :class="{ 'bg-blue-500 text-white': activeTab === 'scenarios' }" class="px-4 py-2 rounded-lg">Scenarios</button>
<button @click="setActiveTab('resourceGame')" :class="{ 'bg-blue-500 text-white': activeTab === 'resourceGame' }" class="px-4 py-2 rounded-lg">Resource Game</button>
<button @click="setActiveTab('checklist')" :class="{ 'bg-blue-500 text-white': activeTab === 'checklist' }" class="px-4 py-2 rounded-lg">Checklist</button>
<button @click="setActiveTab('tools')" :class="{ 'bg-blue-500 text-white': activeTab === 'tools' }" class="px-4 py-2 rounded-lg">Tools</button>
<button @click="setActiveTab('library')" :class="{ 'bg-blue-500 text-white': activeTab === 'library' }" class="px-4 py-2 rounded-lg">Guides</button>
</div>
</nav>
<!-- Training Tab -->
<div x-show="activeTab === 'training'" x-data="training" class="space-y-6">
<!-- Progress Tracker -->
<div class="bg-gray-800 p-6 rounded-lg shadow-md">
<h2 class="text-2xl font-semibold mb-4">Overall Progress</h2>
<span class="text-sm text-gray-400">Skill Level: <span class="font-bold text-white" x-text="overallSkillLevel"></span></span>
<div class="flex items-center gap-2">
<div class="flex-1 h-2 bg-gray-700 rounded-full overflow-hidden">
<div class="h-full bg-blue-500" :style="`width: ${globalProgress}%`"></div>
</div>
<span class="text-sm text-gray-400" x-text="`${globalProgress}%`"></span>
</div>
</div>
<!-- Skill Tree -->
<div class="grid gap-4">
<template x-for="skill in skills" :key="skill.name">
<div class="bg-gray-800 p-6 rounded-lg shadow-md">
<!-- Skill Header -->
<div class="flex items-center gap-2 cursor-pointer" @click="toggleSkill(skill)">
<span class="text-2xl" x-text="skill.icon"></span>
<h3 class="text-xl font-semibold" x-text="skill.name"></h3>
<span class="ml-auto text-gray-400" x-text="skill.expanded ? '▲' : '▼'"></span>
</div>
<!-- Skill Progress -->
<div class="mt-4">
<span class="text-sm text-gray-400">Skill Level: <span class="font-bold text-white" x-text="skill.level"></span></span>
<div class="flex items-center gap-2 mt-2">
<div class="flex-1 h-2 bg-gray-700 rounded-full overflow-hidden">
<div class="h-full bg-blue-500" :style="`width: ${getSkillProgress(skill)}%`"></div>
</div>
<span class="text-sm text-gray-400" x-text="`${getSkillProgress(skill)}%`"></span>
</div>
</div>
<!-- Skill Challenge -->
<div class="mt-4" x-show="skill.expanded">
<p class="text-sm text-gray-400" x-text="skill.challenge ? skill.challenge.text : 'No challenge available'"></p>
<button @click="startChallenge(skill)" class="mt-4 px-4 py-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600 transition-colors">
Start Challenge
</button>
<div x-show="skill.challengeActive" class="mt-2">
<p class="text-sm text-gray-400">Time Remaining: <span x-text="skill.timeRemaining"></span></p>
<button @click="completeChallenge(skill, true)" class="mt-2 px-4 py-2 bg-green-500 text-white rounded-lg hover:bg-green-600 transition-colors">
Completed
</button>
<button @click="completeChallenge(skill, false)" class="mt-2 px-4 py-2 bg-red-500 text-white rounded-lg hover:bg-red-600 transition-colors">
Failed
</button>
</div>
</div>
</div>
</template>
</div>
</div>
<!-- Survival Scenario Simulator -->
<div x-show="activeTab === 'scenarios'" x-data="scenarioSimulator" class="space-y-6">
<h2 class="text-2xl font-light">Survival Scenario Simulator:</h2><br>
<!-- 📌 Scenario Selection & Display -->
<template x-for="scenario in scenarios" :key="scenario.title">
<div class="bg-gray-800 p-6 rounded-lg shadow-md">
<!-- Scenario Button -->
<button @click="toggleScenario(scenario)" class="block w-full text-left text-white font-semibold hover:text-blue-400 transition">
<span x-text="scenario.title"></span>
</button>
<!-- 📌 Active Scenario Details (Only Shown Inside the Clicked Box) -->
<div x-show="scenario.expanded" class="mt-4">
<p class="mt-2 text-gray-400" x-text="scenario.description"></p>
<p class="mt-2"><strong>Time Remaining:</strong> <span x-text="scenario.timeRemaining"></span> minutes</p>
<p class="mt-2"><strong>Current Challenge:</strong> <span x-text="scenario.challenges[scenario.challengeIndex]"></span></p>
<!-- Start Scenario Button (Shown Only If Scenario Hasn't Started) -->
<button x-show="!scenario.started" @click="startScenario(scenario)" class="mt-4 px-4 py-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600 transition-colors">
Start Scenario
</button>
<!-- Complete Challenge Button (Only Shown After Clicking Start) -->
<button x-show="scenario.started" @click="completeChallenge(scenario)" class="mt-4 px-4 py-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600 transition-colors">
Complete Challenge
</button>
<p class="mt-2 font-bold text-green-400" x-text="scenario.outcome"></p>
</div>
</div>
</template>
</div>
<!-- Resource Management Mini-Game -->
<div x-show="activeTab === 'resourceGame'" x-data="resourceGame" class="bg-gray-800 p-6 rounded-lg shadow-md">
<h2 class="text-2xl font-light">Survival Game</h2>
<div class="grid grid-cols-2 gap-4 mt-4">
<button @click="toggleStats()" class="px-4 py-2 bg-gray-500 text-white rounded-lg">Toggle Stats</button>
<button @click="toggleCrafting()" class="px-4 py-2 bg-red-500 text-white rounded-lg">Craft Items</button>
</div>
<!-- 📊 Stats Interface -->
<div x-show="showStats" class="grid grid-cols-1 sm:grid-cols-2 gap-4">
<!-- 📊 Survival Stats -->
<div class="mt-4 bg-gray-700 p-4 rounded-lg h-[400px] sm:h-[450px] overflow-y-auto">
<h3 class="text-lg font-light mb-2">📊 Survival Stats</h3>
<!-- Top Row: General Stats -->
<div class="grid grid-cols-3 gap-2 text-sm text-gray-300">
<p>📆 Days: <span x-text="daysSurvived"></span></p>
<p>🌦 Weather: <span x-text="weather"></span></p>
<p>🍂 Season: <span x-text="season"></span></p>
</div>
<!-- Middle Row: Resources -->
<div class="grid grid-cols-2 gap-2 text-sm text-gray-300 mt-2">
<p>💧 Water: <span x-text="resources.water"></span> L</p>
<p>🍖 Food: <span x-text="resources.food"></span> kg</p>
<p>🌡 Temp: <span x-text="temperature"></span>°C</p>
<p>🐾 Pet: <span x-text="pet ? 'Yes' : 'No'"></span></p>
</div>
<!-- Bars for Health, Energy, Hunger, and Thirst -->
<div class="mt-3">
<p>❤️ Health</p>
<div class="w-full bg-gray-600 rounded h-3">
<div class="bg-green-500 h-3 rounded" :style="'width: ' + Math.min(health, 100) + '%'"></div>
</div>
<p class="mt-2">⚡ Energy</p>
<div class="w-full bg-gray-600 rounded h-3">
<div class="bg-yellow-400 h-3 rounded" :style="'width: ' + Math.min(energy, 100) + '%'"></div>
</div>
<p class="mt-2">🍽 Hunger <span x-show="hunger >= 80" class="text-red-400">(Starving!)</span></p>
<div class="w-full bg-gray-600 rounded h-3">
<div :class="hunger >= 80 ? 'bg-red-600' : 'bg-red-500'" class="h-3 rounded" :style="'width: ' + Math.min(hunger, 100) + '%'"></div>
</div>
<p class="mt-2">🥤 Thirst <span x-show="thirst >= 80" class="text-red-400">(Dehydrated!)</span></p>
<div class="w-full bg-gray-600 rounded h-3">
<div :class="thirst >= 80 ? 'bg-red-600' : 'bg-blue-500'" class="h-3 rounded" :style="'width: ' + Math.min(thirst, 100) + '%'"></div>
</div>
</div>
<!-- Injury & Sickness -->
<div class="grid grid-cols-2 gap-2 text-sm text-gray-300 mt-3">
<p>🩹 Injury: <span x-text="injury ? injury : 'None'"></span></p>
<p>🤒 Sickness: <span x-text="sickness ? sickness : 'Healthy'"></span></p>
</div>
<!-- 🥩 Consume Resources -->
<div class="mt-4">
<h3 class="text-lg font-light">🍽 Consume Resources</h3>
<div class="flex gap-2 mt-2">
<button @click="consume('food')" class="px-3 py-1 bg-yellow-500 text-white rounded-lg">🍖 Eat</button>
<button @click="consume('water')" class="px-3 py-1 bg-blue-500 text-white rounded-lg">💧 Drink</button>
<button @click="consume('medicine')" class="px-3 py-1 bg-red-500 text-white rounded-lg">💊 Heal</button>
</div>
</div>
</div>
<!-- 🎒 Inventory (Now Wraps Text Properly on Mobile) -->
<div class="mt-4 bg-gray-700 p-4 rounded-lg h-[400px] sm:h-[450px] overflow-y-auto">
<h3 class="text-lg font-light mb-2">🎒 Inventory</h3>
<template x-if="inventory.length > 0">
<div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-2">
<template x-for="(item, index) in inventory" :key="index">
<div class="flex flex-col bg-gray-800 p-2 rounded-md text-center">
<!-- Make Text Wrap Instead of Truncate -->
<span class="text-sm break-words w-full" x-text="item"></span>
<button @click="removeItem(index)" class="bg-red-500 text-white px-2 py-1 rounded hover:bg-red-600 transition mt-1">
✖
</button>
</div>
</template>
</div>
</template>
<p x-show="inventory.length === 0" class="text-gray-400 text-sm">Inventory is empty.</p>
</div>
</div>
<!-- 🛠 Crafting Interface -->
<div x-show="showCrafting" class="mt-4 bg-gray-700 p-4 rounded-lg h-[400px] sm:h-[450px] overflow-y-auto">
<h3 class="text-lg font-light mb-2">🛠 Crafting Options</h3>
<template x-if="Object.keys(availableCraftingOptions()).length > 0">
<div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-4">
<template x-for="(recipe, name) in availableCraftingOptions()" :key="name">
<div class="flex flex-col items-center bg-gray-800 p-3 rounded-md shadow-md">
<!-- 📌 Item Name -->
<h4 class="text-md font-semibold text-orange-400 capitalize" x-text="name"></h4>
<!-- 🔧 Required Materials -->
<p class="text-xs text-gray-300 mt-1">Requires:
<span class="text-gray-200" x-text="recipe.materials.join(', ')"></span>
</p>
<!-- ⚡ Energy Cost -->
<p class="text-xs text-yellow-400 mt-1">⚡ -<span x-text="recipe.energy"></span> Energy</p>
<!-- 🎯 Craft Button -->
<button @click="$data.craft(name)" class="mt-2 px-4 py-2 bg-orange-500 text-white rounded-lg hover:bg-orange-600 transition">
Craft
</button>
</div>
</template>
</div>
</template>
<p x-show="Object.keys(availableCraftingOptions()).length === 0" class="text-gray-400 text-sm text-center">
❌ No craftable items available.
</p>
</div>
<h3 class="text-lg font-light mt-4">Actions:</h3>
<div class="grid grid-cols-2 gap-4">
<button @click="forageFood()" class="px-4 py-2 bg-green-500 text-white rounded-lg">Forage (-10 Energy, +Food)</button>
<button @click="collectWater()" class="px-4 py-2 bg-blue-500 text-white rounded-lg">Collect Water (-8 Energy, +Water)</button>
<button @click="hunt()" class="px-4 py-2 bg-yellow-500 text-white rounded-lg">Hunt (-15 Energy, Injury Risk, +Food)</button>
<button @click="explore()" class="px-4 py-2 bg-purple-500 text-white rounded-lg">Explore (-10 Energy, Find Loot)</button>
<button @click="trade()" class="px-4 py-2 bg-indigo-500 text-white rounded-lg">Trade Items</button>
<button @click="rest()" class="px-4 py-2 bg-gray-500 text-white rounded-lg">Rest (+30 Energy, +Health)</button>
</div>
<p class="mt-4 font-bold text-red-500" x-text="alertMessage"></p>
<p class="mt-4" x-text="eventLog"></p>
</div>
<!-- Emergency Preparedness Checklist -->
<div x-show="activeTab === 'checklist'" x-data="emergencyChecklist" class="space-y-6">
<!-- Global Readiness Progress -->
<div class="bg-gray-800 p-6 rounded-lg shadow-md">
<h2 class="text-2xl font-semibold mb-4">Overall Readiness</h2>
<span class="text-sm text-gray-400">Survival Level: <span class="font-bold text-white" x-text="globalReadinessLevel"></span></span>
<div class="flex items-center gap-2">
<div class="flex-1 h-2 bg-gray-700 rounded-full overflow-hidden">
<div class="h-full bg-green-500" :style="`width: ${globalReadinessPercentage}%`"></div>
</div>
<span class="text-sm text-gray-400" x-text="`${globalReadinessPercentage}%`"></span>
</div>
</div>
<!-- Survival Checklist Categories -->
<div class="grid gap-4 mt-6">
<template x-for="category in categories" :key="category.name">
<div class="bg-gray-800 p-6 rounded-lg shadow-md">
<!-- Category Header -->
<div class="flex items-center gap-2 cursor-pointer" @click="toggleCategory(category)">
<span class="text-2xl" x-text="category.icon"></span>
<h3 class="text-xl font-semibold" x-text="category.name"></h3>
<span class="ml-auto text-gray-400" x-text="category.expanded ? '▲' : '▼'"></span>
</div>
<!-- Category Readiness Progress -->
<div class="mt-4">
<span class="text-sm text-gray-400">Readiness Level: <span class="font-bold text-white" x-text="getCategoryReadinessLevel(category.name)"></span></span>
<div class="flex items-center gap-2 mt-2">
<div class="flex-1 h-2 bg-gray-700 rounded-full overflow-hidden">
<div class="h-full bg-blue-500" :style="`width: ${getCategoryPercentage(category.name)}%`"></div>
</div>
<span class="text-sm text-gray-400" x-text="`${getCategoryPercentage(category.name)}%`"></span>
</div>
</div>
<!-- Category Items (Expandable) -->
<div class="mt-4" x-show="category.expanded">
<template x-for="(item, index) in category.items" :key="item.name">
<label class="block cursor-pointer flex items-center gap-2 mt-2">
<input type="checkbox" class="form-checkbox text-blue-500" :id="`${category.name}-${index}`" x-model="item.checked" @change="toggleItem(item)">
<span class="text-white" x-text="`${item.name}`"></span>
</label>
</template>
</div>
</div>
</template>
</div>
</div>
<!-- Tools Tab -->
<div x-show="activeTab === 'tools'" class="container grid grid-cols-2 gap-4" x-data="survivalTools">
<!-- Backup & Restore Data -->
<article class="bg-gray-800 p-6 h-48 rounded-lg shadow-md flex flex-col justify-center items-center col-span-2">
<h2 class="text-xl font-semibold text-white">Beacon v<span x-text="status.currentVersion"></span></h2>
<button x-show="status.updateAvailable" @click="status.updatePWA()" class="mt-2 px-4 py-2 bg-red-500 text-white rounded-lg hover:bg-red-600 transition">
Update Beacon
</button>
<div class="grid grid-cols-2 gap-4">
<button @click="status.exportAppData()" class="mt-2 px-4 py-2 bg-green-500 text-white rounded-lg hover:bg-green-600 transition">
Export Data
</button>
<label class="mt-2 px-4 py-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600 transition cursor-pointer">
Import Data
<input type="file" accept=".json" class="hidden" @change="status.importAppData(event)">
</label>
</div>
</article>
<!-- SOS Signal -->
<article class="bg-gray-800 p-6 h-48 rounded-lg shadow-md flex flex-col justify-center items-center" x-data="{ playing: false }">
<button class="w-full px-4 py-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600 transition-colors" @click="playing = !playing; playing ? $refs.sos.play() : ($refs.sos.pause(), $refs.sos.currentTime = 0);" x-text="playing ? 'STOP SOS' : 'SOS'">
<audio x-ref="sos">
<source src="audios/audio_dfb929b28d.mp3" type="audio/mpeg">
</audio>
</button>
</article>
<!-- Signal Mirror -->
<article class="bg-gray-800 p-6 h-48 rounded-lg shadow-md flex flex-col justify-center items-center">
<button class="w-full px-4 py-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600 transition-colors" @click="signalMirror.flashing = !signalMirror.flashing; signalMirror.flashScreen();">
<span x-text="signalMirror.flashing ? 'STOP Flash' : 'Signal Mirror'"></span>
</button>
</article>
<!-- Compass -->
<article class="bg-gray-800 p-6 h-48 rounded-lg shadow-md flex flex-col justify-center items-center" x-data="survivalTools.compass" x-init="initCompass()">
<span x-text="compass.heading"></span>
<svg class="w-full my-4" viewBox="0 0 512 512">
<path d="M 255.5 0.024 C 255.06 0.105 254.62 0.228 254.221 0.341 L 253.903 0.341 C 115.633 1.702 3.354 113.11 0.632 251.054 L 0.632 251.371 C -0.728 254.201 -0.728 257.495 0.632 260.325 L 0.632 260.653 C 3.027 399.742 116.799 512 256.462 512 L 259.02 512 C 398.365 510.598 511.248 397.511 512.291 258.084 C 512.371 257.449 512.371 256.806 512.291 256.17 L 512.291 254.891 C 512.331 254.466 512.331 254.038 512.291 253.612 C 510.93 114.39 397.966 1.538 258.703 0.341 C 257.661 0.055 256.578 -0.052 255.5 0.024 Z M 244.315 21.114 C 244.949 21.073 245.594 21.155 246.229 21.114 L 246.229 31.04 C 246.188 34.724 248.111 38.152 251.304 40.035 C 254.507 41.877 258.416 41.877 261.619 40.035 C 264.812 38.152 266.736 34.714 266.695 31.04 L 266.695 21.124 C 388.654 26.333 486.299 123.978 491.508 245.937 L 479.678 245.937 C 474.04 246.449 469.875 251.494 470.397 257.132 C 470.919 262.761 475.953 266.925 481.592 266.404 L 491.508 266.404 C 486.299 388.363 388.654 486.008 266.695 491.216 L 266.695 481.3 C 266.799 478.357 265.63 475.51 263.487 473.49 C 261.343 471.47 258.432 470.471 255.5 470.75 C 255.06 470.832 254.62 470.955 254.221 471.067 C 249.461 472.142 246.118 476.422 246.229 481.3 L 246.229 491.216 C 124.269 486.008 26.624 388.363 21.416 266.404 L 31.332 266.404 C 35.016 266.444 38.444 264.521 40.327 261.328 C 42.165 258.135 42.165 254.205 40.327 251.013 C 38.444 247.82 35.005 245.896 31.332 245.937 L 21.416 245.937 C 26.573 124.623 123.236 27.244 244.315 21.124 L 244.315 21.114 Z" fill="currentColor"></path>
<g x-ref="compassArrow" style="transform-origin: center;">
<path d="M 248.445 67.683 C 248.279 67.978 248.129 68.282 247.997 68.594 C 247.526 69.592 247.22 70.66 247.092 71.756 L 210.24 254.254 C 210.013 255.45 210.013 256.677 210.24 257.872 L 246.875 440.559 C 247.687 445.167 251.69 448.526 256.369 448.526 C 261.048 448.526 265.051 445.166 265.862 440.559 L 302.498 257.858 C 302.724 256.663 302.724 255.435 302.498 254.24 L 265.638 71.749 C 264.964 68.179 262.335 65.294 258.844 64.29 C 255.352 63.287 251.593 64.336 249.126 67.002 L 248.453 67.675 L 248.445 67.683 Z M 229.683 256.056 L 283.048 256.049 L 256.362 389.465 L 229.683 256.056 Z" fill="currentColor"></path>
</g>
</svg>
</article>
<!-- Pedometer -->
<article class="bg-gray-800 p-6 h-48 rounded-lg shadow-md flex flex-col justify-center items-center" x-data="survivalTools.pedometer" x-init="pedometer.init()">
<p>Steps Taken: <span x-text="pedometer.steps"></span></p>
<p>Estimated Distance: <span x-text="(pedometer.steps * pedometer.stepLength).toFixed(2)"></span> meters</p>
<button @click="pedometer.reset()" class="w-full mt-4 px-4 py-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600 transition-colors">Reset</button>
</article>
<!-- Morse Code Translator -->
<article class="bg-gray-800 p-6 h-48 rounded-lg shadow-md flex flex-col justify-center items-center col-span-2">
<span class="text-2xl mb-4">Morse Code</span>
<fieldset class="flex w-full gap-0">
<input x-model="morseCode.textInput" @input="morseCode.updateMorse()" type="text" placeholder="Enter text..." class="w-full px-4 py-2 bg-gray-700 text-white rounded-l-lg focus:outline-none focus:ring-2 focus:ring-blue-500">
<button @click="morseCode.playMorseCode()" class="px-4 py-2 bg-blue-500 text-white rounded-r-lg hover:bg-blue-600 transition-colors" x-text="morseCode.playing ? 'Stop' : 'Play'">Play</button>
</fieldset>
<p x-text="morseCode.morseOutput" class="text-lg mt-2"></p>
</article>
<!-- Level Tool -->
<article class="bg-gray-800 p-6 h-48 rounded-lg shadow-md flex flex-col justify-center items-center col-span-2" x-data="survivalTools.levelTool" x-init="levelTool.init()">
<span class="text-2xl mb-4">Level Tool</span>
<div class="relative w-full h-40 border-4 border-gray-700 rounded-full flex justify-center items-center overflow-hidden bg-gray-200">
<div class="absolute w-8 h-8 bg-green-500 rounded-full transition-transform" :style="'transform: ' + levelTool.bubbleX() + ' ' + levelTool.bubbleY()"></div>
</div>
<p class="mt-4 text-sm">Tilt your device to see the bubble move.</p>
</article>
</div>
<!-- Survival Library -->
<div x-show="activeTab === 'library'" x-data="library" class="space-y-6">
<!-- 📊 Global Survival Readiness Tracker -->
<div class="bg-gray-800 p-6 rounded-lg shadow-md">
<h2 class="text-2xl font-semibold mb-4">Survival Readiness Progress</h2>
<span class="text-sm text-gray-400">
Guides Completed: <span class="font-bold text-white" x-text="completedGuides"></span> /
<span x-text="totalGuides"></span>
</span>
<div class="flex items-center gap-2 mt-2">
<div class="flex-1 h-2 bg-gray-700 rounded-full overflow-hidden">
<div class="h-full bg-blue-500 transition-all" :style="`width: ${libraryProgress}%`"></div>
</div>
<span class="text-sm text-gray-400" x-text="`${libraryProgress}%`"></span>
</div>
<h3 class="mt-2 text-lg text-white">Readiness Level:
<span class="font-bold text-blue-400" x-text="globalReadinessLevel"></span>
</h3>
</div>
<!-- 🔎 Search Bar -->
<div class="mb-4">
<input x-model="searchQuery" type="text" placeholder="Search guides..." class="w-full px-4 py-2 bg-gray-700 text-white rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500">
</div>
<!-- 🏷️ Dynamic Category Tabs -->
<div class="flex overflow-x-auto gap-2 pb-2 whitespace-nowrap">
<template x-for="category in categories" :key="category.key">
<button @click="activeCategory = category.key" :class="{ 'bg-blue-500 text-white': activeCategory === category.key }" class="px-4 py-2 rounded-lg transition">
<span x-text="category.name"></span>
<span class="ml-2 text-xs text-gray-300" x-text="`(${getCategoryPercentage(category.key)}%)`"></span>
</button>
</template>
</div>
<!-- 📚 Guide List -->
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
<template x-for="guide in filteredGuides" :key="guide.title">
<div class="p-4 bg-gray-700 rounded-lg shadow-md">
<h3 class="font-semibold text-lg text-white" x-text="guide.title"></h3>
<p class="text-gray-400 mt-2" x-text="guide.description"></p>
<div class="mt-4 flex justify-between items-center">
<!-- 📖 Expand Guide -->
<button @click="toggleGuide(guide)" class="px-3 py-1 bg-blue-500 text-white rounded-lg">
<span x-text="guide.expanded ? 'Hide Details' : 'View Guide'"></span>
</button>
<!-- ✅ Toggle Switch for Completion -->
<label class="flex items-center cursor-pointer">
<!-- Hidden Checkbox Input -->
<input type="checkbox" class="hidden" @change="toggleGuideCompletion(guide)" :checked="guide.completed">
<!-- Switch Background (Changes Color Based on Completion) -->
<div class="relative w-10 h-5 rounded-full transition-all" :class="guide.completed ? 'bg-green-500' : 'bg-gray-500'">
<!-- Switch Knob (Moves When Checked) -->
<div class="absolute left-1 top-1 w-3 h-3 bg-white rounded-full transition-all" :class="{'translate-x-5': guide.completed}">
</div>
</div>
<!-- Label Text (Changes Text Color Based on Completion) -->
<span class="ml-2 text-sm" :class="guide.completed ? 'text-green-400' : 'text-gray-300'" x-text="guide.completed ? 'Completed' : 'Incomplete'">
</span>
</label>
</div>
<!-- 📖 Guide Details (Expandable) -->
<div x-show="guide.expanded" class="mt-4 p-4 bg-gray-800 rounded-lg">
<h4 class="text-md font-semibold text-white">Materials Needed:</h4>
<ul class="list-disc list-inside text-gray-300">
<template x-for="material in guide.materials">
<li x-text="material"></li>
</template>
</ul>
<h4 class="text-md font-semibold text-white mt-2">Steps:</h4>
<div class="list-decimal list-inside text-gray-300">
<template x-for="step in guide.steps">
<div x-html="markdownToHTML(step)"></div>
</template>
</div>
<h4 class="text-md font-semibold text-white mt-2">Tips:</h4>
<ul class="list-disc list-inside text-gray-300">
<template x-for="tip in guide.tips">
<li x-html="markdownToHTML(tip)"></li>
</template>
</ul>
</div>
</div>
</template>
</div>
</div>
</div>
</div>
<div id="signalMirror" class="fixed inset-0 bg-white hidden z-20"></div>
<script src="dist/script.js" ></script>
<script src="https://storage.googleapis.com/workbox-cdn/releases/6.4.1/workbox-sw.js"></script>
<script>
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('./sw.js').then(reg => {
reg.addEventListener('updatefound', () => {
const newSW = reg.installing;
newSW.addEventListener('statechange', () => {
if (newSW.state === 'installed' && navigator.serviceWorker.controller) {
// Notify the user and reload if they confirm
if (confirm('A new version is available. Reload now?')) {
window.location.reload();
}
}
});
});
});
// Ensure immediate activation of a new service worker
navigator.serviceWorker.ready.then(registration => {
registration.active.postMessage({ type: 'SKIP_WAITING' });
});
}
</script>
</body>
</html>