@lupinum/ginko-docs 0.4.0 → 0.6.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 (49) hide show
  1. package/CHANGELOG.md +74 -0
  2. package/README.md +111 -16
  3. package/app/app.config.ts +2 -0
  4. package/app/assets/docs.css +79 -15
  5. package/app/components/DocsBanner.vue +1 -1
  6. package/app/components/DocsExample.vue +1 -1
  7. package/app/components/DocsHeader.vue +7 -6
  8. package/app/components/DocsIcon.vue +12 -0
  9. package/app/components/DocsLanding.vue +8 -5
  10. package/app/components/DocsLanguage.vue +18 -7
  11. package/app/components/DocsNavSheet.vue +37 -0
  12. package/app/components/DocsNavTree.vue +2 -2
  13. package/app/components/DocsPage.vue +16 -12
  14. package/app/components/DocsPageActions.vue +5 -5
  15. package/app/components/DocsPageEnd.ts +12 -0
  16. package/app/components/DocsPost.vue +11 -11
  17. package/app/components/DocsSearch.vue +7 -5
  18. package/app/components/DocsShell.vue +19 -3
  19. package/app/components/DocsSidebar.vue +2 -1
  20. package/app/components/DocsSidebarEnd.ts +4 -0
  21. package/app/components/DocsThemeToggle.vue +3 -3
  22. package/app/components/DocsToc.vue +2 -2
  23. package/app/composables/useDocs.ts +3 -2
  24. package/app/composables/useDocsBlog.ts +8 -7
  25. package/app/composables/useDocsFrame.ts +11 -0
  26. package/app/composables/useDocsSite.ts +24 -3
  27. package/app/docs-pages/blog.vue +12 -4
  28. package/app/docs-pages/changelog.vue +13 -5
  29. package/app/docs-pages/doc.vue +3 -1
  30. package/app/error.vue +16 -1
  31. package/app/layouts/default.vue +4 -0
  32. package/app/lib/docsPage.ts +40 -0
  33. package/app/lib/markdownComponents.ts +1 -2
  34. package/dist/config.d.mts +29 -8
  35. package/dist/config.d.ts +29 -8
  36. package/dist/config.mjs +17 -48
  37. package/dist/icons.d.mts +38 -0
  38. package/dist/icons.d.ts +38 -0
  39. package/dist/icons.mjs +175 -0
  40. package/dist/pages.mjs +56 -24
  41. package/i18n/de.json +1 -1
  42. package/i18n/en.json +1 -1
  43. package/i18n/fr.json +1 -0
  44. package/nuxt.config.ts +2 -17
  45. package/package.json +11 -10
  46. package/server/lib/blogFeed.ts +1 -1
  47. package/shared/layerRoutes.ts +66 -0
  48. package/dist/shared/ginko-docs.BwbF7JBR.mjs +0 -4
  49. package/server/lib/rss.ts +0 -57
package/CHANGELOG.md CHANGED
@@ -1,5 +1,79 @@
1
1
  # @lupinum/ginko-docs
2
2
 
