openfree commited on
Commit
74b1c72
·
verified ·
1 Parent(s): 71f31bd

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +161 -355
app.py CHANGED
@@ -4,379 +4,185 @@ import gradio as gr
4
  # ---------------------------
5
  # Badge URL generation
6
  # ---------------------------
7
- def generate_static_badge(label, message, color, label_color, logo, logo_color, style, link):
 
8
  base = "https://img.shields.io/static/v1"
9
  params = []
10
- if label:
11
- params.append(f"label={urllib.parse.quote(label, safe='')}")
12
- if message:
13
- params.append(f"message={urllib.parse.quote(message, safe='')}")
14
- if color:
15
- params.append(f"color={urllib.parse.quote(color, safe='')}")
16
- if label_color:
17
- params.append(f"labelColor={urllib.parse.quote(label_color, safe='')}")
18
- if logo:
19
- params.append(f"logo={urllib.parse.quote(logo, safe='')}")
20
- if logo_color:
21
- params.append(f"logoColor={urllib.parse.quote(logo_color, safe='')}")
22
- if style:
23
- params.append(f"style={urllib.parse.quote(style, safe='')}")
24
-
25
- badge_url = base + ("?" + "&".join(params) if params else "")
26
  if link:
27
- html_code = f'<a href="{link}" target="_blank"><img src="{badge_url}" alt="badge"></a>'
28
- else:
29
- html_code = f'<img src="{badge_url}" alt="badge">'
 
 
 
30
 
31
- badge_preview = f"""
32
- <div style='padding:30px; background: linear-gradient(135deg, #f8f9fa, #e9ecef);
33
- border-radius: 16px; display: flex; justify-content: center;
34
- box-shadow: 0 6px 12px rgba(0,0,0,0.05);'>
35
- {html_code}
36
- </div>
37
- """
38
- return html_code, badge_preview
39
 
40
  # ---------------------------
41
  # Gradio UI
42
  # ---------------------------
43
  with gr.Blocks(theme=gr.themes.Soft()) as demo:
