# Feature spotlight

> One capability given a full section: eyebrow, heading, detailed copy, check-marked points and up to two actions beside a large screenshot or video set in a quiet frame. Media swaps sides at lg; copy stays first.

- ID: `cmp_feature_spotlight_01`
- Slug: `feature-spotlight-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: `features`
- Detail page: https://pagesugar.com/components/feature-spotlight-01
- Preview: https://pagesugar.com/preview/feature-spotlight-01

## Variants

| Variant | Label | Default | Artifact digest |
| --- | --- | --- | --- |
| `neutral` | Neutral | yes | `sha256-ed128dcfac897189ea5c780f3c8c646f10b50650f2493740ae9dd960434e2ab8` |
| `blue` | Blue accent | no | `sha256-b3eeb6a032a8aca34be28a514ac1b44866b0c382f9fa192d92389a0478d51c23` |

## 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/feature-spotlight-01`

## Dependencies

No third-party runtime packages.

## Services

No external services required.

## Usage

Presentational: renders your heading, copy, points, links and media exactly as supplied. It ships no images and no video player; pass a video element through the media snippet with its own controls and captions. The image loads lazily, since a spotlight usually sits below the fold. Actions are ordinary links, and only the first two render.

Required props: `title`, `body`, `media`

```svelte
<script lang="ts">
	import FeatureSpotlight from '$lib/components/feature-spotlight-01/FeatureSpotlight.svelte';
</script>

<FeatureSpotlight
	eyebrow="Emergency appointments"
	title="Toothache at 7 am? We keep four slots free every weekday."
	body="Ring before 10 am and we will hold the next open slot while you travel in."
	points={['Pain relief and a diagnosis at the first visit', 'X-rays on site, with no referral needed']}
	actions={[
		{ label: 'Call 020 7946 0321', href: 'tel:+442079460321' },
		{ label: 'What an emergency visit costs', href: '/fees#emergency' }
	]}
	media={{
		src: '/images/emergency-slots.png',
		alt: 'Today\'s emergency slots: 8:30, 12:30, 13:30 and 17:15 are open.',
		width: 1200,
		height: 900
	}}
	mediaPosition="start"
/>
```

Limitations:

- Ships no media. The preview's screenshots, illustration and clip are preview-only and are not part of the export.
- No video player. A video goes through the media snippet with native controls; it must not autoplay with sound, and a clip with speech needs a captions track (see usage.md).
- A media snippet sets its own size and aspect ratio; only the built-in image reserves its shape from width and height.
- Points are plain strings. For a lead-in term or links inside a point, edit the list markup in the source.
- At most two actions: the first is filled, the second is a text link. Further entries are ignored.
- Light appearance by default. The tokens retone it for a dark or tinted page, but no dark mode is declared or selected automatically.

## Usage guide

### Feature spotlight

One capability, given a whole section. The copy sits in the narrower column: an eyebrow, the
heading, a paragraph of detail, a short list of check-marked points and up to two actions. The
media takes the wider column inside a quiet frame, an 8 px bezel of translucent fill with a
hairline edge and the section's one shadow. Below the lg breakpoint everything stacks, copy
first.

#### Media

Pass an image as `{ src, alt, width, height }`, using the file's own pixel size. The browser
reserves the frame's shape from those numbers before the file arrives, so nothing below the
section moves as it loads. The image is lazy-loaded; if the spotlight sits at the top of a page,
edit `loading="lazy"` to `"eager"` in the source.

A portrait image (taller than it is wide) would otherwise fill the column and run several
screens tall. The frame narrows instead, so the image stays near 36rem tall and sits centred in
its column. Change `--_media-max-block` in the source to allow more.

Anything else goes through the `media` snippet, rendered inside the same frame. The snippet sets
its own size.

##### A different crop on phones

A screenshot of a whole screen shrinks to a third of its size on a phone, and its labels stop
being readable. Pass a picture element through the `media` snippet with a close-up for small
screens and the full image from the lg breakpoint:

