hd-garden 0.3.13 → 0.4.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 (58) hide show
  1. package/app/a2/[slug]/page.tsx +61 -0
  2. package/app/a2/page.tsx +27 -0
  3. package/app/blog/[...path]/page.tsx +33 -22
  4. package/app/blog/page.tsx +6 -7
  5. package/app/columns/[column]/[...path]/page.tsx +33 -25
  6. package/app/columns/page.tsx +38 -52
  7. package/app/components/ArticleMeasure.tsx +95 -0
  8. package/app/components/ArticleTitle.tsx +20 -0
  9. package/app/components/BlogArchive.tsx +106 -102
  10. package/app/components/MarkdownArticle.tsx +145 -66
  11. package/app/components/MermaidDiagram.tsx +44 -71
  12. package/app/components/SiteFooter.tsx +3 -7
  13. package/app/components/SiteHeader.tsx +55 -41
  14. package/app/components/TableOfContents.tsx +3 -2
  15. package/app/components/TagIndex.tsx +97 -0
  16. package/app/components/garden/GardenBlock.tsx +45 -0
  17. package/app/components/garden/blocks.tsx +530 -0
  18. package/app/components/garden/chrome.tsx +350 -0
  19. package/app/components/garden/sandbox-message.ts +16 -0
  20. package/app/garden-modules.d.ts +2 -0
  21. package/app/globals.css +17 -12
  22. package/app/layout.tsx +9 -0
  23. package/app/lib/a2-meta.ts +22 -0
  24. package/app/lib/a2-preview.ts +50 -0
  25. package/app/lib/content.ts +48 -5
  26. package/app/lib/markdown-plugins.ts +2 -0
  27. package/app/lib/remark-a2.mjs +208 -0
  28. package/app/lib/remark-garden.mjs +88 -0
  29. package/app/page.tsx +122 -93
  30. package/app/styles/a2.css +1383 -0
  31. package/app/styles/shell.css +438 -0
  32. package/app/styles/tokens.css +111 -0
  33. package/app/tags/page.tsx +28 -0
  34. package/app/ui-parity/page.tsx +3 -2
  35. package/package.json +1 -1
  36. package/scripts/build-content.mjs +11 -1
  37. package/scripts/check-post.mjs +18 -4
  38. package/scripts/new-post.mjs +3 -0
  39. package/site.config.yaml +1 -1
  40. package/templates/garden-blocks.md +128 -0
  41. package/templates/garden-snippets.plist +121 -0
  42. package/ui/garden/catalog.mjs +196 -0
  43. package/ui/garden/check.mjs +103 -0
  44. package/ui/garden/enhance.mjs +89 -0
  45. package/ui/garden/parse.mjs +198 -0
  46. package/ui/garden/snippets.mjs +109 -0
  47. package/ui/garden/title.mjs +37 -0
  48. package/ui/garden/word-count.mjs +266 -0
  49. package/ui/host.mjs +45 -28
  50. package/ui/parity-fixtures.mjs +1 -1
  51. package/ui/preview/dist/garden-ui.css +81 -77
  52. package/ui/preview/dist/garden-ui.esm.js +124 -89
  53. package/ui/preview/dist/garden-ui.iife.js +124 -89
  54. package/ui/prompt/rules.md +17 -1
  55. package/ui/react/CatalogPage.tsx +12 -0
  56. package/ui/react/components/display.tsx +1 -1
  57. package/ui/react/components/media.tsx +2 -2
  58. package/ui/react/ui.css +81 -77
