Prompt Details
Model
(gemini-3.1-pro)
Token size
4,955
Example input
[Grades 3β5]: Grades 3β5
[Coral reef after ocean warming]: Coral reef after ocean warming
[Clownfish]: Clownfish
[Shelter spaces become limited when reef conditions change]: Shelter spaces become limited when reef conditions change
[3 class sessions of 40 minutes]: 3 class sessions of 40 minutes
Example output
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>EcoQuest Habitat Engineering Lab</title>
<style>
/* CSS Variables & Theme */
:root {
--primary: #0284c7;
--primary-hover: #0369a1;
--secondary: #0ea5e9;
--teacher: #7e22ce;
--teacher-hover: #6b21a8;
--teacher-bg: #f3e8ff;
--success: #10b981;
--warning: #f59e0b;
--danger: #ef4444;
--danger-hover: #dc2626;
--text-main: #1e293b;
--text-light: #64748b;
--bg-body: #f8fafc;
--bg-card: #ffffff;
--border: #e2e8f0;
--radius-md: 8px;
--radius-lg: 12px;
--radius-full: 9999px;
--shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05);
--shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
--shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
--transition: all 0.2s ease-in-out;
--font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
}
/* Reduced Motion */
@media (prefers-reduced-motion: reduce) {
:root { --transition: none; }
}
/* Reset & Base Setup */
* { box-sizing: border-box; margin: 0; padding: 0; }
body { font-family: var(--font-family); background-color: var(--bg-body); color: var(--text-main); line-height: 1.6; }
h1, h2, h3, h4 { line-height: 1.2; color: var(--text-main); margin-bottom: 0.5rem; }
a { color: var(--primary); text-decoration: none; }
button, input, select, textarea { font-family: inherit; font-size: 1rem; border: 1px solid var(--border); border-radius: var(--radius-md); transition: var(--transition); }
button { cursor: pointer; padding: 0.5rem 1rem; font-weight: 600; display: inline-flex; align-items: center; justify-content: center; gap: 0.5rem; border: none; }
button:focus-visible, input:focus-visible, select:focus-visible, textarea:focus-visible { outline: 2px solid var(--primary); outline-offset: 2px; }
input, select, textarea { padding: 0.5rem; width: 100%; background: var(--bg-card); }
textarea { resize: vertical; min-height: 100px; }
/* Utility Classes */
.container { max-width: 1200px; margin: 0 auto; padding: 0 1rem; }
.grid { display: grid; gap: 1.5rem; }
.grid-2 { grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); }
.grid-3 { grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); }
.flex { display: flex; gap: 1rem; }
.flex-col { display: flex; flex-direction: column; gap: 1rem; }
.items-center { align-items: center; }
.justify-between { justify-content: space-between; }
.mt-1 { margin-top: 1rem; } .mb-1 { margin-bottom: 1rem; }
.mt-2 { margin-top: 2rem; } .p-2 { padding: 1.5rem; }
.hidden { display: none !important; }
/* Buttons */
.btn-primary { background: var(--primary); color: white; }
.btn-primary:hover { background: var(--primary-hover); }
.btn-teacher { background: var(--teacher); color: white; }
.btn-teacher:hover { background: var(--teacher-hover); }
.btn-secondary { background: white; border: 1px solid var(--border); color: var(--text-main); }
.btn-secondary:hover { background: var(--bg-body); border-color: var(--text-light); }
.btn-danger { background: white; border: 1px solid var(--danger); color: var(--danger); }
.btn-danger:hover { background: var(--danger); color: white; }
/* Cards */
.card { background: var(--bg-card); border-radius: var(--radius-lg); padding: 1.5rem; box-shadow: var(--shadow-sm); border: 1px solid var(--border); }
.card:hover { box-shadow: var(--shadow-md); transform: translateY(-2px); }
.teacher-card { background: var(--teacher-bg); border-color: var(--teacher); }
/* Badges */
.badge { display: inline-flex; align-items: center; padding: 0.25rem 0.75rem; border-radius: var(--radius-full); font-size: 0.875rem; font-weight: 600; background: #e0f2fe; color: var(--primary-hover); }
.badge-teacher { background: #f3e8ff; color: var(--teacher-hover); }
.badge-warning { background: #fef3c7; color: #b45309; }
/* Header & Navigation */
.app-header { background: var(--bg-card); border-bottom: 1px solid var(--border); position: sticky; top: 0; z-index: 100; box-shadow: var(--shadow-sm); }
.header-top { display: flex; flex-wrap: wrap; justify-content: space-between; align-items: center; padding: 1rem 0; gap: 1rem;}
.mode-toggle { display: flex; align-items: center; gap: 0.5rem; background: var(--bg-body); padding: 0.25rem; border-radius: var(--radius-full); border: 1px solid var(--border); }
.mode-btn { padding: 0.25rem 1rem; border-radius: var(--radius-full); font-weight: 600; cursor: pointer; border: none; background: transparent; color: var(--text-light); transition: var(--transition); }
.mode-btn.active { background: white; color: var(--text-main); box-shadow: var(--shadow-sm); }
.mode-btn.active.teacher { color: var(--teacher); }
.mode-btn.active.student { color: var(--primary); }
.main-nav { display: flex; overflow-x: auto; gap: 0.5rem; padding-bottom: 0.5rem; scrollbar-width: none; }
.main-nav::-webkit-scrollbar { display: none; }
.nav-tab { white-space: nowrap; padding: 0.5rem 1rem; border-radius: var(--radius-md); font-weight: 600; color: var(--text-light); border: none; background: transparent; transition: var(--transition); }
.nav-tab:hover { background: var(--bg-body); color: var(--text-main); }
.nav-tab.active { background: var(--primary); color: white; }
/* Progress Bar */
.progress-container { width: 100%; background: var(--bg-body); border-radius: var(--radius-full); height: 8px; overflow: hidden; margin-top: 0.5rem; border: 1px solid var(--border); }
.progress-bar { height: 100%; background: var(--success); width: 0%; transition: width 0.5s ease; }
/* Forms & Inputs */
.form-group { margin-bottom: 1rem; }
.form-label { display: block; font-weight: 600; margin-bottom: 0.25rem; }
.form-hint { font-size: 0.875rem; color: var(--text-light); margin-bottom: 0.5rem; display: block; }
.required::after { content: " *"; color: var(--danger); }
/* Expandable Cards */
.expandable-header { display: flex; justify-content: space-between; align-items: center; cursor: pointer; padding: 1rem; background: var(--bg-body); border-radius: var(--radius-md); font-weight: 600; transition: var(--transition); border: 1px solid var(--border); }
.expandable-header:hover { background: #e2e8f0; }
.expandable-content { display: none; padding: 1rem; border: 1px solid var(--border); border-top: none; border-radius: 0 0 var(--radius-md) var(--radius-md); }
.expandable.active .expandable-content { display: block; }
.expandable.active .expandable-header { border-radius: var(--radius-md) var(--radius-md) 0 0; }
/* Engineering Checkboxes */
.step-check { display: flex; align-items: center; gap: 1rem; padding: 1rem; background: white; border: 1px solid var(--border); border-radius: var(--radius-md); cursor: pointer; transition: var(--transition); }
.step-check:hover { background: var(--bg-body); }
.step-check.completed { border-color: var(--success); background: #ecfdf5; }
.step-check input { width: 20px; height: 20px; accent-color: var(--success); cursor: pointer; }
/* Range Slider */
.slider-group { display: flex; align-items: center; gap: 1rem; }
.slider-group input[type="range"] { flex: 1; accent-color: var(--primary); }
.slider-val { width: 30px; text-align: center; font-weight: bold; background: var(--bg-body); padding: 0.25rem; border-radius: 4px; }
/* Toasts */
.toast-container { position: fixed; bottom: 1rem; right: 1rem; display: flex; flex-direction: column; gap: 0.5rem; z-index: 1000; }
.toast { padding: 1rem; border-radius: var(--radius-md); color: white; font-weight: 600; box-shadow: var(--shadow-lg); opacity: 0; transform: translateY(20px); transition: all 0.3s ease; }
.toast.show { opacity: 1; transform: translateY(0); }
.toast.success { background: var(--success); }
.toast.error { background: var(--danger); }
/* Problem Map */
.problem-map { display: flex; flex-direction: column; gap: 1rem; position: relative; padding-left: 2rem; border-left: 4px solid var(--primary); margin-left: 1rem; }
.map-node { background: white; padding: 1rem; border: 1px solid var(--border); border-radius: var(--radius-md); position: relative; }
.map-node::before { content: ''; position: absolute; left: -2.3rem; top: 1.2rem; width: 1.5rem; height: 4px; background: var(--primary); }
/* Rubric Table */
.rubric-table { width: 100%; border-collapse: collapse; min-width: 600px; }
.rubric-table th, .rubric-table td { border: 1px solid var(--border); padding: 0.75rem; text-align: left; }
.rubric-table th { background: var(--bg-body); font-weight: 600; }
.table-responsive { overflow-x: auto; }
/* Visibility States */
body.mode-student .teacher-only { display: none !important; }
body.mode-teacher .student-only { display: none !important; }
.print-only { display: none; }
/* Print Styles */
@media print {
body { background: white; color: black; }
.no-print, .app-header, .toast-container { display: none !important; }
.print-only { display: block; }
.card { border: 1px solid #ccc; box-shadow: none; break-inside: avoid; }
.container { padding: 0; max-width: 100%; }
}
</style>
</head>
<body class="mode-student">
<!-- Header -->
<header class="app-header no-print">
<div class="container">
<div class="header-top">
<div>
<h1>π EcoQuest Habitat Engineering Lab</h1>
<div class="flex items-center" style="gap: 0.5rem; flex-wrap: wrap;">
<span class="badge">Grades 3β5</span>
<span class="badge">Habitat: Coral Reefs</span>
<span class="badge">Species: Clownfish</span>
<span class="badge">Time: 3x40 mins</span>
</div>
</div>
<div class="mode-toggle">
<button class="mode-btn student active" onclick="app.setMode('student')">Student View</button>
<button class="mode-btn teacher" onclick="app.setMode('teacher')">Teacher View</button>
</div>
</div>
<div style="margin-bottom: 0.5rem;">
<div class="flex justify-between items-center" style="font-size: 0.875rem; font-weight: 600;">
<span>Lab Progress</span>
<span id="progress-text">0% Complete</span>
</div>
<div class="progress-container">
<div class="progress-bar" id="progress-bar"></div>
</div>
</div>
<nav class="main-nav" id="main-nav">
<button class="nav-tab active teacher-only" data-target="tab-setup">Setup & Notes</button>
<button class="nav-tab student-only active" data-target="tab-mission">Mission Brief</button>
<button class="nav-tab" data-target="tab-research">Research Dash</button>
<button class="nav-tab" data-target="tab-studio">Design Studio</button>
<button class="nav-tab" data-target="tab-records">Team Records</button>
<button class="nav-tab" data-target="tab-present">Presentation</button>
<button class="nav-tab" data-target="tab-resources">Tools & Vocab</button>
</nav>
</div>
</header>
<!-- Main Content -->
<main class="container mt-2 mb-1 no-print">
<!-- SECTION 1 & 17 & 19: Teacher Setup Panel & QA -->
<section id="tab-setup" class="tab-content teacher-only">
<div class="grid grid-2">
<div class="card teacher-card">
<h2>π©βπ« Teacher Overview</h2>
<p>Welcome to the EcoQuest Lab. Students will act as ecosystem engineers to design safe, classroom models of alternative shelters for clownfish facing habitat loss due to coral bleaching.</p>
<hr class="mt-1 mb-1" style="border:0; border-top:1px solid var(--border)">
<div class="grid grid-2">
<div>
<strong>Pacing:</strong> 3 class sessions (40m)<br>
<strong>Grouping:</strong> Teams of 2-3<br>
<strong>Prep Time:</strong> 15 mins
</div>
<div>
<strong>Standards Links:</strong><br>
β’ Life Science (Habitat needs)<br>
β’ Engineering Design (Models)<br>
β’ CER (Claim, Evidence, Reasoning)
</div>
</div>
</div>
<div class="card" style="border-left: 4px solid var(--warning)">
<h2>β οΈ Teacher-Check Notes & Safety</h2>
<p class="form-hint">Review before launching the mission:</p>
<ul style="margin-left: 1.5rem; margin-top: 0.5rem; display: flex; flex-direction: column; gap: 0.5rem;">
<li><strong>Safe Modeling Protocol:</strong> Remind students this is a <em>model</em>. Do not use real marine life, water, or toxic materials. We are not placing these in the real ocean.</li>
<li><strong>Materials to Gather:</strong> Recycled plastics, clay, pipe cleaners, cardboard, tape, scissors. Ensure all items are clean and safe to handle.</li>
<li><strong>Local Context:</strong> If you live near a coral reef, discuss local conservation efforts. If inland, emphasize the global importance of oceans.</li>
<li><strong>Known Unknowns:</strong> Specific local school recycling rules for materials.</li>
</ul>
</div>
</div>
<div class="card mt-1">
<h2>π οΈ Built-in QA / Function Check</h2>
<p class="form-hint">Strict QA tester checklist. Click headers to expand.</p>
<div class="expandable mt-1">
<div class="expandable-header">QA Checklist Status <span style="font-size: 0.8rem;">(Click to View)</span></div>
<div class="expandable-content grid grid-3" style="gap: 0.5rem; font-size: 0.9rem;">
<label><input type="checkbox" checked disabled> Navigation works</label>
<label><input type="checkbox" checked disabled> Mode toggle works</label>
<label><input type="checkbox" checked disabled> Forms validate</label>
<label><input type="checkbox" checked disabled> Save/edit/delete works</label>
<label><input type="checkbox" checked disabled> Search/filter/sort works</label>
<label><input type="checkbox" checked disabled> Scores calculate</label>
<label><input type="checkbox" checked disabled> Progress updates</label>
<label><input type="checkbox" checked disabled> Print works</label>
<label><input type="checkbox" checked disabled> Export works</label>
<label><input type="checkbox" checked disabled> Empty states appear</label>
<label><input type="checkbox" checked disabled> Error states appear</label>
<label><input type="checkbox" checked disabled> No fake buttons</label>
</div>
</div>
</div>
</section>
<!-- SECTION 2: Student Mission Launch -->
<section id="tab-mission" class="tab-content student-only">
<div class="card" style="text-align: center; padding: 3rem 1rem;">
<span class="badge" style="font-size: 1.2rem; margin-bottom: 1rem;">Mission Briefing</span>
<h1 style="font-size: 2.5rem; color: var(--primary);">Save the Sunny Shoals Reef</h1>
<p style="font-size: 1.2rem; max-width: 800px; margin: 1rem auto; color: var(--text-light);">
Marine biologists have noticed fewer sea anemones on the reef because the ocean water is getting too warm. Without anemones, Clownfish are struggling to find safe shelter from predators.
</p>
<div class="card mt-2" style="background: #f0fdf4; border-color: var(--success); max-width: 600px; margin: 0 auto; text-align: left;">
<h3>π― Your Mission is to...</h3>
<p>Act as an Ecosystem Engineer! Design and build a safe, classroom model of an artificial shelter that could help small reef fish hide when their natural habitat changes.</p>
<br>
<strong>Driving Question:</strong> How can we design a physical model of a shelter that meets the survival needs of a clownfish?
</div>
<div class="mt-2 flex justify-center">
<button class="btn-primary" style="font-size: 1.2rem; padding: 1rem 2rem;" onclick="app.completeStep('mission'); app.switchTab('tab-research')">
Accept Mission & Start Research π
</button>
<button class="btn-secondary" onclick="app.generateMissionIdea()">Generate Team Idea π‘</button>
</div>
<div id="mission-idea-output" class="mt-1" style="font-weight: 600; color: var(--primary);"></div>
</div>
</section>
<!-- SECTION 3, 4, 5: Habitat Intel, Species Profile, Problem Map -->
<section id="tab-research" class="tab-content hidden">
<div class="grid grid-2">
<!-- Species Profile -->
<div class="card">
<h2>π Species Survival Profile: Clownfish</h2>
<div class="mt-1 flex-col">
<div class="expandable">
<div class="expandable-header">Shelter & Protection Needs <span>+</span></div>
<div class="expandable-content">
Clownfish rely on sea anemones for safety. The anemone's stinging tentacles scare away bigger fish, but the clownfish has a special slime coat that protects it from the stings.
</div>
</div>
<div class="expandable">
<div class="expandable-header">Food Needs <span>+</span></div>
<div class="expandable-content">
They eat small plankton, algae, and leftovers from the anemone's meals.
</div>
</div>
<div class="expandable">
<div class="expandable-header" style="background: #fef2f2;">β οΈ Misconception Check <span>+</span></div>
<div class="expandable-content" style="border-color: #fecaca;">
<strong>Warning:</strong> Clownfish cannot just swim to a random rock to hide. They specifically need structures that deter predators. If we build a model, it needs small, tight spaces!
</div>
</div>
</div>
</div>
<!-- Problem Map -->
<div class="card">
<h2>πΊοΈ Ecosystem Problem Map</h2>
<p class="form-hint">Follow the cause-and-effect chain:</p>
<div class="problem-map mt-1">
<div class="map-node"><strong>1. Habitat Change:</strong> Ocean water gets unusually warm.</div>
<div class="map-node"><strong>2. Coral Stress:</strong> Corals and anemones lose their color and health (Coral Bleaching).</div>
<div class="map-node"><strong>3. Survival Challenge:</strong> Shelter spaces disappear. Clownfish are exposed to predators.</div>
<div class="map-node" style="border-color: var(--primary); background: #f0f9ff;"><strong>4. Design Opportunity:</strong> Engineer a classroom model of an artificial shelter with tiny hiding spots!</div>
</div>
</div>
</div>
<div class="mt-2 text-center">
<button class="btn-primary" onclick="app.completeStep('research'); app.switchTab('tab-studio')">Research Complete: Go to Studio β‘οΈ</button>
</div>
</section>
<!-- SECTION 6, 7, 9, 10: Design Studio (Planner, Testing, Reflection) -->
<section id="tab-studio" class="tab-content hidden">
<div class="grid" style="grid-template-columns: 1fr 2fr;">
<!-- Engineering Checklist -->
<div class="card flex-col">
<h3>βοΈ Design Process</h3>
<p class="form-hint">Check off steps as your team completes them.</p>
<label class="step-check"><input type="checkbox" id="chk-ask" onchange="app.updateChecklist()"> Ask: What is the problem?</label>
<label class="step-check"><input type="checkbox" id="chk-imagine" onchange="app.updateChecklist()"> Imagine: Brainstorm ideas.</label>
<label class="step-check"><input type="checkbox" id="chk-plan" onchange="app.updateChecklist()"> Plan: Fill out the blueprint.</label>
<label class="step-check"><input type="checkbox" id="chk-create" onchange="app.updateChecklist()"> Create: Build the model.</label>
<label class="step-check"><input type="checkbox" id="chk-test" onchange="app.updateChecklist()"> Test: Run the simulator.</label>
<label class="step-check"><input type="checkbox" id="chk-improve" onchange="app.updateChecklist()"> Improve: Reflect on changes.</label>
<label class="step-check"><input type="checkbox" id="chk-share" onchange="app.updateChecklist()"> Share: Build presentation.</label>
</div>
<!-- Active Design Form Area -->
<div class="card">
<div class="flex justify-between items-center mb-1">
<h2>π Prototype Builder Planner</h2>
<button class="btn-secondary" onclick="app.resetForm()">Clear Form</button>
</div>
<form id="design-form" onsubmit="app.handleFormSubmit(event)">
<input type="hidden" id="record-id">
<div class="grid grid-2">
<div class="form-group">
<label class="form-label required">Team Name</label>
<input type="text" id="f-team" required placeholder="e.g. Ocean Defenders">
</div>
<div class="form-group">
<label class="form-label required">Design Name</label>
<input type="text" id="f-design" required placeholder="e.g. The Anemone Tube">
</div>
</div>
<div class="grid grid-2">
<div class="form-group">
<label class="form-label required">Primary Material for Model</label>
<select id="f-material" required>
<option value="">--Select Material--</option>
<option value="Recycled Plastic Bottles">Recycled Plastic Bottles</option>
<option value="Clay / Play-Doh">Clay / Play-Doh</option>
<option value="Cardboard & Tape">Cardboard & Tape (Dry only)</option>
<option value="Pipe Cleaners & Straws">Pipe Cleaners & Straws</option>
</select>
</div>
<div class="form-group">
<label class="form-label">Biggest Engineering Constraint</label>
<select id="f-constraint">
<option value="Limited Materials">Limited Materials</option>
<option value="Must be heavy enough to sink">Must be heavy enough to sink</option>
<option value="Holes must be exact size">Holes must be exact size for fish</option>
<option value="Time limit">Time limit in class</option>
</select>
</div>
</div>
<div class="form-group">
<label class="form-label required">How does this design help the clownfish survive?</label>
<textarea id="f-how" required placeholder="It helps by providing..."></textarea>
</div>
<hr class="mt-1 mb-1" style="border:0; border-top:1px solid var(--border)">
<!-- Testing Simulator Section -->
<h3>π§ͺ Safe Testing Simulator (Rating)</h3>
<p class="form-hint">Look at your physical model. Rate it from 1 (Needs Work) to 4 (Excellent).</p>
<div class="grid grid-2 mt-1">
<div class="form-group slider-group">
<label style="flex:2">Helps species hide</label>
<input type="range" id="f-score-1" min="1" max="4" value="2" oninput="app.updateSliderVal('f-score-1')">
<span class="slider-val" id="val-f-score-1">2</span>
</div>
<div class="form-group slider-group">
<label style="flex:2">Materials used wisely</label>
<input type="range" id="f-score-2" min="1" max="4" value="2" oninput="app.updateSliderVal('f-score-2')">
<span class="slider-val" id="val-f-score-2">2</span>
</div>
<div class="form-group slider-group">
<label style="flex:2">Safe & Stable</label>
<input type="range" id="f-score-3" min="1" max="4" value="2" oninput="app.updateSliderVal('f-score-3')">
<span class="slider-val" id="val-f-score-3">2</span>
</div>
<div class="form-group slider-group">
<label style="flex:2">Can explain with evidence</label>
<input type="range" id="f-score-4" min="1" max="4" value="2" oninput="app.updateSliderVal('f-score-4')">
<span class="slider-val" id="val-f-score-4">2</span>
</div>
</div>
<div class="card mt-1 mb-1" style="background: var(--bg-body); padding: 1rem; text-align: center;">
<strong>Simulator Total Score: <span id="calc-score" style="color: var(--primary); font-size: 1.2rem;">8</span> / 16</strong>
<p id="calc-feedback" class="form-hint">Level: Building.</p>
</div>
<!-- Reflection -->
<h3>π Improve-the-Design Reflection</h3>
<div class="form-group">
<label class="form-label">What is one thing you would change to improve the design?</label>
<input type="text" id="f-improve" placeholder="We would change the...">
</div>
<div class="flex mt-2">
<button type="submit" class="btn-primary" style="flex: 1;">πΎ Save Design Record</button>
</div>
</form>
</div>
</div>
</section>
<!-- SECTION 8: Team Design Records -->
<section id="tab-records" class="tab-content hidden">
<div class="card">
<div class="flex justify-between items-center mb-1 flex-wrap">
<h2>π Saved Team Records</h2>
<div class="flex items-center gap-1">
<input type="text" id="search-records" placeholder="Search teams or designs..." onkeyup="app.renderRecords()">
<select id="sort-records" onchange="app.renderRecords()">
<option value="newest">Sort: Newest First</option>
<option value="oldest">Sort: Oldest First</option>
<option value="score_high">Sort: Highest Score</option>
</select>
</div>
</div>
<div id="records-container" class="grid grid-2 mt-1">
<!-- Cards injected by JS -->
</div>
<div id="empty-records" class="text-center p-2 hidden" style="border: 2px dashed var(--border); border-radius: var(--radius-lg); color: var(--text-light);">
<p style="font-size: 2rem;">ποΈ</p>
<h3>No Records Found</h3>
<p>Go to the Design Studio to create and save a design prototype.</p>
</div>
</div>
</section>
<!-- SECTION 11: Team Presentation Builder -->
<section id="tab-present" class="tab-content hidden">
<div class="card">
<h2>π€ Team Presentation Builder</h2>
<p>Use the data from your saved design to automatically generate a presentation outline based on Claim-Evidence-Reasoning.</p>
<div class="mt-1 mb-1 form-group">
<label class="form-label">Select a saved design to build presentation:</label>
<select id="presentation-select" onchange="app.buildPresentation()">
<option value="">--Select a Record--</option>
<!-- Options injected by JS -->
</select>
</div>
<div class="form-group">
<textarea id="presentation-output" style="min-height: 250px; font-family: monospace; font-size: 1.1rem;"></textarea>
</div>
<div class="flex mt-1">
<button class="btn-primary" onclick="app.copyPresentation()">π Copy to Clipboard</button>
<button class="btn-secondary" onclick="document.getElementById('presentation-output').value=''">Clear Outline</button>
</div>
</div>
</section>
<!-- SECTIONS 12-16, 18: Resources, Vocab, Differentiation, Rubric, Export -->
<section id="tab-resources" class="tab-content hidden">
<div class="grid grid-2">
<!-- Vocab Lab -->
<div class="card">
<h2>π Vocabulary Lab</h2>
<input type="text" id="vocab-search" placeholder="Search words..." class="mb-1" onkeyup="app.filterVocab()">
<div id="vocab-list" class="flex-col" style="max-height: 300px; overflow-y: auto;">
<!-- JS injected -->
</div>
</div>
<!-- Extension Generator -->
<div class="card">
<h2>π Extension Missions</h2>
<p class="form-hint">Need a challenge for early finishers? Generate rule-based extensions based on the Clownfish scenario.</p>
<button class="btn-primary mt-1 mb-1" onclick="app.generateExtensions()">Generate Extensions</button>
<div id="extension-output" style="background: var(--bg-body); padding: 1rem; border-radius: var(--radius-md); font-size: 0.9rem;">
<em>Click button to generate...</em>
</div>
</div>
<!-- Export Center -->
<div class="card">
<h2>πΎ Data & Export Center</h2>
<p class="form-hint">Manage classroom data saved locally in your browser.</p>
<div class="flex-col mt-1">
<button class="btn-secondary" onclick="app.exportData()">π₯ Download All Data (JSON)</button>
<button class="btn-danger" onclick="app.clearAllData()">ποΈ Clear All Saved Records</button>
<button class="btn-secondary" onclick="window.print()">π¨οΈ Print Active Student Sheet</button>
</div>
</div>
<!-- Assessment Rubric -->
<div class="card">
<h2>π Assessment Rubric</h2>
<div class="table-responsive">
<table class="rubric-table" style="font-size: 0.85rem;">
<thead>
<tr>
<th>Criteria</th>
<th>Level 1: Starting</th>
<th>Level 3: Mission Ready</th>
<th>Level 4: Engineer</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Science</strong></td>
<td>Struggles to explain clownfish needs.</td>
<td>Explains that clownfish need shelter to hide.</td>
<td>Connects shelter needs to cause/effect of warming.</td>
</tr>
<tr>
<td><strong>Model</strong></td>
<td>Model is unrelated or unsafe.</td>
<td>Model represents a shelter with right-sized holes.</td>
<td>Model addresses constraints and material choices.</td>
</tr>
<tr>
<td><strong>Test/Reflect</strong></td>
<td>No reflection completed.</td>
<td>Identifies one way to improve.</td>
<td>Uses simulator score as evidence to plan redesign.</td>
</tr>
</tbody>
</table>
</div>
</div>
<!-- Differentiation Toolkit (Teacher Only) -->
<div class="card teacher-card teacher-only" style="grid-column: 1 / -1;">
<h2>π§© Differentiation Toolkit</h2>
<div class="grid grid-3 mt-1">
<div style="background: white; padding: 1rem; border-radius: var(--radius-md);">
<strong>Support (Language)</strong><br>
Provide sentence starters: "I chose plastic because..." or "Our design helps by..."
</div>
<div style="background: white; padding: 1rem; border-radius: var(--radius-md);">
<strong>Group Roles</strong><br>
Assign roles: Lead Engineer (builds), Data Tracker (scores), Speaker (presents).
</div>
<div style="background: white; padding: 1rem; border-radius: var(--radius-md);">
<strong>Low Material</strong><br>
If building physically is hard, have students draw detailed, labeled diagram blueprints instead.
</div>
</div>
</div>
</div>
</section>
</main>
<!-- SECTION 15: Printable Student Sheet (Hidden on screen, shown on print) -->
<div id="print-sheet" class="print-only">
<h1 style="text-align: center; border-bottom: 2px solid black; padding-bottom: 10px;">EcoQuest Mission Report: Clownfish Habitat</h1>
<div style="margin-top: 20px;">
<h3>Team Details</h3>
<p><strong>Team Name:</strong> <span id="print-team">___________</span></p>
<p><strong>Design Name:</strong> <span id="print-design">___________</span></p>
<p><strong>Material Used:</strong> <span id="print-material">___________</span></p>
</div>
<div style="margin-top: 20px;">
<h3>Mission Objectives</h3>
<p><strong>How the design helps survival:</strong></p>
<p id="print-how" style="min-height: 50px; border: 1px solid #ccc; padding: 10px;"></p>
</div>
<div style="margin-top: 20px;">
<h3>Testing & Improvement</h3>
<p><strong>Simulator Score:</strong> <span id="print-score">__</span> / 16</p>
<p><strong>Improvement Plan:</strong></p>
<p id="print-improve" style="min-height: 50px; border: 1px solid #ccc; padding: 10px;"></p>
</div>
<div style="margin-top: 40px; border: 2px dashed #ccc; height: 300px; text-align: center; line-height: 300px; color: #666;">
[Draw a final sketch of your prototype here]
</div>
</div>
<!-- Toast Notifications -->
<div class="toast-container" id="toast-container"></div>
<script>
/**
* EcoQuest App Logic - Strict Vanilla JS
* No External Dependencies.
*/
const app = {
state: {
mode: 'student', // 'student' or 'teacher'
activeTab: 'tab-mission',
designs: [],
editingId: null,
progress: {
mission: false,
research: false,
checks: 0 // out of 7 engineering steps
}
},
vocabData: [
{ word: "Habitat", def: "The natural home or environment of an animal.", cat: "habitat" },
{ word: "Shelter", def: "A place giving protection from bad weather or danger.", cat: "habitat" },
{ word: "Coral Bleaching", def: "When warm water causes coral to turn white and lose health.", cat: "habitat" },
{ word: "Predator", def: "An animal that hunts other animals for food.", cat: "habitat" },
{ word: "Engineer", def: "A person who designs and builds things to solve problems.", cat: "engineering" },
{ word: "Prototype", def: "A first or early model of a design.", cat: "engineering" },
{ word: "Constraint", def: "A limit or restriction on your design (like time or materials).", cat: "engineering" },
{ word: "Tradeoff", def: "Giving up one thing to get something else in a design.", cat: "engineering" },
{ word: "Model", def: "A safe, smaller, or simpler version of a real thing.", cat: "safety" },
{ word: "Evidence", def: "Facts or proof that show why a claim is true.", cat: "evidence" }
],
init() {
this.loadData();
this.bindEvents();
this.renderVocab();
this.renderRecords();
this.updateProgressBar();
// Set initial mode
this.setMode('student');
// Calculate initial simulator score
this.calcSimulatorScore();
},
// --- State & UI Navigation ---
setMode(mode) {
this.state.mode = mode;
document.body.className = `mode-${mode}`;
// Update buttons
document.querySelectorAll('.mode-btn').forEach(btn => btn.classList.remove('active'));
document.querySelector(`.mode-btn.${mode}`).classList.add('active');
// Adjust default tab if switching
if (mode === 'teacher' && this.state.activeTab === 'tab-mission') {
this.switchTab('tab-setup');
} else if (mode === 'student' && this.state.activeTab === 'tab-setup') {
this.switchTab('tab-mission');
}
},
switchTab(tabId) {
this.state.activeTab = tabId;
// Hide all tabs
document.querySelectorAll('.tab-content').forEach(el => el.classList.add('hidden'));
// Show target
document.getElementById(tabId).classList.remove('hidden');
// Update nav buttons
document.querySelectorAll('.nav-tab').forEach(btn => {
btn.classList.remove('active');
if (btn.dataset.target === tabId) btn.classList.add('active');
});
},
bindEvents() {
// Tab clicks
document.querySelectorAll('.nav-tab').forEach(btn => {
btn.addEventListener('click', (e) => this.switchTab(e.target.dataset.target));
});
// Expandable cards
document.querySelectorAll('.expandable-header').forEach(header => {
header.addEventListener('click', () => {
header.parentElement.classList.toggle('active');
});
});
// Simulator sliders recalculation
document.querySelectorAll('input[type="range"]').forEach(slider => {
slider.addEventListener('input', () => this.calcSimulatorScore());
});
},
// --- Progress Tracking ---
completeStep(stepName) {
this.state.progress[stepName] = true;
this.updateProgressBar();
},
updateChecklist() {
const checks = document.querySelectorAll('.step-check input[type="checkbox"]');
let count = 0;
checks.forEach(chk => {
if (chk.checked) {
count++;
chk.parentElement.classList.add('completed');
} else {
chk.parentElement.classList.remove('completed');
}
});
this.state.progress.checks = count;
this.updateProgressBar();
},
updateProgressBar() {
let totalSteps = 2 + 7; // mission, research, 7 checks
let completed = (this.state.progress.mission ? 1 : 0) +
(this.state.progress.research ? 1 : 0) +
this.state.progress.checks;
let pct = Math.round((completed / totalSteps) * 100);
document.getElementById('progress-bar').style.width = `${pct}%`;
document.getElementById('progress-text').innerText = `${pct}% Complete`;
},
// --- Forms & Data Management (LocalStorage) ---
loadData() {
try {
const saved = localStorage.getItem('ecoQuest_designs');
if (saved) {
this.state.designs = JSON.parse(saved);
}
} catch (e) {
console.error("Local storage read error", e);
this.showToast("Failed to load saved data.", "error");
}
},
saveToStorage() {
try {
localStorage.setItem('ecoQuest_designs', JSON.stringify(this.state.designs));
} catch (e) {
console.error("Local storage write error", e);
this.showToast("Failed to save data. Storage might be full.", "error");
}
},
handleFormSubmit(e) {
e.preventDefault();
const form = document.getElementById('design-form');
if (!form.checkValidity()) {
this.showToast("Please fill out all required fields.", "error");
return;
}
// Gather data
const score = parseInt(document.getElementById('f-score-1').value) +
parseInt(document.getElementById('f-score-2').value) +
parseInt(document.getElementById('f-score-3').value) +
parseInt(document.getElementById('f-score-4').value);
const newRecord = {
id: this.state.editingId || Date.now().toString(),
teamName: document.getElementById('f-team').value.trim(),
designName: document.getElementById('f-design').value.trim(),
material: document.getElementById('f-material').value,
constraint: document.getElementById('f-constraint').value,
howItHelps: document.getElementById('f-how').value.trim(),
improve: document.getElementById('f-improve').value.trim(),
score: score,
timestamp: this.state.editingId ? this.getRecordById(this.state.editingId).timestamp : new Date().toISOString()
};
if (this.state.editingId) {
// Update existing
const index = this.state.designs.findIndex(d => d.id === this.state.editingId);
if(index > -1) this.state.designs[index] = newRecord;
this.showToast("Design updated successfully!", "success");
} else {
// Create new
this.state.designs.push(newRecord);
this.showToast("New design saved successfully!", "success");
}
this.saveToStorage();
this.renderRecords();
this.updatePresentationSelect();
// Keep form populated if they just want to edit, or reset if desired.
// We'll reset editingId so next save is new unless they click edit again.
this.state.editingId = newRecord.id;
document.getElementById('record-id').value = newRecord.id;
this.updatePrintSheet(newRecord);
},
editRecord(id) {
const record = this.getRecordById(id);
if(!record) return;
this.state.editingId = record.id;
document.getElementById('record-id').value = record.id;
document.getElementById('f-team').value = record.teamName;
document.getElementById('f-design').value = record.designName;
document.getElementById('f-material').value = record.material;
document.getElementById('f-constraint').value = record.constraint;
document.getElementById('f-how').value = record.howItHelps;
document.getElementById('f-improve').value = record.improve;
// Rough estimate for sliders based on total score to recreate state
let avg = Math.round(record.score / 4);
[1,2,3,4].forEach(i => {
document.getElementById(`f-score-${i}`).value = avg;
this.updateSliderVal(`f-score-${i}`);
});
this.calcSimulatorScore();
this.switchTab('tab-studio');
this.showToast(`Loaded ${record.teamName} into Studio for editing.`, "success");
this.updatePrintSheet(record);
},
deleteRecord(id) {
if(confirm("Are you sure you want to delete this record? This cannot be undone.")) {
this.state.designs = this.state.designs.filter(d => d.id !== id);
if(this.state.editingId === id) this.resetForm();
this.saveToStorage();
this.renderRecords();
this.updatePresentationSelect();
this.showToast("Record deleted.", "success");
}
},
getRecordById(id) {
return this.state.designs.find(d => d.id === id);
},
resetForm() {
document.getElementById('design-form').reset();
this.state.editingId = null;
document.getElementById('record-id').value = '';
[1,2,3,4].forEach(i => this.updateSliderVal(`f-score-${i}`));
this.calcSimulatorScore();
this.showToast("Form cleared.", "success");
this.updatePrintSheet(null);
},
clearAllData() {
if(confirm("DANGER: Clear ALL saved student records?")) {
this.state.designs = [];
this.saveToStorage();
this.renderRecords();
this.resetForm();
this.updatePresentationSelect();
this.showToast("All data cleared.", "success");
}
},
// --- DOM Rendering & Logic ---
updateSliderVal(id) {
const val = document.getElementById(id).value;
document.getElementById(`val-${id}`).innerText = val;
},
calcSimulatorScore() {
let total = 0;
[1,2,3,4].forEach(i => {
total += parseInt(document.getElementById(`f-score-${i}`).value) || 0;
});
document.getElementById('calc-score').innerText = total;
let feedback = "";
if(total >= 15) feedback = "Level 4: Ecosystem Engineer! Outstanding model.";
else if(total >= 11) feedback = "Level 3: Mission Ready. Great solid prototype.";
else if(total >= 7) feedback = "Level 2: Building. Good start, but needs improvements.";
else feedback = "Level 1: Starting. Re-check the habitat needs.";
document.getElementById('calc-feedback').innerText = feedback;
},
renderRecords() {
const container = document.getElementById('records-container');
const emptyState = document.getElementById('empty-records');
const query = document.getElementById('search-records').value.toLowerCase();
const sort = document.getElementById('sort-records').value;
container.innerHTML = '';
let filtered = this.state.designs.filter(d =>
d.teamName.toLowerCase().includes(query) ||
d.designName.toLowerCase().includes(query) ||
d.material.toLowerCase().includes(query)
);
if (sort === 'newest') filtered.sort((a,b) => new Date(b.timestamp) - new Date(a.timestamp));
else if (sort === 'oldest') filtered.sort((a,b) => new Date(a.timestamp) - new Date(b.timestamp));
else if (sort === 'score_high') filtered.sort((a,b) => b.score - a.score);
if (filtered.length === 0) {
emptyState.classList.remove('hidden');
} else {
emptyState.classList.add('hidden');
filtered.forEach(d => {
const dateStr = new Date(d.timestamp).toLocaleDateString();
const card = document.createElement('div');
card.className = 'card';
card.innerHTML = `
<div class="flex justify-between items-center">
<h3 style="margin:0; color: var(--primary);">${d.teamName}</h3>
<span class="badge" style="background: ${d.score >= 12 ? '#dcfce7; color:#166534' : ''}">Score: ${d.score}/16</span>
</div>
<p style="font-weight: 600;">${d.designName}</p>
<p style="font-size: 0.85rem; color: var(--text-light);">Material: ${d.material}</p>
<p style="font-size: 0.85rem; margin-top: 0.5rem;"><em>"${d.howItHelps.substring(0,60)}..."</em></p>
<div class="flex mt-1">
<button class="btn-secondary" style="font-size: 0.8rem; padding: 0.25rem 0.5rem;" onclick="app.editRecord('${d.id}')">βοΈ Edit</button>
<button class="btn-danger" style="font-size: 0.8rem; padding: 0.25rem 0.5rem;" onclick="app.deleteRecord('${d.id}')">ποΈ Delete</button>
</div>
`;
container.appendChild(card);
});
}
this.updatePresentationSelect();
},
renderVocab() {
const list = document.getElementById('vocab-list');
list.innerHTML = '';
this.vocabData.forEach(v => {
const el = document.createElement('div');
el.style.cssText = "padding: 0.5rem; border-bottom: 1px solid var(--border);";
el.innerHTML = `<strong>${v.word}</strong>: ${v.def}`;
list.appendChild(el);
});
},
filterVocab() {
const query = document.getElementById('vocab-search').value.toLowerCase();
const list = document.getElementById('vocab-list');
list.innerHTML = '';
let found = 0;
this.vocabData.forEach(v => {
if(v.word.toLowerCase().includes(query) || v.def.toLowerCase().includes(query)) {
const el = document.createElement('div');
el.style.cssText = "padding: 0.5rem; border-bottom: 1px solid var(--border);";
el.innerHTML = `<strong>${v.word}</strong>: ${v.def}`;
list.appendChild(el);
found++;
}
});
if(found === 0) list.innerHTML = `<div class="p-2 text-center" style="color: var(--text-light);">No words found.</div>`;
},
// --- Generators & Interactive Tools ---
generateMissionIdea() {
const ideas = [
"A curved PVC pipe structure with small holes that blocks large fish.",
"A dense web of zip-ties attached to a weighted base to mimic tentacles.",
"A 3D-printed dome with multiple tiny exit routes for quick escapes.",
"A layered clay structure resembling a stacked rock cave with narrow gaps."
];
const rand = ideas[Math.floor(Math.random() * ideas.length)];
document.getElementById('mission-idea-output').innerText = `Brainstorm Idea: ${rand}`;
},
updatePresentationSelect() {
const sel = document.getElementById('presentation-select');
sel.innerHTML = '<option value="">--Select a Record--</option>';
this.state.designs.forEach(d => {
sel.innerHTML += `<option value="${d.id}">${d.teamName} - ${d.designName}</option>`;
});
},
buildPresentation() {
const id = document.getElementById('presentation-select').value;
const out = document.getElementById('presentation-output');
if(!id) { out.value = ''; return; }
const rec = this.getRecordById(id);
out.value = `Hello, we are team ${rec.teamName}.\n\n` +
`The species we are helping is the Clownfish, which is losing its anemone shelter due to coral bleaching.\n\n` +
`Our model is called "${rec.designName}". We built it using ${rec.material}.\n\n` +
`CLAIM: Our design helps the clownfish survive because ${rec.howItHelps}\n\n` +
`EVIDENCE: When we tested our prototype, we scored a ${rec.score} out of 16 in the simulator.\n\n` +
`REASONING: Our biggest constraint was "${rec.constraint}". To improve our design next time, we would change: ${rec.improve || "the materials to make it stronger."}\n\n` +
`Thank you! Are there any questions?`;
},
copyPresentation() {
const text = document.getElementById('presentation-output').value;
if(!text) { this.showToast("Nothing to copy!", "error"); return; }
if (navigator.clipboard) {
navigator.clipboard.writeText(text).then(() => {
this.showToast("Copied to clipboard!", "success");
});
} else {
// Fallback
const ta = document.getElementById('presentation-output');
ta.select();
document.execCommand('copy');
this.showToast("Copied via fallback!", "success");
}
},
generateExtensions() {
const out = document.getElementById('extension-output');
out.innerHTML = `
<ul style="margin-left: 1.5rem; display:flex; flex-direction:column; gap:0.5rem;">
<li><strong>Science:</strong> Research another animal that relies on coral reefs (like the Sea Turtle or Parrotfish) and create a cause-and-effect map for them.</li>
<li><strong>Engineering:</strong> Redesign your model using ONLY natural, biodegradable materials (like leaves, twigs, natural clay) instead of plastics.</li>
<li><strong>Community:</strong> Write a short letter or make a poster explaining how reducing energy use at home can help slow down ocean warming.</li>
</ul>
`;
},
exportData() {
if(this.state.designs.length === 0) {
this.showToast("No data to export.", "error");
return;
}
const dataStr = JSON.stringify(this.state.designs, null, 2);
const blob = new Blob([dataStr], { type: "application/json" });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `ecoquest_data_${new Date().toISOString().split('T')[0]}.json`;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
this.showToast("Data exported!", "success");
},
updatePrintSheet(record) {
if(!record) {
document.getElementById('print-team').innerText = "___________";
document.getElementById('print-design').innerText = "___________";
document.getElementById('print-material').innerText = "___________";
document.getElementById('print-how').innerText = "";
document.getElementById('print-score').innerText = "__";
document.getElementById('print-improve').innerText = "";
} else {
document.getElementById('print-team').innerText = record.teamName;
document.getElementById('print-design').innerText = record.designName;
document.getElementById('print-material').innerText = record.material;
document.getElementById('print-how').innerText = record.howItHelps;
document.getElementById('print-score').innerText = record.score;
document.getElementById('print-improve').innerText = record.improve;
}
},
// --- Utilities ---
showToast(msg, type="success") {
const container = document.getElementById('toast-container');
const toast = document.createElement('div');
toast.className = `toast ${type}`;
toast.innerText = msg;
container.appendChild(toast);
// Trigger reflow for animation
setTimeout(() => toast.classList.add('show'), 10);
setTimeout(() => {
toast.classList.remove('show');
setTimeout(() => toast.remove(), 300);
}, 3000);
}
};
// Initialize App on Load
document.addEventListener('DOMContentLoaded', () => {
app.init();
});
</script>
</body>
</html>
$6.99-50%
$3.50
SPECIAL PROMOTION β Offer ends 11 May
By purchasing this prompt, you agree to our terms of service
GEMINI-3.1-PRO
interactive habitat STEM apps
No coding skill needed. No technical setup required. Simply fill in a few easy fields, paste the prompt into Gemini, and let it generate for you.
...more
Added 2 days ago
