logicaffeine_web/ui/components/
streak_display.rs1use dioxus::prelude::*;
13use crate::game::StreakStatus;
14use crate::ui::components::icon::{Icon, IconVariant, IconSize};
15
16const STREAK_STYLE: &str = r#"
17.streak-display {
18 display: flex;
19 align-items: center;
20 gap: 8px;
21 padding: 6px 12px;
22 border-radius: 20px;
23 font-size: 14px;
24 font-weight: 500;
25}
26
27.streak-active {
28 background: rgba(249, 115, 22, 0.15);
29 border: 1px solid rgba(249, 115, 22, 0.3);
30 color: #f97316;
31}
32
33.streak-at-risk {
34 background: rgba(248, 113, 113, 0.15);
35 border: 1px solid rgba(248, 113, 113, 0.3);
36 color: #f87171;
37 animation: risk-pulse 1s ease-in-out infinite;
38}
39
40@keyframes risk-pulse {
41 0%, 100% { opacity: 1; }
42 50% { opacity: 0.7; }
43}
44
45.streak-frozen {
46 background: rgba(56, 189, 248, 0.15);
47 border: 1px solid rgba(56, 189, 248, 0.3);
48 color: #38bdf8;
49}
50
51.streak-lost {
52 background: rgba(107, 114, 128, 0.15);
53 border: 1px solid rgba(107, 114, 128, 0.3);
54 color: #9ca3af;
55}
56
57.streak-icon {
58 font-size: 16px;
59}
60
61.streak-count {
62 font-weight: 600;
63}
64
65.streak-label {
66 color: #888;
67 font-size: 12px;
68}
69
70.freeze-tokens {
71 display: flex;
72 gap: 4px;
73 margin-left: 8px;
74 padding-left: 8px;
75 border-left: 1px solid rgba(255, 255, 255, 0.1);
76}
77
78.freeze-token {
79 font-size: 12px;
80 opacity: 0.8;
81}
82
83.freeze-token.empty {
84 opacity: 0.3;
85}
86"#;
87
88#[component]
89pub fn StreakDisplay(streak: u32, status: StreakStatus, freezes: u8) -> Element {
90 let (class, icon_variant, icon_color, text) = match status {
91 StreakStatus::Active { days } => {
92 ("streak-display streak-active", IconVariant::Fire, "#f97316", format!("{} day streak", days))
93 }
94 StreakStatus::AtRisk => {
95 ("streak-display streak-at-risk", IconVariant::Warning, "#f87171", "Streak at risk!".to_string())
96 }
97 StreakStatus::Frozen => {
98 ("streak-display streak-frozen", IconVariant::Shield, "#38bdf8", format!("{} days (frozen)", streak))
99 }
100 StreakStatus::Lost { was } => {
101 ("streak-display streak-lost", IconVariant::HeartBroken, "#9ca3af", format!("Lost {} day streak", was))
102 }
103 };
104
105 rsx! {
106 style { "{STREAK_STYLE}" }
107 div { class: "{class}",
108 span { class: "streak-icon",
109 Icon { variant: icon_variant, size: IconSize::Medium, color: icon_color }
110 }
111 span { class: "streak-count", "{text}" }
112 div { class: "freeze-tokens",
113 for i in 0..3u8 {
114 span {
115 class: if i < freezes { "freeze-token" } else { "freeze-token empty" },
116 Icon { variant: IconVariant::Shield, size: IconSize::Small, color: "#38bdf8" }
117 }
118 }
119 }
120 }
121 }
122}