From 8adb8e1fcc301f34a65d00bba5cd22f4916c270c Mon Sep 17 00:00:00 2001 From: alex bisceglie Date: Thu, 24 Mar 2011 13:09:20 -0700 Subject: [PATCH] merging in updates to initial --- .gitignore | 8 + README.md | 54 + ffdemo/markup/__init__.py | 0 ffdemo/markup/common.py | 82 + ffdemo/markup/forms.py | 12 + ffdemo/markup/management/__init__.py | 0 ffdemo/markup/management/commands/__init__.py | 0 .../management/commands/generate_invites.py | 25 + ffdemo/markup/migrations/0001_initial.py | 65 + ...auto__add_unique_invitation_invite_code.py | 50 + ffdemo/markup/migrations/__init__.py | 0 ffdemo/markup/models.py | 34 + ffdemo/markup/requests.py | 459 + ffdemo/markup/templatetags/__init__.py | 0 ffdemo/markup/templatetags/gml_extras.py | 8 + ffdemo/markup/views.py | 119 + ffdemo/middleware.py | 72 + ffdemo/settings.py | 207 + ffdemo/static/Jimfile | 34 + ffdemo/static/assets/css/style.css | 1212 +++ ffdemo/static/assets/images/arrow.png | Bin 0 -> 192 bytes ffdemo/static/assets/images/arrow_orange.png | Bin 0 -> 372 bytes ffdemo/static/assets/images/bg.jpg | Bin 0 -> 7006 bytes ffdemo/static/assets/images/cancel.png | Bin 0 -> 1183 bytes ffdemo/static/assets/images/collapsed.png | Bin 0 -> 145 bytes ffdemo/static/assets/images/collapsible.png | Bin 0 -> 989 bytes .../assets/images/coming-soon-tip-bg.png | Bin 0 -> 1173 bytes .../static/assets/images/contributor_icon.png | Bin 0 -> 1023 bytes .../assets/images/contributor_label.png | Bin 0 -> 1055 bytes ffdemo/static/assets/images/controls.png | Bin 0 -> 1653 bytes ffdemo/static/assets/images/evan_roth.jpg | Bin 0 -> 2257 bytes ffdemo/static/assets/images/evan_roth_bio.jpg | Bin 0 -> 23674 bytes ffdemo/static/assets/images/facebook.png | Bin 0 -> 2131 bytes ffdemo/static/assets/images/favicon.png | Bin 0 -> 1261 bytes ffdemo/static/assets/images/firefox_logo.jpg | Bin 0 -> 3127 bytes ffdemo/static/assets/images/flag.png | Bin 0 -> 1232 bytes ffdemo/static/assets/images/footer_bg.gif | Bin 0 -> 106 bytes ffdemo/static/assets/images/i.png | Bin 0 -> 970 bytes ffdemo/static/assets/images/large_dash.gif | Bin 0 -> 1116 bytes ffdemo/static/assets/images/light_overlay.png | Bin 0 -> 1028 bytes ffdemo/static/assets/images/loader_light.gif | Bin 0 -> 2129 bytes ffdemo/static/assets/images/loading.gif | Bin 0 -> 2061 bytes .../assets/images/locale_selector_arrow.png | Bin 0 -> 1076 bytes ffdemo/static/assets/images/location.png | Bin 0 -> 2082 bytes .../images/location_selector_dropdown_bg.gif | Bin 0 -> 1594 bytes .../static/assets/images/location_small.png | Bin 0 -> 1189 bytes ffdemo/static/assets/images/magnifying.png | Bin 0 -> 3022 bytes ffdemo/static/assets/images/mark_up_logo.jpg | Bin 0 -> 11848 bytes ffdemo/static/assets/images/mozilla_tab.png | Bin 0 -> 3355 bytes ffdemo/static/assets/images/nextMark.png | Bin 0 -> 1331 bytes ffdemo/static/assets/images/overlay.png | Bin 0 -> 197 bytes ffdemo/static/assets/images/playback.png | Bin 0 -> 1519 bytes ffdemo/static/assets/images/prevMark.png | Bin 0 -> 1322 bytes ffdemo/static/assets/images/reset.png | Bin 0 -> 1527 bytes ffdemo/static/assets/images/site_thumb.jpg | Bin 0 -> 2601 bytes ffdemo/static/assets/images/submit.png | Bin 0 -> 1095 bytes ffdemo/static/assets/images/tri.png | Bin 0 -> 993 bytes ffdemo/static/assets/images/tri_rtl.png | Bin 0 -> 1010 bytes ffdemo/static/assets/images/twitter.png | Bin 0 -> 2185 bytes .../static/assets/images/web_of_wonders.jpg | Bin 0 -> 2829 bytes ffdemo/static/assets/images/world_eater.jpg | Bin 0 -> 2142 bytes ffdemo/static/assets/images/zoomIn.png | Bin 0 -> 1183 bytes ffdemo/static/assets/images/zoomOut.png | Bin 0 -> 1389 bytes ffdemo/static/assets/js/app.js | 161 + ffdemo/static/assets/js/bundled.js | 4419 +++++++++ ffdemo/static/assets/js/common.js | 32 + ffdemo/static/assets/js/jquery-1.5.1.js | 8316 +++++++++++++++++ ffdemo/static/assets/js/jquery-1.5.1.min.js | 16 + ffdemo/static/assets/js/markApp.js | 7123 ++++++++++++++ ffdemo/static/assets/js/markApp.min.js | 190 + .../assets/js/vendor/canvas_extensions.js | 52 + .../assets/js/vendor/country_codes.json | 1 + .../assets/js/vendor/jquery.collapsibleMod.js | 102 + .../assets/js/vendor/jquery.delayedBind.js | 68 + .../js/vendor/jquery.markApp.capture.js | 526 ++ .../assets/js/vendor/jquery.markApp.intro.js | 310 + .../static/assets/js/vendor/jquery.markApp.js | 317 + .../assets/js/vendor/jquery.markApp.linear.js | 975 ++ .../assets/js/vendor/jquery.socialShare.js | 79 + .../assets/js/vendor/jquery.ui-selectBox.js | 822 ++ ffdemo/static/assets/js/vendor/json2.js | 478 + .../static/assets/js/vendor/mark/mark.base.js | 13 + .../assets/js/vendor/mark/mark.brushes.js | 218 + .../assets/js/vendor/mark/mark.camera.js | 14 + .../assets/js/vendor/mark/mark.gmlMark.js | 166 + .../assets/js/vendor/mark/mark.gmlPoint.js | 56 + .../assets/js/vendor/mark/mark.layer.js | 45 + .../js/vendor/mark/mark.layerManager.js | 42 + .../assets/js/vendor/mark/mark.renderer.js | 156 + .../assets/js/vendor/mark/mark.scene.js | 37 + .../js/vendor/mark/mark.simplifyPath.js | 86 + .../vendor/plugins/sammy.hash_push_proxy.js | 110 + .../js/vendor/plugins/sammy.template.js | 141 + ffdemo/static/assets/js/vendor/sammy-0.6.3.js | 1829 ++++ ffdemo/static/assets/js/vendor/tween.js | 470 + ffdemo/templates_orig/about.html | 31 + ffdemo/templates_orig/base.html | 103 + ffdemo/templates_orig/base_min.html | 18 + ffdemo/templates_orig/code.html | 13 + ffdemo/templates_orig/community.html | 12 + ffdemo/templates_orig/evan-roth.html | 13 + ffdemo/templates_orig/gml.html | 15 + ffdemo/templates_orig/gml.xml | 24 + ffdemo/templates_orig/home.html | 26 + ffdemo/templates_orig/manifesto.html | 34 + ffdemo/templates_orig/mozilla.html | 13 + ffdemo/templates_orig/newsletter.html | 34 + .../templates_orig/registration/locked.html | 3 + ffdemo/templates_orig/registration/login.html | 38 + ffdemo/templates_orig/sammy/linear.html | 87 + ffdemo/templates_orig/sammy/makemark.html | 56 + ffdemo/templates_orig/sammy/moderate.html | 31 + ffdemo/urls.py | 66 + 113 files changed, 30428 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 ffdemo/markup/__init__.py create mode 100644 ffdemo/markup/common.py create mode 100644 ffdemo/markup/forms.py create mode 100644 ffdemo/markup/management/__init__.py create mode 100644 ffdemo/markup/management/commands/__init__.py create mode 100644 ffdemo/markup/management/commands/generate_invites.py create mode 100644 ffdemo/markup/migrations/0001_initial.py create mode 100644 ffdemo/markup/migrations/0002_auto__add_unique_invitation_invite_code.py create mode 100644 ffdemo/markup/migrations/__init__.py create mode 100644 ffdemo/markup/models.py create mode 100644 ffdemo/markup/requests.py create mode 100644 ffdemo/markup/templatetags/__init__.py create mode 100644 ffdemo/markup/templatetags/gml_extras.py create mode 100644 ffdemo/markup/views.py create mode 100644 ffdemo/middleware.py create mode 100644 ffdemo/settings.py create mode 100644 ffdemo/static/Jimfile create mode 100644 ffdemo/static/assets/css/style.css create mode 100644 ffdemo/static/assets/images/arrow.png create mode 100644 ffdemo/static/assets/images/arrow_orange.png create mode 100644 ffdemo/static/assets/images/bg.jpg create mode 100644 ffdemo/static/assets/images/cancel.png create mode 100644 ffdemo/static/assets/images/collapsed.png create mode 100644 ffdemo/static/assets/images/collapsible.png create mode 100644 ffdemo/static/assets/images/coming-soon-tip-bg.png create mode 100644 ffdemo/static/assets/images/contributor_icon.png create mode 100644 ffdemo/static/assets/images/contributor_label.png create mode 100644 ffdemo/static/assets/images/controls.png create mode 100644 ffdemo/static/assets/images/evan_roth.jpg create mode 100644 ffdemo/static/assets/images/evan_roth_bio.jpg create mode 100644 ffdemo/static/assets/images/facebook.png create mode 100644 ffdemo/static/assets/images/favicon.png create mode 100644 ffdemo/static/assets/images/firefox_logo.jpg create mode 100644 ffdemo/static/assets/images/flag.png create mode 100644 ffdemo/static/assets/images/footer_bg.gif create mode 100644 ffdemo/static/assets/images/i.png create mode 100644 ffdemo/static/assets/images/large_dash.gif create mode 100644 ffdemo/static/assets/images/light_overlay.png create mode 100644 ffdemo/static/assets/images/loader_light.gif create mode 100644 ffdemo/static/assets/images/loading.gif create mode 100644 ffdemo/static/assets/images/locale_selector_arrow.png create mode 100644 ffdemo/static/assets/images/location.png create mode 100644 ffdemo/static/assets/images/location_selector_dropdown_bg.gif create mode 100644 ffdemo/static/assets/images/location_small.png create mode 100644 ffdemo/static/assets/images/magnifying.png create mode 100644 ffdemo/static/assets/images/mark_up_logo.jpg create mode 100644 ffdemo/static/assets/images/mozilla_tab.png create mode 100644 ffdemo/static/assets/images/nextMark.png create mode 100644 ffdemo/static/assets/images/overlay.png create mode 100644 ffdemo/static/assets/images/playback.png create mode 100644 ffdemo/static/assets/images/prevMark.png create mode 100644 ffdemo/static/assets/images/reset.png create mode 100644 ffdemo/static/assets/images/site_thumb.jpg create mode 100644 ffdemo/static/assets/images/submit.png create mode 100644 ffdemo/static/assets/images/tri.png create mode 100644 ffdemo/static/assets/images/tri_rtl.png create mode 100644 ffdemo/static/assets/images/twitter.png create mode 100644 ffdemo/static/assets/images/web_of_wonders.jpg create mode 100644 ffdemo/static/assets/images/world_eater.jpg create mode 100644 ffdemo/static/assets/images/zoomIn.png create mode 100644 ffdemo/static/assets/images/zoomOut.png create mode 100644 ffdemo/static/assets/js/app.js create mode 100644 ffdemo/static/assets/js/bundled.js create mode 100644 ffdemo/static/assets/js/common.js create mode 100644 ffdemo/static/assets/js/jquery-1.5.1.js create mode 100644 ffdemo/static/assets/js/jquery-1.5.1.min.js create mode 100644 ffdemo/static/assets/js/markApp.js create mode 100644 ffdemo/static/assets/js/markApp.min.js create mode 100644 ffdemo/static/assets/js/vendor/canvas_extensions.js create mode 100644 ffdemo/static/assets/js/vendor/country_codes.json create mode 100644 ffdemo/static/assets/js/vendor/jquery.collapsibleMod.js create mode 100644 ffdemo/static/assets/js/vendor/jquery.delayedBind.js create mode 100644 ffdemo/static/assets/js/vendor/jquery.markApp.capture.js create mode 100644 ffdemo/static/assets/js/vendor/jquery.markApp.intro.js create mode 100644 ffdemo/static/assets/js/vendor/jquery.markApp.js create mode 100644 ffdemo/static/assets/js/vendor/jquery.markApp.linear.js create mode 100644 ffdemo/static/assets/js/vendor/jquery.socialShare.js create mode 100644 ffdemo/static/assets/js/vendor/jquery.ui-selectBox.js create mode 100644 ffdemo/static/assets/js/vendor/json2.js create mode 100644 ffdemo/static/assets/js/vendor/mark/mark.base.js create mode 100644 ffdemo/static/assets/js/vendor/mark/mark.brushes.js create mode 100644 ffdemo/static/assets/js/vendor/mark/mark.camera.js create mode 100644 ffdemo/static/assets/js/vendor/mark/mark.gmlMark.js create mode 100644 ffdemo/static/assets/js/vendor/mark/mark.gmlPoint.js create mode 100644 ffdemo/static/assets/js/vendor/mark/mark.layer.js create mode 100644 ffdemo/static/assets/js/vendor/mark/mark.layerManager.js create mode 100644 ffdemo/static/assets/js/vendor/mark/mark.renderer.js create mode 100644 ffdemo/static/assets/js/vendor/mark/mark.scene.js create mode 100644 ffdemo/static/assets/js/vendor/mark/mark.simplifyPath.js create mode 100644 ffdemo/static/assets/js/vendor/plugins/sammy.hash_push_proxy.js create mode 100755 ffdemo/static/assets/js/vendor/plugins/sammy.template.js create mode 100755 ffdemo/static/assets/js/vendor/sammy-0.6.3.js create mode 100644 ffdemo/static/assets/js/vendor/tween.js create mode 100644 ffdemo/templates_orig/about.html create mode 100644 ffdemo/templates_orig/base.html create mode 100644 ffdemo/templates_orig/base_min.html create mode 100644 ffdemo/templates_orig/code.html create mode 100644 ffdemo/templates_orig/community.html create mode 100644 ffdemo/templates_orig/evan-roth.html create mode 100644 ffdemo/templates_orig/gml.html create mode 100644 ffdemo/templates_orig/gml.xml create mode 100644 ffdemo/templates_orig/home.html create mode 100644 ffdemo/templates_orig/manifesto.html create mode 100644 ffdemo/templates_orig/mozilla.html create mode 100644 ffdemo/templates_orig/newsletter.html create mode 100644 ffdemo/templates_orig/registration/locked.html create mode 100644 ffdemo/templates_orig/registration/login.html create mode 100644 ffdemo/templates_orig/sammy/linear.html create mode 100644 ffdemo/templates_orig/sammy/makemark.html create mode 100644 ffdemo/templates_orig/sammy/moderate.html create mode 100644 ffdemo/urls.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..49c9f84 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +settings_local.py +*.py[co] +*.sw[po] +*.pot +*.mo +pip-log.txt +build.py +.DS_Store diff --git a/README.md b/README.md new file mode 100644 index 0000000..1bab793 --- /dev/null +++ b/README.md @@ -0,0 +1,54 @@ +Markup +=== + +Firefox MarkUp is a [Django][Django]-based web application... +[Django]: http://www.djangoproject.com/ + +Getting Started +--- +### Python +You need Python 2.6. Also, you probably want to run this application in a +[virtualenv][virtualenv] environment + +[virtualenv]: http://pypi.python.org/pypi/virtualenv + +### Dependencies + +run + + easy_install pip + +followed by + + ./bootsrap.sh + + + +### Django + +South is used for migrations, so to sync the db, run + + manage.py syncdb + +and to run the migs + + manage.py migrate + + +Then run the invite-generation script. generate_invites.py takes two vars: number of invites to generate, and type of invites to generate. invite-types are 't' for translator, or 'c' for contributor. + +for 5 translator invites, run + manage.py generate_invites 5 t +for 10 contributor invites, run + manage.py generate_invites 10 c + + +For production environments, uncomment the following in settings: + # CACHE_BACKEND = 'caching.backends.memcached://localhost:11211?timeout=500' + + # SESSION_COOKIE_SECURE = True + # SESSION_COOKIE_HTTPONLY = True + +and set + REDIRECT_TO_SSL = True + diff --git a/ffdemo/markup/__init__.py b/ffdemo/markup/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/ffdemo/markup/common.py b/ffdemo/markup/common.py new file mode 100644 index 0000000..9b1fe00 --- /dev/null +++ b/ffdemo/markup/common.py @@ -0,0 +1,82 @@ +from ffdemo.markup.models import Mark +from ffdemo.markup.models import Invitation +from ffdemo.utils import short_url +from datetime import datetime +import re + +def get_invite_from_code(c): + invite = None + try: + invite = Invitation.objects.get(invite_code=c) + except Invitation.DoesNotExist: + return None + return invite +def get_translated_marks(): + return + +# convenience method to unpack marks for request return +def decode_mark_objects(data): + if data: + all_marks = [] + for m in data: + # We need to decode the points obj simplified + decoded_points_obj = decode_points_obj(m.points_obj_simplified) + # Append to all marks + all_marks.append({'date_drawn': m.date_drawn.strftime("%a, %d %b %Y %I:%M:%S"), 'reference': m.reference, 'id': m.id, 'points_obj_simplified': decoded_points_obj, 'country_code': m.country_code, 'contributor': m.contributor, 'is_approved': m.is_approved}) + return all_marks + else: + return + return + +def save_new_mark_with_data(data): + # Remove whitespace from raw full points obj + stripped_points_obj_full = re.sub(r'\s', '', data['points_obj']) + # remove whitespace where not in extra_info (the contributor quote) + j = re.compile('^.*\"extra\_info"\:\"') + k = re.compile('\"extra\_info"\:\".*\"\,*.*$') + sec1 = j.search(data['points_obj_simplified']) + sec2 = k.search(data['points_obj_simplified']) + if sec1 and sec2: + stripped_sec1 = re.sub(r'\s','',sec1.group()) + stripped_sec2 = re.sub('"extra_info":"','',sec2.group()) + stripped_points_obj_simplified = stripped_sec1 + stripped_sec2 + else: + stripped_points_obj_simplified = re.sub(r'\s','',data['points_obj_simplified']) + # stripped_points_obj_simplified = re.sub(r'\s', '', data['points_obj_simplified']) + # Encode both + encoded_points_obj_full = stripped_points_obj_full.encode('base64', 'strict') + encoded_points_obj_simplified = stripped_points_obj_simplified.encode('base64', 'strict') + # New mark + new_mark = Mark.objects.create() + new_mark.points_obj = encoded_points_obj_full + new_mark.points_obj_simplified = encoded_points_obj_simplified + new_mark.reference = short_url.encode_url(new_mark.id) + if 'country_code' in data: + new_mark.country_code = data['country_code'] + invite = None + if 'invite' in data: + invite = get_invite_from_code(data['invite']) + if invite and 'contributor_locale' in data and len(data['contributor_locale'])>0: + new_mark.contributor_locale = data['contributor_locale'] + else: + pass + if invite and 'contributor' in data and len(data['contributor'])>0: + new_mark.contributor = data['contributor'] + else: + pass + else: + pass + + new_mark.save() + if invite: + invite.used_at = datetime.now() + invite.save() + # Catch errors + return new_mark.reference + +def decode_points_obj(obj): + returned_str = str(obj) + decoded_data = returned_str.decode('base64', 'strict') + return decoded_data + + diff --git a/ffdemo/markup/forms.py b/ffdemo/markup/forms.py new file mode 100644 index 0000000..4f5f6d5 --- /dev/null +++ b/ffdemo/markup/forms.py @@ -0,0 +1,12 @@ +from django.forms import ModelForm +from ffdemo.markup.models import Mark + +class MarkForm(ModelForm): + class Meta: + model = Mark + fields = ('points_obj', 'country_code') + + +class LoginForm(ModelForm): + class Meta: + model = Mark diff --git a/ffdemo/markup/management/__init__.py b/ffdemo/markup/management/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/ffdemo/markup/management/commands/__init__.py b/ffdemo/markup/management/commands/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/ffdemo/markup/management/commands/generate_invites.py b/ffdemo/markup/management/commands/generate_invites.py new file mode 100644 index 0000000..c04d37f --- /dev/null +++ b/ffdemo/markup/management/commands/generate_invites.py @@ -0,0 +1,25 @@ +import os, sys +from django.db import models +from django.core.management.base import BaseCommand, CommandError +from django.conf import settings +from ffdemo.markup.models import Invitation + +class Command(BaseCommand): + args = '' + def handle(self, *args, **options): + if len(args) < 2: + raise CommandError('Requires number of invites to generate and type of invite') + num_invites = int(args[0]) + invite_type = args[1] + valid_values = [] + for choice_id, choice_label in settings.CONTRIBUTOR_TYPE_CHOICES: + valid_values += choice_id + if invite_type not in valid_values: + raise CommandError('Invite type must be in ', valid_values) + else: + print "generating ", num_invites, " ", invite_type, "invites" + for i in range(num_invites): + invite = Invitation(contributor_type=invite_type) + invite.save() + print "finished generating invites" + diff --git a/ffdemo/markup/migrations/0001_initial.py b/ffdemo/markup/migrations/0001_initial.py new file mode 100644 index 0000000..ea1953a --- /dev/null +++ b/ffdemo/markup/migrations/0001_initial.py @@ -0,0 +1,65 @@ +# encoding: utf-8 +import datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + +class Migration(SchemaMigration): + + def forwards(self, orm): + + # Adding model 'Mark' + db.create_table('markup_mark', ( + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('date_drawn', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)), + ('reference', self.gf('django.db.models.fields.CharField')(max_length=50, blank=True, unique=True)), + ('points_obj', self.gf('django.db.models.fields.TextField')(blank=True)), + ('points_obj_simplified', self.gf('django.db.models.fields.TextField')(blank=True)), + ('country_code', self.gf('django.db.models.fields.CharField')(max_length=2, blank=True)), + ('contributor_locale', self.gf('django.db.models.fields.CharField')(max_length=5, null=True, blank=True)), + ('contributor', self.gf('django.db.models.fields.CharField')(max_length=75, null=True, blank=True)), + ('flaggings', self.gf('django.db.models.fields.IntegerField')(default=0)), + ('is_approved', self.gf('django.db.models.fields.BooleanField')(default=False)) + )) + db.send_create_signal('markup', ['Mark']) + + # Adding model 'Invitation' + db.create_table('markup_invitation', ( + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('invite_code', self.gf('django.db.models.fields.SlugField')(db_index=True, max_length=50, blank=True)), + ('contributor_type', self.gf('django.db.models.fields.CharField')(max_length=1)), + ('used_at', self.gf('django.db.models.fields.DateTimeField')(blank=True)) + )) + db.send_create_signal('markup', ['Invitation']) + + def backwards(self, orm): + + # Deleting model 'Mark' + db.delete_table('markup_mark') + # Deleting model 'Invitation' + db.delete_table('markup_invitation') + + models = { + 'markup.invitation': { + 'Meta': {'object_name': 'Invitation'}, + 'contributor_type': ('django.db.models.fields.CharField', [], {'max_length': '1'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'invite_code': ('django.db.models.fields.SlugField', [], {'max_length': '50', 'db_index': 'True'}), + 'used_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}) + }, + 'markup.mark': { + 'Meta': {'object_name': 'Mark'}, + 'contributor': ('django.db.models.fields.CharField', [], {'max_length': '75', 'null': 'True', 'blank': 'True'}), + 'contributor_locale': ('django.db.models.fields.CharField', [], {'max_length': '5', 'null': 'True', 'blank': 'True'}), + 'country_code': ('django.db.models.fields.CharField', [], {'max_length': '2', 'blank': 'True'}), + 'date_drawn': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'flaggings': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_approved': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'points_obj': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'points_obj_simplified': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'reference': ('django.db.models.fields.CharField', [], {'max_length': '50', 'blank': 'True'}) + } + } + + complete_apps = ['markup'] diff --git a/ffdemo/markup/migrations/0002_auto__add_unique_invitation_invite_code.py b/ffdemo/markup/migrations/0002_auto__add_unique_invitation_invite_code.py new file mode 100644 index 0000000..004be1f --- /dev/null +++ b/ffdemo/markup/migrations/0002_auto__add_unique_invitation_invite_code.py @@ -0,0 +1,50 @@ +# encoding: utf-8 +import datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + +class Migration(SchemaMigration): + + def forwards(self, orm): + + # Adding unique constraint on 'Invitation', fields ['invite_code'] + db.create_unique('markup_invitation', ['invite_code']) + + # Adding index on 'Mark', fields ['reference'] + db.create_index('markup_mark', ['reference']) + + + def backwards(self, orm): + + # Removing index on 'Mark', fields ['reference'] + db.delete_index('markup_mark', ['reference']) + + # Removing unique constraint on 'Invitation', fields ['invite_code'] + db.delete_unique('markup_invitation', ['invite_code']) + + + models = { + 'markup.invitation': { + 'Meta': {'object_name': 'Invitation'}, + 'contributor_type': ('django.db.models.fields.CharField', [], {'max_length': '1'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'invite_code': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '50', 'db_index': 'True'}), + 'used_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}) + }, + 'markup.mark': { + 'Meta': {'object_name': 'Mark'}, + 'contributor': ('django.db.models.fields.CharField', [], {'max_length': '75', 'null': 'True', 'blank': 'True'}), + 'contributor_locale': ('django.db.models.fields.CharField', [], {'max_length': '5', 'null': 'True', 'blank': 'True'}), + 'country_code': ('django.db.models.fields.CharField', [], {'max_length': '2', 'blank': 'True'}), + 'date_drawn': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'flaggings': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_approved': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'points_obj': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'points_obj_simplified': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'reference': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'unique': 'True', 'max_length': '50', 'blank': 'True'}) + } + } + + complete_apps = ['markup'] diff --git a/ffdemo/markup/migrations/__init__.py b/ffdemo/markup/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/ffdemo/markup/models.py b/ffdemo/markup/models.py new file mode 100644 index 0000000..91d050a --- /dev/null +++ b/ffdemo/markup/models.py @@ -0,0 +1,34 @@ +from django.db import models +from django.template.defaultfilters import slugify +import hashlib +import uuid +from datetime import datetime +from django.conf import settings + +class Mark(models.Model): + date_drawn = models.DateTimeField(auto_now_add=True) + reference = models.CharField(max_length=50, blank=True, db_index=True, unique=True) + points_obj = models.TextField(blank=True) + points_obj_simplified = models.TextField(blank=True) + country_code = models.CharField(max_length=2, blank=True) + flaggings = models.IntegerField(default=0) + is_approved = models.BooleanField(default=False) + # contributor attrs + contributor_locale = models.CharField(max_length=5, blank=True, null=True) + contributor = models.CharField(max_length=75, blank=True, null=True) + + def __unicode__(self): + return unicode(self.date_drawn) + +class Invitation(models.Model): + invite_code = models.SlugField(max_length=50, unique=True, db_index=True) + contributor_type = models.CharField(max_length=1, choices=settings.CONTRIBUTOR_TYPE_CHOICES) + used_at = models.DateTimeField(blank=True, null=True) + + def save(self): + myuuid = uuid.uuid1().hex + self.invite_code = slugify( hashlib.md5(myuuid + datetime.now().strftime("%Y%m%d%H%m%s") ).hexdigest()[:12] ) + super( Invitation, self ).save() + + def __unicode__(self): + return self.invite_code diff --git a/ffdemo/markup/requests.py b/ffdemo/markup/requests.py new file mode 100644 index 0000000..8ebddfc --- /dev/null +++ b/ffdemo/markup/requests.py @@ -0,0 +1,459 @@ +from django.http import HttpResponse +from ffdemo.markup.models import Mark +from ffdemo.markup import common +from django.utils import simplejson +from django.core import serializers +import datetime +from django.db.models import Q + +def get_translated_marks(request): + marks_to_be_dumped = None + dthandler = lambda obj: obj.isoformat() if isinstance(obj, datetime.datetime) else None + response = {'success': False} + marks_to_be_dumped = Mark.objects.exclude(contributor_locale__isnull=True).order_by('id') + if marks_to_be_dumped: + all_marks = [] + for m in marks_to_be_dumped: + # We need to decode the points obj simplified + decoded_points_obj = common.decode_points_obj(m.points_obj_simplified) + # Append to all marks + all_marks.append({'date_drawn': m.date_drawn.strftime("%a, %d %b %Y %I:%M:%S"), 'reference': m.reference, 'id': m.id, 'points_obj_simplified': decoded_points_obj, 'country_code': m.country_code, 'is_approved': m.is_approved, 'contributor_locale': m.contributor_locale}) + response['success'] = True + response['marks'] = all_marks + else: + response['success'] = False + response['error'] = "No marks to be parsed" + + json_response = simplejson.dumps(response) + return HttpResponse(json_response,'application/javascript') + +def flag_mark(request): + response = {'success': False} + if 'reference' in request.POST and len(request.POST['reference']) > 0: + try: + mark = Mark.objects.get(reference=request.POST['reference']) + if mark.is_approved: + pass + else: + mark.flaggings += 1 + mark.save() + response['success'] = True + except Mark.DoesNotExist: + response['error'] = "Mark does not exist" + except Mark.MultipleObjectsReturned: + # should never [ever] happen, purely CYA + response['error'] = "Multiple marks returned" + else: + response['error'] = "No mark specified" + json_response = simplejson.dumps(response) + return HttpResponse(json_response, 'application/javascript') + +def init_viz_data(request): + # grab the last mark + # grab the first mark + response = {} + response['country_total_marks'] = '' + response['country_first_mark'] = '' + response['country_last_mark'] = '' + response['country_first_mark_at'] = '' + # for contributed marks + response['contributor_marks'] = [] + contributor_marks = common.decode_mark_objects(Mark.objects.exclude(contributor__isnull=True).order_by('id')) + response['contributor_marks'] = contributor_marks + all_marks = Mark.objects.exclude(flaggings__gte=1).filter(contributor_locale__isnull=True).order_by('id') + if 'country_code' in request.GET and len(request.GET['country_code']) > 0: + country_marks = Mark.objects.exclude(flaggings__gte=1).filter(contributor_locale__isnull=True,country_code=request.GET['country_code']).order_by('id') + if len(country_marks) > 0: + response['country_total_marks'] = country_marks.count() + response['country_first_mark'] = country_marks[0].reference + response['country_last_mark'] = country_marks[response['country_total_marks']-1].reference + response['country_first_mark_at'] = country_marks[0].date_drawn.strftime("%a, %d %b %Y %I:%M:%S") + else: + pass + else: + pass + + response['total_marks'] = all_marks.count() + response['last_mark'] = all_marks[response['total_marks']-1].reference + response['first_mark'] = all_marks[0].reference + response['first_mark_at'] = all_marks[0].date_drawn.strftime("%a, %d %b %Y %I:%M:%S") + response['total_countries'] = Mark.objects.values('country_code').distinct().count() + json_response = simplejson.dumps(response) + return HttpResponse(json_response, 'application/javascript') + + +def save_mark(request): + # Default response + response = {'success': False} + # Check for mandatory POST data + if 'points_obj' in request.POST and 'points_obj_simplified' in request.POST: + # Cosntruct mark data + mark_data = { 'points_obj': request.POST['points_obj'], 'points_obj_simplified': request.POST['points_obj_simplified'] } + if 'country_code' in request.POST: + mark_data['country_code'] = request.POST['country_code'] + if 'invite' in request.POST: + mark_data['invite'] = request.POST['invite'] + if 'contributor_locale' in request.POST: + mark_data['contributor_locale'] = request.POST['contributor_locale'] + else: + pass + if 'contributor' in request.POST: + mark_data['contributor'] = request.POST['contributor'] + else: + pass + else: + pass + + # Save new mark, handled by common.py + new_mark_reference = common.save_new_mark_with_data(mark_data) + # Successful response, returning new mark reference + response['success'] = True + response['mark_reference'] = new_mark_reference + else: + # Error response + response['success'] = False + response['error'] = 'missing data in POST request' + # Return response as json + json_response = simplejson.dumps(response) + return HttpResponse(json_response, 'application/javascript') + + +def delete_mark(request): + # Completely remove the mark + response = {'success': False} + if 'reference' in request.POST and len(request.POST['reference']) > 0: + try: + m = Mark.objects.get(reference=request.POST['reference']) + m.delete() + response['success'] = True + except Mark.DoesNotExist: + response['error'] = 'Mark does not exist' + else: + response['error'] = "No mark specified" + json_response = simplejson.dumps(response) + return HttpResponse(json_response, 'application/javascript') + + + +def approve_mark(request): + # Approve the mark // CHECK + response = {'success': False} + if 'reference' in request.POST and len(request.POST['reference']) > 0: + try: + m = Mark.objects.get(reference=request.POST['reference']) + should_approve = False + if request.POST['should_approve'] == "true": + should_approve = True + m.is_approved = should_approve + m.save() + response['success'] = True + except Mark.DoesNotExist: + response['error'] = 'Mark does not exist' + else: + response['error'] = "No mark specified" + json_response = simplejson.dumps(response) + return HttpResponse(json_response, 'application/javascript') + +def get_mark(request): + # Get mark by ID + mark = None + try: + mark = Mark.objects.get(reference=request.GET['mark_id']) + except Mark.DoesNotExist: + pass + except Mark.MultipleObjectsReturned: + pass + previous_marks = "" + next_marks = "" + # Decode simplified points data + decoded_points_obj = common.decode_points_obj(mark.points_obj_simplified) + # Return raw + return HttpResponse(decoded_points_obj, 'application/javascript') + + +def marks_by_offset(request): + # Parameters: + # offset: Integer - + # max: Integer - (defaults 15) + # country_code: String - filter by country-code + # + # returns json object including relevant marks with their attributes: id, reference string, points_obj, points_obj_simplified + dthandler = lambda obj: obj.isoformat() if isinstance(obj, datetime.datetime) else None + response = {'success': False} + + marks_to_be_dumped = None + did_fail_get_marks = False + + # We've got an offset to play with + if 'offset' in request.GET: + # An offset requires a max value to be returned from this offset + if 'max' in request.GET: + offset = request.GET['offset'] + max = request.GET['max'] + # We can also filter by country code if need be + if 'country_code' in request.GET: + marks_to_be_dumped = Mark.objects.exclude.exclude(contributor_locale__isnull=False).filter(country_code=request.GET['country_code'])[offset:max] + else: + marks_to_be_dumped = Mark.objects.all()[offset:max] + else: + response['success'] = False + response['error'] = "Querying by offset also requires a 'max' POST var" + did_fail_get_marks = True + else: + # No special query parameters, query for all marks + marks_to_be_dumped = Mark.objects.exclude(contributor_locale__isnull=False) + # Check that we've got marks to dump + if not did_fail_get_marks: + if marks_to_be_dumped: + # Dump out + all_marks = [] + for m in marks_to_be_dumped: + # We need to decode the points obj simplified + decoded_points_obj = common.decode_points_obj(m.points_obj_simplified) + # Append to all marks + all_marks.append({'date_drawn': m.date_drawn.strftime("%a, %d %b %Y %I:%M:%S"), 'reference': m.reference, 'points_obj_simplified': decoded_points_obj, 'contributor': m.contributor, 'country_code': m.country_code}) + response['success'] = True + response['marks'] = all_marks + else: + # No marks to dump + response['success'] = False + response['error'] = "No marks to be parsed" + # Dump and return + json_response = simplejson.dumps(response, default=dthandler) + return HttpResponse(json_response, 'application/javascript') + + + +def marks_by_locale(request): + # Parameters: + # country_code: String - filter by country-code + # max: Integer - (defaults 15) + # + # returns json object including relevant marks with their attributes: id, reference string, points_obj, points_obj_simplified + dthandler = lambda obj: obj.isoformat() if isinstance(obj, datetime.datetime) else None + response = {'success': False} + + max_returned = 15 + + marks_to_be_dumped = None + did_fail_get_marks = False + + +def marks_by_reference(request): + # Parameters: + # reference_mark: String - slug of reference mark + # include_back: Integer - number of returned marks before the reference mark (defaults to 0) + # include_forward: Integer - number of returned marks after the reference mark (defaults to 15) + # include_mark: Boolean - include the reference mark (defaults true) + # country_code: String - filter by country-code + # returns json object including relevant marks with their attributes: id, reference string, points_obj, points_obj_simplified + dthandler = lambda obj: obj.isoformat() if isinstance(obj, datetime.datetime) else None + response = {'success': False} + reference_mark = None + include_back = 0 + include_forward = 15 + include_mark = True + country_code = None + marks_to_be_dumped = None + did_fail_get_marks = False + m_offset = None + # default limit returns 19 marks + m_limit = include_back + 1 + include_forward + all_marks = None + offset_index = 0 + total_marks = 0 + + if 'reference' in request.GET: + reference_mark = request.GET['reference'] + try: + m_offset = Mark.objects.get(reference=reference_mark) + except Mark.DoesNotExist: + response['success'] = False + response['error'] = "Reference mark doesn't exist" + did_fail_get_marks = True + except Mark.MultipleObjectsReturned: + response['success'] = False + response['error'] = "Multiple marks found for reference" + did_fail_get_marks = True + if 'include_mark' in request.GET: + if int(request.GET['include_mark']) == 0: + include_mark = False + + + if 'include_forward' in request.GET: + include_forward = int(request.GET['include_forward']) + if 'include_back' in request.GET: + include_back = int(request.GET['include_back']) + + + + if 'country_code' in request.GET: + kountry_code = request.GET['country_code'] + all_marks = Mark.objects.exclude(flaggings__gte=1).filter(country_code=kountry_code,contributor_locale__isnull=True).order_by('id') + total_marks = all_marks.count() + for i, item in enumerate(all_marks): + if item.reference == reference_mark: + offset_index = i + break + relative_include_back = offset_index - include_back + if relative_include_back < 0: relative_include_back = 0 + unflagged_marks = Mark.objects.exclude(flaggings__gte=1).filter(contributor_locale__isnull=True) + if len(unflagged_marks) > 0: + marks_to_be_dumped = unflagged_marks.exclude(flaggings__gte=1,contributor_locale__isnull=False).filter(country_code=kountry_code,contributor_locale__isnull=True).order_by('id')[relative_include_back:offset_index+include_forward] + else: + response['success'] = False + response['error'] = "No marks to be dumped" + did_fail_get_marks = True + else: + all_marks = Mark.objects.exclude(flaggings__gte=1).filter(contributor_locale__isnull=True).order_by('id') + total_marks = all_marks.count() + for i, item in enumerate(all_marks): + if item.reference == reference_mark: + offset_index = i + break + relative_include_back = offset_index - include_back + if relative_include_back < 0: relative_include_back = 0 + try: + marks_to_be_dumped = Mark.objects.exclude(flaggings__gte=1).filter(contributor_locale__isnull=True).order_by('id')[relative_include_back:offset_index+include_forward] + except Mark.DoesNotExist: + response['success'] = False + response['error'] = "No marks to be dumped" + did_fail_get_marks = True + + else: + # required param + response['success'] = False + response['error'] = "Querying by reference requires a reference string" + did_fail_get_marks = True + + # Check that we've got marks to dump + if not did_fail_get_marks: + if marks_to_be_dumped: + # Dump out + all_marks = [] + + for m in marks_to_be_dumped: + is_reference_mark = False + if m.reference == reference_mark: + is_reference_mark = True + if include_mark == False and is_reference_mark: + pass + else: + # We need to decode the points obj simplified + decoded_points_obj = common.decode_points_obj(m.points_obj_simplified) + # Append to all marks + all_marks.append({'is_reference_mark': is_reference_mark,'date_drawn': m.date_drawn.strftime("%a, %d %b %Y %I:%M:%S"), 'reference': m.reference, 'id': m.id, 'points_obj_simplified': decoded_points_obj, 'contributor': m.contributor, 'country_code': m.country_code}) + response['success'] = True + response['marks'] = all_marks + else: + # No marks to dump + response['success'] = False + response['error'] = "No marks to be parsed" + + # Dump and return + json_response = simplejson.dumps(response, default=dthandler) + return HttpResponse(json_response, 'application/javascript') + + +def all_marks(request): + # Get all marks, all data per mark excluding full points object + # This method can be queried via POST depending what the frontend requires + # Handler for dumping datetime field as JSON + # + # offset: Integer - + # max: Integer - (defaults 15) + # country_code: String - filter by country-code + # + # returns json object including relevant marks with their attributes: id, reference string, points_obj, points_obj_simplified + + dthandler = lambda obj: obj.isoformat() if isinstance(obj, datetime.datetime) else None + response = {'success': False} + + include_back = 3 + include_forward = 15 + include_mark = True + max_returned = 15 + + marks_to_be_dumped = None + did_fail_get_marks = False + + # We've got an offset to play with + if 'offset' in request.GET: + # An offset requires a max value to be returned from this offset + if 'max' in request.GET: + offset = request.GET['offset'] + max = request.GET['max'] + # We can also filter by country code if need be + if 'country_code' in request.GET: + marks_to_be_dumped = Mark.objects.exclude(flaggings__gte=1).filter(contributor_locale__isnull=True).order_by('id').filter(country_code=request.GET['country_code'])[offset:max] + else: + marks_to_be_dumped = Mark.objects.exclude(flaggings__gte=1).filter(contributor_locale__isnull=True).order_by('id')[offset:max] + else: + response['success'] = False + response['error'] = "Querying by offset also requires a 'max' POST var" + did_fail_get_marks = True + else: + # We can also filter by country code here as well if need be + if 'country_code' in request.GET: + marks_to_be_dumped = Mark.objects.exclude(flaggings__gte=1).filter(contributor_locale__isnull=True,country_code=request.GET['country_code']) + else: + # No special query parameters, query for all marks + marks_to_be_dumped = Mark.objects.exclude(flaggings__gte=1).filter(contributor_locale__isnull=True) + # Check that we've got marks to dump + if not did_fail_get_marks: + if marks_to_be_dumped: + # Dump out + all_marks = [] + for m in marks_to_be_dumped: + # We need to decode the points obj simplified + decoded_points_obj = common.decode_points_obj(m.points_obj_simplified) + # Append to all marks + all_marks.append({'date_drawn': m.date_drawn.strftime("%a, %d %b %Y %I:%M:%S"), 'reference': m.reference, 'id': m.id, 'points_obj_simplified': decoded_points_obj, 'contributor': m.contributor, 'country_code': m.country_code, 'flaggings': m.flaggings}) + response['success'] = True + response['marks'] = all_marks + else: + # No marks to dump + response['success'] = False + response['error'] = "No marks to be parsed" + # Dump and return + json_response = simplejson.dumps(response, default=dthandler) + return HttpResponse(json_response, 'application/javascript') + + +def marks_by_flagged(request): + dthandler = lambda obj: obj.isoformat() if isinstance(obj, datetime.datetime) else None + response = {'success': False} + + include_back = 3 + include_forward = 15 + include_mark = True + max_returned = 15 + + marks_to_be_dumped = Mark.objects.filter(flaggings__gte=1).order_by('id') # CHECK offset? + + # Check that we've got marks to dump + if marks_to_be_dumped: + # Dump out + all_marks = [] + for m in marks_to_be_dumped: + # We need to decode the points obj simplified + decoded_points_obj = common.decode_points_obj(m.points_obj_simplified) + # Append to all marks + all_marks.append({'date_drawn': m.date_drawn, 'reference': m.reference, 'id': m.id, 'points_obj_simplified': decoded_points_obj, 'contributor': m.contributor, 'country_code': m.country_code, 'is_approved': m.is_approved}) + response['success'] = True + response['marks'] = all_marks + else: + # No marks to dump + response['success'] = False + response['error'] = "No marks to be parsed" + # Dump and return + json_response = simplejson.dumps(response, default=dthandler) + return HttpResponse(json_response, 'application/javascript') + +def update_language(request): + response = {} + if 'language_code' in request.GET: + set_language(request) + else: + pass + return response diff --git a/ffdemo/markup/templatetags/__init__.py b/ffdemo/markup/templatetags/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/ffdemo/markup/templatetags/gml_extras.py b/ffdemo/markup/templatetags/gml_extras.py new file mode 100644 index 0000000..975c9ed --- /dev/null +++ b/ffdemo/markup/templatetags/gml_extras.py @@ -0,0 +1,8 @@ +from django import template + +register = template.Library() + +@register.filter(name='normalize_point') +def normalize_point(value,arg): + ret_val = float(value)/arg + return str(round(ret_val, 2)).replace(',','.') diff --git a/ffdemo/markup/views.py b/ffdemo/markup/views.py new file mode 100644 index 0000000..34994d0 --- /dev/null +++ b/ffdemo/markup/views.py @@ -0,0 +1,119 @@ +import json +import string +from django.http import Http404, HttpResponse, HttpResponseRedirect +from django.utils.encoding import force_unicode +from django.utils import translation +from django.utils.translation import ugettext_lazy as _lazy +from django.utils.translation import ugettext as _ +from django.utils import simplejson +from django.conf import settings +from django.contrib.sites.models import Site +from django.template.loader import get_template +from django.contrib.auth.decorators import login_required +from django.contrib.auth import authenticate, login, logout +from django.views.decorators.cache import cache_page +from django.core.context_processors import csrf +from ffdemo.utils.render import render_response +from ffdemo.markup.models import Mark +from ffdemo.markup.forms import MarkForm +from django.shortcuts import get_object_or_404 +from ffdemo.markup import common + +@cache_page(15) # cache for 15 seconds +def home(request): + mr = settings.MEDIA_ROOT + return render_response(request, 'home.html', {'mr':mr}) + +@cache_page(60 * 30) # cache for 30 minutes +def about(request): + return render_response(request, 'about.html') + +@cache_page(60 * 30) +def about_gml(request): + return render_response(request, 'gml.html') + +@cache_page(60 * 30) +def credits(request): + return render_response(request, 'credits.html') + +@cache_page(60 * 30) +def code(request): + return render_response(request, 'code.html') + +@cache_page(60 * 30) +def mozilla(request): + return render_response(request, 'mozilla.html') + +@cache_page(60 * 30) +def evan(request): + return render_response(request, 'evan-roth.html') + +def gml(request): + mark = Mark.objects.all()[10] + obj_decoded = simplejson.loads(common.decode_points_obj(mark.points_obj_simplified)) + context = { 'mark': mark, 'obj_decoded': obj_decoded } + return render_response(request, 'gml.xml', context, mimetype='application/xml') + +@cache_page(60 * 30) +def manifesto(request): + return render_response(request, 'manifesto.html') + +def mark(request, mark_reference): + mark = get_object_or_404(Mark, reference=mark_reference) + return render_response(request, 'mark.html', {'mark': mark}) + +def makemark(request): + if request.method == "POST": + mark_form = MarkForm(request.POST) + if mark_form.is_valid(): + mark_data = { 'points_obj': mark_form.cleaned_data['points_obj'], 'country_code': mark_form.cleaned_data['country_code'] } + common.save_new_mark_with_data(mark_data) + else: + mark_form = MarkForm() + return render_response(request, 'makemark.html', {'form': mark_form}) + +@cache_page(30) +def community(request): + if 'offset' in request.GET: + offset = int(request.GET['offset']) + per_page = int(request.GET['per_page']) + top_limit = offset + per_page; + all_marks = Mark.objects.exclude(flaggings__gte=1)[offset:top_limit] + else: + all_marks = Mark.objects.exclude(flaggings__gte=1) + return render_response(request, 'community.html', {'all_marks': all_marks}) + +@cache_page(60 * 30) +def newsletter(request): + return render_response(request, 'newsletter.html', {}) + +@cache_page(60 * 30) +def home_sammy(request): + mr = settings.MEDIA_ROOT + return render_response(request,'sammy/home.html',{'mr':mr}) + +@cache_page(15) +def linear_sammy(request): + return render_response(request, 'sammy/linear.html') + +def mark_sammy(request): + mark = get_object_or_404(Mark, reference=mark_reference) + return render_response(request, 'sammy/mark.html', {'mark':mark}) + +def makemark_sammy(request): + mark_form = MarkForm() + return render_response(request, 'sammy/makemark.html', {'form': mark_form}) + + +### MODERATION VIEWS +def account_locked(request): + return render_response(request, 'registration/locked.html') + + + + +def moderate_sammy(request): + if not request.user.is_authenticated(): + return HttpResponseRedirect('/accounts/login/') + else: + return render_response(request, 'sammy/moderate.html') diff --git a/ffdemo/middleware.py b/ffdemo/middleware.py new file mode 100644 index 0000000..82e7cb0 --- /dev/null +++ b/ffdemo/middleware.py @@ -0,0 +1,72 @@ +from django.db import connection +from django.conf import settings +from django.utils.encoding import * +from urlparse import urlparse + +import re +from django import http +import django.core.exceptions +from django.core import urlresolvers +from django.http import HttpResponseRedirect +from django.utils import translation +import localeurl +from localeurl import utils + +from django.http import HttpResponseRedirect, HttpResponsePermanentRedirect, get_host + +class SQLLogMiddleware: + def process_response(self, request, response): + if settings.DEV and connection.queries: + time = sum([float(q['time']) for q in connection.queries]) + return response + +class DetectReferrer: + def process_request(self, request): + if not request.session.get('HTTP_REFERER', False): + if(request.META.get('HTTP_REFERER')): + ref = urlparse(request.META.get('HTTP_REFERER')) + request.session['HTTP_REFERER'] = ref.hostname + + + +# SSL Middleware +# via: http://djangosnippets.org/snippets/85/ + +# __license__ = "Python" +# __copyright__ = "Copyright (C) 2007, Stephen Zabel" +# __author__ = "Stephen Zabel - sjzabel@gmail.com" +# __contributors__ = "Jay Parlar - parlar@gmail.com" + +SSL = 'SSL' + +class SSLRedirect: + + def process_view(self, request, view_func, view_args, view_kwargs): + if SSL in view_kwargs: + secure = view_kwargs[SSL] + del view_kwargs[SSL] + else: + secure = False + + if not secure == self._is_secure(request): + return self._redirect(request, secure) + + def _is_secure(self, request): + if request.is_secure(): + return True + + #Handle the Webfaction case until this gets resolved in the request.is_secure() + if 'HTTP_X_FORWARDED_SSL' in request.META: + return request.META['HTTP_X_FORWARDED_SSL'] == 'on' + + return False + + def _redirect(self, request, secure): + protocol = secure and "https" or "http" + newurl = "%s://%s%s" % (protocol,get_host(request),request.get_full_path()) + if settings.DEBUG and request.method == 'POST': + raise RuntimeError, \ + """Django can't perform a SSL redirect while maintaining POST data. + Please structure your views so that redirects only occur during GETs.""" + + return HttpResponsePermanentRedirect(newurl) diff --git a/ffdemo/settings.py b/ffdemo/settings.py new file mode 100644 index 0000000..3734e4c --- /dev/null +++ b/ffdemo/settings.py @@ -0,0 +1,207 @@ +import settings_local +import os +import re + +# Django settings for ff4 project. + +DEBUG = settings_local.DEBUG +DEV = settings_local.DEV +TEMPLATE_DEBUG = DEBUG + +PROJECT_PATH = os.path.realpath(os.path.dirname(__file__)) +PROJECT_DOMAIN = '' +PROJECT_DIR = os.path.realpath(os.path.dirname(__file__)) + + + +# UNCOMMENT TO ENABLE SECURE SESSIONS +# SESSION_COOKIE_SECURE = True +# SESSION_COOKIE_HTTPONLY = True +# SESSION_COOKIE_DOMAIN = None + +# global for enabling SSL redirection of admin views +# set True for properly configured production +REDIRECT_TO_SSL = False + +ADMINS = ( + # ('Your Name', 'your_email@domain.com'), +) + +MANAGERS = ADMINS + +# Memcached! +# CACHE_BACKEND = 'caching.backends.memcached://localhost:11211?timeout=500' + +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.mysql', # Add 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'oracle'. + 'NAME': settings_local.DB_NAME, # Or path to database file if using sqlite3. + 'USER': settings_local.DB_USER, # Not used with sqlite3. + 'PASSWORD': settings_local.DB_PASSWORD, # Not used with sqlite3. + 'HOST': settings_local.DB_HOST, # Set to empty string for localhost. Not used with sqlite3. + 'PORT': settings_local.DB_PORT, # Set to empty string for default. Not used with sqlite3. + } +} + + + + +# Local time zone for this installation. Choices can be found here: +# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name +# although not all choices may be available on all operating systems. +# On Unix systems, a value of None will cause Django to use the same +# timezone as the operating system. +# If running in a Windows environment this must be set to the same as your +# system time zone. +TIME_ZONE = 'America/New_York' + +# Language code for this installation. All choices can be found here: +# http://www.i18nguy.com/unicode/language-identifiers.html +LANGUAGE_CODE = 'en-US' + +SITE_ID = 1 + +# If you set this to False, Django will make some optimizations so as not +# to load the internationalization machinery. +USE_I18N = True + +# If you set this to False, Django will not format dates, numbers and +# calendars according to the current locale +USE_L10N = True + +# Accepted locales +#INPUT_LANGUAGES = ('ar', 'bg', 'ca', 'cs', 'da', 'de', 'el', 'en-US', 'es', +# 'fr', 'fy-NL', 'gl', 'he', 'hu', 'id', 'it', 'ko', 'nb-NO', +# 'nl', 'pl', 'pt-PT', 'ro', 'ru', 'sk', 'sq', 'uk', 'vi', +# 'zh-CN', 'zh-TW') + +gettext = lambda s: s +LANGUAGES = ( + ('de', gettext('German')), + ('en', gettext('English')), + ('fr', gettext('French')), + ('ru', gettext('Russian')), +) +# default to accept-language header, per localeurl's settings +LOCALEURL_USE_ACCEPT_LANGUAGE = True + +# don't url-localize requests + +LOCALE_INDEPENDENT_PATHS = ( + re.compile('requests/'), + re.compile('/accounts/login/$'), + re.compile('/accounts/logout/$'), + re.compile('/i18n/'), +) + +#RTL_LANGUAGES = ('ar', 'he',) # ('fa', 'fa-IR') +# Fallbacks for locales that are not recognized by Babel. Bug 596981. +BABEL_FALLBACK = {'fy-nl': 'nl'} + + + + +# Absolute path to the directory that holds media. +# Example: "/home/media/media.lawrence.com/" +MEDIA_ROOT = PROJECT_PATH+'/static/' + +# URL that handles the media served from MEDIA_ROOT. Make sure to use a +# trailing slash if there is a path component (optional in other cases). +# Examples: "http://media.lawrence.com", "http://example.com/media/" +MEDIA_URL = '/media/' + +# URL prefix for admin media -- CSS, JavaScript and images. Make sure to use a +# trailing slash. +# Examples: "http://foo.com/media/", "/media/". +ADMIN_MEDIA_PREFIX = '/media/admin/' + +# Make this unique, and don't share it with anybody. +SECRET_KEY = 'priU+iaciut#uV&aphlADo#zlep?i!rlethiu-wOuslapr2eSp' + +# List of callables that know how to import templates from various sources. +TEMPLATE_LOADERS = ( + #'ffdemo.jinja.Loader', + 'django.template.loaders.filesystem.Loader', + 'django.template.loaders.app_directories.Loader', +# 'django.template.loaders.eggs.Loader', +) + +MIDDLEWARE_CLASSES = ( + 'localeurl.middleware.LocaleURLMiddleware', + 'django.middleware.common.CommonMiddleware', + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', + 'axes.middleware.FailedLoginMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', + #'django.middleware.locale.LocaleMiddleware', + 'ffdemo.middleware.SQLLogMiddleware', + 'ffdemo.middleware.SSLRedirect', +) + +# ADMIN +CONTRIBUTOR_TYPE_CHOICES = ( + ('c','contributor'), + ('t','translator'), +) +LOGIN_REDIRECT_URL = "/#/moderate" + +# AXES SEC. CONFIG +AXES_LOGIN_FAILURE_LIMIT = 5 +AXES_LOCK_OUT_AT_FAILURE = True +AXES_COOLOFF_TIME = 2 +AXES_LOCKOUT_URL = "/auth/locked/" + +ROOT_URLCONF = 'ffdemo.urls' + +TEMPLATE_DIRS = ( + PROJECT_PATH+'/templates_orig', + PROJECT_PATH+'/templates_orig/sammy', +) + +# JINJA_TEMPLATE_DIRS = ( +# PROJECT_PATH+'/templates', +# ) + +def JINJA_CONFIG(): + import jinja2 + config = {'extensions': ['jinja2.ext.loopcontrols', + 'jinja2.ext.with_', 'caching.ext.cache'], + 'finalize': lambda x: x if x is not None else ''} + return config + + +TEMPLATE_CONTEXT_PROCESSORS = ( + 'django.core.context_processors.request', + 'django.core.context_processors.i18n', + 'django.contrib.auth.context_processors.auth', +) + +SERIALIZATION_MODULES = { + 'yml': "django.core.serializers.pyyaml" +} + +INSTALLED_APPS = ( + 'localeurl', + 'ffdemo.markup', + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.sites', + 'django.contrib.messages', + 'south', + 'axes', +) + +FIXTURE_DIRS = ( + PROJECT_PATH+'/fixtures/', +) +SOUTH_TESTS_MIGRATE = False + +EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend' +EMAIL_HOST = settings_local.EMAIL_HOST +EMAIL_PORT = settings_local.EMAIL_PORT +EMAIL_HOST_USER = settings_local.EMAIL_HOST_USER +EMAIL_HOST_PASSWORD = settings_local.EMAIL_HOST_PASSWORD +EMAIL_USE_TLS = settings_local.EMAIL_USE_TLS + diff --git a/ffdemo/static/Jimfile b/ffdemo/static/Jimfile new file mode 100644 index 0000000..bfcf580 --- /dev/null +++ b/ffdemo/static/Jimfile @@ -0,0 +1,34 @@ +{ + "vendor_dir": "assets/js/vendor", + "compressed_path": "assets/js/compressed.js", + "bundle_dir": "assets/js", + "bundles": { + "markApp": [ + "jquery.markApp", + "jquery.markApp.linear", + "jquery.markApp.capture", + "jquery.markApp.intro", + "jquery.delayedBind", + "jquery.collapsibleMod", + "jquery.socialShare", + "jquery.ui-selectBox", + "json2", + "canvas_extensions", + "mark.layer", + "mark.layerManager", + "mark.gmlPoint", + "mark.simplifyPath", + "mark.brushes", + "mark.gmlMark", + "mark.scene", + "mark.renderer", + "mark.camera", + "mark.base", + "tween", + "sammy-0.6.3", + "sammy.hash_push_proxy", + "sammy.template" + ] + }, + "compressed_suffix": ".min" +} \ No newline at end of file diff --git a/ffdemo/static/assets/css/style.css b/ffdemo/static/assets/css/style.css new file mode 100644 index 0000000..72a4f9e --- /dev/null +++ b/ffdemo/static/assets/css/style.css @@ -0,0 +1,1212 @@ +html, +body { + margin: 0; + padding: 0; + background-color: #333; + font-family: Helvetica, Arial, sans; + font-size: 67.5%; + color: #222222; + background-color: #2f2f30; + background-image: url("/media/assets/images/footer_bg.gif"); + background-position: 50% 0; + background-repeat: repeat; +} +body { + overflow-x: hidden; +} +section, +header, +footer, +canvas, +nav { + display: block; +} +a { + text-decoration: none; + outline: none; +} + +a img +{ + border: none; +} +/* SelectBox styles */ +.ui-selectBox { + position: relative; + width: 188px; /* 188px + 35px (padding-right) + 2px (for borders) == 225px wide */ + border: solid 1px #BBB; + text-decoration: none; + color: #000; + outline: none; + vertical-align: middle; + background: #F2F2F2; + background: -moz-linear-gradient(top, #F8F8F8 1%, #E1E1E1 100%); + background: -webkit-gradient(linear, left top, left bottom, color-stop(1%, #F8F8F8), color-stop(100%, #E1E1E1)); + filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#F8F8F8', endColorstr='#E1E1E1', GradientType=0); + -moz-box-shadow: 0 1px 0 rgba(255, 255, 255, .75); + -webkit-box-shadow: 0 1px 0 rgba(255, 255, 255, .75); + box-shadow: 0 1px 0 rgba(255, 255, 255, .75); + padding-right: 35px; + display: inline-block; + cursor: default; +} + +.ui-selectBox-disabled, +.ui-selectBox-disabled .ui-selectBox-arrow { + color: #888 !important; + border-color: #BBB !important; + font-style: italic !important; +} + +.ui-selectBox:hover, +.ui-selectBox-focus, +.ui-selectBox:hover .ui-selectBox-arrow, +.ui-selectBox-focus .ui-selectBox-arrow { + border-color: #666; +} + +.ui-selectBox-label { + width: 100%; + padding: .2em .3em; + display: inline-block; + white-space: nowrap; + overflow: hidden; +} + +.ui-selectBox-arrow { + position: absolute; + top: 0; + right: 0; + width: 23px; + height: 100%; + background: url(jquery.ui-selectBox-arrow.gif) 50% center no-repeat; + border-left: solid 1px #BBB; +} + +/* Dropdown styles */ +#ui-selectBox-dropdown { + max-height: 200px; + border: solid 1px #BBB; + background: #FFF; + margin-top: -1px; + overflow: auto; + -moz-box-shadow: 0 2px 6px rgba(0, 0, 0, .2); + -webkit-box-shadow: 0 2px 6px rgba(0, 0, 0, .2); + box-shadow: 0 2px 6px rgba(0, 0, 0, .2); +} + +#ui-selectBox-dropdown UL, +#ui-selectBox-dropdown UL LI { + list-style: none; + padding: 0; + margin: 0; +} + +#ui-selectBox-dropdown UL LI { + white-space: nowrap; + overflow: hidden; + padding: .2em .6em .2em 20px; + display: block; + cursor: default; +} + +#ui-selectBox-dropdown .ui-selectBox-optgroup { + background: #F2F2F2; + color: #888; + padding-left: .6em; +} + +#ui-selectBox-dropdown.ui-selectBox-hasOptgroups .ui-selectBox-option { + padding-left: 20px; + background-position: 14px center; +} + +#ui-selectBox-dropdown .ui-selectBox-option.ui-selectBox-initial { + background-image: url(jquery.ui-selectBox-tick.gif); + background-position: 4px center; + background-repeat: no-repeat; +} + +#ui-selectBox-dropdown .ui-selectBox-current { + background-color: #C8DEF4; +} + +#ui-selectBox-dropdown .ui-selectBox-disabled { + color: #888; + font-style: italic; +} + + +/* The following can be removed if you already have a jQuery UI theme loaded */ +.ui-corner-tl { -moz-border-radius-topleft: 4px; -webkit-border-top-left-radius: 4px; border-top-left-radius: 4px; } +.ui-corner-tr { -moz-border-radius-topright: 4px; -webkit-border-top-right-radius: 4px; border-top-right-radius: 4px; } +.ui-corner-bl { -moz-border-radius-bottomleft: 4px; -webkit-border-bottom-left-radius: 4px; border-bottom-left-radius: 4px; } +.ui-corner-br { -moz-border-radius-bottomright: 4px; -webkit-border-bottom-right-radius: 4px; border-bottom-right-radius: 4px; } +.ui-corner-top { -moz-border-radius-topleft: 4px; -webkit-border-top-left-radius: 4px; border-top-left-radius: 4px; -moz-border-radius-topright: 4px; -webkit-border-top-right-radius: 4px; border-top-right-radius: 4px; } +.ui-corner-bottom { -moz-border-radius-bottomleft: 4px; -webkit-border-bottom-left-radius: 4px; border-bottom-left-radius: 4px; -moz-border-radius-bottomright: 4px; -webkit-border-bottom-right-radius: 4px; border-bottom-right-radius: 4px; } +.ui-corner-right { -moz-border-radius-topright: 4px; -webkit-border-top-right-radius: 4px; border-top-right-radius: 4px; -moz-border-radius-bottomright: 4px; -webkit-border-bottom-right-radius: 4px; border-bottom-right-radius: 4px; } +.ui-corner-left { -moz-border-radius-topleft: 4px; -webkit-border-top-left-radius: 4px; border-top-left-radius: 4px; -moz-border-radius-bottomleft: 4px; -webkit-border-bottom-left-radius: 4px; border-bottom-left-radius: 4px; } +.ui-corner-all { -moz-border-radius: 4px; -webkit-border-radius: 4px; border-radius: 4px; } +.ui-selectBox +{ + width: 200px; + background: #fff; + border: #bbb 1px solid; + -moz-border-radius: 0; + -webkit-border-radius: 0; + border-radius: 0; + -moz-box-shadow: none; + -webkit-box-shadow: none; + padding: 3px 0 3px 7px; + line-height: 10px; + background: url('/media/assets/images/location_selector_dropdown_bg.gif') 100% 50% no-repeat #ffffff; +} +.ui-selectBox:hover, +.ui-selectBox-focus, +.ui-selectBox:hover .ui-selectBox-arrow, +.ui-selectBox-focus .ui-selectBox-arrow +{ + border-color: #bbb; +} +.ui-selectBox .ui-selectBox-label +{ + font-size: 10px; + line-height: 10px; + color: #6e6e6e; + padding: 4px 0 0 5px; +} +.ui-selectBox-arrow +{ + display: none; + border-color: #bbb; +} +#ui-selectBox-dropdown ul li +{ + font-size: 10px; + margin: 0; + padding: 2px 0 2px 9px; + color: #6e6e6e; +} + +#mark-browsing-options .ui-selectBox +{ + width: 137px; + padding: 0; + border: #e4e4e4 1px solid; +} + +#mark-browsing-options .ui-selectBox:hover, +#mark-browsing-options .ui-selectBox-focus, +#mark-browsing-options .ui-selectBox:hover .ui-selectBox-arrow, +#mark-browsing-options .ui-selectBox-focus .ui-selectBox-arrow +{ + border-color: #e4e4e4; +} + +#mark-browsing-options .ui-selectBox-arrow +{ + border-color: #e4e4e4; +} + +#mark-browsing-options .ui-selectBox-label +{ + padding: 8px 0 4px 6px; + display: block; + font-size: 10px; + line-height: 10px; +} + +/* End Select Box Styles */ + +#main-links, +#main-navigation, +#legal, +#callout-boxes { + +} +header { + background-color: #000000; + border-top: #717171 2px solid; +} +#main-links { + width: 970px; + margin: 0 auto; + position: relative; +} +#main-links #mozilla-logo { + position: absolute; + top: 0; + right: 0; + width: 118px; + height: 47px; + background: url('/media/assets/images/mozilla_tab.png') top right no-repeat; + padding: 0; + margin: 0; + text-indent: -9000em; +} +header h1 { + margin: 0; + float: left; + font-size: 3em; +} +header h1 a { + color: #F66C1E; + background-image: url(/media/assets/images/mark_up_logo.jpg); + background-position: 0 0; + background-repeat: no-repeat; + text-transform: uppercase; + text-indent: -99999px; + display: block; + width: 425px; + height: 62px; +} +header h2 +{ + margin: 0; + padding: 0; +} +#main-navigation { + position: absolute; + z-index: 1000; + width: 100%; +} +#main-navigation nav { + position: relative; + width: 970px; + margin: 0 auto; + color: #000000; +} + +#main-navigation nav ul { + list-style: none; + margin: 0; + padding: 0; +} +#main-navigation nav ul li { + margin: 0; + float: left; + background-color: #fff; + opacity: 0.9; + filter:alpha(opacity=90) +} +#main-navigation nav ul li.disabled { + color: #cccccc; + font-weight: bold; + text-transform: uppercase; + font-size: 1.4em; + line-height: 1.4em; + padding: 5px 14px 6px 14px; +} +#main-navigation nav ul li#community +{ + cursor: pointer; +} +#main-navigation nav ul li a { + display: block; + color: #000000; + font-weight: bold; + text-transform: uppercase; + font-size: 1.4em; + line-height: 1.4em; + padding: 5px 14px 6px 14px; +} +#main-navigation nav ul li.download-link, +#main-navigation nav ul li#locale-options { + float: left; +} +#main-navigation nav ul li.download-link a, +#main-navigation nav ul li#locale-options a { + color: #ff5400; +} + +#coming-soon-tip +{ + display: none; + z-index: 1000; + position: absolute; + top: 22px; + left: 344px; + width: 150px; + height: 40px; + background: url('/media/assets/images/coming-soon-tip-bg.png') top left no-repeat; + color: white; + padding: 21px 0 0 9px; + font-size: 1.5em; +} + +body.behind-the-code nav ul #behind-the-code a, +body.manifesto nav ul #manifesto a, +body.make-your-mark ul #make-your-mark a, +body.community ul #community a { + background: #ff5400; + color: #000; +} + +#content { + width: 100%; + background-color: #ffffff; + overflow: hidden; + position: relative; + font-size: 2em; + min-height: 500px; +} +#sammy, +#markapp { + position: absolute; + top: 0; + left: 0; + bottom: 0; + right: 0; + width: auto; + height: auto; +} +#content canvas { + background: transparent; + position: absolute; + top: 0; + width: 100%; +} + +#site-info { + height: 30px; + background: #333; +} +#sammy { + padding: 20px; + color: #333; +} +#sammy a { + color: #F66C1E; +} +#markmaker { + height: 100%; + margin: -20px; + background-image: url(/media/assets/images/large_dash.gif); + background-repeat: repeat-x; + background-position: 0 70%; +} + +#intro-content { + width: 600px; + margin: 0 auto; + padding-left: 370px; +} +#browse-marks, +#intro-main-copy, +#click-anywhere { + position: absolute; +} +#click-anywhere { + font-size: 0.9em; + margin: 10px 0 0 0; +} +#browse-marks { + z-index: 300; +} +#browse-marks a { + float: right; + display: block; + background-image: url("/media/assets/images/magnifying.png"); + background-position: 0 0; + font-size: 0.7em; + text-shadow: 0 0 1px rgba(0,0,0,.25); + height: 63px; + line-height: 1.3em; + padding-right: 13px; + padding-top: 24px; + text-align: center; + width: 76px; + color: #ffffff; +} +#markmaker #intro-main-copy { + width: 600px; +} +#markmaker #intro-main-copy h1 { + margin: 0; +} +#markmaker #intro-main-copy p { + line-height: 26px; + font-size: 0.9em; +} + +#markmaker #drawing { + background: url(/media/assets/images/large_dash.gif ) 0 80% repeat-x; + height: 100%; + margin: -20px; +} +#markmaker-controls { + width: 400px; + position: absolute; + bottom: 0; + left: 50%; + margin-left: -200px; + list-style: none; + z-index: 200; + text-align: center; + padding: 0; +} +#markmaker-controls li { +display: inline-block; +} + +#markmaker-controls li a { + color: #939393; + cursor: pointer; + display: inline-block; + padding: 30px 10px 4px; + margin: 0 10px; + background-repeat: no-repeat; + background-position: 50% 0; + text-transform: lowercase; + font-weight: bold; + font-size: 0.9em; +} +#markmaker-controls li a.disabled { + opacity: 0.5; + cursor: default; + cursor: not-allowed; +} +#markmaker-controls li#markmaker-submit a { + background-image: url(/media/assets/images/submit.png); + background-position: 50% 3px ; +} +#markmaker-controls li#markmaker-reset a { + background-image: url(/media/assets/images/reset.png); +} +#markmaker-controls li#markmaker-cancel a { + background-image: url(/media/assets/images/cancel.png); + background-position: 50% 5px; +} +#markmaker-controls li#markmaker-location a { + background-image: url(/media/assets/images/location.png); +} +#markmaker-information { + position: absolute; + right: 35px; + z-index: 200; + bottom: 25px; + display: block; + width: 30px; + height: 30px; + text-indent: -9999px; + background: url(/media/assets/images/i.png) 50% 50% no-repeat; + cursor: pointer; +} + +#contributor-fields, +#translator-fields { + width: 300px; + position: absolute; + z-index: 200; + bottom: 25px; + left: 25px; + padding: 7px; + background: #f7f7f7; + opacity: 0.9; +} +#translator-fields { + font-size: 12px; +} +#contributor-fields .field-wrapper { + margin: 10px 0; +} +#contributor-fields input, +#contributor-fields textarea { + width: 250px; + resize: none; + padding: 4px 2px; + font-size: 0.8em; + font-family: Helvetica, Arial, sans; + border: 1px solid #999999; +} +#contributor-fields label { + font-size: 0.7em; + display: inline; + width: 40px; + margin: 0px 5px 0px 0; + padding: 4px 0; + vertical-align: top; +} +#view-source { + position: absolute; + left: 12px; + bottom: 12px; + font-size: 0.7em; + font-weight: bold; + z-index: 1000; + padding: 5px; +} +.collapsible { + overflow: hidden; +} +.collapsible h3 { + cursor: pointer; + background-color: #F2F2F2; + background-image: url("/media/assets/images/collapsible.png"); + background-position: 97% 50%; + background-repeat: no-repeat; + color: #6E6E6E; + font-size: 10px; + margin: -5px; + padding: 5px; +} +.collapsibleMod-expanded h3 { + background-image: url("/media/assets/images/collapsed.png"); +} +#stats { + position: absolute; + right: 12px; + bottom: 12px; + z-index: 300; +} +#stats, +#mark-browsing { + padding: 7px; + width: 166px; + background: #f7f7f7; + z-index: 300; + opacity: 0.9; +} +#mark-browsing-options { + margin-bottom: 12px; +} + +#stats ul { + list-style:none; + margin: 0; + padding: 10px 0 0; + font-size: 10px; + color: #6e6e6e; +} +#stats ul li { + margin: 0; + padding: 3px 0; +} +#mark-browsing { + position: absolute; + right: 12px; + top: 20px; +} + +#mark-browsing-controls { + list-style: none; + background-image: url(/media/assets/images/controls.png); + background-repeat: no-repeat; + background-position: 50% 36px; + width: 166px; + height: 112px; + padding: 0; + margin: 10px 0; + position: relative; +} +#mark-browsing-controls li { + position: absolute; + +} +#mark-browsing-controls li a { + display: block; + color: #999999; + opacity: 1; + background-repeat: no-repeat; + font-size: 0px; + font-weight: bold; + text-indent: -9000em; +} +#mark-browsing-controls li a:hover { + opacity: 0.8; +} +#mark-browsing-zoom-in { + left: 48px; + top: 5px; +} +#mark-browsing-zoom-out { + left: 48px; + bottom: 8px; +} +#mark-browsing-next { + right: 21px; + top: 30px; +} +#mark-browsing-prev { + left: 24px; + top: 30px; +} +#mark-browsing-zoom-in a { + background-image: url(/media/assets/images/zoomIn.png); + background-position: 50% 100%; + padding: 0 0 20px 0; + text-align: center; + width: 70px; +} +#mark-browsing-zoom-out a { + background-image: url(/media/assets/images/zoomOut.png); + background-position: 50% 0%; + padding: 40px 0 0 0; + text-align: center; + width: 70px; +} +#mark-browsing-next a { + background-image: url(/media/assets/images/nextMark.png); + background-position: 0% 50%; + padding: 10px 0 10px 36px; + text-align: right; + height: 11px; +} +#mark-browsing-prev a { + background-image: url("/media/assets/images/prevMark.png"); + background-position: 100% 50%; + height: 11px; + padding: 10px 34px 10px 0; + text-align: left; +} +.select-wrapper { + margin-bottom: 8px; +} +#country-select-label, +#contributor-select-label { + display: inline-block; + background-image: url(/media/assets/images/location_small.png); + background-position: 0 0; + background-repeat: no-repeat; + text-indent:-9999px; + width: 14px; + height: 14px; +} +#contributor-select-label { + background-image: url(/media/assets/images/contributor_label.png); +} +#country-select { + margin-bottom: 8px; +} +#country-select, +#contributor-select { + width: 140px; +} +#mark-browsing-shortcuts { + list-style: none; + padding: 0; + margin: 10px 0 0; +} +#mark-browsing-shortcuts li { + text-align: center; +} +#mark-browsing-shortcuts li a { + display: block; + font-size: 10px; + background-color: #f2f2f2; + margin: 3px; + padding: 5px; + color: #6e6e6e; + font-weight: bold; +} +#mark-browsing-shortcuts li a:hover { + background-color: #ededed; + color: #555; +} +#mark-information { + position: absolute; + bottom: 12px; + left: 50%; + width: 206px; + padding: 10px 14px 10px 43px; + margin: 0 0 0 -80px; + background: #f7f7f7; + z-index: 300; + color: #f26522; + opacity: 0.9; +} +#mark-playback { + position: absolute; + top: 9px; + left: 10px; + width: 25px; + height: 25px; + text-indent: -9999px; + display: block; + background-image: url(/media/assets/images/playback.png); + background-repeat: no-repeat; + background-position: 0 0; +} +#mark-flag { + position: absolute; + top: 10px; + right: 12px; + width: 16px; + height: 10px; + text-indent: -9999px; + display: block; + background-image: url(/media/assets/images/flag.png); + background-repeat: no-repeat; + background-position: 0 0; +} +#mark-flag.disabled { + background-position: 0 -10px; + cursor: default; +} +#facebook-share, +#twitter-share { + width: 24px; + height: 23px; + display: block; + float: left; + background-position: 0 0; + background-repeat: no-repeat; + text-indent: -9999px; + margin-right: 2px; +} +#facebook-share { + background-image: url(/media/assets/images/facebook.png); +} +#twitter-share { + background-image: url(/media/assets/images/twitter.png); +} + +#delete-mark +{ + float: left; + margin: 0 20px 0 0; +} +#approve-mark-copy +{ + float: left; + padding: 4px 6px 0 0; +} +#approve-mark-checkbox +{ + float: left; +} + +#mark-information h3, +#mark-information h4, +#url-share label { + font-size: 11px; + margin: 0; + font-weight: normal; +} +#mark-information h4 { + margin-bottom: 5px; +} +#mark-contributor-name { + font-weight: bold; +} +#url-share label { +} +#url-share input { + width: 110px; + height: 20px; + padding: 0 3px; + border: 1px solid #f9a57d; + font-size: 10px; +} +footer { + padding-bottom: 20px; +} +footer #callout-boxes { + background-color: #000000; +} +footer #callout-boxes-wrapper { + width: 970px; + margin: 0 auto; + padding: 18px 0; +} +.callout-box { + width: 227px; + float: left; + color: #a8a8a8; + padding: 0 10px 0 0; + border-right: 1px solid #424242; + margin: 0 9px 10px 0; + font-size: 1.4em; +} +footer #callout-boxes-wrapper .last { + border-right: none; + padding: 0; + margin: 0; +} +.callout-box img { + float: left; + margin: 0 10px 10px 0; +} +.callout-box h3 { + margin: 0; +} +.callout-box h3 { + margin: 0; +} +.callout-box h3 a { + color: #A8A8A8; +} +.callout-box p { + margin: 4px 0; + line-height: 1.6em; +} + + +footer #language +{ + width: 968px; + margin: 20px auto 0; + position: relative; +} + +footer #language #current-locale +{ + float: right; + display: block; + color: #999; + font-size: 1.4em; + text-transform: uppercase; + padding: 5px 7px 7px 20px; + background: #2F2F30 url('/media/assets/images/locale_selector_arrow.png') 0 0 no-repeat; +} + +footer #language #current-locale.selected +{ + background-position: 0 -15px; +} + +footer #language #language-selector +{ + display: none; + position: absolute; + top: 15px; + right: 0; + width: 120px; + background: #4c4c4c; + z-index: 1000; + color: white; + text-shadow: 1px 1px 1px #e14f00; + padding: 0; +} + +footer #language #language-selector li +{ + list-style: none; + text-shadow: none; + font-size: 1.4em; + text-transform: uppercase; +} + +footer #language #language-selector li.selected +{ + padding: 5px 0 5px 10px; + border-left: #2b2b2b 10px solid; +} + +footer #language #language-selector li a +{ + display: block; + color: white; + padding: 5px 0 5px 10px; + border-left: #3d3d3d 10px solid; +} + +footer #language #language-selector li a:hover +{ + border-left: #2b2b2b 10px solid; +} + + + + + + + + + +footer #legal { + background-color: #2f2f30; + color: #727272; + width: 938px; + padding: 15px; + margin: 20px auto 0; +} +footer #legal p { + font-size: 1.4em; + margin: 0; + padding: 0; + line-height: 1.7em; +} +footer #legal p.legal-blurb { + width: 460px; + float: left; + margin-right: 10px; +} +footer #legal p.last { + margin-right: 0; +} +footer #legal a { + color: #727272; + text-decoration: underline; +} +footer #legal a:hover { + background-color: #272727; + margin-bottom: 1px; +} +#legal-links { + clear: both; +} +#legal-links ul { + list-style: none; + margin: 0; + padding: 20px 0 4px; +} +#legal-links ul li { + float: left; + border-right: 1px solid #616161; + margin-right: 10px; + padding-right: 10px; + font-size: 1.4em; +} +#legal-links ul li.last { + border-right: none; + margin-right: 0; + padding-right: 0; +} +.infobox-inner { + background-color: #e4e4e4; +} +.infobox-arrow { + background-image: url("/media/assets/images/tri.png"); + background-position: 100% 100%; + background-repeat: no-repeat; + height: 14px; + width: 30px; +} +.infobox-inner p { + margin: 0; + padding: 5px; + line-height: 1.4em; +} +#contributor-quote-box { + width: 350px; + font-size: 0.7em; + z-index: 201; + position: absolute; + opacity: 0.8; +} +#contributor-quote-box .infobox-inner { + padding: 10px; +} +#contributor-quote-box p { + margin: 0 0 10px; + padding: 0; +} +#contributor-quote-box #contributor-name { + text-align: right; + font-size: 10px; + margin-bottom: 0; +} +#contributor-quote-box #contributor-quote { + font-style: italic; + font-size: 11px; +} +#location-dialog { + width: 350px; + font-size: 0.7em; + z-index: 201; + position: absolute; +} +#location-dialog .infobox-inner { + padding: 14px 10px 6px; +} +#location-dialog select { + +} +#information-dialog { + font-size: 0.7em; + z-index: 201; + position: absolute; +} +#information-dialog p { + margin: 0; + padding: 0; + max-width: 300px; +} +#information-dialog .infobox-inner { + padding: 6px 10px; +} +#information-dialog .infobox-arrow { + background-image: url("/media/assets/images/tri_rtl.png"); + height: 14px; + width: 30px; + float: right; + margin-right: 14px; +} +#markmaker-legal-line { + font-size: 0.75em; + font-style: italic; + color: #bbbbbb; + width: 400px; + margin: 0 0 0 -200px; + position: absolute; + left: 50%; + text-align: center; + bottom: 102px; +} +.overlay-wrapper { + background: url(/media/assets/images/overlay.png) 0 0 repeat; +} +.overlay-light { + background: url(/media/assets/images/light_overlay.png) 0 0 repeat; + opacity: 0.9; +} +#markapp-loader { + padding: 100px 0; + z-index: 300; + position: relative; +} +#markapp-loader div { + background: url("/media/assets/images/loading.gif") no-repeat 50% 31px; + color: #FFFFFF; + font-size: 0.9em; + font-weight: bold; + line-height: 1.8em; + margin: 0 auto; + padding-bottom: 20px; + text-align: center; + width: 420px; +} +#markapp-loader.overlay-light div { + color: #ff5400; + background: url("/media/assets/images/loader_light.gif") no-repeat 50% 31px; +} +#fallback, +#markapp-error { + padding: 160px 0 0; + z-index: 300; + position: relative; +} +#fallback-content, +#markapp-error-content { + margin: 0 auto; + width: 420px; + font-size: 0.9em; + background: #ff5400; + border: 2px solid #ffffff; + color: #ffffff; + line-height: 1.8em; +} +#fallback-content p, +#markapp-error-content p { + background-image: url(/media/assets/images/arrow_orange.png); + background-position: 10px 100%; + background-repeat: no-repeat; + margin: 0 0 -18px; + padding: 30px 50px 50px; + font-size: 13px; +} +#fallback-content p a, +#markapp-error-content p a { + color: #7F3100; +} +#fallback-content p a:hover, +#markapp-error-content p a:hover { + color: #FFA680; +} +/* Static content styles */ +#static-content { + width: 970px; + margin: 60px auto 120px; +} +#static-content h1 { + font-size: 2.3em; + margin-top: 0; + margin-bottom: 0.3em; +} +#static-content h2 { + font-size: 1.3em; + margin: 2em 0 0.3em; +} +#static-content p { + line-height: 1.6em ; + font-size: 1em; + margin: 0.4em 0 1em 0; +} +#static-content blockquote { + margin: 0.4em 0.3em 0.4em 0.3em; + line-height: 1.6em; + font-style: italic; +} +#static-content cite { + display: block; + margin: 0.7em 0.3em 1.2em 0.6em; +} +#static-content a { + color: #FF5400; +} +#static-content .col-secondary, +#static-content .col-main { + float: left; + padding-right: 50px; +} +#static-content .col-last { + padding-right: 0; +} +#static-content .col-secondary { + width: 220px; +} +#static-content .col-main { + width: 700px; +} +#about-nav ul { + list-style: none; + padding: 88px 0 10px 0; + margin: 0; +} +#about-nav ul li { + text-align: right; + padding-bottom: 1em; +} +#about-nav ul li a { + color: #000000; + font-weight: bold; + font-size: 1.3em; +} +#newsletter-form +{ + margin: 30px 0 30px 30px; +} + +#newsletter-form label, +#newsletter-form input, +#newsletter-form select +{ + float: left; +} +#newsletter-form label +{ + margin: 0 10px 0 0; +} +#newsletter-form input[type="radio"] +{ + margin: 5px 15px 0 0; +} +#newsletter-form input[type="checkbox"] +{ + margin: 7px 10px 0 0; +} +#newsletter-form cite +{ + margin-left: 0; +} + + +#main-links:after, +.ui-selectBox:after, +#main-navigation:after, +#main-navigation nav ul:after, +#legal:after, +#legal-links ul:after, +#callout-boxes:after, +#static-content:after, +header:after, +.group:after, +footer #language:after, +#newsletter-form p:after { + clear: both; + content: "."; + display: block; + height: 0; + visibility: hidden; +} \ No newline at end of file diff --git a/ffdemo/static/assets/images/arrow.png b/ffdemo/static/assets/images/arrow.png new file mode 100644 index 0000000000000000000000000000000000000000..004e3129cd828c79cf380823bfb82ae01fae98e1 GIT binary patch literal 192 zcmeAS@N?(olHy`uVBq!ia0vp^>_E)M!3-oVE|jDSr z1<%~X^wgl##FWaylc_d9MOpzqA+G;{AcW!nmHq$s&;S4G{{MQ1|En7Qe|i7^{`vnU z2LIy){%0!u-_rkoegTimnlhj+9Zwg>kcwML2}wx|%xP|UW?}-(bK56R6g~3j!PBHC m2?pUXO@geCxzHA+MP literal 0 HcmV?d00001 diff --git a/ffdemo/static/assets/images/arrow_orange.png b/ffdemo/static/assets/images/arrow_orange.png new file mode 100644 index 0000000000000000000000000000000000000000..6e8fcdebe0c0420fe7c58d62436f813918ced3ab GIT binary patch literal 372 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 G2~7Yvo0k#* literal 0 HcmV?d00001 diff --git a/ffdemo/static/assets/images/collapsed.png b/ffdemo/static/assets/images/collapsed.png new file mode 100644 index 0000000000000000000000000000000000000000..2d3ce9bc763619057749808805b6b959dee89a48 GIT binary patch literal 145 zcmeAS@N?(olHy`uVBq!ia0vp^>_E)K!3-o9JDPz6BuiW)N`mv#O3D+9QW+dm@{>{( zJaZG%Q-e|yQz{EjrrH1%2?h9sxc*qt_@~bC>!m|K=M{drd+Gb;iCLl39sp%UJzX3_ pDsCkuB(X52v5Bz>B(QQAFfc4*5c&VmXCF`jgQu&X%Q~loCIE6?Eja)H literal 0 HcmV?d00001 diff --git a/ffdemo/static/assets/images/collapsible.png b/ffdemo/static/assets/images/collapsible.png new file mode 100644 index 0000000000000000000000000000000000000000..c0c269866e43e7c20f96b61efa8adb660719731c GIT binary patch literal 989 zcmaJ=&ui0A9M2d`8QUMA40&V+FKhCW{#wK0=#tD8mQlKZJ?=GmTSJ??n7rAv2T?B~ zcu~Q>!NU+l@Zv%6;sg&m#DgGsD0&fu;!tFuFJ0U1U=6&y_kDampYP9ir@V1_cKZA@ z$8oczHNC?2Gi*(ron-&Tw^V1_IciktCfTMf6k@LEkQxRh5AEOzMo#I`-f?}E|Ni_14_rs(uVhVV1{$urYn>2pb~Y@# z)36H;zkCTS#R?PfFhwBtntr6jD&N;t*g3ftdC-T@hRPoYRW-{%BOwM^0j6x2gCHjf zQaY2*=NEtsB`88!l;o6@SJJWqAsBo-^A*J!F3(Q$Yiowv5?K=bX}4RxMHkiHMl5e(uSes zR*fN7C-$y~eLCbiC*0gvZZZm9z&z_XbgyA&H6$MBr>wZ+V@Z$I8*rWRv1G<_MHY;h zH1<@ZhgWQRlH+J>nK2rC?6YYP*;r5B{t&SzaiP3!89$%iJ3M%KMB(oT=*OpnUyp7c zT`wN)Klx*Eyo2KHKpg-8 literal 0 HcmV?d00001 diff --git a/ffdemo/static/assets/images/coming-soon-tip-bg.png b/ffdemo/static/assets/images/coming-soon-tip-bg.png new file mode 100644 index 0000000000000000000000000000000000000000..c668d7b0cd4a2d4fd0800b7f02efc2833eac373a GIT binary patch literal 1173 zcmbVMPiWg#7#CSv8h2rbv~2yeRaIFaWtN^~*|M~FnXPB7hA0b;VS&*uiu4?dSbCcD zQp+hFTW1EP?P+C%(rr1lP}Yu}Tj)|)AuvXE7#)RD=wTFEy2E-HJMAg9vmQbZ6G-oU z?|r}D_g`nGCf^%M?MpEXGgLVWt8|^F@954Q^jq1r=L%i+lJX3h#&y!NLc|muJcrnd zXU(H3vYhtEYiNvNUaGsb88Tx|Xg2nEE79R&FQ90K85@rS%U(c)okR1kU*P_{af@SJ zr@(!XHGvuEsNo)63eoh^WX)b$uvLc}e}^55HA>(iVzIH;^dl`Ua2vcD-6zKa$8M;Q zg#y=gYQ~&mbsQo#%Y(EHa)8Z=yqJ*{MR|*rfCvO238Iu16)hua0Iqt-&Fu2rGY zvqhx>*B~U&1fkVx@vRJx!+AkeRW;#|q%_q?M{S>2aoUd#_Zc9H?9dH}i+wg>wC3<4 zDR9)&ZU|mrnw!La)JqginGjonAo4))ydHQ9s-V z)7-t-P@+h^(cTYTS`@1ZvAyUapHyIhqc^7e!SnfxMa#L0*={ zjB4~bw!)S(StTz)MTRovD1(x2=(=v?4OP#TU^d^!R{V%qzK!~JU23<7&Akw-=^?TR z4r>@U`yDXVzywDP9I*OymNhNg^%FTMPd8eKLiYr6j1YV5Mt(JS3;VKElto1^%5pdE zl94YOP|1}f2%)Z)xgOT}KRFYqGeXiF&oxW`h<0GI-E4g-Yz`0d>BNL|H2A=TbgX`= zz+x@F_vGFWgSV%4fAK49ez$FP`{~&?cm8tW)PYsCQ`62|JMRqO)auBNwLP6nuY6Ly z`Si!ncYoh}eD&}3LnqGO-!+umpMP-Y;_v%@i+{fU`ufjT3>m`3J-5Qv;X?` z?t#m*w>zWDz)FowEQ9UCBNMBg!Qt-V__dRr;K)A*4?Q~j&A`jdg|A<&C|8eMPJR@X L@+7=onmzq5Na%hB literal 0 HcmV?d00001 diff --git a/ffdemo/static/assets/images/contributor_icon.png b/ffdemo/static/assets/images/contributor_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..dc2e46f429e35ae7c1476d2f4e8975899291d405 GIT binary patch literal 1023 zcmaJ=PiWIn91hg^XKX5n$aZ>U4g}ZaC2iL=tlPS#+d9oCUBTX5n!K*rmb{p}*{mR} z6UE6+wTKEjJP1QX@T}lv2R*s7f`WM1MZAi5@TF@z57r0Cd%wr`eZRkNGcz~c+uh&I zFidZHM$XdxEd9w?2}}lX+YuWz|7ULdOLJ(x$qIvPjj}uDwU23=?3D zT%P2W%aVpoPW3UIXWEp_Fr(w1t!m4NfC5@HtT_AQ^;;GgdYruyQJ`WcQOTI8IB32y zm(wcCT1;oh$H1s3Q2`SX6?o=~|gUNXh6tvoIMCCuVg?HI|xKL7}Ve} z1j7O+gu>BibO?x0fIJj=K@18}DI`h|g63kWH%Bi@S$V4Ii&}BEM2Ic%e7Ria${`Lr zi@Xqv#e5A>3{phUU9*Vl1ub`=r640$a}1jp*aE(yTEMF$&eBK^QZVhdtmQV#L<`1y zs?7@=^iyg973Kd>(`=(%l0}E{{!`e^t=WjrA{VbZ8f{#0z;|U!Ne8I}J2{M3T2;)H zFu`sK+aNg~0g9>_mTx!kiXx>gm#CJ8(sG=o3Y=l+(nt!5LMkGJ6gr^+T@Mk?W6wX;aTKa*XRppK`DX8YxRg$5_Hs9rc=Ay>l$E zBbTSa@P1?eYIN(0PLF?Vk1aLYgO4ma?G7F5i?^;mq))<4%ZZ%#vGBY8JY1#gUiI#J z*PS!#54IoO7KJ@#t6y5$-+6lTb0$;GGP^4?g=Z5lHl{xefw4>P_PR=iS6)rOuTtg+dXx9f!WzQS=+A-{_>w> MIyEQ1oLt!W0~QoTS^xk5 literal 0 HcmV?d00001 diff --git a/ffdemo/static/assets/images/contributor_label.png b/ffdemo/static/assets/images/contributor_label.png new file mode 100644 index 0000000000000000000000000000000000000000..845888675c51f846830cc020faf22caf0364dad4 GIT binary patch literal 1055 zcmaJ=OH30%7+xMC0u7?Z2qrQtiHXs6AAKy{utMop+vt*}G_i?^VY@rfrQHX+gDrTl z%0c8JQLiR=HZk5kXiPZhO%p*q33}9+_>2dmv!yK_tb5q`|H=1#|8uS<$4`4Y`Z@@L z@Fe153ZGrB-QI@(caGf{!$&WQWzd8 zg}f40$&r&_xWr=t9U>W&^n&5=C4sEy^4NB-DH2p5C@+xPNoAxYh?+J8K_Bf^=nxG; zjF0gLIF363Sel_Jnxz=l%W%A( z6cY}IT@9A?VuaV3H;`QN8qQ!%L4=NCYZlT>1GtKE)|^8Ei8I|ULAUC%hEr`5ZWvXP zEsF8cZb>zuB>f+%>vgn)QgA2Ve+oP4c?(i0=$Lc1f(JJ>=!UZRs10Rg+G*1))Vi3= znaFf$Fd+&gR`6+>$`8e8mWc&}0g($cOpG3iM8p_J zixJu%V_Cf5 zq~5hVFIqvUb*nRT;6nMCH2R|@zSVTl)4k%!KH1#tj^Ev1K7M<0-{EqmXPH~gkF8I3 vf6IWIo-0Y_QlV#K$@_6s=sa`2i)d;i)-DhD*Zc0p-6xxfjf?lkCa?Yhx@1gx literal 0 HcmV?d00001 diff --git a/ffdemo/static/assets/images/controls.png b/ffdemo/static/assets/images/controls.png new file mode 100644 index 0000000000000000000000000000000000000000..f64289c611cbdd2ac5ee97d03a3c99c5500d5034 GIT binary patch literal 1653 zcmaJ?eM}Q)9In$W8_^Lqx6CEe+ZpHP*7jP;M=J%TE!c!A)-JNdkJa{o2HLB=Q(6qp zIgO$+6GUe=H*tP21(kq+UkIWgf}p@ap*mMB43UoltG#Rc%XUS;{W10?_rCA%&GS6J z&*ZWbm&eZv`XGqI;mlGlldIYDBlZh-L*mxQ_4V-BTAqE->fREE}qbC&9f4LG7S zYEy7EuGQN%-o~Rioav}RlSCz{5+pjp$kPTeJgd>nvN@cn7^_*UOT#H-1D;|qNztJz zRVZT6OVQO~DokaT;i-mYnHGFi=5mcLGfgMfqcPD)lvTnC7;#FASdHl>Qeu^&W4aRd z9(d-X$QXo5lcFz!N>U{vGQxr*VLXhh!@@BnT)-2AiXtK+K1YO@0OMmqzCg$oL`Xt~ z5)4De7s`6G=#wRCdEB@!){>&B6lIq1`574*yo^vDVM*Z&#A0zkLn!332rg+eQCch4 zM9!a3kmIDzVlYz%!h{4AwHt^{loVwneVKyMJSl4;$IHYPjBnMN`2rpmNNED7QvE;F zXq-folp22(?|%xD8k-sCt8tRpWYMvWOP(KaWtPY+xRxR;8iGilsA6I&K@nssVMb)D z!Vr~KXD|iqF}zA8QJP3fYtrFLxfEp;cm{)Bq7aF~WJ|>YQMg<#5Gb%%MQoT1ixi4N z6$)8+oMM72Cv=;PxQUwJ>R)k%Q*r~NU^KIy<+#PL8P~^I2qQ9Wy>t zQ^$f$$>p=b@B@wgSEDDM*y#yeC&!jGCI=rkvD0p0$6EOJ_YMwcT0vrhMnO9|{I#cP zN4xLVIi~k9xQF5TaUglL(+<_Y!1-*Zr4k-B!;4Y6zlUZ4)yF(9+@9e<@Oh!Nk{;-V zk$xEN0}`WM{hq%@;O<4}cfw!~IPb&hZP0!lm>2%yY{>ZnDu4E0It&G0dt0l(?eg8d z1ZBDYk{s{(Y_K=N=pg*j$~e2AyAw!0(`{!SHZ#v0^ppGEj+=D19e!_yx*}gmj<2@J zd;JtR+L*=*{?h&4hEk@_>8m&dkMF^QIsm_~aEE8;Ib_B7Zxn#N0a~y6D-P1no&dW) zcY1~ge3eI`@(A7M^j|y#1z8^VD3s^T z!>~&Q&1F!P?{6r9vIC603AQfrRvx8?2jJc%DB1~+n!L4zURJiF##?LW#LNsQjN8X%567I7tEFuZOJ$8C}nu^%hj{l-{Llvt2KuBlBl`!Y=hrs z*WNDX_EM5#266O>$QawU!|M+2E?c*CasBEa-gbn(wrkx&SCRZ;P)qq0%iNv$@(ZGR za?K|phnCDVI~Rqd=ZloT4Y*=r3sJQF{g?-e_!DIt*Dp9Zbfh@t*OLXZg$aw_Or9LEWkNZa3#)>1V z`R47W8O8OB9#vmXev20Qx*XTqPQ1R-rFeR^qG#dEq%S`{ntE!>n~@)0-dF9+YTRGb zk^t(pLad=gWnuQ#4&@2YWwthcT9dwlYwryD(amo@{ycBS%HVlx_x)sCu;)yu>wIoB hw@!MtfAzL$oTux1gF|fjqk$iRQn6fqF7~VK{{Z`^6r}(F literal 0 HcmV?d00001 diff --git a/ffdemo/static/assets/images/evan_roth.jpg b/ffdemo/static/assets/images/evan_roth.jpg new file mode 100644 index 0000000000000000000000000000000000000000..54d736d09f91f085de53db3a5115b135c868d14c GIT binary patch literal 2257 zcmY*Z2{@E%8-Bm}zHdxpXl5+gvKuk>HAxJ`ul_70YnG8A6fKH664@p5lZp_Iu_uGF zlx0MS5}_oO9KxSEsN+nf<{zp4>)hXUz3=lr&wD@H`&=Jyls5s0JFRW40fYg-kN*MQ zJRog9aUl2ibYp}#bE{bz~XT0@Ob<>9N{~JS3&#*2u2Vr7E8e42xMV`Fj;if zB1-yhC#`M7AKNd^KLp-gK*9kNU;;xVfFVIJ3F37Ee&P@cLHzW80?W@!00;Ow4EzrR zj6fI^V2S)f5eVTI@`JT{kw`*Nvb=%_R#!k#l%}V@fgb`OFe-q+Km76=NrDjtT{4k& zR8K_SF}+Z64@$T66+Nb}Oa?!F{kUx5;6oDO`^8}Rv3_O&0g{3)k>*H08Y90@*q&Z9 zxcHhk1_*pEh6I!Nh13liWM$>bq-F+)3iemGJd`NY8B=o|K98bW@sBH(%U6~v@gapvttxe2YE`+IM_dlt zke^R`WksoUGDRwkU29B2Pf3TWyh~0na=w1R=h4_7>w;)~db3+5M`-h3grxgZj-O<& zyr0YdOET6+EjLnj;yFK29KV|oit#ty$&{=xq9Fk<%?{W4GJFH9{43GP3-ryco0Pm> zx16;0H^}v_7_ZfNDuDMf;7)i1`k!>xFxl%n#~M9l<}E*caIX4@j=?FVbu~^0l?+$sM<5$#L(>v@>0OQdr+V#?k4ZdlwhZMZF5(9t-1vD&6pc zTC))Dl+mM}cV8gJm#8+^2Y<4|^)JusQSM*x3rKan)%vV;xckC#f+r8m-eyGw3UF<| zzWrVVX0-@Py%{CuCY4)QWNBnqODU;$wl_3j-kxNJoJ&cZ`OhBdkGT6@#N1xPD|X4n zGK|ShP9;U&-|6rw9dggL|5uTNUD{Vj3VPOHFLw#EiTc>;xW)C>4TgOM@_WxsI&7@+ zxM5oo-}^SB&31MzG6rjou`M~f=4bT~{au70z9np+hI?gWK-u77Fp`j)|zMS}eirC#;H8_L6OgEQQ$fRava z39-6){Ctta+3+Og#XH;LsRNf;vSzWPYyE*C zbA{H%Z%NDIN(<4ZuCqS%5#hh(b7e4j`U9#Z`HSYAAA)4vuLh~Nz6zBy{N3m?-LjPt zZY9_T{WmQy`kIZg_65Oj#Ksfo;mrNVoF5K{xUF{juZI&@g~|wPWSw$m~tva9gr8}5y`BNhq3wAz zOXlge;W3?K{dJn*E|S|@dUZm?rS?<@YyWL_#Pw|E$|LT~Cv(k?@`lW#YooK;f^&Uw z`kcPWI?)KH{Y`DMwpKz6z3HXOt2XM{O(w}3gKOOji;M0|(a5JGDh8=~#Ya@bq&mw| zl*%U}3{oj8kL1<{Z`DvvMwc_W%D?#IO$9nah64uUE7eXgby4x0whME#jbs{;wA!`q{;! z2a+6cXTI!EezIJ*sc3{LBN`uMp%F=Qz@!TKVdluN+88qR8AS+k?+A4CqVH5MV+{#y)tbU|Lc@oA2(F)EDPF~ChIakc z^)I?xoIsE7(l!aToR>=W3o#x2O14%kEf;I<`6EeyutirZIEOkCk(H%bo)~(Nta_l# z>6517lFyc)NY@~9s*7_nn;WNIoKX5gZVCTeX1{NgdBfbN)6sNE;z~(Yw7+cgP>0L;<~ifd zzlMnzLwurOcf<3(C_@HajB%b0NGLdOtnqYDO}j;ET9Utfs4|DJlm2u)ViFrZEY@r{ zDPDE--(CBQ)P|7Zs=d-$?%rxvO+&*RHn~t^LT~qIc1@X+G(BW`lu^_*@KlS-d-gvu C=xlKS literal 0 HcmV?d00001 diff --git a/ffdemo/static/assets/images/evan_roth_bio.jpg b/ffdemo/static/assets/images/evan_roth_bio.jpg new file mode 100644 index 0000000000000000000000000000000000000000..9a5f221042e099c215a6473b20536ac68bd6a49d GIT binary patch literal 23674 zcmbTdbx>W;vM9W9cXxM(4Q$-q-QAsydvJI6Kya7fPH=aJ;1E1GgpfRb=iYPP_f_3` zuj=(5Yt2mite%;!o?3s`|Lz0urF`tH0RTBUMgTnEf5YEP0EVQeg@Z2u0s#FntN;M~ z-G_3q_3&`zXJd1AXEn2QF}Gs1aB*VuF>__(U}a|m2#WZ)nprqld61i1+1NP?QQma- zQ#btB0M7Gx$mK)hm{Vw~(eAPx>bE>2!C zDRE9o4t7Z%E@|MuW&BUPAZb33)IUTL?3^4NQtV!i6Zya9m*4Jx(Eoq;`@eWC|6k^e?ZX(he}d!x6D1qcT~LH+|DPbe5DC@2^hXecOX z7+9Ep02Uq=?gL=q;SmrK5fR{#{{^W30Q!Fb0ty-$78Vv69v&GJ6&V#1`yUK8#=r0w z|Mf!u@7I5L{(p_X0{{$oz%K}9COU9&B;dmem=7fSM*|WD0u~Ay9ugh~=7SIe5()rK!GQ?_E2d@!ro@5^;lxfU z=7Mh;To6~M0^qgA!Sl^s(t{iS!wyIYXb30>Xm}_%n1A#@Ktf@B zNP&hCQ^TS(19QTLq!fRM;KKeXuD);XMkPTFybVol#`(JmK!$?&a03bhAObjp!UYlg zJFLy+{`8@uet!t3hkkiqz64T-c1bg2uwS8#5Ri<1CQoW@u=i5!fo&Ed+D5p%EV9G2 zN(HzlLh;87={|rl(UXvEiKx|ZbCrFUU?VO4Ao;!bmH9{(GgKn*krmT|(IhX+iKBS- z)(4q>C4lV7YrczNv&aY>s_{|lC2(4)_?)meg5Z*HD$CYoyLH5sa@HtMl(x)<}(@UVS&kyiI;y|+}xNjaW`h)xa( zev60I5#~MPjp6-}kIHgheTmUTjrv_}y9>G;FI%lQ4$%!9-NDC{Zm@%`mFYEqWK9N6TbECx$p?mn7{b8N^~Yd1 z3K2qUK2}(BfwxKnxaIU8(iu)N<2h8LQc1aKX4DEX34p*0H6FcK8jdh%93;ys_;es+ z1hy+-CI6aU)T4^gRC_$M+O!2Eb|XY^4a@ic-A;{Z_->b71Ko-YTu` z->d$fV5AeP;gnHfDL_XCJ(L)YD#PV%?dxk_A7@Ux$#Kx8512mh^GR63uIx?eqaHf7 zUo#4I$odLxGxI(7l(h$UkYSrh3}t)ZjrKxkjb1>=4)Ge#*V7~HHtP{%iZP#H)>oiG zUQbrMzcRnXt@4uI-DkSa1`{JON7PhnKErP$dw%m+Lhx>#@~IWV`-+kqq%sG74KoVV z$`WkJ<+_L%&A?N8&AGNrvGIlBy#R)Yv~RsCQTpPT3L&69Kd(hx$*dmFv9FBaJL;Yn zOooUQRMz)6W#$-G`d;_)al@p|E5NY*1uzOG6|X-I_4VTaq@Wo_{xq47 zHNv+Pk@BM3K`Ye4OdioANesq8&IZS@e*@4}I7y7R6p@em#Ren|gX`=Oaffsne_!kr zB3$*x-S7#Yk#uL{WY$vL3k}mlgy8_oP&tly(gQbGsi&GOvcuaRowbT@K6Is zvLLsHF>qTXyxmVyG%-EmVF5`Ard|Q3#&t}FpS!mnN!k_eqRcM^P6t1g^Y{OehMMl@ z6uxxtF7fPiN!|L2KIJciPigCB8ZWftkahWby^<%)v_WzzbJs4O;By~zxAq7>Pn$0nxMjeuiLW-TX*^%tO&lm3&WMR4q8grwef6GGKh+U9eV6WTc5u$- zNhTeN&1WvBAG|LTPn;4y125aeXrh`cE5IjCE6p#D5=#c$oQm|#NFf-vpihbEd#08l z!J5Djb3=A~IxC8BPq7I0@bu{b?R;@mkd-W$)jn})=}{eDt`vRY!(56+nyF;t@4qCX z#Wm!H`qJ>lXnMi;A+iC{tNY(!n9Hcfoco8 zTYt3ZSYX1=-~VH`4hHi6PXxy0e}Z4p;<`(m_&iZsr{_%B*^ zoXZ&eJdp4gsys%q6fOqkb7PLwJG|W;mvvOX<&Iq)WQLbBMm2n5$9X2X$2dW2yZy7j zdGPLUKRf3lYp0gJ)g*FePiT+AD$#J9GjX*apI->G8&+Ig_UEkl%uV4wQ&zaUhON-N zeBQn6o0(-j61)gJ=B2%G>qRbB8f+GdUKIncB)%jS{>_UYiU3Qz^9XR6hS>1og+=J-@nZC$ z9(S!-9YIR0SxmLFKAxt8wgJxa>Zo0`|9YJx3#xVtvKzu;jr=sO1#(fiKXN2*XZ$0n zusJ%!IwP+cWi6G}h{8MNOQ8{KGARz2FBr~B@`^@_Jp8w?124b~Ee6&vzsyx5Sw6uq z+k^mT+bK)g!P!i>m>fZL1zjyeWGdtjdC>9T5eD~9IKE-NYiBbYNOWZnwM*19CK1rDPxE8XaM!W!<}6! z@@DWXs{N_lhpSR6qUVIo%NNA*yZ_#Oaq_T zoj1TS^|E=V+-2`vK3Pt~3>?ifnt%6(*VOTPe1c~!M?R81G2tbljMH)?rG^>nq*l#_{&@y{{`61vUPNJ<&c8iZ>jg>epDbHlu<1lM4+Fvq{^z$ z8)C;ZBBxRNY!HvaANY|RMRs8Pfy-n?Z~-xP?;YMw|1SR~Y(tR5s#V~PS8uBPg&yOrPv z1RUB%uspUl?c2mo{DSYJCg-#B$HdflQw^bc{0W|s+Vt^rx1^a4+H48g$$G@?Z)oVL zWfK(a4v|y2+?iTu$Vuy#HlfVyt%w~51MJf`_@#1(wpjX}T{vX#cGZ3j#p$CKW_vwW zr#2lmrZAe=J%!j`wWfpy`Q!ssMO6{bTgk@nSe1VYT)sE|&H{gfi|eiHFJ#AV&FiPP$s)sA=={KGnz zVE6ErpjWKYsz?KuCbaDY?U#Gx#+I$)jbLTD=M+hbI5KTkKOv=@Bdtc!o2${NMhP3) zG}N3=Ov>Gr6v3RBydP8bt13(l^T#*J{pCem_#4xetgKcBOJW3!y|}E4vXr0Hqa3OQ zk;#!rhJwR1AWC9!%P4+bV%rFl6kN1`HzW|E+xdDa9RUQiYNDZrO?PO{4)poQ59joy~Q2U{Hm=DCA(T&UDme%TH zm+X37ov!gSC^9q*PyxfI)#c!x8cXlRngDjo`qQxf>hxOndwlz?ZqnJ2M`sFk$)Orl z_nAqQA+K>DWBgQC*kW7;v3hs0J?Liprk~|SZ@J)^t=@p`p~0enibJ^}AQ(331XA-E z_JOveQ?4)xT8<^4HW9=gj>eqID_=RBKD^?kE23}Uw>jXQQf9(gK!uhLGyUGe5}CsI z{hLF!Gi@w8RmeA9bEH<#-aLP5FTG)^S!J8<6;DhZOP%Ai(_^_o73tv}klq%+Hpx+) zn(NfOO%V|$zd_O_MH9JUM*8)v^G{25j^Sb5thdOS8xU=z{&Nc8$E0I6Db- zM_77B(zKCAiUG=|;%jIbL)D8a5M0Uc4^OE<7@h6lP2Fe5=h9yeC+y>3j*h2KC|-Zq z`tMmee=9uYH>8~X3^@Gy9?2EhenvoP-s@1JcxoBkEK@cMZ6h7KC;>@{^_A0hK-LT7 zc^o`pD_4SXBOwh};e;yJfpHX1PY~243QEA=Ngl9H*M*BtBMPSH4$2ij;QR|9OVo_s zAQgvz5@cz zzbs6@1}ER?Ef>0038_%2hN>b(07WS}Jam><;dvG1w646quyc4*N!BmfgZ(GtRf0VqRlXvV!J)yBjTkFmE*P^lP--6=!Y+=>A)G7 zMN#MLzM7I1mbk9vnsa-e=Ucu064g(u?jt4oMTNvrPD8*1du2YB3tWI>tEmuj-q#PAm!e?Q!QZq{&^<>8ilg-~)~R<{~Yty@HL1RGOrY z>Da5UGVE#k^`*{2*~FQppY>Wu0@Lk!TneQ8Ry0oy0z>wt;hVd75;PrNq$gVlX1wmnI#As2t>ex&p9TItt z>nhu4DCaF|HO!Y$`*05&^~t8Dpqe~}{HYkHfEvrrjhl5coFN63TVQ;?z^Hjiy<5FK zcn3FEJube@2Fd+9gYU5S#Y~c`OX0>a$2;jzoS{H;gWS0nm8ZSzes`d^=C)qJF83D` z#J>2>kFrI?P*;3BL)#XDJHAIzigROrw{p4tVk~>*og!R!D@R(TSpmN(5G{oe2ZvD{ z*C#hG*aS6w$kO#H$-&c;@3AbE2emU~AzvCO3oGR#1WQutQ3}6AbVB{`q7j3}U4jXo zR&Ab?&|rsQ`FZ_z7MU*FZ0b+H0EJ#Z;lG+s{Mk+jhx^CxBZ}nJ)V?2S>(!PjD9tzG-PgNPd~%5nr=;YzkB;$qjd`OQ*@!a zG;sBTj$Hk7=T(8pfB|nEmJ@#wIyIqC1pmZ!>mvfgZYiDB70h_hH&e7Rc zu0i48lX_~38*F@PMMG~?N6e-WIe57rDpAeOX$oY_!5uBus+RTd zIM@dY2)J8Bncuff=DKmmp9hJQrp1br4O%A+q5P78EuKOv$`1Ilodh=Env1NdzW^*& z)B2aTwtQ|XhBdsFC*iSS^wbRm|vd@#csI5 z_ghJr9(BGk;??SwCzizaB4D_)1CYH^Mp+Uk_MN5AaNsG5Z!qC5v=en$zs}Px%dWND z0}tt_$!K#3yU-jxv{Hw;2J+WWXDw?b_awu-$lwihYm1+km$eg=zm<5C$Xl(3C9{Cs zgHhC4Lxc8Tkr#d1?j72{OB$bKrq+YOcDD2d-+^C#PCgAz(sq+xIDoBJMN{#L7Z)mF z0kCvsChkh6Lod0Ry22+sMiND>vdaLen|DuTJD_3M5R;A<$6!4#w5zbX>(KA>xJVTE znXh^Mz~4+4PrANFX_zM?--sG>hA8enhxIFYkfJ^l8M)ZJP2Vu7@ca5TY`a;FE_qV+ zB&`|U;3lGKNFvl(#UE^cEywglyo44y;Mx(NQnfk|pcP*6E0bOjmM?#^r(~s~NZ`bk zFPLyHB|tv6)JlVQG6-mQlle^@62S~O4%cgF;~l{T5OciI1LD&dh+2a3w%5=e7Rm*K zuWcFKB%({G1Z`zLKOD+>fY7-G7jWs$Cu5Q;s-`T~QkKR?-OEj?U_V*-Z&Qgrw>ELiA;LCC2-c77>(FPxF!bMz zq`TDZ@SmHUr2erP?OizW?=<*mzl(lm^@C8H(rZ~GstQQ= z0~e2d&XdRjjQ%Z@JzzK_8cAz&&`wDF36C{X^k*QCwSqXLKj7UHIx`|$_N#&MMr$O( zSoJ|byhAfp%ZekeFN(Z*nr?E$2$XnITLKT4T-aYg3nyHcEC*rOb`&rT5g3V>T{B%D;5SS zXOt-`6~w>k6Iu^CTY-f$+u-;JI8anqt0ZzgD;FvA*xsN@BU1G`aIs57H9MX? z@HKD!OpEQ6jzBM4BDwR?8*yQpuh9d=0~)Ob(O6>#%LpA#c$`^GD+Bf;2rpWE+Eb0K z*g+O!b`Xf$Mb*w#Rdq|Opc=}9C)-(@tb&zt36Z%2ahzd9F1MDqwN`^ zN~{M^6ovj(Rnzs7DKfA@)J`&)miQ8@@`>I8LO<3AWqaG%_*6z-6~ zyGR$v@S>(8CxMZkD!-ZPcx?j91*c8J( z&=y*pqs`7C4TcN-V3K>ys2wypbpT^C zEZE7`xb{_e(0Rf*)`N;#?fyiR4!JsDgfhN5Z$NoB!%-)ZHNpZKn)>Lvg%$h&wb$g# zM|A{Z(30%ebmffcX$PT-A<&rKSQO)$PMwNi8aKcS)K@fwu$E?SyWnt z7qgn14G9>a(0@pp>MaxQAAL@`egMr)ljsm>)z}5y6YMxqE{j>`{xDSbA{fBelb+zf ze_u{lKooP3vOt3UsIF&FMTYS~w)=2ns^L~o;-hd@b?S}J%bTJ=Mf;3@pK^V>ON=9H0P zbi@`vd+=WXY>i9}?+@Kqb)vT4laBGZw1W((g52n`DDM8)jARJ{=M#RY^@{VLvA#Fh zGcG;s%83+wQk;4rlD!x$I>{7j>zqoxyIY~|{6$#@X)9NdmLO1W1i3p?Z)@PyrNRM! z_d9qX>`-Z>X>;(iyNf%I(bLvj&4UH;S0POjd=9U0q`Q)t3?aUpYI~`;Np` z)ts!7OGu9j_r%KZ1#S%*Nvuo)g$}}kn=#rQors9XOKnNiu~gt2m_DuRvguC=@ZH;a zT&l#fqoIm4#5g;nHo;e{x0N5)GJ@-aT=t-J^#D65oH~6hZ@At%{=6+Q%4)`};zU-( z+iyiy9d&>-c8*szyK-W)!C|xDcc5H6^|>wezA{DgT}68t4@^o$%V%!T*zp+K&-6xN zm9I`V`VWzF1Oq?#VPrZCkY_#j)n&vP?7{u-BTg270m*G~JHlXB^C@mSzAxmrl>z^p zFb-F2KESo)$-e-F_tWW%H4F;#wDq9#y^RamL@J2fg5=qf30hcWC*M_hFWyG=@pU2^ zLyf5oSqibD@UgatL;)=D7{u0MNTeJzZm=dMy{=rEli@1KCypNi^aRXuUqr)UZr?*A z$Va&A;ilDk!_|oub&%&@wpW%l`95heqojC6X@*Q%01iLrhDtWFY>`Z>kq36$P=}Ou z#AEn{S1(2?%x9utB{;oV)I$}nNf9U9XYw_J@XP_ud_F5gSc4@7DEG=p%~x+!?MzGu z8_|C(F?$-3n*bSFSC!RY4S@=vHY;$Ar7J5m@B5zQEspP(Y9Ho&m1JJ~Ud#FnkL2D; z#)Rc#Jo~Tn7k>c`&u=pq336Rcn!I-p(L4*u{&Cl&$K6x3Yj%DV zu{!97b?h|k*EJ9Gsk87$TkBo^a!pqPPT_sw2|dYNn$1|}>=9Zfgj=_o0^Mqpi~L_d z->v-?vg0lBW9tp;EExB=19|0M-}vRDX|ut=`c8iCsneV&Pk~hkokrVGT(?8uRWFx( zR&_(?ygQJg7x9ELZDX_9mPEThC>EqYq->3&zIY!gOj*N&R$qT$B) z(_+9=PnE?Lq= zw&Q%R!&E&Q7riAIwwRRsNfpR%hHGrF1ys~zrwm4Anx{cUIHZz)8vV6+a5U4aB8clU zAm(@@H_K2PH7%Y*KaTFe_BEraeN}myQU=2R<6A<00Nh;y4mFT52&FmYvPnVo0)mlwHH z7=v8blgwi%=R$+2aw)`f_h9^|!dBhChtJpODCyw#&*!=FozC zt@d@!FARd_;Oo4tah&IP{9~S`;I{R3H?Op}YV>>`)CiDDFxf#s`vGJvSOueoi1^C7 zD(Aldz0ubR@mFTSvR|<-bp4JEUVN#tb^bJ&78=NuIbc5M!+kZ&u9TA<+lz^Tx$e;@ zR~lwKkk41CN`4MSWe0T-Gr?l#QH7gO&QlUKH>|^kX6a8Af}`r;Gpf(XQ4(^pv=|v* zWCu(sz*M7Yrh7BRGWi)$A&l@Li z4|=Mp z+d%uw^iG>Oe9K^gR-Yh>A@tYb9_JGBuWXiHAQGbnLShN`f64cVd1{OvVCCD}|AQGW zh^Y%%!a-F>i{8MQ0U|y_7J|l-qff-e1Er(5yxf$#$$I3mxVkz^zj>)}m(=a2bmag; zh@Muw*CWBH%YOG3d6{D{^&3c*UH0c{*iMLdOR?P?qP0`BFN;7I`;MKmW%R*ry_hTF zgBSgc?IEIMd$)*jWKC@w>($STPLJn_+&ouv8zRn~+8a(V@AZ3yC06a7E+vAN@fvc- zAZ+n|#OXi3#xM0gOLt~{r+5u~T1vPyIJxPN@cv}yi&U29((g~*nlfNbwMabn2{ntp zTK!s&wspOUQ9U+RhUJEI?aDxg-e(K{<%;xOl|9)IUp`pw5~t>UyMjdi_PnwuFRSs- zM{bfRMN+s&>evdK+@;MGmU}c!IMGM2yd0{Yh$6827huyt*MX}Y5Ix8|ufXNZQr5co za|2$5OIf~5qfTb^dPCyddX;dk^P>NkgelY5Z;PNIC}d_wvyu}1dB6*Vam`A4nR{3pq)SWEsk1gL#Hpk%r3@7{Tx|Ho(ESq|7?b_3u zzaDesmqf+$C5Mm9hilf?1yXi3Wx32+>D;J_K(R~DO-5=o2iFxV=F*W4n~ppSK|lr&m~zKp%DJu>2T_zf zQd*=g62FNCpJsf7a*0Y}0w*uJ^gVA0QpgZuyZjW_jgO3w{~1sq3umY8bt;ZFRLa4f z8V1K?d?LV!u#dL=j)9End0a@KJ5#93@S7&-%4E-DVJBR=WJ~tu6_=ptX*XGiCZ>Ka z_8*Kbre^V0R|0Dk32NQh(BUkuov)(I5U{m=>PrAix~eqx-csC0DrQ7YPC-%wQ5pj@zC*&V`PL=ZNoFrxzb7FIWA6( z!ke2sBN%oHLB%B%TL~I4f?relYU)h4j~y#;Qwa}K876|xm#VCwcy!uGCYcJR4d8;s z@ax6w4Jdyy9yNQ+wf4lc6B*_^_@LsZg;IUe?yx*LjIdOu?|EEtRulTMaozF0lmnur zYvCvOie_)!+uM5HC(@17b$i&QYkSu~vs7S|BX>oYKI^e_txyg&?$o@>SdGRcZie)? z&q$1gLKKn5I^NBVYKR)uKPh+pML#ifNsNM>|w2_P?+J8P_A5}MYB_DAePPpfK#X3 zH9vM4M3wu#xSg9i3dRo=s3Drr^)kjx;1>4xZ*LZ{Icm3BNFknMjSmTdBW6l$viDBE zN-~esR%*6JR&=Hkjv}mKnb9{6@BKo5*k;*Gmv28t&PdnErH`=fEl2%nN$e;Zjng^^ z_*>K>^O=%}&Z{MXMM&&It+g^rNJn;wXe&|JIa~uCr$cC=vnd|H_pBKWD`fh_cf14l zqjXe}op5o**3fmVtAtuSk2VxZ4iGS*_)U?w&3P`Gx2E57;|pQL7)^)b;!6XwyAVD} znMhzMwA-Kb+AVu-hITqp$E-Gih}Qo41ZdOMU_wnxI8QSrpp#CdbKm#2#no9JcHsC) z2)(;&Dp0jb?};~cDN!w`eUrsNz$)R3keLF}0(jiCAVaGSjs<~eb-~3QA(fM?DmWOG zym*Xw;%9}AUkCzs=lx&6Jz3XZfaS^VhLiPu2OJ1J7Xn0oW`rkCT%6i_XP5onch&FM ze$^i^YMZ~er!VImh$g?}IjR67yMA!zvW>S}kP5r0f4vk4tQEFmTtm${*3Ejb>EdR% z9J8q46$lc#?r*7g^mfer#UpG$$z^Au)$v4J-JsFL+FET=+|Y=+gLrhXT9|YN^7b-I zyq4V!%k{`;P*kD6_Z>q6tdLh?=isYPUM$R^4g-->(6+A|SVWJD%pvEzUXwx(cjYLe z>se`Zn8(kQ*%QP7d=D>6N4V4n2r~?$g#Ml+VuJkY_;#wvL7Sp+k=~Mf1q@>o?uXBx zG`}fJK&gbhBJCDKavsJ$7MY#WaA^n^<6W0hT~3(6cHfUfPlO@7*-;!laP@&b43JB) z2M!Hz?65F>(`VJ6c|BRX{Gqs%kiTd}EZkYxHMi zL563tk9!ceLf)3JN>hyvFE1Ji`lEKkpAOqHx#Lp?UI^;awET_s*f?o{q~$lpmnCJr z?I*iSIaX`bX8jy|p7lYSKb^kmNnf?!5gACP{rl3OC4Mo7@2jYXvgv^=Nf@@BquPw*0uRDC&-H6453mXQeBH|kf$zam2u;0B*O7)dfh?cV_k<_;WTIEvX+2Jx zx99S~*BlIiJ-b_h3{)-MDfNyjDMRucV{nvJ^3z7}yVlxM?&5#$zckc%H>qrYNxAR6 zCPwtovZLgi}h( zsozf+0F^=~XWZM1nQxZrJ|m317wu*)Gpvp;Yp0e-*PI;ZJV1hPJ6DIku>HArgz3P& zi$-!{L2f>%p)c0Gcp+1Xa3jk0XE~(w;HArFf`u1-2DY ziRxIUi`XO?>}(L|(rqwd=RuR)(SuPlEld6g+e=sHNT)y6H0E`l?&w&fzrO425{(CW z`a6-1z2iNqOo53CTRXxnjh}9v&B-f{JK zVU^rthcz#DI*5|31&Kds_rh9B3 z4ZNr9pyQEJOi;YWzR^^<3>r}~;$i%DXo#RauK~(>^yj<#U|RKP{92%SrlZvW8&x<1)wC%tb{R+-xE4#hrekgnCM8|5ghy=;8+ z0;>ZX+oxy7n`6y<{Q8au&^CApz%KTaZa854;EYV5rdHO@RY}xe01xTvch^1#)?<{W z#L?df#VK`n3^OGItKQayZ1k&VUf7vJ%0WKlXW%)iL%ow`v#Z<(qEB*vOsC<6B5RcF z4eG`4^kvwE)>>LM)-WMG1H-xBKQ0 zGcdkBwj`LLqDsQITw0=sj~=1$9sG7LMx(k+nNCK>7(?Ggk8vtEu5*mi#{}a)7h{7E zWZgFTXj~WT%|MkCGET@blp7^K2bhXpWeX1a;J2G1$ck?ymf{u@#>@>ibA1n$NFeIWqGXr~Lw>{%Y z)d$2HDj<3C>cYz+Q?>}2xa{}xJkY-)l03*WxlMJ50SGrE5)sFE(Ph95#`Pg#5aKaA z5%>_+*DU_})N@sDRk;XUbF}?N?2_`%6qYwZDl;3nMbr|9#t|;g=rXBl3;7#ze*yUw zT?JHbRWlBZM(uRP^E!vi+GS}@y&WB8T{+MSm>b8{41BwgmDN{M~sD&3?a;FI0_{bRoJ91R5B5(FW8U`Ae-% zbQtP>m;7NJah-76iH)01{-VP`zrT0Ql$)xA;@JFY7`|If`BwwbsP3E$`U_U3bWMvM zp?|cZo8q5M>nIZUWt{QTKbk|FFiQkfMns9!Hj>#aN4SNlF~ZlqjG}77n?T!uM7T=D z;4@ifXm%DfD2yy;5?gWNg!)`Dj606unk41zN&^J)(OqoYgKzUEnYDiMxYlxRMU@70 zvA}w&cA*g=9@z(f6hBs2PBSX4? z+-DdgBWc3I!itofAFaOQGipq0?Wx?e$fQCUNNLQ{2M2*S6 z?U@@pXl3JEIiX1A=jh^B6BM@bkR4@u2?;eY79&#jU|cKrUgvO7ripN~$f_WF+(tRUFnAdt(f=@>jO9@U!PszSZ@{5*|f)`a&z; zW6Ot3&?*g)?+gXBTXixA=@R=_?KBOz)*Zdhy;eLOkW{44^DviuVK(5Tqn;b- zQolvE-$?6vDhqMj=M?kEML>GRF7T_LI0tITG1y4*d_?nUMS+5$cA_T_dk4CsBCLof zQQ->bH@nO3It%#@ltH!j2tG_@L{}v3R~y(Cwo!L|_xV}hcK8ieKJ0)zOVd!4Z<2+h zKsz6N*^_u!@(B6m+Z6XEv5VjFY+N*k2D2USMS%xAkmMnyJnV$?(V`HLYo$Ns%t#ex zCfX)~<6_TeK0D&-ci&NdC<^phYiWr_S-AYtgx zi6{(*s0a<_&BZDQx81Gxh`(kSOPDn|EgC7g8m~$ZMnBAVbXa3(sVKTt9)rp<*+dghI{Y~tOf09eI-AB<^;NKkn{DhYatYN2J~Ot8?-z(Sgl)t;mg|)7b^qBBS-QdH z(i@kPtw^}-2$rRWg9r^}^jKQ21IaUK^8aC>vH_@(mdBO zSMmEBiW|;^5q;6`xMhC*xkW1#X_TFGO(GWGp)niN*7!3qhe*ed z3Hu3;7Dh*38|$j;`QFZ0Qr%t%ATJ;U&Zl}!zf}4%3Y_vL)YJb3^nG=@B*b~lQP_U% zjx+pmB_u+z#Nd~(9xDtSTnvt_~5DS=9E=pj4_`7)#kI!cK*g(&g^o~ z_eo!7T!-Y-y5Aj>@lu+$3+jZa=Hh73SLI}MH>#n;oCFX`!LrRpf`YC45c*)`&Rvf9 zKh@&c#0U`U3yj3!$)5vX|v%^v)Mg=e?MYum1z7lc7vF+ZoJqrks zG@tMYI@Ntzn%vD(6ZbTionDjL`&4^0>pn~OQ_wGidy{gZQkeGUXt4%nWg-G?@SrxE}wO zT^mpm*}kCS+3rnH=3D9x3QNv)%1lR?%Su#6JjMOz-JCIP6niy?W2a9UL2Ea^PAv#JGp2`Kz!fmP_Zb|~QODNNHyoOcDbLg{Ky}>Cf zS)T3Mz8LLx3QB;FKWZWZ(O36|`d4oe>%6Evj*e!&O%#_NW=RQ~M>GlQ$IdX2;~i2e zAeVWsrC+4AC!gK;KDYGn`sGdvU93d&ea}JfoMnN7j9Sw)%<*I z(w0{W_oKZs`({6YN_h-qiqO8nB zu-y1#e_y#va3Z}Y`%Z}E)~OX}RPEFZiMP{*Dtm~5z-rcot;UB%gp2VSWr$SQ#$clAQc5vjO26uzi4u3vCvbkB%)S*d@c zMQ|Z-t;P`EVNK;Ol2K=lFNULt1OX{%RU-FVh&4O*PSTBkauk8-)N!YB;^uqIgVa0T z(^=qf^?kbQE%Ub|mxyBQJ4@7qj{y;*UvEZF3Q$gAUx%MpNg5rm%_o-=4k7CyR6G5S zb-ii@^E@jkMLP0Db)8lhBil?2`E@uVfV`Z*2g$0+esTk4#S8294zHuBEg8N>efd>s zLIq>#9;l$l+p7B9Q6b%`s;`#&_j0U*ZFzd{LQ>NmZ7AXQ%a5ot&+Ma z_X&TJdI4yc4#wfyq;lH~p#rJUvcS{Stlrx!(W18I+J=~Z88g|6Ya)k38aR6|Eg39% zPG~6v%R|+n_R-YsluN|tyyR}neU7X7e#Nb{hfbV8`dg}lqbimK(~3Wi+1~9~!+*@o z*&gELEX{fXx?13=DDCvG$kW{|7F8|n!wYfc$RF80-3J6z#1OE7b6eCMmI+C2lT_HCNF+>7=&`ybuko9rO8|7=1lK@RTr_-}QSlty1IkPEp z;u#6d@Xx+oMYlc%B&aX$T21UHjP$Gd?+f-`t=RVcc_uT0#8_Kb4IA}eHhpqxW_@umk>twBX$iz5aEg(0RnEK$S6k zoa8OGqY!;9`RRrzw4wG&0qEK?!=TI+LA4A%)7ous)~PY8F7J3{8cM6*XQRv9WkzfL8j<@ z_Bo}pJ3Xc~rbHaQOv1r@H>F=pe`IKPLv7vxBo zEiq#EOQWnAX=5~~hkx#Ykv3b9sRhuX&)$#6kTF9d84aXmp^(%dTm1-4gYRU(P7LrR zW%9ZB25bubS>%5KBdO|O5{|v0674dS7gd5La7N@YvTpY~R^b)=A(Ci& zT6`!SUg=#Gur5%aWI+Ew3ZoU zXbc23js4M4s9>=>a#4N5-fy?f{^t8}kh)8Q>J{yIE;MmmRuNuPY^Zu^+$v0fAk?3FSn^ENCbN>J;j>nRvmGZ5lp*Ee`$CzyK?sp6Yk;Kije2Dp*C7Xw*6wVo| z9(0q~M`dKnJSC5F&n<-Od~=CMRL)!X6NX)IK3jLOO^gk9sb@DyAbu=x_+WeMLK5VU zT=8Ay+v~=DyG!o`K`A~_k7tcDM&3d;@)8sw3xd!zNTd^qx<;Xbr)pGqCQOnWsi0(X zUk;Qw4G`{aTIM=K*h&t);*k0RCbyOnt;Au9W8p@aj~On20aQ4V(9_^#$3x+A6h^K` z3LFGDUcv-<^bYXtN(PA&3PHYHLj+MpE}cI&lmZFO2+dF$h1RlKS2VB`9Fc{NlG)}t zp453KEL7vwY8OdrH~SVnkC4D^Eu$0YxDv-BCe6$N00*%SCaGIiyBZbUp>SsK>l=8l ztz#0HrE_j&1enX5t_^uPt42WdtoK$~qZ@D>+jqNGOX@s0+P)^gK_%#3x|-K!qY$~( zSyanP!%y~FIVR_V)5K*E30EYICgde)Myaf(jxU&cI1?oL1>#h#Z+pPYc zHY$88+wp~jeAziUODrPt*D()FswNIW7xr<=2}%bHlfujuE$d;om5x}|1w$%U4H~-* z8TRwSnK^M04&s%WZ%s3Hj{8eMQKhvMZ z+>858yVzwj*tBQm{Z->#g^8a*b!C_$mF^9peYC(1hONU`6@LwfA$yBvW4)8h)Ylo@ zjwX7?ik@pnF8lGzVB76t+2C)hBVHdWpCE7_ygH)QH&TpKd$D#q61K+$qF=Pg?XK<} zEEj->0)k=uKA=oK+JiVLPC8&>d30{hYJ|`ScWn!KAZzG0Kg;go`>tlrIU|9z-6E0; zn`4QQj~Ym8PGl~x0JTrDzMPz=m8mqQx5=k_oB3(p{l@m<&y;P9A62$}MZ9;)(#ebx zx)RAT)HN1@se!7s_EcqtSMc{)({A25u3P<>dz@u`wqI33N2%rukQon zLOMh_;sLm!X~}a2m?lD-Zg~alV)Ynjg&sykFHO;?b8zYnK13+6fUWqBp(sK<^Jy$h zzS6yEF(PDA7Fo*~C`*Hy!kV#Gl%RW)r=VMEwwgAwR+ln%xE(|*2<`s>!i!K?(QdHk z_lcV6E#Prj-ry72-M37Q>If>=H1r_jSzVf=WN^)nwUUIgo~IUjiR`B# z_V;sr^^+ZYwHKdC`ZlhXl1LJU{H_V%Tz%bH*8S1f)!2=T?K6GDWpomf)}1dRNRe`J z#h{MDwOF+r5GC6s8iQDYzF+`A6FjNbLNq=%uwC<8L9(5?N#%>l>#z+B4H!7Q;N1Gq z{BmzV`9dq|49odswJ`ekSMHAC%3Rpv4q*6{D^obMh}5FqfUBR&_SI>Gm$JK|NZ8!I z+J)q>t4#9W*!h9B<6A7w6h3Dhh90Q3I<5&EBB>!$j2{3K?*f9+(S3J;Z$ zd~EFxZKHgfov3R3!z15cS$RkQ04<*i)|>4`FLSWA3!7cLXl>@@Ja(`#t0lzpHYmUY ztDZow742&`gWtMxP^mdQedZ7Ecjmjo6F~p@4{h!fH*(ySv2?>s8FA4zBa~3rNGT(+)Rrw6g9p=HOa!00js3Q)E5JF4~_Hk~KadKKgu*f;qC6<#R|m zjacDJBEcfdZoR&kLuGF1UR>5`Ye~h}G-1||&7^C*f7uP(+5JZMap;$5(Qd#)vjNMJ zCSCNDQ)Cadjg4!FeqVS)9~16Q^H!{{6Ao=pH@8_~y?uC)+Jq zZs5SgML^szClx>|c0E`}WOFxmt(wG{dE0$B&IBb|g29zNL(yJnXO-Ah_*iDnv^8_O zh?u&v=|?L8jYW5KXhu0EY^|+NoDJ_>MN)t*g~|9!VN~#Wa`Wm-TT^p4ZffF?YcOe33sb( zOb#TKBNTpgrSAJ&md-8>PVrRb0{mx?H?KCYPZjLP!lL3$lxeqq_HJBu2D97SSmwP< zFYdu1g#Q5P#)K2z@3Oti%zkR3YmIuJ8+hLgp>N51!}Lhc`9HWm>hf*DmwfVDtK%VS zS^V)VF^6I_#Ltu`V{6PiK3aBYo)OtTzg}BUFa7QR0BIj-nYZl%>MvGCX%fA}#$wbp zHKhO`q3ku!S!zuNus+bnjS;%}wo7GSe(yBb=_f_bBnPy*z@ zsxD}&Rk-|mt&FaCvxGc z;U7fH?xx6i9%Z}3n$y|kBM_bR!{DDBZ7(b+RRfV|7)cZD_PC>Hk?hn$_0{oOjgq8W zJ-zn-0B+@b^cGjl4YG|Nc_UgKsOHv=elL%2lp<~TK}ikhtXdKwXwNDtg*3dvoG&-@yJ&H9#9 z7of}LE^f9s5t$23oRh~uF67rt(s1rttzs_0mA|6@0Et8k+wrRWOF1G~Prlws>03tA z>fxhPS`Xd@Lgc+qnPj1yWtPpeh;wNgZeh1`TJ&??C2WHM*rcsjm9>tcKk|~K<9oZO z^<@S*zzlTmYfFd)P^bij=}((>nPyKTk0%U;ug9Vze~mB!fNn;#bBJ8wVR#?8oG8*UoG1a}h! zjz%;RBApS%tMWW~m~z7ksW@?YM=>>LQpDo%?EQ)#F!uX&;#boH3yq^6pt(c+67MrZ zoZJX-#g86W46#rO6HN4v8RX&Gjy97202x4gc+?ERT>5>KXGDT9L>%H*0*OLSku|V2 z%@`p}dntn#9k2)Gz~xACWOi(8gU`%6zfB7XAf=t3t)?b4sVKnQ$v6sy;F>A3c-XzZ zyx_Y9tq~Bt=W|Brf<2sK7i4yHYUmm7q+fqj)ZbDyj_~ikvPYSoWek&`cVANOR zT@5j97=4^3AKq`%pTN+{&}J%-*5H*DFXiG$1Vt1d=XG86hH0hhvqkdhjj(W3w`p~%<= z+*@l_fD(-ZkVWpt9IYU*9}z(%l1NRWkIJ~*FvRT)8pvACzAFa5TGsb5ZcDLFC40k& z?j0+fyOL*D*Rf!u?lyOj2VhEdtjSH}+JutK zMa8+J&VLFe_KfM2rHHJ9q}%yt_LuH3i*$j(&yA5i#x@d|ml1(?l$5~u)QgTv(uJ-q z>7XBO-nT=&+*rpfR`0Me%@3ywV=fo5>Tr}hQiW03)|yd7k+H=lj${15eqA?8qn(bva_bB1zca`57 z(Pw8k@`y^=;wg)Nb;l}#bfh9=QR>0XE1ne; znKFr~`!~f|KJ7m$-B_D;!34X6#pBBN#M9}s;_A%p+lcX7vrn%Cvp2FN?Ox_Pp6$0= zJ;m9zxSB^9kbK~}t-`;JBj+zST&LKa^7VNgZKV5C1&j@S)MGgdF$by>jacWCGpFQx zT}gc{we9Ffe&KO!;v7m0*9w|1C#U$|F-LJ>X*IlG@GU!kA#c-w9n-e08m>Bw%fB_Z zAzY)zj=5om)`o?{x9FI_3%oI|qKU32KiP|HvfSJ=bV$p*t@)}}xiY+85YHOgp>@M_ z3>$#04!vm^{i_}ghmYAu6m8NHv|h(KEz4!Q0B98pV-FU9KV1lj*9{s-J!#M^hGJ8Q z{Zv^1V&#owMZ|beFz``1Fm!?B=Z+Pd*qNu+j)rHtZKmc&4^2;fU|8oSbTqV)SlxJ( zk~F->@b9V?RQEJt*hYW8aoH{8cabwSOH^vrW=d}9Y3a0w3~4zRP!s8fNlnO?(A)n2 zES~w_b8lyDc+4=q^LN-!qC-d|+e?uoo`$l!G$>i0MtM(>uFAjVrT2Ztu+A60URhjj zo8$BgcCz8ydvR!Hpa^mOd zFem#o482DlY5Fyv)C-FKCvH)1J9rPYzlHZQ+dK^uMoDDsOuWD^jXF;lWEMU)@>?~Z z=3_gwc24)3knPqI32lCc*|z5p{UGXQ9iTfEt8vf9oA)uh5sxPos`L(8TFZMJmTDVy z07kXgHb!<25kTGwkj$1nQNy2^Uu$FG9HaZr(F35`_h&ucfogp<$SU~z5d6)pe!ZDk zXQuvM@=oghIsQ0)<0!Y7$s1e`nYcQvGozev!gli)v^l>sF&NO4Cma?VDcNxdQ-}y^ zH@S^$99jBkTK5>wmC31xp-Obc8DmhN7Ub7ECn09zdaiLT9-t^joRHHvrignzWjd0F zrl!}(xy)O}$~g||1ojY!?QJdOd2FqcOw4B^)pKbXilZ9v@Qf;zyz?E=SLR$b{AS`S zs0OP!)B@aE$C0cw5u04X9MkaWM+s#0PYdN2Y8++w?Z>}_GN zmiFW>pmZ<{T|pTz%^nx(+J zrZai6HPIg`)Kjb|*@kEQmyHPvk%2rbBI=EbFEP$xvKw7!2m`N5%#y=8Dr{0HYVT9g z)88n`jI}0?|;$UW%y6e<;Z8<`s)oG z{rf+-q}cxe{Jp>C{-1FCPs02Y{{WM=rGBP2TWEfgx5R(=ClmZn>F<9N=lnze0GG0g ze~pEA;fLJJa@+p2ZeNN0CBA>`51YEJzv-BdW%^Aq{{a60Uc)b&{mb}Fm29)r z?b!t_eq)|=454PhZL9GepX#8N#&=_2mCkVZ{Ax5(hp5f>;y1))pZaLxOIF4Amw(ev zG5G%g)ldHb+W8xw`rpIdP{aC+&g%X->}=eN{*7Dy7xeZY)_+m+N%Hsq01tIi{{X5N zf&T!^j{g9o^!~#Yk2n0aW$b$-LN>pP56holg+(IDIzRIJ@=yIQ;t!YlkC=Sj)q&pq zCg)dUdE7twM-TjO>#jcwr(4t6s!c{Kqfx*;Sc1)c*j4?7vk+!&EhJ%2fFdLW0)!<5LH3Y5l1E71BYBVzMwC&k zdIXh*MF~4(AqkMMs8B?$3!jTXt0g_0GpeZ+y}T~FmK^NRyY#Q@F#O1p$*%Nv5^U3l#Ri-y31fTM*tw$cz_3r+;Bg% zw&SoM*9{luz#uSS3cv?_C(r1soC==l&(uRYpbygaE?E%7jTG30dZbd!yWQB!3QpD^k=FVqX=tE|!mIWXs2L({Agl|5>(euH`$wNd z$&In;4c-C`U8waF)jWIl?Btokh-0SE6vOD$gWzOiXYbu$v7RY0P~u7#7Z>|(&xc9t zR##WU8zQ2m#|`<{H&@Yp=>s}|nr z*8G~G8-4S5R(?m{{eQ2oC!f1s(=yd}dyX2d%qr>>m3aD^A7AAQX}kLFl~wBDGtH)snX#z{KHM^m&KQE|NnwS{ zD{FMN^3KG9=bPNhTX_}T9+6kzb2mMsizo@I*kg@R za+5;c8;;cZ3DuE^;dAEYAh4ouY|%GSABVOyNyS8TgCny;BXeHyWfhI1w3sWIS2{&! znjJzEh5F&<_KEuTdp@AL+|W-5Q$AdJ>d&i6JJ(pPsSSknffAj2jIz0Vws&|or}$=o zNNXEWY#&+DdTWlsul5I341xNn#LzQv|8jNL%F1eJiato7QD_H)VI33J&dfbz!a7-Y zi}u>2Qq%jjdO&q`g2va8k6s>~{Eosd?e3o*93JiIpY9o$2@}_L^iFwl%45?_yo`pl zOE>-yke_s_QJCEZUAPe;tB;f!YYe8SG^2A&sXwGyda^URnfQ=1-c>Uu!4+J3Po85 zm0e1UQMz*~p`05)M$`KGx-r%c!CwD!h~(wR?)H04%jNHPjb*nj9uMv? z6ea#H-EuTl4)4Mq)?S(Z+o6wEzTQLJ=KrYe;O_0m@~=uQOJA(^=dM-!{^XJ%bhp*D zO1Z56^POMKdKa>Ef44mMkE3TP9)p>t3kzt%=e+uO(olV7kN>+Cg?nE9b7y9y-%xWK zPZOm*v}3z##=Z4*@3i+~W~%l-Ki`ox;$yLOj@Z354~ib^TAQ?TE9h9%RpKlyWA?Od zQSdMH>WGMP?bJdi-eo&M_HI;@r4{?-8l#`%!r6a1m(Tcs@U7p!e!czRkM;tg<#6rT z#hoXA@{8Ly_@@t-MZaP!KQ_g!s-MohTwgz?UU4sZ;nj9G`@oW-;Ui0DV?fT%Keo@^ z{Qj@X_xj>iXY)R`Sh>3O)AxltOi|ZQ?2Ae|UKZzP7t|>sUF>Ow9lNS&0uQUz7>wdk z9mb^J&m$@Wr;K|uPc)UD*oXg(7xfF|@vLQP%EPxQ9o;VupS@n1l~sCsxsmPsJpHG) z(?No4?>2>1;Q?X0(}9EMJ6uT@3nF88Z&RgcHJV1{_`~mu3)sVP0#iifE{n&Cyu9NW h@M-I3&6q7aFmo->JAK#M=FPu(x>o?T)-&$U{{dydt)l<{ literal 0 HcmV?d00001 diff --git a/ffdemo/static/assets/images/favicon.png b/ffdemo/static/assets/images/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..ad65cde0619cf1d056a8a1d39fdf80b93e128766 GIT binary patch literal 1261 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 Cek%_E literal 0 HcmV?d00001 diff --git a/ffdemo/static/assets/images/large_dash.gif b/ffdemo/static/assets/images/large_dash.gif new file mode 100644 index 0000000000000000000000000000000000000000..ec58e780911c08bccb040aa25cb4bc5929b43fe4 GIT binary patch literal 1116 zcmZ?wbhEHblwe?IXkcUjf`88sEB@z>@C{IKNi0bO(l+)L1&PVosU-?Ysp*+{wo31J z?^jaDOtDo8H}y5}EpSfF$n>ZxN)4{^3rViZPPR-@vbR&Psj#ZZEyztRNmQuF&B-ga zs<2f`Ovz75wF0t1!um=IU?nBlwn~m52?day&iO^D3Z{Any2%D+1`1||dWOa(=H})) z3PuKo2Koj@`i4fjhUQkrMpgy}3Q(W~w5=#5%__*n4QdyVXRDM^Qc_^0uU}qXu2*iX zmtT~wZ)j<02{OaTNEfI=x41H|B(Xv_uUHvof=g;~a#3bMNoIbY0?5R~r2NtnTP2`N zAzsKWfE$}vtOxdvUUGh}ennz|zM-B0$V)JVzP|XC=H|jx7ncO3BHWAB;NpiyW z)Z+ZoqU2Pda%GTJ1y;^Qsfi`|MIrh5Ij~R+$jC3rFV4s>P;d@5Rq#zr&ddYx!Rmc| ztvvIJOA_;vQ$1a5m4GJbWoD*WSr}M2nHd=v8#}rh8XCG9SeUvRI2o8YIXSyJ85yZZwhX=7~yn_KF~4xpom3^XqXT%^?;c0WDDfL6MkwQFtrx}lQrZ2e}Dh{ z{`K?6_itaneE#(D!~1t{-@JbH^2PIKPoF$~^zgy`dw1{LzIF4)^=ntJT)uSi!ufM& z&zwGW^2G6DM~@snbnw9beS7!p-nDbb_HA3YY~HkS!}@h=*Q{Q(a>epxOP4HOv~a=v zd2{E?o;7pE^l4M4OrA7xLVsUxPj^>mM|)dqOLJ3WLw#LsO?6deMR{3iNpVqOL4ICt zPIgviMtWLmN^(+SLVR3oOmtLaM0i+eNN`YKfWM!wkGGenhr64ri?frXgT0-tjkT4f zg}IrjiLntdOX=zAXlrR|sH>@}C@U!{$jiyfNJ~jdh>MAe2nz`c@bmHVaC32Tu(PqU zFf#!Q2*sZ)K$4L`2UJ#oN(}}k@s@tE(|b7^XDh0`(mCWSf3AWnqBA9cfx#L8ymE$M literal 0 HcmV?d00001 diff --git a/ffdemo/static/assets/images/light_overlay.png b/ffdemo/static/assets/images/light_overlay.png new file mode 100644 index 0000000000000000000000000000000000000000..c9e909933899e8f42cee56e6c717c3b349219922 GIT binary patch literal 1028 zcmaJ=OK1~87~UeSrL`VP5iAPhwg`o0Uuhn@G;Nb?Vgha`X`nf%xY?a1Yr7A3r)ET2M@#l06LN>K0}b#PFzV$;vcDpa6@S5h1^=J|lsqM##y4#7I^gmbBEI z4JYO@Ic08I;Z<^U1Pr?Z7SJJ*fve9Lj^IYfny!G?-Zo8w8U#&8$Q@I8X&l5&8-f7E z_!K6{fFMh;{-IDPGzd6`WoU+@SdEV3DI3Gs%oQi>D*Jn6=bp;VRimh2lGY#M=$^~;4MM&&)rv%+<$QsV> zG2sKFUD={pit$RS110JIP+f1J9h8N8ewzwAxrznpEOg9STfrAM)#s(Mgt!f5WZF5? zoT;DUc*#VjQ!*_Op9la+Ry4zlYj{Z#(uRX%LxE{ALSh9<(^Mgr48~(2mLG~UBFiS2 zXgCpy1$ZVH2nE<6@2_)3Q<>GFf$Cg!j~m^W>$QSz;mjhmwM$S<+NKU_B@0>;FX1E~ zP6UU-SdVM&WrwTo@5SRbuY<;BXzybGx#;>9?jCR1Xj>dK8Xp?C+il#|TgOHh@sn`V zVl?M&-g~>!u?`Mh-}-*C`2Nr0&))6KYn^Lf-kksaxNYQc%wEc7R&P6xmTvu8es|&i z!!Kw0kAP}xs&FpT|M`kT{9i`UOg;br literal 0 HcmV?d00001 diff --git a/ffdemo/static/assets/images/loader_light.gif b/ffdemo/static/assets/images/loader_light.gif new file mode 100644 index 0000000000000000000000000000000000000000..aa9d0917b0591a81f70dc15862211f9ee1a27c7d GIT binary patch literal 2129 zcmZ?wbhEHbG-BXlI3myR|I7RT*AM@H{qX<#_Wutr{|{sS-{JQ^j{pCw`~UBs|9|7? z|9j{D-#qsJ>xcho(*Nu1|EEa&ud(@mc-jBejsLG5`X9sde`ELm{qz5C?)|@a?*Fx| z|F0hSzq0;+2*dv?`~Uy{ulS$a&ow02*)hP?NY8+ok%55$C=}rvpx}~Nk_e=2>?;Zq zle1Gx6p~WYGxKbf-tXS8q>!0ns}yePYv5bpoSKp8QB{;0T;&&%T$P<{nWAKGr(jcI zRgqhen_7~nP?4LHS8P>btCX0MpOk6^WP^nDl@!2AO0sR096=HaAUmD&i&7O#^$c{A z4a^J_%nbDmjZMtW&2GK4GRy>*)Z*l#%z~24 z{5%DaiHS-1r6smXK$k+ikXzvE3pY5gSP$$Wz2y8{{ffi_eM3D1{oGuTzrd=COM+4n z&cLd=IHa;5RX-@TIKQ+gITff}8KxN+cK9s7g>=oo!a;y_9)Fd<+H1To=BAIO1cp42>Gjwu3W4aWcf{{H#>>*tT}-@bnN z{ORL|_wU}mdHw3;i|5atK6(7;;e-44?%uh5>*kH?*REcZ;0$^0LyB;-bQW{Jh+p?5xa;^t9BJ z|cQ;oTXD3GodplbjYb#3&U?MUxHUef# zJzX7bElmw|HB}X5B}D~!IawKLDM<-&F;Nj=AwdCtK3*PfE=~@1HdYp9CSU=h_>+Z| zi$R`22UylHfQmT=*5U{1Oe}{58dj{+&RmqUW>@+uuDEsFpSoUK#=U3b=`(XZzs!(D zq5Jg1<1>5oCha`(q3deiM!8%D8Hv7)sm4Ex{erICFu(Knq1s-fYQg#J*((ElY#ST` zef?XT>>Zq(yRCzi`rV9KxVQ`^22Yz|HcMstOhtq{K!qL`*d3Y=zAFl~d3O9@jd{)X z_{pxx6V84%eeiC>XPL9S6^%+HDO0MW_w9 literal 0 HcmV?d00001 diff --git a/ffdemo/static/assets/images/loading.gif b/ffdemo/static/assets/images/loading.gif new file mode 100644 index 0000000000000000000000000000000000000000..3ec2f62c145f46d28805122e588b437f0656da39 GIT binary patch literal 2061 zcmZ?wbhEHbG-BXlI3mwrqNx&TDC?lD)MF#!ttbB{m|?FQ&!<490Dbvz135<>#c%^z zV@(x*efbyuEGIoV&v^nUk#C56lsTcvPQUjyF)=hTc$kE){7;3~h6MhI|Z8xtBTx$+|-gp zg^Jvqyke^gTcyO5{G?PXAR8pCucQE0Qj%?}|N*N_31y=g{ z<>lpi<;HsXMd|v6mX?+vGmMOMfhu&1EAvVcD|GXUm0>2hq!uR^WfqiV=I1GZOiWD5 zFD$Tv3bRMVDIQ9=jZBIBo^o!>KTB%1XJkii(hGOE?jkSNl+@ny;uz{ z4yi0i)elN7&Mz%WP6aAg23b{L-^Aq1 zJP;qO-q+X4Gq1QLF)umQ)5TT^Xo6m5W{Q=Yk%^Mv>2~2MaLa#GUy`ZF!TL84#CABECEH%ZgC_h&L>}9J=+-|YN zX&zK>3U0SJ;?%1Tbc{YIVv!;mCIn19ASOK70y*%6pPC0u?M1+3&G`S{-#@>9{rvI$ z+t)9jKYjf0{@vR*uV1}<@%-7-CyyUJd~pBX-8;8$-Mn%A+SMzUFI~KF{@mF!r%#aDv3%LmC5sm=Trhv$+&Qyn z&73iP+SDnNCrzBt-`CsI-PPIA-qzaE+|<}mUsqdGT~%38URGLCTvS+)pO>4Hot2r9 zo|c-DoRpXl9~T=F9Tgc79u^uB926Mf@8|2|?d9p=?&j*^?BwWRZ)a;`ZDna;Zf0s? zYy`|wdb&E=TACW_YN{&AN{R~da4dCha`-q3deiM!8%D8Hv7)sm4Ex{erICFu(Knq1s-fYQg#J*((El zY#ST`ef?XT>>Zq(yRCzi`rV9KxVa4`22Yz|HcMstOhtq{Km`^T*d3Y=zH0_g^X>Y< z8uOZM@snMX3eIjeeeiC>XPL9S8G$DXwu@mx?;2nbCT^w2{n?_8j5aW?Y0I0Q3lMj literal 0 HcmV?d00001 diff --git a/ffdemo/static/assets/images/locale_selector_arrow.png b/ffdemo/static/assets/images/locale_selector_arrow.png new file mode 100644 index 0000000000000000000000000000000000000000..8fa37ae52947c329eeb258814967cf1625ece899 GIT binary patch literal 1076 zcmaJ=TSyd97#`OU5iOx7D|8qwirSrvdmY@=opE!)k!4+IgC3lnIlALImu8ObDuHMq z1X3o^#U8YL$RK)7B9bU1Ff0j#f*ztC3L&Ex4av@|?%IPkaORx<@crNa{g-nk+J2^@ z>`)noVHJ^9K8E%KXjPY%p#QJ)JVaX+Y)QZly$fbU6JX7HIO%kH8f@oQAz`dCqcKO1&JR|pA)zu2Z^&Dff5W3q?FbH z6Lbu;$EAU^6p)F=(|ALcMFc8`P3e7;jSL(vpTF(l16X&+n9uoQ)Fe*}tV%3W-X4{ygpR**Fa~z??n-RM`wc+GfTj1D5^7vmtsTl>ITMIb zH{-hAo3COtr9<6H=?2bqcyK|K6wS7CctKzzngvBo0ueq)AO@GB$gDr)W;mMmxw&wF zrdy~G7v?xF><MEW~nN@bt zLWP+?J?FFe{JU$}@|E}2;@wjViNuh0WB+M%{t8WxeJqSEG75taG&Jod8teFA>kxVp zGZDTyp8b$~zwBFEY&zDNoWe$Xs+oy%t9Lgh?}b)QT;4zLE1x~t_+<6t#+=g^KjT%s2N%MXHFfPbCdHCRzmGeXOC6(|o0~OLZ@m#KY-3lj|h+Y)zts@vgCvZ%dgf$NHyX<#tiq%btB-rAq7_Ra|2_TC&2-8sn zTEkYRV2^DrRiGfEAj=?@11MlsaHLhBAfO1AvIP3Q?R`MppQt-s+Ood(ANx z1-ym8LO;|WMX{OT02Pvg01<<8 z;N62#XOr?`$ShjWyIktX55Ka{%CKdvB>yq6Wr-nB{HFsyWG zibU_xGMEMaS?~W8mPIHfAeIHnAi0#Q9$XAslS)FSNd{N5Oee zC{#LyK)@5%xip9?7lC4Uoy+@^8@M4?V+D~!otXwo1t&mWkQ5RDZ?8-iY+MUzL%nxg z-o~|fZOFx{i@|CJ`6Mdh+HR`jpytvR_DI}YXzow zSPupb9Y18$^Rf2P_7sZ_3Mx4Vtz*Hjbp5WbqQZ3%s9FrSkM!MfwXn2wRlAyExX`m{ z;wO*pj+&Yp^z(gv^7O4`S&x#NYW<<4-Wpodsvaq-L3^~U2tOvJi$QF}l7 zQg1xQ)emVBUKuouh8$~KIYk|y?v=hi*4Ys+{MDi^fqi=FUXYl5cw)yGat@GMr<&w- zJ8zkOm3_AN*8_P8!%dUY`fK^c8F87uZt@$Y$eG=jA5}A&bQ}?O)aa)u>prVY!2IPg zS!v(J8M4k! zJAX07HQ)s8qqKvh-81hTKlws{k$rXPB-6JFtJ0Tw?wIq06FPl2O_sxfiI|+{_V*@JIl$i*fAQOI z$D>{g>-qarno^(iK8$Iy+U&Dr(8&(f3+U+x-L--o1n@?Ami><8SFI1D4(M?E$vk33({t(T~ck8K1yLWN7pwVO)@GsMm7e0O=|HNrY|MfPq~E5$49NM&R@+I z@@*XYi#OGOin6i4++%}?(p#`wx<`dwKFzK#C9m1*`}E#Xo*pt?&0x#km`+@>y?-$S zbLpuIa{GAoE}+#YtNnKZo8-P(FvQ7A)wM)qZb7Y>g?#f;^Veq3!)RA1$4>nh>h)GU literal 0 HcmV?d00001 diff --git a/ffdemo/static/assets/images/location_selector_dropdown_bg.gif b/ffdemo/static/assets/images/location_selector_dropdown_bg.gif new file mode 100644 index 0000000000000000000000000000000000000000..051a0a97ca3850b5bc771249784fc18cdba727f1 GIT binary patch literal 1594 zcmZ?wbhEHb6k||lxXQrr`}gl(zkXF!Rek^d{nV*bKY#vgZf>rqsF*fwT1iPsV`Jlq z6DMZOnDOGpi&w8+ojrT@@#Dwk<>e<&o;-8rOlfK9)TvWDJ3HULef#d+yXxxdH*em2 z{ra`6tnBL5tEW$&{`2S0lP6F9{{8#^|Nnpg{xQ%1DE{Y;@C{IKNi0bO(l+)L1&PVo zsU-?Ysp*+{wo31J?^jaDOtDo8H}y5}EpSfF$n>ZxN)4{^3rViZPPR-@vbR&Psj#ZZ zEyztRNmQuF&B-gas<2f`Ovz75wF0t1!um=IU?nBlwn~m52?day&iO^D3Z{Any2%D+ z1`1||dWOa(=H}))3PuKo2Koj@`i4fjhUQkrMpgy}3Q(W~w5=#5%__*n4QdyVXRDM^ zQc_^0uMZ4ay>er{{GxPyLrY6bkQqisxf(^ff>iyW)Z+ZoqU2Pda%GriVA$ce2&53`8Y};zOkkuW=D6f1m*%GCm3X??DgkBm zQZiGl+#Jo_3=Pdq49txT%q$EIEsdNEEG&%;4J=Gd42><_U}j*`Yiwy~?&xglX5!*( zY-s3eVBzTK;%aW-;%H#(YUF6{0@Lf6S6q^qmz)Z-Hxp%#K*B?iFpr)v|?K?`Io}2+VX{TwEplt7TGpiH#r6O~+Qns<~pVH%jv} z%c_d%vKkfSmE@bVtJAux1G?pVi_;Yu(p(iL)+%OYEc9q&77&u-XW?dH5ten$n3uLt zT9jQxMv#e#jkj)L-4-PVRwYIrPNvgr;)@S8RWUN3;E-hJl9-iyaf|%ygA5#EQV*5O zwiYmCPF3Vv$lTi+cXVRfYz9Tv*^}lLcDs4p63L2_5m` sdZFazB0iy4DQLzE2Pg4mUY?rj^OqdCbkys#pMK)87Nf~&XB8N%0nkuBK>z>% literal 0 HcmV?d00001 diff --git a/ffdemo/static/assets/images/location_small.png b/ffdemo/static/assets/images/location_small.png new file mode 100644 index 0000000000000000000000000000000000000000..b94060fa3ad2c2fa8dd38cd6d6a0e17ee60a7858 GIT binary patch literal 1189 zcmeAS@N?(olHy`uVBq!ia0vp^d?3uh1|;P@bT0xa$r9IylHmNblJdl&R0hYC{G?O` z&)mfH)S%SFl*+=BsWuD@%u1Od5hW46K32*3xq68pHF_1f1wh>l3^w)^1&PVosU-?Y zsp*+{wo31J?^jaDOtDo8H}y5}EpSfF$n>ZxN)4{^3rViZPPR-@vbR&PsjvbXkegbP zs8ErclUHn2VXFi-*9yo63F|80+w{G(j&jGsVi;(9PAvz|`2p(!|uz(AB`n#ni;Y$j#Z=(%8+? z&BX+!*Cju>G&eP`1g19yq1ObbUQklVEdbi=l3J8mmYU*Ll%J~r_Oewb7Ppw28d{ne zIXat|Te@I(3q)@UZnqfWbc;UFG5Vm0MT%&c5HR(CnDAr^bP0l+XkK D!ork+ literal 0 HcmV?d00001 diff --git a/ffdemo/static/assets/images/magnifying.png b/ffdemo/static/assets/images/magnifying.png new file mode 100644 index 0000000000000000000000000000000000000000..78f28f50083088b5d5f6f2225700155e44dfaef7 GIT binary patch literal 3022 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 C_UM2B literal 0 HcmV?d00001 diff --git a/ffdemo/static/assets/images/mozilla_tab.png b/ffdemo/static/assets/images/mozilla_tab.png new file mode 100644 index 0000000000000000000000000000000000000000..819ba573d7f200db863eb0533fa641b6d4188272 GIT binary patch literal 3355 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 B6{r9J literal 0 HcmV?d00001 diff --git a/ffdemo/static/assets/images/overlay.png b/ffdemo/static/assets/images/overlay.png new file mode 100644 index 0000000000000000000000000000000000000000..027d6db116c625ff7821377240a1c3290d0a2a19 GIT binary patch literal 197 zcmeAS@N?(olHy`uVBq!ia0vp^A|TAc1|)ksWqE;=WQl7;NpOBzNqJ&XDuZK6ep0G} zXKrG8YEWuoN@d~6R2!h8bWaz@kcwMtE_-t^C~&w27XO#dF>MV{2>e+sE@xq7EG<~N zDLXr?dEyLqp2Q*pq2G;~Z&;UYl#V#$x1lFfpea|N4M=t#^*F=aX7D=r&<%c{M16za py9b2Ino@n51#+4Vd}cB6FnkEtU`mfU!Vk2P!PC{xWt~$(69AX&KBNEu literal 0 HcmV?d00001 diff --git a/ffdemo/static/assets/images/playback.png b/ffdemo/static/assets/images/playback.png new file mode 100644 index 0000000000000000000000000000000000000000..41d34e9c8d9e43edf4ba72ad8c98e4d621e88cb6 GIT binary patch literal 1519 zcmaJ>eM}Q)9InB2BDz1!mSrSvxAOxDw!OOorDrQ@k4h(HjJ6q@52)IASF1fq+_vU$? z-^csp4d>-9oIEjcqDrNjY|Ezd<^OBSJ0VW~{$VJ4M+u~?0?EM_OBJ-ps^&9%5sTQo zbP1c!(oE&b9@eB%#i5+DKq|23kZ#_qr4@`e;1y)HN@dCn2()_zDX9UY)afcJDzp`PEiabnaHG+vXb^-(Mriz%K8X%!eEw-83KZ*i zi<}^Fybn(&sy*bqq08#@R;O*=>oK%!?l>w(g@IQ|9`00JBs#8`Rwz0 z|5MoStQ1&XKI`YpM7Ml!#nY5j0%;LhTH-|~&zFu&G0($Gyx+qMh{chH*l9QCQ{o}K z-A>wkeu?(ESsP_WWd$wAF(jUa;a01~kZLeexEw5?(o!=BtHol}Ta8vKb%aat?lLdy zlSa7Ab8g0%T*V4rLC#FEB3I5bR+0B2LoJis*tMjMsW;4J#;zrOOs-BYMyDL?zYcn2 zOLmX49JQ?+j2fTy$!-^ATbCXm2&+`%Ue3#LW_5IQKur$Zy#poFVWl3nE``2t;rr7N zIS&0dfX87=VI&fP)@?BG8+_diw=ToZ5@-m(4_#21nUIhG)eGUmNx0by-L25s0O11I zD?mAU<;oS4$pl@8U=;;l?uDK;S{;kU;OJJU_dj^>ARG?Ev7NoW zz0e$j+T6IfxW2x=Xf&$V>*3BV_+UYNd^~J=A2zt4bT(Y-gtk46jg9d6rtRCe!>I%C zv9GGC3fk-8>=6*tgTWwpwXlN;g+g$69jviIY!D9BK*#~RIoMYQgZJU;dAN2F*5`pI z39f$!%ai3>0$3CPmaU&5$@8(;sQJ#o4d)J5(K&3=yjMzU$`{?#+`G_vxpQk>UEQ0H z|Bm53<)1ulWzCB|no+Z%TkC2WT(WBXnbmzs8DXw1l3lzviHJ`BrmLUB&un;-DOC@+ zN~1p!w$)B%o-TFC)UPnxEM0taOHQfA^LTC468d$*?lt}U29vjFdx=`+jfxBEv;7Cf z`|c;1>PZczZ5`|AjFwy0hf{V5XEtVMoGxsP9T=QSwpVvkr|9;?KsJ#)#aBN8zt}M@ zCA-?e#Kt{RM}O|M{xY+vZR5a;#gEi4)SaH$&DJe`)%$whrP+?AADv4Nezt3V{qm$) z`|l;Td)|vAK0NVheZsNpo0kVa3~60oywyFmyRiK3p@UCP%$ifTXWH$HzpIT|`&}*e zbJ$gEd~M*RzVn@h?&j;si;q>EB<8w`4m%>AKh1N9zY;K2PwaSd!`tVZlq=Yll}jC6 Hux#x=+t_Ok literal 0 HcmV?d00001 diff --git a/ffdemo/static/assets/images/prevMark.png b/ffdemo/static/assets/images/prevMark.png new file mode 100644 index 0000000000000000000000000000000000000000..9e84ff5e2efa281a6f7971afe0d951e9cd63ff23 GIT binary patch literal 1322 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 literal 0 HcmV?d00001 diff --git a/ffdemo/static/assets/images/tri.png b/ffdemo/static/assets/images/tri.png new file mode 100644 index 0000000000000000000000000000000000000000..374f61b599eab3ff9569488a270ebc89978e2cd6 GIT binary patch literal 993 zcmaJ=&ui0A91je14Ce5l;4gS&C&A|Dx~z}Ikv6F-m{Qt`J$NunUf0kiFD7p`+i}c^ zyLb@1sED3Lk0KMQf-rx<4$_0C{Q<&y^d!D?ZRf!nczN&p_;%Iw zlZ8dSO!q0ePK+O;|L%7FfNp1qQ6m*xCoL;PjOO4a1PY$DjLOJz)^2@5^9(b_y44z~ znOC8WJ+tY>goLuuP=s`$RWe! zCee_j6h)+>HJ?~<%8#Z83Ob7H&<%)-eUKg z0CJTyFfH5l6T62uO<3?FV)-^I=qgJUIM;Qcrern4$jY*j)kM({=H|q#uBAm?o|P3% zlLlNJ+p8Y($$;w|ac4$ylTq*j>RCsjdmB0V5PP7PGIU4BB97GSbDhz#NF%vC4Tet| z`&Xj}S9E%k<8W-LF&up4(`gUsSi{eqQ}jt(E9hD^{_*(B&PfK0>+c_Qet!7At$i_gylXy|Y`-UfvzI1Kp!5VmZ-|yq|`Fwx8@7pe~+#MU890dT3 zm6r4h-LKGf`65IAr#rr=YDtq$gEejo6W$&d8!SFTIc9u2+RNY*uS(~d? z&Sn?x!1+j_0xlvZjNCOZR3eq_>MGQZk9ih$A!Jo$&yuPeWvJl*!89i%Eg>VojL3;8 zIh)PSLP-z>UXXZEN{U$}B`Jacdml@q1$I-Z=*3Zxp-5MSZ>PN&0lQXCFiyqL@7 zVhu@3QbaP`@Q4{Dy>P0pprgp+re~p&uCi2ta~xaAYk46j$=Ot)sEJ}h$fad@u8__MnbcxY zF68@M9b4-z@<^X+pL6Gia^q2OeHvLuf%6#I#Q?joTe9K|&m}!nugA5A=aL!950w3*ivUO_{gKv9?-F#__$@zC($hF`C9a~@#NRfqnixF+-E-LcE3ztKY0G())APQZRph0UfoXYogQA5x3;&APbUv_{q=Wh qAJ*SJ1ml;S3-7_qwd(ZF2pD0&(zhEr{`BH^{0d8j6@7p4!LvU|Fg&^d literal 0 HcmV?d00001 diff --git a/ffdemo/static/assets/images/twitter.png b/ffdemo/static/assets/images/twitter.png new file mode 100644 index 0000000000000000000000000000000000000000..c0106dd7c7cd01351d6a70c355272f7a1edcfff1 GIT binary patch literal 2185 zcmaJ@dr%X19>?NY>YO*%dNZi4b1~kj70F`*gruNV0vZL6Q|5}j(-O!cIhuz_z<_u< z+S3D31VN}E^(_xE$fJND!l1SuASm+i0pz*a_ijicILEcOv0(d$-p=g)9`pHpzMt>! z``y{ij@`O(!TcrjeSCZtNHZ&xSl_sX=O&w9B! z5oAe~vfbb|P$tjVa~}-z@$qFV;^Ou3G11~AwUQ(AVmJn+hGF~ogsn4ZWJ#%@o|Oph zR;a?+RO@XvOCb+ue-<3Wi_t`YDT+;*T5x;j*0`k1)FhFdy>2Zl%phh2l%QV5GAO@P z>BNR`_A6a6Gxu(D*{oL(eQG%SCsXk;v8)KS7Gwo;c!5d05FRUp&*2M#ghJsO7Qo~4 zxIBQ%2Lky*u>cVBc&wR=%|z46lf~O4Q8TfaRX96Euh)pV+`W7Ea`p;1YVB?=UnCNF zH2@%xK?LeDRC<{qP^DWws~`b&Nm_+QuTZO4UPW1=I!zzWW}N<%f>QHZR;8OcCg#An z2APJ-=kUBK%>rX${y$Wye2v!Ww}C(VeWS20E<*!yw}Co!nl_0kT=H^nD2+Hm3(E9r zZJb*D439ECzI76$W0 zA;5YdNGOu<`H{R0lHj0FASxn4B#074NrGm%5_M9V5>)ADx$>X6OqJfk^|pdi!$g*V zT17f2kJ74@tXCspugy%;^MM&PNH^Y^`!OILc@~vDhV}|Q3>@NyEyTx>mclx?* znZ@hI2USeBYnisLd2e$s^S1cJM#n`4ba;9(JjFrMPABPf;xwH+{G3;GC*SG-8lhbS zF9QzO14eUhWmQ0(!%WjT567R7l%S1lrku63lXK*2UaWcmYov=7c?8pTeJL^D8 zA*2o`k6WNTt3z;$J_Dgw5d5?qsX&SA81{_=J#KMag7HEdobdSNAVIbg&Rh$$F|W23 zCo53`L6Bn>s1~Pg;1qEBZjJ?-n3yn(T1p@^i*yHE8aZKg7%|L<5j_*`A{%@e!3(VL zRfH(EBj;eO+<`tO$oz3zXnAiHh8t<;bBZp5(AF0(jy^O2*-e!=Rb_Xyc6L4^sbPY= ziV|64)}y0z>Yd?0Rf)2vzlo&5@_K_E+c#$Y21W-6l7IL{PVdu|nb$tfsu>~3uS~Ww z7!4@zk8T(+lVptpDTUDMIN5HoorBQ_81V=vy6n&>Nj)X0A%YwtsFOBFvDG2F+rVXsW!qXIN5%K zpnwaHf+~lAiYK{NX!zN)dfItna6BTnMtahC!GUpe+Y)*nwz%9C2!3!FGSqctHs3FL z^sEmj&caAFiXOdMk!yy|coT%BT~x}@RG|$ivctUWW+1Pgt(?Ww?tgX6X;f;`gs;2eUThtWeOd)5;; z^v-119nV9YD0LwBAP0(~X?BcLIkM-uIqkXm$bh9~(sLQZFT&V9(p`fQH*m7YH+W%K`yZwX$!DHd~KM1D} zzw_%KyRYm7H}$X947>ZoYL_ABkFJ<&X!f6mc5iAu>1Wvm74 zr^Y|YgR--i?wI&+-l5G?i|2X{q^A6<(0I0~-6&n}JO3ZT%>utO_jbfbzhAMYy7@ml z|FCwKWXyi@g>WaE&Qzs^KWV#^dN}|?)S_lQ|Kwz3%>87wm%n{Onxu_-q$hp z$DWaqk(9w(x^z}Y<0^K?NQrezkni%+izo8)8~^%!_NQA|eq}_8{F3!va$l|W^M@a& zE?v4bRq3ah3yiL{|0y}4ytPnzm-7FB-Y$E3yWs22LC$R7w>k z3WlmUfuaa1G8AP{K??oT`}_8Azcc-CdH}HX zR|+-&0Fy}oP{3!KehFaw6g*)H00R)Z@Nxi{9)P>V$>kCTi6oK{Ieak}B=W=%Ny(9r zC`2*|SiVvz;qZi@9LEJW3PfJ`;i_6ZPQdrVucfibEQv1|CkRZHg5jyb5xi6(kIu)h zT!CAzWGEpC1j;!$C6pkNF_d2TS#pNXp9z!jxY-oB&TYl3jV^ zrDWVv3X$UG?&0CF1V<%P$RskAM4`G;JQ!|N2APceV0c|MDSrba%rD?WE#1ir|6!=) z0cv2l25e%xU0V8_{YPS1M2Z9?g@H1$LdpYm=?iAS5{9o7 zW?)ku`dx3NSi9jIq5lDS~q#;uG8KaO$lrh@K$k@mTZH}Hb zbF`_MxtS^2($dPx(h@s&?p&HJEvXd1zlxU+k5_3d2_+5%s2L&Fr2uWETy%Js{b^c6G!WH;bMX^mN(GkzBOi5IJZP z5dW1k37*){`kU3)EfTM)h>-Dkv&6!FgX zRW@&akluRlt|G>q-=$9Ikz=fH5oxEcyCJVOYZzxY zI;WJ?R^4Me+N+?~-}lj8ZYoeI-(2doog}174jgkD`@+A4rjiMQ6roEO?2E|dAk zET_i>HXr3~E$OCi@lZU&;*eTb1;?yCNTNQL4xKn##?yqYRW+LujGh_M<{A>$q*OE> z_BJ`1C%xvySad8(`G=RO`-Q;A-{B1HPR3Oq`}tcJCv=@f+U?)g-5Gt^7~naYZTDh- zY2j}f_iauQTdS{}zkDZJZIK5)b}!x1aQshO)Q0%})j5{}_sVu`sfhj;ZHVBK<0u~r zjd9MV@Zt_iN{s9As__Zyd{MSZRrdkofRin1X0hcVcH8({@OAWt*Lyc6pfsY#6Z@D2 zNy4uX8=20ln#fkE5?Jg`ljrB+I%T|J$Dc@c3tf*u#14LYJ&7|Jvby=vx$tc8THe(s z6MHVbA3M+K@nBDG&oMM^^1DL=pPnMe84-8yuBU5V$4)pR-cmB$c`*-ed8X!=Pc(kJ zfBg-S{#eP1@U(1)#nl`w?&Z<<{Z+g6JuV$*Ss#3QcyJH-$9?*=13weSwE<_oS!I$? zbDy7u#{aJBDm7#&tap$+An*l=>;y5MF^Qu@Iu~bvV?yl>+(s zSGj@1f36S2s95)UhTaCbO5YT@-Vy|~u_9br52>4sG%f@KT0qs-JqC!5WAg@TSlbez zd3_ZxY~?{sPh&V%XO?Utbn}A7dI)KIHC+x5%$He4Qoc`p-$uHBX{s;2R5h`!a5S^E z=jRDk#>3upa*#ALc>zYW0NtoA&$&?5@7`!u(TTf=t*#7`W#>e9si7SYf?CxSy|huCOB5zKd`1k7kVADNvP~ zyt$xk8rc1gwJ&j71&K#nkpKVy literal 0 HcmV?d00001 diff --git a/ffdemo/static/assets/images/world_eater.jpg b/ffdemo/static/assets/images/world_eater.jpg new file mode 100644 index 0000000000000000000000000000000000000000..bf406ef40d0b2aa4c26ee6684856aea8eb353e4f GIT binary patch literal 2142 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 0 ) { + $( '#markapp' ).markApp( 'unloadModule', 'intro' ); + + $( '#markapp' ).markApp( 'addModule', { 'capture': modOptions } ); + } else { + // template is not yet loaded, so lets start fresh + // load the template + this.partial( 'makemark_sammy.html' ) + .then( function() { + // init the capture interface, specifying that the intro should not be shown + $( '#markapp' ).markApp( 'addModule', { 'capture': modOptions } ); + $( '#sammy' ).css( 'zIndex', '' ); + } ); + } + } ); + + // MODERATION PAGE + this.get( '#/moderate', function( context ) { + // unload the other modules if they're loaded + $( '#markapp' ).markApp( 'unloadModule', 'intro' ); + $( '#markapp' ).markApp( 'unloadModule', 'capture' ); + this.partial( '/en/moderate_sammy.html' ) + .then( function() { + // load the linear module + $( '#markapp' ).markApp( 'addModule', { 'linear': { 'is_flagged': true, 'linear_root': 'moderate' } } ); + } ); + } ); + // Moderation via direct reference + this.get( '#\/moderate\/(.*)', function( context ) { + // unload the other modules if they're loaded + $( '#markapp' ).markApp( 'unloadModule', 'intro' ); + $( '#markapp' ).markApp( 'unloadModule', 'capture' ); + if ( $( '#linear' ).size() > 0 ) { + // already setup, just load the new reference mark into the module + $( '#markapp' ).markApp( 'addModule', { 'linear': { 'is_flagged': true, 'linear_root': 'moderate', 'reference_mark': context.params['splat'][0], 'playback': context.params['playback'] } } ); + } else { + // show all the signatures + this.partial( '/en/moderate_sammy.html' ) + .then( function() { + $( '#sammy' ).css( 'zIndex', '' ); + $( '#markapp' ).css( { 'zIndex': 100, 'cursor': 'default' } ); + // load up the visualization + $( '#markapp' ).markApp( 'addModule', { 'linear': { 'is_flagged': true, 'linear_root': 'moderate', 'reference_mark': context.params['splat'][0], 'playback': context.params['playback'] } } ); + } ); + } + } ); + + // visualization + // visualization with country filtering + this.get( '#/linear/country/:country_code\/(.*)', function( context ) { + // unload the other modules if they're loaded + $( '#markapp' ).markApp( 'unloadModule', 'intro' ); + $( '#markapp' ).markApp( 'unloadModule', 'capture' ); + if ( $( '#linear' ).size() > 0 ) { + // already setup, just load the new reference mark into the module + $( '#markapp' ).markApp( 'addModule', { 'linear': { 'country_code': context.params['country_code'], 'reference_mark': context.params['splat'][0] } } ); + } else { + this.partial( 'linear_sammy.html' ) + .then( function() { + $( '#sammy' ).css( 'zIndex', '' ); + $( '#markapp' ).css( { 'zIndex': 100, 'cursor': 'default' } ); + // load up the visualization + $( '#markapp' ).markApp( 'addModule', { 'linear': { 'country_code': context.params['country_code'], 'reference_mark': context.params['splat'][0] } } ); + } ); + } + } ); + // visualization without country filtering + this.get( '#\/linear\/(.*)', function( context ) { + // unload the other modules if they're loaded + $( '#markapp' ).markApp( 'unloadModule', 'intro' ); + $( '#markapp' ).markApp( 'unloadModule', 'capture' ); + if ( $( '#linear' ).size() > 0 ) { + // already setup, just load the new reference mark into the module + $( '#markapp' ).markApp( 'addModule', { 'linear': { 'reference_mark': context.params['splat'][0], 'playback': context.params['playback'] } } ); + } else { + // show all the signatures + this.partial( 'linear_sammy.html' ) + .then( function() { + $( '#sammy' ).css( 'zIndex', '' ); + $( '#markapp' ).css( { 'zIndex': 100, 'cursor': 'default' } ); + // load up the visualization + $( '#markapp' ).markApp( 'addModule', { 'linear': { 'reference_mark': context.params['splat'][0], 'playback': context.params['playback'] } } ); + } ); + } + } ); + + // event handlers + this.bind( 'run', function() { + // add an instance of markApp + $( '#markapp' ) + .markApp() + // and privide it a way of accessing the sammy app + .data( 'markApp-context' )['app'] = app; + } ); + + // other stuff + this.swap = function( content ) { + this.$element().fadeOut( 'fast', function() { + $( this ).html( content ).fadeIn( 'normal' ); + $( '#markapp' ).trigger( 'ready' ); + } ); + $( '#markapp' ).trigger( 'swap' ); + + }; + + this.use( 'Template' ); + + } ); + + $( document ).ready( function () { + function browserSupportsRequiredFeatures() { + // detect canvas support + return !!document.createElement('canvas').getContext; + } + if ( browserSupportsRequiredFeatures ) { + // remove the placeholder content + $( '#fallback' ).remove(); + // run the app + app.run( '#/' ); + } + + // Try binding click event to locale here + $("#current-locale").click(function () + { + $(this).parent().find("ul").toggle(); + $(this).toggleClass("selected"); + return false; + }); + + } ); //document ready +} )( jQuery ); \ No newline at end of file diff --git a/ffdemo/static/assets/js/bundled.js b/ffdemo/static/assets/js/bundled.js new file mode 100644 index 0000000..38b940d --- /dev/null +++ b/ffdemo/static/assets/js/bundled.js @@ -0,0 +1,4419 @@ +( function( $ ) { + + // TODO -- support loose augmentation of $.markApp, in case module code is ever loaded before this. + + // The markApp namespace is used for storing available module code, instances of markApp, and any context free functions + $.markApp = { + // holds all available modules + modules: {}, + // we keep track of all instances of markApp in here + instances: [], + // helper functions go here + fn: {} + }; + + // Creates a markApp instances with an element, and handles function calls on the instance + $.fn.markApp = function( options ) { + var $this = $( this ); + // The context each markApp instance is stored as data on that element + var context = $this.data( 'markApp-context' ); + // On first call, we need to set things up, but on all following calls we can skip right to the API handling + if ( !context || typeof context == 'undefined' ) { + context = { + // useful variables -- jquery objects get prefixed with $ + app: null, + $container: $this, + frameCount: 0, + width: 0, + height: 0, + minWidth: 300, + minHeight: 400, + countries: [], + mouseX: null, + mouseY: null, + mouseDown: false, + mouseIn: false, + modules: {}, + usersMark: null, + defaultErrorMsg: $( '#default-error-msg' ).text(), + defaultLoadingMsg: $( '#default-loading-msg' ).text(), + locale: ( window.location.pathname.split("/").length > 1 ) ? window.location.pathname.split("/")[1] : "en", // locale -- set by URL, default to 'en' + instance: $.markApp.instances.push( $this ) - 1, // store this instances index in the global instace array + // events + evt: { + resize: function( e ) { + var availableWidth = $( window ).width(); + var availableHeight = $( window ).height() - ( $( 'header' ).height() + $( '#callout-boxes' ).height() ); + if ( availableWidth < context.minWidth ) availableWidth = context.minWidth; + if ( availableHeight < context.minHeight ) availableHeight = context.minHeight; + context.$container.parent().width( availableWidth ); + context.$container.parent().height( availableHeight ); + context.width = availableWidth; + context.height = availableHeight; + // resize any elements with the autoResize class + $( '.autoResize' ) + .height( availableHeight ) + .width( availableWidth ) + .trigger( 'resize.markApp', [availableWidth, availableHeight] ); + }, + mousemove: function( e ) { + context.mouseX = e.layerX; + context.mouseY = e.layerY; + }, + mousedown: function( e ) { + if( 'preventDefault' in e ) e.preventDefault(); + context.mouseDown = true; + }, + mouseup: function( e ) { + if( 'preventDefault' in e ) e.preventDefault(); + context.mouseDown = false; + }, + mouseover: function( e ) { + if( 'preventDefault' in e ) e.preventDefault(); + context.mouseIn = true; + }, + mouseout: function( e ) { + if( 'preventDefault' in e ) e.preventDefault(); + context.mouseX = null; + context.mouseY = null; + context.mouseIn = false; + } + }, + // publicly accessible functions + public_fn: { + addModule: function( context, data ) { + var moduleName, + moduleOptions = {}; + + if( typeof data == "string" ) { + moduleName = data + } else if( typeof data == "object" ) { + for ( var moduleData in data ) { + moduleName = moduleData + moduleOptions = data[moduleData]; + break; + } + } + + if( $.markApp.modules[moduleName] ) { + // give this module it's own space for storing stuff if it doesn't already have it + context.modules[moduleName] = context.modules[moduleName] || {}; + // if it has an init function, run it + if( 'init' in $.markApp.modules[moduleName].fn ) + $.markApp.modules[moduleName].fn.init( context, moduleOptions ); + } + }, + unloadModule: function( context, moduleName ) { + if( moduleName == "all" ) { + // unload all the currently loaded modules + for ( moduleName in context.modules ) { + // if it has a deinit function, run it + if( 'deinit' in $.markApp.modules[moduleName].fn ) + $.markApp.modules[moduleName].fn.deinit( context ); + // remove it from our modules + delete context.modules[moduleName]; + } + } else if ( moduleName in context.modules ) { + // if it has a deinit function, run it + if( 'deinit' in $.markApp.modules[moduleName].fn ) + $.markApp.modules[moduleName].fn.deinit( context ); + // remove it from our modules + delete context.modules[moduleName]; + } + } + }, + // internal functions + fn: { + // trigger event handlers on modules + trigger: function( eventName, eventObj, args ) { + + // Add some assurances to our eventObj + if( typeof eventObj == "undefined" ) + eventObj = { 'type': 'custom' }; + + // trigger the global handlers first + if ( eventName in context.evt ) { + // if it returns false, stop the train + if ( context.evt[eventName]( eventObj ) == false ) { + return false; + } + } + + // run the event handler on each module that's got it + for( var module in context.modules ) { + if( module in $.markApp.modules && + 'evt' in $.markApp.modules[module] && + eventName in $.markApp.modules[module].evt ) { + $.markApp.modules[module].evt[eventName]( context, eventObj, args ); + } + } + }, + loop: function( e ) { + // reset the delay + setTimeout( function() { context.fn.loop( ); }, 42 ); + // incremenet the counter + context.frameCount++; + // dispatch the event + context.fn.trigger( 'loop', {}, [] ); + }, + // useful for delayed loading of the country data + withCountryCodes: function ( callback ) { + if ( 'US' in context.countries ) { + callback( context.countries ); + } else { + $.ajax( { + 'url': '/media/assets/js/vendor/country_codes.json', + 'dataType': 'JSON', + 'success': function ( data ) { + context.countries = data; + for( var i = 0; i < data.length; i++ ) { + context.countries[ data[i].code ] = data[i].name; + } + callback( context.countries ); + }, + 'error': function () { + // handle error loading countries + } + } ); + } + }, + showLoader: function( msg, custom_class ) { + var custom_class = custom_class || ''; + var msg = typeof msg === "string" ? msg : context.defaultLoadingMsg; + // append our loader + var $loader = $( '
' ) + .width( context.width ) + .height( context.height ) + .hide() + .addClass( 'overlay-wrapper autoResize' ) + .addClass( custom_class ) + .attr( 'id', 'markapp-loader' ) + .append( $( '
' ) + .text( msg ) ); + context.$container + .append( $loader ); + $loader.fadeIn( 'fast' ); + }, + hideLoader: function( ) { + $( '#markapp-loader' ).fadeOut( 'fast', function() { + $( this ).remove(); + } ); + }, + showError: function ( msg ) { + // TODO -- translate this default string + var msg = typeof msg === "string" ? msg : context.defaultErrorMsg; + var $error = $( '
' ) + .width( context.width ) + .height( context.height ) + .hide() + .click( function ( e ) { + e.preventDefault(); + context.fn.hideError(); + } ) + .addClass( 'overlay-wrapper autoResize' ) + .attr( 'id', 'markapp-error' ) + .append( $( '
' ) + .attr( 'id', 'markapp-error-content' ) + .append( $( '

