jmontp commited on
Commit
580d7d2
·
1 Parent(s): 642aebb

Sync from reorganized task-similarity-analysis repository

Browse files

- Update all module imports to match reorganized structure
- Consolidate styling modules (shared_styling and plot_styling now unified)
- Add CLAUDE.md with clear instructions not to modify directly
- All changes synced from main repository using sync_dashboard.py script

CLAUDE.md ADDED
@@ -0,0 +1,168 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # CLAUDE.md
2
+
3
+ ⚠️ **DO NOT MODIFY THIS FILE DIRECTLY** ⚠️
4
+
5
+ This file is automatically synchronized from the main task-similarity-analysis repository.
6
+ Any changes should be made to `/home/jmontp/workspace/task-similarity-analysis/CLAUDE.md`
7
+ and will be synced here using the sync_dashboard.py script.
8
+
9
+ ---
10
+
11
+ This file provides guidance to Claude Code (claude.ai/code) when working with the Task Similarity Analysis project.
12
+
13
+ ## Project Overview
14
+
15
+ The Task Similarity Analysis project is a web-based tool for analyzing potential conflicts in sensor configurations for lower-limb wearable robots (exoskeletons and prostheses). It helps identify when similar sensor readings might require different control outputs across various locomotion tasks.
16
+
17
+ ## Key Concepts
18
+
19
+ ### Similarity Analysis
20
+ - **Multivariate Gaussian Overlap**: Core statistical method for measuring sensor distribution similarity
21
+ - **Bhattacharyya-like Distance**: Vectorized metric for efficient computation
22
+ - **Output-Difference Filtering**: Accounts for biomechanical significance of control differences
23
+ - **Phase-Windowed Analysis**: Analyzes current phase data with optional historical context
24
+
25
+ ### Analysis Levels
26
+ 1. **High Level**: Global conflict detection across all tasks
27
+ 2. **Medium Level**: Task-type comparisons (e.g., incline vs decline)
28
+ 3. **Low Level**: Specific parameter comparisons (e.g., 5° vs 10° incline)
29
+
30
+ ## Development Commands
31
+
32
+ ```bash
33
+ # Run the Streamlit application
34
+ streamlit run Home.py
35
+
36
+ # Install dependencies
37
+ pip install -r requirements.txt
38
+
39
+ # Generate pre-calculated statistics (if needed)
40
+ python generate_results_dicts.py
41
+
42
+ # Run tests
43
+ python test_multivariate_gaussian_overlap.py
44
+ python test_plot_similarity.py
45
+ python test_sensor_illustration.py
46
+
47
+ # Profile performance
48
+ python profile_multivariate_gaussian_overlap.py
49
+
50
+ # Preprocess data for hosting
51
+ python preprocess_data_for_hosting.py
52
+
53
+ # Sync to dashboard repository
54
+ python sync_to_dashboard_repo.py
55
+ ```
56
+
57
+ ## Project Structure
58
+
59
+ ### Core Application Files
60
+ - `Home.py`: Main Streamlit application entry point with authentication
61
+ - `pages/02_Tool.py`: Interactive analysis tool interface
62
+ - `pages/01_Documentation.py`: User documentation and tutorials
63
+ - `pages/02_Glossary.py`: Technical terms and definitions
64
+
65
+ ### Analysis Engine
66
+ - `multivariate_gaussian_overlap.py`: Core statistical computations
67
+ - `plot_similarity.py`: Visualization generation for heatmaps and conflict maps
68
+ - `sensor_illustration.py`: Visual representation of sensor configurations
69
+ - `data_tools.py`: Data processing and filtering utilities
70
+
71
+ ### Configuration
72
+ - `config.py`: Application settings and constants
73
+ - `shared_styling.py`: Common visual styling definitions
74
+ - `plot_styling.py`: Plot-specific styling configurations
75
+
76
+ ### Data Files
77
+ - `cached_data/precalculated_stats.pkl.gz`: Pre-computed statistics for performance
78
+ - Results stored in dictionaries with hierarchical task organization
79
+
80
+ ## Key Development Patterns
81
+
82
+ ### Performance Optimization
83
+ - Pre-calculate statistics for common queries
84
+ - Use vectorized operations for overlap calculations
85
+ - Implement caching for expensive computations
86
+ - Compress pickle files for storage efficiency
87
+
88
+ ### Visualization Best Practices
89
+ - Use consistent color schemes across all plots
90
+ - Support both light and dark themes
91
+ - Generate publication-quality figures
92
+ - Include clear axis labels and legends
93
+
94
+ ### Data Structure
95
+ ```python
96
+ # Results dictionary structure
97
+ {
98
+ 'task1_name': {
99
+ 'task2_name': {
100
+ 'overlap_matrix': np.array, # Phase x Phase overlap values
101
+ 'output_diff_matrix': np.array, # Output differences
102
+ 'filtered_overlap': np.array, # Overlap after filtering
103
+ }
104
+ }
105
+ }
106
+ ```
107
+
108
+ ### Sensor Configuration Format
109
+ - Unilateral: Single leg sensors (e.g., 'hip_angle_r')
110
+ - Bilateral: Both legs (e.g., 'hip_angle_r', 'hip_angle_l')
111
+ - Available measurements: angles, velocities, torques
112
+ - Joint support: hip, knee, ankle, foot
113
+
114
+ ## Common Tasks
115
+
116
+ ### Adding New Sensors
117
+ 1. Update sensor lists in `config.py`
118
+ 2. Regenerate statistics with new sensor configurations
119
+ 3. Update sensor illustration if needed
120
+
121
+ ### Modifying Analysis Methods
122
+ 1. Core algorithms in `multivariate_gaussian_overlap.py`
123
+ 2. Update visualization in `plot_similarity.py`
124
+ 3. Run performance profiling to ensure efficiency
125
+
126
+ ### Updating Documentation
127
+ 1. Edit markdown content in `pages/01_Documentation.py`
128
+ 2. Update glossary terms in `pages/02_Glossary.py`
129
+ 3. Keep examples current with latest features
130
+
131
+ ## Important Considerations
132
+
133
+ ### Statistical Validity
134
+ - Ensure sufficient data samples for Gaussian approximation
135
+ - Consider phase-dependency of sensor readings
136
+ - Account for inter-subject variability
137
+
138
+ ### User Experience
139
+ - Maintain responsive interface with progress indicators
140
+ - Provide clear error messages for invalid configurations
141
+ - Include helpful tooltips and documentation links
142
+
143
+ ### Performance
144
+ - Pre-calculate expensive operations when possible
145
+ - Use efficient data structures (NumPy arrays)
146
+ - Implement lazy loading for large datasets
147
+
148
+ ## Testing Guidelines
149
+
150
+ ### Unit Tests
151
+ - Test statistical calculations with known inputs
152
+ - Verify visualization outputs
153
+ - Check edge cases (empty data, single samples)
154
+
155
+ ### Integration Tests
156
+ - Test full analysis pipeline end-to-end
157
+ - Verify Streamlit session state management
158
+ - Check authentication flow
159
+
160
+ ### Visual Tests
161
+ - Compare generated plots against reference images
162
+ - Verify color schemes and styling consistency
163
+ - Test responsive layout on different screen sizes
164
+
165
+ ---
166
+
167
+ ⚠️ **REMINDER: DO NOT MODIFY THIS FILE DIRECTLY** ⚠️
168
+ Changes should be made to the source file in task-similarity-analysis repository.
__pycache__/shared_styling.cpython-312.pyc ADDED
Binary file (32.5 kB). View file
 
