パッケージのソース · Weather and Date Chart

CC BY 4.0 · @joeのクレジットを残してください · v1

← ゲームの詳細

持っていく。リミックスする。授業で使う。

下のコードは、パッケージの遊べる入口HTMLだけです。相対パスの素材をすべて含めて編集またはリミックスするには、完全なZIPをダウンロードしてください。

⬇ 入口のHTMLをダウンロード ⬇ 完全なパッケージをダウンロード
AI編集プロンプトの下書き
この教室ゲームを電子黒板向けに編集してください。大きなタッチ対象と学級全体での遊びを保ってください。トラッキング、ログイン、個人情報の収集、遠隔APIの呼び出しは禁止です。下に表示された入口HTMLだけでなく、ダウンロードした完全なZIPを使い、ブラウザーで動くフォルダー構成を保ってください。教育目標は保ってください。元の制作者: ClassBoard Gamesの@joe(CC BY 4.0)。
入口HTMLのみ · index.html · 3939.4 KB
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
  <meta name="theme-color" content="#1d3557">
  <title>Weather and Date Chart 1.0</title>
  <style>
    :root {
      --navy: #1d3557;
      --navy-2: #274c77;
      --cream: #fffdf7;
      --paper: #fff9e8;
      --yellow: #ffc335;
      --orange: #ff7a2d;
      --coral: #ff595e;
      --coral-deep: #d62828;
      --sky: #4cc9f0;
      --sky-pale: #e0f7ff;
      --lime: #8ac926;
      --mint: #2ec4b6;
      --pink: #ff70a6;
      --purple: #8338ec;
      --ink-soft: #4a5d80;
      --shadow: 0 14px 32px rgba(29, 53, 87, .16);
      --font: "Trebuchet MS", "Nunito", "Century Gothic", system-ui, -apple-system, sans-serif;
      --header-h: 68px;
      --app-scale: 1;
    }

    * { box-sizing: border-box; }
    html, body {
      width: 100%;
      height: 100%;
      margin: 0;
      padding: 0;
      overflow: hidden;
      background:
        radial-gradient(circle at 18% 18%, rgba(255, 228, 94, .45), transparent 34%),
        radial-gradient(circle at 84% 16%, rgba(76, 201, 240, .38), transparent 38%),
        linear-gradient(160deg, #dff7ff 0%, #fffdf7 54%, #fff3c4 100%);
      color: var(--navy);
      font-family: var(--font);
      -webkit-tap-highlight-color: transparent;
    }

    body::before {
      content: "";
      position: fixed;
      z-index: 0;
      inset: 0;
      background-image:
        radial-gradient(circle at 24px 24px, rgba(255, 195, 53, .22) 4px, transparent 5px),
        radial-gradient(circle at 84px 68px, rgba(76, 201, 240, .22) 6px, transparent 7px);
      background-size: 120px 120px;
      pointer-events: none;
    }

    button, input, select { font: inherit; }
    button { color: inherit; }
    img { display: block; user-select: none; -webkit-user-drag: none; }
    button:focus-visible, input:focus-visible {
      outline: 4px solid #fff;
      outline-offset: 2px;
      box-shadow: 0 0 0 7px var(--navy);
    }
    .sr-only {
      position: absolute !important;
      width: 1px !important;
      height: 1px !important;
      padding: 0 !important;
      margin: -1px !important;
      overflow: hidden !important;
      clip: rect(0, 0, 0, 0) !important;
      white-space: nowrap !important;
      border: 0 !important;
    }
    [hidden] { display: none !important; }

    /* Fixed 1280x720 Virtual Canvas scaled proportionally to fit ANY screen size */
    #viewport {
      position: fixed;
      inset: 0;
      overflow: hidden;
      display: grid;
      place-items: center;
      z-index: 1;
    }
    #app {
      position: absolute;
      left: 50%;
      top: 50%;
      width: 1280px;
      height: 720px;
      transform: translate(-50%, -50%) scale(var(--app-scale, 1));
      transform-origin: center center;
      overflow: hidden;
      isolation: isolate;
      border: 5px solid rgba(255, 255, 255, .95);
      border-radius: 28px;
      background: var(--cream);
      box-shadow: 0 24px 64px rgba(29, 53, 87, .24), 0 6px 0 rgba(29, 53, 87, .12);
    }
    :fullscreen #app {
      border-radius: 18px;
    }

    .screen {
      position: absolute;
      inset: 0;
      display: none;
      overflow: hidden;
      background: var(--cream);
    }
    .screen.active { display: block; }

    /* Streamlined Teacher Strip (Repeat, Undo, Sound, Fullscreen only) */
    .teacher-bar {
      position: absolute;
      z-index: 100;
      left: 14px;
      right: 14px;
      top: 10px;
      height: var(--header-h);
      display: none;
      align-items: center;
      gap: 14px;
      padding: 8px 14px;
      border: 3px solid rgba(255, 255, 255, .92);
      border-radius: 22px;
      background: linear-gradient(90deg, rgba(29, 53, 87, .96), rgba(39, 76, 119, .96));
      color: #fff;
      box-shadow: 0 7px 0 rgba(29, 53, 87, .18), 0 12px 24px rgba(29, 53, 87, .18);
    }
    .teacher-bar.visible { display: flex; }
    .brand-menu {
      position: relative;
      flex: 0 0 auto;
    }
    .brand-menu-button {
      width: 144px;
      height: 48px;
      display: grid;
      place-items: center;
      padding: 2px 6px;
      border: 0;
      border-radius: 14px;
      cursor: pointer;
      background: rgba(255, 255, 255, .1);
      transition: transform .15s, background .15s;
    }
    .brand-menu-button:hover,
    .brand-menu-button[aria-expanded="true"] {
      background: rgba(255, 255, 255, .22);
      transform: translateY(-1px);
    }
    .bar-logo {
      width: 100%;
      height: 100%;
      object-fit: contain;
    }
    .brand-popover {
      position: absolute;
      left: 0;
      top: calc(100% + 10px);
      z-index: 130;
      min-width: 225px;
      display: grid;
      gap: 6px;
      padding: 8px;
      border: 3px solid #fff;
      border-radius: 18px;
      background: var(--navy);
      box-shadow: var(--shadow);
    }
    .brand-popover[hidden] { display: none !important; }
    .menu-nav-button {
      width: 100%;
      min-height: 42px;
      display: flex;
      align-items: center;
      gap: 10px;
      padding: 7px 12px;
      border: 0;
      border-radius: 12px;
      cursor: pointer;
      background: #fff;
      color: var(--navy);
      font-size: 15px;
      font-weight: 900;
      text-align: left;
      transition: background .15s, transform .15s;
    }
    .menu-nav-button:hover { background: #fff3b0; transform: translateX(3px); }

    .speaker-block {
      width: 180px;
      display: flex;
      flex-direction: column;
      gap: 2px;
    }
    .speaker-block strong { font-size: 13px; line-height: 1; color: #ffe45e; }
    .speaker-name {
      width: 100%;
      height: 30px;
      padding: 3px 9px;
      border: 0;
      border-radius: 8px;
      color: var(--navy);
      background: #fff;
      font-size: 14px;
      font-weight: 800;
    }
    .star-meter {
      flex: 1 1 220px;
      max-width: 300px;
      display: grid;
      grid-template-columns: auto 1fr auto;
      align-items: center;
      gap: 8px;
      font-weight: 900;
      font-size: 15px;
    }
    .meter-track {
      height: 16px;
      overflow: hidden;
      border: 2px solid rgba(255, 255, 255, .78);
      border-radius: 999px;
      background: rgba(255, 255, 255, .18);
    }
    .meter-fill {
      width: 0;
      height: 100%;
      border-radius: inherit;
      background: linear-gradient(90deg, var(--yellow), #fff37a, #8ac926);
      transition: width .5s cubic-bezier(.2, .8, .2, 1);
    }
    .teacher-tools {
      margin-left: auto;
      display: flex;
      gap: 8px;
    }
    .tool-button {
      height: 46px;
      padding: 0 14px;
      border: 0;
      border-radius: 14px;
      display: inline-flex;
      align-items: center;
      justify-content: center;
      gap: 7px;
      cursor: pointer;
      background: #fff;
      color: var(--navy);
      font-size: 18px;
      font-weight: 1000;
      box-shadow: inset 0 -3px 0 rgba(29, 53, 87, .12);
      transition: transform .15s, background .15s;
    }
    .tool-button:hover { background: #fff1a9; transform: translateY(-2px); }
    .tool-button:active { transform: translateY(2px); }
    .tool-button .tool-label { font-size: 12px; text-transform: uppercase; letter-spacing: .03em; }
    .tool-button.active { background: var(--yellow); }

    /* Shared Buttons & Labels */
    .eyebrow {
      display: inline-flex;
      align-items: center;
      gap: 8px;
      height: 32px;
      padding: 4px 13px;
      border-radius: 999px;
      background: var(--navy);
      color: #fff;
      font-size: 13px;
      font-weight: 1000;
      letter-spacing: .08em;
      text-transform: uppercase;
    }
    .eyebrow::before {
      content: "";
      width: 9px;
      height: 9px;
      border-radius: 50%;
      background: var(--yellow);
      box-shadow: 0 0 0 3px rgba(255, 195, 53, .26);
    }
    .primary-button, .secondary-button, .accent-button {
      min-height: 54px;
      padding: 10px 22px;
      border-radius: 18px;
      cursor: pointer;
      font-size: 21px;
      font-weight: 1000;
      transition: transform .15s, box-shadow .15s, filter .15s;
    }
    .primary-button {
      border: 3px solid #fff;
      color: var(--navy);
      background: linear-gradient(180deg, #fff37a, var(--yellow) 68%, #ffa823);
      box-shadow: 0 6px 0 #dd7527, 0 12px 24px rgba(68, 58, 28, .18);
    }
    .accent-button {
      border: 3px solid #fff;
      color: #fff;
      background: linear-gradient(180deg, #4cc9f0, #1982c4 72%, #1d3557);
      box-shadow: 0 6px 0 #144575, 0 12px 24px rgba(29, 53, 87, .18);
    }
    .secondary-button {
      border: 3px solid rgba(29, 53, 87, .16);
      color: var(--navy);
      background: #fff;
      box-shadow: 0 5px 0 rgba(29, 53, 87, .12);
    }
    .primary-button:hover, .secondary-button:hover, .accent-button:hover {
      transform: translateY(-2px);
      filter: saturate(1.08);
    }
    .primary-button:active, .secondary-button:active, .accent-button:active {
      transform: translateY(3px);
      box-shadow: 0 2px 0 rgba(29, 53, 87, .18);
    }

    /* TITLE SCREEN */
    #title-screen {
      background:
        radial-gradient(circle at 10% 14%, rgba(76, 201, 240, .36), transparent 27%),
        radial-gradient(circle at 92% 10%, rgba(255, 195, 53, .4), transparent 26%),
        linear-gradient(145deg, #fffdf7 0%, #fff8da 52%, #e0f7ff 100%);
    }
    .title-shell {
      height: 100%;
      display: grid;
      grid-template-columns: 560px 1fr;
      align-items: center;
      gap: 42px;
      padding: 38px 52px;
    }
    .title-copy { position: relative; z-index: 2; }
    .title-logo { width: 240px; height: auto; margin-bottom: 14px; }
    .title-copy h1 {
      margin: 0;
      font-size: 76px;
      line-height: .9;
      letter-spacing: -.045em;
      color: var(--navy);
    }
    .title-copy h1 span { display: block; color: var(--coral); }
    .title-kicker {
      margin: 16px 0 0;
      font-size: 21px;
      line-height: 1.3;
      color: var(--ink-soft);
      font-weight: 800;
    }
    .title-flow {
      margin: 18px 0 22px;
      display: flex;
      flex-wrap: wrap;
      align-items: center;
      gap: 7px;
      color: var(--navy);
      font-weight: 1000;
      font-size: 16px;
    }
    .title-flow span {
      padding: 6px 12px;
      border-radius: 999px;
      background: #fff;
      border: 2px solid rgba(29, 53, 87, .12);
      box-shadow: 0 3px 0 rgba(29, 53, 87, .08);
    }
    .title-flow i { color: var(--orange); font-style: normal; font-size: 1.3em; }
    .title-actions { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
    .campaign-line {
      margin: 18px 0 0;
      font-size: 16px;
      font-weight: 1000;
      color: var(--navy-2);
      letter-spacing: .025em;
    }
    .title-art {
      position: relative;
      height: 570px;
      border: 7px solid #fff;
      border-radius: 44px;
      overflow: hidden;
      background: #e0f7ff;
      box-shadow: 0 22px 58px rgba(29, 53, 87, .2), 0 8px 0 rgba(29, 53, 87, .1);
      transform: rotate(1deg);
    }
    .title-art img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
    .title-art::after {
      content: "☀️ OUR CLASS WEATHER STATION";
      position: absolute;
      right: 20px;
      bottom: 18px;
      padding: 9px 15px;
      border: 3px solid #fff;
      border-radius: 999px;
      color: #fff;
      background: rgba(29, 53, 87, .9);
      font-size: 14px;
      font-weight: 1000;
      letter-spacing: .06em;
    }

    /* PLAY SCREEN & STAGES */
    #play-screen { background: #dff7ff; }
    .stage {
      position: absolute;
      inset: 0;
      display: none;
      padding: calc(var(--header-h) + 20px) 28px 18px;
    }
    .stage.active { display: flex; flex-direction: column; animation: stage-in .28s ease both; }
    .stage-heading {
      position: relative;
      z-index: 4;
      text-align: center;
      flex: 0 0 auto;
    }
    .stage-heading h2 {
      margin: 3px 0 0;
      font-size: 40px;
      line-height: .98;
      letter-spacing: -.035em;
    }
    .stage-heading p {
      margin: 3px auto 0;
      font-size: 17px;
      color: var(--ink-soft);
      font-weight: 800;
    }

    /* STAGE 1: COMPACT DATE PANELS */
    #date-stage {
      background:
        radial-gradient(circle at 88% 15%, rgba(255, 195, 53, .28), transparent 28%),
        linear-gradient(145deg, #e0f7ff 0%, #fffdf7 55%, #fef9c3 100%);
    }
    .date-stage-shell {
      width: 1200px;
      margin: 10px auto 0;
      flex: 1;
      display: flex;
      flex-direction: column;
      justify-content: center;
      gap: 14px;
    }
    .date-quick-bar {
      display: flex;
      justify-content: center;
      align-items: center;
      gap: 10px;
      flex: 0 0 auto;
    }
    .quick-pill {
      height: 38px;
      padding: 0 16px;
      border: 3px solid #fff;
      border-radius: 999px;
      cursor: pointer;
      background: var(--navy);
      color: #fff;
      font-size: 15px;
      font-weight: 900;
      box-shadow: 0 4px 0 rgba(29, 53, 87, .15);
      transition: transform .15s, background .15s;
    }
    .quick-pill:hover { transform: translateY(-2px); background: var(--purple); }
    .quick-pill.today-pill { background: linear-gradient(90deg, #ff595e, #ff924c); }

    /* Compact, non-stretched 4-column date cards */
    .date-cards-grid {
      display: grid;
      grid-template-columns: 1.15fr 1.25fr 1.15fr .85fr;
      gap: 14px;
      align-items: start;
      flex: 0 0 auto;
    }
    .date-column-card {
      display: flex;
      flex-direction: column;
      gap: 10px;
      padding: 12px 14px;
      border: 4px solid #fff;
      border-radius: 24px;
      background: rgba(255, 255, 255, .94);
      box-shadow: 0 8px 0 rgba(29, 53, 87, .09), var(--shadow);
    }
    .date-column-card.card-day { background: linear-gradient(180deg, #fff0f5, #ffffff); border-color: #ff8fab; }
    .date-column-card.card-month { background: linear-gradient(180deg, #e8f8f0, #ffffff); border-color: #52b788; }
    .date-column-card.card-date { background: linear-gradient(180deg, #fff8d6, #ffffff); border-color: #ffb703; }
    .date-column-card.card-year { background: linear-gradient(180deg, #f3e8ff, #ffffff); border-color: #a855f7; }

    .col-header {
      display: flex;
      align-items: center;
      justify-content: space-between;
      font-weight: 1000;
      font-size: 13px;
      text-transform: uppercase;
      letter-spacing: .05em;
      color: var(--ink-soft);
    }
    .spinner-box {
      display: grid;
      grid-template-columns: 38px 1fr 38px;
      align-items: center;
      gap: 4px;
      padding: 6px;
      border-radius: 16px;
      background: #fff;
      border: 3px solid rgba(29, 53, 87, .12);
      box-shadow: inset 0 2px 5px rgba(29, 53, 87, .05);
    }
    .spin-arrow {
      width: 36px;
      height: 36px;
      border: 0;
      border-radius: 11px;
      cursor: pointer;
      background: var(--sky-pale);
      color: var(--navy);
      font-size: 16px;
      font-weight: 1000;
      transition: background .15s, transform .15s;
    }
    .spin-arrow:hover { background: var(--yellow); transform: scale(1.06); }
    .spin-value {
      text-align: center;
      font-size: 24px;
      font-weight: 1000;
      line-height: 1.05;
      color: var(--navy);
      user-select: none;
      cursor: pointer;
    }
    .ordinal-badge {
      display: inline-block;
      margin-left: 2px;
      padding: 2px 6px;
      border-radius: 8px;
      background: var(--coral);
      color: #fff;
      font-size: .6em;
      vertical-align: super;
    }
    .chip-grid {
      display: flex;
      flex-wrap: wrap;
      gap: 5px;
      justify-content: center;
      max-height: 112px;
      overflow-y: auto;
      padding: 2px;
    }
    .chip-btn {
      padding: 5px 8px;
      border: 2px solid rgba(29, 53, 87, .14);
      border-radius: 10px;
      cursor: pointer;
      background: #fff;
      color: var(--navy);
      font-size: 13px;
      font-weight: 900;
      transition: transform .12s, background .12s, border-color .12s;
    }
    .chip-btn:hover { transform: translateY(-1px); border-color: var(--navy); background: #fff9db; }
    .chip-btn.selected {
      background: var(--navy);
      color: #fff;
      border-color: var(--navy);
      box-shadow: 0 2px 0 rgba(29, 53, 87, .22);
    }

    .date-preview-bar {
      display: flex;
      align-items: center;
      justify-content: space-between;
      gap: 14px;
      padding: 10px 18px;
      border: 4px solid #fff;
      border-radius: 22px;
      background: rgba(255, 255, 255, .96);
      box-shadow: 0 8px 0 rgba(29, 53, 87, .1), var(--shadow);
      flex: 0 0 auto;
    }
    .date-preview-sentence {
      display: flex;
      align-items: center;
      gap: 10px;
      font-size: 25px;
      font-weight: 1000;
      color: var(--navy);
      cursor: pointer;
    }

    /* STAGE 2: WEATHER CONCEPT MAP */
    #weather-stage {
      background:
        radial-gradient(circle at 50% 48%, rgba(255, 253, 247, .9) 0 20%, transparent 21%),
        linear-gradient(145deg, rgba(224, 247, 255, .92), rgba(255, 248, 216, .92) 62%, rgba(232, 248, 240, .92));
    }
    .concept-map {
      position: relative;
      z-index: 1;
      width: 1180px;
      flex: 1;
      min-height: 0;
      margin: 10px auto 46px;
      display: grid;
      grid-template-columns: repeat(3, 1fr);
      grid-template-rows: repeat(2, 1fr);
      gap: 16px;
    }
    .place-node {
      position: relative;
      z-index: 2;
      width: 100%;
      height: 100%;
      padding: 0;
      overflow: hidden;
      border: 5px solid #fff;
      border-radius: 24px;
      cursor: pointer;
      background: #fff;
      box-shadow: 0 9px 0 rgba(29, 53, 87, .12), var(--shadow);
      display: flex;
      flex-direction: column;
      transition: transform .18s, box-shadow .18s, border-color .18s;
    }
    .place-node:hover { transform: translateY(-4px); border-color: var(--yellow); }
    .place-node:active { transform: translateY(2px); }
    .place-node img {
      width: 100%;
      flex: 1;
      min-height: 0;
      object-fit: cover;
      object-position: center;
    }
    .place-node .node-label {
      width: 100%;
      height: 48px;
      display: flex;
      align-items: center;
      justify-content: space-between;
      gap: 8px;
      padding: 6px 14px;
      border-top: 3px solid rgba(255, 255, 255, .9);
      color: #fff;
      background: rgba(29, 53, 87, .92);
      font-size: 20px;
      font-weight: 1000;
    }
    .node-label b {
      width: 30px;
      height: 30px;
      display: grid;
      place-items: center;
      border-radius: 9px;
      background: var(--number-color, var(--yellow));
      color: var(--navy);
      font-size: 15px;
    }
    .weather-bottom-strip {
      position: absolute;
      left: 50%;
      bottom: 12px;
      transform: translateX(-50%);
      display: flex;
      gap: 12px;
      z-index: 4;
    }
    .other-place {
      height: 40px;
      padding: 0 18px;
      border: 3px solid var(--navy);
      border-radius: 15px;
      cursor: pointer;
      color: var(--navy);
      background: #fff;
      box-shadow: 0 5px 0 rgba(29, 53, 87, .14);
      font-size: 15px;
      font-weight: 1000;
    }
    .other-place:hover { background: #fff3b0; }

    /* STAGE 3 & 4: TEMPERATURE & SPEAK */
    #temp-stage, #speak-stage { background: #fffaf0; }
    .scene-art {
      position: absolute;
      inset: calc(var(--header-h) + 12px) 0 0 0;
      width: 100%;
      height: calc(100% - var(--header-h) - 12px);
      object-fit: cover;
      object-position: center;
    }
    .scene-vignette {
      position: absolute;
      inset: calc(var(--header-h) + 12px) 0 0;
      background: linear-gradient(90deg, rgba(255, 253, 247, .97) 0%, rgba(255, 253, 247, .94) 48%, rgba(255, 253, 247, .38) 72%, rgba(255, 253, 247, .12) 100%);
      pointer-events: none;
    }
    .action-panel, .speech-panel {
      position: relative;
      z-index: 3;
      width: 660px;
      flex: 1;
      margin-left: 36px;
      display: flex;
      flex-direction: column;
      justify-content: center;
    }
    .action-panel h2, .speech-panel h2 {
      margin: 6px 0 4px;
      font-size: 48px;
      line-height: .94;
      letter-spacing: -.04em;
    }
    .action-panel > .eyebrow,
    .speech-panel > .eyebrow,
    .chart-sidebar > .eyebrow { align-self: flex-start; }
    .action-panel > p, .speech-panel > p {
      margin: 0 0 10px;
      font-size: 18px;
      color: var(--ink-soft);
      font-weight: 800;
    }

    /* Interactive Thermometer Widget */
    .therm-widget {
      display: flex;
      align-items: center;
      gap: 10px;
      padding: 8px 14px;
      margin-bottom: 12px;
      border: 4px solid #fff;
      border-radius: 999px;
      background: rgba(255, 255, 255, .96);
      box-shadow: 0 7px 0 rgba(29, 53, 87, .12), var(--shadow);
      user-select: none;
      touch-action: none;
    }
    .therm-step-btn {
      width: 38px;
      height: 38px;
      border: 0;
      border-radius: 50%;
      cursor: pointer;
      background: var(--sky-pale);
      color: var(--navy);
      font-size: 20px;
      font-weight: 1000;
    }
    .therm-step-btn:hover { background: var(--yellow); }
    .therm-bar-shell {
      flex: 1;
      display: flex;
      align-items: center;
      cursor: grab;
    }
    .therm-bar-shell:active { cursor: grabbing; }
    .therm-bulb {
      width: 44px;
      height: 44px;
      border-radius: 50%;
      border: 4px solid #cbd5e1;
      background: #fbbf24;
      z-index: 2;
      display: grid;
      place-items: center;
      font-size: 22px;
      transition: background .3s;
    }
    .therm-tube {
      flex: 1;
      height: 22px;
      margin-left: -10px;
      border: 4px solid #cbd5e1;
      border-left: 0;
      border-radius: 0 999px 999px 0;
      background: #f1f5f9;
      overflow: hidden;
      position: relative;
    }
    .therm-fill {
      height: 100%;
      width: 55%;
      background: linear-gradient(90deg, #38bdf8, #fbbf24);
      border-radius: 0 999px 999px 0;
      transition: width .3s ease, background .3s ease;
    }
    .therm-readout {
      min-width: 125px;
      text-align: right;
      font-size: 20px;
      font-weight: 1000;
    }

    .action-choices {
      display: grid;
      grid-template-columns: repeat(2, 1fr);
      gap: 9px;
    }
    .action-choice {
      width: 100%;
      height: 60px;
      display: grid;
      grid-template-columns: 44px 1fr 30px;
      align-items: center;
      gap: 10px;
      padding: 6px 12px 6px 8px;
      border: 4px solid #fff;
      border-radius: 18px;
      cursor: pointer;
      background: rgba(255, 255, 255, .95);
      color: var(--navy);
      box-shadow: 0 5px 0 rgba(29, 53, 87, .11), 0 8px 16px rgba(29, 53, 87, .08);
      text-align: left;
      font-size: 19px;
      font-weight: 1000;
      transition: transform .15s, border-color .15s;
    }
    .action-choice:hover, .action-choice.selected {
      transform: translateY(-2px);
      border-color: var(--yellow);
      background: #fffef5;
    }
    .action-choice .action-icon {
      width: 42px;
      height: 42px;
      display: grid;
      place-items: center;
      border-radius: 12px;
      background: var(--choice-color, var(--sky-pale));
      font-size: 24px;
    }
    .action-choice .keycap {
      width: 28px;
      height: 28px;
      display: grid;
      place-items: center;
      border: 2px solid rgba(29, 53, 87, .15);
      border-radius: 8px;
      background: var(--key-color, #fff);
      font-size: 13px;
    }
    .back-link {
      align-self: flex-start;
      margin-top: 8px;
      padding: 6px 4px;
      border: 0;
      cursor: pointer;
      color: var(--navy-2);
      background: transparent;
      font-size: 16px;
      font-weight: 1000;
      text-decoration: underline;
      text-underline-offset: 4px;
    }

    /* STAGE 4: SPEAK STAGE */
    .speech-panel { width: 700px; }
    .speech-bubble {
      position: relative;
      margin-top: 8px;
      padding: 16px 22px;
      border: 5px solid var(--navy);
      border-radius: 28px;
      background: rgba(255, 255, 255, .97);
      box-shadow: 0 10px 0 rgba(29, 53, 87, .13), var(--shadow);
    }
    .sentence-row {
      display: grid;
      grid-template-columns: 40px 1fr 46px;
      align-items: center;
      gap: 12px;
      min-height: 64px;
      padding: 6px 0;
      border-bottom: 3px dashed rgba(29, 53, 87, .14);
      font-size: 26px;
      line-height: 1.15;
      font-weight: 1000;
    }
    .sentence-row:last-child { border-bottom: 0; }
    .sentence-number {
      width: 36px;
      height: 36px;
      display: grid;
      place-items: center;
      border-radius: 11px;
      color: #fff;
      background: var(--coral);
      font-size: 18px;
    }
    .speak-one {
      width: 44px;
      height: 44px;
      border: 0;
      border-radius: 13px;
      cursor: pointer;
      background: var(--sky-pale);
      font-size: 20px;
      transition: transform .15s, background .15s;
    }
    .speak-one:hover { background: var(--yellow); transform: scale(1.08); }
    .mascot-advice-pill {
      margin: 10px 0;
      padding: 9px 15px;
      border-radius: 16px;
      background: #fff3b0;
      border: 3px solid #ffb703;
      display: flex;
      align-items: center;
      justify-content: space-between;
      gap: 10px;
      font-size: 17px;
      font-weight: 900;
      color: var(--navy);
    }
    .speech-actions { display: flex; flex-wrap: wrap; gap: 12px; margin-top: 4px; }

    .mascot-stage-card {
      position: absolute;
      right: 42px;
      bottom: 36px;
      z-index: 3;
      width: 310px;
      padding: 16px 18px;
      border: 5px solid #fff;
      border-radius: 28px;
      background: rgba(255, 255, 255, .94);
      box-shadow: var(--shadow);
      text-align: center;
    }
    .mascot-avatar {
      font-size: 64px;
      line-height: 1.05;
      margin-bottom: 6px;
    }
    .mascot-stage-card strong {
      display: block;
      font-size: 20px;
      color: var(--navy);
    }
    .mascot-stage-card span {
      display: block;
      margin-top: 4px;
      font-size: 15px;
      color: var(--ink-soft);
      font-weight: 800;
    }

    /* STAGE 5: CLASS CHART + SLOT BOARD */
    #chart-stage {
      background:
        radial-gradient(circle at 5% 8%, rgba(255, 195, 53, .25), transparent 24%),
        linear-gradient(145deg, #dff7ff, #fff9e1 70%);
    }
    .chart-layout {
      flex: 1;
      min-height: 0;
      display: grid;
      grid-template-columns: 1.45fr .65fr;
      gap: 20px;
      padding-top: 4px;
    }
    .map-visual {
      position: relative;
      min-width: 0;
      overflow: hidden;
      border: 5px solid #fff;
      border-radius: 32px;
      background: #e0f7ff;
      box-shadow: var(--shadow);
      display: flex;
      flex-direction: column;
    }
    .board-mode-tabs {
      position: absolute;
      top: 12px;
      right: 14px;
      z-index: 10;
      display: flex;
      gap: 6px;
      background: rgba(29, 53, 87, .9);
      padding: 4px;
      border-radius: 999px;
      border: 3px solid #fff;
    }
    .board-tab {
      padding: 6px 13px;
      border: 0;
      border-radius: 999px;
      cursor: pointer;
      background: transparent;
      color: #fff;
      font-size: 13px;
      font-weight: 900;
    }
    .board-tab.active {
      background: var(--yellow);
      color: var(--navy);
    }

    .visual-pane {
      position: relative;
      flex: 1;
      width: 100%;
      height: 100%;
      overflow: hidden;
    }
    .visual-pane > img { width: 100%; height: 100%; object-fit: cover; }
    .map-title {
      position: absolute;
      left: 16px;
      top: 12px;
      z-index: 2;
      max-width: 56%;
      padding: 9px 15px;
      border: 3px solid #fff;
      border-radius: 18px;
      color: #fff;
      background: rgba(29, 53, 87, .92);
      box-shadow: 0 5px 0 rgba(29, 53, 87, .16);
    }
    .map-title strong { display: block; font-size: 22px; }
    .map-title span { display: block; margin-top: 2px; font-size: 13px; opacity: .9; font-weight: 800; }

    .weather-tally-grid {
      position: absolute;
      left: 16px;
      right: 16px;
      bottom: 14px;
      z-index: 4;
      display: grid;
      grid-template-columns: repeat(6, 1fr);
      gap: 8px;
    }
    .tally-card {
      padding: 7px 5px;
      border: 3px solid #fff;
      border-radius: 16px;
      background: rgba(255, 255, 255, .94);
      text-align: center;
      box-shadow: 0 5px 0 rgba(29, 53, 87, .14);
      font-weight: 1000;
    }
    .tally-card .tally-icon { font-size: 24px; display: block; }
    .tally-card .tally-name { font-size: 11px; color: var(--ink-soft); display: block; }
    .tally-card .tally-num {
      display: inline-block;
      margin-top: 2px;
      min-width: 28px;
      padding: 2px 7px;
      border-radius: 999px;
      background: var(--yellow);
      color: var(--navy);
      font-size: 15px;
    }

    .total-stories {
      position: absolute;
      left: 50%;
      top: 44%;
      z-index: 3;
      transform: translate(-50%, -50%);
      min-width: 155px;
      padding: 9px 15px;
      border: 4px solid #fff;
      border-radius: 22px;
      color: #fff;
      background: rgba(29, 53, 87, .92);
      text-align: center;
      box-shadow: var(--shadow);
      font-weight: 1000;
    }
    .total-stories b { display: block; color: var(--yellow); font-size: 32px; line-height: 1; }
    .map-star {
      position: absolute;
      z-index: 4;
      color: var(--yellow);
      font-size: 28px;
      filter: drop-shadow(0 3px 0 #ff7a2d);
      animation: star-arrive .55s cubic-bezier(.1, .9, .15, 1.3) both;
      pointer-events: none;
    }

    .slot-board-pane {
      display: flex;
      flex-direction: column;
      justify-content: center;
      align-items: center;
      gap: 14px;
      padding: 64px 20px 20px;
      background:
        radial-gradient(circle at 85% 18%, rgba(255, 223, 108, .45), transparent 28%),
        linear-gradient(180deg, #cde6f7 0%, #e8f7ff 65%, #d8f3dc 100%);
    }
    .slot-sentence-card {
      width: 100%;
      padding: 18px 16px;
      border: 5px solid #fff;
      border-radius: 26px;
      background: rgba(255, 255, 255, .92);
      box-shadow: var(--shadow);
      display: flex;
      flex-direction: column;
      align-items: center;
      gap: 14px;
      text-align: center;
    }
    .slot-line {
      display: flex;
      flex-wrap: wrap;
      align-items: center;
      justify-content: center;
      gap: 8px;
      font-size: 26px;
      font-weight: 1000;
      color: var(--navy);
    }
    .drum-pill {
      display: inline-flex;
      align-items: center;
      gap: 5px;
      padding: 5px 10px;
      border-radius: 999px;
      border: 4px solid var(--drum-border, #38bdf8);
      background: var(--drum-bg, #e0f2fe);
      color: var(--drum-text, #0369a1);
      box-shadow: 0 4px 0 rgba(29, 53, 87, .12);
      cursor: pointer;
      user-select: none;
    }
    .drum-pill button {
      width: 26px;
      height: 26px;
      border: 0;
      border-radius: 50%;
      background: rgba(255, 255, 255, .82);
      cursor: pointer;
      font-weight: 1000;
      font-size: 13px;
    }
    .drum-pill button:hover { background: var(--yellow); }

    .chart-sidebar {
      min-width: 0;
      overflow: hidden;
      min-height: 0;
      display: flex;
      flex-direction: column;
      padding: 16px 20px;
      border: 5px solid #fff;
      border-radius: 32px;
      background: rgba(255, 253, 247, .96);
      box-shadow: var(--shadow);
    }
    .chart-sidebar h2 {
      margin: 6px 0 4px;
      font-size: 32px;
      line-height: .96;
      letter-spacing: -.035em;
    }
    .last-story {
      margin: 8px 0 10px;
      padding: 10px 12px;
      border-left: 7px solid var(--coral);
      border-radius: 14px;
      background: #fff1e7;
      font-size: 15px;
      line-height: 1.3;
      font-weight: 900;
    }
    .reaction-title { margin: 6px 0 6px; font-size: 14px; font-weight: 1000; }
    .reactions { display: grid; grid-template-columns: repeat(3, 1fr); gap: 6px; }
    .reaction-button {
      min-height: 48px;
      padding: 5px 4px;
      border: 3px solid rgba(29, 53, 87, .1);
      border-radius: 14px;
      cursor: pointer;
      background: #fff;
      font-size: 14px;
      font-weight: 1000;
      transition: transform .15s, background .15s;
    }
    .reaction-button:hover { transform: translateY(-2px); background: #fff1a9; }
    .reaction-button b { display: block; margin-top: 2px; color: var(--coral-deep); }
    .chart-actions { margin-top: auto; display: grid; grid-template-columns: 1fr 1fr; gap: 6px; padding-top: 8px; }
    .chart-actions .primary-button, .chart-actions .secondary-button {
      width: 100%;
      min-height: 42px;
      padding: 6px 10px;
      font-size: 14px;
      line-height: 1.05;
    }
    .class-progress-card {
      grid-column: 1 / -1;
      margin: 2px 0;
      padding: 8px 11px;
      border: 3px solid rgba(29, 53, 87, .1);
      border-radius: 14px;
      background: #eefaff;
      color: var(--navy);
      font-weight: 1000;
    }
    .class-progress-card strong { display: block; font-size: 15px; }
    .class-progress-card span { display: block; margin-top: 2px; color: var(--ink-soft); font-size: 12px; }
    .previous-results-button { grid-column: 1 / -1; }

    /* Dialog, Toast, Effects */
    dialog {
      width: 520px;
      padding: 0;
      overflow: hidden;
      border: 5px solid #fff;
      border-radius: 28px;
      color: var(--navy);
      background: var(--cream);
      box-shadow: 0 24px 70px rgba(0, 0, 0, .35);
    }
    dialog::backdrop {
      background-color: rgba(29, 53, 87, .68);
      backdrop-filter: blur(5px);
    }
    .dialog-shell { padding: 24px; }
    .dialog-shell h2 { margin: 8px 0 6px; font-size: 30px; line-height: 1; }
    .dialog-shell p { margin: 0 0 14px; color: var(--ink-soft); font-weight: 800; }
    .field { display: grid; gap: 6px; margin: 10px 0; font-weight: 1000; }
    .field input {
      width: 100%;
      height: 48px;
      padding: 8px 12px;
      border: 3px solid rgba(29, 53, 87, .18);
      border-radius: 14px;
      color: var(--navy);
      background: #fff;
      font-size: 18px;
      font-weight: 800;
    }
    .dialog-actions { display: flex; justify-content: flex-end; gap: 10px; margin-top: 16px; }
    .dialog-actions button { height: 44px; padding: 0 16px; border-radius: 13px; cursor: pointer; font-weight: 1000; }
    .cancel-dialog { border: 2px solid rgba(29, 53, 87, .15); background: #fff; }
    .save-dialog { border: 2px solid #fff; background: var(--yellow); box-shadow: 0 4px 0 #dd7527; }

    .toast {
      position: absolute;
      z-index: 150;
      left: 50%;
      bottom: 18px;
      transform: translate(-50%, 30px);
      max-width: 620px;
      padding: 10px 18px;
      border: 3px solid #fff;
      border-radius: 999px;
      color: #fff;
      background: rgba(29, 53, 87, .95);
      box-shadow: var(--shadow);
      font-size: 16px;
      font-weight: 1000;
      opacity: 0;
      pointer-events: none;
      transition: opacity .2s, transform .2s;
    }
    .toast.show { opacity: 1; transform: translate(-50%, 0); }
    .effect-layer { position: absolute; z-index: 140; inset: 0; overflow: hidden; pointer-events: none; }
    .burst-star {
      position: absolute;
      left: 50%;
      top: 52%;
      color: var(--yellow);
      font-size: var(--size);
      text-shadow: 0 3px 0 var(--orange);
      animation: burst 1.2s cubic-bezier(.12, .7, .15, 1) both;
    }

    @keyframes stage-in { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: none; } }
    @keyframes star-arrive { from { opacity: 0; transform: translate(-60px, 50px) scale(.1) rotate(-180deg); } to { opacity: 1; transform: none; } }
    @keyframes burst { from { opacity: 1; transform: translate(-50%, -50%) scale(.3) rotate(0); } to { opacity: 0; transform: translate(calc(-50% + var(--x)), calc(-50% + var(--y))) scale(1.2) rotate(var(--r)); } }
  </style>
</head>
<body>
  <div id="viewport">
    <div id="app">
      <!-- STREAMLINED TEACHER BAR: Repeat, Undo, Sound, Fullscreen only -->
      <header class="teacher-bar" id="teacher-bar" aria-label="Teacher controls">
        <div class="brand-menu">
          <button class="brand-menu-button" id="brand-menu-button" type="button" aria-label="Open navigation menu" aria-expanded="false" aria-controls="brand-popover">
            <img class="bar-logo" src="assets/sun-badge.svg" alt="Weather and Date Club">
          </button>
          <div class="brand-popover" id="brand-popover" hidden>
            <button class="menu-nav-button" id="home-button" type="button"><span aria-hidden="true">🏠</span> Title screen</button>
            <button class="menu-nav-button" id="menu-guided-button" type="button"><span aria-hidden="true">📅</span> Step-by-step turn</button>
            <button class="menu-nav-button" id="menu-slot-button" type="button"><span aria-hidden="true">🎰</span> Quick Slot Board</button>
            <button class="menu-nav-button" id="menu-chart-button" type="button"><span aria-hidden="true">📊</span> Class Weather Chart</button>
          </div>
        </div>
        <div class="speaker-block">
          <strong id="speaker-label">Reporter 1 of 30</strong>
          <label class="sr-only" for="speaker-name">Student name, optional</label>
          <input class="speaker-name" id="speaker-name" type="text" maxlength="18" placeholder="Helper name (optional)" autocomplete="off">
        </div>
        <div class="star-meter" aria-label="Class star progress">
          <span aria-hidden="true">⭐</span>
          <div class="meter-track"><div class="meter-fill" id="meter-fill"></div></div>
          <span id="meter-label">0 / 30</span>
        </div>
        <nav class="teacher-tools" aria-label="Game tools">
          <button class="tool-button" id="repeat-button" type="button" title="Repeat voice (R)" aria-label="Repeat voice"><span aria-hidden="true">🔊</span><span class="tool-label">Repeat</span></button>
          <button class="tool-button" id="undo-button" type="button" title="Undo last report (U)" aria-label="Undo last report"><span aria-hidden="true">↶</span><span class="tool-label">Undo</span></button>
          <button class="tool-button active" id="sound-button" type="button" title="Voice & sound on or off (M)" aria-pressed="true"><span id="sound-icon" aria-hidden="true">♪</span><span class="tool-label">Sound</span></button>
          <button class="tool-button" id="fullscreen-button" type="button" title="Fullscreen (F)" aria-label="Toggle fullscreen"><span aria-hidden="true">⛶</span><span class="tool-label">Full</span></button>
        </nav>
      </header>

      <main>
        <!-- TITLE SCREEN -->
        <section class="screen active" id="title-screen" aria-labelledby="game-title">
          <div class="title-shell">
            <div class="title-copy">
              <img class="title-logo" src="assets/sun-badge.svg" alt="Sunny Sky Weather and Date Club">
              <h1 id="game-title">Weather &amp; Date <span>Chart 1.0</span></h1>
              <p class="title-kicker">Check today’s calendar, look out the window at the sky, and share our daily weather report with Neural Jenny!</p>
              <div class="title-flow" aria-label="Pick the date, choose weather, feel temperature, and speak">
                <span>📅 1. Date</span><i aria-hidden="true">›</i><span>☀️ 2. Weather</span><i aria-hidden="true">›</i><span>🌡️ 3. Temp</span><i aria-hidden="true">›</i><span>⭐ 4. Speak!</span>
              </div>
              <div class="title-actions">
                <button class="primary-button" id="start-button" type="button">Start Weather Chart</button>
                <button class="accent-button" id="quick-board-button" type="button">🎰 Quick Slot Board</button>
                <button class="secondary-button" id="new-class-button" type="button" hidden>Start a new class</button>
              </div>
              <p class="campaign-line">Every Day Is a Great Day to Learn &amp; Talk Together!</p>
            </div>
            <div class="title-art" aria-hidden="true">
              <img src="assets/weather-world.svg" alt="">
            </div>
          </div>
        </section>

        <!-- PLAY SCREEN -->
        <section class="screen" id="play-screen" aria-label="Weather and Date Chart activity">
          <!-- STAGE 1: COMPACT DATE PANELS -->
          <section class="stage active" id="date-stage" aria-labelledby="date-title">
            <div class="stage-heading">
              <span class="eyebrow">Step 1 · Calendar Helper</span>
              <h2 id="date-title">What is the date today?</h2>
              <p>Tap any day, month, date, or year to hear Jenny say it!</p>
            </div>
            <div class="date-stage-shell">
              <div class="date-quick-bar">
                <button class="quick-pill today-pill" id="use-today-button" type="button">✨ Use Today’s Real Date</button>
                <button class="quick-pill" id="random-date-button" type="button">🎲 Mystery Practice Date</button>
                <button class="quick-pill" id="speak-date-preview-btn" type="button">🔊 Hear Full Sentence</button>
              </div>

              <div class="date-cards-grid">
                <!-- Day of Week -->
                <div class="date-column-card card-day">
                  <div class="col-header"><span>📅 Day of Week</span><span>1–7</span></div>
                  <div class="spinner-box">
                    <button class="spin-arrow" type="button" data-spin="day" data-dir="-1" aria-label="Previous day">◀</button>
                    <div class="spin-value" id="display-day" title="Click to hear day">Sunday</div>
                    <button class="spin-arrow" type="button" data-spin="day" data-dir="1" aria-label="Next day">▶</button>
                  </div>
                  <div class="chip-grid" id="chips-day"></div>
                </div>

                <!-- Month -->
                <div class="date-column-card card-month">
                  <div class="col-header"><span>🌸 Month</span><span>Jan–Dec</span></div>
                  <div class="spinner-box">
                    <button class="spin-arrow" type="button" data-spin="month" data-dir="-1" aria-label="Previous month">◀</button>
                    <div class="spin-value" id="display-month" title="Click to hear month">September</div>
                    <button class="spin-arrow" type="button" data-spin="month" data-dir="1" aria-label="Next month">▶</button>
                  </div>
                  <div class="chip-grid" id="chips-month"></div>
                </div>

                <!-- Date + Ordinal -->
                <div class="date-column-card card-date">
                  <div class="col-header"><span>🔢 Date &amp; Ordinal</span><span>st/nd/rd/th</span></div>
                  <div class="spinner-box">
                    <button class="spin-arrow" type="button" data-spin="date" data-dir="-1" aria-label="Previous date">◀</button>
                    <div class="spin-value" id="display-date-wrap" title="Click to hear ordinal"><span id="display-date">27</span><span class="ordinal-badge" id="display-ordinal">th</span></div>
                    <button class="spin-arrow" type="button" data-spin="date" data-dir="1" aria-label="Next date">▶</button>
                  </div>
                  <div class="chip-grid" id="chips-date"></div>
                </div>

                <!-- Year -->
                <div class="date-column-card card-year">
                  <div class="col-header"><span>🎉 Year</span><span>Calendar</span></div>
                  <div class="spinner-box">
                    <button class="spin-arrow" type="button" data-spin="year" data-dir="-1" aria-label="Previous year">◀</button>
                    <div class="spin-value" id="display-year" title="Click to hear year">2026</div>
                    <button class="spin-arrow" type="button" data-spin="year" data-dir="1" aria-label="Next year">▶</button>
                  </div>
                  <div class="chip-grid" id="chips-year"></div>
                </div>
              </div>

              <div class="date-preview-bar">
                <div class="date-preview-sentence" id="date-sentence-bar-click" title="Click to hear Jenny read the sentence">
                  <span aria-hidden="true">🔊</span>
                  <span id="live-date-sentence">Today is Sunday, September 27th, 2026.</span>
                </div>
                <button class="primary-button" id="to-weather-button" type="button">Next: How is the weather? ☀️ →</button>
              </div>
            </div>
          </section>

          <!-- STAGE 2: WEATHER SKY -->
          <section class="stage" id="weather-stage" aria-labelledby="weather-title">
            <div class="stage-heading">
              <span class="eyebrow">Step 2 · Look at the Sky</span>
              <h2 id="weather-title">How is the weather today?</h2>
              <p>Choose the sky picture that matches outside.</p>
            </div>
            <div class="concept-map" id="concept-map">
              <button class="place-node numbered-choice" type="button" data-weather="sunny" data-key="1">
                <img src="assets/sunny.svg" alt="Sunny sky with smiling sun">
                <span class="node-label"><span>☀️ Sunny</span><b style="--number-color:#ffc335">1</b></span>
              </button>
              <button class="place-node numbered-choice" type="button" data-weather="partly-cloudy" data-key="2">
                <img src="assets/partly-cloudy.svg" alt="Partly cloudy sky with sun peeking out">
                <span class="node-label"><span>⛅ Partly Cloudy</span><b style="--number-color:#71cbff">2</b></span>
              </button>
              <button class="place-node numbered-choice" type="button" data-weather="cloudy" data-key="3">
                <img src="assets/cloudy.svg" alt="Cozy white and gray clouds">
                <span class="node-label"><span>☁️ Cloudy</span><b style="--number-color:#cbd5e1">3</b></span>
              </button>
              <button class="place-node numbered-choice" type="button" data-weather="rainy" data-key="4">
                <img src="assets/rainy.svg" alt="Raindrops falling with a red umbrella">
                <span class="node-label"><span>🌧️ Rainy</span><b style="--number-color:#38bdf8">4</b></span>
              </button>
              <button class="place-node numbered-choice" type="button" data-weather="windy" data-key="5">
                <img src="assets/windy.svg" alt="Windy sky with spinning pinwheel and leaves">
                <span class="node-label"><span>🍃 Windy</span><b style="--number-color:#86efac">5</b></span>
              </button>
              <button class="place-node numbered-choice" type="button" data-weather="stormy" data-key="6">
                <img src="assets/stormy.svg" alt="Stormy cloud with lightning bolt">
                <span class="node-label"><span>⛈️ Stormy / Typhoon</span><b style="--number-color:#c084fc">6</b></span>
              </button>
            </div>
            <div class="weather-bottom-strip">
              <button class="other-place" id="back-to-date-button" type="button">← Back to Date</button>
              <button class="other-place numbered-choice" type="button" data-weather="other" data-key="7">✏️ Custom Weather</button>
            </div>
          </section>

          <!-- STAGE 3: TEMPERATURE & THERMOMETER -->
          <section class="stage" id="temp-stage" aria-labelledby="temp-title">
            <img class="scene-art" id="temp-art" src="assets/sunny.svg" alt="">
            <div class="scene-vignette"></div>
            <div class="action-panel">
              <span class="eyebrow" id="temp-weather-label">☀️ Sunny Sky</span>
              <h2 id="temp-title">How does it feel outside?</h2>
              <p>Slide the thermometer or tap a temperature card (1–5)!</p>

              <div class="therm-widget" aria-label="Interactive Thermometer">
                <button class="therm-step-btn" id="stage-therm-down" type="button" aria-label="Cooler">−</button>
                <div class="therm-bar-shell" id="stage-therm-drag" title="Drag left or right to change temperature!">
                  <div class="therm-bulb" id="stage-therm-bulb">😊</div>
                  <div class="therm-tube">
                    <div class="therm-fill" id="stage-therm-fill"></div>
                  </div>
                </div>
                <button class="therm-step-btn" id="stage-therm-up" type="button" aria-label="Warmer">+</button>
                <div class="therm-readout" id="stage-therm-readout">😊 WARM</div>
              </div>

              <div class="action-choices" id="temp-choices"></div>
              <button class="back-link" id="back-to-weather" type="button">← Choose different weather</button>
            </div>

            <aside class="mascot-stage-card" aria-live="polite">
              <div class="mascot-avatar" id="temp-mascot-avatar">🧸🕶️</div>
              <strong id="temp-mascot-title">Weather Bear’s Gear</strong>
              <span id="temp-mascot-tip">Wear a T-shirt and play outside!</span>
            </aside>
          </section>

          <!-- STAGE 4: SPEAK 2 SENTENCES -->
          <section class="stage" id="speak-stage" aria-labelledby="speak-title">
            <img class="scene-art" id="speak-art" src="assets/sunny.svg" alt="">
            <div class="scene-vignette"></div>
            <div class="speech-panel">
              <span class="eyebrow">Step 4 · Weather Reporter</span>
              <h2 id="speak-title">Say today’s weather report!</h2>
              <div class="speech-bubble" aria-live="polite">
                <div class="sentence-row">
                  <span class="sentence-number">1</span>
                  <span id="date-sentence-text">Today is Sunday, September 27th, 2026.</span>
                  <button class="speak-one" type="button" data-sentence="date" aria-label="Hear the date sentence">🔊</button>
                </div>
                <div class="sentence-row">
                  <span class="sentence-number">2</span>
                  <span id="weather-sentence-text">It is sunny and warm outside!</span>
                  <button class="speak-one" type="button" data-sentence="weather" aria-label="Hear the weather sentence">🔊</button>
                </div>
              </div>
              <div class="mascot-advice-pill">
                <span id="speak-mascot-pill">🧸 Weather Bear says: Put on your sunglasses and have fun!</span>
                <button class="speak-one" type="button" data-sentence="tip" aria-label="Hear Weather Bear tip">🔊</button>
              </div>
              <div class="speech-actions">
                <button class="primary-button" id="said-it-button" type="button">We said it! Add a star ⭐</button>
                <button class="secondary-button" id="change-answer-button" type="button">Change temperature</button>
              </div>
            </div>

            <aside class="mascot-stage-card">
              <div class="mascot-avatar" id="speak-mascot-avatar">🧸☀️🕶️</div>
              <strong id="speak-mascot-title">Ready to Report!</strong>
              <span id="speak-mascot-sub">Tap 🔊 to hear Jenny, then say each sentence aloud.</span>
            </aside>
          </section>

          <!-- STAGE 5: CLASS CHART + INTERACTIVE SLOT DRUM BOARD -->
          <section class="stage" id="chart-stage" aria-labelledby="chart-title">
            <div class="chart-layout">
              <div class="map-visual" aria-label="Class weather chart and interactive board">
                <div class="board-mode-tabs" role="tablist">
                  <button class="board-tab active" id="tab-map-view" type="button" role="tab" aria-selected="true">📊 Star Chart</button>
                  <button class="board-tab" id="tab-slot-view" type="button" role="tab" aria-selected="false">🎰 Quick Slot Board</button>
                </div>

                <!-- View A: Visual Weather Star Map -->
                <div class="visual-pane" id="pane-map-view">
                  <img src="assets/weather-world.svg" alt="Sunny Sky Weather Club station">
                  <div class="map-title">
                    <strong>Our Weather &amp; Date Chart</strong>
                    <span id="chart-banner-date">Today is Sunday, September 27th, 2026</span>
                  </div>
                  <div class="total-stories"><b id="total-stories">0</b><span>reports shared</span></div>
                  <div id="map-stars" aria-hidden="true"></div>
                  <div class="weather-tally-grid" id="weather-tally-grid"></div>
                </div>

                <!-- View B: All-in-One Slot Drum & Thermometer Board -->
                <div class="visual-pane slot-board-pane" id="pane-slot-view" hidden>
                  <div class="map-title">
                    <strong>Interactive Date &amp; Weather Board</strong>
                    <span>Click arrows or scroll on any pill to spin!</span>
                  </div>
                  <div class="slot-sentence-card">
                    <div class="slot-line">
                      <span>Today is</span>
                      <span class="drum-pill" style="--drum-bg:#ffe5ec;--drum-border:#ff8fab;--drum-text:#9d174d" data-wheel="day">
                        <button type="button" data-spin="day" data-dir="-1">◀</button>
                        <strong id="slot-val-day">Sunday</strong>
                        <button type="button" data-spin="day" data-dir="1">▶</button>
                      </span>
                      <span>,</span>
                      <span class="drum-pill" style="--drum-bg:#d8f3dc;--drum-border:#52b788;--drum-text:#1b4332" data-wheel="month">
                        <button type="button" data-spin="month" data-dir="-1">◀</button>
                        <strong id="slot-val-month">September</strong>
                        <button type="button" data-spin="month" data-dir="1">▶</button>
                      </span>
                      <span class="drum-pill" style="--drum-bg:#fff3b0;--drum-border:#ffb703;--drum-text:#9a3412" data-wheel="date">
                        <button type="button" data-spin="date" data-dir="-1">◀</button>
                        <strong id="slot-val-date">27th</strong>
                        <button type="button" data-spin="date" data-dir="1">▶</button>
                      </span>
                      <span>,</span>
                      <span class="drum-pill" style="--drum-bg:#f3e8ff;--drum-border:#a855f7;--drum-text:#581c87" data-wheel="year">
                        <button type="button" data-spin="year" data-dir="-1">◀</button>
                        <strong id="slot-val-year">2026</strong>
                        <button type="button" data-spin="year" data-dir="1">▶</button>
                      </span>
                    </div>

                    <div class="slot-line">
                      <span>and it is</span>
                      <span class="drum-pill" style="--drum-bg:#e0f2fe;--drum-border:#38bdf8;--drum-text:#0369a1" data-wheel="weather">
                        <button type="button" data-spin="weather" data-dir="-1">◀</button>
                        <strong id="slot-val-weather">☀️ Sunny</strong>
                        <button type="button" data-spin="weather" data-dir="1">▶</button>
                      </span>
                      <span>and</span>
                      <span class="drum-pill" style="--drum-bg:#fef08a;--drum-border:#f59e0b;--drum-text:#92400e" data-wheel="temp">
                        <button type="button" data-spin="temp" data-dir="-1">◀</button>
                        <strong id="slot-val-temp">😊 Warm</strong>
                        <button type="button" data-spin="temp" data-dir="1">▶</button>
                      </span>
                      <span>outside!</span>
                    </div>

                    <div style="display:flex;gap:10px;flex-wrap:wrap;justify-content:center;margin-top:4px;">
                      <button class="primary-button" id="slot-speak-all-btn" type="button" style="min-height:46px;font-size:17px;padding:8px 18px;">🔊 Hear Jenny Read Full Board</button>
                      <button class="accent-button" id="slot-add-star-btn" type="button" style="min-height:46px;font-size:17px;padding:8px 18px;">⭐ Add Report to Class Chart</button>
                    </div>
                  </div>
                </div>
              </div>

              <aside class="chart-sidebar">
                <span class="eyebrow">Class Progress</span>
                <h2 id="chart-title">Great Weather Reporters!</h2>
                <div class="last-story" id="last-story" aria-live="polite">Your first weather report will appear here.</div>
                <p class="reaction-title">How does the class feel about today’s weather?</p>
                <div class="reactions">
                  <button class="reaction-button" type="button" data-reaction="yay">☀️ Yay!<b id="reaction-yay">0</b></button>
                  <button class="reaction-button" type="button" data-reaction="cozy">🌈 Cozy!<b id="reaction-cozy">0</b></button>
                  <button class="reaction-button" type="button" data-reaction="fun">🎈 Fun!<b id="reaction-fun">0</b></button>
                </div>
                <div class="chart-actions">
                  <div class="class-progress-card" aria-live="polite">
                    <strong id="class-progress-text">0 of up to 30 students answered</strong>
                    <span id="next-student-text">Reporter 1 is next.</span>
                  </div>
                  <button class="primary-button" id="next-speaker-button" type="button">Next reporter → 1/30</button>
                  <button class="secondary-button" id="reset-class-chart-btn" type="button">🔄 New Class</button>
                  <button class="secondary-button previous-results-button" id="previous-story-button" type="button">← Previous screen · Change last report</button>
                </div>
              </aside>
            </div>
            <ol class="sr-only" id="story-log" aria-label="Completed weather reports"></ol>
          </section>
        </section>
      </main>

      <dialog id="custom-dialog" aria-labelledby="custom-dialog-title">
        <form class="dialog-shell" id="custom-form" method="dialog">
          <span class="eyebrow">Custom Weather Idea</span>
          <h2 id="custom-dialog-title">Describe today’s sky!</h2>
          <p id="custom-dialog-help">Type the weather word (for example: foggy, snowy, breezy, rainbow).</p>
          <label class="field">How is the weather?
            <input id="custom-weather-input" type="text" maxlength="32" placeholder="snowy and bright" autocomplete="off">
          </label>
          <div class="dialog-actions">
            <button class="cancel-dialog" id="cancel-dialog" type="button">Cancel</button>
            <button class="save-dialog" type="submit">Use this weather →</button>
          </div>
        </form>
      </dialog>

      <div class="toast" id="toast" role="status" aria-live="polite"></div>
      <div class="effect-layer" id="effect-layer" aria-hidden="true"></div>
      <div class="sr-only" id="live-status" aria-live="polite"></div>
    </div>
  </div>

  <!-- Pre-rendered Microsoft Edge TTS (en-US-JennyNeural) Offline Voice Pack -->
  <script src="assets/audio_jenny.js"></script>
  <script>
    const DAYS = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"];
    const MONTHS = [
      { name: "January", short: "Jan", icon: "❄️" },
      { name: "February", short: "Feb", icon: "⛄" },
      { name: "March", short: "Mar", icon: "🌱" },
      { name: "April", short: "Apr", icon: "🌷" },
      { name: "May", short: "May", icon: "🌼" },
      { name: "June", short: "Jun", icon: "☀️" },
      { name: "July", short: "Jul", icon: "🏖️" },
      { name: "August", short: "Aug", icon: "🍉" },
      { name: "September", short: "Sep", icon: "🍎" },
      { name: "October", short: "Oct", icon: "🎃" },
      { name: "November", short: "Nov", icon: "🍂" },
      { name: "December", short: "Dec", icon: "🎄" }
    ];
    const YEARS = [2025, 2026, 2027, 2028, 2029, 2030];

    const WEATHERS = [
      {
        id: "sunny",
        label: "Sunny",
        word: "sunny",
        icon: "☀️",
        image: "assets/sunny.svg",
        color: "#ffc335",
        mascot: "🧸🕶️🧢",
        gearTip: "Put on your sunglasses and sun hat!"
      },
      {
        id: "partly-cloudy",
        label: "Partly Cloudy",
        word: "partly cloudy",
        icon: "⛅",
        image: "assets/partly-cloudy.svg",
        color: "#71cbff",
        mascot: "🧸⛅👟",
        gearTip: "Great sky to fly a kite in the park!"
      },
      {
        id: "cloudy",
        label: "Cloudy",
        word: "cloudy",
        icon: "☁️",
        image: "assets/cloudy.svg",
        color: "#cbd5e1",
        mascot: "🧸☁️👕",
        gearTip: "Soft clouds in the sky, nice and gentle outside!"
      },
      {
        id: "rainy",
        label: "Rainy",
        word: "rainy",
        icon: "🌧️",
        image: "assets/rainy.svg",
        color: "#38bdf8",
        mascot: "🧸☔🥾",
        gearTip: "Take your umbrella and wear rain boots!"
      },
      {
        id: "windy",
        label: "Windy",
        word: "windy",
        icon: "🍃",
        image: "assets/windy.svg",
        color: "#86efac",
        mascot: "🧸🍃🧥",
        gearTip: "Hold onto your hat and watch the pinwheel spin!"
      },
      {
        id: "stormy",
        label: "Stormy",
        word: "stormy",
        icon: "⛈️",
        image: "assets/stormy.svg",
        color: "#c084fc",
        mascot: "🧸🏠📚",
        gearTip: "Stay cozy indoors and read a good book!"
      }
    ];

    const TEMPERATURES = [
      { id: "cold", label: "Cold", word: "cold", icon: "🥶", level: 16, fill: "#3b82f6", color: "#dbeafe", keyColor: "#93c5fd", tip: "Wear a warm winter coat, hat, and scarf!" },
      { id: "cool", label: "Cool", word: "cool", icon: "🧊", level: 36, fill: "#06b6d4", color: "#cffafe", keyColor: "#67e8f9", tip: "Wear a cozy sweater or light jacket!" },
      { id: "warm", label: "Warm", word: "warm", icon: "😊", level: 58, fill: "#facc15", color: "#fef9c3", keyColor: "#fde047", tip: "Wear a comfy T-shirt and play outside!" },
      { id: "hot", label: "Hot", word: "hot", icon: "🥵", level: 78, fill: "#f97316", color: "#ffedd5", keyColor: "#fdba74", tip: "Wear shorts, a sun hat, and drink lots of water!" },
      { id: "very-hot", label: "Very Hot", word: "very hot", icon: "🔥", level: 96, fill: "#ef4444", color: "#fee2e2", keyColor: "#fca5a5", tip: "Stay cool in the shade and drink cold water!" }
    ];

    const CONFIG = {
      goal: 30,
      maxStudents: 30,
      storageKey: "weather-and-date-chart-v1-0"
    };

    const $ = (selector, root = document) => root.querySelector(selector);
    const $$ = (selector, root = document) => [...root.querySelectorAll(selector)];
    const weatherById = id => WEATHERS.find(item => item.id === id) || WEATHERS[0];
    const tempById = id => TEMPERATURES.find(item => item.id === id) || TEMPERATURES[2];

    function getOrdinalSuffix(n) {
      const v = n % 100;
      if (v >= 11 && v <= 13) return "th";
      switch (n % 10) {
        case 1: return "st";
        case 2: return "nd";
        case 3: return "rd";
        default: return "th";
      }
    }

    function getTodaySelection() {
      const now = new Date();
      const jsDay = now.getDay();
      const dayIndex = jsDay === 0 ? 6 : jsDay - 1;
      const monthIndex = now.getMonth();
      const dateNum = now.getDate();
      const yearNum = YEARS.includes(now.getFullYear()) ? now.getFullYear() : 2026;
      return {
        day: DAYS[dayIndex],
        month: MONTHS[monthIndex].name,
        date: dateNum,
        year: yearNum
      };
    }

    const initialDate = getTodaySelection();

    const S = {
      screen: "title",
      stage: "date",
      boardTab: "map",
      speaker: 1,
      day: initialDate.day,
      month: initialDate.month,
      date: initialDate.date,
      year: initialDate.year,
      selectedWeather: WEATHERS[0],
      selectedTemp: TEMPERATURES[2],
      stories: [],
      reactions: { yay: 0, cozy: 0, fun: 0 },
      sound: true,
      classComplete: false,
      audioContext: null,
      decodedBuffers: {},
      activeSources: [],
      activeHtmlAudio: null,
      toastTimer: 0
    };

    /* PROPORTIONAL VIEWPORT CONTAINMENT SCALER */
    function fitAppToScreen() {
      const pad = document.fullscreenElement ? 0 : 18;
      const availW = Math.max(320, window.innerWidth - pad);
      const availH = Math.max(240, window.innerHeight - pad);
      const scale = Math.min(availW / 1280, availH / 720);
      document.documentElement.style.setProperty("--app-scale", scale.toFixed(4));
    }

    /* MICROSOFT NEURAL JENNY (en-US-JennyNeural) VOICE ENGINE */
    function ensureAudio() {
      if (!S.sound) return null;
      if (!S.audioContext) {
        const AudioContextClass = window.AudioContext || window.webkitAudioContext;
        if (AudioContextClass) S.audioContext = new AudioContextClass();
      }
      if (S.audioContext && S.audioContext.state === "suspended") {
        S.audioContext.resume();
      }
      return S.audioContext;
    }

    function stopAllSpeech() {
      S.activeSources.forEach(src => {
        try { src.stop(); } catch (_) {}
      });
      S.activeSources = [];
      if (S.activeHtmlAudio) {
        try {
          S.activeHtmlAudio.pause();
          S.activeHtmlAudio.currentTime = 0;
        } catch (_) {}
        S.activeHtmlAudio = null;
      }
      if ("speechSynthesis" in window) window.speechSynthesis.cancel();
    }

    async function getClipBuffer(key) {
      if (S.decodedBuffers[key]) return S.decodedBuffers[key];
      const clip = window.JENNY_AUDIO?.[key];
      if (!clip || !clip.src) return null;
      const ctx = ensureAudio();
      if (!ctx) return null;
      const base64 = clip.src.split(",")[1];
      const binary = atob(base64);
      const bytes = new Uint8Array(binary.length);
      for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
      const audioBuffer = await ctx.decodeAudioData(bytes.buffer.slice(0));
      S.decodedBuffers[key] = audioBuffer;
      return audioBuffer;
    }

    async function playJennySequence(keys, gapSeconds = 0.05) {
      if (!S.sound) return;
      stopAllSpeech();
      const validKeys = keys.filter(k => window.JENNY_AUDIO?.[k]);
      if (!validKeys.length) return;

      const ctx = ensureAudio();
      if (ctx) {
        try {
          const buffers = await Promise.all(validKeys.map(k => getClipBuffer(k)));
          let cursor = ctx.currentTime + 0.02;
          buffers.forEach((buf, idx) => {
            if (!buf) return;
            const clipMeta = window.JENNY_AUDIO[validKeys[idx]];
            const [startSpan, endSpan] = clipMeta.span || [0, buf.duration];
            const startOffset = Math.max(0, Math.min(startSpan, buf.duration - 0.05));
            const duration = Math.max(0.08, Math.min(endSpan - startOffset, buf.duration - startOffset));

            const source = ctx.createBufferSource();
            source.buffer = buf;
            source.connect(ctx.destination);
            source.start(cursor, startOffset, duration);
            S.activeSources.push(source);
            cursor += duration + gapSeconds;
          });
          return;
        } catch (err) {
          console.warn("WebAudio buffer playback fell back to HTML5 Audio:", err);
        }
      }

      // Fallback sequential HTML5 Audio playback
      for (const k of validKeys) {
        const clip = window.JENNY_AUDIO[k];
        if (!clip) continue;
        await new Promise(resolve => {
          const audio = new Audio(clip.src);
          S.activeHtmlAudio = audio;
          audio.onended = resolve;
          audio.onerror = resolve;
          audio.play().catch(resolve);
        });
      }
    }

    function getDateSequenceKeys() {
      return [
        `today_${S.day.toLowerCase()}`,
        `month_${S.month.toLowerCase()}`,
        `ord_${S.date}`,
        `year_${S.year}`
      ];
    }

    function getWeatherSentenceKey() {
      return `wt_${S.selectedWeather.id}_${S.selectedTemp.id}`;
    }

    function speakDateSentence() {
      return playJennySequence(getDateSequenceKeys(), 0.05);
    }

    function speakWeatherSentence() {
      const wtKey = getWeatherSentenceKey();
      if (window.JENNY_AUDIO?.[wtKey]) {
        return playJennySequence([wtKey]);
      }
      return playJennySequence([`weather_${S.selectedWeather.id}`, `temp_${S.selectedTemp.id}`], 0.08);
    }

    function speakFullReport() {
      const wtKey = getWeatherSentenceKey();
      const seq = [...getDateSequenceKeys()];
      if (window.JENNY_AUDIO?.[wtKey]) seq.push(wtKey);
      return playJennySequence(seq, 0.07);
    }

    function sanitizeText(value) {
      return String(value || "").replace(/[<>]/g, "").replace(/\s+/g, " ").trim();
    }

    function buildDateSentence(day = S.day, month = S.month, date = S.date, year = S.year) {
      return `Today is ${day}, ${month} ${date}${getOrdinalSuffix(date)}, ${year}.`;
    }

    function buildWeatherSentence(weather = S.selectedWeather, temp = S.selectedTemp) {
      return `It is ${weather.word} and ${temp.word} outside!`;
    }

    function loadSavedState() {
      try {
        const saved = JSON.parse(localStorage.getItem(CONFIG.storageKey) || "null");
        if (!saved) return;
        if (Array.isArray(saved.stories)) S.stories = saved.stories.slice(0, CONFIG.maxStudents);
        if (saved.reactions) S.reactions = { ...S.reactions, ...saved.reactions };
        S.speaker = Math.max(1, Number(saved.speaker) || S.stories.length + 1);
        S.sound = saved.sound !== false;
        S.classComplete = saved.classComplete === true;
        if (saved.day && DAYS.includes(saved.day)) S.day = saved.day;
        if (saved.month && MONTHS.some(m => m.name === saved.month)) S.month = saved.month;
        if (saved.date >= 1 && saved.date <= 31) S.date = Number(saved.date);
        if (YEARS.includes(Number(saved.year))) S.year = Number(saved.year);
      } catch (err) {
        console.warn("Saved Weather Chart state could not be read.", err);
      }
    }

    function saveState() {
      try {
        localStorage.setItem(CONFIG.storageKey, JSON.stringify({
          stories: S.stories,
          reactions: S.reactions,
          speaker: S.speaker,
          sound: S.sound,
          classComplete: S.classComplete,
          day: S.day,
          month: S.month,
          date: S.date,
          year: S.year
        }));
      } catch (err) {
        console.warn("Weather Chart state could not be saved.", err);
      }
    }

    function showScreen(name) {
      S.screen = name;
      $$(".screen").forEach(screen => screen.classList.toggle("active", screen.id === `${name}-screen`));
      $("#teacher-bar").classList.toggle("visible", name !== "title");
      document.body.dataset.screen = name;
    }

    function toggleBrandMenu(force) {
      const popover = $("#brand-popover");
      const button = $("#brand-menu-button");
      const shouldOpen = typeof force === "boolean" ? force : popover.hidden;
      popover.hidden = !shouldOpen;
      button.setAttribute("aria-expanded", String(shouldOpen));
    }

    function goHome() {
      toggleBrandMenu(false);
      showScreen("title");
      updateHeader();
    }

    function setStage(stage) {
      S.stage = stage;
      $$(".stage").forEach(panel => panel.classList.toggle("active", panel.id === `${stage}-stage`));
      if (stage === "date") {
        renderDateStage();
        playJennySequence(["prompt_date"]);
      } else if (stage === "weather") {
        playJennySequence(["prompt_weather"]);
      } else if (stage === "temp") {
        renderTempStage();
        playJennySequence([`weather_${S.selectedWeather.id}`, "prompt_temp"], 0.12);
      } else if (stage === "speak") {
        renderSpeakStage();
        speakFullReport();
      } else if (stage === "chart") {
        renderChart();
      }
    }

    function beginGame(startFresh = false, initialMode = "guided") {
      ensureAudio();
      if (startFresh && S.stories.length && !confirm("Start a new class and clear the current weather chart?")) return;
      if (startFresh) clearClass(false);
      showScreen("play");
      if (initialMode === "slot") {
        switchBoardTab("slot");
        setStage("chart");
        speakFullReport();
        return;
      }
      resetTurn();
    }

    function resetTurn() {
      S.speaker = Math.min(S.stories.length + 1, CONFIG.maxStudents);
      $("#speaker-name").value = "";
      setStage("date");
      updateHeader();
    }

    /* STAGE 1: COMPACT DATE STAGE */
    function renderDateStage() {
      const ord = getOrdinalSuffix(S.date);
      $("#display-day").textContent = S.day;
      $("#display-month").textContent = S.month;
      $("#display-date").textContent = S.date;
      $("#display-ordinal").textContent = ord;
      $("#display-year").textContent = S.year;
      $("#live-date-sentence").textContent = buildDateSentence();

      $("#chips-day").innerHTML = DAYS.map((day, idx) => `
        <button class="chip-btn numbered-choice ${day === S.day ? "selected" : ""}" type="button" data-pick-day="${day}" data-key="${idx + 1}">
          ${day.slice(0, 3)}
        </button>
      `).join("");

      $("#chips-month").innerHTML = MONTHS.map(m => `
        <button class="chip-btn ${m.name === S.month ? "selected" : ""}" type="button" data-pick-month="${m.name}">
          ${m.icon} ${m.short}
        </button>
      `).join("");

      $("#chips-date").innerHTML = Array.from({ length: 31 }, (_, i) => i + 1).map(num => `
        <button class="chip-btn ${num === S.date ? "selected" : ""}" type="button" data-pick-date="${num}">
          ${num}${getOrdinalSuffix(num)}
        </button>
      `).join("");

      $("#chips-year").innerHTML = YEARS.map(yr => `
        <button class="chip-btn ${yr === S.year ? "selected" : ""}" type="button" data-pick-year="${yr}">
          ${yr}
        </button>
      `).join("");

      syncSlotBoardValues();
    }

    function spinField(field, dir) {
      ensureAudio();
      if (field === "day") {
        const idx = (DAYS.indexOf(S.day) + dir + DAYS.length) % DAYS.length;
        S.day = DAYS[idx];
        playJennySequence([`day_${S.day.toLowerCase()}`]);
      } else if (field === "month") {
        const idx = (MONTHS.findIndex(m => m.name === S.month) + dir + MONTHS.length) % MONTHS.length;
        S.month = MONTHS[idx].name;
        playJennySequence([`month_${S.month.toLowerCase()}`]);
      } else if (field === "date") {
        S.date = ((S.date - 1 + dir + 31) % 31) + 1;
        playJennySequence([`ord_${S.date}`]);
      } else if (field === "year") {
        const idx = (YEARS.indexOf(S.year) + dir + YEARS.length) % YEARS.length;
        S.year = YEARS[idx];
        playJennySequence([`year_${S.year}`]);
      } else if (field === "weather") {
        const idx = (WEATHERS.findIndex(w => w.id === S.selectedWeather.id) + dir + WEATHERS.length) % WEATHERS.length;
        S.selectedWeather = WEATHERS[idx];
        playJennySequence([`weather_${S.selectedWeather.id}`]);
      } else if (field === "temp") {
        const idx = (TEMPERATURES.findIndex(t => t.id === S.selectedTemp.id) + dir + TEMPERATURES.length) % TEMPERATURES.length;
        S.selectedTemp = TEMPERATURES[idx];
        playJennySequence([`temp_${S.selectedTemp.id}`]);
      }
      saveState();
      renderDateStage();
    }

    function syncSlotBoardValues() {
      $("#slot-val-day").textContent = S.day;
      $("#slot-val-month").textContent = S.month;
      $("#slot-val-date").textContent = `${S.date}${getOrdinalSuffix(S.date)}`;
      $("#slot-val-year").textContent = S.year;
      $("#slot-val-weather").textContent = `${S.selectedWeather.icon} ${S.selectedWeather.label}`;
      $("#slot-val-temp").textContent = `${S.selectedTemp.icon} ${S.selectedTemp.label}`;
      $("#chart-banner-date").textContent = buildDateSentence();
    }

    /* STAGE 2: CHOOSE WEATHER */
    function chooseWeather(id) {
      ensureAudio();
      if (id === "other") {
        openCustomDialog();
        return;
      }
      S.selectedWeather = weatherById(id);
      syncSlotBoardValues();
      setStage("temp");
    }

    /* STAGE 3: TEMPERATURE & THERMOMETER */
    function renderTempStage() {
      const w = S.selectedWeather;
      const t = S.selectedTemp;
      $("#temp-art").src = w.image;
      $("#temp-weather-label").textContent = `${w.icon} ${w.label}`;
      updateThermometerUI();

      $("#temp-choices").innerHTML = TEMPERATURES.map((temp, index) => `
        <button class="action-choice numbered-choice ${temp.id === t.id ? "selected" : ""}" type="button" data-temp="${temp.id}" data-key="${index + 1}" style="--choice-color:${temp.color};--key-color:${temp.keyColor}">
          <span class="action-icon" aria-hidden="true">${temp.icon}</span>
          <span>${temp.label}</span>
          <span class="keycap" aria-hidden="true">${index + 1}</span>
        </button>
      `).join("");
    }

    function updateThermometerUI() {
      const t = S.selectedTemp;
      const w = S.selectedWeather;
      $("#stage-therm-bulb").textContent = t.icon;
      $("#stage-therm-bulb").style.background = t.fill;
      $("#stage-therm-fill").style.width = `${t.level}%`;
      $("#stage-therm-fill").style.background = t.fill;
      $("#stage-therm-readout").textContent = `${t.icon} ${t.label.toUpperCase()}`;
      $("#stage-therm-readout").style.color = t.fill;

      $("#temp-mascot-avatar").textContent = `${w.mascot}${t.icon}`;
      $("#temp-mascot-title").textContent = `${w.label} & ${t.label}`;
      $("#temp-mascot-tip").textContent = `${w.gearTip} ${t.tip}`;
      syncSlotBoardValues();
    }

    function chooseTemp(id, advance = true) {
      ensureAudio();
      S.selectedTemp = tempById(id);
      renderTempStage();
      if (advance) {
        setStage("speak");
      } else {
        playJennySequence([`temp_${S.selectedTemp.id}`]);
      }
    }

    /* STAGE 4: SPEAK STAGE */
    function renderSpeakStage() {
      const w = S.selectedWeather;
      const t = S.selectedTemp;
      $("#speak-art").src = w.image;
      const s1 = buildDateSentence();
      const s2 = buildWeatherSentence();
      $("#date-sentence-text").textContent = s1;
      $("#weather-sentence-text").textContent = s2;
      $("#speak-mascot-pill").textContent = `🧸 Weather Bear: ${w.gearTip} ${t.tip}`;
      $("#speak-mascot-avatar").textContent = `${w.mascot}${t.icon}`;
      $("#speak-mascot-title").textContent = `${w.label} · ${t.label}`;
    }

    /* RECORD REPORT & SHOW CHART */
    function completeStory() {
      const typedName = sanitizeText($("#speaker-name").value);
      const story = {
        id: `${Date.now()}-${Math.random().toString(36).slice(2, 7)}`,
        speakerNumber: S.speaker,
        speakerName: typedName || `Reporter ${S.speaker}`,
        dateSentence: buildDateSentence(),
        weatherSentence: buildWeatherSentence(),
        weather: { ...S.selectedWeather },
        temp: { ...S.selectedTemp }
      };
      S.stories.push(story);
      S.speaker = Math.min(S.stories.length + 1, CONFIG.maxStudents);
      if (S.stories.length >= CONFIG.maxStudents) S.classComplete = true;
      $("#speaker-name").value = "";
      saveState();
      switchBoardTab("map");
      setStage("chart");
      renderChart(story.weather.id);
      playJennySequence(["prompt_chart"]);
      burst(14);
      toast(`${story.speakerName} added a Weather Star! ⭐`);
    }

    function switchBoardTab(tab) {
      S.boardTab = tab;
      const isMap = tab === "map";
      $("#tab-map-view").classList.toggle("active", isMap);
      $("#tab-map-view").setAttribute("aria-selected", String(isMap));
      $("#tab-slot-view").classList.toggle("active", !isMap);
      $("#tab-slot-view").setAttribute("aria-selected", String(!isMap));
      $("#pane-map-view").hidden = !isMap;
      $("#pane-slot-view").hidden = isMap;
      syncSlotBoardValues();
    }

    function renderChart(changedWeatherId = "") {
      const counts = Object.fromEntries(WEATHERS.map(w => [w.id, 0]));
      S.stories.forEach(story => {
        const wid = counts[story.weather.id] !== undefined ? story.weather.id : "sunny";
        counts[wid] += 1;
      });

      $("#weather-tally-grid").innerHTML = WEATHERS.map(w => `
        <div class="tally-card ${changedWeatherId === w.id ? "changed" : ""}">
          <span class="tally-icon" aria-hidden="true">${w.icon}</span>
          <span class="tally-name">${w.label}</span>
          <span class="tally-num">${counts[w.id]}</span>
        </div>
      `).join("");

      $("#total-stories").textContent = S.stories.length;
      const last = S.stories.at(-1);
      $("#last-story").textContent = last
        ? `${last.speakerName}: “${last.dateSentence} ${last.weatherSentence}”`
        : "Your first weather report will appear here.";
      $("#story-log").innerHTML = S.stories.map(s => `<li>${s.speakerName}: ${s.dateSentence} ${s.weatherSentence}</li>`).join("");

      const answered = Math.min(S.stories.length, CONFIG.maxStudents);
      const nextNumber = Math.min(answered + 1, CONFIG.maxStudents);
      $("#class-progress-text").textContent = `${answered} of up to ${CONFIG.maxStudents} reporters shared`;
      $("#next-student-text").textContent = S.classComplete
        ? "The class weather chart is complete!"
        : `Reporter ${nextNumber} is next.`;
      $("#next-speaker-button").textContent = answered >= CONFIG.maxStudents
        ? "All 30 reporters shared"
        : `Next reporter → ${nextNumber}/${CONFIG.maxStudents}`;
      $("#next-speaker-button").disabled = answered >= CONFIG.maxStudents;

      renderMapStars();
      updateReactions();
      updateHeader();
      syncSlotBoardValues();
    }

    function renderMapStars() {
      const positions = [
        [20, 28], [32, 24], [68, 25], [78, 32],
        [24, 52], [36, 48], [64, 50], [75, 55],
        [45, 26], [55, 28], [48, 62], [58, 60]
      ];
      $("#map-stars").innerHTML = S.stories.slice(-24).map((story, idx) => {
        const pos = positions[idx % positions.length];
        const offset = Math.floor(idx / positions.length) * 3;
        return `<span class="map-star" style="left:${pos[0] + offset}%;top:${pos[1] + offset}%;animation-delay:${Math.min(idx * .03, .36)}s">${story.weather.icon || "★"}</span>`;
      }).join("");
    }

    function nextSpeaker() {
      saveState();
      resetTurn();
    }

    function undoStory() {
      if (!S.stories.length) {
        toast("There is no weather report to undo yet.");
        return;
      }
      const story = S.stories.pop();
      S.classComplete = false;
      S.speaker = story.speakerNumber;
      S.selectedWeather = story.weather;
      S.selectedTemp = story.temp;
      $("#speaker-name").value = story.speakerName.startsWith("Reporter ") ? "" : story.speakerName;
      saveState();
      updateHeader();
      showScreen("play");
      setStage("speak");
      toast("Last report removed—ready to change!");
    }

    function addReaction(kind) {
      if (!Object.hasOwn(S.reactions, kind)) return;
      S.reactions[kind] += 1;
      saveState();
      updateReactions();
      const clipMap = { yay: "react_yay", cozy: "react_cozy", fun: "react_fun" };
      playJennySequence([clipMap[kind]]);
    }

    function updateReactions() {
      Object.entries(S.reactions).forEach(([key, value]) => {
        const node = $(`#reaction-${key}`);
        if (node) node.textContent = value;
      });
    }

    function updateHeader() {
      const currentStudent = Math.min(Math.max(S.stories.length + 1, 1), CONFIG.maxStudents);
      $("#speaker-label").textContent = S.classComplete
        ? `Class complete · ${S.stories.length}`
        : `Reporter ${currentStudent} of ${CONFIG.maxStudents}`;
      const progress = Math.min(S.stories.length / CONFIG.goal, 1) * 100;
      $("#meter-fill").style.width = `${progress}%`;
      $("#meter-label").textContent = `${S.stories.length} / ${CONFIG.maxStudents}`;
      $("#sound-button").classList.toggle("active", S.sound);
      $("#sound-button").setAttribute("aria-pressed", String(S.sound));
      $("#sound-icon").textContent = S.sound ? "♪" : "×";
      $("#start-button").textContent = S.stories.length
        ? `Continue Chart · ${S.stories.length} ${S.stories.length === 1 ? "report" : "reports"}`
        : "Start Weather Chart";
      $("#new-class-button").hidden = !S.stories.length;
    }

    function openCustomDialog() {
      $("#custom-weather-input").value = "";
      $("#custom-dialog").showModal();
      window.setTimeout(() => $("#custom-weather-input").focus(), 40);
    }

    function saveCustomWeather(event) {
      event.preventDefault();
      const word = sanitizeText($("#custom-weather-input").value).replace(/[.!?]+$/, "").toLowerCase();
      if (!word) {
        $("#custom-weather-input").focus();
        toast("Please type a weather word!");
        return;
      }
      S.selectedWeather = {
        id: "sunny",
        label: word[0].toUpperCase() + word.slice(1),
        word,
        icon: "🌈",
        image: "assets/weather-world.svg",
        color: "#ff7a2d",
        mascot: "🧸🌈✨",
        gearTip: `Enjoy the ${word} sky today!`
      };
      $("#custom-dialog").close();
      setStage("temp");
    }

    function clearClass(confirmFirst = true) {
      if (confirmFirst && S.stories.length && !confirm("Clear every report and start a new class?")) return false;
      S.stories = [];
      S.reactions = { yay: 0, cozy: 0, fun: 0 };
      S.speaker = 1;
      S.classComplete = false;
      localStorage.removeItem(CONFIG.storageKey);
      updateHeader();
      renderChart();
      return true;
    }

    function repeatPrompt() {
      if (S.stage === "date") speakDateSentence();
      else if (S.stage === "weather") playJennySequence(["prompt_weather"]);
      else if (S.stage === "temp") speakWeatherSentence();
      else if (S.stage === "speak" || S.stage === "chart") speakFullReport();
    }

    function toggleSound() {
      S.sound = !S.sound;
      if (!S.sound) stopAllSpeech();
      saveState();
      updateHeader();
      if (S.sound) repeatPrompt();
      toast(`Voice & Sound ${S.sound ? "on" : "off"}`);
    }

    async function toggleFullscreen() {
      try {
        if (!document.fullscreenElement) await document.documentElement.requestFullscreen();
        else await document.exitFullscreen();
      } catch (_) {
        toast("Fullscreen is not available in this browser.");
      }
      fitAppToScreen();
    }

    function toast(message) {
      const node = $("#toast");
      node.textContent = message;
      node.classList.add("show");
      window.clearTimeout(S.toastTimer);
      S.toastTimer = window.setTimeout(() => node.classList.remove("show"), 2300);
    }

    function burst(count = 14) {
      const layer = $("#effect-layer");
      layer.innerHTML = "";
      const icons = ["★", "☀️", "🌈", "✦"];
      for (let index = 0; index < count; index += 1) {
        const star = document.createElement("span");
        const angle = (Math.PI * 2 * index / count) + (Math.random() * .25);
        const distance = 160 + Math.random() * 280;
        star.className = "burst-star";
        star.textContent = icons[index % icons.length];
        star.style.setProperty("--x", `${Math.cos(angle) * distance}px`);
        star.style.setProperty("--y", `${Math.sin(angle) * distance}px`);
        star.style.setProperty("--r", `${Math.round(Math.random() * 540 - 270)}deg`);
        star.style.setProperty("--size", `${22 + Math.round(Math.random() * 26)}px`);
        star.style.animationDelay = `${Math.random() * .12}s`;
        layer.appendChild(star);
      }
      window.setTimeout(() => { layer.innerHTML = ""; }, 1450);
    }

    function activateNumberChoice(number) {
      if (S.screen !== "play") return;
      const stage = $(`#${S.stage}-stage`);
      const choice = stage?.querySelector(`[data-key="${number}"]`);
      choice?.click();
    }

    function bindThermometerDrag() {
      const bar = $("#stage-therm-drag");
      let dragging = false;
      function updateFromPointer(e) {
        const rect = bar.getBoundingClientRect();
        const x = Math.max(0, Math.min(e.clientX - rect.left, rect.width));
        const idx = Math.min(TEMPERATURES.length - 1, Math.floor((x / rect.width) * TEMPERATURES.length));
        if (TEMPERATURES[idx].id !== S.selectedTemp.id) {
          S.selectedTemp = TEMPERATURES[idx];
          renderTempStage();
          playJennySequence([`temp_${S.selectedTemp.id}`]);
        }
      }
      bar.addEventListener("pointerdown", e => {
        dragging = true;
        bar.setPointerCapture(e.pointerId);
        updateFromPointer(e);
      });
      bar.addEventListener("pointermove", e => {
        if (dragging) updateFromPointer(e);
      });
      const stop = e => {
        if (!dragging) return;
        dragging = false;
        bar.releasePointerCapture(e.pointerId);
      };
      bar.addEventListener("pointerup", stop);
      bar.addEventListener("pointercancel", stop);
    }

    function bindEvents() {
      window.addEventListener("resize", fitAppToScreen);
      document.addEventListener("fullscreenchange", fitAppToScreen);

      $("#start-button").addEventListener("click", () => beginGame(false, "guided"));
      $("#quick-board-button").addEventListener("click", () => beginGame(false, "slot"));
      $("#new-class-button").addEventListener("click", () => beginGame(true, "guided"));

      $("#use-today-button").addEventListener("click", () => {
        const today = getTodaySelection();
        Object.assign(S, today);
        renderDateStage();
        speakDateSentence();
        toast(`Today: ${buildDateSentence()}`);
      });
      $("#random-date-button").addEventListener("click", () => {
        S.day = DAYS[Math.floor(Math.random() * DAYS.length)];
        S.month = MONTHS[Math.floor(Math.random() * MONTHS.length)].name;
        S.date = Math.floor(Math.random() * 28) + 1;
        renderDateStage();
        speakDateSentence();
      });
      $("#speak-date-preview-btn").addEventListener("click", speakDateSentence);
      $("#date-sentence-bar-click").addEventListener("click", speakDateSentence);
      $("#display-day").addEventListener("click", () => playJennySequence([`day_${S.day.toLowerCase()}`]));
      $("#display-month").addEventListener("click", () => playJennySequence([`month_${S.month.toLowerCase()}`]));
      $("#display-date-wrap").addEventListener("click", () => playJennySequence([`ord_${S.date}`]));
      $("#display-year").addEventListener("click", () => playJennySequence([`year_${S.year}`]));

      $("#to-weather-button").addEventListener("click", () => setStage("weather"));

      document.addEventListener("click", event => {
        const spinBtn = event.target.closest("[data-spin]");
        if (spinBtn) {
          spinField(spinBtn.dataset.spin, Number(spinBtn.dataset.dir));
          return;
        }
        const dayBtn = event.target.closest("[data-pick-day]");
        if (dayBtn) {
          S.day = dayBtn.dataset.pickDay;
          renderDateStage();
          playJennySequence([`day_${S.day.toLowerCase()}`]);
          return;
        }
        const monthBtn = event.target.closest("[data-pick-month]");
        if (monthBtn) {
          S.month = monthBtn.dataset.pickMonth;
          renderDateStage();
          playJennySequence([`month_${S.month.toLowerCase()}`]);
          return;
        }
        const dateBtn = event.target.closest("[data-pick-date]");
        if (dateBtn) {
          S.date = Number(dateBtn.dataset.pickDate);
          renderDateStage();
          playJennySequence([`ord_${S.date}`]);
          return;
        }
        const yearBtn = event.target.closest("[data-pick-year]");
        if (yearBtn) {
          S.year = Number(yearBtn.dataset.pickYear);
          renderDateStage();
          playJennySequence([`year_${S.year}`]);
          return;
        }
        if (!event.target.closest(".brand-menu")) toggleBrandMenu(false);
      });

      $$("[data-wheel]").forEach(pill => {
        pill.addEventListener("wheel", e => {
          e.preventDefault();
          spinField(pill.dataset.wheel, e.deltaY > 0 ? 1 : -1);
        }, { passive: false });
      });

      $$("[data-weather]").forEach(btn => {
        btn.addEventListener("click", () => chooseWeather(btn.dataset.weather));
      });
      $("#back-to-date-button").addEventListener("click", () => setStage("date"));

      $("#temp-choices").addEventListener("click", event => {
        const btn = event.target.closest("[data-temp]");
        if (btn) chooseTemp(btn.dataset.temp, true);
      });
      $("#stage-therm-down").addEventListener("click", () => {
        const idx = Math.max(0, TEMPERATURES.findIndex(t => t.id === S.selectedTemp.id) - 1);
        chooseTemp(TEMPERATURES[idx].id, false);
      });
      $("#stage-therm-up").addEventListener("click", () => {
        const idx = Math.min(TEMPERATURES.length - 1, TEMPERATURES.findIndex(t => t.id === S.selectedTemp.id) + 1);
        chooseTemp(TEMPERATURES[idx].id, false);
      });
      $("#back-to-weather").addEventListener("click", () => setStage("weather"));
      bindThermometerDrag();

      $$("[data-sentence]").forEach(btn => {
        btn.addEventListener("click", () => {
          const kind = btn.dataset.sentence;
          if (kind === "date") speakDateSentence();
          else if (kind === "weather") speakWeatherSentence();
          else playJennySequence([`tip_${S.selectedWeather.id}`]);
        });
      });
      $("#change-answer-button").addEventListener("click", () => setStage("temp"));
      $("#said-it-button").addEventListener("click", completeStory);

      $("#tab-map-view").addEventListener("click", () => switchBoardTab("map"));
      $("#tab-slot-view").addEventListener("click", () => switchBoardTab("slot"));
      $("#slot-speak-all-btn").addEventListener("click", speakFullReport);
      $("#slot-add-star-btn").addEventListener("click", completeStory);

      $("#next-speaker-button").addEventListener("click", nextSpeaker);
      $("#reset-class-chart-btn").addEventListener("click", () => {
        if (clearClass(true)) resetTurn();
      });
      $("#previous-story-button").addEventListener("click", undoStory);

      $("#brand-menu-button").addEventListener("click", event => {
        event.stopPropagation();
        toggleBrandMenu();
      });
      $("#home-button").addEventListener("click", goHome);
      $("#menu-guided-button").addEventListener("click", () => {
        toggleBrandMenu(false);
        showScreen("play");
        setStage("date");
      });
      $("#menu-slot-button").addEventListener("click", () => {
        toggleBrandMenu(false);
        showScreen("play");
        switchBoardTab("slot");
        setStage("chart");
        speakFullReport();
      });
      $("#menu-chart-button").addEventListener("click", () => {
        toggleBrandMenu(false);
        showScreen("play");
        switchBoardTab("map");
        setStage("chart");
      });

      $$("[data-reaction]").forEach(btn => btn.addEventListener("click", () => addReaction(btn.dataset.reaction)));

      $("#repeat-button").addEventListener("click", repeatPrompt);
      $("#undo-button").addEventListener("click", undoStory);
      $("#sound-button").addEventListener("click", toggleSound);
      $("#fullscreen-button").addEventListener("click", toggleFullscreen);

      $("#custom-form").addEventListener("submit", saveCustomWeather);
      $("#cancel-dialog").addEventListener("click", () => $("#custom-dialog").close());
      $("#speaker-name").addEventListener("change", saveState);

      window.addEventListener("keydown", event => {
        if ($("#custom-dialog").open || ["INPUT", "TEXTAREA"].includes(event.target.tagName)) return;
        if (/^[1-7]$/.test(event.key)) activateNumberChoice(event.key);
        else if (event.key.toLowerCase() === "r") repeatPrompt();
        else if (event.key.toLowerCase() === "u" || event.key === "Backspace") { event.preventDefault(); undoStory(); }
        else if (event.key.toLowerCase() === "m") toggleSound();
        else if (event.key.toLowerCase() === "f") toggleFullscreen();
        else if (event.key === "Enter" && S.screen === "play") {
          if (S.stage === "date") setStage("weather");
          else if (S.stage === "temp") setStage("speak");
          else if (S.stage === "speak") completeStory();
          else if (S.stage === "chart") nextSpeaker();
        }
      });

      document.addEventListener("visibilitychange", () => {
        if (document.hidden) stopAllSpeech();
      });
    }

    function init() {
      fitAppToScreen();
      loadSavedState();
      bindEvents();
      renderDateStage();
      renderChart();
      updateHeader();
    }

    init();
  </script>
</body>
</html>