```svelte
{#snippet slots()}
	<picture>
		<source media="(min-width: 1024px)" srcset="/images/slots.png" width="1200" height="900" />
		<img
			class="block h-auto w-full"
			src="/images/slots-phone.png"
			alt="Today's emergency slots: 8:30, 12:30, 13:30 and 17:15 are open."
			width="640"
			height="740"
			loading="lazy"
		/>
	</picture>
{/snippet}
```

Give both sources their own width and height so the browser reserves the right shape at each
size.

##### Video

The component has no video player and never plays anything itself. Pass a video element with
native controls:

```svelte
{#snippet clip()}
	<video
		class="block h-auto w-full"
		src="/video/timeline.mp4"
		poster="/video/timeline-poster.webp"
		width="1440"
		height="1080"
		controls
		playsinline
		preload="metadata"
		aria-label="The Guest seats bar slides two weeks later, then back."
	>
		<track kind="captions" src="/video/timeline.en.vtt" srclang="en" label="English" default />
	</video>
{/snippet}

<FeatureSpotlight title="Drag a date. Everything linked to it follows." body="…" media={clip} />
```

- Never autoplay a clip with sound. A silent ambient loop may autoplay only when
  `prefers-reduced-motion` is not set, and it still needs a way to pause (WCAG 2.2.2).
- A clip with speech needs a captions track. A silent clip needs none, but say so in its name.
- Give the poster the same pixel size as the video so the frame does not jump when it loads.

#### Body

A string renders as one paragraph. For two paragraphs, a link or emphasis, pass a snippet; its
paragraphs are spaced 16 px apart and its links are underlined in the ink colour.

```svelte
{#snippet details()}
	<p>Move a bar on the timeline and the cards behind it update.</p>
	<p>
		Dependencies come with the Team plan. <a href="/guides/timelines">Read how linking works</a>.
	</p>
{/snippet}
```

#### Actions

The first action is the filled button and the second a text link with an arrow. Anything after
the second is ignored: a spotlight asks for one decision. On phones the button spans the column
and the link sits under it.

#### Alternating sections

Several spotlights stacked with `mediaPosition` alternating make a product tour. Put them under
one section heading and set `headingLevel={3}` on each. Each spotlight keeps its own section
padding; remove the top padding from all but the first if the stack feels loose.

## Props

| Name | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `title` | `string` | yes |  | Section heading, set at section-title size. |
| `body` | string \| Snippet | yes |  | The detailed copy. A string renders as one paragraph held to a readable measure; a snippet renders as written, with its paragraphs spaced and its links underlined. |
| `media` | FeatureSpotlightImage \| Snippet | yes |  | { src, alt, width, height } for an image, or a snippet (a video with controls, a picture element) set inside the same frame. Width and height are the file's intrinsic size and reserve its shape; alt may be empty only when the image is decorative. |
| `points` | `string[]` | no | `[]` | Short supporting details, each beside a check mark in the accent colour. Empty or omitted, no list renders. |
| `mediaPosition` | 'start' \| 'end' | no | `'end'` | Which side the media takes from the lg breakpoint. Start and end follow the text direction, and the copy always comes first in the DOM. |
| `actions` | `FeatureSpotlightAction[]` | no | `[]` | Up to two links, { label, href }: the first is the filled action, the second a text link with an arrow. Further entries are ignored. |
| `eyebrow` | `string` | no |  | Short uppercase label above the heading. Omitted, no eyebrow renders. |
| `headingLevel` | 2 \| 3 | no | `2` | 2 for a section on the page; 3 when the spotlight sits inside a larger section with its own h2. |

## Customization

Change content through props, retone the section through seven --feature-spotlight-\* CSS variables, and edit Tailwind classes in the source for the column split, spacing or type scale.

