Spaces:
Running on Zero
Running on Zero
Download theme.py from 6chan/ming-image-layered: direct link, hf CLI and curl.
- Browser
- Download file 20 kB
-
https://huggingface.co/spaces/6chan/ming-image-layered/resolve/main/theme.py
- Command line
-
hf download hf://spaces/6chan/ming-image-layered/theme.py
-
curl -L -o theme.py https://huggingface.co/spaces/6chan/ming-image-layered/resolve/main/theme.py
20 kB
| # -*- 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 = """<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M2 12s3-7 10-7 10 7 10 7-3 7-10 7-10-7-10-7Z"></path><circle cx="12" cy="12" r="3"></circle></svg>""" | |
| 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 '<div class="sl-panel"><div class="text-xs text-[#a6adc8]">No layers extracted yet.</div></div>' | |
| 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'<img class="sl-lyr" data-i="{i}" src="{uri}" draggable="false" alt="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'<div class="sl-row{cls}" data-i="{i}">' | |
| f'<button class="sl-eye" data-eye="{i}" title="Toggle visibility">{SVG_EYE_ICON}</button>' | |
| f'<img class="sl-th" src="{th_uri}" draggable="false" alt="">' | |
| f'<span class="sl-name">{name}<em>{tag}</em></span>' | |
| f'</div>' | |
| ) | |
| return ( | |
| f'<div class="sl-panel" data-sel="{sel_i}">' | |
| f' <div class="sl-stagewrap">' | |
| f' <div class="sl-stage" style="aspect-ratio:{width}/{height}">{"".join(stack)}</div>' | |
| f' </div>' | |
| f' <div class="sl-side">' | |
| f' <div class="sl-sidehead"><span>Layers ({n})</span><span>Drag on canvas 路 Eye to hide</span></div>' | |
| f' {"".join(rows)}' | |
| f' <button class="sl-reset">Reset positions</button>' | |
| f' </div>' | |
| f'</div>' | |
| ) | |
| 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""" | |
| <div class="space-header"> | |
| <h1 class="space-title">{title}</h1> | |
| <p class="space-subtitle">{subtitle}</p> | |
| <div class="space-links"> | |
| <a href="https://huggingface.co/6chan/Ming-Image-0.1-Design-Layer-FP8" target="_blank">FP8 Model</a> | |
| <span>路</span> | |
| <a href="https://huggingface.co/inclusionAI/Ming-Image-0.1-Design-Layer" target="_blank">Base Model</a> | |
| <span>路</span> | |
| <a href="https://github.com/inclusionAI/Ming-Image" target="_blank">GitHub</a> | |
| <span>路</span> | |
| <a href="https://huggingface.co/{handle}" target="_blank" class="space-signature">@{handle}</a> | |
| </div> | |
| </div> | |
| <div class="space-helper-line"> | |
| Describe the elements you want on each layer, ordered front to back, with the background last. | |
| </div> | |
| """.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'<a href="{model_url}" target="_blank">{model_name}</a>') | |
| if extra_links: | |
| for name, url in extra_links: | |
| links_html.append(f'<a href="{url}" target="_blank">{name}</a>') | |
| links_str = " 路 ".join(links_html) | |
| links_block = f'<div style="margin-top: 0.25rem;">{links_str}</div>' if links_str else "" | |
| return f""" | |
| <div class="space-footer"> | |
| <div>Built by <a href="https://huggingface.co/{handle}" target="_blank">@{handle}</a> 路 Layer decomposition studio</div> | |
| {links_block} | |
| </div> | |
| """.strip() | |