# -*- coding: utf-8 -*- """Universal Space Design System and Theme Utilities (Catppuccin Minimalist Edition). Theme: Catppuccin Mocha (#1e1e2e Base / #181825 Mantle / #cba6f7 Mauve) Attribution: Built by @6chan """ from __future__ import annotations import base64 import io from typing import List, Sequence import gradio as gr from PIL import Image # Catppuccin Mocha Tokens CAT_BASE = "#1e1e2e" CAT_MANTLE = "#181825" CAT_SURFACE = "#242436" CAT_ELEVATED = "#2f2f45" CAT_BORDER = "#313244" CAT_BORDER_HOVER = "#45475a" CAT_TEXT = "#cdd6f4" CAT_SUBTEXT = "#a6adc8" CAT_MUTED = "#6c7086" CAT_MAUVE = "#cba6f7" CAT_LAVENDER = "#b4befe" CAT_DARK = "#11111b" def build_catppuccin_theme() -> gr.Theme: """Build the clean Catppuccin Mocha Gradio theme aligned with Hugging Face App styling.""" return gr.themes.Default( primary_hue=gr.themes.colors.indigo, secondary_hue=gr.themes.colors.slate, neutral_hue=gr.themes.colors.slate, radius_size=gr.themes.sizes.radius_md, font=[ gr.themes.GoogleFont("Inter"), "system-ui", "-apple-system", "sans-serif", ], font_mono=[ gr.themes.GoogleFont("JetBrains Mono"), "ui-monospace", "monospace", ], ).set( body_background_fill=CAT_MANTLE, body_text_color=CAT_TEXT, block_background_fill=CAT_BASE, block_border_color=CAT_BORDER, block_border_width="1px", block_radius="8px", block_label_background_fill=CAT_SURFACE, block_label_border_color=CAT_BORDER, block_label_text_color=CAT_SUBTEXT, block_title_text_color=CAT_SUBTEXT, input_background_fill=CAT_MANTLE, input_border_color=CAT_BORDER, input_border_color_focus=CAT_MAUVE, input_radius="6px", checkbox_background_color=CAT_MANTLE, checkbox_background_color_selected=CAT_MAUVE, checkbox_border_color=CAT_BORDER, checkbox_border_color_selected=CAT_MAUVE, checkbox_label_background_fill=CAT_MANTLE, checkbox_label_background_fill_hover=CAT_SURFACE, checkbox_label_background_fill_selected=CAT_ELEVATED, checkbox_label_border_color=CAT_BORDER, checkbox_label_border_color_hover=CAT_BORDER_HOVER, checkbox_label_border_color_selected=CAT_MAUVE, checkbox_label_text_color=CAT_SUBTEXT, checkbox_label_text_color_selected=CAT_TEXT, button_primary_background_fill=CAT_MAUVE, button_primary_background_fill_hover=CAT_LAVENDER, button_primary_text_color=CAT_DARK, button_primary_border_color=CAT_MAUVE, button_secondary_background_fill=CAT_SURFACE, button_secondary_background_fill_hover=CAT_ELEVATED, button_secondary_text_color=CAT_TEXT, button_secondary_border_color=CAT_BORDER, body_background_fill_dark=CAT_MANTLE, body_text_color_dark=CAT_TEXT, block_background_fill_dark=CAT_BASE, block_border_color_dark=CAT_BORDER, block_border_width_dark="1px", block_label_background_fill_dark=CAT_SURFACE, block_label_border_color_dark=CAT_BORDER, block_label_text_color_dark=CAT_SUBTEXT, block_title_text_color_dark=CAT_SUBTEXT, input_background_fill_dark=CAT_MANTLE, input_border_color_dark=CAT_BORDER, input_border_color_focus_dark=CAT_MAUVE, checkbox_background_color_dark=CAT_MANTLE, checkbox_background_color_selected_dark=CAT_MAUVE, checkbox_border_color_dark=CAT_BORDER, checkbox_border_color_selected_dark=CAT_MAUVE, checkbox_label_background_fill_dark=CAT_MANTLE, checkbox_label_background_fill_hover_dark=CAT_SURFACE, checkbox_label_background_fill_selected_dark=CAT_ELEVATED, checkbox_label_border_color_dark=CAT_BORDER, checkbox_label_border_color_hover_dark=CAT_BORDER_HOVER, checkbox_label_border_color_selected_dark=CAT_MAUVE, checkbox_label_text_color_dark=CAT_SUBTEXT, checkbox_label_text_color_selected_dark=CAT_TEXT, button_primary_background_fill_dark=CAT_MAUVE, button_primary_background_fill_hover_dark=CAT_LAVENDER, button_primary_text_color_dark=CAT_DARK, button_primary_border_color_dark=CAT_MAUVE, button_secondary_background_fill_dark=CAT_SURFACE, button_secondary_background_fill_hover_dark=CAT_ELEVATED, button_secondary_text_color_dark=CAT_TEXT, button_secondary_border_color_dark=CAT_BORDER, ) build_slate_theme = build_catppuccin_theme build_charcoal_theme = build_catppuccin_theme build_pi_theme = build_catppuccin_theme SPACE_CSS = """ /* Clean Minimal Catppuccin Typography */ body, input, textarea, select, button { font-family: 'Inter', system-ui, -apple-system, sans-serif; -webkit-font-smoothing: antialiased; } code, pre, .font-mono { font-family: 'JetBrains Mono', ui-monospace, monospace !important; } .gradio-container { max-width: 1260px !important; margin: 0 auto !important; padding: 1.5rem 1.5rem 2rem 1.5rem !important; } /* Centered Hugging Face App Header */ .space-header { text-align: center; margin-bottom: 1.25rem; padding-bottom: 0.75rem; } .space-title { margin: 0 0 0.4rem 0; font-size: 1.55rem; font-weight: 700; letter-spacing: -0.02em; color: #cdd6f4; } .space-subtitle { margin: 0 0 0.65rem 0; font-size: 0.92rem; color: #a6adc8; } .space-links { display: inline-flex; align-items: center; gap: 0.55rem; font-size: 0.84rem; color: #6c7086; } .space-links a { color: #b4befe; text-decoration: none; font-weight: 500; transition: color 0.12s ease; } .space-links a:hover { color: #cba6f7; text-decoration: underline; } .space-signature { font-family: 'JetBrains Mono', ui-monospace, monospace; font-size: 0.76rem; font-weight: 500; padding: 0.12rem 0.48rem; background: #242436; color: #cba6f7 !important; border-radius: 5px; border: 1px solid #313244; text-decoration: none !important; } .space-signature:hover { border-color: #cba6f7; background: #2f2f45; } .space-helper-line { font-size: 0.85rem; color: #a6adc8; margin-bottom: 0.65rem; } /* Hugging Face App Style Radio Pills & Inputs */ .gradio-container .wrap.svelte-1cl284s, .gradio-container .form { border-color: #313244 !important; } /* Primary Action Button (Aligned with HF App Button Layout) */ .btn-extract { background: #cba6f7 !important; color: #11111b !important; font-weight: 600 !important; font-size: 0.92rem !important; height: 42px !important; border-radius: 6px !important; border: 1px solid #cba6f7 !important; box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2) !important; transition: background-color 0.14s ease, border-color 0.14s ease, transform 0.08s ease !important; margin-top: 0.25rem !important; } .btn-extract:hover { background: #b4befe !important; border-color: #b4befe !important; } .btn-extract:active { transform: translateY(1px) !important; } /* Workstation Tabs */ #workbench-tabs { border: none !important; background: transparent !important; } #workbench-tabs .tab-nav { border-bottom: 1px solid #313244 !important; margin-bottom: 0.5rem !important; } #workbench-tabs .tab-nav button { font-size: 0.82rem !important; font-weight: 500 !important; padding: 0.45rem 0.9rem !important; color: #a6adc8 !important; border: none !important; background: transparent !important; } #workbench-tabs .tab-nav button.selected { color: #cba6f7 !important; border-bottom: 2px solid #cba6f7 !important; font-weight: 600 !important; } /* Checkerboard Background for RGBA Gallery & Preview */ #layers .preview, #layers .thumbnail-item, #layers .gallery-item, #layers img, #preview-container img { background-color: #181825 !important; background-image: linear-gradient(45deg, #242436 25%, transparent 25%), linear-gradient(-45deg, #242436 25%, transparent 25%), linear-gradient(45deg, transparent 75%, #242436 75%), linear-gradient(-45deg, transparent 75%, #242436 75%) !important; background-size: 16px 16px !important; background-position: 0 0, 0 8px, 8px -8px, -8px 0 !important; border-radius: 6px !important; } .layer-caption-note { font-size: 0.8rem; color: #a6adc8; margin: 0.35rem 0 0.65rem 0.15rem; } /* Result status bar */ .result-status { font-family: 'JetBrains Mono', ui-monospace, monospace; font-size: 0.76rem; padding: 0.4rem 0.7rem; border-radius: 6px; background: #242436; border: 1px solid #313244; color: #cdd6f4; margin-bottom: 0.5rem; } /* Interactive Canvas Stage */ .sl-panel { display: flex; gap: 16px; align-items: flex-start; flex-wrap: wrap; width: 100%; padding: 4px 0; } .sl-panel .sl-stagewrap { flex: 1 1 360px; min-width: 280px; } .sl-panel .sl-stage { position: relative; width: 100%; overflow: hidden; border-radius: 8px; border: 1px solid #313244; touch-action: none; cursor: grab; background-color: #181825; background-image: linear-gradient(45deg, #242436 25%, transparent 25%, transparent 75%, #242436 75%), linear-gradient(45deg, #242436 25%, transparent 25%, transparent 75%, #242436 75%); background-size: 20px 20px; background-position: 0 0, 10px 10px; } .sl-panel .sl-stage:active { cursor: grabbing; } .sl-panel .sl-lyr { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: fill; user-select: none; -webkit-user-drag: none; pointer-events: none; transition: opacity 0.15s ease; } .sl-panel .sl-lyr.sl-off { opacity: 0 !important; } .sl-panel .sl-side { flex: 0 0 230px; display: flex; flex-direction: column; gap: 6px; } .sl-panel .sl-sidehead { font-size: 11px; font-weight: 500; color: #a6adc8; margin-bottom: 2px; display: flex; justify-content: space-between; } .sl-panel .sl-row { display: flex; align-items: center; gap: 8px; padding: 6px 8px; border-radius: 6px; border: 1px solid transparent; cursor: pointer; background: #242436; transition: all 0.12s ease; } .sl-panel .sl-row:hover { background: #2f2f45; border-color: #45475a; } .sl-panel .sl-row.sl-sel { border-color: #cba6f7 !important; background-color: #2f2f45 !important; } .sl-panel .sl-row.sl-dim, .sl-panel .sl-row.sl-blank { opacity: 0.35; } .sl-panel .sl-eye { background: transparent; border: none; cursor: pointer; padding: 3px; color: #6c7086; display: flex; align-items: center; justify-content: center; border-radius: 3px; } .sl-panel .sl-eye:hover { color: #cdd6f4; } .sl-panel .sl-th { width: 34px; height: 34px; object-fit: contain; border-radius: 4px; flex: 0 0 34px; border: 1px solid #313244; background-color: #181825; background-image: linear-gradient(45deg, #242436 25%, transparent 25%, transparent 75%, #242436 75%), linear-gradient(45deg, #242436 25%, transparent 25%, transparent 75%, #242436 75%); background-size: 8px 8px; background-position: 0 0, 4px 4px; } .sl-panel .sl-name { font-size: 12px; font-weight: 500; line-height: 1.25; color: #cdd6f4; display: flex; flex-direction: column; } .sl-panel .sl-name em { font-style: normal; font-size: 10px; color: #6c7086; font-family: 'JetBrains Mono', ui-monospace, monospace; } .sl-panel .sl-reset { margin-top: 6px; font-size: 11px; font-weight: 500; padding: 6px 10px; border-radius: 5px; border: 1px solid #313244; background: transparent; color: #a6adc8; cursor: pointer; transition: all 0.12s ease; width: 100%; text-align: center; } .sl-panel .sl-reset:hover { color: #cdd6f4; border-color: #45475a; background: #242436; } /* Footer with Signature */ .space-footer { margin-top: 1.75rem; padding-top: 1rem; text-align: center; border-top: 1px solid #313244; font-size: 0.76rem; color: #6c7086; } .space-footer a { color: #b4befe; text-decoration: none; font-weight: 500; transition: color 0.12s ease; } .space-footer a:hover { color: #cba6f7; text-decoration: underline; } """ PANEL_JS = r""" () => { if (window.__slPanelWired) return; window.__slPanelWired = true; document.addEventListener("click", (e) => { const eyeBtn = e.target.closest(".sl-eye"); if (eyeBtn) { e.stopPropagation(); const panel = eyeBtn.closest(".sl-panel"); if (!panel) return; const l = panel.querySelector('.sl-lyr[data-i="' + eyeBtn.dataset.eye + '"]'); if (!l) return; const hidden = l.classList.toggle("sl-off"); eyeBtn.classList.toggle("sl-hid", hidden); const row = eyeBtn.closest(".sl-row"); if (row) row.classList.toggle("sl-dim", hidden); return; } const resetBtn = e.target.closest(".sl-reset"); if (resetBtn) { e.stopPropagation(); const panel = resetBtn.closest(".sl-panel"); if (!panel) return; panel.querySelectorAll(".sl-lyr").forEach((l) => { l.classList.remove("sl-off"); l.style.transform = ""; l.dataset.tx = 0; l.dataset.ty = 0; }); panel.querySelectorAll(".sl-eye").forEach((b) => b.classList.remove("sl-hid")); panel.querySelectorAll(".sl-row").forEach((r) => r.classList.remove("sl-dim")); return; } const row = e.target.closest(".sl-row"); if (row) { const panel = row.closest(".sl-panel"); if (!panel) return; panel.querySelectorAll(".sl-row").forEach((r) => r.classList.remove("sl-sel")); row.classList.add("sl-sel"); panel.dataset.sel = row.dataset.i; } }); let drag = null; const pt = (e) => { const t = e.touches && e.touches[0]; return t ? { x: t.clientX, y: t.clientY } : { x: e.clientX, y: e.clientY }; }; const move = (e) => { if (!drag) return; const s = pt(e); const tx = drag.tx + (s.x - drag.sx); const ty = drag.ty + (s.y - drag.sy); drag.l.dataset.tx = tx; drag.l.dataset.ty = ty; drag.l.style.transform = "translate(" + tx + "px," + ty + "px)"; e.preventDefault(); }; const up = () => { drag = null; document.removeEventListener("mousemove", move); document.removeEventListener("mouseup", up); document.removeEventListener("touchmove", move); document.removeEventListener("touchend", up); }; const down = (e) => { const stage = e.target.closest(".sl-stage"); if (!stage) return; const panel = stage.closest(".sl-panel"); if (!panel) return; const l = panel.querySelector('.sl-lyr[data-i="' + panel.dataset.sel + '"]'); if (!l || l.classList.contains("sl-off")) return; const s = pt(e); drag = { l: l, sx: s.x, sy: s.y, tx: parseFloat(l.dataset.tx || 0), ty: parseFloat(l.dataset.ty || 0), }; document.addEventListener("mousemove", move); document.addEventListener("mouseup", up); document.addEventListener("touchmove", move, { passive: false }); document.addEventListener("touchend", up); e.preventDefault(); }; document.addEventListener("mousedown", down); document.addEventListener("touchstart", down, { passive: false }); } """ SVG_EYE_ICON = """""" def pil_to_data_uri(im: Image.Image, max_dim: int | None = None) -> str: """Encode PIL image to inline base64 PNG data URI.""" if max_dim: w, h = im.size scale = max_dim / max(w, h) if scale < 1.0: im = im.resize((max(1, int(w * scale)), max(1, int(h * scale))), Image.Resampling.LANCZOS) buf = io.BytesIO() im.save(buf, format="PNG") b64 = base64.b64encode(buf.getvalue()).decode("ascii") return f"data:image/png;base64,{b64}" def build_layer_panel( layer_pils: list[Image.Image], coverage: list[float], layer_names: Sequence[str] | None = None, width: int = 512, height: int = 552, ) -> str: """Render minimalist Catppuccin layer canvas and inspector.""" n = len(layer_pils) if n == 0: return '
No layers extracted yet.
' sel_i = next((i for i in range(n - 1, 0, -1) if coverage[i] >= 0.5), 0) stack = [] rows = [] for i, pil in enumerate(layer_pils): uri = pil_to_data_uri(pil) stack.append( f'layer {i}' ) for i in range(n - 1, -1, -1): if layer_names and i < len(layer_names): name = layer_names[i] elif i == 0: name = "Background" elif i == n - 1: name = "Foreground" else: name = f"Layer {i + 1}" pct = coverage[i] empty = pct < 0.5 tag = "Empty" if empty else f"{pct:.1f}% cover" cls = " sl-sel" if i == sel_i else "" cls += " sl-blank" if empty else "" th_uri = pil_to_data_uri(layer_pils[i], max_dim=72) rows.append( f'
' f'' f'' f'{name}{tag}' f'
' ) return ( f'
' f'
' f'
{"".join(stack)}
' f'
' f'
' f'
Layers ({n})Drag on canvas · Eye to hide
' f' {"".join(rows)}' f' ' f'
' f'
' ) def render_header( title: str = "Ming-Image-Layers", handle: str = "6chan", subtitle: str = "Decompose flattened designs into independently editable transparent layers.", ) -> str: """Render a centered Hugging Face App header with author signature.""" return f"""

{title}

{subtitle}

Describe the elements you want on each layer, ordered front to back, with the background last.
""".strip() def render_footer( handle: str = "6chan", model_name: str | None = None, model_url: str | None = None, extra_links: list[tuple[str, str]] | None = None, ) -> str: """Render a minimalist developer footer with clear attribution.""" links_html = [] if model_name and model_url: links_html.append(f'{model_name}') if extra_links: for name, url in extra_links: links_html.append(f'{name}') links_str = " · ".join(links_html) links_block = f'
{links_str}
' if links_str else "" return f""" """.strip()