- Close button: Figma `4744:143502` — 4px padding, 4px border-radius, gray-02 icon, hover: gray-06 bg, pressed: gray-05 bg
Resources
15Install
npx skillscat add bendoidic/lego-blocks-interviewer Install via the SkillsCat registry.
TAP Prototype — Reusable Process Skills
Status note (2026-06-03): This file documents the single-file HTML prototype workflow used across TAP prototypes. The Lego Block Interviewer is migrating to a Next.js + Supabase + Vercel app (see
CLAUDE.md), so the single-file/python -m http.server/activity-page sections below are legacy for this repo. What stays relevant: the font setup, icon tinting with CSS filters, local-assets-first rule, and the Figma capture workflow — these carry over to the React app. Brand tokens live inTAP-BRAND.md.This file contains process knowledge that applies to any TAP prototype project.
Project-specific details (filenames, Figma URLs, activity IDs) live in each project's ownREADME.md.
Brand tokens and component styles live inTAP-BRAND.md.
Stack
Plain HTML + CSS — single-file prototypes, no framework, no build step.
- One
.htmlfile per prototype variant (e.g.prototype-v2.html,prototype-v3.html) - All CSS lives in a
<style>block in<head> - All JS lives in a
<script>block before</body> - No npm, no bundler, no dependencies beyond Google Fonts and the Figma capture script
Font Setup
Circular Std — TAP brand font (paid, not on Google Fonts). OTF files live in the font/ folder next to the prototype file.
/* In <head> — always include both */
<link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=DM+Sans:opsz,wght@9..40,300;9..40,400;9..40,500;9..40,700&display=swap" rel="stylesheet">
/* In <style> — @font-face declarations */
@font-face { font-family: 'Circular Std'; src: url('font/CircularStd-Book.otf') format('opentype'); font-weight: 400; font-style: normal; }
@font-face { font-family: 'Circular Std'; src: url('font/CircularStd-BookItalic.otf') format('opentype'); font-weight: 400; font-style: italic; }
@font-face { font-family: 'Circular Std'; src: url('font/CircularStd-Medium.otf') format('opentype'); font-weight: 500; font-style: normal; }
@font-face { font-family: 'Circular Std'; src: url('font/CircularStd-MediumItalic.otf') format('opentype'); font-weight: 500; font-style: italic; }
@font-face { font-family: 'Circular Std'; src: url('font/CircularStd-Bold.otf') format('opentype'); font-weight: 700; font-style: normal; }
@font-face { font-family: 'Circular Std'; src: url('font/CircularStd-BoldItalic.otf') format('opentype'); font-weight: 700; font-style: italic; }
@font-face { font-family: 'Circular Std'; src: url('font/CircularStd-Light.otf') format('opentype'); font-weight: 300; font-style: normal; }
@font-face { font-family: 'Circular Std'; src: url('font/CircularStd-LightItalic.otf') format('opentype'); font-weight: 300; font-style: italic; }
@font-face { font-family: 'Circular Std'; src: url('font/CircularStd-Black.otf') format('opentype'); font-weight: 900; font-style: normal; }
/* CSS variable */
--font: 'Circular Std', 'DM Sans', sans-serif;Weights: 300 Light · 400 Book · 500 Medium · 700 Bold · 900 Black (+ italic variants where available).
DM Sans is the active fallback when Circular Std files are absent.
Figma ↔ Prototype Workflow
Prototype → Figma (push capture)
CRITICAL — how the toolbar works:
The Figma capture toolbar does NOT appear just by loading the page. It only appears when the page is opened with a special hash URL containing a livecaptureId. Thecapture.jsscript tag in the HTML is required but passive — it waits for the hash params to activate it.
Every new session requires a fresh captureId. Always follow all steps below.
Authentication: The
figma-remote-mcpserver requires Figma OAuth. Ifclaude mcp listshows "Needs authentication", the user must re-authenticate via the/plugincommand in Claude Code (notclaude mcp auth— that command doesn't exist). This may need to be done after each Claude Code restart.
When to run this
Run the full steps below whenever the user asks to:
- "spin up the prototype"
- "connect to Figma"
- "send to Figma"
- start a new session where Figma capture is expected
Step 0 — Verify capture script is in the HTML
Every prototype file must have this line in <head>:
<script src="https://mcp.figma.com/mcp/html-to-design/capture.js" async></script>Step 1 — Start the dev server
Check if already running:
lsof -i :8080If not running:
python3 -m http.server 8080Run from the project root directory.
Step 2 — Generate a captureId
Call the MCP tool with the project's target Figma file and node (from the project's README.md):
mcp__figma-remote-mcp__generate_figma_design
outputMode: "existingFile"
fileKey: "<fileKey from README>"
nodeId: "<nodeId from README>"This returns a captureId UUID. Each captureId is single-use.
Step 3 — Open the page with the hash URL
On macOS:
open "http://localhost:8080/<prototype-filename>.html#figmacapture=<captureId>&figmaendpoint=https%3A%2F%2Fmcp.figma.com%2Fmcp%2Fcapture%2F<captureId>%2Fsubmit&figmadelay=1000"Replace both <captureId> instances and <prototype-filename> with the actual values.
Step 4 — Poll until completed
Call the tool every 5 seconds with captureId only, up to 10 times:
mcp__figma-remote-mcp__generate_figma_design
captureId: "<captureId>"Stop when status is completed. The response includes the Figma node URL where the frame landed. Update the project README.md with the new node ID.
Step 5 — Toolbar persists for re-capture
After the first capture completes, the toolbar stays in the browser tab. The user can re-capture at any time without Claude — the toolbar auto-generates new captureIds.
For component-level capture: use the crosshair/selector icon in the toolbar to pick a specific element before submitting.
Figma → Prototype (pull design changes)
- User right-clicks a component in Figma → "Copy link to selection"
- User shares the URL: "Update the prototype to match this Figma node: [URL]"
- Extract
fileKeyandnodeIdfrom the URL (convert-to:in nodeId) - Call
mcp__figma-remote-mcp__get_design_contextwith fileKey and nodeId - Translate the returned React+Tailwind reference code into plain HTML+CSS matching the project's token system
- Apply changes to the active prototype file
Icons & Logos — Local Assets First
CRITICAL: Always prefer local assets over inline SVGs or remote Figma asset URLs.
When rendering any icon or logo in the prototype, follow this priority order:
- Check
assets/icons/andassets/logo/first — look for a matching local SVG file before writing inline SVGs or using Figma-hosted asset URLs - Use
<img>tags referencing local paths (e.g.<img src="assets/icons/start.svg" />) - Fall back to inline SVG only when no suitable local asset exists
Available local assets
| Folder | Contents |
|---|---|
assets/icons/ |
UI icons: dashboard.svg, course.svg, session-viewer.svg, assessment.svg, tutor.svg, support.svg, start.svg, complete.svg, success.svg, open-in-new.svg, lock.svg, locked.svg, unlocked.svg, chevron-left.svg, chevron-right.svg, arrow-down.svg, close.svg, info.svg, warning.svg, search.svg, and many more |
assets/logo/ |
TAP logos: tap-logo.svg, tap-logo-with-text.svg, and individual logo parts |
Color tinting with CSS filters
Local SVGs loaded via <img> tags cannot inherit color or currentColor. Use CSS filter to tint them:
/* Gray-02 (#455764) — default icon color */
filter: brightness(0) saturate(100%) invert(31%) sepia(12%) saturate(1122%) hue-rotate(163deg) brightness(95%) contrast(88%);
/* Primary-02 (#0069e4) — blue active/hover state */
filter: brightness(0) saturate(100%) invert(28%) sepia(98%) saturate(1825%) hue-rotate(203deg) brightness(97%) contrast(101%);
/* White — for icons on filled blue buttons */
filter: brightness(0) invert(1);When translating Figma designs
When get_design_context returns Figma-hosted asset URLs (https://www.figma.com/api/mcp/asset/...), do not use them directly in the prototype. Instead:
- Identify what the icon represents from the Figma code context (node name, alt text, surrounding labels)
- Search
assets/icons/andassets/logo/for a matching local file - Use the local file with an appropriate CSS filter for color
- Only use inline SVG as a last resort if no local asset matches
Button Disabled States — Figma Reference
All six button variants (Filled, Gray, White, Tinted, Red, Dark Grey) have defined disabled states in Figma node 18:177. Key patterns:
| Variant | Disabled background | Disabled text | Figma node |
|---|---|---|---|
| Filled | primary-04 (#88B9F2) |
gray-06 (#F5F5F5) |
18:190 |
| Gray | gray-06 (#F5F5F5) |
gray-04 (#A8B1B7) |
18:241 |
| Dark Grey | 40% white overlay on gray-04 |
gray-06 (#F5F5F5) |
3472:101981 |
| Red | 40% white overlay on red | gray-06 (#F5F5F5) |
2671:24615 |
Dark Grey & Red disabled use a gradient overlay pattern:
background: linear-gradient(90deg, rgba(255,255,255,0.4) 0%, rgba(255,255,255,0.4) 100%), <base-color>;Always add both :disabled and [disabled] selectors, plus cursor: not-allowed.
CSS Filter Tints for Icon Colors
When using <img> tags for SVG icons, currentColor doesn't work. Use CSS filter to tint icons.
Common filters used in TAP prototypes:
| Target color | Hex | CSS filter |
|---|---|---|
| Gray-02 (default) | #455764 |
brightness(0) saturate(100%) invert(31%) sepia(12%) saturate(1122%) hue-rotate(163deg) brightness(95%) contrast(88%) |
| Gray-04 (disabled) | #A8B1B7 |
brightness(0) saturate(100%) invert(75%) sepia(5%) saturate(531%) hue-rotate(163deg) brightness(93%) contrast(87%) |
| Primary-02 (blue) | #0069E4 |
brightness(0) saturate(100%) invert(28%) sepia(98%) saturate(1825%) hue-rotate(203deg) brightness(97%) contrast(101%) |
| Green (success) | #34C759 |
brightness(0) saturate(100%) invert(48%) sepia(42%) saturate(1600%) hue-rotate(115deg) brightness(95%) contrast(88%) |
| White | #FFFFFF |
brightness(0) invert(1) |
To generate a new filter for a hex color, use https://codepen.io/sosuke/pen/Pjoqqp or manually calculate.
Activity Page Creation Pattern
When creating a new activity page, follow this standard structure:
1. File setup
- Single HTML file, same stack as course overviews (no framework, no build step)
- Same font-face declarations and CSS variables as other activity pages
- Include Figma capture script in
<head>
2. URL params + localStorage integration
Every activity page must read actId and storageKey from URL params and write completion to localStorage:
const _params = new URLSearchParams(window.location.search);
const _actId = _params.get('actId');
const _storageKey = _params.get('storageKey');
function markCompleteAndGoBack() {
if (_actId && _storageKey) {
const completed = new Set(JSON.parse(localStorage.getItem(_storageKey) || '[]'));
completed.add(_actId);
localStorage.setItem(_storageKey, JSON.stringify([...completed]));
}
if (document.referrer) {
window.history.back();
} else {
window.close();
}
}3. View toggle pattern (for two-view activities)
Activities with a default view + completion view use CSS class toggling or direct JS style manipulation:
// Direct style toggle (preferred when inline styles are present)
function endActivity() {
document.querySelector('.view-default').style.display = 'none';
document.querySelector('.view-complete').style.display = 'flex';
}Important: If views use inline
style="display:flex", CSS class-based toggling (.is-complete .view-default { display: none }) won't work because inline styles have higher specificity. Use JS direct style manipulation instead.
4. Wiring to course overviews
Add routing in the course overview's startActivity() function:
function startActivity(actId, encodedTitle, type, subtype) {
const qs = '?actId=' + actId + '&storageKey=' + STORAGE_KEY;
let page;
if (actId === 'act-1') page = 'activity-pre-assessment.html';
else if (actId === 'act-10') page = 'activity-post-assessment.html';
else if (type === 'sim' && subtype === 'vr') page = 'activity-vr-pairing.html';
else if (type === 'sim') page = 'activity-webgl.html';
else if (type === 'reading') page = 'activity-e-learning.html';
else page = 'activity-e-learning.html';
window.open(page + qs, '_blank');
}5. Cross-tab detection
Course overviews detect completion via the storage event — no additional wiring needed as long as the activity page writes to the correct storageKey.
Figma Asset Export Tips
When exporting images from Figma for activity pages:
- Use
get_design_contextto get asset URLs, then download withcurl - Check dimensions — Figma may export the full frame (1920×1080) instead of just the target node
- Crop if needed — use
sips -c <height> <width> --cropOffset <y> <x>on macOS to crop to the node's bounds - Calculate crop coordinates from the Figma CSS positioning data (w-[%], h-[%], left-[%], top-[%])
- Verify visually — use the Read tool on the cropped image to confirm it looks correct
Project File Conventions
Every TAP prototype project should have:
| File / Folder | Purpose |
|---|---|
README.md |
Project-specific: active prototype filename, Figma URLs, component rules, activity IDs, changelog |
TAP-BRAND.md |
Shared brand reference: color tokens, typography, spacing, buttons — do not modify per-project |
SKILL.md |
This file — reusable process knowledge, shared across projects |
font/ |
Circular Std OTF files — shared, do not duplicate |
assets/icons/ |
SVG icons — filled + outlined variants |
assets/logo/ |
TAP logo assets |
assets/images/ |
Activity page images (3D scenes, badges, backgrounds) |
activities/ |
Shared activity HTML pages (opened in new tabs from course overviews) |
Folder Structure & Path Rules
Course Overview Prototype/
├── *.html ← active prototypes (root)
├── activities/ ← shared activity pages
├── assets/icons/ ← SVG icons
├── assets/logo/ ← TAP logos
├── assets/images/ ← activity page images
├── font/ ← Circular Std OTF files
├── March 17 User Testing/ ← archived prototypes
├── Past Explorations/ ← archived older prototypesPath rules for startActivity():
- Root prototypes →
activities/activity-*.html March 17 User Testing/prototypes →../activities/activity-*.html
Path rules for assets/fonts:
- Root prototypes →
assets/...,font/... March 17 User Testing/prototypes →../assets/...,../font/...activities/pages →../assets/...,../font/...
Activity Card Layout Variants
Two layout styles for the activity list:
Separated cards (inline3)
Each activity is its own bordered card with gaps between them.
.activities-list { display: flex; flex-direction: column; gap: 12px; }
.activity-row {
border: 1px solid var(--gray-05); border-radius: 12px;
padding: 12px 25px 12px 41px;
}Connected list (inline2, inline4, multimeter)
All activities in one shared bordered container with divider lines.
.activities-list { border-radius: 12px; overflow: hidden; border: 1px solid var(--gray-05); }
.activity-row { border-bottom: 1px solid var(--gray-05); padding: 12px 25px 12px 41px; }
.activity-row:last-child { border-bottom: none; }Activity Button Style Variants
Tinted text buttons (inline3, inline4, multimeter3)
Light blue background, blue text, fixed width. Used for Start/Learn/VR/WebGL labels.
.btn-tinted { background: var(--primary-06); color: var(--primary-02); }
.btn-tinted:disabled { background: var(--gray-06); color: var(--gray-04); cursor: not-allowed; }
.act-btn { width: 88px; justify-content: center; }Filled buttons for focused/current state (sequential prototypes)
When an activity row has primary-06 background (focused/current), tinted buttons blend in. Use btn-filled (solid blue, white text) instead for contrast.
Icon-only buttons (multimeter2, sequential prototypes)
Round icon buttons using btn-icon (outline) and btn-icon-filled (solid blue). Used in older prototypes; newer variants prefer tinted text buttons.
Activity Title Type Prefixes
Some prototypes (inline2, sequential-inline2) prefix activity titles with the type:
- Simulation activities →
"Simulation: <title>" - Reading activities →
"E-Learning: <title>" - Assessment titles remain unprefixed
This is done in the HTML directly (not JS-generated), so update the <span class="act-title"> content.
Confirmation Modal Pattern
Used in sequential-multimeter2.html for AI assessment activities. Shows a "Before You Begin" modal before launching voice-based activities.
When to use
Add to any prototype where assessment activities use voice/microphone (AI conversation). Do NOT add to Leak Checking prototypes — their assessments are standard e-learning, not voice.
Implementation
- CSS — Add
.modal-overlay,.modal-card,.modal-body,.modal-content,.modal-close-row,.modal-close,.modal-icon,.modal-title,.modal-desc,.modal-footerstyles before the CELEBRATION section - HTML — Add
#confirm-modaldiv before<script>tag - JS — Add
openModal(onContinue)andcloseModal()functions beforestartActivity() - Intercept — In
startActivity(), checkif (type === 'ai-assessment')→ callopenModal(() => window.open(...))instead of opening directly
Design references
- Modal layout: Figma TAP Web
3092:74506(Alert Pop Up) - Info icon:
assets/icons/info-blue.svg(exported from Figma4748:156387) - Close button: Figma
4744:143502— 4px padding, 4px border-radius, gray-02 icon, hover: gray-06 bg, pressed: gray-05 bg