config.py CHANGED
@@ -21,7 +21,7 @@ AVAILABLE_TASKS = ['decline_walking', 'level_walking', 'incline_walking',
21
  OUTPUTS_TOTAL = ['hip_torque_s_r', 'knee_torque_s_r', 'ankle_torque_s_r',
22
  'hip_power_s_r', 'knee_power_s_r', 'ankle_power_s_r']
23
 
24
- # Not strictly needed by preprocess_data_for_hosting.py but good to keep related constants together.
25
  ANALYSIS_ABSTRACTION_LEVELS = ['High', 'Medium/Low']
26
 
27
  # Task configurations for pre-calculation and analysis
 
21
  OUTPUTS_TOTAL = ['hip_torque_s_r', 'knee_torque_s_r', 'ankle_torque_s_r',
22
  'hip_power_s_r', 'knee_power_s_r', 'ankle_power_s_r']
23
 
24
+ # Not strictly needed by src.data.preprocessor.py but good to keep related constants together.
25
  ANALYSIS_ABSTRACTION_LEVELS = ['High', 'Medium/Low']
26
 
27
  # Task configurations for pre-calculation and analysis
multivariate_gaussian_overlap.py CHANGED
@@ -56,7 +56,7 @@ try:
56
  # else:
57
  # print(f"Successfully loaded {HOSTING_STATS_PATH}")
58
  except FileNotFoundError:
59
- print(f"ERROR: Could not load {HOSTING_STATS_PATH}. Please run preprocess_data_for_hosting.py first.")
60
  HOSTING_STATS = None
61
  except Exception as e:
62
  print(f"Error loading {HOSTING_STATS_PATH}: {e}")
@@ -453,7 +453,7 @@ def calculate_similarity_portrait_abstraction(sensors: Union[tuple, list], time_
453
  # If subjects could not be inferred or HOSTING_STATS is None, this part will be problematic.
454
  # The original code used total_data['subject'].unique(). This is no longer available.
455
  # For now, let's assume LOW_LEVEL_TASKS and iterating these subjects is the way.
456
- # The `preprocess_data_for_hosting.py` iterates `unique_subjects = total_data['subject'].unique()`.
457
  # So `HOSTING_STATS` keys will contain all subjects that had data.
458
  # `unique_subjects_from_config_or_data` should capture these.
459
  # If this list is empty and HOSTING_STATS is populated, it means keys are not as expected.
@@ -479,7 +479,7 @@ def calculate_similarity_portrait_abstraction(sensors: Union[tuple, list], time_
479
  print("Warning: subjects_to_iterate is empty. Statistics pre-computation might be skipped or fail.")
480
  # Potentially, if LOW_LEVEL_TASKS is what defines the scope, and subjects are per task_config in HOSTING_STATS
481
  # then subject iteration should be nested inside task_config iteration if HOSTING_STATS keys are task_config specific.
482
- # The current `preprocess_data_for_hosting.py` creates keys `(task_config_tuple, subject_str)`.
483
 
484
  total_combinations_to_calc_stats_for = 0
485
  tasks_for_stats_calc_t1 = []
 
56
  # else:
57
  # print(f"Successfully loaded {HOSTING_STATS_PATH}")
58
  except FileNotFoundError:
59
+ print(f"ERROR: Could not load {HOSTING_STATS_PATH}. Please run src.data.preprocessor.py first.")
60
  HOSTING_STATS = None
61
  except Exception as e:
62
  print(f"Error loading {HOSTING_STATS_PATH}: {e}")
 
453
  # If subjects could not be inferred or HOSTING_STATS is None, this part will be problematic.
454
  # The original code used total_data['subject'].unique(). This is no longer available.
455
  # For now, let's assume LOW_LEVEL_TASKS and iterating these subjects is the way.
456
+ # The `src.data.preprocessor.py` iterates `unique_subjects = total_data['subject'].unique()`.
457
  # So `HOSTING_STATS` keys will contain all subjects that had data.
458
  # `unique_subjects_from_config_or_data` should capture these.
459
  # If this list is empty and HOSTING_STATS is populated, it means keys are not as expected.
 
479
  print("Warning: subjects_to_iterate is empty. Statistics pre-computation might be skipped or fail.")
480
  # Potentially, if LOW_LEVEL_TASKS is what defines the scope, and subjects are per task_config in HOSTING_STATS
481
  # then subject iteration should be nested inside task_config iteration if HOSTING_STATS keys are task_config specific.
482
+ # The current `src.data.preprocessor.py` creates keys `(task_config_tuple, subject_str)`.
483
 
484
  total_combinations_to_calc_stats_for = 0
485
  tasks_for_stats_calc_t1 = []
pages/01_Documentation.py CHANGED
@@ -4,7 +4,7 @@ import matplotlib.pyplot as plt
4
  from scipy.stats import norm
5
  import os
6
  from config import AVAILABLE_SENSORS, AVAILABLE_TASKS, OUTPUTS_TOTAL
7
- from plot_styling import set_plot_style, apply_cream_theme_to_figure
8
  from shared_styling import apply_documentation_page_styling, add_theme_toggle
9
 
10
  # Configure the page
 
4
  from scipy.stats import norm
5
  import os
6
  from config import AVAILABLE_SENSORS, AVAILABLE_TASKS, OUTPUTS_TOTAL
7
+ from shared_styling import set_plot_style, apply_cream_theme_to_figure
8
  from shared_styling import apply_documentation_page_styling, add_theme_toggle
9
 
10
  # Configure the page
pages/02_Tool.py CHANGED
@@ -10,7 +10,7 @@ from mpl_toolkits.axes_grid1 import make_axes_locatable
10
  from multivariate_gaussian_overlap import calculate_similarity_portrait_abstraction
11
  from plot_similarity import plot_similarity_measure
12
  from sensor_illustration import LegIllustration
13
- from plot_styling import set_plot_style, apply_cream_theme_to_figure
14
  from shared_styling import apply_tool_page_styling, add_theme_toggle
15
  import io
16
 
 
10
  from multivariate_gaussian_overlap import calculate_similarity_portrait_abstraction
11
  from plot_similarity import plot_similarity_measure
12
  from sensor_illustration import LegIllustration
13
+ from shared_styling import set_plot_style, apply_cream_theme_to_figure
14
  from shared_styling import apply_tool_page_styling, add_theme_toggle
15
  import io
16
 
plot_similarity.py CHANGED
@@ -1,5 +1,5 @@
1
  """Visualization functions for similarity measures"""
2
- from plot_styling import PLOT_COLORS, set_plot_style, get_plot_style
3
  import matplotlib.pyplot as plt
4
  import numpy as np
5
  import seaborn as sns
 
1
  """Visualization functions for similarity measures"""
2
+ from shared_styling import PLOT_COLORS, set_plot_style, get_plot_style
3
  import matplotlib.pyplot as plt
4
  import numpy as np
5
  import seaborn as sns
plot_styling.py CHANGED
@@ -1,62 +1,152 @@
1
- """Module for consistent plot styling across all visualizations"""
 
 
 
2
 
 
3
  import matplotlib.pyplot as plt
4
  import seaborn as sns
5
  import matplotlib.font_manager as fm
6
- import streamlit as st
7
 
8
- # Add Arial font
9
- # Default font family
10
- DEFAULT_FONT_FAMILY = 'sans-serif' # A commonly available default
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
11
 
12
  try:
13
  fm.fontManager.addfont('/usr/share/fonts/truetype/msttcorefonts/Arial.ttf')
14
- # If Arial is successfully added, use it.
15
  PLOT_STYLE_FONT_FAMILY = 'Arial'
16
  print("Successfully loaded Arial font.")
17
  except FileNotFoundError:
18
  print("Arial.ttf not found. Using default system font.")
19
- # If Arial is not found, fall back to a default font.
20
  PLOT_STYLE_FONT_FAMILY = DEFAULT_FONT_FAMILY
21
  except Exception as e:
22
  print(f"An error occurred while trying to load Arial font: {e}. Using default system font.")
23
  PLOT_STYLE_FONT_FAMILY = DEFAULT_FONT_FAMILY
24
 
25
- # Light theme colors
26
- LIGHT_THEME_COLORS = {
27
- 'background': '#FFFFFB', # Very light off-white background
28
- 'figure_background': '#FEFEF8', # Slightly warmer off-white for figure
29
- 'axes_background': '#FFFFFB', # Light off-white for plot area
30
- 'text_primary': '#4A4A4A', # Subtle dark gray for text
31
- 'text_secondary': '#6B6B6B', # Medium gray for labels
32
- 'grid_color': '#F0F0F0', # Very light gray for grid lines
33
- 'spine_color': '#E0E0E0', # Light gray for axes spines
34
- }
35
-
36
- # Dark theme colors
37
- DARK_THEME_COLORS = {
38
- 'background': '#1E1E1E', # Dark background
39
- 'figure_background': '#2D2D2D', # Slightly lighter dark for figure
40
- 'axes_background': '#1E1E1E', # Dark plot area
41
- 'text_primary': '#E8E8E8', # Light gray for text
42
- 'text_secondary': '#B8B8B8', # Medium light gray for labels
43
- 'grid_color': '#404040', # Dark gray for grid lines
44
- 'spine_color': '#505050', # Medium dark gray for axes spines
45
  }
46
 
47
- def get_current_theme_colors():
48
- """Get the current theme colors based on session state."""
49
- if 'dark_theme' not in st.session_state:
50
- st.session_state.dark_theme = False
51
-
52
- return DARK_THEME_COLORS if st.session_state.dark_theme else LIGHT_THEME_COLORS
53
 
54
  def get_plot_style():
55
  """Get plot style with current theme colors."""
56
- theme_colors = get_current_theme_colors()
57
 
58
  return {
59
- 'font_family': PLOT_STYLE_FONT_FAMILY, # Use the determined font family
60
  'font_size': 18,
61
  'title_size': 20,
62
  'label_size': 18,
@@ -78,24 +168,6 @@ def get_plot_style():
78
  'spine_color': theme_colors['spine_color'],
79
  }
80
 
81
- # Color constants
82
- PLOT_COLORS = {
83
- 'input_similarity': sns.color_palette('rocket', as_cmap=True),
84
- 'output_difference': sns.cubehelix_palette(start=.2, rot=-.3, dark=0, light=0.85,
85
- reverse=True, as_cmap=True),
86
- 'conflict': sns.cubehelix_palette(start=2, rot=0, dark=0, light=0.85,
87
- reverse=True, as_cmap=True),
88
- # Define a cubehelix palette starting dark (dark=0) and ending light purple (light=0.85, start=2.8)
89
- 'output_biomechanical': sns.cubehelix_palette(start=2.8, rot=0.4, dark=0, light=0.85,
90
- reverse=True, as_cmap=True)
91
- }
92
-
93
-
94
- purple_helix = sns.cubehelix_palette(start=.2, rot=-.4, dark=0, light=0.85,
95
- reverse=True, as_cmap=True)
96
- my_purple_helix = sns.cubehelix_palette(start=.2, rot=-.1, dark=0, light=0.85,
97
- reverse=True, as_cmap=True)
98
-
99
  def set_plot_style():
100
  """Set consistent plot styling across all figures"""
101
  plot_style = get_plot_style()
@@ -125,7 +197,7 @@ def set_plot_style():
125
 
126
  def apply_theme_to_figure(fig, ax=None):
127
  """Apply current theme to an existing figure and axes"""
128
- theme_colors = get_current_theme_colors()
129
 
130
  if fig:
131
  fig.patch.set_facecolor(theme_colors['figure_background'])
@@ -133,15 +205,11 @@ def apply_theme_to_figure(fig, ax=None):
133
  if ax is not None:
134
  # Handle single axes or list of axes
135
  if hasattr(ax, '__iter__'):
136
- # If it's iterable, check if it's a numpy array or list
137
  if hasattr(ax, 'flatten'):
138
- # It's a numpy array
139
  axes_list = ax.flatten()
140
  else:
141
- # It's a list or other iterable
142
  axes_list = ax
143
  else:
144
- # It's a single axis
145
  axes_list = [ax]
146
 
147
  for axis in axes_list:
@@ -165,7 +233,247 @@ def apply_theme_to_figure(fig, ax=None):
165
 
166
  return fig, ax
167
 
168
- # Keep the old function name for backward compatibility
169
  def apply_cream_theme_to_figure(fig, ax=None):
170
  """Apply current theme to an existing figure and axes (legacy function name)"""
171
- return apply_theme_to_figure(fig, ax)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Unified styling module for both Streamlit UI and matplotlib plots.
3
+ Contains all styling definitions to ensure consistency across the application.
4
+ """
5
 
6
+ import streamlit as st
7
  import matplotlib.pyplot as plt
8
  import seaborn as sns
9
  import matplotlib.font_manager as fm
 
10
 
11
+ # ==========================
12
+ # Shared Color Themes
13
+ # ==========================
14
+
15
+ # Light theme colors - consistent across UI and plots
16
+ LIGHT_COLORS = {
17
+ 'background': '#FFFFFB',
18
+ 'figure_background': '#FEFEF8',
19
+ 'sidebar_bg_start': '#FAFAFA',
20
+ 'sidebar_bg_end': '#F5F5F5',
21
+ 'border_light': '#F0F0F0',
22
+ 'border_medium': '#E0E0E0',
23
+ 'text_primary': '#2C3E50',
24
+ 'text_secondary': '#5D6D7E',
25
+ 'text_tertiary': '#85929E',
26
+ 'text_light': '#A6ACAF',
27
+ 'button_bg_start': '#5D6D7E',
28
+ 'button_bg_end': '#85929E',
29
+ 'button_hover_start': '#85929E',
30
+ 'button_hover_end': '#A6ACAF',
31
+ 'alert_error_bg': '#FFE6E6',
32
+ 'alert_error_border': '#FFAAAA',
33
+ 'alert_error_text': '#CC0000',
34
+ 'alert_info_bg': '#E6F3FF',
35
+ 'alert_info_border': '#99D6FF',
36
+ 'alert_info_text': '#0066CC',
37
+ 'warning_bg': '#FFF8E1',
38
+ 'warning_border': '#FF9800',
39
+ 'success_bg': '#E8F5E8',
40
+ 'success_border': '#4CAF50',
41
+ 'generate_button_bg': '#228B22',
42
+ 'generate_button_hover': '#32CD32',
43
+ # Plot-specific colors
44
+ 'axes_background': '#FFFFFB',
45
+ 'grid_color': '#F0F0F0',
46
+ 'spine_color': '#E0E0E0',
47
+ }
48
+
49
+ # Dark theme colors - consistent across UI and plots
50
+ DARK_COLORS = {
51
+ 'background': '#1E1E1E',
52
+ 'figure_background': '#2D2D2D',
53
+ 'sidebar_bg_start': '#252525',
54
+ 'sidebar_bg_end': '#2A2A2A',
55
+ 'border_light': '#404040',
56
+ 'border_medium': '#505050',
57
+ 'text_primary': '#E8E8E8',
58
+ 'text_secondary': '#B8B8B8',
59
+ 'text_tertiary': '#888888',
60
+ 'text_light': '#666666',
61
+ 'button_bg_start': '#4A5568',
62
+ 'button_bg_end': '#2D3748',
63
+ 'button_hover_start': '#2D3748',
64
+ 'button_hover_end': '#1A202C',
65
+ 'alert_error_bg': '#4A1A1A',
66
+ 'alert_error_border': '#8B0000',
67
+ 'alert_error_text': '#FF6B6B',
68
+ 'alert_info_bg': '#1A2A4A',
69
+ 'alert_info_border': '#4A90E2',
70
+ 'alert_info_text': '#87CEEB',
71
+ 'warning_bg': '#4A3A1A',
72
+ 'warning_border': '#FFA500',
73
+ 'success_bg': '#1A4A1A',
74
+ 'success_border': '#32CD32',
75
+ 'generate_button_bg': '#2E7D32',
76
+ 'generate_button_hover': '#388E3C',
77
+ # Plot-specific colors
78
+ 'axes_background': '#1E1E1E',
79
+ 'grid_color': '#404040',
80
+ 'spine_color': '#505050',
81
+ }
82
+
83
+ def get_current_colors():
84
+ """Get the current color scheme based on session state."""
85
+ if 'dark_theme' not in st.session_state:
86
+ st.session_state.dark_theme = False
87
+
88
+ return DARK_COLORS if st.session_state.dark_theme else LIGHT_COLORS
89
+
90
+ def toggle_theme():
91
+ """Toggle between light and dark themes."""
92
+ if 'dark_theme' not in st.session_state:
93
+ st.session_state.dark_theme = False
94
+ st.session_state.dark_theme = not st.session_state.dark_theme
95
+
96
+ def add_theme_toggle():
97
+ """Add a theme toggle button to the sidebar."""
98
+ with st.sidebar:
99
+ st.markdown("---")
100
+ current_theme = "🌙 Dark" if st.session_state.get('dark_theme', False) else "☀️ Light"
101
+ new_theme = "☀️ Light" if st.session_state.get('dark_theme', False) else "🌙 Dark"
102
+
103
+ if st.button(f"Switch to {new_theme} Theme", key="theme_toggle", use_container_width=True):
104
+ toggle_theme()
105
+ st.rerun()
106
+
107
+ st.caption(f"Current: {current_theme} Theme")
108
+
109
+ # ==========================
110
+ # Plot Styling
111
+ # ==========================
112
+
113
+ # Font configuration
114
+ DEFAULT_FONT_FAMILY = 'sans-serif'
115
 
116
  try:
117
  fm.fontManager.addfont('/usr/share/fonts/truetype/msttcorefonts/Arial.ttf')
 
118
  PLOT_STYLE_FONT_FAMILY = 'Arial'
119
  print("Successfully loaded Arial font.")
120
  except FileNotFoundError:
121
  print("Arial.ttf not found. Using default system font.")
 
122
  PLOT_STYLE_FONT_FAMILY = DEFAULT_FONT_FAMILY
123
  except Exception as e:
124
  print(f"An error occurred while trying to load Arial font: {e}. Using default system font.")
125
  PLOT_STYLE_FONT_FAMILY = DEFAULT_FONT_FAMILY
126
 
127
+ # Color constants for plots
128
+ PLOT_COLORS = {
129
+ 'input_similarity': sns.color_palette('rocket', as_cmap=True),
130
+ 'output_difference': sns.cubehelix_palette(start=.2, rot=-.3, dark=0, light=0.85,
131
+ reverse=True, as_cmap=True),
132
+ 'conflict': sns.cubehelix_palette(start=2, rot=0, dark=0, light=0.85,
133
+ reverse=True, as_cmap=True),
134
+ 'output_biomechanical': sns.cubehelix_palette(start=2.8, rot=0.4, dark=0, light=0.85,
135
+ reverse=True, as_cmap=True)
 
 
 
 
 
 
 
 
 
 
 
136
  }
137
 
138
+ # Additional palettes
139
+ purple_helix = sns.cubehelix_palette(start=.2, rot=-.4, dark=0, light=0.85,
140
+ reverse=True, as_cmap=True)
141
+ my_purple_helix = sns.cubehelix_palette(start=.2, rot=-.1, dark=0, light=0.85,
142
+ reverse=True, as_cmap=True)
 
143
 
144
  def get_plot_style():
145
  """Get plot style with current theme colors."""
146
+ theme_colors = get_current_colors()
147
 
148
  return {
149
+ 'font_family': PLOT_STYLE_FONT_FAMILY,
150
  'font_size': 18,
151
  'title_size': 20,
152
  'label_size': 18,
 
168
  'spine_color': theme_colors['spine_color'],
169
  }
170
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
171
  def set_plot_style():
172
  """Set consistent plot styling across all figures"""
173
  plot_style = get_plot_style()
 
197
 
198
  def apply_theme_to_figure(fig, ax=None):
199
  """Apply current theme to an existing figure and axes"""
200
+ theme_colors = get_current_colors()
201
 
202
  if fig:
203
  fig.patch.set_facecolor(theme_colors['figure_background'])
 
205
  if ax is not None:
206
  # Handle single axes or list of axes
207
  if hasattr(ax, '__iter__'):
 
208
  if hasattr(ax, 'flatten'):
 
209
  axes_list = ax.flatten()
210
  else:
 
211
  axes_list = ax
212
  else:
 
213
  axes_list = [ax]
214
 
215
  for axis in axes_list:
 
233
 
234
  return fig, ax
235
 
236
+ # Legacy function name for backward compatibility
237
  def apply_cream_theme_to_figure(fig, ax=None):
238
  """Apply current theme to an existing figure and axes (legacy function name)"""
239
+ return apply_theme_to_figure(fig, ax)
240
+
241
+ # ==========================
242
+ # Streamlit UI Styling
243
+ # ==========================
244
+
245
+ def get_base_css():
246
+ """Returns the base CSS styling used across all pages."""
247
+ return f"""
248
+ <style>
249
+ /* Main background styling */
250
+ .stApp {{
251
+ background: {get_current_colors()['background']};
252
+ color: {get_current_colors()['text_primary']} !important;
253
+ }}
254
+
255
+ .main .block-container {{
256
+ padding-top: 2rem;
257
+ padding-bottom: 2rem;
258
+ background-color: {get_current_colors()['background']};
259
+ border-radius: 15px;
260
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
261
+ margin-top: 1rem;
262
+ color: {get_current_colors()['text_primary']} !important;
263
+ }}
264
+
265
+ /* Explicit text color styling for all elements */
266
+ .stApp, .stApp * {{
267
+ color: {get_current_colors()['text_primary']} !important;
268
+ }}
269
+
270
+ /* Button styling */
271
+ .stButton>button {{
272
+ width: 100%;
273
+ margin-top: 1rem;
274
+ margin-bottom: 1rem;
275
+ background: linear-gradient(45deg, {get_current_colors()['button_bg_start']}, {get_current_colors()['button_bg_end']});
276
+ color: white !important;
277
+ border: none;
278
+ border-radius: 10px;
279
+ padding: 0.75rem 1rem;
280
+ font-weight: 600;
281
+ transition: all 0.3s ease;
282
+ box-shadow: 0 2px 10px rgba(107, 107, 107, 0.2);
283
+ }}
284
+
285
+ .stButton>button:hover {{
286
+ background: linear-gradient(45deg, {get_current_colors()['button_hover_start']}, {get_current_colors()['button_hover_end']});
287
+ transform: translateY(-2px);
288
+ box-shadow: 0 4px 15px rgba(107, 107, 107, 0.3);
289
+ color: white !important;
290
+ }}
291
+
292
+ /* Sidebar styling */
293
+ section[data-testid="stSidebar"] {{
294
+ background: {get_current_colors()['sidebar_bg_start']};
295
+ border-right: 2px solid {get_current_colors()['border_light']};
296
+ color: {get_current_colors()['text_primary']} !important;
297
+ }}
298
+
299
+ section[data-testid="stSidebar"] > div {{
300
+ background: {get_current_colors()['sidebar_bg_start']};
301
+ padding-top: 2rem;
302
+ color: {get_current_colors()['text_primary']} !important;
303
+ }}
304
+
305
+ /* Headers styling */
306
+ h1 {{
307
+ padding-bottom: 1rem;
308
+ border-bottom: 3px solid {get_current_colors()['border_medium']};
309
+ color: {get_current_colors()['text_primary']} !important;
310
+ text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.05);
311
+ }}
312
+
313
+ h2 {{
314
+ margin-top: 2rem;
315
+ padding-bottom: 0.5rem;
316
+ color: {get_current_colors()['text_secondary']} !important;
317
+ font-weight: 600;
318
+ }}
319
+
320
+ h3 {{
321
+ margin-top: 1.5rem;
322
+ color: {get_current_colors()['text_tertiary']} !important;
323
+ font-weight: 600;
324
+ }}
325
+
326
+ /* Alert box styling */
327
+ .auth-alert {{
328
+ background: {get_current_colors()['alert_error_bg']};
329
+ color: {get_current_colors()['alert_error_text']} !important;
330
+ padding: 15px;
331
+ border-radius: 10px;
332
+ margin: 15px 0;
333
+ border: 1px solid {get_current_colors()['alert_error_border']};
334
+ box-shadow: 0 2px 10px rgba(0, 0, 0, 0.03);
335
+ }}
336
+
337
+ .auth-info {{
338
+ background: {get_current_colors()['alert_info_bg']};
339
+ color: {get_current_colors()['alert_info_text']} !important;
340
+ padding: 15px;
341
+ border-radius: 10px;
342
+ margin: 15px 0;
343
+ border: 1px solid {get_current_colors()['alert_info_border']};
344
+ box-shadow: 0 2px 10px rgba(0, 0, 0, 0.03);
345
+ }}
346
+ </style>
347
+ """
348
+
349
+ def get_home_page_css():
350
+ """Returns additional CSS specific to the home page."""
351
+ return f"""
352
+ <style>
353
+ /* Navigation button styling */
354
+ .nav-button {{
355
+ border: 2px solid {get_current_colors()['border_medium']};
356
+ border-radius: 12px;
357
+ padding: 20px;
358
+ text-align: center;
359
+ margin-bottom: 15px;
360
+ background: {get_current_colors()['background']};
361
+ transition: all 0.3s ease;
362
+ box-shadow: 0 2px 10px rgba(0, 0, 0, 0.03);
363
+ color: {get_current_colors()['text_primary']} !important;
364
+ }}
365
+
366
+ .nav-button:hover {{
367
+ background: {get_current_colors()['figure_background']};
368
+ transform: translateY(-3px);
369
+ box-shadow: 0 6px 20px rgba(0, 0, 0, 0.08);
370
+ border-color: {get_current_colors()['text_secondary']};
371
+ color: {get_current_colors()['text_primary']} !important;
372
+ }}
373
+
374
+ .nav-button h3 {{
375
+ color: {get_current_colors()['text_secondary']} !important;
376
+ margin-bottom: 0.5rem;
377
+ }}
378
+
379
+ /* Description boxes */
380
+ .description-box {{
381
+ text-align: center;
382
+ padding: 15px;
383
+ border: 2px solid {get_current_colors()['border_medium']};
384
+ border-radius: 10px;
385
+ margin-top: -10px;
386
+ background: {get_current_colors()['background']};
387
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.03);
388
+ color: {get_current_colors()['text_primary']} !important;
389
+ }}
390
+ </style>
391
+ """
392
+
393
+ def get_documentation_page_css():
394
+ """Returns additional CSS specific to the documentation page."""
395
+ return f"""
396
+ <style>
397
+ .gaussian-explanation-container {{
398
+ padding: 20px;
399
+ background: {get_current_colors()['background']};
400
+ border-radius: 12px;
401
+ margin-bottom: 20px;
402
+ border: 2px solid {get_current_colors()['border_medium']};
403
+ box-shadow: 0 2px 10px rgba(0, 0, 0, 0.03);
404
+ color: {get_current_colors()['text_primary']} !important;
405
+ }}
406
+
407
+ .metric-card {{
408
+ background: {get_current_colors()['background']};
409
+ padding: 15px;
410
+ border-radius: 10px;
411
+ border: 2px solid {get_current_colors()['border_medium']};
412
+ margin: 10px 0;
413
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
414
+ color: {get_current_colors()['text_primary']} !important;
415
+ }}
416
+
417
+ .glossary-term {{
418
+ background: {get_current_colors()['background']};
419
+ padding: 15px;
420
+ border-radius: 10px;
421
+ border-left: 4px solid {get_current_colors()['text_secondary']};
422
+ margin: 10px 0;
423
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
424
+ color: {get_current_colors()['text_primary']} !important;
425
+ }}
426
+ </style>
427
+ """
428
+
429
+ def get_tool_page_css():
430
+ """Returns additional CSS specific to the analysis tool page."""
431
+ return f"""
432
+ <style>
433
+ /* Expander styling */
434
+ .streamlit-expanderHeader {{
435
+ background-color: {get_current_colors()['figure_background']};
436
+ border: 2px solid {get_current_colors()['border_medium']};
437
+ border-radius: 8px;
438
+ color: {get_current_colors()['text_primary']} !important;
439
+ font-weight: 600;
440
+ }}
441
+
442
+ /* Generate button special styling */
443
+ div[data-testid="stButton"] button {{
444
+ background: linear-gradient(45deg, {get_current_colors()['generate_button_bg']}, {get_current_colors()['generate_button_hover']}) !important;
445
+ color: white !important;
446
+ border: none !important;
447
+ font-weight: 700 !important;
448
+ font-size: 1.1rem !important;
449
+ padding: 1rem !important;
450
+ box-shadow: 0 3px 15px rgba(34, 139, 34, 0.3) !important;
451
+ }}
452
+
453
+ div[data-testid="stButton"] button:hover {{
454
+ background: linear-gradient(45deg, {get_current_colors()['generate_button_hover']}, #7CFC00) !important;
455
+ transform: translateY(-3px) !important;
456
+ box-shadow: 0 5px 20px rgba(34, 139, 34, 0.4) !important;
457
+ color: white !important;
458
+ }}
459
+ </style>
460
+ """
461
+
462
+ def apply_base_styling():
463
+ """Apply the base styling to the current Streamlit page."""
464
+ st.markdown(get_base_css(), unsafe_allow_html=True)
465
+
466
+ def apply_home_page_styling():
467
+ """Apply styling specific to the home page."""
468
+ st.markdown(get_base_css(), unsafe_allow_html=True)
469
+ st.markdown(get_home_page_css(), unsafe_allow_html=True)
470
+
471
+ def apply_documentation_page_styling():
472
+ """Apply styling specific to the documentation page."""
473
+ st.markdown(get_base_css(), unsafe_allow_html=True)
474
+ st.markdown(get_documentation_page_css(), unsafe_allow_html=True)
475
+
476
+ def apply_tool_page_styling():
477
+ """Apply styling specific to the analysis tool page."""
478
+ st.markdown(get_base_css(), unsafe_allow_html=True)
479
+ st.markdown(get_tool_page_css(), unsafe_allow_html=True)
sensor_illustration.py CHANGED
@@ -3,7 +3,7 @@ import matplotlib.pyplot as plt
3
  import numpy as np
4
  from typing import List
5
  from dataclasses import dataclass
6
- from plot_styling import set_plot_style
7
 
8
  @dataclass
9
  class JointPosition:
 
3
  import numpy as np
4
  from typing import List
5
  from dataclasses import dataclass
6
+ from shared_styling import set_plot_style
7
 
8
  @dataclass
9
  class JointPosition:
shared_styling.py CHANGED
@@ -1,11 +1,18 @@
1
  """
2
- Centralized styling module for the Task Similarity Analysis application.
3
- Contains all CSS styling definitions to ensure consistency across pages.
4
  """
5
 
6
  import streamlit as st
 
 
 
7
 
8
- # Color scheme constants - Light and Dark themes
 
 
 
 
9
  LIGHT_COLORS = {
10
  'background': '#FFFFFB',
11
  'figure_background': '#FEFEF8',
@@ -32,9 +39,14 @@ LIGHT_COLORS = {
32
  'success_bg': '#E8F5E8',
33
  'success_border': '#4CAF50',
34
  'generate_button_bg': '#228B22',
35
- 'generate_button_hover': '#32CD32'
 
 
 
 
36
  }
37
 
 
38
  DARK_COLORS = {
39
  'background': '#1E1E1E',
40
  'figure_background': '#2D2D2D',
@@ -61,7 +73,11 @@ DARK_COLORS = {
61
  'success_bg': '#1A4A1A',
62
  'success_border': '#32CD32',
63
  'generate_button_bg': '#2E7D32',
64
- 'generate_button_hover': '#388E3C'
 
 
 
 
65
  }
66
 
67
  def get_current_colors():
@@ -90,6 +106,142 @@ def add_theme_toggle():
90
 
91
  st.caption(f"Current: {current_theme} Theme")
92
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
93
  def get_base_css():
94
  """Returns the base CSS styling used across all pages."""
95
  return f"""
@@ -115,68 +267,6 @@ def get_base_css():
115
  color: {get_current_colors()['text_primary']} !important;
116
  }}
