elismasilva commited on
Commit
278c7cc
·
verified ·
1 Parent(s): 39f83bf

Upload folder using huggingface_hub

Browse files
.gitignore ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .eggs/
2
+ dist/
3
+ .vscode/
4
+ *.pyc
5
+ __pycache__/
6
+ *.py[cod]
7
+ *$py.class
8
+ __tmp/*
9
+ *.pyi
10
+ .mypycache
11
+ .ruff_cache
12
+ node_modules
13
+ backend/**/templates/
14
+ README_TEMPLATE.md
README.md CHANGED
@@ -1,12 +1,781 @@
1
- ---
2
- title: Gradio Creditspanel
3
- emoji: 🏆
4
- colorFrom: purple
5
- colorTo: red
6
- sdk: gradio
7
- sdk_version: 5.45.0
8
- app_file: app.py
9
- pinned: false
10
- ---
11
-
12
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ tags: [gradio-custom-component, HTML]
3
+ title: gradio_creditspanel
4
+ short_description: Credits Panel for Gradio UI
5
+ colorFrom: blue
6
+ colorTo: yellow
7
+ sdk: gradio
8
+ pinned: false
9
+ app_file: space.py
10
+ ---
11
+
12
+ # `gradio_creditspanel`
13
+ <img alt="Static Badge" src="https://img.shields.io/badge/version%20-%200.0.1%20-%20blue"> <a href="https://huggingface.co/spaces/elismasilva/gradio_creditspanel"><img src="https://img.shields.io/badge/%F0%9F%A4%97%20Hugging%20Face-Demo-blue"></a><p><span>💻 <a href='https://github.com/DEVAIEXP/gradio_component_creditspanel'>Component GitHub Code</a></span></p>
14
+
15
+ Credits Panel for Gradio UI
16
+
17
+ ## Installation
18
+
19
+ ```bash
20
+ pip install gradio_creditspanel
21
+ ```
22
+
23
+ ## Usage
24
+
25
+ ```python
26
+ """
27
+ app.py
28
+
29
+ This script serves as an interactive demonstration for the custom Gradio component `CreditsPanel`.
30
+ It showcases all available features of the component, allowing users to dynamically adjust
31
+ properties like animation effects, speed, layout, and styling. The app also demonstrates
32
+ how to handle file dependencies (logo, licenses) in a portable way.
33
+ """
34
+
35
+ import gradio as gr
36
+ from gradio_creditspanel import CreditsPanel
37
+ import os
38
+
39
+ # --- 1. SETUP & DATA PREPARATION ---
40
+ # This section prepares all necessary assets and data for the demo.
41
+ # It ensures the demo runs out-of-the-box without manual setup.
42
+
43
+ def setup_demo_files():
44
+ """
45
+ Creates necessary directories and dummy files (logo, licenses) for the demo.
46
+ This makes the application self-contained and easy to run.
47
+ """
48
+ # Create dummy license files
49
+ os.makedirs("LICENSES", exist_ok=True)
50
+ if not os.path.exists("LICENSES/Apache.txt"):
51
+ with open("LICENSES/Apache.txt", "w") as f:
52
+ f.write("Apache License\nVersion 2.0, January 2004\nhttp://www.apache.org/licenses/...")
53
+ if not os.path.exists("LICENSES/MIT.txt"):
54
+ with open("LICENSES/MIT.txt", "w") as f:
55
+ f.write("MIT License\nCopyright (c) 2025 Author\nPermission is hereby granted...")
56
+
57
+ # Create a placeholder logo if it doesn't exist
58
+ os.makedirs("assets", exist_ok=True)
59
+ if not os.path.exists("./assets/logo.webp"):
60
+ with open("./assets/logo.webp", "w") as f:
61
+ f.write("Placeholder WebP logo")
62
+
63
+ # Initial data for the credits roll
64
+ credits_list = [
65
+ {"title": "Project Manager", "name": "Emma Thompson"},
66
+ {"title": "Lead Developer", "name": "John Doe"},
67
+ {"title": "Senior Backend Engineer", "name": "Michael Chen"},
68
+ {"title": "Frontend Developer", "name": "Sarah Johnson"},
69
+ {"title": "UI/UX Designer", "name": "Jane Smith"},
70
+ {"title": "Database Architect", "name": "Alex Ray"},
71
+ {"title": "DevOps Engineer", "name": "Liam Patel"},
72
+ {"title": "Quality Assurance Lead", "name": "Sam Wilson"},
73
+ {"title": "Test Automation Engineer", "name": "Olivia Brown"},
74
+ {"title": "Security Analyst", "name": "David Kim"},
75
+ {"title": "Data Scientist", "name": "Sophie Martinez"},
76
+ {"title": "Machine Learning Engineer", "name": "Ethan Lee"},
77
+ {"title": "API Developer", "name": "Isabella Garcia"},
78
+ {"title": "Technical Writer", "name": "Noah Davis"},
79
+ {"title": "Scrum Master", "name": "Ava Rodriguez"},
80
+ ]
81
+
82
+ # Paths to license files
83
+ license_paths = {
84
+ "Gradio Framework": "./LICENSES/Apache.txt",
85
+ "This Component": "./LICENSES/MIT.txt"
86
+ }
87
+
88
+ # Default animation speeds for each effect to provide a good user experience
89
+ DEFAULT_SPEEDS = {
90
+ "scroll": 40.0,
91
+ "starwars": 80.0,
92
+ "matrix": 40.0
93
+ }
94
+
95
+ # --- 2. GRADIO EVENT HANDLER FUNCTIONS ---
96
+ # These functions define the application's interactive logic.
97
+
98
+ def update_panel(
99
+ effect: str, speed: float, base_font_size: float,
100
+ intro_title: str, intro_subtitle: str, sidebar_position: str,
101
+ show_logo: bool, show_licenses: bool, logo_position: str,
102
+ logo_sizing: str, logo_width: str | None, logo_height: str | None,
103
+ scroll_background_color: str | None, scroll_title_color: str | None,
104
+ scroll_name_color: str | None
105
+ ) -> dict:
106
+ """
107
+ Callback function that updates all properties of the CreditsPanel component.
108
+ It takes the current state of all UI controls and returns a gr.update() dictionary.
109
+ """
110
+ return gr.update(
111
+ visible=True,
112
+ effect=effect,
113
+ speed=speed,
114
+ base_font_size=base_font_size,
115
+ intro_title=intro_title,
116
+ intro_subtitle=intro_subtitle,
117
+ sidebar_position=sidebar_position,
118
+ show_logo=show_logo,
119
+ show_licenses=show_licenses,
120
+ logo_position=logo_position,
121
+ logo_sizing=logo_sizing,
122
+ logo_width=logo_width,
123
+ logo_height=logo_height,
124
+ scroll_background_color=scroll_background_color,
125
+ scroll_title_color=scroll_title_color,
126
+ scroll_name_color=scroll_name_color,
127
+ value=credits_list # The list of credits to display
128
+ )
129
+
130
+ def update_ui_on_effect_change(effect: str) -> tuple[float, float]:
131
+ """
132
+ Updates the speed and font size sliders to sensible defaults when the
133
+ animation effect is changed.
134
+ """
135
+ font_size = 1.5
136
+ if effect == "starwars":
137
+ font_size = 6.0 # Star Wars effect looks better with a larger font
138
+
139
+ speed = DEFAULT_SPEEDS.get(effect, 40.0)
140
+ return speed, font_size
141
+
142
+ # --- 3. GRADIO UI DEFINITION ---
143
+ # This section constructs the user interface using gr.Blocks.
144
+
145
+ with gr.Blocks(theme=gr.themes.Ocean(), title="CreditsPanel Demo") as demo:
146
+ gr.Markdown(
147
+ """
148
+ # Interactive CreditsPanel Demo
149
+ Use the sidebar controls to customize the `CreditsPanel` component in real-time.
150
+ """
151
+ )
152
+
153
+ with gr.Sidebar(position="right"):
154
+ gr.Markdown("### Effects Settings")
155
+ effect_radio = gr.Radio(
156
+ ["scroll", "starwars", "matrix"], label="Animation Effect", value="scroll",
157
+ info="Select the visual style for the credits."
158
+ )
159
+ speed_slider = gr.Slider(
160
+ minimum=5.0, maximum=100.0, step=1.0, value=DEFAULT_SPEEDS["scroll"],
161
+ label="Animation Speed (seconds)", info="Duration of one animation cycle."
162
+ )
163
+ font_size_slider = gr.Slider(
164
+ minimum=1.0, maximum=10.0, step=0.1, value=1.5,
165
+ label="Base Font Size (rem)", info="Controls the base font size."
166
+ )
167
+
168
+ gr.Markdown("### Intro Text")
169
+ intro_title_input = gr.Textbox(
170
+ label="Intro Title", value="Gradio", info="Main title for the intro sequence."
171
+ )
172
+ intro_subtitle_input = gr.Textbox(
173
+ label="Intro Subtitle", value="The best UI framework", info="Subtitle for the intro sequence."
174
+ )
175
+
176
+ gr.Markdown("### Layout & Visibility")
177
+ sidebar_position_radio = gr.Radio(
178
+ ["right", "bottom"], label="Sidebar Position", value="right",
179
+ info="Place the licenses sidebar on the right or bottom."
180
+ )
181
+ show_logo_checkbox = gr.Checkbox(label="Show Logo", value=True)
182
+ show_licenses_checkbox = gr.Checkbox(label="Show Licenses", value=True)
183
+
184
+ gr.Markdown("### Logo Customization")
185
+ logo_position_radio = gr.Radio(
186
+ ["left", "center", "right"], label="Logo Position", value="center"
187
+ )
188
+ logo_sizing_radio = gr.Radio(
189
+ ["stretch", "crop", "resize"], label="Logo Sizing", value="resize"
190
+ )
191
+ logo_width_input = gr.Textbox(label="Logo Width", value="200px")
192
+ logo_height_input = gr.Textbox(label="Logo Height", value="100px")
193
+
194
+ gr.Markdown("### Color Settings (Scroll Effect)")
195
+ scroll_background_color = gr.ColorPicker(label="Background Color", value="#000000")
196
+ scroll_title_color = gr.ColorPicker(label="Title Color", value="#FFFFFF")
197
+ scroll_name_color = gr.ColorPicker(label="Name Color", value="#FFFFFF")
198
+
199
+ # Instantiate the custom CreditsPanel component with default values
200
+ panel = CreditsPanel(
201
+ credits=credits_list,
202
+ licenses=license_paths,
203
+ effect="scroll",
204
+ height=500,
205
+ speed=DEFAULT_SPEEDS["scroll"],
206
+ base_font_size=1.5,
207
+ intro_title="Gradio",
208
+ intro_subtitle="The best UI framework",
209
+ sidebar_position="right",
210
+ logo_path="./assets/logo.webp",
211
+ show_logo=True,
212
+ show_licenses=True,
213
+ logo_position="center",
214
+ logo_sizing="resize",
215
+ logo_width="200px",
216
+ logo_height="100px",
217
+ scroll_background_color="#000000",
218
+ scroll_title_color="#FFFFFF",
219
+ scroll_name_color="#FFFFFF",
220
+ )
221
+
222
+ # List of all input components that should trigger a panel update
223
+ inputs = [
224
+ effect_radio, speed_slider, font_size_slider,
225
+ intro_title_input, intro_subtitle_input,
226
+ sidebar_position_radio, show_logo_checkbox, show_licenses_checkbox,
227
+ logo_position_radio, logo_sizing_radio, logo_width_input, logo_height_input,
228
+ scroll_background_color, scroll_title_color, scroll_name_color
229
+ ]
230
+
231
+ # --- 4. EVENT BINDING ---
232
+ # Connect the UI controls to the handler functions.
233
+
234
+ # Special event: changing the effect also updates speed and font size sliders
235
+ effect_radio.change(
236
+ fn=update_ui_on_effect_change,
237
+ inputs=effect_radio,
238
+ outputs=[speed_slider, font_size_slider]
239
+ )
240
+
241
+ # General event: any change in an input control updates the main panel
242
+ for input_component in inputs:
243
+ input_component.change(
244
+ fn=update_panel,
245
+ inputs=inputs,
246
+ outputs=panel
247
+ )
248
+
249
+ # --- 5. APP LAUNCH ---
250
+ if __name__ == "__main__":
251
+ setup_demo_files()
252
+ demo.launch()
253
+ ```
254
+
255
+ ## `CreditsPanel`
256
+
257
+ ### Initialization
258
+
259
+ <table>
260
+ <thead>
261
+ <tr>
262
+ <th align="left">name</th>
263
+ <th align="left" style="width: 25%;">type</th>
264
+ <th align="left">default</th>
265
+ <th align="left">description</th>
266
+ </tr>
267
+ </thead>
268
+ <tbody>
269
+ <tr>
270
+ <td align="left"><code>value</code></td>
271
+ <td align="left" style="width: 25%;">
272
+
273
+ ```python
274
+ Any
275
+ ```
276
+
277
+ </td>
278
+ <td align="left"><code>None</code></td>
279
+ <td align="left">None</td>
280
+ </tr>
281
+
282
+ <tr>
283
+ <td align="left"><code>credits</code></td>
284
+ <td align="left" style="width: 25%;">
285
+
286
+ ```python
287
+ typing.Union[
288
+ typing.List[typing.Dict[str, str]],
289
+ typing.Callable,
290
+ NoneType,
291
+ ][
292
+ typing.List[typing.Dict[str, str]][
293
+ typing.Dict[str, str][str, str]
294
+ ],
295
+ Callable,
296
+ None,
297
+ ]
298
+ ```
299
+
300
+ </td>
301
+ <td align="left"><code>None</code></td>
302
+ <td align="left">None</td>
303
+ </tr>
304
+
305
+ <tr>
306
+ <td align="left"><code>height</code></td>
307
+ <td align="left" style="width: 25%;">
308
+
309
+ ```python
310
+ int | str | None
311
+ ```
312
+
313
+ </td>
314
+ <td align="left"><code>None</code></td>
315
+ <td align="left">None</td>
316
+ </tr>
317
+
318
+ <tr>
319
+ <td align="left"><code>width</code></td>
320
+ <td align="left" style="width: 25%;">
321
+
322
+ ```python
323
+ int | str | None
324
+ ```
325
+
326
+ </td>
327
+ <td align="left"><code>None</code></td>
328
+ <td align="left">None</td>
329
+ </tr>
330
+
331
+ <tr>
332
+ <td align="left"><code>licenses</code></td>
333
+ <td align="left" style="width: 25%;">
334
+
335
+ ```python
336
+ typing.Optional[typing.Dict[str, str | pathlib.Path]][
337
+ typing.Dict[str, str | pathlib.Path][
338
+ str, str | pathlib.Path
339
+ ],
340
+ None,
341
+ ]
342
+ ```
343
+
344
+ </td>
345
+ <td align="left"><code>None</code></td>
346
+ <td align="left">None</td>
347
+ </tr>
348
+
349
+ <tr>
350
+ <td align="left"><code>effect</code></td>
351
+ <td align="left" style="width: 25%;">
352
+
353
+ ```python
354
+ "scroll" | "starwars" | "matrix"
355
+ ```
356
+
357
+ </td>
358
+ <td align="left"><code>"scroll"</code></td>
359
+ <td align="left">None</td>
360
+ </tr>
361
+
362
+ <tr>
363
+ <td align="left"><code>speed</code></td>
364
+ <td align="left" style="width: 25%;">
365
+
366
+ ```python
367
+ float
368
+ ```
369
+
370
+ </td>
371
+ <td align="left"><code>40.0</code></td>
372
+ <td align="left">None</td>
373
+ </tr>
374
+
375
+ <tr>
376
+ <td align="left"><code>base_font_size</code></td>
377
+ <td align="left" style="width: 25%;">
378
+
379
+ ```python
380
+ float
381
+ ```
382
+
383
+ </td>
384
+ <td align="left"><code>1.5</code></td>
385
+ <td align="left">None</td>
386
+ </tr>
387
+
388
+ <tr>
389
+ <td align="left"><code>intro_title</code></td>
390
+ <td align="left" style="width: 25%;">
391
+
392
+ ```python
393
+ str | None
394
+ ```
395
+
396
+ </td>
397
+ <td align="left"><code>None</code></td>
398
+ <td align="left">None</td>
399
+ </tr>
400
+
401
+ <tr>
402
+ <td align="left"><code>intro_subtitle</code></td>
403
+ <td align="left" style="width: 25%;">
404
+
405
+ ```python
406
+ str | None
407
+ ```
408
+
409
+ </td>
410
+ <td align="left"><code>None</code></td>
411
+ <td align="left">None</td>
412
+ </tr>
413
+
414
+ <tr>
415
+ <td align="left"><code>sidebar_position</code></td>
416
+ <td align="left" style="width: 25%;">
417
+
418
+ ```python
419
+ "right" | "bottom"
420
+ ```
421
+
422
+ </td>
423
+ <td align="left"><code>"right"</code></td>
424
+ <td align="left">None</td>
425
+ </tr>
426
+
427
+ <tr>
428
+ <td align="left"><code>logo_path</code></td>
429
+ <td align="left" style="width: 25%;">
430
+
431
+ ```python
432
+ str | pathlib.Path | None
433
+ ```
434
+
435
+ </td>
436
+ <td align="left"><code>None</code></td>
437
+ <td align="left">None</td>
438
+ </tr>
439
+
440
+ <tr>
441
+ <td align="left"><code>show_logo</code></td>
442
+ <td align="left" style="width: 25%;">
443
+
444
+ ```python
445
+ bool
446
+ ```
447
+
448
+ </td>
449
+ <td align="left"><code>True</code></td>
450
+ <td align="left">None</td>
451
+ </tr>
452
+
453
+ <tr>
454
+ <td align="left"><code>show_licenses</code></td>
455
+ <td align="left" style="width: 25%;">
456
+
457
+ ```python
458
+ bool
459
+ ```
460
+
461
+ </td>
462
+ <td align="left"><code>True</code></td>
463
+ <td align="left">None</td>
464
+ </tr>
465
+
466
+ <tr>
467
+ <td align="left"><code>logo_position</code></td>
468
+ <td align="left" style="width: 25%;">
469
+
470
+ ```python
471
+ "center" | "left" | "right"
472
+ ```
473
+
474
+ </td>
475
+ <td align="left"><code>"center"</code></td>
476
+ <td align="left">None</td>
477
+ </tr>
478
+
479
+ <tr>
480
+ <td align="left"><code>logo_sizing</code></td>
481
+ <td align="left" style="width: 25%;">
482
+
483
+ ```python
484
+ "stretch" | "crop" | "resize"
485
+ ```
486
+
487
+ </td>
488
+ <td align="left"><code>"resize"</code></td>
489
+ <td align="left">None</td>
490
+ </tr>
491
+
492
+ <tr>
493
+ <td align="left"><code>logo_width</code></td>
494
+ <td align="left" style="width: 25%;">
495
+
496
+ ```python
497
+ int | str | None
498
+ ```
499
+
500
+ </td>
501
+ <td align="left"><code>None</code></td>
502
+ <td align="left">None</td>
503
+ </tr>
504
+
505
+ <tr>
506
+ <td align="left"><code>logo_height</code></td>
507
+ <td align="left" style="width: 25%;">
508
+
509
+ ```python
510
+ int | str | None
511
+ ```
512
+
513
+ </td>
514
+ <td align="left"><code>None</code></td>
515
+ <td align="left">None</td>
516
+ </tr>
517
+
518
+ <tr>
519
+ <td align="left"><code>scroll_background_color</code></td>
520
+ <td align="left" style="width: 25%;">
521
+
522
+ ```python
523
+ str | None
524
+ ```
525
+
526
+ </td>
527
+ <td align="left"><code>None</code></td>
528
+ <td align="left">None</td>
529
+ </tr>
530
+
531
+ <tr>
532
+ <td align="left"><code>scroll_title_color</code></td>
533
+ <td align="left" style="width: 25%;">
534
+
535
+ ```python
536
+ str | None
537
+ ```
538
+
539
+ </td>
540
+ <td align="left"><code>None</code></td>
541
+ <td align="left">None</td>
542
+ </tr>
543
+
544
+ <tr>
545
+ <td align="left"><code>scroll_name_color</code></td>
546
+ <td align="left" style="width: 25%;">
547
+
548
+ ```python
549
+ str | None
550
+ ```
551
+
552
+ </td>
553
+ <td align="left"><code>None</code></td>
554
+ <td align="left">None</td>
555
+ </tr>
556
+
557
+ <tr>
558
+ <td align="left"><code>label</code></td>
559
+ <td align="left" style="width: 25%;">
560
+
561
+ ```python
562
+ str | gradio.i18n.I18nData | None
563
+ ```
564
+
565
+ </td>
566
+ <td align="left"><code>None</code></td>
567
+ <td align="left">None</td>
568
+ </tr>
569
+
570
+ <tr>
571
+ <td align="left"><code>every</code></td>
572
+ <td align="left" style="width: 25%;">
573
+
574
+ ```python
575
+ float | None
576
+ ```
577
+
578
+ </td>
579
+ <td align="left"><code>None</code></td>
580
+ <td align="left">None</td>
581
+ </tr>
582
+
583
+ <tr>
584
+ <td align="left"><code>inputs</code></td>
585
+ <td align="left" style="width: 25%;">
586
+
587
+ ```python
588
+ typing.Union[
589
+ gradio.components.base.Component,
590
+ typing.Sequence[gradio.components.base.Component],
591
+ set[gradio.components.base.Component],
592
+ NoneType,
593
+ ][
594
+ gradio.components.base.Component,
595
+ typing.Sequence[gradio.components.base.Component][
596
+ gradio.components.base.Component
597
+ ],
598
+ set[gradio.components.base.Component],
599
+ None,
600
+ ]
601
+ ```
602
+
603
+ </td>
604
+ <td align="left"><code>None</code></td>
605
+ <td align="left">None</td>
606
+ </tr>
607
+
608
+ <tr>
609
+ <td align="left"><code>show_label</code></td>
610
+ <td align="left" style="width: 25%;">
611
+
612
+ ```python
613
+ bool
614
+ ```
615
+
616
+ </td>
617
+ <td align="left"><code>False</code></td>
618
+ <td align="left">None</td>
619
+ </tr>
620
+
621
+ <tr>
622
+ <td align="left"><code>container</code></td>
623
+ <td align="left" style="width: 25%;">
624
+
625
+ ```python
626
+ bool
627
+ ```
628
+
629
+ </td>
630
+ <td align="left"><code>True</code></td>
631
+ <td align="left">None</td>
632
+ </tr>
633
+
634
+ <tr>
635
+ <td align="left"><code>scale</code></td>
636
+ <td align="left" style="width: 25%;">
637
+
638
+ ```python
639
+ int | None
640
+ ```
641
+
642
+ </td>
643
+ <td align="left"><code>None</code></td>
644
+ <td align="left">None</td>
645
+ </tr>
646
+
647
+ <tr>
648
+ <td align="left"><code>min_width</code></td>
649
+ <td align="left" style="width: 25%;">
650
+
651
+ ```python
652
+ int
653
+ ```
654
+
655
+ </td>
656
+ <td align="left"><code>160</code></td>
657
+ <td align="left">None</td>
658
+ </tr>
659
+
660
+ <tr>
661
+ <td align="left"><code>interactive</code></td>
662
+ <td align="left" style="width: 25%;">
663
+
664
+ ```python
665
+ bool | None
666
+ ```
667
+
668
+ </td>
669
+ <td align="left"><code>None</code></td>
670
+ <td align="left">None</td>
671
+ </tr>
672
+
673
+ <tr>
674
+ <td align="left"><code>visible</code></td>
675
+ <td align="left" style="width: 25%;">
676
+
677
+ ```python
678
+ bool
679
+ ```
680
+
681
+ </td>
682
+ <td align="left"><code>True</code></td>
683
+ <td align="left">None</td>
684
+ </tr>
685
+
686
+ <tr>
687
+ <td align="left"><code>elem_id</code></td>
688
+ <td align="left" style="width: 25%;">
689
+
690
+ ```python
691
+ str | None
692
+ ```
693
+
694
+ </td>
695
+ <td align="left"><code>None</code></td>
696
+ <td align="left">None</td>
697
+ </tr>
698
+
699
+ <tr>
700
+ <td align="left"><code>elem_classes</code></td>
701
+ <td align="left" style="width: 25%;">
702
+
703
+ ```python
704
+ list[str] | str | None
705
+ ```
706
+
707
+ </td>
708
+ <td align="left"><code>None</code></td>
709
+ <td align="left">None</td>
710
+ </tr>
711
+
712
+ <tr>
713
+ <td align="left"><code>render</code></td>
714
+ <td align="left" style="width: 25%;">
715
+
716
+ ```python
717
+ bool
718
+ ```
719
+
720
+ </td>
721
+ <td align="left"><code>True</code></td>
722
+ <td align="left">None</td>
723
+ </tr>
724
+
725
+ <tr>
726
+ <td align="left"><code>key</code></td>
727
+ <td align="left" style="width: 25%;">
728
+
729
+ ```python
730
+ int | str | tuple[int | str, Ellipsis] | None
731
+ ```
732
+
733
+ </td>
734
+ <td align="left"><code>None</code></td>
735
+ <td align="left">None</td>
736
+ </tr>
737
+
738
+ <tr>
739
+ <td align="left"><code>preserved_by_key</code></td>
740
+ <td align="left" style="width: 25%;">
741
+
742
+ ```python
743
+ list[str] | str | None
744
+ ```
745
+
746
+ </td>
747
+ <td align="left"><code>"value"</code></td>
748
+ <td align="left">None</td>
749
+ </tr>
750
+ </tbody></table>
751
+
752
+
753
+ ### Events
754
+
755
+ | name | description |
756
+ |:-----|:------------|
757
+ | `change` | Triggered when the value of the CreditsPanel changes either because of user input (e.g. a user types in a textbox) OR because of a function update (e.g. an image receives a value from the output of an event trigger). See `.input()` for a listener that is only triggered by user input. |
758
+
759
+
760
+
761
+ ### User function
762
+
763
+ The impact on the users predict function varies depending on whether the component is used as an input or output for an event (or both).
764
+
765
+ - When used as an Input, the component only impacts the input signature of the user function.
766
+ - When used as an output, the component only impacts the return signature of the user function.
767
+
768
+ The code snippet below is accurate in cases where the component is used as both an input and an output.
769
+
770
+ - **As output:** Is passed, dict[str, Any] | None: The input payload, returned unchanged.
771
+
772
+
773
+ ```python
774
+ def predict(
775
+ value: typing.Optional[typing.Dict[str, typing.Any]][
776
+ typing.Dict[str, typing.Any][str, Any], None
777
+ ]
778
+ ) -> Any:
779
+ return value
780
+ ```
781
+
__init__.py ADDED
File without changes
app copy.py ADDED
@@ -0,0 +1,238 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ from gradio_creditspanel import CreditsPanel
3
+ import os
4
+
5
+ # Create dummy license files
6
+ os.makedirs("LICENSES", exist_ok=True)
7
+ if not os.path.exists("LICENSES/Apache.txt"):
8
+ with open("LICENSES/Apache.txt", "w") as f:
9
+ f.write("Apache License\nVersion 2.0, January 2004\nhttp://www.apache.org/licenses/...")
10
+ if not os.path.exists("LICENSES/MIT.txt"):
11
+ with open("LICENSES/MIT.txt", "w") as f:
12
+ f.write("MIT License\nCopyright (c) 2025 Author\nPermission is hereby granted...")
13
+
14
+ # Create assets directory for logo
15
+ os.makedirs("assets", exist_ok=True)
16
+ if not os.path.exists("./assets/logo.webp"):
17
+ print("Warning: ./assets/logo.webp not found. Creating placeholder. Replace with a valid WebP image.")
18
+ with open("./assets/logo.webp", "w") as f:
19
+ f.write("Placeholder WebP logo")
20
+ else:
21
+ print("Logo found at ./assets/logo.webp")
22
+
23
+ credits_list = [
24
+ {"title": "Project Manager", "name": "Emma Thompson"},
25
+ {"title": "Lead Developer", "name": "John Doe"},
26
+ {"title": "Senior Backend Engineer", "name": "Michael Chen"},
27
+ {"title": "Frontend Developer", "name": "Sarah Johnson"},
28
+ {"title": "UI/UX Designer", "name": "Jane Smith"},
29
+ {"title": "Database Architect", "name": "Alex Ray"},
30
+ {"title": "DevOps Engineer", "name": "Liam Patel"},
31
+ {"title": "Quality Assurance Lead", "name": "Sam Wilson"},
32
+ {"title": "Test Automation Engineer", "name": "Olivia Brown"},
33
+ {"title": "Security Analyst", "name": "David Kim"},
34
+ {"title": "Data Scientist", "name": "Sophie Martinez"},
35
+ {"title": "Machine Learning Engineer", "name": "Ethan Lee"},
36
+ {"title": "API Developer", "name": "Isabella Garcia"},
37
+ {"title": "Technical Writer", "name": "Noah Davis"},
38
+ {"title": "Scrum Master", "name": "Ava Rodriguez"},
39
+ {"title": "Cloud Infrastructure Engineer", "name": "Lucas Nguyen"},
40
+ {"title": "Mobile Developer", "name": "Mia Hernandez"},
41
+ {"title": "Performance Engineer", "name": "James Taylor"},
42
+ {"title": "Component Concept", "name": "Your Name"},
43
+ {"title": "Support Engineer", "name": "Charlotte Moore"}
44
+ ]
45
+
46
+ license_paths = {
47
+ "Gradio Framework": "./LICENSES/Apache.txt",
48
+ "This Component": "./LICENSES/MIT.txt"
49
+ }
50
+
51
+ DEFAULT_SPEEDS = {
52
+ "scroll": 40.0,
53
+ "starwars": 80.0,
54
+ "matrix": 40.0
55
+ }
56
+
57
+ def update_panel(
58
+ effect: str,
59
+ speed: float,
60
+ sidebar_position: str,
61
+ show_logo: bool,
62
+ show_licenses: bool,
63
+ logo_position: str,
64
+ logo_sizing: str,
65
+ logo_width: int | str | None,
66
+ logo_height: int | str | None,
67
+ scroll_background_color: str | None,
68
+ scroll_title_color: str | None,
69
+ scroll_name_color: str | None
70
+ ):
71
+ print(f"Updating panel: effect={effect}, speed={speed}, sidebar_position={sidebar_position}, show_logo={show_logo}, show_licenses={show_licenses}, logo_position={logo_position}, logo_sizing={logo_sizing}, logo_width={logo_width}, logo_height={logo_height}")
72
+ return gr.update(
73
+ visible=True,
74
+ effect=effect,
75
+ speed=speed,
76
+ sidebar_position=sidebar_position,
77
+ show_logo=show_logo,
78
+ show_licenses=show_licenses,
79
+ logo_position=logo_position,
80
+ logo_sizing=logo_sizing,
81
+ logo_width=logo_width,
82
+ logo_height=logo_height,
83
+ scroll_background_color=scroll_background_color,
84
+ scroll_title_color=scroll_title_color,
85
+ scroll_name_color=scroll_name_color,
86
+ value=credits_list
87
+ # value={
88
+ # "credits": credits_list,
89
+ # "licenses": license_paths,
90
+ # "effect": effect,
91
+ # "speed": speed,
92
+ # "sidebar_position": sidebar_position,
93
+ # "logo_path": "./assets/logo.webp", # Handled by Gradio's file serving
94
+ # "show_logo": show_logo,
95
+ # "show_licenses": show_licenses,
96
+ # "logo_position": logo_position,
97
+ # "logo_sizing": logo_sizing,
98
+ # "logo_width": logo_width,
99
+ # "logo_height": logo_height,
100
+ # "scroll_background_color": scroll_background_color,
101
+ # "scroll_title_color": scroll_title_color,
102
+ # "scroll_name_color": scroll_name_color
103
+ # }
104
+ )
105
+
106
+ def update_speed_on_effect_change(effect: str):
107
+ """Update speed_slider to default speed when effect changes."""
108
+ return DEFAULT_SPEEDS.get(effect, 40.0)
109
+
110
+ with gr.Blocks(theme=gr.themes.Ocean(), title="CreditsPanel Demo", css="") as demo:
111
+ gr.Markdown(
112
+ """
113
+ # Interactive CreditsPanel Demo
114
+ Use the sidebar controls to customize the credits panel.
115
+ """
116
+ )
117
+
118
+ with gr.Sidebar(position="right"):
119
+ effect_radio = gr.Radio(
120
+ ["scroll", "starwars", "matrix"],
121
+ label="Animation Effect",
122
+ value="scroll",
123
+ info="Select the visual style for the credits."
124
+ )
125
+ speed_slider = gr.Slider(
126
+ minimum=5.0,
127
+ maximum=100.0,
128
+ step=1.0,
129
+ value=DEFAULT_SPEEDS["scroll"],
130
+ label="Animation Speed (seconds)",
131
+ info="Duration of the animation cycle."
132
+ )
133
+ sidebar_position_radio = gr.Radio(
134
+ ["right", "bottom"],
135
+ label="Sidebar Position",
136
+ value="right",
137
+ info="Place the licenses sidebar on the right or bottom."
138
+ )
139
+ show_logo_checkbox = gr.Checkbox(
140
+ label="Show Logo",
141
+ value=True,
142
+ info="Toggle the logo panel."
143
+ )
144
+ show_licenses_checkbox = gr.Checkbox(
145
+ label="Show Licenses",
146
+ value=True,
147
+ info="Toggle the licenses sidebar."
148
+ )
149
+ logo_position_radio = gr.Radio(
150
+ ["center", "left", "right"],
151
+ label="Logo Position",
152
+ value="center",
153
+ info="Position of the logo in the panel."
154
+ )
155
+ logo_sizing_radio = gr.Radio(
156
+ ["stretch", "crop", "resize"],
157
+ label="Logo Sizing",
158
+ value="resize",
159
+ info="How the logo fits in the panel."
160
+ )
161
+ logo_width_input = gr.Textbox(
162
+ label="Logo Width (px or CSS)",
163
+ value="200px",
164
+ info="Width of the logo (e.g., '200px' or '50%')."
165
+ )
166
+ logo_height_input = gr.Textbox(
167
+ label="Logo Height (px or CSS)",
168
+ value="100px",
169
+ info="Height of the logo (e.g., '100px' or '10%')."
170
+ )
171
+ scroll_background_color = gr.ColorPicker(
172
+ label="Scroll Background Color",
173
+ value="#000000",
174
+ info="Background color for ScrollEffect."
175
+ )
176
+ scroll_title_color = gr.ColorPicker(
177
+ label="Scroll Title Color",
178
+ value="#FFFFFF",
179
+ info="Color for title text in ScrollEffect."
180
+ )
181
+ scroll_name_color = gr.ColorPicker(
182
+ label="Scroll Name Color",
183
+ value="#FFFFFF",
184
+ info="Color for name text in ScrollEffect."
185
+ )
186
+
187
+ panel = CreditsPanel(
188
+ credits=credits_list,
189
+ licenses=license_paths,
190
+ effect="scroll",
191
+ height=500,
192
+ speed=DEFAULT_SPEEDS["scroll"],
193
+ sidebar_position="right",
194
+ logo_path="./assets/logo.webp", # Handled by Gradio's file serving
195
+ show_logo=True,
196
+ show_licenses=True,
197
+ logo_position="center",
198
+ logo_sizing="resize",
199
+ logo_width="200px",
200
+ logo_height="100px",
201
+ scroll_background_color="#000000",
202
+ scroll_title_color="#FFFFFF",
203
+ scroll_name_color="#FFFFFF",
204
+ visible=True
205
+ )
206
+
207
+ inputs = [
208
+ effect_radio,
209
+ speed_slider,
210
+ sidebar_position_radio,
211
+ show_logo_checkbox,
212
+ show_licenses_checkbox,
213
+ logo_position_radio,
214
+ logo_sizing_radio,
215
+ logo_width_input,
216
+ logo_height_input,
217
+ scroll_background_color,
218
+ scroll_title_color,
219
+ scroll_name_color
220
+ ]
221
+
222
+ # Update speed when effect changes
223
+ effect_radio.change(
224
+ fn=update_speed_on_effect_change,
225
+ inputs=effect_radio,
226
+ outputs=speed_slider
227
+ )
228
+
229
+ # Update panel for all inputs
230
+ for input_component in inputs:
231
+ input_component.change(
232
+ fn=update_panel,
233
+ inputs=inputs,
234
+ outputs=panel
235
+ )
236
+
237
+ if __name__ == "__main__":
238
+ demo.launch(debug=True, share=False)
app.py ADDED
@@ -0,0 +1,227 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ app.py
3
+
4
+ This script serves as an interactive demonstration for the custom Gradio component `CreditsPanel`.
5
+ It showcases all available features of the component, allowing users to dynamically adjust
6
+ properties like animation effects, speed, layout, and styling. The app also demonstrates
7
+ how to handle file dependencies (logo, licenses) in a portable way.
8
+ """
9
+
10
+ import gradio as gr
11
+ from gradio_creditspanel import CreditsPanel
12
+ import os
13
+
14
+ # --- 1. SETUP & DATA PREPARATION ---
15
+ # This section prepares all necessary assets and data for the demo.
16
+ # It ensures the demo runs out-of-the-box without manual setup.
17
+
18
+ def setup_demo_files():
19
+ """
20
+ Creates necessary directories and dummy files (logo, licenses) for the demo.
21
+ This makes the application self-contained and easy to run.
22
+ """
23
+ # Create dummy license files
24
+ os.makedirs("LICENSES", exist_ok=True)
25
+ if not os.path.exists("LICENSES/Apache.txt"):
26
+ with open("LICENSES/Apache.txt", "w") as f:
27
+ f.write("Apache License\nVersion 2.0, January 2004\nhttp://www.apache.org/licenses/...")
28
+ if not os.path.exists("LICENSES/MIT.txt"):
29
+ with open("LICENSES/MIT.txt", "w") as f:
30
+ f.write("MIT License\nCopyright (c) 2025 Author\nPermission is hereby granted...")
31
+
32
+ # Create a placeholder logo if it doesn't exist
33
+ os.makedirs("assets", exist_ok=True)
34
+ if not os.path.exists("./assets/logo.webp"):
35
+ with open("./assets/logo.webp", "w") as f:
36
+ f.write("Placeholder WebP logo")
37
+
38
+ # Initial data for the credits roll
39
+ credits_list = [
40
+ {"title": "Project Manager", "name": "Emma Thompson"},
41
+ {"title": "Lead Developer", "name": "John Doe"},
42
+ {"title": "Senior Backend Engineer", "name": "Michael Chen"},
43
+ {"title": "Frontend Developer", "name": "Sarah Johnson"},
44
+ {"title": "UI/UX Designer", "name": "Jane Smith"},
45
+ {"title": "Database Architect", "name": "Alex Ray"},
46
+ {"title": "DevOps Engineer", "name": "Liam Patel"},
47
+ {"title": "Quality Assurance Lead", "name": "Sam Wilson"},
48
+ {"title": "Test Automation Engineer", "name": "Olivia Brown"},
49
+ {"title": "Security Analyst", "name": "David Kim"},
50
+ {"title": "Data Scientist", "name": "Sophie Martinez"},
51
+ {"title": "Machine Learning Engineer", "name": "Ethan Lee"},
52
+ {"title": "API Developer", "name": "Isabella Garcia"},
53
+ {"title": "Technical Writer", "name": "Noah Davis"},
54
+ {"title": "Scrum Master", "name": "Ava Rodriguez"},
55
+ ]
56
+
57
+ # Paths to license files
58
+ license_paths = {
59
+ "Gradio Framework": "./LICENSES/Apache.txt",
60
+ "This Component": "./LICENSES/MIT.txt"
61
+ }
62
+
63
+ # Default animation speeds for each effect to provide a good user experience
64
+ DEFAULT_SPEEDS = {
65
+ "scroll": 40.0,
66
+ "starwars": 80.0,
67
+ "matrix": 40.0
68
+ }
69
+
70
+ # --- 2. GRADIO EVENT HANDLER FUNCTIONS ---
71
+ # These functions define the application's interactive logic.
72
+
73
+ def update_panel(
74
+ effect: str, speed: float, base_font_size: float,
75
+ intro_title: str, intro_subtitle: str, sidebar_position: str,
76
+ show_logo: bool, show_licenses: bool, logo_position: str,
77
+ logo_sizing: str, logo_width: str | None, logo_height: str | None,
78
+ scroll_background_color: str | None, scroll_title_color: str | None,
79
+ scroll_name_color: str | None
80
+ ) -> dict:
81
+ """
82
+ Callback function that updates all properties of the CreditsPanel component.
83
+ It takes the current state of all UI controls and returns a gr.update() dictionary.
84
+ """
85
+ return gr.update(
86
+ visible=True,
87
+ effect=effect,
88
+ speed=speed,
89
+ base_font_size=base_font_size,
90
+ intro_title=intro_title,
91
+ intro_subtitle=intro_subtitle,
92
+ sidebar_position=sidebar_position,
93
+ show_logo=show_logo,
94
+ show_licenses=show_licenses,
95
+ logo_position=logo_position,
96
+ logo_sizing=logo_sizing,
97
+ logo_width=logo_width,
98
+ logo_height=logo_height,
99
+ scroll_background_color=scroll_background_color,
100
+ scroll_title_color=scroll_title_color,
101
+ scroll_name_color=scroll_name_color,
102
+ value=credits_list # The list of credits to display
103
+ )
104
+
105
+ def update_ui_on_effect_change(effect: str) -> tuple[float, float]:
106
+ """
107
+ Updates the speed and font size sliders to sensible defaults when the
108
+ animation effect is changed.
109
+ """
110
+ font_size = 1.5
111
+ if effect == "starwars":
112
+ font_size = 6.0 # Star Wars effect looks better with a larger font
113
+
114
+ speed = DEFAULT_SPEEDS.get(effect, 40.0)
115
+ return speed, font_size
116
+
117
+ # --- 3. GRADIO UI DEFINITION ---
118
+ # This section constructs the user interface using gr.Blocks.
119
+
120
+ with gr.Blocks(theme=gr.themes.Ocean(), title="CreditsPanel Demo") as demo:
121
+ gr.Markdown(
122
+ """
123
+ # Interactive CreditsPanel Demo
124
+ Use the sidebar controls to customize the `CreditsPanel` component in real-time.
125
+ """
126
+ )
127
+
128
+ with gr.Sidebar(position="right"):
129
+ gr.Markdown("### Effects Settings")
130
+ effect_radio = gr.Radio(
131
+ ["scroll", "starwars", "matrix"], label="Animation Effect", value="scroll",
132
+ info="Select the visual style for the credits."
133
+ )
134
+ speed_slider = gr.Slider(
135
+ minimum=5.0, maximum=100.0, step=1.0, value=DEFAULT_SPEEDS["scroll"],
136
+ label="Animation Speed (seconds)", info="Duration of one animation cycle."
137
+ )
138
+ font_size_slider = gr.Slider(
139
+ minimum=1.0, maximum=10.0, step=0.1, value=1.5,
140
+ label="Base Font Size (rem)", info="Controls the base font size."
141
+ )
142
+
143
+ gr.Markdown("### Intro Text")
144
+ intro_title_input = gr.Textbox(
145
+ label="Intro Title", value="Gradio", info="Main title for the intro sequence."
146
+ )
147
+ intro_subtitle_input = gr.Textbox(
148
+ label="Intro Subtitle", value="The best UI framework", info="Subtitle for the intro sequence."
149
+ )
150
+
151
+ gr.Markdown("### Layout & Visibility")
152
+ sidebar_position_radio = gr.Radio(
153
+ ["right", "bottom"], label="Sidebar Position", value="right",
154
+ info="Place the licenses sidebar on the right or bottom."
155
+ )
156
+ show_logo_checkbox = gr.Checkbox(label="Show Logo", value=True)
157
+ show_licenses_checkbox = gr.Checkbox(label="Show Licenses", value=True)
158
+
159
+ gr.Markdown("### Logo Customization")
160
+ logo_position_radio = gr.Radio(
161
+ ["left", "center", "right"], label="Logo Position", value="center"
162
+ )
163
+ logo_sizing_radio = gr.Radio(
164
+ ["stretch", "crop", "resize"], label="Logo Sizing", value="resize"
165
+ )
166
+ logo_width_input = gr.Textbox(label="Logo Width", value="200px")
167
+ logo_height_input = gr.Textbox(label="Logo Height", value="100px")
168
+
169
+ gr.Markdown("### Color Settings (Scroll Effect)")
170
+ scroll_background_color = gr.ColorPicker(label="Background Color", value="#000000")
171
+ scroll_title_color = gr.ColorPicker(label="Title Color", value="#FFFFFF")
172
+ scroll_name_color = gr.ColorPicker(label="Name Color", value="#FFFFFF")
173
+
174
+ # Instantiate the custom CreditsPanel component with default values
175
+ panel = CreditsPanel(
176
+ credits=credits_list,
177
+ licenses=license_paths,
178
+ effect="scroll",
179
+ height=500,
180
+ speed=DEFAULT_SPEEDS["scroll"],
181
+ base_font_size=1.5,
182
+ intro_title="Gradio",
183
+ intro_subtitle="The best UI framework",
184
+ sidebar_position="right",
185
+ logo_path="./assets/logo.webp",
186
+ show_logo=True,
187
+ show_licenses=True,
188
+ logo_position="center",
189
+ logo_sizing="resize",
190
+ logo_width="200px",
191
+ logo_height="100px",
192
+ scroll_background_color="#000000",
193
+ scroll_title_color="#FFFFFF",
194
+ scroll_name_color="#FFFFFF",
195
+ )
196
+
197
+ # List of all input components that should trigger a panel update
198
+ inputs = [
199
+ effect_radio, speed_slider, font_size_slider,
200
+ intro_title_input, intro_subtitle_input,
201
+ sidebar_position_radio, show_logo_checkbox, show_licenses_checkbox,
202
+ logo_position_radio, logo_sizing_radio, logo_width_input, logo_height_input,
203
+ scroll_background_color, scroll_title_color, scroll_name_color
204
+ ]
205
+
206
+ # --- 4. EVENT BINDING ---
207
+ # Connect the UI controls to the handler functions.
208
+
209
+ # Special event: changing the effect also updates speed and font size sliders
210
+ effect_radio.change(
211
+ fn=update_ui_on_effect_change,
212
+ inputs=effect_radio,
213
+ outputs=[speed_slider, font_size_slider]
214
+ )
215
+
216
+ # General event: any change in an input control updates the main panel
217
+ for input_component in inputs:
218
+ input_component.change(
219
+ fn=update_panel,
220
+ inputs=inputs,
221
+ outputs=panel
222
+ )
223
+
224
+ # --- 5. APP LAUNCH ---
225
+ if __name__ == "__main__":
226
+ setup_demo_files()
227
+ demo.launch()
css.css ADDED
@@ -0,0 +1,157 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ html {
2
+ font-family: Inter;
3
+ font-size: 16px;
4
+ font-weight: 400;
5
+ line-height: 1.5;
6
+ -webkit-text-size-adjust: 100%;
7
+ background: #fff;
8
+ color: #323232;
9
+ -webkit-font-smoothing: antialiased;
10
+ -moz-osx-font-smoothing: grayscale;
11
+ text-rendering: optimizeLegibility;
12
+ }
13
+
14
+ :root {
15
+ --space: 1;
16
+ --vspace: calc(var(--space) * 1rem);
17
+ --vspace-0: calc(3 * var(--space) * 1rem);
18
+ --vspace-1: calc(2 * var(--space) * 1rem);
19
+ --vspace-2: calc(1.5 * var(--space) * 1rem);
20
+ --vspace-3: calc(0.5 * var(--space) * 1rem);
21
+ }
22
+
23
+ .app {
24
+ max-width: 748px !important;
25
+ }
26
+
27
+ .prose p {
28
+ margin: var(--vspace) 0;
29
+ line-height: var(--vspace * 2);
30
+ font-size: 1rem;
31
+ }
32
+
33
+ code {
34
+ font-family: "Inconsolata", sans-serif;
35
+ font-size: 16px;
36
+ }
37
+
38
+ h1,
39
+ h1 code {
40
+ font-weight: 400;
41
+ line-height: calc(2.5 / var(--space) * var(--vspace));
42
+ }
43
+
44
+ h1 code {
45
+ background: none;
46
+ border: none;
47
+ letter-spacing: 0.05em;
48
+ padding-bottom: 5px;
49
+ position: relative;
50
+ padding: 0;
51
+ }
52
+
53
+ h2 {
54
+ margin: var(--vspace-1) 0 var(--vspace-2) 0;
55
+ line-height: 1em;
56
+ }
57
+
58
+ h3,
59
+ h3 code {
60
+ margin: var(--vspace-1) 0 var(--vspace-2) 0;
61
+ line-height: 1em;
62
+ }
63
+
64
+ h4,
65
+ h5,
66
+ h6 {
67
+ margin: var(--vspace-3) 0 var(--vspace-3) 0;
68
+ line-height: var(--vspace);
69
+ }
70
+
71
+ .bigtitle,
72
+ h1,
73
+ h1 code {
74
+ font-size: calc(8px * 4.5);
75
+ word-break: break-word;
76
+ }
77
+
78
+ .title,
79
+ h2,
80
+ h2 code {
81
+ font-size: calc(8px * 3.375);
82
+ font-weight: lighter;
83
+ word-break: break-word;
84
+ border: none;
85
+ background: none;
86
+ }
87
+
88
+ .subheading1,
89
+ h3,
90
+ h3 code {
91
+ font-size: calc(8px * 1.8);
92
+ font-weight: 600;
93
+ border: none;
94
+ background: none;
95
+ letter-spacing: 0.1em;
96
+ text-transform: uppercase;
97
+ }
98
+
99
+ h2 code {
100
+ padding: 0;
101
+ position: relative;
102
+ letter-spacing: 0.05em;
103
+ }
104
+
105
+ blockquote {
106
+ font-size: calc(8px * 1.1667);
107
+ font-style: italic;
108
+ line-height: calc(1.1667 * var(--vspace));
109
+ margin: var(--vspace-2) var(--vspace-2);
110
+ }
111
+
112
+ .subheading2,
113
+ h4 {
114
+ font-size: calc(8px * 1.4292);
115
+ text-transform: uppercase;
116
+ font-weight: 600;
117
+ }
118
+
119
+ .subheading3,
120
+ h5 {
121
+ font-size: calc(8px * 1.2917);
122
+ line-height: calc(1.2917 * var(--vspace));
123
+
124
+ font-weight: lighter;
125
+ text-transform: uppercase;
126
+ letter-spacing: 0.15em;
127
+ }
128
+
129
+ h6 {
130
+ font-size: calc(8px * 1.1667);
131
+ font-size: 1.1667em;
132
+ font-weight: normal;
133
+ font-style: italic;
134
+ font-family: "le-monde-livre-classic-byol", serif !important;
135
+ letter-spacing: 0px !important;
136
+ }
137
+
138
+ #start .md > *:first-child {
139
+ margin-top: 0;
140
+ }
141
+
142
+ h2 + h3 {
143
+ margin-top: 0;
144
+ }
145
+
146
+ .md hr {
147
+ border: none;
148
+ border-top: 1px solid var(--block-border-color);
149
+ margin: var(--vspace-2) 0 var(--vspace-2) 0;
150
+ }
151
+ .prose ul {
152
+ margin: var(--vspace-2) 0 var(--vspace-1) 0;
153
+ }
154
+
155
+ .gap {
156
+ gap: 0;
157
+ }
requirements.txt ADDED
@@ -0,0 +1 @@
 
 
1
+ gradio_creditspanel
space.py ADDED
@@ -0,0 +1,350 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import gradio as gr
3
+ from app import demo as app
4
+ import os
5
+
6
+ _docs = {'CreditsPanel': {'description': 'A Gradio component for displaying credits with customizable visual effects, such as scrolling or Star Wars-style animations.\nSupports displaying a logo, licenses, and configurable text styling.\n\n EVENTS (list): Supported events for the component, currently only `change`.', 'members': {'__init__': {'value': {'type': 'Any', 'default': 'None', 'description': None}, 'credits': {'type': 'typing.Union[\n typing.List[typing.Dict[str, str]],\n typing.Callable,\n NoneType,\n][\n typing.List[typing.Dict[str, str]][\n typing.Dict[str, str][str, str]\n ],\n Callable,\n None,\n]', 'default': 'None', 'description': None}, 'height': {'type': 'int | str | None', 'default': 'None', 'description': None}, 'width': {'type': 'int | str | None', 'default': 'None', 'description': None}, 'licenses': {'type': 'typing.Optional[typing.Dict[str, str | pathlib.Path]][\n typing.Dict[str, str | pathlib.Path][\n str, str | pathlib.Path\n ],\n None,\n]', 'default': 'None', 'description': None}, 'effect': {'type': '"scroll" | "starwars" | "matrix"', 'default': '"scroll"', 'description': None}, 'speed': {'type': 'float', 'default': '40.0', 'description': None}, 'base_font_size': {'type': 'float', 'default': '1.5', 'description': None}, 'intro_title': {'type': 'str | None', 'default': 'None', 'description': None}, 'intro_subtitle': {'type': 'str | None', 'default': 'None', 'description': None}, 'sidebar_position': {'type': '"right" | "bottom"', 'default': '"right"', 'description': None}, 'logo_path': {'type': 'str | pathlib.Path | None', 'default': 'None', 'description': None}, 'show_logo': {'type': 'bool', 'default': 'True', 'description': None}, 'show_licenses': {'type': 'bool', 'default': 'True', 'description': None}, 'logo_position': {'type': '"center" | "left" | "right"', 'default': '"center"', 'description': None}, 'logo_sizing': {'type': '"stretch" | "crop" | "resize"', 'default': '"resize"', 'description': None}, 'logo_width': {'type': 'int | str | None', 'default': 'None', 'description': None}, 'logo_height': {'type': 'int | str | None', 'default': 'None', 'description': None}, 'scroll_background_color': {'type': 'str | None', 'default': 'None', 'description': None}, 'scroll_title_color': {'type': 'str | None', 'default': 'None', 'description': None}, 'scroll_name_color': {'type': 'str | None', 'default': 'None', 'description': None}, 'label': {'type': 'str | gradio.i18n.I18nData | None', 'default': 'None', 'description': None}, 'every': {'type': 'float | None', 'default': 'None', 'description': None}, 'inputs': {'type': 'typing.Union[\n gradio.components.base.Component,\n typing.Sequence[gradio.components.base.Component],\n set[gradio.components.base.Component],\n NoneType,\n][\n gradio.components.base.Component,\n typing.Sequence[gradio.components.base.Component][\n gradio.components.base.Component\n ],\n set[gradio.components.base.Component],\n None,\n]', 'default': 'None', 'description': None}, 'show_label': {'type': 'bool', 'default': 'False', 'description': None}, 'container': {'type': 'bool', 'default': 'True', 'description': None}, 'scale': {'type': 'int | None', 'default': 'None', 'description': None}, 'min_width': {'type': 'int', 'default': '160', 'description': None}, 'interactive': {'type': 'bool | None', 'default': 'None', 'description': None}, 'visible': {'type': 'bool', 'default': 'True', 'description': None}, 'elem_id': {'type': 'str | None', 'default': 'None', 'description': None}, 'elem_classes': {'type': 'list[str] | str | None', 'default': 'None', 'description': None}, 'render': {'type': 'bool', 'default': 'True', 'description': None}, 'key': {'type': 'int | str | tuple[int | str, Ellipsis] | None', 'default': 'None', 'description': None}, 'preserved_by_key': {'type': 'list[str] | str | None', 'default': '"value"', 'description': None}}, 'postprocess': {'value': {'type': 'Any', 'description': None}}, 'preprocess': {'return': {'type': 'typing.Optional[typing.Dict[str, typing.Any]][\n typing.Dict[str, typing.Any][str, Any], None\n]', 'description': 'Dict[str, Any] | None: The input payload, returned unchanged.'}, 'value': None}}, 'events': {'change': {'type': None, 'default': None, 'description': 'Triggered when the value of the CreditsPanel changes either because of user input (e.g. a user types in a textbox) OR because of a function update (e.g. an image receives a value from the output of an event trigger). See `.input()` for a listener that is only triggered by user input.'}}}, '__meta__': {'additional_interfaces': {}, 'user_fn_refs': {'CreditsPanel': []}}}
7
+
8
+ abs_path = os.path.join(os.path.dirname(__file__), "css.css")
9
+
10
+ with gr.Blocks(
11
+ css=abs_path,
12
+ theme=gr.themes.Ocean(
13
+ font_mono=[
14
+ gr.themes.GoogleFont("Inconsolata"),
15
+ "monospace",
16
+ ],
17
+ ),
18
+ ) as demo:
19
+ gr.Markdown(
20
+ """
21
+ # `gradio_creditspanel`
22
+
23
+ <div style="display: flex; gap: 7px;">
24
+ <img alt="Static Badge" src="https://img.shields.io/badge/version%20-%200.0.1%20-%20orange">
25
+ </div>
26
+
27
+ Credits Panel for Gradio UI
28
+ """, elem_classes=["md-custom"], header_links=True)
29
+ app.render()
30
+ gr.Markdown(
31
+ """
32
+ ## Installation
33
+
34
+ ```bash
35
+ pip install gradio_creditspanel
36
+ ```
37
+
38
+ ## Usage
39
+
40
+ ```python
41
+ \"\"\"
42
+ app.py
43
+
44
+ This script serves as an interactive demonstration for the custom Gradio component `CreditsPanel`.
45
+ It showcases all available features of the component, allowing users to dynamically adjust
46
+ properties like animation effects, speed, layout, and styling. The app also demonstrates
47
+ how to handle file dependencies (logo, licenses) in a portable way.
48
+ \"\"\"
49
+
50
+ import gradio as gr
51
+ from gradio_creditspanel import CreditsPanel
52
+ import os
53
+
54
+ # --- 1. SETUP & DATA PREPARATION ---
55
+ # This section prepares all necessary assets and data for the demo.
56
+ # It ensures the demo runs out-of-the-box without manual setup.
57
+
58
+ def setup_demo_files():
59
+ \"\"\"
60
+ Creates necessary directories and dummy files (logo, licenses) for the demo.
61
+ This makes the application self-contained and easy to run.
62
+ \"\"\"
63
+ # Create dummy license files
64
+ os.makedirs("LICENSES", exist_ok=True)
65
+ if not os.path.exists("LICENSES/Apache.txt"):
66
+ with open("LICENSES/Apache.txt", "w") as f:
67
+ f.write("Apache License\nVersion 2.0, January 2004\nhttp://www.apache.org/licenses/...")
68
+ if not os.path.exists("LICENSES/MIT.txt"):
69
+ with open("LICENSES/MIT.txt", "w") as f:
70
+ f.write("MIT License\nCopyright (c) 2025 Author\nPermission is hereby granted...")
71
+
72
+ # Create a placeholder logo if it doesn't exist
73
+ os.makedirs("assets", exist_ok=True)
74
+ if not os.path.exists("./assets/logo.webp"):
75
+ with open("./assets/logo.webp", "w") as f:
76
+ f.write("Placeholder WebP logo")
77
+
78
+ # Initial data for the credits roll
79
+ credits_list = [
80
+ {"title": "Project Manager", "name": "Emma Thompson"},
81
+ {"title": "Lead Developer", "name": "John Doe"},
82
+ {"title": "Senior Backend Engineer", "name": "Michael Chen"},
83
+ {"title": "Frontend Developer", "name": "Sarah Johnson"},
84
+ {"title": "UI/UX Designer", "name": "Jane Smith"},
85
+ {"title": "Database Architect", "name": "Alex Ray"},
86
+ {"title": "DevOps Engineer", "name": "Liam Patel"},
87
+ {"title": "Quality Assurance Lead", "name": "Sam Wilson"},
88
+ {"title": "Test Automation Engineer", "name": "Olivia Brown"},
89
+ {"title": "Security Analyst", "name": "David Kim"},
90
+ {"title": "Data Scientist", "name": "Sophie Martinez"},
91
+ {"title": "Machine Learning Engineer", "name": "Ethan Lee"},
92
+ {"title": "API Developer", "name": "Isabella Garcia"},
93
+ {"title": "Technical Writer", "name": "Noah Davis"},
94
+ {"title": "Scrum Master", "name": "Ava Rodriguez"},
95
+ ]
96
+
97
+ # Paths to license files
98
+ license_paths = {
99
+ "Gradio Framework": "./LICENSES/Apache.txt",
100
+ "This Component": "./LICENSES/MIT.txt"
101
+ }
102
+
103
+ # Default animation speeds for each effect to provide a good user experience
104
+ DEFAULT_SPEEDS = {
105
+ "scroll": 40.0,
106
+ "starwars": 80.0,
107
+ "matrix": 40.0
108
+ }
109
+
110
+ # --- 2. GRADIO EVENT HANDLER FUNCTIONS ---
111
+ # These functions define the application's interactive logic.
112
+
113
+ def update_panel(
114
+ effect: str, speed: float, base_font_size: float,
115
+ intro_title: str, intro_subtitle: str, sidebar_position: str,
116
+ show_logo: bool, show_licenses: bool, logo_position: str,
117
+ logo_sizing: str, logo_width: str | None, logo_height: str | None,
118
+ scroll_background_color: str | None, scroll_title_color: str | None,
119
+ scroll_name_color: str | None
120
+ ) -> dict:
121
+ \"\"\"
122
+ Callback function that updates all properties of the CreditsPanel component.
123
+ It takes the current state of all UI controls and returns a gr.update() dictionary.
124
+ \"\"\"
125
+ return gr.update(
126
+ visible=True,
127
+ effect=effect,
128
+ speed=speed,
129
+ base_font_size=base_font_size,
130
+ intro_title=intro_title,
131
+ intro_subtitle=intro_subtitle,
132
+ sidebar_position=sidebar_position,
133
+ show_logo=show_logo,
134
+ show_licenses=show_licenses,
135
+ logo_position=logo_position,
136
+ logo_sizing=logo_sizing,
137
+ logo_width=logo_width,
138
+ logo_height=logo_height,
139
+ scroll_background_color=scroll_background_color,
140
+ scroll_title_color=scroll_title_color,
141
+ scroll_name_color=scroll_name_color,
142
+ value=credits_list # The list of credits to display
143
+ )
144
+
145
+ def update_ui_on_effect_change(effect: str) -> tuple[float, float]:
146
+ \"\"\"
147
+ Updates the speed and font size sliders to sensible defaults when the
148
+ animation effect is changed.
149
+ \"\"\"
150
+ font_size = 1.5
151
+ if effect == "starwars":
152
+ font_size = 6.0 # Star Wars effect looks better with a larger font
153
+
154
+ speed = DEFAULT_SPEEDS.get(effect, 40.0)
155
+ return speed, font_size
156
+
157
+ # --- 3. GRADIO UI DEFINITION ---
158
+ # This section constructs the user interface using gr.Blocks.
159
+
160
+ with gr.Blocks(theme=gr.themes.Ocean(), title="CreditsPanel Demo") as demo:
161
+ gr.Markdown(
162
+ \"\"\"
163
+ # Interactive CreditsPanel Demo
164
+ Use the sidebar controls to customize the `CreditsPanel` component in real-time.
165
+ \"\"\"
166
+ )
167
+
168
+ with gr.Sidebar(position="right"):
169
+ gr.Markdown("### Effects Settings")
170
+ effect_radio = gr.Radio(
171
+ ["scroll", "starwars", "matrix"], label="Animation Effect", value="scroll",
172
+ info="Select the visual style for the credits."
173
+ )
174
+ speed_slider = gr.Slider(
175
+ minimum=5.0, maximum=100.0, step=1.0, value=DEFAULT_SPEEDS["scroll"],
176
+ label="Animation Speed (seconds)", info="Duration of one animation cycle."
177
+ )
178
+ font_size_slider = gr.Slider(
179
+ minimum=1.0, maximum=10.0, step=0.1, value=1.5,
180
+ label="Base Font Size (rem)", info="Controls the base font size."
181
+ )
182
+
183
+ gr.Markdown("### Intro Text")
184
+ intro_title_input = gr.Textbox(
185
+ label="Intro Title", value="Gradio", info="Main title for the intro sequence."
186
+ )
187
+ intro_subtitle_input = gr.Textbox(
188
+ label="Intro Subtitle", value="The best UI framework", info="Subtitle for the intro sequence."
189
+ )
190
+
191
+ gr.Markdown("### Layout & Visibility")
192
+ sidebar_position_radio = gr.Radio(
193
+ ["right", "bottom"], label="Sidebar Position", value="right",
194
+ info="Place the licenses sidebar on the right or bottom."
195
+ )
196
+ show_logo_checkbox = gr.Checkbox(label="Show Logo", value=True)
197
+ show_licenses_checkbox = gr.Checkbox(label="Show Licenses", value=True)
198
+
199
+ gr.Markdown("### Logo Customization")
200
+ logo_position_radio = gr.Radio(
201
+ ["left", "center", "right"], label="Logo Position", value="center"
202
+ )
203
+ logo_sizing_radio = gr.Radio(
204
+ ["stretch", "crop", "resize"], label="Logo Sizing", value="resize"
205
+ )
206
+ logo_width_input = gr.Textbox(label="Logo Width", value="200px")
207
+ logo_height_input = gr.Textbox(label="Logo Height", value="100px")
208
+
209
+ gr.Markdown("### Color Settings (Scroll Effect)")
210
+ scroll_background_color = gr.ColorPicker(label="Background Color", value="#000000")
211
+ scroll_title_color = gr.ColorPicker(label="Title Color", value="#FFFFFF")
212
+ scroll_name_color = gr.ColorPicker(label="Name Color", value="#FFFFFF")
213
+
214
+ # Instantiate the custom CreditsPanel component with default values
215
+ panel = CreditsPanel(
216
+ credits=credits_list,
217
+ licenses=license_paths,
218
+ effect="scroll",
219
+ height=500,
220
+ speed=DEFAULT_SPEEDS["scroll"],
221
+ base_font_size=1.5,
222
+ intro_title="Gradio",
223
+ intro_subtitle="The best UI framework",
224
+ sidebar_position="right",
225
+ logo_path="./assets/logo.webp",
226
+ show_logo=True,
227
+ show_licenses=True,
228
+ logo_position="center",
229
+ logo_sizing="resize",
230
+ logo_width="200px",
231
+ logo_height="100px",
232
+ scroll_background_color="#000000",
233
+ scroll_title_color="#FFFFFF",
234
+ scroll_name_color="#FFFFFF",
235
+ )
236
+
237
+ # List of all input components that should trigger a panel update
238
+ inputs = [
239
+ effect_radio, speed_slider, font_size_slider,
240
+ intro_title_input, intro_subtitle_input,
241
+ sidebar_position_radio, show_logo_checkbox, show_licenses_checkbox,
242
+ logo_position_radio, logo_sizing_radio, logo_width_input, logo_height_input,
243
+ scroll_background_color, scroll_title_color, scroll_name_color
244
+ ]
245
+
246
+ # --- 4. EVENT BINDING ---
247
+ # Connect the UI controls to the handler functions.
248
+
249
+ # Special event: changing the effect also updates speed and font size sliders
250
+ effect_radio.change(
251
+ fn=update_ui_on_effect_change,
252
+ inputs=effect_radio,
253
+ outputs=[speed_slider, font_size_slider]
254
+ )
255
+
256
+ # General event: any change in an input control updates the main panel
257
+ for input_component in inputs:
258
+ input_component.change(
259
+ fn=update_panel,
260
+ inputs=inputs,
261
+ outputs=panel
262
+ )
263
+
264
+ # --- 5. APP LAUNCH ---
265
+ if __name__ == "__main__":
266
+ setup_demo_files()
267
+ demo.launch()
268
+ ```
269
+ """, elem_classes=["md-custom"], header_links=True)
270
+
271
+
272
+ gr.Markdown("""
273
+ ## `CreditsPanel`
274
+
275
+ ### Initialization
276
+ """, elem_classes=["md-custom"], header_links=True)
277
+
278
+ gr.ParamViewer(value=_docs["CreditsPanel"]["members"]["__init__"], linkify=[])
279
+
280
+
281
+ gr.Markdown("### Events")
282
+ gr.ParamViewer(value=_docs["CreditsPanel"]["events"], linkify=['Event'])
283
+
284
+
285
+
286
+
287
+ gr.Markdown("""
288
+
289
+ ### User function
290
+
291
+ The impact on the users predict function varies depending on whether the component is used as an input or output for an event (or both).
292
+
293
+ - When used as an Input, the component only impacts the input signature of the user function.
294
+ - When used as an output, the component only impacts the return signature of the user function.
295
+
296
+ The code snippet below is accurate in cases where the component is used as both an input and an output.
297
+
298
+ - **As input:** Is passed, dict[str, Any] | None: The input payload, returned unchanged.
299
+
300
+
301
+ ```python
302
+ def predict(
303
+ value: typing.Optional[typing.Dict[str, typing.Any]][
304
+ typing.Dict[str, typing.Any][str, Any], None
305
+ ]
306
+ ) -> Any:
307
+ return value
308
+ ```
309
+ """, elem_classes=["md-custom", "CreditsPanel-user-fn"], header_links=True)
310
+
311
+
312
+
313
+
314
+ demo.load(None, js=r"""function() {
315
+ const refs = {};
316
+ const user_fn_refs = {
317
+ CreditsPanel: [], };
318
+ requestAnimationFrame(() => {
319
+
320
+ Object.entries(user_fn_refs).forEach(([key, refs]) => {
321
+ if (refs.length > 0) {
322
+ const el = document.querySelector(`.${key}-user-fn`);
323
+ if (!el) return;
324
+ refs.forEach(ref => {
325
+ el.innerHTML = el.innerHTML.replace(
326
+ new RegExp("\\b"+ref+"\\b", "g"),
327
+ `<a href="#h-${ref.toLowerCase()}">${ref}</a>`
328
+ );
329
+ })
330
+ }
331
+ })
332
+
333
+ Object.entries(refs).forEach(([key, refs]) => {
334
+ if (refs.length > 0) {
335
+ const el = document.querySelector(`.${key}`);
336
+ if (!el) return;
337
+ refs.forEach(ref => {
338
+ el.innerHTML = el.innerHTML.replace(
339
+ new RegExp("\\b"+ref+"\\b", "g"),
340
+ `<a href="#h-${ref.toLowerCase()}">${ref}</a>`
341
+ );
342
+ })
343
+ }
344
+ })
345
+ })
346
+ }
347
+
348
+ """)
349
+
350
+ demo.launch()
src/.gitignore ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .eggs/
2
+ dist/
3
+ .vscode/
4
+ *.pyc
5
+ __pycache__/
6
+ *.py[cod]
7
+ *$py.class
8
+ __tmp/*
9
+ *.pyi
10
+ .mypycache
11
+ .ruff_cache
12
+ node_modules
13
+ backend/**/templates/
14
+ README_TEMPLATE.md
src/.vscode/launch.json ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ // Use IntelliSense to learn about possible attributes.
3
+ // Hover to view descriptions of existing attributes.
4
+ // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5
+ "version": "0.2.0",
6
+ "configurations": [
7
+ {
8
+ "name": "Python Debugger: Current File",
9
+ "type": "debugpy",
10
+ "request": "launch",
11
+ "program": "${file}",
12
+ "console": "integratedTerminal",
13
+ "justMyCode": false
14
+ },
15
+ {
16
+ "name": "Gradio dev (Python attach)",
17
+ "type": "debugpy",
18
+ "request": "attach",
19
+ "processId": "${command:pickProcess}",
20
+ "justMyCode": false
21
+ },
22
+ {
23
+ "name": "Gradio dev (Svelte attach)",
24
+ "type": "chrome",
25
+ "request": "attach",
26
+ "port": 9222,
27
+ }
28
+ ]
29
+ }
src/LICENSES/Apache.txt ADDED
@@ -0,0 +1,201 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ http://www.apache.org/licenses/
4
+
5
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
+
7
+ 1. Definitions.
8
+
9
+ "License" shall mean the terms and conditions for use, reproduction,
10
+ and distribution as defined by Sections 1 through 9 of this document.
11
+
12
+ "Licensor" shall mean the copyright owner or entity authorized by
13
+ the copyright owner that is granting the License.
14
+
15
+ "Legal Entity" shall mean the union of the acting entity and all
16
+ other entities that control, are controlled by, or are under common
17
+ control with that entity. For the purposes of this definition,
18
+ "control" means (i) the power, direct or indirect, to cause the
19
+ direction or management of such entity, whether by contract or
20
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
21
+ outstanding shares, or (iii) beneficial ownership of such entity.
22
+
23
+ "You" (or "Your") shall mean an individual or Legal Entity
24
+ exercising permissions granted by this License.
25
+
26
+ "Source" form shall mean the preferred form for making modifications,
27
+ including but not limited to software source code, documentation
28
+ source, and configuration files.
29
+
30
+ "Object" form shall mean any form resulting from mechanical
31
+ transformation or translation of a Source form, including but
32
+ not limited to compiled object code, generated documentation,
33
+ and conversions to other media types.
34
+
35
+ "Work" shall mean the work of authorship, whether in Source or
36
+ Object form, made available under the License, as indicated by a
37
+ copyright notice that is included in or attached to the work
38
+ (an example is provided in the Appendix below).
39
+
40
+ "Derivative Works" shall mean any work, whether in Source or Object
41
+ form, that is based on (or derived from) the Work and for which the
42
+ editorial revisions, annotations, elaborations, or other modifications
43
+ represent, as a whole, an original work of authorship. For the purposes
44
+ of this License, Derivative Works shall not include works that remain
45
+ separable from, or merely link (or bind by name) to the interfaces of,
46
+ the Work and Derivative Works thereof.
47
+
48
+ "Contribution" shall mean any work of authorship, including
49
+ the original version of the Work and any modifications or additions
50
+ to that Work or Derivative Works thereof, that is intentionally
51
+ submitted to Licensor for inclusion in the Work by the copyright owner
52
+ or by an individual or Legal Entity authorized to submit on behalf of
53
+ the copyright owner. For the purposes of this definition, "submitted"
54
+ means any form of electronic, verbal, or written communication sent
55
+ to the Licensor or its representatives, including but not limited to
56
+ communication on electronic mailing lists, source code control systems,
57
+ and issue tracking systems that are managed by, or on behalf of, the
58
+ Licensor for the purpose of discussing and improving the Work, but
59
+ excluding communication that is conspicuously marked or otherwise
60
+ designated in writing by the copyright owner as "Not a Contribution."
61
+
62
+ "Contributor" shall mean Licensor and any individual or Legal Entity
63
+ on behalf of whom a Contribution has been received by Licensor and
64
+ subsequently incorporated within the Work.
65
+
66
+ 2. Grant of Copyright License. Subject to the terms and conditions of
67
+ this License, each Contributor hereby grants to You a perpetual,
68
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69
+ copyright license to reproduce, prepare Derivative Works of,
70
+ publicly display, publicly perform, sublicense, and distribute the
71
+ Work and such Derivative Works in Source or Object form.
72
+
73
+ 3. Grant of Patent License. Subject to the terms and conditions of
74
+ this License, each Contributor hereby grants to You a perpetual,
75
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76
+ (except as stated in this section) patent license to make, have made,
77
+ use, offer to sell, sell, import, and otherwise transfer the Work,
78
+ where such license applies only to those patent claims licensable
79
+ by such Contributor that are necessarily infringed by their
80
+ Contribution(s) alone or by combination of their Contribution(s)
81
+ with the Work to which such Contribution(s) was submitted. If You
82
+ institute patent litigation against any entity (including a
83
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
84
+ or a Contribution incorporated within the Work constitutes direct
85
+ or contributory patent infringement, then any patent licenses
86
+ granted to You under this License for that Work shall terminate
87
+ as of the date such litigation is filed.
88
+
89
+ 4. Redistribution. You may reproduce and distribute copies of the
90
+ Work or Derivative Works thereof in any medium, with or without
91
+ modifications, and in Source or Object form, provided that You
92
+ meet the following conditions:
93
+
94
+ (a) You must give any other recipients of the Work or
95
+ Derivative Works a copy of this License; and
96
+
97
+ (b) You must cause any modified files to carry prominent notices
98
+ stating that You changed the files; and
99
+
100
+ (c) You must retain, in the Source form of any Derivative Works
101
+ that You distribute, all copyright, patent, trademark, and
102
+ attribution notices from the Source form of the Work,
103
+ excluding those notices that do not pertain to any part of
104
+ the Derivative Works; and
105
+
106
+ (d) If the Work includes a "NOTICE" text file as part of its
107
+ distribution, then any Derivative Works that You distribute must
108
+ include a readable copy of the attribution notices contained
109
+ within such NOTICE file, excluding those notices that do not
110
+ pertain to any part of the Derivative Works, in at least one
111
+ of the following places: within a NOTICE text file distributed
112
+ as part of the Derivative Works; within the Source form or
113
+ documentation, if provided along with the Derivative Works; or,
114
+ within a display generated by the Derivative Works, if and
115
+ wherever such third-party notices normally appear. The contents
116
+ of the NOTICE file are for informational purposes only and
117
+ do not modify the License. You may add Your own attribution
118
+ notices within Derivative Works that You distribute, alongside
119
+ or as an addendum to the NOTICE text from the Work, provided
120
+ that such additional attribution notices cannot be construed
121
+ as modifying the License.
122
+
123
+ You may add Your own copyright statement to Your modifications and
124
+ may provide additional or different license terms and conditions
125
+ for use, reproduction, or distribution of Your modifications, or
126
+ for any such Derivative Works as a whole, provided Your use,
127
+ reproduction, and distribution of the Work otherwise complies with
128
+ the conditions stated in this License.
129
+
130
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
131
+ any Contribution intentionally submitted for inclusion in the Work
132
+ by You to the Licensor shall be under the terms and conditions of
133
+ this License, without any additional terms or conditions.
134
+ Notwithstanding the above, nothing herein shall supersede or modify
135
+ the terms of any separate license agreement you may have executed
136
+ with Licensor regarding such Contributions.
137
+
138
+ 6. Trademarks. This License does not grant permission to use the trade
139
+ names, trademarks, service marks, or product names of the Licensor,
140
+ except as required for reasonable and customary use in describing the
141
+ origin of the Work and reproducing the content of the NOTICE file.
142
+
143
+ 7. Disclaimer of Warranty. Unless required by applicable law or
144
+ agreed to in writing, Licensor provides the Work (and each
145
+ Contributor provides its Contributions) on an "AS IS" BASIS,
146
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147
+ implied, including, without limitation, any warranties or conditions
148
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149
+ PARTICULAR PURPOSE. You are solely responsible for determining the
150
+ appropriateness of using or redistributing the Work and assume any
151
+ risks associated with Your exercise of permissions under this License.
152
+
153
+ 8. Limitation of Liability. In no event and under no legal theory,
154
+ whether in tort (including negligence), contract, or otherwise,
155
+ unless required by applicable law (such as deliberate and grossly
156
+ negligent acts) or agreed to in writing, shall any Contributor be
157
+ liable to You for damages, including any direct, indirect, special,
158
+ incidental, or consequential damages of any character arising as a
159
+ result of this License or out of the use or inability to use the
160
+ Work (including but not limited to damages for loss of goodwill,
161
+ work stoppage, computer failure or malfunction, or any and all
162
+ other commercial damages or losses), even if such Contributor
163
+ has been advised of the possibility of such damages.
164
+
165
+ 9. Accepting Warranty or Additional Liability. While redistributing
166
+ the Work or Derivative Works thereof, You may choose to offer,
167
+ and charge a fee for, acceptance of support, warranty, indemnity,
168
+ or other liability obligations and/or rights consistent with this
169
+ License. However, in accepting such obligations, You may act only
170
+ on Your own behalf and on Your sole responsibility, not on behalf
171
+ of any other Contributor, and only if You agree to indemnify,
172
+ defend, and hold each Contributor harmless for any liability
173
+ incurred by, or claims asserted against, such Contributor by reason
174
+ of your accepting any such warranty or additional liability.
175
+
176
+ END OF TERMS AND CONDITIONS
177
+
178
+ APPENDIX: How to apply the Apache License to your work.
179
+
180
+ To apply the Apache License to your work, attach the following
181
+ boilerplate notice, with the fields enclosed by brackets "[]"
182
+ replaced with your own identifying information. (Don't include
183
+ the brackets!) The text should be enclosed in the appropriate
184
+ comment syntax for the file format. We also recommend that a
185
+ file or class name and description of purpose be included on the
186
+ same "printed page" as the copyright notice for easier
187
+ identification within third-party archives.
188
+
189
+ Copyright [yyyy] [name of copyright owner]
190
+
191
+ Licensed under the Apache License, Version 2.0 (the "License");
192
+ you may not use this file except in compliance with the License.
193
+ You may obtain a copy of the License at
194
+
195
+ http://www.apache.org/licenses/LICENSE-2.0
196
+
197
+ Unless required by applicable law or agreed to in writing, software
198
+ distributed under the License is distributed on an "AS IS" BASIS,
199
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200
+ See the License for the specific language governing permissions and
201
+ limitations under the License.
src/LICENSES/MIT.txt ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Author
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
src/README.md ADDED
@@ -0,0 +1,781 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ tags: [gradio-custom-component, HTML]
3
+ title: gradio_creditspanel
4
+ short_description: Credits Panel for Gradio UI
5
+ colorFrom: blue
6
+ colorTo: yellow
7
+ sdk: gradio
8
+ pinned: false
9
+ app_file: space.py
10
+ ---
11
+
12
+ # `gradio_creditspanel`
13
+ <img alt="Static Badge" src="https://img.shields.io/badge/version%20-%200.0.1%20-%20blue"> <a href="https://huggingface.co/spaces/elismasilva/gradio_creditspanel"><img src="https://img.shields.io/badge/%F0%9F%A4%97%20Hugging%20Face-Demo-blue"></a><p><span>💻 <a href='https://github.com/DEVAIEXP/gradio_component_creditspanel'>Component GitHub Code</a></span></p>
14
+
15
+ Credits Panel for Gradio UI
16
+
17
+ ## Installation
18
+
19
+ ```bash
20
+ pip install gradio_creditspanel
21
+ ```
22
+
23
+ ## Usage
24
+
25
+ ```python
26
+ """
27
+ app.py
28
+
29
+ This script serves as an interactive demonstration for the custom Gradio component `CreditsPanel`.
30
+ It showcases all available features of the component, allowing users to dynamically adjust
31
+ properties like animation effects, speed, layout, and styling. The app also demonstrates
32
+ how to handle file dependencies (logo, licenses) in a portable way.
33
+ """
34
+
35
+ import gradio as gr
36
+ from gradio_creditspanel import CreditsPanel
37
+ import os
38
+
39
+ # --- 1. SETUP & DATA PREPARATION ---
40
+ # This section prepares all necessary assets and data for the demo.
41
+ # It ensures the demo runs out-of-the-box without manual setup.
42
+
43
+ def setup_demo_files():
44
+ """
45
+ Creates necessary directories and dummy files (logo, licenses) for the demo.
46
+ This makes the application self-contained and easy to run.
47
+ """
48
+ # Create dummy license files
49
+ os.makedirs("LICENSES", exist_ok=True)
50
+ if not os.path.exists("LICENSES/Apache.txt"):
51
+ with open("LICENSES/Apache.txt", "w") as f:
52
+ f.write("Apache License\nVersion 2.0, January 2004\nhttp://www.apache.org/licenses/...")
53
+ if not os.path.exists("LICENSES/MIT.txt"):
54
+ with open("LICENSES/MIT.txt", "w") as f:
55
+ f.write("MIT License\nCopyright (c) 2025 Author\nPermission is hereby granted...")
56
+
57
+ # Create a placeholder logo if it doesn't exist
58
+ os.makedirs("assets", exist_ok=True)
59
+ if not os.path.exists("./assets/logo.webp"):
60
+ with open("./assets/logo.webp", "w") as f:
61
+ f.write("Placeholder WebP logo")
62
+
63
+ # Initial data for the credits roll
64
+ credits_list = [
65
+ {"title": "Project Manager", "name": "Emma Thompson"},
66
+ {"title": "Lead Developer", "name": "John Doe"},
67
+ {"title": "Senior Backend Engineer", "name": "Michael Chen"},
68
+ {"title": "Frontend Developer", "name": "Sarah Johnson"},
69
+ {"title": "UI/UX Designer", "name": "Jane Smith"},
70
+ {"title": "Database Architect", "name": "Alex Ray"},
71
+ {"title": "DevOps Engineer", "name": "Liam Patel"},
72
+ {"title": "Quality Assurance Lead", "name": "Sam Wilson"},
73
+ {"title": "Test Automation Engineer", "name": "Olivia Brown"},
74
+ {"title": "Security Analyst", "name": "David Kim"},
75
+ {"title": "Data Scientist", "name": "Sophie Martinez"},
76
+ {"title": "Machine Learning Engineer", "name": "Ethan Lee"},
77
+ {"title": "API Developer", "name": "Isabella Garcia"},
78
+ {"title": "Technical Writer", "name": "Noah Davis"},
79
+ {"title": "Scrum Master", "name": "Ava Rodriguez"},
80
+ ]
81
+
82
+ # Paths to license files
83
+ license_paths = {
84
+ "Gradio Framework": "./LICENSES/Apache.txt",
85
+ "This Component": "./LICENSES/MIT.txt"
86
+ }
87
+
88
+ # Default animation speeds for each effect to provide a good user experience
89
+ DEFAULT_SPEEDS = {
90
+ "scroll": 40.0,
91
+ "starwars": 80.0,
92
+ "matrix": 40.0
93
+ }
94
+
95
+ # --- 2. GRADIO EVENT HANDLER FUNCTIONS ---
96
+ # These functions define the application's interactive logic.
97
+
98
+ def update_panel(
99
+ effect: str, speed: float, base_font_size: float,
100
+ intro_title: str, intro_subtitle: str, sidebar_position: str,
101
+ show_logo: bool, show_licenses: bool, logo_position: str,
102
+ logo_sizing: str, logo_width: str | None, logo_height: str | None,
103
+ scroll_background_color: str | None, scroll_title_color: str | None,
104
+ scroll_name_color: str | None
105
+ ) -> dict:
106
+ """
107
+ Callback function that updates all properties of the CreditsPanel component.
108
+ It takes the current state of all UI controls and returns a gr.update() dictionary.
109
+ """
110
+ return gr.update(
111
+ visible=True,
112
+ effect=effect,
113
+ speed=speed,
114
+ base_font_size=base_font_size,
115
+ intro_title=intro_title,
116
+ intro_subtitle=intro_subtitle,
117
+ sidebar_position=sidebar_position,
118
+ show_logo=show_logo,
119
+ show_licenses=show_licenses,
120
+ logo_position=logo_position,
121
+ logo_sizing=logo_sizing,
122
+ logo_width=logo_width,
123
+ logo_height=logo_height,
124
+ scroll_background_color=scroll_background_color,
125
+ scroll_title_color=scroll_title_color,
126
+ scroll_name_color=scroll_name_color,
127
+ value=credits_list # The list of credits to display
128
+ )
129
+
130
+ def update_ui_on_effect_change(effect: str) -> tuple[float, float]:
131
+ """
132
+ Updates the speed and font size sliders to sensible defaults when the
133
+ animation effect is changed.
134
+ """
135
+ font_size = 1.5
136
+ if effect == "starwars":
137
+ font_size = 6.0 # Star Wars effect looks better with a larger font
138
+
139
+ speed = DEFAULT_SPEEDS.get(effect, 40.0)
140
+ return speed, font_size
141
+
142
+ # --- 3. GRADIO UI DEFINITION ---
143
+ # This section constructs the user interface using gr.Blocks.
144
+
145
+ with gr.Blocks(theme=gr.themes.Ocean(), title="CreditsPanel Demo") as demo:
146
+ gr.Markdown(
147
+ """
148
+ # Interactive CreditsPanel Demo
149
+ Use the sidebar controls to customize the `CreditsPanel` component in real-time.
150
+ """
151
+ )
152
+
153
+ with gr.Sidebar(position="right"):
154
+ gr.Markdown("### Effects Settings")
155
+ effect_radio = gr.Radio(
156
+ ["scroll", "starwars", "matrix"], label="Animation Effect", value="scroll",
157
+ info="Select the visual style for the credits."
158
+ )
159
+ speed_slider = gr.Slider(
160
+ minimum=5.0, maximum=100.0, step=1.0, value=DEFAULT_SPEEDS["scroll"],
161
+ label="Animation Speed (seconds)", info="Duration of one animation cycle."
162
+ )
163
+ font_size_slider = gr.Slider(
164
+ minimum=1.0, maximum=10.0, step=0.1, value=1.5,
165
+ label="Base Font Size (rem)", info="Controls the base font size."
166
+ )
167
+
168
+ gr.Markdown("### Intro Text")
169
+ intro_title_input = gr.Textbox(
170
+ label="Intro Title", value="Gradio", info="Main title for the intro sequence."
171
+ )
172
+ intro_subtitle_input = gr.Textbox(
173
+ label="Intro Subtitle", value="The best UI framework", info="Subtitle for the intro sequence."
174
+ )
175
+
176
+ gr.Markdown("### Layout & Visibility")
177
+ sidebar_position_radio = gr.Radio(
178
+ ["right", "bottom"], label="Sidebar Position", value="right",
179
+ info="Place the licenses sidebar on the right or bottom."
180
+ )
181
+ show_logo_checkbox = gr.Checkbox(label="Show Logo", value=True)
182
+ show_licenses_checkbox = gr.Checkbox(label="Show Licenses", value=True)
183
+
184
+ gr.Markdown("### Logo Customization")
185
+ logo_position_radio = gr.Radio(
186
+ ["left", "center", "right"], label="Logo Position", value="center"
187
+ )
188
+ logo_sizing_radio = gr.Radio(
189
+ ["stretch", "crop", "resize"], label="Logo Sizing", value="resize"
190
+ )
191
+ logo_width_input = gr.Textbox(label="Logo Width", value="200px")
192
+ logo_height_input = gr.Textbox(label="Logo Height", value="100px")
193
+
194
+ gr.Markdown("### Color Settings (Scroll Effect)")
195
+ scroll_background_color = gr.ColorPicker(label="Background Color", value="#000000")
196
+ scroll_title_color = gr.ColorPicker(label="Title Color", value="#FFFFFF")
197
+ scroll_name_color = gr.ColorPicker(label="Name Color", value="#FFFFFF")
198
+
199
+ # Instantiate the custom CreditsPanel component with default values
200
+ panel = CreditsPanel(
201
+ credits=credits_list,
202
+ licenses=license_paths,
203
+ effect="scroll",
204
+ height=500,
205
+ speed=DEFAULT_SPEEDS["scroll"],
206
+ base_font_size=1.5,
207
+ intro_title="Gradio",
208
+ intro_subtitle="The best UI framework",
209
+ sidebar_position="right",
210
+ logo_path="./assets/logo.webp",
211
+ show_logo=True,
212
+ show_licenses=True,
213
+ logo_position="center",
214
+ logo_sizing="resize",
215
+ logo_width="200px",
216
+ logo_height="100px",
217
+ scroll_background_color="#000000",
218
+ scroll_title_color="#FFFFFF",
219
+ scroll_name_color="#FFFFFF",
220
+ )
221
+
222
+ # List of all input components that should trigger a panel update
223
+ inputs = [
224
+ effect_radio, speed_slider, font_size_slider,
225
+ intro_title_input, intro_subtitle_input,
226
+ sidebar_position_radio, show_logo_checkbox, show_licenses_checkbox,
227
+ logo_position_radio, logo_sizing_radio, logo_width_input, logo_height_input,
228
+ scroll_background_color, scroll_title_color, scroll_name_color
229
+ ]
230
+
231
+ # --- 4. EVENT BINDING ---
232
+ # Connect the UI controls to the handler functions.
233
+
234
+ # Special event: changing the effect also updates speed and font size sliders
235
+ effect_radio.change(
236
+ fn=update_ui_on_effect_change,
237
+ inputs=effect_radio,
238
+ outputs=[speed_slider, font_size_slider]
239
+ )
240
+
241
+ # General event: any change in an input control updates the main panel
242
+ for input_component in inputs:
243
+ input_component.change(
244
+ fn=update_panel,
245
+ inputs=inputs,
246
+ outputs=panel
247
+ )
248
+
249
+ # --- 5. APP LAUNCH ---
250
+ if __name__ == "__main__":
251
+ setup_demo_files()
252
+ demo.launch()
253
+ ```
254
+
255
+ ## `CreditsPanel`
256
+
257
+ ### Initialization
258
+
259
+ <table>
260
+ <thead>
261
+ <tr>
262
+ <th align="left">name</th>
263
+ <th align="left" style="width: 25%;">type</th>
264
+ <th align="left">default</th>
265
+ <th align="left">description</th>
266
+ </tr>
267
+ </thead>
268
+ <tbody>
269
+ <tr>
270
+ <td align="left"><code>value</code></td>
271
+ <td align="left" style="width: 25%;">
272
+
273
+ ```python
274
+ Any
275
+ ```
276
+
277
+ </td>
278
+ <td align="left"><code>None</code></td>
279
+ <td align="left">None</td>
280
+ </tr>
281
+
282
+ <tr>
283
+ <td align="left"><code>credits</code></td>
284
+ <td align="left" style="width: 25%;">
285
+
286
+ ```python
287
+ typing.Union[
288
+ typing.List[typing.Dict[str, str]],
289
+ typing.Callable,
290
+ NoneType,
291
+ ][
292
+ typing.List[typing.Dict[str, str]][
293
+ typing.Dict[str, str][str, str]
294
+ ],
295
+ Callable,
296
+ None,
297
+ ]
298
+ ```
299
+
300
+ </td>
301
+ <td align="left"><code>None</code></td>
302
+ <td align="left">None</td>
303
+ </tr>
304
+
305
+ <tr>
306
+ <td align="left"><code>height</code></td>
307
+ <td align="left" style="width: 25%;">
308
+
309
+ ```python
310
+ int | str | None
311
+ ```
312
+
313
+ </td>
314
+ <td align="left"><code>None</code></td>
315
+ <td align="left">None</td>
316
+ </tr>
317
+
318
+ <tr>
319
+ <td align="left"><code>width</code></td>
320
+ <td align="left" style="width: 25%;">
321
+
322
+ ```python
323
+ int | str | None
324
+ ```
325
+
326
+ </td>
327
+ <td align="left"><code>None</code></td>
328
+ <td align="left">None</td>
329
+ </tr>
330
+
331
+ <tr>
332
+ <td align="left"><code>licenses</code></td>
333
+ <td align="left" style="width: 25%;">
334
+
335
+ ```python
336
+ typing.Optional[typing.Dict[str, str | pathlib.Path]][
337
+ typing.Dict[str, str | pathlib.Path][
338
+ str, str | pathlib.Path
339
+ ],
340
+ None,
341
+ ]
342
+ ```
343
+
344
+ </td>
345
+ <td align="left"><code>None</code></td>
346
+ <td align="left">None</td>
347
+ </tr>
348
+
349
+ <tr>
350
+ <td align="left"><code>effect</code></td>
351
+ <td align="left" style="width: 25%;">
352
+
353
+ ```python
354
+ "scroll" | "starwars" | "matrix"
355
+ ```
356
+
357
+ </td>
358
+ <td align="left"><code>"scroll"</code></td>
359
+ <td align="left">None</td>
360
+ </tr>
361
+
362
+ <tr>
363
+ <td align="left"><code>speed</code></td>
364
+ <td align="left" style="width: 25%;">
365
+
366
+ ```python
367
+ float
368
+ ```
369
+
370
+ </td>
371
+ <td align="left"><code>40.0</code></td>
372
+ <td align="left">None</td>
373
+ </tr>
374
+
375
+ <tr>
376
+ <td align="left"><code>base_font_size</code></td>
377
+ <td align="left" style="width: 25%;">
378
+
379
+ ```python
380
+ float
381
+ ```
382
+
383
+ </td>
384
+ <td align="left"><code>1.5</code></td>
385
+ <td align="left">None</td>
386
+ </tr>
387
+
388
+ <tr>
389
+ <td align="left"><code>intro_title</code></td>
390
+ <td align="left" style="width: 25%;">
391
+
392
+ ```python
393
+ str | None
394
+ ```
395
+
396
+ </td>
397
+ <td align="left"><code>None</code></td>
398
+ <td align="left">None</td>
399
+ </tr>
400
+
401
+ <tr>
402
+ <td align="left"><code>intro_subtitle</code></td>
403
+ <td align="left" style="width: 25%;">
404
+
405
+ ```python
406
+ str | None
407
+ ```
408
+
409
+ </td>
410
+ <td align="left"><code>None</code></td>
411
+ <td align="left">None</td>
412
+ </tr>
413
+
414
+ <tr>
415
+ <td align="left"><code>sidebar_position</code></td>
416
+ <td align="left" style="width: 25%;">
417
+
418
+ ```python
419
+ "right" | "bottom"
420
+ ```
421
+
422
+ </td>
423
+ <td align="left"><code>"right"</code></td>
424
+ <td align="left">None</td>
425
+ </tr>
426
+
427
+ <tr>
428
+ <td align="left"><code>logo_path</code></td>
429
+ <td align="left" style="width: 25%;">
430
+
431
+ ```python
432
+ str | pathlib.Path | None
433
+ ```
434
+
435
+ </td>
436
+ <td align="left"><code>None</code></td>
437
+ <td align="left">None</td>
438
+ </tr>
439
+
440
+ <tr>
441
+ <td align="left"><code>show_logo</code></td>
442
+ <td align="left" style="width: 25%;">
443
+
444
+ ```python
445
+ bool
446
+ ```
447
+
448
+ </td>
449
+ <td align="left"><code>True</code></td>
450
+ <td align="left">None</td>
451
+ </tr>
452
+
453
+ <tr>
454
+ <td align="left"><code>show_licenses</code></td>
455
+ <td align="left" style="width: 25%;">
456
+
457
+ ```python
458
+ bool
459
+ ```
460
+
461
+ </td>
462
+ <td align="left"><code>True</code></td>
463
+ <td align="left">None</td>
464
+ </tr>
465
+
466
+ <tr>
467
+ <td align="left"><code>logo_position</code></td>
468
+ <td align="left" style="width: 25%;">
469
+
470
+ ```python
471
+ "center" | "left" | "right"
472
+ ```
473
+
474
+ </td>
475
+ <td align="left"><code>"center"</code></td>
476
+ <td align="left">None</td>
477
+ </tr>
478
+
479
+ <tr>
480
+ <td align="left"><code>logo_sizing</code></td>
481
+ <td align="left" style="width: 25%;">
482
+
483
+ ```python
484
+ "stretch" | "crop" | "resize"
485
+ ```
486
+
487
+ </td>
488
+ <td align="left"><code>"resize"</code></td>
489
+ <td align="left">None</td>
490
+ </tr>
491
+
492
+ <tr>
493
+ <td align="left"><code>logo_width</code></td>
494
+ <td align="left" style="width: 25%;">
495
+
496
+ ```python
497
+ int | str | None
498
+ ```
499
+
500
+ </td>
501
+ <td align="left"><code>None</code></td>
502
+ <td align="left">None</td>
503
+ </tr>
504
+
505
+ <tr>
506
+ <td align="left"><code>logo_height</code></td>
507
+ <td align="left" style="width: 25%;">
508
+
509
+ ```python
510
+ int | str | None
511
+ ```
512
+
513
+ </td>
514
+ <td align="left"><code>None</code></td>
515
+ <td align="left">None</td>
516
+ </tr>
517
+
518
+ <tr>
519
+ <td align="left"><code>scroll_background_color</code></td>
520
+ <td align="left" style="width: 25%;">
521
+
522
+ ```python
523
+ str | None
524
+ ```
525
+
526
+ </td>
527
+ <td align="left"><code>None</code></td>
528
+ <td align="left">None</td>
529
+ </tr>
530
+
531
+ <tr>
532
+ <td align="left"><code>scroll_title_color</code></td>
533
+ <td align="left" style="width: 25%;">
534
+
535
+ ```python
536
+ str | None
537
+ ```
538
+
539
+ </td>
540
+ <td align="left"><code>None</code></td>
541
+ <td align="left">None</td>
542
+ </tr>
543
+
544
+ <tr>
545
+ <td align="left"><code>scroll_name_color</code></td>
546
+ <td align="left" style="width: 25%;">
547
+
548
+ ```python
549
+ str | None
550
+ ```
551
+
552
+ </td>
553
+ <td align="left"><code>None</code></td>
554
+ <td align="left">None</td>
555
+ </tr>
556
+
557
+ <tr>
558
+ <td align="left"><code>label</code></td>
559
+ <td align="left" style="width: 25%;">
560
+
561
+ ```python
562
+ str | gradio.i18n.I18nData | None
563
+ ```
564
+
565
+ </td>
566
+ <td align="left"><code>None</code></td>
567
+ <td align="left">None</td>
568
+ </tr>
569
+
570
+ <tr>
571
+ <td align="left"><code>every</code></td>
572
+ <td align="left" style="width: 25%;">
573
+
574
+ ```python
575
+ float | None
576
+ ```
577
+
578
+ </td>
579
+ <td align="left"><code>None</code></td>
580
+ <td align="left">None</td>
581
+ </tr>
582
+
583
+ <tr>
584
+ <td align="left"><code>inputs</code></td>
585
+ <td align="left" style="width: 25%;">
586
+
587
+ ```python
588
+ typing.Union[
589
+ gradio.components.base.Component,
590
+ typing.Sequence[gradio.components.base.Component],
591
+ set[gradio.components.base.Component],
592
+ NoneType,
593
+ ][
594
+ gradio.components.base.Component,
595
+ typing.Sequence[gradio.components.base.Component][
596
+ gradio.components.base.Component
597
+ ],
598
+ set[gradio.components.base.Component],
599
+ None,
600
+ ]
601
+ ```
602
+
603
+ </td>
604
+ <td align="left"><code>None</code></td>
605
+ <td align="left">None</td>
606
+ </tr>
607
+
608
+ <tr>
609
+ <td align="left"><code>show_label</code></td>
610
+ <td align="left" style="width: 25%;">
611
+
612
+ ```python
613
+ bool
614
+ ```
615
+
616
+ </td>
617
+ <td align="left"><code>False</code></td>
618
+ <td align="left">None</td>
619
+ </tr>
620
+
621
+ <tr>
622
+ <td align="left"><code>container</code></td>
623
+ <td align="left" style="width: 25%;">
624
+
625
+ ```python
626
+ bool
627
+ ```
628
+
629
+ </td>
630
+ <td align="left"><code>True</code></td>
631
+ <td align="left">None</td>
632
+ </tr>
633
+
634
+ <tr>
635
+ <td align="left"><code>scale</code></td>
636
+ <td align="left" style="width: 25%;">
637
+
638
+ ```python
639
+ int | None
640
+ ```
641
+
642
+ </td>
643
+ <td align="left"><code>None</code></td>
644
+ <td align="left">None</td>
645
+ </tr>
646
+
647
+ <tr>
648
+ <td align="left"><code>min_width</code></td>
649
+ <td align="left" style="width: 25%;">
650
+
651
+ ```python
652
+ int
653
+ ```
654
+
655
+ </td>
656
+ <td align="left"><code>160</code></td>
657
+ <td align="left">None</td>
658
+ </tr>
659
+
660
+ <tr>
661
+ <td align="left"><code>interactive</code></td>
662
+ <td align="left" style="width: 25%;">
663
+
664
+ ```python
665
+ bool | None
666
+ ```
667
+
668
+ </td>
669
+ <td align="left"><code>None</code></td>
670
+ <td align="left">None</td>
671
+ </tr>
672
+
673
+ <tr>
674
+ <td align="left"><code>visible</code></td>
675
+ <td align="left" style="width: 25%;">
676
+
677
+ ```python
678
+ bool
679
+ ```
680
+
681
+ </td>
682
+ <td align="left"><code>True</code></td>
683
+ <td align="left">None</td>
684
+ </tr>
685
+
686
+ <tr>
687
+ <td align="left"><code>elem_id</code></td>
688
+ <td align="left" style="width: 25%;">
689
+
690
+ ```python
691
+ str | None
692
+ ```
693
+
694
+ </td>
695
+ <td align="left"><code>None</code></td>
696
+ <td align="left">None</td>
697
+ </tr>
698
+
699
+ <tr>
700
+ <td align="left"><code>elem_classes</code></td>
701
+ <td align="left" style="width: 25%;">
702
+
703
+ ```python
704
+ list[str] | str | None
705
+ ```
706
+
707
+ </td>
708
+ <td align="left"><code>None</code></td>
709
+ <td align="left">None</td>
710
+ </tr>
711
+
712
+ <tr>
713
+ <td align="left"><code>render</code></td>
714
+ <td align="left" style="width: 25%;">
715
+
716
+ ```python
717
+ bool
718
+ ```
719
+
720
+ </td>
721
+ <td align="left"><code>True</code></td>
722
+ <td align="left">None</td>
723
+ </tr>
724
+
725
+ <tr>
726
+ <td align="left"><code>key</code></td>
727
+ <td align="left" style="width: 25%;">
728
+
729
+ ```python
730
+ int | str | tuple[int | str, Ellipsis] | None
731
+ ```
732
+
733
+ </td>
734
+ <td align="left"><code>None</code></td>
735
+ <td align="left">None</td>
736
+ </tr>
737
+
738
+ <tr>
739
+ <td align="left"><code>preserved_by_key</code></td>
740
+ <td align="left" style="width: 25%;">
741
+
742
+ ```python
743
+ list[str] | str | None
744
+ ```
745
+
746
+ </td>
747
+ <td align="left"><code>"value"</code></td>
748
+ <td align="left">None</td>
749
+ </tr>
750
+ </tbody></table>
751
+
752
+
753
+ ### Events
754
+
755
+ | name | description |
756
+ |:-----|:------------|
757
+ | `change` | Triggered when the value of the CreditsPanel changes either because of user input (e.g. a user types in a textbox) OR because of a function update (e.g. an image receives a value from the output of an event trigger). See `.input()` for a listener that is only triggered by user input. |
758
+
759
+
760
+
761
+ ### User function
762
+
763
+ The impact on the users predict function varies depending on whether the component is used as an input or output for an event (or both).
764
+
765
+ - When used as an Input, the component only impacts the input signature of the user function.
766
+ - When used as an output, the component only impacts the return signature of the user function.
767
+
768
+ The code snippet below is accurate in cases where the component is used as both an input and an output.
769
+
770
+ - **As output:** Is passed, dict[str, Any] | None: The input payload, returned unchanged.
771
+
772
+
773
+ ```python
774
+ def predict(
775
+ value: typing.Optional[typing.Dict[str, typing.Any]][
776
+ typing.Dict[str, typing.Any][str, Any], None
777
+ ]
778
+ ) -> Any:
779
+ return value
780
+ ```
781
+
src/assets/logo.webp ADDED
src/backend/gradio_creditspanel/__init__.py ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+
2
+ from .creditspanel import CreditsPanel
3
+
4
+ __all__ = ['CreditsPanel']
src/backend/gradio_creditspanel/creditspanel.py ADDED
@@ -0,0 +1,260 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+ from typing import Any, Dict, List, Literal, Callable, Sequence
3
+ from pathlib import Path
4
+ from gradio_client import handle_file
5
+ from gradio_client.utils import is_http_url_like
6
+ from gradio_client.documentation import document
7
+ from gradio.components import Component
8
+ from gradio.events import Events
9
+ from gradio.i18n import I18nData
10
+ import os
11
+
12
+ @document()
13
+ class CreditsPanel(Component):
14
+ """
15
+ A Gradio component for displaying credits with customizable visual effects, such as scrolling or Star Wars-style animations.
16
+ Supports displaying a logo, licenses, and configurable text styling.
17
+
18
+ Attributes:
19
+ EVENTS (list): Supported events for the component, currently only `change`.
20
+ """
21
+
22
+ EVENTS = [Events.change]
23
+
24
+ def __init__(
25
+ self,
26
+ value: Any = None,
27
+ credits: List[Dict[str, str]] | Callable | None = None,
28
+ *,
29
+ height: int | str | None = None,
30
+ width: int | str | None = None,
31
+ licenses: Dict[str, str | Path] | None = None,
32
+ effect: Literal["scroll", "starwars", "matrix"] = "scroll",
33
+ speed: float = 40.0,
34
+ base_font_size: float = 1.5,
35
+ intro_title: str | None = None,
36
+ intro_subtitle: str | None = None,
37
+ sidebar_position: Literal["right", "bottom"] = "right",
38
+ logo_path: str | Path | None = None,
39
+ show_logo: bool = True,
40
+ show_licenses: bool = True,
41
+ logo_position: Literal["center", "left", "right"] = "center",
42
+ logo_sizing: Literal["stretch", "crop", "resize"] = "resize",
43
+ logo_width: int | str | None = None,
44
+ logo_height: int | str | None = None,
45
+ scroll_background_color: str | None = None,
46
+ scroll_title_color: str | None = None,
47
+ scroll_name_color: str | None = None,
48
+ label: str | I18nData | None = None,
49
+ every: float | None = None,
50
+ inputs: Component | Sequence[Component] | set[Component] | None = None,
51
+ show_label: bool = False,
52
+ container: bool = True,
53
+ scale: int | None = None,
54
+ min_width: int = 160,
55
+ interactive: bool | None = None,
56
+ visible: bool = True,
57
+ elem_id: str | None = None,
58
+ elem_classes: list[str] | str | None = None,
59
+ render: bool = True,
60
+ key: int | str | tuple[int | str, ...] | None = None,
61
+ preserved_by_key: list[str] | str | None = "value",
62
+ ):
63
+ """
64
+ Initialize the CreditsPanel component.
65
+
66
+ Args:
67
+ value (Any, optional): Initial value for the component.
68
+ credits (List[Dict[str, str]] | Callable | None, optional): List of credits as dictionaries with 'title' and 'name' keys, or a callable that returns such a list.
69
+ height (int | str | None, optional): Height of the component (e.g., in pixels or CSS units).
70
+ width (int | str | None, optional): Width of the component (e.g., in pixels or CSS units).
71
+ licenses (Dict[str, str | Path] | None, optional): Dictionary mapping license names to file paths or content strings.
72
+ effect (Literal["scroll", "starwars", "matrix"], optional): Visual effect for credits display. Defaults to "scroll".
73
+ speed (float, optional): Animation speed in seconds. Defaults to 40.0.
74
+ base_font_size (float, optional): Base font size in rem for credits text. Defaults to 1.5.
75
+ intro_title (str | None, optional): Title for the intro section, if any.
76
+ intro_subtitle (str | None, optional): Subtitle for the intro section, if any.
77
+ sidebar_position (Literal["right", "bottom"], optional): Position of the licenses sidebar. Defaults to "right".
78
+ logo_path (str | Path | None, optional): Path or URL to the logo image.
79
+ show_logo (bool, optional): Whether to display the logo. Defaults to True.
80
+ show_licenses (bool, optional): Whether to display licenses. Defaults to True.
81
+ logo_position (Literal["center", "left", "right"], optional): Logo alignment. Defaults to "center".
82
+ logo_sizing (Literal["stretch", "crop", "resize"], optional): Logo sizing mode. Defaults to "resize".
83
+ logo_width (int | str | None, optional): Logo width (e.g., in pixels or CSS units).
84
+ logo_height (int | str | None, optional): Logo height (e.g., in pixels or CSS units).
85
+ scroll_background_color (str | None, optional): Background color for scroll effect.
86
+ scroll_title_color (str | None, optional): Color for credit titles.
87
+ scroll_name_color (str | None, optional): Color for credit names.
88
+ label (str | I18nData | None, optional): Component label.
89
+ every (float | None, optional): Interval for periodic updates.
90
+ inputs (Component | Sequence[Component] | set[Component] | None, optional): Input components for events.
91
+ show_label (bool, optional): Whether to show the label. Defaults to False.
92
+ container (bool, optional): Whether to render in a container. Defaults to True.
93
+ scale (int | None, optional): Scaling factor for the component.
94
+ min_width (int, optional): Minimum width in pixels. Defaults to 160.
95
+ interactive (bool | None, optional): Whether the component is interactive.
96
+ visible (bool, optional): Whether the component is visible. Defaults to True.
97
+ elem_id (str | None, optional): Custom HTML element ID.
98
+ elem_classes (list[str] | str | None, optional): CSS classes for the component.
99
+ render (bool, optional): Whether to render the component. Defaults to True.
100
+ key (int | str | tuple[int | str, ...] | None, optional): Component key for state preservation.
101
+ preserved_by_key (list[str] | str | None, optional): Properties preserved by key. Defaults to "value".
102
+ """
103
+ self.height = height
104
+ self.width = width
105
+ self.credits_data = credits if credits is not None else []
106
+ self.licenses_paths = licenses or {}
107
+ self.effect = effect
108
+ self.speed = speed
109
+ self.base_font_size = base_font_size
110
+ self.intro_title = intro_title
111
+ self.intro_subtitle = intro_subtitle
112
+ self.sidebar_position = sidebar_position
113
+ self.logo_path = logo_path
114
+ self.show_logo = show_logo
115
+ self.show_licenses = show_licenses
116
+ self.logo_position = logo_position
117
+ self.logo_sizing = logo_sizing
118
+ self.logo_width = logo_width
119
+ self.logo_height = logo_height
120
+ self.scroll_background_color = scroll_background_color
121
+ self.scroll_title_color = scroll_title_color
122
+ self.scroll_name_color = scroll_name_color
123
+ super().__init__(
124
+ label=label,
125
+ every=every,
126
+ inputs=inputs,
127
+ show_label=show_label,
128
+ container=container,
129
+ scale=scale,
130
+ min_width=min_width,
131
+ interactive=interactive,
132
+ elem_id=elem_id,
133
+ elem_classes=elem_classes,
134
+ render=render,
135
+ key=key,
136
+ visible=visible,
137
+ preserved_by_key=preserved_by_key,
138
+ value=value,
139
+ )
140
+
141
+ def _process_license_files(self) -> Dict[str, str]:
142
+ """
143
+ Process license files into a dictionary of name-content pairs.
144
+
145
+ Returns:
146
+ Dict[str, str]: Dictionary mapping license names to their content or error messages if loading fails.
147
+ """
148
+ processed = {}
149
+ for name, path in self.licenses_paths.items():
150
+ try:
151
+ with open(path, "r", encoding="utf-8") as f:
152
+ processed[name] = f.read()
153
+ except Exception as e:
154
+ processed[name] = f"Error loading license file '{path}':\n{e}"
155
+ return processed
156
+
157
+ def _process_logo_path(self) -> Dict[str, Any] | None:
158
+ """
159
+ Process the logo path, handling both local files and URLs.
160
+
161
+ Returns:
162
+ Dict[str, Any] | None: Dictionary with logo details (path, url, orig_name, mime_type) or None if no logo_path is provided or the file is not found.
163
+ """
164
+ if not self.logo_path:
165
+ return None
166
+ path = str(self.logo_path)
167
+ if is_http_url_like(path):
168
+ return {"path": None, "url": path, "orig_name": Path(path).name, "mime_type": None}
169
+ if os.path.exists(path):
170
+ return handle_file(path)
171
+ return None
172
+
173
+ def preprocess(self, payload: Dict[str, Any] | None) -> Dict[str, Any] | None:
174
+ """
175
+ Preprocess the input payload.
176
+
177
+ Args:
178
+ payload (Dict[str, Any] | None): Input data to preprocess.
179
+
180
+ Returns:
181
+ Dict[str, Any] | None: The input payload, returned unchanged.
182
+ """
183
+ return payload
184
+
185
+ def postprocess(self, value: Any) -> Dict[str, Any] | None:
186
+ """
187
+ Postprocess the component's value to prepare data for rendering.
188
+
189
+ Args:
190
+ value (Any): Input value, typically a list of credits.
191
+
192
+ Returns:
193
+ Dict[str, Any] | None: Dictionary containing processed credits, licenses, and configuration, or None if no credits or licenses are provided.
194
+ """
195
+ credits_source = value if isinstance(value, list) else self.credits_data
196
+ if not credits_source and not self.licenses_paths:
197
+ return None
198
+ return {
199
+ "credits": credits_source,
200
+ "licenses": self._process_license_files() if self.show_licenses else {},
201
+ "effect": self.effect,
202
+ "speed": self.speed,
203
+ "base_font_size": self.base_font_size,
204
+ "intro_title": self.intro_title,
205
+ "intro_subtitle": self.intro_subtitle,
206
+ "sidebar_position": self.sidebar_position,
207
+ "logo_path": self._process_logo_path(),
208
+ "show_logo": self.show_logo,
209
+ "show_licenses": self.show_licenses,
210
+ "logo_position": self.logo_position,
211
+ "logo_sizing": self.logo_sizing,
212
+ "logo_width": self.logo_width,
213
+ "logo_height": self.logo_height,
214
+ "scroll_background_color": self.scroll_background_color,
215
+ "scroll_title_color": self.scroll_title_color,
216
+ "scroll_name_color": self.scroll_name_color
217
+ }
218
+
219
+ def api_info(self) -> Dict[str, Any]:
220
+ """
221
+ Provide API information for the component.
222
+
223
+ Returns:
224
+ Dict[str, Any]: Dictionary indicating the component's data type ("object").
225
+ """
226
+ return {"type": "object"}
227
+
228
+ def example_payload(self) -> Any:
229
+ """
230
+ Provide an example payload for the component.
231
+
232
+ Returns:
233
+ Dict[str, Any]: Example data structure for the component's payload.
234
+ """
235
+ return {
236
+ "credits": [{"title": "Example", "name": "Credit"}],
237
+ "licenses": {},
238
+ "effect": "scroll",
239
+ "speed": 20,
240
+ "sidebar_position": "right",
241
+ "logo_path": None,
242
+ "show_logo": True,
243
+ "show_licenses": True,
244
+ "logo_position": "center",
245
+ "logo_sizing": "resize",
246
+ "logo_width": None,
247
+ "logo_height": None,
248
+ "scroll_background_color": None,
249
+ "scroll_title_color": None,
250
+ "scroll_name_color": None
251
+ }
252
+
253
+ def example_value(self) -> Any:
254
+ """
255
+ Provide an example value for the component.
256
+
257
+ Returns:
258
+ List[Dict[str, str]]: Example list of credits.
259
+ """
260
+ return [{"title": "Example", "name": "Credit"}]
src/backend/gradio_creditspanel/templates/component/index.js ADDED
The diff for this file is too large to render. See raw diff
 
src/backend/gradio_creditspanel/templates/component/style.css ADDED
@@ -0,0 +1 @@
 
 
1
+ .block.svelte-239wnu{position:relative;margin:0;box-shadow:var(--block-shadow);border-width:var(--block-border-width);border-color:var(--block-border-color);border-radius:var(--block-radius);background:var(--block-background-fill);width:100%;line-height:var(--line-sm)}.block.fullscreen.svelte-239wnu{border-radius:0}.auto-margin.svelte-239wnu{margin-left:auto;margin-right:auto}.block.border_focus.svelte-239wnu{border-color:var(--color-accent)}.block.border_contrast.svelte-239wnu{border-color:var(--body-text-color)}.padded.svelte-239wnu{padding:var(--block-padding)}.hidden.svelte-239wnu{display:none}.flex.svelte-239wnu{display:flex;flex-direction:column}.hide-container.svelte-239wnu:not(.fullscreen){margin:0;box-shadow:none;--block-border-width:0;background:transparent;padding:0;overflow:visible}.resize-handle.svelte-239wnu{position:absolute;bottom:0;right:0;width:10px;height:10px;fill:var(--block-border-color);cursor:nwse-resize}.fullscreen.svelte-239wnu{position:fixed;top:0;left:0;width:100vw;height:100vh;z-index:1000;overflow:auto}.animating.svelte-239wnu{animation:svelte-239wnu-pop-out .1s ease-out forwards}@keyframes svelte-239wnu-pop-out{0%{position:fixed;top:var(--start-top);left:var(--start-left);width:var(--start-width);height:var(--start-height);z-index:100}to{position:fixed;top:0vh;left:0vw;width:100vw;height:100vh;z-index:1000}}.placeholder.svelte-239wnu{border-radius:var(--block-radius);border-width:var(--block-border-width);border-color:var(--block-border-color);border-style:dashed}Tables */ table,tr,td,th{margin-top:var(--spacing-sm);margin-bottom:var(--spacing-sm);padding:var(--spacing-xl)}.md code,.md pre{background:none;font-family:var(--font-mono);font-size:var(--text-sm);text-align:left;white-space:pre;word-spacing:normal;word-break:normal;word-wrap:normal;line-height:1.5;-moz-tab-size:2;tab-size:2;-webkit-hyphens:none;hyphens:none}.md pre[class*=language-]::selection,.md pre[class*=language-] ::selection,.md code[class*=language-]::selection,.md code[class*=language-] ::selection{text-shadow:none;background:#b3d4fc}.md pre{padding:1em;margin:.5em 0;overflow:auto;position:relative;margin-top:var(--spacing-sm);margin-bottom:var(--spacing-sm);box-shadow:none;border:none;border-radius:var(--radius-md);background:var(--code-background-fill);padding:var(--spacing-xxl);font-family:var(--font-mono);text-shadow:none;border-radius:var(--radius-sm);white-space:nowrap;display:block;white-space:pre}.md :not(pre)>code{padding:.1em;border-radius:var(--radius-xs);white-space:normal;background:var(--code-background-fill);border:1px solid var(--panel-border-color);padding:var(--spacing-xxs) var(--spacing-xs)}.md .token.comment,.md .token.prolog,.md .token.doctype,.md .token.cdata{color:#708090}.md .token.punctuation{color:#999}.md .token.namespace{opacity:.7}.md .token.property,.md .token.tag,.md .token.boolean,.md .token.number,.md .token.constant,.md .token.symbol,.md .token.deleted{color:#905}.md .token.selector,.md .token.attr-name,.md .token.string,.md .token.char,.md .token.builtin,.md .token.inserted{color:#690}.md .token.atrule,.md .token.attr-value,.md .token.keyword{color:#07a}.md .token.function,.md .token.class-name{color:#dd4a68}.md .token.regex,.md .token.important,.md .token.variable{color:#e90}.md .token.important,.md .token.bold{font-weight:700}.md .token.italic{font-style:italic}.md .token.entity{cursor:help}.dark .md .token.comment,.dark .md .token.prolog,.dark .md .token.cdata{color:#5c6370}.dark .md .token.doctype,.dark .md .token.punctuation,.dark .md .token.entity{color:#abb2bf}.dark .md .token.attr-name,.dark .md .token.class-name,.dark .md .token.boolean,.dark .md .token.constant,.dark .md .token.number,.dark .md .token.atrule{color:#d19a66}.dark .md .token.keyword{color:#c678dd}.dark .md .token.property,.dark .md .token.tag,.dark .md .token.symbol,.dark .md .token.deleted,.dark .md .token.important{color:#e06c75}.dark .md .token.selector,.dark .md .token.string,.dark .md .token.char,.dark .md .token.builtin,.dark .md .token.inserted,.dark .md .token.regex,.dark .md .token.attr-value,.dark .md .token.attr-value>.token.punctuation{color:#98c379}.dark .md .token.variable,.dark .md .token.operator,.dark .md .token.function{color:#61afef}.dark .md .token.url{color:#56b6c2}span.svelte-1m32c2s div[class*=code_wrap]{position:relative}span.svelte-1m32c2s span.katex{font-size:var(--text-lg);direction:ltr}span.svelte-1m32c2s div[class*=code_wrap]>button{z-index:1;cursor:pointer;border-bottom-left-radius:var(--radius-sm);padding:var(--spacing-md);width:25px;height:25px;position:absolute;right:0}span.svelte-1m32c2s .check{opacity:0;z-index:var(--layer-top);transition:opacity .2s;background:var(--code-background-fill);color:var(--body-text-color);position:absolute;top:var(--size-1-5);left:var(--size-1-5)}span.svelte-1m32c2s p:not(:first-child){margin-top:var(--spacing-xxl)}span.svelte-1m32c2s .md-header-anchor{margin-left:-25px;padding-right:8px;line-height:1;color:var(--body-text-color-subdued);opacity:0}span.svelte-1m32c2s h1:hover .md-header-anchor,span.svelte-1m32c2s h2:hover .md-header-anchor,span.svelte-1m32c2s h3:hover .md-header-anchor,span.svelte-1m32c2s h4:hover .md-header-anchor,span.svelte-1m32c2s h5:hover .md-header-anchor,span.svelte-1m32c2s h6:hover .md-header-anchor{opacity:1}span.md.svelte-1m32c2s .md-header-anchor>svg{color:var(--body-text-color-subdued)}span.svelte-1m32c2s table{word-break:break-word}div.svelte-17qq50w>.md.prose{font-weight:var(--block-info-text-weight);font-size:var(--block-info-text-size);line-height:var(--line-sm)}div.svelte-17qq50w>.md.prose *{color:var(--block-info-text-color)}div.svelte-17qq50w{margin-bottom:var(--spacing-md)}span.has-info.svelte-zgrq3{margin-bottom:var(--spacing-xs)}span.svelte-zgrq3:not(.has-info){margin-bottom:var(--spacing-lg)}span.svelte-zgrq3{display:inline-block;position:relative;z-index:var(--layer-4);border:solid var(--block-title-border-width) var(--block-title-border-color);border-radius:var(--block-title-radius);background:var(--block-title-background-fill);padding:var(--block-title-padding);color:var(--block-title-text-color);font-weight:var(--block-title-text-weight);font-size:var(--block-title-text-size);line-height:var(--line-sm)}span[dir=rtl].svelte-zgrq3{display:block}.hide.svelte-zgrq3{margin:0;height:0}label.svelte-13ao5pu.svelte-13ao5pu{display:inline-flex;align-items:center;z-index:var(--layer-2);box-shadow:var(--block-label-shadow);border:var(--block-label-border-width) solid var(--block-label-border-color);border-top:none;border-left:none;border-radius:var(--block-label-radius);background:var(--block-label-background-fill);padding:var(--block-label-padding);pointer-events:none;color:var(--block-label-text-color);font-weight:var(--block-label-text-weight);font-size:var(--block-label-text-size);line-height:var(--line-sm)}.gr-group label.svelte-13ao5pu.svelte-13ao5pu{border-top-left-radius:0}label.float.svelte-13ao5pu.svelte-13ao5pu{position:absolute;top:var(--block-label-margin);left:var(--block-label-margin)}label.svelte-13ao5pu.svelte-13ao5pu:not(.float){position:static;margin-top:var(--block-label-margin);margin-left:var(--block-label-margin)}.hide.svelte-13ao5pu.svelte-13ao5pu{height:0}span.svelte-13ao5pu.svelte-13ao5pu{opacity:.8;margin-right:var(--size-2);width:calc(var(--block-label-text-size) - 1px);height:calc(var(--block-label-text-size) - 1px)}.hide-label.svelte-13ao5pu.svelte-13ao5pu{box-shadow:none;border-width:0;background:transparent;overflow:visible}label[dir=rtl].svelte-13ao5pu.svelte-13ao5pu{border:var(--block-label-border-width) solid var(--block-label-border-color);border-top:none;border-right:none;border-bottom-left-radius:var(--block-radius);border-bottom-right-radius:var(--block-label-radius);border-top-left-radius:var(--block-label-radius)}label[dir=rtl].svelte-13ao5pu span.svelte-13ao5pu{margin-left:var(--size-2);margin-right:0}button.svelte-qgco6m{display:flex;justify-content:center;align-items:center;gap:1px;z-index:var(--layer-2);border-radius:var(--radius-xs);color:var(--block-label-text-color);border:1px solid transparent;padding:var(--spacing-xxs)}button.svelte-qgco6m:hover{background-color:var(--background-fill-secondary)}button[disabled].svelte-qgco6m{opacity:.5;box-shadow:none}button[disabled].svelte-qgco6m:hover{cursor:not-allowed}.padded.svelte-qgco6m{background:var(--bg-color)}button.svelte-qgco6m:hover,button.highlight.svelte-qgco6m{cursor:pointer;color:var(--color-accent)}.padded.svelte-qgco6m:hover{color:var(--block-label-text-color)}span.svelte-qgco6m{padding:0 1px;font-size:10px}div.svelte-qgco6m{display:flex;align-items:center;justify-content:center;transition:filter .2s ease-in-out}.x-small.svelte-qgco6m{width:10px;height:10px}.small.svelte-qgco6m{width:14px;height:14px}.medium.svelte-qgco6m{width:20px;height:20px}.large.svelte-qgco6m{width:22px;height:22px}.pending.svelte-qgco6m{animation:svelte-qgco6m-flash .5s infinite}@keyframes svelte-qgco6m-flash{0%{opacity:.5}50%{opacity:1}to{opacity:.5}}.transparent.svelte-qgco6m{background:transparent;border:none;box-shadow:none}.empty.svelte-3w3rth{display:flex;justify-content:center;align-items:center;margin-top:calc(0px - var(--size-6));height:var(--size-full)}.icon.svelte-3w3rth{opacity:.5;height:var(--size-5);color:var(--body-text-color)}.small.svelte-3w3rth{min-height:calc(var(--size-32) - 20px)}.large.svelte-3w3rth{min-height:calc(var(--size-64) - 20px)}.unpadded_box.svelte-3w3rth{margin-top:0}.small_parent.svelte-3w3rth{min-height:100%!important}.dropdown-arrow.svelte-145leq6,.dropdown-arrow.svelte-ihhdbf{fill:currentColor}.circle.svelte-ihhdbf{fill:currentColor;opacity:.1}svg.svelte-pb9pol{animation:svelte-pb9pol-spin 1.5s linear infinite}@keyframes svelte-pb9pol-spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}h2.svelte-1xg7h5n{font-size:var(--text-xl)!important}p.svelte-1xg7h5n,h2.svelte-1xg7h5n{white-space:pre-line}.wrap.svelte-1xg7h5n{display:flex;flex-direction:column;justify-content:center;align-items:center;min-height:var(--size-60);color:var(--block-label-text-color);line-height:var(--line-md);height:100%;padding-top:var(--size-3);text-align:center;margin:auto var(--spacing-lg)}.or.svelte-1xg7h5n{color:var(--body-text-color-subdued);display:flex}.icon-wrap.svelte-1xg7h5n{width:30px;margin-bottom:var(--spacing-lg)}@media (--screen-md){.wrap.svelte-1xg7h5n{font-size:var(--text-lg)}}.hovered.svelte-1xg7h5n{color:var(--color-accent)}div.svelte-q32hvf{border-top:1px solid transparent;display:flex;max-height:100%;justify-content:center;align-items:center;gap:var(--spacing-sm);height:auto;align-items:flex-end;color:var(--block-label-text-color);flex-shrink:0}.show_border.svelte-q32hvf{border-top:1px solid var(--block-border-color);margin-top:var(--spacing-xxl);box-shadow:var(--shadow-drop)}.source-selection.svelte-15ls1gu{display:flex;align-items:center;justify-content:center;border-top:1px solid var(--border-color-primary);width:100%;margin-left:auto;margin-right:auto;height:var(--size-10)}.icon.svelte-15ls1gu{width:22px;height:22px;margin:var(--spacing-lg) var(--spacing-xs);padding:var(--spacing-xs);color:var(--neutral-400);border-radius:var(--radius-md)}.selected.svelte-15ls1gu{color:var(--color-accent)}.icon.svelte-15ls1gu:hover,.icon.svelte-15ls1gu:focus{color:var(--color-accent)}.icon-button-wrapper.svelte-1h0hs6p{display:flex;flex-direction:row;align-items:center;justify-content:center;z-index:var(--layer-2);gap:var(--spacing-sm);box-shadow:var(--shadow-drop);border:1px solid var(--border-color-primary);background:var(--block-background-fill);padding:var(--spacing-xxs)}.icon-button-wrapper.hide-top-corner.svelte-1h0hs6p{border-top:none;border-right:none;border-radius:var(--block-label-right-radius)}.icon-button-wrapper.display-top-corner.svelte-1h0hs6p{border-radius:var(--radius-sm) 0 0 var(--radius-sm);top:var(--spacing-sm);right:-1px}.icon-button-wrapper.svelte-1h0hs6p:not(.top-panel){border:1px solid var(--border-color-primary);border-radius:var(--radius-sm)}.top-panel.svelte-1h0hs6p{position:absolute;top:var(--block-label-margin);right:var(--block-label-margin);margin:0}.icon-button-wrapper.svelte-1h0hs6p button{margin:var(--spacing-xxs);border-radius:var(--radius-xs);position:relative}.icon-button-wrapper.svelte-1h0hs6p a.download-link:not(:last-child),.icon-button-wrapper.svelte-1h0hs6p button:not(:last-child){margin-right:var(--spacing-xxs)}.icon-button-wrapper.svelte-1h0hs6p a.download-link:not(:last-child):not(.no-border *):after,.icon-button-wrapper.svelte-1h0hs6p button:not(:last-child):not(.no-border *):after{content:"";position:absolute;right:-4.5px;top:15%;height:70%;width:1px;background-color:var(--border-color-primary)}.icon-button-wrapper.svelte-1h0hs6p>*{height:100%}svg.svelte-43sxxs.svelte-43sxxs{width:var(--size-20);height:var(--size-20)}svg.svelte-43sxxs path.svelte-43sxxs{fill:var(--loader-color)}div.svelte-43sxxs.svelte-43sxxs{z-index:var(--layer-2)}.margin.svelte-43sxxs.svelte-43sxxs{margin:var(--size-4)}.wrap.svelte-17v219f.svelte-17v219f{display:flex;flex-direction:column;justify-content:center;align-items:center;z-index:var(--layer-2);transition:opacity .1s ease-in-out;border-radius:var(--block-radius);background:var(--block-background-fill);padding:0 var(--size-6);max-height:var(--size-screen-h);overflow:hidden}.wrap.center.svelte-17v219f.svelte-17v219f{top:0;right:0;left:0}.wrap.default.svelte-17v219f.svelte-17v219f{top:0;right:0;bottom:0;left:0}.hide.svelte-17v219f.svelte-17v219f{opacity:0;pointer-events:none}.generating.svelte-17v219f.svelte-17v219f{animation:svelte-17v219f-pulseStart 1s cubic-bezier(.4,0,.6,1),svelte-17v219f-pulse 2s cubic-bezier(.4,0,.6,1) 1s infinite;border:2px solid var(--color-accent);background:transparent;z-index:var(--layer-1);pointer-events:none}.translucent.svelte-17v219f.svelte-17v219f{background:none}@keyframes svelte-17v219f-pulseStart{0%{opacity:0}to{opacity:1}}@keyframes svelte-17v219f-pulse{0%,to{opacity:1}50%{opacity:.5}}.loading.svelte-17v219f.svelte-17v219f{z-index:var(--layer-2);color:var(--body-text-color)}.eta-bar.svelte-17v219f.svelte-17v219f{position:absolute;top:0;right:0;bottom:0;left:0;transform-origin:left;opacity:.8;z-index:var(--layer-1);transition:10ms;background:var(--background-fill-secondary)}.progress-bar-wrap.svelte-17v219f.svelte-17v219f{border:1px solid var(--border-color-primary);background:var(--background-fill-primary);width:55.5%;height:var(--size-4)}.progress-bar.svelte-17v219f.svelte-17v219f{transform-origin:left;background-color:var(--loader-color);width:var(--size-full);height:var(--size-full)}.progress-level.svelte-17v219f.svelte-17v219f{display:flex;flex-direction:column;align-items:center;gap:1;z-index:var(--layer-2);width:var(--size-full)}.progress-level-inner.svelte-17v219f.svelte-17v219f{margin:var(--size-2) auto;color:var(--body-text-color);font-size:var(--text-sm);font-family:var(--font-mono)}.meta-text.svelte-17v219f.svelte-17v219f{position:absolute;bottom:0;right:0;z-index:var(--layer-2);padding:var(--size-1) var(--size-2);font-size:var(--text-sm);font-family:var(--font-mono)}.meta-text-center.svelte-17v219f.svelte-17v219f{display:flex;position:absolute;top:0;right:0;justify-content:center;align-items:center;transform:translateY(var(--size-6));z-index:var(--layer-2);padding:var(--size-1) var(--size-2);font-size:var(--text-sm);font-family:var(--font-mono);text-align:center}.error.svelte-17v219f.svelte-17v219f{box-shadow:var(--shadow-drop);border:solid 1px var(--error-border-color);border-radius:var(--radius-full);background:var(--error-background-fill);padding-right:var(--size-4);padding-left:var(--size-4);color:var(--error-text-color);font-weight:var(--weight-semibold);font-size:var(--text-lg);line-height:var(--line-lg);font-family:var(--font)}.minimal.svelte-17v219f.svelte-17v219f{pointer-events:none}.minimal.svelte-17v219f .progress-text.svelte-17v219f{background:var(--block-background-fill)}.border.svelte-17v219f.svelte-17v219f{border:1px solid var(--border-color-primary)}.clear-status.svelte-17v219f.svelte-17v219f{position:absolute;display:flex;top:var(--size-2);right:var(--size-2);justify-content:flex-end;gap:var(--spacing-sm);z-index:var(--layer-1)}.toast-body.svelte-syezpc{display:flex;position:relative;right:0;left:0;align-items:center;margin:var(--size-6) var(--size-4);margin:auto;border-radius:var(--container-radius);overflow:hidden;pointer-events:auto}.toast-body.error.svelte-syezpc{border:1px solid var(--color-red-700);background:var(--color-red-50)}.dark .toast-body.error.svelte-syezpc{border:1px solid var(--color-red-500);background-color:var(--color-grey-950)}.toast-body.warning.svelte-syezpc{border:1px solid var(--color-yellow-700);background:var(--color-yellow-50)}.dark .toast-body.warning.svelte-syezpc{border:1px solid var(--color-yellow-500);background-color:var(--color-grey-950)}.toast-body.info.svelte-syezpc{border:1px solid var(--color-grey-700);background:var(--color-grey-50)}.dark .toast-body.info.svelte-syezpc{border:1px solid var(--color-grey-500);background-color:var(--color-grey-950)}.toast-body.success.svelte-syezpc{border:1px solid var(--color-green-700);background:var(--color-green-50)}.dark .toast-body.success.svelte-syezpc{border:1px solid var(--color-green-500);background-color:var(--color-grey-950)}.toast-title.svelte-syezpc{display:flex;align-items:center;font-weight:var(--weight-bold);font-size:var(--text-lg);line-height:var(--line-sm)}.toast-title.error.svelte-syezpc{color:var(--color-red-700)}.dark .toast-title.error.svelte-syezpc{color:var(--color-red-50)}.toast-title.warning.svelte-syezpc{color:var(--color-yellow-700)}.dark .toast-title.warning.svelte-syezpc{color:var(--color-yellow-50)}.toast-title.info.svelte-syezpc{color:var(--color-grey-700)}.dark .toast-title.info.svelte-syezpc{color:var(--color-grey-50)}.toast-title.success.svelte-syezpc{color:var(--color-green-700)}.dark .toast-title.success.svelte-syezpc{color:var(--color-green-50)}.toast-close.svelte-syezpc{margin:0 var(--size-3);border-radius:var(--size-3);padding:0px var(--size-1-5);font-size:var(--size-5);line-height:var(--size-5)}.toast-close.error.svelte-syezpc{color:var(--color-red-700)}.dark .toast-close.error.svelte-syezpc{color:var(--color-red-500)}.toast-close.warning.svelte-syezpc{color:var(--color-yellow-700)}.dark .toast-close.warning.svelte-syezpc{color:var(--color-yellow-500)}.toast-close.info.svelte-syezpc{color:var(--color-grey-700)}.dark .toast-close.info.svelte-syezpc{color:var(--color-grey-500)}.toast-close.success.svelte-syezpc{color:var(--color-green-700)}.dark .toast-close.success.svelte-syezpc{color:var(--color-green-500)}.toast-text.svelte-syezpc{font-size:var(--text-lg);word-wrap:break-word;overflow-wrap:break-word;word-break:break-word}.toast-text.error.svelte-syezpc{color:var(--color-red-700)}.dark .toast-text.error.svelte-syezpc{color:var(--color-red-50)}.toast-text.warning.svelte-syezpc{color:var(--color-yellow-700)}.dark .toast-text.warning.svelte-syezpc{color:var(--color-yellow-50)}.toast-text.info.svelte-syezpc{color:var(--color-grey-700)}.dark .toast-text.info.svelte-syezpc{color:var(--color-grey-50)}.toast-text.success.svelte-syezpc{color:var(--color-green-700)}.dark .toast-text.success.svelte-syezpc{color:var(--color-green-50)}.toast-details.svelte-syezpc{margin:var(--size-3) var(--size-3) var(--size-3) 0;width:100%}.toast-icon.svelte-syezpc{display:flex;position:absolute;position:relative;flex-shrink:0;justify-content:center;align-items:center;margin:var(--size-2);border-radius:var(--radius-full);padding:var(--size-1);padding-left:calc(var(--size-1) - 1px);width:35px;height:35px}.toast-icon.error.svelte-syezpc{color:var(--color-red-700)}.dark .toast-icon.error.svelte-syezpc{color:var(--color-red-500)}.toast-icon.warning.svelte-syezpc{color:var(--color-yellow-700)}.dark .toast-icon.warning.svelte-syezpc{color:var(--color-yellow-500)}.toast-icon.info.svelte-syezpc{color:var(--color-grey-700)}.dark .toast-icon.info.svelte-syezpc{color:var(--color-grey-500)}.toast-icon.success.svelte-syezpc{color:var(--color-green-700)}.dark .toast-icon.success.svelte-syezpc{color:var(--color-green-500)}@keyframes svelte-syezpc-countdown{0%{transform:scaleX(1)}to{transform:scaleX(0)}}.timer.svelte-syezpc{position:absolute;bottom:0;left:0;transform-origin:0 0;animation:svelte-syezpc-countdown 10s linear forwards;width:100%;height:var(--size-1)}.timer.error.svelte-syezpc{background:var(--color-red-700)}.dark .timer.error.svelte-syezpc{background:var(--color-red-500)}.timer.warning.svelte-syezpc{background:var(--color-yellow-700)}.dark .timer.warning.svelte-syezpc{background:var(--color-yellow-500)}.timer.info.svelte-syezpc{background:var(--color-grey-700)}.dark .timer.info.svelte-syezpc{background:var(--color-grey-500)}.timer.success.svelte-syezpc{background:var(--color-green-700)}.dark .timer.success.svelte-syezpc{background:var(--color-green-500)}.hidden.svelte-syezpc{display:none}.toast-text.svelte-syezpc a{text-decoration:underline}.toast-wrap.svelte-gatr8h{display:flex;position:fixed;top:var(--size-4);right:var(--size-4);flex-direction:column;align-items:end;gap:var(--size-2);z-index:var(--layer-top);width:calc(100% - var(--size-8))}@media (--screen-sm){.toast-wrap.svelte-gatr8h{width:calc(var(--size-96) + var(--size-10))}}.streaming-bar.svelte-ga0jj6{position:absolute;bottom:0;left:0;right:0;height:4px;background-color:var(--primary-600);animation:svelte-ga0jj6-countdown linear forwards;z-index:1}@keyframes svelte-ga0jj6-countdown{0%{transform:translate(0)}to{transform:translate(-100%)}}.wrapper.svelte-141h5y1.svelte-141h5y1{width:100%;height:100%;overflow:hidden;position:relative;font-family:sans-serif}.credit.intro-block.svelte-141h5y1.svelte-141h5y1{margin-bottom:5rem;text-align:center}.credits-container.svelte-141h5y1.svelte-141h5y1{position:absolute;bottom:0;transform:translateY(100%);width:100%;text-align:center;animation:svelte-141h5y1-scroll var(--animation-duration) linear infinite}.credit.svelte-141h5y1.svelte-141h5y1{margin-bottom:2rem}.credit.svelte-141h5y1 h2.svelte-141h5y1,.credit.svelte-141h5y1 p.svelte-141h5y1{margin:.5rem 0;font-family:sans-serif}@keyframes svelte-141h5y1-scroll{0%{transform:translateY(100%)}to{transform:translateY(-100%)}}.viewport.svelte-1gy01d0{width:100%;height:100%;position:relative;overflow:hidden;perspective:400px;-webkit-mask-image:linear-gradient(to bottom,black 60%,transparent 100%);mask-image:linear-gradient(to bottom,black 60%,transparent 100%);font-family:Droid Sans,sans-serif;font-weight:700}.stars.svelte-1gy01d0{position:absolute;top:0;left:0;width:1px;height:1px;background:transparent;z-index:0;animation:svelte-1gy01d0-twinkle 10s linear infinite}.stars.small.svelte-1gy01d0{animation-duration:10s}.stars.medium.svelte-1gy01d0{animation-duration:15s}.stars.large.svelte-1gy01d0{animation-duration:20s}@keyframes svelte-1gy01d0-twinkle{0%{opacity:.6}50%{opacity:1}to{opacity:.6}}.crawl.svelte-1gy01d0{position:absolute;width:100%;bottom:0;transform-origin:50% 100%;animation:svelte-1gy01d0-crawl-animation var(--animation-duration) linear infinite;z-index:1;text-align:center}@keyframes svelte-1gy01d0-crawl-animation{0%{transform:rotateX(60deg) translateY(100%) translateZ(100px);opacity:1}to{transform:rotateX(60deg) translateY(-150%) translateZ(-1200px);opacity:1}}.credit.intro-block.svelte-1gy01d0{margin-bottom:5rem}.credit.svelte-1gy01d0{margin-bottom:2rem}h2.svelte-1gy01d0,p.svelte-1gy01d0{margin:.5rem 0;padding:0;white-space:nowrap}.matrix-container.svelte-8jsw80{width:100%;height:100%;position:relative;overflow:hidden}canvas.svelte-8jsw80{display:block;position:absolute;top:0;left:0;width:100%;height:100%;z-index:1}.credits-scroll-overlay.svelte-8jsw80{position:absolute;top:0;left:0;width:100%;height:100%;z-index:2;color:#fff;font-family:monospace;text-align:center;-webkit-mask-image:linear-gradient(transparent,black 20%,black 80%,transparent);mask-image:linear-gradient(transparent,black 20%,black 80%,transparent)}.credits-content.svelte-8jsw80{position:absolute;width:100%;bottom:0;transform:translateY(100%);animation:svelte-8jsw80-scroll-from-bottom var(--animation-duration) linear infinite}@keyframes svelte-8jsw80-scroll-from-bottom{0%{transform:translateY(100%)}to{transform:translateY(-100%)}}.credit-block.intro-block.svelte-8jsw80{margin-bottom:5rem}.credit-block.svelte-8jsw80{margin-bottom:2.5em}.title.svelte-8jsw80{color:#0f0;text-transform:uppercase;opacity:.8}.name.svelte-8jsw80{font-weight:700;color:#5f5;text-shadow:0 0 5px #0F0}.unstyled-link.svelte-151nsdd{all:unset;cursor:pointer}img.svelte-kxeri3{object-fit:cover}.image-container.svelte-x2tujq.svelte-x2tujq{height:100%;position:relative;min-width:var(--size-20)}.image-container.svelte-x2tujq button.svelte-x2tujq{width:var(--size-full);height:var(--size-full);border-radius:var(--radius-lg);display:flex;align-items:center;justify-content:center}.image-frame.svelte-x2tujq img{width:var(--size-full);height:var(--size-full);object-fit:scale-down}.selectable.svelte-x2tujq.svelte-x2tujq{cursor:crosshair}.fullscreen-controls svg{position:relative;top:0}.image-container:fullscreen{background-color:#000;display:flex;justify-content:center;align-items:center}.image-container:fullscreen img{max-width:90vw;max-height:90vh;object-fit:scale-down}.image-frame.svelte-x2tujq.svelte-x2tujq{width:auto;height:100%;display:flex;align-items:center;justify-content:center}.block{border:none!important;box-shadow:none!important;border-style:none!important}.outer-logo-wrapper.svelte-1ewbjj5.svelte-1ewbjj5,.outer-credits-wrapper.svelte-1ewbjj5.svelte-1ewbjj5{display:flex;flex-direction:column;width:100%;border:none}.logo-panel.svelte-1ewbjj5.svelte-1ewbjj5{background:var(--background-fill-primary);border:none;border-bottom:1px solid var(--border-color-primary);display:flex!important;align-items:center;justify-content:var(--logo-justify, center);width:100%}.credits-panel-wrapper.svelte-1ewbjj5.svelte-1ewbjj5{display:flex;flex-direction:var(--panel-direction, row);min-height:var(--size-full, 500px);width:100%;background:var(--background-fill-primary);border:1px solid var(--border-color-primary);border-radius:var(--radius-lg);overflow:hidden}.main-credits-panel.svelte-1ewbjj5.svelte-1ewbjj5{flex-grow:1;flex-shrink:1;min-width:200px;background:#000;overflow:hidden;position:relative}.licenses-sidebar.svelte-1ewbjj5.svelte-1ewbjj5{width:var(--sidebar-width, 400px);max-width:100%;max-height:var(--sidebar-max-height, none);flex-shrink:1;flex-grow:0;background:var(--background-fill-secondary);overflow-y:auto;border-left:var(--border-left, 1px solid var(--border-color-primary));border-top:var(--border-top, none)}.licenses-sidebar.svelte-1ewbjj5 h3.svelte-1ewbjj5{margin:var(--spacing-lg);font-size:var(--text-lg)}.licenses-sidebar.svelte-1ewbjj5 li.svelte-1ewbjj5{padding:0;margin:0;cursor:default;border-bottom:1px solid var(--border-color-primary)}.licenses-sidebar.svelte-1ewbjj5 li button.svelte-1ewbjj5{background:none;border:none;font:inherit;color:inherit;text-align:left;width:100%;cursor:pointer;padding:var(--spacing-md) var(--spacing-lg);transition:background-color .2s}.licenses-sidebar.svelte-1ewbjj5 li button.svelte-1ewbjj5:hover{background-color:var(--background-fill-primary)}.licenses-sidebar.svelte-1ewbjj5 li button.svelte-1ewbjj5:focus-visible{outline:2px solid var(--color-accent);outline-offset:-2px}.licenses-sidebar.svelte-1ewbjj5 li button.selected.svelte-1ewbjj5{background-color:var(--color-accent);color:#fff;font-weight:700}.license-display.svelte-1ewbjj5.svelte-1ewbjj5{padding:var(--spacing-lg);overflow-y:auto;flex-grow:1;border-top:1px solid var(--border-color-primary);background:var(--background-fill-primary)}.license-display.svelte-1ewbjj5 h4.svelte-1ewbjj5{margin-top:0}.license-display.svelte-1ewbjj5 pre.svelte-1ewbjj5{white-space:pre-wrap;word-break:break-word;font-size:var(--text-sm);color:var(--body-text-color-subdued)}
src/backend/gradio_creditspanel/templates/example/index.js ADDED
@@ -0,0 +1,97 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const {
2
+ HtmlTagHydration: r,
3
+ SvelteComponent: d,
4
+ attr: u,
5
+ children: o,
6
+ claim_element: h,
7
+ claim_html_tag: m,
8
+ detach: c,
9
+ element: g,
10
+ init: v,
11
+ insert_hydration: y,
12
+ noop: _,
13
+ safe_not_equal: b,
14
+ toggle_class: i
15
+ } = window.__gradio__svelte__internal;
16
+ function q(n) {
17
+ let e, a;
18
+ return {
19
+ c() {
20
+ e = g("div"), a = new r(!1), this.h();
21
+ },
22
+ l(l) {
23
+ e = h(l, "DIV", { class: !0 });
24
+ var t = o(e);
25
+ a = m(t, !1), t.forEach(c), this.h();
26
+ },
27
+ h() {
28
+ a.a = null, u(e, "class", "prose svelte-180qqaf"), i(
29
+ e,
30
+ "table",
31
+ /*type*/
32
+ n[1] === "table"
33
+ ), i(
34
+ e,
35
+ "gallery",
36
+ /*type*/
37
+ n[1] === "gallery"
38
+ ), i(
39
+ e,
40
+ "selected",
41
+ /*selected*/
42
+ n[2]
43
+ );
44
+ },
45
+ m(l, t) {
46
+ y(l, e, t), a.m(
47
+ /*value*/
48
+ n[0],
49
+ e
50
+ );
51
+ },
52
+ p(l, [t]) {
53
+ t & /*value*/
54
+ 1 && a.p(
55
+ /*value*/
56
+ l[0]
57
+ ), t & /*type*/
58
+ 2 && i(
59
+ e,
60
+ "table",
61
+ /*type*/
62
+ l[1] === "table"
63
+ ), t & /*type*/
64
+ 2 && i(
65
+ e,
66
+ "gallery",
67
+ /*type*/
68
+ l[1] === "gallery"
69
+ ), t & /*selected*/
70
+ 4 && i(
71
+ e,
72
+ "selected",
73
+ /*selected*/
74
+ l[2]
75
+ );
76
+ },
77
+ i: _,
78
+ o: _,
79
+ d(l) {
80
+ l && c(e);
81
+ }
82
+ };
83
+ }
84
+ function w(n, e, a) {
85
+ let { value: l } = e, { type: t } = e, { selected: f = !1 } = e;
86
+ return n.$$set = (s) => {
87
+ "value" in s && a(0, l = s.value), "type" in s && a(1, t = s.type), "selected" in s && a(2, f = s.selected);
88
+ }, [l, t, f];
89
+ }
90
+ class E extends d {
91
+ constructor(e) {
92
+ super(), v(this, e, w, q, b, { value: 0, type: 1, selected: 2 });
93
+ }
94
+ }
95
+ export {
96
+ E as default
97
+ };
src/backend/gradio_creditspanel/templates/example/style.css ADDED
@@ -0,0 +1 @@
 
 
1
+ .gallery.svelte-180qqaf{padding:var(--size-2)}
src/demo/__init__.py ADDED
File without changes
src/demo/app copy.py ADDED
@@ -0,0 +1,238 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ from gradio_creditspanel import CreditsPanel
3
+ import os
4
+
5
+ # Create dummy license files
6
+ os.makedirs("LICENSES", exist_ok=True)
7
+ if not os.path.exists("LICENSES/Apache.txt"):
8
+ with open("LICENSES/Apache.txt", "w") as f:
9
+ f.write("Apache License\nVersion 2.0, January 2004\nhttp://www.apache.org/licenses/...")
10
+ if not os.path.exists("LICENSES/MIT.txt"):
11
+ with open("LICENSES/MIT.txt", "w") as f:
12
+ f.write("MIT License\nCopyright (c) 2025 Author\nPermission is hereby granted...")
13
+
14
+ # Create assets directory for logo
15
+ os.makedirs("assets", exist_ok=True)
16
+ if not os.path.exists("./assets/logo.webp"):
17
+ print("Warning: ./assets/logo.webp not found. Creating placeholder. Replace with a valid WebP image.")
18
+ with open("./assets/logo.webp", "w") as f:
19
+ f.write("Placeholder WebP logo")
20
+ else:
21
+ print("Logo found at ./assets/logo.webp")
22
+
23
+ credits_list = [
24
+ {"title": "Project Manager", "name": "Emma Thompson"},
25
+ {"title": "Lead Developer", "name": "John Doe"},
26
+ {"title": "Senior Backend Engineer", "name": "Michael Chen"},
27
+ {"title": "Frontend Developer", "name": "Sarah Johnson"},
28
+ {"title": "UI/UX Designer", "name": "Jane Smith"},
29
+ {"title": "Database Architect", "name": "Alex Ray"},
30
+ {"title": "DevOps Engineer", "name": "Liam Patel"},
31
+ {"title": "Quality Assurance Lead", "name": "Sam Wilson"},
32
+ {"title": "Test Automation Engineer", "name": "Olivia Brown"},
33
+ {"title": "Security Analyst", "name": "David Kim"},
34
+ {"title": "Data Scientist", "name": "Sophie Martinez"},
35
+ {"title": "Machine Learning Engineer", "name": "Ethan Lee"},
36
+ {"title": "API Developer", "name": "Isabella Garcia"},
37
+ {"title": "Technical Writer", "name": "Noah Davis"},
38
+ {"title": "Scrum Master", "name": "Ava Rodriguez"},
39
+ {"title": "Cloud Infrastructure Engineer", "name": "Lucas Nguyen"},
40
+ {"title": "Mobile Developer", "name": "Mia Hernandez"},
41
+ {"title": "Performance Engineer", "name": "James Taylor"},
42
+ {"title": "Component Concept", "name": "Your Name"},
43
+ {"title": "Support Engineer", "name": "Charlotte Moore"}
44
+ ]
45
+
46
+ license_paths = {
47
+ "Gradio Framework": "./LICENSES/Apache.txt",
48
+ "This Component": "./LICENSES/MIT.txt"
49
+ }
50
+
51
+ DEFAULT_SPEEDS = {
52
+ "scroll": 40.0,
53
+ "starwars": 80.0,
54
+ "matrix": 40.0
55
+ }
56
+
57
+ def update_panel(
58
+ effect: str,
59
+ speed: float,
60
+ sidebar_position: str,
61
+ show_logo: bool,
62
+ show_licenses: bool,
63
+ logo_position: str,
64
+ logo_sizing: str,
65
+ logo_width: int | str | None,
66
+ logo_height: int | str | None,
67
+ scroll_background_color: str | None,
68
+ scroll_title_color: str | None,
69
+ scroll_name_color: str | None
70
+ ):
71
+ print(f"Updating panel: effect={effect}, speed={speed}, sidebar_position={sidebar_position}, show_logo={show_logo}, show_licenses={show_licenses}, logo_position={logo_position}, logo_sizing={logo_sizing}, logo_width={logo_width}, logo_height={logo_height}")
72
+ return gr.update(
73
+ visible=True,
74
+ effect=effect,
75
+ speed=speed,
76
+ sidebar_position=sidebar_position,
77
+ show_logo=show_logo,
78
+ show_licenses=show_licenses,
79
+ logo_position=logo_position,
80
+ logo_sizing=logo_sizing,
81
+ logo_width=logo_width,
82
+ logo_height=logo_height,
83
+ scroll_background_color=scroll_background_color,
84
+ scroll_title_color=scroll_title_color,
85
+ scroll_name_color=scroll_name_color,
86
+ value=credits_list
87
+ # value={
88
+ # "credits": credits_list,
89
+ # "licenses": license_paths,
90
+ # "effect": effect,
91
+ # "speed": speed,
92
+ # "sidebar_position": sidebar_position,
93
+ # "logo_path": "./assets/logo.webp", # Handled by Gradio's file serving
94
+ # "show_logo": show_logo,
95
+ # "show_licenses": show_licenses,
96
+ # "logo_position": logo_position,
97
+ # "logo_sizing": logo_sizing,
98
+ # "logo_width": logo_width,
99
+ # "logo_height": logo_height,
100
+ # "scroll_background_color": scroll_background_color,
101
+ # "scroll_title_color": scroll_title_color,
102
+ # "scroll_name_color": scroll_name_color
103
+ # }
104
+ )
105
+
106
+ def update_speed_on_effect_change(effect: str):
107
+ """Update speed_slider to default speed when effect changes."""
108
+ return DEFAULT_SPEEDS.get(effect, 40.0)
109
+
110
+ with gr.Blocks(theme=gr.themes.Ocean(), title="CreditsPanel Demo", css="") as demo:
111
+ gr.Markdown(
112
+ """
113
+ # Interactive CreditsPanel Demo
114
+ Use the sidebar controls to customize the credits panel.
115
+ """
116
+ )
117
+
118
+ with gr.Sidebar(position="right"):
119
+ effect_radio = gr.Radio(
120
+ ["scroll", "starwars", "matrix"],
121
+ label="Animation Effect",
122
+ value="scroll",
123
+ info="Select the visual style for the credits."
124
+ )
125
+ speed_slider = gr.Slider(
126
+ minimum=5.0,
127
+ maximum=100.0,
128
+ step=1.0,
129
+ value=DEFAULT_SPEEDS["scroll"],
130
+ label="Animation Speed (seconds)",
131
+ info="Duration of the animation cycle."
132
+ )
133
+ sidebar_position_radio = gr.Radio(
134
+ ["right", "bottom"],
135
+ label="Sidebar Position",
136
+ value="right",
137
+ info="Place the licenses sidebar on the right or bottom."
138
+ )
139
+ show_logo_checkbox = gr.Checkbox(
140
+ label="Show Logo",
141
+ value=True,
142
+ info="Toggle the logo panel."
143
+ )
144
+ show_licenses_checkbox = gr.Checkbox(
145
+ label="Show Licenses",
146
+ value=True,
147
+ info="Toggle the licenses sidebar."
148
+ )
149
+ logo_position_radio = gr.Radio(
150
+ ["center", "left", "right"],
151
+ label="Logo Position",
152
+ value="center",
153
+ info="Position of the logo in the panel."
154
+ )
155
+ logo_sizing_radio = gr.Radio(
156
+ ["stretch", "crop", "resize"],
157
+ label="Logo Sizing",
158
+ value="resize",
159
+ info="How the logo fits in the panel."
160
+ )
161
+ logo_width_input = gr.Textbox(
162
+ label="Logo Width (px or CSS)",
163
+ value="200px",
164
+ info="Width of the logo (e.g., '200px' or '50%')."
165
+ )
166
+ logo_height_input = gr.Textbox(
167
+ label="Logo Height (px or CSS)",
168
+ value="100px",
169
+ info="Height of the logo (e.g., '100px' or '10%')."
170
+ )
171
+ scroll_background_color = gr.ColorPicker(
172
+ label="Scroll Background Color",
173
+ value="#000000",
174
+ info="Background color for ScrollEffect."
175
+ )
176
+ scroll_title_color = gr.ColorPicker(
177
+ label="Scroll Title Color",
178
+ value="#FFFFFF",
179
+ info="Color for title text in ScrollEffect."
180
+ )
181
+ scroll_name_color = gr.ColorPicker(
182
+ label="Scroll Name Color",
183
+ value="#FFFFFF",
184
+ info="Color for name text in ScrollEffect."
185
+ )
186
+
187
+ panel = CreditsPanel(
188
+ credits=credits_list,
189
+ licenses=license_paths,
190
+ effect="scroll",
191
+ height=500,
192
+ speed=DEFAULT_SPEEDS["scroll"],
193
+ sidebar_position="right",
194
+ logo_path="./assets/logo.webp", # Handled by Gradio's file serving
195
+ show_logo=True,
196
+ show_licenses=True,
197
+ logo_position="center",
198
+ logo_sizing="resize",
199
+ logo_width="200px",
200
+ logo_height="100px",
201
+ scroll_background_color="#000000",
202
+ scroll_title_color="#FFFFFF",
203
+ scroll_name_color="#FFFFFF",
204
+ visible=True
205
+ )
206
+
207
+ inputs = [
208
+ effect_radio,
209
+ speed_slider,
210
+ sidebar_position_radio,
211
+ show_logo_checkbox,
212
+ show_licenses_checkbox,
213
+ logo_position_radio,
214
+ logo_sizing_radio,
215
+ logo_width_input,
216
+ logo_height_input,
217
+ scroll_background_color,
218
+ scroll_title_color,
219
+ scroll_name_color
220
+ ]
221
+
222
+ # Update speed when effect changes
223
+ effect_radio.change(
224
+ fn=update_speed_on_effect_change,
225
+ inputs=effect_radio,
226
+ outputs=speed_slider
227
+ )
228
+
229
+ # Update panel for all inputs
230
+ for input_component in inputs:
231
+ input_component.change(
232
+ fn=update_panel,
233
+ inputs=inputs,
234
+ outputs=panel
235
+ )
236
+
237
+ if __name__ == "__main__":
238
+ demo.launch(debug=True, share=False)
src/demo/app.py ADDED
@@ -0,0 +1,227 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ app.py
3
+
4
+ This script serves as an interactive demonstration for the custom Gradio component `CreditsPanel`.
5
+ It showcases all available features of the component, allowing users to dynamically adjust
6
+ properties like animation effects, speed, layout, and styling. The app also demonstrates
7
+ how to handle file dependencies (logo, licenses) in a portable way.
8
+ """
9
+
10
+ import gradio as gr
11
+ from gradio_creditspanel import CreditsPanel
12
+ import os
13
+
14
+ # --- 1. SETUP & DATA PREPARATION ---
15
+ # This section prepares all necessary assets and data for the demo.
16
+ # It ensures the demo runs out-of-the-box without manual setup.
17
+
18
+ def setup_demo_files():
19
+ """
20
+ Creates necessary directories and dummy files (logo, licenses) for the demo.
21
+ This makes the application self-contained and easy to run.
22
+ """
23
+ # Create dummy license files
24
+ os.makedirs("LICENSES", exist_ok=True)
25
+ if not os.path.exists("LICENSES/Apache.txt"):
26
+ with open("LICENSES/Apache.txt", "w") as f:
27
+ f.write("Apache License\nVersion 2.0, January 2004\nhttp://www.apache.org/licenses/...")
28
+ if not os.path.exists("LICENSES/MIT.txt"):
29
+ with open("LICENSES/MIT.txt", "w") as f:
30
+ f.write("MIT License\nCopyright (c) 2025 Author\nPermission is hereby granted...")
31
+
32
+ # Create a placeholder logo if it doesn't exist
33
+ os.makedirs("assets", exist_ok=True)
34
+ if not os.path.exists("./assets/logo.webp"):
35
+ with open("./assets/logo.webp", "w") as f:
36
+ f.write("Placeholder WebP logo")
37
+
38
+ # Initial data for the credits roll
39
+ credits_list = [
40
+ {"title": "Project Manager", "name": "Emma Thompson"},
41
+ {"title": "Lead Developer", "name": "John Doe"},
42
+ {"title": "Senior Backend Engineer", "name": "Michael Chen"},
43
+ {"title": "Frontend Developer", "name": "Sarah Johnson"},
44
+ {"title": "UI/UX Designer", "name": "Jane Smith"},
45
+ {"title": "Database Architect", "name": "Alex Ray"},
46
+ {"title": "DevOps Engineer", "name": "Liam Patel"},
47
+ {"title": "Quality Assurance Lead", "name": "Sam Wilson"},
48
+ {"title": "Test Automation Engineer", "name": "Olivia Brown"},
49
+ {"title": "Security Analyst", "name": "David Kim"},
50
+ {"title": "Data Scientist", "name": "Sophie Martinez"},
51
+ {"title": "Machine Learning Engineer", "name": "Ethan Lee"},
52
+ {"title": "API Developer", "name": "Isabella Garcia"},
53
+ {"title": "Technical Writer", "name": "Noah Davis"},
54
+ {"title": "Scrum Master", "name": "Ava Rodriguez"},
55
+ ]
56
+
57
+ # Paths to license files
58
+ license_paths = {
59
+ "Gradio Framework": "./LICENSES/Apache.txt",
60
+ "This Component": "./LICENSES/MIT.txt"
61
+ }
62
+
63
+ # Default animation speeds for each effect to provide a good user experience
64
+ DEFAULT_SPEEDS = {
65
+ "scroll": 40.0,
66
+ "starwars": 80.0,
67
+ "matrix": 40.0
68
+ }
69
+
70
+ # --- 2. GRADIO EVENT HANDLER FUNCTIONS ---
71
+ # These functions define the application's interactive logic.
72
+
73
+ def update_panel(
74
+ effect: str, speed: float, base_font_size: float,
75
+ intro_title: str, intro_subtitle: str, sidebar_position: str,
76
+ show_logo: bool, show_licenses: bool, logo_position: str,
77
+ logo_sizing: str, logo_width: str | None, logo_height: str | None,
78
+ scroll_background_color: str | None, scroll_title_color: str | None,
79
+ scroll_name_color: str | None
80
+ ) -> dict:
81
+ """
82
+ Callback function that updates all properties of the CreditsPanel component.
83
+ It takes the current state of all UI controls and returns a gr.update() dictionary.
84
+ """
85
+ return gr.update(
86
+ visible=True,
87
+ effect=effect,
88
+ speed=speed,
89
+ base_font_size=base_font_size,
90
+ intro_title=intro_title,
91
+ intro_subtitle=intro_subtitle,
92
+ sidebar_position=sidebar_position,
93
+ show_logo=show_logo,
94
+ show_licenses=show_licenses,
95
+ logo_position=logo_position,
96
+ logo_sizing=logo_sizing,
97
+ logo_width=logo_width,
98
+ logo_height=logo_height,
99
+ scroll_background_color=scroll_background_color,
100
+ scroll_title_color=scroll_title_color,
101
+ scroll_name_color=scroll_name_color,
102
+ value=credits_list # The list of credits to display
103
+ )
104
+
105
+ def update_ui_on_effect_change(effect: str) -> tuple[float, float]:
106
+ """
107
+ Updates the speed and font size sliders to sensible defaults when the
108
+ animation effect is changed.
109
+ """
110
+ font_size = 1.5
111
+ if effect == "starwars":
112
+ font_size = 6.0 # Star Wars effect looks better with a larger font
113
+
114
+ speed = DEFAULT_SPEEDS.get(effect, 40.0)
115
+ return speed, font_size
116
+
117
+ # --- 3. GRADIO UI DEFINITION ---
118
+ # This section constructs the user interface using gr.Blocks.
119
+
120
+ with gr.Blocks(theme=gr.themes.Ocean(), title="CreditsPanel Demo") as demo:
121
+ gr.Markdown(
122
+ """
123
+ # Interactive CreditsPanel Demo
124
+ Use the sidebar controls to customize the `CreditsPanel` component in real-time.
125
+ """
126
+ )
127
+
128
+ with gr.Sidebar(position="right"):
129
+ gr.Markdown("### Effects Settings")
130
+ effect_radio = gr.Radio(
131
+ ["scroll", "starwars", "matrix"], label="Animation Effect", value="scroll",
132
+ info="Select the visual style for the credits."
133
+ )
134
+ speed_slider = gr.Slider(
135
+ minimum=5.0, maximum=100.0, step=1.0, value=DEFAULT_SPEEDS["scroll"],
136
+ label="Animation Speed (seconds)", info="Duration of one animation cycle."
137
+ )
138
+ font_size_slider = gr.Slider(
139
+ minimum=1.0, maximum=10.0, step=0.1, value=1.5,
140
+ label="Base Font Size (rem)", info="Controls the base font size."
141
+ )
142
+
143
+ gr.Markdown("### Intro Text")
144
+ intro_title_input = gr.Textbox(
145
+ label="Intro Title", value="Gradio", info="Main title for the intro sequence."
146
+ )
147
+ intro_subtitle_input = gr.Textbox(
148
+ label="Intro Subtitle", value="The best UI framework", info="Subtitle for the intro sequence."
149
+ )
150
+
151
+ gr.Markdown("### Layout & Visibility")
152
+ sidebar_position_radio = gr.Radio(
153
+ ["right", "bottom"], label="Sidebar Position", value="right",
154
+ info="Place the licenses sidebar on the right or bottom."
155
+ )
156
+ show_logo_checkbox = gr.Checkbox(label="Show Logo", value=True)
157
+ show_licenses_checkbox = gr.Checkbox(label="Show Licenses", value=True)
158
+
159
+ gr.Markdown("### Logo Customization")
160
+ logo_position_radio = gr.Radio(
161
+ ["left", "center", "right"], label="Logo Position", value="center"
162
+ )
163
+ logo_sizing_radio = gr.Radio(
164
+ ["stretch", "crop", "resize"], label="Logo Sizing", value="resize"
165
+ )
166
+ logo_width_input = gr.Textbox(label="Logo Width", value="200px")
167
+ logo_height_input = gr.Textbox(label="Logo Height", value="100px")
168
+
169
+ gr.Markdown("### Color Settings (Scroll Effect)")
170
+ scroll_background_color = gr.ColorPicker(label="Background Color", value="#000000")
171
+ scroll_title_color = gr.ColorPicker(label="Title Color", value="#FFFFFF")
172
+ scroll_name_color = gr.ColorPicker(label="Name Color", value="#FFFFFF")
173
+
174
+ # Instantiate the custom CreditsPanel component with default values
175
+ panel = CreditsPanel(
176
+ credits=credits_list,
177
+ licenses=license_paths,
178
+ effect="scroll",
179
+ height=500,
180
+ speed=DEFAULT_SPEEDS["scroll"],
181
+ base_font_size=1.5,
182
+ intro_title="Gradio",
183
+ intro_subtitle="The best UI framework",
184
+ sidebar_position="right",
185
+ logo_path="./assets/logo.webp",
186
+ show_logo=True,
187
+ show_licenses=True,
188
+ logo_position="center",
189
+ logo_sizing="resize",
190
+ logo_width="200px",
191
+ logo_height="100px",
192
+ scroll_background_color="#000000",
193
+ scroll_title_color="#FFFFFF",
194
+ scroll_name_color="#FFFFFF",
195
+ )
196
+
197
+ # List of all input components that should trigger a panel update
198
+ inputs = [
199
+ effect_radio, speed_slider, font_size_slider,
200
+ intro_title_input, intro_subtitle_input,
201
+ sidebar_position_radio, show_logo_checkbox, show_licenses_checkbox,
202
+ logo_position_radio, logo_sizing_radio, logo_width_input, logo_height_input,
203
+ scroll_background_color, scroll_title_color, scroll_name_color
204
+ ]
205
+
206
+ # --- 4. EVENT BINDING ---
207
+ # Connect the UI controls to the handler functions.
208
+
209
+ # Special event: changing the effect also updates speed and font size sliders
210
+ effect_radio.change(
211
+ fn=update_ui_on_effect_change,
212
+ inputs=effect_radio,
213
+ outputs=[speed_slider, font_size_slider]
214
+ )
215
+
216
+ # General event: any change in an input control updates the main panel
217
+ for input_component in inputs:
218
+ input_component.change(
219
+ fn=update_panel,
220
+ inputs=inputs,
221
+ outputs=panel
222
+ )
223
+
224
+ # --- 5. APP LAUNCH ---
225
+ if __name__ == "__main__":
226
+ setup_demo_files()
227
+ demo.launch()
src/demo/css.css ADDED
@@ -0,0 +1,157 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ html {
2
+ font-family: Inter;
3
+ font-size: 16px;
4
+ font-weight: 400;
5
+ line-height: 1.5;
6
+ -webkit-text-size-adjust: 100%;
7
+ background: #fff;
8
+ color: #323232;
9
+ -webkit-font-smoothing: antialiased;
10
+ -moz-osx-font-smoothing: grayscale;
11
+ text-rendering: optimizeLegibility;
12
+ }
13
+
14
+ :root {
15
+ --space: 1;
16
+ --vspace: calc(var(--space) * 1rem);
17
+ --vspace-0: calc(3 * var(--space) * 1rem);
18
+ --vspace-1: calc(2 * var(--space) * 1rem);
19
+ --vspace-2: calc(1.5 * var(--space) * 1rem);
20
+ --vspace-3: calc(0.5 * var(--space) * 1rem);
21
+ }
22
+
23
+ .app {
24
+ max-width: 748px !important;
25
+ }
26
+
27
+ .prose p {
28
+ margin: var(--vspace) 0;
29
+ line-height: var(--vspace * 2);
30
+ font-size: 1rem;
31
+ }
32
+
33
+ code {
34
+ font-family: "Inconsolata", sans-serif;
35
+ font-size: 16px;
36
+ }
37
+
38
+ h1,
39
+ h1 code {
40
+ font-weight: 400;
41
+ line-height: calc(2.5 / var(--space) * var(--vspace));
42
+ }
43
+
44
+ h1 code {
45
+ background: none;
46
+ border: none;
47
+ letter-spacing: 0.05em;
48
+ padding-bottom: 5px;
49
+ position: relative;
50
+ padding: 0;
51
+ }
52
+
53
+ h2 {
54
+ margin: var(--vspace-1) 0 var(--vspace-2) 0;
55
+ line-height: 1em;
56
+ }
57
+
58
+ h3,
59
+ h3 code {
60
+ margin: var(--vspace-1) 0 var(--vspace-2) 0;
61
+ line-height: 1em;
62
+ }
63
+
64
+ h4,
65
+ h5,
66
+ h6 {
67
+ margin: var(--vspace-3) 0 var(--vspace-3) 0;
68
+ line-height: var(--vspace);
69
+ }
70
+
71
+ .bigtitle,
72
+ h1,
73
+ h1 code {
74
+ font-size: calc(8px * 4.5);
75
+ word-break: break-word;
76
+ }
77
+
78
+ .title,
79
+ h2,
80
+ h2 code {
81
+ font-size: calc(8px * 3.375);
82
+ font-weight: lighter;
83
+ word-break: break-word;
84
+ border: none;
85
+ background: none;
86
+ }
87
+
88
+ .subheading1,
89
+ h3,
90
+ h3 code {
91
+ font-size: calc(8px * 1.8);
92
+ font-weight: 600;
93
+ border: none;
94
+ background: none;
95
+ letter-spacing: 0.1em;
96
+ text-transform: uppercase;
97
+ }
98
+
99
+ h2 code {
100
+ padding: 0;
101
+ position: relative;
102
+ letter-spacing: 0.05em;
103
+ }
104
+
105
+ blockquote {
106
+ font-size: calc(8px * 1.1667);
107
+ font-style: italic;
108
+ line-height: calc(1.1667 * var(--vspace));
109
+ margin: var(--vspace-2) var(--vspace-2);
110
+ }
111
+
112
+ .subheading2,
113
+ h4 {
114
+ font-size: calc(8px * 1.4292);
115
+ text-transform: uppercase;
116
+ font-weight: 600;
117
+ }
118
+
119
+ .subheading3,
120
+ h5 {
121
+ font-size: calc(8px * 1.2917);
122
+ line-height: calc(1.2917 * var(--vspace));
123
+
124
+ font-weight: lighter;
125
+ text-transform: uppercase;
126
+ letter-spacing: 0.15em;
127
+ }
128
+
129
+ h6 {
130
+ font-size: calc(8px * 1.1667);
131
+ font-size: 1.1667em;
132
+ font-weight: normal;
133
+ font-style: italic;
134
+ font-family: "le-monde-livre-classic-byol", serif !important;
135
+ letter-spacing: 0px !important;
136
+ }
137
+
138
+ #start .md > *:first-child {
139
+ margin-top: 0;
140
+ }
141
+
142
+ h2 + h3 {
143
+ margin-top: 0;
144
+ }
145
+
146
+ .md hr {
147
+ border: none;
148
+ border-top: 1px solid var(--block-border-color);
149
+ margin: var(--vspace-2) 0 var(--vspace-2) 0;
150
+ }
151
+ .prose ul {
152
+ margin: var(--vspace-2) 0 var(--vspace-1) 0;
153
+ }
154
+
155
+ .gap {
156
+ gap: 0;
157
+ }
src/demo/requirements.txt ADDED
@@ -0,0 +1 @@
 
 
1
+ gradio_creditspanel
src/demo/space.py ADDED
@@ -0,0 +1,350 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import gradio as gr
3
+ from app import demo as app
4
+ import os
5
+
6
+ _docs = {'CreditsPanel': {'description': 'A Gradio component for displaying credits with customizable visual effects, such as scrolling or Star Wars-style animations.\nSupports displaying a logo, licenses, and configurable text styling.\n\n EVENTS (list): Supported events for the component, currently only `change`.', 'members': {'__init__': {'value': {'type': 'Any', 'default': 'None', 'description': None}, 'credits': {'type': 'typing.Union[\n typing.List[typing.Dict[str, str]],\n typing.Callable,\n NoneType,\n][\n typing.List[typing.Dict[str, str]][\n typing.Dict[str, str][str, str]\n ],\n Callable,\n None,\n]', 'default': 'None', 'description': None}, 'height': {'type': 'int | str | None', 'default': 'None', 'description': None}, 'width': {'type': 'int | str | None', 'default': 'None', 'description': None}, 'licenses': {'type': 'typing.Optional[typing.Dict[str, str | pathlib.Path]][\n typing.Dict[str, str | pathlib.Path][\n str, str | pathlib.Path\n ],\n None,\n]', 'default': 'None', 'description': None}, 'effect': {'type': '"scroll" | "starwars" | "matrix"', 'default': '"scroll"', 'description': None}, 'speed': {'type': 'float', 'default': '40.0', 'description': None}, 'base_font_size': {'type': 'float', 'default': '1.5', 'description': None}, 'intro_title': {'type': 'str | None', 'default': 'None', 'description': None}, 'intro_subtitle': {'type': 'str | None', 'default': 'None', 'description': None}, 'sidebar_position': {'type': '"right" | "bottom"', 'default': '"right"', 'description': None}, 'logo_path': {'type': 'str | pathlib.Path | None', 'default': 'None', 'description': None}, 'show_logo': {'type': 'bool', 'default': 'True', 'description': None}, 'show_licenses': {'type': 'bool', 'default': 'True', 'description': None}, 'logo_position': {'type': '"center" | "left" | "right"', 'default': '"center"', 'description': None}, 'logo_sizing': {'type': '"stretch" | "crop" | "resize"', 'default': '"resize"', 'description': None}, 'logo_width': {'type': 'int | str | None', 'default': 'None', 'description': None}, 'logo_height': {'type': 'int | str | None', 'default': 'None', 'description': None}, 'scroll_background_color': {'type': 'str | None', 'default': 'None', 'description': None}, 'scroll_title_color': {'type': 'str | None', 'default': 'None', 'description': None}, 'scroll_name_color': {'type': 'str | None', 'default': 'None', 'description': None}, 'label': {'type': 'str | gradio.i18n.I18nData | None', 'default': 'None', 'description': None}, 'every': {'type': 'float | None', 'default': 'None', 'description': None}, 'inputs': {'type': 'typing.Union[\n gradio.components.base.Component,\n typing.Sequence[gradio.components.base.Component],\n set[gradio.components.base.Component],\n NoneType,\n][\n gradio.components.base.Component,\n typing.Sequence[gradio.components.base.Component][\n gradio.components.base.Component\n ],\n set[gradio.components.base.Component],\n None,\n]', 'default': 'None', 'description': None}, 'show_label': {'type': 'bool', 'default': 'False', 'description': None}, 'container': {'type': 'bool', 'default': 'True', 'description': None}, 'scale': {'type': 'int | None', 'default': 'None', 'description': None}, 'min_width': {'type': 'int', 'default': '160', 'description': None}, 'interactive': {'type': 'bool | None', 'default': 'None', 'description': None}, 'visible': {'type': 'bool', 'default': 'True', 'description': None}, 'elem_id': {'type': 'str | None', 'default': 'None', 'description': None}, 'elem_classes': {'type': 'list[str] | str | None', 'default': 'None', 'description': None}, 'render': {'type': 'bool', 'default': 'True', 'description': None}, 'key': {'type': 'int | str | tuple[int | str, Ellipsis] | None', 'default': 'None', 'description': None}, 'preserved_by_key': {'type': 'list[str] | str | None', 'default': '"value"', 'description': None}}, 'postprocess': {'value': {'type': 'Any', 'description': None}}, 'preprocess': {'return': {'type': 'typing.Optional[typing.Dict[str, typing.Any]][\n typing.Dict[str, typing.Any][str, Any], None\n]', 'description': 'Dict[str, Any] | None: The input payload, returned unchanged.'}, 'value': None}}, 'events': {'change': {'type': None, 'default': None, 'description': 'Triggered when the value of the CreditsPanel changes either because of user input (e.g. a user types in a textbox) OR because of a function update (e.g. an image receives a value from the output of an event trigger). See `.input()` for a listener that is only triggered by user input.'}}}, '__meta__': {'additional_interfaces': {}, 'user_fn_refs': {'CreditsPanel': []}}}
7
+
8
+ abs_path = os.path.join(os.path.dirname(__file__), "css.css")
9
+
10
+ with gr.Blocks(
11
+ css=abs_path,
12
+ theme=gr.themes.Ocean(
13
+ font_mono=[
14
+ gr.themes.GoogleFont("Inconsolata"),
15
+ "monospace",
16
+ ],
17
+ ),
18
+ ) as demo:
19
+ gr.Markdown(
20
+ """
21
+ # `gradio_creditspanel`
22
+
23
+ <div style="display: flex; gap: 7px;">
24
+ <img alt="Static Badge" src="https://img.shields.io/badge/version%20-%200.0.1%20-%20orange">
25
+ </div>
26
+
27
+ Credits Panel for Gradio UI
28
+ """, elem_classes=["md-custom"], header_links=True)
29
+ app.render()
30
+ gr.Markdown(
31
+ """
32
+ ## Installation
33
+
34
+ ```bash
35
+ pip install gradio_creditspanel
36
+ ```
37
+
38
+ ## Usage
39
+
40
+ ```python
41
+ \"\"\"
42
+ app.py
43
+
44
+ This script serves as an interactive demonstration for the custom Gradio component `CreditsPanel`.
45
+ It showcases all available features of the component, allowing users to dynamically adjust
46
+ properties like animation effects, speed, layout, and styling. The app also demonstrates
47
+ how to handle file dependencies (logo, licenses) in a portable way.
48
+ \"\"\"
49
+
50
+ import gradio as gr
51
+ from gradio_creditspanel import CreditsPanel
52
+ import os
53
+
54
+ # --- 1. SETUP & DATA PREPARATION ---
55
+ # This section prepares all necessary assets and data for the demo.
56
+ # It ensures the demo runs out-of-the-box without manual setup.
57
+
58
+ def setup_demo_files():
59
+ \"\"\"
60
+ Creates necessary directories and dummy files (logo, licenses) for the demo.
61
+ This makes the application self-contained and easy to run.
62
+ \"\"\"
63
+ # Create dummy license files
64
+ os.makedirs("LICENSES", exist_ok=True)
65
+ if not os.path.exists("LICENSES/Apache.txt"):
66
+ with open("LICENSES/Apache.txt", "w") as f:
67
+ f.write("Apache License\nVersion 2.0, January 2004\nhttp://www.apache.org/licenses/...")
68
+ if not os.path.exists("LICENSES/MIT.txt"):
69
+ with open("LICENSES/MIT.txt", "w") as f:
70
+ f.write("MIT License\nCopyright (c) 2025 Author\nPermission is hereby granted...")
71
+
72
+ # Create a placeholder logo if it doesn't exist
73
+ os.makedirs("assets", exist_ok=True)
74
+ if not os.path.exists("./assets/logo.webp"):
75
+ with open("./assets/logo.webp", "w") as f:
76
+ f.write("Placeholder WebP logo")
77
+
78
+ # Initial data for the credits roll
79
+ credits_list = [
80
+ {"title": "Project Manager", "name": "Emma Thompson"},
81
+ {"title": "Lead Developer", "name": "John Doe"},
82
+ {"title": "Senior Backend Engineer", "name": "Michael Chen"},
83
+ {"title": "Frontend Developer", "name": "Sarah Johnson"},
84
+ {"title": "UI/UX Designer", "name": "Jane Smith"},
85
+ {"title": "Database Architect", "name": "Alex Ray"},
86
+ {"title": "DevOps Engineer", "name": "Liam Patel"},
87
+ {"title": "Quality Assurance Lead", "name": "Sam Wilson"},
88
+ {"title": "Test Automation Engineer", "name": "Olivia Brown"},
89
+ {"title": "Security Analyst", "name": "David Kim"},
90
+ {"title": "Data Scientist", "name": "Sophie Martinez"},
91
+ {"title": "Machine Learning Engineer", "name": "Ethan Lee"},
92
+ {"title": "API Developer", "name": "Isabella Garcia"},
93
+ {"title": "Technical Writer", "name": "Noah Davis"},
94
+ {"title": "Scrum Master", "name": "Ava Rodriguez"},
95
+ ]
96
+
97
+ # Paths to license files
98
+ license_paths = {
99
+ "Gradio Framework": "./LICENSES/Apache.txt",
100
+ "This Component": "./LICENSES/MIT.txt"
101
+ }
102
+
103
+ # Default animation speeds for each effect to provide a good user experience
104
+ DEFAULT_SPEEDS = {
105
+ "scroll": 40.0,
106
+ "starwars": 80.0,
107
+ "matrix": 40.0
108
+ }
109
+
110
+ # --- 2. GRADIO EVENT HANDLER FUNCTIONS ---
111
+ # These functions define the application's interactive logic.
112
+
113
+ def update_panel(
114
+ effect: str, speed: float, base_font_size: float,
115
+ intro_title: str, intro_subtitle: str, sidebar_position: str,
116
+ show_logo: bool, show_licenses: bool, logo_position: str,
117
+ logo_sizing: str, logo_width: str | None, logo_height: str | None,
118
+ scroll_background_color: str | None, scroll_title_color: str | None,
119
+ scroll_name_color: str | None
120
+ ) -> dict:
121
+ \"\"\"
122
+ Callback function that updates all properties of the CreditsPanel component.
123
+ It takes the current state of all UI controls and returns a gr.update() dictionary.
124
+ \"\"\"
125
+ return gr.update(
126
+ visible=True,
127
+ effect=effect,
128
+ speed=speed,
129
+ base_font_size=base_font_size,
130
+ intro_title=intro_title,
131
+ intro_subtitle=intro_subtitle,
132
+ sidebar_position=sidebar_position,
133
+ show_logo=show_logo,
134
+ show_licenses=show_licenses,
135
+ logo_position=logo_position,
136
+ logo_sizing=logo_sizing,
137
+ logo_width=logo_width,
138
+ logo_height=logo_height,
139
+ scroll_background_color=scroll_background_color,
140
+ scroll_title_color=scroll_title_color,
141
+ scroll_name_color=scroll_name_color,
142
+ value=credits_list # The list of credits to display
143
+ )
144
+
145
+ def update_ui_on_effect_change(effect: str) -> tuple[float, float]:
146
+ \"\"\"
147
+ Updates the speed and font size sliders to sensible defaults when the
148
+ animation effect is changed.
149
+ \"\"\"
150
+ font_size = 1.5
151
+ if effect == "starwars":
152
+ font_size = 6.0 # Star Wars effect looks better with a larger font
153
+
154
+ speed = DEFAULT_SPEEDS.get(effect, 40.0)
155
+ return speed, font_size
156
+
157
+ # --- 3. GRADIO UI DEFINITION ---
158
+ # This section constructs the user interface using gr.Blocks.
159
+
160
+ with gr.Blocks(theme=gr.themes.Ocean(), title="CreditsPanel Demo") as demo:
161
+ gr.Markdown(
162
+ \"\"\"
163
+ # Interactive CreditsPanel Demo
164
+ Use the sidebar controls to customize the `CreditsPanel` component in real-time.
165
+ \"\"\"
166
+ )
167
+
168
+ with gr.Sidebar(position="right"):
169
+ gr.Markdown("### Effects Settings")
170
+ effect_radio = gr.Radio(
171
+ ["scroll", "starwars", "matrix"], label="Animation Effect", value="scroll",
172
+ info="Select the visual style for the credits."
173
+ )
174
+ speed_slider = gr.Slider(
175
+ minimum=5.0, maximum=100.0, step=1.0, value=DEFAULT_SPEEDS["scroll"],
176
+ label="Animation Speed (seconds)", info="Duration of one animation cycle."
177
+ )
178
+ font_size_slider = gr.Slider(
179
+ minimum=1.0, maximum=10.0, step=0.1, value=1.5,
180
+ label="Base Font Size (rem)", info="Controls the base font size."
181
+ )
182
+
183
+ gr.Markdown("### Intro Text")
184
+ intro_title_input = gr.Textbox(
185
+ label="Intro Title", value="Gradio", info="Main title for the intro sequence."
186
+ )
187
+ intro_subtitle_input = gr.Textbox(
188
+ label="Intro Subtitle", value="The best UI framework", info="Subtitle for the intro sequence."
189
+ )
190
+
191
+ gr.Markdown("### Layout & Visibility")
192
+ sidebar_position_radio = gr.Radio(
193
+ ["right", "bottom"], label="Sidebar Position", value="right",
194
+ info="Place the licenses sidebar on the right or bottom."
195
+ )
196
+ show_logo_checkbox = gr.Checkbox(label="Show Logo", value=True)
197
+ show_licenses_checkbox = gr.Checkbox(label="Show Licenses", value=True)
198
+
199
+ gr.Markdown("### Logo Customization")
200
+ logo_position_radio = gr.Radio(
201
+ ["left", "center", "right"], label="Logo Position", value="center"
202
+ )
203
+ logo_sizing_radio = gr.Radio(
204
+ ["stretch", "crop", "resize"], label="Logo Sizing", value="resize"
205
+ )
206
+ logo_width_input = gr.Textbox(label="Logo Width", value="200px")
207
+ logo_height_input = gr.Textbox(label="Logo Height", value="100px")
208
+
209
+ gr.Markdown("### Color Settings (Scroll Effect)")
210
+ scroll_background_color = gr.ColorPicker(label="Background Color", value="#000000")
211
+ scroll_title_color = gr.ColorPicker(label="Title Color", value="#FFFFFF")
212
+ scroll_name_color = gr.ColorPicker(label="Name Color", value="#FFFFFF")
213
+
214
+ # Instantiate the custom CreditsPanel component with default values
215
+ panel = CreditsPanel(
216
+ credits=credits_list,
217
+ licenses=license_paths,
218
+ effect="scroll",
219
+ height=500,
220
+ speed=DEFAULT_SPEEDS["scroll"],
221
+ base_font_size=1.5,
222
+ intro_title="Gradio",
223
+ intro_subtitle="The best UI framework",
224
+ sidebar_position="right",
225
+ logo_path="./assets/logo.webp",
226
+ show_logo=True,
227
+ show_licenses=True,
228
+ logo_position="center",
229
+ logo_sizing="resize",
230
+ logo_width="200px",
231
+ logo_height="100px",
232
+ scroll_background_color="#000000",
233
+ scroll_title_color="#FFFFFF",
234
+ scroll_name_color="#FFFFFF",
235
+ )
236
+
237
+ # List of all input components that should trigger a panel update
238
+ inputs = [
239
+ effect_radio, speed_slider, font_size_slider,
240
+ intro_title_input, intro_subtitle_input,
241
+ sidebar_position_radio, show_logo_checkbox, show_licenses_checkbox,
242
+ logo_position_radio, logo_sizing_radio, logo_width_input, logo_height_input,
243
+ scroll_background_color, scroll_title_color, scroll_name_color
244
+ ]
245
+
246
+ # --- 4. EVENT BINDING ---
247
+ # Connect the UI controls to the handler functions.
248
+
249
+ # Special event: changing the effect also updates speed and font size sliders
250
+ effect_radio.change(
251
+ fn=update_ui_on_effect_change,
252
+ inputs=effect_radio,
253
+ outputs=[speed_slider, font_size_slider]
254
+ )
255
+
256
+ # General event: any change in an input control updates the main panel
257
+ for input_component in inputs:
258
+ input_component.change(
259
+ fn=update_panel,
260
+ inputs=inputs,
261
+ outputs=panel
262
+ )
263
+
264
+ # --- 5. APP LAUNCH ---
265
+ if __name__ == "__main__":
266
+ setup_demo_files()
267
+ demo.launch()
268
+ ```
269
+ """, elem_classes=["md-custom"], header_links=True)
270
+
271
+
272
+ gr.Markdown("""
273
+ ## `CreditsPanel`
274
+
275
+ ### Initialization
276
+ """, elem_classes=["md-custom"], header_links=True)
277
+
278
+ gr.ParamViewer(value=_docs["CreditsPanel"]["members"]["__init__"], linkify=[])
279
+
280
+
281
+ gr.Markdown("### Events")
282
+ gr.ParamViewer(value=_docs["CreditsPanel"]["events"], linkify=['Event'])
283
+
284
+
285
+
286
+
287
+ gr.Markdown("""
288
+
289
+ ### User function
290
+
291
+ The impact on the users predict function varies depending on whether the component is used as an input or output for an event (or both).
292
+
293
+ - When used as an Input, the component only impacts the input signature of the user function.
294
+ - When used as an output, the component only impacts the return signature of the user function.
295
+
296
+ The code snippet below is accurate in cases where the component is used as both an input and an output.
297
+
298
+ - **As input:** Is passed, dict[str, Any] | None: The input payload, returned unchanged.
299
+
300
+
301
+ ```python
302
+ def predict(
303
+ value: typing.Optional[typing.Dict[str, typing.Any]][
304
+ typing.Dict[str, typing.Any][str, Any], None
305
+ ]
306
+ ) -> Any:
307
+ return value
308
+ ```
309
+ """, elem_classes=["md-custom", "CreditsPanel-user-fn"], header_links=True)
310
+
311
+
312
+
313
+
314
+ demo.load(None, js=r"""function() {
315
+ const refs = {};
316
+ const user_fn_refs = {
317
+ CreditsPanel: [], };
318
+ requestAnimationFrame(() => {
319
+
320
+ Object.entries(user_fn_refs).forEach(([key, refs]) => {
321
+ if (refs.length > 0) {
322
+ const el = document.querySelector(`.${key}-user-fn`);
323
+ if (!el) return;
324
+ refs.forEach(ref => {
325
+ el.innerHTML = el.innerHTML.replace(
326
+ new RegExp("\\b"+ref+"\\b", "g"),
327
+ `<a href="#h-${ref.toLowerCase()}">${ref}</a>`
328
+ );
329
+ })
330
+ }
331
+ })
332
+
333
+ Object.entries(refs).forEach(([key, refs]) => {
334
+ if (refs.length > 0) {
335
+ const el = document.querySelector(`.${key}`);
336
+ if (!el) return;
337
+ refs.forEach(ref => {
338
+ el.innerHTML = el.innerHTML.replace(
339
+ new RegExp("\\b"+ref+"\\b", "g"),
340
+ `<a href="#h-${ref.toLowerCase()}">${ref}</a>`
341
+ );
342
+ })
343
+ }
344
+ })
345
+ })
346
+ }
347
+
348
+ """)
349
+
350
+ demo.launch()
src/frontend/Example.svelte ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <script lang="ts">
2
+ export let value: string;
3
+ export let type: "gallery" | "table";
4
+ export let selected = false;
5
+ </script>
6
+
7
+ <div
8
+ class:table={type === "table"}
9
+ class:gallery={type === "gallery"}
10
+ class:selected
11
+ class="prose"
12
+ >
13
+ {@html value}
14
+ </div>
15
+
16
+ <style>
17
+ .gallery {
18
+ padding: var(--size-2);
19
+ }
20
+ </style>
src/frontend/Index.svelte ADDED
@@ -0,0 +1,389 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <script lang="ts">
2
+ import type { Gradio } from "@gradio/utils";
3
+ import { Block } from "@gradio/atoms";
4
+ import { StatusTracker } from "@gradio/statustracker";
5
+ import type { LoadingStatus } from "@gradio/statustracker";
6
+ import ScrollEffect from "./shared/ScrollEffect.svelte";
7
+ import StarWarsEffect from "./shared/StarWarsEffect.svelte";
8
+ import MatrixEffect from "./shared/MatrixEffect.svelte";
9
+ import { Image } from "@gradio/image/shared";
10
+
11
+ /**
12
+ * Props for the Index component.
13
+ * @typedef {Object} Value
14
+ * @property {Array<{title: string, name: string}>} credits - List of credits with title and name.
15
+ * @property {Record<string, string>} licenses - License names and content.
16
+ * @property {"scroll" | "starwars" | "matrix"} effect - Credits display effect.
17
+ * @property {number} speed - Animation speed in seconds.
18
+ * @property {number} base_font_size - Base font size in rem.
19
+ * @property {string | null} intro_title - Intro title.
20
+ * @property {string | null} intro_subtitle - Intro subtitle.
21
+ * @property {"right" | "bottom"} sidebar_position - Licenses sidebar position.
22
+ * @property {{path: string | null, url: string | null, orig_name: string | null, mime_type: string | null} | null} logo_path - Logo file details.
23
+ * @property {boolean} show_logo - Show logo.
24
+ * @property {boolean} show_licenses - Show licenses.
25
+ * @property {"center" | "left" | "right"} logo_position - Logo alignment.
26
+ * @property {"stretch" | "crop" | "resize"} logo_sizing - Logo sizing mode.
27
+ * @property {number | string | null} logo_width - Logo width.
28
+ * @property {number | string | null} logo_height - Logo height.
29
+ * @property {string | null} scroll_background_color - Scroll effect background color.
30
+ * @property {string | null} scroll_title_color - Credit title color.
31
+ * @property {string | null} scroll_name_color - Credit name color.
32
+ * @property {number} title_font_size - Title font size (unused in StarWarsEffect).
33
+ * @property {number} name_font_size - Name font size (unused in StarWarsEffect).
34
+ */
35
+ export let value: Value | null = null;
36
+ export let elem_id = "";
37
+ export let elem_classes: string[] = [];
38
+ export let visible = true;
39
+ export let container = true;
40
+ export let scale: number | null = null;
41
+ export let min_width: number | undefined = undefined;
42
+ export let height: number | string | undefined = undefined;
43
+ export let width: number | string | undefined = undefined;
44
+ export let loading_status: LoadingStatus;
45
+ export let gradio: Gradio<{ change: never }>;
46
+
47
+ // Default value if `value` is null
48
+ $: effectiveValue = value || {
49
+ credits: [
50
+ { title: "Lead Developer", name: "John Doe" },
51
+ { title: "UI/UX Design", name: "Jane Smith" },
52
+ { title: "Backend Engineering", name: "Alex Ray" },
53
+ { title: "Component Concept", name: "Your Name" },
54
+ { title: "Quality Assurance", name: "Sam Wilson" },
55
+ ],
56
+ licenses: {
57
+ "Gradio Framework": "Apache License placeholder",
58
+ "This Component": "MIT License placeholder",
59
+ },
60
+ effect: "scroll",
61
+ speed: 40,
62
+ base_font_size: 1.5,
63
+ intro_title: "",
64
+ intro_subtitle: "",
65
+ sidebar_position: "right",
66
+ logo_path: null,
67
+ show_logo: true,
68
+ show_licenses: true,
69
+ logo_position: "center",
70
+ logo_sizing: "resize",
71
+ logo_width: null,
72
+ logo_height: null,
73
+ scroll_background_color: null,
74
+ scroll_title_color: null,
75
+ scroll_name_color: null
76
+ };
77
+
78
+ // Tracks selected license for display
79
+ let selected_license_name: string | null = null;
80
+
81
+ // Toggles license content display
82
+ function show_license(name: string) {
83
+ selected_license_name = selected_license_name === name ? null : name;
84
+ }
85
+
86
+ // Reactive styles for dimensions and logo
87
+ $: height_style =
88
+ typeof height === "number" ? `${height}px` : height || "500px";
89
+ $: width_style = typeof width === "number" ? `${width}px` : width || "100%";
90
+ $: logo_width_style = effectiveValue.logo_width
91
+ ? typeof effectiveValue.logo_width === "number"
92
+ ? `${effectiveValue.logo_width}px`
93
+ : effectiveValue.logo_width
94
+ : "auto";
95
+ $: logo_height_style = effectiveValue.logo_height
96
+ ? typeof effectiveValue.logo_height === "number"
97
+ ? `${effectiveValue.logo_height}px`
98
+ : effectiveValue.logo_height
99
+ : "100px";
100
+ $: logo_panel_height = effectiveValue.logo_height
101
+ ? typeof effectiveValue.logo_height === "number"
102
+ ? `${effectiveValue.logo_height}px`
103
+ : effectiveValue.logo_height
104
+ : "100px";
105
+ $: object_fit =
106
+ effectiveValue.logo_sizing === "stretch"
107
+ ? "fill"
108
+ : effectiveValue.logo_sizing === "crop"
109
+ ? "cover"
110
+ : "contain";
111
+ $: logo_justify =
112
+ effectiveValue.logo_position === "center"
113
+ ? "center"
114
+ : effectiveValue.logo_position === "left"
115
+ ? "flex-start"
116
+ : "flex-end";
117
+ </script>
118
+
119
+ <Block
120
+ {visible}
121
+ {elem_id}
122
+ {elem_classes}
123
+ {container}
124
+ {scale}
125
+ {min_width}
126
+ padding={false}
127
+ >
128
+ <!-- Loading status tracker -->
129
+ <StatusTracker
130
+ autoscroll={gradio.autoscroll}
131
+ i18n={gradio.i18n}
132
+ queue_position={loading_status?.queue_position ?? -1}
133
+ queue_size={loading_status?.queue_size ?? 0}
134
+ status={loading_status?.status ?? "complete"}
135
+ />
136
+ <slot />
137
+
138
+ <!-- Logo panel -->
139
+ {#key effectiveValue.logo_position}
140
+ <div class="outer-logo-wrapper" style:width={width_style}>
141
+ {#if effectiveValue.show_logo && effectiveValue.logo_path?.url}
142
+ <div
143
+ class="logo-panel"
144
+ style:height={logo_panel_height}
145
+ style:display="flex"
146
+ style:justify-content={logo_justify}
147
+ >
148
+ {#if gradio}
149
+ <Image
150
+ src={effectiveValue.logo_path.url}
151
+ alt="Logo"
152
+ loading="lazy"
153
+ {gradio}
154
+ style="width: {logo_width_style}; height: {logo_height_style}; object-fit: {object_fit};"
155
+ />
156
+ {:else}
157
+ <img
158
+ src={effectiveValue.logo_path.url}
159
+ alt="Logo"
160
+ style="width: {logo_width_style}; height: {logo_height_style}; object-fit: {object_fit};"
161
+ />
162
+ {/if}
163
+ </div>
164
+ {/if}
165
+ </div>
166
+ {/key}
167
+
168
+ <!-- Credits and licenses panel -->
169
+ {#key effectiveValue.sidebar_position}
170
+ <div class="outer-credits-wrapper" style:width={width_style}>
171
+ <div
172
+ class="credits-panel-wrapper"
173
+ style:width={width_style}
174
+ style:height={effectiveValue.sidebar_position === "right"
175
+ ? height_style
176
+ : undefined}
177
+ >
178
+ {#key { effect: effectiveValue.effect, speed: effectiveValue.speed }}
179
+ <div
180
+ class="main-credits-panel"
181
+ style:height={height_style}
182
+ style:width={effectiveValue.sidebar_position === "right" &&
183
+ effectiveValue.show_licenses
184
+ ? "calc(100% - var(--sidebar-width, 400px))"
185
+ : width_style}
186
+ >
187
+ {#if effectiveValue.effect === "scroll"}
188
+ <ScrollEffect
189
+ credits={effectiveValue.credits}
190
+ speed={effectiveValue.speed}
191
+ base_font_size={effectiveValue.base_font_size}
192
+ intro_title={effectiveValue.intro_title}
193
+ intro_subtitle={effectiveValue.intro_subtitle}
194
+ background_color={effectiveValue.scroll_background_color}
195
+ title_color={effectiveValue.scroll_title_color}
196
+ name_color={effectiveValue.scroll_name_color}
197
+ />
198
+ {:else if effectiveValue.effect === "starwars"}
199
+ <StarWarsEffect
200
+ credits={effectiveValue.credits}
201
+ speed={effectiveValue.speed}
202
+ base_font_size={effectiveValue.base_font_size}
203
+ intro_title={effectiveValue.intro_title}
204
+ intro_subtitle={effectiveValue.intro_subtitle}
205
+ />
206
+ {:else if effectiveValue.effect === "matrix"}
207
+ <MatrixEffect
208
+ credits={effectiveValue.credits}
209
+ speed={effectiveValue.speed}
210
+ base_font_size={effectiveValue.base_font_size}
211
+ intro_title={effectiveValue.intro_title}
212
+ intro_subtitle={effectiveValue.intro_subtitle}
213
+ />
214
+ {/if}
215
+ </div>
216
+ {/key}
217
+ {#if effectiveValue.show_licenses && Object.keys(effectiveValue.licenses).length > 0}
218
+ <div class="licenses-sidebar">
219
+ <h3>Licenses</h3>
220
+ <ul>
221
+ {#each Object.entries(effectiveValue.licenses) as [name, content] (name)}
222
+ <li>
223
+ <button
224
+ class:selected={selected_license_name === name}
225
+ on:click={() => show_license(name)}
226
+ type="button"
227
+ >
228
+ {name}
229
+ </button>
230
+ </li>
231
+ {/each}
232
+ </ul>
233
+ {#if selected_license_name}
234
+ <div class="license-display">
235
+ <h4>{selected_license_name}</h4>
236
+ <pre>{effectiveValue.licenses[selected_license_name]}</pre>
237
+ </div>
238
+ {/if}
239
+ </div>
240
+ {/if}
241
+ </div>
242
+ </div>
243
+ {/key}
244
+ </Block>
245
+
246
+ <svelte:head>
247
+ {#if effectiveValue.sidebar_position === "bottom"}
248
+ <!-- Bottom sidebar styles -->
249
+ <style>
250
+ .credits-panel-wrapper {
251
+ flex-direction: column !important;
252
+ --panel-direction: column;
253
+ --sidebar-width: 100%;
254
+ --border-left: none;
255
+ --border-top: 1px solid var(--border-color-primary);
256
+ --sidebar-max-height: 400px;
257
+ }
258
+ .licenses-sidebar {
259
+ width: 100% !important;
260
+ border-left: none !important;
261
+ border-top: 1px solid var(--border-color-primary) !important;
262
+ }
263
+ .main-credits-panel {
264
+ width: 100% !important;
265
+ }
266
+ </style>
267
+ {:else}
268
+ <!-- Right sidebar styles -->
269
+ <style>
270
+ .credits-panel-wrapper { flex-direction: row !important; --panel-direction: row; --sidebar-width: 400px; --border-left: 1px solid var(--border-color-primary); --border-top: none; --sidebar-max-height: {height_style}; }
271
+ .licenses-sidebar { width: var(--sidebar-width, 400px) !important; border-left: 1px solid var(--border-color-primary) !important; border-top: none !important; }
272
+ .main-credits-panel { width: calc(100% - var(--sidebar-width, 400px)) !important; }
273
+ </style>
274
+ {/if}
275
+ </svelte:head>
276
+
277
+ <style>
278
+ /* Remove default block styling */
279
+ :global(.block) {
280
+ border: none !important;
281
+ box-shadow: none !important;
282
+ border-style: none !important;
283
+ }
284
+ /* Logo wrapper */
285
+ .outer-logo-wrapper {
286
+ display: flex;
287
+ flex-direction: column;
288
+ width: 100%;
289
+ border: none;
290
+ }
291
+ /* Credits wrapper */
292
+ .outer-credits-wrapper {
293
+ display: flex;
294
+ flex-direction: column;
295
+ width: 100%;
296
+ border: none;
297
+ }
298
+ /* Logo panel */
299
+ .logo-panel {
300
+ background: var(--background-fill-primary);
301
+ border: none;
302
+ border-bottom: 1px solid var(--border-color-primary);
303
+ display: flex !important;
304
+ align-items: center;
305
+ justify-content: var(--logo-justify, center);
306
+ width: 100%;
307
+ }
308
+ /* Credits and licenses container */
309
+ .credits-panel-wrapper {
310
+ display: flex;
311
+ flex-direction: var(--panel-direction, row);
312
+ min-height: var(--size-full, 500px);
313
+ width: 100%;
314
+ background: var(--background-fill-primary);
315
+ border: 1px solid var(--border-color-primary);
316
+ border-radius: var(--radius-lg);
317
+ overflow: hidden;
318
+ }
319
+ /* Credits display panel */
320
+ .main-credits-panel {
321
+ flex-grow: 1;
322
+ flex-shrink: 1;
323
+ min-width: 200px;
324
+ background: black;
325
+ overflow: hidden;
326
+ position: relative;
327
+ }
328
+ /* Licenses sidebar */
329
+ .licenses-sidebar {
330
+ width: var(--sidebar-width, 400px);
331
+ max-width: 100%;
332
+ max-height: var(--sidebar-max-height, none);
333
+ flex-shrink: 1;
334
+ flex-grow: 0;
335
+ background: var(--background-fill-secondary);
336
+ overflow-y: auto;
337
+ border-left: var(--border-left, 1px solid var(--border-color-primary));
338
+ border-top: var(--border-top, none);
339
+ }
340
+ .licenses-sidebar h3 {
341
+ margin: var(--spacing-lg);
342
+ font-size: var(--text-lg);
343
+ }
344
+ .licenses-sidebar li {
345
+ padding: 0;
346
+ margin: 0;
347
+ cursor: default;
348
+ border-bottom: 1px solid var(--border-color-primary);
349
+ }
350
+ .licenses-sidebar li button {
351
+ background: none;
352
+ border: none;
353
+ font: inherit;
354
+ color: inherit;
355
+ text-align: left;
356
+ width: 100%;
357
+ cursor: pointer;
358
+ padding: var(--spacing-md) var(--spacing-lg);
359
+ transition: background-color 0.2s;
360
+ }
361
+ .licenses-sidebar li button:hover {
362
+ background-color: var(--background-fill-primary);
363
+ }
364
+ .licenses-sidebar li button:focus-visible {
365
+ outline: 2px solid var(--color-accent);
366
+ outline-offset: -2px;
367
+ }
368
+ .licenses-sidebar li button.selected {
369
+ background-color: var(--color-accent);
370
+ color: white;
371
+ font-weight: bold;
372
+ }
373
+ .license-display {
374
+ padding: var(--spacing-lg);
375
+ overflow-y: auto;
376
+ flex-grow: 1;
377
+ border-top: 1px solid var(--border-color-primary);
378
+ background: var(--background-fill-primary);
379
+ }
380
+ .license-display h4 {
381
+ margin-top: 0;
382
+ }
383
+ .license-display pre {
384
+ white-space: pre-wrap;
385
+ word-break: break-word;
386
+ font-size: var(--text-sm);
387
+ color: var(--body-text-color-subdued);
388
+ }
389
+ </style>
src/frontend/gradio.config.js ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ export default {
2
+ plugins: [],
3
+ svelte: {
4
+ preprocess: [],
5
+ },
6
+ build: {
7
+ target: "modules",
8
+ },
9
+ };
src/frontend/package-lock.json ADDED
The diff for this file is too large to render. See raw diff
 
src/frontend/package.json ADDED
@@ -0,0 +1,46 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "gradio_creditspanel",
3
+ "version": "0.7.0",
4
+ "description": "Gradio UI packages",
5
+ "type": "module",
6
+ "author": "",
7
+ "license": "ISC",
8
+ "private": false,
9
+ "main_changeset": true,
10
+ "dependencies": {
11
+ "@gradio/atoms": "0.16.5",
12
+ "@gradio/image": "^0.22.17",
13
+ "@gradio/statustracker": "0.10.18",
14
+ "@gradio/utils": "0.10.2",
15
+ "@gradio/icons": "0.13.1"
16
+ },
17
+ "devDependencies": {
18
+ "@gradio/preview": "0.14.0"
19
+ },
20
+ "exports": {
21
+ "./package.json": "./package.json",
22
+ ".": {
23
+ "gradio": "./Index.svelte",
24
+ "svelte": "./dist/Index.svelte",
25
+ "types": "./dist/Index.svelte.d.ts"
26
+ },
27
+ "./example": {
28
+ "gradio": "./Example.svelte",
29
+ "svelte": "./dist/Example.svelte",
30
+ "types": "./dist/Example.svelte.d.ts"
31
+ },
32
+ "./base": {
33
+ "gradio": "./Index.svelte",
34
+ "svelte": "./dist/Index.svelte",
35
+ "types": "./dist/Index.svelte.d.ts"
36
+ }
37
+ },
38
+ "peerDependencies": {
39
+ "svelte": "^4.0.0"
40
+ },
41
+ "repository": {
42
+ "type": "git",
43
+ "url": "git+https://github.com/gradio-app/gradio.git",
44
+ "directory": "js/html"
45
+ }
46
+ }
src/frontend/shared/MatrixEffect.svelte ADDED
@@ -0,0 +1,192 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <script lang="ts">
2
+ import { onMount, onDestroy } from 'svelte';
3
+
4
+ /**
5
+ * Props for the MatrixEffect component.
6
+ * @typedef {Object} Props
7
+ * @property {Array<{title: string, name: string}>} credits - List of credits with title and name.
8
+ * @property {number} speed - Animation speed in seconds (default: 20).
9
+ * @property {number} base_font_size - Base font size in em (default: 1.0).
10
+ * @property {string | null} intro_title - Optional intro title.
11
+ * @property {string | null} intro_subtitle - Optional intro subtitle.
12
+ */
13
+ export let credits: Props['credits'];
14
+ export let speed: number = 20;
15
+ export let base_font_size: number = 1.0;
16
+ export let intro_title: string | null = null;
17
+ export let intro_subtitle: string | null = null;
18
+
19
+ // Combines intro and credits for display
20
+ $: display_items = (() => {
21
+ const items = [];
22
+ if (intro_title || intro_subtitle) {
23
+ items.push({
24
+ title: intro_title || '',
25
+ name: intro_subtitle || '',
26
+ is_intro: true
27
+ });
28
+ }
29
+ return [...items, ...credits.map(c => ({ ...c, is_intro: false }))];
30
+ })();
31
+
32
+ // Reactive font size styles
33
+ $: title_style = (is_intro: boolean) => `font-size: ${is_intro ? base_font_size * 1.2 : base_font_size * 0.8}em;`;
34
+ $: name_style = (is_intro: boolean) => `font-size: ${is_intro ? base_font_size * 1.5 : base_font_size}em;`;
35
+
36
+ // Canvas setup for Matrix effect
37
+ let canvas: HTMLCanvasElement;
38
+ let ctx: CanvasRenderingContext2D;
39
+ let contentElement: HTMLElement | null;
40
+ const fontSize = 16;
41
+ const characters = 'アァカサタナハマヤャラワガザダバパイィキシチニヒミリヰギジヂビピウゥクスツヌフムユュルグズブヅプエェケセテネヘメレヱゲゼデベペオォコソトノホモヨョロヲゴゾドボポヴッン01';
42
+ let columns: number;
43
+ let drops: number[] = [];
44
+ let animationFrameId: number;
45
+
46
+ // Initialize canvas and drops
47
+ function setup() {
48
+ if (!canvas) return;
49
+ const parent = canvas.parentElement;
50
+ if (parent) {
51
+ canvas.width = parent.clientWidth;
52
+ canvas.height = parent.clientHeight;
53
+ }
54
+ ctx = canvas.getContext('2d')!;
55
+ columns = Math.floor(canvas.width / fontSize);
56
+ drops = Array(columns).fill(1);
57
+ }
58
+
59
+ // Draw Matrix falling characters
60
+ function drawMatrix() {
61
+ if (!ctx) return;
62
+ ctx.fillStyle = 'rgba(0, 0, 0, 0.05)';
63
+ ctx.fillRect(0, 0, canvas.width, canvas.height);
64
+ ctx.fillStyle = '#0F0';
65
+ ctx.font = `${fontSize}px monospace`;
66
+ for (let i = 0; i < drops.length; i++) {
67
+ const text = characters.charAt(Math.floor(Math.random() * characters.length));
68
+ ctx.fillText(text, i * fontSize, drops[i] * fontSize);
69
+ if (drops[i] * fontSize > canvas.height && Math.random() > 0.975) {
70
+ drops[i] = 0;
71
+ }
72
+ drops[i]++;
73
+ }
74
+ animationFrameId = requestAnimationFrame(drawMatrix);
75
+ }
76
+
77
+ // Reset credits animation
78
+ function resetCreditsAnimation() {
79
+ if (contentElement) {
80
+ contentElement.style.animation = 'none';
81
+ void contentElement.offsetHeight; // Trigger reflow
82
+ contentElement.style.animation = '';
83
+ }
84
+ }
85
+
86
+ // Setup canvas and animation on mount
87
+ onMount(() => {
88
+ setup();
89
+ drawMatrix();
90
+ resetCreditsAnimation();
91
+ const resizeObserver = new ResizeObserver(() => {
92
+ cancelAnimationFrame(animationFrameId);
93
+ setup();
94
+ drawMatrix();
95
+ });
96
+ if (canvas.parentElement) {
97
+ resizeObserver.observe(canvas.parentElement);
98
+ }
99
+ return () => {
100
+ cancelAnimationFrame(animationFrameId);
101
+ if (canvas.parentElement) {
102
+ resizeObserver.unobserve(canvas.parentElement);
103
+ }
104
+ };
105
+ });
106
+
107
+ // Reset animation on prop changes
108
+ $: credits, speed, intro_title, intro_subtitle, resetCreditsAnimation();
109
+
110
+ // Cleanup on destroy
111
+ onDestroy(() => {
112
+ contentElement = null;
113
+ });
114
+ </script>
115
+
116
+ <div class="matrix-container">
117
+ <canvas bind:this={canvas}></canvas>
118
+ <div class="credits-scroll-overlay">
119
+ <div class="credits-content" bind:this={contentElement} style="--animation-duration: {speed}s;">
120
+ {#each display_items as item}
121
+ <div class="credit-block" class:intro-block={item.is_intro}>
122
+ <div style={title_style(item.is_intro)} class="title">{item.title}</div>
123
+ {#if item.name}
124
+ <div style={name_style(item.is_intro)} class="name">{item.name}</div>
125
+ {/if}
126
+ </div>
127
+ {/each}
128
+ </div>
129
+ </div>
130
+ </div>
131
+
132
+ <style>
133
+ /* Container for Matrix effect */
134
+ .matrix-container {
135
+ width: 100%;
136
+ height: 100%;
137
+ position: relative;
138
+ overflow: hidden;
139
+ }
140
+ /* Canvas for falling characters */
141
+ canvas {
142
+ display: block;
143
+ position: absolute;
144
+ top: 0;
145
+ left: 0;
146
+ width: 100%;
147
+ height: 100%;
148
+ z-index: 1;
149
+ }
150
+ /* Overlay for scrolling credits */
151
+ .credits-scroll-overlay {
152
+ position: absolute;
153
+ top: 0;
154
+ left: 0;
155
+ width: 100%;
156
+ height: 100%;
157
+ z-index: 2;
158
+ color: #fff;
159
+ font-family: monospace;
160
+ text-align: center;
161
+ -webkit-mask-image: linear-gradient(transparent, black 20%, black 80%, transparent);
162
+ mask-image: linear-gradient(transparent, black 20%, black 80%, transparent);
163
+ }
164
+ /* Scrolling credits container */
165
+ .credits-content {
166
+ position: absolute;
167
+ width: 100%;
168
+ bottom: 0;
169
+ transform: translateY(100%);
170
+ animation: scroll-from-bottom var(--animation-duration) linear infinite;
171
+ }
172
+ @keyframes scroll-from-bottom {
173
+ from { transform: translateY(100%); }
174
+ to { transform: translateY(-100%); }
175
+ }
176
+ /* Intro block spacing */
177
+ .credit-block.intro-block { margin-bottom: 5rem; }
178
+ /* Credit block spacing */
179
+ .credit-block { margin-bottom: 2.5em; }
180
+ /* Title styling */
181
+ .title {
182
+ color: #0F0;
183
+ text-transform: uppercase;
184
+ opacity: 0.8;
185
+ }
186
+ /* Name styling */
187
+ .name {
188
+ font-weight: bold;
189
+ color: #5F5;
190
+ text-shadow: 0 0 5px #0F0;
191
+ }
192
+ </style>
src/frontend/shared/ScrollEffect.svelte ADDED
@@ -0,0 +1,108 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <script lang="ts">
2
+ /**
3
+ * Props for the ScrollEffect component.
4
+ * @typedef {Object} Props
5
+ * @property {Array<{title: string, name: string}>} credits - List of credits with title and name.
6
+ * @property {number} speed - Animation speed in seconds.
7
+ * @property {number} base_font_size - Base font size in rem (default: 1.5).
8
+ * @property {string | null} background_color - Background color (default: black).
9
+ * @property {string | null} title_color - Title text color (default: white).
10
+ * @property {string | null} name_color - Name text color (default: white).
11
+ * @property {string | null} intro_title - Optional intro title.
12
+ * @property {string | null} intro_subtitle - Optional intro subtitle.
13
+ */
14
+ export let credits: Props['credits'];
15
+ export let speed: number;
16
+ export let base_font_size: number = 1.5;
17
+ export let background_color: string | null = null;
18
+ export let title_color: string | null = null;
19
+ export let name_color: string | null = null;
20
+ export let intro_title: string | null = null;
21
+ export let intro_subtitle: string | null = null;
22
+
23
+ // Flag to trigger animation reset
24
+ let reset = false;
25
+
26
+ // Combine intro and credits for display
27
+ $: display_items = (() => {
28
+ const items = [];
29
+ if (intro_title || intro_subtitle) {
30
+ items.push({
31
+ title: intro_title || '',
32
+ name: intro_subtitle || '',
33
+ is_intro: true
34
+ });
35
+ }
36
+ return [...items, ...credits.map(c => ({ ...c, is_intro: false }))];
37
+ })();
38
+
39
+ // Reactive styles for title and name
40
+ $: title_style = (is_intro: boolean) => `color: ${title_color || 'white'}; font-size: ${is_intro ? base_font_size * 1.5 : base_font_size}rem;`;
41
+ $: name_style = (is_intro: boolean) => `color: ${name_color || 'white'}; font-size: ${is_intro ? base_font_size * 0.9 : base_font_size * 0.8}rem;`;
42
+
43
+ // Reset animation on prop changes
44
+ function resetAnimation() {
45
+ reset = true;
46
+ setTimeout(() => (reset = false), 0);
47
+ }
48
+
49
+
50
+
51
+ // Trigger reset on prop changes
52
+ $: credits, speed, resetAnimation();
53
+ </script>
54
+
55
+ <div class="wrapper" style:--animation-duration="{speed}s" style:background={background_color || 'black'}>
56
+ {#if !reset}
57
+ <div class="credits-container">
58
+ {#each display_items as item}
59
+ <div class="credit" class:intro-block={item.is_intro}>
60
+ <h2 style={title_style(item.is_intro)}>{item.title}</h2>
61
+ {#if item.name}<p style={name_style(item.is_intro)}>{item.name}</p>{/if}
62
+ </div>
63
+ {/each}
64
+ </div>
65
+ {/if}
66
+ </div>
67
+
68
+ <style>
69
+ /* Container for scrolling credits */
70
+ .wrapper {
71
+ width: 100%;
72
+ height: 100%;
73
+ overflow: hidden;
74
+ position: relative;
75
+ font-family: sans-serif;
76
+ }
77
+ /* Intro block styling */
78
+ .credit.intro-block {
79
+ margin-bottom: 5rem;
80
+ text-align: center;
81
+ }
82
+ /* Credits container with scroll animation */
83
+ .credits-container {
84
+ position: absolute;
85
+ bottom: 0;
86
+ transform: translateY(100%);
87
+ width: 100%;
88
+ text-align: center;
89
+ animation: scroll var(--animation-duration) linear infinite;
90
+ }
91
+ /* Individual credit block */
92
+ .credit {
93
+ margin-bottom: 2rem;
94
+ }
95
+ .credit h2 {
96
+ margin: 0.5rem 0;
97
+ font-family: sans-serif;
98
+ }
99
+ .credit p {
100
+ margin: 0.5rem 0;
101
+ font-family: sans-serif;
102
+ }
103
+ /* Scroll animation keyframes */
104
+ @keyframes scroll {
105
+ 0% { transform: translateY(100%); }
106
+ 100% { transform: translateY(-100%); }
107
+ }
108
+ </style>
src/frontend/shared/StarWarsEffect.svelte ADDED
@@ -0,0 +1,156 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <script lang="ts">
2
+ import { onMount, onDestroy } from 'svelte';
3
+
4
+ /**
5
+ * Props for the StarWarsEffect component.
6
+ * @typedef {Object} Props
7
+ * @property {Array<{title: string, name: string}>} credits - List of credits with title and name.
8
+ * @property {number} speed - Animation speed in seconds (default: 40).
9
+ * @property {number} base_font_size - Base font size in rem (default: 1.5).
10
+ * @property {string | null} background_color - Background color (default: black).
11
+ * @property {string | null} title_color - Title text color (default: #feda4a).
12
+ * @property {string | null} name_color - Name text color (default: #feda4a).
13
+ * @property {string | null} intro_title - Optional intro title.
14
+ * @property {string | null} intro_subtitle - Optional intro subtitle.
15
+ */
16
+ export let credits: Props['credits'];
17
+ export let speed: number = 40;
18
+ export let base_font_size: number = 1.5;
19
+ export let background_color: string | null = null;
20
+ export let title_color: string | null = null;
21
+ export let name_color: string | null = null;
22
+ export let intro_title: string | null = null;
23
+ export let intro_subtitle: string | null = null;
24
+
25
+ // Reactive styles for title and name
26
+ $: title_style = (is_intro: boolean) => `color: ${title_color || '#feda4a'}; font-size: ${is_intro ? base_font_size * 1.5 : base_font_size}rem !important;`;
27
+ $: name_style = (is_intro: boolean) => `color: ${name_color || '#feda4a'}; font-size: ${is_intro ? base_font_size * 0.9 : base_font_size * 0.7}rem !important;`;
28
+
29
+ // Combine intro and credits for display
30
+ $: display_items = (() => {
31
+ const items = [];
32
+ if (intro_title || intro_subtitle) {
33
+ items.push({
34
+ title: intro_title || '',
35
+ name: intro_subtitle || '',
36
+ is_intro: true
37
+ });
38
+ }
39
+ return [...items, ...credits.map(c => ({ ...c, is_intro: false }))];
40
+ })();
41
+
42
+ // Element for animation reset
43
+ let crawlElement: HTMLElement | null;
44
+
45
+ // Reset animation on prop changes
46
+ function resetAnimation() {
47
+ if (crawlElement) {
48
+ crawlElement.style.animation = 'none';
49
+ void crawlElement.offsetHeight; // Trigger reflow
50
+ crawlElement.style.animation = '';
51
+ }
52
+ }
53
+
54
+ // Initialize animation on mount
55
+ onMount(() => {
56
+ resetAnimation();
57
+ return () => {};
58
+ });
59
+
60
+ // Trigger reset on prop changes
61
+ $: credits, speed, base_font_size, background_color, title_color, name_color, intro_title, intro_subtitle, resetAnimation();
62
+
63
+ // Cleanup on destroy
64
+ onDestroy(() => {
65
+ crawlElement = null;
66
+ });
67
+
68
+ // Generate star shadows for background
69
+ const generate_star_shadows = (count: number, size: string) => {
70
+ let shadows = '';
71
+ for (let i = 0; i < count; i++) {
72
+ shadows += `${Math.random() * 2000}px ${Math.random() * 2000}px ${size} white, `;
73
+ }
74
+ return shadows.slice(0, -2);
75
+ };
76
+
77
+ const small_stars = generate_star_shadows(200, '1px');
78
+ const medium_stars = generate_star_shadows(100, '2px');
79
+ const large_stars = generate_star_shadows(50, '3px');
80
+ </script>
81
+
82
+ <div class="viewport" style:background={background_color || 'black'}>
83
+ <!-- Star layers for background -->
84
+ <div class="stars small" style="box-shadow: {small_stars};"></div>
85
+ <div class="stars medium" style="box-shadow: {medium_stars};"></div>
86
+ <div class="stars large" style="box-shadow: {large_stars};"></div>
87
+
88
+ <!-- Crawling credits -->
89
+ <div class="crawl" bind:this={crawlElement} style="--animation-duration: {speed}s;">
90
+ {#each display_items as item}
91
+ <div class="credit" class:intro-block={item.is_intro}>
92
+ <h2 style={title_style(item.is_intro)}>{item.title}</h2>
93
+ {#if item.name}<p style={name_style(item.is_intro)}>{item.name}</p>{/if}
94
+ </div>
95
+ {/each}
96
+ </div>
97
+ </div>
98
+
99
+ <style>
100
+ /* Container with perspective for 3D effect */
101
+ .viewport {
102
+ width: 100%;
103
+ height: 100%;
104
+ position: relative;
105
+ overflow: hidden;
106
+ perspective: 400px;
107
+ -webkit-mask-image: linear-gradient(to bottom, black 60%, transparent 100%);
108
+ mask-image: linear-gradient(to bottom, black 60%, transparent 100%);
109
+ font-family: "Droid Sans", sans-serif;
110
+ font-weight: bold;
111
+ }
112
+ /* Star layers with twinkling animation */
113
+ .stars {
114
+ position: absolute;
115
+ top: 0;
116
+ left: 0;
117
+ width: 1px;
118
+ height: 1px;
119
+ background: transparent;
120
+ z-index: 0;
121
+ animation: twinkle 10s linear infinite;
122
+ }
123
+ .stars.small { animation-duration: 10s; }
124
+ .stars.medium { animation-duration: 15s; }
125
+ .stars.large { animation-duration: 20s; }
126
+ @keyframes twinkle {
127
+ 0% { opacity: 0.6; }
128
+ 50% { opacity: 1; }
129
+ 100% { opacity: 0.6; }
130
+ }
131
+ /* Crawling text container */
132
+ .crawl {
133
+ position: absolute;
134
+ width: 100%;
135
+ bottom: 0;
136
+ transform-origin: 50% 100%;
137
+ animation: crawl-animation var(--animation-duration) linear infinite;
138
+ z-index: 1;
139
+ text-align: center;
140
+ }
141
+ /* Crawl animation with 3D transform */
142
+ @keyframes crawl-animation {
143
+ 0% { transform: rotateX(60deg) translateY(100%) translateZ(100px); opacity: 1; }
144
+ 100% { transform: rotateX(60deg) translateY(-150%) translateZ(-1200px); opacity: 1; }
145
+ }
146
+ /* Intro block spacing */
147
+ .credit.intro-block { margin-bottom: 5rem; }
148
+ /* Credit block spacing */
149
+ .credit { margin-bottom: 2rem; }
150
+ /* Text styling */
151
+ h2, p {
152
+ margin: 0.5rem 0;
153
+ padding: 0;
154
+ white-space: nowrap;
155
+ }
156
+ </style>
src/frontend/tsconfig.json ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "compilerOptions": {
3
+ "allowJs": true,
4
+ "checkJs": true,
5
+ "esModuleInterop": true,
6
+ "forceConsistentCasingInFileNames": true,
7
+ "resolveJsonModule": true,
8
+ "skipLibCheck": true,
9
+ "sourceMap": true,
10
+ "strict": true,
11
+ "verbatimModuleSyntax": true
12
+ },
13
+ "exclude": ["node_modules", "dist", "./gradio.config.js"]
14
+ }
src/package-lock.json ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ {
2
+ "name": "creditspanel",
3
+ "lockfileVersion": 3,
4
+ "requires": true,
5
+ "packages": {}
6
+ }
src/pyproject.toml ADDED
@@ -0,0 +1,51 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [build-system]
2
+ requires = [
3
+ "hatchling",
4
+ "hatch-requirements-txt",
5
+ "hatch-fancy-pypi-readme>=22.5.0",
6
+ ]
7
+ build-backend = "hatchling.build"
8
+
9
+ [project]
10
+ name = "gradio_creditspanel"
11
+ version = "0.0.1"
12
+ description = "Credits Panel for Gradio UI"
13
+ readme = "README.md"
14
+ license = "apache-2.0"
15
+ requires-python = ">=3.10"
16
+ authors = [{ name = "Eliseu Silva", email = "elismasilva@gmail.com" }]
17
+ keywords = ["gradio-custom-component", "gradio-template-HTML"]
18
+ # Add dependencies here
19
+ dependencies = ["gradio>=4.0,<6.0"]
20
+ classifiers = [
21
+ 'Development Status :: 3 - Alpha',
22
+ 'Operating System :: OS Independent',
23
+ 'Programming Language :: Python :: 3',
24
+ 'Programming Language :: Python :: 3 :: Only',
25
+ 'Programming Language :: Python :: 3.8',
26
+ 'Programming Language :: Python :: 3.9',
27
+ 'Programming Language :: Python :: 3.10',
28
+ 'Programming Language :: Python :: 3.11',
29
+ 'Topic :: Scientific/Engineering',
30
+ 'Topic :: Scientific/Engineering :: Artificial Intelligence',
31
+ 'Topic :: Scientific/Engineering :: Visualization',
32
+ ]
33
+
34
+ # The repository and space URLs are optional, but recommended.
35
+ # Adding a repository URL will create a badge in the auto-generated README that links to the repository.
36
+ # Adding a space URL will create a badge in the auto-generated README that links to the space.
37
+ # This will make it easy for people to find your deployed demo or source code when they
38
+ # encounter your project in the wild.
39
+
40
+ # [project.urls]
41
+ # repository = "your github repository"
42
+ # space = "your space url"
43
+
44
+ [project.optional-dependencies]
45
+ dev = ["build", "twine"]
46
+
47
+ [tool.hatch.build]
48
+ artifacts = ["/backend/gradio_creditspanel/templates", "*.pyi", "/\\backend\\gradio_creditspanel\\templates"]
49
+
50
+ [tool.hatch.build.targets.wheel]
51
+ packages = ["/backend/gradio_creditspanel"]