fantasy-cli 1.2.14 → 1.3.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.
@@ -0,0 +1,263 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Gsd
4
+ module TUI
5
+ # Effects - Efeitos visuais avançados para TUI
6
+ #
7
+ # Suporta:
8
+ # - Gradientes de cor
9
+ # - Glow/Bloom effects
10
+ # - Sombras
11
+ # - Blur simulation
12
+ # - Rainbow effects
13
+ class Effects
14
+ # Gera gradiente horizontal de texto
15
+ def self.horizontal_gradient(text, colors)
16
+ return text if colors.empty?
17
+
18
+ chars = text.chars
19
+ result = ''
20
+
21
+ chars.each_with_index do |char, i|
22
+ color_idx = (i.to_f / chars.length * (colors.length - 1)).round
23
+ color = colors[color_idx]
24
+ result << "#{color}#{char}"
25
+ end
26
+
27
+ result + Colors::RESET
28
+ end
29
+
30
+ # Gera gradiente vertical
31
+ def self.vertical_gradient(lines, colors)
32
+ return lines if colors.empty? || lines.empty?
33
+
34
+ lines.map.with_index do |line, i|
35
+ color_idx = (i.to_f / lines.length * (colors.length - 1)).round
36
+ "#{colors[color_idx]}#{line}#{Colors::RESET}"
37
+ end
38
+ end
39
+
40
+ # Efeito de glow (simulado com cores mais brilhantes)
41
+ def self.glow(text, intensity: 1, color: nil)
42
+ t = Colors.theme
43
+ glow_color = color || t[:accent]
44
+
45
+ case intensity
46
+ when 1
47
+ "#{glow_color}#{text}#{Colors::RESET}"
48
+ when 2
49
+ "#{Colors::BOLD}#{glow_color}#{text}#{Colors::RESET}"
50
+ when 3
51
+ "#{Colors::BOLD}#{Colors::ESC}[1m#{glow_color}#{text}#{Colors::RESET}"
52
+ else
53
+ "#{glow_color}#{text}#{Colors::RESET}"
54
+ end
55
+ end
56
+
57
+ # Efeito de pulso (variando intensidade)
58
+ def self.pulse(text, phase: 0.0, color: nil)
59
+ t = Colors.theme
60
+ base_color = color || t[:accent]
61
+
62
+ # Varia entre normal e brilhante baseado na fase
63
+ intensity = Math.sin(phase * Math::PI * 2) * 0.5 + 0.5
64
+
65
+ if intensity > 0.7
66
+ "#{Colors::BOLD}#{base_color}#{text}#{Colors::RESET}"
67
+ else
68
+ "#{base_color}#{text}#{Colors::RESET}"
69
+ end
70
+ end
71
+
72
+ # Rainbow effect
73
+ def self.rainbow(text)
74
+ rainbow_colors = [
75
+ Colors::RED,
76
+ Colors::YELLOW,
77
+ Colors::GREEN,
78
+ Colors::CYAN,
79
+ Colors::BLUE,
80
+ Colors::MAGENTA
81
+ ]
82
+
83
+ horizontal_gradient(text, rainbow_colors)
84
+ end
85
+
86
+ # Sombra (simulada com dim colors)
87
+ def self.shadow(text, offset: 1)
88
+ t = Colors.theme
89
+ shadow_color = t[:dim]
90
+
91
+ lines = text.split("\n")
92
+ result = []
93
+
94
+ lines.each do |line|
95
+ result << "#{shadow_color}#{line}#{Colors::RESET}"
96
+ # Linha de sombra
97
+ result << " #{shadow_color}#{line.gsub(/[^\s]/, '░')}#{Colors::RESET}" if offset > 0
98
+ end
99
+
100
+ result.join("\n")
101
+ end
102
+
103
+ # Blur simulation (usando caracteres de meio-tom)
104
+ BLUR_CHARS = ['█', '▓', '▒', '░', ' '].freeze
105
+
106
+ def self.blur(text, strength: 1)
107
+ return text if strength <= 0
108
+
109
+ chars = text.chars
110
+ result = ''
111
+
112
+ chars.each do |char|
113
+ if char == ' '
114
+ result << char
115
+ else
116
+ blur_idx = [strength, BLUR_CHARS.length - 1].min
117
+ result << BLUR_CHARS[blur_idx]
118
+ end
119
+ end
120
+
121
+ result
122
+ end
123
+
124
+ # Efeito de digitação com cursor
125
+ def self.typing(text, cursor_pos:, cursor_char: '▊')
126
+ before = text[0...cursor_pos]
127
+ after = text[cursor_pos..-1] || ''
128
+
129
+ "#{before}#{Colors::BOLD}#{cursor_char}#{Colors::RESET}#{after}"
130
+ end
131
+
132
+ # Neon effect (cyan/magenta alternando)
133
+ def self.neon(text, phase: 0.0)
134
+ t = Colors.theme
135
+ colors = [t[:accent], t[:accent2]]
136
+
137
+ horizontal_gradient(text, colors)
138
+ end
139
+
140
+ # Glitch effect
141
+ GLITCH_CHARS = '!@#$%^&*()_+-=[]{}|;:,.<>?'.chars.freeze
142
+
143
+ def self.glitch(text, intensity: 0.3)
144
+ return text if intensity <= 0
145
+
146
+ chars = text.chars
147
+ result = ''
148
+
149
+ chars.each do |char|
150
+ if rand < intensity && char != ' '
151
+ result << GLITCH_CHARS.sample
152
+ else
153
+ result << char
154
+ end
155
+ end
156
+
157
+ result
158
+ end
159
+
160
+ # Marquee / scrolling text
161
+ def self.marquee(text, width:, offset: 0)
162
+ return text if text.length <= width
163
+
164
+ # Cria loop infinito
165
+ looped = text + ' ' + text
166
+ start = offset % text.length
167
+
168
+ looped[start, width] || text[0, width]
169
+ end
170
+
171
+ # Border glow (borda pulsante)
172
+ def self.border_glow(width:, height:, phase: 0.0, color: nil)
173
+ t = Colors.theme
174
+ glow_color = color || t[:accent]
175
+ dim_color = t[:dim]
176
+
177
+ # Calcula intensidade baseada na fase
178
+ intensity = Math.sin(phase * Math::PI * 2) * 0.5 + 0.5
179
+ active_color = intensity > 0.5 ? glow_color : dim_color
180
+
181
+ corners = ['╭', '╮', '╰', '╯']
182
+ h_line = '─'
183
+ v_line = '│'
184
+
185
+ top = active_color + corners[0] + (h_line * (width - 2)) + corners[1] + Colors::RESET
186
+ bottom = active_color + corners[2] + (h_line * (width - 2)) + corners[3] + Colors::RESET
187
+ sides = (1..height - 2).map { active_color + v_line + (' ' * (width - 2)) + v_line + Colors::RESET }
188
+
189
+ [top, *sides, bottom]
190
+ end
191
+
192
+ # Wave effect (texto ondulando)
193
+ def self.wave(text, phase: 0.0, amplitude: 1)
194
+ chars = text.chars
195
+ lines = Array.new(amplitude * 2 + 1) { '' }
196
+
197
+ chars.each_with_index do |char, i|
198
+ wave_pos = Math.sin((i + phase * 10) * 0.3) * amplitude
199
+ center = amplitude
200
+
201
+ lines.each_with_index do |line, line_idx|
202
+ if (line_idx - center - wave_pos).abs < 0.5
203
+ lines[line_idx] << char
204
+ else
205
+ lines[line_idx] << ' '
206
+ end
207
+ end
208
+ end
209
+
210
+ lines.join("\n")
211
+ end
212
+
213
+ # Scanline effect
214
+ def self.scanline(text, spacing: 2, dim_color: nil)
215
+ t = Colors.theme
216
+ dim = dim_color || t[:dim]
217
+ normal = t[:text]
218
+
219
+ lines = text.split("\n")
220
+ lines.map.with_index do |line, i|
221
+ if i % spacing == 0
222
+ "#{dim}#{line}#{Colors::RESET}"
223
+ else
224
+ "#{normal}#{line}#{Colors::RESET}"
225
+ end
226
+ end.join("\n")
227
+ end
228
+
229
+ # CRT flicker (simula monitor antigo)
230
+ def self.crt_flicker(text, phase: 0.0)
231
+ flicker = Math.sin(phase * Math::PI * 60) * 0.1 + 0.9
232
+
233
+ if flicker > 0.95
234
+ text
235
+ else
236
+ t = Colors.theme
237
+ "#{t[:dim]}#{text}#{Colors::RESET}"
238
+ end
239
+ end
240
+
241
+ # Matrix rain effect preparer
242
+ def self.matrix_rain_column(height:, chars: '0123456789ABCDEF'.chars)
243
+ column = []
244
+ head_pos = rand(height)
245
+
246
+ height.times do |i|
247
+ if i == head_pos
248
+ column << "#{Colors::WHITE}#{chars.sample}#{Colors::RESET}"
249
+ elsif i < head_pos && i > head_pos - 5
250
+ brightness = head_pos - i
251
+ green_level = [255, brightness * 50].min
252
+ color = "#{Colors::ESC}[38;2;0;#{green_level};0m"
253
+ column << "#{color}#{chars.sample}#{Colors::RESET}"
254
+ else
255
+ column << "#{Colors::DIM_GREEN}#{chars.sample}#{Colors::RESET}"
256
+ end
257
+ end
258
+
259
+ column
260
+ end
261
+ end
262
+ end
263
+ end
@@ -20,16 +20,24 @@ module Gsd
20
20
 
