Axe Feather 2021


I recreated a 16-year old interactive ad. Experience it here. Get the source code here. Or keep reading for the full story.


Back in 2005 I reblogged a Flash-based interactive advert I’d discovered via del.icio,us. And if that sentence wasn’t early-naughties enough for you, buckle up…

A woman lies on a bed with her legs crossed, playfully wagging her finger.
This screenshot isn’t from the original site but from my homage to it. More on that later.

At the end of 2004, Unilever brand Axe (Lynx here in the UK) continued their strategy of marketing their deodorant as magically transforming young men into hyper-attractive sex gods. This is, of course, an endless battle, pitting increasingly sexually-charged advertisements against the fundamental experience of their product, which smells distinctly like locker rooms and school discos. To launch 2005’s new fragrance Feather, they teamed up with London-based design agency Dare Digital to create a game at domain (long since occupied by domain squatters).

In the game, the player’s mouse pointer becomes a feather which they can use to tickle an attractive young woman lying on a bed. The woman’s movements – which vary based on where she’s tickled – have been captured in digital video. This was aggressively compressed using the then-new H.263-ish Sorensen Spark codec to make a download just-about small enough to be tolerable for people still on dial-up Internet access (which was still almost as popular as broadband). The ad became a viral hit. I can’t tell you whether it paid for itself in sales, but it must have paid for itself in brand awareness: on Valentines Day 2005 it felt like it was all the Internet wanted to talk about.

Axe Feather logo visible via, circa August 2005, in a Firefox browser window.
The site was archived by the WayBack Machine… but it doesn’t work in a modern browser.

I suspect its success also did wonders for the career of its creative consultant Olivier Rabenschlag, who left Dare a few years later, hopped around Silicon Valley for a bit, then landed himself a job as Head of Creative (now Chief Creative Officer) with Google. Kudos.


I told you about the site 16 years ago: why am I telling you again? Because this site, which made headlines at the time, is gone.

And not just a little bit gone, like a television ad no longer broadcast but which might still exist on YouTube somewhere (and here it is – you’re welcome for the earworm). The website went down in 2009, and because it was implemented in Flash the content was locked away in a compiled, proprietary format, which has ceased to be meaningfully usable on the modern web.

IE-specific CSS with a comment "Ok, so the scrollbar is IE specific...but I like it, ok?? :)"
The parts of’s code that are openly readable don’t help much, but I love this comment, which carries the scent of the adolescent web in the same way at Lynx deodorant carries the scent of an adolescent human.

The ad was pioneering. Flash had only recently gained video support (this would be used the following year for the first version of YouTube), and it had so far been used mostly for non-interactive linear video. This ad was groundbreaking… but now it’s disappeared like so much other Flash work. And for all that Flash might have been bad for the web, it’s an important part of our digital history [recommended reading].

Ruffle window showing an empty bed.
Third-party Flash emulation is imperfect. I tried to make Axe Feather work in Ruffle and got… an empty bed? What is this, a metaphor for being a lonely nerd?

So on a whim… I decided to see if I could recreate the ad.

Call it lockdown fever if you like, because it’s certainly not the work of a sane mind to attempt to resurrect a 16-year-old Internet advertisement. But that’s what I did.


My plan: to reverse-engineer the digital assets (video, audio, cursor etc.) out of the original Flash file, and use them to construct a moderately-faithful recreation of the ad, suitable for use on the modern web. My version must:

  • Work in any modern browser, without Flash of course.
  • Work on mobile devices/with touchscreens, with all of the original functionality available without a keyboard (the original had secret content hidden behind keyboard keypresses). Nowadays, Rabenschlag knows to put mobile-first, but I think we can forgive him for not doing that twelve months before Flash Lite 2.0 would bring .flv support to mobile devices…
  • Indicate how much of the video content you’d seen, because we live in an era of completionists who want to know they’ve seen it all.
  • Depend on no third-party frameworks/libraries: just vanilla HTML, CSS, and JavaScript.

Let’s get started.


Handbrake converting 19.flv to MP4 format.
At this point I noticed that the videos had no audio tracks: the giggling and other sound effects must be stored separately.

I grabbed the compiled .swf file from and ran it through SWFExtract and an online decompiler: neither was individually able to extract all of the assets, but together they gave me a full set. I ran the .flv files through Handbrake to get myself a set of .mp4 files instead.

Two starting frames from the videos, annotated to show that they are not aligned to the same point.
In what appears to have been an exercise in size optimisation, the original authors cropped the videos differently depending on how much space was needed (e.g. if the subject stretched her arms above her head, more space would be required). Clearly, some re-alignment would be needed.

