diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3a7e4a2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,61 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] + +# C extensions +*.so + +# Distribution / packaging +.Python +env/ +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib64/ +parts/ +sdist/ +var/ +*.egg-info/ +.installed.cfg +*.egg + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +coverage/ +.tox/ +.coverage +.coverage.* +.cache +coverage.xml +*,cover + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +collected-static/ +node_modules/ +static/build + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +private/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..02bcfd7 --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +# My Website diff --git a/build b/build new file mode 100755 index 0000000..7374d1a --- /dev/null +++ b/build @@ -0,0 +1,11 @@ +#!/usr/bin/env bash + +set -e + +pyvenv-3.4 env +env/bin/pip install -r requirements.txt --upgrade + +npm install +npm run build + +env/bin/python manage.py collectstatic --noinput diff --git a/etc/environments/deployment/env b/etc/environments/deployment/env new file mode 100644 index 0000000..d83138a --- /dev/null +++ b/etc/environments/deployment/env @@ -0,0 +1,2 @@ +DEBUG=true +EMAIL_BACKEND=django.core.mail.backends.smtp.EmailBackend \ No newline at end of file diff --git a/etc/environments/deployment/procfile b/etc/environments/deployment/procfile new file mode 100644 index 0000000..c90b8d4 --- /dev/null +++ b/etc/environments/deployment/procfile @@ -0,0 +1 @@ +web: waitress-serve --port $PORT project.wsgi:application diff --git a/etc/environments/development/env b/etc/environments/development/env new file mode 100644 index 0000000..1e9eaf3 --- /dev/null +++ b/etc/environments/development/env @@ -0,0 +1,3 @@ +DEBUG=true +DATABASE_URL=postgres://localhost/theorangeonenet +EMAIL_BACKEND=django.core.mail.backends.console.EmailBackend \ No newline at end of file diff --git a/etc/environments/development/procfile b/etc/environments/development/procfile new file mode 100644 index 0000000..dbc4b47 --- /dev/null +++ b/etc/environments/development/procfile @@ -0,0 +1,2 @@ +web: manage.py runserver 0.0.0.0:$PORT +watcher: npm run watch \ No newline at end of file diff --git a/manage.py b/manage.py new file mode 100755 index 0000000..82cfa83 --- /dev/null +++ b/manage.py @@ -0,0 +1,10 @@ +#!/usr/bin/env python +import os +import sys + +if __name__ == "__main__": + os.environ.setdefault("DJANGO_SETTINGS_MODULE", "project.settings") + + from django.core.management import execute_from_command_line + + execute_from_command_line(sys.argv) diff --git a/package.json b/package.json new file mode 100644 index 0000000..43522c6 --- /dev/null +++ b/package.json @@ -0,0 +1,43 @@ +{ + "name": "TheOrangeOne-Site", + "version": "0.0.1", + "description": "TheOrangeOne.net site", + "main": "app.js", + "scripts": { + "lint": "echo 'Nothing yet.';", + "build-js": "bash scripts/build-js.sh", + "create-build-dirs": "mkdir -p static/build/js static/build/fonts static/build/css static/build/img private", + "build": "npm run create-build-dirs && npm run build-fonts && npm run build-images && npm run build-js && npm run build-less", + "build-less": "lessc --silent static/src/less/style.less static/build/css/style.css && cleancss -d --s0 -o static/build/css/style.css static/build/css/style.css", + "build-fonts": "cp -R node_modules/bootstrap/dist/fonts static/build/ && cp -R node_modules/ionicons-pre/fonts static/build/ ", + "build-images": "cp -r static/src/img/* static/build/img/", + "watch-images": "watch 'npm run build-images' static/src/img/", + "watch-less": "watch 'npm run build-less' static/src/less/", + "watch": "npm run watch-less & npm run watch-images", + "clean": "rm -rf static/build collected-static/ node_modules/ env/ private" + }, + "repository": { + "type": "git", + "url": "https://bitbucket.org/TheOrangeOne/" + }, + "dependencies": { + "bootstrap": "^3.3.5", + "jquery": "^2.1.4", + "less": "^2.5.1", + "markdown": "^0.5.0", + "react": "^0.13.3", + "react-bootstrap": "^0.25.1", + "skrollr": "^0.6.26" + }, + "devDependencies": { + "browserify": "^11.0.1", + "clean-css": "^3.4.1", + "eslint": "^1.5.0", + "eslint-plugin-react": "^3.4.2", + "ionicons-pre": "^1.0.0-pre", + "react-tools": "^0.13.2", + "reactify": "^1.1.1", + "uglify-js": "^2.4.24", + "watch": "^0.16.0" + } +} diff --git a/project/__init__.py b/project/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/project/college/__init__.py b/project/college/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/project/college/admin.py b/project/college/admin.py new file mode 100644 index 0000000..8c38f3f --- /dev/null +++ b/project/college/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/project/college/migrations/__init__.py b/project/college/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/project/college/models.py b/project/college/models.py new file mode 100644 index 0000000..71a8362 --- /dev/null +++ b/project/college/models.py @@ -0,0 +1,3 @@ +from django.db import models + +# Create your models here. diff --git a/project/college/tests.py b/project/college/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/project/college/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/project/college/urls.py b/project/college/urls.py new file mode 100644 index 0000000..e4a01a4 --- /dev/null +++ b/project/college/urls.py @@ -0,0 +1,7 @@ +from django.conf.urls import url +from .views import StudentServerView + + +urlpatterns = [ + url(r'^student-server', StudentServerView.as_view(), name='student-server') +] diff --git a/project/college/views.py b/project/college/views.py new file mode 100644 index 0000000..067dacc --- /dev/null +++ b/project/college/views.py @@ -0,0 +1,5 @@ +from django.views.generic import TemplateView +from project.common.views import CustomHeaderBG + +class StudentServerView(CustomHeaderBG.Template): + template_name = 'college/student-server.html' \ No newline at end of file diff --git a/project/common/__init__.py b/project/common/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/project/common/migrations/__init__.py b/project/common/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/project/common/models.py b/project/common/models.py new file mode 100644 index 0000000..71a8362 --- /dev/null +++ b/project/common/models.py @@ -0,0 +1,3 @@ +from django.db import models + +# Create your models here. diff --git a/project/common/tests.py b/project/common/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/project/common/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/project/common/views.py b/project/common/views.py new file mode 100644 index 0000000..be82893 --- /dev/null +++ b/project/common/views.py @@ -0,0 +1,13 @@ +from django.views.generic import TemplateView + + +class CustomHeaderBG(): + """Allow custom header background""" + + class Template(TemplateView): + header_BG = "" + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + context['header_BG'] = self.header_BG + return context \ No newline at end of file diff --git a/project/media/__init__.py b/project/media/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/project/media/admin.py b/project/media/admin.py new file mode 100644 index 0000000..8c38f3f --- /dev/null +++ b/project/media/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/project/media/migrations/__init__.py b/project/media/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/project/media/tests.py b/project/media/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/project/media/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/project/media/urls.py b/project/media/urls.py new file mode 100644 index 0000000..0bacbc0 --- /dev/null +++ b/project/media/urls.py @@ -0,0 +1,8 @@ +from django.conf.urls import url +from .views import GalleryView, YoutubeView + + +urlpatterns = [ + url(r'^gallery', GalleryView.as_view(), name='gallery'), + url(r'^youtube', YoutubeView.as_view(), name='youtube') +] diff --git a/project/media/views.py b/project/media/views.py new file mode 100644 index 0000000..516f356 --- /dev/null +++ b/project/media/views.py @@ -0,0 +1,10 @@ +from django.views.generic import TemplateView +from project.common.views import CustomHeaderBG + + +class YoutubeView(CustomHeaderBG.Template): + template_name = "media/youtube.html" + + +class GalleryView(TemplateView): + template_name = "media/gallery.html" diff --git a/project/pages/__init__.py b/project/pages/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/project/pages/tests.py b/project/pages/tests.py new file mode 100644 index 0000000..a79ca8b --- /dev/null +++ b/project/pages/tests.py @@ -0,0 +1,3 @@ +# from django.test import TestCase + +# Create your tests here. diff --git a/project/pages/urls.py b/project/pages/urls.py new file mode 100644 index 0000000..6dbaf72 --- /dev/null +++ b/project/pages/urls.py @@ -0,0 +1,11 @@ +from django.conf.urls import url +from . import views + + +urlpatterns = [ + url(r'^about/website/$', views.AboutWebsiteView.as_view(), name='about-website'), + url(r'^about/$', views.AboutIndexView.as_view(), name='about'), + url(r'^no-js/$', views.NoJavascriptView.as_view(), name='no-js'), + url(r'^404/$', views.Custom404View.as_view(), name='404'), + url(r'^$', views.IndexView.as_view(), name='index') +] diff --git a/project/pages/views.py b/project/pages/views.py new file mode 100644 index 0000000..ca5457e --- /dev/null +++ b/project/pages/views.py @@ -0,0 +1,34 @@ +from django.views.generic import TemplateView +from project.common.views import CustomHeaderBG +from django.contrib.staticfiles.templatetags.staticfiles import static + + +class IndexView(TemplateView): + template_name = 'index.html' + + +class NoJavascriptView(TemplateView): + template_name = 'core/no-js.html' + + +class Custom404View(CustomHeaderBG.Template): + template_name = 'core/404.html' + header_BG = static('img/ninjas.png') + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + context['no_footer'] = True + return context + + def get(self, request, *args, **kwargs): + context = self.get_context_data(**kwargs) + return self.render_to_response(context, status=404) + + +class AboutWebsiteView(CustomHeaderBG.Template): + template_name = 'about/website.html' + header_BG = '' + + +class AboutIndexView(TemplateView): + template_name = 'about/index.html' \ No newline at end of file diff --git a/project/projects/__init__.py b/project/projects/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/project/projects/migrations/__init__.py b/project/projects/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/project/projects/tests.py b/project/projects/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/project/projects/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/project/projects/urls.py b/project/projects/urls.py new file mode 100644 index 0000000..551c48f --- /dev/null +++ b/project/projects/urls.py @@ -0,0 +1,16 @@ +from django.conf.urls import url +from . import views + +urlpatterns = [ + # Project URLs + url(r'^attack-on-blocks', views.AttackOnBlocksView.as_view(), name="attack-on-blocks"), + url(r'^bsod-enabler', views.BSODEnablerView.as_view(), name='bsod-enabler'), + url(r'^hipchat-emoticons-for-all', views.HipChatEmoticonsForAllView.as_view(), name='hipchat-emoticons'), + url(r'^pithos', views.PithosView.as_view(), name='pithos'), + + # Code URLs + url(r'^morse-code-decoder', views.MorseCodeDecoderView.as_view(), name='morse-code-decoder'), + url(r'^wiki-game-solver', views.WikiGameSolverView.as_view(), name='wiki-game-solver'), + + url(r'^', views.AllProjectsView.as_view(), name="index") +] diff --git a/project/projects/views.py b/project/projects/views.py new file mode 100644 index 0000000..899ed06 --- /dev/null +++ b/project/projects/views.py @@ -0,0 +1,35 @@ +from django.views.generic import TemplateView +from project.common.views import CustomHeaderBG + + +# Project Views +class AllProjectsView(TemplateView): + template_name = "projects/all_projects.html" + + +class AttackOnBlocksView(CustomHeaderBG.Template): + template_name = "college/attack-on-blocks.html" + + +class BSODEnablerView(CustomHeaderBG.Template): + template_name = 'projects/BSOD-Enabler.html' + header_BG = 'http://cdn9.howtogeek.com/wp-content/uploads/2013/05/xwindows-8-blue-screen-error.png.pagespeed.ic.yOWUS_rYGn.png' + + +class HipChatEmoticonsForAllView(CustomHeaderBG.Template): + template_name = 'projects/hipchat-emoticons-for-all.html' + header_BG = "https://info.seibert-media.net/plugins/servlet/pptslide?attachment=HipChat+Server+Sales+Pitch+Final+PPT.pptx&attachmentId=7536966&attachmentVer=1&pageId=3179011&slide=0" + + +class PithosView(CustomHeaderBG.Template): + template_name = 'projects/pithos.html' + + +# Code Views +class MorseCodeDecoderView(CustomHeaderBG.Template): + template_name = 'projects/morse-code-decoder.html' + + +class WikiGameSolverView(CustomHeaderBG.Template): + template_name = 'projects/wiki-game-solver.html' + header_BG = "http://is1.mzstatic.com/image/pf/us/r30/Purple/v4/a3/3f/31/a33f31dd-3ace-ffe4-5531-89d3f3a1106f/mzl.ihqwkbih.png" \ No newline at end of file diff --git a/project/robotics/__init__.py b/project/robotics/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/project/robotics/migrations/__init__.py b/project/robotics/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/project/robotics/tests.py b/project/robotics/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/project/robotics/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/project/robotics/urls.py b/project/robotics/urls.py new file mode 100644 index 0000000..731879e --- /dev/null +++ b/project/robotics/urls.py @@ -0,0 +1,10 @@ +from django.conf.urls import url +from .views import Index2015View, Index2014View, RoboticsIndexView, Robot2015View + + +urlpatterns = [ + url(r'^$', RoboticsIndexView.as_view(), name='index' ), + url(r'^2015', Index2015View.as_view(), name='2015-index'), + url(r'^2015/robot', Robot2015View.as_view(), name='2015-robot'), + url(r'^2014', Index2014View.as_view(), name='2014-index') +] diff --git a/project/robotics/views.py b/project/robotics/views.py new file mode 100644 index 0000000..0a760f6 --- /dev/null +++ b/project/robotics/views.py @@ -0,0 +1,22 @@ +from django.views.generic import TemplateView +from project.common.views import CustomHeaderBG + + +class RoboticsIndexView(TemplateView): + template_name = 'robotics/index.html' + +# 2015 +class Index2015View(CustomHeaderBG.Template): + template_name = 'robotics/2015-index.html' + header_BG = "https://farm8.staticflickr.com/7711/17122633430_7d1bde923a_k.jpg" + + +class Robot2015View(CustomHeaderBG.Template): + template_name = 'robotics/2015-robot.html' + header_BG = "" + + +# 2014 +class Index2014View(CustomHeaderBG.Template): + template_name = 'robotics/2014-index.html' + header_BG = "nothing" \ No newline at end of file diff --git a/project/settings.py b/project/settings.py new file mode 100644 index 0000000..e4fdcd5 --- /dev/null +++ b/project/settings.py @@ -0,0 +1,108 @@ +""" +Django settings for project. + +Generated by 'django-admin startproject' using Django 1.8.3. + +For more information on this file, see +https://docs.djangoproject.com/en/1.8/topics/settings/ + +For the full list of settings and their values, see +https://docs.djangoproject.com/en/1.8/ref/settings/ +""" +# Build paths inside the project like this: os.path.join(BASE_DIR, ...) +import dj_database_url +# import logging +import os +import sys + +BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + + +# Quick-start development settings - unsuitable for production +# See https://docs.djangoproject.com/en/1.8/howto/deployment/checklist/ + +# SECURITY WARNING: keep the secret key used in production secret! +SECRET_KEY = 'q&^k#n_lkd5l9&g^k7$q1rhj_=%9yqy-)ln78ik414-bcowedy' + +# SECURITY WARNING: don't run with debug turned on in production! +DEBUG = True + +ALLOWED_HOSTS = ['too.ctf.sh', 'theorangeone.net'] + +# Application definition +INSTALLED_APPS = ( + 'django.contrib.admin', + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.messages', + 'django.contrib.staticfiles', + + 'rest_framework', + 'bootstrapform', + + 'project.pages', + 'project.common', + 'project.projects', + 'project.robotics', + 'project.setup', + 'project.media', + 'project.college' +) + +MIDDLEWARE_CLASSES = ( + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.middleware.common.CommonMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.auth.middleware.SessionAuthenticationMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', + 'django.middleware.clickjacking.XFrameOptionsMiddleware', + 'django.middleware.security.SecurityMiddleware', +) + +ROOT_URLCONF = 'project.urls' + +TEMPLATES = [ + { + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'DIRS': [os.path.join(BASE_DIR, 'templates')], + 'APP_DIRS': True, + 'OPTIONS': { + 'context_processors': [ + 'django.template.context_processors.debug', + 'django.template.context_processors.request', + 'django.contrib.auth.context_processors.auth', + 'django.contrib.messages.context_processors.messages', + ], + }, + }, +] + +WSGI_APPLICATION = 'project.wsgi.application' + + +# Database +# https://docs.djangoproject.com/en/1.8/ref/settings/#databases +DATABASES = { + 'default': dj_database_url.config(default=os.environ['DATABASE_URL']) +} + +EMAIL_BACKEND = os.environ['EMAIL_BACKEND'] + +# Internationalization +# https://docs.djangoproject.com/en/1.8/topics/i18n/ +LANGUAGE_CODE = 'en-gb' +TIME_ZONE = 'UTC' +USE_I18N = True +USE_L10N = True +USE_TZ = True + +# Static files (CSS, JavaScript, Images) +# https://docs.djangoproject.com/en/1.8/howto/static-files/ +STATIC_URL = '/static/' +STATIC_ROOT = os.path.join(BASE_DIR, 'collected-static') +STATICFILES_DIRS = ( + os.path.join(BASE_DIR, 'static', 'build'), +) +STATICFILES_STORAGE = 'whitenoise.django.GzipManifestStaticFilesStorage' diff --git a/project/setup/__init__.py b/project/setup/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/project/setup/admin.py b/project/setup/admin.py new file mode 100644 index 0000000..8c38f3f --- /dev/null +++ b/project/setup/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/project/setup/migrations/__init__.py b/project/setup/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/project/setup/models.py b/project/setup/models.py new file mode 100644 index 0000000..b2e2c23 --- /dev/null +++ b/project/setup/models.py @@ -0,0 +1,27 @@ +from django.db import models +import uuid + + +class Device(models.Model): + device_id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) + device_name = models.CharField(max_length=50, blank=False, null=True) + friendly_name = models.CharField(max_length=50, blank=False, null=True, unique=True) + model_name = models.CharField(max_length=50, blank=False, null=True, unique=True) + description = models.TextField(blank=True, null=True) + computer = models.ForeignKey('setup.Computer') + + + +class Computer(models.Model): + computer_id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) + CPU = models.TextField(max_length=30, blank=False, null=True, verbose_name="Processor") + GPU = models.TextField(max_length=40, blank=True, null=True, verbose_name="Graphics Card") + RAM = models.IntegerField(blank=False, null=True) + + + +class Peripheral(models.Model): + peripheral_id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) + name = models.CharField(max_length=40, blank=False, null=True) + model_name = models.CharField(max_length=100, blank=False, null=True) + device = models.ForeignKey('setup.Device') diff --git a/project/setup/tests.py b/project/setup/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/project/setup/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/project/setup/urls.py b/project/setup/urls.py new file mode 100644 index 0000000..234e3b2 --- /dev/null +++ b/project/setup/urls.py @@ -0,0 +1,7 @@ +from django.conf.urls import url +from . import views + +urlpatterns = [ + url(r'^desk', views.DeskView.as_view(), name="desk"), + url(r'^', views.SetupIndexView.as_view(), name="index") +] diff --git a/project/setup/views.py b/project/setup/views.py new file mode 100644 index 0000000..bc04113 --- /dev/null +++ b/project/setup/views.py @@ -0,0 +1,11 @@ +from django.views.generic import TemplateView +from project.common.views import CustomHeaderBG + + +class SetupIndexView(TemplateView): + template_name = "setup/index.html" + + +class DeskView(CustomHeaderBG.Template): + template_name = "setup/my-rig.html" + header_BG = "https://c1.staticflickr.com/1/557/18312934624_b51a541594_h.jpg" diff --git a/project/urls.py b/project/urls.py new file mode 100644 index 0000000..267eb33 --- /dev/null +++ b/project/urls.py @@ -0,0 +1,15 @@ +from django.conf.urls import include, url +from django.contrib import admin +from project.pages.views import Custom404View + +urlpatterns = [ + url(r'^site-admin/', include(admin.site.urls)), + url(r'^student-robotics/', include('project.robotics.urls', namespace='robotics')), + url(r'^projects/', include('project.projects.urls', namespace='projects')), + url(r'^media/', include('project.media.urls', namespace='media')), + url(r'^setup/', include('project.setup.urls', namespace='setup')), + url(r'^college/', include('project.college.urls', namespace='college')), + url(r'', include('project.pages.urls', namespace='pages')) +] + +handler404 = Custom404View \ No newline at end of file diff --git a/project/wsgi.py b/project/wsgi.py new file mode 100644 index 0000000..927d764 --- /dev/null +++ b/project/wsgi.py @@ -0,0 +1,16 @@ +""" +WSGI config for twm project. + +It exposes the WSGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/1.8/howto/deployment/wsgi/ +""" +import os +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "project.settings") + +from django.core.wsgi import get_wsgi_application +from whitenoise.django import DjangoWhiteNoise + +application = get_wsgi_application() +application = DjangoWhiteNoise(application) diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..a3b10a3 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,14 @@ +coverage==3.7.1 +Django==1.8.3 +dj-database-url==0.3.0 +django-bootstrap-form==3.2 +djangorestframework==3.2.0 +djorm-ext-pgfulltext==0.10 +flake8==2.4.1 +pep8==1.5.7 +psycopg2==2.6.1 +pycrypto==2.6.1 +pyflakes==0.8.1 +six==1.9.0 +waitress==0.8.9 +whitenoise==2.0.2 diff --git a/runtests b/runtests new file mode 100755 index 0000000..d032c5d --- /dev/null +++ b/runtests @@ -0,0 +1,9 @@ +#!/usr/bin/env bash + +set -e + +export PATH=env/bin:${PATH} + +coverage run --source=project --omit='*/wsgi.py,*/settings.py,*/migrations/*.py,*__init__*,*/tests.py' manage.py test $@ +flake8 project --ignore=E128,E501 --exclude="migrations,settings,*/wsgi.py" +coverage report diff --git a/scripts/build-js.sh b/scripts/build-js.sh new file mode 100644 index 0000000..6d6d64f --- /dev/null +++ b/scripts/build-js.sh @@ -0,0 +1,18 @@ +#!/usr/bin/bash + +set -e + +cp node_modules/bootstrap/dist/js/bootstrap.js static/src/js/lib/ +cp node_modules/skrollr/src/skrollr.js static/src/js/lib/ + +echo ">> Building Libraries..." +uglifyjs node_modules/jquery/dist/jquery.js --compress --screw-ie8 --define --stats --keep-fnames -o static/build/js/jquery.js +uglifyjs node_modules/markdown/lib/markdown.js --compress --screw-ie8 --define --stats --keep-fnames -o static/build/js/markdown.js +uglifyjs static/src/js/lib/* --compress --screw-ie8 --define --stats --keep-fnames -o static/build/js/libs.js + +echo ">> Building Application JS..." +browserify -t reactify static/src/js/app.js -o static/build/js/app.js +# uglifyjs static/build/js/app.js --compress --screw-ie8 --define --stats --keep-fnames -o static/build/js/app.js + +echo ">> Building Global Utilities..." +uglifyjs static/src/js/utils.js --compress --screw-ie8 --define --stats --keep-fnames -o static/build/js/utils.js diff --git a/scripts/get_private_data.sh b/scripts/get_private_data.sh new file mode 100644 index 0000000..92119cb --- /dev/null +++ b/scripts/get_private_data.sh @@ -0,0 +1,14 @@ +#!/usr/bin/bash + +set -e + +echo ">> Removing old Private data..." +rm -rf private/* +mkdir private/ +cd private/ + +echo ">> Cloning Private Repo..." +git clone git@bitbucket.org:TheOrangeOne/theorangeone.net-site-private-data.git + + +echo ">> That's it for now..." diff --git a/static/src/img/BSOD_Enabler_Screenshot.png b/static/src/img/BSOD_Enabler_Screenshot.png new file mode 100644 index 0000000..24232db Binary files /dev/null and b/static/src/img/BSOD_Enabler_Screenshot.png differ diff --git a/static/src/img/IE-scare.png b/static/src/img/IE-scare.png new file mode 100644 index 0000000..f834d43 Binary files /dev/null and b/static/src/img/IE-scare.png differ diff --git a/static/src/img/header-bg.png b/static/src/img/header-bg.png new file mode 100644 index 0000000..c87c7a9 Binary files /dev/null and b/static/src/img/header-bg.png differ diff --git a/static/src/img/ninjas.png b/static/src/img/ninjas.png new file mode 100644 index 0000000..15525a8 Binary files /dev/null and b/static/src/img/ninjas.png differ diff --git a/static/src/js/app.js b/static/src/js/app.js new file mode 100644 index 0000000..d1ace96 --- /dev/null +++ b/static/src/js/app.js @@ -0,0 +1,14 @@ +var React = require('react'); +var indexCarousel = require('./components/indexCarousel.js'); + +require('./events.js'); + + +if ($('#index-carousel-container').length == 1) { + React.render(indexCarousel, $("#index-carousel-container")[0]); +} + +$(window).load(function(){ + $(window).trigger('scroll').trigger('resize'); + var s = skrollr.init(); +}); \ No newline at end of file diff --git a/static/src/js/components/indexCarousel.js b/static/src/js/components/indexCarousel.js new file mode 100644 index 0000000..0a56fa3 --- /dev/null +++ b/static/src/js/components/indexCarousel.js @@ -0,0 +1,27 @@ +var React = require('react'); +var Bootstrap = require('react-bootstrap'); + +var Carousel = Bootstrap.Carousel; +var CarouselItem = Bootstrap.CarouselItem; + +const indexCarousel = ( + + +
+
+

