@humanforest/slidev-theme 0.1.2 → 0.2.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.
@@ -54,6 +54,21 @@ const props = defineProps({
54
54
  const src = computed(() => (props.tone === 'reverse' ? reverseUrl : positiveUrl));
55
55
  </script>
56
56
 
57
+ <!--
58
+ `alt` names the certification rather than the picture — a reader who cannot see it needs to know
59
+ the deck carries the certification, which is what the mark is there to say.
60
+
61
+ ★★ AND THE COMMENT LIVES OUT HERE, NOT INSIDE `<template>`. A comment beside the `<img>` is a
62
+ SECOND ROOT NODE: Vue compiles the template to `createElementBlock(Fragment, [img, comment])`,
63
+ and a fragment root has nowhere to put inherited attributes, so it drops them silently. That is
64
+ not a dev-only detail — Slidev builds with comments ON, so the shipped bundle carries the
65
+ fragment too. `cover.vue` passes `class="cover-endorsement"` to place the mark in the slide's
66
+ bottom-left corner; the class never arrived, so the mark stayed `position: static` and stretched
67
+ to 1728 x 136 — a band across the foot of every mosaic cover, on a 1920 canvas. With the class it
68
+ draws 82 x 136 at left 120px, bottom 96px, which is `--deck-inset-display` and `--deck-inset-text`
69
+ exactly. Both numbers measured in the built dist, not inferred. The same drop disabled
70
+ `.cover-endorsement-stacked` on the poster variant.
71
+ -->
57
72
  <template>
58
73
  <img
59
74
  class="deck-endorsement"
@@ -61,10 +76,6 @@ const src = computed(() => (props.tone === 'reverse' ? reverseUrl : positiveUrl)
61
76
  :style="{ height: `${size}px` }"
62
77
  alt="Certified B Corporation"
63
78
  >
64
- <!--
65
- `alt` names the certification rather than the picture — a reader who cannot see it needs to know
66
- the deck carries the certification, which is what the mark is there to say.
67
- -->
68
79
  </template>
69
80
 
70
81
  <style scoped>
package/layouts/cover.vue CHANGED
@@ -75,13 +75,12 @@ const props = defineProps({
75
75
  validator: (b) => BEAT_GROUNDS.includes(b),
76
76
  },
77
77
  /**
78
- * WHAT SHAPE the opener is, as opposed to what colour. Six compositions, and every one of them
78
+ * WHAT SHAPE the opener is, as opposed to what colour. Four compositions, and every one of them
79
79
  * takes every `background` and both registers — the two props are orthogonal by construction,
80
80
  * which is the whole reason the ground is not baked into the variant names.
81
81
  *
82
82
  * · `plain` — type alone on the ground. The opener with nothing to look at but the words.
83
83
  * · `inset` — the title in a panel floating on the ground, air all round it.
84
- * · `mark` — the Forest mark oversized as the composition, the title over it.
85
84
  * · `poster` — a photograph edge to edge, the identity stacked over a scrim at the right.
86
85
  * · `mosaic` — the title on a field, beside a staggered grid of pictures bleeding off the edge.
87
86
  *
@@ -88,7 +88,7 @@ const props = defineProps({
88
88
  rank: { type: String, default: 'section', validator: (v) => ['section', 'subsection', 'topic'].includes(v) },
89
89
  /**
90
90
  * The part's own mark, drawn above the rule — `<namespace>:<name>`, the same reference IconTile
91
- * takes (`forest:bike`, `lucide:layers`). SECTION RANK ONLY: it is the identity of a part, and a
91
+ * takes (`forest:bike`, `lucide:map`). SECTION RANK ONLY: it is the identity of a part, and a
92
92
  * subsection is not a new part. Ignored at the other ranks rather than refused, so changing a
93
93
  * slide's rank never breaks it.
94
94
  */
@@ -163,7 +163,7 @@
163
163
  "hasDynamicDefault": false,
164
164
  "defaultSource": "'plain'",
165
165
  "required": false,
166
- "description": "WHAT SHAPE the opener is, as opposed to what colour. Six compositions, and every one of them takes every `background` and both registers — the two props are orthogonal by construction, which is the whole reason the ground is not baked into the variant names. · `plain` — type alone on the ground. The opener with nothing to look at but the words. · `inset` — the title in a panel floating on the ground, air all round it. · `mark` — the Forest mark oversized as the composition, the title over it. · `poster` — a photograph edge to edge, the identity stacked over a scrim at the right. · `mosaic` — the title on a field, beside a staggered grid of pictures bleeding off the edge."
166
+ "description": "WHAT SHAPE the opener is, as opposed to what colour. Four compositions, and every one of them takes every `background` and both registers — the two props are orthogonal by construction, which is the whole reason the ground is not baked into the variant names. · `plain` — type alone on the ground. The opener with nothing to look at but the words. · `inset` — the title in a panel floating on the ground, air all round it. · `poster` — a photograph edge to edge, the identity stacked over a scrim at the right. · `mosaic` — the title on a field, beside a staggered grid of pictures bleeding off the edge."
167
167
  },
168
168
  {
169
169
  "name": "endorsements",
@@ -296,7 +296,7 @@
296
296
  "hasDynamicDefault": false,
297
297
  "defaultSource": "''",
298
298
  "required": false,
299
- "description": "The part's own mark, drawn above the rule — `<namespace>:<name>`, the same reference IconTile takes (`forest:bike`, `lucide:layers`). SECTION RANK ONLY: it is the identity of a part, and a subsection is not a new part. Ignored at the other ranks rather than refused, so changing a slide's rank never breaks it."
299
+ "description": "The part's own mark, drawn above the rule — `<namespace>:<name>`, the same reference IconTile takes (`forest:bike`, `lucide:map`). SECTION RANK ONLY: it is the identity of a part, and a subsection is not a new part. Ignored at the other ranks rather than refused, so changing a slide's rank never breaks it."
300
300
  }
301
301
  ],
302
302
  "slots": [
@@ -6237,7 +6237,7 @@
6237
6237
  "role": "Several DIFFERENT kinds of reading at once — figures beside a figure with a shape, not one form repeated across a uniform row.",
6238
6238
  "layout": "default",
6239
6239
  "props": {},
6240
- "fill": "A `::grid` (`:cols: 2` or 3) mixing two or three `::stat-card` tiles with one figure tile — a status-board read, not a single chart type repeated. `::bars` labels every row and prints its own value, and `::dotted-map` draws no key at all, so neither needs anything around it; a tile whose figure carries several series does need a key, and every Unovis mark gets one from `::chart-frame`, which is where the `height`, `legend` and `legend-position` each mark forwards land (the empty state reaches it as the mark's own `empty`). Two of the frame's defaults are wrong inside a cell: the 480px plot is deeper than a grid cell is wide, and a bottom legend eats what is left of it — set `:height:` down and `legend-position: 'top'`, or `:legend: false` and label the marks. Reach for `::chart-frame` directly only when the tile's figure is not one of the shipped marks: it is the one thing in the deck that puts a room-scale series key, and the honest empty well, under a figure built by hand.",
6240
+ "fill": "A `::grid` (`:cols: 2` or 3) mixing two or three `::stat-card` tiles with one figure tile — a status-board read, not a single chart type repeated. `::bars` labels every row and prints its own value, and `::dotted-map` draws no key at all, so neither needs anything around it; a tile whose figure carries several series does need a key, and every Unovis mark gets one from `::chart-frame`, which is where the `height`, `legend` and `legend-position` each mark forwards land (the empty state reaches it as the mark's own `empty`). Two of the frame's defaults are wrong inside a cell: the 480px plot is deeper than a grid cell is wide, and a bottom legend eats what is left of it — set `:height:` down and `legend-position: 'top-left'`, or `:legend: false` and label the marks. Reach for `::chart-frame` directly only when the tile's figure is not one of the shipped marks: it is the one thing in the deck that puts a room-scale series key, and the honest empty well, under a figure built by hand.",
6241
6241
  "use": "An operating-review slide that needs to show the state of several different things on one screen, the way a real dashboard does.",
6242
6242
  "avoid": "Several figures of the SAME kind that should read as one comparison — `metrics` keeps every tile the same shape on one baseline; mixing figure types into that row is exactly what `dashboard` is for, and exactly what breaks a `metrics` row's own promise."
6243
6243
  },
@@ -6382,7 +6382,7 @@
6382
6382
  "props": {
6383
6383
  "ratio": "2:3"
6384
6384
  },
6385
- "fill": "Heading and a single-sentence reading of the movement in the default slot content; the chart in the `aside` slot — `::line-chart` when the series are independent trajectories to be compared, `::area-chart` when they sum to one whole (its default stack makes exactly that claim; pass `:overlaid: true` when they do not). Every `label` in `data` is a point on a real sequence, in order. In the props block every NON-STRING value is colon-prefixed — `:data:`, `:series:`, `:overlaid:`, `:height:`, `:max-ticks:` — while string props go bare (`y-label: 'Trips'`, `legend-position: 'top'`); drop the colon and the value arrives as a string, so an array plots nothing and a `false` reads as truthy. Set `yLabel` unless the heading already carries the unit, and leave `height` at its 480 default: with the heading in the content column rather than a `::header::`, the `frame: content` figure column starts at the 360px vertical anchor and split.vue caps it at 630px, which 480 plus a legend row just fits. (A `::header::` would size row 1 to the 130 band instead and free the 592 a full-width chart gets — a different recipe, not this one.)",
6385
+ "fill": "Heading and a single-sentence reading of the movement in the default slot content; the chart in the `aside` slot — `::line-chart` when the series are independent trajectories to be compared, `::area-chart` when they sum to one whole (its default stack makes exactly that claim; pass `:overlaid: true` when they do not). Every `label` in `data` is a point on a real sequence, in order. In the props block every NON-STRING value is colon-prefixed — `:data:`, `:series:`, `:overlaid:`, `:height:`, `:max-ticks:` — while string props go bare (`y-label: 'Trips'`, `legend-position: 'bottom-center'`); drop the colon and the value arrives as a string, so an array plots nothing and a `false` reads as truthy. Set `yLabel` unless the heading already carries the unit, and leave `height` at its 480 default: with the heading in the content column rather than a `::header::`, the `frame: content` figure column starts at the 360px vertical anchor and split.vue caps it at 630px, which 480 plus a legend row just fits. (A `::header::` would size row 1 to the 130 band instead and free the 592 a full-width chart gets — a different recipe, not this one.)",
6386
6386
  "use": "A slide whose claim is a direction — growth, a decline, a curve that flattened — where the audience needs to see the shape and read the takeaway in the same breath.",
6387
6387
  "avoid": "A comparison across categories: `chart`'s bars are the mark for that, and a line drawn across category labels claims a rate of change between things that have no 'between'. Also avoid it for two points and a delta — that is `stat-hero`, and a line through two dots dresses a single subtraction up as a trajectory."
6388
6388
  },
@@ -6391,7 +6391,7 @@
6391
6391
  "role": "Two measurements read against each other — the one chart slide whose answer is the shape of a cloud, not a figure anyone can read off an axis.",
6392
6392
  "layout": "default",
6393
6393
  "props": {},
6394
- "fill": "The takeaway as the slide's heading, and `::scatter-chart` under it filling the frame — `data` rows of `{ x, y }`, plus `size` (scaled by `sizeRange`) for a third measure and `group` when the dots split into named sets, which is the only case where the legend says anything. In the props block every NON-STRING value is colon-prefixed — `:data:`, `:height:`, `:labels:`, `:size-range:` — while string props go bare (`x-label: 'Parking bays'`, `legend-position: 'top'`); drop the colon and the value arrives as a string, so an array plots nothing and `labels: false` reads as truthy. Set both `xLabel` and `yLabel` — neither axis of a scatter is self-evident — turn `:labels: true` on only when there are few enough dots to name, about six before they collide, and set `:height: 592`, which is what every full-width chart under a heading in the specimen deck uses; the 480 default is sized for a split's figure column and leaves the foot of the slide empty here.",
6394
+ "fill": "The takeaway as the slide's heading, and `::scatter-chart` under it filling the frame — `data` rows of `{ x, y }`, plus `size` (scaled by `sizeRange`) for a third measure and `group` when the dots split into named sets, which is the only case where the legend says anything. In the props block every NON-STRING value is colon-prefixed — `:data:`, `:height:`, `:labels:`, `:size-range:` — while string props go bare (`x-label: 'Parking bays'`, `legend-position: 'bottom-center'`); drop the colon and the value arrives as a string, so an array plots nothing and `labels: false` reads as truthy. Set both `xLabel` and `yLabel` — neither axis of a scatter is self-evident — turn `:labels: true` on only when there are few enough dots to name, about six before they collide, and set `:height: 592`, which is what every full-width chart under a heading in the specimen deck uses; the 480 default is sized for a split's figure column and leaves the foot of the slide empty here.",
6395
6395
  "use": "A relationship between two measures — parking bays against trips, price against usage — where the point is whether the dots line up at all, and how tightly.",
6396
6396
  "avoid": "Anything measured over time: `trend` is that, and plotting (date, value) pairs as dots throws away the ordering a line's own segments carry, leaving the audience to re-sequence the cloud by eye. And do not move this into `trend`'s `split` figure column — that column caps at 630px and narrows the x range, pressing the cloud into a blob, which is the one compression a correlation slide cannot survive."
6397
6397
  },
