Skip to content

Commit e508098

Browse files
authored
fix(landing): serve pre-encoded footer artwork (#7667)
1 parent 9ac3cea commit e508098

18 files changed

Lines changed: 186 additions & 26 deletions
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/** @vitest-environment jsdom */
2+
import { createHash } from 'node:crypto'
3+
import { readFile } from 'node:fs/promises'
4+
import path from 'node:path'
5+
import { renderToStaticMarkup } from 'react-dom/server'
6+
import sharp from 'sharp'
7+
import { describe, expect, it, vi } from 'vitest'
8+
import { Cta } from '@/app/(landing)/components/cta/cta'
9+
import { FOOTER_ARTWORK } from '@/app/(landing)/components/cta/footer-artwork.generated'
10+
11+
vi.mock('@/app/(landing)/components/hero-cta', () => ({ HeroCta: () => null }))
12+
13+
describe('Footer artwork delivery', () => {
14+
it('renders responsive static sources with lazy, decorative fallbacks and no optimizer requests', () => {
15+
const host = document.createElement('div')
16+
host.innerHTML = renderToStaticMarkup(<Cta />)
17+
expect(host.innerHTML).not.toContain('/_next/image')
18+
expect(host.querySelectorAll('picture')).toHaveLength(2)
19+
expect(host.querySelector('picture')?.className).toContain('dark:hidden')
20+
expect(host.querySelectorAll('picture')[1].className).toContain('dark:block')
21+
for (const picture of host.querySelectorAll('picture')) {
22+
expect(picture.closest('[aria-hidden="true"]')).not.toBeNull()
23+
expect([...picture.querySelectorAll('source')].map((source) => source.type)).toEqual([
24+
'image/avif',
25+
'image/webp',
26+
])
27+
const image = picture.querySelector('img')!
28+
expect(image.getAttribute('loading')).toBe('lazy')
29+
expect(image.alt).toBe('')
30+
expect(image.src).toContain('/landing/footer-artwork/')
31+
expect(image.getAttribute('style')).toContain('data:image/svg+xml')
32+
}
33+
})
34+
35+
it('ships every advertised size with the matching dimensions and immutable content hash', async () => {
36+
for (const artwork of Object.values(FOOTER_ARTWORK)) {
37+
for (const srcSet of [artwork.avifSrcSet, artwork.webpSrcSet]) {
38+
for (const entry of srcSet.split(', ')) {
39+
const [src, descriptor] = entry.split(' ')
40+
const width = Number.parseInt(descriptor, 10)
41+
const buffer = await readFile(path.join(process.cwd(), 'public', src))
42+
const metadata = await sharp(buffer).metadata()
43+
expect(metadata.width).toBe(width)
44+
expect(metadata.height).toBe((width * 9) / 16)
45+
expect(src).toContain(createHash('sha256').update(buffer).digest('hex').slice(0, 12))
46+
}
47+
}
48+
}
49+
})
50+
})

apps/sim/app/(landing)/components/cta/cta.tsx

Lines changed: 26 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,22 @@
11
import { cn } from '@sim/emcn'
22
import Image from 'next/image'
33
import styles from '@/app/(landing)/components/cta/cta.module.css'
4+
import { FOOTER_ARTWORK } from '@/app/(landing)/components/cta/footer-artwork.generated'
45
import { HeroCta } from '@/app/(landing)/components/hero-cta'
56
import {
67
HOME_TYPE,
78
LANDING_CONTENT_WIDTH,
89
LANDING_GUTTER,
910
} from '@/app/(landing)/components/landing-layout'
10-
import ctaDark from '@/public/landing/cta-san-francisco-painted-dark.webp'
11-
import ctaLight from '@/public/landing/cta-san-francisco-painted-light.webp'
1211

1312
/** One shared closing statement, with a deliberate line break between sentences. */
1413
const CTA_HEADLINE = ['Every agent your company runs.', 'All in one place.'] as const
14+
const ARTWORK_SIZES = '(max-width: 639px) 112vw, 100vw'
1515

1616
/**
1717
* Painted pre-footer CTA for every marketing page, mounted once by LandingShell.
1818
* Theme classes select the matching lazy-loaded painting without client state.
19+
* Pre-encoded sources avoid request-time image optimization at every resolution.
1920
* An embedded preview fills the scene while the full-resolution image loads.
2021
* The sky mask keeps the copy clear and the lower edge fades into the footer.
2122
*/
@@ -52,26 +53,29 @@ export function Cta() {
5253
aria-hidden='true'
5354
className='-mt-[clamp(96px,12.5vw,240px)] max-sm:-mt-6 pointer-events-none relative aspect-video w-full max-sm:aspect-[16/10]'
5455
>
55-
<Image
56-
src={ctaLight}
57-
alt=''
58-
fill
59-
placeholder='blur'
60-
fetchPriority='high'
61-
quality={90}
62-
sizes='(max-width: 639px) 112vw, 100vw'
63-
className={cn('object-cover object-center dark:hidden', styles.plate)}
64-
/>
65-
<Image
66-
src={ctaDark}
67-
alt=''
68-
fill
69-
placeholder='blur'
70-
fetchPriority='high'
71-
quality={90}
72-
sizes='(max-width: 639px) 112vw, 100vw'
73-
className={cn('hidden object-cover object-center dark:block', styles.plate)}
74-
/>
56+
{Object.entries(FOOTER_ARTWORK).map(([theme, artwork]) => (
57+
<picture
58+
key={theme}
59+
className={cn(
60+
'absolute inset-0',
61+
theme === 'light' ? 'block dark:hidden' : 'hidden dark:block'
62+
)}
63+
>
64+
<source type='image/avif' srcSet={artwork.avifSrcSet} sizes={ARTWORK_SIZES} />
65+
<source type='image/webp' srcSet={artwork.webpSrcSet} sizes={ARTWORK_SIZES} />
66+
<Image
67+
src={artwork.src}
68+
alt=''
69+
fill
70+
unoptimized
71+
placeholder='blur'
72+
blurDataURL={artwork.blurDataURL}
73+
fetchPriority='high'
74+
sizes={ARTWORK_SIZES}
75+
className={cn('object-cover object-center', styles.plate)}
76+
/>
77+
</picture>
78+
))}
7579
</div>
7680
</section>
7781
)
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/** Generated by scripts/generate-footer-artwork.ts. */
2+
export const FOOTER_ARTWORK = {
3+
light: {
4+
avifSrcSet:
5+
'/landing/footer-artwork/light-1200-f29e84fa39b5.avif 1200w, /landing/footer-artwork/light-1920-352cf7c570ad.avif 1920w, /landing/footer-artwork/light-3840-e889bb4bc039.avif 3840w',
6+
webpSrcSet:
7+
'/landing/footer-artwork/light-1200-8764ab359e07.webp 1200w, /landing/footer-artwork/light-1920-c9ec24660024.webp 1920w, /landing/footer-artwork/light-3840-6a6c3eab4b02.webp 3840w',
8+
src: '/landing/footer-artwork/light-3840-6a6c3eab4b02.webp',
9+
blurDataURL:
10+
'data:image/webp;base64,UklGRjYAAABXRUJQVlA4ICoAAACwAQCdASoIAAQAAkA4JaQAAxaZftQAAP75igYWrDK4ciyu9bneK5BAAAA=',
11+
},
12+
dark: {
13+
avifSrcSet:
14+
'/landing/footer-artwork/dark-1200-a1f7345fe7f6.avif 1200w, /landing/footer-artwork/dark-1920-638fa2f584cb.avif 1920w, /landing/footer-artwork/dark-3840-c0bf8a67335d.avif 3840w',
15+
webpSrcSet:
16+
'/landing/footer-artwork/dark-1200-117c8741a843.webp 1200w, /landing/footer-artwork/dark-1920-2c586b41810a.webp 1920w, /landing/footer-artwork/dark-3840-3140a296c362.webp 3840w',
17+
src: '/landing/footer-artwork/dark-3840-3140a296c362.webp',
18+
blurDataURL:
19+
'data:image/webp;base64,UklGRiwAAABXRUJQVlA4ICAAAABwAQCdASoIAAQAAkA4JaRtgAAIQAD+9MlFFCUE/BGgAA==',
20+
},
21+
} as const

apps/sim/app/(landing)/components/landing-shell/landing-shell.test.tsx

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,8 @@ import ts from '@typescript/typescript6'
99
import { createRoot, type Root } from 'react-dom/client'
1010
import { renderToStaticMarkup } from 'react-dom/server'
1111
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
12+
import { FOOTER_ARTWORK } from '@/app/(landing)/components/cta/footer-artwork.generated'
1213
import { LandingShell } from '@/app/(landing)/components/landing-shell/landing-shell'
13-
import ctaDark from '@/public/landing/cta-san-francisco-painted-dark.webp'
14-
import ctaLight from '@/public/landing/cta-san-francisco-painted-light.webp'
1514

1615
interface TestLinkProps extends AnchorHTMLAttributes<HTMLAnchorElement> {
1716
href: string
@@ -113,8 +112,8 @@ describe('LandingShell shared closing section', () => {
113112
'Every agent your company runs. All in one place. '
114113
)
115114
expect(Array.from(cta.querySelectorAll('img'), (image) => image.getAttribute('src'))).toEqual([
116-
ctaLight,
117-
ctaDark,
115+
FOOTER_ARTWORK.light.src,
116+
FOOTER_ARTWORK.dark.src,
118117
])
119118
expect(Array.from(cta.querySelectorAll('img'), (image) => image.alt)).toEqual(['', ''])
120119
expect(

apps/sim/next.config.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -268,6 +268,11 @@ const nextConfig: NextConfig = {
268268
},
269269
],
270270
},
271+
{
272+
/** Generated footer artwork uses content hashes, so URLs are immutable. */
273+
source: '/landing/footer-artwork/:path*',
274+
headers: [{ key: 'Cache-Control', value: 'public, max-age=31536000, immutable' }],
275+
},
271276
{
272277
source: '/.well-known/:path*',
273278
headers: [
139 KB
Loading
Binary file not shown.
450 KB
Loading
Binary file not shown.
1.66 MB
Loading

0 commit comments

Comments
 (0)