44
- # Custom CSS
45
  gr.HTML("""
46
  <style>
47
- body {
48
- background: linear-gradient(120deg, #f8f9fa, #e2eafc);
49
- font-family: 'Poppins', 'Noto Sans KR', sans-serif;
50
- }
51
- .gradio-container {
52
- background: rgba(255, 255, 255, 0.85);
53
- backdrop-filter: blur(10px);
54
- border-radius: 20px;
55
- padding: 24px;
56
- box-shadow: 0 10px 30px rgba(0,0,0,0.08);
57
- max-width: 1000px;
58
- margin: 0 auto;
59
- }
60
- .gr-button {
61
- background: linear-gradient(135deg, #a8dadc, #88c1e9) !important;
62
- color: #1d3557 !important;
63
- border: none !important;
64
- border-radius: 10px !important;
65
- font-weight: 600 !important;
66
- transition: all 0.3s ease !important;
67
- box-shadow: 0 4px 10px rgba(138, 198, 209, 0.3) !important;
68
- }
69
- .gr-button:hover {
70
- transform: translateY(-2px) !important;
71
- box-shadow: 0 6px 15px rgba(138, 198, 209, 0.4) !important;
72
- }
73
- .gr-textbox, .gr-select, .gr-color {
74
- background: #e8f6f3 !important;
75
- border: 2px solid #d9f0ea !important;
76
- border-radius: 12px !important;
77
- transition: all 0.3s ease !important;
78
- }
79
- .gr-textbox:focus, .gr-select:focus, .gr-color:focus {
80
- border-color: #a8dadc !important;
81
- box-shadow: 0 0 0 3px rgba(168, 218, 220, 0.25) !important;
82
- }
83
- label.block span {
84
- color: #457b9d !important;
85
- font-weight: 600 !important;
86
- font-size: 1rem !important;
87
- }
88
- h1 {
89
- color: #5e60ce;
90
- font-weight: 800;
91
- letter-spacing: -0.5px;
92
- }
93
- h3 {
94
- color: #5e60ce;
95
- font-weight: 600;
96
- }
97
- .footer {
98
- margin-top: 30px;
99
- text-align: center;
100
- font-size: 0.9rem;
101
- color: #6d6875;
102
- }
103
- .badge-section {
104
- background: rgba(255, 255, 255, 0.7);
105
- border-radius: 16px;
106
- padding: 20px;
107
- box-shadow: 0 4px 12px rgba(0,0,0,0.03);
108
- margin-bottom: 24px;
109
- border: 1px solid rgba(230, 240, 255, 0.7);
110
- }
111
- .example-grid {
112
- display: grid;
113
- grid-template-columns: repeat(4, 1fr);
114
- grid-template-rows: repeat(2, auto);
115
- gap: 16px;
116
- margin-top: 20px;
117
- }
118
- .example-item {
119
- background: linear-gradient(135deg, #f1f8ff, #e8f4ff);
120
- border-radius: 12px;
121
- padding: 16px;
122
- text-align: center;
123
- cursor: pointer;
124
- transition: all 0.3s ease;
125
- border: 2px solid transparent;
126
- }
127
- .example-item:hover {
128
- transform: translateY(-3px);
129
- box-shadow: 0 8px 15px rgba(0,0,0,0.05);
130
- border-color: #a8dadc;
131
- }
132
- @media (max-width: 768px) {
133
- .example-grid {
134
- grid-template-columns: repeat(2, 1fr);
135
- }
136
- }
137
- @media (max-width: 600px) {
138
- .example-grid {
139
- grid-template-columns: 1fr;
140
- }
141
- }
142
  </style>
143
- """)
144
-
145
- # Header section
146
- gr.HTML("""
147
- <div style="text-align:center; margin-bottom:24px;">
148
- <h1 style="font-size:2.8rem; margin-bottom:0.2em; background: linear-gradient(90deg, #5e60ce, #64dfdf); -webkit-background-clip: text; -webkit-text-fill-color: transparent;">
149
- 🎨 BadgeCraft
150
- </h1>
151
- <p style="font-size:1.2rem; margin:0.5em 0; color:#457b9d; max-width:700px; margin:0 auto;">
152
- Create beautiful badges with live preview and HTML snippet
153
- </p>
154
- <div style="margin-top:10px; display:flex; justify-content:center; gap:12px; flex-wrap:wrap;">
155
- <span style="display:inline-block; background:#e9f5db; color:#588157; padding:6px 12px; border-radius:30px; font-size:0.9rem;">
156
- <strong>✨ MIT License</strong>
157
- </span>
158
- <span style="display:inline-block; background:#d8f3dc; color:#2d6a4f; padding:6px 12px; border-radius:30px; font-size:0.9rem;">
159
- <strong>👥 Created by OpenFreeAI Team</strong>
160
- </span>
161
- </div>
162
  </div>
163
  """)
164
 
