Skip to content

Commit d591cb1

Browse files
authored
perf(demos): paint the Skill page demo from its first screen (#504)
* perf(demos): paint the Skill page demo from its first screen The Skill page's largest paint was a lazy full-page screenshot, up to 6000 pixels tall. The recorder now keeps a first-screen poster for a taller page. The page loads it eagerly at high priority and fetches the whole page only when the window scrolls. * chore(demos): reshoot tall page demos with posters Retaken with pnpm demo:record --reshoot from each kept output. Demos one screen tall need no poster and stay as they were.
1 parent 80d9b79 commit d591cb1

6 files changed

Lines changed: 196 additions & 49 deletions

File tree

‎layers/registry/app/components/_SkillDemo.vue‎

Lines changed: 13 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
<script setup lang="ts">
22
import type { SkillDemoView } from '../../server/utils/skill-demos'
33
import { demoAgentIcon, demoModelLabel } from '#shared/demo-recording'
4+
import SkillDemoShot from './_SkillDemoShot.vue'
45
56
/**
67
* One demo (GLOSSARY "demo"): the prompt, the output an Agent made with this
@@ -15,6 +16,10 @@ const mobileShot = computed(() => demo.shots.find(shot => shot.viewport === 'mob
1516
const recordedLabel = computed(() => `Recorded with ${demo.agent}, ${demoModelLabel(demo.model)}`)
1617
1718
const live = ref(false)
19+
20+
/** The screens each twin window hides on; the styles below repeat them. */
21+
const PHONE_WINDOW_MEDIA = '(max-width: 47.99rem)'
22+
const DESKTOP_WINDOW_MEDIA = '(min-width: 48rem)'
1823
</script>
1924

2025
<template>
@@ -66,42 +71,26 @@ const live = ref(false)
6671
<!-- Each shot is the whole page, so it sits in a window the size of one screen and scrolls. -->
6772
<!-- One shot per device: the phone shot on phones when one exists, the desktop shot elsewhere. -->
6873
<div v-else class="mt-4">
69-
<div
74+
<SkillDemoShot
7075
v-if="desktopShot"
76+
:shot="desktopShot"
77+
:skip-media="mobileShot ? PHONE_WINDOW_MEDIA : undefined"
7178
class="skill-demo__window"
7279
:class="{ 'skill-demo__window--desktop-only': mobileShot }"
7380
:style="{ aspectRatio: '16 / 10' }"
7481
:tabindex="desktopShot.height > desktopShot.width * 10 / 16 ? 0 : undefined"
7582
role="group"
7683
aria-label="Desktop screenshot. Scroll to see the whole page."
77-
>
78-
<img
79-
:src="desktopShot.src"
80-
:width="desktopShot.width"
81-
:height="desktopShot.height"
82-
:alt="desktopShot.alt"
83-
loading="lazy"
84-
decoding="async"
85-
class="skill-demo__shot"
86-
>
87-
</div>
88-
<div
84+
/>
85+
<SkillDemoShot
8986
v-if="mobileShot"
87+
:shot="mobileShot"
88+
:skip-media="DESKTOP_WINDOW_MEDIA"
9089
class="skill-demo__window skill-demo__window--mobile"
9190
:tabindex="mobileShot.height > 844 ? 0 : undefined"
9291
role="group"
9392
aria-label="Phone screenshot. Scroll to see the whole page."
94-
>
95-
<img
96-
:src="mobileShot.src"
97-
:width="mobileShot.width"
98-
:height="mobileShot.height"
99-
:alt="mobileShot.alt"
100-
loading="lazy"
101-
decoding="async"
102-
class="skill-demo__shot"
103-
>
104-
</div>
93+
/>
10594
</div>
10695

10796
<div class="mt-3 flex flex-wrap items-center gap-x-4 gap-y-2">
@@ -171,12 +160,6 @@ const live = ref(false)
171160
outline-offset: 2px;
172161
}
173162
174-
.skill-demo__shot {
175-
display: block;
176-
width: 100%;
177-
height: auto;
178-
}
179-
180163
/*
181164
* Full width and a fixed height, not an aspect ratio: a max-height on an
182165
* aspect-ratio box shrinks its width too, which left a narrow strip.
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
<script setup lang="ts">
2+
import type { SkillDemoShot } from '../../server/utils/skill-demos'
3+
import { useEventListener } from '@vueuse/core'
4+
5+
/**
6+
* One full-page screenshot inside a window one screen tall. The demo sits
7+
* above the fold, so the window is the Skill page's largest paint: it loads
8+
* the shot's first screen eagerly at high priority, and the whole page only
9+
* once someone scrolls the window. The box already has the whole page's
10+
* height, so the window scrolls before that image arrives.
11+
*
12+
* `skipMedia` names the screens where a twin window shows instead. There the
13+
* picture resolves to an empty pixel, so a hidden window downloads nothing.
14+
*/
15+
const { shot, skipMedia } = defineProps<{
16+
shot: SkillDemoShot
17+
skipMedia?: string
18+
}>()
19+
20+
/** A 1x1 transparent GIF: what the hidden twin resolves to. */
21+
const EMPTY_IMAGE = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7'
22+
23+
const root = useTemplateRef<HTMLElement>('root')
24+
const first = computed(() => shot.poster ?? shot)
25+
const wholePage = ref(false)
26+
27+
useEventListener(root, 'scroll', () => {
28+
wholePage.value = true
29+
}, { passive: true, once: true })
30+
</script>
31+
32+
<template>
33+
<div ref="root">
34+
<div class="skill-demo-shot" :style="{ aspectRatio: `${shot.width} / ${shot.height}` }">
35+
<picture>
36+
<source v-if="skipMedia" :media="skipMedia" :srcset="EMPTY_IMAGE">
37+
<img
38+
:src="first.src"
39+
:width="first.width"
40+
:height="first.height"
41+
:alt="shot.alt"
42+
fetchpriority="high"
43+
decoding="async"
44+
class="skill-demo-shot__first"
45+
>
46+
</picture>
47+
<img
48+
v-if="shot.poster && wholePage"
49+
:src="shot.src"
50+
:width="shot.width"
51+
:height="shot.height"
52+
alt=""
53+
decoding="async"
54+
class="skill-demo-shot__whole"
55+
>
56+
</div>
57+
</div>
58+
</template>
59+
60+
<style scoped>
61+
.skill-demo-shot {
62+
position: relative;
63+
inline-size: 100%;
64+
}
65+
66+
.skill-demo-shot picture,
67+
.skill-demo-shot__first {
68+
display: block;
69+
inline-size: 100%;
70+
block-size: auto;
71+
}
72+
73+
/* Over the first screen, which it repeats, so the swap shows no seam. */
74+
.skill-demo-shot__whole {
75+
position: absolute;
76+
inset: 0;
77+
inline-size: 100%;
78+
block-size: 100%;
79+
}
80+
</style>

