""" PodcastItLater Shared UI Components. Common UI components and utilities shared across web pages. """ # : out podcastitlater-ui # : dep ludic import ludic.html as html def format_duration(seconds: int | None) -> str: """Format duration from seconds to human-readable format. Examples: 300 -> "5m" 3840 -> "1h 4m" 11520 -> "3h 12m" """ if seconds is None or seconds <= 0: return "Unknown" # Constants for time conversion seconds_per_minute = 60 minutes_per_hour = 60 seconds_per_hour = 3600 # Round up to nearest minute minutes = (seconds + seconds_per_minute - 1) // seconds_per_minute # Show as minutes only if under 60 minutes (exclusive) # 3599 seconds rounds up to 60 minutes, which we keep as "60m" if minutes <= minutes_per_hour: # If exactly 3600 seconds (already 60 full minutes without rounding) if seconds >= seconds_per_hour: return "1h" return f"{minutes}m" hours = minutes // minutes_per_hour remaining_minutes = minutes % minutes_per_hour if remaining_minutes == 0: return f"{hours}h" return f"{hours}h {remaining_minutes}m" def create_bootstrap_styles() -> html.style: """Load Bootstrap CSS and icons.""" return html.style( "@import url('https://cdn.jsdelivr.net/npm/bootstrap@5.3.2" "/dist/css/bootstrap.min.css');" "@import url('https://cdn.jsdelivr.net/npm/bootstrap-icons" "@1.11.3/font/bootstrap-icons.min.css');", ) def create_auto_dark_mode_style() -> html.style: """Create CSS for automatic dark mode based on prefers-color-scheme.""" return html.style( """ /* Auto dark mode - applies Bootstrap dark theme via media query */ @media (prefers-color-scheme: dark) { :root { color-scheme: dark; --bs-body-color: #dee2e6; --bs-body-color-rgb: 222, 226, 230; --bs-body-bg: #212529; --bs-body-bg-rgb: 33, 37, 41; --bs-emphasis-color: #fff; --bs-emphasis-color-rgb: 255, 255, 255; --bs-secondary-color: rgba(222, 226, 230, 0.75); --bs-secondary-bg: #343a40; --bs-tertiary-color: rgba(222, 226, 230, 0.5); --bs-tertiary-bg: #2b3035; --bs-heading-color: inherit; --bs-link-color: #6ea8fe; --bs-link-hover-color: #8bb9fe; --bs-link-color-rgb: 110, 168, 254; --bs-link-hover-color-rgb: 139, 185, 254; --bs-code-color: #e685b5; --bs-border-color: #495057; --bs-border-color-translucent: rgba(255, 255, 255, 0.15); } /* Navbar dark mode */ .navbar.bg-body-tertiary { background-color: #2b3035 !important; } .navbar .navbar-text { color: #dee2e6 !important; } } """, ) def create_htmx_script() -> html.script: """Load HTMX library.""" return html.script( src="https://unpkg.com/htmx.org@1.9.10", integrity="sha384-D1Kt99CQMDuVetoL1lrYwg5t+9QdHe7NLX/SoJYkXDFfX37iInKRy5xLSi8nO7UC", crossorigin="anonymous", ) def create_bootstrap_js() -> html.script: """Load Bootstrap JavaScript bundle.""" return html.script( src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js", integrity="sha384-C6RzsynM9kWDrMNeT87bh95OGNyZPhcTNXj1NW7RuBCsyN/o0jlpcV8Qyq46cDfL", crossorigin="anonymous", )