' ).text( msg ) ) ); + context.$container + .append( $error ); + $error.fadeIn( 'fast' ); + }, + hideError: function ( ) { + $( '#markapp-error' ).fadeOut( 'fast', function() { + $( this ).remove(); + } ); + }, + storeData: function( key, value ) { + if ( typeof localStorage != 'undefined' ) { + // use localStorage if it exists + try { + if( typeof value === "object" ) { + value = JSON.stringify( value ); + } + localStorage.setItem( key, value ); + } catch (e) { + if ( e == QUOTA_EXCEEDED_ERR ) { /* data wasn't successfully saved due to quota exceed */ } + } + } else { + // use cookies + // todo -- impliment cookie fallback....maybe + } + }, + getData: function( key ) { + if ( typeof localStorage != 'undefined' ) { + var item = localStorage.getItem( key ); + if( item ) { + if ( item[0]=="{" ) item = JSON.parse( item ); + return item; + } else { + return false; + } + } + } + } + }; + // bindings + $( window ) + .delayedBind( 300, 'resize', function( e ) { + return context.fn.trigger( 'resize', e ); + } ) + .bind( 'keydown keypress keyup', function( e ) { + return context.fn.trigger( e.type, e ); + } ) + .trigger( 'resize' ); + context.$container + .bind( 'mousemove mousedown mouseup mouseover mouseout ready swap', function( e ) { + return context.fn.trigger( e.type, e ); + } ); + // start the loop + context.fn.loop(); + } + + // Convert the arguments to an array to make this easier + var args = $.makeArray( arguments ); + + // handle public function calls + if ( args.length > 0 ) { + var call = args.shift(); + if ( call in context.public_fn ) { + context.public_fn[call]( context, typeof args[0] == 'undefined' ? {} : args[0] ); + } + } + + return $this.data( 'markApp-context', context ); + }; + +}( jQuery ) ); +( function( $ ) { + + // support loose augmentation + markApp = $.markApp = $.markApp || {}; + modules = $.markApp.modules = $.markApp.modules || {}; + + modules.linear = { + defaults: { + reference_mark: null, // optional reference mark -- will init the visualization on this mark if passed + country_code: null, // optional country code -- only loads marks with this county code if present + playback: false, // set this to true to immediately play back the initial mark + is_flagged: false, // optional flagged flag -- only loads marks that are flagged (for use within moderation) + linear_root: 'linear' + }, + config: { + marks: {}, // all marks held here + flaggings: {}, // loaded from local storage, and used for some local checking that users are flagging things multiple times + orderedMarks: [], // array of mark references, arranged by id + leftBuffer: [], // mark buffers for the left and right of the marks currently in the scene + rightBuffer: [], + bufferSize: 20, // amount of marks to keep in a buffer + bufferMinSize: 5, // min amount of marks to keep in a buffer before forcing a refill + scene: null, + // handles camera changes -- could probably be shifted into the camera object + cameraChange: {}, + tweens: {}, + layerManager: null, // layer manager + initialized: false, // boolean flag to indicate if this module is fully setup + requestingMarks: false, // prevents more mark request from being sent when set to true + moreLeft: true, // Flags for the begining and end of the line. Assume we're not there at the start + moreRight: true, + hoverMark: null, // holds the mark currently being hovered over + currentMark: null, + + playbackTimes: {}, // used for storing mark playback times by reference + eventChange: false // flag to tell the render loop if the mouse is causing changes that need rendered + }, + evt: { + ready: function ( context, e ) { + modules.linear.fn.initInterface( context ); + }, + resize: function( context, e ) { + context.modules.linear.eventChange = true; + context.modules.linear.layerManager.resizeAll( context.width, context.height ); + }, + keydown: function ( context, e ) { + var lC = context.modules.linear; + switch( e.keyCode ) { + case 38: + // arrow up + e.preventDefault(); + lC.cameraChange.aZ = 10; + break; + case 40: + // arrow down + e.preventDefault(); + lC.cameraChange.aZ = -10; + break; + case 39: + // arrow right -- pan the camera to the right + e.preventDefault(); + // next mark + lC.cameraChange.aX = 10; + // hide mark info + modules.linear.fn.hideMarkInformation( context ); + break; + case 37: + // arrow left -- pan the camera to the left + e.preventDefault(); + // prev mark + lC.cameraChange.aX = -10; + // hide mark info + modules.linear.fn.hideMarkInformation( context ); + break; + } + }, + keyup: function( context, e ) { + var lC = context.modules.linear; + switch( e.keyCode ) { + case 38: + case 40: + // arrow up + e.preventDefault(); + lC.cameraChange.aZ = 0; + break; + case 39: + case 37: + e.preventDefault(); + lC.cameraChange.aX = 0; + break; + } + }, + // prevent our bound keys from firing native events + keypress: function( context, e ) { + switch( e.keyCode ) { + case 38: + case 40: + case 39: + case 37: + e.preventDefault(); + break; + } + }, + mousedown: function( context, e ) { + var lC = context.modules.linear; + if( mark = modules.linear.fn.hitTest( context, context.mouseX, context.mouseY ) ) { + // if the mark hasn't changed from what's in our URL, we just need to show the details again + if ( mark == lC.currentMark ) { + modules.linear.fn.centerCurrentMark( context, function() { + modules.linear.fn.showMarkInformation( context ); + } ); + // otherwise update the URL, and the module will jump to the referenced mark + } else if ( lC.country_code ) { + context.app.setLocation( '#/' + lC.linear_root + '/country/' + lC.country_code + '/' + mark.reference ); + } else { + context.app.setLocation( '#/' + lC.linear_root + '/' + mark.reference ); + } + } + }, + mousemove: function( context, e ) { + var lC = context.modules.linear; + lC.eventChange = true; + // hover test + if( mark = modules.linear.fn.hoverTest( context, context.mouseX, context.mouseY, lC.hoverMark ) ) { + // reset the old hovered mark + if( lC.hoverMark ) lC.hoverMark.color = lC.hoverMark.contributor_name ? '171,73,27' : '0,0,0'; + // store this hover mark + lC.hoverMark = mark; + if ( lC.hoverMark.reference == lC.currentMark.reference && lC.hoverMark.contributor_name && $( '#mark-information' ).is( ':visible' ) ) { + $( '#contributor-quote-box' ) + .fadeIn( 'fast' ) + .css( { left: context.mouseX - 15, top: context.mouseY - $( '#contributor-quote-box' ).height() - 15 } ); + } else { + lC.hoverMark.color = '0,139,211'; + } + } else if ( lC.hoverMark ) { + lC.hoverMark.color = lC.hoverMark.contributor_name ? '171,73,27' : '0,0,0'; + lC.hoverMark = null; + // fade out any contributor quotes we might happen to be showing + $( '#contributor-quote-box:visible' ).fadeOut( 'fast' ); + } + + }, + loop: function ( context ) { + var lC = context.modules.linear, + dLayer = lC.layerManager.layers['drawingLayer']; + + // update the position of the camera + var lastCameraPosition = {x: lC.scene.camera.position.x, y: lC.scene.camera.position.y, z: lC.scene.camera.position.z }; + // two ways this can happen + if( 'cameraEase' in lC.tweens ) { + // with a tween + TWEEN.update(); + } else { + // or with physics + lC.cameraChange.vZ += lC.cameraChange.aZ; + lC.cameraChange.vX += lC.cameraChange.aX; + lC.cameraChange.vX *= .93; + lC.cameraChange.vZ *= .93; + lC.scene.camera.position.x += lC.cameraChange.vX; + lC.scene.camera.position.z += lC.cameraChange.vZ; + // bring the Y and Z positions close to the current mark + var mark = modules.linear.fn.closestMark( context ); + if ( mark ) { + var dY = lC.scene.camera.position.y - ( mark.position.y + ( mark.bHeight / 2 ) ); + // if Z is not changing, try to maintain + // if( ) + // var dZ = lC.scene.camera.position.z - + if ( dY != 0 && Math.abs(dY) >= 10) lC.scene.camera.position.y += ( dY > 0 ? -10 : 10 ); + } + // TODO: fix the Z index while navigating the line + } + + // if nothing has changed, return RIGHT NOW + if( !lC.eventChange && + lastCameraPosition.x == lC.scene.camera.position.x && + lastCameraPosition.y == lC.scene.camera.position.y && + lastCameraPosition.z == lC.scene.camera.position.z ) { + return false; + } + + // modified hover test -- only removes hover states + if( lC.hoverMark && ! modules.linear.fn.hoverTest( context, context.mouseX, context.mouseY, lC.hoverMark ) ) { + lC.hoverMark.color = lC.hoverMark.contributor_name ? '171,73,27' : '0,0,0'; + lC.hoverMark = null; + // fade out any contributor quotes we might happen to be showing + $( '#contributor-quote-box:visible' ).fadeOut( 'fast' ); + } + + dLayer.clean(); + // update scene marks && add more as needed + modules.linear.fn.updateScene( context, lC.scene.camera.position.x - 10000, lC.scene.camera.position.x + 10000 ); + // cleanup the scene and tend to empty buffers + modules.linear.fn.updateBuffers( context, lC.scene.camera.position.x - 10000, lC.scene.camera.position.x + 10000 ); + + // render the scene + Mark.renderer.renderScene( lC.scene, { 'cursor': {x: context.mouseX, y: context.mouseY}, width: context.width, height: context.height } ); + // ark.renderer.renderScene( lC.scene, dLayer.context, { x: context.mouseX, y: context.mouseY }, context.width, context.height, lC.playbackTimes ); + + // set the eventChange flag back to false -- a mouse/keyboard or playback event will need to set it back to true again before we'll render because of it + lC.eventChange = false + + // cleanup playback times if necissary + for( mark in lC.scene.timers ) { + var now = ( new Date() ).getTime(); + if( lC.scene.timers[mark].end < now ) { + delete lC.scene.timers[mark]; + } + lC.eventChange = true; + } + + } + }, + fn: { + init: function( context, options ) { + var lC = context.modules.linear; + // if this modules has already been initialized, update the options + if ( '$linear' in context.modules.linear ) { + // before we merge options, check if we need to dump our current data + if ( options['country_code'] != lC.country_code ) { + modules.linear.fn.dumpAllMarks( context ); + } + // now our options into our context + $.extend( lC, lC, options ); + // since merging won't replace null or undefined values, make sure we clean up after it + for( option in modules.linear.defaults ) { + if ( options[option] == null ) lC[option] = modules.linear.defaults[option]; + } + // update the interface + modules.linear.fn.updateInterface( context ); + // load our marks + modules.linear.fn.initMarks( context ); + } else { + // allow defaults to be overriden + $.extend( lC, modules.linear.defaults, options ); + // but not the cofig + $.extend( lC, lC, modules.linear.config ); + + // DOM setup + lC.$linear = $( '