‎layers/registry/server/data/skill-demos.json‎

Lines changed: 59 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -14,18 +14,26 @@
1414
"outputFile": "index.html",
1515
"shots": [
1616
{
17-
"file": "desktop-2a201b3766.jpg",
17+
"file": "desktop-d514027082.jpg",
1818
"width": 1440,
1919
"height": 4102,
2020
"viewport": "desktop",
21-
"alt": "Desktop screenshot of the page the Agent built for: Build a single-file landing page for Tidepool, a tide-chart app for surfers. Inline all CSS and JavaScript. Save it as i"
21+
"alt": "Desktop screenshot of the page the Agent built for: Build a single-file landing page for Tidepool, a tide-chart app for surfers. Inline all CSS and JavaScript. Save it as i",
22+
"poster": {
23+
"file": "desktop-poster-3180cdf6a9.jpg",
24+
"height": 900
25+
}
2226
},
2327
{
24-
"file": "mobile-da8e537494.jpg",
28+
"file": "mobile-32b72a3ea8.jpg",
2529
"width": 390,
2630
"height": 5065,
2731
"viewport": "mobile",
28-
"alt": "Phone screenshot of the page the Agent built for: Build a single-file landing page for Tidepool, a tide-chart app for surfers. Inline all CSS and JavaScript. Save it as i"
32+
"alt": "Phone screenshot of the page the Agent built for: Build a single-file landing page for Tidepool, a tide-chart app for surfers. Inline all CSS and JavaScript. Save it as i",
33+
"poster": {
34+
"file": "mobile-poster-f9b907cc90.jpg",
35+
"height": 844
36+
}
2937
}
3038
],
3139
"authorName": "Anthropic",
@@ -49,7 +57,11 @@
4957
"width": 1440,
5058
"height": 927,
5159
"viewport": "desktop",
52-
"alt": "Desktop screenshot of the page the Agent built for: Keep the default style guide. Draw an architecture diagram for Tidewhisk, a fictional ferry-booking service: web app, AP"
60+
"alt": "Desktop screenshot of the page the Agent built for: Keep the default style guide. Draw an architecture diagram for Tidewhisk, a fictional ferry-booking service: web app, AP",
61+
"poster": {
62+
"file": "desktop-poster-caff9f689a.jpg",
63+
"height": 900
64+
}
5365
}
5466
],
5567
"authorName": "Cathryn Lavery",
@@ -75,14 +87,22 @@
7587
"width": 1440,
7688
"height": 6000,
7789
"viewport": "desktop",
78-
"alt": "Desktop screenshot of the page the Agent built for: Build a landing page for Glossmere, a fictional invoicing app for freelance translators that bills per word and per lang"
90+
"alt": "Desktop screenshot of the page the Agent built for: Build a landing page for Glossmere, a fictional invoicing app for freelance translators that bills per word and per lang",
91+
"poster": {
92+
"file": "desktop-poster-2aeacf6003.jpg",
93+
"height": 900
94+
}
7995
},
8096
{
8197
"file": "mobile-4d54ba1970.jpg",
8298
"width": 390,
8399
"height": 6000,
84100
"viewport": "mobile",
85-
"alt": "Phone screenshot of the page the Agent built for: Build a landing page for Glossmere, a fictional invoicing app for freelance translators that bills per word and per lang"
101+
"alt": "Phone screenshot of the page the Agent built for: Build a landing page for Glossmere, a fictional invoicing app for freelance translators that bills per word and per lang",
102+
"poster": {
103+
"file": "mobile-poster-6ab5a5de6f.jpg",
104+
"height": 844
105+
}
86106
}
87107
]
88108
},
@@ -224,11 +244,15 @@
224244
"outputFile": "flow.html",
225245
"shots": [
226246
{
227-
"file": "desktop-70a1bca51d.jpg",
247+
"file": "desktop-5bb32b6afc.jpg",
228248
"width": 1440,
229249
"height": 1060,
230250
"viewport": "desktop",
231-
"alt": "Desktop screenshot of the page the Agent built for: Show me how Cogwhistle, a fictional bike-courier dispatch app, assigns a new delivery to a rider, from order to pickup. "
251+
"alt": "Desktop screenshot of the page the Agent built for: Show me how Cogwhistle, a fictional bike-courier dispatch app, assigns a new delivery to a rider, from order to pickup. ",
252+
"poster": {
253+
"file": "desktop-poster-2dbf5169f1.jpg",
254+
"height": 900
255+
}
232256
}
233257
],
234258
"authorName": "HumanLayer",
@@ -252,14 +276,22 @@
252276
"width": 1440,
253277
"height": 1164,
254278
"viewport": "desktop",
255-
"alt": "Desktop screenshot of the page the Agent built for: Build pricing.html, a single-file HTML pricing section for Glintwork, a fictional photo-editing app: three plan cards, a"
279+
"alt": "Desktop screenshot of the page the Agent built for: Build pricing.html, a single-file HTML pricing section for Glintwork, a fictional photo-editing app: three plan cards, a",
280+
"poster": {
281+
"file": "desktop-poster-21e78314b8.jpg",
282+
"height": 900
283+
}
256284
},
257285
{
258286
"file": "mobile-7180f99b12.jpg",
259287
"width": 390,
260288
"height": 2192,
261289
"viewport": "mobile",
262-
"alt": "Phone screenshot of the page the Agent built for: Build pricing.html, a single-file HTML pricing section for Glintwork, a fictional photo-editing app: three plan cards, a"
290+
"alt": "Phone screenshot of the page the Agent built for: Build pricing.html, a single-file HTML pricing section for Glintwork, a fictional photo-editing app: three plan cards, a",
291+
"poster": {
292+
"file": "mobile-poster-8abcea89bd.jpg",
293+
"height": 844
294+
}
263295
}
264296
],
265297
"authorName": "Jakub Krehel",
@@ -348,14 +380,22 @@
348380
"width": 1440,
349381
"height": 905,
350382
"viewport": "desktop",
351-
"alt": "Desktop screenshot of the page the Agent built for: Build a one-page dashboard for Wrenfold Bikes, a town bike share. This week's rides by station: Harbour 412, Station Roa"
383+
"alt": "Desktop screenshot of the page the Agent built for: Build a one-page dashboard for Wrenfold Bikes, a town bike share. This week's rides by station: Harbour 412, Station Roa",
384+
"poster": {
385+
"file": "desktop-poster-8fa468cdbd.jpg",
386+
"height": 900
387+
}
352388
},
353389
{
354390
"file": "mobile-2c917bf37f.jpg",
355391
"width": 390,
356392
"height": 1751,
357393
"viewport": "mobile",
358-
"alt": "Phone screenshot of the page the Agent built for: Build a one-page dashboard for Wrenfold Bikes, a town bike share. This week's rides by station: Harbour 412, Station Roa"
394+
"alt": "Phone screenshot of the page the Agent built for: Build a one-page dashboard for Wrenfold Bikes, a town bike share. This week's rides by station: Harbour 412, Station Roa",
395+
"poster": {
396+
"file": "mobile-poster-4e7f952eb1.jpg",
397+
"height": 844
398+
}
359399
}
360400
]
361401
},
@@ -375,11 +415,15 @@
375415
"outputFile": "index.html",
376416
"shots": [
377417
{
378-
"file": "desktop-5912c3c3eb.jpg",
418+
"file": "desktop-2bcdf1a732.jpg",
379419
"width": 1440,
380420
"height": 6000,
381421
"viewport": "desktop",
382-
"alt": "Desktop screenshot of the page the Agent built for: Build a landing page for Kilnmarrow, a fictional shared pottery studio that rents kiln shelf space by the firing. Don't "
422+
"alt": "Desktop screenshot of the page the Agent built for: Build a landing page for Kilnmarrow, a fictional shared pottery studio that rents kiln shelf space by the firing. Don't ",
423+
"poster": {
424+
"file": "desktop-poster-77ef6d0637.jpg",
425+
"height": 900
426+
}
383427
}
384428
]
385429
},