Seeing that the extracted video files were clearly designed to be carefully-positioned on a static background, and not all in the exact same position, I decided to make my job easier by combining them all together, and including the background layer (the picture of the bed) as a single video. Integrating the background with the subject meant that I was able to use video editing software to tweak the position, which I imagined would be much easier than doing so in code. Combining all of the video clips into a single file provides compression benefits as well as making it easier to encourage a browser to precache the entire video to begin with.

Four layer design. From bottom to top: web page, video (showing woman on bed), (transparent) canvas, cursor (shaped like a feather).
My design called for three “layers” above my web page: the video, a transparent (and usually hidden) canvas showing the hit areas for debugging purposes, and the feather-shaped cursor.

The longest clip was a little over 6 seconds long, so I split my timeline into blocks of 7 seconds, padding each clip with a freeze-frame of its final image to make each exactly 7 seconds long. This meant that calculating the position in the finished video to which I wanted to jump was as simply as multiplying the (0-indexed) clip number by 7 and seeking to that position. The additional “frozen” frames acted as a safety buffer in case my JavaScript code was delayed by a few milliseconds in jumping to the “next” block.

Davinci Resolve showing composition of the actress onto the bed in a timeline.
I used onion-skinning to help “line up” the actress with herself as I composited her onto the bed in a single unified video of 7-second blocks.

An additional challenge was that in the original binary, the audio files were stored separately from the video clips… and slightly longer than them! A little experimentation revealed that the ends of each clip lined up, presumably something to do with how Flash preloads and synchronises media streams. Luckily for me, the audio clips were numbered such that they mostly mapped to the order in which the videos appeared.

Once I had a video file suitable for use on the web (you can watch the entire clip here, if you really want to), it was time to write some code.

Video timeline showing that each 7-second block is comprised of the original clip plus padding, atop a background layer of the bed and each clip's associated audio.
It feels slightly wasteful that over 50% of the resulting video clip is a freeze-frame, but modern video compression algorithms like H.264 reduce the impact considerably and the resulting video file is about the same size as its more-optimised predecessor.

Regular old engineering

The theory was simple: web page, video, loop the first seven seconds until you click on it, then animate the cursor (a feather) and jump to another seven-second block before jumping back or, in some cases, on to a completely new seven second block. Simple!

Of course, any serious web development is always a little more complex than you first anticipate.

Game map illustrating transition between the states of Axe Feather 2021.
I extracted from the .swf 34 distinct animated clips, which I numbered 0 through 33. 6 and 30 appeared to be duplicates of others. 0 and 33 are each two “idling” states from which interaction can lead to other states. Note that my interpretation of the order and relationship of animation sequences differs from the original.

For example: nowadays, putting a video on a web page is as easy as a <video> tag. But, in an effort to prevent background web pages from annoying you with unexpected audio, modern browsers won’t let a video play sound unless user interaction is the reason that the video starts playing (or unmutes, if it was playing-but-muted to begin with). Broadly-speaking, that means that a definitive user action like a “click” event has to be in the call stack when your code makes the video play/unmute.

But changing the .currentTime of a video to force it into a loop: that’s fine! So I set the video to autoplay muted on page load, with a script to make it loop within its first seven-second block. The actress doesn’t make any sound in block 0 (position A) anyway; so I can unmute the video when the user interacts with a hotspot.

For best performance, I used window.requestAnimationFrame to synchronise my non-interactive events (video loops, virtual cursor repositioning). This posed a slight problem in that animationframes wouldn’t be triggered if the tab was moved to the background: the video would play through each seven-second block and into the next! Fortunately the visibilitychange event came to the rescue and I was able to pause the video when it wasn’t being actively watched.

I originally hoped to use the cursor: CSS directive to make the “feather” cursor, but there’d be no nice way to animate it. Comet Cursor may have been able to use animated GIFs as cursors back in 1997 (when it wasn’t busy selling all your personal information to advertisers, back when that kind of thing used to attract widespread controversy), but modern browsers don’t… presumably because it would be super annoying. They also don’t all respect cursor: none, so I used the old trick of using cursor: url(null.png), none (where null.png is an almost-entirely transparent 1×1 pixel image) to hide the original cursor, then position an image dynamically.  I usegetBoundingClientRect() to allow the video to resize dynamically in CSS and convert coordinates on it represented as percentages into actual pixel values and vice-versa: this allows it to react responsively to any screen size without breakpoints or excessive code.

Once I’d gone that far I was able to drop the GIF idea entirely and used a CSS animation for the “tickling” motion.

Woman on bed in idle position B, with hotspots highlighted on each arm, her hed, her chest, her stomach, her hips, the top of her legs, and the bottom of the leg that's extended straight below her.
The hotspot overlay was added as a debugging feature but I left it in the final version. Hold the space bar to highlight hit areas.

I added a transparent <canvas> element on top of the <video> on which the hit areas are dynamically drawn to help me test the “hotspots” and tweak their position. I briefly considered implementing a visual tool to help me draw the hotspots, but figured it wasn’t quite worth the time it would take.

