elismasilva commited on
Commit
919197a
·
verified ·
1 Parent(s): 0a1b3f9

Upload folder using huggingface_hub

Browse files
README.md CHANGED
@@ -10,7 +10,8 @@ app_file: space.py
10
  ---
11
 
12
  # `gradio_creditspanel`
13
- <a href="https://pypi.org/project/gradio_creditspanel/" target="_blank"><img alt="PyPI - Version" src="https://img.shields.io/pypi/v/gradio_creditspanel"></a>
 
14
 
15
  Credits Panel for Gradio UI
16
 
@@ -43,7 +44,7 @@ def setup_demo_files():
43
  with open("./assets/logo.webp", "w") as f:
44
  f.write("Placeholder WebP logo")
45
 
46
- # --- UPDATED: Credits list with sections ---
47
  credits_list = [
48
  {"section_title": "Project Leadership"},
49
  {"title": "Project Manager", "name": "Emma Thompson"},
@@ -72,6 +73,8 @@ DEFAULT_SPEEDS = {
72
  "starwars": 70.0,
73
  "matrix": 40.0
74
  }
 
 
75
 
76
  def update_panel(
77
  effect: str,
@@ -89,39 +92,52 @@ def update_panel(
89
  logo_height: str | None,
90
  scroll_background_color: str | None,
91
  scroll_title_color: str | None,
 
92
  scroll_name_color: str | None,
93
  layout_style: str,
94
  title_uppercase: bool,
95
  name_uppercase: bool,
96
  section_title_uppercase: bool,
97
- swap_font_sizes: bool
 
 
98
  ) -> dict:
99
  """Callback function that updates all properties of the CreditsPanel component."""
100
- return gr.update(
101
- visible=True,
102
- effect=effect,
103
- speed=speed,
104
- base_font_size=base_font_size,
105
- intro_title=intro_title,
106
- intro_subtitle=intro_subtitle,
107
- sidebar_position=sidebar_position,
108
- show_logo=show_logo,
109
- show_licenses=show_licenses,
110
- show_credits=show_credits,
111
- logo_position=logo_position,
112
- logo_sizing=logo_sizing,
113
- logo_width=logo_width,
114
- logo_height=logo_height,
115
- scroll_background_color=scroll_background_color,
116
- scroll_title_color=scroll_title_color,
117
- scroll_name_color=scroll_name_color,
118
- layout_style=layout_style,
119
- title_uppercase=title_uppercase,
120
- name_uppercase=name_uppercase,
121
- section_title_uppercase=section_title_uppercase,
122
- swap_font_sizes_on_two_column=swap_font_sizes,
123
- value=credits_list
124
- )
 
 
 
 
 
 
 
 
 
 
125
 
126
  def update_ui_on_effect_change(effect: str) -> tuple[float, float]:
127
  """Updates sliders to sensible defaults when the animation effect is changed."""
@@ -163,6 +179,14 @@ with gr.Blocks(theme=gr.themes.Ocean(), title="CreditsPanel Demo") as demo:
163
  name_uppercase_checkbox = gr.Checkbox(label="Name Uppercase", value=False)
164
  section_title_uppercase_checkbox = gr.Checkbox(label="Section Uppercase", value=True)
165
 
 
 
 
 
 
 
 
 
166
  gr.Markdown("### Intro Text")
167
  intro_title_input = gr.Textbox(label="Intro Title", value="Gradio")
168
  intro_subtitle_input = gr.Textbox(label="Intro Subtitle", value="The best UI framework")
@@ -182,19 +206,20 @@ with gr.Blocks(theme=gr.themes.Ocean(), title="CreditsPanel Demo") as demo:
182
  gr.Markdown("### Color Settings (Scroll Effect)")
183
  scroll_background_color = gr.ColorPicker(label="Background Color", value="#000000")
184
  scroll_title_color = gr.ColorPicker(label="Title Color", value="#FFFFFF")
 
185
  scroll_name_color = gr.ColorPicker(label="Name Color", value="#FFFFFF")
186
 
187
- panel = CreditsPanel(
 
188
  credits=credits_list,
189
  licenses=license_paths,
190
  effect="scroll",
191
- height=500,
192
  speed=DEFAULT_SPEEDS["scroll"],
193
  base_font_size=1.5,
194
  intro_title="Gradio",
195
  intro_subtitle="The best UI framework",
196
  sidebar_position="right",
197
- logo_path="./assets/logo.webp",
198
  show_logo=True,
199
  show_licenses=True,
200
  show_credits=True,
@@ -205,11 +230,14 @@ with gr.Blocks(theme=gr.themes.Ocean(), title="CreditsPanel Demo") as demo:
205
  scroll_background_color="#000000",
206
  scroll_title_color="#FFFFFF",
207
  scroll_name_color="#FFFFFF",
 
208
  layout_style="stacked",
209
  title_uppercase=False,
210
  name_uppercase=False,
211
  section_title_uppercase=True,
212
  swap_font_sizes_on_two_column=False,
 
 
213
  )
214
 
215
  inputs = [
@@ -227,15 +255,23 @@ with gr.Blocks(theme=gr.themes.Ocean(), title="CreditsPanel Demo") as demo:
227
  logo_width_input,
228
  logo_height_input,
229
  scroll_background_color,
230
- scroll_title_color,
 
231
  scroll_name_color,
232
  layout_style_radio,
233
  title_uppercase_checkbox,
234
  name_uppercase_checkbox,
235
  section_title_uppercase_checkbox,
236
- swap_sizes_checkbox
 
 
237
  ]
238
 
 
 
 
 
 
239
  layout_style_radio.change(
240
  fn=toggle_swap_checkbox_visibility,
241
  inputs=layout_style_radio,
@@ -270,7 +306,9 @@ if __name__ == "__main__":
270
  <td align="left" style="width: 25%;">
271
 
272
  ```python
273
- Any
 
 
274
  ```
275
 
276
  </td>
@@ -279,21 +317,11 @@ Any
279
  </tr>
280
 
281
  <tr>
282
- <td align="left"><code>credits</code></td>
283
  <td align="left" style="width: 25%;">
284
 
285
  ```python
286
- typing.Union[
287
- typing.List[typing.Dict[str, str]],
288
- typing.Callable,
289
- NoneType,
290
- ][
291
- typing.List[typing.Dict[str, str]][
292
- typing.Dict[str, str][str, str]
293
- ],
294
- Callable,
295
- None,
296
- ]
297
  ```
298
 
299
  </td>
@@ -302,7 +330,7 @@ typing.Union[
302
  </tr>
303
 
304
  <tr>
305
- <td align="left"><code>height</code></td>
306
  <td align="left" style="width: 25%;">
307
 
308
  ```python
@@ -315,11 +343,21 @@ int | str | None
315
  </tr>
316
 
317
  <tr>
318
- <td align="left"><code>width</code></td>
319
  <td align="left" style="width: 25%;">
320
 
321
  ```python
322
- int | str | None
 
 
 
 
 
 
 
 
 
 
323
  ```
324
 
325
  </td>
@@ -566,6 +604,19 @@ str | None
566
  <td align="left">None</td>
567
  </tr>
568
 
 
 
 
 
 
 
 
 
 
 
 
 
 
569
  <tr>
570
  <td align="left"><code>layout_style</code></td>
571
  <td align="left" style="width: 25%;">
@@ -631,6 +682,32 @@ bool
631
  <td align="left">None</td>
632
  </tr>
633
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
634
  <tr>
635
  <td align="left"><code>label</code></td>
636
  <td align="left" style="width: 25%;">
@@ -844,7 +921,6 @@ The impact on the users predict function varies depending on whether the compone
844
 
845
  The code snippet below is accurate in cases where the component is used as both an input and an output.
846
 
847
- - **As output:** Is passed, dict[str, Any] | None: The input payload, returned unchanged.
848
 
849
 
850
  ```python
@@ -852,7 +928,9 @@ The code snippet below is accurate in cases where the component is used as both
852
  value: typing.Optional[typing.Dict[str, typing.Any]][
853
  typing.Dict[str, typing.Any][str, Any], None
854
  ]
855
- ) -> Any:
 
 
856
  return value
857
  ```
858
 
 
10
  ---
11
 
12
  # `gradio_creditspanel`
13
+ <img alt="Static Badge" src="https://img.shields.io/badge/version%20-%200.0.4%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
 
16
  Credits Panel for Gradio UI
17
 
 
44
  with open("./assets/logo.webp", "w") as f:
45
  f.write("Placeholder WebP logo")
46
 
47
+ # --- Credits list with sections ---
48
  credits_list = [
49
  {"section_title": "Project Leadership"},
50
  {"title": "Project Manager", "name": "Emma Thompson"},
 
73
  "starwars": 70.0,
74
  "matrix": 40.0
75
  }
76
+ SCROLL_LOGO_PATH = "./assets/gradio_logo_white.png"
77
+ LOGO_PATH="./assets/logo.webp"
78
 
79
  def update_panel(
80
  effect: str,
 
92
  logo_height: str | None,
93
  scroll_background_color: str | None,
94
  scroll_title_color: str | None,
95
+ scroll_section_title_color: str | None,
96
  scroll_name_color: str | None,
97
  layout_style: str,
98
  title_uppercase: bool,
99
  name_uppercase: bool,
100
  section_title_uppercase: bool,
101
+ swap_font_sizes: bool,
102
+ show_scroll_logo: bool,
103
+ scroll_logo_height: str | None
104
  ) -> dict:
105
  """Callback function that updates all properties of the CreditsPanel component."""
106
+
107
+ scroll_logo_path = SCROLL_LOGO_PATH if show_scroll_logo else None
108
+
109
+ if not scroll_logo_height:
110
+ scroll_logo_height = "120px"
111
+
112
+ return {
113
+ "credits": credits_list,
114
+ "licenses": license_paths,
115
+ "effect": effect,
116
+ "speed": speed,
117
+ "base_font_size": base_font_size,
118
+ "intro_title": intro_title,
119
+ "intro_subtitle": intro_subtitle,
120
+ "sidebar_position": sidebar_position,
121
+ "logo_path": LOGO_PATH,
122
+ "show_logo": show_logo,
123
+ "show_licenses": show_licenses,
124
+ "show_credits": show_credits,
125
+ "logo_position": logo_position,
126
+ "logo_sizing": logo_sizing,
127
+ "logo_width": logo_width,
128
+ "logo_height": logo_height,
129
+ "scroll_background_color": scroll_background_color,
130
+ "scroll_title_color": scroll_title_color,
131
+ "scroll_name_color": scroll_name_color,
132
+ "scroll_section_title_color": scroll_section_title_color,
133
+ "layout_style": layout_style,
134
+ "title_uppercase": title_uppercase,
135
+ "name_uppercase": name_uppercase,
136
+ "section_title_uppercase": section_title_uppercase,
137
+ "swap_font_sizes_on_two_column": swap_font_sizes,
138
+ "scroll_logo_path": scroll_logo_path,
139
+ "scroll_logo_height": scroll_logo_height,
140
+ }
141
 
142
  def update_ui_on_effect_change(effect: str) -> tuple[float, float]:
143
  """Updates sliders to sensible defaults when the animation effect is changed."""
 
179
  name_uppercase_checkbox = gr.Checkbox(label="Name Uppercase", value=False)
180
  section_title_uppercase_checkbox = gr.Checkbox(label="Section Uppercase", value=True)
181
 
182
+ gr.Markdown("### Scrolling Logo")
183
+ show_scroll_logo_checkbox = gr.Checkbox(
184
+ label="Show Logo in Credits Roll",
185
+ value=True,
186
+ info="Toggles the logo above the intro text."
187
+ )
188
+ scroll_logo_height_input = gr.Textbox(label="Scrolling Logo Height", value="100px")
189
+
190
  gr.Markdown("### Intro Text")
191
  intro_title_input = gr.Textbox(label="Intro Title", value="Gradio")
192
  intro_subtitle_input = gr.Textbox(label="Intro Subtitle", value="The best UI framework")
 
206
  gr.Markdown("### Color Settings (Scroll Effect)")
207
  scroll_background_color = gr.ColorPicker(label="Background Color", value="#000000")
208
  scroll_title_color = gr.ColorPicker(label="Title Color", value="#FFFFFF")
209
+ scroll_section_title_color = gr.ColorPicker(label="Section Title Color", value="#FFFFFF")
210
  scroll_name_color = gr.ColorPicker(label="Name Color", value="#FFFFFF")
211
 
212
+ panel = CreditsPanel(
213
+ height=500,
214
  credits=credits_list,
215
  licenses=license_paths,
216
  effect="scroll",
 
217
  speed=DEFAULT_SPEEDS["scroll"],
218
  base_font_size=1.5,
219
  intro_title="Gradio",
220
  intro_subtitle="The best UI framework",
221
  sidebar_position="right",
222
+ logo_path=LOGO_PATH,
223
  show_logo=True,
224
  show_licenses=True,
225
  show_credits=True,
 
230
  scroll_background_color="#000000",
231
  scroll_title_color="#FFFFFF",
232
  scroll_name_color="#FFFFFF",
233
+ scroll_section_title_color="#FFFFFF",
234
  layout_style="stacked",
235
  title_uppercase=False,
236
  name_uppercase=False,
237
  section_title_uppercase=True,
238
  swap_font_sizes_on_two_column=False,
239
+ scroll_logo_path=SCROLL_LOGO_PATH,
240
+ scroll_logo_height="100px",
241
  )
242
 
243
  inputs = [
 
255
  logo_width_input,
256
  logo_height_input,
257
  scroll_background_color,
258
+ scroll_title_color,
259
+ scroll_section_title_color,
260
  scroll_name_color,
261
  layout_style_radio,
262
  title_uppercase_checkbox,
263
  name_uppercase_checkbox,
264
  section_title_uppercase_checkbox,
265
+ swap_sizes_checkbox,
266
+ show_scroll_logo_checkbox,
267
+ scroll_logo_height_input
268
  ]
269
 
270
+ demo.load(
271
+ fn=update_panel,
272
+ inputs=inputs,
273
+ outputs=panel
274
+ )
275
  layout_style_radio.change(
276
  fn=toggle_swap_checkbox_visibility,
277
  inputs=layout_style_radio,
 
306
  <td align="left" style="width: 25%;">
307
 
308
  ```python
309
+ typing.Optional[typing.Dict[str, typing.Any]][
310
+ typing.Dict[str, typing.Any][str, Any], None
311
+ ]
312
  ```
313
 
314
  </td>
 
317
  </tr>
318
 
319
  <tr>
320
+ <td align="left"><code>height</code></td>
321
  <td align="left" style="width: 25%;">
322
 
323
  ```python
324
+ int | str | None
 
 
 
 
 
 
 
 
 
 
325
  ```
326
 
327
  </td>
 
330
  </tr>
331
 
332
  <tr>
333
+ <td align="left"><code>width</code></td>
334
  <td align="left" style="width: 25%;">
335
 
336
  ```python
 
343
  </tr>
344
 
345
  <tr>
346
+ <td align="left"><code>credits</code></td>
347
  <td align="left" style="width: 25%;">
348
 
349
  ```python
350
+ typing.Union[
351
+ typing.List[typing.Dict[str, str]],
352
+ typing.Callable,
353
+ NoneType,
354
+ ][
355
+ typing.List[typing.Dict[str, str]][
356
+ typing.Dict[str, str][str, str]
357
+ ],
358
+ Callable,
359
+ None,
360
+ ]
361
  ```
362
 
363
  </td>
 
604
  <td align="left">None</td>
605
  </tr>
606
 
607
+ <tr>
608
+ <td align="left"><code>scroll_section_title_color</code></td>
609
+ <td align="left" style="width: 25%;">
610
+
611
+ ```python
612
+ str | None
613
+ ```
614
+
615
+ </td>
616
+ <td align="left"><code>None</code></td>
617
+ <td align="left">None</td>
618
+ </tr>
619
+
620
  <tr>
621
  <td align="left"><code>layout_style</code></td>
622
  <td align="left" style="width: 25%;">
 
682
  <td align="left">None</td>
683
  </tr>
684
 
685
+ <tr>
686
+ <td align="left"><code>scroll_logo_path</code></td>
687
+ <td align="left" style="width: 25%;">
688
+
689
+ ```python
690
+ str | pathlib.Path | None
691
+ ```
692
+
693
+ </td>
694
+ <td align="left"><code>None</code></td>
695
+ <td align="left">None</td>
696
+ </tr>
697
+
698
+ <tr>
699
+ <td align="left"><code>scroll_logo_height</code></td>
700
+ <td align="left" style="width: 25%;">
701
+
702
+ ```python
703
+ str
704
+ ```
705
+
706
+ </td>
707
+ <td align="left"><code>"120px"</code></td>
708
+ <td align="left">None</td>
709
+ </tr>
710
+
711
  <tr>
712
  <td align="left"><code>label</code></td>
713
  <td align="left" style="width: 25%;">
 
921
 
922
  The code snippet below is accurate in cases where the component is used as both an input and an output.
923
 
 
924
 
925
 
926
  ```python
 
928
  value: typing.Optional[typing.Dict[str, typing.Any]][
929
  typing.Dict[str, typing.Any][str, Any], None
930
  ]
931
+ ) -> typing.Optional[typing.Dict[str, typing.Any]][
932
+ typing.Dict[str, typing.Any][str, Any], None
933
+ ]:
934
  return value
935
  ```
936
 
app.py CHANGED
@@ -1,226 +1,261 @@
1
- # app.py
2
-
3
- import gradio as gr
4
- from gradio_creditspanel import CreditsPanel
5
- import os
6
-
7
- def setup_demo_files():
8
- """Creates necessary directories and dummy files for the demo."""
9
- os.makedirs("src/LICENSES", exist_ok=True)
10
- if not os.path.exists("src/LICENSES/Apache.txt"):
11
- with open("src/LICENSES/Apache.txt", "w") as f:
12
- f.write("Apache License\nVersion 2.0, January 2004...")
13
- if not os.path.exists("src/LICENSES/MIT.txt"):
14
- with open("src/LICENSES/MIT.txt", "w") as f:
15
- f.write("MIT License\nCopyright (c) 2025 Author...")
16
- os.makedirs("src/assets", exist_ok=True)
17
- if not os.path.exists("src/assets/logo.webp"):
18
- with open("src/assets/logo.webp", "w") as f:
19
- f.write("Placeholder WebP logo")
20
-
21
- # --- Credits list with sections ---
22
- credits_list = [
23
- {"section_title": "Project Leadership"},
24
- {"title": "Project Manager", "name": "Emma Thompson"},
25
- {"title": "Scrum Master", "name": "Ava Rodriguez"},
26
-
27
- {"section_title": "Development Team"},
28
- {"title": "Lead Developer", "name": "John Doe"},
29
- {"title": "Senior Backend Engineer", "name": "Michael Chen"},
30
- {"title": "Frontend Developer", "name": "Sarah Johnson"},
31
- {"title": "UI/UX Designer", "name": "Jane Smith"},
32
- {"title": "Database Architect", "name": "Alex Ray"},
33
-
34
- {"section_title": "Quality & Operations"},
35
- {"title": "DevOps Engineer", "name": "Liam Patel"},
36
- {"title": "Quality Assurance Lead", "name": "Sam Wilson"},
37
- {"title": "Test Automation Engineer", "name": "Olivia Brown"},
38
- ]
39
-
40
- license_paths = {
41
- "Gradio Framework": "src/LICENSES/Apache.txt",
42
- "This Component": "src/LICENSES/MIT.txt"
43
- }
44
-
45
- DEFAULT_SPEEDS = {
46
- "scroll": 40.0,
47
- "starwars": 70.0,
48
- "matrix": 40.0
49
- }
50
-
51
- def update_panel(
52
- effect: str,
53
- speed: float,
54
- base_font_size: float,
55
- intro_title: str,
56
- intro_subtitle: str,
57
- sidebar_position: str,
58
- show_logo: bool,
59
- show_licenses: bool,
60
- show_credits: bool,
61
- logo_position: str,
62
- logo_sizing: str,
63
- logo_width: str | None,
64
- logo_height: str | None,
65
- scroll_background_color: str | None,
66
- scroll_title_color: str | None,
67
- scroll_name_color: str | None,
68
- layout_style: str,
69
- title_uppercase: bool,
70
- name_uppercase: bool,
71
- section_title_uppercase: bool,
72
- swap_font_sizes: bool
73
- ) -> dict:
74
- """Callback function that updates all properties of the CreditsPanel component."""
75
- return gr.update(
76
- visible=True,
77
- effect=effect,
78
- speed=speed,
79
- base_font_size=base_font_size,
80
- intro_title=intro_title,
81
- intro_subtitle=intro_subtitle,
82
- sidebar_position=sidebar_position,
83
- show_logo=show_logo,
84
- show_licenses=show_licenses,
85
- show_credits=show_credits,
86
- logo_position=logo_position,
87
- logo_sizing=logo_sizing,
88
- logo_width=logo_width,
89
- logo_height=logo_height,
90
- scroll_background_color=scroll_background_color,
91
- scroll_title_color=scroll_title_color,
92
- scroll_name_color=scroll_name_color,
93
- layout_style=layout_style,
94
- title_uppercase=title_uppercase,
95
- name_uppercase=name_uppercase,
96
- section_title_uppercase=section_title_uppercase,
97
- swap_font_sizes_on_two_column=swap_font_sizes,
98
- value=credits_list
99
- )
100
-
101
- def update_ui_on_effect_change(effect: str) -> tuple[float, float]:
102
- """Updates sliders to sensible defaults when the animation effect is changed."""
103
- font_size = 1.5
104
- if effect == "starwars":
105
- font_size = 3.8
106
- speed = DEFAULT_SPEEDS.get(effect, 40.0)
107
- return speed, font_size
108
-
109
- def toggle_swap_checkbox_visibility(layout: str) -> dict:
110
- """Show the swap checkbox only for the two-column layout."""
111
- return gr.update(visible=(layout == 'two-column'))
112
-
113
- with gr.Blocks(theme=gr.themes.Ocean(), title="CreditsPanel Demo") as demo:
114
- gr.Markdown(
115
- """
116
- # Interactive CreditsPanel Demo
117
- Use the sidebar controls to customize the `CreditsPanel` component in real-time.
118
- """
119
- )
120
-
121
- with gr.Sidebar(position="right"):
122
- gr.Markdown("### Effects Settings")
123
- effect_radio = gr.Radio(["scroll", "starwars", "matrix"], label="Animation Effect", value="scroll")
124
- speed_slider = gr.Slider(minimum=5.0, maximum=100.0, step=1.0, value=DEFAULT_SPEEDS["scroll"], label="Animation Speed")
125
- font_size_slider = gr.Slider(minimum=1.0, maximum=10.0, step=0.1, value=1.5, label="Base Font Size")
126
-
127
- gr.Markdown("### Credits Layout Settings")
128
- layout_style_radio = gr.Radio(
129
- ["stacked", "two-column"], label="Layout Style", value="stacked",
130
- info="How to display titles and names."
131
- )
132
- swap_sizes_checkbox = gr.Checkbox(
133
- label="Swap Title/Name Font Sizes", value=False,
134
- info="Emphasize name over title in two-column layout.",
135
- visible=False
136
- )
137
- title_uppercase_checkbox = gr.Checkbox(label="Title Uppercase", value=False)
138
- name_uppercase_checkbox = gr.Checkbox(label="Name Uppercase", value=False)
139
- section_title_uppercase_checkbox = gr.Checkbox(label="Section Uppercase", value=True)
140
-
141
- gr.Markdown("### Intro Text")
142
- intro_title_input = gr.Textbox(label="Intro Title", value="Gradio")
143
- intro_subtitle_input = gr.Textbox(label="Intro Subtitle", value="The best UI framework")
144
-
145
- gr.Markdown("### Layout & Visibility")
146
- sidebar_position_radio = gr.Radio(["right", "bottom"], label="Sidebar Position", value="right")
147
- show_logo_checkbox = gr.Checkbox(label="Show Logo", value=True)
148
- show_licenses_checkbox = gr.Checkbox(label="Show Licenses", value=True)
149
- show_credits_checkbox = gr.Checkbox(label="Show Credits", value=True)
150
-
151
- gr.Markdown("### Logo Customization")
152
- logo_position_radio = gr.Radio(["left", "center", "right"], label="Logo Position", value="center")
153
- logo_sizing_radio = gr.Radio(["stretch", "crop", "resize"], label="Logo Sizing", value="resize")
154
- logo_width_input = gr.Textbox(label="Logo Width", value="200px")
155
- logo_height_input = gr.Textbox(label="Logo Height", value="100px")
156
-
157
- gr.Markdown("### Color Settings (Scroll Effect)")
158
- scroll_background_color = gr.ColorPicker(label="Background Color", value="#000000")
159
- scroll_title_color = gr.ColorPicker(label="Title Color", value="#FFFFFF")
160
- scroll_name_color = gr.ColorPicker(label="Name Color", value="#FFFFFF")
161
-
162
- panel = CreditsPanel(
163
- credits=credits_list,
164
- licenses=license_paths,
165
- effect="scroll",
166
- height=500,
167
- speed=DEFAULT_SPEEDS["scroll"],
168
- base_font_size=1.5,
169
- intro_title="Gradio",
170
- intro_subtitle="The best UI framework",
171
- sidebar_position="right",
172
- logo_path="src/assets/logo.webp",
173
- show_logo=True,
174
- show_licenses=True,
175
- show_credits=True,
176
- logo_position="center",
177
- logo_sizing="resize",
178
- logo_width="200px",
179
- logo_height="100px",
180
- scroll_background_color="#000000",
181
- scroll_title_color="#FFFFFF",
182
- scroll_name_color="#FFFFFF",
183
- layout_style="stacked",
184
- title_uppercase=False,
185
- name_uppercase=False,
186
- section_title_uppercase=True,
187
- swap_font_sizes_on_two_column=False,
188
- )
189
-
190
- inputs = [
191
- effect_radio,
192
- speed_slider,
193
- font_size_slider,
194
- intro_title_input,
195
- intro_subtitle_input,
196
- sidebar_position_radio,
197
- show_logo_checkbox,
198
- show_licenses_checkbox,
199
- show_credits_checkbox,
200
- logo_position_radio,
201
- logo_sizing_radio,
202
- logo_width_input,
203
- logo_height_input,
204
- scroll_background_color,
205
- scroll_title_color,
206
- scroll_name_color,
207
- layout_style_radio,
208
- title_uppercase_checkbox,
209
- name_uppercase_checkbox,
210
- section_title_uppercase_checkbox,
211
- swap_sizes_checkbox
212
- ]
213
-
214
- layout_style_radio.change(
215
- fn=toggle_swap_checkbox_visibility,
216
- inputs=layout_style_radio,
217
- outputs=swap_sizes_checkbox
218
- )
219
- effect_radio.change(fn=update_ui_on_effect_change, inputs=effect_radio, outputs=[speed_slider, font_size_slider])
220
-
221
- for input_component in inputs:
222
- input_component.change(fn=update_panel, inputs=inputs, outputs=panel)
223
-
224
- if __name__ == "__main__":
225
- setup_demo_files()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
226
  demo.launch()
 
1
+ # app.py
2
+
3
+ import gradio as gr
4
+ from gradio_creditspanel import CreditsPanel
5
+ import os
6
+
7
+ def setup_demo_files():
8
+ """Creates necessary directories and dummy files for the demo."""
9
+ os.makedirs("LICENSES", exist_ok=True)
10
+ if not os.path.exists("LICENSES/Apache.txt"):
11
+ with open("LICENSES/Apache.txt", "w") as f:
12
+ f.write("Apache License\nVersion 2.0, January 2004...")
13
+ if not os.path.exists("LICENSES/MIT.txt"):
14
+ with open("LICENSES/MIT.txt", "w") as f:
15
+ f.write("MIT License\nCopyright (c) 2025 Author...")
16
+ os.makedirs("assets", exist_ok=True)
17
+ if not os.path.exists("./assets/logo.webp"):
18
+ with open("./assets/logo.webp", "w") as f:
19
+ f.write("Placeholder WebP logo")
20
+
21
+ # --- Credits list with sections ---
22
+ credits_list = [
23
+ {"section_title": "Project Leadership"},
24
+ {"title": "Project Manager", "name": "Emma Thompson"},
25
+ {"title": "Scrum Master", "name": "Ava Rodriguez"},
26
+
27
+ {"section_title": "Development Team"},
28
+ {"title": "Lead Developer", "name": "John Doe"},
29
+ {"title": "Senior Backend Engineer", "name": "Michael Chen"},
30
+ {"title": "Frontend Developer", "name": "Sarah Johnson"},
31
+ {"title": "UI/UX Designer", "name": "Jane Smith"},
32
+ {"title": "Database Architect", "name": "Alex Ray"},
33
+
34
+ {"section_title": "Quality & Operations"},
35
+ {"title": "DevOps Engineer", "name": "Liam Patel"},
36
+ {"title": "Quality Assurance Lead", "name": "Sam Wilson"},
37
+ {"title": "Test Automation Engineer", "name": "Olivia Brown"},
38
+ ]
39
+
40
+ license_paths = {
41
+ "Gradio Framework": "./LICENSES/Apache.txt",
42
+ "This Component": "./LICENSES/MIT.txt"
43
+ }
44
+
45
+ DEFAULT_SPEEDS = {
46
+ "scroll": 40.0,
47
+ "starwars": 70.0,
48
+ "matrix": 40.0
49
+ }
50
+ SCROLL_LOGO_PATH = "./assets/gradio_logo_white.png"
51
+ LOGO_PATH="./assets/logo.webp"
52
+
53
+ def update_panel(
54
+ effect: str,
55
+ speed: float,
56
+ base_font_size: float,
57
+ intro_title: str,
58
+ intro_subtitle: str,
59
+ sidebar_position: str,
60
+ show_logo: bool,
61
+ show_licenses: bool,
62
+ show_credits: bool,
63
+ logo_position: str,
64
+ logo_sizing: str,
65
+ logo_width: str | None,
66
+ logo_height: str | None,
67
+ scroll_background_color: str | None,
68
+ scroll_title_color: str | None,
69
+ scroll_section_title_color: str | None,
70
+ scroll_name_color: str | None,
71
+ layout_style: str,
72
+ title_uppercase: bool,
73
+ name_uppercase: bool,
74
+ section_title_uppercase: bool,
75
+ swap_font_sizes: bool,
76
+ show_scroll_logo: bool,
77
+ scroll_logo_height: str | None
78
+ ) -> dict:
79
+ """Callback function that updates all properties of the CreditsPanel component."""
80
+
81
+ scroll_logo_path = SCROLL_LOGO_PATH if show_scroll_logo else None
82
+
83
+ if not scroll_logo_height:
84
+ scroll_logo_height = "120px"
85
+
86
+ return {
87
+ "credits": credits_list,
88
+ "licenses": license_paths,
89
+ "effect": effect,
90
+ "speed": speed,
91
+ "base_font_size": base_font_size,
92
+ "intro_title": intro_title,
93
+ "intro_subtitle": intro_subtitle,
94
+ "sidebar_position": sidebar_position,
95
+ "logo_path": LOGO_PATH,
96
+ "show_logo": show_logo,
97
+ "show_licenses": show_licenses,
98
+ "show_credits": show_credits,
99
+ "logo_position": logo_position,
100
+ "logo_sizing": logo_sizing,
101
+ "logo_width": logo_width,
102
+ "logo_height": logo_height,
103
+ "scroll_background_color": scroll_background_color,
104
+ "scroll_title_color": scroll_title_color,
105
+ "scroll_name_color": scroll_name_color,
106
+ "scroll_section_title_color": scroll_section_title_color,
107
+ "layout_style": layout_style,
108
+ "title_uppercase": title_uppercase,
109
+ "name_uppercase": name_uppercase,
110
+ "section_title_uppercase": section_title_uppercase,
111
+ "swap_font_sizes_on_two_column": swap_font_sizes,
112
+ "scroll_logo_path": scroll_logo_path,
113
+ "scroll_logo_height": scroll_logo_height,
114
+ }
115
+
116
+ def update_ui_on_effect_change(effect: str) -> tuple[float, float]:
117
+ """Updates sliders to sensible defaults when the animation effect is changed."""
118
+ font_size = 1.5
119
+ if effect == "starwars":
120
+ font_size = 3.8
121
+ speed = DEFAULT_SPEEDS.get(effect, 40.0)
122
+ return speed, font_size
123
+
124
+ def toggle_swap_checkbox_visibility(layout: str) -> dict:
125
+ """Show the swap checkbox only for the two-column layout."""
126
+ return gr.update(visible=(layout == 'two-column'))
127
+
128
+ with gr.Blocks(theme=gr.themes.Ocean(), title="CreditsPanel Demo") as demo:
129
+ gr.Markdown(
130
+ """
131
+ # Interactive CreditsPanel Demo
132
+ Use the sidebar controls to customize the `CreditsPanel` component in real-time.
133
+ """
134
+ )
135
+
136
+ with gr.Sidebar(position="right"):
137
+ gr.Markdown("### Effects Settings")
138
+ effect_radio = gr.Radio(["scroll", "starwars", "matrix"], label="Animation Effect", value="scroll")
139
+ speed_slider = gr.Slider(minimum=5.0, maximum=100.0, step=1.0, value=DEFAULT_SPEEDS["scroll"], label="Animation Speed")
140
+ font_size_slider = gr.Slider(minimum=1.0, maximum=10.0, step=0.1, value=1.5, label="Base Font Size")
141
+
142
+ gr.Markdown("### Credits Layout Settings")
143
+ layout_style_radio = gr.Radio(
144
+ ["stacked", "two-column"], label="Layout Style", value="stacked",
145
+ info="How to display titles and names."
146
+ )
147
+ swap_sizes_checkbox = gr.Checkbox(
148
+ label="Swap Title/Name Font Sizes", value=False,
149
+ info="Emphasize name over title in two-column layout.",
150
+ visible=False
151
+ )
152
+ title_uppercase_checkbox = gr.Checkbox(label="Title Uppercase", value=False)
153
+ name_uppercase_checkbox = gr.Checkbox(label="Name Uppercase", value=False)
154
+ section_title_uppercase_checkbox = gr.Checkbox(label="Section Uppercase", value=True)
155
+
156
+ gr.Markdown("### Scrolling Logo")
157
+ show_scroll_logo_checkbox = gr.Checkbox(
158
+ label="Show Logo in Credits Roll",
159
+ value=True,
160
+ info="Toggles the logo above the intro text."
161
+ )
162
+ scroll_logo_height_input = gr.Textbox(label="Scrolling Logo Height", value="100px")
163
+
164
+ gr.Markdown("### Intro Text")
165
+ intro_title_input = gr.Textbox(label="Intro Title", value="Gradio")
166
+ intro_subtitle_input = gr.Textbox(label="Intro Subtitle", value="The best UI framework")
167
+
168
+ gr.Markdown("### Layout & Visibility")
169
+ sidebar_position_radio = gr.Radio(["right", "bottom"], label="Sidebar Position", value="right")
170
+ show_logo_checkbox = gr.Checkbox(label="Show Logo", value=True)
171
+ show_licenses_checkbox = gr.Checkbox(label="Show Licenses", value=True)
172
+ show_credits_checkbox = gr.Checkbox(label="Show Credits", value=True)
173
+
174
+ gr.Markdown("### Logo Customization")
175
+ logo_position_radio = gr.Radio(["left", "center", "right"], label="Logo Position", value="center")
176
+ logo_sizing_radio = gr.Radio(["stretch", "crop", "resize"], label="Logo Sizing", value="resize")
177
+ logo_width_input = gr.Textbox(label="Logo Width", value="200px")
178
+ logo_height_input = gr.Textbox(label="Logo Height", value="100px")
179
+
180
+ gr.Markdown("### Color Settings (Scroll Effect)")
181
+ scroll_background_color = gr.ColorPicker(label="Background Color", value="#000000")
182
+ scroll_title_color = gr.ColorPicker(label="Title Color", value="#FFFFFF")
183
+ scroll_section_title_color = gr.ColorPicker(label="Section Title Color", value="#FFFFFF")
184
+ scroll_name_color = gr.ColorPicker(label="Name Color", value="#FFFFFF")
185
+
186
+ panel = CreditsPanel(
187
+ height=500,
188
+ credits=credits_list,
189
+ licenses=license_paths,
190
+ effect="scroll",
191
+ speed=DEFAULT_SPEEDS["scroll"],
192
+ base_font_size=1.5,
193
+ intro_title="Gradio",
194
+ intro_subtitle="The best UI framework",
195
+ sidebar_position="right",
196
+ logo_path=LOGO_PATH,
197
+ show_logo=True,
198
+ show_licenses=True,
199
+ show_credits=True,
200
+ logo_position="center",
201
+ logo_sizing="resize",
202
+ logo_width="200px",
203
+ logo_height="100px",
204
+ scroll_background_color="#000000",
205
+ scroll_title_color="#FFFFFF",
206
+ scroll_name_color="#FFFFFF",
207
+ scroll_section_title_color="#FFFFFF",
208
+ layout_style="stacked",
209
+ title_uppercase=False,
210
+ name_uppercase=False,
211
+ section_title_uppercase=True,
212
+ swap_font_sizes_on_two_column=False,
213
+ scroll_logo_path=SCROLL_LOGO_PATH,
214
+ scroll_logo_height="100px",
215
+ )
216
+
217
+ inputs = [
218
+ effect_radio,
219
+ speed_slider,
220
+ font_size_slider,
221
+ intro_title_input,
222
+ intro_subtitle_input,
223
+ sidebar_position_radio,
224
+ show_logo_checkbox,
225
+ show_licenses_checkbox,
226
+ show_credits_checkbox,
227
+ logo_position_radio,
228
+ logo_sizing_radio,
229
+ logo_width_input,
230
+ logo_height_input,
231
+ scroll_background_color,
232
+ scroll_title_color,
233
+ scroll_section_title_color,
234
+ scroll_name_color,
235
+ layout_style_radio,
236
+ title_uppercase_checkbox,
237
+ name_uppercase_checkbox,
238
+ section_title_uppercase_checkbox,
239
+ swap_sizes_checkbox,
240
+ show_scroll_logo_checkbox,
241
+ scroll_logo_height_input
242
+ ]
243
+
244
+ demo.load(
245
+ fn=update_panel,
246
+ inputs=inputs,
247
+ outputs=panel
248
+ )
249
+ layout_style_radio.change(
250
+ fn=toggle_swap_checkbox_visibility,
251
+ inputs=layout_style_radio,
252
+ outputs=swap_sizes_checkbox
253
+ )
254
+ effect_radio.change(fn=update_ui_on_effect_change, inputs=effect_radio, outputs=[speed_slider, font_size_slider])
255
+
256
+ for input_component in inputs:
257
+ input_component.change(fn=update_panel, inputs=inputs, outputs=panel)
258
+
259
+ if __name__ == "__main__":
260
+ setup_demo_files()
261
  demo.launch()
space.py CHANGED
@@ -3,7 +3,7 @@ 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}, 'show_credits': {'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}, 'layout_style': {'type': '"stacked" | "two-column"', 'default': '"stacked"', 'description': None}, 'title_uppercase': {'type': 'bool', 'default': 'False', 'description': None}, 'name_uppercase': {'type': 'bool', 'default': 'False', 'description': None}, 'section_title_uppercase': {'type': 'bool', 'default': 'True', 'description': None}, 'swap_font_sizes_on_two_column': {'type': 'bool', 'default': 'False', '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
 
@@ -58,7 +58,7 @@ def setup_demo_files():
58
  with open("./assets/logo.webp", "w") as f:
59
  f.write("Placeholder WebP logo")
60
 
61
- # --- UPDATED: Credits list with sections ---
62
  credits_list = [
63
  {"section_title": "Project Leadership"},
64
  {"title": "Project Manager", "name": "Emma Thompson"},
@@ -87,6 +87,8 @@ DEFAULT_SPEEDS = {
87
  "starwars": 70.0,
88
  "matrix": 40.0
89
  }
 
 
90
 
91
  def update_panel(
92
  effect: str,
@@ -104,39 +106,52 @@ def update_panel(
104
  logo_height: str | None,
105
  scroll_background_color: str | None,
106
  scroll_title_color: str | None,
 
107
  scroll_name_color: str | None,
108
  layout_style: str,
109
  title_uppercase: bool,
110
  name_uppercase: bool,
111
  section_title_uppercase: bool,
112
- swap_font_sizes: bool
 
 
113
  ) -> dict:
114
  \"\"\"Callback function that updates all properties of the CreditsPanel component.\"\"\"
115
- return gr.update(
116
- visible=True,
117
- effect=effect,
118
- speed=speed,
119
- base_font_size=base_font_size,
120
- intro_title=intro_title,
121
- intro_subtitle=intro_subtitle,
122
- sidebar_position=sidebar_position,
123
- show_logo=show_logo,
124
- show_licenses=show_licenses,
125
- show_credits=show_credits,
126
- logo_position=logo_position,
127
- logo_sizing=logo_sizing,
128
- logo_width=logo_width,
129
- logo_height=logo_height,
130
- scroll_background_color=scroll_background_color,
131
- scroll_title_color=scroll_title_color,
132
- scroll_name_color=scroll_name_color,
133
- layout_style=layout_style,
134
- title_uppercase=title_uppercase,
135
- name_uppercase=name_uppercase,
136
- section_title_uppercase=section_title_uppercase,
137
- swap_font_sizes_on_two_column=swap_font_sizes,
138
- value=credits_list
139
- )
 
 
 
 
 
 
 
 
 
 
140
 
141
  def update_ui_on_effect_change(effect: str) -> tuple[float, float]:
142
  \"\"\"Updates sliders to sensible defaults when the animation effect is changed.\"\"\"
@@ -178,6 +193,14 @@ with gr.Blocks(theme=gr.themes.Ocean(), title="CreditsPanel Demo") as demo:
178
  name_uppercase_checkbox = gr.Checkbox(label="Name Uppercase", value=False)
179
  section_title_uppercase_checkbox = gr.Checkbox(label="Section Uppercase", value=True)
180
 
 
 
 
 
 
 
 
 
181
  gr.Markdown("### Intro Text")
182
  intro_title_input = gr.Textbox(label="Intro Title", value="Gradio")
183
  intro_subtitle_input = gr.Textbox(label="Intro Subtitle", value="The best UI framework")
@@ -197,19 +220,20 @@ with gr.Blocks(theme=gr.themes.Ocean(), title="CreditsPanel Demo") as demo:
197
  gr.Markdown("### Color Settings (Scroll Effect)")
198
  scroll_background_color = gr.ColorPicker(label="Background Color", value="#000000")
199
  scroll_title_color = gr.ColorPicker(label="Title Color", value="#FFFFFF")
 
200
  scroll_name_color = gr.ColorPicker(label="Name Color", value="#FFFFFF")
201
 
202
- panel = CreditsPanel(
 
203
  credits=credits_list,
204
  licenses=license_paths,
205
  effect="scroll",
206
- height=500,
207
  speed=DEFAULT_SPEEDS["scroll"],
208
  base_font_size=1.5,
209
  intro_title="Gradio",
210
  intro_subtitle="The best UI framework",
211
  sidebar_position="right",
212
- logo_path="./assets/logo.webp",
213
  show_logo=True,
214
  show_licenses=True,
215
  show_credits=True,
@@ -220,11 +244,14 @@ with gr.Blocks(theme=gr.themes.Ocean(), title="CreditsPanel Demo") as demo:
220
  scroll_background_color="#000000",
221
  scroll_title_color="#FFFFFF",
222
  scroll_name_color="#FFFFFF",
 
223
  layout_style="stacked",
224
  title_uppercase=False,
225
  name_uppercase=False,
226
  section_title_uppercase=True,
227
  swap_font_sizes_on_two_column=False,
 
 
228
  )
229
 
230
  inputs = [
@@ -242,15 +269,23 @@ with gr.Blocks(theme=gr.themes.Ocean(), title="CreditsPanel Demo") as demo:
242
  logo_width_input,
243
  logo_height_input,
244
  scroll_background_color,
245
- scroll_title_color,
 
246
  scroll_name_color,
247
  layout_style_radio,
248
  title_uppercase_checkbox,
249
  name_uppercase_checkbox,
250
  section_title_uppercase_checkbox,
251
- swap_sizes_checkbox
 
 
252
  ]
253
 
 
 
 
 
 
254
  layout_style_radio.change(
255
  fn=toggle_swap_checkbox_visibility,
256
  inputs=layout_style_radio,
@@ -294,7 +329,6 @@ The impact on the users predict function varies depending on whether the compone
294
 
295
  The code snippet below is accurate in cases where the component is used as both an input and an output.
296
 
297
- - **As input:** Is passed, dict[str, Any] | None: The input payload, returned unchanged.
298
 
299
 
300
  ```python
@@ -302,7 +336,9 @@ def predict(
302
  value: typing.Optional[typing.Dict[str, typing.Any]][
303
  typing.Dict[str, typing.Any][str, Any], None
304
  ]
305
- ) -> Any:
 
 
306
  return value
307
  ```
308
  """, elem_classes=["md-custom", "CreditsPanel-user-fn"], header_links=True)
 
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.\nThis component is configured via a single dictionary `value` that holds all settings.\nIt supports displaying a logo, licenses, sections, and various text styling options.\n\n EVENTS (list): Supported events for the component, currently only `change`.', 'members': {'__init__': {'value': {'type': 'typing.Optional[typing.Dict[str, typing.Any]][\n typing.Dict[str, typing.Any][str, Any], None\n]', 'default': 'None', 'description': None}, 'height': {'type': 'int | str | None', 'default': 'None', 'description': None}, 'width': {'type': 'int | str | None', '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}, '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}, 'show_credits': {'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}, 'scroll_section_title_color': {'type': 'str | None', 'default': 'None', 'description': None}, 'layout_style': {'type': '"stacked" | "two-column"', 'default': '"stacked"', 'description': None}, 'title_uppercase': {'type': 'bool', 'default': 'False', 'description': None}, 'name_uppercase': {'type': 'bool', 'default': 'False', 'description': None}, 'section_title_uppercase': {'type': 'bool', 'default': 'True', 'description': None}, 'swap_font_sizes_on_two_column': {'type': 'bool', 'default': 'False', 'description': None}, 'scroll_logo_path': {'type': 'str | pathlib.Path | None', 'default': 'None', 'description': None}, 'scroll_logo_height': {'type': 'str', 'default': '"120px"', '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': 'typing.Optional[typing.Dict[str, typing.Any]][\n typing.Dict[str, typing.Any][str, Any], None\n]', 'description': None}}, 'preprocess': {'return': {'type': 'typing.Optional[typing.Dict[str, typing.Any]][\n typing.Dict[str, typing.Any][str, Any], None\n]', 'description': None}, '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
 
 
58
  with open("./assets/logo.webp", "w") as f:
59
  f.write("Placeholder WebP logo")
60
 
61
+ # --- Credits list with sections ---
62
  credits_list = [
63
  {"section_title": "Project Leadership"},
64
  {"title": "Project Manager", "name": "Emma Thompson"},
 
87
  "starwars": 70.0,
88
  "matrix": 40.0
89
  }
90
+ SCROLL_LOGO_PATH = "./assets/gradio_logo_white.png"
91
+ LOGO_PATH="./assets/logo.webp"
92
 
93
  def update_panel(
94
  effect: str,
 
106
  logo_height: str | None,
107
  scroll_background_color: str | None,
108
  scroll_title_color: str | None,
109
+ scroll_section_title_color: str | None,
110
  scroll_name_color: str | None,
111
  layout_style: str,
112
  title_uppercase: bool,
113
  name_uppercase: bool,
114
  section_title_uppercase: bool,
115
+ swap_font_sizes: bool,
116
+ show_scroll_logo: bool,
117
+ scroll_logo_height: str | None
118
  ) -> dict:
119
  \"\"\"Callback function that updates all properties of the CreditsPanel component.\"\"\"
120
+
121
+ scroll_logo_path = SCROLL_LOGO_PATH if show_scroll_logo else None
122
+
123
+ if not scroll_logo_height:
124
+ scroll_logo_height = "120px"
125
+
126
+ return {
127
+ "credits": credits_list,
128
+ "licenses": license_paths,
129
+ "effect": effect,
130
+ "speed": speed,
131
+ "base_font_size": base_font_size,
132
+ "intro_title": intro_title,
133
+ "intro_subtitle": intro_subtitle,
134
+ "sidebar_position": sidebar_position,
135
+ "logo_path": LOGO_PATH,
136
+ "show_logo": show_logo,
137
+ "show_licenses": show_licenses,
138
+ "show_credits": show_credits,
139
+ "logo_position": logo_position,
140
+ "logo_sizing": logo_sizing,
141
+ "logo_width": logo_width,
142
+ "logo_height": logo_height,
143
+ "scroll_background_color": scroll_background_color,
144
+ "scroll_title_color": scroll_title_color,
145
+ "scroll_name_color": scroll_name_color,
146
+ "scroll_section_title_color": scroll_section_title_color,
147
+ "layout_style": layout_style,
148
+ "title_uppercase": title_uppercase,
149
+ "name_uppercase": name_uppercase,
150
+ "section_title_uppercase": section_title_uppercase,
151
+ "swap_font_sizes_on_two_column": swap_font_sizes,
152
+ "scroll_logo_path": scroll_logo_path,
153
+ "scroll_logo_height": scroll_logo_height,
154
+ }
155
 
156
  def update_ui_on_effect_change(effect: str) -> tuple[float, float]:
157
  \"\"\"Updates sliders to sensible defaults when the animation effect is changed.\"\"\"
 
193
  name_uppercase_checkbox = gr.Checkbox(label="Name Uppercase", value=False)
194
  section_title_uppercase_checkbox = gr.Checkbox(label="Section Uppercase", value=True)
195
 
196
+ gr.Markdown("### Scrolling Logo")
197
+ show_scroll_logo_checkbox = gr.Checkbox(
198
+ label="Show Logo in Credits Roll",
199
+ value=True,
200
+ info="Toggles the logo above the intro text."
201
+ )
202
+ scroll_logo_height_input = gr.Textbox(label="Scrolling Logo Height", value="100px")
203
+
204
  gr.Markdown("### Intro Text")
205
  intro_title_input = gr.Textbox(label="Intro Title", value="Gradio")
206
  intro_subtitle_input = gr.Textbox(label="Intro Subtitle", value="The best UI framework")
 
220
  gr.Markdown("### Color Settings (Scroll Effect)")
221
  scroll_background_color = gr.ColorPicker(label="Background Color", value="#000000")
222
  scroll_title_color = gr.ColorPicker(label="Title Color", value="#FFFFFF")
223
+ scroll_section_title_color = gr.ColorPicker(label="Section Title Color", value="#FFFFFF")
224
  scroll_name_color = gr.ColorPicker(label="Name Color", value="#FFFFFF")
225
 
226
+ panel = CreditsPanel(
227
+ height=500,
228
  credits=credits_list,
229
  licenses=license_paths,
230
  effect="scroll",
 
231
  speed=DEFAULT_SPEEDS["scroll"],
232
  base_font_size=1.5,
233
  intro_title="Gradio",
234
  intro_subtitle="The best UI framework",
235
  sidebar_position="right",
236
+ logo_path=LOGO_PATH,
237
  show_logo=True,
238
  show_licenses=True,
239
  show_credits=True,
 
244
  scroll_background_color="#000000",
245
  scroll_title_color="#FFFFFF",
246
  scroll_name_color="#FFFFFF",
247
+ scroll_section_title_color="#FFFFFF",
248
  layout_style="stacked",
249
  title_uppercase=False,
250
  name_uppercase=False,
251
  section_title_uppercase=True,
252
  swap_font_sizes_on_two_column=False,
253
+ scroll_logo_path=SCROLL_LOGO_PATH,
254
+ scroll_logo_height="100px",
255
  )
256
 
257
  inputs = [
 
269
  logo_width_input,
270
  logo_height_input,
271
  scroll_background_color,
272
+ scroll_title_color,
273
+ scroll_section_title_color,
274
  scroll_name_color,
275
  layout_style_radio,
276
  title_uppercase_checkbox,
277
  name_uppercase_checkbox,
278
  section_title_uppercase_checkbox,
279
+ swap_sizes_checkbox,
280
+ show_scroll_logo_checkbox,
281
+ scroll_logo_height_input
282
  ]
283
 
284
+ demo.load(
285
+ fn=update_panel,
286
+ inputs=inputs,
287
+ outputs=panel
288
+ )
289
  layout_style_radio.change(
290
  fn=toggle_swap_checkbox_visibility,
291
  inputs=layout_style_radio,
 
329
 
330
  The code snippet below is accurate in cases where the component is used as both an input and an output.
331
 
 
332
 
333
 
334
  ```python
 
336
  value: typing.Optional[typing.Dict[str, typing.Any]][
337
  typing.Dict[str, typing.Any][str, Any], None
338
  ]
339
+ ) -> typing.Optional[typing.Dict[str, typing.Any]][
340
+ typing.Dict[str, typing.Any][str, Any], None
341
+ ]:
342
  return value
343
  ```
344
  """, elem_classes=["md-custom", "CreditsPanel-user-fn"], header_links=True)
src/README.md CHANGED
@@ -10,7 +10,8 @@ app_file: space.py
10
  ---
11
 
12
  # `gradio_creditspanel`
13
- <a href="https://pypi.org/project/gradio_creditspanel/" target="_blank"><img alt="PyPI - Version" src="https://img.shields.io/pypi/v/gradio_creditspanel"></a>
 
14
 
15
  Credits Panel for Gradio UI
16
 
@@ -43,7 +44,7 @@ def setup_demo_files():
43
  with open("./assets/logo.webp", "w") as f:
44
  f.write("Placeholder WebP logo")
45
 
46
- # --- UPDATED: Credits list with sections ---
47
  credits_list = [
48
  {"section_title": "Project Leadership"},
49
  {"title": "Project Manager", "name": "Emma Thompson"},
@@ -72,6 +73,8 @@ DEFAULT_SPEEDS = {
72
  "starwars": 70.0,
73
  "matrix": 40.0
74
  }
 
 
75
 
76
  def update_panel(
77
  effect: str,
@@ -89,39 +92,52 @@ def update_panel(
89
  logo_height: str | None,
90
  scroll_background_color: str | None,
91
  scroll_title_color: str | None,
 
92
  scroll_name_color: str | None,
93
  layout_style: str,
94
  title_uppercase: bool,
95
  name_uppercase: bool,
96
  section_title_uppercase: bool,
97
- swap_font_sizes: bool
 
 
98
  ) -> dict:
99
  """Callback function that updates all properties of the CreditsPanel component."""
100
- return gr.update(
101
- visible=True,
102
- effect=effect,
103
- speed=speed,
104
- base_font_size=base_font_size,
105
- intro_title=intro_title,
106
- intro_subtitle=intro_subtitle,
107
- sidebar_position=sidebar_position,
108
- show_logo=show_logo,
109
- show_licenses=show_licenses,
110
- show_credits=show_credits,
111
- logo_position=logo_position,
112
- logo_sizing=logo_sizing,
113
- logo_width=logo_width,
114
- logo_height=logo_height,
115
- scroll_background_color=scroll_background_color,
116
- scroll_title_color=scroll_title_color,
117
- scroll_name_color=scroll_name_color,
118
- layout_style=layout_style,
119
- title_uppercase=title_uppercase,
120
- name_uppercase=name_uppercase,
121
- section_title_uppercase=section_title_uppercase,
122
- swap_font_sizes_on_two_column=swap_font_sizes,
123
- value=credits_list
124
- )
 
 
 
 
 
 
 
 
 
 
125
 
126
  def update_ui_on_effect_change(effect: str) -> tuple[float, float]:
127
  """Updates sliders to sensible defaults when the animation effect is changed."""
@@ -163,6 +179,14 @@ with gr.Blocks(theme=gr.themes.Ocean(), title="CreditsPanel Demo") as demo:
163
  name_uppercase_checkbox = gr.Checkbox(label="Name Uppercase", value=False)
164
  section_title_uppercase_checkbox = gr.Checkbox(label="Section Uppercase", value=True)
165
 
 
 
 
 
 
 
 
 
166
  gr.Markdown("### Intro Text")
167
  intro_title_input = gr.Textbox(label="Intro Title", value="Gradio")
168
  intro_subtitle_input = gr.Textbox(label="Intro Subtitle", value="The best UI framework")
@@ -182,19 +206,20 @@ with gr.Blocks(theme=gr.themes.Ocean(), title="CreditsPanel Demo") as demo:
182
  gr.Markdown("### Color Settings (Scroll Effect)")
183
  scroll_background_color = gr.ColorPicker(label="Background Color", value="#000000")
184
  scroll_title_color = gr.ColorPicker(label="Title Color", value="#FFFFFF")
 
185
  scroll_name_color = gr.ColorPicker(label="Name Color", value="#FFFFFF")
186
 
187
- panel = CreditsPanel(
 
188
  credits=credits_list,
189
  licenses=license_paths,
190
  effect="scroll",
191
- height=500,
192
  speed=DEFAULT_SPEEDS["scroll"],
193
  base_font_size=1.5,
194
  intro_title="Gradio",
195
  intro_subtitle="The best UI framework",
196
  sidebar_position="right",
197
- logo_path="./assets/logo.webp",
198
  show_logo=True,
199
  show_licenses=True,
200
  show_credits=True,
@@ -205,11 +230,14 @@ with gr.Blocks(theme=gr.themes.Ocean(), title="CreditsPanel Demo") as demo:
205
  scroll_background_color="#000000",
206
  scroll_title_color="#FFFFFF",
207
  scroll_name_color="#FFFFFF",
 
208
  layout_style="stacked",
209
  title_uppercase=False,
210
  name_uppercase=False,
211
  section_title_uppercase=True,
212
  swap_font_sizes_on_two_column=False,
 
 
213
  )
214
 
215
  inputs = [
@@ -227,15 +255,23 @@ with gr.Blocks(theme=gr.themes.Ocean(), title="CreditsPanel Demo") as demo:
227
  logo_width_input,
228
  logo_height_input,
229
  scroll_background_color,
230
- scroll_title_color,
 
231
  scroll_name_color,
232
  layout_style_radio,
233
  title_uppercase_checkbox,
234
  name_uppercase_checkbox,
235
  section_title_uppercase_checkbox,
236
- swap_sizes_checkbox
 
 
237
  ]
238
 
 
 
 
 
 
239
  layout_style_radio.change(
240
  fn=toggle_swap_checkbox_visibility,
241
  inputs=layout_style_radio,
@@ -270,7 +306,9 @@ if __name__ == "__main__":
270
  <td align="left" style="width: 25%;">
271
 
272
  ```python
273
- Any
 
 
274
  ```
275
 
276
  </td>
@@ -279,21 +317,11 @@ Any
279
  </tr>
280
 
281
  <tr>
282
- <td align="left"><code>credits</code></td>
283
  <td align="left" style="width: 25%;">
284
 
285
  ```python
286
- typing.Union[
287
- typing.List[typing.Dict[str, str]],
288
- typing.Callable,
289
- NoneType,
290
- ][
291
- typing.List[typing.Dict[str, str]][
292
- typing.Dict[str, str][str, str]
293
- ],
294
- Callable,
295
- None,
296
- ]
297
  ```
298
 
299
  </td>
@@ -302,7 +330,7 @@ typing.Union[
302
  </tr>
303
 
304
  <tr>
305
- <td align="left"><code>height</code></td>
306
  <td align="left" style="width: 25%;">
307
 
308
  ```python
@@ -315,11 +343,21 @@ int | str | None
315
  </tr>
316
 
317
  <tr>
318
- <td align="left"><code>width</code></td>
319
  <td align="left" style="width: 25%;">
320
 
321
  ```python
322
- int | str | None
 
 
 
 
 
 
 
 
 
 
323
  ```
324
 
325
  </td>
@@ -566,6 +604,19 @@ str | None
566
  <td align="left">None</td>
567
  </tr>
568
 
 
 
 
 
 
 
 
 
 
 
 
 
 
569
  <tr>
570
  <td align="left"><code>layout_style</code></td>
571
  <td align="left" style="width: 25%;">
@@ -631,6 +682,32 @@ bool
631
  <td align="left">None</td>
632
  </tr>
633
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
634
  <tr>
635
  <td align="left"><code>label</code></td>
636
  <td align="left" style="width: 25%;">
@@ -844,7 +921,6 @@ The impact on the users predict function varies depending on whether the compone
844
 
845
  The code snippet below is accurate in cases where the component is used as both an input and an output.
846
 
847
- - **As output:** Is passed, dict[str, Any] | None: The input payload, returned unchanged.
848
 
849
 
850
  ```python
@@ -852,7 +928,9 @@ The code snippet below is accurate in cases where the component is used as both
852
  value: typing.Optional[typing.Dict[str, typing.Any]][
853
  typing.Dict[str, typing.Any][str, Any], None
854
  ]
855
- ) -> Any:
 
 
856
  return value
857
  ```
858
 
 
10
  ---
11
 
12
  # `gradio_creditspanel`
13
+ <img alt="Static Badge" src="https://img.shields.io/badge/version%20-%200.0.4%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
 
16
  Credits Panel for Gradio UI
17
 
 
44
  with open("./assets/logo.webp", "w") as f:
45
  f.write("Placeholder WebP logo")
46
 
47
+ # --- Credits list with sections ---
48
  credits_list = [
49
  {"section_title": "Project Leadership"},
50
  {"title": "Project Manager", "name": "Emma Thompson"},
 
73
  "starwars": 70.0,
74
  "matrix": 40.0
75
  }
76
+ SCROLL_LOGO_PATH = "./assets/gradio_logo_white.png"
77
+ LOGO_PATH="./assets/logo.webp"
78
 
79
  def update_panel(
80
  effect: str,
 
92
  logo_height: str | None,
93
  scroll_background_color: str | None,
94
  scroll_title_color: str | None,
95
+ scroll_section_title_color: str | None,
96
  scroll_name_color: str | None,
97
  layout_style: str,
98
  title_uppercase: bool,
99
  name_uppercase: bool,
100
  section_title_uppercase: bool,
101
+ swap_font_sizes: bool,
102
+ show_scroll_logo: bool,
103
+ scroll_logo_height: str | None
104
  ) -> dict:
105
  """Callback function that updates all properties of the CreditsPanel component."""
106
+
107
+ scroll_logo_path = SCROLL_LOGO_PATH if show_scroll_logo else None
108
+
109
+ if not scroll_logo_height:
110
+ scroll_logo_height = "120px"
111
+
112
+ return {
113
+ "credits": credits_list,
114
+ "licenses": license_paths,
115
+ "effect": effect,
116
+ "speed": speed,
117
+ "base_font_size": base_font_size,
118
+ "intro_title": intro_title,
119
+ "intro_subtitle": intro_subtitle,
120
+ "sidebar_position": sidebar_position,
121
+ "logo_path": LOGO_PATH,
122
+ "show_logo": show_logo,
123
+ "show_licenses": show_licenses,
124
+ "show_credits": show_credits,
125
+ "logo_position": logo_position,
126
+ "logo_sizing": logo_sizing,
127
+ "logo_width": logo_width,
128
+ "logo_height": logo_height,
129
+ "scroll_background_color": scroll_background_color,
130
+ "scroll_title_color": scroll_title_color,
131
+ "scroll_name_color": scroll_name_color,
132
+ "scroll_section_title_color": scroll_section_title_color,
133
+ "layout_style": layout_style,
134
+ "title_uppercase": title_uppercase,
135
+ "name_uppercase": name_uppercase,
136
+ "section_title_uppercase": section_title_uppercase,
137
+ "swap_font_sizes_on_two_column": swap_font_sizes,
138
+ "scroll_logo_path": scroll_logo_path,
139
+ "scroll_logo_height": scroll_logo_height,
140
+ }
141
 
142
  def update_ui_on_effect_change(effect: str) -> tuple[float, float]:
143
  """Updates sliders to sensible defaults when the animation effect is changed."""
 
179
  name_uppercase_checkbox = gr.Checkbox(label="Name Uppercase", value=False)
180
  section_title_uppercase_checkbox = gr.Checkbox(label="Section Uppercase", value=True)
181
 
182
+ gr.Markdown("### Scrolling Logo")
183
+ show_scroll_logo_checkbox = gr.Checkbox(
184
+ label="Show Logo in Credits Roll",
185
+ value=True,
186
+ info="Toggles the logo above the intro text."
187
+ )
188
+ scroll_logo_height_input = gr.Textbox(label="Scrolling Logo Height", value="100px")
189
+
190
  gr.Markdown("### Intro Text")
191
  intro_title_input = gr.Textbox(label="Intro Title", value="Gradio")
192
  intro_subtitle_input = gr.Textbox(label="Intro Subtitle", value="The best UI framework")
 
206
  gr.Markdown("### Color Settings (Scroll Effect)")
207
  scroll_background_color = gr.ColorPicker(label="Background Color", value="#000000")
208
  scroll_title_color = gr.ColorPicker(label="Title Color", value="#FFFFFF")
209
+ scroll_section_title_color = gr.ColorPicker(label="Section Title Color", value="#FFFFFF")
210
  scroll_name_color = gr.ColorPicker(label="Name Color", value="#FFFFFF")
211
 
212
+ panel = CreditsPanel(
213
+ height=500,
214
  credits=credits_list,
215
  licenses=license_paths,
216
  effect="scroll",
 
217
  speed=DEFAULT_SPEEDS["scroll"],
218
  base_font_size=1.5,
219
  intro_title="Gradio",
220
  intro_subtitle="The best UI framework",
221
  sidebar_position="right",
222
+ logo_path=LOGO_PATH,
223
  show_logo=True,
224
  show_licenses=True,
225
  show_credits=True,
 
230
  scroll_background_color="#000000",
231
  scroll_title_color="#FFFFFF",
232
  scroll_name_color="#FFFFFF",
233
+ scroll_section_title_color="#FFFFFF",
234
  layout_style="stacked",
235
  title_uppercase=False,
236
  name_uppercase=False,
237
  section_title_uppercase=True,
238
  swap_font_sizes_on_two_column=False,
239
+ scroll_logo_path=SCROLL_LOGO_PATH,
240
+ scroll_logo_height="100px",
241
  )
242
 
243
  inputs = [
 
255
  logo_width_input,
256
  logo_height_input,
257
  scroll_background_color,
258
+ scroll_title_color,
259
+ scroll_section_title_color,
260
  scroll_name_color,
261
  layout_style_radio,
262
  title_uppercase_checkbox,
263
  name_uppercase_checkbox,
264
  section_title_uppercase_checkbox,
265
+ swap_sizes_checkbox,
266
+ show_scroll_logo_checkbox,
267
+ scroll_logo_height_input
268
  ]
269
 
270
+ demo.load(
271
+ fn=update_panel,
272
+ inputs=inputs,
273
+ outputs=panel
274
+ )
275
  layout_style_radio.change(
276
  fn=toggle_swap_checkbox_visibility,
277
  inputs=layout_style_radio,
 
306
  <td align="left" style="width: 25%;">
307
 
308
  ```python
309
+ typing.Optional[typing.Dict[str, typing.Any]][
310
+ typing.Dict[str, typing.Any][str, Any], None
311
+ ]
312
  ```
313
 
314
  </td>
 
317
  </tr>
318
 
319
  <tr>
320
+ <td align="left"><code>height</code></td>
321
  <td align="left" style="width: 25%;">
322
 
323
  ```python
324
+ int | str | None
 
 
 
 
 
 
 
 
 
 
325
  ```
326
 
327
  </td>
 
330
  </tr>
331
 
332
  <tr>
333
+ <td align="left"><code>width</code></td>
334
  <td align="left" style="width: 25%;">
335
 
336
  ```python
 
343
  </tr>
344
 
345
  <tr>
346
+ <td align="left"><code>credits</code></td>
347
  <td align="left" style="width: 25%;">
348
 
349
  ```python
350
+ typing.Union[
351
+ typing.List[typing.Dict[str, str]],
352
+ typing.Callable,
353
+ NoneType,
354
+ ][
355
+ typing.List[typing.Dict[str, str]][
356
+ typing.Dict[str, str][str, str]
357
+ ],
358
+ Callable,
359
+ None,
360
+ ]
361
  ```
362
 
363
  </td>
 
604
  <td align="left">None</td>
605
  </tr>
606
 
607
+ <tr>
608
+ <td align="left"><code>scroll_section_title_color</code></td>
609
+ <td align="left" style="width: 25%;">
610
+
611
+ ```python
612
+ str | None
613
+ ```
614
+
615
+ </td>
616
+ <td align="left"><code>None</code></td>
617
+ <td align="left">None</td>
618
+ </tr>
619
+
620
  <tr>
621
  <td align="left"><code>layout_style</code></td>
622
  <td align="left" style="width: 25%;">
 
682
  <td align="left">None</td>
683
  </tr>
684
 
685
+ <tr>
686
+ <td align="left"><code>scroll_logo_path</code></td>
687
+ <td align="left" style="width: 25%;">
688
+
689
+ ```python
690
+ str | pathlib.Path | None
691
+ ```
692
+
693
+ </td>
694
+ <td align="left"><code>None</code></td>
695
+ <td align="left">None</td>
696
+ </tr>
697
+
698
+ <tr>
699
+ <td align="left"><code>scroll_logo_height</code></td>
700
+ <td align="left" style="width: 25%;">
701
+
702
+ ```python
703
+ str
704
+ ```
705
+
706
+ </td>
707
+ <td align="left"><code>"120px"</code></td>
708
+ <td align="left">None</td>
709
+ </tr>
710
+
711
  <tr>
712
  <td align="left"><code>label</code></td>
713
  <td align="left" style="width: 25%;">
 
921
 
922
  The code snippet below is accurate in cases where the component is used as both an input and an output.
923
 
 
924
 
925
 
926
  ```python
 
928
  value: typing.Optional[typing.Dict[str, typing.Any]][
929
  typing.Dict[str, typing.Any][str, Any], None
930
  ]
931
+ ) -> typing.Optional[typing.Dict[str, typing.Any]][
932
+ typing.Dict[str, typing.Any][str, Any], None
933
+ ]:
934
  return value
935
  ```
936
 
src/assets/gradio_logo_white.png ADDED
src/backend/gradio_creditspanel/creditspanel.py CHANGED
@@ -13,7 +13,8 @@ import os
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`.
@@ -23,11 +24,13 @@ class CreditsPanel(Component):
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,
@@ -46,11 +49,15 @@ class CreditsPanel(Component):
46
  scroll_background_color: str | None = None,
47
  scroll_title_color: str | None = None,
48
  scroll_name_color: str | None = None,
 
49
  layout_style: Literal["stacked", "two-column"] = "stacked",
50
  title_uppercase: bool = False,
51
  name_uppercase: bool = False,
52
  section_title_uppercase: bool = True,
53
  swap_font_sizes_on_two_column: bool = False,
 
 
 
54
  label: str | I18nData | None = None,
55
  every: float | None = None,
56
  inputs: Component | Sequence[Component] | set[Component] | None = None,
@@ -70,74 +77,77 @@ class CreditsPanel(Component):
70
  Initialize the CreditsPanel component.
71
 
72
  Args:
73
- value (Any, optional): Initial value for the component.
74
- 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.
75
- height (int | str | None, optional): Height of the component (e.g., in pixels or CSS units).
76
- width (int | str | None, optional): Width of the component (e.g., in pixels or CSS units).
77
- licenses (Dict[str, str | Path] | None, optional): Dictionary mapping license names to file paths or content strings.
78
- effect (Literal["scroll", "starwars", "matrix"], optional): Visual effect for credits display. Defaults to "scroll".
 
79
  speed (float, optional): Animation speed in seconds. Defaults to 40.0.
80
- base_font_size (float, optional): Base font size in rem for credits text. Defaults to 1.5.
81
- intro_title (str | None, optional): Title for the intro section, if any.
82
- intro_subtitle (str | None, optional): Subtitle for the intro section, if any.
83
  sidebar_position (Literal["right", "bottom"], optional): Position of the licenses sidebar. Defaults to "right".
84
- logo_path (str | Path | None, optional): Path or URL to the logo image.
85
- show_logo (bool, optional): Whether to display the logo. Defaults to True.
86
  show_licenses (bool, optional): Whether to display licenses. Defaults to True.
87
- show_credits (bool, optional): Whether to display the credits. Defaults to True.
88
- logo_position (Literal["center", "left", "right"], optional): Logo alignment. Defaults to "center".
89
- logo_sizing (Literal["stretch", "crop", "resize"], optional): Logo sizing mode. Defaults to "resize".
90
- logo_width (int | str | None, optional): Logo width (e.g., in pixels or CSS units).
91
- logo_height (int | str | None, optional): Logo height (e.g., in pixels or CSS units).
92
- scroll_background_color (str | None, optional): Background color for scroll effect.
93
  scroll_title_color (str | None, optional): Color for credit titles.
94
  scroll_name_color (str | None, optional): Color for credit names.
95
- layout_style (Literal["stacked", "two-column"], optional): Layout for credits ('title' above 'name' or side-by-side). Defaults to "stacked".
 
96
  title_uppercase (bool, optional): Whether to display titles in uppercase. Defaults to False.
97
- name_uppercase (bool, optional): Whether to display names in uppercase. Defaults to False.
98
  section_title_uppercase (bool, optional): Whether to display section titles in uppercase. Defaults to True.
99
- swap_font_sizes_on_two_column (bool, optional): If True and layout is 'two-column', swap the font sizes of title and name. Defaults to False.
100
- label (str | I18nData | None, optional): Component label.
101
- every (float | None, optional): Interval for periodic updates.
102
- inputs (Component | Sequence[Component] | set[Component] | None, optional): Input components for events.
103
- show_label (bool, optional): Whether to show the label. Defaults to False.
104
- container (bool, optional): Whether to render in a container. Defaults to True.
105
- scale (int | None, optional): Scaling factor for the component.
106
- min_width (int, optional): Minimum width in pixels. Defaults to 160.
107
- interactive (bool | None, optional): Whether the component is interactive.
108
- visible (bool, optional): Whether the component is visible. Defaults to True.
109
- elem_id (str | None, optional): Custom HTML element ID.
110
- elem_classes (list[str] | str | None, optional): CSS classes for the component.
111
- render (bool, optional): Whether to render the component. Defaults to True.
112
- key (int | str | tuple[int | str, ...] | None, optional): Component key for state preservation.
113
- preserved_by_key (list[str] | str | None, optional): Properties preserved by key. Defaults to "value".
114
  """
 
115
  self.height = height
116
  self.width = width
117
- self.credits_data = credits if credits is not None else []
118
- self.licenses_paths = licenses or {}
119
- self.effect = effect
120
- self.speed = speed
121
- self.base_font_size = base_font_size
122
- self.intro_title = intro_title
123
- self.intro_subtitle = intro_subtitle
124
- self.sidebar_position = sidebar_position
125
- self.logo_path = logo_path
126
- self.show_logo = show_logo
127
- self.show_licenses = show_licenses
128
- self.show_credits = show_credits
129
- self.logo_position = logo_position
130
- self.logo_sizing = logo_sizing
131
- self.logo_width = logo_width
132
- self.logo_height = logo_height
133
- self.scroll_background_color = scroll_background_color
134
- self.scroll_title_color = scroll_title_color
135
- self.scroll_name_color = scroll_name_color
136
- self.layout_style = layout_style
137
- self.title_uppercase = title_uppercase
138
- self.name_uppercase = name_uppercase
139
- self.section_title_uppercase = section_title_uppercase
140
- self.swap_font_sizes_on_two_column = swap_font_sizes_on_two_column
 
 
 
 
 
 
 
 
 
 
 
141
 
142
  super().__init__(
143
  label=label,
@@ -154,35 +164,14 @@ class CreditsPanel(Component):
154
  key=key,
155
  visible=visible,
156
  preserved_by_key=preserved_by_key,
157
- value=value,
158
  )
159
 
160
- def _process_license_files(self) -> Dict[str, str]:
161
- """
162
- Process license files into a dictionary of name-content pairs.
163
-
164
- Returns:
165
- Dict[str, str]: Dictionary mapping license names to their content or error messages if loading fails.
166
- """
167
- processed = {}
168
- for name, path in self.licenses_paths.items():
169
- try:
170
- with open(path, "r", encoding="utf-8") as f:
171
- processed[name] = f.read()
172
- except Exception as e:
173
- processed[name] = f"Error loading license file '{path}':\n{e}"
174
- return processed
175
-
176
- def _process_logo_path(self) -> Dict[str, Any] | None:
177
- """
178
- Process the logo path, handling both local files and URLs.
179
-
180
- Returns:
181
- 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.
182
- """
183
- if not self.logo_path:
184
  return None
185
- path = str(self.logo_path)
186
  if is_http_url_like(path):
187
  return {"path": None, "url": path, "orig_name": Path(path).name, "mime_type": None}
188
  if os.path.exists(path):
@@ -191,75 +180,55 @@ class CreditsPanel(Component):
191
 
192
  def preprocess(self, payload: Dict[str, Any] | None) -> Dict[str, Any] | None:
193
  """
194
- Preprocess the input payload.
195
-
196
- Args:
197
- payload (Dict[str, Any] | None): Input data to preprocess.
198
-
199
- Returns:
200
- Dict[str, Any] | None: The input payload, returned unchanged.
201
  """
202
  return payload
203
 
204
- def postprocess(self, value: Any) -> Dict[str, Any] | None:
205
  """
206
- Postprocess the component's value to prepare data for rendering.
207
-
 
208
  Args:
209
- value (Any): Input value, typically a list of credits.
210
 
211
  Returns:
212
- Dict[str, Any] | None: Dictionary containing processed credits, licenses, and configuration, or None if no credits or licenses are provided.
213
  """
214
- credits_source = value if isinstance(value, list) else self.credits_data
215
- if not credits_source and not self.licenses_paths:
216
  return None
217
- return {
218
- "credits": credits_source,
219
- "licenses": self._process_license_files() if self.show_licenses else {},
220
- "effect": self.effect,
221
- "speed": self.speed,
222
- "base_font_size": self.base_font_size,
223
- "intro_title": self.intro_title,
224
- "intro_subtitle": self.intro_subtitle,
225
- "sidebar_position": self.sidebar_position,
226
- "logo_path": self._process_logo_path(),
227
- "show_logo": self.show_logo,
228
- "show_licenses": self.show_licenses,
229
- "show_credits": self.show_credits,
230
- "logo_position": self.logo_position,
231
- "logo_sizing": self.logo_sizing,
232
- "logo_width": self.logo_width,
233
- "logo_height": self.logo_height,
234
- "scroll_background_color": self.scroll_background_color,
235
- "scroll_title_color": self.scroll_title_color,
236
- "scroll_name_color": self.scroll_name_color,
237
- "layout_style": self.layout_style,
238
- "title_uppercase": self.title_uppercase,
239
- "name_uppercase": self.name_uppercase,
240
- "section_title_uppercase": self.section_title_uppercase,
241
- "swap_font_sizes_on_two_column": self.swap_font_sizes_on_two_column,
242
- }
243
 
244
- def api_info(self) -> Dict[str, Any]:
245
- """
246
- Provide API information for the component.
247
 
248
- Returns:
249
- Dict[str, Any]: Dictionary indicating the component's data type ("object").
250
- """
 
251
  return {"type": "object"}
252
 
253
  def example_payload(self) -> Any:
254
- """
255
- Provide an example payload for the component.
256
-
257
- Returns:
258
- Dict[str, Any]: Example data structure for the component's payload.
259
- """
260
  return {
261
- "credits": [{"title": "Example", "name": "Credit"}],
262
- "licenses": {},
263
  "effect": "scroll",
264
  "speed": 20,
265
  "sidebar_position": "right",
@@ -274,18 +243,17 @@ class CreditsPanel(Component):
274
  "scroll_background_color": None,
275
  "scroll_title_color": None,
276
  "scroll_name_color": None,
 
277
  "layout_style": "stacked",
278
  "title_uppercase": False,
279
  "name_uppercase": False,
280
  "section_title_uppercase": True,
281
  "swap_font_sizes_on_two_column": False,
 
 
282
  }
283
 
284
  def example_value(self) -> Any:
285
- """
286
- Provide an example value for the component.
287
-
288
- Returns:
289
- List[Dict[str, str]]: Example list of credits.
290
- """
291
- return [{"title": "Example", "name": "Credit"}]
 
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
+ This component is configured via a single dictionary `value` that holds all settings.
17
+ It supports displaying a logo, licenses, sections, and various text styling options.
18
 
19
  Attributes:
20
  EVENTS (list): Supported events for the component, currently only `change`.
 
24
 
25
  def __init__(
26
  self,
27
+ value: Dict[str, Any] | None = None,
 
28
  *,
29
+ # Structural parameters
30
  height: int | str | None = None,
31
  width: int | str | None = None,
32
+ # Configuration parameters (will be part of the `value` dictionary)
33
+ credits: List[Dict[str, str]] | Callable | None = None,
34
  licenses: Dict[str, str | Path] | None = None,
35
  effect: Literal["scroll", "starwars", "matrix"] = "scroll",
36
  speed: float = 40.0,
 
49
  scroll_background_color: str | None = None,
50
  scroll_title_color: str | None = None,
51
  scroll_name_color: str | None = None,
52
+ scroll_section_title_color: str | None = None,
53
  layout_style: Literal["stacked", "two-column"] = "stacked",
54
  title_uppercase: bool = False,
55
  name_uppercase: bool = False,
56
  section_title_uppercase: bool = True,
57
  swap_font_sizes_on_two_column: bool = False,
58
+ scroll_logo_path: str | Path | None = None,
59
+ scroll_logo_height: str = "120px",
60
+ # Standard Gradio parameters
61
  label: str | I18nData | None = None,
62
  every: float | None = None,
63
  inputs: Component | Sequence[Component] | set[Component] | None = None,
 
77
  Initialize the CreditsPanel component.
78
 
79
  Args:
80
+ value (Dict[str, Any], optional): The dictionary containing the component's full configuration.
81
+ If provided, it overrides all other configuration parameters.
82
+ height (int | str | None, optional): The structural height of the component.
83
+ width (int | str | None, optional): The structural width of the component.
84
+ credits (List[Dict[str, str]] | Callable | None, optional): List of credits, can include section headers.
85
+ licenses (Dict[str, str | Path] | None, optional): Dictionary mapping license names to file paths.
86
+ effect (Literal["scroll", "starwars", "matrix"], optional): Visual effect for the credits. Defaults to "scroll".
87
  speed (float, optional): Animation speed in seconds. Defaults to 40.0.
88
+ base_font_size (float, optional): Base font size in rem. Defaults to 1.5.
89
+ intro_title (str | None, optional): Title for the intro sequence.
90
+ intro_subtitle (str | None, optional): Subtitle for the intro sequence.
91
  sidebar_position (Literal["right", "bottom"], optional): Position of the licenses sidebar. Defaults to "right".
92
+ logo_path (str | Path | None, optional): Path or URL to the main static logo.
93
+ show_logo (bool, optional): Whether to display the main logo. Defaults to True.
94
  show_licenses (bool, optional): Whether to display licenses. Defaults to True.
95
+ show_credits (bool, optional): Whether to display the credits panel. Defaults to True.
96
+ logo_position (Literal["center", "left", "right"], optional): Main logo alignment. Defaults to "center".
97
+ logo_sizing (Literal["stretch", "crop", "resize"], optional): Main logo sizing mode. Defaults to "resize".
98
+ logo_width (int | str | None, optional): Main logo width.
99
+ logo_height (int | str | None, optional): Main logo height.
100
+ scroll_background_color (str | None, optional): Background color for the scroll effect.
101
  scroll_title_color (str | None, optional): Color for credit titles.
102
  scroll_name_color (str | None, optional): Color for credit names.
103
+ scroll_section_title_color (str | None, optional): Color for section titles in the scroll effect.
104
+ layout_style (Literal["stacked", "two-column"], optional): Layout for credits. Defaults to "stacked".
105
  title_uppercase (bool, optional): Whether to display titles in uppercase. Defaults to False.
106
+ name_uppercase (bool, optional): Whether to display names in uppercase. Defaults to False.
107
  section_title_uppercase (bool, optional): Whether to display section titles in uppercase. Defaults to True.
108
+ swap_font_sizes_on_two_column (bool, optional): Swaps title and name font sizes in two-column layout. Defaults to False.
109
+ scroll_logo_path (str | Path | None, optional): Path or URL to a logo inside the scrolling credits.
110
+ scroll_logo_height (str, optional): The height of the scrolling logo. Defaults to "120px".
111
+ (Other standard Gradio arguments)
 
 
 
 
 
 
 
 
 
 
 
112
  """
113
+ # Structural parameters are stored directly on self.
114
  self.height = height
115
  self.width = width
116
+
117
+ # All other configuration parameters are bundled into a dictionary.
118
+ # This dictionary is the component's main `value`.
119
+ self._config = {
120
+ "credits": credits if credits is not None else [],
121
+ "licenses": licenses or {},
122
+ "effect": effect,
123
+ "speed": speed,
124
+ "base_font_size": base_font_size,
125
+ "intro_title": intro_title,
126
+ "intro_subtitle": intro_subtitle,
127
+ "sidebar_position": sidebar_position,
128
+ "logo_path": logo_path,
129
+ "show_logo": show_logo,
130
+ "show_licenses": show_licenses,
131
+ "show_credits": show_credits,
132
+ "logo_position": logo_position,
133
+ "logo_sizing": logo_sizing,
134
+ "logo_width": logo_width,
135
+ "logo_height": logo_height,
136
+ "scroll_background_color": scroll_background_color,
137
+ "scroll_title_color": scroll_title_color,
138
+ "scroll_name_color": scroll_name_color,
139
+ "scroll_section_title_color": scroll_section_title_color,
140
+ "layout_style": layout_style,
141
+ "title_uppercase": title_uppercase,
142
+ "name_uppercase": name_uppercase,
143
+ "section_title_uppercase": section_title_uppercase,
144
+ "swap_font_sizes_on_two_column": swap_font_sizes_on_two_column,
145
+ "scroll_logo_path": scroll_logo_path,
146
+ "scroll_logo_height": scroll_logo_height,
147
+ }
148
+
149
+ # If a `value` dictionary is passed directly, it overrides the individual parameters.
150
+ initial_value = value if value is not None else self._config
151
 
152
  super().__init__(
153
  label=label,
 
164
  key=key,
165
  visible=visible,
166
  preserved_by_key=preserved_by_key,
167
+ value=initial_value, # The component's state is its configuration dictionary.
168
  )
169
 
170
+ def _process_image_path(self, image_path: str | Path | None) -> Dict[str, Any] | None:
171
+ """Helper function to process an image path, handling local files and URLs."""
172
+ if not image_path:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
173
  return None
174
+ path = str(image_path)
175
  if is_http_url_like(path):
176
  return {"path": None, "url": path, "orig_name": Path(path).name, "mime_type": None}
177
  if os.path.exists(path):
 
180
 
181
  def preprocess(self, payload: Dict[str, Any] | None) -> Dict[str, Any] | None:
182
  """
183
+ Passes the payload from the frontend through, unmodified.
 
 
 
 
 
 
184
  """
185
  return payload
186
 
187
+ def postprocess(self, value: Dict[str, Any] | None) -> Dict[str, Any] | None:
188
  """
189
+ Processes the component's `value` dictionary to prepare data for the frontend.
190
+ This involves resolving file paths for licenses and images.
191
+
192
  Args:
193
+ value (Dict[str, Any] | None): The component's configuration dictionary.
194
 
195
  Returns:
196
+ Dict[str, Any] | None: The processed dictionary ready to be sent to the frontend.
197
  """
198
+ if not value:
 
199
  return None
200
+
201
+ processed_value = value.copy()
202
+
203
+ # Process license file paths from the 'licenses' dictionary
204
+ license_paths = processed_value.get("licenses", {})
205
+ processed_licenses = {}
206
+ if isinstance(license_paths, dict): # Check if licenses are in the expected format
207
+ for name, path in license_paths.items():
208
+ try:
209
+ with open(path, "r", encoding="utf-8") as f:
210
+ processed_licenses[name] = f.read()
211
+ except Exception as e:
212
+ # Silently fail or log, to avoid crashing the app for a missing license file
213
+ processed_licenses[name] = f"Error loading license file '{path}':\n{e}"
214
+ processed_value["licenses"] = processed_licenses
 
 
 
 
 
 
 
 
 
 
 
215
 
216
+ # Process image paths
217
+ processed_value["logo_path"] = self._process_image_path(processed_value.get("logo_path"))
218
+ processed_value["scroll_logo_path"] = self._process_image_path(processed_value.get("scroll_logo_path"))
219
 
220
+ return processed_value
221
+
222
+ def api_info(self) -> Dict[str, Any]:
223
+ """Returns API info for the component."""
224
  return {"type": "object"}
225
 
226
  def example_payload(self) -> Any:
227
+ """Returns an example payload for the component's API."""
228
+ # This now directly returns a dictionary matching the `value` structure.
 
 
 
 
229
  return {
230
+ "credits": [{"title": "API Example", "name": "Credit"}],
231
+ "licenses": {"MIT": "MIT License text..."},
232
  "effect": "scroll",
233
  "speed": 20,
234
  "sidebar_position": "right",
 
243
  "scroll_background_color": None,
244
  "scroll_title_color": None,
245
  "scroll_name_color": None,
246
+ "scroll_section_title_color": None,
247
  "layout_style": "stacked",
248
  "title_uppercase": False,
249
  "name_uppercase": False,
250
  "section_title_uppercase": True,
251
  "swap_font_sizes_on_two_column": False,
252
+ "scroll_logo_path": None,
253
+ "scroll_logo_height": "120px",
254
  }
255
 
256
  def example_value(self) -> Any:
257
+ """Returns an example value for the component."""
258
+ # The example value is now the entire configuration dictionary.
259
+ return self.example_payload()
 
 
 
 
src/backend/gradio_creditspanel/templates/component/index.js CHANGED
The diff for this file is too large to render. See raw diff
 
src/backend/gradio_creditspanel/templates/component/style.css CHANGED
@@ -1 +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-1t8lt48.svelte-1t8lt48{width:100%;height:100%;overflow:hidden;position:relative;font-family:sans-serif}.credits-container.svelte-1t8lt48.svelte-1t8lt48{position:absolute;bottom:0;transform:translateY(100%);width:100%;text-align:center;animation:svelte-1t8lt48-scroll var(--animation-duration) linear infinite;padding:0 2rem;box-sizing:border-box}.section-title.svelte-1t8lt48.svelte-1t8lt48{margin-top:4rem;margin-bottom:2.5rem;font-weight:700}.credit.intro-block.svelte-1t8lt48.svelte-1t8lt48{margin-bottom:5rem}.credit.svelte-1t8lt48.svelte-1t8lt48{margin-bottom:2rem}.credit.svelte-1t8lt48 h2.svelte-1t8lt48,.credit.svelte-1t8lt48 p.svelte-1t8lt48{margin:.5rem 0;font-family:sans-serif}.credit-two-column.svelte-1t8lt48.svelte-1t8lt48{display:flex;justify-content:space-between;align-items:baseline;text-align:left;margin:.8rem auto;max-width:80%;gap:1rem}.credit-two-column.svelte-1t8lt48 .title.svelte-1t8lt48{flex:1;text-align:right;padding-right:1rem}.credit-two-column.svelte-1t8lt48 .name.svelte-1t8lt48{flex:1;text-align:left;padding-left:1rem}.uppercase.svelte-1t8lt48.svelte-1t8lt48{text-transform:uppercase}@keyframes svelte-1t8lt48-scroll{0%{transform:translateY(100%)}to{transform:translateY(-100%)}}.viewport.svelte-upsott.svelte-upsott{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-upsott.svelte-upsott{position:absolute;top:0;left:0;width:1px;height:1px;background:transparent;z-index:0;animation:svelte-upsott-twinkle 10s linear infinite}.stars.small.svelte-upsott.svelte-upsott{animation-duration:10s}.stars.medium.svelte-upsott.svelte-upsott{animation-duration:15s}.stars.large.svelte-upsott.svelte-upsott{animation-duration:20s}@keyframes svelte-upsott-twinkle{0%{opacity:.6}50%{opacity:1}to{opacity:.6}}.crawl.svelte-upsott.svelte-upsott{position:absolute;width:100%;bottom:0;transform-origin:50% 100%;animation:svelte-upsott-crawl-animation var(--animation-duration) linear infinite;z-index:1;text-align:center}@keyframes svelte-upsott-crawl-animation{0%{transform:rotateX(60deg) translateY(100%) translateZ(100px)}to{transform:rotateX(60deg) translateY(-150%) translateZ(-1200px)}}.uppercase.svelte-upsott.svelte-upsott{text-transform:uppercase}.section-title.svelte-upsott.svelte-upsott{margin-top:5rem;margin-bottom:3rem;font-weight:700}.credit.intro-block.svelte-upsott.svelte-upsott{margin-bottom:5rem}.credit.svelte-upsott.svelte-upsott{margin-bottom:2rem}.credit.svelte-upsott h2.svelte-upsott,.credit.svelte-upsott p.svelte-upsott{margin:.5rem 0;padding:0;white-space:nowrap}.credit-two-column.svelte-upsott.svelte-upsott{display:flex;justify-content:space-between;align-items:baseline;margin:.8rem auto;width:90%;white-space:nowrap}.credit-two-column.svelte-upsott .spacer.svelte-upsott{flex-grow:1;border-bottom:1px dotted rgba(254,218,74,.3);margin:0 1em;transform:translateY(-.5em)}.matrix-container.svelte-1851m15.svelte-1851m15{width:100%;height:100%;position:relative;overflow:hidden}canvas.svelte-1851m15.svelte-1851m15{display:block;position:absolute;top:0;left:0;width:100%;height:100%;z-index:1}.credits-scroll-overlay.svelte-1851m15.svelte-1851m15{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-1851m15.svelte-1851m15{position:absolute;width:100%;bottom:0;transform:translateY(100%);animation:svelte-1851m15-scroll-from-bottom var(--animation-duration) linear infinite;padding:0 2rem;box-sizing:border-box}@keyframes svelte-1851m15-scroll-from-bottom{0%{transform:translateY(100%)}to{transform:translateY(-100%)}}.uppercase.svelte-1851m15.svelte-1851m15{text-transform:uppercase}.section-title.svelte-1851m15.svelte-1851m15{margin-top:4rem;margin-bottom:2.5rem;font-weight:700;color:#5f5;text-shadow:0 0 8px #0f0}.credit-block.intro-block.svelte-1851m15.svelte-1851m15{margin-bottom:5rem}.credit-block.svelte-1851m15.svelte-1851m15{margin-bottom:2.5em}.title.svelte-1851m15.svelte-1851m15{color:#0f0;opacity:.8}.name.svelte-1851m15.svelte-1851m15{font-weight:700;color:#5f5;text-shadow:0 0 5px #0f0}.credit-two-column.svelte-1851m15.svelte-1851m15{display:flex;justify-content:space-between;align-items:baseline;text-align:left;margin:.8em auto;max-width:80%;gap:1em}.credit-two-column.svelte-1851m15 .title.svelte-1851m15{flex:1;text-align:right;padding-right:1em}.credit-two-column.svelte-1851m15 .name.svelte-1851m15{flex:1;text-align:left;padding-left:1em}.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-1hawtr7.svelte-1hawtr7,.outer-credits-wrapper.svelte-1hawtr7.svelte-1hawtr7{display:flex;flex-direction:column;width:100%;border:none}.logo-panel.svelte-1hawtr7.svelte-1hawtr7{background:var(--background-fill-primary);border:none;display:flex!important;align-items:center;justify-content:var(--logo-justify, center);padding:0 0 20px;width:100%}.credits-panel-wrapper.svelte-1hawtr7.svelte-1hawtr7{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-1hawtr7.svelte-1hawtr7{flex-grow:1;flex-shrink:1;min-width:200px;background:#000;overflow:hidden;position:relative}.licenses-sidebar.svelte-1hawtr7.svelte-1hawtr7{width:calc(100% - var(--main-panel-width, 400px));max-width:100%;max-height:var(--sidebar-max-height, none);flex-shrink:1;flex-grow:1;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-1hawtr7 h3.svelte-1hawtr7{margin:var(--spacing-lg);font-size:var(--text-lg)}.licenses-sidebar.svelte-1hawtr7 li.svelte-1hawtr7{padding:0;margin:0;cursor:default;border-bottom:1px solid var(--border-color-primary)}.licenses-sidebar.svelte-1hawtr7 li button.svelte-1hawtr7{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-1hawtr7 li button.svelte-1hawtr7:hover{background-color:var(--background-fill-primary)}.licenses-sidebar.svelte-1hawtr7 li button.svelte-1hawtr7:focus-visible{outline:2px solid var(--color-accent);outline-offset:-2px}.licenses-sidebar.svelte-1hawtr7 li button.selected.svelte-1hawtr7{background-color:var(--color-accent);color:#fff;font-weight:700}.license-display.svelte-1hawtr7.svelte-1hawtr7{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-1hawtr7 h4.svelte-1hawtr7{margin-top:0}.license-display.svelte-1hawtr7 pre.svelte-1hawtr7{white-space:pre-wrap;word-break:break-word;font-size:var(--text-sm);color:var(--body-text-color-subdued)}
 
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-1nrfbo6.svelte-1nrfbo6{width:100%;height:100%;overflow:hidden;position:relative;font-family:sans-serif}.scroll-logo-container.svelte-1nrfbo6.svelte-1nrfbo6{text-align:center;margin-bottom:2rem}.scroll-logo-container.svelte-1nrfbo6 img.svelte-1nrfbo6{display:block;margin-left:auto;margin-right:auto;max-width:80%;object-fit:contain}.credits-container.svelte-1nrfbo6.svelte-1nrfbo6{position:absolute;bottom:0;transform:translateY(100%);width:100%;text-align:center;animation:svelte-1nrfbo6-scroll var(--animation-duration) linear infinite;padding:0 2rem;box-sizing:border-box}.section-title.svelte-1nrfbo6.svelte-1nrfbo6{margin-top:4rem;margin-bottom:2.5rem;font-weight:700}.credit.intro-block.svelte-1nrfbo6.svelte-1nrfbo6{margin-bottom:5rem}.credit.svelte-1nrfbo6.svelte-1nrfbo6{margin-bottom:2rem}.credit.svelte-1nrfbo6 h2.svelte-1nrfbo6,.credit.svelte-1nrfbo6 p.svelte-1nrfbo6{margin:.5rem 0;font-family:sans-serif}.credit-two-column.svelte-1nrfbo6.svelte-1nrfbo6{display:flex;justify-content:space-between;align-items:baseline;text-align:left;margin:.8rem auto;max-width:80%;gap:1rem}.credit-two-column.svelte-1nrfbo6 .title.svelte-1nrfbo6{flex:1;text-align:right;padding-right:1rem}.credit-two-column.svelte-1nrfbo6 .name.svelte-1nrfbo6{flex:1;text-align:left;padding-left:1rem}.uppercase.svelte-1nrfbo6.svelte-1nrfbo6{text-transform:uppercase}@keyframes svelte-1nrfbo6-scroll{0%{transform:translateY(100%)}to{transform:translateY(-100%)}}.viewport.svelte-1hia40q.svelte-1hia40q{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-1hia40q.svelte-1hia40q{position:absolute;top:0;left:0;width:1px;height:1px;background:transparent;z-index:0;animation:svelte-1hia40q-twinkle 10s linear infinite}.stars.small.svelte-1hia40q.svelte-1hia40q{animation-duration:10s}.stars.medium.svelte-1hia40q.svelte-1hia40q{animation-duration:15s}.stars.large.svelte-1hia40q.svelte-1hia40q{animation-duration:20s}@keyframes svelte-1hia40q-twinkle{0%{opacity:.6}50%{opacity:1}to{opacity:.6}}.crawl.svelte-1hia40q.svelte-1hia40q{position:absolute;width:100%;bottom:0;transform-origin:50% 100%;animation:svelte-1hia40q-crawl-animation var(--animation-duration) linear infinite;z-index:1;text-align:center}@keyframes svelte-1hia40q-crawl-animation{0%{transform:rotateX(60deg) translateY(100%) translateZ(100px)}to{transform:rotateX(60deg) translateY(-150%) translateZ(-1200px)}}.scroll-logo-container.svelte-1hia40q.svelte-1hia40q{text-align:center;margin-bottom:2rem}.scroll-logo-container.svelte-1hia40q img.svelte-1hia40q{display:block;margin-left:auto;margin-right:auto;max-width:70%;object-fit:contain;filter:grayscale(1) sepia(100%) hue-rotate(15deg) saturate(8) brightness(1.2)}.uppercase.svelte-1hia40q.svelte-1hia40q{text-transform:uppercase}.section-title.svelte-1hia40q.svelte-1hia40q{margin-top:5rem;margin-bottom:3rem;font-weight:700}.credit.intro-block.svelte-1hia40q.svelte-1hia40q{margin-bottom:5rem}.credit.svelte-1hia40q.svelte-1hia40q{margin-bottom:2rem}.credit.svelte-1hia40q h2.svelte-1hia40q,.credit.svelte-1hia40q p.svelte-1hia40q{margin:.5rem 0;padding:0;white-space:nowrap}.credit-two-column.svelte-1hia40q.svelte-1hia40q{display:flex;justify-content:space-between;align-items:baseline;margin:.8rem auto;width:90%;white-space:nowrap}.credit-two-column.svelte-1hia40q .spacer.svelte-1hia40q{flex-grow:1;border-bottom:1px dotted rgba(254,218,74,.3);margin:0 1em;transform:translateY(-.5em)}.matrix-container.svelte-1bt5wt2.svelte-1bt5wt2{width:100%;height:100%;position:relative;overflow:hidden}canvas.svelte-1bt5wt2.svelte-1bt5wt2{display:block;position:absolute;top:0;left:0;width:100%;height:100%;z-index:1}.credits-scroll-overlay.svelte-1bt5wt2.svelte-1bt5wt2{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-1bt5wt2.svelte-1bt5wt2{position:absolute;width:100%;bottom:0;transform:translateY(100%);animation:svelte-1bt5wt2-scroll-from-bottom var(--animation-duration) linear infinite;padding:0 2rem;box-sizing:border-box}@keyframes svelte-1bt5wt2-scroll-from-bottom{0%{transform:translateY(100%)}to{transform:translateY(-100%)}}.uppercase.svelte-1bt5wt2.svelte-1bt5wt2{text-transform:uppercase}.section-title.svelte-1bt5wt2.svelte-1bt5wt2{margin-top:4rem;margin-bottom:2.5rem;font-weight:700;color:#5f5;text-shadow:0 0 8px #0f0}.credit-block.intro-block.svelte-1bt5wt2.svelte-1bt5wt2{margin-bottom:5rem}.credit-block.svelte-1bt5wt2.svelte-1bt5wt2{margin-bottom:2.5em}.title.svelte-1bt5wt2.svelte-1bt5wt2{color:#0f0;opacity:.8}.name.svelte-1bt5wt2.svelte-1bt5wt2{font-weight:700;color:#5f5;text-shadow:0 0 5px #0f0}.credit-two-column.svelte-1bt5wt2.svelte-1bt5wt2{display:flex;justify-content:space-between;align-items:baseline;text-align:left;margin:.8em auto;max-width:80%;gap:1em}.credit-two-column.svelte-1bt5wt2 .title.svelte-1bt5wt2{flex:1;text-align:right;padding-right:1em}.credit-two-column.svelte-1bt5wt2 .name.svelte-1bt5wt2{flex:1;text-align:left;padding-left:1em}.scroll-logo-container.svelte-1bt5wt2.svelte-1bt5wt2{text-align:center;margin-bottom:2rem}.scroll-logo-container.svelte-1bt5wt2 img.svelte-1bt5wt2{display:block;margin-left:auto;margin-right:auto;max-width:80%;object-fit:contain;filter:grayscale(1) brightness(.5) sepia(100%) hue-rotate(50deg) saturate(500%)}.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-1hawtr7.svelte-1hawtr7,.outer-credits-wrapper.svelte-1hawtr7.svelte-1hawtr7{display:flex;flex-direction:column;width:100%;border:none}.logo-panel.svelte-1hawtr7.svelte-1hawtr7{background:var(--background-fill-primary);border:none;display:flex!important;align-items:center;justify-content:var(--logo-justify, center);padding:0 0 20px;width:100%}.credits-panel-wrapper.svelte-1hawtr7.svelte-1hawtr7{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-1hawtr7.svelte-1hawtr7{flex-grow:1;flex-shrink:1;min-width:200px;background:#000;overflow:hidden;position:relative}.licenses-sidebar.svelte-1hawtr7.svelte-1hawtr7{width:calc(100% - var(--main-panel-width, 400px));max-width:100%;max-height:var(--sidebar-max-height, none);flex-shrink:1;flex-grow:1;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-1hawtr7 h3.svelte-1hawtr7{margin:var(--spacing-lg);font-size:var(--text-lg)}.licenses-sidebar.svelte-1hawtr7 li.svelte-1hawtr7{padding:0;margin:0;cursor:default;border-bottom:1px solid var(--border-color-primary)}.licenses-sidebar.svelte-1hawtr7 li button.svelte-1hawtr7{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-1hawtr7 li button.svelte-1hawtr7:hover{background-color:var(--background-fill-primary)}.licenses-sidebar.svelte-1hawtr7 li button.svelte-1hawtr7:focus-visible{outline:2px solid var(--color-accent);outline-offset:-2px}.licenses-sidebar.svelte-1hawtr7 li button.selected.svelte-1hawtr7{background-color:var(--color-accent);color:#fff;font-weight:700}.license-display.svelte-1hawtr7.svelte-1hawtr7{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-1hawtr7 h4.svelte-1hawtr7{margin-top:0}.license-display.svelte-1hawtr7 pre.svelte-1hawtr7{white-space:pre-wrap;word-break:break-word;font-size:var(--text-sm);color:var(--body-text-color-subdued)}
src/demo/app.py CHANGED
@@ -18,7 +18,7 @@ def setup_demo_files():
18
  with open("./assets/logo.webp", "w") as f:
19
  f.write("Placeholder WebP logo")
20
 
21
- # --- UPDATED: Credits list with sections ---
22
  credits_list = [
23
  {"section_title": "Project Leadership"},
24
  {"title": "Project Manager", "name": "Emma Thompson"},
@@ -47,6 +47,8 @@ DEFAULT_SPEEDS = {
47
  "starwars": 70.0,
48
  "matrix": 40.0
49
  }
 
 
50
 
51
  def update_panel(
52
  effect: str,
@@ -64,39 +66,52 @@ def update_panel(
64
  logo_height: str | None,
65
  scroll_background_color: str | None,
66
  scroll_title_color: str | None,
 
67
  scroll_name_color: str | None,
68
  layout_style: str,
69
  title_uppercase: bool,
70
  name_uppercase: bool,
71
  section_title_uppercase: bool,
72
- swap_font_sizes: bool
 
 
73
  ) -> dict:
74
  """Callback function that updates all properties of the CreditsPanel component."""
75
- return gr.update(
76
- visible=True,
77
- effect=effect,
78
- speed=speed,
79
- base_font_size=base_font_size,
80
- intro_title=intro_title,
81
- intro_subtitle=intro_subtitle,
82
- sidebar_position=sidebar_position,
83
- show_logo=show_logo,
84
- show_licenses=show_licenses,
85
- show_credits=show_credits,
86
- logo_position=logo_position,
87
- logo_sizing=logo_sizing,
88
- logo_width=logo_width,
89
- logo_height=logo_height,
90
- scroll_background_color=scroll_background_color,
91
- scroll_title_color=scroll_title_color,
92
- scroll_name_color=scroll_name_color,
93
- layout_style=layout_style,
94
- title_uppercase=title_uppercase,
95
- name_uppercase=name_uppercase,
96
- section_title_uppercase=section_title_uppercase,
97
- swap_font_sizes_on_two_column=swap_font_sizes,
98
- value=credits_list
99
- )
 
 
 
 
 
 
 
 
 
 
100
 
101
  def update_ui_on_effect_change(effect: str) -> tuple[float, float]:
102
  """Updates sliders to sensible defaults when the animation effect is changed."""
@@ -138,6 +153,14 @@ with gr.Blocks(theme=gr.themes.Ocean(), title="CreditsPanel Demo") as demo:
138
  name_uppercase_checkbox = gr.Checkbox(label="Name Uppercase", value=False)
139
  section_title_uppercase_checkbox = gr.Checkbox(label="Section Uppercase", value=True)
140
 
 
 
 
 
 
 
 
 
141
  gr.Markdown("### Intro Text")
142
  intro_title_input = gr.Textbox(label="Intro Title", value="Gradio")
143
  intro_subtitle_input = gr.Textbox(label="Intro Subtitle", value="The best UI framework")
@@ -157,19 +180,20 @@ with gr.Blocks(theme=gr.themes.Ocean(), title="CreditsPanel Demo") as demo:
157
  gr.Markdown("### Color Settings (Scroll Effect)")
158
  scroll_background_color = gr.ColorPicker(label="Background Color", value="#000000")
159
  scroll_title_color = gr.ColorPicker(label="Title Color", value="#FFFFFF")
 
160
  scroll_name_color = gr.ColorPicker(label="Name Color", value="#FFFFFF")
161
 
162
- panel = CreditsPanel(
 
163
  credits=credits_list,
164
  licenses=license_paths,
165
  effect="scroll",
166
- height=500,
167
  speed=DEFAULT_SPEEDS["scroll"],
168
  base_font_size=1.5,
169
  intro_title="Gradio",
170
  intro_subtitle="The best UI framework",
171
  sidebar_position="right",
172
- logo_path="./assets/logo.webp",
173
  show_logo=True,
174
  show_licenses=True,
175
  show_credits=True,
@@ -180,11 +204,14 @@ with gr.Blocks(theme=gr.themes.Ocean(), title="CreditsPanel Demo") as demo:
180
  scroll_background_color="#000000",
181
  scroll_title_color="#FFFFFF",
182
  scroll_name_color="#FFFFFF",
 
183
  layout_style="stacked",
184
  title_uppercase=False,
185
  name_uppercase=False,
186
  section_title_uppercase=True,
187
  swap_font_sizes_on_two_column=False,
 
 
188
  )
189
 
190
  inputs = [
@@ -202,15 +229,23 @@ with gr.Blocks(theme=gr.themes.Ocean(), title="CreditsPanel Demo") as demo:
202
  logo_width_input,
203
  logo_height_input,
204
  scroll_background_color,
205
- scroll_title_color,
 
206
  scroll_name_color,
207
  layout_style_radio,
208
  title_uppercase_checkbox,
209
  name_uppercase_checkbox,
210
  section_title_uppercase_checkbox,
211
- swap_sizes_checkbox
 
 
212
  ]
213
 
 
 
 
 
 
214
  layout_style_radio.change(
215
  fn=toggle_swap_checkbox_visibility,
216
  inputs=layout_style_radio,
 
18
  with open("./assets/logo.webp", "w") as f:
19
  f.write("Placeholder WebP logo")
20
 
21
+ # --- Credits list with sections ---
22
  credits_list = [
23
  {"section_title": "Project Leadership"},
24
  {"title": "Project Manager", "name": "Emma Thompson"},
 
47
  "starwars": 70.0,
48
  "matrix": 40.0
49
  }
50
+ SCROLL_LOGO_PATH = "./assets/gradio_logo_white.png"
51
+ LOGO_PATH="./assets/logo.webp"
52
 
53
  def update_panel(
54
  effect: str,
 
66
  logo_height: str | None,
67
  scroll_background_color: str | None,
68
  scroll_title_color: str | None,
69
+ scroll_section_title_color: str | None,
70
  scroll_name_color: str | None,
71
  layout_style: str,
72
  title_uppercase: bool,
73
  name_uppercase: bool,
74
  section_title_uppercase: bool,
75
+ swap_font_sizes: bool,
76
+ show_scroll_logo: bool,
77
+ scroll_logo_height: str | None
78
  ) -> dict:
79
  """Callback function that updates all properties of the CreditsPanel component."""
80
+
81
+ scroll_logo_path = SCROLL_LOGO_PATH if show_scroll_logo else None
82
+
83
+ if not scroll_logo_height:
84
+ scroll_logo_height = "120px"
85
+
86
+ return {
87
+ "credits": credits_list,
88
+ "licenses": license_paths,
89
+ "effect": effect,
90
+ "speed": speed,
91
+ "base_font_size": base_font_size,
92
+ "intro_title": intro_title,
93
+ "intro_subtitle": intro_subtitle,
94
+ "sidebar_position": sidebar_position,
95
+ "logo_path": LOGO_PATH,
96
+ "show_logo": show_logo,
97
+ "show_licenses": show_licenses,
98
+ "show_credits": show_credits,
99
+ "logo_position": logo_position,
100
+ "logo_sizing": logo_sizing,
101
+ "logo_width": logo_width,
102
+ "logo_height": logo_height,
103
+ "scroll_background_color": scroll_background_color,
104
+ "scroll_title_color": scroll_title_color,
105
+ "scroll_name_color": scroll_name_color,
106
+ "scroll_section_title_color": scroll_section_title_color,
107
+ "layout_style": layout_style,
108
+ "title_uppercase": title_uppercase,
109
+ "name_uppercase": name_uppercase,
110
+ "section_title_uppercase": section_title_uppercase,
111
+ "swap_font_sizes_on_two_column": swap_font_sizes,
112
+ "scroll_logo_path": scroll_logo_path,
113
+ "scroll_logo_height": scroll_logo_height,
114
+ }
115
 
116
  def update_ui_on_effect_change(effect: str) -> tuple[float, float]:
117
  """Updates sliders to sensible defaults when the animation effect is changed."""
 
153
  name_uppercase_checkbox = gr.Checkbox(label="Name Uppercase", value=False)
154
  section_title_uppercase_checkbox = gr.Checkbox(label="Section Uppercase", value=True)
155
 
156
+ gr.Markdown("### Scrolling Logo")
157
+ show_scroll_logo_checkbox = gr.Checkbox(
158
+ label="Show Logo in Credits Roll",
159
+ value=True,
160
+ info="Toggles the logo above the intro text."
161
+ )
162
+ scroll_logo_height_input = gr.Textbox(label="Scrolling Logo Height", value="100px")
163
+
164
  gr.Markdown("### Intro Text")
165
  intro_title_input = gr.Textbox(label="Intro Title", value="Gradio")
166
  intro_subtitle_input = gr.Textbox(label="Intro Subtitle", value="The best UI framework")
 
180
  gr.Markdown("### Color Settings (Scroll Effect)")
181
  scroll_background_color = gr.ColorPicker(label="Background Color", value="#000000")
182
  scroll_title_color = gr.ColorPicker(label="Title Color", value="#FFFFFF")
183
+ scroll_section_title_color = gr.ColorPicker(label="Section Title Color", value="#FFFFFF")
184
  scroll_name_color = gr.ColorPicker(label="Name Color", value="#FFFFFF")
185
 
186
+ panel = CreditsPanel(
187
+ height=500,
188
  credits=credits_list,
189
  licenses=license_paths,
190
  effect="scroll",
 
191
  speed=DEFAULT_SPEEDS["scroll"],
192
  base_font_size=1.5,
193
  intro_title="Gradio",
194
  intro_subtitle="The best UI framework",
195
  sidebar_position="right",
196
+ logo_path=LOGO_PATH,
197
  show_logo=True,
198
  show_licenses=True,
199
  show_credits=True,
 
204
  scroll_background_color="#000000",
205
  scroll_title_color="#FFFFFF",
206
  scroll_name_color="#FFFFFF",
207
+ scroll_section_title_color="#FFFFFF",
208
  layout_style="stacked",
209
  title_uppercase=False,
210
  name_uppercase=False,
211
  section_title_uppercase=True,
212
  swap_font_sizes_on_two_column=False,
213
+ scroll_logo_path=SCROLL_LOGO_PATH,
214
+ scroll_logo_height="100px",
215
  )
216
 
217
  inputs = [
 
229
  logo_width_input,
230
  logo_height_input,
231
  scroll_background_color,
232
+ scroll_title_color,
233
+ scroll_section_title_color,
234
  scroll_name_color,
235
  layout_style_radio,
236
  title_uppercase_checkbox,
237
  name_uppercase_checkbox,
238
  section_title_uppercase_checkbox,
239
+ swap_sizes_checkbox,
240
+ show_scroll_logo_checkbox,
241
+ scroll_logo_height_input
242
  ]
243
 
244
+ demo.load(
245
+ fn=update_panel,
246
+ inputs=inputs,
247
+ outputs=panel
248
+ )
249
  layout_style_radio.change(
250
  fn=toggle_swap_checkbox_visibility,
251
  inputs=layout_style_radio,
src/demo/space.py CHANGED
@@ -3,7 +3,7 @@ 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}, 'show_credits': {'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}, 'layout_style': {'type': '"stacked" | "two-column"', 'default': '"stacked"', 'description': None}, 'title_uppercase': {'type': 'bool', 'default': 'False', 'description': None}, 'name_uppercase': {'type': 'bool', 'default': 'False', 'description': None}, 'section_title_uppercase': {'type': 'bool', 'default': 'True', 'description': None}, 'swap_font_sizes_on_two_column': {'type': 'bool', 'default': 'False', '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
 
@@ -58,7 +58,7 @@ def setup_demo_files():
58
  with open("./assets/logo.webp", "w") as f:
59
  f.write("Placeholder WebP logo")
60
 
61
- # --- UPDATED: Credits list with sections ---
62
  credits_list = [
63
  {"section_title": "Project Leadership"},
64
  {"title": "Project Manager", "name": "Emma Thompson"},
@@ -87,6 +87,8 @@ DEFAULT_SPEEDS = {
87
  "starwars": 70.0,
88
  "matrix": 40.0
89
  }
 
 
90
 
91
  def update_panel(
92
  effect: str,
@@ -104,39 +106,52 @@ def update_panel(
104
  logo_height: str | None,
105
  scroll_background_color: str | None,
106
  scroll_title_color: str | None,
 
107
  scroll_name_color: str | None,
108
  layout_style: str,
109
  title_uppercase: bool,
110
  name_uppercase: bool,
111
  section_title_uppercase: bool,
112
- swap_font_sizes: bool
 
 
113
  ) -> dict:
114
  \"\"\"Callback function that updates all properties of the CreditsPanel component.\"\"\"
115
- return gr.update(
116
- visible=True,
117
- effect=effect,
118
- speed=speed,
119
- base_font_size=base_font_size,
120
- intro_title=intro_title,
121
- intro_subtitle=intro_subtitle,
122
- sidebar_position=sidebar_position,
123
- show_logo=show_logo,
124
- show_licenses=show_licenses,
125
- show_credits=show_credits,
126
- logo_position=logo_position,
127
- logo_sizing=logo_sizing,
128
- logo_width=logo_width,
129
- logo_height=logo_height,
130
- scroll_background_color=scroll_background_color,
131
- scroll_title_color=scroll_title_color,
132
- scroll_name_color=scroll_name_color,
133
- layout_style=layout_style,
134
- title_uppercase=title_uppercase,
135
- name_uppercase=name_uppercase,
136
- section_title_uppercase=section_title_uppercase,
137
- swap_font_sizes_on_two_column=swap_font_sizes,
138
- value=credits_list
139
- )
 
 
 
 
 
 
 
 
 
 
140
 
141
  def update_ui_on_effect_change(effect: str) -> tuple[float, float]:
142
  \"\"\"Updates sliders to sensible defaults when the animation effect is changed.\"\"\"
@@ -178,6 +193,14 @@ with gr.Blocks(theme=gr.themes.Ocean(), title="CreditsPanel Demo") as demo:
178
  name_uppercase_checkbox = gr.Checkbox(label="Name Uppercase", value=False)
179
  section_title_uppercase_checkbox = gr.Checkbox(label="Section Uppercase", value=True)
180
 
 
 
 
 
 
 
 
 
181
  gr.Markdown("### Intro Text")
182
  intro_title_input = gr.Textbox(label="Intro Title", value="Gradio")
183
  intro_subtitle_input = gr.Textbox(label="Intro Subtitle", value="The best UI framework")
@@ -197,19 +220,20 @@ with gr.Blocks(theme=gr.themes.Ocean(), title="CreditsPanel Demo") as demo:
197
  gr.Markdown("### Color Settings (Scroll Effect)")
198
  scroll_background_color = gr.ColorPicker(label="Background Color", value="#000000")
199
  scroll_title_color = gr.ColorPicker(label="Title Color", value="#FFFFFF")
 
200
  scroll_name_color = gr.ColorPicker(label="Name Color", value="#FFFFFF")
201
 
202
- panel = CreditsPanel(
 
203
  credits=credits_list,
204
  licenses=license_paths,
205
  effect="scroll",
206
- height=500,
207
  speed=DEFAULT_SPEEDS["scroll"],
208
  base_font_size=1.5,
209
  intro_title="Gradio",
210
  intro_subtitle="The best UI framework",
211
  sidebar_position="right",
212
- logo_path="./assets/logo.webp",
213
  show_logo=True,
214
  show_licenses=True,
215
  show_credits=True,
@@ -220,11 +244,14 @@ with gr.Blocks(theme=gr.themes.Ocean(), title="CreditsPanel Demo") as demo:
220
  scroll_background_color="#000000",
221
  scroll_title_color="#FFFFFF",
222
  scroll_name_color="#FFFFFF",
 
223
  layout_style="stacked",
224
  title_uppercase=False,
225
  name_uppercase=False,
226
  section_title_uppercase=True,
227
  swap_font_sizes_on_two_column=False,
 
 
228
  )
229
 
230
  inputs = [
@@ -242,15 +269,23 @@ with gr.Blocks(theme=gr.themes.Ocean(), title="CreditsPanel Demo") as demo:
242
  logo_width_input,
243
  logo_height_input,
244
  scroll_background_color,
245
- scroll_title_color,
 
246
  scroll_name_color,
247
  layout_style_radio,
248
  title_uppercase_checkbox,
249
  name_uppercase_checkbox,
250
  section_title_uppercase_checkbox,
251
- swap_sizes_checkbox
 
 
252
  ]
253
 
 
 
 
 
 
254
  layout_style_radio.change(
255
  fn=toggle_swap_checkbox_visibility,
256
  inputs=layout_style_radio,
@@ -294,7 +329,6 @@ The impact on the users predict function varies depending on whether the compone
294
 
295
  The code snippet below is accurate in cases where the component is used as both an input and an output.
296
 
297
- - **As input:** Is passed, dict[str, Any] | None: The input payload, returned unchanged.
298
 
299
 
300
  ```python
@@ -302,7 +336,9 @@ def predict(
302
  value: typing.Optional[typing.Dict[str, typing.Any]][
303
  typing.Dict[str, typing.Any][str, Any], None
304
  ]
305
- ) -> Any:
 
 
306
  return value
307
  ```
308
  """, elem_classes=["md-custom", "CreditsPanel-user-fn"], header_links=True)
 
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.\nThis component is configured via a single dictionary `value` that holds all settings.\nIt supports displaying a logo, licenses, sections, and various text styling options.\n\n EVENTS (list): Supported events for the component, currently only `change`.', 'members': {'__init__': {'value': {'type': 'typing.Optional[typing.Dict[str, typing.Any]][\n typing.Dict[str, typing.Any][str, Any], None\n]', 'default': 'None', 'description': None}, 'height': {'type': 'int | str | None', 'default': 'None', 'description': None}, 'width': {'type': 'int | str | None', '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}, '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}, 'show_credits': {'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}, 'scroll_section_title_color': {'type': 'str | None', 'default': 'None', 'description': None}, 'layout_style': {'type': '"stacked" | "two-column"', 'default': '"stacked"', 'description': None}, 'title_uppercase': {'type': 'bool', 'default': 'False', 'description': None}, 'name_uppercase': {'type': 'bool', 'default': 'False', 'description': None}, 'section_title_uppercase': {'type': 'bool', 'default': 'True', 'description': None}, 'swap_font_sizes_on_two_column': {'type': 'bool', 'default': 'False', 'description': None}, 'scroll_logo_path': {'type': 'str | pathlib.Path | None', 'default': 'None', 'description': None}, 'scroll_logo_height': {'type': 'str', 'default': '"120px"', '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': 'typing.Optional[typing.Dict[str, typing.Any]][\n typing.Dict[str, typing.Any][str, Any], None\n]', 'description': None}}, 'preprocess': {'return': {'type': 'typing.Optional[typing.Dict[str, typing.Any]][\n typing.Dict[str, typing.Any][str, Any], None\n]', 'description': None}, '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
 
 
58
  with open("./assets/logo.webp", "w") as f:
59
  f.write("Placeholder WebP logo")
60
 
61
+ # --- Credits list with sections ---
62
  credits_list = [
63
  {"section_title": "Project Leadership"},
64
  {"title": "Project Manager", "name": "Emma Thompson"},
 
87
  "starwars": 70.0,
88
  "matrix": 40.0
89
  }
90
+ SCROLL_LOGO_PATH = "./assets/gradio_logo_white.png"
91
+ LOGO_PATH="./assets/logo.webp"
92
 
93
  def update_panel(
94
  effect: str,
 
106
  logo_height: str | None,
107
  scroll_background_color: str | None,
108
  scroll_title_color: str | None,
109
+ scroll_section_title_color: str | None,
110
  scroll_name_color: str | None,
111
  layout_style: str,
112
  title_uppercase: bool,
113
  name_uppercase: bool,
114
  section_title_uppercase: bool,
115
+ swap_font_sizes: bool,
116
+ show_scroll_logo: bool,
117
+ scroll_logo_height: str | None
118
  ) -> dict:
119
  \"\"\"Callback function that updates all properties of the CreditsPanel component.\"\"\"
120
+
121
+ scroll_logo_path = SCROLL_LOGO_PATH if show_scroll_logo else None
122
+
123
+ if not scroll_logo_height:
124
+ scroll_logo_height = "120px"
125
+
126
+ return {
127
+ "credits": credits_list,
128
+ "licenses": license_paths,
129
+ "effect": effect,
130
+ "speed": speed,
131
+ "base_font_size": base_font_size,
132
+ "intro_title": intro_title,
133
+ "intro_subtitle": intro_subtitle,
134
+ "sidebar_position": sidebar_position,
135
+ "logo_path": LOGO_PATH,
136
+ "show_logo": show_logo,
137
+ "show_licenses": show_licenses,
138
+ "show_credits": show_credits,
139
+ "logo_position": logo_position,
140
+ "logo_sizing": logo_sizing,
141
+ "logo_width": logo_width,
142
+ "logo_height": logo_height,
143
+ "scroll_background_color": scroll_background_color,
144
+ "scroll_title_color": scroll_title_color,
145
+ "scroll_name_color": scroll_name_color,
146
+ "scroll_section_title_color": scroll_section_title_color,
147
+ "layout_style": layout_style,
148
+ "title_uppercase": title_uppercase,
149
+ "name_uppercase": name_uppercase,
150
+ "section_title_uppercase": section_title_uppercase,
151
+ "swap_font_sizes_on_two_column": swap_font_sizes,
152
+ "scroll_logo_path": scroll_logo_path,
153
+ "scroll_logo_height": scroll_logo_height,
154
+ }
155
 
156
  def update_ui_on_effect_change(effect: str) -> tuple[float, float]:
157
  \"\"\"Updates sliders to sensible defaults when the animation effect is changed.\"\"\"
 
193
  name_uppercase_checkbox = gr.Checkbox(label="Name Uppercase", value=False)
194
  section_title_uppercase_checkbox = gr.Checkbox(label="Section Uppercase", value=True)
195
 
196
+ gr.Markdown("### Scrolling Logo")
197
+ show_scroll_logo_checkbox = gr.Checkbox(
198
+ label="Show Logo in Credits Roll",
199
+ value=True,
200
+ info="Toggles the logo above the intro text."
201
+ )
202
+ scroll_logo_height_input = gr.Textbox(label="Scrolling Logo Height", value="100px")
203
+
204
  gr.Markdown("### Intro Text")
205
  intro_title_input = gr.Textbox(label="Intro Title", value="Gradio")
206
  intro_subtitle_input = gr.Textbox(label="Intro Subtitle", value="The best UI framework")
 
220
  gr.Markdown("### Color Settings (Scroll Effect)")
221
  scroll_background_color = gr.ColorPicker(label="Background Color", value="#000000")
222
  scroll_title_color = gr.ColorPicker(label="Title Color", value="#FFFFFF")
223
+ scroll_section_title_color = gr.ColorPicker(label="Section Title Color", value="#FFFFFF")
224
  scroll_name_color = gr.ColorPicker(label="Name Color", value="#FFFFFF")
225
 
226
+ panel = CreditsPanel(
227
+ height=500,
228
  credits=credits_list,
229
  licenses=license_paths,
230
  effect="scroll",
 
231
  speed=DEFAULT_SPEEDS["scroll"],
232
  base_font_size=1.5,
233
  intro_title="Gradio",
234
  intro_subtitle="The best UI framework",
235
  sidebar_position="right",
236
+ logo_path=LOGO_PATH,
237
  show_logo=True,
238
  show_licenses=True,
239
  show_credits=True,
 
244
  scroll_background_color="#000000",
245
  scroll_title_color="#FFFFFF",
246
  scroll_name_color="#FFFFFF",
247
+ scroll_section_title_color="#FFFFFF",
248
  layout_style="stacked",
249
  title_uppercase=False,
250
  name_uppercase=False,
251
  section_title_uppercase=True,
252
  swap_font_sizes_on_two_column=False,
253
+ scroll_logo_path=SCROLL_LOGO_PATH,
254
+ scroll_logo_height="100px",
255
  )
256
 
257
  inputs = [
 
269
  logo_width_input,
270
  logo_height_input,
271
  scroll_background_color,
272
+ scroll_title_color,
273
+ scroll_section_title_color,
274
  scroll_name_color,
275
  layout_style_radio,
276
  title_uppercase_checkbox,
277
  name_uppercase_checkbox,
278
  section_title_uppercase_checkbox,
279
+ swap_sizes_checkbox,
280
+ show_scroll_logo_checkbox,
281
+ scroll_logo_height_input
282
  ]
283
 
284
+ demo.load(
285
+ fn=update_panel,
286
+ inputs=inputs,
287
+ outputs=panel
288
+ )
289
  layout_style_radio.change(
290
  fn=toggle_swap_checkbox_visibility,
291
  inputs=layout_style_radio,
 
329
 
330
  The code snippet below is accurate in cases where the component is used as both an input and an output.
331
 
 
332
 
333
 
334
  ```python
 
336
  value: typing.Optional[typing.Dict[str, typing.Any]][
337
  typing.Dict[str, typing.Any][str, Any], None
338
  ]
339
+ ) -> typing.Optional[typing.Dict[str, typing.Any]][
340
+ typing.Dict[str, typing.Any][str, Any], None
341
+ ]:
342
  return value
343
  ```
344
  """, elem_classes=["md-custom", "CreditsPanel-user-fn"], header_links=True)
src/frontend/Index.svelte CHANGED
@@ -29,6 +29,7 @@
29
  * @property {number | string | null} logo_height - Logo height.
30
  * @property {string | null} scroll_background_color - Scroll effect background color.
31
  * @property {string | null} scroll_title_color - Credit title color.
 
32
  * @property {string | null} scroll_name_color - Credit name color.
33
  * @property {number} title_font_size - Title font size (unused in StarWarsEffect).
34
  * @property {number} name_font_size - Name font size (unused in StarWarsEffect).
@@ -37,6 +38,8 @@
37
  * @property {boolean} name_uppercase - Transform name to uppercase.
38
  * @property {boolean} section_title_uppercase - Transform section title to uppercase.
39
  * @property {boolean} swap_font_sizes_on_two_column - Swap title/name font sizes in two-column layout.
 
 
40
  */
41
  export let value: Value | null = null;
42
  export let elem_id = "";
@@ -77,12 +80,15 @@
77
  logo_height: null,
78
  scroll_background_color: null,
79
  scroll_title_color: null,
 
80
  scroll_name_color: null,
81
  layout_style: "stacked",
82
  title_uppercase: false,
83
  name_uppercase: false,
84
  section_title_uppercase: true,
85
  swap_font_sizes_on_two_column: false,
 
 
86
  };
87
 
88
  // Tracks selected license for display
@@ -211,9 +217,12 @@
211
  name_color={effectiveValue.scroll_name_color}
212
  layout_style={effectiveValue.layout_style}
213
  title_uppercase={effectiveValue.title_uppercase}
 
214
  name_uppercase={effectiveValue.name_uppercase}
215
  section_title_uppercase={effectiveValue.section_title_uppercase}
216
  swap_font_sizes_on_two_column={effectiveValue.swap_font_sizes_on_two_column}
 
 
217
  />
218
  {:else if effectiveValue.effect === "starwars"}
219
  <StarWarsEffect
@@ -227,6 +236,8 @@
227
  name_uppercase={effectiveValue.name_uppercase}
228
  section_title_uppercase={effectiveValue.section_title_uppercase}
229
  swap_font_sizes_on_two_column={effectiveValue.swap_font_sizes_on_two_column}
 
 
230
  />
231
  {:else if effectiveValue.effect === "matrix"}
232
  <MatrixEffect
@@ -240,6 +251,8 @@
240
  name_uppercase={effectiveValue.name_uppercase}
241
  section_title_uppercase={effectiveValue.section_title_uppercase}
242
  swap_font_sizes_on_two_column={effectiveValue.swap_font_sizes_on_two_column}
 
 
243
  />
244
  {/if}
245
  </div>
 
29
  * @property {number | string | null} logo_height - Logo height.
30
  * @property {string | null} scroll_background_color - Scroll effect background color.
31
  * @property {string | null} scroll_title_color - Credit title color.
32
+ * @property {string | null} scroll_section_title_color - Section title color.
33
  * @property {string | null} scroll_name_color - Credit name color.
34
  * @property {number} title_font_size - Title font size (unused in StarWarsEffect).
35
  * @property {number} name_font_size - Name font size (unused in StarWarsEffect).
 
38
  * @property {boolean} name_uppercase - Transform name to uppercase.
39
  * @property {boolean} section_title_uppercase - Transform section title to uppercase.
40
  * @property {boolean} swap_font_sizes_on_two_column - Swap title/name font sizes in two-column layout.
41
+ * @property {{path: string | null, url: string | null, ...} | null} scroll_logo_path - Logo to display inside the scroll.
42
+ * @property {string} scroll_logo_height - Height of the scrolling logo.
43
  */
44
  export let value: Value | null = null;
45
  export let elem_id = "";
 
80
  logo_height: null,
81
  scroll_background_color: null,
82
  scroll_title_color: null,
83
+ scroll_section_title_color: null,
84
  scroll_name_color: null,
85
  layout_style: "stacked",
86
  title_uppercase: false,
87
  name_uppercase: false,
88
  section_title_uppercase: true,
89
  swap_font_sizes_on_two_column: false,
90
+ scroll_logo_path: null,
91
+ scroll_logo_height: "120px",
92
  };
93
 
94
  // Tracks selected license for display
 
217
  name_color={effectiveValue.scroll_name_color}
218
  layout_style={effectiveValue.layout_style}
219
  title_uppercase={effectiveValue.title_uppercase}
220
+ scroll_section_title_color={effectiveValue.scroll_section_title_color}
221
  name_uppercase={effectiveValue.name_uppercase}
222
  section_title_uppercase={effectiveValue.section_title_uppercase}
223
  swap_font_sizes_on_two_column={effectiveValue.swap_font_sizes_on_two_column}
224
+ scroll_logo_path={effectiveValue.scroll_logo_path}
225
+ scroll_logo_height={effectiveValue.scroll_logo_height}
226
  />
227
  {:else if effectiveValue.effect === "starwars"}
228
  <StarWarsEffect
 
236
  name_uppercase={effectiveValue.name_uppercase}
237
  section_title_uppercase={effectiveValue.section_title_uppercase}
238
  swap_font_sizes_on_two_column={effectiveValue.swap_font_sizes_on_two_column}
239
+ scroll_logo_path={effectiveValue.scroll_logo_path}
240
+ scroll_logo_height={effectiveValue.scroll_logo_height}
241
  />
242
  {:else if effectiveValue.effect === "matrix"}
243
  <MatrixEffect
 
251
  name_uppercase={effectiveValue.name_uppercase}
252
  section_title_uppercase={effectiveValue.section_title_uppercase}
253
  swap_font_sizes_on_two_column={effectiveValue.swap_font_sizes_on_two_column}
254
+ scroll_logo_path={effectiveValue.scroll_logo_path}
255
+ scroll_logo_height={effectiveValue.scroll_logo_height}
256
  />
257
  {/if}
258
  </div>
src/frontend/shared/MatrixEffect.svelte CHANGED
@@ -14,6 +14,8 @@
14
  * @property {boolean} name_uppercase - Transform name to uppercase.
15
  * @property {boolean} section_title_uppercase - Transform section title to uppercase.
16
  * @property {boolean} swap_font_sizes_on_two_column - Swap title/name font sizes.
 
 
17
  */
18
  export let credits: Props["credits"];
19
  export let speed: number = 20;
@@ -25,6 +27,8 @@
25
  export let name_uppercase: boolean = false;
26
  export let section_title_uppercase: boolean = true;
27
  export let swap_font_sizes_on_two_column: boolean = false;
 
 
28
 
29
  // Combines intro and credits for display
30
  $: display_items = (() => {
@@ -142,6 +146,11 @@
142
  bind:this={contentElement}
143
  style="--animation-duration: {speed}s;"
144
  >
 
 
 
 
 
145
  {#each display_items as item}
146
  <!-- Render Section Title -->
147
  {#if item.section_title}
@@ -305,4 +314,16 @@
305
  text-align: left;
306
  padding-left: 1em;
307
  }
 
 
 
 
 
 
 
 
 
 
 
 
308
  </style>
 
14
  * @property {boolean} name_uppercase - Transform name to uppercase.
15
  * @property {boolean} section_title_uppercase - Transform section title to uppercase.
16
  * @property {boolean} swap_font_sizes_on_two_column - Swap title/name font sizes.
17
+ * @property {{path: string | null, url: string | null, ...} | null} scroll_logo_path - Logo to display inside the scroll.
18
+ * @property {string} scroll_logo_height - Height of the scrolling logo.
19
  */
20
  export let credits: Props["credits"];
21
  export let speed: number = 20;
 
27
  export let name_uppercase: boolean = false;
28
  export let section_title_uppercase: boolean = true;
29
  export let swap_font_sizes_on_two_column: boolean = false;
30
+ export let scroll_logo_path: { url: string | null } | null = null;
31
+ export let scroll_logo_height: string = "120px";
32
 
33
  // Combines intro and credits for display
34
  $: display_items = (() => {
 
146
  bind:this={contentElement}
147
  style="--animation-duration: {speed}s;"
148
  >
149
+ {#if scroll_logo_path?.url}
150
+ <div class="scroll-logo-container">
151
+ <img src={scroll_logo_path.url} alt="Scrolling Logo" style:height={scroll_logo_height} />
152
+ </div>
153
+ {/if}
154
  {#each display_items as item}
155
  <!-- Render Section Title -->
156
  {#if item.section_title}
 
314
  text-align: left;
315
  padding-left: 1em;
316
  }
317
+ .scroll-logo-container {
318
+ text-align: center;
319
+ margin-bottom: 2rem;
320
+ }
321
+ .scroll-logo-container img {
322
+ display: block;
323
+ margin-left: auto;
324
+ margin-right: auto;
325
+ max-width: 80%;
326
+ object-fit: contain;
327
+ filter: grayscale(1) brightness(0.5) sepia(100%) hue-rotate(50deg) saturate(500%);
328
+ }
329
  </style>
src/frontend/shared/ScrollEffect.svelte CHANGED
@@ -7,6 +7,7 @@
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.
@@ -15,12 +16,16 @@
15
  * @property {boolean} name_uppercase - Transform name to uppercase.
16
  * @property {boolean} section_title_uppercase - Transform section title to uppercase.
17
  * @property {boolean} swap_font_sizes_on_two_column - Swap title/name font sizes.
 
 
 
18
  */
19
  export let credits: Props['credits'];
20
  export let speed: number;
21
  export let base_font_size: number = 1.5;
22
  export let background_color: string | null = null;
23
  export let title_color: string | null = null;
 
24
  export let name_color: string | null = null;
25
  export let intro_title: string | null = null;
26
  export let intro_subtitle: string | null = null;
@@ -29,6 +34,8 @@
29
  export let name_uppercase: boolean = false;
30
  export let section_title_uppercase: boolean = true;
31
  export let swap_font_sizes_on_two_column: boolean = false;
 
 
32
 
33
  // Flag to trigger animation reset
34
  let reset = false;
@@ -48,8 +55,8 @@
48
 
49
  // Reactive styles for title and name
50
  $: title_style = (is_intro: boolean) => `color: ${title_color || 'white'}; font-size: ${is_intro ? base_font_size * 1.5 : base_font_size}rem;`;
51
- $: name_style = (is_intro: boolean) => `color: ${name_color || 'white'}; font-size: ${is_intro ? base_font_size * 0.9 : base_font_size * 0.8}rem;`;
52
- $: section_title_style = `color: ${title_color || 'white'}; font-size: ${base_font_size * 1.2}rem;`;
53
 
54
  // Reset animation on prop changes
55
  function resetAnimation() {
@@ -64,6 +71,11 @@
64
  <div class="wrapper" style:--animation-duration="{speed}s" style:background={background_color || 'black'}>
65
  {#if !reset}
66
  <div class="credits-container">
 
 
 
 
 
67
  {#each display_items as item}
68
  <!-- Render Section Title -->
69
  {#if item.section_title}
@@ -108,6 +120,17 @@
108
  position: relative;
109
  font-family: sans-serif;
110
  }
 
 
 
 
 
 
 
 
 
 
 
111
 
112
  /* Animated container holding all credit items */
113
  .credits-container {
 
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} scroll_section_title_color - Section title color.
11
  * @property {string | null} name_color - Name text color (default: white).
12
  * @property {string | null} intro_title - Optional intro title.
13
  * @property {string | null} intro_subtitle - Optional intro subtitle.
 
16
  * @property {boolean} name_uppercase - Transform name to uppercase.
17
  * @property {boolean} section_title_uppercase - Transform section title to uppercase.
18
  * @property {boolean} swap_font_sizes_on_two_column - Swap title/name font sizes.
19
+ * @property {{path: string | null, url: string | null, ...} | null} scroll_logo_path - Logo to display inside the scroll.
20
+ * @property {string} scroll_logo_height - Height of the scrolling logo.
21
+ *
22
  */
23
  export let credits: Props['credits'];
24
  export let speed: number;
25
  export let base_font_size: number = 1.5;
26
  export let background_color: string | null = null;
27
  export let title_color: string | null = null;
28
+ export let scroll_section_title_color: string | null = null;
29
  export let name_color: string | null = null;
30
  export let intro_title: string | null = null;
31
  export let intro_subtitle: string | null = null;
 
34
  export let name_uppercase: boolean = false;
35
  export let section_title_uppercase: boolean = true;
36
  export let swap_font_sizes_on_two_column: boolean = false;
37
+ export let scroll_logo_path: { url: string | null } | null = null;
38
+ export let scroll_logo_height: string = "120px";
39
 
40
  // Flag to trigger animation reset
41
  let reset = false;
 
55
 
56
  // Reactive styles for title and name
57
  $: title_style = (is_intro: boolean) => `color: ${title_color || 'white'}; font-size: ${is_intro ? base_font_size * 1.5 : base_font_size}rem;`;
58
+ $: name_style = (is_intro: boolean) => `color: ${name_color || 'white'}; font-size: ${is_intro ? base_font_size * 0.9 : base_font_size * 0.8}rem;`;
59
+ $: section_title_style = `color: ${scroll_section_title_color || title_color || 'white'}; font-size: ${base_font_size * 1.2}rem;`;
60
 
61
  // Reset animation on prop changes
62
  function resetAnimation() {
 
71
  <div class="wrapper" style:--animation-duration="{speed}s" style:background={background_color || 'black'}>
72
  {#if !reset}
73
  <div class="credits-container">
74
+ {#if scroll_logo_path?.url}
75
+ <div class="scroll-logo-container" style:height={scroll_logo_height}>
76
+ <img src={scroll_logo_path.url} alt="Scrolling Logo" style:height={scroll_logo_height} />
77
+ </div>
78
+ {/if}
79
  {#each display_items as item}
80
  <!-- Render Section Title -->
81
  {#if item.section_title}
 
120
  position: relative;
121
  font-family: sans-serif;
122
  }
123
+ .scroll-logo-container {
124
+ text-align: center;
125
+ margin-bottom: 2rem; /* Space between logo and intro text */
126
+ }
127
+ .scroll-logo-container img {
128
+ display: block;
129
+ margin-left: auto;
130
+ margin-right: auto;
131
+ max-width: 80%;
132
+ object-fit: contain;
133
+ }
134
 
135
  /* Animated container holding all credit items */
136
  .credits-container {
src/frontend/shared/StarWarsEffect.svelte CHANGED
@@ -19,6 +19,8 @@
19
  * @property {boolean} name_uppercase - Transform name to uppercase.
20
  * @property {boolean} section_title_uppercase - Transform section title to uppercase.
21
  * @property {boolean} swap_font_sizes_on_two_column - Swap title/name font sizes.
 
 
22
  */
23
  export let credits: Props['credits'];
24
  export let speed: number = 40;
@@ -33,6 +35,8 @@
33
  export let name_uppercase: boolean = false;
34
  export let section_title_uppercase: boolean = true;
35
  export let swap_font_sizes_on_two_column: boolean = false;
 
 
36
 
37
  // Reactive styles
38
  $: title_style = (is_intro: boolean) => `color: ${title_color || '#feda4a'}; font-size: ${is_intro ? base_font_size * 1.5 : base_font_size}rem;`;
@@ -84,6 +88,11 @@
84
  <div class="stars large" style="box-shadow: {large_stars};"></div>
85
 
86
  <div class="crawl" bind:this={crawlElement} style="--animation-duration: {speed}s;">
 
 
 
 
 
87
  {#each display_items as item}
88
  <!-- Render Section Title -->
89
  {#if item.section_title}
@@ -112,6 +121,7 @@
112
  </div>
113
 
114
  <style>
 
115
  .viewport {
116
  width: 100%;
117
  height: 100%;
@@ -146,6 +156,18 @@
146
  }
147
 
148
  /* STYLES */
 
 
 
 
 
 
 
 
 
 
 
 
149
  .uppercase { text-transform: uppercase; }
150
 
151
  .section-title {
 
19
  * @property {boolean} name_uppercase - Transform name to uppercase.
20
  * @property {boolean} section_title_uppercase - Transform section title to uppercase.
21
  * @property {boolean} swap_font_sizes_on_two_column - Swap title/name font sizes.
22
+ * @property {{path: string | null, url: string | null, ...} | null} scroll_logo_path - Logo to display inside the scroll.
23
+ * @property {string} scroll_logo_height - Height of the scrolling logo.
24
  */
25
  export let credits: Props['credits'];
26
  export let speed: number = 40;
 
35
  export let name_uppercase: boolean = false;
36
  export let section_title_uppercase: boolean = true;
37
  export let swap_font_sizes_on_two_column: boolean = false;
38
+ export let scroll_logo_path: { url: string | null } | null = null;
39
+ export let scroll_logo_height: string = "120px";
40
 
41
  // Reactive styles
42
  $: title_style = (is_intro: boolean) => `color: ${title_color || '#feda4a'}; font-size: ${is_intro ? base_font_size * 1.5 : base_font_size}rem;`;
 
88
  <div class="stars large" style="box-shadow: {large_stars};"></div>
89
 
90
  <div class="crawl" bind:this={crawlElement} style="--animation-duration: {speed}s;">
91
+ {#if scroll_logo_path?.url}
92
+ <div class="scroll-logo-container">
93
+ <img src={scroll_logo_path.url} alt="Scrolling Logo" style:height={scroll_logo_height} />
94
+ </div>
95
+ {/if}
96
  {#each display_items as item}
97
  <!-- Render Section Title -->
98
  {#if item.section_title}
 
121
  </div>
122
 
123
  <style>
124
+
125
  .viewport {
126
  width: 100%;
127
  height: 100%;
 
156
  }
157
 
158
  /* STYLES */
159
+ .scroll-logo-container {
160
+ text-align: center;
161
+ margin-bottom: 2rem;
162
+ }
163
+ .scroll-logo-container img {
164
+ display: block;
165
+ margin-left: auto;
166
+ margin-right: auto;
167
+ max-width: 70%;
168
+ object-fit: contain;
169
+ filter: grayscale(1) sepia(100%) hue-rotate(15deg) saturate(8) brightness(1.2);
170
+ }
171
  .uppercase { text-transform: uppercase; }
172
 
173
  .section-title {
src/pyproject.toml CHANGED
@@ -8,7 +8,7 @@ build-backend = "hatchling.build"
8
 
9
  [project]
10
  name = "gradio_creditspanel"
11
- version = "0.0.3"
12
  description = "Credits Panel for Gradio UI"
13
  readme = "README.md"
14
  license = "apache-2.0"
 
8
 
9
  [project]
10
  name = "gradio_creditspanel"
11
+ version = "0.0.4"
12
  description = "Credits Panel for Gradio UI"
13
  readme = "README.md"
14
  license = "apache-2.0"