glideshow 0.1.2__tar.gz → 0.2.1__tar.gz

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.
Files changed (152) hide show
  1. {glideshow-0.1.2 → glideshow-0.2.1}/.github/workflows/tests.yml +4 -7
  2. {glideshow-0.1.2 → glideshow-0.2.1}/PKG-INFO +3 -5
  3. {glideshow-0.1.2 → glideshow-0.2.1}/README.md +2 -3
  4. {glideshow-0.1.2 → glideshow-0.2.1}/cloud/auth.js +15 -2
  5. {glideshow-0.1.2 → glideshow-0.2.1}/cloud/pages.js +20 -7
  6. {glideshow-0.1.2 → glideshow-0.2.1}/cloud/share.js +4 -0
  7. {glideshow-0.1.2 → glideshow-0.2.1}/cloud/worker.js +3 -1
  8. {glideshow-0.1.2 → glideshow-0.2.1}/docs/agents.md +44 -3
  9. glideshow-0.2.1/docs/deck-format.md +207 -0
  10. {glideshow-0.1.2 → glideshow-0.2.1}/docs/shortcuts.md +2 -0
  11. glideshow-0.2.1/editor/deck/base.css +355 -0
  12. glideshow-0.2.1/editor/deck/present.js +910 -0
  13. {glideshow-0.1.2 → glideshow-0.2.1}/editor/deck/shapes.js +360 -6
  14. {glideshow-0.1.2 → glideshow-0.2.1}/editor/editor.css +106 -6
  15. {glideshow-0.1.2 → glideshow-0.2.1}/editor/globals.d.ts +3 -1
  16. {glideshow-0.1.2 → glideshow-0.2.1}/editor/home.html +2 -3
  17. {glideshow-0.1.2 → glideshow-0.2.1}/editor/index.html +145 -5
  18. glideshow-0.2.1/editor/js/arrange.js +254 -0
  19. glideshow-0.2.1/editor/js/charts.js +92 -0
  20. {glideshow-0.1.2 → glideshow-0.2.1}/editor/js/comments.js +29 -4
  21. {glideshow-0.1.2 → glideshow-0.2.1}/editor/js/context-menus.js +15 -4
  22. {glideshow-0.1.2 → glideshow-0.2.1}/editor/js/dialogs.js +117 -4
  23. glideshow-0.2.1/editor/js/draw.js +258 -0
  24. {glideshow-0.1.2 → glideshow-0.2.1}/editor/js/file.js +76 -3
  25. glideshow-0.2.1/editor/js/format-panel.js +255 -0
  26. {glideshow-0.1.2 → glideshow-0.2.1}/editor/js/geometry.js +90 -0
  27. glideshow-0.2.1/editor/js/insert.js +163 -0
  28. {glideshow-0.1.2 → glideshow-0.2.1}/editor/js/keyboard.js +17 -4
  29. {glideshow-0.1.2 → glideshow-0.2.1}/editor/js/menubar.js +35 -4
  30. glideshow-0.2.1/editor/js/motion.js +458 -0
  31. {glideshow-0.1.2 → glideshow-0.2.1}/editor/js/objects.js +119 -0
  32. {glideshow-0.1.2 → glideshow-0.2.1}/editor/js/phone-view.js +1 -1
  33. {glideshow-0.1.2 → glideshow-0.2.1}/editor/js/pointer.js +10 -1
  34. {glideshow-0.1.2 → glideshow-0.2.1}/editor/js/render.js +36 -9
  35. {glideshow-0.1.2 → glideshow-0.2.1}/editor/js/shortcuts.js +7 -4
  36. {glideshow-0.1.2 → glideshow-0.2.1}/editor/js/slides.js +63 -0
  37. {glideshow-0.1.2 → glideshow-0.2.1}/editor/js/state.js +4 -3
  38. {glideshow-0.1.2 → glideshow-0.2.1}/editor/js/storage.js +10 -5
  39. {glideshow-0.1.2 → glideshow-0.2.1}/editor/js/text.js +95 -11
  40. {glideshow-0.1.2 → glideshow-0.2.1}/editor/js/theme.js +56 -8
  41. {glideshow-0.1.2 → glideshow-0.2.1}/editor/js/toolbar-groups.js +1 -1
  42. {glideshow-0.1.2 → glideshow-0.2.1}/editor/js/toolbar.js +93 -12
  43. glideshow-0.2.1/editor/lib/polygon-clipping.min.js +25 -0
  44. {glideshow-0.1.2 → glideshow-0.2.1}/editor/phone.css +1 -1
  45. {glideshow-0.1.2 → glideshow-0.2.1}/editor/share/share.js +1 -0
  46. {glideshow-0.1.2 → glideshow-0.2.1}/editor/tsconfig.json +1 -1
  47. {glideshow-0.1.2 → glideshow-0.2.1}/glideshow/__init__.py +1 -1
  48. {glideshow-0.1.2 → glideshow-0.2.1}/glideshow/agent.py +119 -10
  49. {glideshow-0.1.2 → glideshow-0.2.1}/glideshow/cli.py +8 -2
  50. {glideshow-0.1.2 → glideshow-0.2.1}/glideshow/comments.py +5 -1
  51. {glideshow-0.1.2 → glideshow-0.2.1}/glideshow/gslides.py +39 -3
  52. glideshow-0.2.1/glideshow/movie.py +70 -0
  53. glideshow-0.2.1/glideshow/pptx_export.py +330 -0
  54. {glideshow-0.1.2 → glideshow-0.2.1}/glideshow/pptx_import.py +21 -4
  55. {glideshow-0.1.2 → glideshow-0.2.1}/glideshow/render.py +1 -1
  56. {glideshow-0.1.2 → glideshow-0.2.1}/glideshow/server.py +103 -4
  57. {glideshow-0.1.2 → glideshow-0.2.1}/pyproject.toml +1 -2
  58. {glideshow-0.1.2 → glideshow-0.2.1}/test/agent.py +35 -1
  59. {glideshow-0.1.2 → glideshow-0.2.1}/test/fixtures/gslides-api.json +88 -1
  60. {glideshow-0.1.2 → glideshow-0.2.1}/test/keys.py +19 -4
  61. {glideshow-0.1.2 → glideshow-0.2.1}/test/smoke.py +1022 -13
  62. {glideshow-0.1.2 → glideshow-0.2.1}/uv.lock +352 -352
  63. glideshow-0.1.2/docs/deck-format.md +0 -125
  64. glideshow-0.1.2/editor/deck/base.css +0 -92
  65. glideshow-0.1.2/editor/deck/present.js +0 -222
  66. glideshow-0.1.2/editor/js/arrange.js +0 -122
  67. glideshow-0.1.2/editor/js/draw.js +0 -107
  68. glideshow-0.1.2/editor/js/format-panel.js +0 -101
  69. glideshow-0.1.2/editor/js/insert.js +0 -73
  70. glideshow-0.1.2/editor/js/motion.js +0 -148
  71. glideshow-0.1.2/glideshow/pptx_export.py +0 -207
  72. {glideshow-0.1.2 → glideshow-0.2.1}/.github/workflows/checks.yml +0 -0
  73. {glideshow-0.1.2 → glideshow-0.2.1}/.github/workflows/release.yml +0 -0
  74. {glideshow-0.1.2 → glideshow-0.2.1}/.github/workflows/staging.yml +0 -0
  75. {glideshow-0.1.2 → glideshow-0.2.1}/.gitignore +0 -0
  76. {glideshow-0.1.2 → glideshow-0.2.1}/LICENSE +0 -0
  77. {glideshow-0.1.2 → glideshow-0.2.1}/bin/glideshow +0 -0
  78. {glideshow-0.1.2 → glideshow-0.2.1}/cloud/clean.js +0 -0
  79. {glideshow-0.1.2 → glideshow-0.2.1}/cloud/env.d.ts +0 -0
  80. {glideshow-0.1.2 → glideshow-0.2.1}/cloud/github-app.py +0 -0
  81. {glideshow-0.1.2 → glideshow-0.2.1}/cloud/library.js +0 -0
  82. {glideshow-0.1.2 → glideshow-0.2.1}/cloud/setup.sh +0 -0
  83. {glideshow-0.1.2 → glideshow-0.2.1}/cloud/store-drive.js +0 -0
  84. {glideshow-0.1.2 → glideshow-0.2.1}/cloud/store-github.js +0 -0
  85. {glideshow-0.1.2 → glideshow-0.2.1}/cloud/tsconfig.json +0 -0
  86. {glideshow-0.1.2 → glideshow-0.2.1}/cloud/wrangler.toml +0 -0
  87. {glideshow-0.1.2 → glideshow-0.2.1}/docs/self-hosting.md +0 -0
  88. {glideshow-0.1.2 → glideshow-0.2.1}/editor/.assetsignore +0 -0
  89. {glideshow-0.1.2 → glideshow-0.2.1}/editor/apple-touch-icon.png +0 -0
  90. {glideshow-0.1.2 → glideshow-0.2.1}/editor/data/font-metrics.json +0 -0
  91. {glideshow-0.1.2 → glideshow-0.2.1}/editor/data/fonts.json +0 -0
  92. {glideshow-0.1.2 → glideshow-0.2.1}/editor/data/icons.json +0 -0
  93. {glideshow-0.1.2 → glideshow-0.2.1}/editor/data/symbols.js +0 -0
  94. {glideshow-0.1.2 → glideshow-0.2.1}/editor/data/themes.js +0 -0
  95. {glideshow-0.1.2 → glideshow-0.2.1}/editor/deck/template.html +0 -0
  96. {glideshow-0.1.2 → glideshow-0.2.1}/editor/glideshow-1024.png +0 -0
  97. {glideshow-0.1.2 → glideshow-0.2.1}/editor/glideshow.svg +0 -0
  98. {glideshow-0.1.2 → glideshow-0.2.1}/editor/js/boot.js +0 -0
  99. {glideshow-0.1.2 → glideshow-0.2.1}/editor/js/boxes.js +0 -0
  100. {glideshow-0.1.2 → glideshow-0.2.1}/editor/js/dock.js +0 -0
  101. {glideshow-0.1.2 → glideshow-0.2.1}/editor/js/find.js +0 -0
  102. {glideshow-0.1.2 → glideshow-0.2.1}/editor/js/guides.js +0 -0
  103. {glideshow-0.1.2 → glideshow-0.2.1}/editor/js/live.js +0 -0
  104. {glideshow-0.1.2 → glideshow-0.2.1}/editor/js/menus.js +0 -0
  105. {glideshow-0.1.2 → glideshow-0.2.1}/editor/js/notes.js +0 -0
  106. {glideshow-0.1.2 → glideshow-0.2.1}/editor/js/people.js +0 -0
  107. {glideshow-0.1.2 → glideshow-0.2.1}/editor/js/phone-comments.js +0 -0
  108. {glideshow-0.1.2 → glideshow-0.2.1}/editor/js/phone-edit.js +0 -0
  109. {glideshow-0.1.2 → glideshow-0.2.1}/editor/js/phone-home.js +0 -0
  110. {glideshow-0.1.2 → glideshow-0.2.1}/editor/js/phone-mode.js +0 -0
  111. {glideshow-0.1.2 → glideshow-0.2.1}/editor/js/phone-sheets.js +0 -0
  112. {glideshow-0.1.2 → glideshow-0.2.1}/editor/js/phone-touch.js +0 -0
  113. {glideshow-0.1.2 → glideshow-0.2.1}/editor/js/spacing.js +0 -0
  114. {glideshow-0.1.2 → glideshow-0.2.1}/editor/js/theme-toggle.js +0 -0
  115. {glideshow-0.1.2 → glideshow-0.2.1}/editor/lib/browser-db.js +0 -0
  116. {glideshow-0.1.2 → glideshow-0.2.1}/editor/lib/library.js +0 -0
  117. {glideshow-0.1.2 → glideshow-0.2.1}/editor/lib/merge.js +0 -0
  118. {glideshow-0.1.2 → glideshow-0.2.1}/editor/lib/move-local.js +0 -0
  119. {glideshow-0.1.2 → glideshow-0.2.1}/editor/lib/onefile.js +0 -0
  120. {glideshow-0.1.2 → glideshow-0.2.1}/editor/lib/store-browser.js +0 -0
  121. {glideshow-0.1.2 → glideshow-0.2.1}/editor/lib/store-folder.js +0 -0
  122. {glideshow-0.1.2 → glideshow-0.2.1}/editor/local/sw.js +0 -0
  123. {glideshow-0.1.2 → glideshow-0.2.1}/editor/local/tsconfig.json +0 -0
  124. {glideshow-0.1.2 → glideshow-0.2.1}/editor/looks/glider.svg +0 -0
  125. {glideshow-0.1.2 → glideshow-0.2.1}/editor/looks/thermal-phone.css +0 -0
  126. {glideshow-0.1.2 → glideshow-0.2.1}/editor/looks/thermal.css +0 -0
  127. {glideshow-0.1.2 → glideshow-0.2.1}/editor/manifest.json +0 -0
  128. {glideshow-0.1.2 → glideshow-0.2.1}/editor/share/share.css +0 -0
  129. {glideshow-0.1.2 → glideshow-0.2.1}/editor/share/speaker.html +0 -0
  130. {glideshow-0.1.2 → glideshow-0.2.1}/glideshow/__main__.py +0 -0
  131. {glideshow-0.1.2 → glideshow-0.2.1}/glideshow/build.py +0 -0
  132. {glideshow-0.1.2 → glideshow-0.2.1}/glideshow/decks.py +0 -0
  133. {glideshow-0.1.2 → glideshow-0.2.1}/glideshow/figure.py +0 -0
  134. {glideshow-0.1.2 → glideshow-0.2.1}/glideshow/fonts.py +0 -0
  135. {glideshow-0.1.2 → glideshow-0.2.1}/glideshow/history.py +0 -0
  136. {glideshow-0.1.2 → glideshow-0.2.1}/glideshow/merge.py +0 -0
  137. {glideshow-0.1.2 → glideshow-0.2.1}/glideshow/osutil.py +0 -0
  138. {glideshow-0.1.2 → glideshow-0.2.1}/glideshow/publish.py +0 -0
  139. {glideshow-0.1.2 → glideshow-0.2.1}/glideshow/service.py +0 -0
  140. {glideshow-0.1.2 → glideshow-0.2.1}/skills/glideshow/SKILL.md +0 -0
  141. {glideshow-0.1.2 → glideshow-0.2.1}/test/cloud.py +0 -0
  142. {glideshow-0.1.2 → glideshow-0.2.1}/test/coedit.py +0 -0
  143. {glideshow-0.1.2 → glideshow-0.2.1}/test/fake_drive.py +0 -0
  144. {glideshow-0.1.2 → glideshow-0.2.1}/test/fixtures/agent-deck.html +0 -0
  145. {glideshow-0.1.2 → glideshow-0.2.1}/test/fixtures/stack-deck.html +0 -0
  146. {glideshow-0.1.2 → glideshow-0.2.1}/test/media.py +0 -0
  147. {glideshow-0.1.2 → glideshow-0.2.1}/test/share.py +0 -0
  148. {glideshow-0.1.2 → glideshow-0.2.1}/test/snap.py +0 -0
  149. {glideshow-0.1.2 → glideshow-0.2.1}/tools/check.sh +0 -0
  150. {glideshow-0.1.2 → glideshow-0.2.1}/tools/font_metrics.py +0 -0
  151. {glideshow-0.1.2 → glideshow-0.2.1}/tools/gslides2deck.py +0 -0
  152. {glideshow-0.1.2 → glideshow-0.2.1}/tools/make-app.sh +0 -0
