using Astralis; using Invercargill; using Invercargill.DataStructures; using Inversion; using Spry; /** * DemoPage - Interactive demo page * * Features a live Aurora Borealis simulation and counter that use * Server-Sent Events (SSE) for real-time updates. * * Updates are pushed when users click action buttons - no polling! */ public class DemoPage : PageComponent { public const string ROUTE = "/demo"; private AuroraState aurora_state = inject(); private ComponentFactory factory = inject(); public override string markup { get { return """

Interactive Demo

Experience the power of Spry with real-time Server-Sent Events

🌌 Live Aurora Borealis

This aurora uses SSE for real-time updates! Click a button to update all connected clients.

Solar Wind
Waves
Intensity
Color Mode

Updates are pushed via SSE when anyone clicks the buttons. Try it from multiple browsers!

🔢 Shared Counter

A counter that syncs across all connected clients using SSE.

Current Count

âš™ How It Works

This demo showcases Server-Sent Events (SSE) for real-time updates:

📡

Server-Sent Events

SSE pushes updates to all connected clients instantly when actions occur.

🔗

Shared State

AuroraState is a singleton - all visitors see the same aurora and counter!

âš¡

Event-Driven

No polling! Updates only happen when someone clicks a button.

HTML
<!-- SSE connection on parent -->
<div hx-ext="sse" sse-connect="/sse/aurora">
    <!-- Auto-updates when "aurora-canvas" event received -->
    <div sse-swap="aurora-canvas">
        Aurora waves render here
    </div>
</div>

<!-- Button triggers action endpoint -->
<button hx-post="/aurora/action/boost" hx-swap="none">
    Boost Solar Wind
</button>

Want to Learn More?

Explore the features that make Spry powerful.

"""; }} public override async void prepare() throws Error { // Render initial aurora waves var waves = new Series(); int wave_num = 0; foreach (var wave in aurora_state.get_waves()) { var component = factory.create(); component.wave_id = wave_num; component.y_offset = wave.y_offset; component.amplitude = wave.amplitude; component.frequency = wave.frequency; component.color1 = wave.color1; component.color2 = wave.color2; component.opacity = wave.opacity; component.animation_delay = wave.animation_delay; waves.add(component); wave_num++; } set_outlet_children("aurora-waves", waves); // Update stats this["solar-wind"].text_content = "%.1f km/s".printf(aurora_state.solar_wind_speed); this["wave-count"].text_content = aurora_state.wave_count.to_string(); this["intensity"].text_content = "%.0f%%".printf(aurora_state.intensity * 100); this["color-mode"].text_content = aurora_state.color_mode; // Update counter (initial value - will be updated via SSE) // Note: We need to set the stat-value div's content this["counter-value"].text_content = aurora_state.counter.to_string(); } } /** * AuroraWaveData - Data for a single aurora wave */ public class AuroraWaveData : Object { public double y_offset { get; set; } public double amplitude { get; set; } public double frequency { get; set; } public string color1 { get; set; default = "#7c3aed"; } public string color2 { get; set; default = "#22c55e"; } public double opacity { get; set; default = 0.6; } public double animation_delay { get; set; default = 0; } } /** * ColorPalette - A pair of colors for aurora waves */ public class ColorPalette : Object { public string color1 { get; construct set; } public string color2 { get; construct set; } public string name { get; construct set; } public ColorPalette(string color1, string color2, string name) { Object(color1: color1, color2: color2, name: name); } } /** * AuroraState - Singleton state for the aurora simulation */ public class AuroraState : Object { private GenericArray waves = new GenericArray(); private Series color_palettes; public double solar_wind_speed { get; set; default = 400.0; } public double intensity { get; set; default = 0.5; } public string color_mode { get; set; default = "Green"; } public int counter { get; set; default = 0; } private int current_palette = 0; public int wave_count { get { return (int)waves.length; } } construct { // Initialize color palettes using Series color_palettes = new Series(); color_palettes.add(new ColorPalette("#22c55e", "#16a34a", "Green")); color_palettes.add(new ColorPalette("#7c3aed", "#a855f7", "Purple")); color_palettes.add(new ColorPalette("#2563eb", "#3b82f6", "Blue")); color_palettes.add(new ColorPalette("#22c55e", "#7c3aed", "Mixed")); color_palettes.add(new ColorPalette("#f59e0b", "#ef4444", "Fire")); color_palettes.add(new ColorPalette("#06b6d4", "#22c55e", "Ocean")); // Start with some waves add_wave(); add_wave(); add_wave(); } private ColorPalette get_current_palette() { uint idx = (uint)current_palette; return color_palettes.element_at_or_default(idx); } public void add_wave() { var palette = get_current_palette(); var wave = new AuroraWaveData() { y_offset = 20 + Random.double_range(0, 40), amplitude = 10 + Random.double_range(0, 20), frequency = 0.5 + Random.double_range(0, 1.5), color1 = palette.color1, color2 = palette.color2, opacity = 0.3 + Random.double_range(0, 0.5), animation_delay = Random.double_range(0, 3) }; waves.add(wave); // Keep max 8 waves if (waves.length > 8) { waves.remove_index(0); } } public void boost_solar_wind() { solar_wind_speed = 600 + Random.double_range(0, 400); intensity = 0.8 + Random.double_range(0, 0.2); // Increase all wave amplitudes for (int i = 0; i < waves.length; i++) { var w = waves.get(i); w.amplitude *= 1.3; w.opacity = double.min(0.9, w.opacity + 0.1); } } public void calm() { solar_wind_speed = 300 + Random.double_range(0, 100); intensity = 0.3 + Random.double_range(0, 0.2); // Decrease all wave amplitudes for (int i = 0; i < waves.length; i++) { var w = waves.get(i); w.amplitude *= 0.7; w.opacity = double.max(0.2, w.opacity - 0.1); } } public void shift_colors() { current_palette = (current_palette + 1) % 6; var palette = get_current_palette(); // Update color mode name color_mode = palette.name; // Update all wave colors for (int i = 0; i < waves.length; i++) { var w = waves.get(i); w.color1 = palette.color1; w.color2 = palette.color2; } } public void evolve() { // Called periodically to naturally evolve the aurora solar_wind_speed = 300 + Random.double_range(0, 300); intensity = 0.4 + Random.double_range(0, 0.4); // Slightly modify waves for (int i = 0; i < waves.length; i++) { var w = waves.get(i); w.y_offset += Random.double_range(-5, 5); w.y_offset = double.max(10, double.min(70, w.y_offset)); w.amplitude += Random.double_range(-3, 3); w.amplitude = double.max(5, double.min(40, w.amplitude)); } } public GenericArray get_waves() { return waves; } }