‎layers/registry/server/utils/skill-demos.ts‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,15 @@ const shotSchema = z.object({
2727
height: z.number().int().positive(),
2828
alt: z.string().min(1),
2929
viewport: z.enum(['desktop', 'mobile']),
30+
/**
31+
* The first screen of a shot taller than one screen, at the shot's width and
32+
* the viewport's height. The Skill page paints it first and loads the full
33+
* page only when someone scrolls the window.
34+
*/
35+
poster: z.object({
36+
file: z.string().regex(/^[\w-]+\.jpg$/),
37+
height: z.number().int().positive(),
38+
}).optional(),
3039
})
3140

3241
const videoSchema = z.object({
@@ -102,6 +111,8 @@ export interface SkillDemoShot {
102111
height: number
103112
alt: string
104113
viewport: 'desktop' | 'mobile'
114+
/** The first screen, when the shot runs longer than one. */
115+
poster: { src: string, width: number, height: number } | null
105116
}
106117

107118
export interface SkillDemoVideo {
@@ -170,6 +181,7 @@ export function presentSkillDemo(demo: SkillDemoRecord, currentCommit: string |
170181
height: shot.height,
171182
alt: shot.alt,
172183
viewport: shot.viewport,
184+
poster: shot.poster ? { src: `${media}/${shot.poster.file}`, width: shot.width, height: shot.poster.height } : null,
173185
})),
174186
}
175187
}

0 commit comments

Comments
 (0)