@grimoire-rs/indexer 0.3.3 → 0.4.1

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 (110) hide show
  1. package/README.md +220 -9
  2. package/dist/ci.d.ts +6 -1
  3. package/dist/ci.d.ts.map +1 -1
  4. package/dist/ci.js +32 -1
  5. package/dist/ci.js.map +1 -1
  6. package/dist/cli/build.d.ts.map +1 -1
  7. package/dist/cli/build.js +3 -1
  8. package/dist/cli/build.js.map +1 -1
  9. package/dist/cli/ci.d.ts.map +1 -1
  10. package/dist/cli/ci.js +5 -1
  11. package/dist/cli/ci.js.map +1 -1
  12. package/dist/cli/enrich.d.ts +1 -0
  13. package/dist/cli/enrich.d.ts.map +1 -1
  14. package/dist/cli/enrich.js +38 -0
  15. package/dist/cli/enrich.js.map +1 -1
  16. package/dist/cli/init.d.ts.map +1 -1
  17. package/dist/cli/init.js +17 -3
  18. package/dist/cli/init.js.map +1 -1
  19. package/dist/cli/main.d.ts.map +1 -1
  20. package/dist/cli/main.js +9 -0
  21. package/dist/cli/main.js.map +1 -1
  22. package/dist/cli/ratings.d.ts +3 -0
  23. package/dist/cli/ratings.d.ts.map +1 -0
  24. package/dist/cli/ratings.js +160 -0
  25. package/dist/cli/ratings.js.map +1 -0
  26. package/dist/config.d.ts +19 -0
  27. package/dist/config.d.ts.map +1 -1
  28. package/dist/config.js +47 -1
  29. package/dist/config.js.map +1 -1
  30. package/dist/data/index.d.ts.map +1 -1
  31. package/dist/data/index.js +13 -0
  32. package/dist/data/index.js.map +1 -1
  33. package/dist/enrich/checkpoint.d.ts +53 -0
  34. package/dist/enrich/checkpoint.d.ts.map +1 -0
  35. package/dist/enrich/checkpoint.js +296 -0
  36. package/dist/enrich/checkpoint.js.map +1 -0
  37. package/dist/enrich/index.d.ts +14 -0
  38. package/dist/enrich/index.d.ts.map +1 -1
  39. package/dist/enrich/index.js +77 -4
  40. package/dist/enrich/index.js.map +1 -1
  41. package/dist/ratings/budget.d.ts +16 -0
  42. package/dist/ratings/budget.d.ts.map +1 -0
  43. package/dist/ratings/budget.js +7 -0
  44. package/dist/ratings/budget.js.map +1 -0
  45. package/dist/ratings/config.d.ts +56 -0
  46. package/dist/ratings/config.d.ts.map +1 -0
  47. package/dist/ratings/config.js +86 -0
  48. package/dist/ratings/config.js.map +1 -0
  49. package/dist/ratings/marker.d.ts +105 -0
  50. package/dist/ratings/marker.d.ts.map +1 -0
  51. package/dist/ratings/marker.js +141 -0
  52. package/dist/ratings/marker.js.map +1 -0
  53. package/dist/ratings/paging.d.ts +16 -0
  54. package/dist/ratings/paging.d.ts.map +1 -0
  55. package/dist/ratings/paging.js +36 -0
  56. package/dist/ratings/paging.js.map +1 -0
  57. package/dist/ratings/provider.d.ts +100 -0
  58. package/dist/ratings/provider.d.ts.map +1 -0
  59. package/dist/ratings/provider.js +203 -0
  60. package/dist/ratings/provider.js.map +1 -0
  61. package/dist/ratings/provider_github.d.ts +3 -0
  62. package/dist/ratings/provider_github.d.ts.map +1 -0
  63. package/dist/ratings/provider_github.js +135 -0
  64. package/dist/ratings/provider_github.js.map +1 -0
  65. package/dist/ratings/provider_gitlab.d.ts +3 -0
  66. package/dist/ratings/provider_gitlab.d.ts.map +1 -0
  67. package/dist/ratings/provider_gitlab.js +153 -0
  68. package/dist/ratings/provider_gitlab.js.map +1 -0
  69. package/dist/ratings/provider_memory.d.ts +28 -0
  70. package/dist/ratings/provider_memory.d.ts.map +1 -0
  71. package/dist/ratings/provider_memory.js +49 -0
  72. package/dist/ratings/provider_memory.js.map +1 -0
  73. package/dist/ratings/reconcile.d.ts +47 -0
  74. package/dist/ratings/reconcile.d.ts.map +1 -0
  75. package/dist/ratings/reconcile.js +133 -0
  76. package/dist/ratings/reconcile.js.map +1 -0
  77. package/dist/ratings/seed.d.ts +60 -0
  78. package/dist/ratings/seed.d.ts.map +1 -0
  79. package/dist/ratings/seed.js +132 -0
  80. package/dist/ratings/seed.js.map +1 -0
  81. package/dist/renderer/astro/components/Catalog.d.ts +2 -0
  82. package/dist/renderer/astro/components/Catalog.js +65 -16
  83. package/dist/renderer/astro/components/Catalog.tsx +119 -26
  84. package/dist/renderer/astro/components/CommandField.astro +1 -0
  85. package/dist/renderer/astro/components/VersionMenu.astro +31 -23
  86. package/dist/renderer/astro/layouts/Base.astro +218 -263
  87. package/dist/renderer/astro/lib/catalog.d.ts +25 -0
  88. package/dist/renderer/astro/lib/catalog.js +46 -0
  89. package/dist/renderer/astro/lib/catalog.ts +49 -0
  90. package/dist/renderer/astro/pages/p/[...slug].astro +238 -85
  91. package/dist/renderer/astro/styles/tokens.css +157 -0
  92. package/dist/renderer/index.d.ts.map +1 -1
  93. package/dist/renderer/index.js +101 -2
  94. package/dist/renderer/index.js.map +1 -1
  95. package/dist/renderer/types.d.ts +41 -0
  96. package/dist/renderer/types.d.ts.map +1 -1
  97. package/dist/validate/adapters/http.d.ts +29 -1
  98. package/dist/validate/adapters/http.d.ts.map +1 -1
  99. package/dist/validate/adapters/http.js +17 -2
  100. package/dist/validate/adapters/http.js.map +1 -1
  101. package/package.json +1 -1
  102. package/templates/ci/github-enrich.yml +9 -1
  103. package/templates/ci/github-pages.yml +11 -5
  104. package/templates/ci/github-ratings-seed.yml +88 -0
  105. package/templates/ci/github-ratings.yml +51 -0
  106. package/templates/ci/gitlab-ci.yml +3 -3
  107. package/templates/ci/gitlab-enrich.sh +10 -1
  108. package/templates/ci/gitlab-ratings-seed.sh +83 -0
  109. package/templates/ci/gitlab-ratings.yml +37 -0
  110. package/templates/gitignore +8 -0
