Package source · Weather and Date Chart

CC BY 4.0 · please credit @joe · v1

← Game details

Take it, remix it, teach with it

The code below is only the package's playable entry HTML. Download the complete ZIP to edit or remix the game with all of its relative assets.

⬇ Download entry HTML ⬇ Download complete package
AI edit prompt starter
Edit this classroom game for interactive whiteboards. Keep large touch targets and whole-class play. Tracking, logins, personal-data collection, and remote API calls are prohibited. Use the downloaded complete ZIP—not only the entry HTML shown below—and preserve its browser-ready folder structure. Preserve the educational goal. Original creator: @joe on ClassBoard Games (CC BY 4.0).
Entry HTML only · 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>