296 lines
8.5 kB
1
defmodule BlogWeb.RainbowLive do
2
use BlogWeb, :live_view
3
require Logger
4
5
@rainbow_colors [
6
"#FF0000", # Red
7
"#FF7F00", # Orange
8
"#FFFF00", # Yellow
9
"#00FF00", # Green
10
"#0000FF", # Blue
11
"#4B0082", # Indigo
12
"#9400D3" # Violet
13
]
14
@frame_interval 50 # 50ms between frames
15
@max_radius 300
16
@animation_steps 60
17
18
# Add DVD animation configuration
19
@dvd_speed 2
20
@viewport_width 600
21
@viewport_height 400
22
@logo_width 100
23
@logo_height 50
24
25
# Add these module attributes
26
@particle_count 12 # Number of particles per explosion
27
@particle_lifetime 80 # How long particles live
28
29
def mount(_params, _session, socket) do
30
if connected?(socket) do
31
Process.send_after(self(), :animate, @frame_interval)
32
end
33
34
{:ok,
35
assign(socket,
36
rainbows: [], # List of rainbow states
37
letters: [], # Add letters state back
38
particles: [], # Add particles state
39
dvd_pos: %{
40
x: Enum.random(-300..300),
41
y: Enum.random(-200..200),
42
dx: @dvd_speed,
43
dy: @dvd_speed,
44
hue: 0
45
},
46
meta_attrs: [
47
%{name: "title", content: "Type shit and hear sounds and see wild shit or whatever"},
48
%{name: "description", content: "Bobby got high and made it so it looks wild when you press keys, theres web audio too but its broken."},
49
%{property: "og:title", content: "Type shit and hear sounds and see wild shit or whatever"},
50
%{property: "og:description", content: "Bobby got high and made it so it looks wild when you press keys, theres web audio too but its broken."},
51
%{property: "og:type", content: "website"}
52
],
53
page_title: "lol start typing and see what happens"
54
)}
55
end
56
57
def handle_event("keydown", %{"key" => key}, socket) when byte_size(key) == 1 do
58
# Create position for all animations
59
pos_x = Enum.random(-100..100)
60
pos_y = Enum.random(-50..50)
61
62
# Create a new rainbow
63
new_rainbow = %{
64
id: System.unique_integer([:positive]),
65
frame: 0,
66
x: pos_x,
67
y: pos_y
68
}
69
70
# Create particles for spiral explosion
71
new_particles = for i <- 1..@particle_count do
72
angle = i * (2 * :math.pi / @particle_count)
73
%{
74
id: System.unique_integer([:positive]),
75
frame: 0,
76
x: pos_x,
77
y: pos_y,
78
dx: :math.cos(angle) * 3,
79
dy: :math.sin(angle) * 3,
80
hue: Enum.random(0..360),
81
size: Enum.random(5..15),
82
rotation: angle * 180 / :math.pi
83
}
84
end
85
86
# Create a new letter with enhanced animation
87
new_letter = %{
88
id: System.unique_integer([:positive]),
89
char: key,
90
frame: 0,
91
x: pos_x,
92
y: pos_y,
93
size: 200, # Bigger initial size
94
rotation_speed: Enum.random(-5..5),
95
rotation: 0,
96
scale: 0.1 # Start small and grow
97
}
98
99
{:noreply,
100
assign(socket,
101
rainbows: [new_rainbow | socket.assigns.rainbows],
102
letters: [new_letter | socket.assigns.letters],
103
particles: new_particles ++ (socket.assigns[:particles] || [])
104
)}
105
end
106
107
def handle_event("keydown", _key, socket), do: {:noreply, socket}
108
109
def handle_info(:animate, socket) do
110
Process.send_after(self(), :animate, @frame_interval)
111
112
# Update DVD position
113
dvd_pos = update_dvd_position(socket.assigns.dvd_pos)
114
115
# Update rainbows
116
updated_rainbows = socket.assigns.rainbows
117
|> Enum.map(fn rainbow ->
118
%{rainbow | frame: rainbow.frame + 1}
119
end)
120
|> Enum.reject(fn rainbow ->
121
rainbow.frame >= @animation_steps
122
end)
123
124
# Update letters with growth and rotation
125
updated_letters = socket.assigns.letters
126
|> Enum.map(fn letter ->
127
new_scale = min(1.0, letter.scale + 0.05) # Grow to full size
128
%{letter |
129
frame: letter.frame + 1,
130
rotation: letter.rotation + letter.rotation_speed,
131
scale: new_scale
132
}
133
end)
134
|> Enum.reject(fn letter ->
135
letter.frame >= @animation_steps
136
end)
137
138
# Update particles
139
updated_particles = socket.assigns.particles
140
|> Enum.map(fn particle ->
141
%{particle |
142
frame: particle.frame + 1,
143
x: particle.x + particle.dx,
144
y: particle.y + particle.dy,
145
rotation: particle.rotation + 5
146
}
147
end)
148
|> Enum.reject(fn particle ->
149
particle.frame >= @particle_lifetime
150
end)
151
152
{:noreply, assign(socket,
153
rainbows: updated_rainbows,
154
letters: updated_letters,
155
particles: updated_particles,
156
dvd_pos: dvd_pos
157
)}
158
end
159
160
# Add DVD position update logic
161
defp update_dvd_position(pos) do
162
new_x = pos.x + pos.dx
163
new_y = pos.y + pos.dy
164
165
{dx, new_hue} = if new_x <= -(@viewport_width/2) + @logo_width or new_x >= (@viewport_width/2) - @logo_width do
166
{-pos.dx, rem(pos.hue + 60, 360)}
167
else
168
{pos.dx, pos.hue}
169
end
170
171
{dy, final_hue} = if new_y <= -(@viewport_height/2) + @logo_height or new_y >= (@viewport_height/2) - @logo_height do
172
{-pos.dy, rem(new_hue + 60, 360)}
173
else
174
{pos.dy, new_hue}
175
end
176
177
%{
178
x: new_x,
179
y: new_y,
180
dx: dx,
181
dy: dy,
182
hue: final_hue
183
}
184
end
185
186
defp calculate_arcs(frame) do
187
progress = frame / @animation_steps
188
189
@rainbow_colors
190
|> Enum.with_index()
191
|> Enum.map(fn {color, index} ->
192
radius = @max_radius - (index * 40)
193
arc_progress = min(1.0, progress * 1.2 - (index * 0.1))
194
195
if arc_progress > 0 do
196
generate_arc_path(radius, arc_progress, color)
197
end
198
end)
199
|> Enum.reject(&is_nil/1)
200
end
201
202
defp generate_arc_path(radius, progress, color) do
203
end_angle = :math.pi * progress
204
end_x = radius * :math.cos(end_angle)
205
end_y = radius * :math.sin(end_angle)
206
207
path = "M #{radius} 0 A #{radius} #{radius} 0 0 1 #{end_x} #{end_y}"
208
209
%{
210
path: path,
211
color: color,
212
stroke_width: 20
213
}
214
end
215
216
def render(assigns) do
217
~H"""
218
<div class="flex justify-center items-center min-h-screen bg-gray-900" phx-window-keydown="keydown">
219
<svg width="100%" height="100vh" viewBox="-300 -200 600 400">
220
<%!-- DVD Logo --%>
221
<g transform={"translate(#{@dvd_pos.x}, #{@dvd_pos.y})"}>
222
<path
223
d="M-50,-25 h100 v50 h-100 z M-30,-15 L-10,15 H10 L30,-15 H-30 Z M-20,0 h40 M-25,-10 h50"
224
fill={"hsl(#{@dvd_pos.hue}, 100%, 70%)"}
225
style="transform-origin: center; transform: scale(0.8);"
226
>
227
<animate
228
attributeName="opacity"
229
values="0.8;1;0.8"
230
dur="2s"
231
repeatCount="indefinite"
232
/>
233
</path>
234
<text
235
x="0"
236
y="5"
237
text-anchor="middle"
238
fill="white"
239
font-family="Arial Black"
240
font-size="20"
241
>DVD</text>
242
</g>
243
244
<%!-- Particles --%>
245
<%= for particle <- @particles do %>
246
<g transform={"translate(#{particle.x}, #{particle.y}) rotate(#{particle.rotation})"}>
247
<path
248
d="M0,-#{particle.size} L#{particle.size/2},#{particle.size} L-#{particle.size/2},#{particle.size} Z"
249
fill={"hsl(#{particle.hue}, 100%, 70%)"}
250
opacity={calculate_particle_opacity(particle.frame)}
251
/>
252
</g>
253
<% end %>
254
255
<%!-- Rainbows --%>
256
<%= for rainbow <- @rainbows do %>
257
<g transform={"translate(#{rainbow.x}, #{rainbow.y})"}>
258
<%= for arc <- calculate_arcs(rainbow.frame) do %>
259
<path
260
d={arc.path}
261
stroke={arc.color}
262
stroke-width={arc.stroke_width}
263
fill="none"
264
/>
265
<% end %>
266
</g>
267
<% end %>
268
269
<%!-- Letters --%>
270
<%= for letter <- @letters do %>
271
<text
272
x={letter.x}
273
y={letter.y}
274
font-size={letter.size}
275
fill="white"
276
text-anchor="middle"
277
dominant-baseline="middle"
278
opacity={calculate_letter_opacity(letter.frame)}
279
transform={"rotate(#{letter.rotation}, #{letter.x}, #{letter.y}) scale(#{letter.scale})"}
280
>
281
<%= letter.char %>
282
</text>
283
<% end %>
284
</svg>
285
</div>
286
"""
287
end
288
289
defp calculate_letter_opacity(frame) do
290
1 - (frame / @animation_steps)
291
end
292
293
defp calculate_particle_opacity(frame) do
294
1 - (frame / @particle_lifetime)
295
end
296
end
297