elismasilva commited on
Commit
f70a3b6
·
verified ·
1 Parent(s): 436ee19

Upload folder using huggingface_hub

Browse files
README.md CHANGED
@@ -10,7 +10,7 @@ app_file: space.py
10
  ---
11
 
12
  # `gradio_creditspanel`
13
- <img alt="Static Badge" src="https://img.shields.io/badge/version%20-%200.0.2%20-%20blue"> <a href="https://huggingface.co/spaces/elismasilva/gradio_creditspanel"><img src="https://img.shields.io/badge/%F0%9F%A4%97%20Hugging%20Face-Demo-blue"></a><p><span>💻 <a href='https://github.com/DEVAIEXP/gradio_component_creditspanel'>Component GitHub Code</a></span></p>
14
 
15
  Credits Panel for Gradio UI
16
 
@@ -23,78 +23,56 @@ pip install gradio_creditspanel
23
  ## Usage
24
 
25
  ```python
26
- """
27
- app.py
28
-
29
- This script serves as an interactive demonstration for the custom Gradio component `CreditsPanel`.
30
- It showcases all available features of the component, allowing users to dynamically adjust
31
- properties like animation effects, speed, layout, and styling. The app also demonstrates
32
- how to handle file dependencies (logo, licenses) in a portable way.
33
- """
34
 
35
  import gradio as gr
36
  from gradio_creditspanel import CreditsPanel
37
  import os
38
 
39
- # --- 1. SETUP & DATA PREPARATION ---
40
- # This section prepares all necessary assets and data for the demo.
41
- # It ensures the demo runs out-of-the-box without manual setup.
42
-
43
  def setup_demo_files():
44
- """
45
- Creates necessary directories and dummy files (logo, licenses) for the demo.
46
- This makes the application self-contained and easy to run.
47
- """
48
- # Create dummy license files
49
  os.makedirs("LICENSES", exist_ok=True)
50
  if not os.path.exists("LICENSES/Apache.txt"):
51
  with open("LICENSES/Apache.txt", "w") as f:
52
- f.write("Apache License\nVersion 2.0, January 2004\nhttp://www.apache.org/licenses/...")
53
  if not os.path.exists("LICENSES/MIT.txt"):
54
  with open("LICENSES/MIT.txt", "w") as f:
55
- f.write("MIT License\nCopyright (c) 2025 Author\nPermission is hereby granted...")
56
-
57
- # Create a placeholder logo if it doesn't exist
58
  os.makedirs("assets", exist_ok=True)
59
  if not os.path.exists("./assets/logo.webp"):
60
  with open("./assets/logo.webp", "w") as f:
61
  f.write("Placeholder WebP logo")
62
 
63
- # Initial data for the credits roll
64
  credits_list = [
 
65
  {"title": "Project Manager", "name": "Emma Thompson"},
 
 
 
66
  {"title": "Lead Developer", "name": "John Doe"},
67
  {"title": "Senior Backend Engineer", "name": "Michael Chen"},
68
  {"title": "Frontend Developer", "name": "Sarah Johnson"},
69
  {"title": "UI/UX Designer", "name": "Jane Smith"},
70
  {"title": "Database Architect", "name": "Alex Ray"},
 
 
71
  {"title": "DevOps Engineer", "name": "Liam Patel"},
72
  {"title": "Quality Assurance Lead", "name": "Sam Wilson"},
73
  {"title": "Test Automation Engineer", "name": "Olivia Brown"},
74
- {"title": "Security Analyst", "name": "David Kim"},
75
- {"title": "Data Scientist", "name": "Sophie Martinez"},
76
- {"title": "Machine Learning Engineer", "name": "Ethan Lee"},
77
- {"title": "API Developer", "name": "Isabella Garcia"},
78
- {"title": "Technical Writer", "name": "Noah Davis"},
79
- {"title": "Scrum Master", "name": "Ava Rodriguez"},
80
  ]
81
 
82
- # Paths to license files
83
  license_paths = {
84
  "Gradio Framework": "./LICENSES/Apache.txt",
85
  "This Component": "./LICENSES/MIT.txt"
86
  }
87
 
88
- # Default animation speeds for each effect to provide a good user experience
89
  DEFAULT_SPEEDS = {
90
  "scroll": 40.0,
91
- "starwars": 80.0,
92
  "matrix": 40.0
93
  }
94
 
95
- # --- 2. GRADIO EVENT HANDLER FUNCTIONS ---
96
- # These functions define the application's interactive logic.
97
-
98
  def update_panel(
99
  effect: str,
100
  speed: float,
@@ -105,53 +83,57 @@ def update_panel(
105
  show_logo: bool,
106
  show_licenses: bool,
107
  show_credits: bool,
108
- logo_position: str,
109
  logo_sizing: str,
110
  logo_width: str | None,
111
  logo_height: str | None,
112
  scroll_background_color: str | None,
113
- scroll_title_color: str | None,
114
- scroll_name_color: str | None
 
 
 
 
 
115
  ) -> dict:
116
- """
117
- Callback function that updates all properties of the CreditsPanel component.
118
- It takes the current state of all UI controls and returns a gr.update() dictionary.
119
- """
120
  return gr.update(
121
  visible=True,
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
  show_logo=show_logo,
129
- show_licenses=show_licenses,
130
  show_credits=show_credits,
131
- logo_position=logo_position,
132
  logo_sizing=logo_sizing,
133
- logo_width=logo_width,
134
  logo_height=logo_height,
135
  scroll_background_color=scroll_background_color,
136
  scroll_title_color=scroll_title_color,
137
- scroll_name_color=scroll_name_color,
138
- value=credits_list # The list of credits to display
 
 
 
 
 
139
  )
140
 
141
  def update_ui_on_effect_change(effect: str) -> tuple[float, float]:
142
- """
143
- Updates the speed and font size sliders to sensible defaults when the
144
- animation effect is changed.
145
- """
146
  font_size = 1.5
147
  if effect == "starwars":
148
- font_size = 6.0 # Star Wars effect looks better with a larger font
149
-
150
  speed = DEFAULT_SPEEDS.get(effect, 40.0)
151
  return speed, font_size
152
 
153
- # --- 3. GRADIO UI DEFINITION ---
154
- # This section constructs the user interface using gr.Blocks.
 
155
 
156
  with gr.Blocks(theme=gr.themes.Ocean(), title="CreditsPanel Demo") as demo:
157
  gr.Markdown(
@@ -163,42 +145,37 @@ with gr.Blocks(theme=gr.themes.Ocean(), title="CreditsPanel Demo") as demo:
163
 
164
  with gr.Sidebar(position="right"):
165
  gr.Markdown("### Effects Settings")
166
- effect_radio = gr.Radio(
167
- ["scroll", "starwars", "matrix"], label="Animation Effect", value="scroll",
168
- info="Select the visual style for the credits."
169
- )
170
- speed_slider = gr.Slider(
171
- minimum=5.0, maximum=100.0, step=1.0, value=DEFAULT_SPEEDS["scroll"],
172
- label="Animation Speed (seconds)", info="Duration of one animation cycle."
 
173
  )
174
- font_size_slider = gr.Slider(
175
- minimum=1.0, maximum=10.0, step=0.1, value=1.5,
176
- label="Base Font Size (rem)", info="Controls the base font size."
 
177
  )
178
-
 
 
 
179
  gr.Markdown("### Intro Text")
180
- intro_title_input = gr.Textbox(
181
- label="Intro Title", value="Gradio", info="Main title for the intro sequence."
182
- )
183
- intro_subtitle_input = gr.Textbox(
184
- label="Intro Subtitle", value="The best UI framework", info="Subtitle for the intro sequence."
185
- )
186
 
187
  gr.Markdown("### Layout & Visibility")
188
- sidebar_position_radio = gr.Radio(
189
- ["right", "bottom"], label="Sidebar Position", value="right",
190
- info="Place the licenses sidebar on the right or bottom."
191
- )
192
  show_logo_checkbox = gr.Checkbox(label="Show Logo", value=True)
193
  show_licenses_checkbox = gr.Checkbox(label="Show Licenses", value=True)
194
  show_credits_checkbox = gr.Checkbox(label="Show Credits", value=True)
 
195
  gr.Markdown("### Logo Customization")
196
- logo_position_radio = gr.Radio(
197
- ["left", "center", "right"], label="Logo Position", value="center"
198
- )
199
- logo_sizing_radio = gr.Radio(
200
- ["stretch", "crop", "resize"], label="Logo Sizing", value="resize"
201
- )
202
  logo_width_input = gr.Textbox(label="Logo Width", value="200px")
203
  logo_height_input = gr.Textbox(label="Logo Height", value="100px")
204
 
@@ -207,7 +184,6 @@ with gr.Blocks(theme=gr.themes.Ocean(), title="CreditsPanel Demo") as demo:
207
  scroll_title_color = gr.ColorPicker(label="Title Color", value="#FFFFFF")
208
  scroll_name_color = gr.ColorPicker(label="Name Color", value="#FFFFFF")
209
 
210
- # Instantiate the custom CreditsPanel component with default values
211
  panel = CreditsPanel(
212
  credits=credits_list,
213
  licenses=license_paths,
@@ -228,10 +204,14 @@ with gr.Blocks(theme=gr.themes.Ocean(), title="CreditsPanel Demo") as demo:
228
  logo_height="100px",
229
  scroll_background_color="#000000",
230
  scroll_title_color="#FFFFFF",
231
- scroll_name_color="#FFFFFF",
 
 
 
 
 
232
  )
233
 
234
- # List of all input components that should trigger a panel update
235
  inputs = [
236
  effect_radio,
237
  speed_slider,
@@ -241,35 +221,31 @@ with gr.Blocks(theme=gr.themes.Ocean(), title="CreditsPanel Demo") as demo:
241
  sidebar_position_radio,
242
  show_logo_checkbox,
243
  show_licenses_checkbox,
244
- show_credits_checkbox,
245
  logo_position_radio,
246
  logo_sizing_radio,
247
  logo_width_input,
248
  logo_height_input,
249
  scroll_background_color,
250
  scroll_title_color,
251
- scroll_name_color
 
 
 
 
 
252
  ]
253
 
254
- # --- 4. EVENT BINDING ---
255
- # Connect the UI controls to the handler functions.
256
-
257
- # Special event: changing the effect also updates speed and font size sliders
258
- effect_radio.change(
259
- fn=update_ui_on_effect_change,
260
- inputs=effect_radio,
261
- outputs=[speed_slider, font_size_slider]
262
  )
 
263
 
264
- # General event: any change in an input control updates the main panel
265
  for input_component in inputs:
266
- input_component.change(
267
- fn=update_panel,
268
- inputs=inputs,
269
- outputs=panel
270
- )
271
 
272
- # --- 5. APP LAUNCH ---
273
  if __name__ == "__main__":
274
  setup_demo_files()
275
  demo.launch()
@@ -590,6 +566,71 @@ str | None
590
  <td align="left">None</td>
591
  </tr>
592
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
593
  <tr>
594
  <td align="left"><code>label</code></td>
595
  <td align="left" style="width: 25%;">
 
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
 
 
23
  ## Usage
24
 
25
  ```python
26
+ # app.py
 
 
 
 
 
 
 
27
 
28
  import gradio as gr
29
  from gradio_creditspanel import CreditsPanel
30
  import os
31
 
 
 
 
 
32
  def setup_demo_files():
33
+ """Creates necessary directories and dummy files for the demo."""
 
 
 
 
34
  os.makedirs("LICENSES", exist_ok=True)
35
  if not os.path.exists("LICENSES/Apache.txt"):
36
  with open("LICENSES/Apache.txt", "w") as f:
37
+ f.write("Apache License\nVersion 2.0, January 2004...")
38
  if not os.path.exists("LICENSES/MIT.txt"):
39
  with open("LICENSES/MIT.txt", "w") as f:
40
+ f.write("MIT License\nCopyright (c) 2025 Author...")
 
 
41
  os.makedirs("assets", exist_ok=True)
42
  if not os.path.exists("./assets/logo.webp"):
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"},
50
+ {"title": "Scrum Master", "name": "Ava Rodriguez"},
51
+
52
+ {"section_title": "Development Team"},
53
  {"title": "Lead Developer", "name": "John Doe"},
54
  {"title": "Senior Backend Engineer", "name": "Michael Chen"},
55
  {"title": "Frontend Developer", "name": "Sarah Johnson"},
56
  {"title": "UI/UX Designer", "name": "Jane Smith"},
57
  {"title": "Database Architect", "name": "Alex Ray"},
58
+
59
+ {"section_title": "Quality & Operations"},
60
  {"title": "DevOps Engineer", "name": "Liam Patel"},
61
  {"title": "Quality Assurance Lead", "name": "Sam Wilson"},
62
  {"title": "Test Automation Engineer", "name": "Olivia Brown"},
 
 
 
 
 
 
63
  ]
64
 
 
65
  license_paths = {
66
  "Gradio Framework": "./LICENSES/Apache.txt",
67
  "This Component": "./LICENSES/MIT.txt"
68
  }
69
 
 
70
  DEFAULT_SPEEDS = {
71
  "scroll": 40.0,
72
+ "starwars": 70.0,
73
  "matrix": 40.0
74
  }