165
- with gr.Tabs():
166
- with gr.TabItem("Badge Generator"):
167
- with gr.Row():
168
- with gr.Column():
169
- with gr.Group(elem_classes="badge-section"):
170
- gr.HTML("<h3 style='margin-top:0; margin-bottom:16px; font-size:1.3rem;'>✏️ Badge Settings</h3>")
171
- label = gr.Textbox(label="Label", value="Discord", elem_id="label-input", lines=1)
172
- message = gr.Textbox(label="Message", value="Join our community", elem_id="message-input", lines=1)
173
- logo = gr.Textbox(label="Logo", value="discord", elem_id="logo-input", lines=1)
174
- style = gr.Dropdown(
175
- label="Style",
176
- choices=["flat", "flat-square", "plastic", "for-the-badge", "social"],
177
- value="for-the-badge",
178
- elem_id="style-input"
179
- )
180
- color = gr.ColorPicker(label="Background Color", value="#5865F2", elem_id="color-input")
181
- label_color = gr.ColorPicker(label="Label Background Color", value="#99AAFF", elem_id="label-color-input")
182
- logo_color = gr.ColorPicker(label="Logo Color", value="#ffffff", elem_id="logo-color-input")
183
- link = gr.Textbox(label="Link (URL)", value="https://discord.gg/openfreeai", elem_id="link-input", lines=1)
184
- with gr.Column():
185
- with gr.Group(elem_classes="badge-section"):
186
- gr.HTML("<h3 style='margin-top:0; margin-bottom:16px; font-size:1.3rem;'>👁️ Preview</h3>")
187
- out_preview = gr.HTML(label="")
188
- with gr.Group(elem_classes="badge-section"):
189
- gr.HTML("<h3 style='margin-top:0; margin-bottom:16px; font-size:1.3rem;'>💻 HTML Code</h3>")
190
- out_code = gr.Code(label="", language="html", lines=3)
191
-
192
- # 예제 리스트 (label, message, bg_color, label_color, logo, logo_color, style, link)
193
- examples = [
194
- ["Discord", "Openfree AI", "#5865F2", "#99AAFF", "discord", "white", "for-the-badge", "https://discord.gg/openfreeai"],
195
- ["X.com", "Follow us", "#1DA1F2", "#00CFFF", "x", "white", "for-the-badge", "https://x.com/openfree_ai"],
196
- ["Collections","Explore", "#FFB300", "#FFF176", "huggingface","black","for-the-badge", "https://huggingface.co/collections/VIDraft/best-open-ai-services-68057e6e312880ea92abaf4c"],
197
- ["GitHub", "Star us", "#0A0A0A", "#39FF14", "github", "white", "for-the-badge", "https://github.com/openfreeai"],
198
- ["YouTube", "Watch now", "#E50000", "#FF5E5E", "youtube", "white", "for-the-badge", "https://www.youtube.com/@AITechTree"],
199
- ["Facebook", "Like us", "#1877F2", "#6FAFFF", "facebook", "white", "for-the-badge", "https://www.facebook.com/profile.php?id=61575353674679"],
200
- ["Instagram", "友情 萬世", "#E4405F", "#FF77A9", "instagram", "white", "for-the-badge", "https://www.instagram.com/openfree_ai/"],
201
- ["Threads", "함께 즐겨요.", "#000000", "#FF00FF", "threads", "white", "for-the-badge", "https://www.threads.net/@openfree_ai"],
202
- ]
203
-
204
- # 예제 미리보기
205
- html_items = '<div class="example-grid">'
206
- for idx, ex in enumerate(examples):
207
- # ex = [label, message, bg_color, label_color, logo, logo_color, style, link]
208
- badge_url = (
209
- "https://img.shields.io/static/v1?" + "&".join([
210
- f"label={urllib.parse.quote(ex[0], safe='')}",
211
- f"message={urllib.parse.quote(ex[1], safe='')}",
212
- f"color={urllib.parse.quote(ex[2], safe='')}",
213
- f"labelColor={urllib.parse.quote(ex[3], safe='')}",
214
- f"logo={urllib.parse.quote(ex[4], safe='')}",
215
- f"logoColor={urllib.parse.quote(ex[5], safe='')}",
216
- f"style={urllib.parse.quote(ex[6], safe='')}"
217
- ])
218
- )
219
- html_items += f'''
220
- <div class="example-item" onclick="applyExample({idx})">
221
- <img src="{badge_url}" alt="{ex[0]} badge" style="margin-bottom:8px;">
222
- <div style="font-size:0.9rem; color:#457b9d;">{ex[0]}</div>
223
- </div>
224
- '''
225
- html_items += '</div>'
226
-
227
- # React Controlled Input Hack 함수
228
- # - el: input 또는 textarea DOM
229
- # - newVal: 넣을 문자열(혹은 color)
230
- # - tagName에 따라 value setter를 찾아서 적용
231
- hack_js = """
232
- function updateReactValue(el, newVal) {
233
- if (!el) return;
234
- let prototype = window.HTMLInputElement.prototype;
235
- if (el.tagName === "TEXTAREA") {
236
- prototype = window.HTMLTextAreaElement.prototype;
237
- }
238
- const descriptor = Object.getOwnPropertyDescriptor(prototype, "value");
239
- descriptor.set.call(el, newVal);
240
-
241
- // React가 인식하도록 input & change 이벤트 둘 다 디스패치
242
- el.dispatchEvent(new Event("input", { bubbles: true }));
243
- el.dispatchEvent(new Event("change", { bubbles: true }));
244
- }
245
- function convertToHexIfNeeded(colorStr) {
246
- if (!colorStr) return "#000000";
247
- // white/black 문자열이면 #hex로 치환
248
- if (colorStr.toLowerCase() === "white") return "#ffffff";
249
- if (colorStr.toLowerCase() === "black") return "#000000";
250
- return colorStr;
251
- }
252
- function setColorValue(elemId, colorValue) {
253
- const container = document.getElementById(elemId);
254
- if (!container) return;
255
-
256
- // 우선 input[type='color'] 찾기
257
- let colorInput = container.querySelector("input[type='color']");
258
- // 없으면 input[type='text']
259
- if (!colorInput) {
260
- colorInput = container.querySelector("input[type='text']");
261
- }
262
- if (!colorInput) return;
263
-
264
- // React Controlled Hack
265
- updateReactValue(colorInput, colorValue);
266
- }
267
- """
268
-
269
- # 예제를 클릭했을 때 적용하는 main 함수
270
- apply_example_js = f"""
271
- <script>
272
- {hack_js}
273
- const examples = {examples};
274
-
275
- function applyExample(i) {{
276
- const ex = examples[i];
277
- // ex = [label, message, bg_color, label_bg, logo, logo_color, style, link]
278
-
279
- // Label
280
- const labelBox = document.querySelector("#label-input textarea, #label-input input");
281
- if (labelBox) {{
282
- updateReactValue(labelBox, ex[0]);
283
- }}
284
-
285
- // Message
286
- const msgBox = document.querySelector("#message-input textarea, #message-input input");
287
- if (msgBox) {{
288
- updateReactValue(msgBox, ex[1]);
289
- }}
290
-
291
- // Background Color
292
- const newBg = convertToHexIfNeeded(ex[2]);
293
- setColorValue("color-input", newBg);
294
-
295
- // Label Background Color
296
- const newLabel = convertToHexIfNeeded(ex[3]);
297
- setColorValue("label-color-input", newLabel);
298
-
299
- // Logo
300
- const logoBox = document.querySelector("#logo-input textarea, #logo-input input");
301
- if (logoBox) {{
302
- updateReactValue(logoBox, ex[4]);
303
- }}
304
-
305
- // Logo Color
306
- const newLogoColor = convertToHexIfNeeded(ex[5]);
307
- setColorValue("logo-color-input", newLogoColor);
308
-
309
- // Style (Dropdown)
310
- const styleSelect = document.querySelector("#style-input select");
311
- if (styleSelect) {{
312
- styleSelect.value = ex[6];
313
- styleSelect.dispatchEvent(new Event("change", {{ bubbles: true }}));
314
- }}
315
-
316
- // Link
317
- const linkBox = document.querySelector("#link-input textarea, #link-input input");
318
- if (linkBox) {{
319
- updateReactValue(linkBox, ex[7]);
320
- }}
321
- }}
322
- </script>
323
- """
324
-
325
- gr.HTML(html_items + apply_example_js)
326
 
