uxoxo/eb2ab
0
1"""2Design System Theme - Gradio theme configuration3 4This module creates a custom Gradio theme based on the design system tokens.5 6Author: Claude Code7Date: 2025-10-218"""9 10import gradio as gr11from gradio.themes.base import Base12from gradio.themes.utils import colors, fonts, sizes13from .design_tokens import DesignTokens as DT14 15 16class Ebook2AudiobookTheme(Base):17 """18 Custom Gradio theme for ebook2audiobook19 Based on OOUX design principles with clarity and transparency20 """21 22 def __init__(23 self,24 *,25 primary_hue: colors.Color = colors.blue,26 secondary_hue: colors.Color = colors.purple,27 neutral_hue: colors.Color = colors.gray,28 spacing_size: sizes.Size = sizes.spacing_md,29 radius_size: sizes.Size = sizes.radius_md,30 text_size: sizes.Size = sizes.text_md,31 font: tuple[str] = (32 "Inter",33 "-apple-system",34 "BlinkMacSystemFont",35 "Segoe UI",36 "sans-serif",37 ),38 font_mono: tuple[str] = (39 "JetBrains Mono",40 "Consolas",41 "Monaco",42 "monospace",43 ),44 ):45 super().__init__(46 primary_hue=primary_hue,47 secondary_hue=secondary_hue,48 neutral_hue=neutral_hue,49 spacing_size=spacing_size,50 radius_size=radius_size,51 text_size=text_size,52 font=font,53 font_mono=font_mono,54 )55 56 # Override specific theme properties57 self.set(58 # Button styles59 button_primary_background_fill=DT.PRIMARY,60 button_primary_background_fill_hover=DT.PRIMARY_HOVER,61 button_primary_text_color="white",62 button_primary_border_color=DT.PRIMARY,63 64 button_secondary_background_fill=DT.SECONDARY,65 button_secondary_background_fill_hover=DT.SECONDARY_HOVER,66 button_secondary_text_color="white",67 68 # Borders69 border_color_primary=DT.BORDER_DEFAULT,70 border_color_accent=DT.PRIMARY,71 72 # Backgrounds73 background_fill_primary="white",74 background_fill_secondary=DT.BG_SECONDARY,75 76 # Shadow77 shadow_drop=DT.SHADOW_BASE,78 shadow_drop_lg=DT.SHADOW_LG,79 80 # Block81 block_background_fill="white",82 block_border_width="1px",83 block_padding=DT.SPACE_6,84 block_radius=DT.RADIUS_LG,85 block_shadow=DT.SHADOW_SM,86 87 # Input88 input_background_fill="white",89 input_border_color=DT.BORDER_DEFAULT,90 input_border_color_focus=DT.BORDER_FOCUS,91 input_border_width="1px",92 input_padding=DT.SPACE_3,93 input_radius=DT.RADIUS_MD,94 )95 96 97def create_theme() -> gr.Theme:98 """99 Create and return the ebook2audiobook theme100 101 Returns:102 gr.Theme instance103 """104 return Ebook2AudiobookTheme()105 106 107def get_custom_css() -> str:108 """109 Get custom CSS to inject into Gradio app110 111 Returns:112 CSS string113 """114 # Combine design tokens CSS vars with custom styles115 css = DT.to_css_vars()116 117 # Add custom CSS from styles.css118 # Note: In production, you'd load this from the actual CSS file119 css += """120 /* Additional custom styles for Gradio */121 122 /* Gradio-specific overrides */123 .gradio-container {124 font-family: var(--font-primary) !important;125 max-width: 1440px;126 margin: 0 auto;127 }128 129 /* Tab styling */130 .tab-nav button {131 font-weight: 500;132 font-size: var(--font-size-base);133 padding: var(--space-3) var(--space-6);134 border-radius: var(--radius-md) var(--radius-md) 0 0;135 transition: all var(--transition-base);136 }137 138 .tab-nav button.selected {139 background: var(--color-primary-light);140 color: var(--color-primary-dark);141 border-bottom: 2px solid var(--color-primary);142 }143 144 /* Card hover effects */145 .card:hover {146 transform: translateY(-2px);147 box-shadow: var(--shadow-lg);148 }149 150 /* Progress animations */151 @keyframes progress-shimmer {152 0% {153 background-position: -200% 0;154 }155 100% {156 background-position: 200% 0;157 }158 }159 160 .progress-bar-fill.processing {161 background: linear-gradient(162 90deg,163 var(--color-warning) 0%,164 var(--color-warning-light) 50%,165 var(--color-warning) 100%166 );167 background-size: 200% 100%;168 animation: progress-shimmer 2s linear infinite;169 }170 171 /* Smooth transitions for all interactive elements */172 button, input, select, textarea {173 transition: all var(--transition-base);174 }175 176 /* Focus states for accessibility */177 button:focus-visible,178 input:focus-visible,179 select:focus-visible,180 textarea:focus-visible {181 outline: 2px solid var(--color-primary);182 outline-offset: 2px;183 }184 185 /* Responsive typography */186 @media (max-width: 768px) {187 .heading-1 {188 font-size: var(--font-size-3xl);189 }190 191 .heading-2 {192 font-size: var(--font-size-xl);193 }194 195 .gradio-container {196 padding: var(--space-4);197 }198 }199 200 /* Status indicators with pulse animation */201 .tag-processing {202 animation: pulse-border 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;203 }204 205 @keyframes pulse-border {206 0%, 100% {207 border-color: var(--color-warning);208 }209 50% {210 border-color: var(--color-warning-light);211 }212 }213 214 /* File upload area */215 .file-upload {216 border: 2px dashed var(--color-neutral-300);217 border-radius: var(--radius-lg);218 padding: var(--space-8);219 text-align: center;220 transition: all var(--transition-base);221 }222 223 .file-upload:hover {224 border-color: var(--color-primary);225 background: var(--color-primary-light);226 }227 228 /* Toast notifications (if using) */229 .toast {230 position: fixed;231 bottom: var(--space-6);232 right: var(--space-6);233 background: white;234 border-radius: var(--radius-lg);235 box-shadow: var(--shadow-xl);236 padding: var(--space-4);237 min-width: 300px;238 z-index: 9999;239 animation: slide-in-right 0.3s ease-out;240 }241 242 @keyframes slide-in-right {243 from {244 transform: translateX(100%);245 opacity: 0;246 }247 to {248 transform: translateX(0);249 opacity: 1;250 }251 }252 253 /* Scrollbar styling */254 ::-webkit-scrollbar {255 width: 8px;256 height: 8px;257 }258 259 ::-webkit-scrollbar-track {260 background: var(--color-neutral-100);261 }262 263 ::-webkit-scrollbar-thumb {264 background: var(--color-neutral-400);265 border-radius: var(--radius-full);266 }267 268 ::-webkit-scrollbar-thumb:hover {269 background: var(--color-neutral-500);270 }271 272 /* Loading states */273 .loading {274 position: relative;275 pointer-events: none;276 opacity: 0.6;277 }278 279 .loading::after {280 content: "";281 position: absolute;282 top: 50%;283 left: 50%;284 width: 20px;285 height: 20px;286 margin: -10px 0 0 -10px;287 border: 3px solid var(--color-neutral-200);288 border-top-color: var(--color-primary);289 border-radius: 50%;290 animation: spin 0.8s linear infinite;291 }292 293 /* Disabled states */294 button:disabled,295 input:disabled,296 select:disabled,297 textarea:disabled {298 opacity: 0.5;299 cursor: not-allowed;300 }301 302 /* Success checkmark animation */303 @keyframes checkmark {304 0% {305 stroke-dashoffset: 50;306 }307 100% {308 stroke-dashoffset: 0;309 }310 }311 312 .checkmark-icon {313 stroke-dasharray: 50;314 stroke-dashoffset: 50;315 animation: checkmark 0.5s ease-out forwards;316 }317 """318 319 return css320 321 322# Load external CSS file323def load_css_file(filepath: str = "design_system/styles.css") -> str:324 """325 Load CSS from external file326 327 Args:328 filepath: Path to CSS file329 330 Returns:331 CSS content as string332 """333 try:334 with open(filepath, 'r', encoding='utf-8') as f:335 return f.read()336 except FileNotFoundError:337 print(f"Warning: CSS file not found at {filepath}")338 return ""339 340 341def apply_theme_to_app(app: gr.Blocks) -> gr.Blocks:342 """343 Apply design system theme to a Gradio Blocks app344 345 Args:346 app: Gradio Blocks app347 348 Returns:349 Modified app with theme applied350 """351 # This would be called when creating the app352 # Example usage in app creation:353 # with gr.Blocks(theme=create_theme(), css=get_custom_css()) as app:354 # ...355 return app356 