@humanforest/slidev-theme 0.1.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 (124) hide show
  1. package/LICENSE +67 -0
  2. package/README.md +577 -0
  3. package/assetUrl.js +38 -0
  4. package/components/Alert.vue +209 -0
  5. package/components/AreaBox.vue +152 -0
  6. package/components/AreaChart.vue +145 -0
  7. package/components/Avatar.vue +267 -0
  8. package/components/AvatarGroup.vue +94 -0
  9. package/components/BadgeStrip.vue +203 -0
  10. package/components/Bars.vue +207 -0
  11. package/components/BoroughShape.vue +127 -0
  12. package/components/BrowserFrame.vue +484 -0
  13. package/components/CalendarGrid.vue +480 -0
  14. package/components/Card.vue +70 -0
  15. package/components/Carousel.vue +266 -0
  16. package/components/ChartFrame.vue +426 -0
  17. package/components/ChordChart.vue +264 -0
  18. package/components/ComposedChart.vue +187 -0
  19. package/components/Contents.vue +86 -0
  20. package/components/DataTable.vue +244 -0
  21. package/components/DeckMap.vue +409 -0
  22. package/components/DeviceFrame.vue +331 -0
  23. package/components/DonutChart.vue +103 -0
  24. package/components/DottedMap.vue +126 -0
  25. package/components/Endorsement.vue +76 -0
  26. package/components/Grid.vue +169 -0
  27. package/components/GroupedBarChart.vue +141 -0
  28. package/components/IconTile.vue +116 -0
  29. package/components/Kpi.vue +188 -0
  30. package/components/LineChart.vue +133 -0
  31. package/components/Logo.vue +203 -0
  32. package/components/LondonMap.vue +348 -0
  33. package/components/MarkPair.vue +109 -0
  34. package/components/Marquee.vue +263 -0
  35. package/components/NestedDonutChart.vue +295 -0
  36. package/components/Placeholder.vue +88 -0
  37. package/components/SankeyChart.vue +223 -0
  38. package/components/ScatterChart.vue +142 -0
  39. package/components/StackedBarChart.vue +143 -0
  40. package/components/StatCard.vue +134 -0
  41. package/components/StatusTrack.vue +334 -0
  42. package/components/Timeline.vue +249 -0
  43. package/components/TimelineChart.vue +329 -0
  44. package/components/TreemapChart.vue +267 -0
  45. package/components/backgrounds.js +135 -0
  46. package/components/boroughGeometry.ts +59 -0
  47. package/components/chartData.ts +277 -0
  48. package/components/chartTip.ts +201 -0
  49. package/components/codeTheme.ts +239 -0
  50. package/components/deckPalette.ts +157 -0
  51. package/components/dottedMapGeometry.js +60 -0
  52. package/components/iconSlugs.js +55 -0
  53. package/components/iconUrl.js +51 -0
  54. package/components/mermaidTheme.ts +972 -0
  55. package/components/motionDuration.ts +41 -0
  56. package/components/tableRules.ts +257 -0
  57. package/confidential-mark.vue +151 -0
  58. package/custom-nav-controls.vue +79 -0
  59. package/endorsements/b-corp-black.svg +30 -0
  60. package/endorsements/b-corp-white.svg +30 -0
  61. package/global-top.vue +1662 -0
  62. package/icons/forest/bike-asterisk.svg +21 -0
  63. package/icons/forest/bike-down.svg +19 -0
  64. package/icons/forest/bike-off.svg +20 -0
  65. package/icons/forest/bike-up.svg +19 -0
  66. package/icons/forest/bike-x.svg +19 -0
  67. package/icons/forest/bike.svg +18 -0
  68. package/icons/forest/mark-fill.svg +6 -0
  69. package/icons/forest/mark.svg +6 -0
  70. package/icons/forest/parking.svg +3 -0
  71. package/icons/forest/star-fill.svg +4 -0
  72. package/icons/forest/star.svg +4 -0
  73. package/icons/forest/traffic-light-caution.svg +8 -0
  74. package/icons/forest/traffic-light-go.svg +8 -0
  75. package/icons/forest/traffic-light-lit.svg +8 -0
  76. package/icons/forest/traffic-light-stop.svg +8 -0
  77. package/icons/forest/traffic-light.svg +8 -0
  78. package/icons/lucide/battery.svg +1 -0
  79. package/icons/lucide/bike.svg +1 -0
  80. package/icons/lucide/chart-line.svg +1 -0
  81. package/icons/lucide/check.svg +1 -0
  82. package/icons/lucide/circle-check.svg +1 -0
  83. package/icons/lucide/clock.svg +1 -0
  84. package/icons/lucide/code.svg +1 -0
  85. package/icons/lucide/component.svg +1 -0
  86. package/icons/lucide/frame.svg +1 -0
  87. package/icons/lucide/layout-grid.svg +1 -0
  88. package/icons/lucide/map-pin.svg +1 -0
  89. package/icons/lucide/map.svg +1 -0
  90. package/icons/lucide/palette.svg +1 -0
  91. package/icons/lucide/table.svg +1 -0
  92. package/icons/lucide/triangle-alert.svg +1 -0
  93. package/icons/lucide/type.svg +1 -0
  94. package/icons/lucide/wrench.svg +1 -0
  95. package/layouts/bleed.vue +317 -0
  96. package/layouts/cover.vue +1514 -0
  97. package/layouts/default.vue +187 -0
  98. package/layouts/divider.vue +286 -0
  99. package/layouts/end.vue +384 -0
  100. package/layouts/quote.vue +218 -0
  101. package/layouts/split.vue +1875 -0
  102. package/layouts/stack.vue +944 -0
  103. package/layouts/statement.vue +67 -0
  104. package/manifest/chrome.ts +36 -0
  105. package/manifest/components.ts +49 -0
  106. package/manifest/index.ts +57 -0
  107. package/manifest/layouts.ts +37 -0
  108. package/manifest/manifest.json +6514 -0
  109. package/manifest/presets.ts +635 -0
  110. package/notch.js +458 -0
  111. package/notchPath.js +188 -0
  112. package/package.json +77 -0
  113. package/section.js +110 -0
  114. package/setup/mermaid-renderer.ts +2120 -0
  115. package/setup/mermaid.ts +30 -0
  116. package/setup/shiki.ts +44 -0
  117. package/setup/shortcuts.ts +28 -0
  118. package/slide-bottom.vue +305 -0
  119. package/slide-top.vue +268 -0
  120. package/styles/base.css +2611 -0
  121. package/styles/index.ts +71 -0
  122. package/styles/tokens.css +144 -0
  123. package/uno.config.ts +231 -0
  124. package/useNotchCuts.js +200 -0
