@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,41 @@
|
|
|
1
|
+
/*
|
|
2
|
+
A `--motion-*` token as the millisecond NUMBER Unovis' `duration` config takes.
|
|
3
|
+
|
|
4
|
+
Unovis animates in JavaScript — d3 transitions, not CSS — so a chart cannot reference the ladder
|
|
5
|
+
the way base.css does. It needs a number, and the only honest number is the one the ladder is
|
|
6
|
+
currently publishing.
|
|
7
|
+
|
|
8
|
+
★ THE LADDER JSON IS THE FALLBACK, NOT A COPY. `@humanforest/tokens` exports
|
|
9
|
+
`./motion/motion.json` outright, so nothing here restates a duration and nothing can drift from
|
|
10
|
+
the generated source. (packages/charts/src/motionDuration.ts — the product's version of this
|
|
11
|
+
file — keeps a PINNED COPY instead, and its own note says why: it is mirrored into the Nuxt layer
|
|
12
|
+
by scripts/layer.ts, whose self-containment guard rejects a relative climb into a tree the layer
|
|
13
|
+
does not carry. This package is not mirrored, so it can simply import.)
|
|
14
|
+
|
|
15
|
+
The live computed style is read first regardless, so a deck that re-points the tokens moves its
|
|
16
|
+
chart timing with them; the JSON answers only where there is no document to ask (SSR, node tests).
|
|
17
|
+
|
|
18
|
+
★ REDUCED MOTION RESOLVES TO 0, unconditionally. The system-wide floor in motion.css works by
|
|
19
|
+
narrowing `transition-property` to an allow-list, which reaches CSS transitions and cannot reach a
|
|
20
|
+
d3 transition at all. Travel is not on that allow-list in either case, so a crosshair honouring
|
|
21
|
+
the floor is a crosshair that does not travel.
|
|
22
|
+
|
|
23
|
+
This is deliberately NOT in chartData.ts: that file is pure by contract — no Vue, no DOM, no
|
|
24
|
+
Unovis — which is what makes the package's node-only vitest run possible.
|
|
25
|
+
*/
|
|
26
|
+
import MOTION from '@humanforest/tokens/motion/motion.json';
|
|
27
|
+
|
|
28
|
+
export type MotionToken = 'instant' | 'fast' | 'base' | 'slow';
|
|
29
|
+
|
|
30
|
+
export function motionDuration(token: MotionToken = 'instant'): number {
|
|
31
|
+
const fallback = MOTION.durations[token].ms;
|
|
32
|
+
if (typeof window === 'undefined' || typeof document === 'undefined') return fallback;
|
|
33
|
+
if (window.matchMedia?.('(prefers-reduced-motion: reduce)').matches) return 0;
|
|
34
|
+
const raw = getComputedStyle(document.documentElement).getPropertyValue(`--motion-${token}`).trim();
|
|
35
|
+
const ms = raw.endsWith('ms')
|
|
36
|
+
? Number.parseFloat(raw)
|
|
37
|
+
: raw.endsWith('s')
|
|
38
|
+
? Number.parseFloat(raw) * 1000
|
|
39
|
+
: Number.parseFloat(raw);
|
|
40
|
+
return Number.isFinite(ms) ? ms : fallback;
|
|
41
|
+
}
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
/*
|
|
2
|
+
// ★ NAMED tableRules, NOT dataTable, and the rename is load-bearing. unplugin-vue-components scans
|
|
3
|
+
// this directory and registers EVERY file in it as a component by filename — so `dataTable.ts` and
|
|
4
|
+
// `DataTable.vue` both claimed the name "DataTable", and the build resolved it by silently dropping
|
|
5
|
+
// one: "component \"DataTable\"(components/dataTable.ts) has naming conflicts with other
|
|
6
|
+
// components, ignored." It happened to drop the right one, which is the only reason the table ever
|
|
7
|
+
// rendered. A pure module beside its own component must not share its name.
|
|
8
|
+
The pure half of the deck's data table — every decision DataTable.vue makes about what it was
|
|
9
|
+
handed, as plain functions over plain data.
|
|
10
|
+
|
|
11
|
+
Split out for the same reason components/chartData.ts is: a slide is only ever proved right by
|
|
12
|
+
rendering it, so the rules that CAN be proved without a browser are kept where a node test can
|
|
13
|
+
reach them. Nothing here imports Vue.
|
|
14
|
+
|
|
15
|
+
── THE TWO CEILINGS, AND WHY THEY DEGRADE DIFFERENTLY ─────────────────────────────────────────
|
|
16
|
+
A table on a slide is read across a room in a few seconds, not scrolled. Both axes therefore have
|
|
17
|
+
a limit, but they are not the same KIND of limit, so they must not degrade the same way:
|
|
18
|
+
|
|
19
|
+
ROWS are homogeneous instances of one thing. Showing the first N and saying so is a real,
|
|
20
|
+
honest figure — a top-five table is a legitimate thing to put on a slide. So rows TRUNCATE, and
|
|
21
|
+
truncationNotice() states the cut in words on the slide itself. Never a silent slice.
|
|
22
|
+
|
|
23
|
+
COLUMNS are heterogeneous facts. Dropping one does not shorten the table, it changes what the
|
|
24
|
+
table CLAIMS — "revenue by borough" silently becomes "revenue", and nobody in the room can tell.
|
|
25
|
+
So columns REFUSE: past the ceiling the component draws a sentence instead of a table, the same
|
|
26
|
+
honest-degradation shape chartData.ts's `over-capacity` state already uses for a sixth series.
|
|
27
|
+
|
|
28
|
+
BOTH NUMBERS ARE MEASURED — rendered at 1920x1080 and read off the DOM, not derived on paper.
|
|
29
|
+
Deck body-sm is 36px at 1.5 line-height with --deck-space-3 (24px) above and below, and a row
|
|
30
|
+
comes out at 104px including its rule. The header row is 68px and the caption 74px. The safe area
|
|
31
|
+
runs from --deck-inset-text (90px) to 990px, and an h1 plus its margin pushes the first row down
|
|
32
|
+
to y=307.
|
|
33
|
+
|
|
34
|
+
with a heading 307 + 68 + n*104 + 74 <= 990 -> n <= 5 -> DEFAULT_MAX_ROWS
|
|
35
|
+
no heading 90 + 68 + n*104 + 74 <= 990 -> n <= 7 -> ROW_CEILING
|
|
36
|
+
|
|
37
|
+
The first pass of this component defaulted to 6 and capped at 8, both off paper arithmetic. The
|
|
38
|
+
render disagreed: scripts/deckAudit.ts caught the source line 30px past the bottom inset on a
|
|
39
|
+
five-row table that also carried a truncation notice. The notice is why the caption is ONE
|
|
40
|
+
wrapping line rather than two — see DataTable.vue.
|
|
41
|
+
*/
|
|
42
|
+
import { formatMetric } from '@humanforest/ui/kpi/format';
|
|
43
|
+
|
|
44
|
+
/** One cell's raw value, as an author writes it in YAML or a template. */
|
|
45
|
+
export type Cell = string | number | null | undefined;
|
|
46
|
+
|
|
47
|
+
/** One row: a plain object keyed by column key. */
|
|
48
|
+
export type Row = Record<string, Cell>;
|
|
49
|
+
|
|
50
|
+
/** Which edge a column's content sits against. */
|
|
51
|
+
export type Align = 'start' | 'center' | 'end';
|
|
52
|
+
|
|
53
|
+
/** How a numeric column is notated — the same two names Kpi.vue's own `format` prop takes. */
|
|
54
|
+
export type Notation = 'full' | 'compact';
|
|
55
|
+
|
|
56
|
+
/** A column as an author declares it: everything but `key` is optional. */
|
|
57
|
+
export type Column = {
|
|
58
|
+
key: string;
|
|
59
|
+
label?: string;
|
|
60
|
+
align?: Align;
|
|
61
|
+
numeric?: boolean;
|
|
62
|
+
format?: Notation;
|
|
63
|
+
precision?: number;
|
|
64
|
+
unit?: string;
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
/** A column after every optional field has been decided. What the template actually reads. */
|
|
68
|
+
export type ResolvedColumn = {
|
|
69
|
+
key: string;
|
|
70
|
+
label: string;
|
|
71
|
+
align: Align;
|
|
72
|
+
numeric: boolean;
|
|
73
|
+
format: Notation;
|
|
74
|
+
precision: number | undefined;
|
|
75
|
+
unit: string;
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
/** Past this many columns the table refuses to draw. See the header for why columns never truncate. */
|
|
79
|
+
export const COLUMN_CEILING = 5;
|
|
80
|
+
|
|
81
|
+
/** The most rows a slide can hold at deck type size, and only on a slide with no heading at all. */
|
|
82
|
+
export const ROW_CEILING = 7;
|
|
83
|
+
|
|
84
|
+
/** DataTable.vue's own `maxRows` default — the most that fits UNDER A HEADING, which is the normal
|
|
85
|
+
* case. Its literal is gated against this constant by the tests. */
|
|
86
|
+
export const DEFAULT_MAX_ROWS = 5;
|
|
87
|
+
|
|
88
|
+
/** Drawn in place of a cell with nothing in it — the same em dash Kpi.vue renders for a null figure. */
|
|
89
|
+
export const NO_VALUE = '—';
|
|
90
|
+
|
|
91
|
+
/** True for a value that can be measured: a real, finite number. `'12'` is text, not a figure. */
|
|
92
|
+
export const isFigure = (value: Cell): value is number => typeof value === 'number' && Number.isFinite(value);
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* A column key turned into a human label, for the case where no `columns` were declared at all and
|
|
96
|
+
* the keys are all there is to name them by. Splits camelCase, snake_case and kebab-case, then
|
|
97
|
+
* sentence-cases the result: `tripsPerBike` reads as "Trips per bike".
|
|
98
|
+
*
|
|
99
|
+
* Deliberately plain: an author who wants "Trips / bike" writes `label` and this never runs. It
|
|
100
|
+
* exists so the zero-config path produces a header a room can read rather than a variable name.
|
|
101
|
+
*/
|
|
102
|
+
export const humaniseKey = (key: string): string => {
|
|
103
|
+
const words = String(key)
|
|
104
|
+
.replace(/[_-]+/g, ' ')
|
|
105
|
+
.replace(/([a-z0-9])([A-Z])/g, '$1 $2')
|
|
106
|
+
.replace(/\s+/g, ' ')
|
|
107
|
+
.trim()
|
|
108
|
+
.toLowerCase();
|
|
109
|
+
return words ? words.charAt(0).toUpperCase() + words.slice(1) : String(key);
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Whether a column carries figures, decided from the DATA rather than from a declaration: at least
|
|
114
|
+
* one real number, and no non-empty value that is anything else. A column of `'128,400'` strings is
|
|
115
|
+
* text — already formatted by hand, nothing left to align on — which is why an author who wants the
|
|
116
|
+
* tabular treatment on pre-formatted values sets `numeric: true` explicitly and overrides this.
|
|
117
|
+
*/
|
|
118
|
+
export const columnIsNumeric = (rows: readonly Row[], key: string): boolean => {
|
|
119
|
+
let sawFigure = false;
|
|
120
|
+
for (const row of rows) {
|
|
121
|
+
const value = row?.[key];
|
|
122
|
+
if (value === null || value === undefined || value === '') continue;
|
|
123
|
+
if (!isFigure(value)) return false;
|
|
124
|
+
sawFigure = true;
|
|
125
|
+
}
|
|
126
|
+
return sawFigure;
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
/** Every key any row carries, in first-appearance order — the zero-config column list. */
|
|
130
|
+
export const keysOf = (rows: readonly Row[]): string[] => {
|
|
131
|
+
const seen: string[] = [];
|
|
132
|
+
for (const row of rows) {
|
|
133
|
+
for (const key of Object.keys(row ?? {})) if (!seen.includes(key)) seen.push(key);
|
|
134
|
+
}
|
|
135
|
+
return seen;
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Fills in every optional field of every column. `align` follows the data when it is not declared —
|
|
140
|
+
* figures sit against the end edge so their digits line up under one another, text against the
|
|
141
|
+
* start edge — because an author who has already said "this column is a number" should not have to
|
|
142
|
+
* say "so right-align it" as well.
|
|
143
|
+
*/
|
|
144
|
+
export const resolveColumns = (
|
|
145
|
+
rows: readonly Row[],
|
|
146
|
+
columns: readonly Column[] | null | undefined,
|
|
147
|
+
fallbackFormat: Notation = 'full',
|
|
148
|
+
): ResolvedColumn[] => {
|
|
149
|
+
const declared = Array.isArray(columns) && columns.length > 0
|
|
150
|
+
? columns.filter((c) => c && typeof c.key === 'string' && c.key !== '')
|
|
151
|
+
: keysOf(rows).map((key) => ({ key }));
|
|
152
|
+
|
|
153
|
+
return declared.map((column) => {
|
|
154
|
+
const numeric = typeof column.numeric === 'boolean' ? column.numeric : columnIsNumeric(rows, column.key);
|
|
155
|
+
return {
|
|
156
|
+
key: column.key,
|
|
157
|
+
label: typeof column.label === 'string' && column.label !== '' ? column.label : humaniseKey(column.key),
|
|
158
|
+
align: column.align ?? (numeric ? 'end' : 'start'),
|
|
159
|
+
numeric,
|
|
160
|
+
format: column.format ?? fallbackFormat,
|
|
161
|
+
precision: column.precision,
|
|
162
|
+
unit: typeof column.unit === 'string' ? column.unit : '',
|
|
163
|
+
};
|
|
164
|
+
});
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* `maxRows` as the component actually applies it: a whole number in 1…ROW_CEILING, never zero.
|
|
169
|
+
*
|
|
170
|
+
* `null`/`undefined` fall back to the DEFAULT rather than being clamped — `Number(null)` is 0,
|
|
171
|
+
* which is finite, so a bare Number() coercion would silently turn "not set" into "show one row".
|
|
172
|
+
*/
|
|
173
|
+
export const clampMaxRows = (maxRows: number | null | undefined): number => {
|
|
174
|
+
if (maxRows === null || maxRows === undefined) return DEFAULT_MAX_ROWS;
|
|
175
|
+
const n = Number(maxRows);
|
|
176
|
+
if (!Number.isFinite(n)) return DEFAULT_MAX_ROWS;
|
|
177
|
+
return Math.min(ROW_CEILING, Math.max(1, Math.floor(n)));
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
/** What the table should do with what it was handed. Mirrors chartData.ts's own `ChartState`. */
|
|
181
|
+
export type TableState =
|
|
182
|
+
| { kind: 'ready'; columns: ResolvedColumn[]; rows: Row[]; total: number; hidden: number }
|
|
183
|
+
| { kind: 'empty'; reason: 'no-rows' | 'no-columns' }
|
|
184
|
+
| { kind: 'over-capacity'; count: number };
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* The honest-degradation decision, in one pure function so the component and the tests read the
|
|
188
|
+
* same rules.
|
|
189
|
+
*
|
|
190
|
+
* no-rows nothing was passed, or an empty array.
|
|
191
|
+
* no-columns rows exist but carry no keys at all to draw as columns.
|
|
192
|
+
* over-capacity more columns than a table read across a room can carry. See the header.
|
|
193
|
+
* ready `rows` is what gets drawn and `hidden` is what did not fit — a caller that
|
|
194
|
+
* ignores `hidden` is dropping rows silently, which is the thing this exists to
|
|
195
|
+
* stop, so truncationNotice() below turns it into a sentence on the slide.
|
|
196
|
+
*/
|
|
197
|
+
export const tableState = (
|
|
198
|
+
rows: readonly Row[] | null | undefined,
|
|
199
|
+
columns: readonly Column[] | null | undefined,
|
|
200
|
+
maxRows: number | null | undefined,
|
|
201
|
+
fallbackFormat: Notation = 'full',
|
|
202
|
+
): TableState => {
|
|
203
|
+
const all = Array.isArray(rows) ? rows.filter((r) => r && typeof r === 'object') : [];
|
|
204
|
+
if (all.length === 0) return { kind: 'empty', reason: 'no-rows' };
|
|
205
|
+
|
|
206
|
+
const resolved = resolveColumns(all, columns, fallbackFormat);
|
|
207
|
+
if (resolved.length === 0) return { kind: 'empty', reason: 'no-columns' };
|
|
208
|
+
if (resolved.length > COLUMN_CEILING) return { kind: 'over-capacity', count: resolved.length };
|
|
209
|
+
|
|
210
|
+
const limit = clampMaxRows(maxRows);
|
|
211
|
+
const shown = all.slice(0, limit);
|
|
212
|
+
return { kind: 'ready', columns: resolved, rows: shown, total: all.length, hidden: all.length - shown.length };
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
/** The sentence the component prints instead of a table, for every state that is not ready. */
|
|
216
|
+
export const tableMessage = (state: TableState, empty: string): string => {
|
|
217
|
+
if (state.kind === 'over-capacity') {
|
|
218
|
+
return `${state.count} columns — a table read across a room carries ${COLUMN_CEILING}`;
|
|
219
|
+
}
|
|
220
|
+
return empty;
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* The truncation sentence, or null when nothing was withheld. Rendered on the slide, never in a
|
|
225
|
+
* console warning nobody presenting will ever see: a table that shows five of twelve rows and does
|
|
226
|
+
* not say so is a table that lies about its own subject.
|
|
227
|
+
*/
|
|
228
|
+
export const truncationNotice = (total: number, shown: number): string | null => {
|
|
229
|
+
const hidden = total - shown;
|
|
230
|
+
if (!Number.isFinite(hidden) || hidden <= 0) return null;
|
|
231
|
+
return `Showing ${shown} of ${total} rows — ${hidden} not shown`;
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* One cell, split into the figure and its unit so the template can set them at different sizes the
|
|
236
|
+
* way Kpi.vue already does. `unit` is only ever non-empty for a real figure — a unit hung off a
|
|
237
|
+
* text cell would be an author's mistake rendered as fact.
|
|
238
|
+
*
|
|
239
|
+
* Numbers go through formatMetric(), the same function Kpi.vue formats with, reached by relative
|
|
240
|
+
* path for the same reason: @humanforest/ui's own entry point drags in Nuxt UI, which does not
|
|
241
|
+
* coexist with the UnoCSS pipeline a deck runs on.
|
|
242
|
+
*/
|
|
243
|
+
export const cellParts = (
|
|
244
|
+
value: Cell,
|
|
245
|
+
column: ResolvedColumn,
|
|
246
|
+
locale = 'en-GB',
|
|
247
|
+
): { text: string; unit: string } => {
|
|
248
|
+
if (value === null || value === undefined || value === '') return { text: NO_VALUE, unit: '' };
|
|
249
|
+
if (typeof value === 'number') {
|
|
250
|
+
if (!Number.isFinite(value)) return { text: NO_VALUE, unit: '' };
|
|
251
|
+
return {
|
|
252
|
+
text: formatMetric(value, { locale, format: column.format, precision: column.precision }),
|
|
253
|
+
unit: column.unit,
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
return { text: String(value), unit: '' };
|
|
257
|
+
};
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
/*
|
|
3
|
+
The standing confidentiality marking — a stroked, unfilled pill in the slide's own quiet ink.
|
|
4
|
+
|
|
5
|
+
★★ IT IS NOT A BADGE, AND IT LIVED AS ONE FOR TOO LONG. This was `BadgeStrip variant="outline"`,
|
|
6
|
+
and it broke every rule that component otherwise holds: no background pair (it paints --deck-fg-subtle
|
|
7
|
+
directly), no uppercase, no overline tracking, its own smaller size, and a deliberate exemption
|
|
8
|
+
from the contrast floor. BadgeStrip's template carried a ternary that SKIPPED the background binding for
|
|
9
|
+
this one variant — a trap for anyone adding a third — and its `variant` enum advertised a choice
|
|
10
|
+
that had exactly one caller. Two things wearing one name.
|
|
11
|
+
|
|
12
|
+
What it actually is: a legal marking that rides EVERY slide via `defaults:`, so it meets both
|
|
13
|
+
registers, has to be present rather than read, and must never compete with the section pill it
|
|
14
|
+
sits opposite.
|
|
15
|
+
|
|
16
|
+
★ THE INK IS --deck-fg-subtle, NOT A NAMED BACKGROUND, and that is forced rather than chosen. A
|
|
17
|
+
pinned pair cannot do this job: `accent-dark` IS --forest-950, which is also .deck-dark's own
|
|
18
|
+
ground, so a pill stroked in it would be an invisible outline on every dark slide in the deck.
|
|
19
|
+
--deck-fg-subtle is the quietest ink role that re-points per register, and the stroke takes
|
|
20
|
+
currentColor so ink and border dim together — the pill never ends up a dark outline around pale
|
|
21
|
+
text. Muted was tried first and still read as content.
|
|
22
|
+
|
|
23
|
+
★ QUIET BY TREATMENT, NOT BY BEING UNDER THE FLOOR. The marking clears the reading floor on both
|
|
24
|
+
grounds it meets in this deck — 4.55:1 on warm-50 and 10.62:1 on forest-950 — and is held back by
|
|
25
|
+
its SIZE and its unfilled box rather than by its ink: a 1px stroke at 0.6 of the overline size
|
|
26
|
+
reads as a legal marking without being illegible. The audit scores it like any other text.
|
|
27
|
+
|
|
28
|
+
★★ BUT QUIET IS A RATIO, NOT A TOKEN, AND --deck-fg-subtle IS NOT ONE. It is --neutral-500 on BOTH
|
|
29
|
+
registers — the role does not re-point — so the same declaration lands at two different strengths:
|
|
30
|
+
MEASURED, 4.55:1 on the light ground (warm-50) and 3.25:1 on the dark one (forest-950). The mark
|
|
31
|
+
was a third weaker on exactly the slides where it is hardest to see, and nothing chose that.
|
|
32
|
+
|
|
33
|
+
On the dark register it is mixed from the two roles that ARE re-pointed there — --deck-fg-muted
|
|
34
|
+
(neutral-400) carried 60% toward --deck-fg (neutral-0).
|
|
35
|
+
|
|
36
|
+
★★ AND 60 IS SET BY THE WORST DARK GROUND, NOT THE COMMONEST ONE. "Dark" is not one colour here: a
|
|
37
|
+
slide can be the deck's own register (forest-950) or a named pair's dark half, and the loudest of
|
|
38
|
+
those is `vivid` at forest-700 — 2.4x the luminance of the darkest. One pinned ink cannot read the
|
|
39
|
+
same on both, so the rule is a FLOOR rather than a match: the marking is at least as legible on the
|
|
40
|
+
worst dark ground as it is on paper. Measured across every dark ground the theme offers:
|
|
41
|
+
|
|
42
|
+
mix forest-700 warm-700 neutral-800 forest-950
|
|
43
|
+
40% 3.72 4.04 8.67 8.92
|
|
44
|
+
60% 4.43 4.81 10.33 10.62
|
|
45
|
+
|
|
46
|
+
4.43 against paper's 4.55, and higher everywhere the ground is darker — which is the right
|
|
47
|
+
direction for a 1px stroke, as below.
|
|
48
|
+
|
|
49
|
+
⚠ THE forest-700 CELL IS UNDER THE 4.5 FLOOR, and now that the audit resolves this pill's ground
|
|
50
|
+
correctly it is enforced rather than theoretical: a slide pairing `confidential:` with a vivid
|
|
51
|
+
dark half would fail at 4.43:1. No slide in the specimen does. The lever when one does is the mix,
|
|
52
|
+
not the audit. (The 60% row is sRGB — the earlier figures here were P3 and read ~0.1 higher.)
|
|
53
|
+
|
|
54
|
+
★ WELL ABOVE THE LIGHT HALF'S 4.55, AND THAT IS THE POINT RATHER THAN AN OVERSHOOT. This pill is a
|
|
55
|
+
1px stroke of its own colour at 0.6 of the overline size, and a hairline needs far more luminance
|
|
56
|
+
separation to read AS a stroke on a dark ground than on a pale one — matching the ratio does not
|
|
57
|
+
match the appearance. It went to --deck-fg-muted alone first (5.90:1) and still read as a smudge
|
|
58
|
+
on forest-950. What keeps the marking quiet here is its SIZE and its unfilled box, not its ink.
|
|
59
|
+
|
|
60
|
+
★ MIXED FROM ROLES, NOT PINNED TO --neutral-300, which is the same ratio by another route. The
|
|
61
|
+
ramp has no role at that level, and this component's whole argument (above) is that the marking
|
|
62
|
+
reads roles rather than raw values so it survives a register it was not written against.
|
|
63
|
+
|
|
64
|
+
★ SENTENCE CASE AND THE OVERLINE SIZE ARE TWO SEPARATE DECISIONS, and the first pass conflated
|
|
65
|
+
them: reaching for sentence case, it took the whole body-sm role with it and drew a 70px pill next
|
|
66
|
+
to the section pill's 49px — a watermark louder than the section it sat opposite. The overline
|
|
67
|
+
role makes a short label read as a marker, and the SIZE is the part doing that; the uppercase and
|
|
68
|
+
the tracking are what make a sentence-shaped phrase hard to take in. So: overline metrics, with
|
|
69
|
+
the transform and the tracking dropped, then scaled down again below.
|
|
70
|
+
*/
|
|
71
|
+
defineProps({
|
|
72
|
+
/** The marking's text — a bare `confidential:` string from the slide's own frontmatter. */
|
|
73
|
+
label: { type: String, required: true },
|
|
74
|
+
/** The icon class in front of it, a UnoCSS icon. Empty draws the pill text-only. */
|
|
75
|
+
icon: { type: String, default: 'i-lucide-eye-off' },
|
|
76
|
+
});
|
|
77
|
+
</script>
|
|
78
|
+
|
|
79
|
+
<template>
|
|
80
|
+
<div class="deck-confidential">
|
|
81
|
+
<span class="deck-confidential-pill"
|
|
82
|
+
><span v-if="icon" class="deck-confidential-icon" :class="icon" aria-hidden="true" />{{ label }}</span>
|
|
83
|
+
</div>
|
|
84
|
+
</template>
|
|
85
|
+
|
|
86
|
+
<style scoped>
|
|
87
|
+
.deck-confidential {
|
|
88
|
+
display: flex;
|
|
89
|
+
}
|
|
90
|
+
/*
|
|
91
|
+
The box is BadgeStrip's own pill shape — block rather than inline-flex so `text-overflow` has an
|
|
92
|
+
inline formatting context to act on, `flex: none` so a crowded row wraps instead of squeezing, and
|
|
93
|
+
the em cap plus ellipsis because the label is caller-supplied text with no closed vocabulary. All
|
|
94
|
+
three are measured findings recorded on BadgeStrip; they apply here for the same reasons.
|
|
95
|
+
*/
|
|
96
|
+
.deck-confidential-pill {
|
|
97
|
+
display: block;
|
|
98
|
+
flex: none;
|
|
99
|
+
border-radius: var(--deck-radius-full);
|
|
100
|
+
font-family: var(--type-deck-overline-family);
|
|
101
|
+
/* Bold, and it was tried both ways. Regular looked right at the original 30px, but this pill is
|
|
102
|
+
24px inside a 1px stroke of its own colour; at that size the lighter weight goes thin against
|
|
103
|
+
the border and the mark reads as a smudge rather than a label. The deck ships two weights, so
|
|
104
|
+
this is the upper one, not a mid step. */
|
|
105
|
+
font-weight: var(--type-deck-overline-weight);
|
|
106
|
+
line-height: var(--type-deck-overline-line-height);
|
|
107
|
+
white-space: nowrap;
|
|
108
|
+
max-width: 20em;
|
|
109
|
+
overflow: hidden;
|
|
110
|
+
text-overflow: ellipsis;
|
|
111
|
+
|
|
112
|
+
background: transparent;
|
|
113
|
+
color: var(--deck-fg-subtle);
|
|
114
|
+
border: 1px solid currentColor;
|
|
115
|
+
/* See the note above: --deck-fg-subtle does not re-point per register, so the dark half asks for
|
|
116
|
+
the role that does. The border follows, because it is currentColor. */
|
|
117
|
+
/* Smaller than the chrome row it sits under. The section pill and the page dot are both 49px and
|
|
118
|
+
they are NAVIGATION — a reader looks at them on purpose. This is a standing marking that has to
|
|
119
|
+
be present on every slide and read by nobody from the back row, so it is the one element in
|
|
120
|
+
this theme allowed to sit below the room-scale floor. Raised from 0.4 to 0.6 of the overline
|
|
121
|
+
after 12px proved too small to read at all up close; the muting above is what keeps it from
|
|
122
|
+
competing at the larger size. Expressed against the overline size rather than as a literal px
|
|
123
|
+
so it still moves if the type scale ever does. */
|
|
124
|
+
font-size: calc(var(--type-deck-overline-size) * 0.6);
|
|
125
|
+
/* The border is part of the box, so the stroke comes out of the padding — otherwise this pill
|
|
126
|
+
stands taller than the solid one beside it for no reason a reader can see. */
|
|
127
|
+
padding: calc(var(--deck-space-1) * 0.7 - 1px) calc(var(--deck-space-2) - 1px);
|
|
128
|
+
letter-spacing: normal;
|
|
129
|
+
text-transform: none;
|
|
130
|
+
}
|
|
131
|
+
/*
|
|
132
|
+
⚠ THE DARK CLASS IS ON THE BAND, NOT ON THIS COMPONENT'S ROOT. slide-bottom.vue puts `deck-dark`
|
|
133
|
+
on `.deck-chrome-bottom` from the slide's register, and adds it to the MARK only when
|
|
134
|
+
`darkChromeAt()` says that end needs its own ground — which a plain dark slide does not, because
|
|
135
|
+
the band is already sitting on one. Written as `.deck-confidential.deck-dark`, this rule matched
|
|
136
|
+
nothing on exactly the slides it was for, and the pill went on painting --deck-fg-subtle while the
|
|
137
|
+
stylesheet said otherwise. An ancestor selector is what the class placement actually supports; the
|
|
138
|
+
roles reach here by inheritance either way, which is why the mix below resolves correctly.
|
|
139
|
+
*/
|
|
140
|
+
.deck-dark .deck-confidential-pill {
|
|
141
|
+
color: color-mix(in oklab, var(--deck-fg-muted), var(--deck-fg) 60%);
|
|
142
|
+
}
|
|
143
|
+
.deck-confidential-icon {
|
|
144
|
+
display: inline-block;
|
|
145
|
+
width: 1em;
|
|
146
|
+
height: 1em;
|
|
147
|
+
margin-right: 0.4em;
|
|
148
|
+
vertical-align: -0.12em; /* optical centring against the cap height, not the baseline */
|
|
149
|
+
flex: none;
|
|
150
|
+
}
|
|
151
|
+
</style>
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
/*
|
|
3
|
+
The theme's own controls in Slidev's nav bar — a REVIEWER's toolbar, not deck content.
|
|
4
|
+
|
|
5
|
+
Slidev renders this component inside its toolbar automatically: @slidev/client/internals/
|
|
6
|
+
NavControls.vue imports `#slidev/custom-nav-controls` and places it beside the built-in buttons.
|
|
7
|
+
Nothing has to be registered, and a theme without the file simply contributes nothing.
|
|
8
|
+
|
|
9
|
+
WHY THESE TWO. Both answer a question that comes up while LOOKING at a deck, and both previously
|
|
10
|
+
required leaving it: "what does this look like without the confidentiality chip" meant editing
|
|
11
|
+
frontmatter and reloading, and the inset-frame overlay was a keyboard shortcut a reviewer had to
|
|
12
|
+
know existed. A control with a label is discoverable; a keystroke in a comment is not. The
|
|
13
|
+
shortcut still works — this is a second door to the same flag, not a replacement.
|
|
14
|
+
|
|
15
|
+
★ HIDDEN IN PRINT — belt and braces, not the actual safety property.
|
|
16
|
+
|
|
17
|
+
What makes a preview toggle unable to reach a PDF is simpler and stronger than any guard here:
|
|
18
|
+
`slidev export` launches a SEPARATE browser process, so `confidentialHidden` is a module-level ref
|
|
19
|
+
that starts false in it. The override cannot survive into an export by construction.
|
|
20
|
+
|
|
21
|
+
The `isPrintMode` check stays anyway, for the day that stops being true (a persisted setting, an
|
|
22
|
+
export that reuses a live session). It is deliberately paired with an independent re-check in
|
|
23
|
+
slide-bottom.vue, so neither has to be right alone.
|
|
24
|
+
|
|
25
|
+
⚠ MEASURED, and it is NOT what a first test suggested: visiting `/print` in a browser does not set
|
|
26
|
+
`isPrintMode` — Slidev renders its own entire nav there too (13 buttons beside these 2). So this
|
|
27
|
+
guard is untested by that route rather than proven by it, and the export-process argument above is
|
|
28
|
+
the one actually carrying the weight.
|
|
29
|
+
*/
|
|
30
|
+
import { computed } from 'vue';
|
|
31
|
+
import { useNav } from '@slidev/client';
|
|
32
|
+
import { confidentialHidden, layoutDebugEnabled, toggleConfidential, toggleLayoutDebug } from './debug/reviewToggles.js';
|
|
33
|
+
|
|
34
|
+
const { isPrintMode } = useNav();
|
|
35
|
+
const show = computed(() => !isPrintMode.value);
|
|
36
|
+
</script>
|
|
37
|
+
|
|
38
|
+
<template>
|
|
39
|
+
<template v-if="show">
|
|
40
|
+
<button
|
|
41
|
+
class="slidev-icon-btn"
|
|
42
|
+
:class="confidentialHidden ? 'is-off' : ''"
|
|
43
|
+
:title="confidentialHidden ? 'Show the confidentiality chip (preview only)' : 'Hide the confidentiality chip (preview only)'"
|
|
44
|
+
@click="toggleConfidential"
|
|
45
|
+
>
|
|
46
|
+
<span class="sr-only">Toggle the confidentiality chip</span>
|
|
47
|
+
<div :class="confidentialHidden ? 'i-lucide-eye-off' : 'i-lucide-eye'" />
|
|
48
|
+
</button>
|
|
49
|
+
<button
|
|
50
|
+
class="slidev-icon-btn"
|
|
51
|
+
:class="layoutDebugEnabled ? 'is-on' : ''"
|
|
52
|
+
title="Toggle the layout inset frames (shortcut: l)"
|
|
53
|
+
@click="toggleLayoutDebug"
|
|
54
|
+
>
|
|
55
|
+
<span class="sr-only">Toggle the layout debug overlay</span>
|
|
56
|
+
<div class="i-lucide-ruler" />
|
|
57
|
+
</button>
|
|
58
|
+
</template>
|
|
59
|
+
</template>
|
|
60
|
+
|
|
61
|
+
<style scoped>
|
|
62
|
+
/*
|
|
63
|
+
`.slidev-icon-btn` is Slidev's OWN button class, reused rather than restyled — the point of
|
|
64
|
+
sitting in its toolbar is to look like it belongs there, and a theme that reskins one button in a
|
|
65
|
+
row of eight reads as a bug. Only STATE is expressed here, and only through opacity, which is the
|
|
66
|
+
same affordance Slidev's own toggles use.
|
|
67
|
+
*/
|
|
68
|
+
.is-off { opacity: 0.45; }
|
|
69
|
+
.is-on { opacity: 1; color: var(--deck-accent, currentColor); }
|
|
70
|
+
/* Screen-reader label: the icon alone is not a name, and `title` is not reliably announced. */
|
|
71
|
+
.sr-only {
|
|
72
|
+
position: absolute;
|
|
73
|
+
width: 1px;
|
|
74
|
+
height: 1px;
|
|
75
|
+
overflow: hidden;
|
|
76
|
+
clip: rect(0 0 0 0);
|
|
77
|
+
white-space: nowrap;
|
|
78
|
+
}
|
|
79
|
+
</style>
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
<svg width="77" height="128" viewBox="0 0 77 128" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<path d="M38.394 20.7709C48.3456 20.8659 57.3259 24.3082 64.7743 31.463C71.3055 37.7367 75.1782 45.4249 76.168 54.4273C77.4718 66.2852 74.0299 76.6751 65.8036 85.3357C59.444 92.0306 51.5648 95.8857 42.3704 96.8225C30.6332 98.0183 20.4118 94.475 11.8816 86.367C5.21966 80.0368 1.405 72.1885 0.395072 63.0401C-0.396782 55.8666 0.7678 48.9932 3.89888 42.4807C7.07712 35.8699 11.7764 30.5872 17.9737 26.6642C24.0951 22.7893 30.8003 20.8544 38.394 20.7709ZM41.3381 91.9271C49.528 91.0645 56.5286 87.8063 62.1683 81.8811C69.2037 74.4891 72.2359 65.6057 71.2182 55.4716C70.3049 46.3757 66.1654 38.8908 59.0566 33.1459C51.9438 27.3986 43.7143 25.0881 34.6699 26.025C26.725 26.8483 19.9022 30.2418 14.4056 36.0539C7.49083 43.3658 4.41544 52.1254 5.35808 62.1412C6.20793 71.1697 10.2445 78.657 17.2273 84.439C24.2249 90.2335 32.3384 92.656 41.3381 91.9271Z" fill="black" style="fill:black;fill-opacity:1;"/>
|
|
3
|
+
<path d="M0.260098 109.249V104.284H76.357V109.249H0.260098Z" fill="black" style="fill:black;fill-opacity:1;"/>
|
|
4
|
+
<path d="M12.768 4.94998H9.84649C9.55727 3.7356 8.80177 2.93399 7.60316 2.66499C5.92511 2.28931 4.4025 2.86983 3.65163 4.52096C2.86982 6.2401 2.81724 8.01491 3.56578 9.75649C4.16276 11.1456 5.22451 11.8892 6.79815 11.8544C8.35016 11.8196 9.30828 11.1463 9.74983 9.673C9.82871 9.40941 9.86815 9.13422 9.93155 8.83352H12.8824C12.5994 11.1131 11.6614 12.9335 9.47994 13.8766C8.85513 14.1464 8.15607 14.3071 7.47634 14.3775C4.54323 14.6821 1.91943 13.2999 0.716188 10.5859C0.251438 9.53695 0.0658455 8.44315 0.0503798 7.30143C0.0325941 6.01748 0.273862 4.80078 0.834502 3.64747C1.80111 1.66085 3.38637 0.482803 5.55393 0.111764C6.99149 -0.13405 8.40352 0.0174582 9.7274 0.647453C11.3877 1.43745 12.3875 2.74924 12.6752 4.58203C12.6922 4.69179 12.7293 4.7977 12.768 4.94998Z" fill="black" style="fill:black;fill-opacity:1;"/>
|
|
5
|
+
<path d="M73.6088 5.82603V0.816993H76.3346V14.5354H73.765V13.3667C73.7116 13.3721 73.68 13.3667 73.6722 13.3775C72.9901 14.3986 72.0003 14.7434 70.8126 14.7959C68.7155 14.8887 67.2864 13.6558 66.646 11.9451C66.3081 11.043 66.1187 10.1054 66.1922 9.14685C66.2819 7.98579 66.5703 6.86958 67.3095 5.9273C68.3325 4.62402 69.6713 4.1494 71.302 4.40294C72.2409 4.54904 72.9831 4.98269 73.6088 5.82603ZM73.7496 9.715C73.6606 9.07263 73.649 8.55705 73.5113 8.07624C73.0636 6.50859 71.8232 6.14297 70.4074 6.58204C69.7679 6.78069 69.4345 7.34111 69.1971 7.95101C68.9458 8.59725 68.9512 9.26666 68.9629 9.94225C68.9752 10.6527 69.2002 11.2927 69.6054 11.8639C70.634 13.3141 72.8232 12.9662 73.3653 11.4388C73.5741 10.8521 73.6413 10.2144 73.7481 9.71422L73.7496 9.715Z" fill="black" style="fill:black;fill-opacity:1;"/>
|
|
6
|
+
<path d="M23.1124 11.0107C22.9115 12.409 21.5009 13.8599 20.0889 14.1614C19.1469 14.3624 18.1974 14.4188 17.2547 14.2333C15.8033 13.9481 14.7029 13.1573 14.0131 11.8277C13.5305 10.8978 13.3434 9.90218 13.362 8.86636C13.3991 6.79704 14.7284 4.92173 16.6639 4.19822C17.4581 3.90137 18.2661 3.86505 19.1169 3.94853C20.9062 4.124 22.0886 5.07169 22.8131 6.65171C23.2655 7.63884 23.3915 8.69165 23.3089 9.80324H16.1683C16.1118 11.527 17.2516 12.5018 18.9878 12.2776C19.7215 12.1833 20.307 11.8239 20.6982 11.1745C20.7423 11.1019 20.8435 11.0184 20.9201 11.0168C21.6493 11.0052 22.3785 11.0107 23.1124 11.0107ZM20.5266 8.04234C20.5961 6.72051 19.6899 5.99931 18.0342 5.96452C17.2378 5.94752 16.0786 6.89984 16.1876 8.04234H20.5266Z" fill="black" style="fill:black;fill-opacity:1;"/>
|
|
7
|
+
<path d="M65.2537 11.0102C64.8338 12.3127 64.1579 13.3308 62.9091 13.9059C61.8071 14.413 60.6163 14.4756 59.4912 14.2785C57.7203 13.9685 56.4289 12.9234 55.8312 11.1494C55.1699 9.18748 55.3732 7.33537 56.6546 5.69428C57.7706 4.26502 59.3234 3.7386 61.1035 3.89243C62.9291 4.05012 64.216 5.004 64.9328 6.67832C65.3489 7.64998 65.5074 8.68425 65.4346 9.78037H58.2949C58.1633 11.372 59.2622 12.5284 61.0462 12.2864C61.7964 12.1844 62.3948 11.8366 62.7954 11.1741C62.8394 11.1014 62.94 11.0187 63.015 11.0172C63.7442 11.0056 64.4733 11.0102 65.2537 11.0102ZM58.2693 7.98083C58.2917 8.00093 58.3142 8.02103 58.3367 8.04113H62.6547C62.4923 6.79349 61.8899 6.01432 60.917 5.97179C60.1971 5.94088 59.4819 5.92543 58.9135 6.50285C58.5045 6.91795 58.3018 7.40958 58.2686 7.98159L58.2693 7.98083Z" fill="black" style="fill:black;fill-opacity:1;"/>
|
|
8
|
+
<path d="M48.1247 14.08H45.3702V5.9759H43.7586V4.14853H45.3083C45.3508 3.62289 45.3873 3.13204 45.4321 2.64272C45.5133 1.75145 46.4831 0.622106 47.3708 0.50229C48.27 0.38093 49.1842 0.366243 50.1259 0.302084V2.42088C49.7618 2.42088 49.3938 2.41856 49.0255 2.42088C48.4525 2.42551 48.144 2.72311 48.134 3.28509C48.1285 3.57806 48.1332 3.87102 48.1332 4.19259H49.9697V5.96664H48.1256L48.1247 14.08Z" fill="black" style="fill:black;fill-opacity:1;"/>
|
|
9
|
+
<path d="M31.917 5.97976V4.13151H33.5557V1.17014H36.2824V4.11219H38.2921V5.9612H36.3118C36.3118 6.54327 36.3109 7.09364 36.3118 7.64324C36.314 8.82671 36.3149 10.0102 36.3202 11.1936C36.3225 11.7409 36.6102 12.0161 37.1545 12.0076C37.3472 12.0045 37.5404 12.0022 37.7331 12.0006C37.9116 11.9999 38.0903 12.0006 38.2921 12.0006V14.0839C38.1034 14.1094 37.8969 14.1681 37.6928 14.1604C36.8399 14.1287 35.9722 14.1627 35.1401 14.0097C34.1434 13.8265 33.6198 13.1756 33.5935 12.1676C33.5448 10.2908 33.5657 8.4116 33.5573 6.53399C33.5573 6.35697 33.5573 6.17995 33.5573 5.98052L31.917 5.97976Z" fill="black" style="fill:black;fill-opacity:1;"/>
|
|
10
|
+
<path d="M6.97056 121.799H9.0167C9.09094 122.362 8.88911 122.865 8.67568 123.342C8.07637 124.681 7.04789 125.45 5.57167 125.668C4.18129 125.873 2.92467 125.634 1.81267 124.766C0.948909 124.092 0.46792 123.172 0.194946 122.13C-0.21722 120.557 0.0348743 119.076 0.849927 117.687C1.69978 116.241 3.61136 115.381 5.39227 115.66C6.55994 115.844 7.52578 116.251 8.25037 117.182C8.67567 117.729 8.93782 118.459 8.95329 119.075H6.89864C6.8623 118.605 6.62722 118.234 6.27537 117.941C5.92584 117.648 5.54074 117.468 5.05433 117.455C3.46367 117.413 2.61228 118.089 2.27356 119.688C2.05085 120.739 2.10886 121.748 2.62928 122.704C3.10485 123.577 3.98486 124.079 5.10923 123.947C6.01167 123.841 6.64887 123.25 6.90483 122.281C6.9435 122.135 6.94659 121.98 6.97056 121.799Z" fill="black" style="fill:black;fill-opacity:1;"/>
|
|
11
|
+
<path d="M23.66 118.557H25.4069V119.345C25.4997 119.304 25.5538 119.297 25.5793 119.266C26.181 118.548 26.9056 118.286 27.8721 118.366C29.114 118.469 29.9299 119.059 30.3816 120.148C30.9074 121.416 30.9297 122.728 30.356 123.993C29.9029 124.993 29.1312 125.55 27.9851 125.679C26.9775 125.792 26.2529 125.418 25.5655 124.683V127.974H23.6593L23.66 118.557ZM28.8681 122.059C28.7932 121.653 28.7607 121.245 28.6394 120.865C28.4406 120.244 27.9951 119.869 27.3324 119.786C26.7679 119.715 26.2985 119.888 25.9498 120.346C25.3403 121.144 25.3257 122.846 25.9211 123.658C26.2807 124.147 26.7772 124.328 27.368 124.261C27.9998 124.19 28.4436 123.848 28.6378 123.251C28.7607 122.872 28.7938 122.463 28.8681 122.059Z" fill="black" style="fill:black;fill-opacity:1;"/>
|
|
12
|
+
<path d="M27.2677 5.8944C27.7107 4.75036 29.1274 3.70681 30.6693 3.8985C30.7868 3.91319 30.9771 4.07089 30.9802 4.16675C31.0058 4.92351 30.9949 5.68105 30.9949 6.43859C30.5534 6.43859 30.1357 6.44091 29.7189 6.43859C28.6147 6.43085 27.703 7.22318 27.498 8.23659C27.3976 8.73209 27.3402 9.24381 27.3318 9.74858C27.3093 11.0611 27.324 12.3737 27.324 13.6862C27.324 13.8122 27.324 13.9382 27.324 14.082H24.5944V4.142H27.1871V5.91296C27.1989 5.9284 27.2112 5.94465 27.2227 5.9601C27.2374 5.93846 27.253 5.91681 27.2677 5.8944Z" fill="black" style="fill:black;fill-opacity:1;"/>
|
|
13
|
+
<path d="M51.4893 125.52C50.921 125.52 50.3557 125.535 49.7912 125.514C49.4238 125.5 49.5282 125.132 49.4091 124.921C48.5206 125.677 47.4735 125.823 46.374 125.643C45.661 125.527 44.8668 125 44.7377 124.002C44.6 122.936 44.968 122.134 45.8744 121.804C46.439 121.598 47.0536 121.529 47.6491 121.412C47.994 121.344 48.3452 121.308 48.6908 121.244C49.1331 121.163 49.3704 120.911 49.3759 120.54C49.3829 120.055 49.2151 119.792 48.7286 119.713C48.349 119.652 47.946 119.653 47.5656 119.708C47.192 119.763 46.9933 120.063 46.892 120.425C46.8665 120.515 46.7768 120.653 46.7127 120.654C46.1118 120.672 45.5094 120.665 44.9001 120.665C45.1823 119.238 45.937 118.564 47.3498 118.38C48.2809 118.257 49.1788 118.346 50.0742 118.617C50.7864 118.832 51.2821 119.374 51.2982 120.229C51.3253 121.656 51.3053 123.084 51.3269 124.512C51.3316 124.835 51.4283 125.156 51.4893 125.52ZM49.3998 122.201C48.8307 122.305 48.3203 122.378 47.8216 122.496C47.5307 122.565 47.1945 122.639 46.9918 122.83C46.7854 123.026 46.613 123.37 46.6161 123.646C46.6206 123.984 46.858 124.327 47.257 124.351C47.6956 124.377 48.1534 124.376 48.5755 124.276C48.9807 124.179 49.236 123.837 49.2923 123.405C49.3441 123.003 49.3657 122.597 49.3998 122.201Z" fill="black" style="fill:black;fill-opacity:1;"/>
|
|
14
|
+
<path d="M31.3378 122.032C31.35 120.139 32.4776 118.725 34.0326 118.436C35.7045 118.126 37.3724 118.539 38.1713 120.046C38.9546 121.523 38.7506 123.846 37.4645 124.872C36.5559 125.598 35.5189 125.837 34.3651 125.672C32.8154 125.45 31.9161 124.517 31.4722 123.086C31.3687 122.753 31.3794 122.384 31.3378 122.032ZM33.23 122C33.2471 122.589 33.3722 123.119 33.6924 123.597C34.0883 124.188 34.6644 124.361 35.3333 124.242C35.9751 124.127 36.3557 123.725 36.5521 123.103C36.7453 122.489 36.7493 121.877 36.6232 121.261C36.4732 120.528 36.2311 119.927 35.1764 119.795C34.4479 119.703 33.7798 120.021 33.5006 120.838C33.3722 121.212 33.3181 121.612 33.23 122Z" fill="black" style="fill:black;fill-opacity:1;"/>
|
|
15
|
+
<path d="M9.87732 122.231C9.93533 119.956 11.0674 118.765 12.5197 118.444C13.4036 118.248 14.2897 118.292 15.121 118.635C16.298 119.119 16.8942 120.051 17.086 121.307C17.1966 122.032 17.1478 122.728 16.9383 123.404C16.5578 124.63 15.6856 125.345 14.439 125.619C13.1097 125.911 11.9057 125.675 10.912 124.712C10.1519 123.975 9.90826 123.016 9.87732 122.231ZM11.7874 122.017C11.8253 122.291 11.8585 122.565 11.901 122.839C12.0132 123.565 12.6303 124.223 13.235 124.264C14.2148 124.332 14.7986 123.969 15.0754 123.11C15.3051 122.398 15.2502 121.689 15.0685 120.972C14.8891 120.265 14.4266 119.911 13.7345 119.79C13.16 119.689 12.5375 119.948 12.2104 120.457C11.908 120.93 11.7819 121.451 11.7874 122.017Z" fill="black" style="fill:black;fill-opacity:1;"/>
|
|
16
|
+
<path d="M68.5334 122.035C68.5357 124.378 67.0603 125.703 64.8943 125.726C62.8373 125.747 61.2019 124.286 61.2753 121.892C61.3434 119.672 62.7932 118.348 64.7157 118.333C67.1059 118.314 68.4831 119.539 68.5334 122.035ZM66.6991 122.053C66.6675 122.044 66.6357 122.035 66.6041 122.026C66.6041 121.885 66.6087 121.743 66.6041 121.602C66.5693 120.619 66.0813 119.992 65.1565 119.805C64.2748 119.628 63.6184 120.142 63.3678 120.93C63.1497 121.616 63.1281 122.319 63.3393 123.033C63.5117 123.615 64.0383 124.195 64.5679 124.263C65.3537 124.365 66.0164 124.102 66.2994 123.472C66.4988 123.028 66.5709 122.529 66.6991 122.053Z" fill="black" style="fill:black;fill-opacity:1;"/>
|
|
17
|
+
<path d="M75.9587 125.505H74.0889C74.0889 125.36 74.0905 125.223 74.0889 125.087C74.0703 123.752 74.0742 122.416 74.0246 121.082C73.9914 120.178 73.56 119.849 72.8445 119.84C72.0134 119.829 71.4542 120.248 71.4172 121.139C71.3569 122.576 71.4009 124.018 71.4009 125.481H69.4444V118.567H71.2363V119.529C71.7103 118.895 72.2314 118.452 72.9297 118.387C74.4183 118.249 75.8945 118.505 75.9509 120.609C75.9936 122.225 75.9595 123.843 75.9595 125.505L75.9587 125.505Z" fill="black" style="fill:black;fill-opacity:1;"/>
|
|
18
|
+
<path d="M54.5863 14.0705H51.8727V4.14361H54.5863V14.0705Z" fill="black" style="fill:black;fill-opacity:1;"/>
|
|
19
|
+
<path d="M42.6235 14.0767H39.9433V4.14211H42.6235V14.0767Z" fill="black" style="fill:black;fill-opacity:1;"/>
|
|
20
|
+
<path d="M53.2638 116.423H55.1894V118.493H56.5473V119.783C56.2124 119.783 55.883 119.79 55.5535 119.781C55.2991 119.774 55.1932 119.867 55.1963 120.136C55.2079 121.114 55.1994 122.092 55.2016 123.07C55.2032 123.986 55.3262 124.107 56.2402 124.11C56.3299 124.11 56.4189 124.11 56.558 124.11C56.558 124.357 56.558 124.596 56.558 124.835C56.558 125.074 56.558 125.296 56.558 125.611C55.8118 125.573 55.0703 125.595 54.3487 125.483C53.6597 125.375 53.2885 124.768 53.2794 123.906C53.2661 122.71 53.2754 121.513 53.2754 120.317C53.2754 120.176 53.2754 120.036 53.2754 119.871H52.0993V118.532H53.2645L53.2638 116.423Z" fill="black" style="fill:black;fill-opacity:1;"/>
|
|
21
|
+
<path d="M18.3776 118.559H20.1694V119.618L20.2504 119.645C20.3209 119.552 20.3904 119.458 20.4609 119.365C20.9574 118.7 21.6138 118.338 22.4481 118.314C22.7274 118.305 22.8936 118.386 22.872 118.724C22.8434 119.185 22.865 119.648 22.865 120.169C22.6284 120.169 22.3994 120.193 22.1775 120.165C21.0424 120.022 20.3534 120.913 20.3194 121.892C20.2822 122.945 20.2969 124 20.2891 125.053C20.2884 125.192 20.2891 125.331 20.2891 125.492H18.3776V118.559Z" fill="black" style="fill:black;fill-opacity:1;"/>
|
|
22
|
+
<path d="M44.3475 120.17C44.0281 120.17 43.7451 120.195 43.4674 120.165C42.4885 120.062 41.832 121.001 41.8088 121.757C41.7708 122.99 41.7988 124.226 41.7988 125.48H39.8508V118.523H41.6441V119.612C41.6727 119.624 41.7013 119.636 41.7292 119.649C41.7972 119.558 41.8667 119.468 41.9341 119.376C42.429 118.703 43.0893 118.342 43.9269 118.314C44.2044 118.305 44.3731 118.385 44.3537 118.723C44.3266 119.183 44.3468 119.648 44.3468 120.169L44.3475 120.17Z" fill="black" style="fill:black;fill-opacity:1;"/>
|
|
23
|
+
<path d="M59.9845 125.503H58.1039V118.565H59.9845V125.503Z" fill="black" style="fill:black;fill-opacity:1;"/>
|
|
24
|
+
<path d="M51.8759 0.339383H54.5746V2.55325H51.8759V0.339383Z" fill="black" style="fill:black;fill-opacity:1;"/>
|
|
25
|
+
<path d="M39.9233 2.56C39.9233 1.8411 39.9233 1.16087 39.9233 0.479847C39.9233 0.443516 39.9326 0.406412 39.9435 0.329111H42.633V2.56H39.9233Z" fill="black" style="fill:black;fill-opacity:1;"/>
|
|
26
|
+
<path d="M71.3611 98.883C71.4764 100.093 70.4447 101.185 69.1286 101.173C67.8248 101.162 66.8659 100.185 66.8706 98.8551C66.8753 97.5194 67.852 96.669 69.3792 96.6721C70.461 96.6744 71.3651 97.6825 71.3611 98.883ZM70.973 98.9324C70.9659 97.7025 70.2422 97.003 69.0174 96.9852C68.2517 96.9737 67.2294 97.7497 67.2612 98.968C67.2859 99.9341 68.1164 100.842 69.008 100.848C70.3775 100.856 71.0333 99.8331 70.973 98.9324Z" fill="black" style="fill:black;fill-opacity:1;"/>
|
|
27
|
+
<path d="M60.0021 115.858C60.0021 116.309 60.003 116.73 60.0014 117.151C60.0014 117.226 59.9898 117.301 59.982 117.4H58.048V115.858H60.0021Z" fill="black" style="fill:black;fill-opacity:1;"/>
|
|
28
|
+
<path d="M48.2748 57.619C51.8242 58.4647 54.4804 60.3191 55.812 63.7567C56.1562 64.6465 56.3325 65.6181 56.4609 66.5689C56.5815 67.4647 56.6403 68.3932 56.5482 69.289C56.0479 74.1806 53.6221 77.7217 49.0395 79.6635C47.6809 80.2393 46.2502 80.5718 44.7617 80.6112C44.0069 80.6305 43.2538 80.6915 42.499 80.6924C35.9445 80.6993 29.39 80.6962 22.8364 80.6962H22.3716V37.1547C22.5456 37.1547 22.698 37.1547 22.8502 37.1547C28.4002 37.1571 33.9502 37.154 39.5001 37.1671C40.7692 37.1702 42.0428 37.1747 43.3048 37.283C44.6487 37.3981 46.0097 37.525 47.3144 37.8457C50.6595 38.6682 52.8472 40.8582 54.1038 44.0043C54.6235 45.306 54.8284 46.6897 54.7797 48.0933C54.6281 52.4733 52.6184 55.6387 48.5964 57.473C48.4889 57.5217 48.3823 57.5703 48.2748 57.619ZM28.1583 75.8133H28.6145C32.825 75.8133 37.0349 75.8224 41.2455 75.8062C42.2515 75.8024 43.2607 75.7536 44.2628 75.6586C45.8288 75.5109 47.2223 74.9135 48.4139 73.8784C49.2753 73.1308 49.9202 72.2194 50.2876 71.1405C50.7253 69.8557 50.9187 68.5377 50.7732 67.1687C50.6844 66.3308 50.532 65.5184 50.1808 64.7562C49.5321 63.347 48.4295 62.3985 47.0382 61.7662C45.3981 61.021 43.6419 60.7876 41.8726 60.7706C37.3667 60.7281 32.8599 60.7528 28.3539 60.752C28.2936 60.752 28.2324 60.7622 28.159 60.7691V75.8117L28.1583 75.8133ZM28.1614 42.0516V55.8759C28.3043 55.8759 28.4289 55.8759 28.5533 55.8759C32.5769 55.8535 36.6003 55.825 40.6237 55.8118C41.9987 55.8072 43.3288 55.6046 44.6487 55.2027C47.9539 54.1978 48.8478 51.8748 48.9707 49.6163C49.0163 48.7837 48.9351 47.9342 48.8191 47.1047C48.5539 45.2008 47.5665 43.7947 45.7987 42.9714C44.3186 42.2819 42.7248 42.0934 41.1342 42.0702C36.9003 42.0084 32.665 42.0315 28.4296 42.0231C28.343 42.0231 28.2564 42.0409 28.1605 42.0509L28.1614 42.0516Z" fill="black" style="fill:black;fill-opacity:1;"/>
|
|
29
|
+
<path d="M69.5113 99.0667C69.7627 99.4548 69.9977 99.8173 70.2398 100.191C69.9498 100.302 69.7812 100.218 69.659 99.9734C69.574 99.8018 69.4371 99.6558 69.3566 99.4818C69.2151 99.1764 68.9994 99.0776 68.6552 99.1849V100.218H68.2764V97.6087C68.7186 97.6087 69.1656 97.5918 69.6111 97.6143C69.9768 97.632 70.1926 97.9018 70.2065 98.2929C70.2211 98.7173 70.072 98.9144 69.6737 99.001C69.6274 99.011 69.584 99.0373 69.5106 99.0675L69.5113 99.0667ZM68.6692 98.7582C68.9407 98.7582 69.1726 98.78 69.3991 98.752C69.6258 98.725 69.8043 98.606 69.8074 98.34C69.8106 98.068 69.6405 97.9465 69.3991 97.9281C69.1618 97.9096 68.9213 97.9243 68.6692 97.9243V98.7582Z" fill="black" style="fill:black;fill-opacity:1;"/>
|
|
30
|
+
</svg>
|