@hagicode/hagilight-starlight 0.1.0-dev.148.1.e0c630e

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.
Files changed (81) hide show
  1. package/AIDisclosureNotice.astro +12 -0
  2. package/ArticlePromotion.astro +630 -0
  3. package/ArticlePromotionPicker.astro +9 -0
  4. package/ContentLayoutToggle.astro +31 -0
  5. package/Footer.astro +201 -0
  6. package/Header.astro +131 -0
  7. package/LanguageChooser.astro +386 -0
  8. package/MarkdownContent.astro +45 -0
  9. package/NotFoundHero.astro +44 -0
  10. package/PageTitle.astro +7 -0
  11. package/PromotoFooter.astro +7 -0
  12. package/README.md +178 -0
  13. package/SEOHead.astro +67 -0
  14. package/ThemeSelect.astro +176 -0
  15. package/assets/showcase/hero-avatar.webp +0 -0
  16. package/assets/showcase/heroes.webp +0 -0
  17. package/assets/showcase/manifest.json +333 -0
  18. package/assets/showcase/pillar-efficient.svg +1 -0
  19. package/assets/showcase/pillar-fun.svg +1 -0
  20. package/assets/showcase/pillar-smart.svg +1 -0
  21. package/assets/showcase/proposal-workflow.webp +0 -0
  22. package/assets/showcase/workbench.webp +0 -0
  23. package/content-width.css +81 -0
  24. package/dist/ai-disclosures.d.ts +31 -0
  25. package/dist/ai-disclosures.js +105 -0
  26. package/dist/article-promotion-copy/de-DE.d.ts +34 -0
  27. package/dist/article-promotion-copy/de-DE.js +43 -0
  28. package/dist/article-promotion-copy/en-US.d.ts +34 -0
  29. package/dist/article-promotion-copy/en-US.js +43 -0
  30. package/dist/article-promotion-copy/es-ES.d.ts +34 -0
  31. package/dist/article-promotion-copy/es-ES.js +43 -0
  32. package/dist/article-promotion-copy/fr-FR.d.ts +34 -0
  33. package/dist/article-promotion-copy/fr-FR.js +43 -0
  34. package/dist/article-promotion-copy/index.d.ts +32 -0
  35. package/dist/article-promotion-copy/index.js +50 -0
  36. package/dist/article-promotion-copy/ja-JP.d.ts +34 -0
  37. package/dist/article-promotion-copy/ja-JP.js +43 -0
  38. package/dist/article-promotion-copy/ko-KR.d.ts +34 -0
  39. package/dist/article-promotion-copy/ko-KR.js +43 -0
  40. package/dist/article-promotion-copy/pt-BR.d.ts +34 -0
  41. package/dist/article-promotion-copy/pt-BR.js +43 -0
  42. package/dist/article-promotion-copy/ru-RU.d.ts +34 -0
  43. package/dist/article-promotion-copy/ru-RU.js +43 -0
  44. package/dist/article-promotion-copy/zh-CN.d.ts +34 -0
  45. package/dist/article-promotion-copy/zh-CN.js +43 -0
  46. package/dist/article-promotion-copy/zh-Hant.d.ts +34 -0
  47. package/dist/article-promotion-copy/zh-Hant.js +43 -0
  48. package/dist/article-promotion.d.ts +40 -0
  49. package/dist/article-promotion.js +14 -0
  50. package/dist/content-width-i18n.d.ts +6 -0
  51. package/dist/content-width-i18n.js +15 -0
  52. package/dist/content-width.d.ts +5 -0
  53. package/dist/content-width.js +42 -0
  54. package/dist/index.d.ts +105 -0
  55. package/dist/index.js +583 -0
  56. package/dist/language-routing.d.ts +18 -0
  57. package/dist/language-routing.js +95 -0
  58. package/dist/locales.d.ts +48 -0
  59. package/dist/locales.js +13 -0
  60. package/dist/not-found.d.ts +9 -0
  61. package/dist/not-found.js +28 -0
  62. package/dist/rss-renderer.d.ts +14 -0
  63. package/dist/rss-renderer.js +30 -0
  64. package/dist/rss-utils.d.ts +27 -0
  65. package/dist/rss-utils.js +65 -0
  66. package/dist/schema.d.ts +32 -0
  67. package/dist/schema.js +25 -0
  68. package/dist/seo-utils.d.ts +74 -0
  69. package/dist/seo-utils.js +219 -0
  70. package/dist/showcase.d.ts +6 -0
  71. package/dist/showcase.js +5 -0
  72. package/dist/site-metadata.d.ts +38 -0
  73. package/dist/site-metadata.js +109 -0
  74. package/dist/themes.d.ts +43 -0
  75. package/dist/themes.js +156 -0
  76. package/dist/windows-download.d.ts +4 -0
  77. package/dist/windows-download.js +22 -0
  78. package/package.json +88 -0
  79. package/rss.[language].xml.ts +20 -0
  80. package/rss.xml.ts +15 -0
  81. package/themes.css +160 -0
