
Skill
Advanced Visualization Techniques
create advanced generative UI visualizations
Description
UI mockups, dashboards, advanced interactivity, generative art, simulations, math visualizations, and design system rules for producing rich generateSandboxedUi output.
SKILL.md
Agent Visualization Skills — Volume 2: Advanced Techniques
Prerequisite: Volume 1 (SVG diagrams, basic interactive widgets, Chart.js, Mermaid). This volume covers: UI mockups, dashboards, advanced interactivity, generative art, simulations, math visualizations, and the design system that ties everything together.
Part 0: The Tool Contract — generateSandboxedUi
Everything in this volume ships through the generateSandboxedUi tool. The UI
streams as you generate it, so emit the parameters in this EXACT order:
initialHeight— estimated height of the finished UI in px.placeholderMessages— 2-4 short, playful progress messages.css— ALL styles, up front. The user sees a placeholder until css is complete, so keep it lean and put every style here for the css-first reveal.html— clean body markup, streamed in live. No<style>blocks (the css parameter owns all styles), no monolithic inline<script>blocks.jsFunctions— named function declarations: the reusable toolbox of behavior.jsExpressions— small statements invoking those functions, applied one-by-one so the user watches each take effect.
Write parameterized generators in jsFunctions (drawWing(color), not
drawRedWing()). A well-parameterized toolbox lets a later refinement turn —
"make the wings red" — append ONE new expression to jsExpressions instead of
regenerating the whole document.
The sandbox iframe has NO same-origin access: no localStorage, sessionStorage,
cookies, IndexedDB, or same-origin fetch. The host bridge is
await Websandbox.connection.remote.sendPrompt({ text }) and
await Websandbox.connection.remote.openLink({ url }) (https only). The design
system (Part 1) and an importmap for three, gsap, d3, and chart.js
(Part 7) are pre-injected.
Part 1: The Design System
Every visual you produce should feel native to the host interface — not like an embedded iframe from somewhere else. These rules apply to ALL output types.
CSS Variables (Auto Light/Dark Mode)
/* Backgrounds */
--color-background-primary /* white in light, near-black in dark */
--color-background-secondary /* surface cards */
--color-background-tertiary /* page background */
--color-background-info /* blue tint */
--color-background-danger /* red tint */
--color-background-success /* green tint */
--color-background-warning /* amber tint */
/* Text */
--color-text-primary /* main text */
--color-text-secondary /* muted / labels */
--color-text-tertiary /* hints / placeholders */
--color-text-info / -danger / -success / -warning
/* Borders */
--color-border-tertiary /* default: 0.15 alpha */
--color-border-secondary /* hover: 0.3 alpha */
--color-border-primary /* active: 0.4 alpha */
/* Typography */
--font-sans /* default body font */
--font-serif /* editorial / blockquote only */
--font-mono /* code */
/* Layout */
--border-radius-md /* 8px - most elements */
--border-radius-lg /* 12px - cards */
--border-radius-xl /* 16px - large containers */
Critical rule: Never hardcode colors like #333 or #fff in HTML.
They break in the opposite mode. Always use CSS variables.
Typography Rules
- h1 = 22px, h2 = 18px, h3 = 16px — all font-weight: 500
- Body = 16px, weight 400, line-height: 1.7
- Only two weights: 400 (regular) and 500 (medium). Never 600 or 700.
- Sentence case everywhere. Never Title Case or ALL CAPS.
- No mid-sentence bolding. Use
code stylefor entity/class/function names. - No font-size below 11px anywhere.
Component Tokens
- Borders:
0.5px solid var(--color-border-tertiary) - Cards:
background: var(--color-background-primary),border: 0.5px solid var(--color-border-tertiary),border-radius: var(--border-radius-lg),padding: 1rem 1.25rem - No gradients, drop shadows, blur, glow, or neon effects
- No emoji — use CSS shapes or SVG paths for icons
- Background of outer container is always transparent
Number Formatting
Always round displayed numbers. JavaScript float math leaks artifacts:
0.1 + 0.2 = 0.30000000000000004. Every number on screen must go through
Math.round(), .toFixed(n), or Intl.NumberFormat.
Part 2: UI Mockups
For when the user asks you to design or prototype a UI.
When to Use
- "Design a settings page for..."
- "Mock up a dashboard"
- "What should this form look like?"
- "Show me a card layout for..."
- Prototyping before building
Presentation Rules
Contained mockups (mobile screens, modals, chat threads, single cards): Wrap in a background surface so they don't float naked:
<div style="background: var(--color-background-secondary);
border-radius: var(--border-radius-lg);
padding: 2rem; display: flex; justify-content: center;">
<!-- Your mockup inside -->
</div>
Full-width mockups (dashboards, settings pages, data tables): No wrapper needed — they naturally fill the viewport.
Where the styles go: repeated patterns become classes in the css parameter; the html parameter stays clean markup. The metric cards below model the translation — the remaining patterns in this part are shown with inline style attributes for compactness, and you should lift them into css-parameter classes the same way.
Metric Cards (for dashboards)
css parameter:
.metric-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
gap: 12px;
margin-bottom: 1.5rem;
}
.metric-card {
background: var(--color-background-secondary);
border-radius: var(--border-radius-md);
padding: 1rem;
}
.metric-label {
font-size: 13px;
color: var(--color-text-secondary);
margin-bottom: 4px;
}
.metric-value { font-size: 24px; font-weight: 500; }
html parameter:
<div class="metric-grid">
<div class="metric-card">
<div class="metric-label">Total revenue</div>
<div class="metric-value">$142,800</div>
</div>
<div class="metric-card">
<div class="metric-label">Active users</div>
<div class="metric-value">8,421</div>
</div>
</div>
Contact / Data Record Card
<div style="background: var(--color-background-primary);
border-radius: var(--border-radius-lg);
border: 0.5px solid var(--color-border-tertiary);
padding: 1rem 1.25rem;">
<div style="display: flex; align-items: center; gap: 12px;
margin-bottom: 16px;">
<!-- Avatar circle with initials -->
<div style="width: 44px; height: 44px; border-radius: 50%;
background: var(--color-background-info);
display: flex; align-items: center; justify-content: center;
font-weight: 500; font-size: 14px;
color: var(--color-text-info);">JD</div>
<div>
<p style="font-weight: 500; font-size: 15px; margin: 0;">Jane Doe</p>
<p style="font-size: 13px; color: var(--color-text-secondary);
margin: 0;">Lead Engineer</p>
</div>
</div>
<div style="border-top: 0.5px solid var(--color-border-tertiary);
padding-top: 12px;">
<table style="width: 100%; font-size: 13px;">
<tr>
<td style="color: var(--color-text-secondary); padding: 4px 0;">
Email</td>
<td style="text-align: right; padding: 4px 0;
color: var(--color-text-info);">jane@company.com</td>
</tr>
</table>
</div>
</div>
Badges and Status Pills
<!-- Status badge -->
<span style="display: inline-block; font-size: 12px; padding: 4px 12px;
border-radius: var(--border-radius-md);
background: var(--color-background-success);
color: var(--color-text-success);">Active</span>
<!-- Featured accent (the ONLY case where 2px border is allowed) -->
<div style="border: 2px solid var(--color-border-info);
border-radius: var(--border-radius-lg);
padding: 1rem 1.25rem;">
<span style="font-size: 12px; padding: 4px 12px;
border-radius: var(--border-radius-md);
background: var(--color-background-info);
color: var(--color-text-info);">Most popular</span>
</div>
Form Elements
Inputs, selects, textareas, buttons, and range sliders are pre-styled in the host environment. Write bare tags — they inherit correct styling:
- Text inputs: 36px height, hover/focus states built in
- Range sliders: 4px track + 18px thumb
- Buttons: transparent bg, 0.5px border, hover/active states
Never use <form> tags. Use onClick / onChange handlers directly.
Comparison Cards
For "help me choose between X and Y":
<div style="display: grid; grid-template-columns:
repeat(auto-fit, minmax(160px, 1fr)); gap: 12px;">
<div style="background: var(--color-background-primary);
border: 0.5px solid var(--color-border-tertiary);
border-radius: var(--border-radius-lg);
padding: 1rem 1.25rem;">
<h3 style="font-size: 16px; font-weight: 500; margin: 0 0 8px;">
Option A</h3>
<p style="font-size: 13px; color: var(--color-text-secondary);
margin: 0;">Description here</p>
</div>
<!-- Repeat for Option B, C... -->
</div>
Part 3: Advanced Interactive Widgets
Simulations and Physics
For teaching physics, algorithms, or systems behavior with real-time updates.
Pattern: Animation Loop with Controls
css parameter:
.sim-controls {
display: flex; align-items: center; gap: 16px;
margin: 12px 0; font-size: 13px;
color: var(--color-text-secondary);
}
#sim {
width: 100%; height: 300px;
border-radius: var(--border-radius-md);
background: var(--color-background-secondary);
}
html parameter:
<canvas id="sim"></canvas>
<div class="sim-controls">
<button onclick="toggleSim()">Play / Pause</button>
<label>Speed
<input type="range" min="1" max="10" value="5" id="speed"
oninput="setSimSpeed(+this.value)">
</label>
<button onclick="resetSim()">Reset</button>
</div>
jsFunctions parameter:
function initSim(count) {
const canvas = document.getElementById('sim');
canvas.width = canvas.offsetWidth;
canvas.height = canvas.offsetHeight;
window.sim = {
canvas,
ctx: canvas.getContext('2d'),
running: true,
speed: 5,
particles: Array.from({ length: count }, () => ({
x: Math.random() * canvas.width,
y: Math.random() * canvas.height,
vx: (Math.random() - 0.5) * 2,
vy: (Math.random() - 0.5) * 2
}))
};
}
function stepSim() {
const { canvas, ctx, particles, speed, running } = window.sim;
ctx.clearRect(0, 0, canvas.width, canvas.height);
for (const p of particles) {
p.x += p.vx * speed * 0.2;
p.y += p.vy * speed * 0.2;
if (p.x < 0 || p.x > canvas.width) p.vx *= -1;
if (p.y < 0 || p.y > canvas.height) p.vy *= -1;
ctx.beginPath();
ctx.arc(p.x, p.y, 4, 0, Math.PI * 2);
ctx.fillStyle = '#534AB7';
ctx.fill();
}
if (running) requestAnimationFrame(stepSim);
}
function setSimSpeed(value) { window.sim.speed = value; }
function toggleSim() {
window.sim.running = !window.sim.running;
if (window.sim.running) stepSim();
}
function resetSim() {
const wasRunning = window.sim.running;
initSim(window.sim.particles.length);
if (!wasRunning) stepSim();
}
jsExpressions parameter:
initSim(50);
stepSim();
Math Visualizations
For plotting functions, showing geometric relationships, or exploring equations.
Pattern: Function Plotter with SVG
css parameter:
.plot-controls {
display: flex; gap: 16px; align-items: center;
margin: 12px 0; font-size: 13px;
color: var(--color-text-secondary);
}
.plot-controls input[type="number"] { width: 60px; }
.plot-controls input[type="range"] { flex: 1; }
html parameter:
<svg id="plot" width="100%" viewBox="0 0 680 400">
<!-- Grid -->
<line x1="60" y1="200" x2="640" y2="200"
stroke="var(--color-border-tertiary)" stroke-width="0.5"/>
<line x1="340" y1="20" x2="340" y2="380"
stroke="var(--color-border-tertiary)" stroke-width="0.5"/>
<!-- Axes labels -->
<text x="645" y="196" font-size="12"
fill="var(--color-text-tertiary)">x</text>
<text x="345" y="16" font-size="12"
fill="var(--color-text-tertiary)">y</text>
<!-- Function path drawn by JS -->
<path id="fn-path" fill="none" stroke="#534AB7" stroke-width="2"/>
</svg>
<div class="plot-controls">
<label>f(x) = sin(
<input type="number" id="freq" value="1" min="0.1" max="10" step="0.1"
oninput="plotFn()">x)
</label>
<label>Amplitude
<input type="range" id="amp" min="0.1" max="3" value="1" step="0.1"
oninput="plotFn()">
</label>
</div>
jsFunctions parameter:
function plotFn() {
const freq = +document.getElementById('freq').value;
const amp = +document.getElementById('amp').value;
const xMin = -5, xMax = 5, yMin = -3, yMax = 3;
const toSvgX = x => 60 + (x - xMin) / (xMax - xMin) * 580;
const toSvgY = y => 20 + (yMax - y) / (yMax - yMin) * 360;
let d = '';
for (let px = 0; px <= 580; px++) {
const x = xMin + px / 580 * (xMax - xMin);
const y = amp * Math.sin(freq * x);
d += (px === 0 ? 'M' : 'L') + toSvgX(x).toFixed(1)
+ ' ' + toSvgY(y).toFixed(1);
}
document.getElementById('fn-path').setAttribute('d', d);
}
jsExpressions parameter:
plotFn();
Sortable / Filterable Data Tables
css parameter:
.data-table { width: 100%; border-collapse: collapse; font-size: 14px; }
.data-table th {
text-align: left; padding: 8px 12px; font-weight: 500;
border-bottom: 0.5px solid var(--color-border-secondary);
color: var(--color-text-secondary); cursor: pointer;
user-select: none; font-size: 12px;
}
.data-table th:hover { color: var(--color-text-primary); }
.data-table td {
padding: 8px 12px;
border-bottom: 0.5px solid var(--color-border-tertiary);
}
.table-filter { width: 100%; margin-bottom: 12px; }
.status-pill {
font-size: 12px; padding: 2px 10px;
border-radius: var(--border-radius-md);
}
.status-pill.active {
background: var(--color-background-success);
color: var(--color-text-success);
}
.status-pill.paused {
background: var(--color-background-warning);
color: var(--color-text-warning);
}
html parameter:
<input type="text" class="table-filter" placeholder="Filter..."
oninput="filterTable(this.value)">
<table class="data-table" id="table">
<thead>
<tr>
<th onclick="sortTable(0)">Name</th>
<th onclick="sortTable(1)">Value</th>
<th onclick="sortTable(2)">Status</th>
</tr>
</thead>
<tbody id="tbody">
<!-- Rows populated by JS -->
</tbody>
</table>
jsFunctions parameter:
function initTable(rows) {
window.tableData = rows;
window.sortCol = -1;
window.sortAsc = true;
renderRows(rows);
}
function renderRows(rows) {
document.getElementById('tbody').innerHTML = rows.map(r =>
`<tr><td>${r[0]}</td><td>${r[1]}</td>
<td><span class="status-pill ${r[2] === 'Active' ? 'active' : 'paused'}">${r[2]}</span>
</td></tr>`
).join('');
}
function sortTable(col) {
window.sortAsc = window.sortCol === col ? !window.sortAsc : true;
window.sortCol = col;
const asc = window.sortAsc;
window.tableData.sort((a, b) => {
if (a[col] < b[col]) return asc ? -1 : 1;
if (a[col] > b[col]) return asc ? 1 : -1;
return 0;
});
renderRows(window.tableData);
}
function filterTable(q) {
const low = q.toLowerCase();
renderRows(window.tableData.filter(r =>
r.some(c => String(c).toLowerCase().includes(low))));
}
jsExpressions parameter:
initTable([
['Alpha', 42, 'Active'],
['Beta', 18, 'Paused'],
['Gamma', 91, 'Active'],
]);
Part 4: Chart.js — Advanced Patterns
Dark Mode Awareness
const isDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
const textColor = isDark ? '#c2c0b6' : '#3d3d3a';
const gridColor = isDark ? 'rgba(255,255,255,0.08)' : 'rgba(0,0,0,0.06)';
const tooltipBg = isDark ? '#2C2C2A' : '#fff';
Canvas cannot read CSS variables — always detect dark mode and use hardcoded hex values.
Wrapper Pattern (Critical for Sizing)
<div style="position: relative; width: 100%; height: 300px;">
<canvas id="chart"></canvas>
</div>
- Height goes on the wrapper div ONLY, never on canvas.
- Always set
responsive: true, maintainAspectRatio: false. - For horizontal bar charts: height = (bars x 40) + 80 pixels.
Custom Legend (Always Use This)
Disable Chart.js default legend and build HTML:
plugins: { legend: { display: false } }
<div style="display: flex; flex-wrap: wrap; gap: 16px;
margin-bottom: 8px; font-size: 12px;
color: var(--color-text-secondary);">
<span style="display: flex; align-items: center; gap: 4px;">
<span style="width: 10px; height: 10px; border-radius: 2px;
background: #534AB7;"></span>Series A — 65%
</span>
<span style="display: flex; align-items: center; gap: 4px;">
<span style="width: 10px; height: 10px; border-radius: 2px;
background: #0F6E56;"></span>Series B — 35%
</span>
</div>
Dashboard Layout
Metric cards on top -> chart below -> drill-down buttons wired to the sendPrompt bridge (Part 6):
<!-- Metric cards grid -->
<div style="display: grid;
grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
gap: 12px; margin-bottom: 1.5rem;">
<!-- cards here -->
</div>
<!-- Chart (no card wrapper) -->
<div style="position: relative; width: 100%; height: 300px;">
<canvas id="chart"></canvas>
</div>
Chart Type Selection Guide
| Data pattern | Chart type |
|---|---|
| Trend over time | Line |
| Category comparison | Vertical bar |
| Ranking (few items) | Horizontal bar |
| Part of whole | Doughnut |
| Distribution | Histogram (bar) |
| Correlation (2 variables) | Scatter |
| Multi-variable comparison | Radar |
| Range / uncertainty | Line with fill area |
Part 5: Generative Art and Illustration
For when the user asks for something creative, decorative, or aesthetic.
When to Use
- "Draw me a sunset" / "Create a pattern"
- Decorative headers or visual breaks
- Mood illustrations for creative writing
- Abstract visualizations of data or music
Rules (Different from Diagrams)
- Fill the canvas — art should feel rich, not sparse
- Bold colors are encouraged. You can use custom hex freely.
- Layered overlapping shapes create depth
- Organic forms with
<path>curves,<ellipse>,<circle> - Texture via repetition (hatching, dots, parallel lines)
- Geometric patterns with
<g transform="rotate()"> - NO gradients, shadows, blur, or glow (still flat aesthetic)
Pattern: Geometric Art
<svg width="100%" viewBox="0 0 680 400">
<!-- Background shapes -->
<circle cx="200" cy="200" r="150" fill="#EEEDFE" opacity="0.8"/>
<circle cx="480" cy="180" r="120" fill="#E1F5EE" opacity="0.8"/>
<!-- Overlapping geometric forms -->
<rect x="150" y="100" width="200" height="200" rx="8"
fill="#CECBF6" opacity="0.6"
transform="rotate(15 250 200)"/>
<rect x="320" y="80" width="180" height="180" rx="8"
fill="#9FE1CB" opacity="0.6"
transform="rotate(-10 410 170)"/>
<!-- Detail lines -->
<line x1="100" y1="300" x2="580" y2="300"
stroke="#534AB7" stroke-width="0.5" opacity="0.3"/>
<line x1="100" y1="310" x2="580" y2="310"
stroke="#534AB7" stroke-width="0.5" opacity="0.2"/>
</svg>
Pattern: Radial Symmetry
<svg width="100%" viewBox="0 0 680 680">
<g transform="translate(340 340)">
<!-- Repeat a shape at angular intervals -->
<g transform="rotate(0)">
<ellipse cx="0" cy="-120" rx="30" ry="80"
fill="#FAECE7" stroke="#993C1D" stroke-width="0.5"/>
</g>
<g transform="rotate(45)">
<ellipse cx="0" cy="-120" rx="30" ry="80"
fill="#FBEAF0" stroke="#993556" stroke-width="0.5"/>
</g>
<!-- ... repeat for 90, 135, 180, 225, 270, 315 -->
</g>
</svg>
Pattern: Landscape with Layered Shapes
For physical scenes, use ALL hardcoded hex (no theme classes):
<svg width="100%" viewBox="0 0 680 400">
<!-- Sky -->
<rect x="0" y="0" width="680" height="250" fill="#E6F1FB"/>
<!-- Mountains -->
<polygon points="0,250 150,100 300,250" fill="#B4B2A9"/>
<polygon points="200,250 400,60 600,250" fill="#888780"/>
<!-- Ground -->
<rect x="0" y="250" width="680" height="150" fill="#C0DD97"/>
<!-- Sun -->
<circle cx="550" cy="80" r="40" fill="#FAC775"/>
</svg>
Part 6: Advanced Patterns
Tabbed / Multi-View Interfaces
Since html streams top-down, don't use display: none during streaming.
Instead, render all content stacked, then let a jsExpression create the tabs
once the document is complete:
css parameter:
#tabs { display: flex; gap: 4px; margin-bottom: 16px; }
html parameter:
<div id="tabs">
<button onclick="showTab(0)">Overview</button>
<button onclick="showTab(1)">Details</button>
<button onclick="showTab(2)">Code</button>
</div>
<div id="panel-0"><!-- Overview content --></div>
<div id="panel-1"><!-- Details content --></div>
<div id="panel-2"><!-- Code content --></div>
jsFunctions parameter:
function showTab(n) {
for (let i = 0; i < 3; i++) {
document.getElementById('panel-' + i).style.display =
i === n ? 'block' : 'none';
}
document.querySelectorAll('#tabs button').forEach((b, i) => {
b.style.fontWeight = i === n ? '500' : '400';
b.style.color = i === n
? 'var(--color-text-primary)' : 'var(--color-text-tertiary)';
});
}
jsExpressions parameter:
showTab(0);
sendPrompt — Chat-Driven Interactivity
The host bridge exposes await Websandbox.connection.remote.sendPrompt({ text }),
which sends a message as if the user typed it. Use it when the user's next
action benefits from AI thinking. Wire it through a named jsFunction:
jsFunctions parameter:
function drillDown(text) {
Websandbox.connection.remote.sendPrompt({ text });
}
html parameter:
<button onclick="drillDown('Break down Q4 revenue by region')">
Drill into Q4 ↗
</button>
<button onclick="drillDown('Explain what shear force is')">
Learn about shear ↗
</button>
Use for: drill-downs, follow-up questions, "explain this part".
Don't use for: filtering, sorting, toggling — handle those in JS.
Append ↗ to button text when it triggers the bridge.
For external links use await Websandbox.connection.remote.openLink({ url })
(https only — anything else is rejected).
Responsive Grid Pattern
display: grid;
grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
gap: 12px;
Use minmax(0, 1fr) if children have large min-content that could overflow.
CSS Animations (Subtle and Purposeful)
/* Only animate transform and opacity for performance */
@keyframes fadeSlideIn {
from { opacity: 0; transform: translateY(8px); }
to { opacity: 1; transform: translateY(0); }
}
/* Always respect user preferences */
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
}
}
/* Flowing particles / convection currents */
@keyframes flow { to { stroke-dashoffset: -20; } }
.flowing {
stroke-dasharray: 5 5;
animation: flow 1.6s linear infinite;
}
/* Pulsing for active elements */
@keyframes pulse {
0%, 100% { opacity: 0.3; }
50% { opacity: 0.7; }
}
Part 7: External Libraries
Importmap Libraries (Pre-Injected)
An importmap for three, gsap, d3, and chart.js (served via esm.sh) is
pre-injected into every sandbox. jsFunctions and jsExpressions execute as classic
scripts, where top-level await is a SyntaxError that fails silently — so PREFER
loading libraries with dynamic imports INSIDE an async function declared in
jsFunctions, and keep jsExpressions synchronous invocations of those functions:
async function setupScene() {
const THREE = await import('three');
const { OrbitControls } =
await import('three/examples/jsm/controls/OrbitControls.js');
// ... build the scene with real geometry and PBR materials.
// NEVER fake 3D with CSS transforms or Canvas 2D projection.
}
async function setupLibraries() {
const { default: gsap } = await import('gsap');
const d3 = await import('d3');
const { default: Chart } = await import('chart.js/auto');
// ... use the libraries here.
}
Where a module script genuinely belongs in the html parameter,
<script type="module"> with bare specifiers also resolves through the importmap:
<script type="module">
import * as THREE from 'three';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';
// ... your Three.js code here
</script>
Critical: Regular <script> tags cannot use import statements — use
<script type="module"> in html. In jsFunctions/jsExpressions (classic-script
semantics) dynamic await import(...) works only inside an async function body,
never at top level.
CDN Allowlist (For Everything Else)
Only these CDN origins work (CSP-enforced):
cdnjs.cloudflare.comesm.shcdn.jsdelivr.netunpkg.com
<script src> / <link> CDN tags still work in the html head for libraries
outside the importmap:
Mermaid (ERDs, sequence diagrams, class diagrams):
<script type="module">
import mermaid from 'https://esm.sh/mermaid@11/dist/mermaid.esm.min.mjs';
</script>
Tone.js (audio synthesis):
<script src="https://cdnjs.cloudflare.com/ajax/libs/tone/14.8.49/Tone.min.js"></script>
Three.js Coordinate Conventions
Three.js uses a right-handed Y-up coordinate system:
- X = right (positive) / left (negative)
- Y = up (positive) / down (negative)
- Z = toward the viewer (positive) / away from the viewer (negative)
Critical for vehicles and aircraft: The fuselage/body extends along Z (nose at -Z, tail at +Z). Wings extend along X (left/right). The vertical stabilizer extends along Y.
When building an aircraft from primitives:
- Fuselage = cylinder or box, long axis along Z (use
geometrydefault or rotate 90° around X) - Wings = flat box, wide along X, thin along Y, short along Z
- Tail fin = flat box, tall along Y, thin along X, short along Z
// Correct aircraft orientation example:
// Fuselage along Z
const fuselage = new THREE.Mesh(
new THREE.CylinderGeometry(0.15, 0.08, 2.0, 12),
material
);
fuselage.rotation.x = Math.PI / 2; // CylinderGeometry default is Y-up, rotate to Z-forward
// Wings along X
const wing = new THREE.Mesh(
new THREE.BoxGeometry(2.5, 0.03, 0.4), // wide X, thin Y, short Z
material
);
// Vertical stabilizer along Y
const tailFin = new THREE.Mesh(
new THREE.BoxGeometry(0.03, 0.4, 0.3), // thin X, tall Y, short Z
material
);
tailFin.position.set(0, 0.2, 0.9); // above and behind
Rotation axes for flight dynamics:
- Pitch = rotation around X (nose up/down)
- Roll = rotation around Z (wings tilt)
- Yaw = rotation around Y (nose left/right)
Common mistake: Using the wing box as the fuselage (wide along X instead of Z). Always verify: the longest dimension of the fuselage should be along Z.
Part 8: Quality Checklist
Before producing any visual, run through this:
Functional
- Does it work without JavaScript during streaming? (Content visible)
- All styles live in the css parameter (no
<style>blocks in html) - Behavior is split into jsFunctions (parameterized toolbox) + jsExpressions (one invocation per statement)
- Do all interactive controls have event handlers?
- Are all displayed numbers rounded properly?
- Does the canvas/SVG fit within the container width?
Visual
- Dark mode test: would every element be readable on near-black?
- No hardcoded text colors in HTML (use CSS variables)
- No gradients, shadows, blur, or glow
- Borders are 0.5px (except 2px for featured item accent)
- Font weights are only 400 or 500
- All text is sentence case
Content
- Explanatory text is in the response, not inside the widget
- No titles or headings embedded in the HTML output
- Visual is self-explanatory without reading the narration
- Narration adds value beyond what the visual shows
- Offered a clear "go deeper" path
Accessibility
-
@media (prefers-reduced-motion: reduce)for all animations - Text contrast is sufficient (dark text on light fills, vice versa)
- Interactive elements are large enough to click (min 44px touch target)
- No information conveyed by color alone
Part 9: Decision Matrix — Picking the Right Visual
| User asks about... | Output type | Technology |
|---|---|---|
| How X works (physical) | Illustrative diagram | SVG |
| How X works (abstract) | Interactive explainer | HTML + inline SVG |
| Process / steps | Flowchart | SVG |
| Architecture / containment | Structural diagram | SVG |
| Database schema / ERD | Relationship diagram | Mermaid |
| Trends over time | Line chart | Chart.js |
| Category comparison | Bar chart | Chart.js |
| Part of whole | Doughnut chart | Chart.js |
| KPIs / metrics | Dashboard | HTML metric cards |
| Design a UI | Mockup | HTML |
| Choose between options | Comparison cards | HTML grid |
| Cyclic process | Step-through | HTML stepper |
| Physics / math | Simulation | Canvas + JS |
| Function / equation | Plotter | SVG + JS |
| Data exploration | Sortable table | HTML + JS |
| Creative / decorative | Art / illustration | SVG |
| 3D visualization | 3D scene | Three.js |
| Music / audio | Synthesizer | Tone.js |
| Network / graph | Force layout | D3.js |
| Quick factual answer | Plain text | None |
| Code solution | Code block | None |
| Emotional support | Warm text | None |
More skills from the OpenGenerativeUI repository
View all 3 skillsMore from CopilotKit
View publishera2ui-renderer
render declarative agent UI surfaces
CopilotKit
Jul 12CopilotKitDesignFrontendUI Componentscopilotkit-agui
build and debug CopilotKit agent backends
CopilotKit
Jul 12AgentsAPI DevelopmentBackendCopilotKit +1copilotkit-contribute
contribute to CopilotKit open-source project
CopilotKit
Jul 12CopilotKitEngineeringGitHubPull Requestscopilotkit-debug
diagnose and debug CopilotKit issues
CopilotKit
Jul 12CopilotKitDebuggingObservabilitycopilotkit-develop
build AI features with CopilotKit
CopilotKit
Jul 12AgentsAI ContextCopilotKitFrontendcopilotkit-integrations
integrate agent frameworks into CopilotKit applications
CopilotKit
Jul 12AgentsAPI DevelopmentCopilotKitIntegrations