- Accent: set --feature-spotlight-accent and --feature-spotlight-on-accent together, keeping on-accent at 4.5:1 or better. The accent fills the primary action, colours the check marks and draws focus rings.
- Hover: the primary's hover fill mixes 12% of --feature-spotlight-accent-hover-mix into the accent. White (the default) lightens a near-black accent; set #000000 for a mid-tone accent such as the blue palette, because lightening it lowers contrast with white text.
- Text: --feature-spotlight-ink sets the heading, points and the text link; --feature-spotlight-muted sets the eyebrow and body. Keep muted at 4.5:1 against your page.
- Media frame: --feature-spotlight-surface fills the 8 px bezel around the media and --feature-spotlight-hairline draws its edge and the edge over the media. Both are translucent by default, so they follow a tinted page.
- Dark page retone: ink #fafafa, muted #a1a1aa, hairline rgb(255 255 255 / 0.1), surface rgb(255 255 255 / 0.04), accent #fafafa, on-accent #18181b (all --feature-spotlight-\*). The black shadow under the frame disappears on dark; the hairline carries the edge.
- Column split: the grid's lg:grid-cols-\[minmax(0,5fr)\_minmax(0,7fr)\] gives the media the larger share. Change both fractions (and the mirrored pair for mediaPosition start) to 1fr for an even split.
- Portrait media: --\_media-max-block (36rem) is the height a portrait image may reach before its frame narrows. Raise it for tall phone screenshots.
- Phone crop: a full-screen screenshot shrinks to a third of its size on phones. Pass a picture element through the media snippet with a close-up for small screens and the full image from 1024px; give each source its own width and height.
- Video: pass a video element through the media snippet with class="block h-auto w-full", width, height, a poster, controls and no autoplay. Add \<track kind="captions"\> when the clip has speech.
- Alternating sections: render several spotlights in a stack and alternate mediaPosition; set headingLevel to 3 under a section heading.

| Token | Public CSS variable |
| --- | --- |
| `accent` | `--feature-spotlight-accent` |
| `onAccent` | `--feature-spotlight-on-accent` |
| `accentHoverMix` | `--feature-spotlight-accent-hover-mix` |
| `ink` | `--feature-spotlight-ink` |
| `muted` | `--feature-spotlight-muted` |
| `hairline` | `--feature-spotlight-hairline` |
| `surface` | `--feature-spotlight-surface` |

## Accessibility

- The section is labelled by its heading, an h2 by default; set headingLevel to 3 when the spotlight sits under another section's h2.
- The copy comes before the media in the DOM at every width, so reading and tab order put the heading and actions first. mediaPosition moves the media with CSS only, from the lg breakpoint.
- Image alt text comes from you. Say what the screenshot shows that the copy does not; pass alt: '' only when the image is decoration.
- A video passed through the media snippet is yours to make accessible: native controls, no autoplay with sound, a captions track when it has speech, and an accessible name.
- Points are a list (role="list" keeps list semantics where list-style is removed); the check marks are decorative and hidden from assistive technology.
- Actions are links with a two-pixel accent focus ring on :focus-visible, at least 44 px tall. The text link's arrow is decorative and flips under right-to-left text.
- White on-accent text measures 17.7:1 on the neutral accent (#18181b) and 6.7:1 on blue (#1d4ed8); the hover fills keep it above 4.5:1. Muted text (#52525b) measures 7.7:1 on white. Ratios use the WCAG relative-luminance formula.
- The layout uses logical properties and text-start, so it mirrors under dir="rtl"; tracking resets to 0 for right-to-left and CJK text, and Japanese and Chinese break between phrases.
- Element IDs come from $props.id(), so two spotlights 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 and check marks).
- The component cannot check that alt text is accurate or that a video has captions; both are the consumer's responsibility.

## License

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

## Source

- Palette: Neutral (`neutral`)
- Entry: `FeatureSpotlight.svelte`
- Suggested directory: `src/lib/components/feature-spotlight-01`
- Files: 1
- Artifact digest: `sha256-ed128dcfac897189ea5c780f3c8c646f10b50650f2493740ae9dd960434e2ab8`

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

#### `FeatureSpotlight.svelte`

Role: entry · 10691 bytes · SHA-256 `fa194667e529aadf550c36a9256c77020105fca106c501d04b261a6ae5419bda`

