# Product-screenshot hero

> An opening section with the headline and actions above a large product screenshot in a browser frame, a phone frame or none. The frame reserves the image's shape and can run into the section's bottom edge.

- ID: `cmp_hero_product_screenshot_01`
- Slug: `hero-product-screenshot-01`
- Version: `1.0.0` (current)
- Status: published
- Published: 2026-09-30
- Updated: 2026-09-30
- Available versions: `1.0.0`
- Kind: section
- Primary category: `heroes`
- Detail page: https://pagesugar.com/components/hero-product-screenshot-01?variant=neutral
- Preview: https://pagesugar.com/preview/hero-product-screenshot-01

## Variants

| Variant | Label | Default | Artifact digest |
| --- | --- | --- | --- |
| `neutral` | Neutral | yes | `sha256-c2b891757aeef0041ef8cfc6fb4fe47aaf3d49e6026488f96097ff53db894a2c` |
| `indigo` | Indigo accent | no | `sha256-bcaaeb7cc624f9d8f4ebec29d868b0d693eee2a068ebfead90f237539978c947` |
| `emerald` | Emerald accent | no | `sha256-5cdd8d584b4dd264c6efd29c91dbfafbeafb563146db386e7047ee1c0ec66f85` |

## Runtime and compatibility

- Runtime: svelte
- Svelte: 5
- SvelteKit required: no (portable Svelte component)
- Tailwind CSS: 4
- SSR: supported
- Requires client-side JavaScript: no
- Integration level: presentational
- Appearance modes: light
- Suggested directory: `src/lib/components/hero-product-screenshot-01`

## Dependencies

No third-party runtime packages.

## Services

No external services required.

## Usage

Presentational: renders your headline, copy, links and screenshot exactly as supplied. It ships no screenshot and does not capture, resize or optimise one; supply a real image of your product at about twice its displayed width (2300 px covers the browser frame on a 2x screen). The frame is decoration drawn in CSS, and both actions are ordinary links. No JavaScript is needed in the browser.

Required props: `title`, `primaryAction`

```svelte
<script lang="ts">
	import ProductScreenshotHero from '$lib/components/hero-product-screenshot-01/ProductScreenshotHero.svelte';
</script>

<ProductScreenshotHero
	eyebrow="Halcyon boards"
	title="The whole quarter on one board."
	description="Every card shows its owner, its due date and how far along it is."
	primaryAction={{ label: 'Start free', href: '/signup' }}
	secondaryAction={{ label: 'See how boards work', href: '/tour/boards' }}
	frameTitle="Q4 roadmap"
	screenshot={{
		src: '/images/board.png',
		alt: 'The Q4 roadmap board with four columns of cards, from Planned to Done.',
		width: 2400,
		height: 1500
	}}
/>
```

Limitations:

- Ships no screenshot. The preview's interfaces are original mock drawings with sample data; they are preview-only and not part of the export.
- The screenshot is a static image. It does not animate, zoom or respond to clicks; a playable demo is a different component.
- The image loads eagerly with fetchpriority="high" because a hero screenshot is usually the page's largest paint. Use another component below the fold.
- From lg the browser and bare frames run into the section's bottom edge with square lower corners, and the section's own bottom padding is removed. Whatever follows the hero sits directly against the frame; set bleed={false} for a frame with four rounded corners and the usual padding.
- Three renditions at most: narrow under 640 px, medium under 1024 px, the full screenshot above. Without medium, a tablet shows the full screenshot at about half size; without narrow, a phone gets medium if there is one. All share one alt text.
- With no screenshot the frame shows an empty 16:10 (or phone-shaped) placeholder. It is for pages still waiting on an image, not a finished state.
- No dark screenshot swap. The component is light by default; the tokens retone it for a dark page, but you supply an image that suits that page.
- Light appearance by default. No dark mode is declared or selected automatically.

## Usage guide

### Product-screenshot hero

An opening section for a product page: the headline, a paragraph and one or two actions, then a
large screenshot of the product. The screenshot is wider than the copy, starts on the same left
edge as the headline, and sits in a browser frame, a phone frame or no frame at all.