As I implemented more and more of the game, I remembered one feature from the original that I’d missed: the “blowaway”. If you trigger block 31 – a result of tickling the woman’s nose – she’ll blow your cursor off the screen. It’s particularly fun because it subverts the player’s expectations of their user interface: once you’ve got past the surprise of your cursor being a feather, you quickly settle in to it moving like a regular cursor… but then control’s stolen from you and the cursor vanishes! (Well I thought it was cool… 16 years ago.)

A woman blows a feather away from her face.
Sometimes tickling her nose will make her blow your feather off the screen. That’ll show you.

So yeah: that was my project this weekend.

I can’t even begin to explain why anybody would do this. But I did it. If you haven’t already: go have a play. And if you’re interested in how it works, the source code’s free for you to explore.

Leave Me

Just wanted to share with you all a short film I discovered today, Leave Me, in which a recent widower deals with his grief through his wife’s broken digital camera. It’s only about five minutes long, but it’s absolutely breathtaking.

And… I think there’s something in my eye, or something. Where are those tissues? /sniff/

Easter Egg Hunting, Gravity Hooking, and Geocachers You Might Know

This afternoon, like last year, we took the opportunity to spend Easter Sunday hiding one another’s Easter eggs in the woods and then running around looking for them.

Paul & Rory

For some reason, this year Rory didn’t want me to be responsible for hiding his egg (something to do with his eventually being found up a tree, last year), so I ended up hiding Adam‘s, instead. I didn’t even put much effort into it: just propped it on a branch. This turned out to be a bad hiding place because Adam walked right back past it on his way back from hiding JTA‘s egg.

Adam's egg

Paul, meanwhile, hid my egg. He did a pretty good job of it, too, and eventually had to give me a couple of clues. “It’s near Barking Up The Wrong Tree,” he said, knowing perfectly well that this was a geocache that I hadn’t yet hunted for. I pulled out my GPSr and found the cache, and then started looking for my egg in the vicinity.

Adam in a Forest

In a particularly special bit of hiding, Rory managed to hide Matt P‘s egg so well that he himself couldn’t find it again. Eventually we all had to help hunt for Matt’s lost egg. Rory had helpfully taken a photo of the egg in it’s hiding place, but this photo was ultimately useless because it depicted nothing more distinctive than “a wood”, which we were unable to see for all of the trees. I suppose that if we were trying to get to a particular spot and then ascertain that we were in the right place, it would be useful, except for that fact that being in the exact right place would probably have been pretty obvious by the time we were standing on top of an Easter egg.

Hunting for Matt's egg

Finally, Adam basically “tripped over” the hidden egg, and all was well.

Matt finds his egg!

All in all, it was a fabulous afternoon out, and a great way to work off all the calories of Ruth‘s most-excellent Easter lunch (and just in time to be able to scoff down cakes and chocolate later in the afternoon).

Ruth, JTA, and Paul near the edge of the woods

In other news:

A Town Called Eureka Presents “Troma Night”

Matt made a blog post about a TV series – A Town Called Eureka – which he’s been watching. In episode 4 (which has just been broadcast in the UK, two weeks behind the US schedule) several of the characters get together in a cramped space full of technology to watch films, once a week. Matt observes that everything in this segment of the episode just reeks of Troma Night – all that’s missing is a sponge-throwing and a Hollywood Pizza delivery to make the two identical.

I’ve put a copy of the relevent scenes online: click here to watch. You’ll need Flash Player version 8 or above and a reasonably-fast internet connection.

The Mangohol Experiment – Day Two

Flushed with success at my wine-making efforts (which have ranged from “barely drinkable” to “good”) over the last few months, I thought I’d turn my hand to fermenting some different kinds of fruits in my spare time. The first of these that I decided to try is mangoes. So, a few mangoes from the greengrocer on Chalybeate Street and a few litres of additional mango juice from Morrisons later, I was ready to start. I kicked it off yesterday with a hunk of mango pulp, juice, sugar, and – of course – brewers’ yeast. This drink, I have decided, will be called “mangohol”. And if it turns out to be undrinkable, I’ll try my hand at distilling, too, and try to make a spirit out of it. =o)

This morning, I was quite surprised to find that the proto-beverage had escaped from the captivity of it’s bottle, forcing mango pulp up through the airlock and out onto the table by the sheer force of it’s expanding gases. It turns out that mangoes actually have quite a high sugar content, and the yeast in the bottle is having a bit of a party. I looked at my chopping board (which has pictures of various fruits and vegetables and suggestions on how to prepare and serve them). For mangoes, it reads: “Mango [sic] have a juicy, pale, orange flesh, which is full of flavour. Sliced lengthways and served in a fruit salad, puréed for ice creams and mousses, used in chutneys, veg curries, tarts, and pies.” Does it say anywhere, “Warning: may ferment explosively, spewing mango pulp across your surfaces?” Does it buggery.

