Исходники пакета · Am, Is, Are All-Stars
CC BY 4.0 · укажите @joe · v1
Берите, делайте ремикс, учите с ним
Код ниже — только играемый входной HTML пакета. Скачайте полный ZIP, чтобы править или ремикшировать игру со всеми относительными ресурсами.
Заготовка подсказки для ИИ
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 · 4555.2 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>am, is, are — Verb to Be Kingdom & Cup Castle Race</title>
<style>
:root {
--app-scale: 1;
--ink: #1E293B;
--am-c: #EC4899;
--am-bg: #FDF2F8;
--am-bd: #F472B6;
--is-c: #2563EB;
--is-bg: #EFF6FF;
--is-bd: #60A5FA;
--are-c: #059669;
--are-bg: #ECFDF5;
--are-bd: #34D399;
--gold: #F59E0B;
}
* {
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 20% 15%, #38BDF8 0%, #2563EB 48%, #1E1B4B 100%);
color: var(--ink);
}
.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, #F0F9FF 0%, #ECFDF5 55%, #FEFCE8 100%);
border-radius: 30px;
border: 6px solid #FBBF24;
box-shadow: 0 24px 65px rgba(15, 23, 42, 0.55), inset 0 2px 0 rgba(255,255,255,0.9);
display: flex;
flex-direction: column;
padding: 12px 16px;
gap: 9px;
position: relative;
overflow: hidden;
}
/* Soft decorative background dots */
.stage-bg-pattern {
position: absolute;
inset: 0;
pointer-events: none;
background-image: radial-gradient(rgba(59, 130, 246, 0.12) 2px, transparent 2px);
background-size: 32px 32px;
z-index: 0;
}
/* ============ 1. TOP HEADER BAR ============ */
.top-bar {
position: relative;
z-index: 5;
height: 60px;
background: linear-gradient(90deg, #1E3A8A 0%, #2563EB 50%, #0D9488 100%);
border-radius: 20px;
border: 3px solid #93C5FD;
box-shadow: 0 6px 0 rgba(30, 58, 138, 0.35);
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 14px;
color: #fff;
flex-shrink: 0;
}
.brand-cluster {
display: flex;
align-items: center;
gap: 10px;
}
.brand-logo {
width: 44px;
height: 44px;
border-radius: 14px;
background: linear-gradient(135deg, #FDE047, #F97316);
border: 3px solid #FFFBEB;
display: flex;
align-items: center;
justify-content: center;
font-size: 25px;
box-shadow: 0 4px 0 rgba(124, 45, 18, 0.35);
}
.brand-title {
font-size: 24px;
font-weight: 900;
letter-spacing: 0.4px;
line-height: 1.05;
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.35);
}
.brand-sub {
font-size: 11.5px;
font-weight: 800;
color: #BAE6FD;
letter-spacing: 0.3px;
}
/* Mode Switch Tabs */
.mode-tabs {
display: flex;
align-items: center;
gap: 6px;
background: rgba(15, 23, 42, 0.28);
padding: 4px;
border-radius: 14px;
border: 2px solid rgba(255, 255, 255, 0.25);
}
.mode-tab {
border: none;
background: transparent;
color: #E0F2FE;
font-family: inherit;
font-size: 13.5px;
font-weight: 900;
padding: 7px 13px;
border-radius: 10px;
cursor: pointer;
display: flex;
align-items: center;
gap: 6px;
transition: all 0.16s ease;
}
.mode-tab:hover {
background: rgba(255, 255, 255, 0.14);
color: #fff;
}
.mode-tab.active {
background: linear-gradient(180deg, #FDE047, #F59E0B);
color: #451A03;
box-shadow: 0 3px 0 #B45309;
transform: translateY(-1px);
}
/* Right utility controls (strictly 4 clean buttons + star counter) */
.top-right {
display: flex;
align-items: center;
gap: 7px;
}
.star-pill {
background: rgba(255, 255, 255, 0.18);
border: 2px solid #FDE047;
border-radius: 999px;
padding: 5px 11px;
font-size: 14px;
font-weight: 900;
color: #FEF08A;
display: flex;
align-items: center;
gap: 5px;
}
.util-btn {
border: 2px solid rgba(255, 255, 255, 0.55);
background: rgba(255, 255, 255, 0.16);
color: #fff;
font-family: inherit;
font-size: 12.5px;
font-weight: 900;
padding: 6px 10px;
border-radius: 11px;
cursor: pointer;
display: flex;
align-items: center;
gap: 4px;
transition: transform 0.12s, background 0.15s;
}
.util-btn:hover {
background: rgba(255, 255, 255, 0.28);
transform: translateY(-1px);
}
.util-btn:active {
transform: translateY(1px);
}
/* ============ 2. PRONOUN GRAMMAR HELPER & VOICE BAR ============ */
.grammar-bar {
position: relative;
z-index: 5;
height: 58px;
background: #FFFFFF;
border-radius: 18px;
border: 3px solid #BFDBFE;
box-shadow: 0 5px 0 rgba(59, 130, 246, 0.12);
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 12px;
gap: 10px;
flex-shrink: 0;
}
.rule-pills {
display: flex;
align-items: center;
gap: 8px;
}
.rule-pill {
border: 3px solid transparent;
border-radius: 14px;
padding: 5px 11px;
cursor: pointer;
display: flex;
align-items: center;
gap: 7px;
font-family: inherit;
transition: transform 0.14s, box-shadow 0.14s;
}
.rule-pill:hover {
transform: translateY(-2px);
}
.rule-pill.am {
background: var(--am-bg);
border-color: var(--am-bd);
color: #9D174D;
}
.rule-pill.is {
background: var(--is-bg);
border-color: var(--is-bd);
color: #1E40AF;
}
.rule-pill.are {
background: var(--are-bg);
border-color: var(--are-bd);
color: #065F46;
}
.rule-pill.highlight {
transform: scale(1.05);
box-shadow: 0 0 0 4px rgba(251, 191, 36, 0.55);
}
.rp-verb {
font-size: 17px;
font-weight: 900;
padding: 2px 8px;
border-radius: 9px;
color: #fff;
text-transform: lowercase;
}
.rule-pill.am .rp-verb { background: var(--am-c); }
.rule-pill.is .rp-verb { background: var(--is-c); }
.rule-pill.are .rp-verb { background: var(--are-c); }
.rp-subj {
font-size: 12.5px;
font-weight: 900;
line-height: 1.15;
text-align: left;
}
.rp-subj small {
display: block;
font-size: 10.5px;
opacity: 0.85;
}
.voice-actions {
display: flex;
align-items: center;
gap: 7px;
}
.act-btn {
border: 3px solid #F59E0B;
background: linear-gradient(180deg, #FEF08A, #FBBF24);
color: #78350F;
font-family: inherit;
font-size: 13px;
font-weight: 900;
padding: 6px 12px;
border-radius: 13px;
box-shadow: 0 4px 0 #B45309;
cursor: pointer;
display: flex;
align-items: center;
gap: 5px;
transition: transform 0.12s;
}
.act-btn:hover { transform: translateY(-1px); }
.act-btn:active { transform: translateY(2px); box-shadow: 0 1px 0 #B45309; }
.act-btn.uk {
border-color: #10B981;
background: linear-gradient(180deg, #A7F3D0, #34D399);
color: #064E3B;
box-shadow: 0 4px 0 #047857;
}
.act-btn.next {
border-color: #6366F1;
background: linear-gradient(180deg, #C7D2FE, #818CF8);
color: #1E1B4B;
box-shadow: 0 4px 0 #4338CA;
}
/* ============ 3. MAIN SPLIT: LEFT GRAMMAR STAGE + RIGHT CUP CASTLE RACE ============ */
.main-split {
position: relative;
z-index: 5;
flex: 1;
min-height: 0;
display: grid;
grid-template-columns: 1fr 375px;
gap: 12px;
}
.grammar-stage {
background: rgba(255, 255, 255, 0.92);
border-radius: 24px;
border: 4px solid #93C5FD;
box-shadow: 0 10px 25px rgba(30, 58, 138, 0.12);
padding: 12px 16px;
display: flex;
flex-direction: column;
justify-content: space-between;
position: relative;
overflow: hidden;
}
/* World Theme Filter Pills */
.theme-row {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
flex-shrink: 0;
}
.theme-chips {
display: flex;
align-items: center;
gap: 6px;
flex-wrap: wrap;
}
.theme-chip {
border: 2px solid #CBD5E1;
background: #F8FAFC;
color: #475569;
font-family: inherit;
font-size: 12px;
font-weight: 800;
padding: 4px 10px;
border-radius: 999px;
cursor: pointer;
transition: all 0.14s;
}
.theme-chip.active {
background: #2563EB;
border-color: #1D4ED8;
color: #fff;
box-shadow: 0 2px 0 #1E3A8A;
}
.q-counter-badge {
font-size: 12.5px;
font-weight: 900;
color: #1E40AF;
background: #DBEAFE;
border: 2px solid #93C5FD;
padding: 4px 11px;
border-radius: 999px;
}
/* Illustrated Scene & Subject Detective Card */
.scene-card {
background: linear-gradient(180deg, #F8FAFC 0%, #EFF6FF 100%);
border: 3px solid #BFDBFE;
border-radius: 22px;
padding: 14px 18px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 10px;
position: relative;
flex: 1;
margin: 8px 0;
box-shadow: inset 0 2px 6px rgba(255,255,255,0.9);
}
.detective-row {
display: flex;
align-items: center;
gap: 10px;
flex-wrap: wrap;
justify-content: center;
}
.subj-badge {
display: inline-flex;
align-items: center;
gap: 7px;
padding: 5px 14px;
border-radius: 999px;
font-size: 14px;
font-weight: 900;
border: 2.5px solid;
box-shadow: 0 3px 0 rgba(0,0,0,0.08);
}
.subj-badge.am { background: #FDF2F8; border-color: #F472B6; color: #9D174D; }
.subj-badge.is { background: #EFF6FF; border-color: #60A5FA; color: #1E40AF; }
.subj-badge.are { background: #ECFDF5; border-color: #34D399; color: #065F46; }
.scene-illustration {
display: flex;
align-items: center;
justify-content: center;
gap: 14px;
}
.big-emoji-bubble {
width: 86px;
height: 86px;
border-radius: 24px;
background: #FFFFFF;
border: 4px solid #60A5FA;
box-shadow: 0 7px 0 rgba(37, 99, 235, 0.2);
display: flex;
align-items: center;
justify-content: center;
font-size: 50px;
animation: gentleBob 2.8s ease-in-out infinite;
}
@keyframes gentleBob {
0%, 100% { transform: translateY(0) rotate(0deg); }
50% { transform: translateY(-5px) rotate(2deg); }
}
/* Sentence Display Box */
.sentence-bridge-box {
background: #FFFFFF;
border: 4px solid #60A5FA;
border-radius: 22px;
padding: 16px 20px;
width: 100%;
text-align: center;
box-shadow: 0 8px 20px rgba(37, 99, 235, 0.12);
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: center;
gap: 10px;
font-size: 31px;
font-weight: 900;
color: #0F172A;
line-height: 1.3;
}
.subj-highlight {
background: #FEF9C3;
border: 2px dashed #F59E0B;
padding: 2px 10px;
border-radius: 12px;
color: #92400E;
}
.verb-slot {
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 112px;
height: 54px;
padding: 0 16px;
border-radius: 16px;
border: 4px dashed #94A3B8;
background: #F1F5F9;
color: #64748B;
font-size: 32px;
font-weight: 900;
transition: all 0.25s cubic-bezier(0.34, 1.56, 0.64, 1);
}
.verb-slot.filled {
border-style: solid;
transform: scale(1.08);
color: #FFFFFF;
box-shadow: 0 6px 0 rgba(0,0,0,0.2);
}
.verb-slot.filled.am { background: linear-gradient(180deg, #F472B6, #DB2777); border-color: #9D174D; }
.verb-slot.filled.is { background: linear-gradient(180deg, #60A5FA, #2563EB); border-color: #1E3A8A; }
.verb-slot.filled.are { background: linear-gradient(180deg, #34D399, #059669); border-color: #064E3B; }
/* Answer Choice Buttons (am, is, are) */
.choices-row {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 14px;
width: 100%;
flex-shrink: 0;
}
.verb-choice-btn {
height: 88px;
border-radius: 22px;
border: 4px solid;
font-family: inherit;
cursor: pointer;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
transition: transform 0.14s cubic-bezier(0.34, 1.56, 0.64, 1), box-shadow 0.14s, opacity 0.15s;
position: relative;
}
.verb-choice-btn.am {
background: linear-gradient(180deg, #FDF2F8 0%, #FCE7F3 100%);
border-color: #EC4899;
color: #9D174D;
box-shadow: 0 7px 0 #BE185D;
}
.verb-choice-btn.is {
background: linear-gradient(180deg, #EFF6FF 0%, #DBEAFE 100%);
border-color: #2563EB;
color: #1E40AF;
box-shadow: 0 7px 0 #1D4ED8;
}
.verb-choice-btn.are {
background: linear-gradient(180deg, #ECFDF5 0%, #D1FAE5 100%);
border-color: #059669;
color: #065F46;
box-shadow: 0 7px 0 #047857;
}
.verb-choice-btn:hover:not(:disabled) {
transform: translateY(-3px);
}
.verb-choice-btn:active:not(:disabled) {
transform: translateY(4px);
box-shadow: 0 2px 0 rgba(0,0,0,0.25);
}
.verb-choice-btn.wrong {
opacity: 0.42;
transform: scale(0.95);
filter: grayscale(0.5);
cursor: not-allowed;
}
.verb-choice-btn.correct {
background: linear-gradient(180deg, #4ADE80, #16A34A) !important;
border-color: #14532D !important;
color: #FFFFFF !important;
box-shadow: 0 7px 0 #14532D !important;
transform: scale(1.04);
}
.vc-word {
font-size: 36px;
font-weight: 900;
line-height: 1;
}
.vc-hint {
font-size: 12px;
font-weight: 800;
margin-top: 4px;
opacity: 0.88;
}
/* ============ MODE 2: VERB BASKET SORT ============ */
.sort-stage-wrap {
display: flex;
flex-direction: column;
justify-content: space-between;
height: 100%;
gap: 10px;
}
.mystery-bubble-card {
flex: 1;
background: radial-gradient(circle at 50% 35%, #FFFFFF 0%, #EFF6FF 100%);
border: 4px solid #60A5FA;
border-radius: 24px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 14px;
gap: 8px;
position: relative;
box-shadow: 0 8px 22px rgba(37, 99, 235, 0.14);
}
.subject-float-pill {
background: linear-gradient(135deg, #FEF9C3, #FDE047);
border: 4px solid #F59E0B;
border-radius: 26px;
padding: 12px 26px;
display: flex;
align-items: center;
gap: 14px;
box-shadow: 0 8px 0 #D97706;
animation: gentleBob 2.5s ease-in-out infinite;
}
.sf-emoji {
font-size: 48px;
line-height: 1;
}
.sf-text {
font-size: 32px;
font-weight: 900;
color: #78350F;
}
.baskets-row {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 14px;
}
.basket-btn {
height: 126px;
border-radius: 24px;
border: 4px solid;
font-family: inherit;
cursor: pointer;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 10px;
transition: transform 0.15s cubic-bezier(0.34, 1.56, 0.64, 1);
position: relative;
}
.basket-btn:hover:not(:disabled) {
transform: translateY(-4px) scale(1.02);
}
.basket-btn.am {
background: linear-gradient(180deg, #FCE7F3 0%, #FBCFE8 100%);
border-color: #DB2777;
color: #831843;
box-shadow: 0 8px 0 #9D174D;
}
.basket-btn.is {
background: linear-gradient(180deg, #DBEAFE 0%, #BFDBFE 100%);
border-color: #2563EB;
color: #1E3A8A;
box-shadow: 0 8px 0 #1E40AF;
}
.basket-btn.are {
background: linear-gradient(180deg, #D1FAE5 0%, #A7F3D0 100%);
border-color: #059669;
color: #064E3B;
box-shadow: 0 8px 0 #065F46;
}
.basket-icon {
font-size: 26px;
line-height: 1;
}
.basket-title {
font-size: 30px;
font-weight: 900;
margin: 2px 0;
}
.basket-sub {
font-size: 12px;
font-weight: 800;
background: rgba(255,255,255,0.75);
padding: 2px 10px;
border-radius: 999px;
}
/* ============ 4. RIGHT SIDE: 3-TEAM CUP CASTLE TOWER RACE ============ */
.race-stage {
background: linear-gradient(180deg, #87CEEB 0%, #E0F2FE 78%, #86EFAC 78%, #22C55E 100%);
border-radius: 24px;
border: 4px solid #60A5FA;
box-shadow: 0 10px 25px rgba(30, 58, 138, 0.15);
padding: 10px 12px;
display: flex;
flex-direction: column;
justify-content: space-between;
position: relative;
overflow: hidden;
}
.rs-head {
display: flex;
align-items: center;
justify-content: space-between;
background: rgba(255, 255, 255, 0.92);
padding: 6px 10px;
border-radius: 14px;
border: 2px solid #93C5FD;
z-index: 3;
}
.rs-title {
font-size: 15px;
font-weight: 900;
color: #1E3A8A;
display: flex;
align-items: center;
gap: 5px;
}
.towers-grid {
flex: 1;
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 8px;
padding: 8px 0 4px;
position: relative;
z-index: 2;
min-height: 0;
}
.team-column {
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-between;
height: 100%;
background: rgba(255, 255, 255, 0.35);
border-radius: 16px;
border: 2px solid rgba(255, 255, 255, 0.65);
padding: 6px 4px;
cursor: pointer;
transition: border-color 0.15s, background 0.15s;
}
.team-column.active-turn {
background: rgba(255, 255, 255, 0.68);
border: 3px solid #F59E0B;
box-shadow: 0 0 0 3px rgba(251, 191, 36, 0.45);
}
.team-top-pill {
width: 100%;
background: rgba(255, 255, 255, 0.94);
border-radius: 11px;
padding: 4px 5px;
text-align: center;
border: 2px solid;
flex-shrink: 0;
}
.tt-name {
font-size: 12px;
font-weight: 900;
line-height: 1.1;
}
.tt-score-row {
display: flex;
align-items: center;
justify-content: center;
gap: 5px;
margin-top: 2px;
font-size: 13px;
font-weight: 900;
color: #1E293B;
}
.minus-cup-btn {
width: 19px;
height: 19px;
border-radius: 50%;
border: 1.5px solid #FCA5A5;
background: #FEE2E2;
color: #DC2626;
font-size: 12px;
font-weight: 900;
line-height: 1;
cursor: pointer;
display: inline-flex;
align-items: center;
justify-content: center;
padding: 0;
}
/* Cup Tower Stack Container */
.cup-stack-area {
flex: 1;
width: 100%;
display: flex;
flex-direction: column-reverse;
align-items: center;
justify-content: flex-start;
padding: 6px 0 2px;
position: relative;
min-height: 0;
overflow: hidden;
}
.pedestal-base {
width: 86%;
height: 10px;
background: linear-gradient(90deg, #64748B, #94A3B8, #64748B);
border: 2px solid #334155;
border-radius: 6px;
flex-shrink: 0;
}
.castle-cup {
width: 68%;
border: 2.5px solid;
border-radius: 5px 5px 12px 12px;
box-shadow: inset 0 -4px 0 rgba(0,0,0,0.18), 0 2px 4px rgba(0,0,0,0.2);
display: flex;
align-items: center;
justify-content: center;
font-size: 11px;
font-weight: 900;
color: #fff;
position: relative;
margin-top: -2px;
flex-shrink: 0;
animation: dropCup 0.28s cubic-bezier(0.34, 1.56, 0.64, 1);
}
@keyframes dropCup {
0% { transform: translateY(-28px) scale(1.12); opacity: 0; }
100% { transform: translateY(0) scale(1); opacity: 1; }
}
.leader-crown {
position: absolute;
bottom: 102%;
font-size: 24px;
line-height: 1;
filter: drop-shadow(0 3px 4px rgba(0,0,0,0.3));
animation: gentleBob 1.8s ease-in-out infinite;
pointer-events: none;
}
.team-tap-btns {
display: flex;
gap: 4px;
width: 100%;
flex-shrink: 0;
}
.tap-cup-btn {
flex: 1;
border: 2.5px solid;
border-radius: 11px;
background: #FFFFFF;
font-family: inherit;
font-size: 11.5px;
font-weight: 900;
padding: 5px 2px;
cursor: pointer;
box-shadow: 0 3px 0 rgba(0,0,0,0.16);
transition: transform 0.1s;
}
.tap-cup-btn:active {
transform: translateY(2px);
box-shadow: none;
}
/* Fireworks Canvas / Particles */
.confetti-layer {
position: absolute;
inset: 0;
pointer-events: none;
z-index: 40;
overflow: hidden;
}
.confetti-dot {
position: absolute;
width: 12px;
height: 12px;
border-radius: 50%;
animation: burstOut 0.95s 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; }
}
/* Winner Modal */
.modal-overlay {
position: absolute;
inset: 0;
background: rgba(15, 23, 42, 0.72);
backdrop-filter: blur(5px);
z-index: 80;
display: none;
align-items: center;
justify-content: center;
}
.modal-overlay.open {
display: flex;
}
.winner-card {
background: linear-gradient(180deg, #FFFBEB 0%, #FEF3C7 100%);
border: 6px solid #F59E0B;
border-radius: 30px;
padding: 28px 38px;
text-align: center;
max-width: 520px;
box-shadow: 0 25px 60px rgba(0, 0, 0, 0.5);
}
</style>
</head>
<body>
<div class="viewport-shell">
<div class="stage-1280" id="appStage">
<div class="stage-bg-pattern"></div>
<!-- 1. TOP BAR -->
<header class="top-bar">
<div class="brand-cluster">
<div class="brand-logo" aria-hidden="true">👑</div>
<div>
<div class="brand-title">am, is, are</div>
<div class="brand-sub">Verb to Be Kingdom & Cup Castle Race</div>
</div>
</div>
<nav class="mode-tabs" aria-label="Game Modes">
<button class="mode-tab active" data-mode="bridge" type="button">
<span>🧩</span> <span>1. Sentence Bridge</span>
</button>
<button class="mode-tab" data-mode="sort" type="button">
<span>🧺</span> <span>2. Verb Basket Sort</span>
</button>
<button class="mode-tab" data-mode="flip" type="button">
<span>🔄</span> <span>3. Question Flip</span>
</button>
</nav>
<div class="top-right">
<div class="star-pill" title="Total Correct Sentences">
<span>⭐</span> <span id="starCount">0</span>
</div>
<button class="util-btn" id="repeatBtn" type="button" title="Repeat Question Audio">
<span>🔊</span> Repeat
</button>
<button class="util-btn" id="resetBtn" type="button" title="Reset Scores">
<span>🔄</span> Reset
</button>
<button class="util-btn" id="soundBtn" type="button" title="Toggle Sound">
<span id="soundIcon">🔊</span> Sound
</button>
<button class="util-btn" id="fullBtn" type="button" title="Toggle Fullscreen">
<span>⛶</span> Full
</button>
</div>
</header>
<!-- 2. INTERACTIVE PRONOUN GRAMMAR RULE HELPER & VOICE BAR -->
<section class="grammar-bar" aria-label="Verb to Be Grammar Rules">
<div class="rule-pills">
<button class="rule-pill am" id="ruleBtnAm" type="button" title="Tap to hear the AM rule!">
<span class="rp-verb">am</span>
<span class="rp-subj">👑 Only with <b>I</b><small>"I am..." (Me!)</small></span>
</button>
<button class="rule-pill is" id="ruleBtnIs" type="button" title="Tap to hear the IS rule!">
<span class="rp-verb">is</span>
<span class="rp-subj">🌟 <b>1</b> Person or Thing<small>He / She / It / Singular</small></span>
</button>
<button class="rule-pill are" id="ruleBtnAre" type="button" title="Tap to hear the ARE rule!">
<span class="rp-verb">are</span>
<span class="rp-subj">🎉 <b>2+</b> Plural or You<small>We / They / You / Plural</small></span>
</button>
</div>
<div class="voice-actions">
<button class="act-btn" id="hearClueBtn" type="button">
<span>🇺🇸</span> <span>Hear Clue</span>
</button>
<button class="act-btn uk" id="hearSentenceBtn" type="button">
<span>🇬🇧</span> <span>Hear Sentence</span>
</button>
<button class="act-btn next" id="nextQuestionBtn" type="button">
<span>➡️</span> <span>Next Card</span>
</button>
</div>
</section>
<!-- 3. SPLIT STAGE: LEFT GRAMMAR ARENA + RIGHT CUP CASTLE RACE -->
<main class="main-split">
<section class="grammar-stage" id="grammarStage" aria-label="Interactive Grammar Stage"></section>
<aside class="race-stage" aria-label="3-Team Cup Castle Race">
<div class="rs-head">
<div class="rs-title">
<span>🏰</span> <span>Cup Castle Race</span>
</div>
<button class="act-btn" id="whoWinsBtn" type="button" style="padding:3px 10px;font-size:12px">
🏆 Who's Winning?
</button>
</div>
<div class="towers-grid" id="towersGrid"></div>
<div style="font-size:11.5px;font-weight:800;color:#1E3A8A;text-align:center;background:rgba(255,255,255,0.9);padding:5px;border-radius:11px;border:2px solid #93C5FD;z-index:3" id="raceStatusTxt">
✨ Answering stacks a cup on the active team's castle! Tap any team to switch turns!
</div>
</aside>
</main>
<!-- CONFETTI LAYER -->
<div class="confetti-layer" id="confettiLayer"></div>
<!-- WINNER MODAL -->
<div class="modal-overlay" id="winnerModal" role="dialog" aria-modal="true">
<div class="winner-card">
<div style="font-size:72px;line-height:1;margin-bottom:8px">👑</div>
<h2 style="font-size:34px;font-weight:900;color:#92400E;margin:0 0 6px">Cup Castle Champions!</h2>
<div style="font-size:25px;font-weight:900;color:#1E3A8A;margin:0 0 18px" id="winnerText">Team Ruby Wins!</div>
<div style="display:flex;justify-content:center;gap:12px">
<button class="act-btn" id="closeWinnerBtn" type="button">Keep Playing</button>
<button class="act-btn uk" id="newRaceBtn" type="button">🔄 New Castle Race</button>
</div>
</div>
</div>
</div>
</div>
<script src="assets/audio_am_is_are.js"></script>
<script>
(function() {
"use strict";
/* ============ 1. PROPORTIONAL 1280x720 SCALE 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. ALL 24 SENTENCES & GRAMMAR METADATA ============ */
var QUESTIONS = [
// Community Town
{ id: "01", world: "community", worldName: "🚓 Community Town", emoji: "🚓", subjEmoji: "👮", subject: "The police officer", category: "Singular • Just 1 Person (He/She)", before: "The police officer 👮", after: "helping.", answer: "is", flipSubj: "the police officer 👮", flipAfter: "helping?", shortAns: "Yes, he is!" },
{ id: "02", world: "community", worldName: "🚓 Community Town", emoji: "📚", subjEmoji: "🧑🤝🧑", subject: "We", category: "Plural • 2+ People (Us Together)", before: "We 🧑🤝🧑", after: "at the library.", answer: "are", flipSubj: "we 🧑🤝🧑", flipAfter: "at the library?", shortAns: "Yes, we are!" },
{ id: "03", world: "community", worldName: "🚓 Community Town", emoji: "🛒", subjEmoji: "🙋", subject: "I", category: "First Person • Talking About Myself!", before: "I 🙋", after: "at the supermarket.", answer: "am", flipSubj: "I 🙋", flipAfter: "at the supermarket?", shortAns: "Yes, I am!" },
{ id: "04", world: "community", worldName: "🚓 Community Town", emoji: "🥖", subjEmoji: "🥐", subject: "The bakery", category: "Singular • Just 1 Place (It)", before: "The bakery 🥐", after: "smelling good.", answer: "is", flipSubj: "the bakery 🥐", flipAfter: "smelling good?", shortAns: "Yes, it is!" },
{ id: "05", world: "community", worldName: "🚓 Community Town", emoji: "🏞️", subjEmoji: "🌳", subject: "The park trees", category: "Plural • More Than 1 Tree (They)", before: "The park trees 🌳", after: "very tall.", answer: "are", flipSubj: "the park trees 🌳", flipAfter: "very tall?", shortAns: "Yes, they are!" },
// Sunny Farm
{ id: "06", world: "farm", worldName: "🚜 Sunny Farm", emoji: "🚜", subjEmoji: "🐴", subject: "The horses", category: "Plural • More Than 1 Horse (They)", before: "The horses 🐴", after: "running fast.", answer: "are", flipSubj: "the horses 🐴", flipAfter: "running fast?", shortAns: "Yes, they are!" },
{ id: "07", world: "farm", worldName: "🚜 Sunny Farm", emoji: "🌱", subjEmoji: "🎃", subject: "The pumpkin", category: "Singular • Just 1 Pumpkin (It)", before: "The pumpkin 🎃", after: "growing in the soil.", answer: "is", flipSubj: "the pumpkin 🎃", flipAfter: "growing in the soil?", shortAns: "Yes, it is!" },
{ id: "08", world: "farm", worldName: "🚜 Sunny Farm", emoji: "🌻", subjEmoji: "🧑🌾", subject: "I", category: "First Person • Talking About Myself!", before: "I 🧑🌾", after: "planting seeds.", answer: "am", flipSubj: "I 🧑🌾", flipAfter: "planting seeds?", shortAns: "Yes, I am!" },
{ id: "09", world: "farm", worldName: "🚜 Sunny Farm", emoji: "🐑", subjEmoji: "🐑", subject: "The sheep", category: "Singular • 1 Fluffy Sheep (It)", before: "The sheep 🐑", after: "fluffy.", answer: "is", flipSubj: "the sheep 🐑", flipAfter: "fluffy?", shortAns: "Yes, it is!" },
// Cozy Closet
{ id: "10", world: "clothing", worldName: "👕 Cozy Closet", emoji: "🧥", subjEmoji: "🥾", subject: "My boots", category: "Plural • 2 Muddy Boots (They)", before: "My boots 🥾", after: "muddy.", answer: "are", flipSubj: "my boots 🥾", flipAfter: "muddy?", shortAns: "Yes, they are!" },
{ id: "11", world: "clothing", worldName: "👕 Cozy Closet", emoji: "👗", subjEmoji: "👒", subject: "Her hat", category: "Singular • Just 1 Hat (It)", before: "Her hat 👒", after: "yellow.", answer: "is", flipSubj: "her hat 👒", flipAfter: "yellow?", shortAns: "Yes, it is!" },
{ id: "12", world: "clothing", worldName: "👕 Cozy Closet", emoji: "👕", subjEmoji: "👕", subject: "I", category: "First Person • Talking About Myself!", before: "I 👕", after: "wearing a blue shirt.", answer: "am", flipSubj: "I 👕", flipAfter: "wearing a blue shirt?", shortAns: "Yes, I am!" },
{ id: "13", world: "clothing", worldName: "👕 Cozy Closet", emoji: "🧦", subjEmoji: "🧦", subject: "These socks", category: "Plural • 2 Warm Socks (They)", before: "These socks 🧦", after: "warm.", answer: "are", flipSubj: "these socks 🧦", flipAfter: "warm?", shortAns: "Yes, they are!" },
{ id: "14", world: "clothing", worldName: "👕 Cozy Closet", emoji: "🧣", subjEmoji: "🧥", subject: "The jacket", category: "Singular • Just 1 Jacket (It)", before: "The jacket 🧥", after: "big.", answer: "is", flipSubj: "the jacket 🧥", flipAfter: "big?", shortAns: "Yes, it is!" },
// Yummy Kitchen & Friends
{ id: "15", world: "food", worldName: "🍓 Food & Friends", emoji: "🥩", subjEmoji: "🐟", subject: "Fish", category: "Singular Food Group (It)", before: "Fish 🐟", after: "healthy protein.", answer: "is", flipSubj: "fish 🐟", flipAfter: "healthy protein?", shortAns: "Yes, it is!" },
{ id: "16", world: "food", worldName: "🍓 Food & Friends", emoji: "🥪", subjEmoji: "🍞", subject: "I", category: "First Person • Talking About Myself!", before: "I 🍞", after: "eating whole grain bread.", answer: "am", flipSubj: "I 🍞", flipAfter: "eating whole grain bread?", shortAns: "Yes, I am!" },
{ id: "17", world: "food", worldName: "🍓 Food & Friends", emoji: "🍎", subjEmoji: "🍓", subject: "The strawberries", category: "Plural • Lots of Berries (They)", before: "The strawberries 🍓", after: "sweet fruits.", answer: "are", flipSubj: "the strawberries 🍓", flipAfter: "sweet fruits?", shortAns: "Yes, they are!" },
{ id: "18", world: "food", worldName: "🍓 Food & Friends", emoji: "🧀", subjEmoji: "🥛", subject: "Milk", category: "Singular Drink (It)", before: "Milk 🥛", after: "good for my bones.", answer: "is", flipSubj: "milk 🥛", flipAfter: "good for my bones?", shortAns: "Yes, it is!" },
{ id: "19", world: "food", worldName: "🍓 Food & Friends", emoji: "🧥", subjEmoji: "👧", subject: "She", category: "Singular • Just 1 Girl (She)", before: "She 👧", after: "wearing a pink raincoat.", answer: "is", flipSubj: "she 👧", flipAfter: "wearing a pink raincoat?", shortAns: "Yes, she is!" },
{ id: "20", world: "food", worldName: "🍓 Food & Friends", emoji: "🐑", subjEmoji: "🐑", subject: "Three little lambs", category: "Plural • 3 Little Lambs (2+)", before: "There", after: "three little lambs 🐑🐑🐑.", answer: "are", flipSubj: "there", flipAfter: "three little lambs 🐑🐑🐑?", shortAns: "Yes, there are!" },
{ id: "21", world: "food", worldName: "🍓 Food & Friends", emoji: "🥕", subjEmoji: "🙋", subject: "I", category: "First Person • Talking About Myself!", before: "I 🙋", after: "picking carrots for my rabbit 🐇.", answer: "am", flipSubj: "I 🙋", flipAfter: "picking carrots for my rabbit 🐇?", shortAns: "Yes, I am!" },
{ id: "22", world: "food", worldName: "🍓 Food & Friends", emoji: "🥚", subjEmoji: "🐊", subject: "A baby crocodile", category: "Singular • 1 Baby Crocodile (It)", before: "There", after: "a baby crocodile 🐊 inside the egg.", answer: "is", flipSubj: "there", flipAfter: "a baby crocodile 🐊 inside the egg?", shortAns: "Yes, there is!" },
{ id: "23", world: "food", worldName: "🍓 Food & Friends", emoji: "🏥", subjEmoji: "👩", subject: "My mother", category: "Singular • 1 Person (She)", before: "My mother 👩", after: "going to the hospital to see a doctor.", answer: "is", flipSubj: "my mother 👩", flipAfter: "going to the hospital?", shortAns: "Yes, she is!" },
{ id: "24", world: "food", worldName: "🍓 Food & Friends", emoji: "📚", subjEmoji: "🧑🤝🧑", subject: "They", category: "Plural • More Than 1 Person (They)", before: "They 🧑🤝🧑", after: "at the library to read books 📚.", answer: "are", flipSubj: "they 🧑🤝🧑", flipAfter: "at the library to read books 📚?", shortAns: "Yes, they are!" }
];
var TEAMS = [
{ name: "Team Ruby", icon: "🔴", c: "#EF4444", bg: "linear-gradient(180deg, #F87171, #DC2626)", bd: "#991B1B" },
{ name: "Team Sapphire", icon: "🔵", c: "#2563EB", bg: "linear-gradient(180deg, #60A5FA, #2563EB)", bd: "#1E3A8A" },
{ name: "Team Emerald", icon: "🟢", c: "#10B981", bg: "linear-gradient(180deg, #34D399, #059669)", bd: "#064E3B" }
];
/* ============ 3. STATE ============ */
var state = {
mode: "bridge", // "bridge" | "sort" | "flip"
worldFilter: "all", // "all" | "community" | "farm" | "clothing" | "food"
qIdx: 0,
solved: false,
wrongGuesses: [],
streak: 0,
stars: 0,
activeTeam: 0,
teamScores: [0, 0, 0],
soundOn: true,
audioCtx: null,
decoded: {},
activeSources: [],
activeHtmlAudio: null
};
function getFilteredList() {
if (state.worldFilter === "all") return QUESTIONS;
return QUESTIONS.filter(function(item) { return item.world === state.worldFilter; });
}
function currentItem() {
var list = getFilteredList();
if (!list.length) return QUESTIONS[0];
var idx = ((state.qIdx % list.length) + list.length) % list.length;
return list[idx];
}
/* ============ 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.32, t + (dur || 0.15));
g.gain.setValueAtTime(0.0001, t);
g.gain.exponentialRampToValueAtTime(0.09, t + 0.015);
g.gain.exponentialRampToValueAtTime(0.0001, t + (dur || 0.15));
o.connect(g);
g.connect(ctx.destination);
o.start(t);
o.stop(t + (dur || 0.15) + 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.AM_IS_ARE_AUDIO && window.AM_IS_ARE_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.AM_IS_ARE_AUDIO && window.AM_IS_ARE_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.05;
});
return;
} catch (e) {}
}
for (var i = 0; i < valid.length; i++) {
var item = window.AM_IS_ARE_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]);
}
/* ============ 5. CONFETTI / FIREWORKS BURST ============ */
function triggerFireworks() {
var layer = document.getElementById("confettiLayer");
if (!layer) return;
var colors = ["#F59E0B", "#EC4899", "#3B82F6", "#10B981", "#A855F7", "#FACC15"];
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() * 220;
dot.style.left = "42%";
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);
}, 1000, dot);
}
}
/* ============ 6. RENDER GRAMMAR RULE PILLS HIGHLIGHT ============ */
function updateRuleHighlight() {
var item = currentItem();
["am", "is", "are"].forEach(function(v) {
var btn = document.getElementById("ruleBtn" + v.charAt(0).toUpperCase() + v.slice(1));
if (!btn) return;
if (state.solved && item.answer === v) {
btn.classList.add("highlight");
} else {
btn.classList.remove("highlight");
}
});
}
/* ============ 7. HANDLE OPTION PICK across all 3 modes ============ */
function handlePickVerb(selected) {
if (state.solved) return;
var item = currentItem();
if (selected === item.answer) {
state.solved = true;
state.stars += 1;
state.streak += 1;
document.getElementById("starCount").textContent = String(state.stars);
// Award a Cup to the active team and advance active team turn!
var rewardedTeam = state.activeTeam;
state.teamScores[rewardedTeam] += 1;
state.activeTeam = (state.activeTeam + 1) % 3;
playTone(587.33, 0.14, "sine");
setTimeout(function() { playTone(880, 0.18, "triangle"); }, 90);
triggerFireworks();
var statusEl = document.getElementById("raceStatusTxt");
if (statusEl) {
statusEl.innerHTML = "🎉 <b>" + TEAMS[rewardedTeam].name + "</b> stacked Cup #" + state.teamScores[rewardedTeam] + "! Next up: <b>" + TEAMS[state.activeTeam].name + "</b>!";
}
// Speak the completed or flipped sentence with British Male Ryan!
if (state.mode === "flip") {
playClip("flip_" + item.id);
} else {
playClip("ans_" + item.id);
}
renderAll();
} else {
if (state.wrongGuesses.indexOf(selected) === -1) {
state.wrongGuesses.push(selected);
}
state.streak = 0;
playTone(210, 0.22, "sawtooth");
playClip("try_again");
renderStage();
}
}
function nextQuestion(speakClueAfter) {
var list = getFilteredList();
state.qIdx = (state.qIdx + 1) % list.length;
state.solved = false;
state.wrongGuesses = [];
renderAll();
if (speakClueAfter) {
speakCurrentPrompt();
}
}
function speakCurrentPrompt() {
var item = currentItem();
if (state.mode === "sort") {
playClip("sort_" + item.id);
} else if (state.mode === "flip" && state.solved) {
playClip("flip_" + item.id);
} else if (state.solved) {
playClip("ans_" + item.id);
} else {
playClip("q_" + item.id);
}
}
/* ============ 8. RENDER LEFT GRAMMAR STAGE ============ */
function renderThemeHeader(list) {
var idx = ((state.qIdx % list.length) + list.length) % list.length;
var worlds = [
{ id: "all", lbl: "🌎 All 24 Cards" },
{ id: "community", lbl: "🚓 Community" },
{ id: "farm", lbl: "🚜 Sunny Farm" },
{ id: "clothing", lbl: "👕 Cozy Closet" },
{ id: "food", lbl: "🍓 Food & Friends" }
];
var chipsHtml = worlds.map(function(w) {
var active = state.worldFilter === w.id ? " active" : "";
return '<button type="button" class="theme-chip' + active + '" data-world="' + w.id + '">' + w.lbl + '</button>';
}).join("");
return (
'<div class="theme-row">' +
'<div class="theme-chips">' + chipsHtml + '</div>' +
'<div class="q-counter-badge">Card ' + (idx + 1) + ' of ' + list.length + ' • Streak 🔥 ' + state.streak + '</div>' +
'</div>'
);
}
function renderBridgeMode(stage, item, list) {
var slotClass = "verb-slot" + (state.solved ? (" filled " + item.answer) : "");
var slotText = state.solved ? item.answer : "___";
var options = [
{ v: "am", hint: "👑 Only with I" },
{ v: "is", hint: "🌟 1 Person / Thing" },
{ v: "are", hint: "🎉 2+ Plural / You" }
];
var btnsHtml = options.map(function(o) {
var isWrong = state.wrongGuesses.indexOf(o.v) !== -1;
var isCorrect = state.solved && item.answer === o.v;
var cls = "verb-choice-btn " + o.v + (isWrong ? " wrong" : "") + (isCorrect ? " correct" : "");
var dis = (isWrong || state.solved) ? " disabled" : "";
return (
'<button type="button" class="' + cls + '" data-pick="' + o.v + '"' + dis + '>' +
'<span class="vc-word">' + o.v + '</span>' +
'<span class="vc-hint">' + o.hint + '</span>' +
'</button>'
);
}).join("");
stage.innerHTML =
renderThemeHeader(list) +
'<div class="scene-card">' +
'<div class="detective-row">' +
'<span class="subj-badge ' + item.answer + '">🔎 Subject Detective: <b>"' + item.subject + '"</b> • ' + item.category + '</span>' +
'</div>' +
'<div class="scene-illustration">' +
'<div class="big-emoji-bubble">' + item.subjEmoji + '</div>' +
'<div style="font-size:32px;font-weight:900;color:#94A3B8">+</div>' +
'<div class="big-emoji-bubble" style="border-color:#F59E0B">' + item.emoji + '</div>' +
'</div>' +
'<div class="sentence-bridge-box">' +
'<span class="subj-highlight">' + item.before + '</span>' +
'<span class="' + slotClass + '">' + slotText + '</span>' +
'<span>' + item.after + '</span>' +
'</div>' +
'</div>' +
'<div class="choices-row">' + btnsHtml + '</div>';
}
function renderSortMode(stage, item, list) {
var baskets = [
{ v: "am", icon: "👑", title: "AM Basket", sub: "Only for I (Myself!)" },
{ v: "is", icon: "🌟", title: "IS Basket", sub: "He / She / It (Just 1)" },
{ v: "are", icon: "🎉", title: "ARE Basket", sub: "We / They / You (2+)" }
];
var basketsHtml = baskets.map(function(b) {
var isWrong = state.wrongGuesses.indexOf(b.v) !== -1;
var isCorrect = state.solved && item.answer === b.v;
var extraStyle = isWrong ? "opacity:0.4;transform:scale(0.95);" : (isCorrect ? "border-color:#16A34A;background:linear-gradient(180deg,#86EFAC,#22C55E);color:#fff;" : "");
var dis = (isWrong || state.solved) ? " disabled" : "";
return (
'<button type="button" class="basket-btn ' + b.v + '" style="' + extraStyle + '" data-pick="' + b.v + '"' + dis + '>' +
'<span class="basket-icon">' + b.icon + ' 🧺</span>' +
'<span class="basket-title">' + b.title + '</span>' +
'<span class="basket-sub">' + b.sub + '</span>' +
'</button>'
);
}).join("");
var feedbackHtml = state.solved
? '<div style="font-size:22px;font-weight:900;color:#047857;background:#D1FAE5;border:3px solid #34D399;padding:8px 20px;border-radius:16px">✅ ' + item.before + ' <u style="color:#1E40AF">' + item.answer.toUpperCase() + '</u> ' + item.after + '</div>'
: '<div style="font-size:16px;font-weight:800;color:#475569">👇 Tap the matching Verb Basket below to toss this subject bubble in!</div>';
stage.innerHTML =
renderThemeHeader(list) +
'<div class="sort-stage-wrap">' +
'<div class="mystery-bubble-card">' +
'<div style="font-size:13.5px;font-weight:900;color:#2563EB;text-transform:uppercase;letter-spacing:0.6px">🫧 Mystery Subject Bubble</div>' +
'<div class="subject-float-pill">' +
'<span class="sf-emoji">' + item.subjEmoji + '</span>' +
'<span class="sf-text">' + item.subject + '</span>' +
'</div>' +
'<div class="subj-badge ' + item.answer + '">' + item.category + '</div>' +
feedbackHtml +
'</div>' +
'<div class="baskets-row">' + basketsHtml + '</div>' +
'</div>';
}
function renderFlipMode(stage, item, list) {
var capAnswer = item.answer.charAt(0).toUpperCase() + item.answer.slice(1);
var slotClass = "verb-slot" + (state.solved ? (" filled " + item.answer) : "");
var slotText = state.solved ? capAnswer : "___";
var options = [
{ v: "am", word: "Am", hint: "👑 Am I...?" },
{ v: "is", word: "Is", hint: "🌟 Is he / she / it...?" },
{ v: "are", word: "Are", hint: "🎉 Are we / they / you...?" }
];
var btnsHtml = options.map(function(o) {
var isWrong = state.wrongGuesses.indexOf(o.v) !== -1;
var isCorrect = state.solved && item.answer === o.v;
var cls = "verb-choice-btn " + o.v + (isWrong ? " wrong" : "") + (isCorrect ? " correct" : "");
var dis = (isWrong || state.solved) ? " disabled" : "";
return (
'<button type="button" class="' + cls + '" data-pick="' + o.v + '"' + dis + '>' +
'<span class="vc-word">' + o.word + '...?</span>' +
'<span class="vc-hint">' + o.hint + '</span>' +
'</button>'
);
}).join("");
stage.innerHTML =
renderThemeHeader(list) +
'<div class="scene-card">' +
'<div style="font-size:14px;font-weight:900;color:#6D28D9;background:#F3E8FF;border:2px solid #C084FC;padding:5px 14px;border-radius:999px">' +
'🔄 Statement: <b>"' + item.before + ' <span style="color:#2563EB;text-decoration:underline">' + item.answer + '</span> ' + item.after + '"</b> → Flip the verb to the front!' +
'</div>' +
'<div class="sentence-bridge-box">' +
'<span class="' + slotClass + '">' + slotText + '</span>' +
'<span class="subj-highlight">' + item.flipSubj + '</span>' +
'<span>' + item.flipAfter + '</span>' +
'</div>' +
(state.solved
? '<div style="font-size:22px;font-weight:900;color:#065F46;background:#D1FAE5;border:3px solid #34D399;padding:6px 18px;border-radius:14px">🇬🇧 Short Answer: <b>"' + item.shortAns + '"</b> ' + item.emoji + '</div>'
: '<div style="font-size:14.5px;font-weight:800;color:#475569">💡 Which verb jumps to the very beginning to turn this into a question?</div>'
) +
'</div>' +
'<div class="choices-row">' + btnsHtml + '</div>';
}
function renderStage() {
var stage = document.getElementById("grammarStage");
if (!stage) return;
var list = getFilteredList();
var item = currentItem();
if (state.mode === "sort") {
renderSortMode(stage, item, list);
} else if (state.mode === "flip") {
renderFlipMode(stage, item, list);
} else {
renderBridgeMode(stage, item, list);
}
// Bind world filter chips
stage.querySelectorAll("[data-world]").forEach(function(btn) {
btn.addEventListener("click", function() {
state.worldFilter = btn.getAttribute("data-world");
state.qIdx = 0;
state.solved = false;
state.wrongGuesses = [];
playTone(520, 0.09, "sine");
renderAll();
});
});
// Bind verb pick buttons
stage.querySelectorAll("[data-pick]").forEach(function(btn) {
btn.addEventListener("click", function() {
handlePickVerb(btn.getAttribute("data-pick"));
});
});
updateRuleHighlight();
}
/* ============ 9. RENDER RIGHT 3-TEAM CUP CASTLE RACE ============ */
function renderRaceTowers() {
var grid = document.getElementById("towersGrid");
if (!grid) return;
var maxScore = Math.max.apply(null, state.teamScores);
var scaleMax = Math.max(8, maxScore + 1);
grid.innerHTML = TEAMS.map(function(t, idx) {
var score = state.teamScores[idx];
var isLeader = maxScore > 0 && score === maxScore;
var isTurn = state.activeTeam === idx;
var cupsHtml = "";
for (var c = 0; c < score; c++) {
var isTop = c === score - 1;
var hPct = Math.max(16, Math.floor(270 / scaleMax));
cupsHtml +=
'<div class="castle-cup" style="height:' + hPct + 'px;background:' + t.bg + ';border-color:' + t.bd + ';z-index:' + (c + 2) + '">' +
(scaleMax <= 18 ? (c + 1) : "") +
(isTop && isLeader ? '<span class="leader-crown">👑</span>' : "") +
'</div>';
}
return (
'<div class="team-column' + (isTurn ? ' active-turn' : '') + '" data-team-col="' + idx + '">' +
'<div class="team-top-pill" style="border-color:' + t.c + '">' +
'<div class="tt-name" style="color:' + t.c + '">' + t.icon + ' ' + t.name + '</div>' +
'<div class="tt-score-row">' +
'<span>🥤 ' + score + '</span>' +
'<button type="button" class="minus-cup-btn" data-minus="' + idx + '" title="Remove 1 cup">-</button>' +
'</div>' +
'</div>' +
'<div class="cup-stack-area">' +
'<div class="pedestal-base"></div>' +
cupsHtml +
'</div>' +
'<div class="team-tap-btns">' +
'<button type="button" class="tap-cup-btn" style="border-color:' + t.c + ';color:' + t.c + '" data-add="' + idx + '">✋ +1 Cup</button>' +
'</div>' +
'</div>'
);
}).join("");
// Bind column click to switch active team
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);
playTone(440, 0.08, "sine");
renderRaceTowers();
});
});
// Bind +1 cup button
grid.querySelectorAll("[data-add]").forEach(function(btn) {
btn.addEventListener("click", function(e) {
e.stopPropagation();
var ti = parseInt(btn.getAttribute("data-add"), 10);
state.teamScores[ti] += 1;
playTone(620, 0.12, "triangle");
playClip("praise_" + (ti + 1));
renderRaceTowers();
});
});
// Bind -1 cup button
grid.querySelectorAll("[data-minus]").forEach(function(btn) {
btn.addEventListener("click", function(e) {
e.stopPropagation();
var ti = parseInt(btn.getAttribute("data-minus"), 10);
state.teamScores[ti] = Math.max(0, state.teamScores[ti] - 1);
playTone(280, 0.09, "sine");
renderRaceTowers();
});
});
}
function renderAll() {
renderStage();
renderRaceTowers();
}
/* ============ 10. WIRE GLOBAL CONTROLS ============ */
document.querySelectorAll(".mode-tab").forEach(function(tab) {
tab.addEventListener("click", function() {
var m = tab.getAttribute("data-mode");
state.mode = m;
state.solved = false;
state.wrongGuesses = [];
document.querySelectorAll(".mode-tab").forEach(function(t) {
t.classList.toggle("active", t === tab);
});
if (m === "bridge") playClip("mode_bridge");
else if (m === "sort") playClip("mode_sort");
else if (m === "flip") playClip("mode_flip");
renderAll();
});
});
document.getElementById("ruleBtnAm").addEventListener("click", function() {
playClip("rule_am");
});
document.getElementById("ruleBtnIs").addEventListener("click", function() {
playClip("rule_is");
});
document.getElementById("ruleBtnAre").addEventListener("click", function() {
playClip("rule_are");
});
document.getElementById("hearClueBtn").addEventListener("click", function() {
var item = currentItem();
playClip(state.mode === "sort" ? ("sort_" + item.id) : ("q_" + item.id));
});
document.getElementById("hearSentenceBtn").addEventListener("click", function() {
var item = currentItem();
playClip(state.mode === "flip" ? ("flip_" + item.id) : ("ans_" + item.id));
});
document.getElementById("nextQuestionBtn").addEventListener("click", function() {
nextQuestion(true);
});
document.getElementById("repeatBtn").addEventListener("click", function() {
speakCurrentPrompt();
});
document.getElementById("resetBtn").addEventListener("click", function() {
state.teamScores = [0, 0, 0];
state.stars = 0;
state.streak = 0;
state.solved = false;
state.wrongGuesses = [];
document.getElementById("starCount").textContent = "0";
playTone(360, 0.12, "sine");
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 cups first to crown a champion!"
: names + " (" + max + " Cups!)";
document.getElementById("winnerModal").classList.add("open");
triggerFireworks();
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");
renderRaceTowers();
});
// Initial render
renderAll();
})();
</script>
</body>
</html>