using Astralis; using Invercargill; using Invercargill.DataStructures; using Inversion; using Spry; /** * DemoPage - Interactive demo page * * Features a live Aurora Borealis simulation that polls the server * every 5 seconds for new wave/color parameters */ 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 a live, server-backed Aurora simulation

🌌 Live Aurora Borealis

This aurora is generated server-side! It polls for new parameters every 5 seconds.

Solar Wind
Waves
Intensity
Color Mode

The aurora updates every 5 seconds via HTMX polling. Try the controls above!

🔢 Simple Counter

A minimal example of Spry component state and actions.

Current Count

âš™ How It Works

This demo showcases several Spry features working together:

🔄

Server Polling

hx-trigger="every 5s" fetches fresh aurora data from the server periodically.

âš¡

Shared State

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

🎨

CSS Gradients

Server generates CSS gradient strips for beautiful aurora waves.

HTML
<!-- Auto-refreshing aurora canvas -->
<div hx-get="/demo/aurora" 
     hx-trigger="every 5s" 
     hx-swap="innerHTML">
    <!-- Aurora waves render here -->
</div>

<!-- Control buttons -->
<button spry-action=":BoostSolarWind"
        spry-target="demo-page"
        hx-swap="outerHTML">
    Boost Solar Wind
</button>

Want to Learn More?

Explore the features that make Spry powerful.

"""; }} public override async void prepare() throws Error { // Render 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 this["counter-value"].text_content = aurora_state.counter.to_string(); } public async override void handle_action(string action) throws Error { switch (action) { case "BoostSolarWind": aurora_state.boost_solar_wind(); break; case "CalmAurora": aurora_state.calm(); break; case "ShiftColors": aurora_state.shift_colors(); break; case "AddWave": aurora_state.add_wave(); break; case "Increment": aurora_state.counter++; break; case "Decrement": aurora_state.counter--; break; case "Reset": aurora_state.counter = 0; break; } } } /** * 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; } }