@cloudgrid-io/ui 0.33.0 → 0.35.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.
Files changed (126) hide show
  1. package/README.md +25 -0
  2. package/dist/badge/css-live.d.ts +7 -4
  3. package/dist/badge/css-live.d.ts.map +1 -1
  4. package/dist/badge/css-live.js +9 -6
  5. package/dist/badge/css-live.js.map +1 -1
  6. package/dist/badge/palette.d.ts +1 -1
  7. package/dist/badge/palette.d.ts.map +1 -1
  8. package/dist/badge/palette.js +2 -1
  9. package/dist/badge/palette.js.map +1 -1
  10. package/dist/blocks/bar-chart.d.ts +97 -0
  11. package/dist/blocks/bar-chart.d.ts.map +1 -0
  12. package/dist/blocks/bar-chart.js +25 -0
  13. package/dist/blocks/bar-chart.js.map +1 -0
  14. package/dist/blocks/chart-parts.d.ts +101 -0
  15. package/dist/blocks/chart-parts.d.ts.map +1 -0
  16. package/dist/blocks/chart-parts.js +96 -0
  17. package/dist/blocks/chart-parts.js.map +1 -0
  18. package/dist/blocks/coming-soon-tag.d.ts +34 -0
  19. package/dist/blocks/coming-soon-tag.d.ts.map +1 -0
  20. package/dist/blocks/coming-soon-tag.js +37 -0
  21. package/dist/blocks/coming-soon-tag.js.map +1 -0
  22. package/dist/blocks/disabled-button.d.ts +43 -0
  23. package/dist/blocks/disabled-button.d.ts.map +1 -0
  24. package/dist/blocks/disabled-button.js +50 -0
  25. package/dist/blocks/disabled-button.js.map +1 -0
  26. package/dist/blocks/entity-card.d.ts.map +1 -1
  27. package/dist/blocks/entity-card.js +3 -1
  28. package/dist/blocks/entity-card.js.map +1 -1
  29. package/dist/blocks/full-screen-button.d.ts +9 -0
  30. package/dist/blocks/full-screen-button.d.ts.map +1 -0
  31. package/dist/blocks/full-screen-button.js +51 -0
  32. package/dist/blocks/full-screen-button.js.map +1 -0
  33. package/dist/blocks/icons.d.ts +30 -0
  34. package/dist/blocks/icons.d.ts.map +1 -1
  35. package/dist/blocks/icons.js +30 -0
  36. package/dist/blocks/icons.js.map +1 -1
  37. package/dist/blocks/invite-popup.d.ts +156 -0
  38. package/dist/blocks/invite-popup.d.ts.map +1 -0
  39. package/dist/blocks/invite-popup.js +99 -0
  40. package/dist/blocks/invite-popup.js.map +1 -0
  41. package/dist/blocks/meter.d.ts +57 -0
  42. package/dist/blocks/meter.d.ts.map +1 -0
  43. package/dist/blocks/meter.js +19 -0
  44. package/dist/blocks/meter.js.map +1 -0
  45. package/dist/blocks/notice.d.ts +87 -0
  46. package/dist/blocks/notice.d.ts.map +1 -0
  47. package/dist/blocks/notice.js +26 -0
  48. package/dist/blocks/notice.js.map +1 -0
  49. package/dist/blocks/pager.d.ts +76 -0
  50. package/dist/blocks/pager.d.ts.map +1 -0
  51. package/dist/blocks/pager.js +86 -0
  52. package/dist/blocks/pager.js.map +1 -0
  53. package/dist/blocks/phosphor-marks.d.ts +20 -0
  54. package/dist/blocks/phosphor-marks.d.ts.map +1 -1
  55. package/dist/blocks/phosphor-marks.js +30 -0
  56. package/dist/blocks/phosphor-marks.js.map +1 -1
  57. package/dist/blocks/ranked-list.d.ts +67 -0
  58. package/dist/blocks/ranked-list.d.ts.map +1 -0
  59. package/dist/blocks/ranked-list.js +32 -0
  60. package/dist/blocks/ranked-list.js.map +1 -0
  61. package/dist/blocks/side-nav.d.ts.map +1 -1
  62. package/dist/blocks/side-nav.js +3 -1
  63. package/dist/blocks/side-nav.js.map +1 -1
  64. package/dist/blocks/sign-in-options.d.ts.map +1 -1
  65. package/dist/blocks/sign-in-options.js +3 -1
  66. package/dist/blocks/sign-in-options.js.map +1 -1
  67. package/dist/blocks/stacked-bar.d.ts +64 -0
  68. package/dist/blocks/stacked-bar.d.ts.map +1 -0
  69. package/dist/blocks/stacked-bar.js +18 -0
  70. package/dist/blocks/stacked-bar.js.map +1 -0
  71. package/dist/blocks/stat-tile.d.ts +42 -1
  72. package/dist/blocks/stat-tile.d.ts.map +1 -1
  73. package/dist/blocks/stat-tile.js +23 -4
  74. package/dist/blocks/stat-tile.js.map +1 -1
  75. package/dist/blocks/tab-bar.d.ts.map +1 -1
  76. package/dist/blocks/tab-bar.js +3 -1
  77. package/dist/blocks/tab-bar.js.map +1 -1
  78. package/dist/blocks/timeline.d.ts +53 -0
  79. package/dist/blocks/timeline.d.ts.map +1 -0
  80. package/dist/blocks/timeline.js +18 -0
  81. package/dist/blocks/timeline.js.map +1 -0
  82. package/dist/blocks/tool-setup.d.ts.map +1 -1
  83. package/dist/blocks/tool-setup.js +3 -1
  84. package/dist/blocks/tool-setup.js.map +1 -1
  85. package/dist/blocks/trend.d.ts +33 -0
  86. package/dist/blocks/trend.d.ts.map +1 -0
  87. package/dist/blocks/trend.js +13 -0
  88. package/dist/blocks/trend.js.map +1 -0
  89. package/dist/claim-banner/css.d.ts +39 -0
  90. package/dist/claim-banner/css.d.ts.map +1 -0
  91. package/dist/claim-banner/css.js +87 -0
  92. package/dist/claim-banner/css.js.map +1 -0
  93. package/dist/claim-banner/index.d.ts +122 -0
  94. package/dist/claim-banner/index.d.ts.map +1 -0
  95. package/dist/claim-banner/index.js +135 -0
  96. package/dist/claim-banner/index.js.map +1 -0
  97. package/dist/docs.css +466 -0
  98. package/dist/expressive-code-palette.d.ts +5 -0
  99. package/dist/expressive-code-palette.d.ts.map +1 -0
  100. package/dist/expressive-code-palette.js +10 -0
  101. package/dist/expressive-code-palette.js.map +1 -0
  102. package/dist/expressive-code.d.ts +57 -0
  103. package/dist/expressive-code.d.ts.map +1 -0
  104. package/dist/expressive-code.js +101 -0
  105. package/dist/expressive-code.js.map +1 -0
  106. package/dist/index.d.ts +13 -0
  107. package/dist/index.d.ts.map +1 -1
  108. package/dist/index.js +13 -0
  109. package/dist/index.js.map +1 -1
  110. package/dist/tokens/base.json +13 -0
  111. package/dist/tokens/surface-ink.json +13 -0
  112. package/dist/tokens/surface-panel.json +13 -0
  113. package/dist/tokens.css +36 -0
  114. package/dist/tokens.json +6 -3
  115. package/dist/ui/card.d.ts +23 -2
  116. package/dist/ui/card.d.ts.map +1 -1
  117. package/dist/ui/card.js +16 -5
  118. package/dist/ui/card.js.map +1 -1
  119. package/dist/ui/drawer.d.ts +37 -6
  120. package/dist/ui/drawer.d.ts.map +1 -1
  121. package/dist/ui/drawer.js +31 -7
  122. package/dist/ui/drawer.js.map +1 -1
  123. package/dist/ui/input-group.d.ts.map +1 -1
  124. package/dist/ui/input-group.js +3 -1
  125. package/dist/ui/input-group.js.map +1 -1
  126. package/package.json +18 -3
