mirror of
https://github.com/nexu-io/open-design.git
synced 2026-06-01 03:14:35 +07:00
59 commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
9f09d1b649
|
fix(landing-page): wire up mobile nav toggle on the homepage (#3295)
The homepage runs its own inline header enhancer instead of importing the shared header-enhancer.astro component, and that inline copy only ported the scroll-headroom and GitHub stars/version logic — it never included the hamburger toggle handler. As a result the mobile menu button rendered (and animated to an X via CSS) but clicking it did nothing on / and /<locale>/, while sub-pages that do import the shared enhancer worked fine. Port the same toggle handler into the homepage inline enhancer: click flips .is-open on header.nav (which CSS expands into the dropdown panel below 1080px), and outside-click, Escape, and any in-menu link close it, keeping aria-expanded in sync. Co-authored-by: Joey-nexu <joeylee12629@gmail.com> |
||
|
|
3f4fd58937
|
feat(landing-page): surface Discord + X in header, restructure site footer (#3230)
Some checks failed
ci / Detect CI change scopes (push) Successful in 0s
visual-baseline / Capture visual baselines (push) Waiting to run
landing-page-ci / Validate landing page (push) Failing after 2s
landing-page-staging / Deploy landing page to staging (push) Has been skipped
nix-check / build (push) Failing after 2s
ci / Validate Nix flake (push) Has been skipped
ci / Preflight (push) Failing after 2s
ci / Workspace unit tests (push) Failing after 2s
ci / Daemon workspace tests (push) Failing after 1s
ci / Web workspace tests (push) Failing after 1s
ci / Browser tests (push) Failing after 1s
ci / Build workspaces (push) Failing after 2s
ci / Validate workspace (push) Failing after 0s
ci / Runtime trace (push) Has been skipped
* feat(landing-page): surface Discord + X in header, restructure site footer
Two related public-chrome adjustments:
- **Header gains compact Discord + X icon buttons.** Both community
channels were previously buried in the footer, so the typical
visitor never saw them on a page-deep scroll. They now sit before
the Download / Star CTAs in `nav-side`, share the ghost-button
outline language, and stay icon-only with `aria-label` so they
read as social affordances rather than competing with the text
CTAs. At ≤1080px the icon buttons hide alongside the existing
ghost CTA, so the bar still collapses cleanly into the hamburger
panel — Star stays in the bar at every breakpoint.
- **Footer restructured into 4 columns: Products / Plugins /
Resources / Connect.** The old `Plugins / Open Design / Connect`
three-column layout muddled three different things — sister
products, the artifact catalogue, and contributor channels —
under one roof, so visitors hunting for "the other thing this
team makes" had nowhere obvious to go.
- **Products** (new) lists the team's apps: Open Design (links
to homepage) and HTML Anything. Two entries by design — adding
more products without an editorial pass would dilute the
column.
- **Plugins** mirrors the topbar `Plugins` dropdown verbatim:
Templates / Skills / Systems / Craft, with no count prefix on
Systems / Craft so it reads identically to the nav.
- **Resources** (renamed from `Open Design`) carries the
docs-style links: Official source / Quickstart / Agents locaux
/ Compare / Claude Design alternative. The old column heading
was confusing because the OD logo + brand name already sit
under the column.
- **Connect** gains an X / Twitter row pointing at
`@nexudotio`. The brand entries on this column are
contributor / community surfaces only — code, releases,
chat, social, RSS, contact form.
Implementation:
- `_components/header.tsx` — `DISCORD` and `X_TWITTER` consts at
the top alongside `REPO`. Two `<a class="nav-icon">` blocks with
inline SVG before the existing Download / Star CTAs.
- `_components/site-footer.astro` — `HTML_ANYTHING` and `NEXU_IO`
consts. `<div class="sub-footer-col">` re-ordered to put
Products first, Plugins second (no longer carries `counts.*`
values), Resources third, Connect fourth (with the new X / Twitter
row).
- `globals.css` — `.nav-icon` rule cloned from the ghost CTA's
visual language (transparent + 1px line, fills on hover) but
square (36×36 round) so it reads as a social-icon affordance.
Added `display: none` for `.nav-side .nav-icon` to the existing
≤1080px and ≤880px media queries so the icons follow the same
collapse behaviour as the Download CTA.
- `sub-pages.css` — `.sub-footer-grid` switches from
`1.6fr 1fr 1fr 1fr` to `1.4fr 1fr 1fr 1fr 1fr` (brand + 4
columns). At ≤1080px it falls back to a 3-column shape so each
column has room to breathe; at ≤720px it stays a single column
(existing behaviour).
- `i18n.ts` — adds `products`, `resources`, `xTwitter`,
`sisterProjects`, `htmlAnything`, `nexuIo` to `LandingUiCopy.footer`
(the last three are kept around even though `sisterProjects` is no
longer rendered after the column was renamed Products — they're
harmless and avoid churning the type if a future iteration brings
the Sister-projects framing back). All 17 non-English landing
locales gain translations for the new keys via the existing
`LOCALIZED_LANDING_FOOTER_COPY` map (and the `LANDING_UI_COPY_OVERRIDES`
block for `zh` / `zh-tw`). Translations were generated with
`claude-haiku-4-5` over OpenRouter, with explicit instructions
to keep "Open Design", "HTML Anything", and "X / Twitter" in
English and to render "Products" / "Resources" in sentence case
per locale convention. Spot-checked against rendered pages on
`/zh/`, `/zh-tw/`, `/ja/`, `/ko/`, `/de/`, `/fr/` (and `/ar/` for
RTL) for natural phrasing.
Validation: `pnpm --filter @open-design/landing-page typecheck` ->
0 errors / 0 warnings; local dev server smoke-tested on en root
(`/html-anything/`) and 5 locale variants (`/zh/`, `/zh-tw/`,
`/ja/`, `/de/`, `/fr/`) — header renders 2 nav-icon buttons,
footer renders 4 localized column headings in the correct order
with the right link targets.
* fix(landing-page): address PR #3230 review — locale-aware HTML Anything link + drop unused const
Two non-blocking inline review points from @PerishCode on PR #3230:
- The HTML Anything entry in the new Products column hardcoded
`https://open-design.ai/html-anything/` via a top-level
`HTML_ANYTHING` const, but `/html-anything/` is a real localized
route in this app (`pages/[locale]/html-anything/index.astro`)
and `open-design.ai` is the same site's live domain. A visitor
on `/zh/…` clicking through landed on the English route and lost
locale context, and hardcoding the production domain meant a
preview build would surface a link that bounces visitors back
to prod. Switch to `href('/html-anything/')` so the locale prefix
+ the current site's domain (resolved by `localizedHref`) are
honored, matching every other footer link.
- `NEXU_IO` was declared at the top of the component but never
referenced — leftover from an earlier iteration that listed
`nexu.io` as a Sister-projects entry before the column was
renamed Products and reduced to OD + HTML Anything. Removed.
No behavior change beyond the locale routing fix; the i18n keys
and column structure stay as they landed in the original commit.
* fix(landing-page): correct nav-icon comment to match actual responsive behaviour
The JSX comment introduced for the new Discord + X icon buttons in
PR #3230 claimed the icons "survive at narrow widths while text-only
nav items get pushed off". The CSS that shipped in the same PR does
the opposite: both `@media (max-width: 1080px)` and `@media (max-width:
880px)` blocks add `.nav-side .nav-icon { display: none; }`, so at
narrow widths the icons collapse alongside the ghost Download CTA
while the text nav <ul> moves into the hamburger panel — only the
Star CTA remains visible in the bar.
Rewrite the comment to describe the actual responsive contract so
the next reader of `header.tsx` doesn't have to cross-reference
`globals.css` to figure out which surface stays. Reviewer flag from
@PerishCode on PR #3230.
No code-path change; comment-only.
* fix(landing-page): correct sub-footer 1080px comment to describe actual 3-column grid
The CSS comment introduced for the new sub-footer grid claimed the
≤1080px breakpoint drops to "brand + 2x2 grid of columns" — but the
rule produces a 3-column grid, not a 2x2.
`.sub-footer-grid` has 5 children at this breakpoint (the brand
block + the four footer columns) and `.sub-footer-brand` carries
no `grid-column` span, so with `grid-template-columns: 1.6fr
repeat(2, 1fr)` they flow as: row 1 = brand · Products · Plugins,
row 2 = Resources · Connect · empty cell. The brand sits inline
with two columns rather than on its own, and the four content
columns are not a clean 2x2.
The layout itself is fine; only the comment misleads the next
reader about how the columns wrap. Same flavor as the `header.tsx`
icon comment fixed in
|
||
|
|
afc6e9a39f
|
feat(landing-page): localize templates subcategory chip labels across 16 locales (#3256)
The "scene" chip rail under each `/plugins/templates/<kind>/` page shipped 23 chip labels in English (`UI & product mockups`, `Brand & logo`, `Storyboards`, `Social & content`, `Avatar & portrait`, `Illustration & style`, plus the rest of the 24-slug subcategory map covering all seven artifact kinds). Only the `zh` override carried a translation; every other non-English locale fell back to English on its scene rail. The result: a visitor reading the rest of `/ja/plugins/templates/image/` in Japanese (hero, kind chips, FAQ, card chrome — all localized in PR #3218) hit a row of English chips at the bottom that read as machine output rather than first-party copy. This change fills `subcategory: { ... }` for the remaining 16 landing locales: `zh-tw`, `ja`, `ko`, `de`, `fr`, `ru`, `es`, `pt-br`, `it`, `vi`, `pl`, `id`, `nl`, `ar`, `tr`, `uk`. The existing `zh` translation is untouched. Brand-name tokens (`UI`, `HyperFrames`, etc.) stay in English; localizable terms (`Apps`, `Brand`, `Logo`, `Avatar`, `Storyboards`, …) are translated where the language has a clean native equivalent. Conjunctions follow locale convention — `&` for Latin-script locales that read it as native chrome, `·` for CJK locales where it works better than `&` next to ideographs, and `و / & / และ`-style natural conjunctions for the rest. Translations were generated with `claude-haiku-4-5` over OpenRouter using a single batch script with explicit instructions on chip-width budget (≈120px, target 1–4 native words), sentence casing, and brand-token preservation. Output was validated for JSON shape (every locale returns all 23 slugs) before splicing into the override blocks. Validation: pnpm --filter @open-design/landing-page typecheck -> 0 errors / 0 warnings; local dev (port 3067) renders the chip rail in Japanese / Russian / Traditional Chinese / Arabic / German / French on `/<locale>/plugins/templates/image/` (and the same rail on the other six artifact kinds, which share the subcategory slug map). Co-authored-by: Joey-nexu <joeylee12629@gmail.com> |
||
|
|
6ac1450925
|
feat(landing-page): localize templates page chrome + FAQ + categories across 17 locales (#3218)
PR #3185 introduced 9 new copy keys for the templates grid chrome (`templatesHeroEyebrow`, `templatesHeroLead`, `templatesCounterLabel`, `cardFeaturedTag`, `cardReadFullPrompt`, `cardUseTemplate`, `cardShareAria`, `faqHead`, `faqItems`) and used `pcopy.category[slug]` labels and descriptions on the kind facets. The English base was filled in but the per-locale `overrides` map was left as a follow-up, so every non-English visitor saw English chrome on `/<locale>/plugins/templates/` and English H1 + lead on `/<locale>/plugins/templates/<kind>/` (except `zh`, which already shipped a `category` override before PR #3185). This change fills in all 17 non-English landing locales for those new chrome keys, FAQ Q&A, and the artifact-category labels: zh, zh-tw, ja, ko, de, fr, ru, es, pt-br, it, vi, pl, id, nl, ar, tr, uk. Brand names (`Open Design`, `Claude`, `Claude Design`, `Anthropic`, `OpenAI`, `HyperFrames`, `Cloudflare`, `Apache-2.0`, `BYOK`, `PR`, `GitHub`) stay in English in every locale per the SEO anchor strategy. Artifact category labels are localized with the native-language word each design / dev community would actually search for: `プロトタイプ` (ja), `프로토타입` (ko), `Prototyp` (de), `Prototipo` (es), `Прототип` (ru), and so on. `zh` keeps its existing `category` translation untouched since it was already shipped — only the new chrome + FAQ keys land for that locale. Translations were produced with `claude-haiku-4-5` via OpenRouter and spot-checked against rendered pages on 5 locales (zh, ja, ko, de, fr) for natural phrasing, brand-name preservation, and HTML-tag / entity / variable integrity. The remaining 12 locales follow the same prompt and are expected to be merge-ready as a v1; native speakers in the community can refine wording later via small PRs without coordinating across the whole grid. Validation: pnpm --filter @open-design/landing-page typecheck -> 0 errors / 0 warnings; local dev (port 3062) renders 231 cards on each of /zh/, /ja/, /ko/, /de/, /fr/ /plugins/templates/ with hero eyebrow / H1 / counter / CTA / FAQ head / first FAQ Q all localized, and /ja/plugins/templates/prototype/ H1 reads "プロトタイプ" with a localized lead (was English on prod before this PR). Co-authored-by: Joey-nexu <joeylee12629@gmail.com> |
||
|
|
9d65e26c0f
|
feat(landing-page): card grid + share popover for /plugins/templates/ (#3185)
* feat(landing-page): YouMind-style grid + share popover for /plugins/templates/ The list-style catalog rows that landed in PR #3010 read as a long table of items rather than a discoverable grid. Product feedback (after benchmarking against youmind.com/zh-CN/seedance-2-0-prompts) wanted: - A YouMind-shape card with a top accent band, video / poster preview area, author + attribution row, an excerpt frame, and a primary CTA paired with a share button. - Hover-autoplay on the 46 video templates whose manifest carries a Cloudflare Stream MP4. The data was already there since PR #3010; the catalog row just rendered the poster as a static `<img>`. - A counter chip on the right of the hero that surfaces the live total (`Total · 231`) instead of baking the number into the H1 ("231 runnable templates."). The hero now reads as `OPEN SOURCE CLAUDE DESIGN` eyebrow + `Templates.` static H1, which also threads the brand keyword into the page's SEO surface. - A six-question FAQ block below the grid covering license, BYOK keys, contribution, and the "open source Claude Design alternative" positioning explicitly. Implementation: - `_components/template-card.astro` — new card component. Accent band hue is derived from `od.mode` so artifacts of the same kind get a consistent color (video green, prototype blue, deck mustard, image wisteria, hyperframes coral, audio amber, live-artifact teal), falling back to a stable per-index hue for unrecognized modes. Featured tag (yellow, on-brand) is visible when the manifest tag list contains `featured`; the rest of the card is locale-resolved via the same `resolveBundledTitle` / `resolveBundledDescription` helpers PR #3010 added. - `pages/plugins/templates/index.astro` + `[kind]/index.astro` — grid layout (`.tpl-grid`, `repeat(auto-fill, minmax(340px, 1fr))`), hero with counter chip, FAQ section on the parent only. Adjacent filter strips share a single divider rather than drawing one each, so the kind + scene chip block reads as one filter unit instead of three stacked horizontal cuts. - Hover-autoplay observer + share button click handler bundled into one `<script>` per page so they share the same boot lifecycle. The earlier split version dispatched `astro:page-load` from the autoplay block before the share block's listener attached, which dropped the share click on the floor; the merged init() runs eagerly when DOM is ready, re-runs idempotently on `astro:page-load` (Astro view transition), and uses `data-tpl-init` / `data-tpl-share-bound` markers to prevent double-binding. - Card share is a popover, not a system share sheet. The detail page's `<dialog class="detail-share-dialog">` UI is reused (single instance per page populated per click), but `<dialog>.show()` runs in non-modal mode and JS positions it via `getBoundingClientRect()` to unfold above-right of the trigger button. Outside-click and Escape close the popover; the existing `data-share-copy` / `data-copy-link` handlers in `header-enhancer.astro` wire Copy text + Copy link automatically. Width tuned to 420px so it fits next to a 340px-wide card without spilling onto the next column. - `_redirects` already covers retired Skills + Craft routes (PR #3010) so this grid pivot doesn't need new redirects. Out of scope for this PR (kept lean): - Multi-locale hero + FAQ copy. Hero / FAQ render in English on every locale right now; the `pcopy.tileTemplates` chip rail and per-card title/description still localize per PR #3010. Locale rollout for the hero + FAQ is a follow-up. - Sort + filter buttons in the YouMind reference top-right (we still show artifact-kind chips only). Sort by featured weight is the most likely next step. - `od.featured` weight as a featured proxy. We currently key off `tags?.includes('featured')` which is 0-match across the catalog today; promoting the numeric weight into `BundledPluginRecord` is a separate small commit. `pnpm --filter @open-design/landing-page typecheck` clean (0 errors). * feat(landing-page): localize templates chrome + FAQPage JSON-LD + hover-only autoplay Three follow-ups Looper flagged on the YouMind-style grid (PR #3185): - **Localizable hero / FAQ / card chrome.** PR #3185 wired the grid through `pcopy` for record titles + descriptions but hard-coded the surrounding chrome — hero eyebrow / lead / counter label, FAQ head, Featured tag, "Read full prompt", "Use this template", and the share-button `aria-label` — to English. `/ja/plugins/templates/`, `/zh-CN/plugins/templates/video/`, etc. now ship those strings via `pcopy.*` keys (`templatesHeroEyebrow`, `templatesHeroLead`, `templatesCounterLabel`, `cardFeaturedTag`, `cardReadFullPrompt`, `cardUseTemplate`, `cardShareAria`, `faqHead`, `faqItems`). English is the base; per-locale overrides for hero copy + 6 FAQ Q&A pairs remain a follow-up (the PR-#3185 "Out of scope" item), so the 17 non-English locales fall back to English chrome instead of showing undefined values. - **`FAQPage` JSON-LD entity.** The visible accordion was a SEO surface but `jsonLd` was still a single `CollectionPage`. Switched it to an array and appended a `FAQPage` whose `mainEntity` is each question + answer from `pcopy.faqItems`, so the structured-data payload search engines see and the visible <details> share one source of truth — drift between them is now mechanical, not editorial. - **Hover-only autoplay (not viewport autoplay).** The previous observer played every video the moment its card scrolled into the viewport, which contradicted the PR's stated hover-autoplay contract and spawned N simultaneous decoders on a casual scroll. The IntersectionObserver now hydrates `data-src` -> `src` lazily (one-shot, then unobserve) at a 300px rootMargin; `play()` and `pause()` are gated to `pointerenter` / `pointerleave` (plus `focusin` / `focusout` for keyboard users) on the parent `.tpl-media` host so hovering anywhere on the preview frame triggers playback. Same change applied to the `[kind]` route so faceted pages behave identically. Validation: pnpm --filter @open-design/landing-page typecheck -> 0 errors / 0 warnings; local dev (port 3061) renders 231 cards / 46 data-tpl-autoplay markers / FAQPage entity present in jsonLd / 6 FAQ summaries; zh-CN locale falls back to English chrome (expected, the locale routes themselves remain out of scope per PR #3185). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Joey-nexu <joeylee12629@gmail.com> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
9a4816b101
|
feat(plugins): site-wide plugin detail pages, share-to-site links, landing deploy trigger (#2999)
* feat(plugins): site-wide plugin detail pages, share-to-site links, landing deploy trigger Why: a merged plugin PR didn't redeploy the landing site (plugins/** was missing from the deploy paths), and the desktop Share menu copied a local/404 link instead of the public marketplace URL. The landing plugin routing left by the detail-page rework also 404'd: the locale listing's cards used a multi-segment href while detail pages were single-segment, and only 388 bundled _official plugins had pages. What changed: - Deploy: landing-page deploy/ci trigger on plugins/**, and skip the slow previews step on an exact cache hit (cache key aligned across both workflows so a PR-built cache is reused by main). - Share URL: packages/contracts/plugin-url.ts owns the single-segment plugin URL scheme; the web Share menu and the landing site both derive links from it. Web links now point at https://open-design.ai/plugins/<slug>/. - Full detail coverage: detail pages now cover all 403 local plugins (_official incl. atoms + community), each rendered from its local manifest. Fixes the locale-listing 404s and the community manifest-name/catalog-id (- vs /) mismatch. - Self-host: daemon exposes OD_SITE_ORIGIN via /api/app-config; web falls back to the canonical origin until the daemon answers. Validation: pnpm guard, pnpm typecheck (all packages), contracts + web tests green, and a full build E2E confirming all 403 catalog ids and locale-listing cards resolve to built detail pages (0 missing). * chore: retrigger CI * ci(landing): carry plugins/** trigger + previews cache-hit into #2994 split workflows Merged origin/main, which split landing deploy into staging + manual production (#2994). git auto-migrated my landing-page-deploy.yml changes into landing-page-staging.yml via rename detection (plugins/** path, fallback-preview-card.ts cache key, cache-hit skip all carried). The new manual landing-page-production.yml didn't have them, so add the previews cache-key alignment + cache-hit skip there too (plugins/** path is N/A — production is workflow_dispatch only). * fix(ci): wrangler-action uses pnpm so it tolerates landing's workspace dep This PR added @open-design/contracts (workspace:*) to apps/landing-page/package.json so the landing site can share the plugin-url slug rules. But the landing deploy/preview steps run cloudflare/wrangler-action with packageManager: npm in workingDirectory apps/landing-page, and 'npm i wrangler' chokes on the workspace: protocol (EUNSUPPORTEDPROTOCOL), failing 'Validate landing page'. Switch all three landing wrangler-action steps (staging / ci preview / production) to packageManager: pnpm, which is workspace-aware. * test(e2e): bundled plugins now offer the README badge After this branch, buildPluginShareUrl returns a public open-design.ai link for bundled plugins (not just official-marketplace ones), so the home-starter share menu now shows 'Copy README badge'. Update the assertion from toHaveCount(0) to toBeVisible(). * fix(landing): drop @open-design/contracts dep, use a landing-local slug helper Per review on #2999: the marketing site must not import @open-design/contracts (AGENTS.md boundary — it's the web/daemon product-runtime contract layer). Move the slug/path helpers into landing-local app/_lib/plugin-slug.ts; the web client keeps contracts' plugin-url. The two derive the same scheme and are verified in lockstep by the e2e route check (403 share URLs -> 403 detail pages, 0 missing). landing no longer has a workspace dep, so revert the wrangler-action packageManager back to npm. * fix(landing): include plugins/_official in previews cache key Per review on #2999: generate-previews.ts builds bundled-plugin preview jobs from plugins/_official/**/open-design.json and renders fallback cards from manifest fields (title/description/mode/scenario/tags). With plugins/** now triggering the workflow but the cache key not hashing plugin inputs, a plugin-only PR/merge could exact-hit an old cache and skip the preview regen, shipping with a stale or missing /previews/plugins/<manifest-id>.png. Add plugins/_official/** to the cache key in all three landing workflows (ci, staging, production). community is not currently covered by generate-previews so its glob is omitted. * fix(plugins): include community marketplace installs in share gate hasPublicPage now covers sourceMarketplaceId === 'community' so the README badge and public detail link surface for community installs. Community manifest names carry a community- prefix that diverges from the landing-page route slug, so URL derivation uses sourceMarketplaceEntryName (community/<folder>) instead — pluginDetailSlug takes the last segment, matching the /plugins/<folder>/ route the landing page emits. Adds component tests for buildPluginShareUrl, badge copy, and the Open-in-marketplace link for a community/registry-starter record. Generated-By: looper 0.9.2 (runner=fixer, agent=claude-code) --------- Co-authored-by: mrcfps <mrc@powerformer.com> |
||
|
|
1b1fba165f
|
fix(landing-page): drop Homepage CTA from plugin detail page (#3129)
Some checks failed
visual-baseline / Capture visual baselines (push) Waiting to run
actionlint / Lint GitHub Actions workflows (push) Failing after 1s
ci / Detect CI change scopes (push) Successful in 1s
landing-page-ci / Validate landing page (push) Failing after 1s
landing-page-staging / Deploy landing page to staging (push) Has been skipped
nix-check / build (push) Failing after 2s
ci / Validate Nix flake (push) Has been skipped
ci / Preflight (push) Failing after 2s
ci / Workspace unit tests (push) Failing after 1s
ci / Daemon workspace tests (push) Failing after 1s
ci / Web workspace tests (push) Failing after 1s
ci / Browser tests (push) Failing after 1s
ci / Build workspaces (push) Failing after 1s
ci / Validate workspace (push) Failing after 1s
ci / Runtime trace (push) Has been skipped
Per product feedback: the Homepage button (rendered when a plugin's manifest carries a `homepage` field that differs from its `sourceUrl`) sent visitors off to upstream-author or original-source URLs before they had a chance to explore the plugin via Open Design itself. On a marketing detail page that's a leak, not a feature. Removes the conditional block that rendered `<a class="btn btn-ghost">Homepage ↗</a>` between the GitHub link and the Share button. The header-action row is now exactly three controls everywhere: Use this plugin → · Find on GitHub → · Share ↗. The `plugin.homepage` data field stays available on the `BundledPluginRecord` shape since the in-app catalog row, JSON-LD, and any future author-bio surface can still consume it. The `pcopy.detailHomepage` i18n key (with full 18-locale coverage) stays for the same reason — `PluginsCopy` is `Partial<>` everywhere and removing it would mean a 18-locale block edit for zero functional gain. apps/landing-page typecheck stays at 0 errors. Co-authored-by: Joey-nexu <joeylee12629@gmail.com> |
||
|
|
ee1eab77c6
|
feat(landing): add Community link + first-party Ambassadors page (#3066)
* feat(landing): add Community link to top nav
Adds a 'Community' entry to the landing-page nav between Blog and the
Star CTA, linking to /community/ which Cloudflare Pages 302-redirects to
the contributors honor-cards page (currently a Vercel deploy).
Translations added for all 18 locales. The nav slot was previously empty
after Blog because Contact had been intentionally pulled from the bar
(left as a footer + #contact anchor).
* fix(landing): use literal /community/ href so non-default locales don't 404
PerishCode review caught that href('/community/') routes through
localizedHref and produces /zh/community/, /ja/community/, etc. The
_redirects rule only matches the literal /community/ and the
[locale]/[...path].astro catch-all does not generate community pages,
so 17 of the 18 translated locales would have hit a Cloudflare 404.
The destination is a single non-locale-aware external page, so skip
the locale prefix entirely — same shape as the GitHub Star and
Download CTAs.
* feat(landing): host community + ambassadors page first-party
Lands the contributors / ambassadors page as a static asset at
apps/landing-page/public/community/index.html, served at /community/
on open-design.ai. Drops the temporary 302 to the Vercel preview URL
(d5458c46-…vercel.app) — that hostname was a deploy-time UUID Vercel
could recycle, which the reviewer correctly flagged as a follow-up.
The page now opens with an Ambassadors section: vocation, patronage,
covenant — three columns of the program in Renaissance-atelier voice,
with a single Apply on Discord CTA pointing at the ambassador channel
(discord.gg/2p7Ajbxw3h). Maintainers / leaderboards / good-first-issues
sit below as before. Header.tsx comment updated to point at the new
source of truth instead of the deleted redirect rule.
* fix(community): drop time-bound claims, tighten bot heuristic, drop dead CORE_TEAM entry
PerishCode review on
|
||
|
|
f8c860a505
|
feat(landing-page): localize plugins library across 18 locales (#3010)
* feat(landing-page): localize plugins library across 18 locales PR #2926 shipped the new `/plugins/` library hub + four kind sub-routes + detail pages, but the chrome was English-only — visitors landing on `/zh/plugins/` saw the old marketplace registry placeholder rendered by the catch-all instead, and detail pages rendered identical English copy regardless of locale prefix. This PR brings the plugin surface to feature parity with `/zh/skills/`, `/zh/templates/`, `/zh/systems/`, `/zh/craft/`. ## What changes - New `app/_lib/plugins-i18n.ts` — single source for all plugin chrome copy (hub, list pages, chip rails, share dialog, detail-page meta labels). English baseline + 17 locale overrides keyed on `LandingLocaleCode` (the same short-code shape `localeFromPath()` returns). Missing keys per locale fall back to English so a partially-translated locale still renders sensibly. Translations cover hub copy, four tile titles + blurbs, seven artifact-kind labels + descriptions, 23 scene-subcategory labels, 18 detail-page chrome strings, and a six-key share-dialog table with a per-locale `shareTemplate({title, url})` function (translated for every locale where `_lib/i18n.ts` already had one — same voice). - `app/pages/plugins/{,templates/,templates/[kind]/,skills/,systems/, craft/,[slug]/}/index.astro` — every hardcoded English string now reads `getPluginsCopy(locale)` keys. Page logic and routing unchanged. - New short-code wrappers under `app/pages/[locale]/plugins/` — six files (hub + three sub-routes + `[kind]/` and `[slug]/`) following the same pattern `[locale]/skills/index.astro` already uses: each re-exports the canonical page component and adds a per-locale `getStaticPaths()` so the build emits 17 locale prefixes per plugin route. Total plugin-route prerender count goes from ~390 to ~7 000, matching the existing skill/template scaling. - Catch-all (`[locale]/[...path].astro`) — old `getPublicPlugins` / `getRegistryCounts` registry rendering removed (placeholder UI that was never wired to a real marketplace data source). Plugin routes now live exclusively under `[locale]/plugins/...` short-code wrappers, so the catch-all stops claiming `'plugins'` as a route root. The dead-code path also drops a `pluginCounts.all` reference the title row was reading. - `.plugins-tile-grid` styles promoted from a scoped `<style>` in the default-locale hub to global `app/sub-pages.css` so the short-code wrapper renders the same hub markup without re-mounting per-page CSS — `display: contents`-style scoping pitfalls in Astro's per-component CSS scoping made this the cleanest fix. ## Surface area - [ ] **UI** — new page / dialog / panel / menu item / setting / empty state in `apps/web` or `apps/desktop` - [ ] **Keyboard shortcut** — new or changed - [ ] **CLI / env var** — new `od` subcommand or flag, new `tools-dev` flag, or new `OD_*` env var - [ ] **API / contract** — new `/api/*` endpoint, new SSE event, or changed shape in `packages/contracts` - [ ] **Extension point** — new entry under `skills/`, `design-systems/`, `design-templates/`, or `craft/`, or change to the skills protocol - [ ] **i18n keys** — new translation keys (full plugin chrome added across all 18 locales) - [ ] **New top-level dependency** — adding any new entry to the **root** `package.json` - [ ] **Default behavior change** — changes what existing users experience without opting in - [x] **None** — landing-page-only restoration of i18n parity for the plugin surface ## Validation - `pnpm --filter @open-design/landing-page typecheck` → 0 errors - `pnpm --filter @open-design/landing-page build:static` → 16 127 pages built (+6 584 over current main: ~388 plugin detail pages × 17 locale prefixes plus the hub + four sub-routes × 17 locales). - `copy-example-html.ts` reports `266 entry files + 65 referenced files`, identical to before — no regression in the asset-mirroring pipeline. - Local Playwright smoke (`/zh/plugins/...`): - `/zh/plugins/` renders `<title>插件库 · Open Design</title>`, label `插件库`, h1 `407 个可组合的构件。`, four tiles labelled `模板 / 技能 / 设计系统 / 工艺`. - `/zh/plugins/templates/video/` renders h1 `48 视频`, scene chips `全部 / 动效 / 短视频 / 营销 / 产品 / 数据讲解`. - `/zh/plugins/example-article-magazine/` share dialog renders `复制下面的文案、然后跳到你想分享的平台粘贴即可` etc., share template auto-interpolates plugin title + URL into Chinese voice. - All 18 locale prefixes (`/zh`, `/zh-tw`, `/ja`, `/ko`, `/de`, `/fr`, `/ru`, `/es`, `/pt-br`, `/it`, `/vi`, `/pl`, `/id`, `/nl`, `/ar`, `/tr`, `/uk`) → 200 across hub + four sub-routes + sample detail page. - English `/plugins/` unchanged (default-locale path bypasses the `[locale]/...` wrapper). * feat(landing-page): finish plugins i18n chrome across 18 locales The first localization pass shipped a partial fix: hub headings, lead copy, two-level page chrome, detail-page metadata labels, the share dialog, and the chip rail were still falling back to English on every non-English locale because plugins-i18n.ts only filled a chrome slice for `zh` and the file header even claimed "7 artifact-kind labels and 25 scene-subcategory labels are translated" for every locale that did not yet have those blocks. Three changes close the visible gap: 1. plugins-i18n.ts: fills the 27 still-missing chrome fields per locale for zh-tw / ja / ko / de / fr / ru / es / pt-br / it / vi / pl / id / nl / ar / tr / uk. Includes the 7-key category map, the 23-key subcategory map, hubHeading / hubLead, the 4 *Label / *Heading / *Lead triples for the templates / skills / systems / craft hub pages, the 4 tile blurbs, the 4 browse buttons, sceneLabel, allChip, the 12 detail-page metadata labels (mode / scenario / platform / surface / author / manifest id / tags / preview caption / find on GitHub / homepage / open in new tab) and bucket label map, the detail share dialog (title / copy link / jump-to), and the header-side nav.plugins entry. zh receives the same 11 detail-page and share-dialog labels it was also missing. 2. header.tsx + site-footer.astro: routes the hardcoded "Plugins / Templates / Skills / Systems / Craft" labels through `nav.*` from HeaderCopy, so every locale gets its own dropdown trigger and footer column. Adds `nav.plugins` to HeaderCopy and fills it in 18 locales with the local form ("插件" / "プラグイン" / "Plugins" / "Plug-ins" / "Plaginy" / "الإضافات" / etc). 3. plugin-row.astro + content-i18n.ts: chip rail. The bundled-plugin branch now runs raw `mode` / `scenario` slugs through the shared localizeTaxonomyValue, and that helper now also consults the plugins-i18n subcategory map before giving up. localizeTaxonomyValue now returns undefined on a true miss instead of the unknownTag placeholder, so chips drop quietly instead of showing "Category" / "分類" / "Categoría" for taxonomy slugs we have not localized yet. Callers that genuinely want the placeholder (`localizeContentTag`, blog `category`, system noun) still keep the explicit fallback. Out of scope and tracked separately: per-plugin title and description in plugins/_official/* (author-supplied English metadata, ~401 plugins without an i18n schema in the manifest yet — needs RFC + tooling before the manifests can be expanded), and adding the long tail of mode / scenario / category slugs (`code-migration`, `plugin-sharing`, `tune-collab`, `live-artifacts`, `engineering`, ...) to TAXONOMY_TERMS so chips render localized labels for every taxonomy value rather than dropping silently. * feat(landing-page): cover plugins chip rail long-tail taxonomy slugs PR #3010's first round localized the high-frequency mode/scenario chips (prototype, video, image, marketing, design, ...) but left the ~37 mode/scenario and 14 category slugs that show up in real `od.*` metadata — code-migration, plugin-sharing, design-system, planning, scenario, refine, discovery, handoff, token-map, tune-collab, orbit, live-artifacts, engineering, healthcare, hr, sales, support, default-router, downstream-export, figma-migration, media-generation, plugin-authoring, validation, 3d-shaders, animation-motion, audio-music, creative-direction, design-systems, diagrams, documents, image-generation, marketing-creative, screenshots, slides, video-generation, web-artifacts, ... — falling through to undefined and dropping their chip silently on every non-English locale. The data layer is the source of truth here, so this expansion lands in `content-i18n.ts:TAXONOMY_TERMS` / `CATEGORY_LABELS` rather than the plugins-i18n catalog: a single dictionary entry per slug fans out to every chip-rail consumer (catalog rows, detail metadata, the templates/[kind] facets) without each consumer touching its own copy. Translations cover all 17 non-`en` locales. Brand and product nouns (Figma, Open Design, BYOK, plugin) stay literal; technical taxonomy slugs get short equivalents that read as chips rather than full prose. The result on `/ja/plugins/skills/` matches `/plugins/skills/` chip-for-chip (30 chips both sides) instead of dropping 27 of them the way the previous iteration did. * feat(landing-page): read manifest title_i18n / description_i18n on bundled plugins PR #3010's prior rounds localized chrome and chip rails but the catalog's most prominent text — each row's plugin name and blurb — stayed English on every non-English locale. The plugin manifest schema (`packages/contracts/src/plugins/manifest.ts`) has supported `title_i18n` and `description_i18n` (Record<locale, string>) on every manifest from spec v1; ~24 of the 401 first-party manifests already carry one for `zh-CN`. The reader was just never wired to use them. This change does the reader half: bundled-plugins.ts captures the two i18n maps off each `open-design.json`, plugin-row.astro and the detail page resolve them at render time via two new helpers (`resolveBundledTitle`, `resolveBundledDescription`) that mirror the short→long fallback chain documented in the manifest spec (`htmlLang` like `zh-CN` → short `LandingLocaleCode` like `zh` → primary tag → `en` → English baseline). The static-paths pass still runs once for all locales — it has to, since each manifest produces one URL — but the title/description shown on the rendered page now reads the locale off `Astro.url.pathname` and picks the right entry out of the maps. Verified locally: `/zh/plugins/example-card-twitter/` now reads "Twitter 分享卡 / 推特金句 / 数据卡, 适合配推文" from the manifest's existing `zh-CN` block instead of the English baseline. Plugin-data half follows in a separate commit. The 17 non-English locales × 401 manifests need backfilling so the reader has something to resolve to; that's data, not schema, and lands as a sequence of manifest patches rather than tangled with this code change. * feat(plugins): translate scenarios bucket title/description across 17 locales Closes the first chunk of #3028. Eleven scenarios plugins (the default-scenario bundle for each taskKind: code-migration, figma-migration, media-generation, new-generation, tune-collab, plugin-authoring; the default design router; the React / Vue / Next.js downstream-export starters; and the Refine baseline) get title_i18n + description_i18n filled for all 17 non-English locales the landing page serves (zh-CN, zh-TW, ja, ko, de, fr, ru, es, pt-BR, it, vi, pl, id, nl, ar, tr, uk). The reader landed in 7ddfe36; this commit is data-only. taskKind slugs that other docs reference by name (`code-migration`, `figma-migration`, `tune-collab`, etc.) stay literal in the descriptions so cross-references still resolve. Brand nouns — Open Design, Next.js, React, Vue, Figma — also stay literal. `/ja/plugins/od-code-migration/` now reads "コードマイグレーション(デフォルトシナリオ)" instead of the English baseline; `/zh/plugins/skills/` shows "代码迁移(默认场景)" in the catalog row. Remaining buckets (image-templates 45, video-templates 50, examples 140, design-systems 142 = 377 plugins) follow in subsequent commits in this PR. * fix(landing-page): drop CJK template wrap when source name is still English The Chinese / Japanese / Korean fallback templates for craft, skill, template, system, plugin, and blog text splice the source `name` / `title` into a CJK sentence frame: ``${name}工艺规则``, ``Open Design 指南:${topic}``, ``${name} は…のスキルです``. When the underlying SKILL.md / craft markdown / blog frontmatter still ships an English name (true for ~95% of the catalog today), that produces mid-sentence script straddling on `/zh/...`, `/zh-tw/...`, `/ja/...`, `/ko/...` like: H1 : "Editorial typography hierarchy工艺规则" Lead : "这条 Open Design 工艺规则定义 Editorial typography hierarchy 的执行标准…" Plug : "video 插件 · 3D Animated Boy Building Lego" That reads worse than the all-English fallback, because the visitor parses the page in two scripts at once. Adds a `nameNeedsEnglishFallback` guard that fires for the four CJK locales whenever the spliced-in name has no CJK characters of its own, and threads it through every `localizeXxxText` helper: craft, template, system, plugin, skill, blog. When it fires the helper returns the raw English content untouched, so the section renders end-to-end in one language. Chrome (header, footer, breadcrumb, buttons, share dialog) keeps its CJK rendering — only the title-and-lead block falls back. Side benefit: the same guard kicks in on the long tail of plugin manifests still pending `title_i18n` / `description_i18n` backfill (tracked in #3028), so `/zh/plugins/<bundled>/` no longer pairs a "video 插件 · 3D Animated Boy Building Lego" title with a Chinese breadcrumb. The page reads "3D Animated Boy Building Lego" + the English manifest description, while header / footer / breadcrumbs stay localized. Once a manifest ships its i18n maps, the chrome and body re-converge automatically. Non-CJK non-Latin scripts (ar, vi, ...) keep the previous behavior — their templates already read tolerably with English names. If that turns out to be wrong on a real audit, the same guard generalizes by adding the matching Unicode range and locale set. * feat(plugins): translate image-templates bucket title/description across 17 locales 44 of 45 image-templates plugins get title_i18n + description_i18n filled for all 17 non-English locales (zh-CN, zh-TW, ja, ko, de, fr, ru, es, pt-BR, it, vi, pl, id, nl, ar, tr, uk). Generated via Claude Sonnet 4.5 over the OpenRouter gateway, ~$1.38 in API spend, 156s wall-clock. Brand and cultural references stay literal (Open Design, Lego, Hanfu, Showa, Pokémon, Black Myth: Wukong). Long AI generation prompts collapse to a 1-2 sentence summary capturing what the plugin does — the description doubles as catalog blurb on the landing site, not as the actual generation prompt (which lives in example.html / the manifest's preview entry). Skipped: `profile-avatar-realistically-imperfect-ai-selfie` returned malformed JSON on three retries; will rerun with a tighter prompt in a follow-up commit. Catalog rows for that plugin keep falling back to the raw English fields per #3010's reader change, so nothing breaks. Tracking: closes the image-templates row in #3028. * feat(plugins): translate video-templates bucket title/description across 17 locales 49 of 50 video-templates plugins get title_i18n + description_i18n filled for the 17 non-English landing locales. Generated via Claude Sonnet 4.5 over OpenRouter, ~$1.47 in API spend, 177s wall-clock. HyperFrames templates, the Three Kingdoms cinematic series, the Seedance/short-film prompts, and the K-pop / wuxia / anime variants all get a 1-2 sentence catalog blurb in each locale; brand and cultural tokens (Black Myth: Wukong, Hanfu, Showa, Pokémon, Three Kingdoms / 三国志, Lego, Disney, K-pop, HyperFrames) stay literal. Skipped: `live-action-anime-adaptation-water-vs-thunder-breathing-duel` returned malformed JSON on three retries; will rerun in followup. Falls back to the raw English fields per the reader landed in |
||
|
|
7312c64580
|
ci(landing): split landing deploy into staging gate + manual production (#2994)
* ci(landing): split landing deploy into staging gate + manual production A merge to `main` previously published the landing page straight to production (open-design.ai) via `landing-page-deploy`. There was no buffer to review the rendered site, so a bad merge was live instantly. Split deploys across two Cloudflare Pages projects so production is only ever reached by an explicit human action: - `landing-page-staging` (push to main) -> staging project `open-design-landing-staging` -> staging.open-design.ai. - `landing-page-production` (manual workflow_dispatch only) -> production project `open-design-landing` -> open-design.ai. Only this workflow names the production project; gate it with required reviewers on the `production` GitHub environment. - `landing-page-ci` now also deploys a per-PR preview into the staging project (`--branch=pr-<n>`) for same-repo branches and comments the URL. Fork PRs (no secrets / read-only token) skip the deploy and keep just the build validation. Path filters already scope this to landing edits. Decouple search-engine indexing from staging: - `blog-indexing-on-deploy` now triggers on `landing-page-production` (not every main push), so the test environment is never submitted to Google/IndexNow. - It diffs from a new `blog-indexed-prod` tag (the last indexed prod commit) instead of `HEAD^`, and force-advances the tag after a successful run, so a manual promotion bundling several merged posts indexes all of them rather than only the last commit. Staging and PR-preview builds drop `PUBLIC_GA_MEASUREMENT_ID` so test traffic does not pollute the production GA property. * ci(landing): keep staging + PR previews out of the search index staging.open-design.ai mirrors production and is exposed via cert transparency logs, so search engines can discover it. Indexing the mirror competes with open-design.ai for the same content. Emit `<meta name="robots" content="noindex, nofollow">` whenever OD_LANDING_NOINDEX=1, and set that flag on the staging and PR-preview builds (production leaves it unset and stays indexable). noindex is used rather than a robots.txt Disallow so crawlers can still fetch the page and read both the tag and the canonical, which already points at the production origin. * fix(landing): make staging noindex actually take effect The previous commit read `process.env.OD_LANDING_NOINDEX` directly in `seo-head.astro`, but `.astro` frontmatter is transformed by Vite and does not see process.env, so the meta never rendered. Two fixes: - Inject the flag as the compile-time constant `__OD_LANDING_NOINDEX__` via `vite.define` in astro.config.ts (config runs in Node and can read process.env); SeoHead consumes that constant. - The homepage (`index.astro`) and `og.astro` build their own <head> and never use SeoHead, so a per-component meta can miss pages. Add an `astro:build:done` integration that appends a catch-all `/* X-Robots-Tag: noindex, nofollow` to the Cloudflare Pages `_headers` on staging/preview builds, covering every response (homepage, assets, any custom-head page) at the HTTP layer. Production builds leave `_headers` untouched. Verified: build with OD_LANDING_NOINDEX=1 emits the _headers block and the SeoHead <meta>; build without the flag emits neither; astro check clean. * fix(landing): address review — pin prod checkout to main, defer index pointer Two blockers from review: - landing-page-production: workflow_dispatch can be launched from any ref via the Actions "Use workflow from" dropdown, so an operator could ship an arbitrary branch to open-design.ai. Pin the checkout to `ref: main` so the deployed artifact always equals reviewed main. - blog-indexing-on-deploy: the `blog-indexed-prod` pointer was advanced right after sitemap submission, before Inspect / Search Analytics / Render status / Open status PR. A failure in any of those still moved the pointer, so the next production run skipped those posts. Move the advance to the very end, gated on `success()`, so a failure leaves the tag in place and the range is re-processed next run (submissions are idempotent). * fix(landing): gate production promotion to the main ref only Follow-up to the production-path review note: pinning checkout to main fixed the deployed content, but the workflow was still dispatchable from any ref, which records a non-main production run and would dodge blog-indexing's `workflow_run` `branches: [main]` filter. Gate the whole job on `github.ref == 'refs/heads/main'` so a dispatch from any other branch/tag is skipped outright. |
||
|
|
ceb636aa1b
|
feat(landing-page): plugin detail page interactive preview + share dialog (#2958)
* feat(landing-page): plugin detail page interactive preview + share dialog The new `/plugins/<manifest-id>/` detail page that shipped in #2926 landed without the two affordances PR #2679 added to the legacy `/skills/<slug>/` and `/templates/<slug>/` pages: a click-to-expand iframe of the live artifact, and a share dialog with brand-keyword copy plus four-channel jump buttons (X / LinkedIn / Reddit / Facebook). This restores both, sourced from the bundled-plugin manifest under `plugins/_official/<bucket>/<slug>/open-design.json`. ## Interactive preview Three preview-type behaviours, gated on `od.preview.type`: - `video` (Cloudflare Stream URLs already in the manifest) — inline `<video controls poster=...>` with the playable MP4 as `<source>`. Detail-page row is unchanged from #2926; controls double as the open-full affordance. - `html` (a local `example.html` referenced by `od.preview.entry`, only the `examples/` bucket today) — `<details>` toggle wraps the poster image as the summary; clicking opens a sandboxed `<iframe>` that loads the entry HTML lazily, with an "Open in new tab ↗" pill in the frame's top-right corner so the artifact can be inspected at full screen. - `image` or no entry — static `<img>` (existing behaviour). `copy-example-html.ts` is extended to mirror the local entry and any `./assets/...` siblings to `out/plugins/<manifest-id>/<entry>` so the iframe URL resolves on Cloudflare Pages instead of SPA-falling-back to the homepage. The four examples carrying sibling-asset references (flowai-live-dashboard-template, trading-analysis-dashboard-template, open-design-landing, open-design-landing-deck) all render in-place. ## Share dialog Same `<dialog data-share-dialog>` markup the legacy detail pages use, so the global click handlers in `header-enhancer.astro` (`data-share-open` / `data-share-copy` / `data-copy-link`) wire up the open / copy actions automatically — no extra client bundle. Four platform jumps (X / LinkedIn / Reddit / Facebook) plus a Copy-text / Copy-link pair, with a single English template for now (the new `/plugins/...` routes only generate English pages; localisation can land alongside the i18n catch-all follow-up). ## Bundled in - The `copy-example-html.ts` sibling-assets fix from open PR #2880. Without it the existing `/skills/<slug>/` iframe still 404s on Cloudflare Pages for after-hours-editorial-template and the four others; bundling it here means the same script handles both sources in one pass and sidesteps two PRs touching identical helper code. * fix(plugins): remove dangling preview.entry from example-hyperframes The hyperframes example folder ships a SKILL.md (it's an instruction manual for using the HyperFrames HTML format) but no runnable `example.html`. The manifest still claimed `preview.type: html` / `preview.entry: ./example.html`, which made the marketing site try to iframe a non-existent file and forced the preview pipeline into its `Path 3` fallback card — leaving the catalog row visually inconsistent with the eleven sibling `video-template-hyperframes-*` plugins that have real Cloudflare-Stream poster URLs. Drop the preview block entirely so the manifest stops promising a demo it can't deliver. The landing-page detail row continues to render the typographic fallback card (sourced from title / description / mode), which is now the honest representation: "this is an instruction skill, not a renderable template". * fix(landing-page): address PR #2958 review feedback on plugin preview pipeline Two blocking issues called out in code review: 1) `bundled-plugins.ts` exposed `previewEntryUrl` for every manifest that declared `preview.type: "html"`, even when the entry file wasn't shipped. Several first-party manifests fall in this state (example-design-brief's `./brief-preview.html`, example-x-research, example-pptx-html-fidelity-audit, example-hatch-pet, example-last30days, example-guizang-ppt, example-replit-deck, example-live-artifact, example-html-ppt, example-dcf-valuation). The detail page then rendered a click-to-expand iframe and popout link to a file that copy-example-html.ts had skipped, so the iframe URL SPA-fell-back to the homepage on Cloudflare Pages. `entryRelativeUrl()` now `existsSync()`-checks the resolved local path before returning a URL. When the file's missing the detail page falls through to the static thumbnail branch, exactly like plugins that ship no preview entry at all. 2) `copy-example-html.ts` recognised only `(src|href|poster)="./assets/..."` and then bulk-copied the entry's sibling `assets/` folder, so it missed two real ref shapes: bare-relative (`href="assets/styles.css"`, `src="assets/deck-stage.js"` under example-html-ppt-zhangzara-pin-and-paper) and cross-folder (`src="../open-design-landing/assets/hero.png"` under example-open-design-landing-deck). Replaced the heuristic with a generic walker that: - Parses every relative ref in the entry HTML (`(src|href|poster|srcset|data-src)=` plus `url(...)`), splitting srcset on whitespace/commas so multi-URL attrs are honoured. - Resolves each ref against `dirname(entrypointSrc)` for the source and against `dirname(iframeAbsPath)` for the destination — identical to how a browser resolves the same ref against the iframe URL. Files outside the source root or the iframe root are dropped. - Recurses into copied HTML / CSS / JS / SVG so multi-step chains (entry → assets/template.html → assets/fonts/foo.woff) don't strand intermediate files. - Tracks visited *destinations* rather than sources, so a single source that legitimately needs to land at two different out-paths (same-folder copy at /plugins/example-X/assets/foo.png AND a cross-folder copy at /plugins/open-design-landing/assets/foo.png for sibling decks that use `../open-design-landing/assets/foo.png`) gets both copies. Verified manually: - /plugins/example-html-ppt-zhangzara-pin-and-paper/assets/styles.css and assets/deck-stage.js → 200 (bare-relative) - /plugins/open-design-landing/assets/hero.png and assets/about.png → 200 (cross-folder destination, no manifest-id prefix because iframe URL `..` collapses the prefix) - /plugins/example-design-brief/ renders the static thumbnail only, no click-to-expand iframe (broken entry guard) - /plugins/example-flowai-live-dashboard-template/assets/template.html → 200 (existing same-folder behaviour preserved) Build now reports `copied 266 entry files + 65 referenced files`, where the 65 includes both the same-folder `./assets/...` payloads the previous heuristic captured and the bare-relative + cross-folder shapes it didn't. --------- Co-authored-by: Joey-nexu <joeylee12629@gmail.com> |
||
|
|
40ae0836dd
|
feat(landing-page): rebuild plugins library to mirror in-app taxonomy (#2926)
* feat(landing-page): synthesize fallback preview cards for instruction skills
The skill catalog renders a diagonal-stripe placeholder for any skill
without a runnable example.html, which leaves ~70% of /skills/ as a
field of bare grey thumbs (instruction skills like copywriting,
creative-director, color-expert, brainstorming have no static demo
because their output depends on the agent's input).
Synthesize a typographic editorial card from each SKILL.md frontmatter
and screenshot it through the same Playwright pipeline that handles
real demos, so every catalog row carries a thumbnail. Cards include:
- OPEN DESIGN · SKILL top label + Nº NNN index (1..96 over the
instruction subset, sorted by od.featured then alphabetical)
- Big Playfair Display slug with a coral dot accent
- Italic serif description clamped to 3 lines
- mode/category chips + "Curated from <author>" attribution
- Warm-paper background with a subtle 135° stripe to thread the
landing's existing visual language
Bundle a few related improvements caught while building this:
- SkillRecord gains a `kind: 'instruction' | 'template'` field so
the detail page can render differently per kind (instruction
skills now render the SKILL.md body inline as "About this skill",
template skills keep the click-to-expand iframe demo).
- Catalog row thumbnails switch from the bespoke IntersectionObserver
pipeline to native `loading="lazy"` (with eager + fetchpriority=high
on the first 3). The observer's swap latency stranded mid-list
rows on the SVG placeholder during fast scrolls; native lazy uses
the browser's 1250-3000px lookahead so the placeholder flash is
gone.
- precise-lazyload rootMargin bumped to 1500px for any remaining
data-precise-src callers.
- CI cache key for generated previews now folds in
fallback-preview-card.ts so a template tweak invalidates the cache.
* feat(landing-page): rebuild plugins library to mirror in-app taxonomy
The marketing site's `/skills/`, `/templates/`, `/systems/`, `/craft/`
top-level entries were organized around author-supplied `od.mode` /
`od.scenario` taxonomies that visitors never see inside Open Design
itself. The in-app Plugins home (`apps/web/src/components/plugins-home/`)
groups every bundled plugin by the artifact it produces — Prototype,
Live Artifact, Slides, Image, Video, HyperFrames, Audio — and that's
the language users encounter the moment they open the product.
This PR rebuilds the public library around the same taxonomy and the
same data source so a visitor reading "Templates · 231" on the
marketing site sees the same 231 inside the app.
## What changes
- New top-level `/plugins/` hub: four tiles (Templates, Skills,
Systems, Craft) with live counts pulled straight from
`plugins/_official/<bucket>/<slug>/open-design.json` — the daemon's
bundled-plugin registry.
- `/plugins/templates/` lists every bundled plugin that lands in one
of the seven artifact kinds. Seven sub-routes
(`/plugins/templates/prototype/`, `/deck/`, `/image/`, `/video/`,
`/hyperframes/`, `/audio/`, `/live-artifact/`) carry the same chip
rail with an active state, so visitors can switch artifact kinds
with one click without losing the rail.
- Each artifact-kind sub-route shows a Scene chip rail when the kind
has scene buckets (Prototype / Slides / Image / Video each get
five-six). The Scene filter runs client-side via inline `style.display`
toggles; URLs stay one-per-kind so we don't multiply 25 × 18 locales
worth of static pages just for filter combinations.
- `/plugins/skills/` collects the instruction-only entries (mode
doesn't fit any of the seven kinds) — copywriting, color theory,
creative direction, brainstorming, etc.
- `/plugins/systems/` lists the 150 bundled design systems via the
legacy SystemCard renderer (palette swatches, tagline) so the
visual treatment matches the in-product library.
- `/plugins/craft/` keeps the existing craft principles list.
- `/plugins/<manifest-id>/` detail pages built from manifest metadata:
hero (poster image or playable Cloudflare Stream MP4 for video
templates), author / mode / scenario / tags, GitHub source link.
Author URLs pointing at the `nexu-io` org redirect to the
`nexu-io/open-design` repo so the attribution is actionable.
- Header dropdown labelled "Plugins" with the four sub-routes; footer
Library column updated to match.
- Old marketplace registry pages under `/plugins/` and
`/[locale]/plugins/` removed (they were a dormant placeholder UI;
the actual manifests it tried to load lived nowhere). The rest of
the legacy plugin-registry loader stays intact for any other
consumer.
## Preview generation
Bundled plugins ship `od.preview.poster` URLs on R2 for image and
video templates; those are used directly. The other 293 entries
(html-mode examples, design-systems, scenarios) had no poster, so
`generate-previews.ts` was extended to:
1. Screenshot a local `example.html` referenced by `od.preview.entry`
when present (134 examples).
2. Synthesize the same typographic editorial card the SKILL.md
fallback uses, sourced from manifest title / description / mode /
author (159 systems / scenarios / misc).
Output lands at `public/previews/plugins/<manifest-id>.png`. The
catalog loader checks for the local file when the manifest carries no
poster URL, so the row's `<img src>` always has something to point at.
Result: every catalog row and every detail page has a thumbnail;
visiting `/plugins/templates/video/` shows the same 48 entries the
in-app Plugins home shows, hyperframes the same 13, etc.
## Counts
- Templates: 231 (Prototype 59 + Slides 59 + Image 46 + Video 48 +
HyperFrames 13 + Audio 1 + Live Artifact 5)
- Skills: 15
- Systems: 150
- Craft: 11
Atoms (13 infrastructure plugins, `od.kind === 'atom'`) are filtered
to mirror the in-app behaviour.
* fix(landing-page): use Astro 6 render() helper for SKILL.md body
Astro 6 dropped `entry.render()` in favour of a top-level `render(entry)`
helper imported from `astro:content`. The instruction-kind skill detail
page was still using the legacy method, which compiled locally on Astro
6 only because tsx ignored the missing prototype method, but `astro
check` (run in CI) flagged it as ts(2551) and broke the workflow.
---------
Co-authored-by: Joey-nexu <joeylee12629@gmail.com>
|
||
|
|
b8cddc421e
|
fix(landing-page): drop trailing slash from preview iframe URLs (#2790)
Some checks failed
visual-baseline / Capture visual baselines (push) Waiting to run
actionlint / Lint GitHub Actions workflows (push) Failing after 2s
ci / Detect CI change scopes (push) Successful in 1s
landing-page-ci / Validate landing page (push) Failing after 2s
landing-page-deploy / Deploy landing page (push) Has been skipped
nix-check / build (push) Failing after 2s
ci / Validate Nix flake (push) Has been skipped
ci / Preflight (push) Failing after 2s
ci / Workspace unit tests (push) Failing after 1s
ci / Daemon workspace tests (push) Failing after 1s
ci / Web workspace tests (push) Failing after 1s
ci / Browser tests (push) Failing after 1s
ci / Build workspaces (push) Failing after 1s
ci / Validate workspace (push) Failing after 0s
ci / Runtime trace (push) Has been skipped
The detail-page interactive preview iframe pointed at `/skills/<slug>/example.html/` and `/templates/<slug>/preview.html/` with trailing slashes. Cloudflare Pages 308-redirects those URLs to the extension-stripped form, but with the trailing slash present it fails to map back to the published `out/skills/<slug>/example.html` file and SPA-falls-back to the homepage. Result: every preview iframe in production rendered the homepage instead of the skill or live-artifact preview. Verified against the deployed site after the #2679 release: - /skills/deck-guizang-editorial/example.html → 4942 bytes (real preview) - /skills/deck-guizang-editorial/example.html/ → 163377 bytes (homepage SPA fallback) - /skills/deck-guizang-editorial/example → 4942 bytes (real preview) - /skills/deck-guizang-editorial/example/ → 4942 bytes (real preview) Drop the trailing slash from all six iframe `src` and "Open in new tab" `href` attributes in `pages/skills/[slug]/index.astro` and `pages/templates/[slug]/index.astro`, plus the inline comment that documented the URL shape. Co-authored-by: Joey-nexu <joeylee12629@gmail.com> |
||
|
|
1c7233ef10
|
fix(landing-page): speed up landing-page CI builds (#2734)
* fix(landing-page): speed up landing-page CI builds * fix(landing-page): disable dev-only landing caches Generated-By: looper 0.0.0-dev (runner=fixer, agent=opencode) * fix(landing-page): reuse previews across CI runs * fix(landing-page): hash shared preview dependencies Generated-By: looper 0.0.0-dev (runner=fixer, agent=opencode) * fix(landing-page): skip missing preview html reads Generated-By: looper 0.0.0-dev (runner=fixer, agent=opencode) * fix(landing-page): rerun previews on cache hits Generated-By: looper 0.0.0-dev (runner=fixer, agent=opencode) * fix(ci): repair landing-page workflow cache keys |
||
|
|
f2703c8cb3
|
blog(landing-page): announce Open Design 0.8.0 plugin engine rebuild (#2731)
* blog(landing-page): announce Open Design 0.8.0 plugin engine rebuild
Adds the Product (announcement) post for the open-design-v0.8.0 release
(305 PRs · 75 contributors · 7 days; tag
|
||
|
|
829fc01c1c
|
feat(landing-page): detail pages — interactive preview, share row, dual CTAs (#2679)
* feat(landing-page): detail pages — interactive preview, share row, dual CTAs
Joey requested three additions to every `/skills/<slug>/` and
`/templates/<slug>/` detail page, with opendesigner.io's skills
catalog and youmind.com's seedance prompt page as references.
What
- **Interactive preview**: a `<details>` toggle below the static thumb
reveals an `<iframe sandbox>` rendering the canonical artifact
(`/skills/<slug>/example.html` for skill-template origins,
`/templates/<slug>/template.html` for live-artifact origins). The
iframe loads lazily — only on first toggle — so the page stays fast.
An "Open in new tab ↗" pill on top-right of the frame links to the
same URL standalone.
- **Six-channel share row**: Reddit, X, LinkedIn, Facebook, Email,
Copy-link. Each anchor is a vendor "intent" URL (no tracker SDKs);
the copy-link button uses the Clipboard API with a `prompt()`
fallback for older Safari / embedded webviews. Wired by a small
handler appended to `header-enhancer.astro`.
- **Two primary CTAs** in the detail-actions row:
- "Use this skill →" / "Use this template →" routes to
`/quickstart/?skill=<slug>` (or `?template=<slug>`). The OD
desktop client has no public protocol handler yet, so a
`od://skill/<slug>` deep link would 404. Quickstart is the v1
pivot; once the client registers a scheme, the anchor flips to
a JS try-`od://`-then-fallback without changing the page surface.
- "Find on GitHub →" deep-links into the source folder.
Share copy keeps "open-source Claude Design alternative" front and
center across every channel — same brand keyword Google associates
with the homepage and `/alternatives/claude-design/`, so each social
click reinforces the same entity claim. Per-skill name + summary
follow so a reader who lands on a friend's tweet has a concrete
reason to click.
- X intent: "I'm using <skill> from @opendesignai — the open-source
Claude Design alternative.\n\n<description>"
- Reddit submit title: "<skill> — open-source Claude Design alternative"
- Email subject: same as Reddit; body: "I thought you'd like this —
<skill>, an open-source Claude Design alternative skill from Open
Design.\n\n<description>\n\n<url>"
- LinkedIn / Facebook: URL-only (those vendors auto-fetch OG meta,
so they read the existing canonical title + image).
Surface area
- Marketing site only. `apps/landing-page/app/pages/skills/[slug].astro`,
`pages/templates/[slug].astro`, `_components/header-enhancer.astro`,
`sub-pages.css`.
- No `apps/web`, no `apps/daemon`, no contracts, no CLI surfaces.
- No new top-level dependencies. WeChat QR was dropped from the v1
scoping in favor of Joey's revised channel set; brings Reddit and
Facebook in instead.
Validation
- `pnpm --filter @open-design/landing-page typecheck` — 0 errors
- Local dev: `/skills/deck-swiss-international/` shows all six share
buttons, both CTAs, and the iframe `<details>` toggle. Same on
`/templates/magazine-poster/`.
- Local dev: Reddit submit URL contains the SEO keyword in the title
param; X intent URL contains the @opendesignai mention + keyword
in the tweet body; Email mailto: subject + body wired correctly.
Followups
- Once OD desktop client registers a `od://` scheme, flip the "Use
this skill" anchor to JS-driven try + fallback so installed users
bypass /quickstart/.
- Translate the share copy + CTA labels across the 18 landing
locales (currently English-only).
- `i18n.ts` `ui.catalog.skills` keys could absorb the share-copy
template if we want per-locale share text in the future.
* fix(landing-page): preview clicks the thumb; CTA goes to releases
Two follow-ups to #2679 against Joey's review.
1. Preview UX: the thumb is the trigger
The previous shape rendered a static thumb followed by a separate
"View interactive preview ▸" disclosure row underneath. Joey wanted
one composed unit: click the thumb itself to open the live frame.
Wraps the existing `<details>` so that `<summary>` IS the thumb
image (with a hover overlay revealing "Click for live preview ↗"),
and once open the summary hides so the iframe lands in the same
visual slot. The figcaption moves below the open/closed unit so it
labels both states identically.
2. "Use this skill" / "Use this template" → /releases
Sends users straight to the desktop-app release page rather than
pivoting through /quickstart/. The flow is now concrete (download
the binary now) instead of asking users to read an install doc as
step 0. Once the desktop client registers a `od://skill/<slug>`
protocol handler, this anchor flips to a JS try-deep-link-then-
fallback without changing the page surface.
Note on the other two issues Joey raised:
- example.html 404: production has all 4 example files at HTTP 200
(verified with curl). The 404 in his screenshot was production
serving the previous deploy that pre-dates this PR; the fix is in
flight, not a missing route. Once #2679 deploys, the iframe will
resolve cleanly.
- Empty share copy: same root cause. Production HTML still rendered
the pre-#2679 share row (no copy at all). Local dev confirms the
X intent URL contains the full "I'm using <skill> from
@opendesignai — the open-source Claude Design alternative…"
string in the `text` param; Reddit submit URL contains the
"<skill> — open-source Claude Design alternative" title; Email
mailto: subject and body are wired. LinkedIn and Facebook are
URL-only by their vendor design — those platforms read the OG
meta tags from the destination page itself.
Surface area
- Marketing site only. `pages/skills/[slug].astro`,
`pages/templates/[slug].astro`, `sub-pages.css`.
- No `apps/web`, no `apps/daemon`, no contracts, no CLI.
- No new dependencies.
Validation
- `pnpm --filter @open-design/landing-page typecheck` — 0 errors
- Local dev: skill detail thumb shows the live-preview overlay on
hover; click opens the iframe in the same frame. Use this skill
→ opens https://github.com/nexu-io/open-design/releases. Same on
the templates detail page.
* fix(landing-page): example.html copy step + share dialog with copy-then-paste flow
Two follow-ups against Joey's review of #2679.
1. example.html 404 — production was SPA-falling back to the homepage
The "404" Joey screenshotted on
`/skills/deck-guizang-editorial/example.html` was a Cloudflare Pages
SPA fallback: the URL returned HTTP 200 but the body was the
homepage HTML, so the iframe loaded "the homepage inside the iframe"
which the browser displays as broken-page. Root cause: the build
artifact never contained `out/skills/<slug>/example.html`. Astro
generates `<slug>/index.html` for the detail page from `[slug].astro`,
but the canonical `example.html` next to the SKILL.md file in the
repo root never gets copied into `out/`.
Adds `scripts/copy-example-html.ts` and chains it into the
`build` script. After `astro build`, the script walks:
- `skills/<slug>/example.html` → `out/skills/<slug>/example.html`
- `design-templates/<slug>/example.html` → `out/skills/<slug>/example.html`
(design-templates surface as skill-template-origin records in the
catalog and the iframe targets the `/skills/<slug>/example.html`
path for those.)
- `templates/live-artifacts/<slug>/template.html` → `out/templates/<slug>/template.html`
(live-artifact-origin records — the iframe targets template.html.)
Source files that don't exist are silently skipped. The script
prints a summary line so the build log makes the count visible.
2. Share UX — modal with copy-then-paste flow
The previous inline 6-button row had two problems Joey called out:
- Position was below the meta block, not prominent enough.
- LinkedIn and Facebook ignore `text` pre-fill params, so users
landing on those platforms saw an empty composer with no idea
what to write. X / Reddit pre-fill works but truncates Chinese
unpredictably.
Replaces the row with a `<dialog>` modal:
- A `Share ↗` button sits inside `.detail-actions` next to the
primary CTAs, so it has equal visual weight.
- Clicking opens the dialog with the canonical share copy
(containing the brand SEO keyword "open-source Claude Design
alternative") in a readonly `<textarea>`.
- `Copy text` button writes the textarea contents to the clipboard
(with a `prompt()` fallback for older browsers) and flashes the
coral confirmation state.
- `Copy link only` writes just the URL.
- Below: a row of platform jump buttons (X · LinkedIn · Reddit ·
Facebook · Email). Each opens the vendor's compose URL in a new
tab. The user pastes the already-copied text — uniformly
reliable across every platform.
- Modal closes via the × button (form method="dialog") or Escape.
Native `<dialog>` element + `showModal()` API. No new dependencies;
the JS handler lives in the existing `header-enhancer.astro`
inline script alongside the headroom + stars + hamburger handlers.
Surface area
- Marketing site only. `pages/skills/[slug].astro`,
`pages/templates/[slug].astro`, `_components/header-enhancer.astro`,
`sub-pages.css`, plus the new `scripts/copy-example-html.ts` and
one-line `package.json` build script change.
- No `apps/web`, no `apps/daemon`, no contracts, no CLI.
- No new dependencies.
Validation
- `pnpm --filter @open-design/landing-page typecheck` — 0 errors
- Local dev: `/skills/<slug>/` shows the `Share ↗` trigger inside
detail-actions; clicking opens the modal with the readonly
textarea pre-filled with the canonical share copy. Copy text /
Copy link only both flash coral on click and write to clipboard.
Platform buttons open compose pages in new tabs.
- After deploy: `/skills/<slug>/example.html` will resolve to the
actual canonical example output rather than SPA-falling back to
the homepage. Same for templates.
* fix(landing-page): example.html endpoint routes + locale-aware share + brand logos
Three follow-ups against Joey's review of #2679 round 2.
1. example.html 404 — root cause + proper fix
The 404 Joey kept seeing was real, not a deploy lag: nothing in
the build pipeline copied `skills/<slug>/example.html` from the
repo root into the landing-page output. Astro generated only the
detail-page `index.html`; Cloudflare Pages SPA-fell-back to the
homepage on requests for `example.html`, which the browser
rendered as "wrong page in iframe" and Joey read as 404.
Replaces the post-build copy script (`scripts/copy-example-html.ts`,
removed) with two Astro endpoint routes:
- `pages/skills/[slug]/example.html.ts` — streams the canonical
example for skill-template-origin records, including the
design-templates passthrough
(`design-templates/<slug>/example.html` → same URL).
- `pages/templates/[slug]/template.html.ts` — streams the canonical
artifact for live-artifact-origin templates.
Both use `getStaticPaths` so Astro pre-renders into the static
build artifact under `out/`. Works in dev (Astro dev server runs
the endpoint live) and prod (file is on disk after `astro build`).
Required moving `pages/skills/[slug].astro` →
`pages/skills/[slug]/index.astro` (and same for templates) because
Astro can't have BOTH a `[slug].astro` file AND a `[slug]/`
directory with dynamic param children at the same level. The
`[locale]/skills/[slug].astro` re-exporters were updated to point
at the new index files.
`trailingSlash: 'always'` rewrites endpoint URLs to `path/`, so the
iframe `src` and "Open in new tab" anchor now use
`example.html/` and `template.html/` (with trailing slash). Tested
locally: HTTP 200 + real example HTML in the body.
2. Share copy now per-locale; description dropped
The previous template hardcoded the framing in English ("I'm using
X from @opendesignai…") with the description following from
`skill.description`. Joey's catch: when the SKILL.md description is
in one language and the page locale is another, the share text
reads as a forced bilingual mash-up.
Adds an inline `SHARE_COPY` table per landing locale (18 entries,
one per locale). Drops the description from the share template
entirely — the framing + URL is enough to prompt a click, and
removes any chance of a bilingual mismatch when SKILL.md
frontmatter happens to be in a non-matching language.
The brand keyword "open-source Claude Design alternative" stays
English because that's the canonical search query Google
associates with the domain — translating it would split the
entity claim. Surrounding sentence translates per locale so the
message reads as one voice.
Same template added for templates/[slug]/index.astro.
3. Share dialog UI: brand logos for the 4 platform jump buttons; Email dropped
Replaces the previous text labels (`X` / `LinkedIn` / `Reddit` /
`Facebook` / `Email`) with inline-SVG brand logos. Per Joey's
revision the Email channel was dropped — Gmail / Outlook
pre-fill is reliable but the audience reach is much smaller than
the four social platforms, and removing it tightens the row.
Logos are SimpleIcons-style SVG paths inlined directly (no font
dependency, no external icon library). Each button keeps an
`aria-label` plus a visually-hidden `<span class="sr-only">`
for screen readers.
Surface area
- Marketing site only. `pages/skills/[slug]/index.astro`,
`pages/skills/[slug]/example.html.ts`,
`pages/templates/[slug]/index.astro`,
`pages/templates/[slug]/template.html.ts`,
`_components/header-enhancer.astro`, `sub-pages.css`,
`package.json` (build script revert), and the two `[locale]/...`
re-exporters.
- No `apps/web`, no `apps/daemon`, no contracts, no CLI surfaces.
- No new top-level dependencies.
- The two restructured detail pages keep their existing route URLs
and existing static-paths logic — only the file location changed.
Validation
- `pnpm --filter @open-design/landing-page typecheck` — 0 errors
- Local dev: `/skills/deck-guizang-editorial/example.html/` returns
HTTP 200 with a 4942-byte body that's the actual canonical
example output (not the homepage SPA fallback).
- Local dev: `/skills/deck-swiss-international/` share dialog shows
4 brand-logo platform buttons (no Email); textarea contains the
English-only framing + URL. `/zh/skills/...` shows the Chinese
framing + URL with no English bleed-through.
* fix(landing-page): punchier share copy with emojis across 18 locales
The previous share template ("I'm using <name> from @opendesignai —
the open-source Claude Design alternative.\n\n<url>") was too flat to
spark a click — Joey called it out as 平淡 with the keyword
front-and-center but no hook.
New shape: three-line punchy block with emojis as visual anchors.
Skills surface (`/skills/<slug>/`):
🎨 Just discovered <name> on @opendesignai — the open-source
Claude Design alternative.
✨ Local-first · BYOK · your agent does the design.
→ <url>
Templates surface (`/templates/<slug>/`):
🎨 Just forked <name> from @opendesignai — the open-source
Claude Design alternative.
✨ Templates as files, not vendor docs. Fork → swap → ship.
→ <url>
Pattern per locale:
- Line 1: action verb hook (`Just discovered` / `Just forked` /
locale equivalent like `安利一个` / `推薦一個` / `Gerade entdeckt` /
`Découvert` / etc) + skill name + brand keyword.
- Line 2: tight value-prop with `·` separators — Local-first ·
BYOK · agent does the design (skills) or Templates as files,
not vendor docs (templates).
- Line 3: → URL.
Both lines lead with an emoji (🎨 then ✨) so the post visually pops
in a feed. The brand keyword "open-source Claude Design alternative"
stays English in every locale (canonical search query for the
domain); surrounding sentence translates per locale.
All 18 landing locales rewritten — ar, de, en, es, fr, id, it, ja,
ko, nl, pl, pt-br, ru, tr, uk, vi, zh, zh-tw. Skills and templates
each have their own `SHARE_COPY` table; the templates variant has
fork-flavored framing because the user action there is fork-and-ship,
not run-once.
Surface area
- Marketing site only. `pages/skills/[slug]/index.astro` and
`pages/templates/[slug]/index.astro`.
- No other files touched. No new dependencies.
Validation
- `pnpm --filter @open-design/landing-page typecheck` — 0 errors
- Local dev: en / zh / ja all render with emojis intact and
language-specific framing; X intent URL preserves the multiline
breaks via `\n` in the `text` query param.
* fix(landing-page): restore post-build copy step for preview iframes
The detail-page interactive preview iframe pointed at endpoint routes
(`pages/skills/[slug]/example.html.ts`,
`pages/templates/[slug]/template.html.ts`) introduced in
|
||
|
|
1fd50197dd
|
perf(landing): route HTML Anything screenshots through R2 + Image Resizing (#2696)
The `/html-anything/` landing page was loading 11 screenshots directly
from `raw.githubusercontent.com/nexu-io/html-anything/main/docs/…` —
that's 6.5 MB of raw PNGs per first visit, no AVIF/WebP transcoding,
and a separate cross-origin TLS handshake the rest of the site doesn't
pay. The TODO comment at line 70 of the page has been calling this out
since the page landed.
This PR routes those images through the same pipeline as `hero.png`:
- Source PNGs upload to `static.open-design.ai/landing/assets/html-anything/`
on the existing `open-design-static` R2 bucket. Upload package lives
at `~/Downloads/r2-html-anything-orig/` (README + upload.sh ready);
merge AFTER running that script.
- Page uses `imageAsset('html-anything/<name>.png', { width, quality })`
so CF Image Resizing emits `format=auto` AVIF/WebP variants — same
contract every other landing image already uses.
- Banner is the LCP element; gets `srcset` across 768/1280/1920/2400
(mirroring `heroImageSrcset`) plus its existing `fetchpriority="high"`.
- 10 below-the-fold screenshots become `<LazyImg>` so they pick up the
precise IntersectionObserver (rootMargin 300px) instead of Chrome's
native lazy load that over-prefetches up to 3000px.
Expected on first visit (mobile, AVIF-capable browser):
banner: ~150 KB AVIF (was 2.05 MB PNG)
10 screenshots ~80-120 KB each (was 500-900 KB PNG each)
total ~1.2-1.5 MB (was 6.5 MB)
LCP estimated -800ms to -1.5s
|
||
|
|
558fedd207
|
fix(landing): wire GA4 rollout config (#2615)
Co-authored-by: ashley li <ashleyli@ashleydeMacBook-Air-2.local> Co-authored-by: Cursor <cursoragent@cursor.com> |
||
|
|
5f7d65d513
|
perf(landing): preconnect api.github.com + rAF-throttle scroll listener (#2666)
Two PSI-targeted wins (split from #2599 follow-up). 1. New `resource-hints.astro` mounted in every page's <head> declares `<link rel="preconnect" href="https://api.github.com" crossorigin>`. The inline enhancer script on /` issues 3 fetch() calls to api.github.com right after DOMContentLoaded (stars, latest release, contributors). Without preconnect each pays a full DNS + TCP + TLS handshake (~150-300ms) inline with the fetch. With preconnect those handshakes happen in parallel with HTML parse and all three share one warmed HTTP/2 connection. 2. Wrap the scroll listener's read + classList write in requestAnimationFrame. Trackpads and high-rate wheels fire scroll faster than display refresh, and every callback that hits classList triggers layout recalc. PSI was attributing ~700ms of "forced reflow" to the un-throttled version. The rAF gate collapses each burst to one DOM mutation per frame; `{ passive: true }` is preserved so the listener still doesn't block the scroll thread. Same throttling pattern mirrored to `header-enhancer.astro` (used by every sub-page) and `home-enhancer.astro` (kept in lockstep even though /` currently uses its own inline copy). Expected PSI delta: - "Preconnect to required origins" hint: cleared - "Forced reflow" diagnostic 700ms → near zero - LCP: small bonus from earlier GH fetch warm-up (~100-300ms) |
||
|
|
7f03030f3f
|
perf(landing): self-host fonts + inline critical CSS (#2599)
* perf(landing): self-host fonts + inline critical CSS
PageSpeed Insights flagged ~2.3s of render-blocking on /:
globals.css 12.9 KB external link, 160ms
fonts CSS 2.2 KB fonts.googleapis.com, 750ms
+ 4 woff2 ~1200ms each from fonts.gstatic.com
Two changes drop that whole chain:
1. Self-host fonts via @fontsource-variable/{inter,inter-tight,
playfair-display,jetbrains-mono}. Each family ships a single variable
woff2 (covers all weights we use) that Astro bundles into /_astro/*
alongside the rest of the build, served same-origin through CF Pages —
no separate TLS handshake, no Google Fonts CSS round-trip. The CSS
variable names get an extra alias in front (`'Inter Tight Variable',
'Inter Tight', ...`) so a system fallback still works if the package
ever ships under a different family name.
2. `astro.config.ts: build.inlineStylesheets: 'always'` inlines every
emitted <style> into the HTML <head> instead of emitting a separate
/_astro/*.css link. The HTML grows from ~13KB to ~28KB (gzip) but
loses one stylesheet round-trip + the entire @font-face chain that
used to gate text rendering.
Component cleanup: the `<FontStylesheet>` component (preconnect + link to
fonts.googleapis.com) is no longer needed and is deleted, removed from
all 7 places that mounted it. og.astro keeps its own font setup since
it renders to a screenshot.
Expected effect (from PageSpeed Insights "Render-blocking requests"
diagnostic on the previous build):
FCP 1.9s → ~1.2s
LCP 2.2s → ~1.5s
Verified: pnpm typecheck 0 errors, pnpm build 1853 pages 78s, preview
serves /_astro/*.woff2 as font/woff2 same-origin, 0 fonts.googleapis or
fonts.gstatic references in the built HTML.
* perf(landing): include Playfair italic + bump nix pnpm-deps hash
Two follow-ups on the self-host fonts PR:
1. globals.css imported only `@fontsource-variable/playfair-display`,
which ships @font-face for font-style: normal only. The previous
Google Fonts URL included the italic axis (`ital,wght@0,500;1,400;
...`) and several rules (.roman, .work-rule .roman, .sec-rule .roman,
plus 8 other italics across globals.css + sub-pages.css) render
Playfair italics via `font-family: var(--serif); font-style: italic`.
Without the italic face self-hosted, those would fall through to
Times New Roman italic or browser synthesis. Adding
`wght-italic.css` keeps the typography visually equivalent.
2. nix/pnpm-deps.nix uses a fixed-output derivation hash that has to
match the pnpm vendored store; adding the four fontsource packages
changed pnpm-lock.yaml so the hash has to be bumped to the value Nix
reported in CI.
Codex (Looper reviewer) flagged #1 as non-blocking.
* perf(landing): pin fontsource versions exactly per repo guard
`pnpm add` defaulted to caret ranges (`^5.2.8`) but repo guard rejects
non-exact specs ("dependency specs must be exact versions like 1.2.3 or
workspace:*"). That was the actual cause of the Preflight + Validate
workspace failures — pinning to the locked versions Codex reviewer
called out:
@fontsource-variable/inter 5.2.8
@fontsource-variable/inter-tight 5.2.7
@fontsource-variable/jetbrains-mono 5.2.8
@fontsource-variable/playfair-display 5.2.8
`pnpm guard` now passes locally (6/6 tests).
|
||
|
|
4cb5778669
|
feat(landing-page): retitle HA page for brand-defense SEO across 18 locales (#2618)
Some checks failed
visual-baseline / Capture visual baselines (push) Waiting to run
ci / Detect CI change scopes (push) Successful in 1s
landing-page-ci / Validate landing page (push) Failing after 3s
landing-page-deploy / Deploy landing page (push) Has been skipped
nix-check / build (push) Failing after 2s
ci / Validate Nix flake (push) Failing after 1s
ci / Preflight (push) Failing after 1s
ci / Core package tests (push) Failing after 1s
ci / Tools workspace tests (push) Failing after 1s
ci / Daemon workspace tests (1/2) (push) Failing after 1s
ci / Daemon workspace tests (2/2) (push) Failing after 1s
ci / Web workspace tests (push) Failing after 1s
ci / E2E vitest (push) Failing after 1s
ci / Playwright critical (starters) (push) Failing after 2s
ci / Playwright critical (core) (push) Failing after 1s
ci / Build workspaces (push) Failing after 1s
ci / App workspace tests (push) Failing after 1s
ci / Validate workspace (push) Failing after 1s
ci / Runtime trace (push) Has been skipped
Replaces the HA page `<title>` from the previous descriptive form
("HTML Anything — your local AI agent writes the HTML, you ship
it | Open Design") with a noun-stack that captures three high-value
brand-defense queries:
- "open source html anything" — researching alternatives
- "html anything official" — verifying the canonical source
- "open source html anything official" — precise long tail
Why
- The descriptive title before this change told the story but didn't
match what users actually type when they land on this page from
search. The brand has been growing fast (4.1K+ stars on the repo);
visitors increasingly arrive with intent to verify ("is this the
real one?"), to compare alternatives, or to find the canonical
install path. The new title format leads with the tokens those
queries hit.
- Joey called the change explicitly. The brand has reached a stage
where SEO defense beats descriptive copy on a one-line surface.
What
- New `OPEN_SOURCE_OFFICIAL_TITLE` constant maps every landing-page
locale to its translated title. Pattern: `{open-source} HTML Anything
{official}` with no separator. Word order follows what's natural in
each locale (German: Offiziell at end; French/Romance: adjectives
after the noun; CJK: adjectives before).
- The three explicit copy blocks (`HTML_ANYTHING_COPY_EN`,
`_ZH`, `_ZH_TW`) all reference the constant rather than holding
their own literal title strings, so future title changes update
in one place.
- `derivedHtmlAnythingCopy()` (which builds copy for the 15
non-explicit locales by spreading EN over locale UI strings) now
pulls the title from the same constant via `[locale]` lookup with
EN as a defensive fallback.
- Adds three new `SoftwareApplication.alternateName` entries for the
same query stack. Schema-level signal complements the title-level
signal: the structured-data entity now matches whichever shape the
user typed regardless of UI locale, since these are proper-noun
query variants that don't translate.
Surface area
- Marketing site only. Single file: `app/pages/html-anything/index.astro`.
- No `apps/web`, no `apps/daemon`, no contracts, no CLI.
- No new dependencies.
Validation
- `pnpm --filter @open-design/landing-page typecheck` — 0 errors.
- Local dev: every locale's HA page renders the new title.
- en → `<title>Open Source HTML Anything Official</title>`
- zh → `<title>开源 HTML Anything 官方</title>`
- ja → `<title>オープンソース HTML Anything 公式</title>`
- de → `<title>Open Source HTML Anything Offiziell</title>`
- ar → `<title>HTML Anything مفتوح المصدر الرسمي</title>`
- …same shape across all 18 landing locales.
Co-authored-by: Joey-nexu <joeylee12629@gmail.com>
|
||
|
|
3db1e27b81
|
fix(landing-page): translate Library/Tutorials, drop dropdown numbers, dedupe Product menu (#2610)
* fix(landing-page): translate Library/Tutorials, drop dropdown numbers, dedupe Product menu Three small UX issues surfaced after #2605 landed Tom's full i18n bundle. None of them block deploy; all of them affect every visitor who lands on the site, especially the non-English ones. Issues 1. The new Library / Tutorials nav labels in #2605's header restructure were hardcoded English. On `/zh/`, `/ja/`, `/de/`, etc. the rest of the nav was localized but those two labels stayed "Library" / "Tutorials" inside otherwise-translated chrome. 2. The four catalog facets inside the Library dropdown carried the live count badges from the previous top-row treatment ("Skills132", "设计系统150", etc.). Inside a dropdown they read as a glued-on suffix rather than a tag, and Joey called them out as "weird — drop them entirely". 3. The Product dropdown still listed Tutorials as a child item alongside Open Design and HTML Anything. After #2605 made Tutorials a standalone top-level link, the duplicate was confusing — same URL appearing in two places in the same nav row. 4. Chinese (zh and zh-tw) had `skills: 'Skill'` — the singular English word, untranslated in an otherwise fully Chinese nav. Fixes - Extends `HeaderCopy.nav` interface with `library` and `tutorials` keys; populates both across all 18 landing locales (en, zh, zh-tw, ja, ko, de, fr, ru, es, pt-br, it, vi, pl, id, nl, ar, tr, uk). - Updates `header.tsx` to read `headerCopy.nav.library` and `headerCopy.nav.tutorials` instead of literal strings. - Removes the four `<span class="dropdown-num">{count}</span>` badges from inside the Library dropdown items. The numbers still sit on the catalog index pages themselves (header counts, hero) and on the homepage; the dropdown is for navigation, not status. - Removes the Tutorials `<li>` from the Product dropdown — Tutorials lives at the top-row level only. Updates the Product trigger's `is-active` set so it no longer highlights when on `/tutorials/`. - zh/zh-tw `nav.skills` switches to `技能` (the actual translation every other Chinese localized site uses for "skills" in this context). Bonus: catalog-row horizontal padding unified at 24px to match the fix already applied across `featured-card` / `template-card` / `system-card` / `source-card` (see PR #2600). The skill list grid on `/skills/` was the last family still on `padding: 22px 0`. Surface area - Marketing site only. `apps/landing-page/app/_components/header.tsx`, `app/i18n.ts`, `app/sub-pages.css`, `app/globals.css`. - No `apps/web`, no `apps/daemon`, no contracts, no CLI surfaces. - No new dependencies. Validation - `pnpm --filter @open-design/landing-page typecheck` — 0 errors - Local dev: `/zh/` shows `资源库 / 教程 / 博客`; `/zh/` Library dropdown shows `技能 / 设计系统 / 模板 / 工艺` without count badges. - Local dev: `/` Product dropdown shows only Open Design + HTML Anything (no Tutorials child); top-row Tutorials link is the single canonical entry to that page. * fix(landing-page): keep catalog-row horizontal padding at narrow widths #2610's catalog-row 24 px fix only addressed the desktop rule. The `@media (max-width: 720px)` override block still set `.catalog-row a { padding: 18px 0 }` — zero horizontal — which is what Joey's screenshot was actually showing on narrow viewports. Switches the narrow padding to `18px 20px`. Matches the same narrow inset `.source-card` already uses (line 1058 of this file) so the catalog-row family stays in lockstep with the rest of the catalog tile families at narrow widths. Also drops the now-stale hover override (`padding-left: 8px; padding-right: 8px`) — that rule existed only to compensate for the zero-horizontal default and now produces a visible jolt against the new 20 px base. * fix(landing-page): featured-card description honors the 24px gutter Same shorthand fragility pattern that #2600 fixed for template-card, just one card family further down the file. `.featured-card p` (the description line on each featured strip tile, e.g. "Nº 0.001 deck-swiss-international" with the 16-列网格 blurb under it) had `margin: 0 0 12px` — three-arg shorthand, which expands to TOP RIGHT BOTTOM = LEFT, so margin-left and margin-right both got reset to 0. The group rule `.featured-card a > * + *` had set the horizontal margin to 24px so every text child sits inside a clean 24px gutter from the card border. The shorthand on `<p>` overrode the group rule and the description text bled right up against the card's right edge. Switches to `margin-bottom: 12px` only. Horizontal margin stays owned by the group rule, matching the fix template-summary already got in #2600. Surfaced by Joey on `/skills/` featured strip — the exact symptom he's been calling out for the last few rounds. Same shape, different file. --------- Co-authored-by: Joey-nexu <joeylee12629@gmail.com> |
||
|
|
439f071cb0
|
feat(landing-page): replicate #2469 SEO content with deploy + regression fixes (#2605)
* chore(landing-page): bring PR #2469 content wholesale onto post-revert main Step 1 of replicating @pftom's #2469 work without the deploy-blocking issues that forced #2603. This commit copies the full \`apps/landing-page/\` diff from #2469's HEAD (`9d2a4f1`) onto current main verbatim — every i18n bundle, every page rewrite, every \`[locale]/\` wrapper. Subsequent commits on this branch then surgically restore the SEO fixes that #2469 silently regressed and configure the sitemap to survive the Cloudflare Pages 25 MiB limit, so deploy is healthy when this lands. What's in this commit - Tom's i18n bundle: \`i18n.ts\` (5377 lines), \`home-page-i18n.ts\`, \`info-page-i18n.ts\`, \`landing-ui-i18n.ts\`, \`content-i18n.ts\` (~10K lines total of locale data) - 18 landing-page locales: en, zh, zh-tw, ja, ko, de, fr, ru, es, pt-br, it, vi, pl, id, nl, ar, tr, uk - All existing pages rewritten to consume the new i18n bundle - Full \`[locale]/<route>/\` wrapper tree for every catalog page - \`plugin-registry.ts\` rewrite, \`catalog.ts\` adjustments - \`astro.config.ts\` route + sitemap reconfiguration - \`public/_headers\`, \`public/_redirects\`, \`public/favicon.svg\` adds - \`_components/locale-switcher-script.astro\` add What's intentionally NOT done in this commit (handled in follow-ups on this same branch): - Restore brand mark 44px + rounded corners (was lost from #2588) - Restore HA SoftwareApplication \`alternateName\` array (was lost from #2566) - Restore HA \`url\` canonical pointing at the landing page (was lost from #2586) - Restore Product/Library/Tutorials/Blog nav grouping (was lost from #2588) - Restore catalog-card padding 24px (was lost from #2600) - Configure sitemap to filter \`[locale]/\` routes so the generated XML stays under 25 MiB and Cloudflare Pages accepts the deploy - Add \`/zh-CN/* → /zh/*\` redirects for backwards-compatibility with any externally-linked OD-canonical locale URLs Validation so far - \`pnpm --filter @open-design/landing-page typecheck\` — 0 errors * fix(landing-page): unblock deploy + restore SEO regressions on top of #2469 Step 2 of replicating @pftom's #2469. The previous commit on this branch brings #2469's content wholesale; this commit applies the surgical fixes that make the result actually deploy and preserves the SEO improvements that #2469 silently regressed. Fix 1 — sitemap stays under Cloudflare Pages 25 MiB upload limit - `astro.config.ts` `filter` now drops every `/{locale}/...` route so the sitemap only emits canonical English URLs. - Locale variants are still discoverable via the `<xhtml:link rel="alternate" hreflang="...">` annotations the `namespaces.xhtml: true` option emits inside each canonical entry. This is Google's recommended pattern for a multi-language site. - Verified: post-fix `out/sitemap-0.xml` = 179 KB (was 38.4 MiB on the prior attempt that forced #2603's revert). Fix 2 — header brand block restored to the polished version - Logo `width/height` 36 → 44 (matches PR #2588's brand-mark refresh for visual weight against the new black speech-bubble glyph) - `.brand-meta` block ("Studio Nº 01 · Berlin / Open / Earth") removed from the header bar; the same editorial flourish still lives on the rotated `.side-rail .rail-text` pseudo-elements at page edges. Fix 3 — header nav grouped into Library + standalone Tutorials/Blog - Skills / Systems / Templates / Craft are now children of a Library dropdown (matches PR #2588's grouping). Each row keeps its count badge inline; the trigger highlights when any of the four facet pages is active. - Tutorials and Blog stay as standalone top-row items (PR #2588's original decision after Joey's review on the Learn dropdown). - Contact removed from the header — it was a same-page anchor that the footer already surfaces. - Hardcoded "Library" / "Tutorials" labels match the brand-name pattern: unlocalized across all 18 landing-page locales. Fix 4 — HA SoftwareApplication entity canonicalized on the LP again - `alternateName` is back to an explicit array of real query variants `["html anything", "html-anything", "htmlanything", "HTML Anything Editor", "The agentic HTML editor"]`. #2469 re-routed it through `copy.schemaAlternateName` which dropped the literal alias declarations Google needs for spaced-vs- hyphenated-vs-joined matching. (Restores PR #2566.) - `url` flips back from `HA_URL` (the GitHub repo) to the LP URL itself, matching the `BreadcrumbList` block on the same page. GitHub repo lives in `sameAs` as a peer surface. (Restores PR #2586. Without this, Google credits the GitHub repo as canonical for the entity, which is the opposite of what this surface exists for.) Fix 5 — catalog-card horizontal padding unified at 24 px - featured-card 22 → 24, template-card 20 → 24, system-card 18 → 24, source-card 28 → 24. - For template-card, also moved horizontal padding into the group rule exclusively so future siblings join without re-asserting margin shorthands. (Restores PR #2600.) Fix 6 — `_redirects` for the locale-code rename - This bundle uses `zh` / `zh-tw` / `pt-br` / `es` (the codes Tom's i18n.ts ships). The previous OD landing-page used `zh-CN` / `zh-TW` / `pt-BR` / `es-ES`. Externally-indexed and inbound-linked URLs against the old prefixes now 301 to the new canonical. Validation - `pnpm --filter @open-design/landing-page typecheck` — 0 errors - `pnpm --filter @open-design/landing-page build` — completed successfully; 18,204 pages built; sitemap-0.xml is 179 KB (well under the 25 MiB Cloudflare Pages limit). * docs: promote 'open-source alternative to Claude Design' to README H1 Brings the missing README and .gitignore changes from #2469 that the first wholesale-checkout in this branch missed (the auto-pulled diff scope was filtered to apps/landing-page/ initially). What - Every README.*.md (13 locale variants) now leads with the "open-source alternative to Claude Design" tagline as a subtitle to the project name in the H1 / first paragraph. This was @pftom's brand-positioning commit (`ee851dc`) on the original #2469 branch. - `.gitignore` adds `growth/**` to keep growth-research scratch out of the repo. Why - The README is one of the highest-PageRank surfaces a GitHub project exposes to Google. Promoting the "alternative to Claude Design" framing into the H1/subtitle position makes the project surface for exactly the query the SEO work in this PR is trying to capture. - Without this commit, the replicated #2469 in this branch would still rank against the previous H1 ("Open Design") on GitHub crawls, letting the SEO win at the LP fall short on the GitHub surface. This is a strict subset of #2469's content — pure docs, no code, no behavior change beyond what GitHub renders on the repo overview. --------- Co-authored-by: Joey-nexu <joeylee12629@gmail.com> |
||
|
|
eefaf4504a
|
Revert "Enhance landing page with SEO-focused content and FAQ section (#2469)" (#2603)
This reverts commit
|
||
|
|
26ee030b4c
|
Enhance landing page with SEO-focused content and FAQ section (#2469)
* Enhance landing page with SEO-focused content and FAQ section - Updated `.gitignore` to include growth directory. - Modified `astro.config.ts` to prioritize high-intent landing pages for SEO. - Added new FAQ styles and layout in `globals.css` for better user experience. - Implemented FAQ section in `page.tsx`, ensuring it aligns with structured data requirements. - Created dedicated pages for agents and alternatives to Claude Design, enhancing SEO and user navigation. - Introduced comparison page for evaluating Open Design against competitors. - Added favicon links component for consistent branding across all pages. * Add SVG favicon and update favicon links for improved branding * Enhance landing page with official source pillars for improved branding and navigation - Added five canonical "official source" pillars to the homepage, reinforcing key links: official site, GitHub repository, releases, documentation, and Discord community. - Updated URLs for releases, issues, documentation, and license to streamline access and improve user experience. * Add locale support and enhance landing page with language switcher - Introduced locale management with a new i18n module, defining multiple languages for the landing page. - Implemented a locale switcher in the topbar and header, allowing users to select their preferred language. - Updated global styles for the locale selector and adjusted layout for better responsiveness. - Enhanced SEO by ensuring localized content is served based on user selection. - Added a script for automatic locale detection and persistence in local storage. * Implement localized routing and enhance navigation with href utility - Added `stripLocaleFromPath` and `localizedHref` functions to manage locale-based URL paths. - Updated `localePath` to normalize paths based on the detected locale. - Refactored links in the header, footer, and main page components to utilize the new `localizedHref` function for improved navigation. - Introduced locale-aware routing for new pages, ensuring consistent user experience across different languages. - Enhanced SEO with alternate links for localized content in the sub-page layout. * Update header component to use new logo format - Replaced favicon image with a new logo in WebP format for improved performance and quality. - Ensured consistent branding across the landing page with the updated logo. * Enhance landing page with localization and new UI components - Introduced a comprehensive localization schema for content management, allowing for multilingual support across various sections. - Updated the blog and collection schemas to include internationalization (i18n) fields for better content localization. - Implemented a new official source strip for improved navigation and branding, linking to key resources like the official site and documentation. - Enhanced the locale switcher functionality, allowing users to select their preferred language with improved UX. - Updated styles for the locale switcher and added new components for better responsiveness and accessibility. - Refactored existing components to utilize localized URLs, ensuring a consistent user experience across different languages. * Implement comprehensive localization and enhance landing page UI - Introduced new localization features, including `EXTRA_LOCALIZED_HOME_BODY_COPY` and `EXTRA_LOCALIZED_LANDING_UI_COPY`, to support multilingual content across the landing page. - Updated `astro.config.ts` to integrate internationalization (i18n) settings for the sitemap, improving SEO for localized content. - Created new files for home page and info page internationalization, defining structured content for various languages. - Enhanced the locale switcher functionality with improved UX, allowing users to easily select their preferred language. - Refactored existing components to utilize localized content and URLs, ensuring a consistent experience across different languages. - Made CSS adjustments for better responsiveness and accessibility in the UI components. * Enhance landing page with new design templates and localization improvements - Added support for design templates in the content management system, allowing for better organization and access to design resources. - Implemented comprehensive localization for blog topics, enhancing multilingual support across various sections of the landing page. - Updated the header component to include new product menu items, improving navigation and user experience. - Refactored CSS for improved responsiveness and accessibility, including a new sticky chrome bar for better navigation. - Enhanced the locale switcher functionality, ensuring a seamless experience for users selecting their preferred language. * docs(readme): promote 'open-source alternative to Claude Design' tagline to subtitle across locales |
||
|
|
e5bea2c134
|
feat(landing-page): SEO surfaces, schema upgrades, manifest — cherry-pick from #2469 (#2596)
Some checks failed
visual-baseline / Capture visual baselines (push) Waiting to run
ci / Detect CI change scopes (push) Successful in 0s
landing-page-ci / Validate landing page (push) Failing after 1s
landing-page-deploy / Deploy landing page (push) Has been skipped
nix-check / build (push) Failing after 1s
ci / Validate Nix flake (push) Failing after 1s
ci / Preflight (push) Failing after 1s
ci / Core package tests (push) Failing after 1s
ci / Tools workspace tests (push) Failing after 1s
ci / Daemon workspace tests (1/2) (push) Failing after 1s
ci / Daemon workspace tests (2/2) (push) Failing after 1s
ci / Web workspace tests (push) Failing after 1s
ci / E2E vitest (push) Failing after 1s
ci / Playwright critical (starters) (push) Failing after 2s
ci / Playwright critical (core) (push) Failing after 1s
ci / Build workspaces (push) Failing after 2s
ci / App workspace tests (push) Failing after 0s
ci / Validate workspace (push) Failing after 0s
ci / Runtime trace (push) Has been skipped
Pulls the high-value, low-conflict slices of @pftom's #2469 onto current main. That PR's branch was based on `af63af3` (May 20) and diverged from a fast-moving main, so its Tier 1 SEO content is shipped here as a fresh PR rather than rebased. What's included - **5 new SEO landing pages**, English-only for v1: - `/official/` — brand-authority hub naming every alias the project is searched as (Open Design, OpenDesign, open-design, opendesign, Open Design AI, OD); Organization JSON-LD with the same alternateName. - `/quickstart/` — three-command install path with HowTo JSON-LD, requirements, expected output, troubleshooting, next steps. - `/agents/` — 17 BYOK adapters across three tiers, ItemList JSON-LD, BYOK explainer. - `/compare/` — evaluation-stage comparison hub vs Claude Design, Figma Make, v0, Lovable/Bolt, Open CoDesign; mandatory honest-limits block as FAQPage JSON-LD. - `/alternatives/claude-design/` — primary commercial-intent page; TL;DR, why-people-search, BYOK explainer, feature-comparison table, who-should-pick-which, migration steps, FAQPage JSON-LD. - **`[locale]/` wrappers** for each of the 5 new pages, generated via `PREFIXED_LOCALES` from OD's existing `_lib/i18n.ts`. Each wrapper is a thin re-export of the canonical English page; per-locale translations are a follow-up. - **`seo-head.astro` schema upgrades** — `inLanguage` on Article / WebSite / Blog JSON-LD, `availableLanguage` on the WebSite block, `og:locale:alternate` for non-current locales, hreflang `x-default` link. - **`favicon-links.astro` extracted as a shared component** so every page emits the same icon set; new sizes (favicon-16x16, favicon-32x32, android-chrome-192x192, android-chrome-512x512) generated from the current brand mark; `site.webmanifest` published for PWA / Android install affordances. - **`llms.txt`** restructured to lead with brand-alias declaration and list every new official entry point. The Sister Projects section introduced in #2566 stays. What's intentionally NOT included - @pftom's parallel i18n bundle (`i18n.ts` / `home-page-i18n.ts` / `info-page-i18n.ts` / `landing-ui-i18n.ts` / `content-i18n.ts`, ~10K lines of locale data). It duplicates and conflicts with OD's existing `_lib/i18n.ts` + `_lib/home-copy.ts`. Adapting the new pages to OD's existing system instead keeps the codebase on a single source of truth; per-locale translations of the new pages can land as a separate PR. - Mass-rewrites of existing pages (homepage, HA, Skills/Systems catalogs, blog detail). #2469's branch base predates a number of in-flight PRs that touched those files heavily; cherry-picking the rewrites would re-litigate already-merged work. - The `[locale]/<existing-route>/` routing tree for Skills, Systems, Templates, etc. Without translations, mirroring 26 routes per locale produces duplicate-content signals; that lift belongs with a per-locale copy push. Surface area - Marketing site only. No `apps/web`, no `apps/daemon`, no contracts, no CLI surfaces. No new dependencies. No env vars. - New favicon assets are static PNGs generated from the canonical brand-mark source via `magick`; committed as files, no pipeline. Validation - `pnpm --filter @open-design/landing-page typecheck` — 0 errors - Local dev server: every new English route returns 200; every `[locale]/<route>/` variant (e.g. `/zh-CN/agents/`) returns 200; `/favicon.ico` and `/site.webmanifest` resolve. - Local dev server: `seo-head.astro` emits the new `inLanguage`, `availableLanguage`, `og:locale:alternate`, and `hreflang=x-default` signals; `<FaviconLinks />` renders the consolidated icon set including the new manifest link. Followups - Translate the 5 new pages and replace the English-content wrappers under `[locale]/`. - Swap `sub-page-layout.astro` to use `<SeoHead />` so the new schema upgrades reach all sub-pages, not just the homepage and any future pages that opt into SeoHead directly. - Mass-update the homepage and HA page along the lines of #2469's rewrite, but rebased onto current main rather than off `af63af3`. Co-authored-by: Joey-nexu <joeylee12629@gmail.com> |
||
|
|
682a9c9a9a
|
feat(landing-page): group header nav into Product / Library / Learn (#2588)
* feat(landing-page): group header nav into Product / Library / Learn Reduces the top-level nav from 7 link slots (Product, Skills, Systems, Templates, Craft, Tutorials, Blog, Contact — plus the locale switcher and two CTAs) to 3 dropdown groups, eliminating the row-overflow that forced the brand sub-meta to truncate at narrow desktop widths and cramming the hamburger panel on tablet. Why - The four catalog pages — Skills, Systems, Templates, Craft — share the same shape: a list page that links into per-record details. They are facets of one library, not competing peer destinations. Letting each occupy its own top-row slot was a policy of last resort when there were only four; with Product newly added and Tutorials newly added since #2452 + #2266, the row no longer fits. - Tutorials and Blog are both editorial reading surfaces (videos vs articles), so they pair naturally under one Learn group. - Contact in the top nav was always misleading — it links to `#contact`, a same-page anchor on the homepage CTA section. The footer already lists it. Promoting it to top-row real estate while the row was overflowing made no sense. What - New `Library` dropdown holds Skills (132) / Systems (150) / Templates (111) / Craft (11). Each row inside the panel keeps its count badge inline beside the label, plus a one-line blurb so the panel reads as a directory rather than an undifferentiated list. The trigger highlights `is-active` whenever any of the four facet pages is the active route, so users still see "you are inside Library" feedback. - New `Learn` dropdown holds Tutorials and Blog. Same pattern: inline blurbs, parent-highlight when either child page is active. - `Contact` removed from the header. The footer surface still lists it; the top-row capacity it occupied is reclaimed. - `.dropdown-num` CSS class added for the inline count badge inside dropdown rows, distinct from the existing `.num` (which floats above top-row links as a superscript). Active-state semantics are preserved — every sub-page that already passes `active="skills"`, `active="systems"`, etc. keeps working without changes; the new Library trigger reads those same values to decide whether to highlight itself. Surface area - Marketing site only. No `apps/web`, no `apps/daemon`, no contracts, no CLI surfaces. No new dependencies. No i18n keys (existing `copy.navSkills` / `copy.navSystems` / `copy.navTemplates` / `copy.navCraft` / `copy.navBlog` keep working; "Library" and "Learn" are universal English labels that don't need translation tables for this iteration). Validation - `pnpm --filter @open-design/landing-page typecheck` — 0 errors - Local dev server at narrow breakpoints (≤1080px) — hamburger panel flat-expands the new Library / Learn dropdowns; existing handler closes the panel on link click. - Local dev server at desktop widths (≥1180px) — top row now reads Product · Library · Learn, matching the design intent. * feat(landing-page): polish brand mark — bigger glyph, single-line wordmark Three small refinements to the header brand block, requested after the new logo + grouped-nav landed: 1. The "Open Design" wordmark was wrapping to two lines once the new nav groups (Product · Library · Learn) sat alongside it on narrow desktop widths. The hamburger fallback already hides the entire nav at ≤1080px so the wordmark has plenty of horizontal room there — wrapping was a layout glitch, not a width constraint. Applies `white-space: nowrap` on `.brand` and adds an explicit `.brand-name` span so the rule is intent-bearing rather than relying on the parent inline-flex behaviour. 2. The brand mark felt undersized against the wordmark's optical weight, especially with the new black mark where the negative space inside the speech bubble visually shrinks the glyph. Bumps the displayed size from 36×36 to 44×44 (+22%) in both the header and the footer, so the proportion matches what users see in the logo.webp source. 3. Removes the `<span class="brand-meta">` block ("STUDIO Nº 01 · BERLIN / OPEN / EARTH"). It was decorative metadata that earned a third of the brand block's horizontal real estate without carrying any user-facing function. Drops the JSX, drops the styles, drops the now-stale `display:none` overrides at the 1180 and 880 breakpoints. The same rotated brand-string still lives on the side-rail pseudo-elements at the page edges (`.side-rail .rail-text`) — that surface is the canonical home for that editorial flourish, not the nav. Surface area - Marketing site only. `_components/header.tsx`, `page.tsx` (footer brand block), and `globals.css`. No `apps/web`, no `apps/daemon`, no contracts, no CLI. No new dependencies, no i18n changes. Validation - `pnpm --filter @open-design/landing-page typecheck` — 0 errors - Local dev server: header HTML no longer contains `brand-meta`; the brand block on `/` and any sub-page renders the bigger 44px glyph + single-line "Open Design" wordmark. * refactor(landing-page): unbundle Learn dropdown and round the brand mark Two small follow-ups after looking at the rendered nav: 1. Reverts the Learn dropdown back to two standalone top-row links (Tutorials, Blog). Rolling exactly two items into a dropdown adds a click without reclaiming any horizontal space — the Library grouping is what genuinely cleared the row, and Tutorials + Blog can live side by side. Active-state semantics are preserved automatically since both pages already pass `active="tutorials"` and `active="blog"`. 2. Adds `border-radius: 10px` to the brand-mark image. The source PNG is a solid-fill square; clipping the corners gives the mark the modern app-icon silhouette (~22% of side length, matching the iOS / macOS convention) so it reads as a brand glyph rather than a raw screenshot next to the wordmark. Applies to both the header and footer brand blocks via the shared `.brand-mark img` selector. Validation - `pnpm --filter @open-design/landing-page typecheck` — 0 errors - Local dev server: top row reads `Product ▾ · Library ▾ · Tutorials · Blog` - Local dev server: brand mark renders with rounded corners on `/` and any sub-page (header + footer reuse the same `.brand-mark` styling) --------- Co-authored-by: Joey-nexu <joeylee12629@gmail.com> |
||
|
|
0f1b74ee44
|
fix(landing-page): canonicalize HA SoftwareApplication on the landing page (#2586)
Follow-up to #2566. That PR introduced `url: HA_URL` plus a two-element `sameAs` on the SoftwareApplication block, which declared the GitHub repo as the entity's canonical surface — Google uses `url` to decide which page to attribute structured-data entities to. The BreadcrumbList block right below uses the landing page URL, so the two blocks contradicted each other on what the canonical page was. Since the goal of #2566 was making "html anything" surface the landing page in search, canonicalizing on GitHub was an inversion of intent. This commit switches `url` to the landing page URL and keeps `HA_URL` only in `sameAs` so the GitHub repo is registered as a peer reference surface, not the canonical one. The two structured-data blocks now agree. Surfaced by Looper (PerishCode) on the #2566 review thread; the fix landed too late to ride the original PR, hence this small follow-up. Co-authored-by: Joey-nexu <joeylee12629@gmail.com> |
||
|
|
c37c691d82
|
[codex] Add landing page Google Analytics (#2582)
* Add Claude-style design system workflow * Merge design system workflow into main * Restore design system workflow UI styles * Fix design system setup scrolling * Fix design system setup connector button * Preserve connector auth link after popup block * Simplify connected GitHub setup state * Open generated design system workspace project * Summarize design system auto prompt in chat * Add bounded GitHub connector design intake * Prefer path-scoped GitHub intake tools * Restore branch GitHub design context intake * Restore design system review workspace * Restore design system manager tab * Let design system workflow routes own details * Open editable design systems as projects * Restore design system workspace coverage * Fix bounded GitHub connector intake * Hide design system review while generating * Suppress design system generation questions * Constrain GitHub design intake to bounded command * Tolerate oversized GitHub metadata during intake * Rebuild daemon CLI when sources change * Fallback when GitHub connector snapshots are rate limited * Allow GitHub intake without Composio * Use native GitHub auth for design intake * Remove design system review group heading * Improve design system extraction evidence * Align design system scaffold with Claude output * Add evidence inventory for design system intake * Add local design system evidence intake * Add design system package audit gate * Allow auditing Claude Design reference packages * Audit design system package content quality * Migrate legacy design system artifacts * Clean migrated design system artifacts * Require modular design system UI kits * Reject thin design system UI kits * Prioritize core design evidence intake * Require role-based design system UI kits * Clean stale design system manifest references * Require representative preserved design assets * Warn on generic design system visuals * Enforce design system quality warnings * Audit connected design system UI kits * Require mounted design system UI kits * Require composed design system app shells * Require runnable JSX design system kits * Require browser globals for design system components * Infer design system names from source URLs * Require source examples in design system packages * Bind preserved fonts in design system tokens * Require skill frontmatter in design system packages * Preserve build icons in design system packages * Require real assets in brand previews * Require substantive source examples * Require product overview in design system README * Require reusable UI kit README * Require reusable design system skill docs * Seed Claude-style UI kit entry contract * Preserve runtime build assets in design packages * Audit design system packages after generation * Audit design system first-run output * Audit source-backed preview cards * Align design system UI kit scaffolds * Materialize design evidence package artifacts * Show project chat during design system setup * Hand off design system setup to project chat * Auto-repair design system audit failures * Harden design system evidence preservation * Tighten design system package guidance * Add targeted design system repair guidance * Bound design system audit auto repair * Use connector statuses in design system setup * Audit design system preview manifests * Require README preview manifests for design systems * Add landing page Google Analytics * Keep GA PR scoped to landing page * Cover new landing routes with Google Analytics * Load Google Analytics without static script src --------- Co-authored-by: qiongyu1999 <2694684348@qq.com> |
||
|
|
0a5598c0e3
|
feat(landing-page): improve HTML Anything discoverability for SEO (#2566)
Three small additions that close the gap between users searching "html anything" (with a space) and finding the existing `/html-anything/` page: 1. JSON-LD `SoftwareApplication.alternateName` is now an array of real query variants — `html anything`, `html-anything`, `htmlanything`, `HTML Anything Editor`, plus the existing tagline. Each entry maps to a query users actually type. Feeding them as declared alternates is a stronger signal than relying on Google's tokenizer to figure out hyphen ↔ space equivalence on its own. Also adds a `sameAs` array linking the canonical landing page and the GitHub repo so structured-data crawlers see the entity span both surfaces. 2. `llms.txt` now lists the HA landing page in its canonical entry points and adds a dedicated "Sister Projects" paragraph that spells out the name variants and the export targets. LLM crawlers (Claude, Perplexity, ChatGPT browsing) read this file to decide what to surface; the previous version made HA invisible to them. 3. The home page footer's Library column now lists "HTML Anything" as a peer to Skills / Systems / Templates / Craft, which puts a real body-anchor link to `/html-anything/` on `/`. Nav-dropdown anchors (the Product menu) carry less SEO weight than an anchor in a discoverable content area, so this makes the home actually pass link equity to the sub-page. The label is hardcoded English intentionally — "HTML Anything" stays unlocalized as a brand name on every locale, so threading a new key through all 18 home-copy translations would just churn translators. ## Why now The HA landing page has been live since #2452 but a "html anything" query (the variant most users type) doesn't surface us, while the hyphenated form does. Tokenizer equivalence isn't enough on a young domain — we need declared signals, an inbound link from a content-area on the highest-PR page, and machine-readable hints for LLM crawlers. This PR ships all three. ## Test plan - [x] `pnpm --filter @open-design/landing-page typecheck` — 0 errors - [ ] After deploy: `curl https://open-design.ai/html-anything/` shows `"alternateName":[...]` array in JSON-LD - [ ] After deploy: `curl https://open-design.ai/llms.txt` includes a `## Sister Projects` section - [ ] After deploy: home page footer Library column lists HTML Anything ## Surface area - [x] `apps/landing-page` only - [ ] no `apps/web`, no `apps/daemon`, no contracts, no CLI - [ ] no new dependencies - [ ] no skill / design-system / template / craft additions ## Followups (not in this PR) - Mirror the HA hero banner to R2 + Image Resizing (Core Web Vitals) - Generate a HA-specific Open Graph card so social previews stay on brand (currently reuses the GitHub-hosted banner) - Add a `SoftwareSourceCode` JSON-LD block to associate the landing page with the GitHub repository entity - Land a Chinese landing page at `/zh-CN/html-anything/` to capture CN-locale queries Co-authored-by: Joey-nexu <joeylee12629@gmail.com> |
||
|
|
af63af3951
|
feat(landing-page): refresh brand mark and publish a real favicon.ico (#2561)
Replaces the four icon assets in `apps/landing-page/public/` with
renders of the new brand mark — black-fill speech bubble + white
pointer arrow — and adds a real multi-resolution `favicon.ico` at
the path SEO crawlers actually probe.
Why
- The brand mark was refreshed on 2026-05-21 (canonical source:
black 2988×2988 PNG of the speech-bubble + pointer logo). The
marketing site needed the matching favicon, apple-touch-icon, and
header brand mark refreshed in lockstep so the browser tab, iOS
home-screen tile, and the in-page nav glyph all line up with the
new identity.
- `/favicon.ico` did not exist on the published site. The Astro head
declares `<link rel="icon" href="/favicon.png">`, which modern
browsers honor, but a long tail of SEO crawlers, link-preview
services (Slack, Discord, third-party SEO tools), and older
clients hard-probe `/favicon.ico` regardless of the link tag. Hits
to that URL were falling through to the SPA fallback HTML
(200 with `content-type: text/html`), so those clients rendered
an empty/broken favicon. Several SEO surfaces showed an empty
black circle instead of the brand mark.
- Adding a real `favicon.ico` plus an explicit
`<link rel="icon" type="image/x-icon" href="/favicon.ico">` is
the smallest defensive fix that covers both well-behaved and
hard-probing clients.
What
- Regenerated icon assets from the new logo source:
- `favicon.ico` — multi-resolution ICO with 16/32/48/64 PNG-encoded
entries. The 16/32 entries are what browser tabs, bookmarks, and
most crawlers sample; 48/64 cover high-DPI tabs and Windows
pinned-tile sampling.
- `favicon.png` — 32×32 PNG (existing slot).
- `apple-touch-icon.png` — 180×180 PNG (existing slot, iOS
home-screen).
- `logo.webp` — 144×144 WebP, 4× the 36px logical size used by
the header brand mark for crisp retina rendering.
- Added `<link rel="icon" type="image/x-icon" href="/favicon.ico" sizes="any">`
to both `app/pages/index.astro` and the shared `sub-page-layout`
so every route under `open-design.ai` advertises the ICO. Existing
PNG and apple-touch links are preserved — modern browsers will
still pick the PNG, the ICO catches the hard-probing tail.
Surface area
- Marketing site only. No `apps/web`, `apps/daemon`, contracts, or
CLI surfaces touched.
- No new dependencies; assets generated locally from the canonical
source via `magick` + `cwebp` and committed as static files.
Validation
- `pnpm --filter @open-design/landing-page typecheck` — 0 errors.
- File integrity:
- `favicon.ico` — `MS Windows icon resource - 4 icons,
16x16, 32x32, 48x48, 64x64`
- `logo.webp` — `RIFF Web/P image, VP8 encoding, 144x144`
- Manual: `/favicon.ico` will return `image/x-icon` once deployed,
not the SPA fallback HTML it returns today.
Followup
- Once Cloudflare's edge cache rolls (or is purged), third-party
favicon caches (Google SERP, Slack link-preview) take days-to-weeks
to refresh on their own; that lag is expected and not a deploy
problem.
Co-authored-by: Joey-nexu <joeylee12629@gmail.com>
|
||
|
|
86dafa9be8
|
feat(landing): add 19-locale URL routing with full home translations (#2408)
* feat(landing): add 19-locale URL routing with full home translations
Adds locale-prefixed routes (/zh-CN/, /ja/, /de/, …) for 18 non-default
locales while keeping English as the only unprefixed canonical. Generates
proper hreflang + og:locale, points hreflang="en" / x-default at the
unprefixed canonical, and serves localized RSS and plugin search JSON
under each prefix.
Adds a visible language switcher (globe pill + native names) on every
page, replacing the small topbar dropdown. Native-name menu, current
locale marked aria-current, closes on outside click / Escape, only one
open at a time.
Adds app/_lib/home-copy.ts as the source of truth for marketing copy
on the landing page, with full translations for zh-CN, zh-TW, ja, ko,
de, fr, es-ES, pt-BR. Remaining locales (it, pl, hu, ru, uk, tr, ar, fa,
th, id) fall back to English for marketing copy while still getting
fully localized chrome.
Extracts the IntersectionObserver reveal + GitHub stats + wire ticker
script into _components/home-enhancer.astro so localized homepages
animate in the same way as the canonical home.
- New: app/_lib/i18n.ts, app/_lib/home-copy.ts
- New: app/_components/home-enhancer.astro, locale-switcher-enhancer.astro
- New: app/pages/[locale]/{index,[...path]}.astro, blog/rss.xml.ts,
plugins/search.json.ts
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(landing): bound localized routing to listing pages only
Detail pages (skills/<slug>, blog/<id>, systems/<slug>, templates/<slug>,
craft/<slug>, plugins/<slug>) no longer fan out across 18 prefixed
locales — they stay at their canonical English URLs. Localized chrome
on listing pages links straight to those English detail URLs.
Generated page count drops from ~6,000+ to ~1,800 and the landing-page
CI build returns to ~50s. Localized homes, listings, and filter index
pages (skills/mode/*, skills/scenario/*, systems/category/*) are all
still produced per locale.
Co-authored-by: Cursor <cursoragent@cursor.com>
---------
Co-authored-by: ashley li <ashleyli@ashleydeMacBook-Air-2.local>
Co-authored-by: Cursor <cursoragent@cursor.com>
|
||
|
|
b1aa62e63c
|
fix(landing-page): restore 'tutorials' in Header active union (#2458) | ||
|
|
69469c639e
|
feat(landing-page): add HTML Anything page and responsive header (#2452) | ||
|
|
e4d6d4e805
|
fix(landing): keep template preview lookup stable in CI (#2412)
Resolve generated preview thumbnails from both workspace-root and package-root build layouts so Astro production builds can see the images created by the landing preview step. A later merge restored the source-relative lookup, which made /templates render 111 cards with placeholder thumbs despite the PNG files being deployed. Co-authored-by: ashley li <ashleyli@ashleydeMacBook-Air-2.local> Co-authored-by: Cursor <cursoragent@cursor.com> |
||
|
|
93dd7066fd
|
feat(landing): refresh templates and add tutorials channel (#2409)
* feat(landing): rebuild /templates/ catalog from design-templates Source the public templates page from `design-templates/*/SKILL.md` so the catalog reflects the renderable template registry (111 entries with mode, platform, and scenario metadata) instead of the previous handful of live-artifact + skill-mode shims. Render per-template thumbnails by shooting each design template's `example.html`, surface mode/platform/ scenario chips on cards and detail pages, and bump the preview navigation timeout so heavier examples no longer flake under concurrency. Co-authored-by: Cursor <cursoragent@cursor.com> * feat(landing-page): add /tutorials/ channel for YouTube content Adds an editorial tutorials channel to open-design.ai. Each video maps to a Markdown entry in `app/content/tutorials/` and renders inline via `youtube-nocookie.com` so the page does not require a cookie consent banner. The list page mirrors the magazine-style /blog/ layout and shares the same category-filter UX. Seeds the channel with 12 community-produced videos (English + Chinese) sourced from YouTube as of 2026-05-19, spanning getting-started, tutorial, demo, and review categories. Header now exposes a "Tutorials" nav item between Craft and Blog. * fix(landing-page): switch tutorial player to click-through to YouTube The original inline `youtube-nocookie.com/embed/<id>` iframe failed to play in practice. YouTube's embedder identity check rejected the request with `PLAYABILITY_ERROR_CODE_EMBEDDER_IDENTITY_MISSING_REFERRER` and the in-frame "Sign in to confirm you're not a bot" challenge. Removing the explicit `referrerpolicy` attribute did not resolve it, and the alternative — providing a Referrer-Policy that always sends a full referrer to a third party — is a regression we don't want to ship. Replace the iframe with a click-through facade: high-resolution thumbnail + YouTube-style red play button + "Watch on YouTube ↗" pill, wrapped in an anchor that opens the canonical `youtube.com/watch?v=<id>` page in a new tab. The detail page still renders all of the editorial content — title, summary, author, date, duration, chapter notes — so it remains a useful in-site landing for SEO and social shares. Only the playback itself moves to YouTube proper. * fix(landing-page): correct chase-ai tutorial chapter map The seeded chapter map for the Chase AI tutorial mirrored the typo in the original YouTube description: it listed `14:06 — Dashboard` (past the 13:47 runtime) and put `12:40 — Final verdict` after it, breaking strictly-ascending order. YouTube's auto-detected chapter list for this video (`yt-dlp --dump-json` `chapters` field) shows only four sections — Open Design, Install + Demo, Design Systems, Final Verdict — with no Dashboard segment, so drop that line and keep the remaining four chapters in order. --------- Co-authored-by: ashley li <ashleyli@ashleydeMacBook-Air-2.local> Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: Joey-nexu <joeylee12629@gmail.com> |
||
|
|
96b6db14c2
|
feat(landing): rebuild /templates/ catalog from design-templates (#2369)
Some checks failed
ci / Detect CI change scopes (push) Successful in 1s
landing-page-ci / Validate landing page (push) Failing after 2s
landing-page-deploy / Deploy landing page (push) Has been skipped
nix-check / build (push) Failing after 2s
ci / Preflight (push) Failing after 1s
ci / Core package tests (push) Failing after 1s
ci / Tools workspace tests (push) Failing after 1s
ci / Daemon workspace tests (1/2) (push) Failing after 1s
ci / Daemon workspace tests (2/2) (push) Failing after 1s
ci / Web workspace tests (push) Failing after 1s
ci / E2E vitest (push) Failing after 1s
ci / Playwright critical (starters) (push) Failing after 1s
ci / Playwright critical (core) (push) Failing after 1s
ci / Build workspaces (push) Failing after 1s
ci / App workspace tests (push) Failing after 0s
ci / Validate workspace (push) Failing after 0s
ci / Runtime trace (push) Has been skipped
Source the public templates page from `design-templates/*/SKILL.md` so the catalog reflects the renderable template registry (111 entries with mode, platform, and scenario metadata) instead of the previous handful of live-artifact + skill-mode shims. Render per-template thumbnails by shooting each design template's `example.html`, surface mode/platform/ scenario chips on cards and detail pages, and bump the preview navigation timeout so heavier examples no longer flake under concurrency. Co-authored-by: ashley li <ashleyli@ashleydeMacBook-Air-2.local> Co-authored-by: Cursor <cursoragent@cursor.com> |
||
|
|
c85da3eb40
|
fix: sync landing source-of-truth paths (#2066) | ||
|
|
a0b2e17c15
|
Fix blog code blocks and add missing cover images (#2255)
* fix(landing-page): paper theme for blog code blocks Configure Shiki with a custom open-design-editorial theme so fenced code blocks render on the cream --bone paper with --ink text, matching the rest of the editorial layout. Astro's default github-dark theme was inlining background-color:#24292e on every <pre>, producing a slate- dark slab inside the warm paper page (visible on /blog/byok-design-workflow-claude-codex-qwen/ and any other post that ships a fenced block). Token colors are pulled from the same palette as globals.css (--coral, --olive, --ink-*) so syntax accents read as part of the brand instead of as a foreign widget. Belt-and-braces CSS in blog/[slug].astro pins the <pre.astro-code> background to --bone so a future Shiki upgrade cannot silently re-introduce the dark slab. Co-authored-by: Cursor <cursoragent@cursor.com> * feat(landing-page): add missing blog cover plates Add generated editorial plate images for the layout-layer and Figma-plugin blog posts, then wire them into the blog index image map so those cards use real covers instead of fallback placeholders. Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: ashley li <ashleyli@ashleydeMacBook-Air-2.local> Co-authored-by: Cursor <cursoragent@cursor.com> |
||
|
|
7fc4362ba8
|
perf(landing): edge-cache HTML and precise-load thumbnails (#2235)
* perf(landing): edge-cache HTML and precise-load thumbnails Without `public/_headers` Cloudflare Pages serves every HTML with `cf-cache-status: DYNAMIC` so each request roundtrips to the Pages origin — observed TTFB 660–900ms from Seattle, worse from Asia. With `s-maxage=3600, stale-while-revalidate=86400` HTML stays cached at the edge between deploys (CF Pages auto-purges on every deploy so freshness is unchanged in practice), and `_astro/` hash bundles flip to `immutable` so the existing 4h+must-revalidate roundtrips go away. For thumbnails, native `loading="lazy"` is browser-decided — Chrome over-prefetches (1250–3000px), Safari fires near in-viewport. A new `<LazyImg>` Astro component and global IntersectionObserver (rootMargin 300px for images, 600px for videos) replaces all 10 site-wide `loading="lazy"` usages with precise control. Above-the-fold slots (first 4 rows, detail-page hero previews) opt into `eager` or `priority` to skip the IO roundtrip. Homepage hero LCP gets `<link rel="preload" imagesrcset>`, a 4-step `srcset` (768/1280/1920/2560) plus `fetchpriority="high"` so retina devices stop repainting from the 1024-only variant — was the P99 long tail. Verified: `pnpm guard` 6/6, `pnpm typecheck` 0 errors, `pnpm build` 865 pages 28s, generated `out/index.html` contains the preload link and 15 `data-precise-src` thumbnails, `out/plugins/index.html` has 95 precise-loaded thumbnails plus the IO script. * perf(landing): logo to webp + parallelize Google Fonts load Two HAR-validated wins on top of the edge-cache / precise-load commit: logo: 500x500 192KB PNG → 200x200 7.5KB WebP. Footer/header actually render at 36x36, so the source is 5x larger than necessary at the display size and ships RGBA PNG bytes for what reads as a flat graphic. WebP at q=85 keeps the gradient ring crisp at every DPR we care about. fonts: globals.css used `@import url(...)` for Google Fonts, which serialized HTML → CSS → fonts.googleapis.com/css2 → fonts.gstatic.com/ woff2. HAR measured 953ms for the fonts CSS plus 400–800ms per woff2 × 4 — close to 3s before text could render in the intended family, even with display=swap. Moving to `<link>` + `<link rel=preconnect>` in each page's <head> lets the fonts CSS fetch race the HTML body parse, and warms the TLS handshake to gstatic.com so woff2 requests don't pay DNS+TLS at request time. A shared `font-stylesheet.astro` keeps the four-family URL canonical across all five entry points (index, sub-page-layout, plugins/index, plugins/[slug], blog/index, blog/[slug]). og.astro already had this treatment. |
||
|
|
07659b7272
|
feat(seo): add Search Console reporting workflows (#2229)
* feat(blog): daily 3-day Search Console traffic digest Adds `blog-3day-report.yml` (cron 09:00 Asia/Shanghai) and a companion `report-3day.ts` script that refreshes `docs/blog-traffic-digest.md` once per day. The digest has two sections: - T-3 spotlight: posts published exactly three days ago, with their 3-day Search Analytics window plus current URL Inspection coverage state. - Rolling 30-day cohort: every post 1–30 days old with its latest 3-day Search Analytics window, sorted by impressions descending. The workflow is read-only against Google APIs (no Indexing API, no "request indexing" automation) and mirrors the secret / config plumbing already used by `blog-indexing-monitor.yml`. Output lands in a reviewable `automation/blog-traffic-digest` PR opened by the open-design bot. Also widens `querySearchAnalytics` to accept `windowDays: 3 | 7 | 28` and updates `docs/blog-indexing-automation.md` with the new pipeline. Co-authored-by: Cursor <cursoragent@cursor.com> * feat(seo): post daily Search Console report to Feishu Co-authored-by: Cursor <cursoragent@cursor.com> * feat(blog): push traffic digest to Feishu Emit a compact JSON summary from the daily 3-day traffic digest and add a Feishu custom bot sender for the summary card. Wire the workflow to send the card when `FEISHU_BLOG_DIGEST_WEBHOOK` is configured while keeping Markdown PR output as the source of truth. Co-authored-by: Cursor <cursoragent@cursor.com> * feat(landing-page): add Discord routing CTAs Add a lightweight Discord pill to the landing hero and Discord links in the landing and blog footers so community routing is visible without displacing the primary GitHub and download CTAs. Add a blog-ending conversion card that points guide and use-case readers to the internal workflows library, while keeping Discord as a secondary support path. Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: ashley li <ashleyli@ashleydeMacBook-Air-2.local> Co-authored-by: Cursor <cursoragent@cursor.com> |
||
|
|
5d28f1c19d
|
Add community and use case blog posts (#2103)
* Add community and use case blog posts Co-authored-by: Cursor <cursoragent@cursor.com> * Fix plugin workflow example path Co-authored-by: Cursor <cursoragent@cursor.com> * Fix plugin workflow commands and CTA Co-authored-by: Cursor <cursoragent@cursor.com> * Reduce blog topics diff noise Co-authored-by: Cursor <cursoragent@cursor.com> * Align plugin publishing docs with CLI Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: ashley li <ashleyli@ashleydeMacBook-Air-2.local> Co-authored-by: Cursor <cursoragent@cursor.com> |
||
|
|
2e1dd497aa
|
fix(landing): expose blog RSS feed alias (#1859) | ||
|
|
22a3b99a47 |
Merge origin/main into preview/v0.8.0
Sync 49 commits from main. Conflicts resolved:
- .github/workflows/ci.yml: kept v0.8.0 granular per-area gating, added main's
linux specs + release-stable.yml + release-preview.yml triggers
- .github/workflows/release-preview.yml: kept v0.8.0's full workflow over main's placeholder
- apps/web/src/components/AssistantMessage.tsx: combined v0.8.0 file-ops
summary with main's stripTodoToolGroups + suppressAskUserQuestionFallbackText
- apps/web/src/components/ChatPane.tsx: kept both new imports
- apps/web/src/index.css: kept both .msg-plugin-chip and .user-copy-btn blocks
- e2e/ui/*.test.ts: kept v0.8.0 openEntrySettingsDialog helper over main's
inline dialog navigation (UI was redesigned in v0.8.0)
- nix/package-{daemon,web}.nix: kept v0.8.0 pnpmDepsHash; rerun nix build to refresh
|
||
|
|
e3c7c3c611
|
fix(landing): unify blog chrome and star counts (#1811)
* fix(landing): unify blog chrome and stars Ensure blog and catalog pages share the same header/footer behavior, with safe GitHub star fallbacks and RSS discovery for the refreshed blog. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(nix): update pnpm dependency hash Keep Nix fixed-output dependency hashes aligned with the landing page lockfile changes. Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: ashley li <ashleyli@ashleydeMacBook-Air-2.local> Co-authored-by: Cursor <cursoragent@cursor.com> |
||
|
|
0a5403883c
|
Refresh landing page blog (#1711)
* feat(landing): editorial blog UI + Blog nav entry
Adds the magazine-style /blog/ index and post detail pages backed by an
Astro content collection of long-form posts (Product, Guides, Use cases,
Community), and threads a Blog entry through the shared site Header so
blog readers see the same Skills/Systems/Templates/Craft nav as the
home page.
What's in:
- header.tsx: add Blog item to nav-links + 'blog' active highlight key
- pages/blog/index.astro: editorial list with featured card, category
filter chips, and shared <Header counts={...} active="blog" />
- pages/blog/[slug].astro: long-form post template with SeoHead article
JSON-LD, post-topline kicker (← Back to Blog + category · date),
and 'View source on GitHub ↗' footer link
- _components/seo-head.astro: shared SeoHead helper used by every page
for canonical, OpenGraph, Twitter, and Article JSON-LD
- image-assets.ts: export ogDefaultImage for the SeoHead default card
- content.config.ts: tighten blog schema to enum category +
numeric readingTime, exclude underscore-prefixed files (_topics.md
is the blog-factory topic backlog, not a public post)
- content/blog/*.md: 5 launch posts (4 published + 1 internal _topics
backlog)
What's out:
- _components/blog-layout.astro: the placeholder layout with its own
mini-header was only used by the placeholder posts being removed;
drop it instead of leaving dead code
- 4 placeholder posts under content/blog/*.mdx that documented blog
scaffolding (test-post, atelier-zero-for-articles,
blog-routes-and-post-template, shipping-the-latest-note)
* feat(landing): refresh blog editorial layout
Co-authored-by: Cursor <cursoragent@cursor.com>
---------
Co-authored-by: ashley li <ashleyli@ashleydeMacBook-Air-2.local>
Co-authored-by: Cursor <cursoragent@cursor.com>
|
||
|
|
76defffb93
|
Garnet hemisphere (#1702)
* feat(chat-composer): enhance mention handling and input overlay - Introduced a new overlay for inline mentions in the chat composer, improving user experience by visually indicating mentions as users type. - Updated the `ChatComposer` component to manage mention entities and integrate them into the input field, allowing for better context and interaction. - Enhanced the `AssistantMessage` component to support the display of plugin action panels based on the current project context, facilitating easier plugin management. - Refactored related components to ensure consistent handling of project files and mentions across the application. This update significantly improves the chat interaction model, making it more intuitive for users to engage with mentions and plugins. * feat(plugin-management): enhance plugin action panels and UI components - Updated the `AssistantMessage` component to include plugin action panels based on the latest project context, improving user interaction with generated plugins. - Refactored the `PluginsView` to support detailed views for available marketplace entries, allowing users to access more information and actions for each plugin. - Introduced new CSS styles for improved visual representation of plugin-related UI elements, enhancing overall user experience. - Enhanced the `listPlugins` function to include an option for fetching hidden plugins, providing more flexibility in plugin management. This update significantly improves the usability and functionality of the plugin management system, making it easier for users to interact with and manage their plugins. * fix(assistant-message): refine plugin folder candidate selection logic - Updated the `pluginFoldersTouchedThisTurn` function to improve the logic for selecting plugin folder candidates based on touched paths and message content. - Introduced a new helper function, `pathMatchesFolderFileBasename`, to enhance the matching criteria for folder candidates. - Added a check for explicit folder matches before falling back to a single candidate, improving accuracy in folder selection. - Modified the `shouldRenderSlotAsText` function in `HomeHero` to include the name parameter, refining the rendering logic for slot text. These changes enhance the functionality and reliability of the assistant message component in managing plugin folder candidates. * feat(plugin-folder-actions): implement agent-routed CLI actions for plugin management - Introduced a new `PluginFolderAgentAction` type to streamline actions related to plugin folders, including install, publish, and contribute. - Updated the `DesignFilesPanel`, `FileWorkspace`, and `AssistantMessage` components to utilize the new agent action handling, improving user interaction with generated plugins. - Refactored the action handling logic to send commands to the agent, enhancing the workflow for managing plugin folders. - Added corresponding tests to ensure the new functionality works as expected and integrates seamlessly with existing components. This update significantly enhances the plugin management experience by routing actions through the agent, allowing for a more cohesive and interactive user experience. * Fix PR 1702 CI blockers * Fix PR 1702 remaining CI checks * Prebuild AGUI adapter after install * Restore plugin project snapshot wiring * feat(marketplace): refactor marketplace URL handling and enhance fetching logic - Introduced new functions to normalize marketplace URLs and manage fetching of marketplace manifests, improving the reliability of marketplace integrations. - Updated the server and plugin logic to utilize the new fetching mechanisms, ensuring consistent handling of marketplace data. - Enhanced tests to cover new URL normalization and fetching scenarios, ensuring robustness in marketplace management. This update significantly improves the marketplace experience by streamlining URL handling and enhancing data fetching capabilities. * Fix project auto-send cleanup spec |
||
|
|
b268bbe169 |
Merge origin/garnet-hemisphere (post-9e196d34) — Use Plugin handoff fix
Brings in 11 new garnet commits, most importantly: - |
||
|
|
676a52ecf7 |
feat(plugins): enhance plugin detail and registry display features
- Updated the plugin detail page to include a dynamic preview section, showcasing interactive previews or posters based on plugin availability. - Improved the registry overview with enhanced statistics and a showcase of featured plugins, providing a more engaging browsing experience. - Added support for displaying example queries in plugin details, allowing users to see practical use cases for each plugin. - Refined the plugin metadata structure to include new fields such as `exampleQuery`, improving the overall information richness and usability of the plugin ecosystem. This update significantly enhances user interaction with the plugin registry, making it easier to discover and evaluate plugins. |
||
|
|
79982691f1 |
feat(plugins): add static paths for HTML plugin previews
- Introduced a new Astro page to generate static paths for plugins with HTML previews, enhancing the preview functionality. - Implemented logic to filter and map plugins based on their preview type and availability, ensuring only valid entries are processed. - This update improves the user experience by providing dynamic previews for supported plugins, facilitating better interaction with the plugin ecosystem. |