@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
@@ -0,0 +1,133 @@
1
+ <script setup>
2
+ /*
3
+ A line chart sized for a room — one to five series over a shared category axis.
4
+
5
+ Built on @unovis/vue directly, which is the deck's whole chart stack: the marks are real Unovis
6
+ components, the ceremony is ChartFrame.vue, and the data shaping is chartData.ts. Colours come
7
+ from --deck-series-1…5, which alias the published categorical palette in the product's order, so
8
+ a deck line and a product line of the same data are the same chart.
9
+
10
+ A line CLAIMS the space between its points, so it is the right mark only when the x axis is a
11
+ real sequence — weeks, months, versions. For unordered categories the claim is false and the
12
+ chart wants bars.
13
+
14
+ Missing readings stay missing: chartData.ts never coerces a null to zero, and Unovis renders the
15
+ gap. A run of rows with no readings at all draws the empty state rather than a line along the
16
+ floor.
17
+ */
18
+ import { computed } from 'vue';
19
+ import { VisXYContainer, VisAxis, VisLine, VisCrosshair, VisTooltip } from '@unovis/vue';
20
+ import { deckTooltip, seriesTip } from './chartTip';
21
+ import { motionDuration } from './motionDuration';
22
+ import ChartFrame from './ChartFrame.vue';
23
+ import { chartState, formatValue, labelAt, stateMessage, tickIndices, CROSSHAIR_CIRCLE_R, CROSSHAIR_HIDE_WHEN_FAR } from './chartData';
24
+
25
+ const props = defineProps({
26
+ /** Rows: `[{ label, <seriesKey>: number, … }]`. `label` names the point on the x axis. */
27
+ data: { type: Array, required: true },
28
+ /** Series to plot, in legend order: `[{ key, name }]`. Omit and every numeric field on the first row becomes a series. */
29
+ series: { type: Array, default: null },
30
+ /** Plot height in px. 0 FILLS, and is the default: the chart takes whatever box it is given,
31
+ * which is what a chart under a heading wants. Unovis resolves `config.height || containerHeight`,
32
+ * so a falsy height makes the container measure itself. Pass a number where the box has no
33
+ * definite height of its own — a tiled grid cell, a figure beside copy. */
34
+ height: { type: Number, default: 0 },
35
+ /** Draw the series legend. */
36
+ legend: { type: Boolean, default: true },
37
+ /** Where the legend sits: `top-left`, `top-center`, `top-right`, `bottom-left`, `bottom-center`
38
+ * or `bottom-right`. The EDGE is reading order — above is read before the marks, below is a
39
+ * caption read after them; the ALIGNMENT is for the column the chart sits in, centred under a
40
+ * full-width plot and pulled to an edge where a centred key would float in a gutter. */
41
+ legendPosition: {
42
+ type: String,
43
+ default: 'bottom-center',
44
+ validator: (p) => ['top-left', 'top-center', 'top-right', 'bottom-left', 'bottom-center', 'bottom-right'].includes(p),
45
+ },
46
+ /** Title under the x axis. Omit when the labels already say what they are. */
47
+ xLabel: { type: String, default: null },
48
+ /** Title beside the y axis. Omit when the unit is in the slide's heading. */
49
+ yLabel: { type: String, default: null },
50
+ /** Formats the y tick labels. Defaults to British grouping, compact past four digits. */
51
+ format: { type: Function, default: formatValue },
52
+ /** Ceiling on x tick labels. Deck labels are 36px, so more than a handful collide. */
53
+ maxTicks: { type: Number, default: 6 },
54
+ /** Draw the two axes. Off is a SHAPE reading — the marks alone, no scale — for a chart that is
55
+ * being shown rather than read: a specimen, a register test, a sparkline beside a figure.
56
+ *
57
+ * ★ IT REMOVES THEM, IT DOES NOT HIDE THEM. Unovis reserves the axis band whether or not the
58
+ * axis paints, so hiding one in CSS leaves the plot inset by a tick row and a label column that
59
+ * are no longer there — measured at a 150px plot, the marks drew 22px of a 63px box. Not
60
+ * rendering the component is what gives the room back.
61
+ *
62
+ * ⚠ A chart carrying VALUES needs its scale. Turning this off says the reading is the shape. */
63
+ axes: { type: Boolean, default: true },
64
+ /** The sentence drawn when there is nothing to plot. */
65
+ empty: { type: String, default: 'No data' },
66
+ });
67
+
68
+ const state = computed(() => chartState(props.data, props.series));
69
+ const message = computed(() => (state.value.kind === 'ready' ? null : stateMessage(state.value, props.empty)));
70
+ const seriesList = computed(() => (state.value.kind === 'ready' ? state.value.series : []));
71
+
72
+ const x = (_d, i) => i;
73
+ const y = computed(() => seriesList.value.map((s) => (d) => d[s.key]));
74
+ const colors = computed(() => seriesList.value.map((s) => s.color));
75
+
76
+ const xTicks = computed(() => tickIndices(props.data?.length ?? 0, props.maxTicks));
77
+ const xFormat = (v) => labelAt(props.data ?? [], v);
78
+
79
+ /*
80
+ ★ THE CROSSHAIR CARRIES THE TOOLTIP, not the line. A line chart is read at an x position — every
81
+ series has a value there — so the hover target is the COLUMN, not the stroke. Hovering a 6px line
82
+ to a pixel is not something anyone does from a lectern, and a per-mark trigger would report one
83
+ series where the reader is comparing all of them.
84
+
85
+ The title falls out of the row itself: `forestTooltip` defaults it to the datum's first property,
86
+ and the deck's rows lead with `label`.
87
+ */
88
+ /* Read at mount so `prefers-reduced-motion` is honoured; see motionDuration.ts. */
89
+ const crosshairMs = motionDuration('instant');
90
+
91
+ const tip = computed(() => seriesTip(seriesList.value, props.format));
92
+ </script>
93
+
94
+ <template>
95
+ <ChartFrame
96
+ :series="seriesList"
97
+ :height="height"
98
+ :legend="legend"
99
+ :legend-position="legendPosition"
100
+ :message="message"
101
+ >
102
+ <VisXYContainer :data="data" :height="height" :duration="0" :margin="{ top: 16, right: 24, bottom: 0, left: 0 }">
103
+ <!-- 6px, not the library's 2: a 2px stroke is a hairline on a projector, and the line is the
104
+ whole message. -->
105
+ <VisLine :x="x" :y="y" :color="colors" :line-width="6" />
106
+ <VisAxis
107
+ v-if="axes"
108
+ type="x"
109
+ :grid-line="false"
110
+ :tick-line="false"
111
+ :tick-values="xTicks"
112
+ :tick-format="xFormat"
113
+ :tick-padding="16"
114
+ :label="xLabel"
115
+ :label-margin="24"
116
+ tick-text-hide-overlapping
117
+ />
118
+ <VisAxis
119
+ v-if="axes"
120
+ type="y"
121
+ :tick-format="format"
122
+ :num-ticks="4"
123
+ :tick-line="false"
124
+ :domain-line="false"
125
+ :tick-padding="16"
126
+ :label="yLabel"
127
+ :label-margin="24"
128
+ />
129
+ <VisCrosshair :duration="crosshairMs" :hideWhenFarFromPointer="CROSSHAIR_HIDE_WHEN_FAR" :circleRadius="CROSSHAIR_CIRCLE_R" :x="x" :y="y" :template="tip" :color="colors" />
130
+ <VisTooltip v-bind="deckTooltip()" />
131
+ </VisXYContainer>
132
+ </ChartFrame>
133
+ </template>
@@ -0,0 +1,203 @@
1
+ <script setup>
2
+ /*
3
+ The Forest identity, in the four forms the brand ships — and the reason this is a component
4
+ rather than an <img> in a layout is that two of them cannot be drawn the same way.
5
+
6
+ mark the rounded tile with the white tree, drawn as INLINE paths rather than the
7
+ generated mark.svg. Its four parts do not share one rule: the square is
8
+ forest-700 and the tree white, both brand constants; the RING is currentColor,
9
+ so it reverses with the register; the inner keeps forest-950. That is
10
+ packages/ui/src/logo/FLogo.vue's own split, part for part.
11
+ wordmark "forest" as outlines, shipped as a single dark fill. Painted straight it
12
+ dies on any dark ground — so it is drawn as a MASK and filled with
13
+ currentColor, which lets it reverse. The colour that fills it is the BRAND's
14
+ ink, pinned in the stylesheet below (forest-950, white on .deck-dark), not
15
+ whatever the surrounding slide is set in.
16
+ lockup mark + wordmark, composed HERE rather than taken from lockup-horizontal.svg.
17
+ That file is one flat SVG: masking it would flatten the mark's fixed green
18
+ along with the ink. Composing keeps the two halves on their own rules — the
19
+ mark's green is a brand constant, the ink follows the REGISTER.
20
+ lockup-vertical the same composition, stacked.
21
+
22
+ ★★ THE MARK USED TO BE AN <img src="mark.svg">, AND THAT MADE IT A ONE-REGISTER LOGO. Every fill in
23
+ that file is baked, so the ring stayed Darkest Forest on a dark slide where the product reverses it
24
+ to white — the wordmark beside it turned and the mark did not. The docs' own logo page states the
25
+ rule the deck was quietly breaking: "the mark stays green; the ink — wordmark AND OUTLINE — is
26
+ forest-950 on light and reverses to white on dark". Inline paths are what let the outline take
27
+ currentColor, which is the only reason this is markup rather than a file.
28
+
29
+ Geometry comes from the engine's own lockup constants, so a composed lockup is metrically identical
30
+ to the generated lockup-horizontal.svg / lockup-vertical.svg rather than approximating them.
31
+ */
32
+ import { computed } from 'vue'
33
+ import wordmarkUrl from '@humanforest/tokens/logos/wordmark.svg'
34
+ // The lockup GEOMETRY, from the engine rather than measured off the artwork. `@humanforest/tokens/logo`
35
+ // is a public export with zero imports — a pure geometry leaf, so taking it costs nothing and this
36
+ // component can no longer drift from the SVGs scripts/logo.ts generates from the same constants.
37
+ import { MARK, MARK_SIZE, WORDMARK_W, WORDMARK_H, LOCKUP_H, LOCKUP_V } from '@humanforest/tokens/logo'
38
+
39
+ const props = defineProps({
40
+ /** Which identity form to draw: the self-contained mark, the currentColor-masked wordmark, or a composed mark+wordmark lockup (horizontal or vertical). */
41
+ variant: {
42
+ type: String,
43
+ default: 'mark',
44
+ validator: (v) => ['mark', 'wordmark', 'lockup', 'lockup-vertical'].includes(v),
45
+ },
46
+ /* Height of the MARK in px, which is what sets the scale of every form. Named `size` rather
47
+ than `width` because the wordmark and the lockup have different widths at the same size. */
48
+ size: { type: Number, default: 160 },
49
+ })
50
+
51
+ /* Both gaps are the distance from the mark's edge to where the wordmark starts, taken from the
52
+ canonical lockups. They are NOT the same number — horizontal 104.03, vertical 61.32 — which is
53
+ why this was worth deriving: a single hand-copied gap made the vertical lockup 1.7x too airy.
54
+ The vertical lockup also CENTRES the mark over the wordmark (LOCKUP_V.mark[0] = 203.387 in an
55
+ 842-wide box, i.e. dead centre), where the horizontal one is baseline-ish and left-aligned. */
56
+ const GAP_H = LOCKUP_H.word[0] - MARK_SIZE
57
+ const GAP_V = LOCKUP_V.word[1] - MARK_SIZE
58
+
59
+ const unit = computed(() => props.size / MARK_SIZE)
60
+ const wordmarkWidth = computed(() => WORDMARK_W * unit.value)
61
+ const wordmarkHeight = computed(() => WORDMARK_H * unit.value)
62
+ const gap = computed(() => (props.variant === 'lockup-vertical' ? GAP_V : GAP_H) * unit.value)
63
+
64
+ /* The wordmark as a mask. background-color: currentColor is what the mask reveals, so the ink
65
+ is inherited rather than baked. */
66
+ const inkStyle = computed(() => ({
67
+ width: `${wordmarkWidth.value}px`,
68
+ height: `${wordmarkHeight.value}px`,
69
+ maskImage: `url(${wordmarkUrl})`,
70
+ WebkitMaskImage: `url(${wordmarkUrl})`,
71
+ maskSize: 'contain',
72
+ WebkitMaskSize: 'contain',
73
+ maskRepeat: 'no-repeat',
74
+ WebkitMaskRepeat: 'no-repeat',
75
+ maskPosition: 'center',
76
+ WebkitMaskPosition: 'center',
77
+ }))
78
+ </script>
79
+
80
+ <template>
81
+ <svg
82
+ v-if="variant === 'mark'"
83
+ :width="size"
84
+ :height="size"
85
+ :viewBox="`0 0 ${MARK_SIZE} ${MARK_SIZE}`"
86
+ fill="none"
87
+ xmlns="http://www.w3.org/2000/svg"
88
+ class="forest-logo-mark"
89
+ role="img"
90
+ aria-label="Forest"
91
+ >
92
+ <path :d="MARK.square" fill="var(--forest-700)" />
93
+ <path :d="MARK.ring" fill="currentColor" />
94
+ <path :d="MARK.tree" fill="white" />
95
+ <path :d="MARK.inner" fill="var(--forest-950)" />
96
+ </svg>
97
+
98
+ <span v-else-if="variant === 'wordmark'" class="forest-logo-ink" :style="inkStyle" role="img" aria-label="Forest" />
99
+
100
+ <span
101
+ v-else
102
+ class="forest-logo-lockup"
103
+ :class="variant === 'lockup-vertical' ? 'is-vertical' : 'is-horizontal'"
104
+ :style="{ gap: `${gap}px` }"
105
+ role="img"
106
+ aria-label="Forest"
107
+ >
108
+ <svg
109
+ :width="size"
110
+ :height="size"
111
+ :viewBox="`0 0 ${MARK_SIZE} ${MARK_SIZE}`"
112
+ fill="none"
113
+ xmlns="http://www.w3.org/2000/svg"
114
+ class="forest-logo-mark"
115
+ aria-hidden="true"
116
+ >
117
+ <path :d="MARK.square" fill="var(--forest-700)" />
118
+ <path :d="MARK.ring" fill="currentColor" />
119
+ <path :d="MARK.tree" fill="white" />
120
+ <path :d="MARK.inner" fill="var(--forest-950)" />
121
+ </svg>
122
+ <span class="forest-logo-ink" :style="inkStyle" />
123
+ </span>
124
+ </template>
125
+
126
+ <style scoped>
127
+ /*
128
+ ★★ `flex: none` ON BOTH HALVES — WITHOUT IT THE LOCKUP SILENTLY DEFORMS THE LOGOTYPE.
129
+
130
+ `.forest-logo-lockup` is an inline-flex, and a flex item shrinks by default. The mark is an <img>
131
+ with an intrinsic aspect ratio so it resists; the wordmark is a MASKED SPAN with no intrinsic size
132
+ at all, so it absorbs the entire shortfall on its own. MEASURED in a 400px cell at `size: 180`,
133
+ where the lockup's natural width is 571.6px: the mark stayed exactly 180px and the wordmark was
134
+ compressed from 348.6px to 176.9px — 49% narrower — while its HEIGHT stayed at 83.2px. Not scaled:
135
+ squashed. The letterforms were horizontally distorted, and "forest" beside the same mark rendered
136
+ visibly smaller than the vertical lockup's identical wordmark.
137
+
138
+ Nothing could catch it. Nothing overflowed, no contrast changed, and the component's own promise —
139
+ "metrically identical to the generated lockup-horizontal.svg rather than approximating them" — was
140
+ false in a way only the eye could see. Reported by Jose looking at a specimen slide: "why the
141
+ lockup have a smaller wordmark?"
142
+
143
+ With `flex: none` a lockup that does not fit OVERFLOWS instead, which is loud, catchable by
144
+ scripts/deckAudit.ts, and the correct direction to fail in: an identity that does not fit its box
145
+ is a layout to change, never a logotype to stretch. Same call as `font-synthesis-style: none` in
146
+ base.css, and the same fix BadgeStrip.vue already documents at length for its own pills.
147
+ */
148
+ /*
149
+ ── THE INK IS THE BRAND'S, NOT THE PAGE'S ─────────────────────────────────────────────────────
150
+
151
+ ★★ `currentColor` ON THE RING AND THE WORDMARK IS THE REVERSING MECHANISM, NOT THE COLOUR. Left
152
+ to inherit, both took whatever ink the slide happened to be set in — and on this deck's light
153
+ register that is `--deck-fg`, which is `--neutral-900`. A neutral near-black, which on cream reads
154
+ exactly like the artwork and is not the artwork.
155
+
156
+ The design system's own answer is unambiguous. scripts/logo.ts bakes the downloadable files
157
+ straight off the forest ramp:
158
+
159
+ const INK = 950, GREEN = 700; // wordmark + ring + inner = Darkest Forest; square = Dark Forest
160
+
161
+ So the ink is --forest-950 (#002A24) and the square --forest-700 (#046C4C) — and this file already
162
+ pinned the square and the tree's inner detail to exactly those, leaving only the two currentColor
163
+ paths to drift. They no longer do.
164
+
165
+ ★ THE REVERSE STAYS, because that is what currentColor was for: on `.deck-dark` the ink goes to
166
+ --neutral-0, which is the rule apps/docs' logo foundations page states and the one the specimen's
167
+ own Logo slides assert in prose ("forest-950 on the left and reverses to white on the right").
168
+ Pinning the light half is what makes that sentence true; it was measurably false before.
169
+
170
+ ⚠ THE CHROME IS NOT A DESCENDANT OF THE SLIDE, and it still gets this right. slide-top.vue puts
171
+ its own `.deck-dark` on `.deck-chrome-mark` per END rather than inheriting one — the fix it
172
+ documents at length for the pill — so the mark inside it resolves on the register it is drawn in.
173
+ */
174
+ .forest-logo-mark,
175
+ .forest-logo-ink,
176
+ .forest-logo-lockup {
177
+ color: var(--forest-950);
178
+ }
179
+ .deck-dark .forest-logo-mark,
180
+ .deck-dark .forest-logo-ink,
181
+ .deck-dark .forest-logo-lockup {
182
+ color: var(--neutral-0);
183
+ }
184
+ .forest-logo-mark {
185
+ display: block;
186
+ height: auto;
187
+ flex: none;
188
+ }
189
+ .forest-logo-ink {
190
+ display: block;
191
+ background-color: currentColor;
192
+ flex: none;
193
+ }
194
+ .forest-logo-lockup {
195
+ display: inline-flex;
196
+ align-items: center;
197
+ }
198
+ /* Centred, matching LOCKUP_V — the vertical lockup stacks the mark over the wordmark's midline. */
199
+ .forest-logo-lockup.is-vertical {
200
+ flex-direction: column;
201
+ align-items: center;
202
+ }
203
+ </style>