dsh-motion 0.1.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/NOTICE.md +1 -0
- package/README.md +108 -25
- package/docs/ai-native-motion.md +233 -0
- package/docs/dev-loop.md +55 -0
- package/lib/doctor.js +26 -1
- package/lib/fresh.js +21 -0
- package/lib/index.js +44 -6
- package/lib/ops.js +164 -8
- package/package.json +7 -3
- package/skills/animation-router/SKILL.md +17 -11
- package/skills/design-spec/SKILL.md +25 -1
- package/skills/qa-rubric/SKILL.md +14 -8
- package/styles/README.md +23 -0
- package/styles/blueprint/STYLE.md +59 -0
- package/styles/blueprint/preview.jpg +0 -0
- package/styles/blueprint/style.json +11 -0
- package/styles/blueprint/theme.py +65 -0
- package/styles/blueprint/tokens.json +27 -0
- package/styles/dataviz/STYLE.md +58 -0
- package/styles/dataviz/preview.jpg +0 -0
- package/styles/dataviz/style.json +11 -0
- package/styles/dataviz/theme.py +49 -0
- package/styles/dataviz/tokens.json +31 -0
- package/styles/swiss-motion/STYLE.md +59 -0
- package/styles/swiss-motion/preview.jpg +0 -0
- package/styles/swiss-motion/style.json +11 -0
- package/styles/swiss-motion/theme.py +59 -0
- package/styles/swiss-motion/tokens.json +33 -0
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "blueprint",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"source": "https://github.com/lemomo-ai/lemo-opuscar/tree/main/styles/blueprint",
|
|
5
|
+
"license": "style doc MIT; demo stills CC BY 4.0 (LemoLab)",
|
|
6
|
+
"colors": {
|
|
7
|
+
"table": "#0D1116",
|
|
8
|
+
"paper": "#18336B",
|
|
9
|
+
"paper_light": "#2A58A6",
|
|
10
|
+
"line": "#E7F0F9",
|
|
11
|
+
"muted": "#9FB6D4",
|
|
12
|
+
"stamp": "#B5442E"
|
|
13
|
+
},
|
|
14
|
+
"typography": {
|
|
15
|
+
"hand": { "family": "Chalkboard SE", "size": 24 },
|
|
16
|
+
"tech": { "family": "Menlo", "size": 17 },
|
|
17
|
+
"math": { "family": "New Computer Modern", "size": 54 }
|
|
18
|
+
},
|
|
19
|
+
"layout": {
|
|
20
|
+
"aspect": "16:9",
|
|
21
|
+
"frame": { "width": 14.222, "height": 8.0 },
|
|
22
|
+
"margin": 0.55,
|
|
23
|
+
"title_block": { "x": 4.95, "y": -2.95 }
|
|
24
|
+
},
|
|
25
|
+
"motion": { "fps": 30, "easing": "draw-on", "hold_min_seconds": 1.0 },
|
|
26
|
+
"audio": { "target_lufs": -14, "true_peak_db": -1 }
|
|
27
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: dataviz
|
|
3
|
+
version: 1.0.0
|
|
4
|
+
source: https://github.com/lemomo-ai/lemo-opuscar/tree/main/styles/dataviz
|
|
5
|
+
license: MIT(上游风格文档);demo 静帧 CC BY 4.0(LemoLab)
|
|
6
|
+
status: adapted
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Data Storytelling · 数据叙事
|
|
10
|
+
|
|
11
|
+
> 上游:Lemo-Opuscar `styles/dataviz`(MIT)。保留“图表就是电影”的语法;
|
|
12
|
+
> 铅笔/纸纹等材质实现换成了 manimgx 的平涂与细线。
|
|
13
|
+
|
|
14
|
+
## 1. 定位
|
|
15
|
+
|
|
16
|
+
一张会讲故事的图:真实数据、逐笔画出、手写注释钉在数字上。图表本身就是镜头、节奏和情节。
|
|
17
|
+
|
|
18
|
+
- 适合:趋势、比较、分布、前后变化、科学数据;
|
|
19
|
+
- 不适合:没有数据支撑的口号、纯角色故事。
|
|
20
|
+
|
|
21
|
+
## 2. 不变量
|
|
22
|
+
|
|
23
|
+
| 项 | 规则 |
|
|
24
|
+
|---|---|
|
|
25
|
+
| 纸 | 奶油纸 `#F4EDE1`;墨 `#2B2622`;网格 `#C9BFAE` |
|
|
26
|
+
| 数据色 | 发散数据用 RdBu(`#2166AC → #F7F7F7 → #B2182B`),以有意义的均值为中心 |
|
|
27
|
+
| 强调 | 蓝 `#2F5D8A` 表示过程/记忆;红 `#B5442E` 表示记录/警告/结论 |
|
|
28
|
+
| 字体 | 衬线标题与图注(Songti SC);等宽刻度(Menlo);公式 New Computer Modern |
|
|
29
|
+
| 运动 | 图表操作就是转场:轴生长、点落下、尺度变化、形变;不用淡入淡出 |
|
|
30
|
+
| 注释 | 钉在数据空间(值 + 像素偏移),缩放后仍跟着点 |
|
|
31
|
+
| 声音 | 每个 mark 一个音高;−14 LUFS |
|
|
32
|
+
|
|
33
|
+
## 3. manimgx 映射
|
|
34
|
+
|
|
35
|
+
- `theme.py`:`apply_config()`、`backdrop()`、`rule()`、`leader()`;
|
|
36
|
+
- 用 `Axes`、`Dot`、`Line`、`always_redraw` 实现“轴生长 / 点落下 / 割线滑动”;
|
|
37
|
+
- 上游的铅笔、纸纹、手写抖动没有实现;用颜色与注释层级表达同一语法;
|
|
38
|
+
- 字体替换:Newsreader → Songti SC;IBM Plex Mono → Menlo。
|
|
39
|
+
|
|
40
|
+
## 4. Do / Don't
|
|
41
|
+
|
|
42
|
+
**Do**
|
|
43
|
+
|
|
44
|
+
- 一次只讲一个想法;让轴、标注、尺度变化承担叙事;
|
|
45
|
+
- 注释贴着数据点,颜色有语义(蓝=过程,红=结论);
|
|
46
|
+
- 结论出现时停住,给读者读数时间。
|
|
47
|
+
|
|
48
|
+
**Don't**
|
|
49
|
+
|
|
50
|
+
- 不做 dashboard;不堆多个图;
|
|
51
|
+
- 不为了戏剧性拉伸色阶;
|
|
52
|
+
- 不用装饰性图标代替数据。
|
|
53
|
+
|
|
54
|
+
## 5. 与上游的差异
|
|
55
|
+
|
|
56
|
+
- 上游是 Canvas 2D + 手写笔刷;这里是矢量平涂,质感更干净;
|
|
57
|
+
- 上游 24 fps、50 秒量级;本实现 30 fps、10 秒量级;
|
|
58
|
+
- ColorBrewer RdBu 为标准色板;纸色/墨色取上游描述的中值,验收以帧为准。
|
|
Binary file
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"slug": "dataviz",
|
|
3
|
+
"en": "Data Storytelling",
|
|
4
|
+
"cn": "数据叙事",
|
|
5
|
+
"category_en": "Information & Keynote",
|
|
6
|
+
"category_cn": "信息与发布",
|
|
7
|
+
"uses": ["Data journalism", "Reports", "Climate & science"],
|
|
8
|
+
"source": "https://github.com/lemomo-ai/lemo-opuscar/tree/main/styles/dataviz",
|
|
9
|
+
"license": "style doc MIT; demo stills CC BY 4.0 (LemoLab)",
|
|
10
|
+
"implementation": "manimgx (theme.py + tokens.json)"
|
|
11
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"""dataviz 1.0.0 — Lemo-Opuscar 的 Data Storytelling,manimgx 实现层。
|
|
2
|
+
|
|
3
|
+
上游规范(MIT):styles/dataviz/STYLE.md
|
|
4
|
+
纸、墨、蓝红铅笔;颜色与字号只从 tokens.json 取。
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import json
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
import manimgx as m
|
|
13
|
+
|
|
14
|
+
TOKENS = json.loads(Path(__file__).with_name("tokens.json").read_text(encoding="utf-8"))
|
|
15
|
+
|
|
16
|
+
PAPER = TOKENS["colors"]["paper"]
|
|
17
|
+
PAPER_SHADOW = TOKENS["colors"]["paper_shadow"]
|
|
18
|
+
INK = TOKENS["colors"]["ink"]
|
|
19
|
+
GRID = TOKENS["colors"]["grid"]
|
|
20
|
+
MUTED = TOKENS["colors"]["muted"]
|
|
21
|
+
BLUE = TOKENS["colors"]["blue"]
|
|
22
|
+
RED = TOKENS["colors"]["red"]
|
|
23
|
+
FONT = TOKENS["typography"]["display"]["family"]
|
|
24
|
+
MONO = TOKENS["typography"]["mono"]["family"]
|
|
25
|
+
LAYOUT = TOKENS["layout"]
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def apply_config(width: int = 1920, height: int = 1080, fps: int = 30) -> None:
|
|
29
|
+
m.config.pixel_width = width
|
|
30
|
+
m.config.pixel_height = height
|
|
31
|
+
m.config.frame_rate = fps
|
|
32
|
+
m.config.background_color = PAPER
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def backdrop() -> m.Rectangle:
|
|
36
|
+
"""一张平整的奶油纸;不要渐变、不要投影。"""
|
|
37
|
+
return (
|
|
38
|
+
m.Rectangle(width=LAYOUT["frame"]["width"], height=LAYOUT["frame"]["height"], color=PAPER)
|
|
39
|
+
.set_fill(PAPER, 1)
|
|
40
|
+
.set_stroke(width=0)
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def rule(y: float, width: float, x: float = 0.0, color: str = GRID, stroke_width: float = 1.0) -> m.Line:
|
|
45
|
+
return m.Line([x - width / 2, y, 0], [x + width / 2, y, 0], color=color, stroke_width=stroke_width)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def leader(start, end, color: str = MUTED, stroke_width: float = 1.0) -> m.DashedLine:
|
|
49
|
+
return m.DashedLine(start, end, color=color, stroke_width=stroke_width, dash_length=0.08)
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "dataviz",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"source": "https://github.com/lemomo-ai/lemo-opuscar/tree/main/styles/dataviz",
|
|
5
|
+
"license": "style doc MIT; demo stills CC BY 4.0 (LemoLab)",
|
|
6
|
+
"colors": {
|
|
7
|
+
"paper": "#F4EDE1",
|
|
8
|
+
"paper_shadow": "#E8DECF",
|
|
9
|
+
"ink": "#2B2622",
|
|
10
|
+
"grid": "#C9BFAE",
|
|
11
|
+
"muted": "#7A6F63",
|
|
12
|
+
"blue": "#2F5D8A",
|
|
13
|
+
"red": "#B5442E",
|
|
14
|
+
"ramp_low": "#2166AC",
|
|
15
|
+
"ramp_mid": "#F7F7F7",
|
|
16
|
+
"ramp_high": "#B2182B"
|
|
17
|
+
},
|
|
18
|
+
"typography": {
|
|
19
|
+
"display": { "family": "Songti SC", "weight": "BOLD", "size": 42 },
|
|
20
|
+
"body": { "family": "Songti SC", "size": 24 },
|
|
21
|
+
"mono": { "family": "Menlo", "size": 18 },
|
|
22
|
+
"math": { "family": "New Computer Modern", "size": 56 }
|
|
23
|
+
},
|
|
24
|
+
"layout": {
|
|
25
|
+
"aspect": "16:9",
|
|
26
|
+
"frame": { "width": 14.222, "height": 8.0 },
|
|
27
|
+
"margin": 0.55
|
|
28
|
+
},
|
|
29
|
+
"motion": { "fps": 30, "easing": "chart-operation", "hold_min_seconds": 1.2 },
|
|
30
|
+
"audio": { "target_lufs": -14, "true_peak_db": -1 }
|
|
31
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: swiss-motion
|
|
3
|
+
version: 1.0.0
|
|
4
|
+
source: https://github.com/lemomo-ai/lemo-opuscar/tree/main/styles/swiss-motion
|
|
5
|
+
license: MIT(上游风格文档);demo 静帧 CC BY 4.0(LemoLab)
|
|
6
|
+
status: adapted
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Swiss Motion Graphics · 瑞士动态排版
|
|
10
|
+
|
|
11
|
+
> 上游:Lemo-Opuscar `styles/swiss-motion`(MIT)。本文件是**改编**,不是原样复制:
|
|
12
|
+
> 去掉了 WebGL/Canvas 专属实现,保留不变量与语法;数值落在 `tokens.json`。
|
|
13
|
+
|
|
14
|
+
## 1. 定位
|
|
15
|
+
|
|
16
|
+
国际主义排版风格在动:模块网格、一种字体、一个信号色,运动像印刷机一样精确。
|
|
17
|
+
|
|
18
|
+
- 适合:公式、定义、数据结论、品牌/活动信息;
|
|
19
|
+
- 不适合:角色叙事、手绘、颗粒质感、连续镜头。
|
|
20
|
+
|
|
21
|
+
## 2. 不变量
|
|
22
|
+
|
|
23
|
+
| 项 | 规则 |
|
|
24
|
+
|---|---|
|
|
25
|
+
| 底色 | 纸灰 `#EAE9E5`,纯平;不用渐变、投影、颗粒 |
|
|
26
|
+
| 墨色 | `#111111`;辅助灰 `#6B6B6B`;规矩线 `#C9C7C1` |
|
|
27
|
+
| 强调 | 全片只有一个信号色 `#E30613`,只给“例外/结论/正在动的东西” |
|
|
28
|
+
| 字体 | 一种无衬线,粗细分层;标题最多 3 级 |
|
|
29
|
+
| 网格 | 16:9 画 12 栏;栏线只在页边距露出短刻度 |
|
|
30
|
+
| 运动 | 两个曲线:精确 ease `cubic-bezier(.7,0,.2,1)` 与 linear;无回弹、无弹簧 |
|
|
31
|
+
| 入场 | 划入 / 裁切揭示;不用淡入淡出 |
|
|
32
|
+
| 声音 | 干净的节拍与印刷 Foley;−14 LUFS;不要颗粒 |
|
|
33
|
+
|
|
34
|
+
## 3. manimgx 映射
|
|
35
|
+
|
|
36
|
+
- `theme.py`:`apply_config()`、`backdrop()`、`columns()`、`rule()`;
|
|
37
|
+
- 字体替换:上游 Archivo/Inter → `Noto Sans`;等宽 → `Menlo`;公式 → `New Computer Modern`;
|
|
38
|
+
- manimgx 没有 clip-path 遮罩,入场用 `Create` / `Write` 代替裁切揭示;
|
|
39
|
+
- 网格用上下页边距刻度,避免版式检查把“线穿字”判成问题。
|
|
40
|
+
|
|
41
|
+
## 4. Do / Don't
|
|
42
|
+
|
|
43
|
+
**Do**
|
|
44
|
+
|
|
45
|
+
- 一个画面只讲一个结论;先有线,再有字;
|
|
46
|
+
- 信号色只出现一次,结论落在它上面;
|
|
47
|
+
- 大字号与小字号并置,中间留白。
|
|
48
|
+
|
|
49
|
+
**Don't**
|
|
50
|
+
|
|
51
|
+
- 不用第二个强调色;不用 Helvetica/Akzidenz 字体文件;
|
|
52
|
+
- 不用渐变球、辉光、投影、颗粒;
|
|
53
|
+
- 不让文字压网格线;不把每个点做成卡片。
|
|
54
|
+
|
|
55
|
+
## 5. 与上游的差异
|
|
56
|
+
|
|
57
|
+
- 上游用 WebGL/Canvas 做裁切揭示与精确网格;这里用 manimgx 的矢量对象近似;
|
|
58
|
+
- 上游 24 fps;本实现默认 30 fps(可在 `tokens.motion.fps` 改回 24);
|
|
59
|
+
- 上游字体不随包分发;这里统一用系统/内置 Noto 与 Menlo,观感会有差异,验收以帧为准。
|
|
Binary file
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"slug": "swiss-motion",
|
|
3
|
+
"en": "Swiss Motion Graphics",
|
|
4
|
+
"cn": "瑞士动态排版",
|
|
5
|
+
"category_en": "Graphic & Type",
|
|
6
|
+
"category_cn": "图形与排版",
|
|
7
|
+
"uses": ["Brand identity", "Events", "Design education"],
|
|
8
|
+
"source": "https://github.com/lemomo-ai/lemo-opuscar/tree/main/styles/swiss-motion",
|
|
9
|
+
"license": "style doc MIT; demo stills CC BY 4.0 (LemoLab)",
|
|
10
|
+
"implementation": "manimgx (theme.py + tokens.json)"
|
|
11
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"""swiss-motion 1.0.0 — Lemo-Opuscar 的 Swiss Motion Graphics,manimgx 实现层。
|
|
2
|
+
|
|
3
|
+
上游客服规范(MIT):styles/swiss-motion/STYLE.md
|
|
4
|
+
实现只依赖 tokens.json;颜色/字号/网格从这里取,不散落在场景里。
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import json
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
import manimgx as m
|
|
13
|
+
|
|
14
|
+
TOKENS = json.loads(Path(__file__).with_name("tokens.json").read_text(encoding="utf-8"))
|
|
15
|
+
|
|
16
|
+
GROUND = TOKENS["colors"]["ground"]
|
|
17
|
+
PAPER = TOKENS["colors"]["paper"]
|
|
18
|
+
INK = TOKENS["colors"]["ink"]
|
|
19
|
+
MUTED = TOKENS["colors"]["muted"]
|
|
20
|
+
RULE = TOKENS["colors"]["rule"]
|
|
21
|
+
SIGNAL = TOKENS["colors"]["signal"]
|
|
22
|
+
FONT = TOKENS["typography"]["display"]["family"]
|
|
23
|
+
MOTION = TOKENS["motion"]
|
|
24
|
+
LAYOUT = TOKENS["layout"]
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def apply_config(width: int = 1920, height: int = 1080, fps: int = 30) -> None:
|
|
28
|
+
m.config.pixel_width = width
|
|
29
|
+
m.config.pixel_height = height
|
|
30
|
+
m.config.frame_rate = fps
|
|
31
|
+
m.config.background_color = GROUND
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def backdrop() -> m.Rectangle:
|
|
35
|
+
"""Flat paper ground. Swiss 规定无渐变、无投影、无颗粒。"""
|
|
36
|
+
return (
|
|
37
|
+
m.Rectangle(width=LAYOUT["frame"]["width"], height=LAYOUT["frame"]["height"], color=GROUND)
|
|
38
|
+
.set_fill(GROUND, 1)
|
|
39
|
+
.set_stroke(width=0)
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def columns(count: int = 12, margin: float = 0.5, opacity: float = 0.55) -> m.VGroup:
|
|
44
|
+
"""12 栏规矩线:只在上下页边距画短刻度,不穿过文字。"""
|
|
45
|
+
width = LAYOUT["frame"]["width"] - margin * 2
|
|
46
|
+
x0 = -LAYOUT["frame"]["width"] / 2 + margin
|
|
47
|
+
marks = m.VGroup()
|
|
48
|
+
for i in range(count + 1):
|
|
49
|
+
x = x0 + width * i / count
|
|
50
|
+
top = m.Line([x, 3.55, 0], [x, 3.78, 0], color=RULE, stroke_width=1.0)
|
|
51
|
+
bottom = m.Line([x, -3.55, 0], [x, -3.32, 0], color=RULE, stroke_width=1.0)
|
|
52
|
+
top.set_stroke(opacity=opacity)
|
|
53
|
+
bottom.set_stroke(opacity=opacity)
|
|
54
|
+
marks.add(top, bottom)
|
|
55
|
+
return marks
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def rule(y: float, width: float, x: float = 0.0, color: str = RULE, stroke_width: float = 1.2) -> m.Line:
|
|
59
|
+
return m.Line([x - width / 2, y, 0], [x + width / 2, y, 0], color=color, stroke_width=stroke_width)
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "swiss-motion",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"source": "https://github.com/lemomo-ai/lemo-opuscar/tree/main/styles/swiss-motion",
|
|
5
|
+
"license": "style doc MIT; demo stills CC BY 4.0 (LemoLab)",
|
|
6
|
+
"colors": {
|
|
7
|
+
"ground": "#EAE9E5",
|
|
8
|
+
"paper": "#FFFFFF",
|
|
9
|
+
"ink": "#111111",
|
|
10
|
+
"muted": "#6B6B6B",
|
|
11
|
+
"rule": "#C9C7C1",
|
|
12
|
+
"signal": "#E30613"
|
|
13
|
+
},
|
|
14
|
+
"typography": {
|
|
15
|
+
"display": { "family": "Noto Sans", "weight": "BOLD", "size": 64 },
|
|
16
|
+
"body": { "family": "Noto Sans", "size": 26 },
|
|
17
|
+
"mono": { "family": "Menlo", "size": 18 },
|
|
18
|
+
"math": { "family": "New Computer Modern", "size": 96 }
|
|
19
|
+
},
|
|
20
|
+
"layout": {
|
|
21
|
+
"aspect": "16:9",
|
|
22
|
+
"frame": { "width": 14.222, "height": 8.0 },
|
|
23
|
+
"margin": 0.5,
|
|
24
|
+
"columns": 12
|
|
25
|
+
},
|
|
26
|
+
"motion": {
|
|
27
|
+
"fps": 30,
|
|
28
|
+
"easing": "precise",
|
|
29
|
+
"snap_seconds": 0.4,
|
|
30
|
+
"hold_min_seconds": 1.0
|
|
31
|
+
},
|
|
32
|
+
"audio": { "target_lufs": -14, "true_peak_db": -1 }
|
|
33
|
+
}
|