DemoPage.vala 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374
  1. using Astralis;
  2. using Invercargill;
  3. using Invercargill.DataStructures;
  4. using Inversion;
  5. using Spry;
  6. /**
  7. * DemoPage - Interactive demo page
  8. *
  9. * Features a live Aurora Borealis simulation that polls the server
  10. * every 5 seconds for new wave/color parameters
  11. */
  12. public class DemoPage : PageComponent {
  13. public const string ROUTE = "/demo";
  14. private AuroraState aurora_state = inject<AuroraState>();
  15. private ComponentFactory factory = inject<ComponentFactory>();
  16. public override string markup { get {
  17. return """
  18. <div sid="demo-page" class="section">
  19. <div class="container">
  20. <!-- Header -->
  21. <div class="section-header">
  22. <h1>Interactive Demo</h1>
  23. <p>Experience the power of Spry with a live, server-backed Aurora simulation</p>
  24. </div>
  25. <!-- Aurora Demo -->
  26. <section class="demo-section">
  27. <div class="demo-header">
  28. <h2>🌌 Live Aurora Borealis</h2>
  29. <p style="color: var(--color-text-muted); margin-bottom: var(--space-4);">
  30. This aurora is generated server-side! It polls for new parameters every 5 seconds.
  31. </p>
  32. <div class="demo-controls">
  33. <button class="demo-btn" spry-action=":BoostSolarWind" spry-target="demo-page" hx-swap="outerHTML">
  34. 🌞 Boost Solar Wind
  35. </button>
  36. <button class="demo-btn" spry-action=":CalmAurora" spry-target="demo-page" hx-swap="outerHTML">
  37. 🌙 Calm Aurora
  38. </button>
  39. <button class="demo-btn" spry-action=":ShiftColors" spry-target="demo-page" hx-swap="outerHTML">
  40. 🎨 Shift Colors
  41. </button>
  42. <button class="demo-btn" spry-action=":AddWave" spry-target="demo-page" hx-swap="outerHTML">
  43. 🌊 Add Wave
  44. </button>
  45. </div>
  46. </div>
  47. <!-- Aurora Canvas with auto-refresh -->
  48. <div class="aurora-canvas" sid="aurora-canvas"
  49. spry-action="AuroraCanvasEndpoint:Poll"
  50. hx-trigger="every 5s"
  51. hx-swap="innerHTML transition:true">
  52. <spry-outlet sid="aurora-waves"/>
  53. </div>
  54. <!-- Aurora Stats -->
  55. <div class="aurora-stats" sid="aurora-stats"
  56. spry-action="AuroraStatsEndpoint:Poll"
  57. hx-trigger="every 5s"
  58. hx-swap="outerHTML transition:true">
  59. <div class="aurora-stat">
  60. <div class="aurora-stat-value" sid="solar-wind"></div>
  61. <div class="aurora-stat-label">Solar Wind</div>
  62. </div>
  63. <div class="aurora-stat">
  64. <div class="aurora-stat-value" sid="wave-count"></div>
  65. <div class="aurora-stat-label">Waves</div>
  66. </div>
  67. <div class="aurora-stat">
  68. <div class="aurora-stat-value" sid="intensity"></div>
  69. <div class="aurora-stat-label">Intensity</div>
  70. </div>
  71. <div class="aurora-stat">
  72. <div class="aurora-stat-value" sid="color-mode"></div>
  73. <div class="aurora-stat-label">Color Mode</div>
  74. </div>
  75. </div>
  76. <p style="margin-top: var(--space-4); text-align: center; color: var(--color-text-muted);">
  77. The aurora updates every 5 seconds via HTMX polling. Try the controls above!
  78. </p>
  79. </section>
  80. <!-- Counter Demo -->
  81. <section class="demo-section">
  82. <h2>🔢 Simple Counter</h2>
  83. <p>A minimal example of Spry component state and actions.</p>
  84. <div style="display: flex; align-items: center; gap: var(--space-4); justify-content: center; margin: var(--space-6) 0;">
  85. <div class="stat-card" style="text-align: center; min-width: 200px;">
  86. <div class="stat-value" sid="counter-value"></div>
  87. <div class="stat-label">Current Count</div>
  88. </div>
  89. </div>
  90. <div style="display: flex; gap: var(--space-3); justify-content: center;">
  91. <button class="demo-btn" spry-action=":Decrement" spry-target="demo-page" hx-swap="outerHTML">
  92. − Decrement
  93. </button>
  94. <button class="demo-btn" spry-action=":Reset" spry-target="demo-page" hx-swap="outerHTML">
  95. ↺ Reset
  96. </button>
  97. <button class="demo-btn" spry-action=":Increment" spry-target="demo-page" hx-swap="outerHTML">
  98. + Increment
  99. </button>
  100. </div>
  101. </section>
  102. <!-- How It Works -->
  103. <section class="demo-section">
  104. <h2>⚙ How It Works</h2>
  105. <p>This demo showcases several Spry features working together:</p>
  106. <div class="features-grid" style="margin-top: var(--space-6);">
  107. <div class="feature-card">
  108. <div class="feature-icon purple">🔄</div>
  109. <h3>Server Polling</h3>
  110. <p>hx-trigger="every 5s" fetches fresh aurora data from the server periodically.</p>
  111. </div>
  112. <div class="feature-card">
  113. <div class="feature-icon blue">⚡</div>
  114. <h3>Shared State</h3>
  115. <p>AuroraState is a singleton - all visitors see the same aurora!</p>
  116. </div>
  117. <div class="feature-card">
  118. <div class="feature-icon green">🎨</div>
  119. <h3>CSS Gradients</h3>
  120. <p>Server generates CSS gradient strips for beautiful aurora waves.</p>
  121. </div>
  122. </div>
  123. <div class="code-block" style="margin-top: var(--space-6);">
  124. <div class="code-header">
  125. <div class="code-dots">
  126. <div class="code-dot red"></div>
  127. <div class="code-dot yellow"></div>
  128. <div class="code-dot green"></div>
  129. </div>
  130. <span class="code-lang">HTML</span>
  131. </div>
  132. <pre><code>&lt;!-- Auto-refreshing aurora canvas --&gt;
  133. &lt;div hx-get="/demo/aurora"
  134. hx-trigger="every 5s"
  135. hx-swap="innerHTML"&gt;
  136. &lt;!-- Aurora waves render here --&gt;
  137. &lt;/div&gt;
  138. &lt;!-- Control buttons --&gt;
  139. &lt;button spry-action=":BoostSolarWind"
  140. spry-target="demo-page"
  141. hx-swap="outerHTML"&gt;
  142. Boost Solar Wind
  143. &lt;/button&gt;</code></pre>
  144. </div>
  145. </section>
  146. <!-- CTA -->
  147. <div class="text-center" style="margin-top: var(--space-12);">
  148. <h2>Want to Learn More?</h2>
  149. <p style="margin-bottom: var(--space-6);">Explore the features that make Spry powerful.</p>
  150. <div class="hero-actions" style="justify-content: center;">
  151. <a href="/features" class="btn btn-primary">View Features</a>
  152. <a href="/ecosystem" class="btn btn-secondary">See Ecosystem</a>
  153. </div>
  154. </div>
  155. </div>
  156. </div>
  157. """;
  158. }}
  159. public override async void prepare() throws Error {
  160. // Render aurora waves
  161. var waves = new Series<Renderable>();
  162. int wave_num = 0;
  163. foreach (var wave in aurora_state.get_waves()) {
  164. var component = factory.create<AuroraWaveComponent>();
  165. component.wave_id = wave_num;
  166. component.y_offset = wave.y_offset;
  167. component.amplitude = wave.amplitude;
  168. component.frequency = wave.frequency;
  169. component.color1 = wave.color1;
  170. component.color2 = wave.color2;
  171. component.opacity = wave.opacity;
  172. component.animation_delay = wave.animation_delay;
  173. waves.add(component);
  174. wave_num++;
  175. }
  176. set_outlet_children("aurora-waves", waves);
  177. // Update stats
  178. this["solar-wind"].text_content = "%.1f km/s".printf(aurora_state.solar_wind_speed);
  179. this["wave-count"].text_content = aurora_state.wave_count.to_string();
  180. this["intensity"].text_content = "%.0f%%".printf(aurora_state.intensity * 100);
  181. this["color-mode"].text_content = aurora_state.color_mode;
  182. // Update counter
  183. this["counter-value"].text_content = aurora_state.counter.to_string();
  184. }
  185. public async override void handle_action(string action) throws Error {
  186. switch (action) {
  187. case "BoostSolarWind":
  188. aurora_state.boost_solar_wind();
  189. break;
  190. case "CalmAurora":
  191. aurora_state.calm();
  192. break;
  193. case "ShiftColors":
  194. aurora_state.shift_colors();
  195. break;
  196. case "AddWave":
  197. aurora_state.add_wave();
  198. break;
  199. case "Increment":
  200. aurora_state.counter++;
  201. break;
  202. case "Decrement":
  203. aurora_state.counter--;
  204. break;
  205. case "Reset":
  206. aurora_state.counter = 0;
  207. break;
  208. }
  209. }
  210. }
  211. /**
  212. * AuroraWaveData - Data for a single aurora wave
  213. */
  214. public class AuroraWaveData : Object {
  215. public double y_offset { get; set; }
  216. public double amplitude { get; set; }
  217. public double frequency { get; set; }
  218. public string color1 { get; set; default = "#7c3aed"; }
  219. public string color2 { get; set; default = "#22c55e"; }
  220. public double opacity { get; set; default = 0.6; }
  221. public double animation_delay { get; set; default = 0; }
  222. }
  223. /**
  224. * ColorPalette - A pair of colors for aurora waves
  225. */
  226. public class ColorPalette : Object {
  227. public string color1 { get; construct set; }
  228. public string color2 { get; construct set; }
  229. public string name { get; construct set; }
  230. public ColorPalette(string color1, string color2, string name) {
  231. Object(color1: color1, color2: color2, name: name);
  232. }
  233. }
  234. /**
  235. * AuroraState - Singleton state for the aurora simulation
  236. */
  237. public class AuroraState : Object {
  238. private GenericArray<AuroraWaveData> waves = new GenericArray<AuroraWaveData>();
  239. private Series<ColorPalette> color_palettes;
  240. public double solar_wind_speed { get; set; default = 400.0; }
  241. public double intensity { get; set; default = 0.5; }
  242. public string color_mode { get; set; default = "Green"; }
  243. public int counter { get; set; default = 0; }
  244. private int current_palette = 0;
  245. public int wave_count {
  246. get { return (int)waves.length; }
  247. }
  248. construct {
  249. // Initialize color palettes using Series
  250. color_palettes = new Series<ColorPalette>();
  251. color_palettes.add(new ColorPalette("#22c55e", "#16a34a", "Green"));
  252. color_palettes.add(new ColorPalette("#7c3aed", "#a855f7", "Purple"));
  253. color_palettes.add(new ColorPalette("#2563eb", "#3b82f6", "Blue"));
  254. color_palettes.add(new ColorPalette("#22c55e", "#7c3aed", "Mixed"));
  255. color_palettes.add(new ColorPalette("#f59e0b", "#ef4444", "Fire"));
  256. color_palettes.add(new ColorPalette("#06b6d4", "#22c55e", "Ocean"));
  257. // Start with some waves
  258. add_wave();
  259. add_wave();
  260. add_wave();
  261. }
  262. private ColorPalette get_current_palette() {
  263. uint idx = (uint)current_palette;
  264. return color_palettes.element_at_or_default(idx);
  265. }
  266. public void add_wave() {
  267. var palette = get_current_palette();
  268. var wave = new AuroraWaveData() {
  269. y_offset = 20 + Random.double_range(0, 40),
  270. amplitude = 10 + Random.double_range(0, 20),
  271. frequency = 0.5 + Random.double_range(0, 1.5),
  272. color1 = palette.color1,
  273. color2 = palette.color2,
  274. opacity = 0.3 + Random.double_range(0, 0.5),
  275. animation_delay = Random.double_range(0, 3)
  276. };
  277. waves.add(wave);
  278. // Keep max 8 waves
  279. if (waves.length > 8) {
  280. waves.remove_index(0);
  281. }
  282. }
  283. public void boost_solar_wind() {
  284. solar_wind_speed = 600 + Random.double_range(0, 400);
  285. intensity = 0.8 + Random.double_range(0, 0.2);
  286. // Increase all wave amplitudes
  287. for (int i = 0; i < waves.length; i++) {
  288. var w = waves.get(i);
  289. w.amplitude *= 1.3;
  290. w.opacity = double.min(0.9, w.opacity + 0.1);
  291. }
  292. }
  293. public void calm() {
  294. solar_wind_speed = 300 + Random.double_range(0, 100);
  295. intensity = 0.3 + Random.double_range(0, 0.2);
  296. // Decrease all wave amplitudes
  297. for (int i = 0; i < waves.length; i++) {
  298. var w = waves.get(i);
  299. w.amplitude *= 0.7;
  300. w.opacity = double.max(0.2, w.opacity - 0.1);
  301. }
  302. }
  303. public void shift_colors() {
  304. current_palette = (current_palette + 1) % 6;
  305. var palette = get_current_palette();
  306. // Update color mode name
  307. color_mode = palette.name;
  308. // Update all wave colors
  309. for (int i = 0; i < waves.length; i++) {
  310. var w = waves.get(i);
  311. w.color1 = palette.color1;
  312. w.color2 = palette.color2;
  313. }
  314. }
  315. public void evolve() {
  316. // Called periodically to naturally evolve the aurora
  317. solar_wind_speed = 300 + Random.double_range(0, 300);
  318. intensity = 0.4 + Random.double_range(0, 0.4);
  319. // Slightly modify waves
  320. for (int i = 0; i < waves.length; i++) {
  321. var w = waves.get(i);
  322. w.y_offset += Random.double_range(-5, 5);
  323. w.y_offset = double.max(10, double.min(70, w.y_offset));
  324. w.amplitude += Random.double_range(-3, 3);
  325. w.amplitude = double.max(5, double.min(40, w.amplitude));
  326. }
  327. }
  328. public GenericArray<AuroraWaveData> get_waves() {
  329. return waves;
  330. }
  331. }