stack-site-builder 1.15.0 → 1.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +65 -0
- package/README.md +45 -4
- package/index.d.ts +1 -0
- package/index.mjs +10 -3
- package/package.json +1 -1
- package/src/components/CardsHome.astro +113 -0
- package/src/components/ProductLanding.astro +445 -0
- package/src/components/ProductsIndex.astro +69 -0
- package/src/content.ts +150 -1
- package/src/i18n/ui.ts +8 -0
- package/src/layouts/BaseLayout.astro +66 -13
- package/src/lib/home.ts +63 -0
- package/src/lib/products.ts +31 -0
- package/src/lib/sections.ts +2 -0
- package/src/pages/[...lang]/index.astro +3 -1
- package/src/pages/[...lang]/products/[...id].astro +67 -0
- package/src/pages/[...lang]/products/index.astro +18 -0
package/CHANGELOG.md
CHANGED
|
@@ -11,6 +11,69 @@ content schema, while a consuming site supplies only content, taxonomy data and
|
|
|
11
11
|
config. Sites track the theme with `pnpm up stack-site-builder`, so each release
|
|
12
12
|
here is a plain version bump they pull in.
|
|
13
13
|
|
|
14
|
+
## [1.17.0] - 2026-07-22
|
|
15
|
+
|
|
16
|
+
### Changed
|
|
17
|
+
|
|
18
|
+
- **`apps` → `products` (breaking for 1.16.0 `apps` users)** — the day-old
|
|
19
|
+
apps section is renamed and generalized before real adoption: "apps" was
|
|
20
|
+
too narrow an umbrella for what a studio offers, so the section is now
|
|
21
|
+
**products** with a site-side mini-taxonomy (apps, services, education, …).
|
|
22
|
+
Content moves to `src/content/products/`, routes to `/products/…`, the
|
|
23
|
+
section key becomes `products` and — because the index needs
|
|
24
|
+
`src/data/product-categories.ts` (exporting `productTree` /
|
|
25
|
+
`productCatOf`) — it is now opt-IN (`sections: { products: true }`).
|
|
26
|
+
Schemas gain an optional `category` (validated via the optional
|
|
27
|
+
`productCategoryMap` argument to `defineAasCollections`); the landing/page
|
|
28
|
+
templates, nested subpages and automatic header link carry over unchanged
|
|
29
|
+
as `ProductLanding` / `ProductsIndex`.
|
|
30
|
+
|
|
31
|
+
### Added
|
|
32
|
+
|
|
33
|
+
- **Products index + header link** — `/products/` lists the top-level
|
|
34
|
+
products (icon, name, subtitle, description) grouped by the site's
|
|
35
|
+
category tree, and the header gains a Products link automatically once a
|
|
36
|
+
locale has at least one product. Per-product header links (`nav: true`)
|
|
37
|
+
still work for sites that prefer direct items.
|
|
38
|
+
- **Configurable browser icons** — `site.icons = { favicon, appleTouch,
|
|
39
|
+
manifest }` in site.ts; `favicon` may be SVG, PNG or ICO (type inferred
|
|
40
|
+
from the extension) so a site can keep its existing logo as the tab icon.
|
|
41
|
+
Defaults to the theme's `/favicon.svg`.
|
|
42
|
+
- **Hideable GitHub link** — `repoNav: false` in site.ts removes the
|
|
43
|
+
header's GitHub item (for private-repo sites); it also disappears when no
|
|
44
|
+
`repoUrl` is declared.
|
|
45
|
+
|
|
46
|
+
### Fixed
|
|
47
|
+
|
|
48
|
+
- **Cards home links lost the locale** — card and CTA hrefs on the cards
|
|
49
|
+
home are written locale-less ("/apps/foo/") and now get the current
|
|
50
|
+
locale's prefix at render, so the `/en/` home links to `/en/…` pages
|
|
51
|
+
instead of the default locale's.
|
|
52
|
+
|
|
53
|
+
## [1.16.0] - 2026-07-22
|
|
54
|
+
|
|
55
|
+
### Added
|
|
56
|
+
|
|
57
|
+
- **`apps` collection — product landings** — Things-style marketing pages
|
|
58
|
+
driven entirely by frontmatter (`template: 'landing'`): hero with app icon,
|
|
59
|
+
App Store / Google Play buttons (`"#"` renders disabled with a
|
|
60
|
+
"coming soon" label) and a Product Hunt badge; alternating feature rows
|
|
61
|
+
with optional device-frame screenshots and auto-rotating carousels; a
|
|
62
|
+
video "themes" showcase with tab switching; a highlights grid; pricing
|
|
63
|
+
tiers with a featured ribbon; a closing CTA; and legal links. Entries may
|
|
64
|
+
nest — `apps/<lang>/<slug>/privacy.mdx` renders at
|
|
65
|
+
`/apps/<slug>/privacy/` as a plain prose page (`template: 'page'`) — via
|
|
66
|
+
one catch-all route. Landing media (icons, screenshots, videos, frame art)
|
|
67
|
+
are `public/` paths. `nav: true` puts a landing in the header nav. The
|
|
68
|
+
section is on by default; an empty collection builds zero pages.
|
|
69
|
+
- **Data-driven "cards" home** — a site that isn't a catalog can declare
|
|
70
|
+
`home: { template: 'cards', hero, cards, cta }` in `src/data/site.ts` and
|
|
71
|
+
get a hero + wide-card grid + CTA homepage instead of the stack catalog.
|
|
72
|
+
Localized strings use per-locale records (`{ ko: '…', en: '…' }`) with
|
|
73
|
+
default-locale fallback, like category labels. On a cards home the
|
|
74
|
+
header's catalog-anchored Browse link hides itself; the catalog routes
|
|
75
|
+
still build (empty without stacks).
|
|
76
|
+
|
|
14
77
|
## [1.15.0] - 2026-07-22
|
|
15
78
|
|
|
16
79
|
### Added
|
|
@@ -162,6 +225,8 @@ catalog sites from a thin content-only repository.
|
|
|
162
225
|
- **Standalone development setup** — a devcontainer and a minimal `playground/`
|
|
163
226
|
consuming site for developing and previewing the theme on its own.
|
|
164
227
|
|
|
228
|
+
[1.17.0]: https://github.com/CodeCompose7/stack-site-builder/compare/v1.16.0...v1.17.0
|
|
229
|
+
[1.16.0]: https://github.com/CodeCompose7/stack-site-builder/compare/v1.15.0...v1.16.0
|
|
165
230
|
[1.15.0]: https://github.com/CodeCompose7/stack-site-builder/compare/v1.14.0...v1.15.0
|
|
166
231
|
[1.14.0]: https://github.com/CodeCompose7/stack-site-builder/compare/v1.13.0...v1.14.0
|
|
167
232
|
[1.13.0]: https://github.com/CodeCompose7/stack-site-builder/compare/v1.12.0...v1.13.0
|
package/README.md
CHANGED
|
@@ -36,11 +36,11 @@ export const collections = defineAasCollections({ categoryMap });
|
|
|
36
36
|
|
|
37
37
|
| Where | What |
|
|
38
38
|
| --- | --- |
|
|
39
|
-
| `src/data/site.ts` | Site identity: name, repo URL, the `locales` it ships, optional `sections` toggles, per-locale UI string overrides |
|
|
39
|
+
| `src/data/site.ts` | Site identity: name, repo URL (`repoNav: false` hides the header's GitHub link), the `locales` it ships, optional `sections` toggles, browser icons (`icons: { favicon, appleTouch, manifest }`), the `home` template, per-locale UI string overrides |
|
|
40
40
|
| `src/data/categories.ts` | The tool-catalog category tree (validated against content) |
|
|
41
|
-
| `src/data/concept-categories.ts` · `article-categories.ts` · `course-categories.ts` (opt-in) | Taxonomies for concepts / articles / courses |
|
|
41
|
+
| `src/data/concept-categories.ts` · `article-categories.ts` · `course-categories.ts` · `product-categories.ts` (opt-in) | Taxonomies for concepts / articles / courses / products |
|
|
42
42
|
| `src/data/glossary.mjs` | `[[Term]]` wikilink targets |
|
|
43
|
-
| `src/content/{stacks,concepts,courses,articles,slides}/` | The content, one MDX file per locale |
|
|
43
|
+
| `src/content/{stacks,concepts,courses,products,articles,slides}/` | The content, one MDX file per locale |
|
|
44
44
|
| `src/content/pages/` | Standalone top-level pages (e.g. an About/소개), rendered at `/<slug>/` and optionally linked in the header nav |
|
|
45
45
|
| `public/` · `samples/` | Logos/favicons and runnable sample projects |
|
|
46
46
|
|
|
@@ -72,7 +72,7 @@ The rest are opt-out — **concepts, articles, samples, slides, glossary** and t
|
|
|
72
72
|
standalone **pages** collection (About/소개, …) — so a site can ship only what it
|
|
73
73
|
needs. Turning one off removes both its routes and its header-nav item. (`pages`
|
|
74
74
|
also has finer control: each page's `nav` / `draft` frontmatter, or simply not
|
|
75
|
-
authoring it.) **courses**
|
|
75
|
+
authoring it.) **courses** and **products** are the opt-IN sections — see below.
|
|
76
76
|
|
|
77
77
|
Declare the toggles once in `src/data/site.ts` and forward them to the theme in
|
|
78
78
|
astro.config (which needs them to skip route injection). Import `SectionKey` from
|
|
@@ -125,6 +125,47 @@ teaser: A public one-liner for the login gate.
|
|
|
125
125
|
|
|
126
126
|
Routes mirror concepts: `/course/`, `/course/<slug>/`, `/course/category/<id>/`.
|
|
127
127
|
|
|
128
|
+
## Products (opt-in)
|
|
129
|
+
|
|
130
|
+
The `products` collection is the "what we offer" umbrella — entries grouped
|
|
131
|
+
by a site-side mini-taxonomy (apps, services, education, …) on the
|
|
132
|
+
`/products/` index. Each entry is either a Things-style marketing landing
|
|
133
|
+
(`template: 'landing'`: hero with store buttons and an optional Product Hunt
|
|
134
|
+
badge, alternating feature rows with device-frame screenshots and
|
|
135
|
+
auto-rotating carousels, a video themes showcase, a highlights grid, pricing
|
|
136
|
+
tiers, a closing CTA and legal links — media are `public/` paths) or a plain
|
|
137
|
+
prose page (a service pitch, a legal subpage). Entries nest:
|
|
138
|
+
`products/<lang>/flowstate.mdx` → `/products/flowstate/`, and
|
|
139
|
+
`products/<lang>/flowstate/privacy.mdx` → `/products/flowstate/privacy/`.
|
|
140
|
+
The header gains a Products link automatically once a locale has a product;
|
|
141
|
+
`nav: true` additionally gives an entry its own header item.
|
|
142
|
+
|
|
143
|
+
Enable with `sections: { products: true }` plus
|
|
144
|
+
`src/data/product-categories.ts` (exporting `productTree` / `productCatOf` —
|
|
145
|
+
copy the playground's), and optionally pass `productCategoryMap` to
|
|
146
|
+
`defineAasCollections` for build-time category validation. See
|
|
147
|
+
`playground/src/content/products/` for a complete example.
|
|
148
|
+
|
|
149
|
+
## Homepage
|
|
150
|
+
|
|
151
|
+
The default home is the stack catalog. A site that isn't a catalog can swap in
|
|
152
|
+
a data-driven home from `src/data/site.ts`:
|
|
153
|
+
|
|
154
|
+
```ts
|
|
155
|
+
home: {
|
|
156
|
+
template: 'cards',
|
|
157
|
+
hero: { icon: '/img/logo.png', subtitle: { ko: '…', en: '…' } },
|
|
158
|
+
cardsTitle: { ko: '앱', en: 'Apps' },
|
|
159
|
+
cards: [{ href: '/products/flowstate/', name: 'FlowState', icon: '/img/icon.png',
|
|
160
|
+
rounded: true, description: { ko: '…', en: '…' }, tags: ['iOS'] }],
|
|
161
|
+
cta: { title: { … }, description: { … }, button: { label: { … }, href: '/course/' } },
|
|
162
|
+
},
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
Localized values are either one string or a per-locale record with
|
|
166
|
+
default-locale fallback. On a cards home the header's Browse link (which
|
|
167
|
+
anchors into the catalog) hides itself.
|
|
168
|
+
|
|
128
169
|
## Body components
|
|
129
170
|
|
|
130
171
|
Reusable MDX-body components, importable from any collection's content:
|
package/index.d.ts
CHANGED
package/index.mjs
CHANGED
|
@@ -29,6 +29,11 @@ const PAGES = [
|
|
|
29
29
|
// Standalone top-level pages (the `pages` collection), e.g. an About/소개
|
|
30
30
|
// page. A single dynamic route per locale renders every entry at `/<slug>/`.
|
|
31
31
|
'[page].astro',
|
|
32
|
+
// Product pages (the `products` collection): a category-grouped index, plus
|
|
33
|
+
// the landings/pages and their nested subpages (privacy/terms) via one
|
|
34
|
+
// catch-all.
|
|
35
|
+
'products/index.astro',
|
|
36
|
+
'products/[...id].astro',
|
|
32
37
|
// Per-locale RSS feed of the articles collection (an endpoint, not a page).
|
|
33
38
|
'rss.xml.ts',
|
|
34
39
|
'article/index.astro',
|
|
@@ -67,6 +72,7 @@ function sectionOf(file) {
|
|
|
67
72
|
if (file.startsWith('concept/')) return 'concepts';
|
|
68
73
|
if (file.startsWith('article/')) return 'articles';
|
|
69
74
|
if (file.startsWith('course/')) return 'courses';
|
|
75
|
+
if (file.startsWith('products/')) return 'products';
|
|
70
76
|
if (file.startsWith('sample/')) return 'samples';
|
|
71
77
|
if (file.startsWith('slides/')) return 'slides';
|
|
72
78
|
if (file === 'glossary.astro') return 'glossary';
|
|
@@ -76,9 +82,10 @@ function sectionOf(file) {
|
|
|
76
82
|
}
|
|
77
83
|
|
|
78
84
|
// Sections that are opt-IN rather than opt-out: their routes are injected only
|
|
79
|
-
// when the site passes `{ <key>: true }`.
|
|
80
|
-
// (src/data/course-categories.ts), so a theme upgrade alone must
|
|
81
|
-
|
|
85
|
+
// when the site passes `{ <key>: true }`. Both need site-side data
|
|
86
|
+
// (src/data/{course,product}-categories.ts), so a theme upgrade alone must
|
|
87
|
+
// not enable them.
|
|
88
|
+
const OPT_IN_SECTIONS = new Set(['courses', 'products']);
|
|
82
89
|
|
|
83
90
|
/**
|
|
84
91
|
* @param {object} opts
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "stack-site-builder",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "1.
|
|
4
|
+
"version": "1.17.0",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"description": "The engine behind the awesome-*-stack catalog sites: an Astro theme with the catalog/concepts/articles/slides/samples routes, components, styles and markdown pipeline. Sites provide content, taxonomy data and config.",
|
|
7
7
|
"repository": {
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
---
|
|
2
|
+
// The data-driven "cards" homepage (see src/lib/home.ts): hero with the site
|
|
3
|
+
// icon/name, a stack of wide link cards (products, sections, external links — the
|
|
4
|
+
// Things-style studio home), and a closing CTA banner. Everything renders
|
|
5
|
+
// from `site.home`; sections without data simply don't render.
|
|
6
|
+
import { site } from '@aas-data/site';
|
|
7
|
+
import { getRelativeLocaleUrl } from 'astro:i18n';
|
|
8
|
+
import { home, loc, locList } from '../lib/home';
|
|
9
|
+
import { useTranslations, type Lang } from '../i18n/ui';
|
|
10
|
+
|
|
11
|
+
interface Props {
|
|
12
|
+
lang: Lang;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const { lang } = Astro.props;
|
|
16
|
+
const t = useTranslations(lang);
|
|
17
|
+
const h = home;
|
|
18
|
+
const base = import.meta.env.BASE_URL.replace(/\/$/, '');
|
|
19
|
+
const withBase = (p?: string) => (p && p.startsWith('/') ? base + p : p);
|
|
20
|
+
|
|
21
|
+
const heroTitle = loc(h?.hero?.title, lang) ?? site.name;
|
|
22
|
+
const heroSubtitle = loc(h?.hero?.subtitle, lang) ?? t('site.tagline');
|
|
23
|
+
const extAttrs = (external?: boolean) =>
|
|
24
|
+
external ? { target: '_blank', rel: 'noopener noreferrer' } : {};
|
|
25
|
+
// Internal links ("/products/…") are written locale-less in site.home and get the
|
|
26
|
+
// current locale's prefix here, so the en home links to the en pages.
|
|
27
|
+
const resolveHref = (href: string, external?: boolean) =>
|
|
28
|
+
!external && href.startsWith('/') ? getRelativeLocaleUrl(lang, href.replace(/^\//, '')) : href;
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
<section class="py-14 text-center">
|
|
32
|
+
{h?.hero?.icon && <img src={withBase(h.hero.icon)} alt="" class="mx-auto h-20 w-20 rounded-[22%] shadow-md" />}
|
|
33
|
+
<h1 class="mt-5 text-4xl font-bold tracking-tight sm:text-5xl">{heroTitle}</h1>
|
|
34
|
+
<p class="mx-auto mt-4 max-w-2xl text-xl text-[var(--aas-muted)]" set:html={heroSubtitle} />
|
|
35
|
+
</section>
|
|
36
|
+
|
|
37
|
+
{
|
|
38
|
+
(h?.cards?.length ?? 0) > 0 && (
|
|
39
|
+
<section class="aas-bleed bg-[var(--aas-panel)] py-14">
|
|
40
|
+
<div class="mx-auto max-w-[75rem] px-5">
|
|
41
|
+
{h?.cardsTitle && <h2 class="mb-6 text-3xl font-bold tracking-tight">{loc(h.cardsTitle, lang)}</h2>}
|
|
42
|
+
<div class="flex flex-col gap-5">
|
|
43
|
+
{h?.cards?.map((c) => (
|
|
44
|
+
<a
|
|
45
|
+
href={resolveHref(c.href, c.external)}
|
|
46
|
+
{...extAttrs(c.external)}
|
|
47
|
+
class="aas-lift flex items-center gap-6 rounded-3xl border border-[var(--aas-border)] bg-[var(--aas-bg)] p-6 no-underline"
|
|
48
|
+
>
|
|
49
|
+
{c.icon && (
|
|
50
|
+
<img
|
|
51
|
+
src={withBase(c.icon)}
|
|
52
|
+
alt=""
|
|
53
|
+
class:list={['h-20 w-20 shrink-0', c.rounded ? 'rounded-[22%]' : 'rounded-xl']}
|
|
54
|
+
loading="lazy"
|
|
55
|
+
/>
|
|
56
|
+
)}
|
|
57
|
+
<span class="min-w-0">
|
|
58
|
+
<span class="block text-xl font-semibold text-[var(--aas-text)]">{loc(c.name, lang)}</span>
|
|
59
|
+
{c.description && (
|
|
60
|
+
<span
|
|
61
|
+
class="mt-1 block leading-relaxed text-[var(--aas-muted)]"
|
|
62
|
+
set:html={loc(c.description, lang)}
|
|
63
|
+
/>
|
|
64
|
+
)}
|
|
65
|
+
{locList(c.tags, lang).length > 0 && (
|
|
66
|
+
<span class="mt-3 flex flex-wrap gap-1.5">
|
|
67
|
+
{locList(c.tags, lang).map((tag) => (
|
|
68
|
+
<span class="rounded-full border border-[var(--aas-border)] px-2 py-0.5 text-xs text-[var(--aas-muted)]">
|
|
69
|
+
{tag}
|
|
70
|
+
</span>
|
|
71
|
+
))}
|
|
72
|
+
</span>
|
|
73
|
+
)}
|
|
74
|
+
</span>
|
|
75
|
+
</a>
|
|
76
|
+
))}
|
|
77
|
+
</div>
|
|
78
|
+
</div>
|
|
79
|
+
</section>
|
|
80
|
+
)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
{
|
|
84
|
+
h?.cta && (
|
|
85
|
+
<section class="py-16 text-center">
|
|
86
|
+
{h.cta.title && <h2 class="text-3xl font-bold tracking-tight">{loc(h.cta.title, lang)}</h2>}
|
|
87
|
+
{h.cta.description && (
|
|
88
|
+
<p class="mx-auto mt-3 max-w-2xl text-[var(--aas-muted)]" set:html={loc(h.cta.description, lang)} />
|
|
89
|
+
)}
|
|
90
|
+
{h.cta.button && (
|
|
91
|
+
<a
|
|
92
|
+
href={resolveHref(h.cta.button.href, h.cta.button.external)}
|
|
93
|
+
{...extAttrs(h.cta.button.external)}
|
|
94
|
+
class="mt-7 inline-block rounded-full bg-[var(--aas-accent)] px-7 py-3 font-semibold text-white no-underline"
|
|
95
|
+
>
|
|
96
|
+
{loc(h.cta.button.label, lang)}
|
|
97
|
+
</a>
|
|
98
|
+
)}
|
|
99
|
+
</section>
|
|
100
|
+
)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
<style>
|
|
104
|
+
/* Full-bleed section: escape BaseLayout's centered container. */
|
|
105
|
+
.aas-bleed {
|
|
106
|
+
position: relative;
|
|
107
|
+
left: 50%;
|
|
108
|
+
margin-left: -50vw;
|
|
109
|
+
margin-right: -50vw;
|
|
110
|
+
right: 50%;
|
|
111
|
+
width: 100vw;
|
|
112
|
+
}
|
|
113
|
+
</style>
|