├── runtime.txt ├── db_repository ├── __init__.py ├── versions │ ├── __init__.py │ └── 001_migration.py ├── README ├── manage.py └── migrate.cfg ├── Procfile ├── app.db ├── app ├── static │ ├── img │ │ ├── tile.png │ │ ├── favicon.ico │ │ ├── posters.jpg │ │ ├── no_poster.png │ │ ├── posters_0.jpg │ │ ├── posters_1.jpg │ │ ├── posters_2.jpg │ │ ├── posters_3.jpg │ │ ├── posters_4.jpg │ │ ├── posters_5.jpg │ │ ├── posters_6.jpg │ │ ├── posters_7.jpg │ │ ├── posters_8.jpg │ │ ├── posters_9.jpg │ │ ├── thumbnail.png │ │ ├── tile-wide.png │ │ ├── apple-touch-icon.png │ │ └── striped_background.png │ ├── fonts │ │ ├── FontAwesome.otf │ │ ├── social-share-kit.eot │ │ ├── social-share-kit.ttf │ │ ├── social-share-kit.woff │ │ ├── fontawesome-webfont.eot │ │ ├── fontawesome-webfont.ttf │ │ ├── fontawesome-webfont.woff │ │ ├── fontawesome-webfont.woff2 │ │ ├── glyphicons-halflings-regular.eot │ │ ├── glyphicons-halflings-regular.ttf │ │ ├── glyphicons-halflings-regular.woff │ │ ├── glyphicons-halflings-regular.woff2 │ │ └── social-share-kit.svg │ ├── js │ │ ├── plugins.js │ │ ├── vendor │ │ │ ├── angular-sanitize.min.js │ │ │ ├── angular-sanitize.min.js.map │ │ │ └── bootstrap.min.js │ │ └── main.js │ └── css │ │ ├── main.css │ │ ├── normalize.css │ │ ├── social-share-kit.css │ │ └── font-awesome.min.css ├── models.py ├── __init__.py ├── views.py ├── templates │ ├── 404.html │ ├── index.html │ └── base.html └── movie_info.py ├── run.py ├── requirements.txt ├── local_config_example.py ├── db_create.py ├── config.py ├── db_migrate.py ├── .gitignore ├── LICENSE └── README.md /runtime.txt: -------------------------------------------------------------------------------- 1 | python-3.4.0 -------------------------------------------------------------------------------- /db_repository/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: gunicorn run:app -------------------------------------------------------------------------------- /db_repository/versions/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/billmei/movie-info/HEAD/app.db -------------------------------------------------------------------------------- /app/static/img/tile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/billmei/movie-info/HEAD/app/static/img/tile.png -------------------------------------------------------------------------------- /run.py: -------------------------------------------------------------------------------- 1 | #!flask/bin/python 2 | from app import app 3 | 4 | if __name__ == '__main__': 5 | app.run() -------------------------------------------------------------------------------- /app/static/img/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/billmei/movie-info/HEAD/app/static/img/favicon.ico -------------------------------------------------------------------------------- /app/static/img/posters.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/billmei/movie-info/HEAD/app/static/img/posters.jpg -------------------------------------------------------------------------------- /app/static/img/no_poster.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/billmei/movie-info/HEAD/app/static/img/no_poster.png -------------------------------------------------------------------------------- /app/static/img/posters_0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/billmei/movie-info/HEAD/app/static/img/posters_0.jpg -------------------------------------------------------------------------------- /app/static/img/posters_1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/billmei/movie-info/HEAD/app/static/img/posters_1.jpg -------------------------------------------------------------------------------- /app/static/img/posters_2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/billmei/movie-info/HEAD/app/static/img/posters_2.jpg -------------------------------------------------------------------------------- /app/static/img/posters_3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/billmei/movie-info/HEAD/app/static/img/posters_3.jpg -------------------------------------------------------------------------------- /app/static/img/posters_4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/billmei/movie-info/HEAD/app/static/img/posters_4.jpg -------------------------------------------------------------------------------- /app/static/img/posters_5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/billmei/movie-info/HEAD/app/static/img/posters_5.jpg -------------------------------------------------------------------------------- /app/static/img/posters_6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/billmei/movie-info/HEAD/app/static/img/posters_6.jpg -------------------------------------------------------------------------------- /app/static/img/posters_7.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/billmei/movie-info/HEAD/app/static/img/posters_7.jpg -------------------------------------------------------------------------------- /app/static/img/posters_8.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/billmei/movie-info/HEAD/app/static/img/posters_8.jpg -------------------------------------------------------------------------------- /app/static/img/posters_9.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/billmei/movie-info/HEAD/app/static/img/posters_9.jpg -------------------------------------------------------------------------------- /app/static/img/thumbnail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/billmei/movie-info/HEAD/app/static/img/thumbnail.png -------------------------------------------------------------------------------- /app/static/img/tile-wide.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/billmei/movie-info/HEAD/app/static/img/tile-wide.png -------------------------------------------------------------------------------- /app/static/fonts/FontAwesome.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/billmei/movie-info/HEAD/app/static/fonts/FontAwesome.otf -------------------------------------------------------------------------------- /app/static/fonts/social-share-kit.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/billmei/movie-info/HEAD/app/static/fonts/social-share-kit.eot -------------------------------------------------------------------------------- /app/static/fonts/social-share-kit.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/billmei/movie-info/HEAD/app/static/fonts/social-share-kit.ttf -------------------------------------------------------------------------------- /app/static/img/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/billmei/movie-info/HEAD/app/static/img/apple-touch-icon.png -------------------------------------------------------------------------------- /app/static/img/striped_background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/billmei/movie-info/HEAD/app/static/img/striped_background.png -------------------------------------------------------------------------------- /app/static/fonts/social-share-kit.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/billmei/movie-info/HEAD/app/static/fonts/social-share-kit.woff -------------------------------------------------------------------------------- /app/static/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/billmei/movie-info/HEAD/app/static/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /app/static/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/billmei/movie-info/HEAD/app/static/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /app/static/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/billmei/movie-info/HEAD/app/static/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /app/static/fonts/fontawesome-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/billmei/movie-info/HEAD/app/static/fonts/fontawesome-webfont.woff2 -------------------------------------------------------------------------------- /db_repository/README: -------------------------------------------------------------------------------- 1 | This is a database migration repository. 2 | 3 | More information at 4 | http://code.google.com/p/sqlalchemy-migrate/ 5 | -------------------------------------------------------------------------------- /db_repository/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from migrate.versioning.shell import main 3 | 4 | if __name__ == '__main__': 5 | main() 6 | -------------------------------------------------------------------------------- /app/static/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/billmei/movie-info/HEAD/app/static/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /app/static/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/billmei/movie-info/HEAD/app/static/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /app/static/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/billmei/movie-info/HEAD/app/static/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /app/static/fonts/glyphicons-halflings-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/billmei/movie-info/HEAD/app/static/fonts/glyphicons-halflings-regular.woff2 -------------------------------------------------------------------------------- /app/models.py: -------------------------------------------------------------------------------- 1 | from app import db 2 | 3 | class Movie(db.Model): 4 | id = db.Column(db.Integer, primary_key=True) 5 | imdb_id = db.Column(db.String, unique=True) 6 | title = db.Column(db.String) 7 | year = db.Column(db.String) 8 | movie_data = db.Column(db.String) 9 | 10 | def __repr__(self): 11 | return '' % (self.id, self.title) 12 | -------------------------------------------------------------------------------- /app/__init__.py: -------------------------------------------------------------------------------- 1 | import os 2 | from flask import Flask 3 | from flask.ext.sqlalchemy import SQLAlchemy 4 | 5 | app = Flask(__name__, instance_relative_config=True, static_url_path='/static') 6 | app.config.from_object('config') 7 | try: 8 | # Configuration from instance folder 9 | app.config.from_pyfile('config.py') 10 | except EnvironmentError: 11 | pass 12 | 13 | db = SQLAlchemy(app) 14 | 15 | from app import views, models 16 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | boto==2.38.0 2 | bz2file==0.98 3 | click==4.1 4 | decorator==3.4.0 5 | Flask==0.10.1 6 | Flask-SQLAlchemy==2.0 7 | gunicorn==19.3.0 8 | itsdangerous==0.24 9 | Jinja2==2.7.3 10 | MarkupSafe==0.23 11 | omdb==0.3.1 12 | pbr==0.10.8 13 | python-dotenv==0.1.2 14 | requests==2.6.0 15 | six==1.9.0 16 | smart-open==1.2.1 17 | SQLAlchemy==1.0.0b1 18 | sqlalchemy-migrate==0.9.5 19 | sqlparse==0.1.14 20 | Tempita==0.5.3.dev0 21 | Werkzeug==0.10.1 22 | -------------------------------------------------------------------------------- /local_config_example.py: -------------------------------------------------------------------------------- 1 | import os 2 | from dotenv import load_dotenv 3 | 4 | PROJECT_DIR = os.path.dirname(os.path.abspath(__file__)) 5 | load_dotenv(os.path.join(PROJECT_DIR, '.env')) 6 | 7 | SECRET_KEY = os.environ.get('FLASK_SECRET_KEY') 8 | OMDB_API_KEY = os.environ.get('OMDB_API_KEY') 9 | S3_ACCESS_KEY = os.environ.get('S3_ACCESS_KEY') 10 | S3_SECRET_KEY = os.environ.get('S3_SECRET_KEY') 11 | S3_BUCKET_NAME = os.environ.get('S3_BUCKET_NAME') 12 | 13 | DEBUG = True 14 | -------------------------------------------------------------------------------- /db_create.py: -------------------------------------------------------------------------------- 1 | #!flask/bin/python 2 | from migrate.versioning import api 3 | from config import SQLALCHEMY_DATABASE_URI 4 | from config import SQLALCHEMY_MIGRATE_REPO 5 | from app import db 6 | import os.path 7 | 8 | db.create_all() 9 | if not os.path.exists(SQLALCHEMY_MIGRATE_REPO): 10 | api.create(SQLALCHEMY_MIGRATE_REPO, 'database repository') 11 | api.version_control(SQLALCHEMY_DATABASE_URI, SQLALCHEMY_MIGRATE_REPO) 12 | else: 13 | api.version_control(SQLALCHEMY_DATABASE_URI, SQLALCHEMY_MIGRATE_REPO, 14 | api.version(SQLALCHEMY_MIGRATE_REPO)) 15 | -------------------------------------------------------------------------------- /db_repository/versions/001_migration.py: -------------------------------------------------------------------------------- 1 | from sqlalchemy import * 2 | from migrate import * 3 | 4 | 5 | from migrate.changeset import schema 6 | pre_meta = MetaData() 7 | post_meta = MetaData() 8 | 9 | def upgrade(migrate_engine): 10 | # Upgrade operations go here. Don't create your own engine; bind 11 | # migrate_engine to your metadata 12 | pre_meta.bind = migrate_engine 13 | post_meta.bind = migrate_engine 14 | 15 | 16 | def downgrade(migrate_engine): 17 | # Operations to reverse the above upgrade go here. 18 | pre_meta.bind = migrate_engine 19 | post_meta.bind = migrate_engine 20 | -------------------------------------------------------------------------------- /config.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | APP_NAME = 'movie-info' 4 | PROJECT_DIR = os.path.dirname(os.path.abspath(__file__)) 5 | DEBUG = False 6 | USE_S3 = True 7 | 8 | SECRET_KEY = os.environ.get('FLASK_SECRET_KEY') 9 | OMDB_API_KEY = os.environ.get('OMDB_API_KEY') 10 | S3_ACCESS_KEY = os.environ.get('S3_ACCESS_KEY') 11 | S3_SECRET_KEY = os.environ.get('S3_SECRET_KEY') 12 | S3_BUCKET_NAME = os.environ.get('S3_BUCKET_NAME') 13 | 14 | SQLALCHEMY_DATABASE_URI = 'sqlite:///' + os.path.join(PROJECT_DIR, 'app.db') 15 | SQLALCHEMY_MIGRATE_REPO = os.path.join(PROJECT_DIR, 'db_repository') 16 | 17 | APP_FOLDER = '/app' 18 | POSTERS_FOLDER = '/static/posters/' 19 | MAX_CONTENT_LENGTH = 16 * 1024 * 1024 20 | -------------------------------------------------------------------------------- /app/views.py: -------------------------------------------------------------------------------- 1 | from flask import render_template, request, Response, abort 2 | from app import app 3 | from app import movie_info 4 | 5 | # Templates 6 | @app.route('/', methods=['GET']) 7 | def render_index_page(): 8 | """Display the landing page""" 9 | return render_template('index.html') 10 | 11 | @app.route('/movie/', methods=['GET']) 12 | def render_movie_page(imdb_id): 13 | """Render the movie's data page""" 14 | return render_template('index.html', imdb_id=imdb_id) 15 | 16 | @app.route('/api/get_movie', methods=['GET']) 17 | def api_get_movie(): 18 | """Get movie by title and year""" 19 | result = movie_info.get_movie( 20 | request.args.get('movie_title', None), 21 | request.args.get('movie_year', None), 22 | request.args.get('imdb_id', None) 23 | ) 24 | if result: 25 | return Response(result, mimetype='application/json') 26 | else: 27 | abort(404) 28 | -------------------------------------------------------------------------------- /db_migrate.py: -------------------------------------------------------------------------------- 1 | #!flask/bin/python 2 | import imp 3 | from migrate.versioning import api 4 | from config import SQLALCHEMY_DATABASE_URI 5 | from config import SQLALCHEMY_MIGRATE_REPO 6 | from app import db 7 | 8 | migration = SQLALCHEMY_MIGRATE_REPO + '/versions/%03d_migration.py' % ( 9 | api.db_version(SQLALCHEMY_DATABASE_URI, SQLALCHEMY_MIGRATE_REPO) + 1) 10 | 11 | tmp_module = imp.new_module('old_model') 12 | old_model = api.create_model(SQLALCHEMY_DATABASE_URI, SQLALCHEMY_MIGRATE_REPO) 13 | 14 | exec(old_model, tmp_module.__dict__) 15 | script = api.make_update_script_for_model(SQLALCHEMY_DATABASE_URI, 16 | SQLALCHEMY_MIGRATE_REPO, tmp_module.meta, db.metadata) 17 | 18 | open(migration, 'wt').write(script) 19 | api.upgrade(SQLALCHEMY_DATABASE_URI, SQLALCHEMY_MIGRATE_REPO) 20 | 21 | print('New migration saved as ' + migration) 22 | print('Current database version: ' + str(api.db_version(SQLALCHEMY_DATABASE_URI, 23 | SQLALCHEMY_MIGRATE_REPO))) 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | 5 | # C extensions 6 | *.so 7 | 8 | # Distribution / packaging 9 | .Python 10 | venv/ 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | *.egg-info/ 23 | .installed.cfg 24 | *.egg 25 | 26 | # PyInstaller 27 | # Usually these files are written by a python script from a template 28 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 29 | *.manifest 30 | *.spec 31 | 32 | # Installer logs 33 | pip-log.txt 34 | pip-delete-this-directory.txt 35 | 36 | # Unit test / coverage reports 37 | htmlcov/ 38 | .tox/ 39 | .coverage 40 | .cache 41 | nosetests.xml 42 | coverage.xml 43 | 44 | # Translations 45 | *.mo 46 | *.pot 47 | 48 | # Django stuff: 49 | *.log 50 | 51 | # Sphinx documentation 52 | docs/_build/ 53 | 54 | # PyBuilder 55 | target/ 56 | 57 | # Secret keys 58 | .env 59 | instance/ 60 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Bill Mei 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /db_repository/migrate.cfg: -------------------------------------------------------------------------------- 1 | [db_settings] 2 | # Used to identify which repository this database is versioned under. 3 | # You can use the name of your project. 4 | repository_id=database repository 5 | 6 | # The name of the database table used to track the schema version. 7 | # This name shouldn't already be used by your project. 8 | # If this is changed once a database is under version control, you'll need to 9 | # change the table name in each database too. 10 | version_table=migrate_version 11 | 12 | # When committing a change script, Migrate will attempt to generate the 13 | # sql for all supported databases; normally, if one of them fails - probably 14 | # because you don't have that database installed - it is ignored and the 15 | # commit continues, perhaps ending successfully. 16 | # Databases in this list MUST compile successfully during a commit, or the 17 | # entire commit will fail. List the databases your application will actually 18 | # be using to ensure your updates to that database work properly. 19 | # This must be a list; example: ['postgres','sqlite'] 20 | required_dbs=[] 21 | 22 | # When creating new change scripts, Migrate will stamp the new script with 23 | # a version number. By default this is latest_version + 1. You can set this 24 | # to 'true' to tell Migrate to use the UTC timestamp instead. 25 | use_timestamp_numbering=False 26 | -------------------------------------------------------------------------------- /app/templates/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Page Not Found 6 | 7 | 54 | 55 | 56 |