21
21
  def render
22
22
  t = Colors.theme
23
- accent = t[:accent]
23
+ cyan = t[:accent] # Ciano neon
24
+ magenta = t[:accent2] # Roxo neon
25
+ pink = t[:accent3] # Rosa
26
+ white = t[:text]
24
27
  dim = t[:dim]
25
28
  reset = Colors::RESET
26
29
 
30
+ # Título estilo Warp com gradiente ciano -> roxo
31
+ title_line = "#{cyan}╔══════════════════════════════════════════╗#{reset}"
32
+ title_text = "#{cyan}║#{reset} #{magenta}⚡#{reset} #{white}Fantasy CLI#{reset} #{dim}v#{Gsd::VERSION}#{reset} #{cyan}║#{reset}"
33
+ title_bottom = "#{cyan}╚══════════════════════════════════════════╝#{reset}"
34
+
27
35
  lines = []
28
36
  lines << ''
29
- lines << "#{accent}#{FANTASY_LOGO.chomp}#{reset}"
30
- lines << ''
31
- lines << "#{dim}Terminal User Interface#{reset}"
32
- lines << "#{dim}v1.2.0#{reset}"
37
+ lines << title_line
38
+ lines << title_text
39
+ lines << title_bottom
40
+ lines << "#{dim} Terminal User Interface#{reset}"
33
41
  lines << ''
