Install
$ agentstack add skill-kevintsai1202-teaching-site-skills-web-visual-assets ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ● Filesystem access Used
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
Web Visual Assets
> Schema authority: the Illustration primitive shape ({name, kind, alt, spec} and {kind: 'waived', reason}) and the per-unit illustrations[] Coverage Floor are defined in [_shared/domain-primitives.md](../_shared/domain-primitives.md) §11.
This skill produces the visual layer of a teaching site. Four asset sources cover virtually every need, each with different tradeoffs.
Asset Source Decision Tree
What do you need?
├── Screenshot of a real product/website → Source 1: Playwright scraping
├── A conceptual scene (workflow, metaphor) → Source 2: AI image generation
├── A simple diagram (boxes, arrows, labels) → Source 3: Hand-drawn SVG
└── A scannable code / functional artifact → Source 4: Code generator (QRCode, etc.)
Source 1: Playwright Scraping
Use for screenshots of tools, websites, profile cards. The example workshop scrapes 6 AI tool homepages and a YouTube instructor channel.
Standard headless flow
// scripts/scrape-tools.mjs
import { chromium } from 'playwright';
const browser = await chromium.launch();
const page = await browser.newPage({ viewport: { width: 1440, height: 900 } });
await page.goto(url, { waitUntil: 'networkidle' });
await page.screenshot({ path: `data/tools/${id}.png`, fullPage: false });
await browser.close();
When anti-bot blocks you (CDP mode)
Some sites (Cloudflare, modern OpenAI properties) detect headless Chromium and block. Workaround: connect to a real Chrome via CDP:
# scripts/start-cdp-chrome.ps1
Start-Process chrome.exe -ArgumentList '--remote-debugging-port=9222', '--user-data-dir=C:\tmp\cdp-profile'
const browser = await chromium.connectOverCDP('http://localhost:9222');
const page = (await browser.contexts()[0].pages())[0];
// ... drive an existing real-browser session
Run with --cdp --pause flags so the human can complete any CAPTCHA, then press Enter to continue.
Subset-merge pattern (don't overwrite siblings)
When re-scraping one item out of many, merge with existing data instead of overwriting:
async function writeMerged(jsonPath, updates) {
const existing = JSON.parse(await fs.readFile(jsonPath, 'utf8').catch(() => '{}'));
await fs.writeFile(jsonPath, JSON.stringify({ ...existing, ...updates }, null, 2));
}
CLI: node scrape-tools.mjs --ids codex,notebooklm only touches those two.
YouTube channel quirks
- The channel page is a SPA. URL query strings like
?sort=p&view=0are stripped. Sort client-side after fetching the latest N videos. - Virtual scrolling strips
srcfrom out-of-view `. Derive thumbnail URLs fromvideoId:https://img.youtube.com/vi/{videoId}/hqdefault.jpg. Don't rely on DOMsrc`.
Source 2: AI Image Generation
Use for scenario illustrations, day heroes, conceptual scenes. The example workshop uses Gemini's image generation; any text-to-image API works.
Prompt design rules
- Specify a consistent visual style across the whole course (e.g. "flat illustration, soft pastel palette, no text, no people"). Inconsistent illustrations look amateurish.
- Avoid faces and brand logos — AI image generators struggle with both and produce uncanny results.
- Store prompts alongside the images:
assets/illustrations/
├── day1-token-prediction.png
├── day1-token-prediction.prompt.md ← regenerate-ready prompt
└── ...
This lets you regenerate at higher quality later or tweak style.
PNG + SVG fallback render
AI generation occasionally fails (rate limits, content filters, model issues). Pair every AI PNG with a hand-drawn SVG fallback at the same path stem:
function renderIllustration(name) {
const img = el('img', { src: `assets/illustrations/${name}.png`, alt: '' });
img.onerror = () => { img.src = `assets/illustrations/${name}.svg`; img.onerror = null; };
return img;
}
The browser tries PNG; on 404 swaps to SVG. The fallback ships even if you never use it — defensive against link rot.
Source 3: Hand-drawn SVG
Use for diagrams that benefit from precise control: arrows between named boxes, step-by-step flows, labelled UI mockups, the classroom map.
Hand-coded SVG (or exported from Figma/Excalidraw) is preferable to AI for:
- Text labels (AI can't reliably render Chinese text inside images)
- Arrow + box flows (AI gets layout wrong)
- Anything that needs to update when course details change
Style hint: wrap in a translucent container + drop-shadow filter to match the site's overall aesthetic, even if the SVG itself is line-art.
Source 4: Generated Codes (QR)
import QRCode from 'qrcode';
await QRCode.toFile('assets/qr/workshop-url.png', 'https://your-workshop.example/', {
width: 512,
margin: 1,
color: { dark: '#000000', light: '#FFFFFF' }
});
For dark-themed sites, generate a dark-on-white code, not white-on-dark — most QR scanners require dark foreground on light background.
Asset Folder Convention
assets/
├── tools/ ← Source 1: scraped tool/product screenshots
├── illustrations/ ← Source 2 (PNG) + Source 3 (SVG fallback)
├── scenarios/ ← Source 2: per-unit scene illustrations
├── cases/ ← Source 2: shared case visuals
├── characters/ ← Source 2: persona portraits
├── qr/ ← Source 4: QR codes
└── maps/ ← Source 1 (screenshot) or Source 3 (SVG)
Wiring Assets into the SPA
In course-data.js, every unit carries an illustrations[] array (1–3 entries) populated from the 圖片需求 blocks written in Stage 2:
{
id: 'u-3',
title: '...',
illustrations: [
{ name: 'day1-u3-hero.png', kind: 'hero', alt: '...', spec: '...' },
{ name: 'day1-u3-flow.svg', kind: 'diagram', alt: '...', spec: '...' },
{ name: 'day1-u3-example.png', kind: 'screenshot', alt: '...', spec: '...' } // optional 3rd
],
// ...
}
renderUnit iterates unit.illustrations and calls renderIllustration(entry), which handles PNG-first / SVG-fallback per entry. Render hero first (above the fold), then diagram, then screenshot — in that order.
Legacy single-illustration field: older course-data.js files use illustration: 'foo.png' (single string). Treat it as illustrations: [{ name: 'foo.png', kind: 'hero' }] and migrate to the array form when convenient. Don't rely on the legacy shape for new sites.
Coverage Floor (Hard Rule — 1–3 illustrations per unit)
A teaching site that ships with only a cover image looks like an unfinished draft — Stage 5 must hit a minimum coverage before declaring the site feature-complete:
- [ ] Every unit in
course-data.jshasillustrations.length >= 1(and `` requests automatically.
Run both before each deployment. The audit catches references to files that don't exist; the verify catches assets that exist but are mis-pathed in the rendered HTML. They fail in different ways and need different scripts.
Anti-Patterns
- Generating images one-by-one without style consistency — write the style spec once, paste it as prefix for every prompt.
- AI-generated text in images — almost always broken; use SVG for any image that needs accurate text.
- No SVG fallback for AI PNGs — when the PNG link breaks (file deletion, rename mistake), the site shows broken-image icons; SVG fallback degrades gracefully.
- Storing screenshots without a re-scrape script — six months later someone wants fresh screenshots and there's no record of how the originals were taken.
Hand-off
Tell the user: "visual assets in place. Run node scripts/verify-assets.mjs to confirm nothing's missing. The site is now feature-complete. Next stage (course-ebook-publishing) can turn the same content into a PDF/DOCX deliverable — invoke that only when the web version is stable."
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: kevintsai1202
- Source: kevintsai1202/teaching-site-skills
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.