mdeck 2.3.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/CHANGELOG.md +259 -0
- package/LICENSE +21 -0
- package/README.md +156 -0
- package/assets/base.css +196 -0
- package/assets/extensions/layouts/chapter/extension.toml +26 -0
- package/assets/extensions/layouts/chapter/layout.jsx +21 -0
- package/assets/extensions/layouts/chapter/starter.md +8 -0
- package/assets/extensions/layouts/focus/extension.toml +8 -0
- package/assets/extensions/layouts/focus/layout.jsx +10 -0
- package/assets/extensions/layouts/focus/starter.md +4 -0
- package/assets/extensions/layouts/full-bleed-image/extension.toml +31 -0
- package/assets/extensions/layouts/full-bleed-image/layout.jsx +19 -0
- package/assets/extensions/layouts/full-bleed-image/starter.md +6 -0
- package/assets/extensions/layouts/generic/extension.toml +8 -0
- package/assets/extensions/layouts/generic/layout.jsx +8 -0
- package/assets/extensions/layouts/generic/starter.md +4 -0
- package/assets/extensions/layouts/image-text/extension.toml +25 -0
- package/assets/extensions/layouts/image-text/layout.jsx +17 -0
- package/assets/extensions/layouts/image-text/starter.md +6 -0
- package/assets/extensions/layouts/split/extension.toml +25 -0
- package/assets/extensions/layouts/split/layout.jsx +19 -0
- package/assets/extensions/layouts/split/starter.md +14 -0
- package/assets/extensions/layouts/title/extension.toml +26 -0
- package/assets/extensions/layouts/title/layout.jsx +14 -0
- package/assets/extensions/layouts/title/starter.md +5 -0
- package/assets/extensions/palettes/brand/extension.toml +29 -0
- package/assets/extensions/palettes/cobalt/extension.toml +27 -0
- package/assets/extensions/palettes/ember/extension.toml +27 -0
- package/assets/extensions/palettes/forest/extension.toml +27 -0
- package/assets/extensions/palettes/graphite/extension.toml +27 -0
- package/assets/extensions/palettes/lagoon/extension.toml +27 -0
- package/assets/extensions/palettes/neon/extension.toml +27 -0
- package/assets/extensions/palettes/nordic/extension.toml +27 -0
- package/assets/extensions/palettes/pastel/extension.toml +30 -0
- package/assets/extensions/palettes/phosphor/extension.toml +27 -0
- package/assets/extensions/palettes/swiss/extension.toml +27 -0
- package/assets/extensions/palettes/terra/extension.toml +27 -0
- package/assets/extensions/themes/academic/extension.toml +53 -0
- package/assets/extensions/themes/academic/styles.css +631 -0
- package/assets/extensions/themes/aurora/extension.toml +37 -0
- package/assets/extensions/themes/aurora/styles.css +644 -0
- package/assets/extensions/themes/duet/extension.toml +46 -0
- package/assets/extensions/themes/duet/styles.css +573 -0
- package/assets/extensions/themes/editorial/extension.toml +46 -0
- package/assets/extensions/themes/editorial/styles.css +574 -0
- package/assets/extensions/themes/fhnw/extension.toml +61 -0
- package/assets/extensions/themes/fhnw/styles.css +604 -0
- package/assets/extensions/themes/neue/extension.toml +46 -0
- package/assets/extensions/themes/neue/styles.css +489 -0
- package/assets/extensions/themes/sketch/extension.toml +48 -0
- package/assets/extensions/themes/sketch/styles.css +633 -0
- package/assets/extensions/themes/terminal/extension.toml +38 -0
- package/assets/extensions/themes/terminal/styles.css +550 -0
- package/assets/logo/avatar.png +0 -0
- package/assets/logo/logo-dark.svg +10 -0
- package/assets/logo/logo-light.svg +10 -0
- package/assets/logo/mark.svg +15 -0
- package/bin/mdeck.js +2 -0
- package/docs/README.md +8 -0
- package/docs/reference/authoring.md +795 -0
- package/docs/reference/claude-skill.md +96 -0
- package/docs/reference/extensions.md +250 -0
- package/docs/reference/layouts.md +153 -0
- package/docs/reference/palettes.md +106 -0
- package/docs/reference/structured-slides.md +120 -0
- package/docs/reference/themes.md +133 -0
- package/docs/site/assets/favicon.svg +15 -0
- package/docs/site/assets/github-logo.svg +1 -0
- package/docs/site/assets/site.css +162 -0
- package/docs/site/assets/site.js +123 -0
- package/docs/site/build.js +148 -0
- package/docs/site/content/appearance.md +203 -0
- package/docs/site/content/audience.md +178 -0
- package/docs/site/content/commands.md +153 -0
- package/docs/site/content/components.md +134 -0
- package/docs/site/content/drawing.md +72 -0
- package/docs/site/content/editing.md +59 -0
- package/docs/site/content/getting-started.md +103 -0
- package/docs/site/content/layouts.md +126 -0
- package/docs/site/content/media.md +107 -0
- package/docs/site/content/more-content.md +130 -0
- package/docs/site/content/presenting.md +93 -0
- package/docs/site/content/reusable-layouts.md +74 -0
- package/docs/site/content/sharing.md +88 -0
- package/docs/site/content/theme-authoring.md +144 -0
- package/docs/site/content/theme-repository.md +76 -0
- package/docs/site/content/troubleshooting.md +92 -0
- package/docs/site/content/views.md +56 -0
- package/docs/site/content/welcome.md +71 -0
- package/docs/site/content/writing.md +121 -0
- package/docs/site/examples/first-talk.md +88 -0
- package/docs/site/examples/notebook.svg +58 -0
- package/docs/site/images/themes/academic-dark.webp +0 -0
- package/docs/site/images/themes/academic-light.webp +0 -0
- package/docs/site/images/themes/academic.webp +0 -0
- package/docs/site/images/themes/aurora-dark.webp +0 -0
- package/docs/site/images/themes/aurora-light.webp +0 -0
- package/docs/site/images/themes/aurora.webp +0 -0
- package/docs/site/images/themes/duet-dark.webp +0 -0
- package/docs/site/images/themes/duet-light.webp +0 -0
- package/docs/site/images/themes/duet.webp +0 -0
- package/docs/site/images/themes/editorial-dark.webp +0 -0
- package/docs/site/images/themes/editorial-light.webp +0 -0
- package/docs/site/images/themes/editorial.webp +0 -0
- package/docs/site/images/themes/fhnw.webp +0 -0
- package/docs/site/images/themes/neue-dark.webp +0 -0
- package/docs/site/images/themes/neue-light.webp +0 -0
- package/docs/site/images/themes/neue.webp +0 -0
- package/docs/site/images/themes/sketch-dark.webp +0 -0
- package/docs/site/images/themes/sketch-light.webp +0 -0
- package/docs/site/images/themes/sketch.webp +0 -0
- package/docs/site/images/themes/terminal-dark.webp +0 -0
- package/docs/site/images/themes/terminal-light.webp +0 -0
- package/docs/site/images/themes/terminal.webp +0 -0
- package/docs/site/package.json +9 -0
- package/docs/site/pages.js +27 -0
- package/docs/site/server.js +58 -0
- package/examples/custom-layouts/extensions/comparison/extension.toml +35 -0
- package/examples/custom-layouts/extensions/comparison/layout.jsx +15 -0
- package/examples/custom-layouts/extensions/comparison/starter.md +17 -0
- package/examples/custom-layouts/extensions/comparison/styles.css +4 -0
- package/examples/custom-layouts/slides.md +60 -0
- package/examples/poll/slides.md +127 -0
- package/examples/python/.mdeck-backups/slides.drawings-2026-10-06T08-36-47Z.json +19 -0
- package/examples/python/.mdeck-backups/slides.drawings-2026-10-06T11-27-30Z.json +6 -0
- package/examples/python/slides.drawings.json +6 -0
- package/examples/python/slides.md +493 -0
- package/examples/showcase/.mdeck-backups/slides.drawings-2026-10-06T08-08-30Z.json +10 -0
- package/examples/showcase/.mdeck-backups/slides.drawings-2026-10-06T11-41-35Z.json +13 -0
- package/examples/showcase/img/image.jpg +0 -0
- package/examples/showcase/img/mdeck.svg +66 -0
- package/examples/showcase/slides.drawings.json +11 -0
- package/examples/showcase/slides.md +427 -0
- package/package.json +75 -0
- package/skills/write-slides/SKILL.md +144 -0
- package/src/build/check.js +49 -0
- package/src/build/chrome.js +130 -0
- package/src/build/components.js +44 -0
- package/src/build/config.js +61 -0
- package/src/build/drawings.js +14 -0
- package/src/build/editorPlugin.js +272 -0
- package/src/build/editorServer.js +36 -0
- package/src/build/fonts.js +84 -0
- package/src/build/homePlugin.js +208 -0
- package/src/build/inkPlugin.js +192 -0
- package/src/build/ownWrites.js +18 -0
- package/src/build/pairing.js +81 -0
- package/src/build/pdf.js +34 -0
- package/src/build/relayAccess.js +85 -0
- package/src/build/slidesPlugin.js +371 -0
- package/src/build/themeCheck.js +151 -0
- package/src/build/tunnelClient.js +184 -0
- package/src/cli/args.js +76 -0
- package/src/cli/index.js +758 -0
- package/src/cli/migrate.js +123 -0
- package/src/cli/skill.js +40 -0
- package/src/cli/themes.js +140 -0
- package/src/components/CodeBlock.jsx +168 -0
- package/src/components/ConnectedViews.jsx +25 -0
- package/src/components/Icon.jsx +8 -0
- package/src/components/Poll.jsx +67 -0
- package/src/components/QrCode.jsx +29 -0
- package/src/components/QrCodeTag.jsx +27 -0
- package/src/components/Question.jsx +28 -0
- package/src/components/Scale.jsx +47 -0
- package/src/components/VideoPlayer.jsx +108 -0
- package/src/components/WordCloud.jsx +106 -0
- package/src/components/activity.jsx +35 -0
- package/src/components/callout.css +78 -0
- package/src/components/code-block.css +123 -0
- package/src/components/columns.css +11 -0
- package/src/components/poll.css +81 -0
- package/src/components/video-player.css +14 -0
- package/src/core/devPath.js +4 -0
- package/src/core/editDeck.js +230 -0
- package/src/core/icons.js +69 -0
- package/src/core/ink.js +221 -0
- package/src/core/labels.js +144 -0
- package/src/core/outline.js +33 -0
- package/src/core/parseSlides.js +152 -0
- package/src/core/source.js +137 -0
- package/src/core/urls.js +34 -0
- package/src/core/validateDeck.js +139 -0
- package/src/editor/api.js +67 -0
- package/src/editor/components/App.jsx +75 -0
- package/src/editor/components/ConflictBanner.jsx +10 -0
- package/src/editor/components/DeckSettings.jsx +108 -0
- package/src/editor/components/DesignApp.jsx +252 -0
- package/src/editor/components/Diagnostics.jsx +10 -0
- package/src/editor/components/ExtensionEditor.jsx +66 -0
- package/src/editor/components/ExtensionForms.jsx +134 -0
- package/src/editor/components/ExtensionsPanel.jsx +114 -0
- package/src/editor/components/Field.jsx +24 -0
- package/src/editor/components/Inspector.jsx +36 -0
- package/src/editor/components/LayoutPicker.jsx +23 -0
- package/src/editor/components/LayoutSelect.jsx +25 -0
- package/src/editor/components/LookThumbnail.jsx +8 -0
- package/src/editor/components/MetaFields.jsx +24 -0
- package/src/editor/components/Outline.jsx +28 -0
- package/src/editor/components/PalettePicker.jsx +32 -0
- package/src/editor/components/Preview.jsx +63 -0
- package/src/editor/components/PropsForm.jsx +46 -0
- package/src/editor/components/RawSlideEditor.jsx +9 -0
- package/src/editor/components/RegionEditor.jsx +15 -0
- package/src/editor/components/Resizer.jsx +18 -0
- package/src/editor/designLink.js +19 -0
- package/src/editor/editor.css +139 -0
- package/src/editor/extensions.js +86 -0
- package/src/editor/keyboard.js +21 -0
- package/src/editor/model.js +38 -0
- package/src/editor/sampleDeck.js +165 -0
- package/src/editor/saveQueue.js +47 -0
- package/src/editor/schema.js +36 -0
- package/src/editor/state.js +92 -0
- package/src/editor/useDeckDocument.js +67 -0
- package/src/extensions/appearance.js +79 -0
- package/src/extensions/contrast.js +57 -0
- package/src/extensions/discover.js +126 -0
- package/src/extensions/manifest.js +209 -0
- package/src/extensions/packs.js +268 -0
- package/src/extensions/tokens.js +25 -0
- package/src/home/Home.jsx +362 -0
- package/src/home/home.css +148 -0
- package/src/layouts/layoutApi.jsx +205 -0
- package/src/layouts/layoutProps.js +30 -0
- package/src/layouts/renderSlide.jsx +17 -0
- package/src/live/answer/answer.css +34 -0
- package/src/live/answer/answer.html +14 -0
- package/src/live/answer/answer.js +130 -0
- package/src/live/client.js +290 -0
- package/src/live/code.js +19 -0
- package/src/live/follow.js +17 -0
- package/src/live/pairing.js +34 -0
- package/src/live/presence.js +33 -0
- package/src/live/roomTag.js +38 -0
- package/src/live/rooms.js +249 -0
- package/src/live/server.js +82 -0
- package/src/live/tunnel.js +180 -0
- package/src/paths.js +9 -0
- package/src/runtime/ReaderView.jsx +193 -0
- package/src/runtime/SlideErrorBoundary.jsx +28 -0
- package/src/runtime/chrome.jsx +55 -0
- package/src/runtime/deck-stage.js +1533 -0
- package/src/runtime/design.html +12 -0
- package/src/runtime/design.jsx +5 -0
- package/src/runtime/editor.html +12 -0
- package/src/runtime/editor.jsx +5 -0
- package/src/runtime/editorBridge.js +58 -0
- package/src/runtime/fit.js +149 -0
- package/src/runtime/home.html +13 -0
- package/src/runtime/home.jsx +5 -0
- package/src/runtime/index.html +15 -0
- package/src/runtime/ink/InkLayer.jsx +17 -0
- package/src/runtime/ink/Toolbar.jsx +145 -0
- package/src/runtime/ink/attach.js +69 -0
- package/src/runtime/ink/bus.js +126 -0
- package/src/runtime/ink/controller.js +132 -0
- package/src/runtime/ink/room.js +101 -0
- package/src/runtime/ink/select.js +124 -0
- package/src/runtime/ink/server.js +75 -0
- package/src/runtime/ink/store.js +55 -0
- package/src/runtime/ink/toolbar.css +79 -0
- package/src/runtime/main.jsx +863 -0
- package/src/runtime/markedSetup.js +147 -0
- package/src/runtime/reader.css +59 -0
- package/src/runtime/registry.jsx +26 -0
- package/src/runtime/syncOrder.js +32 -0
- package/src/runtime/themeLoader.js +68 -0
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# Structured slides and validation
|
|
2
|
+
|
|
3
|
+
Existing decks keep their `---` frontmatter syntax. Code fences protect literal
|
|
4
|
+
`---` and `:::notes` examples. A deck can also start directly with Markdown.
|
|
5
|
+
Legacy slide metadata is recognized by a known field at the start of the block
|
|
6
|
+
(such as `layout:`, `section:` or `image:`). For arbitrary metadata, or to remove
|
|
7
|
+
that ambiguity, use an explicit metadata block at the start of a slide:
|
|
8
|
+
|
|
9
|
+
```markdown
|
|
10
|
+
---
|
|
11
|
+
:::meta
|
|
12
|
+
layout: split
|
|
13
|
+
id: deployment-options
|
|
14
|
+
:::
|
|
15
|
+
# Deployment options
|
|
16
|
+
|
|
17
|
+
:::slot left
|
|
18
|
+
## Directory bundle
|
|
19
|
+
- Separate media files
|
|
20
|
+
:::
|
|
21
|
+
|
|
22
|
+
:::slot right
|
|
23
|
+
## Single HTML file
|
|
24
|
+
- Easy to transfer
|
|
25
|
+
:::
|
|
26
|
+
|
|
27
|
+
:::notes
|
|
28
|
+
Explain the tradeoff.
|
|
29
|
+
:::
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
`:::meta` belongs to the slide, while the optional initial deck configuration
|
|
33
|
+
continues to use YAML between `---` lines. Put a blank line before a directive
|
|
34
|
+
when combining it with ordinary Markdown. Directives can nest: for example, a
|
|
35
|
+
callout inside columns inside a slot. `+++` separates columns only at that
|
|
36
|
+
column block's top level. Slots, notes and metadata are top-level slide blocks.
|
|
37
|
+
|
|
38
|
+
Unassigned Markdown is the `body` region. `split` accepts `left` and `right`
|
|
39
|
+
regions with the shared body above them; without named regions it retains the
|
|
40
|
+
legacy first-block-left behavior. An explicit `:::slot body` is also supported,
|
|
41
|
+
but cannot be combined with unassigned content. Duplicate or unclosed regions
|
|
42
|
+
are errors. Layouts declare the other regions they accept.
|
|
43
|
+
|
|
44
|
+
Set `id:` for a durable link such as `#deployment-options`. Numeric links such
|
|
45
|
+
as `#3` remain supported. Without an authored ID, the parser assigns positional
|
|
46
|
+
IDs (`slide-1`, etc.); these are not stable across reordering.
|
|
47
|
+
|
|
48
|
+
Run `mdeck check talk.md` to validate YAML, dimensions, IDs, metadata and local
|
|
49
|
+
asset references. `--strict` also fails on warnings. Builds reject source errors;
|
|
50
|
+
unknown layouts warn and retain the generic fallback for compatibility.
|
|
51
|
+
|
|
52
|
+
## Source model for integrations
|
|
53
|
+
|
|
54
|
+
`parseSlides(source)` returns the original `source`, `deckConfig`, `configSource`,
|
|
55
|
+
`slides` and `diagnostics`. Each slide has its resolved `meta`, `authoredMeta`
|
|
56
|
+
(the authored YAML fields before section inheritance), `id`, `content`, `regions`, directive `blocks`, and
|
|
57
|
+
`source`, `metaSource`, `bodySource` ranges. Ranges are half-open UTF-16 offsets
|
|
58
|
+
into the original string, including its whitespace and line endings.
|
|
59
|
+
|
|
60
|
+
Regions contain Markdown `content`. Explicit regions also carry their original
|
|
61
|
+
`raw` body and contiguous `source` range. The implicit body has `ranges` because
|
|
62
|
+
notes and slots may interrupt it. This is a source-preserving document model,
|
|
63
|
+
not a normalized Markdown serializer. Speaker notes come from `:::notes` body
|
|
64
|
+
blocks and are available as `slide.meta.notes`; they are not authored metadata.
|
|
65
|
+
|
|
66
|
+
`serializeDeck(deck)` returns the original source unchanged. Use
|
|
67
|
+
`replaceRegion(deck, slideId, regionName, markdown)` for targeted explicit-region
|
|
68
|
+
edits, or `applySourceEdits(source, edits)` for general changes. Edits specify
|
|
69
|
+
`start`, `end`, `text` and optionally `expected` text; overlaps and stale expected
|
|
70
|
+
text are rejected. Reparse the returned source after editing. Callers saving to
|
|
71
|
+
disk must also compare against the current file to detect external changes.
|
|
72
|
+
|
|
73
|
+
## Editing helpers
|
|
74
|
+
|
|
75
|
+
`src/core/editDeck.js` builds on the source model to change a deck without
|
|
76
|
+
disturbing anything else in the file. Every function takes the parsed deck,
|
|
77
|
+
never mutates it, and returns the new source (or `{ source, index }` for
|
|
78
|
+
structural changes); reparse the result before the next edit. Untouched bytes
|
|
79
|
+
stay identical, including comments, blank lines and the file's line endings.
|
|
80
|
+
|
|
81
|
+
| Function | Effect |
|
|
82
|
+
|---|---|
|
|
83
|
+
| `setRegion(deck, slideId, name, markdown)` | Replaces an explicit `:::slot` body, the implicit body (discontiguous ranges collapse into the first one), or appends a new slot |
|
|
84
|
+
| `removeRegion(deck, slideId, name)` | Removes a slot block |
|
|
85
|
+
| `setSlideMeta(deck, slideId, patch)` | Patches top-level keys of the slide's YAML; `undefined` deletes a key; works for `:::meta` and the legacy `---` form, inserting or removing the block as needed |
|
|
86
|
+
| `setSlideNotes(deck, slideId, markdown)` | Edits or removes a `:::notes` block, or appends one when absent |
|
|
87
|
+
| `setDeckConfig(deck, patch)` | Patches the frontmatter, inserting it when absent |
|
|
88
|
+
| `insertSlide(deck, index, markdown)` | Inserts a slide, keeping delimiters and blank lines tidy |
|
|
89
|
+
| `removeSlide(deck, slideId)` / `moveSlide(deck, slideId, toIndex)` | Structural changes that return the index to select next |
|
|
90
|
+
| `replaceSlideSource(deck, slideId, text)` | Replaces one slide's Markdown verbatim |
|
|
91
|
+
|
|
92
|
+
Metadata patches rewrite only the block of each changed key
|
|
93
|
+
(`patchYamlMapping` in `source.js`), so comments on other keys survive.
|
|
94
|
+
Comments inside an edited nested block such as `props:` are lost.
|
|
95
|
+
|
|
96
|
+
## Editor API
|
|
97
|
+
|
|
98
|
+
`mdeck edit` serves a small JSON API from the Vite dev server for the browser
|
|
99
|
+
editor. It binds to the loopback interface, refuses requests whose `Origin`
|
|
100
|
+
differs from its own host, and only ever writes the deck file and folders
|
|
101
|
+
under the deck's `extensions/` directory.
|
|
102
|
+
|
|
103
|
+
| Route | Purpose |
|
|
104
|
+
|---|---|
|
|
105
|
+
| `GET /__mdeck/deck` | `{ path, name, source, hash, registry, warnings }` |
|
|
106
|
+
| `POST /__mdeck/source` | `{ source, base }` writes when `base` equals the hash of the file on disk; otherwise `409 { source, hash }` |
|
|
107
|
+
| `GET /__mdeck/extension/:kind/:id` | The extension's manifest and text files |
|
|
108
|
+
| `PUT /__mdeck/extension/:kind/:id` | `{ files: { name: text \| null } }` writes into `extensions/<id>/` after validating the manifest against the files that will exist; built-ins are refused |
|
|
109
|
+
| `DELETE /__mdeck/extension/:kind/:id` | Removes a deck-local extension folder |
|
|
110
|
+
|
|
111
|
+
Two custom websocket events keep the editor in step with the disk:
|
|
112
|
+
`mdeck:deck-changed { hash }` when another program saved the deck, and
|
|
113
|
+
`mdeck:extensions-changed { file }` when an extension file changed.
|
|
114
|
+
|
|
115
|
+
The preview iframe runs the normal deck runtime with `?view=deck&editor=1`. The editor
|
|
116
|
+
posts `{ deckSource: { source, selection, config, overrides } }` to it;
|
|
117
|
+
`config` merges over the deck settings (for example to preview a palette) and
|
|
118
|
+
`overrides` carries unsaved theme and palette manifests. The runtime answers
|
|
119
|
+
with `{ deckEditorReady }`, `{ deckRendered: { slideCount, diagnostics, error } }`
|
|
120
|
+
and the usual `{ deckStateChanged }` messages.
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
# Themes
|
|
2
|
+
|
|
3
|
+
A theme defines how slides look: typography, spacing and layout structure. Its
|
|
4
|
+
colours come from a [palette](palettes.md): each theme names a default, and the
|
|
5
|
+
deck may choose another. A theme is one kind of [extension](extensions.md).
|
|
6
|
+
|
|
7
|
+
## Using a theme
|
|
8
|
+
|
|
9
|
+
Set `theme:` in the deck frontmatter:
|
|
10
|
+
|
|
11
|
+
```yaml
|
|
12
|
+
---
|
|
13
|
+
theme: neue
|
|
14
|
+
---
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
| Theme | Look | Default palette |
|
|
18
|
+
|---|---|---|
|
|
19
|
+
| `neue` | Swiss style: large flush-left type, rules, red on white | `swiss` |
|
|
20
|
+
| `aurora` | Northern lights: curtains of light, night-sky chapters, frosted glass | `neon` |
|
|
21
|
+
| `duet` | Two voices: heavy slab headings, split slides as two colour fields, diagonal chapters | `cobalt` |
|
|
22
|
+
| `editorial` | Magazine spread: masthead rule, drop caps, pull quotes, Roman chapter numerals | `terra` |
|
|
23
|
+
| `fhnw` | The FHNW corporate design | `brand` (the only one) |
|
|
24
|
+
| `terminal` | A terminal session: prompts, a blinking cursor, a tmux status bar | `phosphor`, dark |
|
|
25
|
+
| `academic` | A lecture: compact type, the heading fixed at the top and the content centred below, theorem and definition blocks, booktabs tables, a footnote rule | `nordic` |
|
|
26
|
+
| `sketch` | A sketchbook: handwriting on dotted paper, marker scribbles, sticky-note callouts, taped photos | `pastel` |
|
|
27
|
+
|
|
28
|
+
`mdeck list my-talk.md` lists them with their descriptions.
|
|
29
|
+
|
|
30
|
+
## Theme structure
|
|
31
|
+
|
|
32
|
+
```text
|
|
33
|
+
assets/extensions/themes/my-theme/ built-in
|
|
34
|
+
extensions/my-theme/ beside a deck
|
|
35
|
+
extension.toml identity, default palette, fonts, tokens, parameters
|
|
36
|
+
styles.css slide frame and layout rules
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
No registration step is needed: any theme folder in either place is found
|
|
40
|
+
automatically, and the `mdeck new` wizard offers it.
|
|
41
|
+
|
|
42
|
+
### `extension.toml`
|
|
43
|
+
|
|
44
|
+
```toml
|
|
45
|
+
schema = 1
|
|
46
|
+
kind = "theme"
|
|
47
|
+
id = "my-theme"
|
|
48
|
+
title = "My Theme"
|
|
49
|
+
palette = "lagoon"
|
|
50
|
+
fonts = ["https://fonts.googleapis.com/css2?family=..."]
|
|
51
|
+
|
|
52
|
+
[tokens]
|
|
53
|
+
"--fs-display" = "128px"
|
|
54
|
+
"--fs-h" = "64px"
|
|
55
|
+
"--fs-body" = "34px"
|
|
56
|
+
"--pad-x" = "140px"
|
|
57
|
+
"--pad-y" = "110px"
|
|
58
|
+
"--font-display" = "\"My Display Font\", sans-serif"
|
|
59
|
+
"--font-body" = "\"My Body Font\", sans-serif"
|
|
60
|
+
|
|
61
|
+
[params.fontDisplay]
|
|
62
|
+
token = "--font-display"
|
|
63
|
+
title = "Display font"
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
`palette` names the default palette. `tokens` hold the typography and spacing
|
|
67
|
+
values the stylesheet uses; mdeck writes them into a `:root` block ahead of the
|
|
68
|
+
stylesheet. A theme may not set the nine palette colours.
|
|
69
|
+
|
|
70
|
+
`params` connect author-facing names to tokens. Users set them in the deck:
|
|
71
|
+
|
|
72
|
+
```yaml
|
|
73
|
+
params:
|
|
74
|
+
fontDisplay: "Georgia, serif"
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
A parameter's default is its token's value. Unknown parameter names are
|
|
78
|
+
reported by `mdeck check`.
|
|
79
|
+
|
|
80
|
+
Optional settings: `description`, `appearance = "dark"` for a theme that starts
|
|
81
|
+
dark, and `palettes = [...]` to offer only those palettes (the FHNW theme offers
|
|
82
|
+
only `brand`). `[files] styles` may name one stylesheet or a list.
|
|
83
|
+
|
|
84
|
+
### `styles.css`
|
|
85
|
+
|
|
86
|
+
Layout CSS for all slide types. It refers to the palette colours (`--bg`,
|
|
87
|
+
`--ink`, `--accent` …) and the theme's tokens for every colour and spacing value
|
|
88
|
+
and contains no `:root` defaults of its own. It must style:
|
|
89
|
+
|
|
90
|
+
- `.slide` — base slide
|
|
91
|
+
- `.slide-header`, `.slide-footer`, `.slide-body` — structural rails
|
|
92
|
+
- `.slide--title`, `.slide--chapter`, `.slide--focus`, `.slide--image-text`, `.slide--split`, `.slide--full-bleed-image` — the built-in layouts
|
|
93
|
+
|
|
94
|
+
## Custom layouts
|
|
95
|
+
|
|
96
|
+
Define reusable slide structures with [deck-local layouts](layouts.md).
|
|
97
|
+
They provide named regions, typed properties, and shared slide chrome without
|
|
98
|
+
editing the framework. Theme CSS continues to style those structures.
|
|
99
|
+
|
|
100
|
+
For compatibility, an unrecognised `layout:` value warns and falls through to the
|
|
101
|
+
generic renderer. It renders the full Markdown body with standard slide chrome
|
|
102
|
+
and emits a `.slide--<layout-name>` class. Prefer a declared layout for new layouts.
|
|
103
|
+
|
|
104
|
+
To style a custom layout, target `.slide--<layout-name>` in a CSS file loaded alongside your deck, or use inline styles in your markdown HTML.
|
|
105
|
+
|
|
106
|
+
Because `.slide-body` sets baseline typography (`font-family`, `font-size`, `color`, `line-height`) as inheritable defaults, any raw HTML you write inside a custom slide automatically inherits the theme's body text style. You only need inline styles for structural concerns like grid layout:
|
|
107
|
+
|
|
108
|
+
```markdown
|
|
109
|
+
---
|
|
110
|
+
layout: four-columns
|
|
111
|
+
---
|
|
112
|
+
# My heading
|
|
113
|
+
|
|
114
|
+
<div style="display: grid; grid-template-columns: repeat(4, 1fr); gap: 48px;">
|
|
115
|
+
<div><strong>One</strong> — description text</div>
|
|
116
|
+
<div><strong>Two</strong> — description text</div>
|
|
117
|
+
<div><strong>Three</strong> — description text</div>
|
|
118
|
+
<div><strong>Four</strong> — description text</div>
|
|
119
|
+
</div>
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Standard markdown elements (`h1`–`h6`, `p`, `ul`, `ol`, `strong`, `em`, `code`) are styled by the theme and work without any inline styles.
|
|
123
|
+
|
|
124
|
+
## Colour contract
|
|
125
|
+
|
|
126
|
+
A theme uses only the nine palette colours, and mixes of them
|
|
127
|
+
(`color-mix(in oklab, var(--accent) 14%, var(--bg))`). This is what makes every
|
|
128
|
+
palette fit every theme. For an inverted slide it reads the palette's other
|
|
129
|
+
variant, `--inverse-bg`, `--inverse-ink`, `--inverse-accent` and so on, which is
|
|
130
|
+
tuned to be read on its own background.
|
|
131
|
+
|
|
132
|
+
Themes may define additional tokens for type, spacing and shapes; those are
|
|
133
|
+
theme-specific and are not affected by palettes.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" width="512" height="512">
|
|
2
|
+
<title>mdeck</title>
|
|
3
|
+
<rect width="512" height="512" rx="96" fill="#0d9488"/>
|
|
4
|
+
<rect x="72" y="136" width="368" height="240" rx="18" fill="#f4f1e8"/>
|
|
5
|
+
<g fill="#0d9488">
|
|
6
|
+
<rect x="132" y="176" width="26" height="160" rx="13"/>
|
|
7
|
+
<rect x="184" y="176" width="26" height="160" rx="13"/>
|
|
8
|
+
<rect x="102" y="222" width="140" height="26" rx="13"/>
|
|
9
|
+
<rect x="102" y="266" width="140" height="26" rx="13"/>
|
|
10
|
+
</g>
|
|
11
|
+
<g fill="#1b1a17" opacity="0.85">
|
|
12
|
+
<rect x="268" y="226" width="130" height="20" rx="10"/>
|
|
13
|
+
<rect x="268" y="276" width="88" height="20" rx="10"/>
|
|
14
|
+
</g>
|
|
15
|
+
</svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="20" height="20"><path fill="#24292f" d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z"/></svg>
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
:root {
|
|
2
|
+
--paper: #fff;
|
|
3
|
+
--mist: #eef2f7;
|
|
4
|
+
--ink: #203148;
|
|
5
|
+
--secondary: #54647a;
|
|
6
|
+
--blue: #244ec7;
|
|
7
|
+
--blue-soft: #e8eefc;
|
|
8
|
+
--rule: #dbe3ee;
|
|
9
|
+
--code: #f5f7fa;
|
|
10
|
+
--font: 'Avenir Next', Avenir, 'Segoe UI', sans-serif;
|
|
11
|
+
--mono: 'SFMono-Regular', Consolas, 'Liberation Mono', monospace;
|
|
12
|
+
color: var(--ink);
|
|
13
|
+
background: var(--paper);
|
|
14
|
+
font: 17px/1.7 var(--font);
|
|
15
|
+
scroll-padding-top: 110px;
|
|
16
|
+
}
|
|
17
|
+
* { box-sizing: border-box; }
|
|
18
|
+
body { margin: 0; }
|
|
19
|
+
a { color: var(--blue); text-decoration-thickness: 1px; text-underline-offset: .2em; }
|
|
20
|
+
a:hover { text-decoration-thickness: 2px; }
|
|
21
|
+
button, input, select { font: inherit; }
|
|
22
|
+
button, select { cursor: pointer; }
|
|
23
|
+
button { color: var(--ink); }
|
|
24
|
+
button:disabled { cursor: default; }
|
|
25
|
+
:focus-visible { outline: 3px solid var(--blue); outline-offset: 4px; border-radius: 2px; }
|
|
26
|
+
.sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0; }
|
|
27
|
+
.skip-link { position: fixed; top: -100px; left: 16px; z-index: 100; padding: 12px 20px; background: white; border: 2px solid var(--blue); }
|
|
28
|
+
.skip-link:focus { top: 12px; }
|
|
29
|
+
.site-header { height: 82px; padding: 0 38px; display: flex; align-items: center; justify-content: space-between; gap: 30px; background: var(--paper); border-bottom: 1px solid var(--rule); position: sticky; top: 0; z-index: 10; }
|
|
30
|
+
.brand { display: inline-flex; align-items: center; gap: 13px; color: var(--ink); text-decoration: none; font-size: 27px; font-weight: 750; letter-spacing: -.05em; }
|
|
31
|
+
.brand img { width: 30px; height: 30px; border-radius: 7px; }
|
|
32
|
+
.brand-guide { font-size: 15px; font-weight: 450; color: var(--secondary); letter-spacing: 0; margin-left: 12px; padding-left: 20px; border-left: 1px solid var(--rule); }
|
|
33
|
+
.header-tools { display: flex; align-items: center; gap: 14px; }
|
|
34
|
+
.repo-link { display: inline-flex; align-items: center; gap: 7px; padding: 8px 12px; border: 1px solid var(--rule); border-radius: 6px; color: var(--ink); text-decoration: none; font-size: 14px; font-weight: 500; white-space: nowrap; }
|
|
35
|
+
.repo-link:hover { background: var(--mist); }
|
|
36
|
+
.repo-link img { width: 20px; height: 20px; }
|
|
37
|
+
@media (max-width: 640px) { .repo-link { font-size: 0; gap: 0; padding: 8px; } }
|
|
38
|
+
.search-box { width: min(340px, 45vw); position: relative; }
|
|
39
|
+
.search-box input { appearance: none; width: 100%; padding: 9px 38px 9px 15px; color: var(--ink); background: var(--mist); border: 1px solid transparent; border-radius: 6px; font-size: 14px; }
|
|
40
|
+
.search-box input::placeholder { color: var(--secondary); }
|
|
41
|
+
.search-box kbd { position: absolute; right: 14px; top: 9px; font: 14px/24px var(--mono); color: var(--secondary); pointer-events: none; }
|
|
42
|
+
#search-results { position: absolute; right: 0; top: calc(100% + 10px); width: min(530px, 90vw); max-height: 65vh; overflow: auto; padding: 8px; background: white; border: 1px solid var(--rule); border-radius: 8px; box-shadow: 0 12px 35px #20314820; }
|
|
43
|
+
#search-results[hidden] { display: none; }
|
|
44
|
+
#search-results a { display: block; padding: 12px; text-decoration: none; border-radius: 4px; }
|
|
45
|
+
#search-results a:hover, #search-results a:focus-visible { background: var(--blue-soft); }
|
|
46
|
+
#search-results strong { display: block; font-size: 15px; }
|
|
47
|
+
#search-results span, #search-results p { font-size: 13px; line-height: 1.5; color: var(--secondary); }
|
|
48
|
+
#search-results p { margin: 12px; }
|
|
49
|
+
.site-layout { display: grid; grid-template-columns: 256px minmax(0, 820px) 200px; max-width: 1430px; padding: 0 38px; margin: auto; column-gap: 46px; }
|
|
50
|
+
.nav-shell { position: sticky; top: 82px; height: calc(100dvh - 82px); overflow: auto; padding: 32px 10px 28px 0; scrollbar-width: thin; }
|
|
51
|
+
.nav-shell > summary { display: none; }
|
|
52
|
+
.nav-group { margin: 0 0 23px; }
|
|
53
|
+
.nav-group > p, .advanced-nav > summary { margin: 0 0 8px; color: var(--ink); font-size: 13px; font-weight: 700; }
|
|
54
|
+
.nav-group a, .advanced-nav a { display: block; padding: 6px 12px; margin: 2px 0; border-radius: 4px; font-size: 13px; line-height: 1.55; text-decoration: none; color: var(--secondary); }
|
|
55
|
+
.nav-group a:hover, .advanced-nav a:hover { background: var(--mist); color: var(--ink); }
|
|
56
|
+
.nav-shell a[aria-current] { background: var(--blue-soft); color: var(--blue); font-weight: 650; }
|
|
57
|
+
.advanced-nav { padding-top: 10px; border-top: 1px solid var(--rule); }
|
|
58
|
+
.advanced-nav > summary { padding: 6px 0; cursor: pointer; }
|
|
59
|
+
.nav-help { display: block; margin-top: 27px; padding-top: 18px; border-top: 1px solid var(--rule); color: var(--secondary); text-decoration: none; font-size: 11px; }
|
|
60
|
+
.nav-help code { display: block; margin-top: 3px; color: var(--ink); font-size: 12px; }
|
|
61
|
+
main { min-width: 0; padding: 49px 0 20px; }
|
|
62
|
+
article { max-width: 76ch; }
|
|
63
|
+
article img { display: block; max-width: 100%; height: auto; border: 1px solid var(--rule); border-radius: 6px; }
|
|
64
|
+
article a:has(> img) { display: block; margin: 16px 0 8px; }
|
|
65
|
+
.breadcrumb { color: var(--secondary); font-size: 13px; margin: 0 0 12px; }
|
|
66
|
+
h1, h2, h3 { line-height: 1.2; letter-spacing: -.035em; font-weight: 700; color: var(--ink); }
|
|
67
|
+
h1 { font-size: clamp(32px, 3vw, 46px); margin: 0 0 24px; max-width: 23ch; }
|
|
68
|
+
.welcome h1 { font-size: clamp(40px, 4.5vw, 63px); line-height: 1.08; max-width: 14ch; letter-spacing: -.055em; }
|
|
69
|
+
.welcome article > p:first-of-type { font-size: 20px; line-height: 1.55; color: var(--secondary); max-width: 53ch; }
|
|
70
|
+
.welcome article > p:nth-of-type(3) > a { display: inline-block; background: var(--blue); color: white; text-decoration: none; padding: 10px 19px; border-radius: 5px; font-size: 15px; font-weight: 600; }
|
|
71
|
+
h2 { font-size: 28px; margin: 52px 0 17px; }
|
|
72
|
+
h3 { font-size: 21px; margin: 32px 0 12px; }
|
|
73
|
+
p { margin: 0 0 20px; }
|
|
74
|
+
li { padding-left: 3px; margin-bottom: 9px; }
|
|
75
|
+
ul, ol { padding-left: 25px; margin: 20px 0; }
|
|
76
|
+
strong { font-weight: 650; }
|
|
77
|
+
.heading-link { margin-left: 10px; font-size: .7em; font-weight: 400; opacity: 0; text-decoration: none; }
|
|
78
|
+
h2:hover .heading-link, h3:hover .heading-link, .heading-link:focus-visible { opacity: 1; }
|
|
79
|
+
code, kbd { font-family: var(--mono); }
|
|
80
|
+
:not(pre) > code { font-size: .84em; background: var(--mist); padding: 2px 5px; border-radius: 3px; overflow-wrap: anywhere; }
|
|
81
|
+
.code-example { margin: 25px 0; border: 1px solid var(--rule); border-radius: 7px; overflow: hidden; background: var(--code); }
|
|
82
|
+
.code-example figcaption { display: flex; justify-content: space-between; align-items: center; padding: 8px 16px; border-bottom: 1px solid var(--rule); font-size: 12px; color: var(--secondary); }
|
|
83
|
+
.code-example button { background: none; border: 0; font-size: 12px; padding: 4px 9px; border-radius: 3px; }
|
|
84
|
+
.code-example button:hover { background: var(--blue-soft); }
|
|
85
|
+
pre { margin: 0; padding: 20px; overflow-x: auto; font: 13px/1.8 var(--mono); tab-size: 2; }
|
|
86
|
+
pre code { white-space: pre; }
|
|
87
|
+
.token.comment, .token.prolog { color: #637084; }
|
|
88
|
+
.token.punctuation { color: #536279; }
|
|
89
|
+
.token.keyword, .token.tag, .token.important { color: #244ec7; }
|
|
90
|
+
.token.string, .token.attr-value { color: #17625c; }
|
|
91
|
+
.token.function, .token.attr-name, .token.key { color: #753fa1; }
|
|
92
|
+
.token.number, .token.boolean { color: #a33a51; }
|
|
93
|
+
.token.title, .token.bold { font-weight: 700; }
|
|
94
|
+
.table-scroll { overflow-x: auto; margin: 26px 0; border: 1px solid var(--rule); border-radius: 5px; }
|
|
95
|
+
table { border-collapse: collapse; width: 100%; font-size: 14px; text-align: left; }
|
|
96
|
+
th { background: var(--mist); font-weight: 650; }
|
|
97
|
+
th, td { padding: 12px 16px; border-bottom: 1px solid var(--rule); vertical-align: top; }
|
|
98
|
+
tr:last-child td { border-bottom: 0; }
|
|
99
|
+
td { min-width: 140px; }
|
|
100
|
+
blockquote, .advanced-notice { border-left: 3px solid var(--blue); padding: 12px 19px; margin: 24px 0; background: var(--blue-soft); font-size: 15px; }
|
|
101
|
+
blockquote p:last-child { margin: 0; }
|
|
102
|
+
.slide-example { margin: 32px 0; border-radius: 8px; overflow: hidden; background: var(--blue-soft); border: 1px solid #c6d3f2; }
|
|
103
|
+
.slide-example > figcaption { display: flex; justify-content: space-between; gap: 20px; padding: 13px 18px; font-size: 12px; color: #344d80; }
|
|
104
|
+
.slide-example iframe { display: block; width: calc(100% - 36px); aspect-ratio: 16 / 9; margin: 0 18px; border: 0; background: white; box-shadow: 0 3px 9px #20314815; }
|
|
105
|
+
.preview-controls { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 13px 18px; font-size: 12px; }
|
|
106
|
+
.preview-controls > div { display: flex; align-items: center; gap: 7px; }
|
|
107
|
+
.preview-controls button, .preview-controls select { padding: 5px 9px; border: 1px solid #bdcce9; background: white; border-radius: 4px; font-size: 12px; }
|
|
108
|
+
.preview-controls button:hover { border-color: var(--blue); color: var(--blue); }
|
|
109
|
+
.preview-controls output { margin-left: 5px; color: #344d80; }
|
|
110
|
+
.preview-controls label { display: flex; align-items: center; gap: 7px; color: #344d80; }
|
|
111
|
+
.page-outline { position: sticky; top: 120px; align-self: start; font-size: 12px; max-height: calc(100dvh - 150px); overflow: auto; padding-left: 18px; border-left: 1px solid var(--rule); margin-top: 53px; }
|
|
112
|
+
.page-outline > p { font-weight: 650; margin: 0 0 12px; }
|
|
113
|
+
.page-outline a { display: block; margin-bottom: 10px; color: var(--secondary); text-decoration: none; line-height: 1.5; }
|
|
114
|
+
.page-outline a:hover { color: var(--blue); }
|
|
115
|
+
.page-navigation { display: flex; justify-content: space-between; gap: 24px; margin: 64px 0 30px; padding-top: 25px; border-top: 1px solid var(--rule); font-size: 15px; }
|
|
116
|
+
.page-navigation a { flex: 1; text-decoration: none; }
|
|
117
|
+
.page-navigation a:last-child { text-align: right; }
|
|
118
|
+
.page-navigation small { display: block; font-size: 11px; color: var(--secondary); margin-bottom: 6px; }
|
|
119
|
+
.article-footer { font-size: 11px; color: var(--secondary); display: flex; justify-content: space-between; gap: 20px; padding-bottom: 24px; }
|
|
120
|
+
@media (min-width: 961px) { .nav-shell:not([open]) > nav { display: block; } }
|
|
121
|
+
@media (max-width: 1250px) { .site-layout { grid-template-columns: 224px minmax(0, 1fr); max-width: 1110px; gap: 38px; } .page-outline { display: none; } }
|
|
122
|
+
@media (max-width: 960px) {
|
|
123
|
+
.site-header { padding: 0 22px; height: 70px; }
|
|
124
|
+
.site-layout { display: block; padding: 0 24px; max-width: 780px; }
|
|
125
|
+
.nav-shell { position: static; height: auto; overflow: visible; padding: 0; border-bottom: 1px solid var(--rule); }
|
|
126
|
+
.nav-shell > summary { display: list-item; font-size: 14px; padding: 15px 0; cursor: pointer; }
|
|
127
|
+
.nav-shell nav { padding: 10px 0 20px; }
|
|
128
|
+
main { padding-top: 30px; }
|
|
129
|
+
h1 { max-width: none; }
|
|
130
|
+
}
|
|
131
|
+
@media (max-width: 520px) {
|
|
132
|
+
:root { font-size: 16px; }
|
|
133
|
+
.site-header { padding: 0 16px; gap: 16px; }
|
|
134
|
+
.brand { gap: 8px; font-size: 24px; }
|
|
135
|
+
.brand img, .brand svg { width: 28px; }
|
|
136
|
+
.brand-guide { display: none; }
|
|
137
|
+
.search-box { width: 49vw; }
|
|
138
|
+
.search-box input { font-size: 12px; }
|
|
139
|
+
.site-layout { padding: 0 18px; }
|
|
140
|
+
h2 { font-size: 25px; }
|
|
141
|
+
pre { padding: 15px; font-size: 12px; }
|
|
142
|
+
.preview-controls { flex-wrap: wrap; }
|
|
143
|
+
.slide-example > figcaption { flex-wrap: wrap; gap: 3px; }
|
|
144
|
+
.article-footer span { display: none; }
|
|
145
|
+
}
|
|
146
|
+
@media (prefers-reduced-motion: reduce) { *, *::before, *::after { scroll-behavior: auto !important; } }
|
|
147
|
+
@media print { .site-header, .nav-shell, .page-outline, .page-navigation, .code-example button, .preview-controls { display: none; } .site-layout { display: block; padding: 0; } main { padding: 0; } pre { white-space: pre-wrap; } pre code { white-space: pre-wrap; } a { color: inherit; } }
|
|
148
|
+
|
|
149
|
+
.nav-project { margin-top: 24px; padding-top: 20px; border-top: 1px solid var(--rule); }
|
|
150
|
+
.article-footer { display: flex; justify-content: space-between; gap: 16px; flex-wrap: wrap; }
|
|
151
|
+
.footer-links { display: flex; gap: 14px; }
|
|
152
|
+
.footer-links a { color: var(--secondary); text-decoration: none; }
|
|
153
|
+
.footer-links a:hover { color: var(--blue); }
|
|
154
|
+
|
|
155
|
+
/* Tabs for the theme contact sheets */
|
|
156
|
+
.theme-sheets { margin: 20px 0 32px; }
|
|
157
|
+
.theme-tabs { display: flex; flex-wrap: wrap; gap: 4px; border-bottom: 1px solid var(--rule); margin-bottom: 16px; }
|
|
158
|
+
.theme-tabs [role="tab"] { font: inherit; font-size: 15px; color: var(--secondary); background: none; border: 0; border-bottom: 3px solid transparent; padding: 8px 14px; margin-bottom: -1px; cursor: pointer; }
|
|
159
|
+
.theme-tabs [role="tab"]:hover { color: var(--ink); }
|
|
160
|
+
.theme-tabs [role="tab"][aria-selected="true"] { color: var(--blue); border-bottom-color: var(--blue); font-weight: 600; }
|
|
161
|
+
.theme-tabs [role="tab"]:focus-visible { outline: 2px solid var(--blue); outline-offset: 2px; border-radius: 4px; }
|
|
162
|
+
.theme-sheet a { margin: 0; }
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
const search = document.querySelector('#search')
|
|
2
|
+
const results = document.querySelector('#search-results')
|
|
3
|
+
const status = document.querySelector('#search-status')
|
|
4
|
+
let searchIndex
|
|
5
|
+
let searchVersion = 0
|
|
6
|
+
|
|
7
|
+
async function runSearch() {
|
|
8
|
+
const version = ++searchVersion
|
|
9
|
+
const query = search.value.trim().toLowerCase()
|
|
10
|
+
results.replaceChildren()
|
|
11
|
+
results.hidden = !query
|
|
12
|
+
search.setAttribute('aria-expanded', String(!!query))
|
|
13
|
+
if (!query) { status.textContent = ''; return }
|
|
14
|
+
try {
|
|
15
|
+
searchIndex ??= fetch('search.json').then(response => { if (!response.ok) throw new Error(); return response.json() })
|
|
16
|
+
const index = await searchIndex
|
|
17
|
+
if (version !== searchVersion) return
|
|
18
|
+
const words = query.split(/\s+/)
|
|
19
|
+
const matches = index.map(page => {
|
|
20
|
+
const title = page.title.toLowerCase(), description = page.description.toLowerCase(), text = page.text.toLowerCase()
|
|
21
|
+
return { ...page, score: words.every(word => `${title} ${description} ${text}`.includes(word)) ? words.reduce((score, word) => score + (title.includes(word) ? 10 : description.includes(word) ? 4 : 1), 0) : 0 }
|
|
22
|
+
}).filter(page => page.score > 0).sort((a, b) => b.score - a.score).slice(0, 8)
|
|
23
|
+
status.textContent = `${matches.length} guides found.`
|
|
24
|
+
if (!matches.length) {
|
|
25
|
+
const message = document.createElement('p')
|
|
26
|
+
message.textContent = 'No guides found. Try “picture”, “notes”, or “share”.'
|
|
27
|
+
results.append(message)
|
|
28
|
+
}
|
|
29
|
+
for (const page of matches) {
|
|
30
|
+
const link = document.createElement('a'), title = document.createElement('strong'), description = document.createElement('span')
|
|
31
|
+
link.href = page.href
|
|
32
|
+
title.textContent = page.title
|
|
33
|
+
description.textContent = page.description
|
|
34
|
+
link.append(title, description)
|
|
35
|
+
results.append(link)
|
|
36
|
+
}
|
|
37
|
+
} catch {
|
|
38
|
+
searchIndex = undefined
|
|
39
|
+
const message = document.createElement('p')
|
|
40
|
+
message.textContent = 'Search could not load. Use the guide menu, or open these pages with mdeck docs.'
|
|
41
|
+
results.append(message)
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
search.addEventListener('input', runSearch)
|
|
45
|
+
search.addEventListener('focus', () => { if (search.value.trim()) runSearch() })
|
|
46
|
+
search.addEventListener('keydown', event => {
|
|
47
|
+
if (event.key === 'ArrowDown') { event.preventDefault(); results.querySelector('a')?.focus() }
|
|
48
|
+
if (event.key === 'Enter' && results.querySelector('a')) results.querySelector('a').click()
|
|
49
|
+
})
|
|
50
|
+
document.addEventListener('keydown', event => {
|
|
51
|
+
if (event.key === '/' && !event.target.closest('input, textarea, select, [contenteditable]') && !event.metaKey && !event.ctrlKey) { event.preventDefault(); search.focus() }
|
|
52
|
+
if (event.key === 'Escape') { results.hidden = true; search.setAttribute('aria-expanded', 'false'); if (results.contains(document.activeElement)) search.focus() }
|
|
53
|
+
})
|
|
54
|
+
document.addEventListener('click', event => { if (!event.target.closest('.search-box')) { results.hidden = true; search.setAttribute('aria-expanded', 'false') } })
|
|
55
|
+
|
|
56
|
+
const navigation = document.querySelector('.nav-shell')
|
|
57
|
+
const mobile = matchMedia('(max-width: 960px)')
|
|
58
|
+
function adaptNavigation() { navigation.open = !mobile.matches }
|
|
59
|
+
adaptNavigation()
|
|
60
|
+
mobile.addEventListener('change', adaptNavigation)
|
|
61
|
+
|
|
62
|
+
for (const button of document.querySelectorAll('[data-copy]')) {
|
|
63
|
+
button.addEventListener('click', async () => {
|
|
64
|
+
const code = button.closest('figure').querySelector('code')
|
|
65
|
+
try {
|
|
66
|
+
await navigator.clipboard.writeText(code.textContent)
|
|
67
|
+
button.textContent = 'Copied'
|
|
68
|
+
} catch {
|
|
69
|
+
const range = document.createRange()
|
|
70
|
+
range.selectNodeContents(code)
|
|
71
|
+
const selection = window.getSelection()
|
|
72
|
+
selection.removeAllRanges()
|
|
73
|
+
selection.addRange(range)
|
|
74
|
+
button.textContent = 'Selected — copy now'
|
|
75
|
+
}
|
|
76
|
+
setTimeout(() => { button.textContent = 'Copy' }, 2500)
|
|
77
|
+
})
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
for (const example of document.querySelectorAll('[data-preview]')) {
|
|
81
|
+
const frame = example.querySelector('iframe')
|
|
82
|
+
const output = example.querySelector('output')
|
|
83
|
+
let index = 0
|
|
84
|
+
for (const button of example.querySelectorAll('[data-control]')) {
|
|
85
|
+
button.addEventListener('click', () => frame.contentWindow.postMessage({ deckControl: { command: button.dataset.control } }, location.origin))
|
|
86
|
+
}
|
|
87
|
+
example.querySelector('select').addEventListener('change', event => {
|
|
88
|
+
const url = new URL(frame.src)
|
|
89
|
+
url.searchParams.set('theme', event.target.value)
|
|
90
|
+
url.hash = String(index + 1)
|
|
91
|
+
frame.src = url.toString()
|
|
92
|
+
})
|
|
93
|
+
window.addEventListener('message', event => {
|
|
94
|
+
if (event.source !== frame.contentWindow || event.origin !== location.origin || !event.data?.deckStateChanged) return
|
|
95
|
+
index = event.data.deckStateChanged.index
|
|
96
|
+
output.textContent = `Slide ${index + 1}`
|
|
97
|
+
})
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Tabs: one panel at a time; without the script every panel shows.
|
|
101
|
+
for (const group of document.querySelectorAll('[data-tabs]')) {
|
|
102
|
+
const tabs = [...group.querySelectorAll('[role="tab"]')]
|
|
103
|
+
const select = tab => {
|
|
104
|
+
for (const other of tabs) {
|
|
105
|
+
const selected = other === tab
|
|
106
|
+
other.setAttribute('aria-selected', String(selected))
|
|
107
|
+
other.tabIndex = selected ? 0 : -1
|
|
108
|
+
document.getElementById(other.getAttribute('aria-controls')).hidden = !selected
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
tabs.forEach(tab => tab.addEventListener('click', () => select(tab)))
|
|
112
|
+
group.querySelector('[role="tablist"]').addEventListener('keydown', event => {
|
|
113
|
+
const step = { ArrowRight: 1, ArrowLeft: -1 }[event.key]
|
|
114
|
+
const index = tabs.indexOf(document.activeElement)
|
|
115
|
+
if (index < 0) return
|
|
116
|
+
const next = step ? tabs[(index + step + tabs.length) % tabs.length] : event.key === 'Home' ? tabs[0] : event.key === 'End' ? tabs.at(-1) : null
|
|
117
|
+
if (!next) return
|
|
118
|
+
event.preventDefault()
|
|
119
|
+
select(next)
|
|
120
|
+
next.focus()
|
|
121
|
+
})
|
|
122
|
+
select(tabs.find(tab => tab.getAttribute('aria-selected') === 'true') ?? tabs[0])
|
|
123
|
+
}
|