keyframes 0.1.0__py3-none-any.whl

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 (77) hide show
  1. keyframes/__init__.py +1 -0
  2. keyframes/build.py +102 -0
  3. keyframes/check.py +107 -0
  4. keyframes/cli.py +149 -0
  5. keyframes/deck.py +67 -0
  6. keyframes/export.py +106 -0
  7. keyframes/guide/__init__.py +25 -0
  8. keyframes/guide/components/keyframes-code.md +60 -0
  9. keyframes/guide/components/keyframes-excalidraw.md +148 -0
  10. keyframes/guide/components/keyframes-mujoco.md +76 -0
  11. keyframes/guide/components/keyframes-slider.md +65 -0
  12. keyframes/guide/components/keyframes-three.md +81 -0
  13. keyframes/guide/core.md +113 -0
  14. keyframes/patch.py +77 -0
  15. keyframes/render.py +32 -0
  16. keyframes/scaffold/.gitignore +3 -0
  17. keyframes/scaffold/CLAUDE.md +1 -0
  18. keyframes/scaffold/assets/deck.css +1 -0
  19. keyframes/scaffold/assets/figure.svg +1 -0
  20. keyframes/scaffold/deck.toml +10 -0
  21. keyframes/scaffold/references/.gitkeep +0 -0
  22. keyframes/scaffold/slides/01-title.html +4 -0
  23. keyframes/scaffold/slides/02-intro.html +17 -0
  24. keyframes/server.py +204 -0
  25. keyframes/static/components/keyframes-code.js +423 -0
  26. keyframes/static/components/keyframes-excalidraw.js +189 -0
  27. keyframes/static/components/keyframes-mujoco.js +515 -0
  28. keyframes/static/components/keyframes-slider.js +255 -0
  29. keyframes/static/components/keyframes-three.js +224 -0
  30. keyframes/static/editor/editor.css +228 -0
  31. keyframes/static/editor/editor.js +594 -0
  32. keyframes/static/editor/panel.css +127 -0
  33. keyframes/static/editor/panel.js +357 -0
  34. keyframes/static/index.html +23 -0
  35. keyframes/static/runtime/component.js +126 -0
  36. keyframes/static/runtime/live.js +31 -0
  37. keyframes/static/runtime/math.js +33 -0
  38. keyframes/static/runtime/presenter.js +91 -0
  39. keyframes/static/runtime/runtime.css +91 -0
  40. keyframes/static/runtime/runtime.js +215 -0
  41. keyframes/static/runtime/steps.js +15 -0
  42. keyframes/terminal.py +144 -0
  43. keyframes/themes/default/STYLE_GUIDE.md +73 -0
  44. keyframes/themes/default/assets/arc-top.webp +0 -0
  45. keyframes/themes/default/assets/orbit-ring.webp +0 -0
  46. keyframes/themes/default/assets/streaks-top.webp +0 -0
  47. keyframes/themes/default/assets/streaks-wide.webp +0 -0
  48. keyframes/themes/default/assets/sun-top.webp +0 -0
  49. keyframes/themes/default/assets/title-orbit.webp +0 -0
  50. keyframes/themes/default/layouts/content.html +16 -0
  51. keyframes/themes/default/layouts/figure.html +7 -0
  52. keyframes/themes/default/layouts/grid-2.html +19 -0
  53. keyframes/themes/default/layouts/grid-3x2.html +28 -0
  54. keyframes/themes/default/layouts/grid-4.html +44 -0
  55. keyframes/themes/default/layouts/media-side.html +16 -0
  56. keyframes/themes/default/layouts/section-arc.html +4 -0
  57. keyframes/themes/default/layouts/section-streaks.html +4 -0
  58. keyframes/themes/default/layouts/section.html +4 -0
  59. keyframes/themes/default/layouts/split-wide.html +7 -0
  60. keyframes/themes/default/layouts/split.html +13 -0
  61. keyframes/themes/default/layouts/statement.html +4 -0
  62. keyframes/themes/default/layouts/title.html +5 -0
  63. keyframes/themes/default/layouts/two-column.html +5 -0
  64. keyframes/themes/default/theme.css +330 -0
  65. keyframes/themes/minimal/STYLE_GUIDE.md +41 -0
  66. keyframes/themes/minimal/layouts/figure.html +7 -0
  67. keyframes/themes/minimal/layouts/section.html +4 -0
  68. keyframes/themes/minimal/layouts/statement.html +3 -0
  69. keyframes/themes/minimal/layouts/title-body.html +10 -0
  70. keyframes/themes/minimal/layouts/title.html +5 -0
  71. keyframes/themes/minimal/layouts/two-column.html +11 -0
  72. keyframes/themes/minimal/theme.css +196 -0
  73. keyframes/vendor.py +181 -0
  74. keyframes-0.1.0.dist-info/METADATA +119 -0
  75. keyframes-0.1.0.dist-info/RECORD +77 -0
  76. keyframes-0.1.0.dist-info/WHEEL +4 -0
  77. keyframes-0.1.0.dist-info/entry_points.txt +3 -0
