Behind the Project: Pic A Fight
Last week was interesting. Chad and I took a random idea — a "photo off" FaceMash-like site built on the Instagram real-time API — from whiteboard to successful launch. The premise is simple: Pic A Fight displays two pictures, pick the picture you like the most, new ones load in, repeat. Users can even battle a particular user's pictures instead of globally, making it a bit more personal and incentivizing folks to share their battle pages. Here's a look at how Pic A Fight quickly came to fruition.
Pic A Fight at the whiteboard stage.
Nerdy BitsPic A Fight was built on Ruby 1.9.2 with RVM and Rails 3 hosted on several Amazon EC2 micro instances (we spun up 5 during load) behind an Amazon elastic load balancer instance, using Amazon RDS (it's like a database instance) for our MySQL store and with images hosted via Instagram's Amazon S3.
The crux of it all - top ranked pictures
We used the following Ruby gems:
- mysql2, a faster MySQL library for Ruby
- Chad's fork of the instagram gem
- mixpanel for helping track some basic metrics like skips, likes, follows.
- compass for my Sass usage
- jquery-rails for replacing Rails' Prototype default with jQuery UJS (with Google CDN-hosted jQuery 1.5.1)
We also used whos.amung.us for basic real-time stats to find out how many concurrent users we had and what pages they were on.
Overall nothing too complex about the app; our models were pretty light. The beefiest part of the app is probably a tie between sys admin stuff related to spawning all the EC2 instances and the Elo rating system used for ranking pictures. The first version didn't even have sessions like some of the screenshots in this post do with the username displayed as logged in after OAuthing Instagram. After a user connected their Instagram account, we accessed their recent media feed and sucked down all the metadata related to 21 of their most recent images. Instagram already has three sizes of users images handy (612x612, 306x306, 150x150) so we didn't need to resize anything ourselves.
For Elo rating, Chad stored two versions: global Elo wins and losses, as well as local Elo (just between a user's own pictures, enabling separate ranking for user battles). Some later optimization included removing low ranked images to keep things interesting — people like voting on aesthetically pleasing images and that keeps them using the site. As for Instagram's real-time aspect (inspired by Pubsubhubub), each new image published by a user auth'd with Pic A Fight would be POSTed to special Pic A Fight callback URL and we would add the new picture to that user's pics archive/profile and include the new image in battle circulation.
And so on. After each battle, the results would be tossed into an object and sent client-side via JSON to display in a bar under the battle pics.
As for the design, I wanted to try out a subtle "page curling" effect with
:after pseudo-class selectors on the main content div. I started making the site with a blue near Instagram's blue, as seen in the blue top wrapper div (which has a subtle top and bottom border of lighter and darker blues to make it smooth).
From there I wanted a "super" header and decided to go with a desaturated complement of the aforementioned blue. Sass makes this easy with some desaturate(), complement() and lighten/darken() tinkering. I was going to use a grain background but I had already done that for the body background and didn't want to overdo it so I experimented with a large pixelated transparent background. I ended up making use of CSS3's
background-size to adjust the size of the blocks on the fly. The header navigation is bounded by a mostly transparent box (
rgba(0,0,0,0.075)) and a slightly darker 1px inner shadow. I'm also a big fan of using a lighter text-shadow than the background, but subtle. Subtle is key. Overdone shadows just look awful. Avoid text or box shadow blur if you can.
The main content box has a 1px inner border to draw contrast to the 3px drop shadow and subtle blue gradient. I wanted to play with a new Sass mixin I wrote for CSS3 border gradients but it appears they're not yet supported by webkit. Since this was a quick and dirty project, I didn't spend the time to setup Typekit but I love the hell out of Typekit in all of my other sites.
LaunchWednesday evening we had the prototype pretty much done and set it up on a random security-through-obscurity subdomain while the main domain was still displaying a GoDaddy parked page. We sent this URL around to some fellow Y Combinator folks to get basic feedback.
Paul Graham chimed in and suggested some vital things: 1) putting the images closer together making them easier to visually compare (and reduce the distance users had to move to their mouse to click) and 2) urging that we get higher quality pictures on the site ASAP. During this initial phase we only had two or three Instagram accounts connected via OAuth.
Anyone can vote on pics without an account, but we can only add images from Instagram users that connect to Pic A Fight with OAuth. It was important to seed the site with good pictures before pointing lots of people at it. As for reducing voting friction, we added support for voting with cursor and number keys:
Psst! Pro tip: Vote with your keyboard! Use the left/right arrows or 1/2. Skip with down arrow or 3.
That was one of the best decisions made and made it very easy for folks to play many rounds without fatigue. One particular user completed over 1,400 rounds! Another small update we did before launch was add a skip button. I got some feedback (it was either Ethan, Kyle or Aaron.. forgot!) saying that people don't like making hard decisions/close calls and just want to skip ahead to more interesting rounds. It turns out the skip button gets used a good deal.
With those tweaks ready and deployed to the live site, I direct messaged 10 or so friends on Twitter Thursday night. In particular these were friends that I knew used Instagram. I asked them for basic feedback and if they wanted to connect their Instagram account to preload the site with more pictures and to not share the site until 10am the next day.
Friday morning arrived. Chad and I greeted each other on Campfire, I made my morning eggs and then we got ready for getting the word out. It started with some tweets, sharing on Facebook and Tumblr, a posting on Hacker News, which then got picked up by The Next Web and TechCrunch. By the end of the first day Pic A Fight had been the subject of over 1,000 tweets, had over 100,000 photo battles completed, 15,000 Instagram photos added, up to 500 concurrent users on 5 Amazon EC2 micro instances.
- TechCrunch: Instagrams Go Head-To-Head With Pic A Fight
- The Next Web: Instagram meets FaceMash in Pic A Fight
- Hacker News: Pic A Fight: Instagram meets FaceMash
- Including some Twitter love from our pals at Photojojo and Laughing Squid.
By the end of the weekend, Pic A Fight had 43,000 photos added from close to 1,000 Instagram users, 180,000 completed battles and some interesting stats on popular filter usage:
mysql> select igram_filter, count(igram_filter) as c from pics group by igram_filter order by c desc; +--------------+------+ | igram_filter | c | +--------------+------+ | None | 9539 | | X-Pro II | 7239 | | Lomo-fi | 5365 | | Earlybird | 5058 | | Hefe | 3000 | | Nashville | 2012 | | Sutro | 1730 | | Walden | 1639 | | Toaster | 1561 | | Lord Kelvin | 1316 | | Apollo | 1266 | | Gotham | 1251 | | 1977 | 1231 | | Inkwell | 675 | +--------------+------+ 14 rows in set (1.34 sec)
Just a brief look at a side project of ours that got some unexpected attention. :) The end.
Thanks to Chad Etzel for reading drafts of this.