75
 
 
 
 
76
  def update_panel(
77
  effect: str,
78
  speed: float,
 
83
  show_logo: bool,
84
  show_licenses: bool,
85
  show_credits: bool,
86
+ logo_position: str,
87
  logo_sizing: str,
88
  logo_width: str | None,
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."""
 
 
 
128
  font_size = 1.5
129
  if effect == "starwars":
130
+ font_size = 3.8
 
131
  speed = DEFAULT_SPEEDS.get(effect, 40.0)
132
  return speed, font_size
133
 
134
+ def toggle_swap_checkbox_visibility(layout: str) -> dict:
135
+ """Show the swap checkbox only for the two-column layout."""
136
+ return gr.update(visible=(layout == 'two-column'))
137
 
138
  with gr.Blocks(theme=gr.themes.Ocean(), title="CreditsPanel Demo") as demo:
139
  gr.Markdown(
 
145
 
146
  with gr.Sidebar(position="right"):
147
  gr.Markdown("### Effects Settings")
148
+ effect_radio = gr.Radio(["scroll", "starwars", "matrix"], label="Animation Effect", value="scroll")
149
+ speed_slider = gr.Slider(minimum=5.0, maximum=100.0, step=1.0, value=DEFAULT_SPEEDS["scroll"], label="Animation Speed")
150
+ font_size_slider = gr.Slider(minimum=1.0, maximum=10.0, step=0.1, value=1.5, label="Base Font Size")
151
+
152
+ gr.Markdown("### Credits Layout Settings")
153
+ layout_style_radio = gr.Radio(
154
+ ["stacked", "two-column"], label="Layout Style", value="stacked",
155
+ info="How to display titles and names."
156
  )
157
+ swap_sizes_checkbox = gr.Checkbox(
158
+ label="Swap Title/Name Font Sizes", value=False,
159
+ info="Emphasize name over title in two-column layout.",
160
+ visible=False
161
  )
162
+ title_uppercase_checkbox = gr.Checkbox(label="Title Uppercase", value=False)
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")
 
 
 
 
169
 
170
  gr.Markdown("### Layout & Visibility")
171
+ sidebar_position_radio = gr.Radio(["right", "bottom"], label="Sidebar Position", value="right")
 
 
 
172
  show_logo_checkbox = gr.Checkbox(label="Show Logo", value=True)
173
  show_licenses_checkbox = gr.Checkbox(label="Show Licenses", value=True)
174
  show_credits_checkbox = gr.Checkbox(label="Show Credits", value=True)
175
+
176
  gr.Markdown("### Logo Customization")
177
+ logo_position_radio = gr.Radio(["left", "center", "right"], label="Logo Position", value="center")
178
+ logo_sizing_radio = gr.Radio(["stretch", "crop", "resize"], label="Logo Sizing", value="resize")
 
 
 
 
179
  logo_width_input = gr.Textbox(label="Logo Width", value="200px")
180
  logo_height_input = gr.Textbox(label="Logo Height", value="100px")
181
 
 
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,
 
204
  logo_height="100px",
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 = [
216
  effect_radio,
217
  speed_slider,
 
221
  sidebar_position_radio,
222
  show_logo_checkbox,
223
  show_licenses_checkbox,
224
+ show_credits_checkbox,
225
  logo_position_radio,
226
  logo_sizing_radio,
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,
242
+ outputs=swap_sizes_checkbox
 
 
 
 
243
  )
244
+ effect_radio.change(fn=update_ui_on_effect_change, inputs=effect_radio, outputs=[speed_slider, font_size_slider])
245
 
 
246
  for input_component in inputs:
247
+ input_component.change(fn=update_panel, inputs=inputs, outputs=panel)
 
 
 
 
248
 
 
249
  if __name__ == "__main__":
250
  setup_demo_files()
251
  demo.launch()
 
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%;">
572
+
573
+ ```python
574
+ "stacked" | "two-column"
575
+ ```
576
+
577
+ </td>
578
+ <td align="left"><code>"stacked"</code></td>
579
+ <td align="left">None</td>
580
+ </tr>
581
+
582
+ <tr>
583
+ <td align="left"><code>title_uppercase</code></td>
584
+ <td align="left" style="width: 25%;">
585
+
586
+ ```python
587
+ bool
588
+ ```
589
+
590
+ </td>
591
+ <td align="left"><code>False</code></td>
592
+ <td align="left">None</td>
593
+ </tr>
594
+
595
+ <tr>
596
+ <td align="left"><code>name_uppercase</code></td>
597
+ <td align="left" style="width: 25%;">
598
+
599
+ ```python
600
+ bool
601
+ ```
602
+
603
+ </td>
604
+ <td align="left"><code>False</code></td>
605
+ <td align="left">None</td>
606
+ </tr>
607
+
608
+ <tr>
609
+ <td align="left"><code>section_title_uppercase</code></td>
610
+ <td align="left" style="width: 25%;">
611
+
612
+ ```python
613
+ bool
614
+ ```
615
+
616
+ </td>
617
+ <td align="left"><code>True</code></td>
618
+ <td align="left">None</td>
619
+ </tr>
620
+
621
+ <tr>
622
+ <td align="left"><code>swap_font_sizes_on_two_column</code></td>
623
+ <td align="left" style="width: 25%;">
624
+
625
+ ```python
626
+ bool
627
+ ```
628
+
629
+ </td>
630
+ <td align="left"><code>False</code></td>
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%;">
app.py CHANGED
@@ -1,250 +1,226 @@
1
- """
2
- app.py
3
-
4
- This script serves as an interactive demonstration for the custom Gradio component `CreditsPanel`.
5
- It showcases all available features of the component, allowing users to dynamically adjust
6
- properties like animation effects, speed, layout, and styling. The app also demonstrates
7
- how to handle file dependencies (logo, licenses) in a portable way.
8
- """
9
-
10
- import gradio as gr
11
- from gradio_creditspanel import CreditsPanel
12
- import os
13
-
14
- # --- 1. SETUP & DATA PREPARATION ---
15
- # This section prepares all necessary assets and data for the demo.
16
- # It ensures the demo runs out-of-the-box without manual setup.
17
-
18
- def setup_demo_files():
19
- """
20
- Creates necessary directories and dummy files (logo, licenses) for the demo.
21
- This makes the application self-contained and easy to run.
22
- """
23
- # Create dummy license files
24
- os.makedirs("./src/LICENSES", exist_ok=True)
25
- if not os.path.exists("./src/LICENSES/Apache.txt"):
26
- with open("./src/LICENSES/Apache.txt", "w") as f:
27
- f.write("Apache License\nVersion 2.0, January 2004\nhttp://www.apache.org/licenses/...")
28
- if not os.path.exists("./src/LICENSES/MIT.txt"):
29
- with open("./src/LICENSES/MIT.txt", "w") as f:
30
- f.write("MIT License\nCopyright (c) 2025 Author\nPermission is hereby granted...")
31
-
32
- # Create a placeholder logo if it doesn't exist
33
- os.makedirs(".src/assets", exist_ok=True)
34
- if not os.path.exists(".src/assets/logo.webp"):
35
- with open(".src/assets/logo.webp", "w") as f:
36
- f.write("Placeholder WebP logo")
37
-
38
- # Initial data for the credits roll
39
- credits_list = [
40
- {"title": "Project Manager", "name": "Emma Thompson"},
41
- {"title": "Lead Developer", "name": "John Doe"},
42
- {"title": "Senior Backend Engineer", "name": "Michael Chen"},
43
- {"title": "Frontend Developer", "name": "Sarah Johnson"},
44
- {"title": "UI/UX Designer", "name": "Jane Smith"},
45
- {"title": "Database Architect", "name": "Alex Ray"},
46
- {"title": "DevOps Engineer", "name": "Liam Patel"},
47
- {"title": "Quality Assurance Lead", "name": "Sam Wilson"},
48
- {"title": "Test Automation Engineer", "name": "Olivia Brown"},
49
- {"title": "Security Analyst", "name": "David Kim"},
50
- {"title": "Data Scientist", "name": "Sophie Martinez"},
51
- {"title": "Machine Learning Engineer", "name": "Ethan Lee"},
52
- {"title": "API Developer", "name": "Isabella Garcia"},
53
- {"title": "Technical Writer", "name": "Noah Davis"},
54
- {"title": "Scrum Master", "name": "Ava Rodriguez"},
55
- ]
56
-
57
- # Paths to license files
58
- license_paths = {
59
- "Gradio Framework": "./src/LICENSES/Apache.txt",
60
- "This Component": "./src/LICENSES/MIT.txt"
61
- }
62
-
63
- # Default animation speeds for each effect to provide a good user experience
64
- DEFAULT_SPEEDS = {
65
- "scroll": 40.0,
66
- "starwars": 80.0,
67
- "matrix": 40.0
68
- }
69
-
70
- # --- 2. GRADIO EVENT HANDLER FUNCTIONS ---
71
- # These functions define the application's interactive logic.
72
-
73
- def update_panel(
74
- effect: str,
75
- speed: float,
76
- base_font_size: float,
77
- intro_title: str,
78
- intro_subtitle: str,
79
- sidebar_position: str,
80
- show_logo: bool,
81
- show_licenses: bool,
82
- show_credits: bool,
83
- logo_position: str,
84
- logo_sizing: str,
85
- logo_width: str | None,
86
- logo_height: str | None,
87
- scroll_background_color: str | None,
88
- scroll_title_color: str | None,
89
- scroll_name_color: str | None
90
- ) -> dict:
91
- """
92
- Callback function that updates all properties of the CreditsPanel component.
93
- It takes the current state of all UI controls and returns a gr.update() dictionary.
94
- """
95
- return gr.update(
96
- visible=True,
97
- effect=effect,
98
- speed=speed,
99
- base_font_size=base_font_size,
100
- intro_title=intro_title,
101
- intro_subtitle=intro_subtitle,
102
- sidebar_position=sidebar_position,
103
- show_logo=show_logo,
104
- show_licenses=show_licenses,
105
- show_credits=show_credits,
106
- logo_position=logo_position,
107
- logo_sizing=logo_sizing,
108
- logo_width=logo_width,
109
- logo_height=logo_height,
110
- scroll_background_color=scroll_background_color,
111
- scroll_title_color=scroll_title_color,
112
- scroll_name_color=scroll_name_color,
113
- value=credits_list # The list of credits to display
114
- )
115
-
116
- def update_ui_on_effect_change(effect: str) -> tuple[float, float]:
117
- """
118
- Updates the speed and font size sliders to sensible defaults when the
119
- animation effect is changed.
120
- """
121
- font_size = 1.5
122
- if effect == "starwars":
123
- font_size = 6.0 # Star Wars effect looks better with a larger font
124
-
125
- speed = DEFAULT_SPEEDS.get(effect, 40.0)
126
- return speed, font_size
127
-
128
- # --- 3. GRADIO UI DEFINITION ---
129
- # This section constructs the user interface using gr.Blocks.
130
-
131
- with gr.Blocks(theme=gr.themes.Ocean(), title="CreditsPanel Demo") as demo:
132
- gr.Markdown(
133
- """
134
- # Interactive CreditsPanel Demo
135
- Use the sidebar controls to customize the `CreditsPanel` component in real-time.
136
- """
137
- )
138
-
139
- with gr.Sidebar(position="right"):
140
- gr.Markdown("### Effects Settings")
141
- effect_radio = gr.Radio(
142
- ["scroll", "starwars", "matrix"], label="Animation Effect", value="scroll",
143
- info="Select the visual style for the credits."
144
- )
145
- speed_slider = gr.Slider(
146
- minimum=5.0, maximum=100.0, step=1.0, value=DEFAULT_SPEEDS["scroll"],
147
- label="Animation Speed (seconds)", info="Duration of one animation cycle."
148
- )
149
- font_size_slider = gr.Slider(
150
- minimum=1.0, maximum=10.0, step=0.1, value=1.5,
151
- label="Base Font Size (rem)", info="Controls the base font size."
152
- )
153
-
154
- gr.Markdown("### Intro Text")
155
- intro_title_input = gr.Textbox(
156
- label="Intro Title", value="Gradio", info="Main title for the intro sequence."
157
- )
158
- intro_subtitle_input = gr.Textbox(
159
- label="Intro Subtitle", value="The best UI framework", info="Subtitle for the intro sequence."
160
- )
161
-
162
- gr.Markdown("### Layout & Visibility")
163
- sidebar_position_radio = gr.Radio(
164
- ["right", "bottom"], label="Sidebar Position", value="right",
165
- info="Place the licenses sidebar on the right or bottom."
166
- )
167
- show_logo_checkbox = gr.Checkbox(label="Show Logo", value=True)
168
- show_licenses_checkbox = gr.Checkbox(label="Show Licenses", value=True)
169
- show_credits_checkbox = gr.Checkbox(label="Show Credits", value=True)
170
- gr.Markdown("### Logo Customization")
171
- logo_position_radio = gr.Radio(
172
- ["left", "center", "right"], label="Logo Position", value="center"
173
- )
174
- logo_sizing_radio = gr.Radio(
175
- ["stretch", "crop", "resize"], label="Logo Sizing", value="resize"
176
- )
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_name_color = gr.ColorPicker(label="Name Color", value="#FFFFFF")
184
-
185
- # Instantiate the custom CreditsPanel component with default values
186
- panel = CreditsPanel(
187
- credits=credits_list,
188
- licenses=license_paths,
189
- effect="scroll",
190
- height=500,
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="src/assets/logo.webp",
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
- )
208
-
209
- # List of all input components that should trigger a panel update
210
- inputs = [
211
- effect_radio,
212
- speed_slider,
213
- font_size_slider,
214
- intro_title_input,
215
- intro_subtitle_input,
216
- sidebar_position_radio,
217
- show_logo_checkbox,
218
- show_licenses_checkbox,
219
- show_credits_checkbox,
220
- logo_position_radio,
221
- logo_sizing_radio,
222
- logo_width_input,
223
- logo_height_input,
224
- scroll_background_color,
225
- scroll_title_color,
226
- scroll_name_color
227
- ]
228
-
229
- # --- 4. EVENT BINDING ---
230
- # Connect the UI controls to the handler functions.
231
-
232
- # Special event: changing the effect also updates speed and font size sliders
233
- effect_radio.change(
234
- fn=update_ui_on_effect_change,
235
- inputs=effect_radio,
236
- outputs=[speed_slider, font_size_slider]
237
- )
238
-
239
- # General event: any change in an input control updates the main panel
240
- for input_component in inputs:
241
- input_component.change(
242
- fn=update_panel,
243
- inputs=inputs,
244
- outputs=panel
245
- )
246
-
247
- # --- 5. APP LAUNCH ---
248
- if __name__ == "__main__":
249
- setup_demo_files()
250
  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
+ # --- UPDATED: 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
+
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="./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()
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}, '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
 
@@ -38,78 +38,56 @@ pip install gradio_creditspanel
38
  ## Usage
39
 
40
  ```python
41
- \"\"\"
42
- app.py
43
-
44
- This script serves as an interactive demonstration for the custom Gradio component `CreditsPanel`.
45
- It showcases all available features of the component, allowing users to dynamically adjust
46
- properties like animation effects, speed, layout, and styling. The app also demonstrates
47
- how to handle file dependencies (logo, licenses) in a portable way.
48
- \"\"\"
49
 
50
  import gradio as gr
51
  from gradio_creditspanel import CreditsPanel
52
  import os
53
 
54
- # --- 1. SETUP & DATA PREPARATION ---
55
- # This section prepares all necessary assets and data for the demo.
56
- # It ensures the demo runs out-of-the-box without manual setup.
57
-
58
  def setup_demo_files():
59
- \"\"\"
60
- Creates necessary directories and dummy files (logo, licenses) for the demo.
61
- This makes the application self-contained and easy to run.
62
- \"\"\"
63
- # Create dummy license files
64
  os.makedirs("LICENSES", exist_ok=True)
65
  if not os.path.exists("LICENSES/Apache.txt"):
66
  with open("LICENSES/Apache.txt", "w") as f:
67
- f.write("Apache License\nVersion 2.0, January 2004\nhttp://www.apache.org/licenses/...")
68
  if not os.path.exists("LICENSES/MIT.txt"):
69
  with open("LICENSES/MIT.txt", "w") as f:
70
- f.write("MIT License\nCopyright (c) 2025 Author\nPermission is hereby granted...")
71
-
72
- # Create a placeholder logo if it doesn't exist
73
  os.makedirs("assets", exist_ok=True)
74
  if not os.path.exists("./assets/logo.webp"):
75
  with open("./assets/logo.webp", "w") as f:
76
  f.write("Placeholder WebP logo")
77
 
78
- # Initial data for the credits roll
79
  credits_list = [
 
80
  {"title": "Project Manager", "name": "Emma Thompson"},
 
 
 
81
  {"title": "Lead Developer", "name": "John Doe"},
82
  {"title": "Senior Backend Engineer", "name": "Michael Chen"},
83
  {"title": "Frontend Developer", "name": "Sarah Johnson"},
84
  {"title": "UI/UX Designer", "name": "Jane Smith"},
85
  {"title": "Database Architect", "name": "Alex Ray"},
 
 
86
  {"title": "DevOps Engineer", "name": "Liam Patel"},
87
  {"title": "Quality Assurance Lead", "name": "Sam Wilson"},
88
  {"title": "Test Automation Engineer", "name": "Olivia Brown"},
89
- {"title": "Security Analyst", "name": "David Kim"},
90
- {"title": "Data Scientist", "name": "Sophie Martinez"},
91
- {"title": "Machine Learning Engineer", "name": "Ethan Lee"},
92
- {"title": "API Developer", "name": "Isabella Garcia"},
93
- {"title": "Technical Writer", "name": "Noah Davis"},
94
- {"title": "Scrum Master", "name": "Ava Rodriguez"},
95
  ]
96
 
97
- # Paths to license files
98
  license_paths = {
99
  "Gradio Framework": "./LICENSES/Apache.txt",
100
  "This Component": "./LICENSES/MIT.txt"
101
  }
102
 
103
- # Default animation speeds for each effect to provide a good user experience
104
  DEFAULT_SPEEDS = {
105
  "scroll": 40.0,
106
- "starwars": 80.0,
107
  "matrix": 40.0
108
  }
109
 
110
- # --- 2. GRADIO EVENT HANDLER FUNCTIONS ---
111
- # These functions define the application's interactive logic.
112
-
113
  def update_panel(
114
  effect: str,
115
  speed: float,
@@ -120,53 +98,57 @@ def update_panel(
120
  show_logo: bool,
121
  show_licenses: bool,
122
  show_credits: bool,
123
- logo_position: str,
124
  logo_sizing: str,
125
  logo_width: str | None,
126
  logo_height: str | None,
127
  scroll_background_color: str | None,
128
- scroll_title_color: str | None,
129
- scroll_name_color: str | None
 
 
 
 
 
130
  ) -> dict:
131
- \"\"\"
132
- Callback function that updates all properties of the CreditsPanel component.
133
- It takes the current state of all UI controls and returns a gr.update() dictionary.
134
- \"\"\"
135
  return gr.update(
136
  visible=True,
137
- effect=effect,
138
- speed=speed,
139
  base_font_size=base_font_size,
140
- intro_title=intro_title,
141
  intro_subtitle=intro_subtitle,
142
- sidebar_position=sidebar_position,
143
  show_logo=show_logo,
144
- show_licenses=show_licenses,
145
  show_credits=show_credits,
146
- logo_position=logo_position,
147
  logo_sizing=logo_sizing,
148
- logo_width=logo_width,
149
  logo_height=logo_height,
150
  scroll_background_color=scroll_background_color,
151
  scroll_title_color=scroll_title_color,
152
- scroll_name_color=scroll_name_color,
153
- value=credits_list # The list of credits to display
 
 
 
 
 
154
  )
155
 
156
  def update_ui_on_effect_change(effect: str) -> tuple[float, float]:
157
- \"\"\"
158
- Updates the speed and font size sliders to sensible defaults when the
159
- animation effect is changed.
160
- \"\"\"
161
  font_size = 1.5
162
  if effect == "starwars":
163
- font_size = 6.0 # Star Wars effect looks better with a larger font
164
-
165
  speed = DEFAULT_SPEEDS.get(effect, 40.0)
166
  return speed, font_size
167
 
168
- # --- 3. GRADIO UI DEFINITION ---
169
- # This section constructs the user interface using gr.Blocks.
 
170
 
171
  with gr.Blocks(theme=gr.themes.Ocean(), title="CreditsPanel Demo") as demo:
172
  gr.Markdown(
@@ -178,42 +160,37 @@ with gr.Blocks(theme=gr.themes.Ocean(), title="CreditsPanel Demo") as demo:
178
 
179
  with gr.Sidebar(position="right"):
180
  gr.Markdown("### Effects Settings")
181
- effect_radio = gr.Radio(
182
- ["scroll", "starwars", "matrix"], label="Animation Effect", value="scroll",
183
- info="Select the visual style for the credits."
 
 
 
 
 
184
  )
185
- speed_slider = gr.Slider(
186
- minimum=5.0, maximum=100.0, step=1.0, value=DEFAULT_SPEEDS["scroll"],
187
- label="Animation Speed (seconds)", info="Duration of one animation cycle."
 
188
  )
189
- font_size_slider = gr.Slider(
190
- minimum=1.0, maximum=10.0, step=0.1, value=1.5,
191
- label="Base Font Size (rem)", info="Controls the base font size."
192
- )
193
-
194
  gr.Markdown("### Intro Text")
195
- intro_title_input = gr.Textbox(
196
- label="Intro Title", value="Gradio", info="Main title for the intro sequence."
197
- )
198
- intro_subtitle_input = gr.Textbox(
199
- label="Intro Subtitle", value="The best UI framework", info="Subtitle for the intro sequence."
200
- )
201
 
202
  gr.Markdown("### Layout & Visibility")
203
- sidebar_position_radio = gr.Radio(
204
- ["right", "bottom"], label="Sidebar Position", value="right",
205
- info="Place the licenses sidebar on the right or bottom."
206
- )
207
  show_logo_checkbox = gr.Checkbox(label="Show Logo", value=True)
208
  show_licenses_checkbox = gr.Checkbox(label="Show Licenses", value=True)
209
  show_credits_checkbox = gr.Checkbox(label="Show Credits", value=True)
 
210
  gr.Markdown("### Logo Customization")
211
- logo_position_radio = gr.Radio(
212
- ["left", "center", "right"], label="Logo Position", value="center"
213
- )
214
- logo_sizing_radio = gr.Radio(
215
- ["stretch", "crop", "resize"], label="Logo Sizing", value="resize"
216
- )
217
  logo_width_input = gr.Textbox(label="Logo Width", value="200px")
218
  logo_height_input = gr.Textbox(label="Logo Height", value="100px")
219
 
@@ -222,7 +199,6 @@ with gr.Blocks(theme=gr.themes.Ocean(), title="CreditsPanel Demo") as demo:
222
  scroll_title_color = gr.ColorPicker(label="Title Color", value="#FFFFFF")
223
  scroll_name_color = gr.ColorPicker(label="Name Color", value="#FFFFFF")
224
 
225
- # Instantiate the custom CreditsPanel component with default values
226
  panel = CreditsPanel(
227
  credits=credits_list,
228
  licenses=license_paths,
@@ -243,10 +219,14 @@ with gr.Blocks(theme=gr.themes.Ocean(), title="CreditsPanel Demo") as demo:
243
  logo_height="100px",
244
  scroll_background_color="#000000",
245
  scroll_title_color="#FFFFFF",
246
- scroll_name_color="#FFFFFF",
 
 
 
 
 
247
  )
248
 
249
- # List of all input components that should trigger a panel update
250
  inputs = [
251
  effect_radio,
252
  speed_slider,
@@ -256,35 +236,31 @@ with gr.Blocks(theme=gr.themes.Ocean(), title="CreditsPanel Demo") as demo:
256
  sidebar_position_radio,
257
  show_logo_checkbox,
258
  show_licenses_checkbox,
259
- show_credits_checkbox,
260
  logo_position_radio,
261
  logo_sizing_radio,
262
  logo_width_input,
263
  logo_height_input,
264
  scroll_background_color,
265
  scroll_title_color,
266
- scroll_name_color
 
 
 
 
 
267
  ]
268
 
269
- # --- 4. EVENT BINDING ---
270
- # Connect the UI controls to the handler functions.
271
-
272
- # Special event: changing the effect also updates speed and font size sliders
273
- effect_radio.change(
274
- fn=update_ui_on_effect_change,
275
- inputs=effect_radio,
276
- outputs=[speed_slider, font_size_slider]
277
  )
 
278
 
279
- # General event: any change in an input control updates the main panel
280
  for input_component in inputs:
281
- input_component.change(
282
- fn=update_panel,
283
- inputs=inputs,
284
- outputs=panel
285
- )
286
 
287
- # --- 5. APP LAUNCH ---
288
  if __name__ == "__main__":
289
  setup_demo_files()
290
  demo.launch()
 
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
 
 
38
  ## Usage
39
 
40
  ```python
41
+ # app.py
 
 
 
 
 
 
 
42
 
43
  import gradio as gr
44
  from gradio_creditspanel import CreditsPanel
45
  import os
46
 
 
 
 
 
47
  def setup_demo_files():
48
+ \"\"\"Creates necessary directories and dummy files for the demo.\"\"\"
 
 
 
 
49
  os.makedirs("LICENSES", exist_ok=True)
50
  if not os.path.exists("LICENSES/Apache.txt"):
51
  with open("LICENSES/Apache.txt", "w") as f:
52
+ f.write("Apache License\nVersion 2.0, January 2004...")
53
  if not os.path.exists("LICENSES/MIT.txt"):
54
  with open("LICENSES/MIT.txt", "w") as f:
55
+ f.write("MIT License\nCopyright (c) 2025 Author...")
 
 
56
  os.makedirs("assets", exist_ok=True)
57
  if not os.path.exists("./assets/logo.webp"):
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"},
65
+ {"title": "Scrum Master", "name": "Ava Rodriguez"},
66
+
67
+ {"section_title": "Development Team"},
68
  {"title": "Lead Developer", "name": "John Doe"},
69
  {"title": "Senior Backend Engineer", "name": "Michael Chen"},
70
  {"title": "Frontend Developer", "name": "Sarah Johnson"},
71
  {"title": "UI/UX Designer", "name": "Jane Smith"},
72
  {"title": "Database Architect", "name": "Alex Ray"},
73
+
74
+ {"section_title": "Quality & Operations"},
75
  {"title": "DevOps Engineer", "name": "Liam Patel"},
76
  {"title": "Quality Assurance Lead", "name": "Sam Wilson"},
77
  {"title": "Test Automation Engineer", "name": "Olivia Brown"},
 
 
 
 
 
 
78
  ]
79
 
 
80
  license_paths = {
81
  "Gradio Framework": "./LICENSES/Apache.txt",
82
  "This Component": "./LICENSES/MIT.txt"
83
  }
84
 
 
85
  DEFAULT_SPEEDS = {
86
  "scroll": 40.0,
87
+ "starwars": 70.0,
88
  "matrix": 40.0
89
  }
90
 
 
 
 
91
  def update_panel(
92
  effect: str,
93
  speed: float,
 
98
  show_logo: bool,
99
  show_licenses: bool,
100
  show_credits: bool,
101
+ logo_position: str,
102
  logo_sizing: str,
103
  logo_width: str | None,
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.\"\"\"
 
 
 
143
  font_size = 1.5
144
  if effect == "starwars":
145
+ font_size = 3.8
 
146
  speed = DEFAULT_SPEEDS.get(effect, 40.0)
147
  return speed, font_size
148
 
149
+ def toggle_swap_checkbox_visibility(layout: str) -> dict:
150
+ \"\"\"Show the swap checkbox only for the two-column layout.\"\"\"
151
+ return gr.update(visible=(layout == 'two-column'))
152
 
153
  with gr.Blocks(theme=gr.themes.Ocean(), title="CreditsPanel Demo") as demo:
154
  gr.Markdown(
 
160
 
161
  with gr.Sidebar(position="right"):
162
  gr.Markdown("### Effects Settings")
163
+ effect_radio = gr.Radio(["scroll", "starwars", "matrix"], label="Animation Effect", value="scroll")
164
+ speed_slider = gr.Slider(minimum=5.0, maximum=100.0, step=1.0, value=DEFAULT_SPEEDS["scroll"], label="Animation Speed")
165
+ font_size_slider = gr.Slider(minimum=1.0, maximum=10.0, step=0.1, value=1.5, label="Base Font Size")
166
+
167
+ gr.Markdown("### Credits Layout Settings")
168
+ layout_style_radio = gr.Radio(
169
+ ["stacked", "two-column"], label="Layout Style", value="stacked",
170
+ info="How to display titles and names."
171
  )
172
+ swap_sizes_checkbox = gr.Checkbox(
173
+ label="Swap Title/Name Font Sizes", value=False,
174
+ info="Emphasize name over title in two-column layout.",
175
+ visible=False
176
  )
177
+ title_uppercase_checkbox = gr.Checkbox(label="Title Uppercase", value=False)
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")
 
 
 
 
184
 
185
  gr.Markdown("### Layout & Visibility")
186
+ sidebar_position_radio = gr.Radio(["right", "bottom"], label="Sidebar Position", value="right")
 
 
 
187
  show_logo_checkbox = gr.Checkbox(label="Show Logo", value=True)
188
  show_licenses_checkbox = gr.Checkbox(label="Show Licenses", value=True)
189
  show_credits_checkbox = gr.Checkbox(label="Show Credits", value=True)
190
+
191
  gr.Markdown("### Logo Customization")
192
+ logo_position_radio = gr.Radio(["left", "center", "right"], label="Logo Position", value="center")
193
+ logo_sizing_radio = gr.Radio(["stretch", "crop", "resize"], label="Logo Sizing", value="resize")
 
 
 
 
194
  logo_width_input = gr.Textbox(label="Logo Width", value="200px")
195
  logo_height_input = gr.Textbox(label="Logo Height", value="100px")
196
 
 
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,
 
219
  logo_height="100px",
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 = [
231
  effect_radio,
232
  speed_slider,
 
236
  sidebar_position_radio,
237
  show_logo_checkbox,
238
  show_licenses_checkbox,
239
+ show_credits_checkbox,
240
  logo_position_radio,
241
  logo_sizing_radio,
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,
257
+ outputs=swap_sizes_checkbox
 
 
 
 
258
  )
259
+ effect_radio.change(fn=update_ui_on_effect_change, inputs=effect_radio, outputs=[speed_slider, font_size_slider])
260
 
 
261
  for input_component in inputs:
262
+ input_component.change(fn=update_panel, inputs=inputs, outputs=panel)
 
 
 
 
263
 
 
264
  if __name__ == "__main__":
265
  setup_demo_files()
266
  demo.launch()
src/README.md CHANGED
@@ -10,7 +10,7 @@ app_file: space.py
10
  ---
11
 
12
  # `gradio_creditspanel`
13
- <img alt="Static Badge" src="https://img.shields.io/badge/version%20-%200.0.2%20-%20blue"> <a href="https://huggingface.co/spaces/elismasilva/gradio_creditspanel"><img src="https://img.shields.io/badge/%F0%9F%A4%97%20Hugging%20Face-Demo-blue"></a><p><span>💻 <a href='https://github.com/DEVAIEXP/gradio_component_creditspanel'>Component GitHub Code</a></span></p>
14
 
15
  Credits Panel for Gradio UI
16
 
@@ -23,78 +23,56 @@ pip install gradio_creditspanel
23
  ## Usage
24
 
25
  ```python
26
- """
27
- app.py
28
-
29
- This script serves as an interactive demonstration for the custom Gradio component `CreditsPanel`.
30
- It showcases all available features of the component, allowing users to dynamically adjust
31
- properties like animation effects, speed, layout, and styling. The app also demonstrates
32
- how to handle file dependencies (logo, licenses) in a portable way.
33
- """
34
 
35
  import gradio as gr
36
  from gradio_creditspanel import CreditsPanel
37
  import os
38
 
39
- # --- 1. SETUP & DATA PREPARATION ---
40
- # This section prepares all necessary assets and data for the demo.
41
- # It ensures the demo runs out-of-the-box without manual setup.
42
-
43
  def setup_demo_files():
44
- """
45
- Creates necessary directories and dummy files (logo, licenses) for the demo.
46
- This makes the application self-contained and easy to run.
47
- """
48
- # Create dummy license files
49
  os.makedirs("LICENSES", exist_ok=True)
50
  if not os.path.exists("LICENSES/Apache.txt"):
51
  with open("LICENSES/Apache.txt", "w") as f:
52
- f.write("Apache License\nVersion 2.0, January 2004\nhttp://www.apache.org/licenses/...")
53
  if not os.path.exists("LICENSES/MIT.txt"):
54
  with open("LICENSES/MIT.txt", "w") as f:
55
- f.write("MIT License\nCopyright (c) 2025 Author\nPermission is hereby granted...")
56
-
57
- # Create a placeholder logo if it doesn't exist
58
  os.makedirs("assets", exist_ok=True)
59
  if not os.path.exists("./assets/logo.webp"):
60
  with open("./assets/logo.webp", "w") as f:
61
  f.write("Placeholder WebP logo")
62
 
63
- # Initial data for the credits roll
64
  credits_list = [
 
65
  {"title": "Project Manager", "name": "Emma Thompson"},
 
 
 
66
  {"title": "Lead Developer", "name": "John Doe"},
67
  {"title": "Senior Backend Engineer", "name": "Michael Chen"},
68
  {"title": "Frontend Developer", "name": "Sarah Johnson"},
69
  {"title": "UI/UX Designer", "name": "Jane Smith"},
70
  {"title": "Database Architect", "name": "Alex Ray"},
 
 
71
  {"title": "DevOps Engineer", "name": "Liam Patel"},
72
  {"title": "Quality Assurance Lead", "name": "Sam Wilson"},
73
  {"title": "Test Automation Engineer", "name": "Olivia Brown"},
74
- {"title": "Security Analyst", "name": "David Kim"},
75
- {"title": "Data Scientist", "name": "Sophie Martinez"},
76
- {"title": "Machine Learning Engineer", "name": "Ethan Lee"},
77
- {"title": "API Developer", "name": "Isabella Garcia"},
78
- {"title": "Technical Writer", "name": "Noah Davis"},
79
- {"title": "Scrum Master", "name": "Ava Rodriguez"},
80
  ]
81
 
82
- # Paths to license files
83
  license_paths = {
84
  "Gradio Framework": "./LICENSES/Apache.txt",
85
  "This Component": "./LICENSES/MIT.txt"
86
  }
87
 
88
- # Default animation speeds for each effect to provide a good user experience
89
  DEFAULT_SPEEDS = {
90
  "scroll": 40.0,
91
- "starwars": 80.0,
92
  "matrix": 40.0
93
  }
94
 
95
- # --- 2. GRADIO EVENT HANDLER FUNCTIONS ---
96
- # These functions define the application's interactive logic.
97
-
98
  def update_panel(
99
  effect: str,
100
  speed: float,
@@ -105,53 +83,57 @@ def update_panel(
105
  show_logo: bool,
106
  show_licenses: bool,
107
  show_credits: bool,
108
- logo_position: str,
109
  logo_sizing: str,
110
  logo_width: str | None,
111
  logo_height: str | None,
112
  scroll_background_color: str | None,
113
- scroll_title_color: str | None,
114
- scroll_name_color: str | None
 
 
 
 
 
115
  ) -> dict:
116
- """
117
- Callback function that updates all properties of the CreditsPanel component.
118
- It takes the current state of all UI controls and returns a gr.update() dictionary.
119
- """
120
  return gr.update(
121
  visible=True,
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
  show_logo=show_logo,
129
- show_licenses=show_licenses,
130
  show_credits=show_credits,
131
- logo_position=logo_position,
132
  logo_sizing=logo_sizing,
133
- logo_width=logo_width,
134
  logo_height=logo_height,
135
  scroll_background_color=scroll_background_color,
136
  scroll_title_color=scroll_title_color,
137
- scroll_name_color=scroll_name_color,
138
- value=credits_list # The list of credits to display
 
 
 
 
 
139
  )
140
 
141
  def update_ui_on_effect_change(effect: str) -> tuple[float, float]:
142
- """
143
- Updates the speed and font size sliders to sensible defaults when the
144
- animation effect is changed.
145
- """
146
  font_size = 1.5
147
  if effect == "starwars":
148
- font_size = 6.0 # Star Wars effect looks better with a larger font
149
-
150
  speed = DEFAULT_SPEEDS.get(effect, 40.0)
151
  return speed, font_size
152
 
153
- # --- 3. GRADIO UI DEFINITION ---
154
- # This section constructs the user interface using gr.Blocks.
 
155
 
156
  with gr.Blocks(theme=gr.themes.Ocean(), title="CreditsPanel Demo") as demo:
157
  gr.Markdown(
@@ -163,42 +145,37 @@ with gr.Blocks(theme=gr.themes.Ocean(), title="CreditsPanel Demo") as demo:
163
 
164
  with gr.Sidebar(position="right"):
165
  gr.Markdown("### Effects Settings")
166
- effect_radio = gr.Radio(
167
- ["scroll", "starwars", "matrix"], label="Animation Effect", value="scroll",
168
- info="Select the visual style for the credits."
169
- )
170
- speed_slider = gr.Slider(
171
- minimum=5.0, maximum=100.0, step=1.0, value=DEFAULT_SPEEDS["scroll"],
172
- label="Animation Speed (seconds)", info="Duration of one animation cycle."
 
173
  )
174
- font_size_slider = gr.Slider(
175
- minimum=1.0, maximum=10.0, step=0.1, value=1.5,
176
- label="Base Font Size (rem)", info="Controls the base font size."
 
177
  )
178
-
 
 
 
179
  gr.Markdown("### Intro Text")
180
- intro_title_input = gr.Textbox(
181
- label="Intro Title", value="Gradio", info="Main title for the intro sequence."
182
- )
183
- intro_subtitle_input = gr.Textbox(
184
- label="Intro Subtitle", value="The best UI framework", info="Subtitle for the intro sequence."
185
- )
186
 
187
  gr.Markdown("### Layout & Visibility")
188
- sidebar_position_radio = gr.Radio(
189
- ["right", "bottom"], label="Sidebar Position", value="right",
190
- info="Place the licenses sidebar on the right or bottom."
191
- )
192
  show_logo_checkbox = gr.Checkbox(label="Show Logo", value=True)
193
  show_licenses_checkbox = gr.Checkbox(label="Show Licenses", value=True)
194
  show_credits_checkbox = gr.Checkbox(label="Show Credits", value=True)
 
195
  gr.Markdown("### Logo Customization")
196
- logo_position_radio = gr.Radio(
197
- ["left", "center", "right"], label="Logo Position", value="center"
198
- )
199
- logo_sizing_radio = gr.Radio(
200
- ["stretch", "crop", "resize"], label="Logo Sizing", value="resize"
201
- )
202
  logo_width_input = gr.Textbox(label="Logo Width", value="200px")
203
  logo_height_input = gr.Textbox(label="Logo Height", value="100px")
204
 
@@ -207,7 +184,6 @@ with gr.Blocks(theme=gr.themes.Ocean(), title="CreditsPanel Demo") as demo:
207
  scroll_title_color = gr.ColorPicker(label="Title Color", value="#FFFFFF")
208
  scroll_name_color = gr.ColorPicker(label="Name Color", value="#FFFFFF")
209
 
210
- # Instantiate the custom CreditsPanel component with default values
211
  panel = CreditsPanel(
212
  credits=credits_list,
213
  licenses=license_paths,
@@ -228,10 +204,14 @@ with gr.Blocks(theme=gr.themes.Ocean(), title="CreditsPanel Demo") as demo:
228
  logo_height="100px",
229
  scroll_background_color="#000000",
230
  scroll_title_color="#FFFFFF",
231
- scroll_name_color="#FFFFFF",
 
 
 
 
 
232
  )
233
 
234
- # List of all input components that should trigger a panel update
235
  inputs = [
236
  effect_radio,
237
  speed_slider,
@@ -241,35 +221,31 @@ with gr.Blocks(theme=gr.themes.Ocean(), title="CreditsPanel Demo") as demo:
241
  sidebar_position_radio,
242
  show_logo_checkbox,
243
  show_licenses_checkbox,
244
- show_credits_checkbox,
245
  logo_position_radio,
246
  logo_sizing_radio,
247
  logo_width_input,
248
  logo_height_input,
249
  scroll_background_color,
250
  scroll_title_color,
251
- scroll_name_color
 
 
 
 
 
252
  ]
253
 
254
- # --- 4. EVENT BINDING ---
255
- # Connect the UI controls to the handler functions.
256
-
257
- # Special event: changing the effect also updates speed and font size sliders
258
- effect_radio.change(
259
- fn=update_ui_on_effect_change,
260
- inputs=effect_radio,
261
- outputs=[speed_slider, font_size_slider]
262
  )
 
263
 
264
- # General event: any change in an input control updates the main panel
265
  for input_component in inputs:
266
- input_component.change(
267
- fn=update_panel,
268
- inputs=inputs,
269
- outputs=panel
270
- )
271
 
272
- # --- 5. APP LAUNCH ---
273
  if __name__ == "__main__":
274
  setup_demo_files()
275
  demo.launch()
@@ -590,6 +566,71 @@ str | None
590
  <td align="left">None</td>
591
  </tr>
592
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
593
  <tr>
594
  <td align="left"><code>label</code></td>
595
  <td align="left" style="width: 25%;">
 
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
 
 
23
  ## Usage
24
 
25
  ```python
26
+ # app.py
 
 
 
 
 
 
 
27
 
28
  import gradio as gr
29
  from gradio_creditspanel import CreditsPanel
30
  import os
31
 
 
 
 
 
32
  def setup_demo_files():
33
+ """Creates necessary directories and dummy files for the demo."""
 
 
 
 
34
  os.makedirs("LICENSES", exist_ok=True)
35
  if not os.path.exists("LICENSES/Apache.txt"):
36
  with open("LICENSES/Apache.txt", "w") as f:
37
+ f.write("Apache License\nVersion 2.0, January 2004...")
38
  if not os.path.exists("LICENSES/MIT.txt"):
39
  with open("LICENSES/MIT.txt", "w") as f:
40
+ f.write("MIT License\nCopyright (c) 2025 Author...")
 
 
41
  os.makedirs("assets", exist_ok=True)
42
  if not os.path.exists("./assets/logo.webp"):
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"},
50
+ {"title": "Scrum Master", "name": "Ava Rodriguez"},
51
+
52
+ {"section_title": "Development Team"},
53
  {"title": "Lead Developer", "name": "John Doe"},
54
  {"title": "Senior Backend Engineer", "name": "Michael Chen"},
55
  {"title": "Frontend Developer", "name": "Sarah Johnson"},
56
  {"title": "UI/UX Designer", "name": "Jane Smith"},
57
  {"title": "Database Architect", "name": "Alex Ray"},
58
+
59
+ {"section_title": "Quality & Operations"},
60
  {"title": "DevOps Engineer", "name": "Liam Patel"},
61
  {"title": "Quality Assurance Lead", "name": "Sam Wilson"},
62
  {"title": "Test Automation Engineer", "name": "Olivia Brown"},
 
 
 
 
 
 
63
  ]
64
 
 
65
  license_paths = {
66
  "Gradio Framework": "./LICENSES/Apache.txt",
67
  "This Component": "./LICENSES/MIT.txt"
68
  }
69
 
 
70
  DEFAULT_SPEEDS = {
71
  "scroll": 40.0,
72
+ "starwars": 70.0,
73
  "matrix": 40.0
74
  }
75
 
 
 
 
76
  def update_panel(
77
  effect: str,
78
  speed: float,
 
83
  show_logo: bool,
84
  show_licenses: bool,
85
  show_credits: bool,
86
+ logo_position: str,
87
  logo_sizing: str,
88
  logo_width: str | None,
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."""
 
 
 
128
  font_size = 1.5
129
  if effect == "starwars":
130
+ font_size = 3.8
 
131
  speed = DEFAULT_SPEEDS.get(effect, 40.0)
132
  return speed, font_size
133
 
134
+ def toggle_swap_checkbox_visibility(layout: str) -> dict:
135
+ """Show the swap checkbox only for the two-column layout."""
136
+ return gr.update(visible=(layout == 'two-column'))
137
 
138
  with gr.Blocks(theme=gr.themes.Ocean(), title="CreditsPanel Demo") as demo:
139
  gr.Markdown(
 
145
 
146
  with gr.Sidebar(position="right"):
147
  gr.Markdown("### Effects Settings")
148
+ effect_radio = gr.Radio(["scroll", "starwars", "matrix"], label="Animation Effect", value="scroll")
149
+ speed_slider = gr.Slider(minimum=5.0, maximum=100.0, step=1.0, value=DEFAULT_SPEEDS["scroll"], label="Animation Speed")
150
+ font_size_slider = gr.Slider(minimum=1.0, maximum=10.0, step=0.1, value=1.5, label="Base Font Size")
151
+
152
+ gr.Markdown("### Credits Layout Settings")
153
+ layout_style_radio = gr.Radio(
154
+ ["stacked", "two-column"], label="Layout Style", value="stacked",
155
+ info="How to display titles and names."
156
  )
157
+ swap_sizes_checkbox = gr.Checkbox(
158
+ label="Swap Title/Name Font Sizes", value=False,
159
+ info="Emphasize name over title in two-column layout.",
160
+ visible=False
161
  )
162
+ title_uppercase_checkbox = gr.Checkbox(label="Title Uppercase", value=False)
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")
 
 
 
 
169
 
170
  gr.Markdown("### Layout & Visibility")
171
+ sidebar_position_radio = gr.Radio(["right", "bottom"], label="Sidebar Position", value="right")
 
 
 
172
  show_logo_checkbox = gr.Checkbox(label="Show Logo", value=True)
173
  show_licenses_checkbox = gr.Checkbox(label="Show Licenses", value=True)
174
  show_credits_checkbox = gr.Checkbox(label="Show Credits", value=True)
175
+
176
  gr.Markdown("### Logo Customization")
177
+ logo_position_radio = gr.Radio(["left", "center", "right"], label="Logo Position", value="center")
178
+ logo_sizing_radio = gr.Radio(["stretch", "crop", "resize"], label="Logo Sizing", value="resize")
 
 
 
 
179
  logo_width_input = gr.Textbox(label="Logo Width", value="200px")
180
  logo_height_input = gr.Textbox(label="Logo Height", value="100px")
181
 
 
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,
 
204
  logo_height="100px",
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 = [
216
  effect_radio,
217
  speed_slider,
 
221
  sidebar_position_radio,
222
  show_logo_checkbox,
223
  show_licenses_checkbox,
224
+ show_credits_checkbox,
225
  logo_position_radio,
226
  logo_sizing_radio,
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,
242
+ outputs=swap_sizes_checkbox
 
 
 
 
243
  )
244
+ effect_radio.change(fn=update_ui_on_effect_change, inputs=effect_radio, outputs=[speed_slider, font_size_slider])
245
 
 
246
  for input_component in inputs:
247
+ input_component.change(fn=update_panel, inputs=inputs, outputs=panel)
 
 
 
 
248
 
 
249
  if __name__ == "__main__":
250
  setup_demo_files()
251
  demo.launch()
 
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%;">
572
+
573
+ ```python
574
+ "stacked" | "two-column"
575
+ ```
576
+
577
+ </td>
578
+ <td align="left"><code>"stacked"</code></td>
579
+ <td align="left">None</td>
580
+ </tr>
581
+
582
+ <tr>
583
+ <td align="left"><code>title_uppercase</code></td>
584
+ <td align="left" style="width: 25%;">
585
+
586
+ ```python
587
+ bool
588
+ ```
589
+
590
+ </td>
591
+ <td align="left"><code>False</code></td>
592
+ <td align="left">None</td>
593
+ </tr>
594
+
595
+ <tr>
596
+ <td align="left"><code>name_uppercase</code></td>
597
+ <td align="left" style="width: 25%;">
598
+
599
+ ```python
600
+ bool
601
+ ```
602
+
603
+ </td>
604
+ <td align="left"><code>False</code></td>
605
+ <td align="left">None</td>
606
+ </tr>
607
+
608
+ <tr>
609
+ <td align="left"><code>section_title_uppercase</code></td>
610
+ <td align="left" style="width: 25%;">
611
+
612
+ ```python
613
+ bool
614
+ ```
615
+
616
+ </td>
617
+ <td align="left"><code>True</code></td>
618
+ <td align="left">None</td>
619
+ </tr>
620
+
621
+ <tr>
622
+ <td align="left"><code>swap_font_sizes_on_two_column</code></td>
623
+ <td align="left" style="width: 25%;">
624
+
625
+ ```python
626
+ bool
627
+ ```
628
+
629
+ </td>
630
+ <td align="left"><code>False</code></td>
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%;">
src/backend/gradio_creditspanel/creditspanel.py CHANGED
@@ -46,6 +46,11 @@ 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
  label: str | I18nData | None = None,
50
  every: float | None = None,
51
  inputs: Component | Sequence[Component] | set[Component] | None = None,
@@ -87,6 +92,11 @@ class CreditsPanel(Component):
87
  scroll_background_color (str | None, optional): Background color for scroll effect.
88
  scroll_title_color (str | None, optional): Color for credit titles.
89
  scroll_name_color (str | None, optional): Color for credit names.
 
 
 
 
 
90
  label (str | I18nData | None, optional): Component label.
91
  every (float | None, optional): Interval for periodic updates.
92
  inputs (Component | Sequence[Component] | set[Component] | None, optional): Input components for events.
@@ -123,6 +133,12 @@ class CreditsPanel(Component):
123
  self.scroll_background_color = scroll_background_color
124
  self.scroll_title_color = scroll_title_color
125
  self.scroll_name_color = scroll_name_color
 
 
 
 
 
 
126
  super().__init__(
127
  label=label,
128
  every=every,
@@ -217,7 +233,12 @@ class CreditsPanel(Component):
217
  "logo_height": self.logo_height,
218
  "scroll_background_color": self.scroll_background_color,
219
  "scroll_title_color": self.scroll_title_color,
220
- "scroll_name_color": self.scroll_name_color
 
 
 
 
 
221
  }
222
 
223
  def api_info(self) -> Dict[str, Any]:
@@ -252,7 +273,12 @@ class CreditsPanel(Component):
252
  "logo_height": None,
253
  "scroll_background_color": None,
254
  "scroll_title_color": None,
255
- "scroll_name_color": None
 
 
 
 
 
256
  }
257
 
258
  def example_value(self) -> Any:
 
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,
 
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.
 
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,
144
  every=every,
 
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]:
 