34
42
  lines.join("\n")
35
43
  end
@@ -77,27 +77,30 @@ module Gsd
77
77
 
78
78
  def render
79
79
  t = Colors.theme
80
- accent = t[:accent]
80
+ cyan = t[:accent] # Ciano neon
81
+ magenta = t[:accent2] # Roxo neon
81
82
  dim = t[:dim]
82
83
  white = t[:text]
84
+ bg = t[:bg]
83
85
  reset = Colors::RESET
84
86
 
85
- top_border = "#{accent}┌#{'─' * @width}┐#{reset}"
86
- bottom_border = "#{accent}└#{'─' * @width}┘#{reset}"
87
+ # Bordas estilo Warp com cores neon
88
+ top_border = "#{cyan}╭#{'─' * @width}╮#{reset}"
89
+ bottom_border = "#{cyan}╰#{'─' * @width}╯#{reset}"
87
90
 
88
- content = build_content(accent, dim, white, reset)
91
+ content = build_content(cyan, magenta, dim, white, reset)
89
92
 
90
93
  clean_len = content.gsub(/\e\[\d+m/, '').length
91
94
  padding = ' ' * [@width - clean_len, 0].max
92
95
 
93
96
  lines = []
94
97
  lines << top_border
95
- lines << "#{accent}│#{reset}#{content}#{padding}#{accent}│#{reset}"
98
+ lines << "#{cyan}│#{reset}#{content}#{padding}#{cyan}│#{reset}"
96
99
  lines << bottom_border
97
100
 
98
101
  # Adiciona autocomplete se ativo
99
102
  if @autocomplete_active && @autocomplete_items.any?
100
- lines << render_autocomplete(accent, dim, white, reset)
103
+ lines << render_autocomplete(cyan, magenta, dim, white, reset)
101
104
  end
102
105
 
103
106
  lines.join("\n")
@@ -188,7 +191,7 @@ module Gsd
188
191
  lines.join("\n")
189
192
  end
190
193
 
191
- def build_content(accent, dim, white, reset)
194
+ def build_content(accent, accent2, dim, white, reset)
192
195
  prompt = "#{accent}❯#{reset} "
193
196
  if @text.empty?
194
197
  "#{prompt}#{dim}#{@placeholder}#{reset}"