open-design/apps/landing-page/app/page.tsx
Joey-nexu b0b2067e5c feat(landing-page): point catalog links at /plugins, drop legacy /skills /systems routes
The 2026-05 plugins rebuild left three homepage/library surfaces still
pointing at the retired `/skills/`, `/systems/`, and `/craft/` route
trees, so visitors hit a 301 hop (or a stale facet) instead of landing
directly on the new `/plugins/*` pages. Repoint them at the canonical
destinations and align the homepage Labs pills with the real library.

- system-card: link straight to `/plugins/design-system-<slug>/` via a
  new `detailHrefForSystemSlug` resolver instead of hard-coding
  `/systems/<slug>/` and relying on the redirect. The ~8 systems that
  ship no manifest (hence no detail page) degrade to `/plugins/systems/`,
  the same destination the legacy 301 produced, minus the hop and with no
  risk of linking at a page that doesn't exist.
- homepage Labs pills: replace the hard-coded prototype/deck/mobile/office
  facets (mobile/office had drifted to stale or empty counts) with a live
  top-4 of `PLUGIN_CATEGORIES`, counted with the same `categorizePlugin`
  rule `/plugins/templates/` uses and labelled from `pcopy.category`, so
  the homepage stays in lockstep with the library and never shows a dead
  chip. Counts are surfaced through a new `CatalogCounts.templateCategories`.
- remove the Craft entry points from the homepage footer, sub-page footer,
  header Library dropdown, and the plugins hub tile grid. The `/craft/`
  pages stay live; they're just no longer surfaced in site chrome.

The legacy `/skills/`, `/systems/`, `/templates/` 301s added in the prior
PR stay in place for inbound links and search equity.
2026-05-31 19:45:54 +08:00

1465 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';
import { getPluginsCopy } from './_lib/plugins-i18n';
/**
* `<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>>;
/**
* Live `/plugins/templates/` category breakdown driving the Labs
* pills. Ordered by count desc, zero-count categories dropped.
*/
templateCategories?: {
total: number;
byCategory: ReadonlyArray<{ slug: string; count: 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 commonCopy = getCommonCopy(locale);
const home = getHomePageCopy(locale);
const pcopy = getPluginsCopy(locale);
// Labs pills mirror the live `/plugins/templates/` category strip: an
// "All" chip plus the top categories by count, labelled and counted
// from the same source so the homepage never drifts from the library.
const templateCategories = counts.templateCategories;
const labsPills = templateCategories
? templateCategories.byCategory.slice(0, 4).map((c) => ({
slug: c.slug,
label:
pcopy.category[c.slug as keyof typeof pcopy.category]?.label ?? c.slug,
count: pad2(c.count),
}))
: [];
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/templates/')}>
{pcopy.allChip}
<span className='count'>
{templateCategories ? fmt(templateCategories.total) : skills}
</span>
</a>
{labsPills.map((pill) => (
<a
key={pill.slug}
className='pill'
href={href(`/plugins/templates/${pill.slug}/`)}
>
{pill.label}
<span className='count'>{pill.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>
{/*
* 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>
</>
);
}