Page Not Found

57 |

Sorry, but the page you were trying to view does not exist.

58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /app/static/js/plugins.js: -------------------------------------------------------------------------------- 1 | // Avoid `console` errors in browsers that lack a console. 2 | (function() { 3 | var method; 4 | var noop = function () {}; 5 | var methods = [ 6 | 'assert', 'clear', 'count', 'debug', 'dir', 'dirxml', 'error', 7 | 'exception', 'group', 'groupCollapsed', 'groupEnd', 'info', 'log', 8 | 'markTimeline', 'profile', 'profileEnd', 'table', 'time', 'timeEnd', 9 | 'timeline', 'timelineEnd', 'timeStamp', 'trace', 'warn' 10 | ]; 11 | var length = methods.length; 12 | var console = (window.console = window.console || {}); 13 | 14 | while (length--) { 15 | method = methods[length]; 16 | 17 | // Only stub undefined methods. 18 | if (!console[method]) { 19 | console[method] = noop; 20 | } 21 | } 22 | }()); 23 | 24 | 25 | function assert(condition, message) { 26 | if (!condition) { 27 | throw Error('Assert failed' + (typeof message !== 'undefined' ? ': ' + message : '')); 28 | } 29 | } 30 | 31 | // indexOf Polyfill from MDN 32 | // Production steps of ECMA-262, Edition 5, 15.4.4.14 33 | // Reference: http://es5.github.io/#x15.4.4.14 34 | if (!Array.prototype.indexOf) { 35 | Array.prototype.indexOf = function(searchElement, fromIndex) { 36 | var k; 37 | if (this == null) { 38 | throw new TypeError('"this" is null or not defined'); 39 | } 40 | var O = Object(this); 41 | var len = O.length >>> 0; 42 | if (len === 0) { 43 | return -1; 44 | } 45 | var n = +fromIndex || 0; 46 | if (Math.abs(n) === Infinity) { 47 | n = 0; 48 | } 49 | if (n >= len) { 50 | return -1; 51 | } 52 | k = Math.max(n >= 0 ? n : len - Math.abs(n), 0); 53 | while (k < len) { 54 | if (k in O && O[k] === searchElement) { 55 | return k; 56 | } 57 | k++; 58 | } 59 | return -1; 60 | }; 61 | } 62 | -------------------------------------------------------------------------------- /app/templates/index.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block content %} 4 |
5 |
6 |
7 |
8 |

Find info on millions of movies