117
 
118
- /* Main content text */
119
- .main .block-container p {{
120
- color: {get_current_colors()['text_primary']} !important;
121
- }}
122
-
123
- .main .block-container div {{
124
- color: {get_current_colors()['text_primary']} !important;
125
- }}
126
-
127
- .main .block-container span {{
128
- color: {get_current_colors()['text_primary']} !important;
129
- }}
130
-
131
- /* Streamlit markdown text */
132
- .stMarkdown {{
133
- color: {get_current_colors()['text_primary']} !important;
134
- }}
135
-
136
- .stMarkdown p {{
137
- color: {get_current_colors()['text_primary']} !important;
138
- }}
139
-
140
- .stMarkdown div {{
141
- color: {get_current_colors()['text_primary']} !important;
142
- }}
143
-
144
- /* Sidebar styling */
145
- .css-1d391kg {{
146
- background: {get_current_colors()['sidebar_bg_start']};
147
- border-right: 2px solid {get_current_colors()['border_light']};
148
- }}
149
-
150
- .css-1d391kg .css-1v0mbdj {{
151
- background: {get_current_colors()['sidebar_bg_start']};
152
- }}
153
-
154
- /* Sidebar content */
155
- section[data-testid="stSidebar"] {{
156
- background: {get_current_colors()['sidebar_bg_start']};
157
- border-right: 2px solid {get_current_colors()['border_light']};
158
- color: {get_current_colors()['text_primary']} !important;
159
- }}
160
-
161
- section[data-testid="stSidebar"] > div {{
162
- background: {get_current_colors()['sidebar_bg_start']};
163
- padding-top: 2rem;
164
- color: {get_current_colors()['text_primary']} !important;
165
- }}
166
-
167
- /* Sidebar text elements */
168
- section[data-testid="stSidebar"] * {{
169
- color: {get_current_colors()['text_primary']} !important;
170
- }}
171
-
172
- /* Sidebar title styling */
173
- section[data-testid="stSidebar"] h1 {{
174
- color: {get_current_colors()['text_primary']} !important;
175
- border-bottom: 2px solid {get_current_colors()['border_medium']};
176
- padding-bottom: 0.5rem;
177
- margin-bottom: 1rem;
178
- }}
179
-
180
  /* Button styling */
