hd-garden 0.8.0 → 0.8.2

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 (187) hide show
  1. package/.agents/skills/garden-ui/SKILL.md +2 -2
  2. package/.env.en.example +67 -0
  3. package/README.en.md +2 -2
  4. package/README.md +2 -2
  5. package/app/a2/[slug]/page.tsx +4 -3
  6. package/app/a2/page.tsx +4 -3
  7. package/app/about/page.tsx +8 -9
  8. package/app/admin/page.tsx +7 -6
  9. package/app/admin/preview/[...path]/page.tsx +2 -1
  10. package/app/admin/ui/blocks/page.tsx +2 -1
  11. package/app/admin/ui/catalog/page.tsx +2 -1
  12. package/app/admin/ui/evals/page.tsx +2 -1
  13. package/app/admin/ui/page.tsx +2 -1
  14. package/app/blog/[...path]/page.tsx +7 -6
  15. package/app/blog/page.tsx +4 -2
  16. package/app/columns/[column]/[...path]/page.tsx +8 -7
  17. package/app/columns/page.tsx +12 -8
  18. package/app/components/AdminUiShell.tsx +4 -3
  19. package/app/components/ArticleMeasure.tsx +6 -3
  20. package/app/components/ArticleReferences.tsx +4 -1
  21. package/app/components/ArticleTags.tsx +3 -2
  22. package/app/components/BlogArchive.tsx +20 -16
  23. package/app/components/Comments.tsx +16 -10
  24. package/app/components/ContentEmptyState.tsx +29 -28
  25. package/app/components/ExternalEmbed.tsx +15 -14
  26. package/app/components/GardenContentGuide.tsx +10 -2
  27. package/app/components/GardenDocsNav.tsx +4 -2
  28. package/app/components/GiscusComments.tsx +2 -1
  29. package/app/components/GithubLiveOverview.tsx +10 -8
  30. package/app/components/GithubProfile.tsx +31 -21
  31. package/app/components/HandbookLangSwitch.tsx +8 -4
  32. package/app/components/HandbookView.tsx +92 -9
  33. package/app/components/MarkdownArticle.tsx +31 -7
  34. package/app/components/MermaidDiagram.tsx +4 -3
  35. package/app/components/SiteChrome.tsx +3 -3
  36. package/app/components/SiteFooter.tsx +1 -1
  37. package/app/components/SiteHeader.tsx +13 -4
  38. package/app/components/ThoughtCard.tsx +16 -14
  39. package/app/components/UiBlock.tsx +3 -2
  40. package/app/components/Webmentions.tsx +11 -11
  41. package/app/components/WidgetChart.tsx +8 -6
  42. package/app/components/garden/FilePreview.tsx +2 -2
  43. package/app/components/garden/GardenBlock.tsx +7 -0
  44. package/app/components/garden/GardenFile.tsx +2 -2
  45. package/app/components/garden/RefDemo.tsx +62 -0
  46. package/app/components/garden/blocks.tsx +56 -8
  47. package/app/components/garden/patterns.tsx +497 -0
  48. package/app/garden/[...slug]/page.tsx +3 -2
  49. package/app/garden/en/page.tsx +7 -7
  50. package/app/garden/page.tsx +3 -2
  51. package/app/globals.css +49 -1
  52. package/app/lib/a2-meta.ts +26 -3
  53. package/app/lib/content.ts +2 -2
  54. package/app/lib/garden-docs-meta.ts +24 -0
  55. package/app/lib/github-id.mjs +8 -0
  56. package/app/lib/handbook-switch.mjs +12 -0
  57. package/app/lib/i18n.ts +401 -2
  58. package/app/lib/rehype-field-tables.mjs +47 -0
  59. package/app/lib/remark-a2.mjs +18 -8
  60. package/app/lib/remark-link-card.mjs +22 -0
  61. package/app/lib/remark-ref-demos.mjs +42 -0
  62. package/app/lib/tag-filter.ts +9 -2
  63. package/app/not-found.tsx +32 -4
  64. package/app/page.tsx +17 -14
  65. package/app/rss.xml/route.ts +2 -1
  66. package/app/styles/a2.css +338 -0
  67. package/app/styles/shell.css +15 -2
  68. package/app/tags/page.tsx +7 -5
  69. package/app/thoughts/[slug]/page.tsx +2 -1
  70. package/app/thoughts/page/[n]/page.tsx +1 -1
  71. package/app/thoughts/page.tsx +2 -1
  72. package/app/ui-parity/page.tsx +38 -22
  73. package/bin/garden.mjs +129 -19
  74. package/docs/handbook/ai-writing.md +2 -2
  75. package/docs/handbook/assets/reading-layout-en.svg +26 -0
  76. package/docs/handbook/components/card.md +43 -22
  77. package/docs/handbook/components/chart.md +10 -26
  78. package/docs/handbook/components/choice.md +42 -0
  79. package/docs/handbook/components/compare.md +32 -17
  80. package/docs/handbook/components/figure.md +33 -46
  81. package/docs/handbook/components/file.md +10 -15
  82. package/docs/handbook/components/html.md +10 -30
  83. package/docs/handbook/components/interactive.md +9 -35
  84. package/docs/handbook/components/list.md +65 -0
  85. package/docs/handbook/components/relations.md +95 -0
  86. package/docs/handbook/components/stat.md +10 -28
  87. package/docs/handbook/components/timeline.md +13 -28
  88. package/docs/handbook/components.md +210 -19
  89. package/docs/handbook/config.md +5 -3
  90. package/docs/handbook/deploy.md +1 -1
  91. package/docs/handbook/design.md +20 -12
  92. package/docs/handbook/en/ai-writing.md +2 -2
  93. package/docs/handbook/en/components/card.md +42 -21
  94. package/docs/handbook/en/components/chart.md +10 -26
  95. package/docs/handbook/en/components/choice.md +42 -0
  96. package/docs/handbook/en/components/compare.md +30 -15
  97. package/docs/handbook/en/components/figure.md +29 -42
  98. package/docs/handbook/en/components/file.md +9 -14
  99. package/docs/handbook/en/components/html.md +9 -29
  100. package/docs/handbook/en/components/interactive.md +8 -34
  101. package/docs/handbook/en/components/list.md +65 -0
  102. package/docs/handbook/en/components/relations.md +95 -0
  103. package/docs/handbook/en/components/stat.md +9 -27
  104. package/docs/handbook/en/components/timeline.md +9 -24
  105. package/docs/handbook/en/components.md +211 -19
  106. package/docs/handbook/en/config.md +32 -30
  107. package/docs/handbook/en/deploy.md +12 -12
  108. package/docs/handbook/en/design.md +19 -12
  109. package/docs/handbook/en/index.md +2 -2
  110. package/docs/handbook/en/reference/card.md +85 -0
  111. package/docs/handbook/en/reference/chart.md +68 -0
  112. package/docs/handbook/en/reference/choice.md +69 -0
  113. package/docs/handbook/en/reference/compare.md +59 -0
  114. package/docs/handbook/en/reference/figure.md +84 -0
  115. package/docs/handbook/en/reference/file/checklist.md +17 -0
  116. package/docs/handbook/en/reference/file.md +50 -0
  117. package/docs/handbook/en/reference/html.md +62 -0
  118. package/docs/handbook/en/reference/interactive.md +71 -0
  119. package/docs/handbook/en/reference/list.md +72 -0
  120. package/docs/handbook/en/reference/relations.md +113 -0
  121. package/docs/handbook/en/reference/stat.md +60 -0
  122. package/docs/handbook/en/reference/timeline.md +56 -0
  123. package/docs/handbook/en/reference.md +26 -0
  124. package/docs/handbook/en/start.md +2 -2
  125. package/docs/handbook/en/workflow.md +6 -5
  126. package/docs/handbook/en/writing.md +3 -2
  127. package/docs/handbook/index.md +1 -1
  128. package/docs/handbook/reference/card.md +85 -0
  129. package/docs/handbook/reference/chart.md +68 -0
  130. package/docs/handbook/reference/choice.md +69 -0
  131. package/docs/handbook/reference/compare.md +59 -0
  132. package/docs/handbook/reference/figure.md +84 -0
  133. package/docs/handbook/reference/file/checklist.md +17 -0
  134. package/docs/handbook/reference/file.md +50 -0
  135. package/docs/handbook/reference/html.md +62 -0
  136. package/docs/handbook/reference/interactive.md +71 -0
  137. package/docs/handbook/reference/list.md +72 -0
  138. package/docs/handbook/reference/relations.md +113 -0
  139. package/docs/handbook/reference/stat.md +60 -0
  140. package/docs/handbook/reference/timeline.md +56 -0
  141. package/docs/handbook/reference.md +26 -0
  142. package/docs/handbook/start.md +1 -1
  143. package/docs/handbook/workflow.md +6 -5
  144. package/docs/handbook/writing.md +3 -2
  145. package/next.config.ts +6 -0
  146. package/package.json +2 -1
  147. package/public/images/garden/ref-card-book.svg +1 -0
  148. package/public/images/garden/ref-card-lamp.svg +1 -0
  149. package/scripts/build-content.mjs +37 -16
  150. package/scripts/favicon-ico.mjs +9 -6
  151. package/scripts/file-preview.mjs +10 -7
  152. package/scripts/handbook-allowlist.mjs +18 -0
  153. package/scripts/handbook-facts.mjs +305 -11
  154. package/scripts/link-assets.mjs +7 -0
  155. package/scripts/new-post.mjs +18 -9
  156. package/scripts/prepare-pages-output.mjs +30 -18
  157. package/scripts/redirects.mjs +17 -6
  158. package/templates/garden-blocks.md +67 -0
  159. package/templates/garden-snippets.plist +61 -0
  160. package/templates/site/Makefile.en +45 -0
  161. package/templates/site/README.en.md +15 -0
  162. package/templates/site/site.config.en.yaml +55 -0
  163. package/templates/skills-en/garden-interactive-chart/SKILL.md +84 -0
  164. package/templates/skills-en/garden-interactive-chart/references/shell.html +49 -0
  165. package/templates/skills-en/garden-interactive-chart/references/style.md +61 -0
  166. package/templates/skills-en/garden-post-review/SKILL.md +63 -0
  167. package/templates/skills-en/garden-ui/SKILL.md +118 -0
  168. package/templates/workflows/pages.en.yml +195 -0
  169. package/templates/workflows/pages.yml +195 -0
  170. package/ui/garden/catalog.mjs +205 -4
  171. package/ui/garden/check.mjs +6 -0
  172. package/ui/garden/file-block.mjs +8 -7
  173. package/ui/garden/html-lang.mjs +73 -1
  174. package/ui/garden/markdown-links.mjs +7 -4
  175. package/ui/garden/parse.mjs +45 -0
  176. package/ui/garden/relations.mjs +252 -0
  177. package/ui/garden/search-index.mjs +6 -4
  178. package/ui/garden/source-icon.mjs +54 -0
  179. package/ui/parity-fixtures.mjs +77 -0
  180. package/ui/preview/dist/garden-ui.esm.js +41 -4
  181. package/ui/preview/dist/garden-ui.iife.js +41 -4
  182. package/ui/prompt/rules.md +28 -1
  183. package/ui/react/components/chart.tsx +1 -1
  184. package/ui/react/components/display.tsx +1 -1
  185. package/ui/react/lang.tsx +4 -0
  186. package/ui/react/parity.tsx +8 -6
  187. package/worker/index.ts +8 -2