3
+ ## 0.6.0
4
+
5
+ ### Patch Changes
6
+
7
+ - [#28](https://github.com/lupinum-dev/ginko/pull/28) [`3246028`](https://github.com/lupinum-dev/ginko/commit/3246028d0b767801950f6f54f49e7edddd204143) Thanks [@Mat4m0](https://github.com/Mat4m0)! - Fix the first release of a template or a docs home on the platform: every required field of a single now has a default (or is optional), so the platform can create its entry.
8
+ - Updated dependencies [[`ee8c8a9`](https://github.com/lupinum-dev/ginko/commit/ee8c8a982a64ee15a1cab906b7a3b58fe8c686e6)]:
9
+ - @lupinum/ginko@0.6.0
10
+ - @lupinum/ginko-components@0.6.0
11
+ - @lupinum/ginko-content@0.6.0
12
+ - @lupinum/ginko-i18n@0.6.0
13
+
14
+ ## 0.5.0
15
+
16
+ ### Minor Changes
17
+
18
+ - [#23](https://github.com/lupinum-dev/ginko/pull/23) [`7b165c2`](https://github.com/lupinum-dev/ginko/commit/7b165c25e17ed2f4fb50e8054febf618a6af10d7) Thanks [@Mat4m0](https://github.com/Mat4m0)! - Add public ways to customize the docs: `<DocsShell>` for a site's own pages in the docs frame, the override points `DocsPageEnd` and `DocsSidebarEnd`, and `docs: { search: false }`.
19
+
20
+ - [#23](https://github.com/lupinum-dev/ginko/pull/23) [`7b165c2`](https://github.com/lupinum-dev/ginko/commit/7b165c25e17ed2f4fb50e8054febf618a6af10d7) Thanks [@Mat4m0](https://github.com/Mat4m0)! - Add a docs frame for sites that embed the docs in their own layout: no framed panel, the search above the navigation, a phone bar with the navigation as a sheet, and `--docs-gutter` to line the docs up with the site's container.
21
+
22
+ - [#23](https://github.com/lupinum-dev/ginko/pull/23) [`7b165c2`](https://github.com/lupinum-dev/ginko/commit/7b165c25e17ed2f4fb50e8054febf618a6af10d7) Thanks [@Mat4m0](https://github.com/Mat4m0)! - Add `homePages({ locales })` to `@lupinum/ginko-docs/config`: the start page by locale, with the main locale at `/`.
23
+
24
+ - [#23](https://github.com/lupinum-dev/ginko/pull/23) [`7b165c2`](https://github.com/lupinum-dev/ginko/commit/7b165c25e17ed2f4fb50e8054febf618a6af10d7) Thanks [@Mat4m0](https://github.com/Mat4m0)! - Change the docs layer to draw icons itself instead of with @nuxt/icon: pages outside the docs no longer download an icon library (about 16 KB gzip less on every page of a site that embeds the docs), and docs pages load only the icons they use. Icon names work as before (any installed Iconify set). The build finds them in your Markdown front matter and component attributes (not in prose or code blocks), in YAML and JSON content, in `app.config.ts` and in your own `app/` components (`<Icon name="lucide:x" />`); it reads all locales and ignores commented-out code. A name built at runtime (a variable) is not found, so write it once as a literal; a name that is not installed logs one build warning with its file. A site's own `@nuxt/icon` still wins over the layer's `Icon`. Remove the `icon` options from your nuxt.config if you set any. The icon hints in the editor now say "needs a global Icon component".
25
+
26
+ - [#23](https://github.com/lupinum-dev/ginko/pull/23) [`7b165c2`](https://github.com/lupinum-dev/ginko/commit/7b165c25e17ed2f4fb50e8054febf618a6af10d7) Thanks [@Mat4m0](https://github.com/Mat4m0)! - Add route links outside Markdown: the start page's buttons and cards, `nav`, `footer.links` and `banner.href` take `$docs.<page>` and `$page.<key>`, which lead to the page in the reader's language.
27
+
28
+ - [#24](https://github.com/lupinum-dev/ginko/pull/24) [`c28d1e3`](https://github.com/lupinum-dev/ginko/commit/c28d1e3e4ba1e558f8c71d7b69961f7474a221d1) Thanks [@Mat4m0](https://github.com/Mat4m0)! - Add `source: 'platform'` to docsCollection, docsHome, blogCollections and changelogCollection, so docs content can be edited in Ginko Studio; the default stays `'files'`.
29
+
30
+ ### Patch Changes
31
+
32
+ - [#21](https://github.com/lupinum-dev/ginko/pull/21) [`80f93a4`](https://github.com/lupinum-dev/ginko/commit/80f93a4f6bd171e1146b929913254039becdd991) Thanks [@Mat4m0](https://github.com/Mat4m0)! - Add `breadcrumbJsonLd(steps, siteUrl)` to `@lupinum/ginko`: a JSON-LD BreadcrumbList from steps with `title` and `path`, such as the nodes of `useGinkoBreadcrumbs` (templates/_features/site/app/composables/usePageSeo.ts uses it).
33
+
34
+ - [#23](https://github.com/lupinum-dev/ginko/pull/23) [`7b165c2`](https://github.com/lupinum-dev/ginko/commit/7b165c25e17ed2f4fb50e8054febf618a6af10d7) Thanks [@Mat4m0](https://github.com/Mat4m0)! - Fix the 404 page's main-language link ignoring the site's i18n strategy, and name the language in the reader's language
35
+
36
+ - [#23](https://github.com/lupinum-dev/ginko/pull/23) [`7b165c2`](https://github.com/lupinum-dev/ginko/commit/7b165c25e17ed2f4fb50e8054febf618a6af10d7) Thanks [@Mat4m0](https://github.com/Mat4m0)! - Fix the focus ring of the default theme in light mode: emerald-700 instead of emerald-500, 5.5:1 against the page instead of 2.5:1.
37
+
38
+ - [#23](https://github.com/lupinum-dev/ginko/pull/23) [`7b165c2`](https://github.com/lupinum-dev/ginko/commit/7b165c25e17ed2f4fb50e8054febf618a6af10d7) Thanks [@Mat4m0](https://github.com/Mat4m0)! - Fix layout shift on first load of a docs page: the standalone layout preloads Inter, so text no longer re-wraps when the font arrives.
39
+
40
+ - [#23](https://github.com/lupinum-dev/ginko/pull/23) [`7b165c2`](https://github.com/lupinum-dev/ginko/commit/7b165c25e17ed2f4fb50e8054febf618a6af10d7) Thanks [@Mat4m0](https://github.com/Mat4m0)! - Add French texts to the docs layer.
41
+
42
+ - [#23](https://github.com/lupinum-dev/ginko/pull/23) [`7b165c2`](https://github.com/lupinum-dev/ginko/commit/7b165c25e17ed2f4fb50e8054febf618a6af10d7) Thanks [@Mat4m0](https://github.com/Mat4m0)! - Fix the "not translated" note in the language menu: screen readers read it in the page's language. An empty changelog now says where to add releases.
43
+
44
+ - [#23](https://github.com/lupinum-dev/ginko/pull/23) [`7b165c2`](https://github.com/lupinum-dev/ginko/commit/7b165c25e17ed2f4fb50e8054febf618a6af10d7) Thanks [@Mat4m0](https://github.com/Mat4m0)! - Fix English fallback text on translated docs pages being read in the page language, and add a link to the main-language page on a docs 404 under another language
45
+
46
+ - [#23](https://github.com/lupinum-dev/ginko/pull/23) [`7b165c2`](https://github.com/lupinum-dev/ginko/commit/7b165c25e17ed2f4fb50e8054febf618a6af10d7) Thanks [@Mat4m0](https://github.com/Mat4m0)! - Add CollectionPage JSON-LD and a description to the changelog page, and CollectionPage JSON-LD to the blog list.
47
+
48
+ - [#23](https://github.com/lupinum-dev/ginko/pull/23) [`7b165c2`](https://github.com/lupinum-dev/ginko/commit/7b165c25e17ed2f4fb50e8054febf618a6af10d7) Thanks [@Mat4m0](https://github.com/Mat4m0)! - Fix tables with three columns on phones narrower than 360 px: the last column keeps its width and the table scrolls, instead of showing a few letters per line.
49
+
50
+ - [#23](https://github.com/lupinum-dev/ginko/pull/23) [`7b165c2`](https://github.com/lupinum-dev/ginko/commit/7b165c25e17ed2f4fb50e8054febf618a6af10d7) Thanks [@Mat4m0](https://github.com/Mat4m0)! - Fix docs tables, the home feature list and the install box on phones.
51
+
52
+ - [#23](https://github.com/lupinum-dev/ginko/pull/23) [`7b165c2`](https://github.com/lupinum-dev/ginko/commit/7b165c25e17ed2f4fb50e8054febf618a6af10d7) Thanks [@Mat4m0](https://github.com/Mat4m0)! - Change the phone header and menu of the docs layer: the light/dark button stays in the header, and the menu lists the languages as links at its top, one tap each.
53
+
54
+ - [#21](https://github.com/lupinum-dev/ginko/pull/21) [`80f93a4`](https://github.com/lupinum-dev/ginko/commit/80f93a4f6bd171e1146b929913254039becdd991) Thanks [@Mat4m0](https://github.com/Mat4m0)! - Change the JSON-LD of a blog post from Article to BlogPosting, built by `postJsonLd` of `@lupinum/ginko/basics`, with the post's tags as keywords.
55
+
56
+ - [#23](https://github.com/lupinum-dev/ginko/pull/23) [`7b165c2`](https://github.com/lupinum-dev/ginko/commit/7b165c25e17ed2f4fb50e8054febf618a6af10d7) Thanks [@Mat4m0](https://github.com/Mat4m0)! - Fix route links outside Markdown (nav, footer, banner, start page): they lead to the main-language page when the current language has no such doc, instead of a raw `$docs…` address.
57
+
58
+ - [#23](https://github.com/lupinum-dev/ginko/pull/23) [`7b165c2`](https://github.com/lupinum-dev/ginko/commit/7b165c25e17ed2f4fb50e8054febf618a6af10d7) Thanks [@Mat4m0](https://github.com/Mat4m0)! - Fix the docs layer adding a second blog, blog feed or changelog at /blog and /changelog to a site that has its own: it now adds them only at the address of your posts and changelog and only when you have no page there.
59
+
60
+ A changelog page needs `changelog: changelogPages({ locales })` in `pages` (or a `route` on the changelog brick); without an address the build warns once and the layer adds no changelog. The layer now decides after all of your pages are known, so a page that the page-paths module moves to `/blog` also counts as yours, and its links to the blog and changelog follow the addresses you configured.
61
+
62
+ - [#23](https://github.com/lupinum-dev/ginko/pull/23) [`7b165c2`](https://github.com/lupinum-dev/ginko/commit/7b165c25e17ed2f4fb50e8054febf618a6af10d7) Thanks [@Mat4m0](https://github.com/Mat4m0)! - Fix short tables in the docs ending before the edge of their frame: the rows and the header now fill it.
63
+
64
+ - [#23](https://github.com/lupinum-dev/ginko/pull/23) [`7b165c2`](https://github.com/lupinum-dev/ginko/commit/7b165c25e17ed2f4fb50e8054febf618a6af10d7) Thanks [@Mat4m0](https://github.com/Mat4m0)! - Add a note to the language menu for a language that has no version of the page: "Not translated: opens the start page".
65
+
66
+ - [#23](https://github.com/lupinum-dev/ginko/pull/23) [`7b165c2`](https://github.com/lupinum-dev/ginko/commit/7b165c25e17ed2f4fb50e8054febf618a6af10d7) Thanks [@Mat4m0](https://github.com/Mat4m0)! - Fix wide tables, long titles and the previous and next links making docs pages scroll or squeeze text on phones
67
+
68
+ - [#21](https://github.com/lupinum-dev/ginko/pull/21) [`80f93a4`](https://github.com/lupinum-dev/ginko/commit/80f93a4f6bd171e1146b929913254039becdd991) Thanks [@Mat4m0](https://github.com/Mat4m0)! - Change Markdown refs in page state to carry no table of contents: read `toc` from the loaded text, `(await useGinkoMarkdown(() => entry.value.data.body)).value?.toc`.
69
+
70
+ - [#23](https://github.com/lupinum-dev/ginko/pull/23) [`7b165c2`](https://github.com/lupinum-dev/ginko/commit/7b165c25e17ed2f4fb50e8054febf618a6af10d7) Thanks [@Mat4m0](https://github.com/Mat4m0)! - Add `releaseAnchor(version)` to `@lupinum/ginko/basics`: the anchor of a release on a changelog page (`4.6.0` → `v4-6-0`). The changelog page of `@lupinum/ginko-docs` and the changelog feature of the templates (templates/_features/changelog/app/composables/useChangelog.ts, server/middleware/changelog-feed.ts) use it, so both keep the same public addresses.
71
+ - Updated dependencies [[`80f93a4`](https://github.com/lupinum-dev/ginko/commit/80f93a4f6bd171e1146b929913254039becdd991), [`80f93a4`](https://github.com/lupinum-dev/ginko/commit/80f93a4f6bd171e1146b929913254039becdd991), [`80f93a4`](https://github.com/lupinum-dev/ginko/commit/80f93a4f6bd171e1146b929913254039becdd991), [`80f93a4`](https://github.com/lupinum-dev/ginko/commit/80f93a4f6bd171e1146b929913254039becdd991), [`80f93a4`](https://github.com/lupinum-dev/ginko/commit/80f93a4f6bd171e1146b929913254039becdd991), [`80f93a4`](https://github.com/lupinum-dev/ginko/commit/80f93a4f6bd171e1146b929913254039becdd991), [`80f93a4`](https://github.com/lupinum-dev/ginko/commit/80f93a4f6bd171e1146b929913254039becdd991), [`7b165c2`](https://github.com/lupinum-dev/ginko/commit/7b165c25e17ed2f4fb50e8054febf618a6af10d7), [`7b165c2`](https://github.com/lupinum-dev/ginko/commit/7b165c25e17ed2f4fb50e8054febf618a6af10d7), [`7b165c2`](https://github.com/lupinum-dev/ginko/commit/7b165c25e17ed2f4fb50e8054febf618a6af10d7), [`7b165c2`](https://github.com/lupinum-dev/ginko/commit/7b165c25e17ed2f4fb50e8054febf618a6af10d7), [`7b165c2`](https://github.com/lupinum-dev/ginko/commit/7b165c25e17ed2f4fb50e8054febf618a6af10d7), [`7b165c2`](https://github.com/lupinum-dev/ginko/commit/7b165c25e17ed2f4fb50e8054febf618a6af10d7), [`7b165c2`](https://github.com/lupinum-dev/ginko/commit/7b165c25e17ed2f4fb50e8054febf618a6af10d7), [`7b165c2`](https://github.com/lupinum-dev/ginko/commit/7b165c25e17ed2f4fb50e8054febf618a6af10d7), [`80f93a4`](https://github.com/lupinum-dev/ginko/commit/80f93a4f6bd171e1146b929913254039becdd991), [`80f93a4`](https://github.com/lupinum-dev/ginko/commit/80f93a4f6bd171e1146b929913254039becdd991), [`80f93a4`](https://github.com/lupinum-dev/ginko/commit/80f93a4f6bd171e1146b929913254039becdd991), [`7b165c2`](https://github.com/lupinum-dev/ginko/commit/7b165c25e17ed2f4fb50e8054febf618a6af10d7), [`80f93a4`](https://github.com/lupinum-dev/ginko/commit/80f93a4f6bd171e1146b929913254039becdd991), [`80f93a4`](https://github.com/lupinum-dev/ginko/commit/80f93a4f6bd171e1146b929913254039becdd991), [`80f93a4`](https://github.com/lupinum-dev/ginko/commit/80f93a4f6bd171e1146b929913254039becdd991), [`80f93a4`](https://github.com/lupinum-dev/ginko/commit/80f93a4f6bd171e1146b929913254039becdd991), [`80f93a4`](https://github.com/lupinum-dev/ginko/commit/80f93a4f6bd171e1146b929913254039becdd991), [`80f93a4`](https://github.com/lupinum-dev/ginko/commit/80f93a4f6bd171e1146b929913254039becdd991), [`80f93a4`](https://github.com/lupinum-dev/ginko/commit/80f93a4f6bd171e1146b929913254039becdd991), [`80f93a4`](https://github.com/lupinum-dev/ginko/commit/80f93a4f6bd171e1146b929913254039becdd991), [`80f93a4`](https://github.com/lupinum-dev/ginko/commit/80f93a4f6bd171e1146b929913254039becdd991), [`80f93a4`](https://github.com/lupinum-dev/ginko/commit/80f93a4f6bd171e1146b929913254039becdd991), [`80f93a4`](https://github.com/lupinum-dev/ginko/commit/80f93a4f6bd171e1146b929913254039becdd991), [`7b165c2`](https://github.com/lupinum-dev/ginko/commit/7b165c25e17ed2f4fb50e8054febf618a6af10d7), [`7b165c2`](https://github.com/lupinum-dev/ginko/commit/7b165c25e17ed2f4fb50e8054febf618a6af10d7), [`80f93a4`](https://github.com/lupinum-dev/ginko/commit/80f93a4f6bd171e1146b929913254039becdd991), [`80f93a4`](https://github.com/lupinum-dev/ginko/commit/80f93a4f6bd171e1146b929913254039becdd991), [`80f93a4`](https://github.com/lupinum-dev/ginko/commit/80f93a4f6bd171e1146b929913254039becdd991), [`7b165c2`](https://github.com/lupinum-dev/ginko/commit/7b165c25e17ed2f4fb50e8054febf618a6af10d7), [`7b165c2`](https://github.com/lupinum-dev/ginko/commit/7b165c25e17ed2f4fb50e8054febf618a6af10d7), [`80f93a4`](https://github.com/lupinum-dev/ginko/commit/80f93a4f6bd171e1146b929913254039becdd991), [`80f93a4`](https://github.com/lupinum-dev/ginko/commit/80f93a4f6bd171e1146b929913254039becdd991), [`80f93a4`](https://github.com/lupinum-dev/ginko/commit/80f93a4f6bd171e1146b929913254039becdd991), [`80f93a4`](https://github.com/lupinum-dev/ginko/commit/80f93a4f6bd171e1146b929913254039becdd991), [`80f93a4`](https://github.com/lupinum-dev/ginko/commit/80f93a4f6bd171e1146b929913254039becdd991), [`80f93a4`](https://github.com/lupinum-dev/ginko/commit/80f93a4f6bd171e1146b929913254039becdd991), [`80f93a4`](https://github.com/lupinum-dev/ginko/commit/80f93a4f6bd171e1146b929913254039becdd991), [`80f93a4`](https://github.com/lupinum-dev/ginko/commit/80f93a4f6bd171e1146b929913254039becdd991), [`80f93a4`](https://github.com/lupinum-dev/ginko/commit/80f93a4f6bd171e1146b929913254039becdd991), [`80f93a4`](https://github.com/lupinum-dev/ginko/commit/80f93a4f6bd171e1146b929913254039becdd991), [`80f93a4`](https://github.com/lupinum-dev/ginko/commit/80f93a4f6bd171e1146b929913254039becdd991), [`80f93a4`](https://github.com/lupinum-dev/ginko/commit/80f93a4f6bd171e1146b929913254039becdd991), [`80f93a4`](https://github.com/lupinum-dev/ginko/commit/80f93a4f6bd171e1146b929913254039becdd991), [`80f93a4`](https://github.com/lupinum-dev/ginko/commit/80f93a4f6bd171e1146b929913254039becdd991), [`7b165c2`](https://github.com/lupinum-dev/ginko/commit/7b165c25e17ed2f4fb50e8054febf618a6af10d7), [`7b165c2`](https://github.com/lupinum-dev/ginko/commit/7b165c25e17ed2f4fb50e8054febf618a6af10d7), [`80f93a4`](https://github.com/lupinum-dev/ginko/commit/80f93a4f6bd171e1146b929913254039becdd991), [`80f93a4`](https://github.com/lupinum-dev/ginko/commit/80f93a4f6bd171e1146b929913254039becdd991), [`80f93a4`](https://github.com/lupinum-dev/ginko/commit/80f93a4f6bd171e1146b929913254039becdd991), [`80f93a4`](https://github.com/lupinum-dev/ginko/commit/80f93a4f6bd171e1146b929913254039becdd991), [`80f93a4`](https://github.com/lupinum-dev/ginko/commit/80f93a4f6bd171e1146b929913254039becdd991), [`80f93a4`](https://github.com/lupinum-dev/ginko/commit/80f93a4f6bd171e1146b929913254039becdd991), [`80f93a4`](https://github.com/lupinum-dev/ginko/commit/80f93a4f6bd171e1146b929913254039becdd991), [`80f93a4`](https://github.com/lupinum-dev/ginko/commit/80f93a4f6bd171e1146b929913254039becdd991), [`c28d1e3`](https://github.com/lupinum-dev/ginko/commit/c28d1e3e4ba1e558f8c71d7b69961f7474a221d1), [`80f93a4`](https://github.com/lupinum-dev/ginko/commit/80f93a4f6bd171e1146b929913254039becdd991), [`80f93a4`](https://github.com/lupinum-dev/ginko/commit/80f93a4f6bd171e1146b929913254039becdd991), [`7b165c2`](https://github.com/lupinum-dev/ginko/commit/7b165c25e17ed2f4fb50e8054febf618a6af10d7), [`80f93a4`](https://github.com/lupinum-dev/ginko/commit/80f93a4f6bd171e1146b929913254039becdd991), [`80f93a4`](https://github.com/lupinum-dev/ginko/commit/80f93a4f6bd171e1146b929913254039becdd991), [`7b165c2`](https://github.com/lupinum-dev/ginko/commit/7b165c25e17ed2f4fb50e8054febf618a6af10d7), [`80f93a4`](https://github.com/lupinum-dev/ginko/commit/80f93a4f6bd171e1146b929913254039becdd991), [`80f93a4`](https://github.com/lupinum-dev/ginko/commit/80f93a4f6bd171e1146b929913254039becdd991), [`80f93a4`](https://github.com/lupinum-dev/ginko/commit/80f93a4f6bd171e1146b929913254039becdd991), [`80f93a4`](https://github.com/lupinum-dev/ginko/commit/80f93a4f6bd171e1146b929913254039becdd991), [`80f93a4`](https://github.com/lupinum-dev/ginko/commit/80f93a4f6bd171e1146b929913254039becdd991), [`80f93a4`](https://github.com/lupinum-dev/ginko/commit/80f93a4f6bd171e1146b929913254039becdd991), [`80f93a4`](https://github.com/lupinum-dev/ginko/commit/80f93a4f6bd171e1146b929913254039becdd991), [`80f93a4`](https://github.com/lupinum-dev/ginko/commit/80f93a4f6bd171e1146b929913254039becdd991), [`80f93a4`](https://github.com/lupinum-dev/ginko/commit/80f93a4f6bd171e1146b929913254039becdd991), [`80f93a4`](https://github.com/lupinum-dev/ginko/commit/80f93a4f6bd171e1146b929913254039becdd991), [`80f93a4`](https://github.com/lupinum-dev/ginko/commit/80f93a4f6bd171e1146b929913254039becdd991), [`80f93a4`](https://github.com/lupinum-dev/ginko/commit/80f93a4f6bd171e1146b929913254039becdd991), [`80f93a4`](https://github.com/lupinum-dev/ginko/commit/80f93a4f6bd171e1146b929913254039becdd991), [`80f93a4`](https://github.com/lupinum-dev/ginko/commit/80f93a4f6bd171e1146b929913254039becdd991), [`80f93a4`](https://github.com/lupinum-dev/ginko/commit/80f93a4f6bd171e1146b929913254039becdd991)]:
72
+ - @lupinum/ginko@0.5.0
73
+ - @lupinum/ginko-components@0.5.0
74
+ - @lupinum/ginko-content@0.5.0
75
+ - @lupinum/ginko-i18n@0.5.0
76
+
3
77
  ## 0.4.0
4
78
 
5
79
  ### Minor Changes
package/README.md CHANGED
@@ -15,7 +15,7 @@ npm install @lupinum/ginko @lupinum/ginko-content @lupinum/ginko-i18n @lupinum/g
15
15
  ```ts
16
16
  // ginko.config.ts
17
17
  import { defineSite } from '@lupinum/ginko'
18
- import { components, docsCollection } from '@lupinum/ginko-docs/config'
18
+ import { components, docsCollection, homePages } from '@lupinum/ginko-docs/config'
19
19
 
20
20
  const locales = ['en', 'de'] as const
21
21
 
@@ -26,15 +26,22 @@ export default defineSite({
26
26
  timeZone: 'Europe/Vienna',
27
27
  locales,
28
28
  currency: 'EUR',
29
- pages: { home: { en: '/', de: '/de' } }, // the start page is the page `home`
29
+ pages: { home: homePages({ locales }) }, // the start page is the page `home`
30
30
  components, // the Markdown components: ::callout, ::tabs, ::steps …
31
31
  bricks: { docs: docsCollection({ locales }) },
32
32
  })
33
33
  ```
34
34
 
35
- `docsCollection`, `blogCollections`, `blogPages` and `changelogPages` take one options object. `locales` is the same list as
35
+ `docsCollection`, `blogCollections`, `homePages`, `blogPages` and `changelogPages` take one options object. `locales` is the same list as
36
36
  in `defineSite` (the layer accepts any `readonly string[]`; `defineSite` itself needs the tuple, hence `as const`).
37
37
 
38
+ The bricks read Markdown files from `content/`. To edit them in Ginko Studio instead, pass `source: 'platform'` to
39
+ `docsCollection`, `docsHome`, `blogCollections` or `changelogCollection` (each brick on its own; the default is
40
+ `'files'`). The pages, their Markdown twins and `read_doc` work the same with either source, and a page from the
41
+ platform links "Edit this page" to its form in the Studio. Two things are built from files only: the search index
42
+ (the search box and `search_docs`) finds no page that lives on the platform, and an icon named only in Studio content
43
+ is not bundled (name it once in a file the icon scan reads, see "Icons").
44
+
38
45
  `components` is the registry of `@lupinum/ginko-components` (a dependency of the layer, re-exported here). With it, Ginko checks every component tag and prop in the
39
46
  pages; a site can add its own components next to it (see "Inside an existing site").
40
47
 
@@ -56,7 +63,10 @@ export default defineNuxtConfig({ extends: ['@lupinum/ginko-docs'], modules: ['@
56
63
  ## Configure
57
64
 
58
65
  Options go in the site's `app.config.ts`. All are optional. Internal paths in `nav`, `footer.links` and `banner.href`
59
- follow the reader's language (`/guide` becomes `/de/guide`); absolute URLs stay as written.
66
+ follow the reader's language (`/guide` becomes `/de/guide`); absolute URLs stay as written. A route link names the page
67
+ instead of its address, as in Markdown: `$docs.<page>` (a docs page by its file name, such as `$docs.webhooks`, or its
68
+ slug) and `$page.<key>` (a page of `pages` in `ginko.config.ts`, such as `$page.changelog`), with an optional `#anchor`.
69
+ It leads to the page in the reader's language, also when a translation has another slug.
60
70
 
61
71
  ```ts
62
72
  export default defineAppConfig({
@@ -108,8 +118,26 @@ Each docs page's frontmatter can set, besides `title` and `description`:
108
118
  | `icon` | An Iconify name, e.g. `lucide:rocket`, before the title in the navigation. Main locale only. |
109
119
  | `badge` | A short label after the title, e.g. "New". |
110
120
 
111
- Icons come from `@nuxt/icon` with the `lucide` set installed. Any other Iconify set works once the site installs it
112
- (`pnpm add -D @iconify-json/<set>`). Icons are bundled at build time; no icon API is called.
121
+ ### Icons
122
+
123
+ The layer draws icons itself (no `@nuxt/icon`, no icon API call, no icon library in the browser). At build time it reads
124
+ exactly the icons your site names from the installed `@iconify-json/<set>` packages (`lucide` is installed with the
125
+ layer; add others with `pnpm add -D @iconify-json/<set>`) and bundles them in one small map that only docs pages load.
126
+
127
+ The build scans, in all locales:
128
+
129
+ - the front matter of your Markdown files, the attributes of Markdown components (`::card{icon="lucide:rocket"}`) and the
130
+ `---` block inside a component; prose and code blocks are not read;
131
+ - YAML and JSON content files and `app.config.ts` (`icon:` values and `social`);
132
+ - the layer's own components and your site's `app/**/*.vue` files (`<Icon name="lucide:x" />`, `icon="..."`, or a bound
133
+ literal `:name="'lucide:x'"`). Commented-out code is ignored; other `.ts` files than `app.config.ts` are not read.
134
+
135
+ A name is `set:name`; a bare name such as `book-open` means Lucide. The `i-set-name` form is not supported. Names built at
136
+ runtime (a variable, a template string) are not found; write the name once as a literal somewhere in the places above. A
137
+ name the build cannot find logs one warning with its file and renders nothing.
138
+
139
+ The layer registers a global `Icon` component that the Markdown components (card, tabs, callout) use. If your site installs
140
+ `@nuxt/icon` itself, yours wins (the layer's has a lower priority).
113
141
 
114
142
  ### Start page (optional)
115
143
 
@@ -129,6 +157,9 @@ features:
129
157
  ---
130
158
  ```
131
159
 
160
+ `href` takes a path as written, an absolute URL, or a route link (`$docs.introduction`, `$page.changelog`) that follows
161
+ the reader's language, so the translations (`home.<locale>.md`) can use the same links.
162
+
132
163
  Add `home: docsHome()` to `bricks`. Without it, `/` leads to the first docs page. A site's own `pages/index.vue` wins.
133
164
 
134
165
  ### Look and colours
@@ -170,17 +201,24 @@ bricks: { docs: docsCollection({ locales, base: '/handbook' }), /* … */ },
170
201
  components: { ...components, /* the site's own components; same name wins */ },
171
202
  ```
172
203
 
173
- - **Only the docs pages are added.** The layer registers its pages from the bricks: the docs at the `base` of
174
- `docsCollection` (default `/docs`), `/blog` (with its RSS feed) and `/changelog` only when those bricks exist, `/` only when the site
175
- has no start page of its own. A page the site defines itself always wins.
204
+ - **Only the docs pages are added.** The layer registers its pages from the bricks, after all pages of the site are
205
+ known: the docs at the `base` of `docsCollection` (default `/docs`), the blog (with its RSS feed) at the route of
206
+ the `posts` brick and the changelog at the route of the `changelog` brick (or `pages.changelog`), and `/` only when
207
+ the site has no start page of its own. A page the site defines itself at one of these addresses always wins, also
208
+ when the page-paths module gives it that address (a page file `news.vue` that answers at `/blog`). Then the layer
209
+ adds no page and no feed there, and its header and post links point to the site's page.
176
210
  - **The site's frame stays.** The header, footer and page background are the layer's `layouts/default.vue`. When
177
211
  the site has its own `layouts/default.vue`, that one is used and the docs (navigation, page, table of contents)
178
- sit inside it. On phones the navigation opens from a bar above the page, so the docs need no header of their own.
212
+ sit inside it, without the layer's framed panel, on the site's background. The search sits above the navigation.
213
+ On phones a bar under the site's header holds a "Contents" button (the navigation as a sheet), the search and the
214
+ "On this page" menu, so the docs need no header of their own.
179
215
  - **Nothing leaks.** Every rule is under a `docs-` class; the page background, font and `color-scheme` are only set
180
216
  by the layer's own frame. The search, the language menu and the light/dark switch are components
181
217
  (`<DocsSearch />`, `<DocsLanguage />`, `<DocsThemeToggle />`) the site can put into its own header; the switch sets
182
218
  `data-theme` on `<html>`, so it fits a site that themes with that attribute. `<DocsThemeToggle segmented />` is the
183
- three-way Auto/Light/Dark switch (the phone menu uses it). These three are the public components. The other `Docs*`
219
+ three-way Auto/Light/Dark switch and `<DocsLanguage list />` the languages as a row of links (the phone menu uses
220
+ both). A language without a version of the page says so ("Not translated: opens the start page"). These three,
221
+ `<DocsShell>` and the override points under [Customize](#customize) are the public components. The other `Docs*`
184
222
  components, the `useDocs*` composables and the files in `app/lib` are internal and can change in any release.
185
223
  - **Error page:** the layer's `error.vue` shows a not-found page inside the site's layout, with links home and to
186
224
  the docs. A site with its own `error.vue` keeps it.
@@ -190,10 +228,11 @@ components: { ...components, /* the site's own components; same name wins */ },
190
228
  - **Title:** pages get the title `Page · Site name` from Ginko (`seo.titleTemplate` in `defineSite`). A
191
229
  `titleTemplate` in `app.head` or from nuxt-seo-utils does not apply to them, so the name is never added twice.
192
230
  - **Styling:** map the site's design tokens to the token names above (leave out the ones the site already defines
193
- under the same name, often `--font-sans` and `--font-mono`). Two layout tokens line the docs up with the site:
194
- - `--docs-max`: the width of the site's content container, so the sidebar starts where the header's logo does.
195
- - `--docs-top`: the space the sidebar and table of contents keep from the top of the window when they stick, and
196
- the scroll offset of headings. With a sticky header, its height; with a header that scrolls away, a small gap.
231
+ under the same name, often `--font-sans` and `--font-mono`). Three layout tokens line the docs up with the site:
232
+ - `--docs-max` and `--docs-gutter`: the width and the side padding of the site's content container, so the search
233
+ and the navigation start where the header's logo does.
234
+ - `--docs-top`: the space the sidebar, the phone bar and the table of contents keep from the top of the window when
235
+ they stick, and the scroll offset of headings. With a sticky header, its height; with one that scrolls away, 0.
197
236
 
198
237
  ```css
199
238
  :root {
@@ -205,10 +244,65 @@ components: { ...components, /* the site's own components; same name wins */ },
205
244
  --primary: var(--color-brand);
206
245
  --radius: 0.75rem;
207
246
  --docs-max: 82.5rem;
208
- --docs-top: 4.5rem; /* sticky header 4.5rem high; 1.5rem for one that scrolls away */
247
+ --docs-gutter: 1.5rem;
248
+ --docs-top: 4.5rem; /* sticky header 4.5rem high; 0 for one that scrolls away */
209
249
  }
210
250
  ```
211
251
 
252
+ Headings use the site's own heading styles; the layer sets no font on them.
253
+
254
+ ## Customize
255
+
256
+ Everything here is public and stays stable between releases.
257
+
258
+ **Tokens.** The colour tokens are listed under [Look and colours](#look-and-colours). The layout tokens:
259
+
260
+ | Token | Default | What it sets |
261
+ | --- | --- | --- |
262
+ | `--docs-max` | `90rem` | Width of the docs, gutters included |
263
+ | `--docs-gutter` | `1rem` | Space between the window edge and the docs |
264
+ | `--docs-top` | `0px` (standalone: the header) | Height of what sticks above the docs |
265
+ | `--docs-sidebar` | `17rem` | Width of the navigation |
266
+ | `--docs-toc` | `13.5rem` | Width of "On this page" beside the text (from 80rem window width) |
267
+ | `--docs-measure` | `38rem` | Reading width of running text |
268
+ | `--docs-h1` | `clamp(1.75rem, …, 2.25rem)` | Size of page titles |
269
+ | `--docs-header` | `4rem` | Height of the standalone header |
270
+
271
+ **Override points.** The layer ships empty components that a site replaces with its own file of the same name in
272
+ `app/components/`:
273
+
274
+ - `DocsPageEnd` (prop `doc`, the page's entry): after the text of every page, before "Edit this page" and the pager.
275
+ For a support box or a call to action.
276
+ - `DocsSidebarEnd`: below the navigation, in the sidebar and in the phone menu. For a few links or a contact line.
277
+
278
+ **Own pages in the docs frame.** `<DocsShell>` is the docs body (navigation, search, the phone bar) with a default slot
279
+ for the page and a `bar` slot for controls in the phone bar. A help center's start page, for example:
280
+
281
+ ```vue
282
+ <!-- app/pages/help/index.vue; the docs live at /help (docsCollection({ locales, base: '/help' })) -->
283
+ <script setup lang="ts">
284
+ const docs = useGinko('docs')
285
+ </script>
286
+
287
+ <template>
288
+ <DocsShell>
289
+ <h1>How can we help?</h1>
290
+ <ul>
291
+ <li v-for="item in docs.items" :key="item.id"><NuxtLink :to="item.url">{{ item.data.title }}</NuxtLink></li>
292
+ </ul>
293
+ </DocsShell>
294
+ </template>
295
+ ```
296
+
297
+ The layer then adds no page of its own at that address.
298
+
299
+ **Search.** `docs: { search: false }` in app.config leaves out the built-in search (header, sidebar and phone bar), for
300
+ a site that puts `<DocsSearch />` into its own header.
301
+
302
+ **Texts.** Every text of the layer is a `docs.*` message, and the site's own i18n wins per key. A help center, for
303
+ example, words the search for itself in `i18n/en.json`: `{ "docs": { "searchPlaceholder": "Search help…" } }`. The
304
+ layer brings English, German and French.
305
+
212
306
  ## Blog and changelog (optional)
213
307
 
214
308
  A site opts in by adding the collections to `ginko.config.ts`. The layer then adds the pages and the header links;
@@ -229,6 +323,7 @@ export default defineSite({
229
323
  - **Authors** (`content/authors/<name>[.<locale>].yml`) have `name`, `role`, `avatar` and `url`. A post names them by id: `authors: [authors/jane]`. Authors have no page.
230
324
  - A post page writes Article JSON-LD with the author names, `datePublished` from `date` and `dateModified` from the entry's `updatedAt`.
231
325
  - **RSS:** `/blog/rss.xml` and `/<l>/blog/rss.xml`, RSS 2.0 with title and description (not the full text) and absolute links. It reads the build-time file bundle, so it works for file blogs only. `nuxt generate` writes it: the blog page sends the feed path in the `x-nitro-prerender` header, because Nitro's crawler does not follow `.xml` links.
326
+ - **Addresses:** the blog list is the parent of the `posts` route (`/blog/[slug]` gives `/blog`; `blogCollections` uses `/blog`). The changelog page is at the route of the `changelog` brick, else at `pages.changelog` (`changelogPages({ locales })`, `/changelog`). A changelog brick with neither gets no page, and the build warns once. A site that has its own page at an address keeps it; the layer then adds nothing there.
232
327
  - **Changelog:** `changelogCollection()` (options: `max`) is one collection with `version`, `date` and Markdown text per release (`content/changelog/v1-2-0.md`; no dots in the file name, a dot reads as a language). The page `/changelog` shows all releases newest first, each with an anchor (`#v1-2-0`). It is a collection, not a docs page, so the date orders it and it has its own link in the header.
233
328
 
234
329
  The Ginko docs themselves are built this way: [sites/docs](https://github.com/lupinum-dev/ginko/tree/main/sites/docs).
package/app/app.config.ts CHANGED
@@ -32,6 +32,8 @@ export interface DocsConfig {
32
32
  }
33
33
  /** "Copy page" (default true) and the chats the page menu can open it in (default both, `[]` for none). */
34
34
  markdownActions?: { copy?: boolean, openIn?: DocsAssistant[] }
35
+ /** `false` leaves out the built-in search (header, sidebar, phone bar), for a site with <DocsSearch /> in its own header. */
36
+ search?: boolean
35
37
  /** Footer. Link texts: messages `docs.footer.<id>`. `attribution` (default true) adds "Built with Ginko". */
36
38
  footer?: { links?: { id: string, href: string }[], attribution?: boolean }
37
39
  }
@@ -26,7 +26,7 @@
26
26
  --input: light-dark(#d4d4d8, #52525b);
27
27
  --primary: light-dark(#047857, #34d399);
28
28
  --primary-foreground: light-dark(#ffffff, #09090b);
29
- --ring: light-dark(#10b981, #34d399);
29
+ --ring: light-dark(#047857, #34d399);
30
30
  --success: light-dark(#157a39, #4ade80);
31
31
  --warning: light-dark(#985807, #fbbf24);
32
32
  --info: light-dark(#1d4ed8, #60a5fa);
@@ -43,6 +43,8 @@
43
43
  --docs-sidebar: 17rem;
44
44
  --docs-toc: 13.5rem;
45
45
  --docs-max: 90rem;
46
+ /* Space between the window edge and the docs: the site's container padding when the docs sit in its layout. */
47
+ --docs-gutter: 1rem;
46
48
  /* One page-title size for docs, blog, changelog and 404. */
47
49
  --docs-h1: clamp(1.75rem, 1.4rem + 1vw, 2.25rem);
48
50
  /* Height of the phone bar above the page; 0 where the bar is hidden. Anchors scroll below both. */
@@ -195,7 +197,7 @@
195
197
  /* ---------- Docs shell: sidebar, phone bar, content ---------- */
196
198
 
197
199
  /* The navigation sits on the page background; the page is a framed panel. */
198
- .docs-shell { display: grid; grid-template-columns: var(--docs-sidebar) minmax(0, 1fr); gap: 0.75rem; max-width: var(--docs-max); margin: 0 auto; padding: 0 1rem 1rem; }
200
+ .docs-shell { display: grid; grid-template-columns: var(--docs-sidebar) minmax(0, 1fr); gap: 0.75rem; box-sizing: border-box; max-width: var(--docs-max); margin: 0 auto; padding: 0 var(--docs-gutter) 1rem; }
199
201
  .docs-sidebar { position: sticky; top: var(--docs-top); align-self: start; max-height: calc(100dvh - var(--docs-top)); overflow-y: auto; overscroll-behavior: contain; padding: 1rem 0.5rem 3rem 0.25rem; scrollbar-width: none; }
200
202
  .docs-content { min-width: 0; padding: 2rem 2.25rem 4.5rem 3.5rem; border: 1px solid var(--border); border-radius: var(--docs-r-panel); background: var(--card); }
201
203
  .docs-bar { display: none; }
@@ -239,8 +241,11 @@
239
241
  .docs-menu-links a:hover { background: color-mix(in oklab, var(--foreground) 9%, var(--muted)); }
240
242
  .docs-menu-links a[aria-current] { font-weight: 700; }
241
243
  .docs-menu-foot { display: flex; flex: none; align-items: center; justify-content: space-between; gap: 0.75rem; margin-top: auto; padding: 0.75rem 1rem 1.75rem; border-top: 1px solid var(--border); }
242
- .docs-menu-foot .docs-lang > button { height: 2.25rem; background: var(--muted); color: var(--foreground); }
243
- .docs-menu-foot .docs-lang .docs-popover { top: auto; right: auto; bottom: calc(100% + 0.375rem); left: 0; }
244
+ /* The languages at the top of the phone menu: one link each, named in its own language. */
245
+ .docs-lang-list { display: flex; flex: none; flex-wrap: wrap; gap: 0.5rem; padding: 0.75rem 1rem; border-bottom: 1px solid var(--border); }
246
+ .docs-lang-list a { display: inline-flex; flex-direction: column; justify-content: center; min-height: 2.75rem; padding: 0.25rem 0.875rem; border-radius: var(--docs-r-control); background: var(--muted); color: var(--foreground); font-size: 0.9375rem; font-weight: 500; line-height: 1.25; text-decoration: none; }
247
+ .docs-lang-list a[aria-current] { box-shadow: inset 0 0 0 1px var(--foreground); font-weight: 700; }
248
+ .docs-lang-list small { display: block; color: var(--muted-foreground); font-size: 0.75rem; font-weight: 400; }
244
249
  .docs-segmented { display: inline-flex; gap: 4px; padding: 3px; border-radius: calc(var(--docs-r-control) + 2px); background: var(--muted); }
245
250
  .docs-segmented button { height: 1.875rem; padding: 0 0.75rem; border: 0; border-radius: var(--docs-r-control); background: none; color: var(--muted-foreground); font: 500 0.8125rem/1 var(--font-sans); cursor: pointer; }
246
251
  .docs-segmented button[aria-pressed='true'] { background: light-dark(var(--card), var(--muted)); color: var(--foreground); box-shadow: 0 1px 2px rgb(0 0 0 / 0.08); }
@@ -272,26 +277,30 @@
272
277
  .docs-page-foot { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 0.5rem 1.5rem; margin-top: 3.5rem; color: var(--muted-foreground); font-size: 0.8125rem; }
273
278
  .docs-page-foot a { display: inline-flex; align-items: center; gap: 0.375rem; color: var(--muted-foreground); text-decoration: none; }
274
279
  .docs-page-foot a:hover { color: var(--foreground); }
275
- .docs-pager { display: grid; grid-template-columns: 1fr 1fr; gap: 0.75rem; margin-top: 1rem; }
276
- .docs-pager a { display: flex; flex-direction: column; gap: 0.1875rem; padding: 0.875rem 1rem 0.9375rem; border: 1px solid var(--border); border-radius: var(--docs-r-box); color: var(--foreground); font-size: 0.9375rem; font-weight: 600; text-decoration: none; transition: border-color 0.15s, background-color 0.15s; }
280
+ .docs-pager { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0.75rem; margin-top: 1rem; }
281
+ .docs-pager a { display: flex; flex-direction: column; gap: 0.1875rem; padding: 0.875rem 1rem 0.9375rem; border: 1px solid var(--border); border-radius: var(--docs-r-box); color: var(--foreground); font-size: 0.9375rem; font-weight: 600; text-decoration: none; overflow-wrap: anywhere; hyphens: auto; transition: border-color 0.15s, background-color 0.15s; }
277
282
  .docs-pager a:hover { background: color-mix(in oklab, var(--muted) 60%, transparent); }
278
283
  .docs-pager small { display: inline-flex; align-items: center; gap: 0.375rem; color: var(--muted-foreground); font-size: 0.78125rem; font-weight: 500; }
279
284
  .docs-pager .is-next { grid-column: 2; align-items: flex-end; text-align: right; }
280
285
 
286
+ /* Headings outside .docs-prose have no wrap rule of their own. `anywhere` (not break-word) because the blog head is a flex container and needs the smaller min-content. */
287
+ :where(.docs-page-head, .docs-hero, .docs-blog, .docs-error) h1, .docs-posts h2, .docs-prose :is(h1, h2, h3, h4) { hyphens: auto; overflow-wrap: anywhere; }
288
+
281
289
  /* Table of contents: a rail on wide screens, a menu in the phone bar */
282
290
  .docs-toc { position: sticky; top: calc(var(--docs-top) + 2rem); align-self: start; max-height: calc(100dvh - var(--docs-top) - 4rem); overflow-y: auto; scrollbar-width: none; font-size: 0.8125rem; }
283
291
  .docs-toc-title { display: flex; align-items: center; gap: 0.5rem; margin: 0 0 0.625rem; color: var(--foreground); font-size: 0.78125rem; font-weight: 600; }
284
292
  .docs-toc-title .docs-icon { color: var(--muted-foreground); font-size: 0.875rem; }
285
293
  .docs-toc-list { margin: 0; padding: 0; list-style: none; border-left: 1px solid var(--border); }
286
- .docs-toc-list a { display: block; margin-left: -1px; padding: 0.3125rem 0 0.3125rem 0.875rem; border-left: 2px solid transparent; color: var(--muted-foreground); line-height: 1.4; text-decoration: none; transition: color 0.15s, border-color 0.15s; }
294
+ .docs-toc-list a { display: block; margin-left: -1px; padding: 0.3125rem 0 0.3125rem 0.875rem; border-left: 2px solid transparent; color: var(--muted-foreground); line-height: 1.4; text-decoration: none; overflow-wrap: break-word; hyphens: auto; transition: color 0.15s, border-color 0.15s; }
287
295
  .docs-toc-list a.is-sub { padding-left: 1.625rem; }
288
296
  .docs-toc-list a:hover { color: var(--foreground); }
289
297
  .docs-toc-list a[aria-current] { border-left-color: var(--primary); color: var(--foreground); font-weight: 500; }
290
298
  .docs-toc-more { display: flex; flex-direction: column; gap: 0.125rem; margin-top: 1.125rem; padding-top: 0.875rem; border-top: 1px solid var(--border); }
291
299
  .docs-toc-more a, .docs-toc-more button { display: inline-flex; align-items: center; gap: 0.5rem; padding: 0.25rem 0; border: 0; background: none; color: var(--muted-foreground); font: inherit; text-decoration: none; cursor: pointer; }
292
300
  .docs-toc-more a:hover, .docs-toc-more button:hover { color: var(--foreground); }
293
- .docs-toc-menu { position: relative; }
294
- .docs-toc-menu > button { display: inline-flex; align-items: center; gap: 0.375rem; height: 2rem; padding: 0 0.5rem; border: 0; border-radius: calc(var(--radius) - 2px); background: none; color: var(--muted-foreground); font: 500 0.8125rem/1 var(--font-sans); cursor: pointer; }
301
+ .docs-toc-menu { position: relative; min-width: 0; max-width: 100%; }
302
+ .docs-toc-menu > button > span { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
303
+ .docs-toc-menu > button { display: inline-flex; align-items: center; gap: 0.375rem; height: 2rem; padding: 0 0.5rem; border: 0; border-radius: calc(var(--radius) - 2px); background: none; color: var(--muted-foreground); font: 500 0.8125rem/1 var(--font-sans); cursor: pointer; max-width: 100%;}
295
304
  .docs-toc-menu > button:hover { color: var(--foreground); }
296
305
  .docs-toc-menu > button[aria-expanded='true'] .docs-icon { transform: rotate(180deg); }
297
306
  .docs-toc-menu .docs-popover { right: 0; top: calc(100% + 0.25rem); width: min(20rem, calc(100vw - 2rem)); max-height: 60dvh; overflow-y: auto; padding: 0.75rem 0.75rem 0.75rem 0.5rem; }
@@ -328,12 +337,26 @@
328
337
  .docs-prose hr { margin: 2.5rem 0; border: 0; border-top: 1px solid var(--border); }
329
338
  .docs-prose blockquote { padding-left: 1rem; border-left: 2px solid var(--border); color: var(--muted-foreground); }
330
339
  .docs-prose img { max-width: 100%; height: auto; border-radius: var(--radius); }
331
- .docs-prose .docs-table { width: 100%; max-width: 100%; overflow-x: auto; border: 1px solid var(--border); border-radius: var(--docs-r-box); }
332
- .docs-prose table { border-collapse: separate; border-spacing: 0; margin: 0; font-size: 0.875rem; line-height: 1.5; font-variant-numeric: tabular-nums; }
340
+ /* The edge shadows are the scroll cue: the "local" covers hide them at the ends, so a shadow shows only on a side with more to scroll to. */
341
+ .docs-prose .docs-table {
342
+ width: 100%; max-width: 100%; overflow-x: auto; border: 1px solid var(--border); border-radius: var(--docs-r-box);
343
+ background:
344
+ linear-gradient(to right, var(--card), var(--card)) left center / 1.5rem 100% no-repeat local,
345
+ linear-gradient(to left, var(--card), var(--card)) right center / 1.5rem 100% no-repeat local,
346
+ linear-gradient(to right, color-mix(in oklab, var(--foreground) 14%, transparent), transparent) left center / 0.75rem 100% no-repeat scroll,
347
+ linear-gradient(to left, color-mix(in oklab, var(--foreground) 14%, transparent), transparent) right center / 0.75rem 100% no-repeat scroll;
348
+ }
349
+ /* At least as wide as its frame, so a short table's rows and header reach the frame's edge. */
350
+ .docs-prose table { min-width: 100%; border-collapse: separate; border-spacing: 0; margin: 0; font-size: 0.875rem; line-height: 1.5; font-variant-numeric: tabular-nums; }
333
351
  .docs-prose :where(th, td) { padding: 0.6875rem 0.875rem; border-bottom: 1px solid var(--border); text-align: left; vertical-align: top; }
334
352
  .docs-prose tr:last-child > td { border-bottom: 0; }
335
353
  .docs-prose th { background: var(--sidebar); color: var(--muted-foreground); font-size: 0.78125rem; font-weight: 600; white-space: nowrap; }
336
- .docs-prose td code { white-space: nowrap; }
354
+ .docs-prose td code { white-space: normal; overflow-wrap: normal; }
355
+ .docs-prose td:first-child code { white-space: nowrap; }
356
+ /* Only the last column of a table with 4+ columns needs room; in narrower tables it would force a scroll on phones. */
357
+ .docs-prose table:has(th:nth-child(4)) td:last-child { min-width: 14rem; }
358
+ /* Below 360 px a 3-column table squeezes its last column to a few letters; there it scrolls too. */
359
+ @media (max-width: 22.5rem) { .docs-prose table:has(th:nth-child(3)) td:last-child { min-width: 10rem; } }
337
360
 
338
361
  /* Code blocks: one bordered panel with the file name or tabs on a lighter head (ginko-components draws an inset). */
339
362
  :where(.docs-prose, .docs-landing) :is(.ginko-code, .ginko-code-group, .ginko-code-tree):not([data-bare]) { padding: 0; overflow: hidden; border: 1px solid var(--docs-code-line); border-radius: var(--docs-r-box); }
@@ -403,7 +426,7 @@
403
426
  .docs-hero p { max-width: 40rem; margin: 1.5rem auto 0; color: var(--muted-foreground); font-size: 1.1875rem; line-height: 1.6; text-wrap: pretty; }
404
427
  .docs-hero-links { display: flex; flex-wrap: wrap; justify-content: center; gap: 0.625rem; margin-top: 2.25rem; }
405
428
  .docs-install { display: inline-flex; align-items: center; gap: 0.625rem; max-width: 100%; height: 2.625rem; margin-top: 1rem; padding: 0 0.3125rem 0 0.875rem; border: 1px solid var(--border); border-radius: var(--docs-r-control); background: var(--card); font: 500 0.84375rem/1 var(--font-mono); }
406
- .docs-install code { font: inherit; overflow-x: auto; white-space: nowrap; scrollbar-width: none; }
429
+ .docs-install code { min-width: 0; font: inherit; overflow-x: auto; white-space: nowrap; scrollbar-width: none; }
407
430
  .docs-install code::before { content: '$ '; color: var(--muted-foreground); }
408
431
  .docs-features { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 2.75rem 3rem; margin: 6.5rem 0 0; padding: 0; list-style: none; }
409
432
  .docs-feature { position: relative; display: grid; align-content: start; gap: 0.5rem; padding-top: 1.375rem; border-top: 1px solid var(--border); }
@@ -512,7 +535,7 @@ html[data-docs-banner-off] .docs-banner { display: none; }
512
535
  .docs-sidebar { display: none; }
513
536
  .docs-content { padding: 0 1.5rem 4rem; border: 0; border-radius: 0; background: none; }
514
537
  .docs-bar { justify-content: flex-end; margin: 0 -1.5rem; padding: 0 0.75rem 0 1rem; border-block: 1px solid var(--border); border-radius: 0; background: color-mix(in oklab, var(--background) 92%, transparent); }
515
- .docs-nav-links, .docs-header-end > :not(.docs-search-trigger, .docs-header-menu) { display: none; }
538
+ .docs-nav-links, .docs-header-end > :not(.docs-search-trigger, .docs-theme-toggle, .docs-header-menu) { display: none; }
516
539
  .docs-header-menu { display: inline-grid; }
517
540
  .docs-header-inner { padding: 0 0.5rem 0 1rem; }
518
541
  .docs-search-trigger { width: 2.25rem; padding: 0; justify-content: center; border-color: transparent; background: none; }
@@ -527,10 +550,14 @@ html[data-docs-banner-off] .docs-banner { display: none; }
527
550
  .docs-bar { margin: 0 -1rem; padding: 0 0.5rem; }
528
551
  .docs-page-head { grid-template-columns: minmax(0, 1fr); }
529
552
  .docs-page-head .docs-actions { grid-column: 1; grid-row: auto; margin-top: 0.75rem; }
530
- .docs-pager { grid-template-columns: 1fr; }
553
+ .docs-pager { grid-template-columns: minmax(0, 1fr); }
531
554
  .docs-pager .is-next { grid-column: 1; }
532
555
  .docs-landing-inner { padding: 3rem 1rem 4rem; }
533
556
  .docs-features { margin-top: 3.5rem; }
557
+ .docs-landing-body { margin-top: 3.5rem; }
558
+ .docs-feature:has(.docs-feature-icon) { grid-template-columns: auto minmax(0, 1fr); column-gap: 0.75rem; align-items: center; }
559
+ .docs-feature-icon { margin-bottom: 0; }
560
+ .docs-feature > p { grid-column: 1 / -1; }
534
561
  .docs-blog-panel { padding: 1.5rem 1rem 3rem; border: 0; background: none; }
535
562
  .docs-posts { margin-inline: 0; }
536
563
  .docs-posts li { grid-template-columns: minmax(0, 1fr); gap: 0.5rem; padding-inline: 0; }
@@ -541,6 +568,38 @@ html[data-docs-banner-off] .docs-banner { display: none; }
541
568
  }
542
569
 
543
570
  /* Touch screens: small controls keep their look but get a 44px hit area. */
571
+ /* ---------- Embedded: the docs inside a site's own layout (no html.docs-standalone) ----------
572
+ No framed panel: the page sits on the site's background, and the shell lines up with the site's container through
573
+ --docs-max and --docs-gutter. The search sits above the navigation; on phones the bar always shows, with the
574
+ navigation button and the search on the start side and the table of contents on the end side. */
575
+ :root:not(.docs-standalone) .docs-shell { gap: 3rem; padding-bottom: 0; }
576
+ :root:not(.docs-standalone) .docs-sidebar { padding: 2rem 0 3rem; }
577
+ :root:not(.docs-standalone) .docs-content { padding: 2rem 0 4.5rem; border: 0; border-radius: 0; background: none; }
578
+ .docs-sidebar-search { margin: 0 0 1.25rem; }
579
+ .docs-sidebar-search .docs-search-trigger { width: 100%; }
580
+ .docs-bar-start { display: none; }
581
+ .docs-bar-nav { display: inline-flex; align-items: center; gap: 0.5rem; height: 2.75rem; padding: 0 0.5rem; border: 0; background: none; color: var(--foreground); font: 500 0.875rem/1 var(--font-sans); cursor: pointer; }
582
+ .docs-bar-nav .docs-icon { font-size: 1.125rem; color: var(--muted-foreground); }
583
+ .docs-nav-sheet { inset-block: 0; inset-inline: 0 auto; width: min(20rem, calc(100vw - 3rem)); max-width: none; height: 100dvh; max-height: none; margin: 0; padding: 0; border: 0; border-inline-end: 1px solid var(--border); background: var(--background); color: var(--foreground); font-family: var(--font-sans); animation: docs-menu-in 0.15s ease-out; }
584
+ .docs-nav-sheet[open] { display: flex; flex-direction: column; }
585
+ .docs-nav-sheet::backdrop { background: rgb(0 0 0 / 0.4); }
586
+ .docs-nav-sheet-head { display: flex; flex: none; align-items: center; justify-content: space-between; height: 3.5rem; padding: 0 0.5rem 0 1rem; border-bottom: 1px solid var(--border); }
587
+ .docs-nav-sheet-head p { margin: 0; font-weight: 600; }
588
+ .docs-nav-sheet-body { flex: 1; min-height: 0; overflow-y: auto; overscroll-behavior: contain; padding: 0.75rem 0.5rem 2rem; }
589
+ .docs-nav-sheet-body .docs-nav-item > a { min-height: 2.75rem; }
590
+ @media (prefers-reduced-motion: reduce) { .docs-nav-sheet { animation: none; } }
591
+ @media (max-width: 79.99rem) {
592
+ :root:not(.docs-standalone) .docs-content { padding-top: 0; }
593
+ :root:not(.docs-standalone) .docs-bar { margin: 0; padding: 0; border-radius: 0; background: color-mix(in oklab, var(--background) 92%, transparent); }
594
+ }
595
+ @media (max-width: 63.99rem) {
596
+ :root:not(.docs-standalone) .docs-shell { padding: 0 var(--docs-gutter); }
597
+ :root:not(.docs-standalone) .docs-content { padding: 0 0 4rem; }
598
+ /* No top border: the site's header above has one. */
599
+ :root:not(.docs-standalone) .docs-bar { display: flex; justify-content: space-between; margin: 0 calc(-1 * var(--docs-gutter)); padding: 0 max(0px, calc(var(--docs-gutter) - 0.5rem)); border-top: 0; }
600
+ .docs-bar-start { display: flex; align-items: center; gap: 0.25rem; }
601
+ }
602
+
544
603
  @media (pointer: coarse) {
545
604
  :where(.docs-icon-button, .ginko-code-copy, .ginko-code-tab, .docs-toc-menu > button, .docs-example .ginko-tabs-tab) { position: relative; }
546
605
  :where(.docs-icon-button, .ginko-code-copy, .ginko-code-tab, .docs-toc-menu > button, .docs-example .ginko-tabs-tab)::after { content: ''; position: absolute; inset: -7px; }
@@ -552,4 +611,9 @@ html[data-docs-banner-off] .docs-banner { display: none; }
552
611
  .docs-blog { padding-inline: 0; }
553
612
  .docs-hero p { font-size: 1.0625rem; }
554
613
  }
614
+ /* 320px: both hero buttons fill the row. */
615
+ @media (max-width: 23.99rem) {
616
+ .docs-hero-links { flex-direction: column; }
617
+ .docs-hero-links > .docs-button { width: 100%; justify-content: center; }
618
+ }
555
619
  .ginko-excerpt-source { font-family: var(--font-sans); }
@@ -24,6 +24,6 @@ function dismiss() {
24
24
  <NuxtLink v-if="href" :to="siteHref(href)">{{ text(`docs.banner.${id}`) }}</NuxtLink>
25
25
  <template v-else>{{ text(`docs.banner.${id}`) }}</template>
26
26
  </p>
27
- <button type="button" class="docs-icon-button" :aria-label="t('docs.bannerClose')" @click="dismiss"><Icon name="lucide:x" class="docs-icon" /></button>
27
+ <button type="button" class="docs-icon-button" :aria-label="t('docs.bannerClose')" @click="dismiss"><DocsIcon name="lucide:x" class="docs-icon" /></button>
28
28
  </div>
29
29
  </template>
@@ -36,7 +36,7 @@ onBeforeUnmount(() => clearTimeout(timer))
36
36
  </Tab>
37
37
  </Tabs>
38
38
  <button type="button" class="ginko-code-copy docs-example-copy" :aria-label="tc('code.copy')" @click="copy">
39
- <Icon :name="copied ? 'lucide:check' : 'lucide:copy'" class="docs-icon" />
39
+ <DocsIcon :name="copied ? 'lucide:check' : 'lucide:copy'" class="docs-icon" />
40
40
  <span class="ginko-code-status" aria-live="polite">{{ copied ? tc('code.copied') : '' }}</span>
41
41
  </button>
42
42
  </div>
@@ -1,7 +1,8 @@
1
1
  <script setup lang="ts">
2
2
  // The standalone header: brand, the site's sections (Docs, Blog, Changelog and the site's own links), search,
3
- // language, light/dark and social links. On phones the menu button opens one full-screen menu: the docs
4
- // navigation (on docs pages), the site links and social link, the language and the light/dark switch.
3
+ // language, light/dark and social links. On phones the menu button opens one full-screen menu: the
4
+ // languages as links at its top, the docs navigation (on docs pages), the site links and social links, and the
5
+ // Auto/Light/Dark switch. The light/dark button stays in the header on phones too.
5
6
  const { t, localePath } = useI18n()
6
7
  const cfg = useAppConfig().docs ?? {}
7
8
  const title = cfg.title ?? useGinkoSite().name
@@ -71,14 +72,14 @@ onBeforeUnmount(() => {
71
72
  <NuxtLink v-for="l in links" :key="l.key" :to="l.to" :aria-current="l.active ? 'page' : undefined">{{ l.label }}</NuxtLink>
72
73
  </nav>
73
74
  <div class="docs-header-end">
74
- <DocsSearch />
75
+ <DocsSearch v-if="cfg.search !== false" />
75
76
  <DocsLanguage />
76
77
  <DocsThemeToggle />
77
78
  <template v-if="cfg.social?.length">
78
79
  <span class="docs-header-sep" aria-hidden="true" />
79
80
  <div class="docs-social">
80
81
  <a v-for="s in cfg.social" :key="s.href" class="docs-icon-button" :href="s.href" target="_blank" rel="noopener" :aria-label="s.label" :title="s.label">
81
- <Icon :name="s.icon" class="docs-icon" />
82
+ <DocsIcon :name="s.icon" class="docs-icon" />
82
83
  </a>
83
84
  </div>
84
85
  </template>
@@ -86,13 +87,14 @@ onBeforeUnmount(() => {
86
87
  ref="button" type="button" class="docs-icon-button docs-header-menu" :aria-expanded="menu" aria-controls="docs-menu"
87
88
  :aria-label="menu ? t('docs.close') : t('docs.siteMenu')" @click="menu = !menu"
88
89
  >
89
- <Icon :name="menu ? 'lucide:x' : 'lucide:menu'" class="docs-icon" />
90
+ <DocsIcon :name="menu ? 'lucide:x' : 'lucide:menu'" class="docs-icon" />
90
91
  </button>
91
92
  </div>
92
93
  </div>
93
94
  <!-- Teleported: the header's backdrop blur would otherwise make it the containing block of the fixed menu. -->
94
95
  <Teleport to="body">
95
96
  <div v-if="menu" id="docs-menu" ref="panel" class="docs-menu" role="dialog" tabindex="-1" :aria-label="t('docs.siteMenu')">
97
+ <DocsLanguage list />
96
98
  <div v-if="inDocs" class="docs-menu-scroll">
97
99
  <DocsSidebar />
98
100
  </div>
@@ -103,7 +105,6 @@ onBeforeUnmount(() => {
103
105
  </nav>
104
106
  </div>
105
107
  <div class="docs-menu-foot">
106
- <DocsLanguage />
107
108
  <DocsThemeToggle segmented />
108
109
  </div>
109
110
  </div>
@@ -0,0 +1,12 @@
1
+ <script setup lang="ts">
2
+ // One icon from the build-time map (modules/icons.ts). A bare name is a Lucide icon; an unknown name renders nothing.
3
+ import { computed } from 'vue'
4
+ import icons from '#docs-icons'
5
+
6
+ const props = defineProps<{ name: string }>()
7
+ const icon = computed(() => icons[props.name.includes(':') ? props.name : `lucide:${props.name}`])
8
+ </script>
9
+
10
+ <template>
11
+ <svg v-if="icon" xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" :viewBox="icon[1]" aria-hidden="true" v-html="icon[0]" />
12
+ </template>