77 lines
2.7 KiB
Plaintext
77 lines
2.7 KiB
Plaintext
---
|
|
import { getImage } from 'astro:assets';
|
|
import type { ImageMetadata } from 'astro';
|
|
import { screens, type ScreenName } from '../lib/screens';
|
|
|
|
interface Props {
|
|
name: ScreenName;
|
|
/** Above the fold: load immediately with high priority. */
|
|
priority?: boolean;
|
|
sizes?: string;
|
|
/** Override the default alt text. */
|
|
alt?: string;
|
|
class?: string;
|
|
}
|
|
const { name, priority = false, sizes = '(min-width: 1160px) 1120px, 100vw', alt, class: className } = Astro.props;
|
|
const spec = screens[name];
|
|
|
|
const files = import.meta.glob<{ default: ImageMetadata }>('/src/assets/screens/*.{png,jpg,jpeg,webp,svg}', { eager: true });
|
|
const raster = ['png', 'webp', 'jpg', 'jpeg'];
|
|
|
|
/** Real captures (png/webp/jpg) beat placeholder SVGs; `<name>-<theme>` beats a bare `<name>`. */
|
|
function pick(theme: 'dark' | 'light'): ImageMetadata {
|
|
const tries = [
|
|
...raster.map((e) => `${name}-${theme}.${e}`),
|
|
...raster.map((e) => `${name}.${e}`),
|
|
`${name}-${theme}.svg`,
|
|
`${name}.svg`,
|
|
];
|
|
for (const t of tries) {
|
|
const hit = files[`/src/assets/screens/${t}`];
|
|
if (hit) return hit.default;
|
|
}
|
|
throw new Error(`No screenshot found for "${name}" (${theme}) in src/assets/screens/`);
|
|
}
|
|
/** Optimised webp srcset for real captures; SVG placeholders pass through untouched. */
|
|
async function sources(img: ImageMetadata) {
|
|
if (img.format === 'svg') return { src: img.src, srcset: img.src };
|
|
const widths = [720, 1200, 1600, 2400].filter((w) => w < img.width).concat(img.width);
|
|
const out = await getImage({ src: img, widths, format: 'webp' });
|
|
return { src: out.src, srcset: out.srcSet.attribute };
|
|
}
|
|
const darkImg = pick('dark');
|
|
const dark = await sources(darkImg);
|
|
const light = await sources(pick('light'));
|
|
---
|
|
<figure class:list={['shot', className]}>
|
|
<div class="view">
|
|
<!-- The theme follows the system, so the browser fetches only the matching variant. -->
|
|
<picture>
|
|
<source media="(prefers-color-scheme: light)" srcset={light.srcset} sizes={sizes} />
|
|
<img
|
|
src={dark.src}
|
|
srcset={dark.srcset}
|
|
sizes={sizes}
|
|
width={darkImg.width}
|
|
height={darkImg.height}
|
|
alt={alt ?? spec.alt}
|
|
loading={priority ? 'eager' : 'lazy'}
|
|
fetchpriority={priority ? 'high' : undefined}
|
|
decoding={priority ? 'sync' : 'async'}
|
|
/>
|
|
</picture>
|
|
</div>
|
|
</figure>
|
|
<style>
|
|
.shot {
|
|
margin: 0; border-radius: 14px; overflow: hidden; background: var(--surface);
|
|
border: 1px solid var(--line-2); box-shadow: var(--shadow);
|
|
}
|
|
.view { aspect-ratio: 16 / 10; background: var(--bg); }
|
|
.view picture { display: block; width: 100%; height: 100%; }
|
|
.view img { width: 100%; height: 100%; object-fit: cover; }
|
|
@media (max-width: 520px) {
|
|
.shot { border-radius: 10px; }
|
|
}
|
|
</style>
|