Spaces:
Running
Running
Upload folder using huggingface_hub
Browse files- README.md +147 -106
- app.py +225 -249
- space.py +82 -106
- src/README.md +147 -106
- src/backend/gradio_creditspanel/creditspanel.py +28 -2
- src/backend/gradio_creditspanel/templates/component/index.js +0 -0
- src/backend/gradio_creditspanel/templates/component/style.css +1 -1
- src/demo/app.py +81 -105
- src/demo/space.py +82 -106
- src/frontend/Index.svelte +32 -8
- src/frontend/shared/MatrixEffect.svelte +284 -168
- src/frontend/shared/ScrollEffect.svelte +85 -19
- src/frontend/shared/StarWarsEffect.svelte +79 -60
- src/pyproject.toml +1 -1
README.md
CHANGED
|
@@ -10,7 +10,7 @@ app_file: space.py
|
|
| 10 |
---
|
| 11 |
|
| 12 |
# `gradio_creditspanel`
|
| 13 |
-
<
|
| 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
|
| 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 |
-
|
| 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 |
-
#
|
| 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":
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 =
|
| 149 |
-
|
| 150 |
speed = DEFAULT_SPEEDS.get(effect, 40.0)
|
| 151 |
return speed, font_size
|
| 152 |
|
| 153 |
-
|
| 154 |
-
|
|
|
|
| 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 |
-
|
| 168 |
-
|
| 169 |
-
|
| 170 |
-
|
| 171 |
-
|
| 172 |
-
|
|
|
|
| 173 |
)
|
| 174 |
-
|
| 175 |
-
|
| 176 |
-
|
|
|
|
| 177 |
)
|
| 178 |
-
|
|
|
|
|
|
|
|
|
|
| 179 |
gr.Markdown("### Intro Text")
|
| 180 |
-
intro_title_input = gr.Textbox(
|
| 181 |
-
|
| 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 |
-
|
| 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 |
-
|
| 255 |
-
|
| 256 |
-
|
| 257 |
-
|
| 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 |
-
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
"""
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
|
| 66 |
-
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
|
| 85 |
-
|
| 86 |
-
|
| 87 |
-
|
| 88 |
-
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
|
| 92 |
-
|
| 93 |
-
|
| 94 |
-
|
| 95 |
-
|
| 96 |
-
|
| 97 |
-
|
| 98 |
-
|
| 99 |
-
|
| 100 |
-
|
| 101 |
-
|
| 102 |
-
|
| 103 |
-
|
| 104 |
-
|
| 105 |
-
|
| 106 |
-
|
| 107 |
-
|
| 108 |
-
|
| 109 |
-
|
| 110 |
-
|
| 111 |
-
|
| 112 |
-
|
| 113 |
-
|
| 114 |
-
|
| 115 |
-
|
| 116 |
-
|
| 117 |
-
|
| 118 |
-
|
| 119 |
-
|
| 120 |
-
|
| 121 |
-
|
| 122 |
-
|
| 123 |
-
|
| 124 |
-
|
| 125 |
-
|
| 126 |
-
|
| 127 |
-
|
| 128 |
-
|
| 129 |
-
|
| 130 |
-
|
| 131 |
-
|
| 132 |
-
|
| 133 |
-
|
| 134 |
-
|
| 135 |
-
|
| 136 |
-
|
| 137 |
-
|
| 138 |
-
|
| 139 |
-
|
| 140 |
-
|
| 141 |
-
|
| 142 |
-
|
| 143 |
-
|
| 144 |
-
|
| 145 |
-
|
| 146 |
-
|
| 147 |
-
|
| 148 |
-
)
|
| 149 |
-
|
| 150 |
-
|
| 151 |
-
|
| 152 |
-
)
|
| 153 |
-
|
| 154 |
-
gr.
|
| 155 |
-
|
| 156 |
-
|
| 157 |
-
)
|
| 158 |
-
|
| 159 |
-
|
| 160 |
-
)
|
| 161 |
-
|
| 162 |
-
|
| 163 |
-
|
| 164 |
-
|
| 165 |
-
|
| 166 |
-
|
| 167 |
-
|
| 168 |
-
|
| 169 |
-
|
| 170 |
-
|
| 171 |
-
|
| 172 |
-
|
| 173 |
-
|
| 174 |
-
|
| 175 |
-
|
| 176 |
-
|
| 177 |
-
|
| 178 |
-
|
| 179 |
-
|
| 180 |
-
|
| 181 |
-
|
| 182 |
-
|
| 183 |
-
|
| 184 |
-
|
| 185 |
-
|
| 186 |
-
|
| 187 |
-
|
| 188 |
-
|
| 189 |
-
|
| 190 |
-
|
| 191 |
-
|
| 192 |
-
|
| 193 |
-
|
| 194 |
-
|
| 195 |
-
|
| 196 |
-
|
| 197 |
-
|
| 198 |
-
|
| 199 |
-
|
| 200 |
-
|
| 201 |
-
|
| 202 |
-
|
| 203 |
-
|
| 204 |
-
scroll_background_color
|
| 205 |
-
scroll_title_color
|
| 206 |
-
scroll_name_color
|
| 207 |
-
|
| 208 |
-
|
| 209 |
-
|
| 210 |
-
|
| 211 |
-
|
| 212 |
-
|
| 213 |
-
|
| 214 |
-
|
| 215 |
-
|
| 216 |
-
|
| 217 |
-
|
| 218 |
-
|
| 219 |
-
|
| 220 |
-
|
| 221 |
-
|
| 222 |
-
|
| 223 |
-
|
| 224 |
-
|
| 225 |
-
|
| 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
|
| 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
|
| 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 |
-
#
|
| 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":
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 =
|
| 164 |
-
|
| 165 |
speed = DEFAULT_SPEEDS.get(effect, 40.0)
|
| 166 |
return speed, font_size
|
| 167 |
|
| 168 |
-
|
| 169 |
-
|
|
|
|
| 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 |
-
|
| 183 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 184 |
)
|
| 185 |
-
|
| 186 |
-
|
| 187 |
-
|
|
|
|
| 188 |
)
|
| 189 |
-
|
| 190 |
-
|
| 191 |
-
|
| 192 |
-
|
| 193 |
-
|
| 194 |
gr.Markdown("### Intro Text")
|
| 195 |
-
intro_title_input = gr.Textbox(
|
| 196 |
-
|
| 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 |
-
|
| 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 |
-
|
| 270 |
-
|
| 271 |
-
|
| 272 |
-
|
| 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 |
-
<
|
| 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
|
| 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 |
-
|
| 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 |
-
#
|
| 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":
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 =
|
| 149 |
-
|
| 150 |
speed = DEFAULT_SPEEDS.get(effect, 40.0)
|
| 151 |
return speed, font_size
|
| 152 |
|
| 153 |
-
|
| 154 |
-
|
|
|
|
| 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 |
-
|
| 168 |
-
|
| 169 |
-
|
| 170 |
-
|
| 171 |
-
|
| 172 |
-
|
|
|
|
| 173 |
)
|
| 174 |
-
|
| 175 |
-
|
| 176 |
-
|
|
|
|
| 177 |
)
|
| 178 |
-
|
|
|
|
|
|
|
|
|
|
| 179 |
gr.Markdown("### Intro Text")
|
| 180 |
-
intro_title_input = gr.Textbox(
|
| 181 |
-
|
| 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 |
-
|
| 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 |
-
|
| 255 |
-
|
| 256 |
-
|
| 257 |
-
|
| 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
|
| 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
|
| 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 |
-
#
|
| 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":
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 =
|
| 124 |
-
|
| 125 |
speed = DEFAULT_SPEEDS.get(effect, 40.0)
|
| 126 |
return speed, font_size
|
| 127 |
|
| 128 |
-
|
| 129 |
-
|
|
|
|
| 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 |
-
|
| 143 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 144 |
)
|
| 145 |
-
|
| 146 |
-
|
| 147 |
-
|
|
|
|
| 148 |
)
|
| 149 |
-
|
| 150 |
-
|
| 151 |
-
|
| 152 |
-
|
| 153 |
-
|
| 154 |
gr.Markdown("### Intro Text")
|
| 155 |
-
intro_title_input = gr.Textbox(
|
| 156 |
-
|
| 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 |
-
|
| 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 |
-
|
| 230 |
-
|
| 231 |
-
|
| 232 |
-
|
| 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
|
| 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
|
| 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 |
-
#
|
| 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":
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 =
|
| 164 |
-
|
| 165 |
speed = DEFAULT_SPEEDS.get(effect, 40.0)
|
| 166 |
return speed, font_size
|
| 167 |
|
| 168 |
-
|
| 169 |
-
|
|
|
|
| 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 |
-
|
| 183 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 184 |
)
|
| 185 |
-
|
| 186 |
-
|
| 187 |
-
|
|
|
|
| 188 |
)
|
| 189 |
-
|
| 190 |
-
|
| 191 |
-
|
| 192 |
-
|
| 193 |
-
|
| 194 |
gr.Markdown("### Intro Text")
|
| 195 |
-
intro_title_input = gr.Textbox(
|
| 196 |
-
|
| 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 |
-
|
| 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 |
-
|
| 270 |
-
|
| 271 |
-
|
| 272 |
-
|
| 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
|
| 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 |
-
|
| 3 |
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 18 |
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
| 31 |
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
|
|
|
|
|
|
|
|
|
|
| 35 |
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
|
|
|
|
| 45 |
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
}
|
| 54 |
-
ctx = canvas.getContext('2d')!;
|
| 55 |
-
columns = Math.floor(canvas.width / fontSize);
|
| 56 |
-
drops = Array(columns).fill(1);
|
| 57 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
| 58 |
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
|
| 66 |
-
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
|
| 75 |
}
|
|
|
|
|
|
|
| 76 |
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
|
| 82 |
-
|
| 83 |
-
}
|
| 84 |
}
|
|
|
|
| 85 |
|
| 86 |
-
|
| 87 |
-
|
| 88 |
-
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
|
| 92 |
-
|
| 93 |
-
|
| 94 |
-
|
| 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 |
-
|
| 108 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 109 |
|
| 110 |
-
|
| 111 |
-
|
| 112 |
-
|
| 113 |
-
|
| 114 |
</script>
|
| 115 |
|
| 116 |
<div class="matrix-container">
|
| 117 |
-
|
| 118 |
-
|
| 119 |
-
|
| 120 |
-
|
| 121 |
-
|
| 122 |
-
|
| 123 |
-
|
| 124 |
-
|
| 125 |
-
|
| 126 |
-
|
| 127 |
-
|
| 128 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 129 |
</div>
|
|
|
|
| 130 |
</div>
|
| 131 |
|
| 132 |
<style>
|
| 133 |
-
|
| 134 |
-
|
| 135 |
-
|
| 136 |
-
|
| 137 |
-
|
| 138 |
-
|
| 139 |
-
|
| 140 |
-
|
| 141 |
-
|
| 142 |
-
|
| 143 |
-
|
| 144 |
-
|
| 145 |
-
|
| 146 |
-
|
| 147 |
-
|
| 148 |
-
|
| 149 |
-
|
| 150 |
-
|
| 151 |
-
|
| 152 |
-
|
| 153 |
-
|
| 154 |
-
|
| 155 |
-
|
| 156 |
-
|
| 157 |
-
|
| 158 |
-
|
| 159 |
-
|
| 160 |
-
|
| 161 |
-
|
| 162 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 163 |
}
|
| 164 |
-
|
| 165 |
-
|
| 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 |
-
|
| 173 |
-
|
| 174 |
-
|
| 175 |
-
|
| 176 |
-
|
| 177 |
-
|
| 178 |
-
|
| 179 |
-
|
| 180 |
-
|
| 181 |
-
|
| 182 |
-
|
| 183 |
-
|
| 184 |
-
|
| 185 |
-
|
| 186 |
-
|
| 187 |
-
|
| 188 |
-
|
| 189 |
-
|
| 190 |
-
|
| 191 |
-
|
| 192 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
<script lang="ts">
|
| 2 |
+
import { onMount, onDestroy } from "svelte";
|
| 3 |
|
| 4 |
+
/**
|
| 5 |
+
* Props for the MatrixEffect component.
|
| 6 |
+
* @typedef {Object} Props
|
| 7 |
+
* @property {Array<{title: string, name: string}>} credits - List of credits with title and name.
|
| 8 |
+
* @property {number} speed - Animation speed in seconds (default: 20).
|
| 9 |
+
* @property {number} base_font_size - Base font size in em (default: 1.0).
|
| 10 |
+
* @property {string | null} intro_title - Optional intro title.
|
| 11 |
+
* @property {string | null} intro_subtitle - Optional intro subtitle.
|
| 12 |
+
* @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 |
-
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 63 |
{/each}
|
| 64 |
</div>
|
| 65 |
{/if}
|
| 66 |
</div>
|
| 67 |
|
|
|
|
| 68 |
<style>
|
| 69 |
-
|
| 70 |
.wrapper {
|
| 71 |
width: 100%;
|
| 72 |
height: 100%;
|
|
@@ -74,12 +108,8 @@
|
|
| 74 |
position: relative;
|
| 75 |
font-family: sans-serif;
|
| 76 |
}
|
| 77 |
-
|
| 78 |
-
|
| 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 |
-
|
| 100 |
-
|
| 101 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
|
| 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 |
-
|
| 26 |
-
|
| 27 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
| 55 |
-
|
| 56 |
-
resetAnimation();
|
| 57 |
-
return () => {};
|
| 58 |
-
});
|
| 59 |
|
| 60 |
// Trigger reset on prop changes
|
| 61 |
-
$: credits, speed,
|
| 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 |
-
|
| 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 |
-
|
| 92 |
-
|
| 93 |
-
|
| 94 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
| 116 |
-
|
| 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 |
-
|
| 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);
|
| 144 |
-
100% { transform: rotateX(60deg) translateY(-150%) translateZ(-1200px);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 145 |
}
|
| 146 |
-
|
| 147 |
.credit.intro-block { margin-bottom: 5rem; }
|
| 148 |
-
/* Credit block spacing */
|
| 149 |
.credit { margin-bottom: 2rem; }
|
| 150 |
-
|
| 151 |
-
|
| 152 |
-
|
| 153 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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.
|
| 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"
|