@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,972 @@
|
|
|
1
|
+
// Mermaid, in Forest colours and at room scale.
|
|
2
|
+
//
|
|
3
|
+
// ★★ WHY THIS IS CONFIG AND NOT CSS, which is the fact that shapes everything below. Slidev renders
|
|
4
|
+
// a mermaid diagram into a SHADOW ROOT (@slidev/client/builtin/Mermaid.vue mounts the SVG through
|
|
5
|
+
// internals/ShadowRoot.vue). A stylesheet in this theme cannot reach inside one. Custom properties
|
|
6
|
+
// DO pierce the boundary, but that does not help either: mermaid writes literal colours into a
|
|
7
|
+
// <style> block it generates itself, and derives about a hundred secondary values from the ones it
|
|
8
|
+
// is handed using real colour arithmetic (khroma) — hand `var(--deck-accent)` to that and you get
|
|
9
|
+
// NaN, not a green. So the only way in is mermaid's own `themeVariables`, in literal hex, per
|
|
10
|
+
// register. components/deckPalette.ts is what makes that safe: the hex is derived from the same
|
|
11
|
+
// generated tokens the stylesheet is, never typed.
|
|
12
|
+
//
|
|
13
|
+
// Out of the box the diagrams were unusable on this deck — lavender nodes, 10px labels on a 1920px
|
|
14
|
+
// canvas, and on a dark slide an edge stroke a shade off the ground it was drawn on. Probed before
|
|
15
|
+
// any of this was written.
|
|
16
|
+
//
|
|
17
|
+
// ── THE COLOUR ASSIGNMENT ──────────────────────────────────────────────────────────────────────
|
|
18
|
+
//
|
|
19
|
+
// A diagram is not a chart. Its job is to show a STRUCTURE, so the default node is a quiet surface
|
|
20
|
+
// — the same `--deck-surface` a Card sits on — and colour is spent only where it means something:
|
|
21
|
+
// the accent on the edges and arrowheads that carry the reading order, and the categorical series
|
|
22
|
+
// only where a diagram genuinely encodes categories (pie slices, git branches). A flowchart whose
|
|
23
|
+
// every node is a different colour has spent its whole palette on nothing.
|
|
24
|
+
//
|
|
25
|
+
// FIVE, AND THE DECK'S CEILING IS SIX — so this is a deliberate stop short of it, not a mirror of
|
|
26
|
+
// it. The sixth vivid slot is `red`, and chartData.ts records it sitting dE00 3 from
|
|
27
|
+
// --deck-status-danger-mark on the dark ground. In a chart that is a warning; here it is worse,
|
|
28
|
+
// because the crit bar below is already that hue, so a six-colour pie would put a category and an
|
|
29
|
+
// alarm side by side in the same red. pie6..pie12 cycle back through the five rather than inventing
|
|
30
|
+
// hues nobody validated: mermaid will happily draw a twelve-slice pie, and it will do it in five
|
|
31
|
+
// colours, which is the honest picture of a chart that has run out of palette.
|
|
32
|
+
//
|
|
33
|
+
// ── ROOM SCALE ────────────────────────────────────────────────────────────────────────────────
|
|
34
|
+
//
|
|
35
|
+
// `fontSize` is a NUMBER, in the SVG's own coordinate space, and mermaid lays every box out around
|
|
36
|
+
// it — so it is the one lever that decides whether a diagram is legible. It is set from the deck's
|
|
37
|
+
// own body-sm rung rather than from a taste-picked number, which means a diagram's labels and the
|
|
38
|
+
// paragraph beside it are the same size by construction.
|
|
39
|
+
import { DATAVIZ_CATEGORICAL } from '@humanforest/tokens/colourEngine';
|
|
40
|
+
import { BACKGROUND_NAMES, derivedMix, mixOklab, palette, background, ramp, type Register } from './deckPalette';
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* The categorical series, resolved — the PUBLISHED vivid set, sliced to five.
|
|
44
|
+
*
|
|
45
|
+
* ★★ THIS USED TO BE A BAND OF BRAND RAMPS (forest/river/amber/maple/acid at 600/400) under a
|
|
46
|
+
* comment claiming it mirrored deck.css's `--deck-series-N` aliases. It shared ZERO hex values with
|
|
47
|
+
* them, in either register, and nothing anywhere recorded a reason — the file has one commit and its
|
|
48
|
+
* message repeats the same false claim. So it was a transcription, not a decision.
|
|
49
|
+
*
|
|
50
|
+
* ★★ AND THE BRAND BAND WAS BROKEN AT THE ONE JOB THESE KEYS HAVE. Measured with colourEngine's own
|
|
51
|
+
* dE00cvd: brand amber against brand acid is worst-case-CVD dE00 3 on light and 1 on DARK — two pie
|
|
52
|
+
* slices that are the same colour to a dichromat, forever, with nothing on screen to say so. The
|
|
53
|
+
* published five hold 9 and 8. A categorical slot whose categories collapse is not a palette choice,
|
|
54
|
+
* it is a defect.
|
|
55
|
+
*
|
|
56
|
+
* Taken from DATAVIZ_CATEGORICAL directly, which is the same source scripts/dataviz.ts generates
|
|
57
|
+
* deck.css's aliases from, so there is no second copy to drift.
|
|
58
|
+
*
|
|
59
|
+
* ★ THE GAP THIS CANNOT CLOSE, stated because it is real: a `.deck-pairs` slide re-points
|
|
60
|
+
* --deck-series-* to the brand pairs at render time, and mermaid is baked per REGISTER at build
|
|
61
|
+
* time. A diagram on such a slide keeps the vivid five while the charts beside it turn to pairs.
|
|
62
|
+
* Mermaid takes hexes, not var() references; following a slide class would mean re-theming mermaid
|
|
63
|
+
* per slide, which is a bigger machine than the mismatch is worth.
|
|
64
|
+
*/
|
|
65
|
+
export const SERIES: Record<Register, string[]> = {
|
|
66
|
+
light: DATAVIZ_CATEGORICAL.light.slice(0, 5),
|
|
67
|
+
dark: DATAVIZ_CATEGORICAL.dark.slice(0, 5),
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
/** The label size every box is laid out around — the deck's body-sm rung, in px. */
|
|
71
|
+
export const MERMAID_FONT_PX = 36;
|
|
72
|
+
|
|
73
|
+
/*
|
|
74
|
+
THE DECK'S STRONG EDGE, in SVG USER UNITS so it scales with the drawing rather than standing still
|
|
75
|
+
while everything around it shrinks — see the long note in setup/mermaid-renderer.ts, which
|
|
76
|
+
re-exports this and is where most of the rules that read it live. It is declared HERE because
|
|
77
|
+
radar states its weights as theme variables, and theme variables are built in this file.
|
|
78
|
+
*/
|
|
79
|
+
export const DIAGRAM_STROKE_PX = 4;
|
|
80
|
+
|
|
81
|
+
/*
|
|
82
|
+
A LEGEND ENTRY — the key beside a chart, on any family that draws one. Subordinate to the diagram's
|
|
83
|
+
own prose and read at a glance rather than across the room, which is what four fifths of the body
|
|
84
|
+
rung says. The pie's legend has taken this size since it was written; radar joins it, and the
|
|
85
|
+
renderer's swatch and row-spacing pass read the same number so the three cannot drift apart.
|
|
86
|
+
*/
|
|
87
|
+
export const MERMAID_LEGEND_FONT_PX = Math.round(MERMAID_FONT_PX * 0.8);
|
|
88
|
+
|
|
89
|
+
/*
|
|
90
|
+
Mermaid's own numeric defaults are drawn for its default 16px type. Any of them that is a BOX and
|
|
91
|
+
not a font has to be scaled by hand when the font moves, because mermaid sizes the box from the
|
|
92
|
+
config and the text from `fontSize`, and never compares the two.
|
|
93
|
+
*/
|
|
94
|
+
const MERMAID_DEFAULT_FONT_PX = 16;
|
|
95
|
+
|
|
96
|
+
/*
|
|
97
|
+
The sequence fragment's keyword — `opt`, `alt`, `loop` — is a structural marker rather than part
|
|
98
|
+
of the diagram's prose, and takes a step down from the body rung every label around it uses. The
|
|
99
|
+
renderer sets the TEXT from this same number — with `!important`, because mermaid writes the
|
|
100
|
+
keyword's size as an INLINE STYLE and no plain rule reaches it. The tab is the BOX it sits in, so both move
|
|
101
|
+
together or the word stops fitting again.
|
|
102
|
+
*/
|
|
103
|
+
export const MERMAID_TAG_FONT_PX = Math.round(MERMAID_FONT_PX * 0.5);
|
|
104
|
+
|
|
105
|
+
/*
|
|
106
|
+
The autonumber index, which USED to share the tag rung and no longer does. Both are subordinate
|
|
107
|
+
to the diagram's prose, but they are subordinate to different things: a fragment keyword labels a
|
|
108
|
+
block and can afford to recede, while an index is read against the message it numbers and has
|
|
109
|
+
just enough glyph to be read at all. Tying them meant every step down for one was a step down for
|
|
110
|
+
the other.
|
|
111
|
+
*/
|
|
112
|
+
export const MERMAID_INDEX_FONT_PX = Math.round(MERMAID_FONT_PX * 0.7);
|
|
113
|
+
|
|
114
|
+
/*
|
|
115
|
+
A transition name — the word ON an edge. Subordinate to the node labels it joins, which is the
|
|
116
|
+
argument that also mutes it to 75%, but it is PROSE and not a mark: it has to be read across a
|
|
117
|
+
room, where an index only has to be counted. So it steps down one rung and no further, where the
|
|
118
|
+
fragment keyword steps down two and the index sits between them. Three roles, three rungs, none
|
|
119
|
+
of them borrowing another's — tying any two means a change for one is a change for the other.
|
|
120
|
+
*/
|
|
121
|
+
export const MERMAID_EDGE_LABEL_FONT_PX = Math.round(MERMAID_FONT_PX * 0.8);
|
|
122
|
+
|
|
123
|
+
/** A quadrant point's dot. Its label's clearance is derived from it — see `pointTextPadding`. */
|
|
124
|
+
const QUADRANT_POINT_RADIUS = 12;
|
|
125
|
+
|
|
126
|
+
const toTagScale = (mermaidDefault: number) =>
|
|
127
|
+
Math.round((mermaidDefault * MERMAID_TAG_FONT_PX) / MERMAID_DEFAULT_FONT_PX);
|
|
128
|
+
|
|
129
|
+
/** The deck's sans, as a family string. @font-face is DOCUMENT-scoped, so the face a shadow root
|
|
130
|
+
* names is the same one the slide loaded — no second copy, no second request. */
|
|
131
|
+
export const MERMAID_FONT_FAMILY = "'GT Haptik', system-ui, sans-serif";
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Mermaid's `themeVariables`, for one register.
|
|
135
|
+
*
|
|
136
|
+
* Written as one flat table on purpose. Mermaid's variables are a per-diagram-type vocabulary with
|
|
137
|
+
* no shared spine — `mainBkg` is a flowchart node, `actorBkg` the same idea in a sequence diagram,
|
|
138
|
+
* `nodeBorder` and `actorBorder` likewise — and leaving any of them unset means mermaid DERIVES it
|
|
139
|
+
* from `primaryColor` with its own colour maths, which is exactly how a Forest-green flowchart ends
|
|
140
|
+
* up sitting next to a lavender sequence diagram. So every family is stated, and the grouping
|
|
141
|
+
* comments below say which diagram each block is actually steering.
|
|
142
|
+
*/
|
|
143
|
+
export function mermaidThemeVariables(register: Register): Record<string, string> {
|
|
144
|
+
const p = palette(register);
|
|
145
|
+
const series = SERIES[register];
|
|
146
|
+
const [s1, s2, s3, s4, s5] = series;
|
|
147
|
+
|
|
148
|
+
return {
|
|
149
|
+
// ★★ `darkMode` IS DELIBERATELY NOT SET, and setting it is a trap worth naming. Mermaid's base
|
|
150
|
+
// theme uses that flag to DERIVE a dark palette from a light one — it runs `invert()` over
|
|
151
|
+
// primaryColor and its relatives. Handed an already-dark `primaryColor` it inverts it back to
|
|
152
|
+
// light: measured, the dark register rendered cream nodes with near-black labels on a dark
|
|
153
|
+
// slide, which reads as the light theme leaking through rather than as a bug. Every value here
|
|
154
|
+
// is stated for both registers, so there is nothing left for mermaid to derive.
|
|
155
|
+
background: p.bg,
|
|
156
|
+
fontFamily: MERMAID_FONT_FAMILY,
|
|
157
|
+
fontSize: `${MERMAID_FONT_PX}px`,
|
|
158
|
+
|
|
159
|
+
// ── The spine every diagram type derives from ─────────────────────────────────────────────
|
|
160
|
+
// A node is a Card: the quiet surface one step off the page, with the deck's own border.
|
|
161
|
+
primaryColor: p.surface,
|
|
162
|
+
primaryTextColor: p.fg,
|
|
163
|
+
primaryBorderColor: p.borderStrong,
|
|
164
|
+
secondaryColor: p.surface,
|
|
165
|
+
secondaryTextColor: p.fg,
|
|
166
|
+
secondaryBorderColor: p.border,
|
|
167
|
+
/*
|
|
168
|
+
⚠ THREE SLOTS ON THE DECK SURFACE, AND TWO OF THEM DO NOT DRAW. tertiary, labelBox and
|
|
169
|
+
activation all pointed at --deck-accent-soft, a role that has since been retired because
|
|
170
|
+
nothing in the theme rendered it: drawn on both registers, mermaid v11 leaves the opt/alt label
|
|
171
|
+
box and the activation bar OUTLINED, whatever these two variables say, and no diagram in the
|
|
172
|
+
deck reaches tertiary. They point at the derived surface now — a box on the page, in either
|
|
173
|
+
register — because a slot that does not render today is exactly where not to plant
|
|
174
|
+
--deck-bg-accent, which on the dark register IS the page.
|
|
175
|
+
*/
|
|
176
|
+
tertiaryColor: p.surface,
|
|
177
|
+
tertiaryTextColor: p.fg,
|
|
178
|
+
tertiaryBorderColor: p.border,
|
|
179
|
+
// ★ THE EDGE CARRIES THE READING. It is the accent, at full strength, because the line between
|
|
180
|
+
// two boxes is the only part of a flowchart that says what the diagram MEANS — and on a dark
|
|
181
|
+
// slide the shipped default drew it a shade off the ground.
|
|
182
|
+
lineColor: p.accent,
|
|
183
|
+
textColor: p.fg,
|
|
184
|
+
mainBkg: p.surface,
|
|
185
|
+
nodeBorder: p.borderStrong,
|
|
186
|
+
nodeTextColor: p.fg,
|
|
187
|
+
titleColor: p.fg,
|
|
188
|
+
|
|
189
|
+
// ── Flowchart ─────────────────────────────────────────────────────────────────────────────
|
|
190
|
+
clusterBkg: p.surface,
|
|
191
|
+
clusterBorder: p.border,
|
|
192
|
+
// The label sitting ON an edge needs the PAGE behind it, not the node fill — it interrupts a
|
|
193
|
+
// line, it does not sit in a box.
|
|
194
|
+
edgeLabelBackground: p.bg,
|
|
195
|
+
defaultLinkColor: p.accent,
|
|
196
|
+
arrowheadColor: p.accent,
|
|
197
|
+
|
|
198
|
+
// ── Sequence ──────────────────────────────────────────────────────────────────────────────
|
|
199
|
+
actorBkg: p.surface,
|
|
200
|
+
actorBorder: p.borderStrong,
|
|
201
|
+
actorTextColor: p.fg,
|
|
202
|
+
actorLineColor: p.borderStrong,
|
|
203
|
+
signalColor: p.accent,
|
|
204
|
+
signalTextColor: p.fg,
|
|
205
|
+
labelBoxBkgColor: p.surface,
|
|
206
|
+
labelBoxBorderColor: p.accent,
|
|
207
|
+
labelTextColor: p.fg,
|
|
208
|
+
loopTextColor: p.fg,
|
|
209
|
+
/*
|
|
210
|
+
★★ THE NOTE IS WARM WHERE THE FLOW IS ACCENT, and that is a semantic split rather than a
|
|
211
|
+
preference. Everything else in a sequence belongs TO the sequence — the signals, the actor
|
|
212
|
+
boxes, the activation bars, the opt/alt label boxes — and they are all the accent family. A
|
|
213
|
+
note is the one element that comments ON it from outside, so it takes the other family the
|
|
214
|
+
deck publishes. Give the annotation the same green as the thing it annotates and the reader
|
|
215
|
+
has to parse the shape to find the aside.
|
|
216
|
+
|
|
217
|
+
It is the warm PAIR, not a warm fill: --deck-bg-warm with --deck-on-bg-warm as both the ink and
|
|
218
|
+
the edge, so the box can never assemble an ink that was not measured against its own ground
|
|
219
|
+
(10.26:1 on light, 6.81:1 on dark). The border was `p.accent` — a green edge on a peach fill,
|
|
220
|
+
which is what borrowing a fill from one family and an edge from another looks like.
|
|
221
|
+
|
|
222
|
+
★ IT ALSO SEPARATES BETTER FROM THE PAGE, which is the part I did not expect. Against the
|
|
223
|
+
deck's own grounds, warm measures 1.30:1 on cream and 2.21:1 on near-black, where accent-soft
|
|
224
|
+
manages 1.13 and 1.36. A note box is a filled box whose job is to be visibly a box.
|
|
225
|
+
*/
|
|
226
|
+
noteBkgColor: background('warm', register).fill,
|
|
227
|
+
noteTextColor: background('warm', register).ink,
|
|
228
|
+
noteBorderColor: background('warm', register).ink,
|
|
229
|
+
activationBkgColor: p.surface,
|
|
230
|
+
activationBorderColor: p.accent,
|
|
231
|
+
sequenceNumberColor: p.onAccent,
|
|
232
|
+
|
|
233
|
+
// ── State ─────────────────────────────────────────────────────────────────────────────────
|
|
234
|
+
labelColor: p.fg,
|
|
235
|
+
transitionColor: p.accent,
|
|
236
|
+
transitionLabelColor: p.fg,
|
|
237
|
+
stateLabelColor: p.fg,
|
|
238
|
+
stateBkg: p.surface,
|
|
239
|
+
/*
|
|
240
|
+
★★ ONE GROUND FOR THE WHOLE COMPOSITE, TITLE BAND INCLUDED. `compositeTitleBackground` was the
|
|
241
|
+
page and `compositeBackground` the surface, which drew the container as two stacked grounds
|
|
242
|
+
with a seam across it — a title strip in one colour over a body in another, inside a border
|
|
243
|
+
meant to hold ONE object. mermaid offers the pair so a title can be picked out; this deck
|
|
244
|
+
already picks a container's title out by its position and its weight, and a second fill is the
|
|
245
|
+
kind of decoration the diagram doctrine here spends colour to avoid.
|
|
246
|
+
|
|
247
|
+
Both the surface now, so a composite reads as what it is: a Card with a machine in it, the
|
|
248
|
+
same treatment `subgraph` gets in the flowchart.
|
|
249
|
+
*/
|
|
250
|
+
altBackground: p.surface,
|
|
251
|
+
compositeBackground: p.surface,
|
|
252
|
+
compositeBorder: p.border,
|
|
253
|
+
compositeTitleBackground: p.surface,
|
|
254
|
+
/* ⚠ DEAD IN 11.17.2, KEPT FOR THE NAME. stateEnd fills the inner dot from `stateBorder ?? nodeBorder`
|
|
255
|
+
and never reads this; mermaid's own `.node circle.state-end { fill: innerEndBackground }` cannot
|
|
256
|
+
match either, because the group it targets is classed `outer`. Recorded rather than removed, so
|
|
257
|
+
the next reader does not re-derive it. */
|
|
258
|
+
/*
|
|
259
|
+
★★ THE GANTT'S EXCLUDED BAND IS A LITERAL GREY WITH NO DERIVATION. The base theme defaults it
|
|
260
|
+
`this.excludeBkgColor = this.excludeBkgColor || "#eeeeee"` — a fixed light grey, not a step off
|
|
261
|
+
anything the deck sets, so `excludes weekends` painted #eeeeee on warm paper in the light
|
|
262
|
+
register and the same #eeeeee on near-black in the dark one. It takes the deck's own surface,
|
|
263
|
+
which is what every other quiet band in the family already uses.
|
|
264
|
+
*/
|
|
265
|
+
excludeBkgColor: p.surface,
|
|
266
|
+
innerEndBackground: p.fg,
|
|
267
|
+
specialStateColor: p.fg,
|
|
268
|
+
|
|
269
|
+
// ── Class / ER ────────────────────────────────────────────────────────────────────────────
|
|
270
|
+
classText: p.fg,
|
|
271
|
+
/* Odd/even alternated surface against the 8% bg-subtle — 1.08:1, a stripe in name only. Now
|
|
272
|
+
surface against the page, which is the same alternation every other striped thing here uses
|
|
273
|
+
(gantt sections, quadrants) and one the eye can actually follow.
|
|
274
|
+
|
|
275
|
+
⚠ AND BOTH OF THESE ARE DEAD IN 11.17.2, kept only because they cost nothing and name the
|
|
276
|
+
intent. `attributeBox` appears nowhere in mermaid's dist, and the ER stylesheet has no
|
|
277
|
+
`.attributeBoxOdd`/`.attributeBoxEven` rule to read them. The rows are drawn from `rowOdd`
|
|
278
|
+
and `rowEven` instead — see below. */
|
|
279
|
+
attributeBackgroundColorOdd: p.surface,
|
|
280
|
+
attributeBackgroundColorEven: p.bg,
|
|
281
|
+
/*
|
|
282
|
+
★★ THE KEYS THE ATTRIBUTE TABLE ACTUALLY READS, and leaving them unset painted it WHITE on
|
|
283
|
+
both registers. shapes/erBox reads `rowEven`/`rowOdd` off themeVariables for every attribute
|
|
284
|
+
row; unset, theme-base derives them — and its light branch is `lighten(mainBkg, 75)`, which on
|
|
285
|
+
this deck's surface CLAMPS TO #ffffff. Worse on the dark register than the light one, because
|
|
286
|
+
`darkMode` is deliberately never set here (see above), so the light branch runs there too and
|
|
287
|
+
a near-white band lands on a near-black slide.
|
|
288
|
+
|
|
289
|
+
Same alternation the dead pair above names, now on the keys that read it.
|
|
290
|
+
*/
|
|
291
|
+
rowOdd: p.surface,
|
|
292
|
+
rowEven: p.bg,
|
|
293
|
+
|
|
294
|
+
// ── Pie — CATEGORICAL, and this file stops at five though the deck allows six (see above) ──
|
|
295
|
+
// pie6..pie12 cycle the same five rather than inventing hues. A twelve-slice pie will repeat
|
|
296
|
+
// colours, which is the honest picture of a chart past its palette.
|
|
297
|
+
...Object.fromEntries(
|
|
298
|
+
Array.from({ length: 12 }, (_, i) => [`pie${i + 1}`, series[i % series.length]]),
|
|
299
|
+
),
|
|
300
|
+
/* ★ MERMAID DEFAULTS PIE SLICES TO 0.7 OPACITY, which is why the series came out washed. At 0.7
|
|
301
|
+
over paper they are five colours nobody measured; at full strength they are the five that
|
|
302
|
+
were. The separation between slices comes from the page-coloured stroke below instead.
|
|
303
|
+
|
|
304
|
+
★★ THE OLD VERSION OF THIS NOTE WAS WRONG TWICE and is worth correcting rather than deleting.
|
|
305
|
+
It said the hex handed in "was the published categorical palette" — it was not, it was a band
|
|
306
|
+
of brand ramps (see SERIES above), which is the drift this file has now had fixed. And it
|
|
307
|
+
credited the gate to scripts/dataviz.ts, which contains no contrast code at all: colourEngine
|
|
308
|
+
exports validateDataViz and nothing in the repo calls it. Two claims of provenance, neither
|
|
309
|
+
true, sitting above a line whose actual behaviour was correct.
|
|
310
|
+
|
|
311
|
+
★ THE REAL FLOOR, measured. pieSectionTextSize is the deck's body-sm rung x 0.8 = 29px, which
|
|
312
|
+
is large text, so the in-slice label is held to 3:1 rather than 4.5:1. White ink on the five
|
|
313
|
+
clears it on light at 3.20-6.46, and the dark register's onAccent ink clears it at 5.00-9.04.
|
|
314
|
+
The ceiling: mermaid exposes ONE pieSectionTextColor for every slice, so the ink cannot follow
|
|
315
|
+
the fill — and if a pie is ever shrunk below the large-text threshold the floor becomes 4.5:1,
|
|
316
|
+
where green, amber and sky fall under. A pie that small is the thing to fix, not the ink. */
|
|
317
|
+
pieOpacity: '1',
|
|
318
|
+
pieTitleTextColor: p.fg,
|
|
319
|
+
pieSectionTextColor: p.onAccent,
|
|
320
|
+
pieLegendTextColor: p.fg,
|
|
321
|
+
pieStrokeColor: p.bg,
|
|
322
|
+
pieOuterStrokeColor: p.bg,
|
|
323
|
+
pieTitleTextSize: `${MERMAID_FONT_PX}px`,
|
|
324
|
+
pieSectionTextSize: `${Math.round(MERMAID_FONT_PX * 0.8)}px`,
|
|
325
|
+
pieLegendTextSize: `${Math.round(MERMAID_FONT_PX * 0.8)}px`,
|
|
326
|
+
|
|
327
|
+
/* ── Git graph ─────────────────────────────────────────────────────────────────────────────
|
|
328
|
+
★★ THESE ARE NOT PAINTED AS HANDED IN, and the comment here used to say they were ("categorical
|
|
329
|
+
again, same five"). mermaid darkens every git slot by 25 HSL points before it draws
|
|
330
|
+
(mermaid.js, `if (this.darkMode) { lighten(git0,25) } else { darken(git0,25) }`), and darkMode
|
|
331
|
+
is deliberately never set here, so the ELSE branch runs in BOTH registers — including the dark
|
|
332
|
+
one, where darkening is the wrong direction. The five below are therefore a starting point
|
|
333
|
+
mermaid transforms, not the palette that reaches the screen.
|
|
334
|
+
Left as the series anyway: it is still the right input, and the alternative is pre-lightening
|
|
335
|
+
by 25 to cancel a library behaviour, which breaks the moment mermaid changes it. Recorded
|
|
336
|
+
rather than worked around. */
|
|
337
|
+
...Object.fromEntries(
|
|
338
|
+
Array.from({ length: 8 }, (_, i) => [`git${i}`, series[i % series.length]]),
|
|
339
|
+
),
|
|
340
|
+
gitInv0: p.onAccent,
|
|
341
|
+
gitBranchLabel0: p.onAccent,
|
|
342
|
+
|
|
343
|
+
/*
|
|
344
|
+
── THE TWELVE-SLOT CATEGORICAL, WHICH THE THEME HAD NEVER STATED ─────────────────────────
|
|
345
|
+
|
|
346
|
+
★★ UNSET, THESE COLLAPSE TO ONE COLOUR. Every family that colours itself categorically —
|
|
347
|
+
timeline, mindmap, kanban, radar, treemap — reads `cScale0..11`, and mermaid's base theme
|
|
348
|
+
DERIVES them when they are absent: cScale0 = primaryColor, cScale1 = secondaryColor,
|
|
349
|
+
cScale2 = tertiaryColor, then 3..11 as hue rotations of primaryColor. This theme points all
|
|
350
|
+
three of those at `p.surface`, deliberately, because a NODE is a Card — so the first three
|
|
351
|
+
slots were byte-identical and the rest were hue rotations of one near-neutral. A timeline
|
|
352
|
+
drawn today would have been a row of barely separable greys.
|
|
353
|
+
|
|
354
|
+
Stated from the deck's own five, cycled, the way git0..git7 already are. Twelve slots against
|
|
355
|
+
five colours means a family that needs more than five repeats one rather than inventing a
|
|
356
|
+
sixth — which is the deck's published ceiling and the same rule the pie takes.
|
|
357
|
+
|
|
358
|
+
⚠ AND EACH IS DARKENED BY 25 BEFORE IT REACHES THE PAGE, in BOTH registers, because mermaid
|
|
359
|
+
only lightens when `darkMode` is set and this theme deliberately never sets it. Same
|
|
360
|
+
transform the git slots take; the input is still right, and pre-lightening to cancel a
|
|
361
|
+
library behaviour breaks the moment that behaviour changes.
|
|
362
|
+
*/
|
|
363
|
+
...Object.fromEntries(
|
|
364
|
+
Array.from({ length: 12 }, (_, i) => [`cScale${i}`, series[i % series.length]]),
|
|
365
|
+
),
|
|
366
|
+
/*
|
|
367
|
+
★★ THE INK ON THOSE FILLS IS THE DARK ONE, AND THAT IS THE OPPOSITE OF THE DECK'S USUAL RULE.
|
|
368
|
+
A categorical fill normally takes `onAccent` — the deck pairs a saturated ground with light
|
|
369
|
+
ink. It is wrong here for one reason: mermaid DARKENS every cScale slot by 25 before painting
|
|
370
|
+
it, so the colour on the page is not the colour handed over, and the pairing has to be made
|
|
371
|
+
against what actually lands. MEASURED off the rendered timeline, white against dark ink on the
|
|
372
|
+
three bands a three-period timeline draws:
|
|
373
|
+
|
|
374
|
+
rgb(0,164,71) green white 3.28 dark 5.48
|
|
375
|
+
rgb(218,118,0) orange white 3.20 dark 5.62
|
|
376
|
+
rgb(0,132,200) blue white 4.09 dark 4.39
|
|
377
|
+
|
|
378
|
+
Dark wins on every one, and white only just clears the 3:1 large-text floor on two of them.
|
|
379
|
+
|
|
380
|
+
⚠ THE LIMITATION IS WORTH NAMING: this is one ink for twelve slots, chosen from three measured
|
|
381
|
+
fills. It is right for the deck's five as mermaid darkens them, and it would need revisiting if
|
|
382
|
+
the categorical ever gained a genuinely dark member — there the pairing would flip back. The
|
|
383
|
+
honest fix is a per-slot choice made against the DARKENED value, which needs mermaid's own
|
|
384
|
+
darken() rather than the palette's.
|
|
385
|
+
*/
|
|
386
|
+
...Object.fromEntries(
|
|
387
|
+
Array.from({ length: 12 }, (_, i) => [`cScaleLabel${i}`, p.fg]),
|
|
388
|
+
),
|
|
389
|
+
commitLabelColor: p.fg,
|
|
390
|
+
commitLabelBackground: p.bg,
|
|
391
|
+
/*
|
|
392
|
+
★★ THE COMMIT ID IS SIZED HERE, NOT IN CSS. mermaid inserts its <style> BEFORE
|
|
393
|
+
renderer.draw(), so this variable is live for every getBBox() the renderer takes, and
|
|
394
|
+
setupGraphViewbox then sets the viewBox from that bbox plus padding. Re-pointed AFTERWARDS by
|
|
395
|
+
a stylesheet rule, the box was measured on 10px glyphs: rotateCommitLabel swings a label 2.9x
|
|
396
|
+
longer than the one measured up-and-right of its dot, and the two `main` commits reached
|
|
397
|
+
y -48.12 against a viewBox top of -34.5, where the svg viewport cut them — the audit saw
|
|
398
|
+
"fleet v1"/"fleet v2" 12% hidden, 11-12px off the top. Stated here, the bbox contains them.
|
|
399
|
+
This changes no type size — 29px either way — only when mermaid learns it. Commit spacing does
|
|
400
|
+
not move: COMMIT_STEP is a fixed 40.
|
|
401
|
+
*/
|
|
402
|
+
commitLabelFontSize: `${MERMAID_EDGE_LABEL_FONT_PX}px`,
|
|
403
|
+
|
|
404
|
+
// ── Gantt ─────────────────────────────────────────────────────────────────────────────────
|
|
405
|
+
taskBkgColor: p.surface,
|
|
406
|
+
taskTextColor: p.fg,
|
|
407
|
+
taskTextOutsideColor: p.fg,
|
|
408
|
+
taskTextLightColor: p.onAccent,
|
|
409
|
+
taskBorderColor: p.borderStrong,
|
|
410
|
+
activeTaskBkgColor: p.accent,
|
|
411
|
+
activeTaskBorderColor: p.accent,
|
|
412
|
+
doneTaskBkgColor: p.surface,
|
|
413
|
+
doneTaskBorderColor: p.border,
|
|
414
|
+
/* ★★ A STATE, NOT A CATEGORY, and it only looked right by accident. This read `s4` — the fifth
|
|
415
|
+
series — which under the old brand band happened to be maple, the deck's DANGER family, so a
|
|
416
|
+
critical task came out red for the wrong reason. Re-pointing SERIES to the published palette
|
|
417
|
+
turns s4 into INDIGO, which would have made "critical" a calm blue and nothing would have
|
|
418
|
+
failed. Named against the semantic role instead, so the meaning is the source rather than the
|
|
419
|
+
ordering. Measured: on light this also lifts the task label from 3.42:1 to 4.60:1, because the
|
|
420
|
+
role is maple-500 where the old band took maple-600. */
|
|
421
|
+
critBkgColor: p.dangerMark,
|
|
422
|
+
critBorderColor: p.dangerMark,
|
|
423
|
+
gridColor: p.border,
|
|
424
|
+
todayLineColor: p.accent,
|
|
425
|
+
/* ★★ A SECTION IS A LANE, AND IT HAD NO GROUND TO SAY SO. mermaid bands the rows behind each
|
|
426
|
+
section and cycles THREE variables across four indices: section0 takes `sectionBkgColor`,
|
|
427
|
+
section2 takes `sectionBkgColor2`, and sections 1 and 3 take `altSectionBkgColor`. With the
|
|
428
|
+
page colour in the first and `altSectionBkgColor` left unset — mermaid's own white — both
|
|
429
|
+
bands on a two-section chart were invisible, so the section titles read as labels floating
|
|
430
|
+
beside the bars rather than as names for the rows they own.
|
|
431
|
+
|
|
432
|
+
Set so the cycle ALTERNATES: the tint on 0 and 2, the page on 1 and 3. The band is still the
|
|
433
|
+
quietest surface on the chart; it only has to be visible enough to bound a lane. */
|
|
434
|
+
sectionBkgColor: p.surface,
|
|
435
|
+
sectionBkgColor2: p.surface,
|
|
436
|
+
altSectionBkgColor: p.bg,
|
|
437
|
+
|
|
438
|
+
// ── Quadrant / journey / the odd ones out ─────────────────────────────────────────────────
|
|
439
|
+
quadrant1Fill: p.surface,
|
|
440
|
+
quadrant2Fill: p.bg,
|
|
441
|
+
quadrant3Fill: p.surface,
|
|
442
|
+
quadrant4Fill: p.bg,
|
|
443
|
+
quadrantPointFill: s1,
|
|
444
|
+
quadrantTitleFill: p.fg,
|
|
445
|
+
|
|
446
|
+
/*
|
|
447
|
+
── RADAR ──────────────────────────────────────────────────────────────────────────────────
|
|
448
|
+
Every one of these is READ by the radar renderer; none of them is shared with another family,
|
|
449
|
+
which is the usual shape (see the note above about mermaid's per-family variables having no
|
|
450
|
+
spine).
|
|
451
|
+
|
|
452
|
+
★★ THE LEGEND'S ROW PITCH IS A LITERAL 20 UNITS with no key and no selector behind it — the
|
|
453
|
+
row group is appended with no class at all — so nothing in this file can raise it and the size
|
|
454
|
+
below would be CAPPED at about 18 by a pitch it cannot move. It is not, because the renderer
|
|
455
|
+
re-pitches those rows after the fact: see spaceRadarLegend, which is also what centres the
|
|
456
|
+
block and seats each swatch against its own word. Stated here because the number below only
|
|
457
|
+
makes sense with that pass in place — set it larger with the pass removed and the rows collide.
|
|
458
|
+
|
|
459
|
+
★ AND THE AXIS LABEL IS ANCHORED, NOT MEASURED: it is placed at `radius * axisLabelFactor` and
|
|
460
|
+
grows OUTWARD from there, so raising the size without raising the factor walks the words off
|
|
461
|
+
the viewBox. The two move together, and the margins below are what pays for it.
|
|
462
|
+
*/
|
|
463
|
+
/*
|
|
464
|
+
★★ AND IT IS A NESTED OBJECT OF NUMBERS, not the flat px strings the rest of this file uses.
|
|
465
|
+
Written flat first and every one of them was inert — mermaid reads `this.radar?.axisColor`
|
|
466
|
+
against a `radar` block it builds in theme-base, so a top-level `axisColor` is not a wrong
|
|
467
|
+
value, it is not a value at all. The sixth instance of this family of defect in this file,
|
|
468
|
+
and the only one whose tell was that NOTHING changed rather than that something changed wrong.
|
|
469
|
+
*/
|
|
470
|
+
radar: {
|
|
471
|
+
axisColor: p.border,
|
|
472
|
+
axisStrokeWidth: DIAGRAM_STROKE_PX / 2,
|
|
473
|
+
axisLabelFontSize: MERMAID_EDGE_LABEL_FONT_PX,
|
|
474
|
+
graticuleColor: p.border,
|
|
475
|
+
graticuleOpacity: 1,
|
|
476
|
+
graticuleStrokeWidth: DIAGRAM_STROKE_PX / 2,
|
|
477
|
+
/*
|
|
478
|
+
★ A WASH, NOT A FILL. The polygon's STROKE is what states the shape at the deck's full edge
|
|
479
|
+
weight, so the area under it only has to say which curve owns which region — and the less
|
|
480
|
+
it says, the better the graticule underneath it reads. mermaid's 0.5 mixes the deck's green
|
|
481
|
+
and its orange into an olive that is in neither series; 0.28 kept the hues but still buried
|
|
482
|
+
the scale rings. At this value both curves keep their colour, every ring stays legible
|
|
483
|
+
through them, and the overlap still reads as an overlap rather than as a third colour.
|
|
484
|
+
*/
|
|
485
|
+
curveOpacity: 0.16,
|
|
486
|
+
curveStrokeWidth: DIAGRAM_STROKE_PX,
|
|
487
|
+
legendFontSize: MERMAID_LEGEND_FONT_PX,
|
|
488
|
+
legendBoxSize: Math.round(MERMAID_LEGEND_FONT_PX * 0.8),
|
|
489
|
+
},
|
|
490
|
+
fillType0: s1,
|
|
491
|
+
fillType1: s2,
|
|
492
|
+
fillType2: s3,
|
|
493
|
+
fillType3: s4,
|
|
494
|
+
fillType4: s5,
|
|
495
|
+
fillType5: s1,
|
|
496
|
+
fillType6: s2,
|
|
497
|
+
fillType7: s3,
|
|
498
|
+
};
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
/*
|
|
502
|
+
── THE FILLED NODE ────────────────────────────────────────────────────────────────────────────
|
|
503
|
+
|
|
504
|
+
The deck's four panel grounds, published to mermaid as node classes. An author writes
|
|
505
|
+
|
|
506
|
+
A[Rider opens app]:::vivid
|
|
507
|
+
|
|
508
|
+
and the node takes the same fill and ink a `background: vivid` panel takes on that register.
|
|
509
|
+
|
|
510
|
+
★ WHY CLASSES AND NOT `classDef`. mermaid's own `classDef` is authored per diagram, in literal
|
|
511
|
+
colour, inside the slide — four grounds x two registers x every diagram that wants one, restated
|
|
512
|
+
by hand. `:::name` only ATTACHES the class; what it means is decided here, once, from deck.json's
|
|
513
|
+
own panel table. So a node cannot drift from the panel it is named after, and a fifth ground
|
|
514
|
+
reaches every diagram without touching a slide.
|
|
515
|
+
|
|
516
|
+
★★ AND THE PAIR IS WHY THIS IS SAFE. deck.json stores a ground and the ink measured against it
|
|
517
|
+
TOGETHER, and background() hands back both — so a filled node cannot end up with a light ink on a
|
|
518
|
+
dark ground the way it could if a slide named two colours by hand. That is the whole argument for
|
|
519
|
+
spending a class on this rather than letting authors reach for `style A fill:#...`.
|
|
520
|
+
|
|
521
|
+
The border is the fill carried toward its own ink by deck.json's own `border-strong` step — the
|
|
522
|
+
same mix `nodeBorder` takes for the default node, READ from the table rather than copied, so the
|
|
523
|
+
two cannot separate. That keeps a filled node's edge at the presence of a plain one; the alternative,
|
|
524
|
+
leaving the default border in place, puts a grey ring on a saturated green.
|
|
525
|
+
|
|
526
|
+
★ `.node` IS TWO FAMILIES, measured. A state diagram's boxes are `g.node …statediagram-state`, so
|
|
527
|
+
`class Charged vivid` fills one exactly as `:::vivid` fills a flowchart node — the specimen draws
|
|
528
|
+
that on its dark slide. What is NOT matched is a subgraph (`g.cluster`), and the families that
|
|
529
|
+
have no node at all: a pie slice, a gantt bar and a quadrant point each carry their own meaning
|
|
530
|
+
and their own variables.
|
|
531
|
+
*/
|
|
532
|
+
export const NODE_BORDER_MIX = derivedMix('border-strong');
|
|
533
|
+
|
|
534
|
+
export const MERMAID_BACKGROUNDS: readonly string[] = BACKGROUND_NAMES;
|
|
535
|
+
|
|
536
|
+
export function mermaidBackgroundRules(register: Register): string[] {
|
|
537
|
+
return MERMAID_BACKGROUNDS.flatMap((name) => {
|
|
538
|
+
const { fill, ink } = background(name, register);
|
|
539
|
+
const stroke = mixOklab(fill, ink, NODE_BORDER_MIX);
|
|
540
|
+
return [
|
|
541
|
+
/* ★★ DESCENDANT, NOT `>`, and the shape vocabulary is why. Measured across the flowchart's
|
|
542
|
+
own shapes: `rect`, `round`, `subroutine`, `cylinder`, `circle`, `rhombus`, `hexagon`,
|
|
543
|
+
`parallelogram` and `trapezoid` hang their outline directly under `g.node` — but
|
|
544
|
+
`stadium`, the asymmetric flag and `doublecircle` wrap theirs in an inner
|
|
545
|
+
`g.label-container`, so a child combinator paints six of the nine and silently skips
|
|
546
|
+
three. This is the same selector shape mermaid's own node rule uses, deliberately: an
|
|
547
|
+
override that matches less than the rule it overrides is a background that works on most shapes.
|
|
548
|
+
The cost is the 0x0 measuring rect inside `g.label`, which mermaid's rule already fills
|
|
549
|
+
and which paints nothing at either size. */
|
|
550
|
+
`.node.${name} rect,.node.${name} polygon,.node.${name} path,`
|
|
551
|
+
+ `.node.${name} circle,.node.${name} ellipse{fill:${fill};stroke:${stroke};}`,
|
|
552
|
+
// Both label forms — see the `htmlLabels` note in mermaidConfig. `color` carries to the <p>
|
|
553
|
+
// mermaid nests inside the span; `fill` is what an SVG <text> answers to.
|
|
554
|
+
`.node.${name} span,.node.${name} text{color:${ink};fill:${ink};}`,
|
|
555
|
+
];
|
|
556
|
+
});
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
/*
|
|
560
|
+
── REGISTER-DEPENDENT INK THAT IS NOT A BACKGROUND ─────────────────────────────────────────────────
|
|
561
|
+
|
|
562
|
+
★★ A LABEL ON A CATEGORICAL FILL TAKES `onAccent`, NEVER `fg`, and the ramp level is the reason.
|
|
563
|
+
The published categorical is TW v4's 600 step on light (#00a447, #da7600, #0084c8 …) and its 400
|
|
564
|
+
step on dark. The house rule: at 500 and above a fill carries WHITE (or a -100/-50 tint); at 400
|
|
565
|
+
and below it can carry a dark ink. `p.onAccent` is exactly that pair already — #ffffff on light,
|
|
566
|
+
the deep green on dark — so naming it satisfies the rule in both registers by construction rather
|
|
567
|
+
than by two hand-picked colours that have to be re-checked whenever the palette moves.
|
|
568
|
+
|
|
569
|
+
A journey is the one family that got this wrong: its section and task labels inherited `fg`, so a
|
|
570
|
+
near-black sat on a 600-level green. The pie has always taken `pieSectionTextColor: p.onAccent`,
|
|
571
|
+
which is the same decision made in the one place mermaid gave it a variable.
|
|
572
|
+
|
|
573
|
+
These live apart from `mermaidBackgroundRules` because they are not backgrounds — no panel ground is involved,
|
|
574
|
+
and they apply whether or not an author names a class.
|
|
575
|
+
*/
|
|
576
|
+
export function mermaidInkRules(register: Register): string[] {
|
|
577
|
+
const p = palette(register);
|
|
578
|
+
return [
|
|
579
|
+
/*
|
|
580
|
+
The journey's own label hook: mermaid wraps the foreignObject text in a div carrying the
|
|
581
|
+
section/task class, so this reaches the label WITHOUT touching a flowchart's node labels,
|
|
582
|
+
which are the same foreignObject shape one family over.
|
|
583
|
+
|
|
584
|
+
★ THE INNER `.label` MUST BE NAMED TOO. mermaid sets `#id .label { color: … }` directly on the
|
|
585
|
+
div that holds the text, and a colour set ON an element beats one inherited from its parent no
|
|
586
|
+
matter what the specificity of the parent's rule is. Naming only the section here loses to it
|
|
587
|
+
every time.
|
|
588
|
+
*/
|
|
589
|
+
`.journey-section,.task,.journey-section .label,.task .label{color:${p.onAccent};}`,
|
|
590
|
+
// The satisfaction face is mermaid's own cornsilk on a mid grey, which is the one thing on this
|
|
591
|
+
// chart that belongs to no palette at all.
|
|
592
|
+
`.face{fill:${p.bg};stroke:${p.fg};}`,
|
|
593
|
+
// The guide the face slides along. mermaid writes its colour and dash INLINE, hence the hammer.
|
|
594
|
+
`.task-line{stroke:${p.fgSubtle} !important;}`,
|
|
595
|
+
/*
|
|
596
|
+
★★ A GANTT LABEL INSIDE A DARK BAR NEEDS THE OTHER INK, AND NO THEME VARIABLE CAN SAY SO.
|
|
597
|
+
mermaid paints `.activeText*` and `.doneText*` from ONE variable — `taskTextDarkColor` — and
|
|
598
|
+
uses that same variable for `.taskTextOutsideLeft/Right`, which sit on the PAGE. One value for
|
|
599
|
+
a label on a dark bar and a label on cream: whatever it is set to, one of the two is wrong.
|
|
600
|
+
|
|
601
|
+
Measured before this rule: `#191617` on the active bar's `#046c4c` scored 2.60:1, under even
|
|
602
|
+
the 3:1 large-text floor, on a slide the audit cannot see because a diagram renders into a
|
|
603
|
+
shadow root.
|
|
604
|
+
|
|
605
|
+
So the ink is split by SELECTOR instead. An inside label carries `taskText`; an outside one
|
|
606
|
+
carries `taskTextOutsideLeft` and never `taskText`, so naming the former reaches exactly the
|
|
607
|
+
labels that sit on a fill and leaves the page ones alone. `!important` is required because
|
|
608
|
+
mermaid's own `.activeText*` rule carries it.
|
|
609
|
+
|
|
610
|
+
Both saturated bars take `onAccent`, the same pair the pie and the journey take: the active
|
|
611
|
+
bar is the accent and the critical one the danger mark, and on light both are 500-or-above
|
|
612
|
+
fills, which carry white. ⚠ Only the ACTIVE case is drawn inside today — the specimen's
|
|
613
|
+
critical bar has a label too long for it, so mermaid puts that one outside. The crit rule is
|
|
614
|
+
the same mechanism stated once rather than a second thing to remember later.
|
|
615
|
+
*/
|
|
616
|
+
...[0, 1, 2, 3].flatMap((i) => [
|
|
617
|
+
`.taskText.activeText${i}{fill:${p.onAccent} !important;}`,
|
|
618
|
+
`.taskText.critText${i}{fill:${p.onAccent} !important;}`,
|
|
619
|
+
]),
|
|
620
|
+
// The lane band, at the strength it was given rather than at mermaid's blanket 0.2 — which
|
|
621
|
+
// washed every section ground back to the page it sits on.
|
|
622
|
+
`.section{opacity:1;}`,
|
|
623
|
+
/*
|
|
624
|
+
★★ THE ACTOR DOT IS PAINTED FROM CSS BECAUSE ITS CONFIG KEY CANNOT WORK. `journey.actorColours`
|
|
625
|
+
is a real key on a real path (`getConfig().journey.actorColours`), and setting it changes
|
|
626
|
+
nothing — mermaid merges config with `assignWithDepth`, whose array branch is
|
|
627
|
+
|
|
628
|
+
if (Array.isArray(src) && Array.isArray(dst)) { src.forEach(s => { if (!dst.includes(s)) dst.push(s) }) }
|
|
629
|
+
|
|
630
|
+
i.e. it APPENDS to the default array rather than replacing it. Index 0 stays mermaid's
|
|
631
|
+
DarkSeaGreen and index 1 its LawnChartreuse, whatever is passed. Every other journey key here
|
|
632
|
+
is a scalar and lands normally, which is why this one looked like it had worked.
|
|
633
|
+
|
|
634
|
+
The dot's colour is a presentation ATTRIBUTE, and CSS outranks those, so the same five the pie
|
|
635
|
+
spends reach it from here instead. The dead key is not left in the config beside the live ones:
|
|
636
|
+
a setting that reads as configuration and does nothing is the more expensive kind of wrong.
|
|
637
|
+
*/
|
|
638
|
+
...Array.from({ length: 8 }, (_, i) => {
|
|
639
|
+
const fill = SERIES[register][i % SERIES[register].length];
|
|
640
|
+
return `circle.actor-${i}{fill:${fill};stroke:${mixOklab(fill, p.fg, NODE_BORDER_MIX)};}`;
|
|
641
|
+
}),
|
|
642
|
+
/*
|
|
643
|
+
The band's own edge, per categorical slot. It is derived the way a filled node's is — the fill
|
|
644
|
+
carried 26% toward an ink — but toward the PAGE's ink rather than the label's. A band sits ON
|
|
645
|
+
the page, so that is the relationship its edge has to hold; carried toward `onAccent` it would
|
|
646
|
+
go LIGHTER than the fill on light, and a band whose edge is paler than the band reads as a
|
|
647
|
+
glow rather than a border. Both registers come out right: darker on cream, lighter on the
|
|
648
|
+
dark ground, definition either way.
|
|
649
|
+
|
|
650
|
+
Indexed 0-7 because mermaid indexes the slots and cycles the five past the fifth, exactly as
|
|
651
|
+
the pie does.
|
|
652
|
+
*/
|
|
653
|
+
...Array.from({ length: 8 }, (_, i) => {
|
|
654
|
+
const fill = SERIES[register][i % SERIES[register].length];
|
|
655
|
+
return `.task-type-${i},.section-type-${i}{stroke:${mixOklab(fill, p.fg, NODE_BORDER_MIX)};}`;
|
|
656
|
+
}),
|
|
657
|
+
/*
|
|
658
|
+
★★ A MINDMAP LABEL SITS DIRECTLY ON A CATEGORICAL FILL, so it takes `onAccent` — the same
|
|
659
|
+
answer the journey's does, and for the same reason. It is the one family that paints its slot
|
|
660
|
+
SOLID: everywhere else the categorical is a tint, a stroke or a band this theme has repainted,
|
|
661
|
+
and there the shared `cScaleLabel` (the page ink) is correct.
|
|
662
|
+
|
|
663
|
+
MEASURED against the five as they actually land — mermaid does not darken a slot the theme
|
|
664
|
+
states, so these are the deck's own colours:
|
|
665
|
+
|
|
666
|
+
#00a447 green dark 5.48 white 3.28
|
|
667
|
+
#da7600 orange dark 5.62 white 3.20
|
|
668
|
+
#0084c8 blue dark 4.39 white 4.09
|
|
669
|
+
#4f39f6 indigo dark 2.78 white 6.46
|
|
670
|
+
#e30076 magenta dark 3.87 white 4.64
|
|
671
|
+
|
|
672
|
+
★★ AND THIS IS THE CASE THE cScaleLabel NOTE PREDICTED. It said one ink for twelve slots was
|
|
673
|
+
right for the deck's five and "would need revisiting if the categorical ever gained a genuinely
|
|
674
|
+
dark member — there the pairing would flip back". Indigo is that member: at 2.78 the page ink
|
|
675
|
+
is under the 3:1 floor a 36px label is held to, and it was the audit on this slide that caught
|
|
676
|
+
it. White is over the floor on every one of the five, so the family takes one ink rather than
|
|
677
|
+
a per-slot mixture that would put two colours of label in one picture.
|
|
678
|
+
|
|
679
|
+
⚠ SCOPED TO `.mindmap-node`, because `.section-N` is shared with kanban — whose columns this
|
|
680
|
+
theme paints the surface, where white would be invisible. Twelve, matching the slots: a mindmap
|
|
681
|
+
has as many sections as the author writes branches.
|
|
682
|
+
*/
|
|
683
|
+
/* ★ THE ROOT IS `section--1`, NOT A SLOT — mermaid indexes the branches from 0 and gives the
|
|
684
|
+
node they hang off minus one. Left out, it was the one dark label in a picture of white ones,
|
|
685
|
+
which is the mixture this rule exists to avoid; it draws on the same green the journey and the
|
|
686
|
+
timeline already put white on. */
|
|
687
|
+
...['-1', ...Array.from({ length: 12 }, (_, i) => String(i))].map((slot) => (
|
|
688
|
+
`.mindmap-node.section-${slot} text,.mindmap-node.section-${slot} span{fill:${p.onAccent};color:${p.onAccent};}`
|
|
689
|
+
)),
|
|
690
|
+
];
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
/**
|
|
694
|
+
* The full mermaid config, ready for `mermaid.initialize()`.
|
|
695
|
+
*
|
|
696
|
+
* `theme: 'base'` is not a default — it is the ONLY theme that reads `themeVariables` at all.
|
|
697
|
+
* 'default', 'dark', 'neutral' and 'forest' (mermaid's own, unrelated to this brand) each ship a
|
|
698
|
+
* fixed palette and ignore the table above entirely, so naming any of them silently reverts every
|
|
699
|
+
* colour on this page.
|
|
700
|
+
*/
|
|
701
|
+
/*
|
|
702
|
+
── WHY THE SPACING IS MERMAID'S OWN ───────────────────────────────────────────────────────────
|
|
703
|
+
|
|
704
|
+
`fitToColumn` does not refuse a diagram that outgrows its slide, it SHRINKS it, so a unit of gap
|
|
705
|
+
is paid for twice: once in width, and again in the type it costs when the drawing is scaled to
|
|
706
|
+
fit. That makes tightening the gaps look like free legibility. MEASURED on the specimen's
|
|
707
|
+
flowchart in an 872px column:
|
|
708
|
+
|
|
709
|
+
20/60/70 mermaid's own, shipped natural 1603 units shrink 0.544 labels at 19.6px
|
|
710
|
+
16/44/52 a third off natural 1459 units shrink 0.598 labels at 21.5px
|
|
711
|
+
10/24/28 packed natural 1255 units shrink 0.695 labels at 25.0px
|
|
712
|
+
|
|
713
|
+
★★ AND THE DECK SHIPS THE TOP ROW ANYWAY. The gain is real and it is not what a diagram is for:
|
|
714
|
+
packed nodes read as a denser object than the Card grid with the same content beside them, and a
|
|
715
|
+
deck whose diagrams are tuned for maximum ink stops looking like one deck. A diagram that will not
|
|
716
|
+
fit at mermaid's own spacing is a diagram that wants to be two slides.
|
|
717
|
+
|
|
718
|
+
★ THE TYPE IS NOT THE LEVER EITHER, which is the trap under all of this. Dropping fontSize 36 to
|
|
719
|
+
30 measured 18.0px rendered — WORSE than leaving it alone. mermaid lays a box out around the text
|
|
720
|
+
it measures, but the gaps between boxes are config numbers that do not follow the font, so the cut
|
|
721
|
+
bought 9.4% of width for 16.7% of type.
|
|
722
|
+
|
|
723
|
+
⚠ The fragment tab keys below are TYPE, not spacing. `labelBoxWidth`, `labelBoxHeight` and
|
|
724
|
+
`boxTextMargin` are scaled to the deck's rung because mermaid never measures the keyword against
|
|
725
|
+
the box it draws for it.
|
|
726
|
+
*/
|
|
727
|
+
|
|
728
|
+
export function mermaidConfig(register: Register) {
|
|
729
|
+
return {
|
|
730
|
+
startOnLoad: false,
|
|
731
|
+
theme: 'base' as const,
|
|
732
|
+
themeVariables: mermaidThemeVariables(register),
|
|
733
|
+
fontFamily: MERMAID_FONT_FAMILY,
|
|
734
|
+
/*
|
|
735
|
+
★★ TOP-LEVEL `fontSize` IS THE ONE THAT WORKS, and mermaid's own documentation points the
|
|
736
|
+
other way. `sequence.actorFontSize`, `.messageFontSize` and `.noteFontSize` are all declared
|
|
737
|
+
in mermaid 11's config schema (dist/config.type.d.ts) and all THREE are ignored by the
|
|
738
|
+
renderer. Measured against mermaid 11.17.2 in a live page, same diagram, one variable changed:
|
|
739
|
+
|
|
740
|
+
sequence: { actorFontSize: 36, … } -> style="… font-size: 16px …"
|
|
741
|
+
fontSize: 36 -> style="… font-size: 36px …"
|
|
742
|
+
|
|
743
|
+
The visible symptom was not a wrong number, it was a SMALL DIAGRAM: mermaid lays every box out
|
|
744
|
+
around the text it measures, so at 16px the whole sequence diagram came out about a third of
|
|
745
|
+
the size of the flowchart beside it, and the fit-to-column step below faithfully preserved
|
|
746
|
+
that. It reads as "mermaid just draws sequence diagrams small", which is why the per-family
|
|
747
|
+
keys are not left in place as harmless belt-and-braces — they are a false explanation sitting
|
|
748
|
+
next to the real one.
|
|
749
|
+
*/
|
|
750
|
+
fontSize: MERMAID_FONT_PX,
|
|
751
|
+
/*
|
|
752
|
+
⚠ `htmlLabels: false` DOES NOT TAKE EFFECT, measured. Read in the rendered shadow root of the
|
|
753
|
+
specimen's flowchart against mermaid 11.17.2, a node label's ancestor chain is
|
|
754
|
+
`span.nodeLabel < div < foreignObject < g.label` — an HTML label, which is what this key asks
|
|
755
|
+
mermaid not to emit. Every node carries `data-look="classic"`, i.e. mermaid 11's unified
|
|
756
|
+
renderer, which reads the label mode from its own place and not from this section.
|
|
757
|
+
|
|
758
|
+
It is left set rather than deleted because it is the documented request and costs nothing, but
|
|
759
|
+
nothing downstream may assume SVG <text>: mermaidBackgroundRules() names `span` alongside `text` for
|
|
760
|
+
exactly this reason, and any rule written for one form has to cover the other.
|
|
761
|
+
*/
|
|
762
|
+
flowchart: { htmlLabels: false, curve: 'basis', padding: 20, nodeSpacing: 60, rankSpacing: 70 },
|
|
763
|
+
/*
|
|
764
|
+
Spacing only. Every FONT key this section accepts is dead (see `fontSize` above), so listing
|
|
765
|
+
one here would be decoration that reads like configuration.
|
|
766
|
+
|
|
767
|
+
★★ THE LAST THREE ARE THE FRAGMENT TAB, AND THEY ARE NOT OPTIONAL AT THIS TYPE SIZE. An
|
|
768
|
+
`alt`/`opt`/`loop` fragment hangs its keyword in a tab mermaid draws at a FIXED
|
|
769
|
+
`labelBoxWidth` x `labelBoxHeight` — 50x20 by default, with 5px of text margin, all sized for
|
|
770
|
+
its own 16px type. The deck runs at 36, and mermaid never measures the word against the box:
|
|
771
|
+
it draws a 50-wide tab and then paints a word wider than it. MEASURED in the exported PDF
|
|
772
|
+
before this: "opt" broke three of the tab's four edges, the descender falling 6px clear of the
|
|
773
|
+
bottom. Same failure class the gantt block below exists for, and the fix is the same — scale
|
|
774
|
+
the box with the type rather than reach for a stylesheet, which would only make the text in a
|
|
775
|
+
box drawn for smaller text larger still.
|
|
776
|
+
*/
|
|
777
|
+
sequence: {
|
|
778
|
+
useMaxWidth: true,
|
|
779
|
+
diagramMarginX: 40,
|
|
780
|
+
diagramMarginY: 20,
|
|
781
|
+
boxMargin: 20,
|
|
782
|
+
actorMargin: 80,
|
|
783
|
+
labelBoxWidth: toTagScale(50),
|
|
784
|
+
labelBoxHeight: toTagScale(20),
|
|
785
|
+
boxTextMargin: toTagScale(5),
|
|
786
|
+
/*
|
|
787
|
+
★★ AN `actor` LABEL IS DRAWN BELOW THE BAND THE LAYOUT RESERVES FOR IT. drawActorTypeActor
|
|
788
|
+
centres the name at actorY + 35 + figureBBox/2 = actorY + 67.5, while the bounds only ever
|
|
789
|
+
advance by the stick figure's own 65 — so the room under the last row is diagramMarginY +
|
|
790
|
+
bottomMarginAdj = 21. mermaid's own 14px label needs 8.5 of it; the deck's 36px needs 22.54,
|
|
791
|
+
and "Rider" fell through the floor of the viewBox (2px cut, 9% hidden). 6 buys 5 units back.
|
|
792
|
+
`sequence.height` is NOT the lever — an actor's label y comes from the figure bbox — and
|
|
793
|
+
`diagramMarginY` is worse, because viewBox y is -diagramMarginY, so raising it pads the TOP
|
|
794
|
+
by the same amount. Applied only when mirrorActors is set, which is the default and the only
|
|
795
|
+
case with a bottom row at all.
|
|
796
|
+
*/
|
|
797
|
+
bottomMarginAdj: 6,
|
|
798
|
+
},
|
|
799
|
+
/*
|
|
800
|
+
★★ THE GANTT KEYS ARE LIVE, WHICH IS THE OPPOSITE OF `sequence` ABOVE — so they are set here
|
|
801
|
+
rather than left to the top-level `fontSize`, which this family ignores. Measured, same
|
|
802
|
+
diagram, one key changed: task and section text went 11px -> 36px with `gantt.fontSize`, and
|
|
803
|
+
the bars re-laid out around it. That is the whole reason to reach for a per-family key: it
|
|
804
|
+
moves the LAYOUT, where a stylesheet rule would only enlarge text inside boxes drawn for a
|
|
805
|
+
smaller one.
|
|
806
|
+
|
|
807
|
+
`barHeight` and the paddings follow the type rather than being picked: a 36px label needs a
|
|
808
|
+
bar it fits inside, and the two left paddings are what stop a section title and an outside
|
|
809
|
+
task label from colliding at that size.
|
|
810
|
+
|
|
811
|
+
⚠ TWO SIZES THIS SECTION CANNOT REACH — the chart title (18px) and the axis ticks (10px).
|
|
812
|
+
Neither has a config key, and both are pure text with no box laid out around them, so
|
|
813
|
+
ROOM_SCALE_RULES takes them instead. See the gantt rules there.
|
|
814
|
+
*/
|
|
815
|
+
gantt: {
|
|
816
|
+
useMaxWidth: true,
|
|
817
|
+
fontSize: MERMAID_FONT_PX,
|
|
818
|
+
sectionFontSize: MERMAID_FONT_PX,
|
|
819
|
+
barHeight: 56,
|
|
820
|
+
barGap: 14,
|
|
821
|
+
topPadding: 60,
|
|
822
|
+
leftPadding: 220,
|
|
823
|
+
gridLineStartPadding: 40,
|
|
824
|
+
titleTopMargin: 40,
|
|
825
|
+
},
|
|
826
|
+
/*
|
|
827
|
+
★ THE QUADRANT'S SIZES ARE ALL KEYS, unlike the gantt's title and ticks — every piece of text
|
|
828
|
+
on this chart has one, so none of it is left to a stylesheet. The point labels take the 0.8
|
|
829
|
+
step a caption takes; everything else is the body-sm rung the rest of the deck sets at.
|
|
830
|
+
*/
|
|
831
|
+
quadrantChart: {
|
|
832
|
+
useMaxWidth: true,
|
|
833
|
+
chartWidth: 900,
|
|
834
|
+
chartHeight: 600,
|
|
835
|
+
titleFontSize: MERMAID_FONT_PX,
|
|
836
|
+
quadrantLabelFontSize: MERMAID_FONT_PX,
|
|
837
|
+
xAxisLabelFontSize: Math.round(MERMAID_FONT_PX * 0.8),
|
|
838
|
+
yAxisLabelFontSize: Math.round(MERMAID_FONT_PX * 0.8),
|
|
839
|
+
pointLabelFontSize: Math.round(MERMAID_FONT_PX * 0.8),
|
|
840
|
+
pointRadius: QUADRANT_POINT_RADIUS,
|
|
841
|
+
/*
|
|
842
|
+
★★ THE LABEL'S GAP IS MEASURED FROM THE POINT'S CENTRE, NOT ITS EDGE, so a padding smaller
|
|
843
|
+
than the radius puts the word INSIDE the dot. mermaid ships `pointRadius: 5` beside
|
|
844
|
+
`pointTextPadding: 5` — equal, which is what keeps its own default just clear. This theme
|
|
845
|
+
raised the radius to ${QUADRANT_POINT_RADIUS} and left the padding at mermaid's 5, so the
|
|
846
|
+
label's hanging baseline landed 7px inside the dot and "Lambeth" sat across it.
|
|
847
|
+
|
|
848
|
+
Derived from the radius so the two cannot drift apart again, plus a quarter of the label's
|
|
849
|
+
own rung as the gap — the type is ${Math.round(MERMAID_FONT_PX * 0.8)}px here against the 16px
|
|
850
|
+
mermaid's 5 was drawn for, so a fixed number would be too tight the moment the deck's scale moved.
|
|
851
|
+
*/
|
|
852
|
+
pointTextPadding: QUADRANT_POINT_RADIUS + Math.round(MERMAID_FONT_PX * 0.25),
|
|
853
|
+
quadrantPadding: 12,
|
|
854
|
+
},
|
|
855
|
+
/*
|
|
856
|
+
★★ THE JOURNEY'S BOX IS THE CONFIG, NOT THE TYPE. `taskFontSize` is live — `byTspan` sets it
|
|
857
|
+
straight onto the text — but on its own it makes things worse, because the label is drawn into
|
|
858
|
+
a foreignObject of a FIXED `width` x `height` (150 x 50 by default). Raise the type without
|
|
859
|
+
raising the box and every label wraps and then clips at the box's own edge, which is what a
|
|
860
|
+
first pass at this family looks like: truncated words in boxes that look deliberate.
|
|
861
|
+
|
|
862
|
+
So the box is stated with the type it has to hold. Everything else here is the air around it.
|
|
863
|
+
*/
|
|
864
|
+
journey: {
|
|
865
|
+
useMaxWidth: true,
|
|
866
|
+
taskFontSize: MERMAID_FONT_PX,
|
|
867
|
+
taskFontFamily: MERMAID_FONT_FAMILY,
|
|
868
|
+
width: 340,
|
|
869
|
+
height: 112,
|
|
870
|
+
boxMargin: 16,
|
|
871
|
+
boxTextMargin: 12,
|
|
872
|
+
taskMargin: 64,
|
|
873
|
+
leftMargin: 200,
|
|
874
|
+
diagramMarginX: 40,
|
|
875
|
+
diagramMarginY: 24,
|
|
876
|
+
},
|
|
877
|
+
er: { useMaxWidth: true },
|
|
878
|
+
state: { useMaxWidth: true, nodeSpacing: 60, rankSpacing: 70 },
|
|
879
|
+
/*
|
|
880
|
+
⚠ THE COMMIT LABEL STAYS ROTATED, AND THAT IS mermaid's COLLISION AVOIDANCE rather than a
|
|
881
|
+
style. It tilts every commit id 45° so long ids can sit close together, and the spacing between
|
|
882
|
+
commits is laid out on the assumption that they do. Set straight — tried, and measured — the
|
|
883
|
+
ids at this deck's rung overlapped each other outright: `bay picker` and `battery check` ran
|
|
884
|
+
into one word. The layout is computed from mermaid's own 10px and does not re-run when the
|
|
885
|
+
theme re-points the size in CSS, which is the same seam the ER edge label and the sequence
|
|
886
|
+
fragment tab each sit on.
|
|
887
|
+
|
|
888
|
+
So the size moves and the angle does not.
|
|
889
|
+
*/
|
|
890
|
+
/*
|
|
891
|
+
── RADAR ──────────────────────────────────────────────────────────────────────────────────
|
|
892
|
+
★★ THE MARGINS ARE NOT DECORATION HERE, THEY ARE THE ONLY ROOM THE LABELS HAVE. Radar's plot
|
|
893
|
+
box is fixed at `width` x `height` and everything outside it — the axis names and the legend —
|
|
894
|
+
is placed against the MARGIN rather than measured. mermaid's 50 was sized for its own ~12px
|
|
895
|
+
label; at the deck's 29px rung a five-letter axis name is more than twice as long, and the
|
|
896
|
+
legend's text starts at three quarters of the way into the right margin, so each unit of
|
|
897
|
+
`marginRight` buys the legend only a quarter unit of clear room. 170 is what the longest axis
|
|
898
|
+
name needs beside a plot of 600, and the left margin matches it so the plot stays centred.
|
|
899
|
+
|
|
900
|
+
★ THE TOP AND BOTTOM ARE SMALLER ON PURPOSE, not by oversight. Nothing sits in them but the
|
|
901
|
+
diagram title, which this deck never draws — the heading above the frame is the title. A
|
|
902
|
+
margin is dead space inside the viewBox, fitToColumn caps against that viewBox, and every
|
|
903
|
+
unit of unused gutter is therefore drawn at the cost of the picture.
|
|
904
|
+
|
|
905
|
+
★ `axisLabelFactor` rises with the label, for the reason stated beside `axisLabelFontSize`.
|
|
906
|
+
*/
|
|
907
|
+
radar: {
|
|
908
|
+
useMaxWidth: true,
|
|
909
|
+
width: 600,
|
|
910
|
+
height: 600,
|
|
911
|
+
marginTop: 90,
|
|
912
|
+
marginRight: 170,
|
|
913
|
+
marginBottom: 90,
|
|
914
|
+
marginLeft: 170,
|
|
915
|
+
/*
|
|
916
|
+
★★ THE SPOKE STOPS ON THE RING, NOT AT THE RADIUS — and those are not the same place once
|
|
917
|
+
the graticule is a polygon. mermaid fillets every vertex with a quadratic whose CONTROL is
|
|
918
|
+
the true corner, so the ring never reaches it: the curve's midpoint is (P0 + 2C + P2) / 4,
|
|
919
|
+
which on the outer ring puts the line at 297.06 against a nominal 300. An axis drawn to the
|
|
920
|
+
full radius therefore overshoots the ring it should meet by about 3 units, plus half its own
|
|
921
|
+
stroke — measured on the render, and visible as a spur past every corner.
|
|
922
|
+
|
|
923
|
+
297.06 / 300 is 0.9902. 0.99 lands the spoke six hundredths of a unit inside the ring, which
|
|
924
|
+
is closed. ★ The sagitta is a FIXED chord in user units, not a fraction of the radius (the
|
|
925
|
+
inner rings fillet by the same 8.09 either side), so this factor is right for the outermost
|
|
926
|
+
ring specifically — which is the only one an axis line has to meet.
|
|
927
|
+
*/
|
|
928
|
+
axisScaleFactor: 0.99,
|
|
929
|
+
axisLabelFactor: 1.14,
|
|
930
|
+
/*
|
|
931
|
+
⚠ NO `curveTension` HERE, DELIBERATELY. It is the control-point tension for the cubic mermaid
|
|
932
|
+
draws through the data points — and it is consulted ONLY on the circular graticule. This deck
|
|
933
|
+
draws the polygon one, where the curve is a straight-sided <polygon> and the key is never read.
|
|
934
|
+
Setting it would be a number that looks like configuration and configures nothing, which is the
|
|
935
|
+
shape of every dead key this file already records.
|
|
936
|
+
*/
|
|
937
|
+
},
|
|
938
|
+
/*
|
|
939
|
+
── TREEMAP ────────────────────────────────────────────────────────────────────────────────
|
|
940
|
+
★★ THE LEAF TYPE LADDER IS CHOSEN BY LEAF COUNT, NOT BY CONFIG: 38px at twenty leaves or
|
|
941
|
+
fewer, 16px past that, then shrunk in a loop that MEASURES the fitted text and stops when it
|
|
942
|
+
fits. So the deck states no size here at all. `!important` would win the declaration and lose
|
|
943
|
+
the diagram — the shrink loop measures at mermaid's size, and a label forced larger than the
|
|
944
|
+
cell it was fitted to is culled outright by the family's own `display:none` guard.
|
|
945
|
+
|
|
946
|
+
38 against the deck's 36 rung is a coincidence worth naming rather than relying on: it holds
|
|
947
|
+
only while a treemap here stays under twenty leaves, which is also the point at which the
|
|
948
|
+
picture stops being readable from the back of a room.
|
|
949
|
+
|
|
950
|
+
The box is fixed at `nodeWidth × 10` by `nodeHeight × 10` before any data is read, so these
|
|
951
|
+
two are an ASPECT RATIO rather than a size — fitToColumn does the sizing.
|
|
952
|
+
*/
|
|
953
|
+
treemap: { useMaxWidth: true, nodeWidth: 100, nodeHeight: 44 },
|
|
954
|
+
/*
|
|
955
|
+
── MINDMAP ────────────────────────────────────────────────────────────────────────────────
|
|
956
|
+
⚠ `padding` IS THE ONLY KEY THAT REACHES THE BOX, and it does not reach it evenly: a default
|
|
957
|
+
node is laid out at eight half-paddings wide and TWO high, a 4:1 ratio fixed in code. At the
|
|
958
|
+
deck's 36px rung mermaid's 10 leaves five units above and below the cap height, which is far
|
|
959
|
+
too tight, so this is raised to what the vertical half needs and the horizontal is simply
|
|
960
|
+
wider than it would otherwise be.
|
|
961
|
+
*/
|
|
962
|
+
mindmap: { useMaxWidth: true, padding: 28 },
|
|
963
|
+
/*
|
|
964
|
+
── KANBAN ─────────────────────────────────────────────────────────────────────────────────
|
|
965
|
+
`ticketBaseUrl` is deliberately unset: a ticket id renders as plain text rather than a link,
|
|
966
|
+
because a deck is not a place anyone can click.
|
|
967
|
+
*/
|
|
968
|
+
kanban: { useMaxWidth: true, padding: 20, sectionWidth: 400 },
|
|
969
|
+
gitGraph: { rotateCommitLabel: true },
|
|
970
|
+
pie: { useMaxWidth: true },
|
|
971
|
+
};
|
|
972
|
+
}
|