一、④段整套同步(用户选候选 A)
- 新增 `references/stage-proto-doc/assets/产品文档模板.html`(16 KB/247 行,从备份拷、转 LF)。
- ④段 SKILL.md 换成备份 15:28 那版,并按模板口径改齐:
· 「产品文档框架」从「四块+一附录」改成**六节** —— 页面结构/功能清单/使用场景/操作流程/
跨页与界面状态/待确认与偏离;与模板同构,⛔ 不从空白页重写结构。
· 编号从 R/S/F 改成 **P 页面/S 场景/F 功能/J 二级功能**,各自唯一、按展示顺序通排。
· 保留旧版有价值的要求:每项功能补齐 做什么/入口在哪/输入边界/异常与恢复;重点功能三处标红;
横向流程图每步写清页面/功能/操作/完成判据。
· 完成标准「七节」→「六节」,自查清单同步换掉「需求」那批条目,补模板与配图两条。
· 路径 `designs/<项目>/` → `docs/pm/<项目>/ui/`(2 处)。
· 补回本机那条「每份产出文档开头都要有一张图」的引用(指向主 skill,段内不复述)。
- 主 skill 同步:四段结构表④行、④段沿革、画图小节(④段画页面结构图 + 用户路径)。
二、open-design 移到技能根(用户选候选 A)
- `git mv product-planning/references/stage-delivery/assets/open-design → open-design/`(4178 文件)。
- `.gitignore` 白名单加 `!/open-design/` —— 否则移出去就不再纳入版本控制。
- 引用改 11 处:路径形态 `assets/open-design/...` → `<技能根>/open-design/...`;
「随段携带的自带件」改称「同根技能」;「路径怎么找」那条改成自带件(段内相对路径)与
同根技能(`<技能根>/<技能名>/`)两类分开写。
- open-design 自己的 ATTRIBUTION.md 两处同步(它现在与 oil-ui-pro 同层)。
- 副作用(有益):命名闸门读数由 2063 降到 225 —— 此前它把 open-design 里 919 个第三方 md
当成规则一起扫了;现在只扫本包 23 个 md。已确认④段的 H1 与 4a/4b/4c 子步仍在检查范围内。
三、验收
- check_naming.py --ws E:/ProgramData/AIProject/contentm_agent = 通过 225 / 失败 0 / 提示 27。
- 活文件里 `assets/open-design` 残留 0;工作区 CRLF 文件 0。
448 lines
13 KiB
TypeScript
448 lines
13 KiB
TypeScript
/**
|
|
* 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 `<em>` 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 <em> 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: `<b>{title}</b>{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 <title> 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;
|
|
}
|