Initial commit: add all skills files

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-10 16:52:49 +08:00
commit 6487becf60
396 changed files with 108871 additions and 0 deletions

View File

@@ -0,0 +1,521 @@
#!/usr/bin/env python3
"""
palette.py — Infer design tokens from document metadata.
Usage:
python3 palette.py --title "AI Trends 2025" --type report --out tokens.json
python3 palette.py --title "John Doe Resume" --type resume --out tokens.json
python3 palette.py --meta meta.json --out tokens.json
Outputs tokens.json consumed by all downstream scripts.
Cover fonts are loaded via Google Fonts @import in the cover HTML (no local caching).
Body fonts always use ReportLab system fonts (Times-Bold / Helvetica).
Exit codes: 0 success, 1 bad args, 3 write error
"""
import argparse
import json
import sys
# ── Palette library ────────────────────────────────────────────────────────────
# Each entry: cover colors + cover_pattern + mood
PALETTES = {
"report": {
# Charcoal blue-grey cover; muted steel blue accent — authoritative, not flashy
"cover_bg": "#1B2A38",
"accent": "#3B6D8A",
"accent_lt": "#E6EFF5",
"text_light": "#EDE9E2",
"page_bg": "#FAFAF8",
"dark": "#1A1E24",
"body_text": "#2C2C30",
"muted": "#7A7A84",
"cover_pattern": "fullbleed",
"mood": "authoritative",
},
"proposal": {
# Dark charcoal cover; slate grey-blue accent — confident, understated
"cover_bg": "#22272E",
"accent": "#4E6070",
"accent_lt": "#EAECEE",
"text_light": "#EDE9E2",
"page_bg": "#FAFAF7",
"dark": "#18191E",
"body_text": "#28282E",
"muted": "#7A7870",
"cover_pattern": "split",
"mood": "confident",
},
"resume": {
# White; deep navy accent — clean and unambiguous
"cover_bg": "#FFFFFF",
"accent": "#1C3557",
"accent_lt": "#E8EEF5",
"text_light": "#FFFFFF",
"page_bg": "#FFFFFF",
"dark": "#111111",
"body_text": "#222222",
"muted": "#888888",
"cover_pattern": "typographic",
"mood": "clean",
},
"portfolio": {
# Near-black charcoal; cool slate grey accent — subdued professional
"cover_bg": "#191C20",
"accent": "#6A7A88",
"accent_lt": "#EAECEE",
"text_light": "#EDE9E4",
"page_bg": "#F8F8F8",
"dark": "#18191E",
"body_text": "#28282E",
"muted": "#8A8A96",
"cover_pattern": "atmospheric",
"mood": "expressive",
},
"academic": {
# Warm white; classic navy accent — scholarly standard
"cover_bg": "#F5F4F0",
"accent": "#2A436A",
"accent_lt": "#E6EBF4",
"text_light": "#FFFFFF",
"page_bg": "#F5F4F0",
"dark": "#1A1A28",
"body_text": "#1E1E2A",
"muted": "#686877",
"cover_pattern": "typographic",
"mood": "scholarly",
},
"general": {
# Dark slate; muted steel accent — neutral, no-nonsense
"cover_bg": "#1F2329",
"accent": "#4A6070",
"accent_lt": "#E6EAEC",
"text_light": "#EEEBE5",
"page_bg": "#F8F6F2",
"dark": "#1A1A1A",
"body_text": "#2C2C2C",
"muted": "#888888",
"cover_pattern": "fullbleed",
"mood": "neutral",
},
# ── Extended types — each uses a distinct new cover pattern ─────────────────
"minimal": {
# Warm off-white; dark neutral grey — truly restrained, no color signal
"cover_bg": "#F7F6F4",
"accent": "#4A4A4A",
"accent_lt": "#EBEBEA",
"text_light": "#F7F6F4",
"page_bg": "#F7F6F4",
"dark": "#111111",
"body_text": "#222222",
"muted": "#999999",
"cover_pattern": "minimal",
"mood": "restrained",
},
"stripe": {
# Near-black; charcoal slate accent — structured, no-nonsense
"cover_bg": "#1E222A",
"accent": "#4A5568",
"accent_lt": "#EAECEE",
"text_light": "#FFFFFF",
"page_bg": "#F8F8F7",
"dark": "#0E1117",
"body_text": "#262630",
"muted": "#888898",
"cover_pattern": "stripe",
"mood": "bold",
},
"diagonal": {
# Deep navy; muted slate-blue accent — dignified, controlled
"cover_bg": "#1A2535",
"accent": "#3D5A72",
"accent_lt": "#E4EBF0",
"text_light": "#EEF0F5",
"page_bg": "#F8FAFC",
"dark": "#0F1A2A",
"body_text": "#1E2C3A",
"muted": "#7A8A96",
"cover_pattern": "diagonal",
"mood": "dynamic",
},
"frame": {
# Warm parchment; dark muted brown — classical, formal
"cover_bg": "#F5F2EC",
"accent": "#5C4A38",
"accent_lt": "#EAE5DE",
"text_light": "#F5F2EC",
"page_bg": "#F5F2EC",
"dark": "#2A1E14",
"body_text": "#2C2018",
"muted": "#9A8A78",
"cover_pattern": "frame",
"mood": "classical",
},
"editorial": {
# White; deep burgundy accent — editorial weight without the shout
"cover_bg": "#FFFFFF",
"accent": "#7A2B36",
"accent_lt": "#EEE4E5",
"text_light": "#FFFFFF",
"page_bg": "#FFFFFF",
"dark": "#0A0A0A",
"body_text": "#1A1A1A",
"muted": "#777777",
"cover_pattern": "editorial",
"mood": "editorial",
},
# ── New patterns (v2) ────────────────────────────────────────────────────────
"magazine": {
# Warm linen; deep navy accent — formal publication standard
"cover_bg": "#F0EEE9",
"accent": "#1C3557",
"accent_lt": "#E4EBF3",
"text_light": "#FFFFFF",
"page_bg": "#F0EEE9",
"dark": "#0D1A2B",
"body_text": "#2A2A2A",
"muted": "#888888",
"cover_pattern": "magazine",
"mood": "magazine",
},
"darkroom": {
# Deep navy; muted steel-blue accent — premium, controlled
"cover_bg": "#151C27",
"accent": "#3D5A7A",
"accent_lt": "#E2EBF2",
"text_light": "#EDE9E2",
"page_bg": "#F7F7F5",
"dark": "#0A1018",
"body_text": "#2C2C2C",
"muted": "#8A9AB0",
"cover_pattern": "darkroom",
"mood": "darkroom",
},
"terminal": {
# Near-black; forest green accent — technical, serious (not neon)
"cover_bg": "#0D1117",
"accent": "#3D7A5C",
"accent_lt": "#E2EEE8",
"text_light": "#E6EDF3",
"page_bg": "#F8F8F6",
"dark": "#010409",
"body_text": "#2C2C2C",
"muted": "#5A7A6A",
"cover_pattern": "terminal",
"mood": "terminal",
},
"poster": {
# White; near-black accent sidebar — stark, unambiguous
"cover_bg": "#FFFFFF",
"accent": "#0A0A0A",
"accent_lt": "#EBEBEA",
"text_light": "#FFFFFF",
"page_bg": "#FFFFFF",
"dark": "#0A0A0A",
"body_text": "#1A1A1A",
"muted": "#888888",
"cover_pattern": "poster",
"mood": "poster",
},
}
# ── Font pairs — CSS names for cover HTML, ReportLab names for body ─────────────
# cover uses Google Fonts via @import (no local disk caching needed)
# body always uses system fonts via ReportLab
FONT_PAIRS = {
"authoritative": {
"display_css": "Playfair Display",
"body_css": "IBM Plex Sans",
"gfonts_import": "https://fonts.googleapis.com/css2?family=Playfair+Display:wght@700;900&family=IBM+Plex+Sans:ital,wght@0,400;0,600;1,400&display=swap",
"display_rl": "Times-Bold",
"body_rl": "Helvetica",
"body_b_rl": "Helvetica-Bold",
},
"confident": {
"display_css": "Syne",
"body_css": "Nunito Sans",
"gfonts_import": "https://fonts.googleapis.com/css2?family=Syne:wght@600;800&family=Nunito+Sans:wght@400;600;700&display=swap",
"display_rl": "Times-Bold",
"body_rl": "Helvetica",
"body_b_rl": "Helvetica-Bold",
},
"clean": {
"display_css": "DM Serif Display",
"body_css": "DM Sans",
"gfonts_import": "https://fonts.googleapis.com/css2?family=DM+Serif+Display&family=DM+Sans:wght@300;400;500&display=swap",
"display_rl": "Times-Bold",
"body_rl": "Helvetica",
"body_b_rl": "Helvetica-Bold",
},
"expressive": {
"display_css": "Fraunces",
"body_css": "Inter",
"gfonts_import": "https://fonts.googleapis.com/css2?family=Fraunces:ital,wght@0,700;0,900;1,900&family=Inter:wght@300;400;500&display=swap",
"display_rl": "Times-Bold",
"body_rl": "Helvetica",
"body_b_rl": "Helvetica-Bold",
},
"scholarly": {
"display_css": "EB Garamond",
"body_css": "Source Sans 3",
"gfonts_import": "https://fonts.googleapis.com/css2?family=EB+Garamond:ital,wght@0,400;0,700;1,400&family=Source+Sans+3:wght@400;600&display=swap",
"display_rl": "Times-Bold",
"body_rl": "Helvetica",
"body_b_rl": "Helvetica-Bold",
},
"neutral": {
"display_css": "Outfit",
"body_css": "Outfit",
"gfonts_import": "https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;700;900&display=swap",
"display_rl": "Times-Bold",
"body_rl": "Helvetica",
"body_b_rl": "Helvetica-Bold",
},
"restrained": {
"display_css": "Cormorant Garamond",
"body_css": "Jost",
"gfonts_import": "https://fonts.googleapis.com/css2?family=Cormorant+Garamond:ital,wght@0,300;0,600;1,300&family=Jost:wght@300;400;500&display=swap",
"display_rl": "Times-Bold",
"body_rl": "Helvetica",
"body_b_rl": "Helvetica-Bold",
},
"bold": {
"display_css": "Barlow Condensed",
"body_css": "Barlow",
"gfonts_import": "https://fonts.googleapis.com/css2?family=Barlow+Condensed:wght@700;900&family=Barlow:wght@400;500;600&display=swap",
"display_rl": "Times-Bold",
"body_rl": "Helvetica",
"body_b_rl": "Helvetica-Bold",
},
"dynamic": {
"display_css": "Montserrat",
"body_css": "Montserrat",
"gfonts_import": "https://fonts.googleapis.com/css2?family=Montserrat:ital,wght@0,300;0,700;0,900;1,400&display=swap",
"display_rl": "Times-Bold",
"body_rl": "Helvetica",
"body_b_rl": "Helvetica-Bold",
},
"classical": {
"display_css": "Cormorant",
"body_css": "Crimson Pro",
"gfonts_import": "https://fonts.googleapis.com/css2?family=Cormorant:ital,wght@0,400;0,700;1,400&family=Crimson+Pro:wght@400;600&display=swap",
"display_rl": "Times-Bold",
"body_rl": "Helvetica",
"body_b_rl": "Helvetica-Bold",
},
"editorial": {
"display_css": "Bebas Neue",
"body_css": "Libre Franklin",
"gfonts_import": (
"https://fonts.googleapis.com/css2?family=Bebas+Neue"
"&family=Libre+Franklin:ital,wght@0,400;0,700;1,400&display=swap"
),
"display_rl": "Times-Bold",
"body_rl": "Helvetica",
"body_b_rl": "Helvetica-Bold",
},
# ── New moods (v2) ───────────────────────────────────────────────────────────
"magazine": {
"display_css": "Playfair Display",
"body_css": "EB Garamond",
"gfonts_import": (
"https://fonts.googleapis.com/css2?family=Playfair+Display"
":ital,wght@0,700;0,900;1,700"
"&family=EB+Garamond:ital,wght@0,400;0,600;1,400&display=swap"
),
"display_rl": "Times-Bold",
"body_rl": "Helvetica",
"body_b_rl": "Helvetica-Bold",
},
"darkroom": {
"display_css": "Playfair Display",
"body_css": "EB Garamond",
"gfonts_import": (
"https://fonts.googleapis.com/css2?family=Playfair+Display"
":ital,wght@0,700;0,900;1,700"
"&family=EB+Garamond:ital,wght@0,400;0,600;1,400&display=swap"
),
"display_rl": "Times-Bold",
"body_rl": "Helvetica",
"body_b_rl": "Helvetica-Bold",
},
"terminal": {
"display_css": "Space Mono",
"body_css": "Space Mono",
"gfonts_import": (
"https://fonts.googleapis.com/css2?family=Space+Mono"
":ital,wght@0,400;0,700;1,400&display=swap"
),
"display_rl": "Courier-Bold",
"body_rl": "Courier",
"body_b_rl": "Courier-Bold",
},
"poster": {
"display_css": "Barlow Condensed",
"body_css": "Courier Prime",
"gfonts_import": (
"https://fonts.googleapis.com/css2?family=Barlow+Condensed"
":wght@700;900"
"&family=Courier+Prime:ital,wght@0,400;0,700;1,400&display=swap"
),
"display_rl": "Times-Bold",
"body_rl": "Courier",
"body_b_rl": "Courier-Bold",
},
}
SYSTEM_FALLBACK = {
"display_css": "Georgia",
"body_css": "Arial",
"gfonts_import": "",
"display_rl": "Times-Bold",
"body_rl": "Helvetica",
"body_b_rl": "Helvetica-Bold",
}
# ── Colour helpers ──────────────────────────────────────────────────────────────
def _hex_to_rgb(h: str) -> tuple:
h = h.lstrip("#")
return int(h[0:2], 16), int(h[2:4], 16), int(h[4:6], 16)
def _lighten(hex_color: str, factor: float = 0.09) -> str:
"""Blend hex_color toward white (factor = accent weight, 0=white, 1=full color)."""
r, g, b = _hex_to_rgb(hex_color)
return "#{:02X}{:02X}{:02X}".format(
round(r * factor + 255 * (1 - factor)),
round(g * factor + 255 * (1 - factor)),
round(b * factor + 255 * (1 - factor)),
)
# ── Token assembly ─────────────────────────────────────────────────────────────
def build_tokens(
title: str,
doc_type: str,
author: str = "",
date: str = "",
accent_override: str = "",
cover_bg_override: str = "",
) -> dict:
palette = PALETTES.get(doc_type, PALETTES["general"]).copy()
mood = palette["mood"]
font_pair = FONT_PAIRS.get(mood, SYSTEM_FALLBACK)
# Apply caller-supplied overrides before token assembly
if accent_override:
palette["accent"] = accent_override
palette["accent_lt"] = _lighten(accent_override, 0.09)
if cover_bg_override:
palette["cover_bg"] = cover_bg_override
tokens = {
# Identity
"title": title,
"author": author,
"date": date,
"doc_type": doc_type,
# Palette
"cover_bg": palette["cover_bg"],
"accent": palette["accent"],
"accent_lt": palette["accent_lt"],
"text_light": palette["text_light"],
"page_bg": palette["page_bg"],
"dark": palette["dark"],
"body_text": palette["body_text"],
"muted": palette["muted"],
"cover_pattern": palette["cover_pattern"],
"mood": mood,
# Typography — CSS names for cover HTML (loaded via Google Fonts @import)
"font_display": font_pair["display_css"],
"font_body": font_pair["body_css"],
"gfonts_import": font_pair["gfonts_import"],
# Typography — ReportLab system font names for body pages
"font_display_rl": font_pair["display_rl"],
"font_body_rl": font_pair["body_rl"],
"font_body_b_rl": font_pair["body_b_rl"],
# Legacy keys (kept so render_body.py's register_fonts is a no-op)
"font_heading": font_pair["display_rl"],
"font_body_b": font_pair["body_b_rl"],
"font_paths": {},
# Type scale (pt)
"size_display": 54,
"size_h1": 22,
"size_h2": 15,
"size_h3": 11.5,
"size_body": 10.5,
"size_caption": 8.5,
"size_meta": 8,
# Layout (pt, 1cm ≈ 28.35pt)
"margin_left": 79, # 2.8cm
"margin_right": 79,
"margin_top": 79,
"margin_bottom": 71, # 2.5cm
"section_gap": 26,
"para_gap": 8,
"line_gap": 17,
}
return tokens
# ── CLI ───────────────────────────────────────────────────────────────────────
def main():
parser = argparse.ArgumentParser(description="Generate design tokens from document metadata")
parser.add_argument("--title", default="Untitled Document")
parser.add_argument("--type", default="general",
choices=list(PALETTES.keys()),
help="Document type: " + ", ".join(PALETTES.keys()))
parser.add_argument("--author", default="")
parser.add_argument("--date", default="")
parser.add_argument("--meta", help="JSON file with title/type/author/date keys")
parser.add_argument("--accent", default="",
help="Override accent colour (hex, e.g. #2D6A8F). "
"accent_lt is auto-derived by lightening toward white.")
parser.add_argument("--cover-bg", default="",
help="Override cover background colour (hex).")
parser.add_argument("--out", default="tokens.json")
args = parser.parse_args()
if args.meta:
try:
with open(args.meta) as f:
meta = json.load(f)
args.title = meta.get("title", args.title)
args.type = meta.get("type", args.type)
args.author = meta.get("author", args.author)
args.date = meta.get("date", args.date)
except Exception as e:
print(json.dumps({"status": "error", "error": str(e)}), file=sys.stderr)
sys.exit(1)
tokens = build_tokens(
args.title, args.type, args.author, args.date,
accent_override=args.accent,
cover_bg_override=getattr(args, "cover_bg", ""),
)
try:
with open(args.out, "w") as f:
json.dump(tokens, f, indent=2)
except Exception as e:
print(json.dumps({"status": "error", "error": str(e)}), file=sys.stderr)
sys.exit(3)
print(json.dumps({
"status": "ok",
"out": args.out,
"mood": tokens["mood"],
"pattern": tokens["cover_pattern"],
"fonts": f'{tokens["font_display"]} / {tokens["font_body"]}',
}))
if __name__ == "__main__":
main()