Skip to main content

coding_agent_search/html_export/
styles.rs

1//! CSS generation for HTML export.
2//!
3//! Terminal Noir design system - matching the reference implementation exactly.
4
5use super::template::ExportOptions;
6use tracing::debug;
7
8/// Bundle of CSS styles for the template.
9pub struct StyleBundle {
10    /// Critical CSS inlined in the document
11    pub critical_css: String,
12
13    /// Print-specific CSS
14    pub print_css: String,
15}
16
17/// Generate all CSS styles for the template.
18pub fn generate_styles(options: &ExportOptions) -> StyleBundle {
19    let critical_css = generate_critical_css(options);
20    let print_css = generate_print_css();
21    debug!(
22        component = "styles",
23        operation = "generate",
24        critical_bytes = critical_css.len(),
25        print_bytes = print_css.len(),
26        "Generated CSS styles"
27    );
28    StyleBundle {
29        critical_css,
30        print_css,
31    }
32}
33
34fn generate_critical_css(options: &ExportOptions) -> String {
35    let search_styles = if options.include_search {
36        SEARCH_STYLES
37    } else {
38        ""
39    };
40
41    let encryption_styles = if options.encrypt {
42        ENCRYPTION_STYLES
43    } else {
44        ""
45    };
46
47    format!(
48        "{}\n{}\n{}\n{}",
49        CORE_STYLES, COMPONENT_STYLES, search_styles, encryption_styles
50    )
51}
52
53/// Core design system - Terminal Noir (exact match to reference)
54const CORE_STYLES: &str = r#"
55/* ============================================
56   Agent Flywheel Design System - Terminal Noir
57   Exact match to globals.css reference
58   ============================================ */
59
60@font-face {
61  font-family: 'Space Grotesk';
62  src: local('Space Grotesk'), local('SpaceGrotesk');
63  font-weight: 400 700;
64  font-display: swap;
65}
66
67@font-face {
68  font-family: 'IBM Plex Sans';
69  src: local('IBM Plex Sans'), local('IBMPlexSans');
70  font-weight: 400 700;
71  font-display: swap;
72}
73
74@font-face {
75  font-family: 'JetBrains Mono';
76  src: local('JetBrains Mono'), local('JetBrainsMono');
77  font-weight: 400 700;
78  font-display: swap;
79}
80
81:root {
82  --radius: 0.75rem;
83
84  /* Deep space palette - from reference */
85  --background: oklch(0.11 0.015 260);
86  --foreground: oklch(0.95 0.01 260);
87
88  /* Cards with subtle elevation */
89  --card: oklch(0.14 0.02 260);
90  --card-foreground: oklch(0.95 0.01 260);
91
92  --popover: oklch(0.13 0.02 260);
93  --popover-foreground: oklch(0.95 0.01 260);
94
95  /* Electric cyan primary */
96  --primary: oklch(0.75 0.18 195);
97  --primary-foreground: oklch(0.13 0.02 260);
98
99  /* Muted backgrounds */
100  --secondary: oklch(0.18 0.02 260);
101  --secondary-foreground: oklch(0.85 0.01 260);
102
103  --muted: oklch(0.16 0.015 260);
104  --muted-foreground: oklch(0.6 0.02 260);
105
106  /* Warm amber accent */
107  --accent: oklch(0.78 0.16 75);
108  --accent-foreground: oklch(0.13 0.02 260);
109
110  /* Destructive red */
111  --destructive: oklch(0.65 0.22 25);
112
113  /* Borders and inputs */
114  --border: oklch(0.25 0.02 260);
115  --input: oklch(0.2 0.02 260);
116  --ring: oklch(0.75 0.18 195);
117
118  /* Custom accent colors */
119  --cyan: oklch(0.75 0.18 195);
120  --amber: oklch(0.78 0.16 75);
121  --magenta: oklch(0.7 0.2 330);
122  --green: oklch(0.72 0.19 145);
123  --purple: oklch(0.65 0.18 290);
124  --red: oklch(0.65 0.22 25);
125
126  /* Typography Scale - Fluid */
127  --text-xs: clamp(0.6875rem, 0.65rem + 0.15vw, 0.75rem);
128  --text-sm: clamp(0.8125rem, 0.775rem + 0.2vw, 0.875rem);
129  --text-base: clamp(1rem, 0.95rem + 0.25vw, 1.125rem);
130  --text-lg: clamp(1.125rem, 1.05rem + 0.4vw, 1.375rem);
131  --text-xl: clamp(1.375rem, 1.25rem + 0.65vw, 1.75rem);
132  --text-2xl: clamp(1.625rem, 1.45rem + 0.9vw, 2.25rem);
133
134  /* Spacing System */
135  --space-1: 0.25rem;
136  --space-2: 0.5rem;
137  --space-3: 0.75rem;
138  --space-4: 1rem;
139  --space-5: 1.25rem;
140  --space-6: 1.5rem;
141  --space-8: 2rem;
142  --space-10: 2.5rem;
143  --space-12: 3rem;
144  --space-16: 4rem;
145
146  /* Enhanced Shadow System - from reference */
147  --shadow-xs: 0 1px 2px oklch(0 0 0 / 0.08);
148  --shadow-sm: 0 2px 4px oklch(0 0 0 / 0.08), 0 1px 2px oklch(0 0 0 / 0.06);
149  --shadow-md: 0 4px 8px oklch(0 0 0 / 0.1), 0 2px 4px oklch(0 0 0 / 0.06);
150  --shadow-lg: 0 8px 24px oklch(0 0 0 / 0.12), 0 4px 8px oklch(0 0 0 / 0.06);
151  --shadow-xl: 0 16px 48px oklch(0 0 0 / 0.16), 0 8px 16px oklch(0 0 0 / 0.08);
152
153  /* Colored glow shadows - from reference */
154  --shadow-glow-sm: 0 0 12px oklch(0.75 0.18 195 / 0.2);
155  --shadow-glow: 0 0 24px oklch(0.75 0.18 195 / 0.25), 0 0 48px oklch(0.75 0.18 195 / 0.1);
156  --shadow-glow-primary: 0 4px 20px oklch(0.75 0.18 195 / 0.35), 0 0 0 1px oklch(0.75 0.18 195 / 0.15);
157  --shadow-glow-amber: 0 4px 20px oklch(0.78 0.16 75 / 0.3), 0 0 0 1px oklch(0.78 0.16 75 / 0.15);
158
159  /* Radius system */
160  --radius-sm: calc(var(--radius) - 4px);
161  --radius-md: calc(var(--radius) - 2px);
162  --radius-lg: var(--radius);
163  --radius-xl: calc(var(--radius) + 4px);
164
165  /* Transitions */
166  --transition-fast: 150ms cubic-bezier(0.4, 0, 0.2, 1);
167  --transition-normal: 250ms cubic-bezier(0.4, 0, 0.2, 1);
168
169  /* Touch targets */
170  --touch-min: 44px;
171}
172
173/* Light mode - from reference */
174[data-theme="light"] {
175  --background: oklch(0.98 0.005 260);
176  --foreground: oklch(0.15 0.02 260);
177  --card: oklch(1 0 0);
178  --card-foreground: oklch(0.15 0.02 260);
179  --popover: oklch(1 0 0);
180  --popover-foreground: oklch(0.15 0.02 260);
181  --primary: oklch(0.55 0.2 195);
182  --primary-foreground: oklch(1 0 0);
183  --secondary: oklch(0.94 0.01 260);
184  --secondary-foreground: oklch(0.2 0.02 260);
185  --muted: oklch(0.94 0.01 260);
186  --muted-foreground: oklch(0.45 0.02 260);
187  --accent: oklch(0.65 0.18 75);
188  --accent-foreground: oklch(0.15 0.02 260);
189  --destructive: oklch(0.55 0.25 25);
190  --border: oklch(0.9 0.01 260);
191  --input: oklch(0.92 0.01 260);
192  --ring: oklch(0.55 0.2 195);
193
194  --cyan: oklch(0.55 0.2 195);
195  --green: oklch(0.5 0.18 145);
196  --amber: oklch(0.6 0.18 75);
197}
198
199/* Base reset */
200*, *::before, *::after {
201  box-sizing: border-box;
202  margin: 0;
203  padding: 0;
204}
205
206html {
207  overflow-x: hidden;
208  scroll-behavior: smooth;
209  -webkit-font-smoothing: antialiased;
210  -moz-osx-font-smoothing: grayscale;
211}
212
213body {
214  font-family: 'Space Grotesk', 'IBM Plex Sans', 'Manrope', sans-serif;
215  font-size: var(--text-base);
216  line-height: 1.65;
217  color: #e8e9ed;
218  color: var(--foreground);
219  /* Solid dark background - hex fallback first, then oklch if supported */
220  background-color: #16161f;
221  min-height: 100vh;
222  min-height: 100dvh;
223  overflow-x: hidden;
224  max-width: 100vw;
225}
226
227/* Override background with oklch for modern browsers */
228@supports (background: oklch(0.11 0.015 260)) {
229  body {
230    background-color: oklch(0.11 0.015 260);
231  }
232}
233
234/* Hero background overlay - subtle ambient glow */
235body::before {
236  content: '';
237  position: fixed;
238  inset: 0;
239  pointer-events: none;
240  z-index: -1;
241  background:
242    radial-gradient(ellipse at 30% 20%, rgba(70, 180, 220, 0.12) 0%, transparent 40%),
243    radial-gradient(ellipse at 70% 80%, rgba(200, 100, 180, 0.08) 0%, transparent 40%),
244    radial-gradient(ellipse at 90% 30%, rgba(220, 180, 80, 0.06) 0%, transparent 30%);
245}
246
247/* Custom scrollbar - from reference */
248::-webkit-scrollbar {
249  width: 8px;
250  height: 8px;
251}
252::-webkit-scrollbar-track {
253  background: oklch(0.14 0.02 260);
254}
255::-webkit-scrollbar-thumb {
256  background: oklch(0.3 0.02 260);
257  border-radius: 4px;
258}
259::-webkit-scrollbar-thumb:hover {
260  background: oklch(0.4 0.02 260);
261}
262
263/* Firefox scrollbar */
264* {
265  scrollbar-width: thin;
266  scrollbar-color: oklch(0.3 0.02 260) oklch(0.14 0.02 260);
267}
268
269/* ============================================
270   Layout - Full Width Utilization
271   ============================================ */
272
273.app-container {
274  width: 100%;
275  max-width: 100%;
276  margin: 0 auto;
277  padding: var(--space-4);
278  padding-bottom: calc(var(--space-8) + env(safe-area-inset-bottom, 0px));
279}
280
281@media (min-width: 768px) {
282  .app-container {
283    padding: var(--space-6) var(--space-8);
284  }
285}
286
287@media (min-width: 1024px) {
288  .app-container {
289    padding: var(--space-8) var(--space-12);
290    max-width: calc(100% - 80px);
291  }
292}
293
294@media (min-width: 1280px) {
295  .app-container {
296    max-width: calc(100% - 160px);
297    padding: var(--space-8) var(--space-16);
298  }
299}
300
301@media (min-width: 1536px) {
302  .app-container {
303    max-width: 1400px;
304  }
305}
306
307/* ============================================
308   Glass morphism - exact match to reference
309   ============================================ */
310
311.glass {
312  background: oklch(0.14 0.02 260 / 0.8);
313  backdrop-filter: blur(12px);
314  -webkit-backdrop-filter: blur(12px);
315  border: 1px solid oklch(0.3 0.02 260 / 0.3);
316}
317
318.glass-subtle {
319  background: oklch(0.14 0.02 260 / 0.6);
320  backdrop-filter: blur(8px);
321  -webkit-backdrop-filter: blur(8px);
322}
323
324/* ============================================
325   Typography
326   ============================================ */
327
328h1, h2, h3, h4, h5, h6 {
329  font-weight: 600;
330  line-height: 1.3;
331  color: var(--foreground);
332  letter-spacing: -0.02em;
333}
334
335h1 { font-size: var(--text-2xl); }
336h2 { font-size: var(--text-xl); }
337h3 { font-size: var(--text-lg); }
338
339p {
340  margin-bottom: 1em;
341}
342p:last-child { margin-bottom: 0; }
343
344a {
345  color: var(--primary);
346  text-decoration: none;
347  transition: color var(--transition-fast);
348}
349
350a:hover {
351  color: oklch(0.85 0.18 195);
352  text-decoration: underline;
353}
354
355/* Inline code */
356code:not(pre code) {
357  font-family: 'JetBrains Mono', 'Fira Code', 'SF Mono', ui-monospace, monospace;
358  font-size: 0.875em;
359  padding: 0.125rem 0.375rem;
360  background: var(--secondary);
361  border: 1px solid var(--border);
362  border-radius: var(--radius-sm);
363  color: var(--primary);
364  overflow-wrap: break-word;
365  word-break: break-word;
366}
367
368/* Code blocks */
369pre {
370  font-family: 'JetBrains Mono', 'Fira Code', 'SF Mono', ui-monospace, monospace;
371  font-size: 0.8125rem;
372  line-height: 1.7;
373  background: oklch(0.08 0.015 260);
374  border: 1px solid var(--border);
375  border-radius: var(--radius-lg);
376  padding: var(--space-4);
377  overflow-x: auto;
378  margin: var(--space-4) 0;
379  max-width: 100%;
380}
381
382pre code {
383  padding: 0;
384  background: transparent;
385  border: none;
386  color: var(--foreground);
387  font-size: inherit;
388}
389
390/* Lists */
391ul, ol {
392  margin: var(--space-2) 0;
393  padding-left: 1.5em;
394}
395li {
396  margin-bottom: 0.25em;
397}
398li::marker { color: var(--muted-foreground); }
399
400/* Blockquotes */
401blockquote {
402  border-left: 3px solid var(--primary);
403  padding: var(--space-2) var(--space-4);
404  margin: var(--space-4) 0;
405  background: linear-gradient(90deg, oklch(0.75 0.18 195 / 0.05) 0%, transparent 100%);
406  border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
407  color: var(--secondary-foreground);
408}
409
410/* Tables */
411table {
412  width: 100%;
413  border-collapse: collapse;
414  margin: var(--space-4) 0;
415  font-size: 0.875rem;
416}
417th, td {
418  padding: var(--space-2) var(--space-3);
419  border: 1px solid var(--border);
420  text-align: left;
421}
422th {
423  background: var(--secondary);
424  font-weight: 600;
425  font-size: 0.75rem;
426  text-transform: uppercase;
427  letter-spacing: 0.5px;
428  color: var(--muted-foreground);
429}
430tr:hover td {
431  background: var(--muted);
432}
433"#;
434
435const COMPONENT_STYLES: &str = r#"
436/* ============================================
437   Header - Terminal Style
438   ============================================ */
439
440.header {
441  margin-bottom: var(--space-6);
442  padding: var(--space-4) var(--space-5);
443  background: var(--card);
444  border: 1px solid var(--border);
445  border-radius: var(--radius-xl);
446  position: relative;
447}
448
449/* Terminal traffic lights */
450.header::before {
451  content: '';
452  position: absolute;
453  top: var(--space-4);
454  left: var(--space-5);
455  width: 12px;
456  height: 12px;
457  border-radius: 50%;
458  background: oklch(0.65 0.22 25);
459  box-shadow:
460    20px 0 0 oklch(0.78 0.16 75),
461    40px 0 0 oklch(0.72 0.19 145);
462}
463
464.header-content {
465  padding-left: 72px;
466}
467
468.header-title {
469  font-size: var(--text-lg);
470  font-weight: 600;
471  color: var(--foreground);
472  margin-bottom: var(--space-2);
473  line-height: 1.4;
474  font-family: 'Space Grotesk', 'IBM Plex Sans', sans-serif;
475}
476
477.header-meta {
478  display: flex;
479  flex-wrap: wrap;
480  align-items: center;
481  gap: var(--space-2) var(--space-4);
482  font-size: var(--text-sm);
483  color: var(--muted-foreground);
484}
485
486.header-meta span {
487  display: inline-flex;
488  align-items: center;
489  gap: 6px;
490}
491
492.header-agent {
493  color: var(--primary);
494  font-weight: 500;
495}
496
497.header-project {
498  font-family: 'JetBrains Mono', ui-monospace, monospace;
499  font-size: var(--text-xs);
500  padding: 0.25rem 0.625rem;
501  background: var(--secondary);
502  border: 1px solid var(--border);
503  border-radius: var(--radius-sm);
504  color: var(--muted-foreground);
505}
506
507/* ============================================
508   Toolbar - Glassmorphic
509   ============================================ */
510
511.toolbar {
512  position: sticky;
513  top: var(--space-4);
514  z-index: 50;
515  display: flex;
516  align-items: center;
517  gap: var(--space-2);
518  padding: var(--space-3) var(--space-4);
519  margin-bottom: var(--space-6);
520  background: oklch(0.14 0.02 260 / 0.8);
521  backdrop-filter: blur(12px);
522  -webkit-backdrop-filter: blur(12px);
523  border: 1px solid oklch(0.3 0.02 260 / 0.3);
524  border-radius: var(--radius-xl);
525  box-shadow: var(--shadow-lg);
526  transition: all var(--transition-normal);
527}
528
529.toolbar:hover {
530  box-shadow: var(--shadow-xl), var(--shadow-glow-sm);
531}
532
533[data-theme="light"] .toolbar {
534  background: oklch(1 0 0 / 0.85);
535  border-color: var(--border);
536}
537
538.search-wrapper {
539  flex: 1;
540  position: relative;
541  min-width: 0;
542}
543
544.search-input {
545  width: 100%;
546  padding: 0.625rem 0.875rem;
547  padding-right: 3rem;
548  font-size: var(--text-sm);
549  color: var(--foreground);
550  background: var(--input);
551  border: 1px solid var(--border);
552  border-radius: var(--radius-md);
553  outline: none;
554  transition: all var(--transition-fast);
555}
556
557.search-input::placeholder {
558  color: var(--muted-foreground);
559}
560
561.search-input:hover {
562  border-color: oklch(0.35 0.02 260);
563}
564
565.search-input:focus {
566  border-color: var(--primary);
567  box-shadow: 0 0 0 3px oklch(0.75 0.18 195 / 0.15), var(--shadow-glow-sm);
568}
569
570.search-count {
571  position: absolute;
572  right: 0.875rem;
573  top: 50%;
574  transform: translateY(-50%);
575  font-size: var(--text-xs);
576  font-weight: 500;
577  color: var(--muted-foreground);
578  background: var(--secondary);
579  padding: 0.125rem 0.375rem;
580  border-radius: var(--radius-sm);
581}
582
583.toolbar-btn {
584  display: flex;
585  align-items: center;
586  justify-content: center;
587  width: var(--touch-min);
588  height: var(--touch-min);
589  min-width: var(--touch-min);
590  background: transparent;
591  border: 1px solid transparent;
592  border-radius: var(--radius-md);
593  color: var(--muted-foreground);
594  cursor: pointer;
595  transition: all var(--transition-fast);
596  position: relative;
597}
598
599.toolbar-btn:hover {
600  background: var(--secondary);
601  border-color: var(--border);
602  color: var(--foreground);
603}
604
605.toolbar-btn:active {
606  transform: scale(0.95);
607}
608
609.toolbar-btn svg {
610  width: 20px;
611  height: 20px;
612  transition: transform var(--transition-fast);
613}
614
615.toolbar-btn:hover svg {
616  transform: scale(1.1);
617}
618
619/* Theme toggle icon states */
620.icon-sun, .icon-moon {
621  transition: opacity var(--transition-fast), transform var(--transition-fast);
622}
623[data-theme="dark"] .icon-sun { opacity: 0; position: absolute; transform: rotate(90deg) scale(0.8); }
624[data-theme="dark"] .icon-moon { opacity: 1; }
625[data-theme="light"] .icon-sun { opacity: 1; }
626[data-theme="light"] .icon-moon { opacity: 0; position: absolute; transform: rotate(-90deg) scale(0.8); }
627
628/* ============================================
629   Messages - Card Based
630   ============================================ */
631
632.conversation {
633  display: flex;
634  flex-direction: column;
635  gap: var(--space-4);
636  position: relative;
637  z-index: 1;
638}
639
640/* Message wrapper - inherits conversation layout */
641.conversation-messages {
642  display: flex;
643  flex-direction: column;
644  gap: var(--space-4);
645}
646
647.message {
648  position: relative;
649  padding: var(--space-4) var(--space-5);
650  background: #1e1e28;
651  background: var(--card);
652  border: 1px solid #2d2d3a;
653  border: 1px solid var(--border);
654  border-radius: var(--radius-xl);
655  border-left: 4px solid #2d2d3a;
656  border-left: 4px solid var(--border);
657  transition: all var(--transition-fast);
658}
659
660.message:hover {
661  border-color: oklch(0.35 0.02 260);
662  box-shadow: var(--shadow-md);
663}
664
665.message.search-hit {
666  border-color: var(--primary);
667  box-shadow: var(--shadow-md), var(--shadow-glow-sm);
668}
669
670/* Role-specific styling */
671.message-user {
672  border-left-color: var(--green);
673}
674.message-user:hover {
675  box-shadow: var(--shadow-md), 0 0 30px -10px var(--green);
676}
677
678.message-assistant, .message-agent {
679  border-left-color: var(--primary);
680}
681.message-assistant:hover, .message-agent:hover {
682  box-shadow: var(--shadow-md), 0 0 30px -10px var(--primary);
683}
684
685.message-tool {
686  border-left-color: var(--amber);
687}
688.message-tool:hover {
689  box-shadow: var(--shadow-md), 0 0 30px -10px var(--amber);
690}
691
692.message-system {
693  border-left-color: var(--purple);
694  background: linear-gradient(135deg, var(--card) 0%, oklch(0.65 0.18 290 / 0.03) 100%);
695}
696
697.message-header {
698  display: flex;
699  align-items: center;
700  justify-content: space-between;
701  gap: var(--space-3);
702  margin-bottom: var(--space-3);
703  padding-bottom: var(--space-2);
704  border-bottom: 1px solid oklch(0.25 0.02 260 / 0.5);
705}
706
707.message-header-left {
708  display: flex;
709  align-items: center;
710  gap: var(--space-2);
711  min-width: 0;
712}
713
714.message-header-right {
715  display: flex;
716  flex-wrap: wrap;
717  align-items: center;
718  gap: var(--space-1);
719  flex-shrink: 0;
720}
721
722/* Lucide SVG icon styling */
723.lucide-icon {
724  display: inline-block;
725  vertical-align: middle;
726  flex-shrink: 0;
727}
728
729.lucide-spin {
730  animation: lucide-spin 1s linear infinite;
731}
732
733@keyframes lucide-spin {
734  from { transform: rotate(0deg); }
735  to { transform: rotate(360deg); }
736}
737
738.message-icon {
739  display: flex;
740  align-items: center;
741  justify-content: center;
742  width: 24px;
743  height: 24px;
744  line-height: 1;
745}
746
747.message-icon .lucide-icon {
748  width: 16px;
749  height: 16px;
750}
751
752.message-author {
753  font-weight: 600;
754  font-size: var(--text-sm);
755  letter-spacing: -0.01em;
756}
757
758.message-user .message-author { color: var(--green); }
759.message-assistant .message-author, .message-agent .message-author { color: var(--primary); }
760.message-tool .message-author { color: var(--amber); }
761.message-system .message-author { color: var(--purple); }
762
763.message-time {
764  margin-left: auto;
765  font-size: var(--text-xs);
766  font-weight: 500;
767  color: var(--muted-foreground);
768  font-variant-numeric: tabular-nums;
769}
770
771.message-content {
772  font-size: var(--text-base);
773  line-height: 1.7;
774  color: var(--secondary-foreground);
775}
776
777.message-content > *:first-child { margin-top: 0; }
778.message-content > *:last-child { margin-bottom: 0; }
779
780/* Message content typography */
781.message-content p { margin-bottom: 0.85em; }
782.message-content h1, .message-content h2, .message-content h3 {
783  margin-top: 1.25em;
784  margin-bottom: 0.5em;
785  font-weight: 600;
786  color: var(--foreground);
787}
788.message-content h1 { font-size: 1.25rem; }
789.message-content h2 { font-size: 1.125rem; }
790.message-content h3 { font-size: 1rem; }
791.message-content ul, .message-content ol {
792  margin: 0.5em 0;
793  padding-left: 1.25em;
794}
795.message-content li { margin-bottom: 0.25em; }
796.message-content li::marker { color: var(--muted-foreground); }
797.message-content strong { color: var(--foreground); font-weight: 600; }
798
799/* Message link button */
800.message-link {
801  position: absolute;
802  top: var(--space-4);
803  right: var(--space-4);
804  opacity: 0;
805  padding: 6px;
806  background: var(--secondary);
807  border: 1px solid var(--border);
808  border-radius: var(--radius-sm);
809  color: var(--muted-foreground);
810  cursor: pointer;
811  transition: all var(--transition-fast);
812}
813
814.message:hover .message-link { opacity: 1; }
815.message-link:hover {
816  color: var(--primary);
817  border-color: var(--primary);
818  box-shadow: var(--shadow-glow-sm);
819}
820.message-link.copied {
821  color: var(--green);
822  border-color: var(--green);
823}
824
825/* ============================================
826   Tool Calls - Collapsible
827   ============================================ */
828
829/* Tool Badge - Compact inline badges with hover popovers */
830.tool-badges {
831  display: flex;
832  flex-wrap: wrap;
833  align-items: center;
834  gap: 4px;
835}
836
837.tool-badge {
838  position: relative;
839  display: inline-flex;
840  align-items: center;
841  justify-content: center;
842  min-width: 24px;
843  height: 24px;
844  padding: 0 4px;
845  font-size: 0.6875rem;
846  font-family: 'JetBrains Mono', ui-monospace, monospace;
847  background: transparent;
848  appearance: none;
849  -webkit-appearance: none;
850  border: 1px solid oklch(0.3 0.02 260 / 0.5);
851  border-radius: 6px;
852  cursor: pointer;
853  transition: all var(--transition-fast);
854  white-space: nowrap;
855  color: var(--amber);
856}
857
858.tool-badge:hover,
859.tool-badge:focus {
860  background: oklch(0.78 0.16 75 / 0.15);
861  border-color: var(--amber);
862  transform: scale(1.1);
863  outline: none;
864  box-shadow: var(--shadow-glow-amber);
865}
866
867.tool-badge:focus-visible {
868  box-shadow: 0 0 0 2px var(--primary);
869}
870
871.tool-badge-icon {
872  display: flex;
873  align-items: center;
874  justify-content: center;
875}
876
877.tool-badge-icon .lucide-icon {
878  width: 14px;
879  height: 14px;
880  stroke-width: 2;
881}
882
883.tool-badge-status {
884  display: inline-flex;
885  align-items: center;
886  justify-content: center;
887  position: absolute;
888  top: 2px;
889  right: 2px;
890  width: 6px;
891  height: 6px;
892  border-radius: 50%;
893  padding: 0;
894}
895
896.tool-badge-status .lucide-icon {
897  display: none;
898}
899
900/* Status-based badge styling with subtle left accent */
901.tool-badge.tool-status-success { border-color: var(--green); }
902.tool-badge.tool-status-error { border-color: var(--red); }
903.tool-badge.tool-status-pending { border-color: var(--amber); }
904
905.tool-badge.tool-status-success:hover { box-shadow: 0 4px 20px oklch(0.72 0.19 145 / 0.35); }
906.tool-badge.tool-status-error:hover { box-shadow: 0 4px 20px oklch(0.65 0.22 25 / 0.35); }
907
908.tool-badge-status.success { background: oklch(0.72 0.19 145 / 0.8); }
909.tool-badge-status.error { background: oklch(0.65 0.22 25 / 0.85); }
910.tool-badge-status.pending { background: oklch(0.78 0.16 75 / 0.85); }
911
912/* Overflow badge - "+X more" */
913.tool-badge.tool-overflow {
914  min-width: auto;
915  padding: 0 8px;
916  font-size: 0.6875rem;
917  font-weight: 600;
918  color: var(--muted-foreground);
919  border-style: dashed;
920}
921
922.tool-badge.tool-overflow:hover {
923  color: var(--foreground);
924  border-style: solid;
925}
926
927/* Overflowed badges stay in the DOM so expansion can reveal them. */
928.tool-badge.tool-overflow-extra {
929  display: none;
930}
931
932.message-header-right.expanded .tool-overflow-extra {
933  display: inline-flex;
934}
935
936.message-header-right.expanded .tool-overflow {
937  order: 999; /* Move to end */
938}
939
940/* Popover - Glassmorphic with fixed positioning */
941.tool-popover {
942  position: absolute;
943  z-index: 1000;
944  min-width: 280px;
945  max-width: 400px;
946  max-height: 300px;
947  overflow: auto;
948  padding: var(--space-3);
949  background: oklch(0.14 0.02 260 / 0.95);
950  backdrop-filter: blur(16px);
951  -webkit-backdrop-filter: blur(16px);
952  border: 1px solid oklch(0.3 0.02 260 / 0.5);
953  border-radius: var(--radius-lg);
954  box-shadow: var(--shadow-xl), var(--shadow-glow-sm);
955  opacity: 0;
956  visibility: hidden;
957  transform: translateY(-4px);
958  transition: all 0.15s ease-out;
959  pointer-events: none;
960  text-align: left;
961  white-space: normal;
962  top: calc(100% + 8px);
963  left: 0;
964}
965
966.tool-popover.visible {
967  opacity: 1;
968  visibility: visible;
969  transform: translateY(0);
970  pointer-events: auto;
971}
972
973/* Fallback: show popover on hover/focus even if JS fails */
974.tool-badge:hover .tool-popover,
975.tool-badge:focus-within .tool-popover {
976  opacity: 1;
977  visibility: visible;
978  transform: translateY(0);
979  pointer-events: auto;
980}
981
982/* Light theme popover */
983[data-theme="light"] .tool-popover {
984  background: oklch(1 0 0 / 0.95);
985  border-color: var(--border);
986  box-shadow: 0 8px 32px oklch(0 0 0 / 0.15);
987}
988
989/* Arrow indicator (CSS-only, optional) */
990.tool-popover::before {
991  content: '';
992  position: absolute;
993  top: -6px;
994  left: 20px;
995  width: 12px;
996  height: 12px;
997  background: inherit;
998  border: inherit;
999  border-right: none;
1000  border-bottom: none;
1001  transform: rotate(45deg);
1002  pointer-events: none;
1003}
1004
1005.tool-popover.popover-above::before {
1006  top: auto;
1007  bottom: -6px;
1008  transform: rotate(225deg);
1009}
1010
1011.tool-popover-header {
1012  display: flex;
1013  align-items: center;
1014  gap: var(--space-2);
1015  padding-bottom: var(--space-2);
1016  margin-bottom: var(--space-2);
1017  border-bottom: 1px solid var(--border);
1018  font-weight: 600;
1019  color: var(--amber);
1020}
1021
1022.tool-popover-header .lucide-icon {
1023  width: 14px;
1024  height: 14px;
1025  flex-shrink: 0;
1026}
1027
1028.tool-popover-header span {
1029  overflow: hidden;
1030  text-overflow: ellipsis;
1031  white-space: nowrap;
1032}
1033
1034.tool-popover-section {
1035  margin-bottom: var(--space-2);
1036}
1037.tool-popover-section:last-child { margin-bottom: 0; }
1038
1039.tool-popover-label {
1040  font-size: 0.5625rem;
1041  font-weight: 700;
1042  text-transform: uppercase;
1043  letter-spacing: 0.8px;
1044  color: var(--muted-foreground);
1045  margin-bottom: 0.25rem;
1046}
1047
1048.tool-popover pre {
1049  margin: 0;
1050  padding: var(--space-2);
1051  font-size: 0.625rem;
1052  background: var(--secondary);
1053  border-radius: var(--radius-sm);
1054  max-height: 150px;
1055  overflow: auto;
1056  white-space: pre-wrap;
1057  word-break: break-word;
1058}
1059
1060.tool-truncated {
1061  font-size: 0.5625rem;
1062  color: var(--amber);
1063  margin-top: 0.25rem;
1064  font-weight: 500;
1065  font-style: italic;
1066}
1067
1068/* ============================================
1069   Floating Navigation
1070   ============================================ */
1071
1072.floating-nav {
1073  position: fixed;
1074  bottom: calc(24px + env(safe-area-inset-bottom, 0px));
1075  right: 24px;
1076  display: flex;
1077  flex-direction: column;
1078  gap: var(--space-2);
1079  opacity: 0;
1080  transform: translateY(20px) scale(0.9);
1081  transition: all var(--transition-normal);
1082  pointer-events: none;
1083  z-index: 100;
1084}
1085
1086.floating-nav.visible {
1087  opacity: 1;
1088  transform: translateY(0) scale(1);
1089  pointer-events: auto;
1090}
1091
1092.floating-btn {
1093  width: 48px;
1094  height: 48px;
1095  display: flex;
1096  align-items: center;
1097  justify-content: center;
1098  background: oklch(0.14 0.02 260 / 0.8);
1099  backdrop-filter: blur(12px);
1100  -webkit-backdrop-filter: blur(12px);
1101  border: 1px solid oklch(0.3 0.02 260 / 0.3);
1102  border-radius: var(--radius-xl);
1103  color: var(--muted-foreground);
1104  cursor: pointer;
1105  box-shadow: var(--shadow-lg);
1106  transition: all var(--transition-fast);
1107}
1108
1109.floating-btn:hover {
1110  background: var(--secondary);
1111  border-color: var(--primary);
1112  color: var(--primary);
1113  box-shadow: var(--shadow-lg), var(--shadow-glow);
1114  transform: translateY(-2px);
1115}
1116
1117.floating-btn:active {
1118  transform: scale(0.95);
1119}
1120
1121.floating-btn svg {
1122  width: 22px;
1123  height: 22px;
1124}
1125
1126/* ============================================
1127   Scroll Progress
1128   ============================================ */
1129
1130.scroll-progress {
1131  position: fixed;
1132  top: 0;
1133  left: 0;
1134  height: 3px;
1135  background: linear-gradient(90deg, var(--primary), var(--magenta), var(--primary));
1136  background-size: 200% 100%;
1137  z-index: 1000;
1138  width: 0;
1139  transition: width 0.1s ease-out;
1140  box-shadow: 0 0 10px var(--primary);
1141}
1142
1143/* ============================================
1144   Keyboard Shortcuts Hint
1145   ============================================ */
1146
1147.shortcuts-hint {
1148  position: fixed;
1149  bottom: calc(24px + env(safe-area-inset-bottom, 0px));
1150  left: 50%;
1151  transform: translateX(-50%) translateY(20px);
1152  padding: 0.75rem 1.25rem;
1153  background: oklch(0.14 0.02 260 / 0.8);
1154  backdrop-filter: blur(12px);
1155  -webkit-backdrop-filter: blur(12px);
1156  border: 1px solid oklch(0.3 0.02 260 / 0.3);
1157  border-radius: var(--radius-xl);
1158  font-size: var(--text-xs);
1159  color: var(--secondary-foreground);
1160  opacity: 0;
1161  transition: all var(--transition-normal);
1162  z-index: 100;
1163  box-shadow: var(--shadow-xl);
1164  white-space: nowrap;
1165}
1166
1167.shortcuts-hint.visible {
1168  opacity: 1;
1169  transform: translateX(-50%) translateY(0);
1170}
1171
1172.shortcuts-hint kbd {
1173  display: inline-block;
1174  padding: 0.1875rem 0.5rem;
1175  margin: 0 0.1875rem;
1176  font-family: 'JetBrains Mono', ui-monospace, monospace;
1177  font-size: 0.6875rem;
1178  font-weight: 500;
1179  background: var(--secondary);
1180  border: 1px solid var(--border);
1181  border-radius: 5px;
1182  box-shadow: 0 2px 0 var(--background);
1183}
1184
1185/* ============================================
1186   Animations
1187   ============================================ */
1188
1189@keyframes fadeIn {
1190  from {
1191    opacity: 0;
1192    transform: translateY(12px);
1193  }
1194  to {
1195    opacity: 1;
1196    transform: translateY(0);
1197  }
1198}
1199
1200@keyframes slideUp {
1201  from {
1202    opacity: 0;
1203    transform: translateY(20px);
1204  }
1205  to {
1206    opacity: 1;
1207    transform: translateY(0);
1208  }
1209}
1210
1211/* Staggered fade-in animation - uses forwards to ensure visibility after animation */
1212.message {
1213  animation: fadeIn 0.35s cubic-bezier(0.33, 1, 0.68, 1) forwards;
1214  opacity: 1; /* Fallback for when animations don't run */
1215}
1216
1217/* Staggered animation delays for visual polish */
1218.message:nth-child(1) { animation-delay: 0.02s; }
1219.message:nth-child(2) { animation-delay: 0.04s; }
1220.message:nth-child(3) { animation-delay: 0.06s; }
1221.message:nth-child(4) { animation-delay: 0.08s; }
1222.message:nth-child(5) { animation-delay: 0.1s; }
1223.message:nth-child(n+6) { animation-delay: 0.12s; }
1224
1225/* ============================================
1226   Accessibility
1227   ============================================ */
1228
1229@media (prefers-reduced-motion: reduce) {
1230  *, *::before, *::after {
1231    animation-duration: 0.01ms !important;
1232    animation-delay: 0ms !important;
1233    transition-duration: 0.01ms !important;
1234    scroll-behavior: auto !important;
1235  }
1236  .message { animation: none; }
1237}
1238
1239:focus-visible {
1240  outline: 2px solid var(--primary);
1241  outline-offset: 2px;
1242}
1243
1244@media (prefers-contrast: high) {
1245  :root {
1246    --border: oklch(0.5 0.02 260);
1247    --muted-foreground: oklch(0.75 0.02 260);
1248  }
1249  .tool-badge {
1250    border-width: 2px;
1251  }
1252  .message {
1253    border-width: 2px;
1254  }
1255  .tool-popover {
1256    border-width: 2px;
1257  }
1258}
1259
1260/* ============================================
1261   MOBILE (< 768px)
1262   ============================================ */
1263
1264@media (max-width: 767px) {
1265  .app-container {
1266    padding: var(--space-3);
1267    padding-bottom: calc(80px + env(safe-area-inset-bottom, 0px));
1268  }
1269
1270  .header {
1271    padding: var(--space-3) var(--space-4);
1272    margin-bottom: var(--space-4);
1273  }
1274
1275  .header::before {
1276    width: 10px;
1277    height: 10px;
1278    top: var(--space-3);
1279    left: var(--space-4);
1280    box-shadow:
1281      16px 0 0 oklch(0.78 0.16 75),
1282      32px 0 0 oklch(0.72 0.19 145);
1283  }
1284
1285  .header-content {
1286    padding-left: 56px;
1287  }
1288
1289  .header-title {
1290    font-size: var(--text-base);
1291  }
1292
1293  .header-meta {
1294    gap: var(--space-1) var(--space-2);
1295    font-size: var(--text-xs);
1296  }
1297
1298  .toolbar {
1299    position: fixed;
1300    bottom: 0;
1301    left: 0;
1302    right: 0;
1303    top: auto;
1304    margin: 0;
1305    padding: var(--space-2);
1306    padding-bottom: calc(var(--space-2) + env(safe-area-inset-bottom, 0px));
1307    border-radius: var(--radius-xl) var(--radius-xl) 0 0;
1308    border-bottom: none;
1309    z-index: 100;
1310  }
1311
1312  .search-input {
1313    padding: 0.75rem;
1314    font-size: 1rem; /* Prevent zoom on iOS */
1315  }
1316
1317  .conversation {
1318    gap: var(--space-3);
1319  }
1320
1321  .message {
1322    padding: var(--space-3) var(--space-4);
1323    border-radius: var(--radius-lg);
1324  }
1325
1326  .message-header {
1327    gap: var(--space-1);
1328    margin-bottom: var(--space-2);
1329    padding-bottom: var(--space-1);
1330  }
1331
1332  .message-icon { font-size: 0.875rem; }
1333  .message-author { font-size: var(--text-xs); }
1334  .message-time { font-size: 0.625rem; }
1335
1336  .message-content {
1337    font-size: var(--text-sm);
1338    line-height: 1.6;
1339  }
1340
1341  .message-link {
1342    top: var(--space-3);
1343    right: var(--space-3);
1344    padding: 8px;
1345    opacity: 1; /* Always visible on mobile */
1346  }
1347
1348  .tool-call {
1349    margin-top: var(--space-3);
1350  }
1351
1352  .tool-call summary {
1353    padding: var(--space-2);
1354    min-height: 48px;
1355  }
1356
1357  .tool-call-body {
1358    padding: var(--space-3);
1359  }
1360
1361  .tool-call pre {
1362    font-size: 0.625rem;
1363    padding: var(--space-1) var(--space-2);
1364    max-height: 200px;
1365  }
1366
1367  .floating-nav {
1368    bottom: calc(80px + env(safe-area-inset-bottom, 0px));
1369    right: var(--space-3);
1370  }
1371
1372  .floating-btn {
1373    width: 44px;
1374    height: 44px;
1375  }
1376
1377  .shortcuts-hint {
1378    display: none;
1379  }
1380
1381  /* Larger tap targets */
1382  button, a, summary {
1383    min-height: var(--touch-min);
1384  }
1385
1386  /* Block-level code overflow */
1387  pre, code {
1388    max-width: 100%;
1389  }
1390
1391  /* Tool badges - larger touch targets on mobile */
1392  .tool-badge {
1393    min-width: 32px;
1394    height: 32px;
1395  }
1396
1397  .tool-badges {
1398    gap: 6px;
1399  }
1400
1401  /* Mobile popover - bottom sheet style */
1402  .tool-popover {
1403    position: fixed;
1404    bottom: 0;
1405    left: 0;
1406    right: 0;
1407    top: auto;
1408    max-width: 100%;
1409    max-height: 60vh;
1410    border-radius: var(--radius-xl) var(--radius-xl) 0 0;
1411    padding: var(--space-4);
1412    padding-bottom: calc(var(--space-4) + env(safe-area-inset-bottom, 0px));
1413    transform: translateY(100%);
1414  }
1415
1416  .tool-popover.visible {
1417    transform: translateY(0);
1418  }
1419
1420  /* Hide arrow on mobile */
1421  .tool-popover::before {
1422    display: none;
1423  }
1424
1425  /* Add drag handle indicator */
1426  .tool-popover::after {
1427    content: '';
1428    position: absolute;
1429    top: 8px;
1430    left: 50%;
1431    transform: translateX(-50%);
1432    width: 36px;
1433    height: 4px;
1434    background: oklch(0.4 0.02 260);
1435    border-radius: 2px;
1436  }
1437}
1438
1439/* ============================================
1440   TABLET (768px - 1023px)
1441   ============================================ */
1442
1443@media (min-width: 768px) and (max-width: 1023px) {
1444  .message {
1445    padding: var(--space-4) var(--space-5);
1446  }
1447}
1448
1449/* ============================================
1450   LARGE DESKTOP (1280px+)
1451   ============================================ */
1452
1453@media (min-width: 1280px) {
1454  .message {
1455    padding: var(--space-5) var(--space-6);
1456  }
1457
1458  .message-content {
1459    font-size: 1.0625rem;
1460    line-height: 1.75;
1461  }
1462
1463  .toolbar {
1464    padding: var(--space-4) var(--space-5);
1465  }
1466}
1467
1468/* ============================================
1469   Message Collapse
1470   ============================================ */
1471
1472.message-collapse summary {
1473  cursor: pointer;
1474  list-style: none;
1475}
1476
1477.message-collapse summary::-webkit-details-marker { display: none; }
1478
1479.message-preview {
1480  color: var(--secondary-foreground);
1481  display: -webkit-box;
1482  -webkit-line-clamp: 3;
1483  -webkit-box-orient: vertical;
1484  overflow: hidden;
1485}
1486
1487.message-expand-hint {
1488  display: block;
1489  margin-top: 6px;
1490  font-size: var(--text-xs);
1491  font-weight: 500;
1492  color: var(--primary);
1493}
1494
1495.message-collapse[open] .message-expand-hint { display: none; }
1496
1497.message-expanded { margin-top: var(--space-3); }
1498
1499/* ============================================
1500   Code Block Copy Button
1501   ============================================ */
1502
1503pre {
1504  position: relative;
1505}
1506
1507.copy-code-btn {
1508  position: absolute;
1509  top: 8px;
1510  right: 8px;
1511  padding: 4px;
1512  background: var(--card);
1513  border: 1px solid var(--border);
1514  border-radius: var(--radius-sm);
1515  color: var(--muted-foreground);
1516  cursor: pointer;
1517  opacity: 0;
1518  transition: opacity var(--transition-fast), color var(--transition-fast);
1519}
1520
1521pre:hover .copy-code-btn { opacity: 1; }
1522.copy-code-btn:hover { color: var(--primary); border-color: var(--primary); }
1523.copy-code-btn.copied { color: var(--green); border-color: var(--green); }
1524
1525/* ============================================
1526   Toast Notifications
1527   ============================================ */
1528
1529.toast {
1530  padding: 0.625rem 1rem;
1531  background: var(--card);
1532  border: 1px solid var(--border);
1533  border-radius: var(--radius-md);
1534  color: var(--foreground);
1535  box-shadow: var(--shadow-lg);
1536  font-size: var(--text-sm);
1537}
1538
1539.toast-success { border-color: var(--green); }
1540.toast-error { border-color: var(--red); }
1541
1542/* ============================================
1543   Agent-Specific Theming
1544   ============================================ */
1545
1546.agent-claude .message-assistant { border-left-color: oklch(0.7 0.18 50); }
1547.agent-codex .message-assistant { border-left-color: oklch(0.7 0.2 145); }
1548.agent-cursor .message-assistant { border-left-color: oklch(0.7 0.2 280); }
1549.agent-chatgpt .message-assistant { border-left-color: oklch(0.72 0.19 165); }
1550.agent-gemini .message-assistant { border-left-color: oklch(0.7 0.2 250); }
1551.agent-aider .message-assistant { border-left-color: oklch(0.72 0.16 85); }
1552.agent-copilot .message-assistant { border-left-color: oklch(0.7 0.18 200); }
1553.agent-cody .message-assistant { border-left-color: oklch(0.68 0.2 340); }
1554.agent-windsurf .message-assistant { border-left-color: oklch(0.7 0.2 205); }
1555.agent-amp .message-assistant { border-left-color: oklch(0.7 0.18 270); }
1556.agent-grok .message-assistant { border-left-color: oklch(0.7 0.22 350); }
1557.agent-hermes .message-assistant { border-left-color: oklch(0.78 0.16 95); }
1558
1559/* Print styles */
1560@media print {
1561  body::before { display: none; }
1562  .toolbar, .floating-nav, .scroll-progress { display: none !important; }
1563  .message {
1564    background: white;
1565    backdrop-filter: none;
1566    box-shadow: none;
1567    border: 1px solid #ccc;
1568    break-inside: avoid;
1569  }
1570  .message-link { display: none; }
1571  .copy-code-btn { display: none; }
1572  .tool-popover { display: none !important; }
1573  .tool-badge {
1574    border: 1px solid #666;
1575    background: #f5f5f5;
1576    color: #333;
1577  }
1578  .tool-badge-icon { color: #666; }
1579}
1580"#;
1581
1582const SEARCH_STYLES: &str = r#"
1583/* Search highlighting */
1584.search-highlight {
1585  background: oklch(0.75 0.18 195 / 0.3);
1586  border-radius: 2px;
1587  padding: 1px 0;
1588  box-shadow: 0 0 0 1px oklch(0.75 0.18 195 / 0.35);
1589}
1590
1591.search-current {
1592  background: oklch(0.78 0.16 75 / 0.5);
1593  box-shadow: 0 0 0 1px oklch(0.78 0.16 75 / 0.6);
1594}
1595"#;
1596
1597const ENCRYPTION_STYLES: &str = r#"
1598/* Encryption modal */
1599.decrypt-modal {
1600  position: fixed;
1601  inset: 0;
1602  z-index: 1000;
1603  display: flex;
1604  align-items: center;
1605  justify-content: center;
1606  background: oklch(0 0 0 / 0.85);
1607  backdrop-filter: blur(8px);
1608}
1609
1610.decrypt-form {
1611  width: 100%;
1612  max-width: 360px;
1613  padding: var(--space-6);
1614  background: var(--card);
1615  border: 1px solid var(--border);
1616  border-radius: var(--radius-xl);
1617  box-shadow: var(--shadow-xl);
1618}
1619
1620.decrypt-form h2 {
1621  margin: 0 0 var(--space-4);
1622  font-size: var(--text-lg);
1623  color: var(--foreground);
1624}
1625
1626.decrypt-form input {
1627  width: 100%;
1628  padding: 0.625rem 0.75rem;
1629  margin-bottom: var(--space-3);
1630  background: var(--input);
1631  border: 1px solid var(--border);
1632  border-radius: var(--radius-md);
1633  color: var(--foreground);
1634  font-size: var(--text-sm);
1635}
1636
1637.decrypt-form input:focus {
1638  outline: none;
1639  border-color: var(--primary);
1640  box-shadow: 0 0 0 3px oklch(0.75 0.18 195 / 0.15);
1641}
1642
1643.decrypt-form button {
1644  width: 100%;
1645  padding: 0.625rem;
1646  background: var(--primary);
1647  border: none;
1648  border-radius: var(--radius-md);
1649  color: var(--primary-foreground);
1650  font-size: var(--text-sm);
1651  font-weight: 600;
1652  cursor: pointer;
1653  transition: background var(--transition-fast);
1654}
1655
1656.decrypt-form button:hover {
1657  background: oklch(0.8 0.18 195);
1658}
1659
1660.decrypt-error {
1661  color: var(--red);
1662  font-size: var(--text-sm);
1663  margin-top: var(--space-2);
1664}
1665"#;
1666
1667fn generate_print_css() -> String {
1668    String::from(
1669        r#"@media print {
1670  body {
1671    font-size: 11pt;
1672    background: #fff;
1673    color: #000;
1674  }
1675  .message {
1676    border: 1px solid #ddd;
1677    page-break-inside: avoid;
1678  }
1679  pre {
1680    border: 1px solid #ddd;
1681    background: #f5f5f5;
1682  }
1683  a {
1684    color: #000;
1685    text-decoration: underline;
1686  }
1687}"#,
1688    )
1689}
1690
1691#[cfg(test)]
1692mod tests {
1693    use super::*;
1694
1695    #[test]
1696    fn test_generate_styles_includes_colors() {
1697        let opts = ExportOptions::default();
1698        let bundle = generate_styles(&opts);
1699        assert!(bundle.critical_css.contains("--background"));
1700        assert!(bundle.critical_css.contains("--foreground"));
1701    }
1702
1703    #[test]
1704    fn test_generate_styles_includes_search_when_enabled() {
1705        let opts = ExportOptions {
1706            include_search: true,
1707            ..Default::default()
1708        };
1709        let bundle = generate_styles(&opts);
1710        assert!(bundle.critical_css.contains(".search-highlight"));
1711    }
1712
1713    #[test]
1714    fn test_generate_styles_excludes_search_when_disabled() {
1715        let opts = ExportOptions {
1716            include_search: false,
1717            ..Default::default()
1718        };
1719        let bundle = generate_styles(&opts);
1720        assert!(!bundle.critical_css.contains(".search-highlight"));
1721    }
1722
1723    #[test]
1724    fn test_styles_include_theme_toggle_when_enabled() {
1725        let opts = ExportOptions {
1726            include_theme_toggle: true,
1727            ..Default::default()
1728        };
1729        let bundle = generate_styles(&opts);
1730        assert!(bundle.critical_css.contains("[data-theme=\"light\"]"));
1731    }
1732
1733    #[test]
1734    fn test_styles_include_responsive_breakpoints() {
1735        let opts = ExportOptions::default();
1736        let bundle = generate_styles(&opts);
1737        assert!(bundle.critical_css.contains("@media"));
1738    }
1739
1740    #[test]
1741    fn test_print_css_hides_interactive_elements() {
1742        let opts = ExportOptions::default();
1743        let bundle = generate_styles(&opts);
1744        assert!(bundle.print_css.contains("@media print"));
1745    }
1746
1747    #[test]
1748    fn test_styles_include_accessibility() {
1749        let opts = ExportOptions::default();
1750        let bundle = generate_styles(&opts);
1751        assert!(bundle.critical_css.contains("prefers-reduced-motion"));
1752    }
1753
1754    #[test]
1755    fn test_styles_include_animations() {
1756        let opts = ExportOptions::default();
1757        let bundle = generate_styles(&opts);
1758        assert!(bundle.critical_css.contains("@keyframes"));
1759    }
1760
1761    #[test]
1762    fn test_styles_include_glass_morphism() {
1763        let opts = ExportOptions::default();
1764        let bundle = generate_styles(&opts);
1765        assert!(bundle.critical_css.contains("backdrop-filter: blur"));
1766        assert!(bundle.critical_css.contains(".glass"));
1767    }
1768
1769    #[test]
1770    fn test_styles_include_oklch_colors() {
1771        let opts = ExportOptions::default();
1772        let bundle = generate_styles(&opts);
1773        assert!(bundle.critical_css.contains("oklch(0.11 0.015 260)"));
1774        assert!(bundle.critical_css.contains("oklch(0.75 0.18 195)"));
1775    }
1776
1777    #[test]
1778    fn test_styles_include_tool_badge_styling() {
1779        let opts = ExportOptions::default();
1780        let bundle = generate_styles(&opts);
1781
1782        // Tool badge base styles
1783        assert!(bundle.critical_css.contains(".tool-badge"));
1784        assert!(bundle.critical_css.contains("min-width: 24px"));
1785        assert!(bundle.critical_css.contains("height: 24px"));
1786
1787        // Status variants
1788        assert!(bundle.critical_css.contains(".tool-status-success"));
1789        assert!(bundle.critical_css.contains(".tool-status-error"));
1790
1791        // Overflow badge
1792        assert!(bundle.critical_css.contains(".tool-overflow"));
1793        assert!(
1794            bundle
1795                .critical_css
1796                .contains(".tool-badge.tool-overflow-extra")
1797        );
1798        assert!(
1799            bundle
1800                .critical_css
1801                .contains(".message-header-right.expanded .tool-overflow-extra")
1802        );
1803    }
1804
1805    #[test]
1806    fn test_styles_include_glassmorphism_popover() {
1807        let opts = ExportOptions::default();
1808        let bundle = generate_styles(&opts);
1809
1810        // Glassmorphic popover
1811        assert!(bundle.critical_css.contains(".tool-popover"));
1812        assert!(bundle.critical_css.contains("backdrop-filter: blur(16px)"));
1813        assert!(
1814            bundle
1815                .critical_css
1816                .contains("-webkit-backdrop-filter: blur(16px)")
1817        );
1818
1819        // Fixed positioning
1820        assert!(bundle.critical_css.contains("position: fixed"));
1821
1822        // Visibility states
1823        assert!(bundle.critical_css.contains(".tool-popover.visible"));
1824    }
1825
1826    #[test]
1827    fn test_styles_include_mobile_bottom_sheet() {
1828        let opts = ExportOptions::default();
1829        let bundle = generate_styles(&opts);
1830
1831        // Mobile popover becomes bottom sheet
1832        assert!(bundle.critical_css.contains("max-height: 60vh"));
1833        assert!(
1834            bundle
1835                .critical_css
1836                .contains("border-radius: var(--radius-xl) var(--radius-xl) 0 0")
1837        );
1838    }
1839
1840    #[test]
1841    fn test_styles_include_high_contrast() {
1842        let opts = ExportOptions::default();
1843        let bundle = generate_styles(&opts);
1844
1845        // High contrast mode
1846        assert!(bundle.critical_css.contains("prefers-contrast: high"));
1847        assert!(bundle.critical_css.contains("border-width: 2px"));
1848    }
1849
1850    #[test]
1851    fn test_styles_include_glow_effects() {
1852        let opts = ExportOptions::default();
1853        let bundle = generate_styles(&opts);
1854
1855        // Glow shadow variables
1856        assert!(bundle.critical_css.contains("--shadow-glow"));
1857        assert!(bundle.critical_css.contains("--shadow-glow-amber"));
1858
1859        // Hover glow on tool badges
1860        assert!(
1861            bundle
1862                .critical_css
1863                .contains("box-shadow: var(--shadow-glow-amber)")
1864        );
1865    }
1866
1867    #[test]
1868    fn test_print_styles_hide_popovers() {
1869        let opts = ExportOptions::default();
1870        let bundle = generate_styles(&opts);
1871
1872        // Print mode hides popovers
1873        assert!(
1874            bundle
1875                .critical_css
1876                .contains(".tool-popover { display: none")
1877        );
1878    }
1879}