' ) + .addClass( "linear-container" ); + context.$container + .append( lC.$linear ); + + // scene setup + lC.scene = new Mark.scene(); + lC.cameraChange = { aX: 0, aY: 0, aZ: 0, vX: 0, vY: 0, vZ: 0 }; + // layer setup + lC.layerManager = new Mark.layerManager( lC.$linear.get( 0 ) ); + lC.layerManager.addLayer( 'drawingLayer' ); + lC.scene.canvasContext = lC.layerManager.layers['drawingLayer'].context; + // trigger resize so our new layers are sized to fit + context.fn.trigger( 'resize' ); + + lC.initialized = true; + } + // load flaggings + lC.flaggings = context.fn.getData( 'markFlaggings' ) || {}; + }, + deinit: function( context ) { + var lC = context.modules.linear; + lC.$linear.fadeOut( 'fast', function () { + // remove all our layers + lC.layerManager.removeAll(); + lC.$linear.remove(); + lC.initialized = false; + } ); + }, + initInterface: function( context ) { + var lC = context.modules.linear; + // hide anything that needs data loaded into it + $( '#stats' ) + .hide( ); + // enable the controls + $( '#mark-browsing-zoom-in a, #mark-browsing-zoom-out a, #mark-browsing-next a, #mark-browsing-prev a' ) + .click( function( e ){ e.preventDefault(); } ) + .bind( 'mouseup mouseout', function( e ) { + e.preventDefault(); + if ( $( this ).data( 'mouseDown' ) ) { + if( $( this ).is( '#mark-browsing-zoom-in a, #mark-browsing-zoom-out a' ) ) { + context.modules.linear.cameraChange.aZ = 0; + } else if( $( this ).is( '#mark-browsing-next a, #mark-browsing-prev a' ) ) { + context.modules.linear.cameraChange.aX = 0; + } + $( this ).data( 'mouseDown', false ); + } + } ) + .bind( 'mousedown', function( e ) { + e.preventDefault(); + $( this ).data( 'mouseDown', true ); + if( $( this ).is( '#mark-browsing-zoom-in a, #mark-browsing-zoom-out a' ) ) { + context.modules.linear.cameraChange.aZ = $( this ).is( '#mark-browsing-zoom-in a' ) ? 10 : -10; + } else if ( $( this ).is( '#mark-browsing-next a, #mark-browsing-prev a' ) ) { + context.modules.linear.cameraChange.aX = $( this ).is( '#mark-browsing-next a' ) ? 10 : -10; + // hide the mark information + modules.linear.fn.hideMarkInformation( context ); + } + } ); + // populate our country filter select box + context.fn.withCountryCodes( function ( countryCodes ) { + var $select = $( '#country-select' ); + for( var i = 0; i < countryCodes.length; i++ ) { + var $option = $( '