fun_ci 1.0.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +34 -0
  3. data/README.md +3 -1
  4. data/exe/fun-ci-trigger +6 -6
  5. data/exe/fun-ci-tui +9 -4
  6. data/lib/fun_ci/animations/celebrate.rb +210 -0
  7. data/lib/fun_ci/animations/explosion.rb +138 -0
  8. data/lib/fun_ci/animations/flash.rb +205 -0
  9. data/lib/fun_ci/animations/idle.rb +127 -0
  10. data/lib/fun_ci/animations/leprechauns.rb +240 -0
  11. data/lib/fun_ci/animations/running.rb +129 -0
  12. data/lib/fun_ci/animations/success.rb +281 -0
  13. data/lib/fun_ci/animations/yay.rb +169 -0
  14. data/lib/fun_ci/cli.rb +20 -14
  15. data/lib/fun_ci/persistence/database.rb +52 -0
  16. data/lib/fun_ci/persistence/pipeline_recorder.rb +74 -0
  17. data/lib/fun_ci/persistence/pipeline_run.rb +59 -0
  18. data/lib/fun_ci/persistence/stage_job.rb +46 -0
  19. data/lib/fun_ci/{state_machine.rb → persistence/state_machine.rb} +3 -12
  20. data/lib/fun_ci/pipeline/background_wrapper.rb +27 -0
  21. data/lib/fun_ci/pipeline/pipeline_forker.rb +38 -0
  22. data/lib/fun_ci/pipeline/process_runner.rb +28 -0
  23. data/lib/fun_ci/pipeline/progress_reporter.rb +31 -0
  24. data/lib/fun_ci/pipeline/stage_runner.rb +85 -0
  25. data/lib/fun_ci/pipeline/stale_pipeline_canceller.rb +53 -0
  26. data/lib/fun_ci/pipeline/trigger.rb +153 -0
  27. data/lib/fun_ci/setup/hook_writer.rb +75 -0
  28. data/lib/fun_ci/setup/installer.rb +55 -0
  29. data/lib/fun_ci/setup/maven_linter_detector.rb +26 -0
  30. data/lib/fun_ci/setup/project_config.rb +42 -0
  31. data/lib/fun_ci/setup/project_detector.rb +22 -0
  32. data/lib/fun_ci/setup/setup_checker.rb +30 -0
  33. data/lib/fun_ci/setup/template_writer.rb +53 -0
  34. data/lib/fun_ci/tui/admin_tui.rb +90 -0
  35. data/lib/fun_ci/tui/animation.rb +49 -0
  36. data/lib/fun_ci/tui/animation_compositor.rb +107 -0
  37. data/lib/fun_ci/tui/animation_frames.rb +112 -0
  38. data/lib/fun_ci/tui/animation_library.rb +46 -0
  39. data/lib/fun_ci/tui/animation_renderer.rb +144 -0
  40. data/lib/fun_ci/tui/ansi.rb +34 -0
  41. data/lib/fun_ci/tui/board_data.rb +53 -0
  42. data/lib/fun_ci/tui/board_renderer.rb +105 -0
  43. data/lib/fun_ci/tui/duration_formatter.rb +24 -0
  44. data/lib/fun_ci/tui/header_animation_manager.rb +71 -0
  45. data/lib/fun_ci/tui/header_animation_player.rb +45 -0
  46. data/lib/fun_ci/tui/key_handler.rb +86 -0
  47. data/lib/fun_ci/tui/looping_animation_player.rb +45 -0
  48. data/lib/fun_ci/tui/relative_time.rb +22 -0
  49. data/lib/fun_ci/tui/row_formatter.rb +108 -0
  50. data/lib/fun_ci/tui/screen.rb +103 -0
  51. data/lib/fun_ci/tui/spinner.rb +24 -0
  52. data/lib/fun_ci/tui/stage_change_detector.rb +58 -0
  53. data/lib/fun_ci/tui/streak_counter.rb +29 -0
  54. data/lib/fun_ci/tui/terminal_input.rb +69 -0
  55. data/lib/fun_ci.rb +6 -6
  56. metadata +49 -28
  57. data/lib/fun_ci/admin_tui.rb +0 -226
  58. data/lib/fun_ci/ansi.rb +0 -21
  59. data/lib/fun_ci/background_wrapper.rb +0 -27
  60. data/lib/fun_ci/board_data.rb +0 -51
  61. data/lib/fun_ci/database.rb +0 -50
  62. data/lib/fun_ci/duration_formatter.rb +0 -23
  63. data/lib/fun_ci/hook_writer.rb +0 -73
  64. data/lib/fun_ci/installer.rb +0 -53
  65. data/lib/fun_ci/maven_linter_detector.rb +0 -24
  66. data/lib/fun_ci/pipeline_forker.rb +0 -36
  67. data/lib/fun_ci/pipeline_recorder.rb +0 -72
  68. data/lib/fun_ci/pipeline_run.rb +0 -57
  69. data/lib/fun_ci/progress_reporter.rb +0 -29
  70. data/lib/fun_ci/project_config.rb +0 -40
  71. data/lib/fun_ci/project_detector.rb +0 -18
  72. data/lib/fun_ci/relative_time.rb +0 -20
  73. data/lib/fun_ci/row_formatter.rb +0 -106
  74. data/lib/fun_ci/screen.rb +0 -81
  75. data/lib/fun_ci/setup_checker.rb +0 -28
  76. data/lib/fun_ci/spinner.rb +0 -22
  77. data/lib/fun_ci/stage_job.rb +0 -44
  78. data/lib/fun_ci/stage_runner.rb +0 -108
  79. data/lib/fun_ci/stale_pipeline_canceller.rb +0 -51
  80. data/lib/fun_ci/streak_counter.rb +0 -30
  81. data/lib/fun_ci/template_writer.rb +0 -51
  82. data/lib/fun_ci/trigger.rb +0 -150
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bacc0733c3648d5b5422304a7c319be52f1531e98f1c802e4a3d2a5280928ba9
4
- data.tar.gz: '097d7eaea4e663259c0b745724e6a4d281af1aa60b0ebb3bd78a14c8add5bb57'
3
+ metadata.gz: 8de275f366537fcf143b9dccdd10002a9cbff42632ab7e2549b695b4a3ba6240
4
+ data.tar.gz: 8723aee6ea508e2f9bf44c59cb717a83a08dc60381f389550f714b0b0f0de722
5
5
  SHA512:
