Stop reading clutter

This commit is contained in:
Andras Schmelczer 2026-06-25 23:42:34 +02:00
parent 82c723c192
commit 1cdc202731
8 changed files with 11 additions and 67 deletions

View file

@ -1,13 +0,0 @@
WEBVTT
00:00.000 --> 00:04.000
No spoken dialogue. Game audio only.
00:04.000 --> 00:35.000
The Ad Astra handheld board runs the game on a small OLED display.
00:35.000 --> 01:05.000
The player controls the game through the IR input while the engine updates the display in real time.
01:05.000 --> 01:34.600
The clip continues showing gameplay on the custom ATtiny85-based board.

View file

@ -5,7 +5,6 @@ import ProjectLinks from './ProjectLinks.astro';
type Link = CollectionEntry<'work'>['data']['links'][number]; type Link = CollectionEntry<'work'>['data']['links'][number];
interface Props { interface Props {
role?: string;
projectPeriod?: string; projectPeriod?: string;
stack?: string[]; stack?: string[];
scale?: string; scale?: string;
@ -14,18 +13,9 @@ interface Props {
headingId: string; headingId: string;
} }
const { const { projectPeriod, stack = [], scale, outcome, links = [], headingId } = Astro.props;
role,
projectPeriod,
stack = [],
scale,
outcome,
links = [],
headingId,
} = Astro.props;
const rows: Array<[string, string]> = []; const rows: Array<[string, string]> = [];
if (role) rows.push(['Role', role]);
if (projectPeriod) rows.push(['Period', projectPeriod]); if (projectPeriod) rows.push(['Period', projectPeriod]);
if (stack.length > 0) rows.push(['Stack', stack.join(', ')]); if (stack.length > 0) rows.push(['Stack', stack.join(', ')]);
if (scale) rows.push(['Scale', scale]); if (scale) rows.push(['Scale', scale]);

View file

@ -18,7 +18,7 @@ const videoHeight = item.type === 'video' ? (item.poster?.height ?? 720) : undef
{ {
item.type === 'video' ? ( item.type === 'video' ? (
// Decorative videos stay inert and hidden from assistive tech. Meaningful // Decorative videos stay inert and hidden from assistive tech. Meaningful
// videos expose controls, captions, and an accessible name. // videos expose controls and an accessible name.
item.decorative ? ( item.decorative ? (
<video <video
muted muted
@ -44,15 +44,6 @@ const videoHeight = item.type === 'video' ? (item.poster?.height ?? 720) : undef
> >
{item.webm && <source src={item.webm} type="video/webm" />} {item.webm && <source src={item.webm} type="video/webm" />}
{item.mp4 && <source src={item.mp4} type="video/mp4" />} {item.mp4 && <source src={item.mp4} type="video/mp4" />}
{item.captions && (
<track
kind="captions"
src={item.captions}
srclang="en"
label={item.captionsLabel}
default
/>
)}
</video> </video>
) )
) : ( ) : (

View file

@ -111,17 +111,6 @@ const playScript = `
> >
{video.webm && <source src={video.webm} type="video/webm" />} {video.webm && <source src={video.webm} type="video/webm" />}
{video.mp4 && <source src={video.mp4} type="video/mp4" />} {video.mp4 && <source src={video.mp4} type="video/mp4" />}
{
video.captions && (
<track
kind="captions"
src={video.captions}
srclang="en"
label={video.captionsLabel}
default
/>
)
}
</video> </video>
<noscript> <noscript>

View file

@ -64,13 +64,12 @@ const thumbnailSchema = ({ image }: SchemaContext) =>
alt: z.string().min(1, 'Thumbnail alt text must not be empty.'), alt: z.string().min(1, 'Thumbnail alt text must not be empty.'),
}); });
// Partial thumbnail used by the project card to override just the image, just // Thumbnail override used by the project card to swap just the image when the
// the alt text, or both. Anything omitted falls back to the entry's top-level // card wants a different crop from the article. The alt text is never
// thumbnail, so a card that only rewords its alt doesn't repeat the `src`. // overridden, so it stays a single source of truth on the top-level thumbnail.
const thumbnailOverrideSchema = ({ image }: SchemaContext) => const thumbnailOverrideSchema = ({ image }: SchemaContext) =>
z.object({ z.object({
src: image().optional(), src: image(),
alt: z.string().min(1, 'Thumbnail alt text must not be empty.').optional(),
}); });
const mediaSchema = ({ image }: SchemaContext) => const mediaSchema = ({ image }: SchemaContext) =>
@ -90,8 +89,6 @@ const mediaSchema = ({ image }: SchemaContext) =>
poster: image().optional(), poster: image().optional(),
mp4: mediaUrl.optional(), mp4: mediaUrl.optional(),
webm: mediaUrl.optional(), webm: mediaUrl.optional(),
captions: mediaUrl.optional(),
captionsLabel: z.string().default('English captions'),
alt: z.string().optional(), alt: z.string().optional(),
decorative: z.boolean().optional(), decorative: z.boolean().optional(),
caption: z.string().optional(), caption: z.string().optional(),
@ -104,12 +101,6 @@ const mediaSchema = ({ image }: SchemaContext) =>
.refine((item) => item.decorative || (Boolean(item.alt) && Boolean(item.caption)), { .refine((item) => item.decorative || (Boolean(item.alt) && Boolean(item.caption)), {
message: 'Meaningful media needs both alt text and a caption.', message: 'Meaningful media needs both alt text and a caption.',
}) })
.refine(
(item) => item.type !== 'video' || item.decorative || Boolean(item.captions),
{
message: 'Meaningful video needs captions.',
}
)
.refine( .refine(
(item) => item.type !== 'video' || item.decorative || Boolean(item.transcript), (item) => item.type !== 'video' || item.decorative || Boolean(item.transcript),
{ {
@ -130,11 +121,9 @@ const articleFacet = ({ image }: SchemaContext) =>
iframeThumbnail: z.boolean().default(false), iframeThumbnail: z.boolean().default(false),
featuredOrder: z.number().optional(), featuredOrder: z.number().optional(),
tags: z.array(z.enum(TAGS)).default([]), tags: z.array(z.enum(TAGS)).default([]),
role: z.string().min(1).optional(),
stack: z.array(z.string()).optional(), stack: z.array(z.string()).optional(),
scale: z.string().min(1).optional(), scale: z.string().min(1).optional(),
outcome: z.string().min(1).optional(), outcome: z.string().min(1).optional(),
audience: z.enum(['general', 'technical', 'recruiter-relevant']).default('technical'),
media: z.array(mediaSchema({ image })).default([]), media: z.array(mediaSchema({ image })).default([]),
}); });
@ -145,7 +134,6 @@ const projectFacet = ({ image }: SchemaContext) =>
technologies: z.array(z.string()).optional(), technologies: z.array(z.string()).optional(),
// Card overrides. When omitted, the card uses the top-level value. // Card overrides. When omitted, the card uses the top-level value.
title: z.string().optional(), title: z.string().optional(),
description: z.string().max(160).optional(),
thumbnail: thumbnailOverrideSchema({ image }).optional(), thumbnail: thumbnailOverrideSchema({ image }).optional(),
}); });

View file

@ -137,7 +137,6 @@ const personJsonLd = buildPersonJsonLd();
<AtAGlance <AtAGlance
headingId={`at-a-glance-${post.id}`} headingId={`at-a-glance-${post.id}`}
role={post.data.article?.role}
projectPeriod={post.data.period} projectPeriod={post.data.period}
stack={post.data.article?.stack} stack={post.data.article?.stack}
scale={post.data.article?.scale} scale={post.data.article?.scale}

View file

@ -5,8 +5,8 @@ import type { ImageMetadata } from 'astro';
// Theme background colors, the single source of truth for the // Theme background colors, the single source of truth for the
// <meta name="theme-color"> tags and the FOUC-prevention scripts (injected in // <meta name="theme-color"> tags and the FOUC-prevention scripts (injected in
// Base.astro and Header.astro). Keep --color-bg in global.css in sync; CSS // Base.astro and Header.astro). Keep --color-bg in styles/tokens.css in sync;
// cannot import these values. // CSS cannot import these values.
export const THEME_BG = { light: '#fbfaf7', dark: '#201f1d' }; export const THEME_BG = { light: '#fbfaf7', dark: '#201f1d' };
export const site = { export const site = {
@ -139,10 +139,10 @@ export function projectCard(entry: CollectionEntry<'work'>): ProjectCard {
const project = data.project; const project = data.project;
return { return {
title: project?.title ?? data.title, title: project?.title ?? data.title,
description: project?.description ?? data.description, description: data.description,
thumbnail: { thumbnail: {
src: project?.thumbnail?.src ?? data.thumbnail.src, src: project?.thumbnail?.src ?? data.thumbnail.src,
alt: project?.thumbnail?.alt ?? data.thumbnail.alt, alt: data.thumbnail.alt,
}, },
technologies: project?.technologies ?? data.article?.stack ?? [], technologies: project?.technologies ?? data.article?.stack ?? [],
selected: project?.selected ?? false, selected: project?.selected ?? false,

View file

@ -15,7 +15,7 @@ const STARTING_POINTS = 5;
const posts = await getArticles(); const posts = await getArticles();
const startingPoints = posts const startingPoints = posts
.filter((post) => post.data.article?.audience === 'recruiter-relevant') .filter((post) => post.data.article?.featuredOrder !== undefined)
.sort( .sort(
(a, b) => (a, b) =>
(a.data.article?.featuredOrder ?? 99) - (b.data.article?.featuredOrder ?? 99) (a.data.article?.featuredOrder ?? 99) - (b.data.article?.featuredOrder ?? 99)