@hagicode/hagilight-starlight 0.11.1-dev.129.1.ea757b8 → 0.12.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/ArticlePromotion.astro +180 -41
- package/README.md +9 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.js +33 -3
- package/dist/site-metadata.d.ts +38 -0
- package/dist/site-metadata.js +109 -0
- package/package.json +2 -2
package/ArticlePromotion.astro
CHANGED
|
@@ -57,7 +57,8 @@ const gallery = copy.gallery.flatMap((item) => {
|
|
|
57
57
|
href={homeHref}
|
|
58
58
|
{...showcaseTag('navigation', 'home')}
|
|
59
59
|
>
|
|
60
|
-
{copy.visitLabel}
|
|
60
|
+
<span>{copy.visitLabel}</span>
|
|
61
|
+
<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>
|
|
61
62
|
</a>
|
|
62
63
|
<a
|
|
63
64
|
class="hagilight-article-promotion__cta hagilight-article-promotion__cta--secondary"
|
|
@@ -137,7 +138,7 @@ const gallery = copy.gallery.flatMap((item) => {
|
|
|
137
138
|
class="hagilight-article-promotion__downloads"
|
|
138
139
|
href={downloadsHref}
|
|
139
140
|
{...showcaseTag('download', 'downloadClient')}
|
|
140
|
-
>{copy.allDownloadsLabel}</a>
|
|
141
|
+
><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>
|
|
141
142
|
</div>
|
|
142
143
|
</div>
|
|
143
144
|
|
|
@@ -151,43 +152,74 @@ const gallery = copy.gallery.flatMap((item) => {
|
|
|
151
152
|
|
|
152
153
|
<style>
|
|
153
154
|
/*
|
|
154
|
-
*
|
|
155
|
-
* closing (get it, share it).
|
|
156
|
-
*
|
|
157
|
-
*
|
|
158
|
-
*
|
|
155
|
+
* One stage, four beats, top to bottom: pitch (what it is, the way in, the three pillars),
|
|
156
|
+
* gallery (proof), closing band (get it, share it). The stage is tinted from the active theme's
|
|
157
|
+
* accent so it reads as a deliberate HagiCode moment against the article's neutral page, and the
|
|
158
|
+
* closing band bleeds to the stage edges so "get it" is the last, heaviest surface. Pillars are
|
|
159
|
+
* separated by accent rules rather than boxes. The topology follows the section's own width, so
|
|
160
|
+
* the wide-content toggle and the sidebar layouts reflow it, not just the viewport. Mobile-first:
|
|
161
|
+
* the base rules are the single-column layout, which is also what a browser without container
|
|
162
|
+
* queries renders. Every color derives from Starlight's tokens, so all themes and both modes work.
|
|
159
163
|
*/
|
|
160
164
|
.hagilight-article-promotion {
|
|
165
|
+
--hagilight-promo-pad: 1.5rem;
|
|
166
|
+
--hagilight-promo-edge: color-mix(in srgb, var(--sl-color-text-accent) 38%, var(--sl-color-gray-5));
|
|
167
|
+
--hagilight-promo-tint: color-mix(in srgb, var(--sl-color-accent-low) 55%, var(--sl-color-bg-nav));
|
|
168
|
+
position: relative;
|
|
161
169
|
container-type: inline-size;
|
|
162
170
|
display: grid;
|
|
163
171
|
grid-template-columns: minmax(0, 1fr);
|
|
164
|
-
gap:
|
|
172
|
+
gap: 2.25rem;
|
|
165
173
|
margin: 3rem auto 2rem;
|
|
166
|
-
padding:
|
|
167
|
-
|
|
174
|
+
padding: calc(var(--hagilight-promo-pad) + 0.25rem) var(--hagilight-promo-pad) var(--hagilight-promo-pad);
|
|
175
|
+
overflow: clip;
|
|
176
|
+
border: 1px solid var(--hagilight-promo-edge);
|
|
168
177
|
border-radius: var(--sl-radius-lg, 0.75rem);
|
|
169
|
-
background:
|
|
178
|
+
background:
|
|
179
|
+
radial-gradient(70% 45% at 0% 0%, color-mix(in srgb, var(--sl-color-text-accent) 16%, transparent), transparent 70%),
|
|
180
|
+
linear-gradient(180deg, var(--hagilight-promo-tint), var(--sl-color-bg-nav) 55%);
|
|
170
181
|
color: var(--sl-color-text);
|
|
171
182
|
}
|
|
172
183
|
|
|
184
|
+
/* The accent rule across the top edge: the stage's signature, drawn in on scroll where supported. */
|
|
185
|
+
.hagilight-article-promotion::before {
|
|
186
|
+
content: '';
|
|
187
|
+
position: absolute;
|
|
188
|
+
inset: 0 0 auto;
|
|
189
|
+
height: 4px;
|
|
190
|
+
background: linear-gradient(90deg, var(--sl-color-text-accent), color-mix(in srgb, var(--sl-color-text-accent) 20%, transparent));
|
|
191
|
+
transform-origin: left center;
|
|
192
|
+
pointer-events: none;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
.hagilight-article-promotion ::selection {
|
|
196
|
+
background: color-mix(in srgb, var(--sl-color-text-accent) 38%, transparent);
|
|
197
|
+
color: var(--sl-color-white);
|
|
198
|
+
}
|
|
199
|
+
|
|
173
200
|
.hagilight-article-promotion h2,
|
|
174
201
|
.hagilight-article-promotion h3 {
|
|
175
202
|
margin: 0;
|
|
176
203
|
color: var(--sl-color-white);
|
|
177
|
-
line-height: 1.
|
|
204
|
+
line-height: 1.2;
|
|
205
|
+
text-wrap: balance;
|
|
178
206
|
}
|
|
179
207
|
|
|
180
208
|
.hagilight-article-promotion h2 {
|
|
181
|
-
font-size:
|
|
209
|
+
font-size: clamp(2rem, 1.3rem + 3.2cqi, 3rem);
|
|
210
|
+
font-weight: 800;
|
|
211
|
+
letter-spacing: -0.02em;
|
|
182
212
|
}
|
|
183
213
|
|
|
184
214
|
.hagilight-article-promotion h3 {
|
|
185
|
-
font-size: var(--sl-text-
|
|
215
|
+
font-size: var(--sl-text-xl, 1.25rem);
|
|
216
|
+
font-weight: 700;
|
|
186
217
|
}
|
|
187
218
|
|
|
188
219
|
.hagilight-article-promotion p {
|
|
189
220
|
margin: 0;
|
|
190
221
|
line-height: 1.6;
|
|
222
|
+
text-wrap: pretty;
|
|
191
223
|
}
|
|
192
224
|
|
|
193
225
|
.hagilight-article-promotion ul {
|
|
@@ -201,17 +233,17 @@ const gallery = copy.gallery.flatMap((item) => {
|
|
|
201
233
|
outline-offset: 3px;
|
|
202
234
|
}
|
|
203
235
|
|
|
204
|
-
/* Pitch: headline,
|
|
236
|
+
/* Pitch: headline, one emphasized promise, the way in, then the pillars. */
|
|
205
237
|
.hagilight-article-promotion__pitch {
|
|
206
238
|
display: grid;
|
|
207
|
-
gap:
|
|
239
|
+
gap: 2rem;
|
|
208
240
|
}
|
|
209
241
|
|
|
210
242
|
.hagilight-article-promotion__intro {
|
|
211
243
|
display: grid;
|
|
212
244
|
align-content: start;
|
|
213
245
|
justify-items: start;
|
|
214
|
-
gap: 0.
|
|
246
|
+
gap: 0.875rem;
|
|
215
247
|
}
|
|
216
248
|
|
|
217
249
|
.hagilight-article-promotion__lead,
|
|
@@ -219,9 +251,15 @@ const gallery = copy.gallery.flatMap((item) => {
|
|
|
219
251
|
max-inline-size: 68ch;
|
|
220
252
|
}
|
|
221
253
|
|
|
254
|
+
.hagilight-article-promotion__lead {
|
|
255
|
+
color: var(--sl-color-gray-2);
|
|
256
|
+
}
|
|
257
|
+
|
|
222
258
|
.hagilight-article-promotion__tagline {
|
|
223
259
|
color: var(--sl-color-white);
|
|
224
|
-
font-
|
|
260
|
+
font-size: var(--sl-text-lg, 1.125rem);
|
|
261
|
+
font-weight: 650;
|
|
262
|
+
line-height: 1.45;
|
|
225
263
|
}
|
|
226
264
|
|
|
227
265
|
.hagilight-article-promotion__actions {
|
|
@@ -236,32 +274,60 @@ const gallery = copy.gallery.flatMap((item) => {
|
|
|
236
274
|
display: inline-flex;
|
|
237
275
|
align-items: center;
|
|
238
276
|
justify-content: center;
|
|
277
|
+
gap: 0.5rem;
|
|
239
278
|
min-height: 2.75rem;
|
|
240
|
-
padding: 0.5rem
|
|
279
|
+
padding: 0.5rem 1.25rem;
|
|
241
280
|
border: 1px solid var(--sl-color-text-accent);
|
|
242
281
|
border-radius: var(--sl-radius-sm, 0.375rem);
|
|
243
282
|
font-weight: 700;
|
|
244
283
|
text-decoration: none;
|
|
284
|
+
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;
|
|
245
285
|
}
|
|
246
286
|
|
|
287
|
+
/* The one loud control: bigger than its sibling, lifted, with an arrow that leans forward on hover. */
|
|
247
288
|
.hagilight-article-promotion__cta--primary {
|
|
289
|
+
min-height: 3.25rem;
|
|
290
|
+
padding-inline: 1.625rem;
|
|
248
291
|
background: var(--sl-color-text-accent);
|
|
249
292
|
color: var(--sl-color-bg);
|
|
293
|
+
font-size: var(--sl-text-lg, 1.125rem);
|
|
294
|
+
box-shadow: 0 8px 20px -8px color-mix(in srgb, var(--sl-color-text-accent) 70%, transparent);
|
|
250
295
|
}
|
|
251
296
|
|
|
252
297
|
.hagilight-article-promotion__cta--secondary {
|
|
253
298
|
background: transparent;
|
|
299
|
+
color: var(--sl-color-white);
|
|
300
|
+
border-color: var(--sl-color-gray-3);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
.hagilight-article-promotion__arrow {
|
|
304
|
+
flex: none;
|
|
305
|
+
transition: transform 0.2s cubic-bezier(0.22, 1, 0.36, 1);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
.hagilight-article-promotion__cta--primary:hover {
|
|
309
|
+
transform: translateY(-2px);
|
|
310
|
+
box-shadow: 0 12px 24px -8px color-mix(in srgb, var(--sl-color-text-accent) 80%, transparent);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
.hagilight-article-promotion__cta--primary:active {
|
|
314
|
+
transform: translateY(0);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
.hagilight-article-promotion__cta--secondary:hover {
|
|
318
|
+
border-color: var(--sl-color-text-accent);
|
|
254
319
|
color: var(--sl-color-text-accent);
|
|
255
320
|
}
|
|
256
321
|
|
|
257
|
-
.hagilight-article-promotion__cta:hover
|
|
258
|
-
|
|
322
|
+
.hagilight-article-promotion__cta:hover .hagilight-article-promotion__arrow,
|
|
323
|
+
.hagilight-article-promotion__downloads:hover .hagilight-article-promotion__arrow {
|
|
324
|
+
transform: translateX(3px);
|
|
259
325
|
}
|
|
260
326
|
|
|
261
|
-
/* Pillars:
|
|
327
|
+
/* Pillars: each opens with an accent rule; the pillar word is the loudest text after the title. */
|
|
262
328
|
.hagilight-article-promotion__features {
|
|
263
329
|
display: grid;
|
|
264
|
-
gap:
|
|
330
|
+
gap: 1.25rem 1.5rem;
|
|
265
331
|
}
|
|
266
332
|
|
|
267
333
|
.hagilight-article-promotion__features li {
|
|
@@ -269,16 +335,17 @@ const gallery = copy.gallery.flatMap((item) => {
|
|
|
269
335
|
grid-template-columns: auto minmax(0, 1fr);
|
|
270
336
|
align-content: start;
|
|
271
337
|
align-items: center;
|
|
272
|
-
gap: 0.
|
|
338
|
+
gap: 0.625rem 0.75rem;
|
|
273
339
|
min-width: 0;
|
|
274
|
-
padding-block-start:
|
|
275
|
-
border-block-start:
|
|
340
|
+
padding-block-start: 1rem;
|
|
341
|
+
border-block-start: 2px solid color-mix(in srgb, var(--sl-color-text-accent) 55%, transparent);
|
|
276
342
|
}
|
|
277
343
|
|
|
278
344
|
.hagilight-article-promotion__features strong {
|
|
279
345
|
color: var(--sl-color-text-accent);
|
|
280
|
-
font-size: var(--sl-text-
|
|
281
|
-
|
|
346
|
+
font-size: var(--sl-text-xl, 1.25rem);
|
|
347
|
+
font-weight: 750;
|
|
348
|
+
line-height: 1.2;
|
|
282
349
|
}
|
|
283
350
|
|
|
284
351
|
.hagilight-article-promotion__feature-text {
|
|
@@ -288,6 +355,17 @@ const gallery = copy.gallery.flatMap((item) => {
|
|
|
288
355
|
}
|
|
289
356
|
|
|
290
357
|
.hagilight-article-promotion__icon {
|
|
358
|
+
position: relative;
|
|
359
|
+
display: grid;
|
|
360
|
+
place-items: center;
|
|
361
|
+
width: 2.5rem;
|
|
362
|
+
height: 2.5rem;
|
|
363
|
+
border-radius: var(--sl-radius-md, 0.5rem);
|
|
364
|
+
background: color-mix(in srgb, var(--sl-color-text-accent) 18%, transparent);
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
.hagilight-article-promotion__icon::after {
|
|
368
|
+
content: '';
|
|
291
369
|
width: 1.5rem;
|
|
292
370
|
height: 1.5rem;
|
|
293
371
|
background: var(--sl-color-text-accent);
|
|
@@ -298,12 +376,12 @@ const gallery = copy.gallery.flatMap((item) => {
|
|
|
298
376
|
/* Gallery: three audited screenshots, kept in the catalog's narrative order. */
|
|
299
377
|
.hagilight-article-promotion__gallery-block {
|
|
300
378
|
display: grid;
|
|
301
|
-
gap:
|
|
379
|
+
gap: 1.125rem;
|
|
302
380
|
}
|
|
303
381
|
|
|
304
382
|
.hagilight-article-promotion__gallery {
|
|
305
383
|
display: grid;
|
|
306
|
-
gap: 1.25rem
|
|
384
|
+
gap: 1.5rem 1.25rem;
|
|
307
385
|
}
|
|
308
386
|
|
|
309
387
|
.hagilight-article-promotion__gallery li {
|
|
@@ -312,7 +390,7 @@ const gallery = copy.gallery.flatMap((item) => {
|
|
|
312
390
|
|
|
313
391
|
.hagilight-article-promotion figure {
|
|
314
392
|
display: grid;
|
|
315
|
-
gap: 0.
|
|
393
|
+
gap: 0.625rem;
|
|
316
394
|
margin: 0;
|
|
317
395
|
}
|
|
318
396
|
|
|
@@ -324,22 +402,31 @@ const gallery = copy.gallery.flatMap((item) => {
|
|
|
324
402
|
display: block;
|
|
325
403
|
width: 100%;
|
|
326
404
|
height: auto;
|
|
327
|
-
border: 1px solid var(--
|
|
405
|
+
border: 1px solid var(--hagilight-promo-edge);
|
|
328
406
|
border-radius: var(--sl-radius-md, 0.5rem);
|
|
407
|
+
box-shadow: 0 18px 32px -18px color-mix(in srgb, var(--sl-color-black) 80%, transparent);
|
|
408
|
+
transition: transform 0.35s cubic-bezier(0.22, 1, 0.36, 1), border-color 0.35s;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
.hagilight-article-promotion figure:hover img {
|
|
412
|
+
transform: translateY(-3px);
|
|
413
|
+
border-color: var(--sl-color-text-accent);
|
|
329
414
|
}
|
|
330
415
|
|
|
331
416
|
.hagilight-article-promotion figcaption {
|
|
332
|
-
color: var(--sl-color-gray-
|
|
417
|
+
color: var(--sl-color-gray-1);
|
|
333
418
|
font-size: var(--sl-text-sm, 0.875rem);
|
|
334
419
|
line-height: 1.5;
|
|
335
420
|
}
|
|
336
421
|
|
|
337
|
-
/* Closing: the
|
|
422
|
+
/* Closing: a full-bleed band, the heaviest surface, so "get it" is where the eye lands last. */
|
|
338
423
|
.hagilight-article-promotion__closing {
|
|
339
424
|
display: grid;
|
|
340
|
-
gap: 1.
|
|
341
|
-
|
|
342
|
-
|
|
425
|
+
gap: 1.75rem;
|
|
426
|
+
margin: 0 calc(var(--hagilight-promo-pad) * -1) calc(var(--hagilight-promo-pad) * -1);
|
|
427
|
+
padding: var(--hagilight-promo-pad);
|
|
428
|
+
border-block-start: 1px solid var(--hagilight-promo-edge);
|
|
429
|
+
background: color-mix(in srgb, var(--sl-color-accent-low) 80%, var(--sl-color-bg));
|
|
343
430
|
}
|
|
344
431
|
|
|
345
432
|
.hagilight-article-promotion__windows,
|
|
@@ -347,7 +434,7 @@ const gallery = copy.gallery.flatMap((item) => {
|
|
|
347
434
|
display: grid;
|
|
348
435
|
align-content: start;
|
|
349
436
|
justify-items: start;
|
|
350
|
-
gap: 0.
|
|
437
|
+
gap: 0.625rem;
|
|
351
438
|
min-width: 0;
|
|
352
439
|
}
|
|
353
440
|
|
|
@@ -356,7 +443,7 @@ const gallery = copy.gallery.flatMap((item) => {
|
|
|
356
443
|
display: flex;
|
|
357
444
|
flex-wrap: wrap;
|
|
358
445
|
align-items: center;
|
|
359
|
-
gap: 0 1.
|
|
446
|
+
gap: 0 1.5rem;
|
|
360
447
|
max-width: 100%;
|
|
361
448
|
}
|
|
362
449
|
|
|
@@ -378,15 +465,18 @@ const gallery = copy.gallery.flatMap((item) => {
|
|
|
378
465
|
.hagilight-article-promotion__downloads {
|
|
379
466
|
display: inline-flex;
|
|
380
467
|
align-items: center;
|
|
468
|
+
gap: 0.375rem;
|
|
381
469
|
min-height: 2.75rem;
|
|
382
470
|
color: var(--sl-color-text-accent);
|
|
383
|
-
font-weight:
|
|
471
|
+
font-weight: 650;
|
|
472
|
+
text-decoration-thickness: 1px;
|
|
473
|
+
text-underline-offset: 0.25em;
|
|
384
474
|
}
|
|
385
475
|
|
|
386
476
|
.hagilight-article-promotion__share-text {
|
|
387
477
|
justify-self: stretch;
|
|
388
478
|
padding: 0.75rem 1rem;
|
|
389
|
-
border: 1px dashed var(--sl-color-
|
|
479
|
+
border: 1px dashed var(--sl-color-text-accent);
|
|
390
480
|
border-radius: var(--sl-radius-md, 0.5rem);
|
|
391
481
|
background: var(--sl-color-bg);
|
|
392
482
|
overflow-wrap: anywhere;
|
|
@@ -452,4 +542,53 @@ const gallery = copy.gallery.flatMap((item) => {
|
|
|
452
542
|
justify-self: stretch;
|
|
453
543
|
}
|
|
454
544
|
}
|
|
545
|
+
|
|
546
|
+
/*
|
|
547
|
+
* The one authored moment: as the stage scrolls into view its accent rule draws across and the
|
|
548
|
+
* screenshots rise into place one after another. Scroll-driven, so there is no JS and nothing
|
|
549
|
+
* replays; engines without view timelines, and readers who ask for less motion, get the final
|
|
550
|
+
* state. The animations start at 0.35 opacity, so a tall item that never reaches its full entry
|
|
551
|
+
* range is still readable.
|
|
552
|
+
*/
|
|
553
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
554
|
+
@supports (animation-timeline: view()) {
|
|
555
|
+
.hagilight-article-promotion::before {
|
|
556
|
+
animation-name: hagilight-promotion-rule;
|
|
557
|
+
animation-timing-function: linear;
|
|
558
|
+
animation-fill-mode: both;
|
|
559
|
+
animation-timeline: view();
|
|
560
|
+
animation-range: entry 0% entry 80%;
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
.hagilight-article-promotion__gallery li {
|
|
564
|
+
animation-name: hagilight-promotion-rise;
|
|
565
|
+
animation-timing-function: cubic-bezier(0.22, 1, 0.36, 1);
|
|
566
|
+
animation-fill-mode: both;
|
|
567
|
+
animation-timeline: view();
|
|
568
|
+
animation-range: entry 5% entry 45%;
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
.hagilight-article-promotion__gallery li:nth-child(2) {
|
|
572
|
+
animation-range: entry 12% entry 52%;
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
.hagilight-article-promotion__gallery li:nth-child(3) {
|
|
576
|
+
animation-range: entry 19% entry 59%;
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
@keyframes hagilight-promotion-rule {
|
|
582
|
+
from {
|
|
583
|
+
transform: scaleX(0.04);
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
@keyframes hagilight-promotion-rise {
|
|
588
|
+
from {
|
|
589
|
+
opacity: 0.35;
|
|
590
|
+
translate: 0 1.75rem;
|
|
591
|
+
scale: 0.97;
|
|
592
|
+
}
|
|
593
|
+
}
|
|
455
594
|
</style>
|
package/README.md
CHANGED
|
@@ -82,6 +82,15 @@ hagilight({ themes: { enabled: false } })
|
|
|
82
82
|
|
|
83
83
|
The plugin footer shows the same build line as the Astro package: `Branch:`, `Commit:`, and `Built:` after the attribution. It is always on and has no option of its own. The values come from `HAGILIGHT_BUILD_BRANCH` and `HAGILIGHT_BUILD_COMMIT`, then Git, then `unknown`; the core README gives the order. Detached-HEAD CI checkouts report `Branch: unknown` unless the build step sets `HAGILIGHT_BUILD_BRANCH`, as shown in the Astro README. Footer components mounted directly in a page (`@hagicode/hagilight-starlight/Footer` and `PromotoFooter`) render no build text.
|
|
84
84
|
|
|
85
|
+
### Site metadata
|
|
86
|
+
|
|
87
|
+
The plugin writes `/.well-known/hagilight.metadata.json` once per build, after the sitemap, so `discovery` lists the sitemap and any RSS feed that exist. The file's schema and privacy rules are in `@hagicode/hagilight-core`'s README.
|
|
88
|
+
|
|
89
|
+
- `metadata: false` opts out. `metadata: { name, description }` sets the values; the name defaults to the Starlight `title` for the default locale, then the site hostname.
|
|
90
|
+
- Analytics flags report the resolved options. A provider you disable with `analytics` reads `false`. The IDs and keys are never written.
|
|
91
|
+
- When the plain-Astro `hagilight()` integration is also registered, this plugin writes the file, including `llms.txt` in `discovery`, whatever the order of registration. Set `metadata: false` here to let the plain-Astro integration write it instead.
|
|
92
|
+
- GitHub Pages needs a `.nojekyll` file in the published root, or the `.well-known` directory is dropped. The `/docs/` base path puts the file at `<site URL>/docs/.well-known/hagilight.metadata.json`.
|
|
93
|
+
|
|
85
94
|
### Body images
|
|
86
95
|
|
|
87
96
|
`hagilight()` registers the content-image plugin from `@hagicode/hagilight-core/content-images` for the whole site. Every Markdown or MDX body image outside a `<picture>` element gets `loading="lazy"`, `decoding="async"`, and the class `hagilight-content-image`. Values the author set are kept, and logos and showcase images are not changed.
|
package/dist/index.d.ts
CHANGED
|
@@ -55,7 +55,22 @@ export interface HagilightContentComponentsOptions {
|
|
|
55
55
|
markdownContent?: boolean;
|
|
56
56
|
}
|
|
57
57
|
export type HagilightAIDisclosureOptions = Partial<AIDisclosureDefaults>;
|
|
58
|
+
export interface HagilightMetadataOptions {
|
|
59
|
+
/** Defaults to `true`. */
|
|
60
|
+
enabled?: boolean;
|
|
61
|
+
/** Site name. Defaults to the Starlight `title` for the default locale, then the site hostname. */
|
|
62
|
+
name?: string;
|
|
63
|
+
/** Site description. Defaults to the Starlight `description` when the site sets one. */
|
|
64
|
+
description?: string;
|
|
65
|
+
}
|
|
58
66
|
export interface HagilightStarlightOptions {
|
|
67
|
+
/**
|
|
68
|
+
* Generate `/.well-known/hagilight.metadata.json` after the build. Defaults to `true`; `false`
|
|
69
|
+
* opts out, and an options object may set `name` and `description`. A site-provided file is
|
|
70
|
+
* never overwritten. When a plain-Astro `hagilight()` is also registered, this plugin writes
|
|
71
|
+
* the file and the plain-Astro integration defers to it.
|
|
72
|
+
*/
|
|
73
|
+
metadata?: boolean | HagilightMetadataOptions;
|
|
59
74
|
header?: HagilightToggleOptions;
|
|
60
75
|
notFoundPage?: HagilightToggleOptions;
|
|
61
76
|
rss?: HagilightRssOptions;
|
package/dist/index.js
CHANGED
|
@@ -7,8 +7,11 @@ import { DEFAULT_POSTHOG_API_KEY, POSTHOG_API_KEY_PATTERN } from '@hagicode/hagi
|
|
|
7
7
|
import { resolveRssLocales } from '@hagicode/hagilight-core/rss';
|
|
8
8
|
import { RSS_OWNER, registerStarlightRssOwner, } from '@hagicode/hagilight-core/rss-ownership';
|
|
9
9
|
import { isValidSeoImageReference, resolveSeoImageUrl } from '@hagicode/hagilight-core/seo';
|
|
10
|
+
import { resolveSiteMetadataOption } from '@hagicode/hagilight-core/site-metadata';
|
|
11
|
+
import { registerStarlightSiteMetadataOwner } from '@hagicode/hagilight-core/site-metadata-ownership';
|
|
10
12
|
import { resolveRssOptions } from './rss-utils.js';
|
|
11
13
|
import { resolveSeoLocales } from './seo-utils.js';
|
|
14
|
+
import { createStarlightMetadataFinalizer } from './site-metadata.js';
|
|
12
15
|
import { THEME_BOOTSTRAP } from './themes.js';
|
|
13
16
|
const packageRoot = new URL('../', import.meta.url);
|
|
14
17
|
const packageFile = (name) => fileURLToPath(new URL(name, packageRoot));
|
|
@@ -202,7 +205,7 @@ function resolveDefaultSeoLocale(defaultLocale, locales) {
|
|
|
202
205
|
}
|
|
203
206
|
return found.route;
|
|
204
207
|
}
|
|
205
|
-
function createConfiguredIntegration(instanceId, serializedOptions, rssConfig, rssLocaleFeedUrls, componentIds, getConfiguredRssFeed, generateRss, unregisterRssOwner, contentImagesEnabled) {
|
|
208
|
+
function createConfiguredIntegration(instanceId, serializedOptions, rssConfig, rssLocaleFeedUrls, componentIds, getConfiguredRssFeed, generateRss, unregisterRssOwner, contentImagesEnabled, siteMetadata, unregisterSiteMetadataOwner) {
|
|
206
209
|
const optionsId = `virtual:hagilight-starlight/${instanceId}/options`;
|
|
207
210
|
const rssConfigId = 'virtual:hagilight-starlight/rss-config';
|
|
208
211
|
const headerPath = componentIds.header ? packageFile('Header.astro') : undefined;
|
|
@@ -328,10 +331,18 @@ ${serializedOptions.posthogApiKey ? '<PostHog apiKey={options.posthogApiKey} />'
|
|
|
328
331
|
prerender: true,
|
|
329
332
|
});
|
|
330
333
|
}
|
|
331
|
-
|
|
334
|
+
// Starlight splices its own sitemap into the integration list before this setup runs, so
|
|
335
|
+
// the finalizer appended in the same update runs after the sitemap has written its index.
|
|
336
|
+
updateConfig({
|
|
337
|
+
vite: { plugins: [vitePlugin] },
|
|
338
|
+
...(siteMetadata.enabled
|
|
339
|
+
? { integrations: [createStarlightMetadataFinalizer(siteMetadata, () => buildInfo)] }
|
|
340
|
+
: {}),
|
|
341
|
+
});
|
|
332
342
|
},
|
|
333
343
|
'astro:config:done'({ config, logger }) {
|
|
334
344
|
unregisterRssOwner();
|
|
345
|
+
unregisterSiteMetadataOwner();
|
|
335
346
|
checkContentImageRegistration({ config, logger, enabled: contentImagesEnabled });
|
|
336
347
|
if (config.image?.service?.entrypoint === NOOP_IMAGE_SERVICE) {
|
|
337
348
|
logger.warn(NOOP_IMAGE_SERVICE_WARNING);
|
|
@@ -416,6 +427,8 @@ export default function hagilight(options = {}) {
|
|
|
416
427
|
seo,
|
|
417
428
|
};
|
|
418
429
|
const unregisterRssOwner = registerStarlightRssOwner(rssEnabled);
|
|
430
|
+
const metadata = resolveSiteMetadataOption(input.metadata);
|
|
431
|
+
const unregisterSiteMetadataOwner = registerStarlightSiteMetadataOwner(metadata.enabled);
|
|
419
432
|
return {
|
|
420
433
|
name: '@hagicode/hagilight-starlight',
|
|
421
434
|
hooks: {
|
|
@@ -495,7 +508,24 @@ export default function hagilight(options = {}) {
|
|
|
495
508
|
new URL(`rss.${filename}.xml`, baseUrl).toString(),
|
|
496
509
|
]))
|
|
497
510
|
: {};
|
|
498
|
-
addIntegration(createConfiguredIntegration(instanceId, serializedOptions, { options: rssOptions, locales: rssLocales }, rssLocaleFeedUrls, componentIds, () => getRssFeedUrl(config) ?? rssFeedUrl, generateRss, unregisterRssOwner, contentImagesEnabled
|
|
511
|
+
addIntegration(createConfiguredIntegration(instanceId, serializedOptions, { options: rssOptions, locales: rssLocales }, rssLocaleFeedUrls, componentIds, () => getRssFeedUrl(config) ?? rssFeedUrl, generateRss, unregisterRssOwner, contentImagesEnabled, {
|
|
512
|
+
enabled: metadata.enabled,
|
|
513
|
+
name: metadata.name,
|
|
514
|
+
description: metadata.description,
|
|
515
|
+
title: config.title,
|
|
516
|
+
siteDescription: config.description,
|
|
517
|
+
locales: config.locales,
|
|
518
|
+
defaultLocale: config.defaultLocale,
|
|
519
|
+
site: astroConfig.site,
|
|
520
|
+
base: astroConfig.base,
|
|
521
|
+
publicDir: astroConfig.publicDir,
|
|
522
|
+
analytics: {
|
|
523
|
+
googleAnalytics: serializedOptions.googleAnalyticsMeasurementId !== undefined,
|
|
524
|
+
fiftyOneLa: serializedOptions.fiftyOneLaId !== undefined,
|
|
525
|
+
posthog: serializedOptions.posthogApiKey !== undefined,
|
|
526
|
+
},
|
|
527
|
+
features: { seo: seo.enabled, promoto: promotoEnabled },
|
|
528
|
+
}, unregisterSiteMetadataOwner));
|
|
499
529
|
updateConfig({
|
|
500
530
|
...(config.logo === undefined ? { logo: { src: defaultLogo, alt: 'HagiCode' } } : {}),
|
|
501
531
|
customCss: [
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { AstroIntegration } from 'astro';
|
|
2
|
+
import type { BuildInfo } from '@hagicode/hagilight-core/build-info';
|
|
3
|
+
import { type SiteMetadataAnalytics, type SiteMetadataFeatures } from '@hagicode/hagilight-core/site-metadata';
|
|
4
|
+
import { type SiteMetadataLogger } from '@hagicode/hagilight-core/site-metadata-writer';
|
|
5
|
+
export interface StarlightSiteMetadataSetup {
|
|
6
|
+
enabled: boolean;
|
|
7
|
+
/** Explicit `metadata.name`, when set. */
|
|
8
|
+
name?: string | undefined;
|
|
9
|
+
/** Explicit `metadata.description`, when set. */
|
|
10
|
+
description?: string | undefined;
|
|
11
|
+
/** Starlight `title`: a string or a localized record. */
|
|
12
|
+
title: unknown;
|
|
13
|
+
/** Starlight `description`, when the site configures one. */
|
|
14
|
+
siteDescription?: unknown;
|
|
15
|
+
/** Starlight `locales` map, for example `{ root: { lang: 'en' } }`. */
|
|
16
|
+
locales: unknown;
|
|
17
|
+
/** Starlight `defaultLocale`. */
|
|
18
|
+
defaultLocale: unknown;
|
|
19
|
+
site: unknown;
|
|
20
|
+
base: string | undefined;
|
|
21
|
+
publicDir: URL;
|
|
22
|
+
analytics: SiteMetadataAnalytics;
|
|
23
|
+
features: SiteMetadataFeatures;
|
|
24
|
+
}
|
|
25
|
+
export interface WriteStarlightSiteMetadataInput {
|
|
26
|
+
outDir: string;
|
|
27
|
+
setup: StarlightSiteMetadataSetup;
|
|
28
|
+
build: BuildInfo;
|
|
29
|
+
logger: SiteMetadataLogger;
|
|
30
|
+
}
|
|
31
|
+
/** Build and write the metadata file for a Starlight build. Skips with a warning when no absolute site URL exists. */
|
|
32
|
+
export declare function writeStarlightSiteMetadata({ outDir, setup, build, logger }: WriteStarlightSiteMetadataInput): void;
|
|
33
|
+
/**
|
|
34
|
+
* Integration appended after this plugin's sitemap, so the write runs after
|
|
35
|
+
* `@astrojs/sitemap` has written its index. The owner's write also waits for any
|
|
36
|
+
* deferring plain-Astro peer that reported before it.
|
|
37
|
+
*/
|
|
38
|
+
export declare function createStarlightMetadataFinalizer(setup: StarlightSiteMetadataSetup, buildInfo: () => BuildInfo | undefined): AstroIntegration;
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Facts for `.well-known/hagilight.metadata.json` from a Starlight build. The
|
|
3
|
+
* Starlight plugin owns the file when it is enabled, so this module collects the
|
|
4
|
+
* title, locales and resolved feature flags from its own configuration.
|
|
5
|
+
*/
|
|
6
|
+
import { readFileSync } from 'node:fs';
|
|
7
|
+
import { join } from 'node:path';
|
|
8
|
+
import { fileURLToPath } from 'node:url';
|
|
9
|
+
import { buildSiteMetadata, SITE_METADATA_PATH, } from '@hagicode/hagilight-core/site-metadata';
|
|
10
|
+
import { findDiscoveryOutputs, writeSiteMetadata } from '@hagicode/hagilight-core/site-metadata-writer';
|
|
11
|
+
import { completeSiteMetadataOwnerBuild } from '@hagicode/hagilight-core/site-metadata-ownership';
|
|
12
|
+
import { resolveSeoLocales } from './seo-utils.js';
|
|
13
|
+
const STARLIGHT_PACKAGE_VERSION = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8')).version;
|
|
14
|
+
function isRecord(value) {
|
|
15
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
16
|
+
}
|
|
17
|
+
function absoluteSite(site) {
|
|
18
|
+
try {
|
|
19
|
+
const parsed = new URL(site);
|
|
20
|
+
return ['http:', 'https:'].includes(parsed.protocol) && !parsed.username && !parsed.password ? parsed.href : undefined;
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
return undefined;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
function trimmedText(value) {
|
|
27
|
+
return typeof value === 'string' && value.trim() ? value.trim() : undefined;
|
|
28
|
+
}
|
|
29
|
+
/** Starlight locale routes become metadata locales. The root route uses its language tag as the code. */
|
|
30
|
+
function toMetadataLocales(locales) {
|
|
31
|
+
const routes = new Map();
|
|
32
|
+
const entries = locales.map(({ route, lang }) => {
|
|
33
|
+
const code = route === 'root' ? lang : route;
|
|
34
|
+
routes.set(route, code);
|
|
35
|
+
return { code, lang, path: route === 'root' ? '/' : `/${route}/` };
|
|
36
|
+
});
|
|
37
|
+
return { locales: entries, routes };
|
|
38
|
+
}
|
|
39
|
+
/** Starlight's default locale is the route named in `defaultLocale`, or `root`. */
|
|
40
|
+
function defaultRoute(defaultLocale, locales) {
|
|
41
|
+
const configured = isRecord(defaultLocale) ? defaultLocale.locale ?? defaultLocale.lang : defaultLocale;
|
|
42
|
+
if (typeof configured !== 'string' || !configured.trim())
|
|
43
|
+
return locales.find(({ route }) => route === 'root')?.route ?? locales[0]?.route ?? 'root';
|
|
44
|
+
const match = locales.find(({ route, lang }) => route === configured || lang === configured);
|
|
45
|
+
return match?.route ?? configured;
|
|
46
|
+
}
|
|
47
|
+
/** Starlight's title, read for the default locale. A localized record uses `root` or its first entry. */
|
|
48
|
+
function siteTitle(title, route) {
|
|
49
|
+
if (typeof title === 'string')
|
|
50
|
+
return trimmedText(title);
|
|
51
|
+
if (!isRecord(title))
|
|
52
|
+
return undefined;
|
|
53
|
+
return trimmedText(title[route] ?? title.root ?? Object.values(title)[0]);
|
|
54
|
+
}
|
|
55
|
+
/** Build and write the metadata file for a Starlight build. Skips with a warning when no absolute site URL exists. */
|
|
56
|
+
export function writeStarlightSiteMetadata({ outDir, setup, build, logger }) {
|
|
57
|
+
const siteUrl = absoluteSite(setup.site);
|
|
58
|
+
if (!siteUrl) {
|
|
59
|
+
logger.warn(`${SITE_METADATA_PATH} requires an absolute Astro site URL, so it was not generated.`);
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
const seoLocales = resolveSeoLocales(setup.locales);
|
|
63
|
+
const { locales, routes } = toMetadataLocales(seoLocales);
|
|
64
|
+
const defaultCode = routes.get(defaultRoute(setup.defaultLocale, seoLocales));
|
|
65
|
+
const route = defaultRoute(setup.defaultLocale, seoLocales);
|
|
66
|
+
const base = `${setup.base || '/'}`.replace(/\/?$/u, '/');
|
|
67
|
+
const metadata = buildSiteMetadata({
|
|
68
|
+
generator: { name: '@hagicode/hagilight-starlight', version: STARLIGHT_PACKAGE_VERSION },
|
|
69
|
+
site: {
|
|
70
|
+
name: setup.name ?? siteTitle(setup.title, route),
|
|
71
|
+
url: siteUrl,
|
|
72
|
+
base,
|
|
73
|
+
description: setup.description ?? trimmedText(setup.siteDescription),
|
|
74
|
+
},
|
|
75
|
+
locales,
|
|
76
|
+
defaultLocale: defaultCode,
|
|
77
|
+
fallbackLang: seoLocales.find((entry) => entry.route === route)?.lang,
|
|
78
|
+
build,
|
|
79
|
+
discovery: findDiscoveryOutputs(outDir),
|
|
80
|
+
analytics: setup.analytics,
|
|
81
|
+
features: setup.features,
|
|
82
|
+
});
|
|
83
|
+
writeSiteMetadata({
|
|
84
|
+
outDir,
|
|
85
|
+
metadata,
|
|
86
|
+
siteOwnedFiles: [join(fileURLToPath(setup.publicDir), SITE_METADATA_PATH)],
|
|
87
|
+
logger,
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Integration appended after this plugin's sitemap, so the write runs after
|
|
92
|
+
* `@astrojs/sitemap` has written its index. The owner's write also waits for any
|
|
93
|
+
* deferring plain-Astro peer that reported before it.
|
|
94
|
+
*/
|
|
95
|
+
export function createStarlightMetadataFinalizer(setup, buildInfo) {
|
|
96
|
+
return {
|
|
97
|
+
name: '@hagicode/hagilight-starlight:metadata',
|
|
98
|
+
hooks: {
|
|
99
|
+
'astro:build:done'({ dir, logger }) {
|
|
100
|
+
completeSiteMetadataOwnerBuild(() => {
|
|
101
|
+
const build = buildInfo();
|
|
102
|
+
if (!build)
|
|
103
|
+
return;
|
|
104
|
+
writeStarlightSiteMetadata({ outDir: fileURLToPath(dir), setup, build, logger });
|
|
105
|
+
});
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
};
|
|
109
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hagicode/hagilight-starlight",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.0",
|
|
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.
|
|
56
|
+
"@hagicode/hagilight-core": "0.12.0"
|
|
57
57
|
},
|
|
58
58
|
"peerDependencies": {
|
|
59
59
|
"@astrojs/starlight": "^0.42.4",
|