diff --git a/apps/daemon/src/server.ts b/apps/daemon/src/server.ts index 018db4547..bc71cd64d 100644 --- a/apps/daemon/src/server.ts +++ b/apps/daemon/src/server.ts @@ -17,7 +17,7 @@ import { sanitizeCustomModel, spawnEnvForAgent, } from './agents.js'; -import { listSkills } from './skills.js'; +import { findSkillById, listSkills } from './skills.js'; import { listCodexPets, readCodexPetSpritesheet } from './codex-pets.js'; import { syncCommunityPets } from './community-pets-sync.js'; import { listDesignSystems, readDesignSystem } from './design-systems.js'; @@ -1198,7 +1198,7 @@ export async function startServer({ port = 7456, host = process.env.OD_BIND_HOST app.get('/api/skills/:id', async (req, res) => { try { const skills = await listSkills(SKILLS_DIR); - const skill = skills.find((s) => s.id === req.params.id); + const skill = findSkillById(skills, req.params.id); if (!skill) return res.status(404).json({ error: 'skill not found' }); const { dir: _dir, ...serializable } = skill; res.json(serializable); @@ -1380,7 +1380,7 @@ export async function startServer({ port = 7456, host = process.env.OD_BIND_HOST app.get('/api/skills/:id/example', async (req, res) => { try { const skills = await listSkills(SKILLS_DIR); - const skill = skills.find((s) => s.id === req.params.id); + const skill = findSkillById(skills, req.params.id); if (!skill) { return res.status(404).type('text/plain').send('skill not found'); } @@ -1441,7 +1441,7 @@ export async function startServer({ port = 7456, host = process.env.OD_BIND_HOST app.get('/api/skills/:id/assets/*', async (req, res) => { try { const skills = await listSkills(SKILLS_DIR); - const skill = skills.find((s) => s.id === req.params.id); + const skill = findSkillById(skills, req.params.id); if (!skill) { return res.status(404).type('text/plain').send('skill not found'); } @@ -2231,8 +2231,9 @@ export async function startServer({ port = 7456, host = process.env.OD_BIND_HOST let skillCraftRequires = []; let activeSkillDir = null; if (effectiveSkillId) { - const skill = (await listSkills(SKILLS_DIR)).find( - (s) => s.id === effectiveSkillId, + const skill = findSkillById( + await listSkills(SKILLS_DIR), + effectiveSkillId, ); if (skill) { skillBody = skill.body; diff --git a/apps/daemon/src/skills.ts b/apps/daemon/src/skills.ts index f2121c194..15fd4c66d 100644 --- a/apps/daemon/src/skills.ts +++ b/apps/daemon/src/skills.ts @@ -8,6 +8,36 @@ import path from "node:path"; import { parseFrontmatter } from "./frontmatter.js"; import { SKILLS_CWD_ALIAS } from "./cwd-aliases.js"; +// Persisted skill ids on existing projects can outlive a folder rename. +// listSkills() derives the id from the SKILL.md frontmatter `name`, so once +// a skill is renamed the old id stops resolving and composeSystemPrompt +// silently drops the skill body for projects saved against the old id. +// This map forwards deprecated ids to their current canonical id; callers +// resolve through findSkillById() before scanning the listing. Leave entries +// here for at least one stable release after a rename so on-disk projects +// keep composing with the intended skill prompt. +export const SKILL_ID_ALIASES = Object.freeze({ + "editorial-collage": "open-design-landing", + "editorial-collage-deck": "open-design-landing-deck", +}); + +export function resolveSkillId(id) { + if (typeof id !== "string" || id.length === 0) return id; + return SKILL_ID_ALIASES[id] ?? id; +} + +// Lookup helper that mirrors `skills.find((s) => s.id === id)` but first +// rewrites any deprecated id to its current canonical form. Use this at +// every site that resolves a stored or external skill id; calling +// `.find()` directly will silently miss aliased ids. +export function findSkillById(skills, id) { + if (!Array.isArray(skills) || typeof id !== "string" || id.length === 0) { + return undefined; + } + const canonical = resolveSkillId(id); + return skills.find((s) => s.id === canonical); +} + export async function listSkills(skillsRoot) { const out = []; let entries = []; diff --git a/apps/daemon/tests/skill-asset-rewrite.test.ts b/apps/daemon/tests/skill-asset-rewrite.test.ts index 213708e62..a3afd3ed8 100644 --- a/apps/daemon/tests/skill-asset-rewrite.test.ts +++ b/apps/daemon/tests/skill-asset-rewrite.test.ts @@ -4,8 +4,8 @@ import { rewriteSkillAssetUrls } from '../src/server.js'; describe('rewriteSkillAssetUrls', () => { it('rewrites ./assets/ img sources to the daemon route', () => { const html = ``; - expect(rewriteSkillAssetUrls(html, 'editorial-collage')).toBe( - ``, + expect(rewriteSkillAssetUrls(html, 'open-design-landing')).toBe( + ``, ); }); @@ -17,9 +17,9 @@ describe('rewriteSkillAssetUrls', () => { }); it('rewrites sibling skill asset references', () => { - const html = ``; + const html = ``; expect(rewriteSkillAssetUrls(html, 'foo')).toBe( - ``, + ``, ); }); diff --git a/apps/daemon/tests/skill-id-aliases.test.ts b/apps/daemon/tests/skill-id-aliases.test.ts new file mode 100644 index 000000000..5ce2bb327 --- /dev/null +++ b/apps/daemon/tests/skill-id-aliases.test.ts @@ -0,0 +1,119 @@ +// @ts-nocheck +import { afterAll, beforeAll, describe, expect, it } from 'vitest'; +import { mkdir, mkdtemp, rm, writeFile } from 'node:fs/promises'; +import { tmpdir } from 'node:os'; +import path from 'node:path'; + +import { + SKILL_ID_ALIASES, + findSkillById, + listSkills, + resolveSkillId, +} from '../src/skills.js'; + +// Regression coverage for the editorial-collage → open-design-landing rename. +// The daemon persists the chosen skill_id verbatim on a project row and +// resolves it later by id, so a folder/frontmatter rename without a +// compatibility shim would silently drop the skill prompt for projects +// saved against the old id. These tests pin the alias map and the lookup +// helper that every server-side resolver must go through. + +let skillsRoot; + +beforeAll(async () => { + skillsRoot = await mkdtemp(path.join(tmpdir(), 'od-skills-aliases-')); + // Mimic the on-disk shape the production registry expects: one + // directory per skill, each with a SKILL.md whose frontmatter `name` + // becomes the canonical id returned by listSkills(). + await mkdir(path.join(skillsRoot, 'open-design-landing'), { recursive: true }); + await writeFile( + path.join(skillsRoot, 'open-design-landing', 'SKILL.md'), + '---\nname: open-design-landing\ndescription: Atelier Zero landing.\n---\n\nbody\n', + 'utf8', + ); + await mkdir(path.join(skillsRoot, 'open-design-landing-deck'), { + recursive: true, + }); + await writeFile( + path.join(skillsRoot, 'open-design-landing-deck', 'SKILL.md'), + '---\nname: open-design-landing-deck\ndescription: Atelier Zero deck.\n---\n\nbody\n', + 'utf8', + ); + // An untouched skill so we can prove the helper still resolves + // non-aliased ids and does not match by accident. + await mkdir(path.join(skillsRoot, 'simple-deck'), { recursive: true }); + await writeFile( + path.join(skillsRoot, 'simple-deck', 'SKILL.md'), + '---\nname: simple-deck\ndescription: Plain deck.\n---\n\nbody\n', + 'utf8', + ); +}); + +afterAll(async () => { + if (skillsRoot) await rm(skillsRoot, { recursive: true, force: true }); +}); + +describe('SKILL_ID_ALIASES', () => { + it('maps the editorial-collage rename to its current canonical id', () => { + expect(SKILL_ID_ALIASES['editorial-collage']).toBe('open-design-landing'); + expect(SKILL_ID_ALIASES['editorial-collage-deck']).toBe( + 'open-design-landing-deck', + ); + }); + + it('is frozen so callers cannot mutate the deprecation list at runtime', () => { + expect(Object.isFrozen(SKILL_ID_ALIASES)).toBe(true); + }); +}); + +describe('resolveSkillId', () => { + it('forwards deprecated ids to their canonical replacement', () => { + expect(resolveSkillId('editorial-collage')).toBe('open-design-landing'); + expect(resolveSkillId('editorial-collage-deck')).toBe( + 'open-design-landing-deck', + ); + }); + + it('passes non-aliased ids through unchanged', () => { + expect(resolveSkillId('simple-deck')).toBe('simple-deck'); + expect(resolveSkillId('totally-unknown')).toBe('totally-unknown'); + }); + + it('returns the input unchanged for empty / non-string ids', () => { + expect(resolveSkillId('')).toBe(''); + expect(resolveSkillId(undefined)).toBeUndefined(); + expect(resolveSkillId(null)).toBeNull(); + }); +}); + +describe('findSkillById', () => { + it('resolves a project saved with the old editorial-collage id to the renamed skill', async () => { + const skills = await listSkills(skillsRoot); + const skill = findSkillById(skills, 'editorial-collage'); + expect(skill).toBeDefined(); + expect(skill.id).toBe('open-design-landing'); + expect(skill.body).toContain('body'); + }); + + it('resolves a project saved with the old editorial-collage-deck id to the renamed deck skill', async () => { + const skills = await listSkills(skillsRoot); + const skill = findSkillById(skills, 'editorial-collage-deck'); + expect(skill).toBeDefined(); + expect(skill.id).toBe('open-design-landing-deck'); + }); + + it('still resolves current ids exactly', async () => { + const skills = await listSkills(skillsRoot); + expect(findSkillById(skills, 'open-design-landing')?.id).toBe( + 'open-design-landing', + ); + expect(findSkillById(skills, 'simple-deck')?.id).toBe('simple-deck'); + }); + + it('returns undefined for unknown ids and missing inputs', async () => { + const skills = await listSkills(skillsRoot); + expect(findSkillById(skills, 'definitely-not-a-skill')).toBeUndefined(); + expect(findSkillById(skills, '')).toBeUndefined(); + expect(findSkillById(null, 'open-design-landing')).toBeUndefined(); + }); +}); diff --git a/apps/landing-page/AGENTS.md b/apps/landing-page/AGENTS.md index 19d32a8b9..292bf6701 100644 --- a/apps/landing-page/AGENTS.md +++ b/apps/landing-page/AGENTS.md @@ -9,10 +9,10 @@ records module-level boundaries for `apps/landing-page/`. the canonical Open Design marketing page in the **Atelier Zero** style. It is the deployable counterpart to: -- Skill: `skills/editorial-collage/` — agent workflow + the source-of-truth +- Skill: `skills/open-design-landing/` — agent workflow + the source-of-truth `example.html` known-good rendering. - Design system: `design-systems/atelier-zero/DESIGN.md` — token spec. -- Image assets: `skills/editorial-collage/assets/*.png` are uploaded to +- Image assets: `skills/open-design-landing/assets/*.png` are uploaded to Cloudflare R2 (`open-design-static`) and served through `static.open-design.ai` with Image Resizing (`format=auto`). Do not commit local mirrored PNGs into `apps/landing-page/public/assets/`. @@ -52,7 +52,7 @@ It is the deployable counterpart to: `app/`. If a component grows beyond ~80 lines, extract it to `app/_components/.tsx`. - Must not depend on any non-Google web font. -- When the canonical `skills/editorial-collage/example.html` changes, +- When the canonical `skills/open-design-landing/example.html` changes, the corresponding section JSX in `app/page.tsx` and rules in `app/globals.css` must be updated to match. The two files are kept in lockstep. diff --git a/apps/landing-page/app/_components/wire.tsx b/apps/landing-page/app/_components/wire.tsx index 911405ac6..421aacfe7 100644 --- a/apps/landing-page/app/_components/wire.tsx +++ b/apps/landing-page/app/_components/wire.tsx @@ -44,7 +44,7 @@ type Contributor = { // SSR-safe initial list. Used until the GitHub fetch resolves AND as // the permanent fallback when the network is unavailable. Mirrors the -// canonical wire row in `skills/editorial-collage/example.html` so +// canonical wire row in `skills/open-design-landing/example.html` so // hydration is byte-stable against the static reference rendering. const FALLBACK: ReadonlyArray = [ { handle: 'tw93', role: 'kami', href: 'https://github.com/tw93' }, diff --git a/apps/landing-page/app/globals.css b/apps/landing-page/app/globals.css index b9c75c953..ff69cdd01 100644 --- a/apps/landing-page/app/globals.css +++ b/apps/landing-page/app/globals.css @@ -1,7 +1,7 @@ /* * Atelier Zero — landing page styles. * - * Mirrors `skills/editorial-collage/example.html` + + +
+
+ Open Design · Manifesto · 2026 Edition + open.design + Cover · 01 / 08 · OSS Alternative +
+ +
+
+
+ Apache 2.0 + Local-first + BYOK +
+ +

+ Design with the
+ agent already
+ on your laptop. +

+ +

+ Open Design is the open-source alternative to Claude Design. + Your existing coding agent — Claude · Codex · Cursor · Gemini · OpenCode · Qwen + — becomes the design engine, driven by 31 composable skills and + 72 brand-grade design systems. +

+
+ +
+ Fig. 01 / OD-26 + Plate Nº 08 +
+ +
+ Composed in Open Design +
+
+ + Open
Source
+ +
+
+ 72 + Design
Systems
+
+
+ 31 + Composable
Skills
+
+
+ 12 + Coding
Agents
+
+
+ 0 + Lock-in /
Vendor Cloud
+
+
+
+ + diff --git a/apps/landing-page/package.json b/apps/landing-page/package.json index e5a8415d4..ee3747e77 100644 --- a/apps/landing-page/package.json +++ b/apps/landing-page/package.json @@ -1,6 +1,6 @@ { "name": "@open-design/landing-page", - "version": "0.2.0", + "version": "0.3.0", "private": true, "type": "module", "scripts": { diff --git a/apps/web/src/i18n/content.fr.ts b/apps/web/src/i18n/content.fr.ts index 832b9d210..71fbd90bb 100644 --- a/apps/web/src/i18n/content.fr.ts +++ b/apps/web/src/i18n/content.fr.ts @@ -32,15 +32,15 @@ export const FR_SKILL_COPY: Record = { examplePrompt: 'Eine Dokumentationsseite — linke Navigation, scrollbarer Artikelbereich, rechte Inhaltsübersicht.', }, - 'editorial-collage': { + 'open-design-landing': { examplePrompt: - 'Entwerfen Sie eine Editorial-Landingpage im Atelier-Zero- / Monocle-Stil — warme Papierleinwand, surreale Plaster-und-Architektur-Collage, übergroße gemischte Italic-Serif-Display-Type, römische Ziffern als Sektionsmarker und ein einziger Korallenakzent.', + 'Entwerfen Sie die Open-Design-Marketing-Landingpage im Atelier-Zero- / Monocle-Stil — warme Papierleinwand, surreale Plaster-und-Architektur-Collage, übergroße gemischte Italic-Serif-Display-Type, römische Ziffern als Sektionsmarker und ein einziger Korallenakzent.', }, - 'editorial-collage-deck': { + 'open-design-landing-deck': { examplePrompt: - 'Erstellen Sie ein 11-Slide-Pitch-Deck für „Lumen Field“, ein Studio für Fokus-Soundscapes. Cover mit Hero-Plate, zwei Sektions-Trenner, zwei Produkt-Content-Slides mit Bullets, ein Stats-Slide mit 12 Soundscapes / 4 Presets / 1 Daily Ritual, ein Kundenzitat, ein abschließendes CTA und eine End-Card. Wiederverwenden Sie die Bildbibliothek von editorial-collage.', + 'Erstellen Sie das Open-Design-Pitch-Deck im Atelier-Zero-Stil — Cover mit Hero-Plate, römische Sektions-Trenner, Stats-Slide (31 Skills · 72 Systeme · 12 CLIs), Kundenzitat, CTA und Mega-Italic-Serif-End-Card. Horizontal-Swipe-Pagination wie eine Print-Magazine.', description: - 'Erstellt ein Single-File-Slide-Deck in der Atelier-Zero-Bildsprache (warmes Papier, italic-serif Akzent-Spans, korallenfarbene Schluss-Dots, surreale Collage-Platten). Das Deck nutzt Scroll-Snap-Pagination, Pfeiltasten- + Leertaste-Navigation, ein Live-HUD mit Slide-Zähler und Fortschrittsbalken und erbt das kanonische Stylesheet sowie die 16-Slot-Bildbibliothek der Schwester-Skill `editorial-collage`.', + 'Erstellt ein Single-File-Slide-Deck im Atelier-Zero-Stil (warmes Papier, italic-serif Akzent-Spans, korallenfarbene Schluss-Dots, surreale Collage-Platten). Horizontale Magazin-Pagination mit Pfeiltasten- und Leertaste-Navigation, Live-HUD mit Slide-Zähler und Fortschrittsbalken; teilt sich Stylesheet und 16-Slot-Bildbibliothek mit der Schwester-Skill `open-design-landing`.', }, 'email-marketing': { examplePrompt: @@ -153,6 +153,18 @@ const DE_SKILL_COPY: Record = { examplePrompt: 'Erstellen Sie eine Rechnung eines freiberuflichen Designstudios an einen Kunden für ein Brand-Identity-Projekt — drei Positionen, 10% Retainer, 9% Umsatzsteuer.', }, + 'kami-deck': { + examplePrompt: + 'Erstellen Sie ein sechsteiliges Konferenz-Deck im kami-Stil (紙) — warmes Pergament, Tintenblau auf dem Cover, eine Serifenschnittstärke, horizontaler Magazin-Swipe.', + description: + 'Erzeugt ein druckreifes Slide-Deck im kami-Designsystem: warmes Pergament (oder Tintenblau auf Cover/Kapitel), Serif nur in einer Schnittstärke, Tintenblau-Akzent ≤5% pro Folie, ohne Kursiv. Horizontale Magazin-Pagination (←/→ · Rad · Wischen · ESC-Übersicht). Eine eigenständige HTML-Datei, nur Google Fonts.', + }, + 'kami-landing': { + examplePrompt: + 'Entwerfen Sie eine einseitige Studio-One-Pager im kami-Stil — Pergament-Leinwand, Tintenblau-Akzent, editorial wie ein Whitepaper.', + description: + 'Erzeugt eine druckreife Einseiter im kami-Stil (紙): warmes Pergament, Tintenblau-Akzent, Serif in einer Schnittstärke, kein Kursiv, keine kühlen Grautöne. Liest sich wie Whitepaper oder Studio-One-Pager, nicht wie App-UI. Mehrsprachig (EN · zh-CN · ja). Eine eigenständige HTML-Datei ohne Abhängigkeiten.', + }, 'kanban-board': { examplePrompt: 'Erstellen Sie ein Kanban-Board für ein 5-köpfiges Growth-Team mitten im Sprint — Backlog, Doing, Review, Done.', diff --git a/design-systems/README.md b/design-systems/README.md index 51e76af4a..4d7edf176 100644 --- a/design-systems/README.md +++ b/design-systems/README.md @@ -12,8 +12,9 @@ will read it as part of its system prompt. collage system: warm paper canvas, plaster-and-architecture imagery, oversized italic-mixed display type, Roman-numeral section markers, side rails of rotated micro-text, coordinate annotations, single - coral accent. Pairs with [`skills/editorial-collage/`](../skills/editorial-collage/) - for the canonical landing-page rendering. + coral accent. Pairs with [`skills/open-design-landing/`](../skills/open-design-landing/) + and [`skills/open-design-landing-deck/`](../skills/open-design-landing-deck/) + for the canonical landing-page and slide-deck renderings. - **`kami/`** — 紙 / 纸. Editorial paper system distilled from [`tw93/kami`](https://github.com/tw93/kami) (MIT). Warm parchment canvas, ink-blue accent, serif at one weight, no italic, no cool grays. Pairs with diff --git a/design-systems/atelier-zero/DESIGN.md b/design-systems/atelier-zero/DESIGN.md index 8c9e9dc14..616ef8d89 100644 --- a/design-systems/atelier-zero/DESIGN.md +++ b/design-systems/atelier-zero/DESIGN.md @@ -244,7 +244,7 @@ the page-of-008 counter on the right. ### Anti-patterns specific to AI-generated imagery This system is paired with `gpt-image-fal` / `gpt-image-azure` via the -editorial-collage skill. Several common image-model defaults will +open-design-landing skill. Several common image-model defaults will silently break the Atelier Zero aesthetic, so they are forbidden in every collage prompt and rejected on visual review: @@ -288,7 +288,7 @@ generated to match these constraints: dotted matrices, numbered tags. Never typography that conflicts with on-page copy. -See `skills/editorial-collage/assets/imagegen-prompts.md` for the +See `skills/open-design-landing/assets/imagegen-prompts.md` for the working prompt pack and per-section variants. All renders should be at 16:9 (heroes) or 1:1 (cards / about / cta), saved as PNG, ≥1024px on the long edge. diff --git a/skills/dating-web/SKILL.md b/skills/dating-web/SKILL.md index a70cfd9be..6218a7e69 100644 --- a/skills/dating-web/SKILL.md +++ b/skills/dating-web/SKILL.md @@ -20,7 +20,7 @@ od: mode: prototype platform: desktop scenario: personal - featured: 1 + featured: 5 preview: type: html entry: index.html diff --git a/skills/digital-eguide/SKILL.md b/skills/digital-eguide/SKILL.md index 92492a7c0..7a218b35a 100644 --- a/skills/digital-eguide/SKILL.md +++ b/skills/digital-eguide/SKILL.md @@ -22,7 +22,7 @@ od: mode: prototype platform: desktop scenario: marketing - featured: 2 + featured: 6 preview: type: html entry: index.html diff --git a/skills/editorial-collage-deck/scripts/compose.ts b/skills/editorial-collage-deck/scripts/compose.ts deleted file mode 100644 index a74c533a8..000000000 --- a/skills/editorial-collage-deck/scripts/compose.ts +++ /dev/null @@ -1,698 +0,0 @@ -#!/usr/bin/env -S npx -y tsx -/** - * editorial-collage-deck — slide deck composer. - * - * Reads `inputs.json` (matching `../schema.ts`) and writes a single - * self-contained HTML file: a scroll-snap deck where every slide - * occupies one viewport. Reuses the Atelier Zero stylesheet from the - * sister `editorial-collage` skill, then layers deck-specific rules - * (snap container, slide layout, HUD, keyboard nav). - * - * Usage: - * npx tsx scripts/compose.ts - * - * Re-generate the canonical example: - * npx tsx scripts/compose.ts inputs.example.json example.html - */ - -import { readFile, writeFile, mkdir } from 'node:fs/promises'; -import { resolve, dirname, isAbsolute } from 'node:path'; -import { fileURLToPath } from 'node:url'; -import type { - EditorialCollageDeckInputs, - Slide, - CoverSlide, - SectionSlide, - ContentSlide, - StatsSlide, - QuoteSlide, - CTASlide, - EndSlide, - MixedText, -} from '../schema'; - -const SKILL_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..'); -const SISTER_STYLES = resolve(SKILL_ROOT, '..', 'editorial-collage', 'styles.css'); - -/* ------------------------------------------------------------------ * - * helpers - * ------------------------------------------------------------------ */ - -function mixed(text: MixedText): string { - return text - .map((seg) => { - if (seg.dot) return `${seg.text}`; - if (seg.em) return `${seg.text}`; - return seg.text; - }) - .join(''); -} - -function ext(href: string): string { - return /^(https?:|mailto:|\/\/)/i.test(href) ? ` target='_blank' rel='noreferrer noopener'` : ''; -} - -const ARROW_OUT = ``; - -function imgFor(slot: string | undefined, assets: string): string { - if (!slot) return ''; - return ``; -} - -/* ------------------------------------------------------------------ * - * deck-specific stylesheet (layered on top of editorial-collage CSS) - * ------------------------------------------------------------------ */ - -const DECK_CSS = ` -/* deck container — scroll-snap pagination */ -html, body { height: 100%; } -body { overflow: hidden; } -.deck { - height: 100vh; - overflow-y: scroll; - scroll-snap-type: y mandatory; - scroll-behavior: smooth; - position: relative; -} -.slide { - height: 100vh; - scroll-snap-align: start; - scroll-snap-stop: always; - display: grid; - align-items: stretch; - position: relative; - padding: 0; - border-bottom: 1px solid var(--line-soft); - /* Clip art / oversized content so it cannot bleed into adjacent slides - * at narrow / tall viewports (1/1 aspect-ratio art often exceeds 100vh - * minus padding). */ - overflow: hidden; -} -.slide-inner { - max-width: 1360px; - margin: 0 auto; - padding: 80px 80px 64px; - width: 100%; - height: 100%; - display: grid; - align-content: center; - gap: 28px; - position: relative; - min-height: 0; -} -/* Cap art panels so they fit inside the slide minus the inner padding. */ -.s-cover .art, -.s-content .art, -.s-quote .art { - max-height: calc(100vh - 160px); - min-height: 0; -} - -/* HUD — fixed top bar + slide counter + keyboard hint */ -.deck-hud { - position: fixed; - top: 18px; - left: 0; - right: 0; - display: flex; - justify-content: space-between; - align-items: center; - padding: 0 32px; - z-index: 50; - font-family: var(--sans); - font-size: 10.5px; - letter-spacing: 0.18em; - text-transform: uppercase; - color: var(--ink-faint); - pointer-events: none; -} -.deck-hud .left { display: inline-flex; gap: 14px; align-items: center; pointer-events: auto; } -.deck-hud .right { display: inline-flex; gap: 14px; align-items: center; pointer-events: auto; } -.deck-hud .mark { - width: 24px; height: 24px; border-radius: 50%; - border: 1px solid var(--ink); display: inline-flex; - align-items: center; justify-content: center; - font-family: var(--serif); font-style: italic; font-size: 12px; - color: var(--ink); background: rgba(239,231,210,0.85); - backdrop-filter: blur(2px); -} -.deck-hud .counter { - font-family: var(--mono); - letter-spacing: 0.04em; - color: var(--ink); - background: rgba(239,231,210,0.85); - padding: 4px 8px; - border: 1px solid var(--line); - border-radius: 6px; - backdrop-filter: blur(2px); -} -.deck-hud .keys { - background: rgba(239,231,210,0.85); - padding: 4px 10px; - border: 1px solid var(--line); - border-radius: 6px; - backdrop-filter: blur(2px); -} - -/* progress bar at bottom */ -.deck-progress { - position: fixed; - left: 0; right: 0; bottom: 0; - height: 2px; - background: var(--line-soft); - z-index: 50; -} -.deck-progress .bar { - height: 100%; - background: var(--coral); - width: 0%; - transition: width 240ms ease; -} - -/* ---------- COVER slide ---------- */ -.s-cover .slide-inner { - grid-template-columns: 1.1fr 0.9fr; - align-content: center; - gap: 60px; -} -.s-cover .copy { - display: flex; flex-direction: column; gap: 22px; -} -.s-cover .eyebrow { - font-family: var(--sans); font-size: 11px; font-weight: 600; - letter-spacing: 0.22em; text-transform: uppercase; - color: var(--coral); display: inline-flex; align-items: center; gap: 12px; -} -.s-cover .eyebrow::before { - content: ''; width: 18px; height: 1px; - background: var(--coral); display: inline-block; -} -.s-cover h1 { - font-family: var(--sans); - font-weight: 800; - font-size: clamp(40px, 5.2vw, 80px); - line-height: 1.02; - letter-spacing: -0.028em; - color: var(--ink); - margin: 0; -} -.s-cover h1 em { - font-family: var(--serif); - font-style: italic; font-weight: 500; - letter-spacing: -0.018em; -} -.s-cover h1 .dot { color: var(--coral); } -.s-cover .subtitle { - font-family: var(--serif); font-style: italic; font-weight: 500; - font-size: 22px; color: var(--ink-soft); margin-top: -8px; -} -.s-cover .lead { - font-family: var(--body); font-size: 17px; - color: var(--ink-soft); max-width: 42ch; line-height: 1.55; -} -.s-cover .meta { - margin-top: 32px; - font-family: var(--mono); font-size: 11px; letter-spacing: 0.06em; - color: var(--ink-faint); -} -.s-cover .art { - position: relative; aspect-ratio: 1 / 1; max-width: 620px; - margin-left: auto; margin-right: 0; - border: 1px solid var(--line-soft); border-radius: 14px; - overflow: hidden; background: var(--bone); -} -.s-cover .art img { width: 100%; height: 100%; object-fit: contain; } - -/* ---------- SECTION divider slide ---------- */ -.s-section .slide-inner { - grid-template-columns: 1fr; - align-content: center; - text-align: center; - gap: 32px; -} -.s-section .roman { - font-family: var(--serif); font-style: italic; font-weight: 500; - font-size: clamp(80px, 10vw, 160px); - color: var(--coral); line-height: 1; letter-spacing: -0.02em; -} -.s-section h2 { - font-family: var(--sans); font-weight: 800; - font-size: clamp(54px, 6.6vw, 100px); - letter-spacing: -0.028em; line-height: 1.0; color: var(--ink); - max-width: 18ch; margin: 0 auto; -} -.s-section h2 em { - font-family: var(--serif); font-style: italic; font-weight: 500; -} -.s-section h2 .dot { color: var(--coral); } -.s-section .lead { - font-family: var(--body); font-size: 17px; - color: var(--ink-soft); max-width: 50ch; margin: 0 auto; -} - -/* ---------- CONTENT slide ---------- */ -.s-content .slide-inner { gap: 48px; } -.s-content.layout-left .slide-inner { grid-template-columns: 1fr 0.9fr; } -.s-content.layout-right .slide-inner { grid-template-columns: 0.9fr 1fr; } -.s-content.layout-right .copy { order: 2; } -.s-content.layout-right .art { order: 1; } -.s-content.layout-full .slide-inner { grid-template-columns: 1fr; max-width: 980px; } -.s-content .copy { display: flex; flex-direction: column; gap: 22px; } -.s-content .eyebrow { - font-family: var(--sans); font-size: 11px; font-weight: 600; - letter-spacing: 0.22em; text-transform: uppercase; color: var(--coral); - display: inline-flex; align-items: center; gap: 12px; -} -.s-content .eyebrow::before { - content: ''; width: 18px; height: 1px; - background: var(--coral); display: inline-block; -} -.s-content h2 { - font-family: var(--sans); font-weight: 800; - font-size: clamp(40px, 4.6vw, 64px); - letter-spacing: -0.024em; line-height: 1.05; - color: var(--ink); margin: 0; -} -.s-content h2 em { font-family: var(--serif); font-style: italic; font-weight: 500; } -.s-content h2 .dot { color: var(--coral); } -.s-content .body { - font-family: var(--body); font-size: 16px; - color: var(--ink-soft); max-width: 56ch; line-height: 1.55; -} -.s-content .body code { font-family: var(--mono); font-size: 14px; background: var(--bone); padding: 1px 6px; border-radius: 4px; } -.s-content ul { - list-style: none; padding: 0; margin: 0; - display: flex; flex-direction: column; gap: 12px; -} -.s-content li { - font-family: var(--sans); font-size: 15px; - color: var(--ink-soft); display: flex; gap: 14px; align-items: flex-start; -} -.s-content li::before { - content: ''; width: 12px; height: 1px; - background: var(--coral); margin-top: 11px; flex-shrink: 0; -} -.s-content .art { - position: relative; aspect-ratio: 1 / 1; - border: 1px solid var(--line-soft); border-radius: 14px; - overflow: hidden; background: var(--bone); -} -.s-content .art img { width: 100%; height: 100%; object-fit: contain; } - -/* ---------- STATS slide ---------- */ -.s-stats .slide-inner { grid-template-columns: 1fr; gap: 60px; } -.s-stats .head { display: flex; flex-direction: column; gap: 22px; } -.s-stats .eyebrow { - font-family: var(--sans); font-size: 11px; font-weight: 600; - letter-spacing: 0.22em; text-transform: uppercase; color: var(--coral); - display: inline-flex; align-items: center; gap: 12px; -} -.s-stats .eyebrow::before { content: ''; width: 18px; height: 1px; background: var(--coral); display: inline-block; } -.s-stats h2 { - font-family: var(--sans); font-weight: 800; - font-size: clamp(44px, 5vw, 72px); - letter-spacing: -0.026em; line-height: 1.05; max-width: 18ch; margin: 0; -} -.s-stats h2 em { font-family: var(--serif); font-style: italic; font-weight: 500; } -.s-stats h2 .dot { color: var(--coral); } -.s-stats .grid { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); - gap: 36px; - border-top: 1px solid var(--line); - padding-top: 36px; -} -.s-stats .stat { display: flex; flex-direction: column; gap: 10px; } -.s-stats .stat .num { - font-family: var(--sans); font-weight: 800; - font-size: clamp(80px, 9vw, 140px); line-height: 1; - letter-spacing: -0.04em; color: var(--ink); -} -.s-stats .stat .num em { color: var(--coral); font-family: var(--serif); font-style: italic; font-weight: 500; } -.s-stats .stat .label { - font-family: var(--sans); font-size: 12px; - letter-spacing: 0.18em; text-transform: uppercase; - color: var(--ink); font-weight: 600; -} -.s-stats .stat .sub { - font-family: var(--body); font-size: 13px; - color: var(--ink-mute); max-width: 26ch; line-height: 1.5; -} -.s-stats .caption { - font-family: var(--mono); font-size: 11px; - color: var(--ink-faint); letter-spacing: 0.04em; -} - -/* ---------- QUOTE slide ---------- */ -.s-quote .slide-inner { grid-template-columns: 1.4fr 0.8fr; gap: 60px; align-items: center; } -.s-quote.no-art .slide-inner { grid-template-columns: 1fr; max-width: 980px; } -.s-quote blockquote { - font-family: var(--sans); font-weight: 700; - font-size: clamp(36px, 4vw, 56px); - letter-spacing: -0.022em; line-height: 1.15; - color: var(--ink); margin: 0; - position: relative; -} -.s-quote blockquote em { font-family: var(--serif); font-style: italic; font-weight: 500; } -.s-quote .author { - margin-top: 38px; display: flex; align-items: center; gap: 16px; -} -.s-quote .author .avatar { - width: 48px; height: 48px; border-radius: 50%; background: var(--ink); - color: var(--paper); font-family: var(--serif); font-style: italic; font-size: 22px; - display: inline-flex; align-items: center; justify-content: center; -} -.s-quote .author p { font-family: var(--sans); font-size: 14px; font-weight: 600; } -.s-quote .author p span { display: block; color: var(--ink-mute); font-weight: 400; } -.s-quote .art { - position: relative; aspect-ratio: 1 / 1; - border: 1px solid var(--line-soft); border-radius: 14px; - overflow: hidden; background: var(--bone); -} -.s-quote .art img { width: 100%; height: 100%; object-fit: contain; } - -/* ---------- CTA slide ---------- */ -.s-cta .slide-inner { grid-template-columns: 1fr; max-width: 980px; gap: 32px; text-align: left; } -.s-cta .eyebrow { - font-family: var(--sans); font-size: 11px; font-weight: 600; - letter-spacing: 0.22em; text-transform: uppercase; color: var(--coral); - display: inline-flex; align-items: center; gap: 12px; -} -.s-cta .eyebrow::before { content: ''; width: 18px; height: 1px; background: var(--coral); display: inline-block; } -.s-cta h2 { - font-family: var(--sans); font-weight: 800; - font-size: clamp(54px, 6.4vw, 96px); - letter-spacing: -0.028em; line-height: 1.0; color: var(--ink); margin: 0; -} -.s-cta h2 em { font-family: var(--serif); font-style: italic; font-weight: 500; } -.s-cta h2 .dot { color: var(--coral); } -.s-cta .body { font-family: var(--body); font-size: 17px; color: var(--ink-soft); max-width: 50ch; line-height: 1.55; } -.s-cta .actions { display: inline-flex; gap: 14px; margin-top: 12px; } - -/* ---------- END slide ---------- */ -.s-end .slide-inner { - grid-template-columns: 1fr; - align-content: end; - padding-bottom: 72px; - text-align: left; - gap: 16px; - max-width: none; - padding-left: 64px; padding-right: 64px; -} -.s-end .word { - font-family: var(--sans); font-weight: 900; - font-size: clamp(80px, 14vw, 220px); - letter-spacing: -0.04em; line-height: 1.05; - color: var(--ink); white-space: nowrap; - overflow-x: hidden; - padding-bottom: 0.18em; -} -.s-end .word em { font-family: var(--serif); font-style: italic; font-weight: 500; color: var(--coral); } -.s-end .footer { - border-top: 1px solid var(--line); - padding-top: 22px; - font-family: var(--sans); font-size: 11px; - letter-spacing: 0.18em; text-transform: uppercase; - color: var(--ink-faint); -} - -/* responsive */ -@media (max-width: 1080px) { - .slide-inner { padding: 48px 56px; } - .s-cover .slide-inner, - .s-content.layout-left .slide-inner, - .s-content.layout-right .slide-inner, - .s-quote .slide-inner { grid-template-columns: 1fr; gap: 36px; } - .s-content.layout-right .copy { order: 1; } - .s-content.layout-right .art { order: 2; } -} -@media (max-width: 640px) { - .slide-inner { padding: 36px 24px; } - .deck-hud { padding: 0 16px; font-size: 9.5px; letter-spacing: 0.14em; } - .deck-hud .keys { display: none; } -} -`; - -/* ------------------------------------------------------------------ * - * slide renderers - * ------------------------------------------------------------------ */ - -function renderCover(s: CoverSlide, assets: string): string { - return `
-
-
- ${s.eyebrow} -

${mixed(s.title)}

- ${s.subtitle ? `
${s.subtitle}
` : ''} -

${s.lead}

- ${s.meta ? `
${s.meta}
` : ''} -
-
${imgFor(s.image_slot, assets)}
-
-
`; -} - -function renderSection(s: SectionSlide): string { - return `
-
-
${s.roman}
-

${mixed(s.title)}

- ${s.lead ? `

${s.lead}

` : ''} -
-
`; -} - -function renderContent(s: ContentSlide, assets: string): string { - const layout = s.layout ?? 'left'; - const hasArt = !!s.image_slot; - return `
-
-
- ${s.eyebrow ? `${s.eyebrow}` : ''} -

${mixed(s.title)}

- ${s.body ? `

${s.body}

` : ''} - ${s.bullets && s.bullets.length ? `
    ${s.bullets.map((b) => `
  • ${b}
  • `).join('')}
` : ''} -
- ${hasArt ? `
${imgFor(s.image_slot, assets)}
` : ''} -
-
`; -} - -function renderStats(s: StatsSlide): string { - const stats = s.stats - .map( - (st) => - `
-
${st.value}
-
${st.label}
- ${st.sub ? `
${st.sub}
` : ''} -
`, - ) - .join('\n '); - return `
-
-
- ${s.eyebrow ? `${s.eyebrow}` : ''} -

${mixed(s.title)}

-
-
- ${stats} -
- ${s.caption ? `
${s.caption}
` : ''} -
-
`; -} - -function renderQuote(s: QuoteSlide, assets: string): string { - const hasArt = !!s.image_slot; - return `
-
-
-
“${mixed(s.quote)}”
-
- ${s.author.initial} -

${s.author.name}
${s.author.title}

-
-
- ${hasArt ? `
${imgFor(s.image_slot, assets)}
` : ''} -
-
`; -} - -function renderCTA(s: CTASlide): string { - return `
-
- ${s.eyebrow ? `${s.eyebrow}` : ''} -

${mixed(s.title)}

- ${s.body ? `

${s.body}

` : ''} - -
-
`; -} - -function renderEnd(s: EndSlide): string { - return `
-
-
${mixed(s.mega)}
- ${s.footer ? `` : ''} -
-
`; -} - -function renderSlide(s: Slide, assets: string): string { - switch (s.kind) { - case 'cover': return renderCover(s, assets); - case 'section': return renderSection(s); - case 'content': return renderContent(s, assets); - case 'stats': return renderStats(s); - case 'quote': return renderQuote(s, assets); - case 'cta': return renderCTA(s); - case 'end': return renderEnd(s); - } -} - -/* ------------------------------------------------------------------ * - * runtime script (keyboard nav + counter + progress) - * ------------------------------------------------------------------ */ - -const RUNTIME_SCRIPT = ` -`; - -/* ------------------------------------------------------------------ * - * top-level - * ------------------------------------------------------------------ */ - -export function renderDeck(inputs: EditorialCollageDeckInputs, baseCss: string): string { - const assets = inputs.imagery.assets_path.replace(/\/?$/, '/'); - const slides = inputs.slides.map((s) => renderSlide(s, assets)).join('\n '); - const total = inputs.slides.length; - return [ - ``, - ``, - ``, - ``, - ``, - `${inputs.deck_title}`, - ``, - ``, - ``, - ``, - ``, - ``, - ``, - `
`, - `
`, - ` ${inputs.brand.mark}`, - ` ${inputs.deck_title}`, - `
`, - `
`, - ` ← / → · Space`, - ` 01 / ${String(total).padStart(2, '0')}`, - `
`, - `
`, - `
`, - ` ${slides}`, - `
`, - `
`, - RUNTIME_SCRIPT, - ``, - ``, - ``, - ].join('\n'); -} - -async function main(): Promise { - const [, , inputsArg, outputArg] = process.argv; - if (!inputsArg || !outputArg) { - console.error('Usage: npx tsx scripts/compose.ts '); - process.exit(1); - } - - const inputsPath = isAbsolute(inputsArg) ? inputsArg : resolve(process.cwd(), inputsArg); - const outputPath = isAbsolute(outputArg) ? outputArg : resolve(process.cwd(), outputArg); - - const [inputsRaw, css] = await Promise.all([ - readFile(inputsPath, 'utf8'), - readFile(SISTER_STYLES, 'utf8'), - ]); - const inputs = JSON.parse(inputsRaw) as EditorialCollageDeckInputs; - const html = renderDeck(inputs, css); - - await mkdir(dirname(outputPath), { recursive: true }); - await writeFile(outputPath, html, 'utf8'); - console.log( - `✓ wrote ${outputPath} (${(html.length / 1024).toFixed(1)} KB, ${inputs.slides.length} slides)`, - ); -} - -const isMain = import.meta.url === `file://${process.argv[1]}`; -if (isMain) { - main().catch((err) => { - console.error(err); - process.exit(1); - }); -} diff --git a/skills/email-marketing/SKILL.md b/skills/email-marketing/SKILL.md index 2ddd8805d..bfeff8098 100644 --- a/skills/email-marketing/SKILL.md +++ b/skills/email-marketing/SKILL.md @@ -19,7 +19,7 @@ od: mode: prototype platform: desktop scenario: marketing - featured: 3 + featured: 7 preview: type: html entry: index.html diff --git a/skills/gamified-app/SKILL.md b/skills/gamified-app/SKILL.md index e78f135f0..e24051fae 100644 --- a/skills/gamified-app/SKILL.md +++ b/skills/gamified-app/SKILL.md @@ -22,7 +22,7 @@ od: mode: prototype platform: mobile scenario: personal - featured: 4 + featured: 12 preview: type: html entry: index.html diff --git a/skills/hatch-pet/SKILL.md b/skills/hatch-pet/SKILL.md index 1ceceba1e..70071fd27 100644 --- a/skills/hatch-pet/SKILL.md +++ b/skills/hatch-pet/SKILL.md @@ -13,7 +13,7 @@ od: mode: image surface: image scenario: personal - featured: 3 + featured: 11 preview: type: image entry: final/spritesheet.png diff --git a/skills/kami-deck/README.md b/skills/kami-deck/README.md new file mode 100644 index 000000000..88ce9306b --- /dev/null +++ b/skills/kami-deck/README.md @@ -0,0 +1,72 @@ +# kami-deck + +Sister skill to [`kami-landing`](../kami-landing/). Produces a single +self-contained HTML file: a horizontal magazine-style swipe deck in +the **kami (紙 / 纸)** design system — print rhythm, ink-blue accent, +serif at one weight, no italic, no cool grays. + +> **Read first** — agent contract, schema, and self-check live in +> [`SKILL.md`](./SKILL.md). This README is the human quick-start. + +## What you get + +- N viewport-sized slides laid out horizontally on a transformed + flex track. +- **Cover / chapter / end slides** flip background to ink-blue + (`#1B365D`) with ivory text. **All other slides** stay on + parchment (`#f5f4ed`) with serif at weight 500. +- **Per-slide chrome strip**: brand mark · deck title · live + slide counter (`01 / 09`). +- **Tabular-nums** on every counter, metric, and date. +- **Ink-blue progress bar** at the bottom that fills as you advance. +- **Dot indicator** near the bottom; click to jump. +- **ESC overview grid** with scaled thumbnails. +- **Keyboard / wheel / touch nav** — same model as `guizang-ppt`. +- **Multilingual stack** — EN / zh-CN / ja, set on `:root` via the + `language` parameter. + +## 30-second tour + +The skill is "agent-driven, no script": there's no `compose.ts`. The +agent reads `SKILL.md`, gathers the brief, then writes +`out/index.html` directly using the tokens from +[`design-systems/kami/DESIGN.md`](../../design-systems/kami/DESIGN.md) +and the layout primitives in [`example.html`](./example.html). + +To preview the canonical Open Design instance: + +```bash +open example.html +``` + +To start a fresh project: + +1. Open the skill in your agent (Claude · Cursor · Codex · …). +2. Answer two rounds of brief questions (identity + content). +3. Write the file. Done. + +## Files + +```text +skills/kami-deck/ +├── SKILL.md # ← agent contract (read this first) +├── README.md # ← you are here +└── example.html # canonical Open Design rendering (9 slides) +``` + +## Boundaries + +- No second accent color. No italic. No cool blue-grays. No hard + drop shadows. +- One self-contained HTML file. No router, no external JS bundle. +- Cover / chapter / end slides only — no other slide kind goes dark. +- Tag fills must be solid hex (kami's print invariant), not `rgba()`. + +## See also + +- [`kami-landing`](../kami-landing/) — long-form one-pager sister. +- [`design-systems/kami/DESIGN.md`](../../design-systems/kami/DESIGN.md) — token spec. +- [`open-design-landing-deck`](../open-design-landing-deck/) — same + swipe nav model, different visual language (Atelier Zero). +- Upstream: [`tw93/kami`](https://github.com/tw93/kami) — original + Claude skill (MIT) the design system adapts. diff --git a/skills/kami-deck/SKILL.md b/skills/kami-deck/SKILL.md new file mode 100644 index 000000000..7808acad1 --- /dev/null +++ b/skills/kami-deck/SKILL.md @@ -0,0 +1,196 @@ +--- +name: kami-deck +description: > + Produce a print-grade slide deck in the kami (紙 / 纸) design system — + warm parchment background (or ink-blue for cover / chapter slides), + serif at one weight, ink-blue accent ≤ 5% per slide, no italic. + Horizontal magazine swipe pagination (←/→ · wheel · swipe · ESC + overview). One self-contained HTML file, zero dependencies beyond + Google Fonts. +triggers: + - kami deck + - 紙 deck + - 纸 deck + - paper slides + - white paper deck + - editorial deck + - print-style slides + - kami slides +od: + category: brand-deck + surface: web + mode: deck + scenario: marketing + featured: 4 + audience: founders, researchers, design studios, conference talks + tone: editorial, restrained, print-first + scale: 6-15 viewport-locked slides + preview: + type: html + entry: index.html + design_system: + requires: false + craft: + requires: + - typographic-rhythm + - pixel-discipline +inputs: + - id: brand + label: Brand identity (shared across slides) + - id: deck_title + label: Deck title shown in the per-slide chrome + - id: slides + label: Ordered list of typed slides (cover · chapter · content · stats · quote · cta · end) + - id: language + label: Primary language stack +parameters: + language: + type: enum + values: [en, zh-CN, ja] + default: en + description: Sets `--serif` to Charter / TsangerJinKai02 / YuMincho respectively. +outputs: + - path: /index.html + description: Self-contained kami deck with horizontal swipe pagination. +capabilities_required: + - file-write +example_prompt: | + Build me a 9-slide kami-style internal deck for "Hokuto Research" — + a Q1 portfolio review. Cover slide on ink-blue with the firm name. + Chapter dividers between Macro, Equities, and Outlook. Three content + slides with ink-blue numbered headings. One stats slide showing + AUM / IRR / fund count. One closing CTA. End card with the firm + signature. Japanese language stack. +--- + +# kami-deck + +Sister skill to [`kami-landing`](../kami-landing/). Produces a single +self-contained HTML file: a horizontal magazine-style swipe deck in +the **kami (紙 / 纸)** design system — print rhythm, ink-blue accent, +serif at one weight, no italic, no cool grays. + +The navigation model is intentionally borrowed from the +[`guizang-ppt`](../guizang-ppt/) skill — `←/→` arrow keys, wheel / +swipe, ESC for the overview grid. The aesthetic stays kami: parchment +content slides, ink-blue cover and chapter slides, serif everywhere. + +> **Design system source of truth:** +> [`design-systems/kami/DESIGN.md`](../../design-systems/kami/DESIGN.md). +> Read it before shipping. Tokens, type rules, and forbidden colors +> all live there. Slide-specific scale ratios (macro × 1.6, +> letter-spacing × 0.6 vs. print) are documented in §3 "Hierarchy" +> and §5 "Layout Principles · Slides". + +## What you get + +- N viewport-sized slides (6-15 is the sweet spot) laid out + horizontally on one transformed flex track. +- **Cover and chapter slides** flip background to ink-blue + (`#1B365D`) with ivory text — the only place dark theme is used. +- **Content / stats / quote / CTA slides** stay on parchment + (`#f5f4ed`) with serif at weight 500. +- **Per-slide chrome strip**: brand mark · deck title · live slide + counter (`01 / 09`). +- **Tabular-nums** on every counter, metric, page number. +- **Coral-free** — kami's accent is ink-blue. Progress bar and dot + nav are ink-blue too. +- **Keyboard / wheel / touch nav**, ESC overview grid, dot indicator. +- **Multilingual stack** — EN / zh-CN / ja, set on `:root` via + the `language` parameter. + +## Slide types + +| Kind | Background | Use it for | +| :---------- | :--------- | :-------------------------------------------------------- | +| `cover` | ink-blue | Title plate at the start. Centered serif title + tagline. | +| `chapter` | ink-blue | Roman/Arabic numeral chapter divider. | +| `content` | parchment | Section number + title + body + optional bullets. | +| `stats` | parchment | 3-4 metric cells (value · label · sub). | +| `quote` | parchment | Pull quote with ink-blue left rule + author signature. | +| `cta` | parchment | Closing pitch + 1-2 buttons. | +| `end` | ink-blue | Mega serif kicker word + colophon footer. | + +A typical 11-slide deck: + +``` +1. cover — ink-blue title plate +2. chapter — "01 / Why now" +3. content — manifesto +4. content — capabilities + bullets +5. stats — 4 numbers +6. chapter — "02 / How it feels" +7. content — method +8. content — selected work +9. quote — testimonial +10. cta — primary action +11. end — ink-blue kicker +``` + +## Workflow + +### 1. Gather the brief + +Ask in two rounds (don't dump the whole list at once): + +1. Identity round — name, mark, tagline, location, edition, language. +2. Content round — for each slide, kind + the typed fields. + +### 2. Pick the language stack + +Same as [`kami-landing`](../kami-landing/SKILL.md#2-pick-the-language-stack): +EN → Charter, zh-CN → TsangerJinKai02 / Source Han Serif, ja → +YuMincho. JA also overrides `--olive` to `#4d4c48` because YuMincho +strokes are thinner. + +### 3. Write `index.html` + +Output a single file with all CSS inline. Mirror the structure of +[`example.html`](./example.html). Use only the tokens from +`design-systems/kami/DESIGN.md`. + +The runtime script (keyboard / wheel / touch nav, dot indicator, +progress bar, ESC overview) should match the model documented in +[`open-design-landing-deck/scripts/compose.ts`](../open-design-landing-deck/scripts/compose.ts). +Do **not** reuse the open-design-landing-deck CSS; the visual +language is different. + +### 4. Self-check + +- [ ] All cover / chapter / end slides use ink-blue background + (`#1B365D`) with ivory text. All other slides are on + parchment. +- [ ] Ink-blue covers ≤ 5% of any parchment slide's surface. +- [ ] Slide titles use serif weight 500 only. No italic. +- [ ] All numeric stacks (counter, metrics, page numbers) carry + `font-variant-numeric: tabular-nums`. +- [ ] Press `→` / `Space` / scroll. Smoothly slides one viewport + to the right; dot nav advances; the ink-blue progress bar + ticks forward. +- [ ] Press `Esc`. Overview grid appears with scaled thumbnails. +- [ ] Resize to 1080px and 640px. Cover / content collapse to a + single column; dot nav still works. +- [ ] Lighthouse: contrast AA, font-display swap, no layout shift. + +## Boundaries + +- **Do not** introduce a second accent color. Pick ink-blue or + pick nothing. +- **Do not** use italic anywhere — emphasis swaps to ink-blue. +- **Do not** use `rgba()` for tag fills; pre-blend over parchment + and use solid hex from the table in + `design-systems/kami/DESIGN.md` §2. +- **Do not** add a router. This is a single-file artifact. +- **Do not** reuse Atelier Zero collage imagery (the open-design-landing + visual system). Kami is gradient-free, image-light, and hierarchy + is carried by type. + +## See also + +- [`kami-landing`](../kami-landing/) — long-form one-pager sister skill. +- [`design-systems/kami/DESIGN.md`](../../design-systems/kami/DESIGN.md) — token spec. +- [`open-design-landing-deck`](../open-design-landing-deck/) — same + horizontal swipe nav model, different visual language (Atelier Zero). +- Upstream: [`tw93/kami`](https://github.com/tw93/kami) — original + Claude skill (MIT). Kami's slides.py template documents the macro + × 1.6 / micro × 0.6 ratios this skill applies. diff --git a/skills/kami-deck/example.html b/skills/kami-deck/example.html new file mode 100644 index 000000000..6281a05fe --- /dev/null +++ b/skills/kami-deck/example.html @@ -0,0 +1,1101 @@ + + + + + +Open Design · kami deck — Vol. 01 / Issue Nº 26 + + + + + + + + +
+ + +
+
+ kami Open Design · Vol. 01 / Issue Nº 26 + Open Design · kami deck +
+
+ Open-source design studio · Nº 01 +

Designing intelligence on warm paper.

+

The open-source studio for editorial documents and slide decks — typeset by your own coding agent.

+
+ Berlin · 52.5200° N · 13.4050° E + + MMXXVI · Apache-2.0 +
+
+
+ Berlin · MMXXVI + 01 / 09 +
+
+ + +
+
+ kami Open Design · Vol. 01 / Issue Nº 26 + Open Design · kami deck +
+
+

01

+

Why design needs another tool.

+

Because the strongest agents already live on your laptop — and they deserve a real workflow, not a chat window.

+
+
+ Berlin · MMXXVI + 02 / 09 +
+
+ + +
+
+ kami Open Design · Vol. 01 / Issue Nº 26 + Open Design · kami deck +
+
+
+

01.1

+

What it is.

+

A local-first design studio for the agent you already trust.

+
+
+

+ Open Design is the open-source alternative to Anthropic's Claude Design. It runs on your laptop. Your agent reads a folder of SKILL.md files and a folder of DESIGN.md systems, then produces real files — landing pages, decks, white papers, dashboards. +

+
    +
  • Files, not opaque prompts — every skill is a folder of Markdown.
  • +
  • Deterministic visual directions, not random generation.
  • +
  • Sandboxed iframe preview, real cwd, exportable artifacts.
  • +
+
+ Apache-2.0 + Local-first + BYOK +
+
+
+
+ Berlin · MMXXVI + 03 / 09 +
+
+ + +
+
+ kami Open Design · Vol. 01 / Issue Nº 26 + Open Design · kami deck +
+
+
+

01.2

+

How it feels.

+

Editorial discipline, not chat-window improvisation.

+
+
+

+ A new project starts with a 30-second question form: brand, audience, scale, language. The agent picks one of five visual directions, locks the type stack, and writes the artifact to disk. You can read every file it touched. +

+

+ Every iteration is reviewed in a sandboxed iframe with comment-mode anchors on every editable element. Re-runs are deterministic — same brief, same output. +

+
    +
  • Brief → 30s question form locks brand + audience + scale.
  • +
  • Direction → 5 visual directions in OKLch + locked type stack.
  • +
  • Artifact → real file on disk, sandboxed preview, comment anchors.
  • +
+
+
+
+ Berlin · MMXXVI + 04 / 09 +
+
+ + +
+
+ kami Open Design · Vol. 01 / Issue Nº 26 + Open Design · kami deck +
+
+
+

01.3

+

By the numbers.

+
+
+
+
31
+
Skills
+
file-based, shippable today, drop-in compatible.
+
+
+
72
+
Design systems
+
portable DESIGN.md tokens — Linear, Vercel, Stripe, kami…
+
+
+
12
+
Agent CLIs
+
auto-detected on your $PATH; switch backends instantly.
+
+
+
3
+
Commands
+
from git clone to first artifact, locally.
+
+
+

Open Design v0.2.0 · Apache-2.0 · MMXXVI · figures as of Issue Nº 26.

+
+
+ Berlin · MMXXVI + 05 / 09 +
+
+ + +
+
+ kami Open Design · Vol. 01 / Issue Nº 26 + Open Design · kami deck +
+
+

02

+

What ships next.

+

Q2 2026 — packaging, multi-tenant tokens, daemon hardening. The roadmap is public.

+
+
+ Berlin · MMXXVI + 06 / 09 +
+
+ + +
+
+ kami Open Design · Vol. 01 / Issue Nº 26 + Open Design · kami deck +
+
+
+ Open Design helped us turn vague AI ideas into a visual system that felt sharp, believable, and genuinely new — without ever opening a chat window. +
+
+ m +

+ Mina Kovac + Creative Director · North Form, Berlin +

+
+
+
+ Berlin · MMXXVI + 07 / 09 +
+
+ + +
+
+ kami Open Design · Vol. 01 / Issue Nº 26 + Open Design · kami deck +
+
+ Start a conversation · Nº 03 +

Let's build something open and visually unforgettable.

+

+ Star the repo on GitHub, drop into the issues, or run pnpm tools-dev tonight. Three commands and the loop is yours. +

+ +
+
+ Berlin · MMXXVI + 08 / 09 +
+
+ + +
+
+ kami Open Design · Vol. 01 / Issue Nº 26 + Open Design · kami deck +
+
+
Open Design.
+

Apache-2.0 · MMXXVI · Berlin · 52.5200° N · 13.4050° E · Composed in kami

+
+
+ Berlin · MMXXVI + 09 / 09 +
+
+ +
+ + +
← / → · esc · swipe
+
+ + + + diff --git a/skills/kami-landing/README.md b/skills/kami-landing/README.md new file mode 100644 index 000000000..f306ab2a3 --- /dev/null +++ b/skills/kami-landing/README.md @@ -0,0 +1,69 @@ +# kami-landing + +A drop-in skill that turns a brief into a print-grade kami one-pager — +warm parchment canvas, ink-blue accent, serif at one weight, no +italic, no cool grays. The output reads like a white paper or studio +one-pager, not an app UI. + +> **Read first** — the agent contract, schema, and self-check live in +> [`SKILL.md`](./SKILL.md). This README is the human quick-start. + +## What you get + +A single self-contained HTML file with: + +- **Warm parchment canvas** (`#f5f4ed`), never `#ffffff`. +- **Single chromatic accent** — ink-blue (`#1B365D`), constrained to + ≤ 5% of visible surface. +- **Serif at weight 500** for hierarchy. No italic anywhere. +- **Tight print rhythm** — line-heights 1.10–1.55, language-aware + letter-spacing. +- **Tabular-nums** on every numeric stack. +- **Solid-hex tag fills** (no `rgba()`, which print renderers + double-paint). +- **1px rings + whisper shadows** for depth — no hard drop shadows. +- **Multilingual** by design (EN / zh-CN / ja stacks selectable via + the `language` parameter). + +## 30-second tour + +The skill is "agent-driven, no script": there's no `compose.ts`. The +agent reads `SKILL.md`, gathers the brief, then writes +`out/index.html` directly using the tokens and components catalogued +in [`design-systems/kami/DESIGN.md`](../../design-systems/kami/DESIGN.md). + +To preview the canonical Open Design instance: + +```bash +open example.html +``` + +To start a fresh project: + +1. Open the skill in your agent (Claude · Cursor · Codex · …). +2. Answer two rounds of brief questions (identity + content). +3. Write the file. Done. + +## Files + +```text +skills/kami-landing/ +├── SKILL.md # ← agent contract (read this first) +├── README.md # ← you are here +└── example.html # canonical Open Design rendering +``` + +## Boundaries + +- No external JavaScript. The page is paper, not an app. +- No hard drop shadows, no neumorphism, no `backdrop-filter`. +- No second accent color. No italic. No cool blue-grays. +- One `.tag.brush` per page maximum (it's the only sanctioned gradient). + +## See also + +- [`design-systems/kami/DESIGN.md`](../../design-systems/kami/DESIGN.md) — the full token spec. +- [`skills/kami-deck/`](../kami-deck/) — sibling skill that produces a + slide deck in the same kami language. +- Upstream: [`tw93/kami`](https://github.com/tw93/kami) — original + Claude skill (MIT) that the design system adapts. diff --git a/skills/kami-landing/SKILL.md b/skills/kami-landing/SKILL.md new file mode 100644 index 000000000..9430399db --- /dev/null +++ b/skills/kami-landing/SKILL.md @@ -0,0 +1,234 @@ +--- +name: kami-landing +description: > + Produce a print-grade single-page kami (紙 / 纸) document — warm + parchment canvas, ink-blue accent, serif at one weight, no italic, + no cool grays. The output reads like a professional white paper or + studio one-pager, not an app UI. Multilingual by design (EN · + zh-CN · ja). One self-contained HTML file, zero dependencies. +triggers: + - kami + - 紙 + - 纸 + - paper one-pager + - 白皮书 + - white paper + - parchment landing + - editorial document + - print-grade page + - kami landing +od: + category: brand-page + surface: web + mode: prototype + platform: desktop + scenario: marketing + featured: 3 + audience: founders, design studios, OSS maintainers, researchers + tone: editorial, restrained, print-first + scale: viewport-anchored long-form single page + preview: + type: html + entry: index.html + reload: debounce-100 + design_system: + requires: false + craft: + requires: + - typographic-rhythm + - pixel-discipline +inputs: + - id: brand + label: Brand identity + description: Name, tagline, location, edition / version, primary URL. + - id: hero + label: Hero / cover block + description: Eyebrow + headline (one line, ≤ 6 words at display size) + tagline + 3 hero meta tokens. + - id: manifesto + label: Manifesto paragraph + signature + - id: metrics + label: 3-6 metric tiles (value · label · sub) + - id: chapters + label: 3-5 numbered chapters (title + lede + body) + - id: footer + label: License · year · contact + 3-column site index +parameters: + output_format: + type: enum + values: [standalone-html] + default: standalone-html + language: + type: enum + values: [en, zh-CN, ja] + default: en + description: > + Sets the primary serif stack on `:root`. EN uses Charter, + zh-CN uses TsangerJinKai02 / Source Han Serif, ja uses + YuMincho. Mixed-script content is allowed inline; the browser + resolves per-glyph fallback automatically. +outputs: + - path: /index.html + description: Self-contained HTML, kami CSS inlined, zero JS, zero external dependencies beyond Google Fonts. +capabilities_required: + - file-write +example_prompt: | + Build me a kami-style one-pager for "Lumen Field", an indie studio + shipping a soundscape app for focus. Hero headline "Soundscapes for + focused work.", manifesto paragraph + signature "by Lumen Field, + Berlin", 3 metric tiles (12 soundscapes / 4 presets / 1 daily ritual), + three numbered chapters covering the studio, the app, and the roadmap. + English-language stack. +--- + +# kami-landing + +Produce a single-page document in the **kami (紙 / 纸)** design system. +The aesthetic borrows from editorial print, technical white papers, +and old typewritten correspondence — the goal is *good content on +good paper*, not *modern app UI*. + +> **Design system source of truth:** [`design-systems/kami/DESIGN.md`](../../design-systems/kami/DESIGN.md). +> Read it before shipping. Tokens, type rules, the "ten invariants", +> and forbidden colors all live there. + +## What you get + +A single self-contained HTML file with: + +- **Warm parchment canvas** (`#f5f4ed`) — never `#ffffff`. +- **Single chromatic accent** — ink-blue (`#1B365D`), used on the + section number, the headline accent word, the left rule of the + manifesto, and the metric values. Anywhere else, ink-blue must + cover ≤ 5% of the document surface area. +- **Serif at one weight (500) for hierarchy** — Charter (EN), + TsangerJinKai02 / Source Han Serif (CN), or YuMincho (JA), + selected by the `language` parameter. **No italic anywhere.** +- **Tight print rhythm** — line-heights 1.10–1.55, letter-spacing + per language (0 for EN, 0.35px for CN, 0.02em for JA). +- **Numeric stacks set in `font-variant-numeric: tabular-nums`** so + metric columns and pagination digits sit cleanly aligned. +- **Depth via 1px rings + whisper shadows** (`0 4px 24px rgba(0,0,0,0.05)`). + No hard drop shadows, no neumorphism, no backdrop-filter blurs. +- **Tag fills as solid hex** (e.g. `#E4ECF5`), never `rgba()` — + print renderers double-paint alpha tags. +- **Responsive** at 1280 / 980 / 768 / 560. + +## Page structure + +```text +1. Eyebrow row — locale switcher · edition · version (12px sans uppercase) +2. Hero — display headline (96–106px serif 500), tagline (21px), + three hero-token chips (paper-tinted) +3. Manifesto — pull paragraph in serif 400, 20px, 1.65 LH, with + ink-blue left-rule and signature footer +4. Metrics row — 3-6 cells: value (24px serif 500 ink-blue, tabular-nums), + label (12px serif 500 olive) +5. Chapters — numbered (`01`, `02`, …) ink-blue serif 500 14px, + section title 28-32px, body 14-15px +6. Footer — kicker word (mega serif 500), license · year · contact, + three-column site index in 12px serif 500 +``` + +## Workflow contract + +### 1. Gather brand brief + +Use `AskQuestion` (or equivalent) to collect the brand brief in +chunks. Don't dump the whole input list on the user; ask in two +rounds: + +1. Identity round — name, tagline, location, edition / version, + primary URL, dominant language. +2. Content round — manifesto paragraph + signature, 3-6 metric + tiles, 3-5 chapter (title + lede + body) entries. + +### 2. Pick the language stack + +The `language` parameter controls which `--serif` stack is set on +`:root`. Pick based on the dominant language of the manifesto and +chapter body copy: + +| `language` | `--serif` | Notes | +| :--------- | :-------------------------------------------------------- | :------------------------------------- | +| `en` | Charter, Georgia, Palatino, Times New Roman, serif | default | +| `zh-CN` | TsangerJinKai02, Source Han Serif SC, Songti SC, Georgia | letter-spacing 0.35px on body | +| `ja` | YuMincho, Hiragino Mincho ProN, Source Han Serif JP | also override `--olive` to `#4d4c48` (YuMincho strokes are thinner) | + +Inline mixed-script content is fine — the browser per-glyph fallback +chain handles it. Do **not** chain all three families inside one +`font-family` declaration; that dilutes character. + +### 3. Write `index.html` + +Output a single file with all CSS inline. Mirror the structure of +[`example.html`](./example.html) and use only the tokens from +`design-systems/kami/DESIGN.md`. Do **not** invent new colors, +weights, or font families. + +Component primitives the agent can drop in (all defined in the +example's ` + + + +
+ + +
+
+ EN + 中文 + 日本語 +
+
+ Vol. 01 · Issue Nº 26 + v0.2.0 + Apache-2.0 + MMXXVI +
+
+ + +
+
+

Designing intelligence on warm paper.

+

The open-source studio for editorial documents, white papers, and one-pagers — typeset by your own coding agent.

+
+
+ 31 Skills + 72 Systems + 12 Agents · BYOK +
+
+ + +
+ Manifesto · Nº 01 +
+

+ We treat your existing coding agent as a creative collaborator, not a black box. Open Design gives it 31 composable skills and 72 brand-grade design systems, then steps out of the way. The output is a real file — not a prompt — that you can hand to a client tomorrow. +

+
+ — Open Design Studio + Berlin · Open · Earth · 52.5200° N · 13.4050° E +
+
+
+ + +
+
+
31
+
Skills
+
file-based, shippable today, drop-in compatible with Claude Code.
+
+
+
72
+
Design systems
+
portable DESIGN.md tokens — Linear, Vercel, Stripe, Apple, kami…
+
+
+
12
+
Agent CLIs
+
auto-detected on your $PATH; switch backends in one keystroke.
+
+
+
3
+
Commands
+
from git clone to first artifact, locally and offline.
+
+
+ + +
+ +
+
+

01

+

What it is

+

A local-first design studio for the agent you already trust.

+
+
+

+ Open Design is the open-source alternative to Anthropic's Claude Design. It runs on your laptop. Your agent reads a folder of SKILL.md files and a folder of DESIGN.md systems, then produces real files — landing pages, decks, white papers, one-pagers, mobile prototypes, dashboards. +

+

+ Skills supply behavior. Systems supply taste. Adapters bridge agents. BYOK respects your wallet. Every output is portable HTML or Markdown — no proprietary file format, no vendor lock-in. +

+
    +
  • Files, not opaque prompts — every skill is a folder of Markdown.
  • +
  • Deterministic visual directions, not random generation.
  • +
  • Sandboxed iframe preview, real cwd, exportable artifacts.
  • +
+
+
Three commands · 30 seconds
+
# Clone, install, launch the local daemon.
+git clone https://github.com/nexu-io/open-design
+pnpm install
+pnpm tools-dev
+
+
+
+ +
+
+

02

+

How it feels

+

Editorial discipline, not chat-window improvisation.

+
+
+

+ A new project starts with a 30-second question form: brand, audience, scale, language. The agent picks one of five visual directions in OKLch, locks the type stack, and writes the artifact to disk. You can read every file it touched. +

+

+ Every iteration is reviewed in a sandboxed iframe preview, with comment-mode anchors on every editable element so you can give targeted feedback instead of restating the whole brief. Re-runs are deterministic — same brief, same output. +

+

+ The result is the difference between looks AI and looks shipped. One brush tag per page by convention; everything else stays solid hex. +

+
+
+ +
+
+

03

+

What ships next

+

Q2 2026 — packaging, multi-tenant tokens, daemon hardening.

+
+
+

+ The roadmap is public. Three threads we're pulling on right now: +

+
    +
  • Packaged desktop builds — signed Mac, Windows NSIS, Linux AppImage, all sourced from tools-pack.
  • +
  • Multi-tenant brand tokens — one running daemon, many tenants; OD_DATA_DIR and OD_MEDIA_CONFIG_DIR already laid the groundwork.
  • +
  • Daemon-side artifact persistence — every render is a file on disk under .od/artifacts/ with a stable SHA, so design history survives every agent restart.
  • +
+

+ Nothing in this list requires a paid plan. Open Design will stay Apache-2.0 forever; the studio earns by selling brand-grade design systems, not by gating the runtime. +

+
+
+ +
+ + + + + + +
+ + diff --git a/skills/mobile-onboarding/SKILL.md b/skills/mobile-onboarding/SKILL.md index 7340e858d..fa7a15379 100644 --- a/skills/mobile-onboarding/SKILL.md +++ b/skills/mobile-onboarding/SKILL.md @@ -16,7 +16,7 @@ od: mode: prototype platform: mobile scenario: design - featured: 5 + featured: 13 preview: type: html entry: index.html diff --git a/skills/editorial-collage-deck/README.md b/skills/open-design-landing-deck/README.md similarity index 52% rename from skills/editorial-collage-deck/README.md rename to skills/open-design-landing-deck/README.md index 12ba07892..6b8a13ca0 100644 --- a/skills/editorial-collage-deck/README.md +++ b/skills/open-design-landing-deck/README.md @@ -1,10 +1,11 @@ -# editorial-collage-deck +# open-design-landing-deck -Sister skill to [`editorial-collage`](../editorial-collage/). Produces -a single-file slide deck in the **Atelier Zero** design language — -warm-paper background, italic-serif emphasis, coral terminating dots, -surreal collage plates — with scroll-snap pagination and arrow-key -navigation. +Sister skill to [`open-design-landing`](../open-design-landing/). +Produces a single-file slide deck in the **Atelier Zero** design +language — warm-paper background, italic-serif emphasis, coral +terminating dots, surreal collage plates — paginated as a horizontal +magazine swipe deck (←/→ · wheel · touch · ESC overview), the same +nav model as [`guizang-ppt`](../guizang-ppt/). > **Read first** — agent contract, schema, and self-check live in > [`SKILL.md`](./SKILL.md). This README is the human quick-start. @@ -19,17 +20,19 @@ npx tsx scripts/compose.ts inputs.example.json example.html open example.html ``` -The deck assumes 16 collage assets at `../editorial-collage/assets/` +The deck assumes 16 collage assets at `../open-design-landing/assets/` (the sister skill ships them). Use ←/→ · Space · PageUp/PageDown · -Home/End to navigate. +Home/End to navigate, ESC for the overview grid. ## What you get -- N viewport-height slides (the worked example has 11) with - `scroll-snap-type: y mandatory` for clean pagination. -- HUD at top: brand mark · deck title · keyboard hint · live - `NN / TT` counter. +- N viewport-sized slides (the worked example has 11) laid out + horizontally on a `transform: translateX(...)` flex track. +- Per-slide chrome strip (top + bottom): brand mark · deck title · + location · live `NN / TT` counter. - Coral progress bar at the bottom that fills as you advance. +- Dot indicator near the bottom (click to jump). +- ESC overview grid with scaled thumbnails. - 7 slide kinds: `cover`, `section`, `content`, `stats`, `quote`, `cta`, `end`. Mix freely. - Same 16-slot image library as the landing-page sister skill — @@ -38,7 +41,7 @@ Home/End to navigate. ## Files ```text -skills/editorial-collage-deck/ +skills/open-design-landing-deck/ ├── SKILL.md # ← agent contract (read this first) ├── README.md # ← you are here ├── schema.ts # typed slide variants + brand block (re-exports from sister) @@ -52,7 +55,7 @@ skills/editorial-collage-deck/ 1. Copy `inputs.example.json` to your project as `inputs.json`. 2. Edit `brand` (or copy from a sister-skill `inputs.json` you already have). -3. Set `deck_title` (the kicker shown in the HUD). +3. Set `deck_title` (the kicker shown in the chrome strip). 4. Build the `slides` array. Each entry is one of seven kinds — see [`schema.ts`](./schema.ts) for the full type. A typical pitch: @@ -78,21 +81,38 @@ skills/editorial-collage-deck/ The deck inherits the sister skill's 16-slot image library. Set `inputs.imagery.assets_path` to wherever those PNGs live; the example -uses `'../editorial-collage/assets/'`. +uses `'../open-design-landing/assets/'`. To regenerate or stub: ```bash # Generate via gpt-image-2 (fal.ai) -FAL_KEY=fal-... npx tsx ../editorial-collage/scripts/imagegen.ts \ - ../editorial-collage/inputs.example.json \ - --out=../editorial-collage/assets/ +FAL_KEY=fal-... npx tsx ../open-design-landing/scripts/imagegen.ts \ + ../open-design-landing/inputs.example.json \ + --out=../open-design-landing/assets/ # Or paper-textured SVG placeholders -npx tsx ../editorial-collage/scripts/placeholder.ts ../editorial-collage/assets/ +npx tsx ../open-design-landing/scripts/placeholder.ts ../open-design-landing/assets/ ``` +## Migrating from `editorial-collage-deck` + +This skill replaces the older `editorial-collage-deck` skill. The renames +are mechanical: + +| Old | New | +| --- | --- | +| skill folder `editorial-collage-deck/` | `open-design-landing-deck/` | +| shared assets `../editorial-collage/assets/` | `../open-design-landing/assets/` | +| TS type `EditorialCollageDeckInputs` | `OpenDesignLandingDeckInputs` | + +The `EditorialCollageDeckInputs` alias re-exported from +[`schema.ts`](./schema.ts) is a temporary bridge: it is kept for the +**v0.3.x** line and removed in the next minor release (**v0.4.0**). +Update imports before then. + ## See also -- [`editorial-collage`](../editorial-collage/) — landing page sister skill. +- [`open-design-landing`](../open-design-landing/) — landing page sister skill. +- [`guizang-ppt`](../guizang-ppt/) — the magazine-deck navigation pattern this skill borrows. - [`design-systems/atelier-zero/DESIGN.md`](../../design-systems/atelier-zero/DESIGN.md) — design tokens. diff --git a/skills/editorial-collage-deck/SKILL.md b/skills/open-design-landing-deck/SKILL.md similarity index 55% rename from skills/editorial-collage-deck/SKILL.md rename to skills/open-design-landing-deck/SKILL.md index e8aed50fa..fc1b89b38 100644 --- a/skills/editorial-collage-deck/SKILL.md +++ b/skills/open-design-landing-deck/SKILL.md @@ -1,12 +1,14 @@ --- -name: editorial-collage-deck +name: open-design-landing-deck description: > Produce a single-file slide deck in the Atelier Zero visual language (warm-paper background, italic-serif emphasis spans, coral terminating - dots, surreal collage plates). The deck uses scroll-snap pagination, - arrow-key + space navigation, a live HUD with slide counter and - progress bar, and inherits the canonical stylesheet + 16-slot image - library from the sister `editorial-collage` skill. + dots, surreal collage plates) — Open Design's brand deck recipe. + The deck uses **horizontal magazine-style swipe pagination** (←/→, + wheel, swipe), a per-slide chrome strip with brand mark and slide + counter, an ESC overview grid, a coral progress bar, and inherits + the canonical stylesheet + 16-slot image library from the sister + `open-design-landing` skill. triggers: - slide deck - 演示文稿 @@ -14,12 +16,20 @@ triggers: - keynote - editorial slides - atelier zero deck + - open design deck + - open design landing deck od: category: brand-deck surface: web + mode: deck + scenario: marketing + featured: 2 audience: founders pitching, conference talks, internal reviews tone: editorial, restrained, premium scale: 6-15 viewport-locked slides + preview: + type: html + entry: index.html craft: requires: - typographic-rhythm @@ -29,7 +39,7 @@ inputs: label: Brand identity (shared across slides) schema_path: ./schema.ts#BrandBlock - id: deck_title - label: Kicker shown in the HUD top bar + label: Kicker shown in the per-slide top chrome description: e.g. `'Open Design · Vol. 01 / Issue Nº 26'`. - id: slides label: Ordered list of typed slides @@ -39,7 +49,7 @@ inputs: schema_path: ./schema.ts#Slide - id: imagery label: Image library (defaults to sister skill's assets) - schema_path: ../editorial-collage/schema.ts#ImageryConfig + schema_path: ../open-design-landing/schema.ts#ImageryConfig parameters: slides_recommended_count: type: number @@ -56,34 +66,45 @@ example_prompt: | studio. Cover with hero plate, two section dividers, two product content slides with bullets, a stats slide showing 12 soundscapes / 4 presets / 1 daily ritual, a customer quote, a closing CTA, and an end - card. Reuse the editorial-collage image library. + card. Reuse the open-design-landing image library. --- -# editorial-collage-deck +# open-design-landing-deck -Sister skill to [`editorial-collage`](../editorial-collage/). Same +Sister skill to [`open-design-landing`](../open-design-landing/). Same Atelier Zero visual system (warm paper, Inter Tight + Playfair Display, -italic-serif emphasis, coral dots), but paginated as a slide deck -instead of a long landing page. +italic-serif emphasis, coral dots), but paginated as a **horizontal +magazine-style swipe deck** instead of a long scrolling page. + +The navigation model is intentionally borrowed from the +[`guizang-ppt`](../guizang-ppt/) skill — `←/→` arrow keys, wheel / +swipe, ESC for the overview grid — so it feels like a print magazine +flipping page by page rather than a web slide deck scrolling. ```text -inputs.json + ../editorial-collage/styles.css +inputs.json + ../open-design-landing/styles.css │ └──────────► scripts/compose.ts │ ▼ /index.html - (one viewport per slide, scroll-snap) + (one viewport per slide, horizontal swipe) ``` ## What you get -- A single self-contained HTML file with N viewport-height slides. -- **Keyboard navigation**: ←/→ · ↑/↓ · PageUp/PageDown · Space · Home/End. -- **HUD top bar**: brand mark, deck title, key hint, live slide counter. +- A single self-contained HTML file with N viewport-sized slides laid + out horizontally on one transformed flex track. +- **Keyboard navigation**: `←/→` · `↑/↓` · PageUp/PageDown · Space · + Home/End. +- **Wheel + touch swipe** (with momentum guard so a single trackpad + flick doesn't overshoot). +- **Per-slide chrome strip**: brand mark, deck title, location, + Roman-numeral year, live slide counter (`01 / 11`). - **Coral progress bar** at the bottom that fills as you advance. -- **Scroll-snap pagination** with `scroll-snap-stop: always` so each - slide settles cleanly. +- **Dot indicator** strip near the bottom; click any dot to jump. +- **ESC overview grid** — scaled thumbnails of every slide, click to + jump. Mirrors `guizang-ppt`'s overview UX. - Reuses the **same 16-slot image library** as the sister skill — no duplicate assets. @@ -122,29 +143,29 @@ A typical 11-slide pitch: Start from [`inputs.example.json`](./inputs.example.json) (the Open Design pitch deck). The brand block, image strategy, and assets path mirror the sister skill — if you already filled out an -`editorial-collage` brief, copy `brand` and `imagery` over verbatim. +`open-design-landing` brief, copy `brand` and `imagery` over verbatim. For each slide, pick a `kind` and fill the typed fields from -[`schema.ts`](./schema.ts). `MixedText` (sans-serif baseline + italic-serif -emphasis spans + coral terminating dot) is the same encoding used by -the sister skill — see its `inputs.example.json` for examples. +[`schema.ts`](./schema.ts). `MixedText` (sans-serif baseline + +italic-serif emphasis spans + coral terminating dot) is the same +encoding used by the sister skill — see its `inputs.example.json`. ### 2. (Optional) generate or stub imagery This skill does **not** ship its own image generator or placeholder -script — it shares the 16-slot library from `editorial-collage`. To +script — it shares the 16-slot library from `open-design-landing`. To regenerate or stub: ```bash # generate via gpt-image-2 (fal.ai) -FAL_KEY=... npx tsx ../editorial-collage/scripts/imagegen.ts ../editorial-collage/inputs.example.json --out=../editorial-collage/assets/ +FAL_KEY=... npx tsx ../open-design-landing/scripts/imagegen.ts ../open-design-landing/inputs.example.json --out=../open-design-landing/assets/ # or paper-textured SVG placeholders -npx tsx ../editorial-collage/scripts/placeholder.ts ../editorial-collage/assets/ +npx tsx ../open-design-landing/scripts/placeholder.ts ../open-design-landing/assets/ ``` Set your deck's `inputs.imagery.assets_path` to wherever those PNGs -live (default in the example: `../editorial-collage/assets/`). +live (default in the example: `../open-design-landing/assets/`). ### 3. Compose the deck @@ -153,40 +174,49 @@ npx tsx scripts/compose.ts inputs.json out/index.html ``` The composer reads `inputs.json`, loads the canonical Atelier Zero -stylesheet from `../editorial-collage/styles.css`, layers deck-specific -rules (scroll-snap container, slide layout grid, HUD, keyboard nav) -on top, and writes one self-contained HTML file. +stylesheet from `../open-design-landing/styles.css`, layers +deck-specific rules on top (horizontal flex track, slide layouts, +HUD, dot nav, ESC overview, keyboard / wheel / touch handlers), and +writes one self-contained HTML file. ### 4. Self-check - [ ] Open the HTML in a fresh browser tab; slide 1 (cover) shows - with HUD `01 / N` in the corner. -- [ ] Press `→` (or Space). Smoothly advances to slide 2 with - `02 / N` in the counter and the coral progress bar filling. + with chrome strip top-right showing `01 / N`. +- [ ] Press `→` (or Space, or scroll-down). Smoothly slides one + viewport to the right; dot nav advances; the coral progress bar + ticks forward. - [ ] Press `End`. Jumps to the final slide. - [ ] Press `Home`. Returns to slide 1. -- [ ] `prefers-reduced-motion: reduce` (DevTools → Rendering): smooth - scroll still works, but page transitions are instant. -- [ ] Resize to 1080px and 640px. Slides stack appropriately; no - horizontal scrollbar; HUD shrinks gracefully. +- [ ] Press `Esc`. Overview grid appears with scaled thumbnails; + click any tile to jump and dismiss the overview. +- [ ] Resize to 1080px and 640px. Cover / content slides collapse to + a single column; dot nav still works; chrome strips shrink. +- [ ] `prefers-reduced-motion: reduce` (DevTools → Rendering): page + transitions stay snappy and don't induce motion sickness. - [ ] Lighthouse: contrast AA, font-display swap, no layout shift. ## Boundaries - **Reuse the sister skill's stylesheet.** The composer reads - `../editorial-collage/styles.css` at compile time. Do not maintain a - duplicate copy here; if Atelier Zero tokens evolve, edit them once - in the sister skill. + `../open-design-landing/styles.css` at compile time. Do not + maintain a duplicate copy here; if Atelier Zero tokens evolve, edit + them once in the sister skill. - **Reuse the sister skill's image library.** No need to re-prompt or re-render — the same 16 plates work for both surfaces. - **Keep slides single-viewport.** If a slide's content does not fit - 100vh at 1280×800 it will overflow and feel cramped. Trim copy or - split into two slides. + 100vh × 100vw at 1280×800 it will overflow and feel cramped. Trim + copy or split into two slides. +- **Do not switch to vertical scroll-snap.** The horizontal swipe + posture is what makes this skill feel like a magazine spread; a + vertical scroller would just be a long landing page. - **Do not add a router.** This is a single-file artifact. Multi-page decks are out of scope; for a multi-deck experience, render each deck separately and link from a parent index. ## See also -- [`editorial-collage`](../editorial-collage/) — landing page sister skill. +- [`open-design-landing`](../open-design-landing/) — landing page sister skill. +- [`guizang-ppt`](../guizang-ppt/) — the magazine-deck navigation + pattern this skill borrows. - [`design-systems/atelier-zero/DESIGN.md`](../../design-systems/atelier-zero/DESIGN.md) — token spec. diff --git a/skills/editorial-collage-deck/example.html b/skills/open-design-landing-deck/example.html similarity index 64% rename from skills/editorial-collage-deck/example.html rename to skills/open-design-landing-deck/example.html index 0b4a97311..1a971e2b4 100644 --- a/skills/editorial-collage-deck/example.html +++ b/skills/open-design-landing-deck/example.html @@ -11,12 +11,12 @@ -
+
+
+
Ø - Open Design · Vol. 01 / Issue Nº 26 + Open Design · Vol. 01 / Issue Nº 26
- ← / → · Space - 01 / 11 + + Open Design · Vol. 01 / Issue Nº 26
-
-
-
-
- Open-source design studio · Nº 01 -

Designing intelligence with skills, taste, and code.

-
The open-source alternative to Anthropic's Claude Design.
-

12 coding agents drive 31 composable skills and 72 brand-grade design systems. Local-first, web-deployable, BYOK at every layer.

-
Berlin · MMXXVI · 52.5200° N · 13.4050° E
-
-
+
+
+ Open-source design studio · Nº 01 +

Designing intelligence with skills, taste, and code.

+
The open-source alternative to Anthropic's Claude Design.
+

12 coding agents drive 31 composable skills and 72 brand-grade design systems. Local-first, web-deployable, BYOK at every layer.

+
Berlin · MMXXVI · 52.5200° N · 13.4050° E
+
+ + + + + +
+
+
+ MMXXVI · Berlin / Open / Earth + 01 / 11 +
-
-
I.
-

Why another design tool?

-

Because the strongest agents already live on your laptop — and they deserve a real workflow.

+
+
+ Ø + Open Design · Vol. 01 / Issue Nº 26
+
+ + Open Design · Vol. 01 / Issue Nº 26 +
+
+
+
I.
+

Why another design tool?

+

Because the strongest agents already live on your laptop — and they deserve a real workflow.

+
+
+ MMXXVI · Berlin / Open / Earth + 02 / 11 +
-
-
- About the studio · Nº 02 -

We treat your agent as a creative collaborator.

-

We don't ship one — we wire whichever you trust into a skill-driven design workflow that runs locally with pnpm tools-dev, deploys to Vercel, and stays BYOK at every layer.

-
  • Files, not opaque prompts — every skill is a folder of Markdown.
  • Deterministic visual directions, not random generation.
  • Sandboxed iframe preview, real cwd, exportable artifacts.
-
-
+
+
+ Ø + Open Design · Vol. 01 / Issue Nº 26
+
+ + Open Design · Vol. 01 / Issue Nº 26 +
+
+
+
+ About the studio · Nº 02 +

We treat your agent as a creative collaborator.

+

We don't ship one — we wire whichever you trust into a skill-driven design workflow that runs locally with pnpm tools-dev, deploys to Vercel, and stays BYOK at every layer.

+
  • Files, not opaque prompts — every skill is a folder of Markdown.
  • Deterministic visual directions, not random generation.
  • Sandboxed iframe preview, real cwd, exportable artifacts.
+
+
+
+
+ MMXXVI · Berlin / Open / Earth + 03 / 11 +
-
-
- Capabilities · Nº 03 -

Skills, systems, surfaces — for creative intelligence.

-

Four composable surfaces, one feedback loop. Skills supply behavior. Systems supply taste. Adapters bridge agents. BYOK respects your wallet.

-
  • 31 file-based SKILL.md bundles — drop in, restart, appears.
  • 72 portable DESIGN.md systems — Linear, Vercel, Stripe, Apple…
  • 12 agent adapters — Claude · Codex · Gemini · Cursor · …
  • OpenAI-compatible proxy — paste a baseUrl + key, ship.
-
-
+
+
+ Ø + Open Design · Vol. 01 / Issue Nº 26
+
+ + Open Design · Vol. 01 / Issue Nº 26 +
+
+
+
+ Capabilities · Nº 03 +

Skills, systems, surfaces — for creative intelligence.

+

Four composable surfaces, one feedback loop. Skills supply behavior. Systems supply taste. Adapters bridge agents. BYOK respects your wallet.

+
  • 31 file-based SKILL.md bundles — drop in, restart, appears.
  • 72 portable DESIGN.md systems — Linear, Vercel, Stripe, Apple…
  • 12 agent adapters — Claude · Codex · Gemini · Cursor · …
  • OpenAI-compatible proxy — paste a baseUrl + key, ship.
+
+
+
+
+ MMXXVI · Berlin / Open / Earth + 04 / 11 +
-
-
- By the numbers · Nº 04 -

Composable, shippable, portable.

-
-
-
+
+
+ Ø + Open Design · Vol. 01 / Issue Nº 26 +
+
+ + Open Design · Vol. 01 / Issue Nº 26 +
+
+
+
+ By the numbers · Nº 04 +

Composable, shippable, portable.

+
+
+
31
Skills
file-based, shippable today
@@ -2139,128 +2563,338 @@ body { overflow: hidden; }
Commands
from clone to first artifact
-
-
Open Design v0.2.0 · Apache-2.0 · MMXXVI
+
Open Design v0.3.0 · Apache-2.0 · MMXXVI
+
+
+ MMXXVI · Berlin / Open / Earth + 05 / 11 +
-
-
II.
-

How it feels to use it.

- +
+
+ Ø + Open Design · Vol. 01 / Issue Nº 26
+
+ + Open Design · Vol. 01 / Issue Nº 26 +
+
+
+
II.
+

How it feels to use it.

+ +
+
+ MMXXVI · Berlin / Open / Earth + 06 / 11 +
-
-
- Method · Nº 05 -

From signals to systems.

-

Every project moves through four iterative stages. The agent picks each stage's tools deterministically; you stay in control.

-
  • 01 · Detect — daemon scans $PATH, auto-loads skills + systems.
  • 02 · Discover — 30s question form locks brand · audience · scale.
  • 03 · Direct — pick one of 5 visual directions in OKLch + type stack.
  • 04 · Deliver — write to disk, preview in sandbox, export anywhere.
-
-
+
+
+ Ø + Open Design · Vol. 01 / Issue Nº 26
+
+ + Open Design · Vol. 01 / Issue Nº 26 +
+
+
+
+ Method · Nº 05 +

From signals to systems.

+

Every project moves through four iterative stages. The agent picks each stage's tools deterministically; you stay in control.

+
  • 01 · Detect — daemon scans $PATH, auto-loads skills + systems.
  • 02 · Discover — 30s question form locks brand · audience · scale.
  • 03 · Direct — pick one of 5 visual directions in OKLch + type stack.
  • 04 · Deliver — write to disk, preview in sandbox, export anywhere.
+
+
+
+
+ MMXXVI · Berlin / Open / Earth + 07 / 11 +
-
-
- Selected work · Nº 06 -

Skills that turn briefs into memorable artifacts.

-

From editorial decks to consumer dashboards — the same loop, different surface. Every output is a real file you can hand to a client tomorrow.

- -
-
+
+
+ Ø + Open Design · Vol. 01 / Issue Nº 26
+
+ + Open Design · Vol. 01 / Issue Nº 26 +
+
+
+
+ Selected work · Nº 06 +

Skills that turn briefs into memorable artifacts.

+

From editorial decks to consumer dashboards — the same loop, different surface. Every output is a real file you can hand to a client tomorrow.

+ +
+
+
+
+ MMXXVI · Berlin / Open / Earth + 08 / 11 +
-
-
-
“Open Design helped us turn vague AI ideas into a visual system that felt sharp, believable, and genuinely new.”
-
- m -

Mina Kovac
Creative Director · North Form

-
-
-
+
+
+ Ø + Open Design · Vol. 01 / Issue Nº 26
+
+ + Open Design · Vol. 01 / Issue Nº 26 +
+
+
+
+
“Open Design helped us turn vague AI ideas into a visual system that felt sharp, believable, and genuinely new.”
+
+ m +

Mina KovacCreative Director · North Form

+
+
+
+
+
+ MMXXVI · Berlin / Open / Earth + 09 / 11 +
-
- Start a conversation · Nº 07 -

Let's build something open and visually unforgettable.

-

Star us on GitHub, drop into the issues, or run pnpm tools-dev tonight. Three commands and the loop is yours.

- +
+
+ Ø + Open Design · Vol. 01 / Issue Nº 26
+
+ + Open Design · Vol. 01 / Issue Nº 26 +
+
+
+ Start a conversation · Nº 07 +

Let's build something open and visually unforgettable.

+

Star us on GitHub, drop into the issues, or run pnpm tools-dev tonight. Three commands and the loop is yours.

+ +
+
+ MMXXVI · Berlin / Open / Earth + 10 / 11 +
-
-
Open Design.
- +
+
+ Ø + Open Design · Vol. 01 / Issue Nº 26
+
+ + Open Design · Vol. 01 / Issue Nº 26 +
+
+
+
Open Design.
+ +
+
+ MMXXVI · Berlin / Open / Earth + 11 / 11 +
+ +
← / → · esc · swipe
diff --git a/skills/editorial-collage-deck/inputs.example.json b/skills/open-design-landing-deck/inputs.example.json similarity index 96% rename from skills/editorial-collage-deck/inputs.example.json rename to skills/open-design-landing-deck/inputs.example.json index 826643e88..16b966622 100644 --- a/skills/editorial-collage-deck/inputs.example.json +++ b/skills/open-design-landing-deck/inputs.example.json @@ -1,6 +1,6 @@ { "$schema": "./schema.ts", - "_doc": "Worked example — Open Design pitch deck. 11 slides covering cover, two sections, four content slides, one stats, one quote, one CTA, one end. Reuses brand identity and assets from the sister editorial-collage skill. Run `npx tsx scripts/compose.ts inputs.example.json example.html` to build.", + "_doc": "Worked example — Open Design pitch deck. 11 slides covering cover, two sections, four content slides, one stats, one quote, one CTA, one end. Reuses brand identity and assets from the sister open-design-landing skill. Run `npx tsx scripts/compose.ts inputs.example.json example.html` to build.", "brand": { "name": "Open Design", @@ -11,7 +11,7 @@ "description": "Open Design pitch deck — Vol. 01.", "locale": "en", "edition": "Vol. 01 / Issue Nº 26", - "version": "v0.2.0", + "version": "v0.3.0", "license": "Apache-2.0", "primary_url": "https://github.com/nexu-io/open-design", "primary_url_label": "Star · 0K", @@ -23,7 +23,7 @@ "founded": "Est. MMXXVI", "rails": { "right": "", "left": "" }, "languages": ["EN"], - "status": "Live · v0.2.0" + "status": "Live · v0.3.0" }, "deck_title": "Open Design · Vol. 01 / Issue Nº 26", @@ -114,7 +114,7 @@ { "value": "12", "label": "Agents", "sub": "auto-detected on your $PATH" }, { "value": "3", "label": "Commands","sub": "from clone to first artifact" } ], - "caption": "Open Design v0.2.0 · Apache-2.0 · MMXXVI" + "caption": "Open Design v0.3.0 · Apache-2.0 · MMXXVI" }, { @@ -204,7 +204,7 @@ "imagery": { "strategy": "bring-your-own", - "assets_path": "../editorial-collage/assets/", + "assets_path": "../open-design-landing/assets/", "provider": "fal" } } diff --git a/skills/editorial-collage-deck/schema.ts b/skills/open-design-landing-deck/schema.ts similarity index 79% rename from skills/editorial-collage-deck/schema.ts rename to skills/open-design-landing-deck/schema.ts index b2f572572..5febf87fb 100644 --- a/skills/editorial-collage-deck/schema.ts +++ b/skills/open-design-landing-deck/schema.ts @@ -1,17 +1,17 @@ /** - * editorial-collage-deck — input schema. + * open-design-landing-deck — input schema. * - * Sister skill to `editorial-collage`. Produces a single-file slide - * deck (scroll-snap pagination + arrow-key nav) in the Atelier Zero - * visual language, reusing the same `styles.css` + the same 16-slot - * image library. + * Sister skill to `open-design-landing`. Produces a single-file slide + * deck (horizontal swipe pagination, magazine-style) in the Atelier + * Zero visual language, reusing the same `styles.css` + the same + * 16-slot image library. * * The schema is intentionally smaller than the landing page schema: * a deck is an ordered array of typed slides, each driving one - * viewport-height frame. Brand identity is shared across slides. + * viewport-height/width frame. Brand identity is shared across slides. */ -import type { MixedText, BrandBlock, ImageryConfig } from '../editorial-collage/schema'; +import type { MixedText, BrandBlock, ImageryConfig } from '../open-design-landing/schema'; export type { MixedText, BrandBlock, ImageryConfig }; @@ -109,7 +109,7 @@ export type Slide = /* ---------- top-level ---------- */ -export interface EditorialCollageDeckInputs { +export interface OpenDesignLandingDeckInputs { $schema?: string; brand: BrandBlock; /** Deck-wide title shown in the HUD — `'Open Design · Vol. 01'`. */ @@ -117,3 +117,12 @@ export interface EditorialCollageDeckInputs { slides: Slide[]; imagery: ImageryConfig; } + +/** + * @deprecated Use `OpenDesignLandingDeckInputs`. + * + * Backwards-compat alias kept for the v0.3.x line and removed in the next + * minor (v0.4.0). Migration steps live in `README.md` under + * "Migrating from `editorial-collage-deck`". + */ +export type EditorialCollageDeckInputs = OpenDesignLandingDeckInputs; diff --git a/skills/open-design-landing-deck/scripts/compose.ts b/skills/open-design-landing-deck/scripts/compose.ts new file mode 100644 index 000000000..b24d878bc --- /dev/null +++ b/skills/open-design-landing-deck/scripts/compose.ts @@ -0,0 +1,1056 @@ +#!/usr/bin/env -S npx -y tsx +/** + * open-design-landing-deck — slide deck composer. + * + * Reads `inputs.json` (matching `../schema.ts`) and writes a single + * self-contained HTML file: a horizontal magazine-style swipe deck + * where every slide occupies one viewport. Reuses the Atelier Zero + * stylesheet from the sister `open-design-landing` skill, then layers + * deck-specific rules (horizontal flex track, slide layouts, HUD, + * keyboard / wheel / touch nav, ESC overview). + * + * Inspired by `skills/guizang-ppt/assets/template.html`: same horizontal + * pagination model, same nav primitives — but the visual system is + * Atelier Zero (warm paper, italic-serif emphasis, coral dots) instead + * of Monocle dark/light WebGL. + * + * Usage: + * npx tsx scripts/compose.ts + * + * Re-generate the canonical example: + * npx tsx scripts/compose.ts inputs.example.json example.html + */ + +import { readFile, writeFile, mkdir } from 'node:fs/promises'; +import { resolve, dirname, isAbsolute } from 'node:path'; +import { fileURLToPath } from 'node:url'; +import type { + OpenDesignLandingDeckInputs, + Slide, + CoverSlide, + SectionSlide, + ContentSlide, + StatsSlide, + QuoteSlide, + CTASlide, + EndSlide, + MixedText, +} from '../schema'; + +const SKILL_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..'); +const SISTER_STYLES = resolve(SKILL_ROOT, '..', 'open-design-landing', 'styles.css'); + +/* ------------------------------------------------------------------ * + * helpers + * ------------------------------------------------------------------ */ + +function mixed(text: MixedText): string { + return text + .map((seg) => { + if (seg.dot) return `${seg.text}`; + if (seg.em) return `${seg.text}`; + return seg.text; + }) + .join(''); +} + +function ext(href: string): string { + return /^(https?:|mailto:|\/\/)/i.test(href) + ? ` target='_blank' rel='noreferrer noopener'` + : ''; +} + +const ARROW_OUT = ``; + +function imgFor(slot: string | undefined, assets: string): string { + if (!slot) return ''; + return ``; +} + +/* ------------------------------------------------------------------ * + * deck-specific stylesheet (layered on top of open-design-landing CSS). + * + * Strategy: keep tokens, type scale, paper texture from the base CSS. + * Override only the things a horizontal deck demands — body overflow, + * the .deck flex track, the .slide frame, the HUD, the dot nav, the + * ESC overview grid. The .hero / .nav / .topbar rules from the base + * stylesheet are unused here (we don't render those sections). + * ------------------------------------------------------------------ */ + +const DECK_CSS = ` +/* ---------- base host ---------- */ +html, body { width: 100%; height: 100%; overflow: hidden; } +body { background: var(--paper); color: var(--ink); } +/* the base stylesheet's body::before paper texture sits at z-index:3 + * which is above our slide content. Re-pin it to behind the deck. */ +body::before { z-index: 0; } + +/* ---------- deck flex track (horizontal pagination) ---------- */ +#deck { + position: fixed; + inset: 0; + height: 100vh; + display: flex; + flex-wrap: nowrap; + transition: transform 0.9s cubic-bezier(0.77, 0, 0.175, 1); + z-index: 5; + will-change: transform; +} +.slide { + width: 100vw; + height: 100vh; + flex: 0 0 100vw; + position: relative; + padding: 64px 80px 80px; + display: flex; + flex-direction: column; + overflow: hidden; +} +.slide-inner { + max-width: 1360px; + margin: 0 auto; + width: 100%; + height: 100%; + display: grid; + align-content: center; + gap: 28px; + position: relative; + min-height: 0; +} +/* keep art panels inside the slide footprint */ +.s-cover .art, +.s-content .art, +.s-quote .art { + max-height: calc(100vh - 200px); + min-height: 0; +} + +/* ---------- magazine chrome (top + bottom strips on every slide) ---------- */ +.slide-chrome { + position: absolute; + top: 22px; left: 0; right: 0; + display: flex; + justify-content: space-between; + align-items: center; + padding: 0 80px; + font-family: var(--sans); + font-size: 10px; + font-weight: 600; + letter-spacing: 0.22em; + text-transform: uppercase; + color: var(--ink-faint); + z-index: 4; + pointer-events: none; +} +.slide-chrome .left, +.slide-chrome .right { + display: inline-flex; + align-items: center; + gap: 14px; +} +.slide-chrome .mark { + width: 22px; height: 22px; + border-radius: 50%; + border: 1px solid var(--ink); + display: inline-flex; + align-items: center; + justify-content: center; + font-family: var(--serif); + font-style: italic; + font-size: 12px; + color: var(--ink); + background: rgba(239, 231, 210, 0.85); +} +.slide-chrome .coral { color: var(--coral); } +.slide-foot { + position: absolute; + bottom: 22px; left: 0; right: 0; + display: flex; + justify-content: space-between; + align-items: center; + padding: 0 80px; + font-family: var(--mono); + font-size: 10px; + letter-spacing: 0.18em; + text-transform: uppercase; + color: var(--ink-faint); + z-index: 4; + pointer-events: none; +} +.slide-foot .counter { + font-family: var(--mono); + letter-spacing: 0.04em; + color: var(--ink); + background: rgba(239, 231, 210, 0.85); + padding: 4px 8px; + border: 1px solid var(--line); + border-radius: 4px; +} + +/* ---------- progress bar ---------- */ +.deck-progress { + position: fixed; + left: 0; right: 0; bottom: 0; + height: 2px; + background: var(--line-soft); + z-index: 30; +} +.deck-progress .bar { + height: 100%; + background: var(--coral); + width: 0%; + transition: width 0.6s cubic-bezier(0.77, 0, 0.175, 1); +} + +/* ---------- dot nav ---------- */ +#nav { + position: fixed; + left: 50%; + bottom: 40px; + transform: translateX(-50%); + z-index: 30; + display: flex; + gap: 10px; + padding: 8px 14px; + border-radius: 999px; + background: rgba(239, 231, 210, 0.78); + border: 1px solid var(--line-soft); + backdrop-filter: blur(8px); + -webkit-backdrop-filter: blur(8px); +} +#nav .dot { + width: 7px; height: 7px; + border-radius: 50%; + background: rgba(21, 20, 15, 0.22); + cursor: pointer; + transition: all 0.3s ease; + border: 0; + padding: 0; +} +#nav .dot:hover { + background: rgba(21, 20, 15, 0.45); + transform: scale(1.15); +} +#nav .dot.active { + background: var(--coral); + width: 22px; + border-radius: 999px; +} + +/* ---------- key hint ---------- */ +#hint { + position: fixed; + bottom: 36px; right: 28px; + z-index: 30; + font-family: var(--mono); + font-size: 9.5px; + letter-spacing: 0.22em; + text-transform: uppercase; + color: var(--ink-faint); + opacity: 0.75; +} + +/* ---------- COVER slide ---------- */ +.s-cover .slide-inner { + grid-template-columns: 1.05fr 0.95fr; + align-content: center; + gap: 60px; +} +.s-cover .copy { display: flex; flex-direction: column; gap: 22px; } +.s-cover .eyebrow { + font-family: var(--sans); font-size: 11px; font-weight: 600; + letter-spacing: 0.22em; text-transform: uppercase; + color: var(--coral); + display: inline-flex; align-items: center; gap: 12px; +} +.s-cover .eyebrow::before { + content: ''; width: 18px; height: 1px; + background: var(--coral); display: inline-block; +} +.s-cover h1 { + font-family: var(--sans); + font-weight: 800; + font-size: clamp(40px, 5.6vw, 84px); + line-height: 1.0; + letter-spacing: -0.028em; + color: var(--ink); + margin: 0; +} +.s-cover h1 em { + font-family: var(--serif); + font-style: italic; font-weight: 500; + letter-spacing: -0.018em; +} +.s-cover h1 .dot { color: var(--coral); } +.s-cover .subtitle { + font-family: var(--serif); font-style: italic; font-weight: 500; + font-size: 22px; color: var(--ink-soft); margin-top: -6px; +} +.s-cover .lead { + font-family: var(--body); font-size: 17px; + color: var(--ink-soft); max-width: 42ch; line-height: 1.6; +} +.s-cover .meta { + margin-top: 28px; + font-family: var(--mono); font-size: 11px; letter-spacing: 0.06em; + color: var(--ink-faint); +} +.s-cover .art { + position: relative; aspect-ratio: 1 / 1; max-width: 600px; + margin-left: auto; margin-right: 0; + border: 1px solid var(--line-soft); border-radius: 14px; + overflow: hidden; background: var(--bone); +} +.s-cover .art img { width: 100%; height: 100%; object-fit: contain; } +.s-cover .art .corner { + position: absolute; + width: 22px; height: 22px; + border-color: var(--ink-faint); + border-style: solid; + border-width: 0; +} +.s-cover .art .corner.tl { top: 0; left: 0; border-top-width: 1px; border-left-width: 1px; } +.s-cover .art .corner.tr { top: 0; right: 0; border-top-width: 1px; border-right-width: 1px; } +.s-cover .art .corner.bl { bottom: 0; left: 0; border-bottom-width: 1px; border-left-width: 1px; } +.s-cover .art .corner.br { bottom: 0; right: 0; border-bottom-width: 1px; border-right-width: 1px; } + +/* ---------- SECTION divider slide ---------- */ +.s-section .slide-inner { + grid-template-columns: 1fr; + align-content: center; + text-align: center; + gap: 28px; +} +.s-section .roman { + font-family: var(--serif); font-style: italic; font-weight: 500; + font-size: clamp(80px, 10vw, 160px); + color: var(--coral); line-height: 1; letter-spacing: -0.02em; +} +.s-section h2 { + font-family: var(--sans); font-weight: 800; + font-size: clamp(54px, 7vw, 110px); + letter-spacing: -0.028em; line-height: 1.0; color: var(--ink); + max-width: 18ch; margin: 0 auto; +} +.s-section h2 em { + font-family: var(--serif); font-style: italic; font-weight: 500; +} +.s-section h2 .dot { color: var(--coral); } +.s-section .lead { + font-family: var(--body); font-size: 17px; + color: var(--ink-soft); max-width: 50ch; margin: 0 auto; + line-height: 1.6; +} + +/* ---------- CONTENT slide ---------- */ +.s-content .slide-inner { gap: 48px; } +.s-content.layout-left .slide-inner { grid-template-columns: 1fr 0.9fr; } +.s-content.layout-right .slide-inner { grid-template-columns: 0.9fr 1fr; } +.s-content.layout-right .copy { order: 2; } +.s-content.layout-right .art { order: 1; } +.s-content.layout-full .slide-inner { grid-template-columns: 1fr; max-width: 980px; } +.s-content .copy { display: flex; flex-direction: column; gap: 22px; } +.s-content .eyebrow { + font-family: var(--sans); font-size: 11px; font-weight: 600; + letter-spacing: 0.22em; text-transform: uppercase; color: var(--coral); + display: inline-flex; align-items: center; gap: 12px; +} +.s-content .eyebrow::before { + content: ''; width: 18px; height: 1px; + background: var(--coral); display: inline-block; +} +.s-content h2 { + font-family: var(--sans); font-weight: 800; + font-size: clamp(40px, 4.8vw, 64px); + letter-spacing: -0.024em; line-height: 1.05; + color: var(--ink); margin: 0; +} +.s-content h2 em { + font-family: var(--serif); font-style: italic; font-weight: 500; +} +.s-content h2 .dot { color: var(--coral); } +.s-content .body { + font-family: var(--body); font-size: 16px; + color: var(--ink-soft); max-width: 56ch; line-height: 1.6; +} +.s-content .body code { + font-family: var(--mono); font-size: 14px; + background: var(--bone); padding: 1px 6px; border-radius: 4px; +} +.s-content ul { + list-style: none; padding: 0; margin: 0; + display: flex; flex-direction: column; gap: 12px; +} +.s-content li { + font-family: var(--sans); font-size: 15px; + color: var(--ink-soft); display: flex; gap: 14px; align-items: flex-start; + line-height: 1.5; +} +.s-content li::before { + content: ''; width: 12px; height: 1px; + background: var(--coral); margin-top: 11px; flex-shrink: 0; +} +.s-content .art { + position: relative; aspect-ratio: 1 / 1; + border: 1px solid var(--line-soft); border-radius: 14px; + overflow: hidden; background: var(--bone); +} +.s-content .art img { width: 100%; height: 100%; object-fit: contain; } + +/* ---------- STATS slide ---------- */ +.s-stats .slide-inner { grid-template-columns: 1fr; gap: 56px; } +.s-stats .head { display: flex; flex-direction: column; gap: 22px; } +.s-stats .eyebrow { + font-family: var(--sans); font-size: 11px; font-weight: 600; + letter-spacing: 0.22em; text-transform: uppercase; color: var(--coral); + display: inline-flex; align-items: center; gap: 12px; +} +.s-stats .eyebrow::before { + content: ''; width: 18px; height: 1px; background: var(--coral); display: inline-block; +} +.s-stats h2 { + font-family: var(--sans); font-weight: 800; + font-size: clamp(44px, 5vw, 72px); + letter-spacing: -0.026em; line-height: 1.05; max-width: 18ch; margin: 0; +} +.s-stats h2 em { + font-family: var(--serif); font-style: italic; font-weight: 500; +} +.s-stats h2 .dot { color: var(--coral); } +.s-stats .grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); + gap: 36px; + border-top: 1px solid var(--line); + padding-top: 36px; +} +.s-stats .stat { display: flex; flex-direction: column; gap: 10px; } +.s-stats .stat .num { + font-family: var(--sans); font-weight: 800; + font-size: clamp(72px, 8vw, 128px); line-height: 1; + letter-spacing: -0.04em; color: var(--ink); + font-feature-settings: 'tnum'; +} +.s-stats .stat .num em { + color: var(--coral); font-family: var(--serif); font-style: italic; font-weight: 500; +} +.s-stats .stat .label { + font-family: var(--sans); font-size: 11.5px; + letter-spacing: 0.22em; text-transform: uppercase; + color: var(--ink); font-weight: 700; +} +.s-stats .stat .sub { + font-family: var(--body); font-size: 13px; + color: var(--ink-mute); max-width: 26ch; line-height: 1.5; +} +.s-stats .caption { + font-family: var(--mono); font-size: 11px; + color: var(--ink-faint); letter-spacing: 0.04em; +} + +/* ---------- QUOTE slide ---------- */ +.s-quote .slide-inner { + grid-template-columns: 1.4fr 0.8fr; + gap: 60px; align-items: center; +} +.s-quote.no-art .slide-inner { grid-template-columns: 1fr; max-width: 980px; } +.s-quote blockquote { + font-family: var(--sans); font-weight: 700; + font-size: clamp(34px, 4vw, 56px); + letter-spacing: -0.022em; line-height: 1.18; + color: var(--ink); margin: 0; + position: relative; +} +.s-quote blockquote em { + font-family: var(--serif); font-style: italic; font-weight: 500; +} +.s-quote .author { + margin-top: 38px; display: flex; align-items: center; gap: 16px; +} +.s-quote .author .avatar { + width: 48px; height: 48px; border-radius: 50%; + background: var(--ink); color: var(--paper); + font-family: var(--serif); font-style: italic; font-size: 22px; + display: inline-flex; align-items: center; justify-content: center; +} +.s-quote .author p { + font-family: var(--sans); font-size: 14px; font-weight: 600; + color: var(--ink); +} +.s-quote .author p span { + display: block; color: var(--ink-mute); font-weight: 400; + margin-top: 2px; +} +.s-quote .art { + position: relative; aspect-ratio: 1 / 1; + border: 1px solid var(--line-soft); border-radius: 14px; + overflow: hidden; background: var(--bone); +} +.s-quote .art img { width: 100%; height: 100%; object-fit: contain; } + +/* ---------- CTA slide ---------- */ +.s-cta .slide-inner { + grid-template-columns: 1fr; max-width: 980px; + gap: 32px; text-align: left; +} +.s-cta .eyebrow { + font-family: var(--sans); font-size: 11px; font-weight: 600; + letter-spacing: 0.22em; text-transform: uppercase; color: var(--coral); + display: inline-flex; align-items: center; gap: 12px; +} +.s-cta .eyebrow::before { + content: ''; width: 18px; height: 1px; + background: var(--coral); display: inline-block; +} +.s-cta h2 { + font-family: var(--sans); font-weight: 800; + font-size: clamp(54px, 6.4vw, 96px); + letter-spacing: -0.028em; line-height: 1.0; + color: var(--ink); margin: 0; +} +.s-cta h2 em { + font-family: var(--serif); font-style: italic; font-weight: 500; +} +.s-cta h2 .dot { color: var(--coral); } +.s-cta .body { + font-family: var(--body); font-size: 17px; + color: var(--ink-soft); max-width: 50ch; line-height: 1.6; +} +.s-cta .actions { + display: inline-flex; gap: 14px; margin-top: 12px; + align-items: center; flex-wrap: wrap; +} + +/* ---------- END slide ---------- */ +.s-end .slide-inner { + grid-template-columns: 1fr; + align-content: end; + padding-bottom: 32px; + text-align: left; + gap: 16px; + max-width: none; +} +.s-end .word { + font-family: var(--sans); font-weight: 900; + font-size: clamp(96px, 16vw, 240px); + letter-spacing: -0.04em; line-height: 1.0; + color: var(--ink); white-space: nowrap; + overflow-x: hidden; + padding-bottom: 0.18em; +} +.s-end .word em { + font-family: var(--serif); font-style: italic; font-weight: 500; + color: var(--coral); +} +.s-end .footer { + border-top: 1px solid var(--line); + padding-top: 22px; + font-family: var(--sans); font-size: 11px; + letter-spacing: 0.22em; text-transform: uppercase; + color: var(--ink-faint); +} + +/* ---------- ESC overview grid ---------- */ +#overview { + position: fixed; inset: 0; + z-index: 100; + background: rgba(239, 231, 210, 0.96); + backdrop-filter: blur(12px); + display: none; + overflow-y: auto; + padding: 60px 56px; +} +#overview .ov-head { + display: flex; justify-content: space-between; align-items: baseline; + margin-bottom: 32px; + font-family: var(--sans); font-size: 11px; + letter-spacing: 0.22em; text-transform: uppercase; color: var(--ink-faint); +} +#overview .ov-head b { color: var(--ink); font-weight: 700; } +#overview .ov-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); + gap: 22px; + max-width: 1280px; + margin: 0 auto; +} +#overview .ov-card { + cursor: pointer; + border-radius: 8px; + overflow: hidden; + border: 1px solid var(--line); + transition: border-color 0.2s, transform 0.2s; + background: var(--bone); +} +#overview .ov-card:hover { + border-color: var(--coral); + transform: translateY(-2px); +} +#overview .ov-card.active { border-color: var(--coral); border-width: 2px; } +#overview .ov-thumb { + width: 100%; + aspect-ratio: 16 / 10; + overflow: hidden; + position: relative; + pointer-events: none; + background: var(--paper); +} +#overview .ov-thumb .clone { + width: 100vw; height: 100vh; + transform: scale(0.18); + transform-origin: top left; + position: absolute; + top: 0; left: 0; + pointer-events: none; +} +#overview .ov-label { + padding: 8px 12px; + font-family: var(--mono); font-size: 10px; + letter-spacing: 0.18em; text-transform: uppercase; + color: var(--ink-mute); + display: flex; justify-content: space-between; align-items: center; +} +#overview .ov-label b { color: var(--ink); font-weight: 600; } + +/* ---------- responsive ---------- */ +@media (max-width: 1080px) { + .slide { padding: 56px 48px 64px; } + .slide-chrome, .slide-foot { padding: 0 48px; } + .s-cover .slide-inner, + .s-content.layout-left .slide-inner, + .s-content.layout-right .slide-inner, + .s-quote .slide-inner { + grid-template-columns: 1fr; gap: 36px; + } + .s-content.layout-right .copy { order: 1; } + .s-content.layout-right .art { order: 2; } +} +@media (max-width: 640px) { + .slide { padding: 36px 24px 56px; } + .slide-chrome, .slide-foot { padding: 0 24px; font-size: 9px; letter-spacing: 0.18em; } + #hint { display: none; } +} +`; + +/* ------------------------------------------------------------------ * + * slide renderers + * ------------------------------------------------------------------ */ + +function chromeStrip(brand: OpenDesignLandingDeckInputs['brand'], deckTitle: string): string { + return `
+
+ ${brand.mark} + ${brand.name} · ${brand.edition ?? ''} +
+
+ + ${deckTitle} +
+
`; +} + +function footStrip(idx: number, total: number, brand: OpenDesignLandingDeckInputs['brand']): string { + const counter = `${String(idx + 1).padStart(2, '0')} / ${String(total).padStart(2, '0')}`; + return `
+ ${brand.year_roman ?? brand.year ?? ''} · ${brand.location ?? ''} + ${counter} +
`; +} + +function renderCover(s: CoverSlide, assets: string): string { + return `
+
+ ${s.eyebrow} +

${mixed(s.title)}

+ ${s.subtitle ? `
${s.subtitle}
` : ''} +

${s.lead}

+ ${s.meta ? `
${s.meta}
` : ''} +
+
+ + + + + ${imgFor(s.image_slot, assets)} +
+
`; +} + +function renderSection(s: SectionSlide): string { + return `
+
${s.roman}
+

${mixed(s.title)}

+ ${s.lead ? `

${s.lead}

` : ''} +
`; +} + +function renderContent(s: ContentSlide, assets: string): string { + const layout = s.layout ?? 'left'; + const hasArt = !!s.image_slot; + return `
+
+ ${s.eyebrow ? `${s.eyebrow}` : ''} +

${mixed(s.title)}

+ ${s.body ? `

${s.body}

` : ''} + ${s.bullets && s.bullets.length ? `
    ${s.bullets.map((b) => `
  • ${b}
  • `).join('')}
` : ''} +
+ ${hasArt ? `
${imgFor(s.image_slot, assets)}
` : ''} +
`; +} + +function renderStats(s: StatsSlide): string { + const stats = s.stats + .map( + (st) => + `
+
${st.value}
+
${st.label}
+ ${st.sub ? `
${st.sub}
` : ''} +
`, + ) + .join('\n '); + return `
+
+ ${s.eyebrow ? `${s.eyebrow}` : ''} +

${mixed(s.title)}

+
+
+ ${stats} +
+ ${s.caption ? `
${s.caption}
` : ''} +
`; +} + +function renderQuote(s: QuoteSlide, assets: string): string { + const hasArt = !!s.image_slot; + return `
+
+
“${mixed(s.quote)}”
+
+ ${s.author.initial} +

${s.author.name}${s.author.title}

+
+
+ ${hasArt ? `
${imgFor(s.image_slot, assets)}
` : ''} +
`; +} + +function renderCTA(s: CTASlide): string { + return `
+ ${s.eyebrow ? `${s.eyebrow}` : ''} +

${mixed(s.title)}

+ ${s.body ? `

${s.body}

` : ''} + +
`; +} + +function renderEnd(s: EndSlide): string { + return `
+
${mixed(s.mega)}
+ ${s.footer ? `` : ''} +
`; +} + +function renderSlideBody(s: Slide, assets: string): string { + switch (s.kind) { + case 'cover': return renderCover(s, assets); + case 'section': return renderSection(s); + case 'content': return renderContent(s, assets); + case 'stats': return renderStats(s); + case 'quote': return renderQuote(s, assets); + case 'cta': return renderCTA(s); + case 'end': return renderEnd(s); + } +} + +function classFor(s: Slide): string { + switch (s.kind) { + case 'cover': return 's-cover'; + case 'section': return 's-section'; + case 'content': { + const layout = s.layout ?? 'left'; + const noArt = !s.image_slot; + return `s-content layout-${layout}${noArt ? ' no-art' : ''}`; + } + case 'stats': return 's-stats'; + case 'quote': return `s-quote${s.image_slot ? '' : ' no-art'}`; + case 'cta': return 's-cta'; + case 'end': return 's-end'; + } +} + +function renderSlide( + s: Slide, + i: number, + total: number, + inputs: OpenDesignLandingDeckInputs, + assets: string, +): string { + return `
+${chromeStrip(inputs.brand, inputs.deck_title)} +${renderSlideBody(s, assets)} +${footStrip(i, total, inputs.brand)} +
`; +} + +/* ------------------------------------------------------------------ * + * runtime script — keyboard / wheel / touch nav, dot indicator, + * progress bar, ESC overview. Mirrors `guizang-ppt`'s navigation + * model so it feels like a real magazine deck (←/→, ESC, swipe). + * ------------------------------------------------------------------ */ + +const RUNTIME_SCRIPT = ` +`; + +/* ------------------------------------------------------------------ * + * top-level + * ------------------------------------------------------------------ */ + +export function renderDeck(inputs: OpenDesignLandingDeckInputs, baseCss: string): string { + const assets = inputs.imagery.assets_path.replace(/\/?$/, '/'); + const total = inputs.slides.length; + const slides = inputs.slides + .map((s, i) => renderSlide(s, i, total, inputs, assets)) + .join('\n '); + return [ + ``, + ``, + ``, + ``, + ``, + `${inputs.deck_title}`, + ``, + ``, + ``, + ``, + ``, + ``, + ``, + `
`, + ` ${slides}`, + `
`, + ``, + `
← / → · esc · swipe
`, + `
`, + RUNTIME_SCRIPT, + ``, + ``, + ``, + ].join('\n'); +} + +async function main(): Promise { + const [, , inputsArg, outputArg] = process.argv; + if (!inputsArg || !outputArg) { + console.error('Usage: npx tsx scripts/compose.ts '); + process.exit(1); + } + + const inputsPath = isAbsolute(inputsArg) ? inputsArg : resolve(process.cwd(), inputsArg); + const outputPath = isAbsolute(outputArg) ? outputArg : resolve(process.cwd(), outputArg); + + const [inputsRaw, css] = await Promise.all([ + readFile(inputsPath, 'utf8'), + readFile(SISTER_STYLES, 'utf8'), + ]); + const inputs = JSON.parse(inputsRaw) as OpenDesignLandingDeckInputs; + const html = renderDeck(inputs, css); + + await mkdir(dirname(outputPath), { recursive: true }); + await writeFile(outputPath, html, 'utf8'); + console.log( + `✓ wrote ${outputPath} (${(html.length / 1024).toFixed(1)} KB, ${inputs.slides.length} slides)`, + ); +} + +const isMain = import.meta.url === `file://${process.argv[1]}`; +if (isMain) { + main().catch((err) => { + console.error(err); + process.exit(1); + }); +} diff --git a/skills/editorial-collage/README.md b/skills/open-design-landing/README.md similarity index 85% rename from skills/editorial-collage/README.md rename to skills/open-design-landing/README.md index 0654574dc..b049f4707 100644 --- a/skills/editorial-collage/README.md +++ b/skills/open-design-landing/README.md @@ -1,4 +1,4 @@ -# editorial-collage +# open-design-landing Reusable skill that produces a world-class editorial landing page in the **Atelier Zero** design language — the warm-paper, italic-serif, @@ -73,7 +73,7 @@ Every section has scroll-reveal motion (IntersectionObserver, respects ## Files ```text -skills/editorial-collage/ +skills/open-design-landing/ ├── SKILL.md # ← agent contract (read this first) ├── README.md # ← you are here ├── schema.ts # typed inputs (single source of truth) @@ -102,8 +102,18 @@ The `example.html` in this folder is the pre-rendered known-good demo — useful as a visual reference and for QA against the live composer output. +## Migrating from `editorial-collage` + +This skill replaces the older `editorial-collage` folder: + +- **Path:** `skills/editorial-collage/` → `skills/open-design-landing/`. +- **Shared assets:** downstream paths such as `../editorial-collage/assets/` + (for example from the slide-deck skill) should use + [`../open-design-landing/assets/`](./assets/) — see + [`open-design-landing-deck`](../open-design-landing-deck/README.md). + ## See also - [`design-systems/atelier-zero/DESIGN.md`](../../design-systems/atelier-zero/DESIGN.md) — colors, type, motion tokens. -- [`apps/landing-page/`](../../apps/landing-page/) — Next.js 16 deployable counterpart of this skill. -- [`skills/editorial-collage-deck/`](../editorial-collage-deck/) — sibling skill that produces a slide deck in the same visual language. +- [`apps/landing-page/`](../../apps/landing-page/) — Astro static site that mirrors this skill’s markup at deploy time. +- [`skills/open-design-landing-deck/`](../open-design-landing-deck/) — sibling skill that produces a slide deck in the same visual language. diff --git a/skills/editorial-collage/SKILL.md b/skills/open-design-landing/SKILL.md similarity index 86% rename from skills/editorial-collage/SKILL.md rename to skills/open-design-landing/SKILL.md index 0a427c898..14544a0c5 100644 --- a/skills/editorial-collage/SKILL.md +++ b/skills/open-design-landing/SKILL.md @@ -1,12 +1,14 @@ --- -name: editorial-collage +name: open-design-landing description: > Produce a world-class single-page editorial landing site in the Atelier Zero visual language (Monocle / Apartamento / Études editorial - collage). The agent fills a typed `inputs.json` from a brand brief, + collage) — the same aesthetic Open Design uses for its own marketing + surface. The agent fills a typed `inputs.json` from a brand brief, optionally generates 16 collage assets via gpt-image-2, then runs a - pure-function composer that emits a self-contained HTML file plus a - ready-to-deploy Next.js app. Drop-in scroll-reveal motion and a + pure-function composer that emits a self-contained HTML file; a + separate path can mirror the Astro marketing site in `apps/landing-page/`. + Drop-in scroll-reveal motion and a Headroom-style sticky nav are wired automatically. triggers: - landing page @@ -15,9 +17,12 @@ triggers: - magazine layout - hero collage - atelier zero + - open design landing od: category: brand-page surface: web + scenario: marketing + featured: 1 audience: founders, design studios, OSS maintainers tone: editorial, restrained, premium scale: viewport-anchored long-form single page @@ -71,9 +76,9 @@ parameters: default: standalone-html description: > `standalone-html` writes one self-contained .html (CSS inlined, - scripts inline, images relative). `nextjs-app` clones the - `apps/landing-page/` scaffold and wires the same content. `both` - writes both products into the output dir. + scripts inline, images relative). `nextjs-app` is the historical + enum label for cloning the Astro-based `apps/landing-page/` tree and + wiring the same content. `both` writes both products into the output dir. image_strategy: type: enum values: [generate, placeholder, bring-your-own] @@ -96,7 +101,7 @@ outputs: description: 16 collage assets, generated or placeholder per strategy. - path: /nextjs/ when: output_format in [nextjs-app, both] - description: Next.js 16 App Router scaffold mirroring apps/landing-page. + description: Astro static tree mirroring apps/landing-page (folder name is historical). capabilities_required: - file-write - http-fetch # only when image_strategy=generate @@ -108,15 +113,18 @@ example_prompt: | presets / 1 daily ritual. Use the placeholder image strategy. --- -# editorial-collage +# open-design-landing Build a single-page editorial landing site (or a slide deck — see the -sibling [`editorial-collage-deck`](../editorial-collage-deck/) skill) +sibling [`open-design-landing-deck`](../open-design-landing-deck/) skill) in the **Atelier Zero** design system: warm-paper background, Inter Tight + Playfair Display, italic serif emphasis spans, dotted hairline rules, coral terminating dots, scroll-reveal motion, and 16 surreal collage plates. +This is the canonical Open Design marketing-page recipe — the example +output is the very page you see at [open-design](https://github.com/nexu-io/open-design). + The skill is fully **parameterized**. The agent fills one typed `inputs.json` from the user's brief; the composer turns that JSON + the canonical [`styles.css`](./styles.css) into a deployable artifact. @@ -238,17 +246,18 @@ self-contained HTML file. The page includes: - Inline Headroom nav script (mirrors `header.tsx`). - Inline GitHub star-count fetcher (auto-detects from `brand.primary_url`). -### 4. (Optional) Generate the Next.js scaffold +### 4. (Optional) Mirror the deployable Astro site -For deployable production output, **fork the `apps/landing-page/` -module**: copy it to your project root, swap the JSX in `app/page.tsx` -for content from your `inputs.json`, and copy your `/assets/*.png` -into `public/assets/`. The Next.js variant supports `next build` → -static `out/` export ready for any CDN. +For deployable production output, **fork the `apps/landing-page/`** +package: copy it into your workspace, align `app/page.tsx` with content +from your `inputs.json`, and copy your `/assets/*.png` into the +paths expected by `app/image-assets.ts` / R2 URLs. Build with +`pnpm --filter @open-design/landing-page build` for a static `out/` +export ready for any CDN. -> A future iteration will bundle a `scripts/compose-nextjs.ts` that -> emits the entire `apps/landing-page/` tree from `inputs.json` so this -> step is one command. Until then, fork-and-edit is the supported path. +> A future iteration may bundle a composer that emits the full +> `apps/landing-page/` tree from `inputs.json` in one command. Until +> then, fork-and-edit is the supported path. --- @@ -272,7 +281,7 @@ Before marking done, the agent **must** verify: ## Files in this skill ```text -skills/editorial-collage/ +skills/open-design-landing/ ├── SKILL.md # this contract ├── README.md # quick-start ├── schema.ts # typed inputs (single source of truth) @@ -301,12 +310,12 @@ skills/editorial-collage/ - **Do not** wrap the composed HTML in a framework that injects its own stylesheet ordering — Atelier Zero relies on stylesheet-order cascade for paper texture and z-index of side rails. -- **Do not** add a separate stylesheet file for the Next.js variant; - copy `styles.css` verbatim into `app/globals.css` so visual parity +- **Do not** add a separate stylesheet file for the Astro landing-page + fork; copy `styles.css` verbatim into `app/globals.css` so visual parity stays one-to-one. ## See also - [`design-systems/atelier-zero/DESIGN.md`](../../design-systems/atelier-zero/DESIGN.md) — token spec. -- [`apps/landing-page/`](../../apps/landing-page/) — deployable Next.js counterpart. -- [`skills/editorial-collage-deck/`](../editorial-collage-deck/) — sibling slides skill that reuses this design system. +- [`apps/landing-page/`](../../apps/landing-page/) — deployable Astro static counterpart. +- [`skills/open-design-landing-deck/`](../open-design-landing-deck/) — sibling slides skill that reuses this design system. diff --git a/skills/editorial-collage/assets/about.png b/skills/open-design-landing/assets/about.png similarity index 100% rename from skills/editorial-collage/assets/about.png rename to skills/open-design-landing/assets/about.png diff --git a/skills/editorial-collage/assets/capabilities.png b/skills/open-design-landing/assets/capabilities.png similarity index 100% rename from skills/editorial-collage/assets/capabilities.png rename to skills/open-design-landing/assets/capabilities.png diff --git a/skills/editorial-collage/assets/cta.png b/skills/open-design-landing/assets/cta.png similarity index 100% rename from skills/editorial-collage/assets/cta.png rename to skills/open-design-landing/assets/cta.png diff --git a/skills/editorial-collage/assets/hero.png b/skills/open-design-landing/assets/hero.png similarity index 100% rename from skills/editorial-collage/assets/hero.png rename to skills/open-design-landing/assets/hero.png diff --git a/skills/editorial-collage/assets/image-manifest.json b/skills/open-design-landing/assets/image-manifest.json similarity index 99% rename from skills/editorial-collage/assets/image-manifest.json rename to skills/open-design-landing/assets/image-manifest.json index f00ebf155..972543f53 100644 --- a/skills/editorial-collage/assets/image-manifest.json +++ b/skills/open-design-landing/assets/image-manifest.json @@ -1,6 +1,6 @@ { "$schema": "https://open-design.dev/schemas/image-manifest.v1.json", - "skill": "editorial-collage", + "skill": "open-design-landing", "design_system": "atelier-zero", "default_quality": "high", "slots": [ diff --git a/skills/editorial-collage/assets/imagegen-prompts.md b/skills/open-design-landing/assets/imagegen-prompts.md similarity index 99% rename from skills/editorial-collage/assets/imagegen-prompts.md rename to skills/open-design-landing/assets/imagegen-prompts.md index 55f712e8d..03a6920ca 100644 --- a/skills/editorial-collage/assets/imagegen-prompts.md +++ b/skills/open-design-landing/assets/imagegen-prompts.md @@ -1,6 +1,6 @@ # Atelier Zero — Image Generation Prompt Pack -This pack is consumed by the `editorial-collage` skill. Every page-level +This pack is consumed by the `open-design-landing` skill. Every page-level image is rendered with `gpt-image-fal` (preferred) or `gpt-image-azure`. The pack has three layers: diff --git a/skills/editorial-collage/assets/lab-1.png b/skills/open-design-landing/assets/lab-1.png similarity index 100% rename from skills/editorial-collage/assets/lab-1.png rename to skills/open-design-landing/assets/lab-1.png diff --git a/skills/editorial-collage/assets/lab-2.png b/skills/open-design-landing/assets/lab-2.png similarity index 100% rename from skills/editorial-collage/assets/lab-2.png rename to skills/open-design-landing/assets/lab-2.png diff --git a/skills/editorial-collage/assets/lab-3.png b/skills/open-design-landing/assets/lab-3.png similarity index 100% rename from skills/editorial-collage/assets/lab-3.png rename to skills/open-design-landing/assets/lab-3.png diff --git a/skills/editorial-collage/assets/lab-4.png b/skills/open-design-landing/assets/lab-4.png similarity index 100% rename from skills/editorial-collage/assets/lab-4.png rename to skills/open-design-landing/assets/lab-4.png diff --git a/skills/editorial-collage/assets/lab-5.png b/skills/open-design-landing/assets/lab-5.png similarity index 100% rename from skills/editorial-collage/assets/lab-5.png rename to skills/open-design-landing/assets/lab-5.png diff --git a/skills/editorial-collage/assets/method-1.png b/skills/open-design-landing/assets/method-1.png similarity index 100% rename from skills/editorial-collage/assets/method-1.png rename to skills/open-design-landing/assets/method-1.png diff --git a/skills/editorial-collage/assets/method-2.png b/skills/open-design-landing/assets/method-2.png similarity index 100% rename from skills/editorial-collage/assets/method-2.png rename to skills/open-design-landing/assets/method-2.png diff --git a/skills/editorial-collage/assets/method-3.png b/skills/open-design-landing/assets/method-3.png similarity index 100% rename from skills/editorial-collage/assets/method-3.png rename to skills/open-design-landing/assets/method-3.png diff --git a/skills/editorial-collage/assets/method-4.png b/skills/open-design-landing/assets/method-4.png similarity index 100% rename from skills/editorial-collage/assets/method-4.png rename to skills/open-design-landing/assets/method-4.png diff --git a/skills/editorial-collage/assets/testimonial.png b/skills/open-design-landing/assets/testimonial.png similarity index 100% rename from skills/editorial-collage/assets/testimonial.png rename to skills/open-design-landing/assets/testimonial.png diff --git a/skills/editorial-collage/assets/work-1.png b/skills/open-design-landing/assets/work-1.png similarity index 100% rename from skills/editorial-collage/assets/work-1.png rename to skills/open-design-landing/assets/work-1.png diff --git a/skills/editorial-collage/assets/work-2.png b/skills/open-design-landing/assets/work-2.png similarity index 100% rename from skills/editorial-collage/assets/work-2.png rename to skills/open-design-landing/assets/work-2.png diff --git a/skills/editorial-collage/example.html b/skills/open-design-landing/example.html similarity index 98% rename from skills/editorial-collage/example.html rename to skills/open-design-landing/example.html index 4e45e949c..8e13ecf30 100644 --- a/skills/editorial-collage/example.html +++ b/skills/open-design-landing/example.html @@ -11,12 +11,12 @@