6
- metadata.gz: feb14f7ff5ba1b67c66b3aa9cddeb9a160320aab302abf9aa60f3cb58b88395e0493abd50dd44db2ddd4030680d0a3c77e99f50cd7408309fe7e3cec9f692163
7
- data.tar.gz: 8c3cf91d027932fbb998bb3f341cfced2752de43d926a10310f1900ce672de041f75b830379c665b3286daf93bb0c0b0364af47ba1dd116af8c1cc393481ea85
6
+ metadata.gz: f4122b18c73b6434eedbe7124fa19b79c64b03fce502a34fdc978aa0d97006091477495d6d9ccfe1cb3ca8eba8be0fa9d53341d328a3bd20ecff43e00c482ddc
7
+ data.tar.gz: fde4e5346d6bb6038ad35137456a6b1188a61c16f75973e9a55e44b73bc751afed551e1ea063a6a58a38adfbde4eefc1919297d870810ec8ddadfabd6106ce38
data/CHANGELOG.md CHANGED
@@ -5,6 +5,40 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [1.2.0] - 2026-02-23
9
+
10
+ ### Bug Fixes
11
+ - Replaced unreliable `Timeout.timeout` with process groups (`pgroup: true`) and `Thread#join` for reliable time budget enforcement on lint/build/fast/slow stages
12
+ - Fixed off-by-one in TUI row truncation that caused the top pipeline row to be hidden behind the header in short terminals
13
+ - Added `Screen#height=` to clear screen on terminal height changes, preventing the header from scrolling off-screen when tmux panes resize
14
+ - Fixed header flicker by skipping `println` when animation is active
15
+ - Allowed null SHA on root commits so pipeline still runs
16
+ - Detected multi-module Gradle projects from `settings.gradle`
17
+
18
+ ### Refactoring
19
+ - Extracted `TerminalInput`, `KeyHandler`, `BoardRenderer` from `AdminTui` (was 257 lines/12 ivars, now 4 focused classes all under 150 lines/4 ivars)
20
+ - Organized flat `lib/fun_ci/` into cohesive module subdirectories: `persistence/`, `pipeline/`, `setup/`, `tui/`
21
+
22
+ ### Internal
23
+ - Extracted shared `ProcessRunner` module for consistent process spawning across stages
24
+ - Updated `BackgroundWrapper` to use `[output, status, timed_out]` triples instead of exception-based timeout signaling
25
+ - Truncated TUI board rows to fit terminal height
26
+
27
+ ## [1.1.0] - 2026-02-22
28
+
29
+ ### Added
30
+ - Multi-line ASCII art header animations for pipeline events (explosion on failure, celebrations on success)
31
+ - Looping idle starfield animation in the TUI header
32
+ - Rocket animation while pipelines are running, with flickering exhaust and parallax starfield
33
+ - HeaderAnimationManager with idle/running/event state transitions
34
+ - AnimationLibrary with 8 animation data files (explosion, success, celebrate, flash, leprechauns, yay, idle, running)
35
+
36
+ ### Fixed
37
+ - Footer animations now display 8x slower so text is actually readable
38
+ - Animation refresh no longer blocks for 5 seconds between frames when no pipelines are running
39
+ - Removed stale single-line header banner that conflicted with multi-line animations
40
+ - Animation frames padded to global max width across all frames, preventing horizontal jitter
41
+
8
42
  ## [1.0.0] - 2026-02-21
9
43
 
10
44
  ### Added
data/README.md CHANGED
@@ -74,7 +74,9 @@ After `fun-ci install-hooks`, two hooks are active:
74
74
  fun-ci console
