@hagicode/hagilight-starlight 0.14.1-dev.144.1.bcc5ef8 → 0.14.1-dev.149.1.1dadf5c

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.
@@ -3,18 +3,29 @@ import { Picture } from 'astro:assets';
3
3
  import { gaEventAttributes } from '@hagicode/hagilight-core/analytics-events';
4
4
  import { resolveSiteLinks } from '@hagicode/hagilight-core/links';
5
5
  import { getArticlePromotionCopy } from './dist/article-promotion.js';
6
+ import { randomPoolAttribute, randomPoolFromManifest } from '@hagicode/hagilight-core/random-image';
6
7
  import {
7
8
  resolveMicrosoftStoreBadgeLanguage,
8
9
  resolveMicrosoftStoreProductId,
9
10
  } from './dist/windows-download.js';
11
+ import catHead from './assets/showcase/cat-head.webp';
10
12
  import heroes from './assets/showcase/heroes.webp';
11
13
  import pillarEfficient from './assets/showcase/pillar-efficient.svg';
12
14
  import pillarFun from './assets/showcase/pillar-fun.svg';
13
15
  import pillarSmart from './assets/showcase/pillar-smart.svg';
14
16
  import proposalWorkflow from './assets/showcase/proposal-workflow.webp';
15
17
  import workbench from './assets/showcase/workbench.webp';
18
+ import showcaseManifest from './assets/showcase/manifest.json';
19
+ import ArticlePromotionPicker from './ArticlePromotionPicker.astro';
16
20
 
17
- const { lang } = Astro.props;
21
+ interface Props {
22
+ lang?: string;
23
+ /** Swap the illustration for one random pool entry per page view. Defaults to `true`. */
24
+ randomAvatar?: boolean;
25
+ }
26
+
27
+ const { lang, randomAvatar = true } = Astro.props;
28
+ if (typeof randomAvatar !== 'boolean') throw new TypeError('Hagilight hagicodePromotion randomAvatar option must be a boolean.');
18
29
  const copy = getArticlePromotionCopy(lang);
19
30
  const siteLinks = resolveSiteLinks(lang);
20
31
  const linkHref = (id: string, fallback: string) =>
@@ -33,7 +44,12 @@ const galleryImages: Record<string, ImageMetadata> = {
33
44
  workbench,
34
45
  'proposal-workflow': proposalWorkflow,
35
46
  heroes,
47
+ 'cat-head': catHead,
36
48
  };
