套件原始碼 · Storybook Sequence Library
CC BY 4.0 · 請標明 @joe · v1
AI 編輯提示起頭
Edit this classroom game for interactive whiteboards. Keep large touch targets and whole-class play. Tracking, logins, personal-data collection, and remote API calls are prohibited. Use the downloaded complete ZIP—not only the entry HTML shown below—and preserve its browser-ready folder structure. Preserve the educational goal. Original creator: @joe on ClassBoard Games (CC BY 4.0).
僅入口 HTML · index.html · 3518.4 KB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" />
<title>Storybook Sequence Library — FIRST, NEXT, THEN, LAST</title>
<style>
:root {
--app-scale: 1;
--wood-dark: #3B1D0A;
--wood-mid: #6D3714;
--wood-light: #9A5220;
--parchment: #FFFDF5;
--parchment-warm: #FEF7E0;
--c-first: #E11D48;
--c-next: #D97706;
--c-then: #2563EB;
--c-last: #059669;
}
* {
box-sizing: border-box;
user-select: none;
-webkit-user-select: none;
-webkit-touch-callout: none;
}
html, body {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
overflow: hidden;
font-family: "Nunito", "Fredoka", "Comic Sans MS", "Trebuchet MS", system-ui, -apple-system, sans-serif;
background: radial-gradient(circle at 50% 20%, #78350F 0%, #3B1D0A 55%, #1C0D04 100%);
color: #1E293B;
}
.viewport-shell {
width: 100vw;
height: 100vh;
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
position: relative;
}
.stage-1280 {
width: 1280px;
height: 720px;
transform: scale(var(--app-scale));
transform-origin: center center;
background: linear-gradient(180deg, #5C2D12 0%, #451A03 100%);
border-radius: 30px;
border: 7px solid #D97706;
box-shadow: 0 26px 70px rgba(0, 0, 0, 0.7), inset 0 2px 0 rgba(254, 243, 199, 0.55);
display: flex;
flex-direction: column;
padding: 11px 15px;
gap: 8px;
position: relative;
overflow: hidden;
}
/* Warm Library Wallpaper & Bookshelf Texture */
.library-backdrop {
position: absolute;
inset: 0;
pointer-events: none;
background-image:
radial-gradient(circle at 18% 12%, rgba(251, 191, 36, 0.16) 0%, transparent 36%),
radial-gradient(circle at 82% 12%, rgba(251, 191, 36, 0.16) 0%, transparent 36%),
repeating-linear-gradient(90deg, rgba(255,255,255,0.03) 0px, rgba(255,255,255,0.03) 28px, transparent 28px, transparent 56px);
z-index: 0;
}
/* ============ 1. TOP LIBRARY HEADER BAR ============ */
.top-bar {
position: relative;
z-index: 10;
height: 58px;
background: linear-gradient(90deg, #78350F 0%, #92400E 50%, #78350F 100%);
border-radius: 18px;
border: 3px solid #FBBF24;
box-shadow: 0 5px 0 #291003;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 14px;
color: #FFFBEB;
flex-shrink: 0;
}
.brand-cluster {
display: flex;
align-items: center;
gap: 10px;
}
.brand-logo {
width: 43px;
height: 43px;
border-radius: 13px;
background: linear-gradient(135deg, #FDE047, #D97706);
border: 3px solid #FFFBEB;
display: flex;
align-items: center;
justify-content: center;
font-size: 24px;
box-shadow: 0 3px 0 #451A03;
}
.brand-title {
font-size: 21px;
font-weight: 900;
letter-spacing: 0.3px;
line-height: 1.05;
text-shadow: 0 2px 4px rgba(0,0,0,0.45);
}
.brand-sub {
font-size: 11px;
font-weight: 800;
color: #FDE68A;
}
.mode-tabs {
display: flex;
align-items: center;
gap: 6px;
background: rgba(28, 13, 4, 0.45);
padding: 4px;
border-radius: 13px;
border: 2px solid rgba(253, 224, 71, 0.3);
}
.mode-tab {
border: none;
background: transparent;
color: #FEF3C7;
font-family: inherit;
font-size: 12.5px;
font-weight: 900;
padding: 6px 12px;
border-radius: 9px;
cursor: pointer;
display: flex;
align-items: center;
gap: 5px;
transition: all 0.14s;
}
.mode-tab:hover { background: rgba(255,255,255,0.12); }
.mode-tab.active {
background: linear-gradient(180deg, #FDE047, #F59E0B);
color: #451A03;
box-shadow: 0 3px 0 #92400E;
}
.top-right {
display: flex;
align-items: center;
gap: 6px;
}
.star-pill {
background: rgba(28, 13, 4, 0.45);
border: 2px solid #FDE047;
border-radius: 999px;
padding: 4px 11px;
font-size: 13.5px;
font-weight: 900;
color: #FEF08A;
display: flex;
align-items: center;
gap: 4px;
}
.util-btn {
border: 2px solid rgba(254, 243, 199, 0.55);
background: rgba(255, 255, 255, 0.14);
color: #FFFBEB;
font-family: inherit;
font-size: 12px;
font-weight: 900;
padding: 6px 10px;
border-radius: 10px;
cursor: pointer;
display: flex;
align-items: center;
gap: 4px;
}
.util-btn:hover { background: rgba(255, 255, 255, 0.25); }
/* ============ 2. SEQUENCE WORDS RIBBON & 10-BOOK SHELF BAR ============ */
.library-shelf-bar {
position: relative;
z-index: 10;
height: 60px;
background: linear-gradient(180deg, #FFFBEB 0%, #FEF3C7 100%);
border-radius: 18px;
border: 3px solid #F59E0B;
box-shadow: 0 5px 0 rgba(0, 0, 0, 0.28);
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 10px;
gap: 8px;
flex-shrink: 0;
}
.seq-rule-pills {
display: flex;
align-items: center;
gap: 6px;
}
.seq-rule-btn {
border: 2.5px solid;
border-radius: 12px;
padding: 4px 9px;
cursor: pointer;
font-family: inherit;
display: flex;
align-items: center;
gap: 6px;
transition: transform 0.12s;
}
.seq-rule-btn:hover { transform: translateY(-2px); }
.seq-rule-btn.first { background: #FFF1F2; border-color: #FB7185; color: #9F1239; }
.seq-rule-btn.next { background: #FFFBEB; border-color: #FBBF24; color: #92400E; }
.seq-rule-btn.then { background: #EFF6FF; border-color: #60A5FA; color: #1E40AF; }
.seq-rule-btn.last { background: #ECFDF5; border-color: #34D399; color: #065F46; }
.srb-tag {
font-size: 13px;
font-weight: 900;
padding: 2px 7px;
border-radius: 7px;
color: #fff;
}
.seq-rule-btn.first .srb-tag { background: var(--c-first); }
.seq-rule-btn.next .srb-tag { background: var(--c-next); }
.seq-rule-btn.then .srb-tag { background: var(--c-then); }
.seq-rule-btn.last .srb-tag { background: var(--c-last); }
.srb-lbl {
font-size: 11px;
font-weight: 900;
line-height: 1.1;
text-align: left;
}
.srb-lbl small {
display: block;
font-size: 9.5px;
opacity: 0.85;
}
/* 10-Book Carousel Picker */
.book-spines-row {
display: flex;
align-items: center;
gap: 4px;
overflow-x: auto;
padding: 2px;
}
.book-spine-btn {
border: 2px solid #D97706;
background: #FFFFFF;
color: #78350F;
font-family: inherit;
font-size: 11.5px;
font-weight: 900;
padding: 5px 8px;
border-radius: 10px;
cursor: pointer;
display: flex;
align-items: center;
gap: 4px;
white-space: nowrap;
transition: all 0.14s;
}
.book-spine-btn:hover { background: #FEF3C7; transform: translateY(-1px); }
.book-spine-btn.active {
background: linear-gradient(180deg, #D97706, #92400E);
border-color: #451A03;
color: #FFFBEB;
box-shadow: 0 3px 0 #451A03;
}
/* ============ 3. MAIN SPLIT: LEFT LIBRARY READING DESK + RIGHT 3-TEAM BOOK STACK ============ */
.main-split {
position: relative;
z-index: 10;
flex: 1;
min-height: 0;
display: grid;
grid-template-columns: 1fr 310px;
gap: 11px;
}
.reading-desk {
background: linear-gradient(180deg, #FFFDF5 0%, #FEF7E0 100%);
border-radius: 24px;
border: 5px solid #B45309;
box-shadow: 0 12px 28px rgba(0, 0, 0, 0.35), inset 0 2px 0 #FFFFFF;
padding: 10px 14px;
display: flex;
flex-direction: column;
justify-content: space-between;
position: relative;
overflow: hidden;
}
.desk-header {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
padding-bottom: 6px;
border-bottom: 2.5px dashed #FCD34D;
flex-shrink: 0;
}
.book-banner-title {
display: flex;
align-items: center;
gap: 8px;
font-size: 18px;
font-weight: 900;
color: #451A03;
}
.desk-actions {
display: flex;
align-items: center;
gap: 6px;
}
.lib-btn {
border: 2.5px solid #D97706;
background: linear-gradient(180deg, #FEF08A, #FBBF24);
color: #451A03;
font-family: inherit;
font-size: 12px;
font-weight: 900;
padding: 5px 11px;
border-radius: 11px;
box-shadow: 0 3px 0 #92400E;
cursor: pointer;
display: inline-flex;
align-items: center;
gap: 5px;
}
.lib-btn:active { transform: translateY(2px); box-shadow: 0 1px 0 #92400E; }
.lib-btn.green {
border-color: #059669;
background: linear-gradient(180deg, #A7F3D0, #34D399);
color: #064E3B;
box-shadow: 0 3px 0 #065F46;
}
.lib-btn.purple {
border-color: #6D28D9;
background: linear-gradient(180deg, #DDD6FE, #A78BFA);
color: #2E1065;
box-shadow: 0 3px 0 #4C1D95;
}
/* SHUFFLED BOOKMARK TRAY (Mode 1 & Mode 2) */
.bookmark-tray {
background: linear-gradient(90deg, #78350F 0%, #92400E 50%, #78350F 100%);
border: 3px solid #F59E0B;
border-radius: 18px;
padding: 8px 12px;
margin: 6px 0;
display: flex;
align-items: center;
justify-content: space-between;
gap: 10px;
flex-shrink: 0;
box-shadow: inset 0 3px 8px rgba(0,0,0,0.35);
}
.tray-instruction {
font-size: 12.5px;
font-weight: 900;
color: #FEF3C7;
max-width: 195px;
line-height: 1.25;
}
.draggable-bookmarks {
flex: 1;
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 10px;
}
.seq-bookmark-card {
height: 54px;
border-radius: 14px;
border: 3.5px solid #FFFBEB;
font-family: inherit;
cursor: grab;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
color: #FFFFFF;
box-shadow: 0 5px 0 rgba(0,0,0,0.35);
transition: transform 0.14s cubic-bezier(0.34, 1.56, 0.64, 1), opacity 0.15s, box-shadow 0.14s;
position: relative;
}
.seq-bookmark-card:hover:not(.placed) {
transform: translateY(-3px) scale(1.03);
}
.seq-bookmark-card.selected {
box-shadow: 0 0 0 4px #FDE047, 0 6px 0 rgba(0,0,0,0.4);
transform: translateY(-3px) scale(1.05);
}
.seq-bookmark-card.placed {
opacity: 0.28;
cursor: default;
transform: scale(0.94);
box-shadow: none;
}
.seq-bookmark-card.shake {
animation: shakeErr 0.4s ease;
}
@keyframes shakeErr {
0%, 100% { transform: translateX(0); }
20% { transform: translateX(-8px); }
40% { transform: translateX(8px); }
60% { transform: translateX(-5px); }
80% { transform: translateX(5px); }
}
.seq-bookmark-card.first { background: linear-gradient(180deg, #F43F5E, #BE123C); }
.seq-bookmark-card.next { background: linear-gradient(180deg, #F59E0B, #B45309); }
.seq-bookmark-card.then { background: linear-gradient(180deg, #3B82F6, #1D4ED8); }
.seq-bookmark-card.last { background: linear-gradient(180deg, #10B981, #047857); }
.sbc-word {
font-size: 20px;
font-weight: 900;
letter-spacing: 0.8px;
line-height: 1;
}
.sbc-sub {
font-size: 10px;
font-weight: 800;
opacity: 0.92;
margin-top: 2px;
}
/* 4 STORYBOOK PAGE SLOTS GRID */
.four-pages-grid {
flex: 1;
min-height: 0;
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 10px;
}
.story-page-slot {
background: #FFFFFF;
border: 3.5px dashed #CBD5E1;
border-radius: 20px;
padding: 8px 9px;
display: flex;
flex-direction: column;
justify-content: space-between;
position: relative;
transition: all 0.2s ease;
cursor: pointer;
overflow: hidden;
}
.story-page-slot:hover {
border-color: #F59E0B;
background: #FFFDF7;
}
.story-page-slot.filled {
border-style: solid;
background: linear-gradient(180deg, #FFFFFF 0%, #FFFBEB 100%);
box-shadow: 0 8px 18px rgba(120, 53, 15, 0.14);
}
.story-page-slot.filled.first { border-color: var(--c-first); }
.story-page-slot.filled.next { border-color: var(--c-next); }
.story-page-slot.filled.then { border-color: var(--c-then); }
.story-page-slot.filled.last { border-color: var(--c-last); }
.sps-step-header {
display: flex;
align-items: center;
justify-content: space-between;
font-size: 11.5px;
font-weight: 900;
color: #64748B;
}
/* Target Drop Slot for Sequence Word Bookmark */
.sps-word-dropzone {
height: 44px;
border-radius: 12px;
border: 2.5px dashed #94A3B8;
background: #F1F5F9;
display: flex;
align-items: center;
justify-content: center;
font-size: 13px;
font-weight: 900;
color: #64748B;
margin: 4px 0;
transition: all 0.2s;
}
.sps-word-dropzone.locked {
border-style: solid;
border-color: #FFFFFF;
color: #FFFFFF;
font-size: 19px;
letter-spacing: 0.8px;
box-shadow: 0 4px 0 rgba(0,0,0,0.2);
}
.sps-word-dropzone.locked.first { background: linear-gradient(180deg, #F43F5E, #BE123C); }
.sps-word-dropzone.locked.next { background: linear-gradient(180deg, #F59E0B, #B45309); }
.sps-word-dropzone.locked.then { background: linear-gradient(180deg, #3B82F6, #1D4ED8); }
.sps-word-dropzone.locked.last { background: linear-gradient(180deg, #10B981, #047857); }
/* Illustrated Diorama Plaque inside each Story Page */
.sps-illustration-box {
height: 112px;
border-radius: 14px;
border: 2.5px solid #E2E8F0;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
position: relative;
overflow: hidden;
background: linear-gradient(180deg, #E0F2FE 0%, #FEF9C3 100%);
}
.sps-emoji-stage {
font-size: 46px;
line-height: 1;
filter: drop-shadow(0 4px 6px rgba(0,0,0,0.16));
z-index: 2;
}
.sps-scene-caption {
font-size: 10.5px;
font-weight: 900;
background: rgba(255,255,255,0.86);
padding: 2px 8px;
border-radius: 999px;
color: #334155;
margin-top: 4px;
z-index: 2;
}
.sps-story-text {
font-size: 12.5px;
font-weight: 800;
color: #1E293B;
line-height: 1.32;
text-align: left;
flex: 1;
display: flex;
align-items: center;
padding: 4px 2px;
}
.sps-story-text b.seq-lead {
padding: 1px 6px;
border-radius: 6px;
color: #fff;
margin-right: 4px;
}
.sps-story-text b.seq-lead.first { background: var(--c-first); }
.sps-story-text b.seq-lead.next { background: var(--c-next); }
.sps-story-text b.seq-lead.then { background: var(--c-then); }
.sps-story-text b.seq-lead.last { background: var(--c-last); }
.sps-footer-hint {
font-size: 10.5px;
font-weight: 900;
text-align: center;
padding: 4px 6px;
border-radius: 8px;
background: #FEF3C7;
color: #92400E;
border: 1.5px solid #FCD34D;
}
/* ============ MODE 3: SEQUENCE WORD DETECTIVE ============ */
.detective-card {
flex: 1;
background: #FFFFFF;
border: 4px solid #F59E0B;
border-radius: 22px;
padding: 14px 20px;
margin: 6px 0;
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-between;
box-shadow: 0 8px 24px rgba(120, 53, 15, 0.14);
}
.timeline-stepper {
display: flex;
align-items: center;
gap: 10px;
}
.tl-dot {
padding: 5px 14px;
border-radius: 999px;
font-size: 13px;
font-weight: 900;
background: #F1F5F9;
color: #64748B;
border: 2px solid #CBD5E1;
}
.tl-dot.current {
background: #FEF08A;
color: #78350F;
border-color: #D97706;
transform: scale(1.08);
box-shadow: 0 4px 0 #B45309;
}
/* ============ 4. RIGHT SIDE: 3-TEAM BOOKWORM STACK RACE ============ */
.bookworm-race {
background: linear-gradient(180deg, #78350F 0%, #451A03 100%);
border-radius: 24px;
border: 5px solid #D97706;
box-shadow: 0 12px 28px rgba(0, 0, 0, 0.4);
padding: 10px 11px;
display: flex;
flex-direction: column;
justify-content: space-between;
color: #FFFBEB;
position: relative;
overflow: hidden;
}
.br-head {
display: flex;
align-items: center;
justify-content: space-between;
background: rgba(255, 255, 255, 0.12);
padding: 6px 10px;
border-radius: 14px;
border: 2px solid rgba(253, 224, 71, 0.35);
}
.br-title {
font-size: 14px;
font-weight: 900;
color: #FDE047;
display: flex;
align-items: center;
gap: 5px;
}
.book-stacks-grid {
flex: 1;
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 7px;
padding: 8px 0 4px;
min-height: 0;
}
.team-pedestal-col {
background: rgba(255, 255, 255, 0.09);
border: 2px solid rgba(255, 255, 255, 0.22);
border-radius: 15px;
padding: 5px 4px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-between;
cursor: pointer;
transition: all 0.15s;
}
.team-pedestal-col.active-turn {
background: rgba(254, 243, 199, 0.22);
border-color: #FDE047;
box-shadow: 0 0 0 3px rgba(253, 224, 71, 0.35);
}
.tp-badge {
width: 100%;
background: #FFFBEB;
color: #1E293B;
border-radius: 10px;
padding: 3px 4px;
text-align: center;
border: 2px solid;
}
.stack-column {
flex: 1;
width: 100%;
display: flex;
flex-direction: column-reverse;
align-items: center;
justify-content: flex-start;
padding: 4px 0;
overflow: hidden;
}
.oak-shelf-base {
width: 88%;
height: 9px;
background: linear-gradient(90deg, #B45309, #F59E0B, #B45309);
border: 2px solid #451A03;
border-radius: 4px;
flex-shrink: 0;
}
.stacked-book {
width: 78%;
height: 18px;
border-radius: 4px 7px 7px 4px;
border: 2px solid #FFFBEB;
margin-bottom: 1px;
display: flex;
align-items: center;
justify-content: center;
font-size: 10px;
font-weight: 900;
color: #fff;
box-shadow: 0 2px 4px rgba(0,0,0,0.3);
animation: dropBook 0.25s cubic-bezier(0.34, 1.56, 0.64, 1);
}
@keyframes dropBook {
0% { transform: translateY(-20px); opacity: 0; }
100% { transform: translateY(0); opacity: 1; }
}
/* ============ 5. ENCHANTED POP-UP STORYBOOK DETAIL VIEWER MODAL ============ */
.modal-overlay {
position: absolute;
inset: 0;
background: rgba(28, 13, 4, 0.82);
backdrop-filter: blur(6px);
z-index: 80;
display: none;
align-items: center;
justify-content: center;
padding: 20px;
}
.modal-overlay.open { display: flex; }
.popup-storybook {
width: 920px;
height: 510px;
background: linear-gradient(90deg, #FEF9E7 0%, #FFFDF7 48%, #E2D5B5 50%, #FFFDF7 52%, #FEF9E7 100%);
border: 8px solid #92400E;
border-radius: 28px;
box-shadow: 0 28px 70px rgba(0,0,0,0.65), inset 0 0 0 4px #F59E0B;
display: grid;
grid-template-columns: 1fr 1fr;
padding: 24px 28px;
gap: 28px;
position: relative;
}
.ps-left-page {
display: flex;
flex-direction: column;
justify-content: space-between;
padding-right: 8px;
}
.ps-right-page {
background: #FFFFFF;
border: 5px solid #F59E0B;
border-radius: 22px;
overflow: hidden;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
position: relative;
box-shadow: inset 0 4px 16px rgba(0,0,0,0.08);
}
/* Confetti Layer */
.confetti-layer {
position: absolute;
inset: 0;
pointer-events: none;
z-index: 90;
overflow: hidden;
}
.confetti-dot {
position: absolute;
width: 12px;
height: 12px;
border-radius: 50%;
animation: burstOut 0.9s cubic-bezier(0.16, 1, 0.3, 1) forwards;
}
@keyframes burstOut {
0% { transform: translate(0, 0) scale(1); opacity: 1; }
100% { transform: translate(var(--tx), var(--ty)) scale(0.1); opacity: 0; }
}
</style>
</head>
<body>
<div class="viewport-shell">
<div class="stage-1280" id="appStage">
<div class="library-backdrop"></div>
<!-- 1. TOP HEADER BAR -->
<header class="top-bar">
<div class="brand-cluster">
<div class="brand-logo">📚</div>
<div>
<div class="brand-title">Storybook Sequence Library</div>
<div class="brand-sub">Tell the Steps of Every Story in Order: FIRST • NEXT • THEN • LAST</div>
</div>
</div>
<nav class="mode-tabs" aria-label="Library Game Modes">
<button class="mode-tab active" data-mode="words" type="button">
<span>🔖</span> <span>1. Place Sequence Words</span>
</button>
<button class="mode-tab" data-mode="cards" type="button">
<span>🔀</span> <span>2. Order Story Pages</span>
</button>
<button class="mode-tab" data-mode="quiz" type="button">
<span>🕵️</span> <span>3. Sequence Detective</span>
</button>
</nav>
<div class="top-right">
<div class="star-pill"><span>⭐</span> <span id="starCount">0</span></div>
<button class="util-btn" id="repeatBtn" type="button"><span>🔊</span> Repeat</button>
<button class="util-btn" id="resetBtn" type="button"><span>🔄</span> Reset</button>
<button class="util-btn" id="soundBtn" type="button"><span id="soundIcon">🔊</span> Sound</button>
<button class="util-btn" id="fullBtn" type="button"><span>⛶</span> Full</button>
</div>
</header>
<!-- 2. SEQUENCE WORD RULES & 10-BOOK LIBRARY SHELF -->
<section class="library-shelf-bar" aria-label="Sequence Words and Bookshelf">
<div class="seq-rule-pills">
<button class="seq-rule-btn first" data-rule="first" type="button" title="Tap to hear how FIRST is used!">
<span class="srb-tag">1. FIRST</span>
<span class="srb-lbl">Beginning<small>Step 1</small></span>
</button>
<button class="seq-rule-btn next" data-rule="next" type="button" title="Tap to hear how NEXT is used!">
<span class="srb-tag">2. NEXT</span>
<span class="srb-lbl">After That<small>Step 2</small></span>
</button>
<button class="seq-rule-btn then" data-rule="then" type="button" title="Tap to hear how THEN is used!">
<span class="srb-tag">3. THEN</span>
<span class="srb-lbl">Keep Going<small>Step 3</small></span>
</button>
<button class="seq-rule-btn last" data-rule="last" type="button" title="Tap to hear how LAST is used!">
<span class="srb-tag">4. LAST</span>
<span class="srb-lbl">The Ending<small>Step 4</small></span>
</button>
</div>
<div class="book-spines-row" id="bookSpinesRow"></div>
</section>
<!-- 3. MAIN SPLIT: LEFT READING DESK + RIGHT 3-TEAM BOOKWORM RACE -->
<main class="main-split">
<section class="reading-desk" id="readingDesk" aria-label="Interactive Storybook Desk"></section>
<aside class="bookworm-race" aria-label="3-Team Library Book Stack Race">
<div class="br-head">
<div class="br-title"><span>🦉</span> <span>Library Book Stack</span></div>
<button class="lib-btn" id="whoWinsBtn" type="button" style="padding:3px 9px;font-size:11.5px">🏆 Winner</button>
</div>
<div class="book-stacks-grid" id="bookStacksGrid"></div>
<div style="font-size:11px;font-weight:800;color:#FDE68A;text-align:center;background:rgba(0,0,0,0.28);padding:5px;border-radius:10px;border:1.5px solid rgba(253,224,71,0.3)" id="raceStatusTxt">
✨ Placing sequence words stacks a hardcover book for the active team!
</div>
</aside>
</main>
<!-- 4. FULL-SCREEN POP-UP STORYBOOK DETAIL VIEWER (Upgraded from original DETAIL state!) -->
<div class="modal-overlay" id="detailModal" role="dialog" aria-modal="true">
<div class="popup-storybook">
<div class="ps-left-page">
<div>
<div style="display:flex;align-items:center;justify-content:space-between">
<span id="detailStepBadge" style="padding:6px 18px;border-radius:12px;font-size:26px;font-weight:900;color:#fff;background:#E11D48">1. FIRST</span>
<span id="detailBookTitle" style="font-size:14px;font-weight:900;color:#92400E">🎮 Book 1: Ben's Game</span>
</div>
<p id="detailStoryText" style="font-size:24px;font-weight:900;color:#1E293B;line-height:1.45;margin:22px 0"></p>
</div>
<div style="display:flex;flex-wrap:wrap;gap:10px">
<button class="lib-btn green" id="detailReadBtn" type="button" style="font-size:15px;padding:10px 18px">
<span>🇬🇧</span> <span>Read This Page Aloud</span>
</button>
<button class="lib-btn purple" id="detailReadAllBtn" type="button" style="font-size:15px;padding:10px 18px">
<span>📖</span> <span>Read Whole Story</span>
</button>
<button class="lib-btn" id="detailCloseBtn" type="button" style="font-size:15px;padding:10px 18px">
<span>🔙</span> <span>Back to Desk</span>
</button>
</div>
</div>
<div class="ps-right-page" id="detailRightIllustration"></div>
</div>
</div>
<!-- 5. WINNER MODAL -->
<div class="modal-overlay" id="winnerModal" role="dialog" aria-modal="true">
<div style="background:linear-gradient(180deg,#FFFBEB,#FEF3C7);border:6px solid #D97706;border-radius:28px;padding:28px 36px;text-align:center;max-width:500px;color:#1E293B;box-shadow:0 25px 60px rgba(0,0,0,0.6)">
<div style="font-size:68px;line-height:1">👑📚</div>
<h2 style="font-size:32px;font-weight:900;color:#92400E;margin:8px 0">Master Storytellers!</h2>
<div style="font-size:24px;font-weight:900;color:#1E3A8A;margin-bottom:18px" id="winnerText">Team Owl Wins!</div>
<div style="display:flex;justify-content:center;gap:12px">
<button class="lib-btn" id="closeWinnerBtn" type="button" style="font-size:15px;padding:8px 18px">Keep Reading</button>
<button class="lib-btn green" id="newRaceBtn" type="button" style="font-size:15px;padding:8px 18px">🔄 New Book Stack</button>
</div>
</div>
</div>
<!-- CONFETTI LAYER -->
<div class="confetti-layer" id="confettiLayer"></div>
</div>
</div>
<script src="assets/audio_sequence_library.js"></script>
<script>
(function() {
"use strict";
/* ============ 1. PROPORTIONAL 1280x720 CONTAINMENT ============ */
var BASE_W = 1280;
var BASE_H = 720;
function fitAppToScreen() {
var vw = window.innerWidth || document.documentElement.clientWidth || BASE_W;
var vh = window.innerHeight || document.documentElement.clientHeight || BASE_H;
var pad = (vw < 900 || vh < 600) ? 4 : 10;
var scale = Math.min((vw - pad * 2) / BASE_W, (vh - pad * 2) / BASE_H);
scale = Math.max(0.28, Math.min(scale, 1.35));
document.documentElement.style.setProperty("--app-scale", scale.toFixed(4));
}
window.addEventListener("resize", fitAppToScreen);
window.addEventListener("orientationchange", fitAppToScreen);
fitAppToScreen();
/* ============ 2. 10 COMPLETE 4-STEP STORYBOOKS (40 CARDS!) ============ */
var SEQ_META = [
{ id: "first", num: 1, label: "FIRST", sub: "1st • Beginning", color: "#E11D48" },
{ id: "next", num: 2, label: "NEXT", sub: "2nd • Step Two", color: "#D97706" },
{ id: "then", num: 3, label: "THEN", sub: "3rd • Step Three",color: "#2563EB" },
{ id: "last", num: 4, label: "LAST", sub: "4th • The Ending",color: "#059669" }
];
var STORYBOOKS = [
{
id: "b01",
shortTitle: "🎮 1. Ben's Game",
fullTitle: "Book 1: Ben's Video Game Award (“Stop Light”)",
icon: "🎮",
steps: [
{ id: "first", label: "FIRST", emoji: "🏆🎮", sceneTag: "Winning the Award", bg: "linear-gradient(135deg,#FEF3C7,#FDE68A)", imgUrl: "https://i.ibb.co/d4vwNpP1/image.png", text: "Ben wins an award for his video game, “Stop Light”, and is asked to give a speech." },
{ id: "next", label: "NEXT", emoji: "😰🎤", sceneTag: "Called to the Stage", bg: "linear-gradient(135deg,#DBEAFE,#BFDBFE)", imgUrl: "https://i.ibb.co/gLwrrY1M/image.png", text: "Ben is nervous, but still attends the ceremony. He is called to the stage as the winner." },
{ id: "then", label: "THEN", emoji: "🚦💡", sceneTag: "Red Light, Green Light", bg: "linear-gradient(135deg,#DCFCE7,#A7F3D0)", imgUrl: "https://i.ibb.co/tPkfrnRr/image.png", text: "Ben talks about playing “Red Light, Green Light” which made him make the game." },
{ id: "last", label: "LAST", emoji: "👏😊", sceneTag: "Everyone Claps!", bg: "linear-gradient(135deg,#FCE7F3,#FBCFE8)", imgUrl: "https://i.ibb.co/ZpC5dFZ3/image.png", text: "Ben finishes his speech and everyone claps. He feels proud of his work." }
]
},
{
id: "b02",
shortTitle: "🌻 2. Sunflower",
fullTitle: "Book 2: The Little Sunflower Seed",
icon: "🌻",
steps: [
{ id: "first", label: "FIRST", emoji: "🌱🕳️", sceneTag: "Planting the Seed", bg: "linear-gradient(135deg,#FEF3C7,#FDE68A)", text: "Maya digs a small hole in the warm garden soil and drops in a tiny sunflower seed." },
{ id: "next", label: "NEXT", emoji: "🚿💧", sceneTag: "Watering Every Day", bg: "linear-gradient(135deg,#E0F2FE,#BAE6FD)", text: "She waters the soil every morning with her shiny blue watering can." },
{ id: "then", label: "THEN", emoji: "🌿☀️", sceneTag: "Sprouting Leaves", bg: "linear-gradient(135deg,#DCFCE7,#86EFAC)", text: "A green stem and two little leaves pop up toward the bright sun." },
{ id: "last", label: "LAST", emoji: "🌻🐝", sceneTag: "Giant Golden Flower", bg: "linear-gradient(135deg,#FEF08A,#FACC15)", text: "A giant golden sunflower blooms taller than Maya's head!" }
]
},
{
id: "b03",
shortTitle: "🥞 3. Pancakes",
fullTitle: "Book 3: Sunday Morning Pancakes",
icon: "🥞",
steps: [
{ id: "first", label: "FIRST", emoji: "🥣🥚", sceneTag: "Mixing the Batter", bg: "linear-gradient(135deg,#FEF3C7,#FDE68A)", text: "Leo mixes flour, fresh milk, and one egg in a big yellow bowl." },
{ id: "next", label: "NEXT", emoji: "🍳🔥", sceneTag: "Pouring on the Pan", bg: "linear-gradient(135deg,#FFEDD5,#FDBA74)", text: "Dad pours a round circle of batter onto the warm frying pan." },
{ id: "then", label: "THEN", emoji: "🫧🤸", sceneTag: "Flipping the Pancake", bg: "linear-gradient(135deg,#DBEAFE,#93C5FD)", text: "Little bubbles pop on top, and Leo flips the golden pancake in the air!" },
{ id: "last", label: "LAST", emoji: "🥞🍯", sceneTag: "Syrup & Breakfast!", bg: "linear-gradient(135deg,#DCFCE7,#86EFAC)", text: "They stack the warm pancakes on a plate with sweet maple syrup." }
]
},
{
id: "b04",
shortTitle: "🐣 4. Robin's Egg",
fullTitle: "Book 4: The Mystery Blue Egg",
icon: "🐣",
steps: [
{ id: "first", label: "FIRST", emoji: "🪺🌳", sceneTag: "Building the Nest", bg: "linear-gradient(135deg,#DCFCE7,#BBF7D0)", text: "Mother Robin builds a cozy twig nest high up in the green apple tree." },
{ id: "next", label: "NEXT", emoji: "🥚🐦", sceneTag: "Keeping Eggs Warm", bg: "linear-gradient(135deg,#E0F2FE,#BAE6FD)", text: "She sits on three shiny blue eggs to keep them warm and safe." },
{ id: "then", label: "THEN", emoji: "🐣💥", sceneTag: "Tap, Tap, Hatch!", bg: "linear-gradient(135deg,#FEF9C3,#FDE047)", text: "A tiny crack appears—tap, tap, tap—and a baby bird pokes its beak out!" },
{ id: "last", label: "LAST", emoji: "🐦☁️", sceneTag: "Flying in the Sky", bg: "linear-gradient(135deg,#DBEAFE,#60A5FA)", text: "The little robin grows soft feathers and flies high into the blue sky!" }
]
},
{
id: "b05",
shortTitle: "⛄ 5. Snowman",
fullTitle: "Book 5: Building Frosty the Snowman",
icon: "⛄",
steps: [
{ id: "first", label: "FIRST", emoji: "❄️🌨️", sceneTag: "Snowy Blanket", bg: "linear-gradient(135deg,#E0F2FE,#BAE6FD)", text: "Fluffy white snowflakes fall all night and cover the yard like a blanket." },
{ id: "next", label: "NEXT", emoji: "⚪⛄", sceneTag: "Rolling 3 Snowballs", bg: "linear-gradient(135deg,#DBEAFE,#93C5FD)", text: "Sam and Lily roll three big snowballs and stack them from biggest to smallest." },
{ id: "then", label: "THEN", emoji: "🥕👀", sceneTag: "Carrot Nose & Eyes", bg: "linear-gradient(135deg,#FFEDD5,#FDBA74)", text: "They push in a crunchy orange carrot nose, button eyes, and two stick arms." },
{ id: "last", label: "LAST", emoji: "🧣🎩", sceneTag: "Scarf & Top Hat!", bg: "linear-gradient(135deg,#FCE7F3,#F9A8D4)", text: "They wrap a cozy red scarf around the snowman and put a top hat on its head!" }
]
},
{
id: "b06",
shortTitle: "🦷 6. Bedtime",
fullTitle: "Book 6: Toby's Cozy Bedtime Routine",
icon: "😴",
steps: [
{ id: "first", label: "FIRST", emoji: "🛁🫧", sceneTag: "Warm Bubble Bath", bg: "linear-gradient(135deg,#E0F2FE,#BAE6FD)", text: "Toby takes a warm bubble bath and puts on his blue star pajamas." },
{ id: "next", label: "NEXT", emoji: "🪥🦷", sceneTag: "Brushing Teeth", bg: "linear-gradient(135deg,#DCFCE7,#A7F3D0)", text: "He squeezes minty toothpaste onto his brush and brushes his teeth." },
{ id: "then", label: "THEN", emoji: "📖🐉", sceneTag: "Bedtime Story", bg: "linear-gradient(135deg,#FEF3C7,#FDE68A)", text: "Mom sits by his bed and reads a library book about friendly dragons." },
{ id: "last", label: "LAST", emoji: "🧸😴", sceneTag: "Fast Asleep", bg: "linear-gradient(135deg,#EDE9FE,#C4B5FD)", text: "Toby hugs his soft teddy bear, closes his eyes, and falls fast asleep." }
]
},
{
id: "b07",
shortTitle: "🦋 7. Butterfly",
fullTitle: "Book 7: The Hungry Caterpillar's Surprise",
icon: "🦋",
steps: [
{ id: "first", label: "FIRST", emoji: "🐛🍃", sceneTag: "Tiny Caterpillar", bg: "linear-gradient(135deg,#DCFCE7,#BBF7D0)", text: "A tiny green caterpillar hatches on a sunny leaf in the garden." },
{ id: "next", label: "NEXT", emoji: "🥬😋", sceneTag: "Munching Leaves", bg: "linear-gradient(135deg,#FEF9C3,#FDE047)", text: "The hungry caterpillar munches crunchy green leaves all week to grow big." },
{ id: "then", label: "THEN", emoji: "🥜🌿", sceneTag: "Cozy Chrysalis", bg: "linear-gradient(135deg,#E0F2FE,#BAE6FD)", text: "It spins a quiet green chrysalis shell around itself and rests inside." },
{ id: "last", label: "LAST", emoji: "🦋🌸", sceneTag: "Beautiful Butterfly!", bg: "linear-gradient(135deg,#FCE7F3,#F9A8D4)", text: "The shell opens and a colorful orange butterfly spreads its wings and flies!" }
]
},
{
id: "b08",
shortTitle: "🍕 8. Pizza",
fullTitle: "Book 8: Chef Mia's Homemade Pizza",
icon: "🍕",
steps: [
{ id: "first", label: "FIRST", emoji: "🫓👐", sceneTag: "Rolling the Dough", bg: "linear-gradient(135deg,#FEF3C7,#FDE68A)", text: "Chef Mia rolls the soft pizza dough into a big round circle." },
{ id: "next", label: "NEXT", emoji: "🍅🥄", sceneTag: "Spreading Tomato Sauce", bg: "linear-gradient(135deg,#FEE2E2,#FCA5A5)", text: "She spreads bright red tomato sauce all over the dough with a spoon." },
{ id: "then", label: "THEN", emoji: "🧀🫑", sceneTag: "Cheese & Peppers", bg: "linear-gradient(135deg,#FEF9C3,#FDE047)", text: "She sprinkles stretchy white cheese and sweet bell peppers on top." },
{ id: "last", label: "LAST", emoji: "🍕♨️", sceneTag: "Hot & Ready to Share!", bg: "linear-gradient(135deg,#FFEDD5,#FDBA74)", text: "The pizza bakes in the oven until the crust is golden and ready to share!" }
]
},
{
id: "b09",
shortTitle: "🏖️ 9. Sandcastle",
fullTitle: "Book 9: Noah's Royal Sandcastle",
icon: "🏰",
steps: [
{ id: "first", label: "FIRST", emoji: "🪣🌊", sceneTag: "Filling the Bucket", bg: "linear-gradient(135deg,#E0F2FE,#BAE6FD)", text: "Noah fills his red bucket with wet sand near the splashing ocean waves." },
{ id: "next", label: "NEXT", emoji: "🏰✨", sceneTag: "Shaping 4 Towers", bg: "linear-gradient(135deg,#FEF3C7,#FDE68A)", text: "He flips the bucket upside down and lifts it to make four tall sand towers." },
{ id: "then", label: "THEN", emoji: "🐚🦀", sceneTag: "Seashell Walls", bg: "linear-gradient(135deg,#FCE7F3,#FBCFE8)", text: "He decorates the castle walls with shiny seashells and digs a moat." },
{ id: "last", label: "LAST", emoji: "🚩📸", sceneTag: "Blue Flag on Top!", bg: "linear-gradient(135deg,#DCFCE7,#86EFAC)", text: "He places a little blue flag on the highest tower and smiles for a photo!" }
]
},
{
id: "b10",
shortTitle: "🚀 10. Moon Rocket",
fullTitle: "Book 10: Blast Off to the Moon!",
icon: "🚀",
steps: [
{ id: "first", label: "FIRST", emoji: "🧑🚀🥽", sceneTag: "Putting on Spacesuit", bg: "linear-gradient(135deg,#E0F2FE,#BAE6FD)", text: "Astronaut Ava puts on her white spacesuit, boots, and shiny helmet." },
{ id: "next", label: "NEXT", emoji: "🔟🎙️", sceneTag: "3, 2, 1 Countdown!", bg: "linear-gradient(135deg,#FEF3C7,#FDE68A)", text: "She climbs into the tall rocket ship and counts down: Three, Two, One!" },
{ id: "then", label: "THEN", emoji: "🚀🔥", sceneTag: "Blast Off into Space!", bg: "linear-gradient(135deg,#FFEDD5,#FDBA74)", text: "The engines roar with bright flames and the rocket zooms up into space!" },
{ id: "last", label: "LAST", emoji: "🌕🌎", sceneTag: "Waving from the Moon!", bg: "linear-gradient(135deg,#EDE9FE,#C4B5FD)", text: "Ava lands gently on the Moon and waves back at planet Earth!" }
]
}
];
var TEAMS = [
{ name: "Team Owl", icon: "🦉", c: "#D97706", bookColors: ["#E11D48", "#F59E0B", "#2563EB", "#059669"] },
{ name: "Team Fox", icon: "🦊", c: "#EA580C", bookColors: ["#2563EB", "#8B5CF6", "#EC4899", "#F59E0B"] },
{ name: "Team Lion", icon: "🦁", c: "#059669", bookColors: ["#059669", "#D97706", "#E11D48", "#3B82F6"] }
];
function shuffle(arr) {
var a = arr.slice();
for (var i = a.length - 1; i > 0; i--) {
var j = Math.floor(Math.random() * (i + 1));
var tmp = a[i]; a[i] = a[j]; a[j] = tmp;
}
return a;
}
/* ============ 3. STATE ============ */
var state = {
mode: "words", // "words" | "cards" | "quiz"
bookIdx: 0,
filledSlots: [false, false, false, false],
trayOrder: [2, 0, 3, 1], // shuffled indices 0..3
selectedTrayIdx: null,
quizStepIdx: 0,
quizSolved: false,
stars: 0,
activeTeam: 0,
teamScores: [0, 0, 0],
detailStepIdx: 0,
soundOn: true,
audioCtx: null,
decoded: {},
activeSources: [],
activeHtmlAudio: null,
preloadedImages: {}
};
/* Preload original Book 1 images cleanly */
STORYBOOKS[0].steps.forEach(function(st) {
if (st.imgUrl) {
var img = new Image();
img.crossOrigin = "Anonymous";
img.src = st.imgUrl;
state.preloadedImages[st.id] = img;
}
});
function currentBook() {
return STORYBOOKS[state.bookIdx % STORYBOOKS.length];
}
function resetCurrentBookPuzzle() {
state.filledSlots = [false, false, false, false];
state.trayOrder = shuffle([0, 1, 2, 3]);
// Ensure it isn't trivially [0,1,2,3]
if (state.trayOrder[0] === 0 && state.trayOrder[1] === 1 && state.trayOrder[2] === 2) {
state.trayOrder = [2, 0, 3, 1];
}
state.selectedTrayIdx = null;
state.quizSolved = false;
}
/* ============ 4. STUDIO VOICE ENGINE (JENNY US FEMALE + RYAN UK MALE) ============ */
function ensureAudio() {
if (!state.audioCtx) {
var AC = window.AudioContext || window.webkitAudioContext;
if (AC) state.audioCtx = new AC();
}
if (state.audioCtx && state.audioCtx.state === "suspended") {
state.audioCtx.resume().catch(function() {});
}
return state.audioCtx;
}
function stopAllSpeech() {
state.activeSources.forEach(function(s) {
try { s.stop(0); } catch (e) {}
});
state.activeSources = [];
if (state.activeHtmlAudio) {
try {
state.activeHtmlAudio.pause();
state.activeHtmlAudio.currentTime = 0;
} catch (e) {}
state.activeHtmlAudio = null;
}
}
function playTone(freq, dur, type) {
if (!state.soundOn) return;
var ctx = ensureAudio();
if (!ctx) return;
try {
var o = ctx.createOscillator();
var g = ctx.createGain();
var t = ctx.currentTime;
o.type = type || "sine";
o.frequency.setValueAtTime(freq, t);
o.frequency.exponentialRampToValueAtTime(freq * 1.3, t + (dur || 0.14));
g.gain.setValueAtTime(0.0001, t);
g.gain.exponentialRampToValueAtTime(0.09, t + 0.015);
g.gain.exponentialRampToValueAtTime(0.0001, t + (dur || 0.14));
o.connect(g); g.connect(ctx.destination);
o.start(t); o.stop(t + (dur || 0.14) + 0.02);
} catch (e) {}
}
function computeTrimSpan(buf) {
var ch = buf.getChannelData(0);
var sr = buf.sampleRate;
var n = ch.length;
var thr = 0.012;
var first = 0;
var last = n - 1;
for (var i = 0; i < n; i++) {
if (Math.abs(ch[i]) > thr) { first = i; break; }
}
for (var j = n - 1; j >= 0; j--) {
if (Math.abs(ch[j]) > thr) { last = j; break; }
}
var start = Math.max(0, (first / sr) - 0.02);
var end = Math.min(buf.duration, (last / sr) + 0.045);
return [start, Math.max(start + 0.08, end)];
}
async function decodeClip(key) {
if (state.decoded[key]) return state.decoded[key];
var meta = window.SEQUENCE_LIBRARY_AUDIO && window.SEQUENCE_LIBRARY_AUDIO[key];
if (!meta || !meta.src) return null;
var ctx = ensureAudio();
if (!ctx) return null;
var b64 = meta.src.split(",")[1];
var bin = atob(b64);
var bytes = new Uint8Array(bin.length);
for (var i = 0; i < bin.length; i++) bytes[i] = bin.charCodeAt(i);
var buf = await ctx.decodeAudioData(bytes.buffer.slice(0));
buf._trimSpan = computeTrimSpan(buf);
state.decoded[key] = buf;
return buf;
}
async function playClips(keys) {
if (!state.soundOn) return;
stopAllSpeech();
var valid = (keys || []).filter(function(k) {
return window.SEQUENCE_LIBRARY_AUDIO && window.SEQUENCE_LIBRARY_AUDIO[k];
});
if (!valid.length) return;
var ctx = ensureAudio();
if (ctx) {
try {
var bufs = await Promise.all(valid.map(decodeClip));
var cursor = ctx.currentTime + 0.02;
bufs.forEach(function(buf) {
if (!buf) return;
var span = buf._trimSpan || [0, buf.duration];
var start = span[0];
var dur = Math.max(0.08, span[1] - start);
var src = ctx.createBufferSource();
src.buffer = buf;
src.connect(ctx.destination);
src.start(cursor, start, dur);
state.activeSources.push(src);
cursor += dur + 0.09;
});
return;
} catch (e) {}
}
for (var i = 0; i < valid.length; i++) {
var item = window.SEQUENCE_LIBRARY_AUDIO[valid[i]];
if (!item) continue;
await new Promise(function(resolve) {
var a = new Audio(item.src);
state.activeHtmlAudio = a;
a.onended = resolve;
a.onerror = resolve;
a.play().catch(resolve);
});
}
}
function playClip(key) {
return playClips([key]);
}
function triggerConfetti() {
var layer = document.getElementById("confettiLayer");
if (!layer) return;
var colors = ["#E11D48", "#D97706", "#2563EB", "#059669", "#FACC15", "#A855F7"];
for (var i = 0; i < 34; i++) {
var dot = document.createElement("div");
dot.className = "confetti-dot";
var angle = (Math.PI * 2 * i) / 34 + (Math.random() * 0.25);
var dist = 90 + Math.random() * 230;
dot.style.left = "46%";
dot.style.top = "48%";
dot.style.background = colors[i % colors.length];
dot.style.setProperty("--tx", Math.cos(angle) * dist + "px");
dot.style.setProperty("--ty", Math.sin(angle) * dist + "px");
layer.appendChild(dot);
setTimeout(function(el) {
if (el && el.parentNode) el.parentNode.removeChild(el);
}, 950, dot);
}
}
function awardTeamBook() {
state.stars += 1;
document.getElementById("starCount").textContent = String(state.stars);
var t = state.activeTeam;
state.teamScores[t] += 1;
state.activeTeam = (state.activeTeam + 1) % 3;
var statusEl = document.getElementById("raceStatusTxt");
if (statusEl) {
statusEl.innerHTML = "📚 <b>" + TEAMS[t].name + "</b> added Book #" + state.teamScores[t] + "! Next up: <b>" + TEAMS[state.activeTeam].name + "</b>!";
}
}
/* ============ 5. RENDER 10-BOOK LIBRARY SPINES ============ */
function renderBookSpines() {
var row = document.getElementById("bookSpinesRow");
if (!row) return;
row.innerHTML = STORYBOOKS.map(function(b, idx) {
var active = (idx === state.bookIdx) ? " active" : "";
return '<button type="button" class="book-spine-btn' + active + '" data-book="' + idx + '">' + b.shortTitle + '</button>';
}).join("");
row.querySelectorAll("[data-book]").forEach(function(btn) {
btn.addEventListener("click", function() {
state.bookIdx = parseInt(btn.getAttribute("data-book"), 10);
resetCurrentBookPuzzle();
playClip(currentBook().id + "_intro");
renderAll();
});
});
}
/* ============ 6. POP-UP STORYBOOK DETAIL MODAL ============ */
function openDetailModal(stepIdx) {
state.detailStepIdx = stepIdx;
var book = currentBook();
var step = book.steps[stepIdx];
var meta = SEQ_META[stepIdx];
var badge = document.getElementById("detailStepBadge");
badge.textContent = meta.num + ". " + meta.label;
badge.style.background = meta.color;
document.getElementById("detailBookTitle").textContent = book.fullTitle;
document.getElementById("detailStoryText").innerHTML =
'<span style="color:' + meta.color + ';text-decoration:underline">' + meta.label + ',</span> ' + step.text;
var rightBox = document.getElementById("detailRightIllustration");
var preImg = (book.id === "b01") && state.preloadedImages[step.id];
if (preImg && preImg.complete && preImg.naturalWidth > 0) {
rightBox.style.background = "#1E293B";
rightBox.innerHTML =
'<img src="' + step.imgUrl + '" alt="' + step.label + '" style="max-width:94%;max-height:84%;border-radius:14px;border:4px solid #fff;box-shadow:0 8px 22px rgba(0,0,0,0.4)" />' +
'<div style="margin-top:8px;font-size:14px;font-weight:900;color:#FEF08A">' + step.emoji + ' ' + step.sceneTag + '</div>';
} else {
rightBox.style.background = step.bg;
rightBox.innerHTML =
'<div style="font-size:128px;line-height:1;filter:drop-shadow(0 10px 14px rgba(0,0,0,0.2))">' + step.emoji + '</div>' +
'<div style="margin-top:16px;font-size:22px;font-weight:900;color:#1E293B;background:rgba(255,255,255,0.9);padding:8px 24px;border-radius:999px;border:3px solid ' + meta.color + '">' +
meta.label + ' • ' + step.sceneTag +
'</div>';
}
document.getElementById("detailModal").classList.add("open");
playClip(book.id + "_" + step.id);
}
/* ============ 7. HANDLE MATCHING A SLOT (Modes 1 & 2) ============ */
function attemptPlaceIntoSlot(sourceStepIdx, targetSlotIdx) {
if (state.filledSlots[targetSlotIdx]) return;
var book = currentBook();
if (sourceStepIdx === targetSlotIdx) {
state.filledSlots[targetSlotIdx] = true;
state.selectedTrayIdx = null;
awardTeamBook();
playTone(587.33, 0.14, "sine");
setTimeout(function() { playTone(880, 0.18, "triangle"); }, 85);
triggerConfetti();
var stepKey = book.steps[targetSlotIdx].id;
playClip(book.id + "_" + stepKey);
renderAll();
} else {
playTone(210, 0.22, "sawtooth");
playClip("try_again");
var trayEl = document.querySelector('[data-tray-step="' + sourceStepIdx + '"]');
if (trayEl) {
trayEl.classList.add("shake");
setTimeout(function() { trayEl.classList.remove("shake"); }, 450);
}
}
}
/* ============ 8. RENDER READING DESK (MODES 1, 2, 3) ============ */
function renderDeskHeader(book) {
var allDone = state.filledSlots.every(Boolean);
return (
'<div class="desk-header">' +
'<div class="book-banner-title">' +
'<span>' + book.icon + '</span> <span>' + book.fullTitle + '</span>' +
'</div>' +
'<div class="desk-actions">' +
'<button type="button" class="lib-btn" id="hearBookIntroBtn"><span>🇺🇸</span> <span>Book Intro</span></button>' +
'<button type="button" class="lib-btn green" id="readWholeBookBtn"><span>🇬🇧</span> <span>Read Full Story</span></button>' +
'<button type="button" class="lib-btn purple" id="nextStorybookBtn"><span>➡️</span> <span>Next Book (' + ((state.bookIdx + 1) % STORYBOOKS.length + 1) + '/10)</span></button>' +
'</div>' +
'</div>'
);
}
function renderWordsMode(desk, book) {
var allPlaced = state.filledSlots.every(Boolean);
var bookmarksHtml = state.trayOrder.map(function(stepIdx) {
var m = SEQ_META[stepIdx];
var placed = state.filledSlots[stepIdx];
var sel = (state.selectedTrayIdx === stepIdx) ? " selected" : "";
return (
'<button type="button" class="seq-bookmark-card ' + m.id + (placed ? " placed" : "") + sel + '" ' +
'draggable="' + (!placed) + '" data-tray-step="' + stepIdx + '">' +
'<span class="sbc-word">' + m.label + '</span>' +
'<span class="sbc-sub">' + m.sub + '</span>' +
'</button>'
);
}).join("");
var slotsHtml = book.steps.map(function(st, idx) {
var m = SEQ_META[idx];
var isFilled = state.filledSlots[idx];
var slotCls = "story-page-slot" + (isFilled ? (" filled " + m.id) : "");
return (
'<div class="' + slotCls + '" data-slot-idx="' + idx + '">' +
'<div class="sps-step-header">' +
'<span>PAGE ' + (idx + 1) + ' OF 4</span>' +
'<span>' + (isFilled ? '✅ Complete' : 'Step ' + (idx + 1)) + '</span>' +
'</div>' +
'<div class="sps-word-dropzone' + (isFilled ? (' locked ' + m.id) : '') + '">' +
(isFilled ? (m.num + '. ' + m.label) : '🔖 Drop "' + m.label + '" Here') +
'</div>' +
'<div class="sps-illustration-box" style="background:' + st.bg + '">' +
'<div class="sps-emoji-stage">' + st.emoji + '</div>' +
'<div class="sps-scene-caption">' + st.sceneTag + '</div>' +
'</div>' +
'<div class="sps-story-text">' +
'<span>' +
(isFilled ? ('<b class="seq-lead ' + m.id + '">' + m.label + ',</b> ') : '<b style="color:#94A3B8">[ ___ ], </b>') +
st.text +
'</span>' +
'</div>' +
'<div class="sps-footer-hint">' +
(isFilled ? '🔍 Click Page to View Picture & Listen!' : '👆 Tap or Drag the Sequence Word!') +
'</div>' +
'</div>'
);
}).join("");
desk.innerHTML =
renderDeskHeader(book) +
'<div class="bookmark-tray">' +
'<div class="tray-instruction">' +
(allPlaced
? '🎉 <b>Story Complete!</b> Click any page below to open the Pop-Up Picture Book!'
: '🔖 <b>Step 1:</b> Drag or tap <b>FIRST, NEXT, THEN, LAST</b> onto the 4 story pages in order!') +
'</div>' +
'<div class="draggable-bookmarks">' + bookmarksHtml + '</div>' +
'</div>' +
'<div class="four-pages-grid">' + slotsHtml + '</div>';
}
function renderCardsMode(desk, book) {
var cardsTrayHtml = state.trayOrder.map(function(stepIdx) {
var st = book.steps[stepIdx];
var placed = state.filledSlots[stepIdx];
var sel = (state.selectedTrayIdx === stepIdx) ? " selected" : "";
return (
'<button type="button" class="seq-bookmark-card" style="background:#FFFBEB;color:#1E293B;border-color:#F59E0B;height:58px;padding:4px 8px" ' +
'draggable="' + (!placed) + '" data-tray-step="' + stepIdx + '">' +
'<div style="font-size:18px;line-height:1">' + st.emoji + ' <b style="font-size:12px">' + st.sceneTag + '</b></div>' +
'<div style="font-size:10px;font-weight:800;color:#78350F;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:165px">' + st.text + '</div>' +
'</button>'
);
}).join("");
var slotsHtml = book.steps.map(function(st, idx) {
var m = SEQ_META[idx];
var isFilled = state.filledSlots[idx];
var slotCls = "story-page-slot" + (isFilled ? (" filled " + m.id) : "");
return (
'<div class="' + slotCls + '" data-slot-idx="' + idx + '">' +
'<div class="sps-word-dropzone locked ' + m.id + '" style="margin-top:0">' +
m.num + '. ' + m.label +
'</div>' +
(isFilled
? (
'<div class="sps-illustration-box" style="background:' + st.bg + '">' +
'<div class="sps-emoji-stage">' + st.emoji + '</div>' +
'<div class="sps-scene-caption">' + st.sceneTag + '</div>' +
'</div>' +
'<div class="sps-story-text"><span><b class="seq-lead ' + m.id + '">' + m.label + ',</b> ' + st.text + '</span></div>' +
'<div class="sps-footer-hint">🔍 Click to Open Picture Book!</div>'
)
: (
'<div style="flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center;color:#64748B;font-weight:900;font-size:13px;padding:12px;text-align:center;background:#F8FAFC;border-radius:14px;border:2px dashed #CBD5E1">' +
'<div style="font-size:38px;margin-bottom:6px">📖❓</div>' +
'<div>Which story card happens <b>' + m.label + '</b> (Step ' + (idx + 1) + ')?</div>' +
'</div>'
)
) +
'</div>'
);
}).join("");
desk.innerHTML =
renderDeskHeader(book) +
'<div class="bookmark-tray">' +
'<div class="tray-instruction">🔀 <b>Scrambled Story Cards:</b> Tap or drag each story scene under <b>FIRST, NEXT, THEN, LAST</b>!</div>' +
'<div class="draggable-bookmarks">' + cardsTrayHtml + '</div>' +
'</div>' +
'<div class="four-pages-grid">' + slotsHtml + '</div>';
}
function renderQuizMode(desk, book) {
var sIdx = state.quizStepIdx % 4;
var st = book.steps[sIdx];
var m = SEQ_META[sIdx];
var dotsHtml = SEQ_META.map(function(meta, i) {
var cur = (i === sIdx) ? " current" : "";
return '<div class="tl-dot' + cur + '">Step ' + (i + 1) + (i === sIdx ? " (Here!)" : "") + '</div>';
}).join(' <span style="font-weight:900;color:#94A3B8">→</span> ');
var choicesHtml = SEQ_META.map(function(meta, i) {
var isRight = state.quizSolved && (i === sIdx);
return (
'<button type="button" class="seq-bookmark-card ' + meta.id + '" style="height:68px" data-quiz-pick="' + i + '">' +
'<span class="sbc-word" style="font-size:24px">' + meta.label + '</span>' +
'<span class="sbc-sub">' + meta.sub + '</span>' +
'</button>'
);
}).join("");
desk.innerHTML =
renderDeskHeader(book) +
'<div class="detective-card">' +
'<div class="timeline-stepper">' + dotsHtml + '</div>' +
'<div style="display:flex;align-items:center;gap:20px;background:' + st.bg + ';padding:14px 26px;border-radius:20px;border:3px solid #CBD5E1">' +
'<div style="font-size:70px;line-height:1">' + st.emoji + '</div>' +
'<div style="font-size:24px;font-weight:900;color:#1E293B;max-width:560px;line-height:1.35">' +
(state.quizSolved
? ('<span style="background:' + m.color + ';color:#fff;padding:2px 12px;border-radius:10px">' + m.label + ',</span> ')
: '<span style="background:#FEF08A;border:3px dashed #D97706;padding:2px 18px;border-radius:10px;color:#92400E">___?___</span> '
) +
st.text +
'</div>' +
'</div>' +
'<div style="font-size:14px;font-weight:900;color:#475569">' +
(state.quizSolved
? '✅ Correct! Tap "Next Detective Card" below for another chapter!'
: '🕵️ Look at the timeline step above! Which Sequence Word begins this sentence?') +
'</div>' +
'</div>' +
'<div class="draggable-bookmarks" style="flex:0">' + choicesHtml + '</div>';
}
function renderDesk() {
var desk = document.getElementById("readingDesk");
if (!desk) return;
var book = currentBook();
if (state.mode === "cards") renderCardsMode(desk, book);
else if (state.mode === "quiz") renderQuizMode(desk, book);
else renderWordsMode(desk, book);
// Wire header buttons
var introBtn = document.getElementById("hearBookIntroBtn");
if (introBtn) {
introBtn.addEventListener("click", function() {
playClip(book.id + "_intro");
});
}
var readAllBtn = document.getElementById("readWholeBookBtn");
if (readAllBtn) {
readAllBtn.addEventListener("click", function() {
playClips([
book.id + "_first",
book.id + "_next",
book.id + "_then",
book.id + "_last"
]);
});
}
var nextBookBtn = document.getElementById("nextStorybookBtn");
if (nextBookBtn) {
nextBookBtn.addEventListener("click", function() {
state.bookIdx = (state.bookIdx + 1) % STORYBOOKS.length;
resetCurrentBookPuzzle();
playClip(currentBook().id + "_intro");
renderAll();
});
}
// Wire Tray Bookmarks (Click + Drag)
desk.querySelectorAll("[data-tray-step]").forEach(function(el) {
var stepIdx = parseInt(el.getAttribute("data-tray-step"), 10);
if (state.filledSlots[stepIdx]) return;
el.addEventListener("click", function() {
state.selectedTrayIdx = stepIdx;
playClip("rule_" + SEQ_META[stepIdx].id);
renderDesk();
});
el.addEventListener("dragstart", function(e) {
state.selectedTrayIdx = stepIdx;
if (e.dataTransfer) e.dataTransfer.setData("text/plain", String(stepIdx));
});
});
// Wire Story Slots (Click + Drop + Open Detail View)
desk.querySelectorAll("[data-slot-idx]").forEach(function(slotEl) {
var slotIdx = parseInt(slotEl.getAttribute("data-slot-idx"), 10);
slotEl.addEventListener("dragover", function(e) {
e.preventDefault();
});
slotEl.addEventListener("drop", function(e) {
e.preventDefault();
var srcIdx = state.selectedTrayIdx;
if (e.dataTransfer) {
var dt = parseInt(e.dataTransfer.getData("text/plain"), 10);
if (!isNaN(dt)) srcIdx = dt;
}
if (srcIdx !== null && srcIdx !== undefined) {
attemptPlaceIntoSlot(srcIdx, slotIdx);
}
});
slotEl.addEventListener("click", function() {
if (state.filledSlots[slotIdx]) {
openDetailModal(slotIdx);
} else if (state.selectedTrayIdx !== null) {
attemptPlaceIntoSlot(state.selectedTrayIdx, slotIdx);
} else {
// If nothing selected in tray, tapping the matching slot can guide or play rule
playClip("rule_" + SEQ_META[slotIdx].id);
}
});
});
// Wire Mode 3 Detective choices
desk.querySelectorAll("[data-quiz-pick]").forEach(function(btn) {
btn.addEventListener("click", function() {
var picked = parseInt(btn.getAttribute("data-quiz-pick"), 10);
var target = state.quizStepIdx % 4;
if (picked === target) {
state.quizSolved = true;
awardTeamBook();
triggerConfetti();
playClip(book.id + "_" + SEQ_META[target].id);
renderAll();
setTimeout(function() {
state.quizStepIdx = (state.quizStepIdx + 1) % 4;
if (state.quizStepIdx === 0) {
state.bookIdx = (state.bookIdx + 1) % STORYBOOKS.length;
}
state.quizSolved = false;
renderAll();
}, 2900);
} else {
playTone(210, 0.22, "sawtooth");
playClip("try_again");
}
});
});
}
/* ============ 9. RENDER RIGHT 3-TEAM BOOKWORM STACK RACE ============ */
function renderRaceStacks() {
var grid = document.getElementById("bookStacksGrid");
if (!grid) return;
grid.innerHTML = TEAMS.map(function(t, idx) {
var sc = state.teamScores[idx];
var isTurn = (state.activeTeam === idx);
var booksHtml = "";
for (var b = 0; b < Math.min(sc, 16); b++) {
var col = t.bookColors[b % t.bookColors.length];
booksHtml += '<div class="stacked-book" style="background:' + col + '">' + (b + 1) + '</div>';
}
return (
'<div class="team-pedestal-col' + (isTurn ? ' active-turn' : '') + '" data-team-col="' + idx + '">' +
'<div class="tp-badge" style="border-color:' + t.c + '">' +
'<div style="font-size:11.5px;font-weight:900;color:' + t.c + '">' + t.icon + ' ' + t.name + '</div>' +
'<div style="font-size:13px;font-weight:900">📚 ' + sc + '</div>' +
'</div>' +
'<div class="stack-column">' +
'<div class="oak-shelf-base"></div>' +
booksHtml +
'</div>' +
'<button type="button" class="lib-btn" data-add-book="' + idx + '" style="width:100%;justify-content:center;padding:4px 2px;font-size:11px">+1 Book</button>' +
'</div>'
);
}).join("");
grid.querySelectorAll("[data-team-col]").forEach(function(col) {
col.addEventListener("click", function(e) {
if (e.target.closest("button")) return;
state.activeTeam = parseInt(col.getAttribute("data-team-col"), 10);
renderRaceStacks();
});
});
grid.querySelectorAll("[data-add-book]").forEach(function(btn) {
btn.addEventListener("click", function(e) {
e.stopPropagation();
var ti = parseInt(btn.getAttribute("data-add-book"), 10);
state.teamScores[ti] += 1;
playClip("praise_" + (ti + 1));
renderRaceStacks();
});
});
}
function renderAll() {
renderBookSpines();
renderDesk();
renderRaceStacks();
}
/* ============ 10. WIRE GLOBAL CONTROLS ============ */
document.querySelectorAll(".mode-tab").forEach(function(tab) {
tab.addEventListener("click", function() {
state.mode = tab.getAttribute("data-mode");
resetCurrentBookPuzzle();
document.querySelectorAll(".mode-tab").forEach(function(t) {
t.classList.toggle("active", t === tab);
});
if (state.mode === "words") playClip("mode_words");
else if (state.mode === "cards") playClip("mode_cards");
else if (state.mode === "quiz") playClip("mode_quiz");
renderAll();
});
});
document.querySelectorAll("[data-rule]").forEach(function(btn) {
btn.addEventListener("click", function() {
playClip("rule_" + btn.getAttribute("data-rule"));
});
});
document.getElementById("detailReadBtn").addEventListener("click", function() {
var book = currentBook();
var step = book.steps[state.detailStepIdx];
playClip(book.id + "_" + step.id);
});
document.getElementById("detailReadAllBtn").addEventListener("click", function() {
var book = currentBook();
playClips([
book.id + "_first",
book.id + "_next",
book.id + "_then",
book.id + "_last"
]);
});
document.getElementById("detailCloseBtn").addEventListener("click", function() {
document.getElementById("detailModal").classList.remove("open");
});
document.getElementById("repeatBtn").addEventListener("click", function() {
playClip(currentBook().id + "_intro");
});
document.getElementById("resetBtn").addEventListener("click", function() {
state.teamScores = [0, 0, 0];
state.stars = 0;
document.getElementById("starCount").textContent = "0";
resetCurrentBookPuzzle();
renderAll();
});
document.getElementById("soundBtn").addEventListener("click", function() {
state.soundOn = !state.soundOn;
if (!state.soundOn) stopAllSpeech();
document.getElementById("soundIcon").textContent = state.soundOn ? "🔊" : "🔇";
});
document.getElementById("fullBtn").addEventListener("click", function() {
var el = document.documentElement;
if (!document.fullscreenElement) {
if (el.requestFullscreen) el.requestFullscreen().catch(function() {});
} else {
if (document.exitFullscreen) document.exitFullscreen().catch(function() {});
}
});
document.getElementById("whoWinsBtn").addEventListener("click", function() {
var max = Math.max.apply(null, state.teamScores);
var winners = TEAMS.filter(function(_, i) { return state.teamScores[i] === max; });
var names = winners.map(function(w) { return w.icon + " " + w.name; }).join(" & ");
document.getElementById("winnerText").textContent = max === 0
? "Stack some storybooks first to crown a winner!"
: names + " (" + max + " Books!)";
document.getElementById("winnerModal").classList.add("open");
triggerConfetti();
playClip("winner");
});
document.getElementById("closeWinnerBtn").addEventListener("click", function() {
document.getElementById("winnerModal").classList.remove("open");
});
document.getElementById("newRaceBtn").addEventListener("click", function() {
state.teamScores = [0, 0, 0];
document.getElementById("winnerModal").classList.remove("open");
renderRaceStacks();
});
resetCurrentBookPuzzle();
renderAll();
})();
</script>
</body>
</html>