mirror of
https://github.com/nexu-io/open-design.git
synced 2026-05-31 19:04:39 +07:00
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.
1465 lines
56 KiB
TypeScript
1465 lines
56 KiB
TypeScript
/*
|
||
* 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 1250–3000px 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'>· Nº 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'>· Nº 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'>· Nº 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'>· Nº 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'>· Nº 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'>· Nº 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'>· Nº 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'>· Nº 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'>Nº 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>
|
||
</>
|
||
);
|
||
}
|