Skip to content

Commit ff22b19

Browse files
committed
Add particle effects
1 parent b89d99e commit ff22b19

File tree

3 files changed

+153
-36
lines changed

3 files changed

+153
-36
lines changed

app/index.html

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
<script src="bundle.js" inline></script>
77
</head>
88
<body>
9+
<div class="background"></div>
10+
<canvas class="canvas-overlay"></canvas>
911
<pre id="editor"></pre>
1012
<div class="streak-container">
1113
<div class="current">Current Streak</div>

app/scripts/app.coffee

Lines changed: 111 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -12,24 +12,54 @@ class App
1212
POWER_MODE_ACTIVATION_THRESHOLD: 250
1313
STREAK_TIMEOUT: 10 * 1000
1414

15+
MAX_PARTICLES: 500
16+
PARTICLE_NUM_RANGE: [5..12]
17+
PARTICLE_GRAVITY: 0.075
18+
PARTICLE_ALPHA_FADEOUT: 0.96
19+
PARTICLE_VELOCITY_RANGE:
20+
x: [-1, 1]
21+
y: [-3.5, -1.5]
22+
23+
PARTICLE_COLORS:
24+
"text": [255, 255, 255]
25+
"text.xml": [255, 255, 255]
26+
"keyword": [0, 221, 255]
27+
"variable": [0, 221, 255]
28+
"meta.tag.tag-name.xml": [0, 221, 255]
29+
"keyword.operator.attribute-equals.xml": [0, 221, 255]
30+
"constant": [249, 255, 0]
31+
"constant.numeric": [249, 255, 0]
32+
"support.constant": [249, 255, 0]
33+
"string.attribute-value.xml": [249, 255, 0]
34+
"string.unquoted.attribute-value.html": [249, 255, 0]
35+
"entity.other.attribute-name.xml": [129, 148, 244]
36+
1537
EXCLAMATION_EVERY: 10
1638
EXCLAMATIONS: ["Super!", "Radical!", "Fantastic!", "Great!", "OMG",
1739
"Whoah!", ":O", "Nice!", "Splendid!", "Wild!", "Grand!", "Impressive!",
1840
"Stupendous!", "Extreme!", "Awesome!"]
1941

2042
currentStreak: 0
2143
powerMode: false
44+
particles: []
45+
particlePointer: 0
46+
lastDraw: 0
2247

2348
constructor: ->
2449
@$streakCounter = $ ".streak-container .counter"
2550
@$exclamations = $ ".streak-container .exclamations"
2651
@$reference = $ ".reference-screenshot-container"
52+
@$editor = $ "#editor"
53+
@canvas = @setupCanvas()
54+
@canvasContext = @canvas.getContext "2d"
2755
@$download = $ ".download-button"
2856

2957
@$body = $ "body"
3058

3159
@debouncedSaveContent = _.debounce @saveContent, 300
3260
@debouncedEndStreak = _.debounce @endStreak, @STREAK_TIMEOUT
61+
@throttledShake = _.throttle @shake, 100, trailing: false
62+
@throttledSpawnParticles = _.throttle @spawnParticles, 25, trailing: false
3363

3464
@editor = @setupAce()
3565
@loadContent()
@@ -38,10 +68,14 @@ class App
3868
@editor.getSession().on "change", @onChange
3969
$(window).on "beforeunload", -> "Hold your horses!"
4070

41-
$(".instructions-container, .instructions-button").on "click", => $("body").toggleClass "show-instructions"
71+
$(".instructions-container, .instructions-button").on "click", ->
72+
$("body").toggleClass "show-instructions"
73+
4274
@$reference.on "click", => @$reference.toggleClass "active"
4375
@$download.on "click", @onClickDownload
4476

77+
window.requestAnimationFrame? @onFrame
78+
4579
setupAce: ->
4680
editor = ace.edit "editor"
4781

