Perfect, Ray — let’s adapt this PWA class so anyone can build a Progressive Web App using only their cell phone, specifically targeting iPhone or Android users with basic tools like mobile browsers and code editors.
Class for any day next week
Great idea, Ray. Let’s create a 1-hour class for beginners that teaches how to make a simple PWA, step-by-step — hands-on, clear, and paced for learners.
Glasswork
#!/bin/bash
# Base directory for PWAs
BASE_DIR="$HOME/16_PWAs"
mkdir -p "$BASE_DIR"
for i in {1..16}
do
DIR="$BASE_DIR/$i"
mkdir -p "$DIR"
# Create index.html
cat > "$DIR/index.html" <<EOF
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>PWA #$i</title>
<link rel="manifest" href="manifest.json" />
</head>
<body>
<h1>PWA #$i</h1>
<p>This is PWA number $i.</p>
</body>
</html>
EOF
# Create manifest.json
cat > "$DIR/manifest.json" <<EOF
{
"name": "PWA #$i",
"short_name": "PWA$i",
"start_url": ".",
"display": "standalone",
"background_color": "#ffffff",
"description": "Progressive Web App number $i.",
"icons": []
}
EOF
# Create sw.js
cat > "$DIR/sw.js" <<EOF
self.addEventListener(‘install’, function(event) {
console.log(‘Service Worker installing PWA #$i’);
self.skipWaiting();
});
self.addEventListener(‘activate’, function(event) {
console.log(‘Service Worker activating PWA #$i’);
});
self.addEventListener(‘fetch’, function(event) {
event.respondWith(fetch(event.request));
});
EOF
done
# Create ZIP file
cd "$HOME"
zip -r 16_PWAs.zip 16_PWAs
echo "All PWAs created and zipped at $HOME/16_PWAs.zip"
Mahalo
SIGNATURE:
Clifford "RAY" Hackett I founded www.adapt.org in 1980 it now has over 50 million members.
$500 of material=World’s fastest hydrofoil sailboat. http://sunrun.biz
Bingo checker, V2
<!DOCTYPE html>
<html lang=”en”>
<head>
<meta charset=”UTF-8″ />
<meta name=”viewport” content=”width=device-width, initial-scale=1.0″/>
<title>Bingo Checker PWA</title>
<link rel=”manifest” href=”manifest.json”>
<style>
body { font-family: sans-serif; padding: 20px; background: #f0f0f0; }
input, select, button, textarea {
display: block; width: 100%; margin: 10px 0; padding: 10px; font-size: 16px;
}
#result { font-weight: bold; margin-top: 20px; }
</style>
</head>
<body>
<h1>Bingo Checker</h1>
<label for=”pattern”>Choose Bingo Pattern:</label>
<select id=”pattern”>
<option>Blackout</option>
<option>Four Corners</option>
<option>Horizontal Line</option>
<option>Vertical Line</option>
<option>Diagonal Line</option>
<option>X Pattern</option>
<option>T Pattern</option>
<option>L Pattern</option>
<option>Z Pattern</option>
<option>Diamond Pattern</option>
<option>Postage Stamp</option>
<option>Crazy Kite</option>
<option>Arrow Pattern</option>
<option>Plus Sign (+)</option>
<option>Small Picture Frame</option>
<option>Large Picture Frame</option>
<option>Outside Edge</option>
<option>Inside Square</option>
<option>Custom Pattern</option>
<option>Any Bingo</option>
</select>
<label for=”card”>Enter Bingo Card (5×5 grid, comma-separated rows):</label>
<textarea id=”card” rows=”5″ placeholder=”e.g. 1,16,31,46,61\n2,17,32,47,62…”></textarea>
<label for=”called”>Enter Called Numbers (comma-separated):</label>
<textarea id=”called” rows=”2″ placeholder=”e.g. 1,2,3,4,5,10,15,…”></textarea>
<button onclick=”checkBingo()”>Check for Bingo</button>
<div id=”result”></div>
<script>
function parseCard(text) {
return text.trim().split(‘\n’).map(row => row.split(‘,’).map(n => parseInt(n.trim())));
}
function isMarked(card, called, r, c) {
return called.includes(card[r][c]);
}
function checkPattern(card, called, pattern) {
const marked = (r, c) => isMarked(card, called, r, c);
switch (pattern) {
case ‘Blackout’:
return card.every((row, r) => row.every((_, c) => marked(r, c)));
case ‘Four Corners’:
return marked(0,0) && marked(0,4) && marked(4,0) && marked(4,4);
case ‘Horizontal Line’:
return card.some((_, r) => card[r].every((_, c) => marked(r, c)));
case ‘Vertical Line’:
return card[0].some((_, c) => card.every((_, r) => marked(r, c)));
case ‘Diagonal Line’:
return [0,1,2,3,4].every(i => marked(i,i)) || [0,1,2,3,4].every(i => marked(i,4-i));
case ‘X Pattern’:
return [0,1,2,3,4].every(i => marked(i,i) && marked(i,4-i));
case ‘T Pattern’:
return card[0].every((_, c) => marked(0,c)) && [0,1,2,3,4].every(r => marked(r,2));
case ‘L Pattern’:
return [0,1,2,3,4].every(r => marked(r,0)) && card[4].every((_, c) => marked(4,c));
case ‘Z Pattern’:
return [0,1,2,3,4].every(i => marked(i,4-i)) && (marked(0,0) && marked(0,1) && marked(4,3) && marked(4,4));
case ‘Diamond Pattern’:
return marked(0,2) && marked(1,1) && marked(1,3) && marked(2,0) && marked(2,4) && marked(3,1) && marked(3,3) && marked(4,2);
case ‘Postage Stamp’:
return marked(0,0) && marked(0,1) && marked(1,0) && marked(1,1);
case ‘Crazy Kite’:
return marked(0,4) && marked(1,3) && marked(2,2) && marked(3,1) && marked(4,0);
case ‘Arrow Pattern’:
return marked(0,2) && marked(1,2) && marked(2,2) && marked(2,1) && marked(2,3);
case ‘Plus Sign (+)’:
return [0,1,2,3,4].every(i => marked(i,2)) && card[2].every((_, c) => marked(2,c));
case ‘Small Picture Frame’:
return [0,1,3,4].every(r => card[r].every((_, c) => (c === 0 || c === 4) ? marked(r, c) : true));
case ‘Large Picture Frame’:
return card.map((row, r) => row.map((_, c) =>
(r === 0 || r === 4 || c === 0 || c === 4) ? marked(r, c) : true)).flat().every(v => v);
case ‘Outside Edge’:
return checkPattern(card, called, ‘Large Picture Frame’);
case ‘Inside Square’:
return [1,2,3].every(r => [1,2,3].every(c => marked(r,c)));
case ‘Any Bingo’:
return checkPattern(card, called, ‘Horizontal Line’) ||
checkPattern(card, called, ‘Vertical Line’) ||
checkPattern(card, called, ‘Diagonal Line’);
case ‘Custom Pattern’:
return false; // Placeholder
default:
return false;
}
}
function checkBingo() {
const pattern = document.getElementById(‘pattern’).value;
const cardText = document.getElementById(‘card’).value;
const calledText = document.getElementById(‘called’).value;
const called = calledText.split(‘,’).map(n => parseInt(n.trim()));
const card = parseCard(cardText);
const valid = checkPattern(card, called, pattern);
document.getElementById(‘result’).innerText = valid ? ‘✅ Bingo!’ : ‘❌ Not a Bingo Yet’;
}
// PWA
if (‘serviceWorker’ in navigator) {
navigator.serviceWorker.register(‘sw.js’);
}
</script>
<script type=”application/json” id=”manifest”>
{
“name”: “Bingo Checker”,
“short_name”: “Bingo”,
“start_url”: “.”,
“display”: “standalone”,
“background_color”: “#ffffff”,
“theme_color”: “#317EFB”,
“icons”: [{
“src”: “https://cdn-icons-png.flaticon.com/512/1048/1048940.png”,
“sizes”: “512×512”,
“type”: “image/png”
}]
}
</script>
<script id=”sw” type=”javascript/worker”>
self.addEventListener(‘install’, event => {
event.waitUntil(caches.open(‘bingo-v1’).then(cache => {
return cache.addAll([‘./’]);
}));
});
self.addEventListener(‘fetch’, event => {
event.respondWith(
caches.match(event.request).then(response => response || fetch(event.request))
);
});
</script>
</body>
</html>
One minute apps and sites image
Mahalo
SIGNATURE:
Clifford "RAY" Hackett I founded www.adapt.org in 1980 it now has over 50 million members.
$500 of material=World’s fastest hydrofoil sailboat. http://sunrun.biz

One minute apps and sites image
New bingo APP I built it this morning please approve
https://ray2407.github.io/250529bingo/
Mahalo
SIGNATURE:
Clifford "RAY" Hackett I founded www.adapt.org in 1980 it now has over 50 million members.
$500 of material=World’s fastest hydrofoil sailboat. http://sunrun.biz
On Fri, May 30, 2025 at 7:12 AM Clifford Hackett <3659745> wrote:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>Bingo Checker</title>
<link rel="manifest" href="manifest.json" />
<style>
body { font-family: sans-serif; text-align: center; background: #f0f0f0; }
table { border-collapse: collapse; margin: 10px auto; }
td { width: 60px; height: 60px; text-align: center; vertical-align: middle;
border: 1px solid #888; font-size: 20px; cursor: pointer; }
.marked { background-color: #4caf50; color: white; }
input, button { margin: 10px; padding: 10px; font-size: 16px; }
#status { font-size: 1.2em; margin-top: 10px; color: #d32f2f; }
</style>
</head>
<body>
<h1>Bingo Checker</h1>
<div>
<table id="bingoCard"></table>
<input type="text" id="calledInput" placeholder="Enter called number (e.g., B5)" />
<button onclick="markCalled()">Mark</button>
<div id="status"></div>
</div>
<script>
const bingoCard = [
["B1", "B2", "B3", "B4", "B5"],
["I16", "I17", "I18", "I19", "I20"],
["N31", "N32", "FREE", "N34", "N35"],
["G46", "G47", "G48", "G49", "G50"],
["O61", "O62", "O63", "O64", "O65"]
];
const marked = Array.from({ length: 5 }, () => Array(5).fill(false));
marked[2][2] = true; // Free space
function renderCard() {
const table = document.getElementById("bingoCard");
table.innerHTML = "";
for (let r = 0; r < 5; r++) {
const row = document.createElement("tr");
for (let c = 0; c < 5; c++) {
const cell = document.createElement("td");
cell.textContent = bingoCard[r][c];
if (marked[r][c]) cell.classList.add("marked");
row.appendChild(cell);
}
table.appendChild(row);
}
}
function markCalled() {
const input = document.getElementById("calledInput").value.toUpperCase().trim();
for (let r = 0; r < 5; r++) {
for (let c = 0; c < 5; c++) {
if (bingoCard[r][c] === input) {
marked[r][c] = true;
renderCard();
checkBingo();
return;
}
}
}
document.getElementById("status").textContent = "Number not found.";
}
function checkBingo() {
const status = document.getElementById("status");
// Rows
for (let r = 0; r < 5; r++) if (marked[r].every(Boolean)) return status.textContent = "🎉 BINGO!";
// Columns
for (let c = 0; c < 5; c++) if (marked.every(row => row[c])) return status.textContent = "🎉 BINGO!";
// Diagonals
if ([0,1,2,3,4].every(i => marked[i][i]) || [0,1,2,3,4].every(i => marked[i][4-i]))
return status.textContent = "🎉 BINGO!";
status.textContent = "";
}
window.onload = () => {
renderCard();
if (‘serviceWorker’ in navigator) navigator.serviceWorker.register(‘sw.js’);
};
</script>
<!– Manifest (inlined for PWA functionality) –>
<script type="application/json" id="manifest.json">
{
"name": "Bingo Checker",
"short_name": "BingoApp",
"start_url": ".",
"display": "standalone",
"background_color": "#ffffff",
"theme_color": "#4caf50",
"icons": [{
"src": "https://ray2407.github.io/250527b/icon.png",
"sizes": "192×192",
"type": "image/png"
}]
}
</script>
<!– Service Worker (inlined for offline support) –>
<script id="sw.js" type="javascript/worker">
const cacheName = "bingo-cache-v1";
self.addEventListener("install", e => {
e.waitUntil(
caches.open(cacheName).then(cache => cache.addAll([".", "index.html"]))
);
});
self.addEventListener("fetch", e => {
e.respondWith(
caches.match(e.request).then(res => res || fetch(e.request))
);
});
</script>
</body>
</html>
Mahalo
SIGNATURE:
Clifford "RAY" Hackett I founded www.adapt.org in 1980 it now has over 50 million members.
$500 of material=World’s fastest hydrofoil sailboat. http://sunrun.biz
New bingo APP I built it this morning please approve
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>Bingo Checker</title>
<link rel="manifest" href="manifest.json" />
<style>
body { font-family: sans-serif; text-align: center; background: #f0f0f0; }
table { border-collapse: collapse; margin: 10px auto; }
td { width: 60px; height: 60px; text-align: center; vertical-align: middle;
border: 1px solid #888; font-size: 20px; cursor: pointer; }
.marked { background-color: #4caf50; color: white; }
input, button { margin: 10px; padding: 10px; font-size: 16px; }
#status { font-size: 1.2em; margin-top: 10px; color: #d32f2f; }
</style>
</head>
<body>
<h1>Bingo Checker</h1>
<div>
<table id="bingoCard"></table>
<input type="text" id="calledInput" placeholder="Enter called number (e.g., B5)" />
<button onclick="markCalled()">Mark</button>
<div id="status"></div>
</div>
<script>
const bingoCard = [
["B1", "B2", "B3", "B4", "B5"],
["I16", "I17", "I18", "I19", "I20"],
["N31", "N32", "FREE", "N34", "N35"],
["G46", "G47", "G48", "G49", "G50"],
["O61", "O62", "O63", "O64", "O65"]
];
const marked = Array.from({ length: 5 }, () => Array(5).fill(false));
marked[2][2] = true; // Free space
function renderCard() {
const table = document.getElementById("bingoCard");
table.innerHTML = "";
for (let r = 0; r < 5; r++) {
const row = document.createElement("tr");
for (let c = 0; c < 5; c++) {
const cell = document.createElement("td");
cell.textContent = bingoCard[r][c];
if (marked[r][c]) cell.classList.add("marked");
row.appendChild(cell);
}
table.appendChild(row);
}
}
function markCalled() {
const input = document.getElementById("calledInput").value.toUpperCase().trim();
for (let r = 0; r < 5; r++) {
for (let c = 0; c < 5; c++) {
if (bingoCard[r][c] === input) {
marked[r][c] = true;
renderCard();
checkBingo();
return;
}
}
}
document.getElementById("status").textContent = "Number not found.";
}
function checkBingo() {
const status = document.getElementById("status");
// Rows
for (let r = 0; r < 5; r++) if (marked[r].every(Boolean)) return status.textContent = "🎉 BINGO!";
// Columns
for (let c = 0; c < 5; c++) if (marked.every(row => row[c])) return status.textContent = "🎉 BINGO!";
// Diagonals
if ([0,1,2,3,4].every(i => marked[i][i]) || [0,1,2,3,4].every(i => marked[i][4-i]))
return status.textContent = "🎉 BINGO!";
status.textContent = "";
}
window.onload = () => {
renderCard();
if (‘serviceWorker’ in navigator) navigator.serviceWorker.register(‘sw.js’);
};
</script>
<!– Manifest (inlined for PWA functionality) –>
<script type="application/json" id="manifest.json">
{
"name": "Bingo Checker",
"short_name": "BingoApp",
"start_url": ".",
"display": "standalone",
"background_color": "#ffffff",
"theme_color": "#4caf50",
"icons": [{
"src": "https://ray2407.github.io/250527b/icon.png",
"sizes": "192×192",
"type": "image/png"
}]
}
</script>
<!– Service Worker (inlined for offline support) –>
<script id="sw.js" type="javascript/worker">
const cacheName = "bingo-cache-v1";
self.addEventListener("install", e => {
e.waitUntil(
caches.open(cacheName).then(cache => cache.addAll([".", "index.html"]))
);
});
self.addEventListener("fetch", e => {
e.respondWith(
caches.match(e.request).then(res => res || fetch(e.request))
);
});
</script>
</body>
</html>
Mahalo
SIGNATURE:
Clifford "RAY" Hackett I founded www.adapt.org in 1980 it now has over 50 million members.
$500 of material=World’s fastest hydrofoil sailboat. http://sunrun.biz
Image for flyer approval requested
Mahalo
SIGNATURE:
Clifford "RAY" Hackett I founded www.adapt.org in 1980 it now has over 50 million members.
$500 of material=World’s fastest hydrofoil sailboat. http://sunrun.biz