```svelte
<script lang="ts" module>
	export interface FeatureSpotlightAction {
		label: string;
		href: string;
	}

	/**
	 * Width and height are the file's intrinsic size. They reserve the frame's shape before the
	 * image loads, and a portrait image is held to a readable height instead of filling the column.
	 */
	export interface FeatureSpotlightImage {
		src: string;
		alt: string;
		width: number;
		height: number;
	}
</script>

<script lang="ts">
	import type { Snippet } from 'svelte';

	interface Props {
		/** Section heading. */
		title: string;
		/** The detailed copy: a string renders as one paragraph, a snippet renders as written. */
		body: string | Snippet;
		/** A screenshot or image, or a snippet (a video, a picture element) set inside the same frame. */
		media: FeatureSpotlightImage | Snippet;
		/** Short supporting details, each set beside a check mark. */
		points?: string[];
		/** Which side the media takes from the lg breakpoint. The copy always comes first in the DOM. */
		mediaPosition?: 'start' | 'end';
		/** Up to two links: the first is the filled action, the second a quieter text link. */
		actions?: FeatureSpotlightAction[];
		/** Short label above the heading. */
		eyebrow?: string;
		/** 2 for a section on the page, 3 when the spotlight sits inside a larger section. */
		headingLevel?: 2 | 3;
	}

	let {
		title,
		body,
		media,
		points = [],
		mediaPosition = 'end',
		actions = [],
		eyebrow,
		headingLevel = 2
	}: Props = $props();

	const uid = $props.id();

	const start = $derived(mediaPosition === 'start');
	const image = $derived(typeof media === 'function' ? undefined : media);
	const mediaSnippet = $derived(typeof media === 'function' ? media : undefined);
	const bodySnippet = $derived(typeof body === 'function' ? body : undefined);
	const [primary, secondary] = $derived(actions.slice(0, 2));
	/*
	 * A portrait image would otherwise take the full column width and run several screens tall.
	 * The frame's width is capped so its height stays near --_media-max-block; landscape images
	 * never reach the cap and fill the column.
	 */
	const frameWidth = $derived(
		image && image.width > 0 && image.height > 0
			? `min(100%, calc(var(--_media-max-block) * ${image.width} / ${image.height} + 1rem))`
			: undefined
	);
</script>

<section
	class="feature-spotlight px-4 py-16 sm:px-6 sm:py-24 lg:px-8 lg:py-32"
	aria-labelledby="{uid}-title"
>
	<div
		class={[
			'mx-auto grid max-w-7xl items-center gap-12 lg:gap-16',
			start
				? 'lg:grid-cols-[minmax(0,7fr)_minmax(0,5fr)]'
				: 'lg:grid-cols-[minmax(0,5fr)_minmax(0,7fr)]'
		]}
	>
		<div class="max-w-2xl min-w-0 text-start">
			{#if eyebrow}
				<p
					class="feature-spotlight__eyebrow feature-spotlight__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 === 3 ? 'h3' : 'h2'}
				id="{uid}-title"
				class="feature-spotlight__title feature-spotlight__tracked text-3xl/[1.2] font-semibold tracking-[-0.02em] text-balance break-words text-[var(--_ink)] sm:text-4xl/[1.15]"
			>
				{title}
			</svelte:element>
			{#if bodySnippet}
				<div
					class="feature-spotlight__prose feature-spotlight__body mt-4 max-w-xl text-base/6 text-pretty break-words text-[var(--_muted)] sm:text-lg/7"
				>
					{@render bodySnippet()}
				</div>
			{:else if body}
				<p
					class="feature-spotlight__prose mt-4 max-w-xl text-base/6 text-pretty break-words text-[var(--_muted)] sm:text-lg/7"
				>
					{body}
				</p>
			{/if}
			{#if points.length > 0}
				<ul class="mt-8 grid max-w-xl gap-4" role="list">
					{#each points as point, i (i)}
						<li class="flex gap-4 text-base/6 text-[var(--_ink)]">
							<!-- One line tall, so the mark stays level with the first line of a wrapped point. -->
							<span class="flex h-lh shrink-0 items-center" aria-hidden="true">
								<svg
									class="size-5 text-[var(--_accent)]"
									viewBox="0 0 20 20"
									fill="none"
									stroke="currentColor"
									stroke-width="1.5"
									stroke-linecap="round"
									stroke-linejoin="round"
								>
									<path d="M4.5 10.5 8 14l7.5-8" />
								</svg>
							</span>
							<span class="feature-spotlight__prose min-w-0 text-pretty break-words">{point}</span>
						</li>
					{/each}
				</ul>
			{/if}
			{#if primary}
				<div class="mt-8 flex flex-col gap-4 sm:flex-row sm:flex-wrap sm:items-center sm:gap-x-8">
					<a
						href={primary.href}
						class="feature-spotlight__action feature-spotlight__primary inline-flex min-h-11 max-w-full items-center justify-center rounded-lg bg-[var(--_accent)] px-4 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)] sm:px-6"
					>
						<span class="min-w-0 text-balance [overflow-wrap:anywhere]">{primary.label}</span>
					</a>
					{#if secondary}
						<a
							href={secondary.href}
							class="feature-spotlight__action feature-spotlight__secondary inline-flex min-h-11 max-w-full items-center self-start rounded-md text-base/6 font-medium text-[var(--_ink)] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[var(--_accent)] sm:self-auto"
						>
							<!-- The arrow is joined to the last word (U+2060) so it follows a wrapped label. -->
							<span class="min-w-0 text-balance [overflow-wrap:anywhere]"
								>{secondary.label}&#8288;<svg
									class="feature-spotlight__arrow ms-2 inline-block size-4 align-[-0.1875em] rtl:-scale-x-100"
									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>
			{/if}
		</div>

		<figure class={['m-0 min-w-0', start && 'lg:order-first']}>
			<!-- The stage: an 8px bezel of quiet fill around the media, and the section's one elevation. -->
			<div
				class="feature-spotlight__stage mx-auto rounded-2xl bg-[var(--_surface)] p-2 ring-1 ring-[var(--_hairline)] ring-inset"
				style:max-inline-size={frameWidth}
			>
				<div class="relative overflow-hidden rounded-lg bg-[var(--_placeholder)]">
					{#if mediaSnippet}
						{@render mediaSnippet()}
					{:else if image}
						<img
							class="block h-auto w-full"
							src={image.src}
							alt={image.alt}
							width={image.width}
							height={image.height}
							loading="lazy"
							decoding="async"
						/>
					{/if}
					<!-- The hairline sits over the media so a pale screenshot still has an edge. -->
					<span
						class="pointer-events-none absolute inset-0 rounded-[inherit] ring-1 ring-[var(--_hairline)] ring-inset"
						aria-hidden="true"
					></span>
				</div>
			</div>
		</figure>
	</div>
</section>

<style>
	/* Public tokens: set --feature-spotlight-* on this section or any ancestor to retone it. */
	.feature-spotlight {
		--_accent: var(--feature-spotlight-accent, #18181b);
		--_on-accent: var(--feature-spotlight-on-accent, #ffffff);
		--_ink: var(--feature-spotlight-ink, #18181b);
		--_muted: var(--feature-spotlight-muted, #52525b);
		--_hairline: var(--feature-spotlight-hairline, rgb(0 0 0 / 0.08));
		/* The stage around the media: a translucent step off the page, so it follows a tinted page. */
		--_surface: var(--feature-spotlight-surface, rgb(24 24 27 / 0.03));
		/*
		 * 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(--feature-spotlight-accent-hover-mix, #ffffff);
		/* The tallest a portrait image is allowed to run before its frame narrows instead. */
		--_media-max-block: 36rem;
		/* The frame's fill before the media paints. */
		--_placeholder: color-mix(in oklab, var(--_ink) 4%, transparent);
	}

	/* The one elevation: the media stage, lit from above. */
	.feature-spotlight__stage {
		box-shadow:
			0 1px 2px rgb(0 0 0 / 0.05),
			0 12px 40px rgb(0 0 0 / 0.08);
	}

	/* A snippet's paragraphs and links follow the string body's rhythm. */
	.feature-spotlight__body :global(p + p) {
		margin-top: 1rem;
	}
	.feature-spotlight__body :global(a) {
		color: var(--_ink);
		text-decoration-line: underline;
		text-decoration-thickness: 1px;
		text-underline-offset: 0.2em;
	}

	.feature-spotlight__action,
	.feature-spotlight__arrow {
		transition-property: color, background-color, transform;
		transition-duration: 150ms;
		transition-timing-function: cubic-bezier(0.2, 0, 0, 1);
	}
	.feature-spotlight__action:active {
		transform: scale(0.98);
		transition-duration: 80ms;
	}
	/* Hover shifts the fill one step instead of fading the button. */
	.feature-spotlight__primary:hover {
		background-color: color-mix(in oklab, var(--_accent) 88%, var(--_accent-hover-mix));
	}
	/* The text link underlines and its arrow steps forward, in the reading direction. */
	.feature-spotlight__secondary:hover {
		text-decoration-line: underline;
		text-decoration-thickness: 1px;
		text-underline-offset: 0.25em;
	}
	/* The arrow's step is movement, so it only exists when motion is allowed. It uses transform,
	   which composes with the scale that mirrors the arrow in right-to-left text. */
	@media (prefers-reduced-motion: no-preference) {
		.feature-spotlight__secondary:hover .feature-spotlight__arrow {
			transform: translateX(2px);
		}
		.feature-spotlight__secondary:dir(rtl):hover .feature-spotlight__arrow {
			transform: translateX(-2px);
		}
	}

	/* Arabic and Hebrew are never letter-spaced, and CJK is not tightened. */
	.feature-spotlight__tracked:dir(rtl),
	.feature-spotlight__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. */
	.feature-spotlight__eyebrow:dir(rtl) {
		font-size: 0.8125rem;
	}
	/*
	 * 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(.feature-spotlight__title, .feature-spotlight__prose):is(:lang(ja), :lang(zh)) {
		word-break: normal;
		word-break: auto-phrase;
		line-break: strict;
	}
	:is(.feature-spotlight__title, .feature-spotlight__prose):lang(ko) {
		word-break: keep-all;
	}

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

## Artifacts

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

- Artifact digest: `sha256-ed128dcfac897189ea5c780f3c8c646f10b50650f2493740ae9dd960434e2ab8`
- Entry: `FeatureSpotlight.svelte`
- Receipt: https://pagesugar.com/artifacts/cmp_feature_spotlight_01/1.0.0/neutral/sha256-ed128dcfac897189ea5c780f3c8c646f10b50650f2493740ae9dd960434e2ab8/manifest.json
- Bundle: https://pagesugar.com/artifacts/cmp_feature_spotlight_01/1.0.0/neutral/sha256-ed128dcfac897189ea5c780f3c8c646f10b50650f2493740ae9dd960434e2ab8/bundle.zip (7024 bytes, sha256 `39a0ceded88d332aba65982761237bed2d53a80ba90e64de466e36d861e475ea`)

Files:

- `FeatureSpotlight.svelte` (entry, 10691 bytes): https://pagesugar.com/artifacts/cmp_feature_spotlight_01/1.0.0/neutral/sha256-ed128dcfac897189ea5c780f3c8c646f10b50650f2493740ae9dd960434e2ab8/source/FeatureSpotlight.svelte

### Blue accent (`blue`)

- Artifact digest: `sha256-b3eeb6a032a8aca34be28a514ac1b44866b0c382f9fa192d92389a0478d51c23`
- Entry: `FeatureSpotlight.svelte`
- Receipt: https://pagesugar.com/artifacts/cmp_feature_spotlight_01/1.0.0/blue/sha256-b3eeb6a032a8aca34be28a514ac1b44866b0c382f9fa192d92389a0478d51c23/manifest.json
- Bundle: https://pagesugar.com/artifacts/cmp_feature_spotlight_01/1.0.0/blue/sha256-b3eeb6a032a8aca34be28a514ac1b44866b0c382f9fa192d92389a0478d51c23/bundle.zip (7032 bytes, sha256 `8089ea4668557a6e7364815901f185838150acf2229456d368d971de72088965`)

Files:

- `FeatureSpotlight.svelte` (entry, 10691 bytes): https://pagesugar.com/artifacts/cmp_feature_spotlight_01/1.0.0/blue/sha256-b3eeb6a032a8aca34be28a514ac1b44866b0c382f9fa192d92389a0478d51c23/source/FeatureSpotlight.svelte
