/** * open-design-landing — input schema. * * This is the contract between users and `scripts/compose.ts`. A valid * `inputs.json` matching `EditorialCollageInputs` is enough to produce * a complete Atelier Zero landing page, end-to-end, with no further * code changes needed. * * Convention: every field that drives visible copy lives here. The * structural CSS, layout grid, motion, and 16 image slots are fixed by * the design system (`design-systems/atelier-zero/DESIGN.md`); only * brand identity and content text are user-controlled. */ /* ---------- text helpers ---------- */ /** * A `MixedText` is a sentence whose visual rhythm comes from alternating * sans-serif and italic-serif spans. Encode it as an array of segments; * the composer concatenates them into HTML, wrapping `em: true` segments * in `` tags. The trailing `dot: true` segment renders the coral * full-stop accent. * * Example: * [ * { text: 'We treat ' }, * { text: 'your agent', em: true }, * { text: ' as a creative ' }, * { text: 'collaborator,', em: true }, * { text: ' not a black box' }, * { text: '.', dot: true }, * ] */ export interface TextSegment { text: string; /** Wrap in for italic-serif emphasis. */ em?: boolean; /** Render as the coral terminating dot accent (use as the final segment). */ dot?: boolean; } export type MixedText = TextSegment[]; /* ---------- brand block ---------- */ export interface BrandBlock { /** Display name (appears in nav, footer, og:title, browser tab). */ name: string; /** Single glyph for the circled brand mark — `Ø`, `▲`, `★`, etc. */ mark: string; /** * Two-line meta block in the nav: `{title}{subtitle}` with a * dividing rule. e.g. `{ title: 'Studio Nº 01', subtitle: 'Berlin / Open / Earth' }`. */ meta: { title: string; subtitle: string }; /** Filed-under tagline shown in the topbar. */ filed_under: string; /** Tagline shown in the page alongside the brand. */ tagline: string; /** SEO description; appears in `<meta name='description'>`. */ description: string; /** ISO 639-1 language code; defaults to `en`. */ locale?: string; /** Edition badge — `'Vol. 01 / Issue Nº 26'`. */ edition: string; /** Visible build version — `'v0.4.6'`. */ version: string; /** SPDX license identifier or short label — `'Apache-2.0'`. */ license: string; /** Primary CTA URL (Star on GitHub, etc.). */ primary_url: string; /** Star-button label in the nav. */ primary_url_label: string; /** * Optional secondary CTA URL surfaced as a ghost pill in the nav and as * a button in the footer brand column. When set, the marketing surface * advertises a "Download" entry so users know they can install directly. */ download_url?: string; /** Label for the download CTA — defaults to `'Download'` when omitted. */ download_url_label?: string; /** Email address shown in the CTA section. */ contact_email: string; /** Pretty location line — `'Berlin / Open / Earth'`. */ location: string; /** Coordinates string — `'52.5200° N · 13.4050° E'`. */ coordinates: string; /** Year of publication — `'2026'`. */ year: string; /** Roman numeral year for the footer kicker — `'MMXXVI'`. */ year_roman: string; /** Founding tagline — `'Est. MMXXVI'`. */ founded: string; /** Side rails (the rotated text fixed to viewport edges). */ rails: { right: string; left: string }; /** Topbar live channel languages — `['EN', 'DE', '中文', '日本語']`. First entry is bolded. */ languages: string[]; /** Topbar pulse text — `'Live · v0.4.6'`. */ status: string; } /* ---------- nav ---------- */ export interface NavLink { label: string; href: string; /** Optional superscript count badge — `'31'`, `'72'`, etc. */ count?: string; } /* ---------- hero ---------- */ export interface HeroStat { /** Number or short string inside the ring — `'31'`. */ value: string; /** Bold label below the ring — `'skills'`. */ label: string; /** Sub-label — `'shippable'`. */ sub: string; /** Visual treatment: dashed border (default), solid border, or coral accent. */ variant?: 'dashed' | 'solid' | 'coral'; } export interface HeroIndexItem { /** Two-digit number — `'01'`. */ num: string; /** Step name — `'Detect'`. */ label: string; /** Mark this item as the active one (rendered in solid ink). */ active?: boolean; } export interface HeroBlock { /** Eyebrow label (left) — `'Open-source design studio'`. */ label: string; /** Eyebrow index (right of label) — `'· Nº 01'`. */ ix: string; /** The H1 — encoded as MixedText. */ headline: MixedText; /** Lead paragraph; can include `<code>` via raw HTML — keep ASCII-quotes safe. */ lead: string; /** Primary CTA. */ primary: { label: string; href: string }; /** Secondary CTA. */ secondary: { label: string; href: string }; /** Three stat rings displayed below the CTAs. */ stats: [HeroStat, HeroStat, HeroStat]; /** Bottom-left meta line in the hero foot. */ meta: string; /** Four index items rendered over the hero collage. */ index: [HeroIndexItem, HeroIndexItem, HeroIndexItem, HeroIndexItem]; /** Image annotations (corner labels). */ annotations: { tl: string; tr: string; bl: string; br: string; }; } /* ---------- about ---------- */ export interface AboutBlock { label: string; ix: string; headline: MixedText; lead: string; cta_label: string; cta_href: string; /** Footer row text — `'Research · Design · Engineering · Repeat'`. */ footer_text: string; /** Stamp top line (coral) — `'Studio practice'`. */ stamp_top: string; /** Stamp bottom line (ink) — `'Est. MMXXVI'`. */ stamp_bottom: string; /** Side note (right of the about image). */ side_note: string; /** Caption below the about image. */ caption: { bold: string; rest: string }; } /* ---------- capabilities ---------- */ export interface CapabilityCard { /** Two-digit accent — `'01'`. */ num: string; /** Tag — `'Skills'`. */ tag: string; /** SVG inner contents (paths/circles/rects only — no <svg> wrapper). */ icon_svg: string; /** Title; use \n for line breaks. */ title: string; /** Body; can include `<code>` raw HTML. */ body: string; href: string; } export interface CapabilitiesBlock { label: string; ix: string; headline: MixedText; lead: string; ribbon: string; /** Exactly four cards. */ cards: [CapabilityCard, CapabilityCard, CapabilityCard, CapabilityCard]; } /* ---------- labs ---------- */ export interface LabPill { label: string; count: string; active?: boolean; } export interface LabCard { badge: string; num: string; year: string; title: string; body: string; href: string; } export interface LabsBlock { label: string; ix: string; headline: MixedText; pills: LabPill[]; meta: { ring: string; bold: string; sub: string }; /** Exactly five lab cards. */ cards: [LabCard, LabCard, LabCard, LabCard, LabCard]; /** Progress bar — total segments and how many are filled. */ progress: { total: number; filled: number }; foot: string; } /* ---------- method ---------- */ export interface MethodStep { num: string; title: string; body: string; } export interface MethodBlock { label: string; ix: string; headline: MixedText; right: string; /** Exactly four steps. */ steps: [MethodStep, MethodStep, MethodStep, MethodStep]; foot_left: string; foot_right_bold: string; foot_right_rest: string; } /* ---------- work ---------- */ export interface WorkCard { small_label: string; index: string; title: string; body: string; year: string; tag: string; } export interface WorkBlock { label: string; headline: MixedText; link_label: string; link_href: string; /** Two cards — first regular, second has the .alt tilt. */ cards: [WorkCard, WorkCard]; } /* ---------- testimonial / partners ---------- */ export interface Partner { /** SVG inner contents (paths/circles/rects only — no <svg> wrapper). */ glyph_svg: string; name: string; role: string; /** Click target for the partner card. When omitted, falls back to `'#'`. */ href?: string; } export interface TestimonialBlock { label: string; ix: string; /** Quote with em emphasis; the leading `"` and trailing `"` are added by the composer. */ quote: MixedText; author: { initial: string; name: string; title: string }; partners_text: string; /** Up to five partners; the design fits five comfortably. */ partners: Partner[]; read_more_label: string; read_more_href: string; } /* ---------- cta ---------- */ export interface CTABlock { label: string; ix: string; headline: MixedText; lead: string; primary: { label: string; href: string }; ribbon: string; } /* ---------- wire / global ticker ---------- */ /** * A single city pinned to the studio's "from the field" ticker. The * marquee renders `{coord} {name}`, so keep `coord` short — `52.52°N`, * `1.29°S`, etc. */ export interface WireCity { /** Display name — `'Berlin'`, `'São Paulo'`. Title-case is fine; the * stylesheet uppercases it visually. */ name: string; /** Latitude only, prettified — `'52.52°N'`. */ coord: string; } /** * A named contributor / lineage handle in the ticker's bottom row. The * marquee renders `@{handle} {role}` and the whole pill becomes a link * to `href` (typically a GitHub profile or org page). */ export interface WireContributor { /** GitHub-style handle without the leading `@` — `'tw93'`, `'OpenCoworkAI'`. */ handle: string; /** Short role tag — `'kami'`, `'core'`, `'be next'`. Rendered in coral. */ role: string; /** Click target for the handle pill. */ href: string; } /** * Optional editorial ticker rendered between the hero and the about * section. Two counter-scrolling marquees: cities (left → right) and * contributors (right → left). Designed to signal that the project is * global and community-driven without disrupting the roman-numeral * section count. */ export interface WireBlock { /** Bold uppercase headline on the left rail — `'From the field'`. */ title: string; /** Sub-label — `'Open · 23 cities · 6 contributors'`. Optional; computed * from the lists when omitted. */ subtitle?: string; cities: WireCity[]; contributors: WireContributor[]; } /* ---------- footer ---------- */ export interface FooterColumn { title: string; links: { label: string; href: string }[]; } export interface FooterBlock { brand_description: string; /** * Optional CTA rendered under the brand description in the footer * (e.g. `{ label: 'Download desktop', href: 'https://.../releases', * meta: 'macOS · v0.3.0' }`). When `brand.download_url` is set this is * filled in automatically; explicit values take precedence. */ brand_cta?: { label: string; href: string; meta?: string }; /** Up to five columns; the design fits five at the widest breakpoint. */ columns: FooterColumn[]; /** Footer mega kicker — encoded as MixedText so the brand can italicize part of it. */ mega: MixedText; } /* ---------- section rules (the I., II., III. dividers) ---------- */ export interface SectionRule { /** Roman numeral string — `'I.'`, `'II.'`, etc. */ roman: string; /** Three middle text spans separated by a coral dot. */ meta: [string, string, string]; /** Pagination — `'002 / 008'`. */ pagination: string; } export interface SectionRules { about: SectionRule; capabilities: SectionRule; labs: SectionRule; method: SectionRule; work: SectionRule; testimonial: SectionRule; cta: SectionRule; } /* ---------- image strategy ---------- */ /** * `'generate'` — call gpt-image-2 (via fal.ai or Azure) for every slot * using `assets/imagegen-prompts.md` as the prompt source, brand-keyed * via the `imagery_prompts` field on the inputs. * `'placeholder'` — emit SVG paper-textured frames into `out/assets/` * so the layout is fully rendered even with no AI image budget. * Users can swap real PNGs in later without touching markup. * `'bring-your-own'` — assume the 16 PNGs are already at the configured * `assets_path`; do nothing. */ export type ImageStrategy = 'generate' | 'placeholder' | 'bring-your-own'; export interface ImageryConfig { strategy: ImageStrategy; /** Relative path (from the output) to the asset folder. Default: `./assets/`. */ assets_path: string; /** Per-slot prompt overrides for `'generate'` strategy. */ prompts?: Record<string, string>; /** When `strategy: 'generate'`, which provider to call. */ provider?: 'fal' | 'azure'; } /* ---------- top-level ---------- */ export interface EditorialCollageInputs { $schema?: string; brand: BrandBlock; nav: NavLink[]; rules: SectionRules; hero: HeroBlock; about: AboutBlock; capabilities: CapabilitiesBlock; labs: LabsBlock; method: MethodBlock; work: WorkBlock; testimonial: TestimonialBlock; cta: CTABlock; footer: FooterBlock; /** * Optional editorial wire/ticker between hero and about. Omit to hide * the strip entirely. */ wire?: WireBlock; imagery: ImageryConfig; }