#### The screenshot

Use a real screenshot of your product, exported at about twice the size it is shown at (about
2300 px wide for the browser frame, 640 px for the phone), and pass its real `width` and
`height`. They reserve the image's space before it loads.

Write `alt` as a description of what the interface shows: "The Q4 roadmap board with four
columns of cards, from Planned to Done", not "Screenshot of our app". Do not rely on text inside
the image to make your point; say it in the headline or paragraph too.

If your interface shows numbers, make sure they are either real or clearly sample data. A
dashboard of invented revenue figures reads as a claim. The `caption` prop is a good place to
say "A sample workspace".

##### A crop for phones

A desktop screenshot shrunk to a 360 px screen is unreadable. Pass a `narrow` crop of the part
that matters, and the component serves it under 640 px. Take it from your product's own phone
layout, or crop the desktop view to one column or panel, and export it at the size it is shown:
about 330 px wide on the page, so 660 to 1000 px for a sharp 2x or 3x image, with the interface's
text at its real size. A crop that still has to shrink three times is not a fix.

```svelte
<ProductScreenshotHero
	title="The whole quarter on one board."
	primaryAction={{ label: 'Start free', href: '/signup' }}
	screenshot={{
		src: '/images/board.png',
		alt: 'The Q4 roadmap board: cards grouped by status, each with an owner and a due date.',
		width: 2400,
		height: 1500,
		narrow: { src: '/images/board-column.png', width: 1080, height: 1716 }
	}}
/>
```

##### A view for tablets

Between 640 and 1023 px the frame is about 600 to 980 px wide, so a full desktop screenshot shows
at roughly half size. Pass `medium` (for example two board columns, drawn for about 720 px) and
the component serves it there:

```ts
medium: { src: '/images/board-two-columns.png', width: 1600, height: 1200 }
```

The narrow crop and the medium view share the wide image's `alt`, so write alt text that is true of both: describe
what the crop keeps ("cards grouped by status, each with an owner and a due date") rather than
everything only the wide image shows ("four columns").

#### Frames

- `browser` (default): a title bar with three quiet dots. Pass `frameTitle` for the page name in
  the bar; the component never invents an address.
- `device`: a phone with concentric corners. From the md breakpoint it sits beside the copy
  instead of under it. Use a portrait screenshot, about 9:19.5.
- `none`: the image with rounded corners and a hairline edge.

#### Bleed

From the lg breakpoint the browser and bare frames run into the section's bottom edge: the
section drops its bottom padding, and the frame's lower corners are square, so the screenshot
reads as continuing past the fold. Put a section with its own background or a top hairline
after it, or set `bleed={false}` for the usual padding and four rounded corners.

#### Retoning for a dark page

```css
.dark-hero {
	--hero-product-screenshot-ink: #fafafa;
	--hero-product-screenshot-muted: #a1a1aa;
	--hero-product-screenshot-hairline: rgb(255 255 255 / 0.1);
	--hero-product-screenshot-frame: #27272a;
	--hero-product-screenshot-accent: #fafafa;
	--hero-product-screenshot-on-accent: #18181b;
}
```

Pair it with a screenshot taken in your product's dark theme; the component cannot recolour an
image.

## Props

| Name | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `title` | `string` | yes |  | Headline text, set at display size. |
| `primaryAction` | `ProductScreenshotHeroAction` | yes |  | The one filled action: { label, href }. |
| `eyebrow` | `string` | no |  | Short label set small and uppercase above the headline. Omitted, no eyebrow renders. |
| `description` | `string` | no |  | Supporting paragraph under the headline, held to a readable measure. |
| `secondaryAction` | `ProductScreenshotHeroAction` | no |  | Optional quieter link beside the primary action: { label, href }. |
| `screenshot` | `ProductScreenshotHeroScreenshot` | no |  | { src, alt, width, height, narrow?, medium? }. Width and height are the file's intrinsic size and reserve its shape. alt describes what the interface shows and must be true of every rendition. narrow ({ src, width, height }) is used under 640 px and medium under 1024 px; each carries its own size. Omitted, the frame holds an empty placeholder. |
| `frame` | 'browser' \| 'device' \| 'none' | no | `'browser'` | Decorative frame around the screenshot. 'device' draws a phone and places it beside the copy from the md breakpoint; 'none' rounds the image and gives it a hairline. |
| `frameTitle` | `string` | no |  | Text centred in the browser frame's title bar, such as the page name. Without it the bar shows only its dots. Ignored by the other frames. |
| `caption` | `string` | no |  | Short visible caption set above the screenshot inside the figure, such as "A sample workspace". |
| `bleed` | `boolean` | no | `true` | From the lg breakpoint, run the browser or bare frame into the section's bottom edge. Ignored by the device frame. |
| `headingLevel` | 1 \| 2 | no | `1` | 1 when the hero opens the page; 2 when the page already has an h1. |

