logicaffeine_web/ui/components/
achievement_toast.rs1use dioxus::prelude::*;
12use crate::achievements::Achievement;
13use crate::audio::{SoundEffect, play_sound};
14use crate::ui::components::icon::{Icon, IconVariant, IconSize};
15
16const ACHIEVEMENT_STYLE: &str = r#"
17.achievement-overlay {
18 position: fixed;
19 top: 0;
20 left: 0;
21 right: 0;
22 bottom: 0;
23 background: rgba(0, 0, 0, 0.8);
24 display: flex;
25 align-items: center;
26 justify-content: center;
27 z-index: 2000;
28 animation: overlay-fade 0.3s ease-out;
29}
30
31@keyframes overlay-fade {
32 from { opacity: 0; }
33 to { opacity: 1; }
34}
35
36.achievement-card {
37 background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
38 border: 2px solid #fbbf24;
39 border-radius: 20px;
40 padding: 40px 60px;
41 text-align: center;
42 box-shadow: 0 0 60px rgba(251, 191, 36, 0.4);
43 animation: card-appear 0.5s ease-out;
44}
45
46@keyframes card-appear {
47 0% { transform: scale(0.5) translateY(50px); opacity: 0; }
48 70% { transform: scale(1.05) translateY(0); }
49 100% { transform: scale(1) translateY(0); opacity: 1; }
50}
51
52.achievement-icon {
53 font-size: 64px;
54 margin-bottom: 16px;
55 animation: icon-bounce 0.5s ease-out 0.3s both;
56}
57
58@keyframes icon-bounce {
59 0% { transform: scale(0); }
60 50% { transform: scale(1.3); }
61 100% { transform: scale(1); }
62}
63
64.achievement-label {
65 font-size: 14px;
66 color: #fbbf24;
67 text-transform: uppercase;
68 letter-spacing: 2px;
69 margin-bottom: 8px;
70}
71
72.achievement-title {
73 font-size: 32px;
74 font-weight: 700;
75 color: #fff;
76 margin-bottom: 12px;
77}
78
79.achievement-description {
80 font-size: 16px;
81 color: #888;
82 margin-bottom: 24px;
83}
84
85.achievement-reward {
86 font-size: 24px;
87 color: #4ade80;
88 font-weight: 600;
89 margin-bottom: 16px;
90}
91
92.achievement-title-unlock {
93 background: linear-gradient(90deg, #fbbf24, #f59e0b);
94 -webkit-background-clip: text;
95 -webkit-text-fill-color: transparent;
96 background-clip: text;
97 font-size: 18px;
98 font-weight: 600;
99 margin-bottom: 24px;
100}
101
102.achievement-dismiss {
103 padding: 12px 32px;
104 background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
105 border: none;
106 border-radius: 8px;
107 color: white;
108 font-size: 16px;
109 font-weight: 600;
110 cursor: pointer;
111 transition: transform 0.2s ease;
112}
113
114.achievement-dismiss:hover {
115 transform: scale(1.05);
116}
117
118.particles {
119 position: absolute;
120 top: 0;
121 left: 0;
122 right: 0;
123 bottom: 0;
124 pointer-events: none;
125 overflow: hidden;
126}
127
128.particle {
129 position: absolute;
130 font-size: 24px;
131 animation: particle-fall 2s ease-out forwards;
132}
133
134@keyframes particle-fall {
135 0% { transform: translateY(-50px) rotate(0deg); opacity: 1; }
136 100% { transform: translateY(100vh) rotate(720deg); opacity: 0; }
137}
138"#;
139
140#[component]
141pub fn AchievementToast(achievement: &'static Achievement, on_dismiss: EventHandler<()>) -> Element {
142 use_effect(move || {
143 play_sound(SoundEffect::Achievement);
144 });
145
146 rsx! {
147 style { "{ACHIEVEMENT_STYLE}" }
148 div {
149 class: "achievement-overlay",
150 onclick: move |_| on_dismiss.call(()),
151 div { class: "particles",
152 for i in 0..20 {
153 span {
154 class: "particle",
155 style: "left: {(i * 5) % 100}%; animation-delay: {i as f32 * 0.1}s;",
156 if i % 2 == 0 {
157 Icon { variant: IconVariant::Star, size: IconSize::Large, color: "#fbbf24" }
158 } else {
159 Icon { variant: IconVariant::Sparkles, size: IconSize::Large, color: "#a78bfa" }
160 }
161 }
162 }
163 }
164 div { class: "achievement-card",
165 div { class: "achievement-icon",
166 Icon { variant: IconVariant::Trophy, size: IconSize::XXLarge, color: "#fbbf24" }
167 }
168 div { class: "achievement-label", "Achievement Unlocked" }
169 div { class: "achievement-title", "{achievement.title}" }
170 div { class: "achievement-description", "{achievement.description}" }
171 div { class: "achievement-reward", "+{achievement.xp_reward} XP" }
172 if let Some(title) = achievement.unlocks_title {
173 div { class: "achievement-title-unlock",
174 "Title Unlocked: {title}"
175 }
176 }
177 if achievement.grants_freeze {
178 div { class: "achievement-title-unlock",
179 style: "display: flex; align-items: center; gap: 8px; justify-content: center;",
180 Icon { variant: IconVariant::Shield, size: IconSize::Medium, color: "#38bdf8" }
181 "+1 Streak Freeze!"
182 }
183 }
184 button {
185 class: "achievement-dismiss",
186 onclick: move |e| {
187 e.stop_propagation();
188 on_dismiss.call(());
189 },
190 "Continue"
191 }
192 }
193 }
194 }
195}