skills/ppt-station-skill/ppt_station/composer/themes.py
wangyitong a65adcc2e5 Initial commit: merged, deduplicated, and vetted skill collection
Sources: extracted from two upstream archives (skill-repo, skills-main),
merged with the following policy:

- 15 broken symlinks (pointing to /Users/jameslee/.cc-switch/skills or
  ../../.agents/skills on a foreign machine) discarded
- 3 real name collisions with identical content (ai-pair, ifind-http-api,
  zhipu-websearch) kept as one copy
- Functional overlaps deduped keeping the strongest variant:
  - docx family: kept docx (official, full toolchain) + docx-cn
    (GB/T 9704 Chinese official-document constants),
    dropped docx_writer (no scripts, name collided with docx)
  - humanizer family: kept humanizer-zh (6 zh reference docs),
    dropped humanizer (en, redundant for CN workflow)
- Skills that only ran in a foreign environment removed:
  ablemind-ops, app-publish, hlb-design-system, openclaw-adj-skill,
  claude-driver
- alphapai excluded from this public repo because its SKILL.md hard-coded
  live credentials

Result: 25 skills, 572 files, ~7.5 MB.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-13 14:47:12 +08:00

295 lines
9.4 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""主题配置 — 配色、字体、间距、结构几何"""
# ── 新增 tokens 的默认值(供旧主题/自定义主题 fallback ──────────────
_LAYOUT_DEFAULTS = {
# 字号层级
"cover_title_size": 36,
"page_title_size": 16,
"chart_subtitle_size": 11,
"footer_size": 8,
# 幻灯片尺寸 (inches)
"slide_w": 13.333, # 16:9 default
"slide_h": 7.5,
# 结构几何 (inches)
"header_y": 0.25,
"divider_y": 0.80,
"divider_h": 0.015,
"content_y": 1.00,
"content_h": 5.80,
"footer_y": 7.10,
# content_w 由 _theme() 自动计算: slide_w - 2 * margin
# 表格样式
"table_header_bg": "1F4E79",
"table_zebra_even": "F8FAFC",
"table_zebra_odd": "FFFFFF",
# 图表默认
"chart_default_scheme": "default",
}
def _theme(base: dict) -> dict:
"""合并 layout defaults保证每个 theme 都有完整 tokens"""
merged = {**_LAYOUT_DEFAULTS, **base}
# table_header_bg 默认跟随 primary
if "table_header_bg" not in base:
merged["table_header_bg"] = base.get("primary", _LAYOUT_DEFAULTS["table_header_bg"])
# content_w = slide_w - 2 * margin自动派生
if "content_w" not in base:
merged["content_w"] = merged["slide_w"] - 2 * merged.get("margin", 0.6)
return merged
THEMES = {
"midnight": _theme({
"name": "Midnight Executive",
"primary": "1E2761",
"secondary": "CADCFC",
"accent": "E8B931",
"bg_dark": "1E2761",
"bg_light": "F5F7FA",
"card_bg": "FFFFFF",
"text_dark": "1E2761",
"text_light": "FFFFFF",
"text_muted": "8896AB",
"border": "E2E8F0",
"positive": "10B981",
"negative": "EF4444",
"header_font": "微软雅黑",
"body_font": "微软雅黑",
"margin": 0.6,
"title_size": 28,
"subtitle_size": 18,
"body_size": 14,
"caption_size": 10,
"kpi_size": 44,
"chart_default_scheme": "midnight",
}),
"charcoal": _theme({
"name": "Charcoal Minimal",
"primary": "36454F",
"secondary": "F2F2F2",
"accent": "E8B931",
"bg_dark": "36454F",
"bg_light": "F8F8F8",
"card_bg": "FFFFFF",
"text_dark": "212121",
"text_light": "FFFFFF",
"text_muted": "808080",
"border": "E0E0E0",
"positive": "10B981",
"negative": "EF4444",
"header_font": "微软雅黑",
"body_font": "微软雅黑",
"margin": 0.6,
"title_size": 28,
"subtitle_size": 18,
"body_size": 14,
"caption_size": 10,
"kpi_size": 44,
"chart_default_scheme": "charcoal",
}),
# ── 新主题(设计源自投行级研报分析 + 行业报告研究) ──────────────
"jp_finance": _theme({
"name": "IB Finance",
"primary": "1B3D6E",
"secondary": "00A5BD",
"accent": "C9A84C",
"bg_dark": "0F2340",
"bg_light": "F4F6FA",
"card_bg": "FFFFFF",
"text_dark": "1A2744",
"text_light": "FFFFFF",
"text_muted": "6B7C93",
"border": "C8D6E5",
"positive": "0B7B3E",
"negative": "C0392B",
"header_font": "微软雅黑",
"body_font": "微软雅黑",
"margin": 0.6,
"title_size": 28,
"subtitle_size": 18,
"body_size": 12,
"caption_size": 9,
"kpi_size": 44,
# IB-specific overrides
"table_header_bg": "1B3D6E",
"chart_default_scheme": "jp_finance",
}),
"pension_warm": _theme({
"name": "Pension Warm",
"primary": "2E5FA3",
"secondary": "7BA7BC",
"accent": "D4903F",
"bg_dark": "1C3557",
"bg_light": "F7F4F0",
"card_bg": "FDFAF7",
"text_dark": "2C3E50",
"text_light": "FAFAFA",
"text_muted": "7F8C8D",
"border": "D5C9B8",
"positive": "27AE60",
"negative": "E74C3C",
"header_font": "微软雅黑",
"body_font": "微软雅黑",
"margin": 0.6,
"title_size": 28,
"subtitle_size": 18,
"body_size": 12,
"caption_size": 9,
"kpi_size": 44,
"chart_default_scheme": "pension_warm",
}),
"tech_blue": _theme({
"name": "Tech Blue",
"primary": "1565C0",
"secondary": "29B6F6",
"accent": "00BFA5",
"bg_dark": "0A1628",
"bg_light": "F0F4FF",
"card_bg": "FFFFFF",
"text_dark": "102040",
"text_light": "E8F1FF",
"text_muted": "7A8CA8",
"border": "C0D0E8",
"positive": "00C896",
"negative": "FF4B5C",
"header_font": "微软雅黑",
"body_font": "微软雅黑",
"margin": 0.6,
"title_size": 28,
"subtitle_size": 18,
"body_size": 12,
"caption_size": 9,
"kpi_size": 44,
"chart_default_scheme": "tech_blue",
}),
"state_red": _theme({
"name": "State Red",
"primary": "8B0000",
"secondary": "B8860B",
"accent": "DAA520",
"bg_dark": "5C0000",
"bg_light": "FDF8F0",
"card_bg": "FFFFFF",
"text_dark": "2C1810",
"text_light": "FFF8E7",
"text_muted": "8C7B6B",
"border": "E0CFAA",
"positive": "2E6B30",
"negative": "8B0000",
"header_font": "微软雅黑",
"body_font": "微软雅黑",
"margin": 0.6,
"title_size": 28,
"subtitle_size": 18,
"body_size": 12,
"caption_size": 9,
"kpi_size": 44,
"chart_default_scheme": "state_red",
}),
"esg_green": _theme({
"name": "ESG Green",
"primary": "1A5C2A",
"secondary": "4CAF50",
"accent": "8BC34A",
"bg_dark": "0D3318",
"bg_light": "F1F8F2",
"card_bg": "FFFFFF",
"text_dark": "1B2B1C",
"text_light": "E8F5E9",
"text_muted": "5C7A5E",
"border": "B8D8BA",
"positive": "2E7D32",
"negative": "C62828",
"header_font": "微软雅黑",
"body_font": "微软雅黑",
"margin": 0.6,
"title_size": 28,
"subtitle_size": 18,
"body_size": 12,
"caption_size": 9,
"kpi_size": 44,
"chart_default_scheme": "esg_green",
}),
# ── 研究级主题Morningstar / 宏观研究风格) ──────────────────────
"morningstar": _theme({
"name": "Morningstar Research",
"primary": "1D2B3A", # 深石板灰
"secondary": "5A9BD5", # 中蓝
"accent": "E67E22", # 暖橙
"bg_dark": "1A1A2E", "bg_light": "FAFBFC", "card_bg": "FFFFFF",
"text_dark": "2C3E50", "text_light": "F8F9FA", "text_muted": "7B8794",
"border": "DEE2E6", "positive": "28A745", "negative": "DC3545",
"header_font": "微软雅黑", "body_font": "微软雅黑",
"margin": 0.6, "title_size": 28, "subtitle_size": 18,
"body_size": 11, "caption_size": 9, "kpi_size": 36,
"chart_default_scheme": "morningstar",
}),
"macro_research": _theme({
"name": "Macro Research",
"primary": "2C3E50", # 深藏蓝灰
"secondary": "3498DB", # 净蓝
"accent": "95A5A6", # 中性灰(低调)
"bg_dark": "1A252F", "bg_light": "F7F9FB", "card_bg": "FFFFFF",
"text_dark": "2C3E50", "text_light": "ECF0F1", "text_muted": "95A5A6",
"border": "D5D8DC", "positive": "27AE60", "negative": "E74C3C",
"header_font": "微软雅黑", "body_font": "微软雅黑",
"margin": 0.6, "title_size": 28, "subtitle_size": 18,
"body_size": 11, "caption_size": 9, "kpi_size": 36,
"chart_default_scheme": "macro_research",
}),
"dark_pro": _theme({
"name": "Dark Pro",
"primary": "00BFFF",
"secondary": "7B68EE",
"accent": "FFD700",
"bg_dark": "080808",
"bg_light": "141820",
"card_bg": "1E2430",
"text_dark": "E0E8F0",
"text_light": "FFFFFF",
"text_muted": "6880A0",
"border": "2A3848",
"positive": "00E676",
"negative": "FF1744",
"header_font": "微软雅黑",
"body_font": "微软雅黑",
"margin": 0.6,
"title_size": 30,
"subtitle_size": 20,
"body_size": 13,
"caption_size": 10,
"kpi_size": 48,
"chart_default_scheme": "dark_pro",
}),
}
DEFAULT_THEME = THEMES["midnight"]
# ── 比例预设 ──────────────────────────────────────────────────────────
ASPECT_4_3 = {"slide_w": 10.0, "slide_h": 7.5}
ASPECT_16_9 = {"slide_w": 13.333, "slide_h": 7.5}
def resolve_theme(theme, aspect_ratio=None) -> dict:
"""解析主题参数,确保所有 tokens 都有值
Args:
theme: 主题名称(str) 或主题字典(dict) 或 None
aspect_ratio: 可选比例 "4:3""16:9"
Returns:
完整的主题字典(包含所有 layout tokens
"""
if isinstance(theme, str):
result = dict(THEMES.get(theme, DEFAULT_THEME))
elif isinstance(theme, dict):
result = {**DEFAULT_THEME, **theme}
else:
result = dict(DEFAULT_THEME)
if aspect_ratio == "4:3":
result.update(ASPECT_4_3)
result["content_w"] = result["slide_w"] - 2 * result.get("margin", 0.6)
return result