open-design/skills/open-design-landing/SKILL.md
Tom Huang aefba56a3f
feat(skills): open-design-landing rename, kami skills, landing OG (#428)
* feat(skills): open-design-landing rename, kami skills, landing OG

- Rename editorial-collage skills to open-design-landing and -deck; refresh examples and compose script layout
- Add kami-deck and kami-landing skills with HTML examples
- Landing page: og.astro, index wiring, and style tweaks; package.json bump
- Web i18n: German and Russian copy for renamed and new skills
- Daemon test: update skill-asset-rewrite expectations for new paths
- Design systems: README and atelier-zero doc touch-ups
- Cross-skill SKILL.md reference updates

Co-authored-by: Cursor <cursoragent@cursor.com>

* docs(landing-page): document version-slot invariant and deprecation timeline

Address P3 review notes on PR #428:
- Note the `data-github-version` wrapper invariant (version string only)
  near the canonical URL block in `app/page.tsx`.
- Expand the `formatVersion` helper comment in `app/pages/index.astro`
  with concrete `release.name` / `tag_name` example shapes for each
  branch of the regex fallback.
- Tighten the `EditorialCollageDeckInputs` deprecation in
  `skills/open-design-landing-deck/schema.ts` to a specific removal
  version (v0.4.0) and add a "Migrating from editorial-collage-deck"
  section to the skill README.

Generated-By: looper 0.4.0 (runner=fixer, agent=claude-code)

* docs(landing-page, skills): clarify version slot script and rename migrations

- Describe GitHub version slots as driven by the inline enhancement script,
  not React hydration.
- Add editorial-collage → open-design-landing migration notes; fix README
  link copy (Astro static landing app).
- Extend deck README migration table with shared asset path renames.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(daemon): alias deprecated editorial-collage skill ids

The PR renames the editorial-collage / editorial-collage-deck skills
to open-design-landing / open-design-landing-deck, but the daemon
persists exact skill_id strings on projects and resolves them via
listSkills().find((s) => s.id === storedId). After the rename, any
project saved against an old id silently composes without the intended
skill prompt because the listing no longer exposes that id.

Add a SKILL_ID_ALIASES map in skills.ts plus a findSkillById() helper
that rewrites deprecated ids to their current canonical form, then
route every server-side lookup (skill detail, example HTML, asset
proxy, system-prompt composer) through it. Cover the alias map, the
resolver, and end-to-end resolution against a temp skills directory
with a regression test.

Generated-By: looper 0.4.0 (runner=fixer, agent=claude-code)

* fix(kami-deck): route host od:slide messages through local go()

The host bridge classifies kami-deck as class-driven because go() toggles
.slide.active, but the visible slide is moved by deck.style.transform
which the bridge cannot drive. Listen for od:slide messages and dispatch
them through the local go() so toolbar next/prev and initialSlideIndex
restore actually shift the deck.

Generated-By: looper 0.4.0 (runner=fixer, agent=claude-code)

* fix(kami-deck): sync deck transform with host-driven .active changes

The previous fix added a local od:slide listener but the host bridge in
apps/web/src/runtime/srcdoc.ts also listens for the same message and
calls setActive() (toggles .slide.active) without driving the deck
transform. Both listeners fired, the bridge re-read the just-toggled
active class, and overshot by one — and the bridge's restoreInitialSlide
path could move .active without a message at all, leaving the deck on
the original transform.

Stop the bridge from double-handling by calling stopImmediatePropagation
in the local listener (registered first because the bridge script is
appended to </body>), and add a MutationObserver that pulls the deck
transform onto whichever slide currently carries .active so the bridge's
direct setActive calls (notably the initial-slide restore) move the deck
too.

Generated-By: looper 0.4.0 (runner=fixer, agent=claude-code)

* fix(i18n): align French content with renamed/new skills

PR #434 (French localization) merged into main with French copy for the
old editorial-collage / editorial-collage-deck skill ids; this branch
renamed those to open-design-landing / open-design-landing-deck and
added kami-deck and kami-landing. Update content.fr.ts to track the
rename and add French copy for the new kami skills so the
LOCALIZED_CONTENT_IDS coverage test passes once main is merged.

Generated-By: looper 0.4.0 (runner=fixer, agent=claude-code)

* fix(open-design-landing-deck): sync deck transform with host-driven .active changes

Apply the same fix that landed in skills/kami-deck/example.html
(commits 96b255b, 8cbca30) to the open-design-landing-deck composer
runtime: the host bridge classifies this deck as class-driven because
go() toggles .slide.active, but the visible slide is moved by
deck.style.transform which the bridge can't drive. Add an od:slide
message listener that calls stopImmediatePropagation() and routes nav
through the local go(), plus a MutationObserver that pulls the deck
transform onto whichever slide carries .active so the bridge's direct
setActive calls (notably restoreInitialSlide) move the deck too.

Regenerates example.html via scripts/compose.ts; the regeneration also
picks up upstream nav-cta and brand-meta CSS additions in the sister
open-design-landing styles.css that the example had drifted from.

Generated-By: looper 0.4.0 (runner=fixer, agent=claude-code)

* docs(open-design-landing): align deploy story with Astro landing app

- Update SKILL contract: apps/landing-page is Astro static; clarify
  nextjs-app output_format as a historical enum label and <out>/nextjs
  as a legacy folder name.
- Replace optional-deploy section with fork + pnpm --filter landing-page build.
- Fix styles.css header and regenerate landing + deck example.html so
  inlined comments match.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(deck-runtime): bypass interaction lock for host/observer slide sync

The slide deck runtimes for kami-deck and open-design-landing-deck
gate go() behind a 700ms `lock` so wheel/key/touch input bursts can't
overshoot the transform transition. But applying the same gate to the
host bridge's od:slide messages and the MutationObserver watching
`.slide.active` creates a startup race: go(0) at the end of init sets
lock=true, and any host-driven `.active` change inside that window
(notably restoreInitialSlide) fires the observer, which calls go(i),
which exits at the lock guard — leaving the visible deck on slide 1
while the host counter advances to N.

Split the actual state update into an unthrottled `applySlide(n)`
helper that updates transform, `.active`, dot nav, and the progress
bar. Keep `lock` only on the user-input path through `go()`. Route
the message listener, the MutationObserver, and the initial render
through `applySlide` directly so host-driven sync always reaches the
deck transform regardless of the throttle state.

Generated-By: looper 0.4.0 (runner=fixer, agent=claude-code)

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-04 19:22:46 +08:00

13 KiB
Raw Blame History


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 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; 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
  • 落地页
  • editorial site
  • 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 craft: requires:
    • pixel-discipline
    • typographic-rhythm inputs:
  • id: brand label: Brand identity description: Name, mark, tagline, location, languages, license, repo url. schema_path: ./schema.ts#BrandBlock
  • id: nav label: Navigation links description: Up to 5 nav entries, each with optional count badge. schema_path: ./schema.ts#NavLink
  • id: hero label: Hero copy + 3 stat rings + 4-step index schema_path: ./schema.ts#HeroBlock
  • id: about label: Manifesto / about block schema_path: ./schema.ts#AboutBlock
  • id: capabilities label: 4 capability cards schema_path: ./schema.ts#CapabilitiesBlock
  • id: labs label: 5 lab cards + filter pills schema_path: ./schema.ts#LabsBlock
  • id: method label: 4 method steps with thumbnails schema_path: ./schema.ts#MethodBlock
  • id: work label: 2 selected-work cards on dark slab schema_path: ./schema.ts#WorkBlock
  • id: testimonial label: Pull quote + author + 5 partner glyphs schema_path: ./schema.ts#TestimonialBlock
  • id: cta label: Closing CTA + ribbon schema_path: ./schema.ts#CTABlock
  • id: footer label: Brand description + 4 link columns + mega kicker schema_path: ./schema.ts#FooterBlock
  • id: imagery label: Image strategy (generate / placeholder / bring-your-own) schema_path: ./schema.ts#ImageryConfig parameters: output_format: type: enum values: [standalone-html, nextjs-app, both] default: standalone-html description: > standalone-html writes one self-contained .html (CSS inlined, 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] default: placeholder description: > generate calls gpt-image-2 (fal.ai or Azure) for all 16 slots. placeholder writes paper-textured SVG frames so the layout is fully visible without an image budget. bring-your-own assumes the user has dropped 16 PNGs at imagery.assets_path already. image_provider: type: enum values: [fal, azure] default: fal description: Provider for image_strategy: generate. fal.ai is faster. outputs:
  • path: /index.html when: output_format in [standalone-html, both] description: Self-contained HTML with Atelier Zero CSS inlined.
  • path: /assets/*.png (or *.svg) description: 16 collage assets, generated or placeholder per strategy.
  • path: /nextjs/ when: output_format in [nextjs-app, both] description: Astro static tree mirroring apps/landing-page (folder name is historical). capabilities_required:
  • file-write
  • http-fetch # only when image_strategy=generate
  • node-runtime # tsx or compatible example_prompt: | Build me an editorial landing page for "Lumen Field", an indie studio shipping a soundscape app for focus. Coral accent, Berlin coordinates, mention the iOS Beta TestFlight, three stats: 12 soundscapes / 4 presets / 1 daily ritual. Use the placeholder image strategy.

open-design-landing

Build a single-page editorial landing site (or a slide deck — see the sibling 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.

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 into a deployable artifact.

inputs.json + styles.css                       16 image slots
        │                                            │
        └──────────► scripts/compose.ts ◄────────────┘
                            │
                            ▼
              <out>/index.html  (self-contained)
              <out>/assets/      (PNG or SVG)

What you get

A single HTML file with all of:

  • Editorial topbar (volume / issue / language strip), Headroom-style sticky nav with live GitHub star count.
  • 8 numbered Roman-numeral sections with paper-textured background: hero (with 3 stat rings + 4-step index), about, capabilities (4 cards), labs (5 cards + filter pills + progress bar), method (4 steps with thumbnails), selected work (dark slab + 2 tilted cards), testimonial (pull quote + 5 partner glyphs), CTA (ribbon + email pill).
  • Footer with 4 link columns + huge italic-serif kicker word.
  • Scroll-reveal motion on every section (IntersectionObserver, respects prefers-reduced-motion).
  • Fully responsive at 1280 / 1080 / 880 / 560 breakpoints.

Workflow contract

Run these four steps in order. The agent should complete each step before moving on, and prefer asking the user a focused question over inventing copy.

1. Gather brand inputs

Use AskQuestion (or the equivalent in your UI) to collect the brand brief in chunks; do not dump the entire schema.ts on the user. Map their answers into inputs.json matching the typed shape.

The eight question groups, in order:

Group Schema fields Min answers Notes
1 brand.{name,mark,tagline,description,location} 5 Mark = single glyph (Ø, ▲, ★…)
2 brand.{license,version,year,primary_url,contact_email} 4 URL is required; license defaults Apache-2.0
3 nav[] (up to 5) 3 Optional count badges
4 hero.{label,headline,lead,primary,secondary,stats} All Headline as MixedText (sans+em+dot)
5 about + capabilities.cards[4] All 4 cards × {num,tag,title,body}
6 labs.cards[5] + method.steps[4] All Both grids fixed-arity
7 work.cards[2] + testimonial All 5 partner glyphs as inline SVG path data
8 cta + footer.{columns[4],mega} All Mega kicker is a MixedText like the headlines

Open inputs.example.json for a complete worked example (Open Design itself).

2. Decide the image strategy

Strategy When to choose Cost / latency
placeholder First pass. Demo. Slide internal. No image budget yet. $0, <1s
generate Final delivery. Brand wants original collages. ~$0.40, ~6 min
bring-your-own User has art direction PNGs. Drop them at assets_path. $0, 0s

Set inputs.imagery.strategy accordingly.

placeholder — frame mode

npx tsx scripts/placeholder.ts <out>/assets/

Writes 16 .svg files (with .png aliases for compatibility) into <out>/assets/. Each placeholder shows the slot id, ratio, pixel dimensions, and the prompt hint from image-manifest.json. The composer's <img src='./assets/hero.png'> etc. just work.

generate — gpt-image-2 mode

FAL_KEY=... npx tsx scripts/imagegen.ts <inputs.json> --out=<out>/assets/

Calls fal.ai's openai/gpt-image-2 synchronous endpoint per slot. Composes prompts as: style anchor (paper-collage editorial system)

  • brand variables (name / nav / headline / italic emphasis pulled from inputs.json) + per-slot composition (e.g. cropped plaster head + tree growing through arch). Skips slots whose target file already exists; pass --force to re-render.

Without FAL_KEY, the script prints the prompts so the operator can route them through the /gpt-image-fal slash-command skill manually.

bring-your-own

Drop 16 PNGs matching assets/image-manifest.json filenames at inputs.imagery.assets_path. Done.

3. Compose the artifact

npx tsx scripts/compose.ts <inputs.json> <out>/index.html

The composer reads inputs.json and ../styles.css, then writes one self-contained HTML file. The page includes:

  • The full Atelier Zero stylesheet, inlined.
  • All section markup with data-reveal attributes for staggered scroll motion.
  • Inline IntersectionObserver script (mirrors apps/landing-page/app/_components/reveal-root.tsx).
  • Inline Headroom nav script (mirrors header.tsx).
  • Inline GitHub star-count fetcher (auto-detects from brand.primary_url).

4. (Optional) Mirror the deployable Astro site

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 <out>/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 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.


Self-check before delivering

Before marking done, the agent must verify:

  • <out>/index.html opens in a browser without console errors.
  • All 16 image slots load (no 404s in DevTools network tab).
  • Headline italic emphasis spans render in Playfair (not sans).
  • Coral terminating dots appear at every display h1/h2 end.
  • Scroll from top to bottom; every section animates in once.
  • Resize to 880px and 560px; no horizontal scroll, no overlap.
  • prefers-reduced-motion: reduce (DevTools → Rendering) disables transitions cleanly.
  • Lighthouse: contrast AA, font-display swap, no layout shift on the hero (CLS < 0.05).

Files in this skill

skills/open-design-landing/
├── SKILL.md                 # this contract
├── README.md                # quick-start
├── schema.ts                # typed inputs (single source of truth)
├── styles.css               # Atelier Zero stylesheet (single source of truth)
├── inputs.example.json      # Open Design as the worked example
├── example.html             # canonical rendering (regenerated from inputs.example.json)
├── scripts/
│   ├── compose.ts           # inputs.json + styles.css → index.html
│   ├── imagegen.ts          # gpt-image-2 wrapper (fal.ai)
│   └── placeholder.ts       # SVG paper-textured frames
└── assets/
    ├── *.png                # 16 collage plates (Open Design instance)
    ├── image-manifest.json  # slot → file/dimensions/prompt mapping
    └── imagegen-prompts.md  # human-readable prompt pack

Boundaries

  • Do not invent new colors or typefaces. Tokens live in design-systems/atelier-zero/DESIGN.md; extend the design system before adding a new ramp here.
  • Do not drop data-reveal attributes from generated markup. Without them the page goes static and feels dead.
  • 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 Astro landing-page fork; copy styles.css verbatim into app/globals.css so visual parity stays one-to-one.

See also