327
- # 초기/실시간 업데이트
328
- demo.load(
329
- fn=generate_static_badge,
330
- inputs=[label, message, color, label_color, logo, logo_color, style, link],
331
- outputs=[out_code, out_preview]
332
- )
333
- for inp in [label, message, color, label_color, logo, logo_color, style, link]:
334
- inp.change(
335
- fn=generate_static_badge,
336
- inputs=[label, message, color, label_color, logo, logo_color, style, link],
337
- outputs=[out_code, out_preview]
338
- )
339
 
340
- with gr.TabItem("Help"):
341
- gr.HTML('''
342
- <div style="padding: 20px; background: rgba(255, 255, 255, 0.7); border-radius: 16px; box-shadow: 0 4px 12px rgba(0,0,0,0.03);">
343
- <h3 style="color: #5e60ce; margin-top:0;">📋 How to Use BadgeCraft</h3>
344
- <h4 style="color: #457b9d; margin-bottom: 8px;">✨ What are Badges?</h4>
345
- <p>Badges are small visual indicators that can be used in README files, websites, and documentation. Shields.io badges are widely used to display project status, social media links, version information, and more.</p>
346
- <h4 style="color: #457b9d; margin-bottom: 8px;">🛠️ Basic Settings</h4>
347
- <ul>
348
- <li><strong>Label</strong>: Text displayed on the left side of the badge (e.g., "Discord", "Version", "Status")</li>
349
- <li><strong>Message</strong>: Text displayed on the right side of the badge</li>
350
- <li><strong>Logo</strong>: Name of a logo provided by Simple Icons (<a href="https://simpleicons.org/" target="_blank">View List</a>)</li>
351
- <li><strong>Style</strong>: Determines the shape of the badge (flat, plastic, for-the-badge, etc.)</li>
352
- </ul>
353
- <h4 style="color: #457b9d; margin-bottom: 8px;">🎨 ColorSettings</h4>
354
- <ul>
355
- <li><strong>Background Color</strong>: Background color for the right side of the badge</li>
356
- <li><strong>Label Background Color</strong>: Background color for the left side of the badge</li>
357
- <li><strong>Logo Color</strong>: Color of the logo (e.g. white or black)</li>
358
- </ul>
359
- <h4 style="color: #457b9d; margin-bottom: 8px;">🔗 Using the HTML</h4>
360
- <p>Copy the generated HTML code and paste it into your website, blog, GitHub README, etc.</p>
361
- <p>HTML works in GitHub READMEs, but if you prefer markdown, use <code>![alt text](badge URL)</code>.</p>
362
- <h4 style="color: #457b9d; margin-bottom: 8px;">💡 Tips</h4>
363
- <ul>
364
- <li>Click on any example in the grid to automatically fill in all settings</li>
365
- <li>The preview updates in real-time as you make changes</li>
366
- <li>You can use over 2000+ logos from Simple Icons - just enter the name</li>
367
- <li>Custom colors can be selected with the color picker or by entering a hex code</li>
368
- </ul>
369
- </div>
370
- ''')
371
 
