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.
- keyframes/__init__.py +1 -0
- keyframes/build.py +102 -0
- keyframes/check.py +107 -0
- keyframes/cli.py +149 -0
- keyframes/deck.py +67 -0
- keyframes/export.py +106 -0
- keyframes/guide/__init__.py +25 -0
- keyframes/guide/components/keyframes-code.md +60 -0
- keyframes/guide/components/keyframes-excalidraw.md +148 -0
- keyframes/guide/components/keyframes-mujoco.md +76 -0
- keyframes/guide/components/keyframes-slider.md +65 -0
- keyframes/guide/components/keyframes-three.md +81 -0
- keyframes/guide/core.md +113 -0
- keyframes/patch.py +77 -0
- keyframes/render.py +32 -0
- keyframes/scaffold/.gitignore +3 -0
- keyframes/scaffold/CLAUDE.md +1 -0
- keyframes/scaffold/assets/deck.css +1 -0
- keyframes/scaffold/assets/figure.svg +1 -0
- keyframes/scaffold/deck.toml +10 -0
- keyframes/scaffold/references/.gitkeep +0 -0
- keyframes/scaffold/slides/01-title.html +4 -0
- keyframes/scaffold/slides/02-intro.html +17 -0
- keyframes/server.py +204 -0
- keyframes/static/components/keyframes-code.js +423 -0
- keyframes/static/components/keyframes-excalidraw.js +189 -0
- keyframes/static/components/keyframes-mujoco.js +515 -0
- keyframes/static/components/keyframes-slider.js +255 -0
- keyframes/static/components/keyframes-three.js +224 -0
- keyframes/static/editor/editor.css +228 -0
- keyframes/static/editor/editor.js +594 -0
- keyframes/static/editor/panel.css +127 -0
- keyframes/static/editor/panel.js +357 -0
- keyframes/static/index.html +23 -0
- keyframes/static/runtime/component.js +126 -0
- keyframes/static/runtime/live.js +31 -0
- keyframes/static/runtime/math.js +33 -0
- keyframes/static/runtime/presenter.js +91 -0
- keyframes/static/runtime/runtime.css +91 -0
- keyframes/static/runtime/runtime.js +215 -0
- keyframes/static/runtime/steps.js +15 -0
- keyframes/terminal.py +144 -0
- keyframes/themes/default/STYLE_GUIDE.md +73 -0
- keyframes/themes/default/assets/arc-top.webp +0 -0
- keyframes/themes/default/assets/orbit-ring.webp +0 -0
- keyframes/themes/default/assets/streaks-top.webp +0 -0
- keyframes/themes/default/assets/streaks-wide.webp +0 -0
- keyframes/themes/default/assets/sun-top.webp +0 -0
- keyframes/themes/default/assets/title-orbit.webp +0 -0
- keyframes/themes/default/layouts/content.html +16 -0
- keyframes/themes/default/layouts/figure.html +7 -0
- keyframes/themes/default/layouts/grid-2.html +19 -0
- keyframes/themes/default/layouts/grid-3x2.html +28 -0
- keyframes/themes/default/layouts/grid-4.html +44 -0
- keyframes/themes/default/layouts/media-side.html +16 -0
- keyframes/themes/default/layouts/section-arc.html +4 -0
- keyframes/themes/default/layouts/section-streaks.html +4 -0
- keyframes/themes/default/layouts/section.html +4 -0
- keyframes/themes/default/layouts/split-wide.html +7 -0
- keyframes/themes/default/layouts/split.html +13 -0
- keyframes/themes/default/layouts/statement.html +4 -0
- keyframes/themes/default/layouts/title.html +5 -0
- keyframes/themes/default/layouts/two-column.html +5 -0
- keyframes/themes/default/theme.css +330 -0
- keyframes/themes/minimal/STYLE_GUIDE.md +41 -0
- keyframes/themes/minimal/layouts/figure.html +7 -0
- keyframes/themes/minimal/layouts/section.html +4 -0
- keyframes/themes/minimal/layouts/statement.html +3 -0
- keyframes/themes/minimal/layouts/title-body.html +10 -0
- keyframes/themes/minimal/layouts/title.html +5 -0
- keyframes/themes/minimal/layouts/two-column.html +11 -0
- keyframes/themes/minimal/theme.css +196 -0
- keyframes/vendor.py +181 -0
- keyframes-0.1.0.dist-info/METADATA +119 -0
- keyframes-0.1.0.dist-info/RECORD +77 -0
- keyframes-0.1.0.dist-info/WHEEL +4 -0
- 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,,
|