d8a79ccbe3
- Huemint Magazine tokens for light/dark themes with AA-safe accent variants - FOUC-free theme init + toggle (localStorage, follows OS until chosen) - Seo component (canonical, OG, Twitter, WebSite/Person JSON-LD) - Header with popover mobile menu, 4-column footer with social links - Container, GridLines, SectionHeading, Button, Badge, Card, ExternalLink - site.json / social.json validated with zod - dev-only /styleguide preview Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
68 lines
1.9 KiB
TypeScript
68 lines
1.9 KiB
TypeScript
/**
|
|
* Typed, validated access to src/data/*.json.
|
|
* A schema mismatch throws at import time, so `astro build` fails loudly.
|
|
*/
|
|
import { z } from 'astro/zod';
|
|
import siteJson from '../data/site.json';
|
|
import socialJson from '../data/social.json';
|
|
|
|
const link = z.object({ label: z.string().min(1), href: z.string().min(1) });
|
|
|
|
const siteSchema = z.object({
|
|
name: z.string(),
|
|
title: z.string(),
|
|
description: z.string().max(160),
|
|
url: z.url(),
|
|
lang: z.string(),
|
|
locale: z.string(),
|
|
titleSeparator: z.string(),
|
|
author: z.object({ name: z.string(), callsign: z.string(), locator: z.string() }),
|
|
postsPerPage: z.number().int().positive(),
|
|
nav: z.array(link).min(1),
|
|
footer: z.object({
|
|
about: z.string(),
|
|
quickLinksTitle: z.string(),
|
|
legalTitle: z.string(),
|
|
socialTitle: z.string(),
|
|
rss: link,
|
|
legal: z.array(link),
|
|
license: z.string(),
|
|
signoff: z.string(),
|
|
}),
|
|
ui: z.object({
|
|
skipToContent: z.string(),
|
|
mainNav: z.string(),
|
|
footerNav: z.string(),
|
|
menuOpen: z.string(),
|
|
menuClose: z.string(),
|
|
themeToggle: z.string(),
|
|
newTab: z.string(),
|
|
home: z.string(),
|
|
}),
|
|
});
|
|
|
|
const socialSchema = z.array(
|
|
z.object({
|
|
id: z.string(),
|
|
label: z.string(),
|
|
// Empty string = account not set → hidden in the UI.
|
|
href: z.union([z.url(), z.literal('')]),
|
|
icon: z.string().startsWith('tabler:'),
|
|
handle: z.string().optional(),
|
|
}),
|
|
);
|
|
|
|
function parse<T>(name: string, schema: z.ZodType<T>, data: unknown): T {
|
|
const result = schema.safeParse(data);
|
|
if (!result.success) {
|
|
throw new Error(`src/data/${name} is invalid:\n${z.prettifyError(result.error)}`);
|
|
}
|
|
return result.data;
|
|
}
|
|
|
|
export const site = parse('site.json', siteSchema, siteJson);
|
|
export const social = parse('social.json', socialSchema, socialJson).filter((s) => s.href);
|
|
|
|
export type Site = z.infer<typeof siteSchema>;
|
|
export type SocialLink = (typeof social)[number];
|