372
- # Footer
373
- gr.HTML('''
374
- <div class="footer">
375
- <p>© 2023-2025 BadgeCraft | MIT License
376
- <a href="https://discord.gg/openfreeai" target="_blank" style="color:#5e60ce;">Discord</a>
377
- </p>
378
- </div>
379
- ''')
380
 
381
  if __name__ == "__main__":
382
  demo.launch()
 
4
  # ---------------------------
5
  # Badge URL generation
6
  # ---------------------------
7
+ def generate_static_badge(label, message, color, label_color,
8
+ logo, logo_color, style, link):
9
  base = "https://img.shields.io/static/v1"
10
  params = []
11
+ if label: params.append(f"label={urllib.parse.quote(label, safe='')}")
12
+ if message: params.append(f"message={urllib.parse.quote(message, safe='')}")
13
+ if color: params.append(f"color={urllib.parse.quote(color, safe='')}")
14
+ if label_color: params.append(f"labelColor={urllib.parse.quote(label_color, safe='')}")
15
+ if logo: params.append(f"logo={urllib.parse.quote(logo, safe='')}")
16
+ if logo_color: params.append(f"logoColor={urllib.parse.quote(logo_color, safe='')}")
17
+ if style: params.append(f"style={urllib.parse.quote(style, safe='')}")
18
+
19
+ badge_url = base + "?" + "&".join(params)
20
+ img = f'<img src="{badge_url}" alt="badge">'
 
 
 
 
 
 
21
  if link:
22
+ img = f'<a href="{link}" target="_blank">{img}</a>'
23
+
24
+ preview = (f"<div style='padding:30px;background:linear-gradient(135deg,#f8f9fa,#e9ecef);"
25
+ f"border-radius:16px;display:flex;justify-content:center;box-shadow:0 6px 12px"
26
+ f" rgba(0,0,0,0.05);'>{img}</div>")
27
+ return img, preview
28
 
 
 
 
 
 
 
 
 
29
 
30
  # ---------------------------
31
  # Gradio UI
32
  # ---------------------------
33
  with gr.Blocks(theme=gr.themes.Soft()) as demo:
