open-design/apps/landing-page/app/pages/skills/scenario/[scenario].astro
Jane 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>
2026-05-22 00:59:11 +08:00

77 lines
2.5 KiB
Text

---
/*
* /skills/scenario/<slug>/ — every skill targeting a given use-case
* scenario (marketing, engineering, design, research, ...).
*
* Mirrors the mode page but facets on `od.scenario`. One page per
* distinct scenario value found across all SKILL.md files.
*/
import Layout from '../../../_components/sub-page-layout.astro';
import SkillRow from '../../../_components/skill-row.astro';
import {
getSkillScenarioIndex,
getSkillsForScenario,
type TagDescriptor,
} from '../../../_lib/catalog';
import { getLandingUiCopy, localeFromPath, localizedHref } from '../../../i18n';
export async function getStaticPaths() {
const tags = await getSkillScenarioIndex();
return tags.map((tag) => ({
params: { scenario: tag.slug },
props: { tag },
}));
}
interface Props {
tag: TagDescriptor;
}
const { tag } = Astro.props as Props;
const locale = localeFromPath(Astro.url.pathname);
const ui = getLandingUiCopy(locale);
const href = (path: string) => localizedHref(path, locale);
const { records, label } = await getSkillsForScenario(tag.slug, locale);
const heading = label ?? tag.label;
const title = ui.catalog.skills.filterTitle(heading, records.length);
const description = ui.catalog.skills.scenarioDescription(heading, records.length);
const url = new URL(`/skills/scenario/${tag.slug}/`, Astro.site).toString();
const jsonLd = {
'@context': 'https://schema.org',
'@type': 'CollectionPage',
name: title,
description,
url,
numberOfItems: records.length,
};
---
<Layout title={title} description={description} active="skills" jsonLd={jsonLd}>
<header class="catalog-head">
<nav class="breadcrumb" aria-label={ui.catalog.breadcrumbLabel}>
<a href={href('/skills/')}>{ui.catalog.skills.detailLabel}</a>
<span aria-hidden="true">/</span>
<span>{ui.catalog.skills.scenario}</span>
<span aria-hidden="true">/</span>
<span class="crumb-active">{heading}</span>
</nav>
<span class="label">{ui.catalog.skills.label}</span>
<h1 class="display">
{ui.catalog.skills.scenarioHeading(heading, records.length)}
</h1>
<p class="lead">
{ui.catalog.skills.scenarioLead(label ?? tag.label)}
</p>
<p class="filter-clear">
<a href={href('/skills/')}>{ui.catalog.skills.allSkills()}</a>
</p>
</header>
<section class="catalog-grid catalog-grid-skills" aria-label={ui.catalog.skills.allAria}>
<ol>
{records.map((s, idx) => <SkillRow skill={s} index={idx} />)}
</ol>
</section>
</Layout>