Billy Barrow 1 долоо хоног өмнө
parent
commit
86bd26fd7b

+ 1 - 0
meson.build

@@ -18,3 +18,4 @@ add_project_arguments(['--vapidir', join_paths(meson.current_source_dir(), 'vapi
 
 subdir('src')
 subdir('examples')
+subdir('website')

+ 33 - 0
website/Components/AuroraWaveComponent.vala

@@ -0,0 +1,33 @@
+using Astralis;
+using Inversion;
+using Spry;
+
+/**
+ * AuroraWaveComponent - A single aurora wave strip
+ * 
+ * Renders as a CSS gradient strip with wave animation.
+ * Multiple waves layered create the aurora borealis effect.
+ */
+public class AuroraWaveComponent : Component {
+    
+    public int wave_id { get; set; }
+    public double y_offset { get; set; }
+    public double amplitude { get; set; }
+    public double frequency { get; set; }
+    public string color1 { get; set; default = "#22c55e"; }
+    public string color2 { get; set; default = "#7c3aed"; }
+    public double opacity { get; set; default = 0.6; }
+    public double animation_delay { get; set; default = 0; }
+    
+    public override string markup { get {
+        return """
+        <div sid="wave" class="aurora-wave"></div>
+        """;
+    }}
+    
+    public override async void prepare() throws Error {
+        // Build CSS custom properties as the style attribute
+        var style = @"--wave-y: $(y_offset)%%; --wave-amplitude: $(amplitude)px; --wave-freq: $(frequency); --wave-color1: $(color1); --wave-color2: $(color2); --wave-opacity: $(opacity); --wave-delay: $(animation_delay)s; animation-delay: var(--wave-delay);";
+        this["wave"].set_attribute("style", style);
+    }
+}

+ 33 - 0
website/Components/CodeBlockComponent.vala

@@ -0,0 +1,33 @@
+using Spry;
+
+/**
+ * CodeBlockComponent - A styled code block with syntax highlighting
+ * 
+ * Displays code with a header showing language and window dots
+ */
+public class CodeBlockComponent : Component {
+    
+    public string language { set; get; default = "Vala"; }
+    public string code { set; get; default = ""; }
+    
+    public override string markup { get {
+        return """
+        <div class="code-block">
+            <div class="code-header">
+                <div class="code-dots">
+                    <div class="code-dot red"></div>
+                    <div class="code-dot yellow"></div>
+                    <div class="code-dot green"></div>
+                </div>
+                <span class="code-lang" sid="language"></span>
+            </div>
+            <pre><code sid="code"></code></pre>
+        </div>
+        """;
+    }}
+    
+    public override async void prepare() throws Error {
+        this["language"].text_content = language;
+        this["code"].text_content = code;
+    }
+}

+ 31 - 0
website/Components/FeatureCardComponent.vala

@@ -0,0 +1,31 @@
+using Spry;
+
+/**
+ * FeatureCardComponent - A reusable feature card
+ * 
+ * Displays an icon, title, and description in a styled card
+ */
+public class FeatureCardComponent : Component {
+    
+    public string icon { set; get; default = "purple"; }
+    public string icon_emoji { set; get; default = "⭐"; }
+    public string title { set; get; default = "Feature"; }
+    public string description { set; get; default = "Description"; }
+    
+    public override string markup { get {
+        return """
+        <div class="feature-card">
+            <div class="feature-icon" sid="icon"></div>
+            <h3 sid="title"></h3>
+            <p sid="description"></p>
+        </div>
+        """;
+    }}
+    
+    public override async void prepare() throws Error {
+        this["icon"].text_content = icon_emoji;
+        this["icon"].set_attribute("class", @"feature-icon $icon");
+        this["title"].text_content = title;
+        this["description"].text_content = description;
+    }
+}

+ 25 - 0
website/Components/ParticleCanvasComponent.vala

@@ -0,0 +1,25 @@
+using Spry;
+
+/**
+ * ParticleCanvasComponent - A single particle in the simulation
+ * 
+ * Renders as a colored circle at a specific position
+ */
+public class ParticleCanvasComponent : Component {
+    
+    public double x { set; get; default = 50; }
+    public double y { set; get; default = 50; }
+    public string color { set; get; default = "#7c3aed"; }
+    public int size { set; get; default = 12; }
+    
+    public override string markup { get {
+        return """
+        <div sid="particle" style="position: absolute; border-radius: 50%; box-shadow: 0 0 10px currentColor;"></div>
+        """;
+    }}
+    
+    public override async void prepare() throws Error {
+        var style = @"position: absolute; left: $(x)%; top: $(y)%; width: $(size)px; height: $(size)px; background: $(color); border-radius: 50%; box-shadow: 0 0 $(size / 2)px $(color); transform: translate(-50%, -50%); transition: all 0.1s ease-out;";
+        this["particle"].set_attribute("style", style);
+    }
+}

+ 26 - 0
website/Components/StatCardComponent.vala

@@ -0,0 +1,26 @@
+using Spry;
+
+/**
+ * StatCardComponent - A statistics display card
+ * 
+ * Shows a large value with a label underneath
+ */
+public class StatCardComponent : Component {
+    
+    public string value { set; get; default = "0"; }
+    public string label { set; get; default = "Stat"; }
+    
+    public override string markup { get {
+        return """
+        <div class="stat-card">
+            <div class="stat-value" sid="value"></div>
+            <div class="stat-label" sid="label"></div>
+        </div>
+        """;
+    }}
+    
+    public override async void prepare() throws Error {
+        this["value"].text_content = value;
+        this["label"].text_content = label;
+    }
+}

+ 48 - 0
website/Endpoints/AuroraCanvasEndpoint.vala

@@ -0,0 +1,48 @@
+using Astralis;
+using Invercargill;
+using Invercargill.DataStructures;
+using Inversion;
+using Spry;
+
+/**
+ * AuroraCanvasEndpoint - Returns just the aurora waves for HTMX polling
+ * 
+ * This endpoint is polled every 5 seconds by the demo page.
+ * It evolves the aurora state and returns fresh wave HTML.
+ */
+public class AuroraCanvasEndpoint : Component {
+    
+    private AuroraState aurora_state = inject<AuroraState>();
+    private ComponentFactory factory = inject<ComponentFactory>();
+    
+    public override string markup { get {
+        return """
+        <spry-outlet sid="waves"/>
+        """;
+    }}
+    
+    public override async void prepare() throws Error {
+        // Create wave components
+        var waves = new Series<Renderable>();
+        foreach (var wave in aurora_state.get_waves()) {
+            var component = factory.create<AuroraWaveComponent>();
+            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);
+        }
+        set_outlet_children("waves", waves);
+    }
+    
+    public async override void handle_action(string action) throws Error {
+        if (action == "Poll") {
+            // Evolve the aurora naturally when polled
+            aurora_state.evolve();
+            // prepare() will be called automatically to render the waves
+        }
+    }
+}

+ 54 - 0
website/Endpoints/AuroraStatsEndpoint.vala

@@ -0,0 +1,54 @@
+using Astralis;
+using Inversion;
+using Spry;
+
+/**
+ * AuroraStatsEndpoint - Returns the aurora stats for HTMX polling
+ * 
+ * This component is polled every 5 seconds alongside the canvas.
+ * It renders the stats display with polling attributes to continue polling.
+ */
+public class AuroraStatsEndpoint : Component {
+    
+    private AuroraState aurora_state = inject<AuroraState>();
+    
+    public override string markup { get {
+        return """
+        <div class="aurora-stats" sid="aurora-stats"
+             spry-action="AuroraStatsEndpoint:Poll" 
+             hx-trigger="every 5s" 
+             hx-swap="outerHTML">
+            <div class="aurora-stat">
+                <div class="aurora-stat-value" sid="solar-wind"></div>
+                <div class="aurora-stat-label">Solar Wind</div>
+            </div>
+            <div class="aurora-stat">
+                <div class="aurora-stat-value" sid="wave-count"></div>
+                <div class="aurora-stat-label">Waves</div>
+            </div>
+            <div class="aurora-stat">
+                <div class="aurora-stat-value" sid="intensity"></div>
+                <div class="aurora-stat-label">Intensity</div>
+            </div>
+            <div class="aurora-stat">
+                <div class="aurora-stat-value" sid="color-mode"></div>
+                <div class="aurora-stat-label">Color Mode</div>
+            </div>
+        </div>
+        """;
+    }}
+    
+    public override async void prepare() throws Error {
+        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;
+    }
+    
+    public async override void handle_action(string action) throws Error {
+        if (action == "Poll") {
+            // Stats are updated in prepare() automatically
+            // No additional state changes needed here
+        }
+    }
+}

+ 95 - 0
website/Main.vala

@@ -0,0 +1,95 @@
+using Astralis;
+using Invercargill;
+using Inversion;
+using Spry;
+
+/**
+ * Main.vala - Spry Framework Website
+ * 
+ * A modern, techy website showcasing the Spry framework and its ecosystem:
+ * - Astralis: High-performance web server framework
+ * - Inversion: Dependency injection container
+ * - Spry: Component-based HTMX web framework
+ * 
+ * Features:
+ * - Clean, eye-catching design with purple/blue/green theme
+ * - Interactive particle demo showcasing real-time HTMX updates
+ * - Responsive layout with modern CSS
+ * - Free/Libre open source spirit
+ */
+
+void main(string[] args) {
+    int port = args.length > 1 ? int.parse(args[1]) : 8080;
+    
+    print("═══════════════════════════════════════════════════════════════\n");
+    print("              🚀 Spry Framework Website\n");
+    print("═══════════════════════════════════════════════════════════════\n");
+    print("  Port: %d\n", port);
+    print("═══════════════════════════════════════════════════════════════\n");
+    print("  Pages:\n");
+    print("    /              - Home\n");
+    print("    /features      - Features\n");
+    print("    /ecosystem     - Ecosystem (Astralis, Inversion, Spry)\n");
+    print("    /demo          - Interactive Demo\n");
+    print("    /freedom       - Free/Libre Philosophy\n");
+    print("═══════════════════════════════════════════════════════════════\n");
+    print("\nPress Ctrl+C to stop the server\n\n");
+    
+    try {
+        var application = new WebApplication(port);
+        
+        // Enable compression
+        application.use_compression();
+        
+        // Add Spry module for component actions
+        application.add_module<SpryModule>();
+        
+        // Register stores as singletons
+        application.add_singleton<AuroraState>();
+        
+        // Configure templates
+        var spry_cfg = application.configure_with<SpryConfigurator>();
+        spry_cfg.add_template<SiteLayoutTemplate>("");
+        
+        // Register page components
+        application.add_transient<HomePage>();
+        application.add_endpoint<HomePage>(new EndpointRoute(HomePage.ROUTE));
+        
+        application.add_transient<FeaturesPage>();
+        application.add_endpoint<FeaturesPage>(new EndpointRoute(FeaturesPage.ROUTE));
+        
+        application.add_transient<EcosystemPage>();
+        application.add_endpoint<EcosystemPage>(new EndpointRoute(EcosystemPage.ROUTE));
+        
+        application.add_transient<DemoPage>();
+        application.add_endpoint<DemoPage>(new EndpointRoute(DemoPage.ROUTE));
+        
+        application.add_transient<FreedomPage>();
+        application.add_endpoint<FreedomPage>(new EndpointRoute(FreedomPage.ROUTE));
+        
+        // Register interactive components (for spry-action)
+        application.add_transient<AuroraWaveComponent>();
+        application.add_transient<AuroraCanvasEndpoint>();
+        application.add_transient<AuroraStatsEndpoint>();
+        application.add_transient<FeatureCardComponent>();
+        application.add_transient<CodeBlockComponent>();
+        application.add_transient<StatCardComponent>();
+        
+        // Register CSS as FastResources
+        application.add_startup_endpoint<FastResource>(new EndpointRoute("/styles/main.css"), () => {
+            try {
+                return new FastResource.from_string(Styles.MAIN_CSS)
+                    .with_content_type("text/css; charset=utf-8")
+                    .with_default_compressors();
+            } catch (Error e) {
+                error("Failed to create main CSS resource: %s", e.message);
+            }
+        });
+        
+        application.run();
+        
+    } catch (Error e) {
+        printerr("Error: %s\n", e.message);
+        Process.exit(1);
+    }
+}

+ 374 - 0
website/Pages/DemoPage.vala

@@ -0,0 +1,374 @@
+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<AuroraState>();
+    private ComponentFactory factory = inject<ComponentFactory>();
+    
+    public override string markup { get {
+        return """
+        <div sid="demo-page" class="section">
+            <div class="container">
+                <!-- Header -->
+                <div class="section-header">
+                    <h1>Interactive Demo</h1>
+                    <p>Experience the power of Spry with a live, server-backed Aurora simulation</p>
+                </div>
+
+                <!-- Aurora Demo -->
+                <section class="demo-section">
+                    <div class="demo-header">
+                        <h2>🌌 Live Aurora Borealis</h2>
+                        <p style="color: var(--color-text-muted); margin-bottom: var(--space-4);">
+                            This aurora is generated server-side! It polls for new parameters every 5 seconds.
+                        </p>
+                        <div class="demo-controls">
+                            <button class="demo-btn" spry-action=":BoostSolarWind" spry-target="demo-page" hx-swap="outerHTML">
+                                🌞 Boost Solar Wind
+                            </button>
+                            <button class="demo-btn" spry-action=":CalmAurora" spry-target="demo-page" hx-swap="outerHTML">
+                                🌙 Calm Aurora
+                            </button>
+                            <button class="demo-btn" spry-action=":ShiftColors" spry-target="demo-page" hx-swap="outerHTML">
+                                🎨 Shift Colors
+                            </button>
+                            <button class="demo-btn" spry-action=":AddWave" spry-target="demo-page" hx-swap="outerHTML">
+                                🌊 Add Wave
+                            </button>
+                        </div>
+                    </div>
+                    
+                    <!-- Aurora Canvas with auto-refresh -->
+                    <div class="aurora-canvas" sid="aurora-canvas" 
+                         spry-action="AuroraCanvasEndpoint:Poll" 
+                         hx-trigger="every 5s" 
+                         hx-swap="innerHTML transition:true">
+                        <spry-outlet sid="aurora-waves"/>
+                    </div>
+                    
+                    <!-- Aurora Stats -->
+                    <div class="aurora-stats" sid="aurora-stats"
+                         spry-action="AuroraStatsEndpoint:Poll" 
+                         hx-trigger="every 5s" 
+                         hx-swap="outerHTML transition:true">
+                        <div class="aurora-stat">
+                            <div class="aurora-stat-value" sid="solar-wind"></div>
+                            <div class="aurora-stat-label">Solar Wind</div>
+                        </div>
+                        <div class="aurora-stat">
+                            <div class="aurora-stat-value" sid="wave-count"></div>
+                            <div class="aurora-stat-label">Waves</div>
+                        </div>
+                        <div class="aurora-stat">
+                            <div class="aurora-stat-value" sid="intensity"></div>
+                            <div class="aurora-stat-label">Intensity</div>
+                        </div>
+                        <div class="aurora-stat">
+                            <div class="aurora-stat-value" sid="color-mode"></div>
+                            <div class="aurora-stat-label">Color Mode</div>
+                        </div>
+                    </div>
+                    
+                    <p style="margin-top: var(--space-4); text-align: center; color: var(--color-text-muted);">
+                        The aurora updates every 5 seconds via HTMX polling. Try the controls above!
+                    </p>
+                </section>
+
+                <!-- Counter Demo -->
+                <section class="demo-section">
+                    <h2>🔢 Simple Counter</h2>
+                    <p>A minimal example of Spry component state and actions.</p>
+                    
+                    <div style="display: flex; align-items: center; gap: var(--space-4); justify-content: center; margin: var(--space-6) 0;">
+                        <div class="stat-card" style="text-align: center; min-width: 200px;">
+                            <div class="stat-value" sid="counter-value"></div>
+                            <div class="stat-label">Current Count</div>
+                        </div>
+                    </div>
+                    
+                    <div style="display: flex; gap: var(--space-3); justify-content: center;">
+                        <button class="demo-btn" spry-action=":Decrement" spry-target="demo-page" hx-swap="outerHTML">
+                            − Decrement
+                        </button>
+                        <button class="demo-btn" spry-action=":Reset" spry-target="demo-page" hx-swap="outerHTML">
+                            ↺ Reset
+                        </button>
+                        <button class="demo-btn" spry-action=":Increment" spry-target="demo-page" hx-swap="outerHTML">
+                            + Increment
+                        </button>
+                    </div>
+                </section>
+
+                <!-- How It Works -->
+                <section class="demo-section">
+                    <h2>⚙ How It Works</h2>
+                    <p>This demo showcases several Spry features working together:</p>
+                    
+                    <div class="features-grid" style="margin-top: var(--space-6);">
+                        <div class="feature-card">
+                            <div class="feature-icon purple">🔄</div>
+                            <h3>Server Polling</h3>
+                            <p>hx-trigger="every 5s" fetches fresh aurora data from the server periodically.</p>
+                        </div>
+                        <div class="feature-card">
+                            <div class="feature-icon blue">⚡</div>
+                            <h3>Shared State</h3>
+                            <p>AuroraState is a singleton - all visitors see the same aurora!</p>
+                        </div>
+                        <div class="feature-card">
+                            <div class="feature-icon green">🎨</div>
+                            <h3>CSS Gradients</h3>
+                            <p>Server generates CSS gradient strips for beautiful aurora waves.</p>
+                        </div>
+                    </div>
+                    
+                    <div class="code-block" style="margin-top: var(--space-6);">
+                        <div class="code-header">
+                            <div class="code-dots">
+                                <div class="code-dot red"></div>
+                                <div class="code-dot yellow"></div>
+                                <div class="code-dot green"></div>
+                            </div>
+                            <span class="code-lang">HTML</span>
+                        </div>
+                        <pre><code>&lt;!-- Auto-refreshing aurora canvas --&gt;
+&lt;div hx-get="/demo/aurora" 
+     hx-trigger="every 5s" 
+     hx-swap="innerHTML"&gt;
+    &lt;!-- Aurora waves render here --&gt;
+&lt;/div&gt;
+
+&lt;!-- Control buttons --&gt;
+&lt;button spry-action=":BoostSolarWind"
+        spry-target="demo-page"
+        hx-swap="outerHTML"&gt;
+    Boost Solar Wind
+&lt;/button&gt;</code></pre>
+                    </div>
+                </section>
+
+                <!-- CTA -->
+                <div class="text-center" style="margin-top: var(--space-12);">
+                    <h2>Want to Learn More?</h2>
+                    <p style="margin-bottom: var(--space-6);">Explore the features that make Spry powerful.</p>
+                    <div class="hero-actions" style="justify-content: center;">
+                        <a href="/features" class="btn btn-primary">View Features</a>
+                        <a href="/ecosystem" class="btn btn-secondary">See Ecosystem</a>
+                    </div>
+                </div>
+            </div>
+        </div>
+        """;
+    }}
+    
+    public override async void prepare() throws Error {
+        // Render aurora waves
+        var waves = new Series<Renderable>();
+        int wave_num = 0;
+        foreach (var wave in aurora_state.get_waves()) {
+            var component = factory.create<AuroraWaveComponent>();
+            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<AuroraWaveData> waves = new GenericArray<AuroraWaveData>();
+    private Series<ColorPalette> 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<ColorPalette>();
+        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<AuroraWaveData> get_waves() {
+        return waves;
+    }
+}

+ 220 - 0
website/Pages/EcosystemPage.vala

@@ -0,0 +1,220 @@
+using Astralis;
+using Invercargill;
+using Invercargill.DataStructures;
+using Inversion;
+using Spry;
+
+/**
+ * EcosystemPage - Overview of the framework ecosystem
+ * 
+ * Showcases Astralis, Inversion, and Spry working together
+ */
+public class EcosystemPage : PageComponent {
+    
+    public const string ROUTE = "/ecosystem";
+    
+    public override string markup { get {
+        return """
+        <div sid="ecosystem-page" class="section">
+            <div class="container">
+                <!-- Header -->
+                <div class="section-header">
+                    <h1>The Ecosystem</h1>
+                    <p>Three powerful libraries working together to make web development in Vala a joy</p>
+                </div>
+
+                <!-- Ecosystem Overview -->
+                <div class="ecosystem-grid">
+                    <!-- Astralis -->
+                    <div class="ecosystem-card astralis">
+                        <h3>
+                            <div class="ecosystem-logo">A</div>
+                            Astralis
+                        </h3>
+                        <p>The high-performance web server foundation. Astralis provides the HTTP server, routing, compression, and static file serving that powers Spry applications.</p>
+                        
+                        <div style="margin-top: var(--space-4);">
+                            <span class="tag">HTTP Server</span>
+                            <span class="tag">Routing</span>
+                            <span class="tag">Compression</span>
+                            <span class="tag">Fast Resources</span>
+                            <span class="tag">Middleware</span>
+                        </div>
+                        
+                        <div class="code-block" style="margin-top: var(--space-6);">
+                            <div class="code-header">
+                                <div class="code-dots">
+                                    <div class="code-dot red"></div>
+                                    <div class="code-dot yellow"></div>
+                                    <div class="code-dot green"></div>
+                                </div>
+                                <span class="code-lang">Vala</span>
+                            </div>
+                            <pre><code>var app = new WebApplication(8080);
+app.use_compression();
+app.add_endpoint<MyEndpoint>(
+    new EndpointRoute("/api")
+);
+app.run();</code></pre>
+                        </div>
+                    </div>
+
+                    <!-- Inversion -->
+                    <div class="ecosystem-card inversion">
+                        <h3>
+                            <div class="ecosystem-logo">I</div>
+                            Inversion
+                        </h3>
+                        <p>A lightweight dependency injection container. Inversion manages your services, stores, and component lifecycles with elegant IoC patterns.</p>
+                        
+                        <div style="margin-top: var(--space-4);">
+                            <span class="tag">IoC Container</span>
+                            <span class="tag">DI</span>
+                            <span class="tag">Lifecycles</span>
+                            <span class="tag">Factories</span>
+                            <span class="tag">Modules</span>
+                        </div>
+                        
+                        <div class="code-block" style="margin-top: var(--space-6);">
+                            <div class="code-header">
+                                <div class="code-dots">
+                                    <div class="code-dot red"></div>
+                                    <div class="code-dot yellow"></div>
+                                    <div class="code-dot green"></div>
+                                </div>
+                                <span class="code-lang">Vala</span>
+                            </div>
+                            <pre><code>// Register services
+container.add_singleton<Database>();
+container.add_scoped<UserSession>();
+container.add_transient<EmailService>();
+
+// Inject anywhere
+var db = inject<Database>();</code></pre>
+                        </div>
+                    </div>
+
+                    <!-- Spry -->
+                    <div class="ecosystem-card spry">
+                        <h3>
+                            <div class="ecosystem-logo">S</div>
+                            Spry
+                        </h3>
+                        <p>The component-based web framework. Spry brings reactive templates, HTMX integration, and a delightful developer experience to Vala web development.</p>
+                        
+                        <div style="margin-top: var(--space-4);">
+                            <span class="tag">Components</span>
+                            <span class="tag">HTMX</span>
+                            <span class="tag">Templates</span>
+                            <span class="tag">Outlets</span>
+                            <span class="tag">Actions</span>
+                        </div>
+                        
+                        <div class="code-block" style="margin-top: var(--space-6);">
+                            <div class="code-header">
+                                <div class="code-dots">
+                                    <div class="code-dot red"></div>
+                                    <div class="code-dot yellow"></div>
+                                    <div class="code-dot green"></div>
+                                </div>
+                                <span class="code-lang">Vala</span>
+                            </div>
+                            <pre><code>class MyComponent : Component {
+    public override string markup {
+        owned get {
+            return "<div sid='root'>
+                <button spry-action=':Click'>
+                    Click me!
+                </button>
+            </div>";
+        }
+    }
+}</code></pre>
+                        </div>
+                    </div>
+                </div>
+
+                <!-- How They Work Together -->
+                <section class="demo-section" style="margin-top: var(--space-12);">
+                    <h2>🔗 How They Work Together</h2>
+                    <p>The three libraries form a complete stack for building web applications:</p>
+                    
+                    <div class="features-grid" style="margin-top: var(--space-6);">
+                        <div class="feature-card">
+                            <div class="feature-icon blue">1</div>
+                            <h3>Astralis Handles HTTP</h3>
+                            <p>Receives requests, routes them to the right handler, and sends responses with compression.</p>
+                        </div>
+                        <div class="feature-card">
+                            <div class="feature-icon green">2</div>
+                            <h3>Inversion Manages Dependencies</h3>
+                            <p>Creates and injects services, stores, and components with the right lifecycle.</p>
+                        </div>
+                        <div class="feature-card">
+                            <div class="feature-icon purple">3</div>
+                            <h3>Spry Renders UI</h3>
+                            <p>Components handle requests, render templates, and respond with HTML for HTMX.</p>
+                        </div>
+                    </div>
+                </section>
+
+                <!-- Architecture Diagram -->
+                <section class="demo-section" style="margin-top: var(--space-8);">
+                    <h2>🏗 Architecture Overview</h2>
+                    
+                    <div class="code-block">
+                        <div class="code-header">
+                            <div class="code-dots">
+                                <div class="code-dot red"></div>
+                                <div class="code-dot yellow"></div>
+                                <div class="code-dot green"></div>
+                            </div>
+                            <span class="code-lang">Request Flow</span>
+                        </div>
+                        <pre><code>┌─────────────────────────────────────────────────────────────┐
+│                        Browser                               │
+│                     (HTMX + HTML)                            │
+└─────────────────────────┬───────────────────────────────────┘
+                          │ HTTP Request
+                          ▼
+┌─────────────────────────────────────────────────────────────┐
+│                      Astralis                                │
+│  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐         │
+│  │   Server    │→ │   Router    │→ │ Compression │         │
+│  └─────────────┘  └─────────────┘  └─────────────┘         │
+└─────────────────────────┬───────────────────────────────────┘
+                          │
+                          ▼
+┌─────────────────────────────────────────────────────────────┐
+│                      Inversion                               │
+│  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐         │
+│  │  Container  │→ │  Factories  │→ │   Scopes    │         │
+│  └─────────────┘  └─────────────┘  └─────────────┘         │
+│           ↓ inject() into components                        │
+└─────────────────────────┬───────────────────────────────────┘
+                          │
+                          ▼
+┌─────────────────────────────────────────────────────────────┐
+│                        Spry                                  │
+│  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐         │
+│  │ Components  │→ │  Templates  │→ │   Actions   │         │
+│  └─────────────┘  └─────────────┘  └─────────────┘         │
+│           ↓ HTML Response with HTMX attributes              │
+└─────────────────────────────────────────────────────────────┘</code></pre>
+                    </div>
+                </section>
+
+                <!-- CTA -->
+                <div class="text-center" style="margin-top: var(--space-12);">
+                    <h2>Experience the Power</h2>
+                    <p style="margin-bottom: var(--space-6);">See the ecosystem in action with our interactive demo.</p>
+                    <div class="hero-actions" style="justify-content: center;">
+                        <a href="/demo" class="btn btn-primary">Try Live Demo</a>
+                        <a href="/features" class="btn btn-secondary">View Features</a>
+                    </div>
+                </div>
+            </div>
+        </div>
+        """;
+    }}
+}

+ 190 - 0
website/Pages/FeaturesPage.vala

@@ -0,0 +1,190 @@
+using Astralis;
+using Invercargill;
+using Invercargill.DataStructures;
+using Inversion;
+using Spry;
+
+/**
+ * FeaturesPage - Detailed features overview
+ * 
+ * Showcases all the capabilities of the Spry framework
+ */
+public class FeaturesPage : PageComponent {
+    
+    public const string ROUTE = "/features";
+    
+    public override string markup { get {
+        return """
+        <div sid="features-page" class="section">
+            <div class="container">
+                <!-- Header -->
+                <div class="section-header">
+                    <h1>Features</h1>
+                    <p>Everything you need to build modern, reactive web applications in Vala</p>
+                </div>
+
+                <!-- Component System -->
+                <section class="demo-section">
+                    <h2>🧩 Component-Based Architecture</h2>
+                    <p>Build your UI with reusable, composable components that encapsulate state and behavior.</p>
+                    
+                    <div class="features-grid" style="margin-top: var(--space-6);">
+                        <div class="feature-card">
+                            <div class="feature-icon purple">📦</div>
+                            <h3>Encapsulated State</h3>
+                            <p>Each component manages its own state, making your code predictable and testable.</p>
+                        </div>
+                        <div class="feature-card">
+                            <div class="feature-icon blue">🔗</div>
+                            <h3>Outlets & Children</h3>
+                            <p>Use outlets to compose components together, creating complex UIs from simple parts.</p>
+                        </div>
+                        <div class="feature-card">
+                            <div class="feature-icon green">♻</div>
+                            <h3>Lifecycle Hooks</h3>
+                            <p>prepare() and handle_action() hooks give you control over component behavior.</p>
+                        </div>
+                    </div>
+                </section>
+
+                <!-- HTMX Integration -->
+                <section class="demo-section">
+                    <h2>⚡ HTMX Integration</h2>
+                    <p>Add dynamic behavior without writing JavaScript. Spry makes HTMX even more powerful.</p>
+                    
+                    <div class="code-block">
+                        <div class="code-header">
+                            <div class="code-dots">
+                                <div class="code-dot red"></div>
+                                <div class="code-dot yellow"></div>
+                                <div class="code-dot green"></div>
+                            </div>
+                            <span class="code-lang">HTML with Spry Attributes</span>
+                        </div>
+                        <pre><code>&lt;!-- Declarative actions --&gt;
+&lt;button spry-action=":Toggle"
+        spry-target="item"
+        hx-swap="outerHTML"&gt;
+    Toggle Item
+&lt;/button&gt;
+
+&lt;!-- Cross-component communication --&gt;
+&lt;form spry-action="ListComponent:Add"
+      hx-target="#my-list"&gt;
+    &lt;input name="title" /&gt;
+    &lt;button type="submit"&gt;Add&lt;/button&gt;
+&lt;/form&gt;
+
+&lt;!-- Out-of-band updates --&gt;
+&lt;div spry-global="header"&gt;
+    Updates automatically when data changes
+&lt;/div&gt;</code></pre>
+                    </div>
+                    
+                    <div class="features-grid" style="margin-top: var(--space-6);">
+                        <div class="feature-card">
+                            <div class="feature-icon purple">🎯</div>
+                            <h3>spry-action</h3>
+                            <p>Declare component actions directly in your markup. No event listeners needed.</p>
+                        </div>
+                        <div class="feature-card">
+                            <div class="feature-icon blue">🔍</div>
+                            <h3>spry-target</h3>
+                            <p>Target elements within your component using simple sid references.</p>
+                        </div>
+                        <div class="feature-card">
+                            <div class="feature-icon green">🌐</div>
+                            <h3>spry-global</h3>
+                            <p>Update multiple page regions with out-of-band HTMX swaps.</p>
+                        </div>
+                    </div>
+                </section>
+
+                <!-- Dependency Injection -->
+                <section class="demo-section">
+                    <h2>💉 Dependency Injection</h2>
+                    <p>Powered by Inversion, get clean, testable code with automatic dependency resolution.</p>
+                    
+                    <div class="code-block">
+                        <div class="code-header">
+                            <div class="code-dots">
+                                <div class="code-dot red"></div>
+                                <div class="code-dot yellow"></div>
+                                <div class="code-dot green"></div>
+                            </div>
+                            <span class="code-lang">Vala</span>
+                        </div>
+                        <pre><code>class TodoComponent : Component {
+    // Inject dependencies in field initializers
+    private TodoStore store = inject&lt;TodoStore&gt;();
+    private ComponentFactory factory = inject&lt;ComponentFactory&gt;();
+    private HttpContext http = inject&lt;HttpContext&gt;();
+}
+
+// Register services with different lifecycles
+application.add_singleton&lt;AppState&gt;();      // One instance forever
+application.add_scoped&lt;ComponentFactory&gt;(); // One per request
+application.add_transient&lt;MyComponent&gt;();  // New each time</code></pre>
+                    </div>
+                </section>
+
+                <!-- Templates -->
+                <section class="demo-section">
+                    <h2>📄 Page Templates</h2>
+                    <p>Create reusable layouts with nested templates that automatically wrap your pages.</p>
+                    
+                    <div class="features-grid" style="margin-top: var(--space-6);">
+                        <div class="feature-card">
+                            <div class="feature-icon purple">🏗</div>
+                            <h3>Nested Layouts</h3>
+                            <p>Stack templates to create complex layouts from simple, reusable pieces.</p>
+                        </div>
+                        <div class="feature-card">
+                            <div class="feature-icon blue">📍</div>
+                            <h3>Route-Based</h3>
+                            <p>Templates are applied based on route prefixes, automatically wrapping matching pages.</p>
+                        </div>
+                        <div class="feature-card">
+                            <div class="feature-icon green">🔌</div>
+                            <h3>Template Outlets</h3>
+                            <p>Use <spry-template-outlet /> to define where child content renders.</p>
+                        </div>
+                    </div>
+                </section>
+
+                <!-- Performance -->
+                <section class="demo-section">
+                    <h2>🚀 High Performance</h2>
+                    <p>Built on Astralis for native code performance with web framework convenience.</p>
+                    
+                    <div class="stats-grid" style="margin-top: var(--space-6);">
+                        <div class="stat-card">
+                            <div class="stat-value">Native</div>
+                            <div class="stat-label">Code Execution</div>
+                        </div>
+                        <div class="stat-card">
+                            <div class="stat-value">Brotli</div>
+                            <div class="stat-label">Compression</div>
+                        </div>
+                        <div class="stat-card">
+                            <div class="stat-value">Zero</div>
+                            <div class="stat-label">JavaScript Required</div>
+                        </div>
+                        <div class="stat-card">
+                            <div class="stat-value">Fast</div>
+                            <div class="stat-label">Resource Loading</div>
+                        </div>
+                    </div>
+                </section>
+
+                <!-- CTA -->
+                <div class="text-center" style="margin-top: var(--space-12);">
+                    <h2>Ready to Build?</h2>
+                    <p style="margin-bottom: var(--space-6);">Try the interactive demo and see Spry in action.</p>
+                    <a href="/demo" class="btn btn-primary">Try Live Demo</a>
+                </div>
+            </div>
+        </div>
+        """;
+    }}
+}

+ 171 - 0
website/Pages/FreedomPage.vala

@@ -0,0 +1,171 @@
+using Astralis;
+using Spry;
+
+/**
+ * FreedomPage - Free/Libre philosophy page
+ * 
+ * Celebrates the free software roots and values of the project
+ */
+public class FreedomPage : PageComponent {
+    
+    public const string ROUTE = "/freedom";
+    
+    public override string markup { get {
+        return """
+        <div sid="freedom-page" class="section">
+            <div class="container">
+                <!-- Hero -->
+                <section class="freedom-section" style="margin-bottom: var(--space-12);">
+                    <div class="freedom-icon">🕊</div>
+                    <h1>Free as in Freedom</h1>
+                    <p>
+                        Spry, Astralis, and Inversion are Free/Libre Open Source Software.
+                        We believe software freedom is essential for a free society.
+                    </p>
+                </section>
+
+                <!-- The Four Freedoms -->
+                <section style="margin-bottom: var(--space-12);">
+                    <div class="section-header">
+                        <h2>The Four Essential Freedoms</h2>
+                        <p>As defined by the Free Software Foundation</p>
+                    </div>
+                    
+                    <div class="features-grid">
+                        <div class="feature-card">
+                            <div class="feature-icon purple">0️⃣</div>
+                            <h3>Freedom 0</h3>
+                            <p><strong>The freedom to run the program as you wish, for any purpose.</strong></p>
+                            <p style="margin-top: var(--space-2);">Use Spry for personal projects, commercial applications, education, or anything else. No restrictions.</p>
+                        </div>
+                        <div class="feature-card">
+                            <div class="feature-icon blue">1️⃣</div>
+                            <h3>Freedom 1</h3>
+                            <p><strong>The freedom to study how the program works, and change it.</strong></p>
+                            <p style="margin-top: var(--space-2);">Full source code is available. Learn from it, modify it, make it your own.</p>
+                        </div>
+                        <div class="feature-card">
+                            <div class="feature-icon green">2️⃣</div>
+                            <h3>Freedom 2</h3>
+                            <p><strong>The freedom to redistribute copies so you can help others.</strong></p>
+                            <p style="margin-top: var(--space-2);">Share Spry with your friends, colleagues, and community. Help spread software freedom.</p>
+                        </div>
+                        <div class="feature-card">
+                            <div class="feature-icon purple">3️⃣</div>
+                            <h3>Freedom 3</h3>
+                            <p><strong>The freedom to distribute modified versions to others.</strong></p>
+                            <p style="margin-top: var(--space-2);">Improve Spry and share your improvements. Give the whole community a benefit.</p>
+                        </div>
+                    </div>
+                </section>
+
+                <!-- Why Free Software Matters -->
+                <section class="demo-section" style="margin-bottom: var(--space-12);">
+                    <h2>💚 Why Free Software Matters</h2>
+                    
+                    <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: var(--space-6); margin-top: var(--space-6);">
+                        <div>
+                            <h3>🔓 Transparency</h3>
+                            <p>With source code available, you can verify exactly what the software does. No hidden behavior, no secret data collection.</p>
+                        </div>
+                        <div>
+                            <h3>🛡 Security</h3>
+                            <p>Open code means more eyes reviewing it. Security vulnerabilities can be found and fixed by anyone in the community.</p>
+                        </div>
+                        <div>
+                            <h3>📚 Learning</h3>
+                            <p>Free software is a treasure trove of knowledge. Study real-world code to become a better developer.</p>
+                        </div>
+                        <div>
+                            <h3>🌐 Community</h3>
+                            <p>Free software builds communities. Contributors from around the world work together to improve shared resources.</p>
+                        </div>
+                        <div>
+                            <h3>♾ Longevity</h3>
+                            <p>Free software can't be discontinued. If the original authors stop maintaining it, the community can continue.</p>
+                        </div>
+                        <div>
+                            <h3>⚖ Ethics</h3>
+                            <p>Respecting users' freedom is the ethical approach to software development. Users deserve control over their computing.</p>
+                        </div>
+                    </div>
+                </section>
+
+                <!-- Licensing -->
+                <section class="demo-section" style="margin-bottom: var(--space-12);">
+                    <h2>📜 Licensing</h2>
+                    
+                    <div class="ecosystem-grid" style="margin-top: var(--space-6);">
+                        <div class="ecosystem-card spry">
+                            <h3>
+                                <div class="ecosystem-logo">S</div>
+                                Spry
+                            </h3>
+                            <p>Licensed under the <strong>LGPL v2.1</strong> or later.</p>
+                            <p style="margin-top: var(--space-2);">You can use Spry in proprietary applications while modifications to Spry itself must be shared.</p>
+                        </div>
+                        <div class="ecosystem-card astralis">
+                            <h3>
+                                <div class="ecosystem-logo">A</div>
+                                Astralis
+                            </h3>
+                            <p>Licensed under the <strong>LGPL v2.1</strong> or later.</p>
+                            <p style="margin-top: var(--space-2);">Build high-performance web services on top of Astralis without licensing restrictions.</p>
+                        </div>
+                        <div class="ecosystem-card inversion">
+                            <h3>
+                                <div class="ecosystem-logo">I</div>
+                                Inversion
+                            </h3>
+                            <p>Licensed under the <strong>LGPL v2.1</strong> or later.</p>
+                            <p style="margin-top: var(--space-2);">Use dependency injection in any project with the freedom to keep your code private.</p>
+                        </div>
+                    </div>
+                </section>
+
+                <!-- Contributing -->
+                <section class="demo-section" style="margin-bottom: var(--space-12);">
+                    <h2>🤝 Contributing</h2>
+                    <p>We welcome contributions from everyone! Here's how you can help:</p>
+                    
+                    <div class="features-grid" style="margin-top: var(--space-6);">
+                        <div class="feature-card">
+                            <div class="feature-icon blue">💻</div>
+                            <h3>Code</h3>
+                            <p>Fix bugs, add features, improve performance. Every contribution makes Spry better for everyone.</p>
+                        </div>
+                        <div class="feature-card">
+                            <div class="feature-icon green">📝</div>
+                            <h3>Documentation</h3>
+                            <p>Write tutorials, improve API docs, create examples. Help others learn and use Spry effectively.</p>
+                        </div>
+                        <div class="feature-card">
+                            <div class="feature-icon purple">🐛</div>
+                            <h3>Testing</h3>
+                            <p>Report bugs, test new releases, write test cases. Quality assurance is essential for reliability.</p>
+                        </div>
+                    </div>
+                </section>
+
+                <!-- Quote -->
+                <section class="demo-section">
+                    <blockquote class="pull-quote">
+                        <p>"Free software is a matter of liberty, not price. To understand the concept, you should think of 'free' as in 'free speech,' not as in 'free beer.'"</p>
+                        <cite>— Richard Stallman</cite>
+                    </blockquote>
+                </section>
+
+                <!-- CTA -->
+                <div class="text-center" style="margin-top: var(--space-12);">
+                    <h2>Join the Community</h2>
+                    <p style="margin-bottom: var(--space-6);">Start building with free software today.</p>
+                    <div class="hero-actions" style="justify-content: center;">
+                        <a href="/demo" class="btn btn-primary">Try Demo</a>
+                        <a href="/features" class="btn btn-secondary">Explore Features</a>
+                    </div>
+                </div>
+            </div>
+        </div>
+        """;
+    }}
+}

+ 190 - 0
website/Pages/HomePage.vala

@@ -0,0 +1,190 @@
+using Astralis;
+using Invercargill;
+using Invercargill.DataStructures;
+using Inversion;
+using Spry;
+
+/**
+ * HomePage - The main landing page
+ * 
+ * Eye-catching hero section with gradient text,
+ * feature highlights, and call-to-action buttons
+ */
+public class HomePage : PageComponent {
+    
+    public const string ROUTE = "/";
+    
+    private ComponentFactory factory = inject<ComponentFactory>();
+    
+    public override string markup { get {
+        return """
+        <div sid="home">
+            <!-- Hero Section -->
+            <section class="hero">
+                <div class="container">
+                    <span class="hero-badge">
+                        ⚡ Free & Open Source
+                    </span>
+                    <h1>Build Modern Web Apps in Vala</h1>
+                    <p class="hero-subtitle">
+                        Spry is a component-based web framework featuring HTMX integration,
+                        reactive templates, and dependency injection. Fast, type-safe, and elegant.
+                    </p>
+                    <div class="hero-actions">
+                        <a href="/demo" class="btn btn-primary">
+                            ✨ Try Live Demo
+                        </a>
+                        <a href="/features" class="btn btn-secondary">
+                            Explore Features
+                        </a>
+                    </div>
+                </div>
+            </section>
+
+            <!-- Quick Stats -->
+            <section class="section">
+                <div class="container">
+                    <div class="stats-grid">
+                        <div class="stat-card">
+                            <div class="stat-value">100%</div>
+                            <div class="stat-label">Type-Safe Vala</div>
+                        </div>
+                        <div class="stat-card">
+                            <div class="stat-value">0</div>
+                            <div class="stat-label">JavaScript Required</div>
+                        </div>
+                        <div class="stat-card">
+                            <div class="stat-value">∞</div>
+                            <div class="stat-label">Possibilities</div>
+                        </div>
+                        <div class="stat-card">
+                            <div class="stat-value">FREE</div>
+                            <div class="stat-label">Forever</div>
+                        </div>
+                    </div>
+                </div>
+            </section>
+
+            <!-- Feature Highlights -->
+            <section class="section">
+                <div class="container">
+                    <div class="section-header">
+                        <h2>Why Choose Spry?</h2>
+                        <p>Build powerful web applications with a framework designed for developer happiness</p>
+                    </div>
+                    <div class="features-grid">
+                        <spry-outlet sid="features"/>
+                    </div>
+                </div>
+            </section>
+
+            <!-- Code Example -->
+            <section class="section">
+                <div class="container">
+                    <div class="section-header">
+                        <h2>Clean, Intuitive Code</h2>
+                        <p>Write components that are easy to understand and maintain</p>
+                    </div>
+                    <div class="code-block">
+                        <div class="code-header">
+                            <div class="code-dots">
+                                <div class="code-dot red"></div>
+                                <div class="code-dot yellow"></div>
+                                <div class="code-dot green"></div>
+                            </div>
+                            <span class="code-lang">Vala</span>
+                        </div>
+                        <pre><code>class CounterComponent : Component {
+    private int count = 0;
+
+    public override string markup {
+        owned get {
+            // Use spry-action for interactivity
+            return @"Counter: $(count)
+                [Button: spry-action=':Increment' +]
+                [Button: spry-action=':Decrement' -]";
+        }
+    }
+
+    public async override void handle_action(string action) {
+        if (action == "Increment") count++;
+        else if (action == "Decrement") count--;
+    }
+}</code></pre>
+                    </div>
+                </div>
+            </section>
+
+            <!-- CTA Section -->
+            <section class="section">
+                <div class="container">
+                    <div class="freedom-section">
+                        <div class="freedom-icon">🕊</div>
+                        <h2>Free as in Freedom</h2>
+                        <p>
+                            Spry is Free/Libre Open Source Software. Use it, study it, modify it,
+                            and share it freely. Built by the community, for the community.
+                        </p>
+                        <div class="hero-actions">
+                            <a href="/freedom" class="btn btn-accent">
+                                Learn About Freedom
+                            </a>
+                            <a href="/ecosystem" class="btn btn-secondary">
+                                Explore Ecosystem
+                            </a>
+                        </div>
+                    </div>
+                </div>
+            </section>
+        </div>
+        """;
+    }}
+    
+    public override async void prepare() throws Error {
+        var features = new Series<Renderable>();
+        
+        var feature1 = factory.create<FeatureCardComponent>();
+        feature1.icon = "purple";
+        feature1.icon_emoji = "⚡";
+        feature1.title = "HTMX Integration";
+        feature1.description = "Build dynamic UIs without writing JavaScript. HTMX handles the complexity, you handle the logic.";
+        features.add(feature1);
+        
+        var feature2 = factory.create<FeatureCardComponent>();
+        feature2.icon = "blue";
+        feature2.icon_emoji = "🔧";
+        feature2.title = "Dependency Injection";
+        feature2.description = "Clean architecture with Inversion of Control. Inject services, stores, and components effortlessly.";
+        features.add(feature2);
+        
+        var feature3 = factory.create<FeatureCardComponent>();
+        feature3.icon = "green";
+        feature3.icon_emoji = "🎨";
+        feature3.title = "Reactive Templates";
+        feature3.description = "Declarative templates with outlets and automatic updates. Your UI stays in sync with your data.";
+        features.add(feature3);
+        
+        var feature4 = factory.create<FeatureCardComponent>();
+        feature4.icon = "purple";
+        feature4.icon_emoji = "🔒";
+        feature4.title = "Type-Safe";
+        feature4.description = "Full Vala type safety means fewer runtime errors. The compiler catches bugs before you do.";
+        features.add(feature4);
+        
+        var feature5 = factory.create<FeatureCardComponent>();
+        feature5.icon = "blue";
+        feature5.icon_emoji = "🚀";
+        feature5.title = "High Performance";
+        feature5.description = "Built on Astralis for maximum throughput. Native code performance with web framework convenience.";
+        features.add(feature5);
+        
+        var feature6 = factory.create<FeatureCardComponent>();
+        feature6.icon = "green";
+        feature6.icon_emoji = "📦";
+        feature6.title = "Modular Design";
+        feature6.description = "Use what you need, extend what you want. Clean separation of concerns at every level.";
+        features.add(feature6);
+        
+        set_outlet_children("features", features);
+    }
+}

+ 1175 - 0
website/Styles.vala

@@ -0,0 +1,1175 @@
+/**
+ * Styles.vala - CSS styles for the Spry Framework Website
+ *
+ * Inspired by astralis-css with purple/blue/green theme
+ * Modern, clean, techy design with Free/Libre spirit
+ */
+
+namespace Styles {
+    public const string MAIN_CSS = """
+/* ============================================
+ * CSS VARIABLES & DESIGN TOKENS
+ * ============================================ */
+
+:root {
+  /* Primary: Deep Rich Purple */
+  --purple-50: #faf5ff;
+  --purple-100: #f3e8ff;
+  --purple-200: #e9d5ff;
+  --purple-300: #d8b4fe;
+  --purple-400: #c084fc;
+  --purple-500: #a855f7;
+  --purple-600: #9333ea;
+  --purple-700: #7c3aed;
+  --purple-800: #6b21a8;
+  --purple-900: #581c87;
+  --purple-950: #3b0764;
+  
+  /* Secondary: Technology Blue */
+  --blue-50: #eff6ff;
+  --blue-100: #dbeafe;
+  --blue-200: #bfdbfe;
+  --blue-300: #93c5fd;
+  --blue-400: #60a5fa;
+  --blue-500: #3b82f6;
+  --blue-600: #2563eb;
+  --blue-700: #1d4ed8;
+  --blue-800: #1e40af;
+  --blue-900: #1e3a8a;
+  --blue-950: #172554;
+  
+  /* Accent: Libre Green */
+  --green-50: #f0fdf4;
+  --green-100: #dcfce7;
+  --green-200: #bbf7d0;
+  --green-300: #86efac;
+  --green-400: #4ade80;
+  --green-500: #22c55e;
+  --green-600: #16a34a;
+  --green-700: #15803d;
+  --green-800: #166534;
+  --green-900: #14532d;
+  --green-950: #052e16;
+  
+  /* Neutral */
+  --gray-50: #fafafa;
+  --gray-100: #f4f4f5;
+  --gray-200: #e4e4e7;
+  --gray-300: #d4d4d8;
+  --gray-400: #a1a1aa;
+  --gray-500: #71717a;
+  --gray-600: #52525b;
+  --gray-700: #3f3f46;
+  --gray-800: #27272a;
+  --gray-900: #18181b;
+  --gray-950: #09090b;
+  
+  /* Semantic Colors */
+  --color-primary: var(--purple-700);
+  --color-primary-hover: var(--purple-800);
+  --color-primary-light: var(--purple-100);
+  --color-primary-lighter: var(--purple-50);
+  
+  --color-secondary: var(--blue-600);
+  --color-secondary-hover: var(--blue-700);
+  --color-secondary-light: var(--blue-100);
+  
+  --color-accent: var(--green-500);
+  --color-accent-hover: var(--green-600);
+  --color-accent-light: var(--green-100);
+  
+  --color-bg: #ffffff;
+  --color-bg-secondary: var(--gray-50);
+  --color-surface: #ffffff;
+  --color-text: var(--gray-900);
+  --color-text-secondary: var(--gray-600);
+  --color-text-muted: var(--gray-400);
+  --color-border: var(--gray-200);
+  
+  /* Typography */
+  --font-sans: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
+  --font-mono: "JetBrains Mono", "Fira Code", monospace;
+  
+  /* Spacing */
+  --space-1: 0.25rem;
+  --space-2: 0.5rem;
+  --space-3: 0.75rem;
+  --space-4: 1rem;
+  --space-6: 1.5rem;
+  --space-8: 2rem;
+  --space-12: 3rem;
+  --space-16: 4rem;
+  --space-24: 6rem;
+  
+  /* Border Radius */
+  --radius-sm: 0.25rem;
+  --radius-md: 0.5rem;
+  --radius-lg: 0.75rem;
+  --radius-xl: 1rem;
+  --radius-2xl: 1.5rem;
+  --radius-full: 9999px;
+  
+  /* Shadows */
+  --shadow-sm: 0 1px 2px rgba(0,0,0,0.05);
+  --shadow-md: 0 4px 6px -1px rgba(0,0,0,0.1);
+  --shadow-lg: 0 10px 15px -3px rgba(0,0,0,0.1);
+  --shadow-xl: 0 20px 25px -5px rgba(0,0,0,0.1);
+  --shadow-glow-purple: 0 0 40px rgba(124, 58, 237, 0.3);
+  --shadow-glow-blue: 0 0 40px rgba(37, 99, 235, 0.3);
+  --shadow-glow-green: 0 0 40px rgba(34, 197, 94, 0.3);
+  
+  /* Transitions */
+  --transition-fast: 150ms ease;
+  --transition-base: 200ms ease;
+  --transition-slow: 300ms ease;
+}
+
+/* Dark mode */
+@media (prefers-color-scheme: dark) {
+  :root {
+    --color-bg: var(--gray-900);
+    --color-bg-secondary: var(--gray-800);
+    --color-surface: var(--gray-800);
+    --color-text: var(--gray-50);
+    --color-text-secondary: var(--gray-300);
+    --color-text-muted: var(--gray-500);
+    --color-border: var(--gray-700);
+    --color-primary: var(--purple-400);
+    --color-primary-hover: var(--purple-300);
+    --color-primary-light: var(--purple-900);
+    --color-secondary: var(--blue-400);
+    --color-secondary-hover: var(--blue-300);
+    --color-secondary-light: var(--blue-900);
+    --color-accent: var(--green-400);
+    --color-accent-hover: var(--green-300);
+    --color-accent-light: var(--green-900);
+  }
+}
+
+/* ============================================
+ * BASE RESET
+ * ============================================ */
+
+*, *::before, *::after {
+  box-sizing: border-box;
+  margin: 0;
+  padding: 0;
+}
+
+html {
+  scroll-behavior: smooth;
+}
+
+body {
+  font-family: var(--font-sans);
+  font-size: 16px;
+  line-height: 1.6;
+  color: var(--color-text);
+  background-color: var(--color-bg);
+  min-height: 100vh;
+  -webkit-font-smoothing: antialiased;
+  -moz-osx-font-smoothing: grayscale;
+}
+
+/* ============================================
+ * TYPOGRAPHY
+ * ============================================ */
+
+h1, h2, h3, h4, h5, h6 {
+  font-weight: 700;
+  line-height: 1.2;
+  margin-bottom: var(--space-4);
+}
+
+h1 { font-size: 3rem; letter-spacing: -0.02em; }
+h2 { font-size: 2.25rem; letter-spacing: -0.01em; }
+h3 { font-size: 1.5rem; }
+h4 { font-size: 1.25rem; }
+
+p {
+  margin-bottom: var(--space-4);
+  color: var(--color-text-secondary);
+}
+
+a {
+  color: var(--color-primary);
+  text-decoration: none;
+  transition: color var(--transition-fast);
+}
+
+a:hover {
+  color: var(--color-primary-hover);
+}
+
+code {
+  font-family: var(--font-mono);
+  font-size: 0.9em;
+  background: var(--color-bg-secondary);
+  padding: var(--space-1) var(--space-2);
+  border-radius: var(--radius-sm);
+  color: var(--color-primary);
+}
+
+pre {
+  font-family: var(--font-mono);
+  background: var(--gray-900);
+  color: var(--gray-50);
+  padding: var(--space-4);
+  border-radius: var(--radius-lg);
+  overflow-x: auto;
+  font-size: 0.875rem;
+  line-height: 1.7;
+  margin: var(--space-4) 0;
+}
+
+pre code {
+  background: none;
+  padding: 0;
+  color: inherit;
+}
+
+/* ============================================
+ * LAYOUT
+ * ============================================ */
+
+.container {
+  width: 100%;
+  max-width: 1200px;
+  margin: 0 auto;
+  padding: 0 var(--space-6);
+}
+
+.section {
+  padding: var(--space-24) 0;
+}
+
+.section-header {
+  text-align: center;
+  margin-bottom: var(--space-12);
+}
+
+.section-header h2 {
+  margin-bottom: var(--space-4);
+}
+
+.section-header p {
+  font-size: 1.125rem;
+  max-width: 600px;
+  margin: 0 auto;
+}
+
+/* ============================================
+ * HEADER & NAVIGATION
+ * ============================================ */
+
+.site-header {
+  position: sticky;
+  top: 0;
+  z-index: 100;
+  background: rgba(255, 255, 255, 0.8);
+  backdrop-filter: blur(12px);
+  border-bottom: 1px solid var(--color-border);
+}
+
+@media (prefers-color-scheme: dark) {
+  .site-header {
+    background: rgba(24, 24, 27, 0.8);
+  }
+}
+
+.header-content {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding: var(--space-4) var(--space-6);
+  max-width: 1200px;
+  margin: 0 auto;
+}
+
+.logo {
+  display: flex;
+  align-items: center;
+  gap: var(--space-2);
+  font-size: 1.5rem;
+  font-weight: 800;
+  color: var(--color-text);
+}
+
+.logo-icon {
+  width: 32px;
+  height: 32px;
+  background: linear-gradient(135deg, var(--purple-600), var(--blue-600));
+  border-radius: var(--radius-md);
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  color: white;
+  font-size: 1rem;
+}
+
+.nav-links {
+  display: flex;
+  gap: var(--space-8);
+  list-style: none;
+}
+
+.nav-links a {
+  color: var(--color-text-secondary);
+  font-weight: 500;
+  padding: var(--space-2) 0;
+  position: relative;
+}
+
+.nav-links a:hover {
+  color: var(--color-text);
+}
+
+.nav-links a::after {
+  content: '';
+  position: absolute;
+  bottom: 0;
+  left: 0;
+  width: 0;
+  height: 2px;
+  background: linear-gradient(90deg, var(--purple-600), var(--blue-600));
+  transition: width var(--transition-base);
+}
+
+.nav-links a:hover::after {
+  width: 100%;
+}
+
+.nav-cta {
+  background: linear-gradient(135deg, var(--purple-600), var(--blue-600));
+  color: white;
+  padding: var(--space-2) var(--space-4);
+  border-radius: var(--radius-md);
+  font-weight: 600;
+  transition: transform var(--transition-fast), box-shadow var(--transition-fast);
+}
+
+.nav-cta:hover {
+  transform: translateY(-2px);
+  box-shadow: var(--shadow-glow-purple);
+  color: white;
+}
+
+/* ============================================
+ * HERO SECTION
+ * ============================================ */
+
+.hero {
+  padding: var(--space-24) 0;
+  text-align: center;
+  position: relative;
+  overflow: hidden;
+}
+
+.hero::before {
+  content: '';
+  position: absolute;
+  top: -50%;
+  left: -50%;
+  width: 200%;
+  height: 200%;
+  background: radial-gradient(circle at 30% 30%, var(--purple-100) 0%, transparent 50%),
+              radial-gradient(circle at 70% 70%, var(--blue-100) 0%, transparent 50%);
+  animation: hero-glow 20s ease-in-out infinite;
+  z-index: -1;
+}
+
+@media (prefers-color-scheme: dark) {
+  .hero::before {
+    background: radial-gradient(circle at 30% 30%, var(--purple-950) 0%, transparent 50%),
+                radial-gradient(circle at 70% 70%, var(--blue-950) 0%, transparent 50%);
+  }
+}
+
+@keyframes hero-glow {
+  0%, 100% { transform: translate(0, 0) rotate(0deg); }
+  25% { transform: translate(2%, 2%) rotate(1deg); }
+  50% { transform: translate(-1%, 3%) rotate(-1deg); }
+  75% { transform: translate(1%, -2%) rotate(0.5deg); }
+}
+
+.hero-badge {
+  display: inline-flex;
+  align-items: center;
+  gap: var(--space-2);
+  background: var(--color-primary-light);
+  color: var(--color-primary);
+  padding: var(--space-2) var(--space-4);
+  border-radius: var(--radius-full);
+  font-size: 0.875rem;
+  font-weight: 600;
+  margin-bottom: var(--space-6);
+}
+
+.hero h1 {
+  font-size: 4rem;
+  font-weight: 800;
+  margin-bottom: var(--space-6);
+  background: linear-gradient(135deg, var(--purple-700), var(--blue-600), var(--green-500));
+  -webkit-background-clip: text;
+  -webkit-text-fill-color: transparent;
+  background-clip: text;
+}
+
+.hero-subtitle {
+  font-size: 1.5rem;
+  color: var(--color-text-secondary);
+  max-width: 700px;
+  margin: 0 auto var(--space-8);
+  line-height: 1.5;
+}
+
+.hero-actions {
+  display: flex;
+  gap: var(--space-4);
+  justify-content: center;
+  flex-wrap: wrap;
+}
+
+/* ============================================
+ * BUTTONS
+ * ============================================ */
+
+.btn {
+  display: inline-flex;
+  align-items: center;
+  gap: var(--space-2);
+  padding: var(--space-3) var(--space-6);
+  border-radius: var(--radius-lg);
+  font-weight: 600;
+  font-size: 1rem;
+  cursor: pointer;
+  border: none;
+  transition: all var(--transition-fast);
+}
+
+.btn-primary {
+  background: linear-gradient(135deg, var(--purple-600), var(--blue-600));
+  color: white;
+  box-shadow: var(--shadow-md);
+}
+
+.btn-primary:hover {
+  transform: translateY(-2px);
+  box-shadow: var(--shadow-glow-purple);
+  color: white;
+}
+
+.btn-secondary {
+  background: var(--color-surface);
+  color: var(--color-text);
+  border: 2px solid var(--color-border);
+}
+
+.btn-secondary:hover {
+  border-color: var(--color-primary);
+  color: var(--color-primary);
+}
+
+.btn-accent {
+  background: linear-gradient(135deg, var(--green-500), var(--green-600));
+  color: white;
+}
+
+.btn-accent:hover {
+  transform: translateY(-2px);
+  box-shadow: var(--shadow-glow-green);
+  color: white;
+}
+
+/* ============================================
+ * FEATURE CARDS
+ * ============================================ */
+
+.features-grid {
+  display: grid;
+  grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
+  gap: var(--space-6);
+}
+
+.feature-card {
+  background: var(--color-surface);
+  border: 1px solid var(--color-border);
+  border-radius: var(--radius-xl);
+  padding: var(--space-8);
+  transition: all var(--transition-base);
+}
+
+.feature-card:hover {
+  transform: translateY(-4px);
+  box-shadow: var(--shadow-lg);
+  border-color: var(--color-primary);
+}
+
+.feature-icon {
+  width: 48px;
+  height: 48px;
+  border-radius: var(--radius-lg);
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  font-size: 1.5rem;
+  margin-bottom: var(--space-4);
+}
+
+.feature-icon.purple {
+  background: var(--purple-100);
+  color: var(--purple-600);
+}
+
+.feature-icon.blue {
+  background: var(--blue-100);
+  color: var(--blue-600);
+}
+
+.feature-icon.green {
+  background: var(--green-100);
+  color: var(--green-600);
+}
+
+.feature-card h3 {
+  margin-bottom: var(--space-3);
+}
+
+.feature-card p {
+  margin-bottom: 0;
+}
+
+/* ============================================
+ * ECOSYSTEM SECTION
+ * ============================================ */
+
+.ecosystem-grid {
+  display: grid;
+  grid-template-columns: repeat(auto-fit, minmax(350px, 1fr));
+  gap: var(--space-8);
+}
+
+.ecosystem-card {
+  background: var(--color-surface);
+  border: 1px solid var(--color-border);
+  border-radius: var(--radius-2xl);
+  padding: var(--space-8);
+  position: relative;
+  overflow: hidden;
+}
+
+.ecosystem-card::before {
+  content: '';
+  position: absolute;
+  top: 0;
+  left: 0;
+  right: 0;
+  height: 4px;
+}
+
+.ecosystem-card.astralis::before {
+  background: linear-gradient(90deg, var(--blue-500), var(--blue-600));
+}
+
+.ecosystem-card.inversion::before {
+  background: linear-gradient(90deg, var(--green-500), var(--green-600));
+}
+
+.ecosystem-card.spry::before {
+  background: linear-gradient(90deg, var(--purple-500), var(--purple-600));
+}
+
+.ecosystem-card h3 {
+  display: flex;
+  align-items: center;
+  gap: var(--space-3);
+  margin-bottom: var(--space-4);
+}
+
+.ecosystem-logo {
+  width: 40px;
+  height: 40px;
+  border-radius: var(--radius-md);
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  font-weight: 800;
+  color: white;
+}
+
+.ecosystem-card.astralis .ecosystem-logo {
+  background: linear-gradient(135deg, var(--blue-500), var(--blue-700));
+}
+
+.ecosystem-card.inversion .ecosystem-logo {
+  background: linear-gradient(135deg, var(--green-500), var(--green-700));
+}
+
+.ecosystem-card.spry .ecosystem-logo {
+  background: linear-gradient(135deg, var(--purple-500), var(--purple-700));
+}
+
+.ecosystem-card .tag {
+  display: inline-block;
+  background: var(--color-bg-secondary);
+  padding: var(--space-1) var(--space-3);
+  border-radius: var(--radius-full);
+  font-size: 0.75rem;
+  font-weight: 600;
+  margin-right: var(--space-2);
+  margin-bottom: var(--space-2);
+}
+
+/* ============================================
+ * STATS SECTION
+ * ============================================ */
+
+.stats-grid {
+  display: grid;
+  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
+  gap: var(--space-6);
+}
+
+.stat-card {
+  text-align: center;
+  padding: var(--space-8);
+  background: var(--color-surface);
+  border: 1px solid var(--color-border);
+  border-radius: var(--radius-xl);
+}
+
+.stat-value {
+  font-size: 3rem;
+  font-weight: 800;
+  background: linear-gradient(135deg, var(--purple-600), var(--blue-600));
+  -webkit-background-clip: text;
+  -webkit-text-fill-color: transparent;
+  background-clip: text;
+  margin-bottom: var(--space-2);
+}
+
+.stat-label {
+  color: var(--color-text-secondary);
+  font-weight: 500;
+}
+
+/* ============================================
+ * PARTICLE CANVAS DEMO
+ * ============================================ */
+
+.demo-section {
+  background: var(--color-bg-secondary);
+  border-radius: var(--radius-2xl);
+  padding: var(--space-8);
+  margin: var(--space-8) 0;
+}
+
+.demo-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: var(--space-6);
+  flex-wrap: wrap;
+  gap: var(--space-4);
+}
+
+.demo-controls {
+  display: flex;
+  gap: var(--space-3);
+  flex-wrap: wrap;
+}
+
+.demo-btn {
+  padding: var(--space-2) var(--space-4);
+  border-radius: var(--radius-md);
+  border: 1px solid var(--color-border);
+  background: var(--color-surface);
+  color: var(--color-text);
+  font-weight: 500;
+  cursor: pointer;
+  transition: all var(--transition-fast);
+}
+
+.demo-btn:hover {
+  border-color: var(--color-primary);
+  color: var(--color-primary);
+}
+
+.demo-btn.active {
+  background: var(--color-primary);
+  color: white;
+  border-color: var(--color-primary);
+}
+
+.particle-canvas {
+  width: 100%;
+  height: 400px;
+  background: var(--gray-900);
+  border-radius: var(--radius-xl);
+  position: relative;
+  overflow: hidden;
+}
+
+/* ============================================
+ * AURORA CANVAS DEMO
+ * ============================================ */
+
+.aurora-canvas {
+  width: 100%;
+  height: 400px;
+  background: linear-gradient(180deg, #050510 0%, #0a0a20 50%, #0f0f2a 100%);
+  border-radius: var(--radius-xl);
+  position: relative;
+  overflow: hidden;
+  box-shadow: inset 0 0 100px rgba(124, 58, 237, 0.1);
+}
+
+/* Starfield background */
+.aurora-canvas::before {
+  content: '';
+  position: absolute;
+  inset: 0;
+  background-image: 
+    radial-gradient(1px 1px at 20% 30%, rgba(255,255,255,0.8), transparent),
+    radial-gradient(1px 1px at 40% 70%, rgba(255,255,255,0.6), transparent),
+    radial-gradient(1.5px 1.5px at 60% 20%, rgba(255,255,255,0.9), transparent),
+    radial-gradient(1px 1px at 80% 50%, rgba(255,255,255,0.5), transparent),
+    radial-gradient(1px 1px at 10% 80%, rgba(255,255,255,0.7), transparent),
+    radial-gradient(1.5px 1.5px at 90% 10%, rgba(255,255,255,0.8), transparent),
+    radial-gradient(1px 1px at 30% 50%, rgba(255,255,255,0.4), transparent),
+    radial-gradient(1px 1px at 70% 90%, rgba(255,255,255,0.6), transparent);
+  background-size: 200px 200px;
+  animation: twinkle 4s ease-in-out infinite alternate;
+  z-index: 0;
+}
+
+@keyframes twinkle {
+  0% { opacity: 0.5; }
+  100% { opacity: 1; }
+}
+
+.aurora-wave {
+  position: absolute;
+  left: -20%;
+  right: -20%;
+  height: 80px;
+  top: var(--wave-y, 30%);
+  background: linear-gradient(90deg, 
+    transparent 0%, 
+    var(--wave-color1, #22c55e) 20%, 
+    var(--wave-color2, #7c3aed) 50%, 
+    var(--wave-color1, #22c55e) 80%, 
+    transparent 100%
+  );
+  opacity: var(--wave-opacity, 0.5);
+  filter: blur(25px);
+  z-index: 1;
+  /* Subtle pulsing animation only - no dramatic movement */
+  animation: aurora-pulse 4s ease-in-out infinite;
+  animation-delay: var(--wave-delay, 0s);
+  /* Smooth transitions for server-side parameter changes */
+  transition: top 2s ease-in-out, opacity 2s ease-in-out;
+}
+
+/* Gentle pulsing - just opacity and slight scale */
+@keyframes aurora-pulse {
+  0%, 100% {
+    opacity: var(--wave-opacity, 0.5);
+    filter: blur(25px);
+  }
+  50% {
+    opacity: calc(var(--wave-opacity, 0.5) * 1.3);
+    filter: blur(30px);
+  }
+}
+
+/* Glow effect for waves */
+.aurora-wave::after {
+  content: '';
+  position: absolute;
+  inset: -50%;
+  background: inherit;
+  filter: blur(40px);
+  opacity: 0.5;
+  animation: aurora-glow 4s ease-in-out infinite alternate;
+  animation-delay: var(--wave-delay, 0s);
+}
+
+@keyframes aurora-glow {
+  0% { opacity: 0.3; filter: blur(40px); }
+  100% { opacity: 0.6; filter: blur(60px); }
+}
+
+.aurora-stats {
+  display: grid;
+  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
+  gap: var(--space-4);
+  margin-top: var(--space-6);
+  padding: var(--space-4);
+  background: var(--color-surface);
+  border-radius: var(--radius-lg);
+}
+
+.aurora-stat {
+  text-align: center;
+}
+
+.aurora-stat-value {
+  font-size: 1.5rem;
+  font-weight: 700;
+  color: var(--color-primary);
+}
+
+.aurora-stat-label {
+  font-size: 0.75rem;
+  color: var(--color-text-muted);
+  text-transform: uppercase;
+  letter-spacing: 0.05em;
+}
+
+.particle-info {
+  display: grid;
+  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
+  gap: var(--space-4);
+  margin-top: var(--space-6);
+  padding: var(--space-4);
+  background: var(--color-surface);
+  border-radius: var(--radius-lg);
+}
+
+.particle-stat {
+  text-align: center;
+}
+
+.particle-stat-value {
+  font-size: 1.5rem;
+  font-weight: 700;
+  color: var(--color-primary);
+}
+
+.particle-stat-label {
+  font-size: 0.75rem;
+  color: var(--color-text-muted);
+  text-transform: uppercase;
+  letter-spacing: 0.05em;
+}
+
+/* ============================================
+ * FREEDOM SECTION
+ * ============================================ */
+
+.freedom-section {
+  background: linear-gradient(135deg, var(--green-50), var(--blue-50));
+  border-radius: var(--radius-2xl);
+  padding: var(--space-16);
+  text-align: center;
+}
+
+@media (prefers-color-scheme: dark) {
+  .freedom-section {
+    background: linear-gradient(135deg, var(--green-950), var(--blue-950));
+  }
+}
+
+.freedom-icon {
+  font-size: 4rem;
+  margin-bottom: var(--space-6);
+}
+
+.freedom-section h2 {
+  margin-bottom: var(--space-4);
+}
+
+.freedom-section p {
+  max-width: 700px;
+  margin: 0 auto var(--space-8);
+  font-size: 1.125rem;
+}
+
+.freedom-values {
+  display: flex;
+  justify-content: center;
+  gap: var(--space-8);
+  flex-wrap: wrap;
+  margin-bottom: var(--space-8);
+}
+
+.freedom-value {
+  display: flex;
+  align-items: center;
+  gap: var(--space-2);
+  font-weight: 600;
+}
+
+.freedom-value svg {
+  color: var(--green-500);
+}
+
+/* ============================================
+ * CODE BLOCKS
+ * ============================================ */
+
+.code-block {
+  background: var(--gray-900);
+  border-radius: var(--radius-xl);
+  overflow: hidden;
+  margin: var(--space-6) 0;
+}
+
+.code-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding: var(--space-3) var(--space-4);
+  background: var(--gray-800);
+  border-bottom: 1px solid var(--gray-700);
+}
+
+.code-lang {
+  font-size: 0.75rem;
+  font-weight: 600;
+  color: var(--gray-400);
+  text-transform: uppercase;
+}
+
+.code-dots {
+  display: flex;
+  gap: var(--space-2);
+}
+
+.code-dot {
+  width: 12px;
+  height: 12px;
+  border-radius: 50%;
+}
+
+.code-dot.red { background: #ff5f56; }
+.code-dot.yellow { background: #ffbd2e; }
+.code-dot.green { background: #27c93f; }
+
+.code-block pre {
+  margin: 0;
+  border-radius: 0;
+}
+
+/* Syntax highlighting */
+.code-keyword { color: #c792ea; }
+.code-type { color: #82aaff; }
+.code-string { color: #c3e88d; }
+.code-number { color: #f78c6c; }
+.code-comment { color: #546e7a; font-style: italic; }
+.code-function { color: #82aaff; }
+.code-operator { color: #89ddff; }
+
+/* ============================================
+ * FOOTER
+ * ============================================ */
+
+.site-footer {
+  background: var(--color-bg-secondary);
+  border-top: 1px solid var(--color-border);
+  padding: var(--space-12) 0;
+  margin-top: var(--space-24);
+}
+
+.footer-content {
+  display: grid;
+  grid-template-columns: 2fr 1fr 1fr 1fr;
+  gap: var(--space-8);
+}
+
+@media (max-width: 768px) {
+  .footer-content {
+    grid-template-columns: 1fr;
+    text-align: center;
+  }
+}
+
+.footer-brand {
+  max-width: 300px;
+}
+
+@media (max-width: 768px) {
+  .footer-brand {
+    max-width: none;
+  }
+}
+
+.footer-brand .logo {
+  margin-bottom: var(--space-4);
+}
+
+@media (max-width: 768px) {
+  .footer-brand .logo {
+    justify-content: center;
+  }
+}
+
+.footer-brand p {
+  font-size: 0.875rem;
+}
+
+.footer-section h4 {
+  margin-bottom: var(--space-4);
+  font-size: 0.875rem;
+  text-transform: uppercase;
+  letter-spacing: 0.05em;
+  color: var(--color-text-muted);
+}
+
+.footer-links {
+  list-style: none;
+}
+
+.footer-links li {
+  margin-bottom: var(--space-2);
+}
+
+.footer-links a {
+  color: var(--color-text-secondary);
+  font-size: 0.875rem;
+}
+
+.footer-links a:hover {
+  color: var(--color-primary);
+}
+
+.footer-bottom {
+  margin-top: var(--space-8);
+  padding-top: var(--space-8);
+  border-top: 1px solid var(--color-border);
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  flex-wrap: wrap;
+  gap: var(--space-4);
+}
+
+@media (max-width: 768px) {
+  .footer-bottom {
+    justify-content: center;
+    text-align: center;
+  }
+}
+
+.footer-bottom p {
+  font-size: 0.875rem;
+  margin: 0;
+}
+
+.footer-social {
+  display: flex;
+  gap: var(--space-4);
+}
+
+.footer-social a {
+  color: var(--color-text-muted);
+  transition: color var(--transition-fast);
+}
+
+.footer-social a:hover {
+  color: var(--color-primary);
+}
+
+/* ============================================
+ * RESPONSIVE
+ * ============================================ */
+
+@media (max-width: 768px) {
+  h1 { font-size: 2.5rem; }
+  h2 { font-size: 1.75rem; }
+  
+  .hero h1 { font-size: 2.5rem; }
+  .hero-subtitle { font-size: 1.125rem; }
+  
+  .nav-links { display: none; }
+  
+  .features-grid,
+  .ecosystem-grid {
+    grid-template-columns: 1fr;
+  }
+  
+  .footer-content {
+    grid-template-columns: 1fr;
+  }
+}
+
+/* ============================================
+ * VIEW TRANSITIONS (for smooth HTMX swaps)
+ * ============================================ */
+
+/* View Transition API styles for cross-fade */
+::view-transition-old(root),
+::view-transition-new(root) {
+  animation-duration: 0.3s;
+}
+
+/* Smooth wave entrance when added via HTMX */
+.aurora-wave.htmx-added {
+  animation: aurora-pulse 4s ease-in-out infinite;
+}
+
+/* ============================================
+ * ANIMATIONS
+ * ============================================ */
+
+@keyframes fade-in {
+  from { opacity: 0; transform: translateY(20px); }
+  to { opacity: 1; transform: translateY(0); }
+}
+
+@keyframes pulse {
+  0%, 100% { opacity: 1; }
+  50% { opacity: 0.5; }
+}
+
+@keyframes float {
+  0%, 100% { transform: translateY(0); }
+  50% { transform: translateY(-10px); }
+}
+
+.animate-fade-in {
+  animation: fade-in 0.6s ease-out;
+}
+
+.animate-pulse {
+  animation: pulse 2s ease-in-out infinite;
+}
+
+.animate-float {
+  animation: float 3s ease-in-out infinite;
+}
+
+/* ============================================
+ * UTILITY CLASSES
+ * ============================================ */
+
+.text-center { text-align: center; }
+.text-left { text-align: left; }
+.text-right { text-align: right; }
+
+.text-primary { color: var(--color-primary); }
+.text-secondary { color: var(--color-text-secondary); }
+.text-muted { color: var(--color-text-muted); }
+.text-accent { color: var(--color-accent); }
+
+.font-bold { font-weight: 700; }
+.font-semibold { font-weight: 600; }
+
+.mt-4 { margin-top: var(--space-4); }
+.mb-4 { margin-bottom: var(--space-4); }
+.my-8 { margin-top: var(--space-8); margin-bottom: var(--space-8); }
+
+.hidden { display: none; }
+.flex { display: flex; }
+.grid { display: grid; }
+
+.items-center { align-items: center; }
+.justify-center { justify-content: center; }
+.justify-between { justify-content: space-between; }
+
+.gap-4 { gap: var(--space-4); }
+.gap-6 { gap: var(--space-6); }
+.gap-8 { gap: var(--space-8); }
+""";
+}

+ 94 - 0
website/Templates/SiteLayoutTemplate.vala

@@ -0,0 +1,94 @@
+using Astralis;
+using Spry;
+
+/**
+ * SiteLayoutTemplate - The base template for all pages
+ * 
+ * Provides:
+ * - HTML document structure
+ * - Common <head> elements (scripts, styles)
+ * - Site-wide header with navigation
+ * - Site-wide footer
+ */
+public class SiteLayoutTemplate : PageTemplate {
+    
+    public override string markup { get {
+        return """
+        <!DOCTYPE html>
+        <html lang="en">
+        <head>
+            <meta charset="UTF-8">
+            <meta name="viewport" content="width=device-width, initial-scale=1.0">
+            <title>Spry Framework - Modern Web Development in Vala</title>
+            <meta name="description" content="Spry is a component-based web framework for Vala, featuring HTMX integration, dependency injection, and reactive templates.">
+            <link rel="stylesheet" href="/styles/main.css">
+            <script spry-res="htmx.js"></script>
+        </head>
+        <body>
+            <header class="site-header">
+                <div class="header-content">
+                    <a href="/" class="logo">
+                        <div class="logo-icon">⚡</div>
+                        Spry
+                    </a>
+                    <nav>
+                        <ul class="nav-links">
+                            <li><a href="/features">Features</a></li>
+                            <li><a href="/ecosystem">Ecosystem</a></li>
+                            <li><a href="/demo">Demo</a></li>
+                            <li><a href="/freedom">Freedom</a></li>
+                        </ul>
+                    </nav>
+                    <a href="/demo" class="nav-cta">Try Demo</a>
+                </div>
+            </header>
+            <main>
+                <spry-template-outlet />
+            </main>
+            <footer class="site-footer">
+                <div class="container">
+                    <div class="footer-content">
+                        <div class="footer-brand">
+                            <a href="/" class="logo">
+                                <div class="logo-icon">⚡</div>
+                                Spry
+                            </a>
+                            <p>A modern, component-based web framework for Vala. Build reactive, real-time web applications with ease.</p>
+                        </div>
+                        <div class="footer-section">
+                            <h4>Framework</h4>
+                            <ul class="footer-links">
+                                <li><a href="/features">Features</a></li>
+                                <li><a href="/ecosystem">Ecosystem</a></li>
+                                <li><a href="/demo">Live Demo</a></li>
+                            </ul>
+                        </div>
+                        <div class="footer-section">
+                            <h4>Ecosystem</h4>
+                            <ul class="footer-links">
+                                <li><a href="/ecosystem">Astralis</a></li>
+                                <li><a href="/ecosystem">Inversion</a></li>
+                                <li><a href="/ecosystem">Spry</a></li>
+                            </ul>
+                        </div>
+                        <div class="footer-section">
+                            <h4>Community</h4>
+                            <ul class="footer-links">
+                                <li><a href="/freedom">Free Software</a></li>
+                                <li><a href="https://github.com" rel="external">GitHub</a></li>
+                            </ul>
+                        </div>
+                    </div>
+                    <div class="footer-bottom">
+                        <p>© 2024 Spry Framework. Free and Open Source Software.</p>
+                        <div class="footer-social">
+                            <a href="https://github.com" title="GitHub">⌨</a>
+                        </div>
+                    </div>
+                </div>
+            </footer>
+        </body>
+        </html>
+        """;
+    }}
+}

+ 28 - 0
website/meson.build

@@ -0,0 +1,28 @@
+# Spry Framework Website
+# A modern, techy website showcasing the Spry framework and its ecosystem
+
+website_sources = files(
+    'Main.vala',
+    'Styles.vala',
+    'Templates/SiteLayoutTemplate.vala',
+    'Pages/HomePage.vala',
+    'Pages/FeaturesPage.vala',
+    'Pages/EcosystemPage.vala',
+    'Pages/DemoPage.vala',
+    'Pages/FreedomPage.vala',
+    'Components/FeatureCardComponent.vala',
+    'Components/AuroraWaveComponent.vala',
+    'Components/CodeBlockComponent.vala',
+    'Components/StatCardComponent.vala',
+    'Endpoints/AuroraCanvasEndpoint.vala',
+    'Endpoints/AuroraStatsEndpoint.vala',
+)
+
+# Math library for particle physics (sin/cos in explode function)
+m_dep = meson.get_compiler('c').find_library('m', required: false)
+
+executable('spry-website',
+    website_sources,
+    dependencies: [spry_dep, astralis_dep, invercargill_dep, inversion_dep, m_dep],
+    install: false
+)