@@ -2,7 +2,22 @@
2
2
  // Frozen public URL: /p/<namespace>/<name>/. grim resolves against it, the
3
3
  // VS Code deep link points at it, and search engines index it — the route
4
4
  // shape is a contract, not an implementation detail.
5
- import { ChevronRight, FolderRoot, Globe, Image, ImageOff, Scale, User } from "lucide-preact";
5
+ import {
6
+ BookOpen,
7
+ Bug,
8
+ ChevronRight,
9
+ FolderRoot,
10
+ Globe,
11
+ History,
12
+ House,
13
+ Image,
14
+ ImageOff,
15
+ Mail,
16
+ MessageCircle,
17
+ Scale,
18
+ ShieldAlert,
19
+ User,
20
+ } from "lucide-preact";
6
21
  import Base from "../../layouts/Base.astro";
7
22
  import VersionMenu from "../../components/VersionMenu.astro";
8
23
  import CommandField from "../../components/CommandField.astro";
@@ -19,7 +34,10 @@ import { withBase } from "../../lib/base";
19
34
  import { SHIKI_THEMES } from "../../lib/code";
20
35
  import {
21
36
  compareVersions,
37
+ externalUrl,
38
+ lastUpdated,
22
39
  resolveMemberRef,
40
+ timeAgo,
23
41
  versionCascade,
24
42
  vscodeUrl,
25
43
  type CatalogPackage,
@@ -110,11 +128,45 @@ const scopeChoices = [
110
128
  { name: "Global", command: `grim add --global ${pkg.ref}`, Icon: Globe },
111
129
  { name: "Project", command: addCommand, Icon: FolderRoot },
112
130
  ];
131
+
132
+ // Every outbound string on this page was handed to the index by a registry
133
+ // it does not control, so each one goes through the scheme allowlist before
134
+ // it becomes an href — the repository link included, which predates the rest
135
+ // of them and took the same string unchecked.
136
+ const repository = externalUrl(pkg.repository);
137
+ const homepage = externalUrl(pkg.url);
138
+ const documentation = externalUrl(pkg.documentation);
139
+
140
+ // Who publishes this, as one line under the summary. `vendor` is derived
141
+ // from the repository namespace when the publisher set none, so it is often
142
+ // the same string as `authors` — a set, not a join, keeps it from being
143
+ // printed twice.
144
+ const byline = [...new Set([pkg.authors, pkg.vendor].filter(Boolean))].join(" · ");
145
+
146
+ // "Get help" belongs to the *repository*, not this version: it is read off
147
+ // the description companion, so a maintainer moves a link without any
148
+ // artifact digest moving. Built here and rendered only when a channel is
149
+ // actually set — most repositories publish none, and a block of four dead
150
+ // rows is worse than no block.
151
+ const supportChannels = [
152
+ { label: "Issue tracker", href: externalUrl(pkg.support?.issues), Icon: Bug },
153
+ { label: "Chat", href: externalUrl(pkg.support?.chat), Icon: MessageCircle },
154
+ { label: "Contact", href: externalUrl(pkg.support?.contact), Icon: Mail },
155
+ { label: "Report a vulnerability", href: externalUrl(pkg.support?.security), Icon: ShieldAlert },
156
+ ].filter((channel): channel is { label: string; href: string; Icon: typeof Bug } =>
157
+ channel.href !== null,
158
+ );
159
+
160
+ // A commit SHA is not something to read, it is something to check against a
161
+ // checkout. It rides as the tooltip on the date it explains rather than
162
+ // taking a row of its own and putting 40 hex characters in the rail.
163
+ const updated = lastUpdated(pkg);
164
+ const updatedTitle = pkg.revision ? `${updated} · revision ${pkg.revision}` : updated;
113
165
  ---
114
166
 
115
167
  <Base title={`${title} — ${config.brand}`} description={description} image={pkg.logo}>
116
168
  <div class="detail">
117
- <header class="detail-head">
169
+ <header class="detail-head" data-slot="detail-header">
118
170
  {
119
171
  pkg.logo ? (
120
172
  // The placeholder ships in the markup and the image fades in over
@@ -130,7 +182,7 @@ const scopeChoices = [
130
182
  <img src={withBase(pkg.logo)} alt="" />
131
183
  </span>
132
184
  ) : (
133
- <div class="tile letter" style={`background: var(--kind-${pkg.kind}, var(--muted))`}>
185
+ <div class="tile letter" style={`background: var(--grim-color-kind-${pkg.kind}, var(--grim-color-muted))`}>
134
186
  {pkg.name[0]?.toUpperCase()}
135
187
  </div>
136
188
  )
@@ -138,14 +190,19 @@ const scopeChoices = [
138
190
  <div class="detail-head-text">
139
191
  <h1>
140
192
  {pkg.name} <span class={`badge kind-${pkg.kind}`}>{pkg.kind}</span>
193
+ {/* Skills only — every other kind reports null, and grim's own
194
+ surfaces show it beside the kind for the same reason: it
195
+ qualifies what the thing is, not what it says about itself. */}
196
+ {pkg.compatibility && <span class="badge compat">{pkg.compatibility}</span>}
141
197
  </h1>
142
198
  {description && <p class="summary">{description}</p>}
199
+ {byline && <p class="byline">by {byline}</p>}
143
200
  </div>
144
201
  </header>
145
202
 
146
203
  {
147
204
  pkg.deprecated && (
148
- <p class="deprecated-banner">
205
+ <p class="deprecated-banner" data-slot="deprecated-banner">
149
206
  ⚠️ deprecated{pkg.replacedBy ? ` — replaced by ${pkg.replacedBy}` : ""}
150
207
  </p>
151
208
  )
@@ -172,7 +229,7 @@ const scopeChoices = [
172
229
  }
173
230
  </div>
174
231
 
175
- <div class="detail-body">
232
+ <div class="detail-body" data-slot="detail-body">
176
233
  <main class="detail-main">
177
234
  {/* Radio tabs, not a scripted tablist: the ribbon works on a page
178
235
  whose JS never ran, and a radio group already has the keyboard
@@ -267,7 +324,7 @@ const scopeChoices = [
267
324
  {openTab === null && <p class="panel-empty">No content available.</p>}
268
325
  </main>
269
326
 
270
- <aside class="detail-rail">
327
+ <aside class="detail-rail" data-slot="detail-rail">
271
328
  {tags.length > 0 && (
272
329
  <section>
273
330
  <h2>Versions</h2>
@@ -304,19 +361,45 @@ const scopeChoices = [
304
361
  <dd>{pkg.license}</dd>
305
362
  </>
306
363
  )}
307
- {pkg.repository && (
364
+ {repository && (
308
365
  <>
309
366
  <dt>
310
367
  <BrandMark path={repoMark} size={15} />
311
368
  Repository
312
369
  </dt>
313
370
  <dd>
314
- <a href={pkg.repository} target="_blank" rel="noopener noreferrer">
371
+ <a href={repository} target="_blank" rel="noopener noreferrer">
315
372
  source
316
373
  </a>
317
374
  </dd>
318
375
  </>
319
376
  )}
377
+ {homepage && (
378
+ <>
379
+ <dt>
380
+ <House size={15} aria-hidden="true" />
381
+ Homepage
382
+ </dt>
383
+ <dd>
384
+ <a href={homepage} target="_blank" rel="noopener noreferrer">
385
+ website
386
+ </a>
387
+ </dd>
388
+ </>
389
+ )}
390
+ {documentation && (
391
+ <>
392
+ <dt>
393
+ <BookOpen size={15} aria-hidden="true" />
394
+ Docs
395
+ </dt>
396
+ <dd>
397
+ <a href={documentation} target="_blank" rel="noopener noreferrer">
398
+ documentation
399
+ </a>
400
+ </dd>
401
+ </>
402
+ )}
320
403
  {pkg.owner?.github && (
321
404
  <>
322
405
  <dt>
@@ -334,6 +417,19 @@ const scopeChoices = [
334
417
  </dd>
335
418
  </>
336
419
  )}
420
+ {updated && (
421
+ <>
422
+ <dt>
423
+ <History size={15} aria-hidden="true" />
424
+ Updated
425
+ </dt>
426
+ <dd>
427
+ <time datetime={updated} title={updatedTitle}>
428
+ {timeAgo(updated) || updated}
429
+ </time>
430
+ </dd>
431
+ </>
432
+ )}
337
433
  </dl>
338
434
  {pkg.keywords && pkg.keywords.length > 0 && (
339
435
  <ul class="keywords">
@@ -352,6 +448,22 @@ const scopeChoices = [
352
448
  </ul>
353
449
  )}
354
450
  </section>
451
+
452
+ {supportChannels.length > 0 && (
453
+ <section class="support">
454
+ <h2>Get help</h2>
455
+ <ul>
456
+ {supportChannels.map(({ label, href, Icon }) => (
457
+ <li>
458
+ <Icon size={15} aria-hidden="true" />
459
+ <a href={href} target="_blank" rel="noopener noreferrer">
460
+ {label}
461
+ </a>
462
+ </li>
463
+ ))}
464
+ </ul>
465
+ </section>
466
+ )}
355
467
  </aside>
356
468
  </div>
357
469
  </div>
@@ -397,33 +509,39 @@ const scopeChoices = [
397
509
  </script>
398
510
 
399
511
  <style>
512
+ @layer grimoire {
513
+ /* Layered so a user CSS file (unlayered) outranks these rules. Astro
514
+ scopes a component style to `.foo[data-astro-cid-…]` — specificity
515
+ (0,2,0) — which beats a consumer's `.foo` in EITHER source order, so
516
+ source order alone does not make this overridable. Not reindented:
517
+ the wrapper stays a two-line diff. */
400
518
  .detail {
401
519
  display: flex;
402
520
  flex-direction: column;
403
- gap: 1.25rem;
521
+ gap: var(--grim-space-7);
404
522
  }
405
523
  .detail-head {
406
524
  display: flex;
407
525
  align-items: center;
408
- gap: 1rem;
409
- background: var(--card);
410
- border: 1px solid var(--border);
411
- border-radius: 10px;
412
- padding: 1rem 1.25rem;
526
+ gap: var(--grim-space-6);
527
+ background: var(--grim-color-card);
528
+ border: var(--grim-border-width) solid var(--grim-color-border);
529
+ border-radius: var(--grim-radius-xl);
530
+ padding: var(--grim-space-6) var(--grim-space-7);
413
531
  }
414
532
  .tile {
415
533
  flex-shrink: 0;
416
534
  width: 3.5rem;
417
535
  height: 3.5rem;
418
- border-radius: 10px;
536
+ border-radius: var(--grim-radius-xl);
419
537
  object-fit: cover;
420
538
  }
421
539
  .tile.letter {
422
540
  display: flex;
423
541
  align-items: center;
424
542
  justify-content: center;
425
- color: var(--on-accent);
426
- font-size: 1.5rem;
543
+ color: var(--grim-color-on-accent);
544
+ font-size: var(--grim-text-xl);
427
545
  font-weight: 700;
428
546
  font-family: ui-monospace, "SFMono-Regular", monospace;
429
547
  }
@@ -431,27 +549,58 @@ const scopeChoices = [
431
549
  margin: 0;
432
550
  display: flex;
433
551
  align-items: center;
434
- gap: 0.6rem;
435
- font-size: 1.5rem;
552
+ gap: var(--grim-space-4);
553
+ font-size: var(--grim-text-xl);
436
554
  font-family: ui-monospace, "SFMono-Regular", monospace;
437
555
  }
438
556
  .summary {
439
- margin: 0.3rem 0 0;
440
- color: var(--muted);
557
+ margin: var(--grim-space-2) 0 0;
558
+ color: var(--grim-color-muted);
559
+ }
560
+ .byline {
561
+ margin: var(--grim-space-3) 0 0;
562
+ color: var(--grim-color-muted);
563
+ font-size: var(--grim-text-sm);
564
+ }
565
+ /* The kind badge names a closed set of five and shouts accordingly. A
566
+ compatibility hint is a free string off the registry — upper-casing it
567
+ would misspell whatever the publisher actually wrote. */
568
+ .badge.compat {
569
+ text-transform: none;
570
+ letter-spacing: normal;
571
+ font-family: ui-monospace, "SFMono-Regular", monospace;
572
+ }
573
+ .support ul {
574
+ list-style: none;
575
+ margin: 0;
576
+ padding: 0;
577
+ display: flex;
578
+ flex-direction: column;
579
+ gap: var(--grim-space-4);
580
+ }
581
+ .support li {
582
+ display: flex;
583
+ align-items: center;
584
+ gap: var(--grim-space-3);
585
+ font-size: var(--grim-text-md);
586
+ }
587
+ .support li :global(svg) {
588
+ flex-shrink: 0;
589
+ color: var(--grim-color-muted);
441
590
  }
442
591
  .deprecated-banner {
443
592
  margin: 0;
444
- background: var(--banner-bg);
445
- color: var(--banner-fg);
446
- border: 1px solid var(--banner-border);
447
- border-radius: 8px;
448
- padding: 0.6rem 0.9rem;
593
+ background: var(--grim-color-banner-bg);
594
+ color: var(--grim-color-banner-fg);
595
+ border: var(--grim-border-width) solid var(--grim-color-banner-border);
596
+ border-radius: var(--grim-radius-lg);
597
+ padding: var(--grim-space-4) var(--grim-space-6);
449
598
  font-weight: 600;
450
599
  }
451
600
  .detail-body {
452
601
  display: grid;
453
602
  grid-template-columns: 1fr 18rem;
454
- gap: 2rem;
603
+ gap: var(--grim-space-8);
455
604
  align-items: start;
456
605
  }
457
606
  @media (max-width: 55rem) {
@@ -461,10 +610,10 @@ const scopeChoices = [
461
610
  }
462
611
  .detail-main {
463
612
  min-width: 0;
464
- background: var(--card);
465
- border: 1px solid var(--border);
466
- border-radius: 10px;
467
- padding: 1.25rem 1.5rem;
613
+ background: var(--grim-color-card);
614
+ border: var(--grim-border-width) solid var(--grim-color-border);
615
+ border-radius: var(--grim-radius-xl);
616
+ padding: var(--grim-space-7) var(--grim-space-7);
468
617
  }
469
618
  /* Markdown opens with its own heading, whose margin lands on top of the
470
619
  panel padding and reads as a hole. Every panel sets its own top edge. */
@@ -481,23 +630,26 @@ const scopeChoices = [
481
630
  panel's own top edge rather than a line floating in the content. */
482
631
  .ribbon {
483
632
  display: flex;
484
- gap: 1.25rem;
485
- margin: -1.25rem -1.5rem 1.25rem;
486
- padding: 0 1.5rem;
487
- border-bottom: 1px solid var(--border);
633
+ gap: var(--grim-space-7);
634
+ /* The negation of `.detail-main`'s own padding, written as that token so
635
+ the two cannot drift apart into a bleed that no longer reaches the edge. */
636
+ margin: calc(-1 * var(--grim-space-7)) calc(-1 * var(--grim-space-7))
637
+ var(--grim-space-7);
638
+ padding: 0 var(--grim-space-7);
639
+ border-bottom: var(--grim-border-width) solid var(--grim-color-border);
488
640
  }
489
641
  .ribbon label {
490
- padding: 0.7rem 0;
491
- margin-bottom: -1px; /* the active underline sits on the panel rule */
642
+ padding: var(--grim-space-5) 0;
643
+ margin-bottom: calc(-1 * var(--grim-border-width)); /* the active underline sits on the panel rule */
492
644
  border-bottom: 2px solid transparent;
493
- font-size: 0.85rem;
645
+ font-size: var(--grim-text-sm);
494
646
  text-transform: uppercase;
495
647
  letter-spacing: 0.04em;
496
- color: var(--muted);
648
+ color: var(--grim-color-muted);
497
649
  cursor: pointer;
498
650
  }
499
651
  .ribbon label:hover {
500
- color: var(--fg);
652
+ color: var(--grim-color-fg);
501
653
  }
502
654
  /* Nothing behind it. Still shown, so the ribbon is the same shape on every
503
655
  package page, but visibly not a thing to click. */
@@ -510,7 +662,7 @@ const scopeChoices = [
510
662
  #panel-readme:disabled ~ .ribbon label[for="panel-readme"]:hover,
511
663
  #panel-contents:disabled ~ .ribbon label[for="panel-contents"]:hover,
512
664
  #panel-changelog:disabled ~ .ribbon label[for="panel-changelog"]:hover {
513
- color: var(--muted);
665
+ color: var(--grim-color-muted);
514
666
  }
515
667
  /* Every tab empty: say it once, in the middle. */
516
668
  .panel-empty {
@@ -518,7 +670,7 @@ const scopeChoices = [
518
670
  place-items: center;
519
671
  min-height: 11rem;
520
672
  margin: 0;
521
- color: var(--muted);
673
+ color: var(--grim-color-muted);
522
674
  }
523
675
  .tab-panel {
524
676
  display: none;
@@ -531,16 +683,16 @@ const scopeChoices = [
531
683
  #panel-readme:checked ~ .ribbon label[for="panel-readme"],
532
684
  #panel-contents:checked ~ .ribbon label[for="panel-contents"],
533
685
  #panel-changelog:checked ~ .ribbon label[for="panel-changelog"] {
534
- color: var(--fg);
535
- border-bottom-color: var(--accent);
686
+ color: var(--grim-color-fg);
687
+ border-bottom-color: var(--grim-color-accent);
536
688
  }
537
689
  /* The input carries the focus, the label shows it. */
538
690
  #panel-readme:focus-visible ~ .ribbon label[for="panel-readme"],
539
691
  #panel-contents:focus-visible ~ .ribbon label[for="panel-contents"],
540
692
  #panel-changelog:focus-visible ~ .ribbon label[for="panel-changelog"] {
541
- outline: 2px solid var(--accent);
693
+ outline: 2px solid var(--grim-color-accent);
542
694
  outline-offset: 2px;
543
- border-radius: 3px;
695
+ border-radius: var(--grim-radius-sm);
544
696
  }
545
697
 
546
698
  /* A bundle's member list: what it installs, one row each. A grid, not a
@@ -553,7 +705,7 @@ const scopeChoices = [
553
705
  padding: 0;
554
706
  display: grid;
555
707
  grid-template-columns: auto auto 1fr;
556
- gap: 0.45rem 0.75rem;
708
+ gap: var(--grim-space-4) var(--grim-space-5);
557
709
  align-items: center;
558
710
  }
559
711
  .members li {
@@ -568,8 +720,8 @@ const scopeChoices = [
568
720
  justify-self: start;
569
721
  }
570
722
  .members code {
571
- color: var(--muted);
572
- font-size: 0.82rem;
723
+ color: var(--grim-color-muted);
724
+ font-size: var(--grim-text-sm);
573
725
  overflow-wrap: anywhere;
574
726
  }
575
727
  /* Narrow: three columns of a long ref do not fit, so each member becomes
@@ -586,32 +738,32 @@ const scopeChoices = [
586
738
  .detail-rail {
587
739
  display: flex;
588
740
  flex-direction: column;
589
- gap: 1.25rem;
741
+ gap: var(--grim-space-7);
590
742
  }
591
743
  .detail-rail section {
592
- background: var(--card);
593
- border: 1px solid var(--border);
594
- border-radius: 10px;
595
- padding: 1rem;
744
+ background: var(--grim-color-card);
745
+ border: var(--grim-border-width) solid var(--grim-color-border);
746
+ border-radius: var(--grim-radius-xl);
747
+ padding: var(--grim-space-6);
596
748
  }
597
749
  /* Panel label. The main panel's ribbon labels match this by hand — they
598
750
  are <label>s, not headings, so they cannot join the selector. */
599
751
  .detail-rail h2 {
600
- margin: 0 0 0.6rem;
601
- font-size: 0.85rem;
752
+ margin: 0 0 var(--grim-space-4);
753
+ font-size: var(--grim-text-sm);
602
754
  text-transform: uppercase;
603
755
  letter-spacing: 0.04em;
604
- color: var(--muted);
756
+ color: var(--grim-color-muted);
605
757
  }
606
758
  /* Loose badges, both for the cascade and the older tags — VersionMenu owns
607
759
  the pill itself, this only lays them out. */
608
760
  .cascade {
609
761
  display: flex;
610
762
  flex-wrap: wrap;
611
- gap: 0.3rem;
763
+ gap: var(--grim-space-2);
612
764
  }
613
765
  .older {
614
- margin-top: 0.7rem;
766
+ margin-top: var(--grim-space-5);
615
767
  }
616
768
  /* The UA marker is a glyph that flips between two characters with no
617
769
  in-between; a real chevron can turn. `list-style` kills it everywhere
@@ -621,22 +773,22 @@ const scopeChoices = [
621
773
  .older > summary {
622
774
  display: flex;
623
775
  align-items: center;
624
- gap: 0.35rem;
776
+ gap: var(--grim-space-3);
625
777
  list-style: none;
626
- padding-block: 0.15rem;
778
+ padding-block: var(--grim-space-1);
627
779
  cursor: pointer;
628
- font-size: 0.8rem;
629
- color: var(--muted);
780
+ font-size: var(--grim-text-sm);
781
+ color: var(--grim-color-muted);
630
782
  }
631
783
  .older > summary::-webkit-details-marker {
632
784
  display: none;
633
785
  }
634
786
  .older > summary:hover {
635
- color: var(--fg);
787
+ color: var(--grim-color-fg);
636
788
  }
637
789
  .older > summary :global(svg) {
638
790
  flex-shrink: 0;
639
- transition: transform 160ms ease;
791
+ transition: transform var(--grim-duration-base) ease;
640
792
  }
641
793
  .older[open] > summary :global(svg) {
642
794
  transform: rotate(90deg);
@@ -647,7 +799,7 @@ const scopeChoices = [
647
799
  }
648
800
  }
649
801
  .older[open] > summary {
650
- margin-bottom: 0.5rem;
802
+ margin-bottom: var(--grim-space-4);
651
803
  }
652
804
  dl {
653
805
  margin: 0;
@@ -657,14 +809,14 @@ const scopeChoices = [
657
809
  `stretch` sits each at the top of its own box, so the size difference
658
810
  reads as two lines that never quite line up. */
659
811
  align-items: center;
660
- gap: 0.45rem 0.7rem;
812
+ gap: var(--grim-space-4) var(--grim-space-5);
661
813
  }
662
814
  dt {
663
815
  display: flex;
664
816
  align-items: center;
665
- gap: 0.4rem;
666
- color: var(--muted);
667
- font-size: 0.85rem;
817
+ gap: var(--grim-space-3);
818
+ color: var(--grim-color-muted);
819
+ font-size: var(--grim-text-sm);
668
820
  }
669
821
  /* Every icon is the same box, so the label text lands on one x without an
670
822
  icon column of its own. */
@@ -673,15 +825,15 @@ const scopeChoices = [
673
825
  }
674
826
  dd {
675
827
  margin: 0;
676
- font-size: 0.9rem;
828
+ font-size: var(--grim-text-md);
677
829
  }
678
830
  .keywords {
679
831
  list-style: none;
680
- margin: 0.75rem 0 0;
832
+ margin: var(--grim-space-5) 0 0;
681
833
  padding: 0;
682
834
  display: flex;
683
835
  flex-wrap: wrap;
684
- gap: 0.35rem;
836
+ gap: var(--grim-space-3);
685
837
  }
686
838
  /* The chip is the link, not a box around it — otherwise the padding is
687
839
  dead space the pointer falls through. Bordered and hovering to accent
@@ -690,27 +842,28 @@ const scopeChoices = [
690
842
  that answers differently reads as a different kind of thing. */
691
843
  .keywords a {
692
844
  display: inline-block;
693
- border: 1px solid var(--border);
694
- border-radius: 999px;
695
- background: var(--chip-bg);
696
- padding: 0.15rem 0.6rem;
697
- font-size: 0.78rem;
698
- color: var(--muted);
845
+ border: var(--grim-border-width) solid var(--grim-color-border);
846
+ border-radius: var(--grim-radius-pill);
847
+ background: var(--grim-color-chip-bg);
848
+ padding: var(--grim-space-1) var(--grim-space-4);
849
+ font-size: var(--grim-text-xs);
850
+ color: var(--grim-color-muted);
699
851
  text-decoration: none;
700
852
  transition:
701
- color 120ms ease,
702
- border-color 120ms ease,
703
- background-color 120ms ease;
853
+ color var(--grim-duration-fast) ease,
854
+ border-color var(--grim-duration-fast) ease,
855
+ background-color var(--grim-duration-fast) ease;
704
856
  }
705
857
  .keywords a:hover,
706
858
  .keywords a:focus-visible {
707
- color: var(--accent);
708
- border-color: var(--accent);
709
- background: var(--accent-soft);
859
+ color: var(--grim-color-accent);
860
+ border-color: var(--grim-color-accent);
861
+ background: var(--grim-color-accent-soft);
710
862
  }
711
863
  @media (prefers-reduced-motion: reduce) {
712
864
  .keywords a {
713
865
  transition: none;
714
866
  }
715
867
  }
868
+ }
716
869
  </style>