273
  "logo_height": None,
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:
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-141h5y1.svelte-141h5y1{width:100%;height:100%;overflow:hidden;position:relative;font-family:sans-serif}.credit.intro-block.svelte-141h5y1.svelte-141h5y1{margin-bottom:5rem;text-align:center}.credits-container.svelte-141h5y1.svelte-141h5y1{position:absolute;bottom:0;transform:translateY(100%);width:100%;text-align:center;animation:svelte-141h5y1-scroll var(--animation-duration) linear infinite}.credit.svelte-141h5y1.svelte-141h5y1{margin-bottom:2rem}.credit.svelte-141h5y1 h2.svelte-141h5y1,.credit.svelte-141h5y1 p.svelte-141h5y1{margin:.5rem 0;font-family:sans-serif}@keyframes svelte-141h5y1-scroll{0%{transform:translateY(100%)}to{transform:translateY(-100%)}}.viewport.svelte-1gy01d0{width:100%;height:100%;position:relative;overflow:hidden;perspective:400px;-webkit-mask-image:linear-gradient(to bottom,black 60%,transparent 100%);mask-image:linear-gradient(to bottom,black 60%,transparent 100%);font-family:Droid Sans,sans-serif;font-weight:700}.stars.svelte-1gy01d0{position:absolute;top:0;left:0;width:1px;height:1px;background:transparent;z-index:0;animation:svelte-1gy01d0-twinkle 10s linear infinite}.stars.small.svelte-1gy01d0{animation-duration:10s}.stars.medium.svelte-1gy01d0{animation-duration:15s}.stars.large.svelte-1gy01d0{animation-duration:20s}@keyframes svelte-1gy01d0-twinkle{0%{opacity:.6}50%{opacity:1}to{opacity:.6}}.crawl.svelte-1gy01d0{position:absolute;width:100%;bottom:0;transform-origin:50% 100%;animation:svelte-1gy01d0-crawl-animation var(--animation-duration) linear infinite;z-index:1;text-align:center}@keyframes svelte-1gy01d0-crawl-animation{0%{transform:rotateX(60deg) translateY(100%) translateZ(100px);opacity:1}to{transform:rotateX(60deg) translateY(-150%) translateZ(-1200px);opacity:1}}.credit.intro-block.svelte-1gy01d0{margin-bottom:5rem}.credit.svelte-1gy01d0{margin-bottom:2rem}h2.svelte-1gy01d0,p.svelte-1gy01d0{margin:.5rem 0;padding:0;white-space:nowrap}.matrix-container.svelte-8jsw80{width:100%;height:100%;position:relative;overflow:hidden}canvas.svelte-8jsw80{display:block;position:absolute;top:0;left:0;width:100%;height:100%;z-index:1}.credits-scroll-overlay.svelte-8jsw80{position:absolute;top:0;left:0;width:100%;height:100%;z-index:2;color:#fff;font-family:monospace;text-align:center;-webkit-mask-image:linear-gradient(transparent,black 20%,black 80%,transparent);mask-image:linear-gradient(transparent,black 20%,black 80%,transparent)}.credits-content.svelte-8jsw80{position:absolute;width:100%;bottom:0;transform:translateY(100%);animation:svelte-8jsw80-scroll-from-bottom var(--animation-duration) linear infinite}@keyframes svelte-8jsw80-scroll-from-bottom{0%{transform:translateY(100%)}to{transform:translateY(-100%)}}.credit-block.intro-block.svelte-8jsw80{margin-bottom:5rem}.credit-block.svelte-8jsw80{margin-bottom:2.5em}.title.svelte-8jsw80{color:#0f0;text-transform:uppercase;opacity:.8}.name.svelte-8jsw80{font-weight:700;color:#5f5;text-shadow:0 0 5px #0F0}.unstyled-link.svelte-151nsdd{all:unset;cursor:pointer}img.svelte-kxeri3{object-fit:cover}.image-container.svelte-x2tujq.svelte-x2tujq{height:100%;position:relative;min-width:var(--size-20)}.image-container.svelte-x2tujq button.svelte-x2tujq{width:var(--size-full);height:var(--size-full);border-radius:var(--radius-lg);display:flex;align-items:center;justify-content:center}.image-frame.svelte-x2tujq img{width:var(--size-full);height:var(--size-full);object-fit:scale-down}.selectable.svelte-x2tujq.svelte-x2tujq{cursor:crosshair}.fullscreen-controls svg{position:relative;top:0}.image-container:fullscreen{background-color:#000;display:flex;justify-content:center;align-items:center}.image-container:fullscreen img{max-width:90vw;max-height:90vh;object-fit:scale-down}.image-frame.svelte-x2tujq.svelte-x2tujq{width:auto;height:100%;display:flex;align-items:center;justify-content:center}.block{border:none!important;box-shadow:none!important;border-style:none!important}.outer-logo-wrapper.svelte-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-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)}
src/demo/app.py CHANGED
@@ -1,75 +1,53 @@
1
- """
2
- app.py
3
-
4
- This script serves as an interactive demonstration for the custom Gradio component `CreditsPanel`.
5
- It showcases all available features of the component, allowing users to dynamically adjust
6
- properties like animation effects, speed, layout, and styling. The app also demonstrates
7
- how to handle file dependencies (logo, licenses) in a portable way.
8
- """
9
 
10
  import gradio as gr
11
  from gradio_creditspanel import CreditsPanel
12
  import os
13
 
14
- # --- 1. SETUP & DATA PREPARATION ---
15
- # This section prepares all necessary assets and data for the demo.
16
- # It ensures the demo runs out-of-the-box without manual setup.
17
-
18
  def setup_demo_files():
19
- """
20
- Creates necessary directories and dummy files (logo, licenses) for the demo.
21
- This makes the application self-contained and easy to run.
22
- """
23
- # Create dummy license files
24
  os.makedirs("LICENSES", exist_ok=True)
25
  if not os.path.exists("LICENSES/Apache.txt"):
26
  with open("LICENSES/Apache.txt", "w") as f:
27
- f.write("Apache License\nVersion 2.0, January 2004\nhttp://www.apache.org/licenses/...")
28
  if not os.path.exists("LICENSES/MIT.txt"):
29
  with open("LICENSES/MIT.txt", "w") as f:
30
- f.write("MIT License\nCopyright (c) 2025 Author\nPermission is hereby granted...")
31
-
32
- # Create a placeholder logo if it doesn't exist
33
  os.makedirs("assets", exist_ok=True)
34
  if not os.path.exists("./assets/logo.webp"):
35
  with open("./assets/logo.webp", "w") as f:
36
  f.write("Placeholder WebP logo")
37
 
38
- # Initial data for the credits roll
39
  credits_list = [
 
40
  {"title": "Project Manager", "name": "Emma Thompson"},
 
 
 
41
  {"title": "Lead Developer", "name": "John Doe"},
42
  {"title": "Senior Backend Engineer", "name": "Michael Chen"},
43
  {"title": "Frontend Developer", "name": "Sarah Johnson"},
44
  {"title": "UI/UX Designer", "name": "Jane Smith"},
45
  {"title": "Database Architect", "name": "Alex Ray"},
 
 
46
  {"title": "DevOps Engineer", "name": "Liam Patel"},
47
  {"title": "Quality Assurance Lead", "name": "Sam Wilson"},
48
  {"title": "Test Automation Engineer", "name": "Olivia Brown"},
49
- {"title": "Security Analyst", "name": "David Kim"},
50
- {"title": "Data Scientist", "name": "Sophie Martinez"},
51
- {"title": "Machine Learning Engineer", "name": "Ethan Lee"},
52
- {"title": "API Developer", "name": "Isabella Garcia"},
53
- {"title": "Technical Writer", "name": "Noah Davis"},
54
- {"title": "Scrum Master", "name": "Ava Rodriguez"},
55
  ]
56
 
57
- # Paths to license files
58
  license_paths = {
59
  "Gradio Framework": "./LICENSES/Apache.txt",
60
  "This Component": "./LICENSES/MIT.txt"
61
  }
62
 
63
- # Default animation speeds for each effect to provide a good user experience
64
  DEFAULT_SPEEDS = {
65
  "scroll": 40.0,
66
- "starwars": 80.0,
67
  "matrix": 40.0
68
  }
69
 
70
- # --- 2. GRADIO EVENT HANDLER FUNCTIONS ---
71
- # These functions define the application's interactive logic.
72
-
73
  def update_panel(
74
  effect: str,
75
  speed: float,
@@ -80,53 +58,57 @@ def update_panel(
80
  show_logo: bool,
81
  show_licenses: bool,
82
  show_credits: bool,
83
- logo_position: str,
84
  logo_sizing: str,
85
  logo_width: str | None,
86
  logo_height: str | None,
87
  scroll_background_color: str | None,
88
- scroll_title_color: str | None,
89
- scroll_name_color: str | None
 
 
 
 
 
90
  ) -> dict:
91
- """
92
- Callback function that updates all properties of the CreditsPanel component.
93
- It takes the current state of all UI controls and returns a gr.update() dictionary.
94
- """
95
  return gr.update(
96
  visible=True,
97
- effect=effect,
98
- speed=speed,
99
  base_font_size=base_font_size,
100
- intro_title=intro_title,
101
  intro_subtitle=intro_subtitle,
102
- sidebar_position=sidebar_position,
103
  show_logo=show_logo,
104
- show_licenses=show_licenses,
105
  show_credits=show_credits,
106
- logo_position=logo_position,
107
  logo_sizing=logo_sizing,
108
- logo_width=logo_width,
109
  logo_height=logo_height,
110
  scroll_background_color=scroll_background_color,
111
  scroll_title_color=scroll_title_color,
112
- scroll_name_color=scroll_name_color,
113
- value=credits_list # The list of credits to display
 
 
 
 
 
114
  )
115
 
116
  def update_ui_on_effect_change(effect: str) -> tuple[float, float]:
117
- """
118
- Updates the speed and font size sliders to sensible defaults when the
119
- animation effect is changed.
120
- """
121
  font_size = 1.5
122
  if effect == "starwars":
123
- font_size = 6.0 # Star Wars effect looks better with a larger font
124
-
125
  speed = DEFAULT_SPEEDS.get(effect, 40.0)
126
  return speed, font_size
127
 
128
- # --- 3. GRADIO UI DEFINITION ---
129
- # This section constructs the user interface using gr.Blocks.
 
130
 
131
  with gr.Blocks(theme=gr.themes.Ocean(), title="CreditsPanel Demo") as demo:
132
  gr.Markdown(
@@ -138,42 +120,37 @@ with gr.Blocks(theme=gr.themes.Ocean(), title="CreditsPanel Demo") as demo:
138
 
139
  with gr.Sidebar(position="right"):
140
  gr.Markdown("### Effects Settings")
141
- effect_radio = gr.Radio(
142
- ["scroll", "starwars", "matrix"], label="Animation Effect", value="scroll",
143
- info="Select the visual style for the credits."
 
 
 
 
 
144
  )
145
- speed_slider = gr.Slider(
146
- minimum=5.0, maximum=100.0, step=1.0, value=DEFAULT_SPEEDS["scroll"],
147
- label="Animation Speed (seconds)", info="Duration of one animation cycle."
 
148
  )
149
- font_size_slider = gr.Slider(
150
- minimum=1.0, maximum=10.0, step=0.1, value=1.5,
151
- label="Base Font Size (rem)", info="Controls the base font size."
152
- )
153
-
154
  gr.Markdown("### Intro Text")
155
- intro_title_input = gr.Textbox(
156
- label="Intro Title", value="Gradio", info="Main title for the intro sequence."
157
- )
158
- intro_subtitle_input = gr.Textbox(
159
- label="Intro Subtitle", value="The best UI framework", info="Subtitle for the intro sequence."
160
- )
161
 
162
  gr.Markdown("### Layout & Visibility")
163
- sidebar_position_radio = gr.Radio(
164
- ["right", "bottom"], label="Sidebar Position", value="right",
165
- info="Place the licenses sidebar on the right or bottom."
166
- )
167
  show_logo_checkbox = gr.Checkbox(label="Show Logo", value=True)
168
  show_licenses_checkbox = gr.Checkbox(label="Show Licenses", value=True)
169
  show_credits_checkbox = gr.Checkbox(label="Show Credits", value=True)
 
170
  gr.Markdown("### Logo Customization")
171
- logo_position_radio = gr.Radio(
172
- ["left", "center", "right"], label="Logo Position", value="center"
173
- )
174
- logo_sizing_radio = gr.Radio(
175
- ["stretch", "crop", "resize"], label="Logo Sizing", value="resize"
176
- )
177
  logo_width_input = gr.Textbox(label="Logo Width", value="200px")
178
  logo_height_input = gr.Textbox(label="Logo Height", value="100px")
179
 
@@ -182,7 +159,6 @@ with gr.Blocks(theme=gr.themes.Ocean(), title="CreditsPanel Demo") as demo:
182
  scroll_title_color = gr.ColorPicker(label="Title Color", value="#FFFFFF")
183
  scroll_name_color = gr.ColorPicker(label="Name Color", value="#FFFFFF")
184
 
185
- # Instantiate the custom CreditsPanel component with default values
186
  panel = CreditsPanel(
187
  credits=credits_list,
188
  licenses=license_paths,
@@ -203,10 +179,14 @@ with gr.Blocks(theme=gr.themes.Ocean(), title="CreditsPanel Demo") as demo:
203
  logo_height="100px",
204
  scroll_background_color="#000000",
205
  scroll_title_color="#FFFFFF",
206
- scroll_name_color="#FFFFFF",
 
 
 
 
 
207
  )
208
 
209
- # List of all input components that should trigger a panel update
210
  inputs = [
211
  effect_radio,
212
  speed_slider,
@@ -216,35 +196,31 @@ with gr.Blocks(theme=gr.themes.Ocean(), title="CreditsPanel Demo") as demo:
216
  sidebar_position_radio,
217
  show_logo_checkbox,
218
  show_licenses_checkbox,
219
- show_credits_checkbox,
220
  logo_position_radio,
221
  logo_sizing_radio,
222
  logo_width_input,
223
  logo_height_input,
224
  scroll_background_color,
225
  scroll_title_color,
226
- scroll_name_color
 
 
 
 
 
227
  ]
228
 
229
- # --- 4. EVENT BINDING ---
230
- # Connect the UI controls to the handler functions.
231
-
232
- # Special event: changing the effect also updates speed and font size sliders
233
- effect_radio.change(
234
- fn=update_ui_on_effect_change,
235
- inputs=effect_radio,
236
- outputs=[speed_slider, font_size_slider]
237
  )
 
238
 
239
- # General event: any change in an input control updates the main panel
240
  for input_component in inputs:
241
- input_component.change(
242
- fn=update_panel,
243
- inputs=inputs,
244
- outputs=panel
245
- )
246
 
247
- # --- 5. APP LAUNCH ---
248
  if __name__ == "__main__":
249
  setup_demo_files()
250
  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
+ # --- UPDATED: 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
 
 
 
 
51
  def update_panel(
52
  effect: str,
53
  speed: float,
 
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(
 
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
 
 
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,
 
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,
 
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()
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}, '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
 
@@ -38,78 +38,56 @@ pip install gradio_creditspanel
38
  ## Usage
39
 
40
  ```python
41
- \"\"\"
42
- app.py
43
-
44
- This script serves as an interactive demonstration for the custom Gradio component `CreditsPanel`.
45
- It showcases all available features of the component, allowing users to dynamically adjust
46
- properties like animation effects, speed, layout, and styling. The app also demonstrates
47
- how to handle file dependencies (logo, licenses) in a portable way.
48
- \"\"\"
49
 
50
  import gradio as gr
51
  from gradio_creditspanel import CreditsPanel
52
  import os
53
 
54
- # --- 1. SETUP & DATA PREPARATION ---
55
- # This section prepares all necessary assets and data for the demo.
56
- # It ensures the demo runs out-of-the-box without manual setup.
57
-
58
  def setup_demo_files():
59
- \"\"\"
60
- Creates necessary directories and dummy files (logo, licenses) for the demo.
61
- This makes the application self-contained and easy to run.
62
- \"\"\"
63
- # Create dummy license files
64
  os.makedirs("LICENSES", exist_ok=True)
65
  if not os.path.exists("LICENSES/Apache.txt"):
66
  with open("LICENSES/Apache.txt", "w") as f:
67
- f.write("Apache License\nVersion 2.0, January 2004\nhttp://www.apache.org/licenses/...")
68
  if not os.path.exists("LICENSES/MIT.txt"):
69
  with open("LICENSES/MIT.txt", "w") as f:
70
- f.write("MIT License\nCopyright (c) 2025 Author\nPermission is hereby granted...")
71
-
72
- # Create a placeholder logo if it doesn't exist
73
  os.makedirs("assets", exist_ok=True)
74
  if not os.path.exists("./assets/logo.webp"):
75
  with open("./assets/logo.webp", "w") as f:
76
  f.write("Placeholder WebP logo")
77
 
78
- # Initial data for the credits roll
79
  credits_list = [
 
80
  {"title": "Project Manager", "name": "Emma Thompson"},
 
 
 
81
  {"title": "Lead Developer", "name": "John Doe"},
82
  {"title": "Senior Backend Engineer", "name": "Michael Chen"},
83
  {"title": "Frontend Developer", "name": "Sarah Johnson"},
84
  {"title": "UI/UX Designer", "name": "Jane Smith"},
85
  {"title": "Database Architect", "name": "Alex Ray"},
 
 
86
  {"title": "DevOps Engineer", "name": "Liam Patel"},
87
  {"title": "Quality Assurance Lead", "name": "Sam Wilson"},
88
  {"title": "Test Automation Engineer", "name": "Olivia Brown"},
89
- {"title": "Security Analyst", "name": "David Kim"},
90
- {"title": "Data Scientist", "name": "Sophie Martinez"},
91
- {"title": "Machine Learning Engineer", "name": "Ethan Lee"},
92
- {"title": "API Developer", "name": "Isabella Garcia"},
93
- {"title": "Technical Writer", "name": "Noah Davis"},
94
- {"title": "Scrum Master", "name": "Ava Rodriguez"},
95
  ]
96
 
97
- # Paths to license files
98
  license_paths = {
99
  "Gradio Framework": "./LICENSES/Apache.txt",
100
  "This Component": "./LICENSES/MIT.txt"
101
  }
102
 
103
- # Default animation speeds for each effect to provide a good user experience
104
  DEFAULT_SPEEDS = {
105
  "scroll": 40.0,
106
- "starwars": 80.0,
107
  "matrix": 40.0
108
  }
109
 
110
- # --- 2. GRADIO EVENT HANDLER FUNCTIONS ---
111
- # These functions define the application's interactive logic.
112
-
113
  def update_panel(
114
  effect: str,
115
  speed: float,
@@ -120,53 +98,57 @@ def update_panel(
120
  show_logo: bool,
121
  show_licenses: bool,
122
  show_credits: bool,
123
- logo_position: str,
124
  logo_sizing: str,
125
  logo_width: str | None,
126
  logo_height: str | None,
127
  scroll_background_color: str | None,
128
- scroll_title_color: str | None,
129
- scroll_name_color: str | None
 
 
 
 
 
130
  ) -> dict:
131
- \"\"\"
132
- Callback function that updates all properties of the CreditsPanel component.
133
- It takes the current state of all UI controls and returns a gr.update() dictionary.
134
- \"\"\"
135
  return gr.update(
136
  visible=True,
137
- effect=effect,
138
- speed=speed,
139
  base_font_size=base_font_size,
140
- intro_title=intro_title,
141
  intro_subtitle=intro_subtitle,
142
- sidebar_position=sidebar_position,
143
  show_logo=show_logo,
144
- show_licenses=show_licenses,
145
  show_credits=show_credits,
146
- logo_position=logo_position,
147
  logo_sizing=logo_sizing,
148
- logo_width=logo_width,
149
  logo_height=logo_height,
150
  scroll_background_color=scroll_background_color,
151
  scroll_title_color=scroll_title_color,
152
- scroll_name_color=scroll_name_color,
153
- value=credits_list # The list of credits to display
 
 
 
 
 
154
  )
155
 
156
  def update_ui_on_effect_change(effect: str) -> tuple[float, float]:
157
- \"\"\"
158
- Updates the speed and font size sliders to sensible defaults when the
159
- animation effect is changed.
160
- \"\"\"
161
  font_size = 1.5
162
  if effect == "starwars":
163
- font_size = 6.0 # Star Wars effect looks better with a larger font
164
-
165
  speed = DEFAULT_SPEEDS.get(effect, 40.0)
166
  return speed, font_size
167
 
168
- # --- 3. GRADIO UI DEFINITION ---
169
- # This section constructs the user interface using gr.Blocks.
 
170
 
171
  with gr.Blocks(theme=gr.themes.Ocean(), title="CreditsPanel Demo") as demo:
172
  gr.Markdown(
@@ -178,42 +160,37 @@ with gr.Blocks(theme=gr.themes.Ocean(), title="CreditsPanel Demo") as demo:
178
 
179
  with gr.Sidebar(position="right"):
180
  gr.Markdown("### Effects Settings")
181
- effect_radio = gr.Radio(
182
- ["scroll", "starwars", "matrix"], label="Animation Effect", value="scroll",
183
- info="Select the visual style for the credits."
 
 
 
 
 
184
  )
185
- speed_slider = gr.Slider(
186
- minimum=5.0, maximum=100.0, step=1.0, value=DEFAULT_SPEEDS["scroll"],
187
- label="Animation Speed (seconds)", info="Duration of one animation cycle."
 
188
  )
189
- font_size_slider = gr.Slider(
190
- minimum=1.0, maximum=10.0, step=0.1, value=1.5,
191
- label="Base Font Size (rem)", info="Controls the base font size."
192
- )
193
-
194
  gr.Markdown("### Intro Text")
195
- intro_title_input = gr.Textbox(
196
- label="Intro Title", value="Gradio", info="Main title for the intro sequence."
197
- )
198
- intro_subtitle_input = gr.Textbox(
199
- label="Intro Subtitle", value="The best UI framework", info="Subtitle for the intro sequence."
200
- )
201
 
202
  gr.Markdown("### Layout & Visibility")
203
- sidebar_position_radio = gr.Radio(
204
- ["right", "bottom"], label="Sidebar Position", value="right",
205
- info="Place the licenses sidebar on the right or bottom."
206
- )
207
  show_logo_checkbox = gr.Checkbox(label="Show Logo", value=True)
208
  show_licenses_checkbox = gr.Checkbox(label="Show Licenses", value=True)
209
  show_credits_checkbox = gr.Checkbox(label="Show Credits", value=True)
 
210
  gr.Markdown("### Logo Customization")
211
- logo_position_radio = gr.Radio(
212
- ["left", "center", "right"], label="Logo Position", value="center"
213
- )
214
- logo_sizing_radio = gr.Radio(
215
- ["stretch", "crop", "resize"], label="Logo Sizing", value="resize"
216
- )
217
  logo_width_input = gr.Textbox(label="Logo Width", value="200px")
218
  logo_height_input = gr.Textbox(label="Logo Height", value="100px")
219
 
@@ -222,7 +199,6 @@ with gr.Blocks(theme=gr.themes.Ocean(), title="CreditsPanel Demo") as demo:
222
  scroll_title_color = gr.ColorPicker(label="Title Color", value="#FFFFFF")
223
  scroll_name_color = gr.ColorPicker(label="Name Color", value="#FFFFFF")
224
 
225
- # Instantiate the custom CreditsPanel component with default values
226
  panel = CreditsPanel(
227
  credits=credits_list,
228
  licenses=license_paths,
@@ -243,10 +219,14 @@ with gr.Blocks(theme=gr.themes.Ocean(), title="CreditsPanel Demo") as demo:
243
  logo_height="100px",
244
  scroll_background_color="#000000",
245
  scroll_title_color="#FFFFFF",
246
- scroll_name_color="#FFFFFF",
 
 
 
 
 
247
  )
248
 
249
- # List of all input components that should trigger a panel update
250
  inputs = [
251
  effect_radio,
252
  speed_slider,
@@ -256,35 +236,31 @@ with gr.Blocks(theme=gr.themes.Ocean(), title="CreditsPanel Demo") as demo:
256
  sidebar_position_radio,
257
  show_logo_checkbox,
258
  show_licenses_checkbox,
259
- show_credits_checkbox,
260
  logo_position_radio,
261
  logo_sizing_radio,
262
  logo_width_input,
263
  logo_height_input,
264
  scroll_background_color,
265
  scroll_title_color,
266
- scroll_name_color
 
 
 
 
 
267
  ]
268
 
269
- # --- 4. EVENT BINDING ---
270
- # Connect the UI controls to the handler functions.
271
-
272
- # Special event: changing the effect also updates speed and font size sliders
273
- effect_radio.change(
274
- fn=update_ui_on_effect_change,
275
- inputs=effect_radio,
276
- outputs=[speed_slider, font_size_slider]
277
  )
 
278
 
279
- # General event: any change in an input control updates the main panel
280
  for input_component in inputs:
281
- input_component.change(
282
- fn=update_panel,
283
- inputs=inputs,
284
- outputs=panel
285
- )
286
 
287
- # --- 5. APP LAUNCH ---
288
  if __name__ == "__main__":
289
  setup_demo_files()
290
  demo.launch()
 
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
 
 
38
  ## Usage
39
 
40
  ```python
41
+ # app.py
 
 
 
 
 
 
 
42
 
43
  import gradio as gr
44
  from gradio_creditspanel import CreditsPanel
45
  import os
46
 
 
 
 
 
47
  def setup_demo_files():
48
+ \"\"\"Creates necessary directories and dummy files for the demo.\"\"\"
 
 
 
 
49
  os.makedirs("LICENSES", exist_ok=True)
50
  if not os.path.exists("LICENSES/Apache.txt"):
51
  with open("LICENSES/Apache.txt", "w") as f:
52
+ f.write("Apache License\nVersion 2.0, January 2004...")
53
  if not os.path.exists("LICENSES/MIT.txt"):
54
  with open("LICENSES/MIT.txt", "w") as f:
55
+ f.write("MIT License\nCopyright (c) 2025 Author...")
 
 
56
  os.makedirs("assets", exist_ok=True)
57
  if not os.path.exists("./assets/logo.webp"):
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"},
65
+ {"title": "Scrum Master", "name": "Ava Rodriguez"},
66
+
67
+ {"section_title": "Development Team"},
68
  {"title": "Lead Developer", "name": "John Doe"},
69
  {"title": "Senior Backend Engineer", "name": "Michael Chen"},
70
  {"title": "Frontend Developer", "name": "Sarah Johnson"},
71
  {"title": "UI/UX Designer", "name": "Jane Smith"},
72
  {"title": "Database Architect", "name": "Alex Ray"},
73
+
74
+ {"section_title": "Quality & Operations"},
75
  {"title": "DevOps Engineer", "name": "Liam Patel"},
76
  {"title": "Quality Assurance Lead", "name": "Sam Wilson"},
77
  {"title": "Test Automation Engineer", "name": "Olivia Brown"},
 
 
 
 
 
 
78
  ]
79
 
 
80
  license_paths = {
81
  "Gradio Framework": "./LICENSES/Apache.txt",
82
  "This Component": "./LICENSES/MIT.txt"
83
  }
84
 
 
85
  DEFAULT_SPEEDS = {
86
  "scroll": 40.0,
87
+ "starwars": 70.0,
88
  "matrix": 40.0
89
  }
90
 
 
 
 
91
  def update_panel(
92
  effect: str,
93
  speed: float,
 
98
  show_logo: bool,
99
  show_licenses: bool,
100
  show_credits: bool,
101
+ logo_position: str,
102
  logo_sizing: str,
103
  logo_width: str | None,
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.\"\"\"
 
 
 
143
  font_size = 1.5
144
  if effect == "starwars":
145
+ font_size = 3.8
 
146
  speed = DEFAULT_SPEEDS.get(effect, 40.0)
147
  return speed, font_size
148
 
149
+ def toggle_swap_checkbox_visibility(layout: str) -> dict:
150
+ \"\"\"Show the swap checkbox only for the two-column layout.\"\"\"
151
+ return gr.update(visible=(layout == 'two-column'))
152
 
153
  with gr.Blocks(theme=gr.themes.Ocean(), title="CreditsPanel Demo") as demo:
154
  gr.Markdown(
 
160
 
161
  with gr.Sidebar(position="right"):
162
  gr.Markdown("### Effects Settings")
163
+ effect_radio = gr.Radio(["scroll", "starwars", "matrix"], label="Animation Effect", value="scroll")
164
+ speed_slider = gr.Slider(minimum=5.0, maximum=100.0, step=1.0, value=DEFAULT_SPEEDS["scroll"], label="Animation Speed")
165
+ font_size_slider = gr.Slider(minimum=1.0, maximum=10.0, step=0.1, value=1.5, label="Base Font Size")
166
+
167
+ gr.Markdown("### Credits Layout Settings")
168
+ layout_style_radio = gr.Radio(
169
+ ["stacked", "two-column"], label="Layout Style", value="stacked",
170
+ info="How to display titles and names."
171
  )
172
+ swap_sizes_checkbox = gr.Checkbox(
173
+ label="Swap Title/Name Font Sizes", value=False,
174
+ info="Emphasize name over title in two-column layout.",
175
+ visible=False
176
  )
177
+ title_uppercase_checkbox = gr.Checkbox(label="Title Uppercase", value=False)
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")
 
 
 
 
184
 
185
  gr.Markdown("### Layout & Visibility")
186
+ sidebar_position_radio = gr.Radio(["right", "bottom"], label="Sidebar Position", value="right")
 
 
 
187
  show_logo_checkbox = gr.Checkbox(label="Show Logo", value=True)
188
  show_licenses_checkbox = gr.Checkbox(label="Show Licenses", value=True)
189
  show_credits_checkbox = gr.Checkbox(label="Show Credits", value=True)
190
+
191
  gr.Markdown("### Logo Customization")
192
+ logo_position_radio = gr.Radio(["left", "center", "right"], label="Logo Position", value="center")
193
+ logo_sizing_radio = gr.Radio(["stretch", "crop", "resize"], label="Logo Sizing", value="resize")
 
 
 
 
194
  logo_width_input = gr.Textbox(label="Logo Width", value="200px")
195
  logo_height_input = gr.Textbox(label="Logo Height", value="100px")
196
 
 
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,
 
219
  logo_height="100px",
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 = [
231
  effect_radio,
232
  speed_slider,
 
236
  sidebar_position_radio,
237
  show_logo_checkbox,
238
  show_licenses_checkbox,
239
+ show_credits_checkbox,
240
  logo_position_radio,
241
  logo_sizing_radio,
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,
257
+ outputs=swap_sizes_checkbox
 
 
 
 
258
  )
259
+ effect_radio.change(fn=update_ui_on_effect_change, inputs=effect_radio, outputs=[speed_slider, font_size_slider])
260
 
 
261
  for input_component in inputs:
262
+ input_component.change(fn=update_panel, inputs=inputs, outputs=panel)
 
 
 
 
263
 
 
264
  if __name__ == "__main__":
265
  setup_demo_files()
266
  demo.launch()
src/frontend/Index.svelte CHANGED
@@ -11,7 +11,7 @@
11
  /**
12
  * Props for the Index component.
13
  * @typedef {Object} Value
14
- * @property {Array<{title: string, name: string}>} credits - List of credits with title and name.
15
  * @property {Record<string, string>} licenses - License names and content.
16
  * @property {"scroll" | "starwars" | "matrix"} effect - Credits display effect.
17
  * @property {number} speed - Animation speed in seconds.
@@ -32,6 +32,11 @@
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).
 
 
 
 
 
35
  */
36
  export let value: Value | null = null;
37
  export let elem_id = "";
@@ -48,11 +53,9 @@
48
  // Default value if `value` is null
49
  $: effectiveValue = value || {
50
  credits: [
 
51
  { title: "Lead Developer", name: "John Doe" },
52
  { title: "UI/UX Design", name: "Jane Smith" },
53
- { title: "Backend Engineering", name: "Alex Ray" },
54
- { title: "Component Concept", name: "Your Name" },
55
- { title: "Quality Assurance", name: "Sam Wilson" },
56
  ],
57
  licenses: {
58
  "Gradio Framework": "Apache License placeholder",
@@ -74,7 +77,12 @@
74
  logo_height: null,
75
  scroll_background_color: null,
76
  scroll_title_color: null,
77
- scroll_name_color: null
 
 
 
 
 
78
  };
79
 
80
  // Tracks selected license for display
@@ -200,7 +208,12 @@
200
  intro_subtitle={effectiveValue.intro_subtitle}
201
  background_color={effectiveValue.scroll_background_color}
202
  title_color={effectiveValue.scroll_title_color}
203
- name_color={effectiveValue.scroll_name_color}
 
 
 
 
 
204
  />
205
  {:else if effectiveValue.effect === "starwars"}
206
  <StarWarsEffect
@@ -208,7 +221,12 @@
208
  speed={effectiveValue.speed}
209
  base_font_size={effectiveValue.base_font_size}
210
  intro_title={effectiveValue.intro_title}
211
- intro_subtitle={effectiveValue.intro_subtitle}
 
 
 
 
 
212
  />
213
  {:else if effectiveValue.effect === "matrix"}
214
  <MatrixEffect
@@ -216,7 +234,12 @@
216
  speed={effectiveValue.speed}
217
  base_font_size={effectiveValue.base_font_size}
218
  intro_title={effectiveValue.intro_title}
219
- intro_subtitle={effectiveValue.intro_subtitle}
 
 
 
 
 
220
  />
221
  {/if}
222
  </div>
@@ -251,6 +274,7 @@
251
  {/key}
252
  {/if}
253
  </Block>
 
254
  <svelte:head>
255
  {#if effectiveValue.sidebar_position === "bottom"}
256
  <!-- Bottom sidebar styles -->
 
11
  /**
12
  * Props for the Index component.
13
  * @typedef {Object} Value
14
+ * @property {Array<{title?: string, name?: string, section_title?: string}>} credits - List of credits with title and name.
15
  * @property {Record<string, string>} licenses - License names and content.
16
  * @property {"scroll" | "starwars" | "matrix"} effect - Credits display effect.
17
  * @property {number} speed - Animation speed in seconds.
 
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).
35
+ * @property {"stacked" | "two-column"} layout_style - Credit layout style.
36
+ * @property {boolean} title_uppercase - Transform title to uppercase.
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 = "";
 
53
  // Default value if `value` is null
54
  $: effectiveValue = value || {
55
  credits: [
56
+ { section_title: "Default Team"},
57
  { title: "Lead Developer", name: "John Doe" },
58
  { title: "UI/UX Design", name: "Jane Smith" },
 
 
 
59
  ],
60
  licenses: {
61
  "Gradio Framework": "Apache License placeholder",
 
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
 
208
  intro_subtitle={effectiveValue.intro_subtitle}
209
  background_color={effectiveValue.scroll_background_color}
210
  title_color={effectiveValue.scroll_title_color}
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
 
221
  speed={effectiveValue.speed}
222
  base_font_size={effectiveValue.base_font_size}
223
  intro_title={effectiveValue.intro_title}
224
+ intro_subtitle={effectiveValue.intro_subtitle}
225
+ layout_style={effectiveValue.layout_style}
226
+ title_uppercase={effectiveValue.title_uppercase}
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
 
234
  speed={effectiveValue.speed}
235
  base_font_size={effectiveValue.base_font_size}
236
  intro_title={effectiveValue.intro_title}
237
+ intro_subtitle={effectiveValue.intro_subtitle}
238
+ layout_style={effectiveValue.layout_style}
239
+ title_uppercase={effectiveValue.title_uppercase}
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>
 
274
  {/key}
275
  {/if}
276
  </Block>
277
+
278
  <svelte:head>
279
  {#if effectiveValue.sidebar_position === "bottom"}
280
  <!-- Bottom sidebar styles -->
src/frontend/shared/MatrixEffect.svelte CHANGED
@@ -1,192 +1,308 @@
1
  <script lang="ts">
2
- import { onMount, onDestroy } from 'svelte';
3
 
4
- /**
5
- * Props for the MatrixEffect component.
6
- * @typedef {Object} Props
7
- * @property {Array<{title: string, name: string}>} credits - List of credits with title and name.
8
- * @property {number} speed - Animation speed in seconds (default: 20).
9
- * @property {number} base_font_size - Base font size in em (default: 1.0).
10
- * @property {string | null} intro_title - Optional intro title.
11
- * @property {string | null} intro_subtitle - Optional intro subtitle.
12
- */
13
- export let credits: Props['credits'];
14
- export let speed: number = 20;
15
- export let base_font_size: number = 1.0;
16
- export let intro_title: string | null = null;
17
- export let intro_subtitle: string | null = null;
 
 
 
 
 
 
 
 
 
 
18
 
19
- // Combines intro and credits for display
20
- $: display_items = (() => {
21
- const items = [];
22
- if (intro_title || intro_subtitle) {
23
- items.push({
24
- title: intro_title || '',
25
- name: intro_subtitle || '',
26
- is_intro: true
27
- });
28
- }
29
- return [...items, ...credits.map(c => ({ ...c, is_intro: false }))];
30
- })();
31
 
32
- // Reactive font size styles
33
- $: title_style = (is_intro: boolean) => `font-size: ${is_intro ? base_font_size * 1.2 : base_font_size * 0.8}em;`;
34
- $: name_style = (is_intro: boolean) => `font-size: ${is_intro ? base_font_size * 1.5 : base_font_size}em;`;
 
 
 
35
 
36
- // Canvas setup for Matrix effect
37
- let canvas: HTMLCanvasElement;
38
- let ctx: CanvasRenderingContext2D;
39
- let contentElement: HTMLElement | null;
40
- const fontSize = 16;
41
- const characters = 'アァカサタナハマヤャラワガザダバパイィキシチニヒミリヰギジヂビピウゥクスツヌフムユュルグズブヅプエェケセテネヘメレヱゲゼデベペオォコソトノホモヨョロヲゴゾドボポヴッン01';
42
- let columns: number;
43
- let drops: number[] = [];
44
- let animationFrameId: number;
 
45
 
46
- // Initialize canvas and drops
47
- function setup() {
48
- if (!canvas) return;
49
- const parent = canvas.parentElement;
50
- if (parent) {
51
- canvas.width = parent.clientWidth;
52
- canvas.height = parent.clientHeight;
53
- }
54
- ctx = canvas.getContext('2d')!;
55
- columns = Math.floor(canvas.width / fontSize);
56
- drops = Array(columns).fill(1);
57
  }
 
 
 
 
58
 
59
- // Draw Matrix falling characters
60
- function drawMatrix() {
61
- if (!ctx) return;
62
- ctx.fillStyle = 'rgba(0, 0, 0, 0.05)';
63
- ctx.fillRect(0, 0, canvas.width, canvas.height);
64
- ctx.fillStyle = '#0F0';
65
- ctx.font = `${fontSize}px monospace`;
66
- for (let i = 0; i < drops.length; i++) {
67
- const text = characters.charAt(Math.floor(Math.random() * characters.length));
68
- ctx.fillText(text, i * fontSize, drops[i] * fontSize);
69
- if (drops[i] * fontSize > canvas.height && Math.random() > 0.975) {
70
- drops[i] = 0;
71
- }
72
- drops[i]++;
73
- }
74
- animationFrameId = requestAnimationFrame(drawMatrix);
75
  }
 
 
76
 
77
- // Reset credits animation
78
- function resetCreditsAnimation() {
79
- if (contentElement) {
80
- contentElement.style.animation = 'none';
81
- void contentElement.offsetHeight; // Trigger reflow
82
- contentElement.style.animation = '';
83
- }
84
  }
 
85
 
86
- // Setup canvas and animation on mount
87
- onMount(() => {
88
- setup();
89
- drawMatrix();
90
- resetCreditsAnimation();
91
- const resizeObserver = new ResizeObserver(() => {
92
- cancelAnimationFrame(animationFrameId);
93
- setup();
94
- drawMatrix();
95
- });
96
- if (canvas.parentElement) {
97
- resizeObserver.observe(canvas.parentElement);
98
- }
99
- return () => {
100
- cancelAnimationFrame(animationFrameId);
101
- if (canvas.parentElement) {
102
- resizeObserver.unobserve(canvas.parentElement);
103
- }
104
- };
105
  });
 
 
 
 
 
 
 
 
 
 
106
 
107
- // Reset animation on prop changes
108
- $: credits, speed, intro_title, intro_subtitle, resetCreditsAnimation();
 
 
 
 
 
109
 
110
- // Cleanup on destroy
111
- onDestroy(() => {
112
- contentElement = null;
113
- });
114
  </script>
115
 
116
  <div class="matrix-container">
117
- <canvas bind:this={canvas}></canvas>
118
- <div class="credits-scroll-overlay">
119
- <div class="credits-content" bind:this={contentElement} style="--animation-duration: {speed}s;">
120
- {#each display_items as item}
121
- <div class="credit-block" class:intro-block={item.is_intro}>
122
- <div style={title_style(item.is_intro)} class="title">{item.title}</div>
123
- {#if item.name}
124
- <div style={name_style(item.is_intro)} class="name">{item.name}</div>
125
- {/if}
126
- </div>
127
- {/each}
128
- </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
129
  </div>
 
130
  </div>
131
 
132
  <style>
133
- /* Container for Matrix effect */
134
- .matrix-container {
135
- width: 100%;
136
- height: 100%;
137
- position: relative;
138
- overflow: hidden;
139
- }
140
- /* Canvas for falling characters */
141
- canvas {
142
- display: block;
143
- position: absolute;
144
- top: 0;
145
- left: 0;
146
- width: 100%;
147
- height: 100%;
148
- z-index: 1;
149
- }
150
- /* Overlay for scrolling credits */
151
- .credits-scroll-overlay {
152
- position: absolute;
153
- top: 0;
154
- left: 0;
155
- width: 100%;
156
- height: 100%;
157
- z-index: 2;
158
- color: #fff;
159
- font-family: monospace;
160
- text-align: center;
161
- -webkit-mask-image: linear-gradient(transparent, black 20%, black 80%, transparent);
162
- mask-image: linear-gradient(transparent, black 20%, black 80%, transparent);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
163
  }
164
- /* Scrolling credits container */
165
- .credits-content {
166
- position: absolute;
167
- width: 100%;
168
- bottom: 0;
169
- transform: translateY(100%);
170
- animation: scroll-from-bottom var(--animation-duration) linear infinite;
171
  }
172
- @keyframes scroll-from-bottom {
173
- from { transform: translateY(100%); }
174
- to { transform: translateY(-100%); }
175
- }
176
- /* Intro block spacing */
177
- .credit-block.intro-block { margin-bottom: 5rem; }
178
- /* Credit block spacing */
179
- .credit-block { margin-bottom: 2.5em; }
180
- /* Title styling */
181
- .title {
182
- color: #0F0;
183
- text-transform: uppercase;
184
- opacity: 0.8;
185
- }
186
- /* Name styling */
187
- .name {
188
- font-weight: bold;
189
- color: #5F5;
190
- text-shadow: 0 0 5px #0F0;
191
- }
192
- </style>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  <script lang="ts">
2
+ import { onMount, onDestroy } from "svelte";
3
 
4
+ /**
5
+ * Props for the MatrixEffect component.
6
+ * @typedef {Object} Props
7
+ * @property {Array<{title: string, name: string}>} credits - List of credits with title and name.
8
+ * @property {number} speed - Animation speed in seconds (default: 20).
9
+ * @property {number} base_font_size - Base font size in em (default: 1.0).
10
+ * @property {string | null} intro_title - Optional intro title.
11
+ * @property {string | null} intro_subtitle - Optional intro subtitle.
12
+ * @property {"stacked" | "two-column"} layout_style - Layout for credits.
13
+ * @property {boolean} title_uppercase - Transform title to uppercase.
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;
20
+ export let base_font_size: number = 1.0;
21
+ export let intro_title: string | null = null;
22
+ export let intro_subtitle: string | null = null;
23
+ export let layout_style: "stacked" | "two-column" = "stacked";
24
+ export let title_uppercase: boolean = false;
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 = (() => {
31
+ const items = [];
32
+ if (intro_title || intro_subtitle) {
33
+ items.push({
34
+ title: intro_title || "",
35
+ name: intro_subtitle || "",
36
+ is_intro: true,
37
+ });
38
+ }
39
+ return [...items, ...credits.map((c) => ({ ...c, is_intro: false }))];
40
+ })();
41
 
42
+ // Reactive font size styles
43
+ $: title_style = (is_intro: boolean) =>
44
+ `font-size: ${is_intro ? base_font_size * 1.5 : base_font_size}em;`;
45
+ $: name_style = (is_intro: boolean) =>
46
+ `font-size: ${is_intro ? base_font_size * 0.9 : base_font_size * 0.8}em;`;
47
+ $: section_title_style = `font-size: ${base_font_size * 1.2}em;`;
48
 
49
+ // Canvas setup for Matrix effect
50
+ let canvas: HTMLCanvasElement;
51
+ let ctx: CanvasRenderingContext2D;
52
+ let contentElement: HTMLElement | null;
53
+ const fontSize = 16;
54
+ const characters =
55
+ "アァカサタナハマヤャラワガザダバパイィキシチニヒミリヰギジヂビピウゥクスツヌフムユュルグズブヅプエェケセテネヘメレヱゲゼデベペオォコソトノホモヨョロヲゴゾドボポヴッン01";
56
+ let columns: number;
57
+ let drops: number[] = [];
58
+ let animationFrameId: number;
59
 
60
+ // Initialize canvas and drops
61
+ function setup() {
62
+ if (!canvas) return;
63
+ const parent = canvas.parentElement;
64
+ if (parent) {
65
+ canvas.width = parent.clientWidth;
66
+ canvas.height = parent.clientHeight;
 
 
 
 
67
  }
68
+ ctx = canvas.getContext("2d")!;
69
+ columns = Math.floor(canvas.width / fontSize);
70
+ drops = Array(columns).fill(1);
71
+ }
72
 
73
+ // Draw Matrix falling characters
74
+ function drawMatrix() {
75
+ if (!ctx) return;
76
+ ctx.fillStyle = "rgba(0, 0, 0, 0.05)";
77
+ ctx.fillRect(0, 0, canvas.width, canvas.height);
78
+ ctx.fillStyle = "#0F0";
79
+ ctx.font = `${fontSize}px monospace`;
80
+ for (let i = 0; i < drops.length; i++) {
81
+ const text = characters.charAt(
82
+ Math.floor(Math.random() * characters.length)
83
+ );
84
+ ctx.fillText(text, i * fontSize, drops[i] * fontSize);
85
+ if (drops[i] * fontSize > canvas.height && Math.random() > 0.975) {
86
+ drops[i] = 0;
87
+ }
88
+ drops[i]++;
89
  }
90
+ animationFrameId = requestAnimationFrame(drawMatrix);
91
+ }
92
 
93
+ // Reset credits animation
94
+ function resetCreditsAnimation() {
95
+ if (contentElement) {
96
+ contentElement.style.animation = "none";
97
+ void contentElement.offsetHeight; // Trigger reflow
98
+ contentElement.style.animation = "";
 
99
  }
100
+ }
101
 
102
+ // Setup canvas and animation on mount
103
+ onMount(() => {
104
+ setup();
105
+ drawMatrix();
106
+ resetCreditsAnimation();
107
+ const resizeObserver = new ResizeObserver(() => {
108
+ cancelAnimationFrame(animationFrameId);
109
+ setup();
110
+ drawMatrix();
 
 
 
 
 
 
 
 
 
 
111
  });
112
+ if (canvas.parentElement) {
113
+ resizeObserver.observe(canvas.parentElement);
114
+ }
115
+ return () => {
116
+ cancelAnimationFrame(animationFrameId);
117
+ if (canvas.parentElement) {
118
+ resizeObserver.unobserve(canvas.parentElement);
119
+ }
120
+ };
121
+ });
122
 
123
+ // Reset animation on prop changes
124
+ $: credits,
125
+ speed,
126
+ intro_title,
127
+ intro_subtitle,
128
+ layout_style,
129
+ resetCreditsAnimation();
130
 
131
+ // Cleanup on destroy
132
+ onDestroy(() => {
133
+ contentElement = null;
134
+ });
135
  </script>
136
 
137
  <div class="matrix-container">
138
+ <canvas bind:this={canvas}></canvas>
139
+ <div class="credits-scroll-overlay">
140
+ <div
141
+ class="credits-content"
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}
148
+ <div
149
+ class="section-title"
150
+ style={section_title_style}
151
+ class:uppercase={section_title_uppercase}
152
+ >
153
+ {item.section_title}
154
+ </div>
155
+ <!-- Render Credit or Intro -->
156
+ {:else if layout_style === "two-column" && !item.is_intro}
157
+ <!-- Two-Column Layout -->
158
+ <div class="credit-two-column">
159
+ <div
160
+ class="title"
161
+ style={swap_font_sizes_on_two_column
162
+ ? name_style(false)
163
+ : title_style(false)}
164
+ class:uppercase={title_uppercase}
165
+ >
166
+ {item.title}
167
+ </div>
168
+ <div
169
+ class="name"
170
+ style={swap_font_sizes_on_two_column
171
+ ? title_style(false)
172
+ : name_style(false)}
173
+ class:uppercase={name_uppercase}
174
+ >
175
+ {item.name}
176
+ </div>
177
+ </div>
178
+ {:else}
179
+ <!-- Stacked Layout -->
180
+ <div class="credit-block" class:intro-block={item.is_intro}>
181
+ <div
182
+ style={title_style(item.is_intro)}
183
+ class="title"
184
+ class:uppercase={title_uppercase && !item.is_intro}
185
+ >
186
+ {item.title}
187
+ </div>
188
+ {#if item.name}
189
+ <div
190
+ style={name_style(item.is_intro)}
191
+ class="name"
192
+ class:uppercase={name_uppercase && !item.is_intro}
193
+ >
194
+ {item.name}
195
+ </div>
196
+ {/if}
197
+ </div>
198
+ {/if}
199
+ {/each}
200
  </div>
201
+ </div>
202
  </div>
203
 
204
  <style>
205
+ /* Container for Matrix effect */
206
+ .matrix-container {
207
+ width: 100%;
208
+ height: 100%;
209
+ position: relative;
210
+ overflow: hidden;
211
+ }
212
+ /* Canvas for falling characters */
213
+ canvas {
214
+ display: block;
215
+ position: absolute;
216
+ top: 0;
217
+ left: 0;
218
+ width: 100%;
219
+ height: 100%;
220
+ z-index: 1;
221
+ }
222
+ /* Overlay for scrolling credits */
223
+ .credits-scroll-overlay {
224
+ position: absolute;
225
+ top: 0;
226
+ left: 0;
227
+ width: 100%;
228
+ height: 100%;
229
+ z-index: 2;
230
+ color: #fff;
231
+ font-family: monospace;
232
+ text-align: center;
233
+ -webkit-mask-image: linear-gradient(
234
+ transparent,
235
+ black 20%,
236
+ black 80%,
237
+ transparent
238
+ );
239
+ mask-image: linear-gradient(transparent, black 20%, black 80%, transparent);
240
+ }
241
+ /* Scrolling credits container */
242
+ .credits-content {
243
+ position: absolute;
244
+ width: 100%;
245
+ bottom: 0;
246
+ transform: translateY(100%);
247
+ animation: scroll-from-bottom var(--animation-duration) linear infinite;
248
+ padding: 0 2rem;
249
+ box-sizing: border-box;
250
+ }
251
+ @keyframes scroll-from-bottom {
252
+ from {
253
+ transform: translateY(100%);
254
  }
255
+ to {
256
+ transform: translateY(-100%);
 
 
 
 
 
257
  }
258
+ }
259
+ .uppercase {
260
+ text-transform: uppercase;
261
+ }
262
+
263
+ .section-title {
264
+ margin-top: 4rem;
265
+ margin-bottom: 2.5rem;
266
+ font-weight: bold;
267
+ color: #5f5;
268
+ text-shadow: 0 0 8px #0f0;
269
+ }
270
+ /* Intro block spacing */
271
+ .credit-block.intro-block {
272
+ margin-bottom: 5rem;
273
+ }
274
+ /* Credit block spacing */
275
+ .credit-block {
276
+ margin-bottom: 2.5em;
277
+ }
278
+ /* Title styling */
279
+ .title {
280
+ color: #0f0;
281
+ opacity: 0.8;
282
+ }
283
+ /* Name styling */
284
+ .name {
285
+ font-weight: bold;
286
+ color: #5f5;
287
+ text-shadow: 0 0 5px #0f0;
288
+ }
289
+ .credit-two-column {
290
+ display: flex;
291
+ justify-content: space-between;
292
+ align-items: baseline;
293
+ text-align: left;
294
+ margin: 0.8em auto;
295
+ max-width: 80%;
296
+ gap: 1em;
297
+ }
298
+ .credit-two-column .title {
299
+ flex: 1;
300
+ text-align: right;
301
+ padding-right: 1em;
302
+ }
303
+ .credit-two-column .name {
304
+ flex: 1;
305
+ text-align: left;
306
+ padding-left: 1em;
307
+ }
308
+ </style>
src/frontend/shared/ScrollEffect.svelte CHANGED
@@ -10,6 +10,11 @@
10
  * @property {string | null} name_color - Name text color (default: white).
11
  * @property {string | null} intro_title - Optional intro title.
12
  * @property {string | null} intro_subtitle - Optional intro subtitle.
 
 
 
 
 
13
  */
14
  export let credits: Props['credits'];
15
  export let speed: number;
@@ -19,6 +24,11 @@
19
  export let name_color: string | null = null;
20
  export let intro_title: string | null = null;
21
  export let intro_subtitle: string | null = null;
 
 
 
 
 
22
 
23
  // Flag to trigger animation reset
24
  let reset = false;
@@ -39,15 +49,14 @@
39
  // Reactive styles for title and name
40
  $: title_style = (is_intro: boolean) => `color: ${title_color || 'white'}; font-size: ${is_intro ? base_font_size * 1.5 : base_font_size}rem;`;
41
  $: name_style = (is_intro: boolean) => `color: ${name_color || 'white'}; font-size: ${is_intro ? base_font_size * 0.9 : base_font_size * 0.8}rem;`;
42
-
 
43
  // Reset animation on prop changes
44
  function resetAnimation() {
45
  reset = true;
46
  setTimeout(() => (reset = false), 0);
47
  }
48
 
49
-
50
-
51
  // Trigger reset on prop changes
52
  $: credits, speed, resetAnimation();
53
  </script>
@@ -56,17 +65,42 @@
56
  {#if !reset}
57
  <div class="credits-container">
58
  {#each display_items as item}
59
- <div class="credit" class:intro-block={item.is_intro}>
60
- <h2 style={title_style(item.is_intro)}>{item.title}</h2>
61
- {#if item.name}<p style={name_style(item.is_intro)}>{item.name}</p>{/if}
62
- </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
63
  {/each}
64
  </div>
65
  {/if}
66
  </div>
67
 
 
68
  <style>
69
- /* Container for scrolling credits */
70
  .wrapper {
71
  width: 100%;
72
  height: 100%;
@@ -74,12 +108,8 @@
74
  position: relative;
75
  font-family: sans-serif;
76
  }
77
- /* Intro block styling */
78
- .credit.intro-block {
79
- margin-bottom: 5rem;
80
- text-align: center;
81
- }
82
- /* Credits container with scroll animation */
83
  .credits-container {
84
  position: absolute;
85
  bottom: 0;
@@ -87,19 +117,55 @@
87
  width: 100%;
88
  text-align: center;
89
  animation: scroll var(--animation-duration) linear infinite;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
90
  }
91
- /* Individual credit block */
92
  .credit {
93
  margin-bottom: 2rem;
94
  }
95
- .credit h2 {
96
  margin: 0.5rem 0;
97
  font-family: sans-serif;
98
  }
99
- .credit p {
100
- margin: 0.5rem 0;
101
- font-family: sans-serif;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
102
  }
 
 
 
 
 
 
103
  /* Scroll animation keyframes */
104
  @keyframes scroll {
105
  0% { transform: translateY(100%); }
 
10
  * @property {string | null} name_color - Name text color (default: white).
11
  * @property {string | null} intro_title - Optional intro title.
12
  * @property {string | null} intro_subtitle - Optional intro subtitle.
13
+ * @property {"stacked" | "two-column"} layout_style - Layout for credits.
14
+ * @property {boolean} title_uppercase - Transform title to uppercase.
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;
 
24
  export let name_color: string | null = null;
25
  export let intro_title: string | null = null;
26
  export let intro_subtitle: string | null = null;
27
+ export let layout_style: "stacked" | "two-column" = "stacked";
28
+ export let title_uppercase: boolean = false;
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;
 
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() {
56
  reset = true;
57
  setTimeout(() => (reset = false), 0);
58
  }
59
 
 
 
60
  // Trigger reset on prop changes
61
  $: credits, speed, resetAnimation();
62
  </script>
 
65
  {#if !reset}
66
  <div class="credits-container">
67
  {#each display_items as item}
68
+ <!-- Render Section Title -->
69
+ {#if item.section_title}
70
+ <div class="section-title" style={section_title_style} class:uppercase={section_title_uppercase}>
71
+ {item.section_title}
72
+ </div>
73
+
74
+ <!-- Render Credit or Intro -->
75
+ {:else}
76
+ {#if layout_style === 'two-column' && !item.is_intro}
77
+ <!-- Two-Column Layout -->
78
+ <div class="credit-two-column">
79
+ <div class="title" style={swap_font_sizes_on_two_column ? name_style(false) : title_style(false)} class:uppercase={title_uppercase}>
80
+ {item.title}
81
+ </div>
82
+ <div class="name" style={swap_font_sizes_on_two_column ? title_style(false) : name_style(false)} class:uppercase={name_uppercase}>
83
+ {item.name}
84
+ </div>
85
+ </div>
86
+ {:else}
87
+ <!-- Stacked Layout (Default and for Intro) -->
88
+ <div class="credit" class:intro-block={item.is_intro}>
89
+ <h2 style={title_style(item.is_intro)} class:uppercase={title_uppercase && !item.is_intro}>{item.title}</h2>
90
+ {#if item.name}
91
+ <p style={name_style(item.is_intro)} class:uppercase={name_uppercase && !item.is_intro}>{item.name}</p>
92
+ {/if}
93
+ </div>
94
+ {/if}
95
+ {/if}
96
  {/each}
97
  </div>
98
  {/if}
99
  </div>
100
 
101
+
102
  <style>
103
+ /* Main container for the scrolling effect */
104
  .wrapper {
105
  width: 100%;
106
  height: 100%;
 
108
  position: relative;
109
  font-family: sans-serif;
110
  }
111
+
112
+ /* Animated container holding all credit items */
 
 
 
 
113
  .credits-container {
114
  position: absolute;
115
  bottom: 0;
 
117
  width: 100%;
118
  text-align: center;
119
  animation: scroll var(--animation-duration) linear infinite;
120
+ padding: 0 2rem; /* Adds horizontal padding */
121
+ box-sizing: border-box;
122
+ }
123
+
124
+ /* Section Title Style */
125
+ .section-title {
126
+ margin-top: 4rem;
127
+ margin-bottom: 2.5rem;
128
+ font-weight: bold;
129
+ }
130
+
131
+ /* Stacked Layout */
132
+ .credit.intro-block {
133
+ margin-bottom: 5rem;
134
  }
 
135
  .credit {
136
  margin-bottom: 2rem;
137
  }
138
+ .credit h2, .credit p {
139
  margin: 0.5rem 0;
140
  font-family: sans-serif;
141
  }
142
+
143
+ /* Two-Column Layout */
144
+ .credit-two-column {
145
+ display: flex;
146
+ justify-content: space-between;
147
+ align-items: baseline;
148
+ text-align: left;
149
+ margin: 0.8rem auto;
150
+ max-width: 80%; /* Limits width for better readability */
151
+ gap: 1rem;
152
+ }
153
+ .credit-two-column .title {
154
+ flex: 1;
155
+ text-align: right;
156
+ padding-right: 1rem;
157
+ }
158
+ .credit-two-column .name {
159
+ flex: 1;
160
+ text-align: left;
161
+ padding-left: 1rem;
162
  }
163
+
164
+ /* Utility class for uppercase */
165
+ .uppercase {
166
+ text-transform: uppercase;
167
+ }
168
+
169
  /* Scroll animation keyframes */
170
  @keyframes scroll {
171
  0% { transform: translateY(100%); }
src/frontend/shared/StarWarsEffect.svelte CHANGED
@@ -1,10 +1,12 @@
 
 
1
  <script lang="ts">
2
  import { onMount, onDestroy } from 'svelte';
3
 
4
  /**
5
  * Props for the StarWarsEffect component.
6
  * @typedef {Object} Props
7
- * @property {Array<{title: string, name: string}>} credits - List of credits with title and name.
8
  * @property {number} speed - Animation speed in seconds (default: 40).
9
  * @property {number} base_font_size - Base font size in rem (default: 1.5).
10
  * @property {string | null} background_color - Background color (default: black).
@@ -12,6 +14,11 @@
12
  * @property {string | null} name_color - Name text color (default: #feda4a).
13
  * @property {string | null} intro_title - Optional intro title.
14
  * @property {string | null} intro_subtitle - Optional intro subtitle.
 
 
 
 
 
15
  */
16
  export let credits: Props['credits'];
17
  export let speed: number = 40;
@@ -20,11 +27,17 @@
20
  export let title_color: string | null = null;
21
  export let name_color: string | null = null;
22
  export let intro_title: string | null = null;
23
- export let intro_subtitle: string | null = null;
24
-
25
- // Reactive styles for title and name
26
- $: title_style = (is_intro: boolean) => `color: ${title_color || '#feda4a'}; font-size: ${is_intro ? base_font_size * 1.5 : base_font_size}rem !important;`;
27
- $: name_style = (is_intro: boolean) => `color: ${name_color || '#feda4a'}; font-size: ${is_intro ? base_font_size * 0.9 : base_font_size * 0.7}rem !important;`;
 
 
 
 
 
 
28
 
29
  // Combine intro and credits for display
30
  $: display_items = (() => {
@@ -39,10 +52,8 @@
39
  return [...items, ...credits.map(c => ({ ...c, is_intro: false }))];
40
  })();
41
 
42
- // Element for animation reset
43
  let crawlElement: HTMLElement | null;
44
 
45
- // Reset animation on prop changes
46
  function resetAnimation() {
47
  if (crawlElement) {
48
  crawlElement.style.animation = 'none';
@@ -51,53 +62,56 @@
51
  }
52
  }
53
 
54
- // Initialize animation on mount
55
- onMount(() => {
56
- resetAnimation();
57
- return () => {};
58
- });
59
 
60
  // Trigger reset on prop changes
61
- $: credits, speed, base_font_size, background_color, title_color, name_color, intro_title, intro_subtitle, resetAnimation();
62
-
63
- // Cleanup on destroy
64
- onDestroy(() => {
65
- crawlElement = null;
66
- });
67
 
68
  // Generate star shadows for background
69
  const generate_star_shadows = (count: number, size: string) => {
70
- let shadows = '';
71
- for (let i = 0; i < count; i++) {
72
- shadows += `${Math.random() * 2000}px ${Math.random() * 2000}px ${size} white, `;
73
- }
74
- return shadows.slice(0, -2);
75
  };
76
-
77
  const small_stars = generate_star_shadows(200, '1px');
78
  const medium_stars = generate_star_shadows(100, '2px');
79
  const large_stars = generate_star_shadows(50, '3px');
80
  </script>
81
 
82
  <div class="viewport" style:background={background_color || 'black'}>
83
- <!-- Star layers for background -->
84
  <div class="stars small" style="box-shadow: {small_stars};"></div>
85
  <div class="stars medium" style="box-shadow: {medium_stars};"></div>
86
  <div class="stars large" style="box-shadow: {large_stars};"></div>
87
 
88
- <!-- Crawling credits -->
89
  <div class="crawl" bind:this={crawlElement} style="--animation-duration: {speed}s;">
90
  {#each display_items as item}
91
- <div class="credit" class:intro-block={item.is_intro}>
92
- <h2 style={title_style(item.is_intro)}>{item.title}</h2>
93
- {#if item.name}<p style={name_style(item.is_intro)}>{item.name}</p>{/if}
94
- </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
95
  {/each}
96
  </div>
97
  </div>
98
 
99
  <style>
100
- /* Container with perspective for 3D effect */
101
  .viewport {
102
  width: 100%;
103
  height: 100%;
@@ -109,48 +123,53 @@
109
  font-family: "Droid Sans", sans-serif;
110
  font-weight: bold;
111
  }
112
- /* Star layers with twinkling animation */
113
  .stars {
114
- position: absolute;
115
- top: 0;
116
- left: 0;
117
- width: 1px;
118
- height: 1px;
119
- background: transparent;
120
- z-index: 0;
121
  animation: twinkle 10s linear infinite;
122
  }
123
  .stars.small { animation-duration: 10s; }
124
  .stars.medium { animation-duration: 15s; }
125
  .stars.large { animation-duration: 20s; }
126
- @keyframes twinkle {
127
- 0% { opacity: 0.6; }
128
- 50% { opacity: 1; }
129
- 100% { opacity: 0.6; }
130
- }
131
- /* Crawling text container */
132
  .crawl {
133
- position: absolute;
134
- width: 100%;
135
- bottom: 0;
136
  transform-origin: 50% 100%;
137
  animation: crawl-animation var(--animation-duration) linear infinite;
138
- z-index: 1;
139
- text-align: center;
140
  }
141
- /* Crawl animation with 3D transform */
142
  @keyframes crawl-animation {
143
- 0% { transform: rotateX(60deg) translateY(100%) translateZ(100px); opacity: 1; }
144
- 100% { transform: rotateX(60deg) translateY(-150%) translateZ(-1200px); opacity: 1; }
 
 
 
 
 
 
 
 
 
145
  }
146
- /* Intro block spacing */
147
  .credit.intro-block { margin-bottom: 5rem; }
148
- /* Credit block spacing */
149
  .credit { margin-bottom: 2rem; }
150
- /* Text styling */
151
- h2, p {
152
- margin: 0.5rem 0;
153
- padding: 0;
 
 
 
 
154
  white-space: nowrap;
155
  }
 
 
 
 
 
 
156
  </style>
 
1
+ <!-- StarWarsEffect.svelte -->
2
+
3
  <script lang="ts">
4
  import { onMount, onDestroy } from 'svelte';
5
 
6
  /**
7
  * Props for the StarWarsEffect component.
8
  * @typedef {Object} Props
9
+ * @property {Array<{title?: string, name?: string, section_title?: string}>} credits - List of credits, can include sections.
10
  * @property {number} speed - Animation speed in seconds (default: 40).
11
  * @property {number} base_font_size - Base font size in rem (default: 1.5).
12
  * @property {string | null} background_color - Background color (default: black).
 
14
  * @property {string | null} name_color - Name text color (default: #feda4a).
15
  * @property {string | null} intro_title - Optional intro title.
16
  * @property {string | null} intro_subtitle - Optional intro subtitle.
17
+ * @property {"stacked" | "two-column"} layout_style - Layout for credits.
18
+ * @property {boolean} title_uppercase - Transform title to uppercase.
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;
 
27
  export let title_color: string | null = null;
28
  export let name_color: string | null = null;
29
  export let intro_title: string | null = null;
30
+ export let intro_subtitle: string | null = null;
31
+ export let layout_style: "stacked" | "two-column" = "stacked";
32
+ export let title_uppercase: boolean = false;
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;`;
39
+ $: name_style = (is_intro: boolean) => `color: ${name_color || '#feda4a'}; font-size: ${is_intro ? base_font_size * 0.9 : base_font_size * 0.7}rem;`;
40
+ $: section_title_style = `color: ${title_color || '#feda4a'}; font-size: ${base_font_size * 1.2}rem;`;
41
 
42
  // Combine intro and credits for display
43
  $: display_items = (() => {
 
52
  return [...items, ...credits.map(c => ({ ...c, is_intro: false }))];
53
  })();
54
 
 
55
  let crawlElement: HTMLElement | null;
56
 
 
57
  function resetAnimation() {
58
  if (crawlElement) {
59
  crawlElement.style.animation = 'none';
 
62
  }
63
  }
64
 
65
+ onMount(resetAnimation);
66
+ onDestroy(() => { crawlElement = null; });
 
 
 
67
 
68
  // Trigger reset on prop changes
69
+ $: credits, speed, layout_style, resetAnimation();
 
 
 
 
 
70
 
71
  // Generate star shadows for background
72
  const generate_star_shadows = (count: number, size: string) => {
73
+ let shadows = Array.from({ length: count }, () => `${Math.random() * 2000}px ${Math.random() * 2000}px ${size} white`).join(', ');
74
+ return shadows;
 
 
 
75
  };
 
76
  const small_stars = generate_star_shadows(200, '1px');
77
  const medium_stars = generate_star_shadows(100, '2px');
78
  const large_stars = generate_star_shadows(50, '3px');
79
  </script>
80
 
81
  <div class="viewport" style:background={background_color || 'black'}>
 
82
  <div class="stars small" style="box-shadow: {small_stars};"></div>
83
  <div class="stars medium" style="box-shadow: {medium_stars};"></div>
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}
90
+ <div class="section-title" style={section_title_style} class:uppercase={section_title_uppercase}>
91
+ {item.section_title}
92
+ </div>
93
+ <!-- Render Credit or Intro -->
94
+ {:else}
95
+ {#if layout_style === 'two-column' && !item.is_intro}
96
+ <!-- Two-Column Layout -->
97
+ <div class="credit-two-column">
98
+ <span style={swap_font_sizes_on_two_column ? name_style(false) : title_style(false)} class:uppercase={title_uppercase}>{item.title}</span>
99
+ <span class="spacer"></span>
100
+ <span style={swap_font_sizes_on_two_column ? title_style(false) : name_style(false)} class:uppercase={name_uppercase}>{item.name}</span>
101
+ </div>
102
+ {:else}
103
+ <!-- Stacked Layout -->
104
+ <div class="credit" class:intro-block={item.is_intro}>
105
+ <h2 style={title_style(item.is_intro)} class:uppercase={title_uppercase && !item.is_intro}>{item.title}</h2>
106
+ {#if item.name}<p style={name_style(item.is_intro)} class:uppercase={name_uppercase && !item.is_intro}>{item.name}</p>{/if}
107
+ </div>
108
+ {/if}
109
+ {/if}
110
  {/each}
111
  </div>
112
  </div>
113
 
114
  <style>
 
115
  .viewport {
116
  width: 100%;
117
  height: 100%;
 
123
  font-family: "Droid Sans", sans-serif;
124
  font-weight: bold;
125
  }
 
126
  .stars {
127
+ position: absolute; top: 0; left: 0;
128
+ width: 1px; height: 1px;
129
+ background: transparent; z-index: 0;
 
 
 
 
130
  animation: twinkle 10s linear infinite;
131
  }
132
  .stars.small { animation-duration: 10s; }
133
  .stars.medium { animation-duration: 15s; }
134
  .stars.large { animation-duration: 20s; }
135
+ @keyframes twinkle { 0% { opacity: 0.6; } 50% { opacity: 1; } 100% { opacity: 0.6; } }
136
+
 
 
 
 
137
  .crawl {
138
+ position: absolute; width: 100%; bottom: 0;
 
 
139
  transform-origin: 50% 100%;
140
  animation: crawl-animation var(--animation-duration) linear infinite;
141
+ z-index: 1; text-align: center;
 
142
  }
 
143
  @keyframes crawl-animation {
144
+ 0% { transform: rotateX(60deg) translateY(100%) translateZ(100px); }
145
+ 100% { transform: rotateX(60deg) translateY(-150%) translateZ(-1200px); }
146
+ }
147
+
148
+ /* STYLES */
149
+ .uppercase { text-transform: uppercase; }
150
+
151
+ .section-title {
152
+ margin-top: 5rem;
153
+ margin-bottom: 3rem;
154
+ font-weight: bold;
155
  }
156
+
157
  .credit.intro-block { margin-bottom: 5rem; }
 
158
  .credit { margin-bottom: 2rem; }
159
+ .credit h2, .credit p { margin: 0.5rem 0; padding: 0; white-space: nowrap; }
160
+
161
+ .credit-two-column {
162
+ display: flex;
163
+ justify-content: space-between;
164
+ align-items: baseline;
165
+ margin: 0.8rem auto;
166
+ width: 90%;
167
  white-space: nowrap;
168
  }
169
+ .credit-two-column .spacer {
170
+ flex-grow: 1;
171
+ border-bottom: 1px dotted rgba(254, 218, 74, 0.3);
172
+ margin: 0 1em;
173
+ transform: translateY(-0.5em);
174
+ }
175
  </style>
src/pyproject.toml CHANGED
@@ -8,7 +8,7 @@ build-backend = "hatchling.build"
8
 
9
  [project]
10
  name = "gradio_creditspanel"
11
- version = "0.0.2"
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.3"
12
  description = "Credits Panel for Gradio UI"
13
  readme = "README.md"
14
  license = "apache-2.0"