Lower-Limb-Similarity-Analysis / shared_styling.py
jmontp's picture
Major update
c3a09bd
"""
Unified styling module for both Streamlit UI and matplotlib plots.
Contains all styling definitions to ensure consistency across the application.
Note: When used outside of Streamlit environment (e.g., in Jupyter notebooks),
you may see warnings about missing ScriptRunContext or Session state. These
warnings are harmless and can be safely ignored - the core plotting functions
(get_plot_style, set_plot_style, PLOT_COLORS) work correctly regardless.
"""
import warnings
import logging
import matplotlib.pyplot as plt
import seaborn as sns
import matplotlib.font_manager as fm
# Suppress Streamlit warnings when running outside streamlit environment
warnings.filterwarnings('ignore', category=UserWarning, module='streamlit')
warnings.filterwarnings('ignore', message='.*ScriptRunContext.*')
warnings.filterwarnings('ignore', message='.*Session state.*')
warnings.filterwarnings('ignore', message='.*missing ScriptRunContext.*')
warnings.filterwarnings('ignore', message='.*does not function when running.*')
warnings.filterwarnings('ignore', module='streamlit.runtime.*')
warnings.filterwarnings('ignore', module='streamlit.runtime.scriptrunner_utils.*')
warnings.filterwarnings('ignore', module='streamlit.runtime.state.*')
# Suppress Streamlit loggers that generate warnings outside streamlit environment
logging.getLogger('streamlit.runtime.scriptrunner_utils.script_run_context').setLevel(logging.ERROR)
logging.getLogger('streamlit.runtime.state.session_state_proxy').setLevel(logging.ERROR)
logging.getLogger('streamlit').setLevel(logging.ERROR)
try:
# Set logging level before importing to suppress initial warnings
for logger_name in ['streamlit', 'streamlit.runtime', 'streamlit.runtime.scriptrunner_utils',
'streamlit.runtime.state', 'streamlit.runtime.scriptrunner_utils.script_run_context',
'streamlit.runtime.state.session_state_proxy']:
logging.getLogger(logger_name).setLevel(logging.ERROR)
import streamlit as st
_STREAMLIT_AVAILABLE = True
except ImportError:
_STREAMLIT_AVAILABLE = False
# Create a mock streamlit module for non-streamlit environments
class MockStreamlit:
class session_state:
dark_theme = False
st = MockStreamlit()
def _suppress_streamlit_warnings(func):
"""Decorator to suppress streamlit warnings in functions."""
def wrapper(*args, **kwargs):
with warnings.catch_warnings():
warnings.simplefilter('ignore')
return func(*args, **kwargs)
return wrapper
# ==========================
# Shared Color Themes
# ==========================
# Light theme colors - consistent across UI and plots
LIGHT_COLORS = {
'background': '#F7FAFC',
'figure_background': '#FFFFFF',
'sidebar_bg_start': '#F0F4F8',
'sidebar_bg_end': '#E4EBF3',
'border_light': '#E2E8F0',
'border_medium': '#CBD5E1',
'text_primary': '#1F2933',
'text_secondary': '#334E68',
'text_tertiary': '#52606D',
'text_light': '#829AB1',
'card_background': '#FFFFFF',
'code_background': '#EEF2FF',
'code_text': '#1E3A8A',
'button_bg_start': '#2563EB',
'button_bg_end': '#1D4ED8',
'button_hover_start': '#1D4ED8',
'button_hover_end': '#1E40AF',
'alert_error_bg': '#FDE8E8',
'alert_error_border': '#F76B6B',
'alert_error_text': '#B91C1C',
'alert_info_bg': '#E0F2FE',
'alert_info_border': '#3B82F6',
'alert_info_text': '#1E3A8A',
'warning_bg': '#FEF3C7',
'warning_border': '#F59E0B',
'success_bg': '#DCFCE7',
'success_border': '#16A34A',
'generate_button_bg': '#047857',
'generate_button_hover': '#0F9D58',
'panel_background': '#FFFFFF',
'panel_border': '#E2E8F0',
'panel_shadow': '0 8px 24px rgba(15, 23, 42, 0.08)',
# Plot-specific colors
'axes_background': '#FFFFFF',
'grid_color': '#E2E8F0',
'spine_color': '#CBD5E1',
}
# Paper theme colors - pure white backgrounds for publication
PAPER_COLORS = {
'background': '#FFFFFF',
'figure_background': '#FFFFFF',
'sidebar_bg_start': '#F5F7FA',
'sidebar_bg_end': '#E4EBF3',
'border_light': '#E2E8F0',
'border_medium': '#CBD5E1',
'text_primary': '#1F2933',
'text_secondary': '#334E68',
'text_tertiary': '#52606D',
'text_light': '#829AB1',
'card_background': '#FFFFFF',
'code_background': '#EEF2FF',
'code_text': '#1E3A8A',
'button_bg_start': '#2563EB',
'button_bg_end': '#1D4ED8',
'button_hover_start': '#1D4ED8',
'button_hover_end': '#1E40AF',
'alert_error_bg': '#FDE8E8',
'alert_error_border': '#F76B6B',
'alert_error_text': '#B91C1C',
'alert_info_bg': '#E0F2FE',
'alert_info_border': '#3B82F6',
'alert_info_text': '#1E3A8A',
'warning_bg': '#FEF3C7',
'warning_border': '#F59E0B',
'success_bg': '#DCFCE7',
'success_border': '#16A34A',
'generate_button_bg': '#047857',
'generate_button_hover': '#0F9D58',
'panel_background': '#FFFFFF',
'panel_border': '#E2E8F0',
'panel_shadow': '0 8px 24px rgba(15, 23, 42, 0.08)',
# Plot-specific colors - pure white for papers
'axes_background': '#FFFFFF',
'grid_color': '#E2E8F0',
'spine_color': '#CBD5E1',
}
# Dark theme colors - consistent across UI and plots
DARK_COLORS = {
'background': '#0F172A',
'figure_background': '#1E293B',
'sidebar_bg_start': '#111C2E',
'sidebar_bg_end': '#1B2537',
'border_light': '#27364C',
'border_medium': '#334155',
'text_primary': '#F8FAFC',
'text_secondary': '#CBD5F5',
'text_tertiary': '#94A3B8',
'text_light': '#64748B',
'card_background': '#1F2937',
'code_background': '#1E3A5F',
'code_text': '#C7D2FE',
'button_bg_start': '#3B82F6',
'button_bg_end': '#2563EB',
'button_hover_start': '#2563EB',
'button_hover_end': '#1D4ED8',
'alert_error_bg': '#451A1A',
'alert_error_border': '#F87171',
'alert_error_text': '#FCA5A5',
'alert_info_bg': '#1E293B',
'alert_info_border': '#60A5FA',
'alert_info_text': '#BFDBFE',
'warning_bg': '#3D2D12',
'warning_border': '#FBBF24',
'success_bg': '#163225',
'success_border': '#34D399',
'generate_button_bg': '#10B981',
'generate_button_hover': '#34D399',
'panel_background': '#1F2937',
'panel_border': '#334155',
'panel_shadow': '0 18px 36px rgba(2, 6, 23, 0.55)',
# Plot-specific colors
'axes_background': '#0F172A',
'grid_color': '#27364C',
'spine_color': '#334155',
}
@_suppress_streamlit_warnings
def get_current_colors():
"""Return the active color scheme, defaulting to the light palette."""
try:
dark_mode = getattr(st.session_state, 'dark_theme', False)
except Exception:
dark_mode = False
return DARK_COLORS if dark_mode else LIGHT_COLORS
# ==========================
# Plot Styling
# ==========================
# Font configuration
DEFAULT_FONT_FAMILY = 'sans-serif'
try:
fm.fontManager.addfont('/usr/share/fonts/truetype/msttcorefonts/Arial.ttf')
PLOT_STYLE_FONT_FAMILY = 'Arial'
print("Successfully loaded Arial font.")
except FileNotFoundError:
print("Arial.ttf not found. Using default system font.")
PLOT_STYLE_FONT_FAMILY = DEFAULT_FONT_FAMILY
except Exception as e:
print(f"An error occurred while trying to load Arial font: {e}. Using default system font.")
PLOT_STYLE_FONT_FAMILY = DEFAULT_FONT_FAMILY
# Color constants for plots
PLOT_COLORS = {
'input_similarity': sns.color_palette('rocket', as_cmap=True),
'output_difference': sns.cubehelix_palette(start=.2, rot=-.3, dark=0, light=0.85,
reverse=True, as_cmap=True),
'conflict': sns.cubehelix_palette(start=2, rot=0, dark=0, light=0.85,
reverse=True, as_cmap=True),
'output_biomechanical': sns.cubehelix_palette(start=2.8, rot=0.4, dark=0, light=0.85,
reverse=True, as_cmap=True)
}
# Additional palettes
purple_helix = sns.cubehelix_palette(start=.2, rot=-.4, dark=0, light=0.85,
reverse=True, as_cmap=True)
my_purple_helix = sns.cubehelix_palette(start=.2, rot=-.1, dark=0, light=0.85,
reverse=True, as_cmap=True)
def get_plot_style(style='default'):
"""Get plot style with specified color theme.
Args:
style: 'default' for cream theme, 'paper' for pure white backgrounds, 'dark' for dark theme
"""
if style == 'paper':
theme_colors = PAPER_COLORS
elif style == 'dark':
theme_colors = DARK_COLORS
else: # default
theme_colors = get_current_colors()
return {
'font_family': PLOT_STYLE_FONT_FAMILY,
'font_size': 18,
'title_size': 20,
'label_size': 18,
'tick_size': 15,
'tick_length': 5,
'tick_width': 0.5,
'tick_pad': 5,
'label_pad_x': -15,
'label_pad_y': -35,
'figure_dpi': 300,
'aspect_ratio': 'equal',
'subplot_wspace': 0.05,
'subplot_hspace': 0.1,
# Theme-specific styling
'figure_facecolor': theme_colors['figure_background'],
'axes_facecolor': theme_colors['axes_background'],
'text_color': theme_colors['text_primary'],
'grid_color': theme_colors['grid_color'],
'spine_color': theme_colors['spine_color'],
}
def set_plot_style(style='default'):
"""Set consistent plot styling across all figures.
Args:
style: 'default' for cream theme, 'paper' for pure white backgrounds, 'dark' for dark theme
"""
plot_style = get_plot_style(style=style)
plt.rcParams['font.family'] = plot_style['font_family']
plt.rcParams['font.size'] = plot_style['font_size']
plt.rcParams['axes.labelsize'] = plot_style['label_size']
plt.rcParams['axes.titlesize'] = plot_style['title_size']
plt.rcParams['xtick.labelsize'] = plot_style['tick_size']
plt.rcParams['ytick.labelsize'] = plot_style['tick_size']
plt.rcParams['xtick.major.pad'] = plot_style['tick_pad']
plt.rcParams['ytick.major.pad'] = plot_style['tick_pad']
plt.rcParams['figure.dpi'] = plot_style['figure_dpi']
plt.rcParams['figure.subplot.wspace'] = plot_style['subplot_wspace']
plt.rcParams['figure.subplot.hspace'] = plot_style['subplot_hspace']
# Apply theme styling
plt.rcParams['figure.facecolor'] = plot_style['figure_facecolor']
plt.rcParams['axes.facecolor'] = plot_style['axes_facecolor']
plt.rcParams['text.color'] = plot_style['text_color']
plt.rcParams['axes.labelcolor'] = plot_style['text_color']
plt.rcParams['xtick.color'] = plot_style['text_color']
plt.rcParams['ytick.color'] = plot_style['text_color']
plt.rcParams['axes.edgecolor'] = plot_style['spine_color']
plt.rcParams['grid.color'] = plot_style['grid_color']
plt.rcParams['grid.alpha'] = 0.7
def apply_theme_to_figure(fig, ax=None):
"""Apply current theme to an existing figure and axes"""
theme_colors = get_current_colors()
if fig:
fig.patch.set_facecolor(theme_colors['figure_background'])
if ax is not None:
# Handle single axes or iterables of axes robustly
if hasattr(ax, 'flatten'):
axes_list = ax.flatten()
elif isinstance(ax, (list, tuple)):
axes_list = ax
elif hasattr(ax, '__iter__'):
axes_list = list(ax)
else:
axes_list = [ax]
for axis in axes_list:
if axis is not None:
axis.set_facecolor(theme_colors['axes_background'])
# Update text colors
axis.title.set_color(theme_colors['text_primary'])
axis.xaxis.label.set_color(theme_colors['text_primary'])
axis.yaxis.label.set_color(theme_colors['text_primary'])
# Update tick colors
axis.tick_params(colors=theme_colors['text_primary'])
# Update spine colors
for spine in axis.spines.values():
spine.set_color(theme_colors['spine_color'])
# Update grid
axis.grid(True, color=theme_colors['grid_color'], alpha=0.7)
return fig, ax
def set_paper_plot_style():
"""Convenience function to set pure white backgrounds for paper publication."""
set_plot_style(style='paper')
# Legacy function name for backward compatibility
def apply_cream_theme_to_figure(fig, ax=None):
"""Apply current theme to an existing figure and axes (legacy function name)"""
return apply_theme_to_figure(fig, ax)
# ==========================
# Streamlit UI Styling
# ==========================
def get_base_css():
"""Returns the base CSS styling used across all pages."""
return f"""
<style>
/* Main background styling */
.stApp {{
background: {get_current_colors()['background']};
color: {get_current_colors()['text_primary']} !important;
}}
header[role="banner"] {{
display: none !important;
}}
header[data-testid="stHeader"] {{
display: none !important;
}}
div[data-testid="stDecoration"] {{
display: none !important;
}}
div[data-testid="stToolbar"] {{
display: none !important;
}}
.main .block-container {{
padding-top: 6rem;
padding-bottom: 2rem;
background-color: {get_current_colors()['background']};
border-radius: 15px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
margin-top: 1rem;
color: {get_current_colors()['text_primary']} !important;
}}
/* Explicit text color styling for all elements */
.stApp, .stApp * {{
color: {get_current_colors()['text_primary']} !important;
}}
code {{
background: {get_current_colors()['code_background']};
color: {get_current_colors()['code_text']} !important;
padding: 0.15rem 0.45rem;
border-radius: 6px;
font-weight: 600;
}}
pre code {{
display: block;
padding: 0.75rem 1rem;
border-radius: 10px;
}}
/* Button styling */
.stButton>button {{
width: 100%;
margin-top: 1rem;
margin-bottom: 1rem;
background: linear-gradient(45deg, {get_current_colors()['button_bg_start']}, {get_current_colors()['button_bg_end']});
color: white !important;
border: none;
border-radius: 10px;
padding: 0.75rem 1rem;
font-weight: 600;
transition: all 0.3s ease;
box-shadow: 0 2px 10px rgba(107, 107, 107, 0.2);
}}
.stButton>button:hover {{
background: linear-gradient(45deg, {get_current_colors()['button_hover_start']}, {get_current_colors()['button_hover_end']});
transform: translateY(-2px);
box-shadow: 0 4px 15px rgba(107, 107, 107, 0.3);
color: white !important;
}}
/* Sidebar styling */
section[data-testid="stSidebar"] {{
background: linear-gradient(180deg, {get_current_colors()['sidebar_bg_start']} 0%, {get_current_colors()['figure_background']} 85%);
border-right: 1px solid {get_current_colors()['border_medium']};
color: {get_current_colors()['text_primary']} !important;
margin-top: 5.5rem;
height: calc(100vh - 5.5rem);
}}
section[data-testid="stSidebar"] > div {{
background: transparent;
padding: 1.75rem 1.2rem 2.3rem 1.2rem;
color: {get_current_colors()['text_primary']} !important;
}}
/* Headers styling */
h1 {{
padding-bottom: 1rem;
border-bottom: 3px solid {get_current_colors()['border_medium']};
color: {get_current_colors()['text_primary']} !important;
text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.05);
}}
h2 {{
margin-top: 2rem;
padding-bottom: 0.5rem;
color: {get_current_colors()['text_secondary']} !important;
font-weight: 600;
}}
h3 {{
margin-top: 1.5rem;
color: {get_current_colors()['text_tertiary']} !important;
font-weight: 600;
}}
/* Alert box styling */
.auth-alert {{
background: {get_current_colors()['alert_error_bg']};
color: {get_current_colors()['alert_error_text']} !important;
padding: 15px;
border-radius: 10px;
margin: 15px 0;
border: 1px solid {get_current_colors()['alert_error_border']};
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.03);
}}
.auth-info {{
background: {get_current_colors()['alert_info_bg']};
color: {get_current_colors()['alert_info_text']} !important;
padding: 15px;
border-radius: 10px;
margin: 15px 0;
border: 1px solid {get_current_colors()['alert_info_border']};
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.03);
}}
/* Hide default multipage navigation */
section[data-testid="stSidebar"] nav[data-testid="stSidebarNav"] {{
display: none !important;
}}
section[data-testid="stSidebar"] div[data-testid="stSidebarNav"] {{
display: none !important;
}}
section[data-testid="stSidebarNav"] {{
display: none !important;
}}
nav[data-testid="stSidebarNav"] {{
display: none !important;
}}
/* Top navigation */
.top-nav-outer {{
position: fixed;
top: 0;
left: 0;
width: 100%;
z-index: 1000;
padding: 0.85rem 1.5rem;
background: linear-gradient(180deg, {get_current_colors()['background']} 0%, {get_current_colors()['figure_background']} 100%);
border-bottom: 1px solid {get_current_colors()['border_medium']};
box-shadow: 0 12px 28px rgba(0, 0, 0, 0.1);
}}
.top-nav-inner {{
max-width: 1100px;
margin: 0 auto;
display: flex;
gap: 0.75rem;
justify-content: center;
flex-wrap: wrap;
}}
.top-nav-inner .nav-link {{
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 150px;
padding: 0.55rem 1rem;
font-weight: 600;
border-radius: 12px;
text-decoration: none;
background: linear-gradient(180deg, {get_current_colors()['figure_background']} 0%, {get_current_colors()['background']} 100%) !important;
border: 2px solid {get_current_colors()['border_medium']} !important;
color: {get_current_colors()['text_secondary']} !important;
box-shadow: 0 6px 14px rgba(0, 0, 0, 0.08);
transition: all 0.2s ease;
}}
.top-nav-inner .nav-link:hover {{
background: linear-gradient(180deg, {get_current_colors()['background']} 0%, {get_current_colors()['figure_background']} 100%) !important;
color: {get_current_colors()['text_primary']} !important;
border-color: {get_current_colors()['text_secondary']} !important;
transform: translateY(-2px);
}}
.nav-active {{
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 160px;
padding: 0.65rem 1.1rem;
border-radius: 14px;
border: 2px solid {get_current_colors()['text_secondary']};
background: linear-gradient(180deg, {get_current_colors()['figure_background']} 0%, {get_current_colors()['background']} 100%);
font-weight: 700;
box-shadow: 0 10px 20px rgba(0, 0, 0, 0.12);
color: {get_current_colors()['text_primary']} !important;
text-align: center;
}}
</style>
"""
def get_home_page_css():
"""Returns additional CSS specific to the home page."""
return f"""
<style>
/* Hide sidebar affordance on the landing page where it is unused */
[data-testid="stSidebar"] {{
display: none !important;
}}
/* Navigation button styling */
.nav-button {{
border: 2px solid {get_current_colors()['border_medium']};
border-radius: 12px;
padding: 20px;
text-align: center;
margin-bottom: 15px;
background: {get_current_colors()['background']};
transition: all 0.3s ease;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.03);
color: {get_current_colors()['text_primary']} !important;
}}
.nav-button:hover {{
background: {get_current_colors()['figure_background']};
transform: translateY(-3px);
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.08);
border-color: {get_current_colors()['text_secondary']};
color: {get_current_colors()['text_primary']} !important;
}}
.nav-button h3 {{
color: {get_current_colors()['text_secondary']} !important;
margin-bottom: 0.5rem;
}}
/* Description boxes */
.description-box {{
text-align: center;
padding: 15px;
border-radius: 10px;
margin-top: -10px;
background: {get_current_colors()['card_background']};
border: 1px solid {get_current_colors()['border_medium']};
box-shadow: none;
color: {get_current_colors()['text_primary']} !important;
}}
/* Hero banner */
.hero {{
background: linear-gradient(135deg, {get_current_colors()['figure_background']}, {get_current_colors()['background']});
border: 2px solid {get_current_colors()['border_medium']};
border-radius: 18px;
padding: 2.5rem 3rem;
margin-bottom: 2.5rem;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.08);
color: {get_current_colors()['text_primary']} !important;
}}
.hero-overline {{
text-transform: uppercase;
letter-spacing: 0.28rem;
font-size: 0.75rem;
font-weight: 700;
color: {get_current_colors()['text_tertiary']} !important;
display: inline-block;
margin-bottom: 0.8rem;
}}
.hero-subtext {{
font-size: 1.1rem;
line-height: 1.7rem;
color: {get_current_colors()['text_secondary']} !important;
margin-top: 1rem;
max-width: 720px;
}}
/* Grid layouts for value props and steps */
.value-grid {{
display: grid;
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
gap: 1.25rem;
margin-top: 1.5rem;
}}
.value-card {{
background: {get_current_colors()['card_background']};
border: 1px solid {get_current_colors()['border_medium']};
border-radius: 12px;
padding: 1.25rem;
box-shadow: none;
color: {get_current_colors()['text_primary']} !important;
}}
.value-card h3 {{
margin-bottom: 0.6rem;
color: {get_current_colors()['text_secondary']} !important;
font-size: 1.1rem;
}}
.checklist {{
list-style: none;
padding: 0;
margin: 1rem 0 0 0;
}}
.checklist li {{
display: flex;
align-items: flex-start;
gap: 0.6rem;
margin-bottom: 0.75rem;
color: {get_current_colors()['text_secondary']} !important;
}}
.checklist span {{
font-weight: 600;
color: {get_current_colors()['text_primary']} !important;
}}
.step-grid {{
display: grid;
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
gap: 1.5rem;
margin-top: 1.5rem;
}}
.step-card {{
background: {get_current_colors()['card_background']};
border: 1px solid {get_current_colors()['border_medium']};
border-radius: 12px;
padding: 1.5rem;
box-shadow: none;
position: relative;
color: {get_current_colors()['text_primary']} !important;
}}
.step-number {{
display: inline-flex;
align-items: center;
justify-content: center;
width: 32px;
height: 32px;
border-radius: 50%;
background: {get_current_colors()['text_secondary']};
color: {get_current_colors()['background']} !important;
font-weight: 700;
margin-bottom: 1rem;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
}}
.deliverables-grid {{
display: grid;
grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
gap: 1.25rem;
margin-top: 1.5rem;
}}
.deliverable-card {{
background: {get_current_colors()['card_background']};
border: 1px solid {get_current_colors()['border_medium']};
border-radius: 12px;
padding: 1.25rem;
box-shadow: none;
color: {get_current_colors()['text_primary']} !important;
}}
.auth-help {{
margin-top: 1.5rem;
color: {get_current_colors()['text_secondary']} !important;
text-align: center;
font-size: 0.95rem;
}}
.auth-help a {{
color: {get_current_colors()['text_secondary']} !important;
font-weight: 600;
}}
</style>
"""
def get_documentation_page_css():
"""Returns additional CSS specific to the documentation page."""
return f"""
<style>
/* Reduce sidebar padding so controls sit right below the nav */
[data-testid="stSidebar"] > div {{
padding: 1.05rem 1.1rem 1.8rem 1.1rem !important;
}}
.gaussian-explanation-container {{
padding: 20px;
background: {get_current_colors()['background']};
border-radius: 12px;
margin-bottom: 20px;
border: 2px solid {get_current_colors()['border_medium']};
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.03);
color: {get_current_colors()['text_primary']} !important;
}}
.metric-card {{
background: {get_current_colors()['background']};
padding: 15px;
border-radius: 10px;
border: 2px solid {get_current_colors()['border_medium']};
margin: 10px 0;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
color: {get_current_colors()['text_primary']} !important;
}}
.glossary-term {{
background: {get_current_colors()['background']};
padding: 15px;
border-radius: 10px;
border-left: 4px solid {get_current_colors()['text_secondary']};
margin: 10px 0;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
color: {get_current_colors()['text_primary']} !important;
}}
</style>
"""
def get_tool_page_css():
"""Returns additional CSS specific to the analysis tool page."""
return f"""
<style>
/* Reduce sidebar padding so controls sit right below the nav */
[data-testid="stSidebar"] > div {{
padding: 1.05rem 1.1rem 1.8rem 1.1rem !important;
}}
/* Expander styling */
.streamlit-expanderHeader {{
background-color: {get_current_colors()['figure_background']};
border: 2px solid {get_current_colors()['border_medium']};
border-radius: 8px;
color: {get_current_colors()['text_primary']} !important;
font-weight: 600;
box-shadow: 0 6px 14px rgba(0, 0, 0, 0.06);
}}
/* Generate button special styling */
div[data-testid="stButton"] button {{
background: linear-gradient(45deg, {get_current_colors()['generate_button_bg']}, {get_current_colors()['generate_button_hover']}) !important;
color: white !important;
border: none !important;
font-weight: 700 !important;
font-size: 1.1rem !important;
padding: 1rem !important;
box-shadow: 0 3px 15px rgba(34, 139, 34, 0.3) !important;
}}
div[data-testid="stButton"] button:hover {{
background: linear-gradient(45deg, {get_current_colors()['generate_button_hover']}, #7CFC00) !important;
transform: translateY(-3px) !important;
box-shadow: 0 5px 20px rgba(34, 139, 34, 0.4) !important;
color: white !important;
}}
.quick-start {{
display: grid;
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
gap: 1rem;
margin-bottom: 1.5rem;
}}
.quick-step {{
background: linear-gradient(135deg, {get_current_colors()['card_background']}, {get_current_colors()['figure_background']});
border: 1px solid {get_current_colors()['border_medium']};
border-radius: 14px;
padding: 1rem 1.25rem;
box-shadow: 0 10px 20px rgba(0, 0, 0, 0.08);
color: {get_current_colors()['text_primary']} !important;
}}
.quick-step .step-index {{
display: inline-block;
margin-bottom: 0.45rem;
font-size: 0.85rem;
font-weight: 700;
letter-spacing: 0.08em;
text-transform: uppercase;
background: {get_current_colors()['text_secondary']};
color: {get_current_colors()['background']} !important;
padding: 0.2rem 0.75rem;
border-radius: 999px;
box-shadow: 0 6px 16px rgba(0, 0, 0, 0.12);
}}
.sidebar-step-title {{
font-size: 0.85rem;
letter-spacing: 0.1em;
text-transform: uppercase;
font-weight: 700;
margin: 1.1rem 0 0.5rem 0;
color: {get_current_colors()['text_secondary']} !important;
}}
.tooltip-container {{
display: flex;
flex-direction: column;
gap: 0.75rem;
background: {get_current_colors()['figure_background']};
border: 1px solid {get_current_colors()['border_medium']};
border-radius: 16px;
padding: 1rem;
box-shadow: 0 12px 28px rgba(0, 0, 0, 0.08);
margin-bottom: 1rem;
}}
.tooltip-content {{
background: {get_current_colors()['background']};
border-radius: 12px;
padding: 0.75rem 1rem;
box-shadow: inset 0 0 0 1px {get_current_colors()['border_light']};
display: flex;
flex-direction: column;
gap: 0.45rem;
}}
.tooltip-content .color-legend {{
display: flex;
align-items: center;
gap: 0.5rem;
color: {get_current_colors()['text_secondary']} !important;
}}
.tooltip-content .color-box {{
width: 14px;
height: 14px;
border-radius: 4px;
border: 1px solid {get_current_colors()['border_light']};
}}
</style>
"""
def apply_base_styling():
"""Apply the base styling to the current Streamlit page."""
if not _STREAMLIT_AVAILABLE:
return
st.markdown(get_base_css(), unsafe_allow_html=True)
def apply_home_page_styling():
"""Apply styling specific to the home page."""
if not _STREAMLIT_AVAILABLE:
return
st.markdown(get_base_css(), unsafe_allow_html=True)
st.markdown(get_home_page_css(), unsafe_allow_html=True)
def apply_documentation_page_styling():
"""Apply styling specific to the documentation page."""
if not _STREAMLIT_AVAILABLE:
return
st.markdown(get_base_css(), unsafe_allow_html=True)
st.markdown(get_documentation_page_css(), unsafe_allow_html=True)
def apply_tool_page_styling():
"""Apply styling specific to the analysis tool page."""
if not _STREAMLIT_AVAILABLE:
return
st.markdown(get_base_css(), unsafe_allow_html=True)
st.markdown(get_tool_page_css(), unsafe_allow_html=True)