package/app/styles/a2.css CHANGED
@@ -2298,3 +2298,341 @@ html[data-theme="dark"] .gf-scrim { background: rgb(0 0 0 / 45%); }
2298
2298
  color: var(--muted);
2299
2299
  font: 400 13px/1.4 var(--sans);
2300
2300
  }
2301
+
2302
+ /* ----- Intelligent UI patterns: cycle, list, choice, verdicts, card rows, source chips ----- */
2303
+
2304
+ .g-ic { flex: none; color: var(--soft); }
2305
+
2306
+ /* Source chip: a small pill with the site's icon (or its first letter) and host. */
2307
+ .g-srcs { display: inline-flex; align-items: center; gap: 4px; vertical-align: 1px; white-space: nowrap; }
2308
+ .markdown-body a.ext.g-src,
2309
+ .markdown-body a.ext.g-src-chip {
2310
+ display: inline-flex;
2311
+ align-items: center;
2312
+ gap: 5px;
2313
+ padding: 1px 8px 1px 5px;
2314
+ border-radius: 999px;
2315
+ background: var(--well);
2316
+ color: var(--soft);
2317
+ font: 400 12px / 1.7 var(--sans);
2318
+ text-decoration: none;
2319
+ white-space: nowrap;
2320
+ max-width: 14em;
2321
+ overflow: hidden;
2322
+ text-overflow: ellipsis;
2323
+ }
2324
+ .markdown-body a.ext.g-src,
2325
+ .markdown-body a.ext.g-src-chip { background-image: none; padding-right: 8px; vertical-align: 1px; }
2326
+ .markdown-body a.ext.g-src-chip img.fav { margin: 0; vertical-align: 0; }
2327
+ .markdown-body a.ext.g-src:hover,
2328
+ .markdown-body a.ext.g-src-chip:hover { background: var(--rule); color: var(--ink); text-decoration: none; }
2329
+ .g-src-ic {
2330
+ display: inline-grid;
2331
+ place-items: center;
2332
+ width: 14px;
2333
+ height: 14px;
2334
+ border-radius: 50%;
2335
+ background: var(--paper);
2336
+ color: var(--ink);
2337
+ font: 600 9px / 1 var(--sans);
2338
+ text-transform: uppercase;
2339
+ }
2340
+ .g-src-fav { width: 14px; height: 14px; border-radius: 3px; object-fit: contain; }
2341
+ .g-src-more { font: 400 12px / 1.7 var(--sans); color: var(--muted); }
2342
+
2343
+ /* relations: key elements on a circle; narrow screens list each node's links as text */
2344
+ .g-rel {
2345
+ margin: 28px 0;
2346
+ padding: 28px 24px 16px;
2347
+ border: 1px solid var(--rule);
2348
+ border-radius: var(--r-lg);
2349
+ container-type: inline-size;
2350
+ }
2351
+ .markdown-body .g-rel-h { margin: 0 0 16px; font: 600 var(--fs-small) / 1.5 var(--sans); color: var(--ink); }
2352
+ .g-rel-ring { position: relative; height: 440px; }
2353
+ .g-rel-ring[data-n="3"] { height: 360px; }
2354
+ .g-rel-ring[data-n="4"] { height: 420px; }
2355
+ .g-rel-ring[data-n="6"] { height: 480px; }
2356
+ .g-rel-node {
2357
+ position: absolute;
2358
+ left: var(--x);
2359
+ top: var(--y);
2360
+ width: var(--w);
2361
+ transform: translate(-50%, -50%);
2362
+ z-index: 1;
2363
+ box-sizing: border-box;
2364
+ padding: 12px 14px;
2365
+ border: 1.5px solid var(--rule-strong);
2366
+ border-radius: 16px;
2367
+ background: var(--card-hover);
2368
+ text-align: center;
2369
+ }
2370
+ .markdown-body .g-rel-t { margin: 0; font: 700 var(--fs-lead) / 1.35 var(--sans); color: var(--ink); }
2371
+ .markdown-body .g-rel-x { margin: 4px 0 0; font: 400 15px / 1.5 var(--sans); color: var(--soft); }
2372
+ /* On the circle the arrows say it; the text lines stay for screen readers. */
2373
+ .markdown-body .g-rel-out { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; margin: 0; }
2374
+ .g-rel-svg { position: absolute; inset: 0; overflow: visible; pointer-events: none; }
2375
+ .g-rel-svg path { fill: none; stroke: var(--muted); stroke-width: 2; stroke-linecap: round; }
2376
+ .g-rel-svg path.dash { stroke-dasharray: 5 6; stroke-width: 1.75; }
2377
+ .g-rel-svg marker path { fill: var(--muted); stroke: none; }
2378
+ /* A label sits on its line with a page-coloured pad that hides the line under it. */
2379
+ .g-rel-label {
2380
+ position: absolute;
2381
+ z-index: 2;
2382
+ transform: translate(-50%, -50%);
2383
+ padding: 1px 6px;
2384
+ border-radius: 6px;
2385
+ background: var(--paper);
2386
+ color: var(--soft);
2387
+ font: 500 12.5px / 1.4 var(--sans);
2388
+ white-space: nowrap;
2389
+ pointer-events: none;
2390
+ }
2391
+ .g-rel figcaption { margin-top: 18px; font-size: 13px; color: var(--muted); text-align: center; }
2392
+ /* Same graph at every width. Below 560px the nodes take a larger share of the width,
2393
+ show their title on at most two lines (>= 13px), and open the full text on tap. */
2394
+ .g-rel-node { font: inherit; color: inherit; appearance: none; -webkit-appearance: none; }
2395
+ .g-rel-node > span { display: block; }
2396
+ /* Every width: titles stop at two lines, descriptions at three, both with an ellipsis; tap opens the full text. */
2397
+ .g-rel-node { cursor: pointer; }
2398
+ .g-rel-node:focus-visible { outline: 2px solid var(--ink); outline-offset: 2px; }
2399
+ .g-rel-node[aria-expanded="true"] { border-color: var(--ink); }
2400
+ .markdown-body .g-rel-t, .markdown-body .g-rel-x { display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; overflow: hidden; text-overflow: ellipsis; overflow-wrap: normal; word-break: normal; hyphens: manual; text-align: center; }
2401
+ .markdown-body .g-rel-x { -webkit-line-clamp: 3; }
2402
+ .g-rel[data-compact] .g-rel-node { padding: 8px 8px; border-radius: 12px; }
2403
+ .markdown-body .g-rel[data-compact] .g-rel-t {
2404
+ font-size: 13.5px;
2405
+ line-height: 1.3;
2406
+ display: -webkit-box;
2407
+ -webkit-box-orient: vertical;
2408
+ -webkit-line-clamp: 2;
2409
+ overflow: hidden;
2410
+ overflow-wrap: normal;
2411
+ }
2412
+ /* The middle node of a narrow hub shows its title only; tap for the description. */
2413
+ .g-rel[data-compact] .g-rel-node[data-center] .g-rel-x { display: none; }
2414
+ .g-rel[data-compact] .g-rel-node[data-center] { padding-left: 3px; padding-right: 3px; }
2415
+ .markdown-body .g-rel[data-compact] .g-rel-node[data-center] .g-rel-t { font-size: 13px; }
2416
+ /* A wide hub has five or six nodes on one ring: titles at 16px keep two lines without crowding the spokes. */
2417
+ .markdown-body .g-rel:not([data-compact]) .g-rel-ring[data-center] .g-rel-t { font-size: 16px; }
2418
+ .markdown-body .g-rel[data-compact] .g-rel-x { margin-top: 2px; font-size: 13px; line-height: 1.35; -webkit-line-clamp: 2; }
2419
+ .g-rel-badge {
2420
+ display: inline-grid;
2421
+ place-items: center;
2422
+ min-width: 22px;
2423
+ height: 22px;
2424
+ padding: 0;
2425
+ border-radius: 50%;
2426
+ border: 1px solid var(--rule-strong);
2427
+ color: var(--ink);
2428
+ font: 600 13px / 1 var(--sans);
2429
+ }
2430
+ .g-rel-legend { list-style: none; margin: 12px 4px 0; padding: 0; font: 400 13px / 1.6 var(--sans); color: var(--soft); }
2431
+ .markdown-body .g-rel-legend { list-style: none; padding-left: 0; }
2432
+ .markdown-body .g-rel-legend li { margin: 0; padding-left: 0; }
2433
+ .g-rel-detail { margin: 12px 4px 0; padding: 10px 12px; border-radius: 12px; background: var(--well); font: 400 14px / 1.55 var(--sans); color: var(--text); }
2434
+ .g-rel-detail-t { display: block; font-weight: 700; color: var(--ink); }
2435
+ .g-rel-detail-x { display: block; color: var(--soft); }
2436
+ @media (max-width: 600px) {
2437
+ .g-rel { padding: 16px 12px 10px; }
2438
+ }
2439
+
2440
+ /* list: compact icon rows, or titled icon steps */
2441
+ .g-list { margin: 24px 0; }
2442
+ .g-list ul { list-style: none; margin: 0; padding: 0; }
2443
+ .markdown-body .g-list-h { margin: 0 0 10px; font: 600 var(--fs-small) / 1.5 var(--sans); color: var(--ink); }
2444
+ .g-list-rows ul { padding: 12px 18px; border: 1px solid var(--rule); border-radius: var(--r); }
2445
+ .markdown-body .g-list li { margin: 0; }
2446
+ .g-list-rows li { display: flex; align-items: flex-start; gap: 10px; padding: 5px 0; }
2447
+ .g-list-rows .g-list-ic { display: flex; height: 1.7em; align-items: center; }
2448
+ .markdown-body .g-list-rows .g-list-x { margin: 0; line-height: 1.7; }
2449
+ .g-list-steps li { display: grid; grid-template-columns: 28px minmax(0, 1fr); gap: 16px; padding: 22px 0; border-top: 1px solid var(--rule); }
2450
+ .g-list-steps li:first-child { border-top: 0; padding-top: 6px; }
2451
+ .g-list-steps .g-list-ic { padding-top: 3px; }
2452
+ .markdown-body .g-list-t { margin: 0 0 4px; font-weight: 600; color: var(--ink); }
2453
+ .markdown-body .g-list-steps .g-list-x { margin: 0 0 4px; color: var(--text); }
2454
+ .g-list figcaption { margin-top: 8px; font-size: 13px; color: var(--muted); }
2455
+
2456
+ /* choice */
2457
+ .g-choice { margin: 28px 0; padding: 16px; border: 1px solid var(--rule); border-radius: var(--r-lg); }
2458
+ .markdown-body .g-choice-h { margin: 0 0 12px; font-weight: 600; color: var(--ink); }
2459
+ .g-choice-opts { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 12px; }
2460
+ .g-choice-opt {
2461
+ display: flex;
2462
+ flex-direction: column;
2463
+ align-items: center;
2464
+ gap: 6px;
2465
+ padding: 20px 16px 18px;
2466
+ border: 1px solid var(--rule);
2467
+ border-radius: 16px;
2468
+ background: transparent;
2469
+ color: var(--text);
2470
+ font: inherit;
2471
+ text-align: center;
2472
+ cursor: pointer;
2473
+ transition: background .15s ease, border-color .15s ease;
2474
+ }
2475
+ .g-choice-opt:hover { background: var(--card-hover); }
2476
+ .g-choice-opt:focus-visible { outline: 2px solid var(--ink); outline-offset: 2px; }
2477
+ .g-choice-opt[aria-checked="true"] { border-color: var(--ink); background: var(--well); }
2478
+ .g-choice-ic .g-ic { color: var(--ink); }
2479
+ .g-choice-v { font: 700 22px / 1.3 var(--sans); color: var(--ink); font-variant-numeric: tabular-nums; }
2480
+ .g-choice-x { font-size: 14px; line-height: 1.6; color: var(--muted); }
2481
+ .g-choice-l { display: inline-flex; align-items: center; gap: 8px; margin-top: 6px; font-size: 14px; font-weight: 500; color: var(--ink); }
2482
+ .g-choice-dot { width: 16px; height: 16px; border-radius: 50%; border: 1.5px solid var(--muted); box-sizing: border-box; }
2483
+ .g-choice-opt[aria-checked="true"] .g-choice-dot { border: 5px solid var(--ink); }
2484
+ .markdown-body .g-choice-reply { margin: 14px 4px 0; font-size: 15px; color: var(--text); }
2485
+ .g-choice figcaption { margin: 12px 2px 0; font-size: 13px; color: var(--muted); }
2486
+ @media (max-width: 600px) {
2487
+ .g-choice { padding: 12px; }
2488
+ .g-choice-opts { grid-template-columns: 1fr; }
2489
+ .g-choice-opt { display: grid; grid-template-columns: auto 1fr; grid-template-areas: "ic v" "ic x" "ic l"; justify-items: start; text-align: left; column-gap: 14px; row-gap: 2px; padding: 14px 16px; }
2490
+ .g-choice-ic { grid-area: ic; align-self: center; }
2491
+ .g-choice-v { grid-area: v; font-size: 20px; }
2492
+ .g-choice-x { grid-area: x; }
2493
+ .g-choice-l { grid-area: l; }
2494
+ }
2495
+
2496
+ /* compare with verdicts: stacked bad / good blocks */
2497
+ .g-verdicts { margin: 20px 0; }
2498
+ .g-verdict { padding: 14px 0; border-top: 1px solid var(--rule); }
2499
+ .g-verdict:first-child { border-top: 0; padding-top: 4px; }
2500
+ .markdown-body .g-verdict h4 { display: flex; align-items: center; gap: 10px; margin: 0 0 6px; font: 600 var(--fs-body) / 1.5 var(--sans); color: var(--ink); }
2501
+ .markdown-body .g-verdict p { margin: 0 0 0 30px; color: var(--text); }
2502
+ .g-verdict-ic.bad { color: #b4473a; }
2503
+ .g-verdict-ic.good { color: #2d6a3e; }
2504
+ html[data-theme="dark"] .g-verdict-ic.bad { color: #e48a7c; }
2505
+ html[data-theme="dark"] .g-verdict-ic.good { color: #7fbf8e; }
2506
+
2507
+ /* card rows: thumbnail left, text right, dividers between */
2508
+ .g-entities { margin: 24px 0; }
2509
+ .g-entity-row { display: grid; grid-template-columns: 132px minmax(0, 1fr); gap: 18px; padding: 22px 0; border-top: 1px solid var(--rule); }
2510
+ .g-entity-row:first-child { border-top: 0; padding-top: 4px; }
2511
+ .g-entity-row:not(:has(.g-entity-img)) { grid-template-columns: minmax(0, 1fr); }
2512
+ .g-entity-row .g-entity-img img { display: block; width: 132px; height: 132px; object-fit: cover; border-radius: 14px; border: 1px solid var(--rule); margin: 0; }
2513
+ .markdown-body .g-entity-row .g-entity-t { margin: 0 0 6px; font: 600 var(--fs-body) / 1.5 var(--sans); }
2514
+ .markdown-body .g-entity-row p { margin: 0 0 6px; }
2515
+ .markdown-body .g-entity-k { color: var(--text); }
2516
+ @media (max-width: 600px) {
2517
+ .g-entity-row { grid-template-columns: 76px minmax(0, 1fr); gap: 14px; }
2518
+ .g-entity-row .g-entity-img img { width: 76px; height: 76px; border-radius: 10px; }
2519
+ }
2520
+
2521
+ /* review fixes: chip gap, truncated hosts, the "none of these" choice */
2522
+ .markdown-body a.ext.g-src-chip > span,
2523
+ .markdown-body a.ext.g-src > span { overflow: hidden; text-overflow: ellipsis; }
2524
+ /* A single source shows its whole domain; a tag with "+N" cuts only hosts past ~14 characters. */
2525
+ .markdown-body .g-srcs.is-multi a.ext.g-src > span { max-width: 14ch; }
2526
+ .markdown-body .g-srcs:not(.is-multi) a.ext.g-src { max-width: none; }
2527
+ .markdown-body a.ext.g-src-chip,
2528
+ .markdown-body a.ext.g-src { gap: 6px; }
2529
+ .g-choice-group { display: grid; gap: 0; }
2530
+ .g-choice-none {
2531
+ justify-self: center;
2532
+ margin: 12px 0 0;
2533
+ padding: 4px 10px;
2534
+ border: 0;
2535
+ border-radius: 999px;
2536
+ background: none;
2537
+ color: var(--muted);
2538
+ font: 500 14px / 1.6 var(--sans);
2539
+ text-decoration: underline;
2540
+ text-decoration-color: var(--rule);
2541
+ text-underline-offset: 3px;
2542
+ cursor: pointer;
2543
+ }
2544
+ .g-choice-none:hover { color: var(--ink); }
2545
+ .g-choice-none:focus-visible { outline: 2px solid var(--ink); outline-offset: 2px; }
2546
+ .g-choice-none[aria-checked="true"] { color: var(--ink); background: var(--well); text-decoration: none; }
2547
+ .markdown-body img.g-src-fav { display: block; flex: none; width: 14px; height: 14px; max-width: none; margin: 0; border-radius: 3px; object-fit: contain; box-shadow: none; }
2548
+
2549
+ /* Handbook field tables: Required never wraps; narrow screens show each row as a card. */
2550
+ /* Short values (必填, Optional) stay on one line; long conditions wrap instead of widening the column. */
2551
+ .table-wrap .field-table th.ft-req,
2552
+ .table-wrap .field-table td.ft-req { white-space: normal; min-width: 4em; max-width: 9em; width: 9em; }
2553
+ @media (max-width: 640px) {
2554
+ .table-wrap.field-wrap { border: 0; border-radius: 0; overflow: visible; }
2555
+ .field-wrap .field-table,
2556
+ .field-wrap .field-table tbody,
2557
+ .field-wrap .field-table tr,
2558
+ .field-wrap .field-table td { display: block; width: auto; }
2559
+ .field-wrap .field-table thead { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); }
2560
+ .field-wrap .field-table tr { margin: 0 0 12px; padding: 12px 14px; border-radius: var(--r); background: var(--well); }
2561
+ .field-wrap .field-table td { padding: 4px 0; border: 0; white-space: normal; min-width: 0; }
2562
+ .field-wrap .field-table td:first-child { padding: 0 0 6px; font-weight: 600; color: var(--ink); }
2563
+ .field-wrap .field-table td:not(:first-child)::before {
2564
+ content: attr(data-label);
2565
+ display: block;
2566
+ font: 600 12px / 1.5 var(--sans);
2567
+ color: var(--muted);
2568
+ }
2569
+ }
2570
+
2571
+ /* ===== Component reference (/garden/reference/*): code beside the real block ===== */
2572
+
2573
+ @media (min-width: 1280px) {
2574
+ .garden-doc-page.is-reference { grid-template-columns: 224px minmax(0, 980px) 188px; }
2575
+ .garden-doc-page.is-reference .article-column { max-width: 980px; width: 100%; }
2576
+ }
2577
+ .garden-doc-page.is-reference .markdown-body > p:first-child { margin: 0 0 8px; }
2578
+ .garden-doc-page.is-reference .markdown-body > p:first-child + h2 { margin-top: 0; padding-top: 0; }
2579
+ .markdown-body .ref-pane[data-p="c"] pre[data-language]::before { display: none; }
2580
+ .ref-demo {
2581
+ --rd-s: #f0eee6; --rd-p: #faf9f5; --rd-i: #30302e; --rd-m: #6b6a65; --rd-r: #e3e0d5;
2582
+ border: 1px solid var(--rd-r);
2583
+ border-radius: 14px;
2584
+ margin: 1.2em 0 1.6em;
2585
+ overflow: hidden;
2586
+ background: var(--rd-p);
2587
+ }
2588
+ html[data-theme="dark"] .ref-demo { --rd-s: #262624; --rd-p: #1f1e1d; --rd-i: #eceae3; --rd-m: #a9a69c; --rd-r: #3a3936; }
2589
+ .ref-tabs { display: flex; gap: 6px; padding: 6px; border-bottom: 1px solid var(--rd-r); background: var(--rd-s); }
2590
+ .ref-tabs button {
2591
+ font: inherit; font-size: 14px; min-height: 38px; min-width: 72px; border: 0; border-radius: 9px;
2592
+ padding: 0 16px; background: transparent; color: var(--rd-m); cursor: pointer;
2593
+ }
2594
+ .ref-tabs button[aria-selected="true"] { background: var(--rd-p); color: var(--rd-i); box-shadow: 0 1px 2px rgb(0 0 0 / 8%); }
2595
+ .ref-pane { padding: 16px; min-width: 0; }
2596
+ .ref-pane[data-off] { display: none; }
2597
+ .ref-pane-h { display: none; }
2598
+ .ref-pane > :last-child { margin-bottom: 0; }
2599
+ .ref-pane[data-p="c"] .ux-cb { display: block; margin: 0; padding: 0; min-height: 0; }
2600
+ .ref-pane[data-p="c"] pre {
2601
+ margin: 0; max-height: 560px; overflow: auto; white-space: pre-wrap; overflow-wrap: anywhere;
2602
+ }
2603
+ .ref-pane[data-p="c"] pre code { white-space: pre-wrap; overflow-wrap: anywhere; }
2604
+ @media (min-width: 1024px) {
2605
+ .ref-demo {
2606
+ display: grid;
2607
+ grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
2608
+ align-items: start;
2609
+ background: linear-gradient(90deg, var(--rd-s) calc(50% - 0.5px), var(--rd-r) calc(50% - 0.5px) calc(50% + 0.5px), var(--rd-p) calc(50% + 0.5px));
2610
+ }
2611
+ .ref-tabs { display: none; }
2612
+ /* Flex columns: no margin collapsing, so both panes start their content on the same line. */
2613
+ .ref-demo .ref-pane, .ref-demo .ref-pane[data-off] { display: flex; flex-direction: column; }
2614
+ .ref-demo .ref-pane > * { margin-top: 0 !important; }
2615
+ /* interactive: its own top spacing (g-play margin and padding, the figure's
2616
+ margin) would push the preview 67px below the code. Reference only. */
2617
+ .ref-demo .ref-pane[data-p="v"] .ux-fs > .g-play { margin-top: 0; padding-top: 0; }
2618
+ .ref-demo .ref-pane[data-p="v"] .g-play > .garden-ui:first-child { margin-top: 0; }
2619
+ .ref-pane[data-p="c"] { grid-column: 1; grid-row: 1; }
2620
+ .ref-pane[data-p="v"] { grid-column: 2; grid-row: 1; }
2621
+ .ref-pane-h { display: block; font-size: 12px; line-height: 18px; letter-spacing: 0.04em; color: var(--rd-m); margin: 0 0 10px !important; }
2622
+ /* Code starts on the same line as the preview: no pre padding or frame on top. */
2623
+ .ref-pane[data-p="c"] pre { padding: 0 4px 4px 0; background: transparent; border: 0; }
2624
+ .ref-pane[data-p="c"] .ux-cb > :not(pre) { display: none; }
2625
+ }
2626
+ @media (max-width: 1023px) {
2627
+ .ref-pane[data-p="c"] pre { padding-right: 56px; }
2628
+ .ref-demo-in .toc-mobile-trigger { opacity: 0; visibility: hidden; pointer-events: none; }
2629
+ }
2630
+
2631
+ /* figure reference page: the drawing needs room, so the example stacks
2632
+ (preview on top, full width; code below). Scoped to the reference demo;
2633
+ figures in articles are untouched. */
2634
+ @media (min-width: 1024px) {
2635
+ .ref-demo[data-name="figure"] { grid-template-columns: minmax(0, 1fr); background: var(--rd-p); }
2636
+ .ref-demo[data-name="figure"] .ref-pane[data-p="v"] { grid-column: 1; grid-row: 1; }
2637
+ .ref-demo[data-name="figure"] .ref-pane[data-p="c"] { grid-column: 1; grid-row: 2; background: var(--rd-s); border-top: 1px solid var(--rd-r); }
2638
+ }
@@ -129,6 +129,10 @@ html[data-theme="dark"] .sh-rail{color:var(--muted)}
129
129
  .sh-year > h2{font:700 var(--fs-lead)/1.3 var(--sans);color:var(--ink);margin:40px 0 0;padding:0 0 12px;border-bottom:1px solid var(--rule-strong);display:flex;align-items:baseline;gap:10px}
130
130
  .sh-year > h2 .n{font:400 var(--fs-meta)/1 var(--sans);color:var(--muted)}
131
131
  .sh-year[hidden],.sh-row[hidden]{display:none}
132
+ /* Home list dates drop the year in the current year, so the track is fixed to the locale's longest label
133
+ (zh「2025 年 12 月 19 日」≈ 7.1em, en "May 28, 2025" ≈ 4.9em at the row's 17px) and every title starts at the same x. */
134
+ :root{--sh-date-track:7.5em}
135
+ :root:lang(en){--sh-date-track:5.5em}
132
136
  .sh-rows{list-style:none;margin:0;padding:0}
133
137
  .sh-row{position:relative;display:grid;grid-template-columns:72px minmax(0,1fr) auto;gap:4px 20px;padding:18px 0 19px;border-bottom:1px solid var(--rule)}
134
138
  .sh-row time{grid-row:1;font-size:var(--fs-small);line-height:1.6;color:var(--muted);font-variant-numeric:tabular-nums;padding-top:2px;white-space:nowrap}
@@ -144,6 +148,7 @@ html[data-theme="dark"] .sh-rail{color:var(--muted)}
144
148
  .sh-row .series{font-weight:400;color:var(--muted);font-size:var(--fs-meta);margin-left:8px;white-space:nowrap}
145
149
  .sh-row:hover h3 a{color:var(--blue);text-decoration:underline;text-decoration-thickness:1px;text-underline-offset:4px}
146
150
  .sh-row h3 a{transition:color .15s ease}
151
+ .sh-home-grid .sh-row{grid-template-columns:var(--sh-date-track) minmax(0,1fr) auto}
147
152
  .is-compact .sh-row{padding:11px 0 12px}
148
153
  .is-compact .sh-row p{display:none}
149
154
  .is-compact .sh-row h3{font-weight:500}
@@ -170,8 +175,6 @@ html[data-theme="dark"] .sh-rail{color:var(--muted)}
170
175
  .sh-feature p{margin:14px 0 0;font-size:var(--fs-body);color:var(--soft)}
171
176
  .sh-feature .tags{display:flex;flex-wrap:wrap;gap:6px 14px;margin-top:16px;font-size:var(--fs-meta);color:var(--muted)}
172
177
  .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}
173
- .sh-home-grid .sh-row{grid-template-columns:minmax(6em,max-content) minmax(0,1fr) auto}
174
- .sh-home-grid .sh-row time{min-width:6em;font-variant-numeric:tabular-nums}
175
178
  .sh-side-list{list-style:none;margin:0;padding:0}
176
179
  .sh-side-list li{position:relative;padding:16px 0;border-bottom:1px solid var(--rule)}
177
180
  .sh-side-list a{font:600 15px/1.6 var(--sans);color:var(--ink);text-decoration:none}
@@ -394,6 +397,7 @@ html[data-theme="dark"] .sh-repos > li.sk::after{background:linear-gradient(90de
394
397
  /* ===== 专栏 v4: moderate cover, no numbers, dates, link to category ===== */
395
398
  .sh-series{display:grid;grid-template-columns:240px minmax(0,760px);gap:0 40px;padding:36px 0;border-top:1px solid var(--rule)}
396
399
  .sh-series:first-of-type{border-top:0;padding-top:8px}
400
+ .sh-series.no-cover{grid-template-columns:minmax(0,760px)}
397
401
  .sh-series .cover{aspect-ratio:16/9;border-radius:var(--r);display:flex;align-items:center;justify-content:center;overflow:hidden;align-self:start}
398
402
  .sh-series h2{font:700 var(--fs-h2)/1.3 var(--sans);color:var(--ink);margin:0}
399
403
  .sh-series .count{font-size:var(--fs-small);color:var(--muted);margin:6px 0 0}
@@ -426,6 +430,8 @@ html[data-theme="dark"] .sh-repos > li.sk::after{background:linear-gradient(90de
426
430
  .sh-series li a{padding:0}
427
431
  .sh-series li time{margin-top:2px}
428
432
  .sh-series .tocat{margin-left:0}
433
+ .sh-series.no-cover{grid-template-columns:minmax(0,1fr)}
434
+ .sh-series.no-cover h2,.sh-series.no-cover .count{grid-column:1 / -1;grid-row:auto}
429
435
  }
430
436
 
431
437
  .sh-row .incol{grid-column:2;justify-self:start;position:relative;z-index:1;margin-top:4px;font-size:var(--fs-meta);line-height:1.6;color:var(--muted);text-decoration:none}
@@ -443,3 +449,10 @@ html[data-theme="dark"] .sh-repos > li.sk::after{background:linear-gradient(90de
443
449
  a.sh-lang:hover{color:var(--ink)}
444
450
  a.sh-lang:focus-visible{outline:2px solid var(--blue);outline-offset:2px}
445
451
  .sh-lang.is-disabled{opacity:.55;cursor:default}
452
+
453
+ /* English pages: Latin system fonts first, Chinese fonts only as fallback
454
+ (otherwise English text renders in Noto Sans CJK / YaHei). */
455
+ :root:lang(en){
456
+ --sans:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"PingFang SC","Hiragino Sans GB","Noto Sans SC","Noto Sans CJK SC","Microsoft YaHei",system-ui,sans-serif;
457
+ --serif:"Source Serif 4",Georgia,"Times New Roman","Source Han Serif SC","Noto Serif SC","Noto Serif CJK SC","Songti SC","STSong",serif;
458
+ }
package/app/tags/page.tsx CHANGED
@@ -6,26 +6,28 @@ import { articleTagHref, tagCounts } from "../lib/tag-filter";
6
6
  import { TagRedirect } from "../components/TagRedirect";
7
7
  import { siteDocumentTitle } from "../site.config";
8
8
  import { pageAlternates } from "../lib/page-alternates";
9
+ import { siteLang, ui } from "../lib/i18n";
9
10
 
10
11
  export const metadata = {
11
12
  title: siteDocumentTitle,
12
- description: "全部标签。",
13
+ description: ui().tagsDescription,
13
14
  alternates: pageAlternates("/tags"),
14
15
  };
15
16
 
16
17
  export default function TagsPage() {
18
+ const t = ui();
17
19
  const tags = tagCounts(posts);
18
20
  return (
19
21
  <main className="sh-main" id="content">
20
22
  <div className="sh-head">
21
- <h1>标签</h1>
22
- <p className="sh-deck">{tags.length} 个标签,按文章数排序。点一个标签,查看带这个标签的文章。</p>
23
+ <h1>{t.tagsTitle}</h1>
24
+ <p className="sh-deck">{siteLang === "en" ? t.tagsDeck(tags.length) : <>{tags.length} 个标签,按文章数排序。点一个标签,查看带这个标签的文章。</>}</p>
23
25
  </div>
24
26
  <Suspense>
25
27
  <TagRedirect />
26
28
  </Suspense>
27
29
  {tags.length ? (
28
- <ul className="sh-tagchips sh-tagindex" aria-label="全部标签">
30
+ <ul className="sh-tagchips sh-tagindex" aria-label={t.tagsIndexLabel}>
29
31
  {tags.map(([tag, count]) => (
30
32
  <li key={tag}>
31
33
  <Link className="sh-tagchip" href={siteHref(articleTagHref(tag))}>
@@ -35,7 +37,7 @@ export default function TagsPage() {
35
37
  ))}
36
38
  </ul>
37
39
  ) : (
38
- <p className="sh-deck">还没有标签。</p>
40
+ <p className="sh-deck">{t.tagsEmpty}</p>
39
41
  )}
40
42
  </main>
41
43
  );
@@ -15,6 +15,7 @@ import {
15
15
  } from "../../lib/site";
16
16
  import { siteHref } from "../../lib/site-href";
17
17
  import { isStaticPlaceholder, STATIC_PLACEHOLDER, staticParams } from "../../../ui/garden/static-params.mjs";
18
+ import { ui } from "../../lib/i18n";
18
19
 
19
20
  export function generateStaticParams() {
20
21
  return staticParams(thoughts.map((thought) => ({ slug: thought.slug })), { slug: STATIC_PLACEHOLDER });
@@ -72,7 +73,7 @@ export default async function ThoughtPage({
72
73
  <main className="page-shell thought-detail-page">
73
74
  <Link href={siteHref("/thoughts")} className="back-link">
74
75
  <ArrowLeft size={16} />
75
- 返回随想
76
+ {ui().backToThoughts}
76
77
  </Link>
77
78
  <ThoughtCard thought={thought} showPermalink={false} />
78
79
  <Webmentions
@@ -37,7 +37,7 @@ export default async function ThoughtsPage({ params }: { params: Promise<{ n: st
37
37
  }
38
38
  return (
39
39
  <main className="page-shell thoughts-page">
40
- <PageIntro eyebrow="THOUGHTS / 随想" title={siteConfig.pages.thoughts.title} subtitle={siteConfig.pages.thoughts.subtitle} />
40
+ <PageIntro eyebrow={ui().thoughtsEyebrow} title={siteConfig.pages.thoughts.title} subtitle={siteConfig.pages.thoughts.subtitle} />
41
41
  <ThoughtFeed page={page} />
42
42
  </main>
43
43
  );
@@ -4,6 +4,7 @@ import { ContentEmptyState } from "../components/ContentEmptyState";
4
4
  import { PageIntro } from "../components/PageIntro";
5
5
  import { siteConfig, siteDocumentTitle } from "../site.config";
6
6
  import { pageAlternates } from "../lib/page-alternates";
7
+ import { ui } from "../lib/i18n";
7
8
 
8
9
  export const metadata = {
9
10
  title: siteDocumentTitle,
@@ -17,7 +18,7 @@ export default function ThoughtsPage() {
17
18
  return (
18
19
  <main className="page-shell thoughts-page">
19
20
  <PageIntro
20
- eyebrow="THOUGHTS / 随想"
21
+ eyebrow={ui().thoughtsEyebrow}
21
22
  title={siteConfig.pages.thoughts.title}
22
23
  subtitle={siteConfig.pages.thoughts.subtitle}
23
24
  />
@@ -1,16 +1,25 @@
1
1
  import Image from "next/image";
2
2
  import { ParityInteractions } from "../../ui/react/parity";
3
+ import { siteLang } from "../lib/i18n";
3
4
 
4
5
  // Internal comparison page (docs/intelligent-ui-parity.md): Garden's renders next
5
6
  // to the ChatGPT originals. Kept out of search, so it has no canonical address.
6
7
  export const metadata = { robots: { index: false, follow: false } };
7
8
 
8
- const slices = [
9
+ const zhSlices = [
9
10
  { label: "商品销售", value: 78.2, color: "#539af8" },
10
11
  { label: "平台与营销服务", value: 9.4, color: "#6bc67f" },
11
12
  { label: "物流及其他服务", value: 12.4, color: "#ef8b57" },
12
13
  ];
13
14
 
15
+ const enSlices = [
16
+ { label: "Product sales", value: 78.2, color: "#539af8" },
17
+ { label: "Marketplace & marketing", value: 9.4, color: "#6bc67f" },
18
+ { label: "Logistics & other services", value: 12.4, color: "#ef8b57" },
19
+ ];
20
+
21
+ const slices = siteLang === "en" ? enSlices : zhSlices;
22
+
14
23
  function ring(start: number, sweep: number) {
15
24
  const cx = 371;
16
25
  const cy = 181;
@@ -51,32 +60,39 @@ export default function UiParityPage() {
51
60
  items: [...state.items, { ...slice, path: ring(state.angle, sweep) }],
52
61
  };
53
62
  }, { angle: -Math.PI / 2, items: [] }).items;
54
- const rows = [
55
- ["消费者状态", "不选择拼多多的原因", "对应的经济学概念"],
56
- ["不知道拼多多更便宜", "没有进行跨平台比价", "信息不对称、搜索成本"],
57
- ["知道更便宜,但不放心", "怀疑商品品质、商家或售后", "感知风险、信任成本"],
58
- ["知道更便宜,也相信是正品", "习惯使用天猫,不想迁移", "转换成本、消费惯性"],
59
- ];
63
+ const rows = siteLang === "en"
64
+ ? [
65
+ ["Consumer state", "Reason not to choose PDD", "Economic concept"],
66
+ ["Unaware PDD is cheaper", "No cross-platform comparison", "Information asymmetry, search cost"],
67
+ ["Knows it is cheaper, but hesitant", "Doubts product quality or after-sales", "Perceived risk, trust cost"],
68
+ ["Knows it is cheaper and authentic", "Used to Tmall, reluctant to switch", "Switching cost, consumer inertia"],
69
+ ]
70
+ : [
71
+ ["消费者状态", "不选择拼多多的原因", "对应的经济学概念"],
72
+ ["不知道拼多多更便宜", "没有进行跨平台比价", "信息不对称、搜索成本"],
73
+ ["知道更便宜,但不放心", "怀疑商品品质、商家或售后", "感知风险、信任成本"],
74
+ ["知道更便宜,也相信是正品", "习惯使用天猫,不想迁移", "转换成本、消费惯性"],
75
+ ];
60
76
  return (
61
77
  <main className="garden-ui" lang="en-US" style={{ margin: 0, border: 0, background: "#fff", padding: 32 }}>
62
78
  <section id="parity-stat" className="garden-ui-card" style={{ width: 768, marginBottom: 32 }}>
63
- <p className="garden-ui-kicker">2025 抖音电商发展数据报告</p>
79
+ <p className="garden-ui-kicker">{siteLang === "en" ? "2025 Douyin E-Commerce Report" : "2025 抖音电商发展数据报告"}</p>
64
80
  <p className="garden-ui-title-2xl">+49%</p>
65
- <p className="garden-ui-subtitle">货架场景成交额同比增长</p>
66
- <p className="garden-ui-text-sm">统计期为 2024 年 8 月至 2025 年 8 月。</p>
81
+ <p className="garden-ui-subtitle">{siteLang === "en" ? "Shelf-commerce GMV YoY growth" : "货架场景成交额同比增长"}</p>
82
+ <p className="garden-ui-text-sm">{siteLang === "en" ? "Measured from Aug 2024 to Aug 2025." : "统计期为 2024 年 8 月至 2025 年 8 月。"}</p>
67
83
  <p className="garden-ui-caption">
68
- {"抖音电商官方报告,经新华社报道。该增速不能直接代表抖音电商整体 GMV 增速。"}<a id="parity-cite" className="garden-ui-cite" href="https://www.news.cn">
84
+ {siteLang === "en" ? "Official Douyin E-Commerce report via Xinhua. Does not represent overall GMV growth." : "抖音电商官方报告,经新华社报道。该增速不能直接代表抖音电商整体 GMV 增速。"}<a id="parity-cite" className="garden-ui-cite" href="https://www.news.cn">
69
85
  <Image src="https://www.google.com/s2/favicons?domain=www.news.cn&sz=32" alt="" width={12} height={12} unoptimized />
70
86
  Xinhua Net
71
87
  </a>
72
88
  </p>
73
89
  </section>
74
90
  <section id="parity-card" className="garden-ui-card" style={{ width: 378, marginBottom: 32 }}>
75
- <p className="garden-ui-kicker">2025 年淘宝天猫商品索引规模</p>
76
- <p className="garden-ui-title-2xl">20 亿+</p>
77
- <p className="garden-ui-text-xs">商品条目</p>
91
+ <p className="garden-ui-kicker">{siteLang === "en" ? "2025 Taobao & Tmall Product Index Scale" : "2025 年淘宝天猫商品索引规模"}</p>
92
+ <p className="garden-ui-title-2xl">{siteLang === "en" ? "2B+" : "20 亿+"}</p>
93
+ <p className="garden-ui-text-xs">{siteLang === "en" ? "Product items" : "商品条目"}</p>
78
94
  <p className="garden-ui-caption">
79
- {"阿里巴巴披露其 AI 搜索推荐系统索引了超过 20 亿商品条目。"}<a className="garden-ui-cite" href="https://www.alibabagroup.com">
95
+ {siteLang === "en" ? "Alibaba disclosed that its AI search and recommendation system indexes over 2 billion items." : "阿里巴巴披露其 AI 搜索推荐系统索引了超过 20 亿商品条目。"}<a className="garden-ui-cite" href="https://www.alibabagroup.com">
80
96
  <Image src="https://www.google.com/s2/favicons?domain=www.alibabagroup.com&sz=32" alt="" width={12} height={12} unoptimized />
81
97
  Alibaba Group
82
98
  </a>
@@ -84,7 +100,7 @@ export default function UiParityPage() {
84
100
  </section>
85
101
  <section id="parity-pie" className="garden-ui-pie" style={{ width: 742, marginBottom: 32 }}>
86
102
  <div className="garden-ui-pie-chart">
87
- <svg viewBox="0 0 742 400" role="img" aria-label="收入结构">
103
+ <svg viewBox="0 0 742 400" role="img" aria-label={siteLang === "en" ? "Revenue mix" : "收入结构"}>
88
104
  {arcs.map((arc) => <path key={arc.label} d={arc.path} fill={arc.color} />)}
89
105
  </svg>
90
106
  <ul className="garden-ui-legend">
@@ -93,7 +109,7 @@ export default function UiParityPage() {
93
109
  ))}
94
110
  </ul>
95
111
  </div>
96
- <p className="garden-ui-pie-note">京东集团2025财年;商品与服务收入的会计确认方式不同。</p>
112
+ <p className="garden-ui-pie-note">{siteLang === "en" ? "JD.com FY2025; product and service revenues use different accounting recognition." : "京东集团2025财年;商品与服务收入的会计确认方式不同。"}</p>
97
113
  </section>
98
114
  <section id="parity-table" style={{ width: 768, marginBottom: 32 }}>
99
115
  <table className="garden-ui-table" style={{ tableLayout: "fixed" }}>
@@ -113,16 +129,16 @@ export default function UiParityPage() {
113
129
  </table>
114
130
  </section>
115
131
  <section id="parity-flow" className="garden-ui-card garden-ui-flow" style={{ width: 768 }}>
116
- <p>丰富的商家与商品供给</p>
132
+ <p>{siteLang === "en" ? "Broad merchant and product supply" : "丰富的商家与商品供给"}</p>
117
133
  <Arrow />
118
- <p>吸引更多具有不同需求的消费者</p>
134
+ <p>{siteLang === "en" ? "Attracts consumers with diverse needs" : "吸引更多具有不同需求的消费者"}</p>
119
135
  <Arrow />
120
- <p>更大的潜在成交机会吸引商家</p>
136
+ <p>{siteLang === "en" ? "Larger GMV opportunity attracts merchants" : "更大的潜在成交机会吸引商家"}</p>
121
137
  <Arrow />
122
- <p>更丰富的供给与交易数据</p>
138
+ <p>{siteLang === "en" ? "Richer supply and transaction data" : "更丰富的供给与交易数据"}</p>
123
139
  <Loop />
124
140
  </section>
125
- <ParityInteractions />
141
+ {siteLang === "en" ? <ParityInteractions lang="en" /> : <ParityInteractions />}
126
142
  </main>
127
143
  );
128
144
  }