SimulationEmbed React island with click-to-activate iframes, theme sync via postMessage/MutationObserver, and graceful fallback when SpiceBook is unavailable. 12 simulations across 6 books (555 timer, op-amp, semiconductor, formulas, communications, sensor). Books without simulations render no extra markup. client:visible hydration defers JS cost until scrolled into view.
38 lines
1.1 KiB
TypeScript
38 lines
1.1 KiB
TypeScript
import { defineCollection, z } from 'astro:content';
|
|
|
|
const booksCollection = defineCollection({
|
|
type: 'content',
|
|
schema: z.object({
|
|
title: z.string(),
|
|
shortTitle: z.string(),
|
|
collection: z.enum(['mims', 'uglys', 'other']),
|
|
year: z.number().optional(),
|
|
description: z.string(),
|
|
topics: z.array(z.string()),
|
|
archiveOrgId: z.string().optional(),
|
|
archiveOrgUrl: z.string().url().optional(),
|
|
localPdf: z.string(),
|
|
coverImage: z.string().optional(),
|
|
pageCount: z.number().optional(),
|
|
sortOrder: z.number().default(0),
|
|
formats: z.array(z.object({
|
|
type: z.string(),
|
|
filename: z.string(),
|
|
url: z.string().optional()
|
|
})).optional(),
|
|
simulations: z.array(z.object({
|
|
notebookId: z.string().min(1).max(100).regex(
|
|
/^[a-z0-9][a-z0-9-]*[a-z0-9]$/,
|
|
'notebookId must be lowercase alphanumeric with hyphens, no leading/trailing hyphens'
|
|
),
|
|
title: z.string(),
|
|
description: z.string(),
|
|
pageRef: z.string().optional(),
|
|
})).optional()
|
|
})
|
|
});
|
|
|
|
export const collections = {
|
|
books: booksCollection
|
|
};
|