181
  .stButton>button {{
182
  width: 100%;
@@ -199,159 +289,20 @@ def get_base_css():
199
  color: white !important;
200
  }}
201
 
202
- /* Input styling */
203
- .stTextInput>div>div>input {{
204
- background-color: {get_current_colors()['background']};
205
- border: 2px solid {get_current_colors()['border_light']};
206
- border-radius: 8px;
207
- color: {get_current_colors()['text_primary']} !important;
208
- }}
209
-
210
- .stTextInput>div>div>input:focus {{
211
- border-color: {get_current_colors()['button_bg_start']};
212
- box-shadow: 0 0 0 2px rgba(107, 107, 107, 0.1);
213
- color: {get_current_colors()['text_primary']} !important;
214
- }}
215
-
216
- /* Selectbox and multiselect styling */
217
- .stSelectbox>div>div>div {{
218
- background-color: {get_current_colors()['background']};
219
- border: 2px solid {get_current_colors()['border_light']};
220
- border-radius: 8px;
221
- color: {get_current_colors()['text_primary']} !important;
222
- }}
223
-
224
- /* Selectbox dropdown options */
225
- .stSelectbox div[data-baseweb="select"] {{
226
- background-color: {get_current_colors()['background']} !important;
227
- color: {get_current_colors()['text_primary']} !important;
228
- }}
229
-
230
- .stSelectbox div[data-baseweb="select"] > div {{
231
- background-color: {get_current_colors()['background']} !important;
232
- color: {get_current_colors()['text_primary']} !important;
233
- }}
234
-
235
- /* Dropdown menu styling */
236
- .stSelectbox div[role="listbox"] {{
237
- background-color: {get_current_colors()['background']} !important;
238
- border: 2px solid {get_current_colors()['border_medium']} !important;
239
- border-radius: 8px !important;
240
- box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1) !important;
241
- }}
242
-
243
- /* Individual dropdown options */
244
- .stSelectbox div[role="option"] {{
245
- background-color: {get_current_colors()['background']} !important;
246
- color: {get_current_colors()['text_primary']} !important;
247
- padding: 8px 12px !important;
248
- border-radius: 4px !important;
249
- margin: 2px !important;
250
- }}
251
-
252
- .stSelectbox div[role="option"]:hover {{
253
- background-color: {get_current_colors()['figure_background']} !important;
254
- color: {get_current_colors()['text_primary']} !important;
255
- }}
256
-
257
- .stSelectbox div[role="option"][aria-selected="true"] {{
258
- background-color: {get_current_colors()['button_bg_start']} !important;
259
- color: white !important;
260
- }}
261
-
262
- /* Selectbox text and placeholder */
263
- .stSelectbox div[data-baseweb="select"] span {{
264
- color: {get_current_colors()['text_primary']} !important;
265
- }}
266
-
267
- .stSelectbox div[data-baseweb="select"] input {{
268
- color: {get_current_colors()['text_primary']} !important;
269
- }}
270
-
271
- /* Alternative selectbox selectors for different Streamlit versions */
272
- div[data-testid="stSelectbox"] {{
273
- color: {get_current_colors()['text_primary']} !important;
274
- }}
275
-
276
- div[data-testid="stSelectbox"] > div {{
277
- background-color: {get_current_colors()['background']} !important;
278
- color: {get_current_colors()['text_primary']} !important;
279
- }}
280
-
281
- div[data-testid="stSelectbox"] div {{
282
- color: {get_current_colors()['text_primary']} !important;
283
- }}
284
-
285
- /* Dropdown portal styling (for options that appear outside the main container) */
286
- .css-1n76uvr {{
287
- background-color: {get_current_colors()['background']} !important;
288
- border: 2px solid {get_current_colors()['border_medium']} !important;
289
- border-radius: 8px !important;
290
- box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1) !important;
291
- }}
292
-
293
- .css-1n76uvr div {{
294
- background-color: {get_current_colors()['background']} !important;
295
- color: {get_current_colors()['text_primary']} !important;
296
- }}
297
-
298
- .stMultiSelect>div>div>div {{
299
- background-color: {get_current_colors()['background']};
300
- border: 2px solid {get_current_colors()['border_light']};
301
- border-radius: 8px;
302
- color: {get_current_colors()['text_primary']} !important;
303
- }}
304
-
305
- /* Multiselect dropdown options */
306
- .stMultiSelect div[role="listbox"] {{
307
- background-color: {get_current_colors()['background']} !important;
308
- border: 2px solid {get_current_colors()['border_medium']} !important;
309
- border-radius: 8px !important;
310
- box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1) !important;
311
- }}
312
-
313
- .stMultiSelect div[role="option"] {{
314
- background-color: {get_current_colors()['background']} !important;
315
- color: {get_current_colors()['text_primary']} !important;
316
- padding: 8px 12px !important;
317
- border-radius: 4px !important;
318
- margin: 2px !important;
319
- }}
320
-
321
- .stMultiSelect div[role="option"]:hover {{
322
- background-color: {get_current_colors()['figure_background']} !important;
323
- color: {get_current_colors()['text_primary']} !important;
324
- }}
325
-
326
- .stMultiSelect div[role="option"][aria-selected="true"] {{
327
- background-color: {get_current_colors()['button_bg_start']} !important;
328
- color: white !important;
329
- }}
330
-
331
- /* Radio button styling */
332
- .stRadio>div {{
333
- background-color: {get_current_colors()['background']};
334
- border-radius: 8px;
335
- padding: 10px;
336
- color: {get_current_colors()['text_primary']} !important;
337
- }}
338
-
339
- .stRadio label {{
340
  color: {get_current_colors()['text_primary']} !important;
341
  }}
