@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.
- package/LICENSE +67 -0
- package/README.md +577 -0
- package/assetUrl.js +38 -0
- package/components/Alert.vue +209 -0
- package/components/AreaBox.vue +152 -0
- package/components/AreaChart.vue +145 -0
- package/components/Avatar.vue +267 -0
- package/components/AvatarGroup.vue +94 -0
- package/components/BadgeStrip.vue +203 -0
- package/components/Bars.vue +207 -0
- package/components/BoroughShape.vue +127 -0
- package/components/BrowserFrame.vue +484 -0
- package/components/CalendarGrid.vue +480 -0
- package/components/Card.vue +70 -0
- package/components/Carousel.vue +266 -0
- package/components/ChartFrame.vue +426 -0
- package/components/ChordChart.vue +264 -0
- package/components/ComposedChart.vue +187 -0
- package/components/Contents.vue +86 -0
- package/components/DataTable.vue +244 -0
- package/components/DeckMap.vue +409 -0
- package/components/DeviceFrame.vue +331 -0
- package/components/DonutChart.vue +103 -0
- package/components/DottedMap.vue +126 -0
- package/components/Endorsement.vue +76 -0
- package/components/Grid.vue +169 -0
- package/components/GroupedBarChart.vue +141 -0
- package/components/IconTile.vue +116 -0
- package/components/Kpi.vue +188 -0
- package/components/LineChart.vue +133 -0
- package/components/Logo.vue +203 -0
- package/components/LondonMap.vue +348 -0
- package/components/MarkPair.vue +109 -0
- package/components/Marquee.vue +263 -0
- package/components/NestedDonutChart.vue +295 -0
- package/components/Placeholder.vue +88 -0
- package/components/SankeyChart.vue +223 -0
- package/components/ScatterChart.vue +142 -0
- package/components/StackedBarChart.vue +143 -0
- package/components/StatCard.vue +134 -0
- package/components/StatusTrack.vue +334 -0
- package/components/Timeline.vue +249 -0
- package/components/TimelineChart.vue +329 -0
- package/components/TreemapChart.vue +267 -0
- package/components/backgrounds.js +135 -0
- package/components/boroughGeometry.ts +59 -0
- package/components/chartData.ts +277 -0
- package/components/chartTip.ts +201 -0
- package/components/codeTheme.ts +239 -0
- package/components/deckPalette.ts +157 -0
- package/components/dottedMapGeometry.js +60 -0
- package/components/iconSlugs.js +55 -0
- package/components/iconUrl.js +51 -0
- package/components/mermaidTheme.ts +972 -0
- package/components/motionDuration.ts +41 -0
- package/components/tableRules.ts +257 -0
- package/confidential-mark.vue +151 -0
- package/custom-nav-controls.vue +79 -0
- package/endorsements/b-corp-black.svg +30 -0
- package/endorsements/b-corp-white.svg +30 -0
- package/global-top.vue +1662 -0
- package/icons/forest/bike-asterisk.svg +21 -0
- package/icons/forest/bike-down.svg +19 -0
- package/icons/forest/bike-off.svg +20 -0
- package/icons/forest/bike-up.svg +19 -0
- package/icons/forest/bike-x.svg +19 -0
- package/icons/forest/bike.svg +18 -0
- package/icons/forest/mark-fill.svg +6 -0
- package/icons/forest/mark.svg +6 -0
- package/icons/forest/parking.svg +3 -0
- package/icons/forest/star-fill.svg +4 -0
- package/icons/forest/star.svg +4 -0
- package/icons/forest/traffic-light-caution.svg +8 -0
- package/icons/forest/traffic-light-go.svg +8 -0
- package/icons/forest/traffic-light-lit.svg +8 -0
- package/icons/forest/traffic-light-stop.svg +8 -0
- package/icons/forest/traffic-light.svg +8 -0
- package/icons/lucide/battery.svg +1 -0
- package/icons/lucide/bike.svg +1 -0
- package/icons/lucide/chart-line.svg +1 -0
- package/icons/lucide/check.svg +1 -0
- package/icons/lucide/circle-check.svg +1 -0
- package/icons/lucide/clock.svg +1 -0
- package/icons/lucide/code.svg +1 -0
- package/icons/lucide/component.svg +1 -0
- package/icons/lucide/frame.svg +1 -0
- package/icons/lucide/layout-grid.svg +1 -0
- package/icons/lucide/map-pin.svg +1 -0
- package/icons/lucide/map.svg +1 -0
- package/icons/lucide/palette.svg +1 -0
- package/icons/lucide/table.svg +1 -0
- package/icons/lucide/triangle-alert.svg +1 -0
- package/icons/lucide/type.svg +1 -0
- package/icons/lucide/wrench.svg +1 -0
- package/layouts/bleed.vue +317 -0
- package/layouts/cover.vue +1514 -0
- package/layouts/default.vue +187 -0
- package/layouts/divider.vue +286 -0
- package/layouts/end.vue +384 -0
- package/layouts/quote.vue +218 -0
- package/layouts/split.vue +1875 -0
- package/layouts/stack.vue +944 -0
- package/layouts/statement.vue +67 -0
- package/manifest/chrome.ts +36 -0
- package/manifest/components.ts +49 -0
- package/manifest/index.ts +57 -0
- package/manifest/layouts.ts +37 -0
- package/manifest/manifest.json +6514 -0
- package/manifest/presets.ts +635 -0
- package/notch.js +458 -0
- package/notchPath.js +188 -0
- package/package.json +77 -0
- package/section.js +110 -0
- package/setup/mermaid-renderer.ts +2120 -0
- package/setup/mermaid.ts +30 -0
- package/setup/shiki.ts +44 -0
- package/setup/shortcuts.ts +28 -0
- package/slide-bottom.vue +305 -0
- package/slide-top.vue +268 -0
- package/styles/base.css +2611 -0
- package/styles/index.ts +71 -0
- package/styles/tokens.css +144 -0
- package/uno.config.ts +231 -0
- package/useNotchCuts.js +200 -0
|
@@ -0,0 +1,426 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
/*
|
|
3
|
+
The ceremony every deck chart shares: the room-scale Unovis type ramp, the legend, and the
|
|
4
|
+
empty state. The MARK never comes through here — each chart component mounts its own
|
|
5
|
+
@unovis/vue mark in the default slot, exactly the way packages/charts' FChartFrame keeps mark
|
|
6
|
+
props out of the frame. Wrap ceremony, never the chart.
|
|
7
|
+
|
|
8
|
+
★ THE TYPE RAMP IS THE POINT. Unovis ships 12px axis tick labels and a 9px legend bullet, which
|
|
9
|
+
are correct on a dashboard and invisible from the back of a room. The `--vis-*` block below
|
|
10
|
+
re-points every one of them at the deck type roles (--type-deck-*), so a chart on a 1920x1080
|
|
11
|
+
canvas carries 36px axis labels — the same tier Bars.vue already sets its category labels at, and
|
|
12
|
+
unconditionally WCAG large text, so the 3:1 floor is the one that governs the muted ink.
|
|
13
|
+
|
|
14
|
+
★★ DECLARED ON `.deck-chart`, NOT ON `:root`, and both halves of that matter.
|
|
15
|
+
|
|
16
|
+
1. Unovis injects its OWN `:root` defaults at RUNTIME, after this sheet has loaded, so a `:root`
|
|
17
|
+
block here loses on order (packages/tokens/dataviz/unovis.css fights the same battle with a
|
|
18
|
+
`:root:root` specificity bump). A declaration on the chart's own element sidesteps the
|
|
19
|
+
contest entirely: custom properties inherit, and a value declared ON the element always beats
|
|
20
|
+
one inherited from an ancestor, whatever either selector's specificity.
|
|
21
|
+
2. A custom property computes ONCE where it is declared. Declared at `:root`, every
|
|
22
|
+
`var(--deck-fg-muted)` below would freeze at the LIGHT value and a `class: deck-dark` slide
|
|
23
|
+
would draw light-mode axis ink on a dark ground — the same island trap deck.css's own derived
|
|
24
|
+
surfaces carry a three-selector fix for. Declared here, the reference resolves at a node that
|
|
25
|
+
is INSIDE the `.deck-dark` subtree, so it flips with the slide and no JS runs at all.
|
|
26
|
+
|
|
27
|
+
Colours reach the marks as `var()` REFERENCES rather than resolved values for the same reason:
|
|
28
|
+
CSS re-resolves them continuously, so a theme flip repaints with no remount, no repaint key and no
|
|
29
|
+
cached-palette bug to work around.
|
|
30
|
+
|
|
31
|
+
EVERY CHART CONTAINER SETS `:duration="0"` — the chart is finished the moment its slide is.
|
|
32
|
+
Slidev pre-renders neighbouring slides, so an entry animation runs while nobody is looking and is
|
|
33
|
+
already over (or worse, caught mid-flight) by the time the slide is on screen; and the deck's
|
|
34
|
+
reduced-motion floor suppresses it for part of the audience anyway, so the animated version is
|
|
35
|
+
never the one everyone sees. A chart that draws itself in is a dashboard idea.
|
|
36
|
+
|
|
37
|
+
★ THE GRID IS DELIBERATELY FINER THAN THE LABELS, and it is the library's decision, not a defect
|
|
38
|
+
to chase. @unovis/ts's Axis builds its grid from `this._getNumTicks() * 2` (components/axis/
|
|
39
|
+
index.js, `_buildGrid`), so four labelled ticks paint seven or eight grid lines — minor rules
|
|
40
|
+
between the labelled ones. Read back from the live DOM, the axis holds two sets of `.tick` nodes:
|
|
41
|
+
four carrying the labels with their tick MARK suppressed, and a second set with empty text whose
|
|
42
|
+
lines span the plot. The only way to make the two agree is to pass explicit `tickValues` (the same
|
|
43
|
+
`_buildGrid` prefers them over its doubled count when they are set), which means computing the
|
|
44
|
+
domain and a nice-number ladder here instead of letting the library scale itself. Not worth it:
|
|
45
|
+
the minor rules are pale (--deck-border) and give the eye a halfway reference. Recorded so the
|
|
46
|
+
next reader does not spend an afternoon hunting a bug that is not there.
|
|
47
|
+
*/
|
|
48
|
+
defineProps({
|
|
49
|
+
/** Resolved series for the legend — `[{ key, name, color }]`, as `chartData.ts` returns them. */
|
|
50
|
+
series: { type: Array, default: () => [] },
|
|
51
|
+
/**
|
|
52
|
+
* Plot height in px. The deck default fills a content column under a heading; a chart that owns a
|
|
53
|
+
* whole slide wants more.
|
|
54
|
+
*
|
|
55
|
+
* ★ 0 MEANS FILL — take whatever the parent gives, rather than a number the slide had to guess.
|
|
56
|
+
* The same convention DeckMap's own `height` already uses, and it is the library's behaviour
|
|
57
|
+
* rather than an invention: Unovis' container resolves `config.height || containerHeight`, so a
|
|
58
|
+
* falsy height makes it MEASURE its box, and it keeps a ResizeObserver on that box to re-fit.
|
|
59
|
+
*
|
|
60
|
+
* ⚠ THE CELL HAS TO HAVE A HEIGHT TO GIVE. A percentage resolves against a definite ancestor, so
|
|
61
|
+
* a frame in an auto-sized grid row fills nothing — put it in a row that stretches.
|
|
62
|
+
*/
|
|
63
|
+
height: { type: Number, default: 0 },
|
|
64
|
+
/** Draw the series legend. Off for a chart that labels its marks directly. */
|
|
65
|
+
legend: { type: Boolean, default: true },
|
|
66
|
+
/**
|
|
67
|
+
* Where the legend sits: one of `top-left`, `top-center`, `top-right`, `bottom-left`,
|
|
68
|
+
* `bottom-center`, `bottom-right`.
|
|
69
|
+
*
|
|
70
|
+
* Two axes in one name, because they are two different decisions. The EDGE is about reading
|
|
71
|
+
* order — a legend above is read before the marks and belongs to a chart whose colours need
|
|
72
|
+
* introducing; one below is a caption, read after the picture has been looked at. The ALIGNMENT
|
|
73
|
+
* is about the column the chart sits in: centred under a plot that fills the frame, and pulled to
|
|
74
|
+
* an edge when the chart shares a row with copy and a centred key would float in the gutter.
|
|
75
|
+
*
|
|
76
|
+
* `bottom-center` is the default because a caption is the common case.
|
|
77
|
+
*
|
|
78
|
+
* This replaced a two-value `top`/`bottom` enum in which the alignment was BAKED IN — `top` was
|
|
79
|
+
* always start-aligned and `bottom` always centred — so half of the six were unreachable and the
|
|
80
|
+
* two that existed could not be told apart from their own names.
|
|
81
|
+
*/
|
|
82
|
+
legendPosition: {
|
|
83
|
+
type: String,
|
|
84
|
+
default: 'bottom-center',
|
|
85
|
+
validator: (p) => ['top-left', 'top-center', 'top-right', 'bottom-left', 'bottom-center', 'bottom-right'].includes(p),
|
|
86
|
+
},
|
|
87
|
+
/** When set, the frame draws this sentence in place of the chart — the honest empty state. Null renders the mark. */
|
|
88
|
+
message: { type: String, default: null },
|
|
89
|
+
});
|
|
90
|
+
</script>
|
|
91
|
+
|
|
92
|
+
<template>
|
|
93
|
+
<div class="deck-chart" :class="{ 'is-fill': !height }">
|
|
94
|
+
<!-- The empty state is a real WELL, not a bare line of text: a chart that has nothing to say
|
|
95
|
+
should still occupy the space the chart would have, so a slide does not reflow around
|
|
96
|
+
missing data and an audience can see that something was meant to be there. -->
|
|
97
|
+
<div v-if="message" class="deck-chart-empty" :style="height ? { height: `${height}px` } : undefined">
|
|
98
|
+
{{ message }}
|
|
99
|
+
</div>
|
|
100
|
+
<template v-else>
|
|
101
|
+
<div class="deck-chart-plot">
|
|
102
|
+
<slot :height="height" />
|
|
103
|
+
</div>
|
|
104
|
+
<!-- The legend sits AFTER the plot in the DOM and is lifted by `order` when it belongs above.
|
|
105
|
+
Source order is the caption case, which is the default, so the common reading order is
|
|
106
|
+
the one that needs no reordering at all.
|
|
107
|
+
|
|
108
|
+
Two classes come out of one prop name: the edge decides source order, the alignment
|
|
109
|
+
decides the row. Split rather than six classes so the CSS below stays two small tables. -->
|
|
110
|
+
<div
|
|
111
|
+
v-if="legend && series.length"
|
|
112
|
+
class="deck-chart-legend"
|
|
113
|
+
:class="[
|
|
114
|
+
legendPosition.startsWith('top') ? 'is-top' : 'is-bottom',
|
|
115
|
+
`is-${legendPosition.slice(legendPosition.indexOf('-') + 1)}`,
|
|
116
|
+
]"
|
|
117
|
+
>
|
|
118
|
+
<!-- Hand-drawn rather than <VisBulletLegend>: the library's legend is a flex row of spans
|
|
119
|
+
sized off --vis-legend-*, which works, but a deck legend also has to survive being read
|
|
120
|
+
at distance and wrap cleanly inside a half-slide column. Owning the markup is what lets
|
|
121
|
+
the bullet be a real disc at deck scale and the label take the deck's own body tier. -->
|
|
122
|
+
<span v-for="s in series" :key="s.key" class="deck-chart-key">
|
|
123
|
+
<span class="deck-chart-bullet" :style="{ background: s.color }" />
|
|
124
|
+
{{ s.name }}
|
|
125
|
+
</span>
|
|
126
|
+
</div>
|
|
127
|
+
</template>
|
|
128
|
+
</div>
|
|
129
|
+
</template>
|
|
130
|
+
|
|
131
|
+
<style scoped>
|
|
132
|
+
.deck-chart {
|
|
133
|
+
display: flex;
|
|
134
|
+
flex-direction: column;
|
|
135
|
+
gap: var(--deck-space-3);
|
|
136
|
+
width: 100%;
|
|
137
|
+
|
|
138
|
+
/* ── The Unovis adapter, at room scale ──────────────────────────────────────────────────── */
|
|
139
|
+
--vis-font-family: var(--font-sans);
|
|
140
|
+
--vis-axis-font-family: var(--font-sans);
|
|
141
|
+
--vis-legend-font-family: var(--font-sans);
|
|
142
|
+
|
|
143
|
+
/* Series colours. SIX: --deck-series-1…6 alias the published categorical palette
|
|
144
|
+
in the product's order, and the palette's ceiling is six. Leaving --vis-color5 and up
|
|
145
|
+
undefined is deliberate — a chart that somehow reaches for a sixth gets Unovis' own off-brand
|
|
146
|
+
default rather than a Forest colour that was never designed, and chartData.ts's
|
|
147
|
+
`over-capacity` state stops it before it renders at all. */
|
|
148
|
+
--vis-color0: var(--deck-series-1);
|
|
149
|
+
--vis-color1: var(--deck-series-2);
|
|
150
|
+
--vis-color2: var(--deck-series-3);
|
|
151
|
+
--vis-color3: var(--deck-series-4);
|
|
152
|
+
--vis-color4: var(--deck-series-5);
|
|
153
|
+
--vis-color5: var(--deck-series-6);
|
|
154
|
+
|
|
155
|
+
/* Axis ink and rules. The grid is the deck's own border role, so it reads as structure rather
|
|
156
|
+
than as data — one step of ink on the page, no more. */
|
|
157
|
+
--vis-axis-grid-color: var(--deck-border);
|
|
158
|
+
--vis-axis-tick-color: var(--deck-border);
|
|
159
|
+
--vis-axis-domain-color: var(--deck-border-strong);
|
|
160
|
+
--vis-axis-grid-line-width: 2px;
|
|
161
|
+
--vis-axis-tick-line-width: 2px;
|
|
162
|
+
--vis-axis-domain-line-width: 2px;
|
|
163
|
+
|
|
164
|
+
/* Tick labels take the small body tier (36px) — the same tier Bars.vue's category labels use,
|
|
165
|
+
and the smallest thing on this deck that is still a sentence rather than a caption. */
|
|
166
|
+
--vis-axis-tick-label-color: var(--deck-fg-muted);
|
|
167
|
+
--vis-axis-tick-label-font-size: var(--type-deck-body-sm-size);
|
|
168
|
+
--vis-axis-tick-label-weight: var(--type-deck-body-sm-weight);
|
|
169
|
+
/* An axis TITLE is a label role: same size, bold — the two-weight system has nothing between. */
|
|
170
|
+
--vis-axis-label-color: var(--deck-fg);
|
|
171
|
+
--vis-axis-label-font-size: var(--type-deck-label-size);
|
|
172
|
+
--vis-axis-label-weight: var(--type-deck-label-weight);
|
|
173
|
+
|
|
174
|
+
/* The donut's centre. Title tier for the figure, small body for what it counts. */
|
|
175
|
+
--vis-donut-central-label-font-size: var(--type-deck-title-size);
|
|
176
|
+
--vis-donut-central-label-font-weight: var(--type-deck-title-weight);
|
|
177
|
+
--vis-donut-central-label-font-family: var(--font-sans);
|
|
178
|
+
--vis-donut-central-label-text-color: var(--deck-fg);
|
|
179
|
+
--vis-donut-central-sub-label-font-size: var(--type-deck-body-sm-size);
|
|
180
|
+
--vis-donut-central-sub-label-font-weight: var(--type-deck-body-sm-weight);
|
|
181
|
+
--vis-donut-central-sub-label-font-family: var(--font-sans);
|
|
182
|
+
--vis-donut-central-sub-label-text-color: var(--deck-fg-muted);
|
|
183
|
+
|
|
184
|
+
/*
|
|
185
|
+
★★ THESE WERE INERT. Every declaration below sat inside the `:deep([class*='-central-label'])`
|
|
186
|
+
rule further down, so they were declared on the central LABEL — a text node that is a sibling of
|
|
187
|
+
the marks, not an ancestor of them. Custom properties inherit downwards only, so the arcs, bars,
|
|
188
|
+
scatter points and areas never saw one of them and drew Unovis' own defaults for the whole life
|
|
189
|
+
of this file, while the comments beside them described a treatment nobody was getting.
|
|
190
|
+
|
|
191
|
+
READ BACK FROM THE LIVE DOM before the move, on the specimen's donut slide: the frame reported
|
|
192
|
+
an EMPTY `--vis-donut-segment-stroke-color`, the label reported the cream, and the arc itself
|
|
193
|
+
was stroked `oklab(0.782…)` at 2px — the library default, not `--deck-bg` at 4px. Scatter and
|
|
194
|
+
area read 1px and 0px against the 3px and 4px written here.
|
|
195
|
+
|
|
196
|
+
Nothing about the values changed in moving them; only where they are declared.
|
|
197
|
+
*/
|
|
198
|
+
|
|
199
|
+
/*
|
|
200
|
+
── THE CROSSHAIR ─────────────────────────────────────────────────────────────────────────────
|
|
201
|
+
|
|
202
|
+
The circle sits ON the series it reports, so it takes that series' colour as its fill and the
|
|
203
|
+
chart's own ground as its ring — the same treatment the donut segment gets, and the reason it
|
|
204
|
+
reads as a mark lifted off the line rather than a bead threaded onto it. Unovis rings it at 1px,
|
|
205
|
+
which at this size is a hairline around a 9px dot.
|
|
206
|
+
|
|
207
|
+
The line itself is a guide, not a mark: it takes the border's weight, not a series colour.
|
|
208
|
+
*/
|
|
209
|
+
--vis-crosshair-circle-stroke-color: var(--deck-bg);
|
|
210
|
+
--vis-crosshair-circle-stroke-width: 4;
|
|
211
|
+
--vis-crosshair-line-stroke-color: var(--deck-border-strong);
|
|
212
|
+
--vis-crosshair-line-stroke-width: 2;
|
|
213
|
+
|
|
214
|
+
/*
|
|
215
|
+
── THE TOOLTIP ───────────────────────────────────────────────────────────────────────────────
|
|
216
|
+
|
|
217
|
+
⚠ ITS VARIABLES ARE NOT HERE, and cannot be. The panel is mounted on `document.body`, so nothing
|
|
218
|
+
declared on this element reaches it — custom properties inherit downwards. They live on `:root`
|
|
219
|
+
in base.css, beside the note explaining why the body is the only container that can position a
|
|
220
|
+
tooltip correctly on a slide the deck scales.
|
|
221
|
+
*/
|
|
222
|
+
|
|
223
|
+
/* The track behind the arcs. Our slices always sum to the whole, so a visible track can only
|
|
224
|
+
ever surface in the gaps — where any opaque colour is wrong the moment the slide is dark. */
|
|
225
|
+
--vis-donut-background-color: transparent;
|
|
226
|
+
/* The arc separator is the SLIDE GROUND, so segments read as separated rather than outlined. */
|
|
227
|
+
--vis-donut-segment-stroke-color: var(--deck-bg);
|
|
228
|
+
--vis-donut-segment-stroke-width: 4px;
|
|
229
|
+
|
|
230
|
+
/* Marks. The stroke that lifts a bar off its neighbour is the ground, same reasoning as the
|
|
231
|
+
donut's; the scatter carries one too so overlapping points stay countable. */
|
|
232
|
+
--vis-stacked-bar-stroke-color: var(--deck-bg);
|
|
233
|
+
--vis-stacked-bar-stroke-width: 3px;
|
|
234
|
+
--vis-grouped-bar-stroke-color: var(--deck-bg);
|
|
235
|
+
--vis-grouped-bar-stroke-width: 0px;
|
|
236
|
+
--vis-scatter-stroke-color: var(--deck-bg);
|
|
237
|
+
--vis-scatter-stroke-width: 3px;
|
|
238
|
+
--vis-scatter-fill-opacity: 0.85;
|
|
239
|
+
/* Unovis paints a scatter's point labels in its OWN default #5b5f6d, which nothing here ever
|
|
240
|
+
re-pointed — the block above sets the mark's stroke, width and fill, not its label. Measured
|
|
241
|
+
against the well's own edge (--deck-border resolves to #dad5cf) that is 4.36:1, under the 4.5
|
|
242
|
+
floor a 12px/500 label is held to; --deck-fg-muted takes it to 5.32:1 there and 7.42:1 on the
|
|
243
|
+
plot's own ground. */
|
|
244
|
+
--vis-scatter-point-label-text-color-dark: var(--deck-fg-muted);
|
|
245
|
+
--vis-area-stroke-width: 4px;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/*
|
|
249
|
+
★ THE CENTRE BLOCK IS NOT CENTRED, and it cannot be from inside Unovis. The library places the
|
|
250
|
+
figure and its sub-label at dy -0.55em and +0.55em — of their OWN font sizes, which the deck
|
|
251
|
+
deliberately makes different (title 60px over body-sm 36px). Equal fractions of unequal sizes are
|
|
252
|
+
unequal offsets, so the pair hangs above the ring's true centre by half their difference.
|
|
253
|
+
|
|
254
|
+
MEASURED on apps/deck's donut slides, ring centre against the union of the two text
|
|
255
|
+
boxes: 16.8px high, or 0.7 of the 24px the two roles differ by. Written as that subtraction so the
|
|
256
|
+
correction follows the roles if either is retuned; the 0.7 is the empirical part, true at the one
|
|
257
|
+
size pair this deck ships. Residual after it: 1.9px, which is glyph-box noise rather than offset.
|
|
258
|
+
|
|
259
|
+
⚠ MEASURE THIS WITH THE FONTS LOADED. The first reading said 12px and was taken mid-swap, on
|
|
260
|
+
fallback metrics — correcting to it left the block visibly high and the second measurement
|
|
261
|
+
disagreed with the first by 40%.
|
|
262
|
+
|
|
263
|
+
Both lines carry a `-central-label` suffix (the prefix is an emotion hash and changes per build),
|
|
264
|
+
so the selector matches on the suffix and moves the pair together — correcting one would re-open
|
|
265
|
+
the gap between them.
|
|
266
|
+
*/
|
|
267
|
+
/*
|
|
268
|
+
★★ THE CHART DOCTRINE HAD A HOLE IN IT, AND THE HOLE WAS A CSS TRANSITION. Every container in this
|
|
269
|
+
theme sets :duration="0" and the comment at the top of this file says why — "the chart is finished
|
|
270
|
+
the moment its slide is". That prop governs Unovis' own data animation. It does not reach the
|
|
271
|
+
opacity transition Unovis puts on axis tick labels, so charts animated anyway, in the one way
|
|
272
|
+
nobody had thought to switch off.
|
|
273
|
+
|
|
274
|
+
What it looked like, and why it read as a bug rather than a flourish: Slidev mounts every slide, so
|
|
275
|
+
a chart is laid out long before it is shown — at 0x0. With no width every x tick collapses onto the
|
|
276
|
+
same point, Unovis' overlap-hider does exactly its job and keeps the FIRST label while hiding the
|
|
277
|
+
rest at opacity 0. Arrive on the slide, the container finally has width, the hider recomputes, and
|
|
278
|
+
the five it had hidden fade back in over ~436ms while the first one — never hidden, so nothing to
|
|
279
|
+
animate — is simply there. One label pops, the others drift in behind it. Measured on slide 82
|
|
280
|
+
arriving from 81: Camden opacity 1 at frame zero, the other five 0 -> 1 at 436ms.
|
|
281
|
+
|
|
282
|
+
A full page load hides this completely: the container has width from the start, every label begins
|
|
283
|
+
hidden, and they all fade together. It only appears when moving slide to slide, which is the only
|
|
284
|
+
way a deck is ever actually shown.
|
|
285
|
+
|
|
286
|
+
The transition goes, not the opacity — a label that genuinely overlaps at real width should still
|
|
287
|
+
be hidden, because that is the hider doing useful work. Only the drift between the two states is
|
|
288
|
+
removed, which is what :duration="0" was always meant to say.
|
|
289
|
+
|
|
290
|
+
Substring-matched on the emotion class for the reason the central-label rule below is: the hash in
|
|
291
|
+
css-6qjh0y-tick-label-hideable is generated and must never be written down.
|
|
292
|
+
*/
|
|
293
|
+
:deep([class*='-tick-label']) {
|
|
294
|
+
transition: none;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
:deep([class*='-central-label']) {
|
|
298
|
+
/* ★ `translate`, NOT `transform`. Unovis positions these texts with a transform ATTRIBUTE, and a
|
|
299
|
+
CSS `transform` property replaces it outright rather than composing — the label left the ring
|
|
300
|
+
entirely and landed at the plot's origin. `translate` is its own property and composes after. */
|
|
301
|
+
translate: 0 calc((var(--type-deck-title-size) - var(--type-deck-body-sm-size)) * 0.7);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
.deck-chart-plot {
|
|
305
|
+
width: 100%;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/*
|
|
309
|
+
★★ FILL MODE, AND IT IS THREE RULES BECAUSE THE HEIGHT HAS TO BE HANDED DOWN A CHAIN. The frame
|
|
310
|
+
takes the cell, the plot takes what the frame has left once the legend is laid out, and the
|
|
311
|
+
container Unovis renders takes the plot — a percentage stops at the first ancestor with no
|
|
312
|
+
definite height, so skipping any one of the three leaves the chart measuring zero and falling back
|
|
313
|
+
to the library's 150px default.
|
|
314
|
+
|
|
315
|
+
`min-height: 0` on the plot because it is a flex item: without it a flex child refuses to shrink
|
|
316
|
+
below its content, and a chart that overflows its cell pushes the legend off the frame instead of
|
|
317
|
+
giving up its own space.
|
|
318
|
+
*/
|
|
319
|
+
.deck-chart.is-fill {
|
|
320
|
+
height: 100%;
|
|
321
|
+
}
|
|
322
|
+
.deck-chart.is-fill .deck-chart-plot {
|
|
323
|
+
flex: 1;
|
|
324
|
+
min-height: 0;
|
|
325
|
+
}
|
|
326
|
+
.deck-chart.is-fill .deck-chart-plot > :deep(*),
|
|
327
|
+
.deck-chart.is-fill .deck-chart-empty {
|
|
328
|
+
height: 100%;
|
|
329
|
+
}
|
|
330
|
+
/*
|
|
331
|
+
★★ AND ONE STEP DEEPER FOR THE TWO MARKS THAT WRAP THEIR CONTAINER. Unovis reads
|
|
332
|
+
`config.height || containerHeight`, and containerHeight is the clientHeight of the element IT was
|
|
333
|
+
handed — so the chain has to reach that element, not merely the plot's first child. Ten of the
|
|
334
|
+
twelve charts put the container there directly; ChordChart and TimelineChart each wrap theirs in a
|
|
335
|
+
div of their own, so the wrapper took the 100% and the container inside it measured zero and fell
|
|
336
|
+
back to the library's 300px default: measured on the chord at a 559px plot, svg height="300",
|
|
337
|
+
unmoved by a window resize because nothing about its own box had changed.
|
|
338
|
+
|
|
339
|
+
Named rather than matched with a universal descendant: `:deep(*)` would also reach inside the SVG,
|
|
340
|
+
and a 100% height on a <g> is not nothing.
|
|
341
|
+
*/
|
|
342
|
+
.deck-chart.is-fill .deck-chart-plot > :deep(.deck-chord),
|
|
343
|
+
.deck-chart.is-fill .deck-chart-plot > :deep(.deck-timeline),
|
|
344
|
+
.deck-chart.is-fill .deck-chart-plot > :deep(.deck-chord) > *,
|
|
345
|
+
.deck-chart.is-fill .deck-chart-plot > :deep(.deck-timeline) > * {
|
|
346
|
+
height: 100%;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
.deck-chart-legend {
|
|
350
|
+
display: flex;
|
|
351
|
+
flex-wrap: wrap;
|
|
352
|
+
/* ROW gap space-1, column gap unchanged. Only the wrapped case is affected — a legend that fits on
|
|
353
|
+
one line never uses a row gap at all — and that is exactly the case that overflows: on
|
|
354
|
+
deck-template slide 40 four keys wrap 3 + 1, putting the second row's baseline 4px past the 96px
|
|
355
|
+
text inset. 8px buys twice the overrun.
|
|
356
|
+
|
|
357
|
+
★ NOT `.deck-chart`'s own gap, which is the obvious one-line fix and the wrong one. That gap is
|
|
358
|
+
the plot-to-legend rhythm LondonMap deliberately mirrors ("a map key and a chart key read as
|
|
359
|
+
the same furniture"), and a4fe8cd resized two maps rather than move it. Trimming it here would
|
|
360
|
+
undo that from the other side. This is the legend's own internal wrap spacing — a different
|
|
361
|
+
measurement that no other component copies. */
|
|
362
|
+
gap: var(--deck-space-1) var(--deck-space-5);
|
|
363
|
+
font-size: var(--type-deck-body-sm-size);
|
|
364
|
+
line-height: var(--type-deck-body-sm-line-height);
|
|
365
|
+
color: var(--deck-fg);
|
|
366
|
+
}
|
|
367
|
+
/* The EDGE. Source order is the caption case, so only `top` reorders — the legend sits after the
|
|
368
|
+
plot in the DOM, which is the reading order a screen reader gets either way. */
|
|
369
|
+
.deck-chart-legend.is-top {
|
|
370
|
+
order: -1;
|
|
371
|
+
}
|
|
372
|
+
/* The ALIGNMENT, as its own table. These used to be welded to the edge — `top` implied
|
|
373
|
+
start-aligned and `bottom` implied centred — which made four of the six arrangements
|
|
374
|
+
unreachable and the other two impossible to name honestly. */
|
|
375
|
+
.deck-chart-legend.is-left {
|
|
376
|
+
justify-content: flex-start;
|
|
377
|
+
}
|
|
378
|
+
.deck-chart-legend.is-center {
|
|
379
|
+
justify-content: center;
|
|
380
|
+
}
|
|
381
|
+
.deck-chart-legend.is-right {
|
|
382
|
+
justify-content: flex-end;
|
|
383
|
+
}
|
|
384
|
+
.deck-chart-key {
|
|
385
|
+
display: inline-flex;
|
|
386
|
+
align-items: center;
|
|
387
|
+
gap: var(--deck-space-2);
|
|
388
|
+
white-space: nowrap;
|
|
389
|
+
}
|
|
390
|
+
.deck-chart-bullet {
|
|
391
|
+
/* A real disc at reading distance. The library's 9px bullet is a speck beside 36px type. */
|
|
392
|
+
width: 0.6em;
|
|
393
|
+
height: 0.6em;
|
|
394
|
+
border-radius: var(--deck-radius-full);
|
|
395
|
+
flex: none;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
.deck-chart-empty {
|
|
399
|
+
display: flex;
|
|
400
|
+
align-items: center;
|
|
401
|
+
justify-content: center;
|
|
402
|
+
text-align: center;
|
|
403
|
+
padding: var(--deck-space-5);
|
|
404
|
+
background: var(--deck-surface);
|
|
405
|
+
border-radius: var(--deck-radius-nested);
|
|
406
|
+
color: var(--deck-fg-muted);
|
|
407
|
+
font-size: var(--type-deck-body-size);
|
|
408
|
+
line-height: var(--type-deck-body-line-height);
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
/*
|
|
412
|
+
★ THE TOOLTIP'S SWATCH IS THE ONE THING IN THE HOUSE TEMPLATE THAT IS NOT A CUSTOM PROPERTY.
|
|
413
|
+
Every size, colour and padding in `forestTipFrame` is themeable — the dot's 8px width and height
|
|
414
|
+
are written inline, and only its border-radius is a variable. Eight pixels beside 36px type is a
|
|
415
|
+
speck, so it is matched to the crosshair's own circle instead.
|
|
416
|
+
|
|
417
|
+
Selected by the `aspect-ratio` in that inline style, which is the stable half of the contract: the
|
|
418
|
+
swatch is the only element the template gives one to. `!important` is required because the value
|
|
419
|
+
it overrides is inline, which no stylesheet rule outranks by specificity or source order.
|
|
420
|
+
*/
|
|
421
|
+
:deep([class*='tooltip'] span[style*='aspect-ratio']) {
|
|
422
|
+
width: 18px !important;
|
|
423
|
+
height: 18px !important;
|
|
424
|
+
border-radius: 9px !important;
|
|
425
|
+
}
|
|
426
|
+
</style>
|