index.html (15.5 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 			width: 100%;
287 		}
288 		
289 		/* Dark mode styles */
290 		@media (prefers-color-scheme: dark) {
291 			body {
292 				background-color: #141414;
293 				color: #ffffff;
294 			}
295 
296 			h1, h2 {
297 				color: #ffffff;
298 				border-color: #666;
299 			}
300 
301 			.toc, .section {
302 				background-color: #1f1f22;
303 				border: none;
304 				box-shadow: 0 2px 8px rgba(0, 0, 0, 0.7);
305 			}
306 
307 			a.auto-detected,
308 			a.auto-detected:visited {
309 				background: linear-gradient(to top right, #1a1a3a 0%, #47496b 100%);
310 				text-shadow: 0px 1px 1px rgb(0, 0, 0);
311 				color: #ffffff;
312 			}
313 
314 			.pwa-icon {
315 				filter: drop-shadow(2px 2px 3px rgba(0, 0, 0, 0.5));
316 			}
317 
318 			a {
319 				color: #80d4ff;
320 			}
321 
322 			a:visited {
323 				color: #80d4ff;
324 			}
325 
326 			.auto-detected #detected-link {
327 				color: #80d4ff;
328 			}
329 
330 			.ui-element {
331 				background-color: #3d3d3d;
332 				border-color: #555;
333 				color: #e0e0e0;
334 			}
335 
336 			.arrow::before {
337 				background-color: #fff;
338 			}
339 
340 			.arrow::after {
341 				border-left-color: #fff;
342 			}
343 
344 			.dots::before,
345 			.dots::after,
346 			.dots span::before {
347 				background-color: #e0e0e0;
348 			}
349 
350 			.vdots::before,
351 			.vdots::after,
352 			.vdots span::before {
353 				background-color: #e0e0e0;
354 			}
355 
356 			.note {
357 				color: #aaa;
358 				border-left-color: #666;
359 			}
360 			
361 			footer {
362 				border-top-color: #666;
363 				color: #aaa;
364 			}
365 		}
366 		
367 		/* Mobile responsive styles */
368 		@media (max-width: 768px) {
369 			body {
370 				padding: 0px;
371 			}
372 			
373 			h1 {
374 				margin-top: 10px;
375 				margin-bottom: 20px;
376 				font-size: 1.8em;
377 			}
378 			
379 			.subtitle {
380 				display: none;
381 			}
382 			
383 			.section, .toc {
384 				margin: 20px 0;
385 				padding: 15px 10px;
386 				border-left: none;
387 				border-right: none;
388 			}
389 
390 			.section:first-of-type {
391 				margin-top: 0px;
392 				padding-top: .1px;
393 				padding-bottom: 6px;
394 			}
395 
396 			a.auto-detected {
397 				margin: 20px 0;
398 				padding: 12px 16px;
399 				border: none;
400 				border-radius: 0;
401 			}
402 	}
403 	</style>
404 </head>
405 <body>
406 	<div id="about-pwas" class="section">
407 		<span id="pwa-emoji" class="pwa-icon">📲</span>
408 		<img id="pwa-infrared" src="infrared.png" alt="">
409 		<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>
410 
411 		<p>You can use the instructions below to install PWAs directly from your web browser.</p>
412 	</div>
413 
414 	<a id="auto-detected" class="auto-detected" href="#">
415 		<p><span class="lightbulb">💡</span> Based on your system, you might want to jump directly to: <span id="detected-link"></span></p>
416 	</a>
417 
418 	<div id="safari-ios" class="section">
419 		<h2>Safari (iOS / iPadOS)</h2>
420 		<p class="note">Note: on iOS, Safari is the only browser that can install PWAs.</p>
421 		<p>Open Safari and navigate to a website that supports PWA installation, then tap:</p>
422 		<p>1. The three dots button in the bottom right corner of the screen:</p>
423 		<img src="ios_safari/dots.jpeg" class="walkthrough-image">
424 		<p>2. Share:</p>
425 		<img src="ios_safari/share.jpeg" class="walkthrough-image">
426 		<p>3. View More:</p>
427 		<img src="ios_safari/view_more.jpeg" class="walkthrough-image">
428 		<p>4. Scroll down to reveal the hidden "Add to Home Screen" button:</p>
429 		<video src="ios_safari/scroll_down.mov" autoplay muted loop playsinline class="walkthrough-image"></video>
430 		<p>5. Tap the "Add to Home Screen" button:</p>
431 		<img src="ios_safari/add_to_home_screen.jpeg" class="walkthrough-image">
432 		<p>6. Tap "Add":</p>
433 		<img src="ios_safari/add.jpeg" class="walkthrough-image">
434 		<p>The app will be installed directly to your home screen.</p>
435 
436 	</div>
437 
438 	<div id="chrome-android" class="section">
439 		<h2>Chrome (Android)</h2>
440 		<p>Navigate to a website that supports PWA installation, then tap:</p>
441 		<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>
442 	</div>
443 
444 	<div id="firefox-android" class="section">
445 		<h2>Firefox (Android)</h2>
446 		<p>Navigate to a website that supports PWA installation, then tap:</p>
447 		<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>
448 	</div>
449 
450 	<div id="safari-mac" class="section">
451 		<h2>Safari (macOS)</h2>
452 		<p>Navigate to a website that supports PWA installation, then click File <span class="arrow"></span> Add to Dock.</p>
453 		<p>The app will appear in your Dock and Applications folder, and will open in its own window without browser UI.</p>
454 		<!-- <p class="note">Note: Requires macOS Sonoma 14+. Older versions may not support this feature.</p> -->
455 	</div>
456 
457 	<div id="edge-windows" class="section">
458 		<h2>Microsoft Edge (Windows)</h2>
459 		<p>Navigate to a website that supports PWA installation, then click:</p>
460 		<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>
461 		<p>The app will appear in the Start menu.</p>
462 	</div>
463 
464 	<div id="chrome-desktop" class="section">
465 		<h2>Chrome (Desktop)</h2>
466 		<p>Navigate to a website that supports PWA installation, click the install icon (downward pointing arrow) in the address bar, then click Install.</p>
467 		<!-- <p class="note">Alternatively: <code>⋮</code> ➛ Cast, save, and share ➛ Install page as app</p> -->
468 		<p>The app will appear in your applications folder or Start menu.</p>
469 	</div>
470 
471 	<div id="firefox-windows" class="section">
472 		<h2>Firefox (Windows)</h2>
473 		<p class="note">Note: Requires Firefox version 143 or later (150 or later if Firefox was installed from the Microsoft Store).</p>
474 		<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>
475 		<p>The app will appear in your taskbar and Start menu, and will open in its own window.</p>
476 	</div>
477 
478 	<div id="firefox-desktop" class="section">
479 		<h2>Firefox (macOS / Linux)</h2>
480 		<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>
481 		<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>
482 	</div>
483 
484 	<div id="try-these-pwas" class="section">
485 		<h2>Progressive Web Apps You Can Install Right Now</h2>
486 		
487 		<h3><a href="https://hunterirving.github.io/web_workshop/" target="_blank">Web Workshop</a></h3>
488 		<p class="note">Craft handmade websites in their natural habitat with this in-browser HTML editor. Works great on desktop or mobile.</p>
489 
490 		<h3><a href="https://hunterirving.com">HunterIrving.com</a></h3>
491 		<p class="note">Experience Huntergram, Gobbler, and more in the dedicated hunterirving.com PWA.</p>
492 
493 		<h3><a href="https://hunterirving.github.io/qr/">QR</a></h3>
494 		<p class="note">A no-nonsense QR code generator. Great for sharing links to PWAs you've developed with nearby friends.</p>
495 
496 		<h3><a href="https://github.com/hunterirving/mixapps">Mixapps</a></h3>
497 		<p class="note">Mixapps are like mixtapes or mix CDs, but packaged as PWAs that you can share with friends and 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 listen to one assembled from public domain recordings.</p>
498 
499 		<!-- <h3><a href="https://hacksburg.org">Hacksburg.org</a></h3>
500 		<p class="note">Stay up to date with goings on at Hacksburg, a makerspace in Blacksburg, VA.</p> -->
501 
502 		<!-- <h3><a href="https://hunterirving.github.io/klaxon/" target="_blank">Klaxon</a></h3>
503 		<p class="note">A chess clock in your pocket, optimized for touchscreen devices and competitive play.</p>		
504 		
505 		<h3><a href="https://hunterirving.github.io/hourglass/">Hourglass</a></h3>
506 		<p class="note">Speedrun your life with this simple, focused time tracking tool.</p>
507 	
508 		<h3><a href="https://hunterirving.github.io/pop_viz/">Pop Viz</a></h3>
509 		<p class="note">What does 100,000 people look like? Don't ask me — install this Progressive Web App.</p> -->
510 
511 		<!-- <h3><a href="https://hunterirving.github.io/web_workshop/pages/snake/">SNAKE</a></h3>
512 		<p class="note">The classic game, reimagined for the modern era.</p> -->
513 	</div>
514 
515 	<script>
516 		function detectPlatform() {
517 			const userAgent = navigator.userAgent.toLowerCase();
518 			const platform = navigator.platform.toLowerCase();
519 			
520 			let detectedOS = '';
521 			let detectedBrowser = '';
522 			let targetSection = '';
523 			
524 			// Detect OS
525 			if (userAgent.includes('android')) {
526 				detectedOS = 'Android';
527 			} else if (userAgent.includes('iphone') || userAgent.includes('ipad')) {
528 				detectedOS = 'iOS';
529 			} else if (platform.includes('win')) {
530 				detectedOS = 'Windows';
531 			} else if (platform.includes('mac')) {
532 				detectedOS = 'macOS';
533 			} else if (platform.includes('linux')) {
534 				detectedOS = 'Linux';
535 			}
536 			
537 			// Detect Browser - iOS only allows PWA installation through Safari
538 			if (detectedOS === 'iOS') {
539 				// On iOS, all browsers use Safari's WebKit engine
540 				// Only Safari can install PWAs, so always direct to Safari section
541 				detectedBrowser = 'Safari';
542 				targetSection = 'safari-ios';
543 			} else if (userAgent.includes('firefox')) {
544 				detectedBrowser = 'Firefox';
545 				if (detectedOS === 'Android') {
546 					targetSection = 'firefox-android';
547 				} else if (detectedOS === 'Windows') {
548 					targetSection = 'firefox-windows';
549 				} else {
550 					targetSection = 'firefox-desktop';
551 				}
552 			} else if (userAgent.includes('safari') && !userAgent.includes('chrome')) {
553 				detectedBrowser = 'Safari';
554 				targetSection = 'safari-mac';
555 			} else if (userAgent.includes('edg')) {
556 				detectedBrowser = 'Edge';
557 				targetSection = 'edge-windows';
558 			} else if (userAgent.includes('chrome')) {
559 				detectedBrowser = 'Chrome';
560 				if (detectedOS === 'Android') {
561 					targetSection = 'chrome-android';
562 				} else {
563 					targetSection = 'chrome-desktop';
564 				}
565 			}
566 			
567 			const isPhone = (detectedOS === 'Android' || detectedOS === 'iOS');
568 		if (isPhone) {
569 			document.getElementById('pwa-emoji').style.display = 'inline';
570 		} else {
571 			document.getElementById('pwa-infrared').style.display = 'block';
572 		}
573 
574 		if (targetSection) {
575 				const autoDetectedDiv = document.getElementById('auto-detected');
576 				const detectedLink = document.getElementById('detected-link');
577 
578 				autoDetectedDiv.href = `#${targetSection}`;
579 				detectedLink.textContent = `${detectedBrowser} on ${detectedOS}`;
580 				autoDetectedDiv.style.display = 'block';
581 			}
582 		}
583 		
584 		// Run detection when page loads
585 		window.addEventListener('load', detectPlatform);
586 	</script>
587 </body>
588 </html>