100% Chance of Social
I’ve always been fascinated by weather, and one million websites ago I thought about becoming a meteorologist. That’s probably because I grew up in south Louisiana where we have a “season” for destructive storms. I remember riding out Hurricane Andrew on a house boat with my family. The reason? Well, if the water rose, we would to. We survived the storm and the dream of replacing Jim Cantore waned.
Fast forward to this month and one of my favorite clients, Jimmy Eat World, is releasing their new record Damage, with an umbrella gracing the Morning Breath designed album artwork. No doubt a nod to the aspirations of my youth. Anyway, they needed a way to premiere “Damage” in an accessibly fun way that played on their social strengths. What’s important here is not to satisfy their current fans. The music should do that. Instead we’re trying to trick non fans into participating in an experience that just so happens to be playing Jimmy Eat World music.
I don’t usually take artwork so literally when coming up with ideas, but fuck it, let’s make it rain. Let’s build an experience that translates social activity into a storm and brings out everyone’s inner meteorologist.
Precipitation
Getting Rain Activity
The social activity we’ll be polling is hashtags from Twitter and we can do that by creating a simple twitter.rb
worker that utilizes the excellent TweetStream gem to listen to Twitter’s Streaming API and using Pusher we can send these updates to the client.
We begin with configuring Pusher and TweetStream by supplying the appopriate API credentials:
# Configure Pusher
Pusher.app_id = ""
Pusher.key = ""
Pusher.secret = ""
# Configure TweetStream
TweetStream.configure do |config|
config.consumer_key = ""
config.consumer_secret = ""
config.oauth_token = ""
config.oauth_token_secret = ""
config.auth_method = :oauth
end
Then we create our TweetStream tracking event which gets notified everytime someone tweets the hashtag #damage. This status is then stored in a local database and pushed to the client for rain drop generation.
# Listen for incoming tweets
TweetStream::Client.new.track("#damage") do |status, client|
tweet = Tweet.from_twitter(status)
Pusher['damage'].trigger('new_tweet', tweet.to_json(methods: [:track])
end
Our tweet.rb
and track.rb
models also include some basic associations that allow us to create a simple incrementer to count the amount of tweets accumulated for each track. We can compare this count to the track’s threshold to figure out if it was unlocked.
class Track
include DataMapper::Resource
property :tweets_count, Integer, default: 0
property :threshold, Integer, default: 1000
has n, :tweets
end
class Tweet
include DataMapper::Resource
belongs_to :track
after :create, :increment_counter_cache
def increment_counter_cache
if self.track.reload.tweets_count >= self.track.reload.threshold
self.track.update(state: "unlocked")
else
self.track.update(tweets_count: self.track.reload.tweets_count + 1)
end
end
end
In order to test TweetStream on our local machine, I simply create a Rakefile
require 'rubygems'
require 'bundler/setup'
namespace :jobs do
desc "Heroku worker"
task :work do
exec('ruby ./twitter.rb run')
end
end
and run:
$ rake jobs:work
Our server, Heroku, requires a Procfile
worker: env TERM_CHILD=1 QUEUES=* rake jobs:work
and scaling a single worker process:
$ heroku ps scale worker=1
Creating Rain Drops
I used CSS3 to create the drops on Call Drops, but for this I wanted to use Canvas to maximize performance and compatibility. I found the excellent HTML5 JavaScript framework Kinetic.JS that makes simple animation tweening a breeze.
Let’s start by setting up our stage and the main layer our graphics will live.
WIDTH = $(window).width()
HEIGHT = $(window).height()
stage = new Kinetic.Stage
container: 'rain'
layer = new Kinetic.Layer()
stage.add layer
Rather than learn the ends and outs of Bezier Curves, I provided a simple png sprite image of the rain drop so I could get the exact shape I wanted.
dropImg = new Image()
dropImg = "/images/drop.png"
The drop itself consists of a Kinetic.JS group of two elements, drop and ripple, which are generated right above the page. The drop is created from our preloaded drop image above, and the ripple is created from a new Kinetic.Circle which is scaled down, stroked, and hidden initially. In addition to the drop and ripple, you can customize the elements by using the provided Twitter status
varibales.
makeDrop = (status) ->
# Create group to hold all elements
group = new Kinetic.Group
x: Math.random() * WIDTH + 1
y: 0
# Create the drop from image
drop = new Kinetic.Image
x : -7
y : -25
image : dropImg
width : 14
height : 25
group.add drop
# Create ripple base
ripple = new Kinetic.Circle
y : -10
radius : 0
scaleX : 5
stroke : 'white'
strokeScaleEnabled : false
strokeWidth : 4
visible : false
group.add ripple
The animation itself consists of two tweens:
- Fall - This animates the drop falling from the top of the page to bottom. Once the animation completes, we hide the drop and show the ripple.
- Splash - Once the ripple is shown, we grow the radius while also fading it out with easing out and in to simulate a delicate water ripple.
Let’s take a look at that series of animations.
# Animate the fall and splash with series of callbacks
fall = new Kinetic.Tween
node : group
duration : 3
y : HEIGHT - 100
onFinish: ->
drop.destroy()
ripple.show()
slash = new Kinetic.Tween
node : ripple
duration : 3
radius : 20
opacity : 0
easing : Kinetic.Easing.EaseOutIn
onFinish: ->
ripple.destroy()
splash.play()
fall.play()
We then listen for those Pusher updates from the server and create a new rain drop each time a new status is tweeted.
pusher = new Pusher ''
channel = pusher.subscribe 'damage'
channel.bind 'new_tweet', (status) ->
makeRain(status)
flash() if status.nickname is "jimmyeatworld"
But what if Jimmy Eat World tweets? Make some lightning using Howler.JS of course.
lightning = new Howl(urls: ["/lightning.mp3"])
flash = ->
lightning.play()
$("#flash").fadeIn 0, () -> $("#flash").fadeOut(1000)
Rain Gauge
In addition to making sure it played audio and scaled responsively, I wanted to do something special with the mobile interface, so I decided to use the same real-time unlocking data to create a mobile udometer to compliment the desktop experience. The idea being that you could watch the drops falling on your desktop screen while gauging the unlock levels on your mobile device.
It was a fun experiment in dual interactive design and something I will no doubt revisit in the future.
Post Storm
Thanks for reading. I hope this sheds a bit of sunlight on what it takes to pull off a campaign like this. The power truly lies in all of the excellent libraries I used, so special thanks goes out to those authors. Follow me on Twitter for the latest and please let me know if you have any questions or comments.
Finally, don’t forget to pick up Jimmy Eat World’s eight studio album, Damage, in stores everywhere tomorrow!