342
 
343
- /* Sidebar content styling */
344
- .sidebar .sidebar-content {{
345
- padding-top: 1rem;
346
- background-color: {get_current_colors()['sidebar_bg_start']};
347
  color: {get_current_colors()['text_primary']} !important;
348
  }}
349
 
350
- hr {{
351
- margin: 1rem 0;
352
- border-color: {get_current_colors()['border_light']};
353
- }}
354
-
355
  h1 {{
356
  padding-bottom: 1rem;
357
  border-bottom: 3px solid {get_current_colors()['border_medium']};
@@ -372,11 +323,6 @@ def get_base_css():
372
  font-weight: 600;
373
  }}
374
 
375
- /* Ensure all text elements have proper color */
376
- p, span, div, label, li, td, th {{
377
- color: {get_current_colors()['text_primary']} !important;
378
- }}
379
-
380
  /* Alert box styling */
381
  .auth-alert {{
382
  background: {get_current_colors()['alert_error_bg']};
@@ -397,138 +343,6 @@ def get_base_css():
397
  border: 1px solid {get_current_colors()['alert_info_border']};
398
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.03);
399
  }}
400
-
401
- /* Sidebar info boxes */
402
- .stAlert {{
403
- background: {get_current_colors()['alert_info_bg']};
404
- border: 1px solid {get_current_colors()['text_secondary']};
405
- border-radius: 8px;
406
- color: {get_current_colors()['text_primary']} !important;
407
- }}
408
-
409
- .info-box {{
410
- padding: 20px;
411
- background: {get_current_colors()['alert_info_bg']};
412
- border-left: 5px solid {get_current_colors()['text_secondary']};
413
- border-radius: 10px;
414
- margin: 20px 0;
415
- box-shadow: 0 2px 10px rgba(0, 0, 0, 0.03);
416
- color: {get_current_colors()['text_primary']} !important;
417
- }}
418
-
419
- .warning-box {{
420
- padding: 20px;
421
- background: {get_current_colors()['warning_bg']};
422
- border-left: 5px solid {get_current_colors()['warning_border']};
423
- border-radius: 10px;
424
- margin: 20px 0;
425
- box-shadow: 0 2px 10px rgba(0, 0, 0, 0.03);
426
- color: {get_current_colors()['text_primary']} !important;
427
- }}
428
-
429
- .success-box {{
430
- padding: 20px;
431
- background: {get_current_colors()['success_bg']};
432
- border-left: 5px solid {get_current_colors()['success_border']};
433
- border-radius: 10px;
434
- margin: 20px 0;
435
- box-shadow: 0 2px 10px rgba(0, 0, 0, 0.03);
436
- color: {get_current_colors()['text_primary']} !important;
437
- }}
438
-
439
- .centered {{
440
- display: flex;
441
- justify-content: center;
442
- align-items: center;
443
- flex-direction: column;
444
- }}
445
-
446
- /* Footer styling */
447
- .footer {{
448
- text-align: center;
449
- font-size: 0.8rem;
450
- color: {get_current_colors()['text_light']} !important;
451
- margin-top: 2rem;
452
- padding-top: 1rem;
453
- border-top: 1px solid {get_current_colors()['border_light']};
454
- }}
455
-
456
- /* Additional dropdown styling for various Streamlit implementations */
457
- [data-baseweb="popover"] {{
458
- background-color: {get_current_colors()['background']} !important;
459
- border: 2px solid {get_current_colors()['border_medium']} !important;
460
- border-radius: 8px !important;
461
- box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1) !important;
462
- }}
463
-
464
- [data-baseweb="popover"] * {{
465
- color: {get_current_colors()['text_primary']} !important;
466
- background-color: {get_current_colors()['background']} !important;
467
- }}
468
-
469
- [data-baseweb="menu"] {{
470
- background-color: {get_current_colors()['background']} !important;
471
- border: 2px solid {get_current_colors()['border_medium']} !important;
472
- border-radius: 8px !important;
473
- }}
474
-
475
- [data-baseweb="menu"] li {{
476
- background-color: {get_current_colors()['background']} !important;
477
- color: {get_current_colors()['text_primary']} !important;
478
- padding: 8px 12px !important;
479
- }}
480
-
481
- [data-baseweb="menu"] li:hover {{
482
- background-color: {get_current_colors()['figure_background']} !important;
483
- color: {get_current_colors()['text_primary']} !important;
484
- }}
485
-
486
- /* Streamlit select widget styling */
487
- .stSelectbox .css-1wa3eu0-placeholder {{
488
- color: {get_current_colors()['text_primary']} !important;
489
- }}
490
-
491
- .stSelectbox .css-1uccc91-singleValue {{
492
- color: {get_current_colors()['text_primary']} !important;
493
- }}
494
-
495
- /* Generic dropdown styling */
496
- .css-26l3qy-menu {{
497
- background-color: {get_current_colors()['background']} !important;
498
- border: 2px solid {get_current_colors()['border_medium']} !important;
499
- border-radius: 8px !important;
500
- box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1) !important;
501
- }}
502
-
503
- .css-26l3qy-menu div {{
504
- color: {get_current_colors()['text_primary']} !important;
505
- background-color: {get_current_colors()['background']} !important;
506
- }}
507
-
508
- .css-1n7v3ny-option {{
509
- background-color: {get_current_colors()['background']} !important;
510
- color: {get_current_colors()['text_primary']} !important;
511
- padding: 8px 12px !important;
512
- }}
513
-
514
- .css-1n7v3ny-option:hover {{
515
- background-color: {get_current_colors()['figure_background']} !important;
516
- color: {get_current_colors()['text_primary']} !important;
517
- }}
518
-
519
- /* Force all dropdown-related elements to have proper colors */
520
- div[role="listbox"] * {{
521
- color: {get_current_colors()['text_primary']} !important;
522
- background-color: {get_current_colors()['background']} !important;
523
- }}
524
-
525
- div[role="option"] * {{
526
- color: {get_current_colors()['text_primary']} !important;
527
- }}
528
-
529
- div[role="combobox"] * {{
530
- color: {get_current_colors()['text_primary']} !important;
531
- }}
532
  </style>