34
+ # ------------------ CSS & Header ------------------
35
  gr.HTML("""
36
  <style>
37
+ body{background:linear-gradient(120deg,#f8f9fa,#e2eafc);font-family:'Poppins','Noto Sans KR',sans-serif;}
38
+ .gradio-container{background:rgba(255,255,255,0.85);backdrop-filter:blur(10px);
39
+ border-radius:20px;padding:24px;box-shadow:0 10px 30px rgba(0,0,0,0.08);max-width:1000px;margin:0 auto;}
40
+ .gr-button{background:linear-gradient(135deg,#a8dadc,#88c1e9)!important;color:#1d3557!important;
41
+ border:none!important;border-radius:10px!important;font-weight:600!important;transition:all .3s;
42
+ box-shadow:0 4px 10px rgba(138,198,209,.3)!important;}
43
+ .gr-button:hover{transform:translateY(-2px)!important;box-shadow:0 6px 15px rgba(138,198,209,.4)!important;}
44
+ .gr-textbox,.gr-select,.gr-color{background:#e8f6f3!important;border:2px solid #d9f0ea!important;
45
+ border-radius:12px!important;transition:all .3s;}
46
+ .gr-textbox:focus,.gr-select:focus,.gr-color:focus{
47
+ border-color:#a8dadc!important;box-shadow:0 0 0 3px rgba(168,218,220,.25)!important;}
48
+ .example-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:16px;margin-top:20px;}
49
+ .example-item{background:linear-gradient(135deg,#f1f8ff,#e8f4ff);border-radius:12px;padding:16px;
50
+ text-align:center;cursor:pointer;transition:all .3s;border:2px solid transparent;}
51
+ .example-item:hover{transform:translateY(-3px);box-shadow:0 8px 15px rgba(0,0,0,0.05);border-color:#a8dadc;}
52
+ @media(max-width:768px){.example-grid{grid-template-columns:repeat(2,1fr);} }
53
+ @media(max-width:600px){.example-grid{grid-template-columns:1fr;} }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
54
  </style>
55
+ <div style="text-align:center;margin-bottom:24px;">
56
+ <h1 style="font-size:2.8rem;margin-bottom:.2em;background:linear-gradient(90deg,#5e60ce,#64dfdf);
57
+ -webkit-background-clip:text;-webkit-text-fill-color:transparent;">🎨 BadgeCraft</h1>
58
+ <p style="font-size:1.2rem;color:#457b9d;">Create beautiful badges with live preview and HTML snippet</p>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
59
  </div>
60
  """)
61
 
