open-design/apps/landing-page/app/page.tsx
Jane d66a463d62
feat(landing-page): 301 legacy /skills /systems /templates to /plugins (#3352)
The 2026-05 plugins library rebuild introduced /plugins/skills/,
/plugins/systems/, /plugins/templates/ and a unified detail route
/plugins/<manifest-slug>/, but the old /skills/, /systems/, /templates/
catalogs were left live in parallel. Two equivalent page trees split SEO
equity, and the homepage, footer, quickstart, agents, official and blog
pages all still linked to the old routes.

Retire the legacy generators and 301 every old URL to its new plugins
equivalent so inbound links and search equity are preserved:

- Remove the /skills, /systems, /templates page generators (English +
  [locale] wrappers) and the now-orphaned skill-row component, and prune
  the skills/systems/templates branches from the [locale]/[...path]
  catch-all (it now renders only craft + blog).
- Add the migration block to public/_redirects. Detail slugs differ from
  the old folder names (new slugs are manifest-name based, e.g.
  design-system-<x>, example-<x>), so systems/templates use a prefixed
  splat plus a short degrade list, and skills map the 27 with a template
  equivalent explicitly while the ~110 instruction-only skills and all
  mode/scenario/category facet pages degrade to the section landing.
  'replicate' is forced to the section to avoid colliding with the
  design-system of the same name. Locale variants (zh, zh-tw, ja, ko)
  strip to the section.
- Repoint in-site links to /plugins/* across page.tsx (footer, work,
  labs pills), info-page-i18n.ts (en + zh + sourceNames), official,
  quickstart, agents, blog and html-anything, and update the sitemap
  serialize priority list. The system-card keeps linking through
  /systems/<slug>/ so the 8 systems without a detail page ride the
  redirect's degrade rather than pointing at a missing page.

Verified with a full astro build: old routes no longer emit any HTML,
the new section pages exist, _redirects is copied verbatim, and no
in-site link targets a removed route (the remaining /systems/<slug>/
hrefs are the system cards that 301 by design). astro check passes.

Co-authored-by: Joey-nexu <joeylee12629@gmail.com>
2026-05-31 01:04:20 +00:00

1453 lines
56 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* Open Design — Atelier Zero landing page.
*
* Mirrors `design-templates/open-design-landing/example.html` 1:1. When the canonical
* example.html changes, mirror the diff here and into `app/globals.css`.
*
* Static React component rendered by Astro. The Header and Wire components
* own the small client-side behaviors; promote other sections to Astro
* islands only when behavior is needed.
*/
import { Header, type HeaderProps } from './_components/header';
import { Wire } from './_components/wire';
import {
DEFAULT_LOCALE,
LANDING_LOCALES,
getCommonCopy,
getHomePageCopy,
getLocaleDefinition,
localePath,
localizedHref,
type LandingLocaleCode,
} from './i18n';
import {
heroImage,
heroImageSrcset,
imageAsset,
PRECISE_LAZY_PLACEHOLDER,
} from './image-assets';
/**
* `<img>` wrapper for non-hero homepage images. Outputs `data-precise-src`
* so the global IntersectionObserver in `precise-lazyload.astro` swaps it
* to a real `src` once the element enters viewport ± 300px. Avoids the
* Chrome native-lazy 12503000px over-prefetch on this image-heavy page.
*
* Use a plain `<img>` (NOT this) for above-the-fold or LCP-critical images
* where waiting on IntersectionObserver would defeat the priority hint.
*/
function LazyImg(props: { src: string; alt?: string; className?: string }) {
return (
<img
src={PRECISE_LAZY_PLACEHOLDER}
data-precise-src={props.src}
alt={props.alt ?? ''}
className={props.className}
decoding='async'
/>
);
}
function BreakText({ text }: { text: string }) {
return (
<>
{text.split('\n').map((part, index) => (
<span key={`${part}-${index}`}>
{index > 0 ? <br /> : null}
{part}
</span>
))}
</>
);
}
const arrowOut = (
<svg viewBox='0 0 24 24'>
<path d='M5 19L19 5M19 5H8M19 5v11' />
</svg>
);
const arrowPlus = (
<svg viewBox='0 0 24 24'>
<circle cx='12' cy='12' r='9' />
<path d='M9 12h6M12 9v6' />
</svg>
);
const NBSP = '\u00A0';
// Canonical project URLs. Keep in sync with design-templates/open-design-landing/example.html.
//
// `data-github-version` invariant: every wrapper must contain ONLY the version
// string (e.g. `v0.3.0`), never any surrounding label or punctuation. The
// inline enhancement script in `app/pages/index.astro` assigns `textContent`
// on each slot, so any extra text inside the wrapper would be clobbered.
const REPO = 'https://github.com/nexu-io/open-design';
const REPO_RELEASES = `${REPO}/releases`;
const REPO_ISSUES = `${REPO}/issues`;
const REPO_CONTRIBUTORS = `${REPO}/graphs/contributors`;
const REPO_DAEMON = `${REPO}/tree/main/apps/daemon`;
const REPO_SKILLS = `${REPO}/tree/main/skills`;
const REPO_DESIGN_SYSTEMS = `${REPO}/tree/main/design-systems`;
const REPO_DOCS = `${REPO}#readme`;
const DISCORD = 'https://discord.gg/9ptkbbqRu';
// Lineage / inspiration projects — make every brand mention clickable.
const LINEAGE = {
'huashu-design': 'https://github.com/alchaincyf/huashu-design',
'guizang-ppt': 'https://github.com/op7418/guizang-ppt-skill',
'multica-ai': 'https://github.com/multica-ai/multica',
'open-codesign': 'https://github.com/OpenCoworkAI/open-codesign',
'devin-cli': 'https://devin.ai/terminal',
hyperframes: 'https://github.com/heygen-com/hyperframes',
} as const;
const ext = {
target: '_blank',
rel: 'noreferrer noopener',
} as const;
// Global wire — cities the studio is composed from. The cities feed
// the top counter-scrolling marquee in the editorial ticker between
// the hero and the About section; the bottom contributor marquee is
// owned by `<Wire />`, which fetches the actual repo contributors
// from GitHub at runtime. Keep coordinates rough to fit the
// editorial register.
const WIRE_CITIES = [
{ name: 'Berlin', coord: '52.52°N' },
{ name: 'Tokyo', coord: '35.68°N' },
{ name: 'Shanghai', coord: '31.23°N' },
{ name: 'Beijing', coord: '39.90°N' },
{ name: 'Taipei', coord: '25.03°N' },
{ name: 'Singapore', coord: '1.35°N' },
{ name: 'Bangalore', coord: '12.97°N' },
{ name: 'Dubai', coord: '25.20°N' },
{ name: 'Lagos', coord: '6.52°N' },
{ name: 'Nairobi', coord: '1.29°S' },
{ name: 'Cape Town', coord: '33.92°S' },
{ name: 'Lisbon', coord: '38.72°N' },
{ name: 'Madrid', coord: '40.42°N' },
{ name: 'Paris', coord: '48.86°N' },
{ name: 'London', coord: '51.51°N' },
{ name: 'Amsterdam', coord: '52.37°N' },
{ name: 'Stockholm', coord: '59.33°N' },
{ name: 'Toronto', coord: '43.65°N' },
{ name: 'New York', coord: '40.71°N' },
{ name: 'San Francisco', coord: '37.77°N' },
{ name: 'Mexico City', coord: '19.43°N' },
{ name: 'São Paulo', coord: '23.55°S' },
{ name: 'Sydney', coord: '33.87°S' },
] as const;
/**
* Question / answer pair for the visible homepage FAQ. The exact same
* shape is consumed by the FAQPage JSON-LD in `pages/index.astro`, so
* the two stay in lockstep: every schema entry has a visible answer on
* the page (which Google requires for the rich result to be eligible).
*/
export interface HomeFaqEntry {
q: string;
a: string;
}
interface PageProps {
/**
* Live counts from the Markdown catalogs. Required: every visible
* "X skills / Y systems" claim on the page reads from here so meta,
* nav, hero copy, capability cards, labs pills, selected-work
* fractions, and the footer Library never disagree.
*/
counts: HeaderProps['counts'] & {
/** Optional richer breakdown used by the Labs filter pills. */
byMode?: Readonly<Record<string, number>>;
byPlatform?: Readonly<Record<string, number>>;
};
github: {
starsLabel: string;
versionLabel: string;
};
/**
* FAQ pairs the page renders above the contact section. Required so
* the structured-data block on `/` can reference visible content
* verbatim — see `FAQ Rules` in `growth/seo-opendesigner-analysis.md`.
*/
faq: ReadonlyArray<HomeFaqEntry>;
/** Locale for shared chrome, topbar language links, and localized FAQ text. */
locale?: LandingLocaleCode;
}
/**
* Format a count for inline editorial copy. Returns the live value when
* positive (so a fresh `git pull` immediately reflects the new totals),
* falls back to a neutral em-dash when the catalog couldn't be read so
* we never publish "0 skills" to a visitor by mistake.
*/
function fmt(n: number | undefined): string {
return typeof n === 'number' && n > 0 ? String(n) : '—';
}
/** Two-digit padded count for the Labs pills (matches the "04", "27" feel). */
function pad2(n: number | undefined): string {
if (typeof n !== 'number' || n <= 0) return '—';
return n < 10 ? `0${n}` : String(n);
}
export default function Page({
counts,
github,
faq,
locale = DEFAULT_LOCALE,
}: PageProps) {
const skills = fmt(counts.skills);
const systems = fmt(counts.systems);
const deckCount = pad2(counts.byMode?.deck);
const prototypeCount = pad2(counts.byMode?.prototype);
const mobileCount = pad2(counts.byPlatform?.mobile);
const commonCopy = getCommonCopy(locale);
const home = getHomePageCopy(locale);
const localeDef = getLocaleDefinition(locale);
const localeOptions = LANDING_LOCALES.map((entry) => ({
...entry,
href: localePath(entry.code, '/'),
}));
const href = (path: string) => localizedHref(path, locale);
return (
<>
{/* side rails (rotated brand text) */}
<div className='side-rail right' data-od-id='rail-right'>
<span className='rail-text'>{home.rail.right}</span>
</div>
<div className='side-rail left' data-od-id='rail-left'>
<span className='rail-text'>{home.rail.left}</span>
</div>
<div className='shell'>
{/* ====== STICKY CHROME (topbar + nav as one unit) ====== */}
<div className='site-chrome' data-chrome-headroom>
{/* ====== TOP METADATA STRIP ====== */}
<div className='topbar' data-od-id='topbar'>
<div className='container topbar-inner'>
<span>
<b>OD / 2026</b>
{NBSP}·{NBSP}
{commonCopy.topbar.issue ?? 'Vol. 01 / Issue Nº 26'}
</span>
<span className='mid'>
<span>
{commonCopy.topbar.filedUnder}{' '}
<b className='coral'>{commonCopy.topbar.category}</b>
</span>
<span>{commonCopy.topbar.madeOnEarth}</span>
</span>
<span className='right'>
<a className='topbar-link' href={REPO_RELEASES} {...ext}>
<span className='pulse' />
{commonCopy.topbar.live} ·{' '}
<span data-github-version>{github.versionLabel}</span>
</a>
<details className='locale-switch' data-locale-switch>
<summary
className='locale-trigger'
aria-label={commonCopy.topbar.languageSwitcherLabel}
>
<span className='locale-trigger-prefix' aria-hidden='true'>
{commonCopy.topbar.languageSwitcherPrefix ?? 'Lang'}
</span>
<span className='locale-trigger-sep' aria-hidden='true'>
·
</span>
<span className='locale-trigger-code'>
{localeDef.shortLabel}
</span>
<svg
className='locale-trigger-caret'
viewBox='0 0 8 5'
aria-hidden='true'
focusable='false'
>
<path
d='M0.5 0.75 L4 4 L7.5 0.75'
fill='none'
stroke='currentColor'
strokeWidth='1'
strokeLinecap='square'
/>
</svg>
</summary>
<div className='locale-menu' role='menu'>
{localeOptions.map((entry) => (
<a
className={`locale-menu-item${
entry.code === locale ? ' is-active' : ''
}`}
role='menuitem'
data-locale-link
data-locale-code={entry.code}
href={entry.href}
lang={entry.htmlLang}
aria-current={entry.code === locale ? 'true' : undefined}
key={entry.code}
>
<span className='locale-menu-code'>
{entry.code.toUpperCase()}
</span>
<span className='locale-menu-label'>{entry.label}</span>
</a>
))}
</div>
</details>
</span>
</div>
</div>
{/* ====== NAV ====== */}
{/* Headroom slide handled by `.site-chrome` wrapper above. */}
<Header counts={counts} github={github} locale={locale} />
</div>{/* /site-chrome */}
{/* ====== HERO ====== */}
<section className='hero' id='top' data-od-id='hero'>
<div className='container hero-grid'>
<div className='hero-copy'>
<a
className='hero-discord-pill'
href={DISCORD}
aria-label={home.hero.discordAria}
{...ext}
data-reveal
>
<span aria-hidden='true'></span>
{home.hero.joinDiscord}
</a>
<span className='label' data-reveal>
{home.hero.label} <span className='ix'>· {home.hero.issue}</span>
</span>
<h1 className='display' data-reveal>
{home.hero.titlePrefix} <em>{home.hero.titleEmphasis}</em>{' '}
{home.hero.titleMiddle} <em>{home.hero.titleSecondEmphasis}</em>
<span className='dot'>.</span>
</h1>
<p className='lead' data-reveal>
{home.hero.lead(skills, systems)}
</p>
<div className='hero-actions' data-reveal>
<a className='btn btn-primary' href={REPO} {...ext}>
{home.hero.star}
<span className='arrow'>{arrowOut}</span>
</a>
<a className='btn btn-ghost' href={REPO_RELEASES} {...ext}>
{home.hero.download}
<span className='arrow'>{arrowPlus}</span>
</a>
</div>
<div className='hero-stats' data-reveal>
<div className='stat'>
<span className='ring solid'>{skills}</span>
<span className='stat-label'>
<b>{home.hero.stats[0].strong}</b>
{home.hero.stats[0].text}
</span>
</div>
<div className='stat'>
<span className='ring'>{systems}</span>
<span className='stat-label'>
<b>{home.hero.stats[1].strong}</b>
{home.hero.stats[1].text}
</span>
</div>
<div className='stat'>
<span className='ring coral'>12</span>
<span className='stat-label'>
<b>{home.hero.stats[2].strong}</b>
{home.hero.stats[2].text}
</span>
</div>
</div>
<div className='hero-foot' data-reveal>
<span className='meta'>{NBSP}{NBSP}{home.hero.foot}</span>
<span className='coord'>
52.5200° N{NBSP}·{NBSP}13.4050° E
</span>
</div>
</div>
<div className='hero-art' data-reveal='scale'>
<span className='corner tl' />
<span className='corner tr' />
<span className='corner bl' />
<span className='corner br' />
<span className='annot annot-tl coord'>FIG. 01 / OD-26</span>
<span className='annot annot-tr'>{home.hero.plate}</span>
<span className='annot annot-bl coord'>SHA · a1b2c3d</span>
<span className='annot annot-br'>
{home.hero.composedIn}
{NBSP}
<span style={{ color: 'var(--coral)' }}>Open Design</span>
</span>
<img
src={heroImage}
srcSet={heroImageSrcset}
sizes='(max-width: 768px) 100vw, 60vw'
width={1280}
height={1600}
alt=''
fetchPriority='high'
decoding='async'
/>
<div className='index'>
<span>
<span className='n'>01</span>
{home.hero.index[0]}
</span>
<span className='on'>
<span className='n'>02</span>
{home.hero.index[1]}
</span>
<span>
<span className='n'>03</span>
{home.hero.index[2]}
</span>
<span>
<span className='n'>04</span>
{home.hero.index[3]}
</span>
</div>
</div>
</div>
</section>
{/* ====== WIRE / GLOBAL TICKER ====== */}
{/*
* Slim editorial ticker between the hero and About. Two
* counter-scrolling marquees signal that the project is
* global (cities, top row) and contributor-driven (handles,
* bottom row). Pure CSS animation; the track content is
* doubled in markup so the loop wraps seamlessly.
*
* Lives inside a client island because the contributor row is
* fetched live from the GitHub contributors API; the cities
* row is passed through as static data.
*/}
<Wire cities={WIRE_CITIES} />
{/* ====== OFFICIAL SOURCE STRIP ======
*
* Thin attestation band that reinforces the canonical surfaces:
* official site, GitHub repo, releases, download, docs, Discord.
* Mirrors the Organization.sameAs + SoftwareApplication signals
* emitted in `pages/index.astro` so both Google entity-merge and
* human verification see the same six links in the same order.
* Keep this small (one line of icons + labels); the editorial
* sections below carry the heavy explanation.
*/}
<section
className='official-strip'
data-od-id='official-strip'
aria-label={home.official.aria}
>
<div className='container'>
<div className='official-strip-inner' data-reveal>
<span className='official-strip-label'>
{home.official.label} <span className='ix'>· 00</span>
</span>
<ul className='official-strip-list'>
<li>
<a href={href('/official/')}>
<span className='label'>{home.official.items[0].label}</span>
<span className='value'>{home.official.items[0].value}</span>
</a>
</li>
<li>
<a href={REPO} {...ext}>
<span className='label'>{home.official.items[1].label}</span>
<span className='value'>{home.official.items[1].value}</span>
</a>
</li>
<li>
<a href={REPO_RELEASES} {...ext}>
<span className='label'>{home.official.items[2].label}</span>
<span className='value' data-github-version>
{github.versionLabel}
</span>
</a>
</li>
<li>
<a href={REPO_RELEASES} {...ext}>
<span className='label'>{home.official.items[3].label}</span>
<span className='value'>{home.official.items[3].value}</span>
</a>
</li>
<li>
<a href={REPO_DOCS} {...ext}>
<span className='label'>{home.official.items[4].label}</span>
<span className='value'>{home.official.items[4].value}</span>
</a>
</li>
<li>
<a href={DISCORD} {...ext}>
<span className='label'>{home.official.items[5].label}</span>
<span className='value'>{home.official.items[5].value}</span>
</a>
</li>
</ul>
</div>
</div>
</section>
{/* ====== ABOUT ====== */}
<section className='about' data-od-id='about'>
<div className='container'>
<div className='sec-rule'>
<span className='roman'>I.</span>
<span className='meta-grp'>
<span>{home.about.rule}</span>
<span className='dot-mark'></span>
<span>{home.about.volume}</span>
</span>
<span>002 / 008</span>
</div>
<div className='about-grid'>
<div className='about-copy' data-reveal>
<span className='label'>
{home.about.label} <span className='ix'>· 02</span>
</span>
<h2 className='display'>
{home.about.titlePrefix} <em>{home.about.titleAgent}</em>{' '}
{home.about.titleMiddle} <em>{home.about.titleCollaborator}</em>{' '}
{home.about.titleSuffix}
<span className='dot'>.</span>
</h2>
<p className='lead'>{home.about.lead}</p>
<a className='btn btn-ghost' href={REPO_DAEMON} {...ext}>
{home.about.approach}
<span className='arrow'>{arrowOut}</span>
</a>
<div className='footer-row'>
<span className='mark'>Ø</span>
<span>{home.about.practice}</span>
<span className='stamp'>
<span>{home.about.stampTop}</span>
<span style={{ color: 'var(--ink)' }}>
{home.about.stampBottom}
</span>
</span>
</div>
</div>
<div className='about-art' data-reveal='right'>
<LazyImg src={imageAsset('about.png', { width: 1024, quality: 82 })} />
<div className='about-side-note'>
<b />
{home.about.sideNote.map((line) => (
<span key={line}>
{line}
<br />
</span>
))}
</div>
<div className='about-caption'>
<b>{home.about.caption}</b>
</div>
</div>
</div>
</div>
</section>
{/* ====== CAPABILITIES ====== */}
<section
className='capabilities'
id='agents'
data-od-id='capabilities'
>
<div className='container'>
<div className='sec-rule'>
<span className='roman'>II.</span>
<span className='meta-grp'>
<span>{home.capabilities.rule}</span>
<span className='dot-mark'></span>
<span>{home.capabilities.surfaces}</span>
</span>
<span>003 / 008</span>
</div>
<div className='capabilities-grid'>
<div className='capabilities-art' data-reveal='left'>
<span className='corner tl' />
<span className='corner br' />
<LazyImg src={imageAsset('capabilities.png', { width: 1024, quality: 82 })} />
<div className='ribbon'>
<b>{home.capabilities.ribbon}</b>
</div>
</div>
<div className='capabilities-copy' data-reveal>
<span className='label'>
{home.capabilities.label} <span className='ix'>· 03</span>
</span>
<h2 className='display'>
{home.capabilities.titlePrefix}{' '}
<em>{home.capabilities.titleEmphasis}</em>{' '}
{home.capabilities.titleSuffix}
<span className='dot'>.</span>
</h2>
<p className='lead'>{home.capabilities.lead}</p>
<div className='cards'>
<div className='card' data-reveal>
<div className='num'>
01<span className='tag'>{home.capabilities.cards[0].tag}</span>
</div>
<svg
className='icon'
viewBox='0 0 24 24'
fill='none'
stroke='currentColor'
strokeWidth='1.5'
>
<circle cx='9' cy='9' r='5' />
<path d='M14 14l5 5' />
</svg>
<h3>
<BreakText text={home.capabilities.cards[0].title} />
</h3>
<p>{home.capabilities.cards[0].body(skills, systems)}</p>
<a
className='arrow-mark'
href={REPO_SKILLS}
aria-label={home.capabilities.cards[0].aria}
{...ext}
>
{arrowOut}
</a>
</div>
<div className='card' data-reveal>
<div className='num'>
02<span className='tag'>{home.capabilities.cards[1].tag}</span>
</div>
<svg
className='icon'
viewBox='0 0 24 24'
fill='none'
stroke='currentColor'
strokeWidth='1.5'
>
<rect x='3.5' y='3.5' width='8' height='8' />
<rect x='12.5' y='3.5' width='8' height='8' />
<rect x='3.5' y='12.5' width='8' height='8' />
<rect x='12.5' y='12.5' width='8' height='8' />
</svg>
<h3>
<BreakText text={home.capabilities.cards[1].title} />
</h3>
<p>{home.capabilities.cards[1].body(skills, systems)}</p>
<a
className='arrow-mark'
href={REPO_DESIGN_SYSTEMS}
aria-label={home.capabilities.cards[1].aria}
{...ext}
>
{arrowOut}
</a>
</div>
<div className='card' data-reveal>
<div className='num'>
03<span className='tag'>{home.capabilities.cards[2].tag}</span>
</div>
<svg
className='icon'
viewBox='0 0 24 24'
fill='none'
stroke='currentColor'
strokeWidth='1.5'
>
<circle cx='8' cy='12' r='4.5' />
<circle cx='16' cy='12' r='4.5' />
</svg>
<h3>
<BreakText text={home.capabilities.cards[2].title} />
</h3>
<p>{home.capabilities.cards[2].body(skills, systems)}</p>
<a
className='arrow-mark'
href={REPO_DAEMON}
aria-label={home.capabilities.cards[2].aria}
{...ext}
>
{arrowOut}
</a>
</div>
<div className='card' data-reveal>
<div className='num'>
04<span className='tag'>{home.capabilities.cards[3].tag}</span>
</div>
<svg
className='icon'
viewBox='0 0 24 24'
fill='none'
stroke='currentColor'
strokeWidth='1.5'
>
<path d='M5 8h14v8H5z' />
<path d='M9 12h6M12 9v6' />
</svg>
<h3>
<BreakText text={home.capabilities.cards[3].title} />
</h3>
<p>{home.capabilities.cards[3].body(skills, systems)}</p>
<a
className='arrow-mark'
href={REPO}
aria-label={home.capabilities.cards[3].aria}
{...ext}
>
{arrowOut}
</a>
</div>
</div>
</div>
</div>
</div>
</section>
{/* ====== LABS ====== */}
<section className='labs' id='labs' data-od-id='labs'>
<div className='container'>
<div className='sec-rule'>
<span className='roman'>III.</span>
<span className='meta-grp'>
<span>{home.labs.rule}</span>
<span className='dot-mark'></span>
<span>{home.labs.ongoing(skills)}</span>
</span>
<span>004 / 008</span>
</div>
<div className='labs-head'>
<div data-reveal>
<span className='label'>
{home.labs.label} <span className='ix'>· 04</span>
</span>
<h2 className='display' style={{ marginTop: 30 }}>
{home.labs.titlePrefix} <em>{home.labs.titleEmphasis}</em>{' '}
{home.labs.titleSuffix}
<span className='dot'>.</span>
</h2>
</div>
<div className='pills' data-reveal='right'>
<a className='pill active' href={href('/plugins/skills/')}>
{home.labs.pills.all}
<span className='count'>{skills}</span>
</a>
<a className='pill' href={href('/plugins/templates/')}>
{home.labs.pills.prototype}
<span className='count'>{prototypeCount}</span>
</a>
<a className='pill' href={href('/plugins/templates/')}>
{home.labs.pills.deck}
<span className='count'>{deckCount}</span>
</a>
<a className='pill' href={href('/plugins/templates/')}>
{home.labs.pills.mobile}
<span className='count'>{mobileCount}</span>
</a>
<a className='pill' href={href('/plugins/templates/')}>
{home.labs.pills.office}
<span className='count'></span>
</a>
</div>
</div>
<div className='labs-meta'>
<span className='ring'>05</span>
<div className='meta-text'>
<b>{home.labs.metaTitle}</b>
<BreakText text={home.labs.metaBody} />
</div>
</div>
<div className='labs-grid'>
{[
{
badge: home.labs.items[0].badge,
num: 'Nº 01',
title: home.labs.items[0].title,
body: home.labs.items[0].body,
src: imageAsset('lab-1.png', { width: 768, quality: 82 }),
href: `${REPO_SKILLS}/guizang-ppt`,
},
{
badge: home.labs.items[1].badge,
num: 'Nº 02',
title: home.labs.items[1].title,
body: home.labs.items[1].body,
src: imageAsset('lab-2.png', { width: 768, quality: 82 }),
href: `${REPO_SKILLS}/hyperframes`,
},
{
badge: home.labs.items[2].badge,
num: 'Nº 03',
title: home.labs.items[2].title,
body: home.labs.items[2].body,
src: imageAsset('lab-3.png', { width: 768, quality: 82 }),
href: `${REPO_SKILLS}/design-brief`,
},
{
badge: home.labs.items[3].badge,
num: 'Nº 04',
title: home.labs.items[3].title,
body: home.labs.items[3].body,
src: imageAsset('lab-4.png', { width: 768, quality: 82 }),
href: `${REPO_SKILLS}/critique`,
},
{
badge: home.labs.items[4].badge,
num: 'Nº 05',
title: home.labs.items[4].title,
body: home.labs.items[4].body,
src: imageAsset('lab-5.png', { width: 768, quality: 82 }),
href: REPO_DAEMON,
},
].map((lab) => (
<div className='lab' key={lab.num} data-reveal>
<div className='lab-img'>
<span className='badge'>{lab.badge}</span>
<LazyImg src={lab.src} />
</div>
<div className='num-row'>
<span>{lab.num}</span>
<span>2026</span>
</div>
<h4>{lab.title}</h4>
<p>{lab.body}</p>
<a
className='arrow-mark'
href={lab.href}
aria-label={home.labs.openAria(lab.title)}
{...ext}
>
{arrowOut}
</a>
</div>
))}
</div>
<div className='labs-foot'>
<div className='progress'>
<span className='on' />
<span className='on' />
<span className='on' />
<span className='on' />
<span className='on' />
<span />
<span />
<span />
</div>
<span className='meta'>
{home.labs.foot(skills)}
{NBSP}·{NBSP}
<a
href={href('/plugins/skills/')}
className='library-link'
style={{ color: 'var(--coral)' }}
>
{home.labs.viewLibrary}
</a>
</span>
</div>
</div>
</section>
{/* ====== METHOD ====== */}
<section className='method' data-od-id='method'>
<div className='container'>
<div className='sec-rule'>
<span className='roman'>IV.</span>
<span className='meta-grp'>
<span>{home.method.rule}</span>
<span className='dot-mark'></span>
<span>{home.method.stages}</span>
</span>
<span>005 / 008</span>
</div>
<div className='method-head'>
<div data-reveal>
<span className='label'>
{home.method.label} <span className='ix'>· 05</span>
</span>
<h2 className='display' style={{ marginTop: 30 }}>
{home.method.titlePrefix} <em>{home.method.titleEmphasis}</em>{' '}
{home.method.titleSuffix}
<span className='dot'>.</span>
</h2>
</div>
<div className='right' data-reveal='right'>
<span className='plus'>+</span>
<p>{home.method.lead}</p>
</div>
</div>
<div className='method-grid'>
{[
{
num: '01',
title: home.method.steps[0].title,
body: home.method.steps[0].body(skills, systems),
src: imageAsset('method-1.png', { width: 816, quality: 82 }),
},
{
num: '02',
title: home.method.steps[1].title,
body: home.method.steps[1].body(skills, systems),
src: imageAsset('method-2.png', { width: 816, quality: 82 }),
},
{
num: '03',
title: home.method.steps[2].title,
body: home.method.steps[2].body(skills, systems),
src: imageAsset('method-3.png', { width: 816, quality: 82 }),
},
{
num: '04',
title: home.method.steps[3].title,
body: home.method.steps[3].body(skills, systems),
src: imageAsset('method-4.png', { width: 816, quality: 82 }),
},
].map((step) => (
<div className='method-step' key={step.num} data-reveal>
<div className='num'>{step.num}</div>
<h4>
{step.title} <span className='arrow-r'></span>
</h4>
<p>{step.body}</p>
<div className='img'>
<LazyImg src={step.src} />
</div>
</div>
))}
</div>
<div className='method-foot'>
<div className='left'>
<span className='ring' />
<span>{home.method.footLeft}</span>
</div>
<div className='right'>
<a className='method-repo-link' href={REPO} {...ext}>
<b>github.com/nexu-io/open-design</b>
</a>
{NBSP}·{NBSP}Apache-2.0
</div>
</div>
</div>
</section>
{/* ====== SELECTED WORK ====== */}
<section className='tight' data-od-id='work'>
<div className='work'>
<div className='work-rule'>
<span className='roman'>V.</span>
<span style={{ display: 'inline-flex', gap: 24 }}>
<span>{home.work.rule}</span>
<span style={{ color: 'var(--coral)' }}></span>
<span>{home.work.editedBy}</span>
</span>
<span>006 / 008</span>
</div>
<div className='work-grid'>
<div className='work-copy' data-reveal>
<span className='label'>{home.work.label}</span>
<h2>
{home.work.titlePrefix} <em>{home.work.titleEmphasisA}</em>{' '}
{home.work.titleMiddle} <em>{home.work.titleEmphasisB}</em>{' '}
{home.work.titleSuffix}
<span className='dot'>.</span>
</h2>
<a className='work-link' href={href('/plugins/skills/')}>
{home.work.viewAll(skills)}
</a>
</div>
<a
className='work-card'
data-reveal
href={`${REPO_SKILLS}/guizang-ppt`}
{...ext}
>
<div className='label-row'>
<span className='small-label'>{home.work.cards[0].label}</span>
<span className='index'>01 / {skills}</span>
</div>
<h3>{home.work.cards[0].title}</h3>
<p>{home.work.cards[0].body}</p>
<div className='img'>
<LazyImg src={imageAsset('work-1.png', { width: 768, quality: 82 })} />
</div>
<div className='meta-row'>
<span className='year'>{home.work.cards[0].metaLeft}</span>
<span>{home.work.cards[0].metaRight}</span>
</div>
</a>
<a
className='work-card alt'
data-reveal
href='https://github.com/tw93/kami'
{...ext}
>
<div className='label-row'>
<span className='small-label'>{home.work.cards[1].label}</span>
<span className='index'>04 / {systems}</span>
</div>
<h3>{home.work.cards[1].title}</h3>
<p>{home.work.cards[1].body}</p>
<div className='img'>
<LazyImg src={imageAsset('work-2.png', { width: 768, quality: 82 })} />
</div>
<div className='meta-row'>
<span className='year'>{home.work.cards[1].metaLeft}</span>
<span>{home.work.cards[1].metaRight}</span>
</div>
</a>
</div>
<div className='work-arrows'>
<button type='button' className='nav-btn'>
<svg
width='14'
height='14'
viewBox='0 0 24 24'
fill='none'
stroke='currentColor'
strokeWidth='1.6'
>
<path d='M14 6l-6 6 6 6' />
</svg>
</button>
<button type='button' className='nav-btn active'>
<svg
width='14'
height='14'
viewBox='0 0 24 24'
fill='none'
stroke='currentColor'
strokeWidth='1.6'
>
<path d='M10 6l6 6-6 6' />
</svg>
</button>
</div>
</div>
</section>
{/* ====== TESTIMONIAL / COLLABORATORS ====== */}
<section className='testimonial' data-od-id='testimonial'>
<div className='container'>
<div className='sec-rule'>
<span className='roman'>VI.</span>
<span className='meta-grp'>
<span>{home.testimonial.rule}</span>
<span className='dot-mark'></span>
<span>{home.testimonial.shoulders}</span>
</span>
<span>007 / 008</span>
</div>
<div className='testimonial-grid'>
<div className='testimonial-copy' data-reveal>
<span className='label'>
{home.testimonial.label} <span className='ix'>· 06</span>
</span>
<h2 style={{ marginTop: 30 }}>
{home.testimonial.quote}
</h2>
<div className='author'>
<span className='avatar'>m</span>
<p>
{home.testimonial.authorName}
<br />
<span>{home.testimonial.authorTitle}</span>
</p>
</div>
<div className='divider' />
<p className='partners-text'>
{home.testimonial.partnersText}
</p>
<div className='partners'>
<a
className='partner'
data-reveal
href={LINEAGE['huashu-design']}
{...ext}
>
<div className='glyph'>
<svg
viewBox='0 0 80 30'
fill='none'
stroke='currentColor'
strokeWidth='2'
>
<path d='M5 24L20 6L35 24M12 18h16' />
</svg>
</div>
<span>huashu-design</span>
<small>{home.testimonial.partnerLabels[0]}</small>
</a>
<a
className='partner'
data-reveal
href={LINEAGE['guizang-ppt']}
{...ext}
>
<div className='glyph'>
<svg
viewBox='0 0 80 30'
fill='none'
stroke='currentColor'
strokeWidth='2'
>
<path d='M8 24L20 6L24 22L36 4' />
</svg>
</div>
<span>guizang-ppt</span>
<small>{home.testimonial.partnerLabels[1]}</small>
</a>
<a
className='partner'
data-reveal
href={LINEAGE['open-codesign']}
{...ext}
>
<div className='glyph'>
<svg
viewBox='0 0 80 30'
fill='none'
stroke='currentColor'
strokeWidth='2'
>
<circle cx='15' cy='15' r='9' />
<path d='M15 6v18M6 15h18' />
</svg>
</div>
<span>open-codesign</span>
<small>{home.testimonial.partnerLabels[2]}</small>
</a>
<a
className='partner'
data-reveal
href={LINEAGE['devin-cli']}
{...ext}
>
<div className='glyph'>
<svg
viewBox='0 0 80 30'
fill='none'
stroke='currentColor'
strokeWidth='2'
>
<path d='M5 8l9 7-9 7M20 24h18' />
</svg>
</div>
<span>Devin CLI</span>
<small>{home.testimonial.partnerLabels[3]}</small>
</a>
<a
className='partner'
data-reveal
href={LINEAGE['hyperframes']}
{...ext}
>
<div className='glyph'>
<svg
viewBox='0 0 80 30'
fill='none'
stroke='currentColor'
strokeWidth='2'
>
<rect x='4' y='5' width='22' height='18' />
<rect x='14' y='9' width='22' height='18' />
</svg>
</div>
<span>hyperframes</span>
<small>{home.testimonial.partnerLabels[4]}</small>
</a>
</div>
<a className='read-more' href={REPO} {...ext}>
{home.testimonial.readMore}
</a>
</div>
<div className='testimonial-art' data-reveal='right'>
<LazyImg src={imageAsset('testimonial.png', { width: 1024, quality: 82 })} />
</div>
</div>
</div>
</section>
{/* ====== FAQ ======
*
* Visible answers — kept in lockstep with the FAQPage JSON-LD
* defined in `app/pages/index.astro`. Each entry mirrors the
* `q`/`a` pair, so the structured data describes content the
* user actually sees (Google's rich-result eligibility rule).
*/}
<section className='faq' id='faq' data-od-id='faq'>
<div className='container'>
<div className='sec-rule'>
<span className='roman'>VI·5.</span>
<span className='meta-grp'>
<span>{home.faqSection.rule}</span>
<span className='dot-mark'></span>
<span>{home.faqSection.answers}</span>
</span>
<span>{`00${faq.length}`.slice(-3)} / 008</span>
</div>
<div className='faq-head' data-reveal>
<span className='label'>
{home.faqSection.label} <span className='ix'>· 06.5</span>
</span>
<h2 className='display'>
{home.faqSection.titlePrefix} <em>Open Design</em>,{' '}
<em>OpenDesign</em>, {home.faqSection.titleMiddle}{' '}
<em>{home.faqSection.titleSuffix}</em>
<span className='dot'>.</span>
</h2>
</div>
<ol className='faq-list'>
{faq.map(({ q, a }, idx) => (
<li className='faq-item' key={q} data-reveal>
<details>
<summary>
<span className='faq-index'>
{String(idx + 1).padStart(2, '0')}
</span>
<span className='faq-q'>{q}</span>
<span className='faq-toggle' aria-hidden='true'>
+
</span>
</summary>
<p className='faq-a'>{a}</p>
</details>
</li>
))}
</ol>
</div>
</section>
{/* ====== CTA ====== */}
<section className='cta' id='contact' data-od-id='cta'>
<div className='container'>
<div className='sec-rule'>
<span className='roman'>VII.</span>
<span className='meta-grp'>
<span>{home.cta.rule}</span>
<span className='dot-mark'></span>
<span>{home.cta.command}</span>
</span>
<span>008 / 008</span>
</div>
<div className='cta-grid'>
<div data-reveal>
<span className='label'>
{home.cta.label} <span className='ix'>· 07</span>
</span>
<h2 className='display'>
{home.cta.titlePrefix} <em>{home.cta.titleOpen}</em>{' '}
{home.cta.titleMiddle} <em>{home.cta.titleVisual}</em>{' '}
{home.cta.titleSuffix}
<span className='dot'>.</span>
</h2>
<p className='lead'>{home.cta.lead}</p>
<div className='cta-actions'>
<a className='btn btn-primary' href={REPO} {...ext}>
{home.cta.star}
<span className='arrow'>{arrowOut}</span>
</a>
<a className='email-pill' href={REPO_ISSUES} {...ext}>
{home.cta.issue}
<span className='arrow-circle'></span>
</a>
</div>
<div className='cta-foot'>
<span className='stamp'> {home.cta.live}</span>
<span>
<span data-github-version>{github.versionLabel}</span> / Apache-2.0
</span>
<span style={{ marginLeft: 'auto' }}>
52.5200° N · 13.4050° E
</span>
</div>
</div>
<div className='cta-art' data-reveal='right'>
<LazyImg src={imageAsset('cta.png', { width: 1024, quality: 82 })} />
<div className='index'> 08</div>
<div className='ribbon'>
{home.cta.ribbon}
</div>
</div>
</div>
</div>
</section>
{/* ====== FOOTER ====== */}
<footer data-od-id='footer'>
<div className='container'>
<div className='foot-grid'>
<div className='foot-brand'>
<a href='#top' className='brand'>
<span className='brand-mark'>
<img src='/logo.webp' alt='' width={44} height={44} />
</span>
<span className='brand-name'>Open Design</span>
</a>
<p style={{ marginTop: 18 }}>
{home.footer.summary}
</p>
<a
className='foot-cta'
href={REPO_RELEASES}
aria-label={home.footer.downloadAria}
{...ext}
>
{home.footer.download}
<span className='meta'>
macOS · <span data-github-version>{github.versionLabel}</span>
</span>
</a>
</div>
<div className='foot-col'>
<h5>{home.footer.columns.studio}</h5>
<ul>
<li>
<a href='#agents'>{home.footer.studioLinks[0]}</a>
</li>
<li>
<a href='#labs'>{home.footer.studioLinks[1]}</a>
</li>
<li>
<a href={REPO_DAEMON} {...ext}>
{home.footer.studioLinks[2]}
</a>
</li>
<li>
<a href={REPO} {...ext}>
{home.footer.studioLinks[3]}
</a>
</li>
</ul>
</div>
<div className='foot-col'>
<h5>{home.footer.columns.library}</h5>
<ul>
<li>
<a href={href('/plugins/skills/')}>
{home.footer.libraryLinks.skills(skills)}
</a>
</li>
<li>
<a href={href('/plugins/systems/')}>
{home.footer.libraryLinks.systems(systems)}
</a>
</li>
<li>
<a href={href('/plugins/templates/')}>
{home.footer.libraryLinks.templates}
</a>
</li>
<li>
<a href={href('/craft/')}>{home.footer.libraryLinks.craft}</a>
</li>
{/*
* Sister product: HTML Anything is the agent-driven HTML
* editor from the same team. Listed here as a peer to the
* Open Design library facets so the home delivers a real
* inline anchor link to /html-anything/ — nav-only entries
* (the Product dropdown) carry less SEO weight than a body
* anchor in a discoverable section like the footer. The
* brand name stays in English on every locale, so we
* hardcode the label rather than threading a new key
* through 18 home-copy translations.
*/}
<li>
<a href='/html-anything/'>HTML Anything</a>
</li>
</ul>
</div>
<div className='foot-col'>
<h5>{home.footer.columns.connect}</h5>
<ul>
<li>
<a href={REPO} {...ext}>
{home.footer.connectLinks[0]}
</a>
</li>
<li>
<a href={REPO_ISSUES} {...ext}>
{home.footer.connectLinks[1]}
</a>
</li>
<li>
<a href={REPO_CONTRIBUTORS} {...ext}>
{home.footer.connectLinks[2]}
</a>
</li>
<li>
<a href={REPO_RELEASES} {...ext}>
{home.footer.connectLinks[3]}
</a>
</li>
<li>
<a href={DISCORD} {...ext}>
{home.footer.connectLinks[4]}
</a>
</li>
</ul>
</div>
<div className='foot-col'>
<h5>{home.footer.columns.openDesign}</h5>
<ul>
<li>
<a href={href('/official/')}>
{home.footer.openDesignLinks.official}
</a>
</li>
<li>
<a href={href('/quickstart/')}>
{home.footer.openDesignLinks.quickstart}
</a>
</li>
<li>
<a href={href('/agents/')}>
{home.footer.openDesignLinks.agents}
</a>
</li>
<li>
<a href={href('/compare/')}>
{home.footer.openDesignLinks.compare}
</a>
</li>
<li>
<a href={href('/alternatives/claude-design/')}>
{home.footer.openDesignLinks.alternative}
</a>
</li>
</ul>
</div>
</div>
<div className='foot-bottom'>
<span>
<span className='pulse' />{' '}
<b style={{ color: 'var(--ink)' }}>{home.footer.bottomLeft}</b>
</span>
<span className='right'>
<span>{home.footer.bottomRightA}</span>
<span>{home.footer.bottomRightB}</span>
<span style={{ color: 'var(--coral)' }}> MMXXVI</span>
</span>
</div>
<div className='foot-mega'>
<div className='word' data-reveal='rise-lg'>
{(() => {
const parts = home.footer.mega.split('Design');
if (parts.length !== 2) return home.footer.mega;
return (
<>
{parts[0]}
<span style={{ color: 'var(--coral)' }}>Design</span>
{parts[1]}
</>
);
})()}
</div>
</div>
</div>
</footer>
</div>
</>
);
}