533
  """
534
 
@@ -562,10 +376,6 @@ def get_home_page_css():
562
  margin-bottom: 0.5rem;
563
  }}
564
 
565
- .nav-button * {{
566
- color: {get_current_colors()['text_primary']} !important;
567
- }}
568
-
569
  /* Description boxes */
570
  .description-box {{
571
  text-align: center;
@@ -577,16 +387,6 @@ def get_home_page_css():
577
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.03);
578
  color: {get_current_colors()['text_primary']} !important;
579
  }}
580
-
581
- .description-box p {{
582
- color: {get_current_colors()['text_secondary']} !important;
583
- margin: 0;
584
- font-weight: 500;
585
- }}
586
-
587
- .description-box * {{
588
- color: {get_current_colors()['text_secondary']} !important;
589
- }}
590
  </style>
591
  """
592
 
@@ -604,18 +404,6 @@ def get_documentation_page_css():
604
  color: {get_current_colors()['text_primary']} !important;
605
  }}
606
 
607
- .gaussian-explanation-container * {{
608
- color: {get_current_colors()['text_primary']} !important;
609
- }}
610
-
611
- .caption {{
612
- font-size: 0.85rem;
613
- color: {get_current_colors()['text_light']} !important;
614
- text-align: center;
615
- margin-top: 5px;
616
- font-style: italic;
617
- }}
618
-
619
  .metric-card {{
620
  background: {get_current_colors()['background']};
621
  padding: 15px;
@@ -626,25 +414,6 @@ def get_documentation_page_css():
626
  color: {get_current_colors()['text_primary']} !important;
627
  }}
