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 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** is the one opt-IN section — see below.
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
@@ -7,6 +7,7 @@ export type SectionKey =
7
7
  | 'concepts'
8
8
  | 'articles'
9
9
  | 'courses'
10
+ | 'products'
10
11
  | 'samples'
11
12
  | 'slides'
12
13
  | 'glossary'
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 }`. `courses` needs site-side data
80
- // (src/data/course-categories.ts), so a theme upgrade alone must not enable it.
81
- const OPT_IN_SECTIONS = new Set(['courses']);
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.15.0",
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>