49
+ // The illustration slot is the only gallery image that may change; its pool lives in the showcase manifest.
50
+ const ILLUSTRATION_ID = 'cat-head';
51
+ const avatarEntry = showcaseManifest.assets.find(({ id }) => id === ILLUSTRATION_ID);
52
+ const avatarPool = randomAvatar && avatarEntry && 'pool' in avatarEntry ? randomPoolFromManifest(avatarEntry.pool) : undefined;
37
53
  const pillarIcons: Record<string, ImageMetadata> = {
38
54
  smart: pillarSmart,
39
55
  efficient: pillarEfficient,
@@ -90,7 +106,7 @@ const gallery = copy.gallery.flatMap((item) => {
90
106
  <ul class="hagilight-article-promotion__gallery" aria-labelledby="hagilight-article-promotion-gallery-title">
91
107
  {gallery.map((item) => (
92
108
  <li>
93
- <figure>
109
+ <figure data-random-pool={item.id === ILLUSTRATION_ID && avatarPool ? randomPoolAttribute(avatarPool) : undefined}>
94
110
  <Picture
95
111
  src={item.image}
96
112
  alt={item.alt}
@@ -111,6 +127,8 @@ const gallery = copy.gallery.flatMap((item) => {
111
127
  </ul>
112
128
  </div>
113
129
 
130
+ {avatarPool && <ArticlePromotionPicker />}
131
+
114
132
  <div class="hagilight-article-promotion__closing">
115
133
  <div class="hagilight-article-promotion__windows">
116
134
  <h3 id="hagilight-article-promotion-windows-title" class="hagilight-article-promotion__windows-label">{copy.windowsLabel}</h3>
@@ -419,6 +437,11 @@ const gallery = copy.gallery.flatMap((item) => {
419
437
  line-height: 1.5;
420
438
  }
421
439
 
440
+ /* The illustration is a small square with its caption beside it, so it reads as an avatar, not a screenshot. */
441
+ .hagilight-article-promotion__gallery li:nth-child(4) picture {
442
+ max-width: 12rem;
443
+ }
444
+
422
445
  /* Closing: a full-bleed band, the heaviest surface, so "get it" is where the eye lands last. */
423
446
  .hagilight-article-promotion__closing {
424
447
  display: grid;
@@ -519,6 +542,15 @@ const gallery = copy.gallery.flatMap((item) => {
519
542
  grid-template-columns: repeat(2, minmax(0, 1fr));
520
543
  gap: 2rem;
521
544
  }
545
+
546
+ .hagilight-article-promotion__gallery li:nth-child(4) {
547
+ grid-column: 1 / -1;
548
+ }
549
+
550
+ .hagilight-article-promotion__gallery li:nth-child(4) figure {
551
+ grid-template-columns: minmax(0, 12rem) minmax(0, 1fr);
552
+ align-items: end;
553
+ }
522
554
  }
523
555
 
524
556
  /* Wide: the pitch takes the left column and the pillars stack beside it. */
@@ -575,6 +607,10 @@ const gallery = copy.gallery.flatMap((item) => {
575
607
  .hagilight-article-promotion__gallery li:nth-child(3) {
576
608
  animation-range: entry 19% entry 59%;
577
609
  }
610
+
611
+ .hagilight-article-promotion__gallery li:nth-child(4) {
612
+ animation-range: entry 26% entry 66%;
613
+ }
578
614
  }
579
615
  }
580
616
 
@@ -0,0 +1,9 @@
1
+ ---
2
+ // Rendered only when the showcase's random option is on, so a site that turns it off ships no picker script.
3
+ ---
4
+
5
+ <script>
6
+ import { mountRandomImages } from '@hagicode/hagilight-core/random-image';
7
+
8
+ void mountRandomImages(document);
9
+ </script>
package/Footer.astro CHANGED
@@ -13,6 +13,8 @@ interface Props {
13
13
  rssFeedUrl?: string;
14
14
  rssLocaleFeedUrls?: Readonly<Record<string, string>>;
15
15
  buildInfo?: BuildInfo;
16
+ avatar?: boolean;
17
+ randomAvatar?: boolean;
16
18
  }
17
19
 
18
20
  const {
@@ -21,6 +23,8 @@ const {
21
23
  rssFeedUrl,
22
24
  rssLocaleFeedUrls,
23
25
  buildInfo,
26
+ avatar,
27
+ randomAvatar,
24
28
  } = Astro.props;
25
29
  const buildItems = buildInfoItems(buildInfo);
26
30
  if (typeof manifest.version !== 'string' || manifest.version.trim().length === 0) {
@@ -46,7 +50,7 @@ const resolvedLinks = resolveSiteLinks(currentLanguage ?? locale, {
46
50
 
47
51
  <DefaultFooter />
48
52
  <div class="hagilight-brand-host">
49
- <FooterBrand locale={resolvedLinks.locale} links={resolvedLinks.quick} />
53
+ <FooterBrand locale={resolvedLinks.locale} links={resolvedLinks.quick} avatar={avatar} randomAvatar={randomAvatar} />
50
54
  </div>
51
55
  <div class="hagilight-site-links">
52
56
  {resolvedLinks.relatedSites.length > 0 && (
@@ -13,6 +13,7 @@ import { resolveArticlePromotion } from './dist/article-promotion.js';
13
13
  const {
14
14
  aiDisclosures = { isAITranslation: false, isAIAuthor: false, sourceLocale: 'root' },
15
15
  hagicodePromotionEnabled = true,
16
+ hagicodePromotionRandomAvatar = true,
16
17
  } = Astro.props;
17
18
  const route = Astro.locals.starlightRoute;
18
19
  const isDocsEntry = route.entry.id !== '404' && !route.entry.id.endsWith('/404');
@@ -41,4 +42,4 @@ if (showTranslation) {
41
42
  {flags.isAIAuthor && <AIDisclosureNotice kind="author" lang={route.lang} />}
42
43
  <DefaultMarkdownContent><slot /></DefaultMarkdownContent>
43
44
  {showTranslation && <AIDisclosureNotice kind="translation" lang={route.lang} sourceHref={sourceHref} />}
44
- {showHagicodePromotion && <ArticlePromotion lang={route.lang} />}
45
+ {showHagicodePromotion && <ArticlePromotion lang={route.lang} randomAvatar={hagicodePromotionRandomAvatar} />}
package/README.md CHANGED
@@ -105,7 +105,7 @@ hagilight({ contentImages: { enabled: false } })
105
105
 
106
106
  ### HagiCode showcase
107
107
 
108
- After the body (and any translation notice) of every documentation article, the plugin renders a static HagiCode showcase: a headline, lead, and tagline with calls to action to the website and the documentation, three pillars (Smart, Efficient, Fun), a three-image gallery with captions, and a closing row with the Windows download button beside a selectable, copy-ready share sentence. The layout follows the showcase's own width: one column when narrow, a three-up pillar row, a gallery mosaic, and a two-column closing row from 36rem, and the pillars beside the pitch from 56rem (for example in the wide content mode). All text is localized for the ten published locales (`en-US`, `zh-CN`, `zh-Hant`, `ja-JP`, `ko-KR`, `de-DE`, `fr-FR`, `es-ES`, `pt-BR`, `ru-RU`) and falls back to English for any other language. Links come from the same locale-aware catalog as the footer.
108
+ After the body (and any translation notice) of every documentation article, the plugin renders a static HagiCode showcase: a headline, lead, and tagline with calls to action to the website and the documentation, three pillars (Smart, Efficient, Fun), a gallery with captions (three audited screenshots and one cat-head illustration), and a closing row with the Windows download button beside a selectable, copy-ready share sentence. The layout follows the showcase's own width: one column when narrow, a three-up pillar row, a gallery mosaic, and a two-column closing row from 36rem, and the pillars beside the pitch from 56rem (for example in the wide content mode). All text is localized for the ten published locales (`en-US`, `zh-CN`, `zh-Hant`, `ja-JP`, `ko-KR`, `de-DE`, `fr-FR`, `es-ES`, `pt-BR`, `ru-RU`) and falls back to English for any other language. Links come from the same locale-aware catalog as the footer.
109
109
 
110
110
  Turn it off for the whole site, or override it per article:
111
111
 
@@ -143,7 +143,7 @@ This is the only script the showcase loads: `https://get.microsoft.com/badge/ms-
143
143
 
144
144
  #### Assets and image traffic
145
145
 
146
- The gallery images and pillar icons live in `assets/showcase/`, with a provenance record in `assets/showcase/manifest.json`. Each entry states its kind (`sourced` from the HagiCode web app or documentation, a hand-authored `svg`, or a `generated` illustration), its source path or generation record (tool, prompt, parameters, date), the crop and conversion applied, its size, and the audit verdict. Nothing is fetched from the product sites at build or read time.
146
+ The gallery images and pillar icons live in `assets/showcase/`. The illustration `cat-head.webp` is the R2 object `cat-head-gallery/g067-pixar-style-3d-02.avif` (1024 px) upscaled to the 1200 px minimum; its manifest entry records the source and this transform, with a provenance record in `assets/showcase/manifest.json`. Each entry states its kind (`sourced` from the HagiCode web app or documentation, a hand-authored `svg`, or a `generated` illustration), its source path or generation record (tool, prompt, parameters, date), the crop and conversion applied, its size, and the audit verdict. Nothing is fetched from the product sites at build or read time.
147
147
 
148
148
  Every asset passes the same audit before it is committed: the current `HagiCode` brand, no Steam or other retired-channel content, no third-party promotion, no credentials, private repository names, or personal data, and rasters at least 1200 px wide. A removable offending region may be cropped out, and the crop is recorded. Pick images in this order to keep traffic low: an audited existing image converted to WebP, then a hand-authored SVG, then a newly generated raster only when neither fits. Generated art is an illustration, never a replica of the product UI, contains no text or logos, is captioned as an illustration, is reviewed by a person, and records its prompt and parameters (never credentials). Generation is an authoring step; the package never calls a generator.
149
149
 
@@ -166,7 +166,9 @@ To check a deployed site, run `curl -sI https://<site>/_astro/<file>.avif` with
166
166
  The copy and assets mirror `repos/web` and `repos/docs` in the HagiCode monorepo, which this package never edits.
167
167
 
168
168
  - **Copy:** edit `src/article-promotion-copy/<locale>.ts` (every locale must keep the same fields, feature ids, and gallery ids). Terminology is pinned in `GLOSSARY` in `src/article-promotion-copy/index.ts`, seeded from the product locale files in `repos/web/src/locales/<locale>/`. `HagiCode`, `OpenSpec`, `Hero Scenario`, `Microsoft Store`, and `Windows` are never translated.
169
- - **Images:** audit the candidate, crop and convert it to WebP (the AVIF and JPEG tiers are derived at build time, so an opaque image needs no extra file), add or update its `manifest.json` entry, register it in the `galleryImages` map in `ArticlePromotion.astro`, and add a caption and text alternative in every locale. The gallery mosaic places the first, second, and third catalog entries as the left top, left bottom, and right tall slot, so keep the gallery at three images or revisit the placement in the component's `@container (min-width: 36rem)` block. `npm test` checks that the manifest, the files, the component registry, and the copy agree.
169
+ - **Images:** audit the candidate, crop and convert it to WebP (the AVIF and JPEG tiers are derived at build time, so an opaque image needs no extra file), add or update its `manifest.json` entry, register it in the `galleryImages` map in `ArticlePromotion.astro`, and add a caption and text alternative in every locale. The gallery mosaic places the first, second, and third catalog entries as the left top, left bottom, and right tall slot, so keep the three screenshots first. The illustration is the fourth entry: from 36rem it takes a full-width row with a 12 rem square and its caption, and it is the only gallery image that may change. To add a screenshot, revisit the placement in the component's `@container (min-width: 36rem)` block. `npm test` checks that the manifest, the files, the component registry, and the copy agree.
170
+ - **Random illustration (R2 pool):** the `cat-head` entry in `assets/showcase/manifest.json` carries `source` (`repo: img-gen`, `album`, `objectKey`), its audit, and `pool` (`album`, `maxBytes` 200 KiB, `entries` with `key` and `bytes`). `ArticlePromotion.astro` builds the pool attribute at build time and renders `ArticlePromotionPicker.astro` only when the option is on. Refresh: take keys from the `cat-head-gallery` album `index.json`, keep 12 to 200 unique keys each within `maxBytes`, audit them text-free and current, update `pool.entries`, and keep the footer pool in `repos/hagilight/packages/core/assets/footer/manifest.json` in step. To rebuild the WebP, resize the source to 1200 px (lanczos3) and encode at quality 88. `npm test` (`test/showcase-assets.test.mjs`) checks the entry, the pool, the byte caps and the budgets.
171
+ - **Options:** `hagicodePromotion: { randomAvatar: false }` keeps the default illustration and loads no picker; `footer: { avatar: false }` removes the footer avatar, and `footer: { randomAvatar: false }` keeps its default. A site whose `img-src` policy blocks `https://img-gen.hagicode.com` keeps the defaults.
170
172
  - **Badge:** the product id is parsed from the Store URL and the badge language map lives in `src/windows-download.ts`. Re-check both against `repos/docs/src/components/MicrosoftStoreBadge.tsx` and the official site when the Store listing or the supported languages change.
171
173
 
172
174
  The plugin footer and direct `/Footer` and `/PromotoFooter` components append
Binary file
@@ -72,6 +72,237 @@
72
72
  "notes": "Current HagiCode UI, English panel text with two Chinese hero names. Hero portraits are the product's own art; no third-party brand, no private data, no Steam content in the kept region."
73
73
  }
74
74
  },
75
+ {
76
+ "id": "cat-head",
77
+ "kind": "sourced",
78
+ "file": "cat-head.webp",
79
+ "width": 1200,
80
+ "height": 1200,
81
+ "bytes": 80780,
82
+ "source": {
83
+ "repo": "img-gen",
84
+ "album": "cat-head-gallery",
85
+ "objectKey": "cat-head-gallery/g067-pixar-style-3d-02.avif",
86
+ "width": 1024,
87
+ "height": 1024,
88
+ "bytes": 31093
89
+ },
90
+ "transforms": [
91
+ "upscale 1024x1024 to 1200x1200 (lanczos3); the R2 source is 1024 px and the showcase minimum is 1200 px",
92
+ "webp quality 88"
93
+ ],
94
+ "audit": {
95
+ "verdict": "pass",
96
+ "checkedOn": "2026-10-22",
97
+ "notes": "Pixar-style 3D cat head, text-free, from cat-head-gallery (style pixar-style-3d). Captioned and labeled as an illustration, not a product screenshot. No visible text, third-party marks, real faces, retired channel or personal data seen. The pool is the same 50 keys as the footer pool; neither default is in it, so the illustration always changes when it swaps."
98
+ },
99
+ "pool": {
100
+ "album": "cat-head-gallery",
101
+ "maxBytes": 204800,
102
+ "entries": [
103
+ {
104
+ "key": "g002-ukiyo-e-01",
105
+ "bytes": 25795
106
+ },
107
+ {
108
+ "key": "g003-watercolor-wash-02",
109
+ "bytes": 30932
110
+ },
111
+ {
112
+ "key": "g004-pixel-art-03",
113
+ "bytes": 24154
114
+ },
115
+ {
116
+ "key": "g005-oil-impasto-06",
117
+ "bytes": 37209
118
+ },
119
+ {
120
+ "key": "g006-paper-cut-layers-07",
121
+ "bytes": 15837
122
+ },
123
+ {
124
+ "key": "g008-low-poly-07",
125
+ "bytes": 17251
126
+ },
127
+ {
128
+ "key": "g009-claymation-07",
129
+ "bytes": 16599
130
+ },
131
+ {
132
+ "key": "g010-neon-line-07",
133
+ "bytes": 21312
134
+ },
135
+ {
136
+ "key": "g016-bauhaus-geometry-06",
137
+ "bytes": 13663
138
+ },
139
+ {
140
+ "key": "g017-cyanotype-06",
141
+ "bytes": 18762
142
+ },
143
+ {
144
+ "key": "g020-origami-03",
145
+ "bytes": 26060
146
+ },
147
+ {
148
+ "key": "g022-blueprint-03",
149
+ "bytes": 23845
150
+ },
151
+ {
152
+ "key": "g023-chalk-pastel-10",
153
+ "bytes": 29427
154
+ },
155
+ {
156
+ "key": "g025-voxel-03",
157
+ "bytes": 8242
158
+ },
159
+ {
160
+ "key": "g031-illuminated-manuscript-04",
161
+ "bytes": 32530
162
+ },
163
+ {
164
+ "key": "g034-vaporwave-04",
165
+ "bytes": 36264
166
+ },
167
+ {
168
+ "key": "g035-synthwave-chrome-09",
169
+ "bytes": 18415
170
+ },
171
+ {
172
+ "key": "g038-frosted-glass-02",
173
+ "bytes": 28185
174
+ },
175
+ {
176
+ "key": "g039-needle-felt-09",
177
+ "bytes": 33696
178
+ },
179
+ {
180
+ "key": "g041-glossy-balloon-05",
181
+ "bytes": 14623
182
+ },
183
+ {
184
+ "key": "g042-blue-white-porcelain-05",
185
+ "bytes": 31257
186
+ },
187
+ {
188
+ "key": "g043-glazed-ceramic-06",
189
+ "bytes": 11086
190
+ },
191
+ {
192
+ "key": "g045-impressionist-08",
193
+ "bytes": 32479
194
+ },
195
+ {
196
+ "key": "g047-fauvist-09",
197
+ "bytes": 18358
198
+ },
199
+ {
200
+ "key": "g048-cubist-10",
201
+ "bytes": 31586
202
+ },
203
+ {
204
+ "key": "g051-naive-folk-art-07",
205
+ "bytes": 19613
206
+ },
207
+ {
208
+ "key": "g056-wax-print-fabric-07",
209
+ "bytes": 16978
210
+ },
211
+ {
212
+ "key": "g058-red-paper-cut-10",
213
+ "bytes": 20948
214
+ },
215
+ {
216
+ "key": "g060-kawaii-pastel-01",
217
+ "bytes": 12179
218
+ },
219
+ {
220
+ "key": "g061-retro-90s-anime-01",
221
+ "bytes": 29377
222
+ },
223
+ {
224
+ "key": "g062-shoujo-sparkle-08",
225
+ "bytes": 27048
226
+ },
227
+ {
228
+ "key": "g065-saturday-cartoon-09",
229
+ "bytes": 19421
230
+ },
231
+ {
232
+ "key": "g067-pixar-style-3d-03",
233
+ "bytes": 16925
234
+ },
235
+ {
236
+ "key": "g068-toon-shaded-3d-04",
237
+ "bytes": 14116
238
+ },
239
+ {
240
+ "key": "g069-ps1-lowpoly-06",
241
+ "bytes": 18227
242
+ },
243
+ {
244
+ "key": "g070-matte-clay-render-07",
245
+ "bytes": 12775
246
+ },
247
+ {
248
+ "key": "g071-y2k-chrome-08",
249
+ "bytes": 24059
250
+ },
251
+ {
252
+ "key": "g072-glossy-3d-icon-08",
253
+ "bytes": 13376
254
+ },
255
+ {
256
+ "key": "g073-marble-bust-08",
257
+ "bytes": 17446
258
+ },
259
+ {
260
+ "key": "g075-wood-carving-10",
261
+ "bytes": 38433
262
+ },
263
+ {
264
+ "key": "g076-carved-jade-10",
265
+ "bytes": 14512
266
+ },
267
+ {
268
+ "key": "g078-toy-brick-02",
269
+ "bytes": 11910
270
+ },
271
+ {
272
+ "key": "g079-designer-vinyl-toy-02",
273
+ "bytes": 10594
274
+ },
275
+ {
276
+ "key": "g081-patchwork-quilt-03",
277
+ "bytes": 25601
278
+ },
279
+ {
280
+ "key": "g083-warhol-silkscreen-04",
281
+ "bytes": 14747
282
+ },
283
+ {
284
+ "key": "g088-vintage-lithograph-02",
285
+ "bytes": 27832
286
+ },
287
+ {
288
+ "key": "g090-alcohol-marker-09",
289
+ "bytes": 34894
290
+ },
291
+ {
292
+ "key": "g091-painterly-concept-art-09",
293
+ "bytes": 18574
294
+ },
295
+ {
296
+ "key": "g092-gradient-minimal-09",
297
+ "bytes": 14823
298
+ },
299
+ {
300
+ "key": "g095-constellation-map-09",
301
+ "bytes": 25940
302
+ }
303
+ ]
304
+ }
305
+ },
75
306
  {
76
307
  "id": "pillar-smart",
77
308
  "kind": "svg",
@@ -28,6 +28,11 @@ export default {
28
28
  caption: 'Hero Scenario: Helden erledigen die Arbeit, Sie behalten Fortschritt, Level und Geschichte im Blick.',
29
29
  alt: 'Bereich Hero Scenario von HagiCode namens Ember Keep mit drei laufenden Helden samt Level, Fortschritt und Geschichtstext.',
30
30
  },
31
+ {
32
+ id: 'cat-head',
33
+ caption: 'Illustration: ein Katzenkopf-Porträt aus dem HagiCode-Illustrationsset, das bei jedem Besuch wechselt.',
34
+ alt: 'Stilisierte Katzenkopf-Illustration aus dem HagiCode-Illustrationsset.',
35
+ },
31
36
  ],
32
37
  windowsLabel: 'Für Windows herunterladen',
33
38
  windowsStoreLabel: 'Im Microsoft Store erhalten',
@@ -28,6 +28,11 @@ export default {
28
28
  caption: 'Hero Scenario: heroes run the work while you follow progress, levels, and story.',
29
29
  alt: 'HagiCode Hero Scenario panel named Ember Keep, listing three running heroes with their level, progress, and story text.',
30
30
  },
31
+ {
32
+ id: 'cat-head',
33
+ caption: 'Illustration: a cat-head portrait from the HagiCode illustration set, one of many that change between visits.',
34
+ alt: 'Stylized cat-head illustration from the HagiCode illustration set.',
35
+ },
31
36
  ],
32
37
  windowsLabel: 'Download for Windows',
33
38
  windowsStoreLabel: 'Get it from Microsoft Store',
@@ -28,6 +28,11 @@ export default {
28
28
  caption: 'Hero Scenario: los héroes sacan adelante el trabajo mientras sigues el progreso, los niveles y la historia.',
29
29
  alt: 'Panel Hero Scenario de HagiCode llamado Ember Keep, con tres héroes en ejecución y su nivel, progreso y texto de la historia.',
30
30
  },
31
+ {
32
+ id: 'cat-head',
33
+ caption: 'Ilustración: un retrato de cabeza de gato del conjunto de ilustraciones de HagiCode, que cambia en cada visita.',
34
+ alt: 'Ilustración estilizada de una cabeza de gato del conjunto de ilustraciones de HagiCode.',
35
+ },
31
36
  ],
32
37
  windowsLabel: 'Descargar para Windows',
33
38
  windowsStoreLabel: 'Obtenerlo en Microsoft Store',
@@ -28,6 +28,11 @@ export default {
28
28
  caption: 'Hero Scenario : les héros avancent le travail pendant que vous suivez progression, niveaux et récit.',
29
29
  alt: 'Panneau Hero Scenario de HagiCode nommé Ember Keep, listant trois héros en cours avec leur niveau, leur progression et le texte du récit.',
30
30
  },
31
+ {
32
+ id: 'cat-head',
33
+ caption: 'Illustration : un portrait de tête de chat tiré de l’ensemble d’illustrations HagiCode, qui change à chaque visite.',
34
+ alt: 'Illustration stylisée d’une tête de chat tirée de l’ensemble d’illustrations HagiCode.',
35
+ },
31
36
  ],
32
37
  windowsLabel: 'Télécharger pour Windows',
33
38
  windowsStoreLabel: 'Obtenir sur le Microsoft Store',
@@ -8,6 +8,8 @@ export interface ShowcaseGlossaryEntry {
8
8
  workflow: string;
9
9
  /** Approved word for "documentation". */
10
10
  documentation: string;
11
+ /** Approved word for the decorative avatar caption ("illustration"). */
12
+ illustration: string;
11
13
  /** Approved labels for the Smart, Efficient, and Interesting pillars. */
12
14
  pillars: {
13
15
  smart: string;
@@ -17,16 +17,16 @@ import zhHant from './zh-Hant.js';
17
17
  * Internal to the catalog: it is not part of the package's public entry points.
18
18
  */
19
19
  export const GLOSSARY = {
20
- 'zh-CN': { agent: 'Agent', multiAgent: '多 Agent', workflow: '工作流', documentation: '文档', pillars: { smart: '智能', efficient: '高效', fun: '有趣' }, avoid: ['智能体', '代理'] },
21
- 'en-US': { agent: 'agent', multiAgent: 'multi-agent', workflow: 'workflow', documentation: 'documentation', pillars: { smart: 'Smart', efficient: 'Efficient', fun: 'Interesting' }, avoid: ['multiagent'] },
22
- 'zh-Hant': { agent: 'Agent', multiAgent: '多 Agent', workflow: '工作流程', documentation: '文件', pillars: { smart: '智慧', efficient: '高效', fun: '有趣' }, avoid: ['代理程式', '代理商', '智慧代理'] },
23
- 'ja-JP': { agent: 'エージェント', multiAgent: 'マルチエージェント', workflow: 'ワークフロー', documentation: 'ドキュメント', pillars: { smart: 'スマート', efficient: '効率的', fun: '興味深い' }, avoid: ['エージェンシー', 'ドキュメンテーション'] },
24
- 'ko-KR': { agent: '에이전트', multiAgent: '다중 에이전트', workflow: '워크플로', documentation: '문서', pillars: { smart: '스마트', efficient: '효율적', fun: '흥미로운' }, avoid: ['대리인', '멀티 에이전트'] },
25
- 'de-DE': { agent: 'Agent', multiAgent: 'Multi-Agent', workflow: 'Workflow', documentation: 'Dokumentation', pillars: { smart: 'Intelligent', efficient: 'Effizient', fun: 'Interessant' }, avoid: ['Multiagent', 'Arbeitsablauf'] },
26
- 'fr-FR': { agent: 'agent', multiAgent: 'multi-agent', workflow: 'workflow', documentation: 'documentation', pillars: { smart: 'Intelligent', efficient: 'Efficace', fun: 'Intéressant' }, avoid: ['multiagent', 'flux de travail'] },
27
- 'es-ES': { agent: 'agente', multiAgent: 'multiagente', workflow: 'flujos de trabajo', documentation: 'documentación', pillars: { smart: 'Inteligente', efficient: 'Eficiente', fun: 'Interesante' }, avoid: ['multi-agente', 'workflow'] },
28
- 'pt-BR': { agent: 'agente', multiAgent: 'multiagente', workflow: 'fluxos de trabalho', documentation: 'documentação', pillars: { smart: 'Inteligente', efficient: 'Eficiente', fun: 'Interessante' }, avoid: ['multi-agente', 'workflow'] },
29
- 'ru-RU': { agent: 'агент', multiAgent: 'мультиагент', workflow: 'рабочие процессы', documentation: 'документаци', pillars: { smart: 'Умный', efficient: 'Эффективный', fun: 'Интересный' }, avoid: ['мульти-агент'] },
20
+ 'zh-CN': { agent: 'Agent', multiAgent: '多 Agent', workflow: '工作流', documentation: '文档', pillars: { smart: '智能', efficient: '高效', fun: '有趣' }, illustration: '插图', avoid: ['智能体', '代理'] },
21
+ 'en-US': { agent: 'agent', multiAgent: 'multi-agent', workflow: 'workflow', documentation: 'documentation', pillars: { smart: 'Smart', efficient: 'Efficient', fun: 'Interesting' }, illustration: 'illustration', avoid: ['multiagent'] },
22
+ 'zh-Hant': { agent: 'Agent', multiAgent: '多 Agent', workflow: '工作流程', documentation: '文件', pillars: { smart: '智慧', efficient: '高效', fun: '有趣' }, illustration: '插圖', avoid: ['代理程式', '代理商', '智慧代理'] },
23
+ 'ja-JP': { agent: 'エージェント', multiAgent: 'マルチエージェント', workflow: 'ワークフロー', documentation: 'ドキュメント', pillars: { smart: 'スマート', efficient: '効率的', fun: '興味深い' }, illustration: 'イラスト', avoid: ['エージェンシー', 'ドキュメンテーション'] },
24
+ 'ko-KR': { agent: '에이전트', multiAgent: '다중 에이전트', workflow: '워크플로', documentation: '문서', pillars: { smart: '스마트', efficient: '효율적', fun: '흥미로운' }, illustration: '일러스트', avoid: ['대리인', '멀티 에이전트'] },
25
+ 'de-DE': { agent: 'Agent', multiAgent: 'Multi-Agent', workflow: 'Workflow', documentation: 'Dokumentation', pillars: { smart: 'Intelligent', efficient: 'Effizient', fun: 'Interessant' }, illustration: 'Illustration', avoid: ['Multiagent', 'Arbeitsablauf'] },
26
+ 'fr-FR': { agent: 'agent', multiAgent: 'multi-agent', workflow: 'workflow', documentation: 'documentation', pillars: { smart: 'Intelligent', efficient: 'Efficace', fun: 'Intéressant' }, illustration: 'illustration', avoid: ['multiagent', 'flux de travail'] },
27
+ 'es-ES': { agent: 'agente', multiAgent: 'multiagente', workflow: 'flujos de trabajo', documentation: 'documentación', pillars: { smart: 'Inteligente', efficient: 'Eficiente', fun: 'Interesante' }, illustration: 'ilustración', avoid: ['multi-agente', 'workflow'] },
28
+ 'pt-BR': { agent: 'agente', multiAgent: 'multiagente', workflow: 'fluxos de trabalho', documentation: 'documentação', pillars: { smart: 'Inteligente', efficient: 'Eficiente', fun: 'Interessante' }, illustration: 'ilustração', avoid: ['multi-agente', 'workflow'] },
29
+ 'ru-RU': { agent: 'агент', multiAgent: 'мультиагент', workflow: 'рабочие процессы', documentation: 'документаци', pillars: { smart: 'Умный', efficient: 'Эффективный', fun: 'Интересный' }, illustration: 'иллюстрация', avoid: ['мульти-агент'] },
30
30
  };
31
31
  function deepFreeze(value) {
32
32
  for (const child of Object.values(value)) {
@@ -28,6 +28,11 @@ export default {
28
28
  caption: 'Hero Scenario:ヒーローが作業を進め、進行状況・レベル・物語を見守れます。',
29
29
  alt: 'HagiCode の Hero Scenario パネル「Ember Keep」。実行中の 3 人のヒーローのレベル、進行状況、物語のテキストが並ぶ。',
30
30
  },
31
+ {
32
+ id: 'cat-head',
33
+ caption: 'イラスト:HagiCode のイラスト集にある猫の頭部の肖像。訪問するたびに入れ替わります。',
34
+ alt: 'HagiCode のイラスト集から。様式化された猫の頭部のイラストです。',
35
+ },
31
36
  ],
32
37
  windowsLabel: 'Windows 版をダウンロード',
33
38
  windowsStoreLabel: 'Microsoft Store で入手',
@@ -28,6 +28,11 @@ export default {
28
28
  caption: 'Hero Scenario: 영웅이 작업을 진행하고, 당신은 진행 상황과 레벨, 이야기를 지켜봅니다.',
29
29
  alt: 'Ember Keep이라는 이름의 HagiCode Hero Scenario 패널. 실행 중인 영웅 세 명의 레벨, 진행률, 이야기 문구가 표시됩니다.',
30
30
  },
31
+ {
32
+ id: 'cat-head',
33
+ caption: '일러스트: HagiCode 일러스트 세트의 고양이 머리 초상. 방문할 때마다 바뀝니다.',
34
+ alt: 'HagiCode 일러스트 세트의 양식화된 고양이 머리 일러스트.',
35
+ },
31
36
  ],
32
37
  windowsLabel: 'Windows용 다운로드',
33
38
  windowsStoreLabel: 'Microsoft Store에서 받기',
@@ -28,6 +28,11 @@ export default {
28
28
  caption: 'Hero Scenario: os heróis tocam o trabalho enquanto você acompanha progresso, níveis e história.',
29
29
  alt: 'Painel Hero Scenario do HagiCode chamado Ember Keep, com três heróis em execução e seu nível, progresso e texto da história.',
30
30
  },
31
+ {
32
+ id: 'cat-head',
33
+ caption: 'Ilustração: um retrato de cabeça de gato do conjunto de ilustrações do HagiCode, que muda a cada visita.',
34
+ alt: 'Ilustração estilizada de uma cabeça de gato do conjunto de ilustrações do HagiCode.',
35
+ },
31
36
  ],
32
37
  windowsLabel: 'Baixar para Windows',
33
38
  windowsStoreLabel: 'Obter na Microsoft Store',
@@ -28,6 +28,11 @@ export default {
28
28
  caption: 'Hero Scenario: герои ведут работу, а вы следите за прогрессом, уровнями и сюжетом.',
29
29
  alt: 'Панель Hero Scenario в HagiCode под названием Ember Keep: три работающих героя с уровнем, прогрессом и текстом сюжета.',
30
30
  },
31
+ {
32
+ id: 'cat-head',
33
+ caption: 'Иллюстрация: портрет головы кота из набора иллюстраций HagiCode, который меняется при каждом визите.',
34
+ alt: 'Стилизованная иллюстрация головы кота из набора иллюстраций HagiCode.',
35
+ },
31
36
  ],
32
37
  windowsLabel: 'Скачать для Windows',
33
38
  windowsStoreLabel: 'Получить в Microsoft Store',
@@ -28,6 +28,11 @@ export default {
28
28
  caption: 'Hero Scenario:英雄推进工作,你随时查看进度、等级与剧情。',
29
29
  alt: 'HagiCode Hero Scenario 面板截图,名为 Ember Keep,列出三位运行中的英雄及其等级、进度与剧情文字。',
30
30
  },
31
+ {
32
+ id: 'cat-head',
33
+ caption: '插图:HagiCode 插画集中的一幅猫头像,每次访问会换一幅。',
34
+ alt: '风格化的猫头插图,来自 HagiCode 插画集。',
35
+ },
31
36
  ],
32
37
  windowsLabel: '下载 Windows 版',
33
38
  windowsStoreLabel: '从 Microsoft Store 获取',
@@ -28,6 +28,11 @@ export default {
28
28
  caption: 'Hero Scenario:英雄推進工作,你隨時查看進度、等級與劇情。',
29
29
  alt: 'HagiCode Hero Scenario 面板畫面,名為 Ember Keep,列出三位執行中的英雄及其等級、進度與劇情文字。',
30
30
  },
31
+ {
32
+ id: 'cat-head',
33
+ caption: '插圖:HagiCode 插畫集中的一幅貓頭像,每次造訪會換一幅。',
34
+ alt: '風格化的貓頭插圖,來自 HagiCode 插畫集。',
35
+ },
31
36
  ],
32
37
  windowsLabel: '下載 Windows 版',
33
38
  windowsStoreLabel: '從 Microsoft Store 取得',
package/dist/index.d.ts CHANGED
@@ -6,6 +6,16 @@ import { type SeoOrganization } from './seo-utils.js';
6
6
  export interface HagilightToggleOptions {
7
7
  enabled?: boolean;
8
8
  }
9
+ export interface HagilightPromotionOptions extends HagilightToggleOptions {
10
+ /** Swap the showcase illustration for one random pool entry per page view. Defaults to `true`. */
11
+ randomAvatar?: boolean;
12
+ }
13
+ export interface HagilightFooterOptions {
14
+ /** Show the decorative avatar in the footer brand block. Defaults to `true`. */
15
+ avatar?: boolean;
16
+ /** Swap the footer avatar for one random pool entry per page view. Defaults to `true`. */
17
+ randomAvatar?: boolean;
18
+ }
9
19
  export interface HagilightGoogleAnalyticsOptions {
10
20
  /** Defaults to `true` when the provider options are omitted, otherwise to whether `measurementId` is set. */
11
21
  enabled?: boolean;
@@ -80,8 +90,10 @@ export interface HagilightStarlightOptions {
80
90
  aiDisclosures?: HagilightAIDisclosureOptions;
81
91
  /** Theme picker with three extra light/dark themes and a Forest first-visit default. */
82
92
  themes?: HagilightThemesOptions;
93
+ /** Footer brand block options: the decorative avatar and its random pool. */
94
+ footer?: HagilightFooterOptions;
83
95
  /** End-of-article HagiCode introduction. */
84
- hagicodePromotion?: HagilightToggleOptions;
96
+ hagicodePromotion?: HagilightPromotionOptions;
85
97
  /** Floating promotion banner rendered after the footer. */
86
98
  promoto?: HagilightToggleOptions;
87
99
  /** Body-image baseline (`loading`, `decoding`, marker class) for Markdown and MDX. Defaults to enabled. */
package/dist/index.js CHANGED
@@ -2,6 +2,7 @@ import { randomUUID } from 'node:crypto';
2
2
  import { fileURLToPath } from 'node:url';
3
3
  import { resolveFaviconHeadEntry } from '@hagicode/hagilight-core/favicon';
4
4
  import { resolveBuildInfo } from '@hagicode/hagilight-core/build-info-resolver';
5
+ import { resolveFooterAvatarOptions } from '@hagicode/hagilight-core/footer-avatar';
5
6
  import { checkContentImageRegistration, registerContentImages } from '@hagicode/hagilight-core/content-images';
6
7
  import { DEFAULT_POSTHOG_API_KEY, POSTHOG_API_KEY_PATTERN } from '@hagicode/hagilight-core/posthog';
7
8
  import { resolveRssLocales } from '@hagicode/hagilight-core/rss';
@@ -105,6 +106,14 @@ function resolveHagicodePromotion(config) {
105
106
  }
106
107
  return config.enabled ?? true;
107
108
  }
109
+ function resolveHagicodePromotionRandomAvatar(config) {
110
+ if (config === undefined || !isRecord(config))
111
+ return true;
112
+ if (config.randomAvatar !== undefined && typeof config.randomAvatar !== 'boolean') {
113
+ throw new TypeError('Hagilight hagicodePromotion randomAvatar option must be a boolean.');
114
+ }
115
+ return config.randomAvatar ?? true;
116
+ }
108
117
  function getRssFeedUrl(config) {
109
118
  if (!Array.isArray(config.head))
110
119
  return undefined;
@@ -247,6 +256,8 @@ import options from '${optionsId}';
247
256
  rssFeedUrl={options.rssFeedUrl}
248
257
  rssLocaleFeedUrls={options.rssLocaleFeedUrls}
249
258
  buildInfo={options.buildInfo}
259
+ avatar={options.footerAvatar.avatar}
260
+ randomAvatar={options.footerAvatar.randomAvatar}
250
261
  />
251
262
  ${promotionRender}
252
263
  ${analyticsRender}
@@ -265,6 +276,7 @@ import options from '${optionsId}';
265
276
  <MarkdownContent
266
277
  aiDisclosures={options.aiDisclosures}
267
278
  hagicodePromotionEnabled={options.hagicodePromotionEnabled}
279
+ hagicodePromotionRandomAvatar={options.hagicodePromotionRandomAvatar}
268
280
  ><slot /></MarkdownContent>
269
281
  `;
270
282
  const headSource = `---
@@ -373,6 +385,12 @@ export default function hagilight(options = {}) {
373
385
  const markdownContentEnabled = optionalBoolean(contentComponents, 'markdownContent', 'contentComponents markdownContent') !== false;
374
386
  const aiDisclosures = resolveAIDisclosures(input.aiDisclosures);
375
387
  const hagicodePromotionEnabled = resolveHagicodePromotion(input.hagicodePromotion);
388
+ const hagicodePromotionRandomAvatar = resolveHagicodePromotionRandomAvatar(input.hagicodePromotion);
389
+ const footerOptions = optionalRecord(input.footer, 'footer');
390
+ const footerAvatar = resolveFooterAvatarOptions({
391
+ avatar: optionalBoolean(footerOptions, 'avatar', 'footer avatar'),
392
+ randomAvatar: optionalBoolean(footerOptions, 'randomAvatar', 'footer randomAvatar'),
393
+ });
376
394
  const googleAnalyticsMeasurementId = resolveProvider(analytics.googleAnalytics, {
377
395
  name: 'Google Analytics',
378
396
  idKey: 'measurementId',
@@ -418,6 +436,8 @@ export default function hagilight(options = {}) {
418
436
  const serializedOptions = {
419
437
  promotoEnabled,
420
438
  hagicodePromotionEnabled,
439
+ hagicodePromotionRandomAvatar,
440
+ footerAvatar,
421
441
  links: options.links ?? {},
422
442
  googleAnalyticsMeasurementId,
423
443
  fiftyOneLaId,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hagicode/hagilight-starlight",
3
- "version": "0.14.1-dev.144.1.bcc5ef8",
3
+ "version": "0.14.1-dev.149.1.1dadf5c",
4
4
  "type": "module",
5
5
  "description": "Starlight plugin and components for HagiCode documentation",
6
6
  "repository": {
@@ -53,7 +53,7 @@
53
53
  "assets/showcase/*"
54
54
  ],
55
55
  "dependencies": {
56
- "@hagicode/hagilight-core": "0.14.1-dev.144.1.bcc5ef8"
56
+ "@hagicode/hagilight-core": "0.14.1-dev.149.1.1dadf5c"
57
57
  },
58
58
  "peerDependencies": {
59
59
  "@astrojs/starlight": "^0.42.4",