Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions COPY.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,8 @@ These exact strings. Do not paraphrase them per page.
| 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 page | What /{name} made Β· One recorded run: the prompt, and what the Agent built with the Skill. | guidance only: the H1 and line of a demo page, `/skills/demos/<owner>/<repo>/<name>`. The meta title is "{name} skill example: {noun}" for the query "<skill> skill example", with `noun` from `DEMO_GROUPS`. The meta description is "{agent} made this {noun} with the /{name} skill from one prompt: β€œ{prompt}”", cut at a word to 160 characters |
| 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 Β· Prompt Β· 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 stage | Open the demo | guidance only: the demo stage link. On the homepage it opens the demo page; on a demo page it opens the Skill page Demo panel |
| Demo panel | Demo Β· Prompt Β· Open live demo Β· Open in a new tab Β· Run it yourself Β· All demos | guidance only: the Skill page Demo panel. "Show screenshots" replaces "Open live demo" while the live output shows. "All demos" opens the demo page and hides while the Skill holds a run check flag |
| 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 |
| Demo outdated | Recorded on an older version of this Skill. | guidance only: under the Demo panel when the Skill moved past the recorded commit |
| Home section: changes | Keep up with skill changes. | guidance only: the homepage section heading for watching and the digest |
Expand Down
10 changes: 7 additions & 3 deletions app/components/DemoStage.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<script setup lang="ts">
import type { HomeDemoItem } from '~/utils/home-demos'
import { demoPagePath } from '#shared/demo-pages'
import { demoCardSkill, demoHasPhoneFrame, demoHref, demoPhoneRatio } from '~/utils/home-demos'
import DemoMedia from './home-demos/_DemoMedia.vue'
import DemoRecording from './home-demos/_DemoRecording.vue'
Expand All @@ -11,15 +12,18 @@ import SkillCard from './SkillCard.vue'
* and the Skill's row with its run command. `showPrompt` adds the prompt in
* full, for a list that names the demos without their prompts. `live` puts a
* page demo's real output in the window, in a frame with no same-origin
* access; the route serves it under a CSP sandbox too.
* access; the route serves it under a CSP sandbox too. `opens` picks where
* "Open the demo" goes: the demo's own page, or, on that page, the Skill
* page's Demo panel.
*/
const { demo, surface = 'demos', eager = false, showPrompt = false, live = false } = defineProps<{
const { demo, surface = 'demos', eager = false, showPrompt = false, live = false, opens = 'demo-page' } = defineProps<{
demo: HomeDemoItem
/** The analytics surface for the run chip, such as `home-demos`. */
surface?: string
eager?: boolean
showPrompt?: boolean
live?: boolean
opens?: 'demo-page' | 'skill-page'
}>()

/** The output page itself, when this stage shows it live. A film always plays as video. */
Expand All @@ -30,7 +34,7 @@ const liveUrl = computed(() => live && !demo.video ? demo.liveUrl : null)
<div class="demo-stage">
<div class="demo-stage__head">
<DemoRecording :demo />
<NuxtLink :to="demoHref(demo)" class="demo-stage__open">
<NuxtLink :to="opens === 'skill-page' ? demoHref(demo) : demoPagePath(demo)" class="demo-stage__open">
Open the demo<span class="sr-only"> of /{{ demo.name }}</span>
<UIcon name="i-lucide-arrow-right" class="size-3.5 shrink-0" aria-hidden="true" />
</NuxtLink>
Expand Down
2 changes: 1 addition & 1 deletion layers/marketing/app/pages/skills/demos/[...demo].vue
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ defineOgImage('Page.takumi', pageDemo.value

<div id="demos-stage" ref="stage" class="demos-stage scroll-mt-24">
<Transition name="demos-swap" mode="out-in">
<DemoStage v-if="current" :key="currentKey" :demo="current" show-prompt live eager surface="demos-page" />
<DemoStage v-if="current" :key="currentKey" :demo="current" show-prompt live eager opens="skill-page" surface="demos-page" />
</Transition>
<p v-if="!current" class="text-sm text-muted">
No demos are published yet.
Expand Down
2 changes: 1 addition & 1 deletion layers/registry/app/components/SkillDetail.vue
Original file line number Diff line number Diff line change
Expand Up @@ -1722,7 +1722,7 @@ useHead(computed(() => ({

<!-- The header width, like Related: the demo answers the header question, not the file viewer one. -->
<div v-if="data.demo" class="mx-auto max-w-5xl px-4 pt-6 sm:px-6 md:pt-8">
<SkillDemo :demo="data.demo" />
<SkillDemo :demo="data.demo" :on-board="!runFlag" />
</div>

<!-- The two command blocks are breakpoint twins, so neither can hold the anchor. -->
Expand Down
15 changes: 13 additions & 2 deletions layers/registry/app/components/_SkillDemo.vue
Original file line number Diff line number Diff line change
@@ -1,15 +1,22 @@
<script setup lang="ts">
import type { SkillDemoView } from '../../server/utils/skill-demos'
import { demoPagePath } from '#shared/demo-pages'
import { demoAgentIcon, demoModelLabel } from '#shared/demo-recording'
import SkillDemoShot from './_SkillDemoShot.vue'

/**
* One demo (GLOSSARY "demo"): the prompt, the output an Agent made with this
* Skill, and how it was recorded. Screenshots paint first. The live output
* loads only on request, in a frame with no same-origin access, because the
* route serves it under a CSP sandbox too.
* route serves it under a CSP sandbox too. "All demos" opens the demo's own
* page, the board of every demo with this one on the stage. A Skill with a run
* check flag is off the board, so its panel has no such link.
*/
const { demo } = defineProps<{ demo: SkillDemoView }>()
const { demo, onBoard = true } = defineProps<{
demo: SkillDemoView
/** False while the Skill holds a run check flag: the board leaves it out, and its demo page answers 404. */
onBoard?: boolean
}>()

const desktopShot = computed(() => demo.shots.find(shot => shot.viewport === 'desktop') ?? demo.shots[0])
const mobileShot = computed(() => demo.shots.find(shot => shot.viewport === 'mobile' && shot !== desktopShot.value))
Expand Down Expand Up @@ -119,6 +126,10 @@ const DESKTOP_WINDOW_MEDIA = '(min-width: 48rem)'
Run it yourself
<UIcon name="i-lucide-arrow-down" class="size-3.5 shrink-0" aria-hidden="true" />
</a>
<NuxtLink v-if="onBoard" :to="demoPagePath(demo)" class="inline-flex min-h-11 items-center gap-1 text-sm text-default underline underline-offset-4 hover:text-primary">
All demos
<UIcon name="i-lucide-arrow-right" class="size-3.5 shrink-0" aria-hidden="true" />
</NuxtLink>
</div>
<p v-if="demo.outdated" class="mt-1 text-xs text-muted">
Recorded on an older version of this Skill.
Expand Down
9 changes: 7 additions & 2 deletions test/a11y.nuxt.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -634,13 +634,13 @@ describe('accessibility: components', () => {
expect(picks).toHaveLength(3)
expect(picks[0]?.getAttribute('aria-pressed')).toBe('true')
const stage = () => container.querySelector('.demo-index__stage .demo-stage__open')?.getAttribute('href')
expect(stage()).toBe('/gh/anthropics/skills/one#demo')
expect(stage()).toBe('/skills/demos/anthropics/skills/one')

picks[1]!.click()
await nextTick()
await new Promise(done => setTimeout(done, 400))
expect(picks[1]?.getAttribute('aria-pressed')).toBe('true')
expect(stage()).toBe('/gh/anthropics/skills/two#demo')
expect(stage()).toBe('/skills/demos/anthropics/skills/two')
expect(container.querySelector('a[href="/skills/demos"]')?.textContent).toContain('All 3 demos')
wrapper.unmount()
})
Expand Down Expand Up @@ -722,6 +722,11 @@ describe('accessibility: components', () => {
expect(results.violations, formatViolations(results)).toHaveLength(0)
expect(container.textContent).toContain('Recorded with Claude Code, Opus 5.5')
expect(container.textContent).toContain('Recorded on an older version of this Skill.')
expect(container.querySelector('a[href="/skills/demos/anthropics/skills/frontend-design"]')?.textContent).toContain('All demos')

// A flagged Skill is off the board, and its demo page answers 404.
await wrapper.setProps({ onBoard: false })
expect(container.querySelector('a[href^="/skills/demos/"]')).toBeNull()

await wrapper.get('button[aria-pressed="false"]').trigger('click')
await nextTick()
Expand Down
Loading