feat: add about, contact with map, legal pages and 404
- About page with identity cards and equipment lists (about.json) - Contact page: station info, obfuscated e-mail, lazy Leaflet/OSM map with antenna tower marker and dark-mode tiles - Privacy, license and disclaimer pages from a legal collection - QRT-themed 404 page - LICENSE (MIT) and LICENSE-CONTENT (CC BY-SA 4.0 legal code) - Shared PageHeader and CodeCopy components Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -8,6 +8,9 @@ import socialJson from '../data/social.json';
|
||||
import homeJson from '../data/home.json';
|
||||
import categoriesJson from '../data/categories.json';
|
||||
import blogJson from '../data/blog.json';
|
||||
import aboutJson from '../data/about.json';
|
||||
import contactJson from '../data/contact.json';
|
||||
import pagesJson from '../data/pages.json';
|
||||
|
||||
const link = z.object({ label: z.string().min(1), href: z.string().min(1) });
|
||||
const icon = z.string().startsWith('tabler:');
|
||||
@@ -180,6 +183,66 @@ const blogSchema = z.object({
|
||||
),
|
||||
});
|
||||
|
||||
const seo = z.object({ title: z.string().max(60), description: z.string().max(160) });
|
||||
|
||||
const aboutSchema = z.object({
|
||||
seo,
|
||||
eyebrow: z.string(),
|
||||
title: z.string(),
|
||||
lead: z.string(),
|
||||
identity: z.object({
|
||||
title: z.string(),
|
||||
items: z.array(z.object({ label: z.string(), value: z.string(), icon })),
|
||||
}),
|
||||
equipment: z.array(
|
||||
z.object({
|
||||
title: z.string(),
|
||||
icon,
|
||||
items: z.array(z.object({ name: z.string(), note: z.string().optional() })).min(1),
|
||||
}),
|
||||
),
|
||||
});
|
||||
|
||||
const contactSchema = z.object({
|
||||
seo,
|
||||
eyebrow: z.string(),
|
||||
title: z.string(),
|
||||
icon,
|
||||
lead: z.string(),
|
||||
infoTitle: z.string(),
|
||||
map: z.object({
|
||||
title: z.string(),
|
||||
lat: z.number().min(-90).max(90),
|
||||
lng: z.number().min(-180).max(180),
|
||||
zoom: z.number().int().min(1).max(19),
|
||||
popup: z.string(),
|
||||
ariaLabel: z.string(),
|
||||
note: z.string(),
|
||||
loading: z.string(),
|
||||
}),
|
||||
items: z.array(
|
||||
z.object({
|
||||
label: z.string(),
|
||||
icon,
|
||||
values: z.array(z.string()).optional(),
|
||||
href: z.url().optional(),
|
||||
email: z.email().optional(),
|
||||
}),
|
||||
),
|
||||
});
|
||||
|
||||
const pagesSchema = z.object({
|
||||
legal: strings(['eyebrow', 'updated', 'notice']),
|
||||
notFound: z.object({
|
||||
seoTitle: z.string(),
|
||||
description: z.string(),
|
||||
eyebrow: z.string(),
|
||||
title: z.string(),
|
||||
lead: z.string(),
|
||||
links: z.array(link.extend({ variant: z.enum(['primary', 'secondary', 'link']), icon })),
|
||||
}),
|
||||
});
|
||||
|
||||
function parse<T>(name: string, schema: z.ZodType<T>, data: unknown): T {
|
||||
const result = schema.safeParse(data);
|
||||
if (!result.success) {
|
||||
@@ -192,6 +255,9 @@ export const site = parse('site.json', siteSchema, siteJson);
|
||||
export const home = parse('home.json', homeSchema, homeJson);
|
||||
export const categories = parse('categories.json', categoriesSchema, categoriesJson);
|
||||
export const blog = parse('blog.json', blogSchema, blogJson);
|
||||
export const about = parse('about.json', aboutSchema, aboutJson);
|
||||
export const contact = parse('contact.json', contactSchema, contactJson);
|
||||
export const pages = parse('pages.json', pagesSchema, pagesJson);
|
||||
export const social = parse('social.json', socialSchema, socialJson).filter((s) => s.href);
|
||||
|
||||
export type Site = z.infer<typeof siteSchema>;
|
||||
|
||||
Reference in New Issue
Block a user