From bfbbe28471ef8f4c86114776dbcb997c4177d2b7 Mon Sep 17 00:00:00 2001 From: Harlan Wilton Date: Tue, 6 Oct 2026 23:46:08 +1100 Subject: [PATCH 1/4] feat(skills): add /skills/demos, grouped by what each Skill makes The homepage section capped demos at six, so new ones fell off it. /skills/demos lists every demo under Films and launch videos, Landing pages, UI components, and Diagrams and explainers, each a prompt index with a shareable ?demo= link. The homepage keeps a teaser, films pinned first, and links the page. Indexable for "claude skills examples" (720/mo, KD 32), noindex below six demos. --- COPY.md | 3 + GLOSSARY.md | 2 + app/components/DemoIndex.vue | 361 ++++++++++++++++++ app/components/HomeDemos.vue | 350 +---------------- app/utils/home-demos.ts | 3 + layers/marketing/app/pages/skills/demos.vue | 163 ++++++++ layers/marketing/app/utils/page-admissions.ts | 5 + layers/registry/server/data/skill-demos.json | 14 +- layers/registry/server/utils/skill-demos.ts | 6 + .../server/utils/skills-route-policy.ts | 1 + nuxt.config.ts | 2 + scripts/record-skill-demo.ts | 25 +- shared/demo-groups.ts | 47 +++ test/a11y.spec.ts | 37 +- test/unit/demo-groups.test.ts | 16 + 15 files changed, 692 insertions(+), 343 deletions(-) create mode 100644 app/components/DemoIndex.vue create mode 100644 layers/marketing/app/pages/skills/demos.vue create mode 100644 shared/demo-groups.ts create mode 100644 test/unit/demo-groups.test.ts diff --git a/COPY.md b/COPY.md index 672cbd1b..241b3f7c 100644 --- a/COPY.md +++ b/COPY.md @@ -39,6 +39,9 @@ These exact strings. Do not paraphrase them per page. | Agents way: terminal | From a terminal · Teaches your agent to search and run Skills in every project. | guidance only: the second way, over `npx skilld install skilld --global` | | Agents way: MCP | In ChatGPT, Claude, and other MCP apps · Add the server once. Then ask the chat to find a Skill. | guidance only: the third way, over the MCP server URL. Its link, "Setup steps for each app", opens `/developers?setup=mcp` | | Home section: demos | See what skills make. · Each demo is one recorded run: the prompt, and what the Agent built with the Skill. | guidance only: the homepage demo section heading and line. The section hides below three demos | +| Demos link | All {n} demos | guidance only: under the homepage demo section. Links `/skills/demos` | +| Demos page | See what skills make · Each demo is one recorded run: the prompt, and what the Agent built with the Skill. Pick a prompt to see the output, or open it live. | guidance only: the `/skills/demos` H1 and line. The meta title is "Claude skill examples: see what each one makes" for the admitted query | +| Demo groups | Films and launch videos · Landing pages · UI components · Diagrams and explainers | guidance only: `DEMO_GROUPS` in `shared/demo-groups.ts`: the `/skills/demos` group headings and their lines | | Demo stage | Open the demo | guidance only: the homepage demo stage link to the Skill page Demo panel | | Demo panel | Demo · You say · Open live demo · Open in a new tab · Run it yourself | guidance only: the Skill page Demo panel. "Show screenshots" replaces "Open live demo" while the live output shows | | Demo provenance | {Agent logo} {model}, such as Opus 5.5 | guidance only: the recording line on the Skill page panel and the homepage stage. Screen readers and the title get "Recorded with {agent}, {model}". No date and no GitHub link: the Skill page is where visitors inspect a Skill | diff --git a/GLOSSARY.md b/GLOSSARY.md index 3a991e98..f2f78383 100644 --- a/GLOSSARY.md +++ b/GLOSSARY.md @@ -94,6 +94,8 @@ Collisions **Rule:** a demo shows the Agent's output, never the Skill author's work. It always names how it was recorded. +**Groups:** demos group by what the Skill makes, on `/skills/demos`: films and launch videos, landing pages, UI components, and diagrams and explainers. A group is not a track: tracks sort Skills by kind of work. + ### install **Is:** `skilld install`. Skill files land in the project and the lockfile records them. The opt-in second step. diff --git a/app/components/DemoIndex.vue b/app/components/DemoIndex.vue new file mode 100644 index 00000000..2449634e --- /dev/null +++ b/app/components/DemoIndex.vue @@ -0,0 +1,361 @@ + + + + + diff --git a/app/components/HomeDemos.vue b/app/components/HomeDemos.vue index 83ac10cb..6d2a60d9 100644 --- a/app/components/HomeDemos.vue +++ b/app/components/HomeDemos.vue @@ -1,71 +1,23 @@ - - diff --git a/app/utils/home-demos.ts b/app/utils/home-demos.ts index bbe08534..6fb200c0 100644 --- a/app/utils/home-demos.ts +++ b/app/utils/home-demos.ts @@ -1,3 +1,4 @@ +import type { DemoMakes } from '#shared/demo-groups' import type { SkillCardSkill } from '~/types/skill-card' import { demoAgentIcon, demoModelLabel } from '#shared/demo-recording' @@ -10,6 +11,8 @@ export interface HomeDemoItem { repo: string name: string skillPath: string + /** What the Skill made: the demo's group on `/skills/demos`. */ + makes: DemoMakes /** The Skill author, and the SKILL.md in their Repository (VISION principle 1). */ authorName: string | null sourceUrl: string | null diff --git a/layers/marketing/app/pages/skills/demos.vue b/layers/marketing/app/pages/skills/demos.vue new file mode 100644 index 00000000..95e55dce --- /dev/null +++ b/layers/marketing/app/pages/skills/demos.vue @@ -0,0 +1,163 @@ + + + + + diff --git a/layers/marketing/app/utils/page-admissions.ts b/layers/marketing/app/utils/page-admissions.ts index c5b2c0c7..7fa1d897 100644 --- a/layers/marketing/app/utils/page-admissions.ts +++ b/layers/marketing/app/utils/page-admissions.ts @@ -53,9 +53,14 @@ export const FREEZE_AUDIT_PATHS: readonly string[] = [ '/learn/author-ruby-package-skills', '/learn/author-rust-package-skills', '/learn/author-go-package-skills', + '/skills/demos', ] export const PAGE_ADMISSIONS: Readonly> = { + '/skills/demos': { + targetQuery: 'claude skills examples', + admissionBar: '720 searches a month, KD 32. NuxtSEO research keywords, 2026-10-06. Holds only demos a human approved by merging, and renders noindex below six. Review at the 2026-11-11 gate.', + }, '/compare/humanize-writing-skills': { targetQuery: 'best humanizer skill for claude', admissionBar: 'Experiment F, owner-approved 2026-10-05: one original source comparison with contextual internal links. Query estimate: 30 US searches a month, below the volume bar. Retain admission at the 2026-11-11 gate only if indexed with relevant query impressions.', diff --git a/layers/registry/server/data/skill-demos.json b/layers/registry/server/data/skill-demos.json index 1e822cb7..cac6a97b 100644 --- a/layers/registry/server/data/skill-demos.json +++ b/layers/registry/server/data/skill-demos.json @@ -4,6 +4,7 @@ "owner": "anthropics", "repo": "skills", "name": "frontend-design", + "makes": "landing-page", "prompt": "Build a single-file landing page for Tidepool, a tide-chart app for surfers. Inline all CSS and JavaScript. Save it as index.html.", "agent": "Claude Code", "agentVersion": "2.1.291", @@ -34,6 +35,7 @@ "owner": "cathrynlavery", "repo": "diagram-design", "name": "diagram-design", + "makes": "diagram", "prompt": "Keep the default style guide. Draw an architecture diagram for Tidewhisk, a fictional ferry-booking service: web app, API gateway, booking service, seat cache, payments worker, database, and SMS notifier. Inline all CSS and JavaScript. Save it as diagram.html.", "agent": "Claude Code", "agentVersion": "2.1.291", @@ -57,6 +59,7 @@ "owner": "emilkowalski", "repo": "skills", "name": "emil-design-eng", + "makes": "ui-component", "prompt": "Build toast.html, a single-file HTML demo of a toast notification stack for Pebblequill, a fictional note-sync app. Show three stacked toasts on load that stay until dismissed, expand on hover, and dismiss on swipe. Inline all CSS and JavaScript.", "agent": "Claude Code", "agentVersion": "2.1.291", @@ -89,6 +92,7 @@ "name": "hyperframes", "authorName": "HeyGen", "sourceUrl": "https://github.com/heygen-com/hyperframes/blob/main/skills/hyperframes/SKILL.md", + "makes": "film", "prompt": "Make a 12-second 1920x1080 promo video for Fernlight, a fictional plant-care app: a title card, three feature beats, and an end card. Render it as promo.mp4 in this folder.", "agent": "Claude Code", "agentVersion": "2.1.291", @@ -110,12 +114,14 @@ "viewport": "desktop", "alt": "A frame from the video the Agent rendered for: Make a 12-second 1920x1080 promo video for Fernlight, a fictional plant-care app: a title card, three feature beats, and" } - ] + ], + "pin": 3 }, { "owner": "humanlayer", "repo": "skills", "name": "show-me", + "makes": "diagram", "prompt": "Show me how Cogwhistle, a fictional bike-courier dispatch app, assigns a new delivery to a rider, from order to pickup. Write it as one focused HTML file named flow.html in this folder, with all CSS and JavaScript inline.", "agent": "Claude Code", "agentVersion": "2.1.291", @@ -139,6 +145,7 @@ "owner": "jakubkrehel", "repo": "make-interfaces-feel-better", "name": "make-interfaces-feel-better", + "makes": "ui-component", "prompt": "Build pricing.html, a single-file HTML pricing section for Glintwork, a fictional photo-editing app: three plan cards, a monthly and yearly toggle, and full hover and focus states. Inline all CSS and JavaScript.", "agent": "Claude Code", "agentVersion": "2.1.291", @@ -171,6 +178,7 @@ "name": "brag", "authorName": "Latent Spaces", "sourceUrl": "https://github.com/latent-spaces/brag/blob/main/skills/brag/SKILL.md", + "makes": "film", "prompt": "/brag: make a short launch video for this project. Save the final video as launch.mp4 in this folder.", "setup": "The folder held the Tidepool landing page from the frontend-design demo, and a one-line README.", "agent": "Claude Code", @@ -202,6 +210,7 @@ "name": "lemo-opuscar", "authorName": "Lemomo", "sourceUrl": "https://github.com/lemomo-ai/lemo-opuscar/blob/main/plugin/skills/lemo-opuscar/SKILL.md", + "makes": "film", "prompt": "Make a 15-second film in the Chinese Ink Wash style about a lighthouse keeper on Wrenhollow, a fictional island, who rescues a paper boat from a storm. No narration. Render the final film as film.mp4 in this folder.", "agent": "Claude Code", "agentVersion": "2.1.291", @@ -223,7 +232,8 @@ "viewport": "desktop", "alt": "A frame from the video the Agent rendered for: Make a 15-second film in the Chinese Ink Wash style about a lighthouse keeper on Wrenhollow, a fictional island, who res" } - ] + ], + "pin": 2 } ] } diff --git a/layers/registry/server/utils/skill-demos.ts b/layers/registry/server/utils/skill-demos.ts index e28d1530..6be474b6 100644 --- a/layers/registry/server/utils/skill-demos.ts +++ b/layers/registry/server/utils/skill-demos.ts @@ -1,4 +1,6 @@ +import type { DemoMakes } from '#shared/demo-groups' import { z } from 'zod' +import { DEMO_MAKES } from '#shared/demo-groups' import { repoSkillPath } from '#shared/skill-routes' import manifest from '../data/skill-demos.json' @@ -45,6 +47,8 @@ const demoSchema = z.object({ authorName: z.string().min(1).nullable().optional(), /** The SKILL.md in the author's Repository. */ sourceUrl: z.string().url().optional(), + /** What the Skill made, which groups the demo on `/skills/demos`. */ + makes: z.enum(DEMO_MAKES), /** The exact text the Agent received after loading the Skill. */ prompt: z.string().min(1), /** What the folder held before the Agent started, when the recording seeded one. */ @@ -114,6 +118,7 @@ export interface SkillDemoView { repo: string name: string skillPath: string + makes: DemoMakes authorName: string | null sourceUrl: string | null prompt: string @@ -139,6 +144,7 @@ export function presentSkillDemo(demo: SkillDemoRecord, currentCommit: string | repo: demo.repo, name: demo.name, skillPath: repoSkillPath(demo.owner, demo.repo, demo.name), + makes: demo.makes, authorName: demo.authorName ?? null, sourceUrl: demo.sourceUrl ?? null, prompt: demo.prompt, diff --git a/layers/registry/server/utils/skills-route-policy.ts b/layers/registry/server/utils/skills-route-policy.ts index 888ff17b..76d6ccd8 100644 --- a/layers/registry/server/utils/skills-route-policy.ts +++ b/layers/registry/server/utils/skills-route-policy.ts @@ -9,6 +9,7 @@ const marketingPaths = new Set([ '/skills', '/skills/', '/skills/best', + '/skills/demos', '/skills/guide', // Merged into /skills/trending?range=all on 2026-08-15. The page is gone, so // this entry exists only to let the request reach nuxt.config's routeRules diff --git a/nuxt.config.ts b/nuxt.config.ts index 45649617..2b45c2e2 100644 --- a/nuxt.config.ts +++ b/nuxt.config.ts @@ -573,6 +573,8 @@ export default defineNuxtConfig({ // inside the 36000s nuxt-skew-protection keeps old chunks for. // `scripts/check-edge-cache.ts` proves it after each deploy. '/skills/trending': edgeCache({ maxAge: 60, staleWhileRevalidate: 3600 }), + // Demos change only on deploy. + '/skills/demos': edgeCache({ maxAge: 300, staleWhileRevalidate: 86400 }), // The homepage is the same shape as the board: rendered signed out, no // cookie read, and fed by the same feeds, so the same lifetimes hold. A // render measured 300 to 480 ms to first byte from Sydney on 2026-10-01. diff --git a/scripts/record-skill-demo.ts b/scripts/record-skill-demo.ts index 2b760d0d..6d95334e 100644 --- a/scripts/record-skill-demo.ts +++ b/scripts/record-skill-demo.ts @@ -1,9 +1,12 @@ /** * Records one demo (GLOSSARY "demo") and writes it into the repository. * - * pnpm demo:record owner/repo/skill --prompt "Build ... Save it as index.html." --output index.html - * pnpm demo:record owner/repo/skill --prompt "Make ... Render it as film.mp4." --output film.mp4 - * pnpm demo:record owner/repo/skill --prompt "..." --output launch.mp4 --seed ./site --setup "The folder held ..." + * pnpm demo:record owner/repo/skill --makes landing-page --prompt "Build ... Save it as index.html." --output index.html + * pnpm demo:record owner/repo/skill --makes film --prompt "Make ... Render it as film.mp4." --output film.mp4 + * pnpm demo:record owner/repo/skill --makes film --prompt "..." --output launch.mp4 --seed ./site --setup "The folder held ..." + * + * `--makes` is the demo's group on /skills/demos: one of DEMO_MAKES in + * shared/demo-groups.ts. * pnpm demo:record owner/repo/skill --reshoot * * 1. Reads the Skill's current source commit from the public API. @@ -30,6 +33,7 @@ * the review step: a human approves each demo by merging it. */ +import type { DemoMakes } from '../shared/demo-groups' import { execFile } from 'node:child_process' import { createHash } from 'node:crypto' import { copyFile, cp, mkdir, mkdtemp, open, readdir, readFile, rm, stat, writeFile } from 'node:fs/promises' @@ -39,6 +43,7 @@ import process from 'node:process' import { fileURLToPath, pathToFileURL } from 'node:url' import { parseArgs, promisify } from 'node:util' import { chromium } from '@playwright/test' +import { DEMO_MAKES } from '../shared/demo-groups' const run = promisify(execFile) @@ -105,6 +110,7 @@ interface DemoEntry { owner: string repo: string name: string + makes: DemoMakes authorName: string | null sourceUrl: string prompt: string @@ -123,7 +129,7 @@ interface DemoEntry { type OutputKind = 'page' | 'video' type Parsed - = | { _tag: 'record', owner: string, repo: string, name: string, prompt: string, output: string, kind: OutputKind, seed: string | null, setup: string | null } + = | { _tag: 'record', owner: string, repo: string, name: string, makes: DemoMakes, prompt: string, output: string, kind: OutputKind, seed: string | null, setup: string | null } | { _tag: 'reshoot', owner: string, repo: string, name: string } | { _tag: 'usage', message: string } @@ -133,6 +139,7 @@ function parseInput(argv: string[]): Parsed { allowPositionals: true, options: { prompt: { type: 'string' }, + makes: { type: 'string' }, output: { type: 'string', default: 'index.html' }, seed: { type: 'string' }, setup: { type: 'string' }, @@ -148,12 +155,15 @@ function parseInput(argv: string[]): Parsed { return { _tag: 'reshoot', owner, repo, name } if (!values.prompt) return { _tag: 'usage', message: 'Give the prompt with --prompt.' } + const makes = DEMO_MAKES.find(value => value === values.makes) + if (!makes) + return { _tag: 'usage', message: `Give the demo's group with --makes: one of ${DEMO_MAKES.join(', ')}.` } const kind: OutputKind | null = /^[\w-]+\.html$/.test(values.output) ? 'page' : /^[\w-]+\.mp4$/.test(values.output) ? 'video' : null if (!kind) return { _tag: 'usage', message: 'The --output file must be one HTML or MP4 file name, such as index.html or film.mp4.' } if (values.seed && !values.setup) return { _tag: 'usage', message: 'A --seed folder needs a --setup line that tells visitors what the folder held.' } - return { _tag: 'record', owner, repo, name, prompt: values.prompt, output: values.output, kind, seed: values.seed ? resolve(values.seed) : null, setup: values.setup ?? null } + return { _tag: 'record', owner, repo, name, makes, prompt: values.prompt, output: values.output, kind, seed: values.seed ? resolve(values.seed) : null, setup: values.setup ?? null } } interface SkillSource { @@ -421,7 +431,7 @@ async function reshoot(owner: string, repo: string, name: string): Promise async function main(): Promise { const input = parseInput(process.argv.slice(2)) if (input._tag === 'usage') { - console.error(`${input.message}\nUsage: pnpm demo:record owner/repo/skill --prompt "..." [--output index.html|film.mp4] [--seed dir --setup "..."]\n pnpm demo:record owner/repo/skill --reshoot`) + console.error(`${input.message}\nUsage: pnpm demo:record owner/repo/skill --makes --prompt "..." [--output index.html|film.mp4] [--seed dir --setup "..."]\n pnpm demo:record owner/repo/skill --reshoot`) process.exitCode = 2 return } @@ -429,7 +439,7 @@ async function main(): Promise { await reshoot(input.owner, input.repo, input.name) return } - const { owner, repo, name, prompt, output, kind, seed, setup } = input + const { owner, repo, name, makes, prompt, output, kind, seed, setup } = input const skillRef = `${owner}/${repo}/${name}` const source = await skillSource(owner, repo, name) @@ -458,6 +468,7 @@ async function main(): Promise { owner, repo, name, + makes, authorName: source.authorName, sourceUrl: source.sourceUrl, prompt, diff --git a/shared/demo-groups.ts b/shared/demo-groups.ts new file mode 100644 index 00000000..92c666cf --- /dev/null +++ b/shared/demo-groups.ts @@ -0,0 +1,47 @@ +/** + * What a demo's Skill makes, which is how `/skills/demos` groups them. Named + * by the thing a visitor wants made, not by the kind of work, so a film Skill + * and a page Skill never share a group. The order is the page order. + * + * This module imports nothing, so the recorder script can load it directly. + */ +export const DEMO_GROUPS = [ + { + makes: 'film', + label: 'Films and launch videos', + line: 'Videos a Skill directs and renders in code, from a prompt or a project folder.', + }, + { + makes: 'landing-page', + label: 'Landing pages', + line: 'A whole page from one prompt.', + }, + { + makes: 'ui-component', + label: 'UI components', + line: 'One component, with the states and motion the Skill teaches.', + }, + { + makes: 'diagram', + label: 'Diagrams and explainers', + line: 'Systems and flows drawn so a reader can follow them.', + }, +] as const + +export type DemoMakes = typeof DEMO_GROUPS[number]['makes'] + +export const DEMO_MAKES = DEMO_GROUPS.map(group => group.makes) as [DemoMakes, ...DemoMakes[]] + +export interface DemoGroup { + makes: DemoMakes + label: string + line: string + demos: T[] +} + +/** Demos under their group, in page order. A group with no demos is left out. */ +export function groupDemos(demos: readonly T[]): DemoGroup[] { + return DEMO_GROUPS + .map(group => ({ makes: group.makes, label: group.label, line: group.line, demos: demos.filter(demo => demo.makes === group.makes) })) + .filter(group => group.demos.length > 0) +} diff --git a/test/a11y.spec.ts b/test/a11y.spec.ts index cac0e473..f00b100b 100644 --- a/test/a11y.spec.ts +++ b/test/a11y.spec.ts @@ -600,6 +600,7 @@ describe('accessibility: components', () => { repo: 'skills', name, skillPath: `/gh/anthropics/skills/${name}`, + makes: 'landing-page' as const, authorName: 'Anthropic', sourceUrl: `https://github.com/anthropics/skills/blob/main/skills/${name}/SKILL.md`, prompt: `Build the ${name} page for Tidepool.`, @@ -619,7 +620,7 @@ describe('accessibility: components', () => { const picks = [...container.querySelectorAll('button[aria-pressed]')] expect(picks).toHaveLength(3) expect(picks[0]?.getAttribute('aria-pressed')).toBe('true') - const stage = () => container.querySelector('.home-demos__stage .home-demos__open')?.getAttribute('href') + const stage = () => container.querySelector('.demo-index__stage .demo-index__open')?.getAttribute('href') expect(stage()).toBe('/gh/anthropics/skills/one#demo') picks[1]!.click() @@ -627,6 +628,40 @@ describe('accessibility: components', () => { await new Promise(done => setTimeout(done, 400)) expect(picks[1]?.getAttribute('aria-pressed')).toBe('true') expect(stage()).toBe('/gh/anthropics/skills/two#demo') + expect(container.querySelector('a[href="/skills/demos"]')?.textContent).toContain('All 3 demos') + wrapper.unmount() + }) + + it('demoIndex opens on a shared demo and reports each pick', async () => { + const shot = { src: '/d.jpg', width: 1440, height: 900, alt: 'Desktop screenshot', viewport: 'desktop' as const } + const demos = ['one', 'two'].map(name => ({ + owner: 'o', + repo: 'r', + name, + skillPath: `/gh/o/r/${name}`, + makes: 'diagram' as const, + authorName: null, + sourceUrl: null, + prompt: `Draw ${name}.`, + agent: 'Claude Code', + model: 'claude-opus-5-5', + recordedAt: '2026-10-06', + shots: [shot], + video: null, + })) + const container = createIsolatedContainer() + const wrapper = await mountSuspended( + await loadComponent('DemoIndex'), + { attachTo: container, props: { demos, initialKey: 'o/r/two' } }, + ) + const results = await runAxe(container) + expect(results.violations, formatViolations(results)).toHaveLength(0) + const picks = [...container.querySelectorAll('button[aria-pressed]')] + expect(picks[1]?.getAttribute('aria-pressed')).toBe('true') + + picks[0]!.click() + await nextTick() + expect(wrapper.emitted('pick')?.[0]?.[0]).toMatchObject({ name: 'one' }) wrapper.unmount() }) diff --git a/test/unit/demo-groups.test.ts b/test/unit/demo-groups.test.ts new file mode 100644 index 00000000..16d22eec --- /dev/null +++ b/test/unit/demo-groups.test.ts @@ -0,0 +1,16 @@ +import { describe, expect, it } from 'vitest' +import { groupDemos } from '../../shared/demo-groups' + +describe('groupDemos', () => { + it('puts demos under their group in page order and leaves empty groups out', () => { + const groups = groupDemos([ + { name: 'diagram-design', makes: 'diagram' as const }, + { name: 'brag', makes: 'film' as const }, + { name: 'show-me', makes: 'diagram' as const }, + ]) + expect(groups.map(group => [group.label, group.demos.map(demo => demo.name)])).toEqual([ + ['Films and launch videos', ['brag']], + ['Diagrams and explainers', ['diagram-design', 'show-me']], + ]) + }) +}) From e132f78af6c88827604e265593cc6fa539caa91a Mon Sep 17 00:00:00 2001 From: Harlan Wilton Date: Tue, 6 Oct 2026 23:52:56 +1100 Subject: [PATCH 2/4] feat(skills): put /skills/demos in the board shell, with a header link Harlan's review: the trending page's full-width shell and header, the groups as compact headings in the left rail, no descriptions or jump chips, and one stage that shows the full prompt. The stage moves into DemoStage so the homepage teaser and the page share it. Adds Skill Demos to the header and the mobile menu. --- COPY.md | 1 + app/app.vue | 8 + app/components/DemoIndex.vue | 106 +------- app/components/DemoStage.vue | 140 +++++++++++ app/components/_HeaderNavigation.vue | 12 + layers/marketing/app/pages/skills/demos.vue | 261 +++++++++++++------- test/a11y.spec.ts | 3 +- 7 files changed, 338 insertions(+), 193 deletions(-) create mode 100644 app/components/DemoStage.vue diff --git a/COPY.md b/COPY.md index 241b3f7c..7723c17f 100644 --- a/COPY.md +++ b/COPY.md @@ -39,6 +39,7 @@ These exact strings. Do not paraphrase them per page. | Agents way: terminal | From a terminal · Teaches your agent to search and run Skills in every project. | guidance only: the second way, over `npx skilld install skilld --global` | | Agents way: MCP | In ChatGPT, Claude, and other MCP apps · Add the server once. Then ask the chat to find a Skill. | guidance only: the third way, over the MCP server URL. Its link, "Setup steps for each app", opens `/developers?setup=mcp` | | Home section: demos | See what skills make. · Each demo is one recorded run: the prompt, and what the Agent built with the Skill. | guidance only: the homepage demo section heading and line. The section hides below three demos | +| Nav: demos | Skill Demos | guidance only: the header link after Trending Skills, and the same item in the mobile menu. Links `/skills/demos` | | Demos link | All {n} demos | guidance only: under the homepage demo section. Links `/skills/demos` | | Demos page | See what skills make · Each demo is one recorded run: the prompt, and what the Agent built with the Skill. Pick a prompt to see the output, or open it live. | guidance only: the `/skills/demos` H1 and line. The meta title is "Claude skill examples: see what each one makes" for the admitted query | | Demo groups | Films and launch videos · Landing pages · UI components · Diagrams and explainers | guidance only: `DEMO_GROUPS` in `shared/demo-groups.ts`: the `/skills/demos` group headings and their lines | diff --git a/app/app.vue b/app/app.vue index 64b9c320..cbcd246b 100644 --- a/app/app.vue +++ b/app/app.vue @@ -177,6 +177,14 @@ watch(() => route.fullPath, () => { Trending Skills + import type { HomeDemoItem } from '~/utils/home-demos' -import { createReusableTemplate } from '@vueuse/core' import { githubAvatarProxyUrl } from '#shared/image-proxy' -import { demoCardSkill, demoHasPhoneFrame, demoHref, demoKey, demoPhoneRatio } from '~/utils/home-demos' +import { demoKey } from '~/utils/home-demos' import { resolveAuthorName } from '~/utils/skill-byline' -import DemoMedia from './home-demos/_DemoMedia.vue' -import DemoRecording from './home-demos/_DemoRecording.vue' -import SkillCard from './SkillCard.vue' +import DemoStage from './DemoStage.vue' /** * Demos as a prompt index. The prompts are the menu: pick one and the stage @@ -39,45 +36,11 @@ function pick(index: number): void { function author(demo: HomeDemoItem): string { return resolveAuthorName(demo.owner, demo.authorName) ?? demo.owner } - -const [DefineStage, ReuseStage] = createReusableTemplate<{ demo: HomeDemoItem, eager?: boolean }>()