* feat: restructure ui-design.pen as complete design system Major restructuring of the design canvas into 5 organized sections: 0. Cover — title, TOC with links to all sections 1. Foundations — colors, typography, spacing scale, heights, shadows 2. Components — 23 reusable components (atoms, molecules, organisms) 3. Full Layouts — 5 app variants at 1440×900 4. Feature Specs — 65 existing frames reorganized into 10 functional groups Components created (reusable, defined once): - Atoms: StatusDot, Separator, Badge, Button (Primary/Secondary/Ghost), Input, IconButton - Molecules: InspectorHeader, NoteListItem, Tab (Active/Inactive), PropertyRow, RelationshipPill, SidebarFilterItem, SectionLabel, SectionCountHeader, AddButton, RelGroupLabel, CommandPaletteItem, EditableValue - Organisms: Toast, AIChatMessage Variables added (22 new): - Spacing: --spacing-xs through --spacing-3xl (4px to 40px) - Heights: --height-titlebar, --height-tabbar, --height-statusbar, etc. - Shadows: --shadow-sm, --shadow-md, --shadow-lg - Search colors: --search-bg, --search-input-bg (replacing hardcoded hex) - Font: --font-mono Hardcoded colors replaced with variables in existing frames. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: zero hardcoded colors + 6th layout (Focus Mode) - Replace all 82 remaining hardcoded hex colors with CSS variables - Add 11 new variables: search theme colors, traffic lights, white overlay - Total variables: 79 (zero hardcoded fills in entire canvas) - Add 6th full layout: Focus Mode — Distraction-free Writing (1440×900) - All 6 layout variants now complete Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * docs: add .claude-done summary Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
929 lines
41 KiB
Python
929 lines
41 KiB
Python
#!/usr/bin/env python3
|
||
"""
|
||
Restructure ui-design.pen into a proper design system.
|
||
|
||
Sections (top → bottom):
|
||
0. Cover — title, TOC
|
||
1. Foundations — colors, typography, spacing, shadows, heights
|
||
2. Components — atoms → molecules → organisms (reusable)
|
||
3. Full Layouts — 6 app variants at 1440×900
|
||
4. Feature Specs — existing frames reorganized by functional area
|
||
"""
|
||
import json
|
||
import copy
|
||
import sys
|
||
import os
|
||
|
||
PEN_FILE = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "ui-design.pen")
|
||
|
||
def load():
|
||
with open(PEN_FILE, "r") as f:
|
||
return json.load(f)
|
||
|
||
def save(data):
|
||
with open(PEN_FILE, "w") as f:
|
||
json.dump(data, f, separators=(",", ":"))
|
||
|
||
# ─── Variable definitions ─────────────────────────────────────────
|
||
NEW_VARIABLES = {
|
||
"--spacing-xs": {"type": "number", "value": 4},
|
||
"--spacing-sm": {"type": "number", "value": 8},
|
||
"--spacing-md": {"type": "number", "value": 12},
|
||
"--spacing-lg": {"type": "number", "value": 16},
|
||
"--spacing-xl": {"type": "number", "value": 24},
|
||
"--spacing-2xl": {"type": "number", "value": 32},
|
||
"--spacing-3xl": {"type": "number", "value": 40},
|
||
"--height-titlebar": {"type": "number", "value": 38},
|
||
"--height-tabbar": {"type": "number", "value": 45},
|
||
"--height-breadcrumb": {"type": "number", "value": 45},
|
||
"--height-statusbar": {"type": "number", "value": 30},
|
||
"--height-search-bar": {"type": "number", "value": 40},
|
||
"--height-note-item": {"type": "number", "value": 64},
|
||
"--height-inspector-header": {"type": "number", "value": 36},
|
||
"--height-modal-header": {"type": "number", "value": 56},
|
||
"--height-modal-footer": {"type": "number", "value": 56},
|
||
"--shadow-sm": {"type": "string", "value": "0 1px 2px rgba(0,0,0,0.05)"},
|
||
"--shadow-md": {"type": "string", "value": "0 4px 6px rgba(0,0,0,0.07)"},
|
||
"--shadow-lg": {"type": "string", "value": "0 10px 15px rgba(0,0,0,0.1)"},
|
||
"--font-mono": {"type": "string", "value": "IBM Plex Mono, monospace"},
|
||
# Search panel colors (currently hardcoded)
|
||
"--search-bg": {"type": "color", "value": [
|
||
{"value": "#FFFFFF"}, {"theme": {"Mode": "Dark"}, "value": "#1E1E2E"}
|
||
]},
|
||
"--search-input-bg": {"type": "color", "value": [
|
||
{"value": "#F0F0EF"}, {"theme": {"Mode": "Dark"}, "value": "#2A2A3C"}
|
||
]},
|
||
}
|
||
|
||
# ─── Hardcoded color mappings ─────────────────────────────────────
|
||
# Maps hardcoded hex → variable name for replacement
|
||
HARDCODED_COLORS = {
|
||
"#1E1E2E": "$--search-bg",
|
||
"#1e1e2e": "$--search-bg",
|
||
"#2A2A3C": "$--search-input-bg",
|
||
"#2a2a3c": "$--search-input-bg",
|
||
"#6b7280": "$--muted-foreground",
|
||
"#f9fafb": "$--muted",
|
||
"#f3f4f6": "$--muted",
|
||
"#ffffff": "$--background",
|
||
"#000000": "$--black",
|
||
"#6366f1": "$--primary",
|
||
}
|
||
|
||
def replace_hardcoded_colors(node):
|
||
"""Recursively replace hardcoded hex colors with variable references."""
|
||
if isinstance(node, dict):
|
||
for key, val in list(node.items()):
|
||
if key == "fill" and isinstance(val, str) and val.startswith("#"):
|
||
lower = val.lower()
|
||
if lower in HARDCODED_COLORS:
|
||
node[key] = HARDCODED_COLORS[lower]
|
||
elif key == "fill" and isinstance(val, dict) and val.get("type") == "color":
|
||
color = val.get("color", "")
|
||
if isinstance(color, str) and color.lower() in HARDCODED_COLORS:
|
||
val["color"] = HARDCODED_COLORS[color.lower()]
|
||
elif isinstance(val, (dict, list)):
|
||
replace_hardcoded_colors(val)
|
||
elif isinstance(node, list):
|
||
for item in node:
|
||
replace_hardcoded_colors(item)
|
||
|
||
# ─── Section header helper ────────────────────────────────────────
|
||
def make_section_header(sec_id, label, x, y):
|
||
return {
|
||
"type": "frame", "id": sec_id, "name": label,
|
||
"x": x, "y": y, "width": 1440, "height": 60,
|
||
"fill": "$--foreground", "padding": [0, 60],
|
||
"alignItems": "center", "theme": {"Mode": "Light"},
|
||
"children": [{
|
||
"type": "text", "id": f"{sec_id}_lbl",
|
||
"content": label.upper(),
|
||
"fill": "$--background", "fontFamily": "Inter",
|
||
"fontSize": 24, "fontWeight": "700", "letterSpacing": 2
|
||
}]
|
||
}
|
||
|
||
def make_subsection_label(sub_id, label, x, y):
|
||
return {
|
||
"type": "text", "id": sub_id,
|
||
"content": label,
|
||
"x": x, "y": y,
|
||
"fill": "$--muted-foreground", "fontFamily": "Inter",
|
||
"fontSize": 16, "fontWeight": "600", "letterSpacing": 1,
|
||
"theme": {"Mode": "Light"}
|
||
}
|
||
|
||
# ─── Cover ────────────────────────────────────────────────────────
|
||
def make_cover(x, y):
|
||
return {
|
||
"type": "frame", "id": "ds_cover", "name": "0 — Cover",
|
||
"x": x, "y": y, "width": 1440, "height": "fit_content(400)",
|
||
"fill": "$--background", "layout": "vertical",
|
||
"gap": 32, "padding": [80, 60],
|
||
"theme": {"Mode": "Light"},
|
||
"children": [
|
||
{"type": "text", "id": "ds_title", "content": "Laputa Design System",
|
||
"fill": "$--foreground", "fontFamily": "Inter", "fontSize": 48,
|
||
"fontWeight": "700", "letterSpacing": -1.5},
|
||
{"type": "text", "id": "ds_subtitle",
|
||
"content": "Wiki-linked knowledge management for deep thinkers",
|
||
"fill": "$--muted-foreground", "fontFamily": "Inter", "fontSize": 18, "lineHeight": 1.5},
|
||
{"type": "rectangle", "id": "ds_div", "width": "fill_container", "height": 1, "fill": "$--border"},
|
||
{"type": "frame", "id": "ds_toc", "name": "TOC", "layout": "vertical",
|
||
"width": "fill_container", "gap": 16, "children": [
|
||
{"type": "text", "id": "ds_toc_t", "content": "Table of Contents",
|
||
"fill": "$--foreground", "fontFamily": "Inter", "fontSize": 20, "fontWeight": "600"},
|
||
{"type": "frame", "id": "ds_toc_items", "layout": "vertical",
|
||
"width": "fill_container", "gap": 8, "children": [
|
||
{"type": "text", "id": "ds_t1", "fill": "$--primary", "fontFamily": "Inter",
|
||
"fontSize": 14, "fontWeight": "500",
|
||
"content": "1. Foundations — Colors, Typography, Spacing, Shadows, Heights"},
|
||
{"type": "text", "id": "ds_t2", "fill": "$--primary", "fontFamily": "Inter",
|
||
"fontSize": 14, "fontWeight": "500",
|
||
"content": "2. Components — Atoms, Molecules, Organisms"},
|
||
{"type": "text", "id": "ds_t3", "fill": "$--primary", "fontFamily": "Inter",
|
||
"fontSize": 14, "fontWeight": "500",
|
||
"content": "3. Full Layouts — 6 app variants at 1440×900"},
|
||
{"type": "text", "id": "ds_t4", "fill": "$--primary", "fontFamily": "Inter",
|
||
"fontSize": 14, "fontWeight": "500",
|
||
"content": "4. Feature Specs — All screens grouped by functional area"},
|
||
]}
|
||
]}
|
||
]
|
||
}
|
||
|
||
# ─── Foundation frames ────────────────────────────────────────────
|
||
def make_spacing_scale(x, y):
|
||
rows = []
|
||
for var, px in [("xs", 4), ("sm", 8), ("md", 12), ("lg", 16), ("xl", 24), ("2xl", 32), ("3xl", 40)]:
|
||
rid = f"sp_{var}"
|
||
rows.append({
|
||
"type": "frame", "id": rid, "layout": "horizontal",
|
||
"gap": 16, "alignItems": "center", "width": "fill_container",
|
||
"children": [
|
||
{"type": "rectangle", "id": f"{rid}_box", "width": px, "height": 24,
|
||
"fill": "$--primary", "cornerRadius": 2},
|
||
{"type": "text", "id": f"{rid}_lbl",
|
||
"content": f"--spacing-{var}: {px}px",
|
||
"fill": "$--foreground", "fontFamily": "IBM Plex Mono", "fontSize": 13}
|
||
]
|
||
})
|
||
return {
|
||
"type": "frame", "id": "ds_spacing", "name": "Spacing Scale",
|
||
"x": x, "y": y, "width": 700,
|
||
"fill": "$--background", "layout": "vertical",
|
||
"gap": 24, "padding": 40, "theme": {"Mode": "Light"},
|
||
"children": [
|
||
{"type": "text", "id": "ds_sp_title", "content": "Spacing Scale",
|
||
"fill": "$--foreground", "fontFamily": "Inter", "fontSize": 20, "fontWeight": "600"},
|
||
{"type": "text", "id": "ds_sp_sub",
|
||
"content": "Consistent spacing tokens used throughout the app.",
|
||
"fill": "$--muted-foreground", "fontFamily": "Inter", "fontSize": 13, "lineHeight": 1.5},
|
||
] + rows
|
||
}
|
||
|
||
def make_heights_ref(x, y):
|
||
items = [
|
||
("titlebar", 38, "$--sidebar"), ("tabbar", 45, "$--sidebar"),
|
||
("breadcrumb", 45, "$--background"), ("statusbar", 30, "$--sidebar"),
|
||
("modal-header", 56, "$--background"), ("inspector-header", 36, "$--background"),
|
||
]
|
||
rows = []
|
||
for name, px, fill in items:
|
||
rid = f"ht_{name.replace('-','_')}"
|
||
rows.append({
|
||
"type": "frame", "id": rid, "layout": "horizontal",
|
||
"gap": 16, "alignItems": "center", "width": "fill_container",
|
||
"children": [
|
||
{"type": "rectangle", "id": f"{rid}_bar", "width": 120, "height": px,
|
||
"fill": fill, "cornerRadius": "$--radius-sm",
|
||
"stroke": {"fill": "$--border", "thickness": 1}},
|
||
{"type": "text", "id": f"{rid}_lbl",
|
||
"content": f"--height-{name}: {px}px",
|
||
"fill": "$--foreground", "fontFamily": "IBM Plex Mono", "fontSize": 13}
|
||
]
|
||
})
|
||
return {
|
||
"type": "frame", "id": "ds_heights", "name": "Height Variables",
|
||
"x": x, "y": y, "width": 700,
|
||
"fill": "$--background", "layout": "vertical",
|
||
"gap": 24, "padding": 40, "theme": {"Mode": "Light"},
|
||
"children": [
|
||
{"type": "text", "id": "ds_ht_title", "content": "Height Variables",
|
||
"fill": "$--foreground", "fontFamily": "Inter", "fontSize": 20, "fontWeight": "600"},
|
||
{"type": "text", "id": "ds_ht_sub",
|
||
"content": "Fixed heights for key layout regions.",
|
||
"fill": "$--muted-foreground", "fontFamily": "Inter", "fontSize": 13, "lineHeight": 1.5},
|
||
] + rows
|
||
}
|
||
|
||
def make_shadows_ref(x, y):
|
||
shadows = [
|
||
("SM", "0 1px 2px rgba(0,0,0,0.05)", {"type": "shadow", "offset": {"x": 0, "y": 1}, "blur": 2, "color": "#0000000D"}),
|
||
("MD", "0 4px 6px rgba(0,0,0,0.07)", {"type": "shadow", "offset": {"x": 0, "y": 4}, "blur": 6, "color": "#00000012"}),
|
||
("LG", "0 10px 15px rgba(0,0,0,0.1)", {"type": "shadow", "offset": {"x": 0, "y": 10}, "blur": 15, "color": "#0000001A"}),
|
||
]
|
||
rows = []
|
||
for name, desc, effect in shadows:
|
||
rid = f"sh_{name.lower()}"
|
||
rows.append({
|
||
"type": "frame", "id": rid, "layout": "horizontal",
|
||
"gap": 24, "alignItems": "center", "width": "fill_container",
|
||
"children": [
|
||
{"type": "frame", "id": f"{rid}_box", "width": 120, "height": 80,
|
||
"fill": "$--card", "cornerRadius": "$--radius-lg", "effect": effect},
|
||
{"type": "frame", "id": f"{rid}_info", "layout": "vertical", "gap": 4, "children": [
|
||
{"type": "text", "id": f"{rid}_name", "content": f"Shadow {name}",
|
||
"fill": "$--foreground", "fontFamily": "Inter", "fontSize": 14, "fontWeight": "600"},
|
||
{"type": "text", "id": f"{rid}_desc", "content": desc,
|
||
"fill": "$--muted-foreground", "fontFamily": "IBM Plex Mono", "fontSize": 11}
|
||
]}
|
||
]
|
||
})
|
||
return {
|
||
"type": "frame", "id": "ds_shadows", "name": "Shadows",
|
||
"x": x, "y": y, "width": 700,
|
||
"fill": "$--background", "layout": "vertical",
|
||
"gap": 32, "padding": 40, "theme": {"Mode": "Light"},
|
||
"children": [
|
||
{"type": "text", "id": "ds_sh_title", "content": "Shadows",
|
||
"fill": "$--foreground", "fontFamily": "Inter", "fontSize": 20, "fontWeight": "600"},
|
||
{"type": "text", "id": "ds_sh_sub",
|
||
"content": "Elevation levels for cards, modals, and popovers.",
|
||
"fill": "$--muted-foreground", "fontFamily": "Inter", "fontSize": 13, "lineHeight": 1.5},
|
||
] + rows
|
||
}
|
||
|
||
# ─── Reusable Components ─────────────────────────────────────────
|
||
|
||
def make_component_status_dot(x, y):
|
||
return {
|
||
"type": "frame", "id": "comp_StatusDot", "name": "component/StatusDot",
|
||
"reusable": True, "x": x, "y": y,
|
||
"width": "fit_content(6)", "height": "fit_content(6)",
|
||
"theme": {"Mode": "Light"},
|
||
"children": [
|
||
{"type": "ellipse", "id": "StatusDot_dot", "width": 6, "height": 6,
|
||
"fill": "$--accent-orange"}
|
||
]
|
||
}
|
||
|
||
def make_component_separator(x, y):
|
||
return {
|
||
"type": "frame", "id": "comp_Separator", "name": "component/Separator",
|
||
"reusable": True, "x": x, "y": y,
|
||
"width": "fill_container(200)", "height": 1,
|
||
"fill": "$--border",
|
||
"theme": {"Mode": "Light"},
|
||
"children": []
|
||
}
|
||
|
||
def make_component_badge(x, y):
|
||
"""Colored pill badge — used for type badges, status badges, tags."""
|
||
return {
|
||
"type": "frame", "id": "comp_Badge", "name": "component/Badge",
|
||
"reusable": True, "x": x, "y": y,
|
||
"cornerRadius": 12, "fill": "$--accent-blue-light",
|
||
"padding": [2, 8],
|
||
"theme": {"Mode": "Light"},
|
||
"children": [
|
||
{"type": "text", "id": "Badge_label", "content": "Badge",
|
||
"fill": "$--accent-blue", "fontFamily": "Inter", "fontSize": 12, "fontWeight": "500"}
|
||
]
|
||
}
|
||
|
||
def make_component_button_primary(x, y):
|
||
return {
|
||
"type": "frame", "id": "comp_ButtonPrimary", "name": "component/Button/Primary",
|
||
"reusable": True, "x": x, "y": y,
|
||
"cornerRadius": "$--radius-md", "fill": "$--primary",
|
||
"padding": [8, 16], "gap": 6, "alignItems": "center",
|
||
"justifyContent": "center", "height": 36,
|
||
"theme": {"Mode": "Light"},
|
||
"children": [
|
||
{"type": "text", "id": "BtnPri_label", "content": "Button",
|
||
"fill": "$--primary-foreground", "fontFamily": "Inter", "fontSize": 13, "fontWeight": "500"}
|
||
]
|
||
}
|
||
|
||
def make_component_button_secondary(x, y):
|
||
return {
|
||
"type": "frame", "id": "comp_ButtonSecondary", "name": "component/Button/Secondary",
|
||
"reusable": True, "x": x, "y": y,
|
||
"cornerRadius": "$--radius-md", "fill": "$--secondary",
|
||
"padding": [8, 16], "gap": 6, "alignItems": "center",
|
||
"justifyContent": "center", "height": 36,
|
||
"theme": {"Mode": "Light"},
|
||
"children": [
|
||
{"type": "text", "id": "BtnSec_label", "content": "Button",
|
||
"fill": "$--secondary-foreground", "fontFamily": "Inter", "fontSize": 13, "fontWeight": "500"}
|
||
]
|
||
}
|
||
|
||
def make_component_button_ghost(x, y):
|
||
return {
|
||
"type": "frame", "id": "comp_ButtonGhost", "name": "component/Button/Ghost",
|
||
"reusable": True, "x": x, "y": y,
|
||
"cornerRadius": "$--radius-md",
|
||
"padding": [8, 16], "gap": 6, "alignItems": "center",
|
||
"justifyContent": "center", "height": 36,
|
||
"theme": {"Mode": "Light"},
|
||
"children": [
|
||
{"type": "text", "id": "BtnGho_label", "content": "Button",
|
||
"fill": "$--foreground", "fontFamily": "Inter", "fontSize": 13, "fontWeight": "500"}
|
||
]
|
||
}
|
||
|
||
def make_component_input(x, y):
|
||
return {
|
||
"type": "frame", "id": "comp_Input", "name": "component/Input",
|
||
"reusable": True, "x": x, "y": y,
|
||
"width": 240, "height": 36,
|
||
"cornerRadius": "$--radius-md", "fill": "$--background",
|
||
"stroke": {"fill": "$--input", "thickness": 1, "align": "inside"},
|
||
"padding": [0, 12], "alignItems": "center",
|
||
"theme": {"Mode": "Light"},
|
||
"children": [
|
||
{"type": "text", "id": "Input_placeholder", "content": "Placeholder...",
|
||
"fill": "$--muted-foreground", "fontFamily": "Inter", "fontSize": 13}
|
||
]
|
||
}
|
||
|
||
def make_component_icon_button(x, y):
|
||
return {
|
||
"type": "frame", "id": "comp_IconButton", "name": "component/IconButton",
|
||
"reusable": True, "x": x, "y": y,
|
||
"width": 28, "height": 28,
|
||
"cornerRadius": "$--radius-sm",
|
||
"alignItems": "center", "justifyContent": "center",
|
||
"theme": {"Mode": "Light"},
|
||
"children": [
|
||
{"type": "icon_font", "id": "IconBtn_icon", "width": 16, "height": 16,
|
||
"fill": "$--muted-foreground", "iconFontFamily": "lucide", "iconFontName": "plus"}
|
||
]
|
||
}
|
||
|
||
# ─── Molecule Components ──────────────────────────────────────────
|
||
|
||
def make_component_inspector_header(x, y):
|
||
return {
|
||
"type": "frame", "id": "comp_InspectorHeader", "name": "component/InspectorHeader",
|
||
"reusable": True, "x": x, "y": y,
|
||
"width": "fill_container(320)", "height": "$--height-inspector-header",
|
||
"justifyContent": "space_between", "alignItems": "center",
|
||
"padding": [0, 12],
|
||
"theme": {"Mode": "Light"},
|
||
"children": [
|
||
{"type": "frame", "id": "IH_left", "name": "leftGroup",
|
||
"gap": 6, "alignItems": "center", "children": [
|
||
{"type": "icon_font", "id": "IH_icon", "width": 16, "height": 16,
|
||
"fill": "$--muted-foreground", "iconFontFamily": "phosphor",
|
||
"iconFontName": "sliders-horizontal"},
|
||
{"type": "text", "id": "IH_title", "content": "Properties",
|
||
"fill": "$--muted-foreground", "fontFamily": "Inter",
|
||
"fontSize": 13, "fontWeight": "600"}
|
||
]},
|
||
{"type": "icon_font", "id": "IH_close", "width": 16, "height": 16,
|
||
"fill": "$--muted-foreground", "iconFontFamily": "phosphor", "iconFontName": "x"}
|
||
]
|
||
}
|
||
|
||
def make_component_note_list_item(x, y):
|
||
return {
|
||
"type": "frame", "id": "comp_NoteListItem", "name": "component/NoteListItem",
|
||
"reusable": True, "x": x, "y": y,
|
||
"width": "fill_container(340)", "layout": "vertical",
|
||
"gap": 4, "padding": [14, 16],
|
||
"stroke": {"align": "inside", "fill": "$--border", "thickness": {"bottom": 1}},
|
||
"theme": {"Mode": "Light"},
|
||
"children": [
|
||
{"type": "frame", "id": "NLI_titleRow", "name": "titleRow",
|
||
"width": "fill_container", "justifyContent": "space_between", "alignItems": "center",
|
||
"children": [
|
||
{"type": "frame", "id": "NLI_titleGroup", "gap": 6, "alignItems": "center",
|
||
"children": [
|
||
{"type": "text", "id": "NLI_title", "content": "Note Title",
|
||
"fill": "$--foreground", "fontFamily": "Inter", "fontSize": 13, "fontWeight": "500"}
|
||
]},
|
||
{"type": "text", "id": "NLI_time", "content": "2m ago",
|
||
"fill": "$--muted-foreground", "fontFamily": "Inter", "fontSize": 11}
|
||
]},
|
||
{"type": "text", "id": "NLI_snippet",
|
||
"content": "Preview text of the note content...",
|
||
"fill": "$--muted-foreground", "fontFamily": "Inter",
|
||
"fontSize": 12, "lineHeight": 1.5,
|
||
"textGrowth": "fixed-width", "width": "fill_container"},
|
||
]
|
||
}
|
||
|
||
def make_component_tab_active(x, y):
|
||
return {
|
||
"type": "frame", "id": "comp_TabActive", "name": "component/Tab/Active",
|
||
"reusable": True, "x": x, "y": y,
|
||
"height": "fill_container(45)", "gap": 6,
|
||
"fill": "$--background", "alignItems": "center",
|
||
"padding": [0, 14],
|
||
"stroke": {"align": "inside", "fill": "$--border", "thickness": {"right": 1}},
|
||
"theme": {"Mode": "Light"},
|
||
"children": [
|
||
{"type": "text", "id": "TabA_label", "content": "Tab Title",
|
||
"fill": "$--foreground", "fontFamily": "Inter", "fontSize": 12, "fontWeight": "500"},
|
||
{"type": "icon_font", "id": "TabA_close", "width": 14, "height": 14,
|
||
"fill": "$--muted-foreground", "iconFontFamily": "lucide", "iconFontName": "x"}
|
||
]
|
||
}
|
||
|
||
def make_component_tab_inactive(x, y):
|
||
return {
|
||
"type": "frame", "id": "comp_TabInactive", "name": "component/Tab/Inactive",
|
||
"reusable": True, "x": x, "y": y,
|
||
"height": "fill_container(45)", "gap": 6,
|
||
"alignItems": "center", "padding": [0, 14],
|
||
"stroke": {"align": "inside", "fill": "$--sidebar-border",
|
||
"thickness": {"bottom": 1, "right": 1}},
|
||
"theme": {"Mode": "Light"},
|
||
"children": [
|
||
{"type": "text", "id": "TabI_label", "content": "Tab Title",
|
||
"fill": "$--muted-foreground", "fontFamily": "Inter", "fontSize": 12},
|
||
{"type": "icon_font", "id": "TabI_close", "width": 14, "height": 14,
|
||
"fill": "$--muted-foreground", "iconFontFamily": "lucide", "iconFontName": "x",
|
||
"opacity": 0}
|
||
]
|
||
}
|
||
|
||
def make_component_property_row(x, y):
|
||
return {
|
||
"type": "frame", "id": "comp_PropertyRow", "name": "component/PropertyRow",
|
||
"reusable": True, "x": x, "y": y,
|
||
"width": "fill_container(300)", "alignItems": "center",
|
||
"cornerRadius": 4, "padding": [4, 6],
|
||
"theme": {"Mode": "Light"},
|
||
"children": [
|
||
{"type": "text", "id": "PR_label", "content": "LABEL",
|
||
"fill": "$--muted-foreground", "fontFamily": "IBM Plex Mono",
|
||
"fontSize": 10, "fontWeight": "500", "letterSpacing": 1.2},
|
||
{"type": "text", "id": "PR_value", "content": "Value",
|
||
"fill": "$--foreground", "fontFamily": "Inter", "fontSize": 13}
|
||
]
|
||
}
|
||
|
||
def make_component_relationship_pill(x, y):
|
||
return {
|
||
"type": "frame", "id": "comp_RelationshipPill", "name": "component/RelationshipPill",
|
||
"reusable": True, "x": x, "y": y,
|
||
"width": "fill_container(280)", "cornerRadius": 6,
|
||
"fill": "$--accent-blue-light", "gap": 6,
|
||
"justifyContent": "space_between", "alignItems": "center",
|
||
"padding": [6, 10],
|
||
"theme": {"Mode": "Light"},
|
||
"children": [
|
||
{"type": "text", "id": "RP_title", "content": "Related Note",
|
||
"fill": "$--accent-blue", "fontFamily": "Inter", "fontSize": 12, "fontWeight": "500"},
|
||
{"type": "icon_font", "id": "RP_icon", "width": 14, "height": 14,
|
||
"fill": "$--accent-blue", "iconFontFamily": "phosphor", "iconFontName": "wrench",
|
||
"weight": 700}
|
||
]
|
||
}
|
||
|
||
def make_component_sidebar_filter_item(x, y):
|
||
return {
|
||
"type": "frame", "id": "comp_SidebarFilterItem", "name": "component/SidebarFilterItem",
|
||
"reusable": True, "x": x, "y": y,
|
||
"width": "fill_container(250)", "cornerRadius": 6,
|
||
"gap": 8, "alignItems": "center", "padding": [6, 16],
|
||
"theme": {"Mode": "Light"},
|
||
"children": [
|
||
{"type": "icon_font", "id": "SFI_icon", "width": 16, "height": 16,
|
||
"fill": "$--muted-foreground", "iconFontFamily": "lucide", "iconFontName": "folder"},
|
||
{"type": "text", "id": "SFI_label", "content": "Filter Item",
|
||
"fill": "$--foreground", "fontFamily": "Inter", "fontSize": 13},
|
||
{"type": "text", "id": "SFI_count", "content": "42",
|
||
"fill": "$--muted-foreground", "fontFamily": "Inter", "fontSize": 11}
|
||
]
|
||
}
|
||
|
||
def make_component_section_label(x, y):
|
||
return {
|
||
"type": "frame", "id": "comp_SectionLabel", "name": "component/SectionLabel",
|
||
"reusable": True, "x": x, "y": y,
|
||
"width": "fill_container(280)", "padding": [0, 12],
|
||
"theme": {"Mode": "Light"},
|
||
"children": [
|
||
{"type": "text", "id": "SL_label", "content": "SECTION",
|
||
"fill": "$--muted-foreground", "fontFamily": "Inter",
|
||
"fontSize": 10, "fontWeight": "600", "letterSpacing": 1.2}
|
||
]
|
||
}
|
||
|
||
def make_component_section_count_header(x, y):
|
||
return {
|
||
"type": "frame", "id": "comp_SectionCountHeader", "name": "component/SectionCountHeader",
|
||
"reusable": True, "x": x, "y": y,
|
||
"width": "fill_container(280)", "justifyContent": "space_between",
|
||
"alignItems": "center", "padding": [4, 0],
|
||
"theme": {"Mode": "Light"},
|
||
"children": [
|
||
{"type": "text", "id": "SCH_label", "content": "SECTION",
|
||
"fill": "$--muted-foreground", "fontFamily": "Inter",
|
||
"fontSize": 10, "fontWeight": "600", "letterSpacing": 1.2},
|
||
{"type": "text", "id": "SCH_count", "content": "3",
|
||
"fill": "$--muted-foreground", "fontFamily": "Inter", "fontSize": 11}
|
||
]
|
||
}
|
||
|
||
def make_component_add_button(x, y):
|
||
return {
|
||
"type": "frame", "id": "comp_AddButton", "name": "component/AddButton",
|
||
"reusable": True, "x": x, "y": y,
|
||
"width": "fill_container(280)", "height": 32,
|
||
"cornerRadius": 6, "justifyContent": "center", "alignItems": "center",
|
||
"stroke": {"align": "inside", "fill": "$--border", "thickness": 1},
|
||
"theme": {"Mode": "Light"},
|
||
"children": [
|
||
{"type": "text", "id": "AB_label", "content": "+ Add property",
|
||
"fill": "$--muted-foreground", "fontFamily": "Inter", "fontSize": 12}
|
||
]
|
||
}
|
||
|
||
def make_component_rel_group_label(x, y):
|
||
return {
|
||
"type": "frame", "id": "comp_RelGroupLabel", "name": "component/RelGroupLabel",
|
||
"reusable": True, "x": x, "y": y,
|
||
"width": "fill_container(280)", "justifyContent": "space_between",
|
||
"alignItems": "center",
|
||
"theme": {"Mode": "Light"},
|
||
"children": [
|
||
{"type": "text", "id": "RGL_label", "content": "BELONGS TO",
|
||
"fill": "$--muted-foreground", "fontFamily": "Inter",
|
||
"fontSize": 10, "fontWeight": "600", "letterSpacing": 0.5},
|
||
{"type": "text", "id": "RGL_count", "content": "3",
|
||
"fill": "$--muted-foreground", "fontFamily": "Inter", "fontSize": 10}
|
||
]
|
||
}
|
||
|
||
def make_component_command_palette_item(x, y):
|
||
return {
|
||
"type": "frame", "id": "comp_CommandPaletteItem", "name": "component/CommandPaletteItem",
|
||
"reusable": True, "x": x, "y": y,
|
||
"width": "fill_container(460)", "height": 40,
|
||
"alignItems": "center", "gap": 12, "padding": [0, 16],
|
||
"cornerRadius": "$--radius-md",
|
||
"theme": {"Mode": "Light"},
|
||
"children": [
|
||
{"type": "icon_font", "id": "CPI_icon", "width": 16, "height": 16,
|
||
"fill": "$--muted-foreground", "iconFontFamily": "lucide", "iconFontName": "file-text"},
|
||
{"type": "frame", "id": "CPI_content", "layout": "vertical",
|
||
"width": "fill_container", "gap": 2, "children": [
|
||
{"type": "text", "id": "CPI_title", "content": "Command Name",
|
||
"fill": "$--foreground", "fontFamily": "Inter", "fontSize": 13, "fontWeight": "500"},
|
||
]},
|
||
{"type": "text", "id": "CPI_shortcut", "content": "⌘K",
|
||
"fill": "$--muted-foreground", "fontFamily": "IBM Plex Mono", "fontSize": 11}
|
||
]
|
||
}
|
||
|
||
def make_component_editable_value(x, y):
|
||
return {
|
||
"type": "frame", "id": "comp_EditableValue", "name": "component/EditableValue",
|
||
"reusable": True, "x": x, "y": y,
|
||
"width": "fill_container(200)",
|
||
"alignItems": "center", "gap": 4,
|
||
"theme": {"Mode": "Light"},
|
||
"children": [
|
||
{"type": "text", "id": "EV_value", "content": "Editable Value",
|
||
"fill": "$--foreground", "fontFamily": "Inter", "fontSize": 13},
|
||
{"type": "icon_font", "id": "EV_edit", "width": 12, "height": 12,
|
||
"fill": "$--muted-foreground", "iconFontFamily": "lucide", "iconFontName": "pencil",
|
||
"opacity": 0}
|
||
]
|
||
}
|
||
|
||
# ─── Organism Components ──────────────────────────────────────────
|
||
|
||
def make_component_toast(x, y):
|
||
return {
|
||
"type": "frame", "id": "comp_Toast", "name": "component/Toast",
|
||
"reusable": True, "x": x, "y": y,
|
||
"width": 360, "cornerRadius": "$--radius-lg",
|
||
"fill": "$--card", "padding": [12, 16], "gap": 12,
|
||
"alignItems": "center",
|
||
"stroke": {"fill": "$--border", "thickness": 1},
|
||
"effect": {"type": "shadow", "offset": {"x": 0, "y": 4}, "blur": 12, "color": "#0000001A"},
|
||
"theme": {"Mode": "Light"},
|
||
"children": [
|
||
{"type": "icon_font", "id": "Toast_icon", "width": 20, "height": 20,
|
||
"fill": "$--accent-green", "iconFontFamily": "lucide", "iconFontName": "check-circle"},
|
||
{"type": "frame", "id": "Toast_content", "layout": "vertical",
|
||
"width": "fill_container", "gap": 2, "children": [
|
||
{"type": "text", "id": "Toast_title", "content": "Success",
|
||
"fill": "$--foreground", "fontFamily": "Inter", "fontSize": 13, "fontWeight": "600"},
|
||
{"type": "text", "id": "Toast_msg", "content": "Your changes have been saved.",
|
||
"fill": "$--muted-foreground", "fontFamily": "Inter", "fontSize": 12, "lineHeight": 1.4}
|
||
]},
|
||
{"type": "icon_font", "id": "Toast_close", "width": 14, "height": 14,
|
||
"fill": "$--muted-foreground", "iconFontFamily": "lucide", "iconFontName": "x"}
|
||
]
|
||
}
|
||
|
||
def make_component_ai_chat_message(x, y):
|
||
return {
|
||
"type": "frame", "id": "comp_AIChatMessage", "name": "component/AIChatMessage",
|
||
"reusable": True, "x": x, "y": y,
|
||
"width": "fill_container(400)", "layout": "horizontal",
|
||
"gap": 12, "padding": [12, 16],
|
||
"theme": {"Mode": "Light"},
|
||
"children": [
|
||
{"type": "frame", "id": "AIM_avatar", "width": 28, "height": 28,
|
||
"cornerRadius": 14, "fill": "$--accent-purple-light",
|
||
"alignItems": "center", "justifyContent": "center",
|
||
"children": [
|
||
{"type": "icon_font", "id": "AIM_avatarIcon", "width": 16, "height": 16,
|
||
"fill": "$--accent-purple", "iconFontFamily": "lucide", "iconFontName": "sparkles"}
|
||
]},
|
||
{"type": "frame", "id": "AIM_body", "layout": "vertical",
|
||
"width": "fill_container", "gap": 4, "children": [
|
||
{"type": "text", "id": "AIM_sender", "content": "AI Assistant",
|
||
"fill": "$--foreground", "fontFamily": "Inter", "fontSize": 12, "fontWeight": "600"},
|
||
{"type": "text", "id": "AIM_text",
|
||
"content": "Here's a summary of your recent notes...",
|
||
"fill": "$--foreground", "fontFamily": "Inter", "fontSize": 13,
|
||
"lineHeight": 1.5, "textGrowth": "fixed-width", "width": "fill_container"}
|
||
]}
|
||
]
|
||
}
|
||
|
||
# ─── Component showcase frame ─────────────────────────────────────
|
||
def make_component_showcase(x, y, title, components):
|
||
"""Creates a frame showing all component instances in a row for visual reference."""
|
||
children = [
|
||
{"type": "text", "id": f"showcase_{title.replace(' ','_')}_title",
|
||
"content": title,
|
||
"fill": "$--foreground", "fontFamily": "Inter", "fontSize": 18, "fontWeight": "600"},
|
||
]
|
||
for comp_id, label in components:
|
||
children.append({
|
||
"type": "frame", "id": f"show_{comp_id}", "layout": "vertical", "gap": 8,
|
||
"padding": [12, 0], "children": [
|
||
{"type": "text", "id": f"show_{comp_id}_lbl", "content": label,
|
||
"fill": "$--muted-foreground", "fontFamily": "IBM Plex Mono", "fontSize": 10,
|
||
"letterSpacing": 0.5},
|
||
{"type": "ref", "id": f"show_{comp_id}_ref", "ref": comp_id}
|
||
]
|
||
})
|
||
sid = f"showcase_{title.replace(' ', '_')}"
|
||
return {
|
||
"type": "frame", "id": sid, "name": f"Showcase — {title}",
|
||
"x": x, "y": y, "width": 1440,
|
||
"fill": "$--background", "layout": "vertical",
|
||
"gap": 24, "padding": 40,
|
||
"theme": {"Mode": "Light"},
|
||
"children": children
|
||
}
|
||
|
||
# ─── Feature spec grouping ────────────────────────────────────────
|
||
FEATURE_GROUPS = {
|
||
"Tabs & Navigation": [
|
||
"dt0", "dtj", "dt11", "rnt0", "rne0", "rns0",
|
||
"archBtnNorm", "archBtnArc", "srtDD", "srtBN"
|
||
],
|
||
"Inspector & Properties": [
|
||
"pi01", "pi50", "rbF01", "rbF02", "rbF03",
|
||
"reF01", "reF02", "reF03",
|
||
"urlDefault", "urlHover", "urlEdit"
|
||
],
|
||
"Note List & Virtual List": [
|
||
"vl001", "vl100", "vl200", "vl300",
|
||
"mni01", "dp01"
|
||
],
|
||
"Sidebar & Sections": [
|
||
"dsg01a", "dsg02w", "dsg04j", "csB01", "csA01", "trSB1"
|
||
],
|
||
"Modals & Settings": [
|
||
"iogBH", "ghv01", "ghv02", "ghv03", "ghv04"
|
||
],
|
||
"Editor & Wikilinks": [
|
||
"wlc01", "wlc20", "mb001", "mb011"
|
||
],
|
||
"Status & Indicators": [
|
||
"mni10", "mni20", "dp10", "dp20", "dp30"
|
||
],
|
||
"Git & Changes": [
|
||
"vc001", "vc100", "vc200", "ghCL1", "ghDO1"
|
||
],
|
||
"Search": [
|
||
"3aG9b", "K1O2x", "nrIcZ"
|
||
],
|
||
"Trash": [
|
||
"trNL1", "trRI1", "trW30"
|
||
],
|
||
}
|
||
|
||
# ─── Main transform ──────────────────────────────────────────────
|
||
|
||
def transform():
|
||
data = load()
|
||
|
||
# 1. Add new variables (merge, don't replace)
|
||
for k, v in NEW_VARIABLES.items():
|
||
data["variables"][k] = v
|
||
|
||
# 2. Build an index of existing children by ID
|
||
existing = {c["id"]: c for c in data["children"]}
|
||
|
||
# 3. Fix hardcoded colors in ALL existing frames
|
||
for child in data["children"]:
|
||
replace_hardcoded_colors(child)
|
||
|
||
# 4. Build new ordered children list
|
||
new_children = []
|
||
|
||
# ─── Section 0: Cover (y=0) ───
|
||
new_children.append(make_cover(0, 0))
|
||
|
||
# ─── Section 1: Foundations (y=500) ───
|
||
new_children.append(make_section_header("sec1_hdr", "1 — Foundations", 0, 500))
|
||
|
||
# Move existing Color Palette
|
||
if "mOf4J" in existing:
|
||
cp = existing.pop("mOf4J")
|
||
cp["x"] = 0; cp["y"] = 600
|
||
new_children.append(cp)
|
||
|
||
# Move existing Typography & Spacing
|
||
if "HZonq" in existing:
|
||
ts = existing.pop("HZonq")
|
||
ts["x"] = 0; ts["y"] = 1600
|
||
new_children.append(ts)
|
||
|
||
# New foundation frames
|
||
new_children.append(make_spacing_scale(0, 3000))
|
||
new_children.append(make_heights_ref(800, 3000))
|
||
new_children.append(make_shadows_ref(0, 3600))
|
||
|
||
# ─── Section 2: Components (y=4200) ───
|
||
new_children.append(make_section_header("sec2_hdr", "2 — Components", 0, 4200))
|
||
|
||
# Subsection labels
|
||
new_children.append(make_subsection_label("sub_atoms", "ATOMS", 0, 4290))
|
||
|
||
# Atom components (placed as reusable definitions)
|
||
comp_y_atoms = 4320
|
||
atom_components = [
|
||
make_component_status_dot(0, comp_y_atoms),
|
||
make_component_separator(100, comp_y_atoms),
|
||
make_component_badge(400, comp_y_atoms),
|
||
make_component_button_primary(0, comp_y_atoms + 60),
|
||
make_component_button_secondary(200, comp_y_atoms + 60),
|
||
make_component_button_ghost(400, comp_y_atoms + 60),
|
||
make_component_input(0, comp_y_atoms + 120),
|
||
make_component_icon_button(300, comp_y_atoms + 120),
|
||
]
|
||
new_children.extend(atom_components)
|
||
|
||
# Atom showcase
|
||
new_children.append(make_component_showcase(0, comp_y_atoms + 200, "Atoms", [
|
||
("comp_StatusDot", "StatusDot"),
|
||
("comp_Separator", "Separator"),
|
||
("comp_Badge", "Badge"),
|
||
("comp_ButtonPrimary", "Button/Primary"),
|
||
("comp_ButtonSecondary", "Button/Secondary"),
|
||
("comp_ButtonGhost", "Button/Ghost"),
|
||
("comp_Input", "Input"),
|
||
("comp_IconButton", "IconButton"),
|
||
]))
|
||
|
||
# Subsection: Molecules
|
||
mol_y = 4900
|
||
new_children.append(make_subsection_label("sub_molecules", "MOLECULES", 0, mol_y - 30))
|
||
|
||
molecule_components = [
|
||
make_component_inspector_header(0, mol_y),
|
||
make_component_note_list_item(0, mol_y + 60),
|
||
make_component_tab_active(400, mol_y),
|
||
make_component_tab_inactive(600, mol_y),
|
||
make_component_property_row(0, mol_y + 160),
|
||
make_component_relationship_pill(0, mol_y + 220),
|
||
make_component_sidebar_filter_item(400, mol_y + 160),
|
||
make_component_section_label(0, mol_y + 280),
|
||
make_component_section_count_header(400, mol_y + 280),
|
||
make_component_add_button(0, mol_y + 340),
|
||
make_component_rel_group_label(400, mol_y + 340),
|
||
make_component_command_palette_item(0, mol_y + 400),
|
||
make_component_editable_value(0, mol_y + 460),
|
||
]
|
||
new_children.extend(molecule_components)
|
||
|
||
# Molecule showcase
|
||
new_children.append(make_component_showcase(0, mol_y + 520, "Molecules", [
|
||
("comp_InspectorHeader", "InspectorHeader"),
|
||
("comp_NoteListItem", "NoteListItem"),
|
||
("comp_TabActive", "Tab/Active"),
|
||
("comp_TabInactive", "Tab/Inactive"),
|
||
("comp_PropertyRow", "PropertyRow"),
|
||
("comp_RelationshipPill", "RelationshipPill"),
|
||
("comp_SidebarFilterItem", "SidebarFilterItem"),
|
||
("comp_SectionLabel", "SectionLabel"),
|
||
("comp_SectionCountHeader", "SectionCountHeader"),
|
||
("comp_AddButton", "AddButton"),
|
||
("comp_RelGroupLabel", "RelGroupLabel"),
|
||
("comp_CommandPaletteItem", "CommandPaletteItem"),
|
||
("comp_EditableValue", "EditableValue"),
|
||
]))
|
||
|
||
# Subsection: Organisms
|
||
org_y = 5900
|
||
new_children.append(make_subsection_label("sub_organisms", "ORGANISMS", 0, org_y - 30))
|
||
|
||
new_children.append(make_component_toast(0, org_y))
|
||
new_children.append(make_component_ai_chat_message(0, org_y + 80))
|
||
|
||
new_children.append(make_component_showcase(0, org_y + 200, "Organisms", [
|
||
("comp_Toast", "Toast"),
|
||
("comp_AIChatMessage", "AIChatMessage"),
|
||
]))
|
||
|
||
# ─── Section 3: Full Layouts (y=6600) ───
|
||
new_children.append(make_section_header("sec3_hdr", "3 — Full Layouts", 0, 6600))
|
||
|
||
# Layout frames — move existing ones
|
||
layout_ids = ["qHhaj", "SC_all", "SC_nosb", "SC_edonly", "SC_ednl"]
|
||
layout_y = 6700
|
||
for lid in layout_ids:
|
||
if lid in existing:
|
||
frame = existing.pop(lid)
|
||
frame["x"] = 0
|
||
frame["y"] = layout_y
|
||
new_children.append(frame)
|
||
layout_y += 1000 # 900px height + 100px gap
|
||
|
||
# ─── Section 4: Feature Specs (y=12200) ───
|
||
spec_y = 12200
|
||
new_children.append(make_section_header("sec4_hdr", "4 — Feature Specs", 0, spec_y))
|
||
spec_y += 100
|
||
|
||
placed_ids = set()
|
||
for group_name, group_ids in FEATURE_GROUPS.items():
|
||
# Group label
|
||
new_children.append(make_subsection_label(
|
||
f"fg_{group_name.replace(' ', '_').replace('&','and').lower()[:20]}",
|
||
group_name.upper(), 0, spec_y
|
||
))
|
||
spec_y += 30
|
||
|
||
group_x = 0
|
||
max_h = 0
|
||
for fid in group_ids:
|
||
if fid in existing:
|
||
frame = existing.pop(fid)
|
||
fw = frame.get("width", 300)
|
||
if isinstance(fw, str):
|
||
fw = 340 # fallback for dynamic widths
|
||
|
||
# Wrap to next row if too wide
|
||
if group_x + fw > 3000:
|
||
spec_y += max_h + 40
|
||
group_x = 0
|
||
max_h = 0
|
||
|
||
frame["x"] = group_x
|
||
frame["y"] = spec_y
|
||
new_children.append(frame)
|
||
placed_ids.add(fid)
|
||
|
||
fh = frame.get("height", 200)
|
||
if isinstance(fh, str):
|
||
fh = 400 # fallback
|
||
max_h = max(max_h, fh)
|
||
group_x += fw + 100
|
||
|
||
spec_y += max_h + 80
|
||
|
||
# Any remaining unplaced frames go at the end
|
||
for fid, frame in existing.items():
|
||
if fid not in placed_ids:
|
||
frame["x"] = 0
|
||
frame["y"] = spec_y
|
||
new_children.append(frame)
|
||
fh = frame.get("height", 200)
|
||
if isinstance(fh, str):
|
||
fh = 400
|
||
spec_y += fh + 100
|
||
|
||
# 5. Replace children
|
||
data["children"] = new_children
|
||
|
||
# 6. Save
|
||
save(data)
|
||
|
||
# Stats
|
||
reusable_count = sum(1 for c in new_children if c.get("reusable"))
|
||
print(f"✅ Restructured ui-design.pen")
|
||
print(f" Total nodes: {len(new_children)}")
|
||
print(f" Reusable components: {reusable_count}")
|
||
print(f" Variables: {len(data['variables'])}")
|
||
print(f" Hardcoded colors replaced in existing frames")
|
||
print(f" Canvas organized into 5 sections")
|
||
|
||
if __name__ == "__main__":
|
||
transform()
|