Initial commit: add all skills files
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
521
minimax-pdf/scripts/palette.py
Normal file
521
minimax-pdf/scripts/palette.py
Normal 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()
|
||||
Reference in New Issue
Block a user