@@ -324,7 +324,7 @@ export const PRESETS: Preset[] = [
324
324
  role: 'Several DIFFERENT kinds of reading at once — figures beside a figure with a shape, not one form repeated across a uniform row.',
325
325
  layout: 'default',
326
326
  props: {},
327
- fill: 'A `::grid` (`:cols: 2` or 3) mixing two or three `::stat-card` tiles with one figure tile — a status-board read, not a single chart type repeated. `::bars` labels every row and prints its own value, and `::dotted-map` draws no key at all, so neither needs anything around it; a tile whose figure carries several series does need a key, and every Unovis mark gets one from `::chart-frame`, which is where the `height`, `legend` and `legend-position` each mark forwards land (the empty state reaches it as the mark\'s own `empty`). Two of the frame\'s defaults are wrong inside a cell: the 480px plot is deeper than a grid cell is wide, and a bottom legend eats what is left of it — set `:height:` down and `legend-position: \'top\'`, or `:legend: false` and label the marks. Reach for `::chart-frame` directly only when the tile\'s figure is not one of the shipped marks: it is the one thing in the deck that puts a room-scale series key, and the honest empty well, under a figure built by hand.',
327
+ fill: 'A `::grid` (`:cols: 2` or 3) mixing two or three `::stat-card` tiles with one figure tile — a status-board read, not a single chart type repeated. `::bars` labels every row and prints its own value, and `::dotted-map` draws no key at all, so neither needs anything around it; a tile whose figure carries several series does need a key, and every Unovis mark gets one from `::chart-frame`, which is where the `height`, `legend` and `legend-position` each mark forwards land (the empty state reaches it as the mark\'s own `empty`). Two of the frame\'s defaults are wrong inside a cell: the 480px plot is deeper than a grid cell is wide, and a bottom legend eats what is left of it — set `:height:` down and `legend-position: \'top-left\'`, or `:legend: false` and label the marks. Reach for `::chart-frame` directly only when the tile\'s figure is not one of the shipped marks: it is the one thing in the deck that puts a room-scale series key, and the honest empty well, under a figure built by hand.',
328
328
  use: 'An operating-review slide that needs to show the state of several different things on one screen, the way a real dashboard does.',
329
329
  avoid: 'Several figures of the SAME kind that should read as one comparison — `metrics` keeps every tile the same shape on one baseline; mixing figure types into that row is exactly what `dashboard` is for, and exactly what breaks a `metrics` row\'s own promise.',
330
330
  },