@@ -51,16 +85,28 @@ class App
5185
editor.getSession().setMode "ace/mode/html"
5286
editor.session.setOption "useWorker", false
5387
editor.session.setFoldStyle "manual"
88+
editor.$blockScrolling = Infinity
5489

5590
editor
5691

92+
setupCanvas: ->
93+
canvas = $(".canvas-overlay")[0]
94+
canvas.width = window.innerWidth
95+
canvas.height = window.innerHeight
96+
canvas
97+
5798
loadContent: ->
5899
return unless (content = localStorage["content"])
59100
@editor.setValue content, -1
60101

61102
saveContent: =>
62103
localStorage["content"] = @editor.getValue()
63104

105+
onFrame: (time) =>
106+
@drawParticles time - @lastDraw
107+
@lastDraw = time
108+
window.requestAnimationFrame? @onFrame
109+
64110
increaseStreak: ->
65111
@currentStreak++
66112
@showExclamation() if @currentStreak > 0 and @currentStreak % @EXCLAMATION_EVERY is 0
@@ -93,21 +139,63 @@ class App
93139
$exclamation.remove()
94140
, 3000
95141

142+
getCursorPosition: ->
143+
{left, top} = @editor.renderer.$cursorLayer.getPixelPosition()
144+
left += @editor.renderer.gutterWidth + 4
145+
{x: left, y: top}
146+
147+
spawnParticles: (type) ->
148+
return unless @powerMode
149+
150+
{x, y} = @getCursorPosition()
151+
numParticles = _(@PARTICLE_NUM_RANGE).sample()
152+
color = @getParticleColor type
153+
_(numParticles).times =>
154+
@particles[@particlePointer] = @createParticle x, y, color
155+
@particlePointer = (@particlePointer + 1) % @MAX_PARTICLES
156+
157+
getParticleColor: (type) ->
158+
@PARTICLE_COLORS[type] or [255, 255, 255]
159+
160+
createParticle: (x, y, color) ->
161+
x: x
162+
y: y + 10
163+
alpha: 1
164+
color: color
165+
velocity:
166+
x: @PARTICLE_VELOCITY_RANGE.x[0] + Math.random() *
167+
(@PARTICLE_VELOCITY_RANGE.x[1] - @PARTICLE_VELOCITY_RANGE.x[0])
168+
y: @PARTICLE_VELOCITY_RANGE.y[0] + Math.random() *
169+
(@PARTICLE_VELOCITY_RANGE.y[1] - @PARTICLE_VELOCITY_RANGE.y[0])
170+
171+
drawParticles: (timeDelta) =>
172+
@canvasContext.clearRect 0, 0, @canvas.width, @canvas.height
173+
174+
for particle in @particles
175+
continue if particle.alpha <= 0.1
176+
177+
particle.velocity.y += @PARTICLE_GRAVITY
178+
particle.x += particle.velocity.x
179+
particle.y += particle.velocity.y
180+
particle.alpha *= @PARTICLE_ALPHA_FADEOUT
181+
182+
@canvasContext.fillStyle = "rgba(#{particle.color.join ", "}, #{particle.alpha})"
183+
@canvasContext.fillRect Math.round(particle.x - 1), Math.round(particle.y - 1), 3, 3
184+
96185
shake: ->
97-
intensity = -(Math.random() * 5 + 5)
98-
x = intensity * (Math.random() > 0.5 ? -1 : 1)
99-
y = intensity * (Math.random() > 0.5 ? -1 : 1)
186+
return unless @powerMode
100187

101-
translate = "translate3D(#{x}px, #{y}px, 0)"
102-
@$body.css
103-
"webkit-transform": translate
104-
"transform": translate
188+
intensity = 1 + 2 * Math.random() * Math.floor(
189+
(@currentStreak - @POWER_MODE_ACTIVATION_THRESHOLD) / 100
190+
)
191+
x = intensity * (if Math.random() > 0.5 then -1 else 1)
192+
y = intensity * (if Math.random() > 0.5 then -1 else 1)
193+
194+
@$editor.css "margin", "#{y}px #{x}px"
105195

