index.html (15.6 KB)


  1 <!DOCTYPE html>
  2 <html lang="en">
  3 <head>
  4 	<meta charset="UTF-8">
  5 	<meta name="viewport" content="width=device-width, initial-scale=1.0">
  6 	<link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>💡</text></svg>">
  7 	<title>PWA Installation Guide</title>
  8 	<style>
  9 		html {
 10 			scroll-behavior: smooth;
 11 		}
 12 		
 13 		body {
 14 			font-family: sans-serif;
 15 			max-width: 800px;
 16 			margin: 0 auto;
 17 			padding: 20px;
 18 			background-color: #f5f5f5;
 19 			color: #333;
 20 			line-height: 1.6;
 21 		}
 22 		
 23 		h1 {
 24 			text-align: center;
 25 			border-bottom: 3px double #333;
 26 			padding-bottom: 10px;
 27 			margin-bottom: 30px;
 28 		}
 29 		
 30 		.subtitle {
 31 			text-align: center;
 32 			margin-bottom: 30px;
 33 		}
 34 		
 35 		h2 {
 36 			border-bottom: 1px solid #666;
 37 			padding-bottom: 5px;
 38 			margin-top: 0;
 39 		}
 40 
 41 		.toc {
 42 			border: 1px solid #ccc;
 43 			padding: 15px;
 44 			margin: 20px 0;
 45 			background-color: #fff;
 46 		}
 47 		
 48 		.toc h3 {
 49 			margin-top: 0;
 50 			text-decoration: underline;
 51 		}
 52 		
 53 		.toc ul {
 54 			margin: 10px 0;
 55 			padding-left: 20px;
 56 		}
 57 		
 58 		
 59 		a {
 60 			color: #0000EE;
 61 			text-decoration: underline;
 62 		}
 63 		
 64 		a:visited {
 65 			color: #0000EE;
 66 		}
 67 		
 68 		a.auto-detected,
 69 		a.auto-detected:visited {
 70 			background: linear-gradient(to top right, #64b5f6 0%, #e3f2fd 100%);
 71 			padding: 12px;
 72 			font-weight: bold;
 73 			font-size: 1.15em;
 74 			display: none;
 75 			text-align: center;
 76 			cursor: pointer;
 77 			border-radius: 12px;
 78 			text-decoration: none;
 79 			color: #ffffff;
 80 		}
 81 
 82 		.auto-detected p {
 83 			padding: 10px;
 84 		}
 85 
 86 		.auto-detected #detected-link {
 87 			color: #0000EE;
 88 			text-decoration: underline;
 89 		}
 90 
 91 		.lightbulb {
 92 			text-shadow: 0px 0px 1px black;
 93 			font-size: 1.4em;
 94 			vertical-align: middle;
 95 			line-height: 1;
 96 			user-select: none;
 97 		}
 98 
 99 		
