synpad 0.1.0

A full-featured Matrix chat client built with Dioxus
use dioxus::prelude::*;

/// Message effect type (triggered by /confetti, /fireworks, etc.)
#[derive(Clone, Debug, PartialEq)]
pub enum EffectType {
    Confetti,
    Fireworks,
    Snow,
    Hearts,
    Rainbow,
    Rainfall,
    SpaceInvaders,
}

/// Message effects overlay.
/// Shows animated effects that overlay the chat when triggered.
#[component]
pub fn MessageEffectsOverlay(effect: Option<EffectType>, on_complete: EventHandler<()>) -> Element {
    let mut visible = use_signal(|| false);
    let mut effect_sig = use_signal(|| effect.clone());

    // Update signal when prop changes
    if *effect_sig.read() != effect {
        effect_sig.set(effect.clone());
        if effect.is_some() {
            visible.set(true);
            spawn(async move {
                tokio::time::sleep(std::time::Duration::from_secs(3)).await;
                visible.set(false);
                on_complete.call(());
            });
        }
    }

    if !*visible.read() || effect.is_none() {
        return rsx! {};
    }

    let effect = effect.unwrap();

    let (class, particles) = match effect {
        EffectType::Confetti => ("message-effects message-effects--confetti", generate_confetti()),
        EffectType::Fireworks => ("message-effects message-effects--fireworks", generate_fireworks()),
        EffectType::Snow => ("message-effects message-effects--snow", generate_snow()),
        EffectType::Hearts => ("message-effects message-effects--hearts", generate_hearts()),
        EffectType::Rainbow => ("message-effects message-effects--rainbow", vec![]),
        EffectType::Rainfall => ("message-effects message-effects--rainfall", generate_rainfall()),
        EffectType::SpaceInvaders => ("message-effects message-effects--spaceinvaders", generate_space_invaders()),
    };

    rsx! {
        div {
            class: class,
            onclick: move |_| {
                visible.set(false);
                on_complete.call(());
            },
            for particle in particles.iter() {
                {
                    let p = particle.clone();
                    rsx! {
                        span {
                            class: "message-effects__particle",
                            style: "left: {p.x}%; animation-delay: {p.delay}s; font-size: {p.size}px;",
                            "{p.char}"
                        }
                    }
                }
            }
        }
    }
}

#[derive(Clone)]
struct Particle {
    char: String,
    x: f32,
    delay: f32,
    size: u32,
}

fn generate_confetti() -> Vec<Particle> {
    let colors = ["🟥", "🟧", "🟨", "🟩", "🟦", "🟪", ""];
    (0..30)
        .map(|i| Particle {
            char: colors[i % colors.len()].to_string(),
            x: (i as f32 * 3.33) % 100.0,
            delay: (i as f32 * 0.1) % 2.0,
            size: 16 + (i % 3) as u32 * 4,
        })
        .collect()
}

fn generate_fireworks() -> Vec<Particle> {
    let sparks = ["", "💥", "🎆", "", "🌟"];
    (0..20)
        .map(|i| Particle {
            char: sparks[i % sparks.len()].to_string(),
            x: (i as f32 * 5.0) % 100.0,
            delay: (i as f32 * 0.15) % 2.0,
            size: 20 + (i % 4) as u32 * 6,
        })
        .collect()
}

fn generate_snow() -> Vec<Particle> {
    (0..40)
        .map(|i| Particle {
            char: if i % 3 == 0 { "" } else { "" }.to_string(),
            x: (i as f32 * 2.5) % 100.0,
            delay: (i as f32 * 0.2) % 3.0,
            size: 14 + (i % 4) as u32 * 4,
        })
        .collect()
}

fn generate_hearts() -> Vec<Particle> {
    let hearts = ["❤️", "💕", "💖", "💗", "💓"];
    (0..25)
        .map(|i| Particle {
            char: hearts[i % hearts.len()].to_string(),
            x: (i as f32 * 4.0) % 100.0,
            delay: (i as f32 * 0.12) % 2.0,
            size: 18 + (i % 3) as u32 * 6,
        })
        .collect()
}

fn generate_rainfall() -> Vec<Particle> {
    let drops = ["💧", "🌧", "💦"];
    (0..35)
        .map(|i| Particle {
            char: drops[i % drops.len()].to_string(),
            x: (i as f32 * 2.86) % 100.0,
            delay: (i as f32 * 0.08) % 2.5,
            size: 14 + (i % 3) as u32 * 4,
        })
        .collect()
}

fn generate_space_invaders() -> Vec<Particle> {
    let aliens = ["👾", "🛸", "🚀", "", "💫"];
    (0..25)
        .map(|i| Particle {
            char: aliens[i % aliens.len()].to_string(),
            x: (i as f32 * 4.0) % 100.0,
            delay: (i as f32 * 0.12) % 2.0,
            size: 18 + (i % 4) as u32 * 5,
        })
        .collect()
}