@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,484 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
/*
|
|
3
|
+
Desktop browser chrome — the deck's port of the design system's FBrowserFrame. A neutral
|
|
4
|
+
macOS/Safari window: traffic lights, one centred address pill, a new-tab glyph, wrapping a live
|
|
5
|
+
screen at a real display aspect.
|
|
6
|
+
|
|
7
|
+
★★ NO CC-LICENSED ART HERE, AND THAT IS THE DIFFERENCE FROM DeviceFrame. A phone needs a photoreal
|
|
8
|
+
render of hardware, which is where the Wikimedia CC BY-SA vectors come in. A window is DRAWN, from
|
|
9
|
+
deck tokens, and owes nobody anything — so this file imports nothing. The traffic-light colours are
|
|
10
|
+
taken verbatim (#ff5f57 / #febc2e / #28c840): they are macOS's own, and a Forest-tinted
|
|
11
|
+
approximation would read as a mistake rather than as a window.
|
|
12
|
+
|
|
13
|
+
Redrawn rather than imported because FBrowserFrame.vue is written in Nuxt UI's semantic classes —
|
|
14
|
+
bg-default, bg-elevated, border-default, text-muted, text-dimmed — which this deck does not have.
|
|
15
|
+
|
|
16
|
+
★★ THE PORT WENT WRONG TWICE, IN OPPOSITE DIRECTIONS, AND BOTH ARE WORTH RECORDING.
|
|
17
|
+
|
|
18
|
+
First it was too little: the structure was right but it lost the shadow the original carries, and
|
|
19
|
+
it kept the original's 12px lights beside the DECK'S type. Jose: "where's the shadow? and the
|
|
20
|
+
traffic light buttons are too small compared with the url displayed."
|
|
21
|
+
|
|
22
|
+
Then, reading "the frame should look like actual chrome of a browser", it was rebuilt as GOOGLE
|
|
23
|
+
CHROME — a tab strip, back/forward/reload, a menu. Wrong word, wrong browser, and wrong instinct:
|
|
24
|
+
"chrome" is the generic term for a window's furniture, and the instruction was to match what the
|
|
25
|
+
design system already draws, which is Safari's single bar. Inventing a richer window made the deck
|
|
26
|
+
disagree with the system it is a specimen OF.
|
|
27
|
+
|
|
28
|
+
So this is the original's ARRANGEMENT — lights, one centred address field, a new-tab glyph — at the
|
|
29
|
+
deck's scale, with its shadow.
|
|
30
|
+
|
|
31
|
+
★★ AND ITS FINISH IS DELIBERATELY NOT THE ORIGINAL'S, WHICH IS A DIVERGENCE WORTH NAMING. Ported
|
|
32
|
+
literally it came out dated — Jose: "not like a real safari browser, not very modern" — and he was
|
|
33
|
+
right: FBrowserFrame draws a BORDERED RECTANGLE for the address field on a heavy grey slab, which
|
|
34
|
+
is macOS circa 2015. Current Safari is a capsule with a soft fill and no border, on a toolbar that
|
|
35
|
+
sits close to the page, under a wide low shadow.
|
|
36
|
+
|
|
37
|
+
A mock is judged on whether it reads as the thing it is imitating, so the finish follows Safari and
|
|
38
|
+
not the package. The arrangement still follows the package, so the two are the same window. If the
|
|
39
|
+
design system is refreshed, this is the direction it should take rather than the other way round.
|
|
40
|
+
|
|
41
|
+
★★ THE WHOLE BAR IS SIZED FROM THE WINDOW, NOT FROM THE DECK'S TYPE RAMP, and that is what makes
|
|
42
|
+
it read as a browser rather than as a deck component shaped like one. Set from
|
|
43
|
+
`--type-deck-caption-size` the bar came out 125px tall on a 1080px window — 11.6% of its width,
|
|
44
|
+
where Safari's is about 4% — a chunky slab that no amount of finish would have fixed.
|
|
45
|
+
|
|
46
|
+
So the bar's type is `width * 0.022` and every metric in it is `em`. The window is a PICTURE of a
|
|
47
|
+
browser, and a picture keeps its subject's proportions; DeviceFrame makes the same move for the
|
|
48
|
+
same reason, rendering its screen at true logical points and scaling the whole thing. At 1080 wide
|
|
49
|
+
that is 24px text — smaller than the deck's caption, and still legible from the back of a room.
|
|
50
|
+
|
|
51
|
+
★ THE LIGHTS ARE SIZED IN `em`, WHICH IS THE ACTUAL FIX rather than a bigger number. In the design
|
|
52
|
+
system the lights are 12px and the URL is 12px — one to one, and it reads correctly because both
|
|
53
|
+
are at a web page's scale. The port kept the 12 and inherited the deck's 36px caption type, so the
|
|
54
|
+
lights came out a third the height of the text beside them. Tying them to `em` makes the
|
|
55
|
+
relationship the thing that is preserved, so neither scale can drift from the other again.
|
|
56
|
+
*/
|
|
57
|
+
import { computed } from 'vue';
|
|
58
|
+
import { assetUrl } from '../assetUrl.js';
|
|
59
|
+
|
|
60
|
+
const RATIOS = { '16:10': 16 / 10, '16:9': 16 / 9 };
|
|
61
|
+
|
|
62
|
+
const props = defineProps({
|
|
63
|
+
/**
|
|
64
|
+
* What the address bar shows. Unset with a `src`, it is that URL's own host — so a window pointed
|
|
65
|
+
* at a real page cannot caption itself with somebody else's address, which a literal default of
|
|
66
|
+
* `'forest.bike'` would have let it do silently.
|
|
67
|
+
*/
|
|
68
|
+
url: { type: String, default: '' },
|
|
69
|
+
/**
|
|
70
|
+
* A LIVE PAGE to put in the screen, as a URL — rendered in an `<iframe>` at the window's own size.
|
|
71
|
+
*
|
|
72
|
+
* ★★ SLIDEV SUPPORTS THIS OUT OF THE BOX and this prop is not a re-implementation of it: the
|
|
73
|
+
* upstream theme ships `layout: iframe`, `iframe-left` and `iframe-right`, which give a URL a
|
|
74
|
+
* whole slide or half of one. This deck has its own nine layouts and none of them is that, and an
|
|
75
|
+
* iframe with no chrome around it is a rectangle of somebody else's website rather than a picture
|
|
76
|
+
* of a website — so the frame is what makes it read. A raw `<iframe>` in the slot still works
|
|
77
|
+
* anywhere, and is the escape hatch for a case this prop does not cover.
|
|
78
|
+
*
|
|
79
|
+
* ⚠ A LIVE PAGE IS A NETWORK DEPENDENCY ON A SLIDE. It draws nothing offline, it can change under
|
|
80
|
+
* the deck between rehearsal and the room, and `slidev export` renders whatever the site serves at
|
|
81
|
+
* export time. Where the page is the ARGUMENT rather than the illustration, `image` is the honest
|
|
82
|
+
* choice — a capture says what it said when it was taken. Reach for `src` when the point is that
|
|
83
|
+
* the thing is real and current.
|
|
84
|
+
*
|
|
85
|
+
* It is LIVE in every sense by default — see `interactive` below.
|
|
86
|
+
*/
|
|
87
|
+
src: { type: String, default: '' },
|
|
88
|
+
/**
|
|
89
|
+
* Whether a `src` page can be scrolled and clicked.
|
|
90
|
+
*
|
|
91
|
+
* ★★ TRUE, BECAUSE `src` ALREADY MEANT "I WANT THE REAL THING". This was frozen at first, on the
|
|
92
|
+
* reasoning that a deck is a picture of a product — and that is the argument for `image`, not for
|
|
93
|
+
* a prop whose whole purpose is that the page is current. Somebody who reaches for `src` has asked
|
|
94
|
+
* for the site; handing them a still of it that happens to cost a network request is the surprise.
|
|
95
|
+
* Jose, on the first cut: "it is not interactable? at least scrollable?"
|
|
96
|
+
*
|
|
97
|
+
* ⚠ THE ONE REAL COST IS KEYBOARD FOCUS. Slidev advances on arrow keys and space, and a focus
|
|
98
|
+
* inside the frame takes those with it — click the page, and the next arrow scrolls the site
|
|
99
|
+
* rather than turning the slide. Clicking the slide outside the frame gives it back. Scrolling and
|
|
100
|
+
* clicking are otherwise free: Slidev does not scroll slides, and the frame's `sandbox` denies
|
|
101
|
+
* top-navigation, so a link inside cannot move the deck out from under the presenter.
|
|
102
|
+
*
|
|
103
|
+
* Pass `false` for a page that must sit still — a slide left on screen unattended, or one where a
|
|
104
|
+
* stray scroll would leave the audience looking at the middle of somebody's footer. It is set with
|
|
105
|
+
* `inert` as well as `pointer-events`, so the frozen frame is out of the focus order too.
|
|
106
|
+
*/
|
|
107
|
+
interactive: { type: Boolean, default: true },
|
|
108
|
+
/**
|
|
109
|
+
* The screen's shape.
|
|
110
|
+
*
|
|
111
|
+
* ★ TWO VALUES, AND THE RESTRAINT IS THE DESIGN SYSTEM'S OWN: "the two shapes a desktop actually
|
|
112
|
+
* comes in". This port briefly offered 4:3 and 3:2 as well, which is a specimen of a monitor
|
|
113
|
+
* nobody has — and the frames package states why in its own comment: a hand-picked height drifts
|
|
114
|
+
* into ratios no display uses, which makes a mock's reflow decisions untestable against anything
|
|
115
|
+
* real. A phone gets its shape from a device registry; a desktop gets it from this pair.
|
|
116
|
+
*/
|
|
117
|
+
ratio: {
|
|
118
|
+
type: String,
|
|
119
|
+
default: '16:10',
|
|
120
|
+
// Repeated inline: defineProps() is hoisted out of setup() at build time, so this validator
|
|
121
|
+
// cannot close over the RATIOS const above — that compiles in dev and breaks a production build.
|
|
122
|
+
validator: (r) => ['16:10', '16:9'].includes(r),
|
|
123
|
+
},
|
|
124
|
+
/**
|
|
125
|
+
* How wide to draw the window, in canvas pixels. Unset, it takes the room it is given — as wide as
|
|
126
|
+
* that room allows, or narrower where the room is too short for a window that wide.
|
|
127
|
+
*
|
|
128
|
+
* ★★ IT USED TO NEED ONE, AND THAT WAS THE DEFECT RATHER THAN THE CONTRACT. The SCREEN's height is
|
|
129
|
+
* derived from the width and the ratio, so a full-width window on the 1728px content band is 1080
|
|
130
|
+
* of screen plus its bar — the whole canvas and then some — and every caller had to compute a
|
|
131
|
+
* width that fit. deck-template's slide 25 did not, and hung 367px off the bottom until the audit's
|
|
132
|
+
* canvas pass could see it again.
|
|
133
|
+
*
|
|
134
|
+
* The wrapper now caps itself against the room's HEIGHT (see `.deck-browser-room` below, and the
|
|
135
|
+
* hosts that give it one in base.css), so an unset width is a working default rather than a trap.
|
|
136
|
+
* Pass one only to draw the window SMALLER than its room on purpose — a figure at a chosen size,
|
|
137
|
+
* not a fit correction.
|
|
138
|
+
*/
|
|
139
|
+
width: { type: Number, default: 0 },
|
|
140
|
+
/** Draw the window on the dark register, for a mock of a dark-mode page. */
|
|
141
|
+
dark: { type: Boolean, default: false },
|
|
142
|
+
/**
|
|
143
|
+
* A screenshot to fill the screen with, as a URL — the same one-rule convention the layouts use for
|
|
144
|
+
* their figures. Cropped with `object-fit: cover`, so a capture taken at some other shape is
|
|
145
|
+
* trimmed rather than squashed. The slot wins for anything that is not a still.
|
|
146
|
+
*/
|
|
147
|
+
image: { type: String, default: '' },
|
|
148
|
+
/** Alt text for `image`, and the accessible title of a `src` frame. */
|
|
149
|
+
alt: { type: String, default: '' },
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
/*
|
|
153
|
+
The host, with `www.` dropped — what a browser's own address field shows and what somebody reading
|
|
154
|
+
a slide from the back of a room can actually take in. Wrapped because `new URL()` throws on a bare
|
|
155
|
+
host ('forest.bike'), which is exactly what a caller writing an address rather than a source would
|
|
156
|
+
pass; the empty string then falls through to the literal default below.
|
|
157
|
+
*/
|
|
158
|
+
const hostOf = (u) => {
|
|
159
|
+
try { return new URL(u).host.replace(/^www\./, ''); } catch { return ''; }
|
|
160
|
+
};
|
|
161
|
+
const address = computed(() => props.url || hostOf(props.src) || 'forest.bike');
|
|
162
|
+
</script>
|
|
163
|
+
|
|
164
|
+
<template>
|
|
165
|
+
<div class="deck-browser-room" :style="{ '--deck-browser-screen-ratio': String(RATIOS[ratio] ?? RATIOS['16:10']) }">
|
|
166
|
+
<div class="deck-browser-fit" :style="width ? { width: `${width}px` } : undefined">
|
|
167
|
+
<div class="deck-browser" :class="dark ? 'deck-dark is-dark' : ''">
|
|
168
|
+
<div class="deck-browser-bar">
|
|
169
|
+
<span class="deck-browser-lights" aria-hidden="true">
|
|
170
|
+
<i style="background: #ff5f57" /><i style="background: #febc2e" /><i style="background: #28c840" />
|
|
171
|
+
</span>
|
|
172
|
+
<span class="deck-browser-url">
|
|
173
|
+
<svg class="deck-browser-glyph" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
|
174
|
+
<rect x="4" y="10.5" width="16" height="11" rx="2.5" /><path d="M8 10.5V7a4 4 0 0 1 8 0v3.5" />
|
|
175
|
+
</svg>
|
|
176
|
+
<span class="deck-browser-url-text">{{ address }}</span>
|
|
177
|
+
</span>
|
|
178
|
+
<!-- The new-tab glyph the original carries, and the reason the address pill sits optically
|
|
179
|
+
centred: it balances the lights on the other side. -->
|
|
180
|
+
<svg class="deck-browser-glyph deck-browser-plus" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" aria-hidden="true">
|
|
181
|
+
<path d="M12 5v14M5 12h14" />
|
|
182
|
+
</svg>
|
|
183
|
+
</div>
|
|
184
|
+
|
|
185
|
+
<div class="deck-browser-screen">
|
|
186
|
+
<img v-if="image" :src="assetUrl(image)" :alt="alt" class="deck-browser-media" draggable="false">
|
|
187
|
+
<!--
|
|
188
|
+
`loading="lazy"` because a deck mounts more slides than it shows. This book sets
|
|
189
|
+
`preload: false`, so only the neighbours are built — but a deck that does not would
|
|
190
|
+
otherwise fetch every live page in it the moment it opens, and lazy defers an off-screen
|
|
191
|
+
frame in either case.
|
|
192
|
+
|
|
193
|
+
`sandbox="allow-scripts"` and nothing else: measured against forest.bike, scripts alone
|
|
194
|
+
render the page identically to an unsandboxed frame, and withholding `allow-same-origin`
|
|
195
|
+
gives it an opaque origin — no cookies, no storage — while the default denial of
|
|
196
|
+
top-navigation, forms, popups and downloads means an embedded page cannot move the deck
|
|
197
|
+
out from under the presenter. `referrerpolicy` keeps the deck's own URL out of the
|
|
198
|
+
embedded site's logs.
|
|
199
|
+
-->
|
|
200
|
+
<iframe
|
|
201
|
+
v-if="src"
|
|
202
|
+
class="deck-browser-live"
|
|
203
|
+
:class="interactive ? '' : 'is-frozen'"
|
|
204
|
+
:src="assetUrl(src)"
|
|
205
|
+
:title="alt || `A live view of ${address}`"
|
|
206
|
+
:inert="!interactive"
|
|
207
|
+
loading="lazy"
|
|
208
|
+
sandbox="allow-scripts"
|
|
209
|
+
referrerpolicy="no-referrer"
|
|
210
|
+
/>
|
|
211
|
+
<slot />
|
|
212
|
+
</div>
|
|
213
|
+
</div>
|
|
214
|
+
</div>
|
|
215
|
+
</div>
|
|
216
|
+
</template>
|
|
217
|
+
|
|
218
|
+
<style scoped>
|
|
219
|
+
/*
|
|
220
|
+
★ THE SHADOW IS THE ORIGINAL'S, not a new idea: FBrowserFrame carries
|
|
221
|
+
`drop-shadow(0 25px 45px rgba(0,0,0,.28))` and the first port simply dropped it, leaving the window
|
|
222
|
+
flat on the slide. It is also the only drop shadow in this theme, and that is right — every other
|
|
223
|
+
object here is OF the page, where a window is a picture of something in front of it.
|
|
224
|
+
|
|
225
|
+
A box-shadow rather than a filter: the frame is a plain rounded rectangle, so the cheaper property
|
|
226
|
+
does the same job, and a filter would establish a containing block for anything positioned inside.
|
|
227
|
+
*/
|
|
228
|
+
/*
|
|
229
|
+
★★ A CONTAINER, SO THE CHROME SCALES WITH THE WIDTH IT IS ACTUALLY DRAWN AT.
|
|
230
|
+
|
|
231
|
+
The bar's type was `width * 0.022` as an inline style, which only exists when a caller passes
|
|
232
|
+
`width`. Put in a split's figure cell the window takes the CELL's width and that prop is absent —
|
|
233
|
+
so it fell back to the deck's 36px caption and the chrome came out chunky again, the same defect
|
|
234
|
+
in a new place.
|
|
235
|
+
|
|
236
|
+
`container-type: inline-size` here and `cqw` inside means the proportion holds however the width
|
|
237
|
+
arrives: an explicit prop, a grid cell, a figure column. The wrapper exists because an element
|
|
238
|
+
cannot query itself — the window has to be the container's CHILD to read `cqw`.
|
|
239
|
+
*/
|
|
240
|
+
/*
|
|
241
|
+
★★ AND A SECOND WRAPPER OUTSIDE IT, WHICH IS THE ROOM RATHER THAN THE WINDOW.
|
|
242
|
+
|
|
243
|
+
Two boxes because they answer two different questions and each needs its own container. `-fit` is
|
|
244
|
+
the WINDOW's width, and the chrome queries it. `-room` is the space the window was given, and the
|
|
245
|
+
fit box queries THAT — a box cannot query itself, so the height the cap is computed from has to
|
|
246
|
+
come from one level up.
|
|
247
|
+
|
|
248
|
+
The room only knows its height where something outside gives it one; base.css names the three
|
|
249
|
+
hosts that do (`.slidev-layout > `, split's figure cell, stack's band), makes it a size container
|
|
250
|
+
there and caps the fit box against `100cqh` of it. Everywhere else there is no cap at all and the
|
|
251
|
+
window behaves as it always did — width-driven, uncapped — which is the right answer inside a grid
|
|
252
|
+
cell that has a caption under it and no height to spare.
|
|
253
|
+
*/
|
|
254
|
+
.deck-browser-room {
|
|
255
|
+
display: flex;
|
|
256
|
+
flex-direction: column;
|
|
257
|
+
justify-content: center;
|
|
258
|
+
align-items: center;
|
|
259
|
+
width: 100%;
|
|
260
|
+
}
|
|
261
|
+
.deck-browser-fit {
|
|
262
|
+
/*
|
|
263
|
+
THE BAR'S TYPE, PUBLISHED — because two elements need it and they resolve it against DIFFERENT
|
|
264
|
+
containers. `.deck-browser` uses it as `font-size`, where `cqw` is the fit box: the width the
|
|
265
|
+
window is actually drawn at, which is the whole point of the container below. The cap uses it
|
|
266
|
+
here, where `cqw` is the ROOM: wider than or equal to the window, so the height it reserves for
|
|
267
|
+
the bar is never an under-estimate. One declaration, two correct readings.
|
|
268
|
+
*/
|
|
269
|
+
--deck-browser-chrome: clamp(14px, 2.2cqw, 30px);
|
|
270
|
+
/*
|
|
271
|
+
★★ WHAT THE WINDOW ADDS ABOVE AND BELOW ITS SCREEN, WHICH IS THE ONE NUMBER THE FIT MATHS NEEDS.
|
|
272
|
+
|
|
273
|
+
`max-height` does not travel back through `aspect-ratio` — a box at `width: 100%` with a ratio
|
|
274
|
+
and a height cap keeps its width and simply loses its shape (measured: 500x300 in a 500x300 room
|
|
275
|
+
where 480x300 was wanted). So the cap has to be a MAX-WIDTH, and a max-width has to know how much
|
|
276
|
+
of the room the screen does not get.
|
|
277
|
+
|
|
278
|
+
2.1 is an upper bound on the bar's own height in `em`, not its exact height: the bar is 2.02em of
|
|
279
|
+
padding and pill plus a 1px border, so it measures 2.05em at the type's 30px ceiling and 2.09em
|
|
280
|
+
at its 14px floor. The 2px is the window's own border, top and bottom. Rounding UP is what makes
|
|
281
|
+
the whole expression safe — over-reserving leaves a few pixels of room unused, under-reserving
|
|
282
|
+
hangs the window off the slide, and only one of those is a defect.
|
|
283
|
+
|
|
284
|
+
ponytail: a constant restating a height CSS computes. The bar is not pinned to it deliberately —
|
|
285
|
+
a pinned height would clip the bar's own contents silently, where an under-reserve fails loudly
|
|
286
|
+
in scripts/deckAudit.ts's canvas pass. If the bar is ever retuned past 2.1em, that gate is what
|
|
287
|
+
says so; raise the number then.
|
|
288
|
+
*/
|
|
289
|
+
--deck-browser-frame-h: calc(2.1 * var(--deck-browser-chrome) + 2px);
|
|
290
|
+
container-type: inline-size;
|
|
291
|
+
width: 100%;
|
|
292
|
+
}
|
|
293
|
+
.deck-browser {
|
|
294
|
+
display: flex;
|
|
295
|
+
flex-direction: column;
|
|
296
|
+
width: 100%;
|
|
297
|
+
overflow: hidden;
|
|
298
|
+
/* 2.2cqw is the same factor the inline style used, expressed against the real width. The clamp is
|
|
299
|
+
a floor and a ceiling for the extremes: a thumbnail-sized window still needs legible chrome, and
|
|
300
|
+
a full-bleed one should not grow toolbar type without limit. Declared one level up so the fit
|
|
301
|
+
box can reserve the bar's height from the same expression — see `--deck-browser-chrome`. */
|
|
302
|
+
font-size: var(--deck-browser-chrome);
|
|
303
|
+
/*
|
|
304
|
+
★★ THE WINDOW'S OWN CORNER, NOT THE DECK'S CARD RADIUS. This was `--deck-radius-card`, which is
|
|
305
|
+
`--deck-radius * 6` — the radius a CARD takes in this deck, and far rounder than any window has.
|
|
306
|
+
Jose: "keep the rounded corners of the component, dont force a slide doctrine corner radius."
|
|
307
|
+
|
|
308
|
+
He is right, and the reason is what this component is. A card is furniture the deck owns, so it
|
|
309
|
+
takes the deck's radius; a browser window is a PICTURE OF SOMETHING ELSE, and its corner is a
|
|
310
|
+
fact about macOS the same way the traffic-light colours are. Overriding it makes the mock read
|
|
311
|
+
as a Forest card with a toolbar drawn on it.
|
|
312
|
+
|
|
313
|
+
★ AND IT IS THE CURRENT macOS CORNER, NOT THE OLD ONE. 0.55em landed at ~11px, which is Big Sur's
|
|
314
|
+
radius and the `rounded-xl` the design system's frame uses — and reads dated next to a machine
|
|
315
|
+
running anything recent, where windows are visibly rounder. Jose: "use a proper corner radius,
|
|
316
|
+
like how the newest macos do." 0.9em is about 20px on a 940px window, which is the modern shape.
|
|
317
|
+
|
|
318
|
+
In `em` so it scales with the window like every other metric in the chrome — the corner is a fact
|
|
319
|
+
about the subject, and it has to survive being drawn at any size.
|
|
320
|
+
*/
|
|
321
|
+
border-radius: 0.9em;
|
|
322
|
+
border: 1px solid var(--deck-border);
|
|
323
|
+
background: var(--deck-bg);
|
|
324
|
+
/*
|
|
325
|
+
★ TIGHTER AND FAINTER THAN THE DRAFT, AND IN `em` SO IT SCALES WITH THE WINDOW.
|
|
326
|
+
|
|
327
|
+
It was `0 40px 80px -24px / 0.30` plus a contact layer — which read as a spread-out haze under the
|
|
328
|
+
frame rather than as a window sitting on the page. Jose: "less prominent, not that spread out,
|
|
329
|
+
more subtle." The blur is roughly halved and the opacity cut, so it separates the window from the
|
|
330
|
+
slide without announcing itself from the back of a room.
|
|
331
|
+
|
|
332
|
+
In `em` for the same reason every other metric here is: the shadow is part of the object, and a
|
|
333
|
+
fixed 40px under a window drawn at 900 reads differently from the same 40px under one drawn at
|
|
334
|
+
1400. Two layers still — a broad ambient one and a short contact one, which is what a real window
|
|
335
|
+
casts — just quieter.
|
|
336
|
+
*/
|
|
337
|
+
box-shadow: 0 0.55em 1.1em -0.35em rgb(0 0 0 / 0.16), 0 0.1em 0.3em -0.12em rgb(0 0 0 / 0.10);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
.deck-browser-bar {
|
|
341
|
+
display: flex;
|
|
342
|
+
align-items: center;
|
|
343
|
+
/* The address pill was setting the bar's height through its own line box — 71px on a 1000px
|
|
344
|
+
window, 7.1% where Safari's toolbar is about 4%. A tight line-height hands the height back to
|
|
345
|
+
the padding, which is the thing that was tuned. */
|
|
346
|
+
line-height: 1.15;
|
|
347
|
+
gap: 1em;
|
|
348
|
+
padding: 0.36em 0.7em;
|
|
349
|
+
/*
|
|
350
|
+
Close to the page, not a slab on top of it. The original's `bg-elevated/60` reads as a second
|
|
351
|
+
surface; a modern toolbar is barely separated from the content — a hairline and a couple of
|
|
352
|
+
percent of tint, which is what keeps the window feeling like one object.
|
|
353
|
+
*/
|
|
354
|
+
border-bottom: 1px solid color-mix(in oklab, var(--deck-border), transparent 40%);
|
|
355
|
+
background: color-mix(in oklab, var(--deck-bg), var(--deck-fg) 3%);
|
|
356
|
+
color: var(--deck-fg-muted);
|
|
357
|
+
font-family: var(--font-sans);
|
|
358
|
+
}
|
|
359
|
+
/* One `em` each, so the lights and the address text keep the design system's one-to-one relationship
|
|
360
|
+
at whatever size this bar is set. See the note at the top — this is the whole fix. */
|
|
361
|
+
/*
|
|
362
|
+
★ HALF AN `em`, AND THE FIRST em-BASED NUMBER WAS STILL WRONG. Tying the lights to the type fixed
|
|
363
|
+
the relationship but 0.9em then made them huge — Jose: "after scaling the traffic light buttons are
|
|
364
|
+
huge compared to the other things". Measured against the real thing rather than guessed again:
|
|
365
|
+
macOS draws a 12px light in a 52px toolbar, so a light is about 23% of the bar's height. At 0.9em
|
|
366
|
+
on a 48px bar these were 20px — 42%, nearly double. 0.5em lands at 11px, which is 23%.
|
|
367
|
+
|
|
368
|
+
The lesson is that the type was the wrong thing to size them against: `em` is only the MECHANISM
|
|
369
|
+
that keeps them in step. The proportion they actually owe is to the BAR.
|
|
370
|
+
*/
|
|
371
|
+
.deck-browser-lights {
|
|
372
|
+
display: flex;
|
|
373
|
+
flex: none;
|
|
374
|
+
gap: 0.34em;
|
|
375
|
+
}
|
|
376
|
+
.deck-browser-lights i {
|
|
377
|
+
display: block;
|
|
378
|
+
width: 0.5em;
|
|
379
|
+
height: 0.5em;
|
|
380
|
+
border-radius: var(--deck-radius-full);
|
|
381
|
+
}
|
|
382
|
+
/*
|
|
383
|
+
`margin-inline: auto` with a max width, exactly as the original does it: the pill is centred in the
|
|
384
|
+
BAR rather than in the space left over, which is what keeps it centred under the window's own
|
|
385
|
+
midline whether or not the URL is long.
|
|
386
|
+
*/
|
|
387
|
+
.deck-browser-url {
|
|
388
|
+
display: flex;
|
|
389
|
+
align-items: center;
|
|
390
|
+
justify-content: center;
|
|
391
|
+
gap: 0.42em;
|
|
392
|
+
width: 100%;
|
|
393
|
+
max-width: 22em;
|
|
394
|
+
min-width: 0;
|
|
395
|
+
margin-inline: auto;
|
|
396
|
+
padding: 0.18em 1em;
|
|
397
|
+
/*
|
|
398
|
+
A CAPSULE WITH A FILL, NOT A BORDERED RECTANGLE. The border is what dated the port: current Safari
|
|
399
|
+
draws the field as a soft capsule that sits INSIDE the toolbar rather than as a control drawn on
|
|
400
|
+
top of it, and an outline around it reads as a text input from a decade ago.
|
|
401
|
+
*/
|
|
402
|
+
border-radius: var(--deck-radius-full);
|
|
403
|
+
background: color-mix(in oklab, var(--deck-bg), var(--deck-fg) 7%);
|
|
404
|
+
}
|
|
405
|
+
.deck-browser-url-text {
|
|
406
|
+
overflow: hidden;
|
|
407
|
+
/* Smaller than the bar's own type. Safari sets the address in a size below the rest of the
|
|
408
|
+
toolbar's, and at parity the URL was the loudest thing in the window — the address is a label,
|
|
409
|
+
not a headline. Applied to the TEXT rather than the pill so the field's own geometry, which is
|
|
410
|
+
tuned in `em`, does not shift with it. */
|
|
411
|
+
font-size: 0.82em;
|
|
412
|
+
text-overflow: ellipsis;
|
|
413
|
+
white-space: nowrap;
|
|
414
|
+
}
|
|
415
|
+
.deck-browser-glyph {
|
|
416
|
+
flex: none;
|
|
417
|
+
width: 0.9em;
|
|
418
|
+
height: 0.9em;
|
|
419
|
+
opacity: 0.6;
|
|
420
|
+
}
|
|
421
|
+
.deck-browser-plus {
|
|
422
|
+
width: 1.1em;
|
|
423
|
+
height: 1.1em;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
.deck-browser-screen {
|
|
427
|
+
/* From the ratio prop, as a custom property rather than an inline `aspect-ratio`, because the fit
|
|
428
|
+
box one level up needs the same number to turn a height cap into a width. */
|
|
429
|
+
aspect-ratio: var(--deck-browser-screen-ratio);
|
|
430
|
+
min-height: 0;
|
|
431
|
+
overflow: hidden;
|
|
432
|
+
background: var(--deck-bg);
|
|
433
|
+
color: var(--deck-fg);
|
|
434
|
+
}
|
|
435
|
+
/* The screen is a fixed shape and a capture is whatever shape it was taken at, so it is cropped
|
|
436
|
+
rather than distorted. `video` included — `<SlidevVideo>` is what carries one into a print export,
|
|
437
|
+
where a plain `<video>` prints blank. */
|
|
438
|
+
/*
|
|
439
|
+
The live page fills the screen at the window's own size, so the site lays out for the width the
|
|
440
|
+
window is drawn at — a 1000px window gets a desktop page and a narrow one gets what a narrow
|
|
441
|
+
browser gets, which is the whole reason to embed a page rather than photograph one.
|
|
442
|
+
|
|
443
|
+
A white ground rather than the deck's, so a page that has not painted yet reads as a blank page
|
|
444
|
+
rather than as a cream panel with a toolbar.
|
|
445
|
+
*/
|
|
446
|
+
.deck-browser-live {
|
|
447
|
+
display: block;
|
|
448
|
+
width: 100%;
|
|
449
|
+
height: 100%;
|
|
450
|
+
border: 0;
|
|
451
|
+
background: #fff;
|
|
452
|
+
}
|
|
453
|
+
/* `interactive: false`. `inert` on the element already blocks pointer events and focus in every
|
|
454
|
+
browser that ships it; this is the same statement in CSS, for the one that does not. */
|
|
455
|
+
.deck-browser-live.is-frozen {
|
|
456
|
+
pointer-events: none;
|
|
457
|
+
}
|
|
458
|
+
.deck-browser-screen :deep(img),
|
|
459
|
+
.deck-browser-screen :deep(video),
|
|
460
|
+
.deck-browser-screen :deep(picture),
|
|
461
|
+
.deck-browser-media {
|
|
462
|
+
display: block;
|
|
463
|
+
width: 100%;
|
|
464
|
+
height: 100%;
|
|
465
|
+
object-fit: cover;
|
|
466
|
+
}
|
|
467
|
+
/*
|
|
468
|
+
★★ DARK IS NEUTRAL, NOT FOREST, AND THAT IS THE ONE PLACE THIS COMPONENT LEAVES THE REGISTER.
|
|
469
|
+
|
|
470
|
+
`.deck-dark` re-points every `--deck-*` role, which is the deck's one dark mechanism and is why
|
|
471
|
+
there are no per-colour rules here. But its ground is `--forest-950` — a dark GREEN — and a browser
|
|
472
|
+
window rendered in it comes out a green window. Jose: "for dark dont use the green, use the neutral
|
|
473
|
+
palette." A window is a picture of macOS, not a Forest surface: its dark mode is grey.
|
|
474
|
+
|
|
475
|
+
So the class is still applied — every text and border role should take its dark mapping, and
|
|
476
|
+
anything slotted into the screen themes correctly — and only the three GROUNDS are re-pointed onto
|
|
477
|
+
the neutral ramp. `--deck-fg` and friends are left alone, because deck.css already resolves those
|
|
478
|
+
through `--neutral-*` rather than through the forest hue.
|
|
479
|
+
*/
|
|
480
|
+
.deck-browser.is-dark {
|
|
481
|
+
--deck-bg: var(--neutral-900);
|
|
482
|
+
--deck-border: var(--neutral-700);
|
|
483
|
+
}
|
|
484
|
+
</style>
|