100 		.section {
101 			margin: 30px 0;
102 			padding: 20px;
103 			padding-top: 16px;
104 			background-color: #fff;
105 			border: 1px solid #ccc;
106 			font-size: 1.1em;
107 		}
108 
109 		.section:first-of-type {
110 			margin-top: 3px;
111 			padding-top: 4px;
112 			padding-bottom: 6px;
113 		}
114 
115 		#pwa-emoji {
116 			display: none;
117 			float: right;
118 			margin: 15px;
119 			margin-top: 40px;
120 			font-size: 70px;
121 			line-height: 1;
122 			cursor: default;
123 			user-select: none;
124 		}
125 
126 		#pwa-infrared {
127 			display: none;
128 			float: right;
129 			margin: 10px;
130 			margin-top: 15px;
131 			margin-right: 7px;
132 			padding-left: 10px;
133 			width: 100px;
134 			image-rendering: pixelated;
135 			cursor: default;
136 			user-select: none;
137 		}
138 
139 		.big-letter {
140 			font-size: 1.25em;
141 			font-variant: normal;
142 			line-height: 0;
143 		}
144 
145 		.section:last-of-type {
146 			margin-bottom: 0;
147 			padding-bottom: 10px;
148 		}
149 		
150 		ol {
151 			padding-left: 25px;
152 		}
153 		
154 		li {
155 			margin: 8px 0;
156 		}
157 		
158 		.toc li {
159 			margin: 5px 0;
160 		}
161 		
162 		code {
163 			background-color: #f0f0f0;
164 			padding: 2px 4px;
165 			font-family: "Courier New", monospace;
166 			border: 1px solid #ddd;
167 		}
168 
169 		.arrow {
170 			display: inline-block;
171 			width: 16px;
172 			height: 10px;
173 			position: relative;
174 			margin: 0 6px;
175 			vertical-align: middle;
176 		}
177 
178 		.arrow::before {
179 			content: '';
180 			position: absolute;
181 			top: 50%;
182 			left: 0;
183 			width: 7.5px;
184 			height: 2px;
185 			background-color: #333;
186 			transform: translateY(-50%);
187 		}
188 
189 		.arrow::after {
190 			content: '';
191 			position: absolute;
192 			top: 50%;
193 			left: 7.5px;
194 			width: 0;
195 			height: 0;
196 			border-top: 5px solid transparent;
197 			border-bottom: 5px solid transparent;
198 			border-left: 6px solid #333;
199 			transform: translateY(-50%);
200 		}
201 
202 		.ui-element {
203 			background-color: #f0f0f0;
204 			padding: 2px 6px;
205 			border-radius: 4px;
206 			border: 1px solid #ddd;
207 			font-size: 1em;
208 		}
209 
210 		.dots {
211 			display: inline-flex;
212 			align-items: center;
213 			gap: 3px;
214 			height: 1em;
215 			vertical-align: middle;
216 		}
217 
218 		.dots::before,
219 		.dots::after,
220 		.dots span::before {
221 			content: '';
222 			display: inline-block;
223 			width: 3.5px;
224 			height: 3.5px;
225 			background-color: #333;
226 			border-radius: 50%;
227 		}
228 
229 		.dots span {
230 			display: inline-flex;
231 		}
232 
233 		.dots span::before {
234 			content: '';
235 		}
236 
237 		.vdots {
238 			display: inline-flex;
239 			flex-direction: column;
240 			align-items: center;
241 			gap: 3px;
242 			vertical-align: middle;
243 			justify-content: center;
244 			min-height: 1.8em;
245 			box-sizing: content-box;
246 			padding: 2px 0;
247 		}
248 
249 		.vdots::before,
250 		.vdots::after,
251 		.vdots span::before {
252 			content: '';
253 			display: block;
254 			width: 3.5px;
255 			height: 3.5px;
256 			background-color: #333;
257 			border-radius: 50%;
258 		}
259 
260 		.vdots span {
261 			display: flex;
262 		}
263 
264 		.vdots span::before {
265 			content: '';
266 		}
267 
268 		.ui-element:has(.vdots:only-child) {
269 			padding-top: 6px;
270 			padding-bottom: 6px;
271 		}
272 
273 		.note {
274 			font-style: italic;
275 			color: #666;
276 			border-left: 3px solid #ccc;
277 			padding-left: 10px;
278 			margin: 15px 0;
279 		}
280 
281 		.note code {
282 			font-style: normal;
283 		}
284 
285 		.walkthrough-image {
286 			max-width: 100%;
287 			width: 500px;
288 			display: block;
289 			margin-left: auto;
290 			margin-right: auto;
291 		}
292 		
293 		/* Dark mode styles */
294 		@media (prefers-color-scheme: dark) {
295 			body {
296 				background-color: #141414;
297 				color: #ffffff;
298 			}
299 
300 			h1, h2 {
301 				color: #ffffff;
302 				border-color: #666;
303 			}
304 
305 			.toc, .section {
306 				background-color: #1f1f22;
307 				border: none;
308 				box-shadow: 0 2px 8px rgba(0, 0, 0, 0.7);
309 			}
310 
311 			a.auto-detected,
312 			a.auto-detected:visited {
313 				background: linear-gradient(to top right, #1a1a3a 0%, #47496b 100%);
314 				text-shadow: 0px 1px 1px rgb(0, 0, 0);
315 				color: #ffffff;
316 			}
317 
318 			.pwa-icon {
319 				filter: drop-shadow(2px 2px 3px rgba(0, 0, 0, 0.5));
320 			}
321 
322 			a {
323 				color: #80d4ff;
324 			}
325 
326 			a:visited {
327 				color: #80d4ff;
328 			}
329 
330 			.auto-detected #detected-link {
331 				color: #80d4ff;
332 			}
333 
334 			.ui-element {
335 				background-color: #3d3d3d;
336 				border-color: #555;
337 				color: #e0e0e0;
338 			}
339 
340 			.arrow::before {
341 				background-color: #fff;
342 			}
343 
344 			.arrow::after {
345 				border-left-color: #fff;
346 			}
347 
348 			.dots::before,
349 			.dots::after,
350 			.dots span::before {
351 				background-color: #e0e0e0;
352 			}
353 
354 			.vdots::before,
355 			.vdots::after,
356 			.vdots span::before {
357 				background-color: #e0e0e0;
358 			}
359 
360 			.note {
361 				color: #aaa;
362 				border-left-color: #666;
363 			}
364 			
365 			footer {
366 				border-top-color: #666;
367 				color: #aaa;
368 			}
369 		}
370 		
371 		/* Mobile responsive styles */
372 		@media (max-width: 768px) {
373 			body {
374 				padding: 0px;
375 			}
376 			
377 			h1 {
378 				margin-top: 10px;
379 				margin-bottom: 20px;
380 				font-size: 1.8em;
381 			}
382 			
383 			.subtitle {
384 				display: none;
385 			}
386 			
387 			.section, .toc {
388 				margin: 20px 0;
389 				padding: 15px 10px;
390 				border-left: none;
391 				border-right: none;
392 			}
393 
394 			.section:first-of-type {
395 				margin-top: 0px;
396 				padding-top: .1px;
397 				padding-bottom: 6px;
398 			}
399 
400 			a.auto-detected {
401 				margin: 20px 0;
402 				padding: 12px 16px;
403 				border: none;
404 				border-radius: 0;
405 			}
406 	}
407 	</style>
408 </head>
409 <body>
410 	<div id="about-pwas" class="section">
411 		<span id="pwa-emoji" class="pwa-icon">📲</span>
412 		<img id="pwa-infrared" src="infrared.png" alt="">
413 		<p><b><span class="big-letter">P</span>rogressive <span class="big-letter">W</span>eb <span class="big-letter">A</span>pps</b> (PWAs) are apps that can be installed directly from supported websites, independent of centralized app stores.</p>
414 
415 		<p>You can use the instructions below to install PWAs directly from your web browser.</p>
416 	</div>
417 
418 	<a id="auto-detected" class="auto-detected" href="#">
419 		<p><span class="lightbulb">💡</span> Based on your system, you might want to jump directly to: <span id="detected-link"></span></p>
420 	</a>
421 
422 	<div id="safari-ios" class="section">
423 		<h2>Safari (iOS / iPadOS)</h2>
424 		<p class="note">Note: on iOS, Safari is the only browser that can install PWAs.</p>
425 		<p>Open Safari and navigate to a website that supports PWA installation, then tap:</p>
426 		<p>1. The three dots button in the bottom right corner of the screen:</p>
427 		<img src="ios_safari/dots.jpeg" class="walkthrough-image">
428 		<p>2. Share:</p>
429 		<img src="ios_safari/share.jpeg" class="walkthrough-image">
430 		<p>3. View More:</p>
431 		<img src="ios_safari/view_more.jpeg" class="walkthrough-image">
432 		<p>4. Scroll down to reveal the hidden "Add to Home Screen" button:</p>
433 		<video src="ios_safari/scroll_down.mov" autoplay muted loop playsinline class="walkthrough-image"></video>
434 		<p>5. Tap the "Add to Home Screen" button:</p>
435 		<img src="ios_safari/add_to_home_screen.jpeg" class="walkthrough-image">
436 		<p>6. Tap "Add":</p>
437 		<img src="ios_safari/add.jpeg" class="walkthrough-image">
438 		<p>The app will be installed directly to your home screen.</p>
439 
440 	</div>
441 
442 	<div id="chrome-android" class="section">
443 		<h2>Chrome (Android)</h2>
444 		<p>Navigate to a website that supports PWA installation, then tap:</p>
445 		<p><span class="ui-element"><span class="vdots"><span></span></span></span> <span class="arrow"></span> Add to Home screen <span class="arrow"></span> Install.</p>
446 	</div>
447 
448 	<div id="firefox-android" class="section">
449 		<h2>Firefox (Android)</h2>
450 		<p>Navigate to a website that supports PWA installation, then tap:</p>
451 		<p><span class="ui-element"><span class="vdots"><span></span></span></span> <span class="arrow"></span> <span class="ui-element"><span class="dots"><span></span></span> <span style="position: relative; top: 1px;">More</span></span> <span class="arrow"></span> Add to Home screen <span class="arrow"></span> Add to Home screen.</p>
452 	</div>
453 
454 	<div id="safari-mac" class="section">
455 		<h2>Safari (macOS)</h2>
456 		<p>Navigate to a website that supports PWA installation, then click File <span class="arrow"></span> Add to Dock.</p>
457 		<p>The app will appear in your Dock and Applications folder, and will open in its own window without browser UI.</p>
458 		<!-- <p class="note">Note: Requires macOS Sonoma 14+. Older versions may not support this feature.</p> -->
459 	</div>
460 
461 	<div id="edge-windows" class="section">
462 		<h2>Microsoft Edge (Windows)</h2>
463 		<p>Navigate to a website that supports PWA installation, then click:</p>
464 		<p><span class="ui-element"><span class="dots"><span></span></span></span> <span class="arrow"></span> Apps <span class="arrow"></span> Install this site as an app.</p>
465 		<p>The app will appear in the Start menu.</p>
466 	</div>
467 
468 	<div id="chrome-desktop" class="section">
469 		<h2>Chrome (Desktop)</h2>
470 		<p>Navigate to a website that supports PWA installation, click the install icon (downward pointing arrow) in the address bar, then click Install.</p>
471 		<!-- <p class="note">Alternatively: <code>⋮</code> ➛ Cast, save, and share ➛ Install page as app</p> -->
472 		<p>The app will appear in your applications folder or Start menu.</p>
473 	</div>
474 
475 	<div id="firefox-windows" class="section">
476 		<h2>Firefox (Windows)</h2>
477 		<p class="note">Note: Requires Firefox version 143 or later (150 or later if Firefox was installed from the Microsoft Store).</p>
478 		<p>Navigate to a website that supports PWA installation, then click the <a href="https://support.mozilla.org/en-US/kb/web-apps-firefox-windows#w_install-web-apps" target="_blank">install button</a> in the address bar.</p>
479 		<p>The app will appear in your taskbar and Start menu, and will open in its own window.</p>
480 	</div>
481 
482 	<div id="firefox-desktop" class="section">
483 		<h2>Firefox (macOS / Linux)</h2>
484 		<p><strong>⚠️ The macOS and Linux versions of Firefox do not support web app installation.</strong><br>Try <a href="#chrome-desktop">Chrome</a> or <a href="#safari-mac">Safari</a> instead.</p>
485 		<p>Firefox for Windows added web app support in <a href="https://support.mozilla.org/en-US/kb/web-apps-firefox-windows" target="_blank">version 143</a> (September 2025), but support for macOS and Linux has not yet been announced.</p>
486 	</div>
487 
488 	<div id="try-these-pwas" class="section">
489 		<h2>Progressive Web Apps You Can Install Right Now</h2>
490 		
491 		<h3><a href="https://hunterirving.github.io/web_workshop/" target="_blank">Web Workshop</a></h3>
492 		<p class="note">Craft handmade websites in their natural habitat with this in-browser HTML editor. Works great on desktop or mobile.</p>
493 
494 		<h3><a href="https://hunterirving.com">HunterIrving.com</a></h3>
495 		<p class="note">Experience Huntergram, Gobbler, and more in the dedicated hunterirving.com PWA.</p>
496 
497 		<h3><a href="https://hunterirving.github.io/qr/">QR</a></h3>
498 		<p class="note">A no-nonsense QR code generator. Great for sharing links to PWAs you've developed with nearby friends.</p>
499 
500 		<h3><a href="https://github.com/hunterirving/mixapps">Mixapps</a></h3>
501 		<p class="note">Mixapps are like mixtapes or mix CDs, but packaged as PWAs that you can install for offline use. Use <a href="https://github.com/hunterirving/mixapps">this link</a> to learn how to create your own mixapps, or <a href="https://hunterirving.com/worn_grooves/">click here</a> to install one assembled from public domain recordings.</p>
502 
503 		<h3><a href="https://hunterirving.github.io/almanac">Almanac</a></h3>
504 		<p class="note">A companion app for the first Animal Crossing game that lets you add in-game events to your device's calendar.</p>
505 
506 		<!-- <h3><a href="https://hunterirving.github.io/klaxon/" target="_blank">Klaxon</a></h3>
507 		<p class="note">A chess clock in your pocket, optimized for touchscreen devices and competitive play.</p>		
508 		
509 		<h3><a href="https://hunterirving.github.io/hourglass/">Hourglass</a></h3>
510 		<p class="note">Speedrun your life with this simple, focused time tracking tool.</p>
511 	
512 		<h3><a href="https://hunterirving.github.io/pop_viz/">Pop Viz</a></h3>
513 		<p class="note">What does 100,000 people look like? Don't ask me — install this Progressive Web App.</p> -->
514 
515 		<!-- <h3><a href="https://hunterirving.github.io/web_workshop/pages/snake/">SNAKE</a></h3>
516 		<p class="note">The classic game, reimagined for the modern era.</p> -->
517 	</div>
518 
519 	<script>
520 		function detectPlatform() {
521 			const userAgent = navigator.userAgent.toLowerCase();
522 			const platform = navigator.platform.toLowerCase();
523 			
524 			let detectedOS = '';
525 			let detectedBrowser = '';
526 			let targetSection = '';
527 			
528 			// Detect OS
529 			if (userAgent.includes('android')) {
530 				detectedOS = 'Android';
531 			} else if (userAgent.includes('iphone') || userAgent.includes('ipad')) {
532 				detectedOS = 'iOS';
533 			} else if (platform.includes('win')) {
534 				detectedOS = 'Windows';
535 			} else if (platform.includes('mac')) {
536 				detectedOS = 'macOS';
537 			} else if (platform.includes('linux')) {
538 				detectedOS = 'Linux';
539 			}
540 			
541 			// Detect Browser - iOS only allows PWA installation through Safari
542 			if (detectedOS === 'iOS') {
543 				// On iOS, all browsers use Safari's WebKit engine
544 				// Only Safari can install PWAs, so always direct to Safari section
545 				detectedBrowser = 'Safari';
546 				targetSection = 'safari-ios';
547 			} else if (userAgent.includes('firefox')) {
548 				detectedBrowser = 'Firefox';
549 				if (detectedOS === 'Android') {
550 					targetSection = 'firefox-android';
551 				} else if (detectedOS === 'Windows') {
552 					targetSection = 'firefox-windows';
553 				} else {
554 					targetSection = 'firefox-desktop';
555 				}
556 			} else if (userAgent.includes('safari') && !userAgent.includes('chrome')) {
557 				detectedBrowser = 'Safari';
558 				targetSection = 'safari-mac';
559 			} else if (userAgent.includes('edg')) {
560 				detectedBrowser = 'Edge';
561 				targetSection = 'edge-windows';
562 			} else if (userAgent.includes('chrome')) {
563 				detectedBrowser = 'Chrome';
564 				if (detectedOS === 'Android') {
565 					targetSection = 'chrome-android';
566 				} else {
567 					targetSection = 'chrome-desktop';
568 				}
569 			}
570 			
571 			const isPhone = (detectedOS === 'Android' || detectedOS === 'iOS');
572 		if (isPhone) {
573 			document.getElementById('pwa-emoji').style.display = 'inline';
574 		} else {
575 			document.getElementById('pwa-infrared').style.display = 'block';
576 		}
577 
578 		if (targetSection) {
579 				const autoDetectedDiv = document.getElementById('auto-detected');
580 				const detectedLink = document.getElementById('detected-link');
581 
582 				autoDetectedDiv.href = `#${targetSection}`;
583 				detectedLink.textContent = `${detectedBrowser} on ${detectedOS}`;
584 				autoDetectedDiv.style.display = 'block';
585 			}
586 		}
587 		
588 		// Run detection when page loads
589 		window.addEventListener('load', detectPlatform);
590 	</script>
591 </body>
592 </html>