Mark overdrive as beta, remove product-level suggestions

Skill changes:
- Remove real-time collaboration section (product decision, not UI)
- Remove service workers / offline-first (product decision)
- Remove SharedArrayBuffer (too niche)
- Replace "application architecture" section with "performance-critical
  UI" — focused on making existing features feel fast
- Add explicit note: this skill enhances how UI FEELS, not what a
  product DOES

Beta label:
- Add betaCommands list to data.js
- Show BETA badge on /overdrive in command palette
- Show β symbol on periodic table element
- Show BETA badge on cheatsheet page

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Paul Bakaus
2026-03-16 11:42:59 -07:00
co-authored by Claude Opus 4.6
parent 5b5bba6488
commit 9bdb836903
9 changed files with 190 additions and 110 deletions
+6 -9
View File
@@ -1,6 +1,6 @@
---
name: overdrive
description: Push interfaces past conventional limits with technically ambitious implementations. Whether that's a shader, a 60fps virtual table, spring physics on a dialog, or real-time collaboration — make users ask "how did they do that?"
description: Push interfaces past conventional limits with technically ambitious implementations. Whether that's a shader, a 60fps virtual table, spring physics on a dialog, or scroll-driven reveals — make users ask "how did they do that?"
user-invokable: true
args:
- name: target
@@ -28,8 +28,8 @@ Pages, hero sections, landing pages, portfolios — the "wow" is often sensory:
### For functional UI
Tables, forms, dialogs, navigation — the "wow" is in how it FEELS: a dialog that morphs from the button that triggered it via View Transitions, a data table that renders 100k rows at 60fps via virtual scrolling, a form with streaming validation that feels instant, drag-and-drop with spring physics.
### For application architecture
The "wow" is invisible but felt: offline-first with service workers (the app works on a plane), real-time collaboration (cursors appearing as someone else types), near-native computation via WASM, background processing via Web Workers that never blocks the UI.
### For performance-critical UI
The "wow" is invisible but felt: a search that filters 50k items without a flicker, a complex form that never blocks the main thread, an image editor that processes in near-real-time. The interface just never hesitates.
### For data-heavy interfaces
Charts and dashboards — the "wow" is in fluidity: GPU-accelerated rendering via Canvas/WebGL for massive datasets, animated transitions between data states, force-directed graph layouts that settle naturally.
@@ -66,17 +66,14 @@ Organized by what you're trying to achieve, not by technology name.
### Push performance boundaries
- **Web Workers** — move computation off the main thread. Heavy data processing, image manipulation, search indexing — anything that would cause jank.
- **OffscreenCanvas** — render in a Worker thread. The main thread stays free while complex visuals render in the background.
- **WASM** — near-native performance for computation-heavy features. Image processing, encryption, physics simulations, codecs.
- **Service Workers** — offline-first experiences, background sync, push notifications. The app works without a network.
### Enable real-time collaboration
- **WebSockets / SSE** — live cursors, collaborative editing, streaming updates. The interface feels alive.
- **SharedArrayBuffer** — shared memory between threads for high-performance real-time data.
- **WASM** — near-native performance for computation-heavy features. Image processing, physics simulations, codecs.
### Interact with the device
- **Web Audio API** — spatial audio, audio-reactive visualizations, sonic feedback. Requires user gesture to start.
- **Device APIs** — orientation, ambient light, geolocation. Use sparingly and always with user permission.
**NOTE**: This skill is about enhancing how an interface FEELS, not changing what a product DOES. Adding real-time collaboration, offline support, or new backend capabilities are product decisions, not UI enhancements. Focus on making existing features feel extraordinary.
## Implement with Discipline
### Progressive enhancement is non-negotiable
+2 -1
View File
@@ -267,8 +267,9 @@
}
}
const isBeta = cmd.id === 'overdrive';
html += `<div class="command">
<div class="command-name">/${cmd.id}</div>
<div class="command-name">/${cmd.id}${isBeta ? ' <span style="font-size:0.55rem;font-weight:600;letter-spacing:0.08em;text-transform:uppercase;color:var(--color-accent,#c8956c);border:1px solid;border-radius:3px;padding:1px 5px;vertical-align:middle;margin-left:4px;">BETA</span>' : ''}</div>
<div class="command-info">
<div class="command-desc">${cmd.description}</div>
${metaHtml ? `<div class="command-meta">${metaHtml}</div>` : ''}
File diff suppressed because one or more lines are too long
+14
View File
@@ -78,6 +78,20 @@
font-weight: 500;
}
.beta-badge {
font-family: var(--font-body);
font-size: 0.55rem;
font-weight: 600;
letter-spacing: 0.08em;
text-transform: uppercase;
color: var(--color-accent);
border: 1px solid var(--color-accent);
border-radius: 3px;
padding: 1px 5px;
vertical-align: middle;
margin-left: 6px;
}
.manual-cmd-desc {
font-size: 1rem;
line-height: 1.6;
+19 -1
View File
@@ -3,7 +3,7 @@
* Clean grid visualization showing all commands organized by category
*/
import { commandCategories, commandRelationships } from '../data.js';
import { commandCategories, commandRelationships, betaCommands } from '../data.js';
// Colors now reference CSS custom properties for dark mode support
const categoryColors = {
@@ -334,6 +334,24 @@ export class PeriodicTable {
name.textContent = `/${cmd}`;
el.appendChild(name);
// Beta badge
if (betaCommands.includes(cmd)) {
const badge = document.createElement('div');
badge.style.cssText = `
position: absolute;
top: 2px;
right: 3px;
font-family: var(--font-mono);
font-size: 5px;
letter-spacing: 0.05em;
color: ${colors.text};
opacity: 0.45;
text-transform: uppercase;
`;
badge.textContent = 'β';
el.appendChild(badge);
}
// Shared handler for activation (hover or focus)
const activate = () => {
// Visual feedback
+4 -2
View File
@@ -1,6 +1,6 @@
import { renderCommandDemo, initCommandDemo } from "../demo-renderer.js";
import { initSplitCompare } from "../effects/split-compare.js";
import { commandProcessSteps, commandCategories, commandRelationships } from "../data.js";
import { commandProcessSteps, commandCategories, commandRelationships, betaCommands } from "../data.js";
// Track current split instance and command for cleanup
let currentSplitInstance = null;
@@ -141,9 +141,11 @@ function renderManualEntry(cmd) {
}
}
const isBeta = betaCommands.includes(cmd.id);
return `
<div class="manual-entry" data-id="${cmd.id}" id="cmd-${cmd.id}">
<h3 class="manual-cmd-name">/${cmd.id}</h3>
<h3 class="manual-cmd-name">/${cmd.id}${isBeta ? ' <span class="beta-badge">BETA</span>' : ''}</h3>
<p class="manual-cmd-desc">${cmd.description}</p>
${relationshipHTML}
</div>
+5
View File
@@ -12,6 +12,11 @@ export const readyCommands = [
'normalize' // First command to be fully completed
];
// Commands marked as beta — shown with a badge in the UI
export const betaCommands = [
'overdrive'
];
// Consolidated frontend-design skill with reference domains
export const skillFocusAreas = {
'frontend-design': [
+133 -87
View File
@@ -47,116 +47,140 @@ export default {
ctx.fillStyle = '#0e0d0b';
ctx.fillRect(0, 0, w, h);
// ── Signature path ──
function buildSignaturePath() {
const pts = [];
function bez(x0,y0, cx1,cy1, cx2,cy2, x1,y1, n) {
for (let i = 0; i <= n; i++) {
const t = i / n, mt = 1-t;
pts.push({
x: mt*mt*mt*x0 + 3*mt*mt*t*cx1 + 3*mt*t*t*cx2 + t*t*t*x1,
y: mt*mt*mt*y0 + 3*mt*mt*t*cy1 + 3*mt*t*t*cy2 + t*t*t*y1
});
// ── Signature paths — two separate strokes ──
function buildSignaturePaths() {
function makePath(buildFn) {
const pts = [];
function bez(x0,y0, cx1,cy1, cx2,cy2, x1,y1, n) {
for (let i = 0; i <= n; i++) {
const t = i / n, mt = 1-t;
pts.push({
x: mt*mt*mt*x0 + 3*mt*mt*t*cx1 + 3*mt*t*t*cx2 + t*t*t*x1,
y: mt*mt*mt*y0 + 3*mt*mt*t*cy1 + 3*mt*t*t*cy2 + t*t*t*y1
});
}
}
buildFn(bez);
return pts;
}
// P
bez(6,44, 5,32, 4,18, 8,8, 14);
bez(8,8, 16,5, 26,7, 26,16, 12);
bez(26,16, 26,22, 18,26, 14,28, 10);
// a
bez(14,28, 18,22, 23,20, 26,22, 8);
bez(26,22, 29,24, 28,30, 24,32, 6);
bez(24,32, 28,34, 30,30, 32,28, 5);
// u
bez(32,28, 34,36, 38,40, 42,32, 8);
bez(42,32, 44,26, 47,24, 48,28, 6);
// l
bez(48,28, 49,16, 50,6, 53,8, 10);
bez(53,8, 55,14, 56,28, 58,32, 8);
// B
bez(66,44, 66,32, 67,16, 70,8, 14);
bez(70,8, 78,4, 83,10, 79,18, 12);
bez(79,18, 84,15, 87,24, 80,30, 12);
bez(80,30, 78,34, 80,36, 84,32, 5);
// akaus
bez(84,32, 89,24, 94,22, 97,26, 8);
bez(97,26, 99,30, 96,34, 100,30, 5);
bez(100,30, 101,20, 102,14, 104,16, 8);
bez(104,16, 106,24, 109,28, 107,32, 6);
bez(107,32, 105,36, 110,36, 113,30, 6);
bez(113,30, 118,24, 122,22, 125,28, 8);
bez(125,28, 128,36, 133,38, 137,30, 8);
bez(137,30, 139,26, 142,24, 144,28, 5);
bez(144,28, 154,24, 170,22, 195,28, 16);
// Same scaling as the static preview version
const paul = makePath(bez => {
// P
bez(6,44, 5,32, 4,18, 8,8, 14);
bez(8,8, 16,5, 26,7, 26,16, 12);
bez(26,16, 26,22, 18,26, 14,28, 10);
// a
bez(14,28, 18,22, 23,20, 26,22, 8);
bez(26,22, 29,24, 28,30, 24,32, 6);
bez(24,32, 28,34, 30,30, 32,28, 5);
// u
bez(32,28, 34,36, 38,40, 42,32, 8);
bez(42,32, 44,26, 47,24, 48,28, 6);
// l
bez(48,28, 49,16, 50,6, 53,8, 10);
bez(53,8, 55,14, 56,28, 58,32, 8);
});
const bakaus = makePath(bez => {
// B
bez(66,44, 66,32, 67,16, 70,8, 14);
bez(70,8, 78,4, 83,10, 79,18, 12);
bez(79,18, 84,15, 87,24, 80,30, 12);
bez(80,30, 78,34, 80,36, 84,32, 5);
// akaus
bez(84,32, 89,24, 94,22, 97,26, 8);
bez(97,26, 99,30, 96,34, 100,30, 5);
bez(100,30, 101,20, 102,14, 104,16, 8);
bez(104,16, 106,24, 109,28, 107,32, 6);
bez(107,32, 105,36, 110,36, 113,30, 6);
bez(113,30, 118,24, 122,22, 125,28, 8);
bez(125,28, 128,36, 133,38, 137,30, 8);
bez(137,30, 139,26, 142,24, 144,28, 5);
bez(144,28, 154,24, 170,22, 195,28, 16);
});
// Scale both paths
const rawW = 200;
const scale = (w * 0.7) / rawW;
const ox = (w - rawW * scale) / 2;
const oy = h * 0.52;
return pts.map(p => ({ x: p.x * scale + ox, y: p.y * scale * 0.75 + oy }));
const transform = p => ({ x: p.x * scale + ox, y: p.y * scale * 0.75 + oy });
return [paul.map(transform), bakaus.map(transform)];
}
const sigPoints = buildSignaturePath();
const segLengths = [];
let totalLength = 0;
for (let i = 1; i < sigPoints.length; i++) {
const dx = sigPoints[i].x - sigPoints[i-1].x;
const dy = sigPoints[i].y - sigPoints[i-1].y;
segLengths.push(Math.sqrt(dx*dx + dy*dy));
totalLength += segLengths[segLengths.length - 1];
const strokes = buildSignaturePaths();
// Precompute lengths for each stroke
function computeLengths(pts) {
const lens = [];
let total = 0;
for (let i = 1; i < pts.length; i++) {
const dx = pts[i].x - pts[i-1].x, dy = pts[i].y - pts[i-1].y;
const l = Math.sqrt(dx*dx + dy*dy);
lens.push(l); total += l;
}
return { lens, total };
}
function posAt(dist) {
const strokeData = strokes.map(pts => {
const { lens, total } = computeLengths(pts);
return { pts, lens, total };
});
function posAtStroke(stroke, dist) {
let d = 0;
for (let i = 0; i < segLengths.length; i++) {
if (d + segLengths[i] >= dist) {
const t = segLengths[i] > 0 ? (dist - d) / segLengths[i] : 0;
const p0 = sigPoints[i], p1 = sigPoints[i+1];
for (let i = 0; i < stroke.lens.length; i++) {
if (d + stroke.lens[i] >= dist) {
const t = stroke.lens[i] > 0 ? (dist - d) / stroke.lens[i] : 0;
const p0 = stroke.pts[i], p1 = stroke.pts[i+1];
return { x: p0.x + (p1.x - p0.x) * t, y: p0.y + (p1.y - p0.y) * t };
}
d += segLengths[i];
d += stroke.lens[i];
}
return sigPoints[sigPoints.length - 1];
return stroke.pts[stroke.pts.length - 1];
}
const totalLength = strokeData.reduce((s, d) => s + d.total, 0);
// ── State ──
let currentStroke = 0;
let drawnLength = 0;
const drawSpeed = totalLength / 3.0;
let prevTip = sigPoints[0];
let prevTip = strokes[0][0];
let sparks = [];
let phase = 'drawing';
let phase = 'drawing'; // drawing, lifting, holding, fading
let phaseTimer = 0;
let lastTime = 0;
// Track all drawn points for smooth rendering
const drawnPts = [];
// Track drawn points per stroke for smooth rendering
const allDrawnStrokes = [[], []];
function drawBurnTrail() {
if (drawnPts.length < 2) return;
ctx.lineCap = 'round';
ctx.lineJoin = 'round';
// Draw smooth path through all points using quadratic curves
function strokeSmooth(color, width) {
function strokeSmooth(pts, color, width) {
if (pts.length < 2) return;
ctx.beginPath();
ctx.moveTo(drawnPts[0].x, drawnPts[0].y);
for (let i = 1; i < drawnPts.length - 1; i++) {
const mx = (drawnPts[i].x + drawnPts[i+1].x) / 2;
const my = (drawnPts[i].y + drawnPts[i+1].y) / 2;
ctx.quadraticCurveTo(drawnPts[i].x, drawnPts[i].y, mx, my);
ctx.moveTo(pts[0].x, pts[0].y);
for (let i = 1; i < pts.length - 1; i++) {
const mx = (pts[i].x + pts[i+1].x) / 2;
const my = (pts[i].y + pts[i+1].y) / 2;
ctx.quadraticCurveTo(pts[i].x, pts[i].y, mx, my);
}
const last = drawnPts[drawnPts.length - 1];
ctx.lineTo(last.x, last.y);
ctx.lineTo(pts[pts.length-1].x, pts[pts.length-1].y);
ctx.strokeStyle = color;
ctx.lineWidth = width;
ctx.stroke();
}
strokeSmooth('rgba(180, 100, 30, 0.12)', 5);
strokeSmooth('rgba(220, 140, 50, 0.3)', 2.5);
strokeSmooth('rgba(255, 210, 130, 0.7)', 1.2);
strokeSmooth('rgba(255, 248, 235, 0.6)', 0.4);
// Draw all accumulated strokes
for (const pts of allDrawnStrokes) {
strokeSmooth(pts, 'rgba(180, 100, 30, 0.12)', 5);
strokeSmooth(pts, 'rgba(220, 140, 50, 0.3)', 2.5);
strokeSmooth(pts, 'rgba(255, 210, 130, 0.7)', 1.2);
strokeSmooth(pts, 'rgba(255, 248, 235, 0.6)', 0.4);
}
}
function emitSparks(x, y, count) {
@@ -180,23 +204,42 @@ export default {
lastTime = timestamp;
switch (phase) {
case 'drawing':
case 'drawing': {
const sd = strokeData[currentStroke];
drawnLength += drawSpeed * dt;
if (drawnLength >= totalLength) {
drawnLength = totalLength;
phase = 'holding';
phaseTimer = 0;
emitSparks(prevTip.x, prevTip.y, 8);
if (drawnLength >= sd.total) {
drawnLength = sd.total;
emitSparks(prevTip.x, prevTip.y, 6);
if (currentStroke < strokes.length - 1) {
// Lift — pause briefly before starting next stroke
phase = 'lifting';
phaseTimer = 0;
} else {
phase = 'holding';
phaseTimer = 0;
}
}
const tip = posAt(drawnLength);
drawnPts.push({ x: tip.x, y: tip.y });
const tip = posAtStroke(sd, drawnLength);
allDrawnStrokes[currentStroke].push({ x: tip.x, y: tip.y });
prevTip = tip;
if (Math.random() < 0.4) emitSparks(tip.x, tip.y, 1);
// Redraw full smooth trail (clear burn canvas first)
// Redraw full smooth trail
ctx.fillStyle = '#0e0d0b';
ctx.fillRect(0, 0, w, h);
drawBurnTrail();
break;
}
case 'lifting':
phaseTimer += dt;
if (phaseTimer >= 0.25) {
currentStroke++;
drawnLength = 0;
prevTip = strokes[currentStroke][0];
phase = 'drawing';
phaseTimer = 0;
}
break;
case 'holding':
phaseTimer += dt;
@@ -210,8 +253,11 @@ export default {
if (phaseTimer >= 2.0) {
ctx.fillStyle = '#0e0d0b';
ctx.fillRect(0, 0, w, h);
drawnLength = 0; prevTip = sigPoints[0];
sparks = []; drawnPts.length = 0;
currentStroke = 0; drawnLength = 0;
prevTip = strokes[0][0];
sparks = [];
allDrawnStrokes[0].length = 0;
allDrawnStrokes[1].length = 0;
phase = 'drawing'; phaseTimer = 0;
}
break;
@@ -255,8 +301,8 @@ export default {
}
// Draw laser tip on overlay
if (phase === 'drawing' && drawnLength < totalLength) {
const tip = posAt(drawnLength);
if (phase === 'drawing' && drawnLength < strokeData[currentStroke].total) {
const tip = posAtStroke(strokeData[currentStroke], drawnLength);
const fl = 0.85 + Math.random() * 0.15;
// Wide heat bloom
+6 -9
View File
@@ -1,6 +1,6 @@
---
name: overdrive
description: Push interfaces past conventional limits with technically ambitious implementations. Whether that's a shader, a 60fps virtual table, spring physics on a dialog, or real-time collaboration — make users ask "how did they do that?"
description: Push interfaces past conventional limits with technically ambitious implementations. Whether that's a shader, a 60fps virtual table, spring physics on a dialog, or scroll-driven reveals — make users ask "how did they do that?"
args:
- name: target
description: The feature or area to push into overdrive (optional)
@@ -28,8 +28,8 @@ Pages, hero sections, landing pages, portfolios — the "wow" is often sensory:
### For functional UI
Tables, forms, dialogs, navigation — the "wow" is in how it FEELS: a dialog that morphs from the button that triggered it via View Transitions, a data table that renders 100k rows at 60fps via virtual scrolling, a form with streaming validation that feels instant, drag-and-drop with spring physics.
### For application architecture
The "wow" is invisible but felt: offline-first with service workers (the app works on a plane), real-time collaboration (cursors appearing as someone else types), near-native computation via WASM, background processing via Web Workers that never blocks the UI.
### For performance-critical UI
The "wow" is invisible but felt: a search that filters 50k items without a flicker, a complex form that never blocks the main thread, an image editor that processes in near-real-time. The interface just never hesitates.
### For data-heavy interfaces
Charts and dashboards — the "wow" is in fluidity: GPU-accelerated rendering via Canvas/WebGL for massive datasets, animated transitions between data states, force-directed graph layouts that settle naturally.
@@ -66,17 +66,14 @@ Organized by what you're trying to achieve, not by technology name.
### Push performance boundaries
- **Web Workers** — move computation off the main thread. Heavy data processing, image manipulation, search indexing — anything that would cause jank.
- **OffscreenCanvas** — render in a Worker thread. The main thread stays free while complex visuals render in the background.
- **WASM** — near-native performance for computation-heavy features. Image processing, encryption, physics simulations, codecs.
- **Service Workers** — offline-first experiences, background sync, push notifications. The app works without a network.
### Enable real-time collaboration
- **WebSockets / SSE** — live cursors, collaborative editing, streaming updates. The interface feels alive.
- **SharedArrayBuffer** — shared memory between threads for high-performance real-time data.
- **WASM** — near-native performance for computation-heavy features. Image processing, physics simulations, codecs.
### Interact with the device
- **Web Audio API** — spatial audio, audio-reactive visualizations, sonic feedback. Requires user gesture to start.
- **Device APIs** — orientation, ambient light, geolocation. Use sparingly and always with user permission.
**NOTE**: This skill is about enhancing how an interface FEELS, not changing what a product DOES. Adding real-time collaboration, offline support, or new backend capabilities are product decisions, not UI enhancements. Focus on making existing features feel extraordinary.
## Implement with Discipline
### Progressive enhancement is non-negotiable