@@ -459,7 +459,7 @@ export const PRESETS: Preset[] = [
459
459
  role: 'A quantity moving through time, with the one sentence that says what the movement means sitting beside it.',
460
460
  layout: 'split',
461
461
  props: { ratio: '2:3' },
462
- fill: 'Heading and a single-sentence reading of the movement in the default slot content; the chart in the `aside` slot — `::line-chart` when the series are independent trajectories to be compared, `::area-chart` when they sum to one whole (its default stack makes exactly that claim; pass `:overlaid: true` when they do not). Every `label` in `data` is a point on a real sequence, in order. In the props block every NON-STRING value is colon-prefixed — `:data:`, `:series:`, `:overlaid:`, `:height:`, `:max-ticks:` — while string props go bare (`y-label: \'Trips\'`, `legend-position: \'top\'`); drop the colon and the value arrives as a string, so an array plots nothing and a `false` reads as truthy. Set `yLabel` unless the heading already carries the unit, and leave `height` at its 480 default: with the heading in the content column rather than a `::header::`, the `frame: content` figure column starts at the 360px vertical anchor and split.vue caps it at 630px, which 480 plus a legend row just fits. (A `::header::` would size row 1 to the 130 band instead and free the 592 a full-width chart gets — a different recipe, not this one.)',
462
+ fill: 'Heading and a single-sentence reading of the movement in the default slot content; the chart in the `aside` slot — `::line-chart` when the series are independent trajectories to be compared, `::area-chart` when they sum to one whole (its default stack makes exactly that claim; pass `:overlaid: true` when they do not). Every `label` in `data` is a point on a real sequence, in order. In the props block every NON-STRING value is colon-prefixed — `:data:`, `:series:`, `:overlaid:`, `:height:`, `:max-ticks:` — while string props go bare (`y-label: \'Trips\'`, `legend-position: \'bottom-center\'`); drop the colon and the value arrives as a string, so an array plots nothing and a `false` reads as truthy. Set `yLabel` unless the heading already carries the unit, and leave `height` at its 480 default: with the heading in the content column rather than a `::header::`, the `frame: content` figure column starts at the 360px vertical anchor and split.vue caps it at 630px, which 480 plus a legend row just fits. (A `::header::` would size row 1 to the 130 band instead and free the 592 a full-width chart gets — a different recipe, not this one.)',
463
463
  use: 'A slide whose claim is a direction — growth, a decline, a curve that flattened — where the audience needs to see the shape and read the takeaway in the same breath.',
464
464
  avoid: 'A comparison across categories: `chart`\'s bars are the mark for that, and a line drawn across category labels claims a rate of change between things that have no \'between\'. Also avoid it for two points and a delta — that is `stat-hero`, and a line through two dots dresses a single subtraction up as a trajectory.',
465
465
  },
