Storybook — Alphabet Flashcard Set
A bright, tactile A-to-Z flashcard deck for early readers, built with rounded everything and a friendly storybook palette. Each card shows a giant letter, a bouncing emoji object, and the matching word; tap to flip it with a 3D card spin and read a fun fact on the back. Prev and next buttons hop through the deck, a shuffle remixes it, progress dots and an alphabet jump strip track where you are, and a Say it button speaks the letter aloud with an animated mouth. Includes an easy-read font toggle.
MCP
Code
:root {
--bg: #fff8ef;
--bg-2: #fff1e0;
--ink: #2c2350;
--ink-soft: #5b5378;
--primary: #ff8a3d;
--primary-deep: #f56b1d;
--secondary: #5ec5d6;
--accent: #ffd23f;
--pink: #ff6f9c;
--green: #7bd389;
--purple: #9b7ede;
--card-front: #ffffff;
--card-back: #fef0f6;
--border: #2c2350;
--r: 26px;
--r-sm: 16px;
--shadow: 0 14px 0 -4px rgba(44, 35, 80, 0.14), 0 24px 40px -18px rgba(44, 35, 80, 0.4);
--shadow-sm: 0 6px 0 -2px rgba(44, 35, 80, 0.16);
--font-display: "Baloo 2", system-ui, sans-serif;
--font-body: "Nunito", system-ui, -apple-system, sans-serif;
}
* {
box-sizing: border-box;
}
html {
-webkit-text-size-adjust: 100%;
}
body {
margin: 0;
min-height: 100vh;
font-family: var(--font-body);
line-height: 1.5;
color: var(--ink);
background:
radial-gradient(circle at 12% 8%, rgba(94, 197, 214, 0.22), transparent 38%),
radial-gradient(circle at 88% 12%, rgba(255, 111, 156, 0.2), transparent 40%),
radial-gradient(circle at 50% 100%, rgba(255, 210, 63, 0.24), transparent 46%),
var(--bg);
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
/* Dyslexia-friendly toggle */
body.easy-read {
--font-body: "Nunito", "Comic Sans MS", system-ui, sans-serif;
letter-spacing: 0.035em;
word-spacing: 0.12em;
line-height: 1.7;
}
.page {
max-width: 760px;
margin: 0 auto;
padding: clamp(18px, 4vw, 40px) clamp(14px, 4vw, 28px) 56px;
}
/* ---------- Masthead ---------- */
.masthead {
text-align: center;
}
.kicker {
display: inline-flex;
align-items: center;
gap: 8px;
margin: 0 0 10px;
padding: 7px 16px;
font-family: var(--font-display);
font-weight: 700;
font-size: 0.82rem;
letter-spacing: 0.04em;
text-transform: uppercase;
color: var(--primary-deep);
background: #fff;
border: 3px solid var(--border);
border-radius: 999px;
box-shadow: var(--shadow-sm);
}
.kicker-star {
color: var(--accent);
-webkit-text-stroke: 1px var(--border);
}
.title {
margin: 6px 0 12px;
font-family: var(--font-display);
font-weight: 800;
font-size: clamp(2.1rem, 8vw, 3.2rem);
line-height: 1.05;
letter-spacing: -0.01em;
}
.title span {
color: var(--primary);
display: inline-block;
text-shadow: 3px 3px 0 var(--accent);
}
.lede {
max-width: 46ch;
margin: 0 auto;
color: var(--ink-soft);
font-weight: 600;
font-size: clamp(0.98rem, 2.6vw, 1.08rem);
}
.lede strong {
color: var(--pink);
}
.lede em {
font-style: normal;
color: var(--secondary);
font-weight: 800;
}
/* ---------- Toolbar ---------- */
.toolbar {
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 10px;
margin-top: 20px;
}
.chip {
display: inline-flex;
align-items: center;
gap: 7px;
min-height: 48px;
padding: 0 18px;
font-family: var(--font-display);
font-weight: 700;
font-size: 0.95rem;
color: var(--ink);
background: #fff;
border: 3px solid var(--border);
border-radius: 999px;
box-shadow: var(--shadow-sm);
cursor: pointer;
transition: transform 0.12s ease, box-shadow 0.12s ease, background 0.18s ease;
}
.chip:hover {
background: var(--bg-2);
transform: translateY(-2px);
}
.chip:active {
transform: translateY(2px);
box-shadow: 0 2px 0 -1px rgba(44, 35, 80, 0.2);
}
.chip-toggle[aria-pressed="true"] {
background: var(--green);
color: var(--ink);
}
.chip:focus-visible {
outline: 3px solid var(--secondary);
outline-offset: 3px;
}
/* ---------- Stage ---------- */
.stage {
display: flex;
align-items: center;
justify-content: center;
gap: clamp(8px, 2.5vw, 22px);
margin: 32px 0 8px;
}
.nav-btn {
flex: 0 0 auto;
width: 60px;
height: 60px;
display: grid;
place-items: center;
font-size: 2.4rem;
line-height: 1;
font-weight: 800;
color: #fff;
background: var(--secondary);
border: 4px solid var(--border);
border-radius: 50%;
box-shadow: var(--shadow-sm);
cursor: pointer;
transition: transform 0.12s ease, background 0.18s ease;
}
.nav-btn span {
margin-top: -6px;
}
.nav-next {
background: var(--primary);
}
.nav-btn:hover {
transform: scale(1.08) rotate(-3deg);
}
.nav-next:hover {
transform: scale(1.08) rotate(3deg);
}
.nav-btn:active {
transform: scale(0.94);
}
.nav-btn:focus-visible {
outline: 4px solid var(--accent);
outline-offset: 3px;
}
/* ---------- Card ---------- */
.card-wrap {
flex: 1 1 auto;
max-width: 360px;
display: flex;
flex-direction: column;
align-items: center;
gap: 18px;
}
.flashcard {
position: relative;
width: 100%;
aspect-ratio: 3 / 4;
perspective: 1400px;
cursor: pointer;
border-radius: var(--r);
-webkit-tap-highlight-color: transparent;
}
.flashcard:focus-visible {
outline: 4px solid var(--purple);
outline-offset: 6px;
}
.card-face {
position: absolute;
inset: 0;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 6px;
padding: 22px 18px;
border: 5px solid var(--border);
border-radius: var(--r);
box-shadow: var(--shadow);
backface-visibility: hidden;
-webkit-backface-visibility: hidden;
transition: transform 0.55s cubic-bezier(0.5, 1.4, 0.4, 1);
overflow: hidden;
}
.card-front {
background:
radial-gradient(circle at 50% 18%, #fff, var(--card-front)),
var(--card-front);
transform: rotateY(0deg);
}
.card-back {
background:
radial-gradient(circle at 50% 16%, #fff, var(--card-back)),
var(--card-back);
transform: rotateY(180deg);
text-align: center;
}
.flashcard.flipped .card-front {
transform: rotateY(-180deg);
}
.flashcard.flipped .card-back {
transform: rotateY(0deg);
}
/* pop animation on flip */
.flashcard.popping {
animation: pop 0.45s ease;
}
@keyframes pop {
0% {
transform: scale(1);
}
45% {
transform: scale(1.05);
}
100% {
transform: scale(1);
}
}
.badge {
position: absolute;
top: 14px;
font-family: var(--font-display);
font-weight: 700;
font-size: 0.78rem;
padding: 4px 13px;
border: 3px solid var(--border);
border-radius: 999px;
}
.badge-letter {
right: 14px;
background: var(--accent);
color: var(--ink);
}
.badge-back {
right: 14px;
background: var(--pink);
color: #fff;
}
.big-letter {
font-family: var(--font-display);
font-weight: 800;
font-size: clamp(3.6rem, 22vw, 6rem);
line-height: 1;
color: var(--primary);
text-shadow: 4px 4px 0 var(--accent), 7px 7px 0 rgba(44, 35, 80, 0.12);
}
.object-art {
font-size: clamp(3rem, 18vw, 4.6rem);
line-height: 1;
margin-top: 4px;
filter: drop-shadow(0 6px 6px rgba(44, 35, 80, 0.2));
animation: bob 3.2s ease-in-out infinite;
}
@keyframes bob {
0%,
100% {
transform: translateY(0) rotate(-2deg);
}
50% {
transform: translateY(-7px) rotate(2deg);
}
}
.object-word {
margin: 6px 0 0;
font-family: var(--font-display);
font-weight: 700;
font-size: clamp(1.3rem, 6vw, 1.7rem);
color: var(--ink);
}
.flip-hint {
position: absolute;
bottom: 14px;
margin: 0;
font-weight: 700;
font-size: 0.8rem;
color: var(--ink-soft);
opacity: 0.85;
}
.flip-hint-back {
color: var(--ink-soft);
}
/* back face */
.back-emoji {
font-size: clamp(2.6rem, 16vw, 4rem);
line-height: 1;
margin-bottom: 2px;
filter: drop-shadow(0 5px 5px rgba(44, 35, 80, 0.18));
}
.back-word {
margin: 0 0 8px;
font-family: var(--font-display);
font-weight: 800;
font-size: clamp(1.5rem, 7vw, 2rem);
color: var(--pink);
}
.back-fact {
margin: 0;
max-width: 26ch;
font-weight: 600;
font-size: clamp(0.95rem, 4vw, 1.05rem);
color: var(--ink);
}
/* ---------- Say it button + mouth ---------- */
.say-row {
display: flex;
justify-content: center;
}
.say-btn {
display: inline-flex;
align-items: center;
gap: 12px;
min-height: 52px;
padding: 6px 24px 6px 12px;
font-family: var(--font-display);
font-weight: 800;
font-size: 1.15rem;
color: #fff;
background: var(--pink);
border: 4px solid var(--border);
border-radius: 999px;
box-shadow: var(--shadow-sm);
cursor: pointer;
transition: transform 0.12s ease, background 0.18s ease;
}
.say-btn:hover {
background: #ff5a8c;
transform: translateY(-2px);
}
.say-btn:active {
transform: translateY(2px);
}
.say-btn:focus-visible {
outline: 4px solid var(--accent);
outline-offset: 3px;
}
.mouth {
width: 38px;
height: 38px;
display: grid;
place-items: center;
background: #fff;
border: 3px solid var(--border);
border-radius: 50%;
overflow: hidden;
}
.mouth-inner {
display: block;
width: 18px;
height: 6px;
background: var(--ink);
border-radius: 0 0 12px 12px;
transition: height 0.1s ease, border-radius 0.1s ease;
}
.say-btn.talking .mouth-inner {
animation: talk 0.28s ease-in-out infinite alternate;
}
@keyframes talk {
0% {
height: 4px;
border-radius: 0 0 12px 12px;
}
100% {
height: 16px;
border-radius: 12px;
}
}
/* ---------- Progress dots ---------- */
.progress {
margin-top: 26px;
text-align: center;
}
.dots {
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 7px;
max-width: 480px;
margin: 0 auto;
}
.dot {
width: 13px;
height: 13px;
padding: 0;
border: 2px solid var(--border);
border-radius: 50%;
background: #fff;
cursor: pointer;
transition: transform 0.14s ease, background 0.18s ease;
}
.dot:hover {
transform: scale(1.35);
background: var(--secondary);
}
.dot.done {
background: var(--green);
}
.dot.current {
background: var(--primary);
transform: scale(1.5);
box-shadow: 0 0 0 3px rgba(255, 138, 61, 0.28);
}
.dot:focus-visible {
outline: 3px solid var(--purple);
outline-offset: 2px;
}
.progress-label {
margin: 14px 0 0;
font-family: var(--font-display);
font-weight: 700;
font-size: 0.95rem;
color: var(--ink-soft);
}
/* ---------- Alphabet jump strip ---------- */
.alpha-strip {
margin-top: 30px;
padding: 16px;
background: #fff;
border: 4px solid var(--border);
border-radius: var(--r);
box-shadow: var(--shadow-sm);
}
.alpha-grid {
display: grid;
grid-template-columns: repeat(13, 1fr);
gap: 8px;
}
.alpha-btn {
aspect-ratio: 1;
min-width: 0;
display: grid;
place-items: center;
font-family: var(--font-display);
font-weight: 700;
font-size: clamp(0.85rem, 2.4vw, 1.05rem);
color: var(--ink);
background: var(--bg-2);
border: 2px solid var(--border);
border-radius: 12px;
cursor: pointer;
transition: transform 0.12s ease, background 0.16s ease, color 0.16s ease;
}
.alpha-btn:hover {
transform: translateY(-3px) rotate(-4deg);
background: var(--accent);
}
.alpha-btn.current {
background: var(--primary);
color: #fff;
transform: translateY(-2px) scale(1.05);
}
.alpha-btn:focus-visible {
outline: 3px solid var(--purple);
outline-offset: 2px;
}
/* ---------- Toast ---------- */
.toast {
position: fixed;
left: 50%;
bottom: 26px;
transform: translate(-50%, 130%);
padding: 12px 22px;
font-family: var(--font-display);
font-weight: 700;
font-size: 1rem;
color: #fff;
background: var(--purple);
border: 3px solid var(--border);
border-radius: 999px;
box-shadow: var(--shadow-sm);
opacity: 0;
pointer-events: none;
transition: transform 0.32s cubic-bezier(0.5, 1.5, 0.4, 1), opacity 0.32s ease;
z-index: 50;
}
.toast.show {
transform: translate(-50%, 0);
opacity: 1;
}
/* ---------- Responsive ---------- */
@media (max-width: 540px) {
.nav-btn {
width: 50px;
height: 50px;
font-size: 2rem;
}
.alpha-grid {
grid-template-columns: repeat(7, 1fr);
}
}
@media (max-width: 380px) {
.stage {
flex-wrap: wrap;
}
.card-wrap {
order: -1;
width: 100%;
}
.nav-btn {
width: 46px;
height: 46px;
}
}
/* ---------- Reduced motion ---------- */
@media (prefers-reduced-motion: reduce) {
.card-face,
.object-art,
.say-btn.talking .mouth-inner,
.flashcard.popping {
animation: none !important;
transition: none !important;
}
.card-front {
transform: rotateY(0deg);
}
.flashcard.flipped .card-front {
transform: rotateY(-180deg);
}
}(function () {
"use strict";
/* ---------- The A–Z deck (fictional, friendly facts) ---------- */
var DECK = [
{ l: "A", word: "Apple", emoji: "🍎", fact: "Apples float because almost a quarter of an apple is air!" },
{ l: "B", word: "Bear", emoji: "🐻", fact: "A baby bear is called a cub and loves to tumble." },
{ l: "C", word: "Cat", emoji: "🐱", fact: "Cats say 'purr' when they feel happy and cozy." },
{ l: "D", word: "Dog", emoji: "🐶", fact: "Dogs can learn over a hundred words, just like you!" },
{ l: "E", word: "Egg", emoji: "🥚", fact: "An egg is a tiny home a baby bird grows inside." },
{ l: "F", word: "Fish", emoji: "🐠", fact: "Fish breathe underwater using gills, not a nose." },
{ l: "G", word: "Grapes", emoji: "🍇", fact: "Grapes grow in bunches on twisty climbing vines." },
{ l: "H", word: "Hat", emoji: "🎩", fact: "A hat keeps the sunshine off your nose on hot days." },
{ l: "I", word: "Igloo", emoji: "🛖", fact: "An igloo is a snug little house built out of snow blocks." },
{ l: "J", word: "Jelly", emoji: "🍮", fact: "Jelly wiggles and wobbles because it is mostly water." },
{ l: "K", word: "Kite", emoji: "🪁", fact: "A kite dances in the sky when the wind gives it a push." },
{ l: "L", word: "Lion", emoji: "🦁", fact: "A lion's roar is so loud it can be heard miles away!" },
{ l: "M", word: "Moon", emoji: "🌙", fact: "The Moon has no light of its own — it borrows the Sun's." },
{ l: "N", word: "Nest", emoji: "🪺", fact: "Birds build a soft nest to keep their eggs warm and safe." },
{ l: "O", word: "Owl", emoji: "🦉", fact: "An owl can turn its head almost all the way around." },
{ l: "P", word: "Panda", emoji: "🐼", fact: "A panda munches bamboo for many hours every single day." },
{ l: "Q", word: "Queen", emoji: "👑", fact: "A queen wears a sparkly crown on top of her head." },
{ l: "R", word: "Rocket", emoji: "🚀", fact: "A rocket pushes so hard it can zoom all the way to space." },
{ l: "S", word: "Sun", emoji: "☀️", fact: "The Sun is a giant star that keeps our whole planet warm." },
{ l: "T", word: "Tree", emoji: "🌳", fact: "Trees breathe out the fresh air that we breathe in." },
{ l: "U", word: "Umbrella", emoji: "☂️", fact: "An umbrella is a little roof you can carry in the rain." },
{ l: "V", word: "Violin", emoji: "🎻", fact: "A violin sings when you slide a bow across its strings." },
{ l: "W", word: "Whale", emoji: "🐳", fact: "A whale is the biggest animal that has ever lived!" },
{ l: "X", word: "Xylophone", emoji: "🎶", fact: "Tap a xylophone's bars to make a bright, bouncy tune." },
{ l: "Y", word: "Yo-yo", emoji: "🪀", fact: "A yo-yo rolls down its string and then climbs right back up." },
{ l: "Z", word: "Zebra", emoji: "🦓", fact: "Every zebra has stripes in a pattern all its own." }
];
/* ---------- State ---------- */
var order = DECK.map(function (_, i) { return i; }); // indexes into DECK, in display order
var pos = 0; // position within `order`
var visited = {};
/* ---------- Elements ---------- */
var card = document.getElementById("flashcard");
var frontLetter = document.getElementById("front-letter");
var frontArt = document.getElementById("front-art");
var frontWord = document.getElementById("front-word");
var badgePos = document.getElementById("badge-pos");
var backEmoji = document.getElementById("back-emoji");
var backWord = document.getElementById("back-word");
var backFact = document.getElementById("back-fact");
var prevBtn = document.getElementById("prev-btn");
var nextBtn = document.getElementById("next-btn");
var shuffleBtn = document.getElementById("shuffle-btn");
var resetBtn = document.getElementById("reset-btn");
var dyslexiaBtn = document.getElementById("dyslexia-btn");
var sayBtn = document.getElementById("say-btn");
var dotsWrap = document.getElementById("dots");
var progressLabel = document.getElementById("progress-label");
var alphaGrid = document.getElementById("alpha-grid");
var toastEl = document.getElementById("toast");
var reduceMotion = window.matchMedia
? window.matchMedia("(prefers-reduced-motion: reduce)").matches
: false;
/* ---------- Toast helper ---------- */
var toastTimer;
function toast(msg) {
toastEl.textContent = msg;
toastEl.classList.add("show");
clearTimeout(toastTimer);
toastTimer = setTimeout(function () {
toastEl.classList.remove("show");
}, 1800);
}
/* ---------- Build progress dots + alpha strip once ---------- */
function buildDots() {
dotsWrap.innerHTML = "";
DECK.forEach(function (entry, i) {
var b = document.createElement("button");
b.type = "button";
b.className = "dot";
b.setAttribute("aria-label", "Go to letter " + entry.l);
b.addEventListener("click", function () {
var p = order.indexOf(i);
goTo(p, true);
});
dotsWrap.appendChild(b);
});
}
function buildAlpha() {
alphaGrid.innerHTML = "";
DECK.forEach(function (entry, i) {
var b = document.createElement("button");
b.type = "button";
b.className = "alpha-btn";
b.textContent = entry.l;
b.setAttribute("aria-label", "Jump to letter " + entry.l + " for " + entry.word);
b.addEventListener("click", function () {
var p = order.indexOf(i);
goTo(p, true);
});
alphaGrid.appendChild(b);
});
}
function current() {
return DECK[order[pos]];
}
/* ---------- Render the current card ---------- */
function render(flipBack) {
var c = current();
var deckIndex = order[pos];
if (flipBack !== false) {
card.classList.remove("flipped");
card.setAttribute("aria-pressed", "false");
}
frontLetter.textContent = c.l + c.l.toLowerCase();
frontArt.textContent = c.emoji;
frontWord.textContent = c.word;
badgePos.textContent = (pos + 1) + " / " + DECK.length;
backEmoji.textContent = c.emoji;
backWord.textContent = c.word;
backFact.textContent = c.fact;
card.setAttribute(
"aria-label",
"Flashcard for letter " + c.l + ", " + c.word + ". Press Enter or Space to flip."
);
visited[deckIndex] = true;
// progress dots — dot i maps to DECK[i]
var dotEls = dotsWrap.children;
for (var i = 0; i < dotEls.length; i++) {
dotEls[i].classList.toggle("done", !!visited[i]);
dotEls[i].classList.toggle("current", i === deckIndex);
}
// alpha strip highlight
var alphaEls = alphaGrid.children;
for (var j = 0; j < alphaEls.length; j++) {
alphaEls[j].classList.toggle("current", j === deckIndex);
}
progressLabel.textContent =
"Letter " + c.l + " — card " + (pos + 1) + " of " + DECK.length;
// pop animation
if (!reduceMotion) {
card.classList.remove("popping");
void card.offsetWidth; // reflow to restart
card.classList.add("popping");
}
}
function goTo(newPos, announce) {
if (newPos < 0) newPos = DECK.length - 1;
if (newPos >= DECK.length) newPos = 0;
pos = newPos;
render(true);
if (announce) toast(current().l + " is for " + current().word + "!");
}
/* ---------- Flip ---------- */
function flip() {
var flipped = card.classList.toggle("flipped");
card.setAttribute("aria-pressed", flipped ? "true" : "false");
}
/* ---------- Shuffle (Fisher–Yates) ---------- */
function shuffle() {
var activeDeckIndex = order[pos];
for (var i = order.length - 1; i > 0; i--) {
var k = Math.floor(Math.random() * (i + 1));
var tmp = order[i];
order[i] = order[k];
order[k] = tmp;
}
pos = order.indexOf(activeDeckIndex); // keep the same card on screen
render(true);
toast("Cards shuffled! 🎉");
}
function resetOrder() {
var activeDeckIndex = order[pos];
order = DECK.map(function (_, i) { return i; });
pos = activeDeckIndex; // back to natural index
render(true);
toast("Back in A–Z order");
}
/* ---------- Say it (Web Speech + animated mouth fallback) ---------- */
var speakTimer;
function sayIt() {
var c = current();
var phrase = c.l + " is for " + c.word;
// animate the little mouth
sayBtn.classList.add("talking");
clearTimeout(speakTimer);
var hasSpeech =
"speechSynthesis" in window && typeof SpeechSynthesisUtterance !== "undefined";
if (hasSpeech && !reduceMotion) {
try {
window.speechSynthesis.cancel();
var u = new SpeechSynthesisUtterance(phrase);
u.rate = 0.85;
u.pitch = 1.25;
u.onend = stopTalking;
u.onerror = stopTalking;
window.speechSynthesis.speak(u);
// safety stop in case onend never fires
speakTimer = setTimeout(stopTalking, 3500);
} catch (e) {
speakTimer = setTimeout(stopTalking, 1300);
}
} else {
// no speech available — just animate the mouth for a beat
speakTimer = setTimeout(stopTalking, 1300);
}
toast("🔊 " + phrase);
}
function stopTalking() {
sayBtn.classList.remove("talking");
}
/* ---------- Dyslexia-friendly font toggle ---------- */
function toggleEasyRead() {
var on = document.body.classList.toggle("easy-read");
dyslexiaBtn.setAttribute("aria-pressed", on ? "true" : "false");
toast(on ? "Easy-read font on" : "Easy-read font off");
}
/* ---------- Wire up events ---------- */
card.addEventListener("click", flip);
card.addEventListener("keydown", function (e) {
if (e.key === "Enter" || e.key === " " || e.key === "Spacebar") {
e.preventDefault();
flip();
}
});
prevBtn.addEventListener("click", function () { goTo(pos - 1, false); });
nextBtn.addEventListener("click", function () { goTo(pos + 1, false); });
shuffleBtn.addEventListener("click", shuffle);
resetBtn.addEventListener("click", resetOrder);
dyslexiaBtn.addEventListener("click", toggleEasyRead);
sayBtn.addEventListener("click", sayIt);
// arrow keys move through the deck
document.addEventListener("keydown", function (e) {
if (e.target === card) return; // card handles its own keys
var tag = e.target && e.target.tagName;
if (tag === "INPUT" || tag === "TEXTAREA") return;
if (e.key === "ArrowLeft") {
goTo(pos - 1, false);
} else if (e.key === "ArrowRight") {
goTo(pos + 1, false);
}
});
/* ---------- Boot ---------- */
buildDots();
buildAlpha();
render(true);
})();<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Storybook — Alphabet Flashcard Set</title>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=Baloo+2:wght@500;600;700;800&family=Nunito:ital,wght@0,400;0,600;0,700;0,800;1,600&display=swap"
rel="stylesheet"
/>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<div class="page">
<header class="masthead">
<p class="kicker">
<span class="kicker-star" aria-hidden="true">★</span>
Little Readers — ABC Adventure
</p>
<h1 class="title">Alphabet <span>Flashcards</span></h1>
<p class="lede">
Tap the card to flip it, hop from <strong>A</strong> to
<strong>Z</strong>, shuffle the deck, and press <em>Say it!</em> to
hear every letter out loud.
</p>
<div class="toolbar" role="toolbar" aria-label="Card tools">
<button class="chip" type="button" id="shuffle-btn">
<span aria-hidden="true">🎲</span> Shuffle
</button>
<button class="chip" type="button" id="reset-btn">
<span aria-hidden="true">↩</span> A–Z order
</button>
<button
class="chip chip-toggle"
type="button"
id="dyslexia-btn"
aria-pressed="false"
>
<span aria-hidden="true">🔤</span> Easy-read font
</button>
</div>
</header>
<main class="stage" aria-labelledby="title">
<!-- Prev -->
<button class="nav-btn nav-prev" type="button" id="prev-btn" aria-label="Previous letter">
<span aria-hidden="true">‹</span>
</button>
<!-- The flashcard -->
<div class="card-wrap">
<div
class="flashcard"
id="flashcard"
role="button"
tabindex="0"
aria-pressed="false"
aria-label="Flashcard. Press Enter or Space to flip."
>
<div class="card-face card-front" id="card-front">
<span class="badge badge-letter" id="badge-pos">1 / 26</span>
<div class="big-letter" id="front-letter">Aa</div>
<div class="object-art" id="front-art" aria-hidden="true">🍎</div>
<p class="object-word"><span id="front-word">Apple</span></p>
<p class="flip-hint">
<span aria-hidden="true">👆</span> tap to flip
</p>
</div>
<div class="card-face card-back" id="card-back">
<span class="badge badge-back">Fun fact</span>
<div class="back-emoji" id="back-emoji" aria-hidden="true">🍎</div>
<p class="back-word"><span id="back-word">Apple</span></p>
<p class="back-fact" id="back-fact">
An apple a day was a real old saying about keeping healthy!
</p>
<p class="flip-hint flip-hint-back">
<span aria-hidden="true">👆</span> tap to flip back
</p>
</div>
</div>
<!-- Say it + mouth -->
<div class="say-row">
<button class="say-btn" type="button" id="say-btn">
<span class="mouth" id="mouth" aria-hidden="true">
<span class="mouth-inner"></span>
</span>
<span class="say-label">Say it!</span>
</button>
</div>
</div>
<!-- Next -->
<button class="nav-btn nav-next" type="button" id="next-btn" aria-label="Next letter">
<span aria-hidden="true">›</span>
</button>
</main>
<!-- Progress dots -->
<nav class="progress" aria-label="Alphabet progress">
<div class="dots" id="dots"></div>
<p class="progress-label" id="progress-label">Letter A of 26</p>
</nav>
<!-- Mini alphabet jump strip -->
<section class="alpha-strip" aria-label="Jump to a letter">
<div class="alpha-grid" id="alpha-grid"></div>
</section>
</div>
<div class="toast" id="toast" role="status" aria-live="polite"></div>
<script src="script.js"></script>
</body>
</html>Alphabet Flashcard Set
A full A–Z deck of flip flashcards for little readers, drawn entirely in CSS with a soft, sunny storybook palette and rounded everything. The front of each card carries a giant two-case letter (like Aa), a gently bobbing emoji object, and its word — A is for Apple. Tap or press the card and it flips with a springy 3D spin to reveal the word again plus a friendly, fictional fun fact on a pink reverse.
The round prev and next buttons hop one letter at a time, wrapping around from Z back to A. Shuffle remixes the whole deck with a Fisher–Yates pass while keeping your current card on screen, and A–Z order puts it back in line. A row of progress dots fills in green as you visit each letter and highlights the current one in orange, while a 26-button alphabet strip lets you jump straight to any letter. Arrow keys move through the deck and Enter or Space flips the focused card.
The Say it! button reads the letter and word aloud through the Web Speech API when it is available, with a little animated mouth that opens and closes either way, so the interaction always works. An easy-read font toggle relaxes the body type, letter spacing, and line height for friendlier reading, the layout collapses to a single stacked column down to ~360px, and motion-sensitive visitors get a calm, animation-free experience.
Illustrative kids’ UI only — fictional stories, characters, and audio.