@codename/deck 0.0.0-stage → 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/docs/format.md ADDED
@@ -0,0 +1,70 @@
1
+ # The deck file
2
+
3
+ ## The page
4
+
5
+ A deck is one HTML file, named `<name>.deck.html`. Any browser opens it; the renderer draws and presents it. A host reads its Markdown as text and never runs the page.
6
+
7
+ ````html
8
+ <!doctype html>
9
+ <html lang="en" dir="ltr">
10
+ <meta charset="utf-8">
11
+ <meta name="viewport" content="width=device-width, initial-scale=1">
12
+ <meta name="generator" content="deck 0.1.0">
13
+ <title>A second oven</title>
14
+ <style>#deck{display:block;white-space:pre-wrap;overflow-wrap:anywhere;font:1rem/1.6 ui-monospace,Menlo,Consolas,monospace;max-width:72ch;margin:2rem auto;padding:0 1rem}</style>
15
+ <body>
16
+ <script type="text/markdown" id="deck">
17
+ …the Markdown…
18
+ </script>
19
+ <script src="https://cdn.jsdelivr.net/npm/@codename/deck@0.1.0/dist/deck.js"></script>
20
+ </html>
21
+ ````
22
+
23
+ - **The Markdown** sits whole in one element, `<script type="text/markdown" id="deck">`, which no browser runs. Nothing inside a script element is decoded, so `R&D` stays `R&D`.
24
+ - **One escape.** `<script` and `</script`, in any case, gain a backslash after their `<`, and one more for each already there; reading takes one away. The HTML parser would otherwise end the element at `</script`, and a `<script` after `<!--` would keep it from ending. `readDeckFile(writeDeckFile(md))` gives back `md` exactly.
25
+ - **In the body.** Without the explicit `<body>`, the parser would put the element in the head, which is never shown.
26
+ - **With scripts off,** the style above shows the Markdown as plain text in the body.
27
+ - **The renderer** is linked by default, to this version of the package on jsDelivr (`rendererUrl()`). A host may link another address, or hold the renderer whole (`{ code }`, about 113 KB), its `<!--`, `<script` and `</script` written with `\x3C`. A `data-base` attribute on the renderer's script says where the libraries a slide needs come from: a CDN's npm mirror (the default) or a `node_modules/` folder.
28
+ - **Pictures** the deck names by a host's own address are held inside the file as `data:` addresses when it leaves the host (`inlinePictures`). Pictures on the web stay links unless asked for. A page opened from disk can't read back a picture beside it, so a file meant to travel holds its pictures.
29
+ - **Title, language and direction** are in the head for whatever reads the page before the renderer does.
30
+
31
+ ## The Markdown
32
+
33
+ Slidev's form, as `@slidev/parser` splits it, and the parts a deck for knowledge workers needs. Every deck written this way opens in Slidev too; ECharts blocks and kits need an addon there.
34
+
35
+ ### Written and read
36
+
37
+ - **Slides** between lines holding only `---`. A `---` followed by a `key:` line opens that slide's settings, YAML up to the next `---` line; a ` ```yaml ` block at a slide's top does the same.
38
+ - **The first slide's settings** are the deck's too (its headmatter): `title`, `lang`, `dir`, `colorSchema` (`auto`, `light`, `dark`), `transition`, `theme`, `themeConfig`, `kit`.
39
+ - **A slide's settings:** `layout`, `transition` (`slide-left`, `slide-right`, `slide-up`, `fade`, `view-transition`; `a | b` for forward and back), `class` (`invert`, `text-center`), `image`, `background`, `alt`, `title`, `paginate`.
40
+ - **Slots:** `::name::` alone on a line starts the slot of that name; what comes before is `default`.
41
+ - **Notes:** the last HTML comment at a slide's end.
42
+ - **Steps:** `<v-clicks>` and `</v-clicks>` around a list, each on its own line with empty lines around; `<v-click>` around a block.
43
+ - **Code** in fences, with lines marked as Slidev marks them: ` ```ts {2,4-5} `.
44
+ - **Charts:** ` ```echarts ` holding an ECharts option as JSON, data only.
45
+ - **Diagrams:** ` ```mermaid ` holding Mermaid's language.
46
+ - **Markdown** as CommonMark and GFM have it: headings, lists, task lists, tables, quotes, emphasis, links, pictures.
47
+
48
+ ### Added by deck
49
+
50
+ - ` ```echarts `, `lang` and `dir`.
51
+ - `kit`: tokens and layouts as data ([kits.md](kits.md)).
52
+ - A layout chosen from what a slide holds when it names none.
53
+ - A Morph between two slides that share a heading, a picture or a list item, with `transition: view-transition`.
54
+
55
+ ### Also read, as brains write it
56
+
57
+ Brains blend dialects. These are read, never rewritten:
58
+
59
+ - Pandoc's `::: notes`, `:::: columns` holding `::: column`s (slots `left`, `right`, or `left`, `middle`, `right`; what follows them `bottom`), `::: incremental`.
60
+ - remark's `???` and reveal.js's `Note:` (what follows is the notes), reveal.js's `<aside class="notes">`.
61
+ - Slides at headings of the first two levels when a deck has no `---` at all, as Pandoc splits a document.
62
+ - Steps written as `<li v-click>`.
63
+ - A `---` followed by words, which Slidev would take for settings, as a new slide.
64
+ - Names brains give layouts: `title`, `two-columns`, `comparison`, `divider`, `thanks`…
65
+
66
+ `deckIn(answer)` finds a deck in a brain's answer and mends what brains get wrong most: a deck fenced as ` ```markdown ` or with nothing, a chart's fence inside three backticks, settings after an empty line, steps without their empty lines, a chart fenced as ` ```json `, words after a slot's marker.
67
+
68
+ ### Left out
69
+
70
+ Vue components and expressions, UnoCSS classes, `<style>`, imported slides, Monaco, drawing. Other HTML gives up its words alone.
package/docs/host.md ADDED
@@ -0,0 +1,49 @@
1
+ # Drawing decks in a host
2
+
3
+ A host is a page that draws decks itself: Jean, or any other. deck builds every element with the DOM's own calls, sets custom properties through the CSSOM rather than style attributes, and needs no Trusted Types policy, so it runs under a policy with `style-src 'self'` and `trusted-types 'none'`. Load `@codename/deck` (with your own `marked` through an import map) or `@codename/deck/bundle`, and `@codename/deck/css` as a stylesheet.
4
+
5
+ ## createDeck(element, options)
6
+
7
+ ```js
8
+ import { createDeck } from '@codename/deck'
9
+
10
+ const view = createDeck(document.querySelector('#deck'), {
11
+ source, // the Markdown
12
+ lang: 'fr', dir: 'ltr', // else the deck's own, else the page's
13
+ kit, // the host's kit: an organisation's, a person's, a project's, merged
14
+ scheme: 'auto', // 'light', 'dark' or 'auto', over the deck's colorSchema
15
+ blocks: { echarts, mermaid }, // drawers for fenced blocks: (box, source) => void | Promise<void>
16
+ resolve: (src) => url, // a picture's address as the page can load it, or null
17
+ strings, // the bar's words, else deck's own (en, fr, ar, ja)
18
+ headings: 'aria', // 'real' (h1–h6) or 'aria' (role=heading), for a deck inside a page
19
+ editable: true,
20
+ onChange: (source) => {}, // after each change, by the Markdown or on a slide
21
+ onSave: (source) => {}, // ⌘S or Ctrl+S
22
+ onExport: (view) => {}, // the bar's Save as PowerPoint
23
+ })
24
+ ```
25
+
26
+ The view: `source`, `deck` (parsed), `at`, `scheme`, `kit`, `kitProblems`, `lang`, `dir`, `slides()`, `setSource(text)`, `go(index, step)`, `toggle('is-editing' | 'is-presenting' | 'is-overview')`, `present()`, `setScheme(scheme)`, `fitAll()`, `destroy()`.
27
+
28
+ Events on the element: `deck-scheme` (`detail: { scheme, colours, tokens }`) when the look is set, `deck-move` (`detail: { at, step }`) on each move.
29
+
30
+ Every block that can be changed by hand carries `data-edit="from-to"`, the offsets of its words in the source, so a host can point at a part of a slide and change it in the Markdown.
31
+
32
+ ## Drawers
33
+
34
+ `defaultBlocks({ base, root, versions })` gives the drawers a file uses alone: ECharts as SVG in the deck's colours and type, its category axis mirrored right to left; Pele for Mermaid's language, its SVG shown as a picture coloured by Pele's variables, its words the picture's text. Each library loads the first time a slide needs it, from `base` (jsDelivr's npm mirror, or a `node_modules/` folder, `libraryAt()`). A host with a strict policy gives its own: Pele's SVG holds style attributes, so it belongs in an image or a worker's output, not in the page's DOM.
35
+
36
+ ## PowerPoint
37
+
38
+ ```js
39
+ const { blob, report } = await toPptx(view, { PptxGenJS, JSZip })
40
+ ```
41
+
42
+ PptxGenJS 4 (its bundle carries JSZip) is the host's to load, on demand. The file is made from the deck as drawn: a slide layout per deck layout, named by its label in the deck's language, with a placeholder for its title and each slot; lists as lists with their levels; tables; bars, lines, areas, pies, rings, points and radars as native charts with their numbers, other charts and diagrams as pictures; notes; transitions; the kit's colours as the theme. `report` counts slides, charts, tables, pictures and placeholders.
43
+
44
+ ## Files
45
+
46
+ - `readDeckFile(html)`: the Markdown, or `null`; never parses the page.
47
+ - `writeDeckFile(markdown, { title, lang, dir, renderer, base })`: the page. `renderer` is `{ src }` (default: `rendererUrl()`) or `{ code }`.
48
+ - `inlinePictures(source, read, { web, largest })`: the deck with its pictures inside it. `read(place)` gives `{ type, bytes }` for a picture the host holds. Hosts call it on a deck leaving them (a download, a share).
49
+ - `deckIn(answer)`: a brain's deck from its answer, mended, with what was mended.
package/docs/kits.md ADDED
@@ -0,0 +1,76 @@
1
+ # Kits
2
+
3
+ A kit is a design system and the layouts that go with it, as data. deck checks every layout against the grammar below and drops what fails, with a reason, so no kit can do more than lay out a slide: no CSS, no addresses, no code. A person, an organisation or a brain can make one safely.
4
+
5
+ ```yaml
6
+ kit:
7
+ name: Martin's bakery
8
+ tokens: { primary: '#c2410c', voice: elegant, radius: 4 }
9
+ layouts:
10
+ three-numbers:
11
+ label: { en: Three numbers, fr: Trois chiffres }
12
+ intent: Prove the title with three numbers.
13
+ slots: [default, one, two, three]
14
+ areas: default default default | one two three
15
+ rows: auto 1fr
16
+ align: center
17
+ pptx: Comparison
18
+ ```
19
+
20
+ ## Where kits come from
21
+
22
+ Kits stack, the later over the earlier: deck's own (`STANDARD`), then what the host gives (`options.kit`: an organisation's, a person's, a project's, merged by the host), then the file's own `kit` in its headmatter. A host writes the kit a deck was drawn with into its file, so it looks the same wherever it goes.
23
+
24
+ ## Tokens
25
+
26
+ Flat:
27
+
28
+ | Token | What it is |
29
+ |---|---|
30
+ | `paper`, `ink`, `primary` | Colours, as `#rgb` or `#rrggbb` |
31
+ | `muted`, `soft` | Optional: secondary words, a pale fill |
32
+ | `voice` | `calm`, `elegant`, `bold`, `friendly`: the device's own faces for headings and text |
33
+ | `fonts` | Optional: `{ heading: [...], body: [...] }`, face stacks, over the voice |
34
+ | `radius` | Corners, in pixels |
35
+ | `dark` | Optional: `{ paper, ink, primary, muted, soft }` for the dark scheme, else worked out |
36
+
37
+ Or in the W3C's Design Tokens format (DTCG 2025.10): `color.light` and `color.dark` groups of `bg`, `surface`, `fg`, `muted`, `accent` (or flat colours), `font.sans`, `font.heading`, `font.body`; each `$value` a hex string or a colour object with its `hex`. `tokensFrom()` reads them.
38
+
39
+ Whatever the tokens, light and dark are each checked: words at 4.5:1 on their paper, a chart's marks at 3:1, each colour deepened or lightened at its hue until it reads.
40
+
41
+ ## Layouts
42
+
43
+ | Field | Grammar | Default |
44
+ |---|---|---|
45
+ | `slots` | Names (`a-z`, `0-9`, `-`), or a map of names to budgets; `title` there means the heading the default slot opens with | `[default]` |
46
+ | `areas` | Rows of slot names or `.`, split by `\|` or new lines, or a list of rows; every slot a rectangle | the slots in a row |
47
+ | `columns`, `rows` | One track a column or row: `auto`, `min-content`, `max-content`, `1fr`…`99fr`, `10%`… | `1fr` each |
48
+ | `align` | `start`, `center`, `end`: across | `start` |
49
+ | `justify` | `start`, `center`, `end`: down | `start` |
50
+ | `paper` | `paper`, `fill` (the primary), `ink`, `soft` | `paper` |
51
+ | `title` | `normal`, `large`, `huge` | `normal` |
52
+ | `pptx` | Title Slide, Title and Content, Section Header, Two Content, Comparison, Title Only, Blank, Content with Caption, Picture with Caption | by its slots |
53
+ | `numbered` | `false` for no slide number | `true` |
54
+ | `label` | Words, or words by language: `{ en: …, fr: … }`; PowerPoint's layout is named so | the name |
55
+ | `intent`, `use_when`, `avoid_when` | Words for the brain choosing a layout | none |
56
+ | budgets | `required`, `max_words`, `max_chars`, `items: [min, max]`, `item_max_words`, `heading_max_words`, `kind` | none |
57
+
58
+ A catalog's `types` are read as `layouts`. Budgets are kept with each layout for a linter and for the brain; deck 0.1 does not check them yet.
59
+
60
+ ## deck's own layouts
61
+
62
+ | Layout | Slots | In PowerPoint |
63
+ |---|---|---|
64
+ | `cover`, `intro`, `end` | one | Title Slide |
65
+ | `default` | one | Title and Content |
66
+ | `section` | one | Section Header |
67
+ | `two-cols` | `default`, `right` | Two Content |
68
+ | `two-cols-header` | `default` across, `left`, `right`, `bottom` | Comparison |
69
+ | `three-cols` | `default` across, `left`, `middle`, `right` | Comparison |
70
+ | `center`, `statement`, `fact`, `quote` | one | Title Only |
71
+ | `image-left`, `image-right` | one, beside the slide's `image` | Picture with Caption |
72
+ | `image`, `full`, `none` | one | Blank |
73
+
74
+ ## A slide that names none
75
+
76
+ As Pandoc's PowerPoint writer chooses: a slide with `middle` is `three-cols`, with `left` `two-cols-header`, with `right` `two-cols`; the first slide is `cover`; a heading alone is `section`; a number alone as its heading, a line or two under it, is `fact`; a quote alone, its author under it, is `quote`; else `default`. A layout the kit lacks falls back the same way.
package/package.json CHANGED
@@ -1,6 +1,56 @@
1
1
  {
2
2
  "name": "@codename/deck",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.0",
4
+ "description": "Slide decks as one web page: Markdown in Slidev's form inside a .deck.html file any browser presents, a renderer that runs anywhere, kits of layouts and looks, and an editable PowerPoint out",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "exports": {
8
+ ".": "./dist/deck.mjs",
9
+ "./bundle": "./dist/deck.bundle.mjs",
10
+ "./file": "./dist/deck.js",
11
+ "./css": "./dist/deck.css",
12
+ "./src/*": "./src/*"
13
+ },
14
+ "files": [
15
+ "dist",
16
+ "src",
17
+ "docs",
18
+ "LICENSE",
19
+ "README.md"
20
+ ],
21
+ "keywords": [
22
+ "slides",
23
+ "presentation",
24
+ "markdown",
25
+ "slidev",
26
+ "powerpoint",
27
+ "pptx"
28
+ ],
29
+ "scripts": {
30
+ "build": "node build.mjs",
31
+ "examples": "node build.mjs && node examples/make.mjs",
32
+ "test": "node --test test/*.test.js",
33
+ "test:browsers": "npm run examples && node test/browser.mjs && node test/kit-browser.mjs",
34
+ "typecheck": "tsc --noEmit",
35
+ "check": "npm run typecheck && npm test && npm run test:browsers",
36
+ "eval": "node eval/run.mjs",
37
+ "eval:check": "node eval/check.mjs && node eval/dialects/check.mjs && node eval/dialects/draw.mjs",
38
+ "prepare": "git config core.hooksPath .githooks || true",
39
+ "prepublishOnly": "npm run build && npm test"
40
+ },
41
+ "peerDependencies": {
42
+ "marked": "^18.1.1"
43
+ },
44
+ "devDependencies": {
45
+ "echarts": "6.1.0",
46
+ "esbuild": "0.28.2",
47
+ "marked": "^18.1.1",
48
+ "pele": "1.10.0",
49
+ "playwright": "1.64.1",
50
+ "pptxgenjs": "^4.0.0",
51
+ "typescript": "^7.0.2"
52
+ },
53
+ "engines": {
54
+ "node": ">=22"
55
+ }
56
+ }
package/src/blocks.js ADDED
@@ -0,0 +1,99 @@
1
+ // The drawers a deck opened on its own uses for ```echarts and ```mermaid, each library loaded
2
+ // from `base` (a CDN by default) the first time a slide needs it, never before. A chart is data,
3
+ // read with JSON.parse; it takes the deck's colours and type, light or dark, and is drawn as SVG.
4
+ // A diagram is drawn by Pele from Mermaid's language as SVG, then shown as a picture, so nothing it
5
+ // makes runs or joins the page; its words are its picture's text for whoever can't see it. A host (Jean) gives
6
+ // its own drawers instead.
7
+
8
+ /** @typedef {{ base: string, root: HTMLElement, versions?: { echarts?: string, pele?: string } }} Where */
9
+
10
+ /** @type {Map<string, Promise<any>>} */
11
+ const loading = new Map()
12
+
13
+ /** A classic script, loaded once: from a file:// page, modules from elsewhere are not always let in. @param {string} src @param {string} global */
14
+ function script(src, global) {
15
+ if (!loading.has(src)) loading.set(src, new Promise((resolve, reject) => {
16
+ const tag = document.createElement('script')
17
+ tag.src = src
18
+ tag.onload = () => resolve(/** @type {any} */ (globalThis)[global])
19
+ tag.onerror = reject
20
+ document.head.append(tag)
21
+ }))
22
+ return /** @type {Promise<any>} */ (loading.get(src))
23
+ }
24
+
25
+ /**
26
+ * A library's file: from a CDN's npm mirror, at its version (`https://…/npm/name@1.2.3/file`), or from a
27
+ * node_modules folder, which holds one version (`…/node_modules/name/file`).
28
+ * @param {string} base @param {string} name @param {string} version @param {string} file
29
+ */
30
+ export const libraryAt = (base, name, version, file) => (/node_modules\/$/.test(base) ? `${base}${name}/${file}` : `${base}${name}@${version}/${file}`)
31
+
32
+ /** @param {Where} where */
33
+ export function defaultBlocks({ base, root, versions = {} }) {
34
+ const { echarts: chartsAt = '6.1.0', pele: diagramsAt = '1.10.0' } = versions
35
+ /** @type {Set<{ chart: any, option: any, box: HTMLElement }>} */
36
+ const charts = new Set()
37
+ /** @type {any} */
38
+ let colours = null
39
+ root.addEventListener('deck-scheme', (event) => {
40
+ colours = /** @type {CustomEvent} */ (event).detail.colours
41
+ for (const one of charts) one.chart.setOption(themed(one.option, one.box), true)
42
+ })
43
+ // Its type sized with the slide's, its category axis read from the right in a language written so.
44
+ const themed = (/** @type {any} */ option, /** @type {HTMLElement} */ box) => {
45
+ const size = parseFloat(getComputedStyle(box).fontSize) || 16
46
+ const rtl = getComputedStyle(box).direction === 'rtl'
47
+ const small = Math.round(size * 0.6)
48
+ const axis = (/** @type {any} */ given, /** @type {boolean} */ across) => given === undefined ? undefined : [given].flat().map((one) => ({
49
+ axisLabel: { color: colours?.muted, fontSize: small }, axisLine: { lineStyle: { color: colours?.line } }, splitLine: { lineStyle: { color: colours?.line } },
50
+ nameTextStyle: { color: colours?.muted, fontSize: small }, ...(rtl && across && one.type === 'category' ? { inverse: true } : {}), ...(rtl && !across ? { position: 'right' } : {}), ...one,
51
+ }))
52
+ return {
53
+ color: colours?.series, backgroundColor: 'transparent', animation: !matchMedia('(prefers-reduced-motion: reduce)').matches,
54
+ textStyle: { fontFamily: getComputedStyle(root).getPropertyValue('--deck-body') || 'system-ui', color: colours?.ink, fontSize: small },
55
+ ...option,
56
+ title: option.title ? { ...option.title, textStyle: { color: colours?.ink, fontSize: Math.round(size * 0.8), ...option.title?.textStyle } } : undefined,
57
+ legend: option.legend ? { itemWidth: small * 1.6, itemHeight: small, ...option.legend, textStyle: { color: colours?.ink, fontSize: small, ...option.legend?.textStyle } } : undefined,
58
+ grid: { containLabel: true, left: small, right: small, top: option.title ? size * 1.8 : small, bottom: option.legend ? size * 1.6 : small, ...option.grid },
59
+ xAxis: axis(option.xAxis, true), yAxis: axis(option.yAxis, false), toolbox: undefined,
60
+ series: [option.series ?? []].flat().map((one) => (one.type === 'pie' ? { label: { color: colours?.ink, fontSize: small }, ...one } : one)),
61
+ }
62
+ }
63
+ return {
64
+ /** @param {HTMLElement} box @param {string} source */
65
+ async echarts(box, source) {
66
+ const option = JSON.parse(source)
67
+ const echarts = await script(libraryAt(base, 'echarts', chartsAt, 'dist/echarts.min.js'), 'echarts')
68
+ const place = document.createElement('div')
69
+ place.className = 'deck-chart'
70
+ place.setAttribute('role', 'img')
71
+ place.setAttribute('aria-label', String(option.title?.text ?? ''))
72
+ box.append(place)
73
+ const chart = echarts.init(place, null, { renderer: 'svg', locale: document.documentElement.lang?.startsWith('ja') ? 'JA' : 'EN' })
74
+ const one = { chart, option, box }
75
+ charts.add(one)
76
+ chart.setOption(themed(option, box))
77
+ new ResizeObserver(() => { chart.resize(); chart.setOption(themed(option, box), true) }).observe(place)
78
+ Object.assign(box, { chart })
79
+ },
80
+ /**
81
+ * A diagram written in Mermaid's language, drawn by Pele (Obsidian's, MIT): every kind Mermaid
82
+ * 12.1 has, no DOM needed, an SVG with no script or HTML in it. Shown as a picture, its colours
83
+ * the deck's, set as Pele's variables inside it. @param {HTMLElement} box @param {string} source
84
+ */
85
+ async mermaid(box, source) {
86
+ const pele = await script(libraryAt(base, 'pele', diagramsAt, 'dist/pele.global.js'), 'pele')
87
+ const font = getComputedStyle(root).getPropertyValue('--deck-body') || 'system-ui, sans-serif'
88
+ const { svg } = pele.render(source, { fontFamily: font, links: false, images: false, autoDirection: true, maxWidth: Math.max(320, box.clientWidth * 1.4) })
89
+ const look = `--pele-bg:transparent;--pele-fg:${colours?.ink};--pele-muted:${colours?.muted};--pele-line:${colours?.muted};--pele-surface:${colours?.soft};--pele-surface-alt:${colours?.line};--pele-border:${colours?.primary};--pele-accent:${colours?.primary};--pele-font:${font.replace(/[<>"]/g, '')}`
90
+ const picture = document.createElement('img')
91
+ picture.alt = source
92
+ picture.src = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svg.replace(/^(<svg\b[^>]*>)/, `$1<style>svg{${look}}</style>`))}`
93
+ const place = document.createElement('div')
94
+ place.className = 'deck-diagram'
95
+ place.append(picture)
96
+ box.append(place)
97
+ },
98
+ }
99
+ }
package/src/css.d.ts ADDED
@@ -0,0 +1,5 @@
1
+ // The stylesheet, bundled as text by build.mjs.
2
+ declare module '*.css' {
3
+ const text: string
4
+ export default text
5
+ }
package/src/deck.css ADDED
@@ -0,0 +1,164 @@
1
+ /* deck: a slide is a 16:9 box, a size container, whose canvas sizes its type and space with the
2
+ slide's width (cqi), so it wraps the same at any size; its colours and faces are the look's tokens as custom properties; logical properties
3
+ only, so a deck in Arabic mirrors. The others are laid out behind the one shown, unseen. */
4
+ .deck { --deck-paper: #fffdf8; --deck-ink: #1d1b16; --deck-muted: #6b675e; --deck-line: #e4e0d6; --deck-primary: #8a6100; --deck-fill: #f7c531; --deck-on-fill: #1d1b16; --deck-soft: #fdf1c9;
5
+ --deck-heading: system-ui, sans-serif; --deck-body: system-ui, sans-serif; --deck-radius: 10px;
6
+ position: relative; display: grid; gap: 0.5rem; color: var(--deck-ink); font-family: var(--deck-body); }
7
+ .deck *, .deck *::before, .deck *::after { box-sizing: border-box; }
8
+ .deck-stage { position: relative; view-transition-name: deck-stage; }
9
+ .deck-slide {
10
+ container-type: inline-size; position: relative; aspect-ratio: 16 / 9; overflow: hidden; inline-size: 100%;
11
+ background: var(--deck-paper); color: var(--deck-ink); border-radius: var(--deck-radius); box-shadow: 0 0 0 1px var(--deck-line);
12
+ }
13
+ .deck-canvas { position: absolute; inset: 0; display: grid; padding: 6cqi 7cqi 7cqi; gap: 0 4cqi; font-size: 2.6cqi; line-height: 1.4; }
14
+ .deck-slide:not(.is-shown) { position: absolute; inset-block-start: 0; inset-inline-start: 0; visibility: hidden; }
15
+ .deck-slot { --deck-fit: 1; min-block-size: 0; min-inline-size: 0; display: flex; flex-direction: column; justify-content: flex-start; font-size: calc(1em * var(--deck-fit)); overflow: hidden; }
16
+ .deck-slot > :first-child { margin-block-start: 0; }
17
+ .deck-slot > :last-child { margin-block-end: 0; }
18
+ .deck-slide :is(h1, h2, h3, h4, .deck-h) { font-family: var(--deck-heading); font-weight: 700; line-height: 1.15; margin: 0 0 0.55em; text-wrap: balance; letter-spacing: -0.01em; }
19
+ .deck-slide :is(h1, .deck-h1) { font-size: 2.1em; }
20
+ .deck-slide :is(h2, .deck-h2) { font-size: 1.6em; }
21
+ .deck-slide :is(h3, .deck-h3) { font-size: 1.25em; color: var(--deck-primary); }
22
+ .deck-slide :is(p, ul, ol, blockquote, pre, .deck-table, .deck-block) { margin-block: 0 0.7em; }
23
+ .deck-slide :is(ul, ol) { padding-inline-start: 1.2em; }
24
+ .deck-slide li { margin-block-end: 0.3em; }
25
+ .deck-slide li::marker { color: var(--deck-primary); }
26
+ .deck-slide .deck-tasks { list-style: none; padding-inline-start: 0; }
27
+ .deck-slide .deck-tasks input { font-size: inherit; inline-size: 0.8em; block-size: 0.8em; margin: 0; margin-inline-end: 0.5em; vertical-align: -0.05em; accent-color: var(--deck-primary); }
28
+ .deck-slide strong { color: var(--deck-primary); }
29
+ .deck-slide a { color: var(--deck-primary); }
30
+ .deck-slide code { font-family: ui-monospace, Menlo, Consolas, monospace; font-size: 0.85em; background: var(--deck-soft); padding: 0.05em 0.3em; border-radius: 0.2em; }
31
+ .deck-slide pre { background: color-mix(in srgb, var(--deck-ink) 6%, var(--deck-paper)); padding: 0.8em 1em; border-radius: calc(var(--deck-radius) * 0.6); overflow: hidden; font-size: 0.75em; line-height: 1.45; text-align: start; }
32
+ .deck-slide pre code { background: none; padding: 0; font-size: 1em; }
33
+ .deck-slide pre.has-marks .deck-line:not(.is-marked) { opacity: 0.45; }
34
+ .deck-slide blockquote { border-inline-start: 0.25em solid var(--deck-fill); padding-inline-start: 0.9em; margin-inline: 0; font-style: italic; }
35
+ .deck-slide img { max-inline-size: 100%; max-block-size: 100%; object-fit: contain; }
36
+ .deck-slide .deck-picture { flex: 1; min-block-size: 0; display: grid; place-items: center; }
37
+ .deck-slide .deck-picture img { block-size: 100%; }
38
+ .deck-table { overflow: hidden; }
39
+ .deck-slide table { border-collapse: collapse; inline-size: 100%; font-size: 0.85em; }
40
+ .deck-slide :is(th, td) { padding: 0.35em 0.7em; border-block-end: 1px solid var(--deck-line); text-align: start; }
41
+ .deck-slide th { border-block-end: 2px solid var(--deck-primary); font-weight: 700; }
42
+ .deck-slide [data-align="right"] { text-align: end; font-variant-numeric: tabular-nums; }
43
+ .deck-slide [data-align="center"] { text-align: center; }
44
+ .deck-block { position: relative; flex: 1 1 auto; min-block-size: 12cqi; overflow: hidden; }
45
+ .deck-block .deck-chart, .deck-block .deck-diagram { position: absolute; inset: 0; display: grid; place-items: center; }
46
+ .deck-block .deck-diagram img { max-inline-size: 100%; max-block-size: 100%; }
47
+ .deck-block .deck-block-source { display: none; }
48
+ .deck-block.is-failed .deck-block-source { display: block; }
49
+ .deck-number { position: absolute; inset-block-end: 2.6cqi; inset-inline-end: 3cqi; font-size: 1.5cqi; color: var(--deck-muted); font-variant-numeric: tabular-nums; }
50
+
51
+ /* Layouts from their kit's definition: the paper they are on, where their words sit, how big their heading is. */
52
+ .deck-slide[data-paper="fill"] { background: var(--deck-fill); color: var(--deck-on-fill); }
53
+ .deck-slide[data-paper="ink"] { background: var(--deck-ink); color: var(--deck-paper); }
54
+ .deck-slide[data-paper="soft"] { background: var(--deck-soft); }
55
+ .deck-slide:is([data-paper="fill"], [data-paper="ink"]) :is(strong, a, h3, .deck-h3) { color: inherit; }
56
+ .deck-slide[data-justify="center"] .deck-slot { justify-content: center; }
57
+ .deck-slide[data-justify="end"] .deck-slot { justify-content: flex-end; }
58
+ .deck-slide[data-align="center"] .deck-slot { align-items: center; text-align: center; }
59
+ .deck-slide[data-align="end"] .deck-slot { align-items: flex-end; text-align: end; }
60
+ .deck-slide[data-title="large"] :is(h1, h2, .deck-h1, .deck-h2) { font-size: 2.4em; }
61
+ .deck-slide[data-title="huge"] :is(h1, .deck-h1) { font-size: 2.9em; }
62
+ .deck-slide[data-title="huge"] :is(h2, .deck-h2) { font-size: 2.4em; }
63
+ /* What the usual layouts add to their definition: Slidev's looks for them. */
64
+ .deck-layout-section .deck-slot::before { content: ""; display: block; inline-size: 3em; block-size: 0.3em; background: var(--deck-fill); margin-block-end: 0.8em; border-radius: 0.15em; }
65
+ .deck-layout-statement :is(h1, h2, p, .deck-h1, .deck-h2) { font-size: 2.4em; font-family: var(--deck-heading); font-weight: 700; line-height: 1.2; }
66
+ .deck-slide.deck-layout-fact :is(h1, .deck-h1) { font-size: 5em; color: var(--deck-primary); margin-block-end: 0.15em; }
67
+ .deck-layout-quote blockquote { font-size: 1.7em; border: 0; padding: 0; font-family: var(--deck-heading); }
68
+ /* Scripts without italics keep their upright letters. */
69
+ .deck :is(blockquote, em):is(:lang(ar), :lang(fa), :lang(ur), :lang(he), :lang(ja), :lang(zh), :lang(ko), :lang(hi), :lang(th)) { font-style: normal; }
70
+ .deck-layout-quote blockquote::before { content: "\201C"; display: block; font-size: 2.4em; line-height: 0.6; color: var(--deck-primary); }
71
+ .deck-layout-full .deck-canvas { padding: 0; }
72
+ .deck-slide.has-image .deck-image { background: var(--deck-image) center / cover no-repeat, var(--deck-soft); }
73
+ .deck-layout-image-left.has-image .deck-canvas, .deck-layout-image-right.has-image .deck-canvas { padding: 0; grid-template: "image s-default" 1fr / 1fr 1fr !important; gap: 0; }
74
+ .deck-layout-image-right.has-image .deck-canvas { grid-template-areas: "s-default image" !important; }
75
+ :is(.deck-layout-image-left, .deck-layout-image-right).has-image .deck-image { grid-area: image; }
76
+ :is(.deck-layout-image-left, .deck-layout-image-right).has-image .deck-slot { padding: 6cqi 5cqi; justify-content: center; }
77
+ :is(.deck-layout-image, .deck-layout-cover).has-image .deck-image { position: absolute; inset: 0; }
78
+ :is(.deck-layout-image, .deck-layout-cover).has-image .deck-slot { position: relative; color: #fff; text-shadow: 0 0.05em 0.4em rgb(0 0 0 / 0.6); }
79
+ .deck-layout-cover.has-image .deck-image::after { content: ""; position: absolute; inset: 0; background: linear-gradient(to top, rgb(0 0 0 / 0.65), rgb(0 0 0 / 0.1)); }
80
+ .deck-class-invert { background: var(--deck-ink); color: var(--deck-paper); }
81
+ .deck-class-text-center .deck-slot { text-align: center; align-items: center; }
82
+
83
+ /* Steps: what a click shows next, there but unseen until then. */
84
+ .deck-step { transition: opacity 0.3s, translate 0.3s; }
85
+ .deck-step:not(.is-on) { opacity: 0; visibility: hidden; translate: 0 0.3em; }
86
+ .deck.is-editing .deck-step:not(.is-on), .deck.is-overview .deck-step:not(.is-on), .deck-print .deck-step { opacity: 1; visibility: visible; translate: none; }
87
+
88
+ /* Its bar. */
89
+ .deck-bar { display: flex; align-items: center; gap: 0.25rem; font: 0.85rem/1 system-ui, sans-serif; color: var(--deck-muted); }
90
+ .deck-bar button { display: inline-grid; place-items: center; min-inline-size: 2.25rem; min-block-size: 2.25rem; padding: 0 0.5rem; border: 0; border-radius: 0.5rem; background: none; color: var(--deck-ink); font: inherit; cursor: pointer; }
91
+ .deck-bar button:hover { background: color-mix(in srgb, var(--deck-ink) 8%, transparent); }
92
+ .deck-bar button:focus-visible { outline: 2px solid var(--deck-primary); outline-offset: 1px; }
93
+ .deck-bar button:disabled { opacity: 0.35; cursor: default; }
94
+ .deck-bar button[aria-pressed="true"] { background: color-mix(in srgb, var(--deck-primary) 18%, transparent); }
95
+ .deck-bar svg { inline-size: 1.15rem; block-size: 1.15rem; fill: none; stroke: currentColor; stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; }
96
+ .deck-bar [dir="rtl"] .deck-flip, .deck[dir="rtl"] .deck-flip { scale: -1 1; }
97
+ .deck-where { font-variant-numeric: tabular-nums; min-inline-size: 4.5em; text-align: center; }
98
+ .deck-bar .deck-gap { flex: 1; }
99
+
100
+ /* Presented: the stage fills the screen, black around it. */
101
+ .deck-stage:fullscreen { display: grid; place-items: center; background: #000; }
102
+ .deck-stage:fullscreen .deck-slide { inline-size: min(100vw, 100vh * 16 / 9); border-radius: 0; box-shadow: none; }
103
+ .deck-stage:fullscreen .deck-slide:not(.is-shown) { inset-block-start: 50%; translate: 0 -50%; }
104
+
105
+ /* The presenter's view: this slide, the next, the notes and the time. */
106
+ .deck-presenter { display: none; }
107
+ .deck.is-presenting { grid-template: "stage next" auto "stage notes" 1fr "bar bar" auto / 3fr 2fr; min-block-size: 70vh; }
108
+ .deck.is-presenting .deck-stage { grid-area: stage; align-self: start; }
109
+ .deck.is-presenting .deck-presenter { display: contents; }
110
+ .deck.is-presenting .deck-bar { grid-area: bar; }
111
+ .deck-next { grid-area: next; display: grid; gap: 0.3rem; font: 0.8rem system-ui, sans-serif; color: var(--deck-muted); }
112
+ .deck-next .deck-slide { position: relative; visibility: visible; }
113
+ .deck-notes { grid-area: notes; overflow: auto; padding: 0.8rem 1rem; border-radius: 0.6rem; background: color-mix(in srgb, var(--deck-ink) 5%, transparent); font: 1.1rem/1.5 system-ui, sans-serif; white-space: pre-wrap; }
114
+ .deck-clock { font-variant-numeric: tabular-nums; font-size: 1.2rem; color: var(--deck-ink); }
115
+
116
+ /* All the slides at once. */
117
+ .deck.is-overview .deck-stage { display: grid; grid-template-columns: repeat(auto-fill, minmax(14rem, 1fr)); gap: 0.8rem; view-transition-name: none; }
118
+ .deck.is-overview .deck-slide { position: relative; visibility: visible; cursor: pointer; }
119
+ .deck.is-overview .deck-slide.is-shown { box-shadow: 0 0 0 3px var(--deck-primary); }
120
+
121
+ /* Changed by hand: the Markdown beside the slides, and words changed on a slide in place. */
122
+ .deck.is-editing { grid-template: "source stage" auto "source bar" 1fr / minmax(18rem, 2fr) 3fr; align-items: start; }
123
+ .deck.is-editing .deck-stage { grid-area: stage; }
124
+ .deck.is-editing .deck-bar { grid-area: bar; }
125
+ .deck-source { display: none; }
126
+ .deck.is-editing .deck-source { display: block; grid-area: source; inline-size: 100%; block-size: min(80vh, 48rem); resize: vertical; padding: 0.8rem; border: 1px solid var(--deck-line); border-radius: 0.6rem; background: var(--deck-paper); color: var(--deck-ink); font: 0.85rem/1.55 ui-monospace, Menlo, Consolas, monospace; tab-size: 2; }
127
+ .deck.is-editing [data-edit] { cursor: text; border-radius: 0.15em; }
128
+ .deck.is-editing [data-edit]:hover { box-shadow: 0 0 0 0.12em color-mix(in srgb, var(--deck-primary) 45%, transparent); }
129
+ .deck [data-edit][contenteditable] { outline: 0.12em solid var(--deck-primary); outline-offset: 0.1em; }
130
+ .deck-slide[data-overflow] { box-shadow: 0 0 0 3px #c92a2a; }
131
+ @media (max-width: 52rem) { .deck.is-editing, .deck.is-presenting { grid-template: none; display: grid; } .deck.is-editing .deck-source { block-size: 40vh; } .deck.is-presenting .deck-presenter { display: grid; } }
132
+
133
+ /* Transitions between slides, as Slidev names them; none for those who'd rather not. */
134
+ ::view-transition-group(deck-stage) { animation-duration: 0.45s; animation-timing-function: cubic-bezier(0.2, 0.8, 0.2, 1); }
135
+ html[data-deck-transition~="fade"]::view-transition-old(deck-stage) { animation: 0.35s ease both deck-out-fade; }
136
+ html[data-deck-transition~="fade"]::view-transition-new(deck-stage) { animation: 0.35s ease both deck-in-fade; }
137
+ html[data-deck-transition~="slide-left"]::view-transition-old(deck-stage) { animation: 0.45s cubic-bezier(0.2, 0.8, 0.2, 1) both deck-out-start; }
138
+ html[data-deck-transition~="slide-left"]::view-transition-new(deck-stage) { animation: 0.45s cubic-bezier(0.2, 0.8, 0.2, 1) both deck-in-end; }
139
+ html[data-deck-transition~="slide-right"]::view-transition-old(deck-stage) { animation: 0.45s cubic-bezier(0.2, 0.8, 0.2, 1) both deck-out-end; }
140
+ html[data-deck-transition~="slide-right"]::view-transition-new(deck-stage) { animation: 0.45s cubic-bezier(0.2, 0.8, 0.2, 1) both deck-in-start; }
141
+ html[data-deck-transition~="slide-up"]::view-transition-old(deck-stage) { animation: 0.45s cubic-bezier(0.2, 0.8, 0.2, 1) both deck-out-up; }
142
+ html[data-deck-transition~="slide-up"]::view-transition-new(deck-stage) { animation: 0.45s cubic-bezier(0.2, 0.8, 0.2, 1) both deck-in-up; }
143
+ html[data-deck-transition~="morph"]::view-transition-old(deck-stage) { animation: 0.3s ease both deck-out-fade; }
144
+ html[data-deck-transition~="morph"]::view-transition-new(deck-stage) { animation: 0.3s ease both deck-in-fade; }
145
+ html[data-deck-transition~="morph"]::view-transition-group(*) { animation-duration: 0.6s; }
146
+ @keyframes deck-out-fade { to { opacity: 0; } }
147
+ @keyframes deck-in-fade { from { opacity: 0; } }
148
+ @keyframes deck-out-start { to { translate: -30% 0; opacity: 0; } }
149
+ @keyframes deck-in-end { from { translate: 30% 0; opacity: 0; } }
150
+ @keyframes deck-out-end { to { translate: 30% 0; opacity: 0; } }
151
+ @keyframes deck-in-start { from { translate: -30% 0; opacity: 0; } }
152
+ @keyframes deck-out-up { to { translate: 0 -20%; opacity: 0; } }
153
+ @keyframes deck-in-up { from { translate: 0 20%; opacity: 0; } }
154
+ @media (prefers-reduced-motion: reduce) { ::view-transition-group(*), ::view-transition-old(*), ::view-transition-new(*) { animation: none !important; } .deck-step { transition: none; } }
155
+ @media (forced-colors: active) { .deck-slide { border: 1px solid CanvasText; } .deck-step:not(.is-on) { visibility: hidden; } }
156
+
157
+ /* On paper: every slide, one a page, every step shown, no bar. */
158
+ @media print {
159
+ @page { size: 13.333in 7.5in; margin: 0; }
160
+ .deck-bar, .deck-source, .deck-presenter { display: none !important; }
161
+ .deck, .deck.is-editing, .deck.is-presenting { display: block; }
162
+ .deck-slide, .deck-slide:not(.is-shown) { position: relative; visibility: visible; break-after: page; border-radius: 0; box-shadow: none; inline-size: 100vw; }
163
+ .deck-step:not(.is-on) { opacity: 1; visibility: visible; translate: none; }
164
+ }