File size: 11,436 Bytes
f56a29b | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 | # Simulation Widget Content Generator
Generate a self-contained HTML simulation with embedded widget configuration.
## Output Structure
Your output must be a complete HTML document with:
1. **Standard HTML5 structure**
2. **Embedded widget configuration** in a `<script type="application/json" id="widget-config">` tag
3. **Interactive controls** for variables
4. **Canvas or SVG visualization**
5. **Mobile-responsive design**
6. **postMessage listener** for teacher actions (REQUIRED)
## Widget Config Schema
```json
{
"type": "simulation",
"concept": "projectile_motion",
"description": "...",
"variables": [
{ "name": "angle", "label": "Launch Angle", "min": 0, "max": 90, "default": 45, "unit": "°" }
],
"presets": [
{ "name": "Hit the target", "variables": { "angle": 30, "velocity": 25 } }
]
}
```
## CRITICAL: postMessage Listener for Teacher Actions
Your HTML MUST include this message listener to respond to teacher actions:
```javascript
// Add this script at the end of your HTML
window.addEventListener('message', function(event) {
const { type, target, state, content } = event.data;
switch (type) {
case 'SET_WIDGET_STATE':
// Update all variables in the state object
if (state) {
Object.entries(state).forEach(([key, value]) => {
// Find the slider/input for this variable and update it
const slider = document.getElementById(key + '-slider') || document.querySelector('[data-var="' + key + '"]');
if (slider) {
slider.value = value;
// Trigger change event to update simulation
slider.dispatchEvent(new Event('input', { bubbles: true }));
}
});
}
break;
case 'HIGHLIGHT_ELEMENT':
// Highlight the target element with a pulsing border
const highlightEl = document.querySelector(target);
if (highlightEl) {
highlightEl.style.outline = '3px solid rgba(139, 92, 246, 0.8)';
highlightEl.style.outlineOffset = '4px';
highlightEl.style.animation = 'pulse-highlight 2s infinite';
// Remove highlight after 3 seconds
setTimeout(() => {
highlightEl.style.outline = '';
highlightEl.style.animation = '';
}, 3000);
}
break;
case 'ANNOTATE_ELEMENT':
// Show an annotation tooltip near the target element
const annotateEl = document.querySelector(target);
if (annotateEl && content) {
const rect = annotateEl.getBoundingClientRect();
const tooltip = document.createElement('div');
tooltip.className = 'teacher-annotation';
tooltip.style.cssText = 'position:fixed; top:' + (rect.top - 40) + 'px; left:' + rect.left + 'px; background:rgba(139,92,246,0.95); color:white; padding:8px 12px; border-radius:8px; font-size:14px; z-index:1000; animation:fadeIn 0.3s;';
tooltip.textContent = content;
document.body.appendChild(tooltip);
setTimeout(() => tooltip.remove(), 4000);
}
break;
case 'REVEAL_ELEMENT':
// Reveal a hidden element
const revealEl = document.querySelector(target);
if (revealEl) {
revealEl.style.display = '';
revealEl.style.opacity = '1';
}
break;
}
});
// Add this CSS for animations
const style = document.createElement('style');
style.textContent = '@keyframes pulse-highlight { 0%, 100% { outline-color: rgba(139, 92, 246, 0.8); } 50% { outline-color: rgba(139, 92, 246, 0.4); } } @keyframes fadeIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } }';
document.head.appendChild(style);
```
## Element Naming Convention
To make highlight/annotation work, use consistent IDs for controls:
- Sliders: `id="{variable_name}-slider"` (e.g., `id="angle-slider"`, `id="velocity-slider"`)
- Buttons: `id="{action}-btn"` (e.g., `id="start-btn"`, `id="reset-btn"`)
- Displays: `id="{variable_name}-display"` (e.g., `id="acceleration-display"`)
## CRITICAL Design Requirements
### 1. Mobile Layout - NO OVERLAP
- **Control panel MUST NOT overlap with canvas on mobile**
- Use one of these mobile-safe layouts:
- **Stacked layout**: Control panel on top, canvas below (with proper spacing)
- **Bottom sheet**: Control panel slides up from bottom on mobile
- **Side drawer**: Collapsible panel that doesn't block canvas
- Test viewport widths: 320px, 375px, 414px, 768px
- Use `min-height` for canvas to ensure it's visible on mobile
- Control panel should be collapsible on mobile if large
Example mobile-safe layout:
```html
<body class="flex flex-col min-h-screen md:flex-row">
<!-- Mobile: Full-width, collapsible control panel -->
<div id="controls" class="w-full md:w-80 shrink-0 overflow-auto max-h-[40vh] md:max-h-screen">
<!-- Controls here -->
<button onclick="toggleControls()" class="md:hidden">Hide Controls</button>
</div>
<!-- Canvas area gets remaining space -->
<div class="flex-1 min-h-[300px] relative">
<canvas id="canvas"></canvas>
</div>
</body>
```
### 2. Reset Button - MUST WORK CORRECTLY
- **Reset button MUST return simulation to initial state**
- Common bug: Button changes text to "重新开始" but clicking it doesn't reset
- Solution: Use a separate reset function, or check state properly
Correct implementation:
```javascript
let state = { running: false, ended: false, posX: 50, velocity: 0 };
function handleMainButton() {
if (state.ended) {
// If simulation ended, reset first
resetSimulation();
} else if (state.running) {
pauseSimulation();
} else {
startSimulation();
}
}
function resetSimulation() {
state.running = false;
state.ended = false;
state.posX = 50; // Reset to initial position!
state.velocity = 0; // Reset velocity!
updateButton('启动');
draw();
}
// When simulation hits boundary/ends:
function onSimulationEnd() {
state.running = false;
state.ended = true;
updateButton('重新开始');
}
function updateButton(text) {
document.getElementById('mainBtn').innerText = text;
}
```
### 3. Button State Management
- Use clear state variables: `running`, `paused`, `ended`
- Button text should reflect what will happen when clicked:
- "启动" / "开始" → Start simulation
- "暂停" / "暂停" → Pause running simulation
- "继续" / "继续" → Resume paused simulation
- "重新开始" / "重试" → Reset and start fresh (when ended)
- One button should NOT do different things based on text alone
### 4. Touch-Friendly Controls
- Minimum touch target: 44x44px for buttons
- Sliders: Increase thumb size for mobile (min 24px)
- Add `touch-action: manipulation` to prevent double-tap zoom
- Use `touch-action: none` on canvas for custom gesture handling
### 5. Canvas Sizing
- Use `ResizeObserver` or window resize event
- Canvas should fill available space but respect `max-height`
- Don't use fixed pixel dimensions
- Account for control panel height on mobile
### 6. Visual Feedback
- Clear indication when simulation starts/pauses/ends
- Show current state in UI (running indicator, paused icon)
- Highlight end boundary or target
- Show success/failure message when simulation ends
- Animate the "重新开始" button appearance
### 7. Visible Animation (CRITICAL)
**When the user clicks "启动" (Start), there MUST be OBVIOUS visual animation.**
#### Animation Requirements:
1. **Moving objects**: Objects should visibly move, rotate, or change when simulation runs
2. **Clear motion**: Animation should be immediately noticeable - not subtle
3. **Rotation animations**: For spinning/rotating objects (earth, wheels, etc.), show actual rotation:
```javascript
// GOOD: Earth visibly rotates
function draw() {
ctx.clearRect(0, 0, w, h);
ctx.save();
ctx.translate(centerX, centerY);
ctx.rotate(rotationAngle); // Earth rotates!
// Draw earth content...
ctx.restore();
if (state.running) {
rotationAngle += 0.02 * state.speed; // Update rotation
}
}
```
4. **Multiple visual cues**: Combine motion with other feedback:
- Object position/rotation changes
- Clock/timer updates
- Color changes or highlights
- Particle effects for dynamic simulations
#### BAD Example (User can't tell if it's running):
```javascript
// Earth is static 2D circle, only time number changes
// User clicks "Start" → Nothing visibly moves → Confusing!
```
#### GOOD Example (Clear visual feedback):
```javascript
// Earth rotates, sun position moves, day/night boundary shifts
// User clicks "Start" → Earth visibly spins → Satisfying!
```
### 8. Data Display
- Real-time values should be clearly visible
- Use monospace font for numbers
- Show units consistently
- Consider a floating info panel that doesn't block the simulation
### 9. Presets
- Each preset should clearly describe what it demonstrates
- Preset buttons should be touch-friendly (larger on mobile)
- Applying a preset should reset the simulation
### 10. Accessibility
- ARIA labels on all controls
- Keyboard support (Space to start/pause, R to reset)
- Focus indicators
- High contrast text on canvas
### 11. Performance
- Use `requestAnimationFrame` for animations
- Clear canvas each frame
- Don't create objects in render loop
- Throttle slider input events if needed
## Common Bugs to Avoid
| Bug | Cause | Solution |
|-----|-------|----------|
| Reset doesn't work | Button calls wrong function | Ensure reset function resets ALL state variables |
| Canvas overlap on mobile | Fixed positioning | Use flex/grid with proper responsive classes |
| Simulation stuck | Missing `ended` state | Track `ended` separately from `running` |
| Button does nothing | State logic error | Clear state machine with defined transitions |
| Touch issues | Small touch targets | Min 44px touch targets, larger sliders |
## Output Format
Return ONLY the HTML document, no markdown fences or explanations.
**CRITICAL: Output EXACTLY ONE HTML document.**
- Do NOT duplicate content
- Do NOT include multiple `<!DOCTYPE html>` tags
- The output must end with exactly one `</html>` tag
## Object Positioning with UI Overlays
When calculating positions for simulation objects, account for UI overlays:
```javascript
// BAD: Object overlaps with controls/HUD
const objectY = baseY - (value / maxValue) * canvas.height;
// GOOD: Reserve space for UI elements
const TOP_MARGIN = 100; // Space for HUD/stats at top
const BOTTOM_MARGIN = 200; // Space for controls at bottom
const playableHeight = canvas.height - TOP_MARGIN - BOTTOM_MARGIN;
const objectY = baseY - BOTTOM_MARGIN - (value / maxValue) * playableHeight;
```
## Quality Checklist (verify before output)
- [ ] Control panel does NOT overlap canvas on mobile (test 320px width)
- [ ] Reset button returns simulation to EXACT initial state
- [ ] Button text matches button action correctly
- [ ] Touch targets are at least 44px
- [ ] Canvas resizes properly on window resize
- [ ] State machine is clear (running/paused/ended)
- [ ] All state variables reset on resetSimulation()
- [ ] Works on both desktop and mobile browsers
- [ ] **NO DUPLICATED HTML** - exactly ONE `<!DOCTYPE html>` tag
- [ ] Simulation objects are visible and not hidden under UI overlays
- [ ] **Visible animation: Objects visibly move/rotate when simulation runs**
- [ ] **Animation is OBVIOUS, not subtle - user can tell simulation is running** |