@@ -0,0 +1,266 @@
1
+ /**
2
+ * Article word count, the same rule as the A2 demo generator (`lib/page.mjs`):
3
+ *
4
+ * renderedHtml.replace(/<[^>]+>/g, "").replace(/\s/g, "").length
5
+ *
6
+ * There is no second filter that drops code, tables, or math. Whatever text the
7
+ * renderer leaves between tags is counted, including characters that HTML
8
+ * escaping expanded (`&` → `&amp;`). Whitespace of every kind is removed first.
9
+ *
10
+ * What that means for each construct:
11
+ *
12
+ * - Fenced code is included. The source is emitted inside `<pre><code>`.
13
+ * Highlight markup is only tags, so it does not change the count.
14
+ * - Inline code is included.
15
+ * - Tables are included (the cell text).
16
+ * - Math is included as the text KaTeX emits (visible glyphs and the MathML
17
+ * annotation). The TeX source is not counted a second time on top of that.
18
+ * - Garden block props are not counted as YAML keys. Only text the widget
19
+ * actually emits is counted:
20
+ * - `garden html` that is a full document (the demo's `sandbox` fence) goes
21
+ * into an iframe `srcdoc` attribute. The tag strip deletes the attribute,
22
+ * so the document and its props are excluded. A `fallback:` line is
23
+ * emitted in `<noscript>` and is counted.
24
+ * - `garden html` that is already a widget fragment is inlined, so its text
25
+ * (including `<script>` text) is counted.
26
+ * - `garden figure` emits tab labels, part explanations, the caption, and
27
+ * SVG `<text>`. Prop keys and part ids stay in attributes and are excluded.
28
+ * - A heading written `## 1.2 Title` keeps the digits (they move into
29
+ * `.sec-no`). The dot is dropped. The space was never going to be counted.
30
+ *
31
+ * The Go parity article comes out at 35,105. The demo page prints 35,234 for
32
+ * the same prose and the same fenced code. The missing 129 characters are the
33
+ * demo sandbox's `fallback:` paragraph, which that source renders in
34
+ * `<noscript>` and this parity article does not contain.
35
+ */
36
+ import katex from "katex";
37
+ import { Marked } from "marked";
38
+ import { parse as parseYaml } from "yaml";
39
+
40
+ const marked = new Marked({
41
+ gfm: true,
42
+ breaks: false,
43
+ renderer: {
44
+ code(token) {
45
+ const text = typeof token === "string" ? token : token.text ?? "";
46
+ const lang = typeof token === "string" ? "" : token.lang ?? "";
47
+ const name = String(lang).split(/\s/u)[0] ?? "";
48
+ const body = escapeHtml(text);
49
+ if (name === "mermaid") {
50
+ return `<figure class="g-mermaid"><pre class="code mermaid-src"><code>${body}</code></pre></figure>\n`;
51
+ }
52
+ return `<pre class="code"><code class="hljs${name ? ` language-${escapeHtml(name)}` : ""}">${body}</code></pre>\n`;
53
+ },
54
+ },
55
+ extensions: [
56
+ {
57
+ name: "mathBlock",
58
+ level: "block",
59
+ start(src) {
60
+ return src.indexOf("$$");
61
+ },
62
+ tokenizer(src) {
63
+ const match = /^\$\$\s*\n?([\s\S]+?)\n?\$\$\s*(?:\n|$)/u.exec(src);
64
+ if (!match) return undefined;
65
+ return { type: "mathBlock", raw: match[0], tex: match[1] };
66
+ },
67
+ renderer(token) {
68
+ return `<div class="math-display">${katex.renderToString(String(token.tex ?? "").trim(), { displayMode: true, throwOnError: false })}</div>\n`;
69
+ },
70
+ },
71
+ {
72
+ name: "mathInline",
73
+ level: "inline",
74
+ start(src) {
75
+ const index = src.indexOf("$");
76
+ return index < 0 ? undefined : index;
77
+ },
78
+ tokenizer(src) {
79
+ const match = /^\$(?!\s)((?:\\\$|[^$\n])+?)(?<!\s)\$(?!\d)/u.exec(src);
80
+ if (!match) return undefined;
81
+ return { type: "mathInline", raw: match[0], tex: match[1] };
82
+ },
83
+ renderer(token) {
84
+ return katex.renderToString(String(token.tex ?? ""), { throwOnError: false });
85
+ },
86
+ },
87
+ ],
88
+ });
89
+
90
+ function escapeHtml(value) {
91
+ return String(value ?? "")
92
+ .replace(/&/g, "&amp;")
93
+ .replace(/</g, "&lt;")
94
+ .replace(/>/g, "&gt;")
95
+ .replace(/"/g, "&quot;");
96
+ }
97
+
98
+ function splitProps(body) {
99
+ const text = String(body ?? "").replace(/\s+$/u, "");
100
+ const lines = text.split("\n");
101
+ const sep = lines.findIndex((line, index) => index > 0 && /^---\s*$/u.test(line));
102
+ if (sep === -1) return { yamlText: text, children: "" };
103
+ return { yamlText: lines.slice(0, sep).join("\n"), children: lines.slice(sep + 1).join("\n").replace(/^\n/u, "") };
104
+ }
105
+
106
+ function readYaml(yamlText) {
107
+ try {
108
+ const parsed = yamlText.trim() ? parseYaml(yamlText) : {};
109
+ return parsed && typeof parsed === "object" ? parsed : {};
110
+ } catch {
111
+ return null;
112
+ }
113
+ }
114
+
115
+ function isHtmlDocument(body) {
116
+ return /^\s*(?:<!doctype|<html|<style|<script)/iu.test(body);
117
+ }
118
+
119
+ function sandboxHtml(title, body) {
120
+ const fallback = /^fallback:\s*(.*)$/mu.exec(body);
121
+ const src = fallback ? body.replace(/^fallback:\s*.*(?:\n|$)/mu, "") : body;
122
+ const noscript = fallback ? `<noscript><p>${escapeHtml(fallback[1])}</p></noscript>` : "";
123
+ return `<div class="g-sandbox"><iframe title="${escapeHtml(title)}" srcdoc="${escapeHtml(src)}"></iframe>${noscript}</div>`;
124
+ }
125
+
126
+ function inline(value) {
127
+ return marked.parseInline(String(value ?? ""), { async: false });
128
+ }
129
+
130
+ function figureHtml(data, svg) {
131
+ const parts = Array.isArray(data.parts) ? data.parts : [];
132
+ const tabs = parts.map((part) => `<button type="button" data-sel="${escapeHtml(part.id)}">${escapeHtml(part.label ?? "")}</button>`).join("");
133
+ const notes = parts.map((part) => `<p data-exp="${escapeHtml(part.id)}">${inline(part.text ?? "")}</p>`).join("");
134
+ const caption = data.caption ? `<figcaption>${inline(data.caption)}</figcaption>` : "";
135
+ return `<figure class="g-explore"><div role="tablist">${tabs}</div><div class="dx-wrap">${svg}</div><div class="dx-exp">${notes}</div>${caption}</figure>`;
136
+ }
137
+
138
+ function visibleStrings(value, html) {
139
+ if (typeof value === "string") html.push(inline(value));
140
+ else if (Array.isArray(value)) value.forEach((item) => visibleStrings(item, html));
141
+ else if (value && typeof value === "object") {
142
+ for (const [key, item] of Object.entries(value)) {
143
+ if (key === "id" || key === "url" || key === "src" || key === "svg" || key === "svgFile") continue;
144
+ visibleStrings(item, html);
145
+ }
146
+ }
147
+ }
148
+
149
+ function gardenHtml(info, body) {
150
+ const name = info.trim().split(/\s+/u)[0]?.toLowerCase() ?? "";
151
+ const { yamlText, children } = splitProps(body);
152
+ if (name === "html" || name === "interactive") {
153
+ const data = readYaml(yamlText) ?? {};
154
+ const title = typeof data.title === "string" ? data.title : "";
155
+ const document = children.trim() ? children : body;
156
+ if (!children.trim() || isHtmlDocument(document)) return sandboxHtml(title, document);
157
+ return document;
158
+ }
159
+ if (name === "figure") {
160
+ const data = readYaml(yamlText);
161
+ if (!data) return `<pre class="code"><code>${escapeHtml(body)}</code></pre>`;
162
+ return figureHtml(data, children);
163
+ }
164
+ const data = readYaml(yamlText);
165
+ if (!data) return `<pre class="code"><code>${escapeHtml(body)}</code></pre>`;
166
+ const bits = [];
167
+ visibleStrings(data, bits);
168
+ if (children.trim()) bits.push(children);
169
+ return bits.join("");
170
+ }
171
+
172
+ function renderBlocks(markdown) {
173
+ const lines = String(markdown ?? "").split("\n");
174
+ const parts = [];
175
+ let buffer = [];
176
+ const flush = () => {
177
+ if (!buffer.length) return;
178
+ parts.push(marked.parse(buffer.join("\n"), { async: false }));
179
+ buffer = [];
180
+ };
181
+ for (let index = 0; index < lines.length; index += 1) {
182
+ const opened = /^(`{3,}|~{3,})(.*)$/u.exec(lines[index]);
183
+ if (!opened) {
184
+ buffer.push(lines[index]);
185
+ continue;
186
+ }
187
+ const marker = opened[1];
188
+ const info = opened[2].trim();
189
+ const lang = info.split(/\s+/u)[0]?.toLowerCase() ?? "";
190
+ const body = [];
191
+ index += 1;
192
+ while (index < lines.length && !new RegExp(`^${marker[0]}{${marker.length},}\\s*$`, "u").test(lines[index])) {
193
+ body.push(lines[index]);
194
+ index += 1;
195
+ }
196
+ if (lang === "garden") {
197
+ flush();
198
+ parts.push(gardenHtml(info.slice("garden".length).trim(), body.join("\n")));
199
+ } else {
200
+ buffer.push(lines[index - body.length - 1], ...body, lines[index] ?? marker);
201
+ }
202
+ }
203
+ flush();
204
+ return parts.join("\n");
205
+ }
206
+
207
+ function numberHeadings(html, markdown) {
208
+ let h2 = 0;
209
+ let h3 = 0;
210
+ const numbered = /^#{2,3}\s+\d/mu.test(markdown);
211
+ const minLevel = /^##\s/mu.test(markdown) ? 2 : 3;
212
+ return html.replace(/<h([234])>(.*?)<\/h\1>/gu, (all, level, inner) => {
213
+ const sourceLevel = Number(level);
214
+ const mapped = sourceLevel - minLevel + 2;
215
+ if (mapped > 3) return `<h4>${inner}</h4>`;
216
+ const text = inner
217
+ .replace(/<[^>]+>/g, "")
218
+ .replace(/&quot;/g, "\"")
219
+ .replace(/&#39;/g, "'")
220
+ .replace(/&lt;/g, "<")
221
+ .replace(/&gt;/g, ">")
222
+ .replace(/&amp;/g, "&");
223
+ const numberedHeading = /^(\d+(?:\.\d+)*)\.?\s+(.*)$/u.exec(text);
224
+ let number = "";
225
+ let body = inner;
226
+ if (numberedHeading) {
227
+ number = numberedHeading[1];
228
+ body = inner.replace(/^(\d+(?:\.\d+)*)\.?\s+/u, "");
229
+ } else if (!numbered) {
230
+ if (mapped === 2) {
231
+ h2 += 1;
232
+ h3 = 0;
233
+ number = String(h2);
234
+ } else {
235
+ h3 += 1;
236
+ number = h2 ? `${h2}.${h3}` : String(h3);
237
+ }
238
+ }
239
+ return `<h${mapped}>${number ? `<span class="sec-no">${number}</span>` : ""}${body}</h${mapped}>`;
240
+ });
241
+ }
242
+
243
+ const ALERT_TITLE = { note: "提醒", tip: "建议", important: "重要", warning: "警告", caution: "注意" };
244
+
245
+ function githubAlerts(html) {
246
+ return html.replace(/<blockquote>\s*<p>\[!(NOTE|TIP|IMPORTANT|WARNING|CAUTION)\][ \t]*([^\n<]*)\n?/gu, (_, kind, title) => {
247
+ const name = String(kind).toLowerCase();
248
+ const label = title.trim() ? escapeHtml(title.trim()) : ALERT_TITLE[name];
249
+ return `<blockquote class="markdown-alert markdown-alert-${name}"><p class="markdown-alert-title">${label}</p><p>`;
250
+ });
251
+ }
252
+
253
+ function captionImages(html) {
254
+ return html.replace(/<p>(<img [^>]+>)<\/p>/gu, (_, img) => {
255
+ const alt = (/alt="([^"]*)"/u.exec(img) ?? [])[1] ?? "";
256
+ const title = (/title="([^"]*)"/u.exec(img) ?? [])[1] ?? "";
257
+ const caption = title || (alt && !/^image|^截图|^img/iu.test(alt) ? alt : "");
258
+ return `<figure class="fig"><div class="fig-card">${img}</div>${caption ? `<figcaption>${caption}</figcaption>` : ""}</figure>`;
259
+ });
260
+ }
261
+
262
+ /** Rendered-article word count. Pure: no DOM, safe to run while generating content. */
263
+ export function countDemoWords(markdown) {
264
+ const html = githubAlerts(captionImages(numberHeadings(renderBlocks(markdown), markdown)));
265
+ return html.replace(/<[^>]+>/g, "").replace(/\s/g, "").length;
266
+ }
package/ui/host.mjs CHANGED
@@ -53,26 +53,34 @@ export function isHttpsUrl(value) {
53
53
 
54
54
  const THEMES = {
55
55
  light: {
56
- color: "#0d0d0d",
57
- text: "#0d0d0d",
58
- muted: "#5d5d5d",
59
- subtle: "#8f8f8f",
60
- panel: "#ffffff",
61
- border: "#0000001a",
62
- accent: "#0169cc",
63
- series: "#0285ff",
64
- track: "#0d0d0d1f",
56
+ ink: "#1B1A18",
57
+ text: "#2D2C29",
58
+ soft: "#4A4844",
59
+ muted: "#6E6B64",
60
+ grey: "#737069",
61
+ rule: "#E6E3DC",
62
+ ruleStrong: "#D6D2C9",
63
+ card: "#FFFFFF",
64
+ paper: "#F8F7F3",
65
+ well: "#F0EEE8",
66
+ blue: "#2C55B4",
67
+ blueTint: "#EAF0FC",
68
+ blueLine: "rgba(44,85,180,.28)",
65
69
  },
66
70
  dark: {
67
- color: "#ededed",
68
- text: "#ededed",
69
- muted: "#cdcdcd",
70
- subtle: "#afafaf",
71
- panel: "#212121",
72
- border: "#ffffff1a",
73
- accent: "#66b5ff",
74
- series: "#0285ff",
75
- track: "#ffffff26",
71
+ ink: "#EEECE7",
72
+ text: "#DCD9D2",
73
+ soft: "#BDB9B0",
74
+ muted: "#A09B91",
75
+ grey: "#A09B91",
76
+ rule: "#2F2D29",
77
+ ruleStrong: "#45423C",
78
+ card: "#1E1D1A",
79
+ paper: "#161513",
80
+ well: "#22211D",
81
+ blue: "#93B2F4",
82
+ blueTint: "rgba(147,178,244,.13)",
83
+ blueLine: "rgba(147,178,244,.35)",
76
84
  },
77
85
  };
78
86
 
@@ -80,19 +88,28 @@ function tokenCss(theme) {
80
88
  const token = THEMES[theme] ?? THEMES.light;
81
89
  return `
82
90
  :root {
83
- color: ${token.color};
91
+ color: ${token.text};
92
+ color-scheme: ${theme === "dark" ? "dark" : "light"};
84
93
  background: transparent;
85
- font-family: -apple-system-body, ui-sans-serif, system-ui, "Segoe UI", Helvetica, Arial, sans-serif;
86
- --viz-text: ${token.text};
87
- --viz-muted: ${token.muted};
88
- --viz-subtle: ${token.subtle};
89
- --viz-panel: ${token.panel};
90
- --viz-border: ${token.border};
91
- --viz-accent: ${token.accent};
92
- --viz-series: ${token.series};
93
- --viz-track: ${token.track};
94
+ font-family: -apple-system, BlinkMacSystemFont, "PingFang SC", "Hiragino Sans GB", "Noto Sans SC", sans-serif;
95
+ --ink: ${token.ink};
96
+ --text: ${token.text};
97
+ --soft: ${token.soft};
98
+ --muted: ${token.muted};
99
+ --grey: ${token.grey};
100
+ --rule: ${token.rule};
101
+ --rule-strong: ${token.ruleStrong};
102
+ --card: ${token.card};
103
+ --paper: ${token.paper};
104
+ --well: ${token.well};
105
+ --blue: ${token.blue};
106
+ --blue-tint: ${token.blueTint};
107
+ --blue-line: ${token.blueLine};
94
108
  }
109
+ html, body { background: transparent !important; overflow: hidden !important; height: auto !important; }
95
110
  body { margin: 0; }
111
+ body > :first-child { padding-right: 44px; box-sizing: border-box; }
112
+ svg { max-width: 100%; height: auto; }
96
113
  `;
97
114
  }
98
115
 
@@ -70,7 +70,7 @@ fmt.Println(b)\`} />
70
70
  id: "parity-sandbox",
71
71
  width: 768,
72
72
  body: `${header("parity-sandbox", "目录以外的交互放在无网络的画布里。")}
73
- <Sandbox title="计数" html={"<p id=\\"n\\">0</p><button id=\\"b\\" type=\\"button\\">加一</button>"} css="button{color:var(--viz-accent)}" js="document.getElementById('b').onclick=()=>{const n=document.getElementById('n');n.textContent=String(Number(n.textContent)+1)}" />
73
+ <Sandbox title="计数" html={"<p id=\\"n\\">0</p><button id=\\"b\\" type=\\"button\\">加一</button>"} css="button{color:var(--blue)}" js="document.getElementById('b').onclick=()=>{const n=document.getElementById('n');n.textContent=String(Number(n.textContent)+1)}" />
74
74
  `,
75
75
  },
76
76
  ];
@@ -6,41 +6,41 @@
6
6
  .garden-ui,
7
7
  .garden-catalog,
8
8
  .garden-ui-preview {
9
- --gui-bg: #fff;
10
- --gui-chart: #f4f4f4;
11
- --gui-track: #e4e4e4;
12
- --gui-thumb: #fff;
13
- --gui-slider-track: #0d0d0d1f;
14
- --gui-slider-thumb: #333;
15
- --gui-text: #0d0d0d;
16
- --gui-muted: #5d5d5d;
17
- --gui-subtle: #8f8f8f;
18
- --gui-completed: #777;
19
- --gui-border: #0000001a;
20
- --gui-border-soft: #00000014;
21
- --gui-border-strong: #0000001f;
22
- --gui-control-border: #ccc;
23
- --gui-check-border: #d2d2d2;
24
- --gui-control-bg: #fff;
25
- --gui-surface: #f5f5f5;
26
- --gui-chip: #f3f3f3;
27
- --gui-hover: #f5f5f5;
28
- --gui-solid: #171717;
29
- --gui-solid-hover: #303030;
30
- --gui-on-solid: #fff;
31
- --gui-segment: #ededed;
32
- --gui-segment-selected: #fff;
33
- --gui-segment-shadow: 0 1px 4px -1px #0003;
34
- --gui-progress: #0d0d0d;
35
- --gui-progress-track: #f3f3f3;
36
- --gui-focus: #0285ff;
37
- --gui-bubble: #eaf2ff;
38
- --gui-popover-border: #0000000d;
39
- --gui-shadow: 0 2px 6px #00000014;
40
- --gui-code-bg: #f9f9f9;
41
- --gui-accent: #0285ff;
42
- --gui-series-plain: #171717;
43
- --gui-series-accent: #0285ff;
9
+ --gui-bg: var(--card, #fff);
10
+ --gui-chart: var(--well);
11
+ --gui-track: var(--rule, #e6e3dc);
12
+ --gui-thumb: var(--card, #fff);
13
+ --gui-slider-track: var(--rule, #e6e3dc);
14
+ --gui-slider-thumb: var(--ink, #1b1a18);
15
+ --gui-text: var(--ink, #1b1a18);
16
+ --gui-muted: var(--muted, #6e6b64);
17
+ --gui-subtle: var(--grey, #737069);
18
+ --gui-completed: var(--muted, #6e6b64);
19
+ --gui-border: var(--rule, #e6e3dc);
20
+ --gui-border-soft: var(--rule, #e6e3dc);
21
+ --gui-border-strong: var(--rule-strong, #d6d2c9);
22
+ --gui-control-border: var(--rule-strong, #d6d2c9);
23
+ --gui-check-border: var(--rule-strong, #d6d2c9);
24
+ --gui-control-bg: var(--card, #fff);
25
+ --gui-surface: var(--well, #f0eee8);
26
+ --gui-chip: var(--well, #f0eee8);
27
+ --gui-hover: var(--well, #f0eee8);
28
+ --gui-solid: var(--ink, #1b1a18);
29
+ --gui-solid-hover: var(--soft, #4a4844);
30
+ --gui-on-solid: var(--paper, #f8f7f3);
31
+ --gui-segment: var(--well, #f0eee8);
32
+ --gui-segment-selected: var(--card, #fff);
33
+ --gui-segment-shadow: none;
34
+ --gui-progress: var(--blue, #2c55b4);
35
+ --gui-progress-track: var(--well, #f0eee8);
36
+ --gui-focus: var(--blue, #2c55b4);
37
+ --gui-bubble: var(--blue-tint, #eaf0fc);
38
+ --gui-popover-border: var(--rule, #e6e3dc);
39
+ --gui-shadow: none;
40
+ --gui-code-bg: var(--well, #f0eee8);
41
+ --gui-accent: var(--blue, #2c55b4);
42
+ --gui-series-plain: var(--ink, #1b1a18);
43
+ --gui-series-accent: var(--blue);
44
44
  --gui-series-new: #00a240;
45
45
  --gui-series-waste: #e02e2a;
46
46
  --gui-series-same: #924ff7;
@@ -56,7 +56,7 @@
56
56
  --gui-discovery: #924ff7;
57
57
  --gui-discovery-text: #8046d9;
58
58
  --gui-discovery-soft: #924ff71a;
59
- --gui-font: -apple-system-body, ui-sans-serif, -apple-system, system-ui, "Segoe UI", Helvetica, "Apple Color Emoji", Arial, sans-serif, "Segoe UI Emoji", "Segoe UI Symbol";
59
+ --gui-font: var(--sans, -apple-system, BlinkMacSystemFont, "PingFang SC", "Hiragino Sans GB", "Noto Sans SC", sans-serif);
60
60
  --gui-mono: ui-monospace, "SF Mono", SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace;
61
61
  }
62
62
 
@@ -67,40 +67,41 @@
67
67
  .garden-ui[data-theme="dark"],
68
68
  .garden-catalog[data-theme="dark"],
69
69
  .garden-ui-preview[data-theme="dark"] {
70
- --gui-bg: #0e0e0e;
71
- --gui-chart: #212121;
72
- --gui-text: #ededed;
73
- --gui-muted: #cdcdcd;
74
- --gui-subtle: #afafaf;
75
- --gui-completed: #afafaf;
76
- --gui-border: #ffffff1a;
77
- --gui-border-soft: #ffffff0d;
78
- --gui-border-strong: #fff3;
79
- --gui-control-border: #fff3;
80
- --gui-check-border: #fff3;
81
- --gui-control-bg: #303030;
82
- --gui-surface: #2a2a2a;
83
- --gui-chip: #303030;
84
- --gui-hover: #ffffff1a;
85
- --gui-solid: #ededed;
86
- --gui-solid-hover: #dcdcdc;
87
- --gui-on-solid: #0d0d0d;
88
- --gui-segment: #303030;
89
- --gui-segment-selected: #303030;
70
+ --gui-bg: var(--card);
71
+ --gui-chart: var(--well);
72
+ --gui-text: var(--ink);
73
+ --gui-muted: var(--muted);
74
+ --gui-subtle: var(--grey);
75
+ --gui-completed: var(--muted);
76
+ --gui-border: var(--rule);
77
+ --gui-border-soft: var(--rule);
78
+ --gui-border-strong: var(--rule-strong);
79
+ --gui-control-border: var(--rule-strong);
80
+ --gui-check-border: var(--rule-strong);
81
+ --gui-control-bg: var(--card);
82
+ --gui-surface: var(--well);
83
+ --gui-chip: var(--well);
84
+ --gui-hover: var(--tint);
85
+ --gui-solid: var(--ink);
86
+ --gui-solid-hover: var(--soft);
87
+ --gui-on-solid: var(--paper);
88
+ --gui-segment: var(--well);
89
+ --gui-segment-selected: var(--card);
90
90
  --gui-segment-shadow: none;
91
- --gui-track: #3a3a3a;
92
- --gui-thumb: #fff;
93
- --gui-slider-track: #ffffff26;
94
- --gui-slider-thumb: #ededed;
95
- --gui-progress: #0285ff;
96
- --gui-progress-track: #303030;
97
- --gui-focus: #3a83f7;
98
- --gui-bubble: #133463;
99
- --gui-popover-border: #ffffff26;
100
- --gui-shadow: 0 2px 8px #00000066;
101
- --gui-code-bg: #171717;
102
- --gui-accent: #0285ff;
103
- --gui-series-plain: #ededed;
91
+ --gui-track: var(--rule);
92
+ --gui-thumb: var(--card);
93
+ --gui-slider-track: var(--rule-strong);
94
+ --gui-slider-thumb: var(--ink);
95
+ --gui-progress: var(--blue);
96
+ --gui-progress-track: var(--well);
97
+ --gui-focus: var(--blue);
98
+ --gui-bubble: var(--blue-tint);
99
+ --gui-popover-border: var(--rule);
100
+ --gui-shadow: none;
101
+ --gui-code-bg: var(--well);
102
+ --gui-accent: var(--blue);
103
+ --gui-series-plain: var(--ink);
104
+ --gui-series-accent: var(--blue);
104
105
  --gui-series-new: #04b84c;
105
106
  --gui-series-waste: #fa423e;
106
107
  --gui-info: #0285ff;
@@ -919,13 +920,14 @@
919
920
  line-height: 1.3 !important;
920
921
  letter-spacing: normal !important;
921
922
  }
922
- .garden-ui-diagram-canvas .garden-ui-diagram-on rect,
923
- .garden-ui-diagram-canvas .garden-ui-diagram-on polygon,
924
- .garden-ui-diagram-canvas .garden-ui-diagram-on circle,
925
- .garden-ui-diagram-canvas rect.garden-ui-diagram-on { fill: var(--gui-info-soft) !important; stroke: var(--gui-accent) !important; }
926
- .garden-ui-diagram-canvas .garden-ui-diagram-on .nodeLabel,
927
- .garden-ui-diagram-canvas .garden-ui-diagram-on text { color: var(--gui-accent) !important; fill: var(--gui-accent) !important; }
928
- .garden-ui-diagram-canvas path.garden-ui-diagram-on { stroke: var(--gui-accent) !important; stroke-width: 2px !important; }
923
+ /* Selected nodes keep the 「全部」 look, text included. Only the rest fades. */
924
+ .garden-ui-diagram-canvas:has(.garden-ui-diagram-on) .node:not(.garden-ui-diagram-on),
925
+ .garden-ui-diagram-canvas:has(.garden-ui-diagram-on) .statediagram-state:not(.garden-ui-diagram-on),
926
+ .garden-ui-diagram-canvas:has(.garden-ui-diagram-on) .cluster:not(.garden-ui-diagram-on) { opacity: 0.22; }
927
+ .garden-ui-diagram-canvas .garden-ui-diagram-on,
928
+ .garden-ui-diagram-canvas .garden-ui-diagram-on text,
929
+ .garden-ui-diagram-canvas .garden-ui-diagram-on .nodeLabel { opacity: 1; }
930
+ .garden-ui-diagram-canvas path.garden-ui-diagram-on { stroke: var(--gui-accent); stroke-width: 2px; }
929
931
 
930
932
  .garden-ui-sandbox {
931
933
  display: block;
@@ -1131,7 +1133,9 @@
1131
1133
  position: relative;
1132
1134
  top: -2px;
1133
1135
  }
1134
- .garden-ui-cite img { width: 12px; height: 12px; border-radius: 2px; }
1136
+ .garden-ui-cite img,
1137
+ .garden-ui-cite-icon { width: 12px; height: 12px; border-radius: 2px; }
1138
+ .garden-ui-cite-icon { display: inline-block; background-position: center; background-size: contain; background-repeat: no-repeat; vertical-align: -2px; }
1135
1139
 
1136
1140
  .garden-ui-pie { display: flex; flex-direction: column; align-items: stretch; gap: 0; margin: 0; }
1137
1141
  .garden-ui-pie-title { align-self: stretch; margin: 0 0 8px; color: var(--gui-text); font-size: 16px; font-weight: 600; line-height: 26px; text-align: start; }