@@ -1,11 +1,8 @@
1
- # The end-to-end tests in headless Chrome (about 10 minutes on macOS): on pull requests, every night on main, and by hand
2
- # (Actions → tests → Run workflow, or `gh workflow run tests.yml`). They gate nothing: a failure is a heads-up to look
3
- # at. Before tagging a release, run `uv run test/smoke.py` locally.
1
+ # The end-to-end tests in headless Chrome (about 30 minutes on macOS), by hand only (Actions → tests → Run workflow, or
2
+ # `gh workflow run tests.yml`): the repo is private, where a macOS minute costs ten of the free ones. They gate nothing.
3
+ # Before tagging a release, run `uv run test/smoke.py` locally.
4
4
  name: tests
5
5
  on:
6
- pull_request:
7
- schedule:
8
- - cron: '0 7 * * *' # 07:00 UTC, every night
9
6
  workflow_dispatch:
10
7
  permissions:
11
8
  contents: read
@@ -13,7 +10,7 @@ permissions:
13
10
  jobs:
14
11
  test:
15
12
  runs-on: macos-latest # the tests are macOS's: ⌘ shortcuts (docs/shortcuts.md), its fonts in the text-fitting checks
16
- timeout-minutes: 30
13
+ timeout-minutes: 45 # smoke + keys + agent took 25–30 minutes by 2026-11
17
14
  steps:
18
15
  - uses: actions/checkout@v4
19
16
  - uses: astral-sh/setup-uv@v5
@@ -1,9 +1,8 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: glideshow
3
- Version: 0.1.2
3
+ Version: 0.2.1
4
4
  Summary: Slides you own: a Google Slides-style editor whose decks are plain HTML folders that AI agents can edit
5
5
  Project-URL: Homepage, https://glideshow.org
6
- Project-URL: Source, https://github.com/heyodog0/glideshow
7
6
  License-Expression: AGPL-3.0-or-later
8
7
  License-File: LICENSE
9
8
  Requires-Python: >=3.10
@@ -27,7 +26,7 @@ presents in any browser. Every save is a version in git.
27
26
  Needs Python 3.10+ and Chrome; macOS, Linux and Windows.
28
27
 