package/package.json ADDED
@@ -0,0 +1,77 @@
1
+ {
2
+ "name": "@humanforest/slidev-theme",
3
+ "version": "0.1.0",
4
+ "description": "Forest brand theme for Slidev — the guardrail. Layouts + components wired to @humanforest/tokens, so any deck is on-brand by construction. Do not hand-pick colours/fonts here; everything aliases the engine tokens.",
5
+ "keywords": [
6
+ "slidev-theme",
7
+ "slidev",
8
+ "theme",
9
+ "forest"
10
+ ],
11
+ "license": "SEE LICENSE IN LICENSE",
12
+ "scripts": {
13
+ "test": "vitest run"
14
+ },
15
+ "engines": {
16
+ "node": ">=18.0.0",
17
+ "slidev": ">=0.50.0"
18
+ },
19
+ "slidev": {
20
+ "colorSchema": "light",
21
+ "defaults": {
22
+ "transition": "view-transition",
23
+ "aspectRatio": "16/9",
24
+ "canvasWidth": 1920,
25
+ "fonts": {
26
+ "sans": "GT Haptik",
27
+ "mono": "JetBrains Mono",
28
+ "provider": "none",
29
+ "local": "GT Haptik,JetBrains Mono,MohrAlt"
30
+ }
31
+ }
32
+ },
33
+ "dependencies": {
34
+ "@humanforest/fonts": "^0.1.0",
35
+ "@humanforest/frames": "^0.1.0",
36
+ "@humanforest/tokens": "^0.1.0",
37
+ "@humanforest/ui": "^0.1.0",
38
+ "@iconify-json/lucide": "^1.2.0",
39
+ "@iconify-json/vscode-icons": "^1.2.75",
40
+ "@unovis/ts": "^1.6.7",
41
+ "@unovis/vue": "^1.6.7",
42
+ "mapbox-gl": "^3.28.1",
43
+ "mermaid": "^11.17.2"
44
+ },
45
+ "devDependencies": {
46
+ "@unocss/preset-wind3": "^66.7.5",
47
+ "unocss": "^66.7.5",
48
+ "vitest": "^4.1.10"
49
+ },
50
+ "peerDependencies": {
51
+ "unocss": ">=66"
52
+ },
53
+ "files": [
54
+ "layouts",
55
+ "components",
56
+ "styles",
57
+ "setup",
58
+ "global-top.vue",
59
+ "slide-top.vue",
60
+ "slide-bottom.vue",
61
+ "custom-nav-controls.vue",
62
+ "confidential-mark.vue",
63
+ "assetUrl.js",
64
+ "notch.js",
65
+ "notchPath.js",
66
+ "section.js",
67
+ "useNotchCuts.js",
68
+ "uno.config.ts",
69
+ "icons",
70
+ "endorsements",
71
+ "manifest",
72
+ "README.md"
73
+ ],
74
+ "publishConfig": {
75
+ "access": "public"
76
+ }
77
+ }
package/section.js ADDED
@@ -0,0 +1,110 @@
1
+ // The section pill's logic, split out of slide-top.vue so it is unit-testable at all.
2
+ //
3
+ // packages/slidev-theme's own suite (test/frontmatter.static.test.ts) runs under plain Node, no
4
+ // DOM (see vitest.config.ts: `environment: 'node'`) — there is no @vue/test-utils here and no
5
+ // component ever gets mounted. A computed buried inside slide-top.vue's <script setup> would
6
+ // therefore be unreachable from a test. Pulling the derivation out into a plain, exported function
7
+ // is the same move backgrounds.js already made for the background lookup, and the same shape the
8
+ // suite already tests directly for contrast() (packages/tokens/src/colourEngine.ts) — a pure
9
+ // function, imported and called with plain data, no framework runtime required.
10
+ //
11
+ // A slide's section is the nearest PRECEDING divider's title — not a value the slide carries
12
+ // itself. Two traps, both load-bearing (see task-5-api-notes.md, which this was checked against):
13
+ //
14
+ // 1. Indexing. `useNav()`'s `slides` is a zero-indexed array; `currentPage` is Slidev's own
15
+ // ONE-based page number. `currentPage - 1` is the current slide's own array index. Walking
16
+ // back from `currentPage` directly (forgetting the -1) starts one slide too far forward and
17
+ // lets a divider claim the slide AFTER it as "its own" section — while the divider itself
18
+ // would report whatever divider preceded IT, or nothing. The walk here starts at
19
+ // `currentPage - 1` specifically so a divider slide reports ITS OWN title as its section
20
+ // (there is nothing wrong with a divider naming itself — the audience is looking at "The
21
+ // seam" while "The seam" is the current slide), and the slide immediately before it must not.
22
+ // 2. Precedence. `frontmatter.section` is the author's explicit override (a plain custom key,
23
+ // not reserved anywhere in Slidev's frontmatter). `meta.slide.title` is Slidev's OWN
24
+ // first-heading extraction — reading it is fine, but a layout must never be told to WRITE
25
+ // `title:` in its own frontmatter to name a section, because `title` is reserved on slide 1
26
+ // (RESERVED_ON_SLIDE_ONE in test/props.ts) and would silently be dropped there.
27
+
28
+ /**
29
+ * The minimal shape this needs out of a `useNav()` slide route — `SlideRoute` from
30
+ * `@slidev/types`, narrowed to the two fields actually read.
31
+ * @typedef {{ meta: { slide: { frontmatter: Record<string, any>, title?: string } } }} MinimalSlideRoute
32
+ */
33
+
34
+ /**
35
+ * Derives the section pill's text for the slide at `currentPage` (Slidev's own one-based page
36
+ * number) by walking backwards through the deck's own slide list to the first slide — inclusive
37
+ * of the current one — whose `layout` is `'divider'`.
38
+ *
39
+ * @param {MinimalSlideRoute[]} slides `useNav()`'s own `slides.value` — zero-indexed.
40
+ * @param {number} currentPage `useNav()`'s own `currentPage.value` — one-based.
41
+ * @returns {string} the nearest preceding (or the current) divider's `section:` override, falling
42
+ * back to its own extracted title, or `''` when no divider precedes this slide at all — the
43
+ * empty string is the caller's own signal to render no pill.
44
+ *
45
+ * Three ways to get that empty string on purpose, narrowest first:
46
+ * - `section: false` on a SLIDE — this slide only, chrome otherwise untouched
47
+ * - `section: false` on a DIVIDER — that divider and every slide until the next one
48
+ * - `sectionPill: false` in HEADMATTER — the whole deck, and nothing can out-rank it
49
+ */
50
+ export const deriveSection = (slides, currentPage) => {
51
+ /*
52
+ ★★ `sectionPill: false` — THE DECK-WIDE SWITCH, AND IT IS CHECKED BEFORE EVERYTHING ELSE.
53
+
54
+ Read off slide ONE's frontmatter, which in Slidev is the deck's headmatter. Not merged, not
55
+ inherited: read directly, once, from the only slide that can carry a deck-level statement.
56
+
57
+ ★ WHY NOT `defaults: { section: false }`, WHICH ALREADY ALMOST WORKS. Slidev's `defaults:` block
58
+ merges into every slide's own frontmatter, so it looks like the deck-wide lever and behaves like
59
+ one until it doesn't: a slide's OWN frontmatter outranks a default, and every divider now
60
+ declares `section:` because a divider that names nothing takes the pill off every slide behind
61
+ it (see the walk below, and the gate in test/frontmatter.static.test.ts). So dividers — and only
62
+ dividers — would out-rank the default and keep their pill.
63
+
64
+ Measured before this existed: `defaults: { section: false }` cleaned 224 slides across the two
65
+ decks and left a pill on three, all of them dividers drawn as SPECIMENS of the divider layout
66
+ rather than as section openers. That is a rule nobody could predict from the outside, failing on
67
+ the category hardest to guess. A switch that works by accident of precedence is not a switch.
68
+
69
+ This one cannot be out-ranked, because nothing else is consulted once it is set.
70
+
71
+ NOT the same key as `section:`, deliberately. `sections` would have been the natural plural and
72
+ is one keystroke from `section` — a typo that silently changes SCOPE rather than erroring is
73
+ exactly the class of defect this file's own history is made of.
74
+ */
75
+ if (slides[0]?.meta?.slide?.frontmatter?.sectionPill === false) return '';
76
+
77
+ /*
78
+ ★ `section: false` — the PER-SLIDE opt-out, and the only thing a non-divider slide may say about
79
+ its own section.
80
+
81
+ Two levers already existed and neither was per-slide: `section: '…'` on a DIVIDER renames the
82
+ pill for that whole run of slides, and `chrome: false` removes all the chrome at once, mark
83
+ included. A slide that wants its band's top-left corner back — and nothing else changed — had no
84
+ way to ask, because this function only ever read divider frontmatter.
85
+
86
+ FALSE, not empty string. `section: ''` on a divider already means "this section has no name",
87
+ which is a statement about the SECTION; `section: false` on any slide means "not here", which is
88
+ a statement about the SLIDE. Keeping them distinct matters because the first propagates forward
89
+ to every slide under that divider and the second does not.
90
+
91
+ It composes with the notch for free and that is not a coincidence: the cut is measured from
92
+ whatever chrome is actually on the panel (../useNotchCuts.js), so a slide that draws no pill
93
+ simply has no box in that corner and gets its plain --deck-radius-card corner back. Nothing
94
+ tells the panel; there is nothing to tell.
95
+ */
96
+ if (slides[currentPage - 1]?.meta?.slide?.frontmatter?.section === false) return '';
97
+
98
+ for (let i = currentPage - 1; i >= 0; i--) {
99
+ const frontmatter = slides[i]?.meta?.slide?.frontmatter;
100
+ if (frontmatter?.layout === 'divider') {
101
+ // A divider may opt its whole section out the same way. Checked explicitly rather than left
102
+ // to `?? title`, because `false` is not nullish — it would fall through and be returned AS a
103
+ // boolean, which happens to render as no pill today only because the caller filters falsy
104
+ // values. Relying on that would be a contract nobody wrote down.
105
+ if (frontmatter.section === false) return '';
106
+ return frontmatter.section ?? slides[i].meta.slide.title ?? '';
107
+ }
108
+ }
109
+ return '';
110
+ };