""" Bake the Vibe BI header into each feature SVG in assets/. For each feature SVG: 1. Inject the VIBE BI branding header (ASCII art, connection flow, install cmd) 2. Shift original content down by HEADER_HEIGHT 3. Write the updated SVG back to disk Logos are referenced via to preserve original dimensions. """ import re import sys from pathlib import Path ASSETS_DIR = Path(__file__).parent.parent / "assets" HEADER_HEIGHT = 210 SVG_WIDTH = 850 # Feature SVGs to process (same list as generate_images.py) FEATURE_SVGS = [ "before-after.svg", "architecture-flow.svg", "backup-restore.svg", "bulk-operations.svg", "chat-demo.svg", "dax-debugging.svg", "feature-grid.svg", "model-health-check.svg", "rls-testing.svg", "skills-hub.svg", "token-cost.svg", "how-it-works.svg", "dax-skill.svg", "modeling-skill.svg", "deploy-secure.svg", "docs-diagnostics.svg", "cta-start.svg", "report-layer.svg", "dual-layer.svg", "visual-types.svg", "report-workflow.svg", "auto-sync.svg", "chat-demo-report.svg", "commands.svg", "layers.svg", "release-vibe-bi.svg", "stats.svg", "workflow.svg", ] # The Vibe BI header SVG fragment (210px tall, 850px wide) HEADER_DEFS = """\ """ HEADER_BODY = """\ ██╗ ██╗ ██╗ ██████╗ ███████╗ ██████╗ ██╗ ██║ ██║ ██║ ██╔══██╗ ██╔════╝ ██╔══██╗ ██║ ██║ ██║ ██║ ██████╔╝ █████╗ ██████╔╝ ██║ ╚██╗ ██╔╝ ██║ ██╔══██╗ ██╔══╝ ██╔══██╗ ██║ ╚████╔╝ ██║ ██████╔╝ ███████╗ ██████╔╝ ██║ ╚═══╝ ╚═╝ ╚═════╝ ╚══════╝ ╚═════╝ ╚═╝ ██╗ ██╗ ██╗ ██████╗ ███████╗ ██████╗ ██╗ ██║ ██║ ██║ ██╔══██╗ ██╔════╝ ██╔══██╗ ██║ ██║ ██║ ██║ ██████╔╝ █████╗ ██████╔╝ ██║ ╚██╗ ██╔╝ ██║ ██╔══██╗ ██╔══╝ ██╔══██╗ ██║ ╚████╔╝ ██║ ██████╔╝ ███████╗ ██████╔╝ ██║ ╚═══╝ ╚═╝ ╚═════╝ ╚══════╝ ╚═════╝ ╚═╝ The First CLI for Both Power BI Modeling and Reporting Claude Code ██████╗ ██████╗ ██╗ ██████╗ ██╗ ██╗ ██╔══██╗ ██╔══██╗ ██║ ██╔════╝ ██║ ██║ ██████╔╝ ██████╔╝ ██║ ███╗ ██║ ██║ ██║ ██╔═══╝ ██╔══██╗ ██║ ╚══╝ ██║ ██║ ██║ ██║ ██████╔╝ ██║ ╚██████╗ ███████╗ ██║ ╚═╝ ╚═════╝ ╚═╝ ╚═════╝ ╚══════╝ ╚═╝ ██████╗ ██████╗ ██╗ ██████╗ ██╗ ██╗ ██╔══██╗ ██╔══██╗ ██║ ██╔════╝ ██║ ██║ ██████╔╝ ██████╔╝ ██║ ███╗ ██║ ██║ ██║ ██╔═══╝ ██╔══██╗ ██║ ╚══╝ ██║ ██║ ██║ ██║ ██████╔╝ ██║ ╚██████╗ ███████╗ ██║ ╚═╝ ╚═════╝ ╚═╝ ╚═════╝ ╚══════╝ ╚═╝ Power BI Modeling + Reporting """ def get_svg_height(svg_text: str) -> int: """Extract height from SVG viewBox (expects '0 0 W H' format).""" match = re.search(r'viewBox="0 0 \d+ (\d+)"', svg_text) if match: return int(match.group(1)) match = re.search(r'height="(\d+)"', svg_text) if match: return int(match.group(1)) return 400 def extract_svg_inner(svg_text: str) -> str: """Extract everything between and tags.""" inner = re.sub(r"<\?xml[^>]*\?>\s*", "", svg_text) inner = re.sub(r"]*>", "", inner, count=1) inner = re.sub(r"\s*$", "", inner) return inner def extract_defs_content(svg_text: str) -> str: """Extract the content inside ... if present.""" match = re.search(r"(.*?)", svg_text, re.DOTALL) return match.group(1) if match else "" def remove_bg_rect(svg_inner: str) -> str: """Remove the first background rect (fill='#0d1117') from the SVG inner content.""" return re.sub( r'\s*]*fill="#0d1117"[^/]*/>\s*', "\n", svg_inner, count=1, ) def remove_defs_block(svg_inner: str) -> str: """Remove the ... block from SVG inner content.""" return re.sub(r"\s*.*?\s*", "\n", svg_inner, flags=re.DOTALL) def is_already_baked(svg_text: str) -> bool: """Check if the SVG already contains the Vibe BI header.""" return "VIBE BI HEADER" in svg_text def unbake_header(svg_text: str) -> str: """Strip the baked header and reconstruct the original feature SVG.""" # Extract the feature content from inside ... match = re.search( r'\s*(.*?)\s*\s*', svg_text, re.DOTALL, ) if not match: return svg_text feature_inner = match.group(1) original_height = get_svg_height(svg_text) - HEADER_HEIGHT # Extract feature-specific defs (skip header gradient defs) all_defs = extract_defs_content(svg_text) # Remove header-specific defs (hdr-bar* and pbi-lg*) feature_defs = re.sub( r'\s*]*>.*?', "", all_defs, flags=re.DOTALL, ) feature_defs = feature_defs.strip() defs_block = "" if feature_defs: defs_block = f"\n \n{feature_defs}\n \n" return f""" {defs_block} {feature_inner} """ def bake_header(svg_text: str) -> str: """Inject the Vibe BI header into a feature SVG.""" original_height = get_svg_height(svg_text) new_height = original_height + HEADER_HEIGHT # Extract parts feature_inner = extract_svg_inner(svg_text) feature_defs = extract_defs_content(svg_text) # Clean feature inner: remove defs block and background rect clean_inner = remove_defs_block(feature_inner) clean_inner = remove_bg_rect(clean_inner) # Merge defs all_defs = HEADER_DEFS if feature_defs.strip(): all_defs += "\n" + feature_defs return f""" {all_defs} {HEADER_BODY} {clean_inner} """ def process_file(svg_file: str, dry_run: bool = False, force: bool = False) -> None: """Process a single feature SVG file.""" filepath = ASSETS_DIR / svg_file if not filepath.exists(): print(f" SKIP {svg_file} (not found)") return svg_text = filepath.read_text(encoding="utf-8") if is_already_baked(svg_text): if force: svg_text = unbake_header(svg_text) print(f" STRIP {svg_file} (removed old header)") else: print(f" SKIP {svg_file} (already has header, use --force)") return original_height = get_svg_height(svg_text) result = bake_header(svg_text) new_height = original_height + HEADER_HEIGHT if dry_run: print(f" DRY {svg_file}: {original_height} -> {new_height}px") else: filepath.write_text(result, encoding="utf-8") print(f" OK {svg_file}: {original_height} -> {new_height}px") def main(): dry_run = "--dry-run" in sys.argv force = "--force" in sys.argv targets = sys.argv[1:] targets = [t for t in targets if not t.startswith("--")] if not targets: targets = FEATURE_SVGS mode = "DRY RUN" if dry_run else ("FORCE REBAKE" if force else "BAKING") print(f"\n{mode}: Injecting Vibe BI header into {len(targets)} SVGs\n") for svg_file in targets: process_file(svg_file, dry_run=dry_run, force=force) print(f"\nDone! Processed {len(targets)} files.") if __name__ == "__main__": main()