106196
setTimeout =>
107-
@$body.css
108-
"webkit-transform": "none"
109-
"transform": "none"
110-
, 50
197+
@$editor.css "margin", ""
198+
, 75
111199

112200
activatePowerMode: =>
113201
@powerMode = true
@@ -133,6 +221,16 @@ class App
133221
@increaseStreak()
134222
@debouncedEndStreak()
135223

136-
@shake() if @powerMode
224+
@throttledShake()
225+
226+
pos = if e.data.action is "insertText"
227+
e.data.range.end
228+
else
229+
e.data.range.start
230+
231+
token = @editor.session.getTokenAt pos.row, pos.column
232+
233+
_.defer =>
234+
@throttledSpawnParticles(token.type) if token
137235

138236
$ -> new App

app/styles/index.scss

Lines changed: 40 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,8 @@ body, html {
1414
}
1515

1616
body {
17-
background-image: url(~assets/images/logo.png);
18-
background-position: 50% 50%;
19-
background-repeat: no-repeat;
20-
background-size: 520px 476px;
21-
position: relative;
2217

23-
&.power-mode {
24-
animation: body-power 2s infinite both;
25-
}
18+
position: relative;
2619
}
2720

2821
button {
@@ -68,15 +61,43 @@ body.show-instructions {
6861
}
6962
}
7063

64+
.background {
65+
position: absolute;
66+
top: 0;
67+
left: 0;
68+
bottom: 0;
69+
right: 0;
70+
z-index: 0;
71+
opacity: 0.5;
72+
background-image: url(~assets/images/logo.png);
73+
background-position: 50% 50%;
74+
background-repeat: no-repeat;
75+
background-size: 520px 476px;
76+
77+
.power-mode & {
78+
background-image: url(~assets/images/logo-power.png);
79+
animation: background-power 2s infinite both;
80+
}
81+
}
82+
83+
.canvas-overlay {
84+
position: absolute;
85+
top: 0;
86+
left: 0;
87+
z-index: 10;
88+
pointer-events: none;
89+
}
90+
7191
#editor {
7292
position: absolute;
7393
top: 0;
7494
bottom: 0;
7595
left: 0;
7696
right: 0;
7797
margin: 0;
78-
background-color: rgba(0,0,0,0.5);
98+
background: transparent;
7999
font-size: 14px;
100+
transform: translate3d(0, 0, 0);
80101

81102
.ace_keyword,
82103
.ace_meta,
@@ -255,8 +276,6 @@ body.power-mode #editor {
255276
}
256277

257278
body.power-mode {
258-
background-image: url(~assets/images/logo-power.png);
259-
260279
.power-mode-indicator {
261280
opacity: 1;
262281
animation: power-mode-indicator 750ms linear both;
@@ -295,23 +314,23 @@ body.power-mode {
295314
}
296315
}
297316

298-
@keyframes body-power {
299-
0% {
300-
animation-timing-function: ease-out;
301-
}
302-
50% {
303-
background-size: 650px 595px;
304-
animation-timing-function: ease-in;
305-
}
306-
}
307-
308317
@keyframes exclamation {
309318
100% {
310319
opacity: 0;
311320
transform: translate3D(0, 100px, 0);
312321
}
313322
}
314323

324+
@keyframes background-power {
325+
0% {
326+
animation-timing-function: ease-out;
327+
}
328+
50% {
329+
transform: scale(1.5);
330+
animation-timing-function: ease-in;
331+
}
332+
}
333+
315334
@keyframes power-mode-indicator {
316335
0% { transform: matrix3d(0.5, 0, 0, 0, 0, 0.5, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); }
317336
3.2% { transform: matrix3d(0.673, 0.192, 0, 0, 0.126, 0.673, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); }
@@ -333,5 +352,3 @@ body.power-mode {
333352
84.68% { transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); }
334353
100% { transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); }
335354
}
336-
337-

0 commit comments

Comments
 (0)