The mangohol escapes from the bottle.

Mangohol spreading itself around.

So violent was the push of the excited fungi, they even managed to compress whole chunks of mango through the airlock, where they became lodged. I’ve no idea how – if it’s at all possible – I will get them out, but I’ll be using one of the larger-style airlocks for the rest of the brewing process.

Blocked airlock

Of course, it doesn’t take a physicist – even one who’s not been caught in the explosion of an immersion heater (whoever that might have been) – to tell you that the expansion of gasses in an enclosed space is a bad thing. In fact, what biologists might call an “uncontrolled yeast reaction in a sealed container” has another, more brutal, name amongst chemists and physicists. The name they use for it is “bomb.”

Thankfully I noticed the problem before the pressure became sufficient to detonate my (glass!) demijohn, and I had the sense to remove the cork and airlock from the neck of the bottle. No prizes for guessing what happened: suddenly, I found my face, my hands, my body, the room – pretty much everything, actually – showered with partially-fermented mango juice and pulp. It’s not nice stuff to be shot in the eye with. That said, it smells fantastic.

The majority of the drink remained in the bottle, and it’ll be continuing to ferment for a couple of weeks, yet (although I’ll be keeping a closer eye on it’s airlock). I’d never had guessed mangoes were so sugary, but this is really volatile stuff: having already diffused it the first time around I took a short video clip of it bubbling out (observe in the video how it “spurts out” if I hold my hand over the top of the bottle for a few seconds, and how much of the bottle is “froth” generated by the yeast):


Duality, my main desktop PC at home, has been misbehaving, and I’ve had to take it to pieces, both virtually (tracing driver DLL calls – yum!) and physically (computer components littering the floor, etc.). It’s been two years since it’s last reformat/reinstall, which is a pretty long time for a Windows XP box treated the way I’ve treated it, so it’s definately time for a rebuild.

Shame I couldn’t have done it sooner/later, as it’s put a dent both in my NaNoWriMo writing and in my ability to investigate some code for a client I’m dealing with “on the side”. It’s going to be a busy little weekend.

In other news, I’ve been playing with Gosu, a sprite animation/drawing layer module for Ruby. Ruby’s execution speed (within an object-oriented paradigm) lends itself well to 2D animation and games. Here’s a Flash animation showing a recording of what I came up with:

Yes, that really is the sprite for Dan in the Dan & Alex comic.

If you’re really sad (and using Windows – I haven’t tested it under other operating systems and can’t be arsed packaging up the C-layer stuff for them, yet), you can actually have a play. Just install Ruby 1.8.2-15 For Windows (15MB) and the game itself (740K).

If I get really bored or suddenly find a lot more free time, I might actually finish making a game out of the engine I’ve put together.

Edit: The download link for the game has now been fixed. If you downloaded it and just got error messages when you tried to run it, try again now.

More Flash: “Second Term”

Jon has posted to his blog about "Second Term", JibJab‘s most recent parody of the American policial system (you’ll remember It’s Good To Be In D.C. and This Land, which I blogged about earlier). In any case, the versions you’ll find on JibJab and Yahoo are surrounded by advertisements and can’t easily be resized (hey; if you’ve got the processing power to run it full-screen, do so!), so I’ve made a copy of it here for you to watch.

Flash MX 2004 Data Integration/XML Features

Geeky post.

I’ve just been playing with the data integration and XML-parsing features offered by the new version of Macromedia Flash (traditionally used for animation on the web, but nowadays used for all kinds of things, like those silly games at They’re actually quite impressive – here’s the result of my fiddling this evening (requires Flash Player 7 – not worth downloading just to see it, though):


It’s an RSS reader, connecting to the Scatmania web site – or, more simply – it connects to this web site and picks up the summaries of the most recent posts and provides them in a compact browser (with a little ‘Go…’ button to take you to the full article).

Why’ve I posted it here? Because it impressed me to see what Flash is capable of these days. Apologies to the non-geeks who are by now going “La la, la la…”

Bug In Internet Explorer… But How Do I Tell Anybody?

This morning, I found a bug in Internet Explorer. I wasn’t using it, of course, but I’d sent a Macromedia Flash file to a colleague by e-mail, who opened it in IE, but couldn’t.

It turns out that Internet Explorer can’t cope with opening Flash (.swf) files from the local file system, if the filename contains an apostrophe (e.g. “Dan’s Pictures.swf”). Crazy little bug, but I’ve tested it a little and it seems that this really is the case. But how do I report it?

Microsoft‘s web site, despite a redesign, is a sprawling mess. Eventually I gave up and submitted it as a ‘feature request’. I submitted PNG-support as a feature request, too, because it would be nice if sites like Abnib looked as good to the unwashed masses of IE users as it does to users of real web browsers.