logicaffeine_web/ui/components/
combo_indicator.rs1use dioxus::prelude::*;
13use crate::ui::components::icon::{Icon, IconVariant, IconSize};
14
15const COMBO_STYLE: &str = r#"
16.combo-record {
17 position: absolute;
18 top: -24px;
19 left: 50%;
20 transform: translateX(-50%);
21 font-size: 12px;
22 color: #fbbf24;
23 font-weight: 600;
24 animation: record-flash 1.5s ease-out forwards;
25 white-space: nowrap;
26 pointer-events: none;
27}
28
29@keyframes record-flash {
30 0% { opacity: 0; transform: translateX(-50%) translateY(10px) scale(0.8); }
31 20% { opacity: 1; transform: translateX(-50%) translateY(0) scale(1.1); }
32 40% { opacity: 1; transform: translateX(-50%) translateY(0) scale(1); }
33 100% { opacity: 0; transform: translateX(-50%) translateY(-10px) scale(0.9); }
34}
35.combo-indicator {
36 display: flex;
37 align-items: center;
38 gap: 8px;
39 padding: 8px 16px;
40 background: rgba(249, 115, 22, 0.15);
41 border: 1px solid rgba(249, 115, 22, 0.3);
42 border-radius: 24px;
43 animation: combo-pulse 0.3s ease-out;
44}
45
46@keyframes combo-pulse {
47 0% { transform: scale(1); }
48 50% { transform: scale(1.1); }
49 100% { transform: scale(1); }
50}
51
52.combo-flames {
53 display: flex;
54 gap: 2px;
55}
56
57.flame {
58 font-size: 16px;
59 animation: flame-dance 0.5s ease-in-out infinite alternate;
60}
61
62.flame:nth-child(2) { animation-delay: 0.1s; }
63.flame:nth-child(3) { animation-delay: 0.2s; }
64
65@keyframes flame-dance {
66 from { transform: translateY(0) scale(1); }
67 to { transform: translateY(-2px) scale(1.1); }
68}
69
70.combo-count {
71 font-size: 20px;
72 font-weight: 700;
73 color: #f97316;
74}
75
76.combo-multiplier {
77 font-size: 14px;
78 color: #fb923c;
79 font-weight: 500;
80}
81
82.combo-wrapper {
83 position: relative;
84 display: inline-flex;
85}
86"#;
87
88#[component]
89pub fn ComboIndicator(combo: u32, multiplier: f64, is_new_record: bool) -> Element {
90 let mut show_record = use_signal(|| false);
91 let mut last_record_combo = use_signal(|| 0u32);
92
93 if is_new_record && combo > last_record_combo() {
94 show_record.set(true);
95 last_record_combo.set(combo);
96
97 spawn(async move {
98 gloo_timers::future::TimeoutFuture::new(1500).await;
99 show_record.set(false);
100 });
101 }
102
103 if combo == 0 {
104 return rsx! {};
105 }
106
107 let flame_count = (combo.min(5)) as usize;
108
109 rsx! {
110 style { "{COMBO_STYLE}" }
111 div { class: "combo-wrapper",
112 if show_record() {
113 div { class: "combo-record", "NEW RECORD!" }
114 }
115 div { class: "combo-indicator",
116 div { class: "combo-flames",
117 for _ in 0..flame_count {
118 span { class: "flame",
119 Icon { variant: IconVariant::Fire, size: IconSize::Medium, color: "#f97316" }
120 }
121 }
122 }
123 span { class: "combo-count", "{combo}x" }
124 span { class: "combo-multiplier", "({multiplier:.1}x)" }
125 }
126 }
127 }
128}