@@ -0,0 +1,196 @@
1
+ @import url("../_vendor/fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;600&display=swap");
2
+
3
+ :root {
4
+ --bg: #ffffff;
5
+ --fg: #16181d;
6
+ --muted: #5d6470;
7
+ --accent: #2f6fed;
8
+ --accent-2: #f2994a;
9
+ --surface: #f4f6fa;
10
+ --border: #dfe3ea;
11
+ --font-sans: "Inter", system-ui, sans-serif;
12
+ --font-mono: "JetBrains Mono", ui-monospace, monospace;
13
+ --margin: 96px;
14
+ }
15
+
16
+ .keyframes-slide > section {
17
+ background: var(--bg);
18
+ color: var(--fg);
19
+ font-family: var(--font-sans);
20
+ font-size: 32px;
21
+ line-height: 1.4;
22
+ }
23
+
24
+ .keyframes-slide::after {
25
+ content: attr(data-number);
26
+ position: absolute;
27
+ right: 48px;
28
+ bottom: 32px;
29
+ font: 500 20px var(--font-sans);
30
+ color: var(--muted);
31
+ }
32
+
33
+ .keyframes-slide:has(> section[data-layout="title"])::after,
34
+ .keyframes-slide:has(> section[data-layout="section"])::after {
35
+ display: none;
36
+ }
37
+
38
+ /* Text roles */
39
+
40
+ .title {
41
+ margin: 0;
42
+ font-size: 64px;
43
+ font-weight: 700;
44
+ letter-spacing: -0.02em;
45
+ line-height: 1.1;
46
+ }
47
+
48
+ .subtitle {
49
+ margin: 0;
50
+ font-size: 36px;
51
+ font-weight: 400;
52
+ color: var(--muted);
53
+ }
54
+
55
+ .eyebrow {
56
+ margin: 0;
57
+ font-size: 22px;
58
+ font-weight: 600;
59
+ letter-spacing: 0.12em;
60
+ text-transform: uppercase;
61
+ color: var(--accent);
62
+ }
63
+
64
+ .body {
65
+ margin: 0;
66
+ }
67
+
68
+ .body ul,
69
+ ul.body {
70
+ margin: 0.15em 0 0.6em;
71
+ padding-left: 1.1em;
72
+ }
73
+
74
+ .body li + li {
75
+ margin-top: 0.45em;
76
+ }
77
+
78
+ .body li::marker {
79
+ color: var(--muted);
80
+ }
81
+
82
+ .lead {
83
+ margin: 0 0 0.5em;
84
+ font-size: 28px;
85
+ line-height: 1.35;
86
+ }
87
+
88
+ .subhead {
89
+ margin: 0 0 0.3em;
90
+ font-size: 28px;
91
+ font-weight: 600;
92
+ letter-spacing: -0.01em;
93
+ }
94
+
95
+ .small {
96
+ font-size: 24px;
97
+ }
98
+
99
+ .caption,
100
+ .cite {
101
+ margin: 0;
102
+ font-size: 22px;
103
+ color: var(--muted);
104
+ }
105
+
106
+ .cite {
107
+ font-size: 18px;
108
+ }
109
+
110
+ table {
111
+ width: 100%;
112
+ border-collapse: collapse;
113
+ font-size: 24px;
114
+ }
115
+
116
+ th {
117
+ font-weight: 600;
118
+ text-align: left;
119
+ }
120
+
121
+ th,
122
+ td {
123
+ padding: 10px 20px 10px 0;
124
+ border-bottom: 1px solid var(--border);
125
+ }
126
+
127
+ .accent {
128
+ color: var(--accent);
129
+ }
130
+
131
+ /* Containers */
132
+
133
+ .card {
134
+ background: var(--surface);
135
+ border: 1px solid var(--border);
136
+ border-radius: 20px;
137
+ padding: 40px;
138
+ }
139
+
140
+ .media {
141
+ border-radius: 16px;
142
+ overflow: hidden;
143
+ background: #000;
144
+ }
145
+
146
+ .media > img,
147
+ .media > video,
148
+ .fill {
149
+ display: block;
150
+ width: 100%;
151
+ height: 100%;
152
+ object-fit: cover;
153
+ }
154
+
155
+ img.contain,
156
+ video.contain,
157
+ .contain,
158
+ .media.contain > * {
159
+ object-fit: contain;
160
+ }
161
+
162
+ .media.figure {
163
+ background: #fff;
164
+ }
165
+
166
+ /* Layout backgrounds */
167
+
168
+ section[data-layout="title"] {
169
+ background: linear-gradient(135deg, #0f1b3d 0%, #1d3b8f 100%);
170
+ color: #fff;
171
+ }
172
+
173
+ section[data-layout="title"] .subtitle {
174
+ color: #c8d3f5;
175
+ }
176
+
177
+ section[data-layout="section"] {
178
+ background: var(--accent);
179
+ color: #fff;
180
+ }
181
+
182
+ section[data-layout="section"] .eyebrow {
183
+ color: rgba(255, 255, 255, 0.75);
184
+ }
185
+
186
+ code {
187
+ font-family: var(--font-mono);
188
+ font-size: 0.9em;
189
+ background: var(--surface);
190
+ border-radius: 6px;
191
+ padding: 0.1em 0.3em;
192
+ }
193
+
194
+ section[data-layout="title"] .eyebrow {
195
+ color: #8fb0ff;
196
+ }
keyframes/vendor.py ADDED
@@ -0,0 +1,181 @@
1
+ """Caching CDN proxy. Decks reference `_vendor/<host>/<path>`; first fetch hits the network,
2
+ every later fetch (and the static build) is served from `.keyframes/vendor/`."""
3
+
4
+ import hashlib
5
+ import json
6
+ import posixpath
7
+ import re
8
+ from dataclasses import dataclass
9
+ from pathlib import Path
10
+ from urllib.parse import urlsplit
11
+
12
+ import httpx
13
+
14
+ ALLOWED_HOSTS = {
15
+ "cdn.jsdelivr.net",
16
+ "esm.sh",
17
+ "unpkg.com",
18
+ "fonts.googleapis.com",
19
+ "fonts.gstatic.com",
20
+ }
21
+
22
+ # Pinned CodeMirror packages. Packages holding shared state stay bare specifiers inside every
23
+ # esm.sh build (`?external=`), so the import map hands all modules one @codemirror/state etc.
24
+ _CODEMIRROR = {
25
+ "@codemirror/state": "6.7.6",
26
+ "@codemirror/view": "6.43.14",
27
+ "@codemirror/commands": "6.11.1",
28
+ "@codemirror/language": "6.13.1",
29
+ "@codemirror/lang-python": "6.2.1",
30
+ "@codemirror/lang-javascript": "6.2.5",
31
+ "@codemirror/lang-cpp": "6.0.3",
32
+ "@codemirror/lang-rust": "6.0.2",
33
+ "@codemirror/lang-json": "6.0.2",
34
+ "@codemirror/lang-css": "6.3.1",
35
+ "@codemirror/lang-html": "6.4.12",
36
+ "@lezer/common": "1.5.3",
37
+ "@lezer/highlight": "1.2.5",
38
+ "@codemirror/theme-one-dark": "6.1.3",
39
+ }
40
+ _CODEMIRROR_SHARED = (
41
+ "@codemirror/state",
42
+ "@codemirror/view",
43
+ "@codemirror/language",
44
+ "@lezer/common",
45
+ "@lezer/highlight",
46
+ )
47
+ _CODEMIRROR_EXTERNAL = ",".join(_CODEMIRROR_SHARED)
48
+
49
+ DEFAULT_IMPORTS = {
50
+ "three": "https://cdn.jsdelivr.net/npm/three@0.186.1/build/three.module.js",
51
+ "three/addons/": "https://cdn.jsdelivr.net/npm/three@0.186.1/examples/jsm/",
52
+ "katex": "https://cdn.jsdelivr.net/npm/katex@0.19.0/dist/katex.mjs",
53
+ "katex/": "https://cdn.jsdelivr.net/npm/katex@0.19.0/dist/",
54
+ "@mujoco/mujoco": "https://cdn.jsdelivr.net/npm/@mujoco/mujoco@3.15.0/mujoco.js",
55
+ "@excalidraw/excalidraw": "https://esm.sh/@excalidraw/excalidraw@0.18.1?deps=react@19.2.0,react-dom@19.2.0",
56
+ "@excalidraw/excalidraw/": "https://esm.sh/@excalidraw/excalidraw@0.18.1/",
57
+ **{
58
+ name: f"https://esm.sh/{name}@{version}?external={_CODEMIRROR_EXTERNAL}"
59
+ for name, version in _CODEMIRROR.items()
60
+ },
61
+ "@xterm/xterm": "https://cdn.jsdelivr.net/npm/@xterm/xterm@6.0.0/lib/xterm.mjs",
62
+ "@xterm/xterm/": "https://cdn.jsdelivr.net/npm/@xterm/xterm@6.0.0/",
63
+ "@xterm/addon-fit": "https://cdn.jsdelivr.net/npm/@xterm/addon-fit@0.11.0/lib/addon-fit.mjs",
64
+ "pyodide": "https://cdn.jsdelivr.net/pyodide/v314.0.7/full/pyodide.mjs",
65
+ }
66
+
67
+ VENDOR_PREFIX = "_vendor"
68
+ # Google Fonts picks the font format from the user agent; a modern one gets woff2.
69
+ USER_AGENT = (
70
+ "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 "
71
+ "(KHTML, like Gecko) Chrome/140.0 Safari/537.36"
72
+ )
73
+
74
+ _JS_SPECIFIER = re.compile(
75
+ r"""(\bfrom\s*|\bimport\s*\(?\s*|\bexport\s*\*\s*from\s*)(["'])(https?://[^"']+|/[^/"'][^"']*)\2"""
76
+ )
77
+ _CSS_URL = re.compile(r"""(url\(\s*|@import\s+)(["']?)(https?://[^"')\s]+)\2""")
78
+
79
+
80
+ @dataclass
81
+ class VendorEntry:
82
+ body: bytes
83
+ content_type: str
84
+
85
+
86
+ def to_vendor_path(url: str) -> str:
87
+ """https://host/a/b?q -> _vendor/host/a/b?q"""
88
+ parts = urlsplit(url)
89
+ query = f"?{parts.query}" if parts.query else ""
90
+ return f"{VENDOR_PREFIX}/{parts.netloc}{parts.path}{query}"
91
+
92
+
93
+ def vendor_import_map(extra: dict[str, str]) -> dict[str, str]:
94
+ imports = {**DEFAULT_IMPORTS, **extra}
95
+ return {name: f"./{to_vendor_path(url)}" for name, url in imports.items()}
96
+
97
+
98
+ def _short(segment: str) -> str:
99
+ if len(segment.encode()) <= 120:
100
+ return segment
101
+ return "@h" + hashlib.sha1(segment.encode()).hexdigest()[:16]
102
+
103
+
104
+ def _cache_name(host: str, path: str, query: str) -> str:
105
+ name = "/".join([host, *(_short(s) for s in path.lstrip("/").split("/"))])
106
+ if name.endswith("/"):
107
+ name += "__index"
108
+ if query:
109
+ name += "@q" + hashlib.sha1(query.encode()).hexdigest()[:10]
110
+ return name
111
+
112
+
113
+ def _relative(from_url_path: str, to_url_path: str) -> str:
114
+ rel = posixpath.relpath(to_url_path.split("?")[0], posixpath.dirname(from_url_path))
115
+ query = to_url_path[len(to_url_path.split("?")[0]) :]
116
+ return (rel if rel.startswith(".") else f"./{rel}") + query
117
+
118
+
119
+ def rewrite_text(text: str, self_url_path: str, host: str, kind: str) -> str:
120
+ """Point absolute CDN references at the proxy, as paths relative to the importing file."""
121
+
122
+ def target(spec: str) -> str:
123
+ if spec.startswith("/"):
124
+ return f"/{VENDOR_PREFIX}/{host}{spec}"
125
+ return "/" + to_vendor_path(spec)
126
+
127
+ if kind == "js":
128
+
129
+ def sub(m: re.Match) -> str:
130
+ if m.group(3).startswith("http") and urlsplit(m.group(3)).netloc not in ALLOWED_HOSTS:
131
+ return m.group(0)
132
+ return f"{m.group(1)}{m.group(2)}{_relative(self_url_path, target(m.group(3)))}{m.group(2)}"
133
+
134
+ return _JS_SPECIFIER.sub(sub, text)
135
+
136
+ def css_sub(m: re.Match) -> str:
137
+ if urlsplit(m.group(3)).netloc not in ALLOWED_HOSTS:
138
+ return m.group(0)
139
+ return f"{m.group(1)}{m.group(2)}{_relative(self_url_path, target(m.group(3)))}{m.group(2)}"
140
+
141
+ return _CSS_URL.sub(css_sub, text)
142
+
143
+
144
+ def _kind(content_type: str) -> str | None:
145
+ if "javascript" in content_type:
146
+ return "js"
147
+ if "css" in content_type:
148
+ return "css"
149
+ return None
150
+
151
+
152
+ class VendorCache:
153
+ def __init__(self, cache_dir: Path):
154
+ self.dir = cache_dir / "vendor"
155
+ self.client = httpx.AsyncClient(follow_redirects=True, timeout=60)
156
+
157
+ async def get(self, host: str, path: str, query: str) -> VendorEntry:
158
+ if host not in ALLOWED_HOSTS:
159
+ raise PermissionError(f"Host {host!r} is not in the vendor allowlist")
160
+ file = self.dir / _cache_name(host, path, query)
161
+ meta = file.with_name(file.name + ".meta.json")
162
+ if file.exists() and meta.exists():
163
+ return VendorEntry(file.read_bytes(), json.loads(meta.read_text())["type"])
164
+
165
+ url = f"https://{host}{path}" + (f"?{query}" if query else "")
166
+ response = await self.client.get(url, headers={"User-Agent": USER_AGENT})
167
+ response.raise_for_status()
168
+ content_type = response.headers.get("content-type", "application/octet-stream")
169
+ body = response.content
170
+ kind = _kind(content_type)
171
+ if kind:
172
+ self_path = f"/{VENDOR_PREFIX}/{host}{path}"
173
+ body = rewrite_text(body.decode(), self_path, host, kind).encode()
174
+
175
+ file.parent.mkdir(parents=True, exist_ok=True)
176
+ file.write_bytes(body)
177
+ meta.write_text(json.dumps({"url": url, "type": content_type}))
178
+ return VendorEntry(body, content_type)
179
+
180
+ async def close(self):
181
+ await self.client.aclose()
@@ -0,0 +1,119 @@
1
+ Metadata-Version: 2.3
2
+ Name: keyframes
3
+ Version: 0.1.0
4
+ Summary: Agent-written, interactive HTML slide decks with live 3D, physics, math, code and diagrams
5
+ Keywords: slides,presentation,llm,agents,three.js,mujoco,katex
6
+ Author: Pranay Thangeda
7
+ Author-email: Pranay Thangeda <contact@prny.me>
8
+ Classifier: License :: Other/Proprietary License
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: Topic :: Multimedia :: Graphics :: Presentation
11
+ Requires-Dist: httpx>=0.28.1
12
+ Requires-Dist: starlette>=1.7.0
13
+ Requires-Dist: uvicorn>=0.54.0
14
+ Requires-Dist: watchfiles>=1.3.0
15
+ Requires-Dist: websockets>=17.2
16
+ Requires-Dist: playwright>=1.63.0 ; extra == 'export'
17
+ Requires-Dist: pypdf>=6.20.1 ; extra == 'export'
18
+ Requires-Python: >=3.12
19
+ Provides-Extra: export
20
+ Description-Content-Type: text/markdown
21
+
22
+ # keyframes
23
+
24
+ HTML slide decks that LLMs write and you arrange by hand.
25
+
26
+ - **Slides are plain HTML files** on a fixed 1920×1080 canvas. Claude Code, Codex & co.
27
+ write them from your references; a generated `AGENTS.md` teaches them the format.
28
+ - **Live editor in Chrome**: press `E` to drag, resize, snap and nudge blocks. Changes are
29
+ written straight back into the slide source (only the touched attributes change), so
30
+ your layout and the LLM's edits never fight.
31
+ - **Rich embeds** as web components: three.js scenes, MuJoCo WASM physics, editable/runnable
32
+ code (JS + Python via Pyodide), Excalidraw figures, KaTeX math, video.
33
+ - **Present anywhere**: presenter view with notes and timer, offline-safe dependency cache,
34
+ PDF export, static site export.
35
+
36
+ ## Quick start
37
+
38
+ ```bash
39
+ uv init my-talk && cd my-talk
40
+ uv add keyframes # or: uv add 'keyframes[export]' for pdf/shot/check --render
41
+ uv run keyframes init .
42
+ uv run keyframes dev # opens Chrome; edit slides/*.html or ask your LLM to
43
+ ```
44
+
45
+ Then ask your agent something like *"Using the papers in references/, draft a 12-slide talk
46
+ on X. Put the MuJoCo demo from assets/ on slide 5."* and rearrange the result with `E`.
47
+
48
+ For exports install the browser once: `uv run playwright install chromium` (or have Chrome).
49
+
50
+ ## In the browser
51
+
52
+ | key | action |
53
+ | --- | --- |
54
+ | → / Space, ← | next / previous (build steps included) |
55
+ | E | toggle the layout editor |
56
+ | S | presenter view (notes, next slide, timer) |
57
+ | F | fullscreen · B blackout |
58
+
59
+ Editor: drag to move (Shift locks axis, Alt disables snapping), handles resize (Shift keeps
60
+ aspect), arrows nudge (Shift ×10), `[`/`]` z-order, double-click to interact with a live
61
+ embed, `C` copies a block reference like `slides/04-results.html#sim` for your next prompt,
62
+ ⌘Z / ⌘⇧Z undo/redo, `?` for help.
63
+
64
+ Edit mode also opens a resizable side panel (toggle with `` ` ``):
65
+
66
+ - **Source**: the current slide's HTML. Selecting a block jumps to its tag; edits save and
67
+ hot-reload as you type; drags and LLM edits flow back in.
68
+ - **Claude / Codex / Shell**: real terminals running in the deck folder (whichever CLIs are
69
+ installed). Sessions survive page reloads. `+ ref` (or `R`) types the selected blocks'
70
+ reference into the prompt, e.g. `@slides/04-problem.html (block "figure")`.
71
+
72
+ The dev server only accepts editor/terminal requests from its own page, but it does give that
73
+ page a shell: keep it bound to 127.0.0.1 (the default).
74
+
75
+ ## Commands
76
+
77
+ ```
78
+ keyframes init [path] new deck from the scaffold
79
+ keyframes dev live server + editor
80
+ keyframes present serve without the editor
81
+ keyframes check [--render] lint; --render adds browser errors and text overflow
82
+ keyframes shot [N ...] PNG screenshots → .keyframes/shots/
83
+ keyframes pdf PDF export (one page per slide, all builds shown)
84
+ keyframes build static site → dist/
85
+ keyframes guide full authoring reference (for LLMs)
86
+ keyframes themes list built-in themes
87
+ keyframes eject-theme copy the theme into the deck to customize it
88
+ ```
89
+
90
+ ## Deck layout
91
+
92
+ ```
93
+ my-talk/
94
+ deck.toml title, theme, canvas size, extra <head> tags, extra ES module imports
95
+ AGENTS.md authoring rules for LLMs (CLAUDE.md imports it)
96
+ slides/ 01-title.html, 02-motivation.html, ... (ordered by filename)
97
+ assets/ images, video, MJCF models, three.js scene modules, .excalidraw files
98
+ references/ papers/notes for the LLM to draw from (never exported)
99
+ .keyframes/ dependency cache + screenshots (gitignored)
100
+ ```
101
+
102
+ ## Themes
103
+
104
+ - `default`: a clone of Google Slides' "simple-light" template (Google Sans + Roboto Light,
105
+ orbit artwork on title/section slides, split layouts with a grey media panel).
106
+ - `minimal`: plain Inter/JetBrains Mono theme.
107
+
108
+ A theme is a folder with `theme.css` (CSS variables + text/container classes),
109
+ `STYLE_GUIDE.md` (rules the LLM follows) and `layouts/*.html` (starting-point slides).
110
+ Use `theme = "./theme"` in `deck.toml` for a deck-local theme.
111
+
112
+ ## Development
113
+
114
+ ```bash
115
+ uv sync --all-extras
116
+ uv run pytest
117
+ uv run ruff check src tests && uv run ruff format src tests
118
+ cd examples/showcase && uv run keyframes dev # every component in one deck
119
+ ```
@@ -0,0 +1,77 @@
1
+ keyframes/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
2
+ keyframes/build.py,sha256=CFIQPmYVI7qjsltlg2MIpZdGUKBn8t_15WmL3QWegr8,3760
3
+ keyframes/check.py,sha256=gUtxsgAgSuHqgNFKrnF7IxvMG7zYrE0yDnTrtUN2jNg,4175
4
+ keyframes/cli.py,sha256=45tPP9FBSDh-VVH9zJZW4EQBg8xXBBzXThY---Lu3Mo,5106
5
+ keyframes/deck.py,sha256=aD0HBtflgVZfm83rHswoSGVAqbYWrieLM9HctjGgwnc,1984
6
+ keyframes/export.py,sha256=xCsPsDmxPcfcRGiCebhtZtKKSEW85DeCyKcIhrxbk5o,3682
7
+ keyframes/guide/__init__.py,sha256=ZTczNGOqFWnKR3C7i2c3CUdfyOx2c6urwhdKhN5mLwk,845
8
+ keyframes/guide/components/keyframes-code.md,sha256=FVH8cW262g9UecenGp6ByVZXaXcQuIXG1VqAVJiJWjo,4777
9
+ keyframes/guide/components/keyframes-excalidraw.md,sha256=te3aox35-6Z8hGx9Qynm5giumlYmV_4PBkWcVtWPsEo,7391
10
+ keyframes/guide/components/keyframes-mujoco.md,sha256=tsgTnTwIBgbv2HxqFNrqCTBM2B_iZSrvbrNERmn16hU,7407
11
+ keyframes/guide/components/keyframes-slider.md,sha256=9Al2ag9DwDM2HhMv0q3vnRC-AyYj-x3JYgYTwAjQIjE,5117
12
+ keyframes/guide/components/keyframes-three.md,sha256=v8GOw0eUY2si0aD3sDb2RvtImo_1wsvoFlAh-fDQOTw,6738
13
+ keyframes/guide/core.md,sha256=KgU8_CNx4JZONsECvs7MXbOkW2upu59OYr4m4nVaqrU,5785
14
+ keyframes/patch.py,sha256=M2372O1JXxQs1lsl4tly1TGprFhFewXyZQ1vP4U65Xw,2788
15
+ keyframes/render.py,sha256=yu2W8Av3e-YgoXH_k6wGIEY2eUoeimqytlgrWqU9890,1247
16
+ keyframes/scaffold/.gitignore,sha256=Kxw1DwfYGYw0frTjLwak4v7_m3Jo-c9HnwnlC04bVr4,24
17
+ keyframes/scaffold/CLAUDE.md,sha256=M2zE-_Gb6q2nzPmYZBT6kYUajXoH37PMvoAKae7Qq0k,11
18
+ keyframes/scaffold/assets/deck.css,sha256=v0gfCrsIkUrnV_Jcv8dGp5wAs8T5Gw_oBIb2_YmF2Zs,36
19
+ keyframes/scaffold/assets/figure.svg,sha256=nX-74Xjun-TiyR393Zw7a07PvTCaSv9mXVgHrvUalmU,238
20
+ keyframes/scaffold/deck.toml,sha256=uaJVAuOXgb7LL9PtGAD3mipo7VqfQ3VK0J2zlOWBEIg,312
21
+ keyframes/scaffold/references/.gitkeep,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
22
+ keyframes/scaffold/slides/01-title.html,sha256=rRzPyLxTnJFOZ2iEEUKy-KD5iKnLNe3jCEMV0rNw7Z4,290
23
+ keyframes/scaffold/slides/02-intro.html,sha256=-TDwsbfhchPjxJlkm7_oIKxLWa2UgZMjC4uN37BAnyo,1045
24
+ keyframes/server.py,sha256=Ps4gx9_24VW7pSXxuEaFM-DbEMSGVv-c0_pFDJxqc2I,7534
25
+ keyframes/static/components/keyframes-code.js,sha256=1emWhvIBeeUgr7smfP5djMIOR7LlfrexPZXIHThOcvc,15894
26
+ keyframes/static/components/keyframes-excalidraw.js,sha256=60v65VcRTtwKKnb5fQ3cpzwPOsdTCh2LE3zTsKP5Bxo,6560
27
+ keyframes/static/components/keyframes-mujoco.js,sha256=LEB2ocp76H9mNAwTb1FqS2pROgYu5GOUruTAU-VNKP0,18019
28
+ keyframes/static/components/keyframes-slider.js,sha256=2_HM6rLrdqHmENXunyNsRHePYmjNXhzW34WOmT3IySU,8173
29
+ keyframes/static/components/keyframes-three.js,sha256=ex68wRA9Kdq0_cvcIvx5LP3DNURrU3ejkTvgkFdUYUk,6628
30
+ keyframes/static/editor/editor.css,sha256=vA2BuCjtznHmfUxSy5ncZWbMZeEHuowiNvr36arKDGk,4692
31
+ keyframes/static/editor/editor.js,sha256=nWqTUg4Yq_1c9-ouSX3ZuDSJvtWHxjh-A19U97CuMgU,19786
32
+ keyframes/static/editor/panel.css,sha256=Ffj59rDo2dDsGwJB58kUztjndobIzsSsE99dAIT_Gcg,2290
33
+ keyframes/static/editor/panel.js,sha256=3-ksQgnfplBcbr2-A6FP0W4TZuJmJ6L9BPvkwpcrviE,12026
34
+ keyframes/static/index.html,sha256=7twRfsZ_qNJ0stisYOvBD3hN5pwkEmQAkZFpJBJuW8s,569
35
+ keyframes/static/runtime/component.js,sha256=DZebtJmAAt3ugNZNw5XjkOtPHxmR2E0uxlJZ930zJGI,3473
36
+ keyframes/static/runtime/live.js,sha256=ihwSkpMnumSGvARug2zQAs7J3roisgZds-4MOzZ3XVU,1040
37
+ keyframes/static/runtime/math.js,sha256=paGHgdkH-DDgD8cycoizyuaaBShx6aGR9XGV-ypKg4g,1133
38
+ keyframes/static/runtime/presenter.js,sha256=6msUDY_lA8e9p-Zej1UKDVSoA0ypH7u8RUr_IiXz8do,4590
39
+ keyframes/static/runtime/runtime.css,sha256=VoKjfPsAzaNQQ5tDkH1eIK-OX61QbESqw9IPbwxZyhw,1428
40
+ keyframes/static/runtime/runtime.js,sha256=RJK8XcBSjlDoLfrl5sRIdN0H6b22FMKiESk8TDudqX4,8185
41
+ keyframes/static/runtime/steps.js,sha256=AnSoJlMpS_xByOL_esHJqp9oy6q8MbZFUp81vUXr-3M,570
42
+ keyframes/terminal.py,sha256=ba-O_IbhiZF64cMlXsyJwKbzp0orejzOMCZyK8V4ZdA,4682
43
+ keyframes/themes/default/STYLE_GUIDE.md,sha256=gdJUPByquuDZq2jz2f9MWiXH_uiBeJbpmB1Zp7tOMsQ,4454
44
+ keyframes/themes/default/assets/arc-top.webp,sha256=ELUXqAND_ZwCMuyCb6mhnFIJflTDxRSubLIFt67Ofo8,69528
45
+ keyframes/themes/default/assets/orbit-ring.webp,sha256=qpI7_ktS17tBHBK-QiQyj0JOFmXeXMVuyQrr8zGe_kQ,25634
46
+ keyframes/themes/default/assets/streaks-top.webp,sha256=EjPII1CMYQumHH-K4SPXX9PphR1VP5RhsW0p3GMp308,18944
47
+ keyframes/themes/default/assets/streaks-wide.webp,sha256=qbkm_m1s9ehGFTSypIrZyxG3TLkn_Kp9yaaDQc93z2U,18814
48
+ keyframes/themes/default/assets/sun-top.webp,sha256=etZsOjnRm7j8a7aS2MyL5hLVfPeD_reoCHc9cSZ-f8c,31078
49
+ keyframes/themes/default/assets/title-orbit.webp,sha256=3262BtnRRLfHh-kt0lZ67HsD-5BSrBHEKalvpxlwub8,63014
50
+ keyframes/themes/default/layouts/content.html,sha256=3krYAxYGEUz-YcfV1MfQtX6-aDzlN3ksFG_yYdOaL8M,640
51
+ keyframes/themes/default/layouts/figure.html,sha256=M494UarB8BdbeTFcKqScmMNPqQxiu8JqRElBq5TXxiw,478
52
+ keyframes/themes/default/layouts/grid-2.html,sha256=MIh1kYDiuVRn36POk425YE1AgP_IANeHIGBjsZ7Fo5s,1266
53
+ keyframes/themes/default/layouts/grid-3x2.html,sha256=XVHTy0Dto4nTFgC5j2yLwz-dOLoRVlQeVC37rjZigFQ,2115
54
+ keyframes/themes/default/layouts/grid-4.html,sha256=fB8pT2i-M-_Lo4J3AVsLigzQX1D_9GV9_Urctg21lCw,1861
55
+ keyframes/themes/default/layouts/media-side.html,sha256=ITX7ZvZTizcZCr-XImUqLkC44TxVyWkjxvWbQpCdvO4,918
56
+ keyframes/themes/default/layouts/section-arc.html,sha256=DJMyVLr99Fp4JRND6JczR4apTLsbpBvLtPDvjFIMZIw,288
57
+ keyframes/themes/default/layouts/section-streaks.html,sha256=1IWBqGKdowM9qBj6DDyYdMEfds0ZzJzUAGPMO2-F6Oo,302
58
+ keyframes/themes/default/layouts/section.html,sha256=o50pfI-DLmeQalgN6yqMPGUBQxAfjTO-waFCbA6OI3o,292
59
+ keyframes/themes/default/layouts/split-wide.html,sha256=x5amUdA5-8kgRnsAGxCywdFr6SbaYnZjTXeAggvnAIY,426
60
+ keyframes/themes/default/layouts/split.html,sha256=23gUxOxjVgNKX4GcanPnEikCQ1CPTYotrd80_2buQRg,511
61
+ keyframes/themes/default/layouts/statement.html,sha256=FKItR44klqVjWzPPg2-XnOijWtXnQlAGwW3ZUic0oBs,265
62
+ keyframes/themes/default/layouts/title.html,sha256=28Z9k6cQj2_71FH6UK69Us1ydchmlX1B6Wov-qowGzQ,408
63
+ keyframes/themes/default/layouts/two-column.html,sha256=teLhiOCoCJ6AaG9TB-82wYPAvRCBustSHc0bFbbFZko,367
64
+ keyframes/themes/default/theme.css,sha256=XF8oHY3XqoaZiwaFJfPF7VCgHEj8imRbtEFWyHUImNo,5664
65
+ keyframes/themes/minimal/STYLE_GUIDE.md,sha256=aZ4NNiUHsP-zCoADnLAKfsbK-v4yL9FKccs5BGP_Yac,1715
66
+ keyframes/themes/minimal/layouts/figure.html,sha256=lmkqTis8hilu_G8oo_ionsRJxcpheu20LO2NHnHkrN0,429
67
+ keyframes/themes/minimal/layouts/section.html,sha256=oDxREOBCaR7OuKbILzQMhf-k_q5L_mm3DkFKQx9LfgE,258
68
+ keyframes/themes/minimal/layouts/statement.html,sha256=IaT4pegHsJ1_sRE4jKZLPXQuWx50aU0eWQFWNygyh6A,200
69
+ keyframes/themes/minimal/layouts/title-body.html,sha256=38GdrRLWrYm1dQVCHYEy6Ehlpk6zEz8CQXUf_XwGE-c,409
70
+ keyframes/themes/minimal/layouts/title.html,sha256=UgJuUyfuVMFevCaDR5EhZ_ZC5kfVkbtDzWxorvuUBJw,390
71
+ keyframes/themes/minimal/layouts/two-column.html,sha256=vxjZ7rquAL0F8VEdLWqwpoU8QwGVFsVj-VQ8eXWvdzc,451
72
+ keyframes/themes/minimal/theme.css,sha256=ECK8YstOTWZ8X60M4nhJNfSCmbUWz-V74dScXLQYAlA,2979
73
+ keyframes/vendor.py,sha256=51xEY5wSipCtsno14HifbKj_GsMP0uDeaAsktIw98Kc,6666
74
+ keyframes-0.1.0.dist-info/WHEEL,sha256=si2p8vW03RnhyBnhWl4tj-ObJa78SpE-c9HsEtblGR0,80
75
+ keyframes-0.1.0.dist-info/entry_points.txt,sha256=lMTaSLdpKPM5pSvpwznSA-taI-pAGxqi_wXZYwsE7aI,50
76
+ keyframes-0.1.0.dist-info/METADATA,sha256=kaFJvSEzI5QXGKHzewasfa0SYjvNKj2NRv9Sy3ZxTg8,5066
77
+ keyframes-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: uv 0.13.5
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,3 @@
1
+ [console_scripts]
2
+ keyframes = keyframes.cli:main
3
+