Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Add "Download Firefox" Facebook tab

Adds "Download Firefox" Facebook tab under /facebook/downloadtab/ URL. The tab revolves around a download button (using the new download_firefox function) but also has Facebook social integration after the download in the form of timeline shares and app invites. The third screen has cross promos for Affiliates and Facebook messenger for Firefox. There's a slightly tweaked layout of the Download Tab available under /facebook/downloadtab/noscroll/ with fixed 800px height so that the Facebook tab iframe doesn't show a vertical scrollbar.

The Download Tab requires two local settings for its Facebook integration: FACEBOOK_PAGE_NAMESPACE, the username/namespace of the Facebook page in which the tab is to be installed, and FACEBOOK_APP_ID, the app ID for the Download Tab Facebook app.

Other additions include support for decorators in mozorg.utils.page, adding a bare template for URLs like this one that aren't part of the main mozilla.org site, server-side redirects for the top frame via small JavaScript template and other Facebook-related utilities.

Fixes bug 835506.
  • Loading branch information...
commit 21c571d4042b44c7a419e34b9f91ba54e31fa0f5 1 parent 4366c25
@khalifenizar khalifenizar authored
Showing with 2,420 additions and 7 deletions.
  1. 0  apps/facebookapps/__init__.py
  2. +71 −0 apps/facebookapps/decorators.py
  3. +7 −0 apps/facebookapps/models.py
  4. +1 −0  apps/facebookapps/templates/facebookapps/channel.html
  5. +126 −0 apps/facebookapps/templates/facebookapps/downloadtab.html
  6. +10 −0 apps/facebookapps/templates/facebookapps/js-redirect.html
  7. +21 −0 apps/facebookapps/urls.py
  8. +92 −0 apps/facebookapps/utils.py
  9. +21 −0 apps/facebookapps/views.py
  10. +19 −2 apps/mozorg/util.py
  11. +276 −0 media/css/facebookapps/animate.less
  12. +390 −0 media/css/facebookapps/downloadtab.less
  13. +303 −0 media/css/libs/h5bp_main.less
  14. +527 −0 media/css/libs/normalize.less
  15. BIN  media/img/facebookapps/downloadtab/bg-progress.png
  16. BIN  media/img/facebookapps/downloadtab/browser-linux.png
  17. BIN  media/img/facebookapps/downloadtab/browser-mac.png
  18. BIN  media/img/facebookapps/downloadtab/browser-win.png
  19. BIN  media/img/facebookapps/downloadtab/browser.png
  20. BIN  media/img/facebookapps/downloadtab/facepile.png
  21. BIN  media/img/facebookapps/downloadtab/firefox.png
  22. BIN  media/img/facebookapps/downloadtab/header-firefox.png
  23. BIN  media/img/facebookapps/downloadtab/icon_like_with_txt.png
  24. BIN  media/img/facebookapps/downloadtab/install1-mac.png
  25. BIN  media/img/facebookapps/downloadtab/install1-win.png
  26. BIN  media/img/facebookapps/downloadtab/install1-winIE8.png
  27. BIN  media/img/facebookapps/downloadtab/install2-mac.png
  28. BIN  media/img/facebookapps/downloadtab/install2-win.png
  29. BIN  media/img/facebookapps/downloadtab/install3-mac.png
  30. BIN  media/img/facebookapps/downloadtab/install3-win.png
  31. BIN  media/img/facebookapps/downloadtab/placeholder.gif
  32. BIN  media/img/facebookapps/downloadtab/sharing-image.jpg
  33. BIN  media/img/facebookapps/downloadtab/steps-installation.png
  34. BIN  media/img/facebookapps/downloadtab/tabzilla-tabless.png
  35. BIN  media/img/facebookapps/downloadtab/ticker-arrows.png
  36. +160 −0 media/js/facebookapps/App.js
  37. +13 −0 media/js/facebookapps/Base.js
  38. +140 −0 media/js/facebookapps/Facebook.js
  39. +73 −0 media/js/facebookapps/Slider.js
  40. +83 −0 media/js/facebookapps/Theater.js
  41. +12 −0 media/js/facebookapps/downloadtab-init.js
  42. +10 −0 media/js/facebookapps/downloadtab.js
  43. +8 −0 media/js/facebookapps/redirect.js
  44. +28 −1 settings/base.py
  45. +4 −0 settings/local.py-dist
  46. +10 −0 templates/bare.html
  47. +6 −2 templates/base-resp.html
  48. +6 −2 templates/base.html
  49. +3 −0  urls.py
