aimodelshare 0.3.7__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- aimodelshare/README.md +26 -0
- aimodelshare/__init__.py +100 -0
- aimodelshare/aimsonnx.py +2381 -0
- aimodelshare/api.py +836 -0
- aimodelshare/auth.py +163 -0
- aimodelshare/aws.py +511 -0
- aimodelshare/aws_client.py +173 -0
- aimodelshare/base_image.py +154 -0
- aimodelshare/bucketpolicy.py +106 -0
- aimodelshare/color_mappings/color_mapping_keras.csv +121 -0
- aimodelshare/color_mappings/color_mapping_pytorch.csv +117 -0
- aimodelshare/containerisation.py +244 -0
- aimodelshare/containerization.py +712 -0
- aimodelshare/containerization_templates/Dockerfile.txt +8 -0
- aimodelshare/containerization_templates/Dockerfile_PySpark.txt +23 -0
- aimodelshare/containerization_templates/buildspec.txt +14 -0
- aimodelshare/containerization_templates/lambda_function.txt +40 -0
- aimodelshare/custom_approach/__init__.py +1 -0
- aimodelshare/custom_approach/lambda_function.py +17 -0
- aimodelshare/custom_eval_metrics.py +103 -0
- aimodelshare/data_sharing/__init__.py +0 -0
- aimodelshare/data_sharing/data_sharing_templates/Dockerfile.txt +3 -0
- aimodelshare/data_sharing/data_sharing_templates/__init__.py +1 -0
- aimodelshare/data_sharing/data_sharing_templates/buildspec.txt +15 -0
- aimodelshare/data_sharing/data_sharing_templates/codebuild_policies.txt +129 -0
- aimodelshare/data_sharing/data_sharing_templates/codebuild_trust_relationship.txt +12 -0
- aimodelshare/data_sharing/download_data.py +620 -0
- aimodelshare/data_sharing/share_data.py +373 -0
- aimodelshare/data_sharing/utils.py +8 -0
- aimodelshare/deploy_custom_lambda.py +246 -0
- aimodelshare/documentation/Makefile +20 -0
- aimodelshare/documentation/karma_sphinx_theme/__init__.py +28 -0
- aimodelshare/documentation/karma_sphinx_theme/_version.py +2 -0
- aimodelshare/documentation/karma_sphinx_theme/breadcrumbs.html +70 -0
- aimodelshare/documentation/karma_sphinx_theme/layout.html +172 -0
- aimodelshare/documentation/karma_sphinx_theme/search.html +50 -0
- aimodelshare/documentation/karma_sphinx_theme/searchbox.html +14 -0
- aimodelshare/documentation/karma_sphinx_theme/static/css/custom.css +2 -0
- aimodelshare/documentation/karma_sphinx_theme/static/css/custom.css.map +1 -0
- aimodelshare/documentation/karma_sphinx_theme/static/css/theme.css +2751 -0
- aimodelshare/documentation/karma_sphinx_theme/static/css/theme.css.map +1 -0
- aimodelshare/documentation/karma_sphinx_theme/static/css/theme.min.css +2 -0
- aimodelshare/documentation/karma_sphinx_theme/static/css/theme.min.css.map +1 -0
- aimodelshare/documentation/karma_sphinx_theme/static/font/fontello.eot +0 -0
- aimodelshare/documentation/karma_sphinx_theme/static/font/fontello.svg +32 -0
- aimodelshare/documentation/karma_sphinx_theme/static/font/fontello.ttf +0 -0
- aimodelshare/documentation/karma_sphinx_theme/static/font/fontello.woff +0 -0
- aimodelshare/documentation/karma_sphinx_theme/static/font/fontello.woff2 +0 -0
- aimodelshare/documentation/karma_sphinx_theme/static/js/theme.js +68 -0
- aimodelshare/documentation/karma_sphinx_theme/theme.conf +9 -0
- aimodelshare/documentation/make.bat +35 -0
- aimodelshare/documentation/requirements.txt +2 -0
- aimodelshare/documentation/source/about.rst +18 -0
- aimodelshare/documentation/source/advanced_features.rst +137 -0
- aimodelshare/documentation/source/competition.rst +218 -0
- aimodelshare/documentation/source/conf.py +58 -0
- aimodelshare/documentation/source/create_credentials.rst +86 -0
- aimodelshare/documentation/source/example_notebooks.rst +132 -0
- aimodelshare/documentation/source/functions.rst +151 -0
- aimodelshare/documentation/source/gettingstarted.rst +390 -0
- aimodelshare/documentation/source/images/creds1.png +0 -0
- aimodelshare/documentation/source/images/creds2.png +0 -0
- aimodelshare/documentation/source/images/creds3.png +0 -0
- aimodelshare/documentation/source/images/creds4.png +0 -0
- aimodelshare/documentation/source/images/creds5.png +0 -0
- aimodelshare/documentation/source/images/creds_file_example.png +0 -0
- aimodelshare/documentation/source/images/predict_tab.png +0 -0
- aimodelshare/documentation/source/index.rst +110 -0
- aimodelshare/documentation/source/modelplayground.rst +132 -0
- aimodelshare/exceptions.py +11 -0
- aimodelshare/generatemodelapi.py +1270 -0
- aimodelshare/iam/codebuild_policy.txt +129 -0
- aimodelshare/iam/codebuild_trust_relationship.txt +12 -0
- aimodelshare/iam/lambda_policy.txt +15 -0
- aimodelshare/iam/lambda_trust_relationship.txt +12 -0
- aimodelshare/json_templates/__init__.py +1 -0
- aimodelshare/json_templates/api_json.txt +155 -0
- aimodelshare/json_templates/auth/policy.txt +1 -0
- aimodelshare/json_templates/auth/role.txt +1 -0
- aimodelshare/json_templates/eval/policy.txt +1 -0
- aimodelshare/json_templates/eval/role.txt +1 -0
- aimodelshare/json_templates/function/policy.txt +1 -0
- aimodelshare/json_templates/function/role.txt +1 -0
- aimodelshare/json_templates/integration_response.txt +5 -0
- aimodelshare/json_templates/lambda_policy_1.txt +15 -0
- aimodelshare/json_templates/lambda_policy_2.txt +8 -0
- aimodelshare/json_templates/lambda_role_1.txt +12 -0
- aimodelshare/json_templates/lambda_role_2.txt +16 -0
- aimodelshare/leaderboard.py +174 -0
- aimodelshare/main/1.txt +132 -0
- aimodelshare/main/1B.txt +112 -0
- aimodelshare/main/2.txt +153 -0
- aimodelshare/main/3.txt +134 -0
- aimodelshare/main/4.txt +128 -0
- aimodelshare/main/5.txt +109 -0
- aimodelshare/main/6.txt +105 -0
- aimodelshare/main/7.txt +144 -0
- aimodelshare/main/8.txt +142 -0
- aimodelshare/main/__init__.py +1 -0
- aimodelshare/main/authorization.txt +275 -0
- aimodelshare/main/eval_classification.txt +79 -0
- aimodelshare/main/eval_lambda.txt +1709 -0
- aimodelshare/main/eval_regression.txt +80 -0
- aimodelshare/main/lambda_function.txt +8 -0
- aimodelshare/main/nst.txt +149 -0
- aimodelshare/model.py +1543 -0
- aimodelshare/modeluser.py +215 -0
- aimodelshare/moral_compass/README.md +408 -0
- aimodelshare/moral_compass/__init__.py +65 -0
- aimodelshare/moral_compass/_version.py +3 -0
- aimodelshare/moral_compass/api_client.py +601 -0
- aimodelshare/moral_compass/apps/__init__.py +69 -0
- aimodelshare/moral_compass/apps/ai_consequences.py +540 -0
- aimodelshare/moral_compass/apps/bias_detective.py +714 -0
- aimodelshare/moral_compass/apps/ethical_revelation.py +898 -0
- aimodelshare/moral_compass/apps/fairness_fixer.py +889 -0
- aimodelshare/moral_compass/apps/judge.py +888 -0
- aimodelshare/moral_compass/apps/justice_equity_upgrade.py +853 -0
- aimodelshare/moral_compass/apps/mc_integration_helpers.py +820 -0
- aimodelshare/moral_compass/apps/model_building_game.py +1104 -0
- aimodelshare/moral_compass/apps/model_building_game_beginner.py +687 -0
- aimodelshare/moral_compass/apps/moral_compass_challenge.py +858 -0
- aimodelshare/moral_compass/apps/session_auth.py +254 -0
- aimodelshare/moral_compass/apps/shared_activity_styles.css +349 -0
- aimodelshare/moral_compass/apps/tutorial.py +481 -0
- aimodelshare/moral_compass/apps/what_is_ai.py +853 -0
- aimodelshare/moral_compass/challenge.py +365 -0
- aimodelshare/moral_compass/config.py +187 -0
- aimodelshare/placeholders/model.onnx +0 -0
- aimodelshare/placeholders/preprocessor.zip +0 -0
- aimodelshare/playground.py +1968 -0
- aimodelshare/postprocessormodules.py +157 -0
- aimodelshare/preprocessormodules.py +373 -0
- aimodelshare/pyspark/1.txt +195 -0
- aimodelshare/pyspark/1B.txt +181 -0
- aimodelshare/pyspark/2.txt +220 -0
- aimodelshare/pyspark/3.txt +204 -0
- aimodelshare/pyspark/4.txt +187 -0
- aimodelshare/pyspark/5.txt +178 -0
- aimodelshare/pyspark/6.txt +174 -0
- aimodelshare/pyspark/7.txt +211 -0
- aimodelshare/pyspark/8.txt +206 -0
- aimodelshare/pyspark/__init__.py +1 -0
- aimodelshare/pyspark/authorization.txt +258 -0
- aimodelshare/pyspark/eval_classification.txt +79 -0
- aimodelshare/pyspark/eval_lambda.txt +1441 -0
- aimodelshare/pyspark/eval_regression.txt +80 -0
- aimodelshare/pyspark/lambda_function.txt +8 -0
- aimodelshare/pyspark/nst.txt +213 -0
- aimodelshare/python/my_preprocessor.py +58 -0
- aimodelshare/readme.md +26 -0
- aimodelshare/reproducibility.py +181 -0
- aimodelshare/sam/Dockerfile.txt +8 -0
- aimodelshare/sam/Dockerfile_PySpark.txt +24 -0
- aimodelshare/sam/__init__.py +1 -0
- aimodelshare/sam/buildspec.txt +11 -0
- aimodelshare/sam/codebuild_policies.txt +129 -0
- aimodelshare/sam/codebuild_trust_relationship.txt +12 -0
- aimodelshare/sam/codepipeline_policies.txt +173 -0
- aimodelshare/sam/codepipeline_trust_relationship.txt +12 -0
- aimodelshare/sam/spark-class.txt +2 -0
- aimodelshare/sam/template.txt +54 -0
- aimodelshare/tools.py +103 -0
- aimodelshare/utils/__init__.py +78 -0
- aimodelshare/utils/optional_deps.py +38 -0
- aimodelshare/utils.py +57 -0
- aimodelshare-0.3.7.dist-info/METADATA +298 -0
- aimodelshare-0.3.7.dist-info/RECORD +171 -0
- aimodelshare-0.3.7.dist-info/WHEEL +5 -0
- aimodelshare-0.3.7.dist-info/licenses/LICENSE +5 -0
- aimodelshare-0.3.7.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Session-based authentication helpers for multi-user Gradio apps in Cloud Run.
|
|
3
|
+
|
|
4
|
+
This module provides utilities for managing per-session authentication state
|
|
5
|
+
instead of using global environment variables, which are unsafe in multi-user
|
|
6
|
+
Cloud Run deployments where multiple users share the same container instance.
|
|
7
|
+
|
|
8
|
+
Key Design Principles:
|
|
9
|
+
- All authentication state is stored in Gradio State objects (per-session)
|
|
10
|
+
- No usage of os.environ for username, password, or tokens
|
|
11
|
+
- Thread-safe token generation
|
|
12
|
+
- Backward compatible with existing get_aws_token() function
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
import logging
|
|
16
|
+
from typing import Optional, Dict, Any, Tuple
|
|
17
|
+
import os
|
|
18
|
+
|
|
19
|
+
# Import dependencies at module level
|
|
20
|
+
try:
|
|
21
|
+
import botocore.config
|
|
22
|
+
import boto3
|
|
23
|
+
from aimodelshare.exceptions import AuthorizationError
|
|
24
|
+
except ImportError as e:
|
|
25
|
+
# These are required dependencies
|
|
26
|
+
raise ImportError(
|
|
27
|
+
"Required dependencies not found. Ensure boto3 and botocore are installed."
|
|
28
|
+
) from e
|
|
29
|
+
|
|
30
|
+
logger = logging.getLogger("aimodelshare.moral_compass.apps.session_auth")
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def generate_auth_token(username: str, password: str) -> str:
|
|
34
|
+
"""
|
|
35
|
+
Generate an authentication token for a user session.
|
|
36
|
+
|
|
37
|
+
This function wraps the existing get_aws_token() function from aimodelshare.aws
|
|
38
|
+
but accepts username and password as parameters instead of reading from
|
|
39
|
+
environment variables. This makes it safe for multi-user Cloud Run deployments.
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
username: The user's username
|
|
43
|
+
password: The user's password
|
|
44
|
+
|
|
45
|
+
Returns:
|
|
46
|
+
str: The AWS authentication token (IdToken from Cognito)
|
|
47
|
+
|
|
48
|
+
Raises:
|
|
49
|
+
AuthorizationError: If authentication fails
|
|
50
|
+
ValueError: If username or password is empty
|
|
51
|
+
|
|
52
|
+
Example:
|
|
53
|
+
>>> token = generate_auth_token("myuser", "mypass")
|
|
54
|
+
>>> # Store token in Gradio State, not in os.environ
|
|
55
|
+
"""
|
|
56
|
+
if not username or not username.strip():
|
|
57
|
+
raise ValueError("Username cannot be empty")
|
|
58
|
+
|
|
59
|
+
if not password or not password.strip():
|
|
60
|
+
raise ValueError("Password cannot be empty")
|
|
61
|
+
|
|
62
|
+
try:
|
|
63
|
+
# Get Cognito client ID from environment or use default
|
|
64
|
+
# Note: This default is for the shared modelshare.ai Cognito pool
|
|
65
|
+
client_id = os.getenv('COGNITO_CLIENT_ID', '7ptv9f8pt36elmg0e4v9v7jo9t')
|
|
66
|
+
region = os.getenv('COGNITO_REGION', 'us-east-2')
|
|
67
|
+
|
|
68
|
+
# Create unsigned config for Cognito client
|
|
69
|
+
config = botocore.config.Config(signature_version=botocore.UNSIGNED)
|
|
70
|
+
|
|
71
|
+
# Initialize Cognito provider client
|
|
72
|
+
provider_client = boto3.client(
|
|
73
|
+
"cognito-idp",
|
|
74
|
+
region_name=region,
|
|
75
|
+
config=config
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
# Authenticate with Cognito using USER_PASSWORD_AUTH flow
|
|
79
|
+
response = provider_client.initiate_auth(
|
|
80
|
+
ClientId=client_id,
|
|
81
|
+
AuthFlow="USER_PASSWORD_AUTH",
|
|
82
|
+
AuthParameters={
|
|
83
|
+
"USERNAME": username.strip(),
|
|
84
|
+
"PASSWORD": password.strip()
|
|
85
|
+
},
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
# Extract IdToken from response
|
|
89
|
+
token = response["AuthenticationResult"]["IdToken"]
|
|
90
|
+
|
|
91
|
+
logger.info(f"Successfully generated auth token for user: {username}")
|
|
92
|
+
return token
|
|
93
|
+
|
|
94
|
+
except Exception as err:
|
|
95
|
+
logger.error(f"Authentication failed for user {username}: {err}")
|
|
96
|
+
raise AuthorizationError(f"Could not authorize user. {str(err)}")
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def create_session_state() -> Dict[str, Any]:
|
|
100
|
+
"""
|
|
101
|
+
Create an initial session state dictionary for authentication.
|
|
102
|
+
|
|
103
|
+
This state object should be stored in a Gradio State component and
|
|
104
|
+
passed through all functions that need authentication information.
|
|
105
|
+
|
|
106
|
+
Returns:
|
|
107
|
+
Dict containing:
|
|
108
|
+
- 'username': str or None
|
|
109
|
+
- 'token': str or None
|
|
110
|
+
- 'team_name': str or None
|
|
111
|
+
- 'is_authenticated': bool
|
|
112
|
+
|
|
113
|
+
Example:
|
|
114
|
+
>>> session_state = gr.State(value=create_session_state())
|
|
115
|
+
"""
|
|
116
|
+
return {
|
|
117
|
+
'username': None,
|
|
118
|
+
'token': None,
|
|
119
|
+
'team_name': None,
|
|
120
|
+
'is_authenticated': False
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def authenticate_session(
|
|
125
|
+
session_state: Dict[str, Any],
|
|
126
|
+
username: str,
|
|
127
|
+
password: str
|
|
128
|
+
) -> Tuple[Dict[str, Any], bool, str]:
|
|
129
|
+
"""
|
|
130
|
+
Authenticate a user and update the session state.
|
|
131
|
+
|
|
132
|
+
Args:
|
|
133
|
+
session_state: Current session state dictionary
|
|
134
|
+
username: Username to authenticate
|
|
135
|
+
password: Password to authenticate
|
|
136
|
+
|
|
137
|
+
Returns:
|
|
138
|
+
Tuple of (updated_session_state, success, message)
|
|
139
|
+
- updated_session_state: New session state with auth info
|
|
140
|
+
- success: True if authentication succeeded
|
|
141
|
+
- message: User-friendly message about authentication result
|
|
142
|
+
|
|
143
|
+
Example:
|
|
144
|
+
>>> session_state = create_session_state()
|
|
145
|
+
>>> new_state, success, msg = authenticate_session(
|
|
146
|
+
... session_state, "myuser", "mypass"
|
|
147
|
+
... )
|
|
148
|
+
>>> if success:
|
|
149
|
+
... print(f"Logged in as {new_state['username']}")
|
|
150
|
+
"""
|
|
151
|
+
try:
|
|
152
|
+
# Generate token
|
|
153
|
+
token = generate_auth_token(username, password)
|
|
154
|
+
|
|
155
|
+
# Update session state
|
|
156
|
+
new_state = session_state.copy()
|
|
157
|
+
new_state['username'] = username.strip()
|
|
158
|
+
new_state['token'] = token
|
|
159
|
+
new_state['is_authenticated'] = True
|
|
160
|
+
|
|
161
|
+
message = f"✓ Successfully authenticated as {username}"
|
|
162
|
+
logger.info(f"Session authenticated for user: {username}")
|
|
163
|
+
|
|
164
|
+
return new_state, True, message
|
|
165
|
+
|
|
166
|
+
except Exception as e:
|
|
167
|
+
# Authentication failed - don't update state
|
|
168
|
+
error_msg = f"⚠️ Authentication failed: {str(e)}"
|
|
169
|
+
logger.error(f"Session authentication failed for user {username}: {e}")
|
|
170
|
+
|
|
171
|
+
return session_state, False, error_msg
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def get_session_token(session_state: Dict[str, Any]) -> Optional[str]:
|
|
175
|
+
"""
|
|
176
|
+
Get the authentication token from session state.
|
|
177
|
+
|
|
178
|
+
Args:
|
|
179
|
+
session_state: Current session state dictionary
|
|
180
|
+
|
|
181
|
+
Returns:
|
|
182
|
+
The authentication token, or None if not authenticated
|
|
183
|
+
|
|
184
|
+
Example:
|
|
185
|
+
>>> token = get_session_token(session_state)
|
|
186
|
+
>>> if token:
|
|
187
|
+
... # User is authenticated, proceed with API call
|
|
188
|
+
... api_client.set_token(token)
|
|
189
|
+
"""
|
|
190
|
+
if session_state and session_state.get('is_authenticated'):
|
|
191
|
+
return session_state.get('token')
|
|
192
|
+
return None
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def get_session_username(session_state: Dict[str, Any]) -> Optional[str]:
|
|
196
|
+
"""
|
|
197
|
+
Get the username from session state.
|
|
198
|
+
|
|
199
|
+
Args:
|
|
200
|
+
session_state: Current session state dictionary
|
|
201
|
+
|
|
202
|
+
Returns:
|
|
203
|
+
The username, or None if not authenticated
|
|
204
|
+
"""
|
|
205
|
+
if session_state and session_state.get('is_authenticated'):
|
|
206
|
+
return session_state.get('username')
|
|
207
|
+
return None
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def is_session_authenticated(session_state: Dict[str, Any]) -> bool:
|
|
211
|
+
"""
|
|
212
|
+
Check if the session is authenticated.
|
|
213
|
+
|
|
214
|
+
Args:
|
|
215
|
+
session_state: Current session state dictionary
|
|
216
|
+
|
|
217
|
+
Returns:
|
|
218
|
+
True if session is authenticated, False otherwise
|
|
219
|
+
"""
|
|
220
|
+
return bool(session_state and session_state.get('is_authenticated'))
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
def set_session_team(
|
|
224
|
+
session_state: Dict[str, Any],
|
|
225
|
+
team_name: str
|
|
226
|
+
) -> Dict[str, Any]:
|
|
227
|
+
"""
|
|
228
|
+
Set the team name in session state.
|
|
229
|
+
|
|
230
|
+
Args:
|
|
231
|
+
session_state: Current session state dictionary
|
|
232
|
+
team_name: Team name to assign
|
|
233
|
+
|
|
234
|
+
Returns:
|
|
235
|
+
Updated session state dictionary
|
|
236
|
+
"""
|
|
237
|
+
new_state = session_state.copy()
|
|
238
|
+
new_state['team_name'] = team_name
|
|
239
|
+
return new_state
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
def get_session_team(session_state: Dict[str, Any]) -> Optional[str]:
|
|
243
|
+
"""
|
|
244
|
+
Get the team name from session state.
|
|
245
|
+
|
|
246
|
+
Args:
|
|
247
|
+
session_state: Current session state dictionary
|
|
248
|
+
|
|
249
|
+
Returns:
|
|
250
|
+
The team name, or None if not set
|
|
251
|
+
"""
|
|
252
|
+
if session_state:
|
|
253
|
+
return session_state.get('team_name')
|
|
254
|
+
return None
|
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Shared Activity Styles for Activities 7, 8, and 9
|
|
3
|
+
*
|
|
4
|
+
* Consistent styling aligned with model_building_game.py patterns
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/* ============================================================================
|
|
8
|
+
KPI Cards (Moral Compass widgets, score displays)
|
|
9
|
+
============================================================================ */
|
|
10
|
+
|
|
11
|
+
.kpi-card {
|
|
12
|
+
background: var(--card-bg-strong, var(--block-background-fill));
|
|
13
|
+
border: 2px solid var(--accent-strong, #4f46e5);
|
|
14
|
+
padding: 24px;
|
|
15
|
+
border-radius: var(--slide-radius-lg, 12px);
|
|
16
|
+
text-align: center;
|
|
17
|
+
max-width: 600px;
|
|
18
|
+
margin: auto;
|
|
19
|
+
color: var(--text-main, #1f2937);
|
|
20
|
+
box-shadow: var(--shadow-drop, 0 4px 6px -1px rgba(0,0,0,0.08));
|
|
21
|
+
min-height: 200px;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
.kpi-card-body {
|
|
25
|
+
display: flex;
|
|
26
|
+
flex-wrap: wrap;
|
|
27
|
+
justify-content: space-around;
|
|
28
|
+
align-items: flex-end;
|
|
29
|
+
margin-top: 24px;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.kpi-metric-box {
|
|
33
|
+
min-width: 150px;
|
|
34
|
+
margin: 10px;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.kpi-label {
|
|
38
|
+
font-size: 1rem;
|
|
39
|
+
color: var(--text-muted, #6b7280);
|
|
40
|
+
margin: 0;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.kpi-score {
|
|
44
|
+
font-size: 3rem;
|
|
45
|
+
font-weight: 700;
|
|
46
|
+
margin: 0;
|
|
47
|
+
line-height: 1.1;
|
|
48
|
+
color: var(--accent-strong, #4f46e5);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
.kpi-subtext-muted {
|
|
52
|
+
font-size: 1.2rem;
|
|
53
|
+
font-weight: 500;
|
|
54
|
+
color: var(--text-muted, #6b7280);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/* KPI Card Variants */
|
|
58
|
+
.kpi-card--neutral {
|
|
59
|
+
border-color: var(--card-border-subtle, #e5e7eb);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.kpi-card--subtle-accent {
|
|
63
|
+
border-color: var(--accent-strong, #4f46e5);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.kpi-score--muted {
|
|
67
|
+
color: var(--text-muted, #6b7280);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/* ============================================================================
|
|
71
|
+
Leaderboard Tables
|
|
72
|
+
============================================================================ */
|
|
73
|
+
|
|
74
|
+
.leaderboard-html-table {
|
|
75
|
+
width: 100%;
|
|
76
|
+
border-collapse: collapse;
|
|
77
|
+
text-align: left;
|
|
78
|
+
font-size: 1rem;
|
|
79
|
+
color: var(--text-main, #1f2937);
|
|
80
|
+
min-height: 300px;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
.leaderboard-html-table thead {
|
|
84
|
+
background: var(--block-background-fill, #f9fafb);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
.leaderboard-html-table th {
|
|
88
|
+
padding: 12px 16px;
|
|
89
|
+
font-size: 0.9rem;
|
|
90
|
+
color: var(--text-muted, #6b7280);
|
|
91
|
+
font-weight: 500;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
.leaderboard-html-table tbody tr {
|
|
95
|
+
border-bottom: 1px solid var(--card-border-subtle, #e5e7eb);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
.leaderboard-html-table td {
|
|
99
|
+
padding: 12px 16px;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
.leaderboard-html-table tbody tr:hover {
|
|
103
|
+
background: var(--block-background-fill, #f9fafb);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/* User row highlighting */
|
|
107
|
+
.user-row-highlight {
|
|
108
|
+
background: color-mix(in srgb, var(--accent-strong, #4f46e5) 10%, transparent) !important;
|
|
109
|
+
font-weight: 600;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
.user-row-highlight td {
|
|
113
|
+
color: var(--accent-strong, #4f46e5);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/* ============================================================================
|
|
117
|
+
Buttons (Consistent variants)
|
|
118
|
+
============================================================================ */
|
|
119
|
+
|
|
120
|
+
/* Primary buttons - main progression actions */
|
|
121
|
+
.btn-primary {
|
|
122
|
+
background: var(--accent-strong, #4f46e5);
|
|
123
|
+
color: white;
|
|
124
|
+
border: none;
|
|
125
|
+
padding: 12px 24px;
|
|
126
|
+
border-radius: 8px;
|
|
127
|
+
font-weight: 600;
|
|
128
|
+
cursor: pointer;
|
|
129
|
+
transition: background 0.2s;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
.btn-primary:hover {
|
|
133
|
+
background: color-mix(in srgb, var(--accent-strong, #4f46e5) 90%, black);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/* Secondary buttons - back, cancel */
|
|
137
|
+
.btn-secondary {
|
|
138
|
+
background: var(--block-background-fill, #f9fafb);
|
|
139
|
+
color: var(--text-main, #1f2937);
|
|
140
|
+
border: 2px solid var(--card-border-subtle, #e5e7eb);
|
|
141
|
+
padding: 12px 24px;
|
|
142
|
+
border-radius: 8px;
|
|
143
|
+
font-weight: 600;
|
|
144
|
+
cursor: pointer;
|
|
145
|
+
transition: all 0.2s;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
.btn-secondary:hover {
|
|
149
|
+
border-color: var(--accent-strong, #4f46e5);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/* Neutral buttons - force sync, utility actions */
|
|
153
|
+
.btn-neutral {
|
|
154
|
+
background: var(--block-background-fill, #f9fafb);
|
|
155
|
+
color: var(--text-muted, #6b7280);
|
|
156
|
+
border: 1px solid var(--card-border-subtle, #e5e7eb);
|
|
157
|
+
padding: 10px 20px;
|
|
158
|
+
border-radius: 8px;
|
|
159
|
+
font-weight: 500;
|
|
160
|
+
cursor: pointer;
|
|
161
|
+
transition: all 0.2s;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
.btn-neutral:hover {
|
|
165
|
+
background: var(--card-bg-strong, var(--block-background-fill));
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/* Success buttons - certificate, completion */
|
|
169
|
+
.btn-success {
|
|
170
|
+
background: #10b981;
|
|
171
|
+
color: white;
|
|
172
|
+
border: none;
|
|
173
|
+
padding: 12px 24px;
|
|
174
|
+
border-radius: 8px;
|
|
175
|
+
font-weight: 600;
|
|
176
|
+
cursor: pointer;
|
|
177
|
+
transition: background 0.2s;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
.btn-success:hover {
|
|
181
|
+
background: #059669;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/* Info buttons - educational expansions, help */
|
|
185
|
+
.btn-info {
|
|
186
|
+
background: #3b82f6;
|
|
187
|
+
color: white;
|
|
188
|
+
border: none;
|
|
189
|
+
padding: 10px 20px;
|
|
190
|
+
border-radius: 8px;
|
|
191
|
+
font-weight: 500;
|
|
192
|
+
cursor: pointer;
|
|
193
|
+
transition: background 0.2s;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
.btn-info:hover {
|
|
197
|
+
background: #2563eb;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/* ============================================================================
|
|
201
|
+
Moral Compass Widget
|
|
202
|
+
============================================================================ */
|
|
203
|
+
|
|
204
|
+
.moral-compass-widget {
|
|
205
|
+
background: var(--block-background-fill, #f9fafb);
|
|
206
|
+
padding: 16px;
|
|
207
|
+
border-radius: 8px;
|
|
208
|
+
border: 2px solid var(--accent-strong, #4f46e5);
|
|
209
|
+
margin: 16px 0;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
.moral-compass-widget h3 {
|
|
213
|
+
margin-top: 0;
|
|
214
|
+
color: var(--text-main, #1f2937);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
.moral-compass-metrics {
|
|
218
|
+
display: flex;
|
|
219
|
+
justify-content: space-around;
|
|
220
|
+
flex-wrap: wrap;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
.moral-compass-metric {
|
|
224
|
+
text-align: center;
|
|
225
|
+
margin: 10px;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
.moral-compass-metric-label {
|
|
229
|
+
font-size: 0.9rem;
|
|
230
|
+
color: var(--text-muted, #6b7280);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
.moral-compass-metric-value {
|
|
234
|
+
font-size: 2rem;
|
|
235
|
+
font-weight: bold;
|
|
236
|
+
color: var(--accent-strong, #4f46e5);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
.moral-compass-metric-status {
|
|
240
|
+
font-size: 0.8rem;
|
|
241
|
+
color: var(--text-muted, #6b7280);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/* ============================================================================
|
|
245
|
+
Alert Panels (Error/Warning/Success messages)
|
|
246
|
+
============================================================================ */
|
|
247
|
+
|
|
248
|
+
.alert-panel {
|
|
249
|
+
padding: 16px;
|
|
250
|
+
border-radius: 8px;
|
|
251
|
+
margin: 16px 0;
|
|
252
|
+
border-left: 4px solid;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
.alert-panel--error {
|
|
256
|
+
background: #fef2f2;
|
|
257
|
+
border-color: #ef4444;
|
|
258
|
+
color: #991b1b;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
.alert-panel--warning {
|
|
262
|
+
background: #fffbeb;
|
|
263
|
+
border-color: #f59e0b;
|
|
264
|
+
color: #92400e;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
.alert-panel--success {
|
|
268
|
+
background: #f0fdf4;
|
|
269
|
+
border-color: #10b981;
|
|
270
|
+
color: #065f46;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
.alert-panel--info {
|
|
274
|
+
background: #eff6ff;
|
|
275
|
+
border-color: #3b82f6;
|
|
276
|
+
color: #1e40af;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/* ============================================================================
|
|
280
|
+
Educational Content Expansions
|
|
281
|
+
============================================================================ */
|
|
282
|
+
|
|
283
|
+
.educational-expansion {
|
|
284
|
+
background: var(--block-background-fill, #f9fafb);
|
|
285
|
+
padding: 20px;
|
|
286
|
+
border-radius: 8px;
|
|
287
|
+
border: 1px solid var(--card-border-subtle, #e5e7eb);
|
|
288
|
+
margin: 16px 0;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
.educational-expansion h4 {
|
|
292
|
+
margin-top: 0;
|
|
293
|
+
color: var(--accent-strong, #4f46e5);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
.educational-expansion-content {
|
|
297
|
+
color: var(--text-main, #1f2937);
|
|
298
|
+
line-height: 1.6;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/* ============================================================================
|
|
302
|
+
Dark Mode Support
|
|
303
|
+
============================================================================ */
|
|
304
|
+
|
|
305
|
+
@media (prefers-color-scheme: dark) {
|
|
306
|
+
.kpi-card {
|
|
307
|
+
background: color-mix(in srgb, var(--block-background-fill) 85%, #000 15%);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
.leaderboard-html-table thead {
|
|
311
|
+
background: color-mix(in srgb, var(--block-background-fill) 75%, #000 25%);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
.btn-secondary {
|
|
315
|
+
background: color-mix(in srgb, var(--block-background-fill) 90%, #fff 10%);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
.btn-neutral {
|
|
319
|
+
background: color-mix(in srgb, var(--block-background-fill) 85%, #fff 15%);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/* ============================================================================
|
|
324
|
+
Responsive Design
|
|
325
|
+
============================================================================ */
|
|
326
|
+
|
|
327
|
+
@media (max-width: 768px) {
|
|
328
|
+
.kpi-card {
|
|
329
|
+
padding: 16px;
|
|
330
|
+
min-height: 150px;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
.kpi-score {
|
|
334
|
+
font-size: 2rem;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
.leaderboard-html-table {
|
|
338
|
+
font-size: 0.9rem;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
.leaderboard-html-table th,
|
|
342
|
+
.leaderboard-html-table td {
|
|
343
|
+
padding: 8px 12px;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
.moral-compass-metric-value {
|
|
347
|
+
font-size: 1.5rem;
|
|
348
|
+
}
|
|
349
|
+
}
|