learnings/index.html
2026-03-18 11:31:57 +00:00

225 lines
5.9 KiB
HTML
Executable File

<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Keyboard Learning Gamesd/title>
<style>
:root {
--bg: #0b57ff;
--fg: #ffffff;
--muted: rgba(255, 255, 255, 0.85);
--shadow: rgba(0, 0, 0, 0.25);
}
html,
body {
height: 100%;
margin: 0;
}
body {
background: var(--bg);
color: var(--fg);
font-family: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial, "Apple Color Emoji",
"Segoe UI Emoji";
display: flex;
align-items: center;
justify-content: center;
user-select: none;
}
.wrap {
width: min(1100px, 92vw);
text-align: center;
}
.sound {
position: fixed;
top: 18px;
right: 18px;
border: 0;
border-radius: 999px;
padding: 10px 14px;
background: rgba(255, 255, 255, 0.18);
color: var(--fg);
font-weight: 900;
letter-spacing: 0.02em;
box-shadow: 0 10px 0 rgba(0, 0, 0, 0.12);
cursor: pointer;
}
.sound:focus-visible {
outline: 4px solid rgba(255, 255, 255, 0.55);
outline-offset: 4px;
}
.big {
font-weight: 900;
letter-spacing: 0.03em;
font-size: clamp(72px, 18vw, 220px);
line-height: 1;
text-shadow: 0 12px 0 var(--shadow);
margin: 0;
}
.hint {
margin-top: 18px;
font-size: clamp(16px, 2.5vw, 26px);
color: var(--muted);
}
.kbd {
display: inline-block;
padding: 6px 12px;
border-radius: 12px;
background: rgba(255, 255, 255, 0.15);
box-shadow: 0 10px 0 rgba(0, 0, 0, 0.12);
font-weight: 800;
}
.pulse {
animation: pop 120ms ease-out;
}
@keyframes pop {
from {
transform: scale(0.94);
filter: brightness(0.95);
}
to {
transform: scale(1);
filter: brightness(1);
}
}
</style>
</head>
<body>
<button id="soundBtn" class="sound" type="button" aria-pressed="true">Sound: ON</button>
<main class="wrap" aria-live="polite" aria-atomic="true">
<h1 id="big" class="big">Press a key</h1>
<div class="hint">
Type any letter, number, or symbol on your keyboard (try <span class="kbd">A</span>, <span class="kbd">1</span>,
<span class="kbd">!</span>, <span class="kbd">@</span>).
</div>
</main>
<script>
const big = document.getElementById("big");
const soundBtn = document.getElementById("soundBtn");
let soundOn = true;
function setSound(on) {
soundOn = on;
soundBtn.textContent = `Sound: ${soundOn ? "ON" : "OFF"}`;
soundBtn.setAttribute("aria-pressed", String(soundOn));
}
setSound(true);
soundBtn.addEventListener("click", () => {
setSound(!soundOn);
});
function prettyKey(e) {
const k = e.key;
if (k === " ") return "SPACE";
if (k === "Enter") return "ENTER";
if (k === "Backspace") return "BACKSPACE";
if (k === "Tab") return "TAB";
if (k === "Escape") return "ESC";
if (k === "Shift" || k === "Control" || k === "Alt" || k === "Meta") return k.toUpperCase();
if (k.startsWith("Arrow")) return k.replace("Arrow", "ARROW ");
// Printable characters come through as length 1 (letters, numbers, symbols).
if (k.length === 1) return k.toUpperCase();
// Fallback for other keys like "Home", "End", "PageUp", etc.
return k.toUpperCase();
}
const speakMap = {
" ": "space",
Enter: "enter",
Backspace: "backspace",
Tab: "tab",
Escape: "escape",
"!": "exclamation mark",
"@": "at sign",
"#": "hash",
"$": "dollar sign",
"%": "percent",
"^": "caret",
"&": "ampersand",
"*": "asterisk",
"(": "left parenthesis",
")": "right parenthesis",
"-": "hyphen",
_: "underscore",
"+": "plus",
"=": "equals",
"[": "left bracket",
"]": "right bracket",
"{": "left brace",
"}": "right brace",
"\\": "backslash",
"/": "slash",
"|": "pipe",
":": "colon",
";": "semicolon",
"'": "apostrophe",
'"': "quote",
",": "comma",
".": "period",
"<": "less than",
">": "greater than",
"?": "question mark",
"`": "backtick",
"~": "tilde"
};
function speakForKey(e) {
if (!soundOn) return;
if (!("speechSynthesis" in window)) return;
const raw = e.key;
let phrase = speakMap[raw];
if (!phrase) {
if (raw.startsWith("Arrow")) phrase = raw.replace("Arrow", "arrow ").toLowerCase();
else if (raw.length === 1) phrase = raw.toUpperCase(); // letters/numbers
else phrase = raw.toLowerCase();
}
try {
window.speechSynthesis.cancel();
const u = new SpeechSynthesisUtterance(phrase);
u.rate = 0.9;
u.pitch = 1.0;
u.volume = 1.0;
window.speechSynthesis.speak(u);
} catch {
// ignore
}
}
function show(text) {
big.textContent = text;
big.classList.remove("pulse");
// Force reflow so animation restarts reliably
void big.offsetWidth;
big.classList.add("pulse");
}
window.addEventListener("keydown", (e) => {
// Prevent browser shortcuts from stealing focus in some cases
// (Still allows normal typing, and this is a full-screen toy app.)
e.preventDefault();
show(prettyKey(e));
speakForKey(e);
});
</script>
</body>
</html>