View
0  apps/facebookapps/__init__.py
No changes.
View
71 apps/facebookapps/decorators.py
@@ -0,0 +1,71 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+import urllib
+from functools import wraps
+
+from django.shortcuts import redirect
+from django.utils.decorators import available_attrs
+
+from funfactory import urlresolvers
+
+from facebookapps import utils
+
+
+def facebook_locale(view_fn):
+ """
+ Redirects to Facebook user's locale retrieved from `signed_request` or
+ best supported approximation of it.
+ """
+ @wraps(view_fn, assigned=available_attrs(view_fn))
+ def _decorated_view(request, *args, **kwargs):
+ # Get locale from Facebook's `signed_request`
+ signed_request = utils.unwrap_signed_request(request)
+ try:
+ facebook_locale = signed_request['user']['locale']
+ except KeyError:
+ pass
+ else:
+ # If user's locale isn't supported, get the next best one.
+ # Defaults to en-US if no locale in same language as the
+ # user's is found.
+ best_locale = utils.get_best_locale(request, facebook_locale)
+
+ prefix = urlresolvers.get_url_prefix()
+
+ # Compare locales in lowercase just in case. Heh.
+ # If we aren't using the best locale, redirect to it
+ if prefix.locale.lower() != best_locale.lower():
+ prefix.locale = best_locale
+ locale_url = prefix.fix(request.path_info)
+ query_string = urllib.urlencode(request.GET)
+ final_url = '?'.join([locale_url, query_string])
+ return redirect(final_url)
+
+ return view_fn(request, *args, **kwargs)
+
+ return _decorated_view
+
+def extract_app_data(view_fn):
+ """
+ Extracts custom data from Facebook's `signed_request` and places it in
+ the `request.GET` dictionary.
+ """
+ @wraps(view_fn, assigned=available_attrs(view_fn))
+ def _decorated_view(request, *args, **kwargs):
+ # Get custom data from Facebook's `signed_request`
+ signed_request = utils.unwrap_signed_request(request)
+ try:
+ app_data = signed_request['app_data']
+ except KeyError:
+ pass
+ else:
+ # Add it to the GET dictionary
+ new_get = request.GET.copy()
+ new_get.update(app_data)
+ request.GET = new_get
+
+ return view_fn(request, *args, **kwargs)
+
+ return _decorated_view
View
7 apps/facebookapps/models.py
@@ -0,0 +1,7 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+from django.db import models
+
+# Create your models here.
View
1  apps/facebookapps/templates/facebookapps/channel.html
@@ -0,0 +1 @@
+<script src="//connect.facebook.net/en_US/all.js"></script>
View
126 apps/facebookapps/templates/facebookapps/downloadtab.html
@@ -0,0 +1,126 @@
+{% extends 'bare.html' %}
+
+{% block site_css %}
+ {{ css('facebookapps_downloadtab') }}
+{% endblock %}
+
+{% block body_class -%}
+ {% if noscroll %}
+ noscroll
+ {% endif %}
+{%- endblock %}
+
+{% block string_data %}
+ data-app-title="{{ _('Download Firefox') }}"
+ data-share-caption="{{ _('A fast and safe browser that’s easy to use.') }}"
+ data-share-description="{{ _('Just what the open web deserves.') }}"
+ data-invite-title="{{ _('Invite friends to Download Firefox') }}"
+ data-invite-message="{{ _('invited you to download a faster and safer browser, Firefox.') }}"
+{% endblock %}
+
+{% block content %}
+<header id="masthead">
+ <a href="http://www.mozilla.org/" id="tabzilla" class="tabzilla-closed" title="{{ _('Mozilla links') }}">{{ _('Mozilla') }}</a>
+ {% if noscroll %}
+ <a href="#" id="logo">
+ <img src="{{ media('img/facebookapps/downloadtab/header-firefox.png') }}" alt="{{ _('Firefox for desktop') }}" width="180">
+ </a>
+ {% endif %}
+
+ <div id="progress">
+ <ol>
+ <li class="step1">{{ _('Download') }}</li>
+ <li class="step2">{{ _('Share') }}</li>
+ <li class="step3">{{ _('Get involved') }}</li>
+ </ol>
+ <div class="ir" role="progressbar" aria-valuenow="1" aria-valuemin="1" aria-valuenow="3">{{ _('Step 1: Download') }}</div>
+ </div>
+</header>
+
+<div class="theater" id="theater-firefox">
+ <div class="stage" id="stage">
+ <div class="scene animated hidden" id="scene1">
+ <h1>{{ _('Different by design') }}</h1>
+
+ <ul id="features">
+ <li>{{ _('Proudly <span>non-profit</span>')|safe }}</li>
+ <li>{{ _('Innovating <span>for you</span>')|safe }}</li>
+ <li>{{ _('Fast, flexible, <span>secure</span>')|safe }}</li>
+ </ul>
+
+ {{ download_firefox(force_direct=true) }}
+ </div><!-- /#scene1 -->
+
+ <div class="scene animated hidden" id="scene2">
+ <h2>{{ _('Thanks for downloading Firefox') }}</h2>
+ <p class="message">{{ _('As a non-profit, we’re free to deliver continuous innovation to meet your browsing needs. Discover a new and faster Web.') }}</p>
+
+ <ol class="installation">
+ <li id="install1">
+ <span class="install-win">{{ _('Start the process by clicking Run.') }}</span>
+ {{ _('Your download should begin automatically. If not, <a id="{link_id}" href="{download_url}">click here</a>. It could take a few minutes, but it’s worth the wait.')|fe(link_id='direct-download-link', download_url='#download_url') }}
+ </li>
+ <li id="install2">
+ <span class="install-osx">{{ _('When prompted, drag the Firefox icon into the image of your Applications folder.') }}</span>
+ <span class="install-win">{{ _('Click Run to launch the Mozilla Firefox setup wizard. Then, just follow the steps (we’ve made the process as painless as possible).') }}</span>
+ </li>
+ <li id="install3">
+ <span class="install-osx">{{ _('Drag the Firefox icon from the Applications folder into the dock. Then, just click on Firefox whenever you want to use the web!') }}</span>
+ <span class="install-win">{{ _('Now you’re ready to leap boldly into a new era of Web surfing. Double-click on the Firefox icon whenever you want to go online.') }}</span>
+ </li>
+ </ol>
+
+ <a id="button" class="button js-share" href="#">{{ _('Share the open web') }}</a>
+ </div><!-- /#scene2 -->
+
+ <div class="scene animated hidden" id="scene3">
+ <h2>{{ _('Congratulations!') }}</h2>
+ <p class="message">{{ _('You’re now part of a global community working to build a brighter future for the Web.') }}</p>
+
+ <p class="message facepile">{{ _('Join the Firefox team and help spread the word.') }}</p>
+
+ <div id="slider-container">
+ <ul id="slider">
+ <li>
+ <h3>{{ _('Love Firefox? Like our Fan Page.') }}</h3>
+ <p>{{ _('Join our community to interact with over 13 million fans.') }}</p>
+ <img src="{{ media('img/facebookapps/downloadtab/icon_like_with_txt.png') }}" width="133" height="42" alt="{{ _('Like icon') }}" class="like-txt"/>
+ </li>
+ <li class="visuallyhidden">
+ <h3>{{ _('Facebook Messenger for Firefox') }}</h3>
+ <p>{{ _('Keep up with friends wherever you go on the web.') }}</p>
+ <a class="button" href="https://addons.mozilla.org/en-US/firefox/addon/facebook-messenger/" target="_top">{{ _('Turn On Messenger') }}</a>
+ </li>
+ <li class="visuallyhidden">
+ <h3>{{ _('Become a Firefox Affiliate') }}</h3>
+ <p>{{ _('Get the tools you need to share your love of Firefox.') }}</p>
+ <a class="button" href="https://www.facebook.com/Firefox/app_373528039377278" target="_top">{{ _('Learn more') }}</a>
+ </li>
+ <li class="visuallyhidden">
+ <h3>{{ _('Friends tell friends to download Firefox') }}</h3>
+ <p>{{ _('Invite friends to download a faster and safer browser.') }}</p>
+ <a class="button js-invite" href="#">{{ _('Invite Friends') }}</a>
+ </li>
+ </ul>
+ <button class="ir prev">{{ _('Previous') }}</button>
+ <button class="ir next">{{ _('Next') }}</button>
+ </div><!-- /#slider-container -->
+ </div><!-- /#scene3 -->
+ </div><!-- /#stage -->
+</div><!-- /#theater-firefox -->
+{% endblock %}
+
+{% block js %}
+ <div id="init-data"
+ data-app-id="{{ settings.FACEBOOK_APP_ID }}"
+ data-page-namespace="{{ settings.FACEBOOK_PAGE_NAMESPACE }}"
+ data-active-scene="{{ request.GET.get('scene') }}"
+ data-tab-redirect-path="{{ url('facebookapps.tab_redirect') }}"
+ data-share-image-path="{{ media('img/facebookapps/downloadtab/sharing-image.jpg') }}"
+ data-is-dev="{{ 'true' if settings.DEV else 'false' }}"
+ ></div>
+
+ <div id="fb-root"></div>
+
+ {{ js('facebookapps_downloadtab') }}
+{% endblock %}
View
10 apps/facebookapps/templates/facebookapps/js-redirect.html
@@ -0,0 +1,10 @@
+<!doctype html>
+<html lang="{{ LANG }}" dir="{{ DIR }}">
+ <head>
+ <meta charset="utf-8">
+ </head>
+ <body>
+ <div id="redirect" data-redirect-url="{{ redirect_url }}"></div>
+ {{ js('facebookapps_redirect') }}
+ </body>
+</html>
View
21 apps/facebookapps/urls.py
@@ -0,0 +1,21 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+from django.conf.urls.defaults import *
+
+from commonware.decorators import xframe_allow
+
+from facebookapps import views
+from facebookapps.decorators import extract_app_data, facebook_locale
+from mozorg.util import page
+
+
+urlpatterns = patterns('',
+ url(r'^tab_redirect/$', views.tab_redirect, name='facebookapps.tab_redirect'),
+ url(r'^tab_redirect/(?P<redirect_type>[a-z]*)/$', views.tab_redirect, name='facebookapps.tab_redirect'),
+
+ page('channel', 'facebookapps/channel.html'),
+ page('downloadtab', 'facebookapps/downloadtab.html', decorators=(xframe_allow, extract_app_data, facebook_locale)),
+ page('downloadtab/noscroll', 'facebookapps/downloadtab.html', decorators=(xframe_allow, extract_app_data, facebook_locale), noscroll=True),
+)
View
92 apps/facebookapps/utils.py
@@ -0,0 +1,92 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+import json
+import urllib
+from base64 import urlsafe_b64decode
+
+from django.conf import settings
+from django.utils.translation import get_language
+
+import commonware.log
+import l10n_utils
+import tower
+
+
+log = commonware.log.getLogger('facebookapps.utils')
+
+def unwrap_signed_request(request):
+ """
+ Decodes and returns Facebook's `signed_request` data.
+
+ See https://developers.facebook.com/docs/howtos/login/signed-request/
+ """
+ try:
+ encoded_signed_request = request.REQUEST['signed_request']
+ except KeyError:
+ log.exception('signed_request not set')
+ return {}
+
+ encoded_string_data = encoded_signed_request.partition('.')[2]
+ # Pad with `=` to make string length a multiple of 4
+ # and thus prevent a base64 error
+ padding = ''.ljust(4 - len(encoded_string_data) % 4, '=')
+ padded_string = ''.join([encoded_string_data, padding])
+ # Convert to byte data for base64
+ encoded_byte_data = bytes(padded_string)
+ signed_request = json.loads(urlsafe_b64decode(encoded_byte_data))
+
+ # Change Facebook locale's underscore to hyphen
+ # ex. `en_US` to `en-US`
+ try:
+ locale = signed_request['user']['locale']
+ except KeyError:
+ locale = None
+
+ if locale:
+ signed_request['user']['locale'] = locale.replace('_', '-')
+
+ return signed_request
+
+
+def app_data_query_string_encode(app_data):
+ return urllib.urlencode([('app_data[{}]'.format(key), value) for key, value in app_data.items()])
+
+
+def get_best_locale(request, locale):
+ """
+ Returns the most appropriate locale from the list of supported locales.
+ This can either be the locale itself (if it's supported), the main locale
+ for that language if any or failing any of that the default `en-US`.
+
+ Adapted from `activate_locale` in https://github.com/mozilla/affiliates/blob/master/apps/facebook/utils.py
+ """
+ # HACK: It's not totally clear to me where Django or tower do the matching
+ # that equates locales like es-LA to es, and I'm scared enough of getting it
+ # wrong to want to avoid it for the first release. So instead, we'll
+ # activate the requested locale, and then check what locale got chosen by
+ # django as the usable locale, and match that against our locale whitelist.
+ # TODO: Properly filter out locales prior to calling activate.
+ old_locale = get_language()
+ tower.activate(locale)
+ lang = get_language()
+
+ if lang not in settings.FACEBOOK_LOCALES:
+ lang_prefix = lang.split('-')[0]
+ tower.activate(lang_prefix)
+ lang = get_language()
+
+ if lang not in settings.FACEBOOK_LOCALES:
+ try:
+ lang = next(locale for locale in settings.FACEBOOK_LOCALES
+ if locale.startswith(lang_prefix))
+ except TypeError:
+ lang = 'en-US'
+
+ tower.activate(old_locale)
+ return lang
+
+
+def js_redirect(redirect_url, request):
+ return l10n_utils.render(request, 'facebookapps/js-redirect.html', {'redirect_url': redirect_url})
View
21 apps/facebookapps/views.py
@@ -0,0 +1,21 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+from commonware.decorators import xframe_allow
+from django.conf import settings
+from django.shortcuts import redirect
+from facebookapps import utils
+
+
+@xframe_allow
+def tab_redirect(request, redirect_type = 'server'):
+ app_data_query_string = utils.app_data_query_string_encode(request.GET)
+ # Cast into unicode string to avoid `join` treating it as a `__proxy__`
+ tab_url = unicode(settings.FACEBOOK_TAB_URL)
+ final_url = '?'.join([tab_url, app_data_query_string])
+
+ if redirect_type == 'js':
+ return utils.js_redirect(final_url, request)
+
+ return redirect(final_url)
View
21 apps/mozorg/util.py
@@ -9,10 +9,13 @@
from django.views.decorators.csrf import csrf_exempt
from funfactory.urlresolvers import reverse
+import commonware.log
import l10n_utils
-def page(name, tmpl, **kwargs):
+log = commonware.log.getLogger('mozorg.util')
+
+def page(name, tmpl, decorators=None, **kwargs):
# The URL pattern is the name with a forced trailing slash if not
# empty
pattern = r'^%s/$' % name if name else r'^$'
@@ -30,5 +33,19 @@ def _view(request):
# This is for graphite so that we can differentiate pages
_view.page_name = name
- return url(pattern, _view, name=name)
+ # Apply decorators
+ if decorators:
+ if callable(decorators):
+ _view = decorators(_view)
+ else:
+ try:
+ # Decorators should be applied in reverse order so that input
+ # can be sent in the order your would write nested decorators
+ # e.g. dec1(dec2(_view)) -> [dec1, dec2]
+ for decorator in reversed(decorators):
+ _view = decorator(_view)
+ except TypeError:
+ log.exception('decorators not iterable or does not contain callable items')
+
+ return url(pattern, _view, name=name)
View
276 media/css/facebookapps/animate.less
@@ -0,0 +1,276 @@
+/*
+Animate.css - http://daneden.me/animate
+LICENSED UNDER THE MIT LICENSE (MIT)
+
+Copyright (c) 2012 Dan Eden
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+
+.animated {
+ -webkit-animation-fill-mode: both;
+ -moz-animation-fill-mode: both;
+ -ms-animation-fill-mode: both;
+ -o-animation-fill-mode: both;
+ animation-fill-mode: both;
+ -webkit-animation-duration: 1s;
+ -moz-animation-duration: 1s;
+ -ms-animation-duration: 1s;
+ -o-animation-duration: 1s;
+ animation-duration: 1s;
+}
+
+.animated.hinge {
+ -webkit-animation-duration: 2s;
+ -moz-animation-duration: 2s;
+ -ms-animation-duration: 2s;
+ -o-animation-duration: 2s;
+ animation-duration: 2s;
+}
+
+@-webkit-keyframes fadeIn {
+ 0% {opacity: 0;}
+ 100% {opacity: 1;}
+}
+
+@-moz-keyframes fadeIn {
+ 0% {opacity: 0;}
+ 100% {opacity: 1;}
+}
+
+@-o-keyframes fadeIn {
+ 0% {opacity: 0;}
+ 100% {opacity: 1;}
+}
+
+@keyframes fadeIn {
+ 0% {opacity: 0;}
+ 100% {opacity: 1;}
+}
+
+.fadeIn {
+ -webkit-animation-name: fadeIn;
+ -moz-animation-name: fadeIn;
+ -o-animation-name: fadeIn;
+ animation-name: fadeIn;
+}
+@-webkit-keyframes fadeInUp {
+ 0% {
+ opacity: 0;
+ -webkit-transform: translateY(20px);
+ }
+
+ 100% {
+ opacity: 1;
+ -webkit-transform: translateY(0);
+ }
+}
+
+@-moz-keyframes fadeInUp {
+ 0% {
+ opacity: 0;
+ -moz-transform: translateY(20px);
+ }
+
+ 100% {
+ opacity: 1;
+ -moz-transform: translateY(0);
+ }
+}
+
+@-o-keyframes fadeInUp {
+ 0% {
+ opacity: 0;
+ -o-transform: translateY(20px);
+ }
+
+ 100% {
+ opacity: 1;
+ -o-transform: translateY(0);
+ }
+}
+
+@keyframes fadeInUp {
+ 0% {
+ opacity: 0;
+ transform: translateY(20px);
+ }
+
+ 100% {
+ opacity: 1;
+ transform: translateY(0);
+ }
+}
+
+.fadeInUp {
+ -webkit-animation-name: fadeInUp;
+ -moz-animation-name: fadeInUp;
+ -o-animation-name: fadeInUp;
+ animation-name: fadeInUp;
+}
+@-webkit-keyframes fadeInDown {
+ 0% {
+ opacity: 0;
+ -webkit-transform: translateY(-20px);
+ }
+
+ 100% {
+ opacity: 1;
+ -webkit-transform: translateY(0);
+ }
+}
+
+@-moz-keyframes fadeInDown {
+ 0% {
+ opacity: 0;
+ -moz-transform: translateY(-20px);
+ }
+
+ 100% {
+ opacity: 1;
+ -moz-transform: translateY(0);
+ }
+}
+
+@-o-keyframes fadeInDown {
+ 0% {
+ opacity: 0;
+ -o-transform: translateY(-20px);
+ }
+
+ 100% {
+ opacity: 1;
+ -o-transform: translateY(0);
+ }
+}
+
+@keyframes fadeInDown {
+ 0% {
+ opacity: 0;
+ transform: translateY(-20px);
+ }
+
+ 100% {
+ opacity: 1;
+ transform: translateY(0);
+ }
+}
+
+.fadeInDown {
+ -webkit-animation-name: fadeInDown;
+ -moz-animation-name: fadeInDown;
+ -o-animation-name: fadeInDown;
+ animation-name: fadeInDown;
+}
+@-webkit-keyframes fadeInLeft {
+ 0% {
+ opacity: 0;
+ -webkit-transform: translateX(-20px);
+ }
+
+ 100% {
+ opacity: 1;
+ -webkit-transform: translateX(0);
+ }
+}
+
+@-moz-keyframes fadeInLeft {
+ 0% {
+ opacity: 0;
+ -moz-transform: translateX(-20px);
+ }
+
+ 100% {
+ opacity: 1;
+ -moz-transform: translateX(0);
+ }
+}
+
+@-o-keyframes fadeInLeft {
+ 0% {
+ opacity: 0;
+ -o-transform: translateX(-20px);
+ }
+
+ 100% {
+ opacity: 1;
+ -o-transform: translateX(0);
+ }
+}
+
+@keyframes fadeInLeft {
+ 0% {
+ opacity: 0;
+ transform: translateX(-20px);
+ }
+
+ 100% {
+ opacity: 1;
+ transform: translateX(0);
+ }
+}
+
+.fadeInLeft {
+ -webkit-animation-name: fadeInLeft;
+ -moz-animation-name: fadeInLeft;
+ -o-animation-name: fadeInLeft;
+ animation-name: fadeInLeft;
+}
+@-webkit-keyframes fadeInRight {
+ 0% {
+ opacity: 0;
+ -webkit-transform: translateX(20px);
+ }
+
+ 100% {
+ opacity: 1;
+ -webkit-transform: translateX(0);
+ }
+}
+
+@-moz-keyframes fadeInRight {
+ 0% {
+ opacity: 0;
+ -moz-transform: translateX(20px);
+ }
+
+ 100% {
+ opacity: 1;
+ -moz-transform: translateX(0);
+ }
+}
+
+@-o-keyframes fadeInRight {
+ 0% {
+ opacity: 0;
+ -o-transform: translateX(20px);
+ }
+
+ 100% {
+ opacity: 1;
+ -o-transform: translateX(0);
+ }
+}
+
+@keyframes fadeInRight {
+ 0% {
+ opacity: 0;
+ transform: translateX(20px);
+ }
+
+ 100% {
+ opacity: 1;
+ transform: translateX(0);
+ }
+}
+
+.fadeInRight {
+ -webkit-animation-name: fadeInRight;
+ -moz-animation-name: fadeInRight;
+ -o-animation-name: fadeInRight;
+ animation-name: fadeInRight;
+}
View
390 media/css/facebookapps/downloadtab.less
@@ -0,0 +1,390 @@
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+@import "../sandstone/variables.less";
+@import "../sandstone/mixins.less";
+@import "../sandstone/fonts.less";
+@import "../sandstone/buttons.less";
+@import "../libs/normalize.less";
+@import "../libs/h5bp_main.less";
+@import "animate.less";
+
+.noscroll {
+ overflow: hidden;
+}
+
+#outer-wrapper {
+ position: relative;
+ z-index: 0;
+
+ margin: 0 auto;
+ width: 790px;
+
+ background: #eaeff2;
+ background: -moz-radial-gradient(center, ellipse cover, rgba(234,239,242,1) 0%, rgba(212,221,228,.5) 60%),
+ -moz-linear-gradient(top, rgba(202,225,244,1) 0%, rgba(125,185,232,0) 100%);
+ background: -webkit-gradient(radial, center center, 0px, center center, 60%, color-stop(0%,rgba(234,239,242,.9)), color-stop(60%,rgba(212,221,228,.5))),
+ -webkit-linear-gradient(top, rgba(202,225,244,1) 0%, rgba(125,185,232,0) 100%);
+ background: -webkit-radial-gradient(center, ellipse cover, rgba(234,239,242,1) 0%, rgba(212,221,228,.4) 60%),
+ -webkit-linear-gradient(top, rgba(202,225,244,1) 0%, rgba(125,185,232,0) 100%);
+ background: -ms-radial-gradient(center, ellipse cover, rgba(234,239,242,1) 0%, rgba(212,221,228,.5) 60%),
+ -ms-linear-gradient(top, rgba(202,225,244,1) 0%, rgba(125,185,232,0) 100%);
+ background: radial-gradient(ellipse at center, rgba(234,239,242,1) 0%,rgba(212,221,228,.5) 50%),
+ linear-gradient(to bottom, rgba(202,225,244,1) 0%,rgba(125,185,232,0) 100%);
+ .border-radius(10px);
+ font-family: 'Open Sans Light', sans-serif;
+ text-align: center;
+}
+
+#wrapper {
+ position: relative;
+ z-index: 1;
+
+ padding-bottom: 430px;
+ background: url(/media/img/facebookapps/downloadtab/firefox.png) no-repeat 50% 100%;
+ color: @textColorSecondary;
+
+ .noscroll & {
+ height: 800px;
+ padding: 0;
+
+ background-image: none;
+ }
+ .on-scene1 & {
+ background-position: 50% 100%;
+ }
+ .windows .on-scene1.noscroll &, .linux .on-scene1.noscroll & {
+ background-image: url(/media/img/facebookapps/downloadtab/browser-win.png);
+ }
+ .osx .on-scene1.noscroll & {
+ background-image: url(/media/img/facebookapps/downloadtab/browser-mac.png);
+ }
+}
+
+
+h1, h2, h3, h4, h5, h6 {
+ .open-sans-light;
+}
+h1 {
+ clear: both;
+ margin: 30px 0 0;
+
+ font-size: 52px;
+}
+h2 {
+ font-size: 30px;
+}
+
+// Header
+#masthead {
+ #logo {
+ display: none;
+ float: left;
+ margin: 20px 70px;
+
+ text-align: left;
+
+ .noscroll & { display: block }
+ }
+ #tabzilla {
+ position: relative;
+ z-index: 999;
+
+ display: block;
+ float: right;
+ overflow: hidden;
+ margin: 0 30px 50px 0;
+ width: 148px;
+ height: 42px;
+
+ background: url(/media/img/facebookapps/downloadtab/tabzilla-tabless.png);
+ text-indent: -2000px;
+ }
+}
+
+
+
+// Subcaption
+.message {
+ margin: 0 auto;
+ width: 560px;
+
+ font-size: 16px;
+}
+
+// Steps progress bar
+#progress {
+ clear: both;
+ padding: 0;
+
+ ol {
+ .clearfix;
+ margin: 5px auto;
+ padding: 0;
+ width: 640px;
+
+ list-style: none;
+
+ li {
+ float: left;
+ width: 33.3%;
+
+ .open-sans-light;
+ font-size: 21px;
+ }
+ }
+ .step1 { text-align: left }
+ .step2 { text-align: center }
+ .step3 {
+ position: relative;
+ right: -10px; // needs a little nudge to be aligned right
+
+ text-align: right;
+ }
+
+ .ir {
+ margin: 0 auto;
+ width: 570px;
+ height: 30px;
+
+ background-image: url(/media/img/facebookapps/downloadtab/bg-progress.png);
+ }
+}
+
+// Buttons and CTAs
+.button, .button:link, .button:visited {
+ margin-bottom: 15px;
+ padding: 0 15px;
+ height: 45px;
+
+ font-size: 22px;
+ font-weight: normal;
+ line-height: 45px;
+ text-shadow: 0 -1px 0 rgba(0,0,0,.75);
+}
+
+// All scenes
+.scene {
+ position: relative;
+}
+
+// Change scenes
+.on-scene1 {
+ #scene1 { display: block; }
+ #progress .ir { background-position: 0 0 }
+}
+.on-scene2 {
+ #scene2 { display: block; }
+ #progress .ir { background-position: 0 50% }
+}
+.on-scene3 {
+ #scene3 { display: block; }
+ #progress .ir { background-position: 0 100% }
+}
+
+//
+// Scene 1: Download
+//
+#features {
+ .clearfix;
+ margin: 15px auto 40px;
+ padding: 0;
+ width: 480px;
+
+ list-style: none;
+
+ li {
+ float: left;
+ padding: 28px 20px 0 0;
+ width: 140px;
+ height: 89px;
+
+ background: url(/media/img/firefox/new/features-divider.png) right top no-repeat;
+ color: @textColorSecondary;
+ .open-sans-light;
+ font-size: 24px;
+ letter-spacing: -0.03em;
+ line-height: 110%;
+ text-align: center;
+
+ &:last-child {
+ background: none;
+ }
+ }
+
+ span {
+ display: block;
+ }
+}
+
+// Override the standard download button to match the approved design better
+.download-button {
+ .download-list {
+ padding: 0;
+ }
+
+ .download-link {
+ height: auto;
+ margin-bottom: 0;
+ }
+}
+
+//
+// Scene 2: Share
+//
+#scene2 {
+ h2 {
+ margin: 25px 0 5px;
+ }
+}
+
+.installation {
+ .clearfix;
+ display: block;
+ margin: 50px auto;
+ padding: 0;
+ width: 590px;
+
+ list-style-type: none;
+
+ li {
+ position: relative;
+
+ float: left;
+ margin-right: 30px;
+ padding: 150px 0 0 0;
+ width: 175px;
+
+ background-repeat: no-repeat;
+ color: @textColorTertiary;
+ font-size: 12px;
+ text-align: left;
+
+ &:after {
+ position: absolute;
+ top: -15px;
+ left: -15px;
+
+ display: block;
+ width: 35px;
+ height: 35px;
+
+ background: url(/media/img/facebookapps/downloadtab/steps-installation.png);
+ content: ' ';
+ }
+ }
+ #install1:after { background-position: 0 0 }
+ #install2:after { background-position: 0 50% }
+ #install3 {
+ margin: 0;
+ &:after { background-position: 0 100% }
+ }
+}
+
+.osx {
+ #install1 { background-image: url(/media/img/facebookapps/downloadtab/install1-mac.png); }
+ #install2 { background-image: url(/media/img/facebookapps/downloadtab/install2-mac.png); }
+ #install3 { background-image: url(/media/img/facebookapps/downloadtab/install3-mac.png); }
+}
+
+.windows {
+ #install1 { background-image: url(/media/img/facebookapps/downloadtab/install1-win.png); }
+ #install2 { background-image: url(/media/img/facebookapps/downloadtab/install2-win.png); }
+ #install3 { background-image: url(/media/img/facebookapps/downloadtab/install3-win.png); }
+}
+
+.winIE8 #install1 {
+ background-image: url(/media/img/facebookapps/downloadtab/install1-winIE8.png);
+}
+
+.osx .install-win { display: none; }
+.windows .install-osx { display: none; }
+
+//
+// Scene 3: Get involved
+//
+#scene3 {
+ h2 {
+ margin: 25px 0 5px;
+ }
+ .message { width: auto }
+
+ .facepile {
+ display: block;
+ margin: 1em auto 0;
+ padding-top: 225px;
+ width: 661px;
+
+ background: url(/media/img/facebookapps/downloadtab/facepile.png) no-repeat top center;
+ }
+}
+
+#slider-container {
+ position: relative;
+ margin: 20px auto 40px;
+ width: 650px;
+
+ .ir {
+ display: block;
+ position: absolute;
+ top: 50%;
+
+ margin-top: -13px;
+ width: 30px;
+ height: 30px;
+
+ background-image: url(/media/img/facebookapps/downloadtab/ticker-arrows.png);
+ font: 0/0 a;
+ }
+ .prev {
+ left: -15px;
+ }
+ .next {
+ right: -15px;
+ background-position: top right;
+ }
+}
+
+#slider {
+ position: relative;
+ margin: 0;
+ padding: 0;
+
+ background: #fff;
+ .border-radius(10px);
+ .box-shadow(0 1px 2px rgba(0,0,0,.20));
+ list-style: none;
+ text-align: left;
+
+ li {
+ padding: 30px 0 30px 40px;
+ }
+
+ h3 {
+ margin: 0;
+ font-size: 17px;
+ }
+ p {
+ margin: 0;
+ font-size: 14px;
+ }
+
+ .button,
+ .like-txt {
+ position: absolute;
+ top: 50%;
+ right: 40px;
+ display: block;
+ }
+
+ .button {
+ margin: -20px 0 0 0;
+ padding: 0 20px;
+ height: 40px;
+
+ font-size: 18px;
+ line-height: 40px;
+ }
+ .like-txt { margin-top: -21px }
+}
View
303 media/css/libs/h5bp_main.less
@@ -0,0 +1,303 @@
+/*
+ * HTML5 Boilerplate
+ *
+ * What follows is the result of much research on cross-browser styling.
+ * Credit left inline and big thanks to Nicolas Gallagher, Jonathan Neal,
+ * Kroc Camen, and the H5BP dev community and team.
+ */
+
+/* ==========================================================================
+ Base styles: opinionated defaults
+ ========================================================================== */
+
+html,
+button,
+input,
+select,
+textarea {
+ color: #222;
+}
+
+body {
+ font-size: 1em;
+ line-height: 1.4;
+}
+
+/*
+ * Remove text-shadow in selection highlight: h5bp.com/i
+ * These selection declarations have to be separate.
+ * Customize the background color to match your design.
+ */
+
+::-moz-selection {
+ background: #b3d4fc;
+ text-shadow: none;
+}
+
+::selection {
+ background: #b3d4fc;
+ text-shadow: none;
+}
+
+/*
+ * A better looking default horizontal rule
+ */
+
+hr {
+ display: block;
+ height: 1px;
+ border: 0;
+ border-top: 1px solid #ccc;
+ margin: 1em 0;
+ padding: 0;
+}
+
+/*
+ * Remove the gap between images and the bottom of their containers: h5bp.com/i/440
+ */
+
+img {
+ vertical-align: middle;
+}
+
+/*
+ * Remove default fieldset styles.
+ */
+
+fieldset {
+ border: 0;
+ margin: 0;
+ padding: 0;
+}
+
+/*
+ * Allow only vertical resizing of textareas.
+ */
+
+textarea {
+ resize: vertical;
+}
+
+/* ==========================================================================
+ Chrome Frame prompt
+ ========================================================================== */
+
+.chromeframe {
+ margin: 0.2em 0;
+ background: #ccc;
+ color: #000;
+ padding: 0.2em 0;
+}
+
+/* ==========================================================================
+ Author's custom styles
+ ========================================================================== */
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+/* ==========================================================================
+ Helper classes
+ ========================================================================== */
+
+/*
+ * Image replacement
+ */
+
+.ir {
+ background-color: transparent;
+ border: 0;
+ overflow: hidden;
+ /* IE 6/7 fallback */
+ *text-indent: -9999px;
+}
+
+.ir:before {
+ content: "";
+ display: block;
+ width: 0;
+ height: 150%;
+}
+
+/*
+ * Hide from both screenreaders and browsers: h5bp.com/u
+ */
+
+.hidden {
+ display: none !important;
+ visibility: hidden;
+}
+
+/*
+ * Hide only visually, but have it available for screenreaders: h5bp.com/v
+ */
+
+.visuallyhidden {
+ border: 0;
+ clip: rect(0 0 0 0);
+ height: 1px;
+ margin: -1px;
+ overflow: hidden;
+ padding: 0;
+ position: absolute;
+ width: 1px;
+}
+
+/*
+ * Extends the .visuallyhidden class to allow the element to be focusable
+ * when navigated to via the keyboard: h5bp.com/p
+ */
+
+.visuallyhidden.focusable:active,
+.visuallyhidden.focusable:focus {
+ clip: auto;
+ height: auto;
+ margin: 0;
+ overflow: visible;
+ position: static;
+ width: auto;
+}
+
+/*
+ * Hide visually and from screenreaders, but maintain layout
+ */
+
+.invisible {
+ visibility: hidden;
+}
+
+/*
+ * Clearfix: contain floats
+ *
+ * For modern browsers
+ * 1. The space content is one way to avoid an Opera bug when the
+ * `contenteditable` attribute is included anywhere else in the document.
+ * Otherwise it causes space to appear at the top and bottom of elements
+ * that receive the `clearfix` class.
+ * 2. The use of `table` rather than `block` is only necessary if using
+ * `:before` to contain the top-margins of child elements.
+ */
+
+.clearfix:before,
+.clearfix:after {
+ content: " "; /* 1 */
+ display: table; /* 2 */
+}
+
+.clearfix:after {
+ visibility: visible; /* undo variables.less clearfix */
+ height: auto; /* undo variables.less clearfix */
+ clear: both;
+}
+
+/*
+ * For IE 6/7 only
+ * Include this rule to trigger hasLayout and contain floats.
+ */
+
+.clearfix {
+ zoom: normal; /* undo variables.less clearfix */
+ *zoom: 1;
+}
+
+/* ==========================================================================
+ EXAMPLE Media Queries for Responsive Design.
+ Theses examples override the primary ('mobile first') styles.
+ Modify as content requires.
+ ========================================================================== */
+
+@media only screen and (min-width: 35em) {
+ /* Style adjustments for viewports that meet the condition */
+}
+
+@media print,
+ (-o-min-device-pixel-ratio: 5/4),
+ (-webkit-min-device-pixel-ratio: 1.25),
+ (min-resolution: 120dpi) {
+ /* Style adjustments for high resolution devices */
+}
+
+/* ==========================================================================
+ Print styles.
+ Inlined to avoid required HTTP connection: h5bp.com/r
+ ========================================================================== */
+
+@media print {
+ * {
+ background: transparent !important;
+ color: #000 !important; /* Black prints faster: h5bp.com/s */
+ box-shadow: none !important;
+ text-shadow: none !important;
+ }
+
+ a,
+ a:visited {
+ text-decoration: underline;
+ }
+
+ a[href]:after {
+ content: " (" attr(href) ")";
+ }
+
+ abbr[title]:after {
+ content: " (" attr(title) ")";
+ }
+
+ /*
+ * Don't show links for images, or javascript/internal links
+ */
+
+ .ir a:after,
+ a[href^="javascript:"]:after,
+ a[href^="#"]:after {
+ content: "";
+ }
+
+ pre,
+ blockquote {
+ border: 1px solid #999;
+ page-break-inside: avoid;
+ }
+
+ thead {
+ display: table-header-group; /* h5bp.com/t */
+ }
+
+ tr,
+ img {
+ page-break-inside: avoid;
+ }
+
+ img {
+ max-width: 100% !important;
+ }
+
+ @page {
+ margin: 0.5cm;
+ }
+
+ p,
+ h2,
+ h3 {
+ orphans: 3;
+ widows: 3;
+ }
+
+ h2,
+ h3 {
+ page-break-after: avoid;
+ }
+}
View
527 media/css/libs/normalize.less
@@ -0,0 +1,527 @@
+/*! normalize.css v1.1.0 | MIT License | git.io/normalize */
+
+/* ==========================================================================
+ HTML5 display definitions
+ ========================================================================== */
+
+/**
+ * Correct `block` display not defined in IE 6/7/8/9 and Firefox 3.
+ */
+
+article,
+aside,
+details,
+figcaption,
+figure,
+footer,
+header,
+hgroup,
+main,
+nav,
+section,
+summary {
+ display: block;
+}
+
+/**
+ * Correct `inline-block` display not defined in IE 6/7/8/9 and Firefox 3.
+ */
+
+audio,
+canvas,
+video {
+ display: inline-block;
+ *display: inline;
+ *zoom: 1;
+}
+
+/**
+ * Prevent modern browsers from displaying `audio` without controls.
+ * Remove excess height in iOS 5 devices.
+ */
+
+audio:not([controls]) {
+ display: none;
+ height: 0;
+}
+
+/**
+ * Address styling not present in IE 7/8/9, Firefox 3, and Safari 4.
+ * Known issue: no IE 6 support.
+ */
+
+[hidden] {
+ display: none;
+}
+
+/* ==========================================================================
+ Base
+ ========================================================================== */
+
+/**
+ * 1. Correct text resizing oddly in IE 6/7 when body `font-size` is set using
+ * `em` units.
+ * 2. Prevent iOS text size adjust after orientation change, without disabling
+ * user zoom.
+ */
+
+html {
+ font-size: 100%; /* 1 */
+ -webkit-text-size-adjust: 100%; /* 2 */
+ -ms-text-size-adjust: 100%; /* 2 */
+}
+
+/**
+ * Address `font-family` inconsistency between `textarea` and other form
+ * elements.
+ */
+
+html,
+button,
+input,
+select,
+textarea {
+ font-family: sans-serif;
+}
+
+/**
+ * Address margins handled incorrectly in IE 6/7.
+ */
+
+body {
+ margin: 0;
+}
+
+/* ==========================================================================
+ Links
+ ========================================================================== */
+
+/**
+ * Address `outline` inconsistency between Chrome and other browsers.
+ */
+
+a:focus {
+ outline: thin dotted;
+}
+
+/**
+ * Improve readability when focused and also mouse hovered in all browsers.
+ */
+
+a:active,
+a:hover {
+ outline: 0;
+}
+
+/* ==========================================================================
+ Typography
+ ========================================================================== */
+
+/**
+ * Address font sizes and margins set differently in IE 6/7.
+ * Address font sizes within `section` and `article` in Firefox 4+, Safari 5,
+ * and Chrome.
+ */
+
+h1 {
+ font-size: 2em;
+ margin: 0.67em 0;
+}
+
+h2 {
+ font-size: 1.5em;
+ margin: 0.83em 0;
+}
+
+h3 {
+ font-size: 1.17em;
+ margin: 1em 0;
+}
+
+h4 {
+ font-size: 1em;
+ margin: 1.33em 0;
+}
+
+h5 {
+ font-size: 0.83em;
+ margin: 1.67em 0;
+}
+
+h6 {
+ font-size: 0.67em;
+ margin: 2.33em 0;
+}
+
+/**
+ * Address styling not present in IE 7/8/9, Safari 5, and Chrome.
+ */
+
+abbr[title] {
+ border-bottom: 1px dotted;
+}
+
+/**
+ * Address style set to `bolder` in Firefox 3+, Safari 4/5, and Chrome.
+ */
+
+b,
+strong {
+ font-weight: bold;
+}
+
+blockquote {
+ margin: 1em 40px;
+}
+
+/**
+ * Address styling not present in Safari 5 and Chrome.
+ */
+
+dfn {
+ font-style: italic;
+}
+
+/**
+ * Address differences between Firefox and other browsers.
+ * Known issue: no IE 6/7 normalization.
+ */
+
+hr {
+ -moz-box-sizing: content-box;
+ box-sizing: content-box;
+ height: 0;
+}
+
+/**
+ * Address styling not present in IE 6/7/8/9.
+ */
+
+mark {
+ background: #ff0;
+ color: #000;
+}
+
+/**
+ * Address margins set differently in IE 6/7.
+ */
+
+p,
+pre {
+ margin: 1em 0;
+}
+
+/**
+ * Correct font family set oddly in IE 6, Safari 4/5, and Chrome.
+ */
+
+code,
+kbd,
+pre,
+samp {
+ font-family: monospace, serif;
+ _font-family: 'courier new', monospace;
+ font-size: 1em;
+}
+
+/**
+ * Improve readability of pre-formatted text in all browsers.
+ */
+
+pre {
+ white-space: pre;
+ white-space: pre-wrap;
+ word-wrap: break-word;
+}
+
+/**
+ * Address CSS quotes not supported in IE 6/7.
+ */
+
+q {
+ quotes: none;
+}
+
+/**
+ * Address `quotes` property not supported in Safari 4.
+ */
+
+q:before,
+q:after {
+ content: '';
+ content: none;
+}
+
+/**
+ * Address inconsistent and variable font size in all browsers.
+ */
+
+small {
+ font-size: 80%;
+}
+
+/**
+ * Prevent `sub` and `sup` affecting `line-height` in all browsers.
+ */
+
+sub,
+sup {
+ font-size: 75%;
+ line-height: 0;
+ position: relative;
+ vertical-align: baseline;
+}
+
+sup {
+ top: -0.5em;
+}
+
+sub {
+ bottom: -0.25em;
+}
+
+/* ==========================================================================
+ Lists
+ ========================================================================== */
+
+/**
+ * Address margins set differently in IE 6/7.
+ */
+
+dl,
+menu,
+ol,
+ul {
+ margin: 1em 0;
+}
+
+dd {
+ margin: 0 0 0 40px;
+}
+
+/**
+ * Address paddings set differently in IE 6/7.
+ */
+
+menu,
+ol,
+ul {
+ padding: 0 0 0 40px;
+}
+
+/**
+ * Correct list images handled incorrectly in IE 7.
+ */
+
+nav ul,
+nav ol {
+ list-style: none;
+ list-style-image: none;
+}
+
+/* ==========================================================================
+ Embedded content
+ ========================================================================== */
+
+/**
+ * 1. Remove border when inside `a` element in IE 6/7/8/9 and Firefox 3.
+ * 2. Improve image quality when scaled in IE 7.
+ */
+
+img {
+ border: 0; /* 1 */
+ -ms-interpolation-mode: bicubic; /* 2 */
+}
+
+/**
+ * Correct overflow displayed oddly in IE 9.
+ */
+
+svg:not(:root) {
+ overflow: hidden;
+}
+
+/* ==========================================================================
+ Figures
+ ========================================================================== */
+
+/**
+ * Address margin not present in IE 6/7/8/9, Safari 5, and Opera 11.
+ */
+
+figure {
+ margin: 0;
+}
+
+/* ==========================================================================
+ Forms
+ ========================================================================== */
+
+/**
+ * Correct margin displayed oddly in IE 6/7.
+ */
+
+form {
+ margin: 0;
+}
+
+/**
+ * Define consistent border, margin, and padding.
+ */
+
+fieldset {
+ border: 1px solid #c0c0c0;
+ margin: 0 2px;
+ padding: 0.35em 0.625em 0.75em;
+}
+
+/**
+ * 1. Correct color not being inherited in IE 6/7/8/9.
+ * 2. Correct text not wrapping in Firefox 3.
+ * 3. Correct alignment displayed oddly in IE 6/7.
+ */
+
+legend {
+ border: 0; /* 1 */
+ padding: 0;
+ white-space: normal; /* 2 */
+ *margin-left: -7px; /* 3 */
+}
+
+/**
+ * 1. Correct font size not being inherited in all browsers.
+ * 2. Address margins set differently in IE 6/7, Firefox 3+, Safari 5,
+ * and Chrome.
+ * 3. Improve appearance and consistency in all browsers.
+ */
+
+button,
+input,
+select,
+textarea {
+ font-size: 100%; /* 1 */
+ margin: 0; /* 2 */
+ vertical-align: baseline; /* 3 */
+ *vertical-align: middle; /* 3 */
+}
+
+/**
+ * Address Firefox 3+ setting `line-height` on `input` using `!important` in
+ * the UA stylesheet.
+ */
+
+button,
+input {
+ line-height: normal;
+}
+
+/**
+ * Address inconsistent `text-transform` inheritance for `button` and `select`.
+ * All other form control elements do not inherit `text-transform` values.
+ * Correct `button` style inheritance in Chrome, Safari 5+, and IE 6+.
+ * Correct `select` style inheritance in Firefox 4+ and Opera.
+ */
+
+button,
+select {
+ text-transform: none;
+}
+
+/**
+ * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio`
+ * and `video` controls.
+ * 2. Correct inability to style clickable `input` types in iOS.
+ * 3. Improve usability and consistency of cursor style between image-type
+ * `input` and others.
+ * 4. Remove inner spacing in IE 7 without affecting normal text inputs.
+ * Known issue: inner spacing remains in IE 6.
+ */
+
+button,
+html input[type="button"], /* 1 */
+input[type="reset"],
+input[type="submit"] {
+ -webkit-appearance: button; /* 2 */
+ cursor: pointer; /* 3 */
+ *overflow: visible; /* 4 */
+}
+
+/**
+ * Re-set default cursor for disabled elements.
+ */
+
+button[disabled],
+html input[disabled] {
+ cursor: default;
+}
+
+/**
+ * 1. Address box sizing set to content-box in IE 8/9.
+ * 2. Remove excess padding in IE 8/9.
+ * 3. Remove excess padding in IE 7.
+ * Known issue: excess padding remains in IE 6.
+ */
+
+input[type="checkbox"],
+input[type="radio"] {
+ box-sizing: border-box; /* 1 */
+ padding: 0; /* 2 */
+ *height: 13px; /* 3 */
+ *width: 13px; /* 3 */
+}
+
+/**
+ * 1. Address `appearance` set to `searchfield` in Safari 5 and Chrome.
+ * 2. Address `box-sizing` set to `border-box` in Safari 5 and Chrome
+ * (include `-moz` to future-proof).
+ */
+
+input[type="search"] {
+ -webkit-appearance: textfield; /* 1 */
+ -moz-box-sizing: content-box;
+ -webkit-box-sizing: content-box; /* 2 */
+ box-sizing: content-box;
+}
+
+/**
+ * Remove inner padding and search cancel button in Safari 5 and Chrome
+ * on OS X.
+ */
+
+input[type="search"]::-webkit-search-cancel-button,
+input[type="search"]::-webkit-search-decoration {
+ -webkit-appearance: none;
+}
+
+/**
+ * Remove inner padding and border in Firefox 3+.
+ */
+
+button::-moz-focus-inner,
+input::-moz-focus-inner {
+ border: 0;
+ padding: 0;
+}
+
+/**
+ * 1. Remove default vertical scrollbar in IE 6/7/8/9.
+ * 2. Improve readability and alignment in all browsers.
+ */
+
+textarea {
+ overflow: auto; /* 1 */
+ vertical-align: top; /* 2 */
+}
+
+/* ==========================================================================
+ Tables
+ ========================================================================== */
+
+/**
+ * Remove most spacing between table cells.
+ */
+
+table {
+ border-collapse: collapse;
+ border-spacing: 0;
+}
View
BIN  media/img/facebookapps/downloadtab/bg-progress.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN  media/img/facebookapps/downloadtab/browser-linux.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN  media/img/facebookapps/downloadtab/browser-mac.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN  media/img/facebookapps/downloadtab/browser-win.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN  media/img/facebookapps/downloadtab/browser.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN  media/img/facebookapps/downloadtab/facepile.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN  media/img/facebookapps/downloadtab/firefox.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN  media/img/facebookapps/downloadtab/header-firefox.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN  media/img/facebookapps/downloadtab/icon_like_with_txt.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN  media/img/facebookapps/downloadtab/install1-mac.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN  media/img/facebookapps/downloadtab/install1-win.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN  media/img/facebookapps/downloadtab/install1-winIE8.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN  media/img/facebookapps/downloadtab/install2-mac.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN  media/img/facebookapps/downloadtab/install2-win.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN  media/img/facebookapps/downloadtab/install3-mac.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN  media/img/facebookapps/downloadtab/install3-win.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN  media/img/facebookapps/downloadtab/placeholder.gif
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN  media/img/facebookapps/downloadtab/sharing-image.jpg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN  media/img/facebookapps/downloadtab/steps-installation.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN  media/img/facebookapps/downloadtab/tabzilla-tabless.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN  media/img/facebookapps/downloadtab/ticker-arrows.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
160 media/js/facebookapps/App.js
@@ -0,0 +1,160 @@
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+// Main app class
+DOWNLOADTAB.classes.App = (function (singleton) {
+ function App ($, win) {
+ singleton.classes.Base.call (this);
+
+ this.$ = $;
+ this.window = win;
+
+ this.appId = this._initData.appId;
+ this.pageNamespace = this._initData.pageNamespace;
+ this.tabRedirectUrl = this.absoluteUrl(this._initData.tabRedirectPath);
+ this.shareImageUrl = this.absoluteUrl(this._initData.shareImagePath);
+ this._initData = undefined;
+ }
+
+ App.prototype = new singleton.classes.Base();
+ App.prototype.constructor = App;
+
+ App.prototype.domInit = function() {
+ var self = this;
+ var path_parts = self.window.location.pathname.split('/');
+ var referrer = path_parts[path_parts.length - 2];
+
+ self.locale = path_parts[1];
+ self.virtualUrl = '/' + self.locale + '/products/download.html?referrer=' + referrer;
+
+ self.setupDownloadLinks();
+
+ self.theater = new self._classes.Theater(self.$, self.window.document);
+ self.slider = new self._classes.Slider(self.$, '#slider-container');
+ };
+
+ App.prototype.facebookInit = function() {
+ var self = this;
+
+ self.$(function() {
+ var redirectUrl = self.tabRedirectUrl + '?scene=get-involved';
+
+ self.$('.js-share').on('click', function(event) {
+ event.preventDefault();
+
+ self.facebook.share({
+ link: self.facebook.getTabUrl(self.pageNamespace),
+ picture: self.shareImageUrl,
+ name: trans('app-title'),
+ caption: trans('share-caption'),
+ description: trans('share-description')
+ }, redirectUrl);
+ });
+
+ self.$('.js-invite').on('click', function(event) {
+ event.preventDefault();
+
+ self.facebook.invite({
+ title: trans('invite-title'),
+ message: trans('invite-message'),
+ data: 'tab-invite'
+ }, redirectUrl);
+ });
+ });
+ };
+
+ App.prototype.startApp = function() {
+ var self = this;
+
+ self.facebook = new self._classes.Facebook ({
+ window: self.window,
+ appId: self.appId,
+ useUrlDialogs: true,
+ callback: function() {
+ self.facebookInit();
+ }
+ });
+
+ self.$(function() {
+ self.domInit();
+ });
+ };
+
+ App.prototype.setupDownloadLinks = function() {
+ var self = this;
+
+ // Pull Firefox download link from the download button and add to
+ // the 'click here' link.
+ // TODO: Remove and generate link in bedrock.
+ $('#direct-download-link').attr(
+ 'href', $('.download-list li:visible .download-link').attr('href')
+ );
+
+ $('#direct-download-link, .download-link').on('click', function(event) {
+ var $activeScene;
+ var downloadUrl;
+
+ event.preventDefault();
+
+ downloadUrl = $(event.currentTarget).attr('href');
+ self.trackRedirect(downloadUrl, this.virtualUrl);
+
+ $activeScene = self.theater.getActiveScene();
+
+ if ($activeScene.attr('id') !== 'scene2') {
+ self.theater.showScene(2);
+ }
+ });
+ };
+
+ App.prototype.getBaseUrl = function (options) {
+ var protocol = '';
+ var port = '';
+ options = options || {};
+
+ if (options.protocol === true) {
+ protocol = this.window.location.protocol;
+ } else if (options.protocol && typeof options.protocol === 'string') {
+ protocol = options.protocol;
+ }
+
+ if (this.window.location.port) {
+ port = ':' + this.window.location.port;
+ }
+
+ return protocol + '//' + this.window.location.hostname + port;
+ };
+
+ App.prototype.absoluteUrl = function(relativeUrl) {
+ var self = this;
+ return self.getBaseUrl({ protocol: true }) + relativeUrl;
+ };
+
+ App.prototype.trackRedirect = function(url, virtualUrl) {
+ var self = this;
+ virtualUrl = virtualUrl || url;
+
+ if (!self.window._gat) {
+ self.redirect(url);
+ }
+
+ self.window._gaq.push(['_trackPageview', virtualUrl]);
+ self.window._gaq.push(function() {
+ self.redirect(url);
+ });
+ };
+
+ App.prototype.redirect = function(url, options) {
+ var self = this;
+ options = options || {};
+
+ if (options.top) {
+ self.window.top.location.href = url;
+ } else {
+ self.window.location.href = url;
+ }
+ };
+
+ return App;
+} (DOWNLOADTAB));
View
13 media/js/facebookapps/Base.js
@@ -0,0 +1,13 @@
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+// Base parent class
+// Exposes references to singleton's properties
+DOWNLOADTAB.classes.Base = (function (singleton) {
+ function Base(){}
+ Base.prototype._initData = singleton.initData;
+ Base.prototype._classes = singleton.classes;
+
+ return Base;
+} (DOWNLOADTAB));
View
140 media/js/facebookapps/Facebook.js
@@ -0,0 +1,140 @@
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+// Facebook view class
+DOWNLOADTAB.classes.Facebook = (function (singleton) {
+ function Facebook (parametersObject) {
+ var options = parametersObject || {};
+
+ singleton.classes.Base.call (this);
+
+ if (typeof options.startImmediately === 'undefined') {
+ options.startImmediately = true;
+ }
+
+ if (typeof options.useUrlDialogs === 'undefined') {
+ options.useUrlDialogs = false;
+ }
+
+ this.window = options.window;
+ this.appId = options.appId;
+ this.channelUrl = options.channelUrl;
+ this.callback = options.callback;
+ this.isImmediate = options.startImmediately;
+ this.isUrlDialog = options.useUrlDialogs;
+
+ this.isDebug = this._initData.isDev;
+ this._initData = undefined;
+
+ if (this.isImmediate) {
+ this.start();
+ }
+ }
+
+ Facebook.prototype = new singleton.classes.Base();
+ Facebook.prototype.constructor = Facebook;
+
+ Facebook.prototype.start = function() {
+ var self = this;
+
+ if (self.isUrlDialog) {
+ self._invokeIfCallable(self.callback);
+ } else {
+ self.window.fbAsyncInit = function() {
+ // init the FB JS SDK
+ FB.init ({
+ appId: self.appId, // App ID from the App Dashboard
+ channelUrl: self.channelUrl, // Channel File for x-domain communication
+ status: true, // check the login status upon init?
+ cookie: true, // set sessions cookies to allow your server to access the session?
+ xfbml: true // parse XFBML tags on this page?
+ });
+
+ self._invokeIfCallable(self.callback);
+ };
+
+ // Load the SDK's source Asynchronously
+ (function(d, debug){
+ var js, id = 'facebook-jssdk', ref = d.getElementsByTagName('script')[0];
+ if (d.getElementById(id)) {return;}
+ js = d.createElement('script'); js.id = id; js.async = true;
+ js.src = '//connect.facebook.net/en_US/all' + (debug ? '/debug' : '') + '.js';
+ ref.parentNode.insertBefore(js, ref);
+ }(self.window.document, /*debug*/ self.isDebug));
+ }
+ };
+
+ Facebook.prototype.getTabUrl = function(pageNamespace) {
+ var self = this;
+ return self.window.location.protocol + '//www.facebook.com/' + pageNamespace + '/app_' + self.appId;
+ };
+
+ Facebook.prototype.share = function(options, callback) {
+ var self = this;
+ self._dialog('feed', options, callback);
+ };
+
+ Facebook.prototype.invite = function(options, callback) {
+ var self = this;
+ self._dialog('apprequests', options, callback);
+ };
+
+ Facebook.prototype._dialog = function(dialogMethod, options, callback) {
+ var self = this;
+ var dialogUrl;
+ options = options || {};
+ options.show_error = self.isDebug;
+
+ if (self.isUrlDialog) {
+ options.app_id = self.appId;
+
+ if (typeof callback === 'string') {
+ options.redirect_uri = callback;
+ } else {
+ options.redirect_uri = self.window.location.href;
+ }
+
+ dialogUrl = '//www.facebook.com/dialog/' + dialogMethod + '/?';
+ dialogUrl += self._objectToQueryString(options);
+
+ self.window.top.location = dialogUrl;
+ } else {
+ options.method = dialogMethod;
+
+ if (typeof options.callback !== 'function') {
+ callback = undefined;
+ }
+
+ FB.ui(options, callback);
+ }
+ };
+
+
+ // Utility methods
+ //------------------------------------------
+ Facebook.prototype._invokeIfCallable = function(fnCallableThing) {
+ if (typeof fnCallableThing === 'function') {
+ fnCallableThing();
+ }
+ };
+
+ Facebook.prototype._objectToQueryString = function(argumentsObject) {
+ var self = this;
+ var urlEncode = self.window.encodeURIComponent;
+ var fieldsArray = [];
+ var field;
+ var value;
+ var queryString;
+
+ for (field in argumentsObject) {
+ value = argumentsObject[field];
+ fieldsArray.push(urlEncode(field) + '=' + urlEncode(value));
+ }
+
+ queryString = fieldsArray.join('&');
+ return queryString;
+ };
+
+ return Facebook;
+} (DOWNLOADTAB));
View
73 media/js/facebookapps/Slider.js
@@ -0,0 +1,73 @@
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+// Slider class
+DOWNLOADTAB.classes.Slider = (function (singleton) {
+ function Slider($, selector) {
+ singleton.classes.Base.call(this);
+
+ this.$ = $;
+
+ this._initData = undefined;
+
+ // Cache
+ this.cache = {
+ slider: this.$(selector),
+ items: this.$(selector).find('li')
+ };
+
+ // Initialize slider
+ this.init();
+ }
+
+ // Initialize Base object and set correct constructor
+ Slider.prototype = new singleton.classes.Base();
+ Slider.prototype.constructor = Slider;
+
+ // Class methods
+ Slider.prototype.init = function() {
+ var self = this;
+
+ self.cache.slider.find('.next').on('click', function() {
+ self.cycle('next');
+ });
+ self.cache.slider.find('.prev').on('click', function() {
+ self.cycle('prev');
+ });
+ };
+
+ Slider.prototype.cycle = function (direction) {
+ var self = this;
+ var visible = self.cache.items.not('.visuallyhidden');
+ var animation;
+
+ // Hide them all
+ self.cache.items.removeClass().addClass('visuallyhidden');
+
+ // Decide which animation we're gonna use.
+ if (direction === 'next') {
+ animation = 'fadeInRight';
+ } else {
+ animation = 'fadeInLeft';
+ }
+
+ if (visible[direction]().length !== 0) {
+ visible[direction]()
+ .removeClass()