@@ -468,7 +468,7 @@ export const PRESETS: Preset[] = [
468
468
  role: 'Two measurements read against each other — the one chart slide whose answer is the shape of a cloud, not a figure anyone can read off an axis.',
469
469
  layout: 'default',
470
470
  props: {},
471
- fill: 'The takeaway as the slide\'s heading, and `::scatter-chart` under it filling the frame — `data` rows of `{ x, y }`, plus `size` (scaled by `sizeRange`) for a third measure and `group` when the dots split into named sets, which is the only case where the legend says anything. In the props block every NON-STRING value is colon-prefixed — `:data:`, `:height:`, `:labels:`, `:size-range:` — while string props go bare (`x-label: \'Parking bays\'`, `legend-position: \'top\'`); drop the colon and the value arrives as a string, so an array plots nothing and `labels: false` reads as truthy. Set both `xLabel` and `yLabel` — neither axis of a scatter is self-evident — turn `:labels: true` on only when there are few enough dots to name, about six before they collide, and set `:height: 592`, which is what every full-width chart under a heading in the specimen deck uses; the 480 default is sized for a split\'s figure column and leaves the foot of the slide empty here.',
471
+ fill: 'The takeaway as the slide\'s heading, and `::scatter-chart` under it filling the frame — `data` rows of `{ x, y }`, plus `size` (scaled by `sizeRange`) for a third measure and `group` when the dots split into named sets, which is the only case where the legend says anything. In the props block every NON-STRING value is colon-prefixed — `:data:`, `:height:`, `:labels:`, `:size-range:` — while string props go bare (`x-label: \'Parking bays\'`, `legend-position: \'bottom-center\'`); drop the colon and the value arrives as a string, so an array plots nothing and `labels: false` reads as truthy. Set both `xLabel` and `yLabel` — neither axis of a scatter is self-evident — turn `:labels: true` on only when there are few enough dots to name, about six before they collide, and set `:height: 592`, which is what every full-width chart under a heading in the specimen deck uses; the 480 default is sized for a split\'s figure column and leaves the foot of the slide empty here.',
472
472
  use: 'A relationship between two measures — parking bays against trips, price against usage — where the point is whether the dots line up at all, and how tightly.',
473
473
  avoid: 'Anything measured over time: `trend` is that, and plotting (date, value) pairs as dots throws away the ordering a line\'s own segments carry, leaving the audience to re-sequence the cloud by eye. And do not move this into `trend`\'s `split` figure column — that column caps at 630px and narrows the x range, pressing the cloud into a blob, which is the one compression a correlation slide cannot survive.',
474
474
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@humanforest/slidev-theme",
3
- "version": "0.1.2",
3
+ "version": "0.2.0",
4
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
5
  "keywords": [
6
6
  "slidev-theme",
@@ -19,6 +19,7 @@
19
19
  "slidev": {
20
20
  "colorSchema": "light",
21
21
  "defaults": {
22
+ "comark": true,
22
23
  "transition": "view-transition",
23
24
  "aspectRatio": "16/9",
24
25
  "canvasWidth": 1920,
@@ -31,10 +32,10 @@
31
32
  }
32
33
  },
33
34
  "dependencies": {
34
- "@humanforest/fonts": "^0.1.2",
35
- "@humanforest/frames": "^0.1.2",
36
- "@humanforest/tokens": "^0.1.2",
37
- "@humanforest/ui": "^0.1.2",
35
+ "@humanforest/fonts": "^0.2.0",
36
+ "@humanforest/frames": "^0.2.0",
37
+ "@humanforest/tokens": "^0.2.0",
38
+ "@humanforest/ui": "^0.2.0",
38
39
  "@iconify-json/lucide": "^1.2.0",
39
40
  "@iconify-json/vscode-icons": "^1.2.75",
40
41
  "@unovis/ts": "^1.6.7",