9 |
10 |
11 |
12 |
13 |
14 | 15 | 16 |
17 |
18 | 19 | 20 |
21 |
22 | 23 | 24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 | No results found :( 33 |
34 |
35 |
36 | 37 | 42 |
43 |
44 |
45 |
    46 | 47 |
  • {[movieCtrl.movie.title]}
  • 48 |
  • {[movieCtrl.movie.parsedRated]}
  • 49 |
  • 50 |
  • 51 |
  • Released {[movieCtrl.movie.year]}
  • 52 |
  • {[movieCtrl.movie.genre]}
  • 53 | 54 | 55 |
  • {[movieCtrl.movie.plot === 'N/A' ? '' : movieCtrl.movie.plot]}
  • 56 | 57 | 58 |
  • Runtime {[movieCtrl.movie.runtime]}
  • 59 |
  • 60 |
  • 61 |
  • 62 |
  • 63 |
64 |
65 |
66 |
67 |
68 |
69 | {% endblock %} 70 | -------------------------------------------------------------------------------- /app/movie_info.py: -------------------------------------------------------------------------------- 1 | from app import db, models 2 | from config import PROJECT_DIR, APP_FOLDER, POSTERS_FOLDER, OMDB_API_KEY 3 | from config import USE_S3, S3_BUCKET_NAME, S3_ACCESS_KEY, S3_SECRET_KEY 4 | from werkzeug import secure_filename 5 | import requests 6 | import smart_open 7 | import json 8 | import omdb 9 | 10 | def get_movie(title, year=None, imdb_id=None): 11 | """ 12 | Retrieves movies from the database by title and year, or IMDB id. 13 | Automatically tries to fetch from the OMDB API if the movie does not 14 | exist in the database. 15 | """ 16 | # First try fetching from our database 17 | if imdb_id: 18 | movie = models.Movie.query.filter_by(imdb_id=imdb_id).first() 19 | 20 | else: 21 | title = title.lower() 22 | 23 | if len(year) == 0 or not year: 24 | movie = models.Movie.query.filter( 25 | models.Movie.title.startswith(title)).order_by( 26 | models.Movie.year.desc()).first() 27 | else: 28 | movie = models.Movie.query.filter( 29 | models.Movie.title.startswith(title)).filter_by( 30 | year=year).first() 31 | 32 | if movie: 33 | # Movie exists in our the database 34 | return movie.movie_data 35 | else: 36 | # Otherwise fetch from OMDB 37 | movie = search_omdb(title, year, imdb_id) 38 | 39 | if movie: 40 | movie.poster = get_poster(movie.imdb_id) 41 | cache_movie(movie) 42 | return json.dumps(movie) 43 | 44 | return None 45 | 46 | def search_omdb(title, year=None, imdb_id=None): 47 | """ 48 | Retrieves a movie from the OMDB API 49 | """ 50 | try: 51 | if imdb_id: 52 | movie = omdb.imdbid(imdb_id, fullplot=True) 53 | else: 54 | movie = omdb.get(title=title, year=year, fullplot=True) 55 | except requests.exceptions.HTTPError: 56 | return None 57 | 58 | return movie 59 | 60 | def cache_movie(movie): 61 | """Caches the movie in the database if it does not exist yet""" 62 | if not movie: 63 | return 64 | 65 | movie_title = movie['title'].lower() 66 | 67 | m = models.Movie( 68 | imdb_id=movie['imdb_id'], 69 | title=movie_title, 70 | year=movie['year'], 71 | movie_data=json.dumps(movie) 72 | ) 73 | db.session.add(m) 74 | db.session.commit() 75 | 76 | def get_poster(imdb_id): 77 | """ 78 | Builds a local filename for a movie poster by imdb_id 79 | from the OMDB Poster API 80 | """ 81 | uri = 'http://img.omdbapi.com/?i=' + imdb_id + '&apikey=' + OMDB_API_KEY 82 | r = requests.get(uri, stream=True) 83 | if r.status_code == 404 or r.status_code == 500 or r.status_code == 403: 84 | # No poster found, return the default image 85 | return None 86 | 87 | if USE_S3: 88 | remote_file = POSTERS_FOLDER + secure_filename(imdb_id) + '.jpg' 89 | 90 | s3_path = 's3://' + S3_ACCESS_KEY + ':' + S3_SECRET_KEY + \ 91 | '@' + S3_BUCKET_NAME + '/' 92 | 93 | with smart_open.smart_open(s3_path + remote_file, 'wb') as poster_file: 94 | write_to_file(poster_file, r) 95 | 96 | return '//s3.amazonaws.com/' + S3_BUCKET_NAME + remote_file 97 | 98 | else: 99 | local_file = PROJECT_DIR + APP_FOLDER + \ 100 | POSTERS_FOLDER + secure_filename(imdb_id) + '.jpg' 101 | with open(local_file, 'wb') as poster_file: 102 | write_to_file(poster_file, r) 103 | 104 | return POSTERS_FOLDER + secure_filename(imdb_id) + '.jpg' 105 | 106 | def write_to_file(filename, req): 107 | """Writes arbitrary binary data retrieved from a request to a file""" 108 | for chunk in req.iter_content(chunk_size=1024): 109 | if chunk: 110 | filename.write(chunk) 111 | -------------------------------------------------------------------------------- /app/templates/base.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Movie Info: Find info on millions of movies 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | {% block content %}{% endblock %} 34 | 35 |
36 |

Created by Bill Mei using Flask and Angular.js. View this project on GitHub.

37 |

All poster images belong to their respective copyright owners.

38 |
39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | Fork me on GitHub 48 | 49 | 58 | 59 | 73 | 74 | 75 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Movie Info 2 | Find info on movies. **[Click here](http://ga-movie-info.herokuapp.com/) for the demo**. Built on Flask, Angular.js, Bootstrap, and jQuery. 3 | 4 | ## Architecture 5 | 6 | [![Movie Info Architecture](http://i.imgur.com/2g7WuYf.png)](https://docs.google.com/drawings/d/146xaqXPqNgaNZRwv9V2uxdQjtM3Uo4iHrLg7-DirJMU) 7 | 8 | ## Features 9 | - Uses a Python backend to cache results from OMDb so that we don't need to keep fetching data from a third party API. Not only does this improve performance, but the backend also has routes so that the user can hotlink directly to a result once they have seen it. For example, navigating to [http://ga-movie-info.herokuapp.com**/movie/tt0110912/**](http://ga-movie-info.herokuapp.com/movie/tt0110912) takes you directly to a cached results page that doesn't reply on external API calls. 10 | - Connects to S3 to cache movie poster images once they're downloaded. This avoids exposing the OMDb API key to the client. 11 | - Fully responsive on mobile, tablet, and desktop. 12 | - Social sharing of each result page to Facebook, Twitter, and Google+ 13 | - Tested to work on Chrome, Firefox, and IE. 14 | - Handles multiple error cases and zero data cases gracefully. 15 | 16 | ## Installation Instructions 17 | 18 | Python Version 3.4.0 19 | 20 | Flask Version 0.10.1 21 | 22 | ### Step 1 23 | 24 | $ git clone git@github.com:Kortaggio/movie-info.git 25 | $ cd movie-info 26 | 27 | ### Step 2 28 | 29 | Install `virtualenv` and the python3 interpreter: 30 | 31 | $ pip install virtualenv 32 | $ virtualenv -p python3 venv 33 | 34 | ### Step 3 35 | 36 | Create an `instance` folder in the root directory and copy `local_config_example.py` into it, except rename it as `config.py` (i.e. `/instance/config.py`). Here you can set instance variables that are only relevant to your local machine. See `local_config_example.py` for an example of what this file can look like. 37 | 38 | ### Step 4 39 | 40 | This app needs [Amazon S3](https://aws.amazon.com/s3/) to store the images of the movie posters. If you don't have an S3 account and *only* want to run the app locally, set the config flag `USE_S3 = False` in `config.py`, and skip the rest of Step 4. 41 | 42 | If you have an S3 account and want to use it in conjunction with deploying on Heroku, run: 43 | 44 | $ heroku config:set S3_ACCESS_KEY=YOUR-ACCESS-KEY-HERE 45 | $ heroku config:set S3_SECRET_KEY=YOUR-SECRET-KEY-HERE 46 | $ heroku config:set S3_BUCKET_NAME=YOUR-BUCKET-NAME-HERE 47 | 48 | If you have an S3 account but want to run the app locally (e.g. when testing), you still need to copy the following environment variables into a file named `/instance/.env` on your local machine: 49 | 50 | S3_ACCESS_KEY=YOUR-ACCESS-KEY-HERE 51 | S3_SECRET_KEY=YOUR-SECRET-KEY-HERE 52 | S3_BUCKET_NAME=YOUR-BUCKET-NAME-HERE 53 | 54 | This will make sure the environment variables for your S3 bucket are configured correctly every time you run the virtualenv wrapper. When setting up your S3 account, make sure to set the following policy on your bucket so that the files have permission to be viewed publicly (you need to do this regardless of whether you are running the app locally or on a remote host like Heroku): 55 | 56 | { 57 | "Version": "2012-10-17", 58 | "Id": "Policy1431347864012", 59 | "Statement": [ 60 | { 61 | "Sid": "Stmt1431347858318", 62 | "Effect": "Allow", 63 | "Principal": "*", 64 | "Action": "s3:GetObject", 65 | "Resource": "arn:aws:s3:::YOUR-BUCKET-NAME-HERE/static/*" 66 | } 67 | ] 68 | } 69 | 70 | ### Step 5 71 | 72 | Activate `virtualenv` and install the app requirements 73 | 74 | $ source venv/bin/activate 75 | $ pip install -r requirements.txt 76 | 77 | ### Step 6 78 | 79 | Generate a Flask secret key `FLASK_SECRET_KEY=YOUR-SECRET-KEY-HERE` and append the line to a file called `/instance/.env`. If running on Heroku instead of localhost, set the environment variable: 80 | 81 | $ heroku config:set FLASK_SECRET_KEY=YOUR-SECRET-KEY-HERE 82 | 83 | You also need to [get an OMDB Poster API key](http://beforethecode.com/projects/omdb/apikey.aspx) and save it as `OMDB_API_KEY=YOUR-API-KEY-HERE` on a new line in `/instance/.env`. Again, you also need to set the environment varaible if you are running on Heroku: 84 | 85 | $ heroku config:set OMDB_API_KEY=YOUR-API-KEY-HERE 86 | 87 | ### Step 6.1 (Optional) 88 | 89 | If at any point you want to wipe the database and create a new one from scratch, delete the `app.db` file and the `db_repository` folder. You can then create and migrate the database using: 90 | 91 | $ python db_create.py 92 | $ python db_migrate.py 93 | 94 | If you `git clone`'d the repo then you shouldn't need to do this as the database uploaded here is already empty. 95 | 96 | ### Step 7 97 | 98 | Run with 99 | 100 | $ python run.py 101 | 102 | # License 103 | 104 | MIT License 105 | -------------------------------------------------------------------------------- /app/static/js/vendor/angular-sanitize.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | AngularJS v1.3.15 3 | (c) 2010-2014 Google, Inc. http://angularjs.org 4 | License: MIT 5 | */ 6 | (function(n,h,p){'use strict';function E(a){var e=[];r(e,h.noop).chars(a);return e.join("")}function g(a){var e={};a=a.split(",");var d;for(d=0;d=c;d--)e.end&&e.end(f[d]);f.length=c}}"string"!==typeof a&&(a=null===a||"undefined"===typeof a?"":""+a);var b,k,f=[],m=a,l;for(f.last=function(){return f[f.length-1]};a;){l="";k=!0;if(f.last()&&w[f.last()])a=a.replace(new RegExp("([\\W\\w]*)<\\s*\\/\\s*"+f.last()+"[^>]*>","i"),function(a,b){b=b.replace(H,"$1").replace(I,"$1");e.chars&&e.chars(q(b));return""}),c("",f.last());else{if(0===a.indexOf("\x3c!--"))b=a.indexOf("--",4),0<=b&&a.lastIndexOf("--\x3e",b)===b&&(e.comment&& 8 | e.comment(a.substring(4,b)),a=a.substring(b+3),k=!1);else if(x.test(a)){if(b=a.match(x))a=a.replace(b[0],""),k=!1}else if(J.test(a)){if(b=a.match(y))a=a.substring(b[0].length),b[0].replace(y,c),k=!1}else K.test(a)&&((b=a.match(z))?(b[4]&&(a=a.substring(b[0].length),b[0].replace(z,d)),k=!1):(l+="<",a=a.substring(1)));k&&(b=a.indexOf("<"),l+=0>b?a:a.substring(0,b),a=0>b?"":a.substring(b),e.chars&&e.chars(q(l)))}if(a==m)throw L("badparse",a);m=a}c()}function q(a){if(!a)return"";A.innerHTML=a.replace(//g,">")}function r(a,e){var d=!1,c=h.bind(a,a.push);return{start:function(a,k,f){a=h.lowercase(a);!d&&w[a]&&(d=a);d||!0!==C[a]||(c("<"),c(a),h.forEach(k,function(d,f){var k=h.lowercase(f),g="img"===a&&"src"===k||"background"=== 10 | k;!0!==O[k]||!0===D[k]&&!e(d,g)||(c(" "),c(f),c('="'),c(B(d)),c('"'))}),c(f?"/>":">"))},end:function(a){a=h.lowercase(a);d||!0!==C[a]||(c(""));a==d&&(d=!1)},chars:function(a){d||c(B(a))}}}var L=h.$$minErr("$sanitize"),z=/^<((?:[a-zA-Z])[\w:-]*)((?:\s+[\w:-]+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)\s*(>?)/,y=/^<\/\s*([\w:-]+)[^>]*>/,G=/([\w:-]+)(?:\s*=\s*(?:(?:"((?:[^"])*)")|(?:'((?:[^'])*)')|([^>\s]+)))?/g,K=/^]*?)>/i, 11 | I=/"\u201d\u2019]/,d=/^mailto:/;return function(c,b){function k(a){a&&g.push(E(a))}function f(a,c){g.push("');k(c);g.push("")}if(!c)return c;for(var m,l=c,g=[],n,p;m=l.match(e);)n=m[0],m[2]||m[4]||(n=(m[3]?"http://":"mailto:")+n),p=m.index,k(l.substr(0,p)),f(n,m[0].replace(d,"")),l=l.substring(p+m[0].length);k(l);return a(g.join(""))}}])})(window,window.angular); 16 | //# sourceMappingURL=angular-sanitize.min.js.map 17 | -------------------------------------------------------------------------------- /app/static/css/main.css: -------------------------------------------------------------------------------- 1 | html { 2 | /* Always show scrollbars */ 3 | overflow: -moz-scrollbars-vertical; 4 | overflow-y: scroll; 5 | } 6 | 7 | body { 8 | background-image: url('../img/striped_background.png'); 9 | background-repeat: repeat; 10 | } 11 | 12 | .vertical-space { 13 | margin-top: 20px; 14 | } 15 | 16 | .header-container { 17 | height: 500px; 18 | background-image: url('../img/posters.jpg'); 19 | background-repeat: repeat; 20 | } 21 | .header-container .row { 22 | position: relative; 23 | z-index: 3; 24 | } 25 | #scrolling-background { 26 | position: absolute; 27 | top: 0; 28 | width: 100%; 29 | height: 500px; 30 | z-index: 3; 31 | overflow: hidden; 32 | } 33 | #scrolling-background .transparent-overlay { 34 | position: absolute; 35 | top: 0; 36 | background-color: rgba(0, 0, 0, 0.6); 37 | width: 100%; 38 | height: 500px; 39 | z-index: 5; 40 | } 41 | #scrolling-background .background-poster { 42 | position: relative; 43 | width: 3200px; 44 | height: 100px; 45 | z-index: 2; 46 | background-repeat: repeat; 47 | } 48 | .scroll-left { 49 | -webkit-animation: loop_left 90s linear infinite; 50 | -moz-animation: loop_left 90s linear infinite; 51 | -o-animation: loop_left 90s linear infinite; 52 | animation: loop_left 90s linear infinite; 53 | } 54 | .scroll-right { 55 | left: -1600px; 56 | -webkit-animation: loop_right 90s linear infinite; 57 | -moz-animation: loop_right 90s linear infinite; 58 | -o-animation: loop_right 90s linear infinite; 59 | animation: loop_right 90s linear infinite; 60 | } 61 | 62 | .background-0 { 63 | background-image: url('../img/posters_0.jpg'); 64 | } 65 | .background-1 { 66 | background-image: url('../img/posters_1.jpg'); 67 | } 68 | .background-2 { 69 | background-image: url('../img/posters_2.jpg'); 70 | } 71 | .background-3 { 72 | background-image: url('../img/posters_3.jpg'); 73 | } 74 | .background-4 { 75 | background-image: url('../img/posters_4.jpg'); 76 | } 77 | .background-5 { 78 | background-image: url('../img/posters_5.jpg'); 79 | } 80 | .background-6 { 81 | background-image: url('../img/posters_6.jpg'); 82 | } 83 | .background-7 { 84 | background-image: url('../img/posters_7.jpg'); 85 | } 86 | .background-8 { 87 | background-image: url('../img/posters_8.jpg'); 88 | } 89 | .background-9 { 90 | background-image: url('../img/posters_9.jpg'); 91 | } 92 | 93 | .loading-spinner { 94 | display: inline-block; 95 | position: relative; 96 | opacity: 0; 97 | top: 1.5em; 98 | z-index: -2; 99 | color: white; 100 | } 101 | .loading-enabled { 102 | opacity: 1; 103 | z-index: 20; 104 | } 105 | .loading-disabled, 106 | .loading-disabled:hover, 107 | .loading-disabled:active, 108 | .loading-disabled:focus { 109 | color: rgba(0,0,0,0); 110 | } 111 | 112 | .headline, .sub-headline { 113 | text-align: center; 114 | text-shadow: 1px 1px 4px rgb(70, 69, 69); 115 | } 116 | .headline { 117 | margin-top: 150px; 118 | margin-bottom: 100px; 119 | } 120 | .sub-headline { 121 | margin-bottom: 2em; 122 | } 123 | 124 | #search-form { 125 | width: 500px; 126 | margin: 0 auto 50px auto; 127 | padding: 20px; 128 | border-radius: 4px; 129 | color: #000; 130 | background-color: rgba(70, 69, 69, 0.8); 131 | text-align: center 132 | } 133 | #search-form label { 134 | color: #fff; 135 | text-align: left; 136 | display: block; 137 | padding-left: 5px; 138 | } 139 | .form-group { 140 | display: inline-block; 141 | margin: 0 2px; 142 | vertical-align: bottom; 143 | } 144 | #movie-year { 145 | width: 8em; 146 | } 147 | #search-btn { 148 | display: block; 149 | } 150 | 151 | #no-results { 152 | display: none; 153 | text-align: center; 154 | font-size: 200%; 155 | font-weight: bold; 156 | opacity: 0.5; 157 | text-shadow: 0 -1px 0 #333; 158 | margin-top: 1em; 159 | } 160 | 161 | #movie-results { 162 | opacity: 0; 163 | margin-top: 50px; 164 | margin-bottom: 50px; 165 | } 166 | 167 | #movie-poster img { 168 | width: 100%; 169 | } 170 | 171 | .movie-sidebar { 172 | float: none; 173 | } 174 | #movie-info { 175 | list-style: none; 176 | padding: 0; 177 | } 178 | #movie-info .title { 179 | font-size: 150%; 180 | display: inline-block; 181 | } 182 | #movie-info .rated { 183 | display: inline-block; 184 | vertical-align: top; 185 | margin-top: 0.75em; 186 | margin-left: 1em; 187 | } 188 | #movie-info .stars, 189 | #movie-info .year, 190 | #movie-info .genre { 191 | color: #999999; 192 | display: inline-block; 193 | margin-right: 0.5em; 194 | margin-bottom: 0.5em; 195 | } 196 | #movie-info .stars { 197 | color: #FFD700; 198 | } 199 | #movie-info .stars:empty { 200 | display: none; 201 | } 202 | #movie-info .plot { 203 | margin-bottom: 0.5em; 204 | } 205 | #movie-info .movie-label { 206 | display: block; 207 | color: #fff; 208 | } 209 | 210 | #movie-info .runtime, 211 | #movie-info .director, 212 | #movie-info .actors, 213 | #movie-info .writer, 214 | #movie-info .awards { 215 | color: #999999 216 | } 217 | #movie-info .runtime { 218 | margin-bottom: 0.5em; 219 | } 220 | 221 | .share-links { 222 | padding: 0; 223 | margin-bottom: 2em; 224 | list-style: none; 225 | } 226 | .share-links a { 227 | display: block; 228 | text-align: left; 229 | margin-top: 1em; 230 | position: relative; 231 | z-index: 2; 232 | } 233 | 234 | @media (max-width: 500px) { 235 | #search-form { 236 | width: 100%; 237 | } 238 | .headline { 239 | margin-top: 75px; 240 | margin-bottom: 50px; 241 | } 242 | .form-group { 243 | display: block; 244 | margin-top: 1em; 245 | margin-bottom: 1em; 246 | } 247 | .form-group:first-child { 248 | margin-top: 0; 249 | } 250 | .form-group:last-child { 251 | margin-bottom: 0; 252 | } 253 | #movie-title, 254 | #movie-year, 255 | #search-btn { 256 | width: 100%; 257 | } 258 | } 259 | 260 | @-webkit-keyframes loop_left { 0% { left: 0;} 100% { left: -1600px; } } 261 | @-moz-keyframes loop_left { 0% { left: 0;} 100% { left: -1600px; } } 262 | @-o-keyframes loop_left { 0% { left: 0;} 100% { left: -1600px; } } 263 | @-keyframes loop_left { 0% { left: 0;} 100% { left: -1600px; } } 264 | 265 | @-webkit-keyframes loop_right { 0% { left: -1600px} 100% { left: 0; } } 266 | @-moz-keyframes loop_right { 0% { left: -1600px} 100% { left: 0; } } 267 | @-o-keyframes loop_right { 0% { left: -1600px} 100% { left: 0; } } 268 | @-keyframes loop_right { 0% { left: -1600px} 100% { left: 0; } } 269 | -------------------------------------------------------------------------------- /app/static/css/normalize.css: -------------------------------------------------------------------------------- 1 | /*! normalize.css v3.0.2 | MIT License | git.io/normalize */ 2 | 3 | /** 4 | * 1. Set default font family to sans-serif. 5 | * 2. Prevent iOS text size adjust after orientation change, without disabling 6 | * user zoom. 7 | */ 8 | 9 | html { 10 | font-family: sans-serif; /* 1 */ 11 | -ms-text-size-adjust: 100%; /* 2 */ 12 | -webkit-text-size-adjust: 100%; /* 2 */ 13 | } 14 | 15 | /** 16 | * Remove default margin. 17 | */ 18 | 19 | body { 20 | margin: 0; 21 | } 22 | 23 | /* HTML5 display definitions 24 | ========================================================================== */ 25 | 26 | /** 27 | * Correct `block` display not defined for any HTML5 element in IE 8/9. 28 | * Correct `block` display not defined for `details` or `summary` in IE 10/11 29 | * and Firefox. 30 | * Correct `block` display not defined for `main` in IE 11. 31 | */ 32 | 33 | article, 34 | aside, 35 | details, 36 | figcaption, 37 | figure, 38 | footer, 39 | header, 40 | hgroup, 41 | main, 42 | menu, 43 | nav, 44 | section, 45 | summary { 46 | display: block; 47 | } 48 | 49 | /** 50 | * 1. Correct `inline-block` display not defined in IE 8/9. 51 | * 2. Normalize vertical alignment of `progress` in Chrome, Firefox, and Opera. 52 | */ 53 | 54 | audio, 55 | canvas, 56 | progress, 57 | video { 58 | display: inline-block; /* 1 */ 59 | vertical-align: baseline; /* 2 */ 60 | } 61 | 62 | /** 63 | * Prevent modern browsers from displaying `audio` without controls. 64 | * Remove excess height in iOS 5 devices. 65 | */ 66 | 67 | audio:not([controls]) { 68 | display: none; 69 | height: 0; 70 | } 71 | 72 | /** 73 | * Address `[hidden]` styling not present in IE 8/9/10. 74 | * Hide the `template` element in IE 8/9/11, Safari, and Firefox < 22. 75 | */ 76 | 77 | [hidden], 78 | template { 79 | display: none; 80 | } 81 | 82 | /* Links 83 | ========================================================================== */ 84 | 85 | /** 86 | * Remove the gray background color from active links in IE 10. 87 | */ 88 | 89 | a { 90 | background-color: transparent; 91 | } 92 | 93 | /** 94 | * Improve readability when focused and also mouse hovered in all browsers. 95 | */ 96 | 97 | a:active, 98 | a:hover { 99 | outline: 0; 100 | } 101 | 102 | /* Text-level semantics 103 | ========================================================================== */ 104 | 105 | /** 106 | * Address styling not present in IE 8/9/10/11, Safari, and Chrome. 107 | */ 108 | 109 | abbr[title] { 110 | border-bottom: 1px dotted; 111 | } 112 | 113 | /** 114 | * Address style set to `bolder` in Firefox 4+, Safari, and Chrome. 115 | */ 116 | 117 | b, 118 | strong { 119 | font-weight: bold; 120 | } 121 | 122 | /** 123 | * Address styling not present in Safari and Chrome. 124 | */ 125 | 126 | dfn { 127 | font-style: italic; 128 | } 129 | 130 | /** 131 | * Address variable `h1` font-size and margin within `section` and `article` 132 | * contexts in Firefox 4+, Safari, and Chrome. 133 | */ 134 | 135 | h1 { 136 | font-size: 2em; 137 | margin: 0.67em 0; 138 | } 139 | 140 | /** 141 | * Address styling not present in IE 8/9. 142 | */ 143 | 144 | mark { 145 | background: #ff0; 146 | color: #000; 147 | } 148 | 149 | /** 150 | * Address inconsistent and variable font size in all browsers. 151 | */ 152 | 153 | small { 154 | font-size: 80%; 155 | } 156 | 157 | /** 158 | * Prevent `sub` and `sup` affecting `line-height` in all browsers. 159 | */ 160 | 161 | sub, 162 | sup { 163 | font-size: 75%; 164 | line-height: 0; 165 | position: relative; 166 | vertical-align: baseline; 167 | } 168 | 169 | sup { 170 | top: -0.5em; 171 | } 172 | 173 | sub { 174 | bottom: -0.25em; 175 | } 176 | 177 | /* Embedded content 178 | ========================================================================== */ 179 | 180 | /** 181 | * Remove border when inside `a` element in IE 8/9/10. 182 | */ 183 | 184 | img { 185 | border: 0; 186 | } 187 | 188 | /** 189 | * Correct overflow not hidden in IE 9/10/11. 190 | */ 191 | 192 | svg:not(:root) { 193 | overflow: hidden; 194 | } 195 | 196 | /* Grouping content 197 | ========================================================================== */ 198 | 199 | /** 200 | * Address margin not present in IE 8/9 and Safari. 201 | */ 202 | 203 | figure { 204 | margin: 1em 40px; 205 | } 206 | 207 | /** 208 | * Address differences between Firefox and other browsers. 209 | */ 210 | 211 | hr { 212 | -moz-box-sizing: content-box; 213 | box-sizing: content-box; 214 | height: 0; 215 | } 216 | 217 | /** 218 | * Contain overflow in all browsers. 219 | */ 220 | 221 | pre { 222 | overflow: auto; 223 | } 224 | 225 | /** 226 | * Address odd `em`-unit font size rendering in all browsers. 227 | */ 228 | 229 | code, 230 | kbd, 231 | pre, 232 | samp { 233 | font-family: monospace, monospace; 234 | font-size: 1em; 235 | } 236 | 237 | /* Forms 238 | ========================================================================== */ 239 | 240 | /** 241 | * Known limitation: by default, Chrome and Safari on OS X allow very limited 242 | * styling of `select`, unless a `border` property is set. 243 | */ 244 | 245 | /** 246 | * 1. Correct color not being inherited. 247 | * Known issue: affects color of disabled elements. 248 | * 2. Correct font properties not being inherited. 249 | * 3. Address margins set differently in Firefox 4+, Safari, and Chrome. 250 | */ 251 | 252 | button, 253 | input, 254 | optgroup, 255 | select, 256 | textarea { 257 | color: inherit; /* 1 */ 258 | font: inherit; /* 2 */ 259 | margin: 0; /* 3 */ 260 | } 261 | 262 | /** 263 | * Address `overflow` set to `hidden` in IE 8/9/10/11. 264 | */ 265 | 266 | button { 267 | overflow: visible; 268 | } 269 | 270 | /** 271 | * Address inconsistent `text-transform` inheritance for `button` and `select`. 272 | * All other form control elements do not inherit `text-transform` values. 273 | * Correct `button` style inheritance in Firefox, IE 8/9/10/11, and Opera. 274 | * Correct `select` style inheritance in Firefox. 275 | */ 276 | 277 | button, 278 | select { 279 | text-transform: none; 280 | } 281 | 282 | /** 283 | * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio` 284 | * and `video` controls. 285 | * 2. Correct inability to style clickable `input` types in iOS. 286 | * 3. Improve usability and consistency of cursor style between image-type 287 | * `input` and others. 288 | */ 289 | 290 | button, 291 | html input[type="button"], /* 1 */ 292 | input[type="reset"], 293 | input[type="submit"] { 294 | -webkit-appearance: button; /* 2 */ 295 | cursor: pointer; /* 3 */ 296 | } 297 | 298 | /** 299 | * Re-set default cursor for disabled elements. 300 | */ 301 | 302 | button[disabled], 303 | html input[disabled] { 304 | cursor: default; 305 | } 306 | 307 | /** 308 | * Remove inner padding and border in Firefox 4+. 309 | */ 310 | 311 | button::-moz-focus-inner, 312 | input::-moz-focus-inner { 313 | border: 0; 314 | padding: 0; 315 | } 316 | 317 | /** 318 | * Address Firefox 4+ setting `line-height` on `input` using `!important` in 319 | * the UA stylesheet. 320 | */ 321 | 322 | input { 323 | line-height: normal; 324 | } 325 | 326 | /** 327 | * It's recommended that you don't attempt to style these elements. 328 | * Firefox's implementation doesn't respect box-sizing, padding, or width. 329 | * 330 | * 1. Address box sizing set to `content-box` in IE 8/9/10. 331 | * 2. Remove excess padding in IE 8/9/10. 332 | */ 333 | 334 | input[type="checkbox"], 335 | input[type="radio"] { 336 | box-sizing: border-box; /* 1 */ 337 | padding: 0; /* 2 */ 338 | } 339 | 340 | /** 341 | * Fix the cursor style for Chrome's increment/decrement buttons. For certain 342 | * `font-size` values of the `input`, it causes the cursor style of the 343 | * decrement button to change from `default` to `text`. 344 | */ 345 | 346 | input[type="number"]::-webkit-inner-spin-button, 347 | input[type="number"]::-webkit-outer-spin-button { 348 | height: auto; 349 | } 350 | 351 | /** 352 | * 1. Address `appearance` set to `searchfield` in Safari and Chrome. 353 | * 2. Address `box-sizing` set to `border-box` in Safari and Chrome 354 | * (include `-moz` to future-proof). 355 | */ 356 | 357 | input[type="search"] { 358 | -webkit-appearance: textfield; /* 1 */ 359 | -moz-box-sizing: content-box; 360 | -webkit-box-sizing: content-box; /* 2 */ 361 | box-sizing: content-box; 362 | } 363 | 364 | /** 365 | * Remove inner padding and search cancel button in Safari and Chrome on OS X. 366 | * Safari (but not Chrome) clips the cancel button when the search input has 367 | * padding (and `textfield` appearance). 368 | */ 369 | 370 | input[type="search"]::-webkit-search-cancel-button, 371 | input[type="search"]::-webkit-search-decoration { 372 | -webkit-appearance: none; 373 | } 374 | 375 | /** 376 | * Define consistent border, margin, and padding. 377 | */ 378 | 379 | fieldset { 380 | border: 1px solid #c0c0c0; 381 | margin: 0 2px; 382 | padding: 0.35em 0.625em 0.75em; 383 | } 384 | 385 | /** 386 | * 1. Correct `color` not being inherited in IE 8/9/10/11. 387 | * 2. Remove padding so people aren't caught out if they zero out fieldsets. 388 | */ 389 | 390 | legend { 391 | border: 0; /* 1 */ 392 | padding: 0; /* 2 */ 393 | } 394 | 395 | /** 396 | * Remove default vertical scrollbar in IE 8/9/10/11. 397 | */ 398 | 399 | textarea { 400 | overflow: auto; 401 | } 402 | 403 | /** 404 | * Don't inherit the `font-weight` (applied by a rule above). 405 | * NOTE: the default cannot safely be changed in Chrome and Safari on OS X. 406 | */ 407 | 408 | optgroup { 409 | font-weight: bold; 410 | } 411 | 412 | /* Tables 413 | ========================================================================== */ 414 | 415 | /** 416 | * Remove most spacing between table cells. 417 | */ 418 | 419 | table { 420 | border-collapse: collapse; 421 | border-spacing: 0; 422 | } 423 | 424 | td, 425 | th { 426 | padding: 0; 427 | } 428 | -------------------------------------------------------------------------------- /app/static/js/vendor/angular-sanitize.min.js.map: -------------------------------------------------------------------------------- 1 | { 2 | "version":3, 3 | "file":"angular-sanitize.min.js", 4 | "lineCount":15, 5 | "mappings":"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", 6 | "sources":["angular-sanitize.js"], 7 | "names":["window","angular","undefined","sanitizeText","chars","buf","htmlSanitizeWriter","writer","noop","join","makeMap","str","obj","items","split","i","length","htmlParser","html","handler","parseStartTag","tag","tagName","rest","unary","lowercase","blockElements","stack","last","inlineElements","parseEndTag","optionalEndTagElements","voidElements","push","attrs","replace","ATTR_REGEXP","match","name","doubleQuotedValue","singleQuotedValue","unquotedValue","decodeEntities","start","pos","end","index","text","stack.last","specialElements","RegExp","all","COMMENT_REGEXP","CDATA_REGEXP","indexOf","lastIndexOf","comment","substring","DOCTYPE_REGEXP","test","BEGING_END_TAGE_REGEXP","END_TAG_REGEXP","BEGIN_TAG_REGEXP","START_TAG_REGEXP","$sanitizeMinErr","value","hiddenPre","innerHTML","textContent","encodeEntities","SURROGATE_PAIR_REGEXP","hi","charCodeAt","low","NON_ALPHANUMERIC_REGEXP","uriValidator","ignore","out","bind","validElements","forEach","key","lkey","isImage","validAttrs","uriAttrs","$$minErr","optionalEndTagBlockElements","optionalEndTagInlineElements","extend","svgElements","htmlAttrs","svgAttrs","document","createElement","module","provider","$SanitizeProvider","$get","$$sanitizeUri","uri","filter","$sanitize","LINKY_URL_REGEXP","MAILTO_REGEXP","target","addText","addLink","url","isDefined","raw","substr"] 8 | } 9 | -------------------------------------------------------------------------------- /app/static/css/social-share-kit.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Social Share Kit v1.0.1 (http://socialsharekit.com) 3 | * Copyright 2015 Social Share Kit / Kaspars Sprogis. 4 | * Licensed under Creative Commons Attribution-NonCommercial 3.0 license: 5 | * https://github.com/darklow/social-share-kit/blob/master/LICENSE 6 | * --- 7 | */@font-face{font-family:"social-share-kit";src:url("../fonts/social-share-kit.eot");src:url("../fonts/social-share-kit.eot?#iefix") format("embedded-opentype"),url("../fonts/social-share-kit.woff") format("woff"),url("../fonts/social-share-kit.ttf") format("truetype"),url("../fonts/social-share-kit.svg#social-share-kit") format("svg");font-weight:normal;font-style:normal}.ssk:before{display:inline-block;font-family:"social-share-kit" !important;font-style:normal !important;font-weight:normal !important;font-variant:normal !important;text-transform:none !important;speak:none;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.ssk-facebook:before{content:"a";text-indent:4px;margin-right:-4px}.ssk-twitter:before{content:"b"}.ssk-google-plus:before{content:"c"}.ssk-email:before{content:"d";top:-1px;position:relative}.ssk-pinterest:before{content:"e"}.ssk-tumblr:before{content:"f"}.ssk-linkedin:before{content:"g"}.ssk-github:before{content:"h"}.ssk-vk:before{content:"i"}.ssk-instagram:before{content:"j"}.ssk-amazon:before{content:"k"}.ssk-skype:before{content:"s"}.ssk-youtube:before{content:"x"}.ssk-vimeo:before{content:"u"}.ssk-ebay:before{content:"p"}.ssk-apple:before{content:"l"}.ssk-behance:before{content:"q"}.ssk-dribble:before{content:"n"}.ssk-android:before{content:"o"}.ssk{background-color:#757575;color:white;display:inline-block;font-size:22px;line-height:1px;margin-right:2px;margin-bottom:2px;padding:7px;text-align:center;text-decoration:none;transition:background-color .1s;-webkit-transition:background-color .1s;-moz-transition:background-color .1s;-ms-transition:background-color .1s;-o-transition:background-color .1s}.ssk:before,.ssk .glyphicon{font-size:22px;top:0}.ssk.ssk-xs,.ssk-xs>.ssk{padding:4px}.ssk.ssk-xs:before,.ssk-xs>.ssk:before,.ssk.ssk-xs .glyphicon,.ssk-xs>.ssk .glyphicon{font-size:15px}.ssk.ssk-sm,.ssk-sm>.ssk{padding:5px}.ssk.ssk-sm:before,.ssk-sm>.ssk:before,.ssk.ssk-sm .glyphicon,.ssk-sm>.ssk .glyphicon{font-size:20px}.ssk.ssk-lg,.ssk-lg>.ssk{padding:9px}.ssk.ssk-lg:before,.ssk-lg>.ssk:before,.ssk.ssk-lg .glyphicon,.ssk-lg>.ssk .glyphicon{font-size:28px}.ssk:last-child{margin-right:0}.ssk:hover{background-color:#424242}.ssk:hover,.ssk:focus{color:#fff;text-decoration:none}.ssk.ssk-round,.ssk-round .ssk{border-radius:50%}.ssk.ssk-round:before,.ssk-round .ssk:before{text-indent:0;margin-right:0}.ssk.ssk-rounded,.ssk-rounded .ssk{border-radius:15%}.ssk.ssk-icon{color:#757575;padding:2px;font-size:24px}.ssk.ssk-icon,.ssk.ssk-icon:hover{background-color:transparent}.ssk.ssk-icon:hover{color:#424242}.ssk.ssk-icon.ssk-xs,.ssk-xs>.ssk.ssk-icon{font-size:16px}.ssk.ssk-icon.ssk-sm,.ssk-sm>.ssk.ssk-icon{font-size:20px}.ssk.ssk-icon.ssk-lg,.ssk-lg>.ssk.ssk-icon{font-size:28px}.ssk.ssk-text{overflow:hidden;font-size:17px;line-height:normal;padding-right:10px}.ssk.ssk-text:before,.ssk.ssk-text .glyphicon{margin:-7px 10px -7px -7px;padding:7px;background-color:rgba(0,0,0,0.15);vertical-align:bottom;text-indent:0}.ssk-block .ssk.ssk-text{display:block;margin-right:0;text-align:left}.ssk.ssk-text.ssk-xs,.ssk-xs>.ssk.ssk-text{font-size:12px;padding-right:6px}.ssk.ssk-text.ssk-xs:before,.ssk-xs>.ssk.ssk-text:before,.ssk.ssk-text.ssk-xs .glyphicon,.ssk-xs>.ssk.ssk-text .glyphicon{margin:-4px 6px -4px -4px;padding:4px}.ssk.ssk-text.ssk-sm,.ssk-sm>.ssk.ssk-text{font-size:16px;padding-right:7px}.ssk.ssk-text.ssk-sm:before,.ssk-sm>.ssk.ssk-text:before,.ssk.ssk-text.ssk-sm .glyphicon,.ssk-sm>.ssk.ssk-text .glyphicon{margin:-5px 7px -5px -5px;padding:5px}.ssk.ssk-text.ssk-lg,.ssk-lg>.ssk.ssk-text{font-size:22px;padding-right:13px}.ssk.ssk-text.ssk-lg:before,.ssk-lg>.ssk.ssk-text:before,.ssk.ssk-text.ssk-lg .glyphicon,.ssk-lg>.ssk.ssk-text .glyphicon{margin:-9px 13px -9px -9px;padding:9px}.ssk-group,.ssk-sticky{font-size:0}.ssk-sticky{top:0;position:fixed;z-index:2000}.ssk-sticky .ssk{transition:padding .1s ease-out;-webkit-transition:padding .1s ease-out;-moz-transition:padding .1s ease-out;-ms-transition:padding .1s ease-out;-o-transition:padding .1s ease-out;margin:0}.ssk-sticky.ssk-left .ssk,.ssk-sticky.ssk-right .ssk{display:block;clear:both}.ssk-sticky.ssk-left.ssk-center,.ssk-sticky.ssk-right.ssk-center{top:50%;transform:translateY(-50%);-webkit-transform:translateY(-50%);-moz-transform:translateY(-50%);-ms-transform:translateY(-50%);-o-transform:translateY(-50%)}.ssk-sticky.ssk-left{left:0}.ssk-sticky.ssk-left .ssk{float:left}.ssk-sticky.ssk-left .ssk:hover{padding-left:15px}.ssk-sticky.ssk-right{right:0}.ssk-sticky.ssk-right .ssk{float:right}.ssk-sticky.ssk-right .ssk:hover{padding-right:15px}.ssk-sticky.ssk-bottom{font-size:0;top:auto;bottom:0}.ssk-sticky.ssk-bottom.ssk-center{left:50%;right:auto;transform:translateX(-50%);-webkit-transform:translateX(-50%);-moz-transform:translateX(-50%);-ms-transform:translateX(-50%);-o-transform:translateX(-50%)}.ssk-sticky.ssk-bottom .ssk{vertical-align:bottom}.ssk-sticky.ssk-bottom .ssk:hover{padding-bottom:15px}.ssk-sticky.ssk-round.ssk-xs .ssk:hover{padding:8px}.ssk-sticky.ssk-round.ssk-sm .ssk:hover{padding:9px}.ssk-sticky.ssk-round .ssk:hover{padding:11px}.ssk-sticky.ssk-round.ssk-lg .ssk:hover{padding:13px}@media (max-width:767px){.ssk-sticky{display:none}}.ssk-count{padding-top:20px}.ssk-count .ssk{position:relative}.ssk-count .ssk-num{border-radius:4px;color:#8f8f8f;background-color:rgba(50,50,50,0.03);display:block;font-size:12px;left:0;line-height:20px;position:absolute;right:0;text-align:center;top:-20px}.ssk-count.ssk-sticky{padding-top:0}.ssk-count.ssk-sticky.ssk-left .ssk-num,.ssk-count.ssk-sticky.ssk-right .ssk-num{top:20%;background-color:transparent}.ssk-count.ssk-sticky.ssk-left .ssk-num{left:100%;margin-left:5px}.ssk-count.ssk-sticky.ssk-right .ssk-num{right:115%;margin-left:-100%;text-align:right}.ssk-facebook{background-color:#255c95}.ssk-grayscale>.ssk-facebook{background-color:#757575}.ssk-facebook:hover{background-color:#1b436c}.ssk-facebook:hover{background-color:#1b436c}.ssk-grayscale>.ssk-facebook:hover{background-color:#255c95}.ssk-facebook.ssk-icon{color:#255c95}.ssk-facebook.ssk-icon:hover{color:#1b436c}.ssk-facebook.ssk-icon:before{text-indent:0;margin-right:0}.ssk-twitter{background-color:#00b4e0}.ssk-grayscale>.ssk-twitter{background-color:#757575}.ssk-twitter:hover{background-color:#008bad}.ssk-twitter:hover{background-color:#008bad}.ssk-grayscale>.ssk-twitter:hover{background-color:#00b4e0}.ssk-twitter.ssk-icon{color:#00b4e0}.ssk-twitter.ssk-icon:hover{color:#008bad}.ssk-google-plus{background-color:#f1403a}.ssk-grayscale>.ssk-google-plus{background-color:#757575}.ssk-google-plus:hover{background-color:#e81810}.ssk-google-plus:hover{background-color:#e81810}.ssk-grayscale>.ssk-google-plus:hover{background-color:#f1403a}.ssk-google-plus.ssk-icon{color:#f1403a}.ssk-google-plus.ssk-icon:hover{color:#e81810}.ssk-pinterest{background-color:#cb2027}.ssk-grayscale>.ssk-pinterest{background-color:#757575}.ssk-pinterest:hover{background-color:#9f191f}.ssk-pinterest:hover{background-color:#9f191f}.ssk-grayscale>.ssk-pinterest:hover{background-color:#cb2027}.ssk-pinterest.ssk-icon{color:#cb2027}.ssk-pinterest.ssk-icon:hover{color:#9f191f}.ssk-tumblr{background-color:#395773}.ssk-grayscale>.ssk-tumblr{background-color:#757575}.ssk-tumblr:hover{background-color:#283d51}.ssk-tumblr:hover{background-color:#283d51}.ssk-grayscale>.ssk-tumblr:hover{background-color:#395773}.ssk-tumblr.ssk-icon{color:#395773}.ssk-tumblr.ssk-icon:hover{color:#283d51}.ssk-email{background-color:#757575}.ssk-grayscale>.ssk-email{background-color:#757575}.ssk-email:hover{background-color:#5b5b5b}.ssk-email:hover{background-color:#5b5b5b}.ssk-grayscale>.ssk-email:hover{background-color:#757575}.ssk-grayscale>.ssk-email:hover{background-color:#5b5b5b}.ssk-email.ssk-icon{color:#757575}.ssk-email.ssk-icon:hover{color:#5b5b5b}.ssk-vk{background-color:#54769a}.ssk-grayscale>.ssk-vk{background-color:#757575}.ssk-vk:hover{background-color:#425d79}.ssk-vk:hover{background-color:#425d79}.ssk-grayscale>.ssk-vk:hover{background-color:#54769a}.ssk-vk.ssk-icon{color:#54769a}.ssk-vk.ssk-icon:hover{color:#425d79}.ssk-linkedin{background-color:#1c87bd}.ssk-grayscale>.ssk-linkedin{background-color:#757575}.ssk-linkedin:hover{background-color:#156791}.ssk-linkedin:hover{background-color:#156791}.ssk-grayscale>.ssk-linkedin:hover{background-color:#1c87bd}.ssk-linkedin.ssk-icon{color:#1c87bd}.ssk-linkedin.ssk-icon:hover{color:#156791}.ssk-turquoise{background-color:#1abc9c}.ssk-turquoise:hover{background-color:#148f77}.ssk-emerald{background-color:#2ecc71}.ssk-emerald:hover{background-color:#25a25a}.ssk-peter-river{background-color:#3498db}.ssk-peter-river:hover{background-color:#217dbb}.ssk-belize-hole{background-color:#2980b9}.ssk-belize-hole:hover{background-color:#20638f}.ssk-amethyst{background-color:#9b59b6}.ssk-amethyst:hover{background-color:#804399}.ssk-wisteria{background-color:#8e44ad}.ssk-wisteria:hover{background-color:#703688}.ssk-wet-asphalt{background-color:#34495e}.ssk-wet-asphalt:hover{background-color:#222f3d}.ssk-midnight-blue{background-color:#2c3e50}.ssk-midnight-blue:hover{background-color:#1a242f}.ssk-green-sea{background-color:#16a085}.ssk-green-sea:hover{background-color:#107360}.ssk-nephritis{background-color:#27ae60}.ssk-nephritis:hover{background-color:#1e8449}.ssk-sunflower{background-color:#f1c40f}.ssk-sunflower:hover{background-color:#c29d0b}.ssk-orange{background-color:#f39c12}.ssk-orange:hover{background-color:#c87f0a}.ssk-carrot{background-color:#e67e22}.ssk-carrot:hover{background-color:#bf6516}.ssk-pumpkin{background-color:#d35400}.ssk-pumpkin:hover{background-color:#a04000}.ssk-alizarin{background-color:#e74c3c}.ssk-alizarin:hover{background-color:#d62c1a}.ssk-pomegranate{background-color:#c0392b}.ssk-pomegranate:hover{background-color:#962d22}.ssk-clouds{background-color:#cfd9db}.ssk-clouds:hover{background-color:#b1c2c6}.ssk-concrete{background-color:#95a5a6}.ssk-concrete:hover{background-color:#798d8f}.ssk-silver{background-color:#bdc3c7}.ssk-silver:hover{background-color:#a1aab0}.ssk-asbestos{background-color:#7f8c8d}.ssk-asbestos:hover{background-color:#667273}.ssk-dark-gray{background-color:#555}.ssk-dark-gray:hover{background-color:#3b3b3b}.ssk-black{background-color:#333}.ssk-black:hover{background-color:#1a1a1a} -------------------------------------------------------------------------------- /app/static/js/main.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | buildHeaderBackground($(window).height(), 100, 5); 3 | resizeDiv($('#scrolling-background'), 500); 4 | resizeDiv($('.transparent-overlay'), 500); 5 | resizeDiv($('.header-container'), 500); 6 | 7 | var app = angular.module('movieInfo', ['ngSanitize']); 8 | 9 | // Resolve conflict between Jinja2 tags and Angular tags 10 | // Jinja2 uses {{}} so make Angular use {[]} 11 | app.config(['$interpolateProvider', function($interpolateProvider) { 12 | $interpolateProvider.startSymbol('{['); 13 | $interpolateProvider.endSymbol(']}'); 14 | }]); 15 | 16 | app.factory('MovieService', function() { 17 | return { 18 | 'movie' : {} 19 | }; 20 | }); 21 | 22 | app.controller('SearchController', ['$rootScope', '$http', 'MovieService', 23 | function($rootScope, $http, MovieService) { 24 | $rootScope.movie = MovieService.movie; 25 | var self = this; 26 | 27 | this.searchMovie = function(imdb_id) { 28 | $('#no-results').hide(); 29 | $('#movie-results').animate({opacity: 0}, 200, 'linear', function() { 30 | var serverURL; 31 | 32 | if (imdb_id) { 33 | serverURL = '/api/get_movie?imdb_id=' + imdb_id; 34 | } else { 35 | var title = $rootScope.movie.input_title; 36 | var year = $rootScope.movie.input_year || ''; 37 | 38 | // Validate results 39 | if (!self.isValidInput(title, 'is_not_empty')) { 40 | $('#movie-title').popover('show'); 41 | return; 42 | } 43 | if (!self.isValidInput(+year, 'is_valid_year')) { 44 | $('#movie-year').popover('show'); 45 | return; 46 | } 47 | 48 | $('#movie-title').popover('destroy'); 49 | $('#movie-year').popover('destroy'); 50 | serverURL = '/api/get_movie?movie_title=' + title + '&movie_year=' + year; 51 | 52 | // Results are valid, go ahead and search 53 | startLoadingSpinner(); 54 | } 55 | 56 | // Search for the movie 57 | $http.get(serverURL, {timeout: 5000} 58 | ).success(function(response) { 59 | 60 | $rootScope.movie = response; 61 | 62 | }).error(function(data, status, headers, config) { 63 | if (status === 404) { 64 | alertModal('Movie Not Found', '

Sorry! We could not find a movie with that title.

'); 65 | } else { 66 | alertModal('OMDb is down', '

It looks like the OMDb server where we fetch our data is down. If you try again later the server may be back online.

'); 67 | } 68 | 69 | $rootScope.movie = MovieService.movie; 70 | 71 | stopLoadingSpinner(); 72 | $('#no-results').slideDown(500); 73 | }); 74 | }); 75 | }; 76 | 77 | this.isValidInput = function(input, condition) { 78 | // Ensures the movie title is not blank and the year entered is reasonable 79 | switch (condition) { 80 | case undefined: 81 | return false; 82 | case 'is_not_empty': 83 | return !!input; 84 | case 'is_valid_year': 85 | return input === 0 || !isNaN(input) && input > 1000 && input <= new Date().getFullYear(); 86 | default: 87 | return false; 88 | } 89 | }; 90 | }]); 91 | 92 | app.controller('MovieController', ['$scope', '$rootScope', 'MovieService', 93 | function($scope, $rootScope, MovieService){ 94 | $rootScope.movie = MovieService.movie; 95 | var self = this; 96 | self.movie = {}; 97 | 98 | $rootScope.$watch(function() { 99 | return $rootScope.movie.title; 100 | }, function() { 101 | self.movie = $rootScope.movie; 102 | self.movie.parsedRated = self.getRating(); 103 | self.movie.parsedStars = self.getStars(self.movie.imdb_rating); 104 | self.movie.parsedDirectors = self.getRole("Director", self.movie.director); 105 | self.movie.parsedActors = self.getRole("Actor", self.movie.actors); 106 | self.movie.parsedWriters = self.getRole("Writer", self.movie.writer); 107 | self.movie.parsedAwards = self.getRole("Award", self.movie.awards); 108 | self.movie.facebookURL = self.getShareUrl("facebook", self.movie.title, self.movie.imdb_id); 109 | self.movie.twitterURL = self.getShareUrl("twitter", self.movie.title, self.movie.imdb_id); 110 | self.movie.gplusURL = self.getShareUrl("gplus", self.movie.title, self.movie.imdb_id); 111 | 112 | if (self.movie.imdb_id) { 113 | window.history.pushState(null, null, '/movie/' + self.movie.imdb_id); 114 | scrollToResults(); 115 | } 116 | }); 117 | 118 | this.getRating = function() { 119 | if (this.movie.rated && this.movie.rated !== 'Not Rated' && this.movie.rated !== 'N/A') { 120 | return 'Rated ' + this.movie.rated; 121 | } else { 122 | return 'Unrated'; 123 | } 124 | }; 125 | 126 | this.getStars = function(score, maxStars, numStars) { 127 | // Conerts an n-star system into a 5-star system. 128 | score = parseFloat(score); 129 | maxStars = maxStars || 10; 130 | numStars = numStars || 5; 131 | 132 | var stars = score / maxStars; 133 | 134 | var filledStars = Math.floor(stars * numStars); 135 | var halfStars = (stars * numStars - filledStars) > 0.4; // Only make a half star if the LSV is > 0.4 136 | var emptyStars = numStars - filledStars - halfStars; 137 | 138 | var result = ''; 139 | for (var i = 0; i < filledStars; i++) { 140 | result += ''; 141 | } 142 | if (halfStars) { 143 | result += ''; 144 | } 145 | for (var j = 0; j < emptyStars; j++) { 146 | result += ''; 147 | } 148 | return result; 149 | }; 150 | 151 | this.getRole = function(role, people) { 152 | if (!people || people === 'N/A') { return; } 153 | return '' + this.pluralize(role, people) +': ' + 154 | '

' + people + '

'; 155 | }; 156 | 157 | this.pluralize = function(role, people) { 158 | // Pluralize the noun if there is more than one person 159 | // E.g. "Director" -> "Directors" 160 | if (people.indexOf(',') > -1 || role === 'Award') { 161 | return role + 's'; 162 | } else { 163 | return role; 164 | } 165 | }; 166 | 167 | this.getShareUrl = function(channel, movieTitle, imdbID) { 168 | // Generate links for social media buttons 169 | switch (channel) { 170 | case 'facebook': 171 | return 'https://www.facebook.com/sharer/sharer.php?u=' + window.location.origin + '/movie/' + imdbID; 172 | case 'twitter': 173 | var statusMesssage; 174 | if (movieTitle !== 'N/A') { 175 | statusMesssage = encodeURIComponent('Check out the movie '+ movieTitle + ': '); 176 | } else { 177 | statusMesssage = ''; 178 | } 179 | return 'https://twitter.com/home?status=' + statusMesssage + window.location.origin + '/movie/' + imdbID; 180 | case 'gplus': 181 | return 'https://plus.google.com/share?url=' + window.location.origin + '/movie/' + imdbID; 182 | } 183 | }; 184 | }]); 185 | 186 | // Popovers to display validation errors 187 | $('#movie-title').popover({'trigger':'manual'}); 188 | $('#movie-year').popover({'trigger':'manual'}); 189 | $('#movie-title').on('focus', function() { 190 | $(this).popover('destroy'); 191 | }); 192 | $('#movie-year').on('focus', function() { 193 | $(this).popover('destroy'); 194 | }); 195 | 196 | 197 | function buildHeaderBackground(containerHeight, rowHeight, minRows) { 198 | // Creates the wall of movie-posters in the background that is animated via CSS. 199 | var container = $('#scrolling-background'); 200 | 201 | var rowsNeeded = Math.ceil(containerHeight / rowHeight); 202 | 203 | // Minimum 5 rows 204 | rowsNeeded = rowsNeeded < 5 ? 5 : rowsNeeded; 205 | 206 | for (var i = 0; i < rowsNeeded; i++) { 207 | direction = i % 2 === 0 ? 'left' : 'right'; 208 | container.append('
'); 209 | } 210 | } 211 | 212 | function resizeDiv(div, minHeight) { 213 | // Resizes a div to match the window height 214 | if ($(window).height() > minHeight) { 215 | div.css('height', $(window).height()); 216 | } 217 | } 218 | 219 | function scrollToResults() { 220 | $('#movie-results').animate({opacity: 1}, 200, 'linear', function() { 221 | $('html,body').animate({ 222 | scrollTop: $('#results-page').offset().top 223 | }, 500); 224 | }); 225 | stopLoadingSpinner(); 226 | } 227 | 228 | function startLoadingSpinner() { 229 | // Start the loading spinner 230 | $('.loading-spinner').addClass('loading-enabled'); 231 | $('#search-btn').addClass('loading-disabled'); 232 | } 233 | 234 | function stopLoadingSpinner() { 235 | // Stop the loading spinner 236 | $('.loading-spinner').removeClass('loading-enabled'); 237 | $('#search-btn').removeClass('loading-disabled'); 238 | } 239 | 240 | function alertModal(title, body) { 241 | // Display error message to the user in a modal 242 | $('#alert-modal-title').html(title); 243 | $('#alert-modal-body').html(body); 244 | $('#alert-modal').modal('show'); 245 | } 246 | })(); 247 | -------------------------------------------------------------------------------- /app/static/fonts/social-share-kit.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Generated by Fontastic.me 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /app/static/css/font-awesome.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Font Awesome 4.3.0 by @davegandy - http://fontawesome.io - @fontawesome 3 | * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) 4 | */@font-face{font-family:'FontAwesome';src:url('../fonts/fontawesome-webfont.eot?v=4.3.0');src:url('../fonts/fontawesome-webfont.eot?#iefix&v=4.3.0') format('embedded-opentype'),url('../fonts/fontawesome-webfont.woff2?v=4.3.0') format('woff2'),url('../fonts/fontawesome-webfont.woff?v=4.3.0') format('woff'),url('../fonts/fontawesome-webfont.ttf?v=4.3.0') format('truetype'),url('../fonts/fontawesome-webfont.svg?v=4.3.0#fontawesomeregular') format('svg');font-weight:normal;font-style:normal}.fa{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;transform:translate(0, 0)}.fa-lg{font-size:1.33333333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571429em;text-align:center}.fa-ul{padding-left:0;margin-left:2.14285714em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14285714em;width:2.14285714em;top:.14285714em;text-align:center}.fa-li.fa-lg{left:-1.85714286em}.fa-border{padding:.2em .25em .15em;border:solid .08em #eee;border-radius:.1em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left{margin-right:.3em}.fa.pull-right{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s infinite linear;animation:fa-spin 2s infinite linear}.fa-pulse{-webkit-animation:fa-spin 1s infinite steps(8);animation:fa-spin 1s infinite steps(8)}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-rotate-90{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=1);-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=2);-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=3);-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1);-webkit-transform:scale(-1, 1);-ms-transform:scale(-1, 1);transform:scale(-1, 1)}.fa-flip-vertical{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1);-webkit-transform:scale(1, -1);-ms-transform:scale(1, -1);transform:scale(1, -1)}:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270,:root .fa-flip-horizontal,:root .fa-flip-vertical{filter:none}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:"\f000"}.fa-music:before{content:"\f001"}.fa-search:before{content:"\f002"}.fa-envelope-o:before{content:"\f003"}.fa-heart:before{content:"\f004"}.fa-star:before{content:"\f005"}.fa-star-o:before{content:"\f006"}.fa-user:before{content:"\f007"}.fa-film:before{content:"\f008"}.fa-th-large:before{content:"\f009"}.fa-th:before{content:"\f00a"}.fa-th-list:before{content:"\f00b"}.fa-check:before{content:"\f00c"}.fa-remove:before,.fa-close:before,.fa-times:before{content:"\f00d"}.fa-search-plus:before{content:"\f00e"}.fa-search-minus:before{content:"\f010"}.fa-power-off:before{content:"\f011"}.fa-signal:before{content:"\f012"}.fa-gear:before,.fa-cog:before{content:"\f013"}.fa-trash-o:before{content:"\f014"}.fa-home:before{content:"\f015"}.fa-file-o:before{content:"\f016"}.fa-clock-o:before{content:"\f017"}.fa-road:before{content:"\f018"}.fa-download:before{content:"\f019"}.fa-arrow-circle-o-down:before{content:"\f01a"}.fa-arrow-circle-o-up:before{content:"\f01b"}.fa-inbox:before{content:"\f01c"}.fa-play-circle-o:before{content:"\f01d"}.fa-rotate-right:before,.fa-repeat:before{content:"\f01e"}.fa-refresh:before{content:"\f021"}.fa-list-alt:before{content:"\f022"}.fa-lock:before{content:"\f023"}.fa-flag:before{content:"\f024"}.fa-headphones:before{content:"\f025"}.fa-volume-off:before{content:"\f026"}.fa-volume-down:before{content:"\f027"}.fa-volume-up:before{content:"\f028"}.fa-qrcode:before{content:"\f029"}.fa-barcode:before{content:"\f02a"}.fa-tag:before{content:"\f02b"}.fa-tags:before{content:"\f02c"}.fa-book:before{content:"\f02d"}.fa-bookmark:before{content:"\f02e"}.fa-print:before{content:"\f02f"}.fa-camera:before{content:"\f030"}.fa-font:before{content:"\f031"}.fa-bold:before{content:"\f032"}.fa-italic:before{content:"\f033"}.fa-text-height:before{content:"\f034"}.fa-text-width:before{content:"\f035"}.fa-align-left:before{content:"\f036"}.fa-align-center:before{content:"\f037"}.fa-align-right:before{content:"\f038"}.fa-align-justify:before{content:"\f039"}.fa-list:before{content:"\f03a"}.fa-dedent:before,.fa-outdent:before{content:"\f03b"}.fa-indent:before{content:"\f03c"}.fa-video-camera:before{content:"\f03d"}.fa-photo:before,.fa-image:before,.fa-picture-o:before{content:"\f03e"}.fa-pencil:before{content:"\f040"}.fa-map-marker:before{content:"\f041"}.fa-adjust:before{content:"\f042"}.fa-tint:before{content:"\f043"}.fa-edit:before,.fa-pencil-square-o:before{content:"\f044"}.fa-share-square-o:before{content:"\f045"}.fa-check-square-o:before{content:"\f046"}.fa-arrows:before{content:"\f047"}.fa-step-backward:before{content:"\f048"}.fa-fast-backward:before{content:"\f049"}.fa-backward:before{content:"\f04a"}.fa-play:before{content:"\f04b"}.fa-pause:before{content:"\f04c"}.fa-stop:before{content:"\f04d"}.fa-forward:before{content:"\f04e"}.fa-fast-forward:before{content:"\f050"}.fa-step-forward:before{content:"\f051"}.fa-eject:before{content:"\f052"}.fa-chevron-left:before{content:"\f053"}.fa-chevron-right:before{content:"\f054"}.fa-plus-circle:before{content:"\f055"}.fa-minus-circle:before{content:"\f056"}.fa-times-circle:before{content:"\f057"}.fa-check-circle:before{content:"\f058"}.fa-question-circle:before{content:"\f059"}.fa-info-circle:before{content:"\f05a"}.fa-crosshairs:before{content:"\f05b"}.fa-times-circle-o:before{content:"\f05c"}.fa-check-circle-o:before{content:"\f05d"}.fa-ban:before{content:"\f05e"}.fa-arrow-left:before{content:"\f060"}.fa-arrow-right:before{content:"\f061"}.fa-arrow-up:before{content:"\f062"}.fa-arrow-down:before{content:"\f063"}.fa-mail-forward:before,.fa-share:before{content:"\f064"}.fa-expand:before{content:"\f065"}.fa-compress:before{content:"\f066"}.fa-plus:before{content:"\f067"}.fa-minus:before{content:"\f068"}.fa-asterisk:before{content:"\f069"}.fa-exclamation-circle:before{content:"\f06a"}.fa-gift:before{content:"\f06b"}.fa-leaf:before{content:"\f06c"}.fa-fire:before{content:"\f06d"}.fa-eye:before{content:"\f06e"}.fa-eye-slash:before{content:"\f070"}.fa-warning:before,.fa-exclamation-triangle:before{content:"\f071"}.fa-plane:before{content:"\f072"}.fa-calendar:before{content:"\f073"}.fa-random:before{content:"\f074"}.fa-comment:before{content:"\f075"}.fa-magnet:before{content:"\f076"}.fa-chevron-up:before{content:"\f077"}.fa-chevron-down:before{content:"\f078"}.fa-retweet:before{content:"\f079"}.fa-shopping-cart:before{content:"\f07a"}.fa-folder:before{content:"\f07b"}.fa-folder-open:before{content:"\f07c"}.fa-arrows-v:before{content:"\f07d"}.fa-arrows-h:before{content:"\f07e"}.fa-bar-chart-o:before,.fa-bar-chart:before{content:"\f080"}.fa-twitter-square:before{content:"\f081"}.fa-facebook-square:before{content:"\f082"}.fa-camera-retro:before{content:"\f083"}.fa-key:before{content:"\f084"}.fa-gears:before,.fa-cogs:before{content:"\f085"}.fa-comments:before{content:"\f086"}.fa-thumbs-o-up:before{content:"\f087"}.fa-thumbs-o-down:before{content:"\f088"}.fa-star-half:before{content:"\f089"}.fa-heart-o:before{content:"\f08a"}.fa-sign-out:before{content:"\f08b"}.fa-linkedin-square:before{content:"\f08c"}.fa-thumb-tack:before{content:"\f08d"}.fa-external-link:before{content:"\f08e"}.fa-sign-in:before{content:"\f090"}.fa-trophy:before{content:"\f091"}.fa-github-square:before{content:"\f092"}.fa-upload:before{content:"\f093"}.fa-lemon-o:before{content:"\f094"}.fa-phone:before{content:"\f095"}.fa-square-o:before{content:"\f096"}.fa-bookmark-o:before{content:"\f097"}.fa-phone-square:before{content:"\f098"}.fa-twitter:before{content:"\f099"}.fa-facebook-f:before,.fa-facebook:before{content:"\f09a"}.fa-github:before{content:"\f09b"}.fa-unlock:before{content:"\f09c"}.fa-credit-card:before{content:"\f09d"}.fa-rss:before{content:"\f09e"}.fa-hdd-o:before{content:"\f0a0"}.fa-bullhorn:before{content:"\f0a1"}.fa-bell:before{content:"\f0f3"}.fa-certificate:before{content:"\f0a3"}.fa-hand-o-right:before{content:"\f0a4"}.fa-hand-o-left:before{content:"\f0a5"}.fa-hand-o-up:before{content:"\f0a6"}.fa-hand-o-down:before{content:"\f0a7"}.fa-arrow-circle-left:before{content:"\f0a8"}.fa-arrow-circle-right:before{content:"\f0a9"}.fa-arrow-circle-up:before{content:"\f0aa"}.fa-arrow-circle-down:before{content:"\f0ab"}.fa-globe:before{content:"\f0ac"}.fa-wrench:before{content:"\f0ad"}.fa-tasks:before{content:"\f0ae"}.fa-filter:before{content:"\f0b0"}.fa-briefcase:before{content:"\f0b1"}.fa-arrows-alt:before{content:"\f0b2"}.fa-group:before,.fa-users:before{content:"\f0c0"}.fa-chain:before,.fa-link:before{content:"\f0c1"}.fa-cloud:before{content:"\f0c2"}.fa-flask:before{content:"\f0c3"}.fa-cut:before,.fa-scissors:before{content:"\f0c4"}.fa-copy:before,.fa-files-o:before{content:"\f0c5"}.fa-paperclip:before{content:"\f0c6"}.fa-save:before,.fa-floppy-o:before{content:"\f0c7"}.fa-square:before{content:"\f0c8"}.fa-navicon:before,.fa-reorder:before,.fa-bars:before{content:"\f0c9"}.fa-list-ul:before{content:"\f0ca"}.fa-list-ol:before{content:"\f0cb"}.fa-strikethrough:before{content:"\f0cc"}.fa-underline:before{content:"\f0cd"}.fa-table:before{content:"\f0ce"}.fa-magic:before{content:"\f0d0"}.fa-truck:before{content:"\f0d1"}.fa-pinterest:before{content:"\f0d2"}.fa-pinterest-square:before{content:"\f0d3"}.fa-google-plus-square:before{content:"\f0d4"}.fa-google-plus:before{content:"\f0d5"}.fa-money:before{content:"\f0d6"}.fa-caret-down:before{content:"\f0d7"}.fa-caret-up:before{content:"\f0d8"}.fa-caret-left:before{content:"\f0d9"}.fa-caret-right:before{content:"\f0da"}.fa-columns:before{content:"\f0db"}.fa-unsorted:before,.fa-sort:before{content:"\f0dc"}.fa-sort-down:before,.fa-sort-desc:before{content:"\f0dd"}.fa-sort-up:before,.fa-sort-asc:before{content:"\f0de"}.fa-envelope:before{content:"\f0e0"}.fa-linkedin:before{content:"\f0e1"}.fa-rotate-left:before,.fa-undo:before{content:"\f0e2"}.fa-legal:before,.fa-gavel:before{content:"\f0e3"}.fa-dashboard:before,.fa-tachometer:before{content:"\f0e4"}.fa-comment-o:before{content:"\f0e5"}.fa-comments-o:before{content:"\f0e6"}.fa-flash:before,.fa-bolt:before{content:"\f0e7"}.fa-sitemap:before{content:"\f0e8"}.fa-umbrella:before{content:"\f0e9"}.fa-paste:before,.fa-clipboard:before{content:"\f0ea"}.fa-lightbulb-o:before{content:"\f0eb"}.fa-exchange:before{content:"\f0ec"}.fa-cloud-download:before{content:"\f0ed"}.fa-cloud-upload:before{content:"\f0ee"}.fa-user-md:before{content:"\f0f0"}.fa-stethoscope:before{content:"\f0f1"}.fa-suitcase:before{content:"\f0f2"}.fa-bell-o:before{content:"\f0a2"}.fa-coffee:before{content:"\f0f4"}.fa-cutlery:before{content:"\f0f5"}.fa-file-text-o:before{content:"\f0f6"}.fa-building-o:before{content:"\f0f7"}.fa-hospital-o:before{content:"\f0f8"}.fa-ambulance:before{content:"\f0f9"}.fa-medkit:before{content:"\f0fa"}.fa-fighter-jet:before{content:"\f0fb"}.fa-beer:before{content:"\f0fc"}.fa-h-square:before{content:"\f0fd"}.fa-plus-square:before{content:"\f0fe"}.fa-angle-double-left:before{content:"\f100"}.fa-angle-double-right:before{content:"\f101"}.fa-angle-double-up:before{content:"\f102"}.fa-angle-double-down:before{content:"\f103"}.fa-angle-left:before{content:"\f104"}.fa-angle-right:before{content:"\f105"}.fa-angle-up:before{content:"\f106"}.fa-angle-down:before{content:"\f107"}.fa-desktop:before{content:"\f108"}.fa-laptop:before{content:"\f109"}.fa-tablet:before{content:"\f10a"}.fa-mobile-phone:before,.fa-mobile:before{content:"\f10b"}.fa-circle-o:before{content:"\f10c"}.fa-quote-left:before{content:"\f10d"}.fa-quote-right:before{content:"\f10e"}.fa-spinner:before{content:"\f110"}.fa-circle:before{content:"\f111"}.fa-mail-reply:before,.fa-reply:before{content:"\f112"}.fa-github-alt:before{content:"\f113"}.fa-folder-o:before{content:"\f114"}.fa-folder-open-o:before{content:"\f115"}.fa-smile-o:before{content:"\f118"}.fa-frown-o:before{content:"\f119"}.fa-meh-o:before{content:"\f11a"}.fa-gamepad:before{content:"\f11b"}.fa-keyboard-o:before{content:"\f11c"}.fa-flag-o:before{content:"\f11d"}.fa-flag-checkered:before{content:"\f11e"}.fa-terminal:before{content:"\f120"}.fa-code:before{content:"\f121"}.fa-mail-reply-all:before,.fa-reply-all:before{content:"\f122"}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:"\f123"}.fa-location-arrow:before{content:"\f124"}.fa-crop:before{content:"\f125"}.fa-code-fork:before{content:"\f126"}.fa-unlink:before,.fa-chain-broken:before{content:"\f127"}.fa-question:before{content:"\f128"}.fa-info:before{content:"\f129"}.fa-exclamation:before{content:"\f12a"}.fa-superscript:before{content:"\f12b"}.fa-subscript:before{content:"\f12c"}.fa-eraser:before{content:"\f12d"}.fa-puzzle-piece:before{content:"\f12e"}.fa-microphone:before{content:"\f130"}.fa-microphone-slash:before{content:"\f131"}.fa-shield:before{content:"\f132"}.fa-calendar-o:before{content:"\f133"}.fa-fire-extinguisher:before{content:"\f134"}.fa-rocket:before{content:"\f135"}.fa-maxcdn:before{content:"\f136"}.fa-chevron-circle-left:before{content:"\f137"}.fa-chevron-circle-right:before{content:"\f138"}.fa-chevron-circle-up:before{content:"\f139"}.fa-chevron-circle-down:before{content:"\f13a"}.fa-html5:before{content:"\f13b"}.fa-css3:before{content:"\f13c"}.fa-anchor:before{content:"\f13d"}.fa-unlock-alt:before{content:"\f13e"}.fa-bullseye:before{content:"\f140"}.fa-ellipsis-h:before{content:"\f141"}.fa-ellipsis-v:before{content:"\f142"}.fa-rss-square:before{content:"\f143"}.fa-play-circle:before{content:"\f144"}.fa-ticket:before{content:"\f145"}.fa-minus-square:before{content:"\f146"}.fa-minus-square-o:before{content:"\f147"}.fa-level-up:before{content:"\f148"}.fa-level-down:before{content:"\f149"}.fa-check-square:before{content:"\f14a"}.fa-pencil-square:before{content:"\f14b"}.fa-external-link-square:before{content:"\f14c"}.fa-share-square:before{content:"\f14d"}.fa-compass:before{content:"\f14e"}.fa-toggle-down:before,.fa-caret-square-o-down:before{content:"\f150"}.fa-toggle-up:before,.fa-caret-square-o-up:before{content:"\f151"}.fa-toggle-right:before,.fa-caret-square-o-right:before{content:"\f152"}.fa-euro:before,.fa-eur:before{content:"\f153"}.fa-gbp:before{content:"\f154"}.fa-dollar:before,.fa-usd:before{content:"\f155"}.fa-rupee:before,.fa-inr:before{content:"\f156"}.fa-cny:before,.fa-rmb:before,.fa-yen:before,.fa-jpy:before{content:"\f157"}.fa-ruble:before,.fa-rouble:before,.fa-rub:before{content:"\f158"}.fa-won:before,.fa-krw:before{content:"\f159"}.fa-bitcoin:before,.fa-btc:before{content:"\f15a"}.fa-file:before{content:"\f15b"}.fa-file-text:before{content:"\f15c"}.fa-sort-alpha-asc:before{content:"\f15d"}.fa-sort-alpha-desc:before{content:"\f15e"}.fa-sort-amount-asc:before{content:"\f160"}.fa-sort-amount-desc:before{content:"\f161"}.fa-sort-numeric-asc:before{content:"\f162"}.fa-sort-numeric-desc:before{content:"\f163"}.fa-thumbs-up:before{content:"\f164"}.fa-thumbs-down:before{content:"\f165"}.fa-youtube-square:before{content:"\f166"}.fa-youtube:before{content:"\f167"}.fa-xing:before{content:"\f168"}.fa-xing-square:before{content:"\f169"}.fa-youtube-play:before{content:"\f16a"}.fa-dropbox:before{content:"\f16b"}.fa-stack-overflow:before{content:"\f16c"}.fa-instagram:before{content:"\f16d"}.fa-flickr:before{content:"\f16e"}.fa-adn:before{content:"\f170"}.fa-bitbucket:before{content:"\f171"}.fa-bitbucket-square:before{content:"\f172"}.fa-tumblr:before{content:"\f173"}.fa-tumblr-square:before{content:"\f174"}.fa-long-arrow-down:before{content:"\f175"}.fa-long-arrow-up:before{content:"\f176"}.fa-long-arrow-left:before{content:"\f177"}.fa-long-arrow-right:before{content:"\f178"}.fa-apple:before{content:"\f179"}.fa-windows:before{content:"\f17a"}.fa-android:before{content:"\f17b"}.fa-linux:before{content:"\f17c"}.fa-dribbble:before{content:"\f17d"}.fa-skype:before{content:"\f17e"}.fa-foursquare:before{content:"\f180"}.fa-trello:before{content:"\f181"}.fa-female:before{content:"\f182"}.fa-male:before{content:"\f183"}.fa-gittip:before,.fa-gratipay:before{content:"\f184"}.fa-sun-o:before{content:"\f185"}.fa-moon-o:before{content:"\f186"}.fa-archive:before{content:"\f187"}.fa-bug:before{content:"\f188"}.fa-vk:before{content:"\f189"}.fa-weibo:before{content:"\f18a"}.fa-renren:before{content:"\f18b"}.fa-pagelines:before{content:"\f18c"}.fa-stack-exchange:before{content:"\f18d"}.fa-arrow-circle-o-right:before{content:"\f18e"}.fa-arrow-circle-o-left:before{content:"\f190"}.fa-toggle-left:before,.fa-caret-square-o-left:before{content:"\f191"}.fa-dot-circle-o:before{content:"\f192"}.fa-wheelchair:before{content:"\f193"}.fa-vimeo-square:before{content:"\f194"}.fa-turkish-lira:before,.fa-try:before{content:"\f195"}.fa-plus-square-o:before{content:"\f196"}.fa-space-shuttle:before{content:"\f197"}.fa-slack:before{content:"\f198"}.fa-envelope-square:before{content:"\f199"}.fa-wordpress:before{content:"\f19a"}.fa-openid:before{content:"\f19b"}.fa-institution:before,.fa-bank:before,.fa-university:before{content:"\f19c"}.fa-mortar-board:before,.fa-graduation-cap:before{content:"\f19d"}.fa-yahoo:before{content:"\f19e"}.fa-google:before{content:"\f1a0"}.fa-reddit:before{content:"\f1a1"}.fa-reddit-square:before{content:"\f1a2"}.fa-stumbleupon-circle:before{content:"\f1a3"}.fa-stumbleupon:before{content:"\f1a4"}.fa-delicious:before{content:"\f1a5"}.fa-digg:before{content:"\f1a6"}.fa-pied-piper:before{content:"\f1a7"}.fa-pied-piper-alt:before{content:"\f1a8"}.fa-drupal:before{content:"\f1a9"}.fa-joomla:before{content:"\f1aa"}.fa-language:before{content:"\f1ab"}.fa-fax:before{content:"\f1ac"}.fa-building:before{content:"\f1ad"}.fa-child:before{content:"\f1ae"}.fa-paw:before{content:"\f1b0"}.fa-spoon:before{content:"\f1b1"}.fa-cube:before{content:"\f1b2"}.fa-cubes:before{content:"\f1b3"}.fa-behance:before{content:"\f1b4"}.fa-behance-square:before{content:"\f1b5"}.fa-steam:before{content:"\f1b6"}.fa-steam-square:before{content:"\f1b7"}.fa-recycle:before{content:"\f1b8"}.fa-automobile:before,.fa-car:before{content:"\f1b9"}.fa-cab:before,.fa-taxi:before{content:"\f1ba"}.fa-tree:before{content:"\f1bb"}.fa-spotify:before{content:"\f1bc"}.fa-deviantart:before{content:"\f1bd"}.fa-soundcloud:before{content:"\f1be"}.fa-database:before{content:"\f1c0"}.fa-file-pdf-o:before{content:"\f1c1"}.fa-file-word-o:before{content:"\f1c2"}.fa-file-excel-o:before{content:"\f1c3"}.fa-file-powerpoint-o:before{content:"\f1c4"}.fa-file-photo-o:before,.fa-file-picture-o:before,.fa-file-image-o:before{content:"\f1c5"}.fa-file-zip-o:before,.fa-file-archive-o:before{content:"\f1c6"}.fa-file-sound-o:before,.fa-file-audio-o:before{content:"\f1c7"}.fa-file-movie-o:before,.fa-file-video-o:before{content:"\f1c8"}.fa-file-code-o:before{content:"\f1c9"}.fa-vine:before{content:"\f1ca"}.fa-codepen:before{content:"\f1cb"}.fa-jsfiddle:before{content:"\f1cc"}.fa-life-bouy:before,.fa-life-buoy:before,.fa-life-saver:before,.fa-support:before,.fa-life-ring:before{content:"\f1cd"}.fa-circle-o-notch:before{content:"\f1ce"}.fa-ra:before,.fa-rebel:before{content:"\f1d0"}.fa-ge:before,.fa-empire:before{content:"\f1d1"}.fa-git-square:before{content:"\f1d2"}.fa-git:before{content:"\f1d3"}.fa-hacker-news:before{content:"\f1d4"}.fa-tencent-weibo:before{content:"\f1d5"}.fa-qq:before{content:"\f1d6"}.fa-wechat:before,.fa-weixin:before{content:"\f1d7"}.fa-send:before,.fa-paper-plane:before{content:"\f1d8"}.fa-send-o:before,.fa-paper-plane-o:before{content:"\f1d9"}.fa-history:before{content:"\f1da"}.fa-genderless:before,.fa-circle-thin:before{content:"\f1db"}.fa-header:before{content:"\f1dc"}.fa-paragraph:before{content:"\f1dd"}.fa-sliders:before{content:"\f1de"}.fa-share-alt:before{content:"\f1e0"}.fa-share-alt-square:before{content:"\f1e1"}.fa-bomb:before{content:"\f1e2"}.fa-soccer-ball-o:before,.fa-futbol-o:before{content:"\f1e3"}.fa-tty:before{content:"\f1e4"}.fa-binoculars:before{content:"\f1e5"}.fa-plug:before{content:"\f1e6"}.fa-slideshare:before{content:"\f1e7"}.fa-twitch:before{content:"\f1e8"}.fa-yelp:before{content:"\f1e9"}.fa-newspaper-o:before{content:"\f1ea"}.fa-wifi:before{content:"\f1eb"}.fa-calculator:before{content:"\f1ec"}.fa-paypal:before{content:"\f1ed"}.fa-google-wallet:before{content:"\f1ee"}.fa-cc-visa:before{content:"\f1f0"}.fa-cc-mastercard:before{content:"\f1f1"}.fa-cc-discover:before{content:"\f1f2"}.fa-cc-amex:before{content:"\f1f3"}.fa-cc-paypal:before{content:"\f1f4"}.fa-cc-stripe:before{content:"\f1f5"}.fa-bell-slash:before{content:"\f1f6"}.fa-bell-slash-o:before{content:"\f1f7"}.fa-trash:before{content:"\f1f8"}.fa-copyright:before{content:"\f1f9"}.fa-at:before{content:"\f1fa"}.fa-eyedropper:before{content:"\f1fb"}.fa-paint-brush:before{content:"\f1fc"}.fa-birthday-cake:before{content:"\f1fd"}.fa-area-chart:before{content:"\f1fe"}.fa-pie-chart:before{content:"\f200"}.fa-line-chart:before{content:"\f201"}.fa-lastfm:before{content:"\f202"}.fa-lastfm-square:before{content:"\f203"}.fa-toggle-off:before{content:"\f204"}.fa-toggle-on:before{content:"\f205"}.fa-bicycle:before{content:"\f206"}.fa-bus:before{content:"\f207"}.fa-ioxhost:before{content:"\f208"}.fa-angellist:before{content:"\f209"}.fa-cc:before{content:"\f20a"}.fa-shekel:before,.fa-sheqel:before,.fa-ils:before{content:"\f20b"}.fa-meanpath:before{content:"\f20c"}.fa-buysellads:before{content:"\f20d"}.fa-connectdevelop:before{content:"\f20e"}.fa-dashcube:before{content:"\f210"}.fa-forumbee:before{content:"\f211"}.fa-leanpub:before{content:"\f212"}.fa-sellsy:before{content:"\f213"}.fa-shirtsinbulk:before{content:"\f214"}.fa-simplybuilt:before{content:"\f215"}.fa-skyatlas:before{content:"\f216"}.fa-cart-plus:before{content:"\f217"}.fa-cart-arrow-down:before{content:"\f218"}.fa-diamond:before{content:"\f219"}.fa-ship:before{content:"\f21a"}.fa-user-secret:before{content:"\f21b"}.fa-motorcycle:before{content:"\f21c"}.fa-street-view:before{content:"\f21d"}.fa-heartbeat:before{content:"\f21e"}.fa-venus:before{content:"\f221"}.fa-mars:before{content:"\f222"}.fa-mercury:before{content:"\f223"}.fa-transgender:before{content:"\f224"}.fa-transgender-alt:before{content:"\f225"}.fa-venus-double:before{content:"\f226"}.fa-mars-double:before{content:"\f227"}.fa-venus-mars:before{content:"\f228"}.fa-mars-stroke:before{content:"\f229"}.fa-mars-stroke-v:before{content:"\f22a"}.fa-mars-stroke-h:before{content:"\f22b"}.fa-neuter:before{content:"\f22c"}.fa-facebook-official:before{content:"\f230"}.fa-pinterest-p:before{content:"\f231"}.fa-whatsapp:before{content:"\f232"}.fa-server:before{content:"\f233"}.fa-user-plus:before{content:"\f234"}.fa-user-times:before{content:"\f235"}.fa-hotel:before,.fa-bed:before{content:"\f236"}.fa-viacoin:before{content:"\f237"}.fa-train:before{content:"\f238"}.fa-subway:before{content:"\f239"}.fa-medium:before{content:"\f23a"} -------------------------------------------------------------------------------- /app/static/js/vendor/bootstrap.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap v3.3.2 (http://getbootstrap.com) 3 | * Copyright 2011-2015 Twitter, Inc. 4 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 5 | */ 6 | if("undefined"==typeof jQuery)throw new Error("Bootstrap's JavaScript requires jQuery");+function(a){"use strict";var b=a.fn.jquery.split(" ")[0].split(".");if(b[0]<2&&b[1]<9||1==b[0]&&9==b[1]&&b[2]<1)throw new Error("Bootstrap's JavaScript requires jQuery version 1.9.1 or higher")}(jQuery),+function(a){"use strict";function b(){var a=document.createElement("bootstrap"),b={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"};for(var c in b)if(void 0!==a.style[c])return{end:b[c]};return!1}a.fn.emulateTransitionEnd=function(b){var c=!1,d=this;a(this).one("bsTransitionEnd",function(){c=!0});var e=function(){c||a(d).trigger(a.support.transition.end)};return setTimeout(e,b),this},a(function(){a.support.transition=b(),a.support.transition&&(a.event.special.bsTransitionEnd={bindType:a.support.transition.end,delegateType:a.support.transition.end,handle:function(b){return a(b.target).is(this)?b.handleObj.handler.apply(this,arguments):void 0}})})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var c=a(this),e=c.data("bs.alert");e||c.data("bs.alert",e=new d(this)),"string"==typeof b&&e[b].call(c)})}var c='[data-dismiss="alert"]',d=function(b){a(b).on("click",c,this.close)};d.VERSION="3.3.2",d.TRANSITION_DURATION=150,d.prototype.close=function(b){function c(){g.detach().trigger("closed.bs.alert").remove()}var e=a(this),f=e.attr("data-target");f||(f=e.attr("href"),f=f&&f.replace(/.*(?=#[^\s]*$)/,""));var g=a(f);b&&b.preventDefault(),g.length||(g=e.closest(".alert")),g.trigger(b=a.Event("close.bs.alert")),b.isDefaultPrevented()||(g.removeClass("in"),a.support.transition&&g.hasClass("fade")?g.one("bsTransitionEnd",c).emulateTransitionEnd(d.TRANSITION_DURATION):c())};var e=a.fn.alert;a.fn.alert=b,a.fn.alert.Constructor=d,a.fn.alert.noConflict=function(){return a.fn.alert=e,this},a(document).on("click.bs.alert.data-api",c,d.prototype.close)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.button"),f="object"==typeof b&&b;e||d.data("bs.button",e=new c(this,f)),"toggle"==b?e.toggle():b&&e.setState(b)})}var c=function(b,d){this.$element=a(b),this.options=a.extend({},c.DEFAULTS,d),this.isLoading=!1};c.VERSION="3.3.2",c.DEFAULTS={loadingText:"loading..."},c.prototype.setState=function(b){var c="disabled",d=this.$element,e=d.is("input")?"val":"html",f=d.data();b+="Text",null==f.resetText&&d.data("resetText",d[e]()),setTimeout(a.proxy(function(){d[e](null==f[b]?this.options[b]:f[b]),"loadingText"==b?(this.isLoading=!0,d.addClass(c).attr(c,c)):this.isLoading&&(this.isLoading=!1,d.removeClass(c).removeAttr(c))},this),0)},c.prototype.toggle=function(){var a=!0,b=this.$element.closest('[data-toggle="buttons"]');if(b.length){var c=this.$element.find("input");"radio"==c.prop("type")&&(c.prop("checked")&&this.$element.hasClass("active")?a=!1:b.find(".active").removeClass("active")),a&&c.prop("checked",!this.$element.hasClass("active")).trigger("change")}else this.$element.attr("aria-pressed",!this.$element.hasClass("active"));a&&this.$element.toggleClass("active")};var d=a.fn.button;a.fn.button=b,a.fn.button.Constructor=c,a.fn.button.noConflict=function(){return a.fn.button=d,this},a(document).on("click.bs.button.data-api",'[data-toggle^="button"]',function(c){var d=a(c.target);d.hasClass("btn")||(d=d.closest(".btn")),b.call(d,"toggle"),c.preventDefault()}).on("focus.bs.button.data-api blur.bs.button.data-api",'[data-toggle^="button"]',function(b){a(b.target).closest(".btn").toggleClass("focus",/^focus(in)?$/.test(b.type))})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.carousel"),f=a.extend({},c.DEFAULTS,d.data(),"object"==typeof b&&b),g="string"==typeof b?b:f.slide;e||d.data("bs.carousel",e=new c(this,f)),"number"==typeof b?e.to(b):g?e[g]():f.interval&&e.pause().cycle()})}var c=function(b,c){this.$element=a(b),this.$indicators=this.$element.find(".carousel-indicators"),this.options=c,this.paused=this.sliding=this.interval=this.$active=this.$items=null,this.options.keyboard&&this.$element.on("keydown.bs.carousel",a.proxy(this.keydown,this)),"hover"==this.options.pause&&!("ontouchstart"in document.documentElement)&&this.$element.on("mouseenter.bs.carousel",a.proxy(this.pause,this)).on("mouseleave.bs.carousel",a.proxy(this.cycle,this))};c.VERSION="3.3.2",c.TRANSITION_DURATION=600,c.DEFAULTS={interval:5e3,pause:"hover",wrap:!0,keyboard:!0},c.prototype.keydown=function(a){if(!/input|textarea/i.test(a.target.tagName)){switch(a.which){case 37:this.prev();break;case 39:this.next();break;default:return}a.preventDefault()}},c.prototype.cycle=function(b){return b||(this.paused=!1),this.interval&&clearInterval(this.interval),this.options.interval&&!this.paused&&(this.interval=setInterval(a.proxy(this.next,this),this.options.interval)),this},c.prototype.getItemIndex=function(a){return this.$items=a.parent().children(".item"),this.$items.index(a||this.$active)},c.prototype.getItemForDirection=function(a,b){var c=this.getItemIndex(b),d="prev"==a&&0===c||"next"==a&&c==this.$items.length-1;if(d&&!this.options.wrap)return b;var e="prev"==a?-1:1,f=(c+e)%this.$items.length;return this.$items.eq(f)},c.prototype.to=function(a){var b=this,c=this.getItemIndex(this.$active=this.$element.find(".item.active"));return a>this.$items.length-1||0>a?void 0:this.sliding?this.$element.one("slid.bs.carousel",function(){b.to(a)}):c==a?this.pause().cycle():this.slide(a>c?"next":"prev",this.$items.eq(a))},c.prototype.pause=function(b){return b||(this.paused=!0),this.$element.find(".next, .prev").length&&a.support.transition&&(this.$element.trigger(a.support.transition.end),this.cycle(!0)),this.interval=clearInterval(this.interval),this},c.prototype.next=function(){return this.sliding?void 0:this.slide("next")},c.prototype.prev=function(){return this.sliding?void 0:this.slide("prev")},c.prototype.slide=function(b,d){var e=this.$element.find(".item.active"),f=d||this.getItemForDirection(b,e),g=this.interval,h="next"==b?"left":"right",i=this;if(f.hasClass("active"))return this.sliding=!1;var j=f[0],k=a.Event("slide.bs.carousel",{relatedTarget:j,direction:h});if(this.$element.trigger(k),!k.isDefaultPrevented()){if(this.sliding=!0,g&&this.pause(),this.$indicators.length){this.$indicators.find(".active").removeClass("active");var l=a(this.$indicators.children()[this.getItemIndex(f)]);l&&l.addClass("active")}var m=a.Event("slid.bs.carousel",{relatedTarget:j,direction:h});return a.support.transition&&this.$element.hasClass("slide")?(f.addClass(b),f[0].offsetWidth,e.addClass(h),f.addClass(h),e.one("bsTransitionEnd",function(){f.removeClass([b,h].join(" ")).addClass("active"),e.removeClass(["active",h].join(" ")),i.sliding=!1,setTimeout(function(){i.$element.trigger(m)},0)}).emulateTransitionEnd(c.TRANSITION_DURATION)):(e.removeClass("active"),f.addClass("active"),this.sliding=!1,this.$element.trigger(m)),g&&this.cycle(),this}};var d=a.fn.carousel;a.fn.carousel=b,a.fn.carousel.Constructor=c,a.fn.carousel.noConflict=function(){return a.fn.carousel=d,this};var e=function(c){var d,e=a(this),f=a(e.attr("data-target")||(d=e.attr("href"))&&d.replace(/.*(?=#[^\s]+$)/,""));if(f.hasClass("carousel")){var g=a.extend({},f.data(),e.data()),h=e.attr("data-slide-to");h&&(g.interval=!1),b.call(f,g),h&&f.data("bs.carousel").to(h),c.preventDefault()}};a(document).on("click.bs.carousel.data-api","[data-slide]",e).on("click.bs.carousel.data-api","[data-slide-to]",e),a(window).on("load",function(){a('[data-ride="carousel"]').each(function(){var c=a(this);b.call(c,c.data())})})}(jQuery),+function(a){"use strict";function b(b){var c,d=b.attr("data-target")||(c=b.attr("href"))&&c.replace(/.*(?=#[^\s]+$)/,"");return a(d)}function c(b){return this.each(function(){var c=a(this),e=c.data("bs.collapse"),f=a.extend({},d.DEFAULTS,c.data(),"object"==typeof b&&b);!e&&f.toggle&&"show"==b&&(f.toggle=!1),e||c.data("bs.collapse",e=new d(this,f)),"string"==typeof b&&e[b]()})}var d=function(b,c){this.$element=a(b),this.options=a.extend({},d.DEFAULTS,c),this.$trigger=a(this.options.trigger).filter('[href="#'+b.id+'"], [data-target="#'+b.id+'"]'),this.transitioning=null,this.options.parent?this.$parent=this.getParent():this.addAriaAndCollapsedClass(this.$element,this.$trigger),this.options.toggle&&this.toggle()};d.VERSION="3.3.2",d.TRANSITION_DURATION=350,d.DEFAULTS={toggle:!0,trigger:'[data-toggle="collapse"]'},d.prototype.dimension=function(){var a=this.$element.hasClass("width");return a?"width":"height"},d.prototype.show=function(){if(!this.transitioning&&!this.$element.hasClass("in")){var b,e=this.$parent&&this.$parent.children(".panel").children(".in, .collapsing");if(!(e&&e.length&&(b=e.data("bs.collapse"),b&&b.transitioning))){var f=a.Event("show.bs.collapse");if(this.$element.trigger(f),!f.isDefaultPrevented()){e&&e.length&&(c.call(e,"hide"),b||e.data("bs.collapse",null));var g=this.dimension();this.$element.removeClass("collapse").addClass("collapsing")[g](0).attr("aria-expanded",!0),this.$trigger.removeClass("collapsed").attr("aria-expanded",!0),this.transitioning=1;var h=function(){this.$element.removeClass("collapsing").addClass("collapse in")[g](""),this.transitioning=0,this.$element.trigger("shown.bs.collapse")};if(!a.support.transition)return h.call(this);var i=a.camelCase(["scroll",g].join("-"));this.$element.one("bsTransitionEnd",a.proxy(h,this)).emulateTransitionEnd(d.TRANSITION_DURATION)[g](this.$element[0][i])}}}},d.prototype.hide=function(){if(!this.transitioning&&this.$element.hasClass("in")){var b=a.Event("hide.bs.collapse");if(this.$element.trigger(b),!b.isDefaultPrevented()){var c=this.dimension();this.$element[c](this.$element[c]())[0].offsetHeight,this.$element.addClass("collapsing").removeClass("collapse in").attr("aria-expanded",!1),this.$trigger.addClass("collapsed").attr("aria-expanded",!1),this.transitioning=1;var e=function(){this.transitioning=0,this.$element.removeClass("collapsing").addClass("collapse").trigger("hidden.bs.collapse")};return a.support.transition?void this.$element[c](0).one("bsTransitionEnd",a.proxy(e,this)).emulateTransitionEnd(d.TRANSITION_DURATION):e.call(this)}}},d.prototype.toggle=function(){this[this.$element.hasClass("in")?"hide":"show"]()},d.prototype.getParent=function(){return a(this.options.parent).find('[data-toggle="collapse"][data-parent="'+this.options.parent+'"]').each(a.proxy(function(c,d){var e=a(d);this.addAriaAndCollapsedClass(b(e),e)},this)).end()},d.prototype.addAriaAndCollapsedClass=function(a,b){var c=a.hasClass("in");a.attr("aria-expanded",c),b.toggleClass("collapsed",!c).attr("aria-expanded",c)};var e=a.fn.collapse;a.fn.collapse=c,a.fn.collapse.Constructor=d,a.fn.collapse.noConflict=function(){return a.fn.collapse=e,this},a(document).on("click.bs.collapse.data-api",'[data-toggle="collapse"]',function(d){var e=a(this);e.attr("data-target")||d.preventDefault();var f=b(e),g=f.data("bs.collapse"),h=g?"toggle":a.extend({},e.data(),{trigger:this});c.call(f,h)})}(jQuery),+function(a){"use strict";function b(b){b&&3===b.which||(a(e).remove(),a(f).each(function(){var d=a(this),e=c(d),f={relatedTarget:this};e.hasClass("open")&&(e.trigger(b=a.Event("hide.bs.dropdown",f)),b.isDefaultPrevented()||(d.attr("aria-expanded","false"),e.removeClass("open").trigger("hidden.bs.dropdown",f)))}))}function c(b){var c=b.attr("data-target");c||(c=b.attr("href"),c=c&&/#[A-Za-z]/.test(c)&&c.replace(/.*(?=#[^\s]*$)/,""));var d=c&&a(c);return d&&d.length?d:b.parent()}function d(b){return this.each(function(){var c=a(this),d=c.data("bs.dropdown");d||c.data("bs.dropdown",d=new g(this)),"string"==typeof b&&d[b].call(c)})}var e=".dropdown-backdrop",f='[data-toggle="dropdown"]',g=function(b){a(b).on("click.bs.dropdown",this.toggle)};g.VERSION="3.3.2",g.prototype.toggle=function(d){var e=a(this);if(!e.is(".disabled, :disabled")){var f=c(e),g=f.hasClass("open");if(b(),!g){"ontouchstart"in document.documentElement&&!f.closest(".navbar-nav").length&&a('