628
 
629
- .metric-card * {{
630
- color: {get_current_colors()['text_primary']} !important;
631
- }}
632
-
633
- .step-number {{
634
- background: linear-gradient(45deg, {get_current_colors()['button_bg_start']}, {get_current_colors()['button_bg_end']});
635
- color: white !important;
636
- border-radius: 50%;
637
- width: 30px;
638
- height: 30px;
639
- display: inline-flex;
640
- align-items: center;
641
- justify-content: center;
642
- margin-right: 10px;
643
- font-weight: bold;
644
- box-shadow: 0 2px 8px rgba(107, 107, 107, 0.3);
645
- }}
646
-
647
- /* Glossary-specific styles */
648
  .glossary-term {{
649
  background: {get_current_colors()['background']};
650
  padding: 15px;
@@ -654,41 +423,6 @@ def get_documentation_page_css():
654
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
655
  color: {get_current_colors()['text_primary']} !important;
656
  }}
657
-
658
- .glossary-term * {{
659
- color: {get_current_colors()['text_primary']} !important;
660
- }}
661
-
662
- .term-title {{
663
- font-size: 1.2rem;
664
- font-weight: bold;
665
- color: {get_current_colors()['text_secondary']} !important;
666
- margin-bottom: 8px;
667
- }}
668
-
669
- .term-category {{
670
- background: {get_current_colors()['alert_info_bg']};
671
- padding: 15px;
672
- border-radius: 10px;
673
- margin: 20px 0;
674
- border-left: 4px solid {get_current_colors()['text_tertiary']};
675
- box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
676
- color: {get_current_colors()['text_primary']} !important;
677
- }}
678
-
679
- .term-category * {{
680
- color: {get_current_colors()['text_primary']} !important;
681
- }}
682
-
683
- .formula {{
684
- background-color: {get_current_colors()['figure_background']};
685
- padding: 10px;
686
- border-radius: 8px;
687
- font-family: monospace;
688
- margin: 5px 0;
689
- border: 1px solid {get_current_colors()['border_light']};
690
- color: {get_current_colors()['text_primary']} !important;
691
- }}
692
  </style>
693
  """
694
 
@@ -705,61 +439,6 @@ def get_tool_page_css():
705
  font-weight: 600;
706
  }}
707
 
708
- .streamlit-expanderContent {{
709
- background-color: {get_current_colors()['background']};
710
- border: 1px solid {get_current_colors()['border_light']};
711
- border-radius: 0 0 8px 8px;
712
- color: {get_current_colors()['text_primary']} !important;
713
- }}
714
-
715
- .streamlit-expanderContent * {{
716
- color: {get_current_colors()['text_primary']} !important;
717
- }}
718
-
719
- /* Tooltip styles */
720
- .tooltip-container {{
721
- position: relative;
722
- display: inline-block;
723
- width: 100%;
724
- color: {get_current_colors()['text_primary']} !important;
725
- }}
726
-
727
- .tooltip-content {{
728
- visibility: hidden;
729
- background-color: {get_current_colors()['background']};
730
- color: {get_current_colors()['text_primary']} !important;
731
- text-align: left;
732
- padding: 10px;
733
- border-radius: 8px;
734
- border: 2px solid {get_current_colors()['border_medium']};
735
- position: absolute;
736
- z-index: 1;
737
- top: 100%;
738
- left: 0;
739
- margin-top: 5px;
740
- box-shadow: 0 4px 15px rgba(0,0,0,0.1);
741
- width: 200px;
742
- }}
743
-
744
- .tooltip-container:hover .tooltip-content {{
745
- visibility: visible;
746
- }}
747
-
748
- .color-legend {{
749
- display: flex;
750
- align-items: center;
751
- margin: 5px 0;
752
- color: {get_current_colors()['text_primary']} !important;
753
- }}
754
-
755
- .color-box {{
756
- width: 15px;
757
- height: 15px;
758
- margin-right: 10px;
759
- border-radius: 3px;
760
- border: 1px solid {get_current_colors()['border_medium']};
761
- }}
762
-
763
  /* Generate button special styling */
764
  div[data-testid="stButton"] button {{
765
  background: linear-gradient(45deg, {get_current_colors()['generate_button_bg']}, {get_current_colors()['generate_button_hover']}) !important;
@@ -797,4 +476,4 @@ def apply_documentation_page_styling():
797
  def apply_tool_page_styling():
798
  """Apply styling specific to the analysis tool page."""
799
  st.markdown(get_base_css(), unsafe_allow_html=True)
800
- st.markdown(get_tool_page_css(), unsafe_allow_html=True)
 
1
  """
