/* [RFC-0202] Canonical LivePhoto config — the content contract that turns a static authored image into a "living photo": a looping, muted, decorative Do not carry a video/poster path — the clip is derived from the image token. Do not implement playback or resolution — that is @warpgogol/werkstatt-site/ui + resolveVideo. RFC-0202: introduced the LivePhoto content contract. */ import { z } from "zod"; /** How the loop starts. */ export const livePhotoTriggerSchema = z.enum(["in-viewport", "tap", "autoplay"]); export type LivePhotoTrigger = z.infer; export const livePhotoSchema = z .object({ /** Turn the image into a living photo. Default true (presence implies intent). */ enabled: z.boolean().default(true), /** * When the loop starts: * - "in-viewport" (default): plays while scrolled into view, pauses when out (IntersectionObserver). * - "tap": stays a static photo until the visitor activates the play control. * - "autoplay": plays immediately on load via native attributes — no JavaScript. */ trigger: livePhotoTriggerSchema.optional(), /** Loop the clip continuously. Default true. false = play once, then rest on the last frame. */ loop: z.boolean().optional(), /** * What a tap does once the clip is running: * - "toggle" (default): tap pauses a playing clip / resumes a paused one. * - "play-only": tap can start the clip but never pauses it (the control only kick-starts). */ tapBehavior: z.enum(["toggle", "play-only"]).optional(), /** Media preload hint. Default "metadata" (spec-recommended baseline). */ preload: z.enum(["none", "metadata", "auto"]).optional(), }) .strict(); export type LivePhoto = z.infer;