|
|
|
|
|
|
|
|
|
|
|
import urllib.parse |
|
import time |
|
import json |
|
import gradio as gr |
|
|
|
|
|
|
|
|
|
|
|
def generate_static_badge(label, message, |
|
color, label_color, |
|
logo, logo_color, |
|
style, link): |
|
"""Shields.io badge URL 과 HTML / 미리보기 카드 반환""" |
|
|
|
|
|
strip = lambda c: c.lstrip("#") if isinstance(c, str) else c |
|
color, label_color, logo_color = map(strip, (color, label_color, logo_color)) |
|
|
|
|
|
base = "https://img.shields.io/static/v1" |
|
qs = { |
|
"label": label, |
|
"message": message, |
|
"color": color, |
|
"labelColor": label_color, |
|
"logo": logo, |
|
"logoColor": logo_color, |
|
"style": style, |
|
"_ts": str(int(time.time())) |
|
} |
|
query = "&".join(f"{k}={urllib.parse.quote(v, safe='')}" |
|
for k, v in qs.items() if v) |
|
badge_url = f"{base}?{query}" |
|
|
|
|
|
img_tag = f'<img src="{badge_url}" alt="badge">' |
|
html_code = f'<a href="{link}" target="_blank">{img_tag}</a>' if link else img_tag |
|
preview = ( |
|
"<div style='padding:30px;text-align:center;" |
|
"border-radius:16px;background:linear-gradient(135deg,#f8f9fa,#e9ecef);" |
|
"box-shadow:0 6px 12px rgba(0,0,0,.05);'>" |
|
f"{html_code}</div>" |
|
) |
|
return html_code, 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,.85); |
|
backdrop-filter:blur(10px); |
|
border-radius:20px; |
|
padding:24px; |
|
box-shadow:0 10px 30px rgba(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 ease!important; |
|
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 ease!important; |
|
} |
|
.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; |
|
} |
|
label.block span{ |
|
color:#457b9d!important; |
|
font-weight:600!important; |
|
font-size:1rem!important; |
|
} |
|
h1{ |
|
color:#5e60ce;font-weight:800;letter-spacing:-.5px; |
|
} |
|
h3{color:#5e60ce;font-weight:600;} |
|
.footer{ |
|
margin-top:30px;text-align:center;font-size:.9rem;color:#6d6875; |
|
} |
|
.badge-section{ |
|
background:rgba(255,255,255,.7); |
|
border-radius:16px; |
|
padding:20px; |
|
box-shadow:0 4px 12px rgba(0,0,0,.03); |
|
margin-bottom:24px; |
|
border:1px solid rgba(230,240,255,.7); |
|
} |
|
.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 ease;border:2px solid transparent; |
|
} |
|
.example-item:hover{ |
|
transform:translateY(-3px); |
|
box-shadow:0 8px 15px rgba(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> |
|
""") |
|
|
|
|
|
gr.HTML(""" |
|
<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;margin:.5em 0;color:#457b9d;max-width:700px;margin:0 auto;"> |
|
Create beautiful badges with live preview and HTML snippet |
|
</p> |
|
<div style="margin-top:10px;display:flex;justify-content:center;gap:12px;flex-wrap:wrap;"> |
|
<span style="display:inline-block;background:#e9f5db;color:#588157; |
|
padding:6px 12px;border-radius:30px;font-size:.9rem;"> |
|
<strong>✨ MIT License</strong> |
|
</span> |
|
<span style="display:inline-block;background:#d8f3dc;color:#2d6a4f; |
|
padding:6px 12px;border-radius:30px;font-size:.9rem;"> |
|
<strong>👥 Created by OpenFreeAI Team</strong> |
|
</span> |
|
</div> |
|
</div> |
|
""") |
|
|
|
|
|
with gr.Tabs(): |
|
|
|
with gr.TabItem("Badge Generator"): |
|
|
|
|
|
with gr.Row(): |
|
with gr.Column(): |
|
with gr.Group(elem_classes="badge-section"): |
|
gr.HTML("<h3 style='margin-top:0;margin-bottom:16px;font-size:1.3rem;'>✏️ Badge Settings</h3>") |
|
label = gr.Textbox(label="Label", value="Discord", lines=1, elem_id="label-input") |
|
message = gr.Textbox(label="Message", value="Join our community", lines=1, elem_id="message-input") |
|
logo = gr.Textbox(label="Logo", value="discord", lines=1, elem_id="logo-input") |
|
style = gr.Dropdown(label="Style", |
|
choices=["flat","flat-square","plastic","for-the-badge","social"], |
|
value="for-the-badge", elem_id="style-input") |
|
color = gr.ColorPicker(label="Background Color", value="#5865F2", elem_id="color-input") |
|
label_color = gr.ColorPicker(label="Label Background Color",value="#99AAFF", elem_id="label-color-input") |
|
logo_color = gr.ColorPicker(label="Logo Color", value="#ffffff", elem_id="logo-color-input") |
|
link = gr.Textbox(label="Link (URL)", value="https://discord.gg/openfreeai", lines=1, elem_id="link-input") |
|
|
|
|
|
with gr.Column(): |
|
with gr.Group(elem_classes="badge-section"): |
|
gr.HTML("<h3 style='margin-top:0;margin-bottom:16px;font-size:1.3rem;'>👁️ Preview</h3>") |
|
out_preview = gr.HTML() |
|
with gr.Group(elem_classes="badge-section"): |
|
gr.HTML("<h3 style='margin-top:0;margin-bottom:16px;font-size:1.3rem;'>💻 HTML Code</h3>") |
|
out_code = gr.Code(language="html", lines=3) |
|
|
|
|
|
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"], |
|
] |
|
|
|
|
|
html_items = '<div class="example-grid">' |
|
for idx, ex in enumerate(examples): |
|
badge_url = "https://img.shields.io/static/v1?" + "&".join([ |
|
f"label={urllib.parse.quote(ex[0], safe='')}", |
|
f"message={urllib.parse.quote(ex[1], safe='')}", |
|
f"color={urllib.parse.quote(ex[2].lstrip('#'), safe='')}", |
|
f"labelColor={urllib.parse.quote(ex[3].lstrip('#'), safe='')}", |
|
f"logo={urllib.parse.quote(ex[4], safe='')}", |
|
f"logoColor={urllib.parse.quote(ex[5], safe='')}", |
|
f"style={urllib.parse.quote(ex[6], safe='')}", |
|
]) |
|
html_items += f''' |
|
<div class="example-item" onclick="applyExample({idx})"> |
|
<img src="{badge_url}" alt="{ex[0]} badge" style="margin-bottom:8px;"> |
|
<div style="font-size:.9rem;color:#457b9d;">{ex[0]}</div> |
|
</div> |
|
''' |
|
html_items += '</div>' |
|
|
|
|
|
hack_js = """ |
|
function updateReactValue(el,val){ |
|
if(!el)return; |
|
let proto=window.HTMLInputElement.prototype; |
|
if(el.tagName==="TEXTAREA")proto=window.HTMLTextAreaElement.prototype; |
|
const desc=Object.getOwnPropertyDescriptor(proto,"value"); |
|
desc.set.call(el,val); |
|
el.dispatchEvent(new Event("input",{bubbles:true})); |
|
el.dispatchEvent(new Event("change",{bubbles:true})); |
|
} |
|
function toHexIfNeeded(c){ |
|
if(!c)return"#000000"; |
|
if(c.toLowerCase()==="white")return"#ffffff"; |
|
if(c.toLowerCase()==="black")return"#000000"; |
|
return c; |
|
} |
|
function setColor(elemId,val){ |
|
const wrap=document.getElementById(elemId); |
|
if(!wrap)return; |
|
let input=wrap.querySelector("input[type='color']"); |
|
if(!input)input=wrap.querySelector("input[type='text']"); |
|
if(input)updateReactValue(input,val); |
|
} |
|
""" |
|
|
|
apply_example_js = ( |
|
"<script>\n" + hack_js + |
|
"const examples=" + json.dumps(examples) + ";\n" |
|
"function applyExample(i){\n" |
|
" const ex=examples[i];\n" |
|
" updateReactValue(document.querySelector('#label-input textarea,#label-input input'),ex[0]);\n" |
|
" updateReactValue(document.querySelector('#message-input textarea,#message-input input'),ex[1]);\n" |
|
" setColor('color-input',toHexIfNeeded(ex[2]));\n" |
|
" setColor('label-color-input',toHexIfNeeded(ex[3]));\n" |
|
" updateReactValue(document.querySelector('#logo-input textarea,#logo-input input'),ex[4]);\n" |
|
" setColor('logo-color-input',toHexIfNeeded(ex[5]));\n" |
|
" const sel=document.querySelector('#style-input select');\n" |
|
" if(sel){sel.value=ex[6];sel.dispatchEvent(new Event('change',{bubbles:true}));}\n" |
|
" updateReactValue(document.querySelector('#link-input textarea,#link-input input'),ex[7]);\n" |
|
"}\n</script>" |
|
) |
|
gr.HTML(html_items + apply_example_js) |
|
|
|
|
|
demo.load( |
|
fn=generate_static_badge, |
|
inputs=[label, message, color, label_color, logo, logo_color, style, link], |
|
outputs=[out_code, out_preview] |
|
) |
|
|
|
|
|
for comp in [label, message, logo, style, link]: |
|
comp.change( |
|
fn=generate_static_badge, |
|
inputs=[label, message, color, label_color, logo, logo_color, style, link], |
|
outputs=[out_code, out_preview] |
|
) |
|
|
|
for comp in [color, label_color, logo_color]: |
|
comp.input( |
|
fn=generate_static_badge, |
|
inputs=[label, message, color, label_color, logo, logo_color, style, link], |
|
outputs=[out_code, out_preview] |
|
) |
|
|
|
|
|
with gr.TabItem("Help"): |
|
gr.HTML(''' |
|
<div style="padding:20px;background:rgba(255,255,255,.7); |
|
border-radius:16px;box-shadow:0 4px 12px rgba(0,0,0,.03);"> |
|
<h3 style="color:#5e60ce;margin-top:0;">📋 How to Use BadgeCraft</h3> |
|
<h4 style="color:#457b9d;margin-bottom:8px;">✨ What are Badges?</h4> |
|
<p>Badges are small visual indicators that can be used in README files, websites, and documentation. Shields.io badges display project status, social links, version info, and more.</p> |
|
<h4 style="color:#457b9d;margin-bottom:8px;">🛠️ Basic Settings</h4> |
|
<ul> |
|
<li><strong>Label</strong>: Text on the left side (e.g., "Discord")</li> |
|
<li><strong>Message</strong>: Text on the right side</li> |
|
<li><strong>Logo</strong>: A Simple‑Icons name (<a href="https://simpleicons.org" target="_blank">List</a>)</li> |
|
<li><strong>Style</strong>: Badge shape (flat, plastic, for‑the‑badge …)</li> |
|
</ul> |
|
<h4 style="color:#457b9d;margin-bottom:8px;">🎨 Color Settings</h4> |
|
<ul> |
|
<li><strong>Background Color</strong>: right‑side color</li> |
|
<li><strong>Label Background Color</strong>: left‑side color</li> |
|
<li><strong>Logo Color</strong>: logo tint (white / black / #HEX)</li> |
|
</ul> |
|
<h4 style="color:#457b9d;margin-bottom:8px;">🔗 Using the HTML</h4> |
|
<p>Copy the generated HTML and paste it into your site, blog, or GitHub README.<br> |
|
GitHub also accepts raw Markdown: <code></code></p> |
|
<h4 style="color:#457b9d;margin-bottom:8px;">💡 Tips</h4> |
|
<ul> |
|
<li>Click any example tile to auto‑fill all fields</li> |
|
<li>Preview refreshes live as you type or pick colors</li> |
|
<li>Over 2 000 Simple‑Icons logos are supported</li> |
|
</ul> |
|
</div> |
|
''') |
|
|
|
|
|
gr.HTML(''' |
|
<div class="footer"> |
|
<p>© 2023‑2025 BadgeCraft | MIT License | |
|
<a href="https://discord.gg/openfreeai" target="_blank" style="color:#5e60ce;">Discord</a> |
|
</p> |
|
</div> |
|
''') |
|
|
|
|
|
|
|
|
|
if __name__ == "__main__": |
|
demo.launch() |
|
|