2
+ Unified styling module for both Streamlit UI and matplotlib plots.
3
+ Contains all styling definitions to ensure consistency across the application.
4
  """
5
 
6
  import streamlit as st
7
+ import matplotlib.pyplot as plt
8
+ import seaborn as sns
9
+ import matplotlib.font_manager as fm
10
 
11
+ # ==========================
12
+ # Shared Color Themes
13
+ # ==========================
14
+
15
+ # Light theme colors - consistent across UI and plots
16
  LIGHT_COLORS = {
17
  'background': '#FFFFFB',
18
  'figure_background': '#FEFEF8',
 
39
  'success_bg': '#E8F5E8',
40
  'success_border': '#4CAF50',
41
  'generate_button_bg': '#228B22',
42
+ 'generate_button_hover': '#32CD32',
43
+ # Plot-specific colors
44
+ 'axes_background': '#FFFFFB',
45
+ 'grid_color': '#F0F0F0',
46
+ 'spine_color': '#E0E0E0',
47
  }
48
 
49
+ # Dark theme colors - consistent across UI and plots
50
  DARK_COLORS = {
51
  'background': '#1E1E1E',
52
  'figure_background': '#2D2D2D',
 
73
  'success_bg': '#1A4A1A',
74
  'success_border': '#32CD32',
75
  'generate_button_bg': '#2E7D32',
76
+ 'generate_button_hover': '#388E3C',
77
+ # Plot-specific colors
78
+ 'axes_background': '#1E1E1E',
79
+ 'grid_color': '#404040',
80
+ 'spine_color': '#505050',
81
  }
82
 
83
  def get_current_colors():
 
106
 
107
  st.caption(f"Current: {current_theme} Theme")
108
 
109
+ # ==========================
110
+ # Plot Styling
111
+ # ==========================
112
+
113
+ # Font configuration
114
+ DEFAULT_FONT_FAMILY = 'sans-serif'
115
+
116
+ try:
117
+ fm.fontManager.addfont('/usr/share/fonts/truetype/msttcorefonts/Arial.ttf')
118
+ PLOT_STYLE_FONT_FAMILY = 'Arial'
119
+ print("Successfully loaded Arial font.")
120
+ except FileNotFoundError:
121
+ print("Arial.ttf not found. Using default system font.")
122
+ PLOT_STYLE_FONT_FAMILY = DEFAULT_FONT_FAMILY
123
+ except Exception as e:
124
+ print(f"An error occurred while trying to load Arial font: {e}. Using default system font.")
125
+ PLOT_STYLE_FONT_FAMILY = DEFAULT_FONT_FAMILY
126
+
127
+ # Color constants for plots
128
+ PLOT_COLORS = {
129
+ 'input_similarity': sns.color_palette('rocket', as_cmap=True),
130
+ 'output_difference': sns.cubehelix_palette(start=.2, rot=-.3, dark=0, light=0.85,
131
+ reverse=True, as_cmap=True),
132
+ 'conflict': sns.cubehelix_palette(start=2, rot=0, dark=0, light=0.85,
133
+ reverse=True, as_cmap=True),
134
+ 'output_biomechanical': sns.cubehelix_palette(start=2.8, rot=0.4, dark=0, light=0.85,
135
+ reverse=True, as_cmap=True)
136
+ }
137
+
138
+ # Additional palettes
139
+ purple_helix = sns.cubehelix_palette(start=.2, rot=-.4, dark=0, light=0.85,
140
+ reverse=True, as_cmap=True)
141
+ my_purple_helix = sns.cubehelix_palette(start=.2, rot=-.1, dark=0, light=0.85,
142
+ reverse=True, as_cmap=True)
143
+
144
+ def get_plot_style():
145
+ """Get plot style with current theme colors."""
146
+ theme_colors = get_current_colors()
147
+
148
+ return {
149
+ 'font_family': PLOT_STYLE_FONT_FAMILY,
150
+ 'font_size': 18,
151
+ 'title_size': 20,
152
+ 'label_size': 18,
153
+ 'tick_size': 15,
154
+ 'tick_length': 5,
155
+ 'tick_width': 0.5,
156
+ 'tick_pad': 5,
157
+ 'label_pad_x': -15,
158
+ 'label_pad_y': -35,
159
+ 'figure_dpi': 300,
160
+ 'aspect_ratio': 'equal',
161
+ 'subplot_wspace': 0.05,
162
+ 'subplot_hspace': 0.1,
163
+ # Theme-specific styling
164
+ 'figure_facecolor': theme_colors['figure_background'],
165
+ 'axes_facecolor': theme_colors['axes_background'],
166
+ 'text_color': theme_colors['text_primary'],
167
+ 'grid_color': theme_colors['grid_color'],
168
+ 'spine_color': theme_colors['spine_color'],
169
+ }
170
+
171
+ def set_plot_style():
172
+ """Set consistent plot styling across all figures"""
173
+ plot_style = get_plot_style()
174
+
175
+ plt.rcParams['font.family'] = plot_style['font_family']
176
+ plt.rcParams['font.size'] = plot_style['font_size']
177
+ plt.rcParams['axes.labelsize'] = plot_style['label_size']
178
+ plt.rcParams['axes.titlesize'] = plot_style['title_size']
179
+ plt.rcParams['xtick.labelsize'] = plot_style['tick_size']
180
+ plt.rcParams['ytick.labelsize'] = plot_style['tick_size']
181
+ plt.rcParams['xtick.major.pad'] = plot_style['tick_pad']
182
+ plt.rcParams['ytick.major.pad'] = plot_style['tick_pad']
183
+ plt.rcParams['figure.dpi'] = plot_style['figure_dpi']
184
+ plt.rcParams['figure.subplot.wspace'] = plot_style['subplot_wspace']
185
+ plt.rcParams['figure.subplot.hspace'] = plot_style['subplot_hspace']
186
+
187
+ # Apply theme styling
188
+ plt.rcParams['figure.facecolor'] = plot_style['figure_facecolor']
189
+ plt.rcParams['axes.facecolor'] = plot_style['axes_facecolor']
190
+ plt.rcParams['text.color'] = plot_style['text_color']
191
+ plt.rcParams['axes.labelcolor'] = plot_style['text_color']
192
+ plt.rcParams['xtick.color'] = plot_style['text_color']
193
+ plt.rcParams['ytick.color'] = plot_style['text_color']
194
+ plt.rcParams['axes.edgecolor'] = plot_style['spine_color']
195
+ plt.rcParams['grid.color'] = plot_style['grid_color']
196
+ plt.rcParams['grid.alpha'] = 0.7
197
+
198
+ def apply_theme_to_figure(fig, ax=None):
199
+ """Apply current theme to an existing figure and axes"""
200
+ theme_colors = get_current_colors()
201
+
202
+ if fig:
203
+ fig.patch.set_facecolor(theme_colors['figure_background'])
204
+
205
+ if ax is not None:
206
+ # Handle single axes or list of axes
207
+ if hasattr(ax, '__iter__'):
208
+ if hasattr(ax, 'flatten'):
209
+ axes_list = ax.flatten()
210
+ else:
211
+ axes_list = ax
212
+ else:
213
+ axes_list = [ax]
214
+
215
+ for axis in axes_list:
216
+ if axis is not None:
217
+ axis.set_facecolor(theme_colors['axes_background'])
218
+
219
+ # Update text colors
220
+ axis.title.set_color(theme_colors['text_primary'])
221
+ axis.xaxis.label.set_color(theme_colors['text_primary'])
222
+ axis.yaxis.label.set_color(theme_colors['text_primary'])
223
+
224
+ # Update tick colors
225
+ axis.tick_params(colors=theme_colors['text_primary'])
226
+
227
+ # Update spine colors
228
+ for spine in axis.spines.values():
229
+ spine.set_color(theme_colors['spine_color'])
230
+
231
+ # Update grid
232
+ axis.grid(True, color=theme_colors['grid_color'], alpha=0.7)
233
+
234
+ return fig, ax
235
+
236
+ # Legacy function name for backward compatibility
237
+ def apply_cream_theme_to_figure(fig, ax=None):
238
+ """Apply current theme to an existing figure and axes (legacy function name)"""
239
+ return apply_theme_to_figure(fig, ax)
240
+
241
+ # ==========================
242
+ # Streamlit UI Styling
243
+ # ==========================
244
+
245
  def get_base_css():
246
  """Returns the base CSS styling used across all pages."""
247
  return f"""
 
267
  color: {get_current_colors()['text_primary']} !important;
268
  }}
269
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
270
  /* Button styling */
271
  .stButton>button {{
272
  width: 100%;
 
289
  color: white !important;
290
  }}
291
 
292
+ /* Sidebar styling */
293
+ section[data-testid="stSidebar"] {{
294
+ background: {get_current_colors()['sidebar_bg_start']};
295
+ border-right: 2px solid {get_current_colors()['border_light']};
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
296
  color: {get_current_colors()['text_primary']} !important;
297
  }}
298
 
299
+ section[data-testid="stSidebar"] > div {{
300
+ background: {get_current_colors()['sidebar_bg_start']};
301
+ padding-top: 2rem;
 
302
  color: {get_current_colors()['text_primary']} !important;
303
  }}
304
 
305
+ /* Headers styling */
 
 
 
 
306
  h1 {{
307
  padding-bottom: 1rem;
308
  border-bottom: 3px solid {get_current_colors()['border_medium']};
 
323
  font-weight: 600;
324
  }}
325
 
 
 
 
 
 
326
  /* Alert box styling */
327
  .auth-alert {{
328
  background: {get_current_colors()['alert_error_bg']};
 
343
  border: 1px solid {get_current_colors()['alert_info_border']};
344
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.03);
345
  }}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
346
  </style>
347
  """
348
 
 
376
  margin-bottom: 0.5rem;
377
  }}
378
 
 
 
 
 
379
  /* Description boxes */
380
  .description-box {{
381
  text-align: center;
 
387
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.03);
388
  color: {get_current_colors()['text_primary']} !important;
389
  }}
 
 
 
 
 
 
 
 
 
 
390
  </style>
391
  """
392
 
 
404
  color: {get_current_colors()['text_primary']} !important;
405
  }}
406
 
 
 
 
 
 
 
 
 
 
 
 
 
407
  .metric-card {{
408
  background: {get_current_colors()['background']};
409
  padding: 15px;
 
414
  color: {get_current_colors()['text_primary']} !important;
415
  }}
416
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
417
  .glossary-term {{
418
  background: {get_current_colors()['background']};
419
  padding: 15px;
 
423
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
424
  color: {get_current_colors()['text_primary']} !important;
425
  }}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
426
  </style>
427
  """
428
 
 
439
  font-weight: 600;
440
  }}
441
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
442
  /* Generate button special styling */
443
  div[data-testid="stButton"] button {{
444
  background: linear-gradient(45deg, {get_current_colors()['generate_button_bg']}, {get_current_colors()['generate_button_hover']}) !important;
 
476
  def apply_tool_page_styling():
477
  """Apply styling specific to the analysis tool page."""
478
  st.markdown(get_base_css(), unsafe_allow_html=True)
479
+ st.markdown(get_tool_page_css(), unsafe_allow_html=True)