hd-garden 0.4.1 → 0.5.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 (55) hide show
  1. package/.agents/skills/garden-interactive-chart/SKILL.md +5 -5
  2. package/Makefile +2 -3
  3. package/app/a2/[slug]/page.tsx +0 -1
  4. package/app/a2/page.tsx +1 -1
  5. package/app/about/page.tsx +8 -12
  6. package/app/admin/page.tsx +2 -2
  7. package/app/blog/[...path]/page.tsx +2 -12
  8. package/app/blog/page.tsx +2 -2
  9. package/app/columns/[column]/[...path]/page.tsx +2 -3
  10. package/app/columns/page.tsx +2 -2
  11. package/app/components/AdminDashboard.tsx +3 -3
  12. package/app/components/ArticleMeasure.tsx +114 -42
  13. package/app/components/ArticleTags.tsx +17 -0
  14. package/app/components/BlogArchive.tsx +85 -33
  15. package/app/components/ContentEmptyState.tsx +6 -6
  16. package/app/components/GardenAdminGuide.tsx +1 -1
  17. package/app/components/GardenContentGuide.tsx +1 -2
  18. package/app/components/GardenMediaGuide.tsx +3 -3
  19. package/app/components/GithubProfile.tsx +1 -1
  20. package/app/components/MarkdownImage.tsx +4 -2
  21. package/app/components/SiteFooter.tsx +5 -5
  22. package/app/components/SiteHeader.tsx +1 -5
  23. package/app/components/TagRedirect.tsx +17 -0
  24. package/app/garden/page.tsx +6 -6
  25. package/app/garden-modules.d.ts +0 -1
  26. package/app/globals.css +19 -101
  27. package/app/lib/a2-meta.ts +10 -0
  28. package/app/lib/a2-preview.ts +0 -2
  29. package/app/lib/content.ts +10 -7
  30. package/app/lib/fallback-site-config.ts +15 -15
  31. package/app/lib/generated-content.ts +58 -47
  32. package/app/lib/tag-filter.ts +114 -0
  33. package/app/not-found.tsx +13 -0
  34. package/app/page.tsx +9 -26
  35. package/app/styles/a2.css +96 -15
  36. package/app/styles/shell.css +54 -59
  37. package/app/styles/tokens.css +35 -22
  38. package/app/tags/page.tsx +20 -9
  39. package/package.json +1 -1
  40. package/scripts/build-content.mjs +3 -3
  41. package/scripts/check-post.mjs +0 -3
  42. package/scripts/new-post.mjs +1 -3
  43. package/site.config.yaml +15 -15
  44. package/templates/site/Makefile +2 -3
  45. package/templates/site/site.config.yaml +15 -15
  46. package/ui/garden/check.mjs +2 -4
  47. package/ui/garden/enhance.mjs +57 -0
  48. package/ui/garden/title.mjs +5 -8
  49. package/ui/host.mjs +13 -13
  50. package/ui/preview/dist/garden-ui.css +11 -11
  51. package/ui/preview/dist/garden-ui.esm.js +6 -6
  52. package/ui/preview/dist/garden-ui.iife.js +7 -7
  53. package/ui/react/components/media.tsx +2 -2
  54. package/ui/react/ui.css +11 -11
  55. package/app/components/TagIndex.tsx +0 -97
package/app/styles/a2.css CHANGED
@@ -105,7 +105,8 @@ body {
105
105
  }
106
106
 
107
107
  .post-reading-page {
108
- width: min(1340px, 100%);
108
+ /* rail + gap + 740px column + gap + rail, plus side padding */
109
+ width: min(1360px, 100%);
109
110
  padding-right: 32px;
110
111
  padding-left: 32px;
111
112
  }
@@ -191,6 +192,55 @@ body {
191
192
  font-size: var(--fs-meta);
192
193
  }
193
194
 
195
+ .article-tagrow {
196
+ display: flex;
197
+ flex-wrap: wrap;
198
+ justify-content: center;
199
+ gap: 8px;
200
+ margin: 14px 0 0;
201
+ padding: 0;
202
+ list-style: none;
203
+ }
204
+
205
+ .article-tagrow li {
206
+ margin: 0;
207
+ padding: 0;
208
+ }
209
+
210
+ .article-tagrow .tag-chip {
211
+ position: relative;
212
+ display: inline-block;
213
+ padding: 2px 10px;
214
+ border-radius: 999px;
215
+ background: var(--chip);
216
+ color: var(--soft);
217
+ font-size: var(--fs-meta);
218
+ line-height: 1.6;
219
+ text-decoration: none;
220
+ white-space: nowrap;
221
+ }
222
+
223
+ /* 44px tall hit area without enlarging the chip */
224
+ .article-tagrow .tag-chip::after {
225
+ content: "";
226
+ position: absolute;
227
+ left: 0;
228
+ right: 0;
229
+ top: 50%;
230
+ height: 44px;
231
+ transform: translateY(-50%);
232
+ }
233
+
234
+ .article-tagrow .tag-chip:hover {
235
+ background: var(--chip-hover);
236
+ color: var(--ink);
237
+ }
238
+
239
+ .article-tagrow .tag-chip:focus-visible {
240
+ outline: 2px solid var(--blue);
241
+ outline-offset: 2px;
242
+ }
243
+
194
244
  .cover-card.cover-img,