@@ -0,0 +1,12 @@
1
+ ---
2
+ import { getAIDisclosureCopy } from './dist/ai-disclosures.js';
3
+ const { kind, lang, sourceHref } = Astro.props;
4
+ const copy = getAIDisclosureCopy(lang);
5
+ ---
6
+
7
+ <aside class="hagilight-ai-disclosure" role="note">
8
+ {kind === 'author' ? copy.author : copy.translation}
9
+ {kind === 'translation' && sourceHref && (
10
+ <> <a href={sourceHref}>{copy.source}</a></>
11
+ )}
12
+ </aside>
@@ -0,0 +1,630 @@
1
+ ---
2
+ import { Picture } from 'astro:assets';
3
+ import { gaEventAttributes } from '@hagicode/hagilight-core/analytics-events';
4
+ import { resolveSiteLinks } from '@hagicode/hagilight-core/links';
5
+ import { getArticlePromotionCopy } from './dist/article-promotion.js';
6
+ import { randomPoolAttribute, randomPoolFromManifest } from '@hagicode/hagilight-core/random-image';
7
+ import {
8
+ resolveMicrosoftStoreBadgeLanguage,
9
+ resolveMicrosoftStoreProductId,
10
+ } from './dist/windows-download.js';
11
+ import heroAvatar from './assets/showcase/hero-avatar.webp';
12
+ import heroes from './assets/showcase/heroes.webp';
13
+ import pillarEfficient from './assets/showcase/pillar-efficient.svg';
14
+ import pillarFun from './assets/showcase/pillar-fun.svg';
15
+ import pillarSmart from './assets/showcase/pillar-smart.svg';
16
+ import proposalWorkflow from './assets/showcase/proposal-workflow.webp';
17
+ import workbench from './assets/showcase/workbench.webp';
18
+ import showcaseManifest from './assets/showcase/manifest.json';
19
+ import ArticlePromotionPicker from './ArticlePromotionPicker.astro';
20
+
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.');
29
+ const copy = getArticlePromotionCopy(lang);
30
+ const siteLinks = resolveSiteLinks(lang);
31
+ const linkHref = (id: string, fallback: string) =>
32
+ [...siteLinks.header, ...siteLinks.quick].find((link) => link.id === id)?.href ?? fallback;
33
+ const homeHref = linkHref('home', 'https://www.hagicode.com/');
34
+ const docsHref = linkHref('productDocs', 'https://docs.hagicode.com/');
35
+ const storeHref = linkHref('microsoftStore', 'https://apps.microsoft.com/detail/9N3PM0N3SVDW');
36
+ const downloadsHref = linkHref('downloadClient', 'https://www.hagicode.com/');
37
+ const storeProductId = resolveMicrosoftStoreProductId(storeHref);
38
+ const badgeLanguage = resolveMicrosoftStoreBadgeLanguage(lang);
39
+ const showcaseTag = (category: 'download' | 'store' | 'navigation', label: string, url?: string) =>
40
+ gaEventAttributes({ category, label, location: 'article_promotion', url });
41
+
42
+ // Keyed by the ids in assets/showcase/manifest.json and in the copy catalog's gallery.
43
+ const galleryImages: Record<string, ImageMetadata> = {
44
+ workbench,
45
+ 'proposal-workflow': proposalWorkflow,
46
+ heroes,
47
+ 'hero-avatar': heroAvatar,
48
+ };
49
+ // The illustration slot is the only gallery image that may change; its pool lives in the showcase manifest.
50
+ const AVATAR_ID = 'hero-avatar';
51
+ const avatarEntry = showcaseManifest.assets.find(({ id }) => id === AVATAR_ID);
52
+ const avatarPool = randomAvatar && avatarEntry && 'pool' in avatarEntry ? randomPoolFromManifest(avatarEntry.pool) : undefined;
53
+ const pillarIcons: Record<string, ImageMetadata> = {
54
+ smart: pillarSmart,
55
+ efficient: pillarEfficient,
56
+ fun: pillarFun,
57
+ };
58
+ const gallery = copy.gallery.flatMap((item) => {
59
+ const image = galleryImages[item.id];
60
+ return image ? [{ ...item, image }] : [];
61
+ });
62
+ ---
63
+
64
+ <section class="hagilight-article-promotion" aria-labelledby="hagilight-article-promotion-title">
65
+ <div class="hagilight-article-promotion__pitch">
66
+ <div class="hagilight-article-promotion__intro">
67
+ <h2 id="hagilight-article-promotion-title">{copy.title}</h2>
68
+ <p class="hagilight-article-promotion__lead">{copy.lead}</p>
69
+ <p class="hagilight-article-promotion__tagline">{copy.subheadline}</p>
70
+ <div class="hagilight-article-promotion__actions">
71
+ <a
72
+ class="hagilight-article-promotion__cta hagilight-article-promotion__cta--primary"
73
+ href={homeHref}
74
+ {...showcaseTag('navigation', 'home')}
75
+ >
76
+ <span>{copy.visitLabel}</span>
77
+ <svg class="hagilight-article-promotion__arrow" aria-hidden="true" focusable="false" viewBox="0 0 20 20" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 10h11M11 5l5 5-5 5" /></svg>
78
+ </a>
79
+ <a
80
+ class="hagilight-article-promotion__cta hagilight-article-promotion__cta--secondary"
81
+ href={docsHref}
82
+ {...showcaseTag('navigation', 'productDocs')}
83
+ >
84
+ {copy.docsLabel}
85
+ </a>
86
+ </div>
87
+ </div>
88
+
89
+ <ul class="hagilight-article-promotion__features" aria-label={copy.title}>
90
+ {copy.features.map((feature) => (
91
+ <li>
92
+ <span
93
+ class="hagilight-article-promotion__icon"
94
+ aria-hidden="true"
95
+ style={`--hagilight-pillar-icon: url(${pillarIcons[feature.id]?.src})`}
96
+ ></span>
97
+ <strong>{feature.label}</strong>
98
+ <span class="hagilight-article-promotion__feature-text">{feature.description}</span>
99
+ </li>
100
+ ))}
101
+ </ul>
102
+ </div>
103
+
104
+ <div class="hagilight-article-promotion__gallery-block">
105
+ <h3 id="hagilight-article-promotion-gallery-title">{copy.galleryHeading}</h3>
106
+ <ul class="hagilight-article-promotion__gallery" aria-labelledby="hagilight-article-promotion-gallery-title">
107
+ {gallery.map((item) => (
108
+ <li>
109
+ <figure data-random-pool={item.id === AVATAR_ID && avatarPool ? randomPoolAttribute(avatarPool) : undefined}>
110
+ <Picture
111
+ src={item.image}
112
+ alt={item.alt}
113
+ width={item.image.width}
114
+ height={item.image.height}
115
+ formats={['avif', 'webp']}
116
+ fallbackFormat="jpeg"
117
+ widths={[480, 800, item.image.width]}
118
+ sizes="(min-width: 50rem) 560px, 100vw"
119
+ loading="lazy"
120
+ decoding="async"
121
+ fetchpriority="low"
122
+ />
123
+ <figcaption>{item.caption}</figcaption>
124
+ </figure>
125
+ </li>
126
+ ))}
127
+ </ul>
128
+ </div>
129
+
130
+ {avatarPool && <ArticlePromotionPicker />}
131
+
132
+ <div class="hagilight-article-promotion__closing">
133
+ <div class="hagilight-article-promotion__windows">
134
+ <h3 id="hagilight-article-promotion-windows-title" class="hagilight-article-promotion__windows-label">{copy.windowsLabel}</h3>
135
+ <div class="hagilight-article-promotion__windows-actions">
136
+ <ms-store-badge
137
+ productid={storeProductId}
138
+ productname="HagiCode"
139
+ window-mode="direct"
140
+ theme="auto"
141
+ size="large"
142
+ animation="on"
143
+ language={badgeLanguage}
144
+ aria-label={copy.windowsStoreAriaLabel}
145
+ title={copy.windowsStoreAriaLabel}
146
+ {...showcaseTag('store', 'microsoftStore', storeHref)}
147
+ >
148
+ <a
149
+ class="hagilight-article-promotion__store-fallback"
150
+ href={storeHref}
151
+ aria-label={copy.windowsStoreAriaLabel}
152
+ title={copy.windowsStoreAriaLabel}
153
+ >{copy.windowsStoreLabel}</a>
154
+ </ms-store-badge>
155
+ <a
156
+ class="hagilight-article-promotion__downloads"
157
+ href={downloadsHref}
158
+ {...showcaseTag('download', 'downloadClient')}
159
+ ><span>{copy.allDownloadsLabel}</span><svg class="hagilight-article-promotion__arrow" aria-hidden="true" focusable="false" viewBox="0 0 20 20" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 10h11M11 5l5 5-5 5" /></svg></a>
160
+ </div>
161
+ </div>
162
+
163
+ <div class="hagilight-article-promotion__share">
164
+ <h3 id="hagilight-article-promotion-share-title">{copy.shareLabel}</h3>
165
+ <p class="hagilight-article-promotion__share-text">{copy.shareText}</p>
166
+ </div>
167
+ </div>
168
+ </section>
169
+ <script type="module" src="https://get.microsoft.com/badge/ms-store-badge.bundled.js" is:inline></script>
170
+
171
+ <style>
172
+ /*
173
+ * One stage, four beats, top to bottom: pitch (what it is, the way in, the three pillars),
174
+ * gallery (proof), closing band (get it, share it). The stage is tinted from the active theme's
175
+ * accent so it reads as a deliberate HagiCode moment against the article's neutral page, and the
176
+ * closing band bleeds to the stage edges so "get it" is the last, heaviest surface. Pillars are
177
+ * separated by accent rules rather than boxes. The topology follows the section's own width, so
178
+ * the wide-content toggle and the sidebar layouts reflow it, not just the viewport. Mobile-first:
179
+ * the base rules are the single-column layout, which is also what a browser without container
180
+ * queries renders. Every color derives from Starlight's tokens, so all themes and both modes work.
181
+ */
182
+ .hagilight-article-promotion {
183
+ --hagilight-promo-pad: 1.5rem;
184
+ --hagilight-promo-edge: color-mix(in srgb, var(--sl-color-text-accent) 38%, var(--sl-color-gray-5));
185
+ --hagilight-promo-tint: color-mix(in srgb, var(--sl-color-accent-low) 55%, var(--sl-color-bg-nav));
186
+ position: relative;
187
+ container-type: inline-size;
188
+ display: grid;
189
+ grid-template-columns: minmax(0, 1fr);
190
+ gap: 2.25rem;
191
+ margin: 3rem auto 2rem;
192
+ padding: calc(var(--hagilight-promo-pad) + 0.25rem) var(--hagilight-promo-pad) var(--hagilight-promo-pad);
193
+ overflow: clip;
194
+ border: 1px solid var(--hagilight-promo-edge);
195
+ border-radius: var(--sl-radius-lg, 0.75rem);
196
+ background:
197
+ radial-gradient(70% 45% at 0% 0%, color-mix(in srgb, var(--sl-color-text-accent) 16%, transparent), transparent 70%),
198
+ linear-gradient(180deg, var(--hagilight-promo-tint), var(--sl-color-bg-nav) 55%);
199
+ color: var(--sl-color-text);
200
+ }
201
+
202
+ /* The accent rule across the top edge: the stage's signature, drawn in on scroll where supported. */
203
+ .hagilight-article-promotion::before {
204
+ content: '';
205
+ position: absolute;
206
+ inset: 0 0 auto;
207
+ height: 4px;
208
+ background: linear-gradient(90deg, var(--sl-color-text-accent), color-mix(in srgb, var(--sl-color-text-accent) 20%, transparent));
209
+ transform-origin: left center;
210
+ pointer-events: none;
211
+ }
212
+
213
+ .hagilight-article-promotion ::selection {
214
+ background: color-mix(in srgb, var(--sl-color-text-accent) 38%, transparent);
215
+ color: var(--sl-color-white);
216
+ }
217
+
218
+ .hagilight-article-promotion h2,
219
+ .hagilight-article-promotion h3 {
220
+ margin: 0;
221
+ color: var(--sl-color-white);
222
+ line-height: 1.2;
223
+ text-wrap: balance;
224
+ }
225
+
226
+ .hagilight-article-promotion h2 {
227
+ font-size: clamp(2rem, 1.3rem + 3.2cqi, 3rem);
228
+ font-weight: 800;
229
+ letter-spacing: -0.02em;
230
+ }
231
+
232
+ .hagilight-article-promotion h3 {
233
+ font-size: var(--sl-text-xl, 1.25rem);
234
+ font-weight: 700;
235
+ }
236
+
237
+ .hagilight-article-promotion p {
238
+ margin: 0;
239
+ line-height: 1.6;
240
+ text-wrap: pretty;
241
+ }
242
+
243
+ .hagilight-article-promotion ul {
244
+ margin: 0;
245
+ padding: 0;
246
+ list-style: none;
247
+ }
248
+
249
+ .hagilight-article-promotion a:focus-visible {
250
+ outline: 2px solid var(--sl-color-text-accent);
251
+ outline-offset: 3px;
252
+ }
253
+
254
+ /* Pitch: headline, one emphasized promise, the way in, then the pillars. */
255
+ .hagilight-article-promotion__pitch {
256
+ display: grid;
257
+ gap: 2rem;
258
+ }
259
+
260
+ .hagilight-article-promotion__intro {
261
+ display: grid;
262
+ align-content: start;
263
+ justify-items: start;
264
+ gap: 0.875rem;
265
+ }
266
+
267
+ .hagilight-article-promotion__lead,
268
+ .hagilight-article-promotion__tagline {
269
+ max-inline-size: 68ch;
270
+ }
271
+
272
+ .hagilight-article-promotion__lead {
273
+ color: var(--sl-color-gray-2);
274
+ }
275
+
276
+ .hagilight-article-promotion__tagline {
277
+ color: var(--sl-color-white);
278
+ font-size: var(--sl-text-lg, 1.125rem);
279
+ font-weight: 650;
280
+ line-height: 1.45;
281
+ }
282
+
283
+ .hagilight-article-promotion__actions {
284
+ display: flex;
285
+ flex-wrap: wrap;
286
+ align-items: center;
287
+ gap: 0.75rem;
288
+ margin-block-start: 0.5rem;
289
+ }
290
+
291
+ .hagilight-article-promotion__cta {
292
+ display: inline-flex;
293
+ align-items: center;
294
+ justify-content: center;
295
+ gap: 0.5rem;
296
+ min-height: 2.75rem;
297
+ padding: 0.5rem 1.25rem;
298
+ border: 1px solid var(--sl-color-text-accent);
299
+ border-radius: var(--sl-radius-sm, 0.375rem);
300
+ font-weight: 700;
301
+ text-decoration: none;
302
+ transition: transform 0.2s cubic-bezier(0.22, 1, 0.36, 1), box-shadow 0.2s cubic-bezier(0.22, 1, 0.36, 1), background-color 0.2s;
303
+ }
304
+
305
+ /* The one loud control: bigger than its sibling, lifted, with an arrow that leans forward on hover. */
306
+ .hagilight-article-promotion__cta--primary {
307
+ min-height: 3.25rem;
308
+ padding-inline: 1.625rem;
309
+ background: var(--sl-color-text-accent);
310
+ color: var(--sl-color-bg);
311
+ font-size: var(--sl-text-lg, 1.125rem);
312
+ box-shadow: 0 8px 20px -8px color-mix(in srgb, var(--sl-color-text-accent) 70%, transparent);
313
+ }
314
+
315
+ .hagilight-article-promotion__cta--secondary {
316
+ background: transparent;
317
+ color: var(--sl-color-white);
318
+ border-color: var(--sl-color-gray-3);
319
+ }
320
+
321
+ .hagilight-article-promotion__arrow {
322
+ flex: none;
323
+ transition: transform 0.2s cubic-bezier(0.22, 1, 0.36, 1);
324
+ }
325
+
326
+ .hagilight-article-promotion__cta--primary:hover {
327
+ transform: translateY(-2px);
328
+ box-shadow: 0 12px 24px -8px color-mix(in srgb, var(--sl-color-text-accent) 80%, transparent);
329
+ }
330
+
331
+ .hagilight-article-promotion__cta--primary:active {
332
+ transform: translateY(0);
333
+ }
334
+
335
+ .hagilight-article-promotion__cta--secondary:hover {
336
+ border-color: var(--sl-color-text-accent);
337
+ color: var(--sl-color-text-accent);
338
+ }
339
+
340
+ .hagilight-article-promotion__cta:hover .hagilight-article-promotion__arrow,
341
+ .hagilight-article-promotion__downloads:hover .hagilight-article-promotion__arrow {
342
+ transform: translateX(3px);
343
+ }
344
+
345
+ /* Pillars: each opens with an accent rule; the pillar word is the loudest text after the title. */
346
+ .hagilight-article-promotion__features {
347
+ display: grid;
348
+ gap: 1.25rem 1.5rem;
349
+ }
350
+
351
+ .hagilight-article-promotion__features li {
352
+ display: grid;
353
+ grid-template-columns: auto minmax(0, 1fr);
354
+ align-content: start;
355
+ align-items: center;
356
+ gap: 0.625rem 0.75rem;
357
+ min-width: 0;
358
+ padding-block-start: 1rem;
359
+ border-block-start: 2px solid color-mix(in srgb, var(--sl-color-text-accent) 55%, transparent);
360
+ }
361
+
362
+ .hagilight-article-promotion__features strong {
363
+ color: var(--sl-color-text-accent);
364
+ font-size: var(--sl-text-xl, 1.25rem);
365
+ font-weight: 750;
366
+ line-height: 1.2;
367
+ }
368
+
369
+ .hagilight-article-promotion__feature-text {
370
+ grid-column: 1 / -1;
371
+ font-size: var(--sl-text-sm, 0.875rem);
372
+ line-height: 1.55;
373
+ }
374
+
375
+ .hagilight-article-promotion__icon {
376
+ position: relative;
377
+ display: grid;
378
+ place-items: center;
379
+ width: 2.5rem;
380
+ height: 2.5rem;
381
+ border-radius: var(--sl-radius-md, 0.5rem);
382
+ background: color-mix(in srgb, var(--sl-color-text-accent) 18%, transparent);
383
+ }
384
+
385
+ .hagilight-article-promotion__icon::after {
386
+ content: '';
387
+ width: 1.5rem;
388
+ height: 1.5rem;
389
+ background: var(--sl-color-text-accent);
390
+ mask: var(--hagilight-pillar-icon) center / contain no-repeat;
391
+ -webkit-mask: var(--hagilight-pillar-icon) center / contain no-repeat;
392
+ }
393
+
394
+ /* Gallery: three audited screenshots, kept in the catalog's narrative order. */
395
+ .hagilight-article-promotion__gallery-block {
396
+ display: grid;
397
+ gap: 1.125rem;
398
+ }
399
+
400
+ .hagilight-article-promotion__gallery {
401
+ display: grid;
402
+ gap: 1.5rem 1.25rem;
403
+ }
404
+
405
+ .hagilight-article-promotion__gallery li {
406
+ min-width: 0;
407
+ }
408
+
409
+ .hagilight-article-promotion figure {
410
+ display: grid;
411
+ gap: 0.625rem;
412
+ margin: 0;
413
+ }
414
+
415
+ .hagilight-article-promotion picture {
416
+ display: block;
417
+ }
418
+
419
+ .hagilight-article-promotion img {
420
+ display: block;
421
+ width: 100%;
422
+ height: auto;
423
+ border: 1px solid var(--hagilight-promo-edge);
424
+ border-radius: var(--sl-radius-md, 0.5rem);
425
+ box-shadow: 0 18px 32px -18px color-mix(in srgb, var(--sl-color-black) 80%, transparent);
426
+ transition: transform 0.35s cubic-bezier(0.22, 1, 0.36, 1), border-color 0.35s;
427
+ }
428
+
429
+ .hagilight-article-promotion figure:hover img {
430
+ transform: translateY(-3px);
431
+ border-color: var(--sl-color-text-accent);
432
+ }
433
+
434
+ .hagilight-article-promotion figcaption {
435
+ color: var(--sl-color-gray-1);
436
+ font-size: var(--sl-text-sm, 0.875rem);
437
+ line-height: 1.5;
438
+ }
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
+
445
+ /* Closing: a full-bleed band, the heaviest surface, so "get it" is where the eye lands last. */
446
+ .hagilight-article-promotion__closing {
447
+ display: grid;
448
+ gap: 1.75rem;
449
+ margin: 0 calc(var(--hagilight-promo-pad) * -1) calc(var(--hagilight-promo-pad) * -1);
450
+ padding: var(--hagilight-promo-pad);
451
+ border-block-start: 1px solid var(--hagilight-promo-edge);
452
+ background: color-mix(in srgb, var(--sl-color-accent-low) 80%, var(--sl-color-bg));
453
+ }
454
+
455
+ .hagilight-article-promotion__windows,
456
+ .hagilight-article-promotion__share {
457
+ display: grid;
458
+ align-content: start;
459
+ justify-items: start;
460
+ gap: 0.625rem;
461
+ min-width: 0;
462
+ }
463
+
464
+ /* Reserve the large badge's height (104px) so it replaces the fallback link without a layout jump. */
465
+ .hagilight-article-promotion__windows-actions {
466
+ display: flex;
467
+ flex-wrap: wrap;
468
+ align-items: center;
469
+ gap: 0 1.5rem;
470
+ max-width: 100%;
471
+ }
472
+
473
+ .hagilight-article-promotion__windows ms-store-badge {
474
+ display: inline-flex;
475
+ align-items: center;
476
+ max-width: 100%;
477
+ min-height: 6.5rem;
478
+ }
479
+
480
+ /* Keep the badge's fixed height and shrink it to the slot on narrow screens without distortion. */
481
+ .hagilight-article-promotion__windows ms-store-badge::part(img) {
482
+ max-width: 100%;
483
+ object-fit: contain;
484
+ object-position: left center;
485
+ }
486
+
487
+ .hagilight-article-promotion__store-fallback,
488
+ .hagilight-article-promotion__downloads {
489
+ display: inline-flex;
490
+ align-items: center;
491
+ gap: 0.375rem;
492
+ min-height: 2.75rem;
493
+ color: var(--sl-color-text-accent);
494
+ font-weight: 650;
495
+ text-decoration-thickness: 1px;
496
+ text-underline-offset: 0.25em;
497
+ }
498
+
499
+ .hagilight-article-promotion__share-text {
500
+ justify-self: stretch;
501
+ padding: 0.75rem 1rem;
502
+ border: 1px dashed var(--sl-color-text-accent);
503
+ border-radius: var(--sl-radius-md, 0.5rem);
504
+ background: var(--sl-color-bg);
505
+ overflow-wrap: anywhere;
506
+ user-select: all;
507
+ }
508
+
509
+ /* Medium: pillars become a three-up row, the gallery a mosaic, the closing two columns. */
510
+ @container (min-width: 36rem) {
511
+ .hagilight-article-promotion__features {
512
+ grid-template-columns: repeat(3, minmax(0, 1fr));
513
+ }
514
+
515
+ /*
516
+ * Mosaic in reading order: the workbench and the proposal strip stack in the left column
517
+ * beside the tall Hero Scenario panel. The 1.1fr split lands the image bottoms of both columns within
518
+ * a caption's height of each other for the current screenshots; the placement assumes exactly
519
+ * three gallery images.
520
+ */
521
+ .hagilight-article-promotion__gallery {
522
+ grid-template-columns: minmax(0, 1.1fr) minmax(0, 1fr);
523
+ align-items: start;
524
+ }
525
+
526
+ .hagilight-article-promotion__gallery li:nth-child(1) {
527
+ grid-column: 1;
528
+ grid-row: 1;
529
+ }
530
+
531
+ .hagilight-article-promotion__gallery li:nth-child(2) {
532
+ grid-column: 1;
533
+ grid-row: 2;
534
+ }
535
+
536
+ .hagilight-article-promotion__gallery li:nth-child(3) {
537
+ grid-column: 2;
538
+ grid-row: 1 / span 2;
539
+ }
540
+
541
+ .hagilight-article-promotion__closing {
542
+ grid-template-columns: repeat(2, minmax(0, 1fr));
543
+ gap: 2rem;
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
+ }
554
+ }
555
+
556
+ /* Wide: the pitch takes the left column and the pillars stack beside it. */
557
+ @container (min-width: 56rem) {
558
+ .hagilight-article-promotion__pitch {
559
+ grid-template-columns: minmax(0, 1.15fr) minmax(0, 1fr);
560
+ gap: 3rem;
561
+ align-items: start;
562
+ }
563
+
564
+ .hagilight-article-promotion__features {
565
+ grid-template-columns: minmax(0, 1fr);
566
+ }
567
+ }
568
+
569
+ @media (max-width: 30rem) {
570
+ .hagilight-article-promotion__actions {
571
+ flex-direction: column;
572
+ flex-wrap: nowrap;
573
+ align-items: stretch;
574
+ justify-self: stretch;
575
+ }
576
+ }
577
+
578
+ /*
579
+ * The one authored moment: as the stage scrolls into view its accent rule draws across and the
580
+ * screenshots rise into place one after another. Scroll-driven, so there is no JS and nothing
581
+ * replays; engines without view timelines, and readers who ask for less motion, get the final
582
+ * state. The animations start at 0.35 opacity, so a tall item that never reaches its full entry
583
+ * range is still readable.
584
+ */
585
+ @media (prefers-reduced-motion: no-preference) {
586
+ @supports (animation-timeline: view()) {
587
+ .hagilight-article-promotion::before {
588
+ animation-name: hagilight-promotion-rule;
589
+ animation-timing-function: linear;
590
+ animation-fill-mode: both;
591
+ animation-timeline: view();
592
+ animation-range: entry 0% entry 80%;
593
+ }
594
+
595
+ .hagilight-article-promotion__gallery li {
596
+ animation-name: hagilight-promotion-rise;
597
+ animation-timing-function: cubic-bezier(0.22, 1, 0.36, 1);
598
+ animation-fill-mode: both;
599
+ animation-timeline: view();
600
+ animation-range: entry 5% entry 45%;
601
+ }
602
+
603
+ .hagilight-article-promotion__gallery li:nth-child(2) {
604
+ animation-range: entry 12% entry 52%;
605
+ }
606
+
607
+ .hagilight-article-promotion__gallery li:nth-child(3) {
608
+ animation-range: entry 19% entry 59%;
609
+ }
610
+
611
+ .hagilight-article-promotion__gallery li:nth-child(4) {
612
+ animation-range: entry 26% entry 66%;
613
+ }
614
+ }
615
+ }
616
+
617
+ @keyframes hagilight-promotion-rule {
618
+ from {
619
+ transform: scaleX(0.04);
620
+ }
621
+ }
622
+
623
+ @keyframes hagilight-promotion-rise {
624
+ from {
625
+ opacity: 0.35;
626
+ translate: 0 1.75rem;
627
+ scale: 0.97;
628
+ }
629
+ }
630
+ </style>
@@ -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>
@@ -0,0 +1,31 @@
1
+ ---
2
+ import { getContentWidthLabels } from './dist/content-width-i18n.js';
3
+ const { lang } = Astro.locals.starlightRoute;
4
+ const labels = getContentWidthLabels(lang);
5
+ ---
6
+
7
+ <div class="hagilight-content-width-toggle" role="group" aria-label={labels.group}>
8
+ <button type="button" data-hagilight-content-width-choice="wide" aria-pressed="true">
9
+ {labels.wide}
10
+ </button>
11
+ <button type="button" data-hagilight-content-width-choice="narrow" aria-pressed="false">
12
+ {labels.narrow}
13
+ </button>
14
+ </div>
15
+
16
+ <script>
17
+ import {
18
+ restoreContentWidth,
19
+ setContentWidth,
20
+ synchronizeContentWidthFromStorage,
21
+ } from './dist/content-width.js';
22
+
23
+ const root = document.documentElement;
24
+ restoreContentWidth(root);
25
+ document.querySelectorAll('[data-hagilight-content-width-choice]').forEach((button) => {
26
+ button.addEventListener('click', () => {
27
+ setContentWidth(button.dataset.hagilightContentWidthChoice, root);
28
+ });
29
+ });
30
+ window.addEventListener('storage', (event) => synchronizeContentWidthFromStorage(event, root));
31
+ </script>