npx claudepluginhub careerhackeralex/visualize --plugin visualizeThis skill uses the workspace's default tool permissions.
Turn any idea, data, or content into a stunning single-file HTML visualization.
Creates reveal.js presentations with themes, multi-column layouts, code highlighting, animations, speaker notes, and custom styling. Generates HTML + CSS with no build step. Use for slides, decks, or slideshows.
Generates self-contained HTML slide decks from topics, outlines, data, or documents using 22 layout types, ALPA design system, responsive typography, and keyboard/touch navigation.
Generates self-contained Apple Keynote-style HTML presentations from markdown, text descriptions, topics, or files, with cinematic animations and glassmorphism design.
Share bugs, ideas, or general feedback.
Turn any idea, data, or content into a stunning single-file HTML visualization.
Always do BOTH of these after writing the HTML file:
open <filename>.html (macOS) or xdg-open <filename>.html (Linux) so the user sees it immediatelyfile://<absolute-path> in your response so the user can click to open itExample response after creation:
Created your visualization! Opening in browser now...
📄 file:///Users/you/project/my-dashboard.html
⚠️ EVALUATION FAILURE GUARANTEED WITHOUT THESE 8 ELEMENTS ⚠️
EVERY file MUST start from the skeleton template in references/skeleton.md — copy the ENTIRE template, then add your content.
--bg, --surface, --surface-hover, --border, --text, --text-secondary, --accent, --accent-secondary, --positive, --negative, --warning — NO other names (not --bg-primary, not --text-primary). CRITICAL: These exact property names are required for evaluation system compatibility..viz-menu element with .viz-menu-toggle button, .viz-menu-dropdown container, download PNG button (onclick="downloadImage()"), print button (onclick="window.print()"), and html-to-image CDN script (<script src="https://cdn.jsdelivr.net/npm/html-to-image@1.11.11/dist/html-to-image.js"></script>). EVALUATION CRITICAL: Menu system is automatically checked and WILL CAUSE FAILURES if missing..theme-light and .theme-dark classes in stylesheet with complete custom property definitions. EXAMPLE REQUIRED::root { /* base properties */ }
.theme-light { --bg: #ffffff; --surface: #f8f9fa; --text: #1a1a1a; /* etc */ }
.theme-dark { --bg: #0a0a0a; --surface: #1a1a1a; --text: #ffffff; /* etc */ }
NEVER rely on just :root or @media (prefers-color-scheme) — evaluation system checks for class-based themes.
4. Semantic HTML: <main id="main-content"> element, MANDATORY: Multiple <section> elements for major content blocks (header, metrics, charts, etc.), skip-to-content link. Each distinct content area must be wrapped in semantic <section> tags.
5. Chart.js Requirements (EVALUATION CRITICAL): MUST include <script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.7/dist/chart.umd.min.js"></script> before closing </head>. MANDATORY: IMMEDIATELY after Chart.js script, add <script>Chart.defaults.animation = false;</script> (prevents animation glitches and is automatically checked by evaluation system). MANDATORY CHART VALIDATION: Every chart function MUST start with if (typeof Chart === 'undefined') { console.error('Chart.js not loaded'); return; }. CHART ACCESSIBILITY: Every canvas element MUST have role="img" and descriptive aria-label attributes. CRITICAL CHART CONFIG: Set maintainAspectRatio: false, responsive: true, and plugins: { tooltip: { enabled: true } } for accessibility. NEVER disable tooltips - evaluation system checks for enabled tooltips. CHART RELIABILITY SYSTEM: Use dedicated ChartManager pattern for bulletproof integration:
var ChartManager = {
charts: new Map(),
safeInit: function(canvasId, config) {
if (typeof Chart === 'undefined') {
console.error('Chart.js library not loaded - check CDN inclusion');
return null;
}
try {
if (this.charts.has(canvasId)) {
this.charts.get(canvasId).destroy();
this.charts.delete(canvasId);
}
var ctx = document.getElementById(canvasId);
if (!ctx) {
console.error('Canvas element not found: ' + canvasId);
return null;
}
// Ensure no conflicting chart instances
if (ctx.chart) {
ctx.chart.destroy();
delete ctx.chart;
}
// Set accessibility attributes
ctx.setAttribute('role', 'img');
if (!ctx.getAttribute('aria-label')) {
ctx.setAttribute('aria-label', 'Chart visualization');
}
// Initialize with enhanced error handling
var chart = new Chart(ctx, config);
this.charts.set(canvasId, chart);
return chart;
} catch (error) {
console.error('Chart initialization failed for ' + canvasId + ':', error);
return null;
}
},
updateTheme: function() {
if (typeof Chart === 'undefined') return;
this.charts.forEach(function(chart, canvasId) {
try {
chart.update();
} catch (error) {
console.error('Chart theme update failed for ' + canvasId + ':', error);
}
});
},
destroyAll: function() {
this.charts.forEach(function(chart) {
try {
chart.destroy();
} catch (error) {
console.error('Chart destruction failed:', error);
}
});
this.charts.clear();
}
};
Use ChartManager.safeInit() instead of raw new Chart(). CRITICAL CHART CONFIG: Set maintainAspectRatio: false, responsive: true, and plugins: { tooltip: { enabled: true } } for accessibility. CHART CONTAINER DIMENSIONS: Container must have explicit height >= 300px for charts to render properly. Use theme-aware colors with CSS custom properties, never static hex colors. NEVER use import/export syntax with Chart.js CDN — use standard var declarations only.
CHART.JS TROUBLESHOOTING (CRITICAL): If charts appear as blank white spaces:
</head>Chart.defaults.animation = false; is immediately after CDNrole="img" and aria-label attributes@media (max-width: 375px) { body { overflow-x: hidden; } } to prevent horizontal scroll), MANDATORY FONT-SIZE HIERARCHY: h1 ≥ 2.5rem, h2 ≥ 2rem, h3 ≥ 1.5rem, body = 1rem. SLIDE DECK REQUIREMENTS: Title slide h1 ≥ 3rem, content slide titles ≥ 2.5rem, clear visual distinction between heading levels. SLIDE SECTION SPACING: Major sections within slides must have ≥48px spacing (title-to-content, content-to-charts, charts-to-navigation). Test all layouts at 375px width — dashboards especially prone to chart container overflow. CSS CONTAINER QUERIES: For advanced responsiveness, use container-based queries:.chart-container { container-type: inline-size; }
@container (max-width: 400px) {
.chart-legend { display: none; }
.chart-title { font-size: 1rem; }
}
This provides true component-level responsiveness beyond viewport media queries.
7. Print & Accessibility: @media print styles, @media (prefers-reduced-motion: reduce) with disabled animations
8. Entrance Animations (MANDATORY): Must include entrance animations via .animate classes, data-reveal attributes, or CSS @keyframes. EVALUATION CRITICAL: Animation presence is automatically detected and required.
9. JavaScript Functions: cycleTheme(), toggleMenu(), top-level variables use var not let/const
🔥 CRITICAL: Copy skeleton.md exactly → Replace "YOUR CONTENT HERE" with visualization content → Save file
.html file with inline CSS/JS. Opens in any browser, works offline, emails easily.HTML is not a "website" — it's a visualization tool. Code is cheap. Everyone should feel empowered to visualize anything. This skill turns conversation context, URLs, articles, data, or raw ideas into something visual and digestible in seconds.
Users invoke this mid-conversation with Claude Code. Use the full conversation context — whatever they've been discussing, any links they've shared, any data they've pasted — as source material. When given a URL, crawl it and extract the content to visualize.
MANDATORY FIRST STEP: Copy the complete skeleton from references/skeleton.md — this includes all required elements (menu, theme system, CSS properties, semantic HTML, accessibility features). Never write HTML from scratch.
.html file to ~/Downloads/ (or user-specified path)q4-revenue-dashboard.html, team-roadmap-deck.html<!-- YOUR CONTENT HERE --> section<style> after the skeleton's base styleshttps://cdn.tailwindcss.com (utility-first styling, use freely)https://cdn.jsdelivr.net/npm/chart.js (bar, line, pie, radar, doughnut)https://cdn.jsdelivr.net/npm/d3@7 (complex/custom data viz, force graphs)https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.min.js (flowcharts, sequence diagrams)html, body { height: 100%; overflow: hidden; } and give the .reveal container height: 100%. Config MUST use numeric dimensions: Reveal.initialize({ width: 1280, height: 720, center: true, controls: false }) — NEVER use string percentages like '100%' which cause zero-height viewport and blank slides. MANDATORY: Disable Reveal.js default controls (controls: false) — the default < > arrow overlays are ugly. Instead, add a custom minimal bottom nav bar:<nav class="slide-nav" aria-label="Slide navigation">
<button onclick="prevSlide()" aria-label="Previous slide">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M15 18l-6-6 6-6"/></svg>
</button>
<span class="slide-counter" id="slideCounter">1 / 8</span>
<button onclick="nextSlide()" aria-label="Next slide">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 18l6-6-6-6"/></svg>
</button>
</nav>
.slide-nav { position: fixed; bottom: 16px; left: 50%; transform: translateX(-50%); display: flex; align-items: center; gap: 8px; z-index: 9998; }
.slide-nav button { width: 28px; height: 28px; border-radius: 6px; background: transparent; border: none; color: var(--text-secondary); cursor: pointer; display: flex; align-items: center; justify-content: center; opacity: 0.3; transition: opacity 0.2s; }
.slide-nav button:hover { opacity: 0.7; }
.slide-counter { font-size: 12px; color: var(--text-secondary); font-weight: 400; min-width: 40px; text-align: center; opacity: 0.35; }
https://unpkg.com/leaflet@1.9/dist/leaflet.js + CSS). Required for geographic data — never hand-draw SVG continent shapes. Use OpenStreetMap tiles or a minimal tile provider.See references/libraries.md for detailed CDN links, patterns, and tips.
Apply these defaults. They are opinionated and tested — override only when user requests it.
Full design system reference: See references/design-system.md for complete typography, color, spacing, animation, accessibility, and visual polish specifications.
Key highlights (consult reference for full details):
Theming System (CRITICAL):
<html class="theme-dark"> or <html class="theme-light">document.documentElement.className = 'theme-' + newThemedata-theme attributes — the evaluation system expects class-based themes--bg, --surface, --text, --accent, --border (minimum set for evaluation compatibility)Typography:
https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800;900&display=swapline-height: 1.6 for Korean (vs 1.4 for Latin). Korean Medium weight maps to Western Regular (400). Include: https://fonts.googleapis.com/css2?family=Noto+Sans+KR:wght@300;400;500;600;700&display=swapColors:
backdrop-filter: blur(8px), semi-transparent backgrounds with CSS var --glass-opacity: 0.08, and elevated shadows. Apply selectively to hero sections or primary cards for sophisticated layering..animate classes instead. Use data-reveal sparingly (max 3-4 sections) for below-fold content only.Chart.defaults.animation = false; at top of script, destroy+recreate on theme toggle, explicit rgba() colors, tooltips always enabled, maintainAspectRatio: false on all chart options. Accessibility: Wrap canvas in div with role="img" and descriptive aria-label. Guard pattern: Use chartsBuilt flag — onThemeChange() must check if (chartsBuilt) before rebuilding. Chart containers need min-height: 360px for substantial presence.layout: { padding: 30 }), remove excessive gridlines (opacity ≤ 0.04), use rounded corners (borderRadius: 4), thoughtful color palettes that match theme. Chart containers need 12px border radius, 40px internal padding, and 360px minimum height for substantial presence. Avoid library defaults that look auto-generated.var(--text). Never randomly colorize stat values. For KPI grids with 4+ cards: use at most 2 accent colors for values — var(--accent) for the single most important metric and var(--text) for all others. Reserve var(--positive)/var(--negative) only for delta indicators (arrows, percentages), not the main card value.MANDATORY: Use the skeleton template — see references/skeleton.md for complete copy-paste HTML with all requirements built-in.
JavaScript Implementation Rules:
var (not let/const) to avoid TDZ errors with function hoistingcycleTheme() function — this is built into the skeleton with proper applyTheme() implementationtoggleMenu() with outside-click handling — skeleton includes automatic dropdown closure on outside clicks and escape keyfunction onThemeChange() {} for chart re-rendering on theme changesminmax(320px, 1fr) for card gridsEvaluation Checkers Expect:
cycleTheme() function exists and works (changes html class)toggleMenu() function exists and closes on outside clicksvarThe skeleton template automatically provides all required functionality. ALWAYS start from skeleton.md to avoid implementation errors.
All visualizations must include these semantic elements:
Required Structure:
<main> element containing primary content<section> elements for major content blocksrole="banner", role="main", role="complementary") OR skip-to-content linkrole="img" and aria-label on chart containersAdditional Requirements:
@media print styles defined@media (prefers-reduced-motion) styles for accessibilityChoose the right format. See references/types.md for detailed patterns.
| Type | When to Use | Key Feature |
|---|---|---|
| Slide Deck | Presentations, pitches | 16:9, keyboard nav, transitions |
| Infographic | Data summaries, visual stories | Long scroll, big numbers, sections |
| Dashboard | Metrics, KPIs | Grid of cards + charts |
| Flowchart | Processes, architecture | Mermaid or SVG diagrams |
| Timeline | Chronological events | Alternating left/right, scroll-triggered |
| Comparison | Side-by-side analysis | Feature matrix, pros/cons |
| Data Viz | Charts, data stories | Chart.js or D3 |
| One-Pager | Summaries, briefs | Single viewport, print-friendly |
| Mind Map | Concept relationships | Radial SVG layout |
| Kanban | Status tracking | Column-based cards |
| Carousel Cards | Social media (IG/LinkedIn) | 1080×1080 per card, swipeable, bold text |
| Event Poster | Conferences, meetups, webinars | Portrait A4/letter, bold headline, date/venue |
| Resume/CV | Job applications | One-page, two-column, print-optimized |
| Banner/Header | Email, blog, social cover | 1200×630 or 1500×500, centered text on visual bg |
| Quote Card | Social proof, testimonials | Portrait/square, large quote, attribution |
| Process Guide | How-to, step-by-step | Numbered steps, icons, clear flow |
| Status Report | Executive updates | KPIs + progress bars + highlights, one-page |
| Org Chart | Team structure | Hierarchical tree, photos/avatars, roles |
| Data Story | Narrative + data | Scrollytelling, charts woven with narrative text |
| Product Card | Feature highlight, launch | Hero image area, feature pills, CTA |
Carousel cards are huge for social media. Get these right:
1080×1080px (or configurable via CSS var)When the user asks for something that fits "one screen," "phone screen," "9:16," or "mobile-fit," create a fixed-dimension single-viewport visualization — NOT a scrolling page.
Dimensions:
width: 1080px; height: 1920px; — standard Instagram Story / phone screenwidth: 1080px; height: 1080px; — Instagram postwidth: 1080px; height: 1350px; — Instagram portrait postwidth: 1920px; height: 1080px; — presentation slideCritical CSS pattern:
body {
width: 1080px; height: 1920px; /* or chosen ratio */
overflow: hidden; /* MUST — prevents scroll, enforces single screen */
display: flex; flex-direction: column; /* Flex column fills canvas completely */
}
.poster-header { padding: 44px 48px 0; }
.poster-grid { flex: 1; padding: 24px 48px 0; } /* flex:1 expands to fill remaining space */
.poster-footer { padding: 16px 48px 36px; }
Layout rules:
overflow: hidden on body — this is what makes it "one screen." Non-negotiable.justify-content: space-between on the main container — distributes sections evenly with NO dead gaps.flex: 1 on the main content area (grid, body, etc.) so it expands to fill ALL remaining space between header and footer. Never use fixed height values that leave dead space.<div> so flexbox distributes them as blocks.Content density for 9:16:
Font sizing for 1080px-wide posters:
68-80px (bigger than web — this is a poster)15-18px uppercase, letter-spacing 0.06em16-20px20-24pxCommon mistake: Making a scrolling page and screenshotting it. That's NOT a poster — it's a webpage screenshot. A poster is a fixed canvas where every pixel is intentional.
Slides are the most common request. Get these right:
100vw × 100vh, content centeredclamp() and container queries for mobile-friendly slides:
.slide-container { container-type: inline-size; }
.slide-title { font-size: clamp(2rem, 8vw, 4rem); }
@container (width < 768px) { .slide-content { padding: 1rem; } }
transform: translateX() with 500ms cubic-bezierdata-notes attribute, visible in print onlyFor investor presentations, startup pitches, and executive briefings:
Slide decks MUST look visually distinct in dark vs light themes. Gradient backgrounds must change:
/* Dark theme: deep, saturated gradients */
.theme-dark .slide-title { background: linear-gradient(135deg, #1e1b4b 0%, #312e81 50%, #1e3a5f 100%); }
.theme-dark .slide-content { background: var(--bg); }
/* Light theme: soft, pastel gradients */
.theme-light .slide-title { background: linear-gradient(135deg, #e0e7ff 0%, #c7d2fe 50%, #dbeafe 100%); }
.theme-light .slide-content { background: var(--bg); }
Rules:
var(--bg) or var(--surface) — NOT hardcoded dark backgroundsvar(--surface) with var(--border) — they auto-adapt#1a1a2e or similar dark colors on slide content — use CSS variablesChart slides in presentations MUST follow the same container standards as dashboards:
<div class="chart-slide-container">
<h2>Chart Title</h2>
<div class="chart-container" style="height: 400px; padding: 40px; border-radius: 12px; background: var(--surface);">
<canvas id="slideChart" role="img" aria-label="Description"></canvas>
</div>
</div>
When user provides data:
This skill is used mid-conversation. Leverage everything:
Always use real content. Never generate placeholder data when real context exists.
Every file MUST have at least ONE meaningful interaction beyond theme toggle + menu. Static-feeling pages score low on interactivity.
| Type | Required Interaction |
|---|---|
| Cheatsheet | Search/filter input + copy-to-clipboard on code blocks. Use <details name="..."> for collapsible groups. |
| Dashboard | Filter toolbar or metric drill-down. At minimum: date range or category filter. |
| Status Report | Collapsible detail sections (use <details>). Progress bars animate on scroll. |
| Quote Card | Auto-cycling quotes OR swipeable carousel. Share/copy button. |
| Event Poster | Animated countdown timer (days/hours/min/sec). RSVP/register button. |
| Process Guide | Steps as exclusive accordion (<details name="steps">). Or interactive progress tracker. |
| Architecture | Clickable nodes with popover details (use Popover API). Hover highlights connections. |
| Timeline | Filter by era/category. Or click to expand event details. |
| Comparison | Toggle categories on/off. Or highlight winner per row. |
| Carousel | Touch swipe + keyboard + auto-advance option. Card counter always visible. |
| Slide Deck | Already interactive (nav). Add: presenter timer, slide overview grid. |
If a type isn't listed, add at minimum: a filter, search, sort, or expand/collapse interaction.
Every file must feel like a UNIQUE design, not a template with different text. Vary these per file type:
span 2 for featured cards. CRITICAL: Always test at 768px and 375px - no horizontal overflow allowed.Mobile-First Responsive Pattern (MANDATORY):
.grid {
display: grid;
gap: 24px;
grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
}
@media (max-width: 768px) {
.grid { grid-template-columns: 1fr; gap: 16px; }
.container { padding: 24px 16px; }
}
@media (max-width: 375px) {
.card { padding: 16px; }
.stat-value { font-size: 2rem; }
}
grid-column: span 2 on the last item or adjust grid-template-columns to avoid a single card stranded alone on a row.@media print stylesUse these when they add value. See references/css-techniques.md for code snippets.
backdrop-blur-md bg-white/5 border border-white/10 for floating cardsbackground: linear-gradient(...); -webkit-background-clip: text for hero headlinesscroll-snap-type: y mandatory as alternative slide navigation (no JS needed)conic-gradient() for pure CSS pie/donut chartscubic-bezier(0.34, 1.56, 0.64, 1) for playful micro-interactionsinterpolate-size: allow-keywords on :root enables smooth height: auto transitions (Chrome 129+)<svg> paths, no icon library neededEVERY visualization MUST start from the skeleton. Copy it, then add content.
Full skeleton code: See references/skeleton.md for the complete copy-paste HTML template with themes, print styles, Inter font, animations, menu, and hover effects.
The skeleton provides:
var for all top-level JS variables (prevents TDZ errors)data-reveal for scroll animation OR .animate.delay-N for page-load entrance. Add JavaScript scroll observer for .reveal classes.function onThemeChange() {} to re-render charts on theme toggle<main>, <section>, <header>, <article>let/const at script top levelElements must be large enough to read and feel substantial:
flex-grow: 1 to fill available space — 300px is a floor, not a target.margin-bottom: 48px or larger on section elementsIf content feels too small, it IS too small. Err on the side of larger.
Text must ALWAYS be visible. This is the #1 cause of broken outputs.
var(--text) which resolves to #f9fafb (near-white)var(--text) which resolves to #0f172a (near-black)text-shadow: 0 1px 3px rgba(0,0,0,0.3) for readabilityrgba(0,0,0,0.5))Charts are the second most common failure. These rules are MANDATORY for every chart:
<!-- MANDATORY PATTERN FOR EVERY CHART -->
<div role="img" aria-label="Detailed description of chart data and insights">
<div class="chart-container" style="height: 360px; padding: 40px; border-radius: 12px; background: var(--surface);">
<canvas id="uniqueChartId"></canvas>
</div>
</div>
maintainAspectRatio: false// REQUIRED: Chart destruction and canvas reset to prevent "Canvas already in use" errors
var chartsBuilt = false; // Guard flag
function buildCharts() {
if (chartsBuilt) return; // Prevent double-initialization during theme detection
// REQUIRED: Reset canvas before building
function resetCanvas(id) {
var old = document.getElementById(id);
if (!old) return null;
var parent = old.parentNode;
var canvas = document.createElement('canvas');
canvas.id = id;
parent.replaceChild(canvas, old);
return canvas;
}
// Example chart with required settings
var ctx = resetCanvas('myChart');
if (ctx) {
new Chart(ctx, {
type: 'bar',
data: { /* your data */ },
options: {
responsive: true,
maintainAspectRatio: false, // REQUIRED
animation: false, // MANDATORY: Plus set Chart.defaults.animation = false globally
plugins: {
tooltip: {
enabled: true, // NEVER disable tooltips
padding: 12,
cornerRadius: 8
}
},
layout: { padding: 20 } // REQUIRED: breathing room
}
});
}
chartsBuilt = true; // Mark as built
}
// CRITICAL: Disable Chart.js default animations IMMEDIATELY after Chart.js loads
Chart.defaults.animation = false; // MUST be set before any chart creation
// REQUIRED: Build charts after DOM loads
document.addEventListener('DOMContentLoaded', buildCharts);
// REQUIRED: Rebuild charts on theme change
function onThemeChange() {
chartsBuilt = false; // Reset flag
setTimeout(buildCharts, 100); // Slight delay for CSS variable updates
}
options: {
plugins: {
tooltip: {
enabled: true, // NEVER set to false
mode: 'index',
intersect: false
}
}
}
layout: { padding: { top: 20, right: 20, bottom: 20, left: 20 } } for breathing roommaxRotation: 0 to keep labels horizontal. If labels overflow, use maxTicksLimit to reduce countrgba(255,255,255,0.04) in dark, rgba(0,0,0,0.06) in lightpadding: 12, cornerRadius: 8, titleFont: { size: 14 }, bodyFont: { size: 13 }maintainAspectRatio: false and control size via CSS containerChart.defaults.color = getComputedStyle(root).getPropertyValue('--text-secondary').trim()var(--border) valueresponsive: true is default, but container must have explicit dimensions// Theme-aware Chart.js setup (include in every chart visualization)
function getChartColors() {
var s = getComputedStyle(document.documentElement);
return {
text: s.getPropertyValue('--text').trim(),
textSecondary: s.getPropertyValue('--text-secondary').trim(),
border: s.getPropertyValue('--border').trim(),
surface: s.getPropertyValue('--surface').trim(),
accent: s.getPropertyValue('--accent').trim(),
};
}
// REQUIRED: Reset canvas before rebuilding charts (prevents "Canvas already in use" errors)
function resetCanvas(id) {
var old = document.getElementById(id);
var parent = old.parentNode;
var canvas = document.createElement('canvas');
canvas.id = id;
parent.replaceChild(canvas, old);
return canvas;
}
// Usage in buildCharts():
// try { if (window.myChart) window.myChart.destroy(); } catch(e) {}
// window.myChart = new Chart(resetCanvas('myChart'), { ... });
// CRITICAL: Always check chart existence before destroy() to prevent console errors
function buildCharts() {
var isDark = document.documentElement.classList.contains('theme-dark');
var colors = getChartColors();
// Safe chart destruction and rebuild pattern
if (window.myChart) {
try { window.myChart.destroy(); } catch(e) { /* ignore */ }
}
window.myChart = new Chart(resetCanvas('myChart'), {
// chart config with theme-aware colors
options: {
scales: {
x: { ticks: { color: colors.textSecondary }, grid: { color: colors.border } },
y: { ticks: { color: colors.textSecondary }, grid: { color: colors.border } }
}
}
});
}
If KPI values show "0%" instead of animating, add this debug pattern:
// DEBUG: Add after counter observer setup to verify intersection
var counterEl = document.querySelector('[data-count]');
if (counterEl) {
console.log('Counter element found:', counterEl); // DEBUG
var cObs = new IntersectionObserver(function(entries) {
console.log('Counter intersection triggered:', entries); // DEBUG
entries.forEach(function(e) {
if (e.isIntersecting) {
console.log('Starting counter animation'); // DEBUG
animateCounters();
cObs.disconnect();
}
});
}, { threshold: 0.3 });
cObs.observe(counterEl);
} else {
console.warn('No [data-count] elements found'); // DEBUG
}
MANDATORY for all Chart.js usage to prevent console errors:
// STEP 1: Global variables - MUST use var, never let/const
var chartsBuilt = false;
// STEP 2: Chart building function with validation
function buildCharts() {
// CRITICAL: Always validate Chart.js loaded first
if (chartsBuilt || typeof Chart === 'undefined') return;
// STEP 3: Destroy existing charts to prevent "Canvas already in use"
if (window.myChart) window.myChart.destroy();
// STEP 4: Reset canvas elements
var canvas = document.getElementById('chartId');
if (!canvas) return;
// STEP 5: Get theme colors from CSS variables
var isDark = document.documentElement.className.includes('theme-dark');
var textColor = isDark ? '#EDEDED' : '#0f172a';
var gridColor = isDark ? 'rgba(255,255,255,0.04)' : 'rgba(0,0,0,0.06)';
// STEP 6: Create chart with proper options
try {
window.myChart = new Chart(canvas.getContext('2d'), {
// Your chart configuration here
options: {
responsive: true,
maintainAspectRatio: false, // REQUIRED
plugins: {
tooltip: { enabled: true }, // REQUIRED - never disable
legend: {
labels: { color: textColor, font: { family: 'Inter' } }
}
},
scales: {
x: {
ticks: { color: textColor },
grid: { color: gridColor }
},
y: {
ticks: { color: textColor },
grid: { color: gridColor }
}
}
}
});
chartsBuilt = true;
} catch (error) {
console.error('Chart creation failed:', error);
}
}
// STEP 7: Theme change handler
function onThemeChange() {
if (chartsBuilt) {
chartsBuilt = false;
buildCharts();
}
var ctx = document.getElementById('myChart');
if (!ctx) {
console.error('Chart canvas #myChart not found');
return;
}
// ... build chart
} catch (error) {
console.error('Chart building failed:', error);
}
}
Ensure menu closes when clicking outside by strengthening the event handler:
document.addEventListener('click', function(e) {
var menu = document.querySelector('.viz-menu');
var dropdown = document.getElementById('vizMenuDropdown');
if (!e.target.closest('.viz-menu') && dropdown) {
dropdown.classList.remove('open');
}
});
html.theme-dark and html.theme-light class-based theme selectors (NO @media prefers-color-scheme)?var(--text) or var(--text-secondary)?@media print hides menu, shows all content?@media (prefers-reduced-motion: reduce) present?.viz-menu with toggle, theme, download, print?.animate classes (CSS @keyframes)?data-reveal (visible without JS)?.card:hover has transform effect?var (not let/const)?var declarations + onThemeChange hook?role="img" aria-label="..."?data-count where stats exist?<main>, <section>, <header>, <article>?The quality bar: "good, period" — not "good for AI-generated."