@@ -0,0 +1,135 @@
1
+ /**
2
+ * THE GUEST CLAIM BANNER, FRAMEWORK-FREE (ui#342, for cloudgrid#4872).
3
+ *
4
+ * The top bar on every unclaimed guest drop: "This site disappears in N days
5
+ * unless it's claimed." and a lime "Claim it" pill, with a close x that hides
6
+ * it for the visit. The platform's inspiration-server builds HTML strings, so
7
+ * this is the badge's shape: a builder that returns a string, and the
8
+ * stylesheet and the inline script as strings beside it. No React, no DOM.
9
+ *
10
+ * ```ts
11
+ * import { claimBanner } from "@cloudgrid-io/ui/claim-banner"
12
+ * const ctaMarkup = claimBanner({ claimUrl, expiresAt: entity.inspiration_block?.expires_at, nonce })
13
+ * ```
14
+ *
15
+ * What the consumer's constraints settle (cloudgrid#4872):
16
+ *
17
+ * byte-identical nothing here depends on the viewer or the clock. The day
18
+ * count is computed by the inline script, in the browser,
19
+ * from `data-expires-at`, so a cached render is right for
20
+ * every viewer on every day
21
+ * no flash the banner is `hidden` in the bytes; the script reveals it
22
+ * unless this visit dismissed it (`sessionStorage`)
23
+ * nonce with `nonce`, the `<style>` and `<script>` carry it, for
24
+ * the wrapper's `script-src 'nonce-...'`; without it they
25
+ * carry nothing, for the legacy inline path
26
+ * escaping every caller value is text; the URL is attribute-escaped
27
+ * and refused (`#`) unless it is http(s)
28
+ * sentinel nothing here says `data-cloudgrid-badge`, so the badge
29
+ * injector's idempotency check never trips on it
30
+ * fonts no `@font-face` and no `@import`; the UI stack falls back
31
+ * to the system faces under `font-src 'self' api.cloudgrid.io`
32
+ */
33
+ import { esc, glyph, href, wordmark } from "../badge/html.js";
34
+ import { X } from "../badge/marks.js";
35
+ import { BADGE_FONT_FACES } from "../badge/palette.js";
36
+ import { CLAIM_BANNER_CSS } from "./css.js";
37
+ export { CLAIM_BANNER_CSS, CLAIM_BANNER_PHONE_MAX, CLAIM_BANNER_TOKEN_NAMES } from "./css.js";
38
+ /** The banner element's id. The script finds the banner by it. */
39
+ export const CLAIM_BANNER_ID = "cg-claim-banner";
40
+ /**
41
+ * The `sessionStorage` key the close x sets to "1": hidden for this visit, back
42
+ * in a new tab. The platform's own banner's key (cloudgrid#4978), so a viewer
43
+ * who dismissed that one is not shown this one again in the same visit.
44
+ */
45
+ export const CLAIM_BANNER_DISMISS_KEY = "cg_anon_cta_dismissed";
46
+ /** The days shown when the drop carries no expiry, or one the browser cannot read. */
47
+ export const CLAIM_BANNER_FALLBACK_DAYS = 7;
48
+ /** The button's label. The arrow after it is decorative. */
49
+ export const CLAIM_BANNER_LABEL = "Claim it";
50
+ /** The close x's accessible name. */
51
+ export const CLAIM_BANNER_CLOSE_LABEL = "Hide for this visit";
52
+ /**
53
+ * The one face the banner sets type in: Manrope, the badge's file. The banner
54
+ * loads nothing itself. On the wrapper the badge already registers it; a page
55
+ * without the badge hosts this file (the package carries it at
56
+ * `@cloudgrid-io/ui/brand/badge/<file>`) and declares it. Without it the system
57
+ * stack renders.
58
+ */
59
+ export const CLAIM_BANNER_FONT_FACES = BADGE_FONT_FACES.filter((f) => f.family === "Manrope Variable");
60
+ /**
61
+ * The inline script. ES5, no globals left behind. It finds the banner, leaves
62
+ * it hidden if this visit dismissed it, writes the day count, reveals it, and
63
+ * wires the close x. Storage that throws (a sandbox, a privacy mode) counts as
64
+ * "not dismissed", and the x still hides the banner for the page.
65
+ *
66
+ * The count is in whole calendar days in the viewer's time zone, from today to
67
+ * the expiry's date: "in N days", "in 1 day", "today" on the last day. A past
68
+ * expiry reads "today", never a negative number (the platform's expired page
69
+ * normally answers first). No expiry, or one that does not parse, reads
70
+ * "in 7 days".
71
+ *
72
+ * With `placement: "fixed"` (`data-placement="fixed"` on the banner) the bar is
73
+ * fixed to the top of the viewport, and the script pads the body by the bar's
74
+ * height (rounded up, so a fractional bar never overlaps), on top of the body's
75
+ * own top padding, so it covers nothing. It keeps
76
+ * the padding in step on resize (the bar wraps on a narrow screen) and puts the
77
+ * body's own padding back on the x.
78
+ */
79
+ export const CLAIM_BANNER_SCRIPT = "(function(){" +
80
+ `var b=document.getElementById(${JSON.stringify(CLAIM_BANNER_ID)});if(!b)return;` +
81
+ `var k=${JSON.stringify(CLAIM_BANNER_DISMISS_KEY)};` +
82
+ 'try{if(window.sessionStorage.getItem(k)==="1")return}catch(e){}' +
83
+ `var d=${CLAIM_BANNER_FALLBACK_DAYS},a=b.getAttribute("data-expires-at");` +
84
+ "if(a){var t=new Date(a);if(!isNaN(t.getTime())){var n=new Date();" +
85
+ "d=Math.round((new Date(t.getFullYear(),t.getMonth(),t.getDate())-new Date(n.getFullYear(),n.getMonth(),n.getDate()))/864e5);" +
86
+ "if(!(d>0))d=0}}" +
87
+ 'var w=b.querySelector("[data-claim-when]");' +
88
+ 'if(w)w.textContent=d===0?"today":"in "+d+(d===1?" day":" days");' +
89
+ "b.hidden=false;" +
90
+ 'var f=b.getAttribute("data-placement")==="fixed",y=document.body,s0="",p0=0;' +
91
+ 'function pad(){if(!b.hidden)y.style.paddingTop=(p0+Math.ceil(b.getBoundingClientRect().height))+"px"}' +
92
+ 'if(f&&y){s0=y.style.paddingTop;p0=parseFloat(window.getComputedStyle(y).paddingTop)||0;pad();window.addEventListener("resize",pad)}' +
93
+ 'var x=b.querySelector("[data-claim-dismiss]");' +
94
+ 'if(x)x.addEventListener("click",function(){b.hidden=true;if(f&&y)y.style.paddingTop=s0;try{window.sessionStorage.setItem(k,"1")}catch(e){}})' +
95
+ "})();";
96
+ /**
97
+ * A caller value for an attribute: escaped, and the badge injector's sentinel
98
+ * broken with a character reference, so even a slug that spells it out cannot
99
+ * put `data-cloudgrid-badge` in the bytes. The browser reads the value unchanged.
100
+ */
101
+ function safe(value) {
102
+ return value.replace(/data-cloudgrid-badge/gi, (m) => m.replace("-", "&#45;"));
103
+ }
104
+ function expiryAttr(expiresAt) {
105
+ if (expiresAt == null)
106
+ return "";
107
+ if (expiresAt instanceof Date) {
108
+ return Number.isNaN(expiresAt.getTime()) ? "" : ` data-expires-at="${safe(esc(expiresAt.toISOString()))}"`;
109
+ }
110
+ const value = String(expiresAt).trim();
111
+ return value ? ` data-expires-at="${safe(esc(value))}"` : "";
112
+ }
113
+ /**
114
+ * The banner's markup alone: the bar, hidden, with the fallback count in its
115
+ * bytes. Needs `CLAIM_BANNER_CSS` on the page and `CLAIM_BANNER_SCRIPT` after it.
116
+ */
117
+ export function claimBannerMarkup({ claimUrl, expiresAt, placement = "flow" }) {
118
+ const fixed = placement === "fixed" ? ' data-placement="fixed"' : "";
119
+ return (`<div id="${CLAIM_BANNER_ID}" class="cgcb" role="note"${fixed}${expiryAttr(expiresAt)} hidden>` +
120
+ `<span class="cgcb-mark">${wordmark("cgcb-wordmark")}</span>` +
121
+ `<span class="cgcb-msg"><b>This site disappears <span data-claim-when>in ${CLAIM_BANNER_FALLBACK_DAYS} days</span></b> unless it&#39;s claimed.</span>` +
122
+ `<a class="cgcb-btn" href="${safe(href(claimUrl))}">${CLAIM_BANNER_LABEL} <span aria-hidden="true">&rarr;</span></a>` +
123
+ `<button class="cgcb-x" type="button" data-claim-dismiss aria-label="${CLAIM_BANNER_CLOSE_LABEL}">${glyph(X)}</button>` +
124
+ `</div>`);
125
+ }
126
+ /**
127
+ * The whole drop-in: the stylesheet, the banner, and the script that reveals
128
+ * it, in that order. One string, the same for every viewer of a drop; with a
129
+ * `nonce`, the same for every viewer of one response.
130
+ */
131
+ export function claimBanner({ claimUrl, expiresAt, nonce, placement }) {
132
+ const n = nonce ? ` nonce="${safe(esc(nonce))}"` : "";
133
+ return `<style${n}>${CLAIM_BANNER_CSS}</style>` + claimBannerMarkup({ claimUrl, expiresAt, placement }) + `<script${n}>${CLAIM_BANNER_SCRIPT}</script>`;
134
+ }
135
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/claim-banner/index.ts"],"names":[],"mappings":"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"}
package/dist/docs.css ADDED
@@ -0,0 +1,466 @@
1
+ /*
2
+ A PRODUCT PRESET for the docs site (docs.cloudgrid.io, Astro Starlight). ui#341.
3
+
4
+ It styles Starlight entirely from this package's tokens, so the docs repo
5
+ keeps an import and its config and nothing else: no hex, no font stack, no
6
+ second copy of the brand.
7
+
8
+ HOW. Starlight paints almost everything from about sixty `--sl-*` custom
9
+ properties. Part one of this file maps them onto `--cg-*` tokens. Part two is
10
+ a short list of targeted rules, only where Starlight hard-wires a pairing the
11
+ brand does not want or has no variable at all. Every value is a token; a test
12
+ (test/docs-preset.test.mjs) holds the file to that.
13
+
14
+ LIGHT ONLY (founder, 2026-10-19). This package has no dark mode: dark is a
15
+ container concern (`data-cg-surface='ink'`), never a page mode. So the dark
16
+ scope is not styled here at all. The consumer forces `data-theme='light'` and
17
+ removes the theme picker; without that, a dark-OS reader would get this
18
+ palette's light values under Starlight's own dark logo and code theme.
19
+
20
+ IMPORT ORDER. After tokens.css, which is what every var() below reads:
21
+
22
+ customCss: [
23
+ '@cloudgrid-io/ui/fonts.css',
24
+ '@cloudgrid-io/ui/tokens.css',
25
+ '@cloudgrid-io/ui/docs.css',
26
+ ]
27
+
28
+ UNLAYERED ON PURPOSE. Starlight puts its own rules in `@layer starlight.*`,
29
+ and an unlayered rule beats any layered one whatever the specificity or the
30
+ order. That is why the targeted rules below can be plain class selectors.
31
+
32
+ SAME ELEMENT, SAME RESOLUTION. Rule 2 of this library: a custom property that
33
+ references another resolves where it is declared. Starlight declares its
34
+ derived variables (`--sl-color-text: var(--sl-color-gray-2)`) on :root, and
35
+ this file remaps the ramp on :root too, so both resolve on the same element.
36
+ Do not flip tokens per container inside the docs; it would not follow.
37
+
38
+ PROSE PARITY. prose.css cannot be applied to Starlight's markup (it is scoped
39
+ to `.prose`, and adding that class means overriding MarkdownContent and then
40
+ fighting `.prose pre` on Expressive Code). The few element rules are mirrored
41
+ here against `.sl-markdown-content`, with the same tokens, and
42
+ test/docs-preset.test.mjs fails if the two drift.
43
+
44
+ STARLIGHT COUPLING. Measured against @astrojs/starlight 0.42.6. Part one uses
45
+ the documented `--sl-*` contract. Part two depends on a handful of internal
46
+ selectors (`#_top`, `.sidebar-content`, `.sl-link-button`, `.card`,
47
+ `.sl-link-card`, `.pagination-links`, `button[data-open-modal] > kbd`,
48
+ `#starlight__search`). A Starlight upgrade can rename one silently; take
49
+ upgrades with a screenshot check.
50
+
51
+ NOT IN THIS FILE. Icons stay Starlight's own until the Phosphor follow-up.
52
+ Code blocks are styled by the Expressive Code export,
53
+ `@cloudgrid-io/ui/expressive-code`, plus the one rule below that Expressive
54
+ Code has no setting for.
55
+ */
56
+
57
+ /* ---- part one: Starlight's variables, onto the tokens ---------------------- */
58
+
59
+ /* `::backdrop` because Starlight declares its colours there too: the search
60
+ dialog's backdrop reads --sl-color-backdrop-overlay from it. */
61
+ :root,
62
+ ::backdrop {
63
+ /* The neutral ramp. Starlight's `white` is its highest-contrast ink and its
64
+ `black` is the page, so the ramp runs from text-primary to the ground. */
65
+ --sl-color-white: var(--cg-text-primary);
66
+ --sl-color-gray-1: var(--cg-text-primary);
67
+ /* Body text. prose.css sets `p, li` in text-secondary and `strong` in
68
+ text-primary, and so does this. */
69
+ --sl-color-gray-2: var(--cg-text-secondary);
70
+ --sl-color-gray-3: var(--cg-text-muted-strong);
71
+ --sl-color-gray-4: var(--cg-text-muted);
72
+ /* Structural borders: the search control, table rules, cards. */
73
+ --sl-color-gray-5: var(--cg-border-control);
74
+ --sl-color-gray-6: var(--cg-border-hairline);
75
+ --sl-color-gray-7: var(--cg-surface-subtle);
76
+ /* The library's page ground, with white cards on it (founder, 2026-10-19). */
77
+ --sl-color-black: var(--cg-surface-page);
78
+
79
+ /* The accent triplet: selection tint, focus and active marks. */
80
+ --sl-color-accent-low: var(--cg-primary-tint);
81
+ --sl-color-accent: var(--cg-primary);
82
+ --sl-color-accent-high: var(--cg-primary-press);
83
+
84
+ /* The hue triplets, `-low` fill, base, `-high` text. Each is a library tone.
85
+ Caution borrows the expired state's amber: an editorial role on a state
86
+ tone, stated here rather than minted as a new token. The note (blue) has no
87
+ tinted rung in the library, so its fill is the neutral raised surface. */
88
+ --sl-color-purple-low: var(--cg-primary-tint);
89
+ --sl-color-purple: var(--cg-primary);
90
+ --sl-color-purple-high: var(--cg-primary-press);
91
+ --sl-color-orange-low: var(--cg-expired-fill);
92
+ --sl-color-orange: var(--cg-expired);
93
+ --sl-color-orange-high: var(--cg-expired-strong);
94
+ --sl-color-red-low: var(--cg-danger-fill);
95
+ --sl-color-red: var(--cg-danger);
96
+ --sl-color-red-high: var(--cg-danger-strong);
97
+ --sl-color-green-low: var(--cg-online-fill);
98
+ --sl-color-green: var(--cg-online);
99
+ --sl-color-green-high: var(--cg-online-strong);
100
+ --sl-color-blue-low: var(--cg-surface-raised);
101
+ --sl-color-blue: var(--cg-accent);
102
+ --sl-color-blue-high: var(--cg-accent-press);
103
+
104
+ /* The semantic slots Starlight derives, set explicitly so none of them
105
+ falls back to Starlight's own light values. */
106
+ --sl-color-text: var(--cg-text-secondary);
107
+ --sl-color-text-accent: var(--cg-primary);
108
+ --sl-color-text-invert: var(--cg-text-on-primary);
109
+ --sl-color-bg: var(--cg-surface-page);
110
+ --sl-color-bg-nav: var(--cg-surface-card);
111
+ --sl-color-bg-sidebar: var(--cg-surface-page);
112
+ --sl-color-bg-inline-code: var(--cg-surface-page-alt);
113
+ --sl-color-bg-accent: var(--cg-primary);
114
+ --sl-color-hairline-light: var(--cg-border-hairline);
115
+ --sl-color-hairline: var(--cg-border-hairline);
116
+ --sl-color-hairline-shade: var(--cg-border-hairline);
117
+ --sl-color-backdrop-overlay: var(--cg-scrim);
118
+
119
+ /* The brand is flat and contains by hairlines; the card shadow is the one it
120
+ uses under a white card on the ground. */
121
+ --sl-shadow-sm: var(--cg-shadow-card);
122
+ --sl-shadow-md: var(--cg-shadow-card);
123
+ --sl-shadow-lg: var(--cg-shadow-raised);
124
+
125
+ /* Type. Space Grotesk for everything a reader reads, headings included, and
126
+ no Tanker anywhere (founder, 2026-10-19). */
127
+ --sl-font: var(--cg-font-body);
128
+ --sl-font-mono: var(--cg-font-mono);
129
+
130
+ /* The size scale. Nothing under 13px: Starlight's 12px rung (the search
131
+ shortcut) lands on the caption, the brand's floor. */
132
+ --sl-text-2xs: var(--cg-caption);
133
+ --sl-text-xs: var(--cg-caption);
134
+ --sl-text-sm: var(--cg-body-14);
135
+ --sl-text-base: var(--cg-body-md);
136
+ --sl-text-lg: var(--cg-body-18);
137
+ --sl-text-xl: var(--cg-body-xl);
138
+ --sl-text-2xl: var(--cg-title-24);
139
+ --sl-text-3xl: var(--cg-display-sm);
140
+ --sl-text-4xl: var(--cg-title-36);
141
+ --sl-text-5xl: var(--cg-display-lg);
142
+ --sl-text-6xl: var(--cg-display-64);
143
+ --sl-text-body: var(--cg-body-md);
144
+ --sl-text-body-sm: var(--cg-caption);
145
+ --sl-text-code: var(--cg-mono-size);
146
+ --sl-text-code-sm: var(--cg-caption);
147
+
148
+ /* Headings follow prose.css: h1 is its clamp, h2 and h3 its two rungs, and
149
+ h4 and h5 continue down the heading ramp. Starlight re-declares h1 to h4
150
+ above 50em inside its layer; this unlayered block wins at every width. */
151
+ --sl-text-h1: clamp(var(--cg-display-sm), 5vw, var(--cg-display-lg));
152
+ --sl-text-h2: var(--cg-heading-lg);
153
+ --sl-text-h3: var(--cg-heading-md);
154
+ --sl-text-h4: var(--cg-heading-sm);
155
+ --sl-text-h5: var(--cg-body-md);
156
+
157
+ --sl-line-height: var(--cg-body-line);
158
+ --sl-line-height-headings: var(--cg-heading-line);
159
+ }
160
+
161
+ /* Starlight's reset sets `color-scheme: dark` under a dark theme. Light only,
162
+ so scrollbars and form controls stay light whatever the OS says. */
163
+ :root {
164
+ color-scheme: light;
165
+ }
166
+
167
+ /* ---- part two: targeted rules -------------------------------------------- */
168
+
169
+ /* Headings: the body face at medium, as the library sets a heading. `#_top` is
170
+ Starlight's page title (PAGE_TITLE_ID); it defaults to 600. */
171
+ .sl-markdown-content :is(h1, h2, h3, h4, h5, h6):not(:where(.not-content *)),
172
+ #_top,
173
+ .hero h1 {
174
+ font-weight: var(--cg-weight-medium);
175
+ }
176
+
177
+ /* The heading anchor glyph, quiet until hovered. */
178
+ .sl-markdown-content .sl-anchor-link {
179
+ color: var(--cg-text-muted);
180
+ }
181
+
182
+ /* prose.css: strong is the ink. */
183
+ .sl-markdown-content strong:not(:where(.not-content *)) {
184
+ color: var(--cg-text-primary);
185
+ font-weight: var(--cg-weight-bold);
186
+ }
187
+
188
+ /* prose.css links: --cg-link, the accent blue since #197, one link colour
189
+ across the brand (founder, 2026-10-19). Inside an aside too: no per-callout
190
+ link colour. */
191
+ .sl-markdown-content a:not(:where(.not-content *)) {
192
+ color: var(--cg-link);
193
+ text-decoration-thickness: 1px;
194
+ text-underline-offset: 3px;
195
+ }
196
+
197
+ .sl-markdown-content a:hover:not(:where(.not-content *)) {
198
+ color: var(--cg-link-hover);
199
+ }
200
+
201
+ /* prose.css inline code. `:not(pre) >` keeps it off code inside a block. */
202
+ .sl-markdown-content :not(pre) > code:not(:where(.not-content *)) {
203
+ background: var(--cg-surface-page-alt);
204
+ border: var(--cg-border-width) solid var(--cg-border-hairline);
205
+ border-radius: var(--cg-radius-xs);
206
+ padding: 0.1em 0.35em;
207
+ font-size: 0.9em;
208
+ overflow-wrap: anywhere;
209
+ }
210
+
211
+ /* No chip inside a heading: the mono face only (founder, 2026-10-19). Every CLI
212
+ h2 is a command, and a chip at heading size reads as a button. */
213
+ .sl-markdown-content :is(h1, h2, h3, h4, h5, h6) code:not(:where(.not-content *)) {
214
+ background: none;
215
+ border: 0;
216
+ padding: 0;
217
+ }
218
+
219
+ /* Inside a tinted callout the chip is the white card, so it reads as a chip
220
+ on every tone instead of a mix of the tone. */
221
+ .sl-markdown-content .starlight-aside :not(pre) > code:not(:where(.not-content *)) {
222
+ background: var(--cg-surface-card);
223
+ }
224
+
225
+ /* prose.css blockquote. Unused in the content today, kept so a first one does
226
+ not arrive in Starlight's grey. */
227
+ .sl-markdown-content blockquote:not(:where(.not-content *)) {
228
+ border-inline-start: var(--cg-border-width-strong) solid var(--cg-border-accent);
229
+ color: var(--cg-text-primary);
230
+ }
231
+
232
+ /* prose.css tables: hairline cells, a tinted header row. The cells are white
233
+ so a table reads as a card on the ground.
234
+
235
+ THE TABLE IS ITS OWN SCROLL CONTAINER. Starlight renders a bare <table> with
236
+ no wrapper, so the scroll goes on the table itself: `display: block` with
237
+ `overflow-x: auto`, exactly prose.css's recipe. A table wider than the column
238
+ scrolls inside itself and the page never scrolls sideways. */
239
+ .sl-markdown-content table:not(:where(.not-content *)) {
240
+ border-collapse: collapse;
241
+ font-size: var(--cg-body-sm);
242
+ display: block;
243
+ width: 100%;
244
+ max-width: max-content;
245
+ overflow-x: auto;
246
+ }
247
+
248
+ /* Code in a cell never breaks inside a word. prose.css's `overflow-wrap:
249
+ anywhere` on inline code also lowers a cell's minimum width, so on a phone
250
+ the table squeezed `inside` into `insid` / `e` rather than scrolling
251
+ (founder, 2026-10-19). `nowrap` keeps a flag such as `--require-signin` whole
252
+ too, which `overflow-wrap: normal` alone would still break after a hyphen;
253
+ the table scrolls instead. */
254
+ .sl-markdown-content :is(th, td) code:not(:where(.not-content *)) {
255
+ white-space: nowrap;
256
+ overflow-wrap: normal;
257
+ }
258
+
259
+ .sl-markdown-content :is(th, td):not(:where(.not-content *)) {
260
+ border: var(--cg-border-width) solid var(--cg-border-hairline);
261
+ padding: var(--cg-space-2) var(--cg-space-3);
262
+ background: var(--cg-surface-card);
263
+ }
264
+
265
+ .sl-markdown-content th:not(:where(.not-content *)) {
266
+ background: var(--cg-surface-page-alt);
267
+ font-weight: var(--cg-weight-bold);
268
+ }
269
+
270
+ /* Asides: a hairline box with the library's corner, in place of Starlight's
271
+ 4px bar. Each type takes its tone's own border rung. */
272
+ .starlight-aside {
273
+ border: var(--cg-border-width) solid var(--sl-color-asides-border);
274
+ border-radius: var(--cg-radius-md);
275
+ }
276
+
277
+ .starlight-aside--note {
278
+ --sl-color-asides-border: var(--cg-border-control);
279
+ --sl-color-asides-text-accent: var(--cg-text-primary);
280
+ }
281
+
282
+ .starlight-aside--tip {
283
+ --sl-color-asides-border: var(--cg-primary-tint);
284
+ }
285
+
286
+ .starlight-aside--caution {
287
+ --sl-color-asides-border: var(--cg-expired-border);
288
+ }
289
+
290
+ .starlight-aside--danger {
291
+ --sl-color-asides-border: var(--cg-danger-border);
292
+ }
293
+
294
+ /* Sidebar, as the library SideNav draws it: a group header and its rows in
295
+ the body size, rounded rows, the hover wash, and the current page as the
296
+ brand-tint chip rather than Starlight's solid purple bar. */
297
+ .sidebar-content .large {
298
+ font-size: var(--cg-body-sm);
299
+ font-weight: var(--cg-weight-medium);
300
+ color: var(--cg-text-primary);
301
+ }
302
+
303
+ .sidebar-content summary {
304
+ border-radius: var(--cg-radius-lg);
305
+ }
306
+
307
+ .sidebar-content summary:hover {
308
+ background: var(--cg-hover);
309
+ }
310
+
311
+ .sidebar-content a {
312
+ font-size: var(--cg-body-sm);
313
+ color: var(--cg-text-secondary);
314
+ border-radius: var(--cg-radius-lg);
315
+ }
316
+
317
+ .sidebar-content a:hover,
318
+ .sidebar-content a:focus {
319
+ background: var(--cg-hover);
320
+ color: var(--cg-text-primary);
321
+ }
322
+
323
+ .sidebar-content a[aria-current='page'],
324
+ .sidebar-content a[aria-current='page']:hover,
325
+ .sidebar-content a[aria-current='page']:focus {
326
+ background: var(--cg-primary-tint);
327
+ color: var(--cg-primary);
328
+ font-weight: var(--cg-weight-medium);
329
+ }
330
+
331
+ /* The header's social link, quiet ink rather than the accent. */
332
+ .social-icons a {
333
+ color: var(--cg-text-secondary);
334
+ }
335
+
336
+ .social-icons a:hover {
337
+ color: var(--cg-text-primary);
338
+ }
339
+
340
+ /* The search control: a white control with the library's control corner. */
341
+ @media (min-width: 50rem) {
342
+ button[data-open-modal] {
343
+ background-color: var(--cg-surface-card);
344
+ border-radius: var(--cg-radius-sm);
345
+ }
346
+ }
347
+
348
+ /* The search shortcut, a key cap at the caption size, the brand's floor. */
349
+ button[data-open-modal] > kbd {
350
+ font-size: var(--cg-caption);
351
+ background-color: var(--cg-surface-control);
352
+ border-radius: var(--cg-radius-2xs);
353
+ }
354
+
355
+ /* Pagefind sets its own scale to 0.8, which puts result text at 11 to 13px.
356
+ At 1 it is 14, 16 and 21px. */
357
+ #starlight__search {
358
+ --pagefind-ui-scale: 1;
359
+ --pagefind-ui-font: var(--cg-font-body);
360
+ --pagefind-ui-border-radius: var(--cg-radius-sm);
361
+ }
362
+
363
+ /* Hero buttons as the library's primary Button: the brand fill with its own
364
+ on-primary text. Starlight paints that text in --sl-color-black, which is
365
+ now the page ground, not white. */
366
+ .sl-link-button {
367
+ font-family: var(--cg-font-ui);
368
+ font-weight: var(--cg-label-weight);
369
+ }
370
+
371
+ .sl-link-button.primary,
372
+ .sl-link-button.primary:hover {
373
+ background: var(--cg-primary);
374
+ border-color: var(--cg-primary);
375
+ color: var(--cg-text-on-primary);
376
+ }
377
+
378
+ .sl-link-button.primary:hover {
379
+ background: var(--cg-primary-press);
380
+ border-color: var(--cg-primary-press);
381
+ }
382
+
383
+ /* Cards on the ground, as the library Card: white, a hairline ring, the card
384
+ corner. One brand tone for every icon tile instead of Starlight's rotation
385
+ through five hues; unlayered, this beats its :nth-child rules outright. */
386
+ .card,
387
+ .sl-link-card,
388
+ .pagination-links a {
389
+ background-color: var(--cg-surface-card);
390
+ border-color: var(--cg-border-hairline);
391
+ border-radius: var(--cg-radius-xl);
392
+ }
393
+
394
+ .card {
395
+ --sl-card-border: var(--cg-primary-tint);
396
+ --sl-card-bg: var(--cg-primary-tint);
397
+ }
398
+
399
+ .card .icon {
400
+ color: var(--cg-primary);
401
+ border-radius: var(--cg-radius-2xs);
402
+ }
403
+
404
+ .card .title {
405
+ font-weight: var(--cg-weight-medium);
406
+ }
407
+
408
+ /* The Card body at the library Card's size. Starlight's clamp bottoms out at
409
+ 14px on a phone, where inline code at prose's 0.9em measured 12.6px. */
410
+ .card .body {
411
+ font-size: var(--cg-body-sm);
412
+ }
413
+
414
+ .sl-link-card:hover,
415
+ .pagination-links a:hover {
416
+ background-color: var(--cg-surface-card);
417
+ border-color: var(--cg-border-control);
418
+ }
419
+
420
+ /* Expressive Code draws its copy button at a fixed 0.2rem corner and has no
421
+ setting for it. The library's copy control is a pill. */
422
+ .expressive-code .copy button,
423
+ .expressive-code .copy button div {
424
+ border-radius: var(--cg-radius-pill);
425
+ }
426
+
427
+ /* NO WINDOW DOTS (founder, 2026-10-19: "the icons are not relevant").
428
+ Expressive Code draws a terminal frame for every sh/bash block: a title bar
429
+ with three window dots and nothing else in it. The dots go, and so does the
430
+ empty bar they sat in, so a terminal block is the same ink box as every other
431
+ block, the way CodeBlock draws one.
432
+
433
+ The bar is collapsed, never `display: none`. It holds the screen-reader label
434
+ "Terminal window", and hiding the bar would remove that text from the page.
435
+ A titled terminal frame keeps its bar for the title, without the dots. */
436
+ .expressive-code .frame.is-terminal .header::before {
437
+ content: none;
438
+ }
439
+
440
+ .expressive-code .frame.is-terminal:not(.has-title) {
441
+ /* The copy button's offset from the top: the bar is gone, so it sits where
442
+ an untitled code frame's does. */
443
+ --button-spacing: var(--cg-space-2);
444
+ }
445
+
446
+ .expressive-code .frame.is-terminal:not(.has-title) .header {
447
+ padding: 0;
448
+ border: 0;
449
+ }
450
+
451
+ .expressive-code .frame.is-terminal:not(.has-title) .header .title,
452
+ .expressive-code .frame.is-terminal:not(.has-title) .header::after {
453
+ display: none;
454
+ }
455
+
456
+ /* With the bar gone the code box carries the top edge and corners again,
457
+ which Expressive Code removes from any terminal frame. */
458
+ .expressive-code .frame.is-terminal:not(.has-title) pre {
459
+ border-top: var(--cg-border-width) solid var(--cg-surface-code);
460
+ border-top-left-radius: var(--header-border-radius);
461
+ border-top-right-radius: var(--header-border-radius);
462
+ }
463
+
464
+ .expressive-code .frame:not(.has-title):not(.is-terminal) {
465
+ --button-spacing: var(--cg-space-2);
466
+ }
@@ -0,0 +1,5 @@
1
+ export declare const CODE_PALETTE: {
2
+ readonly background: "#222222";
3
+ readonly foreground: "#e7e7e7";
4
+ };
5
+ //# sourceMappingURL=expressive-code-palette.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"expressive-code-palette.d.ts","sourceRoot":"","sources":["../src/expressive-code-palette.ts"],"names":[],"mappings":"AAMA,eAAO,MAAM,YAAY;;;CAGf,CAAA"}
@@ -0,0 +1,10 @@
1
+ // GENERATED by scripts/build-expressive-code.mjs from src/tokens.css. Do not edit.
2
+ //
3
+ // Expressive Code does colour arithmetic on its theme at build time and cannot
4
+ // read a CSS custom property there, so these two are the resolved values of the
5
+ // tokens named beside them, at :root. Retune the token and rebuild.
6
+ export const CODE_PALETTE = {
7
+ background: "#222222", // --cg-surface-code
8
+ foreground: "#e7e7e7", // --cg-text-on-code
9
+ };
10
+ //# sourceMappingURL=expressive-code-palette.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"expressive-code-palette.js","sourceRoot":"","sources":["../src/expressive-code-palette.ts"],"names":[],"mappings":"AAAA,mFAAmF;AACnF,EAAE;AACF,+EAA+E;AAC/E,gFAAgF;AAChF,oEAAoE;AAEpE,MAAM,CAAC,MAAM,YAAY,GAAG;IAC1B,UAAU,EAAE,SAAS,EAAE,oBAAoB;IAC3C,UAAU,EAAE,SAAS,EAAE,oBAAoB;CACnC,CAAA"}