@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,944 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
/*
|
|
3
|
+
The horizontal counterpart of `split` — a hero BAND across the top, the title demoted to a strip
|
|
4
|
+
below it. Everything else in this theme is either one region or a left/right split; nothing
|
|
5
|
+
divided top from bottom, and that was the single biggest gap the reference deck turned up.
|
|
6
|
+
|
|
7
|
+
★ WHY IT EXISTS, from the source rather than from taste. The 65-page Forest template catalogue
|
|
8
|
+
(../../../.superpowers/sdd/2026-08-21-deck-layout-system/template-archetypes.md) uses this
|
|
9
|
+
arrangement NINE times — tied for the most-used interior structure in the whole document — for
|
|
10
|
+
numbered cards, icon cards, phone mockups, a data table, a milestone timeline and pricing tiers.
|
|
11
|
+
Its own §3 ranks it "the biggest gap", and its §5 records the rule that makes it a distinct layout
|
|
12
|
+
rather than a variant: "Two-band pages always subordinate the title. The content that matters
|
|
13
|
+
occupies the top ~65% colour band; title+body is pushed into a short bottom cream strip — the
|
|
14
|
+
reverse of normal top-down reading order, but consistent across all 9 instances."
|
|
15
|
+
|
|
16
|
+
That inversion is the whole point. On a `split` the title leads and the figure supports it; here
|
|
17
|
+
the hero content leads and the title captions it. A slide that wants the normal order already has
|
|
18
|
+
two layouts for it.
|
|
19
|
+
|
|
20
|
+
THE VOCABULARY IS SPLIT'S, deliberately — `ratio` content-relative, `frame` photo/panel/none, an
|
|
21
|
+
`eyebrow` — so an author who knows one knows the other, and the two read as one system rather than
|
|
22
|
+
as two people's ideas about slides. Only `flip` is missing, and only because a full-width band has
|
|
23
|
+
no side to sit on: its outer top corner IS the slide's, which is where the running mark already is.
|
|
24
|
+
|
|
25
|
+
★★ WHY THERE IS NO ACCENT OR DARK BAND, and it is a MEASUREMENT, not a preference. The catalogue's
|
|
26
|
+
band is always the same mint whatever it holds ("never a different accent per content type", §5),
|
|
27
|
+
and the first pass here added the deck's two loud registers anyway on the grounds that `divider`
|
|
28
|
+
already proves both. Rendered, that is a defect: a full-width band rises to --deck-inset-chrome, so
|
|
29
|
+
the running chrome's PAGE PILL — which the notch cannot make room for, because its width is a
|
|
30
|
+
section name and therefore unbounded — lands ON the band. Measured pill fill (--deck-bg-vivid-dark,
|
|
31
|
+
forest-700) against each candidate ground:
|
|
32
|
+
|
|
33
|
+
soft mint, forest-100 5.45:1 ✓
|
|
34
|
+
subtle --deck-bg-subtle 5.28:1 ✓ (split's own panel fill, so the two can match)
|
|
35
|
+
accent forest-700 1.00:1 ✗ the pill is the ground; it disappears entirely
|
|
36
|
+
dark forest-950 2.40:1 ✗ under the 3:1 non-text floor
|
|
37
|
+
|
|
38
|
+
The chrome cannot adapt: slide-top.vue is forbidden from naming a layout (there is a test — see
|
|
39
|
+
frontmatter.static.test.ts, "names no layout, in any form" — because chrome that varies by layout
|
|
40
|
+
is a defect this project has already paid for). So the two loud grounds are not expressible here.
|
|
41
|
+
A slide that wants a full-field green or black cut has `divider`, which owns the whole canvas and
|
|
42
|
+
ships `chrome: false` on every occurrence in every deck.
|
|
43
|
+
|
|
44
|
+
★★ `frame: photo` HAD the same risk in an unmeasurable form, and this comment used to answer it by
|
|
45
|
+
asking the author to remember something. A photograph's top-left corner can be any colour, so no
|
|
46
|
+
contrast row above can be written for it — and the notch only ever protected the MARK end, because
|
|
47
|
+
the chrome USED TO mirror, sending the pills to the text side, and a full-width band has none. Rendered
|
|
48
|
+
on the retired deck-layouts 28 that was a live defect, not a latent one: the mark sits in a clean cut and "28"
|
|
49
|
+
and "LAYOUTS" sit directly on a forest photograph.
|
|
50
|
+
|
|
51
|
+
A photo band therefore STANDS CLEAR of the running chrome rather than cutting into it — it starts
|
|
52
|
+
seated in a cut of its own — useNotchCuts measures every chrome element on the band, so both ends sit
|
|
53
|
+
on the slide's own cream ground and nothing depends on what is in the picture. The cost is stated
|
|
54
|
+
rather than discovered: those slides lose the mark's seat and 108px of band height. See
|
|
55
|
+
../notch.js's retirement note for the full history; EVERY band notches now, because its
|
|
56
|
+
ground is one of the two tones measured in the table above.
|
|
57
|
+
*/
|
|
58
|
+
|
|
59
|
+
// Same clamp discipline as split.vue and bleed.vue: Vue strips prop validators from a production
|
|
60
|
+
// build, so an out-of-range value (an AI's `ratio: '5:7'`, a typo'd background) must not reach the
|
|
61
|
+
// template raw and mint a class name with no matching CSS rule — an unstyled slide, invisible until
|
|
62
|
+
// something overflows. These lookups fall back to the documented default independent of the
|
|
63
|
+
// validators below.
|
|
64
|
+
//
|
|
65
|
+
// The band is the FIGURE and the strip is the CONTENT, so a `ratio` reads exactly as it does on a
|
|
66
|
+
// split: content first, figure second. '1:2' therefore gives the band two thirds of the usable
|
|
67
|
+
// height, which is the catalogue's own ~65%. Split's six are all here so nothing an author already
|
|
68
|
+
// knows is invalid, plus '1:3' — split has no such rung (its ladder is lopsided towards content:
|
|
69
|
+
// 3:1 exists, 1:3 does not) and a band that dominates harder than 2/3 is the one thing this layout
|
|
70
|
+
// specifically needs it for.
|
|
71
|
+
//
|
|
72
|
+
// ★★ AND '0:1' — THE BAND WITH NO STRIP, which is the panel filling the field. A ratio rather than a
|
|
73
|
+
// second layout because that is what it is: content 0, figure everything.
|
|
74
|
+
//
|
|
75
|
+
// ⚠ IT WAS SPELLED '1:0' AND THAT WAS BACKWARDS. The convention two paragraphs up is content first,
|
|
76
|
+
// figure second, and the sentence above defends the rung as "content 0, figure everything" — which
|
|
77
|
+
// is 0:1. The old name said the strip took everything and the band nothing, the exact inverse of
|
|
78
|
+
// what the rung does. Renamed while it was one release old. The band keeps every
|
|
79
|
+
// treatment it has at any other rung — the panel frame, the named background, `panelDark`, the card
|
|
80
|
+
// corner, the media inset — and, the point of it, THE NOTCH. `bleed` is the other way to make a band
|
|
81
|
+
// big and it gives the notch up, because at the canvas edge there is no ring of deck ground left to
|
|
82
|
+
// cut a seat out of. This rung keeps the ring: 36px all the way round, half margin and half cut-out,
|
|
83
|
+
// mark seated top-right and section pill bottom-left.
|
|
84
|
+
const RATIO_ROWS = {
|
|
85
|
+
'1:1': 'is-1-1',
|
|
86
|
+
'3:2': 'is-3-2',
|
|
87
|
+
'2:3': 'is-2-3',
|
|
88
|
+
'2:1': 'is-2-1',
|
|
89
|
+
'1:2': 'is-1-2',
|
|
90
|
+
'3:1': 'is-3-1',
|
|
91
|
+
'1:3': 'is-1-3',
|
|
92
|
+
'0:1': 'is-0-1',
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
// Split's own three, same meanings: a photo frame, a tinted panel, or nothing (the author brings
|
|
96
|
+
// their own ground and owns their own corner — including the notch, which is why `none` is not in
|
|
97
|
+
// PANEL_MEDIA).
|
|
98
|
+
/*
|
|
99
|
+
Split's `panelAlign` without its two `title-*` values. Those position a HEADING that sits beside
|
|
100
|
+
the panel; a stack's heading is in the strip BELOW the band, so there is nothing beside the band
|
|
101
|
+
for them to act on and they would be `start` and `center` under another name. Same subsetting the
|
|
102
|
+
`frame` and `ratio` docs above state for their own omissions.
|
|
103
|
+
*/
|
|
104
|
+
const PANEL_ALIGN_CLASS = {
|
|
105
|
+
start: 'is-panel-start',
|
|
106
|
+
center: 'is-panel-center',
|
|
107
|
+
end: 'is-panel-end',
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
const FRAME_CLASS = {
|
|
111
|
+
photo: 'is-photo',
|
|
112
|
+
panel: 'is-panel',
|
|
113
|
+
code: 'is-code',
|
|
114
|
+
content: 'is-content',
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
// Two grounds, both light, both measured legible under the running chrome — see the ★★ note above
|
|
118
|
+
// for the two that were cut and the numbers that cut them.
|
|
119
|
+
/*
|
|
120
|
+
★★ THE TONE VALUES ARE THE PANEL GROUNDS, AND THEY ARE HUES RATHER THAN TREATMENTS.
|
|
121
|
+
|
|
122
|
+
This read `soft | subtle | warm | vivid` — two treatment words and two hue words in one enum, and
|
|
123
|
+
half of them reaching past the roles into the raw ramp (`--warm-100`, `--forest-300`) so they did
|
|
124
|
+
not follow the register at all. A panel on a dark slide kept its light fill under light ink.
|
|
125
|
+
|
|
126
|
+
Each value is now a NAMED GROUND from the engine (see PANEL in scripts/deck.ts), which publishes a
|
|
127
|
+
fill AND its ink for BOTH registers. So the ink is paired rather than inherited, and every value
|
|
128
|
+
moves with the slide:
|
|
129
|
+
|
|
130
|
+
warm peach on light, bark on dark accent the quiet green field
|
|
131
|
+
vivid the loud green, MIRRORED neutral no hue at all
|
|
132
|
+
|
|
133
|
+
The renames are `soft` → `accent` (same forest-100 on light) and `subtle` → `neutral`. `warm` and
|
|
134
|
+
`vivid` keep their names; warm deepens one step (warm-100 → warm-200) to be a ground rather than a
|
|
135
|
+
tint of the page.
|
|
136
|
+
|
|
137
|
+
★★ AND `dark` IS NOT ONE OF THEM, BECAUSE IT IS NOT A HUE — it is the OTHER axis. `background` says
|
|
138
|
+
which ground; `panelDark` says which register that ground is read in. They compose: every one of
|
|
139
|
+
the four has a light half and a dark half already, so a dark panel is `background` unchanged with the
|
|
140
|
+
register flipped, and the enum stays four values rather than five with one of them meaning
|
|
141
|
+
something different from the rest.
|
|
142
|
+
*/
|
|
143
|
+
const TONE_CLASS = {
|
|
144
|
+
warm: 'bg-warm',
|
|
145
|
+
accent: 'bg-accent',
|
|
146
|
+
vivid: 'bg-vivid',
|
|
147
|
+
neutral: 'bg-neutral',
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
import { computed, inject, ref } from 'vue';
|
|
151
|
+
import { useNotchCuts } from '../useNotchCuts.js';
|
|
152
|
+
import { injectionCurrentPage, injectionFrontmatter } from '@slidev/client/constants.ts';
|
|
153
|
+
import deckTokens from '@humanforest/tokens/deck/deck.json';
|
|
154
|
+
import { chromeShows, coBrandFitted, hasNotch, notchShows } from '../notch.js';
|
|
155
|
+
import { assetUrl } from '../assetUrl.js';
|
|
156
|
+
|
|
157
|
+
const props = defineProps({
|
|
158
|
+
/** The strip's share of the slide against the band's — content-relative, exactly like `split`'s. The default gives the band two thirds, which is the proportion the reference template uses on every one of its nine two-band pages. */
|
|
159
|
+
ratio: {
|
|
160
|
+
type: String,
|
|
161
|
+
default: '1:2',
|
|
162
|
+
// Inlined rather than reading RATIO_ROWS: Vue hoists defineProps() out of setup() at build
|
|
163
|
+
// time, so a validator cannot close over a script-setup-local const — it compiles in dev and
|
|
164
|
+
// breaks the production build. The template clamps through RATIO_ROWS anyway, which is what
|
|
165
|
+
// actually protects a real deck.
|
|
166
|
+
validator: (r) => ['1:1', '3:2', '2:3', '2:1', '1:2', '3:1', '1:3', '0:1'].includes(r),
|
|
167
|
+
},
|
|
168
|
+
/**
|
|
169
|
+
* A CODE SURFACE — the panel IS the block, rather than a mat with a block sitting on it.
|
|
170
|
+
*
|
|
171
|
+
* ★★ IT EXISTS TO AVOID TWO NESTED WELLS. On `frame: panel` a code block draws its own ground and
|
|
172
|
+
* edge inside the panel's, so the slide is a card on a card — on the dark register a #191617 well
|
|
173
|
+
* inside a neutral-800 panel, 1.10:1 apart, which is two surfaces an audience cannot separate and
|
|
174
|
+
* one of them doing no work.
|
|
175
|
+
*
|
|
176
|
+
* Here the panel takes `--slidev-code-background` and the block inside it goes flat: no fill, no
|
|
177
|
+
* edge, no padding of its own. The magic-move container owns its surface the same way; this is
|
|
178
|
+
* that rule with a name.
|
|
179
|
+
*
|
|
180
|
+
* ★ AN EXPLICIT VALUE, NOT A `:has()` ON `panel`. A panel that silently changed ground because it
|
|
181
|
+
* happened to contain a code block is exactly the shape of `frame: none` — a positive behaviour
|
|
182
|
+
* named as an absence — which this layout already paid for once. It also makes the conflict
|
|
183
|
+
* visible: `background` names a tinted ground and a code frame has none, so the two do not compose, and
|
|
184
|
+
* an author reading `frame: code` can see that where a silent override would not tell them.
|
|
185
|
+
*/
|
|
186
|
+
/** How the band is FRAMED: clipped as a photo, matted in a tinted panel, or not a figure at all — the author's own full-width block, with no ground and no notch. Split's `frame` without `figure`, which a full-width band has no use for. */
|
|
187
|
+
frame: { type: String, default: 'panel', validator: (f) => ['photo', 'panel', 'content', 'code'].includes(f) },
|
|
188
|
+
/**
|
|
189
|
+
* Take the band to the CANVAS EDGE instead of the 36px media inset — same frame, same fill, same
|
|
190
|
+
* background, same content, no ring of ground around it.
|
|
191
|
+
*
|
|
192
|
+
* A MODIFIER rather than a fourth `frame`, and the same one split takes: a photo band and a tinted
|
|
193
|
+
* panel band can each want the edge and each keeps its own treatment there. Inert on
|
|
194
|
+
* `frame: content`, which is the author's own block and keeps the text inset it already has.
|
|
195
|
+
*
|
|
196
|
+
* `inset` keeps the ring of ground around the band; `bleed` takes it to the canvas.
|
|
197
|
+
*
|
|
198
|
+
* ★ NAMED `edge`, NOT `bleed`, BECAUSE `bleed` IS ALSO A LAYOUT. One word cannot mean both "this
|
|
199
|
+
* slide is a full-canvas image" and "this figure reaches the edge" in the same frontmatter. The
|
|
200
|
+
* key carries the axis and the value carries the state, so `layout: bleed` keeps sole ownership
|
|
201
|
+
* of the word as a layout and `edge: bleed` is unambiguous beside it.
|
|
202
|
+
*
|
|
203
|
+
* `inset` is the deck's own word — --deck-inset-text, --deck-inset-media and --deck-inset-chrome
|
|
204
|
+
* are the three it is measured in — so naming the default costs an author no new concept. It is
|
|
205
|
+
* NOT called "notched": `notch: false` is a separate key that keeps the inset and drops only the
|
|
206
|
+
* cut, so a slide can be inset without being notched.
|
|
207
|
+
*
|
|
208
|
+
* ★ `bleed` SUPPRESSES THE NOTCH. The cut seats the running chrome in the ring of ground around
|
|
209
|
+
* the band; at inset 0 there is no ring. A band is full width, so BOTH ends of the chrome then
|
|
210
|
+
* sit on it — pair this with `chrome: false`, or use the `bleed` layout and its scrim.
|
|
211
|
+
*/
|
|
212
|
+
/*
|
|
213
|
+
* ★★ `spill` IS `bleed` PLUS ONE MORE STEP — see split.vue's own note, which this shares. The
|
|
214
|
+
* band's box reaches the canvas either way; on `spill` what is INSIDE it is sized past the box and
|
|
215
|
+
* cropped by the slide. On this layout the crop is the BOTTOM edge, because the band is a strip
|
|
216
|
+
* across the frame: a browser window whose foot is below the canvas, three device frames standing
|
|
217
|
+
* on the bottom of the slide.
|
|
218
|
+
*/
|
|
219
|
+
edge: { type: String, default: 'inset', validator: (e) => ['inset', 'bleed', 'spill'].includes(e) },
|
|
220
|
+
// ⚠ THIS DESCRIBED 'soft' AND 'subtle', WHICH THE ENUM NO LONGER HAS, and it ships: a prop
|
|
221
|
+
// description lands in manifest.json and is what an AI consumer reads, so a stale one is a stale
|
|
222
|
+
// contract rather than a stale comment. The band took loose fills once — forest-100 ('soft', the
|
|
223
|
+
// reference template's own field) and --deck-bg-subtle — and it now takes the same four names
|
|
224
|
+
// split.vue's panel does.
|
|
225
|
+
//
|
|
226
|
+
// The reason 'soft' went is worth keeping: its contrast passed, but rendered against this deck's
|
|
227
|
+
// own --warm-50 ground it is a COOL field on a WARM one, a clash no ratio can express and one the
|
|
228
|
+
// designer rejected on sight. `warm` is the default for that reason.
|
|
229
|
+
/** The tinted band's register, for `frame: panel` only — one of the four named backgrounds, each a fill and the ink published with it. */
|
|
230
|
+
background: { type: String, default: 'warm', validator: (t) => ['warm', 'accent', 'vivid', 'neutral'].includes(t) },
|
|
231
|
+
/**
|
|
232
|
+
* Run the PANEL in the dark register, leaving the rest of the slide light.
|
|
233
|
+
*
|
|
234
|
+
* ★★ A SECOND AXIS, NOT A FIFTH `background`. The four tones are HUES and each already publishes both
|
|
235
|
+
* halves (see PANEL in scripts/deck.ts): warm is peach on light and bark on dark, accent is the
|
|
236
|
+
* quiet green field either way. So "dark" is not a ground to pick, it is which half of the ground
|
|
237
|
+
* you picked gets used — and folding it into `background` would have made one value mean a register
|
|
238
|
+
* while the other four meant colours.
|
|
239
|
+
*
|
|
240
|
+
* It works by putting `.deck-dark` on the panel element itself. That class is a plain class in
|
|
241
|
+
* packages/tokens/deck/deck.css — bound alongside `.dark`, not scoped to a slide root — so every
|
|
242
|
+
* `--deck-*` role is re-declared AT the panel and everything inside inherits the dark values:
|
|
243
|
+
* prose, chips, alerts, charts, anything reading the roles. Nothing has to opt in a second time.
|
|
244
|
+
*
|
|
245
|
+
* ★ SCOPED TO THE FRAMES THAT BRING A FILL — `panel` and `code` — exactly as `background` is scoped to
|
|
246
|
+
* `panel`, and for a stronger reason. Those two paint a ground, so flipping the register repaints
|
|
247
|
+
* it under the ink. `photo`, `figure` and `content` bring no fill at all: the class would flip the
|
|
248
|
+
* INK to the dark register's near-white and leave it on the slide's own light ground.
|
|
249
|
+
*
|
|
250
|
+
* On `code` it is the whole point of a dark code surface: the ground is --slidev-code-background,
|
|
251
|
+
* which is register-aware, so the panel becomes the DARK well (--neutral-900) rather than a dark
|
|
252
|
+
* tint of the light one.
|
|
253
|
+
*
|
|
254
|
+
*
|
|
255
|
+
* ★★ AND THE RUNNING CHROME FOLLOWS IT, PER END. A bleeding dark panel leaves no ring of ground
|
|
256
|
+
* for the chrome to sit on, so the row lands directly on it and half the row is then over a dark
|
|
257
|
+
* ground while the other half is over a light one. Measured before that was answered: the page
|
|
258
|
+
* marker painted the LIGHT pill (forest-700) on forest-900, 1.09:1. `darkChromeParts()` in
|
|
259
|
+
* ../notch.js decides which end flips, from this same frontmatter, so the layout and the chrome
|
|
260
|
+
* cannot disagree about where the dark half is.
|
|
261
|
+
* The whole slide going dark is a different thing and has its own spelling: `dark: true`, which
|
|
262
|
+
* every layout that can carry a register now takes — `default`, `bleed`, `divider`, `split` and
|
|
263
|
+
* `stack`. `class: deck-dark` still works and is what the prop writes.
|
|
264
|
+
*/
|
|
265
|
+
panelDark: { type: Boolean, default: false },
|
|
266
|
+
/**
|
|
267
|
+
* Draw this slide on the dark register. Writes `deck-dark` on the layout root — the same class and
|
|
268
|
+
* the same element split takes, said as a prop the layout documents rather than a raw class name
|
|
269
|
+
* an author had to know.
|
|
270
|
+
*
|
|
271
|
+
* Distinct from `panelDark`, which flips the BAND alone and leaves the strip below it on the light
|
|
272
|
+
* ground. This flips the whole slide, strip included, and the running chrome follows the slide
|
|
273
|
+
* rather than an end of it.
|
|
274
|
+
*/
|
|
275
|
+
dark: { type: Boolean, default: false },
|
|
276
|
+
/**
|
|
277
|
+
* Mirror the layout: by default the band leads and the strip captions it from below; flipped, the
|
|
278
|
+
* strip is on top and the band sits under it.
|
|
279
|
+
*
|
|
280
|
+
* ★★ THE SAME KEY AND THE SAME DEFAULT AS `split`, which is the whole reason it is a Boolean. The
|
|
281
|
+
* two layouts invert deliberately, so any value naming a POSITION comes out opposite between them
|
|
282
|
+
* — content-first on split is content-last here. `false` means "this layout's own arrangement" on
|
|
283
|
+
* both, and that is the only thing they genuinely share.
|
|
284
|
+
*
|
|
285
|
+
* ⚠ IT MOVES THE CHROME, NOT JUST THE BOX. A band is full width, so whichever end of the slide it
|
|
286
|
+
* occupies is the end whose running chrome lands on it: unflipped that is the top row, and the
|
|
287
|
+
* notch cuts a seat for the mark; flipped it is the bottom row, and the seat is cut for the page
|
|
288
|
+
* marker instead. ../notch.js reads this same key so the layout and the chrome cannot disagree.
|
|
289
|
+
*/
|
|
290
|
+
flip: { type: Boolean, default: false },
|
|
291
|
+
/**
|
|
292
|
+
* Where the band's content sits on the band's own vertical axis when it is shorter than the band.
|
|
293
|
+
* `center` is the default and what the band did before this was a prop.
|
|
294
|
+
*
|
|
295
|
+
* Split's two `title-*` values are deliberately absent — they align a heading beside the panel,
|
|
296
|
+
* and a stack's heading is in the strip below the band.
|
|
297
|
+
*/
|
|
298
|
+
panelAlign: { type: String, default: 'center', validator: (a) => ['start', 'center', 'end'].includes(a) },
|
|
299
|
+
/** The figure's image URL. This is Slidev's own frontmatter key, used as Slidev means it: `extractImagesUsage()` harvests it and emits a `<link rel=preload as=image>`, so the URL belongs here rather than in an `<img>` a slide writes by hand. Slot content still wins when a slide provides its own figure. */
|
|
300
|
+
image: { type: String, default: '' },
|
|
301
|
+
/** Alt text — for `image` when there is one, and the label the `<Placeholder>` carries when there is not. */
|
|
302
|
+
alt: { type: String, default: '' },
|
|
303
|
+
/** Kicker above the heading, in the strip. */
|
|
304
|
+
eyebrow: { type: String, default: '' },
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
/*
|
|
308
|
+
The notch, and the two frontmatter keys that reach it — the same three questions split.vue asks,
|
|
309
|
+
through the same shared module, so the two layouts cannot disagree about whether this slide has a
|
|
310
|
+
cut or how wide it is. See ../notch.js.
|
|
311
|
+
|
|
312
|
+
Read the same two injections slide-top.vue reads, and for the same reasons documented there:
|
|
313
|
+
`injectionFrontmatter` is the RAW object (not a ref), and `injectionCurrentPage` — never
|
|
314
|
+
useNav().currentPage — is the only page number that is correct on the param-less /print and
|
|
315
|
+
/overview routes the PDF export renders through.
|
|
316
|
+
*/
|
|
317
|
+
const frontmatter = inject(injectionFrontmatter, {});
|
|
318
|
+
const currentPage = inject(injectionCurrentPage, computed(() => 1));
|
|
319
|
+
/*
|
|
320
|
+
★★ EVERY BAND NOTCHES, INCLUDING A PHOTOGRAPH — and it did not always.
|
|
321
|
+
|
|
322
|
+
task-26 made a full-width photo band stand CLEAR of the chrome instead of cutting into it, because
|
|
323
|
+
a band spans the whole canvas so both ends of the chrome land on it, and the cut of the day only
|
|
324
|
+
seated the mark: the page marker and the section pill sat directly on a photograph whose corner
|
|
325
|
+
can be any colour.
|
|
326
|
+
|
|
327
|
+
useNotchCuts.js retired that. It measures EVERY running-chrome element on the band and cuts the
|
|
328
|
+
corner each one sits in, so both ends are seated and nothing depends on what is in the picture.
|
|
329
|
+
MEASURED on apps/deck's `frame: photo` band: the clip opens at `M 234.508 0` — the
|
|
330
|
+
top-left cut for the section pill, mirroring the top-right cut for the mark — and the deck audit
|
|
331
|
+
reports 0 text elements painted under an opaque media surface across the whole deck.
|
|
332
|
+
|
|
333
|
+
See ../notch.js's retirement note for the full history.
|
|
334
|
+
*/
|
|
335
|
+
/* `&& props.edge !== 'bleed'` here rather than inside hasNotch(), for the reason split.vue gives at
|
|
336
|
+
line: bleeding is a fact about this slide's band, not about the frame KIND, and that function's
|
|
337
|
+
arity is pinned by a test after it was once given an argument too many. */
|
|
338
|
+
const notched = computed(() =>
|
|
339
|
+
props.edge === 'inset'
|
|
340
|
+
&&
|
|
341
|
+
hasNotch(props.frame, chromeShows(frontmatter, currentPage.value), notchShows(frontmatter)),
|
|
342
|
+
);
|
|
343
|
+
|
|
344
|
+
// The cut, measured — see ../useNotchCuts.js. A full-width band is the case that forced this: it
|
|
345
|
+
// runs under BOTH top corners, so the mark and the section pill each land on it, and the pill's
|
|
346
|
+
// width is a section name that no token could ever have stated.
|
|
347
|
+
const bandEl = ref(null);
|
|
348
|
+
const { clip: notchClip } = useNotchCuts(bandEl, { enabled: () => notched.value });
|
|
349
|
+
const companion = computed(() => coBrandFitted(frontmatter, currentPage.value, deckTokens));
|
|
350
|
+
const bandStyle = computed(() =>
|
|
351
|
+
notched.value && companion.value
|
|
352
|
+
? { '--deck-notch-occupant-w': `${companion.value.occupantPx}px` }
|
|
353
|
+
: undefined,
|
|
354
|
+
);
|
|
355
|
+
</script>
|
|
356
|
+
|
|
357
|
+
<template>
|
|
358
|
+
<div
|
|
359
|
+
class="slidev-layout stack h-full flex flex-col"
|
|
360
|
+
:class="[
|
|
361
|
+
RATIO_ROWS[ratio] ?? 'is-1-2',
|
|
362
|
+
FRAME_CLASS[frame] ?? 'is-panel',
|
|
363
|
+
edge === 'bleed' || edge === 'spill' ? 'is-bleed' : '',
|
|
364
|
+
edge === 'spill' ? 'is-spill' : '',
|
|
365
|
+
dark ? 'deck-dark' : '',
|
|
366
|
+
flip ? 'is-flipped' : '',
|
|
367
|
+
]"
|
|
368
|
+
>
|
|
369
|
+
<div class="stack-grid">
|
|
370
|
+
<!--
|
|
371
|
+
⚠ `deck-notch` WITHOUT `has-notch`, and that asymmetry is deliberate rather than an
|
|
372
|
+
oversight. `has-notch` is split.vue's own marker, and scripts/deckAudit.ts counts it: on any
|
|
373
|
+
non-split layout a `.has-notch` element is reported as "a notch cut with no panel to seat
|
|
374
|
+
it", so wearing the class here would fail the audit on every stack slide. The CUT is the
|
|
375
|
+
shared `deck-notch` rule in styles/base.css either way.
|
|
376
|
+
|
|
377
|
+
★★ THE AUDIT HAS SINCE LEARNED ABOUT THIS LAYOUT, and the gap this note used to describe was
|
|
378
|
+
real for as long as it stood: because `has-notch` is 0 on every stack slide by design, the
|
|
379
|
+
only notch assertion a stack could reach was satisfied by construction, and all 15 stack
|
|
380
|
+
slides in the specimen went unmeasured while the summary read "0 notch-seating failure(s)".
|
|
381
|
+
deckAudit.ts now counts `.is-notched` as this layout's own marker and seats the band at BOTH
|
|
382
|
+
ends — mark right, section pill left, because a band is full width. Each marker is a defect
|
|
383
|
+
on the other layout, so the two cannot be crossed silently.
|
|
384
|
+
|
|
385
|
+
The background class rides on the BAND, not the root: it is the band's own ground, and the strip
|
|
386
|
+
below is cream by definition — the archetype is "colour band, cream title strip".
|
|
387
|
+
-->
|
|
388
|
+
<div
|
|
389
|
+
ref="bandEl"
|
|
390
|
+
class="stack-band"
|
|
391
|
+
:class="[
|
|
392
|
+
FRAME_CLASS[frame] ?? 'is-panel',
|
|
393
|
+
TONE_CLASS[background] ?? 'bg-warm',
|
|
394
|
+
PANEL_ALIGN_CLASS[panelAlign] ?? 'is-panel-center',
|
|
395
|
+
panelDark && (frame === 'panel' || frame === 'code') ? 'deck-dark' : '',
|
|
396
|
+
// A panel band paints its own fill, so markdown inside it needs the same bullet and
|
|
397
|
+
// marker protection every other own-ground surface gets — base.css keys those off this
|
|
398
|
+
// one class. Missing since the layout shipped, and invisible until a full-field band made
|
|
399
|
+
// a list on one the obvious thing to write: the bullet painted --deck-accent, which is
|
|
400
|
+
// the one green mark on a slide whose ink is the panel's.
|
|
401
|
+
// ⚠ NOT WHILE SPILLING. A spilling band is a STAGE, not a surface — the thing inside it
|
|
402
|
+
// leaves the band on one edge, so a fill behind it draws a box the object visibly ignores.
|
|
403
|
+
// `deck-own-ground` is what re-points bullets and markers to a panel's own ink, and with no
|
|
404
|
+
// fill there is no own ground to re-point to.
|
|
405
|
+
frame === 'panel' && edge !== 'spill' ? 'deck-own-ground' : '',
|
|
406
|
+
edge === 'bleed' || edge === 'spill' ? 'is-bleed' : '',
|
|
407
|
+
edge === 'spill' ? 'is-spill' : '',
|
|
408
|
+
notched ? 'deck-notch is-notched' : '',
|
|
409
|
+
]"
|
|
410
|
+
data-notch-corner="right"
|
|
411
|
+
:style="[bandStyle, notchClip ? { clipPath: notchClip } : null]"
|
|
412
|
+
>
|
|
413
|
+
<slot name="band">
|
|
414
|
+
<img v-if="frame === 'photo' && image" :src="assetUrl(image)" :alt="alt">
|
|
415
|
+
<Placeholder v-else-if="frame === 'photo'" :label="alt || 'Image'" ratio="16:9" />
|
|
416
|
+
</slot>
|
|
417
|
+
</div>
|
|
418
|
+
<div class="stack-strip">
|
|
419
|
+
<p v-if="eyebrow" class="deck-eyebrow">{{ eyebrow }}</p>
|
|
420
|
+
<slot />
|
|
421
|
+
</div>
|
|
422
|
+
</div>
|
|
423
|
+
</div>
|
|
424
|
+
</template>
|
|
425
|
+
|
|
426
|
+
<style scoped>
|
|
427
|
+
/*
|
|
428
|
+
VERTICAL is owned here: the layout's top and bottom bounds are a function of whether there is a
|
|
429
|
+
notch, which `.slidev-layout`'s single uniform padding cannot express. Horizontally nothing
|
|
430
|
+
changes: padding-left/-right stay --deck-inset-text (base.css), so the strip gets the measure
|
|
431
|
+
every other text box in the theme gets and the band claws back the 54px difference to reach
|
|
432
|
+
--deck-inset-media.
|
|
433
|
+
|
|
434
|
+
★ THIS USED TO SAY "exactly as split.vue owns its own", AND SPLIT NO LONGER DOES. Split zeroed its
|
|
435
|
+
root so a `calc(100% / 3)` anchor had the canvas to resolve against; that anchor is a pixel now
|
|
436
|
+
and split is an ordinary padded box. The two layouts arrived at the same reset for different
|
|
437
|
+
reasons, and only stack's reason survives — its band is full-bleed and has to reach the media
|
|
438
|
+
inset at the TOP as well, unconditionally, which a padded root cannot express without a negative
|
|
439
|
+
margin on every band rather than only a notched one.
|
|
440
|
+
|
|
441
|
+
The known cost is the one split just paid: a root with no vertical padding makes every region
|
|
442
|
+
inside responsible for its own edge clearance, and nothing enforces that. `.stack-strip` below
|
|
443
|
+
carries such a reserve. It is correct today because stack has no third region beneath the strip
|
|
444
|
+
for it to collide with — if one is ever added, that reserve is the first thing to re-derive.
|
|
445
|
+
*/
|
|
446
|
+
.slidev-layout.stack {
|
|
447
|
+
padding-top: 0;
|
|
448
|
+
padding-bottom: 0;
|
|
449
|
+
}
|
|
450
|
+
.stack-grid {
|
|
451
|
+
flex: 1;
|
|
452
|
+
display: grid;
|
|
453
|
+
min-height: 0; /* lets the rows shrink instead of overflowing the slide */
|
|
454
|
+
/*
|
|
455
|
+
THE GAP between band and strip, and why it is NOT split's conditional gutter.
|
|
456
|
+
|
|
457
|
+
Split's rule is Jose's: "text one side, image other side: no gutter... both sides text or both
|
|
458
|
+
sides image: gutter 36px." A mixed HORIZONTAL pair needs no gutter because the text column
|
|
459
|
+
already carries --deck-inset-text on its own outer edge and a paragraph's ragged right supplies
|
|
460
|
+
the air on the inner one. Neither is true on the VERTICAL axis: a heading's box has no ragged
|
|
461
|
+
top, and a band's hard bottom edge would meet the cap-height of the h1 directly. So the mixed
|
|
462
|
+
pair takes a real gap here, and it is --deck-space-5 — one full grid unit, the same figure
|
|
463
|
+
--deck-heading-top-space uses for precisely this problem one layout over (air above a heading
|
|
464
|
+
that would otherwise read as flush).
|
|
465
|
+
*/
|
|
466
|
+
row-gap: var(--deck-space-5);
|
|
467
|
+
/*
|
|
468
|
+
Two rows. Every ratio is content:figure — strip:band — so each rung names its two shares and the
|
|
469
|
+
TRACK ORDER is applied once, below: positional order is the flip's business and the ratio is not.
|
|
470
|
+
Written as two custom properties rather than a `grid-template-rows` pair per rung so that
|
|
471
|
+
mirroring costs one rule instead of a second copy of all eight.
|
|
472
|
+
*/
|
|
473
|
+
--stack-band-fr: 2fr;
|
|
474
|
+
--stack-strip-fr: 1fr;
|
|
475
|
+
grid-template-rows: var(--stack-band-fr) var(--stack-strip-fr);
|
|
476
|
+
}
|
|
477
|
+
.is-1-1 .stack-grid { --stack-band-fr: 1fr; --stack-strip-fr: 1fr; }
|
|
478
|
+
.is-3-2 .stack-grid { --stack-band-fr: 2fr; --stack-strip-fr: 3fr; }
|
|
479
|
+
.is-2-3 .stack-grid { --stack-band-fr: 3fr; --stack-strip-fr: 2fr; }
|
|
480
|
+
.is-2-1 .stack-grid { --stack-band-fr: 1fr; --stack-strip-fr: 2fr; }
|
|
481
|
+
.is-1-2 .stack-grid { --stack-band-fr: 2fr; --stack-strip-fr: 1fr; }
|
|
482
|
+
.is-3-1 .stack-grid { --stack-band-fr: 1fr; --stack-strip-fr: 3fr; }
|
|
483
|
+
.is-1-3 .stack-grid { --stack-band-fr: 3fr; --stack-strip-fr: 1fr; }
|
|
484
|
+
|
|
485
|
+
/* ── THE MIRROR ────────────────────────────────────────────────────────────────────────────────
|
|
486
|
+
The strip takes row 1 and the band row 2, and the track pair reverses with them so each region
|
|
487
|
+
keeps the share its rung gave it. Everything below that names an outer edge — the band's margin,
|
|
488
|
+
the notch lift, the strip's own bound — swaps ends too, because the layout's outer edge is the
|
|
489
|
+
only thing that moved. */
|
|
490
|
+
.is-flipped .stack-grid { grid-template-rows: var(--stack-strip-fr) var(--stack-band-fr); }
|
|
491
|
+
.is-flipped .stack-band { grid-row: 2; }
|
|
492
|
+
.is-flipped .stack-strip { grid-row: 1; }
|
|
493
|
+
.is-flipped .stack-band { margin-top: 0; margin-bottom: var(--deck-inset-text); }
|
|
494
|
+
.is-flipped .stack-band.is-notched { margin-top: 0; margin-bottom: var(--deck-inset-chrome); }
|
|
495
|
+
.is-flipped .stack-strip { padding-bottom: 0; padding-top: var(--deck-inset-text); }
|
|
496
|
+
/* ★★ ONE ROW, NOT A 0fr SECOND ONE. `0fr` sizes a track by its own min-content, and the strip
|
|
497
|
+
carries `padding-bottom: --deck-inset-text` — so a zero-share strip would still reserve 96px and
|
|
498
|
+
the band would stop short of the field it is meant to fill. The strip is removed instead, and the
|
|
499
|
+
gap with it, since a gap only exists between two rows. */
|
|
500
|
+
.is-0-1 .stack-grid { grid-template-rows: 1fr; gap: 0; }
|
|
501
|
+
.is-0-1 .stack-strip { display: none; }
|
|
502
|
+
/* The band owns both ends now. `.is-notched` already lifts the top to the chrome inset so the mark's
|
|
503
|
+
outer corner and the band's are one point; the bottom needs the same ring for the section pill,
|
|
504
|
+
which at every other rung is the strip's business. */
|
|
505
|
+
.is-0-1 .stack-band { margin-bottom: var(--deck-inset-text); }
|
|
506
|
+
.is-0-1 .stack-band.is-notched { margin-bottom: var(--deck-inset-chrome); }
|
|
507
|
+
|
|
508
|
+
/*
|
|
509
|
+
THE BAND. Its own vertical bound is --deck-inset-text, the same 96px every figure in this theme
|
|
510
|
+
gets for free from the shared ancestor padding that this layout zeroed — restored as an explicit
|
|
511
|
+
MARGIN, not padding, so it positions the frame rather than shrinking the photograph inside it
|
|
512
|
+
(split.vue's figure makes the same distinction for the same reason).
|
|
513
|
+
|
|
514
|
+
Horizontally it claws back the 54px difference between the text inset it inherits and the media
|
|
515
|
+
inset a hard-edged figure wants — on BOTH edges, because a full-width band has two outer edges
|
|
516
|
+
where a split's figure has one. Negative margins rather than a wider box: the band sits in an
|
|
517
|
+
`fr`-sized grid row, so a margin shifts the rendered box without feeding back into track sizing.
|
|
518
|
+
*/
|
|
519
|
+
.stack-band {
|
|
520
|
+
grid-row: 1;
|
|
521
|
+
min-height: 0;
|
|
522
|
+
margin-top: var(--deck-inset-text);
|
|
523
|
+
margin-inline: calc(-1 * (var(--deck-inset-text) - var(--deck-inset-media)));
|
|
524
|
+
}
|
|
525
|
+
/* `frame: content` is the author's own content, not a figure — it keeps the TEXT inset, because
|
|
526
|
+
escaping it to the media inset would narrow the measure on the very edge --deck-inset-text
|
|
527
|
+
exists to protect. Same carve-out, same reasoning, as split.vue's. */
|
|
528
|
+
.stack-band.is-content {
|
|
529
|
+
margin-inline: 0;
|
|
530
|
+
}
|
|
531
|
+
/* ★ THE BAND RISES TO MEET THE MARK — the notch's other half, and the reason chrome and media share
|
|
532
|
+
an inset (scripts/deck.ts, above INSET). At --deck-inset-text the mark and the band would overlap
|
|
533
|
+
by 18px, which is a nick rather than a seat; at --deck-inset-chrome the mark's outer corner and
|
|
534
|
+
the band's outer corner are the SAME POINT, with one uniform 36px ring — half slide margin, half
|
|
535
|
+
cut-out. Drops back to the text inset on `notch: false`, together with the cut and the
|
|
536
|
+
reservation, so an opted-out band has no raised top with nothing cut into it. */
|
|
537
|
+
.stack-band.is-notched {
|
|
538
|
+
margin-top: var(--deck-inset-chrome);
|
|
539
|
+
}
|
|
540
|
+
/*
|
|
541
|
+
★★ `bleed` — THE SAME BAND, AT THE CANVAS EDGE. The band already claws back to --deck-inset-media
|
|
542
|
+
on BOTH outer edges, because a band has two of them where split's figure has one; this gives up the
|
|
543
|
+
whole text inset instead, so left, right and top are 0, 1920 and 0. The strip below is untouched
|
|
544
|
+
and keeps its text inset.
|
|
545
|
+
|
|
546
|
+
The frame keeps its treatment: a photo band still crops, a panel band still carries its background. Only
|
|
547
|
+
the edges and the corner radius move, which is what makes this a modifier rather than a frame kind.
|
|
548
|
+
|
|
549
|
+
⚠ NOT ON `.is-content` — the author's own block, which wants the text inset it already has.
|
|
550
|
+
|
|
551
|
+
It never notches (see the prop's note), so this rule and `.is-notched` above can never both apply;
|
|
552
|
+
it is written after it anyway so the margin-top needs no specificity fight to win.
|
|
553
|
+
*/
|
|
554
|
+
.stack-band.is-bleed:not(.is-content) {
|
|
555
|
+
margin-top: 0;
|
|
556
|
+
margin-inline: calc(-1 * var(--deck-inset-text));
|
|
557
|
+
border-radius: 0;
|
|
558
|
+
}
|
|
559
|
+
/*
|
|
560
|
+
⚠ THE FLIPPED BAND KEEPS A BOTTOM MARGIN, so it stopped 96px short of the canvas — the rule above
|
|
561
|
+
zeroes the TOP, which is the edge an unflipped band bleeds by. Measured on the spill slides, which
|
|
562
|
+
are the first flipped bleeding bands in the book: the band ran to y 984 with cream under it, and
|
|
563
|
+
a window "cut by the bottom of the slide" was cut by a line inside it instead.
|
|
564
|
+
*/
|
|
565
|
+
.is-flipped .stack-band.is-bleed:not(.is-content) { margin-bottom: 0; }
|
|
566
|
+
/*
|
|
567
|
+
★★ THE BAND BLEEDS; THE CONTENT INSIDE IT MUST NOT. Same correction as split's, same reason: the
|
|
568
|
+
padding rode out with the box, so a bleeding panel band put its content at x 32 where the same band
|
|
569
|
+
unbled puts it at 68. Each bled edge gives the inset back, so the content lands where it would have
|
|
570
|
+
without the bleed and only the surface moves.
|
|
571
|
+
|
|
572
|
+
Only `panel` — a bleeding photo band is meant to be edge to edge, and `content` never bleeds.
|
|
573
|
+
|
|
574
|
+
⚠ The top value here assumes no chrome above it; styles/base.css raises it when the chrome row is
|
|
575
|
+
drawn, because a scoped style cannot see `.slidev-page`.
|
|
576
|
+
*/
|
|
577
|
+
/*
|
|
578
|
+
★★ A BLEEDING PANEL TAKES THE REGION INSET (96), NOT THE CARD INSET (68).
|
|
579
|
+
|
|
580
|
+
These gave the bled edge its inset back as padding — card pad 32 + media inset 36 — so the content
|
|
581
|
+
landed exactly where the same panel puts it unbled. Correct arithmetic, wrong premise: it treats a
|
|
582
|
+
bleeding panel as a card that happens to reach the edge. It is not one. A card's interior is inset
|
|
583
|
+
from its own EDGES, which are drawn; a bleeding panel has no drawn edge left, so its interior is
|
|
584
|
+
measuring against the canvas and against the seam with the content column — which is exactly the
|
|
585
|
+
situation a text column is in, and --deck-inset-text is the number for it.
|
|
586
|
+
|
|
587
|
+
MEASURED on the register-comparison split (specimen 124, split) before this, which is the slide that found
|
|
588
|
+
it. Seam at 960:
|
|
589
|
+
|
|
590
|
+
left column 96 -> 864 96 from the canvas edge, 96 from the seam 768 wide
|
|
591
|
+
right panel 1028 -> 1852 68 from the seam, 68 from the canvas edge 824 wide
|
|
592
|
+
|
|
593
|
+
Both halves internally symmetric, and 56px apart from each other — so the same paragraph wrapped to
|
|
594
|
+
three lines on the left and two on the right, on a slide whose whole claim is that the register is
|
|
595
|
+
the only thing that differs. At the region inset both halves are 768 and the comparison is exact.
|
|
596
|
+
|
|
597
|
+
⚠ Only when it BLEEDS. A panel sitting on the ground keeps --deck-card-pad: it has edges, and 96
|
|
598
|
+
inside a 924px card is a frame with a stamp in it. Jose asked whether the two sides should match and
|
|
599
|
+
what it would cost; the answer is 3 slides, all bleeding panels, because that is the only case where
|
|
600
|
+
a panel stops being a card.
|
|
601
|
+
*/
|
|
602
|
+
.stack-band.is-bleed.is-panel {
|
|
603
|
+
padding-top: var(--deck-inset-text);
|
|
604
|
+
padding-inline: var(--deck-inset-text);
|
|
605
|
+
}
|
|
606
|
+
/*
|
|
607
|
+
══ SPILL ══════════════════════════════════════════════════════════════════════════════════════
|
|
608
|
+
|
|
609
|
+
★★ THE BAND IS ALREADY A STRIP ACROSS THE FRAME, so what spills here is its HEIGHT. `bleed` takes
|
|
610
|
+
the band's box to the canvas on three sides and keeps everything inside it; `spill` lets the thing
|
|
611
|
+
inside grow past the bottom of the box and be cut by the slide — a browser window whose foot is
|
|
612
|
+
below the canvas, three device frames standing on the bottom edge.
|
|
613
|
+
|
|
614
|
+
⚠ THE STRIP BELOW IS UNTOUCHED, which is the whole reason this is a band modifier rather than a
|
|
615
|
+
full-bleed layout: the words keep their row and their inset, and the picture behind them runs out
|
|
616
|
+
of the frame.
|
|
617
|
+
*/
|
|
618
|
+
/*
|
|
619
|
+
⚠ THE SELECTOR CARRIES THE CLASSES IT HAS TO BEAT. Two rules outranked a plain `.stack-band.is-spill`
|
|
620
|
+
at equal specificity and source order decided both against it: `.stack-band.is-panel.bg-warm` kept
|
|
621
|
+
the band's peach fill, and `.stack-band.is-panel-center` kept the vertical centring — which put a
|
|
622
|
+
900px row of device frames 54px ABOVE the band's own top and cropped their heads as well as their
|
|
623
|
+
feet. Named here rather than fought with `!important`.
|
|
624
|
+
*/
|
|
625
|
+
.stack-band.is-spill.is-panel:not(.is-content),
|
|
626
|
+
.stack-band.is-spill.is-panel-center:not(.is-content),
|
|
627
|
+
.stack-band.is-spill:not(.is-content) {
|
|
628
|
+
/* The band stops clipping its own content — the SLIDE does the cropping, three rules down. */
|
|
629
|
+
overflow: visible;
|
|
630
|
+
align-items: flex-start;
|
|
631
|
+
/*
|
|
632
|
+
★★ AND IT PAINTS NOTHING. Jose: "for the stack, no need to paint the panel". A band's fill says
|
|
633
|
+
"this strip is a surface"; a spilling band's content runs off the canvas, so the fill draws a box
|
|
634
|
+
the object plainly ignores — two edges that disagree about where the thing ends. The frame still
|
|
635
|
+
decides the CONTENT's treatment (a photo band still crops); only the surface goes.
|
|
636
|
+
*/
|
|
637
|
+
background: none;
|
|
638
|
+
border: none;
|
|
639
|
+
/*
|
|
640
|
+
⚠ THE BLEED'S PADDING COMES OFF THE BLOCK AXIS AND STAYS ON THE INLINE ONE, and the asymmetry is
|
|
641
|
+
the whole point of a spill. `is-bleed` gives the inset back as padding so the content lands where
|
|
642
|
+
it would have unbled — correct for a bleed, and on the TOP the exact opposite of what a spill
|
|
643
|
+
wants: measured, it started the window 132px below the band's own edge and the slide then cut a
|
|
644
|
+
window that had never reached the top.
|
|
645
|
+
|
|
646
|
+
★ THE SIDES ARE NOT THE SAME CASE, WHICH TOOK GOING TOO FAR TO ESTABLISH. Zeroing all four ran
|
|
647
|
+
the window 0-1920 and it stopped reading as a window at all — Jose: "leave some padding at the
|
|
648
|
+
sides". A spill crops in ONE direction, and here that is downward; the inset on the other axis is
|
|
649
|
+
what keeps the object recognisable as an object rather than as a texture. So `padding-block`
|
|
650
|
+
rather than `padding`.
|
|
651
|
+
|
|
652
|
+
★ THE TOP ALSO NEEDED A FIX ONE FILE AWAY. This declaration was being outranked by base.css's
|
|
653
|
+
chrome-clearance rule, whose `:has()` made it (0,8,0) against this rule's (0,5,0). That rule now
|
|
654
|
+
excludes `.is-spill` — see its own note — so this one is what decides the top again.
|
|
655
|
+
*/
|
|
656
|
+
padding-block: 0;
|
|
657
|
+
}
|
|
658
|
+
/*
|
|
659
|
+
★★ SPILL RELEASES THE CAPS; THE CONTENT STATES ITS OWN SIZE. Two sizing schemes were tried and both
|
|
660
|
+
were wrong for half the cases: `height: 128%` did nothing to a browser frame (it takes its height
|
|
661
|
+
from its own ratio — measured, the room stayed 433px in a 597px band), and `width: 128%` blew up a
|
|
662
|
+
row of device frames that were already taller than the band. What every spilling component has in
|
|
663
|
+
common is that it KNOWS its size — a map's `width`, a frame's `height` — and what the band was
|
|
664
|
+
doing was capping it. So the cap comes off and nothing is imposed.
|
|
665
|
+
*/
|
|
666
|
+
.stack-band.is-spill > :deep(*) {
|
|
667
|
+
max-width: none;
|
|
668
|
+
max-height: none;
|
|
669
|
+
/* ⚠ AND IT STILL HAS TO FILL THE BAND. The band is a flex row, so a grid inside it shrink-wraps to
|
|
670
|
+
its own tracks the moment nothing stretches it — measured with the caps off: the row of three
|
|
671
|
+
phones came out 1428 wide against a 1728 band and sat hard left, 96 of air on one side and 396
|
|
672
|
+
on the other. Grid's own `justify: center` used to supply the stretch as a side effect; this
|
|
673
|
+
states it instead, so the centring below has the full band to centre in. */
|
|
674
|
+
width: 100%;
|
|
675
|
+
}
|
|
676
|
+
/*
|
|
677
|
+
⚠ AND ONE CAP HAS TO BE BROKEN BY NAME. BrowserFrame sizes its window so the WHOLE of it fits the
|
|
678
|
+
room's height — that is the component doing its job, and it is computed as a max-width because a
|
|
679
|
+
height cap does not travel back through `aspect-ratio` (its own note says so at length). A spilling
|
|
680
|
+
band is the one state where the window is meant to be taller than the room, so the cap comes off —
|
|
681
|
+
and ONLY the cap. At the band's own width a 16:9 window is 972px tall against a band of 597, so the
|
|
682
|
+
ratio does the spilling and the window stays a recognisable window.
|
|
683
|
+
|
|
684
|
+
⚠ 128% WAS THE FIRST TRY AND IT WAS WRONG, which is worth keeping: a window wider than the canvas
|
|
685
|
+
loses both of its side edges, and what is left reads as a horizontal band with a URL in it rather
|
|
686
|
+
than as a browser. `--spill` is still the knob for a slide that wants more.
|
|
687
|
+
*/
|
|
688
|
+
.stack-band.is-spill :deep(.deck-browser-fit) {
|
|
689
|
+
max-width: none;
|
|
690
|
+
width: var(--spill, 100%);
|
|
691
|
+
}
|
|
692
|
+
/* ⚠ AND THE ROOM STOPS CENTRING IT. The room centres its window vertically, which is right when the
|
|
693
|
+
window fits and wrong the moment it does not: a 128% window centred in the band grew UPWARDS as
|
|
694
|
+
well, over the heading in the strip above. Anchored to the top, all of the growth leaves by the
|
|
695
|
+
bottom, which is the edge the slide is cropping. */
|
|
696
|
+
.stack-band.is-spill :deep(.deck-browser-room) { justify-content: flex-start; }
|
|
697
|
+
/*
|
|
698
|
+
★★ AND A ROW OF FIXED-WIDTH OBJECTS CENTRES IN ITS TRACKS. Grid's own `justify` reaches INSIDE a
|
|
699
|
+
cell — `.is-justify-center > *` makes each cell a centred flex column — which is right when a cell
|
|
700
|
+
holds content and does nothing at all when the cell IS the object: with one device frame per cell,
|
|
701
|
+
that selector matches the phone itself and centres the phone's own children. Measured: three
|
|
702
|
+
frames flush against the left of their tracks, the group running 96 → 1724 in a 1920 frame, 96 of
|
|
703
|
+
air on one side and 196 on the other.
|
|
704
|
+
|
|
705
|
+
`justify-items` is the property that moves a fixed-width cell within its track, and it is scoped to
|
|
706
|
+
a spilling band rather than added to Grid because Grid's note argues against it for its own case:
|
|
707
|
+
a cell that shrink-wraps to its content leaves the content at the shrunken cell's edge. A device
|
|
708
|
+
frame does not shrink-wrap — its width comes from its height — so here it is exactly right.
|
|
709
|
+
*/
|
|
710
|
+
.stack-band.is-spill :deep(.deck-grid) { justify-items: center; }
|
|
711
|
+
/*
|
|
712
|
+
★★ THE SLIDE DOES THE CROPPING, AND IT HAS TO SAY SO — same note as split.vue's. Nothing on this
|
|
713
|
+
layout clipped before, so an overflowing band painted over the strip below it and past the canvas
|
|
714
|
+
into the deck's own page.
|
|
715
|
+
*/
|
|
716
|
+
.stack.is-spill { overflow: hidden; }
|
|
717
|
+
|
|
718
|
+
/*
|
|
719
|
+
THE BAND OWNS ITS OWN TREATMENT, same contract as split's figure: an author writes the content and
|
|
720
|
+
nothing else and gets a filled, corner-clipped photo or a tinted panel. `object-fit: cover` rather
|
|
721
|
+
than a raw stretch is what makes filling safe for an <img> of any intrinsic ratio.
|
|
722
|
+
*/
|
|
723
|
+
.stack-band.is-photo {
|
|
724
|
+
border-radius: var(--deck-radius-card);
|
|
725
|
+
overflow: hidden;
|
|
726
|
+
display: flex;
|
|
727
|
+
}
|
|
728
|
+
.stack-band.is-photo :deep(img),
|
|
729
|
+
.stack-band.is-photo :deep(video),
|
|
730
|
+
.stack-band.is-photo :deep(picture) {
|
|
731
|
+
width: 100%;
|
|
732
|
+
height: 100%;
|
|
733
|
+
object-fit: cover;
|
|
734
|
+
display: block;
|
|
735
|
+
}
|
|
736
|
+
.stack-band.is-panel {
|
|
737
|
+
border-radius: var(--deck-radius-card);
|
|
738
|
+
/*
|
|
739
|
+
★★ THE INLINE PADDING PUTS CONTENT ON THE TEXT INSET, NOT ON THE CARD PAD. The band claws back
|
|
740
|
+
to --deck-inset-media, 36px, so a uniform card pad landed its content at 68 — 28px left of where
|
|
741
|
+
the strip below it, and every slide without a band, puts theirs. Two text columns on one slide
|
|
742
|
+
that do not share a left edge. `--deck-inset-text - --deck-inset-media` is the difference the
|
|
743
|
+
band gave up, handed back as padding, so the content lands at 96 exactly as it would with no
|
|
744
|
+
band at all and only the SURFACE reaches wider.
|
|
745
|
+
|
|
746
|
+
The bleeding rule below already did this for its own case (it gives back the whole text inset
|
|
747
|
+
because it gave up the whole thing); this is the same correction for the unbled band, which is
|
|
748
|
+
the common one. Vertical stays the card pad — the band's height is the ratio's business, and the
|
|
749
|
+
notched rule raises the top on its own.
|
|
750
|
+
*/
|
|
751
|
+
padding: var(--deck-card-pad) calc(var(--deck-inset-text) - var(--deck-inset-media));
|
|
752
|
+
display: flex;
|
|
753
|
+
/* A COLUMN that stretches, where split's panel is a centred row. The difference is what the two
|
|
754
|
+
hold: split's panel is a mat for one figure, this band is the slide's main content region — a Grid
|
|
755
|
+
of cards, a table, a timeline — all of which want the full width and do their own internal
|
|
756
|
+
alignment.
|
|
757
|
+
|
|
758
|
+
The vertical placement of a block SHORTER than the band is `panelAlign`'s, in the three rules
|
|
759
|
+
below. It used to be a bare `justify-content: center` here, which was the default with no way
|
|
760
|
+
to say anything else. */
|
|
761
|
+
flex-direction: column;
|
|
762
|
+
align-items: stretch;
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
/* `panelAlign` — where a block shorter than the band sits on the band's own axis. Only meaningful
|
|
766
|
+
on a frame that HAS a band box to sit in; `content` brings its own and is unaffected. */
|
|
767
|
+
.stack-band.is-panel-start { justify-content: flex-start; }
|
|
768
|
+
.stack-band.is-panel-center { justify-content: center; }
|
|
769
|
+
.stack-band.is-panel-end { justify-content: flex-end; }
|
|
770
|
+
/* Media matted inside a panel takes the CONCENTRIC radius — the card's corner minus its padding —
|
|
771
|
+
and opts back OUT of the stretch above, because an <img> stretched to a band's full width is
|
|
772
|
+
distorted rather than filled. */
|
|
773
|
+
/*
|
|
774
|
+
── A CODE SURFACE ────────────────────────────────────────────────────────────────────────────
|
|
775
|
+
The panel IS the block. It takes the code ground and its own edge, and the `pre` inside goes flat,
|
|
776
|
+
so a code slide is ONE surface rather than a well inside a mat. See the `frame` prop's own note for
|
|
777
|
+
why this is a named value rather than a `:has()` on `panel`.
|
|
778
|
+
|
|
779
|
+
The inner reset is the same three declarations the magic-move container has always used on its own
|
|
780
|
+
`pre.shiki`, for the same reason: a container that owns the surface must strip the one its child
|
|
781
|
+
would otherwise draw, or the two edges sit 4px apart and read as a mistake.
|
|
782
|
+
*/
|
|
783
|
+
.stack-band.is-code {
|
|
784
|
+
background: var(--slidev-code-background);
|
|
785
|
+
border: 1px solid var(--deck-code-border);
|
|
786
|
+
border-radius: var(--deck-radius-card);
|
|
787
|
+
padding: var(--deck-card-pad);
|
|
788
|
+
display: flex;
|
|
789
|
+
flex-direction: column;
|
|
790
|
+
justify-content: center;
|
|
791
|
+
min-width: 0;
|
|
792
|
+
}
|
|
793
|
+
/*
|
|
794
|
+
★★ THE PADDING IS ZEROED AT ITS SOURCE, NOT OVERRIDDEN — one cause, two effects.
|
|
795
|
+
|
|
796
|
+
The obvious way to flatten the block is `padding: 0 !important` (Slidev's own rule carries
|
|
797
|
+
`!important`, so nothing less wins). It works, and it leaves a 24px horizontal scrollbar on every
|
|
798
|
+
code frame, because a SECOND rule reads the same token: base.css sizes each line
|
|
799
|
+
|
|
800
|
+
width: calc(100% + space-1 + space-2 + var(--slidev-code-padding))
|
|
801
|
+
|
|
802
|
+
so the row overhangs to the right by exactly the block's own right padding, which is what lets a
|
|
803
|
+
lit band reach both padding edges. Flatten the padding without telling that rule and the overhang
|
|
804
|
+
has nothing left to fill: measured on this frame, scrollWidth 965 against clientWidth 941 — the
|
|
805
|
+
token, to the pixel.
|
|
806
|
+
|
|
807
|
+
Setting `--slidev-code-padding: 0px` collapses both at once: Slidev's padding resolves to zero with
|
|
808
|
+
no `!important` needed, and the line's overhang term goes with it. The surface's own inset is the
|
|
809
|
+
panel's, which is the whole point of the frame.
|
|
810
|
+
*/
|
|
811
|
+
.stack-band.is-code {
|
|
812
|
+
--slidev-code-padding: 0px;
|
|
813
|
+
}
|
|
814
|
+
.stack-band.is-code :deep(.slidev-code-wrapper),
|
|
815
|
+
.stack-band.is-code :deep(pre.slidev-code) {
|
|
816
|
+
margin: 0 !important;
|
|
817
|
+
background: none;
|
|
818
|
+
border: none;
|
|
819
|
+
border-radius: 0;
|
|
820
|
+
}
|
|
821
|
+
/*
|
|
822
|
+
★★ AN EDGE AND A CLIP CANNOT SHARE AN ELEMENT. `clip-path` cuts the rendered box, border included,
|
|
823
|
+
so a notched code surface drew its edge continuously on three sides and then simply lost it around
|
|
824
|
+
the cut — a frame that stops. Measured on the specimen's own pair: `border: 1px` and a clip-path on
|
|
825
|
+
the same figure.
|
|
826
|
+
|
|
827
|
+
The border is what a code surface has when it is a CARD on the slide. Notched, its outer corner is
|
|
828
|
+
the mark's seat rather than a corner; bleeding, there is no ground for it to sit on at all. Neither
|
|
829
|
+
is a card, so neither takes an edge. `panel` never hit this because it has no border to lose.
|
|
830
|
+
*/
|
|
831
|
+
.stack-band.is-code.is-notched {
|
|
832
|
+
border: none;
|
|
833
|
+
padding-bottom: var(--deck-card-pad);
|
|
834
|
+
}
|
|
835
|
+
/* Bleeding, it is a REGION like every other bleeding panel — the region inset on all four sides, so
|
|
836
|
+
the code lines up with a content column across the seam. See the note above the panel's own. */
|
|
837
|
+
.stack-band.is-code.is-bleed {
|
|
838
|
+
border: none;
|
|
839
|
+
border-radius: 0;
|
|
840
|
+
padding: var(--deck-inset-text) var(--deck-card-pad);
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
/*
|
|
844
|
+
⚠ `:not(.mark-pair-companion)`. A co-branding tile is not matted media: MarkPair clips the
|
|
845
|
+
partner's artwork to the MARK's corner — a ratio of the tile — and this rule outranked it
|
|
846
|
+
(`.stack-band.is-panel[data-v-…] img` beats a single class), so a pair dropped into a panel came out with
|
|
847
|
+
the panel's 16px nested radius on one half and the mark's on the other. Measured on the specimen's
|
|
848
|
+
own MarkPair slide: 31.49px on the light half, 16px on the dark one, same component, same size.
|
|
849
|
+
slide-top.vue carries the same exclusion for the same reason.
|
|
850
|
+
*/
|
|
851
|
+
.stack-band.is-panel :deep(img:not(.mark-pair-companion)),
|
|
852
|
+
.stack-band.is-panel :deep(video),
|
|
853
|
+
.stack-band.is-panel :deep(picture) {
|
|
854
|
+
max-width: 100%;
|
|
855
|
+
align-self: center;
|
|
856
|
+
border-radius: var(--deck-radius-nested);
|
|
857
|
+
display: block;
|
|
858
|
+
}
|
|
859
|
+
/*
|
|
860
|
+
⚠ MASKING CLIPS, IT DOES NOT REFLOW — so a band holding real content reserves the cut's height as
|
|
861
|
+
top padding, never less than the card padding it already had. Identical guarantee, identical
|
|
862
|
+
mechanism and identical reason as split.vue's `.is-panel.has-notch`: a band a mask has bitten into
|
|
863
|
+
would otherwise slice the corner off a card. `.is-photo` deliberately gets none of it — clipping
|
|
864
|
+
the photograph IS the effect.
|
|
865
|
+
*/
|
|
866
|
+
/*
|
|
867
|
+
★★ RESERVED AT BOTH ENDS, THE SAME WAY split's PANEL IS, AND FOR THE SAME MEASURED REASON.
|
|
868
|
+
|
|
869
|
+
The top reservation is the notch: content must clear the bite cut for the running chrome, so the
|
|
870
|
+
band keeps `--notch-h` (mark 72 + ring 24 = 96) above it. Only the top was reserved, leaving the
|
|
871
|
+
box 96/32 — so anything this band centres lands (96 - 32) / 2 = 32px LOW inside it. Measured across
|
|
872
|
+
five notched band slides, every one off by exactly 32:
|
|
873
|
+
|
|
874
|
+
slide 23 band 36-416 content 132-384 96 above, 32 below
|
|
875
|
+
slide 56 band 36-780 content 375-505 339 above, 275 below
|
|
876
|
+
slide 60 band 36-416 content 193-323 157 above, 93 below
|
|
877
|
+
|
|
878
|
+
⚠ AND THE ARGUMENT FOR LEAVING THIS ONE ALONE WAS WRONG. It was "a band is full width and holds
|
|
879
|
+
full-width content, so it is less likely to look wrong" — which is about HORIZONTAL extent and has
|
|
880
|
+
nothing to do with a vertical offset. Slide 56 is the proof: 130px of content floating in a 744px
|
|
881
|
+
band is the most visible case in the deck, not the least.
|
|
882
|
+
|
|
883
|
+
The bottom padding buys nothing but symmetry — there is no notch down there — and that is the
|
|
884
|
+
point: the box this band centres in now shares its centre with the band a viewer sees.
|
|
885
|
+
*/
|
|
886
|
+
.stack-band.is-panel.is-notched {
|
|
887
|
+
padding-top: max(var(--deck-card-pad), var(--notch-h));
|
|
888
|
+
padding-bottom: max(var(--deck-card-pad), var(--notch-h));
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
/* ── The band's two registers ─────────────────────────────────────────────────────────────
|
|
892
|
+
The four named backgrounds, each a fill AND the ink published with it — so a band can never put an
|
|
893
|
+
ink on a fill that was not measured against it. They are the same four split.vue's panel takes, so
|
|
894
|
+
a stack band and a split panel in the same deck read as the same object.
|
|
895
|
+
|
|
896
|
+
⚠ THIS DESCRIBED A PALETTE THAT NO LONGER EXISTS. It named 'soft' (--deck-accent-soft), 'subtle'
|
|
897
|
+
(--deck-bg-subtle), --warm-100 and --forest-300, from when a band picked a loose fill and kept the
|
|
898
|
+
slide's own ink. The rules below take --deck-bg-<name> / --deck-on-bg-<name>: one table, four
|
|
899
|
+
names, fill and ink together. See the ★★ note at the top of this file for the grounds that are
|
|
900
|
+
deliberately not offered, and the contrast numbers behind it. */
|
|
901
|
+
.stack-band.is-panel.bg-warm { background: var(--deck-bg-warm); color: var(--deck-on-bg-warm); }
|
|
902
|
+
.stack-band.is-panel.bg-accent { background: var(--deck-bg-accent); color: var(--deck-on-bg-accent); }
|
|
903
|
+
.stack-band.is-panel.bg-vivid { background: var(--deck-bg-vivid); color: var(--deck-on-bg-vivid); }
|
|
904
|
+
.stack-band.is-panel.bg-neutral { background: var(--deck-bg-neutral); color: var(--deck-on-bg-neutral); }
|
|
905
|
+
/*
|
|
906
|
+
Two further grounds, added after the mint was judged to sit badly against the deck's ground — and
|
|
907
|
+
it does so for a reason the contrast table above could not show: --deck-bg is --warm-50, a WARM
|
|
908
|
+
cream, while forest-100 is a COOL mint. The table only ever asked "can the pill be seen", which
|
|
909
|
+
both pass; temperature is a separate question and the mint loses it.
|
|
910
|
+
|
|
911
|
+
'warm' — --warm-100, the ground's own family one step up. Same temperature, so the band reads as
|
|
912
|
+
a deeper pour of the slide rather than a different material laid on top.
|
|
913
|
+
'vivid' — --forest-300. This is as green as this layout can legally go: the running page pill is
|
|
914
|
+
--deck-bg-vivid-dark (forest-700, #046C4C) and lands ON the band, so the band is bounded
|
|
915
|
+
by the 3:1 non-text floor against that fill. Measured against the same pill:
|
|
916
|
+
|
|
917
|
+
warm-100 peach cream 5.32:1 ✓
|
|
918
|
+
forest-200 deeper mint 4.53:1 ✓
|
|
919
|
+
forest-300 emerald 3.55:1 ✓ <- 'vivid', the last rung that clears
|
|
920
|
+
forest-700 brand green 1.00:1 ✗ the pill IS the ground; it vanishes
|
|
921
|
+
forest-950 dark 2.40:1 ✗
|
|
922
|
+
|
|
923
|
+
So "just use the brand green" is not available here, and the reason is structural rather
|
|
924
|
+
than aesthetic: the chrome cannot re-tone itself per layout (frontmatter.static.test.ts,
|
|
925
|
+
"names no layout, in any form"). A slide that wants a full-field forest-700 has `divider`,
|
|
926
|
+
which owns the whole canvas and ships chrome: false.
|
|
927
|
+
*/
|
|
928
|
+
|
|
929
|
+
/*
|
|
930
|
+
THE STRIP — the title, subordinated. Top-aligned in its own row (it captions the band above it, so
|
|
931
|
+
it belongs against it rather than floating in the middle of what is left), and carrying the same
|
|
932
|
+
--deck-inset-text bottom bound every other text box in the theme has, which is also what keeps it
|
|
933
|
+
clear of the confidentiality pill's band.
|
|
934
|
+
*/
|
|
935
|
+
.stack-strip {
|
|
936
|
+
grid-row: 2;
|
|
937
|
+
min-width: 0;
|
|
938
|
+
min-height: 0;
|
|
939
|
+
padding-bottom: var(--deck-inset-text);
|
|
940
|
+
}
|
|
941
|
+
/* The strip is a caption-scale region, not a full slide of prose: two tiers of content in a third
|
|
942
|
+
of the canvas. The dense body tier, same as split's two columns, for the same reason. */
|
|
943
|
+
.stack-strip { font-size: var(--type-deck-body-sm-size); line-height: var(--type-deck-body-sm-line-height); }
|
|
944
|
+
</style>
|