29
28
  ```console
30
- $ uv tool install git+https://github.com/heyodog0/glideshow
29
+ $ uv tool install glideshow
31
30
  $ glideshow # the home screen: your decks (in ~/decks)
32
31
  $ glideshow new "Lab meeting" # a new deck, opened in the editor
33
32
  $ glideshow build lab-meeting out/ # a copy that presents offline
@@ -51,8 +50,7 @@ Issues and pull requests are welcome. Before sending one, run `tools/check.sh` (
51
50
  ## Releasing
52
51
 
53
52
  Work lands on `main` (small changes directly, bigger ones through a short branch and a pull request). Every push to
54
- `main` that passes the checks goes to [dev.glideshow.org](https://dev.glideshow.org), the nightly channel
55
- (`uv tool install git+https://github.com/heyodog0/glideshow@main` for the command). A release is a version tag:
53
+ `main` that passes the checks goes to [dev.glideshow.org](https://dev.glideshow.org), the nightly channel. A release is a version tag:
56
54
 
57
55
  ```console
58
56
  $ # bump `version` in pyproject.toml, commit it, then:
@@ -14,7 +14,7 @@ presents in any browser. Every save is a version in git.
14
14
  Needs Python 3.10+ and Chrome; macOS, Linux and Windows.
15
15
 
16
16
  ```console
17
- $ uv tool install git+https://github.com/heyodog0/glideshow
17
+ $ uv tool install glideshow
18
18
  $ glideshow # the home screen: your decks (in ~/decks)
19
19
  $ glideshow new "Lab meeting" # a new deck, opened in the editor
20
20
  $ glideshow build lab-meeting out/ # a copy that presents offline
@@ -38,8 +38,7 @@ Issues and pull requests are welcome. Before sending one, run `tools/check.sh` (
38
38
  ## Releasing
39
39
 
40
40
  Work lands on `main` (small changes directly, bigger ones through a short branch and a pull request). Every push to
41
- `main` that passes the checks goes to [dev.glideshow.org](https://dev.glideshow.org), the nightly channel
42
- (`uv tool install git+https://github.com/heyodog0/glideshow@main` for the command). A release is a version tag:
41
+ `main` that passes the checks goes to [dev.glideshow.org](https://dev.glideshow.org), the nightly channel. A release is a version tag:
43
42
 
44
43
  ```console
45
44
  $ # bump `version` in pyproject.toml, commit it, then:
@@ -188,11 +188,24 @@ const DARK = `--card: #1d1e25; --ink: #ececee; --muted: #a1a3aa; --line: #3a3b40
188
188
  --sky: radial-gradient(120% 140% at 88% 130%, rgba(226, 140, 112, .5) 0%, rgba(226, 140, 112, 0) 58%), radial-gradient(140% 70% at 15% 120%, rgba(150, 88, 140, .45) 0%, rgba(150, 88, 140, 0) 70%), linear-gradient(180deg, #18182c 0%, #221e41 50%, #2f2749 100%);
189
189
  --grain-blend: overlay; --grain: .2; color-scheme: dark;`; // dusk: the system's dark mode, or the switch in the corner
190
190
 
191
- export function page(title, body, status = 200) {
191
+ /* What search engines and link previews see, for the public pages only (opts.index); every other page stays noindex. */
192
+ const SITE = 'https://glideshow.org';
193
+ function meta(title, { index = false, path = '/', description = '', ld = null } = {}) {
194
+ if (!index) return '<meta name="robots" content="noindex">';
195
+ const d = esc(description), t = esc(title);
196
+ return `<meta name="description" content="${d}"><link rel="canonical" href="${SITE}${path}">
197
+ <meta property="og:type" content="website"><meta property="og:site_name" content="Glideshow"><meta property="og:title" content="${t}">
198
+ <meta property="og:description" content="${d}"><meta property="og:url" content="${SITE}${path}"><meta property="og:image" content="${SITE}/og.png">
199
+ <meta name="twitter:card" content="summary"><meta name="twitter:title" content="${t}"><meta name="twitter:description" content="${d}"><meta name="twitter:image" content="${SITE}/og.png">${ld ? `
200
+ <script type="application/ld+json">${JSON.stringify(ld).replace(/</g, '\\u003c')}</script>` : ''}`;
201
+ }
202
+
203
+ /** @param {string} title @param {string} body @param {number} [status] @param {{ index?: boolean, path?: string, description?: string, ld?: any, full?: string }} [opts] */
204
+ export function page(title, body, status = 200, opts = {}) {
192
205
  // Thermal's front page: the sky is the page itself (dusk in dark mode), one plain card, the mascot gliding above it
193
206
  const grain = `url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.9' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='.5'/%3E%3C/svg%3E")`;
194
207
  return new Response(`<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1">
195
- <meta name="robots" content="noindex"><link rel="icon" href="/favicon.svg" type="image/svg+xml"><link rel="apple-touch-icon" href="/apple-touch-icon.png"><title>${esc(title)} · Glideshow</title>
208
+ ${meta(opts.full || (title === 'Glideshow' ? title : title + ' · Glideshow'), opts)}<link rel="icon" href="/favicon.svg" type="image/svg+xml"><link rel="apple-touch-icon" href="/apple-touch-icon.png"><title>${esc(opts.full || (title === 'Glideshow' ? title : title + ' · Glideshow'))}</title>
196
209
  <script>try { const v = localStorage.getItem('slides-dark'); if (v) document.documentElement.dataset.theme = v === '1' ? 'dark' : 'light'; } catch {}</script>
197
210
  <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Hanken+Grotesk:wght@300;400;500;600&display=swap"><style>
198
211
  .stage { display: inline-block; margin-left: .5em; padding: .15em .55em; border-radius: 999px; background: var(--accent); color: var(--on-accent); font-size: .32em; font-weight: 600; letter-spacing: .06em; text-transform: uppercase; vertical-align: .55em; } /* alpha (the front page) */
@@ -5,7 +5,7 @@
5
5
  import { page, esc } from './auth.js';
6
6
 
7
7
  const CONTACT = 'heyodogo@gmail.com';
8
- const foot = '<small><a href="/">Glideshow</a> · <a href="/privacy">Privacy</a> · <a href="/terms">Terms</a> · <a href="https://github.com/heyodog0/glideshow">GitHub</a></small>';
8
+ const foot = '<small><a href="/">Glideshow</a> · <a href="/privacy">Privacy</a> · <a href="/terms">Terms</a></small>';
9
9
 
10
10
  export function landing() {
11
11
  return page('Glideshow', `<p class="tag">Slides you own, <em>gliding</em> where you work.</p>
@@ -25,11 +25,17 @@ GitHub repo. You see every version, and AI agents can read and edit them with yo
25
25
  };
26
26
  }</script>
27
27
  <small>No account needed: your decks stay in this browser. Sign in with Google or GitHub to keep them in your Drive or a repo,
28
- and to share them. Glideshow is free and in early testing.</small>${foot}`);
28
+ and to share them. Glideshow is free and in early testing.</small>${foot}`, 200, {
29
+ index: true, path: '/', full: 'Glideshow: slides you own, as plain HTML files',
30
+ description: 'Glideshow is a free, open-source slide editor. Decks are plain HTML files you keep in a folder, your Google Drive or a GitHub repo, with every version saved. Import Google Slides and PowerPoint, share with a link, and edit with AI agents like Claude Code.',
31
+ ld: { '@context': 'https://schema.org', '@type': 'SoftwareApplication', name: 'Glideshow', url: 'https://glideshow.org/',
32
+ applicationCategory: 'BusinessApplication', operatingSystem: 'Web, macOS, Linux, Windows', image: 'https://glideshow.org/og.png',
33
+ description: 'A free, open-source slide editor whose decks are plain HTML files you own.', offers: { '@type': 'Offer', price: '0', priceCurrency: 'USD' },
34
+ sameAs: ['https://github.com/heyodog0/glideshow', 'https://pypi.org/project/glideshow/'] } });
29
35
  }
30
36
 
31
37
  export function privacy() {
32
- return page('Privacy policy', `<small>Last updated October 12, 2026</small>
38
+ return page('Privacy policy', `<small>Last updated November 1, 2026</small>
33
39
  <p><b>What Glideshow keeps.</b> When you sign in with GitHub: your GitHub user id, username and name. When you sign in with Google: your
34
40
  Google account id, name and email address. These go into a signed cookie in your browser (your session, for up to 90 days), not into a
35
41
  database of users. For someone who keeps decks here, Glideshow also remembers which GitHub repo holds them and their recently opened decks.
@@ -47,7 +53,8 @@ owner's repo or Drive, where the owner (and anyone with access to it) can see th
47
53
  <p><b>Reports.</b> If you report a shared deck, Glideshow keeps the report with your IP address and, if you give it, your email address, so
48
54
  the person running Glideshow can review it; reports are deleted once dealt with.</p>
49
55
  <p><b>What Glideshow doesn't do.</b> No ads, no tracking or analytics cookies, no selling or sharing of your information, and no use of your
50
- decks or comments to train AI models. Search engines and AI crawlers are asked to stay out (robots.txt and noindex, noai headers).</p>
56
+ decks or comments to train AI models. Search engines and AI crawlers are asked to stay out of everything but the home page, this
57
+ policy and the terms: no deck, shared or not, is indexed (robots.txt and noindex, noai headers).</p>
51
58
  <p><b>Google user data.</b> Glideshow's use and transfer of information received from Google APIs adheres to the
52
59
  <a href="https://developers.google.com/terms/api-services-user-data-policy">Google API Services User Data Policy</a>, including the Limited Use
53
60
  requirements: it is used only to sign you in and to keep your decks in your Drive, never for ads, never sold, and never read by people except
@@ -56,7 +63,7 @@ to keep the service secure or at your request.</p>
56
63
  for a few days; GitHub holds the decks; Google and GitHub handle sign-in.
57
64
  Their own privacy policies apply to them.</p>
58
65
  <p><b>Your choices.</b> Sign out anytime; remove the Glideshow app from your GitHub account (Settings → Applications) to cut its access to your
59
- repos; ask a deck's owner to delete your comments, or delete them yourself. Questions or deletion requests: <a href="mailto:${CONTACT}">${esc(CONTACT)}</a>.</p>${foot}`);
66
+ repos; ask a deck's owner to delete your comments, or delete them yourself. Questions or deletion requests: <a href="mailto:${CONTACT}">${esc(CONTACT)}</a>.</p>${foot}`, 200, { index: true, path: '/privacy', description: 'What Glideshow keeps about you and your decks, and what it never does.' });
60
67
  }
61
68
 
62
69
  export function terms() {
@@ -76,7 +83,13 @@ can be made per minute.</p>
76
83
  <p><b>Accounts.</b> Access can be suspended for misuse. You can stop using Glideshow at any time by signing out and removing its GitHub app.</p>
77
84
  <p><b>Liability.</b> To the extent the law allows, Glideshow and the person running it are not liable for any indirect or consequential loss,
78
85
  or loss of data, from using it.</p>
79
- <p>Contact: <a href="mailto:${CONTACT}">${esc(CONTACT)}</a>.</p>${foot}`);
86
+ <p>Contact: <a href="mailto:${CONTACT}">${esc(CONTACT)}</a>.</p>${foot}`, 200, { index: true, path: '/terms', description: 'The terms for using Glideshow, a free slide editor in early testing.' });
80
87
  }
81
88
 
82
- export const robots = 'User-agent: *\nAllow: /$\nAllow: /privacy\nAllow: /terms\nDisallow: /\n';
89
+ export const robots = 'User-agent: *\nAllow: /$\nAllow: /privacy\nAllow: /terms\nAllow: /og.png\nAllow: /sitemap.xml\nDisallow: /\n\nSitemap: https://glideshow.org/sitemap.xml\n';
90
+
91
+ export const sitemap = `<?xml version="1.0" encoding="UTF-8"?>
92
+ <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
93
+ ${['/', '/privacy', '/terms'].map(p => ` <url><loc>https://glideshow.org${p}</loc></url>`).join('\n')}
94
+ </urlset>
95
+ `;
@@ -104,6 +104,10 @@ async function changeComments(lib, deck, r, by, isOwner) {
104
104
  } else if (r.op === 'reply') {
105
105
  const c = find(r.id); if (!c || !text) return { ...doc, error: 'nothing to reply to' };
106
106
  c.replies.push({ id: rand(8), text, by: who, at: nowISO() });
107
+ } else if (r.op === 'edit') { // your own comment or reply (by your email)
108
+ const c = find(r.id), t = c && (r.reply ? c.replies.find(x => x.id === r.reply) : c);
109
+ if (!t || t.event || !text || !(t.by?.e && t.by.e === who.e)) return { ...doc, error: "you can only edit your own comments" };
110
+ t.text = text; t.edited = nowISO();
107
111
  } else if (r.op === 'resolve') {
108
112
  const c = find(r.id); if (!c) return { ...doc, error: 'no such comment' };
109
113
  c.resolved = !!r.resolved; c.replies.push({ id: rand(8), text: r.resolved ? '(resolved)' : '(reopened)', by: who, at: nowISO(), event: true });
@@ -12,7 +12,7 @@
12
12
  import { DurableObject } from 'cloudflare:workers';
13
13
  import { approvedFrom, cleanSlides, composeDeck, deckForCollaborator } from './clean.js';
14
14
  import { session, isOwner, isAdmin, libraryFor, githubStart, githubCallback, googleStart, googleCallback, devLogin, loginPage, page, esc, json, redirect, cookie, safeNext } from './auth.js';
15
- import { landing, privacy, terms, robots } from './pages.js';
15
+ import { landing, privacy, terms, robots, sitemap } from './pages.js';
16
16
  export { Library } from './library.js';
17
17
 
18
18
  export default {
@@ -52,6 +52,8 @@ const WHY = { shared: 'This deck is shared with specific people. Sign in to open
52
52
 
53
53
  async function route(req, env, url, path) {
54
54
  if (path === '/robots.txt') return new Response(robots, { headers: { 'content-type': 'text/plain' } });
55
+ if (path === '/sitemap.xml') return new Response(sitemap, { headers: { 'content-type': 'application/xml' } });
56
+ if (path === '/og.png') return env.ASSETS.fetch(new Request(new URL('/glideshow-1024.png', url.origin))); // the link-preview picture
55
57
  if (path === '/favicon.svg' || path === '/favicon.ico') return env.ASSETS.fetch(new Request(new URL('/glideshow.svg', url.origin))); // the mascot, for every page (/editor/ is behind sign-in)
56
58
  if (path === '/apple-touch-icon.png') return env.ASSETS.fetch(new Request(new URL(path, url.origin)));
57
59
  if (path === '/privacy') return privacy();
@@ -20,7 +20,7 @@ outline and `check` show both where they differ (`9 = 8.1`).
20
20
  |---|---|
21
21
  | `glideshow new "Title" --no-open` | makes `~/decks/<slug>-<date>/` from the template and prints the folder; the user's editor is left alone |
22
22
  | `glideshow find <words>` | prints the folder of the deck that best matches; nothing is opened |
23
- | `glideshow slides <deck> --outline` | one line per slide: its number, layout, title, each box's id and first words, notes for you in full |
23
+ | `glideshow slides <deck> --outline` | one line per slide: its number, layout, title, each box's id and first words (a chart as kind and series × categories, builds in `{…}`), the layout's own objects, notes for you in full |
24
24
  | `glideshow slides <deck> [N …]` | those slides' HTML (numbered, with their line in `deck.html`) |
25
25
  | `glideshow put <deck> N` | replaces slide N with the `<section>`s on stdin; `--after N` inserts (0: first), `--delete N …` removes, `--all` replaces every slide |
26
26
  | `glideshow layout [NAME]` | the layouts' exact placeholder boxes; with a name, a slide in that layout to fill in |
@@ -118,7 +118,9 @@ EOF
118
118
  ```
119
119
  `put` prints the new slide's outline line and the `check` to run. To change a slide, `glideshow slides <deck> N`, edit
120
120
  that, and `glideshow put <deck> N` it back (its `data-sid` is kept). It refuses, writing nothing, when a `<div>` isn't
121
- closed, an id is already used elsewhere in the deck, or a slide carries another slide's `data-sid`. A script that builds
121
+ closed, an id is already used elsewhere in the deck, a slide carries another slide's `data-sid`, or a build, delivery,
122
+ chart table, layout or paragraph style is wrong (one line each, with what is allowed; a link to a missing slide is only
123
+ reported, and `check` says all of these of the whole deck). A script that builds
122
124
  the whole deck prints its `<section>`s and pipes them into `glideshow put <deck> --all`; don't let it rewrite `deck.html`
123
125
  (a pattern that cuts the file at the wrong `</div>` leaves slides outside the deck).
124
126
 
@@ -170,6 +172,8 @@ the slide's pixels and, beside it, where each piece of its text is (`curves.png.
170
172
  `--at x,y,w,h`), and records `data-source` (`{out}` stands for the picture's path) and `data-made`. `figure refresh` says
171
173
  when a picture came out the same (and leaves the deck alone) and when its font lacks a character. In matplotlib, a
172
174
  figure 1 in wide fills 192 px of slide, so a point in the figure is a point on the slide: labels at 12–16 pt read well.
175
+ In the editor the user can press Refresh (Format options → Figure) and, once they trust the command, the figure is
176
+ made again whenever its script or a data file named in the command changes, so keep the data file's path in the command.
173
177
 
174
178
  **An equation.** Any box: `<div class="content" style="font-size: 48px; text-align: center;">$$\mathcal{L} = -\sum_t \log \pi_\theta(a_t \mid h_{<t})$$</div>`.
175
179
  The deck keeps the LaTeX; KaTeX typesets it.
@@ -181,9 +185,28 @@ lines short or the font smaller (`check` reports code that is cut off).
181
185
  (`data-adj` / `data-adj2`, 0..1 of the box, may be negative) points at the feature, or an `ellipse` with
182
186
  `--fill: transparent; --stroke: #d93025; --sw: 6px` around it.
183
187
 
188
+ **A logo or footer on every slide.** Put it on the layout, not on each slide: in `<template id="deck-layouts">` (after
189
+ `.reveal`, create it if missing) add `<div class="layout" data-layout="title-body">` with the box inside; every slide with
190
+ that `data-layout` shows it. See the deck format's "Layouts with their own objects".
191
+
192
+ **Text styles.** To keep a deck's text consistent, put paragraphs in a style rather than sizing each: `<p class="ps-heading">`,
193
+ `ps-title`, `ps-subtitle`, `ps-body`, `ps-caption` or `ps-quote`. Change a style's look for the whole deck in its rule in
194
+ `<style id="deck-theme">` (the `/* text-styles */` block), not on each paragraph.
195
+
196
+ **More than one build on a box.** A box's own build is its `fragment` class (with `data-fragment-index`); further builds on
197
+ the same box go in one attribute, `data-builds`, a `;`-separated list of `effect key=value …`. Each is a step of its own
198
+ after the box's own build (`start=with`: the same step; `start=after`: its own step that starts by itself). A Move goes
199
+ on from where the box last stopped; `to` is the box's top-left at its end, in slide px:
200
+ ```html
201
+ <div class="box fragment move-to" id="dot" data-fragment-index="0" data-to="800,300" data-builds="move-to to=1400,600; spin angle=90 start=after; fade-out duration=0.8" style="left: 200px; top: 300px; width: 120px; height: 120px; --fill: #1a73e8;"></div>
202
+ ```
203
+ Effects for these: `move-to` (`to=x,y`), `spin` (`angle`), `pulse`, `pop`, `fade-out`, `disappear`, `scale-out`, `blur-out`,
204
+ `iris-out`; keys `duration`, `delay` (s), `ease`, `start`.
205
+
184
206
  **Building a diagram up step by step (Morph).** One slide per step. Each step's boxes have ids unique in the whole deck,
185
207
  and each step after the first has `data-auto-animate` and pairs every box that carries over with `data-morph="<its id on
186
- the slide before>"`; a box with no partner fades in. The first step has no `data-auto-animate`.
208
+ the slide before>"`; a box with no partner fades in. The first step has no `data-auto-animate`. When a step rewords a
209
+ title or label, `data-morph-match="word"` on that step's `<section>` slides the words that stay into their new places.
187
210
  ```html
188
211
  <section data-layout="title-only">
189
212
  <div class="box" id="s1-title" data-placeholder="Click to add title" data-role="title" data-valign="top" style="left: 65px; top: 93px; width: 1790px; height: 122px;"><div class="content" style="font-size: calc(var(--th-title-size, 74.67px) * 1); text-align: left; padding: 9.6px 19.2px;">The pipeline</div></div>
@@ -199,6 +222,19 @@ A third step pairs with the second's ids (`data-morph="s2-actors"`), and so on.
199
222
  `glideshow render <deck> --morph` lists each step's pairs (what moves, resizes, recolours, fades in or out) and saves a
200
223
  picture halfway through each animation.
201
224
 
225
+ **A chart from a few numbers** (a column, bar, line, area, pie or scatter chart the user can edit later in the data
226
+ editor): a `.box.chart` with its data as a plain table, first row the categories, each later row a series:
227
+ ```html
228
+ <div class="box chart" id="s5-sales" data-chart="column" data-labels style="left: 160px; top: 260px; width: 1600px; height: 720px;"><div class="content"></div><table class="chart-data">
229
+ <tr><th></th><th>Q1</th><th>Q2</th><th>Q3</th></tr>
230
+ <tr><th>North</th><td>17</td><td>26</td><td>53</td></tr>
231
+ <tr><th>South</th><td>55</td><td>43</td><td>70</td></tr>
232
+ </table></div>
233
+ ```
234
+ Options are in docs/deck-format.md (Charts); to build it a series at a time, add `fragment fade-in` to its classes and
235
+ `data-build="series"` (or `set`, `element-series`, `element-set`, `background`; a pie's `wedge`). A plot made from data files or many points belongs to a script figure
236
+ (`glideshow figure`), not a chart.
237
+
202
238
  **A table that doesn't fit.** A `.box.table` grows with its rows. Imported tables carry Google's cell padding
203
239
  (`padding: 19.2px` on each `<td>`) and font sizes: to make one fit, lower the padding on its cells (a rule in the slide's
204
240
  own `<style class="slide-css" media="not all">`, such as `.box.table td { padding: 6px 12px; }`, does every cell at once)
@@ -217,6 +253,11 @@ Mark the claim with a short `[1]` in the text if there are several.
217
253
  get today's).
218
254
  - Every box has a unique `id` (`check` reports any without one); ids starting with `ed-` are reserved for the editor.
219
255
  - Don't write SVG or `--tx-*` / `--sh*` style variables: they are derived when the deck loads.
256
+ - A link to another slide: `<a href="#slide=<that section's data-sid>">` (or `data-link` on a picture's box), not
257
+ `#/3`, so it survives reordering; `#next`, `#previous`, `#first`, `#last` and `#exit` also work (deck-format.md).
258
+ - Slide numbers: `data-slide-numbers="on"` on `.slides` (never type numbers into a box; they'd go stale).
259
+ - Builds that play by themselves: `data-start="after"` (or `"transition"` on a slide's first build), with `data-delay` /
260
+ `data-duration` in seconds; see deck-format.md for `data-ease`.
220
261
  - The editor commits every save to git when the deck is in a repo, so `git log` / `git diff` show what the person
221
262
  changed. Don't commit yourself.
222
263
  - Your own test library, apart from the user's: `GLIDESHOW_HOME=/tmp/x/decks GLIDESHOW_CACHE=/tmp/x/cache
@@ -0,0 +1,207 @@
1
+ ## Deck format
2
+
3
+ The deck's name is its `<title>` and its date (the talk's or meeting's) is `<meta name="date" content="YYYY-MM-DD">` in
4
+ the `<head>`; keep the date out of the title (the home screen shows and sorts by the date).
5
+
6
+ `deck.html` is a normal reveal.js file. Every slide is a 1920×1080 `<section>` whose children are absolutely positioned
7
+ boxes:
8
+
9
+ ```html
10
+ <section data-background-color="#ffffff">
11
+ <div class="box" id="fits-title" style="left: 65px; top: 93px; width: 1790px; height: 122px;">
12
+ <div class="content" style="font-size: 74.67px;">Model fits</div>
13
+ </div>
14
+ <div class="box image" id="fits-fig" style="left: 120px; top: 260px; width: 900px; height: 700px;">
15
+ <div class="content"><img src="assets/fits.png" alt="Model fits per condition"></div>
16
+ </div>
17
+ <div class="box" id="callout" data-shape="roundrect" style="left: 1100px; top: 300px; width: 700px; height: 300px; --fill: #fce8e6; --stroke: #d93025; --sw: 2px;">
18
+ <div class="content" style="font-size: 36px;">Fit is worse for <i>β</i> &gt; 0.5</div>
19
+ </div>
20
+ <aside class="notes">Speaker notes go here.</aside>
21
+ </section>
22
+ ```
23
+
24
+ Rules:
25
+ - Geometry lives only in a box's inline `left/top/width/height` (in px on the 1920×1080 canvas). Rotation is an inline `transform: rotate(Ndeg)` about the box centre (absent when 0).
26
+ - Inside `.content`, anything goes: HTML, lists, `<pre><code>` blocks, tables, iframes.
27
+ - Guides: `data-guides="v960 h540"` on the `.slides` element (vertical/horizontal, px); editor only, never drawn when presenting.
28
+ - Slide numbers (Insert → Slide numbers): `data-slide-numbers="on"` (or `"skip-title"`: not on title-layout slides) on `.slides`; a slide's `data-slide-number="on" | "off"` overrides it. Skipped slides aren't counted. shapes.js draws each number (bottom right); it is never saved.
29
+ - Audio (Insert → Audio): `<div class="box audio" …><div class="content"><svg class="audio-icon" …>…</svg><audio src="assets/….mp3" preload="metadata"></audio></div></div>`; a click on it plays / pauses in the slideshow. On the `<audio>`: `data-autoplay` (plays when its slide shows), `loop`, `data-ignore` (keeps playing across slides); `data-hide-icon` on the box hides the icon while presenting. A soundtrack for the whole show: `data-soundtrack="assets/….mp3"` (and `data-soundtrack-loop`) on `.slides`.
30
+ - A recorded slideshow (Slideshow ▾ → Record slideshow): `data-narration="assets/….weba"` and `data-narration-steps="0:0.0.-1 4.2:1.0.-1 …"` (seconds:h.v.fragment) on `.slides`; with `data-play="recorded"` the show plays the narration and follows the steps.
31
+ - Slideshow settings (Slideshow ▾ → Slideshow settings), on `.slides`: `data-play="self"` plays by itself (`data-play-build` seconds between builds, default 2; `data-play-slide` seconds before the next slide, default 5) or `data-play="links"` (only links move the show); `data-loop` goes back to the first slide after the last; `data-idle="5"` returns to the first slide after that many minutes without a key or click.
32
+ - Cropped pictures: `data-crop="top right bottom left"` on a `.box.image`, each the share (0–1) of the picture cut off that side; the box is the visible part.
33
+ - Remove background writes a new PNG beside the original in `assets/` and keeps the original's path in `data-original` on the `.box.image` (Reset image goes back to it); the frame is trimmed with `data-crop`.
34
+ - Printing: `deck.html?print-pdf` lays one slide per page, with a Print preview bar on screen; add `&notes` (each slide's notes under it), `&builds` (a page per build stage), `&skipped` (skipped slides too; served by `glideshow` only, as reveal drops them on load) and `&nobg` (white backgrounds).
35
+ - Masked pictures (Mask image): `data-mask="<shape>"` on a `.box.image` (any `data-shape` preset: `ellipse`, `roundrect`, `star`, `heart` …) clips the box to that shape; with `data-crop` the picture can be larger than the frame and offset in it.
36
+ - Videos: `<div class="box video" style="…"><div class="content"><video src="assets/clip.mp4" poster="assets/clip.jpg" controls playsinline></video></div></div>`;
37
+ add `data-autoplay` (reveal.js plays it when the slide appears), `muted` and/or `loop` on the `<video>`. Trim with a media fragment: `src="assets/clip.mp4#t=5,12"`.
38
+ - Groups: `<div class="box group" style="left/top/width/height">` holding member `.box`es whose geometry is relative to the group's top-left; the group can be rotated/flipped like any box and has no `.content`. Groups can nest. The editor keeps a group's box hugging its members.
39
+ - Theme: a block in `<style id="deck-theme">` between `/* slides-theme {…} */` and `/* end slides-theme */` (written by the Themes panel) sets the deck font, text and title colours, and `--th-title-size` / `--th-body-size`; text without its own font or colour inherits it, layout placeholders size from it, and the rest of that style is left as you wrote it.
40
+ - Paragraph styles (Format options → Paragraph style): a class on a paragraph (`<p class="ps-heading">`, or on `.content` when the text isn't in paragraphs): `ps-title`, `ps-subtitle`, `ps-heading`, `ps-body`, `ps-caption`, `ps-quote`. Their rules are a block in `#deck-theme` between `/* text-styles */` and `/* end text-styles */`, one `.slides .ps-… { … }` each (sizes from `--th-title-size` / `--th-body-size` until Update to match rewrites them); a run's own inline style still wins.
41
+ - Edited layouts live in the theme JSON (`"layouts": {"title-body": [[role, x, y, w, h, pt, valign, align], …]}`, sizes relative to 28 / 18 pt titles / bodies).
42
+ - Layouts and placeholders: `data-layout` on a `<section>` names its layout (title, section, title-body, two-columns, title-only, one-column,
43
+ main-point, section-description, caption, big-number, blank); a box with `data-placeholder="Click to add title"` (and `data-role` title / sub / body)
44
+ shows that prompt in the editor while empty, survives being left empty, and is hidden in the deck until it has text.
45
+ - **Layouts with their own objects (master slides, D5).** Layouts that hold objects and a background, shown on every
46
+ slide that uses them and edited in one place (Keynote's master slides, Slides' layouts).
47
+ - *Where they live:* one `<template id="deck-layouts">` in `<body>`, after `.reveal` (never shown; reveal.js never sees
48
+ it, so a deck without one plays as before). In it one `<div class="layout" data-layout="title-body" data-name="Title and
49
+ body">` per layout, with `data-background-color` / `-image` / `-size` and its boxes: ordinary boxes (a logo, a footer,
50
+ a rule) and placeholder boxes (`data-placeholder`, `data-role`) that give a placeholder's place and size. A layout
51
+ whose name isn't one of the eleven built-in ones is a layout of the deck's own.
52
+ - *How a slide uses one:* by its `data-layout`. The slide shows copies of the layout's ordinary boxes behind its own
53
+ (`data-from-layout="<the layout box's id>"`), and the layout's background when the slide sets none
54
+ (`data-bg-from-layout` marks one it got that way). `data-layout-objects="off"` on a slide leaves the objects off it.
55
+ - *Copies:* each copy's id (and its inner ids) becomes `<id>--<the slide's data-sid>`, unique per slide; a copied line's
56
+ `data-from` / `data-to` pointing at another box of the layout is re-pointed at that box's copy on the same slide. Copies
57
+ take no builds or links (their `fragment`, `data-builds`, `data-link` and text links are dropped).
58
+ - *Placeholders stay on the slide:* each slide keeps its own placeholder boxes with inline place and size, so slides
59
+ play and import without the template; Edit theme's "Reapply to all" re-lays a layout's slides.
60
+ - *Who draws them:* shapes.js `applyLayouts()` as the deck loads (so the slideshow, PDF, images, movie and presenter
61
+ view have them), and the editor on its slides, which drops the copies (and borrowed backgrounds) when saving.
62
+ - *Editing:* Slide → Edit theme: put objects and a background on any layout; **Add to all layouts** copies the
63
+ selected objects onto every layout; New layout, Duplicate, Rename and Delete (the deck's own layouts).
64
+ - *Imports and exports:* Google Slides and PowerPoint imports put each used layout's own shapes and its master's (not
65
+ placeholders) into the template, keyed `g-…` / `p-…` (the same layout imported again reuses its key), and the
66
+ slides name it; a layout with nothing of its own isn't written. PowerPoint export draws a layout's objects on each of
67
+ its slides, behind the slide's own, named alike so Morph keeps them still.
68
+ - *Agents:* to put a logo or footer on every slide of a layout, add the box to that layout in `#deck-layouts`, not to
69
+ each slide.
70
+ - Slide ids: the editor gives each `<section>` a `data-sid` (how edits made in two places merge, slide by slide). Keep it when you change a slide; a slide you add may leave it out (it gets one), but never copy one slide's `data-sid` onto another.
71
+ - Slide groups (Keynote's indented slides, editor-only): `data-group="1"`…`"5"` on a main `<section>` is its indent; its group is the slides after it indented further. `data-group-folded` on a group's top folds it in the filmstrip. The slideshow ignores both; skipping, moving or deleting a folded group in the editor takes all of it (skipped slides get their own `data-visibility="hidden"`). Separate from vertical stacks.
72
+ - Vertical stacks (reveal.js sub-slides): wrap slides in an outer `<section>`: `<section><section>main</section><section>below</section></section>`.
73
+ The first is the main slide, the rest sit below it (presenting: ↓ / ↑). Numbering follows reveal: main slide 3, its sub-slides 3.1, 3.2
74
+ (filmstrip, the slideshow pill and its slide picker). The filmstrip draws a stack as a tree (sub-slides smaller, under a rail; ▾ / ▸ on the
75
+ main slide folds them, showing "+2", remembered per deck); grid view keeps one cell per main slide, with a stack's sub-slides in that cell under it.
76
+
77
+ - Slide background (Slide → Change background): `data-background-color` on the `<section>`, plus optional `data-background-image="assets/….png"` (any URL or data URI), drawn to cover the slide unless `data-background-size` says otherwise (`contain`, `100% 100%`, `auto`; `data-background-repeat="repeat"` tiles), with `data-background-opacity` (0–1, the picture over the colour) and `data-background-tint` (`#rrggbbaa` laid over it). Or `data-background-gradient` = a CSS `linear-gradient(<deg>deg, #a, #b)` / `radial-gradient(circle, #a, #b)` (keep `data-background-color` as its first colour).
78
+ - Shapes: `data-shape` = `rect | roundrect | ellipse | triangle | diamond | parallelogram | trapezoid | pentagon | hexagon | octagon | star | plus | minus | multiply | divide | equal | notequal | brackets | braces | leftbracket | rightbracket | leftbrace | rightbrace | cloud | arrow | leftarrow | uparrow | downarrow | leftrightarrow | updownarrow | quadarrow | bentarrow | uturnarrow | notchedarrow | curvedarrow | curvedleftarrow | curveduparrow | curveddownarrow | leftrightuparrow | leftuparrow | bentuparrow | stripedarrow | homeplate | rightarrowcallout | leftarrowcallout | uparrowcallout | downarrowcallout | leftrightarrowcallout | quadarrowcallout | circulararrow | chevron | callout | roundcallout | ovalcallout | cloudcallout | explosion1 | explosion2 | star4 | star6 | star7 | star8 | star10 | star12 | star16 | star24 | star32 | ribbonup | ribbon | curvedribbonup | curvedribbon | vscroll | hscroll | wave | doublewave | snip1rect | snip2samerect | snip2diagrect | sniproundrect | round1rect | round2samerect | round2diagrect | rttriangle | heptagon | decagon | dodecagon | pie | arc | teardrop | chord | frame | halfframe | corner | diagstripe | cross | plaque | can | cube | bevel | donut | nosmoking | blockarc | foldedcorner | smileyface | heart | lightningbolt | sun | moon | flow-process … flow-display (Slides' 28 flowchart shapes: flow- + the OOXML name after flowChart, lowercased) | wordart | line | arrowline | elbow | curve`,
79
+ styled with `--fill`, `--stroke`, `--sw`; `data-dash="dot|dash|dashdot|longdash|longdashdot"` dashes the outline (text boxes too). Lines take
80
+ `data-start` / `data-end` = `none | arrow | fill-arrow | circle | square | diamond | open-arrow | open-circle | open-square | open-diamond | barb-arrow | tail-arrow | bar`
81
+ (`barb-arrow` has a notched back, `tail-arrow` is a small arrow pointing back into the line, `bar` a stroke across its end; `arrowline` is a line whose end defaults to `fill-arrow`).
82
+ `data-brush` on a line or open drawing draws its stroke as a brush: `speckled | rounded | tapered | rough | swelled` (Keynote's five brushes, named by their look; instead of `data-dash`; Line Draw draws it in and out as its brush). Ends grow with the line weight as in Slides (an arrow is ~9.4× the weight long).
83
+ `elbow` and `curve` are connectors drawn corner to corner like lines,
84
+ leaving and arriving horizontally (a right-angle step, or an S-curve).
85
+ `wordart` draws the box's text as one line of letters stretched to the box (fill `--fill`, outline `--stroke` / `--sw`; font from `.content`).
86
+ A line, arrow or connector can be attached to shapes with `data-from` / `data-to` = `"boxId:t|r|b|l"` (the middle of a side; `from` is
87
+ the left end): whenever the deck loads (editor and slideshow), an attached line is fitted to those points, so it follows the shapes
88
+ however they moved; with one end attached the whole line moves with that shape. The editor writes the fitted geometry back.
89
+ An elbow connector (`data-shape="elbow"`) routes from its ends' sides: two horizontal (l/r, or unattached) or two vertical (t/b) ends give
90
+ three segments with the middle one at `data-bend` (0..1 of the span, default 0.5; the yellow handle; right-click → Reroute connector resets it),
91
+ one of each gives a single bend, and two ends on the same side (`b` and `b`, or `r` and `r`, …) give a U that runs `data-out` px
92
+ (default 60; the yellow handle) past the further end: a feedback edge. A curved connector (`curve`) routes the same way, its S bending at `data-bend` (handle on the curve); with one bend it is OOXML's curvedConnector2, a gentle arc whose controls sit at the midpoints of the two legs.
93
+ `data-shape="path"` with `data-path="M0,0 C… L…"` is a freehand stroke (Curve, Polyline, Scribble): SVG path commands in 0..1 units of the box, so it scales with it. Ending it with ` Z` closes it into a filled shape (`--fill`, Close path); Edit points changes it in place. Several `M … Z` outlines in one path (Arrange → Combine shapes) fill even-odd, so inner outlines are holes.
94
+ Shapes are drawn from the OOXML preset geometry Google Slides uses (ECMA-376 `presetShapeDefinitions`), including where text sits.
95
+ `data-adj`, `data-adj2`, … are the preset's adj1, adj2, … scaled 0–1 (e.g. `arrow` = rightArrow: adj1 shaft thickness of the height,
96
+ adj2 head length of the shorter side, both 0.5 by default; `roundrect`: corner radius of the shorter side, 0.1667); the editor's yellow handles set them.
97
+ Outlines are centred on the shape's edge, as in Slides. Lines run corner to corner (`data-dir="up"` flips them);
98
+ height 0 gives a horizontal line. Don't write SVG; it's drawn at load time.
99
+ - Line spacing: the editor's Single / 1.15 / 1.5 / Double mean what they mean in Slides (multiples of the font's natural line height, about 1.15), written as CSS `line-height` 1.15 / 1.323 / 1.725 / 2.3; a `.content` without one keeps 1.3.
100
+ - Page size: `<div class="slides" data-size="1920x1440">` makes every slide 1920 × 1440 (4:3; 192 px = 1 in); absent means 1920 × 1080. shapes.js sizes the sections and tells reveal.js. File → Page setup sets it (16:9, 16:10, 4:3 or W x H inches); objects keep their positions.
101
+ - Fonts: `font-family` on `.content` (or a `<span>`), e.g. `style="font-family: 'Roboto Slab', serif"`. A Google Fonts family also needs
102
+ `<link id="deck-fonts" rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Roboto+Slab:wght@400;700&display=swap">` in the head
103
+ (one link, `family=` repeated per font); the font menu writes it. Web-safe fonts (Arial, Georgia, …) need nothing.
104
+ A font file of your own goes in `assets/fonts/` with an `@font-face` in `#deck-theme`, e.g.
105
+ `@font-face { font-family: 'My Font'; src: url('assets/fonts/My-Font.ttf'); font-display: swap; }`; the font menu's Your fonts tab
106
+ (fonts in `~/Library/Fonts` and `/Library/Fonts`) and dropping a .ttf / .otf / .woff / .woff2 on the slide write both.
107
+ - Equations: LaTeX between `$…$` / `\(…\)` (inline) or `$$…$$` / `\[…\]` (display) in any box is typeset by KaTeX (shapes.js loads it
108
+ from jsDelivr; `--build` vendors it). The deck keeps the source; Insert → Equation adds one, double-click shows the source to edit.
109
+ - Tables: `<div class="box table" data-valign="top" …><div class="content" style="padding: 0px"><table><tr><td>…</td></tr></table></div></div>`;
110
+ the table fills the box with 1pt black rules (base.css). Insert → Table picks the size; while editing, Tab / ⇧Tab move between
111
+ cells (Tab in the last cell adds a row) and right-click inserts or deletes rows and columns. Merged cells are plain `colspan` /
112
+ `rowspan`; a cell's fill is an inline `background-color` on the `<td>`, its borders inline `border-<side>-color / -width / -style`.
113
+ On the table box (Format options → Table): `data-table-style` = `light | blue | dark | minimal`, `data-header-rows` / `data-header-cols` (those cells are `<th>`), `data-footer-rows` (1–3, bold with a heavier rule above), `data-alt-rows` with `--alt-row: #rrggbb` (every second row's fill).
114
+ - Charts: `<div class="box chart" data-chart="column" …><div class="content"></div><table class="chart-data"><tr><th></th><th>Q1</th><th>Q2</th></tr><tr><th>North</th><td>17</td><td>26</td></tr></table></div>`.
115
+ The table is the data, hidden (base.css): its first row holds the categories, each later row a series' name and then its
116
+ values (blank cells are gaps). shapes.js draws it as `svg.chart` (never saved), in the editor and the slideshow alike.
117
+ `data-chart` = `column | bar | line | area | pie | scatter`; `data-plot="columns"` makes the columns the series instead of
118
+ the rows; a pie's wedges are the series, each its first value; a scatter's categories are the x values (numbers). Options:
119
+ `data-title="…"`, `data-legend="off"`, `data-labels` (value labels), `data-grid="off"`, `data-palette="1"` (the second colour
120
+ set; 0 is the default), `--chart-fs` (text size in px; by default from the box's size). Insert → Chart adds one;
121
+ double-click, ⇧⌘D or Format options → Chart → Edit chart data opens the data editor. Each series is a `g.series[data-s]`,
122
+ each mark an `.el[data-i]`. PowerPoint export writes a PowerPoint chart.
123
+ An animated chart's `data-build` (Keynote's Delivery for charts): `background` (Background first: axes, gridlines,
124
+ legend and title on one click, the data on the next), `series`, `set` (a category across the series), `element-series`,
125
+ `element-set`; a pie's `wedge`. Each starts with a click for the axes, then one per piece (a pie's legend and title come
126
+ with its first wedge); `data-build-from` / `data-build-to` take the pieces as for text.
127
+ - Web embeds: `<div class="box embed" …><div class="content"><iframe src="https://…" allowfullscreen></iframe></div></div>` shows a live page
128
+ (or a YouTube / Vimeo player: Insert → Video → By URL converts the page address). The editor shows it but doesn't pass clicks
129
+ to it; the slideshow does.
130
+ - Live boxes (Insert → Live box): a box running a page of its own, for a demo, a simulation or a plot you can hover:
131
+ `<div class="box live" …><div class="content"><iframe src="assets/demo.html" sandbox="allow-scripts allow-pointer-lock"></iframe></div></div>`.
132
+ Write the page as an ordinary HTML file in `assets/` (its own `<script>`, sized to the window: the box's size). It runs in a
133
+ sandbox: it can draw, animate and take the mouse and keys, and load things from the web, but can't reach the deck, other
134
+ files or anyone's accounts. Keep the `sandbox` attribute exactly so. The editor shows it running; double-clicking it edits the page.
135
+ - Code: `<pre><code class="language-python">…</code></pre>` inside a box's `.content` is coloured by highlight.js (reveal.js' highlight
136
+ plugin, loaded by shapes.js beside reveal.js); any highlight.js language name works. Insert → Code block picks the language,
137
+ right-click → Code language changes it; Enter inside a code block adds a line.
138
+ - Icons: `<div class="box" data-shape="icon" data-icon="lightbulb" style="…; --fill: #202124">` draws that Material Symbols glyph
139
+ (any name from fonts.google.com/icons) filling the box, coloured by `--fill`. The deck needs
140
+ `<link id="deck-icons" rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@24,400,0,0&icon_names=lightbulb&display=block">`
141
+ (every icon used, comma-separated and sorted); Insert → Icon writes both.
142
+ - Letter spacing: CSS `letter-spacing` on `.content` (whole box) or on a `<span>` (selected text); Format → Text → Letter spacing writes -0.05em / -0.025em / normal / 0.05em / 0.1em. Capitalization (lowercase / UPPERCASE / Title Case) rewrites the characters themselves, so it needs no attribute.
143
+ - Indents (Format → Align & indent → Indentation options): `--ind-left`, `--ind-right`, `--ind-first` (px) on `.content` become each paragraph's left / right margin and first-line indent; a hanging indent is a negative `--ind-first` with `--ind-left` that much larger.
144
+ - Tab stops (click the ruler while editing; ⌥Tab types a tab): `data-tabs="192l 576c 960r 1152d"` on `.content` (canvas px from the text's left edge; l / c / r / d = left, center, right, decimal); past the last stop, tabs fall every 0.5 in. The text keeps plain tab characters.
145
+ - Text effects: `data-text-gradient="linear 90 #a #b"` on a box runs a gradient through its text (Text colour → Gradient); `-webkit-text-stroke: 2px #000; paint-order: stroke fill` on `.content` outlines it (Format options → Text outline); `--u-style: double | dotted | dashed | wavy` or `--u-weight: 0.12em` (thick) on `.content` style its underlines (Format → Text → Underline style); `data-dropcap` on the box makes the first letter a drop cap.
146
+ - Paragraph spacing: `--space-before` / `--space-after` on `.content` (e.g. `style="--space-after: 26.67px"`, 10pt) become top / bottom margins on each paragraph (every block child and list item). The line-spacing menu's Add / Remove space before / after paragraph toggles 10pt.
147
+ - Text insets: the editor writes Slides' defaults on new boxes (`padding: 9.6px 19.2px` on `.content`, i.e. 0.05 in / 0.1 in); a `.content` without its own padding gets 8 px / 16 px.
148
+ - Gradient fill (Fill colour → Gradient): `data-gradient="linear <deg> #from #to"` (CSS angles: 90 runs left to right, 180 top to bottom; more colours allowed) or `"radial #inner #outer"` on a shape or text box; keep `--fill` as its first colour (what other programs fall back to).
149
+ - Image fill (Fill colour → Image): `data-fill-image="assets/….png"` on a shape or text box, with `data-fill-scale` = `fit | stretch | tile | original` (left out: scale to fill). It takes the place of the colour or gradient.
150
+ - Text boxes can also take `--fill`, `--stroke`, `--sw`. A translucent fill is just a colour with alpha (`--fill: #4a86e880`); an image's opacity is an inline `opacity` on its `<img>`. `data-valign="top|bottom"` changes vertical alignment.
151
+ - Optional box attributes (Format → Format options): `data-flip="h|v|hv"` mirrors the box (text stays readable),
152
+ `data-shadow` adds a drop shadow (tuned by `data-shadow-color` `#rrggbb`, `-opacity` 0–1, `-angle` deg with 90 = down, `-distance` and `-blur` px; defaults #000000, 0.3, 90, 6, 8), `data-lock="aspect"` keeps proportions when resizing, `data-locked` (Arrange → Lock) keeps the box from being moved, resized, edited or deleted in the editor (the slideshow ignores it),
153
+ `data-autofit="none|shrink|resize"`: no fitting, shrink the text to fit the box (done at load by `#deck-shapes`), or grow the box to fit the text (the default for text boxes).
154
+ - Text boxes grow to fit their text while editing. `data-minh` is the height the user dragged; the editor never shrinks a box below it (optional; defaults to the current height).
155
+ - Links (Insert → Link): text links are `<a href>`; a picture, line, drawing or group links as a whole with `data-link` on its `.box`. Either takes a URL, `mailto:` / `tel:`, or a slideshow target: `#next`, `#previous`, `#first`, `#last` (slides, not builds), `#slide=<data-sid>` (that slide, wherever it moves), `#exit` (end the slideshow). present.js follows them; a click on a link doesn't also advance.
156
+ - Slide transitions: `data-transition` on the `<section>` = `fade` (Dissolve) | `fade-in fade-out` (Fade) | `slide` | `sl-left` | `sl-up` / `sl-down` (slide from bottom / top: Keynote's Push) | `convex` (Flip) | `concave` (Cube) | `zoom` (Gallery) | `wipe-r` / `wipe-l` / `wipe-d` / `wipe-u` (Wipe from left / right / top / bottom) | `move-in-r` / `-l` / `-d` / `-u` (Move in from left … : the new slide moves over the old one) | `iris` (opens from a circle in the middle) | `reveal-l` (Reveal: the old slide moves off to the left) | Keynote's others: `fade-black` (through black), `fade-move`, `drop` (falls in and bounces), `clothesline`, `fall` (the old slide tips towards you), `pivot` (swings in about its top-left corner), `revolving-door`, `k-scale` (Scale), `swap`, `twirl`, and `obj-pop` / `obj-zoom` / `obj-push` / `obj-flip` / `obj-revolve` / `obj-cube` (each slide's boxes leave and arrive while the background changes), `doorway`, `flop`, `mosaic`, `blinds`, `page-flip`, `grid`, `color-planes`, `reflection`, `twist` (played in pieces of the slides by present.js), `switch`, `perspective`, `droplet`, `swoosh`, `swing`, and `sparkle`, `shimmer`, `confetti` (with particles), optional `data-transition-speed="slow|fast"`. One transition plays between two slides, the later slide's, going forward or back (present.js gives it to both; reveal.js alone would also play the earlier slide's own on its way out).
157
+ - Morph (Keynote's Magic Move, reveal's Auto-Animate): `data-auto-animate` on a `<section>` (instead of `data-transition`)
158
+ animates from the slide before: each box that has a partner there moves, resizes and recolours into place, and the rest fade.
159
+ A box's partner is the box on the slide before whose `id` equals this box's `data-morph` (or, without one, its own `id`);
160
+ ids stay unique in the deck, so a copied box gets a new id and `data-morph` names the one it continues. In the editor:
161
+ duplicate the slide (Duplicate slide sets `data-morph` on each copy), change the copy, and give it `data-auto-animate`. By
162
+ hand, see "Building a diagram up step by step" above. Optional `data-auto-animate-duration` in seconds (0.5 fast, 1.4 slow).
163
+ Keynote's options, also optional: `data-auto-animate-unmatched="false"` (boxes with no partner appear at once instead of
164
+ fading in), `data-auto-animate-easing` (a CSS easing: `linear`, `ease-in`, `ease-out`, `ease-in-out`; `ease` by default)
165
+ and `data-morph-match="word|character"`: within each pair of boxes, each word (or letter) that is in both moves from where
166
+ it was to where it is, so reworded text rearranges itself; new words fade in. A box's text always moves with its box.
167
+ - Object animations are reveal fragments on the box: `class="box fragment fade-in"` (or `appear`, `fade-right` = fly in from left, `fade-left`, `fade-up`, `fade-down`, `zoom-in`), ordered by `data-fragment-index` (`data-with-previous` marks one that plays with the one before it, sharing its index). Alt text: the image's `alt`, or `aria-label` on the box.
168
+ - Image adjustments (Format options → Adjustments): `data-adjust="exposure:20 saturation:-30"` on an image's box, any of `exposure saturation contrast highlights shadows sharpness temperature tint`, each -100…100 (0 = left out). shapes.js draws them as an SVG filter on the `<img>` (derived, never saved).
169
+ More effects: `fade-out`, `semi-fade-out` (dim), `fade-in-then-out`, `fade-in-then-semi-out` (fade in, then dim when the
170
+ next step comes: walking through bullets), `highlight-current-blue` (coloured while it is the current step), `grow`, `shrink`,
171
+ `strike`. Keynote's: `wipe-r` / `wipe-l` / `wipe-d` / `wipe-u` (Wipe from left / right / top / bottom), `move-in-r` / `-l` / `-d` /
172
+ `-u` (Move in from off the slide, from the left …), `scale-in` (Scale), `pop-in` (Pop); actions on a box already shown: `spin`
173
+ (Rotate by `data-angle` degrees, 360 by default, negative counterclockwise; it stays turned), `pulse`, `pop` (Pop emphasis).
174
+ More effects: in `blur-in`, `drop-in` (falls in and bounces), `spin-in` (Twirl: a flat spin), `iris-in`, `scale-big` (from three times its size),
175
+ `drift-in`, `drift-scale`, `fade-move-in`, `fade-scale`, `blinds-in` (seven slats), `cube-in`, `flip-in`, `spin-y` (Spin: a turn about
176
+ the vertical axis), `pivot-in`, `twist-scale`, `squish-in` (falls and bounces, squashing), and Keynote's special effects `sparkle-in`, `shimmer-in`, `confetti-in`, `flash-in`, `fireworks-in`, `trace-in`, `lens-in`, `flame-in`, `anvil-in` (falls and lands in dust), `blast-in` (shards fly together), `comet-in` (loops in from the right with a trail) (present.js draws their particles and shards), `skid-in`, `swoosh-in`, `bouncy-in` (springs up on a slant, squashes and settles);
177
+ out `disappear`, `blur-out`, `scale-out`, `iris-out`.
178
+ `move-to` (Move): the box moves to `data-to="x,y"` (its top-left there, in slide px; moving the box keeps the end) and
179
+ stays; a straight path, or a curve with `data-via="x,y"` (the box's top-left halfway: the path is the quadratic Bézier
180
+ through it). `data-align="path"` (Align to path) turns the box with the path's direction, from no turn at the start.
181
+ More builds on the same box (Keynote's Add Action): `data-builds="effect key=value …; …"`, each a step of its own after the
182
+ box's own build (`start=with` shares the step before, `start=after` starts by itself), e.g. `"move-to to=1400,600; spin
183
+ angle=90 start=after; fade-out"`; keys `to` (a Move's end, its top-left; it goes on from where the box last stopped),
184
+ `angle`, `duration`, `delay`, `ease`, `start`. present.js wraps the box in one element per build so they compose.
185
+ On a line or an open drawing: `line-draw` (drawn along its stroke) and `line-erase` (undrawn from its start); a
186
+ dashed line just appears. `data-direction="end"` (end to start) or `"middle"` (middle to ends); the arrowheads ride the
187
+ drawn ends unless `data-endpoints="off"`, which shows them once it's drawn.
188
+ `data-build="paragraph"` on an animated text box makes each paragraph or bullet (sub-bullets too) its own step with the box's effect; `"highlight"` the same, dimming the ones already shown; `"word"` / `"character"` keep one step, the text arriving a word or letter at a time over `data-duration` (default 1 s; in-effects only). `"group"`: a bullet with its sub-bullets per step. `data-build-from` / `data-build-to` (paragraph numbers) build only that range, the ones before coming with the first step and after with the last. `data-text-order` on a word / character build: `backward`, `center`, `edges` or `random`. On a table box `"row"`, `"column"` or `"cell"` makes each its own step; add `-content` (the grid stays, only the cells' contents build) and / or `-reverse` (last first), e.g. `"row-content-reverse"`.
189
+ Timing (Motion panel): `data-start="after"` plays a build by itself once the one before has finished, `data-start="transition"` (the slide's first build) once the slide's transition has; `data-duration` and `data-delay` in seconds (defaults reveal's ~0.5 s and 0); `data-ease` = `linear | ease-in | ease-out | ease-in-out`.
190
+ Pictures that replace each other in one place (a plot that updates): stack them at the same position, the first
191
+ `fade-out` with the second's step, each middle one `fade-in-then-out`, the last `fade-in` (Arrange → Stack as a sequence).
192
+ - Backgrounds beyond colour and image (reveal's own): `data-background-video="assets/clip.mp4"` (with `data-background-video-loop`
193
+ and `data-background-video-muted`) or `data-background-iframe="https://…"` (a live web page) on the `<section>`.
194
+ - A slide's own style: `<style class="slide-css" media="not all">` inside the `<section>`, holding ordinary CSS rules
195
+ (`.box > .content { … }`) that apply to that slide only. `media="not all"` keeps it from applying to anything else; the
196
+ editor and the slideshow scope it to its slide (Slide → Slide style).
197
+ - Any deck reads as a scrolling page with `?view=scroll` (reveal's scroll view; narrow phone screens get it by themselves).
198
+ - Figures: a `.box.image` may carry `data-source` (the command that makes its picture; `{out}` is the picture's path, run in the
199
+ deck's folder by `glideshow figure refresh`) and `data-made` (when it was last made, ISO time). Both are optional. In the
200
+ editor, Format options → Figure edits the command and Refresh runs it. Running needs your approval on this machine,
201
+ which holds a hash of the command and of the code files it runs: after that the server runs it again whenever a file it
202
+ names changes; if its script (or other code) changed, it waits until the editor has asked again.
203
+ - Give every box a unique `id`. Ids starting with `ed-` are reserved for the editor's own interface. Put deck-specific CSS in `<style id="deck-theme">`.
204
+ - Don't edit `#deck-base`, `#deck-shapes` or `#deck-present`. The editor overwrites them from `editor/` on every save.
205
+
206
+ The editor watches `deck.html`: edits made on disk appear within a second. Saves are tied to the
207
+ file's mtime, so if the file changed underneath the editor, it reloads instead of overwriting it.
@@ -13,6 +13,7 @@ A new binding needs a row in both.
13
13
 
14
14
  - [x] ⌘M new slide (next main slide); ⇧⌘M new sub-slide below (reveal vertical stack; not in Slides) — ⌘M and Slides' Mac key ⌃M (verified: Google lists Ctrl+M); ⇧⌘M adds a sub-slide under the current slide
15
15
  - [x] ⌘D duplicate (selected objects; selected slide when the filmstrip has focus)
16
+ - [x] ⇧⌘D Chart data editor when a chart is selected (Keynote's key; Slides has none)
16
17
  - [x] ⌘Z / ⇧⌘Z undo / redo (⌘Y also redo)
17
18
  - [x] ⌘C ⌘X ⌘V copy cut paste; ⇧⌘V paste without formatting (⌘V into text now keeps formatting, cleaned)
18
19
  - [x] ⌥⌘C / ⌥⌘V copy / paste formatting (paint format)
@@ -68,6 +69,7 @@ Also ⌥⇧⌘F moves focus to the filmstrip (verified in Google's list).
68
69
  - [x] type a number then ⏎ to jump to that slide
69
70
  - [x] B black screen; W white screen (toggle)
70
71
  - [x] L laser pointer toggle (a red dot following the cursor)
72
+ - [x] Keynote's where Slides has none: ⇧→ / ⇧↓ next slide skipping its builds (no transition), ⇧← / ⇧↑ back one build, Z back through the slides as viewed, C hide / show the pointer, ? or / the keys, Q end the show; reveal's H / J / K are off
71
73
  - [x] S open speaker notes (presenter view in a new window, reveal's notes plugin)
72
74
  - [x] M note for the AI at the pointer (ours, not Slides': only when presenting from the editor; ⏎ keeps it on the slide as a `.box.note`, Esc drops it)
73
75
  - [ ] A toggle auto-advance *(verify)*; F toggle fullscreen (opt-in; presenting starts windowed, see BACKLOG) — F done (reveal's own, works in the in-place frame too); A not yet verified/implemented