62
+ # ------------------ Components ------------------
63
+ label = gr.Textbox("Discord", label="Label", elem_id="label-input")
64
+ message = gr.Textbox("Join our community", label="Message", elem_id="message-input")
65
+ color = gr.ColorPicker("#5865F2", label="Background Color", elem_id="color-input")
66
+ label_color = gr.ColorPicker("#99AAFF", label="Label Background Color", elem_id="label-color-input")
67
+ logo = gr.Textbox("discord", label="Logo", elem_id="logo-input")
68
+ logo_color = gr.ColorPicker("#ffffff", label="Logo Color", elem_id="logo-color-input")
69
+ style = gr.Dropdown(["flat","flat-square","plastic","for-the-badge","social"],
70
+ value="for-the-badge", label="Style", elem_id="style-input")
71
+ link = gr.Textbox("https://discord.gg/openfreeai", label="Link (URL)", elem_id="link-input")
72
+
73
+ out_code = gr.Code(language="html", lines=3, label="HTML Snippet")
74
+ out_prev = gr.HTML()
75
+
76
+ # ------------------ Example badges ------------------
77
+ examples = [
78
+ ["Discord","Openfree AI","#5865F2","#99AAFF","discord","white","for-the-badge",
79
+ "https://discord.gg/openfreeai"],
80
+ ["X.com","Follow us","#1DA1F2","#00CFFF","x","white","for-the-badge",
81
+ "https://x.com/openfree_ai"],
82
+ ["Collections","Explore","#FFB300","#FFF176","huggingface","black","for-the-badge",
83
+ "https://huggingface.co/collections/VIDraft/best-open-ai-services-68057e6e312880ea92abaf4c"],
84
+ ["GitHub","Star us","#0A0A0A","#39FF14","github","white","for-the-badge",
85
+ "https://github.com/openfreeai"],
86
+ ["YouTube","Watch now","#E50000","#FF5E5E","youtube","white","for-the-badge",
87
+ "https://www.youtube.com/@AITechTree"],
88
+ ["Facebook","Like us","#1877F2","#6FAFFF","facebook","white","for-the-badge",
89
+ "https://www.facebook.com/profile.php?id=61575353674679"],
90
+ ["Instagram","友情 萬世","#E4405F","#FF77A9","instagram","white","for-the-badge",
91
+ "https://www.instagram.com/openfree_ai/"],
92
+ ["Threads","함께 즐겨요.","#000000","#FF00FF","threads","white","for-the-badge",
93
+ "https://www.threads.net/@openfree_ai"]
94
+ ]
95
+
96
+ # grid html
97
+ grid = '<div class="example-grid">'
98
+ for i,e in enumerate(examples):
99
+ lbl,msg,bg,lbg,logo_e,logoc,sty,_ = e
100
+ def fix(c):
101
+ return "#ffffff" if c=="white" else "#000000" if c=="black" else c
102
+ url = ("https://img.shields.io/static/v1?"
103
+ f"label={urllib.parse.quote(lbl)}&message={urllib.parse.quote(msg)}&"
104
+ f"color={urllib.parse.quote(fix(bg))}&labelColor={urllib.parse.quote(fix(lbg))}&"
105
+ f"logo={urllib.parse.quote(logo_e)}&logoColor={urllib.parse.quote(fix(logoc))}&"
106
+ f"style={urllib.parse.quote(sty)}")
107
+ grid += (f'<div class="example-item" onclick="applyExample({i})">'
108
+ f'<img src="{url}" style="margin-bottom:8px;"><br>'
109
+ f'<span style="font-size:.9rem;color:#457b9d;">{lbl}</span></div>')
110
+ grid += "</div>"
111
+
112
+ # ------------------ JS (fixed color propagation) ------------------
113
+ js = f"""
114
+ <script>
115
+ const examples = {examples};
116
+
117
+ /* -------- helpers -------- */
118
+ function reactSet(el,val){{
119
+ if(!el) return;
120
+ const proto = (el.tagName==="TEXTAREA")
121
+ ? HTMLTextAreaElement.prototype
122
+ : HTMLInputElement.prototype;
123
+ const set = Object.getOwnPropertyDescriptor(proto,"value").set;
124
+ set.call(el,val);
125
+ el.dispatchEvent(new Event("input",{{bubbles:true}}));
126
+ el.dispatchEvent(new Event("change",{{bubbles:true}})); // ColorPicker는 change에 반응
127
+ el.dispatchEvent(new Event("blur", {{bubbles:true}})); // 최종 확정
128
+ }}
129
+ function toHex(c){{
130
+ if(!c) return "#000000";
131
+ c = c.toLowerCase();
132
+ if(c==="white") return "#ffffff";
133
+ if(c==="black") return "#000000";
134
+ return c;
135
+ }}
136
+ function setColor(elemId,val){{
137
+ const root = document.getElementById(elemId);
138
+ if(!root) return;
139
+ // gr.ColorPicker 내부 input type=text 이므로 먼저 찾음
140
+ let inp = root.querySelector("input[type='text'],input[type='color']");
141
+ if(!inp) return;
142
+ reactSet(inp,val);
143
+ // gradio 4.17 이후 : data-testid=color-picker 값 동기화 필요
144
+ const pickerDiv = root.querySelector("[data-testid='color-picker']")
145
+ if(pickerDiv) pickerDiv.style.background = val; // 버튼 시각적 즉시 변경
146
+ }}
147
+
148
+ /* -------- main: example click -------- */
149
+ window.applyExample = (i) => {{
150
+ const ex = examples[i];
151
+ // 0,label
152
+ reactSet(document.querySelector("#label-input input,#label-input textarea"), ex[0]);
153
+ // 1,message
154
+ reactSet(document.querySelector("#message-input input,#message-input textarea"), ex[1]);
155
+ // 2,bg color
156
+ setColor("color-input", toHex(ex[2]));
157
+ // 3,label bg
158
+ setColor("label-color-input", toHex(ex[3]));
159
+ // 4,logo
160
+ reactSet(document.querySelector("#logo-input input,#logo-input textarea"), ex[4]);
161
+ // 5,logo color
162
+ setColor("logo-color-input", toHex(ex[5]));
163
+ // 6,style
164
+ const sel = document.querySelector("#style-input select");
165
+ if(sel){{
166
+ sel.value = ex[6];
167
+ sel.dispatchEvent(new Event("change",{{bubbles:true}}));
168
+ }}
169
+ // 7,link
170
+ reactSet(document.querySelector("#link-input input,#link-input textarea"), ex[7]);
171
+ }}
172
+ </script>
173
+ """
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
174
 
175
+ gr.HTML(grid + js)
 
 
 
 
 
 
 
 
 
 
 
176
 
177
+ # ------------------ live update handlers ------------------
178
+ comps = [label,message,color,label_color,logo,logo_color,style,link]
179
+ for c in comps:
180
+ c.change(generate_static_badge, comps, [out_code,out_prev])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
181
 
182
+ # ------------------ footer ------------------
183
+ gr.HTML('<p style="text-align:center;margin-top:30px;font-size:.9rem;color:#6d6875">'
184
+ '© 2023‑2025 BadgeCraft | MIT License &nbsp; '
185
+ '<a href="https://discord.gg/openfreeai" style="color:#5e60ce" target="_blank">Discord</a></p>')
 
 
 
 
186
 
187
  if __name__ == "__main__":
188
  demo.launch()