1use dioxus::prelude::*;
22use crate::ui::router::Route;
23use crate::ui::state::{LicenseState, RegistryAuthState};
24use crate::ui::theme;
25use crate::ui::theme_state::{ThemeState, theme_css};
26
27const GLOBAL_STYLE: &str = r#"
29:root {
30 /* Primary colors */
31 --color-primary-blue: #667eea;
32 --color-primary-purple: #764ba2;
33 --color-accent-blue: #60a5fa;
34 --color-accent-purple: #a78bfa;
35
36 /* Semantic colors */
37 --color-success: #4ade80;
38 --color-warning: #f59e0b;
39 --color-error: #e06c75;
40 --color-info: #60a5fa;
41
42 /* Text colors - MUCH lighter for readability */
43 --text-primary: #f5f5f5;
44 --text-secondary: #d0d0d0;
45 --text-tertiary: #b8b8b8;
46 --text-muted: #c0c0c0;
47 --text-placeholder: #a0a0a0;
48
49 /* Font sizes - minimum 14px for readability */
50 --font-display-xl: 66px;
51 --font-display-lg: 50px;
52 --font-display-md: 34px;
53 --font-heading-lg: 26px;
54 --font-heading-md: 22px;
55 --font-heading-sm: 20px;
56 --font-body-lg: 18px;
57 --font-body-md: 16px;
58 --font-body-sm: 15px;
59 --font-caption-lg: 15px;
60 --font-caption-md: 14px;
61 --font-caption-sm: 14px;
62
63 /* Font families */
64 --font-mono: 'SF Mono', 'Fira Code', 'Consolas', monospace;
65 --font-sans: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
66
67 /* Spacing */
68 --spacing-xs: 4px;
69 --spacing-sm: 8px;
70 --spacing-md: 12px;
71 --spacing-lg: 16px;
72 --spacing-xl: 24px;
73 --spacing-xxl: 32px;
74
75 /* Border radius */
76 --radius-sm: 4px;
77 --radius-md: 8px;
78 --radius-lg: 12px;
79 --radius-xl: 16px;
80 --radius-full: 9999px;
81}
82
83* {
84 margin: 0;
85 padding: 0;
86 box-sizing: border-box;
87}
88
89html, body {
90 height: 100%;
91 background: linear-gradient(135deg, #1a1a2e 0%, #16213e 50%, #0f3460 100%);
92 color: var(--text-primary);
93 font-family: var(--font-sans);
94 font-size: var(--font-body-lg);
95 overflow-x: hidden;
96}
97
98#main {
99 min-height: 100vh;
100}
101
102a {
103 color: inherit;
104 text-decoration: none;
105}
106
107.chat-area {
108 flex: 1;
109 overflow-y: auto;
110 padding: 30px;
111 display: flex;
112 flex-direction: column;
113 gap: var(--spacing-lg);
114}
115
116.message {
117 max-width: 75%;
118 padding: 14px 20px;
119 border-radius: var(--radius-xl);
120 line-height: 1.6;
121 animation: fadeIn 0.3s ease;
122}
123
124@keyframes fadeIn {
125 from { opacity: 0; transform: translateY(10px); }
126 to { opacity: 1; transform: translateY(0); }
127}
128
129.message.user {
130 align-self: flex-end;
131 background: linear-gradient(135deg, var(--color-primary-blue) 0%, var(--color-primary-purple) 100%);
132 color: white;
133 border-bottom-right-radius: var(--radius-sm);
134 box-shadow: 0 4px 15px rgba(102, 126, 234, 0.3);
135}
136
137.message.system {
138 align-self: flex-start;
139 background: rgba(255, 255, 255, 0.08);
140 border: 1px solid rgba(255, 255, 255, 0.15);
141 border-bottom-left-radius: var(--radius-sm);
142 font-family: var(--font-mono);
143 font-size: var(--font-heading-sm);
144 color: var(--accent-primary);
145 text-shadow: 0 0 20px var(--accent-glow);
146}
147
148.message.error {
149 align-self: flex-start;
150 background: linear-gradient(135deg, #ff6b6b 0%, #c92a2a 100%);
151 color: white;
152 border-bottom-left-radius: var(--radius-sm);
153 font-style: italic;
154 box-shadow: 0 4px 15px rgba(255, 107, 107, 0.3);
155}
156
157.input-area {
158 background: rgba(0, 0, 0, 0.4);
159 backdrop-filter: blur(10px);
160 padding: 20px 30px;
161 border-top: 1px solid rgba(255, 255, 255, 0.1);
162}
163
164.input-row {
165 display: flex;
166 gap: var(--spacing-md);
167 align-items: center;
168}
169
170.input-row input {
171 flex: 1;
172 background: rgba(255, 255, 255, 0.08);
173 border: 1px solid rgba(255, 255, 255, 0.2);
174 border-radius: var(--radius-lg);
175 padding: 14px 20px;
176 font-size: var(--font-body-lg);
177 color: white;
178 outline: none;
179 transition: all 0.2s ease;
180}
181
182.input-row input::placeholder {
183 color: var(--text-placeholder);
184}
185
186.input-row input:focus {
187 border-color: var(--color-primary-blue);
188 box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.2);
189}
190
191.input-row button {
192 background: linear-gradient(135deg, var(--color-primary-blue) 0%, var(--color-primary-purple) 100%);
193 border: none;
194 border-radius: var(--radius-lg);
195 padding: 14px 28px;
196 font-size: var(--font-body-lg);
197 font-weight: 600;
198 color: white;
199 cursor: pointer;
200 transition: all 0.2s ease;
201 box-shadow: 0 4px 15px rgba(102, 126, 234, 0.3);
202}
203
204.input-row button:hover {
205 transform: translateY(-2px);
206 box-shadow: 0 6px 20px rgba(102, 126, 234, 0.4);
207}
208
209.input-row button:active {
210 transform: translateY(0);
211}
212
213/* Interactive reveal section */
214.reveal-section {
215 margin-top: var(--spacing-lg);
216 padding-top: var(--spacing-lg);
217 border-top: 1px solid rgba(255,255,255,0.06);
218}
219
220.reveal-buttons {
221 display: flex;
222 gap: var(--spacing-md);
223 flex-wrap: wrap;
224 margin-bottom: var(--spacing-lg);
225}
226
227.reveal-btn {
228 padding: 10px 16px;
229 border-radius: var(--radius-md);
230 border: 1px solid rgba(255,255,255,0.15);
231 background: rgba(255,255,255,0.05);
232 color: var(--text-secondary);
233 font-size: var(--font-body-sm);
234 font-weight: 500;
235 cursor: pointer;
236 transition: all 0.2s ease;
237 display: inline-flex;
238 align-items: center;
239 gap: 6px;
240}
241
242.reveal-btn:hover {
243 background: rgba(255,255,255,0.10);
244 color: var(--text-primary);
245}
246
247.reveal-btn.active {
248 background: linear-gradient(135deg, rgba(var(--accent-primary-rgb),0.2), rgba(var(--accent-secondary-rgb),0.2));
249 border-color: rgba(var(--accent-secondary-rgb),0.4);
250 color: var(--text-primary);
251}
252
253.revealed-content {
254 padding: var(--spacing-lg);
255 background: rgba(255,255,255,0.03);
256 border: 1px solid rgba(255,255,255,0.08);
257 border-radius: var(--radius-lg);
258 margin-top: var(--spacing-md);
259 animation: fadeIn 0.2s ease;
260}
261
262.revealed-label {
263 font-size: var(--font-caption-sm);
264 font-weight: 600;
265 text-transform: uppercase;
266 letter-spacing: 0.5px;
267 color: var(--text-tertiary);
268 margin-bottom: var(--spacing-sm);
269}
270
271.revealed-logic {
272 font-family: var(--font-mono);
273 font-size: var(--font-heading-sm);
274 color: var(--accent-primary);
275 padding: var(--spacing-md);
276 background: rgba(var(--accent-primary-rgb), 0.08);
277 border-radius: var(--radius-md);
278 margin: var(--spacing-md) 0;
279}
280
281/* Socratic hint box */
282.socratic-hint-box {
283 margin-top: var(--spacing-lg);
284 padding: var(--spacing-lg);
285 background: linear-gradient(135deg, rgba(var(--accent-secondary-rgb),0.08), rgba(var(--accent-primary-rgb),0.08));
286 border: 1px solid rgba(var(--accent-secondary-rgb),0.2);
287 border-radius: var(--radius-lg);
288 border-left: 4px solid var(--accent-secondary);
289}
290
291.hint-header {
292 display: flex;
293 align-items: center;
294 gap: var(--spacing-sm);
295 margin-bottom: var(--spacing-sm);
296 font-size: var(--font-caption-md);
297 font-weight: 600;
298 color: var(--accent-secondary);
299}
300
301.hint-text {
302 color: var(--text-secondary);
303 line-height: 1.6;
304}
305
306/* Multiple choice options */
307.multiple-choice-options {
308 display: flex;
309 flex-direction: column;
310 gap: var(--spacing-sm);
311 margin: var(--spacing-lg) 0;
312}
313
314.multiple-choice-options .reveal-btn {
315 width: 100%;
316 text-align: left;
317 padding: var(--spacing-md) var(--spacing-lg);
318 font-family: var(--font-mono);
319}
320
321.multiple-choice-options .reveal-btn.correct {
322 background: rgba(74, 222, 128, 0.15);
323 border-color: var(--color-success);
324}
325
326.multiple-choice-options .reveal-btn.incorrect {
327 background: rgba(248, 113, 113, 0.15);
328 border-color: var(--color-error);
329}
330
331/* Progress indicator - shows exercise completion within a module */
332.exercise-progress {
333 display: flex;
334 align-items: center;
335 gap: var(--spacing-sm);
336 font-size: var(--font-caption-md);
337 color: var(--text-tertiary);
338 margin-bottom: var(--spacing-md);
339}
340
341.progress-bar {
342 flex: 1;
343 height: 4px;
344 background: rgba(255,255,255,0.1);
345 border-radius: 2px;
346 overflow: hidden;
347}
348
349.progress-fill {
350 height: 100%;
351 background: linear-gradient(90deg, var(--accent-primary), var(--accent-secondary));
352 border-radius: 2px;
353 transition: width 0.3s ease;
354}
355
356.practice-score {
357 font-weight: 600;
358 color: var(--color-success);
359}
360
361/* Exercise mode badges */
362.exercise-mode-badge {
363 display: inline-flex;
364 align-items: center;
365 gap: 4px;
366 padding: 4px 10px;
367 border-radius: var(--radius-full);
368 font-size: var(--font-caption-sm);
369 font-weight: 600;
370 text-transform: uppercase;
371 letter-spacing: 0.5px;
372 margin-right: var(--spacing-md);
373}
374
375.exercise-mode-badge.test {
376 background: rgba(251, 191, 36, 0.15);
377 color: #fbbf24;
378}
379
380.exercise-mode-badge.practice {
381 background: rgba(74, 222, 128, 0.15);
382 color: var(--color-success);
383}
384"#;
385
386pub fn App() -> Element {
397 let license_state = use_context_provider(LicenseState::new);
398 let _registry_auth = use_context_provider(RegistryAuthState::new);
399 let theme_state = use_context_provider(ThemeState::new);
400
401 use_effect(move || {
402 let mut license_state = license_state.clone();
403 spawn(async move {
404 if license_state.has_license() && license_state.needs_revalidation() {
405 license_state.validate().await;
406 }
407 });
408 });
409
410 let current_theme = theme_state.current();
412 let dynamic_theme_css = theme_css(current_theme);
413
414 rsx! {
415 style { "{GLOBAL_STYLE}" }
416 style { "{dynamic_theme_css}" }
417 div {
418 "data-theme": "{current_theme.data_attr()}",
419 id: "app-root",
420 Router::<Route> {}
425 }
426 }
427}