Исходники пакета · Storybook Sequence Library

CC BY 4.0 · укажите @joe · v1

← Сведения об игре

Берите, делайте ремикс, учите с ним

Код ниже — только играемый входной HTML пакета. Скачайте полный ZIP, чтобы править или ремикшировать игру со всеми относительными ресурсами.

⬇ Скачать входной HTML ⬇ Скачать полный пакет
Заготовка подсказки для ИИ
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 &bull; NEXT &bull; THEN &bull; 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 + ' &bull; ' + 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 &amp; 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">&rarr;</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>