## Customization

Change content through props, retone the section through seven --hero-product-screenshot-\* CSS variables, and edit Tailwind classes in the source for the frame radius, widths or type scale.

- Accent: set --hero-product-screenshot-accent and --hero-product-screenshot-on-accent together, keeping on-accent at 4.5:1 or better. The accent fills the primary action and draws focus rings.
- Hover: the primary's hover fill mixes 12% of --hero-product-screenshot-accent-hover-mix into the accent. White (the default) lightens a near-black accent; set #000000 for a mid-tone accent such as the indigo and emerald palettes, because lightening it lowers contrast with white text.
- Text: --hero-product-screenshot-ink sets the headline and the secondary link; --hero-product-screenshot-muted sets the eyebrow, paragraph, caption and frame title. Keep muted at 4.5:1 against your page.
- Frame: --hero-product-screenshot-frame fills the browser title bar and the phone bezel (#f4f4f5 by default). --hero-product-screenshot-hairline draws the frame's edge, the line under the title bar and the edge over a bare image. The window dots and the empty placeholder are mixed from the ink colour, so they follow a retone.
- Dark page retone: ink #fafafa, muted #a1a1aa, hairline rgb(255 255 255 / 0.1), frame #27272a, accent #fafafa, on-accent #18181b (all --hero-product-screenshot-\*). The black shadow under the frame disappears on dark; the hairline carries the edge. Supply a screenshot taken in your product's dark theme.
- Screenshot: export it at about twice its displayed width and pass its real width and height. For phones, pass a narrow crop of the part that matters (one column, one panel), exported for its displayed width of about 330 px (660 to 1000 px wide for a sharp 2x or 3x image); for tablets, a medium view for about 720 px (two columns, say). Each keeps the interface's text near its real size.
- Frame shape: the browser and bare frames use rounded-xl (12 px). The phone uses a 44 px bezel radius with 8 px of padding around a 36 px screen radius; change all three together so the corners stay concentric.
- Bleed: set bleed={false} when the next section is also white and needs the usual space, or when the bottom of the screenshot must show a rounded edge.
- Headings: set headingLevel to 2 when the page already has an h1.

| Token | Public CSS variable |
| --- | --- |
| `accent` | `--hero-product-screenshot-accent` |
| `onAccent` | `--hero-product-screenshot-on-accent` |
| `accentHoverMix` | `--hero-product-screenshot-accent-hover-mix` |
| `ink` | `--hero-product-screenshot-ink` |
| `muted` | `--hero-product-screenshot-muted` |
| `hairline` | `--hero-product-screenshot-hairline` |
| `frame` | `--hero-product-screenshot-frame` |

## Accessibility

- The section is labelled by its headline, which is an h1 by default; set headingLevel to 2 when the page already has one.
- The screenshot is an img inside a figure; a caption renders as that figure's figcaption, so it is associated with the image.
- Alt text comes from you and is required: describe what the interface shows, not that it is a screenshot. Anything a visitor must know also belongs in the headline or paragraph, because text inside an image cannot be resized, translated or searched.
- The browser chrome, the window dots, the frame title and every hairline are aria-hidden decoration. The frame title is visual only; put anything important in the copy.
- The copy comes before the screenshot in the DOM at every width, so reading and tab order put the headline and actions first.
- The image has width and height attributes (and the narrow crop has its own), so its space is reserved before it loads and nothing below it moves.
- Links show a two-pixel accent focus ring on :focus-visible only, and are 48 px tall. The pressed state scales the link to 98% and is removed under prefers-reduced-motion.
- White on-accent text measures 17.7:1 on the neutral accent (#18181b), 6.3:1 on indigo (#4f46e5) and 5.5:1 on emerald (#047857); the hover fills measure about 12:1 (the neutral fill lightens), 8.1:1 and 7.2:1. Muted text (#52525b) measures 7.7:1 on white and 7.0:1 on the #f4f4f5 title bar. Ratios use the WCAG relative-luminance formula.
- The layout uses logical properties and text-start, so it mirrors under dir="rtl"; the screenshot itself is never mirrored. Tracking resets to 0 for right-to-left and CJK text, and CJK headlines keep phrases whole.
- Element IDs come from $props.id(), so two heroes on one page stay unique.

Known limitations:

- Contrast ratios are computed for the shipped palettes only; re-check any changed token (4.5:1 for text, 3:1 for the focus ring).
- The component cannot check that alt text is accurate or that the screenshot's own text is legible; both are the consumer's responsibility.
- The outlined secondary link's phone-width border is mixed from the ink colour at 50%, which clears 3:1 on white; re-check it on a tinted page.

## License

- Declared source: MIT
- Default license approval is pending. See https://pagesugar.com/docs/license.

## Source

- Palette: Neutral (`neutral`)
- Entry: `ProductScreenshotHero.svelte`
- Suggested directory: `src/lib/components/hero-product-screenshot-01`
- Files: 1
- Artifact digest: `sha256-c2b891757aeef0041ef8cfc6fb4fe47aaf3d49e6026488f96097ff53db894a2c`

Paths below are relative to the suggested directory. Copy the files as they are;
they import nothing from this site.

#### `ProductScreenshotHero.svelte`

Role: entry · 11833 bytes · SHA-256 `364f9a850d9707435a903efaff50fb526fe893e83cea23728e1ab591bdd92cda`

```svelte
<script lang="ts" module>
	export interface ProductScreenshotHeroAction {
		/** Link text. Say where it goes: "Start free", not "Click here". */
		label: string;
		href: string;
	}

	/**
	 * Width and height are the file's intrinsic size. They reserve the frame's shape before the
	 * file loads, so nothing below the hero moves. Alt describes what the interface shows.
	 */
	export interface ProductScreenshotHeroScreenshot {
		src: string;
		alt: string;
		width: number;
		height: number;
		/**
		 * An optional crop for screens under 640 px, such as one board column instead of the whole
		 * board, so the interface stays legible on a phone instead of shrinking to noise.
		 */
		narrow?: { src: string; width: number; height: number };
		/**
		 * An optional view for 640 to 1023 px, such as two board columns, so a tablet shows the
		 * interface near its real size rather than the whole desktop screen at half scale.
		 */
		medium?: { src: string; width: number; height: number };
	}

	export type ProductScreenshotHeroFrame = 'browser' | 'device' | 'none';
</script>

<script lang="ts">
	interface Props {
		/** Headline text, set at display size. */
		title: string;
		/** Short label above the headline. */
		eyebrow?: string;
		/** Supporting paragraph under the headline. */
		description?: string;
		/** The one filled action. */
		primaryAction: ProductScreenshotHeroAction;
		/** Optional quieter link beside the primary action. */
		secondaryAction?: ProductScreenshotHeroAction;
		/** The product image. Omitted, the frame holds an empty placeholder of the same shape. */
		screenshot?: ProductScreenshotHeroScreenshot;
		/** Decorative frame around the screenshot. */
		frame?: ProductScreenshotHeroFrame;
		/** Text in the browser frame's title bar, such as the page name. Nothing renders without it. */
		frameTitle?: string;
		/** Short visible caption above the screenshot, such as "Sample workspace". */
		caption?: string;
		/** From the lg breakpoint, run the browser or bare frame into the section's bottom edge. */
		bleed?: boolean;
		/** 1 when the hero opens the page, 2 when the page already has an h1. */
		headingLevel?: 1 | 2;
	}

	let {
		title,
		eyebrow,
		description,
		primaryAction,
		secondaryAction,
		screenshot,
		frame = 'browser',
		frameTitle,
		caption,
		bleed = true,
		headingLevel = 1
	}: Props = $props();

	const uid = $props.id();

	const device = $derived(frame === 'device');
	/* The phone sits beside the copy, so it never meets the bottom edge. */
	const bleeds = $derived(bleed && !device);
	/* A missing screenshot keeps the frame's shape: a wide window, or a phone screen. */
	const placeholderRatio = $derived(device ? '9 / 19.5' : '16 / 10');
</script>

<section
	class={[
		'hero-product-screenshot overflow-hidden px-4 py-16 sm:px-6 sm:py-24 lg:px-8 lg:py-32',
		bleeds && 'lg:pb-0'
	]}
	aria-labelledby="{uid}-title"
>
	<div
		class={[
			'mx-auto max-w-6xl',
			device
				? 'grid gap-12 md:grid-cols-[minmax(0,1fr)_auto] md:items-center lg:gap-16'
				: 'flex flex-col gap-12 lg:gap-16'
		]}
	>
		<div class="min-w-0 text-start">
			{#if eyebrow}
				<p
					class="hero-product-screenshot__eyebrow hero-product-screenshot__tracked mb-2 text-xs/none font-medium tracking-[0.06em] text-balance break-words text-[var(--_muted)] uppercase"
				>
					{eyebrow}
				</p>
			{/if}
			<svelte:element
				this={headingLevel === 2 ? 'h2' : 'h1'}
				id="{uid}-title"
				class="hero-product-screenshot__title hero-product-screenshot__tracked max-w-[15em] text-4xl/[1.1] font-semibold tracking-[-0.022em] text-balance break-words text-[var(--_ink)] sm:text-5xl/[1.05] lg:text-6xl/[1.05]"
			>
				{title}
			</svelte:element>
			{#if description}
				<p
					class="hero-product-screenshot__prose mt-4 max-w-[34rem] text-base/6 text-pretty break-words text-[var(--_muted)] sm:mt-6 sm:text-lg/7"
				>
					{description}
				</p>
			{/if}
			<div class="mt-8 flex flex-col gap-4 sm:flex-row sm:flex-wrap">
				<a
					href={primaryAction.href}
					class="hero-product-screenshot__action hero-product-screenshot__primary inline-flex min-h-12 max-w-full items-center justify-center rounded-lg bg-[var(--_accent)] px-6 py-2 text-center text-base/6 font-medium text-[var(--_on-accent)] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[var(--_accent)]"
				>
					<span class="min-w-0 [overflow-wrap:anywhere]">{primaryAction.label}</span>
				</a>
				{#if secondaryAction}
					<a
						href={secondaryAction.href}
						class="hero-product-screenshot__action hero-product-screenshot__secondary inline-flex min-h-12 max-w-full items-center justify-center rounded-lg px-6 py-2 text-center text-base/6 font-medium text-[var(--_ink)] ring-1 ring-[var(--_control-border)] ring-inset focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[var(--_accent)] sm:ring-0"
					>
						<!-- The arrow is joined to the last word (U+2060) so it follows a wrapped label. -->
						<span class="min-w-0 [overflow-wrap:anywhere]"
							>{secondaryAction.label}&#8288;<svg
								class="hero-product-screenshot__arrow ms-2 inline-block size-4 align-[-0.1875em]"
								viewBox="0 0 16 16"
								fill="none"
								stroke="currentColor"
								stroke-width="1.75"
								stroke-linecap="round"
								stroke-linejoin="round"
								aria-hidden="true"
							>
								<path d="M3 8h10M9 4l4 4-4 4" />
							</svg></span
						>
					</a>
				{/if}
			</div>
		</div>

		<figure class={['m-0 min-w-0', device && 'w-72 max-w-full sm:w-80']}>
			{#if caption}
				<figcaption
					class="hero-product-screenshot__prose mb-2 text-[13px]/5 text-balance break-words text-[var(--_muted)]"
				>
					{caption}
				</figcaption>
			{/if}
			<div
				class={[
					'hero-product-screenshot__frame relative overflow-hidden',
					frame === 'browser' && 'rounded-xl bg-[var(--_frame)]',
					frame === 'device' && 'rounded-[44px] bg-[var(--_frame)] p-2',
					frame === 'none' && 'rounded-xl',
					bleeds && 'lg:rounded-b-none'
				]}
			>
				{#if frame === 'browser'}
					<!-- Window chrome: three quiet dots and an optional title, no invented address. -->
					<div
						class="grid h-10 grid-cols-[3rem_minmax(0,1fr)_3rem] items-center gap-4 px-4 shadow-[inset_0_-1px_0_var(--_hairline)]"
						aria-hidden="true"
					>
						<span class="flex gap-2">
							<span class="size-2.5 rounded-full bg-[var(--_dot)]"></span>
							<span class="size-2.5 rounded-full bg-[var(--_dot)]"></span>
							<span class="size-2.5 rounded-full bg-[var(--_dot)]"></span>
						</span>
						{#if frameTitle}
							<span
								class="hero-product-screenshot__frame-title truncate text-center text-xs/4 font-medium text-[var(--_muted)]"
								>{frameTitle}</span
							>
						{/if}
					</div>
				{/if}
				<div
					class={[
						'hero-product-screenshot__screen relative overflow-hidden bg-[var(--_placeholder)]',
						device && 'rounded-[36px]'
					]}
					style:aspect-ratio={screenshot ? undefined : placeholderRatio}
				>
					{#if screenshot}
						<picture>
							{#if screenshot.narrow}
								<source
									media="(max-width: 639.98px)"
									srcset={screenshot.narrow.src}
									width={screenshot.narrow.width}
									height={screenshot.narrow.height}
								/>
							{/if}
							{#if screenshot.medium}
								<source
									media="(max-width: 1023.98px)"
									srcset={screenshot.medium.src}
									width={screenshot.medium.width}
									height={screenshot.medium.height}
								/>
							{/if}
							<img
								class="block h-auto w-full"
								src={screenshot.src}
								alt={screenshot.alt}
								width={screenshot.width}
								height={screenshot.height}
								loading="eager"
								fetchpriority="high"
								decoding="async"
							/>
						</picture>
					{/if}
					{#if device}
						<!-- The screen's own edge inside the bezel, so a white screenshot still reads as a screen. -->
						<span
							class="pointer-events-none absolute inset-0 rounded-[inherit] ring-1 ring-[var(--_hairline)] ring-inset"
							aria-hidden="true"
						></span>
					{/if}
				</div>
				<!-- The frame's edge, over the image so a white screenshot still has one. Under bleed it runs
				     a pixel past the clip, so no rule is drawn along the section's bottom edge. -->
				<span
					class={[
						'pointer-events-none absolute inset-0 rounded-[inherit] ring-1 ring-[var(--_hairline)] ring-inset',
						bleeds && 'lg:-bottom-px'
					]}
					aria-hidden="true"
				></span>
			</div>
		</figure>
	</div>
</section>

<style>
	/* Public tokens: set --hero-product-screenshot-* on this section or any ancestor to retone it. */
	.hero-product-screenshot {
		--_accent: var(--hero-product-screenshot-accent, #18181b);
		--_on-accent: var(--hero-product-screenshot-on-accent, #ffffff);
		--_ink: var(--hero-product-screenshot-ink, #18181b);
		--_muted: var(--hero-product-screenshot-muted, #52525b);
		--_hairline: var(--hero-product-screenshot-hairline, rgb(0 0 0 / 0.08));
		/* The browser chrome and the phone bezel. */
		--_frame: var(--hero-product-screenshot-frame, #f4f4f5);
		/*
		 * The primary's hover mixes 12% of this colour into the accent. White lightens a near-black
		 * accent; a mid-tone accent needs black, since lightening it drops white text's contrast.
		 */
		--_accent-hover-mix: var(--hero-product-screenshot-accent-hover-mix, #ffffff);
		/* Derived from ink so a retone carries them: window dots, the empty screen, the phone outline. */
		--_dot: color-mix(in oklab, var(--_ink) 16%, transparent);
		--_placeholder: color-mix(in oklab, var(--_ink) 4%, transparent);
		--_control-border: color-mix(in oklab, var(--_ink) 50%, transparent);
	}

	/* The one elevation: the frame, lit from above. */
	.hero-product-screenshot__frame {
		box-shadow:
			0 1px 2px rgb(0 0 0 / 0.05),
			0 12px 40px rgb(0 0 0 / 0.08);
	}

	.hero-product-screenshot__action {
		transition-property: color, background-color, box-shadow, transform;
		transition-duration: 150ms;
		transition-timing-function: cubic-bezier(0.2, 0, 0, 1);
	}
	.hero-product-screenshot__action:active {
		transform: scale(0.98);
		transition-duration: 80ms;
	}
	/* Hover shifts the tone one step instead of fading the button. */
	.hero-product-screenshot__primary:hover {
		background-color: color-mix(in oklab, var(--_accent) 88%, var(--_accent-hover-mix));
	}
	.hero-product-screenshot__secondary:hover {
		background-color: color-mix(in oklab, var(--_ink) 5%, transparent);
	}
	/* The arrow points along the reading direction. */
	.hero-product-screenshot__arrow:dir(rtl) {
		scale: -1 1;
	}

	/* Arabic and Hebrew are never letter-spaced, and CJK is not tightened. */
	.hero-product-screenshot__tracked:dir(rtl),
	.hero-product-screenshot__tracked:is(:lang(ja), :lang(zh), :lang(ko)) {
		letter-spacing: 0;
	}
	/* Uppercase does nothing for Hebrew or Arabic, so the eyebrow takes a step up in size instead. */
	.hero-product-screenshot__eyebrow:dir(rtl) {
		text-transform: none;
		font-size: 0.8125rem;
	}
	.hero-product-screenshot__title:dir(rtl) {
		line-height: 1.25;
	}
	/*
	 * Japanese and Chinese have no spaces: break between phrases where the browser can
	 * (auto-phrase), keep closing punctuation on its line (strict), and never mid-word.
	 * Korean spaces its words, so it keeps them whole.
	 */
	:is(.hero-product-screenshot__title, .hero-product-screenshot__prose):is(:lang(ja), :lang(zh)) {
		word-break: normal;
		word-break: auto-phrase;
		line-break: strict;
	}
	.hero-product-screenshot__title:is(:lang(ja), :lang(zh), :lang(ko)) {
		line-height: 1.2;
	}
	:is(.hero-product-screenshot__title, .hero-product-screenshot__prose):lang(ko) {
		word-break: keep-all;
	}

	@media (prefers-reduced-motion: reduce) {
		.hero-product-screenshot__action:active {
			transform: none;
		}
	}
</style>
```

## Artifacts

### Neutral (`neutral`) (default)

- Artifact digest: `sha256-c2b891757aeef0041ef8cfc6fb4fe47aaf3d49e6026488f96097ff53db894a2c`
- Entry: `ProductScreenshotHero.svelte`
- Receipt: https://pagesugar.com/artifacts/cmp_hero_product_screenshot_01/1.0.0/neutral/sha256-c2b891757aeef0041ef8cfc6fb4fe47aaf3d49e6026488f96097ff53db894a2c/manifest.json
- Bundle: https://pagesugar.com/artifacts/cmp_hero_product_screenshot_01/1.0.0/neutral/sha256-c2b891757aeef0041ef8cfc6fb4fe47aaf3d49e6026488f96097ff53db894a2c/bundle.zip (7510 bytes, sha256 `74e7083c019944ab8bf7f75fe08f12d05cd8d1a19432386e2f9633b9a1c9497b`)

Files:

- `ProductScreenshotHero.svelte` (entry, 11833 bytes): https://pagesugar.com/artifacts/cmp_hero_product_screenshot_01/1.0.0/neutral/sha256-c2b891757aeef0041ef8cfc6fb4fe47aaf3d49e6026488f96097ff53db894a2c/source/ProductScreenshotHero.svelte