Setup

+

Nulla vitae elit libero, a pharetra augue mollis interdum.

+
+
+ + +
+
+

Student Robotics

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit.

+
+
+
+); + +module.exports = indexCarousel; \ No newline at end of file diff --git a/static/src/js/events.js b/static/src/js/events.js new file mode 100644 index 0000000..cc9be5b --- /dev/null +++ b/static/src/js/events.js @@ -0,0 +1,16 @@ +$(window).scroll(function() { + position_navbar(); +}); + + +$(window).resize(function() { + set_page_sizes(); + space_navbar(); +}); + + +$("#page-down").click(function() { + $('html, body').animate({ + scrollTop: $("#navbar-anchor").offset().top + }, 750); +}); diff --git a/static/src/js/lib/bootstrap.js b/static/src/js/lib/bootstrap.js new file mode 100644 index 0000000..5debfd7 --- /dev/null +++ b/static/src/js/lib/bootstrap.js @@ -0,0 +1,2363 @@ +/*! + * Bootstrap v3.3.5 (http://getbootstrap.com) + * Copyright 2011-2015 Twitter, Inc. + * Licensed under the MIT license + */ + +if (typeof jQuery === 'undefined') { + throw new Error('Bootstrap\'s JavaScript requires jQuery') +} + ++function ($) { + 'use strict'; + var version = $.fn.jquery.split(' ')[0].split('.') + if ((version[0] < 2 && version[1] < 9) || (version[0] == 1 && version[1] == 9 && version[2] < 1)) { + throw new Error('Bootstrap\'s JavaScript requires jQuery version 1.9.1 or higher') + } +}(jQuery); + +/* ======================================================================== + * Bootstrap: transition.js v3.3.5 + * http://getbootstrap.com/javascript/#transitions + * ======================================================================== + * Copyright 2011-2015 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * ======================================================================== */ + + ++function ($) { + 'use strict'; + + // CSS TRANSITION SUPPORT (Shoutout: http://www.modernizr.com/) + // ============================================================ + + function transitionEnd() { + var el = document.createElement('bootstrap') + + var transEndEventNames = { + WebkitTransition : 'webkitTransitionEnd', + MozTransition : 'transitionend', + OTransition : 'oTransitionEnd otransitionend', + transition : 'transitionend' + } + + for (var name in transEndEventNames) { + if (el.style[name] !== undefined) { + return { end: transEndEventNames[name] } + } + } + + return false // explicit for ie8 ( ._.) + } + + // http://blog.alexmaccaw.com/css-transitions + $.fn.emulateTransitionEnd = function (duration) { + var called = false + var $el = this + $(this).one('bsTransitionEnd', function () { called = true }) + var callback = function () { if (!called) $($el).trigger($.support.transition.end) } + setTimeout(callback, duration) + return this + } + + $(function () { + $.support.transition = transitionEnd() + + if (!$.support.transition) return + + $.event.special.bsTransitionEnd = { + bindType: $.support.transition.end, + delegateType: $.support.transition.end, + handle: function (e) { + if ($(e.target).is(this)) return e.handleObj.handler.apply(this, arguments) + } + } + }) + +}(jQuery); + +/* ======================================================================== + * Bootstrap: alert.js v3.3.5 + * http://getbootstrap.com/javascript/#alerts + * ======================================================================== + * Copyright 2011-2015 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * ======================================================================== */ + + ++function ($) { + 'use strict'; + + // ALERT CLASS DEFINITION + // ====================== + + var dismiss = '[data-dismiss="alert"]' + var Alert = function (el) { + $(el).on('click', dismiss, this.close) + } + + Alert.VERSION = '3.3.5' + + Alert.TRANSITION_DURATION = 150 + + Alert.prototype.close = function (e) { + var $this = $(this) + var selector = $this.attr('data-target') + + if (!selector) { + selector = $this.attr('href') + selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') // strip for ie7 + } + + var $parent = $(selector) + + if (e) e.preventDefault() + + if (!$parent.length) { + $parent = $this.closest('.alert') + } + + $parent.trigger(e = $.Event('close.bs.alert')) + + if (e.isDefaultPrevented()) return + + $parent.removeClass('in') + + function removeElement() { + // detach from parent, fire event then clean up data + $parent.detach().trigger('closed.bs.alert').remove() + } + + $.support.transition && $parent.hasClass('fade') ? + $parent + .one('bsTransitionEnd', removeElement) + .emulateTransitionEnd(Alert.TRANSITION_DURATION) : + removeElement() + } + + + // ALERT PLUGIN DEFINITION + // ======================= + + function Plugin(option) { + return this.each(function () { + var $this = $(this) + var data = $this.data('bs.alert') + + if (!data) $this.data('bs.alert', (data = new Alert(this))) + if (typeof option == 'string') data[option].call($this) + }) + } + + var old = $.fn.alert + + $.fn.alert = Plugin + $.fn.alert.Constructor = Alert + + + // ALERT NO CONFLICT + // ================= + + $.fn.alert.noConflict = function () { + $.fn.alert = old + return this + } + + + // ALERT DATA-API + // ============== + + $(document).on('click.bs.alert.data-api', dismiss, Alert.prototype.close) + +}(jQuery); + +/* ======================================================================== + * Bootstrap: button.js v3.3.5 + * http://getbootstrap.com/javascript/#buttons + * ======================================================================== + * Copyright 2011-2015 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * ======================================================================== */ + + ++function ($) { + 'use strict'; + + // BUTTON PUBLIC CLASS DEFINITION + // ============================== + + var Button = function (element, options) { + this.$element = $(element) + this.options = $.extend({}, Button.DEFAULTS, options) + this.isLoading = false + } + + Button.VERSION = '3.3.5' + + Button.DEFAULTS = { + loadingText: 'loading...' + } + + Button.prototype.setState = function (state) { + var d = 'disabled' + var $el = this.$element + var val = $el.is('input') ? 'val' : 'html' + var data = $el.data() + + state += 'Text' + + if (data.resetText == null) $el.data('resetText', $el[val]()) + + // push to event loop to allow forms to submit + setTimeout($.proxy(function () { + $el[val](data[state] == null ? this.options[state] : data[state]) + + if (state == 'loadingText') { + this.isLoading = true + $el.addClass(d).attr(d, d) + } else if (this.isLoading) { + this.isLoading = false + $el.removeClass(d).removeAttr(d) + } + }, this), 0) + } + + Button.prototype.toggle = function () { + var changed = true + var $parent = this.$element.closest('[data-toggle="buttons"]') + + if ($parent.length) { + var $input = this.$element.find('input') + if ($input.prop('type') == 'radio') { + if ($input.prop('checked')) changed = false + $parent.find('.active').removeClass('active') + this.$element.addClass('active') + } else if ($input.prop('type') == 'checkbox') { + if (($input.prop('checked')) !== this.$element.hasClass('active')) changed = false + this.$element.toggleClass('active') + } + $input.prop('checked', this.$element.hasClass('active')) + if (changed) $input.trigger('change') + } else { + this.$element.attr('aria-pressed', !this.$element.hasClass('active')) + this.$element.toggleClass('active') + } + } + + + // BUTTON PLUGIN DEFINITION + // ======================== + + function Plugin(option) { + return this.each(function () { + var $this = $(this) + var data = $this.data('bs.button') + var options = typeof option == 'object' && option + + if (!data) $this.data('bs.button', (data = new Button(this, options))) + + if (option == 'toggle') data.toggle() + else if (option) data.setState(option) + }) + } + + var old = $.fn.button + + $.fn.button = Plugin + $.fn.button.Constructor = Button + + + // BUTTON NO CONFLICT + // ================== + + $.fn.button.noConflict = function () { + $.fn.button = old + return this + } + + + // BUTTON DATA-API + // =============== + + $(document) + .on('click.bs.button.data-api', '[data-toggle^="button"]', function (e) { + var $btn = $(e.target) + if (!$btn.hasClass('btn')) $btn = $btn.closest('.btn') + Plugin.call($btn, 'toggle') + if (!($(e.target).is('input[type="radio"]') || $(e.target).is('input[type="checkbox"]'))) e.preventDefault() + }) + .on('focus.bs.button.data-api blur.bs.button.data-api', '[data-toggle^="button"]', function (e) { + $(e.target).closest('.btn').toggleClass('focus', /^focus(in)?$/.test(e.type)) + }) + +}(jQuery); + +/* ======================================================================== + * Bootstrap: carousel.js v3.3.5 + * http://getbootstrap.com/javascript/#carousel + * ======================================================================== + * Copyright 2011-2015 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * ======================================================================== */ + + ++function ($) { + 'use strict'; + + // CAROUSEL CLASS DEFINITION + // ========================= + + var Carousel = function (element, options) { + this.$element = $(element) + this.$indicators = this.$element.find('.carousel-indicators') + this.options = options + this.paused = null + this.sliding = null + this.interval = null + this.$active = null + this.$items = null + + this.options.keyboard && this.$element.on('keydown.bs.carousel', $.proxy(this.keydown, this)) + + this.options.pause == 'hover' && !('ontouchstart' in document.documentElement) && this.$element + .on('mouseenter.bs.carousel', $.proxy(this.pause, this)) + .on('mouseleave.bs.carousel', $.proxy(this.cycle, this)) + } + + Carousel.VERSION = '3.3.5' + + Carousel.TRANSITION_DURATION = 600 + + Carousel.DEFAULTS = { + interval: 5000, + pause: 'hover', + wrap: true, + keyboard: true + } + + Carousel.prototype.keydown = function (e) { + if (/input|textarea/i.test(e.target.tagName)) return + switch (e.which) { + case 37: this.prev(); break + case 39: this.next(); break + default: return + } + + e.preventDefault() + } + + Carousel.prototype.cycle = function (e) { + e || (this.paused = false) + + this.interval && clearInterval(this.interval) + + this.options.interval + && !this.paused + && (this.interval = setInterval($.proxy(this.next, this), this.options.interval)) + + return this + } + + Carousel.prototype.getItemIndex = function (item) { + this.$items = item.parent().children('.item') + return this.$items.index(item || this.$active) + } + + Carousel.prototype.getItemForDirection = function (direction, active) { + var activeIndex = this.getItemIndex(active) + var willWrap = (direction == 'prev' && activeIndex === 0) + || (direction == 'next' && activeIndex == (this.$items.length - 1)) + if (willWrap && !this.options.wrap) return active + var delta = direction == 'prev' ? -1 : 1 + var itemIndex = (activeIndex + delta) % this.$items.length + return this.$items.eq(itemIndex) + } + + Carousel.prototype.to = function (pos) { + var that = this + var activeIndex = this.getItemIndex(this.$active = this.$element.find('.item.active')) + + if (pos > (this.$items.length - 1) || pos < 0) return + + if (this.sliding) return this.$element.one('slid.bs.carousel', function () { that.to(pos) }) // yes, "slid" + if (activeIndex == pos) return this.pause().cycle() + + return this.slide(pos > activeIndex ? 'next' : 'prev', this.$items.eq(pos)) + } + + Carousel.prototype.pause = function (e) { + e || (this.paused = true) + + if (this.$element.find('.next, .prev').length && $.support.transition) { + this.$element.trigger($.support.transition.end) + this.cycle(true) + } + + this.interval = clearInterval(this.interval) + + return this + } + + Carousel.prototype.next = function () { + if (this.sliding) return + return this.slide('next') + } + + Carousel.prototype.prev = function () { + if (this.sliding) return + return this.slide('prev') + } + + Carousel.prototype.slide = function (type, next) { + var $active = this.$element.find('.item.active') + var $next = next || this.getItemForDirection(type, $active) + var isCycling = this.interval + var direction = type == 'next' ? 'left' : 'right' + var that = this + + if ($next.hasClass('active')) return (this.sliding = false) + + var relatedTarget = $next[0] + var slideEvent = $.Event('slide.bs.carousel', { + relatedTarget: relatedTarget, + direction: direction + }) + this.$element.trigger(slideEvent) + if (slideEvent.isDefaultPrevented()) return + + this.sliding = true + + isCycling && this.pause() + + if (this.$indicators.length) { + this.$indicators.find('.active').removeClass('active') + var $nextIndicator = $(this.$indicators.children()[this.getItemIndex($next)]) + $nextIndicator && $nextIndicator.addClass('active') + } + + var slidEvent = $.Event('slid.bs.carousel', { relatedTarget: relatedTarget, direction: direction }) // yes, "slid" + if ($.support.transition && this.$element.hasClass('slide')) { + $next.addClass(type) + $next[0].offsetWidth // force reflow + $active.addClass(direction) + $next.addClass(direction) + $active + .one('bsTransitionEnd', function () { + $next.removeClass([type, direction].join(' ')).addClass('active') + $active.removeClass(['active', direction].join(' ')) + that.sliding = false + setTimeout(function () { + that.$element.trigger(slidEvent) + }, 0) + }) + .emulateTransitionEnd(Carousel.TRANSITION_DURATION) + } else { + $active.removeClass('active') + $next.addClass('active') + this.sliding = false + this.$element.trigger(slidEvent) + } + + isCycling && this.cycle() + + return this + } + + + // CAROUSEL PLUGIN DEFINITION + // ========================== + + function Plugin(option) { + return this.each(function () { + var $this = $(this) + var data = $this.data('bs.carousel') + var options = $.extend({}, Carousel.DEFAULTS, $this.data(), typeof option == 'object' && option) + var action = typeof option == 'string' ? option : options.slide + + if (!data) $this.data('bs.carousel', (data = new Carousel(this, options))) + if (typeof option == 'number') data.to(option) + else if (action) data[action]() + else if (options.interval) data.pause().cycle() + }) + } + + var old = $.fn.carousel + + $.fn.carousel = Plugin + $.fn.carousel.Constructor = Carousel + + + // CAROUSEL NO CONFLICT + // ==================== + + $.fn.carousel.noConflict = function () { + $.fn.carousel = old + return this + } + + + // CAROUSEL DATA-API + // ================= + + var clickHandler = function (e) { + var href + var $this = $(this) + var $target = $($this.attr('data-target') || (href = $this.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '')) // strip for ie7 + if (!$target.hasClass('carousel')) return + var options = $.extend({}, $target.data(), $this.data()) + var slideIndex = $this.attr('data-slide-to') + if (slideIndex) options.interval = false + + Plugin.call($target, options) + + if (slideIndex) { + $target.data('bs.carousel').to(slideIndex) + } + + e.preventDefault() + } + + $(document) + .on('click.bs.carousel.data-api', '[data-slide]', clickHandler) + .on('click.bs.carousel.data-api', '[data-slide-to]', clickHandler) + + $(window).on('load', function () { + $('[data-ride="carousel"]').each(function () { + var $carousel = $(this) + Plugin.call($carousel, $carousel.data()) + }) + }) + +}(jQuery); + +/* ======================================================================== + * Bootstrap: collapse.js v3.3.5 + * http://getbootstrap.com/javascript/#collapse + * ======================================================================== + * Copyright 2011-2015 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * ======================================================================== */ + + ++function ($) { + 'use strict'; + + // COLLAPSE PUBLIC CLASS DEFINITION + // ================================ + + var Collapse = function (element, options) { + this.$element = $(element) + this.options = $.extend({}, Collapse.DEFAULTS, options) + this.$trigger = $('[data-toggle="collapse"][href="#' + element.id + '"],' + + '[data-toggle="collapse"][data-target="#' + element.id + '"]') + this.transitioning = null + + if (this.options.parent) { + this.$parent = this.getParent() + } else { + this.addAriaAndCollapsedClass(this.$element, this.$trigger) + } + + if (this.options.toggle) this.toggle() + } + + Collapse.VERSION = '3.3.5' + + Collapse.TRANSITION_DURATION = 350 + + Collapse.DEFAULTS = { + toggle: true + } + + Collapse.prototype.dimension = function () { + var hasWidth = this.$element.hasClass('width') + return hasWidth ? 'width' : 'height' + } + + Collapse.prototype.show = function () { + if (this.transitioning || this.$element.hasClass('in')) return + + var activesData + var actives = this.$parent && this.$parent.children('.panel').children('.in, .collapsing') + + if (actives && actives.length) { + activesData = actives.data('bs.collapse') + if (activesData && activesData.transitioning) return + } + + var startEvent = $.Event('show.bs.collapse') + this.$element.trigger(startEvent) + if (startEvent.isDefaultPrevented()) return + + if (actives && actives.length) { + Plugin.call(actives, 'hide') + activesData || actives.data('bs.collapse', null) + } + + var dimension = this.dimension() + + this.$element + .removeClass('collapse') + .addClass('collapsing')[dimension](0) + .attr('aria-expanded', true) + + this.$trigger + .removeClass('collapsed') + .attr('aria-expanded', true) + + this.transitioning = 1 + + var complete = function () { + this.$element + .removeClass('collapsing') + .addClass('collapse in')[dimension]('') + this.transitioning = 0 + this.$element + .trigger('shown.bs.collapse') + } + + if (!$.support.transition) return complete.call(this) + + var scrollSize = $.camelCase(['scroll', dimension].join('-')) + + this.$element + .one('bsTransitionEnd', $.proxy(complete, this)) + .emulateTransitionEnd(Collapse.TRANSITION_DURATION)[dimension](this.$element[0][scrollSize]) + } + + Collapse.prototype.hide = function () { + if (this.transitioning || !this.$element.hasClass('in')) return + + var startEvent = $.Event('hide.bs.collapse') + this.$element.trigger(startEvent) + if (startEvent.isDefaultPrevented()) return + + var dimension = this.dimension() + + this.$element[dimension](this.$element[dimension]())[0].offsetHeight + + this.$element + .addClass('collapsing') + .removeClass('collapse in') + .attr('aria-expanded', false) + + this.$trigger + .addClass('collapsed') + .attr('aria-expanded', false) + + this.transitioning = 1 + + var complete = function () { + this.transitioning = 0 + this.$element + .removeClass('collapsing') + .addClass('collapse') + .trigger('hidden.bs.collapse') + } + + if (!$.support.transition) return complete.call(this) + + this.$element + [dimension](0) + .one('bsTransitionEnd', $.proxy(complete, this)) + .emulateTransitionEnd(Collapse.TRANSITION_DURATION) + } + + Collapse.prototype.toggle = function () { + this[this.$element.hasClass('in') ? 'hide' : 'show']() + } + + Collapse.prototype.getParent = function () { + return $(this.options.parent) + .find('[data-toggle="collapse"][data-parent="' + this.options.parent + '"]') + .each($.proxy(function (i, element) { + var $element = $(element) + this.addAriaAndCollapsedClass(getTargetFromTrigger($element), $element) + }, this)) + .end() + } + + Collapse.prototype.addAriaAndCollapsedClass = function ($element, $trigger) { + var isOpen = $element.hasClass('in') + + $element.attr('aria-expanded', isOpen) + $trigger + .toggleClass('collapsed', !isOpen) + .attr('aria-expanded', isOpen) + } + + function getTargetFromTrigger($trigger) { + var href + var target = $trigger.attr('data-target') + || (href = $trigger.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '') // strip for ie7 + + return $(target) + } + + + // COLLAPSE PLUGIN DEFINITION + // ========================== + + function Plugin(option) { + return this.each(function () { + var $this = $(this) + var data = $this.data('bs.collapse') + var options = $.extend({}, Collapse.DEFAULTS, $this.data(), typeof option == 'object' && option) + + if (!data && options.toggle && /show|hide/.test(option)) options.toggle = false + if (!data) $this.data('bs.collapse', (data = new Collapse(this, options))) + if (typeof option == 'string') data[option]() + }) + } + + var old = $.fn.collapse + + $.fn.collapse = Plugin + $.fn.collapse.Constructor = Collapse + + + // COLLAPSE NO CONFLICT + // ==================== + + $.fn.collapse.noConflict = function () { + $.fn.collapse = old + return this + } + + + // COLLAPSE DATA-API + // ================= + + $(document).on('click.bs.collapse.data-api', '[data-toggle="collapse"]', function (e) { + var $this = $(this) + + if (!$this.attr('data-target')) e.preventDefault() + + var $target = getTargetFromTrigger($this) + var data = $target.data('bs.collapse') + var option = data ? 'toggle' : $this.data() + + Plugin.call($target, option) + }) + +}(jQuery); + +/* ======================================================================== + * Bootstrap: dropdown.js v3.3.5 + * http://getbootstrap.com/javascript/#dropdowns + * ======================================================================== + * Copyright 2011-2015 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * ======================================================================== */ + + ++function ($) { + 'use strict'; + + // DROPDOWN CLASS DEFINITION + // ========================= + + var backdrop = '.dropdown-backdrop' + var toggle = '[data-toggle="dropdown"]' + var Dropdown = function (element) { + $(element).on('click.bs.dropdown', this.toggle) + } + + Dropdown.VERSION = '3.3.5' + + function getParent($this) { + var selector = $this.attr('data-target') + + if (!selector) { + selector = $this.attr('href') + selector = selector && /#[A-Za-z]/.test(selector) && selector.replace(/.*(?=#[^\s]*$)/, '') // strip for ie7 + } + + var $parent = selector && $(selector) + + return $parent && $parent.length ? $parent : $this.parent() + } + + function clearMenus(e) { + if (e && e.which === 3) return + $(backdrop).remove() + $(toggle).each(function () { + var $this = $(this) + var $parent = getParent($this) + var relatedTarget = { relatedTarget: this } + + if (!$parent.hasClass('open')) return + + if (e && e.type == 'click' && /input|textarea/i.test(e.target.tagName) && $.contains($parent[0], e.target)) return + + $parent.trigger(e = $.Event('hide.bs.dropdown', relatedTarget)) + + if (e.isDefaultPrevented()) return + + $this.attr('aria-expanded', 'false') + $parent.removeClass('open').trigger('hidden.bs.dropdown', relatedTarget) + }) + } + + Dropdown.prototype.toggle = function (e) { + var $this = $(this) + + if ($this.is('.disabled, :disabled')) return + + var $parent = getParent($this) + var isActive = $parent.hasClass('open') + + clearMenus() + + if (!isActive) { + if ('ontouchstart' in document.documentElement && !$parent.closest('.navbar-nav').length) { + // if mobile we use a backdrop because click events don't delegate + $(document.createElement('div')) + .addClass('dropdown-backdrop') + .insertAfter($(this)) + .on('click', clearMenus) + } + + var relatedTarget = { relatedTarget: this } + $parent.trigger(e = $.Event('show.bs.dropdown', relatedTarget)) + + if (e.isDefaultPrevented()) return + + $this + .trigger('focus') + .attr('aria-expanded', 'true') + + $parent + .toggleClass('open') + .trigger('shown.bs.dropdown', relatedTarget) + } + + return false + } + + Dropdown.prototype.keydown = function (e) { + if (!/(38|40|27|32)/.test(e.which) || /input|textarea/i.test(e.target.tagName)) return + + var $this = $(this) + + e.preventDefault() + e.stopPropagation() + + if ($this.is('.disabled, :disabled')) return + + var $parent = getParent($this) + var isActive = $parent.hasClass('open') + + if (!isActive && e.which != 27 || isActive && e.which == 27) { + if (e.which == 27) $parent.find(toggle).trigger('focus') + return $this.trigger('click') + } + + var desc = ' li:not(.disabled):visible a' + var $items = $parent.find('.dropdown-menu' + desc) + + if (!$items.length) return + + var index = $items.index(e.target) + + if (e.which == 38 && index > 0) index-- // up + if (e.which == 40 && index < $items.length - 1) index++ // down + if (!~index) index = 0 + + $items.eq(index).trigger('focus') + } + + + // DROPDOWN PLUGIN DEFINITION + // ========================== + + function Plugin(option) { + return this.each(function () { + var $this = $(this) + var data = $this.data('bs.dropdown') + + if (!data) $this.data('bs.dropdown', (data = new Dropdown(this))) + if (typeof option == 'string') data[option].call($this) + }) + } + + var old = $.fn.dropdown + + $.fn.dropdown = Plugin + $.fn.dropdown.Constructor = Dropdown + + + // DROPDOWN NO CONFLICT + // ==================== + + $.fn.dropdown.noConflict = function () { + $.fn.dropdown = old + return this + } + + + // APPLY TO STANDARD DROPDOWN ELEMENTS + // =================================== + + $(document) + .on('click.bs.dropdown.data-api', clearMenus) + .on('click.bs.dropdown.data-api', '.dropdown form', function (e) { e.stopPropagation() }) + .on('click.bs.dropdown.data-api', toggle, Dropdown.prototype.toggle) + .on('keydown.bs.dropdown.data-api', toggle, Dropdown.prototype.keydown) + .on('keydown.bs.dropdown.data-api', '.dropdown-menu', Dropdown.prototype.keydown) + +}(jQuery); + +/* ======================================================================== + * Bootstrap: modal.js v3.3.5 + * http://getbootstrap.com/javascript/#modals + * ======================================================================== + * Copyright 2011-2015 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * ======================================================================== */ + + ++function ($) { + 'use strict'; + + // MODAL CLASS DEFINITION + // ====================== + + var Modal = function (element, options) { + this.options = options + this.$body = $(document.body) + this.$element = $(element) + this.$dialog = this.$element.find('.modal-dialog') + this.$backdrop = null + this.isShown = null + this.originalBodyPad = null + this.scrollbarWidth = 0 + this.ignoreBackdropClick = false + + if (this.options.remote) { + this.$element + .find('.modal-content') + .load(this.options.remote, $.proxy(function () { + this.$element.trigger('loaded.bs.modal') + }, this)) + } + } + + Modal.VERSION = '3.3.5' + + Modal.TRANSITION_DURATION = 300 + Modal.BACKDROP_TRANSITION_DURATION = 150 + + Modal.DEFAULTS = { + backdrop: true, + keyboard: true, + show: true + } + + Modal.prototype.toggle = function (_relatedTarget) { + return this.isShown ? this.hide() : this.show(_relatedTarget) + } + + Modal.prototype.show = function (_relatedTarget) { + var that = this + var e = $.Event('show.bs.modal', { relatedTarget: _relatedTarget }) + + this.$element.trigger(e) + + if (this.isShown || e.isDefaultPrevented()) return + + this.isShown = true + + this.checkScrollbar() + this.setScrollbar() + this.$body.addClass('modal-open') + + this.escape() + this.resize() + + this.$element.on('click.dismiss.bs.modal', '[data-dismiss="modal"]', $.proxy(this.hide, this)) + + this.$dialog.on('mousedown.dismiss.bs.modal', function () { + that.$element.one('mouseup.dismiss.bs.modal', function (e) { + if ($(e.target).is(that.$element)) that.ignoreBackdropClick = true + }) + }) + + this.backdrop(function () { + var transition = $.support.transition && that.$element.hasClass('fade') + + if (!that.$element.parent().length) { + that.$element.appendTo(that.$body) // don't move modals dom position + } + + that.$element + .show() + .scrollTop(0) + + that.adjustDialog() + + if (transition) { + that.$element[0].offsetWidth // force reflow + } + + that.$element.addClass('in') + + that.enforceFocus() + + var e = $.Event('shown.bs.modal', { relatedTarget: _relatedTarget }) + + transition ? + that.$dialog // wait for modal to slide in + .one('bsTransitionEnd', function () { + that.$element.trigger('focus').trigger(e) + }) + .emulateTransitionEnd(Modal.TRANSITION_DURATION) : + that.$element.trigger('focus').trigger(e) + }) + } + + Modal.prototype.hide = function (e) { + if (e) e.preventDefault() + + e = $.Event('hide.bs.modal') + + this.$element.trigger(e) + + if (!this.isShown || e.isDefaultPrevented()) return + + this.isShown = false + + this.escape() + this.resize() + + $(document).off('focusin.bs.modal') + + this.$element + .removeClass('in') + .off('click.dismiss.bs.modal') + .off('mouseup.dismiss.bs.modal') + + this.$dialog.off('mousedown.dismiss.bs.modal') + + $.support.transition && this.$element.hasClass('fade') ? + this.$element + .one('bsTransitionEnd', $.proxy(this.hideModal, this)) + .emulateTransitionEnd(Modal.TRANSITION_DURATION) : + this.hideModal() + } + + Modal.prototype.enforceFocus = function () { + $(document) + .off('focusin.bs.modal') // guard against infinite focus loop + .on('focusin.bs.modal', $.proxy(function (e) { + if (this.$element[0] !== e.target && !this.$element.has(e.target).length) { + this.$element.trigger('focus') + } + }, this)) + } + + Modal.prototype.escape = function () { + if (this.isShown && this.options.keyboard) { + this.$element.on('keydown.dismiss.bs.modal', $.proxy(function (e) { + e.which == 27 && this.hide() + }, this)) + } else if (!this.isShown) { + this.$element.off('keydown.dismiss.bs.modal') + } + } + + Modal.prototype.resize = function () { + if (this.isShown) { + $(window).on('resize.bs.modal', $.proxy(this.handleUpdate, this)) + } else { + $(window).off('resize.bs.modal') + } + } + + Modal.prototype.hideModal = function () { + var that = this + this.$element.hide() + this.backdrop(function () { + that.$body.removeClass('modal-open') + that.resetAdjustments() + that.resetScrollbar() + that.$element.trigger('hidden.bs.modal') + }) + } + + Modal.prototype.removeBackdrop = function () { + this.$backdrop && this.$backdrop.remove() + this.$backdrop = null + } + + Modal.prototype.backdrop = function (callback) { + var that = this + var animate = this.$element.hasClass('fade') ? 'fade' : '' + + if (this.isShown && this.options.backdrop) { + var doAnimate = $.support.transition && animate + + this.$backdrop = $(document.createElement('div')) + .addClass('modal-backdrop ' + animate) + .appendTo(this.$body) + + this.$element.on('click.dismiss.bs.modal', $.proxy(function (e) { + if (this.ignoreBackdropClick) { + this.ignoreBackdropClick = false + return + } + if (e.target !== e.currentTarget) return + this.options.backdrop == 'static' + ? this.$element[0].focus() + : this.hide() + }, this)) + + if (doAnimate) this.$backdrop[0].offsetWidth // force reflow + + this.$backdrop.addClass('in') + + if (!callback) return + + doAnimate ? + this.$backdrop + .one('bsTransitionEnd', callback) + .emulateTransitionEnd(Modal.BACKDROP_TRANSITION_DURATION) : + callback() + + } else if (!this.isShown && this.$backdrop) { + this.$backdrop.removeClass('in') + + var callbackRemove = function () { + that.removeBackdrop() + callback && callback() + } + $.support.transition && this.$element.hasClass('fade') ? + this.$backdrop + .one('bsTransitionEnd', callbackRemove) + .emulateTransitionEnd(Modal.BACKDROP_TRANSITION_DURATION) : + callbackRemove() + + } else if (callback) { + callback() + } + } + + // these following methods are used to handle overflowing modals + + Modal.prototype.handleUpdate = function () { + this.adjustDialog() + } + + Modal.prototype.adjustDialog = function () { + var modalIsOverflowing = this.$element[0].scrollHeight > document.documentElement.clientHeight + + this.$element.css({ + paddingLeft: !this.bodyIsOverflowing && modalIsOverflowing ? this.scrollbarWidth : '', + paddingRight: this.bodyIsOverflowing && !modalIsOverflowing ? this.scrollbarWidth : '' + }) + } + + Modal.prototype.resetAdjustments = function () { + this.$element.css({ + paddingLeft: '', + paddingRight: '' + }) + } + + Modal.prototype.checkScrollbar = function () { + var fullWindowWidth = window.innerWidth + if (!fullWindowWidth) { // workaround for missing window.innerWidth in IE8 + var documentElementRect = document.documentElement.getBoundingClientRect() + fullWindowWidth = documentElementRect.right - Math.abs(documentElementRect.left) + } + this.bodyIsOverflowing = document.body.clientWidth < fullWindowWidth + this.scrollbarWidth = this.measureScrollbar() + } + + Modal.prototype.setScrollbar = function () { + var bodyPad = parseInt((this.$body.css('padding-right') || 0), 10) + this.originalBodyPad = document.body.style.paddingRight || '' + if (this.bodyIsOverflowing) this.$body.css('padding-right', bodyPad + this.scrollbarWidth) + } + + Modal.prototype.resetScrollbar = function () { + this.$body.css('padding-right', this.originalBodyPad) + } + + Modal.prototype.measureScrollbar = function () { // thx walsh + var scrollDiv = document.createElement('div') + scrollDiv.className = 'modal-scrollbar-measure' + this.$body.append(scrollDiv) + var scrollbarWidth = scrollDiv.offsetWidth - scrollDiv.clientWidth + this.$body[0].removeChild(scrollDiv) + return scrollbarWidth + } + + + // MODAL PLUGIN DEFINITION + // ======================= + + function Plugin(option, _relatedTarget) { + return this.each(function () { + var $this = $(this) + var data = $this.data('bs.modal') + var options = $.extend({}, Modal.DEFAULTS, $this.data(), typeof option == 'object' && option) + + if (!data) $this.data('bs.modal', (data = new Modal(this, options))) + if (typeof option == 'string') data[option](_relatedTarget) + else if (options.show) data.show(_relatedTarget) + }) + } + + var old = $.fn.modal + + $.fn.modal = Plugin + $.fn.modal.Constructor = Modal + + + // MODAL NO CONFLICT + // ================= + + $.fn.modal.noConflict = function () { + $.fn.modal = old + return this + } + + + // MODAL DATA-API + // ============== + + $(document).on('click.bs.modal.data-api', '[data-toggle="modal"]', function (e) { + var $this = $(this) + var href = $this.attr('href') + var $target = $($this.attr('data-target') || (href && href.replace(/.*(?=#[^\s]+$)/, ''))) // strip for ie7 + var option = $target.data('bs.modal') ? 'toggle' : $.extend({ remote: !/#/.test(href) && href }, $target.data(), $this.data()) + + if ($this.is('a')) e.preventDefault() + + $target.one('show.bs.modal', function (showEvent) { + if (showEvent.isDefaultPrevented()) return // only register focus restorer if modal will actually get shown + $target.one('hidden.bs.modal', function () { + $this.is(':visible') && $this.trigger('focus') + }) + }) + Plugin.call($target, option, this) + }) + +}(jQuery); + +/* ======================================================================== + * Bootstrap: tooltip.js v3.3.5 + * http://getbootstrap.com/javascript/#tooltip + * Inspired by the original jQuery.tipsy by Jason Frame + * ======================================================================== + * Copyright 2011-2015 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * ======================================================================== */ + + ++function ($) { + 'use strict'; + + // TOOLTIP PUBLIC CLASS DEFINITION + // =============================== + + var Tooltip = function (element, options) { + this.type = null + this.options = null + this.enabled = null + this.timeout = null + this.hoverState = null + this.$element = null + this.inState = null + + this.init('tooltip', element, options) + } + + Tooltip.VERSION = '3.3.5' + + Tooltip.TRANSITION_DURATION = 150 + + Tooltip.DEFAULTS = { + animation: true, + placement: 'top', + selector: false, + template: '', + trigger: 'hover focus', + title: '', + delay: 0, + html: false, + container: false, + viewport: { + selector: 'body', + padding: 0 + } + } + + Tooltip.prototype.init = function (type, element, options) { + this.enabled = true + this.type = type + this.$element = $(element) + this.options = this.getOptions(options) + this.$viewport = this.options.viewport && $($.isFunction(this.options.viewport) ? this.options.viewport.call(this, this.$element) : (this.options.viewport.selector || this.options.viewport)) + this.inState = { click: false, hover: false, focus: false } + + if (this.$element[0] instanceof document.constructor && !this.options.selector) { + throw new Error('`selector` option must be specified when initializing ' + this.type + ' on the window.document object!') + } + + var triggers = this.options.trigger.split(' ') + + for (var i = triggers.length; i--;) { + var trigger = triggers[i] + + if (trigger == 'click') { + this.$element.on('click.' + this.type, this.options.selector, $.proxy(this.toggle, this)) + } else if (trigger != 'manual') { + var eventIn = trigger == 'hover' ? 'mouseenter' : 'focusin' + var eventOut = trigger == 'hover' ? 'mouseleave' : 'focusout' + + this.$element.on(eventIn + '.' + this.type, this.options.selector, $.proxy(this.enter, this)) + this.$element.on(eventOut + '.' + this.type, this.options.selector, $.proxy(this.leave, this)) + } + } + + this.options.selector ? + (this._options = $.extend({}, this.options, { trigger: 'manual', selector: '' })) : + this.fixTitle() + } + + Tooltip.prototype.getDefaults = function () { + return Tooltip.DEFAULTS + } + + Tooltip.prototype.getOptions = function (options) { + options = $.extend({}, this.getDefaults(), this.$element.data(), options) + + if (options.delay && typeof options.delay == 'number') { + options.delay = { + show: options.delay, + hide: options.delay + } + } + + return options + } + + Tooltip.prototype.getDelegateOptions = function () { + var options = {} + var defaults = this.getDefaults() + + this._options && $.each(this._options, function (key, value) { + if (defaults[key] != value) options[key] = value + }) + + return options + } + + Tooltip.prototype.enter = function (obj) { + var self = obj instanceof this.constructor ? + obj : $(obj.currentTarget).data('bs.' + this.type) + + if (!self) { + self = new this.constructor(obj.currentTarget, this.getDelegateOptions()) + $(obj.currentTarget).data('bs.' + this.type, self) + } + + if (obj instanceof $.Event) { + self.inState[obj.type == 'focusin' ? 'focus' : 'hover'] = true + } + + if (self.tip().hasClass('in') || self.hoverState == 'in') { + self.hoverState = 'in' + return + } + + clearTimeout(self.timeout) + + self.hoverState = 'in' + + if (!self.options.delay || !self.options.delay.show) return self.show() + + self.timeout = setTimeout(function () { + if (self.hoverState == 'in') self.show() + }, self.options.delay.show) + } + + Tooltip.prototype.isInStateTrue = function () { + for (var key in this.inState) { + if (this.inState[key]) return true + } + + return false + } + + Tooltip.prototype.leave = function (obj) { + var self = obj instanceof this.constructor ? + obj : $(obj.currentTarget).data('bs.' + this.type) + + if (!self) { + self = new this.constructor(obj.currentTarget, this.getDelegateOptions()) + $(obj.currentTarget).data('bs.' + this.type, self) + } + + if (obj instanceof $.Event) { + self.inState[obj.type == 'focusout' ? 'focus' : 'hover'] = false + } + + if (self.isInStateTrue()) return + + clearTimeout(self.timeout) + + self.hoverState = 'out' + + if (!self.options.delay || !self.options.delay.hide) return self.hide() + + self.timeout = setTimeout(function () { + if (self.hoverState == 'out') self.hide() + }, self.options.delay.hide) + } + + Tooltip.prototype.show = function () { + var e = $.Event('show.bs.' + this.type) + + if (this.hasContent() && this.enabled) { + this.$element.trigger(e) + + var inDom = $.contains(this.$element[0].ownerDocument.documentElement, this.$element[0]) + if (e.isDefaultPrevented() || !inDom) return + var that = this + + var $tip = this.tip() + + var tipId = this.getUID(this.type) + + this.setContent() + $tip.attr('id', tipId) + this.$element.attr('aria-describedby', tipId) + + if (this.options.animation) $tip.addClass('fade') + + var placement = typeof this.options.placement == 'function' ? + this.options.placement.call(this, $tip[0], this.$element[0]) : + this.options.placement + + var autoToken = /\s?auto?\s?/i + var autoPlace = autoToken.test(placement) + if (autoPlace) placement = placement.replace(autoToken, '') || 'top' + + $tip + .detach() + .css({ top: 0, left: 0, display: 'block' }) + .addClass(placement) + .data('bs.' + this.type, this) + + this.options.container ? $tip.appendTo(this.options.container) : $tip.insertAfter(this.$element) + this.$element.trigger('inserted.bs.' + this.type) + + var pos = this.getPosition() + var actualWidth = $tip[0].offsetWidth + var actualHeight = $tip[0].offsetHeight + + if (autoPlace) { + var orgPlacement = placement + var viewportDim = this.getPosition(this.$viewport) + + placement = placement == 'bottom' && pos.bottom + actualHeight > viewportDim.bottom ? 'top' : + placement == 'top' && pos.top - actualHeight < viewportDim.top ? 'bottom' : + placement == 'right' && pos.right + actualWidth > viewportDim.width ? 'left' : + placement == 'left' && pos.left - actualWidth < viewportDim.left ? 'right' : + placement + + $tip + .removeClass(orgPlacement) + .addClass(placement) + } + + var calculatedOffset = this.getCalculatedOffset(placement, pos, actualWidth, actualHeight) + + this.applyPlacement(calculatedOffset, placement) + + var complete = function () { + var prevHoverState = that.hoverState + that.$element.trigger('shown.bs.' + that.type) + that.hoverState = null + + if (prevHoverState == 'out') that.leave(that) + } + + $.support.transition && this.$tip.hasClass('fade') ? + $tip + .one('bsTransitionEnd', complete) + .emulateTransitionEnd(Tooltip.TRANSITION_DURATION) : + complete() + } + } + + Tooltip.prototype.applyPlacement = function (offset, placement) { + var $tip = this.tip() + var width = $tip[0].offsetWidth + var height = $tip[0].offsetHeight + + // manually read margins because getBoundingClientRect includes difference + var marginTop = parseInt($tip.css('margin-top'), 10) + var marginLeft = parseInt($tip.css('margin-left'), 10) + + // we must check for NaN for ie 8/9 + if (isNaN(marginTop)) marginTop = 0 + if (isNaN(marginLeft)) marginLeft = 0 + + offset.top += marginTop + offset.left += marginLeft + + // $.fn.offset doesn't round pixel values + // so we use setOffset directly with our own function B-0 + $.offset.setOffset($tip[0], $.extend({ + using: function (props) { + $tip.css({ + top: Math.round(props.top), + left: Math.round(props.left) + }) + } + }, offset), 0) + + $tip.addClass('in') + + // check to see if placing tip in new offset caused the tip to resize itself + var actualWidth = $tip[0].offsetWidth + var actualHeight = $tip[0].offsetHeight + + if (placement == 'top' && actualHeight != height) { + offset.top = offset.top + height - actualHeight + } + + var delta = this.getViewportAdjustedDelta(placement, offset, actualWidth, actualHeight) + + if (delta.left) offset.left += delta.left + else offset.top += delta.top + + var isVertical = /top|bottom/.test(placement) + var arrowDelta = isVertical ? delta.left * 2 - width + actualWidth : delta.top * 2 - height + actualHeight + var arrowOffsetPosition = isVertical ? 'offsetWidth' : 'offsetHeight' + + $tip.offset(offset) + this.replaceArrow(arrowDelta, $tip[0][arrowOffsetPosition], isVertical) + } + + Tooltip.prototype.replaceArrow = function (delta, dimension, isVertical) { + this.arrow() + .css(isVertical ? 'left' : 'top', 50 * (1 - delta / dimension) + '%') + .css(isVertical ? 'top' : 'left', '') + } + + Tooltip.prototype.setContent = function () { + var $tip = this.tip() + var title = this.getTitle() + + $tip.find('.tooltip-inner')[this.options.html ? 'html' : 'text'](title) + $tip.removeClass('fade in top bottom left right') + } + + Tooltip.prototype.hide = function (callback) { + var that = this + var $tip = $(this.$tip) + var e = $.Event('hide.bs.' + this.type) + + function complete() { + if (that.hoverState != 'in') $tip.detach() + that.$element + .removeAttr('aria-describedby') + .trigger('hidden.bs.' + that.type) + callback && callback() + } + + this.$element.trigger(e) + + if (e.isDefaultPrevented()) return + + $tip.removeClass('in') + + $.support.transition && $tip.hasClass('fade') ? + $tip + .one('bsTransitionEnd', complete) + .emulateTransitionEnd(Tooltip.TRANSITION_DURATION) : + complete() + + this.hoverState = null + + return this + } + + Tooltip.prototype.fixTitle = function () { + var $e = this.$element + if ($e.attr('title') || typeof $e.attr('data-original-title') != 'string') { + $e.attr('data-original-title', $e.attr('title') || '').attr('title', '') + } + } + + Tooltip.prototype.hasContent = function () { + return this.getTitle() + } + + Tooltip.prototype.getPosition = function ($element) { + $element = $element || this.$element + + var el = $element[0] + var isBody = el.tagName == 'BODY' + + var elRect = el.getBoundingClientRect() + if (elRect.width == null) { + // width and height are missing in IE8, so compute them manually; see https://github.com/twbs/bootstrap/issues/14093 + elRect = $.extend({}, elRect, { width: elRect.right - elRect.left, height: elRect.bottom - elRect.top }) + } + var elOffset = isBody ? { top: 0, left: 0 } : $element.offset() + var scroll = { scroll: isBody ? document.documentElement.scrollTop || document.body.scrollTop : $element.scrollTop() } + var outerDims = isBody ? { width: $(window).width(), height: $(window).height() } : null + + return $.extend({}, elRect, scroll, outerDims, elOffset) + } + + Tooltip.prototype.getCalculatedOffset = function (placement, pos, actualWidth, actualHeight) { + return placement == 'bottom' ? { top: pos.top + pos.height, left: pos.left + pos.width / 2 - actualWidth / 2 } : + placement == 'top' ? { top: pos.top - actualHeight, left: pos.left + pos.width / 2 - actualWidth / 2 } : + placement == 'left' ? { top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left - actualWidth } : + /* placement == 'right' */ { top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left + pos.width } + + } + + Tooltip.prototype.getViewportAdjustedDelta = function (placement, pos, actualWidth, actualHeight) { + var delta = { top: 0, left: 0 } + if (!this.$viewport) return delta + + var viewportPadding = this.options.viewport && this.options.viewport.padding || 0 + var viewportDimensions = this.getPosition(this.$viewport) + + if (/right|left/.test(placement)) { + var topEdgeOffset = pos.top - viewportPadding - viewportDimensions.scroll + var bottomEdgeOffset = pos.top + viewportPadding - viewportDimensions.scroll + actualHeight + if (topEdgeOffset < viewportDimensions.top) { // top overflow + delta.top = viewportDimensions.top - topEdgeOffset + } else if (bottomEdgeOffset > viewportDimensions.top + viewportDimensions.height) { // bottom overflow + delta.top = viewportDimensions.top + viewportDimensions.height - bottomEdgeOffset + } + } else { + var leftEdgeOffset = pos.left - viewportPadding + var rightEdgeOffset = pos.left + viewportPadding + actualWidth + if (leftEdgeOffset < viewportDimensions.left) { // left overflow + delta.left = viewportDimensions.left - leftEdgeOffset + } else if (rightEdgeOffset > viewportDimensions.right) { // right overflow + delta.left = viewportDimensions.left + viewportDimensions.width - rightEdgeOffset + } + } + + return delta + } + + Tooltip.prototype.getTitle = function () { + var title + var $e = this.$element + var o = this.options + + title = $e.attr('data-original-title') + || (typeof o.title == 'function' ? o.title.call($e[0]) : o.title) + + return title + } + + Tooltip.prototype.getUID = function (prefix) { + do prefix += ~~(Math.random() * 1000000) + while (document.getElementById(prefix)) + return prefix + } + + Tooltip.prototype.tip = function () { + if (!this.$tip) { + this.$tip = $(this.options.template) + if (this.$tip.length != 1) { + throw new Error(this.type + ' `template` option must consist of exactly 1 top-level element!') + } + } + return this.$tip + } + + Tooltip.prototype.arrow = function () { + return (this.$arrow = this.$arrow || this.tip().find('.tooltip-arrow')) + } + + Tooltip.prototype.enable = function () { + this.enabled = true + } + + Tooltip.prototype.disable = function () { + this.enabled = false + } + + Tooltip.prototype.toggleEnabled = function () { + this.enabled = !this.enabled + } + + Tooltip.prototype.toggle = function (e) { + var self = this + if (e) { + self = $(e.currentTarget).data('bs.' + this.type) + if (!self) { + self = new this.constructor(e.currentTarget, this.getDelegateOptions()) + $(e.currentTarget).data('bs.' + this.type, self) + } + } + + if (e) { + self.inState.click = !self.inState.click + if (self.isInStateTrue()) self.enter(self) + else self.leave(self) + } else { + self.tip().hasClass('in') ? self.leave(self) : self.enter(self) + } + } + + Tooltip.prototype.destroy = function () { + var that = this + clearTimeout(this.timeout) + this.hide(function () { + that.$element.off('.' + that.type).removeData('bs.' + that.type) + if (that.$tip) { + that.$tip.detach() + } + that.$tip = null + that.$arrow = null + that.$viewport = null + }) + } + + + // TOOLTIP PLUGIN DEFINITION + // ========================= + + function Plugin(option) { + return this.each(function () { + var $this = $(this) + var data = $this.data('bs.tooltip') + var options = typeof option == 'object' && option + + if (!data && /destroy|hide/.test(option)) return + if (!data) $this.data('bs.tooltip', (data = new Tooltip(this, options))) + if (typeof option == 'string') data[option]() + }) + } + + var old = $.fn.tooltip + + $.fn.tooltip = Plugin + $.fn.tooltip.Constructor = Tooltip + + + // TOOLTIP NO CONFLICT + // =================== + + $.fn.tooltip.noConflict = function () { + $.fn.tooltip = old + return this + } + +}(jQuery); + +/* ======================================================================== + * Bootstrap: popover.js v3.3.5 + * http://getbootstrap.com/javascript/#popovers + * ======================================================================== + * Copyright 2011-2015 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * ======================================================================== */ + + ++function ($) { + 'use strict'; + + // POPOVER PUBLIC CLASS DEFINITION + // =============================== + + var Popover = function (element, options) { + this.init('popover', element, options) + } + + if (!$.fn.tooltip) throw new Error('Popover requires tooltip.js') + + Popover.VERSION = '3.3.5' + + Popover.DEFAULTS = $.extend({}, $.fn.tooltip.Constructor.DEFAULTS, { + placement: 'right', + trigger: 'click', + content: '', + template: '' + }) + + + // NOTE: POPOVER EXTENDS tooltip.js + // ================================ + + Popover.prototype = $.extend({}, $.fn.tooltip.Constructor.prototype) + + Popover.prototype.constructor = Popover + + Popover.prototype.getDefaults = function () { + return Popover.DEFAULTS + } + + Popover.prototype.setContent = function () { + var $tip = this.tip() + var title = this.getTitle() + var content = this.getContent() + + $tip.find('.popover-title')[this.options.html ? 'html' : 'text'](title) + $tip.find('.popover-content').children().detach().end()[ // we use append for html objects to maintain js events + this.options.html ? (typeof content == 'string' ? 'html' : 'append') : 'text' + ](content) + + $tip.removeClass('fade top bottom left right in') + + // IE8 doesn't accept hiding via the `:empty` pseudo selector, we have to do + // this manually by checking the contents. + if (!$tip.find('.popover-title').html()) $tip.find('.popover-title').hide() + } + + Popover.prototype.hasContent = function () { + return this.getTitle() || this.getContent() + } + + Popover.prototype.getContent = function () { + var $e = this.$element + var o = this.options + + return $e.attr('data-content') + || (typeof o.content == 'function' ? + o.content.call($e[0]) : + o.content) + } + + Popover.prototype.arrow = function () { + return (this.$arrow = this.$arrow || this.tip().find('.arrow')) + } + + + // POPOVER PLUGIN DEFINITION + // ========================= + + function Plugin(option) { + return this.each(function () { + var $this = $(this) + var data = $this.data('bs.popover') + var options = typeof option == 'object' && option + + if (!data && /destroy|hide/.test(option)) return + if (!data) $this.data('bs.popover', (data = new Popover(this, options))) + if (typeof option == 'string') data[option]() + }) + } + + var old = $.fn.popover + + $.fn.popover = Plugin + $.fn.popover.Constructor = Popover + + + // POPOVER NO CONFLICT + // =================== + + $.fn.popover.noConflict = function () { + $.fn.popover = old + return this + } + +}(jQuery); + +/* ======================================================================== + * Bootstrap: scrollspy.js v3.3.5 + * http://getbootstrap.com/javascript/#scrollspy + * ======================================================================== + * Copyright 2011-2015 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * ======================================================================== */ + + ++function ($) { + 'use strict'; + + // SCROLLSPY CLASS DEFINITION + // ========================== + + function ScrollSpy(element, options) { + this.$body = $(document.body) + this.$scrollElement = $(element).is(document.body) ? $(window) : $(element) + this.options = $.extend({}, ScrollSpy.DEFAULTS, options) + this.selector = (this.options.target || '') + ' .nav li > a' + this.offsets = [] + this.targets = [] + this.activeTarget = null + this.scrollHeight = 0 + + this.$scrollElement.on('scroll.bs.scrollspy', $.proxy(this.process, this)) + this.refresh() + this.process() + } + + ScrollSpy.VERSION = '3.3.5' + + ScrollSpy.DEFAULTS = { + offset: 10 + } + + ScrollSpy.prototype.getScrollHeight = function () { + return this.$scrollElement[0].scrollHeight || Math.max(this.$body[0].scrollHeight, document.documentElement.scrollHeight) + } + + ScrollSpy.prototype.refresh = function () { + var that = this + var offsetMethod = 'offset' + var offsetBase = 0 + + this.offsets = [] + this.targets = [] + this.scrollHeight = this.getScrollHeight() + + if (!$.isWindow(this.$scrollElement[0])) { + offsetMethod = 'position' + offsetBase = this.$scrollElement.scrollTop() + } + + this.$body + .find(this.selector) + .map(function () { + var $el = $(this) + var href = $el.data('target') || $el.attr('href') + var $href = /^#./.test(href) && $(href) + + return ($href + && $href.length + && $href.is(':visible') + && [[$href[offsetMethod]().top + offsetBase, href]]) || null + }) + .sort(function (a, b) { return a[0] - b[0] }) + .each(function () { + that.offsets.push(this[0]) + that.targets.push(this[1]) + }) + } + + ScrollSpy.prototype.process = function () { + var scrollTop = this.$scrollElement.scrollTop() + this.options.offset + var scrollHeight = this.getScrollHeight() + var maxScroll = this.options.offset + scrollHeight - this.$scrollElement.height() + var offsets = this.offsets + var targets = this.targets + var activeTarget = this.activeTarget + var i + + if (this.scrollHeight != scrollHeight) { + this.refresh() + } + + if (scrollTop >= maxScroll) { + return activeTarget != (i = targets[targets.length - 1]) && this.activate(i) + } + + if (activeTarget && scrollTop < offsets[0]) { + this.activeTarget = null + return this.clear() + } + + for (i = offsets.length; i--;) { + activeTarget != targets[i] + && scrollTop >= offsets[i] + && (offsets[i + 1] === undefined || scrollTop < offsets[i + 1]) + && this.activate(targets[i]) + } + } + + ScrollSpy.prototype.activate = function (target) { + this.activeTarget = target + + this.clear() + + var selector = this.selector + + '[data-target="' + target + '"],' + + this.selector + '[href="' + target + '"]' + + var active = $(selector) + .parents('li') + .addClass('active') + + if (active.parent('.dropdown-menu').length) { + active = active + .closest('li.dropdown') + .addClass('active') + } + + active.trigger('activate.bs.scrollspy') + } + + ScrollSpy.prototype.clear = function () { + $(this.selector) + .parentsUntil(this.options.target, '.active') + .removeClass('active') + } + + + // SCROLLSPY PLUGIN DEFINITION + // =========================== + + function Plugin(option) { + return this.each(function () { + var $this = $(this) + var data = $this.data('bs.scrollspy') + var options = typeof option == 'object' && option + + if (!data) $this.data('bs.scrollspy', (data = new ScrollSpy(this, options))) + if (typeof option == 'string') data[option]() + }) + } + + var old = $.fn.scrollspy + + $.fn.scrollspy = Plugin + $.fn.scrollspy.Constructor = ScrollSpy + + + // SCROLLSPY NO CONFLICT + // ===================== + + $.fn.scrollspy.noConflict = function () { + $.fn.scrollspy = old + return this + } + + + // SCROLLSPY DATA-API + // ================== + + $(window).on('load.bs.scrollspy.data-api', function () { + $('[data-spy="scroll"]').each(function () { + var $spy = $(this) + Plugin.call($spy, $spy.data()) + }) + }) + +}(jQuery); + +/* ======================================================================== + * Bootstrap: tab.js v3.3.5 + * http://getbootstrap.com/javascript/#tabs + * ======================================================================== + * Copyright 2011-2015 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * ======================================================================== */ + + ++function ($) { + 'use strict'; + + // TAB CLASS DEFINITION + // ==================== + + var Tab = function (element) { + // jscs:disable requireDollarBeforejQueryAssignment + this.element = $(element) + // jscs:enable requireDollarBeforejQueryAssignment + } + + Tab.VERSION = '3.3.5' + + Tab.TRANSITION_DURATION = 150 + + Tab.prototype.show = function () { + var $this = this.element + var $ul = $this.closest('ul:not(.dropdown-menu)') + var selector = $this.data('target') + + if (!selector) { + selector = $this.attr('href') + selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') // strip for ie7 + } + + if ($this.parent('li').hasClass('active')) return + + var $previous = $ul.find('.active:last a') + var hideEvent = $.Event('hide.bs.tab', { + relatedTarget: $this[0] + }) + var showEvent = $.Event('show.bs.tab', { + relatedTarget: $previous[0] + }) + + $previous.trigger(hideEvent) + $this.trigger(showEvent) + + if (showEvent.isDefaultPrevented() || hideEvent.isDefaultPrevented()) return + + var $target = $(selector) + + this.activate($this.closest('li'), $ul) + this.activate($target, $target.parent(), function () { + $previous.trigger({ + type: 'hidden.bs.tab', + relatedTarget: $this[0] + }) + $this.trigger({ + type: 'shown.bs.tab', + relatedTarget: $previous[0] + }) + }) + } + + Tab.prototype.activate = function (element, container, callback) { + var $active = container.find('> .active') + var transition = callback + && $.support.transition + && ($active.length && $active.hasClass('fade') || !!container.find('> .fade').length) + + function next() { + $active + .removeClass('active') + .find('> .dropdown-menu > .active') + .removeClass('active') + .end() + .find('[data-toggle="tab"]') + .attr('aria-expanded', false) + + element + .addClass('active') + .find('[data-toggle="tab"]') + .attr('aria-expanded', true) + + if (transition) { + element[0].offsetWidth // reflow for transition + element.addClass('in') + } else { + element.removeClass('fade') + } + + if (element.parent('.dropdown-menu').length) { + element + .closest('li.dropdown') + .addClass('active') + .end() + .find('[data-toggle="tab"]') + .attr('aria-expanded', true) + } + + callback && callback() + } + + $active.length && transition ? + $active + .one('bsTransitionEnd', next) + .emulateTransitionEnd(Tab.TRANSITION_DURATION) : + next() + + $active.removeClass('in') + } + + + // TAB PLUGIN DEFINITION + // ===================== + + function Plugin(option) { + return this.each(function () { + var $this = $(this) + var data = $this.data('bs.tab') + + if (!data) $this.data('bs.tab', (data = new Tab(this))) + if (typeof option == 'string') data[option]() + }) + } + + var old = $.fn.tab + + $.fn.tab = Plugin + $.fn.tab.Constructor = Tab + + + // TAB NO CONFLICT + // =============== + + $.fn.tab.noConflict = function () { + $.fn.tab = old + return this + } + + + // TAB DATA-API + // ============ + + var clickHandler = function (e) { + e.preventDefault() + Plugin.call($(this), 'show') + } + + $(document) + .on('click.bs.tab.data-api', '[data-toggle="tab"]', clickHandler) + .on('click.bs.tab.data-api', '[data-toggle="pill"]', clickHandler) + +}(jQuery); + +/* ======================================================================== + * Bootstrap: affix.js v3.3.5 + * http://getbootstrap.com/javascript/#affix + * ======================================================================== + * Copyright 2011-2015 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * ======================================================================== */ + + ++function ($) { + 'use strict'; + + // AFFIX CLASS DEFINITION + // ====================== + + var Affix = function (element, options) { + this.options = $.extend({}, Affix.DEFAULTS, options) + + this.$target = $(this.options.target) + .on('scroll.bs.affix.data-api', $.proxy(this.checkPosition, this)) + .on('click.bs.affix.data-api', $.proxy(this.checkPositionWithEventLoop, this)) + + this.$element = $(element) + this.affixed = null + this.unpin = null + this.pinnedOffset = null + + this.checkPosition() + } + + Affix.VERSION = '3.3.5' + + Affix.RESET = 'affix affix-top affix-bottom' + + Affix.DEFAULTS = { + offset: 0, + target: window + } + + Affix.prototype.getState = function (scrollHeight, height, offsetTop, offsetBottom) { + var scrollTop = this.$target.scrollTop() + var position = this.$element.offset() + var targetHeight = this.$target.height() + + if (offsetTop != null && this.affixed == 'top') return scrollTop < offsetTop ? 'top' : false + + if (this.affixed == 'bottom') { + if (offsetTop != null) return (scrollTop + this.unpin <= position.top) ? false : 'bottom' + return (scrollTop + targetHeight <= scrollHeight - offsetBottom) ? false : 'bottom' + } + + var initializing = this.affixed == null + var colliderTop = initializing ? scrollTop : position.top + var colliderHeight = initializing ? targetHeight : height + + if (offsetTop != null && scrollTop <= offsetTop) return 'top' + if (offsetBottom != null && (colliderTop + colliderHeight >= scrollHeight - offsetBottom)) return 'bottom' + + return false + } + + Affix.prototype.getPinnedOffset = function () { + if (this.pinnedOffset) return this.pinnedOffset + this.$element.removeClass(Affix.RESET).addClass('affix') + var scrollTop = this.$target.scrollTop() + var position = this.$element.offset() + return (this.pinnedOffset = position.top - scrollTop) + } + + Affix.prototype.checkPositionWithEventLoop = function () { + setTimeout($.proxy(this.checkPosition, this), 1) + } + + Affix.prototype.checkPosition = function () { + if (!this.$element.is(':visible')) return + + var height = this.$element.height() + var offset = this.options.offset + var offsetTop = offset.top + var offsetBottom = offset.bottom + var scrollHeight = Math.max($(document).height(), $(document.body).height()) + + if (typeof offset != 'object') offsetBottom = offsetTop = offset + if (typeof offsetTop == 'function') offsetTop = offset.top(this.$element) + if (typeof offsetBottom == 'function') offsetBottom = offset.bottom(this.$element) + + var affix = this.getState(scrollHeight, height, offsetTop, offsetBottom) + + if (this.affixed != affix) { + if (this.unpin != null) this.$element.css('top', '') + + var affixType = 'affix' + (affix ? '-' + affix : '') + var e = $.Event(affixType + '.bs.affix') + + this.$element.trigger(e) + + if (e.isDefaultPrevented()) return + + this.affixed = affix + this.unpin = affix == 'bottom' ? this.getPinnedOffset() : null + + this.$element + .removeClass(Affix.RESET) + .addClass(affixType) + .trigger(affixType.replace('affix', 'affixed') + '.bs.affix') + } + + if (affix == 'bottom') { + this.$element.offset({ + top: scrollHeight - height - offsetBottom + }) + } + } + + + // AFFIX PLUGIN DEFINITION + // ======================= + + function Plugin(option) { + return this.each(function () { + var $this = $(this) + var data = $this.data('bs.affix') + var options = typeof option == 'object' && option + + if (!data) $this.data('bs.affix', (data = new Affix(this, options))) + if (typeof option == 'string') data[option]() + }) + } + + var old = $.fn.affix + + $.fn.affix = Plugin + $.fn.affix.Constructor = Affix + + + // AFFIX NO CONFLICT + // ================= + + $.fn.affix.noConflict = function () { + $.fn.affix = old + return this + } + + + // AFFIX DATA-API + // ============== + + $(window).on('load', function () { + $('[data-spy="affix"]').each(function () { + var $spy = $(this) + var data = $spy.data() + + data.offset = data.offset || {} + + if (data.offsetBottom != null) data.offset.bottom = data.offsetBottom + if (data.offsetTop != null) data.offset.top = data.offsetTop + + Plugin.call($spy, data) + }) + }) + +}(jQuery); diff --git a/static/src/js/lib/skrollr.js b/static/src/js/lib/skrollr.js new file mode 100644 index 0000000..2ff15f3 --- /dev/null +++ b/static/src/js/lib/skrollr.js @@ -0,0 +1,1771 @@ +/*! + * skrollr core + * + * Alexander Prinzhorn - https://github.com/Prinzhorn/skrollr + * + * Free to use under terms of MIT license + */ +(function(window, document, undefined) { + 'use strict'; + + /* + * Global api. + */ + var skrollr = { + get: function() { + return _instance; + }, + //Main entry point. + init: function(options) { + return _instance || new Skrollr(options); + }, + VERSION: '0.6.26' + }; + + //Minify optimization. + var hasProp = Object.prototype.hasOwnProperty; + var Math = window.Math; + var getStyle = window.getComputedStyle; + + //They will be filled when skrollr gets initialized. + var documentElement; + var body; + + var EVENT_TOUCHSTART = 'touchstart'; + var EVENT_TOUCHMOVE = 'touchmove'; + var EVENT_TOUCHCANCEL = 'touchcancel'; + var EVENT_TOUCHEND = 'touchend'; + + var SKROLLABLE_CLASS = 'skrollable'; + var SKROLLABLE_BEFORE_CLASS = SKROLLABLE_CLASS + '-before'; + var SKROLLABLE_BETWEEN_CLASS = SKROLLABLE_CLASS + '-between'; + var SKROLLABLE_AFTER_CLASS = SKROLLABLE_CLASS + '-after'; + + var SKROLLR_CLASS = 'skrollr'; + var NO_SKROLLR_CLASS = 'no-' + SKROLLR_CLASS; + var SKROLLR_DESKTOP_CLASS = SKROLLR_CLASS + '-desktop'; + var SKROLLR_MOBILE_CLASS = SKROLLR_CLASS + '-mobile'; + + var DEFAULT_EASING = 'linear'; + var DEFAULT_DURATION = 1000;//ms + var DEFAULT_MOBILE_DECELERATION = 0.004;//pixel/ms² + + var DEFAULT_SMOOTH_SCROLLING_DURATION = 200;//ms + + var ANCHOR_START = 'start'; + var ANCHOR_END = 'end'; + var ANCHOR_CENTER = 'center'; + var ANCHOR_BOTTOM = 'bottom'; + + //The property which will be added to the DOM element to hold the ID of the skrollable. + var SKROLLABLE_ID_DOM_PROPERTY = '___skrollable_id'; + + var rxTouchIgnoreTags = /^(?:input|textarea|button|select)$/i; + + var rxTrim = /^\s+|\s+$/g; + + //Find all data-attributes. data-[_constant]-[offset]-[anchor]-[anchor]. + var rxKeyframeAttribute = /^data(?:-(_\w+))?(?:-?(-?\d*\.?\d+p?))?(?:-?(start|end|top|center|bottom))?(?:-?(top|center|bottom))?$/; + + var rxPropValue = /\s*(@?[\w\-\[\]]+)\s*:\s*(.+?)\s*(?:;|$)/gi; + + //Easing function names follow the property in square brackets. + var rxPropEasing = /^(@?[a-z\-]+)\[(\w+)\]$/; + + var rxCamelCase = /-([a-z0-9_])/g; + var rxCamelCaseFn = function(str, letter) { + return letter.toUpperCase(); + }; + + //Numeric values with optional sign. + var rxNumericValue = /[\-+]?[\d]*\.?[\d]+/g; + + //Used to replace occurences of {?} with a number. + var rxInterpolateString = /\{\?\}/g; + + //Finds rgb(a) colors, which don't use the percentage notation. + var rxRGBAIntegerColor = /rgba?\(\s*-?\d+\s*,\s*-?\d+\s*,\s*-?\d+/g; + + //Finds all gradients. + var rxGradient = /[a-z\-]+-gradient/g; + + //Vendor prefix. Will be set once skrollr gets initialized. + var theCSSPrefix = ''; + var theDashedCSSPrefix = ''; + + //Will be called once (when skrollr gets initialized). + var detectCSSPrefix = function() { + //Only relevant prefixes. May be extended. + //Could be dangerous if there will ever be a CSS property which actually starts with "ms". Don't hope so. + var rxPrefixes = /^(?:O|Moz|webkit|ms)|(?:-(?:o|moz|webkit|ms)-)/; + + //Detect prefix for current browser by finding the first property using a prefix. + if(!getStyle) { + return; + } + + var style = getStyle(body, null); + + for(var k in style) { + //We check the key and if the key is a number, we check the value as well, because safari's getComputedStyle returns some weird array-like thingy. + theCSSPrefix = (k.match(rxPrefixes) || (+k == k && style[k].match(rxPrefixes))); + + if(theCSSPrefix) { + break; + } + } + + //Did we even detect a prefix? + if(!theCSSPrefix) { + theCSSPrefix = theDashedCSSPrefix = ''; + + return; + } + + theCSSPrefix = theCSSPrefix[0]; + + //We could have detected either a dashed prefix or this camelCaseish-inconsistent stuff. + if(theCSSPrefix.slice(0,1) === '-') { + theDashedCSSPrefix = theCSSPrefix; + + //There's no logic behind these. Need a look up. + theCSSPrefix = ({ + '-webkit-': 'webkit', + '-moz-': 'Moz', + '-ms-': 'ms', + '-o-': 'O' + })[theCSSPrefix]; + } else { + theDashedCSSPrefix = '-' + theCSSPrefix.toLowerCase() + '-'; + } + }; + + var polyfillRAF = function() { + var requestAnimFrame = window.requestAnimationFrame || window[theCSSPrefix.toLowerCase() + 'RequestAnimationFrame']; + + var lastTime = _now(); + + if(_isMobile || !requestAnimFrame) { + requestAnimFrame = function(callback) { + //How long did it take to render? + var deltaTime = _now() - lastTime; + var delay = Math.max(0, 1000 / 60 - deltaTime); + + return window.setTimeout(function() { + lastTime = _now(); + callback(); + }, delay); + }; + } + + return requestAnimFrame; + }; + + var polyfillCAF = function() { + var cancelAnimFrame = window.cancelAnimationFrame || window[theCSSPrefix.toLowerCase() + 'CancelAnimationFrame']; + + if(_isMobile || !cancelAnimFrame) { + cancelAnimFrame = function(timeout) { + return window.clearTimeout(timeout); + }; + } + + return cancelAnimFrame; + }; + + //Built-in easing functions. + var easings = { + begin: function() { + return 0; + }, + end: function() { + return 1; + }, + linear: function(p) { + return p; + }, + quadratic: function(p) { + return p * p; + }, + cubic: function(p) { + return p * p * p; + }, + swing: function(p) { + return (-Math.cos(p * Math.PI) / 2) + 0.5; + }, + sqrt: function(p) { + return Math.sqrt(p); + }, + outCubic: function(p) { + return (Math.pow((p - 1), 3) + 1); + }, + //see https://www.desmos.com/calculator/tbr20s8vd2 for how I did this + bounce: function(p) { + var a; + + if(p <= 0.5083) { + a = 3; + } else if(p <= 0.8489) { + a = 9; + } else if(p <= 0.96208) { + a = 27; + } else if(p <= 0.99981) { + a = 91; + } else { + return 1; + } + + return 1 - Math.abs(3 * Math.cos(p * a * 1.028) / a); + } + }; + + /** + * Constructor. + */ + function Skrollr(options) { + documentElement = document.documentElement; + body = document.body; + + detectCSSPrefix(); + + _instance = this; + + options = options || {}; + + _constants = options.constants || {}; + + //We allow defining custom easings or overwrite existing. + if(options.easing) { + for(var e in options.easing) { + easings[e] = options.easing[e]; + } + } + + _edgeStrategy = options.edgeStrategy || 'set'; + + _listeners = { + //Function to be called right before rendering. + beforerender: options.beforerender, + + //Function to be called right after finishing rendering. + render: options.render, + + //Function to be called whenever an element with the `data-emit-events` attribute passes a keyframe. + keyframe: options.keyframe + }; + + //forceHeight is true by default + _forceHeight = options.forceHeight !== false; + + if(_forceHeight) { + _scale = options.scale || 1; + } + + _mobileDeceleration = options.mobileDeceleration || DEFAULT_MOBILE_DECELERATION; + + _smoothScrollingEnabled = options.smoothScrolling !== false; + _smoothScrollingDuration = options.smoothScrollingDuration || DEFAULT_SMOOTH_SCROLLING_DURATION; + + //Dummy object. Will be overwritten in the _render method when smooth scrolling is calculated. + _smoothScrolling = { + targetTop: _instance.getScrollTop() + }; + + //A custom check function may be passed. + _isMobile = ((options.mobileCheck || function() { + return (/Android|iPhone|iPad|iPod|BlackBerry/i).test(navigator.userAgent || navigator.vendor || window.opera); + })()); + + if(_isMobile) { + _skrollrBody = document.getElementById('skrollr-body'); + + //Detect 3d transform if there's a skrollr-body (only needed for #skrollr-body). + if(_skrollrBody) { + _detect3DTransforms(); + } + + _initMobile(); + _updateClass(documentElement, [SKROLLR_CLASS, SKROLLR_MOBILE_CLASS], [NO_SKROLLR_CLASS]); + } else { + _updateClass(documentElement, [SKROLLR_CLASS, SKROLLR_DESKTOP_CLASS], [NO_SKROLLR_CLASS]); + } + + //Triggers parsing of elements and a first reflow. + _instance.refresh(); + + _addEvent(window, 'resize orientationchange', function() { + var width = documentElement.clientWidth; + var height = documentElement.clientHeight; + + //Only reflow if the size actually changed (#271). + if(height !== _lastViewportHeight || width !== _lastViewportWidth) { + _lastViewportHeight = height; + _lastViewportWidth = width; + + _requestReflow = true; + } + }); + + var requestAnimFrame = polyfillRAF(); + + //Let's go. + (function animloop(){ + _render(); + _animFrame = requestAnimFrame(animloop); + }()); + + return _instance; + } + + /** + * (Re)parses some or all elements. + */ + Skrollr.prototype.refresh = function(elements) { + var elementIndex; + var elementsLength; + var ignoreID = false; + + //Completely reparse anything without argument. + if(elements === undefined) { + //Ignore that some elements may already have a skrollable ID. + ignoreID = true; + + _skrollables = []; + _skrollableIdCounter = 0; + + elements = document.getElementsByTagName('*'); + } else if(elements.length === undefined) { + //We also accept a single element as parameter. + elements = [elements]; + } + + elementIndex = 0; + elementsLength = elements.length; + + for(; elementIndex < elementsLength; elementIndex++) { + var el = elements[elementIndex]; + var anchorTarget = el; + var keyFrames = []; + + //If this particular element should be smooth scrolled. + var smoothScrollThis = _smoothScrollingEnabled; + + //The edge strategy for this particular element. + var edgeStrategy = _edgeStrategy; + + //If this particular element should emit keyframe events. + var emitEvents = false; + + //If we're reseting the counter, remove any old element ids that may be hanging around. + if(ignoreID && SKROLLABLE_ID_DOM_PROPERTY in el) { + delete el[SKROLLABLE_ID_DOM_PROPERTY]; + } + + if(!el.attributes) { + continue; + } + + //Iterate over all attributes and search for key frame attributes. + var attributeIndex = 0; + var attributesLength = el.attributes.length; + + for (; attributeIndex < attributesLength; attributeIndex++) { + var attr = el.attributes[attributeIndex]; + + if(attr.name === 'data-anchor-target') { + anchorTarget = document.querySelector(attr.value); + + if(anchorTarget === null) { + throw 'Unable to find anchor target "' + attr.value + '"'; + } + + continue; + } + + //Global smooth scrolling can be overridden by the element attribute. + if(attr.name === 'data-smooth-scrolling') { + smoothScrollThis = attr.value !== 'off'; + + continue; + } + + //Global edge strategy can be overridden by the element attribute. + if(attr.name === 'data-edge-strategy') { + edgeStrategy = attr.value; + + continue; + } + + //Is this element tagged with the `data-emit-events` attribute? + if(attr.name === 'data-emit-events') { + emitEvents = true; + + continue; + } + + var match = attr.name.match(rxKeyframeAttribute); + + if(match === null) { + continue; + } + + var kf = { + props: attr.value, + //Point back to the element as well. + element: el, + //The name of the event which this keyframe will fire, if emitEvents is + eventType: attr.name.replace(rxCamelCase, rxCamelCaseFn) + }; + + keyFrames.push(kf); + + var constant = match[1]; + + if(constant) { + //Strip the underscore prefix. + kf.constant = constant.substr(1); + } + + //Get the key frame offset. + var offset = match[2]; + + //Is it a percentage offset? + if(/p$/.test(offset)) { + kf.isPercentage = true; + kf.offset = (offset.slice(0, -1) | 0) / 100; + } else { + kf.offset = (offset | 0); + } + + var anchor1 = match[3]; + + //If second anchor is not set, the first will be taken for both. + var anchor2 = match[4] || anchor1; + + //"absolute" (or "classic") mode, where numbers mean absolute scroll offset. + if(!anchor1 || anchor1 === ANCHOR_START || anchor1 === ANCHOR_END) { + kf.mode = 'absolute'; + + //data-end needs to be calculated after all key frames are known. + if(anchor1 === ANCHOR_END) { + kf.isEnd = true; + } else if(!kf.isPercentage) { + //For data-start we can already set the key frame w/o calculations. + //#59: "scale" options should only affect absolute mode. + kf.offset = kf.offset * _scale; + } + } + //"relative" mode, where numbers are relative to anchors. + else { + kf.mode = 'relative'; + kf.anchors = [anchor1, anchor2]; + } + } + + //Does this element have key frames? + if(!keyFrames.length) { + continue; + } + + //Will hold the original style and class attributes before we controlled the element (see #80). + var styleAttr, classAttr; + + var id; + + if(!ignoreID && SKROLLABLE_ID_DOM_PROPERTY in el) { + //We already have this element under control. Grab the corresponding skrollable id. + id = el[SKROLLABLE_ID_DOM_PROPERTY]; + styleAttr = _skrollables[id].styleAttr; + classAttr = _skrollables[id].classAttr; + } else { + //It's an unknown element. Asign it a new skrollable id. + id = (el[SKROLLABLE_ID_DOM_PROPERTY] = _skrollableIdCounter++); + styleAttr = el.style.cssText; + classAttr = _getClass(el); + } + + _skrollables[id] = { + element: el, + styleAttr: styleAttr, + classAttr: classAttr, + anchorTarget: anchorTarget, + keyFrames: keyFrames, + smoothScrolling: smoothScrollThis, + edgeStrategy: edgeStrategy, + emitEvents: emitEvents, + lastFrameIndex: -1 + }; + + _updateClass(el, [SKROLLABLE_CLASS], []); + } + + //Reflow for the first time. + _reflow(); + + //Now that we got all key frame numbers right, actually parse the properties. + elementIndex = 0; + elementsLength = elements.length; + + for(; elementIndex < elementsLength; elementIndex++) { + var sk = _skrollables[elements[elementIndex][SKROLLABLE_ID_DOM_PROPERTY]]; + + if(sk === undefined) { + continue; + } + + //Parse the property string to objects + _parseProps(sk); + + //Fill key frames with missing properties from left and right + _fillProps(sk); + } + + return _instance; + }; + + /** + * Transform "relative" mode to "absolute" mode. + * That is, calculate anchor position and offset of element. + */ + Skrollr.prototype.relativeToAbsolute = function(element, viewportAnchor, elementAnchor) { + var viewportHeight = documentElement.clientHeight; + var box = element.getBoundingClientRect(); + var absolute = box.top; + + //#100: IE doesn't supply "height" with getBoundingClientRect. + var boxHeight = box.bottom - box.top; + + if(viewportAnchor === ANCHOR_BOTTOM) { + absolute -= viewportHeight; + } else if(viewportAnchor === ANCHOR_CENTER) { + absolute -= viewportHeight / 2; + } + + if(elementAnchor === ANCHOR_BOTTOM) { + absolute += boxHeight; + } else if(elementAnchor === ANCHOR_CENTER) { + absolute += boxHeight / 2; + } + + //Compensate scrolling since getBoundingClientRect is relative to viewport. + absolute += _instance.getScrollTop(); + + return (absolute + 0.5) | 0; + }; + + /** + * Animates scroll top to new position. + */ + Skrollr.prototype.animateTo = function(top, options) { + options = options || {}; + + var now = _now(); + var scrollTop = _instance.getScrollTop(); + + //Setting this to a new value will automatically cause the current animation to stop, if any. + _scrollAnimation = { + startTop: scrollTop, + topDiff: top - scrollTop, + targetTop: top, + duration: options.duration || DEFAULT_DURATION, + startTime: now, + endTime: now + (options.duration || DEFAULT_DURATION), + easing: easings[options.easing || DEFAULT_EASING], + done: options.done + }; + + //Don't queue the animation if there's nothing to animate. + if(!_scrollAnimation.topDiff) { + if(_scrollAnimation.done) { + _scrollAnimation.done.call(_instance, false); + } + + _scrollAnimation = undefined; + } + + return _instance; + }; + + /** + * Stops animateTo animation. + */ + Skrollr.prototype.stopAnimateTo = function() { + if(_scrollAnimation && _scrollAnimation.done) { + _scrollAnimation.done.call(_instance, true); + } + + _scrollAnimation = undefined; + }; + + /** + * Returns if an animation caused by animateTo is currently running. + */ + Skrollr.prototype.isAnimatingTo = function() { + return !!_scrollAnimation; + }; + + Skrollr.prototype.isMobile = function() { + return _isMobile; + }; + + Skrollr.prototype.setScrollTop = function(top, force) { + _forceRender = (force === true); + + if(_isMobile) { + _mobileOffset = Math.min(Math.max(top, 0), _maxKeyFrame); + } else { + window.scrollTo(0, top); + } + + return _instance; + }; + + Skrollr.prototype.getScrollTop = function() { + if(_isMobile) { + return _mobileOffset; + } else { + return window.pageYOffset || documentElement.scrollTop || body.scrollTop || 0; + } + }; + + Skrollr.prototype.getMaxScrollTop = function() { + return _maxKeyFrame; + }; + + Skrollr.prototype.on = function(name, fn) { + _listeners[name] = fn; + + return _instance; + }; + + Skrollr.prototype.off = function(name) { + delete _listeners[name]; + + return _instance; + }; + + Skrollr.prototype.destroy = function() { + var cancelAnimFrame = polyfillCAF(); + cancelAnimFrame(_animFrame); + _removeAllEvents(); + + _updateClass(documentElement, [NO_SKROLLR_CLASS], [SKROLLR_CLASS, SKROLLR_DESKTOP_CLASS, SKROLLR_MOBILE_CLASS]); + + var skrollableIndex = 0; + var skrollablesLength = _skrollables.length; + + for(; skrollableIndex < skrollablesLength; skrollableIndex++) { + _reset(_skrollables[skrollableIndex].element); + } + + documentElement.style.overflow = body.style.overflow = ''; + documentElement.style.height = body.style.height = ''; + + if(_skrollrBody) { + skrollr.setStyle(_skrollrBody, 'transform', 'none'); + } + + _instance = undefined; + _skrollrBody = undefined; + _listeners = undefined; + _forceHeight = undefined; + _maxKeyFrame = 0; + _scale = 1; + _constants = undefined; + _mobileDeceleration = undefined; + _direction = 'down'; + _lastTop = -1; + _lastViewportWidth = 0; + _lastViewportHeight = 0; + _requestReflow = false; + _scrollAnimation = undefined; + _smoothScrollingEnabled = undefined; + _smoothScrollingDuration = undefined; + _smoothScrolling = undefined; + _forceRender = undefined; + _skrollableIdCounter = 0; + _edgeStrategy = undefined; + _isMobile = false; + _mobileOffset = 0; + _translateZ = undefined; + }; + + /* + Private methods. + */ + + var _initMobile = function() { + var initialElement; + var initialTouchY; + var initialTouchX; + var currentElement; + var currentTouchY; + var currentTouchX; + var lastTouchY; + var deltaY; + + var initialTouchTime; + var currentTouchTime; + var lastTouchTime; + var deltaTime; + + _addEvent(documentElement, [EVENT_TOUCHSTART, EVENT_TOUCHMOVE, EVENT_TOUCHCANCEL, EVENT_TOUCHEND].join(' '), function(e) { + var touch = e.changedTouches[0]; + + currentElement = e.target; + + //We don't want text nodes. + while(currentElement.nodeType === 3) { + currentElement = currentElement.parentNode; + } + + currentTouchY = touch.clientY; + currentTouchX = touch.clientX; + currentTouchTime = e.timeStamp; + + if(!rxTouchIgnoreTags.test(currentElement.tagName)) { + e.preventDefault(); + } + + switch(e.type) { + case EVENT_TOUCHSTART: + //The last element we tapped on. + if(initialElement) { + initialElement.blur(); + } + + _instance.stopAnimateTo(); + + initialElement = currentElement; + + initialTouchY = lastTouchY = currentTouchY; + initialTouchX = currentTouchX; + initialTouchTime = currentTouchTime; + + break; + case EVENT_TOUCHMOVE: + //Prevent default event on touchIgnore elements in case they don't have focus yet. + if(rxTouchIgnoreTags.test(currentElement.tagName) && document.activeElement !== currentElement) { + e.preventDefault(); + } + + deltaY = currentTouchY - lastTouchY; + deltaTime = currentTouchTime - lastTouchTime; + + _instance.setScrollTop(_mobileOffset - deltaY, true); + + lastTouchY = currentTouchY; + lastTouchTime = currentTouchTime; + break; + default: + case EVENT_TOUCHCANCEL: + case EVENT_TOUCHEND: + var distanceY = initialTouchY - currentTouchY; + var distanceX = initialTouchX - currentTouchX; + var distance2 = distanceX * distanceX + distanceY * distanceY; + + //Check if it was more like a tap (moved less than 7px). + if(distance2 < 49) { + if(!rxTouchIgnoreTags.test(initialElement.tagName)) { + initialElement.focus(); + + //It was a tap, click the element. + var clickEvent = document.createEvent('MouseEvents'); + clickEvent.initMouseEvent('click', true, true, e.view, 1, touch.screenX, touch.screenY, touch.clientX, touch.clientY, e.ctrlKey, e.altKey, e.shiftKey, e.metaKey, 0, null); + initialElement.dispatchEvent(clickEvent); + } + + return; + } + + initialElement = undefined; + + var speed = deltaY / deltaTime; + + //Cap speed at 3 pixel/ms. + speed = Math.max(Math.min(speed, 3), -3); + + var duration = Math.abs(speed / _mobileDeceleration); + var targetOffset = speed * duration + 0.5 * _mobileDeceleration * duration * duration; + var targetTop = _instance.getScrollTop() - targetOffset; + + //Relative duration change for when scrolling above bounds. + var targetRatio = 0; + + //Change duration proportionally when scrolling would leave bounds. + if(targetTop > _maxKeyFrame) { + targetRatio = (_maxKeyFrame - targetTop) / targetOffset; + + targetTop = _maxKeyFrame; + } else if(targetTop < 0) { + targetRatio = -targetTop / targetOffset; + + targetTop = 0; + } + + duration = duration * (1 - targetRatio); + + _instance.animateTo((targetTop + 0.5) | 0, {easing: 'outCubic', duration: duration}); + break; + } + }); + + //Just in case there has already been some native scrolling, reset it. + window.scrollTo(0, 0); + documentElement.style.overflow = body.style.overflow = 'hidden'; + }; + + /** + * Updates key frames which depend on others / need to be updated on resize. + * That is "end" in "absolute" mode and all key frames in "relative" mode. + * Also handles constants, because they may change on resize. + */ + var _updateDependentKeyFrames = function() { + var viewportHeight = documentElement.clientHeight; + var processedConstants = _processConstants(); + var skrollable; + var element; + var anchorTarget; + var keyFrames; + var keyFrameIndex; + var keyFramesLength; + var kf; + var skrollableIndex; + var skrollablesLength; + var offset; + var constantValue; + + //First process all relative-mode elements and find the max key frame. + skrollableIndex = 0; + skrollablesLength = _skrollables.length; + + for(; skrollableIndex < skrollablesLength; skrollableIndex++) { + skrollable = _skrollables[skrollableIndex]; + element = skrollable.element; + anchorTarget = skrollable.anchorTarget; + keyFrames = skrollable.keyFrames; + + keyFrameIndex = 0; + keyFramesLength = keyFrames.length; + + for(; keyFrameIndex < keyFramesLength; keyFrameIndex++) { + kf = keyFrames[keyFrameIndex]; + + offset = kf.offset; + constantValue = processedConstants[kf.constant] || 0; + + kf.frame = offset; + + if(kf.isPercentage) { + //Convert the offset to percentage of the viewport height. + offset = offset * viewportHeight; + + //Absolute + percentage mode. + kf.frame = offset; + } + + if(kf.mode === 'relative') { + _reset(element); + + kf.frame = _instance.relativeToAbsolute(anchorTarget, kf.anchors[0], kf.anchors[1]) - offset; + + _reset(element, true); + } + + kf.frame += constantValue; + + //Only search for max key frame when forceHeight is enabled. + if(_forceHeight) { + //Find the max key frame, but don't use one of the data-end ones for comparison. + if(!kf.isEnd && kf.frame > _maxKeyFrame) { + _maxKeyFrame = kf.frame; + } + } + } + } + + //#133: The document can be larger than the maxKeyFrame we found. + _maxKeyFrame = Math.max(_maxKeyFrame, _getDocumentHeight()); + + //Now process all data-end keyframes. + skrollableIndex = 0; + skrollablesLength = _skrollables.length; + + for(; skrollableIndex < skrollablesLength; skrollableIndex++) { + skrollable = _skrollables[skrollableIndex]; + keyFrames = skrollable.keyFrames; + + keyFrameIndex = 0; + keyFramesLength = keyFrames.length; + + for(; keyFrameIndex < keyFramesLength; keyFrameIndex++) { + kf = keyFrames[keyFrameIndex]; + + constantValue = processedConstants[kf.constant] || 0; + + if(kf.isEnd) { + kf.frame = _maxKeyFrame - kf.offset + constantValue; + } + } + + skrollable.keyFrames.sort(_keyFrameComparator); + } + }; + + /** + * Calculates and sets the style properties for the element at the given frame. + * @param fakeFrame The frame to render at when smooth scrolling is enabled. + * @param actualFrame The actual frame we are at. + */ + var _calcSteps = function(fakeFrame, actualFrame) { + //Iterate over all skrollables. + var skrollableIndex = 0; + var skrollablesLength = _skrollables.length; + + for(; skrollableIndex < skrollablesLength; skrollableIndex++) { + var skrollable = _skrollables[skrollableIndex]; + var element = skrollable.element; + var frame = skrollable.smoothScrolling ? fakeFrame : actualFrame; + var frames = skrollable.keyFrames; + var framesLength = frames.length; + var firstFrame = frames[0]; + var lastFrame = frames[frames.length - 1]; + var beforeFirst = frame < firstFrame.frame; + var afterLast = frame > lastFrame.frame; + var firstOrLastFrame = beforeFirst ? firstFrame : lastFrame; + var emitEvents = skrollable.emitEvents; + var lastFrameIndex = skrollable.lastFrameIndex; + var key; + var value; + + //If we are before/after the first/last frame, set the styles according to the given edge strategy. + if(beforeFirst || afterLast) { + //Check if we already handled this edge case last time. + //Note: using setScrollTop it's possible that we jumped from one edge to the other. + if(beforeFirst && skrollable.edge === -1 || afterLast && skrollable.edge === 1) { + continue; + } + + //Add the skrollr-before or -after class. + if(beforeFirst) { + _updateClass(element, [SKROLLABLE_BEFORE_CLASS], [SKROLLABLE_AFTER_CLASS, SKROLLABLE_BETWEEN_CLASS]); + + //This handles the special case where we exit the first keyframe. + if(emitEvents && lastFrameIndex > -1) { + _emitEvent(element, firstFrame.eventType, _direction); + skrollable.lastFrameIndex = -1; + } + } else { + _updateClass(element, [SKROLLABLE_AFTER_CLASS], [SKROLLABLE_BEFORE_CLASS, SKROLLABLE_BETWEEN_CLASS]); + + //This handles the special case where we exit the last keyframe. + if(emitEvents && lastFrameIndex < framesLength) { + _emitEvent(element, lastFrame.eventType, _direction); + skrollable.lastFrameIndex = framesLength; + } + } + + //Remember that we handled the edge case (before/after the first/last keyframe). + skrollable.edge = beforeFirst ? -1 : 1; + + switch(skrollable.edgeStrategy) { + case 'reset': + _reset(element); + continue; + case 'ease': + //Handle this case like it would be exactly at first/last keyframe and just pass it on. + frame = firstOrLastFrame.frame; + break; + default: + case 'set': + var props = firstOrLastFrame.props; + + for(key in props) { + if(hasProp.call(props, key)) { + value = _interpolateString(props[key].value); + + //Set style or attribute. + if(key.indexOf('@') === 0) { + element.setAttribute(key.substr(1), value); + } else { + skrollr.setStyle(element, key, value); + } + } + } + + continue; + } + } else { + //Did we handle an edge last time? + if(skrollable.edge !== 0) { + _updateClass(element, [SKROLLABLE_CLASS, SKROLLABLE_BETWEEN_CLASS], [SKROLLABLE_BEFORE_CLASS, SKROLLABLE_AFTER_CLASS]); + skrollable.edge = 0; + } + } + + //Find out between which two key frames we are right now. + var keyFrameIndex = 0; + + for(; keyFrameIndex < framesLength - 1; keyFrameIndex++) { + if(frame >= frames[keyFrameIndex].frame && frame <= frames[keyFrameIndex + 1].frame) { + var left = frames[keyFrameIndex]; + var right = frames[keyFrameIndex + 1]; + + for(key in left.props) { + if(hasProp.call(left.props, key)) { + var progress = (frame - left.frame) / (right.frame - left.frame); + + //Transform the current progress using the given easing function. + progress = left.props[key].easing(progress); + + //Interpolate between the two values + value = _calcInterpolation(left.props[key].value, right.props[key].value, progress); + + value = _interpolateString(value); + + //Set style or attribute. + if(key.indexOf('@') === 0) { + element.setAttribute(key.substr(1), value); + } else { + skrollr.setStyle(element, key, value); + } + } + } + + //Are events enabled on this element? + //This code handles the usual cases of scrolling through different keyframes. + //The special cases of before first and after last keyframe are handled above. + if(emitEvents) { + //Did we pass a new keyframe? + if(lastFrameIndex !== keyFrameIndex) { + if(_direction === 'down') { + _emitEvent(element, left.eventType, _direction); + } else { + _emitEvent(element, right.eventType, _direction); + } + + skrollable.lastFrameIndex = keyFrameIndex; + } + } + + break; + } + } + } + }; + + /** + * Renders all elements. + */ + var _render = function() { + if(_requestReflow) { + _requestReflow = false; + _reflow(); + } + + //We may render something else than the actual scrollbar position. + var renderTop = _instance.getScrollTop(); + + //If there's an animation, which ends in current render call, call the callback after rendering. + var afterAnimationCallback; + var now = _now(); + var progress; + + //Before actually rendering handle the scroll animation, if any. + if(_scrollAnimation) { + //It's over + if(now >= _scrollAnimation.endTime) { + renderTop = _scrollAnimation.targetTop; + afterAnimationCallback = _scrollAnimation.done; + _scrollAnimation = undefined; + } else { + //Map the current progress to the new progress using given easing function. + progress = _scrollAnimation.easing((now - _scrollAnimation.startTime) / _scrollAnimation.duration); + + renderTop = (_scrollAnimation.startTop + progress * _scrollAnimation.topDiff) | 0; + } + + _instance.setScrollTop(renderTop, true); + } + //Smooth scrolling only if there's no animation running and if we're not forcing the rendering. + else if(!_forceRender) { + var smoothScrollingDiff = _smoothScrolling.targetTop - renderTop; + + //The user scrolled, start new smooth scrolling. + if(smoothScrollingDiff) { + _smoothScrolling = { + startTop: _lastTop, + topDiff: renderTop - _lastTop, + targetTop: renderTop, + startTime: _lastRenderCall, + endTime: _lastRenderCall + _smoothScrollingDuration + }; + } + + //Interpolate the internal scroll position (not the actual scrollbar). + if(now <= _smoothScrolling.endTime) { + //Map the current progress to the new progress using easing function. + progress = easings.sqrt((now - _smoothScrolling.startTime) / _smoothScrollingDuration); + + renderTop = (_smoothScrolling.startTop + progress * _smoothScrolling.topDiff) | 0; + } + } + + //That's were we actually "scroll" on mobile. + if(_isMobile && _skrollrBody) { + //Set the transform ("scroll it"). + skrollr.setStyle(_skrollrBody, 'transform', 'translate(0, ' + -(_mobileOffset) + 'px) ' + _translateZ); + } + + //Did the scroll position even change? + if(_forceRender || _lastTop !== renderTop) { + //Remember in which direction are we scrolling? + _direction = (renderTop > _lastTop) ? 'down' : (renderTop < _lastTop ? 'up' : _direction); + + _forceRender = false; + + var listenerParams = { + curTop: renderTop, + lastTop: _lastTop, + maxTop: _maxKeyFrame, + direction: _direction + }; + + //Tell the listener we are about to render. + var continueRendering = _listeners.beforerender && _listeners.beforerender.call(_instance, listenerParams); + + //The beforerender listener function is able the cancel rendering. + if(continueRendering !== false) { + //Now actually interpolate all the styles. + _calcSteps(renderTop, _instance.getScrollTop()); + + //Remember when we last rendered. + _lastTop = renderTop; + + if(_listeners.render) { + _listeners.render.call(_instance, listenerParams); + } + } + + if(afterAnimationCallback) { + afterAnimationCallback.call(_instance, false); + } + } + + _lastRenderCall = now; + }; + + /** + * Parses the properties for each key frame of the given skrollable. + */ + var _parseProps = function(skrollable) { + //Iterate over all key frames + var keyFrameIndex = 0; + var keyFramesLength = skrollable.keyFrames.length; + + for(; keyFrameIndex < keyFramesLength; keyFrameIndex++) { + var frame = skrollable.keyFrames[keyFrameIndex]; + var easing; + var value; + var prop; + var props = {}; + + var match; + + while((match = rxPropValue.exec(frame.props)) !== null) { + prop = match[1]; + value = match[2]; + + easing = prop.match(rxPropEasing); + + //Is there an easing specified for this prop? + if(easing !== null) { + prop = easing[1]; + easing = easing[2]; + } else { + easing = DEFAULT_EASING; + } + + //Exclamation point at first position forces the value to be taken literal. + value = value.indexOf('!') ? _parseProp(value) : [value.slice(1)]; + + //Save the prop for this key frame with his value and easing function + props[prop] = { + value: value, + easing: easings[easing] + }; + } + + frame.props = props; + } + }; + + /** + * Parses a value extracting numeric values and generating a format string + * for later interpolation of the new values in old string. + * + * @param val The CSS value to be parsed. + * @return Something like ["rgba(?%,?%, ?%,?)", 100, 50, 0, .7] + * where the first element is the format string later used + * and all following elements are the numeric value. + */ + var _parseProp = function(val) { + var numbers = []; + + //One special case, where floats don't work. + //We replace all occurences of rgba colors + //which don't use percentage notation with the percentage notation. + rxRGBAIntegerColor.lastIndex = 0; + val = val.replace(rxRGBAIntegerColor, function(rgba) { + return rgba.replace(rxNumericValue, function(n) { + return n / 255 * 100 + '%'; + }); + }); + + //Handle prefixing of "gradient" values. + //For now only the prefixed value will be set. Unprefixed isn't supported anyway. + if(theDashedCSSPrefix) { + rxGradient.lastIndex = 0; + val = val.replace(rxGradient, function(s) { + return theDashedCSSPrefix + s; + }); + } + + //Now parse ANY number inside this string and create a format string. + val = val.replace(rxNumericValue, function(n) { + numbers.push(+n); + return '{?}'; + }); + + //Add the formatstring as first value. + numbers.unshift(val); + + return numbers; + }; + + /** + * Fills the key frames with missing left and right hand properties. + * If key frame 1 has property X and key frame 2 is missing X, + * but key frame 3 has X again, then we need to assign X to key frame 2 too. + * + * @param sk A skrollable. + */ + var _fillProps = function(sk) { + //Will collect the properties key frame by key frame + var propList = {}; + var keyFrameIndex; + var keyFramesLength; + + //Iterate over all key frames from left to right + keyFrameIndex = 0; + keyFramesLength = sk.keyFrames.length; + + for(; keyFrameIndex < keyFramesLength; keyFrameIndex++) { + _fillPropForFrame(sk.keyFrames[keyFrameIndex], propList); + } + + //Now do the same from right to fill the last gaps + + propList = {}; + + //Iterate over all key frames from right to left + keyFrameIndex = sk.keyFrames.length - 1; + + for(; keyFrameIndex >= 0; keyFrameIndex--) { + _fillPropForFrame(sk.keyFrames[keyFrameIndex], propList); + } + }; + + var _fillPropForFrame = function(frame, propList) { + var key; + + //For each key frame iterate over all right hand properties and assign them, + //but only if the current key frame doesn't have the property by itself + for(key in propList) { + //The current frame misses this property, so assign it. + if(!hasProp.call(frame.props, key)) { + frame.props[key] = propList[key]; + } + } + + //Iterate over all props of the current frame and collect them + for(key in frame.props) { + propList[key] = frame.props[key]; + } + }; + + /** + * Calculates the new values for two given values array. + */ + var _calcInterpolation = function(val1, val2, progress) { + var valueIndex; + var val1Length = val1.length; + + //They both need to have the same length + if(val1Length !== val2.length) { + throw 'Can\'t interpolate between "' + val1[0] + '" and "' + val2[0] + '"'; + } + + //Add the format string as first element. + var interpolated = [val1[0]]; + + valueIndex = 1; + + for(; valueIndex < val1Length; valueIndex++) { + //That's the line where the two numbers are actually interpolated. + interpolated[valueIndex] = val1[valueIndex] + ((val2[valueIndex] - val1[valueIndex]) * progress); + } + + return interpolated; + }; + + /** + * Interpolates the numeric values into the format string. + */ + var _interpolateString = function(val) { + var valueIndex = 1; + + rxInterpolateString.lastIndex = 0; + + return val[0].replace(rxInterpolateString, function() { + return val[valueIndex++]; + }); + }; + + /** + * Resets the class and style attribute to what it was before skrollr manipulated the element. + * Also remembers the values it had before reseting, in order to undo the reset. + */ + var _reset = function(elements, undo) { + //We accept a single element or an array of elements. + elements = [].concat(elements); + + var skrollable; + var element; + var elementsIndex = 0; + var elementsLength = elements.length; + + for(; elementsIndex < elementsLength; elementsIndex++) { + element = elements[elementsIndex]; + skrollable = _skrollables[element[SKROLLABLE_ID_DOM_PROPERTY]]; + + //Couldn't find the skrollable for this DOM element. + if(!skrollable) { + continue; + } + + if(undo) { + //Reset class and style to the "dirty" (set by skrollr) values. + element.style.cssText = skrollable.dirtyStyleAttr; + _updateClass(element, skrollable.dirtyClassAttr); + } else { + //Remember the "dirty" (set by skrollr) class and style. + skrollable.dirtyStyleAttr = element.style.cssText; + skrollable.dirtyClassAttr = _getClass(element); + + //Reset class and style to what it originally was. + element.style.cssText = skrollable.styleAttr; + _updateClass(element, skrollable.classAttr); + } + } + }; + + /** + * Detects support for 3d transforms by applying it to the skrollr-body. + */ + var _detect3DTransforms = function() { + _translateZ = 'translateZ(0)'; + skrollr.setStyle(_skrollrBody, 'transform', _translateZ); + + var computedStyle = getStyle(_skrollrBody); + var computedTransform = computedStyle.getPropertyValue('transform'); + var computedTransformWithPrefix = computedStyle.getPropertyValue(theDashedCSSPrefix + 'transform'); + var has3D = (computedTransform && computedTransform !== 'none') || (computedTransformWithPrefix && computedTransformWithPrefix !== 'none'); + + if(!has3D) { + _translateZ = ''; + } + }; + + /** + * Set the CSS property on the given element. Sets prefixed properties as well. + */ + skrollr.setStyle = function(el, prop, val) { + var style = el.style; + + //Camel case. + prop = prop.replace(rxCamelCase, rxCamelCaseFn).replace('-', ''); + + //Make sure z-index gets a . + //This is the only case we need to handle. + if(prop === 'zIndex') { + if(isNaN(val)) { + //If it's not a number, don't touch it. + //It could for example be "auto" (#351). + style[prop] = val; + } else { + //Floor the number. + style[prop] = '' + (val | 0); + } + } + //#64: "float" can't be set across browsers. Needs to use "cssFloat" for all except IE. + else if(prop === 'float') { + style.styleFloat = style.cssFloat = val; + } + else { + //Need try-catch for old IE. + try { + //Set prefixed property if there's a prefix. + if(theCSSPrefix) { + style[theCSSPrefix + prop.slice(0,1).toUpperCase() + prop.slice(1)] = val; + } + + //Set unprefixed. + style[prop] = val; + } catch(ignore) {} + } + }; + + /** + * Cross browser event handling. + */ + var _addEvent = skrollr.addEvent = function(element, names, callback) { + var intermediate = function(e) { + //Normalize IE event stuff. + e = e || window.event; + + if(!e.target) { + e.target = e.srcElement; + } + + if(!e.preventDefault) { + e.preventDefault = function() { + e.returnValue = false; + e.defaultPrevented = true; + }; + } + + return callback.call(this, e); + }; + + names = names.split(' '); + + var name; + var nameCounter = 0; + var namesLength = names.length; + + for(; nameCounter < namesLength; nameCounter++) { + name = names[nameCounter]; + + if(element.addEventListener) { + element.addEventListener(name, callback, false); + } else { + element.attachEvent('on' + name, intermediate); + } + + //Remember the events to be able to flush them later. + _registeredEvents.push({ + element: element, + name: name, + listener: callback + }); + } + }; + + var _removeEvent = skrollr.removeEvent = function(element, names, callback) { + names = names.split(' '); + + var nameCounter = 0; + var namesLength = names.length; + + for(; nameCounter < namesLength; nameCounter++) { + if(element.removeEventListener) { + element.removeEventListener(names[nameCounter], callback, false); + } else { + element.detachEvent('on' + names[nameCounter], callback); + } + } + }; + + var _removeAllEvents = function() { + var eventData; + var eventCounter = 0; + var eventsLength = _registeredEvents.length; + + for(; eventCounter < eventsLength; eventCounter++) { + eventData = _registeredEvents[eventCounter]; + + _removeEvent(eventData.element, eventData.name, eventData.listener); + } + + _registeredEvents = []; + }; + + var _emitEvent = function(element, name, direction) { + if(_listeners.keyframe) { + _listeners.keyframe.call(_instance, element, name, direction); + } + }; + + var _reflow = function() { + var pos = _instance.getScrollTop(); + + //Will be recalculated by _updateDependentKeyFrames. + _maxKeyFrame = 0; + + if(_forceHeight && !_isMobile) { + //un-"force" the height to not mess with the calculations in _updateDependentKeyFrames (#216). + body.style.height = ''; + } + + _updateDependentKeyFrames(); + + if(_forceHeight && !_isMobile) { + //"force" the height. + body.style.height = (_maxKeyFrame + documentElement.clientHeight) + 'px'; + } + + //The scroll offset may now be larger than needed (on desktop the browser/os prevents scrolling farther than the bottom). + if(_isMobile) { + _instance.setScrollTop(Math.min(_instance.getScrollTop(), _maxKeyFrame)); + } else { + //Remember and reset the scroll pos (#217). + _instance.setScrollTop(pos, true); + } + + _forceRender = true; + }; + + /* + * Returns a copy of the constants object where all functions and strings have been evaluated. + */ + var _processConstants = function() { + var viewportHeight = documentElement.clientHeight; + var copy = {}; + var prop; + var value; + + for(prop in _constants) { + value = _constants[prop]; + + if(typeof value === 'function') { + value = value.call(_instance); + } + //Percentage offset. + else if((/p$/).test(value)) { + value = (value.slice(0, -1) / 100) * viewportHeight; + } + + copy[prop] = value; + } + + return copy; + }; + + /* + * Returns the height of the document. + */ + var _getDocumentHeight = function() { + var skrollrBodyHeight = (_skrollrBody && _skrollrBody.offsetHeight || 0); + var bodyHeight = Math.max(skrollrBodyHeight, body.scrollHeight, body.offsetHeight, documentElement.scrollHeight, documentElement.offsetHeight, documentElement.clientHeight); + + return bodyHeight - documentElement.clientHeight; + }; + + /** + * Returns a string of space separated classnames for the current element. + * Works with SVG as well. + */ + var _getClass = function(element) { + var prop = 'className'; + + //SVG support by using className.baseVal instead of just className. + if(window.SVGElement && element instanceof window.SVGElement) { + element = element[prop]; + prop = 'baseVal'; + } + + return element[prop]; + }; + + /** + * Adds and removes a CSS classes. + * Works with SVG as well. + * add and remove are arrays of strings, + * or if remove is ommited add is a string and overwrites all classes. + */ + var _updateClass = function(element, add, remove) { + var prop = 'className'; + + //SVG support by using className.baseVal instead of just className. + if(window.SVGElement && element instanceof window.SVGElement) { + element = element[prop]; + prop = 'baseVal'; + } + + //When remove is ommited, we want to overwrite/set the classes. + if(remove === undefined) { + element[prop] = add; + return; + } + + //Cache current classes. We will work on a string before passing back to DOM. + var val = element[prop]; + + //All classes to be removed. + var classRemoveIndex = 0; + var removeLength = remove.length; + + for(; classRemoveIndex < removeLength; classRemoveIndex++) { + val = _untrim(val).replace(_untrim(remove[classRemoveIndex]), ' '); + } + + val = _trim(val); + + //All classes to be added. + var classAddIndex = 0; + var addLength = add.length; + + for(; classAddIndex < addLength; classAddIndex++) { + //Only add if el not already has class. + if(_untrim(val).indexOf(_untrim(add[classAddIndex])) === -1) { + val += ' ' + add[classAddIndex]; + } + } + + element[prop] = _trim(val); + }; + + var _trim = function(a) { + return a.replace(rxTrim, ''); + }; + + /** + * Adds a space before and after the string. + */ + var _untrim = function(a) { + return ' ' + a + ' '; + }; + + var _now = Date.now || function() { + return +new Date(); + }; + + var _keyFrameComparator = function(a, b) { + return a.frame - b.frame; + }; + + /* + * Private variables. + */ + + //Singleton + var _instance; + + /* + A list of all elements which should be animated associated with their the metadata. + Exmaple skrollable with two key frames animating from 100px width to 20px: + + skrollable = { + element: , + styleAttr: + + + + diff --git a/templates/college/attack-on-blocks.html b/templates/college/attack-on-blocks.html new file mode 100644 index 0000000..e7af62b --- /dev/null +++ b/templates/college/attack-on-blocks.html @@ -0,0 +1,60 @@ +{% extends 'base.html' %} +{% load staticfiles %} +{% block htmltitle %}Attack on Blocks{% endblock %} + +{% block headercontent %} +
+

Attack on Blocks

+
+
+

It's like the classic game 'Space Invaders', just with better music, customizable texture packs, and a couple of easter eggs...

+
+{% endblock %} + +{% block content%} +
+

+ Attack on Blocks is a space invaders style game that I wrote for my IT coursework, for the games development unit. We were allowed to make any game that we wanted, provided it could be done within the time limits, be very easy to play, and easily run on the college computer (which were pretty terrible). I had never written a game before, so I knew this was going to be a challenge.

+

+ I decided to write the game in Python, seeing as there were other people in the class that could help me bug report and test features, and because it was easy to run on the college computers. I used PyGame for the graphics library, even though I had never used it before, because it was really simple to use, and there was a lot of support and documentation online. +

+
+
+

The Easter Eggs

+

+ One of the key features of this take on space invaders (and unfortunately the thing I spent the most time on), is the easter eggs. There are a few dotted around the game, which make the game either much easier, or way more fun! At the moment, there are 3 main easter eggs, the first enabling the other 2. If you would like to know what they are, click the button below. If not, pay the game and try and find them, or search through the source to find them (it's not too hard through the source). +

+
+
+ +
+
+

+

+

+

+

+

+
+
+
+
+

+ As you will see (If and when you find the easter eggs), most of them are completely useless, and completely unrelated to the game or anything else. The main reason they were put in was because i'm friends with people that pester to the point it's just easier to give in. Hence there are some really very odd ones! +

+
+ +{% endblock %} diff --git a/templates/college/student-server.html b/templates/college/student-server.html new file mode 100644 index 0000000..31a04ac --- /dev/null +++ b/templates/college/student-server.html @@ -0,0 +1,40 @@ +{% extends 'base.html' %} +{% load staticfiles %} +{% block htmltitle %}Student Server | College{% endblock %} + +{% block headercontent %} +
+

Student Server

+
+
+

The first server I ever properly ran, and surprisingly it didn't really break, that much!

+
+{% endblock %} + +{% block content%} +
+

+ Back when I was in college, we needed a server for computing students to learn how to FTP, and script on a server using python CGI and PHP, as well as possibly for some students coursework. Fortunately, the college already had one, running the IT students microsite for extra course information. The problem was that it was majorly out of date, and no one really new how to use it properly. It was up to me and my friend Alex to bring the server up to date, and make it ready for the students who needed it. +

+
+
+

+ The original plan was to update the server's OS (at that stage running Ubuntu 12.04 LTS), install the python and PHP backends, add the users, and then make sure they couldn't edit eachothers documents. In the end Alex did a server backup, and then fully reinstalled the server OS from scratch, and then pushed the documents back on, which made our lives a lot easier. +

+

+ Because he had worked with servers a lot in the past, and was already very fluent with the ubuntu terminal, he installed the software that was needed, and got it all configured properly, whilst I worked on the student logins and permission structure. +

+
+
+

User Creation

+

+ I knew we would need user accounts for the computing teachers, for the students doing A2 computing, and for updating the IT site, but I wasnt expecting this to amount to over 50 user accounts that needed to be created, and permissions setup for their accounts. It was then that I realied that I would need to write a script to do this in any useful amount of time. Fortunately Alex had started writing one with a small amount of logic, so I had something to work off to get this done, as this was my first major linux project. +

+

+ The basis of the script was to load information about the users from a database I had created with all the required students in, create users based on this information, and configure the permissions for their user and their home directory. The script also allowed for manual entering of users with the same permission template, in case single users needed to be created. An additional feature that I added which has proved useful now that i've left is the ability to delete users manually, and from that original database, to make sure that no student will have access to the server once they have left, well, other than me that is! +

+

+ The script I used to create these files can be found below, hosted as a gist on github. Unfortunately some of the information has been redacted to prevent giving too much information that could be considered a security threat to the server. The script may not work in this redacted state, however all the core logic has been kept. +

+
+{% endblock %} diff --git a/templates/core/404.html b/templates/core/404.html new file mode 100644 index 0000000..684bebc --- /dev/null +++ b/templates/core/404.html @@ -0,0 +1,19 @@ +{% extends 'base.html' %} + +{% block htmltitle %}Oh no, There's nothing here!{% endblock %} + +{% block headercontent %} +
+

404 - Page not found

+
+
+

The page you were looking for could not be found.

+

Don't worry, I've sent these people to to go and find it. Rest assured it will be found!

+
+{% endblock %} + +{% block content %} +
+
Nope, There's nothing down here either. Go back to where you came from!
+
+{% endblock %} \ No newline at end of file diff --git a/templates/core/no-js.html b/templates/core/no-js.html new file mode 100644 index 0000000..fc924b1 --- /dev/null +++ b/templates/core/no-js.html @@ -0,0 +1,72 @@ +{% load staticfiles %} + + + + Javascript Disabled | TheOrangeOne + + + + + + +

Javascript is disabled!

+
+

You appear to have javascript disabled. For my site to function properly, javascript must be enabled! The javascript is used to dynamically change the webpage on your device. Without them then the site will break, cause a tonne of errors, and not look right, all of which aren't very nice, for you or me.

+

The javascript on this page won't damage your computer in any way, and has been written entirely by me, or has used trusted and open-source 3rd-party libraries. You can trust this site! Re-enabling the javascript functions inside your browser is very easy, and helpful tutorials can be found below!

+
+
+

Re-enabling Javascript in your browser

+
+
+

Re-enabling in Firefox

+
+
+
    +
  1. Navigate to 'about:config', using the browsers address bar.
  2. +
  3. Accept the security warning, and notice that no dragons lie ahead.
  4. +
  5. In the search bar, enter 'javascript'
  6. +
  7. Find the entry 'Javascript.enabled' (possibly using ctrl + F)
  8. +
  9. Toggle the entry by either double-clicking the entry, or right-clicking and selecting 'Toggle'.
  10. +
+
+
+ +
+
+

Re-enabling in Chrome

+
+
+
    +
  1. Click the Chrome menu icon () in the top right hand corder of the window.
  2. +
  3. Click 'Settings'.
  4. +
  5. Under the 'Privacy' section, select 'Content settings'.
  6. +
  7. Under 'Javascript', select 'Allow all sites to run Javascript(Recommended)'.
  8. +
+
+
+ +
+
+

Re-enabling in Internet Explorer

+
+ +
+
    +
  1. Select the gear icon in the top right hand corner , and click 'Internet Options'.
  2. +
  3. Select Security > Internet > Custom Level.
  4. +
  5. Scroll down until you find 'Scripting', and select 'Enable'
  6. +
+

+ Also: If you are using Internet Explorer (Or Microsoft Edge for that matter), Stop! Use one of the other ones. They're much faster, and better, especially Firefox! +

+
+
+
+
+

+ After enabling javascript, the page should automatically redirect to the homepage. If it doesn't, just refresh the page, or Click here. +

+
+ + + \ No newline at end of file diff --git a/templates/index.html b/templates/index.html new file mode 100644 index 0000000..9763d0d --- /dev/null +++ b/templates/index.html @@ -0,0 +1,40 @@ +{% extends 'base.html' %} + +{% block htmltitle %}Homepage{% endblock %} + +{% block headercontent %} +
+

Welcome Internet!

+
+
+

Welcome to the place where my knowledge lies - Or at least most of it.

+
+{% endblock %} + +{% block content%} +
+

Welcome people, to my little slice of the internet. I'm Jake, an apprentice software engineer at Dabapps, and this is my website. Somewhere I can put all my work, projects, media, and anything else I can think of. I've done a lot of things in the past, such as Student Robotics, Custom built PC's and College Projects. All of this and more ca be found here. Take a look around, chances are you'll see some very cool things!

+
+
+
+ +
+

+ +
+
+
+
+ +
+{% endblock %} diff --git a/templates/markdown.html b/templates/markdown.html new file mode 100644 index 0000000..53df00a --- /dev/null +++ b/templates/markdown.html @@ -0,0 +1,18 @@ +{% extends 'base.html' %} + +{% block htmltitle %}{% titleMD %}{% endblock %}{% endblock %} + +{% block headercontent %} + {% block headerMD %}{% endblock %} +{% endblock %} + +{% block content%} +
+
+        {% autoescape off %}
+            {% block contentMD %}{% endblock %}
+        {% endautoescape %}
+    
+ + +{% endblock %} diff --git a/templates/media/gallery.html b/templates/media/gallery.html new file mode 100644 index 0000000..7b8d667 --- /dev/null +++ b/templates/media/gallery.html @@ -0,0 +1,28 @@ +{% extends 'base.html' %} + +{% block htmltitle %}Gallery{% endblock %} + +{% block headercontent %} +
+

My Gallery

+
+
+

I tend to take a lot of photos, and need somewhere to put them!

+
+{% endblock %} + +{% block content%} +
+

Naturally, I take a lot of photos, sometimes of really cool things! Unfortunately, due to the amount of storage on my server, and the fact that the connection bandwidth isnt great, I cannot store the images on this server, so I have decided to use Flickr.

+ +

Flickr is a great service for storing images, allowing over 1TB of storage for free, and great album creation, as well as really easy hotlinks, so I can use the images at different sizes on my website, and therefore use their CDN, taking the strain of my server!

+ +

Click the button below to visit my flickr page, and see all the albums I have on there. Also, all my instagram photos are uploaded there too!

+
+ +
+

+ +

+
+{% endblock %} diff --git a/templates/media/youtube.html b/templates/media/youtube.html new file mode 100644 index 0000000..46ae8ee --- /dev/null +++ b/templates/media/youtube.html @@ -0,0 +1,25 @@ +{% extends 'base.html' %} + +{% block htmltitle %}Gallery{% endblock %} + +{% block headercontent %} +
+

My Youtube Channel

+
+
+

Where I put all my videos to help people learn things, or solve a problem.

+
+{% endblock %} + +{% block content%} +
+

I've been making Youtube videos now for the past couple of years, originally on my old channel that I worked on with my mate, and now on this one that's just me. The primary reason I make my videos is to help people out, and hopefully solve a problem for them, as I do exactly the same when I need help with a problem

+

With this channel, I want to kep the content educational and to a very high quality, therfore making it easier for people to find exactly what they need to know from my videos. I've built up a lot of knowledge in the field of technology and computing, and I believe I have a responsibiluty to share this so that others can learn too! +

+ + +{% endblock %} diff --git a/templates/projects/BSOD-Enabler.html b/templates/projects/BSOD-Enabler.html new file mode 100644 index 0000000..1ff4f81 --- /dev/null +++ b/templates/projects/BSOD-Enabler.html @@ -0,0 +1,35 @@ +{% extends 'base.html' %} +{% load staticfiles %} +{% block htmltitle %}BSOD Enabler{% endblock %} + +{% block headercontent %} +
+

BSOD Enabler

+
+
+

Ever wanted to call a Blue Screen of Death on demand? Well now you can!

+
+{% endblock %} + +{% block content%} +
+

For those that use windows, the famous Blue Screen of Death is an annoyance that plagues computers, causing error, frustration, and even loss of work. Whilst trying to configute Nvidia surround on my main rig and it keep blue screening, I thought to myself This is really annoying, I wonder who else I can annoy with a BSOD

+

And thus the BSOD_Enabler was born!

+

After researching into it for a while, it turns out that there are a few different ways to cause a BSOD, unfortunately most of which are by doing things that are meant to cause a BSOD, and can therefore be dangereous to a computer, something I didn't really want. Then I stumbled upon this article, which shows that you can infact envoke a BSOD through completely natural methods, which won't cause damage to a computer. Now to write a program that toggles it.

+
+
+
+
+
+

Obviously there are many differnet ways, and probably far better ways of doing this, but I wanted something that was simple to use, fast, and could be done by anyone, no matter how technically illiterate. So I decided to write it in C#, and use a windows console interface.

+

Below you can find a download link to the application, as well as a link to the source code. I am hoping to upgrade the project in the future to allow for automated triggering, a much faster UI, with options, as well as a simple one-click setup and BSOD.

+
+ +{% endblock %} diff --git a/templates/projects/all_projects.html b/templates/projects/all_projects.html new file mode 100644 index 0000000..a0623c9 --- /dev/null +++ b/templates/projects/all_projects.html @@ -0,0 +1,78 @@ +{% extends 'base.html' %} + +{% block htmltitle %}All Projects{% endblock %} + +{% block headercontent %} +
+

All Projects

+
+
+

The list of (almost) Everything Ive ever done in my life!

+
+{% endblock %} + +{% block content%} +
+

+ Here you can find all the projects I have made that I can publicly share, Some incredibly useful, others less so! Feel free to share any of the content with anyone you know, or feature it wherever you like! However, please reference me as the creator, and use my dedicated download link, so that no illegitimate or malicious copies are made. +

+
+ +
+
+
+

Blue Screen of Death Enabler

+

+ Blue screens of death are an occurance that has plagued windows users for years, when something goes wrong on their computer and windows just goes "Nope, screw this, i'm done, bye". BSOD Enabler is a simple tool that enables the ability to call a Blue Screen on request, whether it's for testing purposes, or just to annoy someone, which trust me it really does! +

+

+ More Information... +

+
+
+
+
+
+ + + +
+
+
+

Hipchat Emoticons for All

+

+ Hipchat emoticons for all is a browser extension that allows the use of hipchat emoticons, by far the best set, on many other websites, including GitHub and Trello. Because life without emoticons is just boring, and some sites have just limited or terrible ones naturally. +

+

+ More Information... +

+
+
+
+
+
+ +
+

Github and Bitbucket?!

+

+ There is a reason that I have both a github and bitbucket account, whilst originally it was becuse different people used different services, now it just seems stupid to keep both and not really have a need for both! Now, after a eurika moment, I have a use for both services! +

+

+ I'm going to use github for production projects, ones that are out and released properly, as it allows for much better contributions. Bitbucket on the other hand will be used for small development projects, and making , as it allows for unlimited private repositories. +

+
+{% endblock %} diff --git a/templates/projects/hipchat-emoticons-for-all.html b/templates/projects/hipchat-emoticons-for-all.html new file mode 100644 index 0000000..6d90ab0 --- /dev/null +++ b/templates/projects/hipchat-emoticons-for-all.html @@ -0,0 +1,33 @@ +{% extends 'base.html' %} +{% load staticfiles %} +{% block htmltitle %}Hipchat Emoticons for All{% endblock %} + +{% block headercontent %} +
+

Hipchat Emoticons for all!

+
+
+

Bombard both your and others lives in emoticons - No website is safe!

+
+{% endblock %} + +{% block content%} +
+

After starting my new job at Dabapps, I was introduced to the world of Hipchat, and it's wonderful array of emoticons, as well as the ones added. It was wonderful, it made communicating with friends and colleagues much more interesting!

+

Unfortunately, the emoticons on the other services we use, like Github, were terrible in comparison. So it was after a discussion with @JakeSidSmith about him just using things like (facepalm), (notsureif), and (wat) in Facebook messenger, and hoping people understand what it means, that I decided to make 'Hipchat Emoticons for all', so people like him could use a much better set of emoticons.

+
+
+

The premis is very simple, whenever it sees a hpichat emoticon code, like (notsureif), it replaces it with an emoticon. If only writing the code could have been this simple! I started writing the plugin in firefox, using Jetpack, which uses Javascript. The initial stages of the code were very simple, but I encountered problems making sure that anything loaded after the page was loaded (such as a facebook message), be changed too.

+

Fortunately after many hours of testing, and changing the code, I finally got everything working perfectly, and in a way that made adding new sites incredibly easy! The code isnt the greatest in terms of performance, and there are some things that could have obviously been done better, but this was all done to help with a shared codebase between Chrome and Firefox, which don't play nice when it comes to extensions.

+

Currently the application is in very beta stages right now, only having tested partial support for github, but the code is all available on GitHub, if people have their own suggestions of improvements.

+
+ +{% endblock %} diff --git a/templates/projects/morse-code-decoder.html b/templates/projects/morse-code-decoder.html new file mode 100644 index 0000000..776f281 --- /dev/null +++ b/templates/projects/morse-code-decoder.html @@ -0,0 +1,44 @@ +{% extends 'base.html' %} +{% load staticfiles %} +{% block htmltitle %}Wiki Game Solver{% endblock %} + +{% block headercontent %} +
+

Wiki Game Solver

+
+
+

Sometimes writing a cheat for a game is more fun than the game itself!

+
+{% endblock %} + +{% block content%} +
+

+ It's not often someone will need to decode text into morse code (and visa-versa), but if I had something like this when I needed it, it would have saved me hours of time! I originally wrote this code for the Student Robotics 2015 Entry, to convert a string message into a morse code message that would be transmitted using LEDs, for aestetics and debugging. Unfortunately due to a fixed time frame, this idea was scraped before it could be fully implemented. Fortunately the decoder worked perfectly! +

+

+ Originally I had written the code in python, but to make it much more useful for people, I've removed the decoding part of the code, and converted the information into JSON, so it can be used in a variety of different languages easily. +

+
+
+
+
+

Using the library

+

+ Because the format is JSON, the library can be used in a wide variety of different languages. Just make sure to look up how to read JSON files in your chosen language, and it should work perfectly! +

+
+
+

View Source

+

+ The source of the library is on github as a gist. I recommend downloading the file to use yourself, however for testing you can use githubs raw view as a hotlink. +

+

+ + View on Github + +

+
+
+
+{% endblock %} diff --git a/templates/projects/wiki-game-solver.html b/templates/projects/wiki-game-solver.html new file mode 100644 index 0000000..1cf49b0 --- /dev/null +++ b/templates/projects/wiki-game-solver.html @@ -0,0 +1,54 @@ +{% extends 'base.html' %} +{% load staticfiles %} +{% block htmltitle %}Wiki Game Solver{% endblock %} + +{% block headercontent %} +
+

Wiki Game Solver

+
+
+

Sometimes writing a cheat for a game is more fun than the game itself!

+
+{% endblock %} + +{% block content%} +
+

+ For those who dont know what the Wiki Game is: The Wiki Game is an online game where you attempt to navigate through wikipedia from a start page to a goal page using as few other pages as possible. Once i was shown the Wiki Game by my friend, and after i realised that I really wasn't very good at it, I looked into how the system worked, and how I could beat it. +

+

+ I couldnt see how the back end worked, but after playing a few games and checking what happened on the page,the way that the game was won was when the iframe was at the final page location, or at least a clone of it on their servers with extra querystring information. +

+

+ With this information, I started to write some javascript that would change the location of the iframe to the goal. Fortunately for me, there was already a link to the real winning page, so I could use that to construct the final URL, and direct the iframe to it, meaning I was able to win the game in 1 turn. +

+
+
+
+
+

Using the code

+

In the interest of ease of use, it's very simple to use. Just use the code, and let it do all the work.

+
    +
  1. Start a new game on Wiki Game, DON'T press start!
  2. +
  3. Open your browser's developers console. This will vary from browser.
  4. +
  5. Open the javascript console
  6. +
  7. Paste the compact version of the code there, and execute it (press enter)
  8. +
  9. Congratulations, you just won!
  10. +
+

+ If you want to win more games, just repaste the code, it works as often as you like! +

+
+
+

View Source

+ +
+
+
+
+

Disclamier

+

+ As I experienced whilst developing this, the people that play Wiki Game don't tend to like people cheating. There were a lot of people getting very annoyed when I was testing it. So please use this at your own risk! At the moment I don't think there is any kind of banning system, but be warned! +

+
+{% endblock %} diff --git a/templates/robotics/2014-index.html b/templates/robotics/2014-index.html new file mode 100644 index 0000000..d55c3ac --- /dev/null +++ b/templates/robotics/2014-index.html @@ -0,0 +1,50 @@ +{% extends 'base.html' %} + +{% block htmltitle %}Student Robotics 2014{% endblock %} + +{% block headercontent %} +
+

Student Robotics 2014

+
+
+

The home of Lucy. (No it doesnt stand for anything)

+
+{% endblock %} + +{% block content%} +
+
+
+ +
+
+

Welcome to the homepage of Collyer's Student Robotics Team 2015 (The 'A' Team) - Creators of 'A.L.I.C.E'! Here you can see everything that goes on throughout the competition.

+

This website is here to bridge the gap between the team and the outside world, and also to document everything that we do so that parents and Blue Shirts can see what we have been getting up to! Not only is the website for this, but it's also in the competition too! There is a prize (Awarded at the competition itself), for the best online presence. Hopefully no one else that has entered has their own domain name with dedicated section for the competition!

+
+
+
+ +
+

The Competition

+

As was announced at kickstart, the game for this year is a take on the classic gamemode Capture the Flag. 4 teams compete over 5 flags to move as many of them as they can into their scoring zones. The person with the most flags in their scoring zone wins.

+

The flags are 25cm cubes of wood on caster wheels weighing roughly 2kg. The rules prevent us from lifting them, so the idea is to drag them around!

+

To see a copy of the rules from the competition, Click Here!

+
+ +
+
+
+ +
+

Why call it 'ALICE'?

+

The decision to name the robot 'Alice' was a decision made by the whole group. Thats a lie, Ben suggested it and as no one had any better ideas and because he gets overruling vote on this for some reason , it stuck.

+

After this decisions was made, I decided to set out to find the cheesiest acronym we could for 'Alice', to make the name slightly more interesting, and not some random girls name pulled from thin air on the bus ride home from kickstart. There were a few rather good ideas, most of them coming from Sam:

+

    +
  • 'Automated Laser-cut Interactive Capturing Entity'
  • +
  • 'Abnormally Lame and Innacurate Control-less Engine'
  • +
  • 'Anti-Losing Immaculate Competitive Extravaganza'
  • +

+

One idea was also suggested that we name the robot after the first sponsor we got, but as we didnt get one util after the team split, the other team took that name instead. In the end we decided to go with one that Sam initially suggested, so 'Alice' officially stands for:

+

'Autonomous Logistics and Inevitable Collision Engine'

+
+{% endblock %} diff --git a/templates/robotics/2015-index.html b/templates/robotics/2015-index.html new file mode 100644 index 0000000..fcefda5 --- /dev/null +++ b/templates/robotics/2015-index.html @@ -0,0 +1,50 @@ +{% extends 'base.html' %} + +{% block htmltitle %}Student Robotics 2015{% endblock %} + +{% block headercontent %} +
+

Student Robotics 2015

+
+
+

The home of the Autonomous Logistics and Inevitable Collision Engine!

+
+{% endblock %} + +{% block content%} +
+
+
+ +
+
+

Welcome to the homepage of Collyer's Student Robotics Team 2015 (The 'A' Team) - Creators of 'A.L.I.C.E'! Here you can see everything that goes on throughout the competition.

+

This website is here to bridge the gap between the team and the outside world, and also to document everything that we do so that parents and Blue Shirts can see what we have been getting up to! Not only is the website for this, but it's also in the competition too! There is a prize (Awarded at the competition itself), for the best online presence. Hopefully no one else that has entered has their own domain name with dedicated section for the competition!

+
+
+
+ +
+
+
+

The Competition

+

As was announced at kickstart, the game for this year is a take on the classic gamemode Capture the Flag. 4 teams compete over 5 flags to move as many of them as they can into their scoring zones. The person with the most flags in their scoring zone wins.

+

The flags are 25cm cubes of wood on caster wheels weighing roughly 2kg. The rules prevent us from lifting them, so the idea is to drag them around!

+

To see a copy of the rules from the competition, Click Here!

+
+ +
+
+
+
+
+{% endblock %} diff --git a/templates/robotics/2015-robot.html b/templates/robotics/2015-robot.html new file mode 100644 index 0000000..d8cd400 --- /dev/null +++ b/templates/robotics/2015-robot.html @@ -0,0 +1,54 @@ +{% extends 'base.html' %} + +{% block htmltitle %}The Robot | Student Robotics 2015{% endblock %} + +{% block headercontent %} +
+

A.L.I.C.E

+
+
+

The Autonomous Logistics and Inevitable Collision Engine!

+
+{% endblock %} + +{% block content%} +
+

+ Our entry for 2015, named 'ALICE', was a massive improvement over last years model, in both design, and the code for it. Before ALICE was built, the design team built us a very basic chassis usind scrap parts from 2014, which allowed us to write a large amount of the code base before we even had the robot built. Originally I wanted the final chassis to be built before the end of January, so we had a lot of time to test out the design for the robot and test using the final, in reality, it was closer to the middle of march before this was a reality. +

+
+
+
+
+ +
+
+
+
+

+ The entire chassis was made from custom, in-house cut, sheets of plywood, which allowed us to be very precise in the design of the robot to make sure that all the pieces would fit together properly, making the chassis less likely to break. +

+

+ The initial design was concieved by Ben, at kickstart, and was then refined over the coming weeks by me and the rest of the building team. This was made much easier from the use of the 3d model (See Right), that ben made, which helped us visualize any changes that were to be made, as well as work out strategy by seeing the measurements we had to work with. +

+

+ Once the build was completed, it was to a much higher design and quality than I could have ever imagined! It allowed us to forget about any shortcomings when it came to chassis, not having to compensate for weight distribution, or worry about the grip on the wheels. +

+
+
+
+
+ +
+

Why call it 'ALICE'?

+

The decision to name the robot 'Alice' was a decision made by the whole group. Thats a lie, Ben suggested it and as no one had any better ideas and because he gets overruling vote on this, for some reason , it stuck.

+

After this decisions was made, I decided to set out to find the cheesiest acronym we could for 'Alice', to make the name slightly more interesting, and not some random girls name pulled from thin air on the bus ride home from kickstart. There were a few rather good ideas, most of them coming from Sam:

+

    +
  • 'Automated Laser-cut Interactive Capturing Entity'
  • +
  • 'Abnormally Lame and Innacurate Control-less Engine'
  • +
  • 'Anti-Losing Immaculate Competitive Extravaganza'
  • +

+

One idea was also suggested that we name the robot after the first sponsor we got, but as we didnt get one util after the team split, the other team took that name instead. In the end we decided to go with one that Sam initially suggested, so 'Alice' officially stands for:

+

'Autonomous Logistics and Inevitable Collision Engine'

+
+{% endblock %} diff --git a/templates/robotics/index.html b/templates/robotics/index.html new file mode 100644 index 0000000..0699f90 --- /dev/null +++ b/templates/robotics/index.html @@ -0,0 +1,54 @@ +{% extends 'base.html' %} + +{% block htmltitle %}Student Robotics{% endblock %} + +{% block headercontent %} +
+

Student Robotics

+
+
+

It's like a completely autonomous, nerdy, non-contact version of Robot Wars!

+
+{% endblock %} + +{% block content%} +
+

Student Robotics is the the place where my development knowledge really started to grow. Thanks to the other people in my team teaching me. I had never done anything robotics related, and so when my computing teacher initially told us about it, I wasnt really interested. After I found out that my friend was also doing it, I signed up, and went along to the kickstart. From then on I was hooked, getting involved with all aspects of the development and design, as well as helping out other teams on the IRC room.

+
+ +
+

What is Student Robotics?

+

Student Robotics is a volunteer organisation that runs an annual robotics competition for 16-18 year olds. It was originally founded by students from the University of Southampton in 2006, and now includes volunteers (“Blue Shirts”) from multiple other universities, including the University of Bristol and Grenoble INP. It primarily takes teams and volunteers from the UK, but also some from Germany and France.

+

To find out more, visit their website Here.

+
+ +
+

Being at college for 2 years, meant I entered 2 years of competitions, SR14, and SR15. We were encouraged to gain an online presence for our team, so I created a website for both years. Unfortunately due to account inactivity, and me changing my website 3 times since, The original pages have been lost, however all the content still remains. Below you can find all the content, media and information about both competitions, as well as the source code for Lucy, the 2014 entry.

+
+ +
+
+
+
+
+
+

Student Robotics 2014

+

Robot Name: Lucy

+

More Info

+
+
+
+ +
+
+
+
+

Student Robotics 2015

+

Robot Name : A.L.I.C.E (Autonomous Logistics and Inevitable Collision Engine)

+

More Info

+
+
+
+
+
+{% endblock %} diff --git a/templates/setup/index.html b/templates/setup/index.html new file mode 100644 index 0000000..d8222c0 --- /dev/null +++ b/templates/setup/index.html @@ -0,0 +1,40 @@ +{% extends 'base.html' %} + +{% block htmltitle %}My Setup{% endblock %} + +{% block headercontent %} +
+

All the Tech!

+
+
+

Yes there is a lot, but it all serves a purpose. Or at least most of it does anyway.

+
+{% endblock %} + +{% block content%} +
+

+ My setup is much more of a project than something that is stable and constant. I am constantly upgrading, and changing things, whether it's on my main machine (which most of it is), my desk at work, or just a standard upgrade to my phone. As I use technology every day, for many, Many hours, I need it to work brilliantly, and not hold me back from anything I want to do. +

+

+ As well as my main desk, there are a few other machines I use often, namely my laptop, work rig, and my phone. All of which follow the same criteria of not holdng me back from what I wantever to do. +

+
+

Main Rig

+

My main rig is the most powerful machine I own, and have ever owned. It's a fully custom built machine, currently in it's second generation, with an upgrade coming early 2016.

+ More Information +
+
+
+
+
+
+

Portable Devices

+

Like most people my age, I'm never far from an interent connection, or at least some kind of gadget.

+ More Information +
+
+
+
+
+{% endblock %} diff --git a/templates/setup/my-rig.html b/templates/setup/my-rig.html new file mode 100644 index 0000000..239d557 --- /dev/null +++ b/templates/setup/my-rig.html @@ -0,0 +1,99 @@ +{% extends 'base.html' %} + +{% block htmltitle %}My Main Rig{% endblock %} + +{% block headercontent %} +
+

My Glorious Setup

+
+
+

Yes it's incredibly overkill for what I do, but that's just the way I like it!

+
+{% endblock %} + +{% block content%} +
+

+ My setup is everything, I use my computer at least once a day to browse the internet, work on projects, even update this website, so it's very important that everything works perfectly and the fastest it can. Obviously these machines are probably a little too powerful for someone that does almost primarily software development, but it means when I want to do things like video editing, 3D modeling, or anything else that would require a high end computer, I can, because my computer is equip to handle it. +

+

+ Over the 2 generations, my setup has evolved from a single monitor i5 workstation machine, to a triple monitor AMD FX-based monster of a computer. Parts lists for both machines, as well as my next planned build can be found below! +

+
+ +
+
+
+
+ +
+
+
+ +
+
+

+ The first generation of this was the first custom build i'd ever done. When I first built this, I wasn't really into gaming, programming, or any kind of heavy workflow for that matter, so this build isn't particullly powerful, although compared to my old Dell Inspiron 1525, it was pretty damn incredible! +

+

+ You can find the full list of parts over on the PCPartPicker link below. Unfortunately due to the lack of catalog, or bad memory on my part, not all the parts are there, but there are as many as possible. +

+

+ Visit PCPartPicker +

+
+
+
+
+ +
+
+

+ The second generation of my build was in response to an updated workflow. I'd recently started working on 3D modeling, and found the wonderful world that is PC gaming, and needed something slightly more powerful than the 1st generation. +

+

+ I've been using the current generation for around 3 years now, and it's worked perfectly for all of this. The main reason I'm upgrading is to get more features, The 990FX chipset is very old, and my motherboard is missing a few key features such as dynamic fan control. And also I want to get into surround gaming, and I need some symetrical monitors, and more graphics horsepower for that! +

+

+ Visit PCPartPicker +

+
+
+
+
+ +
+
+

+ The 3rd generation of my build isn't actually a thing yet, but I'm still actively working on what it's going to look like and the components inside it. Currently it's going to be an Intel based build, unless AMD release the reported 16-core monster, which I obviously have to have! You can find the current parts list at the link below, the list is always changing. +

+

+ The main reason this build will be different, and a complete change from the previous 2, is the OS setup. Unlike the other 2, the main OS will be Ubuntu Gnome, and to maintain support for windows-only software and games, I'm going to be using a virtual machine running windows, with a graphics card hardware pass-through to it, so I can run windows applications, and then game using steam in-home streaming from the VM to the linux machine. The best of both worlds! I got the idea for doing this from a video TekSyndicate made, which is by far the single greatest example of tech wizzardry i've ever seen. You can find the video here. +

+

+ Visit PCPartPicker +

+
+
+
+
+
+{% endblock %}