195
245
  .article-hero.article-cover {
196
246
  width: 100%;
@@ -262,7 +312,7 @@ body {
262
312
 
263
313
  .post-reading-page .e-content,
264
314
  .column-reading-page .e-content {
265
- --qa-bar: #e2e4e8;
315
+ --qa-bar: #e8e6dc;
266
316
  --qa-text: #737069;
267
317
  }
268
318
 
@@ -277,7 +327,7 @@ html[data-theme="dark"] .column-reading-page .e-content {
277
327
  .lead.quote-plain {
278
328
  margin: 0 0 32px;
279
329
  padding: 2px 0 2px 24px;
280
- border-left: 3px solid var(--qa-bar, #e2e4e8);
330
+ border-left: 3px solid var(--qa-bar, #e8e6dc);
281
331
  border-radius: 0;
282
332
  background: none;
283
333
  color: var(--qa-text, #737069);
@@ -331,7 +381,13 @@ html[data-theme="dark"] .column-reading-page .e-content {
331
381
  text-decoration: underline;
332
382
  text-decoration-thickness: 1px;
333
383
  text-underline-offset: 4px;
334
- text-decoration-color: rgb(27 26 24 / 28%);
384
+ text-decoration-color: color-mix(in srgb, var(--ink) 35%, transparent);
385
+ transition: text-decoration-color 0.15s ease;
386
+ }
387
+
388
+ .markdown-body a:hover {
389
+ text-decoration-color: var(--ink);
390
+ text-decoration-thickness: 2px;
335
391
  }
336
392
 
337
393
  .markdown-body a.ext {
@@ -349,6 +405,7 @@ html[data-theme="dark"] .markdown-body a.ext {
349
405
  .markdown-body a.ext:hover {
350
406
  text-decoration-style: solid;
351
407
  text-decoration-color: var(--ink);
408
+ text-decoration-thickness: 2px;
352
409
  }
353
410
 
354
411
  .markdown-body :not(pre) > code {
@@ -381,9 +438,9 @@ html[data-theme="dark"] .markdown-body a.ext {
381
438
  }
382
439
 
383
440
  .markdown-body.prose {
384
- --al-note: #2c55b4;
385
- --al-note-bg: #eef3fc;
386
- --al-note-line: rgba(44, 85, 180, 0.22);
441
+ --al-note: #30302e;
442
+ --al-note-bg: #f0eee6;
443
+ --al-note-line: rgba(20, 20, 19, 0.18);
387
444
  --al-tip: #2d6a3e;
388
445
  --al-tip-bg: #edf5ef;
389
446
  --al-tip-line: rgba(45, 106, 62, 0.22);
@@ -402,9 +459,9 @@ html[data-theme="dark"] .markdown-body a.ext {
402
459
  :root[data-theme="dark"] .prose,
403
460
  html[data-theme="dark"] .markdown-body.prose,
404
461
  html[data-theme="dark"] .prose {
405
- --al-note: #93b2f4;
406
- --al-note-bg: rgba(147, 178, 244, 0.09);
407
- --al-note-line: rgba(147, 178, 244, 0.28);
462
+ --al-note: #eeece7;
463
+ --al-note-bg: rgba(238, 236, 231, 0.06);
464
+ --al-note-line: rgba(238, 236, 231, 0.22);
408
465
  --al-tip: #8fcb9f;
409
466
  --al-tip-bg: rgba(143, 203, 159, 0.08);
410
467
  --al-tip-line: rgba(143, 203, 159, 0.26);
@@ -578,7 +635,9 @@ html[data-theme="dark"] .markdown-body .markdown-alert code {
578
635
  }
579
636
 
580
637
  .markdown-body .fig-card img {
581
- width: 100%;
638
+ width: auto;
639
+ max-width: 100%;
640
+ margin: 0 auto;
582
641
  border-radius: 8px;
583
642
  }
584
643
 
@@ -611,6 +670,7 @@ html[data-theme="dark"] .markdown-body .markdown-alert code {
611
670
  }
612
671
 
613
672
  .table-wrap table {
673
+ margin: 0;
614
674
  border-collapse: collapse;
615
675
  width: 100%;
616
676
  font-size: var(--fs-small);
@@ -620,6 +680,8 @@ html[data-theme="dark"] .markdown-body .markdown-alert code {
620
680
  .table-wrap th,
621
681
  .table-wrap td {
622
682
  padding: 10px 14px;
683
+ overflow-wrap: normal;
684
+ word-break: normal;
623
685
  text-align: left;
624
686
  vertical-align: top;
625
687
  border-bottom: 1px solid var(--rule);
@@ -651,10 +713,27 @@ html[data-theme="dark"] .markdown-body .markdown-alert code {
651
713
  color: var(--grey);
652
714
  }
653
715
 
716
+ .toc .toc-item-row {
717
+ position: relative;
718
+ padding-left: 10px;
719
+ }
720
+
654
721
  .toc a[aria-current="location"],
655
722
  .toc li.active > .toc-item-row > a {
656
723
  color: var(--ink);
657
- font-weight: 500;
724
+ font-weight: 600;
725
+ }
726
+
727
+ /* The current section carries an ink bar, so it reads without colour. */
728
+ .toc li.active > .toc-item-row::before {
729
+ content: "";
730
+ position: absolute;
731
+ left: 0;
732
+ top: 0.2em;
733
+ bottom: 0.2em;
734
+ width: 2px;
735
+ border-radius: 1px;
736
+ background: var(--ink);
658
737
  }
659
738
 
660
739
  @media (min-width: 1200px) {
@@ -670,6 +749,7 @@ html[data-theme="dark"] .markdown-body .markdown-alert code {
670
749
 
671
750
  @media (prefers-reduced-motion: reduce) {
672
751
  .toc,
752
+ .markdown-body a,
673
753
  .h1-line,
674
754
  .ux-btn {
675
755
  transition: none;
@@ -1182,7 +1262,8 @@ html.ux-lock body {
1182
1262
  .hljs-built_in,
1183
1263
  .hljs-type,
1184
1264
  .hljs-meta .hljs-keyword {
1185
- color: #2c55b4;
1265
+ color: #30302e;
1266
+ font-weight: 600;
1186
1267
  }
1187
1268
 
1188
1269
  .hljs-string,
@@ -1216,7 +1297,7 @@ html.ux-lock body {
1216
1297
  html[data-theme="dark"] .hljs-keyword,
1217
1298
  html[data-theme="dark"] .hljs-built_in,
1218
1299
  html[data-theme="dark"] .hljs-type {
1219
- color: #93b2f4;
1300
+ color: #eeece7;
1220
1301
  }
1221
1302
 
1222
1303
  html[data-theme="dark"] .hljs-string {
@@ -1285,7 +1366,7 @@ html[data-theme="dark"] .hljs-params {
1285
1366
 
1286
1367
  .cover-card,
1287
1368
  .article-hero.article-cover {
1288
- border-radius: 16px;
1369
+ border-radius: var(--r-lg);
1289
1370
  margin-top: 28px;
1290
1371
  }
1291
1372
  }
@@ -1,9 +1,11 @@
1
1
 
2
2
  /* ===== Hedon Garden shell (A2). Tokens shared with the A2 article. ===== */
3
3
  :root{
4
- --paper:#F8F7F3; --card:#FFFFFF; --ink:#1B1A18; --text:#2D2C29; --soft:#4A4844; --muted:#6E6B64;
5
- --rule:#E6E3DC; --rule-strong:#D6D2C9; --well:#F0EEE8; --tint:rgba(27,26,24,.055);
6
- --blue:#2C55B4; --blue-tint:#EAF0FC; --blue-line:rgba(44,85,180,.28); --cover:#5E86E0;
4
+ --paper:#FAF9F5; --card:#FFFFFF; --ink:#1B1A18; --text:#2D2C29; --soft:#4A4844; --muted:#6E6B64;
5
+ --rule:#E6E3DC; --rule-strong:#D6D2C9; --well:#F0EEE6; --tint:rgba(27,26,24,.055);
6
+ --blue:#30302E; --blue-tint:#F0EEE6; --blue-line:rgba(20,20,19,.22); --cover:#C2C0B6;
7
+ --chip:#F0EEE6; --chip-hover:#E8E5DB; --chip-on:#30302E; --chip-on-text:#FAF9F5;
8
+ --card-hover:#F5F4ED; --card-shadow:0 1px 2px rgba(20,20,19,.04),0 4px 14px rgba(20,20,19,.05);
7
9
  --sans:-apple-system,BlinkMacSystemFont,"PingFang SC","Hiragino Sans GB","Noto Sans SC","Noto Sans CJK SC","Microsoft YaHei","Segoe UI",system-ui,sans-serif;
8
10
  --serif:"Source Han Serif SC","Noto Serif SC","Noto Serif CJK SC","Songti SC","STSong","Source Serif 4",Georgia,serif;
9
11
  --fs-display:46px; --fs-h2:26px; --fs-lead:20px; --fs-body:17px; --fs-small:14px; --fs-meta:13px;
@@ -12,9 +14,11 @@
12
14
  color-scheme:light;
13
15
  }
14
16
  html[data-theme="dark"]{
15
- --paper:#161513; --card:#1E1D1A; --ink:#EEECE7; --text:#DCD9D2; --soft:#BDB9B0; --muted:#A09B91;
16
- --rule:#2F2D29; --rule-strong:#45423C; --well:#22211D; --tint:rgba(238,236,231,.07);
17
- --blue:#93B2F4; --blue-tint:rgba(147,178,244,.13); --blue-line:rgba(147,178,244,.35); --cover:#4C72C9;
17
+ --paper:#1F1E1D; --card:#262624; --ink:#EEECE7; --text:#DCD9D2; --soft:#BDB9B0; --muted:#A09B91;
18
+ --rule:#3D3D3A; --rule-strong:#4D4C48; --well:#262624; --tint:rgba(238,236,231,.07);
19
+ --blue:#EEECE7; --blue-tint:#30302E; --blue-line:rgba(238,236,231,.25); --cover:#4D4C48;
20
+ --chip:#30302E; --chip-hover:#3D3D3A; --chip-on:#EEECE7; --chip-on-text:#1F1E1D;
21
+ --card-hover:#30302E; --card-shadow:0 1px 2px rgba(0,0,0,.18);
18
22
  color-scheme:dark;
19
23
  }
20
24
  *,*::before,*::after{box-sizing:border-box}
@@ -82,20 +86,28 @@ html[data-theme="dark"] .sh-rail{color:var(--muted)}
82
86
  .sh-rail li a[aria-current="true"]{color:var(--blue)}
83
87
  .sh-rail li[hidden]{display:none}
84
88
 
85
- /* ---------- filter tabs ---------- */
86
- .sh-filters{display:flex;flex-wrap:wrap;gap:6px 4px;margin:0 0 8px;padding:0;list-style:none}
87
- .sh-chip{display:inline-flex;align-items:center;gap:6px;min-height:36px;padding:0 12px;border-radius:var(--r);border:1px solid transparent;background:transparent;color:var(--soft);
88
- font:400 15px/1 var(--sans);text-decoration:none;cursor:pointer;white-space:nowrap;transition:background-color .15s ease,color .15s ease,border-color .15s ease}
89
- .sh-chip .n{font-size:var(--fs-meta);color:var(--muted);font-variant-numeric:tabular-nums}
90
- @media (hover:hover){.sh-chip:hover{background:var(--tint);color:var(--ink)}}
91
- .sh-chip[aria-current="true"],.sh-chip[aria-pressed="true"]{background:var(--blue-tint);color:var(--blue);font-weight:600}
92
- .sh-chip[aria-current="true"] .n,.sh-chip[aria-pressed="true"] .n{color:var(--blue)}
93
- .sh-filters .extra[hidden]{display:none}
94
- .sh-chip.toggle{color:var(--muted)}
95
- .sh-chip.toggle svg{width:14px;height:14px;fill:none;stroke:currentColor;stroke-width:1.8;transition:transform .2s ease}
96
- .sh-chip.toggle[aria-expanded="true"] svg{transform:rotate(180deg)}
89
+ /* ---------- tag filter (/blog) and tag index (/tags) ---------- */
90
+ .sh-tagfilter{margin:0 0 8px}
91
+ .sh-tagchips{display:flex;flex-wrap:wrap;gap:12px 8px;list-style:none;margin:0;padding:0}
92
+ .sh-tagchip{position:relative;display:inline-flex;align-items:center;gap:6px;min-height:32px;padding:4px 12px;border:0;border-radius:999px;background:var(--chip);color:var(--soft);font:400 var(--fs-small)/1.4 var(--sans);text-decoration:none;cursor:pointer;white-space:nowrap;transition:background-color .15s ease,color .15s ease}
93
+ .sh-tagchip::after,.sh-tagmore::after{content:"";position:absolute;left:0;right:0;top:50%;height:44px;transform:translateY(-50%)}
94
+ .sh-tagchip .n{font-size:var(--fs-meta);color:inherit;opacity:.72;font-variant-numeric:tabular-nums}
95
+ @media (hover:hover){.sh-tagchip:hover:not(:disabled){background:var(--chip-hover);color:var(--ink)}}
96
+ .sh-tagchip[aria-pressed="true"]{background:var(--chip-on);color:var(--chip-on-text);font-weight:600}
97
+ @media (hover:hover){.sh-tagchip[aria-pressed="true"]:hover{background:var(--chip-on);color:var(--chip-on-text)}}
98
+ .sh-tagchip[aria-pressed="true"] .n{opacity:.85}
99
+ .sh-tagchip .x{font-size:15px;line-height:1;margin-left:-1px}
100
+ .sh-tagchip:disabled{background:var(--tint);color:var(--muted);cursor:default;opacity:.55}
101
+ .sh-tagchip:focus-visible,.sh-tagmore:focus-visible{outline:2px solid var(--blue);outline-offset:2px}
102
+ .sh-tagmore{position:relative;display:inline-flex;align-items:center;min-height:32px;padding:4px 2px;border:0;background:none;color:var(--blue);font:400 var(--fs-small)/1.4 var(--sans);cursor:pointer;white-space:nowrap;text-decoration:underline;text-decoration-thickness:1px;text-underline-offset:3px}
103
+ @media (hover:hover){.sh-tagmore:hover{text-decoration-thickness:2px}}
104
+ .sh-tagempty{margin-top:16px;padding:40px 0;border-top:1px solid var(--rule-strong)}
105
+ .sh-tagempty p{margin:0 0 4px;color:var(--soft);font-size:var(--fs-body);line-height:1.7}
106
+ .sh-tagempty .acts{display:flex;flex-wrap:wrap;gap:0 24px}
107
+ .sh-tagempty button{font:inherit;font-size:var(--fs-small);color:var(--blue);background:none;border:0;padding:0;min-height:44px;cursor:pointer;text-decoration:underline;text-underline-offset:3px;text-decoration-thickness:1px}
108
+ .sh-tagindex{margin-top:8px}
97
109
  .sh-status{display:flex;align-items:center;gap:12px;min-height:40px;margin:4px 0 0;font-size:var(--fs-small);color:var(--muted)}
98
- .sh-status > button,.sh-status > a{font:inherit;color:var(--blue);background:none;border:0;padding:6px 0;cursor:pointer;text-decoration:underline;text-underline-offset:3px;text-decoration-thickness:1px}
110
+ .sh-status > button,.sh-status > a{font:inherit;color:var(--blue);background:none;border:0;padding:6px 0;min-height:44px;cursor:pointer;text-decoration:underline;text-underline-offset:3px;text-decoration-thickness:1px}
99
111
  .sh-view{display:inline-flex;margin-left:auto;border:1px solid var(--rule);border-radius:var(--r);padding:2px;background:var(--card)}
100
112
  .sh-view button{font:400 13px/1 var(--sans);color:var(--muted);background:none;border:0;border-radius:9px;padding:7px 10px;cursor:pointer;min-height:30px}
101
113
  .sh-view button[aria-pressed="true"]{background:var(--well);color:var(--ink);font-weight:600}
@@ -118,7 +130,7 @@ html[data-theme="dark"] .sh-rail{color:var(--muted)}
118
130
  .sh-row .cat:hover{color:var(--blue);text-decoration:underline;text-underline-offset:3px}
119
131
  .sh-row .pin{font:400 12px/1 var(--sans);color:var(--blue);background:var(--blue-tint);border-radius:6px;padding:3px 6px;margin-left:8px;vertical-align:2px;white-space:nowrap}
120
132
  .sh-row .series{font-weight:400;color:var(--muted);font-size:var(--fs-meta);margin-left:8px;white-space:nowrap}
121
- .sh-row:hover h3 a{color:var(--blue)}
133
+ .sh-row:hover h3 a{color:var(--blue);text-decoration:underline;text-decoration-thickness:1px;text-underline-offset:4px}
122
134
  .sh-row h3 a{transition:color .15s ease}
123
135
  .is-compact .sh-row{padding:11px 0 12px}
124
136
  .is-compact .sh-row p{display:none}
@@ -136,32 +148,31 @@ html[data-theme="dark"] .sh-rail{color:var(--muted)}
136
148
  .sh-feature:not(:has(.cover)){grid-template-columns:minmax(0,1fr)}
137
149
  .sh-feature .cover{border-radius:var(--r-lg);aspect-ratio:auto;display:block;overflow:hidden;background:none}
138
150
  .sh-feature .cover img{display:block;width:100%;height:auto}
139
- .sh-feature .cover .cover-art{height:86%;width:auto}
140
151
  .sh-feature .kicker{display:flex;flex-wrap:wrap;gap:4px 14px;font-size:var(--fs-small);color:var(--muted);margin:0 0 12px}
141
152
  .sh-feature h2{font:700 30px/1.35 var(--sans);color:var(--ink);margin:0;text-wrap:balance}
142
153
  .sh-feature h2 a{text-decoration:none}
143
154
  .sh-feature h2 a::after{content:"";position:absolute;inset:0;border-radius:var(--r-lg)}
144
- .sh-feature:hover h2 a{color:var(--blue)}
155
+ .sh-feature:hover h2 a{color:var(--blue);text-decoration:underline;text-decoration-thickness:1px;text-underline-offset:4px}
145
156
  .sh-feature:has(h2 a:focus-visible){outline:2px solid var(--blue);outline-offset:8px;border-radius:var(--r-lg)}
146
157
  .sh-feature h2 a:focus-visible{outline:none}
147
158
  .sh-feature p{margin:14px 0 0;font-size:var(--fs-body);color:var(--soft)}
148
159
  .sh-feature .tags{display:flex;flex-wrap:wrap;gap:6px 14px;margin-top:16px;font-size:var(--fs-meta);color:var(--muted)}
149
160
  .sh-home-grid{display:grid;grid-template-columns:minmax(0,var(--list)) minmax(0,320px);gap:0 72px;justify-content:space-between;align-items:start}
150
- .sh-home-grid .sh-row{grid-template-columns:96px minmax(0,1fr) auto}
161
+ .sh-home-grid .sh-row{grid-template-columns:minmax(6em,max-content) minmax(0,1fr) auto}
162
+ .sh-home-grid .sh-row time{min-width:6em;font-variant-numeric:tabular-nums}
151
163
  .sh-side-list{list-style:none;margin:0;padding:0}
152
164
  .sh-side-list li{position:relative;padding:16px 0;border-bottom:1px solid var(--rule)}
153
165
  .sh-side-list a{font:600 15px/1.6 var(--sans);color:var(--ink);text-decoration:none}
154
166
  .sh-side-list a::after{content:"";position:absolute;inset:0}
155
- .sh-side-list li:hover a{color:var(--blue)}
167
+ .sh-side-list li:hover a{color:var(--blue);text-decoration:underline;text-decoration-thickness:1px;text-underline-offset:4px}
156
168
  .sh-side-list span{display:block;margin-top:4px;font-size:var(--fs-meta);color:var(--muted)}
157
169
 
158
170
  /* ---------- column (series) cards ---------- */
159
171
  .sh-cols{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:24px;margin-top:24px}
160
- .sh-col{position:relative;display:flex;flex-direction:column;background:var(--card);border:1px solid var(--rule);border-radius:var(--r-lg);overflow:hidden;transition:border-color .15s ease}
161
- .sh-col:hover{border-color:var(--rule-strong)}
162
- .sh-col .cover{aspect-ratio:16/9;display:flex;align-items:center;justify-content:center;border-radius:calc(var(--r-lg) - 8px);margin:8px 8px 0;overflow:hidden}
163
- .sh-col .cover .cover-art{height:84%;width:auto}
164
- .sh-col .body{padding:18px 22px 22px}
172
+ .sh-col{position:relative;display:flex;flex-direction:column;background:transparent;border:1px solid var(--rule);border-radius:var(--r);overflow:hidden;transition:background-color .15s ease,box-shadow .15s ease}
173
+ @media (hover:hover){.sh-col:hover{background:var(--card-hover);box-shadow:var(--card-shadow)}}
174
+ .sh-col .cover{aspect-ratio:16/9;display:flex;align-items:center;justify-content:center;border-radius:0;margin:0;overflow:hidden}
175
+ .sh-col .body{padding:16px 18px 18px}
165
176
  .sh-col h3{margin:0;font:700 var(--fs-lead)/1.4 var(--sans);color:var(--ink)}
166
177
  .sh-col h3 a{text-decoration:none}
167
178
  .sh-col h3 a::after{content:"";position:absolute;inset:0}
@@ -173,22 +184,6 @@ html[data-theme="dark"] .sh-rail{color:var(--muted)}
173
184
 
174
185
  /* columns page */
175
186
 
176
- /* ---------- tags ---------- */
177
- .sh-tagcloud{display:flex;flex-wrap:wrap;gap:6px 4px;list-style:none;margin:0;padding:0}
178
- .sh-tagcloud .sh-chip{border-color:var(--rule);background:var(--card)}
179
- @media (hover:hover){.sh-tagcloud .sh-chip:hover{border-color:var(--rule-strong);background:var(--card)}}
180
- .sh-tagcloud .sh-chip[aria-pressed="true"]{background:var(--blue-tint);border-color:var(--blue-line)}
181
- .sh-once{margin:20px 0 0}
182
- .sh-once summary{display:inline-flex;align-items:center;gap:8px;cursor:pointer;font-size:15px;color:var(--soft);padding:8px 0;list-style:none}
183
- .sh-once summary::-webkit-details-marker{display:none}
184
- .sh-once summary svg{width:14px;height:14px;fill:none;stroke:currentColor;stroke-width:1.8;transition:transform .2s ease}
185
- .sh-once[open] summary svg{transform:rotate(180deg)}
186
- .sh-once .sh-tagcloud{margin-top:10px}
187
- .sh-once .sh-chip{min-height:32px;font-size:var(--fs-small)}
188
- .sh-tagresult{margin-top:40px;scroll-margin-top:calc(var(--hdr) + 16px)}
189
- .sh-tagresult h2{font:700 var(--fs-lead)/1.3 var(--sans);color:var(--ink);margin:0;padding-bottom:12px;border-bottom:1px solid var(--rule-strong)}
190
- .sh-tagresult .hint{color:var(--muted);font-size:15px;padding:20px 0}
191
-
192
187
  /* ---------- about ---------- */
193
188
  .sh-about{display:grid;grid-template-columns:minmax(0,var(--list)) var(--side);gap:0 72px;justify-content:space-between}
194
189
  .sh-prose p{margin:0 0 1.1em}
@@ -213,8 +208,11 @@ html[data-theme="dark"] .sh-rail{color:var(--muted)}
213
208
  .sh-foot p{margin:0}
214
209
  .sh-foot strong{color:var(--ink);font:600 15px/1 var(--serif);margin-right:12px}
215
210
  .sh-foot nav{display:flex;flex-wrap:wrap;gap:4px 20px}
216
- .sh-foot a{color:var(--soft);text-decoration:none;padding:4px 0}
211
+ .sh-foot a{display:inline-flex;align-items:center;gap:6px;min-height:44px;color:var(--soft);text-decoration:none;padding:4px 0;white-space:nowrap}
217
212
  .sh-foot a:hover{color:var(--blue)}
213
+ .sh-foot .footer-framework a{display:inline;min-height:0;padding:0;color:var(--soft);text-decoration:underline;text-underline-offset:3px;text-decoration-thickness:1px}
214
+ .sh-foot-links{display:flex;align-items:center;gap:20px}
215
+ .sh-foot-links a svg{display:block;flex:none;width:16px;height:16px}
218
216
  .sh-demo{width:100%;font-size:var(--fs-meta);color:var(--muted)}
219
217
  .sh-demo a{text-decoration:underline;text-underline-offset:3px}
220
218
 
@@ -275,10 +273,6 @@ html[data-theme="dark"] .sh-pal-scrim{background:rgba(0,0,0,.55)}
275
273
  .sh-feature h2{font-size:24px}
276
274
  .sh-section{margin-top:56px}
277
275
  .sh-cols{grid-template-columns:minmax(0,1fr);gap:16px}
278
- /* one-line, scrollable filter row on phones */
279
- .sh-filters{flex-wrap:nowrap;overflow-x:auto;margin:0 -20px 4px;padding:0 20px;scrollbar-width:none;-webkit-mask-image:linear-gradient(90deg,transparent 0,#000 20px,#000 calc(100% - 28px),transparent)}
280
- .sh-filters::-webkit-scrollbar{display:none}
281
- .sh-filters.is-open{flex-wrap:wrap;overflow:visible;-webkit-mask-image:none}
282
276
  .sh-row,.sh-home-grid .sh-row{grid-template-columns:minmax(0,1fr) auto;gap:2px 12px;padding:16px 0 17px}
283
277
  .sh-row time{grid-column:1;grid-row:1;font-size:var(--fs-meta);padding:0}
284
278
  .sh-row .cat{grid-column:2;grid-row:1;padding:0}
@@ -286,7 +280,7 @@ html[data-theme="dark"] .sh-pal-scrim{background:rgba(0,0,0,.55)}
286
280
  .sh-row h3{font-size:var(--fs-body)}
287
281
  .sh-404{padding:48px 0 0}
288
282
  .sh-footer{margin-top:80px}
289
- .sh-foot{padding:24px 20px 40px;flex-direction:column}
283
+ .sh-foot{padding:24px 20px 40px;flex-direction:column;align-items:flex-start}
290
284
  /* palette becomes a full-screen sheet */
291
285
  .sh-pal{padding:0}
292
286
  .sh-pal-box{width:100%;max-height:none;height:100%;border-radius:0;border:0;animation:none}
@@ -338,8 +332,8 @@ html[data-theme="dark"] .sh-pal-scrim{background:rgba(0,0,0,.55)}
338
332
  .sh-note-audio{margin:16px 0 0;padding:12px 14px;border:1px solid var(--rule);border-radius:var(--r);background:var(--card)}
339
333
  .sh-note-audio figcaption{font-size:var(--fs-meta);color:var(--muted);margin:0 0 8px}
340
334
  .sh-note-audio audio{display:block;width:100%;height:36px}
341
- .sh-note-link{display:grid;gap:2px;margin:16px 0 0;padding:14px 16px;border:1px solid var(--rule);border-radius:var(--r);background:var(--card);text-decoration:none;transition:border-color .15s ease}
342
- @media (hover:hover){.sh-note-link:hover{border-color:var(--rule-strong)}.sh-note-link:hover .t{color:var(--blue)}}
335
+ .sh-note-link{display:grid;gap:2px;margin:16px 0 0;padding:14px 16px;border:1px solid var(--rule);border-radius:var(--r);background:var(--card);text-decoration:none;transition:background-color .15s ease,box-shadow .15s ease}
336
+ @media (hover:hover){.sh-note-link:hover{background:var(--card-hover);box-shadow:var(--card-shadow)}}
343
337
  .sh-note-link .h{font-size:var(--fs-meta);color:var(--muted)}
344
338
  .sh-note-link .t{font:600 15px/1.5 var(--sans);color:var(--ink)}
345
339
  .sh-note-link .t::after{content:" ↗";color:var(--muted);font-weight:400}
@@ -361,7 +355,7 @@ html[data-theme="dark"] .sh-note-imgs img,html[data-theme="dark"] .sh-note-video
361
355
  /* ===== About: GitHub project cards (live) ===== */
362
356
  .sh-repos{list-style:none;margin:0;padding:0!important;display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:16px}
363
357
  .sh-repos > li{position:relative;margin:0;display:grid;grid-template-rows:24px 48px 20px;row-gap:6px;padding:16px 18px;
364
- border:1px solid var(--rule-strong);border-radius:var(--r);background:transparent;transition:border-color .15s ease}
358
+ border:1px solid var(--rule-strong);border-radius:var(--r);background:transparent;transition:background-color .15s ease,box-shadow .15s ease}
365
359
  .sh-repos h3{margin:0;display:flex;align-items:center;gap:8px;min-width:0;font:600 16px/24px var(--sans)}
366
360
  .sh-repos h3 a{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--ink);text-decoration:none}
367
361
  .sh-repos h3 a::after{content:"";position:absolute;inset:0;border-radius:inherit}
@@ -376,9 +370,8 @@ html[data-theme="dark"] .sh-note-imgs img,html[data-theme="dark"] .sh-note-video
376
370
  .sh-repos .meta svg{width:14px;height:14px;fill:currentColor}
377
371
  .sh-repos .meta time{margin-left:auto}
378
372
  @media (hover:hover){
379
- .sh-repos > li:not(.sk):hover{border-color:var(--muted)}
380
- .sh-repos > li:not(.sk):hover h3 a{color:var(--blue)}
381
- .sh-repos > li:not(.sk):hover .arr{color:var(--blue);transform:translate(2px,-2px)}
373
+ .sh-repos > li:not(.sk):hover{background:var(--card-hover);box-shadow:var(--card-shadow)}
374
+ .sh-repos > li:not(.sk):hover .arr{color:var(--ink)}
382
375
  }
383
376
  /* skeleton card: same box, same rows */
384
377
  .sh-repos > li.sk{overflow:hidden;border-color:var(--rule)}
@@ -396,7 +389,6 @@ html[data-theme="dark"] .sh-repos > li.sk::after{background:linear-gradient(90de
396
389
  .sh-series{display:grid;grid-template-columns:240px minmax(0,760px);gap:0 40px;padding:36px 0;border-top:1px solid var(--rule)}
397
390
  .sh-series:first-of-type{border-top:0;padding-top:8px}
398
391
  .sh-series .cover{aspect-ratio:16/9;border-radius:var(--r);display:flex;align-items:center;justify-content:center;overflow:hidden;align-self:start}
399
- .sh-series .cover .cover-art{height:84%;width:auto}
400
392
  .sh-series h2{font:700 var(--fs-h2)/1.3 var(--sans);color:var(--ink);margin:0;scroll-margin-top:calc(var(--hdr) + 16px)}
401
393
  .sh-series .count{font-size:var(--fs-small);color:var(--muted);margin:6px 0 0}
402
394
  .sh-series .desc{font-size:var(--fs-body);line-height:1.7;color:var(--soft);margin:12px 0 0}
@@ -435,4 +427,7 @@ html[data-theme="dark"] .sh-repos > li.sk::after{background:linear-gradient(90de
435
427
  .is-compact .sh-row .incol{display:none}
436
428
  @media (max-width:820px){.sh-row .incol{grid-column:1 / -1}}
437
429
 
438
-
430
+ /* Cards answer hover with a warm fill and a faint shadow; nothing moves. */
431
+ @media (prefers-reduced-motion:reduce){
432
+ .sh-col,.sh-note-link,.sh-repos > li,.sh-tagchip{transition:none}
433
+ }
@@ -2,8 +2,8 @@
2
2
 
3
3
  :root,
4
4
  :root[data-theme="light"] {
5
- --paper: #f8f7f3;
6
- --paper-deep: #f0eee8;
5
+ --paper: #faf9f5;
6
+ --paper-deep: #f0eee6;
7
7
  --card: #ffffff;
8
8
  --ink: #1b1a18;
9
9
  --ink-soft: #2d2c29;
@@ -16,15 +16,22 @@
16
16
  --rule-strong: #d6d2c9;
17
17
  --line: #e6e3dc;
18
18
  --line-strong: #d6d2c9;
19
- --well: #f0eee8;
19
+ --well: #f0eee6;
20
20
  --tint: rgb(27 26 24 / 5.5%);
21
- --blue: #2c55b4;
22
- --blue-tint: #eaf0fc;
23
- --blue-line: rgb(44 85 180 / 28%);
24
- --cover: #5e86e0;
21
+ /* "blue" is the accent slot; the accent is now ink, there is no blue. */
22
+ --blue: #30302e;
23
+ --blue-tint: #f0eee6;
24
+ --blue-line: rgb(20 20 19 / 22%);
25
+ --cover: #c2c0b6;
26
+ --chip: #f0eee6;
27
+ --chip-hover: #e8e5db;
28
+ --chip-on: #30302e;
29
+ --chip-on-text: #faf9f5;
30
+ --card-hover: #f5f4ed;
31
+ --card-shadow: 0 1px 2px rgb(20 20 19 / 4%), 0 4px 14px rgb(20 20 19 / 5%);
25
32
  --accent: var(--blue);
26
33
  --accent-soft: var(--blue-tint);
27
- --selection: #eaf0fc;
34
+ --selection: #e8e5db;
28
35
  --hl: #ffe58f;
29
36
  --red: #d82d48;
30
37
  --red-text: #c2263f;
@@ -42,7 +49,7 @@
42
49
  --fs-meta: 13px;
43
50
  --r: 12px;
44
51
  --r-lg: 24px;
45
- --col: 700px;
52
+ --col: 740px;
46
53
  --rail: 232px;
47
54
  --gap: 48px;
48
55
  --content: var(--col);
@@ -54,9 +61,9 @@
54
61
 
55
62
  :root[data-theme="dark"],
56
63
  html[data-theme="dark"] {
57
- --paper: #161513;
58
- --paper-deep: #22211d;
59
- --card: #1e1d1a;
64
+ --paper: #1f1e1d;
65
+ --paper-deep: #262624;
66
+ --card: #262624;
60
67
  --ink: #eeece7;
61
68
  --ink-soft: #dcd9d2;
62
69
  --text: #dcd9d2;
@@ -64,19 +71,25 @@ html[data-theme="dark"] {
64
71
  --muted: #a09b91;
65
72
  --grey: #a09b91;
66
73
  --faint: #a09b91;
67
- --rule: #2f2d29;
68
- --rule-strong: #45423c;
69
- --line: #2f2d29;
70
- --line-strong: #45423c;
71
- --well: #22211d;
74
+ --rule: #3d3d3a;
75
+ --rule-strong: #4d4c48;
76
+ --line: #3d3d3a;
77
+ --line-strong: #4d4c48;
78
+ --well: #262624;
72
79
  --tint: rgb(238 236 231 / 7%);
73
- --blue: #93b2f4;
74
- --blue-tint: rgb(147 178 244 / 13%);
75
- --blue-line: rgb(147 178 244 / 35%);
76
- --cover: #4c72c9;
80
+ --blue: #eeece7;
81
+ --blue-tint: #30302e;
82
+ --blue-line: rgb(238 236 231 / 25%);
83
+ --cover: #4d4c48;
84
+ --chip: #30302e;
85
+ --chip-hover: #3d3d3a;
86
+ --chip-on: #eeece7;
87
+ --chip-on-text: #1f1e1d;
88
+ --card-hover: #30302e;
89
+ --card-shadow: 0 1px 2px rgb(0 0 0 / 18%);
77
90
  --accent: var(--blue);
78
91
  --accent-soft: var(--blue-tint);
79
- --selection: rgb(147 178 244 / 22%);
92
+ --selection: rgb(238 236 231 / 18%);
80
93
  --hl: #6b5a2a;
81
94
  --red: #f29aab;
82
95
  --red-text: #f29aab;
package/app/tags/page.tsx CHANGED
@@ -1,28 +1,39 @@
1
+ import Link from "next/link";
1
2
  import { Suspense } from "react";
2
3
  import { posts } from "../lib/content";
3
- import { TagIndex } from "../components/TagIndex";
4
+ import { tagCounts, tagFilterHref } from "../lib/tag-filter";
5
+ import { TagRedirect } from "../components/TagRedirect";
4
6
  import { siteDocumentTitle } from "../site.config";
5
7
 
6
8
  export const metadata = {
7
9
  title: siteDocumentTitle,
8
- description: "按标签浏览文章。标签不是专栏,也不是分类。",
10
+ description: "全部标签。",
9
11
  };
10
12
 
11
13
  export default function TagsPage() {
12
- const counts = new Map<string, number>();
13
- for (const post of posts) {
14
- for (const tag of post.tags ?? []) counts.set(tag, (counts.get(tag) ?? 0) + 1);
15
- }
16
- const once = [...counts.values()].filter((count) => count === 1).length;
14
+ const tags = tagCounts(posts);
17
15
  return (
18
16
  <main className="sh-main" id="content">
19
17
  <div className="sh-head">
20
18
  <h1>标签</h1>
21
- <p className="sh-deck">{counts.size} 个标签,按使用次数排序;只用过一次的 {once} 个收在下面。点选一个标签,下方列出对应文章。</p>
19
+ <p className="sh-deck">{tags.length} 个标签,按文章数排序。点一个标签,查看带这个标签的文章。</p>
22
20
  </div>
23
21
  <Suspense>
24
- <TagIndex posts={posts} />
22
+ <TagRedirect />
25
23
  </Suspense>
24
+ {tags.length ? (
25
+ <ul className="sh-tagchips sh-tagindex" aria-label="全部标签">
26
+ {tags.map(([tag, count]) => (
27
+ <li key={tag}>
28
+ <Link className="sh-tagchip" href={tagFilterHref([tag])}>
29
+ #{tag}<span className="n num">{count}</span>
30
+ </Link>
31
+ </li>
32
+ ))}
33
+ </ul>
34
+ ) : (
35
+ <p className="sh-deck">还没有标签。</p>
36
+ )}
26
37
  </main>
27
38
  );
28
39
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hd-garden",
3
- "version": "0.4.1",
3
+ "version": "0.5.0",
4
4
  "private": false,
5
5
  "description": "用 Markdown 写作、用 GitHub Pages 发布的个人博客引擎。",
6
6
  "license": "MIT",
@@ -184,7 +184,7 @@ function assertWidgetSidecar(src, markdownPath, sourcePath) {
184
184
  ) {
185
185
  fail(
186
186
  sourcePath,
187
- `widget src 必须位于同名目录 ${stem}/ 内的 .html 或 .pdf,例如 ./${stem}/notes.pdf`,
187
+ `widget src 必须位于同名目录 ${stem}/ 内的 .html 或 .pdf,例如 ./${stem}/diagram.html`,
188
188
  );
189
189
  }
190
190
  return absoluteSource;
@@ -337,7 +337,6 @@ function validateAndNormalize(data, content, markdownPath, kind, sourcePath, lin
337
337
 
338
338
  if (kind === "post") {
339
339
  requireString(data, "description", sourcePath);
340
- requireString(data, "topic", sourcePath);
341
340
  }
342
341
  if (kind === "thought" && data.mediaType) {
343
342
  if (!["image", "audio", "video", "link", "text"].includes(data.mediaType)) {
@@ -348,6 +347,8 @@ function validateAndNormalize(data, content, markdownPath, kind, sourcePath, lin
348
347
  const publishTime = Date.parse(publishAt ?? date);
349
348
  if (!includeDrafts && (data.draft === true || publishTime > now)) return null;
350
349
  delete data.coverAlt;
350
+ // Older posts may still carry `topic:`; it is no longer used.
351
+ delete data.topic;
351
352
  const references = normalizeReferences(data.references, sourcePath);
352
353
  delete data.references;
353
354
 
@@ -562,7 +563,6 @@ export type ContentEntry = {
562
563
  updated?: string;
563
564
  publishAt?: string;
564
565
  draft?: boolean;
565
- topic?: string;
566
566
  tags?: string[];
567
567
  references?: { title: string; url?: string }[];
568
568
  pinned?: boolean;
@@ -272,9 +272,6 @@ const checkFile = (filePath) => {
272
272
  if (typeof data.description !== "string" || !data.description.trim()) {
273
273
  error(errors, "missing-description", 1, `${sourcePath}: front matter 缺少有效的 description`);
274
274
  }
275
- if (typeof data.topic !== "string" || !data.topic.trim()) {
276
- error(errors, "missing-topic", 1, `${sourcePath}: front matter 缺少有效的 topic`);
277
- }
278
275
  if (data.slug !== undefined) {
279
276
  error(errors, "unexpected-slug", 1, `${sourcePath}: 不要写 slug,文件名就是标识`);
280
277
  }