On Tuesday, we launched a teaser campaign for the now announced Guns N’ Roses’ mega box set, Locked N’ Loaded. The fan experience was a mysterious new web property called GNR.FM which was promoted via posters around the world and on the GNR social properties. The site consisted of a countdown clock, some tough looking skulls, and several different paths a user could take which ultimately led to them sharing the news that “Destruction is Coming.” Fans. Went. Nuts. Search the phrase GNR.FM or hashtag #APPETITEFORDESTRUCTION on any social network to see for yourself.

If that isn’t exciting enough, I also decided to make this project the first I would develop with the JavaScript framework Vue.js. Building with Vue was an absolute dream and I could feel it’s structure making me a better developer every step of the way. For every issue I encountered, the Vue guide and developer community had well documented answers. In order to repay the favor, I’ve decided to take you through some of the components we developed for our experience and explain how Vue and other tech solved each problem. Note that this relates to the full experience available on mobile devices.

Intro

Translations powered by vue-i18n

The landing page of our experience helps set the tone of the application so we focused on speed, accessibility, and bold visuals. The first thing users notice is an animation flicking through each of the original five band members refreshed skull illustrations. That’s a pretty gnarly phrase. I knew I would be using these same five skulls in an image generator later so I needed a high quality transparent image with a low file size. Impossible? No! I created a single sprite sheet of all five skulls and used TinyPNG to make it as tiny as possible without losing noticeable quality. I then used the steps() function in CSS to create an infinite loop that would flick through each skull animation at a furious but orderly pace.

.skulls{

animation: switch 0.5s steps(5, end) infinite;

background-size: cover

}

from {

background-position: 0 0;

}

to {

background-position: -500% 0;

}

} @keyframes switch {from {background-position: 0 0;to {background-position: -500% 0;

Right underneath the skulls is the phrase “Destruction is Coming” or perhaps it says something different for you? That’s because Vue’s vue-i18n plugin makes it trivial to add multiple languages to your application globally or on a component level. Given the global appeal of Guns N’ Roses, we thought it was very important to add these translations throughout the experience. Thanks to our teams around the world for providing the proper translations since I only speak English and a little Cajun French.

i18n: {

messages: {

en: {

tagline: "Destruction is coming"

},

fr: {

tagline: "Destruction Vient"

},

es: {

tagline: "Viene Destrucción"

},

it: {

tagline: "Destruction Sta Arrivando"

},

pt: {

tagline: "“Destruction” Está Vindo"

},

de: {

tagline: "Destruction Kommt!"

},

ja: {

tagline: "デストラクションがやってくる。",

}

}

}

The landing page also includes a very simple <countdown> component. Now I usually use something like jQuery Countdown to accomplish this but I was trying to avoid using jQuery altogether on this project. Instead, I opted for a simple JavaScript interval combined with Moment.js and the plugin Moment Duration Format. Using Moment was a pretty lazy move on my part but I simply did not feel like writing code to pad zeros on the countdown. Incredibly, this is the entire Vue component file:

<template>

<time>{{ remaining }}</time>

</template> <script>

import moment from 'moment'

import momentDurationFormat from 'moment-duration-format' export default{

data() {

return {

time: moment(),

end: moment(new Date(Date.UTC(2018, 4, 4, 3, 0, 0))),

interval: null

}

},

computed: {

remaining() {

return moment.duration(

this.end.diff(this.time)

).format("dd hh mm ss")

}

},

mounted() {

this.interval = setInterval(() => {

this.time = moment()

window.location = "

}

}, 1000)

},

beforeDestroy() {

clearInterval(this.interval)

}

}

</script> if (moment().isAfter(this.end)) {window.location = " https://www.gunsnroses.com }, 1000)},beforeDestroy() {clearInterval(this.interval)

Clicking the Share Location button, prompts the user to share their current location using the geolocation Web API. Since I planned on using that location in several other components, I needed a way to store the user’s coordinates for future use. Vuex to the rescue. Vuex is Vue’s answer to state management and storing data is as easy as setting up a few mutations and committing the changes when required.

this.$store.commit("updatePosition", result)

Info

Once the user’s position is found, the application immediately redirects to another component which helps lead the user to their next action. This redirection is powered by the vue router plugin which associates Vue components with url routes. In addition to the Intro and this Info component, our application includes the routed components Extend, Compass, Camera, and Share. Once your router structure is configured, routing from one component to another programmatically is as easy calling this single method.

this.$router.push("info")

Users probably aren’t marveling in the fact that the application redirected given the fact that they’re now hearing a stream of an unreleased version of “Shadow of Your Love.” This audio is being played from a single element component which I’ve mapped to a Howler.js sound instance. However, this is a sub component to the main view and we need a way to signal the player component that it’s time to load and play the track. We can accomplish this by creating a simple Global Event Bus. My new favorite website Alligator.io has an excellent tutorial on the subject.

# new bus component import Vue from 'vue'

export const Bus = new Vue() # on the player component Bus.$on('tune-in', () => {

this.sound.play()

}) # from the info component Bus.$emit('tune-in')

Redirections and rock songs aside, the core function of the Info component is to inform the user of what their next possible action is. All roads lead to sharing. Here are the three possible paths:

Share — If the user’s position was not found, we would simply encourage them to share the experience with fans. Extend — If the user’s location was found but they were not near one of the posters we placed in the real world, we encouraged them to “extend the signal” which is just a fancy way of saying share with customization. Explore — If the user’s position was found and they were within 10000 meters of one of our posters, we would suggest that they travel to that location to see one of our real-world teasers up close and take a photo.

Here’s how we can use Vue’s conditional rendering to decide which of these three paths should be presented to the user.

<template v-if="this.$store.state.user">

<template v-if="this.$store.getters.distance < 10000">

<!-- Position found and near poster -->

</template>

<template v-else>

<!-- Position found and not near poster -->

</template>

</template>

<template v-else>

<!-- Position not found -->

</template>

Sweet. Now let’s take a look at the second of these paths, Extend.

Extend