|
import urllib.parse |
|
import gradio as gr |
|
|
|
|
|
|
|
|
|
def generate_static_badge(label, message, color, label_color, |
|
logo, logo_color, style, link): |
|
base = "https://img.shields.io/static/v1" |
|
params = [] |
|
if label: params.append(f"label={urllib.parse.quote(label, safe='')}") |
|
if message: params.append(f"message={urllib.parse.quote(message, safe='')}") |
|
if color: params.append(f"color={urllib.parse.quote(color, safe='')}") |
|
if label_color: params.append(f"labelColor={urllib.parse.quote(label_color, safe='')}") |
|
if logo: params.append(f"logo={urllib.parse.quote(logo, safe='')}") |
|
if logo_color: params.append(f"logoColor={urllib.parse.quote(logo_color, safe='')}") |
|
if style: params.append(f"style={urllib.parse.quote(style, safe='')}") |
|
|
|
badge_url = base + "?" + "&".join(params) |
|
img = f'<img src="{badge_url}" alt="badge">' |
|
if link: |
|
img = f'<a href="{link}" target="_blank">{img}</a>' |
|
|
|
preview = (f"<div style='padding:30px;background:linear-gradient(135deg,#f8f9fa,#e9ecef);" |
|
f"border-radius:16px;display:flex;justify-content:center;box-shadow:0 6px 12px" |
|
f" rgba(0,0,0,0.05);'>{img}</div>") |
|
return img, preview |
|
|
|
|
|
|
|
|
|
|
|
with gr.Blocks(theme=gr.themes.Soft()) as demo: |
|
|
|
gr.HTML(""" |
|
<style> |
|
body{background:linear-gradient(120deg,#f8f9fa,#e2eafc);font-family:'Poppins','Noto Sans KR',sans-serif;} |
|
.gradio-container{background:rgba(255,255,255,0.85);backdrop-filter:blur(10px); |
|
border-radius:20px;padding:24px;box-shadow:0 10px 30px rgba(0,0,0,0.08);max-width:1000px;margin:0 auto;} |
|
.gr-button{background:linear-gradient(135deg,#a8dadc,#88c1e9)!important;color:#1d3557!important; |
|
border:none!important;border-radius:10px!important;font-weight:600!important;transition:all .3s; |
|
box-shadow:0 4px 10px rgba(138,198,209,.3)!important;} |
|
.gr-button:hover{transform:translateY(-2px)!important;box-shadow:0 6px 15px rgba(138,198,209,.4)!important;} |
|
.gr-textbox,.gr-select,.gr-color{background:#e8f6f3!important;border:2px solid #d9f0ea!important; |
|
border-radius:12px!important;transition:all .3s;} |
|
.gr-textbox:focus,.gr-select:focus,.gr-color:focus{ |
|
border-color:#a8dadc!important;box-shadow:0 0 0 3px rgba(168,218,220,.25)!important;} |
|
.example-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:16px;margin-top:20px;} |
|
.example-item{background:linear-gradient(135deg,#f1f8ff,#e8f4ff);border-radius:12px;padding:16px; |
|
text-align:center;cursor:pointer;transition:all .3s;border:2px solid transparent;} |
|
.example-item:hover{transform:translateY(-3px);box-shadow:0 8px 15px rgba(0,0,0,0.05);border-color:#a8dadc;} |
|
@media(max-width:768px){.example-grid{grid-template-columns:repeat(2,1fr);} } |
|
@media(max-width:600px){.example-grid{grid-template-columns:1fr;} } |
|
</style> |
|
<div style="text-align:center;margin-bottom:24px;"> |
|
<h1 style="font-size:2.8rem;margin-bottom:.2em;background:linear-gradient(90deg,#5e60ce,#64dfdf); |
|
-webkit-background-clip:text;-webkit-text-fill-color:transparent;">🎨 BadgeCraft</h1> |
|
<p style="font-size:1.2rem;color:#457b9d;">Create beautiful badges with live preview and HTML snippet</p> |
|
</div> |
|
""") |
|
|
|
|
|
label = gr.Textbox("Discord", label="Label", elem_id="label-input") |
|
message = gr.Textbox("Join our community", label="Message", elem_id="message-input") |
|
color = gr.ColorPicker("#5865F2", label="Background Color", elem_id="color-input") |
|
label_color = gr.ColorPicker("#99AAFF", label="Label Background Color", elem_id="label-color-input") |
|
logo = gr.Textbox("discord", label="Logo", elem_id="logo-input") |
|
logo_color = gr.ColorPicker("#ffffff", label="Logo Color", elem_id="logo-color-input") |
|
style = gr.Dropdown(["flat","flat-square","plastic","for-the-badge","social"], |
|
value="for-the-badge", label="Style", elem_id="style-input") |
|
link = gr.Textbox("https://discord.gg/openfreeai", label="Link (URL)", elem_id="link-input") |
|
|
|
out_code = gr.Code(language="html", lines=3, label="HTML Snippet") |
|
out_prev = gr.HTML() |
|
|
|
|
|
examples = [ |
|
["Discord","Openfree AI","#5865F2","#99AAFF","discord","white","for-the-badge", |
|
"https://discord.gg/openfreeai"], |
|
["X.com","Follow us","#1DA1F2","#00CFFF","x","white","for-the-badge", |
|
"https://x.com/openfree_ai"], |
|
["Collections","Explore","#FFB300","#FFF176","huggingface","black","for-the-badge", |
|
"https://huggingface.co/collections/VIDraft/best-open-ai-services-68057e6e312880ea92abaf4c"], |
|
["GitHub","Star us","#0A0A0A","#39FF14","github","white","for-the-badge", |
|
"https://github.com/openfreeai"], |
|
["YouTube","Watch now","#E50000","#FF5E5E","youtube","white","for-the-badge", |
|
"https://www.youtube.com/@AITechTree"], |
|
["Facebook","Like us","#1877F2","#6FAFFF","facebook","white","for-the-badge", |
|
"https://www.facebook.com/profile.php?id=61575353674679"], |
|
["Instagram","友情 萬世","#E4405F","#FF77A9","instagram","white","for-the-badge", |
|
"https://www.instagram.com/openfree_ai/"], |
|
["Threads","함께 즐겨요.","#000000","#FF00FF","threads","white","for-the-badge", |
|
"https://www.threads.net/@openfree_ai"] |
|
] |
|
|
|
|
|
grid = '<div class="example-grid">' |
|
for i,e in enumerate(examples): |
|
lbl,msg,bg,lbg,logo_e,logoc,sty,_ = e |
|
def fix(c): |
|
return "#ffffff" if c=="white" else "#000000" if c=="black" else c |
|
url = ("https://img.shields.io/static/v1?" |
|
f"label={urllib.parse.quote(lbl)}&message={urllib.parse.quote(msg)}&" |
|
f"color={urllib.parse.quote(fix(bg))}&labelColor={urllib.parse.quote(fix(lbg))}&" |
|
f"logo={urllib.parse.quote(logo_e)}&logoColor={urllib.parse.quote(fix(logoc))}&" |
|
f"style={urllib.parse.quote(sty)}") |
|
grid += (f'<div class="example-item" onclick="applyExample({i})">' |
|
f'<img src="{url}" style="margin-bottom:8px;"><br>' |
|
f'<span style="font-size:.9rem;color:#457b9d;">{lbl}</span></div>') |
|
grid += "</div>" |
|
|
|
|
|
js = f""" |
|
<script> |
|
const examples = {examples}; |
|
|
|
/* -------- helpers -------- */ |
|
function reactSet(el,val){{ |
|
if(!el) return; |
|
const proto = (el.tagName==="TEXTAREA") |
|
? HTMLTextAreaElement.prototype |
|
: HTMLInputElement.prototype; |
|
const set = Object.getOwnPropertyDescriptor(proto,"value").set; |
|
set.call(el,val); |
|
el.dispatchEvent(new Event("input",{{bubbles:true}})); |
|
el.dispatchEvent(new Event("change",{{bubbles:true}})); // ColorPicker는 change에 반응 |
|
el.dispatchEvent(new Event("blur", {{bubbles:true}})); // 최종 확정 |
|
}} |
|
function toHex(c){{ |
|
if(!c) return "#000000"; |
|
c = c.toLowerCase(); |
|
if(c==="white") return "#ffffff"; |
|
if(c==="black") return "#000000"; |
|
return c; |
|
}} |
|
function setColor(elemId,val){{ |
|
const root = document.getElementById(elemId); |
|
if(!root) return; |
|
// gr.ColorPicker 내부 input 은 type=text 이므로 먼저 찾음 |
|
let inp = root.querySelector("input[type='text'],input[type='color']"); |
|
if(!inp) return; |
|
reactSet(inp,val); |
|
// gradio 4.17 이후 : data-testid=color-picker 값 동기화 필요 |
|
const pickerDiv = root.querySelector("[data-testid='color-picker']") |
|
if(pickerDiv) pickerDiv.style.background = val; // 버튼 시각적 즉시 변경 |
|
}} |
|
|
|
/* -------- main: example click -------- */ |
|
window.applyExample = (i) => {{ |
|
const ex = examples[i]; |
|
// 0,label |
|
reactSet(document.querySelector("#label-input input,#label-input textarea"), ex[0]); |
|
// 1,message |
|
reactSet(document.querySelector("#message-input input,#message-input textarea"), ex[1]); |
|
// 2,bg color |
|
setColor("color-input", toHex(ex[2])); |
|
// 3,label bg |
|
setColor("label-color-input", toHex(ex[3])); |
|
// 4,logo |
|
reactSet(document.querySelector("#logo-input input,#logo-input textarea"), ex[4]); |
|
// 5,logo color |
|
setColor("logo-color-input", toHex(ex[5])); |
|
// 6,style |
|
const sel = document.querySelector("#style-input select"); |
|
if(sel){{ |
|
sel.value = ex[6]; |
|
sel.dispatchEvent(new Event("change",{{bubbles:true}})); |
|
}} |
|
// 7,link |
|
reactSet(document.querySelector("#link-input input,#link-input textarea"), ex[7]); |
|
}} |
|
</script> |
|
""" |
|
|
|
gr.HTML(grid + js) |
|
|
|
|
|
comps = [label,message,color,label_color,logo,logo_color,style,link] |
|
for c in comps: |
|
c.change(generate_static_badge, comps, [out_code,out_prev]) |
|
|
|
|
|
gr.HTML('<p style="text-align:center;margin-top:30px;font-size:.9rem;color:#6d6875">' |
|
'© 2023‑2025 BadgeCraft | MIT License ' |
|
'<a href="https://discord.gg/openfreeai" style="color:#5e60ce" target="_blank">Discord</a></p>') |
|
|
|
if __name__ == "__main__": |
|
demo.launch() |
|
|