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];
interface Props {
role?: string;
projectPeriod?: string;
stack?: string[];
scale?: string;
@ -14,18 +13,9 @@ interface Props {
headingId: string;
}
const {
role,
projectPeriod,
stack = [],
scale,
outcome,
links = [],
headingId,
} = Astro.props;
const { projectPeriod, stack = [], scale, outcome, links = [], headingId } = Astro.props;
const rows: Array<[string, string]> = [];
if (role) rows.push(['Role', role]);
if (projectPeriod) rows.push(['Period', projectPeriod]);
if (stack.length > 0) rows.push(['Stack', stack.join(', ')]);
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' ? (
// 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 ? (
<video
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.mp4 && <source src={item.mp4} type="video/mp4" />}
{item.captions && (
<track
kind="captions"
src={item.captions}
srclang="en"
label={item.captionsLabel}
default
/>
)}
</video>
)
) : (

View file

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

View file

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

View file

@ -137,7 +137,6 @@ const personJsonLd = buildPersonJsonLd();
<AtAGlance
headingId={`at-a-glance-${post.id}`}
role={post.data.article?.role}
projectPeriod={post.data.period}
stack={post.data.article?.stack}
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
// <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
// cannot import these values.
// Base.astro and Header.astro). Keep --color-bg in styles/tokens.css in sync;
// CSS cannot import these values.
export const THEME_BG = { light: '#fbfaf7', dark: '#201f1d' };
export const site = {
@ -139,10 +139,10 @@ export function projectCard(entry: CollectionEntry<'work'>): ProjectCard {
const project = data.project;
return {
title: project?.title ?? data.title,
description: project?.description ?? data.description,
description: data.description,
thumbnail: {
src: project?.thumbnail?.src ?? data.thumbnail.src,
alt: project?.thumbnail?.alt ?? data.thumbnail.alt,
alt: data.thumbnail.alt,
},
technologies: project?.technologies ?? data.article?.stack ?? [],
selected: project?.selected ?? false,

View file

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