75
75
  ```
76
76
 
77
- Opens a terminal UI showing pipeline status across all branches. Navigation:
77
+ Opens a terminal UI showing pipeline status across all branches. The header area displays animated ASCII art: a gentle starfield when idle, a rocket while pipelines run, and celebration/explosion animations on success or failure.
78
+
79
+ Navigation:
78
80
 
79
81
  - `j` / `k` -- scroll up/down
80
82
  - `c` -- cancel a running pipeline
data/exe/fun-ci-trigger CHANGED
@@ -2,8 +2,8 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require_relative "../lib/fun_ci"
5
- require_relative "../lib/fun_ci/trigger"
6
- require_relative "../lib/fun_ci/pipeline_recorder"
5
+ require_relative "../lib/fun_ci/pipeline/trigger"
6
+ require_relative "../lib/fun_ci/persistence/pipeline_recorder"
7
7
 
8
8
  require "tmpdir"
9
9
 
@@ -11,9 +11,9 @@ db_dir = File.join(Dir.tmpdir, "fun-ci")
11
11
  Dir.mkdir(db_dir) unless Dir.exist?(db_dir)
12
12
  db_path = File.join(db_dir, "db.sqlite3")
13
13
 
14
- db = FunCi::Database.connection(db_path)
15
- FunCi::Database.migrate!(db)
14
+ db = FunCi::Persistence::Database.connection(db_path)
15
+ FunCi::Persistence::Database.migrate!(db)
16
16
 
17
- recorder = FunCi::DbRecorder.new(db)
17
+ recorder = FunCi::Persistence::DbRecorder.new(db)
18
18
 
19
- exit FunCi::Trigger.run_from_args(ARGV, recorder: recorder)
19
+ exit FunCi::Pipeline::Trigger.run_from_args(ARGV, recorder: recorder)
data/exe/fun-ci-tui CHANGED
@@ -2,7 +2,8 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require_relative "../lib/fun_ci"
5
- require_relative "../lib/fun_ci/admin_tui"
5
+ require_relative "../lib/fun_ci/tui/admin_tui"
6
+ require_relative "../lib/fun_ci/tui/animation_renderer"
6
7
 
7
8
  require "tmpdir"
8
9
  require "io/console"
@@ -11,8 +12,12 @@ db_dir = File.join(Dir.tmpdir, "fun-ci")
11
12
  Dir.mkdir(db_dir) unless Dir.exist?(db_dir)
12
13
  db_path = File.join(db_dir, "db.sqlite3")
13
14
 
14
- db = FunCi::Database.connection(db_path)
15
- FunCi::Database.migrate!(db)
15
+ db = FunCi::Persistence::Database.connection(db_path)
16
+ FunCi::Persistence::Database.migrate!(db)
16
17
 
17
- tui = FunCi::AdminTui.new(db: db, width_provider: -> { IO.console&.winsize&.dig(1) || 80 })
18
+ tui = FunCi::Tui::AdminTui.new(
19
+ db: db,
20
+ width_provider: -> { IO.console&.winsize&.dig(1) || 80 },
21
+ animation_renderer: FunCi::Tui::AnimationRenderer.new
22
+ )
18
23
  tui.run
@@ -0,0 +1,210 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FunCi
4
+ module Animations
5
+ module Celebrate
6
+ # Horizontal colour expansion — success celebration
7
+ # Run: ruby exe/animation-player animations/celebrate.rb
8
+
9
+ w = "\e[1;37m" # bold white
10
+ y = "\e[1;33m" # bold yellow
11
+ Y = "\e[33m" # yellow
12
+ g = "\e[1;32m" # bold green
13
+ G = "\e[32m" # green
14
+ d = "\e[2;32m" # dim green
15
+ c = "\e[1;36m" # bold cyan
16
+ C = "\e[36m" # cyan
17
+ m = "\e[1;35m" # bold magenta
18
+ M = "\e[35m" # magenta
19
+ b = "\e[1;34m" # bold blue
20
+ B = "\e[34m" # blue
21
+ r = "\e[1;31m" # bold red
22
+ o = "\e[38;5;208m" # orange
23
+ x = "\e[0m" # reset
24
+
25
+ raw_frames = [
26
+ # Frame 1 — dark field, bright seed in the centre
27
+ [
28
+ " #{d}. . . . . . . . . . . . . . . . . . . . . . . .#{x}",
29
+ " #{d}. . . . . . . . . . . . . . . . . . . . . . . .#{x}",
30
+ " #{d}. . . . . . . . . . . . . . . . . . . . . . . .#{x}",
31
+ " #{d}. . . . . . . . . . . . . . . . . . . . . . . .#{x}",
32
+ " #{d}. . . . . . . . . . . . . . . . . . . . . . . .#{x}",
33
+ " #{d}. . . . . . . . . . . .#{w}*#{d}. . . . . . . . . . .#{x}",
34
+ " #{d}. . . . . . . . . . . . . . . . . . . . . . . .#{x}",
35
+ " #{d}. . . . . . . . . . . . . . . . . . . . . . . .#{x}",
36
+ " #{d}. . . . . . . . . . . . . . . . . . . . . . . .#{x}",
37
+ " #{d}. . . . . . . . . . . . . . . . . . . . . . . .#{x}",
38
+ " #{d}. . . . . . . . . . . . . . . . . . . . . . . .#{x}",
39
+ ],
40
+ # Frame 2 — first horizontal burst, gold/white core
41
+ [
42
+ " #{d}. . . . . . . . . . . . . . . . . . . . . . . .#{x}",
43
+ " #{d}. . . . . . . . . . . . . . . . . . . . . . . .#{x}",
44
+ " #{d}. . . . . . . . . . . . . . . . . . . . . . . .#{x}",
45
+ " #{d}. . . . . . . . . . #{Y}* #{y}* #{Y}*#{d} . . . . . . . . .#{x}",
46
+ " #{d}. . . . . . . . . #{y}* #{w}*#{y} @#{w} @#{y} *#{Y} *#{d} . . . . . . . .#{x}",
47
+ " #{d}. . . . . . . . #{y}*#{w} @#{y} @#{w} # #{w}@#{y} @ #{w}*#{y} *#{d} . . . . . . .#{x}",
48
+ " #{d}. . . . . . . . . #{y}* #{w}*#{y} @#{w} @#{y} *#{Y} *#{d} . . . . . . . .#{x}",
49
+ " #{d}. . . . . . . . . . #{Y}* #{y}* #{Y}*#{d} . . . . . . . . .#{x}",
50
+ " #{d}. . . . . . . . . . . . . . . . . . . . . . . .#{x}",
51
+ " #{d}. . . . . . . . . . . . . . . . . . . . . . . .#{x}",
52
+ " #{d}. . . . . . . . . . . . . . . . . . . . . . . .#{x}",
53
+ ],
54
+ # Frame 3 — wider, green/cyan ring appears
55
+ [
56
+ " #{d}. . . . . . . . . . . . . . . . . . . . . . . .#{x}",
57
+ " #{d}. . . . . . . . . . . . . . . . . . . . . . . .#{x}",
58
+ " #{d}. . . . . . . #{G}*#{g} *#{G} *#{g} *#{G} *#{g} *#{G} *#{g} *#{G} *#{d} . . . . . . .#{x}",
59
+ " #{d}. . . . . . #{g}*#{c} *#{C} *#{y} *#{Y} * #{y}*#{Y} * #{c}*#{C} *#{g} *#{d} . . . . . .#{x}",
60
+ " #{d}. . . . . #{g}*#{c} *#{y} *#{w} @#{y} @#{w} ##{w} @#{y} @#{w} *#{y} *#{c} *#{g} *#{d} . . . . .#{x}",
61
+ " #{d}. . . . #{g}*#{c} *#{y} *#{w} @#{w} ##{w} @#{w} ##{w} @#{w} ##{w} @#{w} *#{y} *#{c} *#{g} *#{d} . . . .#{x}",
62
+ " #{d}. . . . . #{g}*#{c} *#{y} *#{w} @#{y} @#{w} ##{w} @#{y} @#{w} *#{y} *#{c} *#{g} *#{d} . . . . .#{x}",
63
+ " #{d}. . . . . . #{g}*#{c} *#{C} *#{y} *#{Y} * #{y}*#{Y} * #{c}*#{C} *#{g} *#{d} . . . . . .#{x}",
64
+ " #{d}. . . . . . . #{G}*#{g} *#{G} *#{g} *#{G} *#{g} *#{G} *#{g} *#{G} *#{d} . . . . . . .#{x}",
65
+ " #{d}. . . . . . . . . . . . . . . . . . . . . . . .#{x}",
66
+ " #{d}. . . . . . . . . . . . . . . . . . . . . . . .#{x}",
67
+ ],
68
+ # Frame 4 — colours spread wider, magenta/blue outer ring
69
+ [
70
+ " #{d}. . . . . . . . . . . . . . . . . . . . . . . .#{x}",
71
+ " #{d}. . . . . #{B}*#{b} *#{B} *#{b} *#{B} *#{b} *#{B} *#{b} *#{B} *#{b} *#{B} *#{d} . . . . .#{x}",
72
+ " #{d}. . . . #{b}*#{M} *#{m} *#{g} *#{c} *#{g} *#{c} *#{g} *#{c} *#{m} *#{M} *#{b} *#{d} . . . .#{x}",
73
+ " #{d}. . . #{b}*#{m} *#{g} *#{c} *#{C} *#{y} *#{Y} * #{y}*#{C} * #{c}*#{g} *#{m} *#{b} *#{d} . . .#{x}",
74
+ " #{d}. . #{b}*#{m} *#{g} *#{c} *#{y} *#{w} @#{y} @#{w} ##{w} @#{y} @#{y} *#{c} *#{g} *#{m} *#{b} *#{d} . .#{x}",
75
+ " #{d}. #{b}*#{m} *#{g} *#{c} *#{y} *#{w} @#{w} ##{w} @#{w} ##{w} @#{w} ##{w} @#{y} *#{c} *#{g} *#{m} *#{b} *#{d} .#{x}",
76
+ " #{d}. . #{b}*#{m} *#{g} *#{c} *#{y} *#{w} @#{y} @#{w} ##{w} @#{y} @#{y} *#{c} *#{g} *#{m} *#{b} *#{d} . .#{x}",
77
+ " #{d}. . . #{b}*#{m} *#{g} *#{c} *#{C} *#{y} *#{Y} * #{y}*#{C} * #{c}*#{g} *#{m} *#{b} *#{d} . . .#{x}",
78
+ " #{d}. . . . #{b}*#{M} *#{m} *#{g} *#{c} *#{g} *#{c} *#{g} *#{c} *#{m} *#{M} *#{b} *#{d} . . . .#{x}",
79
+ " #{d}. . . . . #{B}*#{b} *#{B} *#{b} *#{B} *#{b} *#{B} *#{b} *#{B} *#{b} *#{B} *#{d} . . . . .#{x}",
80
+ " #{d}. . . . . . . . . . . . . . . . . . . . . . . .#{x}",
81
+ ],
82
+ # Frame 5 — nearly full width, rainbow bands
83
+ [
84
+ " #{d}. . . #{B}*#{b} *#{B} *#{b} *#{B} *#{b} *#{B} *#{b} *#{B} *#{b} *#{B} *#{b} *#{B} *#{b} *#{d} . . .#{x}",
85
+ " #{d}. . #{b}*#{M} *#{m} *#{M} *#{m} *#{M} *#{m} *#{M} *#{m} *#{M} *#{m} *#{M} *#{m} *#{M} *#{b} *#{d} . .#{x}",
86
+ " #{d}. #{m}*#{r} *#{o} *#{g} *#{c} *#{g} *#{c} *#{g} *#{c} *#{g} *#{c} *#{g} *#{o} *#{r} *#{m} *#{d} .#{x}",
87
+ " #{m}*#{g} *#{c} *#{C} *#{c} *#{C} *#{y} *#{Y} *#{y} *#{Y} *#{c} *#{C} *#{c} *#{C} *#{c} *#{g} *#{m} *#{x}",
88
+ " #{g}*#{c} *#{y} *#{Y} *#{w} @#{y} @#{w} ##{w} @#{w} ##{w} @#{y} @#{w} *#{Y} *#{y} *#{c} *#{g} *#{m} *#{x}",
89
+ " #{g}*#{c} *#{y} *#{w} @#{w} ##{w} @#{w} ##{w} @#{w} ##{w} @#{w} ##{w} @#{w} *#{y} *#{c} *#{g} *#{m} *#{x}",
90
+ " #{g}*#{c} *#{y} *#{Y} *#{w} @#{y} @#{w} ##{w} @#{w} ##{w} @#{y} @#{w} *#{Y} *#{y} *#{c} *#{g} *#{m} *#{x}",
91
+ " #{m}*#{g} *#{c} *#{C} *#{c} *#{C} *#{y} *#{Y} *#{y} *#{Y} *#{c} *#{C} *#{c} *#{C} *#{c} *#{g} *#{m} *#{x}",
92
+ " #{d}. #{m}*#{r} *#{o} *#{g} *#{c} *#{g} *#{c} *#{g} *#{c} *#{g} *#{c} *#{g} *#{o} *#{r} *#{m} *#{d} .#{x}",
93
+ " #{d}. . #{b}*#{M} *#{m} *#{M} *#{m} *#{M} *#{m} *#{M} *#{m} *#{M} *#{m} *#{M} *#{m} *#{M} *#{b} *#{d} . .#{x}",
94
+ " #{d}. . . #{B}*#{b} *#{B} *#{b} *#{B} *#{b} *#{B} *#{b} *#{B} *#{b} *#{B} *#{b} *#{B} *#{b} *#{d} . . .#{x}",
95
+ ],
96
+ # Frame 6 — FULL WIDTH, every pixel lit, rainbow explosion
97
+ [
98
+ " #{B}*#{b} *#{M} *#{m} *#{M} *#{m} *#{M} *#{m} *#{M} *#{m} *#{M} *#{m} *#{M} *#{m} *#{M} *#{b} *#{B} *#{x}",
99
+ " #{m}*#{r} *#{o} *#{r} *#{g} *#{c} *#{g} *#{c} *#{g} *#{c} *#{g} *#{c} *#{r} *#{o} *#{r} *#{m} *#{b} *#{x}",
100
+ " #{r}*#{o} *#{g} *#{c} *#{C} *#{c} *#{C} *#{c} *#{C} *#{c} *#{C} *#{c} *#{C} *#{g} *#{o} *#{r} *#{m} *#{x}",
101
+ " #{g}*#{c} *#{C} *#{y} *#{Y} *#{y} *#{Y} *#{w} @#{Y} *#{y} *#{Y} *#{y} *#{C} *#{c} *#{g} *#{o} *#{r} *#{x}",
102
+ " #{c}*#{y} *#{w} @#{w} ##{w} @#{w} ##{w} @#{w} ##{w} @#{w} ##{w} @#{w} ##{w} @#{w} *#{y} *#{c} *#{g} *#{o} *#{x}",
103
+ " #{y}*#{w} @#{w} ##{w} @#{w} ##{w} @#{w} ##{w} @#{w} ##{w} @#{w} ##{w} @#{w} ##{w} @#{w} ##{w} @#{y} *#{c} *#{x}",
104
+ " #{c}*#{y} *#{w} @#{w} ##{w} @#{w} ##{w} @#{w} ##{w} @#{w} ##{w} @#{w} ##{w} @#{w} *#{y} *#{c} *#{g} *#{o} *#{x}",
105
+ " #{g}*#{c} *#{C} *#{y} *#{Y} *#{y} *#{Y} *#{w} @#{Y} *#{y} *#{Y} *#{y} *#{C} *#{c} *#{g} *#{o} *#{r} *#{x}",
106
+ " #{r}*#{o} *#{g} *#{c} *#{C} *#{c} *#{C} *#{c} *#{C} *#{c} *#{C} *#{c} *#{C} *#{g} *#{o} *#{r} *#{m} *#{x}",
107
+ " #{m}*#{r} *#{o} *#{r} *#{g} *#{c} *#{g} *#{c} *#{g} *#{c} *#{g} *#{c} *#{r} *#{o} *#{r} *#{m} *#{b} *#{x}",
108
+ " #{B}*#{b} *#{M} *#{m} *#{M} *#{m} *#{M} *#{m} *#{M} *#{m} *#{M} *#{m} *#{M} *#{m} *#{M} *#{b} *#{B} *#{x}",
109
+ ],
110
+ # (hold)
111
+ [
112
+ " #{b}*#{M} *#{m} *#{M} *#{m} *#{r} *#{o} *#{r} *#{o} *#{r} *#{m} *#{M} *#{m} *#{M} *#{m} *#{M} *#{b} *#{x}",
113
+ " #{m}*#{r} *#{o} *#{g} *#{c} *#{g} *#{c} *#{g} *#{c} *#{g} *#{c} *#{g} *#{c} *#{g} *#{o} *#{r} *#{m} *#{x}",
114
+ " #{o}*#{g} *#{c} *#{C} *#{c} *#{C} *#{y} *#{Y} *#{y} *#{Y} *#{c} *#{C} *#{c} *#{C} *#{g} *#{o} *#{r} *#{x}",
115
+ " #{c}*#{C} *#{y} *#{Y} *#{y} *#{w} @#{w} ##{w} @#{w} ##{w} @#{y} *#{Y} *#{y} *#{C} *#{c} *#{g} *#{o} *#{x}",
116
+ " #{y}*#{w} @#{w} ##{w} @#{w} ##{w} @#{w} ##{w} @#{w} ##{w} @#{w} ##{w} @#{w} ##{w} @#{w} *#{y} *#{c} *#{g} *#{x}",
117
+ " #{w}@#{w} ##{w} @#{w} ##{w} @#{w} ##{w} @#{w} ##{w} @#{w} ##{w} @#{w} ##{w} @#{w} ##{w} @#{w} ##{w} @#{y} *#{x}",
118
+ " #{y}*#{w} @#{w} ##{w} @#{w} ##{w} @#{w} ##{w} @#{w} ##{w} @#{w} ##{w} @#{w} ##{w} @#{w} *#{y} *#{c} *#{g} *#{x}",
119
+ " #{c}*#{C} *#{y} *#{Y} *#{y} *#{w} @#{w} ##{w} @#{w} ##{w} @#{y} *#{Y} *#{y} *#{C} *#{c} *#{g} *#{o} *#{x}",
120
+ " #{o}*#{g} *#{c} *#{C} *#{c} *#{C} *#{y} *#{Y} *#{y} *#{Y} *#{c} *#{C} *#{c} *#{C} *#{g} *#{o} *#{r} *#{x}",
121
+ " #{m}*#{r} *#{o} *#{g} *#{c} *#{g} *#{c} *#{g} *#{c} *#{g} *#{c} *#{g} *#{c} *#{g} *#{o} *#{r} *#{m} *#{x}",
122
+ " #{b}*#{M} *#{m} *#{M} *#{m} *#{r} *#{o} *#{r} *#{o} *#{r} *#{m} *#{M} *#{m} *#{M} *#{m} *#{M} *#{b} *#{x}",
123
+ ],
124
+ # Frame 7 — contracting, colours retreating
125
+ [
126
+ " #{d}. #{B}*#{b} *#{M} *#{m} *#{M} *#{m} *#{M} *#{m} *#{M} *#{m} *#{M} *#{m} *#{M} *#{b} *#{B} *#{d} .#{x}",
127
+ " #{d}. . #{m}*#{r} *#{o} *#{g} *#{c} *#{g} *#{c} *#{g} *#{c} *#{g} *#{c} *#{o} *#{r} *#{m} *#{d} . .#{x}",
128
+ " #{d}. . . #{g}*#{c} *#{C} *#{c} *#{C} *#{y} *#{Y} *#{y} *#{C} *#{c} *#{C} *#{c} *#{g} *#{d} . . .#{x}",
129
+ " #{d}. . . . #{c}*#{C} *#{y} *#{Y} *#{w} @#{w} ##{w} @#{Y} *#{y} *#{C} *#{c} *#{g} *#{d} . . . .#{x}",
130
+ " #{d}. . . #{g}*#{c} *#{y} *#{w} @#{w} ##{w} @#{w} ##{w} @#{w} ##{w} @#{w} *#{y} *#{c} *#{g} *#{d} . . .#{x}",
131
+ " #{d}. . #{g}*#{c} *#{y} *#{w} @#{w} ##{w} @#{w} ##{w} @#{w} ##{w} @#{w} ##{w} @#{y} *#{c} *#{g} *#{d} . .#{x}",
132
+ " #{d}. . . #{g}*#{c} *#{y} *#{w} @#{w} ##{w} @#{w} ##{w} @#{w} ##{w} @#{w} *#{y} *#{c} *#{g} *#{d} . . .#{x}",
133
+ " #{d}. . . . #{c}*#{C} *#{y} *#{Y} *#{w} @#{w} ##{w} @#{Y} *#{y} *#{C} *#{c} *#{g} *#{d} . . . .#{x}",
134
+ " #{d}. . . #{g}*#{c} *#{C} *#{c} *#{C} *#{y} *#{Y} *#{y} *#{C} *#{c} *#{C} *#{c} *#{g} *#{d} . . .#{x}",
135
+ " #{d}. . #{m}*#{r} *#{o} *#{g} *#{c} *#{g} *#{c} *#{g} *#{c} *#{g} *#{c} *#{o} *#{r} *#{m} *#{d} . .#{x}",
136
+ " #{d}. #{B}*#{b} *#{M} *#{m} *#{M} *#{m} *#{M} *#{m} *#{M} *#{m} *#{M} *#{m} *#{M} *#{b} *#{B} *#{d} .#{x}",
137
+ ],
138
+ # Frame 8 — second pulse outward, fresh colours
139
+ [
140
+ " #{d}. . #{b}*#{m} *#{r} *#{o} *#{g} *#{c} *#{g} *#{c} *#{g} *#{o} *#{r} *#{m} *#{b} *#{d} . . .#{x}",
141
+ " #{d}. #{m}*#{g} *#{c} *#{C} *#{c} *#{C} *#{c} *#{C} *#{c} *#{C} *#{c} *#{C} *#{c} *#{g} *#{m} *#{d} .#{x}",
142
+ " #{m}*#{g} *#{c} *#{C} *#{y} *#{Y} *#{y} *#{Y} *#{y} *#{Y} *#{y} *#{Y} *#{C} *#{c} *#{g} *#{m} *#{b} *#{x}",
143
+ " #{g}*#{c} *#{y} *#{w} @#{w} ##{w} @#{w} ##{w} @#{w} ##{w} @#{w} ##{w} @#{w} *#{y} *#{c} *#{g} *#{m} *#{x}",
144
+ " #{c}*#{y} *#{w} @#{w} ##{w} @#{w} ##{w} @#{w} ##{w} @#{w} ##{w} @#{w} ##{w} @#{w} *#{y} *#{c} *#{g} *#{x}",
145
+ " #{y}*#{w} @#{w} ##{w} @#{w} ##{w} @#{w} ##{w} @#{w} ##{w} @#{w} ##{w} @#{w} ##{w} @#{w} ##{w} @#{y} *#{c} *#{x}",
146
+ " #{c}*#{y} *#{w} @#{w} ##{w} @#{w} ##{w} @#{w} ##{w} @#{w} ##{w} @#{w} ##{w} @#{w} *#{y} *#{c} *#{g} *#{x}",
147
+ " #{g}*#{c} *#{y} *#{w} @#{w} ##{w} @#{w} ##{w} @#{w} ##{w} @#{w} ##{w} @#{w} *#{y} *#{c} *#{g} *#{m} *#{x}",
148
+ " #{m}*#{g} *#{c} *#{C} *#{y} *#{Y} *#{y} *#{Y} *#{y} *#{Y} *#{y} *#{Y} *#{C} *#{c} *#{g} *#{m} *#{b} *#{x}",
149
+ " #{d}. #{m}*#{g} *#{c} *#{C} *#{c} *#{C} *#{c} *#{C} *#{c} *#{C} *#{c} *#{C} *#{c} *#{g} *#{m} *#{d} .#{x}",
150
+ " #{d}. . #{b}*#{m} *#{r} *#{o} *#{g} *#{c} *#{g} *#{c} *#{g} *#{o} *#{r} *#{m} *#{b} *#{d} . . .#{x}",
151
+ ],
152
+ # Frame 9 — settling, warm glow
153
+ [
154
+ " #{d}. . . . #{b}*#{m} *#{g} *#{c} *#{g} *#{c} *#{g} *#{c} *#{g} *#{m} *#{b} *#{d} . . . . .#{x}",
155
+ " #{d}. . . #{m}*#{g} *#{c} *#{C} *#{c} *#{C} *#{c} *#{C} *#{c} *#{C} *#{c} *#{g} *#{m} *#{d} . . .#{x}",
156
+ " #{d}. . #{g}*#{c} *#{C} *#{y} *#{Y} *#{y} *#{Y} *#{y} *#{Y} *#{y} *#{C} *#{c} *#{g} *#{d} . . .#{x}",
157
+ " #{d}. #{g}*#{c} *#{y} *#{Y} *#{y} *#{w} @#{y} *#{w} @#{y} *#{Y} *#{y} *#{c} *#{g} *#{m} *#{d} . .#{x}",
158
+ " #{d}. #{c}*#{y} *#{w} @#{y} @#{w} ##{w} @#{w} ##{w} @#{w} ##{y} @#{w} *#{y} *#{c} *#{g} *#{d} . . .#{x}",
159
+ " #{g}*#{c} *#{y} *#{w} @#{w} ##{w} @#{w} ##{w} @#{w} ##{w} @#{w} ##{w} @#{y} *#{y} *#{c} *#{g} *#{d} .#{x}",
160
+ " #{d}. #{c}*#{y} *#{w} @#{y} @#{w} ##{w} @#{w} ##{w} @#{w} ##{y} @#{w} *#{y} *#{c} *#{g} *#{d} . . .#{x}",
161
+ " #{d}. #{g}*#{c} *#{y} *#{Y} *#{y} *#{w} @#{y} *#{w} @#{y} *#{Y} *#{y} *#{c} *#{g} *#{m} *#{d} . .#{x}",
162
+ " #{d}. . #{g}*#{c} *#{C} *#{y} *#{Y} *#{y} *#{Y} *#{y} *#{Y} *#{y} *#{C} *#{c} *#{g} *#{d} . . .#{x}",
163
+ " #{d}. . . #{m}*#{g} *#{c} *#{C} *#{c} *#{C} *#{c} *#{C} *#{c} *#{C} *#{c} *#{g} *#{m} *#{d} . . .#{x}",
164
+ " #{d}. . . . #{b}*#{m} *#{g} *#{c} *#{g} *#{c} *#{g} *#{c} *#{g} *#{m} *#{b} *#{d} . . . . .#{x}",
165
+ ],
166
+ # Frame 10 — fading to green glow
167
+ [
168
+ " #{d}. . . . . . . . . . . . . . . . . . . . . . . .#{x}",
169
+ " #{d}. . . . . . #{G}*#{g} *#{G} *#{g} *#{G} *#{g} *#{G} *#{g} *#{G} *#{d} . . . . . .#{x}",
170
+ " #{d}. . . . . #{g}*#{G} *#{g} *#{G} *#{g} *#{G} *#{g} *#{G} *#{g} *#{G} *#{g} *#{d} . . . . .#{x}",
171
+ " #{d}. . . . #{g}*#{c} *#{g} *#{c} *#{g} *#{Y} *#{g} *#{c} *#{g} *#{c} *#{g} *#{d} . . . . . .#{x}",
172
+ " #{d}. . . #{g}*#{c} *#{C} *#{y} *#{Y} *#{y} *#{Y} *#{y} *#{C} *#{c} *#{g} *#{d} . . . . . . .#{x}",
173
+ " #{d}. . . #{g}*#{c} *#{y} *#{Y} *#{y} *#{w} @#{y} *#{Y} *#{y} *#{c} *#{g} *#{d} . . . . . . .#{x}",
174
+ " #{d}. . . #{g}*#{c} *#{C} *#{y} *#{Y} *#{y} *#{Y} *#{y} *#{C} *#{c} *#{g} *#{d} . . . . . . .#{x}",
175
+ " #{d}. . . . #{g}*#{c} *#{g} *#{c} *#{g} *#{Y} *#{g} *#{c} *#{g} *#{c} *#{g} *#{d} . . . . . .#{x}",
176
+ " #{d}. . . . . #{g}*#{G} *#{g} *#{G} *#{g} *#{G} *#{g} *#{G} *#{g} *#{G} *#{g} *#{d} . . . . .#{x}",
177
+ " #{d}. . . . . . #{G}*#{g} *#{G} *#{g} *#{G} *#{g} *#{G} *#{g} *#{G} *#{d} . . . . . .#{x}",
178
+ " #{d}. . . . . . . . . . . . . . . . . . . . . . . .#{x}",
179
+ ],
180
+ # Frame 11 — almost gone, warm ember
181
+ [
182
+ " #{d}. . . . . . . . . . . . . . . . . . . . . . . .#{x}",
183
+ " #{d}. . . . . . . . . . . . . . . . . . . . . . . .#{x}",
184
+ " #{d}. . . . . . . . #{G}*#{g} *#{G} *#{g} *#{G} *#{g} *#{d} . . . . . . . .#{x}",
185
+ " #{d}. . . . . . . #{g}*#{G} *#{g} *#{G} *#{g} *#{G} *#{g} *#{d} . . . . . . .#{x}",
186
+ " #{d}. . . . . . . #{g}*#{c} *#{g} *#{Y} *#{g} *#{c} *#{g} *#{d} . . . . . . .#{x}",
187
+ " #{d}. . . . . . . . #{g}*#{G} *#{g} *#{G} *#{g} *#{d} . . . . . . . . .#{x}",
188
+ " #{d}. . . . . . . . . #{G}*#{g} *#{G} *#{d} . . . . . . . . . . .#{x}",
189
+ " #{d}. . . . . . . . . . . . . . . . . . . . . . . .#{x}",
190
+ " #{d}. . . . . . . . . . . . . . . . . . . . . . . .#{x}",
191
+ " #{d}. . . . . . . . . . . . . . . . . . . . . . . .#{x}",
192
+ " #{d}. . . . . . . . . . . . . . . . . . . . . . . .#{x}",
193
+ ],
194
+ ]
195
+
196
+ # Pad lines so all rows in each frame have equal stripped length —
197
+ # prevents the player's per-line centering from misaligning the art.
198
+ max = raw_frames.flatten.map { |l| l.gsub(/\e\[[0-9;]*m/, "").length }.max
199
+ raw_frames.each do |frame|
200
+ frame.map! { |l| l + " " * (max - l.gsub(/\e\[[0-9;]*m/, "").length) }
201
+ end
202
+
203
+ DATA = {
204
+ name: "Celebrate",
205
+ fps: 8,
206
+ frames: raw_frames
207
+ }.freeze
208
+ end
209
+ end
210
+ end
@@ -0,0 +1,138 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FunCi
4
+ module Animations
5
+ module Explosion
6
+ # Failure explosion animation — expanding fireball with debris
7
+ # Run: ruby exe/animation-player animations/explosion.rb
8
+
9
+ r = "\e[1;31m" # bold red
10
+ o = "\e[38;5;208m" # orange
11
+ y = "\e[1;33m" # bold yellow
12
+ w = "\e[1;37m" # bold white
13
+ d = "\e[2;31m" # dim red
14
+ k = "\e[38;5;52m" # dark red
15
+ x = "\e[0m" # reset
16
+
17
+ raw_frames = [
18
+ # Frame 1 — spark
19
+ [
20
+ "",
21
+ "",
22
+ "",
23
+ "",
24
+ " #{w}*#{x}",
25
+ "",
26
+ "",
27
+ "",
28
+ "",
29
+ "",
30
+ ],
31
+ # Frame 2 — ignition
32
+ [
33
+ "",
34
+ "",
35
+ "",
36
+ " #{y}\\|/#{x}",
37
+ " #{w}-#{r}*#{w}-#{x}",
38
+ " #{y}/|\\#{x}",
39
+ "",
40
+ "",
41
+ "",
42
+ "",
43
+ ],
44
+ # Frame 3 — small burst
45
+ [
46
+ "",
47
+ "",
48
+ " #{o}. * .#{x}",
49
+ " #{y}*#{r}(#)#{y}*#{x}",
50
+ " #{r}(#{y}*#{w}@@@#{y}*#{r})#{x}",
51
+ " #{y}*#{r}(#)#{y}*#{x}",
52
+ " #{o}' * '#{x}",
53
+ "",
54
+ "",
55
+ "",
56
+ ],
57
+ # Frame 4 — expanding fireball
58
+ [
59
+ "",
60
+ " #{o}* . *#{x}",
61
+ " #{y}.#{r}* #{o}( ) #{r}*#{y}.#{x}",
62
+ " #{r}(#{y}*#{w} @@@@@ #{y}*#{r})#{x}",
63
+ " #{r}(#{o}*#{w}@@@#{y}X#{w}@@@#{o}*#{r})#{x}",
64
+ " #{r}(#{y}*#{w} @@@@@ #{y}*#{r})#{x}",
65
+ " #{y}'#{r}* #{o}( ) #{r}*#{y}'#{x}",
66
+ " #{o}* ' *#{x}",
67
+ "",
68
+ "",
69
+ ],
70
+ # Frame 5 — full explosion
71
+ [
72
+ " #{d}. #{o}* * .#{d} .#{x}",
73
+ " #{o}* #{y}.#{r}* *#{y}. #{o}*#{x}",
74
+ " #{y}. #{r}*#{o}(#{w} @@@@@@@@ #{o})#{r}*#{y} .#{x}",
75
+ " #{r}( #{y}*#{w}@@@@#{y}BOOM#{w}@@@@#{y}* #{r})#{x}",
76
+ " #{r}(#{o}*#{w}@@@@@@@#{y}X#{w}@@@@@@@#{o}*#{r})#{x}",
77
+ " #{r}( #{y}*#{w}@@@@@@@@@@@#{y}* #{r})#{x}",
78
+ " #{y}' #{r}*#{o}(#{w} @@@@@@@@ #{o})#{r}*#{y} '#{x}",
79
+ " #{o}* #{y}'#{r}* *#{y}' #{o}*#{x}",
80
+ " #{d}' #{o}* * '#{d} '#{x}",
81
+ "",
82
+ ],
83
+ # Frame 6 — debris flying out
84
+ [
85
+ " #{d}.#{x} #{o}*#{x} #{o}*#{x} #{d}.#{x}",
86
+ " #{o}. #{y}*#{x} #{d}. .#{x} #{y}* #{o}.#{x}",
87
+ " #{y}*#{x} #{r}*#{o}( #{d}::::: #{o})#{r}*#{x} #{y}*#{x}",
88
+ " #{r}(#{x} #{y}*#{x} #{d}::#w}@@#{y}X#{w}@@#{d}::#{x} #{y}*#{x} #{r})#{x}",
89
+ " #{o}*#{x} #{d}:::#{w}@@@@@@@#{d}:::#{x} #{o}*#{x}",
90
+ " #{r}(#{x} #{y}*#{x} #{d}::#{w}@@@@@#{d}::#{x} #{y}*#{x} #{r})#{x}",
91
+ " #{y}*#{x} #{r}*#{o}( #{d}::::: #{o})#{r}*#{x} #{y}*#{x}",
92
+ " #{o}' #{y}*#{x} #{d}' '#{x} #{y}* #{o}'#{x}",
93
+ " #{d}'#{x} #{o}*#{x} #{o}*#{x} #{d}'#{x}",
94
+ " #{d}.#{x} #{d}. .#{x} #{d}.#{x}",
95
+ ],
96
+ # Frame 7 — dissipating smoke
97
+ [
98
+ " #{d}.#{x} #{d}.#{x} #{d}.#{x} #{d}.#{x}",
99
+ " #{d}*#{x} #{d}*#{x}",
100
+ " #{d}. #{k}::: #{d}.#{x}",
101
+ " #{d}*#{x} #{k}::#{d}.....#{k}::#{x} #{d}*#{x}",
102
+ " #{k}:#{d}.. ..#{k}:#{x}",
103
+ " #{d}*#{x} #{k}::#{d}.....#{k}::#{x} #{d}*#{x}",
104
+ " #{d}' #{k}::: #{d}'#{x}",
105
+ " #{d}*#{x} #{d}*#{x}",
106
+ " #{d}'#{x} #{d}'#{x} #{d}'#{x} #{d}'#{x}",
107
+ "",
108
+ ],
109
+ # Frame 8 — fading embers
110
+ [
111
+ "",
112
+ " #{d}.#{x} #{d}.#{x}",
113
+ "",
114
+ " #{k}. . .#{x}",
115
+ " #{d}.#{x} #{k}. .#{x} #{d}.#{x}",
116
+ " #{k}' ' '#{x}",
117
+ "",
118
+ " #{d}'#{x} #{d}'#{x}",
119
+ "",
120
+ "",
121
+ ],
122
+ ]
123
+
124
+ # Pad lines so all rows in each frame have equal stripped length —
125
+ # prevents the player's per-line centering from misaligning the art.
126
+ max = raw_frames.flatten.map { |l| l.gsub(/\e\[[0-9;]*m/, "").length }.max
127
+ raw_frames.each do |frame|
128
+ frame.map! { |l| l + " " * (max - l.gsub(/\e\[[0-9;]*m/, "").length) }
129
+ end
130
+
131
+ DATA = {
132
+ name: "Explosion",
133
+ fps: 8,
134
+ frames: raw_frames
135
+ }.freeze
136
+ end
137
+ end
138
+ end