summaryrefslogtreecommitdiff
path: root/www/wiki/extensions/ModernTimeline/resources
diff options
context:
space:
mode:
authorYaco <franco@reevo.org>2020-06-04 11:01:00 -0300
committerYaco <franco@reevo.org>2020-06-04 11:01:00 -0300
commitfc7369835258467bf97eb64f184b93691f9a9fd5 (patch)
treedaabd60089d2dd76d9f5fb416b005fbe159c799d /www/wiki/extensions/ModernTimeline/resources
first commit
Diffstat (limited to 'www/wiki/extensions/ModernTimeline/resources')
-rw-r--r--www/wiki/extensions/ModernTimeline/resources/modernTimeline.css4
-rw-r--r--www/wiki/extensions/ModernTimeline/resources/modernTimeline.js26
-rw-r--r--www/wiki/extensions/ModernTimeline/resources/vendor/font.default.css9
-rw-r--r--www/wiki/extensions/ModernTimeline/resources/vendor/timeline.css27
-rw-r--r--www/wiki/extensions/ModernTimeline/resources/vendor/timeline.js14042
-rw-r--r--www/wiki/extensions/ModernTimeline/resources/vendor/tl-icons.eotbin0 -> 14644 bytes
-rw-r--r--www/wiki/extensions/ModernTimeline/resources/vendor/tl-icons.svg63
-rw-r--r--www/wiki/extensions/ModernTimeline/resources/vendor/tl-icons.ttfbin0 -> 14472 bytes
-rw-r--r--www/wiki/extensions/ModernTimeline/resources/vendor/tl-icons.woffbin0 -> 14548 bytes
9 files changed, 14171 insertions, 0 deletions
diff --git a/www/wiki/extensions/ModernTimeline/resources/modernTimeline.css b/www/wiki/extensions/ModernTimeline/resources/modernTimeline.css
new file mode 100644
index 00000000..1c28c64b
--- /dev/null
+++ b/www/wiki/extensions/ModernTimeline/resources/modernTimeline.css
@@ -0,0 +1,4 @@
+.tl-headline,
+.tl-headline-date,
+.tl-slidenav-title,
+.tl-slidenav-description { display: none; }
diff --git a/www/wiki/extensions/ModernTimeline/resources/modernTimeline.js b/www/wiki/extensions/ModernTimeline/resources/modernTimeline.js
new file mode 100644
index 00000000..45356f89
--- /dev/null
+++ b/www/wiki/extensions/ModernTimeline/resources/modernTimeline.js
@@ -0,0 +1,26 @@
+( function ( mw ) {
+
+ modernTimelineLog( 'Timeline script' );
+
+ function modernTimelineLog( message ) {
+ if( mw.config.get( 'debug' ) ) {
+ console.log( message );
+ }
+ }
+
+ mw.loader.using( [ 'ext.modern.timeline' ] ).done( function () {
+ modernTimelineLog( 'Loaded timeline JS' );
+
+ for( var timelineId in window.modernTimeline ) {
+ if( window.modernTimeline.hasOwnProperty( timelineId ) ) {
+ modernTimelineLog( 'Initializing timeline "' + timelineId + '"' );
+
+ var timelineJson = window.modernTimeline[timelineId];
+ new TL.Timeline( timelineId, timelineJson, timelineJson.options );
+
+ modernTimelineLog( 'Done initializing timeline "' + timelineId + '"' );
+ }
+ }
+ } );
+
+}( mediaWiki ) ); \ No newline at end of file
diff --git a/www/wiki/extensions/ModernTimeline/resources/vendor/font.default.css b/www/wiki/extensions/ModernTimeline/resources/vendor/font.default.css
new file mode 100644
index 00000000..0378d186
--- /dev/null
+++ b/www/wiki/extensions/ModernTimeline/resources/vendor/font.default.css
@@ -0,0 +1,9 @@
+/*
+ TimelineJS - ver. 3.6.5 - 2019-05-02
+ Copyright (c) 2012-2016 Northwestern University
+ a project of the Northwestern University Knight Lab, originally created by Zach Wise
+ https://github.com/NUKnightLab/TimelineJS3
+ This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
+ If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
+*/
+@import url(//fonts.googleapis.com/css?family=PT+Sans|PT+Sans+Narrow:700|PT+Serif:400,400italic);.tl-timeline{font-family:'PT Serif',serif;font-weight:400;font-size:16px;line-height:1.3em}.tl-timeline h1,.tl-timeline h2,.tl-timeline h3,.tl-timeline h4,.tl-timeline h5,.tl-timeline h6{font-family:'PT Sans Narrow',sans-serif;text-transform:uppercase;font-weight:700;font-size:46px;line-height:46px}.tl-timeline h2.tl-headline-title{font-size:58px;line-height:58px}.tl-timeline p{font-family:'PT Serif',serif;font-size:16px;line-height:1.3em}.tl-timeline ul{font-family:'PT Serif',serif}.tl-timeline .tl-media .tl-media-wikipedia h4 a,.tl-timeline .tl-media h4,.tl-timeline .tl-media h4 a{font-family:'PT Serif',serif;font-weight:700;font-size:24px;line-height:24px}.tl-timeline .tl-caption,.tl-timeline .tl-credit{font-family:'PT Serif',serif}.tl-timeline .vcard{font-family:'PT Sans Narrow',sans-serif !important;font-weight:normal !important}.tl-timeline .tl-slidenav-next,.tl-timeline .tl-slidenav-previous,.tl-timeline .tl-message,.tl-timeline .tl-timegroup-message{font-family:'PT Sans Narrow',sans-serif;font-weight:700;text-transform:uppercase}.tl-timeline .tl-slidenav-next .tl-slidenav-title,.tl-timeline .tl-slidenav-previous .tl-slidenav-title,.tl-timeline .tl-message .tl-slidenav-title,.tl-timeline .tl-timegroup-message .tl-slidenav-title{line-height:1.3em !important}.tl-timeline .tl-headline-date,.tl-timeline h3.tl-headline-date{font-family:'PT Sans Narrow',sans-serif !important;font-weight:normal !important;text-transform:uppercase !important}.tl-timeline .tl-headline-date small,.tl-timeline h3.tl-headline-date small{font-weight:normal !important}.tl-timeline .tl-timenav-slider{font-family:'PT Sans Narrow',sans-serif;font-weight:700}.tl-timeline .tl-timenav-slider h2.tl-headline{font-family:'PT Sans',sans-serif;font-weight:700;text-transform:none}.tl-timeline .tl-timenav-slider .tl-timeaxis{font-family:'PT Sans Narrow',sans-serif;font-weight:700;text-transform:uppercase}.tl-timeline .tl-menubar{font-family:'PT Sans Narrow',sans-serif}.tl-timeline blockquote,.tl-timeline blockquote p{font-family:'PT Serif',serif;font-style:italic;font-weight:400;line-height:1.3}input[type="text"].editor-headline{font-family:'PT Sans Narrow',sans-serif;text-transform:uppercase;font-weight:700;font-size:46px;line-height:46px}.tl-timeline .tl-timenav-slider .tl-timeaxis{font-weight:normal} \ No newline at end of file
diff --git a/www/wiki/extensions/ModernTimeline/resources/vendor/timeline.css b/www/wiki/extensions/ModernTimeline/resources/vendor/timeline.css
new file mode 100644
index 00000000..5401bccd
--- /dev/null
+++ b/www/wiki/extensions/ModernTimeline/resources/vendor/timeline.css
@@ -0,0 +1,27 @@
+/*
+ TimelineJS - ver. 3.6.5 - 2019-05-02
+ Copyright (c) 2012-2016 Northwestern University
+ a project of the Northwestern University Knight Lab, originally created by Zach Wise
+ https://github.com/NUKnightLab/TimelineJS3
+ This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
+ If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
+*/
+/*!
+ Timeline JS 3
+
+ Designed and built by Zach Wise for the Northwestern University Knight Lab
+
+ This Source Code Form is subject to the terms of the Mozilla Public
+ License, v. 2.0. If a copy of the MPL was not distributed with this
+ file, You can obtain one at https://mozilla.org/MPL/2.0/.
+
+*//*!
+ Timeline JS 3
+
+ Designed and built by Zach Wise for the Northwestern University Knight Lab
+
+ This Source Code Form is subject to the terms of the Mozilla Public
+ License, v. 2.0. If a copy of the MPL was not distributed with this
+ file, You can obtain one at https://mozilla.org/MPL/2.0/.
+
+*/.tl-storyjs div *{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box}.tl-storyjs h1,.tl-storyjs h2,.tl-storyjs h3,.tl-storyjs h4,.tl-storyjs h5,.tl-storyjs h6,.tl-storyjs p,.tl-storyjs blockquote,.tl-storyjs pre,.tl-storyjs a,.tl-storyjs abbr,.tl-storyjs acronym,.tl-storyjs address,.tl-storyjs cite,.tl-storyjs code,.tl-storyjs del,.tl-storyjs dfn,.tl-storyjs em,.tl-storyjs img,.tl-storyjs q,.tl-storyjs s,.tl-storyjs samp,.tl-storyjs small,.tl-storyjs strike,.tl-storyjs strong,.tl-storyjs sub,.tl-storyjs sup,.tl-storyjs tt,.tl-storyjs var,.tl-storyjs dd,.tl-storyjs dl,.tl-storyjs dt,.tl-storyjs li,.tl-storyjs ol,.tl-storyjs ul,.tl-storyjs fieldset,.tl-storyjs form,.tl-storyjs label,.tl-storyjs legend,.tl-storyjs button,.tl-storyjs table,.tl-storyjs caption,.tl-storyjs tbody,.tl-storyjs tfoot,.tl-storyjs thead,.tl-storyjs tr,.tl-storyjs th,.tl-storyjs td,.tl-storyjs .tl-container,.tl-storyjs .content-container,.tl-storyjs .media,.tl-storyjs .text,.tl-storyjs .tl-slider,.tl-storyjs .slider,.tl-storyjs .date,.tl-storyjs .title,.tl-storyjs .message,.tl-storyjs .map,.tl-storyjs .credit,.tl-storyjs .caption,.tl-storyjs .tl-feedback,.tl-storyjs .tl-feature,.tl-storyjs .toolbar,.tl-storyjs .marker,.tl-storyjs .dot,.tl-storyjs .line,.tl-storyjs .flag,.tl-storyjs .time,.tl-storyjs .era,.tl-storyjs .major,.tl-storyjs .minor,.tl-storyjs .tl-navigation,.tl-storyjs .start,.tl-storyjs .active{margin:0;padding:0;border:0;font-weight:400;font-style:normal;font-size:100%;line-height:1;font-family:inherit;width:auto;float:none}.tl-storyjs h1,.tl-storyjs h2,.tl-storyjs h3,.tl-storyjs h4,.tl-storyjs h5,.tl-storyjs h6{clear:none}.tl-storyjs table{border-collapse:collapse;border-spacing:0}.tl-storyjs ol,.tl-storyjs ul{list-style:none}.tl-storyjs q:before,.tl-storyjs q:after,.tl-storyjs blockquote:before,.tl-storyjs blockquote:after{content:""}.tl-storyjs a:focus{outline:thin dotted}.tl-storyjs a:hover,.tl-storyjs a:active{outline:0}.tl-storyjs article,.tl-storyjs aside,.tl-storyjs details,.tl-storyjs figcaption,.tl-storyjs figure,.tl-storyjs footer,.tl-storyjs header,.tl-storyjs hgroup,.tl-storyjs nav,.tl-storyjs section{display:block}.tl-storyjs audio,.tl-storyjs canvas,.tl-storyjs video{display:inline-block;*display:inline;*zoom:1}.tl-storyjs audio:not([controls]){display:none}.tl-storyjs div{max-width:none}.tl-storyjs sub,.tl-storyjs sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}.tl-storyjs sup{top:-.5em}.tl-storyjs sub{bottom:-.25em}.tl-storyjs img{border:0;-ms-interpolation-mode:bicubic}.tl-storyjs button,.tl-storyjs input,.tl-storyjs select,.tl-storyjs textarea{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle}.tl-storyjs button,.tl-storyjs input{line-height:normal;*overflow:visible}.tl-storyjs button::-moz-focus-inner,.tl-storyjs input::-moz-focus-inner{border:0;padding:0}.tl-storyjs button,.tl-storyjs input[type=button],.tl-storyjs input[type=reset],.tl-storyjs input[type=submit]{cursor:pointer;-webkit-appearance:button}.tl-storyjs input[type=search]{-webkit-appearance:textfield;-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box}.tl-storyjs input[type=search]::-webkit-search-decoration{-webkit-appearance:none}.tl-storyjs textarea{overflow:auto;vertical-align:top}.tl-timeline{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif}.tl-timeline h1,.tl-timeline h2,.tl-timeline h3,.tl-timeline h4,.tl-timeline h5,.tl-timeline h6{color:#000}.tl-timeline h1,.tl-timeline h2,.tl-timeline h3{font-size:28px;line-height:28px}.tl-timeline h1 small,.tl-timeline h2 small,.tl-timeline h3 small{font-size:24px;line-height:24px}.tl-timeline h4,.tl-timeline h5,.tl-timeline h6{font-size:24px;line-height:24px;margin-bottom:0}.tl-timeline h4 small,.tl-timeline h5 small,.tl-timeline h6 small{font-size:15px;line-height:15px}.tl-timeline h2.tl-headline-title{font-size:38px;line-height:38px}.tl-timeline h2.tl-headline-title small{display:block;margin-top:5px;font-size:24px;line-height:24px}.tl-timeline h2{margin-top:20px;margin-bottom:5px}.tl-timeline p{margin-top:5px;margin-bottom:10px;font-size:15px;line-height:1.42857143;color:#666}.tl-timeline .tl-caption,.tl-timeline .tl-credit,.tl-timeline .tl-slidenav-next,.tl-timeline .tl-slidenav-previous{font-size:11px;line-height:11px}.tl-timeline .tl-caption a,.tl-timeline .tl-credit a,.tl-timeline .tl-slidenav-next a,.tl-timeline .tl-slidenav-previous a{color:#000}.tl-timeline .tl-makelink{word-break:break-all;word-break:break-word;-webkit-hyphens:auto;-moz-hyphens:auto;hyphens:auto}.tl-timeline blockquote,.tl-timeline blockquote p{font-family:Georgia,Times New Roman,Times,serif;color:#999;font-size:24px;line-height:24px;text-align:left;background:0 0;border:0;padding:0}.tl-timeline blockquote cite,.tl-timeline blockquote p cite{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:11px;color:#666;display:block;text-align:right;font-style:normal}.tl-timeline blockquote cite:before,.tl-timeline blockquote p cite:before{content:"\2014"}.tl-timeline blockquote p:before{content:open-quote;display:inline-block;font-size:28px;position:relative;top:8px;margin-right:5px}.tl-timeline blockquote p:after{content:close-quote;display:inline-block;font-size:28px;position:relative;top:8px;margin-left:3px}.tl-timeline blockquote{margin:10px}.tl-timeline blockquote p{margin:0}.tl-timeline .vcard{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:15px;line-height:15px;*zoom:1;margin-bottom:15px;margin-top:10px}.tl-timeline .vcard:before,.tl-timeline .vcard:after{display:table;content:""}.tl-timeline .vcard:after{clear:both}.tl-timeline .vcard .twitter-date{text-align:left;font-size:11px}.tl-timeline .vcard .author{float:right}.tl-timeline .vcard a{color:#333;text-decoration:none}.tl-timeline .vcard a:hover{text-decoration:none}.tl-timeline .vcard a:hover .fn,.tl-timeline .vcard a:hover .nickname{color:#c34528}.tl-timeline .vcard .fn,.tl-timeline .vcard .nickname{padding-left:42px}.tl-timeline .vcard .fn{display:block;font-weight:700}.tl-timeline .vcard .nickname{margin-top:1px;display:block;color:#666}.tl-timeline .vcard .avatar{float:left;display:block;width:32px;height:32px}.tl-timeline .vcard .avatar img{-moz-border-radius:5px;-webkit-border-radius:5px;border-radius:5px}.tl-timeline .tl-text ul{padding:0;padding-left:30px;margin:0}.tl-timeline .tl-text ul li{margin-bottom:5px}.tl-timeline .tl-button-calltoaction{cursor:pointer;font-weight:700;padding-top:10px;margin-bottom:10px;padding-bottom:10px}.tl-timeline .tl-button-calltoaction .tl-button-calltoaction-text{display:inline-block;background-color:#c34528;color:#fff;padding:10px 15px;border-radius:7px}.tl-timeline .tl-note{display:block;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-style:italic;background-color:#e6e6e6;font-size:15px;line-height:17px;padding:10px;-webkit-border-radius:7px;-moz-border-radius:7px;border-radius:7px;color:#8a6d3b;background-color:#fcf8e3;border:1px solid #faebcc;text-shadow:none}@media only screen and (max-width:800px),only screen and (max-device-width:800px){.tl-timeline h1,.tl-timeline h2,.tl-timeline h3{font-size:28px;line-height:28px}}@media only screen and (max-width:500px),only screen and (max-device-width:500px){.tl-timeline h1,.tl-timeline h2,.tl-timeline h3{font-size:24px;line-height:24px}}.tl-skinny h2{margin-top:0}@font-face{font-family:tl-icons;src:url(tl-icons.eot);src:url(tl-icons.eot?#iefix) format('embedded-opentype'),url(tl-icons.ttf) format('truetype'),url(tl-icons.woff2) format('woff2'),url(tl-icons.woff) format('woff'),url(tl-icons.svg#tl-icons) format('svg');font-weight:400;font-style:normal}[class^=tl-icon-],[class*=" tl-icon-"]{font-family:tl-icons;speak:none;font-style:normal;font-weight:400;font-variant:normal;text-transform:none;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.tl-icon-vine:after{content:"\e64d"}.tl-icon-wikipedia:after{content:"\e64e"}.tl-icon-chevron-right:after{content:"\e64f"}.tl-icon-chevron-left:after{content:"\e650"}.tl-icon-youtube-logo:after{content:"\e651"}.tl-icon-foursquare:after{content:"\e652"}.tl-icon-camera-retro:after{content:"\e653"}.tl-icon-doc:after{content:"\e654"}.tl-icon-weibo:after{content:"\e655"}.tl-icon-resize-horizontal:after{content:"\e656"}.tl-icon-resize-vertical:after{content:"\e657"}.tl-icon-resize-full:after{content:"\e658"}.tl-icon-resize-small:after{content:"\e659"}.tl-icon-twitter:after{content:"\e62b"}.tl-icon-google-plus:after{content:"\e62c"}.tl-icon-video:after{content:"\e62d"}.tl-icon-youtube:after{content:"\e62d"}.tl-icon-plaintext:after{content:"\e62e"}.tl-icon-storify:after{content:"\e62e"}.tl-icon-image-v2:after{content:"\e62f"}.tl-icon-quote-v2:after{content:"\e630"}.tl-icon-zoom-in:after{content:"\e631"}.tl-icon-zoom-out:after{content:"\e632"}.tl-icon-list:after{content:"\e633"}.tl-icon-music:after{content:"\e634"}.tl-icon-spotify:after{content:"\e634"}.tl-icon-location:after{content:"\e635"}.tl-icon-googlemaps:after{content:"\e635"}.tl-icon-web:after{content:"\e636"}.tl-icon-share-v2:after{content:"\e637"}.tl-icon-soundcloud:after{content:"\e639"}.tl-icon-video-v2:after{content:"\e63a"}.tl-icon-dailymotion:after{content:"\e63a"}.tl-icon-tumblr:after{content:"\e63b"}.tl-icon-lastfm:after{content:"\e63c"}.tl-icon-github:after{content:"\e63d"}.tl-icon-goback:after{content:"\e63e"}.tl-icon-doc-v2:after{content:"\e63f"}.tl-icon-googledrive:after{content:"\e640"}.tl-icon-facebook:after{content:"\e641"}.tl-icon-flickr:after{content:"\e642"}.tl-icon-dribbble:after{content:"\e643"}.tl-icon-image:after{content:"\e605"}.tl-icon-vimeo:after{content:"\e606"}.tl-icon-instagram:after{content:"\e644"}.tl-icon-pinterest:after{content:"\e645"}.tl-icon-arrow-left:after{content:"\e646"}.tl-icon-arrow-down:after{content:"\e647"}.tl-icon-arrow-up:after{content:"\e648"}.tl-icon-arrow-right:after{content:"\e649"}.tl-icon-share:after{content:"\e64a"}.tl-icon-blockquote:after{content:"\e64b"}.tl-icon-evernote:after{content:"\e64c"}.tl-icon-mappin:after{content:"\e600"}.tl-icon-swipe-right:after{content:"\e601"}.tl-icon-swipe-left:after{content:"\e602"}.tl-icon-touch-spread:after{content:"\e603"}.tl-icon-touch-pinch:after{content:"\e604"}.tl-dragging{-webkit-touch-callout:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;-o-user-select:none;user-select:none}.tl-menubar{position:absolute;z-index:11;text-align:center;color:#333;overflow:hidden;border-bottom-right-radius:10px;border-top-right-radius:10px;top:100%;left:50%;left:0}@keyframes invertToBlack{from{background-color:#fff}to{background-color:#000}}@-webkit-keyframes invertToBlack{from{background:#fff}to{background:#000}}@keyframes invertToWhite{from{background-color:#000}to{background-color:#fff}}@-webkit-keyframes invertToWhite{from{background:#000}to{background:#fff}}.tl-menubar-button{font-size:18px;line-height:18px;background-color:rgba(242,242,242,.9);cursor:pointer;padding:6px 12px;display:inline-block;display:block;color:#bfbfbf}.tl-menubar-button.tl-menubar-button-inactive{opacity:.33}.tl-menubar-button:hover{background:#333;color:#fff}.tl-menubar-button:hover.tl-menubar-button-inactive{color:#bfbfbf;background-color:rgba(242,242,242,.9)}.tl-mobile .tl-menubar-button{display:block}.tl-mobile .tl-menubar-button:hover{background-color:rgba(242,242,242,.67);color:#737373}.tl-mobile .tl-menubar-button:active{background:#c34528;color:#fff}@keyframes invertToBlack{from{background-color:#fff}to{background-color:#000}}@-webkit-keyframes invertToBlack{from{background:#fff}to{background:#000}}@keyframes invertToWhite{from{background-color:#000}to{background-color:#fff}}@-webkit-keyframes invertToWhite{from{background:#000}to{background:#fff}}.tl-message,.tl-message-full{width:100%;height:100%;position:absolute;display:table;overflow:hidden;top:0;left:0;z-index:99;margin:auto;text-align:center}.tl-message .tl-message-container,.tl-message-full .tl-message-container{padding:20px;margin:20px;text-align:center;vertical-align:middle;display:table-cell}.tl-message .tl-message-container .tl-message-content,.tl-message-full .tl-message-container .tl-message-content{color:#666;text-align:center;font-size:11px;line-height:13px;text-transform:uppercase;margin-top:7.5px;margin-bottom:7.5px;text-shadow:1px 1px 1px #fff}.tl-message .tl-message-container .tl-message-content strong,.tl-message-full .tl-message-container .tl-message-content strong{text-transform:uppercase}.tl-message .tl-message-container .tl-loading-icon,.tl-message-full .tl-message-container .tl-loading-icon{width:30px;height:30px;background-color:#666;vertical-align:middle;-webkit-box-shadow:inset 0 1px 2px rgba(255,255,255,.1);-moz-box-shadow:inset 0 1px 2px rgba(255,255,255,.1);box-shadow:inset 0 1px 2px rgba(255,255,255,.1);margin-left:auto;margin-right:auto;text-align:center;-webkit-animation:rotateplane 1.2s infinite ease-in-out;animation:rotateplane 1.2s infinite ease-in-out}@-webkit-keyframes rotateplane{0%{-webkit-transform:perspective(120px)}50%{-webkit-transform:perspective(120px) rotateY(180deg)}100%{-webkit-transform:perspective(120px) rotateY(180deg) rotateX(180deg)}}@keyframes rotateplane{0%{transform:perspective(120px) rotateX(0deg) rotateY(0deg)}50%{transform:perspective(120px) rotateX(-180.1deg) rotateY(0deg)}100%{transform:perspective(120px) rotateX(-180deg) rotateY(-179.9deg)}}.tl-message-full{background-color:rgba(255,255,255,.8)}.tl-message-full [class^=tl-icon-],.tl-message-full [class*=" tl-icon-"]{color:#666;font-size:72px}.tl-message-full .tl-message-container .tl-message-content{font-size:22px;line-height:22px;text-shadow:none;color:#666;text-transform:none;font-weight:400}.tl-message-full .tl-message-container .tl-message-content .tl-button{display:inline-block;cursor:pointer;background-color:#fff;color:#333;padding:10px;margin-top:10px;-webkit-border-radius:7px;-moz-border-radius:7px;border-radius:7px}.tl-timenav{width:100%;background-color:#f2f2f2;position:relative;overflow:hidden;border-top:1px solid #e5e5e5}.tl-timenav .tl-attribution{cursor:pointer;z-index:9;position:absolute;bottom:2px;left:0;font-size:10px;line-height:10px;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif!important;background-color:rgba(255,255,255,.85);padding:3px}.tl-timenav .tl-attribution a{color:#ccc}.tl-timenav .tl-attribution a:hover{color:#000;text-decoration:none}.tl-timenav .tl-attribution a:hover .tl-knightlab-logo{background-color:#c34528}.tl-timenav .tl-attribution .tl-knightlab-logo{display:inline-block;vertical-align:middle;height:8px;width:8px;margin-right:3px;background-color:#c34528;background-color:#ccc;transform:rotate(45deg);-ms-transform:rotate(45deg);-webkit-transform:rotate(45deg)}.tl-timenav .tl-timenav-line{position:absolute;top:0;left:50%;width:1px;height:100%;background-color:#d9d9d9;z-index:2;display:none}.tl-timenav .tl-timenav-line:before,.tl-timenav .tl-timenav-line:after{font-family:tl-icons;speak:none;font-style:normal;font-weight:400;font-variant:normal;text-transform:none;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;color:#c34528;font-size:32px;line-height:32px;position:absolute;left:-14px}.tl-timenav .tl-timenav-line:before{top:-10px}.tl-timenav .tl-timenav-line:after{content:"\e648";bottom:24px}.tl-timenav .tl-timenav-slider{position:absolute;height:100%;width:100%;top:0}.tl-timenav .tl-timenav-slider.tl-timenav-slider-animate{-webkit-transition:all 1000ms cubic-bezier(.77,0,.175,1);-moz-transition:all 1000ms cubic-bezier(.77,0,.175,1);-o-transition:all 1000ms cubic-bezier(.77,0,.175,1);transition:all 1000ms cubic-bezier(.77,0,.175,1);-webkit-transition-timing-function:cubic-bezier(.77,0,.175,1);-moz-transition-timing-function:cubic-bezier(.77,0,.175,1);-o-transition-timing-function:cubic-bezier(.77,0,.175,1);transition-timing-function:cubic-bezier(.77,0,.175,1)}.tl-timenav .tl-timenav-slider .tl-timenav-slider-background{position:absolute;height:100%;width:100%;cursor:move;z-index:6}.tl-timenav .tl-timenav-slider .tl-timenav-container-mask{position:absolute;height:100%;top:0}.tl-timenav .tl-timenav-slider .tl-timenav-container-mask .tl-timenav-container{position:absolute;height:100%}.tl-timenav .tl-timenav-slider .tl-timenav-container-mask .tl-timenav-container .tl-timenav-item-container{position:absolute;height:100%}.tl-timemarker{height:100%;position:absolute;top:0;left:0;cursor:pointer;-webkit-transition:left 1000ms cubic-bezier(.77,0,.175,1),top 500ms cubic-bezier(.77,0,.175,1),height 500ms cubic-bezier(.77,0,.175,1),width 1000ms cubic-bezier(.77,0,.175,1);-moz-transition:left 1000ms cubic-bezier(.77,0,.175,1),top 500ms cubic-bezier(.77,0,.175,1),height 500ms cubic-bezier(.77,0,.175,1),width 1000ms cubic-bezier(.77,0,.175,1);-o-transition:left 1000ms cubic-bezier(.77,0,.175,1),top 500ms cubic-bezier(.77,0,.175,1),height 500ms cubic-bezier(.77,0,.175,1),width 1000ms cubic-bezier(.77,0,.175,1);transition:left 1000ms cubic-bezier(.77,0,.175,1),top 500ms cubic-bezier(.77,0,.175,1),height 500ms cubic-bezier(.77,0,.175,1),width 1000ms cubic-bezier(.77,0,.175,1);-webkit-transition-timing-function:cubic-bezier(.77,0,.175,1);-moz-transition-timing-function:cubic-bezier(.77,0,.175,1);-o-transition-timing-function:cubic-bezier(.77,0,.175,1);transition-timing-function:cubic-bezier(.77,0,.175,1)}.tl-timemarker.tl-timemarker-fast{-webkit-transition:top 500ms cubic-bezier(.77,0,.175,1),height 500ms cubic-bezier(.77,0,.175,1),width 500ms cubic-bezier(.77,0,.175,1);-moz-transition:top 500ms cubic-bezier(.77,0,.175,1),height 500ms cubic-bezier(.77,0,.175,1),width 500ms cubic-bezier(.77,0,.175,1);-o-transition:top 500ms cubic-bezier(.77,0,.175,1),height 500ms cubic-bezier(.77,0,.175,1),width 500ms cubic-bezier(.77,0,.175,1);transition:top 500ms cubic-bezier(.77,0,.175,1),height 500ms cubic-bezier(.77,0,.175,1),width 500ms cubic-bezier(.77,0,.175,1);-webkit-transition-timing-function:cubic-bezier(.77,0,.175,1);-moz-transition-timing-function:cubic-bezier(.77,0,.175,1);-o-transition-timing-function:cubic-bezier(.77,0,.175,1);transition-timing-function:cubic-bezier(.77,0,.175,1)}.tl-timemarker.tl-timemarker-fast .tl-timemarker-content-container{-webkit-transition:width 500ms cubic-bezier(.77,0,.175,1),height 500ms cubic-bezier(.77,0,.175,1);-moz-transition:width 500ms cubic-bezier(.77,0,.175,1),height 500ms cubic-bezier(.77,0,.175,1);-o-transition:width 500ms cubic-bezier(.77,0,.175,1),height 500ms cubic-bezier(.77,0,.175,1);transition:width 500ms cubic-bezier(.77,0,.175,1),height 500ms cubic-bezier(.77,0,.175,1)}.tl-timemarker.tl-timemarker-fast .tl-timemarker-timespan{-webkit-transition:height 500ms cubic-bezier(.77,0,.175,1),width 500ms cubic-bezier(.77,0,.175,1);-moz-transition:height 500ms cubic-bezier(.77,0,.175,1),width 500ms cubic-bezier(.77,0,.175,1);-o-transition:height 500ms cubic-bezier(.77,0,.175,1),width 500ms cubic-bezier(.77,0,.175,1);transition:height 500ms cubic-bezier(.77,0,.175,1),width 500ms cubic-bezier(.77,0,.175,1)}.tl-timemarker .tl-timemarker-timespan{pointer-events:none;position:absolute;margin:0;width:100%;height:100%;background-color:rgba(229,229,229,.15);border-top-right-radius:4px;border-top-left-radius:4px;-webkit-transition:height 500ms cubic-bezier(.77,0,.175,1),width 1000ms cubic-bezier(.77,0,.175,1);-moz-transition:height 500ms cubic-bezier(.77,0,.175,1),width 1000ms cubic-bezier(.77,0,.175,1);-o-transition:height 500ms cubic-bezier(.77,0,.175,1),width 1000ms cubic-bezier(.77,0,.175,1);transition:height 500ms cubic-bezier(.77,0,.175,1),width 1000ms cubic-bezier(.77,0,.175,1);-webkit-transition-timing-function:cubic-bezier(.77,0,.175,1);-moz-transition-timing-function:cubic-bezier(.77,0,.175,1);-o-transition-timing-function:cubic-bezier(.77,0,.175,1);transition-timing-function:cubic-bezier(.77,0,.175,1)}.tl-timemarker .tl-timemarker-timespan .tl-timemarker-timespan-content{display:none;position:absolute;width:100%;background-color:#e5e5e5;border-top-left-radius:4px;border-top-right-radius:4px;height:100px;box-sizing:border-box}.tl-timemarker .tl-timemarker-line-right{display:none;right:0}.tl-timemarker .tl-timemarker-line-left{width:1px;left:0}.tl-timemarker .tl-timemarker-line-left,.tl-timemarker .tl-timemarker-line-right{margin-top:7px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;border-left:1px solid #d9d9d9;z-index:5;content:" ";position:absolute;height:100%;-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;box-shadow:1px 1px 1px #fff}.tl-timemarker .tl-timemarker-line-left:after,.tl-timemarker .tl-timemarker-line-right:after{display:block;content:" ";position:absolute;left:-4px;bottom:0;height:6px;width:6px;background-color:#919191;z-index:8;-webkit-border-radius:50%;-moz-border-radius:50%;border-radius:50%}.tl-timemarker .tl-timemarker-content-container{position:absolute;background-color:#e5e5e5;border:0;border-top-left-radius:4px;border-top-right-radius:4px;border-bottom-right-radius:4px;height:100%;width:100px;overflow:hidden;z-index:6;-webkit-transition:height 500ms cubic-bezier(.77,0,.175,1),width 1000ms cubic-bezier(.77,0,.175,1);-moz-transition:height 500ms cubic-bezier(.77,0,.175,1),width 1000ms cubic-bezier(.77,0,.175,1);-o-transition:height 500ms cubic-bezier(.77,0,.175,1),width 1000ms cubic-bezier(.77,0,.175,1);transition:height 500ms cubic-bezier(.77,0,.175,1),width 1000ms cubic-bezier(.77,0,.175,1);-webkit-transition-timing-function:cubic-bezier(.77,0,.175,1);-moz-transition-timing-function:cubic-bezier(.77,0,.175,1);-o-transition-timing-function:cubic-bezier(.77,0,.175,1);transition-timing-function:cubic-bezier(.77,0,.175,1);box-sizing:border-box;border:1px solid #d9d9d9;box-shadow:1px 1px 1px #fff}.tl-timemarker .tl-timemarker-content-container:hover{z-index:9}.tl-timemarker .tl-timemarker-content-container .tl-timemarker-content{position:relative;overflow:hidden;height:100%;z-index:8;padding:5px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.tl-timemarker .tl-timemarker-content-container .tl-timemarker-content .tl-timemarker-text{overflow:hidden;position:relative}.tl-timemarker .tl-timemarker-content-container .tl-timemarker-content .tl-timemarker-text h2.tl-headline,.tl-timemarker .tl-timemarker-content-container .tl-timemarker-content .tl-timemarker-text h2.tl-headline p{display:-webkit-box;line-clamp:2;-webkit-line-clamp:2;box-orient:vertical;-webkit-box-orient:vertical;text-overflow:ellipsis;font-size:12px;line-height:12px;height:100%;overflow:hidden;font-weight:400;margin:0;color:#bfbfbf;position:relative}.tl-timemarker .tl-timemarker-content-container .tl-timemarker-content .tl-timemarker-text h2.tl-headline.tl-headline-fadeout:after,.tl-timemarker .tl-timemarker-content-container .tl-timemarker-content .tl-timemarker-text h2.tl-headline p.tl-headline-fadeout:after{content:"";text-align:right;position:absolute;bottom:0;right:0;width:100%;height:50%;background:-moz-linear-gradient(top,rgba(229,229,229,0)0,#e5e5e5 50%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0,rgba(229,229,229,0)),color-stop(50%,#e5e5e5));background:-webkit-linear-gradient(top,rgba(229,229,229,0)0,#e5e5e5 50%);background:-o-linear-gradient(top,rgba(229,229,229,0)0,#e5e5e5 50%);background:-ms-linear-gradient(top,rgba(229,229,229,0)0,#e5e5e5 50%);background:linear-gradient(to bottom,rgba(229,229,229,0)0,#e5e5e5 50%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='@thecolor', endColorstr='@thecolor', GradientType=0)}.tl-timemarker .tl-timemarker-content-container .tl-timemarker-content .tl-timemarker-media-container{float:left;max-width:24px;max-height:24px;overflow:hidden;margin-right:5px;height:100%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.tl-timemarker .tl-timemarker-content-container .tl-timemarker-content .tl-timemarker-media-container .tl-timemarker-media{max-width:24px;max-height:100%;opacity:.25}.tl-timemarker .tl-timemarker-content-container .tl-timemarker-content .tl-timemarker-media-container [class^=tl-icon-],.tl-timemarker .tl-timemarker-content-container .tl-timemarker-content .tl-timemarker-media-container [class*=" tl-icon-"]{display:block;font-size:24px;color:#bfbfbf;margin-top:0}.tl-timemarker .tl-timemarker-content-container .tl-timemarker-content .tl-timemarker-media-container .tl-icon-wikipedia{font-size:16px}.tl-timemarker .tl-timemarker-content-container .tl-timemarker-content.tl-timemarker-content-small .tl-timemarker-text h2.tl-headline{display:block;white-space:nowrap;text-overflow:ellipsis}.tl-timemarker .tl-timemarker-content-container .tl-timemarker-content.tl-timemarker-content-small .tl-timemarker-media-container [class^=tl-icon-],.tl-timemarker .tl-timemarker-content-container .tl-timemarker-content.tl-timemarker-content-small .tl-timemarker-media-container [class*=" tl-icon-"]{font-size:12px}.tl-timemarker:hover .tl-timemarker-timespan{background-color:rgba(191,191,191,.15)}.tl-timemarker:hover .tl-timemarker-timespan .tl-timemarker-timespan-content{background-color:#bfbfbf}.tl-timemarker:hover .tl-timemarker-timespan .tl-timemarker-line-left,.tl-timemarker:hover .tl-timemarker-timespan .tl-timemarker-line-right{border-color:#a6a6a6}.tl-timemarker:hover .tl-timemarker-timespan .tl-timemarker-line-left:after,.tl-timemarker:hover .tl-timemarker-timespan .tl-timemarker-line-right:after{background-color:#3d3d3d}.tl-timemarker:hover .tl-timemarker-content-container{background-color:#bfbfbf;border-color:#a6a6a6;-webkit-transition:height 250ms cubic-bezier(.77,0,.175,1),width 250ms cubic-bezier(.77,0,.175,1);-moz-transition:height 250ms cubic-bezier(.77,0,.175,1),width 250ms cubic-bezier(.77,0,.175,1);-o-transition:height 250ms cubic-bezier(.77,0,.175,1),width 250ms cubic-bezier(.77,0,.175,1);transition:height 250ms cubic-bezier(.77,0,.175,1),width 250ms cubic-bezier(.77,0,.175,1)}.tl-timemarker:hover .tl-timemarker-content-container.tl-timemarker-content-container-small{width:200px}.tl-timemarker:hover .tl-timemarker-content-container .tl-timemarker-content .tl-timemarker-text h2.tl-headline{color:#fff}.tl-timemarker:hover .tl-timemarker-content-container .tl-timemarker-content .tl-timemarker-text h2.tl-headline.tl-headline-fadeout:after{background:-moz-linear-gradient(top,rgba(191,191,191,0)0,#bfbfbf 80%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0,rgba(191,191,191,0)),color-stop(80%,#bfbfbf));background:-webkit-linear-gradient(top,rgba(191,191,191,0)0,#bfbfbf 80%);background:-o-linear-gradient(top,rgba(191,191,191,0)0,#bfbfbf 80%);background:-ms-linear-gradient(top,rgba(191,191,191,0)0,#bfbfbf 80%);background:linear-gradient(to bottom,rgba(191,191,191,0)0,#bfbfbf 80%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='@thecolor', endColorstr='@thecolor', GradientType=0)}.tl-timemarker:hover .tl-timemarker-content-container .tl-timemarker-content .tl-timemarker-media-container .tl-timemarker-media{opacity:1}.tl-timemarker:hover .tl-timemarker-content-container .tl-timemarker-content .tl-timemarker-media-container [class^=tl-icon-],.tl-timemarker:hover .tl-timemarker-content-container .tl-timemarker-content .tl-timemarker-media-container [class*=" tl-icon-"]{color:#fff}.tl-timemarker:hover.tl-timemarker-active .tl-timemarker-content-container .tl-timemarker-content .tl-timemarker-text h2.tl-headline.tl-headline-fadeout:after{background:-moz-linear-gradient(top,rgba(255,255,255,0)0,#fff 80%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0,rgba(255,255,255,0)),color-stop(80%,#fff));background:-webkit-linear-gradient(top,rgba(255,255,255,0)0,#fff 80%);background:-o-linear-gradient(top,rgba(255,255,255,0)0,#fff 80%);background:-ms-linear-gradient(top,rgba(255,255,255,0)0,#fff 80%);background:linear-gradient(to bottom,rgba(255,255,255,0)0,#fff 80%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='@thecolor', endColorstr='@thecolor', GradientType=0)}.tl-timemarker:hover.tl-timemarker-active .tl-timemarker-line-left,.tl-timemarker:hover.tl-timemarker-active .tl-timemarker-line-right{border-color:#000}.tl-timemarker:hover.tl-timemarker-active .tl-timemarker-line-left:after,.tl-timemarker:hover.tl-timemarker-active .tl-timemarker-line-right:after{background-color:#000}.tl-timemarker.tl-timemarker-active .tl-timemarker-timespan{background-color:rgba(255,255,255,.5);z-index:8}.tl-timemarker.tl-timemarker-active .tl-timemarker-timespan .tl-timemarker-timespan-content{background-color:#333}.tl-timemarker.tl-timemarker-active .tl-timemarker-line-left,.tl-timemarker.tl-timemarker-active .tl-timemarker-line-right{border-color:rgba(51,51,51,.5);border-width:1px;z-index:8;box-shadow:0 1px 3px rgba(145,145,145,.5)}.tl-timemarker.tl-timemarker-active .tl-timemarker-line-left:after,.tl-timemarker.tl-timemarker-active .tl-timemarker-line-right:after{background-color:#333}.tl-timemarker.tl-timemarker-active .tl-timemarker-content-container{background-color:#fff;color:#333;z-index:9;border-color:rgba(51,51,51,.5);box-shadow:1px 1px 3px rgba(145,145,145,.5)}.tl-timemarker.tl-timemarker-active .tl-timemarker-content-container .tl-timemarker-content .tl-timemarker-text h2.tl-headline{color:#333}.tl-timemarker.tl-timemarker-active .tl-timemarker-content-container .tl-timemarker-content .tl-timemarker-text h2.tl-headline.tl-headline-fadeout:after{background:-moz-linear-gradient(top,rgba(255,255,255,0)0,#fff 80%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0,rgba(255,255,255,0)),color-stop(80%,#fff));background:-webkit-linear-gradient(top,rgba(255,255,255,0)0,#fff 80%);background:-o-linear-gradient(top,rgba(255,255,255,0)0,#fff 80%);background:-ms-linear-gradient(top,rgba(255,255,255,0)0,#fff 80%);background:linear-gradient(to bottom,rgba(255,255,255,0)0,#fff 80%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='@thecolor', endColorstr='@thecolor', GradientType=0)}.tl-timemarker.tl-timemarker-active .tl-timemarker-content-container .tl-timemarker-content .tl-timemarker-media-container .tl-timemarker-media{opacity:1}.tl-timemarker.tl-timemarker-active .tl-timemarker-content-container .tl-timemarker-content .tl-timemarker-media-container [class^=tl-icon-],.tl-timemarker.tl-timemarker-active .tl-timemarker-content-container .tl-timemarker-content .tl-timemarker-media-container [class*=" tl-icon-"]{color:#333}.tl-timemarker.tl-timemarker-with-end .tl-timemarker-timespan .tl-timemarker-timespan-content{display:block}.tl-timemarker.tl-timemarker-with-end .tl-timemarker-timespan .tl-timemarker-line-left,.tl-timemarker.tl-timemarker-with-end .tl-timemarker-timespan .tl-timemarker-line-right{z-index:5}.tl-timemarker.tl-timemarker-with-end .tl-timemarker-timespan:after{display:block;content:" ";position:absolute;left:0;bottom:-7px;height:6px;width:100%;background-color:rgba(115,115,115,.15);z-index:6;-webkit-border-radius:7px;-moz-border-radius:7px;border-radius:7px}.tl-timemarker.tl-timemarker-with-end .tl-timemarker-content-container.tl-timemarker-content-container-long{box-shadow:none}.tl-timemarker.tl-timemarker-with-end .tl-timemarker-line-right{display:block}.tl-timemarker.tl-timemarker-with-end .tl-timemarker-line-left{box-shadow:none}.tl-timemarker.tl-timemarker-with-end:hover .tl-timemarker-timespan:after{background-color:rgba(0,0,0,.25)}.tl-timemarker.tl-timemarker-with-end.tl-timemarker-active .tl-timemarker-timespan:after{background-color:rgba(51,51,51,.5)}.tl-timemarker.tl-timemarker-with-end.tl-timemarker-active .tl-timemarker-line-left,.tl-timemarker.tl-timemarker-with-end.tl-timemarker-active .tl-timemarker-line-right{border-width:1px}.tl-timemarker.tl-timemarker-with-end.tl-timemarker-active .tl-timemarker-line-left:after,.tl-timemarker.tl-timemarker-with-end.tl-timemarker-active .tl-timemarker-line-right:after{background-color:#333!important}.tl-timemarker.tl-timemarker-with-end.tl-timemarker-active .tl-timemarker-line-left{box-shadow:none}.tl-timemarker.tl-timemarker-with-end.tl-timemarker-active:hover .tl-timemarker-timespan:after{background-color:rgba(51,51,51,.5)}.tl-timeera{height:100%;height:40px;position:absolute;bottom:0;left:0;pointer-events:none;z-index:3;-webkit-transition:left 1000ms cubic-bezier(.77,0,.175,1),top 500ms cubic-bezier(.77,0,.175,1),height 500ms cubic-bezier(.77,0,.175,1),width 1000ms cubic-bezier(.77,0,.175,1);-moz-transition:left 1000ms cubic-bezier(.77,0,.175,1),top 500ms cubic-bezier(.77,0,.175,1),height 500ms cubic-bezier(.77,0,.175,1),width 1000ms cubic-bezier(.77,0,.175,1);-o-transition:left 1000ms cubic-bezier(.77,0,.175,1),top 500ms cubic-bezier(.77,0,.175,1),height 500ms cubic-bezier(.77,0,.175,1),width 1000ms cubic-bezier(.77,0,.175,1);transition:left 1000ms cubic-bezier(.77,0,.175,1),top 500ms cubic-bezier(.77,0,.175,1),height 500ms cubic-bezier(.77,0,.175,1),width 1000ms cubic-bezier(.77,0,.175,1);-webkit-transition-timing-function:cubic-bezier(.77,0,.175,1);-moz-transition-timing-function:cubic-bezier(.77,0,.175,1);-o-transition-timing-function:cubic-bezier(.77,0,.175,1);transition-timing-function:cubic-bezier(.77,0,.175,1)}.tl-timeera.tl-timeera-fast{-webkit-transition:top 500ms cubic-bezier(.77,0,.175,1),height 500ms cubic-bezier(.77,0,.175,1),width 500ms cubic-bezier(.77,0,.175,1);-moz-transition:top 500ms cubic-bezier(.77,0,.175,1),height 500ms cubic-bezier(.77,0,.175,1),width 500ms cubic-bezier(.77,0,.175,1);-o-transition:top 500ms cubic-bezier(.77,0,.175,1),height 500ms cubic-bezier(.77,0,.175,1),width 500ms cubic-bezier(.77,0,.175,1);transition:top 500ms cubic-bezier(.77,0,.175,1),height 500ms cubic-bezier(.77,0,.175,1),width 500ms cubic-bezier(.77,0,.175,1);-webkit-transition-timing-function:cubic-bezier(.77,0,.175,1);-moz-transition-timing-function:cubic-bezier(.77,0,.175,1);-o-transition-timing-function:cubic-bezier(.77,0,.175,1);transition-timing-function:cubic-bezier(.77,0,.175,1)}.tl-timeera .tl-timeera-background{position:absolute;background-color:#28a6c3;width:100%;height:100%;opacity:1}.tl-timeera.tl-timeera-color0 .tl-timeera-background{background-color:#c34528}.tl-timeera.tl-timeera-color1 .tl-timeera-background{background-color:#28a6c3}.tl-timeera.tl-timeera-color2 .tl-timeera-background{background-color:#2832c3}.tl-timeera.tl-timeera-color3 .tl-timeera-background{background-color:#28c36c}.tl-timeera.tl-timeera-color4 .tl-timeera-background{background-color:#286dc3}.tl-timeera.tl-timeera-color5 .tl-timeera-background{background-color:#28c3a7}.tl-timeera .tl-timeera-content-container{position:absolute;border:0;border-top-left-radius:4px;border-top-right-radius:4px;border-bottom-right-radius:4px;height:100%;width:100px;overflow:hidden;-webkit-transition:height 500ms cubic-bezier(.77,0,.175,1),width 1000ms cubic-bezier(.77,0,.175,1);-moz-transition:height 500ms cubic-bezier(.77,0,.175,1),width 1000ms cubic-bezier(.77,0,.175,1);-o-transition:height 500ms cubic-bezier(.77,0,.175,1),width 1000ms cubic-bezier(.77,0,.175,1);transition:height 500ms cubic-bezier(.77,0,.175,1),width 1000ms cubic-bezier(.77,0,.175,1);-webkit-transition-timing-function:cubic-bezier(.77,0,.175,1);-moz-transition-timing-function:cubic-bezier(.77,0,.175,1);-o-transition-timing-function:cubic-bezier(.77,0,.175,1);transition-timing-function:cubic-bezier(.77,0,.175,1);box-sizing:border-box;border:1px solid #d9d9d9}.tl-timeera .tl-timeera-content-container .tl-timeera-content{position:relative;overflow:hidden;height:100%;padding:5px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.tl-timeera .tl-timeera-content-container .tl-timeera-content .tl-timeera-text{overflow:hidden;position:relative;height:100%}.tl-timeera .tl-timeera-content-container .tl-timeera-content .tl-timeera-text h2.tl-headline{bottom:0;position:absolute;display:-webkit-box;line-clamp:4;-webkit-line-clamp:4;box-orient:vertical;-webkit-box-orient:vertical;text-overflow:ellipsis;font-size:10px;line-height:10px;overflow:hidden;font-weight:400;margin:0;color:#fff;margin-left:10px}.tl-timeera .tl-timeera-content-container .tl-timeera-content .tl-timeera-text h2.tl-headline.tl-headline-fadeout:after{content:"";text-align:right;position:absolute;bottom:0;right:0;width:100%;height:50%;background:-moz-linear-gradient(top,rgba(229,229,229,0)0,#e5e5e5 50%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0,rgba(229,229,229,0)),color-stop(50%,#e5e5e5));background:-webkit-linear-gradient(top,rgba(229,229,229,0)0,#e5e5e5 50%);background:-o-linear-gradient(top,rgba(229,229,229,0)0,#e5e5e5 50%);background:-ms-linear-gradient(top,rgba(229,229,229,0)0,#e5e5e5 50%);background:linear-gradient(to bottom,rgba(229,229,229,0)0,#e5e5e5 50%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='@thecolor', endColorstr='@thecolor', GradientType=0)}.tl-timegroup{width:100%;position:absolute;top:0;left:0;background-color:#f2f2f2;display:-ms-flexbox;display:-webkit-flex;display:flex;align-items:center;-ms-flex-align:center;-webkit-align-items:center;-webkit-box-align:center;-webkit-transition:left 1000ms cubic-bezier(.77,0,.175,1),top 500ms cubic-bezier(.77,0,.175,1),height 500ms cubic-bezier(.77,0,.175,1),width 1000ms cubic-bezier(.77,0,.175,1);-moz-transition:left 1000ms cubic-bezier(.77,0,.175,1),top 500ms cubic-bezier(.77,0,.175,1),height 500ms cubic-bezier(.77,0,.175,1),width 1000ms cubic-bezier(.77,0,.175,1);-o-transition:left 1000ms cubic-bezier(.77,0,.175,1),top 500ms cubic-bezier(.77,0,.175,1),height 500ms cubic-bezier(.77,0,.175,1),width 1000ms cubic-bezier(.77,0,.175,1);transition:left 1000ms cubic-bezier(.77,0,.175,1),top 500ms cubic-bezier(.77,0,.175,1),height 500ms cubic-bezier(.77,0,.175,1),width 1000ms cubic-bezier(.77,0,.175,1);-webkit-transition-timing-function:cubic-bezier(.77,0,.175,1);-moz-transition-timing-function:cubic-bezier(.77,0,.175,1);-o-transition-timing-function:cubic-bezier(.77,0,.175,1);transition-timing-function:cubic-bezier(.77,0,.175,1)}.tl-timegroup .tl-timegroup-message{color:#e0e0e0;text-shadow:#fff 0 2px 2px;margin-left:80px}.tl-timegroup.tl-timegroup-alternate{background-color:#fafafa}.tl-timegroup.tl-timegroup-hidden{display:none}.tl-timeaxis-background{height:39px;width:100%;position:absolute;bottom:0;left:0;background-color:#fff;border-top:1px solid #e5e5e5;z-index:2}.tl-timeaxis{height:39px;width:100%;position:absolute;bottom:0;left:0;z-index:3}.tl-timeaxis .tl-timeaxis-content-container{position:relative;bottom:0;height:39px}.tl-timeaxis .tl-timeaxis-content-container .tl-timeaxis-major,.tl-timeaxis .tl-timeaxis-content-container .tl-timeaxis-minor{opacity:0;position:absolute}.tl-timeaxis .tl-timeaxis-content-container .tl-timeaxis-major .tl-timeaxis-tick,.tl-timeaxis .tl-timeaxis-content-container .tl-timeaxis-minor .tl-timeaxis-tick{position:absolute;display:block;top:0;left:0;text-align:center;font-weight:400}.tl-timeaxis .tl-timeaxis-content-container .tl-timeaxis-major .tl-timeaxis-tick .tl-timeaxis-tick-text,.tl-timeaxis .tl-timeaxis-content-container .tl-timeaxis-minor .tl-timeaxis-tick .tl-timeaxis-tick-text{display:inline-block;white-space:nowrap;text-overflow:ellipsis;overflow:hidden}.tl-timeaxis .tl-timeaxis-content-container .tl-timeaxis-major .tl-timeaxis-tick:before,.tl-timeaxis .tl-timeaxis-content-container .tl-timeaxis-minor .tl-timeaxis-tick:before{content:"|";display:block;color:#fff;width:1px;overflow:hidden;border-left:1px solid #bfbfbf;text-align:center}.tl-timeaxis .tl-timeaxis-content-container .tl-timeaxis-major.tl-timeaxis-animate .tl-timeaxis-tick,.tl-timeaxis .tl-timeaxis-content-container .tl-timeaxis-minor.tl-timeaxis-animate .tl-timeaxis-tick{-webkit-transition:all 1000ms cubic-bezier(.77,0,.175,1);-moz-transition:all 1000ms cubic-bezier(.77,0,.175,1);-o-transition:all 1000ms cubic-bezier(.77,0,.175,1);transition:all 1000ms cubic-bezier(.77,0,.175,1);-webkit-transition-timing-function:cubic-bezier(.77,0,.175,1);-moz-transition-timing-function:cubic-bezier(.77,0,.175,1);-o-transition-timing-function:cubic-bezier(.77,0,.175,1);transition-timing-function:cubic-bezier(.77,0,.175,1)}.tl-timeaxis .tl-timeaxis-content-container .tl-timeaxis-major.tl-timeaxis-animate-opacity .tl-timeaxis-tick,.tl-timeaxis .tl-timeaxis-content-container .tl-timeaxis-minor.tl-timeaxis-animate-opacity .tl-timeaxis-tick{-webkit-transition:opacity 1000ms cubic-bezier(.77,0,.175,1);-moz-transition:opacity 1000ms cubic-bezier(.77,0,.175,1);-o-transition:opacity 1000ms cubic-bezier(.77,0,.175,1);transition:opacity 1000ms cubic-bezier(.77,0,.175,1);-webkit-transition-timing-function:cubic-bezier(.77,0,.175,1);-moz-transition-timing-function:cubic-bezier(.77,0,.175,1);-o-transition-timing-function:cubic-bezier(.77,0,.175,1);transition-timing-function:cubic-bezier(.77,0,.175,1)}.tl-timeaxis .tl-timeaxis-content-container .tl-timeaxis-major{z-index:1;background-color:#fff}.tl-timeaxis .tl-timeaxis-content-container .tl-timeaxis-major .tl-timeaxis-tick{font-size:12px;line-height:14px;color:#737373;width:100px;margin-left:-50px}.tl-timeaxis .tl-timeaxis-content-container .tl-timeaxis-major .tl-timeaxis-tick:before{border-color:#a6a6a6;font-size:18px;line-height:18px;margin-bottom:2px;margin-left:50px}.tl-timeaxis .tl-timeaxis-content-container .tl-timeaxis-minor .tl-timeaxis-tick{font-size:11px;line-height:13px;color:#bfbfbf;width:50px;margin-left:-25px}.tl-timeaxis .tl-timeaxis-content-container .tl-timeaxis-minor .tl-timeaxis-tick .tl-timeaxis-tick-text{opacity:0;white-space:normal;padding-left:2px;padding-right:2px}.tl-timeaxis .tl-timeaxis-content-container .tl-timeaxis-minor .tl-timeaxis-tick .tl-timeaxis-tick-text span{display:block;font-size:9px;line-height:9px;margin-top:-2px;color:#e6e6e6}.tl-timeaxis .tl-timeaxis-content-container .tl-timeaxis-minor .tl-timeaxis-tick:before{font-size:9px;line-height:9px;margin-left:25px}.tl-timeaxis .tl-timeaxis-content-container .tl-timeaxis-minor .tl-timeaxis-tick.tl-timeaxis-tick-hidden .tl-timeaxis-tick-text{opacity:0!important}.tl-timeaxis .tl-timeaxis-content-container .tl-timeaxis-minor .tl-timeaxis-tick.tl-timeaxis-tick-hidden:before{opacity:.33}.tlanimate{-webkit-transform:translateZ(0);-webkit-perspective:1000;-webkit-backface-visibility:hidden}.tl-animate{-webkit-transition:all 1000ms cubic-bezier(.77,0,.175,1);-moz-transition:all 1000ms cubic-bezier(.77,0,.175,1);-o-transition:all 1000ms cubic-bezier(.77,0,.175,1);transition:all 1000ms cubic-bezier(.77,0,.175,1);-webkit-transition-timing-function:cubic-bezier(.77,0,.175,1);-moz-transition-timing-function:cubic-bezier(.77,0,.175,1);-o-transition-timing-function:cubic-bezier(.77,0,.175,1);transition-timing-function:cubic-bezier(.77,0,.175,1)}.tl-animate-opacity{-webkit-transition:opacity 1000ms cubic-bezier(.77,0,.175,1);-moz-transition:opacity 1000ms cubic-bezier(.77,0,.175,1);-o-transition:opacity 1000ms cubic-bezier(.77,0,.175,1);transition:opacity 1000ms cubic-bezier(.77,0,.175,1);-webkit-transition-timing-function:cubic-bezier(.77,0,.175,1);-moz-transition-timing-function:cubic-bezier(.77,0,.175,1);-o-transition-timing-function:cubic-bezier(.77,0,.175,1);transition-timing-function:cubic-bezier(.77,0,.175,1)}.tl-slide{position:absolute;width:100%;height:100%;padding:0;margin:0;overflow-x:hidden;overflow-y:auto}.tl-slide .tl-slide-background{position:absolute;left:0;top:0;width:100%;height:100%;z-index:-1;overflow:hidden;display:none;filter:alpha(opacity=50);-khtml-opacity:.5;-moz-opacity:.5;opacity:.5;background:no-repeat center center;-webkit-background-size:cover;-moz-background-size:cover;-o-background-size:cover;background-size:cover}.tl-slide .tl-slide-scrollable-container{display:table;table-layout:fixed;height:100%;z-index:1}.tl-slide .tl-slide-content-container{display:table-cell;vertical-align:middle;position:relative;width:100%;height:100%;z-index:3}.tl-slide .tl-slide-content-container .tl-slide-content{display:table;vertical-align:middle;padding-left:100px;padding-right:100px;position:relative;max-width:100%;user-select:text}.tl-slide .tl-slide-content-container .tl-slide-content .tl-media{position:relative;width:100%;min-width:50%;float:left;margin-top:auto;margin-bottom:auto}.tl-slide .tl-slide-content-container .tl-slide-content .tl-text{width:50%;max-width:50%;min-width:120px;padding:0 20px;display:table-cell;vertical-align:middle;text-align:left}.tl-slide-media-only .tl-slide-content-container .tl-slide-content{text-align:center}.tl-slide-media-only .tl-slide-content-container .tl-slide-content .tl-media{text-align:center;position:relative;width:100%;min-width:50%;max-width:100%;float:none;margin-top:auto;margin-bottom:auto}.tl-slide-media-only .tl-slide-content-container .tl-slide-content .tl-text{width:100%;max-width:100%;display:block;margin-left:auto;margin-right:auto;text-align:center}.tl-slide-text-only .tl-slide-content-container .tl-slide-content{text-align:center}.tl-slide-text-only .tl-slide-content-container .tl-slide-content .tl-text{max-width:80%;width:80%;display:block;margin-left:auto;margin-right:auto}.tl-slide.tl-full-image-background,.tl-slide.tl-full-color-background{color:#fff!important;text-shadow:1px 1px 1px #000!important}.tl-slide.tl-full-image-background p,.tl-slide.tl-full-color-background p,.tl-slide.tl-full-image-background h1,.tl-slide.tl-full-color-background h1,.tl-slide.tl-full-image-background h2,.tl-slide.tl-full-color-background h2,.tl-slide.tl-full-image-background h3,.tl-slide.tl-full-color-background h3,.tl-slide.tl-full-image-background h4,.tl-slide.tl-full-color-background h4,.tl-slide.tl-full-image-background h5,.tl-slide.tl-full-color-background h5,.tl-slide.tl-full-image-background h6,.tl-slide.tl-full-color-background h6{color:#fff!important;text-shadow:1px 1px 1px #000!important}.tl-slide.tl-full-image-background a,.tl-slide.tl-full-color-background a,.tl-slide.tl-full-image-background b,.tl-slide.tl-full-color-background b,.tl-slide.tl-full-image-background i,.tl-slide.tl-full-color-background i,.tl-slide.tl-full-image-background blockquote,.tl-slide.tl-full-color-background blockquote,.tl-slide.tl-full-image-background blockquote p,.tl-slide.tl-full-color-background blockquote p{text-shadow:1px 1px 1px #000;color:#fff}.tl-slide.tl-full-image-background a:hover,.tl-slide.tl-full-color-background a:hover{text-decoration:underline;color:#c34528}.tl-slide.tl-full-image-background .tl-caption,.tl-slide.tl-full-color-background .tl-caption,.tl-slide.tl-full-image-background .tl-credit,.tl-slide.tl-full-color-background .tl-credit{color:#fff!important;text-shadow:1px 1px 1px #000!important}.tl-slide.tl-full-image-background .tl-media-twitter blockquote,.tl-slide.tl-full-color-background .tl-media-twitter blockquote,.tl-slide.tl-full-image-background .tl-media-blockquote blockquote,.tl-slide.tl-full-color-background .tl-media-blockquote blockquote{color:#fff!important;text-shadow:1px 1px 1px #000!important}.tl-slide.tl-full-image-background .tl-media-twitter blockquote p,.tl-slide.tl-full-color-background .tl-media-twitter blockquote p,.tl-slide.tl-full-image-background .tl-media-blockquote blockquote p,.tl-slide.tl-full-color-background .tl-media-blockquote blockquote p{color:#fff!important;text-shadow:1px 1px 1px #000!important}.tl-slide.tl-full-image-background .vcard a,.tl-slide.tl-full-color-background .vcard a,.tl-slide.tl-full-image-background .vcard .nickname,.tl-slide.tl-full-color-background .vcard .nickname{color:#fff!important;text-shadow:1px 1px 1px #000!important}.tl-slide.tl-full-image-background{background:no-repeat center center;-webkit-background-size:cover;-moz-background-size:cover;-o-background-size:cover;background-size:cover;background-position:center 25%;text-shadow:1px 1px 2px #000}.tl-slide.tl-full-image-background p,.tl-slide.tl-full-image-background h1,.tl-slide.tl-full-image-background h2,.tl-slide.tl-full-image-background h3,.tl-slide.tl-full-image-background h4,.tl-slide.tl-full-image-background h5,.tl-slide.tl-full-image-background h6{text-shadow:1px 1px 2px #000}.tl-slide.tl-full-image-background .tl-caption,.tl-slide.tl-full-image-background .tl-credit{text-shadow:1px 1px 2px #000}.tl-slide.tl-full-image-background .tl-media-twitter blockquote,.tl-slide.tl-full-image-background .tl-media-blockquote blockquote{text-shadow:1px 1px 2px #000!important}.tl-slide.tl-full-image-background .tl-media-twitter blockquote p,.tl-slide.tl-full-image-background .tl-media-blockquote blockquote p{text-shadow:1px 1px 2px #000!important}.tl-slide.tl-text-background .tl-text .tl-text-content-container{padding:20px;background:#000 transparent;background:rgba(0,0,0,.6);-webkit-border-radius:7px;-moz-border-radius:7px;border-radius:7px}.tl-slide.tl-text-background .tl-text .tl-text-content-container h2{margin-top:5px}.tl-skinny .tl-slide{display:block;padding-top:10px}.tl-skinny .tl-slide .tl-slide-content-container{display:block;position:static;height:auto;height:100%;display:-webkit-flex;display:flex;align-items:center;-webkit-align-items:center}.tl-skinny .tl-slide .tl-slide-content-container .tl-slide-content{display:block;display:-webkit-flex;display:flex;flex-direction:column-reverse;-webkit-flex-direction:column-reverse;position:static;height:auto;padding-left:50px;padding-right:50px}.tl-skinny .tl-slide .tl-slide-content-container .tl-slide-content .tl-media{position:static;width:100%;height:auto;float:none;display:block;padding-top:20px;border-top:1px solid #e6e6e6}.tl-skinny .tl-slide .tl-slide-content-container .tl-slide-content .tl-text{display:block;height:auto;vertical-align:initial;position:static;width:100%;max-width:100%;min-width:0;float:none;padding:0}.tl-skinny .tl-slide .tl-slide-content-container .tl-slide-content .tl-text .tl-text-content-container{padding-left:10px;padding-right:10px;padding-bottom:10px}.tl-skinny .tl-slide.tl-slide.tl-full-color-background .tl-slide-content-container .tl-slide-content .tl-media,.tl-skinny .tl-slide.tl-full-image-background .tl-slide-content-container .tl-slide-content .tl-media{border-color:rgba(230,230,230,.25)}.tl-skinny .tl-slide.tl-slide-media-only .tl-slide-content-container .tl-slide-content{flex-direction:column;-webkit-flex-direction:column}.tl-skinny .tl-slide.tl-slide-media-only .tl-slide-content-container .tl-slide-content .tl-media{border-top:none;padding-top:0}.tl-mobile.tl-skinny .tl-slide .tl-slide-content-container .tl-slide-content .tl-media img,.tl-mobile.tl-skinny .tl-slide .tl-slide-content-container .tl-slide-content .tl-media embed,.tl-mobile.tl-skinny .tl-slide .tl-slide-content-container .tl-slide-content .tl-media object,.tl-mobile.tl-skinny .tl-slide .tl-slide-content-container .tl-slide-content .tl-media video,.tl-mobile.tl-skinny .tl-slide .tl-slide-content-container .tl-slide-content .tl-media iframe{max-height:175px}.tl-slidenav-previous,.tl-slidenav-next{position:absolute;top:45%;z-index:10;cursor:pointer}.tl-slidenav-previous .tl-slidenav-content-container,.tl-slidenav-next .tl-slidenav-content-container{height:200px;width:100px;position:absolute}.tl-slidenav-previous .tl-slidenav-title,.tl-slidenav-next .tl-slidenav-title,.tl-slidenav-previous .tl-slidenav-description,.tl-slidenav-next .tl-slidenav-description{width:80px;-webkit-line-clamp:2;line-clamp:2;text-overflow:ellipsis;filter:alpha(opacity=15);-khtml-opacity:.15;-moz-opacity:.15;opacity:.15}.tl-slidenav-previous .tl-slidenav-title small,.tl-slidenav-next .tl-slidenav-title small,.tl-slidenav-previous .tl-slidenav-description small,.tl-slidenav-next .tl-slidenav-description small{display:block}.tl-slidenav-previous .tl-slidenav-title,.tl-slidenav-next .tl-slidenav-title{margin-top:10px;font-size:11px;line-height:11px}.tl-slidenav-previous .tl-slidenav-description,.tl-slidenav-next .tl-slidenav-description{font-size:11px;margin-top:5px;filter:alpha(opacity=0);-khtml-opacity:0;-moz-opacity:0;opacity:0}.tl-slidenav-previous .tl-slidenav-description small,.tl-slidenav-next .tl-slidenav-description small{display:none}.tl-slidenav-previous .tl-slidenav-content-container .tl-slidenav-icon,.tl-slidenav-next .tl-slidenav-content-container .tl-slidenav-icon,.tl-slidenav-previous .tl-slidenav-content-container .tl-slidenav-title,.tl-slidenav-next .tl-slidenav-content-container .tl-slidenav-title,.tl-slidenav-previous .tl-slidenav-content-container .tl-slidenav-description,.tl-slidenav-next .tl-slidenav-content-container .tl-slidenav-description{text-shadow:1px 1px 1px #fff;color:#333}.tl-slidenav-previous .tl-slidenav-content-container.tl-slidenav-inverted .tl-slidenav-icon,.tl-slidenav-next .tl-slidenav-content-container.tl-slidenav-inverted .tl-slidenav-icon,.tl-slidenav-previous .tl-slidenav-content-container.tl-slidenav-inverted .tl-slidenav-title,.tl-slidenav-next .tl-slidenav-content-container.tl-slidenav-inverted .tl-slidenav-title,.tl-slidenav-previous .tl-slidenav-content-container.tl-slidenav-inverted .tl-slidenav-description,.tl-slidenav-next .tl-slidenav-content-container.tl-slidenav-inverted .tl-slidenav-description{color:#f2f2f2;text-shadow:1px 1px 1px #333}.tl-slidenav-next .tl-slidenav-icon,.tl-slidenav-previous .tl-slidenav-icon{font-family:tl-icons;speak:none;font-style:normal;font-weight:400;font-variant:normal;text-transform:none;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;font-size:32px;margin-bottom:5px}.tl-slidenav-next{text-align:right;margin-right:10px;right:100px}.tl-slidenav-next .tl-slidenav-title,.tl-slidenav-next .tl-slidenav-description{margin-left:20px}.tl-slidenav-next .tl-slidenav-icon{margin-left:76px}.tl-slidenav-next .tl-slidenav-icon:before{content:"\e64f"}.tl-slidenav-previous{text-align:left;margin-left:10px}.tl-slidenav-previous .tl-slidenav-icon{margin-left:0}.tl-slidenav-previous .tl-slidenav-icon:before{content:"\e650"}.tl-slidenav-previous:hover .tl-slidenav-title,.tl-slidenav-next:hover .tl-slidenav-title{filter:alpha(opacity=100);-khtml-opacity:1;-moz-opacity:1;opacity:1}.tl-slidenav-previous:hover .tl-slidenav-description,.tl-slidenav-next:hover .tl-slidenav-description{filter:alpha(opacity=50);-khtml-opacity:.5;-moz-opacity:.5;opacity:.5}.tl-slidenav-next:hover .tl-slidenav-icon{margin-left:80px}.tl-slidenav-previous:hover .tl-slidenav-icon{margin-left:-4px}.tl-skinny .tl-slidenav-next{right:32px}.tl-skinny .tl-slidenav-next .tl-slidenav-icon{margin-left:8px}.tl-skinny .tl-slidenav-previous .tl-slidenav-content-container,.tl-skinny .tl-slidenav-next .tl-slidenav-content-container{width:32px;height:32px}.tl-skinny .tl-slidenav-previous .tl-slidenav-title,.tl-skinny .tl-slidenav-next .tl-slidenav-title,.tl-skinny .tl-slidenav-previous .tl-slidenav-description,.tl-skinny .tl-slidenav-next .tl-slidenav-description{display:none}.tl-skinny .tl-slidenav-previous .tl-slidenav-icon,.tl-skinny .tl-slidenav-next .tl-slidenav-icon{filter:alpha(opacity=33);-khtml-opacity:.33;-moz-opacity:.33;opacity:.33}.tl-skinny .tl-slidenav-next:hover .tl-slidenav-icon{margin-left:12px;filter:alpha(opacity=100);-khtml-opacity:1;-moz-opacity:1;opacity:1}.tl-skinny .tl-slidenav-previous:hover .tl-slidenav-icon{margin-left:-4px;filter:alpha(opacity=100);-khtml-opacity:1;-moz-opacity:1;opacity:1}.tl-layout-landscape.tl-mobile .tl-slidenav-next:hover{right:70px}.tl-layout-landscape.tl-mobile .tl-slidenav-next:hover .tl-slidenav-icon{margin-left:8px;filter:alpha(opacity=100);-khtml-opacity:1;-moz-opacity:1;opacity:1}.tl-layout-landscape.tl-mobile .tl-slidenav-next:active .tl-slidenav-icon{margin-left:0;filter:alpha(opacity=100);-khtml-opacity:1;-moz-opacity:1;opacity:1}.tl-layout-landscape.tl-mobile .tl-slidenav-previous:hover .tl-slidenav-icon{margin-left:80px;filter:alpha(opacity=100);-khtml-opacity:1;-moz-opacity:1;opacity:1}.tl-layout-landscape.tl-mobile .tl-slidenav-previous:active .tl-slidenav-icon{filter:alpha(opacity=100);-khtml-opacity:1;-moz-opacity:1;opacity:1;margin-left:-4px}.tl-layout-portrait.tl-mobile .tl-slidenav-next:hover .tl-slidenav-icon{filter:alpha(opacity=33);-khtml-opacity:.33;-moz-opacity:.33;opacity:.33}.tl-layout-portrait.tl-mobile .tl-slidenav-next:active .tl-slidenav-icon{filter:alpha(opacity=100);-khtml-opacity:1;-moz-opacity:1;opacity:1}.tl-layout-portrait.tl-mobile .tl-slidenav-previous:hover .tl-slidenav-icon{filter:alpha(opacity=33);-khtml-opacity:.33;-moz-opacity:.33;opacity:.33}.tl-layout-portrait.tl-mobile .tl-slidenav-previous:active .tl-slidenav-icon{filter:alpha(opacity=100);-khtml-opacity:1;-moz-opacity:1;opacity:1}.tl-mobile .tl-slidenav-previous,.tl-skinny.tl-mobile .tl-slidenav-previous,.tl-skinny.tl-layout-landscape.tl-mobile .tl-slidenav-previous,.tl-skinny.tl-layout-portrait.tl-mobile .tl-slidenav-previous,.tl-mobile .tl-slidenav-next,.tl-skinny.tl-mobile .tl-slidenav-next,.tl-skinny.tl-layout-landscape.tl-mobile .tl-slidenav-next,.tl-skinny.tl-layout-portrait.tl-mobile .tl-slidenav-next{display:none}.tl-storyslider{width:100%;height:100%;overflow:hidden;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;-o-user-select:none;user-select:none;position:relative;box-sizing:content-box;z-index:8}.tl-storyslider img,.tl-storyslider embed,.tl-storyslider object,.tl-storyslider video,.tl-storyslider iframe{max-width:100%;position:relative}.tl-storyslider .tl-slider-background{position:absolute;top:0;left:0;width:100%;height:100%;z-index:1}.tl-storyslider .tl-slider-touch-mask{width:100%;height:100%;z-index:25;top:0;left:0;position:absolute}.tl-storyslider .tl-slider-container-mask{text-align:center;width:100%;height:100%;position:relative;z-index:5}.tl-storyslider .tl-slider-container-mask .tl-slider-container{position:absolute;top:0;left:0;width:100%;height:100%;text-align:center}.tl-storyslider .tl-slider-container-mask .tl-slider-container .tl-slider-item-container{width:100%;height:100%;display:table-cell;vertical-align:middle}.tl-media{width:100%;min-width:50%;height:100%;float:left;margin-top:auto;margin-bottom:auto;position:relative}.tl-media .tl-media-content-container.tl-media-content-container-text{border-right:1px solid #e6e6e6;padding-right:20px}.tl-media .tl-media-content-container .tl-media-content{position:relative;*zoom:1}.tl-media .tl-media-content-container .tl-media-content:before,.tl-media .tl-media-content-container .tl-media-content:after{display:table;content:""}.tl-media .tl-media-content-container .tl-media-content:after{clear:both}.tl-media .tl-media-content-container .tl-media-content .tl-media-loaderror p{color:#f2f2f2;text-align:center}.tl-media .tl-media-content-container .tl-media-content .tl-media-loaderror p span{color:#f2f2f2}.tl-media .tl-media-content-container .tl-media-content .tl-media-loaderror [class^=tl-icon-],.tl-media .tl-media-content-container .tl-media-content .tl-media-loaderror [class*=" tl-icon-"]{font-size:28px;color:#f2f2f2;text-align:center}.tl-media .tl-media-content-container .tl-media-content img,.tl-media .tl-media-content-container .tl-media-content embed,.tl-media .tl-media-content-container .tl-media-content object,.tl-media .tl-media-content-container .tl-media-content video{max-width:100%;max-height:100%}.tl-slide-media-only .tl-media .tl-media-content-container.tl-media-content-container-text{border-right:none;padding-right:0}.tl-media-shadow{position:relative;z-index:1;-webkit-box-shadow:0 12px 10px -10px rgba(0,0,0,.6);-moz-box-shadow:0 12px 10px -10px rgba(0,0,0,.6);box-shadow:0 12px 10px -10px rgba(0,0,0,.6)}.tl-slide.tl-full-image-background a,.tl-slide.tl-full-color-background a,.tl-slide.tl-full-image-background .vcard a,.tl-slide.tl-full-color-background .vcard a{text-shadow:1px 1px 1px #000;color:#fff}.tl-slide.tl-full-image-background a:hover,.tl-slide.tl-full-color-background a:hover{text-decoration:underline;color:#c34528}.tl-credit{color:#999;text-align:right;display:block;margin:0 auto;margin-top:6px;font-size:10px;line-height:13px}.tl-caption{text-align:left;margin-right:auto;margin-left:auto;margin-top:10px;color:#666;font-size:11px;line-height:14px;text-rendering:optimizeLegibility;word-wrap:break-word}.tl-full-image-background .tl-media-shadow:before,.tl-full-color-background .tl-media-shadow:before,.tl-full-image-background .tl-media-shadow:after,.tl-full-color-background .tl-media-shadow:after{background:0 0;-webkit-box-shadow:0 0 0 #000;-moz-box-shadow:0 0 0 #000;box-shadow:0 0 0 #000}.tl-skinny .tl-media{width:100%;height:auto;float:none;display:block}.tl-skinny .tl-media .tl-media-content-container.tl-media-content-container-text{border-right:0;padding-right:0}.tl-skinny .tl-media .tl-media-content-container .tl-credit,.tl-skinny .tl-media .tl-media-content-container .tl-caption{margin-top:2px;padding-left:10px;padding-right:10px;font-size:8px}.tl-skinny .tl-media .tl-media-content-container .tl-credit{margin-top:0}.tl-mobile.tl-skinny .tl-media{width:100%;height:auto;float:none;display:block}.tl-mobile.tl-skinny .tl-media .tl-media-content-container.tl-media-content-container-text{border-right:0;padding-right:0}.tl-text{width:50%;max-width:50%;min-width:120px;padding:0 20px;display:table-cell;vertical-align:middle;text-align:left;text-shadow:none;color:#737373}.tl-text p{color:#737373}.tl-text h2.tl-headline-title,.tl-text h2.tl-headline{margin-top:0}.tl-text .tl-headline-date,.tl-text h3.tl-headline-date{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:15px;line-height:15px;font-weight:400;margin:0 0 3px;color:#b3b3b3}.tl-text .tl-headline-date small,.tl-text h3.tl-headline-date small{font-size:15px;line-height:15px;font-weight:400;color:#b3b3b3}.tl-text .tl-text-date{display:inline-block;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-weight:400;margin-top:10px;font-size:12px;color:#b3b3b3}.tl-full-image-background .tl-text,.tl-full-color-background .tl-text,.tl-full-image-background .tl-text p,.tl-full-color-background .tl-text p{color:#f2f2f2!important;text-shadow:1px 1px 2px #000}.tl-full-image-background .tl-text .tl-headline-date,.tl-full-color-background .tl-text .tl-headline-date,.tl-full-image-background .tl-text p .tl-headline-date,.tl-full-color-background .tl-text p .tl-headline-date,.tl-full-image-background .tl-text h3.tl-headline-date,.tl-full-color-background .tl-text h3.tl-headline-date,.tl-full-image-background .tl-text p h3.tl-headline-date,.tl-full-color-background .tl-text p h3.tl-headline-date{color:#f2f2f2!important}.tl-full-image-background .tl-text .tl-headline-date small,.tl-full-color-background .tl-text .tl-headline-date small,.tl-full-image-background .tl-text p .tl-headline-date small,.tl-full-color-background .tl-text p .tl-headline-date small,.tl-full-image-background .tl-text h3.tl-headline-date small,.tl-full-color-background .tl-text h3.tl-headline-date small,.tl-full-image-background .tl-text p h3.tl-headline-date small,.tl-full-color-background .tl-text p h3.tl-headline-date small{color:#f2f2f2!important}.tl-full-image-background .tl-text a:hover,.tl-full-color-background .tl-text a:hover,.tl-full-image-background .tl-text p a:hover,.tl-full-color-background .tl-text p a:hover{text-decoration:underline;color:#c34528}.tl-skinny .tl-text{width:100%;max-width:100%;min-width:auto;float:none;margin-top:20px}.tl-skinny .tl-text h2.tl-headline-title,.tl-skinny .tl-text h2.tl-headline{font-size:32px;line-height:36px}.tl-medium .tl-text h2.tl-headline-title,.tl-medium .tl-text h2.tl-headline{font-size:32px;line-height:36px}.tl-mobile.tl-skinny .tl-media .tl-media-image{max-height:250px!important}.tl-media .tl-media-twitter{text-align:left;clear:both}.tl-media .tl-media-twitter blockquote{margin:0;margin-right:15px;font-size:15px;line-height:21px;color:#333}.tl-media .tl-media-twitter blockquote p{font-size:28px;line-height:30px;color:#000}.tl-media .tl-media-twitter blockquote p:before{display:none}.tl-media .tl-media-twitter blockquote p:after{display:none}.tl-media .tl-media-twitter .tl-icon-twitter{color:#55acee}.tl-media .tl-media-twitter .vcard a:hover,.tl-media .tl-media-twitter .vcard a.tl-date:hover{text-decoration:none;color:#55acee}.tl-media .tl-media-twitter .vcard a:hover .fn,.tl-media .tl-media-twitter .vcard a.tl-date:hover .fn,.tl-media .tl-media-twitter .vcard a:hover .nickname,.tl-media .tl-media-twitter .vcard a.tl-date:hover .nickname{color:#55acee}.tl-slide-media-only .tl-media .tl-media-twitter{width:80%;margin-left:auto;margin-right:auto}.tl-mobile.tl-skinny .tl-media .tl-media-twitter blockquote p{font-size:15px;line-height:21px}.tl-skinny .tl-media .tl-media-twitter{margin-left:10px;margin-right:10px}.tl-skinny .tl-media .tl-media-twitter blockquote p{font-size:24px;line-height:26px}.tl-media .tl-media-blockquote{text-align:left;clear:both}.tl-media .tl-media-blockquote blockquote{margin:0;margin-right:15px;text-align:left;font-size:28px;line-height:30px;color:#333}.tl-media .tl-media-blockquote blockquote p{font-size:28px;line-height:30px;color:#333}.tl-media .tl-media-blockquote blockquote p:before,.tl-media .tl-media-blockquote blockquote p:after{display:inline-block;font-size:36px}.tl-media .tl-media-blockquote blockquote p:before{content:open-quote;margin-right:5px}.tl-media .tl-media-blockquote blockquote p:after{content:close-quote;margin-left:3px}.tl-media .tl-media-blockquote blockquote cite{font-size:15px;line-height:21px;color:#999;text-align:right;margin-top:15px}.tl-slide-media-only .tl-media .tl-media-blockquote{border-right:0;width:80%;margin-left:auto;margin-right:auto}@media only screen and (max-width:800px),only screen and (max-device-width:800px){.tl-media .tl-media-blockquote blockquote p{font-size:24px;line-height:26px}}@media only screen and (max-width:500px),only screen and (max-device-width:500px){.tl-media .tl-media-blockquote blockquote p{font-size:15px;line-height:21px}}.tl-mobile.tl-skinny .tl-media .tl-media-instagram{max-height:250px!important}.tl-media .tl-media-profile{border-radius:50%}.tl-media .tl-media-iframe{width:100%;height:100%}.tl-media .tl-media-iframe iframe{width:100%;height:100%}.tl-media .tl-media-wikipedia{text-align:left;margin-left:auto;margin-right:auto;clear:both}.tl-media .tl-media-wikipedia .tl-icon-wikipedia{font-size:32px;margin-right:10px;float:left;padding-top:3px}.tl-media .tl-media-wikipedia .tl-wikipedia-pageimage{float:left;margin-right:10px;margin-bottom:5px;margin-top:5px}.tl-media .tl-media-wikipedia .tl-wikipedia-title{margin-left:60px;padding-left:10px;border-left:1px solid #e6e6e6;margin-bottom:10px}.tl-media .tl-media-wikipedia .tl-wikipedia-source{font-size:13px;line-height:15px;font-style:italic;margin-top:3px;display:block;color:rgba(0,0,0,.5)}.tl-media .tl-media-wikipedia h4{margin-top:0;margin-bottom:0}.tl-media .tl-media-wikipedia h4 a{color:#000;text-decoration:none}.tl-media .tl-media-wikipedia p{font-size:13px;line-height:19px}.tl-slide-media-only .tl-media .tl-media-wikipedia{border-right:0;border-top:1px solid #e6e6e6;width:80%;margin-left:auto;margin-right:auto;margin-top:25px;padding-top:25px}.tl-slide.tl-full-image-background .tl-media .tl-media-wikipedia,.tl-slide.tl-full-color-background .tl-media .tl-media-wikipedia{padding:20px;background:#000 transparent;background:rgba(0,0,0,.6);-webkit-border-radius:7px;-moz-border-radius:7px;border-radius:7px}.tl-slide.tl-full-image-background .tl-media .tl-media-wikipedia h4 a,.tl-slide.tl-full-color-background .tl-media .tl-media-wikipedia h4 a{color:#fff!important;text-shadow:1px 1px 1px #000!important}.tl-slide.tl-full-image-background .tl-media .tl-media-wikipedia a:hover,.tl-slide.tl-full-color-background .tl-media .tl-media-wikipedia a:hover{text-decoration:underline;color:#c34528}.tl-slide.tl-full-image-background .tl-media .tl-media-wikipedia .tl-wikipedia-title,.tl-slide.tl-full-color-background .tl-media .tl-media-wikipedia .tl-wikipedia-title{border-color:rgba(230,230,230,.25)}.tl-slide.tl-full-image-background .tl-media .tl-media-wikipedia .tl-wikipedia-source,.tl-slide.tl-full-color-background .tl-media .tl-media-wikipedia .tl-wikipedia-source{color:rgba(230,230,230,.85)}.tl-mobile.tl-skinny .tl-media .tl-media-wikipedia,.tl-skinny .tl-media .tl-media-wikipedia{margin-left:10px;margin-right:10px}.tl-media .tl-media-website{text-align:left;margin-left:auto;margin-right:auto;clear:both}.tl-media .tl-media-website .tl-media-website-description{font-size:16px;line-height:19px;font-style:italic;margin-bottom:10px;text-transform:uppercase}.tl-media .tl-media-website h4{margin-top:0;margin-bottom:0;line-height:1}.tl-media .tl-media-website h4 a{color:#000;text-decoration:none}.tl-media .tl-media-website p{font-size:13px;line-height:19px}.tl-media .tl-media-content-container .tl-media-content .tl-media-website img{float:right;max-width:120px;max-height:120px;margin:4px 0 0 15px}.tl-media .tl-media-content-container .tl-media-content .tl-media-website img.tl-media-website-icon{max-width:16px;max-height:16px;float:none;margin:0;margin-right:3px}.tl-slide.tl-full-image-background .tl-media .tl-media-website,.tl-slide.tl-full-color-background .tl-media .tl-media-website{padding:20px;background:#000 transparent;background:rgba(0,0,0,.6);-webkit-border-radius:7px;-moz-border-radius:7px;border-radius:7px}.tl-slide.tl-full-image-background .tl-media .tl-media-website h4 a,.tl-slide.tl-full-color-background .tl-media .tl-media-website h4 a{color:#fff!important;text-shadow:1px 1px 1px #000!important}.tl-slide.tl-full-image-background .tl-media .tl-media-website a:hover,.tl-slide.tl-full-color-background .tl-media .tl-media-website a:hover{text-decoration:underline;color:#c34528}.tl-mobile.tl-skinny .tl-media .tl-media-website{margin-left:10px;margin-right:10px}.tl-timeline{width:100%;height:100%;font-size:16px;line-height:normal;overflow:hidden;position:relative;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;-o-user-select:none;user-select:none;background-color:#fff;color:#737373;-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box}.tl-timeline.tl-timeline-embed{box-sizing:border-box;border-top:1px solid #ccc;border-bottom:1px solid #ccc;border-radius:0}.tl-timeline.tl-timeline-full-embed{box-sizing:border-box;border:1px solid #ccc;border-radius:8px}.tl-layout-portrait .tl-storyslider{-webkit-box-shadow:0 -3px 6px rgba(0,0,0,.2);-moz-box-shadow:0 -3px 6px rgba(0,0,0,.2);box-shadow:0 -3px 6px rgba(0,0,0,.2)}.tl-rtl .tl-text-content,.tl-rtl .tl-headline,.tl-rtl .tl-media-blockquote,.tl-rtl .tl-headline-date,.tl-rtl .tl-timeline blockquote p,.tl-rtl .tl-media-website,.tl-rtl .tl-media-wikipedia,.tl-rtl .tl-media .tl-media-blockquote blockquote,.tl-rtl .blockquote,.tl-rtl blockquote p,.tl-rtl .tl-text-content p{text-align:right;direction:rtl}.tl-rtl .tl-slide-media-only .tl-headline,.tl-rtl .tl-slide-media-only .tl-headline-date{text-align:center}.tl-rtl .tl-timemarker-text{margin-right:35px}.tl-rtl .tl-timemarker-content-container .tl-timemarker-content .tl-timemarker-media-container{float:right}.tl-rtl .tl-caption{text-align:right}.tl-rtl .tl-credit{text-align:left} \ No newline at end of file
diff --git a/www/wiki/extensions/ModernTimeline/resources/vendor/timeline.js b/www/wiki/extensions/ModernTimeline/resources/vendor/timeline.js
new file mode 100644
index 00000000..5b06ea5c
--- /dev/null
+++ b/www/wiki/extensions/ModernTimeline/resources/vendor/timeline.js
@@ -0,0 +1,14042 @@
+/*
+ TimelineJS - ver. 3.6.5 - 2019-05-02
+ Copyright (c) 2012-2016 Northwestern University
+ a project of the Northwestern University Knight Lab, originally created by Zach Wise
+ https://github.com/NUKnightLab/TimelineJS3
+ This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
+ If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
+*/
+
+/* **********************************************
+ Begin TL.js
+********************************************** */
+
+/*!
+ TL
+*/
+
+(function (root) {
+ root.TL = {
+ VERSION: '0.1',
+ _originalL: root.TL
+ };
+}(this));
+
+/* TL.Debug
+ Debug mode
+================================================== */
+TL.debug = false;
+
+
+
+/* TL.Bind
+================================================== */
+TL.Bind = function (/*Function*/ fn, /*Object*/ obj) /*-> Object*/ {
+ return function () {
+ return fn.apply(obj, arguments);
+ };
+};
+
+
+
+/* Trace (console.log)
+================================================== */
+trace = function( msg ) {
+ if (TL.debug) {
+ if (window.console) {
+ console.log(msg);
+ } else if ( typeof( jsTrace ) != 'undefined' ) {
+ jsTrace.send( msg );
+ } else {
+ //alert(msg);
+ }
+ }
+}
+
+
+/* **********************************************
+ Begin TL.Error.js
+********************************************** */
+
+/* Timeline Error class */
+
+function TL_Error(message_key, detail) {
+ this.name = 'TL.Error';
+ this.message = message_key || 'error';
+ this.message_key = this.message;
+ this.detail = detail || '';
+
+ // Grab stack?
+ var e = new Error();
+ if(e.hasOwnProperty('stack')) {
+ this.stack = e.stack;
+ }
+}
+
+TL_Error.prototype = Object.create(Error.prototype);
+TL_Error.prototype.constructor = TL_Error;
+
+TL.Error = TL_Error;
+
+
+/* **********************************************
+ Begin TL.Util.js
+********************************************** */
+
+/* TL.Util
+ Class of utilities
+================================================== */
+
+TL.Util = {
+ mergeData: function(data_main, data_to_merge) {
+ var x;
+ for (x in data_to_merge) {
+ if (Object.prototype.hasOwnProperty.call(data_to_merge, x)) {
+ data_main[x] = data_to_merge[x];
+ }
+ }
+ return data_main;
+ },
+
+ // like TL.Util.mergeData but takes an arbitrarily long list of sources to merge.
+ extend: function (/*Object*/ dest) /*-> Object*/ { // merge src properties into dest
+ var sources = Array.prototype.slice.call(arguments, 1);
+ for (var j = 0, len = sources.length, src; j < len; j++) {
+ src = sources[j] || {};
+ TL.Util.mergeData(dest, src);
+ }
+ return dest;
+ },
+
+ isEven: function(n) {
+ return n == parseFloat(n)? !(n%2) : void 0;
+ },
+
+ isTrue: function(s) {
+ if (s == null) return false;
+ return s == true || String(s).toLowerCase() == 'true' || Number(s) == 1;
+ },
+
+ findArrayNumberByUniqueID: function(id, array, prop, defaultVal) {
+ var _n = defaultVal || 0;
+
+ for (var i = 0; i < array.length; i++) {
+ if (array[i].data[prop] == id) {
+ _n = i;
+ }
+ };
+
+ return _n;
+ },
+
+ convertUnixTime: function(str) {
+ var _date, _months, _year, _month, _day, _time, _date_array = [],
+ _date_str = {
+ ymd:"",
+ time:"",
+ time_array:[],
+ date_array:[],
+ full_array:[]
+ };
+
+ _date_str.ymd = str.split(" ")[0];
+ _date_str.time = str.split(" ")[1];
+ _date_str.date_array = _date_str.ymd.split("-");
+ _date_str.time_array = _date_str.time.split(":");
+ _date_str.full_array = _date_str.date_array.concat(_date_str.time_array)
+
+ for(var i = 0; i < _date_str.full_array.length; i++) {
+ _date_array.push( parseInt(_date_str.full_array[i]) )
+ }
+
+ _date = new Date(_date_array[0], _date_array[1], _date_array[2], _date_array[3], _date_array[4], _date_array[5]);
+ _months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
+ _year = _date.getFullYear();
+ _month = _months[_date.getMonth()];
+ _day = _date.getDate();
+ _time = _month + ', ' + _day + ' ' + _year;
+
+ return _time;
+ },
+
+ setData: function (obj, data) {
+ obj.data = TL.Util.extend({}, obj.data, data);
+ if (obj.data.unique_id === "") {
+ obj.data.unique_id = TL.Util.unique_ID(6);
+ }
+ },
+
+ stamp: (function () {
+ var lastId = 0, key = '_tl_id';
+
+
+ return function (/*Object*/ obj) {
+ obj[key] = obj[key] || ++lastId;
+ return obj[key];
+ };
+ }()),
+
+ isArray: (function () {
+ // Use compiler's own isArray when available
+ if (Array.isArray) {
+ return Array.isArray;
+ }
+
+ // Retain references to variables for performance
+ // optimization
+ var objectToStringFn = Object.prototype.toString,
+ arrayToStringResult = objectToStringFn.call([]);
+
+ return function (subject) {
+ return objectToStringFn.call(subject) === arrayToStringResult;
+ };
+ }()),
+
+ getRandomNumber: function(range) {
+ return Math.floor(Math.random() * range);
+ },
+
+ unique_ID: function(size, prefix) {
+
+ var getRandomNumber = function(range) {
+ return Math.floor(Math.random() * range);
+ };
+
+ var getRandomChar = function() {
+ var chars = "abcdefghijklmnopqurstuvwxyz";
+ return chars.substr( getRandomNumber(32), 1 );
+ };
+
+ var randomID = function(size) {
+ var str = "";
+ for(var i = 0; i < size; i++) {
+ str += getRandomChar();
+ }
+ return str;
+ };
+
+ if (prefix) {
+ return prefix + "-" + randomID(size);
+ } else {
+ return "tl-" + randomID(size);
+ }
+ },
+
+ ensureUniqueKey: function(obj, candidate) {
+ if (!candidate) { candidate = TL.Util.unique_ID(6); }
+
+ if (!(candidate in obj)) { return candidate; }
+
+ var root = candidate.match(/^(.+)(-\d+)?$/)[1];
+ var similar_ids = [];
+ // get an alternative
+ for (key in obj) {
+ if (key.match(/^(.+?)(-\d+)?$/)[1] == root) {
+ similar_ids.push(key);
+ }
+ }
+ candidate = root + "-" + (similar_ids.length + 1);
+
+ for (var counter = similar_ids.length; similar_ids.indexOf(candidate) != -1; counter++) {
+ candidate = root + '-' + counter;
+ }
+
+ return candidate;
+ },
+
+
+ htmlify: function(str) {
+ //if (str.match(/<\s*p[^>]*>([^<]*)<\s*\/\s*p\s*>/)) {
+ if (str.match(/<p>[\s\S]*?<\/p>/)) {
+
+ return str;
+ } else {
+ return "<p>" + str + "</p>";
+ }
+ },
+
+ unhtmlify: function(str) {
+ str = str.replace(/(<[^>]*>)+/g, '');
+ return str.replace('"', "'");
+ },
+
+ /* * Turns plain text links into real links
+ ================================================== */
+ linkify: function(text,targets,is_touch) {
+
+ var make_link = function(url, link_text, prefix) {
+ if (!prefix) {
+ prefix = "";
+ }
+ var MAX_LINK_TEXT_LENGTH = 30;
+ if (link_text && link_text.length > MAX_LINK_TEXT_LENGTH) {
+ link_text = link_text.substring(0,MAX_LINK_TEXT_LENGTH) + "\u2026"; // unicode ellipsis
+ }
+ return prefix + "<a class='tl-makelink' href='" + url + "' onclick='void(0)'>" + link_text + "</a>";
+ }
+ // http://, https://, ftp://
+ var urlPattern = /\b(?:https?|ftp):\/\/([a-z0-9-+&@#\/%?=~_|!:,.;]*[a-z0-9-+&@#\/%=~_|])/gim;
+
+ // www. sans http:// or https://
+ var pseudoUrlPattern = /(^|[^\/>])(www\.[\S]+(\b|$))/gim;
+
+ // Email addresses
+ var emailAddressPattern = /([a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+)/gim;
+
+
+ return text
+ .replace(urlPattern, function(match, url_sans_protocol, offset, string) {
+ // Javascript doesn't support negative lookbehind assertions, so
+ // we need to handle risk of matching URLs in legit hrefs
+ if (offset > 0) {
+ var prechar = string[offset-1];
+ if (prechar == '"' || prechar == "'" || prechar == "=") {
+ return match;
+ }
+ }
+ return make_link(match, url_sans_protocol);
+ })
+ .replace(pseudoUrlPattern, function(match, beforePseudo, pseudoUrl, offset, string) {
+ return make_link('http://' + pseudoUrl, pseudoUrl, beforePseudo);
+ })
+ .replace(emailAddressPattern, function(match, email, offset, string) {
+ return make_link('mailto:' + email, email);
+ });
+ },
+
+ unlinkify: function(text) {
+ if(!text) return text;
+ text = text.replace(/<a\b[^>]*>/i,"");
+ text = text.replace(/<\/a>/i, "");
+ return text;
+ },
+
+ getParamString: function (obj) {
+ var params = [];
+ for (var i in obj) {
+ if (obj.hasOwnProperty(i)) {
+ params.push(i + '=' + obj[i]);
+ }
+ }
+ return '?' + params.join('&');
+ },
+
+ formatNum: function (num, digits) {
+ var pow = Math.pow(10, digits || 5);
+ return Math.round(num * pow) / pow;
+ },
+
+ falseFn: function () {
+ return false;
+ },
+
+ requestAnimFrame: (function () {
+ function timeoutDefer(callback) {
+ window.setTimeout(callback, 1000 / 60);
+ }
+
+ var requestFn = window.requestAnimationFrame ||
+ window.webkitRequestAnimationFrame ||
+ window.mozRequestAnimationFrame ||
+ window.oRequestAnimationFrame ||
+ window.msRequestAnimationFrame ||
+ timeoutDefer;
+
+ return function (callback, context, immediate, contextEl) {
+ callback = context ? TL.Util.bind(callback, context) : callback;
+ if (immediate && requestFn === timeoutDefer) {
+ callback();
+ } else {
+ requestFn(callback, contextEl);
+ }
+ };
+ }()),
+
+ bind: function (/*Function*/ fn, /*Object*/ obj) /*-> Object*/ {
+ return function () {
+ return fn.apply(obj, arguments);
+ };
+ },
+
+ template: function (str, data) {
+ return str.replace(/\{ *([\w_]+) *\}/g, function (str, key) {
+ var value = data[key];
+ if (!data.hasOwnProperty(key)) {
+ throw new TL.Error("template_value_err", str);
+ }
+ return value;
+ });
+ },
+
+ hexToRgb: function(hex) {
+ // Expand shorthand form (e.g. "03F") to full form (e.g. "0033FF")
+ if (TL.Util.css_named_colors[hex.toLowerCase()]) {
+ hex = TL.Util.css_named_colors[hex.toLowerCase()];
+ }
+ var shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
+ hex = hex.replace(shorthandRegex, function(m, r, g, b) {
+ return r + r + g + g + b + b;
+ });
+
+ var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
+ return result ? {
+ r: parseInt(result[1], 16),
+ g: parseInt(result[2], 16),
+ b: parseInt(result[3], 16)
+ } : null;
+ },
+ // given an object with r, g, and b keys, or a string of the form 'rgb(mm,nn,ll)', return a CSS hex string including the leading '#' character
+ rgbToHex: function(rgb) {
+ var r,g,b;
+ if (typeof(rgb) == 'object') {
+ r = rgb.r;
+ g = rgb.g;
+ b = rgb.b;
+ } else if (typeof(rgb.match) == 'function'){
+ var parts = rgb.match(/^rgb\((\d+),(\d+),(\d+)\)$/);
+ if (parts) {
+ r = parts[1];
+ g = parts[2];
+ b = parts[3];
+ }
+ }
+ if (isNaN(r) || isNaN(b) || isNaN(g)) {
+ throw new TL.Error("invalid_rgb_err");
+ }
+ return "#" + TL.Util.intToHexString(r) + TL.Util.intToHexString(g) + TL.Util.intToHexString(b);
+ },
+ colorObjToHex: function(o) {
+ var parts = [o.r, o.g, o.b];
+ return TL.Util.rgbToHex("rgb(" + parts.join(',') + ")")
+ },
+ css_named_colors: {
+ "aliceblue": "#f0f8ff",
+ "antiquewhite": "#faebd7",
+ "aqua": "#00ffff",
+ "aquamarine": "#7fffd4",
+ "azure": "#f0ffff",
+ "beige": "#f5f5dc",
+ "bisque": "#ffe4c4",
+ "black": "#000000",
+ "blanchedalmond": "#ffebcd",
+ "blue": "#0000ff",
+ "blueviolet": "#8a2be2",
+ "brown": "#a52a2a",
+ "burlywood": "#deb887",
+ "cadetblue": "#5f9ea0",
+ "chartreuse": "#7fff00",
+ "chocolate": "#d2691e",
+ "coral": "#ff7f50",
+ "cornflowerblue": "#6495ed",
+ "cornsilk": "#fff8dc",
+ "crimson": "#dc143c",
+ "cyan": "#00ffff",
+ "darkblue": "#00008b",
+ "darkcyan": "#008b8b",
+ "darkgoldenrod": "#b8860b",
+ "darkgray": "#a9a9a9",
+ "darkgreen": "#006400",
+ "darkkhaki": "#bdb76b",
+ "darkmagenta": "#8b008b",
+ "darkolivegreen": "#556b2f",
+ "darkorange": "#ff8c00",
+ "darkorchid": "#9932cc",
+ "darkred": "#8b0000",
+ "darksalmon": "#e9967a",
+ "darkseagreen": "#8fbc8f",
+ "darkslateblue": "#483d8b",
+ "darkslategray": "#2f4f4f",
+ "darkturquoise": "#00ced1",
+ "darkviolet": "#9400d3",
+ "deeppink": "#ff1493",
+ "deepskyblue": "#00bfff",
+ "dimgray": "#696969",
+ "dodgerblue": "#1e90ff",
+ "firebrick": "#b22222",
+ "floralwhite": "#fffaf0",
+ "forestgreen": "#228b22",
+ "fuchsia": "#ff00ff",
+ "gainsboro": "#dcdcdc",
+ "ghostwhite": "#f8f8ff",
+ "gold": "#ffd700",
+ "goldenrod": "#daa520",
+ "gray": "#808080",
+ "green": "#008000",
+ "greenyellow": "#adff2f",
+ "honeydew": "#f0fff0",
+ "hotpink": "#ff69b4",
+ "indianred": "#cd5c5c",
+ "indigo": "#4b0082",
+ "ivory": "#fffff0",
+ "khaki": "#f0e68c",
+ "lavender": "#e6e6fa",
+ "lavenderblush": "#fff0f5",
+ "lawngreen": "#7cfc00",
+ "lemonchiffon": "#fffacd",
+ "lightblue": "#add8e6",
+ "lightcoral": "#f08080",
+ "lightcyan": "#e0ffff",
+ "lightgoldenrodyellow": "#fafad2",
+ "lightgray": "#d3d3d3",
+ "lightgreen": "#90ee90",
+ "lightpink": "#ffb6c1",
+ "lightsalmon": "#ffa07a",
+ "lightseagreen": "#20b2aa",
+ "lightskyblue": "#87cefa",
+ "lightslategray": "#778899",
+ "lightsteelblue": "#b0c4de",
+ "lightyellow": "#ffffe0",
+ "lime": "#00ff00",
+ "limegreen": "#32cd32",
+ "linen": "#faf0e6",
+ "magenta": "#ff00ff",
+ "maroon": "#800000",
+ "mediumaquamarine": "#66cdaa",
+ "mediumblue": "#0000cd",
+ "mediumorchid": "#ba55d3",
+ "mediumpurple": "#9370db",
+ "mediumseagreen": "#3cb371",
+ "mediumslateblue": "#7b68ee",
+ "mediumspringgreen": "#00fa9a",
+ "mediumturquoise": "#48d1cc",
+ "mediumvioletred": "#c71585",
+ "midnightblue": "#191970",
+ "mintcream": "#f5fffa",
+ "mistyrose": "#ffe4e1",
+ "moccasin": "#ffe4b5",
+ "navajowhite": "#ffdead",
+ "navy": "#000080",
+ "oldlace": "#fdf5e6",
+ "olive": "#808000",
+ "olivedrab": "#6b8e23",
+ "orange": "#ffa500",
+ "orangered": "#ff4500",
+ "orchid": "#da70d6",
+ "palegoldenrod": "#eee8aa",
+ "palegreen": "#98fb98",
+ "paleturquoise": "#afeeee",
+ "palevioletred": "#db7093",
+ "papayawhip": "#ffefd5",
+ "peachpuff": "#ffdab9",
+ "peru": "#cd853f",
+ "pink": "#ffc0cb",
+ "plum": "#dda0dd",
+ "powderblue": "#b0e0e6",
+ "purple": "#800080",
+ "rebeccapurple": "#663399",
+ "red": "#ff0000",
+ "rosybrown": "#bc8f8f",
+ "royalblue": "#4169e1",
+ "saddlebrown": "#8b4513",
+ "salmon": "#fa8072",
+ "sandybrown": "#f4a460",
+ "seagreen": "#2e8b57",
+ "seashell": "#fff5ee",
+ "sienna": "#a0522d",
+ "silver": "#c0c0c0",
+ "skyblue": "#87ceeb",
+ "slateblue": "#6a5acd",
+ "slategray": "#708090",
+ "snow": "#fffafa",
+ "springgreen": "#00ff7f",
+ "steelblue": "#4682b4",
+ "tan": "#d2b48c",
+ "teal": "#008080",
+ "thistle": "#d8bfd8",
+ "tomato": "#ff6347",
+ "turquoise": "#40e0d0",
+ "violet": "#ee82ee",
+ "wheat": "#f5deb3",
+ "white": "#ffffff",
+ "whitesmoke": "#f5f5f5",
+ "yellow": "#ffff00",
+ "yellowgreen": "#9acd32"
+ },
+ ratio: {
+ square: function(size) {
+ var s = {
+ w: 0,
+ h: 0
+ }
+ if (size.w > size.h && size.h > 0) {
+ s.h = size.h;
+ s.w = size.h;
+ } else {
+ s.w = size.w;
+ s.h = size.w;
+ }
+ return s;
+ },
+
+ r16_9: function(size) {
+ if (size.w !== null && size.w !== "") {
+ return Math.round((size.w / 16) * 9);
+ } else if (size.h !== null && size.h !== "") {
+ return Math.round((size.h / 9) * 16);
+ } else {
+ return 0;
+ }
+ },
+ r4_3: function(size) {
+ if (size.w !== null && size.w !== "") {
+ return Math.round((size.w / 4) * 3);
+ } else if (size.h !== null && size.h !== "") {
+ return Math.round((size.h / 3) * 4);
+ }
+ }
+ },
+ getObjectAttributeByIndex: function(obj, index) {
+ if(typeof obj != 'undefined') {
+ var i = 0;
+ for (var attr in obj){
+ if (index === i){
+ return obj[attr];
+ }
+ i++;
+ }
+ return "";
+ } else {
+ return "";
+ }
+
+ },
+ getUrlVars: function(string) {
+ var str,
+ vars = [],
+ hash,
+ hashes;
+
+ str = string.toString();
+
+ if (str.match('&#038;')) {
+ str = str.replace("&#038;", "&");
+ } else if (str.match('&#38;')) {
+ str = str.replace("&#38;", "&");
+ } else if (str.match('&amp;')) {
+ str = str.replace("&amp;", "&");
+ }
+
+ hashes = str.slice(str.indexOf('?') + 1).split('&');
+
+ for(var i = 0; i < hashes.length; i++) {
+ hash = hashes[i].split('=');
+ vars.push(hash[0]);
+ vars[hash[0]] = hash[1];
+ }
+
+
+ return vars;
+ },
+ /**
+ * Remove any leading or trailing whitespace from the given string.
+ * If `str` is undefined or does not have a `replace` function, return
+ * an empty string.
+ */
+ trim: function(str) {
+ if (str && typeof(str.replace) == 'function') {
+ return str.replace(/^\s+|\s+$/g, '');
+ }
+ return "";
+ },
+
+ slugify: function(str) {
+ // borrowed from http://stackoverflow.com/a/5782563/102476
+ str = TL.Util.trim(str);
+ str = str.toLowerCase();
+
+ // remove accents, swap ñ for n, etc
+ var from = "ãà áäâẽèéëêìíïîõòóöôùúüûñç·/_,:;";
+ var to = "aaaaaeeeeeiiiiooooouuuunc------";
+ for (var i=0, l=from.length ; i<l ; i++) {
+ str = str.replace(new RegExp(from.charAt(i), 'g'), to.charAt(i));
+ }
+
+ str = str.replace(/[^a-z0-9 -]/g, '') // remove invalid chars
+ .replace(/\s+/g, '-') // collapse whitespace and replace by -
+ .replace(/-+/g, '-'); // collapse dashes
+
+ str = str.replace(/^([0-9])/,'_$1');
+ return str;
+ },
+ maxDepth: function(ary) {
+ // given a sorted array of 2-tuples of numbers, count how many "deep" the items are.
+ // that is, what is the maximum number of tuples that occupy any one moment
+ // each tuple should also be sorted
+ var stack = [];
+ var max_depth = 0;
+ for (var i = 0; i < ary.length; i++) {
+
+ stack.push(ary[i]);
+ if (stack.length > 1) {
+ var top = stack[stack.length - 1]
+ var bottom_idx = -1;
+ for (var j = 0; j < stack.length - 1; j++) {
+ if (stack[j][1] < top[0]) {
+ bottom_idx = j;
+ }
+ };
+ if (bottom_idx >= 0) {
+ stack = stack.slice(bottom_idx + 1);
+ }
+
+ }
+
+ if (stack.length > max_depth) {
+ max_depth = stack.length;
+ }
+ };
+ return max_depth;
+ },
+
+ pad: function (val, len) {
+ val = String(val);
+ len = len || 2;
+ while (val.length < len) val = "0" + val;
+ return val;
+ },
+ intToHexString: function(i) {
+ return TL.Util.pad(parseInt(i,10).toString(16));
+ },
+ findNextGreater: function(list, current, default_value) {
+ // given a sorted list and a current value which *might* be in the list,
+ // return the next greatest value if the current value is >= the last item in the list, return default,
+ // or if default is undefined, return input value
+ for (var i = 0; i < list.length; i++) {
+ if (current < list[i]) {
+ return list[i];
+ }
+ }
+
+ return (default_value) ? default_value : current;
+ },
+
+ findNextLesser: function(list, current, default_value) {
+ // given a sorted list and a current value which *might* be in the list,
+ // return the next lesser value if the current value is <= the last item in the list, return default,
+ // or if default is undefined, return input value
+ for (var i = list.length - 1; i >= 0; i--) {
+ if (current > list[i]) {
+ return list[i];
+ }
+ }
+
+ return (default_value) ? default_value : current;
+ },
+
+ isEmptyObject: function(o) {
+ var properties = []
+ if (Object.keys) {
+ properties = Object.keys(o);
+ } else { // all this to support IE 8
+ for (var p in o) if (Object.prototype.hasOwnProperty.call(o,p)) properties.push(p);
+ }
+ for (var i = 0; i < properties.length; i++) {
+ var k = properties[i];
+ if (o[k] != null && typeof o[k] != "string") return false;
+ if (TL.Util.trim(o[k]).length != 0) return false;
+ }
+ return true;
+ },
+ parseYouTubeTime: function(s) {
+ // given a YouTube start time string in a reasonable format, reduce it to a number of seconds as an integer.
+ if (typeof(s) == 'string') {
+ parts = s.match(/^\s*(\d+h)?(\d+m)?(\d+s)?\s*/i);
+ if (parts) {
+ var hours = parseInt(parts[1]) || 0;
+ var minutes = parseInt(parts[2]) || 0;
+ var seconds = parseInt(parts[3]) || 0;
+ return seconds + (minutes * 60) + (hours * 60 * 60);
+ }
+ } else if (typeof(s) == 'number') {
+ return s;
+ }
+ return 0;
+ },
+ /**
+ * Try to make seamless the process of interpreting a URL to a web page which embeds an image for sharing purposes
+ * as a direct image link. Some services have predictable transformations we can use rather than explain to people
+ * this subtlety.
+ */
+ transformImageURL: function(url) {
+ return url.replace(/(.*)www.dropbox.com\/(.*)/, '$1dl.dropboxusercontent.com/$2')
+ },
+
+ base58: (function(alpha) {
+ var alphabet = alpha || '123456789abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ',
+ base = alphabet.length;
+ return {
+ encode: function(enc) {
+ if(typeof enc!=='number' || enc !== parseInt(enc))
+ throw '"encode" only accepts integers.';
+ var encoded = '';
+ while(enc) {
+ var remainder = enc % base;
+ enc = Math.floor(enc / base);
+ encoded = alphabet[remainder].toString() + encoded;
+ }
+ return encoded;
+ },
+ decode: function(dec) {
+ if(typeof dec!=='string')
+ throw '"decode" only accepts strings.';
+ var decoded = 0;
+ while(dec) {
+ var alphabetPosition = alphabet.indexOf(dec[0]);
+ if (alphabetPosition < 0)
+ throw '"decode" can\'t find "' + dec[0] + '" in the alphabet: "' + alphabet + '"';
+ var powerOf = dec.length - 1;
+ decoded += alphabetPosition * (Math.pow(base, powerOf));
+ dec = dec.substring(1);
+ }
+ return decoded;
+ }
+ };
+ })()
+
+};
+
+
+/* **********************************************
+ Begin TL.Data.js
+********************************************** */
+
+// Expects TL to be visible in scope
+
+;(function(TL){
+ /* Zepto v1.1.2-15-g59d3fe5 - zepto event ajax form ie - zeptojs.com/license */
+
+ var Zepto = (function() {
+ var undefined, key, $, classList, emptyArray = [], slice = emptyArray.slice, filter = emptyArray.filter,
+ document = window.document,
+ elementDisplay = {}, classCache = {},
+ cssNumber = { 'column-count': 1, 'columns': 1, 'font-weight': 1, 'line-height': 1,'opacity': 1, 'z-index': 1, 'zoom': 1 },
+ fragmentRE = /^\s*<(\w+|!)[^>]*>/,
+ singleTagRE = /^<(\w+)\s*\/?>(?:<\/\1>|)$/,
+ tagExpanderRE = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig,
+ rootNodeRE = /^(?:body|html)$/i,
+ capitalRE = /([A-Z])/g,
+
+ // special attributes that should be get/set via method calls
+ methodAttributes = ['val', 'css', 'html', 'text', 'data', 'width', 'height', 'offset'],
+
+ adjacencyOperators = [ 'after', 'prepend', 'before', 'append' ],
+ table = document.createElement('table'),
+ tableRow = document.createElement('tr'),
+ containers = {
+ 'tr': document.createElement('tbody'),
+ 'tbody': table, 'thead': table, 'tfoot': table,
+ 'td': tableRow, 'th': tableRow,
+ '*': document.createElement('div')
+ },
+ readyRE = /complete|loaded|interactive/,
+ classSelectorRE = /^\.([\w-]+)$/,
+ idSelectorRE = /^#([\w-]*)$/,
+ simpleSelectorRE = /^[\w-]*$/,
+ class2type = {},
+ toString = class2type.toString,
+ zepto = {},
+ camelize, uniq,
+ tempParent = document.createElement('div'),
+ propMap = {
+ 'tabindex': 'tabIndex',
+ 'readonly': 'readOnly',
+ 'for': 'htmlFor',
+ 'class': 'className',
+ 'maxlength': 'maxLength',
+ 'cellspacing': 'cellSpacing',
+ 'cellpadding': 'cellPadding',
+ 'rowspan': 'rowSpan',
+ 'colspan': 'colSpan',
+ 'usemap': 'useMap',
+ 'frameborder': 'frameBorder',
+ 'contenteditable': 'contentEditable'
+ },
+ isArray = Array.isArray ||
+ function(object){ return object instanceof Array }
+
+ zepto.matches = function(element, selector) {
+ if (!selector || !element || element.nodeType !== 1) return false
+ var matchesSelector = element.webkitMatchesSelector || element.mozMatchesSelector ||
+ element.oMatchesSelector || element.matchesSelector
+ if (matchesSelector) return matchesSelector.call(element, selector)
+ // fall back to performing a selector:
+ var match, parent = element.parentNode, temp = !parent
+ if (temp) (parent = tempParent).appendChild(element)
+ match = ~zepto.qsa(parent, selector).indexOf(element)
+ temp && tempParent.removeChild(element)
+ return match
+ }
+
+ function type(obj) {
+ return obj == null ? String(obj) :
+ class2type[toString.call(obj)] || "object"
+ }
+
+ function isFunction(value) { return type(value) == "function" }
+ function isWindow(obj) { return obj != null && obj == obj.window }
+ function isDocument(obj) { return obj != null && obj.nodeType == obj.DOCUMENT_NODE }
+ function isObject(obj) { return type(obj) == "object" }
+ function isPlainObject(obj) {
+ return isObject(obj) && !isWindow(obj) && Object.getPrototypeOf(obj) == Object.prototype
+ }
+ function likeArray(obj) { return typeof obj.length == 'number' }
+
+ function compact(array) { return filter.call(array, function(item){ return item != null }) }
+ function flatten(array) { return array.length > 0 ? $.fn.concat.apply([], array) : array }
+ camelize = function(str){ return str.replace(/-+(.)?/g, function(match, chr){ return chr ? chr.toUpperCase() : '' }) }
+ function dasherize(str) {
+ return str.replace(/::/g, '/')
+ .replace(/([A-Z]+)([A-Z][a-z])/g, '$1_$2')
+ .replace(/([a-z\d])([A-Z])/g, '$1_$2')
+ .replace(/_/g, '-')
+ .toLowerCase()
+ }
+ uniq = function(array){ return filter.call(array, function(item, idx){ return array.indexOf(item) == idx }) }
+
+ function classRE(name) {
+ return name in classCache ?
+ classCache[name] : (classCache[name] = new RegExp('(^|\\s)' + name + '(\\s|$)'))
+ }
+
+ function maybeAddPx(name, value) {
+ return (typeof value == "number" && !cssNumber[dasherize(name)]) ? value + "px" : value
+ }
+
+ function defaultDisplay(nodeName) {
+ var element, display
+ if (!elementDisplay[nodeName]) {
+ element = document.createElement(nodeName)
+ document.body.appendChild(element)
+ display = getComputedStyle(element, '').getPropertyValue("display")
+ element.parentNode.removeChild(element)
+ display == "none" && (display = "block")
+ elementDisplay[nodeName] = display
+ }
+ return elementDisplay[nodeName]
+ }
+
+ function children(element) {
+ return 'children' in element ?
+ slice.call(element.children) :
+ $.map(element.childNodes, function(node){ if (node.nodeType == 1) return node })
+ }
+
+ // `$.zepto.fragment` takes a html string and an optional tag name
+ // to generate DOM nodes nodes from the given html string.
+ // The generated DOM nodes are returned as an array.
+ // This function can be overriden in plugins for example to make
+ // it compatible with browsers that don't support the DOM fully.
+ zepto.fragment = function(html, name, properties) {
+ var dom, nodes, container
+
+ // A special case optimization for a single tag
+ if (singleTagRE.test(html)) dom = $(document.createElement(RegExp.$1))
+
+ if (!dom) {
+ if (html.replace) html = html.replace(tagExpanderRE, "<$1></$2>")
+ if (name === undefined) name = fragmentRE.test(html) && RegExp.$1
+ if (!(name in containers)) name = '*'
+
+ container = containers[name]
+ container.innerHTML = '' + html
+ dom = $.each(slice.call(container.childNodes), function(){
+ container.removeChild(this)
+ })
+ }
+
+ if (isPlainObject(properties)) {
+ nodes = $(dom)
+ $.each(properties, function(key, value) {
+ if (methodAttributes.indexOf(key) > -1) nodes[key](value)
+ else nodes.attr(key, value)
+ })
+ }
+
+ return dom
+ }
+
+ // `$.zepto.Z` swaps out the prototype of the given `dom` array
+ // of nodes with `$.fn` and thus supplying all the Zepto functions
+ // to the array. Note that `__proto__` is not supported on Internet
+ // Explorer. This method can be overriden in plugins.
+ zepto.Z = function(dom, selector) {
+ dom = dom || []
+ dom.__proto__ = $.fn
+ dom.selector = selector || ''
+ return dom
+ }
+
+ // `$.zepto.isZ` should return `true` if the given object is a Zepto
+ // collection. This method can be overriden in plugins.
+ zepto.isZ = function(object) {
+ return object instanceof zepto.Z
+ }
+
+ // `$.zepto.init` is Zepto's counterpart to jQuery's `$.fn.init` and
+ // takes a CSS selector and an optional context (and handles various
+ // special cases).
+ // This method can be overriden in plugins.
+ zepto.init = function(selector, context) {
+ var dom
+ // If nothing given, return an empty Zepto collection
+ if (!selector) return zepto.Z()
+ // Optimize for string selectors
+ else if (typeof selector == 'string') {
+ selector = selector.trim()
+ // If it's a html fragment, create nodes from it
+ // Note: In both Chrome 21 and Firefox 15, DOM error 12
+ // is thrown if the fragment doesn't begin with <
+ if (selector[0] == '<' && fragmentRE.test(selector))
+ dom = zepto.fragment(selector, RegExp.$1, context), selector = null
+ // If there's a context, create a collection on that context first, and select
+ // nodes from there
+ else if (context !== undefined) return $(context).find(selector)
+ // If it's a CSS selector, use it to select nodes.
+ else dom = zepto.qsa(document, selector)
+ }
+ // If a function is given, call it when the DOM is ready
+ else if (isFunction(selector)) return $(document).ready(selector)
+ // If a Zepto collection is given, just return it
+ else if (zepto.isZ(selector)) return selector
+ else {
+ // normalize array if an array of nodes is given
+ if (isArray(selector)) dom = compact(selector)
+ // Wrap DOM nodes.
+ else if (isObject(selector))
+ dom = [selector], selector = null
+ // If it's a html fragment, create nodes from it
+ else if (fragmentRE.test(selector))
+ dom = zepto.fragment(selector.trim(), RegExp.$1, context), selector = null
+ // If there's a context, create a collection on that context first, and select
+ // nodes from there
+ else if (context !== undefined) return $(context).find(selector)
+ // And last but no least, if it's a CSS selector, use it to select nodes.
+ else dom = zepto.qsa(document, selector)
+ }
+ // create a new Zepto collection from the nodes found
+ return zepto.Z(dom, selector)
+ }
+
+ // `$` will be the base `Zepto` object. When calling this
+ // function just call `$.zepto.init, which makes the implementation
+ // details of selecting nodes and creating Zepto collections
+ // patchable in plugins.
+ $ = function(selector, context){
+ return zepto.init(selector, context)
+ }
+
+ function extend(target, source, deep) {
+ for (key in source)
+ if (deep && (isPlainObject(source[key]) || isArray(source[key]))) {
+ if (isPlainObject(source[key]) && !isPlainObject(target[key]))
+ target[key] = {}
+ if (isArray(source[key]) && !isArray(target[key]))
+ target[key] = []
+ extend(target[key], source[key], deep)
+ }
+ else if (source[key] !== undefined) target[key] = source[key]
+ }
+
+ // Copy all but undefined properties from one or more
+ // objects to the `target` object.
+ $.extend = function(target){
+ var deep, args = slice.call(arguments, 1)
+ if (typeof target == 'boolean') {
+ deep = target
+ target = args.shift()
+ }
+ args.forEach(function(arg){ extend(target, arg, deep) })
+ return target
+ }
+
+ // `$.zepto.qsa` is Zepto's CSS selector implementation which
+ // uses `document.querySelectorAll` and optimizes for some special cases, like `#id`.
+ // This method can be overriden in plugins.
+ zepto.qsa = function(element, selector){
+ var found,
+ maybeID = selector[0] == '#',
+ maybeClass = !maybeID && selector[0] == '.',
+ nameOnly = maybeID || maybeClass ? selector.slice(1) : selector, // Ensure that a 1 char tag name still gets checked
+ isSimple = simpleSelectorRE.test(nameOnly)
+ return (isDocument(element) && isSimple && maybeID) ?
+ ( (found = element.getElementById(nameOnly)) ? [found] : [] ) :
+ (element.nodeType !== 1 && element.nodeType !== 9) ? [] :
+ slice.call(
+ isSimple && !maybeID ?
+ maybeClass ? element.getElementsByClassName(nameOnly) : // If it's simple, it could be a class
+ element.getElementsByTagName(selector) : // Or a tag
+ element.querySelectorAll(selector) // Or it's not simple, and we need to query all
+ )
+ }
+
+ function filtered(nodes, selector) {
+ return selector == null ? $(nodes) : $(nodes).filter(selector)
+ }
+
+ $.contains = function(parent, node) {
+ return parent !== node && parent.contains(node)
+ }
+
+ function funcArg(context, arg, idx, payload) {
+ return isFunction(arg) ? arg.call(context, idx, payload) : arg
+ }
+
+ function setAttribute(node, name, value) {
+ value == null ? node.removeAttribute(name) : node.setAttribute(name, value)
+ }
+
+ // access className property while respecting SVGAnimatedString
+ function className(node, value){
+ var klass = node.className,
+ svg = klass && klass.baseVal !== undefined
+
+ if (value === undefined) return svg ? klass.baseVal : klass
+ svg ? (klass.baseVal = value) : (node.className = value)
+ }
+
+ // "true" => true
+ // "false" => false
+ // "null" => null
+ // "42" => 42
+ // "42.5" => 42.5
+ // "08" => "08"
+ // JSON => parse if valid
+ // String => self
+ function deserializeValue(value) {
+ var num
+ try {
+ return value ?
+ value == "true" ||
+ ( value == "false" ? false :
+ value == "null" ? null :
+ !/^0/.test(value) && !isNaN(num = Number(value)) ? num :
+ /^[\[\{]/.test(value) ? $.parseJSON(value) :
+ value )
+ : value
+ } catch(e) {
+ return value
+ }
+ }
+
+ $.type = type
+ $.isFunction = isFunction
+ $.isWindow = isWindow
+ $.isArray = isArray
+ $.isPlainObject = isPlainObject
+
+ $.isEmptyObject = function(obj) {
+ var name
+ for (name in obj) return false
+ return true
+ }
+
+ $.inArray = function(elem, array, i){
+ return emptyArray.indexOf.call(array, elem, i)
+ }
+
+ $.camelCase = camelize
+ $.trim = function(str) {
+ return str == null ? "" : String.prototype.trim.call(str)
+ }
+
+ // plugin compatibility
+ $.uuid = 0
+ $.support = { }
+ $.expr = { }
+
+ $.map = function(elements, callback){
+ var value, values = [], i, key
+ if (likeArray(elements))
+ for (i = 0; i < elements.length; i++) {
+ value = callback(elements[i], i)
+ if (value != null) values.push(value)
+ }
+ else
+ for (key in elements) {
+ value = callback(elements[key], key)
+ if (value != null) values.push(value)
+ }
+ return flatten(values)
+ }
+
+ $.each = function(elements, callback){
+ var i, key
+ if (likeArray(elements)) {
+ for (i = 0; i < elements.length; i++)
+ if (callback.call(elements[i], i, elements[i]) === false) return elements
+ } else {
+ for (key in elements)
+ if (callback.call(elements[key], key, elements[key]) === false) return elements
+ }
+
+ return elements
+ }
+
+ $.grep = function(elements, callback){
+ return filter.call(elements, callback)
+ }
+
+ if (window.JSON) $.parseJSON = JSON.parse
+
+ // Populate the class2type map
+ $.each("Boolean Number String Function Array Date RegExp Object Error".split(" "), function(i, name) {
+ class2type[ "[object " + name + "]" ] = name.toLowerCase()
+ })
+
+ // Define methods that will be available on all
+ // Zepto collections
+ $.fn = {
+ // Because a collection acts like an array
+ // copy over these useful array functions.
+ forEach: emptyArray.forEach,
+ reduce: emptyArray.reduce,
+ push: emptyArray.push,
+ sort: emptyArray.sort,
+ indexOf: emptyArray.indexOf,
+ concat: emptyArray.concat,
+
+ // `map` and `slice` in the jQuery API work differently
+ // from their array counterparts
+ map: function(fn){
+ return $($.map(this, function(el, i){ return fn.call(el, i, el) }))
+ },
+ slice: function(){
+ return $(slice.apply(this, arguments))
+ },
+
+ ready: function(callback){
+ // need to check if document.body exists for IE as that browser reports
+ // document ready when it hasn't yet created the body element
+ if (readyRE.test(document.readyState) && document.body) callback($)
+ else document.addEventListener('DOMContentLoaded', function(){ callback($) }, false)
+ return this
+ },
+ get: function(idx){
+ return idx === undefined ? slice.call(this) : this[idx >= 0 ? idx : idx + this.length]
+ },
+ toArray: function(){ return this.get() },
+ size: function(){
+ return this.length
+ },
+ remove: function(){
+ return this.each(function(){
+ if (this.parentNode != null)
+ this.parentNode.removeChild(this)
+ })
+ },
+ each: function(callback){
+ emptyArray.every.call(this, function(el, idx){
+ return callback.call(el, idx, el) !== false
+ })
+ return this
+ },
+ filter: function(selector){
+ if (isFunction(selector)) return this.not(this.not(selector))
+ return $(filter.call(this, function(element){
+ return zepto.matches(element, selector)
+ }))
+ },
+ add: function(selector,context){
+ return $(uniq(this.concat($(selector,context))))
+ },
+ is: function(selector){
+ return this.length > 0 && zepto.matches(this[0], selector)
+ },
+ not: function(selector){
+ var nodes=[]
+ if (isFunction(selector) && selector.call !== undefined)
+ this.each(function(idx){
+ if (!selector.call(this,idx)) nodes.push(this)
+ })
+ else {
+ var excludes = typeof selector == 'string' ? this.filter(selector) :
+ (likeArray(selector) && isFunction(selector.item)) ? slice.call(selector) : $(selector)
+ this.forEach(function(el){
+ if (excludes.indexOf(el) < 0) nodes.push(el)
+ })
+ }
+ return $(nodes)
+ },
+ has: function(selector){
+ return this.filter(function(){
+ return isObject(selector) ?
+ $.contains(this, selector) :
+ $(this).find(selector).size()
+ })
+ },
+ eq: function(idx){
+ return idx === -1 ? this.slice(idx) : this.slice(idx, + idx + 1)
+ },
+ first: function(){
+ var el = this[0]
+ return el && !isObject(el) ? el : $(el)
+ },
+ last: function(){
+ var el = this[this.length - 1]
+ return el && !isObject(el) ? el : $(el)
+ },
+ find: function(selector){
+ var result, $this = this
+ if (typeof selector == 'object')
+ result = $(selector).filter(function(){
+ var node = this
+ return emptyArray.some.call($this, function(parent){
+ return $.contains(parent, node)
+ })
+ })
+ else if (this.length == 1) result = $(zepto.qsa(this[0], selector))
+ else result = this.map(function(){ return zepto.qsa(this, selector) })
+ return result
+ },
+ closest: function(selector, context){
+ var node = this[0], collection = false
+ if (typeof selector == 'object') collection = $(selector)
+ while (node && !(collection ? collection.indexOf(node) >= 0 : zepto.matches(node, selector)))
+ node = node !== context && !isDocument(node) && node.parentNode
+ return $(node)
+ },
+ parents: function(selector){
+ var ancestors = [], nodes = this
+ while (nodes.length > 0)
+ nodes = $.map(nodes, function(node){
+ if ((node = node.parentNode) && !isDocument(node) && ancestors.indexOf(node) < 0) {
+ ancestors.push(node)
+ return node
+ }
+ })
+ return filtered(ancestors, selector)
+ },
+ parent: function(selector){
+ return filtered(uniq(this.pluck('parentNode')), selector)
+ },
+ children: function(selector){
+ return filtered(this.map(function(){ return children(this) }), selector)
+ },
+ contents: function() {
+ return this.map(function() { return slice.call(this.childNodes) })
+ },
+ siblings: function(selector){
+ return filtered(this.map(function(i, el){
+ return filter.call(children(el.parentNode), function(child){ return child!==el })
+ }), selector)
+ },
+ empty: function(){
+ return this.each(function(){ this.innerHTML = '' })
+ },
+ // `pluck` is borrowed from Prototype.js
+ pluck: function(property){
+ return $.map(this, function(el){ return el[property] })
+ },
+ show: function(){
+ return this.each(function(){
+ this.style.display == "none" && (this.style.display = '')
+ if (getComputedStyle(this, '').getPropertyValue("display") == "none")
+ this.style.display = defaultDisplay(this.nodeName)
+ })
+ },
+ replaceWith: function(newContent){
+ return this.before(newContent).remove()
+ },
+ wrap: function(structure){
+ var func = isFunction(structure)
+ if (this[0] && !func)
+ var dom = $(structure).get(0),
+ clone = dom.parentNode || this.length > 1
+
+ return this.each(function(index){
+ $(this).wrapAll(
+ func ? structure.call(this, index) :
+ clone ? dom.cloneNode(true) : dom
+ )
+ })
+ },
+ wrapAll: function(structure){
+ if (this[0]) {
+ $(this[0]).before(structure = $(structure))
+ var children
+ // drill down to the inmost element
+ while ((children = structure.children()).length) structure = children.first()
+ $(structure).append(this)
+ }
+ return this
+ },
+ wrapInner: function(structure){
+ var func = isFunction(structure)
+ return this.each(function(index){
+ var self = $(this), contents = self.contents(),
+ dom = func ? structure.call(this, index) : structure
+ contents.length ? contents.wrapAll(dom) : self.append(dom)
+ })
+ },
+ unwrap: function(){
+ this.parent().each(function(){
+ $(this).replaceWith($(this).children())
+ })
+ return this
+ },
+ clone: function(){
+ return this.map(function(){ return this.cloneNode(true) })
+ },
+ hide: function(){
+ return this.css("display", "none")
+ },
+ toggle: function(setting){
+ return this.each(function(){
+ var el = $(this)
+ ;(setting === undefined ? el.css("display") == "none" : setting) ? el.show() : el.hide()
+ })
+ },
+ prev: function(selector){ return $(this.pluck('previousElementSibling')).filter(selector || '*') },
+ next: function(selector){ return $(this.pluck('nextElementSibling')).filter(selector || '*') },
+ html: function(html){
+ return arguments.length === 0 ?
+ (this.length > 0 ? this[0].innerHTML : null) :
+ this.each(function(idx){
+ var originHtml = this.innerHTML
+ $(this).empty().append( funcArg(this, html, idx, originHtml) )
+ })
+ },
+ text: function(text){
+ return arguments.length === 0 ?
+ (this.length > 0 ? this[0].textContent : null) :
+ this.each(function(){ this.textContent = (text === undefined) ? '' : ''+text })
+ },
+ attr: function(name, value){
+ var result
+ return (typeof name == 'string' && value === undefined) ?
+ (this.length == 0 || this[0].nodeType !== 1 ? undefined :
+ (name == 'value' && this[0].nodeName == 'INPUT') ? this.val() :
+ (!(result = this[0].getAttribute(name)) && name in this[0]) ? this[0][name] : result
+ ) :
+ this.each(function(idx){
+ if (this.nodeType !== 1) return
+ if (isObject(name)) for (key in name) setAttribute(this, key, name[key])
+ else setAttribute(this, name, funcArg(this, value, idx, this.getAttribute(name)))
+ })
+ },
+ removeAttr: function(name){
+ return this.each(function(){ this.nodeType === 1 && setAttribute(this, name) })
+ },
+ prop: function(name, value){
+ name = propMap[name] || name
+ return (value === undefined) ?
+ (this[0] && this[0][name]) :
+ this.each(function(idx){
+ this[name] = funcArg(this, value, idx, this[name])
+ })
+ },
+ data: function(name, value){
+ var data = this.attr('data-' + name.replace(capitalRE, '-$1').toLowerCase(), value)
+ return data !== null ? deserializeValue(data) : undefined
+ },
+ val: function(value){
+ return arguments.length === 0 ?
+ (this[0] && (this[0].multiple ?
+ $(this[0]).find('option').filter(function(){ return this.selected }).pluck('value') :
+ this[0].value)
+ ) :
+ this.each(function(idx){
+ this.value = funcArg(this, value, idx, this.value)
+ })
+ },
+ offset: function(coordinates){
+ if (coordinates) return this.each(function(index){
+ var $this = $(this),
+ coords = funcArg(this, coordinates, index, $this.offset()),
+ parentOffset = $this.offsetParent().offset(),
+ props = {
+ top: coords.top - parentOffset.top,
+ left: coords.left - parentOffset.left
+ }
+
+ if ($this.css('position') == 'static') props['position'] = 'relative'
+ $this.css(props)
+ })
+ if (this.length==0) return null
+ var obj = this[0].getBoundingClientRect()
+ return {
+ left: obj.left + window.pageXOffset,
+ top: obj.top + window.pageYOffset,
+ width: Math.round(obj.width),
+ height: Math.round(obj.height)
+ }
+ },
+ css: function(property, value){
+ if (arguments.length < 2) {
+ var element = this[0], computedStyle = getComputedStyle(element, '')
+ if(!element) return
+ if (typeof property == 'string')
+ return element.style[camelize(property)] || computedStyle.getPropertyValue(property)
+ else if (isArray(property)) {
+ var props = {}
+ $.each(isArray(property) ? property: [property], function(_, prop){
+ props[prop] = (element.style[camelize(prop)] || computedStyle.getPropertyValue(prop))
+ })
+ return props
+ }
+ }
+
+ var css = ''
+ if (type(property) == 'string') {
+ if (!value && value !== 0)
+ this.each(function(){ this.style.removeProperty(dasherize(property)) })
+ else
+ css = dasherize(property) + ":" + maybeAddPx(property, value)
+ } else {
+ for (key in property)
+ if (!property[key] && property[key] !== 0)
+ this.each(function(){ this.style.removeProperty(dasherize(key)) })
+ else
+ css += dasherize(key) + ':' + maybeAddPx(key, property[key]) + ';'
+ }
+
+ return this.each(function(){ this.style.cssText += ';' + css })
+ },
+ index: function(element){
+ return element ? this.indexOf($(element)[0]) : this.parent().children().indexOf(this[0])
+ },
+ hasClass: function(name){
+ if (!name) return false
+ return emptyArray.some.call(this, function(el){
+ return this.test(className(el))
+ }, classRE(name))
+ },
+ addClass: function(name){
+ if (!name) return this
+ return this.each(function(idx){
+ classList = []
+ var cls = className(this), newName = funcArg(this, name, idx, cls)
+ newName.split(/\s+/g).forEach(function(klass){
+ if (!$(this).hasClass(klass)) classList.push(klass)
+ }, this)
+ classList.length && className(this, cls + (cls ? " " : "") + classList.join(" "))
+ })
+ },
+ removeClass: function(name){
+ return this.each(function(idx){
+ if (name === undefined) return className(this, '')
+ classList = className(this)
+ funcArg(this, name, idx, classList).split(/\s+/g).forEach(function(klass){
+ classList = classList.replace(classRE(klass), " ")
+ })
+ className(this, classList.trim())
+ })
+ },
+ toggleClass: function(name, when){
+ if (!name) return this
+ return this.each(function(idx){
+ var $this = $(this), names = funcArg(this, name, idx, className(this))
+ names.split(/\s+/g).forEach(function(klass){
+ (when === undefined ? !$this.hasClass(klass) : when) ?
+ $this.addClass(klass) : $this.removeClass(klass)
+ })
+ })
+ },
+ scrollTop: function(value){
+ if (!this.length) return
+ var hasScrollTop = 'scrollTop' in this[0]
+ if (value === undefined) return hasScrollTop ? this[0].scrollTop : this[0].pageYOffset
+ return this.each(hasScrollTop ?
+ function(){ this.scrollTop = value } :
+ function(){ this.scrollTo(this.scrollX, value) })
+ },
+ scrollLeft: function(value){
+ if (!this.length) return
+ var hasScrollLeft = 'scrollLeft' in this[0]
+ if (value === undefined) return hasScrollLeft ? this[0].scrollLeft : this[0].pageXOffset
+ return this.each(hasScrollLeft ?
+ function(){ this.scrollLeft = value } :
+ function(){ this.scrollTo(value, this.scrollY) })
+ },
+ position: function() {
+ if (!this.length) return
+
+ var elem = this[0],
+ // Get *real* offsetParent
+ offsetParent = this.offsetParent(),
+ // Get correct offsets
+ offset = this.offset(),
+ parentOffset = rootNodeRE.test(offsetParent[0].nodeName) ? { top: 0, left: 0 } : offsetParent.offset()
+
+ // Subtract element margins
+ // note: when an element has margin: auto the offsetLeft and marginLeft
+ // are the same in Safari causing offset.left to incorrectly be 0
+ offset.top -= parseFloat( $(elem).css('margin-top') ) || 0
+ offset.left -= parseFloat( $(elem).css('margin-left') ) || 0
+
+ // Add offsetParent borders
+ parentOffset.top += parseFloat( $(offsetParent[0]).css('border-top-width') ) || 0
+ parentOffset.left += parseFloat( $(offsetParent[0]).css('border-left-width') ) || 0
+
+ // Subtract the two offsets
+ return {
+ top: offset.top - parentOffset.top,
+ left: offset.left - parentOffset.left
+ }
+ },
+ offsetParent: function() {
+ return this.map(function(){
+ var parent = this.offsetParent || document.body
+ while (parent && !rootNodeRE.test(parent.nodeName) && $(parent).css("position") == "static")
+ parent = parent.offsetParent
+ return parent
+ })
+ }
+ }
+
+ // for now
+ $.fn.detach = $.fn.remove
+
+ // Generate the `width` and `height` functions
+ ;['width', 'height'].forEach(function(dimension){
+ var dimensionProperty =
+ dimension.replace(/./, function(m){ return m[0].toUpperCase() })
+
+ $.fn[dimension] = function(value){
+ var offset, el = this[0]
+ if (value === undefined) return isWindow(el) ? el['inner' + dimensionProperty] :
+ isDocument(el) ? el.documentElement['scroll' + dimensionProperty] :
+ (offset = this.offset()) && offset[dimension]
+ else return this.each(function(idx){
+ el = $(this)
+ el.css(dimension, funcArg(this, value, idx, el[dimension]()))
+ })
+ }
+ })
+
+ function traverseNode(node, fun) {
+ fun(node)
+ for (var key in node.childNodes) traverseNode(node.childNodes[key], fun)
+ }
+
+ // Generate the `after`, `prepend`, `before`, `append`,
+ // `insertAfter`, `insertBefore`, `appendTo`, and `prependTo` methods.
+ adjacencyOperators.forEach(function(operator, operatorIndex) {
+ var inside = operatorIndex % 2 //=> prepend, append
+
+ $.fn[operator] = function(){
+ // arguments can be nodes, arrays of nodes, Zepto objects and HTML strings
+ var argType, nodes = $.map(arguments, function(arg) {
+ argType = type(arg)
+ return argType == "object" || argType == "array" || arg == null ?
+ arg : zepto.fragment(arg)
+ }),
+ parent, copyByClone = this.length > 1
+ if (nodes.length < 1) return this
+
+ return this.each(function(_, target){
+ parent = inside ? target : target.parentNode
+
+ // convert all methods to a "before" operation
+ target = operatorIndex == 0 ? target.nextSibling :
+ operatorIndex == 1 ? target.firstChild :
+ operatorIndex == 2 ? target :
+ null
+
+ nodes.forEach(function(node){
+ if (copyByClone) node = node.cloneNode(true)
+ else if (!parent) return $(node).remove()
+
+ traverseNode(parent.insertBefore(node, target), function(el){
+ if (el.nodeName != null && el.nodeName.toUpperCase() === 'SCRIPT' &&
+ (!el.type || el.type === 'text/javascript') && !el.src)
+ window['eval'].call(window, el.innerHTML)
+ })
+ })
+ })
+ }
+
+ // after => insertAfter
+ // prepend => prependTo
+ // before => insertBefore
+ // append => appendTo
+ $.fn[inside ? operator+'To' : 'insert'+(operatorIndex ? 'Before' : 'After')] = function(html){
+ $(html)[operator](this)
+ return this
+ }
+ })
+
+ zepto.Z.prototype = $.fn
+
+ // Export internal API functions in the `$.zepto` namespace
+ zepto.uniq = uniq
+ zepto.deserializeValue = deserializeValue
+ $.zepto = zepto
+
+ return $
+ })()
+
+ window.Zepto = Zepto
+ window.$ === undefined && (window.$ = Zepto)
+
+ ;(function($){
+ var $$ = $.zepto.qsa, _zid = 1, undefined,
+ slice = Array.prototype.slice,
+ isFunction = $.isFunction,
+ isString = function(obj){ return typeof obj == 'string' },
+ handlers = {},
+ specialEvents={},
+ focusinSupported = 'onfocusin' in window,
+ focus = { focus: 'focusin', blur: 'focusout' },
+ hover = { mouseenter: 'mouseover', mouseleave: 'mouseout' }
+
+ specialEvents.click = specialEvents.mousedown = specialEvents.mouseup = specialEvents.mousemove = 'MouseEvents'
+
+ function zid(element) {
+ return element._zid || (element._zid = _zid++)
+ }
+ function findHandlers(element, event, fn, selector) {
+ event = parse(event)
+ if (event.ns) var matcher = matcherFor(event.ns)
+ return (handlers[zid(element)] || []).filter(function(handler) {
+ return handler
+ && (!event.e || handler.e == event.e)
+ && (!event.ns || matcher.test(handler.ns))
+ && (!fn || zid(handler.fn) === zid(fn))
+ && (!selector || handler.sel == selector)
+ })
+ }
+ function parse(event) {
+ var parts = ('' + event).split('.')
+ return {e: parts[0], ns: parts.slice(1).sort().join(' ')}
+ }
+ function matcherFor(ns) {
+ return new RegExp('(?:^| )' + ns.replace(' ', ' .* ?') + '(?: |$)')
+ }
+
+ function eventCapture(handler, captureSetting) {
+ return handler.del &&
+ (!focusinSupported && (handler.e in focus)) ||
+ !!captureSetting
+ }
+
+ function realEvent(type) {
+ return hover[type] || (focusinSupported && focus[type]) || type
+ }
+
+ function add(element, events, fn, data, selector, delegator, capture){
+ var id = zid(element), set = (handlers[id] || (handlers[id] = []))
+ events.split(/\s/).forEach(function(event){
+ if (event == 'ready') return $(document).ready(fn)
+ var handler = parse(event)
+ handler.fn = fn
+ handler.sel = selector
+ // emulate mouseenter, mouseleave
+ if (handler.e in hover) fn = function(e){
+ var related = e.relatedTarget
+ if (!related || (related !== this && !$.contains(this, related)))
+ return handler.fn.apply(this, arguments)
+ }
+ handler.del = delegator
+ var callback = delegator || fn
+ handler.proxy = function(e){
+ e = compatible(e)
+ if (e.isImmediatePropagationStopped()) return
+ e.data = data
+ var result = callback.apply(element, e._args == undefined ? [e] : [e].concat(e._args))
+ if (result === false) e.preventDefault(), e.stopPropagation()
+ return result
+ }
+ handler.i = set.length
+ set.push(handler)
+ if ('addEventListener' in element)
+ element.addEventListener(realEvent(handler.e), handler.proxy, eventCapture(handler, capture))
+ })
+ }
+ function remove(element, events, fn, selector, capture){
+ var id = zid(element)
+ ;(events || '').split(/\s/).forEach(function(event){
+ findHandlers(element, event, fn, selector).forEach(function(handler){
+ delete handlers[id][handler.i]
+ if ('removeEventListener' in element)
+ element.removeEventListener(realEvent(handler.e), handler.proxy, eventCapture(handler, capture))
+ })
+ })
+ }
+
+ $.event = { add: add, remove: remove }
+
+ $.proxy = function(fn, context) {
+ if (isFunction(fn)) {
+ var proxyFn = function(){ return fn.apply(context, arguments) }
+ proxyFn._zid = zid(fn)
+ return proxyFn
+ } else if (isString(context)) {
+ return $.proxy(fn[context], fn)
+ } else {
+ throw new TypeError("expected function")
+ }
+ }
+
+ $.fn.bind = function(event, data, callback){
+ return this.on(event, data, callback)
+ }
+ $.fn.unbind = function(event, callback){
+ return this.off(event, callback)
+ }
+ $.fn.one = function(event, selector, data, callback){
+ return this.on(event, selector, data, callback, 1)
+ }
+
+ var returnTrue = function(){return true},
+ returnFalse = function(){return false},
+ ignoreProperties = /^([A-Z]|returnValue$|layer[XY]$)/,
+ eventMethods = {
+ preventDefault: 'isDefaultPrevented',
+ stopImmediatePropagation: 'isImmediatePropagationStopped',
+ stopPropagation: 'isPropagationStopped'
+ }
+
+ function compatible(event, source) {
+ if (source || !event.isDefaultPrevented) {
+ source || (source = event)
+
+ $.each(eventMethods, function(name, predicate) {
+ var sourceMethod = source[name]
+ event[name] = function(){
+ this[predicate] = returnTrue
+ return sourceMethod && sourceMethod.apply(source, arguments)
+ }
+ event[predicate] = returnFalse
+ })
+
+ if (source.defaultPrevented !== undefined ? source.defaultPrevented :
+ 'returnValue' in source ? source.returnValue === false :
+ source.getPreventDefault && source.getPreventDefault())
+ event.isDefaultPrevented = returnTrue
+ }
+ return event
+ }
+
+ function createProxy(event) {
+ var key, proxy = { originalEvent: event }
+ for (key in event)
+ if (!ignoreProperties.test(key) && event[key] !== undefined) proxy[key] = event[key]
+
+ return compatible(proxy, event)
+ }
+
+ $.fn.delegate = function(selector, event, callback){
+ return this.on(event, selector, callback)
+ }
+ $.fn.undelegate = function(selector, event, callback){
+ return this.off(event, selector, callback)
+ }
+
+ $.fn.live = function(event, callback){
+ $(document.body).delegate(this.selector, event, callback)
+ return this
+ }
+ $.fn.die = function(event, callback){
+ $(document.body).undelegate(this.selector, event, callback)
+ return this
+ }
+
+ $.fn.on = function(event, selector, data, callback, one){
+ var autoRemove, delegator, $this = this
+ if (event && !isString(event)) {
+ $.each(event, function(type, fn){
+ $this.on(type, selector, data, fn, one)
+ })
+ return $this
+ }
+
+ if (!isString(selector) && !isFunction(callback) && callback !== false)
+ callback = data, data = selector, selector = undefined
+ if (isFunction(data) || data === false)
+ callback = data, data = undefined
+
+ if (callback === false) callback = returnFalse
+
+ return $this.each(function(_, element){
+ if (one) autoRemove = function(e){
+ remove(element, e.type, callback)
+ return callback.apply(this, arguments)
+ }
+
+ if (selector) delegator = function(e){
+ var evt, match = $(e.target).closest(selector, element).get(0)
+ if (match && match !== element) {
+ evt = $.extend(createProxy(e), {currentTarget: match, liveFired: element})
+ return (autoRemove || callback).apply(match, [evt].concat(slice.call(arguments, 1)))
+ }
+ }
+
+ add(element, event, callback, data, selector, delegator || autoRemove)
+ })
+ }
+ $.fn.off = function(event, selector, callback){
+ var $this = this
+ if (event && !isString(event)) {
+ $.each(event, function(type, fn){
+ $this.off(type, selector, fn)
+ })
+ return $this
+ }
+
+ if (!isString(selector) && !isFunction(callback) && callback !== false)
+ callback = selector, selector = undefined
+
+ if (callback === false) callback = returnFalse
+
+ return $this.each(function(){
+ remove(this, event, callback, selector)
+ })
+ }
+
+ $.fn.trigger = function(event, args){
+ event = (isString(event) || $.isPlainObject(event)) ? $.Event(event) : compatible(event)
+ event._args = args
+ return this.each(function(){
+ // items in the collection might not be DOM elements
+ if('dispatchEvent' in this) this.dispatchEvent(event)
+ else $(this).triggerHandler(event, args)
+ })
+ }
+
+ // triggers event handlers on current element just as if an event occurred,
+ // doesn't trigger an actual event, doesn't bubble
+ $.fn.triggerHandler = function(event, args){
+ var e, result
+ this.each(function(i, element){
+ e = createProxy(isString(event) ? $.Event(event) : event)
+ e._args = args
+ e.target = element
+ $.each(findHandlers(element, event.type || event), function(i, handler){
+ result = handler.proxy(e)
+ if (e.isImmediatePropagationStopped()) return false
+ })
+ })
+ return result
+ }
+
+ // shortcut methods for `.bind(event, fn)` for each event type
+ ;('focusin focusout load resize scroll unload click dblclick '+
+ 'mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave '+
+ 'change select keydown keypress keyup error').split(' ').forEach(function(event) {
+ $.fn[event] = function(callback) {
+ return callback ?
+ this.bind(event, callback) :
+ this.trigger(event)
+ }
+ })
+
+ ;['focus', 'blur'].forEach(function(name) {
+ $.fn[name] = function(callback) {
+ if (callback) this.bind(name, callback)
+ else this.each(function(){
+ try { this[name]() }
+ catch(e) {}
+ })
+ return this
+ }
+ })
+
+ $.Event = function(type, props) {
+ if (!isString(type)) props = type, type = props.type
+ var event = document.createEvent(specialEvents[type] || 'Events'), bubbles = true
+ if (props) for (var name in props) (name == 'bubbles') ? (bubbles = !!props[name]) : (event[name] = props[name])
+ event.initEvent(type, bubbles, true)
+ return compatible(event)
+ }
+
+ })(Zepto)
+
+ ;(function($){
+ var jsonpID = 0,
+ document = window.document,
+ key,
+ name,
+ rscript = /<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi,
+ scriptTypeRE = /^(?:text|application)\/javascript/i,
+ xmlTypeRE = /^(?:text|application)\/xml/i,
+ jsonType = 'application/json',
+ htmlType = 'text/html',
+ blankRE = /^\s*$/
+
+ // trigger a custom event and return false if it was cancelled
+ function triggerAndReturn(context, eventName, data) {
+ var event = $.Event(eventName)
+ $(context).trigger(event, data)
+ return !event.isDefaultPrevented()
+ }
+
+ // trigger an Ajax "global" event
+ function triggerGlobal(settings, context, eventName, data) {
+ if (settings.global) return triggerAndReturn(context || document, eventName, data)
+ }
+
+ // Number of active Ajax requests
+ $.active = 0
+
+ function ajaxStart(settings) {
+ if (settings.global && $.active++ === 0) triggerGlobal(settings, null, 'ajaxStart')
+ }
+ function ajaxStop(settings) {
+ if (settings.global && !(--$.active)) triggerGlobal(settings, null, 'ajaxStop')
+ }
+
+ // triggers an extra global event "ajaxBeforeSend" that's like "ajaxSend" but cancelable
+ function ajaxBeforeSend(xhr, settings) {
+ var context = settings.context
+ if (settings.beforeSend.call(context, xhr, settings) === false ||
+ triggerGlobal(settings, context, 'ajaxBeforeSend', [xhr, settings]) === false)
+ return false
+
+ triggerGlobal(settings, context, 'ajaxSend', [xhr, settings])
+ }
+ function ajaxSuccess(data, xhr, settings, deferred) {
+ var context = settings.context, status = 'success'
+ settings.success.call(context, data, status, xhr)
+ if (deferred) deferred.resolveWith(context, [data, status, xhr])
+ triggerGlobal(settings, context, 'ajaxSuccess', [xhr, settings, data])
+ ajaxComplete(status, xhr, settings)
+ }
+ // type: "timeout", "error", "abort", "parsererror"
+ function ajaxError(error, type, xhr, settings, deferred) {
+ var context = settings.context
+ settings.error.call(context, xhr, type, error)
+ if (deferred) deferred.rejectWith(context, [xhr, type, error])
+ triggerGlobal(settings, context, 'ajaxError', [xhr, settings, error || type])
+ ajaxComplete(type, xhr, settings)
+ }
+ // status: "success", "notmodified", "error", "timeout", "abort", "parsererror"
+ function ajaxComplete(status, xhr, settings) {
+ var context = settings.context
+ settings.complete.call(context, xhr, status)
+ triggerGlobal(settings, context, 'ajaxComplete', [xhr, settings])
+ ajaxStop(settings)
+ }
+
+ // Empty function, used as default callback
+ function empty() {}
+
+ $.ajaxJSONP = function(options, deferred){
+ if (!('type' in options)) return $.ajax(options)
+
+ var _callbackName = options.jsonpCallback,
+ callbackName = ($.isFunction(_callbackName) ?
+ _callbackName() : _callbackName) || ('jsonp' + (++jsonpID)),
+ script = document.createElement('script'),
+ originalCallback = window[callbackName],
+ responseData,
+ abort = function(errorType) {
+ $(script).triggerHandler('error', errorType || 'abort')
+ },
+ xhr = { abort: abort }, abortTimeout
+
+ if (deferred) deferred.promise(xhr)
+
+ $(script).on('load error', function(e, errorType){
+ clearTimeout(abortTimeout)
+ $(script).off().remove()
+
+ if (e.type == 'error' || !responseData) {
+ ajaxError(null, errorType || 'error', xhr, options, deferred)
+ } else {
+ ajaxSuccess(responseData[0], xhr, options, deferred)
+ }
+
+ window[callbackName] = originalCallback
+ if (responseData && $.isFunction(originalCallback))
+ originalCallback(responseData[0])
+
+ originalCallback = responseData = undefined
+ })
+
+ if (ajaxBeforeSend(xhr, options) === false) {
+ abort('abort')
+ return xhr
+ }
+
+ window[callbackName] = function(){
+ responseData = arguments
+ }
+
+ script.src = options.url.replace(/\?(.+)=\?/, '?$1=' + callbackName)
+ document.head.appendChild(script)
+
+ if (options.timeout > 0) abortTimeout = setTimeout(function(){
+ abort('timeout')
+ }, options.timeout)
+
+ return xhr
+ }
+
+ $.ajaxSettings = {
+ // Default type of request
+ type: 'GET',
+ // Callback that is executed before request
+ beforeSend: empty,
+ // Callback that is executed if the request succeeds
+ success: empty,
+ // Callback that is executed the the server drops error
+ error: empty,
+ // Callback that is executed on request complete (both: error and success)
+ complete: empty,
+ // The context for the callbacks
+ context: null,
+ // Whether to trigger "global" Ajax events
+ global: true,
+ // Transport
+ xhr: function () {
+ return new window.XMLHttpRequest()
+ },
+ // MIME types mapping
+ // IIS returns Javascript as "application/x-javascript"
+ accepts: {
+ script: 'text/javascript, application/javascript, application/x-javascript',
+ json: jsonType,
+ xml: 'application/xml, text/xml',
+ html: htmlType,
+ text: 'text/plain'
+ },
+ // Whether the request is to another domain
+ crossDomain: false,
+ // Default timeout
+ timeout: 0,
+ // Whether data should be serialized to string
+ processData: true,
+ // Whether the browser should be allowed to cache GET responses
+ cache: true
+ }
+
+ function mimeToDataType(mime) {
+ if (mime) mime = mime.split(';', 2)[0]
+ return mime && ( mime == htmlType ? 'html' :
+ mime == jsonType ? 'json' :
+ scriptTypeRE.test(mime) ? 'script' :
+ xmlTypeRE.test(mime) && 'xml' ) || 'text'
+ }
+
+ function appendQuery(url, query) {
+ if (query == '') return url
+ return (url + '&' + query).replace(/[&?]{1,2}/, '?')
+ }
+
+ // serialize payload and append it to the URL for GET requests
+ function serializeData(options) {
+ if (options.processData && options.data && $.type(options.data) != "string")
+ options.data = $.param(options.data, options.traditional)
+ if (options.data && (!options.type || options.type.toUpperCase() == 'GET'))
+ options.url = appendQuery(options.url, options.data), options.data = undefined
+ }
+
+ $.ajax = function(options){
+ var settings = $.extend({}, options || {}),
+ deferred = $.Deferred && $.Deferred()
+ for (key in $.ajaxSettings) if (settings[key] === undefined) settings[key] = $.ajaxSettings[key]
+
+ ajaxStart(settings)
+
+ if (!settings.crossDomain) settings.crossDomain = /^([\w-]+:)?\/\/([^\/]+)/.test(settings.url) &&
+ RegExp.$2 != window.location.host
+
+ if (!settings.url) settings.url = window.location.toString()
+ serializeData(settings)
+ if (settings.cache === false) settings.url = appendQuery(settings.url, '_=' + Date.now())
+
+ var dataType = settings.dataType, hasPlaceholder = /\?.+=\?/.test(settings.url)
+ if (dataType == 'jsonp' || hasPlaceholder) {
+ if (!hasPlaceholder)
+ settings.url = appendQuery(settings.url,
+ settings.jsonp ? (settings.jsonp + '=?') : settings.jsonp === false ? '' : 'callback=?')
+ return $.ajaxJSONP(settings, deferred)
+ }
+
+ var mime = settings.accepts[dataType],
+ headers = { },
+ setHeader = function(name, value) { headers[name.toLowerCase()] = [name, value] },
+ protocol = /^([\w-]+:)\/\//.test(settings.url) ? RegExp.$1 : window.location.protocol,
+ xhr = settings.xhr(),
+ nativeSetHeader = xhr.setRequestHeader,
+ abortTimeout
+
+ if (deferred) deferred.promise(xhr)
+
+ if (!settings.crossDomain) setHeader('X-Requested-With', 'XMLHttpRequest')
+ setHeader('Accept', mime || '*/*')
+ if (mime = settings.mimeType || mime) {
+ if (mime.indexOf(',') > -1) mime = mime.split(',', 2)[0]
+ xhr.overrideMimeType && xhr.overrideMimeType(mime)
+ }
+ if (settings.contentType || (settings.contentType !== false && settings.data && settings.type.toUpperCase() != 'GET'))
+ setHeader('Content-Type', settings.contentType || 'application/x-www-form-urlencoded')
+
+ if (settings.headers) for (name in settings.headers) setHeader(name, settings.headers[name])
+ xhr.setRequestHeader = setHeader
+
+ xhr.onreadystatechange = function(){
+ if (xhr.readyState == 4) {
+ xhr.onreadystatechange = empty
+ clearTimeout(abortTimeout)
+ var result, error = false
+ if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304 || (xhr.status == 0 && protocol == 'file:')) {
+ dataType = dataType || mimeToDataType(settings.mimeType || xhr.getResponseHeader('content-type'))
+ result = xhr.responseText
+
+ try {
+ // http://perfectionkills.com/global-eval-what-are-the-options/
+ if (dataType == 'script') (1,eval)(result)
+ else if (dataType == 'xml') result = xhr.responseXML
+ else if (dataType == 'json') result = blankRE.test(result) ? null : $.parseJSON(result)
+ } catch (e) { error = e }
+
+ if (error) ajaxError(error, 'parsererror', xhr, settings, deferred)
+ else ajaxSuccess(result, xhr, settings, deferred)
+ } else {
+ ajaxError(xhr.statusText || null, xhr.status ? 'error' : 'abort', xhr, settings, deferred)
+ }
+ }
+ }
+
+ if (ajaxBeforeSend(xhr, settings) === false) {
+ xhr.abort()
+ ajaxError(null, 'abort', xhr, settings, deferred)
+ return xhr
+ }
+
+ if (settings.xhrFields) for (name in settings.xhrFields) xhr[name] = settings.xhrFields[name]
+
+ var async = 'async' in settings ? settings.async : true
+ xhr.open(settings.type, settings.url, async, settings.username, settings.password)
+
+ for (name in headers) nativeSetHeader.apply(xhr, headers[name])
+
+ if (settings.timeout > 0) abortTimeout = setTimeout(function(){
+ xhr.onreadystatechange = empty
+ xhr.abort()
+ ajaxError(null, 'timeout', xhr, settings, deferred)
+ }, settings.timeout)
+
+ // avoid sending empty string (#319)
+ xhr.send(settings.data ? settings.data : null)
+ return xhr
+ }
+
+ // handle optional data/success arguments
+ function parseArguments(url, data, success, dataType) {
+ var hasData = !$.isFunction(data)
+ return {
+ url: url,
+ data: hasData ? data : undefined,
+ success: !hasData ? data : $.isFunction(success) ? success : undefined,
+ dataType: hasData ? dataType || success : success
+ }
+ }
+
+ $.get = function(url, data, success, dataType){
+ return $.ajax(parseArguments.apply(null, arguments))
+ }
+
+ $.post = function(url, data, success, dataType){
+ var options = parseArguments.apply(null, arguments)
+ options.type = 'POST'
+ return $.ajax(options)
+ }
+
+ $.getJSON = function(url, data, success){
+ var options = parseArguments.apply(null, arguments)
+ options.dataType = 'json'
+ return $.ajax(options)
+ }
+
+ $.fn.load = function(url, data, success){
+ if (!this.length) return this
+ var self = this, parts = url.split(/\s/), selector,
+ options = parseArguments(url, data, success),
+ callback = options.success
+ if (parts.length > 1) options.url = parts[0], selector = parts[1]
+ options.success = function(response){
+ self.html(selector ?
+ $('<div>').html(response.replace(rscript, "")).find(selector)
+ : response)
+ callback && callback.apply(self, arguments)
+ }
+ $.ajax(options)
+ return this
+ }
+
+ var escape = encodeURIComponent
+
+ function serialize(params, obj, traditional, scope){
+ var type, array = $.isArray(obj), hash = $.isPlainObject(obj)
+ $.each(obj, function(key, value) {
+ type = $.type(value)
+ if (scope) key = traditional ? scope :
+ scope + '[' + (hash || type == 'object' || type == 'array' ? key : '') + ']'
+ // handle data in serializeArray() format
+ if (!scope && array) params.add(value.name, value.value)
+ // recurse into nested objects
+ else if (type == "array" || (!traditional && type == "object"))
+ serialize(params, value, traditional, key)
+ else params.add(key, value)
+ })
+ }
+
+ $.param = function(obj, traditional){
+ var params = []
+ params.add = function(k, v){ this.push(escape(k) + '=' + escape(v)) }
+ serialize(params, obj, traditional)
+ return params.join('&').replace(/%20/g, '+')
+ }
+ })(Zepto)
+
+ ;(function($){
+ $.fn.serializeArray = function() {
+ var result = [], el
+ $([].slice.call(this.get(0).elements)).each(function(){
+ el = $(this)
+ var type = el.attr('type')
+ if (this.nodeName.toLowerCase() != 'fieldset' &&
+ !this.disabled && type != 'submit' && type != 'reset' && type != 'button' &&
+ ((type != 'radio' && type != 'checkbox') || this.checked))
+ result.push({
+ name: el.attr('name'),
+ value: el.val()
+ })
+ })
+ return result
+ }
+
+ $.fn.serialize = function(){
+ var result = []
+ this.serializeArray().forEach(function(elm){
+ result.push(encodeURIComponent(elm.name) + '=' + encodeURIComponent(elm.value))
+ })
+ return result.join('&')
+ }
+
+ $.fn.submit = function(callback) {
+ if (callback) this.bind('submit', callback)
+ else if (this.length) {
+ var event = $.Event('submit')
+ this.eq(0).trigger(event)
+ if (!event.isDefaultPrevented()) this.get(0).submit()
+ }
+ return this
+ }
+
+ })(Zepto)
+
+ ;(function($){
+ // __proto__ doesn't exist on IE<11, so redefine
+ // the Z function to use object extension instead
+ if (!('__proto__' in {})) {
+ $.extend($.zepto, {
+ Z: function(dom, selector){
+ dom = dom || []
+ $.extend(dom, $.fn)
+ dom.selector = selector || ''
+ dom.__Z = true
+ return dom
+ },
+ // this is a kludge but works
+ isZ: function(object){
+ return $.type(object) === 'array' && '__Z' in object
+ }
+ })
+ }
+
+ // getComputedStyle shouldn't freak out when called
+ // without a valid element as argument
+ try {
+ getComputedStyle(undefined)
+ } catch(e) {
+ var nativeGetComputedStyle = getComputedStyle;
+ window.getComputedStyle = function(element, pseudoElement){
+ try {
+ return nativeGetComputedStyle(element, pseudoElement)
+ } catch(e) {
+ return null
+ }
+ }
+ }
+ })(Zepto)
+
+
+ TL.getJSON = Zepto.getJSON;
+ TL.ajax = Zepto.ajax;
+})(TL)
+
+// Based on https://github.com/madrobby/zepto/blob/5585fe00f1828711c04208372265a5d71e3238d1/src/ajax.js
+// Zepto.js
+// (c) 2010-2012 Thomas Fuchs
+// Zepto.js may be freely distributed under the MIT license.
+/*
+Copyright (c) 2010-2012 Thomas Fuchs
+http://zeptojs.com
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+*/
+
+
+/* **********************************************
+ Begin TL.Class.js
+********************************************** */
+
+/* TL.Class
+ Class powers the OOP facilities of the library.
+================================================== */
+TL.Class = function () {};
+
+TL.Class.extend = function (/*Object*/ props) /*-> Class*/ {
+
+ // extended class with the new prototype
+ var NewClass = function () {
+ if (this.initialize) {
+ this.initialize.apply(this, arguments);
+ }
+ };
+
+ // instantiate class without calling constructor
+ var F = function () {};
+ F.prototype = this.prototype;
+ var proto = new F();
+
+ proto.constructor = NewClass;
+ NewClass.prototype = proto;
+
+ // add superclass access
+ NewClass.superclass = this.prototype;
+
+ // add class name
+ //proto.className = props;
+
+ //inherit parent's statics
+ for (var i in this) {
+ if (this.hasOwnProperty(i) && i !== 'prototype' && i !== 'superclass') {
+ NewClass[i] = this[i];
+ }
+ }
+
+ // mix static properties into the class
+ if (props.statics) {
+ TL.Util.extend(NewClass, props.statics);
+ delete props.statics;
+ }
+
+ // mix includes into the prototype
+ if (props.includes) {
+ TL.Util.extend.apply(null, [proto].concat(props.includes));
+ delete props.includes;
+ }
+
+ // merge options
+ if (props.options && proto.options) {
+ props.options = TL.Util.extend({}, proto.options, props.options);
+ }
+
+ // mix given properties into the prototype
+ TL.Util.extend(proto, props);
+
+ // allow inheriting further
+ NewClass.extend = TL.Class.extend;
+
+ // method for adding properties to prototype
+ NewClass.include = function (props) {
+ TL.Util.extend(this.prototype, props);
+ };
+
+ return NewClass;
+};
+
+
+/* **********************************************
+ Begin TL.Events.js
+********************************************** */
+
+/* TL.Events
+ adds custom events functionality to TL classes
+================================================== */
+TL.Events = {
+ addEventListener: function (/*String*/ type, /*Function*/ fn, /*(optional) Object*/ context) {
+ var events = this._tl_events = this._tl_events || {};
+ events[type] = events[type] || [];
+ events[type].push({
+ action: fn,
+ context: context || this
+ });
+ return this;
+ },
+
+ hasEventListeners: function (/*String*/ type) /*-> Boolean*/ {
+ var k = '_tl_events';
+ return (k in this) && (type in this[k]) && (this[k][type].length > 0);
+ },
+
+ removeEventListener: function (/*String*/ type, /*Function*/ fn, /*(optional) Object*/ context) {
+ if (!this.hasEventListeners(type)) {
+ return this;
+ }
+
+ for (var i = 0, events = this._tl_events, len = events[type].length; i < len; i++) {
+ if (
+ (events[type][i].action === fn) &&
+ (!context || (events[type][i].context === context))
+ ) {
+ events[type].splice(i, 1);
+ return this;
+ }
+ }
+ return this;
+ },
+
+ fireEvent: function (/*String*/ type, /*(optional) Object*/ data) {
+ if (!this.hasEventListeners(type)) {
+ return this;
+ }
+
+ var event = TL.Util.mergeData({
+ type: type,
+ target: this
+ }, data);
+
+ var listeners = this._tl_events[type].slice();
+
+ for (var i = 0, len = listeners.length; i < len; i++) {
+ listeners[i].action.call(listeners[i].context || this, event);
+ }
+
+ return this;
+ }
+};
+
+TL.Events.on = TL.Events.addEventListener;
+TL.Events.off = TL.Events.removeEventListener;
+TL.Events.fire = TL.Events.fireEvent;
+
+
+/* **********************************************
+ Begin TL.Browser.js
+********************************************** */
+
+/*
+ Based on Leaflet Browser
+ TL.Browser handles different browser and feature detections for internal use.
+*/
+
+
+(function() {
+
+ var ua = navigator.userAgent.toLowerCase(),
+ doc = document.documentElement,
+
+ ie = 'ActiveXObject' in window,
+
+ webkit = ua.indexOf('webkit') !== -1,
+ phantomjs = ua.indexOf('phantom') !== -1,
+ android23 = ua.search('android [23]') !== -1,
+
+ mobile = typeof orientation !== 'undefined',
+ msPointer = navigator.msPointerEnabled && navigator.msMaxTouchPoints && !window.PointerEvent,
+ pointer = (window.PointerEvent && navigator.pointerEnabled && navigator.maxTouchPoints) || msPointer,
+
+ ie3d = ie && ('transition' in doc.style),
+ webkit3d = ('WebKitCSSMatrix' in window) && ('m11' in new window.WebKitCSSMatrix()) && !android23,
+ gecko3d = 'MozPerspective' in doc.style,
+ opera3d = 'OTransition' in doc.style,
+ opera = window.opera;
+
+
+ var retina = 'devicePixelRatio' in window && window.devicePixelRatio > 1;
+
+ if (!retina && 'matchMedia' in window) {
+ var matches = window.matchMedia('(min-resolution:144dpi)');
+ retina = matches && matches.matches;
+ }
+
+ var touch = !window.L_NO_TOUCH && !phantomjs && (pointer || 'ontouchstart' in window || (window.DocumentTouch && document instanceof window.DocumentTouch));
+
+ TL.Browser = {
+ ie: ie,
+ ua: ua,
+ ie9: Boolean(ie && ua.match(/MSIE 9/i)),
+ ielt9: ie && !document.addEventListener,
+ webkit: webkit,
+ //gecko: (ua.indexOf('gecko') !== -1) && !webkit && !window.opera && !ie,
+ firefox: (ua.indexOf('gecko') !== -1) && !webkit && !window.opera && !ie,
+ android: ua.indexOf('android') !== -1,
+ android23: android23,
+ chrome: ua.indexOf('chrome') !== -1,
+ edge: ua.indexOf('edge/') !== -1,
+
+ ie3d: ie3d,
+ webkit3d: webkit3d,
+ gecko3d: gecko3d,
+ opera3d: opera3d,
+ any3d: !window.L_DISABLE_3D && (ie3d || webkit3d || gecko3d || opera3d) && !phantomjs,
+
+ mobile: mobile,
+ mobileWebkit: mobile && webkit,
+ mobileWebkit3d: mobile && webkit3d,
+ mobileOpera: mobile && window.opera,
+
+ touch: !! touch,
+ msPointer: !! msPointer,
+ pointer: !! pointer,
+
+ retina: !! retina,
+ orientation: function() {
+ var w = window.innerWidth,
+ h = window.innerHeight,
+ _orientation = "portrait";
+
+ if (w > h) {
+ _orientation = "landscape";
+ }
+ if (Math.abs(window.orientation) == 90) {
+ //_orientation = "landscape";
+ }
+ trace(_orientation);
+ return _orientation;
+ }
+ };
+
+}());
+
+
+/* **********************************************
+ Begin TL.Load.js
+********************************************** */
+
+/* TL.Load
+ Loads External Javascript and CSS
+================================================== */
+
+TL.Load = (function (doc) {
+ var loaded = [];
+
+ function isLoaded(url) {
+
+ var i = 0,
+ has_loaded = false;
+
+ for (i = 0; i < loaded.length; i++) {
+ if (loaded[i] == url) {
+ has_loaded = true;
+ }
+ }
+
+ if (has_loaded) {
+ return true;
+ } else {
+ loaded.push(url);
+ return false;
+ }
+
+ }
+
+ return {
+
+ css: function (urls, callback, obj, context) {
+ if (!isLoaded(urls)) {
+ TL.LoadIt.css(urls, callback, obj, context);
+ } else {
+ callback();
+ }
+ },
+
+ js: function (urls, callback, obj, context) {
+ if (!isLoaded(urls)) {
+ TL.LoadIt.js(urls, callback, obj, context);
+ } else {
+ callback();
+ }
+ }
+ };
+
+})(this.document);
+
+
+/*jslint browser: true, eqeqeq: true, bitwise: true, newcap: true, immed: true, regexp: false */
+
+/*
+LazyLoad makes it easy and painless to lazily load one or more external
+JavaScript or CSS files on demand either during or after the rendering of a web
+page.
+
+Supported browsers include Firefox 2+, IE6+, Safari 3+ (including Mobile
+Safari), Google Chrome, and Opera 9+. Other browsers may or may not work and
+are not officially supported.
+
+Visit https://github.com/rgrove/lazyload/ for more info.
+
+Copyright (c) 2011 Ryan Grove <ryan@wonko.com>
+All rights reserved.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the 'Software'), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+@module lazyload
+@class LazyLoad
+@static
+@version 2.0.3 (git)
+*/
+
+TL.LoadIt = (function (doc) {
+ // -- Private Variables ------------------------------------------------------
+
+ // User agent and feature test information.
+ var env,
+
+ // Reference to the <head> element (populated lazily).
+ head,
+
+ // Requests currently in progress, if any.
+ pending = {},
+
+ // Number of times we've polled to check whether a pending stylesheet has
+ // finished loading. If this gets too high, we're probably stalled.
+ pollCount = 0,
+
+ // Queued requests.
+ queue = {css: [], js: []},
+
+ // Reference to the browser's list of stylesheets.
+ styleSheets = doc.styleSheets;
+
+ // -- Private Methods --------------------------------------------------------
+
+ /**
+ Creates and returns an HTML element with the specified name and attributes.
+
+ @method createNode
+ @param {String} name element name
+ @param {Object} attrs name/value mapping of element attributes
+ @return {HTMLElement}
+ @private
+ */
+ function createNode(name, attrs) {
+ var node = doc.createElement(name), attr;
+
+ for (attr in attrs) {
+ if (attrs.hasOwnProperty(attr)) {
+ node.setAttribute(attr, attrs[attr]);
+ }
+ }
+
+ return node;
+ }
+
+ /**
+ Called when the current pending resource of the specified type has finished
+ loading. Executes the associated callback (if any) and loads the next
+ resource in the queue.
+
+ @method finish
+ @param {String} type resource type ('css' or 'js')
+ @private
+ */
+ function finish(type) {
+ var p = pending[type],
+ callback,
+ urls;
+
+ if (p) {
+ callback = p.callback;
+ urls = p.urls;
+
+ urls.shift();
+ pollCount = 0;
+
+ // If this is the last of the pending URLs, execute the callback and
+ // start the next request in the queue (if any).
+ if (!urls.length) {
+ callback && callback.call(p.context, p.obj);
+ pending[type] = null;
+ queue[type].length && load(type);
+ }
+ }
+ }
+
+ /**
+ Populates the <code>env</code> variable with user agent and feature test
+ information.
+
+ @method getEnv
+ @private
+ */
+ function getEnv() {
+ var ua = navigator.userAgent;
+
+ env = {
+ // True if this browser supports disabling async mode on dynamically
+ // created script nodes. See
+ // http://wiki.whatwg.org/wiki/Dynamic_Script_Execution_Order
+ async: doc.createElement('script').async === true
+ };
+
+ (env.webkit = /AppleWebKit\//.test(ua))
+ || (env.ie = /MSIE/.test(ua))
+ || (env.opera = /Opera/.test(ua))
+ || (env.gecko = /Gecko\//.test(ua))
+ || (env.unknown = true);
+ }
+
+ /**
+ Loads the specified resources, or the next resource of the specified type
+ in the queue if no resources are specified. If a resource of the specified
+ type is already being loaded, the new request will be queued until the
+ first request has been finished.
+
+ When an array of resource URLs is specified, those URLs will be loaded in
+ parallel if it is possible to do so while preserving execution order. All
+ browsers support parallel loading of CSS, but only Firefox and Opera
+ support parallel loading of scripts. In other browsers, scripts will be
+ queued and loaded one at a time to ensure correct execution order.
+
+ @method load
+ @param {String} type resource type ('css' or 'js')
+ @param {String|Array} urls (optional) URL or array of URLs to load
+ @param {Function} callback (optional) callback function to execute when the
+ resource is loaded
+ @param {Object} obj (optional) object to pass to the callback function
+ @param {Object} context (optional) if provided, the callback function will
+ be executed in this object's context
+ @private
+ */
+ function load(type, urls, callback, obj, context) {
+ var _finish = function () { finish(type); },
+ isCSS = type === 'css',
+ nodes = [],
+ i, len, node, p, pendingUrls, url;
+
+ env || getEnv();
+
+ if (urls) {
+ // If urls is a string, wrap it in an array. Otherwise assume it's an
+ // array and create a copy of it so modifications won't be made to the
+ // original.
+ urls = typeof urls === 'string' ? [urls] : urls.concat();
+
+ // Create a request object for each URL. If multiple URLs are specified,
+ // the callback will only be executed after all URLs have been loaded.
+ //
+ // Sadly, Firefox and Opera are the only browsers capable of loading
+ // scripts in parallel while preserving execution order. In all other
+ // browsers, scripts must be loaded sequentially.
+ //
+ // All browsers respect CSS specificity based on the order of the link
+ // elements in the DOM, regardless of the order in which the stylesheets
+ // are actually downloaded.
+ if (isCSS || env.async || env.gecko || env.opera) {
+ // Load in parallel.
+ queue[type].push({
+ urls : urls,
+ callback: callback,
+ obj : obj,
+ context : context
+ });
+ } else {
+ // Load sequentially.
+ for (i = 0, len = urls.length; i < len; ++i) {
+ queue[type].push({
+ urls : [urls[i]],
+ callback: i === len - 1 ? callback : null, // callback is only added to the last URL
+ obj : obj,
+ context : context
+ });
+ }
+ }
+ }
+
+ // If a previous load request of this type is currently in progress, we'll
+ // wait our turn. Otherwise, grab the next item in the queue.
+ if (pending[type] || !(p = pending[type] = queue[type].shift())) {
+ return;
+ }
+
+ head || (head = doc.head || doc.getElementsByTagName('head')[0]);
+ pendingUrls = p.urls;
+
+ for (i = 0, len = pendingUrls.length; i < len; ++i) {
+ url = pendingUrls[i];
+
+ if (isCSS) {
+ node = env.gecko ? createNode('style') : createNode('link', {
+ href: url,
+ rel : 'stylesheet'
+ });
+ } else {
+ node = createNode('script', {src: url});
+ node.async = false;
+ }
+
+ node.className = 'lazyload';
+ node.setAttribute('charset', 'utf-8');
+
+ if (env.ie && !isCSS) {
+ node.onreadystatechange = function () {
+ if (/loaded|complete/.test(node.readyState)) {
+ node.onreadystatechange = null;
+ _finish();
+ }
+ };
+ } else if (isCSS && (env.gecko || env.webkit)) {
+ // Gecko and WebKit don't support the onload event on link nodes.
+ if (env.webkit) {
+ // In WebKit, we can poll for changes to document.styleSheets to
+ // figure out when stylesheets have loaded.
+ p.urls[i] = node.href; // resolve relative URLs (or polling won't work)
+ pollWebKit();
+ } else {
+ // In Gecko, we can import the requested URL into a <style> node and
+ // poll for the existence of node.sheet.cssRules. Props to Zach
+ // Leatherman for calling my attention to this technique.
+ node.innerHTML = '@import "' + url + '";';
+ pollGecko(node);
+ }
+ } else {
+ node.onload = node.onerror = _finish;
+ }
+
+ nodes.push(node);
+ }
+
+ for (i = 0, len = nodes.length; i < len; ++i) {
+ head.appendChild(nodes[i]);
+ }
+ }
+
+ /**
+ Begins polling to determine when the specified stylesheet has finished loading
+ in Gecko. Polling stops when all pending stylesheets have loaded or after 10
+ seconds (to prevent stalls).
+
+ Thanks to Zach Leatherman for calling my attention to the @import-based
+ cross-domain technique used here, and to Oleg Slobodskoi for an earlier
+ same-domain implementation. See Zach's blog for more details:
+ http://www.zachleat.com/web/2010/07/29/load-css-dynamically/
+
+ @method pollGecko
+ @param {HTMLElement} node Style node to poll.
+ @private
+ */
+ function pollGecko(node) {
+ var hasRules;
+
+ try {
+ // We don't really need to store this value or ever refer to it again, but
+ // if we don't store it, Closure Compiler assumes the code is useless and
+ // removes it.
+ hasRules = !!node.sheet.cssRules;
+ } catch (ex) {
+ // An exception means the stylesheet is still loading.
+ pollCount += 1;
+
+ if (pollCount < 200) {
+ setTimeout(function () { pollGecko(node); }, 50);
+ } else {
+ // We've been polling for 10 seconds and nothing's happened. Stop
+ // polling and finish the pending requests to avoid blocking further
+ // requests.
+ hasRules && finish('css');
+ }
+
+ return;
+ }
+
+ // If we get here, the stylesheet has loaded.
+ finish('css');
+ }
+
+ /**
+ Begins polling to determine when pending stylesheets have finished loading
+ in WebKit. Polling stops when all pending stylesheets have loaded or after 10
+ seconds (to prevent stalls).
+
+ @method pollWebKit
+ @private
+ */
+ function pollWebKit() {
+ var css = pending.css, i;
+
+ if (css) {
+ i = styleSheets.length;
+
+ // Look for a stylesheet matching the pending URL.
+ while (--i >= 0) {
+ if (styleSheets[i].href === css.urls[0]) {
+ finish('css');
+ break;
+ }
+ }
+
+ pollCount += 1;
+
+ if (css) {
+ if (pollCount < 200) {
+ setTimeout(pollWebKit, 50);
+ } else {
+ // We've been polling for 10 seconds and nothing's happened, which may
+ // indicate that the stylesheet has been removed from the document
+ // before it had a chance to load. Stop polling and finish the pending
+ // request to prevent blocking further requests.
+ finish('css');
+ }
+ }
+ }
+ }
+
+ return {
+
+ /**
+ Requests the specified CSS URL or URLs and executes the specified
+ callback (if any) when they have finished loading. If an array of URLs is
+ specified, the stylesheets will be loaded in parallel and the callback
+ will be executed after all stylesheets have finished loading.
+
+ @method css
+ @param {String|Array} urls CSS URL or array of CSS URLs to load
+ @param {Function} callback (optional) callback function to execute when
+ the specified stylesheets are loaded
+ @param {Object} obj (optional) object to pass to the callback function
+ @param {Object} context (optional) if provided, the callback function
+ will be executed in this object's context
+ @static
+ */
+ css: function (urls, callback, obj, context) {
+ load('css', urls, callback, obj, context);
+ },
+
+ /**
+ Requests the specified JavaScript URL or URLs and executes the specified
+ callback (if any) when they have finished loading. If an array of URLs is
+ specified and the browser supports it, the scripts will be loaded in
+ parallel and the callback will be executed after all scripts have
+ finished loading.
+
+ Currently, only Firefox and Opera support parallel loading of scripts while
+ preserving execution order. In other browsers, scripts will be
+ queued and loaded one at a time to ensure correct execution order.
+
+ @method js
+ @param {String|Array} urls JS URL or array of JS URLs to load
+ @param {Function} callback (optional) callback function to execute when
+ the specified scripts are loaded
+ @param {Object} obj (optional) object to pass to the callback function
+ @param {Object} context (optional) if provided, the callback function
+ will be executed in this object's context
+ @static
+ */
+ js: function (urls, callback, obj, context) {
+ load('js', urls, callback, obj, context);
+ }
+
+ };
+})(this.document);
+
+
+/* **********************************************
+ Begin TL.TimelineConfig.js
+********************************************** */
+
+/* TL.TimelineConfig
+separate the configuration from the display (TL.Timeline)
+to make testing easier
+================================================== */
+TL.TimelineConfig = TL.Class.extend({
+
+ includes: [],
+ initialize: function (data) {
+ this.title = '';
+ this.scale = '';
+ this.events = [];
+ this.eras = [];
+ this.event_dict = {}; // despite name, all slides (events + title) indexed by slide.unique_id
+ this.messages = {
+ errors: [],
+ warnings: []
+ };
+
+ // Initialize the data
+ if (typeof data === 'object' && data.events) {
+ this.scale = data.scale;
+ this.events = [];
+ this._ensureValidScale(data.events);
+
+ if (data.title) {
+ var title_id = this._assignID(data.title);
+ this._tidyFields(data.title);
+ this.title = data.title;
+ this.event_dict[title_id] = this.title;
+ }
+
+ for (var i = 0; i < data.events.length; i++) {
+ try {
+ this.addEvent(data.events[i], true);
+ } catch (e) {
+ this.logError(e);
+ }
+ }
+
+ if (data.eras) {
+ for (var i = 0; i < data.eras.length; i++) {
+ try {
+ this.addEra(data.eras[i], true);
+ } catch (e) {
+ this.logError("Era " + i + ": " + e);
+ }
+ }
+ }
+
+ TL.DateUtil.sortByDate(this.events);
+ TL.DateUtil.sortByDate(this.eras);
+
+ }
+ },
+ logError: function(msg) {
+ trace(msg);
+ this.messages.errors.push(msg);
+ },
+ /*
+ * Return any accumulated error messages. If `sep` is passed, it should be a string which will be used to join all messages, resulting in a string return value. Otherwise,
+ * errors will be returned as an array.
+ */
+ getErrors: function(sep) {
+ if (sep) {
+ return this.messages.errors.join(sep);
+ } else {
+ return this.messages.errors;
+ }
+ },
+ /*
+ * Perform any sanity checks we can before trying to use this to make a timeline. Returns nothing, but errors will be logged
+ * such that after this is called, one can test `this.isValid()` to see if everything is OK.
+ */
+ validate: function() {
+ if (typeof(this.events) == "undefined" || typeof(this.events.length) == "undefined" || this.events.length == 0) {
+ this.logError("Timeline configuration has no events.")
+ }
+
+ // make sure all eras have start and end dates
+ for (var i = 0; i < this.eras.length; i++) {
+ if (typeof(this.eras[i].start_date) == 'undefined' || typeof(this.eras[i].end_date) == 'undefined') {
+ var era_identifier;
+ if (this.eras[i].text && this.eras[i].text.headline) {
+ era_identifier = this.eras[i].text.headline
+ } else {
+ era_identifier = "era " + (i+1);
+ }
+ this.logError("All eras must have start and end dates. [" + era_identifier + "]") // add internationalization (I18N) and context
+ }
+ };
+ },
+
+ isValid: function() {
+ return this.messages.errors.length == 0;
+ },
+ /* Add an event (including cleaning/validation) and return the unique id.
+ * All event data validation should happen in here.
+ * Throws: TL.Error for any validation problems.
+ */
+ addEvent: function(data, defer_sort) {
+ var event_id = this._assignID(data);
+
+ if (typeof(data.start_date) == 'undefined') {
+ throw new TL.Error("missing_start_date_err", event_id);
+ } else {
+ this._processDates(data);
+ this._tidyFields(data);
+ }
+
+ this.events.push(data);
+ this.event_dict[event_id] = data;
+
+ if (!defer_sort) {
+ TL.DateUtil.sortByDate(this.events);
+ }
+ return event_id;
+ },
+
+ addEra: function(data, defer_sort) {
+ var event_id = this._assignID(data);
+
+ if (typeof(data.start_date) == 'undefined') {
+ throw new TL.Error("missing_start_date_err", event_id);
+ } else {
+ this._processDates(data);
+ this._tidyFields(data);
+ }
+
+ this.eras.push(data);
+ this.event_dict[event_id] = data;
+
+ if (!defer_sort) {
+ TL.DateUtil.sortByDate(this.eras);
+ }
+ return event_id;
+ },
+
+ /**
+ * Given a slide, verify that its ID is unique, or assign it one which is.
+ * The assignment happens in this function, and the assigned ID is also
+ * the return value. Not thread-safe, because ids are not reserved
+ * when assigned here.
+ */
+ _assignID: function(slide) {
+ var slide_id = slide.unique_id;
+ if (!TL.Util.trim(slide_id)) {
+ // give it an ID if it doesn't have one
+ slide_id = (slide.text) ? TL.Util.slugify(slide.text.headline) : null;
+ }
+ // make sure it's unique and add it.
+ slide.unique_id = TL.Util.ensureUniqueKey(this.event_dict,slide_id);
+ return slide.unique_id
+ },
+
+ /**
+ * Given an array of slide configs (the events), ensure that each one has a distinct unique_id. The id of the title
+ * is also passed in because in most ways it functions as an event slide, and the event IDs must also all be unique
+ * from the title ID.
+ */
+ _makeUniqueIdentifiers: function(title_id, array) {
+ var used = [title_id];
+
+ // establish which IDs are assigned and if any appear twice, clear out successors.
+ for (var i = 0; i < array.length; i++) {
+ if (TL.Util.trim(array[i].unique_id)) {
+ array[i].unique_id = TL.Util.slugify(array[i].unique_id); // enforce valid
+ if (used.indexOf(array[i].unique_id) == -1) {
+ used.push(array[i].unique_id);
+ } else { // it was already used, wipe it out
+ array[i].unique_id = '';
+ }
+ }
+ };
+
+ if (used.length != (array.length + 1)) {
+ // at least some are yet to be assigned
+ for (var i = 0; i < array.length; i++) {
+ if (!array[i].unique_id) {
+ // use the headline for the unique ID if it's available
+ var slug = (array[i].text) ? TL.Util.slugify(array[i].text.headline) : null;
+ if (!slug) {
+ slug = TL.Util.unique_ID(6); // or generate a random ID
+ }
+ if (used.indexOf(slug) != -1) {
+ slug = slug + '-' + i; // use the index to get a unique ID.
+ }
+ used.push(slug);
+ array[i].unique_id = slug;
+ }
+ }
+ }
+ },
+ _ensureValidScale: function(events) {
+ if(!this.scale) {
+ trace("Determining scale dynamically");
+ this.scale = "human"; // default to human unless there's a slide which is explicitly 'cosmological' or one which has a cosmological year
+
+ for (var i = 0; i < events.length; i++) {
+ if (events[i].scale == 'cosmological') {
+ this.scale = 'cosmological';
+ break;
+ }
+ if (events[i].start_date && typeof(events[i].start_date.year) != "undefined") {
+ var d = new TL.BigDate(events[i].start_date);
+ var year = d.data.date_obj.year;
+ if(year < -271820 || year > 275759) {
+ this.scale = "cosmological";
+ break;
+ }
+ }
+ }
+ }
+ var dateCls = TL.DateUtil.SCALE_DATE_CLASSES[this.scale];
+ if (!dateCls) { this.logError("Don't know how to process dates on scale "+this.scale); }
+ },
+ /*
+ Given a thing which has a start_date and optionally an end_date, make sure that it is an instance
+ of the correct date class (for human or cosmological scale). For slides, remove redundant end dates
+ (people frequently configure an end date which is the same as the start date).
+ */
+ _processDates: function(slide_or_era) {
+ var dateCls = TL.DateUtil.SCALE_DATE_CLASSES[this.scale];
+ if(!(slide_or_era.start_date instanceof dateCls)) {
+ var start_date = slide_or_era.start_date;
+ slide_or_era.start_date = new dateCls(start_date);
+
+ // eliminate redundant end dates.
+ if (typeof(slide_or_era.end_date) != 'undefined' && !(slide_or_era.end_date instanceof dateCls)) {
+ var end_date = slide_or_era.end_date;
+ var equal = true;
+ for (property in start_date) {
+ equal = equal && (start_date[property] == end_date[property]);
+ }
+ if (equal) {
+ trace("End date same as start date is redundant; dropping end date");
+ delete slide_or_era.end_date;
+ } else {
+ slide_or_era.end_date = new dateCls(end_date);
+ }
+
+ }
+ }
+
+ },
+ /**
+ * Return the earliest date that this config knows about, whether it's a slide or an era
+ */
+ getEarliestDate: function() {
+ // counting that dates were sorted in initialization
+ var date = this.events[0].start_date;
+ if (this.eras && this.eras.length > 0) {
+ if (this.eras[0].start_date.isBefore(date)) {
+ return this.eras[0].start_date;
+ }
+ }
+ return date;
+
+ },
+ /**
+ * Return the latest date that this config knows about, whether it's a slide or an era, taking end_dates into account.
+ */
+ getLatestDate: function() {
+ var dates = [];
+ for (var i = 0; i < this.events.length; i++) {
+ if (this.events[i].end_date) {
+ dates.push({ date: this.events[i].end_date });
+ } else {
+ dates.push({ date: this.events[i].start_date });
+ }
+ }
+ for (var i = 0; i < this.eras.length; i++) {
+ if (this.eras[i].end_date) {
+ dates.push({ date: this.eras[i].end_date });
+ } else {
+ dates.push({ date: this.eras[i].start_date });
+ }
+ }
+ TL.DateUtil.sortByDate(dates, 'date');
+ return dates.slice(-1)[0].date;
+ },
+ _tidyFields: function(slide) {
+
+ function fillIn(obj,key,default_value) {
+ if (!default_value) default_value = '';
+ if (!obj.hasOwnProperty(key)) { obj[key] = default_value }
+ }
+
+ if (slide.group) {
+ slide.group = TL.Util.trim(slide.group);
+ }
+
+ if (!slide.text) {
+ slide.text = {};
+ }
+ fillIn(slide.text,'text');
+ fillIn(slide.text,'headline');
+ }
+});
+
+
+/* **********************************************
+ Begin TL.ConfigFactory.js
+********************************************** */
+
+/* TL.ConfigFactory.js
+ * Build TimelineConfig objects from other data sources
+ */
+;(function(TL){
+ /*
+ * Convert a URL to a Google Spreadsheet (typically a /pubhtml version but somewhat flexible) into an object with the spreadsheet key (ID) and worksheet ID.
+
+ If `url` is actually a string which is only letters, numbers, '-' and '_', then it's assumed to be an ID already. If we had a more precise way of testing to see if the input argument was a valid key, we might apply it, but I don't know where that's documented.
+
+ If we're pretty sure this isn't a bare key or a url that could be used to find a Google spreadsheet then return null.
+ */
+ function parseGoogleSpreadsheetURL(url) {
+ parts = {
+ key: null,
+ worksheet: 0 // not really sure how to use this to get the feed for that sheet, so this is not ready except for first sheet right now
+ }
+ // key as url parameter (old-fashioned)
+ var key_pat = /\bkey=([-_A-Za-z0-9]+)&?/i;
+ var url_pat = /docs.google.com\/spreadsheets(.*?)\/d\//; // fixing issue of URLs with u/0/d
+
+ if (url.match(key_pat)) {
+ parts.key = url.match(key_pat)[1];
+ // can we get a worksheet from this form?
+ } else if (url.match(url_pat)) {
+ var pos = url.search(url_pat) + url.match(url_pat)[0].length;
+ var tail = url.substr(pos);
+ parts.key = tail.split('/')[0]
+ if (url.match(/\?gid=(\d+)/)) {
+ parts.worksheet = url.match(/\?gid=(\d+)/)[1];
+ }
+ } else if (url.match(/^\b[-_A-Za-z0-9]+$/)) {
+ parts.key = url;
+ }
+
+ if (parts.key) {
+ return parts;
+ } else {
+ return null;
+ }
+ }
+
+ function extractGoogleEntryData_V1(item) {
+ var item_data = {}
+ for (k in item) {
+ if (k.indexOf('gsx$') == 0) {
+ item_data[k.substr(4)] = item[k].$t;
+ }
+ }
+ if (TL.Util.isEmptyObject(item_data)) return null;
+ var d = {
+ media: {
+ caption: item_data.mediacaption || '',
+ credit: item_data.mediacredit || '',
+ url: item_data.media || '',
+ thumbnail: item_data.mediathumbnail || ''
+ },
+ text: {
+ headline: item_data.headline || '',
+ text: item_data.text || ''
+ },
+ group: item_data.tag || '',
+ type: item_data.type || ''
+ }
+ if (item_data.startdate) {
+ d['start_date'] = TL.Date.parseDate(item_data.startdate);
+ }
+ if (item_data.enddate) {
+ d['end_date'] = TL.Date.parseDate(item_data.enddate);
+ }
+
+
+ return d;
+ }
+
+ function extractGoogleEntryData_V3(item) {
+
+ function clean_integer(s) {
+ if (s) {
+ return s.replace(/[\s,]+/g,''); // doesn't handle '.' as comma separator, but how to distinguish that from decimal separator?
+ }
+ }
+
+ var item_data = {}
+ for (k in item) {
+ if (k.indexOf('gsx$') == 0) {
+ item_data[k.substr(4)] = TL.Util.trim(item[k].$t);
+ }
+ }
+ if (TL.Util.isEmptyObject(item_data)) return null;
+ var d = {
+ media: {
+ caption: item_data.mediacaption || '',
+ credit: item_data.mediacredit || '',
+ url: item_data.media || '',
+ thumbnail: item_data.mediathumbnail || ''
+ },
+ text: {
+ headline: item_data.headline || '',
+ text: item_data.text || ''
+ },
+ start_date: {
+ year: clean_integer(item_data.year),
+ month: clean_integer(item_data.month) || '',
+ day: clean_integer(item_data.day) || ''
+ },
+ end_date: {
+ year: clean_integer(item_data.endyear) || '',
+ month: clean_integer(item_data.endmonth) || '',
+ day: clean_integer(item_data.endday) || ''
+ },
+ display_date: item_data.displaydate || '',
+
+ type: item_data.type || ''
+ }
+
+ if (item_data.time) {
+ TL.Util.mergeData(d.start_date,TL.DateUtil.parseTime(item_data.time));
+ }
+
+ if (item_data.endtime) {
+ TL.Util.mergeData(d.end_date,TL.DateUtil.parseTime(item_data.endtime));
+ }
+
+
+ if (item_data.group) {
+ d.group = item_data.group;
+ }
+
+ if (d.end_date.year == '') {
+ var bad_date = d.end_date;
+ delete d.end_date;
+ if (bad_date.month != '' || bad_date.day != '' || bad_date.time != '') {
+ var label = d.text.headline ||
+ trace("Invalid end date for spreadsheet row. Must have a year if any other date fields are specified.");
+ trace(item);
+ }
+ }
+
+ if (item_data.background) {
+ if (item_data.background.match(/^(https?:)?\/\/?/)) { // support http, https, protocol relative, site relative
+ d['background'] = { 'url': item_data.background }
+ } else { // for now we'll trust it's a color
+ d['background'] = { 'color': item_data.background }
+ }
+ }
+
+ return d;
+ }
+
+
+
+ var getGoogleItemExtractor = function(data) {
+ if (typeof data.feed.entry === 'undefined'
+ || data.feed.entry.length == 0) {
+ throw new TL.Error("empty_feed_err");
+ }
+ var entry = data.feed.entry[0];
+
+ if (typeof entry.gsx$startdate !== 'undefined') {
+ // check headers V1
+ // var headers_V1 = ['startdate', 'enddate', 'headline','text','media','mediacredit','mediacaption','mediathumbnail','media','type','tag'];
+ // for (var i = 0; i < headers_V1.length; i++) {
+ // if (typeof entry['gsx$' + headers_V1[i]] == 'undefined') {
+ // throw new TL.Error("invalid_data_format_err");
+ // }
+ // }
+ return extractGoogleEntryData_V1;
+ } else if (typeof entry.gsx$year !== 'undefined') {
+ // check rest of V3 headers
+ var headers_V3 = ['month', 'day', 'time', 'endmonth', 'endyear', 'endday', 'endtime', 'displaydate', 'headline','text','media','mediacredit','mediacaption','mediathumbnail','type','group','background'];
+ // for (var i = 0; i < headers_V3.length; i++) {
+ // if (typeof entry['gsx$' + headers_V3[i]] == 'undefined') {
+ // throw new TL.Error("invalid_data_format_err");
+ // }
+ // }
+ return extractGoogleEntryData_V3;
+ }
+ throw new TL.Error("invalid_data_format_err");
+ }
+
+ var buildGoogleFeedURL = function(key, api_version) {
+ if (api_version == 'v4') {
+ return "https://sheets.googleapis.com/v4/spreadsheets/" + key + "/values/A1:R1000?key=AIzaSyCInR0kjJJ2Co6aQAXjLBQ14CEHam3K0xg";
+ } else {
+ return "https://spreadsheets.google.com/feeds/list/" + key + "/1/public/values?alt=json";
+ }
+ }
+
+ var jsonFromGoogleURL = function(google_url) {
+ var api_version = 'v3';
+ var parts = parseGoogleSpreadsheetURL(google_url);
+ if (parts && parts.key) {
+ var spreadsheet_key = parts.key;
+ } else {
+ throw new TL.Error('invalid_url_err', google_url);
+ }
+
+ var url = buildGoogleFeedURL(spreadsheet_key, api_version);
+
+ var response = TL.ajax({
+ url: url,
+ async: false
+ });
+
+ // tricky because errors can be in the response object or in the parsed data...
+
+ if (response.status != 200) {
+ console.log("Error fetching data " + api_version + ": " + response.status + " - " + response.statusText);
+ api_version = 'v4';
+ var url = buildGoogleFeedURL(spreadsheet_key, api_version);
+ console.log("trying v4 - " + google_url);
+ var response = TL.ajax({
+ url: url,
+ async: false
+ });
+
+ if (response.status == 403) {
+ throw new TL.Error('invalid_url_share_required');
+ } else if (response.status != 200) {
+ var msg = "Error fetching data " + api_version + ": " + response.status + " - " + response.statusText;
+ console.log(msg);
+ throw new TL.Error("google_error", msg);
+ }
+ }
+
+
+ var data = JSON.parse(response.responseText);
+
+ if (data.error) {
+ var msg = "Error fetching data " + api_version + ": " + response.status + " - " + response.statusText;
+ console.log(msg);
+ console.log(data.error);
+ throw new TL.Error("google_error", msg);
+ }
+
+ return googleFeedJSONtoTimelineJSON(data);
+ }
+
+ function extractGoogleEntryData_V4(column, item) {
+ function clean_integer(s) {
+ if (s) {
+ return s.replace(/[\s,]+/g,''); // doesn't handle '.' as comma separator, but how to distinguish that from decimal separator?
+ }
+ }
+ // console.log(item);
+ var item_data = {};
+ for (var i = 1; i < item.length; i++) {
+ if (column.length >= i) {
+ var column_name = column[i].toLowerCase().replace(" ", "");
+ item_data[column_name] = item[i];
+ }
+
+ }
+
+ var event = {
+ media: {
+ caption: item_data.mediacaption || '',
+ credit: item_data.mediacredit || '',
+ url: item_data.media || '',
+ thumbnail: item_data.mediathumbnail || ''
+ },
+ text: {
+ headline: item_data.headline || '',
+ text: item_data.text || ''
+ },
+ start_date: {
+ year: clean_integer(item[0]),
+ month: clean_integer(item[1]) || '',
+ day: clean_integer(item[2]) || ''
+ },
+ end_date: {
+ year: clean_integer(item_data.endyear) || '',
+ month: clean_integer(item_data.endmonth) || '',
+ day: clean_integer(item_data.endday) || ''
+ },
+ display_date: item_data.displaydate || '',
+
+ type: item_data.type || ''
+ }
+
+
+ if (item_data.time) {
+ TL.Util.mergeData(event.start_date,TL.DateUtil.parseTime(item[3]));
+ }
+
+ if (item_data.endtime) {
+ TL.Util.mergeData(event.end_date,TL.DateUtil.parseTime(item_data.endtime));
+ }
+
+ if (item_data.group) {
+ event.group = item_data.group;
+ }
+
+ if (event.end_date.year == '') {
+ var bad_date = event.end_date;
+ delete event.end_date;
+ if (bad_date.month != '' || bad_date.day != '' || bad_date.time != '') {
+ var label = event.text.headline ||
+ trace("Invalid end date for spreadsheet row. Must have a year if any other date fields are specified.");
+ trace(item);
+ }
+ }
+
+ if (item_data.background) {
+ if (item_data.background.match(/^(https?:)?\/\/?/)) { // support http, https, protocol relative, site relative
+ event['background'] = { 'url': item_data.background }
+ } else { // for now we'll trust it's a color
+ event['background'] = { 'color': item_data.background }
+ }
+ }
+
+ return event;
+ }
+
+ var googleFeedJSONtoTimelineJSON = function(data) {
+ var timeline_config = { 'events': [], 'errors': [], 'warnings': [], 'eras': [] }
+
+ if (data.values) {
+ // Google Sheets API v4
+ for (var i = 1; i < data.values.length; i++) {
+ var event = extractGoogleEntryData_V4(data.values[0], data.values[i]);
+ if (event) { // blank rows return null
+ var row_type = 'event';
+ if (typeof (event.type) != 'undefined') {
+ row_type = event.type;
+ delete event.type;
+ }
+ if (row_type == 'title') {
+ if (!timeline_config.title) {
+ timeline_config.title = event;
+ } else {
+ timeline_config.warnings.push("Multiple title slides detected.");
+ timeline_config.events.push(event);
+ }
+ } else if (row_type == 'era') {
+ timeline_config.eras.push(event);
+ } else {
+ timeline_config.events.push(event);
+ }
+ }
+ }
+ } else {
+
+ // Google Sheets API v3
+ var extract = getGoogleItemExtractor(data);
+ for (var i = 0; i < data.feed.entry.length; i++) {
+ try {
+ var event = extract(data.feed.entry[i]);
+ if (event) { // blank rows return null
+ var row_type = 'event';
+ if (typeof(event.type) != 'undefined') {
+ row_type = event.type;
+ delete event.type;
+ }
+ if (row_type == 'title') {
+ if (!timeline_config.title) {
+ timeline_config.title = event;
+ } else {
+ timeline_config.warnings.push("Multiple title slides detected.");
+ timeline_config.events.push(event);
+ }
+ } else if (row_type == 'era') {
+ timeline_config.eras.push(event);
+ } else {
+ timeline_config.events.push(event);
+ }
+ }
+ } catch(e) {
+ if (e.message) {
+ e = e.message;
+ }
+ timeline_config.errors.push(e + " ["+ i +"]");
+ }
+ };
+
+ }
+
+ return timeline_config;
+
+ }
+
+ var makeConfig = function(url, callback) {
+ var tc,
+ key = parseGoogleSpreadsheetURL(url);
+
+ if (key) {
+ try {
+ var json = jsonFromGoogleURL(url);
+ } catch(e) {
+ tc = new TL.TimelineConfig();
+ if (e.name == 'NetworkError') {
+ tc.logError(new TL.Error("network_err"));
+ } else if(e.name == 'TL.Error') {
+ tc.logError(e);
+ } else {
+ tc.logError(new TL.Error("unknown_read_err", e.name));
+ }
+ callback(tc);
+ return;
+ }
+ tc = new TL.TimelineConfig(json);
+ if (json.errors) {
+ for (var i = 0; i < json.errors.length; i++) {
+ tc.logError(json.errors[i]);
+ };
+ }
+ callback(tc);
+ } else {
+ TL.ajax({
+ url: url,
+ dataType: 'json',
+ success: function(data){
+ try {
+ tc = new TL.TimelineConfig(data);
+ } catch(e) {
+ tc = new TL.TimelineConfig();
+ tc.logError(e);
+ }
+ callback(tc);
+ },
+ error: function(xhr, errorType, error) {
+ tc = new TL.TimelineConfig();
+ if (errorType == 'parsererror') {
+ var error = new TL.Error("invalid_url_err");
+ } else {
+ var error = new TL.Error("unknown_read_err", errorType)
+ }
+ tc.logError(error);
+ callback(tc);
+ }
+ });
+
+ }
+ }
+
+ TL.ConfigFactory = {
+ // export for unit testing and use by authoring tool
+ parseGoogleSpreadsheetURL: parseGoogleSpreadsheetURL,
+ // export for unit testing
+ googleFeedJSONtoTimelineJSON: googleFeedJSONtoTimelineJSON,
+
+
+ fromGoogle: function(url) {
+ console.warn("TL.ConfigFactory.fromGoogle is deprecated and will be removed soon. Use TL.ConfigFactory.makeConfig(url,callback)")
+ return jsonFromGoogleURL(url);
+
+ },
+
+ /*
+ * Given a URL to a Timeline data source, read the data, create a TimelineConfig
+ * object, and call the given `callback` function passing the created config as
+ * the only argument. This should be the main public interface to getting configs
+ * from any kind of URL, Google or direct JSON.
+ */
+ makeConfig: makeConfig,
+ }
+})(TL)
+
+
+/* **********************************************
+ Begin TL.Language.js
+********************************************** */
+
+TL.Language = function(options) {
+ // borrowed from http://stackoverflow.com/a/14446414/102476
+ for (k in TL.Language.languages.en) {
+ this[k] = TL.Language.languages.en[k];
+ }
+ if (options && options.language && typeof(options.language) == 'string' && options.language != 'en') {
+ var code = options.language;
+ if (!(code in TL.Language.languages)) {
+ if (/\.json$/.test(code)) {
+ var url = code;
+ } else {
+ var fragment = "/locale/" + code + ".json";
+ var script_path = options.script_path || TL.Timeline.source_path;
+ if (/\/$/.test(script_path)) { fragment = fragment.substr(1)}
+ var url = script_path + fragment;
+ }
+ var self = this;
+ var xhr = TL.ajax({
+ url: url, async: false
+ });
+ if (xhr.status == 200) {
+ TL.Language.languages[code] = JSON.parse(xhr.responseText);
+ } else {
+ throw "Could not load language [" + code + "]: " + xhr.statusText;
+ }
+ }
+ TL.Util.mergeData(this,TL.Language.languages[code]);
+
+ }
+}
+
+TL.Language.formatNumber = function(val,mask) {
+ if (mask.match(/%(\.(\d+))?f/)) {
+ var match = mask.match(/%(\.(\d+))?f/);
+ var token = match[0];
+ if (match[2]) {
+ val = val.toFixed(match[2]);
+ }
+ return mask.replace(token,val);
+ }
+ // use mask as literal display value.
+ return mask;
+}
+
+
+
+/* TL.Util.mergeData is shallow, we have nested dicts.
+ This is a simplistic handling but should work.
+ */
+TL.Language.prototype.mergeData = function(lang_json) {
+ for (k in TL.Language.languages.en) {
+ if (lang_json[k]) {
+ if (typeof(this[k]) == 'object') {
+ TL.Util.mergeData(lang_json[k], this[k]);
+ } else {
+ this[k] = lang_json[k]; // strings, mostly
+ }
+ }
+ }
+}
+
+TL.Language.fallback = { messages: {} }; // placeholder to satisfy IE8 early compilation
+TL.Language.prototype.getMessage = function(k) {
+ return this.messages[k] || TL.Language.fallback.messages[k] || k;
+}
+
+TL.Language.prototype._ = TL.Language.prototype.getMessage; // keep it concise
+
+TL.Language.prototype.formatDate = function(date, format_name) {
+
+ if (date.constructor == Date) {
+ return this.formatJSDate(date, format_name);
+ }
+
+ if (date.constructor == TL.BigYear) {
+ return this.formatBigYear(date, format_name);
+ }
+
+ if (date.data && date.data.date_obj) {
+ return this.formatDate(date.data.date_obj, format_name);
+ }
+
+ trace("Unfamiliar date presented for formatting");
+ return date.toString();
+}
+
+TL.Language.prototype.formatBigYear = function(bigyear, format_name) {
+ var the_year = bigyear.year;
+ var format_list = this.bigdateformats[format_name] || this.bigdateformats['fallback'];
+
+ if (format_list) {
+ for (var i = 0; i < format_list.length; i++) {
+ var tuple = format_list[i];
+ if (Math.abs(the_year / tuple[0]) > 1) {
+ // will we ever deal with distant future dates?
+ return TL.Language.formatNumber(Math.abs(the_year / tuple[0]),tuple[1])
+ }
+ };
+
+ return the_year.toString();
+
+ } else {
+ trace("Language file dateformats missing cosmological. Falling back.");
+ return TL.Language.formatNumber(the_year,format_name);
+ }
+}
+
+TL.Language.prototype.formatJSDate = function(js_date, format_name) {
+ // ultimately we probably want this to work with TL.Date instead of (in addition to?) JS Date
+ // utc, timezone and timezoneClip are carry over from Steven Levithan implementation. We probably aren't going to use them.
+ var self = this;
+ var formatPeriod = function(fmt, value) {
+ var formats = self.period_labels[fmt];
+ if (formats) {
+ var fmt = (value < 12) ? formats[0] : formats[1];
+ }
+ return "<span class='tl-timeaxis-timesuffix'>" + fmt + "</span>";
+ }
+
+ var utc = false,
+ timezone = /\b(?:[PMCEA][SDP]T|(?:Pacific|Mountain|Central|Eastern|Atlantic) (?:Standard|Daylight|Prevailing) Time|(?:GMT|UTC)(?:[-+]\d{4})?)\b/g,
+ timezoneClip = /[^-+\dA-Z]/g;
+
+
+ if (!format_name) {
+ format_name = 'full';
+ }
+
+ var mask = this.dateformats[format_name] || TL.Language.fallback.dateformats[format_name];
+ if (!mask) {
+ mask = format_name; // allow custom format strings
+ }
+
+
+ var _ = utc ? "getUTC" : "get",
+ d = js_date[_ + "Date"](),
+ D = js_date[_ + "Day"](),
+ m = js_date[_ + "Month"](),
+ y = js_date[_ + "FullYear"](),
+ H = js_date[_ + "Hours"](),
+ M = js_date[_ + "Minutes"](),
+ s = js_date[_ + "Seconds"](),
+ L = js_date[_ + "Milliseconds"](),
+ o = utc ? 0 : js_date.getTimezoneOffset(),
+ year = "",
+ flags = {
+ d: d,
+ dd: TL.Util.pad(d),
+ ddd: this.date.day_abbr[D],
+ dddd: this.date.day[D],
+ m: m + 1,
+ mm: TL.Util.pad(m + 1),
+ mmm: this.date.month_abbr[m],
+ mmmm: this.date.month[m],
+ yy: String(y).slice(2),
+ yyyy: (y < 0 && this.has_negative_year_modifier()) ? Math.abs(y) : y,
+ h: H % 12 || 12,
+ hh: TL.Util.pad(H % 12 || 12),
+ H: H,
+ HH: TL.Util.pad(H),
+ M: M,
+ MM: TL.Util.pad(M),
+ s: s,
+ ss: TL.Util.pad(s),
+ l: TL.Util.pad(L, 3),
+ L: TL.Util.pad(L > 99 ? Math.round(L / 10) : L),
+ t: formatPeriod('t',H),
+ tt: formatPeriod('tt',H),
+ T: formatPeriod('T',H),
+ TT: formatPeriod('TT',H),
+ Z: utc ? "UTC" : (String(js_date).match(timezone) || [""]).pop().replace(timezoneClip, ""),
+ o: (o > 0 ? "-" : "+") + TL.Util.pad(Math.floor(Math.abs(o) / 60) * 100 + Math.abs(o) % 60, 4),
+ S: ["th", "st", "nd", "rd"][d % 10 > 3 ? 0 : (d % 100 - d % 10 != 10) * d % 10]
+ };
+
+ var formatted = mask.replace(TL.Language.DATE_FORMAT_TOKENS, function ($0) {
+ return $0 in flags ? flags[$0] : $0.slice(1, $0.length - 1);
+ });
+
+ return this._applyEra(formatted, y);
+}
+
+TL.Language.prototype.has_negative_year_modifier = function() {
+ return Boolean(this.era_labels.negative_year.prefix || this.era_labels.negative_year.suffix);
+}
+
+
+TL.Language.prototype._applyEra = function(formatted_date, original_year) {
+ // trusts that the formatted_date was property created with a non-negative year if there are
+ // negative affixes to be applied
+ var labels = (original_year < 0) ? this.era_labels.negative_year : this.era_labels.positive_year;
+ var result = '';
+ if (labels.prefix) { result += '<span>' + labels.prefix + '</span> ' }
+ result += formatted_date;
+ if (labels.suffix) { result += ' <span>' + labels.suffix + '</span>' }
+ return result;
+}
+
+
+TL.Language.DATE_FORMAT_TOKENS = /d{1,4}|m{1,4}|yy(?:yy)?|([HhMsTt])\1?|[LloSZ]|"[^"]*"|'[^']*'/g;
+
+TL.Language.languages = {
+ /*
+ This represents the canonical list of message keys which translation files should handle. The existence of the 'en.json' file should not mislead you.
+ It is provided more as a starting point for someone who wants to provide a
+ new translation since the form for non-default languages (JSON not JS) is slightly different from what appears below. Also, those files have some message keys grandfathered in from TimelineJS2 which we'd rather not have to
+ get "re-translated" if we use them.
+*/
+ en: {
+ name: "English",
+ lang: "en",
+ api: {
+ wikipedia: "en" // the two letter code at the beginning of the Wikipedia subdomain for this language
+ },
+ messages: {
+ loading: "Loading",
+ wikipedia: "From Wikipedia, the free encyclopedia",
+ error: "Error",
+ contract_timeline: "Contract Timeline",
+ return_to_title: "Return to Title",
+ loading_content: "Loading Content",
+ expand_timeline: "Expand Timeline",
+ loading_timeline: "Loading Timeline... ",
+ swipe_to_navigate: "Swipe to Navigate<br><span class='tl-button'>OK</span>",
+ unknown_read_err: "An unexpected error occurred trying to read your spreadsheet data",
+ invalid_url_err: "Unable to read Timeline data. Make sure your URL is for a Google Spreadsheet or a Timeline JSON file.",
+ invalid_url_share_required: "Because of unexpected changes to Google's data access API, the creator of this timeline must enable 'anyone with the url can read' access for this spreadsheet. See timeline.knightlab.com for more information.",
+ network_err: "Unable to read your Google Spreadsheet. Make sure you have published it to the web.",
+ empty_feed_err: "No data entries found",
+ missing_start_date_err: "Missing start_date",
+ invalid_data_format_err: "Header row has been modified.",
+ date_compare_err: "Can't compare TL.Dates on different scales",
+ invalid_scale_err: "Invalid scale",
+ invalid_date_err: "Invalid date: month, day and year must be numbers.",
+ invalid_separator_error: "Invalid time: misuse of : or . as separator.",
+ invalid_hour_err: "Invalid time (hour)",
+ invalid_minute_err: "Invalid time (minute)",
+ invalid_second_err: "Invalid time (second)",
+ invalid_fractional_err: "Invalid time (fractional seconds)",
+ invalid_second_fractional_err: "Invalid time (seconds and fractional seconds)",
+ invalid_year_err: "Invalid year",
+ flickr_notfound_err: "Photo not found or private",
+ flickr_invalidurl_err: "Invalid Flickr URL",
+ imgur_invalidurl_err: "Invalid Imgur URL",
+ twitter_invalidurl_err: "Invalid Twitter URL",
+ twitter_load_err: "Unable to load Tweet",
+ twitterembed_invalidurl_err: "Invalid Twitter Embed url",
+ wikipedia_load_err: "Unable to load Wikipedia entry",
+ youtube_invalidurl_err: "Invalid YouTube URL",
+ spotify_invalid_url: "Invalid Spotify URL",
+ template_value_err: "No value provided for variable",
+ invalid_rgb_err: "Invalid RGB argument",
+ time_scale_scale_err: "Don't know how to get date from time for scale",
+ axis_helper_no_options_err: "Axis helper must be configured with options",
+ axis_helper_scale_err: "No AxisHelper available for scale",
+ invalid_integer_option: "Invalid option value—must be a whole number."
+ },
+ date: {
+ month: ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"],
+ month_abbr: ["Jan.", "Feb.", "March", "April", "May", "June", "July", "Aug.", "Sept.", "Oct.", "Nov.", "Dec."],
+ day: ["Sunday","Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"],
+ day_abbr: ["Sun.","Mon.", "Tues.", "Wed.", "Thurs.", "Fri.", "Sat."]
+ },
+ era_labels: { // specify prefix or suffix to apply to formatted date. Blanks mean no change.
+ positive_year: {
+ prefix: '',
+ suffix: ''
+ },
+ negative_year: { // if either of these is specified, the year will be converted to positive before they are applied
+ prefix: '',
+ suffix: 'BCE'
+ }
+ },
+ period_labels: { // use of t/tt/T/TT legacy of original Timeline date format
+ t: ['a', 'p'],
+ tt: ['am', 'pm'],
+ T: ['A', 'P'],
+ TT: ['AM', 'PM']
+ },
+ dateformats: {
+ year: "yyyy",
+ month_short: "mmm",
+ month: "mmmm yyyy",
+ full_short: "mmm d",
+ full: "mmmm d',' yyyy",
+ time: "h:MM:ss TT' <small>'mmmm d',' yyyy'</small>'",
+ time_short: "h:MM:ss TT",
+ time_no_seconds_short: "h:MM TT",
+ time_no_minutes_short: "h TT",
+ time_no_seconds_small_date: "h:MM TT' <small>'mmmm d',' yyyy'</small>'",
+ time_milliseconds: "l",
+ full_long: "mmm d',' yyyy 'at' h:MM TT",
+ full_long_small_date: "h:MM TT' <small>mmm d',' yyyy'</small>'"
+ },
+ bigdateformats: {
+ fallback: [ // a list of tuples, with t[0] an order of magnitude and t[1] a format string. format string syntax may change...
+ [1000000000,"%.2f billion years ago"],
+ [1000000,"%.1f million years ago"],
+ [1000,"%.1f thousand years ago"],
+ [1, "%f years ago"]
+ ],
+ compact: [
+ [1000000000,"%.2f bya"],
+ [1000000,"%.1f mya"],
+ [1000,"%.1f kya"],
+ [1, "%f years ago"]
+ ],
+ verbose: [
+ [1000000000,"%.2f billion years ago"],
+ [1000000,"%.1f million years ago"],
+ [1000,"%.1f thousand years ago"],
+ [1, "%f years ago"]
+ ]
+ }
+ }
+}
+
+TL.Language.fallback = new TL.Language();
+
+
+/* **********************************************
+ Begin TL.I18NMixins.js
+********************************************** */
+
+/* TL.I18NMixins
+ assumes that its class has an options object with a TL.Language instance
+================================================== */
+TL.I18NMixins = {
+ getLanguage: function() {
+ if (this.options && this.options.language) {
+ return this.options.language;
+ }
+ trace("Expected a language option");
+ return TL.Language.fallback;
+ },
+
+ _: function(msg) {
+ return this.getLanguage()._(msg);
+ }
+}
+
+
+/* **********************************************
+ Begin TL.Ease.js
+********************************************** */
+
+/* The equations defined here are open source under BSD License.
+ * http://www.robertpenner.com/easing_terms_of_use.html (c) 2003 Robert Penner
+ * Adapted to single time-based by
+ * Brian Crescimanno <brian.crescimanno@gmail.com>
+ * Ken Snyder <kendsnyder@gmail.com>
+ */
+
+/** MIT License
+ *
+ * KeySpline - use bezier curve for transition easing function
+ * Copyright (c) 2012 Gaetan Renaudeau <renaudeau.gaetan@gmail.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+/**
+ * KeySpline - use bezier curve for transition easing function
+ * is inspired from Firefox's nsSMILKeySpline.cpp
+ * Usage:
+ * var spline = new KeySpline(0.25, 0.1, 0.25, 1.0)
+ * spline.get(x) => returns the easing value | x must be in [0, 1] range
+ */
+
+TL.Easings = {
+ ease: [0.25, 0.1, 0.25, 1.0],
+ linear: [0.00, 0.0, 1.00, 1.0],
+ easein: [0.42, 0.0, 1.00, 1.0],
+ easeout: [0.00, 0.0, 0.58, 1.0],
+ easeinout: [0.42, 0.0, 0.58, 1.0]
+};
+
+TL.Ease = {
+ KeySpline: function(a) {
+ //KeySpline: function(mX1, mY1, mX2, mY2) {
+ this.get = function(aX) {
+ if (a[0] == a[1] && a[2] == a[3]) return aX; // linear
+ return CalcBezier(GetTForX(aX), a[1], a[3]);
+ }
+
+ function A(aA1, aA2) {
+ return 1.0 - 3.0 * aA2 + 3.0 * aA1;
+ }
+
+ function B(aA1, aA2) {
+ return 3.0 * aA2 - 6.0 * aA1;
+ }
+
+ function C(aA1) {
+ return 3.0 * aA1;
+ }
+
+ // Returns x(t) given t, x1, and x2, or y(t) given t, y1, and y2.
+
+ function CalcBezier(aT, aA1, aA2) {
+ return ((A(aA1, aA2) * aT + B(aA1, aA2)) * aT + C(aA1)) * aT;
+ }
+
+ // Returns dx/dt given t, x1, and x2, or dy/dt given t, y1, and y2.
+
+ function GetSlope(aT, aA1, aA2) {
+ return 3.0 * A(aA1, aA2) * aT * aT + 2.0 * B(aA1, aA2) * aT + C(aA1);
+ }
+
+ function GetTForX(aX) {
+ // Newton raphson iteration
+ var aGuessT = aX;
+ for (var i = 0; i < 4; ++i) {
+ var currentSlope = GetSlope(aGuessT, a[0], a[2]);
+ if (currentSlope == 0.0) return aGuessT;
+ var currentX = CalcBezier(aGuessT, a[0], a[2]) - aX;
+ aGuessT -= currentX / currentSlope;
+ }
+ return aGuessT;
+ }
+ },
+
+ easeInSpline: function(t) {
+ var spline = new TL.Ease.KeySpline(TL.Easings.easein);
+ return spline.get(t);
+ },
+
+ easeInOutExpo: function(t) {
+ var spline = new TL.Ease.KeySpline(TL.Easings.easein);
+ return spline.get(t);
+ },
+
+ easeOut: function(t) {
+ return Math.sin(t * Math.PI / 2);
+ },
+ easeOutStrong: function(t) {
+ return (t == 1) ? 1 : 1 - Math.pow(2, - 10 * t);
+ },
+ easeIn: function(t) {
+ return t * t;
+ },
+ easeInStrong: function(t) {
+ return (t == 0) ? 0 : Math.pow(2, 10 * (t - 1));
+ },
+ easeOutBounce: function(pos) {
+ if ((pos) < (1 / 2.75)) {
+ return (7.5625 * pos * pos);
+ } else if (pos < (2 / 2.75)) {
+ return (7.5625 * (pos -= (1.5 / 2.75)) * pos + .75);
+ } else if (pos < (2.5 / 2.75)) {
+ return (7.5625 * (pos -= (2.25 / 2.75)) * pos + .9375);
+ } else {
+ return (7.5625 * (pos -= (2.625 / 2.75)) * pos + .984375);
+ }
+ },
+ easeInBack: function(pos) {
+ var s = 1.70158;
+ return (pos) * pos * ((s + 1) * pos - s);
+ },
+ easeOutBack: function(pos) {
+ var s = 1.70158;
+ return (pos = pos - 1) * pos * ((s + 1) * pos + s) + 1;
+ },
+ bounce: function(t) {
+ if (t < (1 / 2.75)) {
+ return 7.5625 * t * t;
+ }
+ if (t < (2 / 2.75)) {
+ return 7.5625 * (t -= (1.5 / 2.75)) * t + 0.75;
+ }
+ if (t < (2.5 / 2.75)) {
+ return 7.5625 * (t -= (2.25 / 2.75)) * t + 0.9375;
+ }
+ return 7.5625 * (t -= (2.625 / 2.75)) * t + 0.984375;
+ },
+ bouncePast: function(pos) {
+ if (pos < (1 / 2.75)) {
+ return (7.5625 * pos * pos);
+ } else if (pos < (2 / 2.75)) {
+ return 2 - (7.5625 * (pos -= (1.5 / 2.75)) * pos + .75);
+ } else if (pos < (2.5 / 2.75)) {
+ return 2 - (7.5625 * (pos -= (2.25 / 2.75)) * pos + .9375);
+ } else {
+ return 2 - (7.5625 * (pos -= (2.625 / 2.75)) * pos + .984375);
+ }
+ },
+ swingTo: function(pos) {
+ var s = 1.70158;
+ return (pos -= 1) * pos * ((s + 1) * pos + s) + 1;
+ },
+ swingFrom: function(pos) {
+ var s = 1.70158;
+ return pos * pos * ((s + 1) * pos - s);
+ },
+ elastic: function(pos) {
+ return -1 * Math.pow(4, - 8 * pos) * Math.sin((pos * 6 - 1) * (2 * Math.PI) / 2) + 1;
+ },
+ spring: function(pos) {
+ return 1 - (Math.cos(pos * 4.5 * Math.PI) * Math.exp(-pos * 6));
+ },
+ blink: function(pos, blinks) {
+ return Math.round(pos * (blinks || 5)) % 2;
+ },
+ pulse: function(pos, pulses) {
+ return (-Math.cos((pos * ((pulses || 5) - .5) * 2) * Math.PI) / 2) + .5;
+ },
+ wobble: function(pos) {
+ return (-Math.cos(pos * Math.PI * (9 * pos)) / 2) + 0.5;
+ },
+ sinusoidal: function(pos) {
+ return (-Math.cos(pos * Math.PI) / 2) + 0.5;
+ },
+ flicker: function(pos) {
+ var pos = pos + (Math.random() - 0.5) / 5;
+ return easings.sinusoidal(pos < 0 ? 0 : pos > 1 ? 1 : pos);
+ },
+ mirror: function(pos) {
+ if (pos < 0.5) return easings.sinusoidal(pos * 2);
+ else return easings.sinusoidal(1 - (pos - 0.5) * 2);
+ },
+ // accelerating from zero velocity
+ easeInQuad: function (t) { return t*t },
+ // decelerating to zero velocity
+ easeOutQuad: function (t) { return t*(2-t) },
+ // acceleration until halfway, then deceleration
+ easeInOutQuad: function (t) { return t<.5 ? 2*t*t : -1+(4-2*t)*t },
+ // accelerating from zero velocity
+ easeInCubic: function (t) { return t*t*t },
+ // decelerating to zero velocity
+ easeOutCubic: function (t) { return (--t)*t*t+1 },
+ // acceleration until halfway, then deceleration
+ easeInOutCubic: function (t) { return t<.5 ? 4*t*t*t : (t-1)*(2*t-2)*(2*t-2)+1 },
+ // accelerating from zero velocity
+ easeInQuart: function (t) { return t*t*t*t },
+ // decelerating to zero velocity
+ easeOutQuart: function (t) { return 1-(--t)*t*t*t },
+ // acceleration until halfway, then deceleration
+ easeInOutQuart: function (t) { return t<.5 ? 8*t*t*t*t : 1-8*(--t)*t*t*t },
+ // accelerating from zero velocity
+ easeInQuint: function (t) { return t*t*t*t*t },
+ // decelerating to zero velocity
+ easeOutQuint: function (t) { return 1+(--t)*t*t*t*t },
+ // acceleration until halfway, then deceleration
+ easeInOutQuint: function (t) { return t<.5 ? 16*t*t*t*t*t : 1+16*(--t)*t*t*t*t }
+};
+
+/*
+Math.easeInExpo = function (t, b, c, d) {
+ return c * Math.pow( 2, 10 * (t/d - 1) ) + b;
+};
+
+
+
+// exponential easing out - decelerating to zero velocity
+
+
+Math.easeOutExpo = function (t, b, c, d) {
+ return c * ( -Math.pow( 2, -10 * t/d ) + 1 ) + b;
+};
+
+
+
+// exponential easing in/out - accelerating until halfway, then decelerating
+
+
+Math.easeInOutExpo = function (t, b, c, d) {
+ t /= d/2;
+ if (t < 1) return c/2 * Math.pow( 2, 10 * (t - 1) ) + b;
+ t--;
+ return c/2 * ( -Math.pow( 2, -10 * t) + 2 ) + b;
+};
+*/
+
+/* **********************************************
+ Begin TL.Animate.js
+********************************************** */
+
+/* TL.Animate
+ Basic animation
+================================================== */
+
+TL.Animate = function(el, options) {
+ var animation = new tlanimate(el, options),
+ webkit_timeout;
+ /*
+ // POSSIBLE ISSUE WITH WEBKIT FUTURE BUILDS
+ var onWebKitTimeout = function() {
+
+ animation.stop(true);
+ }
+ if (TL.Browser.webkit) {
+ webkit_timeout = setTimeout(function(){onWebKitTimeout()}, options.duration);
+ }
+ */
+ return animation;
+};
+
+
+/* Based on: Morpheus
+ https://github.com/ded/morpheus - (c) Dustin Diaz 2011
+ License MIT
+================================================== */
+window.tlanimate = (function() {
+
+ var doc = document,
+ win = window,
+ perf = win.performance,
+ perfNow = perf && (perf.now || perf.webkitNow || perf.msNow || perf.mozNow),
+ now = perfNow ? function () { return perfNow.call(perf) } : function () { return +new Date() },
+ html = doc.documentElement,
+ fixTs = false, // feature detected below
+ thousand = 1000,
+ rgbOhex = /^rgb\(|#/,
+ relVal = /^([+\-])=([\d\.]+)/,
+ numUnit = /^(?:[\+\-]=?)?\d+(?:\.\d+)?(%|in|cm|mm|em|ex|pt|pc|px)$/,
+ rotate = /rotate\(((?:[+\-]=)?([\-\d\.]+))deg\)/,
+ scale = /scale\(((?:[+\-]=)?([\d\.]+))\)/,
+ skew = /skew\(((?:[+\-]=)?([\-\d\.]+))deg, ?((?:[+\-]=)?([\-\d\.]+))deg\)/,
+ translate = /translate\(((?:[+\-]=)?([\-\d\.]+))px, ?((?:[+\-]=)?([\-\d\.]+))px\)/,
+ // these elements do not require 'px'
+ unitless = { lineHeight: 1, zoom: 1, zIndex: 1, opacity: 1, transform: 1};
+
+ // which property name does this browser use for transform
+ var transform = function () {
+ var styles = doc.createElement('a').style,
+ props = ['webkitTransform', 'MozTransform', 'OTransform', 'msTransform', 'Transform'],
+ i;
+
+ for (i = 0; i < props.length; i++) {
+ if (props[i] in styles) return props[i]
+ };
+ }();
+
+ // does this browser support the opacity property?
+ var opacity = function () {
+ return typeof doc.createElement('a').style.opacity !== 'undefined'
+ }();
+
+ // initial style is determined by the elements themselves
+ var getStyle = doc.defaultView && doc.defaultView.getComputedStyle ?
+ function (el, property) {
+ property = property == 'transform' ? transform : property
+ property = camelize(property)
+ var value = null,
+ computed = doc.defaultView.getComputedStyle(el, '');
+
+ computed && (value = computed[property]);
+ return el.style[property] || value;
+ } : html.currentStyle ?
+
+ function (el, property) {
+ property = camelize(property)
+
+ if (property == 'opacity') {
+ var val = 100
+ try {
+ val = el.filters['DXImageTransform.Microsoft.Alpha'].opacity
+ } catch (e1) {
+ try {
+ val = el.filters('alpha').opacity
+ } catch (e2) {
+
+ }
+ }
+ return val / 100
+ }
+ var value = el.currentStyle ? el.currentStyle[property] : null
+ return el.style[property] || value
+ } :
+
+ function (el, property) {
+ return el.style[camelize(property)]
+ }
+
+ var frame = function () {
+ // native animation frames
+ // http://webstuff.nfshost.com/anim-timing/Overview.html
+ // http://dev.chromium.org/developers/design-documents/requestanimationframe-implementation
+ return win.requestAnimationFrame ||
+ win.webkitRequestAnimationFrame ||
+ win.mozRequestAnimationFrame ||
+ win.msRequestAnimationFrame ||
+ win.oRequestAnimationFrame ||
+ function (callback) {
+ win.setTimeout(function () {
+ callback(+new Date())
+ }, 17) // when I was 17..
+ }
+ }()
+
+ var children = []
+
+ frame(function(timestamp) {
+ // feature-detect if rAF and now() are of the same scale (epoch or high-res),
+ // if not, we have to do a timestamp fix on each frame
+ fixTs = timestamp > 1e12 != now() > 1e12
+ })
+
+ function has(array, elem, i) {
+ if (Array.prototype.indexOf) return array.indexOf(elem)
+ for (i = 0; i < array.length; ++i) {
+ if (array[i] === elem) return i
+ }
+ }
+
+ function render(timestamp) {
+ var i, count = children.length
+ // if we're using a high res timer, make sure timestamp is not the old epoch-based value.
+ // http://updates.html5rocks.com/2012/05/requestAnimationFrame-API-now-with-sub-millisecond-precision
+ if (perfNow && timestamp > 1e12) timestamp = now()
+ if (fixTs) timestamp = now()
+ for (i = count; i--;) {
+ children[i](timestamp)
+ }
+ children.length && frame(render)
+ }
+
+ function live(f) {
+ if (children.push(f) === 1) frame(render)
+ }
+
+ function die(f) {
+ var rest, index = has(children, f)
+ if (index >= 0) {
+ rest = children.slice(index + 1)
+ children.length = index
+ children = children.concat(rest)
+ }
+ }
+
+ function parseTransform(style, base) {
+ var values = {}, m
+ if (m = style.match(rotate)) values.rotate = by(m[1], base ? base.rotate : null)
+ if (m = style.match(scale)) values.scale = by(m[1], base ? base.scale : null)
+ if (m = style.match(skew)) {values.skewx = by(m[1], base ? base.skewx : null); values.skewy = by(m[3], base ? base.skewy : null)}
+ if (m = style.match(translate)) {values.translatex = by(m[1], base ? base.translatex : null); values.translatey = by(m[3], base ? base.translatey : null)}
+ return values
+ }
+
+ function formatTransform(v) {
+ var s = ''
+ if ('rotate' in v) s += 'rotate(' + v.rotate + 'deg) '
+ if ('scale' in v) s += 'scale(' + v.scale + ') '
+ if ('translatex' in v) s += 'translate(' + v.translatex + 'px,' + v.translatey + 'px) '
+ if ('skewx' in v) s += 'skew(' + v.skewx + 'deg,' + v.skewy + 'deg)'
+ return s
+ }
+
+ function rgb(r, g, b) {
+ return '#' + (1 << 24 | r << 16 | g << 8 | b).toString(16).slice(1)
+ }
+
+ // convert rgb and short hex to long hex
+ function toHex(c) {
+ var m = c.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)/)
+ return (m ? rgb(m[1], m[2], m[3]) : c)
+ .replace(/#(\w)(\w)(\w)$/, '#$1$1$2$2$3$3') // short skirt to long jacket
+ }
+
+ // change font-size => fontSize etc.
+ function camelize(s) {
+ return s.replace(/-(.)/g, function (m, m1) {
+ return m1.toUpperCase()
+ })
+ }
+
+ // aren't we having it?
+ function fun(f) {
+ return typeof f == 'function'
+ }
+
+ function nativeTween(t) {
+ // default to a pleasant-to-the-eye easeOut (like native animations)
+ return Math.sin(t * Math.PI / 2)
+ }
+
+ /**
+ * Core tween method that requests each frame
+ * @param duration: time in milliseconds. defaults to 1000
+ * @param fn: tween frame callback function receiving 'position'
+ * @param done {optional}: complete callback function
+ * @param ease {optional}: easing method. defaults to easeOut
+ * @param from {optional}: integer to start from
+ * @param to {optional}: integer to end at
+ * @returns method to stop the animation
+ */
+ function tween(duration, fn, done, ease, from, to) {
+ ease = fun(ease) ? ease : morpheus.easings[ease] || nativeTween
+ var time = duration || thousand
+ , self = this
+ , diff = to - from
+ , start = now()
+ , stop = 0
+ , end = 0
+
+ function run(t) {
+ var delta = t - start
+ if (delta > time || stop) {
+ to = isFinite(to) ? to : 1
+ stop ? end && fn(to) : fn(to)
+ die(run)
+ return done && done.apply(self)
+ }
+ // if you don't specify a 'to' you can use tween as a generic delta tweener
+ // cool, eh?
+ isFinite(to) ?
+ fn((diff * ease(delta / time)) + from) :
+ fn(ease(delta / time))
+ }
+
+ live(run)
+
+ return {
+ stop: function (jump) {
+ stop = 1
+ end = jump // jump to end of animation?
+ if (!jump) done = null // remove callback if not jumping to end
+ }
+ }
+ }
+
+ /**
+ * generic bezier method for animating x|y coordinates
+ * minimum of 2 points required (start and end).
+ * first point start, last point end
+ * additional control points are optional (but why else would you use this anyway ;)
+ * @param points: array containing control points
+ [[0, 0], [100, 200], [200, 100]]
+ * @param pos: current be(tween) position represented as float 0 - 1
+ * @return [x, y]
+ */
+ function bezier(points, pos) {
+ var n = points.length, r = [], i, j
+ for (i = 0; i < n; ++i) {
+ r[i] = [points[i][0], points[i][1]]
+ }
+ for (j = 1; j < n; ++j) {
+ for (i = 0; i < n - j; ++i) {
+ r[i][0] = (1 - pos) * r[i][0] + pos * r[parseInt(i + 1, 10)][0]
+ r[i][1] = (1 - pos) * r[i][1] + pos * r[parseInt(i + 1, 10)][1]
+ }
+ }
+ return [r[0][0], r[0][1]]
+ }
+
+ // this gets you the next hex in line according to a 'position'
+ function nextColor(pos, start, finish) {
+ var r = [], i, e, from, to
+ for (i = 0; i < 6; i++) {
+ from = Math.min(15, parseInt(start.charAt(i), 16))
+ to = Math.min(15, parseInt(finish.charAt(i), 16))
+ e = Math.floor((to - from) * pos + from)
+ e = e > 15 ? 15 : e < 0 ? 0 : e
+ r[i] = e.toString(16)
+ }
+ return '#' + r.join('')
+ }
+
+ // this retreives the frame value within a sequence
+ function getTweenVal(pos, units, begin, end, k, i, v) {
+ if (k == 'transform') {
+ v = {}
+ for (var t in begin[i][k]) {
+ v[t] = (t in end[i][k]) ? Math.round(((end[i][k][t] - begin[i][k][t]) * pos + begin[i][k][t]) * thousand) / thousand : begin[i][k][t]
+ }
+ return v
+ } else if (typeof begin[i][k] == 'string') {
+ return nextColor(pos, begin[i][k], end[i][k])
+ } else {
+ // round so we don't get crazy long floats
+ v = Math.round(((end[i][k] - begin[i][k]) * pos + begin[i][k]) * thousand) / thousand
+ // some css properties don't require a unit (like zIndex, lineHeight, opacity)
+ if (!(k in unitless)) v += units[i][k] || 'px'
+ return v
+ }
+ }
+
+ // support for relative movement via '+=n' or '-=n'
+ function by(val, start, m, r, i) {
+ return (m = relVal.exec(val)) ?
+ (i = parseFloat(m[2])) && (start + (m[1] == '+' ? 1 : -1) * i) :
+ parseFloat(val)
+ }
+
+ /**
+ * morpheus:
+ * @param element(s): HTMLElement(s)
+ * @param options: mixed bag between CSS Style properties & animation options
+ * - {n} CSS properties|values
+ * - value can be strings, integers,
+ * - or callback function that receives element to be animated. method must return value to be tweened
+ * - relative animations start with += or -= followed by integer
+ * - duration: time in ms - defaults to 1000(ms)
+ * - easing: a transition method - defaults to an 'easeOut' algorithm
+ * - complete: a callback method for when all elements have finished
+ * - bezier: array of arrays containing x|y coordinates that define the bezier points. defaults to none
+ * - this may also be a function that receives element to be animated. it must return a value
+ */
+ function morpheus(elements, options) {
+ var els = elements ? (els = isFinite(elements.length) ? elements : [elements]) : [], i
+ , complete = options.complete
+ , duration = options.duration
+ , ease = options.easing
+ , points = options.bezier
+ , begin = []
+ , end = []
+ , units = []
+ , bez = []
+ , originalLeft
+ , originalTop
+
+ if (points) {
+ // remember the original values for top|left
+ originalLeft = options.left;
+ originalTop = options.top;
+ delete options.right;
+ delete options.bottom;
+ delete options.left;
+ delete options.top;
+ }
+
+ for (i = els.length; i--;) {
+
+ // record beginning and end states to calculate positions
+ begin[i] = {}
+ end[i] = {}
+ units[i] = {}
+
+ // are we 'moving'?
+ if (points) {
+
+ var left = getStyle(els[i], 'left')
+ , top = getStyle(els[i], 'top')
+ , xy = [by(fun(originalLeft) ? originalLeft(els[i]) : originalLeft || 0, parseFloat(left)),
+ by(fun(originalTop) ? originalTop(els[i]) : originalTop || 0, parseFloat(top))]
+
+ bez[i] = fun(points) ? points(els[i], xy) : points
+ bez[i].push(xy)
+ bez[i].unshift([
+ parseInt(left, 10),
+ parseInt(top, 10)
+ ])
+ }
+
+ for (var k in options) {
+ switch (k) {
+ case 'complete':
+ case 'duration':
+ case 'easing':
+ case 'bezier':
+ continue
+ }
+ var v = getStyle(els[i], k), unit
+ , tmp = fun(options[k]) ? options[k](els[i]) : options[k]
+ if (typeof tmp == 'string' &&
+ rgbOhex.test(tmp) &&
+ !rgbOhex.test(v)) {
+ delete options[k]; // remove key :(
+ continue; // cannot animate colors like 'orange' or 'transparent'
+ // only #xxx, #xxxxxx, rgb(n,n,n)
+ }
+
+ begin[i][k] = k == 'transform' ? parseTransform(v) :
+ typeof tmp == 'string' && rgbOhex.test(tmp) ?
+ toHex(v).slice(1) :
+ parseFloat(v)
+ end[i][k] = k == 'transform' ? parseTransform(tmp, begin[i][k]) :
+ typeof tmp == 'string' && tmp.charAt(0) == '#' ?
+ toHex(tmp).slice(1) :
+ by(tmp, parseFloat(v));
+ // record original unit
+ (typeof tmp == 'string') && (unit = tmp.match(numUnit)) && (units[i][k] = unit[1])
+ }
+ }
+ // ONE TWEEN TO RULE THEM ALL
+ return tween.apply(els, [duration, function (pos, v, xy) {
+ // normally not a fan of optimizing for() loops, but we want something
+ // fast for animating
+ for (i = els.length; i--;) {
+ if (points) {
+ xy = bezier(bez[i], pos)
+ els[i].style.left = xy[0] + 'px'
+ els[i].style.top = xy[1] + 'px'
+ }
+ for (var k in options) {
+ v = getTweenVal(pos, units, begin, end, k, i)
+ k == 'transform' ?
+ els[i].style[transform] = formatTransform(v) :
+ k == 'opacity' && !opacity ?
+ (els[i].style.filter = 'alpha(opacity=' + (v * 100) + ')') :
+ (els[i].style[camelize(k)] = v)
+ }
+ }
+ }, complete, ease])
+ }
+
+ // expose useful methods
+ morpheus.tween = tween
+ morpheus.getStyle = getStyle
+ morpheus.bezier = bezier
+ morpheus.transform = transform
+ morpheus.parseTransform = parseTransform
+ morpheus.formatTransform = formatTransform
+ morpheus.easings = {}
+
+ return morpheus
+})();
+
+
+/* **********************************************
+ Begin TL.Point.js
+********************************************** */
+
+/* TL.Point
+ Inspired by Leaflet
+ TL.Point represents a point with x and y coordinates.
+================================================== */
+
+TL.Point = function (/*Number*/ x, /*Number*/ y, /*Boolean*/ round) {
+ this.x = (round ? Math.round(x) : x);
+ this.y = (round ? Math.round(y) : y);
+};
+
+TL.Point.prototype = {
+ add: function (point) {
+ return this.clone()._add(point);
+ },
+
+ _add: function (point) {
+ this.x += point.x;
+ this.y += point.y;
+ return this;
+ },
+
+ subtract: function (point) {
+ return this.clone()._subtract(point);
+ },
+
+ // destructive subtract (faster)
+ _subtract: function (point) {
+ this.x -= point.x;
+ this.y -= point.y;
+ return this;
+ },
+
+ divideBy: function (num, round) {
+ return new TL.Point(this.x / num, this.y / num, round);
+ },
+
+ multiplyBy: function (num) {
+ return new TL.Point(this.x * num, this.y * num);
+ },
+
+ distanceTo: function (point) {
+ var x = point.x - this.x,
+ y = point.y - this.y;
+ return Math.sqrt(x * x + y * y);
+ },
+
+ round: function () {
+ return this.clone()._round();
+ },
+
+ // destructive round
+ _round: function () {
+ this.x = Math.round(this.x);
+ this.y = Math.round(this.y);
+ return this;
+ },
+
+ clone: function () {
+ return new TL.Point(this.x, this.y);
+ },
+
+ toString: function () {
+ return 'Point(' +
+ TL.Util.formatNum(this.x) + ', ' +
+ TL.Util.formatNum(this.y) + ')';
+ }
+};
+
+/* **********************************************
+ Begin TL.DomMixins.js
+********************************************** */
+
+/* TL.DomMixins
+ DOM methods used regularly
+ Assumes there is a _el.container and animator
+================================================== */
+TL.DomMixins = {
+
+ /* Adding, Hiding, Showing etc
+ ================================================== */
+ show: function(animate) {
+ if (animate) {
+ /*
+ this.animator = TL.Animate(this._el.container, {
+ left: -(this._el.container.offsetWidth * n) + "px",
+ duration: this.options.duration,
+ easing: this.options.ease
+ });
+ */
+ } else {
+ this._el.container.style.display = "block";
+ }
+ },
+
+ hide: function(animate) {
+ this._el.container.style.display = "none";
+ },
+
+ addTo: function(container) {
+ container.appendChild(this._el.container);
+ this.onAdd();
+ },
+
+ removeFrom: function(container) {
+ container.removeChild(this._el.container);
+ this.onRemove();
+ },
+
+ /* Animate to Position
+ ================================================== */
+ animatePosition: function(pos, el) {
+ var ani = {
+ duration: this.options.duration,
+ easing: this.options.ease
+ };
+ for (var name in pos) {
+ if (pos.hasOwnProperty(name)) {
+ ani[name] = pos[name] + "px";
+ }
+ }
+
+ if (this.animator) {
+ this.animator.stop();
+ }
+ this.animator = TL.Animate(el, ani);
+ },
+
+ /* Events
+ ================================================== */
+
+ onLoaded: function() {
+ this.fire("loaded", this.data);
+ },
+
+ onAdd: function() {
+ this.fire("added", this.data);
+ },
+
+ onRemove: function() {
+ this.fire("removed", this.data);
+ },
+
+ /* Set the Position
+ ================================================== */
+ setPosition: function(pos, el) {
+ for (var name in pos) {
+ if (pos.hasOwnProperty(name)) {
+ if (el) {
+ el.style[name] = pos[name] + "px";
+ } else {
+ this._el.container.style[name] = pos[name] + "px";
+ };
+ }
+ }
+ },
+
+ getPosition: function() {
+ return TL.Dom.getPosition(this._el.container);
+ }
+
+};
+
+
+/* **********************************************
+ Begin TL.Dom.js
+********************************************** */
+
+/* TL.Dom
+ Utilities for working with the DOM
+================================================== */
+
+TL.Dom = {
+
+ get: function(id) {
+ return (typeof id === 'string' ? document.getElementById(id) : id);
+ },
+
+ getByClass: function(id) {
+ if (id) {
+ return document.getElementsByClassName(id);
+ }
+ },
+
+ create: function(tagName, className, container) {
+ var el = document.createElement(tagName);
+ el.className = className;
+ if (container) {
+ container.appendChild(el);
+ }
+ return el;
+ },
+
+ createText: function(content, container) {
+ var el = document.createTextNode(content);
+ if (container) {
+ container.appendChild(el);
+ }
+ return el;
+ },
+
+ getTranslateString: function (point) {
+ return TL.Dom.TRANSLATE_OPEN +
+ point.x + 'px,' + point.y + 'px' +
+ TL.Dom.TRANSLATE_CLOSE;
+ },
+
+ setPosition: function (el, point) {
+ el._tl_pos = point;
+ if (TL.Browser.webkit3d) {
+ el.style[TL.Dom.TRANSFORM] = TL.Dom.getTranslateString(point);
+
+ if (TL.Browser.android) {
+ el.style['-webkit-perspective'] = '1000';
+ el.style['-webkit-backface-visibility'] = 'hidden';
+ }
+ } else {
+ el.style.left = point.x + 'px';
+ el.style.top = point.y + 'px';
+ }
+ },
+
+ getPosition: function(el){
+ var pos = {
+ x: 0,
+ y: 0
+ }
+ while( el && !isNaN( el.offsetLeft ) && !isNaN( el.offsetTop ) ) {
+ pos.x += el.offsetLeft// - el.scrollLeft;
+ pos.y += el.offsetTop// - el.scrollTop;
+ el = el.offsetParent;
+ }
+ return pos;
+ },
+
+ testProp: function(props) {
+ var style = document.documentElement.style;
+
+ for (var i = 0; i < props.length; i++) {
+ if (props[i] in style) {
+ return props[i];
+ }
+ }
+ return false;
+ }
+
+};
+
+TL.Util.mergeData(TL.Dom, {
+ TRANSITION: TL.Dom.testProp(['transition', 'webkitTransition', 'OTransition', 'MozTransition', 'msTransition']),
+ TRANSFORM: TL.Dom.testProp(['transformProperty', 'WebkitTransform', 'OTransform', 'MozTransform', 'msTransform']),
+
+ TRANSLATE_OPEN: 'translate' + (TL.Browser.webkit3d ? '3d(' : '('),
+ TRANSLATE_CLOSE: TL.Browser.webkit3d ? ',0)' : ')'
+});
+
+
+/* **********************************************
+ Begin TL.DomUtil.js
+********************************************** */
+
+/* TL.DomUtil
+ Inspired by Leaflet
+ TL.DomUtil contains various utility functions for working with DOM
+================================================== */
+
+
+TL.DomUtil = {
+ get: function (id) {
+ return (typeof id === 'string' ? document.getElementById(id) : id);
+ },
+
+ getStyle: function (el, style) {
+ var value = el.style[style];
+ if (!value && el.currentStyle) {
+ value = el.currentStyle[style];
+ }
+ if (!value || value === 'auto') {
+ var css = document.defaultView.getComputedStyle(el, null);
+ value = css ? css[style] : null;
+ }
+ return (value === 'auto' ? null : value);
+ },
+
+ getViewportOffset: function (element) {
+ var top = 0,
+ left = 0,
+ el = element,
+ docBody = document.body;
+
+ do {
+ top += el.offsetTop || 0;
+ left += el.offsetLeft || 0;
+
+ if (el.offsetParent === docBody &&
+ TL.DomUtil.getStyle(el, 'position') === 'absolute') {
+ break;
+ }
+ el = el.offsetParent;
+ } while (el);
+
+ el = element;
+
+ do {
+ if (el === docBody) {
+ break;
+ }
+
+ top -= el.scrollTop || 0;
+ left -= el.scrollLeft || 0;
+
+ el = el.parentNode;
+ } while (el);
+
+ return new TL.Point(left, top);
+ },
+
+ create: function (tagName, className, container) {
+ var el = document.createElement(tagName);
+ el.className = className;
+ if (container) {
+ container.appendChild(el);
+ }
+ return el;
+ },
+
+ disableTextSelection: function () {
+ if (document.selection && document.selection.empty) {
+ document.selection.empty();
+ }
+ if (!this._onselectstart) {
+ this._onselectstart = document.onselectstart;
+ document.onselectstart = TL.Util.falseFn;
+ }
+ },
+
+ enableTextSelection: function () {
+ document.onselectstart = this._onselectstart;
+ this._onselectstart = null;
+ },
+
+ hasClass: function (el, name) {
+ return (el.className.length > 0) &&
+ new RegExp("(^|\\s)" + name + "(\\s|$)").test(el.className);
+ },
+
+ addClass: function (el, name) {
+ if (!TL.DomUtil.hasClass(el, name)) {
+ el.className += (el.className ? ' ' : '') + name;
+ }
+ },
+
+ removeClass: function (el, name) {
+ el.className = el.className.replace(/(\S+)\s*/g, function (w, match) {
+ if (match === name) {
+ return '';
+ }
+ return w;
+ }).replace(/^\s+/, '');
+ },
+
+ setOpacity: function (el, value) {
+ if (TL.Browser.ie) {
+ el.style.filter = 'alpha(opacity=' + Math.round(value * 100) + ')';
+ } else {
+ el.style.opacity = value;
+ }
+ },
+
+
+ testProp: function (props) {
+ var style = document.documentElement.style;
+
+ for (var i = 0; i < props.length; i++) {
+ if (props[i] in style) {
+ return props[i];
+ }
+ }
+ return false;
+ },
+
+ getTranslateString: function (point) {
+
+ return TL.DomUtil.TRANSLATE_OPEN +
+ point.x + 'px,' + point.y + 'px' +
+ TL.DomUtil.TRANSLATE_CLOSE;
+ },
+
+ getScaleString: function (scale, origin) {
+ var preTranslateStr = TL.DomUtil.getTranslateString(origin),
+ scaleStr = ' scale(' + scale + ') ',
+ postTranslateStr = TL.DomUtil.getTranslateString(origin.multiplyBy(-1));
+
+ return preTranslateStr + scaleStr + postTranslateStr;
+ },
+
+ setPosition: function (el, point) {
+ el._tl_pos = point;
+ if (TL.Browser.webkit3d) {
+ el.style[TL.DomUtil.TRANSFORM] = TL.DomUtil.getTranslateString(point);
+
+ if (TL.Browser.android) {
+ el.style['-webkit-perspective'] = '1000';
+ el.style['-webkit-backface-visibility'] = 'hidden';
+ }
+ } else {
+ el.style.left = point.x + 'px';
+ el.style.top = point.y + 'px';
+ }
+ },
+
+ getPosition: function (el) {
+ return el._tl_pos;
+ }
+};
+
+/* **********************************************
+ Begin TL.DomEvent.js
+********************************************** */
+
+/* TL.DomEvent
+ Inspired by Leaflet
+ DomEvent contains functions for working with DOM events.
+================================================== */
+// TODO stamp
+
+TL.DomEvent = {
+ /* inpired by John Resig, Dean Edwards and YUI addEvent implementations */
+ addListener: function (/*HTMLElement*/ obj, /*String*/ type, /*Function*/ fn, /*Object*/ context) {
+ var id = TL.Util.stamp(fn),
+ key = '_tl_' + type + id;
+
+ if (obj[key]) {
+ return;
+ }
+
+ var handler = function (e) {
+ return fn.call(context || obj, e || TL.DomEvent._getEvent());
+ };
+
+ if (TL.Browser.touch && (type === 'dblclick') && this.addDoubleTapListener) {
+ this.addDoubleTapListener(obj, handler, id);
+ } else if ('addEventListener' in obj) {
+ if (type === 'mousewheel') {
+ obj.addEventListener('DOMMouseScroll', handler, false);
+ obj.addEventListener(type, handler, false);
+ } else if ((type === 'mouseenter') || (type === 'mouseleave')) {
+ var originalHandler = handler,
+ newType = (type === 'mouseenter' ? 'mouseover' : 'mouseout');
+ handler = function (e) {
+ if (!TL.DomEvent._checkMouse(obj, e)) {
+ return;
+ }
+ return originalHandler(e);
+ };
+ obj.addEventListener(newType, handler, false);
+ } else {
+ obj.addEventListener(type, handler, false);
+ }
+ } else if ('attachEvent' in obj) {
+ obj.attachEvent("on" + type, handler);
+ }
+
+ obj[key] = handler;
+ },
+
+ removeListener: function (/*HTMLElement*/ obj, /*String*/ type, /*Function*/ fn) {
+ var id = TL.Util.stamp(fn),
+ key = '_tl_' + type + id,
+ handler = obj[key];
+
+ if (!handler) {
+ return;
+ }
+
+ if (TL.Browser.touch && (type === 'dblclick') && this.removeDoubleTapListener) {
+ this.removeDoubleTapListener(obj, id);
+ } else if ('removeEventListener' in obj) {
+ if (type === 'mousewheel') {
+ obj.removeEventListener('DOMMouseScroll', handler, false);
+ obj.removeEventListener(type, handler, false);
+ } else if ((type === 'mouseenter') || (type === 'mouseleave')) {
+ obj.removeEventListener((type === 'mouseenter' ? 'mouseover' : 'mouseout'), handler, false);
+ } else {
+ obj.removeEventListener(type, handler, false);
+ }
+ } else if ('detachEvent' in obj) {
+ obj.detachEvent("on" + type, handler);
+ }
+ obj[key] = null;
+ },
+
+ _checkMouse: function (el, e) {
+ var related = e.relatedTarget;
+
+ if (!related) {
+ return true;
+ }
+
+ try {
+ while (related && (related !== el)) {
+ related = related.parentNode;
+ }
+ } catch (err) {
+ return false;
+ }
+
+ return (related !== el);
+ },
+
+ /*jshint noarg:false */ // evil magic for IE
+ _getEvent: function () {
+ var e = window.event;
+ if (!e) {
+ var caller = arguments.callee.caller;
+ while (caller) {
+ e = caller['arguments'][0];
+ if (e && window.Event === e.constructor) {
+ break;
+ }
+ caller = caller.caller;
+ }
+ }
+ return e;
+ },
+ /*jshint noarg:false */
+
+ stopPropagation: function (/*Event*/ e) {
+ if (e.stopPropagation) {
+ e.stopPropagation();
+ } else {
+ e.cancelBubble = true;
+ }
+ },
+
+ // TODO TL.Draggable.START
+ disableClickPropagation: function (/*HTMLElement*/ el) {
+ TL.DomEvent.addListener(el, TL.Draggable.START, TL.DomEvent.stopPropagation);
+ TL.DomEvent.addListener(el, 'click', TL.DomEvent.stopPropagation);
+ TL.DomEvent.addListener(el, 'dblclick', TL.DomEvent.stopPropagation);
+ },
+
+ preventDefault: function (/*Event*/ e) {
+ if (e.preventDefault) {
+ e.preventDefault();
+ } else {
+ e.returnValue = false;
+ }
+ },
+
+ stop: function (e) {
+ TL.DomEvent.preventDefault(e);
+ TL.DomEvent.stopPropagation(e);
+ },
+
+
+ getWheelDelta: function (e) {
+ var delta = 0;
+ if (e.wheelDelta) {
+ delta = e.wheelDelta / 120;
+ }
+ if (e.detail) {
+ delta = -e.detail / 3;
+ }
+ return delta;
+ }
+};
+
+
+
+
+/* **********************************************
+ Begin TL.StyleSheet.js
+********************************************** */
+
+/* TL.StyleSheet
+ Style Sheet Object
+================================================== */
+
+TL.StyleSheet = TL.Class.extend({
+
+ includes: [TL.Events],
+
+ _el: {},
+
+ /* Constructor
+ ================================================== */
+ initialize: function() {
+ // Borrowed from: http://davidwalsh.name/add-rules-stylesheets
+ this.style = document.createElement("style");
+
+ // WebKit hack :(
+ this.style.appendChild(document.createTextNode(""));
+
+ // Add the <style> element to the page
+ document.head.appendChild(this.style);
+
+ this.sheet = this.style.sheet;
+
+ },
+
+ addRule: function(selector, rules, index) {
+ var _index = 0;
+
+ if (index) {
+ _index = index;
+ }
+
+ if("insertRule" in this.sheet) {
+ this.sheet.insertRule(selector + "{" + rules + "}", _index);
+ }
+ else if("addRule" in this.sheet) {
+ this.sheet.addRule(selector, rules, _index);
+ }
+ },
+
+
+ /* Events
+ ================================================== */
+ onLoaded: function(error) {
+ this._state.loaded = true;
+ this.fire("loaded", this.data);
+ }
+
+});
+
+/* **********************************************
+ Begin TL.Date.js
+********************************************** */
+
+/* TL.Date
+ Date object
+ MONTHS are 1-BASED, not 0-BASED (different from Javascript date objects)
+================================================== */
+
+//
+// Class for human dates
+//
+
+TL.Date = TL.Class.extend({
+
+ // @data = ms, JS Date object, or JS dictionary with date properties
+ initialize: function (data, format, format_short) {
+ if (typeof(data) == 'number') {
+ this.data = {
+ format: "yyyy mmmm",
+ date_obj: new Date(data)
+ };
+ } else if(Date == data.constructor) {
+ this.data = {
+ format: "yyyy mmmm",
+ date_obj: data
+ };
+ } else {
+ this.data = JSON.parse(JSON.stringify(data)); // clone don't use by reference.
+ this._createDateObj();
+ }
+
+ this._setFormat(format, format_short);
+ },
+
+ setDateFormat: function(format) {
+ this.data.format = format;
+ },
+
+ getDisplayDate: function(language, format) {
+ if (this.data.display_date) {
+ return this.data.display_date;
+ }
+ if (!language) {
+ language = TL.Language.fallback;
+ }
+ if (language.constructor != TL.Language) {
+ trace("First argument to getDisplayDate must be TL.Language");
+ language = TL.Language.fallback;
+ }
+
+ var format_key = format || this.data.format;
+ return language.formatDate(this.data.date_obj, format_key);
+ },
+
+ getMillisecond: function() {
+ return this.getTime();
+ },
+
+ getTime: function() {
+ return this.data.date_obj.getTime();
+ },
+
+ isBefore: function(other_date) {
+ if (!this.data.date_obj.constructor == other_date.data.date_obj.constructor) {
+ throw new TL.Error("date_compare_err") // but should be able to compare 'cosmological scale' dates once we get to that...
+ }
+ if ('isBefore' in this.data.date_obj) {
+ return this.data.date_obj['isBefore'](other_date.data.date_obj);
+ }
+ return this.data.date_obj < other_date.data.date_obj
+ },
+
+ isAfter: function(other_date) {
+ if (!this.data.date_obj.constructor == other_date.data.date_obj.constructor) {
+ throw new TL.Error("date_compare_err") // but should be able to compare 'cosmological scale' dates once we get to that...
+ }
+ if ('isAfter' in this.data.date_obj) {
+ return this.data.date_obj['isAfter'](other_date.data.date_obj);
+ }
+ return this.data.date_obj > other_date.data.date_obj
+ },
+
+ // Return a new TL.Date which has been 'floored' at the given scale.
+ // @scale = string value from TL.Date.SCALES
+ floor: function(scale) {
+ var d = new Date(this.data.date_obj.getTime());
+ for (var i = 0; i < TL.Date.SCALES.length; i++) {
+ // for JS dates, we iteratively apply flooring functions
+ TL.Date.SCALES[i][2](d);
+ if (TL.Date.SCALES[i][0] == scale) return new TL.Date(d);
+ };
+
+ throw new TL.Error("invalid_scale_err", scale);
+ },
+
+ /* Private Methods
+ ================================================== */
+
+ _getDateData: function() {
+ var _date = {
+ year: 0,
+ month: 1, // stupid JS dates
+ day: 1,
+ hour: 0,
+ minute: 0,
+ second: 0,
+ millisecond: 0
+ };
+
+ // Merge data
+ TL.Util.mergeData(_date, this.data);
+
+ // Make strings into numbers
+ var DATE_PARTS = TL.Date.DATE_PARTS;
+
+ for (var ix in DATE_PARTS) {
+ var x = TL.Util.trim(_date[DATE_PARTS[ix]]);
+ if (!x.match(/^-?\d*$/)) {
+ throw new TL.Error("invalid_date_err", DATE_PARTS[ix] + " = '" + _date[DATE_PARTS[ix]] + "'");
+ }
+
+ var parsed = parseInt(_date[DATE_PARTS[ix]]);
+ if (isNaN(parsed)) {
+ parsed = (ix == 4 || ix == 5) ? 1 : 0; // month and day have diff baselines
+ }
+ _date[DATE_PARTS[ix]] = parsed;
+ }
+
+ if (_date.month > 0 && _date.month <= 12) { // adjust for JS's weirdness
+ _date.month = _date.month - 1;
+ }
+
+ return _date;
+ },
+
+ _createDateObj: function() {
+ var _date = this._getDateData();
+ this.data.date_obj = new Date(_date.year, _date.month, _date.day, _date.hour, _date.minute, _date.second, _date.millisecond);
+ if (this.data.date_obj.getFullYear() != _date.year) {
+ // Javascript has stupid defaults for two-digit years
+ this.data.date_obj.setFullYear(_date.year);
+ }
+ },
+
+ /* Find Best Format
+ * this may not work with 'cosmologic' dates, or with TL.Date if we
+ * support constructing them based on JS Date and time
+ ================================================== */
+ findBestFormat: function(variant) {
+ var eval_array = TL.Date.DATE_PARTS,
+ format = "";
+
+ for (var i = 0; i < eval_array.length; i++) {
+ if ( this.data[eval_array[i]]) {
+ if (variant) {
+ if (!(variant in TL.Date.BEST_DATEFORMATS)) {
+ variant = 'short'; // legacy
+ }
+ } else {
+ variant = 'base'
+ }
+ return TL.Date.BEST_DATEFORMATS[variant][eval_array[i]];
+ }
+ };
+ return "";
+ },
+ _setFormat: function(format, format_short) {
+ if (format) {
+ this.data.format = format;
+ } else if (!this.data.format) {
+ this.data.format = this.findBestFormat();
+ }
+
+ if (format_short) {
+ this.data.format_short = format_short;
+ } else if (!this.data.format_short) {
+ this.data.format_short = this.findBestFormat(true);
+ }
+ }
+});
+
+// offer something that can figure out the right date class to return
+TL.Date.makeDate = function(data) {
+ var date = new TL.Date(data);
+ if (!isNaN(date.getTime())) {
+ return date;
+ }
+ return new TL.BigDate(data);
+}
+
+TL.BigYear = TL.Class.extend({
+ initialize: function (year) {
+ this.year = parseInt(year);
+ if (isNaN(this.year)) {
+ throw new TL.Error('invalid_year_err', year);
+ }
+ },
+
+ isBefore: function(that) {
+ return this.year < that.year;
+ },
+
+ isAfter: function(that) {
+ return this.year > that.year;
+ },
+
+ getTime: function() {
+ return this.year;
+ }
+});
+
+(function(cls){
+ // human scales
+ cls.SCALES = [ // ( name, units_per_tick, flooring function )
+ ['millisecond',1, function(d) { }],
+ ['second',1000, function(d) { d.setMilliseconds(0);}],
+ ['minute',1000 * 60, function(d) { d.setSeconds(0);}],
+ ['hour',1000 * 60 * 60, function(d) { d.setMinutes(0);}],
+ ['day',1000 * 60 * 60 * 24, function(d) { d.setHours(0);}],
+ ['month',1000 * 60 * 60 * 24 * 30, function(d) { d.setDate(1);}],
+ ['year',1000 * 60 * 60 * 24 * 365, function(d) { d.setMonth(0);}],
+ ['decade',1000 * 60 * 60 * 24 * 365 * 10, function(d) {
+ var real_year = d.getFullYear();
+ d.setFullYear( real_year - (real_year % 10))
+ }],
+ ['century',1000 * 60 * 60 * 24 * 365 * 100, function(d) {
+ var real_year = d.getFullYear();
+ d.setFullYear( real_year - (real_year % 100))
+ }],
+ ['millennium',1000 * 60 * 60 * 24 * 365 * 1000, function(d) {
+ var real_year = d.getFullYear();
+ d.setFullYear( real_year - (real_year % 1000))
+ }]
+ ];
+
+ // Date parts from highest to lowest precision
+ cls.DATE_PARTS = ["millisecond", "second", "minute", "hour", "day", "month", "year"];
+
+ var ISO8601_SHORT_PATTERN = /^([\+-]?\d+?)(-\d{2}?)?(-\d{2}?)?$/;
+ // regex below from
+ // http://www.pelagodesign.com/blog/2009/05/20/iso-8601-date-validation-that-doesnt-suck/
+ var ISO8601_PATTERN = /^([\+-]?\d{4}(?!\d{2}\b))((-?)((0[1-9]|1[0-2])(\3([12]\d|0[1-9]|3[01]))?|W([0-4]\d|5[0-2])(-?[1-7])?|(00[1-9]|0[1-9]\d|[12]\d{2}|3([0-5]\d|6[1-6])))([T\s]((([01]\d|2[0-3])((:?)[0-5]\d)?|24\:?00)([\.,]\d+(?!:))?)?(\17[0-5]\d([\.,]\d+)?)?([zZ]|([\+-])([01]\d|2[0-3]):?([0-5]\d)?)?)?)?$/;
+
+ /* For now, rather than extract parts from regexp, lets trust the browser.
+ * Famous last words...
+ * What about UTC vs local time?
+ * see also http://stackoverflow.com/questions/10005374/ecmascript-5-date-parse-results-for-iso-8601-test-cases
+ */
+ cls.parseISODate = function(str) {
+ var d = new Date(str);
+ if (isNaN(d)) {
+ throw new TL.Error("invalid_date_err", str);
+ }
+ return {
+ year: d.getFullYear(),
+ month: d.getMonth() + 1,
+ day: d.getDate(),
+ hour: d.getHours(),
+ minute: d.getMinutes(),
+ second: d.getSeconds(),
+ millisecond: d.getMilliseconds()
+ }
+
+ }
+
+ cls.parseDate = function(str) {
+
+ if (str.match(ISO8601_SHORT_PATTERN)) {
+ // parse short specifically to avoid timezone offset confusion
+ // most browsers assume short is UTC, not local time.
+ var parts = str.match(ISO8601_SHORT_PATTERN).slice(1);
+ var d = { year: parts[0].replace('+','')} // year can be negative
+ if (parts[1]) { d['month'] = parts[1].replace('-',''); }
+ if (parts[2]) { d['day'] = parts[2].replace('-',''); }
+ return d;
+ }
+
+ if (str.match(ISO8601_PATTERN)) {
+ return cls.parseISODate(str);
+ }
+
+ if (str.match(/^\-?\d+$/)) {
+ return { year: str }
+ }
+
+ var parsed = {}
+ if (str.match(/\d+\/\d+\/\d+/)) { // mm/yy/dddd
+ var date = str.match(/\d+\/\d+\/\d+/)[0];
+ str = TL.Util.trim(str.replace(date,''));
+ var date_parts = date.split('/');
+ parsed.month = date_parts[0];
+ parsed.day = date_parts[1];
+ parsed.year = date_parts[2];
+ }
+
+ if (str.match(/\d+\/\d+/)) { // mm/yy
+ var date = str.match(/\d+\/\d+/)[0];
+ str = TL.Util.trim(str.replace(date,''));
+ var date_parts = date.split('/');
+ parsed.month = date_parts[0];
+ parsed.year = date_parts[1];
+ }
+ // todo: handle hours, minutes, seconds, millis other date formats, etc...
+ if (str.match(':')) {
+ var time_parts = str.split(':');
+ parsed.hour = time_parts[0];
+ parsed.minute = time_parts[1];
+ if (time_parts[2]) {
+ second_parts = time_parts[2].split('.');
+ parsed.second = second_parts[0];
+ parsed.millisecond = second_parts[1];
+ }
+ }
+ return parsed;
+ }
+
+ cls.BEST_DATEFORMATS = {
+ base: {
+ millisecond: 'time_short',
+ second: 'time',
+ minute: 'time_no_seconds_small_date',
+ hour: 'time_no_seconds_small_date',
+ day: 'full',
+ month: 'month',
+ year: 'year',
+ decade: 'year',
+ century: 'year',
+ millennium: 'year',
+ age: 'fallback',
+ epoch: 'fallback',
+ era: 'fallback',
+ eon: 'fallback',
+ eon2: 'fallback'
+ },
+
+ short: {
+ millisecond: 'time_short',
+ second: 'time_short',
+ minute: 'time_no_seconds_short',
+ hour: 'time_no_minutes_short',
+ day: 'full_short',
+ month: 'month_short',
+ year: 'year',
+ decade: 'year',
+ century: 'year',
+ millennium: 'year',
+ age: 'fallback',
+ epoch: 'fallback',
+ era: 'fallback',
+ eon: 'fallback',
+ eon2: 'fallback'
+ }
+ }
+
+
+})(TL.Date)
+
+
+//
+// Class for cosmological dates
+//
+TL.BigDate = TL.Date.extend({
+
+ // @data = TL.BigYear object or JS dictionary with date properties
+ initialize: function(data, format, format_short) {
+ if (TL.BigYear == data.constructor) {
+ this.data = {
+ date_obj: data
+ }
+ } else {
+ this.data = JSON.parse(JSON.stringify(data));
+ this._createDateObj();
+ }
+
+ this._setFormat(format, format_short);
+ },
+
+ // Create date_obj
+ _createDateObj: function() {
+ var _date = this._getDateData();
+ this.data.date_obj = new TL.BigYear(_date.year);
+ },
+
+ // Return a new TL.BigDate which has been 'floored' at the given scale.
+ // @scale = string value from TL.BigDate.SCALES
+ floor: function(scale) {
+ for (var i = 0; i < TL.BigDate.SCALES.length; i++) {
+ if (TL.BigDate.SCALES[i][0] == scale) {
+ var floored = TL.BigDate.SCALES[i][2](this.data.date_obj);
+ return new TL.BigDate(floored);
+ }
+ };
+
+ throw new TL.Error("invalid_scale_err", scale);
+ }
+});
+
+(function(cls){
+ // cosmo units are years, not millis
+ var AGE = 1000000;
+ var EPOCH = AGE * 10;
+ var ERA = EPOCH * 10;
+ var EON = ERA * 10;
+
+ var Floorer = function(unit) {
+ return function(a_big_year) {
+ var year = a_big_year.getTime();
+ return new TL.BigYear(Math.floor(year/unit) * unit);
+ }
+ }
+
+ // cosmological scales
+ cls.SCALES = [ // ( name, units_per_tick, flooring function )
+ ['year',1, new Floorer(1)],
+ ['decade',10, new Floorer(10)],
+ ['century',100, new Floorer(100)],
+ ['millennium',1000, new Floorer(1000)],
+ ['age',AGE, new Floorer(AGE)], // 1M years
+ ['epoch',EPOCH, new Floorer(EPOCH)], // 10M years
+ ['era',ERA, new Floorer(ERA)], // 100M years
+ ['eon',EON, new Floorer(EON)] // 1B years
+ ];
+
+})(TL.BigDate)
+
+
+/* **********************************************
+ Begin TL.DateUtil.js
+********************************************** */
+
+/* TL.DateUtil
+ Utilities for parsing time
+================================================== */
+
+
+TL.DateUtil = {
+ get: function (id) {
+ return (typeof id === 'string' ? document.getElementById(id) : id);
+ },
+
+ sortByDate: function(array,prop_name) { // only for use with slide data objects
+ var prop_name = prop_name || 'start_date';
+ array.sort(function(a,b){
+ if (a[prop_name].isBefore(b[prop_name])) return -1;
+ if (a[prop_name].isAfter(b[prop_name])) return 1;
+ return 0;
+ });
+ },
+
+ parseTime: function(time_str) {
+ var parsed = {
+ hour: null, minute: null, second: null, millisecond: null // conform to keys in TL.Date
+ }
+ var period = null;
+ var match = time_str.match(/(\s*[AaPp]\.?[Mm]\.?\s*)$/);
+ if (match) {
+ period = TL.Util.trim(match[0]);
+ time_str = TL.Util.trim(time_str.substring(0,time_str.lastIndexOf(period)));
+ }
+
+ var parts = [];
+ var no_separators = time_str.match(/^\s*(\d{1,2})(\d{2})\s*$/);
+ if (no_separators) {
+ parts = no_separators.slice(1);
+ } else {
+ parts = time_str.split(':');
+ if (parts.length == 1) {
+ parts = time_str.split('.');
+ }
+ }
+
+ if (parts.length > 4) {
+ throw new TL.Error("invalid_separator_error");
+ }
+
+ parsed.hour = parseInt(parts[0]);
+
+ if (period && period.toLowerCase()[0] == 'p' && parsed.hour != 12) {
+ parsed.hour += 12;
+ } else if (period && period.toLowerCase()[0] == 'a' && parsed.hour == 12) {
+ parsed.hour = 0;
+ }
+
+
+ if (isNaN(parsed.hour) || parsed.hour < 0 || parsed.hour > 23) {
+ throw new TL.Error("invalid_hour_err", parsed.hour);
+ }
+
+ if (parts.length > 1) {
+ parsed.minute = parseInt(parts[1]);
+ if (isNaN(parsed.minute)) {
+ throw new TL.Error("invalid_minute_err", parsed.minute);
+ }
+ }
+
+ if (parts.length > 2) {
+ var sec_parts = parts[2].split(/[\.,]/);
+ parts = sec_parts.concat(parts.slice(3)) // deal with various methods of specifying fractional seconds
+ if (parts.length > 2) {
+ throw new TL.Error("invalid_second_fractional_err");
+ }
+ parsed.second = parseInt(parts[0]);
+ if (isNaN(parsed.second)) {
+ throw new TL.Error("invalid_second_err");
+ }
+ if (parts.length == 2) {
+ var frac_secs = parseInt(parts[1]);
+ if (isNaN(frac_secs)) {
+ throw new TL.Error("invalid_fractional_err");
+ }
+ parsed.millisecond = 100 * frac_secs;
+ }
+ }
+
+ return parsed;
+ },
+
+ SCALE_DATE_CLASSES: {
+ human: TL.Date,
+ cosmological: TL.BigDate
+ }
+
+
+};
+
+
+/* **********************************************
+ Begin TL.Draggable.js
+********************************************** */
+
+/* TL.Draggable
+ TL.Draggable allows you to add dragging capabilities to any element. Supports mobile devices too.
+ TODO Enable constraints
+================================================== */
+
+TL.Draggable = TL.Class.extend({
+
+ includes: TL.Events,
+
+ _el: {},
+
+ mousedrag: {
+ down: "mousedown",
+ up: "mouseup",
+ leave: "mouseleave",
+ move: "mousemove"
+ },
+
+ touchdrag: {
+ down: "touchstart",
+ up: "touchend",
+ leave: "mouseleave",
+ move: "touchmove"
+ },
+
+ initialize: function (drag_elem, options, move_elem) {
+
+ // DOM ELements
+ this._el = {
+ drag: drag_elem,
+ move: drag_elem
+ };
+
+ if (move_elem) {
+ this._el.move = move_elem;
+ }
+
+
+ //Options
+ this.options = {
+ enable: {
+ x: true,
+ y: true
+ },
+ constraint: {
+ top: false,
+ bottom: false,
+ left: false,
+ right: false
+ },
+ momentum_multiplier: 2000,
+ duration: 1000,
+ ease: TL.Ease.easeInOutQuint
+ };
+
+
+ // Animation Object
+ this.animator = null;
+
+ // Drag Event Type
+ this.dragevent = this.mousedrag;
+
+ if (TL.Browser.touch) {
+ this.dragevent = this.touchdrag;
+ }
+
+ // Draggable Data
+ this.data = {
+ sliding: false,
+ direction: "none",
+ pagex: {
+ start: 0,
+ end: 0
+ },
+ pagey: {
+ start: 0,
+ end: 0
+ },
+ pos: {
+ start: {
+ x: 0,
+ y:0
+ },
+ end: {
+ x: 0,
+ y:0
+ }
+ },
+ new_pos: {
+ x: 0,
+ y: 0
+ },
+ new_pos_parent: {
+ x: 0,
+ y: 0
+ },
+ time: {
+ start: 0,
+ end: 0
+ },
+ touch: false
+ };
+
+ // Merge Data and Options
+ TL.Util.mergeData(this.options, options);
+
+
+ },
+
+ enable: function(e) {
+
+ this.data.pos.start = 0;
+ this._el.move.style.left = this.data.pos.start.x + "px";
+ this._el.move.style.top = this.data.pos.start.y + "px";
+ this._el.move.style.position = "absolute";
+ },
+
+ disable: function() {
+ TL.DomEvent.removeListener(this._el.drag, this.dragevent.down, this._onDragStart, this);
+ TL.DomEvent.removeListener(this._el.drag, this.dragevent.up, this._onDragEnd, this);
+ },
+
+ stopMomentum: function() {
+ if (this.animator) {
+ this.animator.stop();
+ }
+
+ },
+
+ updateConstraint: function(c) {
+ this.options.constraint = c;
+
+ },
+
+ /* Private Methods
+ ================================================== */
+ _onDragStart: function(e) {
+ if (TL.Browser.touch) {
+ if (e.originalEvent) {
+ this.data.pagex.start = e.originalEvent.touches[0].screenX;
+ this.data.pagey.start = e.originalEvent.touches[0].screenY;
+ } else {
+ this.data.pagex.start = e.targetTouches[0].screenX;
+ this.data.pagey.start = e.targetTouches[0].screenY;
+ }
+ } else {
+ this.data.pagex.start = e.pageX;
+ this.data.pagey.start = e.pageY;
+ }
+
+ // Center element to finger or mouse
+ if (this.options.enable.x) {
+ this._el.move.style.left = this.data.pagex.start - (this._el.move.offsetWidth / 2) + "px";
+ }
+
+ if (this.options.enable.y) {
+ this._el.move.style.top = this.data.pagey.start - (this._el.move.offsetHeight / 2) + "px";
+ }
+
+ this.data.pos.start = TL.Dom.getPosition(this._el.drag);
+ this.data.time.start = new Date().getTime();
+
+ this.fire("dragstart", this.data);
+ TL.DomEvent.addListener(this._el.drag, this.dragevent.move, this._onDragMove, this);
+ TL.DomEvent.addListener(this._el.drag, this.dragevent.leave, this._onDragEnd, this);
+ },
+
+ _onDragEnd: function(e) {
+ this.data.sliding = false;
+ TL.DomEvent.removeListener(this._el.drag, this.dragevent.move, this._onDragMove, this);
+ TL.DomEvent.removeListener(this._el.drag, this.dragevent.leave, this._onDragEnd, this);
+ this.fire("dragend", this.data);
+
+ // momentum
+ this._momentum();
+ },
+
+ _onDragMove: function(e) {
+ e.preventDefault();
+ this.data.sliding = true;
+
+ if (TL.Browser.touch) {
+ if (e.originalEvent) {
+ this.data.pagex.end = e.originalEvent.touches[0].screenX;
+ this.data.pagey.end = e.originalEvent.touches[0].screenY;
+ } else {
+ this.data.pagex.end = e.targetTouches[0].screenX;
+ this.data.pagey.end = e.targetTouches[0].screenY;
+ }
+
+ } else {
+ this.data.pagex.end = e.pageX;
+ this.data.pagey.end = e.pageY;
+ }
+
+ this.data.pos.end = TL.Dom.getPosition(this._el.drag);
+ this.data.new_pos.x = -(this.data.pagex.start - this.data.pagex.end - this.data.pos.start.x);
+ this.data.new_pos.y = -(this.data.pagey.start - this.data.pagey.end - this.data.pos.start.y );
+
+ if (this.options.enable.x) {
+ this._el.move.style.left = this.data.new_pos.x + "px";
+ }
+
+ if (this.options.enable.y) {
+ this._el.move.style.top = this.data.new_pos.y + "px";
+ }
+
+ this.fire("dragmove", this.data);
+ },
+
+ _momentum: function() {
+ var pos_adjust = {
+ x: 0,
+ y: 0,
+ time: 0
+ },
+ pos_change = {
+ x: 0,
+ y: 0,
+ time: 0
+ },
+ swipe = false,
+ swipe_direction = "";
+
+
+ if (TL.Browser.touch) {
+ // Treat mobile multiplier differently
+ //this.options.momentum_multiplier = this.options.momentum_multiplier * 2;
+ }
+
+ pos_adjust.time = (new Date().getTime() - this.data.time.start) * 10;
+ pos_change.time = (new Date().getTime() - this.data.time.start) * 10;
+
+ pos_change.x = this.options.momentum_multiplier * (Math.abs(this.data.pagex.end) - Math.abs(this.data.pagex.start));
+ pos_change.y = this.options.momentum_multiplier * (Math.abs(this.data.pagey.end) - Math.abs(this.data.pagey.start));
+
+ pos_adjust.x = Math.round(pos_change.x / pos_change.time);
+ pos_adjust.y = Math.round(pos_change.y / pos_change.time);
+
+ this.data.new_pos.x = Math.min(this.data.pos.end.x + pos_adjust.x);
+ this.data.new_pos.y = Math.min(this.data.pos.end.y + pos_adjust.y);
+
+
+ if (!this.options.enable.x) {
+ this.data.new_pos.x = this.data.pos.start.x;
+ } else if (this.data.new_pos.x < 0) {
+ this.data.new_pos.x = 0;
+ }
+
+ if (!this.options.enable.y) {
+ this.data.new_pos.y = this.data.pos.start.y;
+ } else if (this.data.new_pos.y < 0) {
+ this.data.new_pos.y = 0;
+ }
+
+ // Detect Swipe
+ if (pos_change.time < 3000) {
+ swipe = true;
+ }
+
+ // Detect Direction
+ if (Math.abs(pos_change.x) > 10000) {
+ this.data.direction = "left";
+ if (pos_change.x > 0) {
+ this.data.direction = "right";
+ }
+ }
+ // Detect Swipe
+ if (Math.abs(pos_change.y) > 10000) {
+ this.data.direction = "up";
+ if (pos_change.y > 0) {
+ this.data.direction = "down";
+ }
+ }
+ this._animateMomentum();
+ if (swipe) {
+ this.fire("swipe_" + this.data.direction, this.data);
+ }
+
+ },
+
+
+ _animateMomentum: function() {
+ var pos = {
+ x: this.data.new_pos.x,
+ y: this.data.new_pos.y
+ },
+ animate = {
+ duration: this.options.duration,
+ easing: TL.Ease.easeOutStrong
+ };
+
+ if (this.options.enable.y) {
+ if (this.options.constraint.top || this.options.constraint.bottom) {
+ if (pos.y > this.options.constraint.bottom) {
+ pos.y = this.options.constraint.bottom;
+ } else if (pos.y < this.options.constraint.top) {
+ pos.y = this.options.constraint.top;
+ }
+ }
+ animate.top = Math.floor(pos.y) + "px";
+ }
+
+ if (this.options.enable.x) {
+ if (this.options.constraint.left || this.options.constraint.right) {
+ if (pos.x > this.options.constraint.left) {
+ pos.x = this.options.constraint.left;
+ } else if (pos.x < this.options.constraint.right) {
+ pos.x = this.options.constraint.right;
+ }
+ }
+ animate.left = Math.floor(pos.x) + "px";
+ }
+
+ this.animator = TL.Animate(this._el.move, animate);
+
+ this.fire("momentum", this.data);
+ }
+});
+
+
+/* **********************************************
+ Begin TL.Swipable.js
+********************************************** */
+
+/* TL.Swipable
+ TL.Draggable allows you to add dragging capabilities to any element. Supports mobile devices too.
+ TODO Enable constraints
+================================================== */
+
+TL.Swipable = TL.Class.extend({
+
+ includes: TL.Events,
+
+ _el: {},
+
+ mousedrag: {
+ down: "mousedown",
+ up: "mouseup",
+ leave: "mouseleave",
+ move: "mousemove"
+ },
+
+ touchdrag: {
+ down: "touchstart",
+ up: "touchend",
+ leave: "mouseleave",
+ move: "touchmove"
+ },
+
+ initialize: function (drag_elem, move_elem, options) {
+
+ // DOM ELements
+ this._el = {
+ drag: drag_elem,
+ move: drag_elem
+ };
+
+ if (move_elem) {
+ this._el.move = move_elem;
+ }
+
+
+ //Options
+ this.options = {
+ snap: false,
+ enable: {
+ x: true,
+ y: true
+ },
+ constraint: {
+ top: false,
+ bottom: false,
+ left: 0,
+ right: false
+ },
+ momentum_multiplier: 2000,
+ duration: 1000,
+ ease: TL.Ease.easeInOutQuint
+ };
+
+
+ // Animation Object
+ this.animator = null;
+
+ // Drag Event Type
+ this.dragevent = this.mousedrag;
+
+ if (TL.Browser.touch) {
+ this.dragevent = this.touchdrag;
+ }
+
+ // Draggable Data
+ this.data = {
+ sliding: false,
+ direction: "none",
+ pagex: {
+ start: 0,
+ end: 0
+ },
+ pagey: {
+ start: 0,
+ end: 0
+ },
+ pos: {
+ start: {
+ x: 0,
+ y:0
+ },
+ end: {
+ x: 0,
+ y:0
+ }
+ },
+ new_pos: {
+ x: 0,
+ y: 0
+ },
+ new_pos_parent: {
+ x: 0,
+ y: 0
+ },
+ time: {
+ start: 0,
+ end: 0
+ },
+ touch: false
+ };
+
+ // Merge Data and Options
+ TL.Util.mergeData(this.options, options);
+
+
+ },
+
+ enable: function(e) {
+ TL.DomEvent.addListener(this._el.drag, this.dragevent.down, this._onDragStart, this);
+ TL.DomEvent.addListener(this._el.drag, this.dragevent.up, this._onDragEnd, this);
+
+ this.data.pos.start = 0; //TL.Dom.getPosition(this._el.move);
+ this._el.move.style.left = this.data.pos.start.x + "px";
+ this._el.move.style.top = this.data.pos.start.y + "px";
+ this._el.move.style.position = "absolute";
+ //this._el.move.style.zIndex = "11";
+ //this._el.move.style.cursor = "move";
+ },
+
+ disable: function() {
+ TL.DomEvent.removeListener(this._el.drag, this.dragevent.down, this._onDragStart, this);
+ TL.DomEvent.removeListener(this._el.drag, this.dragevent.up, this._onDragEnd, this);
+ },
+
+ stopMomentum: function() {
+ if (this.animator) {
+ this.animator.stop();
+ }
+
+ },
+
+ updateConstraint: function(c) {
+ this.options.constraint = c;
+
+ // Temporary until issues are fixed
+
+ },
+
+ /* Private Methods
+ ================================================== */
+ _onDragStart: function(e) {
+
+ if (this.animator) {
+ this.animator.stop();
+ }
+
+ if (TL.Browser.touch) {
+ if (e.originalEvent) {
+ this.data.pagex.start = e.originalEvent.touches[0].screenX;
+ this.data.pagey.start = e.originalEvent.touches[0].screenY;
+ } else {
+ this.data.pagex.start = e.targetTouches[0].screenX;
+ this.data.pagey.start = e.targetTouches[0].screenY;
+ }
+ } else {
+ this.data.pagex.start = e.pageX;
+ this.data.pagey.start = e.pageY;
+ }
+
+ // Center element to finger or mouse
+ if (this.options.enable.x) {
+ //this._el.move.style.left = this.data.pagex.start - (this._el.move.offsetWidth / 2) + "px";
+ }
+
+ if (this.options.enable.y) {
+ //this._el.move.style.top = this.data.pagey.start - (this._el.move.offsetHeight / 2) + "px";
+ }
+
+ this.data.pos.start = {x:this._el.move.offsetLeft, y:this._el.move.offsetTop};
+
+
+ this.data.time.start = new Date().getTime();
+
+ this.fire("dragstart", this.data);
+ TL.DomEvent.addListener(this._el.drag, this.dragevent.move, this._onDragMove, this);
+ TL.DomEvent.addListener(this._el.drag, this.dragevent.leave, this._onDragEnd, this);
+ },
+
+ _onDragEnd: function(e) {
+ this.data.sliding = false;
+ TL.DomEvent.removeListener(this._el.drag, this.dragevent.move, this._onDragMove, this);
+ TL.DomEvent.removeListener(this._el.drag, this.dragevent.leave, this._onDragEnd, this);
+ this.fire("dragend", this.data);
+
+ // momentum
+ this._momentum();
+ },
+
+ _onDragMove: function(e) {
+ var change = {
+ x:0,
+ y:0
+ }
+ //e.preventDefault();
+ this.data.sliding = true;
+
+ if (TL.Browser.touch) {
+ if (e.originalEvent) {
+ this.data.pagex.end = e.originalEvent.touches[0].screenX;
+ this.data.pagey.end = e.originalEvent.touches[0].screenY;
+ } else {
+ this.data.pagex.end = e.targetTouches[0].screenX;
+ this.data.pagey.end = e.targetTouches[0].screenY;
+ }
+
+ } else {
+ this.data.pagex.end = e.pageX;
+ this.data.pagey.end = e.pageY;
+ }
+
+ change.x = this.data.pagex.start - this.data.pagex.end;
+ change.y = this.data.pagey.start - this.data.pagey.end;
+
+ this.data.pos.end = {x:this._el.drag.offsetLeft, y:this._el.drag.offsetTop};
+
+ this.data.new_pos.x = -(change.x - this.data.pos.start.x);
+ this.data.new_pos.y = -(change.y - this.data.pos.start.y );
+
+ if (this.options.enable.x && ( Math.abs(change.x) > Math.abs(change.y) ) ) {
+ e.preventDefault();
+ this._el.move.style.left = this.data.new_pos.x + "px";
+ }
+
+ if (this.options.enable.y && ( Math.abs(change.y) > Math.abs(change.y) ) ) {
+ e.preventDefault();
+ this._el.move.style.top = this.data.new_pos.y + "px";
+ }
+
+ this.fire("dragmove", this.data);
+ },
+
+ _momentum: function() {
+ var pos_adjust = {
+ x: 0,
+ y: 0,
+ time: 0
+ },
+ pos_change = {
+ x: 0,
+ y: 0,
+ time: 0
+ },
+ swipe_detect = {
+ x: false,
+ y: false
+ },
+ swipe = false,
+ swipe_direction = "";
+
+
+ this.data.direction = null;
+
+ pos_adjust.time = (new Date().getTime() - this.data.time.start) * 10;
+ pos_change.time = (new Date().getTime() - this.data.time.start) * 10;
+
+ pos_change.x = this.options.momentum_multiplier * (Math.abs(this.data.pagex.end) - Math.abs(this.data.pagex.start));
+ pos_change.y = this.options.momentum_multiplier * (Math.abs(this.data.pagey.end) - Math.abs(this.data.pagey.start));
+
+ pos_adjust.x = Math.round(pos_change.x / pos_change.time);
+ pos_adjust.y = Math.round(pos_change.y / pos_change.time);
+
+ this.data.new_pos.x = Math.min(this.data.new_pos.x + pos_adjust.x);
+ this.data.new_pos.y = Math.min(this.data.new_pos.y + pos_adjust.y);
+
+ if (!this.options.enable.x) {
+ this.data.new_pos.x = this.data.pos.start.x;
+ } else if (this.options.constraint.left && this.data.new_pos.x > this.options.constraint.left) {
+ this.data.new_pos.x = this.options.constraint.left;
+ }
+
+ if (!this.options.enable.y) {
+ this.data.new_pos.y = this.data.pos.start.y;
+ } else if (this.data.new_pos.y < 0) {
+ this.data.new_pos.y = 0;
+ }
+
+ // Detect Swipe
+ if (pos_change.time < 2000) {
+ swipe = true;
+ }
+
+
+ if (this.options.enable.x && this.options.enable.y) {
+ if (Math.abs(pos_change.x) > Math.abs(pos_change.y)) {
+ swipe_detect.x = true;
+ } else {
+ swipe_detect.y = true;
+ }
+ } else if (this.options.enable.x) {
+ if (Math.abs(pos_change.x) > Math.abs(pos_change.y)) {
+ swipe_detect.x = true;
+ }
+ } else {
+ if (Math.abs(pos_change.y) > Math.abs(pos_change.x)) {
+ swipe_detect.y = true;
+ }
+ }
+
+ // Detect Direction and long swipe
+ if (swipe_detect.x) {
+
+ // Long Swipe
+ if (Math.abs(pos_change.x) > (this._el.drag.offsetWidth/2)) {
+ swipe = true;
+ }
+
+ if (Math.abs(pos_change.x) > 10000) {
+ this.data.direction = "left";
+ if (pos_change.x > 0) {
+ this.data.direction = "right";
+ }
+ }
+ }
+
+ if (swipe_detect.y) {
+
+ // Long Swipe
+ if (Math.abs(pos_change.y) > (this._el.drag.offsetHeight/2)) {
+ swipe = true;
+ }
+
+ if (Math.abs(pos_change.y) > 10000) {
+ this.data.direction = "up";
+ if (pos_change.y > 0) {
+ this.data.direction = "down";
+ }
+ }
+ }
+
+ if (pos_change.time < 1000 ) {
+
+ } else {
+ this._animateMomentum();
+ }
+
+ if (swipe && this.data.direction) {
+ this.fire("swipe_" + this.data.direction, this.data);
+ } else if (this.data.direction) {
+ this.fire("swipe_nodirection", this.data);
+ } else if (this.options.snap) {
+ this.animator.stop();
+
+ this.animator = TL.Animate(this._el.move, {
+ top: this.data.pos.start.y,
+ left: this.data.pos.start.x,
+ duration: this.options.duration,
+ easing: TL.Ease.easeOutStrong
+ });
+ }
+
+ },
+
+
+ _animateMomentum: function() {
+ var pos = {
+ x: this.data.new_pos.x,
+ y: this.data.new_pos.y
+ },
+ animate = {
+ duration: this.options.duration,
+ easing: TL.Ease.easeOutStrong
+ };
+
+ if (this.options.enable.y) {
+ if (this.options.constraint.top || this.options.constraint.bottom) {
+ if (pos.y > this.options.constraint.bottom) {
+ pos.y = this.options.constraint.bottom;
+ } else if (pos.y < this.options.constraint.top) {
+ pos.y = this.options.constraint.top;
+ }
+ }
+ animate.top = Math.floor(pos.y) + "px";
+ }
+
+ if (this.options.enable.x) {
+ if (this.options.constraint.left && pos.x >= this.options.constraint.left) {
+ pos.x = this.options.constraint.left;
+ }
+ if (this.options.constraint.right && pos.x < this.options.constraint.right) {
+ pos.x = this.options.constraint.right;
+ }
+
+ animate.left = Math.floor(pos.x) + "px";
+ }
+
+ this.animator = TL.Animate(this._el.move, animate);
+
+ this.fire("momentum", this.data);
+ }
+});
+
+
+/* **********************************************
+ Begin TL.MenuBar.js
+********************************************** */
+
+/* TL.MenuBar
+ Draggable component to control size
+================================================== */
+
+TL.MenuBar = TL.Class.extend({
+
+ includes: [TL.Events, TL.DomMixins],
+
+ _el: {},
+
+ /* Constructor
+ ================================================== */
+ initialize: function(elem, parent_elem, options) {
+ // DOM ELEMENTS
+ this._el = {
+ parent: {},
+ container: {},
+ button_backtostart: {},
+ button_zoomin: {},
+ button_zoomout: {},
+ arrow: {},
+ line: {},
+ coverbar: {},
+ grip: {}
+ };
+
+ this.collapsed = false;
+
+ if (typeof elem === 'object') {
+ this._el.container = elem;
+ } else {
+ this._el.container = TL.Dom.get(elem);
+ }
+
+ if (parent_elem) {
+ this._el.parent = parent_elem;
+ }
+
+ //Options
+ this.options = {
+ width: 600,
+ height: 600,
+ duration: 1000,
+ ease: TL.Ease.easeInOutQuint,
+ menubar_default_y: 0
+ };
+
+ // Animation
+ this.animator = {};
+
+ // Merge Data and Options
+ TL.Util.mergeData(this.options, options);
+
+ this._initLayout();
+ this._initEvents();
+ },
+
+ /* Public
+ ================================================== */
+ show: function(d) {
+
+ var duration = this.options.duration;
+ if (d) {
+ duration = d;
+ }
+ /*
+ this.animator = TL.Animate(this._el.container, {
+ top: this.options.menubar_default_y + "px",
+ duration: duration,
+ easing: TL.Ease.easeOutStrong
+ });
+ */
+ },
+
+ hide: function(top) {
+ /*
+ this.animator = TL.Animate(this._el.container, {
+ top: top,
+ duration: this.options.duration,
+ easing: TL.Ease.easeOutStrong
+ });
+ */
+ },
+
+ toogleZoomIn: function(show) {
+ if (show) {
+ TL.DomUtil.removeClass(this._el.button_zoomin,'tl-menubar-button-inactive');
+ } else {
+ TL.DomUtil.addClass(this._el.button_zoomin,'tl-menubar-button-inactive');
+ }
+ },
+
+ toogleZoomOut: function(show) {
+ if (show) {
+ TL.DomUtil.removeClass(this._el.button_zoomout,'tl-menubar-button-inactive');
+ } else {
+ TL.DomUtil.addClass(this._el.button_zoomout,'tl-menubar-button-inactive');
+ }
+ },
+
+ setSticky: function(y) {
+ this.options.menubar_default_y = y;
+ },
+
+ /* Color
+ ================================================== */
+ setColor: function(inverted) {
+ if (inverted) {
+ this._el.container.className = 'tl-menubar tl-menubar-inverted';
+ } else {
+ this._el.container.className = 'tl-menubar';
+ }
+ },
+
+ /* Update Display
+ ================================================== */
+ updateDisplay: function(w, h, a, l) {
+ this._updateDisplay(w, h, a, l);
+ },
+
+
+ /* Events
+ ================================================== */
+ _onButtonZoomIn: function(e) {
+ this.fire("zoom_in", e);
+ },
+
+ _onButtonZoomOut: function(e) {
+ this.fire("zoom_out", e);
+ },
+
+ _onButtonBackToStart: function(e) {
+ this.fire("back_to_start", e);
+ },
+
+
+ /* Private Methods
+ ================================================== */
+ _initLayout: function () {
+
+ // Create Layout
+ this._el.button_zoomin = TL.Dom.create('span', 'tl-menubar-button', this._el.container);
+ this._el.button_zoomout = TL.Dom.create('span', 'tl-menubar-button', this._el.container);
+ this._el.button_backtostart = TL.Dom.create('span', 'tl-menubar-button', this._el.container);
+
+ if (TL.Browser.mobile) {
+ this._el.container.setAttribute("ontouchstart"," ");
+ }
+
+ this._el.button_backtostart.innerHTML = "<span class='tl-icon-goback'></span>";
+ this._el.button_zoomin.innerHTML = "<span class='tl-icon-zoom-in'></span>";
+ this._el.button_zoomout.innerHTML = "<span class='tl-icon-zoom-out'></span>";
+
+
+ },
+
+ _initEvents: function () {
+ TL.DomEvent.addListener(this._el.button_backtostart, 'click', this._onButtonBackToStart, this);
+ TL.DomEvent.addListener(this._el.button_zoomin, 'click', this._onButtonZoomIn, this);
+ TL.DomEvent.addListener(this._el.button_zoomout, 'click', this._onButtonZoomOut, this);
+ },
+
+ // Update Display
+ _updateDisplay: function(width, height, animate) {
+
+ if (width) {
+ this.options.width = width;
+ }
+ if (height) {
+ this.options.height = height;
+ }
+ }
+
+});
+
+
+/* **********************************************
+ Begin TL.Message.js
+********************************************** */
+
+/* TL.Message
+
+================================================== */
+
+TL.Message = TL.Class.extend({
+
+ includes: [TL.Events, TL.DomMixins, TL.I18NMixins],
+
+ _el: {},
+
+ /* Constructor
+ ================================================== */
+ initialize: function(data, options, add_to_container) {
+ // DOM ELEMENTS
+ this._el = {
+ parent: {},
+ container: {},
+ message_container: {},
+ loading_icon: {},
+ message: {}
+ };
+
+ //Options
+ this.options = {
+ width: 600,
+ height: 600,
+ message_class: "tl-message",
+ message_icon_class: "tl-loading-icon"
+ };
+
+ this._add_to_container = add_to_container || {}; // save ref
+
+ // Merge Data and Options
+ TL.Util.mergeData(this.data, data);
+ TL.Util.mergeData(this.options, options);
+
+ this._el.container = TL.Dom.create("div", this.options.message_class);
+
+ if (add_to_container) {
+ add_to_container.appendChild(this._el.container);
+ this._el.parent = add_to_container;
+ }
+
+ // Animation
+ this.animator = {};
+
+ this._initLayout();
+ this._initEvents();
+ },
+
+ /* Public
+ ================================================== */
+ updateMessage: function(t) {
+ this._updateMessage(t);
+ },
+
+
+ /* Update Display
+ ================================================== */
+ updateDisplay: function(w, h) {
+ this._updateDisplay(w, h);
+ },
+
+ _updateMessage: function(t) {
+ if (!t) {
+ this._el.message.innerHTML = this._('loading');
+ } else {
+ this._el.message.innerHTML = t;
+ }
+
+ // Re-add to DOM?
+ if(!this._el.parent.atrributes && this._add_to_container.attributes) {
+ this._add_to_container.appendChild(this._el.container);
+ this._el.parent = this._add_to_container;
+ }
+ },
+
+
+ /* Events
+ ================================================== */
+
+
+ _onMouseClick: function() {
+ this.fire("clicked", this.options);
+ },
+
+ _onRemove: function() {
+ this._el.parent = {};
+ },
+
+
+ /* Private Methods
+ ================================================== */
+ _initLayout: function () {
+
+ // Create Layout
+ this._el.message_container = TL.Dom.create("div", "tl-message-container", this._el.container);
+ this._el.loading_icon = TL.Dom.create("div", this.options.message_icon_class, this._el.message_container);
+ this._el.message = TL.Dom.create("div", "tl-message-content", this._el.message_container);
+
+ this._updateMessage();
+
+ },
+
+ _initEvents: function () {
+ TL.DomEvent.addListener(this._el.container, 'click', this._onMouseClick, this);
+ TL.DomEvent.addListener(this, 'removed', this._onRemove, this);
+ },
+
+ // Update Display
+ _updateDisplay: function(width, height, animate) {
+
+ }
+
+});
+
+/* **********************************************
+ Begin TL.MediaType.js
+********************************************** */
+
+/* TL.MediaType
+ Determines the type of media the url string is.
+ returns an object with .type and .id
+ You can add new media types by adding a regex
+ to match and the media class name to use to
+ render the media
+
+ The image_only parameter indicates that the
+ call only wants an image-based media type
+ that can be resolved to an image URL.
+
+ TODO
+ Allow array so a slideshow can be a mediatype
+================================================== */
+TL.MediaType = function(m, image_only) {
+ var media = {},
+ media_types = [
+ {
+ type: "youtube",
+ name: "YouTube",
+ match_str: "^(https?:)?\/*(www.)?youtube|youtu\.be",
+ cls: TL.Media.YouTube
+ },
+ {
+ type: "vimeo",
+ name: "Vimeo",
+ match_str: "^(https?:)?\/*(player.)?vimeo\.com",
+ cls: TL.Media.Vimeo
+ },
+ {
+ type: "dailymotion",
+ name: "DailyMotion",
+ match_str: "^(https?:)?\/*(www.)?dailymotion\.com",
+ cls: TL.Media.DailyMotion
+ },
+ {
+ type: "vine",
+ name: "Vine",
+ match_str: "^(https?:)?\/*(www.)?vine\.co",
+ cls: TL.Media.Vine
+ },
+ {
+ type: "soundcloud",
+ name: "SoundCloud",
+ match_str: "^(https?:)?\/*(player.)?soundcloud\.com",
+ cls: TL.Media.SoundCloud
+ },
+ {
+ type: "twitter",
+ name: "Twitter",
+ match_str: "^(https?:)?\/*(www.)?twitter\.com",
+ cls: TL.Media.Twitter
+ },
+ {
+ type: "twitterembed",
+ name: "TwitterEmbed",
+ match_str: "<blockquote class=['\"]twitter-tweet['\"]",
+ cls: TL.Media.Twitter
+ },
+ {
+ type: "googlemaps",
+ name: "Google Map",
+ match_str: /google.+?\/maps\/@([-\d.]+),([-\d.]+),((?:[-\d.]+[zmayht],?)*)|google.+?\/maps\/search\/([\w\W]+)\/@([-\d.]+),([-\d.]+),((?:[-\d.]+[zmayht],?)*)|google.+?\/maps\/place\/([\w\W]+)\/@([-\d.]+),([-\d.]+),((?:[-\d.]+[zmayht],?)*)|google.+?\/maps\/dir\/([\w\W]+)\/([\w\W]+)\/@([-\d.]+),([-\d.]+),((?:[-\d.]+[zmayht],?)*)/,
+ cls: TL.Media.GoogleMap
+ },
+ {
+ type: "googleplus",
+ name: "Google+",
+ match_str: "^(https?:)?\/*plus.google",
+ cls: TL.Media.GooglePlus
+ },
+ {
+ type: "flickr",
+ name: "Flickr",
+ match_str: "^(https?:)?\/*(www.)?flickr.com\/photos",
+ cls: TL.Media.Flickr
+ },
+ {
+ type: "flickr",
+ name: "Flickr",
+ match_str: "^(https?:\/\/)?flic.kr\/.*",
+ cls: TL.Media.Flickr
+ },
+ {
+ type: "instagram",
+ name: "Instagram",
+ match_str: /^(https?:)?\/*(www.)?(instagr.am|^(https?:)?\/*(www.)?instagram.com)\/p\//,
+ cls: TL.Media.Instagram
+ },
+ {
+ type: "profile",
+ name: "Profile",
+ match_str: /^(https?:)?\/*(www.)?instagr.am\/[a-zA-Z0-9]{2,}|^(https?:)?\/*(www.)?instagram.com\/[a-zA-Z0-9]{2,}/,
+ cls: TL.Media.Profile
+ },
+ {
+ type: "documentcloud",
+ name: "Document Cloud",
+ match_str: /documentcloud.org\//,
+ cls: TL.Media.DocumentCloud
+ },
+ {
+ type: "image",
+ name: "Image",
+ match_str: /(jpg|jpeg|png|gif|svg)(\?.*)?$/i,
+ cls: TL.Media.Image
+ },
+ {
+ type: "imgur",
+ name: "Imgur",
+ match_str: /^.*imgur.com\/.+$|<blockquote class=['\"]imgur-embed-pub['\"]/i,
+ cls: TL.Media.Imgur
+ },
+ {
+ type: "googledocs",
+ name: "Google Doc",
+ match_str: "^(https?:)?\/*[^.]*.google.com\/[^\/]*\/d\/[^\/]*\/[^\/]*\?usp=sharing|^(https?:)?\/*drive.google.com\/open\?id=[^\&]*\&authuser=0|^(https?:)?\/*drive.google.com\/open\?id=[^\&]*|^(https?:)?\/*[^.]*.googledrive.com\/host\/[^\/]*\/",
+ cls: TL.Media.GoogleDoc
+ },
+ {
+ type: "pdf",
+ name: "PDF",
+ match_str: /^.*\.pdf(\?.*)?(\#.*)?/,
+ cls: TL.Media.PDF
+ },
+ {
+ type: "wikipedia",
+ name: "Wikipedia",
+ match_str: "^(https?:)?\/*(www.)?wikipedia\.org|^(https?:)?\/*([a-z][a-z].)?wikipedia\.org",
+ cls: TL.Media.Wikipedia
+ },
+ {
+ type: "spotify",
+ name: "spotify",
+ match_str: "spotify",
+ cls: TL.Media.Spotify
+ },
+ {
+ type: "iframe",
+ name: "iFrame",
+ match_str: "iframe",
+ cls: TL.Media.IFrame
+ },
+ {
+ type: "storify",
+ name: "Storify",
+ match_str: "storify",
+ cls: TL.Media.Storify
+ },
+ {
+ type: "blockquote",
+ name: "Quote",
+ match_str: "blockquote",
+ cls: TL.Media.Blockquote
+ },
+ // {
+ // type: "website",
+ // name: "Website",
+ // match_str: "https?://",
+ // cls: TL.Media.Website
+ // },
+ {
+ type: "video",
+ name: "Video",
+ match_str: /(mp4)(\?.*)?$/i,
+ cls: TL.Media.Video
+ },
+ {
+ type: "wistia",
+ name: "Wistia",
+ match_str: /https?:\/\/(.+)?(wistia\.com|wi\.st)\/.*/i,
+ cls: TL.Media.Wistia
+ },
+ {
+ type: "audio",
+ name: "Audio",
+ match_str: /(mp3|wav|m4a)(\?.*)?$/i,
+ cls: TL.Media.Audio
+ },
+ {
+ type: "imageblank",
+ name: "Imageblank",
+ match_str: "",
+ cls: TL.Media.Image
+ }
+ ];
+
+ if(image_only) {
+ if (m instanceof Array) {
+ return false;
+ }
+ for (var i = 0; i < media_types.length; i++) {
+ switch(media_types[i].type) {
+ case "flickr":
+ case "image":
+ case "instagram":
+ if (m.url.match(media_types[i].match_str)) {
+ media = media_types[i];
+ return media;
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ } else {
+ for (var i = 0; i < media_types.length; i++) {
+ if (m instanceof Array) {
+ return media = {
+ type: "slider",
+ cls: TL.Media.Slider
+ };
+ } else if (m.url.match(media_types[i].match_str)) {
+ media = media_types[i];
+ return media;
+ }
+ }
+ }
+ return false;
+}
+
+
+/* **********************************************
+ Begin TL.Media.js
+********************************************** */
+
+/* TL.Media
+ Main media template for media assets.
+ Takes a data object and populates a dom object
+================================================== */
+// TODO add link
+
+TL.Media = TL.Class.extend({
+
+ includes: [TL.Events, TL.I18NMixins],
+
+ _el: {},
+
+ /* Constructor
+ ================================================== */
+ initialize: function(data, options, add_to_container) {
+ // DOM ELEMENTS
+ this._el = {
+ container: {},
+ content_container: {},
+ content: {},
+ content_item: {},
+ content_link: {},
+ caption: null,
+ credit: null,
+ parent: {},
+ link: null
+ };
+
+ // Player (If Needed)
+ this.player = null;
+
+ // Timer (If Needed)
+ this.timer = null;
+ this.load_timer = null;
+
+ // Message
+ this.message = null;
+
+ // Media ID
+ this.media_id = null;
+
+ // State
+ this._state = {
+ loaded: false,
+ show_meta: false,
+ media_loaded: false
+ };
+
+ // Data
+ this.data = {
+ unique_id: null,
+ url: null,
+ credit: null,
+ caption: null,
+ credit_alternate: null,
+ caption_alternate: null,
+ link: null,
+ link_target: null
+ };
+
+ //Options
+ this.options = {
+ api_key_flickr: "f2cc870b4d233dd0a5bfe73fd0d64ef0",
+ api_key_googlemaps: "AIzaSyB9dW8e_iRrATFa8g24qB6BDBGdkrLDZYI",
+ api_key_embedly: "", // ae2da610d1454b66abdf2e6a4c44026d
+ credit_height: 0,
+ caption_height: 0,
+ background: 0 // is background media (for slide)
+ };
+
+ this.animator = {};
+
+ // Merge Data and Options
+ TL.Util.mergeData(this.options, options);
+ TL.Util.mergeData(this.data, data);
+
+ // Don't create DOM elements if this is background media
+ if(!this.options.background) {
+ this._el.container = TL.Dom.create("div", "tl-media");
+
+ if (this.data.unique_id) {
+ this._el.container.id = this.data.unique_id;
+ }
+
+ this._initLayout();
+
+ if (add_to_container) {
+ add_to_container.appendChild(this._el.container);
+ this._el.parent = add_to_container;
+ }
+ }
+ },
+
+ loadMedia: function() {
+ var self = this;
+
+ if (!this._state.loaded) {
+ try {
+ this.load_timer = setTimeout(function() {
+ self.loadingMessage();
+ self._loadMedia();
+ // self._state.loaded = true; handled in onLoaded()
+ self._updateDisplay();
+ }, 1200);
+ } catch (e) {
+ trace("Error loading media for ", this._media);
+ trace(e);
+ }
+ }
+ },
+
+ _updateMessage: function(msg) {
+ if(this.message) {
+ this.message.updateMessage(msg);
+ }
+ },
+
+ loadingMessage: function() {
+ this._updateMessage(this._('loading') + " " + this.options.media_name);
+ },
+
+ errorMessage: function(msg) {
+ if (msg) {
+ msg = this._('error') + ": " + msg;
+ } else {
+ msg = this._('error');
+ }
+ this._updateMessage(msg);
+ },
+
+ updateMediaDisplay: function(layout) {
+ if (this._state.loaded && !this.options.background) {
+
+ if (TL.Browser.mobile) {
+ this._el.content_item.style.maxHeight = (this.options.height/2) + "px";
+ } else {
+ this._el.content_item.style.maxHeight = this.options.height - this.options.credit_height - this.options.caption_height - 30 + "px";
+ }
+
+ //this._el.content_item.style.maxWidth = this.options.width + "px";
+ this._el.container.style.maxWidth = this.options.width + "px";
+ // Fix for max-width issues in Firefox
+ if (TL.Browser.firefox) {
+ if (this._el.content_item.offsetWidth > this._el.content_item.offsetHeight) {
+ //this._el.content_item.style.width = "100%";
+ }
+ }
+
+ this._updateMediaDisplay(layout);
+
+ if (this._state.media_loaded) {
+ if (this._el.credit) {
+ this._el.credit.style.width = this._el.content_item.offsetWidth + "px";
+ }
+ if (this._el.caption) {
+ this._el.caption.style.width = this._el.content_item.offsetWidth + "px";
+ }
+ }
+
+ }
+ },
+
+ /* Media Specific
+ ================================================== */
+ _loadMedia: function() {
+ // All overrides must call this.onLoaded() to set state
+ this.onLoaded();
+ },
+
+ _updateMediaDisplay: function(l) {
+ //this._el.content_item.style.maxHeight = (this.options.height - this.options.credit_height - this.options.caption_height - 16) + "px";
+ if(TL.Browser.firefox) {
+ this._el.content_item.style.maxWidth = this.options.width + "px";
+ this._el.content_item.style.width = "auto";
+ }
+ },
+
+ _getMeta: function() {
+
+ },
+
+ _getImageURL: function(w, h) {
+ // Image-based media types should return <img>-compatible src url
+ return "";
+ },
+
+ /* Public
+ ================================================== */
+ show: function() {
+
+ },
+
+ hide: function() {
+
+ },
+
+ addTo: function(container) {
+ container.appendChild(this._el.container);
+ this.onAdd();
+ },
+
+ removeFrom: function(container) {
+ container.removeChild(this._el.container);
+ this.onRemove();
+ },
+
+ getImageURL: function(w, h) {
+ return this._getImageURL(w, h);
+ },
+
+ // Update Display
+ updateDisplay: function(w, h, l) {
+ this._updateDisplay(w, h, l);
+ },
+
+ stopMedia: function() {
+ this._stopMedia();
+ },
+
+ loadErrorDisplay: function(message) {
+ try {
+ this._el.content.removeChild(this._el.content_item);
+ } catch(e) {
+ // if this._el.content_item isn't a child of this._el then just keep truckin
+ }
+ this._el.content_item = TL.Dom.create("div", "tl-media-item tl-media-loaderror", this._el.content);
+ this._el.content_item.innerHTML = "<div class='tl-icon-" + this.options.media_type + "'></div><p>" + message + "</p>";
+
+ // After Loaded
+ this.onLoaded(true);
+ },
+
+ /* Events
+ ================================================== */
+ onLoaded: function(error) {
+ this._state.loaded = true;
+ this.fire("loaded", this.data);
+ if (this.message) {
+ this.message.hide();
+ }
+ if (!(error || this.options.background)) {
+ this.showMeta();
+ }
+ this.updateDisplay();
+ },
+
+ onMediaLoaded: function(e) {
+ this._state.media_loaded = true;
+ this.fire("media_loaded", this.data);
+ if (this._el.credit) {
+ this._el.credit.style.width = this._el.content_item.offsetWidth + "px";
+ }
+ if (this._el.caption) {
+ this._el.caption.style.width = this._el.content_item.offsetWidth + "px";
+ }
+ },
+
+ showMeta: function(credit, caption) {
+ this._state.show_meta = true;
+ // Credit
+ if (this.data.credit && this.data.credit != "") {
+ this._el.credit = TL.Dom.create("div", "tl-credit", this._el.content_container);
+ this._el.credit.innerHTML = this.options.autolink == true ? TL.Util.linkify(this.data.credit) : this.data.credit;
+ this.options.credit_height = this._el.credit.offsetHeight;
+ }
+
+ // Caption
+ if (this.data.caption && this.data.caption != "") {
+ this._el.caption = TL.Dom.create("div", "tl-caption", this._el.content_container);
+ this._el.caption.innerHTML = this.options.autolink == true ? TL.Util.linkify(this.data.caption) : this.data.caption;
+ this.options.caption_height = this._el.caption.offsetHeight;
+ }
+
+ if (!this.data.caption || !this.data.credit) {
+ this.getMeta();
+ }
+
+ },
+
+ getMeta: function() {
+ this._getMeta();
+ },
+
+ updateMeta: function() {
+ if (!this.data.credit && this.data.credit_alternate) {
+ this._el.credit = TL.Dom.create("div", "tl-credit", this._el.content_container);
+ this._el.credit.innerHTML = this.data.credit_alternate;
+ this.options.credit_height = this._el.credit.offsetHeight;
+ }
+
+ if (!this.data.caption && this.data.caption_alternate) {
+ this._el.caption = TL.Dom.create("div", "tl-caption", this._el.content_container);
+ this._el.caption.innerHTML = this.data.caption_alternate;
+ this.options.caption_height = this._el.caption.offsetHeight;
+ }
+
+ this.updateDisplay();
+ },
+
+ onAdd: function() {
+ this.fire("added", this.data);
+ },
+
+ onRemove: function() {
+ this.fire("removed", this.data);
+ },
+
+ /* Private Methods
+ ================================================== */
+ _initLayout: function () {
+
+ // Message
+ this.message = new TL.Message({}, this.options);
+ this.message.addTo(this._el.container);
+
+ // Create Layout
+ this._el.content_container = TL.Dom.create("div", "tl-media-content-container", this._el.container);
+
+ // Link
+ if (this.data.link && this.data.link != "") {
+
+ this._el.link = TL.Dom.create("a", "tl-media-link", this._el.content_container);
+ this._el.link.href = this.data.link;
+ if (this.data.link_target && this.data.link_target != "") {
+ this._el.link.target = this.data.link_target;
+ } else {
+ this._el.link.target = "_blank";
+ }
+
+ this._el.content = TL.Dom.create("div", "tl-media-content", this._el.link);
+
+ } else {
+ this._el.content = TL.Dom.create("div", "tl-media-content", this._el.content_container);
+ }
+
+
+ },
+
+ // Update Display
+ _updateDisplay: function(w, h, l) {
+ if (w) {
+ this.options.width = w;
+
+ }
+ //this._el.container.style.width = this.options.width + "px";
+ if (h) {
+ this.options.height = h;
+ }
+
+ if (l) {
+ this.options.layout = l;
+ }
+
+ if (this._el.credit) {
+ this.options.credit_height = this._el.credit.offsetHeight;
+ }
+ if (this._el.caption) {
+ this.options.caption_height = this._el.caption.offsetHeight + 5;
+ }
+
+ this.updateMediaDisplay(this.options.layout);
+
+ },
+
+ _stopMedia: function() {
+
+ }
+
+});
+
+
+/* **********************************************
+ Begin TL.Media.Blockquote.js
+********************************************** */
+
+/* TL.Media.Blockquote
+================================================== */
+
+TL.Media.Blockquote = TL.Media.extend({
+
+ includes: [TL.Events],
+
+ /* Load the media
+ ================================================== */
+ _loadMedia: function() {
+ // Create Dom element
+ this._el.content_item = TL.Dom.create("div", "tl-media-item tl-media-blockquote", this._el.content);
+ this._el.content_container.className = "tl-media-content-container tl-media-content-container-text";
+
+ // Get Media ID
+ this.media_id = this.data.url;
+
+ // API Call
+ this._el.content_item.innerHTML = this.media_id;
+
+ // After Loaded
+ this.onLoaded();
+ },
+
+ updateMediaDisplay: function() {
+
+ },
+
+ _updateMediaDisplay: function() {
+
+ }
+
+
+});
+
+
+/* **********************************************
+ Begin TL.Media.DailyMotion.js
+********************************************** */
+
+/* TL.Media.DailyMotion
+================================================== */
+
+TL.Media.DailyMotion = TL.Media.extend({
+
+ includes: [TL.Events],
+
+ /* Load the media
+ ================================================== */
+ _loadMedia: function() {
+ var api_url,
+ self = this;
+
+ // Create Dom element
+ this._el.content_item = TL.Dom.create("div", "tl-media-item tl-media-iframe tl-media-dailymotion", this._el.content);
+
+ // Get Media ID
+ if (this.data.url.match("video")) {
+ this.media_id = this.data.url.split("video\/")[1].split(/[?&]/)[0];
+ } else {
+ this.media_id = this.data.url.split("embed\/")[1].split(/[?&]/)[0];
+ }
+
+ // API URL
+ api_url = "https://www.dailymotion.com/embed/video/" + this.media_id+"?api=postMessage";
+
+ // API Call
+ this._el.content_item.innerHTML = "<iframe autostart='false' frameborder='0' width='100%' height='100%' src='" + api_url + "'></iframe>"
+
+ // After Loaded
+ this.onLoaded();
+ },
+
+ // Update Media Display
+ _updateMediaDisplay: function() {
+ this._el.content_item.style.height = TL.Util.ratio.r16_9({w:this._el.content_item.offsetWidth}) + "px";
+ },
+
+ _stopMedia: function() {
+ this._el.content_item.querySelector("iframe").contentWindow.postMessage('{"command":"pause","parameters":[]}', "*");
+
+ }
+
+});
+
+
+/* **********************************************
+ Begin TL.Media.DocumentCloud.js
+********************************************** */
+
+/* TL.Media.DocumentCloud
+================================================== */
+
+TL.Media.DocumentCloud = TL.Media.extend({
+
+ includes: [TL.Events],
+
+ /* Load the media
+ ================================================== */
+ _loadMedia: function() {
+ var self = this;
+
+ // Create Dom elements
+ this._el.content_item = TL.Dom.create("div", "tl-media-item tl-media-documentcloud tl-media-shadow", this._el.content);
+ this._el.content_item.id = TL.Util.unique_ID(7)
+
+ // Check url
+ if(this.data.url.match(/\.html$/)) {
+ this.data.url = this._transformURL(this.data.url);
+ } else if(!(this.data.url.match(/.(json|js)$/))) {
+ trace("DOCUMENT CLOUD IN URL BUT INVALID SUFFIX");
+ }
+
+ // Load viewer API
+ TL.Load.js([
+ 'https://assets.documentcloud.org/viewer/loader.js',
+ 'https://assets.documentcloud.org/viewer/viewer.js'],
+ function() {
+ self.createMedia();
+ }
+ );
+ },
+
+ // Viewer API needs js, not html
+ _transformURL: function(url) {
+ return url.replace(/(.*)\.html$/, '$1.js')
+ },
+
+ // Update Media Display
+ _updateMediaDisplay: function() {
+ this._el.content_item.style.height = this.options.height + "px";
+ //this._el.content_item.style.width = this.options.width + "px";
+ },
+
+ createMedia: function() {
+ // DocumentCloud API call
+ DV.load(this.data.url, {
+ container: '#'+this._el.content_item.id,
+ showSidebar: false
+ });
+ this.onLoaded();
+ },
+
+
+
+ /* Events
+ ================================================== */
+
+
+
+});
+
+
+/* **********************************************
+ Begin TL.Media.Flickr.js
+********************************************** */
+
+/* TL.Media.Flickr
+
+================================================== */
+
+TL.Media.Flickr = TL.Media.extend({
+
+ includes: [TL.Events],
+
+ /* Load the media
+ ================================================== */
+ _loadMedia: function() {
+ var api_url,
+ self = this;
+
+ try {
+ // Get Media ID
+ this.establishMediaID();
+
+ // API URL
+ api_url = "https://api.flickr.com/services/rest/?method=flickr.photos.getSizes&api_key=" + this.options.api_key_flickr + "&photo_id=" + this.media_id + "&format=json&jsoncallback=?";
+
+ // API Call
+ TL.getJSON(api_url, function(d) {
+ if (d.stat == "ok") {
+ self.sizes = d.sizes.size; // store sizes info
+
+ if(!self.options.background) {
+ self.createMedia();
+ }
+
+ self.onLoaded();
+ } else {
+ self.loadErrorDisplay(self._("flickr_notfound_err"));
+ }
+ });
+ } catch(e) {
+ self.loadErrorDisplay(self._(e.message_key));
+ }
+ },
+
+ establishMediaID: function() {
+ if (this.data.url.match(/flic.kr\/.+/i)) {
+ var encoded = this.data.url.split('/').slice(-1)[0];
+ this.media_id = TL.Util.base58.decode(encoded);
+ } else {
+ var marker = 'flickr.com/photos/';
+ var idx = this.data.url.indexOf(marker);
+ if (idx == -1) { throw new TL.Error("flickr_invalidurl_err"); }
+ var pos = idx + marker.length;
+ this.media_id = this.data.url.substr(pos).split("/")[1];
+ }
+ },
+
+ createMedia: function() {
+ var self = this;
+
+ // Link
+ this._el.content_link = TL.Dom.create("a", "", this._el.content);
+ this._el.content_link.href = this.data.url;
+ this._el.content_link.target = "_blank";
+
+ // Photo
+ this._el.content_item = TL.Dom.create("img", "tl-media-item tl-media-image tl-media-flickr tl-media-shadow", this._el.content_link);
+
+ if (this.data.alt) {
+ this._el.content_item.alt = this.data.alt;
+ } else if (this.data.caption) {
+ this._el.content_item.alt = TL.Util.unhtmlify(this.data.caption);
+ }
+
+ if (this.data.title) {
+ this._el.content_item.title = this.data.title;
+ } else if (this.data.caption) {
+ this._el.content_item.title = TL.Util.unhtmlify(this.data.caption);
+ }
+
+ // Media Loaded Event
+ this._el.content_item.addEventListener('load', function(e) {
+ self.onMediaLoaded();
+ });
+
+ // Set Image Source
+ this._el.content_item.src = this.getImageURL(this.options.width, this.options.height);
+ },
+
+ getImageURL: function(w, h) {
+ var best_size = this.size_label(h),
+ source = this.sizes[this.sizes.length - 2].source;
+
+ for(var i = 0; i < this.sizes.length; i++) {
+ if (this.sizes[i].label == best_size) {
+ source = this.sizes[i].source;
+ }
+ }
+
+ return source;
+ },
+
+ _getMeta: function() {
+ var self = this,
+ api_url;
+
+ // API URL
+ api_url = "https://api.flickr.com/services/rest/?method=flickr.photos.getInfo&api_key=" + this.options.api_key_flickr + "&photo_id=" + this.media_id + "&format=json&jsoncallback=?";
+
+ // API Call
+ TL.getJSON(api_url, function(d) {
+ self.data.credit_alternate = "<a href='" + self.data.url + "' target='_blank'>" + d.photo.owner.realname + "</a>";
+ self.data.caption_alternate = d.photo.title._content + " " + d.photo.description._content;
+ self.updateMeta();
+ });
+ },
+
+ size_label: function(s) {
+ var _size = "";
+
+ if (s <= 75) {
+ if (s <= 0) {
+ _size = "Large";
+ } else {
+ _size = "Thumbnail";
+ }
+ } else if (s <= 180) {
+ _size = "Small";
+ } else if (s <= 240) {
+ _size = "Small 320";
+ } else if (s <= 375) {
+ _size = "Medium";
+ } else if (s <= 480) {
+ _size = "Medium 640";
+ } else if (s <= 600) {
+ _size = "Large";
+ } else {
+ _size = "Large";
+ }
+
+ return _size;
+ }
+
+
+
+});
+
+
+/* **********************************************
+ Begin TL.Media.GoogleDoc.js
+********************************************** */
+
+/* TL.Media.GoogleDoc
+
+================================================== */
+
+TL.Media.GoogleDoc = TL.Media.extend({
+
+ includes: [TL.Events],
+
+ /* Load the media
+ ================================================== */
+ _loadMedia: function() {
+ var url,
+ self = this;
+
+ // Create Dom element
+ this._el.content_item = TL.Dom.create("div", "tl-media-item tl-media-iframe", this._el.content);
+
+ // Get Media ID
+ if (this.data.url.match("open\?id\=")) {
+ this.media_id = this.data.url.split("open\?id\=")[1];
+ if (this.data.url.match("\&authuser\=0")) {
+ url = this.media_id.match("\&authuser\=0")[0];
+ };
+ } else if (this.data.url.match(/file\/d\/([^/]*)\/?/)) {
+ var doc_id = this.data.url.match(/file\/d\/([^/]*)\/?/)[1];
+ url = 'https://drive.google.com/file/d/' + doc_id + '/preview'
+ } else {
+ url = this.data.url;
+ }
+
+ // this URL makes something suitable for an img src but what if it's not an image?
+ // api_url = "http://www.googledrive.com/host/" + this.media_id + "/";
+
+ this._el.content_item.innerHTML = "<iframe class='doc' frameborder='0' width='100%' height='100%' src='" + url + "'></iframe>";
+
+ // After Loaded
+ this.onLoaded();
+ },
+
+ // Update Media Display
+ _updateMediaDisplay: function() {
+ this._el.content_item.style.height = this.options.height + "px";
+ }
+
+
+});
+
+
+/* **********************************************
+ Begin TL.Media.GooglePlus.js
+********************************************** */
+
+/* TL.Media.GooglePlus
+================================================== */
+
+TL.Media.GooglePlus = TL.Media.extend({
+
+ includes: [TL.Events],
+
+ /* Load the media
+ ================================================== */
+ _loadMedia: function() {
+ var api_url,
+ self = this;
+
+ // Create Dom element
+ this._el.content_item = TL.Dom.create("div", "tl-media-item tl-media-googleplus", this._el.content);
+
+ // Get Media ID
+ this.media_id = this.data.url;
+
+ // API URL
+ api_url = this.media_id;
+
+ // API Call
+ this._el.content_item.innerHTML = "<iframe frameborder='0' width='100%' height='100%' src='" + api_url + "'></iframe>"
+
+ // After Loaded
+ this.onLoaded();
+ },
+
+ // Update Media Display
+ _updateMediaDisplay: function() {
+ this._el.content_item.style.height = this.options.height + "px";
+ }
+
+
+});
+
+
+/* **********************************************
+ Begin TL.Media.IFrame.js
+********************************************** */
+
+/* TL.Media.IFrame
+================================================== */
+
+TL.Media.IFrame = TL.Media.extend({
+
+ includes: [TL.Events],
+
+ /* Load the media
+ ================================================== */
+ _loadMedia: function() {
+ var api_url,
+ self = this;
+
+ // Create Dom element
+ this._el.content_item = TL.Dom.create("div", "tl-media-item tl-media-iframe", this._el.content);
+
+ // Get Media ID
+ this.media_id = this.data.url;
+
+ // API URL
+ api_url = this.media_id;
+
+ // API Call
+ this._el.content_item.innerHTML = api_url;
+
+ // After Loaded
+ this.onLoaded();
+ },
+
+ // Update Media Display
+ _updateMediaDisplay: function() {
+ this._el.content_item.style.height = this.options.height + "px";
+ }
+
+});
+
+
+/* **********************************************
+ Begin TL.Media.Image.js
+********************************************** */
+
+/* TL.Media.Image
+ Produces image assets.
+ Takes a data object and populates a dom object
+================================================== */
+
+TL.Media.Image = TL.Media.extend({
+
+ includes: [TL.Events],
+
+ /* Load the media
+ ================================================== */
+ _loadMedia: function() {
+ // Loading Message
+ this.loadingMessage();
+
+ // Create media?
+ if(!this.options.background) {
+ this.createMedia();
+ }
+
+ // After loaded
+ this.onLoaded();
+ },
+
+ createMedia: function() {
+ var self = this,
+ image_class = "tl-media-item tl-media-image tl-media-shadow";
+
+ if (this.data.url.match(/.png(\?.*)?$/) || this.data.url.match(/.svg(\?.*)?$/)) {
+ image_class = "tl-media-item tl-media-image"
+ }
+
+ // Link
+ if (this.data.link) {
+ this._el.content_link = TL.Dom.create("a", "", this._el.content);
+ this._el.content_link.href = this.data.link;
+ this._el.content_link.target = "_blank";
+ this._el.content_item = TL.Dom.create("img", image_class, this._el.content_link);
+ } else {
+ this._el.content_item = TL.Dom.create("img", image_class, this._el.content);
+ }
+
+ if (this.data.alt) {
+ this._el.content_item.alt = this.data.alt;
+ } else if (this.data.caption) {
+ this._el.content_item.alt = TL.Util.unhtmlify(this.data.caption);
+ }
+
+ if (this.data.title) {
+ this._el.content_item.title = this.data.title;
+ } else if (this.data.caption) {
+ this._el.content_item.title = TL.Util.unhtmlify(this.data.caption);
+ }
+
+ // Media Loaded Event
+ this._el.content_item.addEventListener('load', function(e) {
+ self.onMediaLoaded();
+ });
+
+ this._el.content_item.src = this.getImageURL();
+ },
+
+ getImageURL: function(w, h) {
+ return TL.Util.transformImageURL(this.data.url);
+ },
+
+ _updateMediaDisplay: function(layout) {
+ if(TL.Browser.firefox) {
+ //this._el.content_item.style.maxWidth = (this.options.width/2) - 40 + "px";
+ this._el.content_item.style.width = "auto";
+ }
+ }
+
+});
+
+
+/* **********************************************
+ Begin TL.Media.Imgur.js
+********************************************** */
+
+/* TL.Media.Flickr
+
+================================================== */
+
+TL.Media.Imgur = TL.Media.extend({
+
+ includes: [TL.Events],
+
+ /* Load the media
+ ================================================== */
+ _loadMedia: function() {
+ try {
+ var self = this;
+
+ if (this.data.url.match("<blockquote class=['\"]imgur-embed-pub['\"]")){
+ var found = this.data.url.match(/(imgur\.com)\/(\w+)/);
+ this.media_id = found[2];
+ this.data.url = "http://imgur.com/gallery/" + this.media_id;
+ }
+ else if (this.data.url){
+ this.media_id = this.data.url.split('/').slice(-1)[0];
+ }
+
+ TL.Load.js([
+ 'https://s.imgur.com/min/embed.js'],
+ function(){
+ self.createMedia();
+ }
+ );
+
+ } catch(e) {
+ this.loadErrorDisplay(this._("imgur_invalidurl_err"));
+ }
+ },
+
+ createMedia: function() {
+ var self = this;
+ var api_url = "https://api.imgur.com/oembed.json?url=" + this.data.url;
+
+ // Content div
+ this._el.content_item = TL.Dom.create("div", "tl-media-item tl-media-image tl-media-imgur",
+ this._el.content);
+
+ // API Call
+
+ TL.ajax({
+ type: 'GET',
+ url: api_url,
+ dataType: 'json',
+ success: function(data){
+ try {
+ self._el.content_item.innerHTML = data.html;
+ setInterval(function(){
+ if(document.querySelector("blockquote.imgur-embed-pub") == null){
+ clearInterval();
+ }
+ else{
+ imgurEmbed.createIframe();
+ document.getElementById("imageElement").removeAttribute("style");
+ document.getElementById("image").removeAttribute("style");
+ }
+ }, 2000);
+ } catch(e) {
+ }
+ },
+ error: function(xhr, errorType, error) {
+ tc = new TL.TimelineConfig();
+ if (errorType == 'parsererror') {
+ var error = new TL.Error("invalid_url_err");
+ } else {
+ var error = new TL.Error("unknown_read_err", errorType);
+ }
+ self.loadErrorDisplay(self._("imgur_invalidurl_err"));
+ tc.logError(error);
+ }
+ });
+
+ this.onLoaded();
+
+ },
+
+
+
+ _updateMediaDisplay: function() {
+ //this.el.content_item = document.getElementById(this._el.content_item.id);
+ this._el.content_item.style.width = this.options.width + "px";
+ this._el.content_item.style.height = TL.Util.ratio.r16_9({w:this.options.width}) + "px";
+ }
+
+});
+
+
+/* **********************************************
+ Begin TL.Media.Instagram.js
+********************************************** */
+
+/* TL.Media.Instagram
+
+================================================== */
+
+TL.Media.Instagram = TL.Media.extend({
+
+ includes: [TL.Events],
+
+ /* Load the media
+ ================================================== */
+ _loadMedia: function() {
+ // Get Media ID
+ this.media_id = this.data.url.split("\/p\/")[1].split("/")[0];
+
+ if(!this.options.background) {
+ this.createMedia();
+ }
+
+ // After Loaded
+ this.onLoaded();
+ },
+
+ createMedia: function() {
+ var self = this;
+
+ // Link
+ this._el.content_link = TL.Dom.create("a", "", this._el.content);
+ this._el.content_link.href = this.data.url;
+ this._el.content_link.target = "_blank";
+
+ // Photo
+ this._el.content_item = TL.Dom.create("img", "tl-media-item tl-media-image tl-media-instagram tl-media-shadow", this._el.content_link);
+
+ if (this.data.alt) {
+ this._el.content_item.alt = this.data.alt;
+ } else if (this.data.caption) {
+ this._el.content_item.alt = TL.Util.unhtmlify(this.data.caption);
+ }
+
+ if (this.data.title) {
+ this._el.content_item.title = this.data.title;
+ } else if (this.data.caption) {
+ this._el.content_item.title = TL.Util.unhtmlify(this.data.caption);
+ }
+
+ // Media Loaded Event
+ this._el.content_item.addEventListener('load', function(e) {
+ self.onMediaLoaded();
+ });
+
+ this._el.content_item.src = this.getImageURL(this._el.content.offsetWidth);
+ },
+
+ getImageURL: function(w, h) {
+ return "https://instagram.com/p/" + this.media_id + "/media/?size=" + this.sizes(w);
+ },
+
+ _getMeta: function() {
+ var self = this,
+ api_url;
+
+ // API URL
+ api_url = "https://api.instagram.com/oembed?url=https://instagr.am/p/" + this.media_id + "&callback=?";
+
+ // API Call
+ TL.getJSON(api_url, function(d) {
+ self.data.credit_alternate = "<a href='" + d.author_url + "' target='_blank'>" + d.author_name + "</a>";
+ self.data.caption_alternate = d.title;
+ self.updateMeta();
+ });
+ },
+
+ sizes: function(s) {
+ var _size = "";
+ if (s <= 150) {
+ _size = "t";
+ } else if (s <= 306) {
+ _size = "m";
+ } else {
+ _size = "l";
+ }
+
+ return _size;
+ }
+
+
+
+});
+
+
+/* **********************************************
+ Begin TL.Media.GoogleMap.js
+********************************************** */
+
+/* TL.Media.Map
+================================================== */
+
+TL.Media.GoogleMap = TL.Media.extend({
+ includes: [TL.Events],
+
+ /* Load the media
+ ================================================== */
+ _loadMedia: function() {
+
+ // Create Dom element
+ this._el.content_item = TL.Dom.create("div", "tl-media-item tl-media-map tl-media-shadow", this._el.content);
+
+ // Get Media ID
+ this.media_id = this.data.url;
+
+ // API Call
+ this.mapframe = TL.Dom.create("iframe", "", this._el.content_item);
+ window.stash = this;
+ this.mapframe.width = "100%";
+ this.mapframe.height = "100%";
+ this.mapframe.frameBorder = "0";
+ this.mapframe.src = this.makeGoogleMapsEmbedURL(this.media_id, this.options.api_key_googlemaps);
+
+
+ // After Loaded
+ this.onLoaded();
+ },
+
+ _updateMediaDisplay: function() {
+ if (this._state.loaded) {
+ var dimensions = TL.Util.ratio.square({w:this._el.content_item.offsetWidth});
+ this._el.content_item.style.height = dimensions.h + "px";
+ }
+ },
+
+ makeGoogleMapsEmbedURL: function(url,api_key) {
+ // Test with https://docs.google.com/spreadsheets/d/1zCpvtRdftlR5fBPppmy_-SkGIo7RMwoPUiGFZDAXbTc/edit
+ var Streetview = false;
+
+ function determineMapMode(url){
+ function parseDisplayMode(display_mode, param_string) {
+ // Set the zoom param
+ if (display_mode.slice(-1) == "z") {
+ param_string["zoom"] = display_mode;
+ // Set the maptype to something other than "roadmap"
+ } else if (display_mode.slice(-1) == "m") {
+ // TODO: make this somehow interpret the correct zoom level
+ // until then fake it by using Google's default zoom level
+ param_string["zoom"] = 14;
+ param_string["maptype"] = "satellite";
+ // Set all the fun streetview params
+ } else if (display_mode.slice(-1) == "t") {
+ Streetview = true;
+ // streetview uses "location" instead of "center"
+ // "place" mode doesn't have the center param, so we may need to grab that now
+ if (mapmode == "place") {
+ var center = url.match(regexes["place"])[3] + "," + url.match(regexes["place"])[4];
+ } else {
+ var center = param_string["center"];
+ delete param_string["center"];
+ }
+ // Clear out all the other params -- this is so hacky
+ param_string = {};
+ param_string["location"] = center;
+ streetview_params = display_mode.split(",");
+ for (param in param_defs["streetview"]) {
+ var i = parseInt(param) + 1;
+ if (param_defs["streetview"][param] == "pitch" && streetview_params[i] == "90t"){
+ // Although 90deg is the horizontal default in the URL, 0 is horizontal default for embed URL. WHY??
+ // https://developers.google.com/maps/documentation/javascript/streetview
+ param_string[param_defs["streetview"][param]] = 0;
+ } else {
+ param_string[param_defs["streetview"][param]] = streetview_params[i].slice(0,-1);
+ }
+ }
+
+ }
+ return param_string;
+ }
+ function determineMapModeURL(mapmode, match) {
+ var param_string = {};
+ var url_root = match[1], display_mode = match[match.length - 1];
+ for (param in param_defs[mapmode]) {
+ // skip first 2 matches, because they reflect the URL and not params
+ var i = parseInt(param)+2;
+ if (param_defs[mapmode][param] == "center") {
+ param_string[param_defs[mapmode][param]] = match[i] + "," + match[++i];
+ } else {
+ param_string[param_defs[mapmode][param]] = match[i];
+ }
+ }
+
+ param_string = parseDisplayMode(display_mode, param_string);
+ param_string["key"] = api_key;
+ if (Streetview == true) {
+ mapmode = "streetview";
+ } else {
+ }
+ return (url_root + "/embed/v1/" + mapmode + TL.Util.getParamString(param_string));
+ }
+
+
+ mapmode = "view";
+ if (url.match(regexes["place"])) {
+ mapmode = "place";
+ } else if (url.match(regexes["directions"])) {
+ mapmode = "directions";
+ } else if (url.match(regexes["search"])) {
+ mapmode = "search";
+ }
+ return determineMapModeURL(mapmode, url.match(regexes[mapmode]));
+
+ }
+
+ // These must be in the order they appear in the original URL
+ // "key" param not included since it's not in the URL structure
+ // Streetview "location" param not included since it's captured as "center"
+ // Place "center" param ...um...
+ var param_defs = {
+ "view": ["center"],
+ "place": ["q", "center"],
+ "directions": ["origin", "destination", "center"],
+ "search": ["q", "center"],
+ "streetview": ["fov", "heading", "pitch"]
+ };
+ // Set up regex parts to make updating these easier if Google changes them
+ var root_url_regex = /(https:\/\/.+google.+?\/maps)/;
+ var coords_regex = /@([-\d.]+),([-\d.]+)/;
+ var address_regex = /([\w\W]+)/;
+
+ // Data doesn't seem to get used for anything
+ var data_regex = /data=[\S]*/;
+
+ // Capture the parameters that determine what map tiles to use
+ // In roadmap view, mode URLs include zoom paramater (e.g. "14z")
+ // In satellite (or "earth") view, URLs include a distance parameter (e.g. "84511m")
+ // In streetview, URLs include paramaters like "3a,75y,49.76h,90t" -- see http://stackoverflow.com/a/22988073
+ var display_mode_regex = /,((?:[-\d.]+[zmayht],?)*)/;
+
+ var regexes = {
+ view: new RegExp(root_url_regex.source + "/" + coords_regex.source + display_mode_regex.source),
+ place: new RegExp(root_url_regex.source + "/place/" + address_regex.source + "/" + coords_regex.source + display_mode_regex.source),
+ directions: new RegExp(root_url_regex.source + "/dir/" + address_regex.source + "/" + address_regex.source + "/" + coords_regex.source + display_mode_regex.source),
+ search: new RegExp(root_url_regex.source + "/search/" + address_regex.source + "/" + coords_regex.source + display_mode_regex.source)
+ };
+ return determineMapMode(url);
+ }
+
+});
+
+
+/* **********************************************
+ Begin TL.Media.PDF.js
+********************************************** */
+
+/* TL.Media.PDF
+ * Chrome and Firefox on both OSes and Safari all support PDFs as iframe src.
+ * This prompts for a download on IE10/11. We should investigate using
+ * https://mozilla.github.io/pdf.js/ to support showing PDFs on IE.
+================================================== */
+
+TL.Media.PDF = TL.Media.extend({
+
+ includes: [TL.Events],
+
+ /* Load the media
+ ================================================== */
+ _loadMedia: function() {
+ var url = TL.Util.transformImageURL(this.data.url),
+ self = this;
+
+ // Create Dom element
+ this._el.content_item = TL.Dom.create("div", "tl-media-item tl-media-iframe", this._el.content);
+ var markup = "";
+ // not assigning media_id attribute. Seems like a holdover which is no longer used.
+ if (TL.Browser.ie || TL.Browser.edge || url.match(/dl.dropboxusercontent.com/)) {
+ markup = "<iframe class='doc' frameborder='0' width='100%' height='100%' src='//docs.google.com/viewer?url=" + url + "&amp;embedded=true'></iframe>";
+ } else {
+ markup = "<iframe class='doc' frameborder='0' width='100%' height='100%' src='" + url + "'></iframe>"
+ }
+ this._el.content_item.innerHTML = markup
+ this.onLoaded();
+ },
+
+ // Update Media Display
+ _updateMediaDisplay: function() {
+ this._el.content_item.style.height = this.options.height + "px";
+ }
+
+
+});
+
+
+/* **********************************************
+ Begin TL.Media.Profile.js
+********************************************** */
+
+/* TL.Media.Profile
+
+================================================== */
+
+TL.Media.Profile = TL.Media.extend({
+
+ includes: [TL.Events],
+
+ /* Load the media
+ ================================================== */
+ _loadMedia: function() {
+
+ this._el.content_item = TL.Dom.create("img", "tl-media-item tl-media-image tl-media-profile tl-media-shadow", this._el.content);
+ this._el.content_item.src = this.data.url;
+
+ this.onLoaded();
+ },
+
+ _updateMediaDisplay: function(layout) {
+
+
+ if(TL.Browser.firefox) {
+ this._el.content_item.style.maxWidth = (this.options.width/2) - 40 + "px";
+ }
+ }
+
+});
+
+/* **********************************************
+ Begin TL.Media.Slider.js
+********************************************** */
+
+/* TL.Media.SLider
+ Produces a Slider
+ Takes a data object and populates a dom object
+ TODO
+ Placeholder
+================================================== */
+
+TL.Media.Slider = TL.Media.extend({
+
+ includes: [TL.Events],
+
+ /* Load the media
+ ================================================== */
+ _loadMedia: function() {
+
+ this._el.content_item = TL.Dom.create("img", "tl-media-item tl-media-image", this._el.content);
+ this._el.content_item.src = this.data.url;
+
+ this.onLoaded();
+ }
+
+});
+
+/* **********************************************
+ Begin TL.Media.SoundCloud.js
+********************************************** */
+
+/* TL.Media.SoundCloud
+================================================== */
+
+var soundCoudCreated = false;
+
+TL.Media.SoundCloud = TL.Media.extend({
+
+ includes: [TL.Events],
+
+ /* Load the media
+ ================================================== */
+ _loadMedia: function() {
+ var api_url,
+ self = this;
+
+ // Create Dom element
+ this._el.content_item = TL.Dom.create("div", "tl-media-item tl-media-iframe tl-media-soundcloud tl-media-shadow", this._el.content);
+
+ // Get Media ID
+ this.media_id = this.data.url;
+
+ // API URL
+ api_url = "https://soundcloud.com/oembed?url=" + this.media_id + "&format=js&callback=?"
+
+ // API Call
+ TL.getJSON(api_url, function(d) {
+ TL.Load.js("https://w.soundcloud.com/player/api.js", function() {//load soundcloud api for pausing.
+ self.createMedia(d);
+ });
+ });
+
+ },
+
+ createMedia: function(d) {
+ this._el.content_item.innerHTML = d.html;
+
+ this.soundCloudCreated = true;
+
+ self.widget = SC.Widget(this._el.content_item.querySelector("iframe"));//create widget for api use
+
+ // After Loaded
+ this.onLoaded();
+ },
+
+ _stopMedia: function() {
+ if (this.soundCloudCreated)
+ {
+ self.widget.pause();
+ }
+ }
+
+});
+
+
+/* **********************************************
+ Begin TL.Media.Spotify.js
+********************************************** */
+
+/* TL.Media.Spotify
+================================================== */
+
+TL.Media.Spotify = TL.Media.extend({
+
+ includes: [TL.Events],
+
+ /* Load the media
+ ================================================== */
+ _loadMedia: function() {
+ var api_url,
+ self = this;
+
+ // Create Dom element
+ this._el.content_item = TL.Dom.create("div", "tl-media-item tl-media-iframe tl-media-spotify", this._el.content);
+
+ // Get Media ID
+ if (this.data.url.match(/^spotify:track/) || this.data.url.match(/^spotify:album/) || this.data.url.match(/^spotify:user:.+:playlist:/)) {
+ this.media_id = this.data.url;
+ }
+
+ if (this.data.url.match(/spotify\.com\/track\/(.+)/)) {
+ this.media_id = "spotify:track:" + this.data.url.match(/spotify\.com\/track\/(.+)/)[1];
+ } else if (this.data.url.match(/spotify\.com\/album\/(.+)/)) {
+ this.media_id = "spotify:album:" + this.data.url.match(/spotify\.com\/album\/(.+)/)[1];
+ } else if (this.data.url.match(/spotify\.com\/user\/(.+?)\/playlist\/(.+)/)) {
+ var user = this.data.url.match(/spotify\.com\/user\/(.+?)\/playlist\/(.+)/)[1];
+ var playlist = this.data.url.match(/spotify\.com\/user\/(.+?)\/playlist\/(.+)/)[2];
+ this.media_id = "spotify:user:" + user + ":playlist:" + playlist;
+ } else if (this.data.url.match(/spotify\.com\/artist\/(.+)/)) {
+ var artist = this.data.url.match(/spotify\.com\/artist\/(.+)/)[1];
+ this.media_id = "spotify:artist:" + artist;
+ }
+
+
+ if (this.media_id) {
+ // API URL
+ api_url = "https://embed.spotify.com/?uri=" + this.media_id + "&theme=white&view=coverart";
+
+ this.player = TL.Dom.create("iframe", "tl-media-shadow", this._el.content_item);
+ this.player.width = "100%";
+ this.player.height = "100%";
+ this.player.frameBorder = "0";
+ this.player.src = api_url;
+
+ // After Loaded
+ this.onLoaded();
+
+ } else {
+ this.loadErrorDisplay(this._('spotify_invalid_url'));
+ }
+ },
+
+ // Update Media Display
+
+ _updateMediaDisplay: function(l) {
+ var _height = this.options.height,
+ _player_height = 0,
+ _player_width = 0;
+
+ if (TL.Browser.mobile) {
+ _height = (this.options.height/2);
+ } else {
+ _height = this.options.height - this.options.credit_height - this.options.caption_height - 30;
+ }
+
+ this._el.content_item.style.maxHeight = "none";
+ trace(_height);
+ trace(this.options.width)
+ if (_height > this.options.width) {
+ trace("height is greater")
+ _player_height = this.options.width + 80 + "px";
+ _player_width = this.options.width + "px";
+ } else {
+ trace("width is greater")
+ trace(this.options.width)
+ _player_height = _height + "px";
+ _player_width = _height - 80 + "px";
+ }
+
+
+ this.player.style.width = _player_width;
+ this.player.style.height = _player_height;
+
+ if (this._el.credit) {
+ this._el.credit.style.width = _player_width;
+ }
+ if (this._el.caption) {
+ this._el.caption.style.width = _player_width;
+ }
+ },
+
+
+ _stopMedia: function() {
+ // Need spotify stop code
+
+ }
+
+});
+
+
+/* **********************************************
+ Begin TL.Media.Storify.js
+********************************************** */
+
+/* TL.Media.Storify
+================================================== */
+
+TL.Media.Storify = TL.Media.extend({
+
+ includes: [TL.Events],
+
+ /* Load the media
+ ================================================== */
+ _loadMedia: function() {
+ var content;
+
+ // Create Dom element
+ this._el.content_item = TL.Dom.create("div", "tl-media-item tl-media-iframe tl-media-storify", this._el.content);
+
+ // Get Media ID
+ this.media_id = this.data.url;
+
+ // Content
+ content = "<iframe frameborder='0' width='100%' height='100%' src='" + this.media_id + "/embed'></iframe>";
+ content += "<script src='" + this.media_id + ".js'></script>";
+
+ // API Call
+ this._el.content_item.innerHTML = content;
+
+ // After Loaded
+ this.onLoaded();
+ },
+
+ // Update Media Display
+ _updateMediaDisplay: function() {
+ this._el.content_item.style.height = this.options.height + "px";
+ }
+
+
+});
+
+
+/* **********************************************
+ Begin TL.Media.Text.js
+********************************************** */
+
+TL.Media.Text = TL.Class.extend({
+
+ includes: [TL.Events],
+
+ // DOM ELEMENTS
+ _el: {
+ container: {},
+ content_container: {},
+ content: {},
+ headline: {},
+ date: {}
+ },
+
+ // Data
+ data: {
+ unique_id: "",
+ headline: "headline",
+ text: "text"
+ },
+
+ // Options
+ options: {
+ title: false
+ },
+
+ /* Constructor
+ ================================================== */
+ initialize: function(data, options, add_to_container) {
+
+ TL.Util.setData(this, data);
+
+ // Merge Options
+ TL.Util.mergeData(this.options, options);
+
+ this._el.container = TL.Dom.create("div", "tl-text");
+ this._el.container.id = this.data.unique_id;
+
+ this._initLayout();
+
+ if (add_to_container) {
+ add_to_container.appendChild(this._el.container);
+ };
+
+ },
+
+ /* Adding, Hiding, Showing etc
+ ================================================== */
+ show: function() {
+
+ },
+
+ hide: function() {
+
+ },
+
+ addTo: function(container) {
+ container.appendChild(this._el.container);
+ //this.onAdd();
+ },
+
+ removeFrom: function(container) {
+ container.removeChild(this._el.container);
+ },
+
+ headlineHeight: function() {
+ return this._el.headline.offsetHeight + 40;
+ },
+
+ addDateText: function(str) {
+ this._el.date.innerHTML = str;
+ },
+
+ /* Events
+ ================================================== */
+ onLoaded: function() {
+ this.fire("loaded", this.data);
+ },
+
+ onAdd: function() {
+ this.fire("added", this.data);
+ },
+
+ onRemove: function() {
+ this.fire("removed", this.data);
+ },
+
+ /* Private Methods
+ ================================================== */
+ _initLayout: function () {
+
+ // Create Layout
+ this._el.content_container = TL.Dom.create("div", "tl-text-content-container", this._el.container);
+
+ // Date
+ this._el.date = TL.Dom.create("h3", "tl-headline-date", this._el.content_container);
+
+ // Headline
+ if (this.data.headline != "") {
+ var headline_class = "tl-headline";
+ if (this.options.title) {
+ headline_class = "tl-headline tl-headline-title";
+ }
+ this._el.headline = TL.Dom.create("h2", headline_class, this._el.content_container);
+ this._el.headline.innerHTML = this.data.headline;
+ }
+
+ // Text
+ if (this.data.text != "") {
+ var text_content = "";
+
+ text_content += TL.Util.htmlify(this.options.autolink == true ? TL.Util.linkify(this.data.text) : this.data.text);
+ trace(this.data.text);
+ this._el.content = TL.Dom.create("div", "tl-text-content", this._el.content_container);
+ this._el.content.innerHTML = text_content;
+ trace(text_content);
+ trace(this._el.content)
+ }
+
+ // Fire event that the slide is loaded
+ this.onLoaded();
+
+ }
+
+});
+
+
+/* **********************************************
+ Begin TL.Media.Twitter.js
+********************************************** */
+
+/* TL.Media.Twitter
+ Produces Twitter Display
+================================================== */
+
+TL.Media.Twitter = TL.Media.extend({
+
+ includes: [TL.Events],
+
+
+
+ /* Load the media
+ ================================================== */
+
+ _loadMedia: function() {
+ var api_url,
+ self = this;
+
+ // Create Dom element
+ this._el.content_item = TL.Dom.create("div", "tl-media-twitter", this._el.content);
+ this._el.content_container.className = "tl-media-content-container tl-media-content-container-text";
+
+ // Get Media ID
+ if(this.data.url.match("^(https?:)?\/*(www.)?twitter\.com"))
+ {
+ if (this.data.url.match("status\/")) {
+ this.media_id = this.data.url.split("status\/")[1];
+ } else if (this.data.url.match("statuses\/")) {
+ this.media_id = this.data.url.split("statuses\/")[1];
+ } else {
+ this.media_id = "";
+ }
+ }
+
+ else if(this.data.url.match("<blockquote class=['\"]twitter-tweet['\"]")) {
+
+ var found = this.data.url.match(/(status|statuses)\/(\d+)/);
+ if (found && found.length > 2) {
+ this.media_id = found[2];
+ } else {
+ self.loadErrorDisplay(self._("twitterembed_invalidurl_err"));
+ return;
+ }
+ }
+
+ // API URL
+ api_url = "https://api.twitter.com/1/statuses/oembed.json?id=" + this.media_id + "&omit_script=true&include_entities=true&callback=?";
+
+ // API Call
+ TL.ajax({
+ type: 'GET',
+ url: api_url,
+ dataType: 'json', //json data type
+ success: function(d){
+ self.createMedia(d);
+ },
+ error:function(xhr, type){
+ var error_text = "";
+ error_text += self._("twitter_load_err") + "<br/>" + self.media_id + "<br/>" + type;
+ self.loadErrorDisplay(error_text);
+ }
+ });
+
+ },
+
+ createMedia: function(d) {
+ trace("create_media")
+ var tweet = "",
+ tweet_text = "",
+ tweetuser = "",
+ tweet_status_temp = "",
+ tweet_status_url = "",
+ tweet_status_date = "",
+ self = this;
+
+ // TWEET CONTENT
+ tweet_text = d.html.split("<\/p>\&mdash;")[0] + "</p></blockquote>";
+ tweetuser = d.author_url.split("twitter.com\/")[1];
+ tweet_status_temp = d.html.split("<\/p>\&mdash;")[1].split("<a href=\"")[1];
+ tweet_status_url = tweet_status_temp.split("\"\>")[0];
+ tweet_status_date = tweet_status_temp.split("\"\>")[1].split("<\/a>")[0];
+
+ // Open links in new window
+ tweet_text = tweet_text.replace(/<a href/ig, '<a target="_blank" href');
+
+ if (tweet_text.includes("pic.twitter.com")) {
+
+ TL.Load.js('https://platform.twitter.com/widgets.js', function() {
+ twttr.widgets.createTweet(self.media_id, self._el.content_item,
+ {
+ conversation : 'none', // or all
+ linkColor : '#cc0000', // default is blue
+ theme : 'light' // or dark
+ })
+ });
+
+ this.onLoaded();
+
+ } else {
+
+ // TWEET CONTENT
+ tweet += tweet_text;
+
+ // TWEET AUTHOR
+ tweet += "<div class='vcard'>";
+ tweet += "<a href='" + tweet_status_url + "' class='twitter-date' target='_blank'>" + tweet_status_date + "</a>";
+ tweet += "<img src='" + "' class='tl-media-item tl-media-image' target='_blank'>" + "</a>";
+ tweet += "<div class='author'>";
+ tweet += "<a class='screen-name url' href='" + d.author_url + "' target='_blank'>";
+ tweet += "<span class='avatar'></span>";
+ tweet += "<span class='fn'>" + d.author_name + " <span class='tl-icon-twitter'></span></span>";
+ tweet += "<span class='nickname'>@" + tweetuser + "<span class='thumbnail-inline'></span></span>";
+ tweet += "</a>";
+ tweet += "</div>";
+ tweet += "</div>";
+
+
+ // Add to DOM
+ this._el.content_item.innerHTML = tweet;
+
+ // After Loaded
+ this.onLoaded();
+ }
+ },
+
+
+ updateMediaDisplay: function() {
+
+ },
+
+ _updateMediaDisplay: function() {
+
+ },
+
+
+
+
+});
+
+
+/* **********************************************
+ Begin TL.Media.TwitterEmbed.js
+********************************************** */
+
+/* TL.Media.TwitterEmbed
+ Produces Twitter Display
+================================================== */
+
+var mediaID;
+
+TL.Media.TwitterEmbed = TL.Media.extend({
+ includes: [TL.Events],
+
+
+
+ /* Load the media
+ ================================================== */
+ _loadMedia: function() {
+ var api_url,
+ self = this;
+
+ // Create Dom element
+ this._el.content_item = TL.Dom.create("div", "tl-media-twitter", this._el.content);
+ this._el.content_container.className = "tl-media-content-container tl-media-content-container-text";
+
+ // Get Media ID
+ var found = this.data.url.match(/(status|statuses)\/(\d+)/);
+ if (found && found.length > 2) {
+ this.media_id = found[2];
+ } else {
+ self.loadErrorDisplay(self._("twitterembed_invalidurl_err"));
+ return;
+ }
+
+ // API URL
+ api_url = "https://api.twitter.com/1/statuses/oembed.json?id=" + this.media_id + "&omit_script=true&include_entities=true&callback=?";
+
+ window.twttr = (function(d, s, id) {
+ var js, fjs = d.getElementsByTagName(s)[0],
+ t = window.twttr || {};
+ if (d.getElementById(id)) return t;
+ js = d.createElement(s);
+ js.id = id;
+ js.src = "https://platform.twitter.com/widgets.js";
+ fjs.parentNode.insertBefore(js, fjs);
+
+ t._e = [];
+ t.ready = function(f) {
+ t._e.push(f);
+ };
+
+ return t;
+ }(document, "script", "twitter-wjs"));
+
+ mediaID = this.media_id;
+
+ // API Call
+ TL.ajax({
+ type: 'GET',
+ url: api_url,
+ dataType: 'json', //json data type
+ success: function(d){
+ self.createMedia(d);
+ },
+ error:function(xhr, type){
+ var error_text = "";
+ error_text += self._("twitter_load_err") + "<br/>" + self.media_id + "<br/>" + type;
+ self.loadErrorDisplay(error_text);
+ }
+ });
+
+ },
+
+ createMedia: function(d) {
+ trace("create_media")
+ var tweet = "",
+ tweet_text = "",
+ tweetuser = "",
+ tweet_status_temp = "",
+ tweet_status_url = "",
+ tweet_status_date = "";
+
+ // TWEET CONTENT
+ tweet_text = d.html.split("<\/p>\&mdash;")[0] + "</p></blockquote>";
+ console.log(tweet_text);
+ tweetuser = d.author_url.split("twitter.com\/")[1];
+ tweet_status_temp = d.html.split("<\/p>\&mdash;")[1].split("<a href=\"")[1];
+ tweet_status_url = tweet_status_temp.split("\"\>")[0];
+ tweet_status_date = tweet_status_temp.split("\"\>")[1].split("<\/a>")[0];
+
+ // Open links in new window
+ tweet_text = tweet_text.replace(/<a href/ig, '<a target="_blank" href');
+
+ if (tweet_text.includes("pic.twitter.com")) {
+ twttr.ready(
+ function(evt) {
+ tweet = document.getElementsByClassName("tl-media-twitter")[0];
+ var id = String(mediaID);
+ twttr.widgets.createTweet(id, tweet,
+ {
+ conversation : 'none', // or all
+ linkColor : '#cc0000', // default is blue
+ theme : 'light' // or dark
+ })
+ .then(function (evt) {
+ this.onLoaded();
+ });
+ }
+ );
+ this._el.content_item.innerHTML = tweet;
+ this.onLoaded();
+ } else{
+ // TWEET CONTENT
+ tweet += tweet_text;
+
+ // TWEET AUTHOR
+ tweet += "<div class='vcard'>";
+ tweet += "<a href='" + tweet_status_url + "' class='twitter-date' target='_blank'>" + tweet_status_date + "</a>";
+ tweet += "<div class='author'>";
+ tweet += "<a class='screen-name url' href='" + d.author_url + "' target='_blank'>";
+ tweet += "<span class='avatar'></span>";
+ tweet += "<span class='fn'>" + d.author_name + " <span class='tl-icon-twitter'></span></span>";
+ tweet += "<span class='nickname'>@" + tweetuser + "<span class='thumbnail-inline'></span></span>";
+ tweet += "</a>";
+ tweet += "</div>";
+ tweet += "</div>";
+
+
+ // Add to DOM
+ this._el.content_item.innerHTML = tweet;
+
+ // After Loaded
+ this.onLoaded();
+ }
+
+ },
+
+ updateMediaDisplay: function() {
+
+ },
+
+ _updateMediaDisplay: function() {
+
+ }
+
+
+
+});
+
+/* **********************************************
+ Begin TL.Media.Vimeo.js
+********************************************** */
+
+/* TL.Media.Vimeo
+================================================== */
+
+TL.Media.Vimeo = TL.Media.extend({
+
+ includes: [TL.Events],
+
+ /* Load the media
+ ================================================== */
+ _loadMedia: function() {
+ var api_url,
+ self = this;
+
+ // Create Dom element
+ this._el.content_item = TL.Dom.create("div", "tl-media-item tl-media-iframe tl-media-vimeo tl-media-shadow", this._el.content);
+
+ // Get Media ID
+ this.media_id = this.data.url.split(/video\/|\/\/vimeo\.com\//)[1].split(/[?&]/)[0];
+ var start_time = null;
+
+ // Get start time
+ if (this.data.url.match(/#t=([^&]+).*/)) {
+ start_time = this.data.url.match(/#t=([^&]+).*/)[1];
+ }
+
+ // API URL
+ api_url = "https://player.vimeo.com/video/" + this.media_id + "?api=1&title=0&amp;byline=0&amp;portrait=0&amp;color=ffffff";
+ if (start_time) {
+ api_url = api_url += '&amp;#t=' + start_time;
+ }
+
+ this.player = TL.Dom.create("iframe", "", this._el.content_item);
+
+ // Media Loaded Event
+ this.player.addEventListener('load', function(e) {
+ self.onMediaLoaded();
+ });
+
+ this.player.width = "100%";
+ this.player.height = "100%";
+ this.player.frameBorder = "0";
+ this.player.src = api_url;
+
+ this.player.setAttribute('allowfullscreen', '');
+ this.player.setAttribute('webkitallowfullscreen', '');
+ this.player.setAttribute('mozallowfullscreen', '');
+
+ // After Loaded
+ this.onLoaded();
+ },
+
+ // Update Media Display
+ _updateMediaDisplay: function() {
+ this._el.content_item.style.height = TL.Util.ratio.r16_9({w:this._el.content_item.offsetWidth}) + "px";
+ },
+
+ _stopMedia: function() {
+
+ try {
+ this.player.contentWindow.postMessage(JSON.stringify({method: "pause"}), "https://player.vimeo.com");
+ }
+ catch(err) {
+ trace(err);
+ }
+ }
+});
+
+
+/* **********************************************
+ Begin TL.Media.Vine.js
+********************************************** */
+
+/* TL.Media.Vine
+
+================================================== */
+
+TL.Media.Vine = TL.Media.extend({
+
+ includes: [TL.Events],
+
+ /* Load the media
+ ================================================== */
+ _loadMedia: function() {
+ var api_url,
+ self = this;
+
+ // Create Dom element
+ this._el.content_item = TL.Dom.create("div", "tl-media-item tl-media-iframe tl-media-vine tl-media-shadow", this._el.content);
+
+ // Get Media ID
+ this.media_id = this.data.url.split("vine.co/v/")[1];
+
+ // API URL
+ api_url = "https://vine.co/v/" + this.media_id + "/embed/simple";
+
+ // API Call
+ this._el.content_item.innerHTML = "<iframe frameborder='0' width='100%' height='100%' src='" + api_url + "'></iframe><script async src='https://platform.vine.co/static/scripts/embed.js' charset='utf-8'></script>"
+
+ // After Loaded
+ this.onLoaded();
+ },
+
+ // Update Media Display
+ _updateMediaDisplay: function() {
+ var size = TL.Util.ratio.square({w:this._el.content_item.offsetWidth , h:this.options.height});
+ this._el.content_item.style.height = size.h + "px";
+ },
+
+ _stopMedia: function() {
+ this._el.content_item.querySelector("iframe").contentWindow.postMessage('pause', '*');
+ }
+
+});
+
+
+/* **********************************************
+ Begin TL.Media.Website.js
+********************************************** */
+
+/* TL.Media.Website
+ Uses Embedly
+ http://embed.ly/docs/api/extract/endpoints/1/extract
+================================================== */
+
+TL.Media.Website = TL.Media.extend({
+
+ includes: [TL.Events],
+
+ /* Load the media
+ ================================================== */
+ _loadMedia: function() {
+ var self = this;
+
+ // Get Media ID
+ this.media_id = this.data.url.replace(/.*?:\/\//g, "");
+
+ if (this.options.api_key_embedly) {
+ // API URL
+ api_url = "https://api.embed.ly/1/extract?key=" + this.options.api_key_embedly + "&url=" + this.media_id + "&callback=?";
+
+ // API Call
+ TL.getJSON(api_url, function(d) {
+ self.createMedia(d);
+ });
+ } else {
+ this.createCardContent();
+ }
+ },
+
+ createCardContent: function() {
+ (function(w, d){
+ var id='embedly-platform', n = 'script';
+ if (!d.getElementById(id)){
+ w.embedly = w.embedly || function() {(w.embedly.q = w.embedly.q || []).push(arguments);};
+ var e = d.createElement(n); e.id = id; e.async=1;
+ e.src = ('https:' === document.location.protocol ? 'https' : 'http') + '://cdn.embedly.com/widgets/platform.js';
+ var s = d.getElementsByTagName(n)[0];
+ s.parentNode.insertBefore(e, s);
+ }
+ })(window, document);
+
+ var content = "<a href=\"" + this.data.url + "\" class=\"embedly-card\">" + this.data.url + "</a>";
+ this._setContent(content);
+
+ },
+ createMedia: function(d) { // this costs API credits...
+ var content = "";
+
+
+ content += "<h4><a href='" + this.data.url + "' target='_blank'>" + d.title + "</a></h4>";
+ if (d.images) {
+ if (d.images[0]) {
+ trace(d.images[0].url);
+ content += "<img src='" + d.images[0].url + "' />";
+ }
+ }
+ if (d.favicon_url) {
+ content += "<img class='tl-media-website-icon' src='" + d.favicon_url + "' />";
+ }
+ content += "<span class='tl-media-website-description'>" + d.provider_name + "</span><br/>";
+ content += "<p>" + d.description + "</p>";
+
+ this._setContent(content);
+ },
+
+ _setContent: function(content) {
+ // Create Dom element
+ this._el.content_item = TL.Dom.create("div", "tl-media-item tl-media-website", this._el.content);
+ this._el.content_container.className = "tl-media-content-container tl-media-content-container-text";
+ this._el.content_item.innerHTML = content;
+
+ // After Loaded
+ this.onLoaded();
+
+ },
+
+ updateMediaDisplay: function() {
+
+ },
+
+ _updateMediaDisplay: function() {
+
+ }
+
+
+});
+
+
+/* **********************************************
+ Begin TL.Media.Wikipedia.js
+********************************************** */
+
+/* TL.Media.Wikipedia
+================================================== */
+
+TL.Media.Wikipedia = TL.Media.extend({
+
+ includes: [TL.Events],
+
+ /* Load the media
+ ================================================== */
+ _loadMedia: function() {
+ var api_url,
+ api_language,
+ self = this;
+
+ // Create Dom element
+ this._el.content_item = TL.Dom.create("div", "tl-media-item tl-media-wikipedia", this._el.content);
+ this._el.content_container.className = "tl-media-content-container tl-media-content-container-text";
+
+ // Get Media ID
+ this.media_id = this.data.url.split("wiki\/")[1].split("#")[0].replace("_", " ");
+ this.media_id = this.media_id.replace(" ", "%20");
+ api_language = this.data.url.split("//")[1].split(".wikipedia")[0];
+
+ // API URL
+ api_url = "https://" + api_language + ".wikipedia.org/w/api.php?action=query&prop=extracts|pageimages&redirects=&titles=" + this.media_id + "&exintro=1&format=json&callback=?";
+
+ // API Call
+ TL.ajax({
+ type: 'GET',
+ url: api_url,
+ dataType: 'json', //json data type
+
+ success: function(d){
+ self.createMedia(d);
+ },
+ error:function(xhr, type){
+ var error_text = "";
+ error_text += self._("wikipedia_load_err") + "<br/>" + self.media_id + "<br/>" + type;
+ self.loadErrorDisplay(error_text);
+ }
+ });
+
+ },
+
+ createMedia: function(d) {
+ var wiki = "";
+
+ if (d.query) {
+ var content = "",
+ wiki = {
+ entry: {},
+ title: "",
+ text: "",
+ extract: "",
+ paragraphs: 1,
+ page_image: "",
+ text_array: []
+ };
+
+ wiki.entry = TL.Util.getObjectAttributeByIndex(d.query.pages, 0);
+ wiki.extract = wiki.entry.extract;
+ wiki.title = wiki.entry.title;
+ wiki.page_image = wiki.entry.thumbnail;
+
+ if (wiki.extract.match("<p>")) {
+ wiki.text_array = wiki.extract.split("<p>");
+ } else {
+ wiki.text_array.push(wiki.extract);
+ }
+
+ for(var i = 0; i < wiki.text_array.length; i++) {
+ if (i+1 <= wiki.paragraphs && i+1 < wiki.text_array.length) {
+ wiki.text += "<p>" + wiki.text_array[i+1];
+ }
+ }
+
+
+ content += "<span class='tl-icon-wikipedia'></span>";
+ content += "<div class='tl-wikipedia-title'><h4><a href='" + this.data.url + "' target='_blank'>" + wiki.title + "</a></h4>";
+ content += "<span class='tl-wikipedia-source'>" + this._('wikipedia') + "</span></div>";
+
+ if (wiki.page_image) {
+ //content += "<img class='tl-wikipedia-pageimage' src='" + wiki.page_image.source +"'>";
+ }
+
+ content += wiki.text;
+
+ if (wiki.extract.match("REDIRECT")) {
+
+ } else {
+ // Add to DOM
+ this._el.content_item.innerHTML = content;
+ // After Loaded
+ this.onLoaded();
+ }
+
+
+ }
+
+ },
+
+ updateMediaDisplay: function() {
+
+ },
+
+ _updateMediaDisplay: function() {
+
+ }
+
+});
+
+
+/* **********************************************
+ Begin TL.Media.Wistia.js
+********************************************** */
+
+/* TL.Media.Wistia
+================================================== */
+
+TL.Media.Wistia = TL.Media.extend({
+
+ includes: [TL.Events],
+
+ /* Load the media
+ ================================================== */
+ _loadMedia: function() {
+ var api_url,
+ self = this;
+
+ // Create Dom element
+ this._el.content_item = TL.Dom.create("div", "tl-media-item tl-media-iframe tl-media-wistia tl-media-shadow", this._el.content);
+
+ // Get Media ID
+ this.media_id = this.data.url.split(/https?:\/\/(.+)?(wistia\.com|wi\.st)\/medias\/(.*)/)[3];
+
+ // API URL
+ api_url = "https://fast.wistia.com/embed/iframe/" + this.media_id + "?version=v1&controlsVisibleOnLoad=true&playerColor=aae3d8";
+
+ this.player = TL.Dom.create("iframe", "", this._el.content_item);
+
+ // Media Loaded Event
+ this.player.addEventListener('load', function(e) {
+ self.onMediaLoaded();
+ });
+
+ this.player.width = "100%";
+ this.player.height = "100%";
+ this.player.frameBorder = "0";
+ this.player.src = api_url;
+
+ this.player.setAttribute('allowfullscreen', '');
+ this.player.setAttribute('webkitallowfullscreen', '');
+ this.player.setAttribute('mozallowfullscreen', '');
+
+ // After Loaded
+ this.onLoaded();
+ },
+
+ // Update Media Display
+ _updateMediaDisplay: function() {
+ this._el.content_item.style.height = TL.Util.ratio.r16_9({w:this._el.content_item.offsetWidth}) + "px";
+ },
+
+ _stopMedia: function() {
+ try {
+ this.player.contentWindow.postMessage(JSON.stringify({method: "pause"}), "https://player.vimeo.com");
+ }
+ catch(err) {
+ trace(err);
+ }
+ }
+});
+
+
+/* **********************************************
+ Begin TL.Media.YouTube.js
+********************************************** */
+
+/* TL.Media.YouTube
+================================================== */
+
+TL.Media.YouTube = TL.Media.extend({
+
+ includes: [TL.Events],
+
+ /* Load the media
+ ================================================== */
+ _loadMedia: function() {
+ var self = this,
+ url_vars;
+
+ this.youtube_loaded = false;
+
+ // Create Dom element
+ this._el.content_item = TL.Dom.create("div", "tl-media-item tl-media-youtube tl-media-shadow", this._el.content);
+ this._el.content_item.id = TL.Util.unique_ID(7)
+
+ // URL Vars
+ url_vars = TL.Util.getUrlVars(this.data.url);
+
+ // Get Media ID
+ this.media_id = {};
+
+ if (this.data.url.match('v=')) {
+ this.media_id.id = url_vars["v"];
+ } else if (this.data.url.match('\/embed\/')) {
+ this.media_id.id = this.data.url.split("embed\/")[1].split(/[?&]/)[0];
+ } else if (this.data.url.match(/v\/|v=|youtu\.be\//)){
+ this.media_id.id = this.data.url.split(/v\/|v=|youtu\.be\//)[1].split(/[?&]/)[0];
+ } else {
+ trace("YOUTUBE IN URL BUT NOT A VALID VIDEO");
+ }
+
+ // Get start second
+ if (this.data.url.match("start=")) {
+ this.media_id.start = parseInt(this.data.url.split("start=")[1], 10);
+ }
+ else if (this.data.url.match("t=")) {
+ this.media_id.start = parseInt(this.data.url.split("t=")[1], 10);
+ }
+
+ //Get end second
+ if (this.data.url.match("end=")) {
+ this.media_id.end = parseInt(this.data.url.split("end=")[1], 10);
+ }
+
+ this.media_id.hd = Boolean(typeof(url_vars["hd"]) != 'undefined');
+
+
+ // API Call
+ TL.Load.js('https://www.youtube.com/iframe_api', function() {
+ self.createMedia();
+ });
+
+ },
+
+ // Update Media Display
+ _updateMediaDisplay: function() {
+ //this.el.content_item = document.getElementById(this._el.content_item.id);
+ this._el.content_item.style.height = TL.Util.ratio.r16_9({w:this.options.width}) + "px";
+ this._el.content_item.style.width = this.options.width + "px";
+ },
+
+ _stopMedia: function() {
+ if (this.youtube_loaded) {
+ try {
+ if(this.player.getPlayerState() == YT.PlayerState.PLAYING) {
+ this.player.pauseVideo();
+ }
+ }
+ catch(err) {
+ trace(err);
+ }
+
+ }
+ },
+ createMedia: function() {
+ var self = this;
+
+ clearTimeout(this.timer);
+
+ if(typeof YT != 'undefined' && typeof YT.Player != 'undefined') {
+ // Create Player
+ this.player = new YT.Player(this._el.content_item.id, {
+ playerVars: {
+ enablejsapi: 1,
+ color: 'white',
+ controls: 1,
+ start: this.media_id.start,
+ end: this.media_id.end,
+ fs: 1
+ },
+ videoId: this.media_id.id,
+ events: {
+ onReady: function() {
+ self.onPlayerReady();
+ // After Loaded
+ self.onLoaded();
+ },
+ 'onStateChange': self.onStateChange
+ }
+ });
+ } else {
+ this.timer = setTimeout(function() {
+ self.createMedia();
+ }, 1000);
+ }
+ },
+
+ /* Events
+ ================================================== */
+ onPlayerReady: function(e) {
+ this.youtube_loaded = true;
+ this._el.content_item = document.getElementById(this._el.content_item.id);
+ this.onMediaLoaded();
+
+ },
+
+ onStateChange: function(e) {
+ if(e.data == YT.PlayerState.ENDED) {
+ e.target.seekTo(0);
+ e.target.pauseVideo();
+ }
+ }
+
+
+});
+
+
+/* **********************************************
+ Begin TL.Media.Audio.js
+********************************************** */
+
+/* TL.Media.Audio
+ Produces audio assets.
+ Takes a data object and populates a dom object
+================================================== */
+
+TL.Media.Audio = TL.Media.extend({
+
+ includes: [TL.Events],
+
+ /* Load the media
+ ================================================== */
+ _loadMedia: function() {
+ // Loading Message
+ this.loadingMessage();
+
+ // Create media?
+ if(!this.options.background) {
+ this.createMedia();
+ }
+
+ // After loaded
+ this.onLoaded();
+ },
+
+ createMedia: function() {
+ var self = this,
+ audio_class = "tl-media-item tl-media-audio tl-media-shadow";
+
+ // Link
+ if (this.data.link) {
+ this._el.content_link = TL.Dom.create("a", "", this._el.content);
+ this._el.content_link.href = this.data.link;
+ this._el.content_link.target = "_blank";
+ this._el.content_item = TL.Dom.create("audio", audio_class, this._el.content_link);
+ } else {
+ this._el.content_item = TL.Dom.create("audio", audio_class, this._el.content);
+ }
+
+ this._el.content_item.controls = true;
+ this._el.source_item = TL.Dom.create("source", "", this._el.content_item);
+
+ // Media Loaded Event
+ this._el.content_item.addEventListener('load', function(e) {
+ self.onMediaLoaded();
+ });
+
+ this._el.source_item.src = this.data.url;
+ this._el.source_item.type = this._getType(this.data.url, this.data.mediatype.match_str);
+ this._el.content_item.innerHTML += "Your browser doesn't support HTML5 audio with " + this._el.source_item.type;
+ },
+
+ _updateMediaDisplay: function(layout) {
+ if(TL.Browser.firefox) {
+ this._el.content_item.style.width = "auto";
+ }
+ },
+
+ _getType: function(url, reg) {
+ var ext = url.match(reg);
+ var type = "audio/"
+ switch(ext[1]) {
+ case "mp3":
+ type += "mpeg";
+ break;
+ case "wav":
+ type += "wav";
+ break;
+ case "m4a":
+ type += "mp4";
+ break;
+ default:
+ type = "audio";
+ break;
+ }
+ return type
+ }
+
+});
+
+
+/* **********************************************
+ Begin TL.Media.Video.js
+********************************************** */
+
+/* TL.Media.Video
+ Produces video assets.
+ Takes a data object and populates a dom object
+================================================== */
+
+TL.Media.Video = TL.Media.extend({
+
+ includes: [TL.Events],
+
+ /* Load the media
+ ================================================== */
+ _loadMedia: function() {
+ // Loading Message
+ this.loadingMessage();
+
+ // Create media?
+ if(!this.options.background) {
+ this.createMedia();
+ }
+
+ // After loaded
+ this.onLoaded();
+ },
+
+ createMedia: function() {
+ var self = this,
+ video_class = "tl-media-item tl-media-video tl-media-shadow";
+
+ // Link
+ if (this.data.link) {
+ this._el.content_link = TL.Dom.create("a", "", this._el.content);
+ this._el.content_link.href = this.data.link;
+ this._el.content_link.target = "_blank";
+ this._el.content_item = TL.Dom.create("video", video_class, this._el.content_link);
+ } else {
+ this._el.content_item = TL.Dom.create("video", video_class, this._el.content);
+ }
+
+ this._el.content_item.controls = true;
+ this._el.source_item = TL.Dom.create("source", "", this._el.content_item);
+
+ // Media Loaded Event
+ this._el.content_item.addEventListener('load', function(e) {
+ self.onMediaLoaded();
+ });
+
+ this._el.source_item.src = this.data.url;
+ this._el.source_item.type = this._getType(this.data.url, this.data.mediatype.match_str);
+ this._el.content_item.innerHTML += "Your browser doesn't support HTML5 video with " + this._el.source_item.type;
+ },
+
+ _updateMediaDisplay: function(layout) {
+ if(TL.Browser.firefox) {
+ this._el.content_item.style.width = "auto";
+ }
+ },
+
+ _getType: function(url, reg) {
+ var ext = url.match(reg);
+ var type = "video/"
+ switch(ext[1]) {
+ case "mp4":
+ type += "mp4";
+ break;
+ default:
+ type = "video";
+ break;
+ }
+ return type
+ }
+
+});
+
+
+/* **********************************************
+ Begin TL.Slide.js
+********************************************** */
+
+/* TL.Slide
+ Creates a slide. Takes a data object and
+ populates the slide with content.
+================================================== */
+
+TL.Slide = TL.Class.extend({
+
+ includes: [TL.Events, TL.DomMixins, TL.I18NMixins],
+
+ _el: {},
+
+ /* Constructor
+ ================================================== */
+ initialize: function(data, options, title_slide) {
+ // DOM Elements
+ this._el = {
+ container: {},
+ scroll_container: {},
+ background: {},
+ content_container: {},
+ content: {}
+ };
+
+ // Components
+ this._media = null;
+ this._mediaclass = {};
+ this._text = {};
+ this._background_media = null;
+
+ // State
+ this._state = {
+ loaded: false
+ };
+
+ this.has = {
+ headline: false,
+ text: false,
+ media: false,
+ title: false,
+ background: {
+ image: false,
+ color: false,
+ color_value :""
+ }
+ }
+
+ this.has.title = title_slide;
+
+ // Data
+ this.data = {
+ unique_id: null,
+ background: null,
+ start_date: null,
+ end_date: null,
+ location: null,
+ text: null,
+ media: null,
+ autolink: true
+ };
+
+ // Options
+ this.options = {
+ // animation
+ duration: 1000,
+ slide_padding_lr: 40,
+ ease: TL.Ease.easeInSpline,
+ width: 600,
+ height: 600,
+ skinny_size: 650,
+ media_name: ""
+ };
+
+ // Actively Displaying
+ this.active = false;
+
+ // Animation Object
+ this.animator = {};
+
+ // Merge Data and Options
+ TL.Util.mergeData(this.options, options);
+ TL.Util.mergeData(this.data, data);
+
+ this._initLayout();
+ this._initEvents();
+
+
+ },
+
+ /* Adding, Hiding, Showing etc
+ ================================================== */
+ show: function() {
+ this.animator = TL.Animate(this._el.slider_container, {
+ left: -(this._el.container.offsetWidth * n) + "px",
+ duration: this.options.duration,
+ easing: this.options.ease
+ });
+ },
+
+ hide: function() {
+
+ },
+
+ setActive: function(is_active) {
+ this.active = is_active;
+
+ if (this.active) {
+ if (this.data.background) {
+ this.fire("background_change", this.has.background);
+ }
+ this.loadMedia();
+ } else {
+ this.stopMedia();
+ }
+ },
+
+ addTo: function(container) {
+ container.appendChild(this._el.container);
+ //this.onAdd();
+ },
+
+ removeFrom: function(container) {
+ container.removeChild(this._el.container);
+ },
+
+ updateDisplay: function(w, h, l) {
+ this._updateDisplay(w, h, l);
+ },
+
+ loadMedia: function() {
+ var self = this;
+
+ if (this._media && !this._state.loaded) {
+ this._media.loadMedia();
+ this._state.loaded = true;
+ }
+
+ if(this._background_media && !this._background_media._state.loaded) {
+ this._background_media.on("loaded", function() {
+ self._updateBackgroundDisplay();
+ });
+ this._background_media.loadMedia();
+ }
+ },
+
+ stopMedia: function() {
+ if (this._media && this._state.loaded) {
+ this._media.stopMedia();
+ }
+ },
+
+ getBackground: function() {
+ return this.has.background;
+ },
+
+ scrollToTop: function() {
+ this._el.container.scrollTop = 0;
+ },
+
+ getFormattedDate: function() {
+
+ if (TL.Util.trim(this.data.display_date).length > 0) {
+ return this.data.display_date;
+ }
+ var date_text = "";
+
+ if(!this.has.title) {
+ if (this.data.end_date) {
+ date_text = " &mdash; " + this.data.end_date.getDisplayDate(this.getLanguage());
+ }
+ if (this.data.start_date) {
+ date_text = this.data.start_date.getDisplayDate(this.getLanguage()) + date_text;
+ }
+ }
+ return date_text;
+ },
+
+ /* Events
+ ================================================== */
+
+
+ /* Private Methods
+ ================================================== */
+ _initLayout: function () {
+ // Create Layout
+ this._el.container = TL.Dom.create("div", "tl-slide");
+
+ if (this.has.title) {
+ this._el.container.className = "tl-slide tl-slide-titleslide";
+ }
+
+ if (this.data.unique_id) {
+ this._el.container.id = this.data.unique_id;
+ }
+ this._el.scroll_container = TL.Dom.create("div", "tl-slide-scrollable-container", this._el.container);
+ this._el.content_container = TL.Dom.create("div", "tl-slide-content-container", this._el.scroll_container);
+ this._el.content = TL.Dom.create("div", "tl-slide-content", this._el.content_container);
+ this._el.background = TL.Dom.create("div", "tl-slide-background", this._el.container);
+ // Style Slide Background
+ if (this.data.background) {
+ if (this.data.background.url) {
+ var media_type = TL.MediaType(this.data.background, true);
+ if(media_type) {
+ this._background_media = new media_type.cls(this.data.background, {background: 1});
+
+ this.has.background.image = true;
+ this._el.container.className += ' tl-full-image-background';
+ this.has.background.color_value = "#000";
+ this._el.background.style.display = "block";
+ }
+ }
+ if (this.data.background.color) {
+ this.has.background.color = true;
+ this._el.container.className += ' tl-full-color-background';
+ this.has.background.color_value = this.data.background.color;
+ //this._el.container.style.backgroundColor = this.data.background.color;
+ //this._el.background.style.backgroundColor = this.data.background.color;
+ //this._el.background.style.display = "block";
+ }
+ if (this.data.background.text_background) {
+ this._el.container.className += ' tl-text-background';
+ }
+
+ }
+
+
+
+ // Determine Assets for layout and loading
+ if (this.data.media && this.data.media.url && this.data.media.url != "") {
+ this.has.media = true;
+ }
+ if (this.data.text && this.data.text.text) {
+ this.has.text = true;
+ }
+ if (this.data.text && this.data.text.headline) {
+ this.has.headline = true;
+ }
+
+ // Create Media
+ if (this.has.media) {
+ // Determine the media type
+ this.data.media.mediatype = TL.MediaType(this.data.media);
+ this.options.media_name = this.data.media.mediatype.name;
+ this.options.media_type = this.data.media.mediatype.type;
+ this.options.autolink = this.data.autolink;
+
+ // Create a media object using the matched class name
+ this._media = new this.data.media.mediatype.cls(this.data.media, this.options);
+ }
+
+ // Create Text
+ if (this.has.text || this.has.headline) {
+ this._text = new TL.Media.Text(this.data.text, {title:this.has.title,language: this.options.language, autolink: this.data.autolink });
+ this._text.addDateText(this.getFormattedDate());
+ }
+
+
+
+ // Add to DOM
+ if (!this.has.text && !this.has.headline && this.has.media) {
+ TL.DomUtil.addClass(this._el.container, 'tl-slide-media-only');
+ this._media.addTo(this._el.content);
+ } else if (this.has.headline && this.has.media && !this.has.text) {
+ TL.DomUtil.addClass(this._el.container, 'tl-slide-media-only');
+ this._text.addTo(this._el.content);
+ this._media.addTo(this._el.content);
+ } else if (this.has.text && this.has.media) {
+ this._media.addTo(this._el.content);
+ this._text.addTo(this._el.content);
+ } else if (this.has.text || this.has.headline) {
+ TL.DomUtil.addClass(this._el.container, 'tl-slide-text-only');
+ this._text.addTo(this._el.content);
+ }
+
+ // Fire event that the slide is loaded
+ this.onLoaded();
+
+ },
+
+ _initEvents: function() {
+
+ },
+
+ // Update Display
+ _updateDisplay: function(width, height, layout) {
+ var content_width,
+ content_padding_left = this.options.slide_padding_lr,
+ content_padding_right = this.options.slide_padding_lr;
+
+ if (width) {
+ this.options.width = width;
+ } else {
+ this.options.width = this._el.container.offsetWidth;
+ }
+
+ content_width = this.options.width - (this.options.slide_padding_lr * 2);
+
+ if(TL.Browser.mobile && (this.options.width <= this.options.skinny_size)) {
+ content_padding_left = 0;
+ content_padding_right = 0;
+ content_width = this.options.width;
+ } else if (layout == "landscape") {
+
+ } else if (this.options.width <= this.options.skinny_size) {
+ content_padding_left = 50;
+ content_padding_right = 50;
+ content_width = this.options.width - content_padding_left - content_padding_right;
+ } else {
+
+ }
+
+ this._el.content.style.paddingLeft = content_padding_left + "px";
+ this._el.content.style.paddingRight = content_padding_right + "px";
+ this._el.content.style.width = content_width + "px";
+
+ if (height) {
+ this.options.height = height;
+ //this._el.scroll_container.style.height = this.options.height + "px";
+
+ } else {
+ this.options.height = this._el.container.offsetHeight;
+ }
+
+ if (this._media) {
+
+ if (!this.has.text && this.has.headline) {
+ this._media.updateDisplay(content_width, (this.options.height - this._text.headlineHeight()), layout);
+ } else if (!this.has.text && !this.has.headline) {
+ this._media.updateDisplay(content_width, this.options.height, layout);
+ } else if (this.options.width <= this.options.skinny_size) {
+ this._media.updateDisplay(content_width, this.options.height, layout);
+ } else {
+ this._media.updateDisplay(content_width/2, this.options.height, layout);
+ }
+ }
+
+ this._updateBackgroundDisplay();
+ },
+
+ _updateBackgroundDisplay: function() {
+ if(this._background_media && this._background_media._state.loaded) {
+ this._el.background.style.backgroundImage = "url('" + this._background_media.getImageURL(this.options.width, this.options.height) + "')";
+ }
+ }
+
+});
+
+
+/* **********************************************
+ Begin TL.SlideNav.js
+********************************************** */
+
+/* TL.SlideNav
+ encapsulate DOM display/events for the
+ 'next' and 'previous' buttons on a slide.
+================================================== */
+// TODO null out data
+
+TL.SlideNav = TL.Class.extend({
+
+ includes: [TL.Events, TL.DomMixins],
+
+ _el: {},
+
+ /* Constructor
+ ================================================== */
+ initialize: function(data, options, add_to_container) {
+ // DOM ELEMENTS
+ this._el = {
+ container: {},
+ content_container: {},
+ icon: {},
+ title: {},
+ description: {}
+ };
+
+ // Media Type
+ this.mediatype = {};
+
+ // Data
+ this.data = {
+ title: "Navigation",
+ description: "Description",
+ date: "Date"
+ };
+
+ //Options
+ this.options = {
+ direction: "previous"
+ };
+
+ this.animator = null;
+
+ // Merge Data and Options
+ TL.Util.mergeData(this.options, options);
+ TL.Util.mergeData(this.data, data);
+
+
+ this._el.container = TL.Dom.create("div", "tl-slidenav-" + this.options.direction);
+
+ if (TL.Browser.mobile) {
+ this._el.container.setAttribute("ontouchstart"," ");
+ }
+
+ this._initLayout();
+ this._initEvents();
+
+ if (add_to_container) {
+ add_to_container.appendChild(this._el.container);
+ };
+
+ },
+
+ /* Update Content
+ ================================================== */
+ update: function(slide) {
+ var d = {
+ title: "",
+ description: "",
+ date: slide.getFormattedDate()
+ };
+
+ if (slide.data.text) {
+ if (slide.data.text.headline) {
+ d.title = slide.data.text.headline;
+ }
+ }
+
+ this._update(d);
+ },
+
+ /* Color
+ ================================================== */
+ setColor: function(inverted) {
+ if (inverted) {
+ this._el.content_container.className = 'tl-slidenav-content-container tl-slidenav-inverted';
+ } else {
+ this._el.content_container.className = 'tl-slidenav-content-container';
+ }
+ },
+
+ /* Events
+ ================================================== */
+ _onMouseClick: function() {
+ this.fire("clicked", this.options);
+ },
+
+ /* Private Methods
+ ================================================== */
+ _update: function(d) {
+ // update data
+ this.data = TL.Util.mergeData(this.data, d);
+
+ // Title
+ this._el.title.innerHTML = TL.Util.unlinkify(this.data.title);
+
+ // Date
+ this._el.description.innerHTML = TL.Util.unlinkify(this.data.date);
+ },
+
+ _initLayout: function () {
+
+ // Create Layout
+ this._el.content_container = TL.Dom.create("div", "tl-slidenav-content-container", this._el.container);
+ this._el.icon = TL.Dom.create("div", "tl-slidenav-icon", this._el.content_container);
+ this._el.title = TL.Dom.create("div", "tl-slidenav-title", this._el.content_container);
+ this._el.description = TL.Dom.create("div", "tl-slidenav-description", this._el.content_container);
+
+ this._el.icon.innerHTML = "&nbsp;"
+
+ this._update();
+ },
+
+ _initEvents: function () {
+ TL.DomEvent.addListener(this._el.container, 'click', this._onMouseClick, this);
+ }
+
+
+});
+
+/* **********************************************
+ Begin TL.StorySlider.js
+********************************************** */
+
+/* StorySlider
+ is the central class of the API - it is used to create a StorySlider
+
+ Events:
+ nav_next
+ nav_previous
+ slideDisplayUpdate
+ loaded
+ slideAdded
+ slideLoaded
+ slideRemoved
+
+
+================================================== */
+
+TL.StorySlider = TL.Class.extend({
+
+ includes: [TL.Events, TL.I18NMixins],
+
+ /* Private Methods
+ ================================================== */
+ initialize: function (elem, data, options, init) {
+
+ // DOM ELEMENTS
+ this._el = {
+ container: {},
+ background: {},
+ slider_container_mask: {},
+ slider_container: {},
+ slider_item_container: {}
+ };
+
+ this._nav = {};
+ this._nav.previous = {};
+ this._nav.next = {};
+
+ // Slide Spacing
+ this.slide_spacing = 0;
+
+ // Slides Array
+ this._slides = [];
+
+ // Swipe Object
+ this._swipable;
+
+ // Preload Timer
+ this.preloadTimer;
+
+ // Message
+ this._message;
+
+ // Current Slide
+ this.current_id = '';
+
+ // Data Object
+ this.data = {};
+
+ this.options = {
+ id: "",
+ layout: "portrait",
+ width: 600,
+ height: 600,
+ default_bg_color: {r:255, g:255, b:255},
+ slide_padding_lr: 40, // padding on slide of slide
+ start_at_slide: 1,
+ slide_default_fade: "0%", // landscape fade
+ // animation
+ duration: 1000,
+ ease: TL.Ease.easeInOutQuint,
+ // interaction
+ dragging: true,
+ trackResize: true
+ };
+
+ // Main element ID
+ if (typeof elem === 'object') {
+ this._el.container = elem;
+ this.options.id = TL.Util.unique_ID(6, "tl");
+ } else {
+ this.options.id = elem;
+ this._el.container = TL.Dom.get(elem);
+ }
+
+ if (!this._el.container.id) {
+ this._el.container.id = this.options.id;
+ }
+
+ // Animation Object
+ this.animator = null;
+
+ // Merge Data and Options
+ TL.Util.mergeData(this.options, options);
+ TL.Util.mergeData(this.data, data);
+
+ if (init) {
+ this.init();
+ }
+ },
+
+ init: function() {
+ this._initLayout();
+ this._initEvents();
+ this._initData();
+ this._updateDisplay();
+
+ // Go to initial slide
+ this.goTo(this.options.start_at_slide);
+
+ this._onLoaded();
+ },
+
+ /* Slides
+ ================================================== */
+ _addSlide:function(slide) {
+ slide.addTo(this._el.slider_item_container);
+ slide.on('added', this._onSlideAdded, this);
+ slide.on('background_change', this._onBackgroundChange, this);
+ },
+
+ _createSlide: function(d, title_slide, n) {
+ var slide = new TL.Slide(d, this.options, title_slide);
+ this._addSlide(slide);
+ if(n < 0) {
+ this._slides.push(slide);
+ } else {
+ this._slides.splice(n, 0, slide);
+ }
+ },
+
+ _createSlides: function(array) {
+ for (var i = 0; i < array.length; i++) {
+ if (array[i].unique_id == "") {
+ array[i].unique_id = TL.Util.unique_ID(6, "tl-slide");
+ }
+ this._createSlide(array[i], false, -1);
+ }
+ },
+
+ _removeSlide: function(slide) {
+ slide.removeFrom(this._el.slider_item_container);
+ slide.off('added', this._onSlideRemoved, this);
+ slide.off('background_change', this._onBackgroundChange);
+ },
+
+ _destroySlide: function(n) {
+ this._removeSlide(this._slides[n]);
+ this._slides.splice(n, 1);
+ },
+
+ _findSlideIndex: function(n) {
+ var _n = n;
+ if (typeof n == 'string' || n instanceof String) {
+ _n = TL.Util.findArrayNumberByUniqueID(n, this._slides, "unique_id");
+ }
+ return _n;
+ },
+
+ /* Public
+ ================================================== */
+ updateDisplay: function(w, h, a, l) {
+ this._updateDisplay(w, h, a, l);
+ },
+
+ // Create a slide
+ createSlide: function(d, n) {
+ this._createSlide(d, false, n);
+ },
+
+ // Create Many Slides from an array
+ createSlides: function(array) {
+ this._createSlides(array);
+ },
+
+ // Destroy slide by index
+ destroySlide: function(n) {
+ this._destroySlide(n);
+ },
+
+ // Destroy slide by id
+ destroySlideId: function(id) {
+ this.destroySlide(this._findSlideIndex(id));
+ },
+
+ /* Navigation
+ ================================================== */
+ goTo: function(n, fast, displayupdate) {
+ n = parseInt(n);
+ if (isNaN(n)) n = 0;
+
+ var self = this;
+
+ this.changeBackground({color_value:"", image:false});
+
+ // Clear Preloader Timer
+ if (this.preloadTimer) {
+ clearTimeout(this.preloadTimer);
+ }
+
+ // Set Slide Active State
+ for (var i = 0; i < this._slides.length; i++) {
+ this._slides[i].setActive(false);
+ }
+
+ if (n < this._slides.length && n >= 0) {
+ this.current_id = this._slides[n].data.unique_id;
+
+ // Stop animation
+ if (this.animator) {
+ this.animator.stop();
+ }
+ if (this._swipable) {
+ this._swipable.stopMomentum();
+ }
+
+ if (fast) {
+ this._el.slider_container.style.left = -(this.slide_spacing * n) + "px";
+ this._onSlideChange(displayupdate);
+ } else {
+ this.animator = TL.Animate(this._el.slider_container, {
+ left: -(this.slide_spacing * n) + "px",
+ duration: this.options.duration,
+ easing: this.options.ease,
+ complete: this._onSlideChange(displayupdate)
+ });
+ }
+
+ // Set Slide Active State
+ this._slides[n].setActive(true);
+
+ // Update Navigation and Info
+ if (this._slides[n + 1]) {
+ this.showNav(this._nav.next, true);
+ this._nav.next.update(this._slides[n + 1]);
+ } else {
+ this.showNav(this._nav.next, false);
+ }
+ if (this._slides[n - 1]) {
+ this.showNav(this._nav.previous, true);
+ this._nav.previous.update(this._slides[n - 1]);
+ } else {
+ this.showNav(this._nav.previous, false);
+ }
+
+ // Preload Slides
+ this.preloadTimer = setTimeout(function() {
+ self.preloadSlides(n);
+ }, this.options.duration);
+ }
+ },
+
+ goToId: function(id, fast, displayupdate) {
+ this.goTo(this._findSlideIndex(id), fast, displayupdate);
+ },
+
+ preloadSlides: function(n) {
+ if (this._slides[n + 1]) {
+ this._slides[n + 1].loadMedia();
+ this._slides[n + 1].scrollToTop();
+ }
+ if (this._slides[n + 2]) {
+ this._slides[n + 2].loadMedia();
+ this._slides[n + 2].scrollToTop();
+ }
+ if (this._slides[n - 1]) {
+ this._slides[n - 1].loadMedia();
+ this._slides[n - 1].scrollToTop();
+ }
+ if (this._slides[n - 2]) {
+ this._slides[n - 2].loadMedia();
+ this._slides[n - 2].scrollToTop();
+ }
+ },
+
+ next: function() {
+ var n = this._findSlideIndex(this.current_id);
+ if ((n + 1) < (this._slides.length)) {
+ this.goTo(n + 1);
+ } else {
+ this.goTo(n);
+ }
+ },
+
+ previous: function() {
+ var n = this._findSlideIndex(this.current_id);
+ if (n - 1 >= 0) {
+ this.goTo(n - 1);
+ } else {
+ this.goTo(n);
+ }
+ },
+
+ showNav: function(nav_obj, show) {
+
+ if (this.options.width <= 500 && TL.Browser.mobile) {
+
+ } else {
+ if (show) {
+ nav_obj.show();
+ } else {
+ nav_obj.hide();
+ }
+
+ }
+ },
+
+
+
+ changeBackground: function(bg) {
+ var bg_color = {r:256, g:256, b:256},
+ bg_color_rgb;
+
+ if (bg.color_value && bg.color_value != "") {
+ bg_color = TL.Util.hexToRgb(bg.color_value);
+ if (!bg_color) {
+ trace("Invalid color value " + bg.color_value);
+ bg_color = this.options.default_bg_color;
+ }
+ } else {
+ bg_color = this.options.default_bg_color;
+ bg.color_value = "rgb(" + bg_color.r + " , " + bg_color.g + ", " + bg_color.b + ")";
+ }
+
+ bg_color_rgb = bg_color.r + "," + bg_color.g + "," + bg_color.b;
+ this._el.background.style.backgroundImage = "none";
+
+
+ if (bg.color_value) {
+ this._el.background.style.backgroundColor = bg.color_value;
+ } else {
+ this._el.background.style.backgroundColor = "transparent";
+ }
+
+ if (bg_color.r < 255 || bg_color.g < 255 || bg_color.b < 255 || bg.image) {
+ this._nav.next.setColor(true);
+ this._nav.previous.setColor(true);
+ } else {
+ this._nav.next.setColor(false);
+ this._nav.previous.setColor(false);
+ }
+ },
+ /* Private Methods
+ ================================================== */
+
+ // Update Display
+ _updateDisplay: function(width, height, animate, layout) {
+ var nav_pos, _layout;
+
+ if(typeof layout === 'undefined'){
+ _layout = this.options.layout;
+ } else {
+ _layout = layout;
+ }
+
+ this.options.layout = _layout;
+
+ this.slide_spacing = this.options.width*2;
+
+ if (width) {
+ this.options.width = width;
+ } else {
+ this.options.width = this._el.container.offsetWidth;
+ }
+
+ if (height) {
+ this.options.height = height;
+ } else {
+ this.options.height = this._el.container.offsetHeight;
+ }
+
+ //this._el.container.style.height = this.options.height;
+
+ // position navigation
+ nav_pos = (this.options.height/2);
+ this._nav.next.setPosition({top:nav_pos});
+ this._nav.previous.setPosition({top:nav_pos});
+
+
+ // Position slides
+ for (var i = 0; i < this._slides.length; i++) {
+ this._slides[i].updateDisplay(this.options.width, this.options.height, _layout);
+ this._slides[i].setPosition({left:(this.slide_spacing * i), top:0});
+
+ };
+
+ // Go to the current slide
+ this.goToId(this.current_id, true, true);
+ },
+
+ // Reposition and redraw slides
+ _updateDrawSlides: function() {
+ var _layout = this.options.layout;
+
+ for (var i = 0; i < this._slides.length; i++) {
+ this._slides[i].updateDisplay(this.options.width, this.options.height, _layout);
+ this._slides[i].setPosition({left:(this.slide_spacing * i), top:0});
+ };
+
+ this.goToId(this.current_id, true, false);
+ },
+
+
+ /* Init
+ ================================================== */
+ _initLayout: function () {
+
+ TL.DomUtil.addClass(this._el.container, 'tl-storyslider');
+
+ // Create Layout
+ this._el.slider_container_mask = TL.Dom.create('div', 'tl-slider-container-mask', this._el.container);
+ this._el.background = TL.Dom.create('div', 'tl-slider-background tl-animate', this._el.container);
+ this._el.slider_container = TL.Dom.create('div', 'tl-slider-container tlanimate', this._el.slider_container_mask);
+ this._el.slider_item_container = TL.Dom.create('div', 'tl-slider-item-container', this._el.slider_container);
+
+
+ // Update Size
+ this.options.width = this._el.container.offsetWidth;
+ this.options.height = this._el.container.offsetHeight;
+
+ // Create Navigation
+ this._nav.previous = new TL.SlideNav({title: "Previous", description: "description"}, {direction:"previous"});
+ this._nav.next = new TL.SlideNav({title: "Next",description: "description"}, {direction:"next"});
+
+ // add the navigation to the dom
+ this._nav.next.addTo(this._el.container);
+ this._nav.previous.addTo(this._el.container);
+
+
+
+ this._el.slider_container.style.left="0px";
+
+ if (TL.Browser.touch) {
+ //this._el.slider_touch_mask = TL.Dom.create('div', 'tl-slider-touch-mask', this._el.slider_container_mask);
+ this._swipable = new TL.Swipable(this._el.slider_container_mask, this._el.slider_container, {
+ enable: {x:true, y:false},
+ snap: true
+ });
+ this._swipable.enable();
+
+ // Message
+ this._message = new TL.Message({}, {
+ message_class: "tl-message-full",
+ message_icon_class: "tl-icon-swipe-left"
+ });
+ this._message.updateMessage(this._("swipe_to_navigate"));
+ this._message.addTo(this._el.container);
+ }
+
+ },
+
+ _initEvents: function () {
+ this._nav.next.on('clicked', this._onNavigation, this);
+ this._nav.previous.on('clicked', this._onNavigation, this);
+
+ if (this._message) {
+ this._message.on('clicked', this._onMessageClick, this);
+ }
+
+ if (this._swipable) {
+ this._swipable.on('swipe_left', this._onNavigation, this);
+ this._swipable.on('swipe_right', this._onNavigation, this);
+ this._swipable.on('swipe_nodirection', this._onSwipeNoDirection, this);
+ }
+
+
+ },
+
+ _initData: function() {
+ if(this.data.title) {
+ this._createSlide(this.data.title, true, -1);
+ }
+ this._createSlides(this.data.events);
+ },
+
+ /* Events
+ ================================================== */
+ _onBackgroundChange: function(e) {
+ var n = this._findSlideIndex(this.current_id);
+ var slide_background = this._slides[n].getBackground();
+ this.changeBackground(e);
+ this.fire("colorchange", slide_background);
+ },
+
+ _onMessageClick: function(e) {
+ this._message.hide();
+ },
+
+ _onSwipeNoDirection: function(e) {
+ this.goToId(this.current_id);
+ },
+
+ _onNavigation: function(e) {
+
+ if (e.direction == "next" || e.direction == "left") {
+ this.next();
+ } else if (e.direction == "previous" || e.direction == "right") {
+ this.previous();
+ }
+ this.fire("nav_" + e.direction, this.data);
+ },
+
+ _onSlideAdded: function(e) {
+ trace("slideadded")
+ this.fire("slideAdded", this.data);
+ },
+
+ _onSlideRemoved: function(e) {
+ this.fire("slideRemoved", this.data);
+ },
+
+ _onSlideChange: function(displayupdate) {
+ if (!displayupdate) {
+ this.fire("change", {unique_id: this.current_id});
+ }
+ },
+
+ _onMouseClick: function(e) {
+
+ },
+
+ _fireMouseEvent: function (e) {
+ if (!this._loaded) {
+ return;
+ }
+
+ var type = e.type;
+ type = (type === 'mouseenter' ? 'mouseover' : (type === 'mouseleave' ? 'mouseout' : type));
+
+ if (!this.hasEventListeners(type)) {
+ return;
+ }
+
+ if (type === 'contextmenu') {
+ TL.DomEvent.preventDefault(e);
+ }
+
+ this.fire(type, {
+ latlng: "something", //this.mouseEventToLatLng(e),
+ layerPoint: "something else" //this.mouseEventToLayerPoint(e)
+ });
+ },
+
+ _onLoaded: function() {
+ this.fire("loaded", this.data);
+ }
+
+
+});
+
+
+/* **********************************************
+ Begin TL.TimeNav.js
+********************************************** */
+
+/* TL.TimeNav
+
+================================================== */
+
+TL.TimeNav = TL.Class.extend({
+
+ includes: [TL.Events, TL.DomMixins],
+
+ _el: {},
+
+ /* Constructor
+ ================================================== */
+ initialize: function (elem, timeline_config, options, init) {
+ // DOM ELEMENTS
+ this._el = {
+ parent: {},
+ container: {},
+ slider: {},
+ slider_background: {},
+ line: {},
+ marker_container_mask: {},
+ marker_container: {},
+ marker_item_container: {},
+ timeaxis: {},
+ timeaxis_background: {},
+ attribution: {}
+ };
+
+ this.collapsed = false;
+
+ if (typeof elem === 'object') {
+ this._el.container = elem;
+ } else {
+ this._el.container = TL.Dom.get(elem);
+ }
+
+ this.config = timeline_config;
+
+ //Options
+ this.options = {
+ width: 600,
+ height: 600,
+ duration: 1000,
+ ease: TL.Ease.easeInOutQuint,
+ has_groups: false,
+ optimal_tick_width: 50,
+ scale_factor: 2, // How many screen widths wide should the timeline be
+ marker_padding: 5,
+ timenav_height_min: 150, // Minimum timenav height
+ marker_height_min: 30, // Minimum Marker Height
+ marker_width_min: 100, // Minimum Marker Width
+ zoom_sequence: [0.5, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89] // Array of Fibonacci numbers for TimeNav zoom levels http://www.maths.surrey.ac.uk/hosted-sites/R.Knott/Fibonacci/fibtable.html
+ };
+
+ // Animation
+ this.animator = null;
+
+ // Ready state
+ this.ready = false;
+
+ // Markers Array
+ this._markers = [];
+
+ // Eras Array
+ this._eras = [];
+ this.has_eras = false;
+
+ // Groups Array
+ this._groups = [];
+
+ // Row Height
+ this._calculated_row_height = 100;
+
+ // Current Marker
+ this.current_id = "";
+
+ // TimeScale
+ this.timescale = {};
+
+ // TimeAxis
+ this.timeaxis = {};
+ this.axishelper = {};
+
+ // Max Rows
+ this.max_rows = 6;
+
+ // Animate CSS
+ this.animate_css = false;
+
+ // Swipe Object
+ this._swipable;
+
+ // Merge Data and Options
+ TL.Util.mergeData(this.options, options);
+
+ if (init) {
+ this.init();
+ }
+ },
+
+ init: function() {
+ this._initLayout();
+ this._initEvents();
+ this._initData();
+ this._updateDisplay();
+
+ this._onLoaded();
+ },
+
+ /* Public
+ ================================================== */
+ positionMarkers: function() {
+ this._positionMarkers();
+ },
+
+ /* Update Display
+ ================================================== */
+ updateDisplay: function(w, h, a, l) {
+ this._updateDisplay(w, h, a, l);
+ },
+
+
+ /* TimeScale
+ ================================================== */
+ _getTimeScale: function() {
+ /* maybe the establishing config values (marker_height_min and max_rows) should be
+ separated from making a TimeScale object, which happens in another spot in this file with duplicate mapping of properties of this TimeNav into the TimeScale options object? */
+ // Set Max Rows
+ var marker_height_min = 0;
+ try {
+ marker_height_min = parseInt(this.options.marker_height_min);
+ } catch(e) {
+ trace("Invalid value for marker_height_min option.");
+ marker_height_min = 30;
+ }
+ if (marker_height_min == 0) {
+ trace("marker_height_min option must not be zero.")
+ marker_height_min = 30;
+ }
+ this.max_rows = Math.round((this.options.height - this._el.timeaxis_background.offsetHeight - (this.options.marker_padding)) / marker_height_min);
+ if (this.max_rows < 1) {
+ this.max_rows = 1;
+ }
+ return new TL.TimeScale(this.config, {
+ display_width: this._el.container.offsetWidth,
+ screen_multiplier: this.options.scale_factor,
+ max_rows: this.max_rows
+
+ });
+ },
+
+ _updateTimeScale: function(new_scale) {
+ this.options.scale_factor = new_scale;
+ this._updateDrawTimeline();
+ },
+
+ zoomIn: function() { // move the the next "higher" scale factor
+ var new_scale = TL.Util.findNextGreater(this.options.zoom_sequence, this.options.scale_factor);
+ this.setZoomFactor(new_scale);
+ },
+
+ zoomOut: function() { // move the the next "lower" scale factor
+ var new_scale = TL.Util.findNextLesser(this.options.zoom_sequence, this.options.scale_factor);
+ this.setZoomFactor(new_scale);
+ },
+
+ setZoom: function(level) {
+ var zoom_factor = this.options.zoom_sequence[level];
+ if (typeof(zoom_factor) == 'number') {
+ this.setZoomFactor(zoom_factor);
+ } else {
+ console.warn("Invalid zoom level. Please use an index number between 0 and " + (this.options.zoom_sequence.length - 1));
+ }
+ },
+
+ setZoomFactor: function(factor) {
+ if (factor <= this.options.zoom_sequence[0]) {
+ this.fire("zoomtoggle", {zoom:"out", show:false});
+ } else {
+ this.fire("zoomtoggle", {zoom:"out", show:true});
+ }
+
+ if (factor >= this.options.zoom_sequence[this.options.zoom_sequence.length-1]) {
+ this.fire("zoomtoggle", {zoom:"in", show:false});
+ } else {
+ this.fire("zoomtoggle", {zoom:"in", show:true});
+ }
+
+ if (factor == 0) {
+ console.warn("Zoom factor must be greater than zero. Using 0.1");
+ factor = 0.1;
+ }
+ this.options.scale_factor = factor;
+ //this._updateDrawTimeline(true);
+ this.goToId(this.current_id, !this._updateDrawTimeline(true), true);
+ },
+
+ /* Groups
+ ================================================== */
+ _createGroups: function() {
+ this._groups = [];
+ var group_labels = this.timescale.getGroupLabels();
+
+ if (group_labels) {
+ this.options.has_groups = true;
+ for (var i = 0; i < group_labels.length; i++) {
+ this._createGroup(group_labels[i]);
+ }
+ }
+
+ },
+
+ _createGroup: function(group_label) {
+ var group = new TL.TimeGroup(group_label);
+ this._addGroup(group);
+ this._groups.push(group);
+ },
+
+ _addGroup:function(group) {
+ group.addTo(this._el.container);
+
+ },
+
+ _positionGroups: function() {
+ if (this.options.has_groups) {
+ var available_height = (this.options.height - this._el.timeaxis_background.offsetHeight ),
+ group_height = Math.floor((available_height /this.timescale.getNumberOfRows()) - this.options.marker_padding),
+ group_labels = this.timescale.getGroupLabels();
+
+ for (var i = 0, group_rows = 0; i < this._groups.length; i++) {
+ var group_y = Math.floor(group_rows * (group_height + this.options.marker_padding));
+ var group_hide = false;
+ if (group_y > (available_height- this.options.marker_padding)) {
+ group_hide = true;
+ }
+
+ this._groups[i].setRowPosition(group_y, this._calculated_row_height + this.options.marker_padding/2);
+ this._groups[i].setAlternateRowColor(TL.Util.isEven(i), group_hide);
+
+ group_rows += this._groups[i].data.rows; // account for groups spanning multiple rows
+ }
+ }
+ },
+
+ /* Markers
+ ================================================== */
+ _addMarker:function(marker) {
+ marker.addTo(this._el.marker_item_container);
+ marker.on('markerclick', this._onMarkerClick, this);
+ marker.on('added', this._onMarkerAdded, this);
+ },
+
+ _createMarker: function(data, n) {
+ var marker = new TL.TimeMarker(data, this.options);
+ this._addMarker(marker);
+ if(n < 0) {
+ this._markers.push(marker);
+ } else {
+ this._markers.splice(n, 0, marker);
+ }
+ },
+
+ _createMarkers: function(array) {
+ for (var i = 0; i < array.length; i++) {
+ this._createMarker(array[i], -1);
+ }
+ },
+
+ _removeMarker: function(marker) {
+ marker.removeFrom(this._el.marker_item_container);
+ //marker.off('added', this._onMarkerRemoved, this);
+ },
+
+ _destroyMarker: function(n) {
+ this._removeMarker(this._markers[n]);
+ this._markers.splice(n, 1);
+ },
+
+ _positionMarkers: function(fast) {
+ // POSITION X
+ for (var i = 0; i < this._markers.length; i++) {
+ var pos = this.timescale.getPositionInfo(i);
+ if (fast) {
+ this._markers[i].setClass("tl-timemarker tl-timemarker-fast");
+ } else {
+ this._markers[i].setClass("tl-timemarker");
+ }
+ this._markers[i].setPosition({left:pos.start});
+ this._markers[i].setWidth(pos.width);
+ };
+
+ },
+
+ _calculateMarkerHeight: function(h) {
+ return ((h /this.timescale.getNumberOfRows()) - this.options.marker_padding);
+ },
+
+ _calculateRowHeight: function(h) {
+ return (h /this.timescale.getNumberOfRows());
+ },
+
+ _calculateAvailableHeight: function() {
+ return (this.options.height - this._el.timeaxis_background.offsetHeight - (this.options.marker_padding));
+ },
+
+ _calculateMinimumTimeNavHeight: function() {
+ return (this.timescale.getNumberOfRows() * this.options.marker_height_min) + this._el.timeaxis_background.offsetHeight + (this.options.marker_padding);
+
+ },
+
+ getMinimumHeight: function() {
+ return this._calculateMinimumTimeNavHeight();
+ },
+
+ _assignRowsToMarkers: function() {
+ var available_height = this._calculateAvailableHeight(),
+ marker_height = this._calculateMarkerHeight(available_height);
+
+
+ this._positionGroups();
+
+ this._calculated_row_height = this._calculateRowHeight(available_height);
+
+ for (var i = 0; i < this._markers.length; i++) {
+
+ // Set Height
+ this._markers[i].setHeight(marker_height);
+
+ //Position by Row
+ var row = this.timescale.getPositionInfo(i).row;
+
+ var marker_y = Math.floor(row * (marker_height + this.options.marker_padding)) + this.options.marker_padding;
+
+ var remainder_height = available_height - marker_y + this.options.marker_padding;
+ this._markers[i].setRowPosition(marker_y, remainder_height);
+ };
+
+ },
+
+ _resetMarkersActive: function() {
+ for (var i = 0; i < this._markers.length; i++) {
+ this._markers[i].setActive(false);
+ };
+ },
+
+ _findMarkerIndex: function(n) {
+ var _n = -1;
+ if (typeof n == 'string' || n instanceof String) {
+ _n = TL.Util.findArrayNumberByUniqueID(n, this._markers, "unique_id", _n);
+ }
+ return _n;
+ },
+
+ /* ERAS
+ ================================================== */
+ _createEras: function(array) {
+ for (var i = 0; i < array.length; i++) {
+ this._createEra(array[i], -1);
+ }
+ },
+
+ _createEra: function(data, n) {
+ var era = new TL.TimeEra(data, this.options);
+ this._addEra(era);
+ if(n < 0) {
+ this._eras.push(era);
+ } else {
+ this._eras.splice(n, 0, era);
+ }
+ },
+
+ _addEra:function(era) {
+ era.addTo(this._el.marker_item_container);
+ era.on('added', this._onEraAdded, this);
+ },
+
+ _removeEra: function(era) {
+ era.removeFrom(this._el.marker_item_container);
+ //marker.off('added', this._onMarkerRemoved, this);
+ },
+
+ _destroyEra: function(n) {
+ this._removeEra(this._eras[n]);
+ this._eras.splice(n, 1);
+ },
+
+ _positionEras: function(fast) {
+
+ var era_color = 0;
+ // POSITION X
+ for (var i = 0; i < this._eras.length; i++) {
+ var pos = {
+ start:0,
+ end:0,
+ width:0
+ };
+
+ pos.start = this.timescale.getPosition(this._eras[i].data.start_date.getTime());
+ pos.end = this.timescale.getPosition(this._eras[i].data.end_date.getTime());
+ pos.width = pos.end - pos.start;
+
+ if (fast) {
+ this._eras[i].setClass("tl-timeera tl-timeera-fast");
+ } else {
+ this._eras[i].setClass("tl-timeera");
+ }
+ this._eras[i].setPosition({left:pos.start});
+ this._eras[i].setWidth(pos.width);
+
+ era_color++;
+ if (era_color > 5) {
+ era_color = 0;
+ }
+ this._eras[i].setColor(era_color);
+ };
+
+ },
+
+ /* Public
+ ================================================== */
+
+ // Create a marker
+ createMarker: function(d, n) {
+ this._createMarker(d, n);
+ },
+
+ // Create many markers from an array
+ createMarkers: function(array) {
+ this._createMarkers(array);
+ },
+
+ // Destroy marker by index
+ destroyMarker: function(n) {
+ this._destroyMarker(n);
+ },
+
+ // Destroy marker by id
+ destroyMarkerId: function(id) {
+ this.destroyMarker(this._findMarkerIndex(id));
+ },
+
+ /* Navigation
+ ================================================== */
+ goTo: function(n, fast, css_animation) {
+ var self = this,
+ _ease = this.options.ease,
+ _duration = this.options.duration,
+ _n = (n < 0) ? 0 : n;
+
+ // Set Marker active state
+ this._resetMarkersActive();
+ if(n >= 0 && n < this._markers.length) {
+ this._markers[n].setActive(true);
+ }
+ // Stop animation
+ if (this.animator) {
+ this.animator.stop();
+ }
+
+ if (fast) {
+ this._el.slider.className = "tl-timenav-slider";
+ this._el.slider.style.left = -this._markers[_n].getLeft() + (this.options.width/2) + "px";
+ } else {
+ if (css_animation) {
+ this._el.slider.className = "tl-timenav-slider tl-timenav-slider-animate";
+ this.animate_css = true;
+ this._el.slider.style.left = -this._markers[_n].getLeft() + (this.options.width/2) + "px";
+ } else {
+ this._el.slider.className = "tl-timenav-slider";
+ this.animator = TL.Animate(this._el.slider, {
+ left: -this._markers[_n].getLeft() + (this.options.width/2) + "px",
+ duration: _duration,
+ easing: _ease
+ });
+ }
+ }
+
+ if(n >= 0 && n < this._markers.length) {
+ this.current_id = this._markers[n].data.unique_id;
+ } else {
+ this.current_id = '';
+ }
+ },
+
+ goToId: function(id, fast, css_animation) {
+ this.goTo(this._findMarkerIndex(id), fast, css_animation);
+ },
+
+ /* Events
+ ================================================== */
+ _onLoaded: function() {
+ this.ready = true;
+ this.fire("loaded", this.config);
+ },
+
+ _onMarkerAdded: function(e) {
+ this.fire("dateAdded", this.config);
+ },
+
+ _onEraAdded: function(e) {
+ this.fire("eraAdded", this.config);
+ },
+
+ _onMarkerRemoved: function(e) {
+ this.fire("dateRemoved", this.config);
+ },
+
+ _onMarkerClick: function(e) {
+ // Go to the clicked marker
+ this.goToId(e.unique_id);
+ this.fire("change", {unique_id: e.unique_id});
+ },
+
+ _onMouseScroll: function(e) {
+
+ var delta = 0,
+ scroll_to = 0,
+ constraint = {
+ right: -(this.timescale.getPixelWidth() - (this.options.width/2)),
+ left: this.options.width/2
+ };
+ if (!e) {
+ e = window.event;
+ }
+ if (e.originalEvent) {
+ e = e.originalEvent;
+ }
+
+ // Webkit and browsers able to differntiate between up/down and left/right scrolling
+ if (typeof e.wheelDeltaX != 'undefined' ) {
+ delta = e.wheelDeltaY/6;
+ if (Math.abs(e.wheelDeltaX) > Math.abs(e.wheelDeltaY)) {
+ delta = e.wheelDeltaX/6;
+ } else {
+ //delta = e.wheelDeltaY/6;
+ delta = 0;
+ }
+ }
+ if (delta) {
+ if (e.preventDefault) {
+ e.preventDefault();
+ }
+ e.returnValue = false;
+ }
+ // Stop from scrolling too far
+ scroll_to = parseInt(this._el.slider.style.left.replace("px", "")) + delta;
+
+
+ if (scroll_to > constraint.left) {
+ scroll_to = constraint.left;
+ } else if (scroll_to < constraint.right) {
+ scroll_to = constraint.right;
+ }
+
+ if (this.animate_css) {
+ this._el.slider.className = "tl-timenav-slider";
+ this.animate_css = false;
+ }
+
+ this._el.slider.style.left = scroll_to + "px";
+
+ },
+
+ _onDragMove: function(e) {
+ if (this.animate_css) {
+ this._el.slider.className = "tl-timenav-slider";
+ this.animate_css = false;
+ }
+
+ },
+
+ /* Private Methods
+ ================================================== */
+ // Update Display
+ _updateDisplay: function(width, height, animate) {
+
+ if (width) {
+ this.options.width = width;
+ }
+ if (height && height != this.options.height) {
+ this.options.height = height;
+ this.timescale = this._getTimeScale();
+ }
+
+ // Size Markers
+ this._assignRowsToMarkers();
+
+ // Size swipable area
+ this._el.slider_background.style.width = this.timescale.getPixelWidth() + this.options.width + "px";
+ this._el.slider_background.style.left = -(this.options.width/2) + "px";
+ this._el.slider.style.width = this.timescale.getPixelWidth() + this.options.width + "px";
+
+ // Update Swipable constraint
+ this._swipable.updateConstraint({top: false,bottom: false,left: (this.options.width/2),right: -(this.timescale.getPixelWidth() - (this.options.width/2))});
+
+ // Go to the current slide
+ this.goToId(this.current_id, true);
+ },
+
+ _drawTimeline: function(fast) {
+ this.timescale = this._getTimeScale();
+ this.timeaxis.drawTicks(this.timescale, this.options.optimal_tick_width);
+ this._positionMarkers(fast);
+ this._assignRowsToMarkers();
+ this._createGroups();
+ this._positionGroups();
+
+ if (this.has_eras) {
+
+ this._positionEras(fast);
+ }
+ },
+
+ _updateDrawTimeline: function(check_update) {
+ var do_update = false;
+
+ // Check to see if redraw is needed
+ if (check_update) {
+ /* keep this aligned with _getTimeScale or reduce code duplication */
+ var temp_timescale = new TL.TimeScale(this.config, {
+ display_width: this._el.container.offsetWidth,
+ screen_multiplier: this.options.scale_factor,
+ max_rows: this.max_rows
+
+ });
+
+ if (this.timescale.getMajorScale() == temp_timescale.getMajorScale()
+ && this.timescale.getMinorScale() == temp_timescale.getMinorScale()) {
+ do_update = true;
+ }
+ } else {
+ do_update = true;
+ }
+
+ // Perform update or redraw
+ if (do_update) {
+ this.timescale = this._getTimeScale();
+ this.timeaxis.positionTicks(this.timescale, this.options.optimal_tick_width);
+ this._positionMarkers();
+ this._assignRowsToMarkers();
+ this._positionGroups();
+ if (this.has_eras) {
+ this._positionEras();
+ }
+ this._updateDisplay();
+ } else {
+ this._drawTimeline(true);
+ }
+
+ return do_update;
+
+ },
+
+
+ /* Init
+ ================================================== */
+ _initLayout: function () {
+ // Create Layout
+ this._el.attribution = TL.Dom.create('div', 'tl-attribution', this._el.container);
+ this._el.line = TL.Dom.create('div', 'tl-timenav-line', this._el.container);
+ this._el.slider = TL.Dom.create('div', 'tl-timenav-slider', this._el.container);
+ this._el.slider_background = TL.Dom.create('div', 'tl-timenav-slider-background', this._el.slider);
+ this._el.marker_container_mask = TL.Dom.create('div', 'tl-timenav-container-mask', this._el.slider);
+ this._el.marker_container = TL.Dom.create('div', 'tl-timenav-container', this._el.marker_container_mask);
+ this._el.marker_item_container = TL.Dom.create('div', 'tl-timenav-item-container', this._el.marker_container);
+ this._el.timeaxis = TL.Dom.create('div', 'tl-timeaxis', this._el.slider);
+ this._el.timeaxis_background = TL.Dom.create('div', 'tl-timeaxis-background', this._el.container);
+
+
+ // Knight Lab Logo
+ this._el.attribution.innerHTML = "<a href='http://timeline.knightlab.com' target='_blank'><span class='tl-knightlab-logo'></span>Timeline JS</a>"
+
+ // Time Axis
+ this.timeaxis = new TL.TimeAxis(this._el.timeaxis, this.options);
+
+ // Swipable
+ this._swipable = new TL.Swipable(this._el.slider_background, this._el.slider, {
+ enable: {x:true, y:false},
+ constraint: {top: false,bottom: false,left: (this.options.width/2),right: false},
+ snap: false
+ });
+ this._swipable.enable();
+
+ },
+
+ _initEvents: function () {
+ // Drag Events
+ this._swipable.on('dragmove', this._onDragMove, this);
+
+ // Scroll Events
+ TL.DomEvent.addListener(this._el.container, 'mousewheel', this._onMouseScroll, this);
+ TL.DomEvent.addListener(this._el.container, 'DOMMouseScroll', this._onMouseScroll, this);
+ },
+
+ _initData: function() {
+ // Create Markers and then add them
+ this._createMarkers(this.config.events);
+
+ if (this.config.eras) {
+ this.has_eras = true;
+ this._createEras(this.config.eras);
+ }
+
+ this._drawTimeline();
+
+ }
+
+
+});
+
+
+/* **********************************************
+ Begin TL.TimeMarker.js
+********************************************** */
+
+/* TL.TimeMarker
+
+================================================== */
+
+TL.TimeMarker = TL.Class.extend({
+
+ includes: [TL.Events, TL.DomMixins],
+
+ _el: {},
+
+ /* Constructor
+ ================================================== */
+ initialize: function(data, options) {
+
+ // DOM Elements
+ this._el = {
+ container: {},
+ content_container: {},
+ media_container: {},
+ timespan: {},
+ line_left: {},
+ line_right: {},
+ content: {},
+ text: {},
+ media: {},
+ };
+
+ // Components
+ this._text = {};
+
+ // State
+ this._state = {
+ loaded: false
+ };
+
+
+ // Data
+ this.data = {
+ unique_id: "",
+ background: null,
+ date: {
+ year: 0,
+ month: 0,
+ day: 0,
+ hour: 0,
+ minute: 0,
+ second: 0,
+ millisecond: 0,
+ thumbnail: "",
+ format: ""
+ },
+ text: {
+ headline: "",
+ text: ""
+ },
+ media: null
+ };
+
+ // Options
+ this.options = {
+ duration: 1000,
+ ease: TL.Ease.easeInSpline,
+ width: 600,
+ height: 600,
+ marker_width_min: 100 // Minimum Marker Width
+ };
+
+ // Actively Displaying
+ this.active = false;
+
+ // Animation Object
+ this.animator = {};
+
+ // End date
+ this.has_end_date = false;
+
+ // Merge Data and Options
+ TL.Util.mergeData(this.options, options);
+ TL.Util.mergeData(this.data, data);
+
+ this._initLayout();
+ this._initEvents();
+
+
+ },
+
+ /* Adding, Hiding, Showing etc
+ ================================================== */
+ show: function() {
+
+ },
+
+ hide: function() {
+
+ },
+
+ setActive: function(is_active) {
+ this.active = is_active;
+
+ if (this.active && this.has_end_date) {
+ this._el.container.className = 'tl-timemarker tl-timemarker-with-end tl-timemarker-active';
+ } else if (this.active){
+ this._el.container.className = 'tl-timemarker tl-timemarker-active';
+ } else if (this.has_end_date){
+ this._el.container.className = 'tl-timemarker tl-timemarker-with-end';
+ } else {
+ this._el.container.className = 'tl-timemarker';
+ }
+ },
+
+ addTo: function(container) {
+ container.appendChild(this._el.container);
+ },
+
+ removeFrom: function(container) {
+ container.removeChild(this._el.container);
+ },
+
+ updateDisplay: function(w, h) {
+ this._updateDisplay(w, h);
+ },
+
+ loadMedia: function() {
+
+ if (this._media && !this._state.loaded) {
+ this._media.loadMedia();
+ this._state.loaded = true;
+ }
+ },
+
+ stopMedia: function() {
+ if (this._media && this._state.loaded) {
+ this._media.stopMedia();
+ }
+ },
+
+ getLeft: function() {
+ return this._el.container.style.left.slice(0, -2);
+ },
+
+ getTime: function() { // TODO does this need to know about the end date?
+ return this.data.start_date.getTime();
+ },
+
+ getEndTime: function() {
+
+ if (this.data.end_date) {
+ return this.data.end_date.getTime();
+ } else {
+ return false;
+ }
+ },
+
+ setHeight: function(h) {
+ var text_line_height = 12,
+ text_lines = 1;
+
+ this._el.content_container.style.height = h + "px";
+ this._el.timespan_content.style.height = h + "px";
+ // Handle Line height for better display of text
+ if (h <= 30) {
+ this._el.content.className = "tl-timemarker-content tl-timemarker-content-small";
+ } else {
+ this._el.content.className = "tl-timemarker-content";
+ }
+
+ if (h <= 56) {
+ TL.DomUtil.addClass(this._el.content_container, "tl-timemarker-content-container-small");
+ } else {
+ TL.DomUtil.removeClass(this._el.content_container, "tl-timemarker-content-container-small");
+ }
+
+ // Handle number of lines visible vertically
+
+ if (TL.Browser.webkit) {
+ text_lines = Math.floor(h / (text_line_height + 2));
+ if (text_lines < 1) {
+ text_lines = 1;
+ }
+ this._text.className = "tl-headline";
+ this._text.style.webkitLineClamp = text_lines;
+ } else {
+ text_lines = h / text_line_height;
+ if (text_lines > 1) {
+ this._text.className = "tl-headline tl-headline-fadeout";
+ } else {
+ this._text.className = "tl-headline";
+ }
+ this._text.style.height = (text_lines * text_line_height) + "px";
+ }
+
+ },
+
+ setWidth: function(w) {
+ if (this.data.end_date) {
+ this._el.container.style.width = w + "px";
+
+ if (w > this.options.marker_width_min) {
+ this._el.content_container.style.width = w + "px";
+ this._el.content_container.className = "tl-timemarker-content-container tl-timemarker-content-container-long";
+ } else {
+ this._el.content_container.style.width = this.options.marker_width_min + "px";
+ this._el.content_container.className = "tl-timemarker-content-container";
+ }
+ }
+
+ },
+
+ setClass: function(n) {
+ this._el.container.className = n;
+ },
+
+ setRowPosition: function(n, remainder) {
+ this.setPosition({top:n});
+ this._el.timespan.style.height = remainder + "px";
+
+ if (remainder < 56) {
+ //TL.DomUtil.removeClass(this._el.content_container, "tl-timemarker-content-container-small");
+ }
+ },
+
+ /* Events
+ ================================================== */
+ _onMarkerClick: function(e) {
+ this.fire("markerclick", {unique_id:this.data.unique_id});
+ },
+
+ /* Private Methods
+ ================================================== */
+ _initLayout: function () {
+ //trace(this.data)
+ // Create Layout
+ this._el.container = TL.Dom.create("div", "tl-timemarker");
+ if (this.data.unique_id) {
+ this._el.container.id = this.data.unique_id + "-marker";
+ }
+
+ if (this.data.end_date) {
+ this.has_end_date = true;
+ this._el.container.className = 'tl-timemarker tl-timemarker-with-end';
+ }
+
+ this._el.timespan = TL.Dom.create("div", "tl-timemarker-timespan", this._el.container);
+ this._el.timespan_content = TL.Dom.create("div", "tl-timemarker-timespan-content", this._el.timespan);
+ this._el.content_container = TL.Dom.create("div", "tl-timemarker-content-container", this._el.container);
+
+ this._el.content = TL.Dom.create("div", "tl-timemarker-content", this._el.content_container);
+
+ this._el.line_left = TL.Dom.create("div", "tl-timemarker-line-left", this._el.timespan);
+ this._el.line_right = TL.Dom.create("div", "tl-timemarker-line-right", this._el.timespan);
+
+ // Thumbnail or Icon
+ if (this.data.media) {
+ this._el.media_container = TL.Dom.create("div", "tl-timemarker-media-container", this._el.content);
+ // ugh. needs an overhaul
+ var mtd = {url: this.data.media.thumbnail};
+ var thumbnail_media_type = (this.data.media.thumbnail) ? TL.MediaType(mtd, true) : null;
+ if (thumbnail_media_type) {
+ var thumbnail_media = new thumbnail_media_type.cls(mtd);
+ thumbnail_media.on("loaded", function() {
+ this._el.media = TL.Dom.create("img", "tl-timemarker-media", this._el.media_container);
+ this._el.media.src = thumbnail_media.getImageURL();
+ }.bind(this));
+ thumbnail_media.loadMedia();
+ } else {
+ var media_type = TL.MediaType(this.data.media).type;
+ this._el.media = TL.Dom.create("span", "tl-icon-" + media_type, this._el.media_container);
+
+ }
+
+ }
+
+
+ // Text
+ this._el.text = TL.Dom.create("div", "tl-timemarker-text", this._el.content);
+ this._text = TL.Dom.create("h2", "tl-headline", this._el.text);
+ if (this.data.text.headline && this.data.text.headline != "") {
+ this._text.innerHTML = TL.Util.unlinkify(this.data.text.headline);
+ } else if (this.data.text.text && this.data.text.text != "") {
+ this._text.innerHTML = TL.Util.unlinkify(this.data.text.text);
+ } else if (this.data.media && this.data.media.caption && this.data.media.caption != "") {
+ this._text.innerHTML = TL.Util.unlinkify(this.data.media.caption);
+ }
+
+
+
+ // Fire event that the slide is loaded
+ this.onLoaded();
+
+ },
+
+ _initEvents: function() {
+ TL.DomEvent.addListener(this._el.container, 'click', this._onMarkerClick, this);
+ },
+
+ // Update Display
+ _updateDisplay: function(width, height, layout) {
+
+ if (width) {
+ this.options.width = width;
+ }
+
+ if (height) {
+ this.options.height = height;
+ }
+
+ }
+
+});
+
+
+/* **********************************************
+ Begin TL.TimeEra.js
+********************************************** */
+
+/* TL.TimeMarker
+
+================================================== */
+
+TL.TimeEra = TL.Class.extend({
+
+ includes: [TL.Events, TL.DomMixins],
+
+ _el: {},
+
+ /* Constructor
+ ================================================== */
+ initialize: function(data, options) {
+
+ // DOM Elements
+ this._el = {
+ container: {},
+ background: {},
+ content_container: {},
+ content: {},
+ text: {}
+ };
+
+ // Components
+ this._text = {};
+
+ // State
+ this._state = {
+ loaded: false
+ };
+
+
+ // Data
+ this.data = {
+ unique_id: "",
+ date: {
+ year: 0,
+ month: 0,
+ day: 0,
+ hour: 0,
+ minute: 0,
+ second: 0,
+ millisecond: 0,
+ thumbnail: "",
+ format: ""
+ },
+ text: {
+ headline: "",
+ text: ""
+ }
+ };
+
+ // Options
+ this.options = {
+ duration: 1000,
+ ease: TL.Ease.easeInSpline,
+ width: 600,
+ height: 600,
+ marker_width_min: 100 // Minimum Marker Width
+ };
+
+ // Actively Displaying
+ this.active = false;
+
+ // Animation Object
+ this.animator = {};
+
+ // End date
+ this.has_end_date = false;
+
+ // Merge Data and Options
+ TL.Util.mergeData(this.options, options);
+ TL.Util.mergeData(this.data, data);
+
+ this._initLayout();
+ this._initEvents();
+
+
+ },
+
+ /* Adding, Hiding, Showing etc
+ ================================================== */
+ show: function() {
+
+ },
+
+ hide: function() {
+
+ },
+
+ setActive: function(is_active) {
+
+ },
+
+ addTo: function(container) {
+ container.appendChild(this._el.container);
+ },
+
+ removeFrom: function(container) {
+ container.removeChild(this._el.container);
+ },
+
+ updateDisplay: function(w, h) {
+ this._updateDisplay(w, h);
+ },
+
+ getLeft: function() {
+ return this._el.container.style.left.slice(0, -2);
+ },
+
+ getTime: function() { // TODO does this need to know about the end date?
+ return this.data.start_date.getTime();
+ },
+
+ getEndTime: function() {
+
+ if (this.data.end_date) {
+ return this.data.end_date.getTime();
+ } else {
+ return false;
+ }
+ },
+
+ setHeight: function(h) {
+ var text_line_height = 12,
+ text_lines = 1;
+
+ this._el.content_container.style.height = h + "px";
+ this._el.content.className = "tl-timeera-content";
+
+ // Handle number of lines visible vertically
+
+ if (TL.Browser.webkit) {
+ text_lines = Math.floor(h / (text_line_height + 2));
+ if (text_lines < 1) {
+ text_lines = 1;
+ }
+ this._text.className = "tl-headline";
+ this._text.style.webkitLineClamp = text_lines;
+ } else {
+ text_lines = h / text_line_height;
+ if (text_lines > 1) {
+ this._text.className = "tl-headline tl-headline-fadeout";
+ } else {
+ this._text.className = "tl-headline";
+ }
+ this._text.style.height = (text_lines * text_line_height) + "px";
+ }
+
+ },
+
+ setWidth: function(w) {
+ if (this.data.end_date) {
+ this._el.container.style.width = w + "px";
+
+ if (w > this.options.marker_width_min) {
+ this._el.content_container.style.width = w + "px";
+ this._el.content_container.className = "tl-timeera-content-container tl-timeera-content-container-long";
+ } else {
+ this._el.content_container.style.width = this.options.marker_width_min + "px";
+ this._el.content_container.className = "tl-timeera-content-container";
+ }
+ }
+
+ },
+
+ setClass: function(n) {
+ this._el.container.className = n;
+ },
+
+ setRowPosition: function(n, remainder) {
+ this.setPosition({top:n});
+
+ if (remainder < 56) {
+ //TL.DomUtil.removeClass(this._el.content_container, "tl-timeera-content-container-small");
+ }
+ },
+
+ setColor: function(color_num) {
+ this._el.container.className = 'tl-timeera tl-timeera-color' + color_num;
+ },
+
+ /* Events
+ ================================================== */
+
+
+ /* Private Methods
+ ================================================== */
+ _initLayout: function () {
+ //trace(this.data)
+ // Create Layout
+ this._el.container = TL.Dom.create("div", "tl-timeera");
+ if (this.data.unique_id) {
+ this._el.container.id = this.data.unique_id + "-era";
+ }
+
+ if (this.data.end_date) {
+ this.has_end_date = true;
+ this._el.container.className = 'tl-timeera tl-timeera-with-end';
+ }
+
+ this._el.content_container = TL.Dom.create("div", "tl-timeera-content-container", this._el.container);
+
+ this._el.background = TL.Dom.create("div", "tl-timeera-background", this._el.content_container);
+
+ this._el.content = TL.Dom.create("div", "tl-timeera-content", this._el.content_container);
+
+
+
+ // Text
+ this._el.text = TL.Dom.create("div", "tl-timeera-text", this._el.content);
+ this._text = TL.Dom.create("h2", "tl-headline", this._el.text);
+ if (this.data.text.headline && this.data.text.headline != "") {
+ this._text.innerHTML = TL.Util.unlinkify(this.data.text.headline);
+ }
+
+
+
+ // Fire event that the slide is loaded
+ this.onLoaded();
+
+ },
+
+ _initEvents: function() {
+
+ },
+
+ // Update Display
+ _updateDisplay: function(width, height, layout) {
+
+ if (width) {
+ this.options.width = width;
+ }
+
+ if (height) {
+ this.options.height = height;
+ }
+
+ }
+
+});
+
+
+/* **********************************************
+ Begin TL.TimeGroup.js
+********************************************** */
+
+/* TL.TimeGroup
+
+================================================== */
+
+TL.TimeGroup = TL.Class.extend({
+
+ includes: [TL.Events, TL.DomMixins],
+
+ _el: {},
+
+ /* Constructor
+ ================================================== */
+ initialize: function(data) {
+
+ // DOM ELEMENTS
+ this._el = {
+ parent: {},
+ container: {},
+ message: {}
+ };
+
+ //Options
+ this.options = {
+ width: 600,
+ height: 600
+ };
+
+ // Data
+ this.data = {
+ label: "",
+ rows: 1
+ };
+
+
+ this._el.container = TL.Dom.create("div", "tl-timegroup");
+
+ // Merge Data
+ TL.Util.mergeData(this.data, data);
+
+ // Animation
+ this.animator = {};
+
+
+ this._initLayout();
+ this._initEvents();
+ },
+
+ /* Public
+ ================================================== */
+
+
+
+ /* Update Display
+ ================================================== */
+ updateDisplay: function(w, h) {
+
+ },
+
+ setRowPosition: function(n, h) {
+ // trace(n);
+ // trace(this._el.container)
+ this.options.height = h * this.data.rows;
+ this.setPosition({top:n});
+ this._el.container.style.height = this.options.height + "px";
+
+ },
+
+ setAlternateRowColor: function(alternate, hide) {
+ var class_name = "tl-timegroup";
+ if (alternate) {
+ class_name += " tl-timegroup-alternate";
+ }
+ if (hide) {
+ class_name += " tl-timegroup-hidden";
+ }
+ this._el.container.className = class_name;
+ },
+
+ /* Events
+ ================================================== */
+
+
+ _onMouseClick: function() {
+ this.fire("clicked", this.options);
+ },
+
+
+ /* Private Methods
+ ================================================== */
+ _initLayout: function () {
+
+ // Create Layout
+ this._el.message = TL.Dom.create("div", "tl-timegroup-message", this._el.container);
+ this._el.message.innerHTML = this.data.label;
+
+
+ },
+
+ _initEvents: function () {
+ TL.DomEvent.addListener(this._el.container, 'click', this._onMouseClick, this);
+ },
+
+ // Update Display
+ _updateDisplay: function(width, height, animate) {
+
+ }
+
+});
+
+/* **********************************************
+ Begin TL.TimeScale.js
+********************************************** */
+
+/* TL.TimeScale
+ Strategies for laying out the timenav
+ make a new one if the slides change
+
+ TODOS: deal with clustering
+================================================== */
+TL.TimeScale = TL.Class.extend({
+
+ initialize: function (timeline_config, options) {
+
+ var slides = timeline_config.events;
+ this._scale = timeline_config.scale;
+
+ options = TL.Util.mergeData({ // establish defaults
+ display_width: 500,
+ screen_multiplier: 3,
+ max_rows: null
+ }, options);
+
+ this._display_width = options.display_width;
+ this._screen_multiplier = options.screen_multiplier;
+ this._pixel_width = this._screen_multiplier * this._display_width;
+
+ this._group_labels = undefined;
+ this._positions = [];
+ this._pixels_per_milli = 0;
+
+ this._earliest = timeline_config.getEarliestDate().getTime();
+ this._latest = timeline_config.getLatestDate().getTime();
+ this._span_in_millis = this._latest - this._earliest;
+ if (this._span_in_millis <= 0) {
+ this._span_in_millis = this._computeDefaultSpan(timeline_config);
+ }
+ this._average = (this._span_in_millis)/slides.length;
+
+ this._pixels_per_milli = this.getPixelWidth() / this._span_in_millis;
+
+ this._axis_helper = TL.AxisHelper.getBestHelper(this);
+
+ this._scaled_padding = (1/this.getPixelsPerTick()) * (this._display_width/2)
+ this._computePositionInfo(slides, options.max_rows);
+ },
+
+ _computeDefaultSpan: function(timeline_config) {
+ // this gets called when all events are at the same instant,
+ // or maybe when the span_in_millis is > 0 but still below a desired threshold
+ // TODO: does this need smarts about eras?
+ if (timeline_config.scale == 'human') {
+ var formats = {}
+ for (var i = 0; i < timeline_config.events.length; i++) {
+ var fmt = timeline_config.events[i].start_date.findBestFormat();
+ formats[fmt] = (formats[fmt]) ? formats[fmt] + 1 : 1;
+ };
+
+ for (var i = TL.Date.SCALES.length - 1; i >= 0; i--) {
+ if (formats.hasOwnProperty(TL.Date.SCALES[i][0])) {
+ var scale = TL.Date.SCALES[TL.Date.SCALES.length - 1]; // default
+ if (TL.Date.SCALES[i+1]) {
+ scale = TL.Date.SCALES[i+1]; // one larger than the largest in our data
+ }
+ return scale[1]
+ }
+ };
+ return 365 * 24 * 60 * 60 * 1000; // default to a year?
+ }
+
+ return 200000; // what is the right handling for cosmo dates?
+ },
+ getGroupLabels: function() { /*
+ return an array of objects, one per group, in the order (top to bottom) that the groups are expected to appear. Each object will have two properties:
+ * label (the string as specified in one or more 'group' properties of events in the configuration)
+ * rows (the number of rows occupied by events associated with the label. )
+ */
+ return (this._group_labels || []);
+ },
+
+ getScale: function() {
+ return this._scale;
+ },
+
+ getNumberOfRows: function() {
+ return this._number_of_rows
+ },
+
+ getPixelWidth: function() {
+ return this._pixel_width;
+ },
+
+ getPosition: function(time_in_millis) {
+ // be careful using millis, as they won't scale to cosmological time.
+ // however, we're moving to make the arg to this whatever value
+ // comes from TL.Date.getTime() which could be made smart about that --
+ // so it may just be about the naming.
+ return ( time_in_millis - this._earliest ) * this._pixels_per_milli
+ },
+
+ getPositionInfo: function(idx) {
+ return this._positions[idx];
+ },
+
+ getPixelsPerTick: function() {
+ return this._axis_helper.getPixelsPerTick(this._pixels_per_milli);
+ },
+
+ getTicks: function() {
+ return {
+ major: this._axis_helper.getMajorTicks(this),
+ minor: this._axis_helper.getMinorTicks(this) }
+ },
+
+ getDateFromTime: function(t) {
+ if(this._scale == 'human') {
+ return new TL.Date(t);
+ } else if(this._scale == 'cosmological') {
+ return new TL.BigDate(new TL.BigYear(t));
+ }
+ throw new TL.Error("time_scale_scale_err", this._scale);
+ },
+
+ getMajorScale: function() {
+ return this._axis_helper.major.name;
+ },
+
+ getMinorScale: function() {
+ return this._axis_helper.minor.name;
+ },
+
+ _assessGroups: function(slides) {
+ var groups = [];
+ var empty_group = false;
+ for (var i = 0; i < slides.length; i++) {
+ if(slides[i].group) {
+ if(groups.indexOf(slides[i].group) < 0) {
+ groups.push(slides[i].group);
+ } else {
+ empty_group = true;
+ }
+ }
+ };
+ if (groups.length && empty_group) {
+ groups.push('');
+ }
+ return groups;
+ },
+
+ /* Compute the marker row positions, minimizing the number of
+ overlaps.
+
+ @positions = list of objects from this._positions
+ @rows_left = number of rows available (assume > 0)
+ */
+ _computeRowInfo: function(positions, rows_left) {
+ var lasts_in_row = [];
+ var n_overlaps = 0;
+
+ for (var i = 0; i < positions.length; i++) {
+ var pos_info = positions[i];
+ var overlaps = [];
+
+ // See if we can add item to an existing row without
+ // overlapping the previous item in that row
+ delete pos_info.row;
+
+ for (var j = 0; j < lasts_in_row.length; j++) {
+ overlaps.push(lasts_in_row[j].end - pos_info.start);
+ if(overlaps[j] <= 0) {
+ pos_info.row = j;
+ lasts_in_row[j] = pos_info;
+ break;
+ }
+ }
+
+ // If we couldn't add to an existing row without overlap...
+ if (typeof(pos_info.row) == 'undefined') {
+ if (rows_left === null) {
+ // Make a new row
+ pos_info.row = lasts_in_row.length;
+ lasts_in_row.push(pos_info);
+ } else if (rows_left > 0) {
+ // Make a new row
+ pos_info.row = lasts_in_row.length;
+ lasts_in_row.push(pos_info);
+ rows_left--;
+ } else {
+ // Add to existing row with minimum overlap.
+ var min_overlap = Math.min.apply(null, overlaps);
+ var idx = overlaps.indexOf(min_overlap);
+ pos_info.row = idx;
+ if (pos_info.end > lasts_in_row[idx].end) {
+ lasts_in_row[idx] = pos_info;
+ }
+ n_overlaps++;
+ }
+ }
+ }
+
+ return {n_rows: lasts_in_row.length, n_overlaps: n_overlaps};
+ },
+
+ /* Compute marker positions. If using groups, this._number_of_rows
+ will never be less than the number of groups.
+
+ @max_rows = total number of available rows
+ @default_marker_width should be in pixels
+ */
+ _computePositionInfo: function(slides, max_rows, default_marker_width) {
+ default_marker_width = default_marker_width || 100;
+
+ var groups = [];
+ var empty_group = false;
+
+ // Set start/end/width; enumerate groups
+ for (var i = 0; i < slides.length; i++) {
+ var pos_info = {
+ start: this.getPosition(slides[i].start_date.getTime())
+ };
+ this._positions.push(pos_info);
+
+ if (typeof(slides[i].end_date) != 'undefined') {
+ var end_pos = this.getPosition(slides[i].end_date.getTime());
+ pos_info.width = end_pos - pos_info.start;
+ if (pos_info.width > default_marker_width) {
+ pos_info.end = pos_info.start + pos_info.width;
+ } else {
+ pos_info.end = pos_info.start + default_marker_width;
+ }
+ } else {
+ pos_info.width = default_marker_width;
+ pos_info.end = pos_info.start + default_marker_width;
+ }
+
+ if(slides[i].group) {
+ if(groups.indexOf(slides[i].group) < 0) {
+ groups.push(slides[i].group);
+ }
+ } else {
+ empty_group = true;
+ }
+ }
+
+ if(!(groups.length)) {
+ var result = this._computeRowInfo(this._positions, max_rows);
+ this._number_of_rows = result.n_rows;
+ } else {
+ if(empty_group) {
+ groups.push("");
+ }
+
+ // Init group info
+ var group_info = [];
+
+ for(var i = 0; i < groups.length; i++) {
+ group_info[i] = {
+ label: groups[i],
+ idx: i,
+ positions: [],
+ n_rows: 1, // default
+ n_overlaps: 0
+ };
+ }
+
+ for(var i = 0; i < this._positions.length; i++) {
+ var pos_info = this._positions[i];
+
+ pos_info.group = groups.indexOf(slides[i].group || "");
+ pos_info.row = 0;
+
+ var gi = group_info[pos_info.group];
+ for(var j = gi.positions.length - 1; j >= 0; j--) {
+ if(gi.positions[j].end > pos_info.start) {
+ gi.n_overlaps++;
+ }
+ }
+
+ gi.positions.push(pos_info);
+ }
+
+ var n_rows = groups.length; // start with 1 row per group
+
+ while(true) {
+ // Count free rows available
+ var rows_left = Math.max(0, max_rows - n_rows);
+ if(!rows_left) {
+ break; // no free rows, nothing to do
+ }
+
+ // Sort by # overlaps, idx
+ group_info.sort(function(a, b) {
+ if(a.n_overlaps > b.n_overlaps) {
+ return -1;
+ } else if(a.n_overlaps < b.n_overlaps) {
+ return 1;
+ }
+ return a.idx - b.idx;
+ });
+ if(!group_info[0].n_overlaps) {
+ break; // no overlaps, nothing to do
+ }
+
+ // Distribute free rows among groups with overlaps
+ var n_rows = 0;
+ for(var i = 0; i < group_info.length; i++) {
+ var gi = group_info[i];
+
+ if(gi.n_overlaps && rows_left) {
+ var res = this._computeRowInfo(gi.positions, gi.n_rows + 1);
+ gi.n_rows = res.n_rows; // update group info
+ gi.n_overlaps = res.n_overlaps;
+ rows_left--; // update rows left
+ }
+
+ n_rows += gi.n_rows; // update rows used
+ }
+ }
+
+ // Set number of rows
+ this._number_of_rows = n_rows;
+
+ // Set group labels; offset row positions
+ this._group_labels = [];
+
+ group_info.sort(function(a, b) {return a.idx - b.idx; });
+
+ for(var i = 0, row_offset = 0; i < group_info.length; i++) {
+ this._group_labels.push({
+ label: group_info[i].label,
+ rows: group_info[i].n_rows
+ });
+
+ for(var j = 0; j < group_info[i].positions.length; j++) {
+ var pos_info = group_info[i].positions[j];
+ pos_info.row += row_offset;
+ }
+
+ row_offset += group_info[i].n_rows;
+ }
+ }
+
+ }
+});
+
+
+/* **********************************************
+ Begin TL.TimeAxis.js
+********************************************** */
+
+/* TL.TimeAxis
+ Display element for showing timescale ticks
+================================================== */
+
+TL.TimeAxis = TL.Class.extend({
+
+ includes: [TL.Events, TL.DomMixins, TL.I18NMixins],
+
+ _el: {},
+
+ /* Constructor
+ ================================================== */
+ initialize: function(elem, options) {
+ // DOM Elements
+ this._el = {
+ container: {},
+ content_container: {},
+ major: {},
+ minor: {},
+ };
+
+ // Components
+ this._text = {};
+
+ // State
+ this._state = {
+ loaded: false
+ };
+
+
+ // Data
+ this.data = {};
+
+ // Options
+ this.options = {
+ duration: 1000,
+ ease: TL.Ease.easeInSpline,
+ width: 600,
+ height: 600
+ };
+
+ // Actively Displaying
+ this.active = false;
+
+ // Animation Object
+ this.animator = {};
+
+ // Axis Helper
+ this.axis_helper = {};
+
+ // Minor tick dom element array
+ this.minor_ticks = [];
+
+ // Minor tick dom element array
+ this.major_ticks = [];
+
+ // Date Format Lookup, map TL.Date.SCALES names to...
+ this.dateformat_lookup = {
+ millisecond: 'time_milliseconds', // ...TL.Language.<code>.dateformats
+ second: 'time_short',
+ minute: 'time_no_seconds_short',
+ hour: 'time_no_minutes_short',
+ day: 'full_short',
+ month: 'month_short',
+ year: 'year',
+ decade: 'year',
+ century: 'year',
+ millennium: 'year',
+ age: 'compact', // ...TL.Language.<code>.bigdateformats
+ epoch: 'compact',
+ era: 'compact',
+ eon: 'compact',
+ eon2: 'compact'
+ }
+
+ // Main element
+ if (typeof elem === 'object') {
+ this._el.container = elem;
+ } else {
+ this._el.container = TL.Dom.get(elem);
+ }
+
+ // Merge Data and Options
+ TL.Util.mergeData(this.options, options);
+
+ this._initLayout();
+ this._initEvents();
+
+ },
+
+ /* Adding, Hiding, Showing etc
+ ================================================== */
+ show: function() {
+
+ },
+
+ hide: function() {
+
+ },
+
+ addTo: function(container) {
+ container.appendChild(this._el.container);
+ },
+
+ removeFrom: function(container) {
+ container.removeChild(this._el.container);
+ },
+
+ updateDisplay: function(w, h) {
+ this._updateDisplay(w, h);
+ },
+
+ getLeft: function() {
+ return this._el.container.style.left.slice(0, -2);
+ },
+
+ drawTicks: function(timescale, optimal_tick_width) {
+
+ var ticks = timescale.getTicks();
+
+ var controls = {
+ minor: {
+ el: this._el.minor,
+ dateformat: this.dateformat_lookup[ticks['minor'].name],
+ ts_ticks: ticks['minor'].ticks,
+ tick_elements: this.minor_ticks
+ },
+ major: {
+ el: this._el.major,
+ dateformat: this.dateformat_lookup[ticks['major'].name],
+ ts_ticks: ticks['major'].ticks,
+ tick_elements: this.major_ticks
+ }
+ }
+ // FADE OUT
+ this._el.major.className = "tl-timeaxis-major";
+ this._el.minor.className = "tl-timeaxis-minor";
+ this._el.major.style.opacity = 0;
+ this._el.minor.style.opacity = 0;
+
+ // CREATE MAJOR TICKS
+ this.major_ticks = this._createTickElements(
+ ticks['major'].ticks,
+ this._el.major,
+ this.dateformat_lookup[ticks['major'].name]
+ );
+
+ // CREATE MINOR TICKS
+ this.minor_ticks = this._createTickElements(
+ ticks['minor'].ticks,
+ this._el.minor,
+ this.dateformat_lookup[ticks['minor'].name],
+ ticks['major'].ticks
+ );
+
+ this.positionTicks(timescale, optimal_tick_width, true);
+
+ // FADE IN
+ this._el.major.className = "tl-timeaxis-major tl-animate-opacity tl-timeaxis-animate-opacity";
+ this._el.minor.className = "tl-timeaxis-minor tl-animate-opacity tl-timeaxis-animate-opacity";
+ this._el.major.style.opacity = 1;
+ this._el.minor.style.opacity = 1;
+ },
+
+ _createTickElements: function(ts_ticks,tick_element,dateformat,ticks_to_skip) {
+ tick_element.innerHTML = "";
+ var skip_times = {};
+
+ var yearZero = new Date(-1,13,-30);
+ skip_times[yearZero.getTime()] = true;
+
+ if (ticks_to_skip){
+ for (var i = 0; i < ticks_to_skip.length; i++) {
+ skip_times[ticks_to_skip[i].getTime()] = true;
+ }
+ }
+
+ var tick_elements = []
+ for (var i = 0; i < ts_ticks.length; i++) {
+ var ts_tick = ts_ticks[i];
+ if (!(ts_tick.getTime() in skip_times)) {
+ var tick = TL.Dom.create("div", "tl-timeaxis-tick", tick_element),
+ tick_text = TL.Dom.create("span", "tl-timeaxis-tick-text tl-animate-opacity", tick);
+
+ tick_text.innerHTML = ts_tick.getDisplayDate(this.getLanguage(), dateformat);
+
+ tick_elements.push({
+ tick:tick,
+ tick_text:tick_text,
+ display_date:ts_tick.getDisplayDate(this.getLanguage(), dateformat),
+ date:ts_tick
+ });
+ }
+ }
+ return tick_elements;
+ },
+
+ positionTicks: function(timescale, optimal_tick_width, no_animate) {
+
+ // Handle Animation
+ if (no_animate) {
+ this._el.major.className = "tl-timeaxis-major";
+ this._el.minor.className = "tl-timeaxis-minor";
+ } else {
+ this._el.major.className = "tl-timeaxis-major tl-timeaxis-animate";
+ this._el.minor.className = "tl-timeaxis-minor tl-timeaxis-animate";
+ }
+
+ this._positionTickArray(this.major_ticks, timescale, optimal_tick_width);
+ this._positionTickArray(this.minor_ticks, timescale, optimal_tick_width);
+
+ },
+
+ _positionTickArray: function(tick_array, timescale, optimal_tick_width) {
+ // Poition Ticks & Handle density of ticks
+ if (tick_array[1] && tick_array[0]) {
+ var distance = ( timescale.getPosition(tick_array[1].date.getMillisecond()) - timescale.getPosition(tick_array[0].date.getMillisecond()) ),
+ fraction_of_array = 1;
+
+
+ if (distance < optimal_tick_width) {
+ fraction_of_array = Math.round(optimal_tick_width/timescale.getPixelsPerTick());
+ }
+
+ var show = 1;
+
+ for (var i = 0; i < tick_array.length; i++) {
+
+ var tick = tick_array[i];
+
+ // Poition Ticks
+ tick.tick.style.left = timescale.getPosition(tick.date.getMillisecond()) + "px";
+ tick.tick_text.innerHTML = tick.display_date;
+
+ // Handle density of ticks
+ if (fraction_of_array > 1) {
+ if (show >= fraction_of_array) {
+ show = 1;
+ tick.tick_text.style.opacity = 1;
+ tick.tick.className = "tl-timeaxis-tick";
+ } else {
+ show++;
+ tick.tick_text.style.opacity = 0;
+ tick.tick.className = "tl-timeaxis-tick tl-timeaxis-tick-hidden";
+ }
+ } else {
+ tick.tick_text.style.opacity = 1;
+ tick.tick.className = "tl-timeaxis-tick";
+ }
+
+ };
+ }
+ },
+
+ /* Events
+ ================================================== */
+
+
+ /* Private Methods
+ ================================================== */
+ _initLayout: function () {
+ this._el.content_container = TL.Dom.create("div", "tl-timeaxis-content-container", this._el.container);
+ this._el.major = TL.Dom.create("div", "tl-timeaxis-major", this._el.content_container);
+ this._el.minor = TL.Dom.create("div", "tl-timeaxis-minor", this._el.content_container);
+
+ // Fire event that the slide is loaded
+ this.onLoaded();
+ },
+
+ _initEvents: function() {
+
+ },
+
+ // Update Display
+ _updateDisplay: function(width, height, layout) {
+
+ if (width) {
+ this.options.width = width;
+ }
+
+ if (height) {
+ this.options.height = height;
+ }
+
+ }
+
+});
+
+
+/* **********************************************
+ Begin TL.AxisHelper.js
+********************************************** */
+
+/* TL.AxisHelper
+ Strategies for laying out the timenav
+ markers and time axis
+ Intended as a private class -- probably only known to TimeScale
+================================================== */
+TL.AxisHelper = TL.Class.extend({
+ initialize: function (options) {
+ if (options) {
+ this.scale = options.scale;
+ this.minor = options.minor;
+ this.major = options.major;
+ } else {
+ throw new TL.Error("axis_helper_no_options_err")
+ }
+
+ },
+
+ getPixelsPerTick: function(pixels_per_milli) {
+ return pixels_per_milli * this.minor.factor;
+ },
+
+ getMajorTicks: function(timescale) {
+ return this._getTicks(timescale, this.major)
+ },
+
+ getMinorTicks: function(timescale) {
+ return this._getTicks(timescale, this.minor)
+ },
+
+ _getTicks: function(timescale, option) {
+
+ var factor_scale = timescale._scaled_padding * option.factor;
+ var first_tick_time = timescale._earliest - factor_scale;
+ var last_tick_time = timescale._latest + factor_scale;
+ var ticks = []
+ for (var i = first_tick_time; i < last_tick_time; i += option.factor) {
+ ticks.push(timescale.getDateFromTime(i).floor(option.name));
+ }
+
+ return {
+ name: option.name,
+ ticks: ticks
+ }
+
+ }
+
+});
+
+(function(cls){ // add some class-level behavior
+
+ var HELPERS = {};
+
+ var setHelpers = function(scale_type, scales) {
+ HELPERS[scale_type] = [];
+
+ for (var idx = 0; idx < scales.length - 1; idx++) {
+ var minor = scales[idx];
+ var major = scales[idx+1];
+ HELPERS[scale_type].push(new cls({
+ scale: minor[3],
+ minor: { name: minor[0], factor: minor[1]},
+ major: { name: major[0], factor: major[1]}
+ }));
+ }
+ };
+
+ setHelpers('human', TL.Date.SCALES);
+ setHelpers('cosmological', TL.BigDate.SCALES);
+
+ cls.HELPERS = HELPERS;
+
+ cls.getBestHelper = function(ts,optimal_tick_width) {
+ if (typeof(optimal_tick_width) != 'number' ) {
+ optimal_tick_width = 100;
+ }
+ var ts_scale = ts.getScale();
+ var helpers = HELPERS[ts_scale];
+
+ if (!helpers) {
+ throw new TL.Error("axis_helper_scale_err", ts_scale);
+ }
+
+ var prev = null;
+ for (var idx = 0; idx < helpers.length; idx++) {
+ var curr = helpers[idx];
+ var pixels_per_tick = curr.getPixelsPerTick(ts._pixels_per_milli);
+ if (pixels_per_tick > optimal_tick_width) {
+ if (prev == null) return curr;
+ var curr_dist = Math.abs(optimal_tick_width - pixels_per_tick);
+ var prev_dist = Math.abs(optimal_tick_width - pixels_per_tick);
+ if (curr_dist < prev_dist) {
+ return curr;
+ } else {
+ return prev;
+ }
+ }
+ prev = curr;
+ }
+ return helpers[helpers.length - 1]; // last resort
+ }
+})(TL.AxisHelper);
+
+
+/* **********************************************
+ Begin TL.Timeline.js
+********************************************** */
+
+/* TimelineJS
+Designed and built by Zach Wise at KnightLab
+
+This Source Code Form is subject to the terms of the Mozilla Public
+License, v. 2.0. If a copy of the MPL was not distributed with this
+file, You can obtain one at https://mozilla.org/MPL/2.0/.
+
+================================================== */
+/*
+TODO
+
+*/
+
+/* Required Files
+CodeKit Import
+https://incident57.com/codekit/
+================================================== */
+
+// CORE
+// @codekit-prepend "core/TL.js";
+// @codekit-prepend "core/TL.Error.js";
+// @codekit-prepend "core/TL.Util.js";
+// @codekit-prepend "data/TL.Data.js";
+// @codekit-prepend "core/TL.Class.js";
+// @codekit-prepend "core/TL.Events.js";
+// @codekit-prepend "core/TL.Browser.js";
+// @codekit-prepend "core/TL.Load.js";
+// @codekit-prepend "core/TL.TimelineConfig.js";
+// @codekit-prepend "core/TL.ConfigFactory.js";
+
+
+// LANGUAGE
+// @codekit-prepend "language/TL.Language.js";
+// @codekit-prepend "language/TL.I18NMixins.js";
+
+// ANIMATION
+// @codekit-prepend "animation/TL.Ease.js";
+// @codekit-prepend "animation/TL.Animate.js";
+
+// DOM
+// @codekit-prepend "dom/TL.Point.js";
+// @codekit-prepend "dom/TL.DomMixins.js";
+// @codekit-prepend "dom/TL.Dom.js";
+// @codekit-prepend "dom/TL.DomUtil.js";
+// @codekit-prepend "dom/TL.DomEvent.js";
+// @codekit-prepend "dom/TL.StyleSheet.js";
+
+// Date
+// @codekit-prepend "date/TL.Date.js";
+// @codekit-prepend "date/TL.DateUtil.js";
+
+// UI
+// @codekit-prepend "ui/TL.Draggable.js";
+// @codekit-prepend "ui/TL.Swipable.js";
+// @codekit-prepend "ui/TL.MenuBar.js";
+// @codekit-prepend "ui/TL.Message.js";
+
+// MEDIA
+// @codekit-prepend "media/TL.MediaType.js";
+// @codekit-prepend "media/TL.Media.js";
+
+// MEDIA TYPES
+// @codekit-prepend "media/types/TL.Media.Blockquote.js";
+// @codekit-prepend "media/types/TL.Media.DailyMotion.js";
+// @codekit-prepend "media/types/TL.Media.DocumentCloud.js";
+// @codekit-prepend "media/types/TL.Media.Flickr.js";
+// @codekit-prepend "media/types/TL.Media.GoogleDoc.js";
+// @codekit-prepend "media/types/TL.Media.GooglePlus.js";
+// @codekit-prepend "media/types/TL.Media.IFrame.js";
+// @codekit-prepend "media/types/TL.Media.Image.js";
+// @codekit-prepend "media/types/TL.Media.Imgur.js";
+// @codekit-prepend "media/types/TL.Media.Instagram.js";
+// @codekit-prepend "media/types/TL.Media.GoogleMap.js";
+// @codekit-prepend "media/types/TL.Media.PDF.js";
+// @codekit-prepend "media/types/TL.Media.Profile.js";
+// @codekit-prepend "media/types/TL.Media.Slider.js";
+// @codekit-prepend "media/types/TL.Media.SoundCloud.js";
+// @codekit-prepend "media/types/TL.Media.Spotify.js";
+// @codekit-prepend "media/types/TL.Media.Storify.js";
+// @codekit-prepend "media/types/TL.Media.Text.js";
+// @codekit-prepend "media/types/TL.Media.Twitter.js";
+// @codekit-prepend "media/types/TL.Media.TwitterEmbed.js";
+// @codekit-prepend "media/types/TL.Media.Vimeo.js";
+// @codekit-prepend "media/types/TL.Media.Vine.js";
+// @codekit-prepend "media/types/TL.Media.Website.js";
+// @codekit-prepend "media/types/TL.Media.Wikipedia.js";
+// @codekit-prepend "media/types/TL.Media.Wistia.js";
+// @codekit-prepend "media/types/TL.Media.YouTube.js";
+// @codekit-prepend "media/types/TL.Media.Audio.js";
+// @codekit-prepend "media/types/TL.Media.Video.js";
+
+// STORYSLIDER
+// @codekit-prepend "slider/TL.Slide.js";
+// @codekit-prepend "slider/TL.SlideNav.js";
+// @codekit-prepend "slider/TL.StorySlider.js";
+
+// TIMENAV
+// @codekit-prepend "timenav/TL.TimeNav.js";
+// @codekit-prepend "timenav/TL.TimeMarker.js";
+// @codekit-prepend "timenav/TL.TimeEra.js";
+// @codekit-prepend "timenav/TL.TimeGroup.js";
+// @codekit-prepend "timenav/TL.TimeScale.js";
+// @codekit-prepend "timenav/TL.TimeAxis.js";
+// @codekit-prepend "timenav/TL.AxisHelper.js";
+
+
+TL.Timeline = TL.Class.extend({
+ includes: [TL.Events, TL.I18NMixins],
+
+ /* Private Methods
+ ================================================== */
+ initialize: function (elem, data, options) {
+ var self = this;
+ if (!options) { options = {}};
+ // Version
+ this.version = "3.2.6";
+
+ // Ready
+ this.ready = false;
+
+ // DOM ELEMENTS
+ this._el = {
+ container: {},
+ storyslider: {},
+ timenav: {},
+ menubar: {}
+ };
+
+ // Determine Container Element
+ if (typeof elem === 'object') {
+ this._el.container = elem;
+ } else {
+ this._el.container = TL.Dom.get(elem);
+ }
+
+ // Slider
+ this._storyslider = {};
+
+ // Style Sheet
+ this._style_sheet = new TL.StyleSheet();
+
+ // TimeNav
+ this._timenav = {};
+
+ // Menu Bar
+ this._menubar = {};
+
+ // Loaded State
+ this._loaded = {storyslider:false, timenav:false};
+
+ // Data Object
+ this.config = null;
+
+ this.options = {
+ script_path: "",
+ height: this._el.container.offsetHeight,
+ width: this._el.container.offsetWidth,
+ debug: false,
+ is_embed: false,
+ is_full_embed: false,
+ hash_bookmark: false,
+ default_bg_color: {r:255, g:255, b:255},
+ scale_factor: 2, // How many screen widths wide should the timeline be
+ layout: "landscape", // portrait or landscape
+ timenav_position: "bottom", // timeline on top or bottom
+ optimal_tick_width: 60, // optimal distance (in pixels) between ticks on axis
+ base_class: "tl-timeline", // removing tl-timeline will break all default stylesheets...
+ timenav_height: null,
+ timenav_height_percentage: 25, // Overrides timenav height as a percentage of the screen
+ timenav_mobile_height_percentage: 40, // timenav height as a percentage on mobile devices
+ timenav_height_min: 175, // Minimum timenav height
+ marker_height_min: 30, // Minimum Marker Height
+ marker_width_min: 100, // Minimum Marker Width
+ marker_padding: 5, // Top Bottom Marker Padding
+ start_at_slide: 0,
+ start_at_end: false,
+ menubar_height: 0,
+ skinny_size: 650,
+ medium_size: 800,
+ relative_date: false, // Use momentjs to show a relative date from the slide.text.date.created_time field
+ use_bc: false, // Use declared suffix on dates earlier than 0
+ // animation
+ duration: 1000,
+ ease: TL.Ease.easeInOutQuint,
+ // interaction
+ dragging: true,
+ trackResize: true,
+ map_type: "stamen:toner-lite",
+ slide_padding_lr: 100, // padding on slide of slide
+ slide_default_fade: "0%", // landscape fade
+ zoom_sequence: [0.5, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89], // Array of Fibonacci numbers for TimeNav zoom levels
+ language: "en",
+ ga_property_id: null,
+ track_events: ['back_to_start','nav_next','nav_previous','zoom_in','zoom_out' ]
+ };
+
+ // Animation Objects
+ this.animator_timenav = null;
+ this.animator_storyslider = null;
+ this.animator_menubar = null;
+
+ // Add message to DOM
+ this.message = new TL.Message({}, {message_class: "tl-message-full"}, this._el.container);
+
+ // Merge Options
+ if (typeof(options.default_bg_color) == "string") {
+ var parsed = TL.Util.hexToRgb(options.default_bg_color); // will clear it out if its invalid
+ if (parsed) {
+ options.default_bg_color = parsed;
+ } else {
+ delete options.default_bg_color
+ trace("Invalid default background color. Ignoring.");
+ }
+ }
+ TL.Util.mergeData(this.options, options);
+
+ window.addEventListener("resize", function(e){
+ self.updateDisplay();
+ });
+
+ // Set Debug Mode
+ TL.debug = this.options.debug;
+
+ // Apply base class to container
+ TL.DomUtil.addClass(this._el.container, 'tl-timeline');
+
+ if (this.options.is_embed) {
+ TL.DomUtil.addClass(this._el.container, 'tl-timeline-embed');
+ }
+
+ if (this.options.is_full_embed) {
+ TL.DomUtil.addClass(this._el.container, 'tl-timeline-full-embed');
+ }
+
+ document.addEventListener("keydown", function(event) {
+ var keyName = event.key;
+ var currentSlide = self._getSlideIndex(self.current_id);
+ var _n = self.config.events.length - 1;
+ var lastSlide = self.config.title ? _n + 1 : _n;
+ var firstSlide = 0;
+
+ if (keyName == 'ArrowLeft'){
+ if (currentSlide!=firstSlide){
+ self.goToPrev();
+ }
+ }
+ else if (keyName == 'ArrowRight'){
+ if (currentSlide!=lastSlide){
+ self.goToNext();
+ }
+ }
+ });
+
+ // Use Relative Date Calculations
+ // NOT YET IMPLEMENTED
+ if(this.options.relative_date) {
+ if (typeof(moment) !== 'undefined') {
+ self._loadLanguage(data);
+ } else {
+ TL.Load.js(this.options.script_path + "/library/moment.js", function() {
+ self._loadLanguage(data);
+ trace("LOAD MOMENTJS")
+ });
+ }
+ } else {
+ self._loadLanguage(data);
+ }
+
+ },
+ _translateError: function(e) {
+ if(e.hasOwnProperty('stack')) {
+ trace(e.stack);
+ }
+ if(e.message_key) {
+ return this._(e.message_key) + (e.detail ? ' [' + e.detail +']' : '')
+ }
+ return e;
+ },
+
+ /* Load Language
+ ================================================== */
+ _loadLanguage: function(data) {
+ try {
+ this.options.language = new TL.Language(this.options);
+
+ this._initData(data);
+ } catch(e) {
+ this.showMessage(this._translateError(e));
+ }
+ },
+
+
+ /* Navigation
+ ================================================== */
+
+ // Goto slide with id
+ goToId: function(id) {
+ if (this.current_id != id) {
+ this.current_id = id;
+ this._timenav.goToId(this.current_id);
+ this._storyslider.goToId(this.current_id, false, true);
+ this.fire("change", {unique_id: this.current_id}, this);
+ }
+ },
+
+ // Goto slide n
+ goTo: function(n) {
+ if(this.config.title) {
+ if(n == 0) {
+ this.goToId(this.config.title.unique_id);
+ } else {
+ this.goToId(this.config.events[n - 1].unique_id);
+ }
+ } else {
+ this.goToId(this.config.events[n].unique_id);
+ }
+ },
+
+ // Goto first slide
+ goToStart: function() {
+ this.goTo(0);
+ },
+
+ // Goto last slide
+ goToEnd: function() {
+ var _n = this.config.events.length - 1;
+ this.goTo(this.config.title ? _n + 1 : _n);
+ },
+
+ // Goto previous slide
+ goToPrev: function() {
+ this.goTo(this._getSlideIndex(this.current_id) - 1);
+ },
+
+ // Goto next slide
+ goToNext: function() {
+ this.goTo(this._getSlideIndex(this.current_id) + 1);
+ },
+
+ /* Event maniupluation
+ ================================================== */
+
+ // Add an event
+ add: function(data) {
+ var unique_id = this.config.addEvent(data);
+
+ var n = this._getEventIndex(unique_id);
+ var d = this.config.events[n];
+
+ this._storyslider.createSlide(d, this.config.title ? n+1 : n);
+ this._storyslider._updateDrawSlides();
+
+ this._timenav.createMarker(d, n);
+ this._timenav._updateDrawTimeline(false);
+
+ this.fire("added", {unique_id: unique_id});
+ },
+
+ // Remove an event
+ remove: function(n) {
+ if(n >= 0 && n < this.config.events.length) {
+ // If removing the current, nav to new one first
+ if(this.config.events[n].unique_id == this.current_id) {
+ if(n < this.config.events.length - 1) {
+ this.goTo(n + 1);
+ } else {
+ this.goTo(n - 1);
+ }
+ }
+
+ var event = this.config.events.splice(n, 1);
+ delete this.config.event_dict[event[0].unique_id];
+ this._storyslider.destroySlide(this.config.title ? n+1 : n);
+ this._storyslider._updateDrawSlides();
+
+ this._timenav.destroyMarker(n);
+ this._timenav._updateDrawTimeline(false);
+
+ this.fire("removed", {unique_id: event[0].unique_id});
+ }
+ },
+
+ removeId: function(id) {
+ this.remove(this._getEventIndex(id));
+ },
+
+ /* Get slide data
+ ================================================== */
+
+ getData: function(n) {
+ if(this.config.title) {
+ if(n == 0) {
+ return this.config.title;
+ } else if(n > 0 && n <= this.config.events.length) {
+ return this.config.events[n - 1];
+ }
+ } else if(n >= 0 && n < this.config.events.length) {
+ return this.config.events[n];
+ }
+ return null;
+ },
+
+ getDataById: function(id) {
+ return this.getData(this._getSlideIndex(id));
+ },
+
+ /* Get slide object
+ ================================================== */
+
+ getSlide: function(n) {
+ if(n >= 0 && n < this._storyslider._slides.length) {
+ return this._storyslider._slides[n];
+ }
+ return null;
+ },
+
+ getSlideById: function(id) {
+ return this.getSlide(this._getSlideIndex(id));
+ },
+
+ getCurrentSlide: function() {
+ return this.getSlideById(this.current_id);
+ },
+
+
+ /* Display
+ ================================================== */
+ updateDisplay: function() {
+ if (this.ready) {
+ this._updateDisplay();
+ }
+ },
+
+ /*
+ Compute the height of the navigation section of the Timeline, taking into account
+ the possibility of an explicit height or height percentage, but also honoring the
+ `timenav_height_min` option value. If `timenav_height` is specified it takes precedence over `timenav_height_percentage` but in either case, if the resultant pixel height is less than `options.timenav_height_min` then the value of `options.timenav_height_min` will be returned. (A minor adjustment is made to the returned value to account for marker padding.)
+
+ Arguments:
+ @timenav_height (optional): an integer value for the desired height in pixels
+ @timenav_height_percentage (optional): an integer between 1 and 100
+
+ */
+ _calculateTimeNavHeight: function(timenav_height, timenav_height_percentage) {
+
+ var height = 0;
+
+ if (timenav_height) {
+ height = timenav_height;
+ } else {
+ if (this.options.timenav_height_percentage || timenav_height_percentage) {
+ if (timenav_height_percentage) {
+ height = Math.round((this.options.height/100)*timenav_height_percentage);
+ } else {
+ height = Math.round((this.options.height/100)*this.options.timenav_height_percentage);
+ }
+
+ }
+ }
+
+ // Set new minimum based on how many rows needed
+ if (this._timenav.ready) {
+ if (this.options.timenav_height_min < this._timenav.getMinimumHeight()) {
+ this.options.timenav_height_min = this._timenav.getMinimumHeight();
+ }
+ }
+
+ // If height is less than minimum set it to minimum
+ if (height < this.options.timenav_height_min) {
+ height = this.options.timenav_height_min;
+ }
+
+ height = height - (this.options.marker_padding * 2);
+
+ return height;
+ },
+
+ /* Private Methods
+ ================================================== */
+
+ // Update View
+ _updateDisplay: function(timenav_height, animate, d) {
+ var duration = this.options.duration,
+ display_class = this.options.base_class,
+ menu_position = 0,
+ self = this;
+
+ if (d) {
+ duration = d;
+ }
+
+ // Update width and height
+ this.options.width = this._el.container.offsetWidth;
+ this.options.height = this._el.container.offsetHeight;
+
+ // Check if skinny
+ if (this.options.width <= this.options.skinny_size) {
+ display_class += " tl-skinny";
+ this.options.layout = "portrait";
+ } else if (this.options.width <= this.options.medium_size) {
+ display_class += " tl-medium";
+ this.options.layout = "landscape";
+ } else {
+ this.options.layout = "landscape";
+ }
+
+ // Detect Mobile and Update Orientation on Touch devices
+ if (TL.Browser.touch) {
+ this.options.layout = TL.Browser.orientation();
+ }
+
+ if (TL.Browser.mobile) {
+ display_class += " tl-mobile";
+ // Set TimeNav Height
+ this.options.timenav_height = this._calculateTimeNavHeight(timenav_height, this.options.timenav_mobile_height_percentage);
+ } else {
+ // Set TimeNav Height
+ this.options.timenav_height = this._calculateTimeNavHeight(timenav_height);
+ }
+
+ // LAYOUT
+ if (this.options.layout == "portrait") {
+ // Portrait
+ display_class += " tl-layout-portrait";
+
+ } else {
+ // Landscape
+ display_class += " tl-layout-landscape";
+
+ }
+
+ // Set StorySlider Height
+ this.options.storyslider_height = (this.options.height - this.options.timenav_height);
+
+ // Positon Menu
+ if (this.options.timenav_position == "top") {
+ menu_position = ( Math.ceil(this.options.timenav_height)/2 ) - (this._el.menubar.offsetHeight/2) - (39/2) ;
+ } else {
+ menu_position = Math.round(this.options.storyslider_height + 1 + ( Math.ceil(this.options.timenav_height)/2 ) - (this._el.menubar.offsetHeight/2) - (35/2));
+ }
+
+
+ if (animate) {
+
+ // Animate TimeNav
+
+ /*
+ if (this.animator_timenav) {
+ this.animator_timenav.stop();
+ }
+
+ this.animator_timenav = TL.Animate(this._el.timenav, {
+ height: (this.options.timenav_height) + "px",
+ duration: duration/4,
+ easing: TL.Ease.easeOutStrong,
+ complete: function () {
+ //self._map.updateDisplay(self.options.width, self.options.timenav_height, animate, d, self.options.menubar_height);
+ }
+ });
+ */
+
+ this._el.timenav.style.height = Math.ceil(this.options.timenav_height) + "px";
+
+ // Animate StorySlider
+ if (this.animator_storyslider) {
+ this.animator_storyslider.stop();
+ }
+ this.animator_storyslider = TL.Animate(this._el.storyslider, {
+ height: this.options.storyslider_height + "px",
+ duration: duration/2,
+ easing: TL.Ease.easeOutStrong
+ });
+
+ // Animate Menubar
+ if (this.animator_menubar) {
+ this.animator_menubar.stop();
+ }
+
+ this.animator_menubar = TL.Animate(this._el.menubar, {
+ top: menu_position + "px",
+ duration: duration/2,
+ easing: TL.Ease.easeOutStrong
+ });
+
+ } else {
+ // TimeNav
+ this._el.timenav.style.height = Math.ceil(this.options.timenav_height) + "px";
+
+ // StorySlider
+ this._el.storyslider.style.height = this.options.storyslider_height + "px";
+
+ // Menubar
+ this._el.menubar.style.top = menu_position + "px";
+ }
+
+ if (this.message) {
+ this.message.updateDisplay(this.options.width, this.options.height);
+ }
+ // Update Component Displays
+ this._timenav.updateDisplay(this.options.width, this.options.timenav_height, animate);
+ this._storyslider.updateDisplay(this.options.width, this.options.storyslider_height, animate, this.options.layout);
+
+ if (this.options.language.direction == 'rtl') {
+ display_class += ' tl-rtl';
+ }
+
+
+ // Apply class
+ this._el.container.className = display_class;
+
+ },
+
+ // Update hashbookmark in the url bar
+ _updateHashBookmark: function(id) {
+ var hash = "#" + "event-" + id.toString();
+ if (window.location.protocol != 'file:') {
+ window.history.replaceState(null, "Browsing TimelineJS", hash);
+ }
+ this.fire("hash_updated", {unique_id:this.current_id, hashbookmark:"#" + "event-" + id.toString()}, this);
+ },
+
+ /* Init
+ ================================================== */
+ // Initialize the data
+ _initData: function(data) {
+ var self = this;
+
+ if (typeof data == 'string') {
+ var self = this;
+ TL.ConfigFactory.makeConfig(data, function(config) {
+ self.setConfig(config);
+ });
+ } else if (TL.TimelineConfig == data.constructor) {
+ this.setConfig(data);
+ } else {
+ this.setConfig(new TL.TimelineConfig(data));
+ }
+ },
+
+ setConfig: function(config) {
+ this.config = config;
+ this.config.validate();
+ this._validateOptions();
+ if (this.config.isValid()) {
+ try {
+ this._onDataLoaded();
+ } catch(e) {
+ this.showMessage("<strong>"+ this._('error') +":</strong> " + this._translateError(e));
+ }
+ } else {
+ var translated_errs = [];
+
+ for(var i = 0, errs = this.config.getErrors(); i < errs.length; i++) {
+ translated_errs.push(this._translateError(errs[i]));
+ }
+
+ this.showMessage("<strong>"+ this._('error') +":</strong> " + translated_errs.join('<br>'));
+ // should we set 'self.ready'? if not, it won't resize,
+ // but most resizing would only work
+ // if more setup happens
+ }
+ },
+ _validateOptions: function() {
+ // assumes that this.options and this.config have been set.
+ var INTEGER_PROPERTIES = ['timenav_height', 'timenav_height_min', 'marker_height_min', 'marker_width_min', 'marker_padding', 'start_at_slide', 'slide_padding_lr' ];
+
+ for (var i = 0; i < INTEGER_PROPERTIES.length; i++) {
+ var opt = INTEGER_PROPERTIES[i];
+ var value = this.options[opt];
+ valid = true;
+ if (typeof(value) == 'number') {
+ valid = (value == parseInt(value))
+ } else if (typeof(value) == "string") {
+ valid = (value.match(/^\s*(\-?\d+)?\s*$/));
+ }
+ if (!valid) {
+ this.config.logError({ message_key: 'invalid_integer_option', detail: opt });
+ }
+ }
+ },
+ // Initialize the layout
+ _initLayout: function () {
+ var self = this;
+
+ this.message.removeFrom(this._el.container);
+ this._el.container.innerHTML = "";
+
+ // Create Layout
+ if (this.options.timenav_position == "top") {
+ this._el.timenav = TL.Dom.create('div', 'tl-timenav', this._el.container);
+ this._el.storyslider = TL.Dom.create('div', 'tl-storyslider', this._el.container);
+ } else {
+ this._el.storyslider = TL.Dom.create('div', 'tl-storyslider', this._el.container);
+ this._el.timenav = TL.Dom.create('div', 'tl-timenav', this._el.container);
+ }
+
+ this._el.menubar = TL.Dom.create('div', 'tl-menubar', this._el.container);
+
+
+ // Initial Default Layout
+ this.options.width = this._el.container.offsetWidth;
+ this.options.height = this._el.container.offsetHeight;
+ // this._el.storyslider.style.top = "1px";
+
+ // Set TimeNav Height
+ this.options.timenav_height = this._calculateTimeNavHeight(this.options.timenav_height);
+
+ // Create TimeNav
+ this._timenav = new TL.TimeNav(this._el.timenav, this.config, this.options);
+ this._timenav.on('loaded', this._onTimeNavLoaded, this);
+ this._timenav.on('update_timenav_min', this._updateTimeNavHeightMin, this);
+ this._timenav.options.height = this.options.timenav_height;
+ this._timenav.init();
+
+ // intial_zoom cannot be applied before the timenav has been created
+ if (this.options.initial_zoom) {
+ // at this point, this.options refers to the merged set of options
+ this.setZoom(this.options.initial_zoom);
+ }
+
+ // Create StorySlider
+ this._storyslider = new TL.StorySlider(this._el.storyslider, this.config, this.options);
+ this._storyslider.on('loaded', this._onStorySliderLoaded, this);
+ this._storyslider.init();
+
+ // Create Menu Bar
+ this._menubar = new TL.MenuBar(this._el.menubar, this._el.container, this.options);
+
+ // LAYOUT
+ if (this.options.layout == "portrait") {
+ this.options.storyslider_height = (this.options.height - this.options.timenav_height - 1);
+ } else {
+ this.options.storyslider_height = (this.options.height - 1);
+ }
+
+
+ // Update Display
+ this._updateDisplay(this._timenav.options.height, true, 2000);
+
+ },
+
+ /* Depends upon _initLayout because these events are on things the layout initializes */
+ _initEvents: function () {
+ // TimeNav Events
+ this._timenav.on('change', this._onTimeNavChange, this);
+ this._timenav.on('zoomtoggle', this._onZoomToggle, this);
+
+ // StorySlider Events
+ this._storyslider.on('change', this._onSlideChange, this);
+ this._storyslider.on('colorchange', this._onColorChange, this);
+ this._storyslider.on('nav_next', this._onStorySliderNext, this);
+ this._storyslider.on('nav_previous', this._onStorySliderPrevious, this);
+
+ // Menubar Events
+ this._menubar.on('zoom_in', this._onZoomIn, this);
+ this._menubar.on('zoom_out', this._onZoomOut, this);
+ this._menubar.on('back_to_start', this._onBackToStart, this);
+
+ },
+
+ /* Analytics
+ ================================================== */
+ _initGoogleAnalytics: function() {
+ (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
+
+ ga('create', this.options.ga_property_id, 'auto');
+ ga('set', 'anonymizeIp', true);
+ },
+
+ _initAnalytics: function() {
+ if (this.options.ga_property_id === null) { return; }
+ this._initGoogleAnalytics();
+ ga('send', 'pageview');
+ var events = this.options.track_events;
+ for (i=0; i < events.length; i++) {
+ var event_ = events[i];
+ this.addEventListener(event_, function(e) {
+ ga('send', 'event', e.type, 'clicked');
+ });
+ }
+ },
+
+ _onZoomToggle: function(e) {
+ if (e.zoom == "in") {
+ this._menubar.toogleZoomIn(e.show);
+ } else if (e.zoom == "out") {
+ this._menubar.toogleZoomOut(e.show);
+ }
+
+ },
+
+ /* Get index of event by id
+ ================================================== */
+ _getEventIndex: function(id) {
+ for(var i = 0; i < this.config.events.length; i++) {
+ if(id == this.config.events[i].unique_id) {
+ return i;
+ }
+ }
+ return -1;
+ },
+
+ /* Get index of slide by id
+ ================================================== */
+ _getSlideIndex: function(id) {
+ if(this.config.title && this.config.title.unique_id == id) {
+ return 0;
+ }
+ for(var i = 0; i < this.config.events.length; i++) {
+ if(id == this.config.events[i].unique_id) {
+ return this.config.title ? i+1 : i;
+ }
+ }
+ return -1;
+ },
+
+ /* Events
+ ================================================== */
+
+ _onDataLoaded: function(e) {
+ this.fire("dataloaded");
+ this._initLayout();
+ this._initEvents();
+ this._initAnalytics();
+ if (this.message) {
+ this.message.hide();
+ }
+
+ this.ready = true;
+
+ },
+
+ showMessage: function(msg) {
+ if (this.message) {
+ this.message.updateMessage(msg);
+ } else {
+ trace("No message display available.")
+ trace(msg);
+ }
+ },
+
+ _onColorChange: function(e) {
+ this.fire("color_change", {unique_id:this.current_id}, this);
+ if (e.color || e.image) {
+
+ } else {
+
+ }
+ },
+
+ _onSlideChange: function(e) {
+ if (this.current_id != e.unique_id) {
+ this.current_id = e.unique_id;
+ this._timenav.goToId(this.current_id);
+ this._onChange(e);
+ }
+ },
+
+ _onTimeNavChange: function(e) {
+ if (this.current_id != e.unique_id) {
+ this.current_id = e.unique_id;
+ this._storyslider.goToId(this.current_id);
+ this._onChange(e);
+ }
+ },
+
+ _onChange: function(e) {
+ this.fire("change", {unique_id:this.current_id}, this);
+ if (this.options.hash_bookmark && this.current_id) {
+ this._updateHashBookmark(this.current_id);
+ }
+ },
+
+ _onBackToStart: function(e) {
+ this._storyslider.goTo(0);
+ this.fire("back_to_start", {unique_id:this.current_id}, this);
+ },
+
+ /**
+ * Zoom in and zoom out should be part of the public API.
+ */
+ zoomIn: function() {
+ this._timenav.zoomIn();
+ },
+ zoomOut: function() {
+ this._timenav.zoomOut();
+ },
+
+ setZoom: function(level) {
+ this._timenav.setZoom(level);
+ },
+
+ _onZoomIn: function(e) {
+ this._timenav.zoomIn();
+ this.fire("zoom_in", {zoom_level:this._timenav.options.scale_factor}, this);
+ },
+
+ _onZoomOut: function(e) {
+ this._timenav.zoomOut();
+ this.fire("zoom_out", {zoom_level:this._timenav.options.scale_factor}, this);
+ },
+
+ _onTimeNavLoaded: function() {
+ this._loaded.timenav = true;
+ this._onLoaded();
+ },
+
+ _onStorySliderLoaded: function() {
+ this._loaded.storyslider = true;
+ this._onLoaded();
+ },
+
+ _onStorySliderNext: function(e) {
+ this.fire("nav_next", e);
+ },
+
+ _onStorySliderPrevious: function(e) {
+ this.fire("nav_previous", e);
+ },
+
+ _onLoaded: function() {
+ if (this._loaded.storyslider && this._loaded.timenav) {
+ this.fire("loaded", this.config);
+ // Go to proper slide
+ if (this.options.hash_bookmark && window.location.hash != "") {
+ this.goToId(window.location.hash.replace("#event-", ""));
+ } else {
+ if( TL.Util.isTrue(this.options.start_at_end) || this.options.start_at_slide > this.config.events.length ) {
+ this.goToEnd();
+ } else {
+ this.goTo(this.options.start_at_slide);
+ }
+ if (this.options.hash_bookmark ) {
+ this._updateHashBookmark(this.current_id);
+ }
+ }
+
+ }
+ }
+
+});
+
+TL.Timeline.source_path = (function() {
+ var script_tags = document.getElementsByTagName('script');
+ var src = script_tags[script_tags.length-1].src;
+ return src.substr(0,src.lastIndexOf('/'));
+})();
+
diff --git a/www/wiki/extensions/ModernTimeline/resources/vendor/tl-icons.eot b/www/wiki/extensions/ModernTimeline/resources/vendor/tl-icons.eot
new file mode 100644
index 00000000..179ed2d3
--- /dev/null
+++ b/www/wiki/extensions/ModernTimeline/resources/vendor/tl-icons.eot
Binary files differ
diff --git a/www/wiki/extensions/ModernTimeline/resources/vendor/tl-icons.svg b/www/wiki/extensions/ModernTimeline/resources/vendor/tl-icons.svg
new file mode 100644
index 00000000..e90b1c32
--- /dev/null
+++ b/www/wiki/extensions/ModernTimeline/resources/vendor/tl-icons.svg
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg xmlns="http://www.w3.org/2000/svg">
+<metadata>Generated by IcoMoon</metadata>
+<defs>
+<font id="ti-icons" horiz-adv-x="1024">
+<font-face units-per-em="1024" ascent="960" descent="-64"/>
+<missing-glyph horiz-adv-x="1024"/>
+<glyph unicode=" " d="" horiz-adv-x="512"/>
+<glyph unicode="" d="M240.528 217.598h261.352l-130.673-281.606-130.68 281.606zM179.198 960h383.999c98.969 0 179.205-80.236 179.205-179.198v-384.006c0-98.969-80.228-179.198-179.205-179.198h-383.999c-98.969 0-179.198 80.228-179.198 179.198v384.006c0 98.962 80.228 179.198 179.198 179.198z" horiz-adv-x="742"/>
+<glyph unicode="" d="M681.974 701.665l151.347 131.328-145.439 123.904-9.759-68.362c-0.42-0.164-144.189 3.113-252.078-22.446-128.256-30.382-235.151-71.127-234.578-74.056 0.369-1.905 123.699 26.276 270.94 23.788 118.006-1.997 222.382-36.065 222.382-36.065l-2.816-78.090zM676.915 833.72c1.065 29.645 25.969 52.808 55.613 51.732 29.655-1.075 52.797-25.979 51.732-55.624s-25.979-52.808-55.624-51.732c-29.635 1.085-52.787 25.979-51.722 55.624zM376.023 428.902c0 0 5.448 10.711 11.919 19.251 12.042 15.903 31.703 41.267 49.94 75.223 22.067 41.103 27.341 57.702 53.074 82.35 18.309 17.531 101.018 123.863 123.72 156.068 26.399 37.468 73.114 91.812 87.030 100.014 32.205 18.944 51.272 9.533 65.075-16.855 3.85-7.363 2.54-15.401-2.017-27.945-6.676-18.074-13.199-24.269-24.934-40.223-16.937-23.030-43.244-53.821-47.821-62.822-13.998-27.607-62.73-98.867-62.73-98.867s35.154 30.321 78.121 19.528c24.125-8.899 46.981-22.026 48.835-39.055 3.574-32.983 0-58.593 0-58.593s29.297-1.423 39.066-29.297c9.759-27.873-4.014-78.766-4.014-78.766s14.899-5.898 21.975-29.481c7.076-23.583 6.298-64.85 7.731-75.151 2.836-20.613 3.236-7.68 2.437-22.221-0.215-3.625 0.379-21.146-9.226-43.489-29.030-67.461-94.249-191.744-98.693-213.852-1.638-8.12 1.966-21.023 0.973-27.474-2.529-16.927-7.506-22.477-3.932-27.464 4.925-6.871-0.696-5.97-5.151-19.231-4.444-13.23-295.025-62.863-332.032 19.528-12.431 27.699-16.599 71.301-34.355 105.144-9.574 18.248-43.868 62.034-72.53 126.546-24.515 55.163-43.561 132.168-45.885 148.419-3.676 25.723 15.841 61.891 26.644 101.724 8.991 33.167 16.978 69.714 25.641 76.493 27.648 21.637 51.466 25.416 51.466 25.416s7.967 6.861 11.069 7.895c3.113 1.034 22.804 39.844 45.261 40.868 12.82 0.584 23.142-8.581 27.638-31.59 1.29-6.615 1.331-20.009-1.034-33.659-2.56-14.674-15.124-29.348-24.617-45.117-12.308-20.449-28.948-25.344-28.826-24.084-1.085-6.738 7.567-12.933 2.847-28.979-4.884-16.548-11.039-12.145-12.616-19.855 0 0 14.131 2.785 19.528 0 4.649-2.376 10.424-10.394 10.424-10.394z"/>
+<glyph unicode="" d="M143.036 699.751l-143.036 137.111 152.096 123.138 5.741-69.879c0.398-0.184 143.782 0.051 249.738-28.315 125.977-33.739 230.055-77.57 229.319-80.543-0.48-1.931-121.677 29.449-268.472 30.092-117.663 0.501-223.62-31.972-223.62-31.972l-1.767-79.634zM101.105 889.171c-29.582-0.449-54.107-25.343-54.771-55.578s22.789-54.383 52.371-53.923c29.592 0.449 54.117 25.332 54.781 55.568 0.664 30.246-22.789 54.383-52.381 53.933zM356.277 24.878c-27.672 14.341-45.874 12.636-99.307 35.384-56.783 24.188-121.82 59.112-139.41 78.857-16.354 18.356-30.94 56.416-46.589 86.631-17.018 32.901-34.413 57.866-34.546 73.607-0.153 16.435 12.799 37.845 19.878 68.806 5.935 25.976 0.388 54.066 11.471 68.867 12.83 17.13 80.522-12.901 79.429-104.169 21.114-26.722 31.318-52.013 31.318-52.013l12.799 4.178c0-0.194-0.97 175.335-22.768 237.154-9.694 27.498-32.023 87.478-62.401 155.008-31.87 70.839-50.185 161.351-25.935 177.766 49.613 33.555 55.343-25.782 82.545-78.193 11.992-23.116 39.132-44.883 49.878-66.028 18.897-37.171 35.976-64.475 36.793-64.077 3.422 1.645-4.321 47.079 35.435 72.146 21.277 13.422 55.701-6.139 73.627-17.477 47.386-29.949 47.876-46.395 46.998-43.137 0 0 29.173 11.154 53.024-0.633 41.032-20.286 56.395-65.721 53.699-65.486 0 0 15.414 7.273 37.753 1.164 48.867-13.371 57.406-66.273 71.503-87.867 27.672-42.391 79.664-235.775 74.628-306.757-2.574-36.231 37.529-74.608 16.711-135.467-41.431-121.146-265.020-150.738-318.075-109.828-7.395 5.69-10.787 57.253-38.458 71.564z" horiz-adv-x="719"/>
+<glyph unicode="" d="M306.34 615.24c0 0-49.152-62.198-93.164-128.215-36.188-54.272-68.792-114.698-68.792-114.698l-67.308 17.49c0 0 30.126-233.103 41.83-234.373 11.684-1.27 182.446 148.613 182.446 148.613-1.772-0.020-66.673 28.191-66.673 28.191s11.223 62.935 27.095 125.614c19.569 77.281 44.564 157.379 44.564 157.379zM174.141 214.897c-32.215 0-58.327 26.122-58.327 58.317 0 32.215 26.122 58.327 58.327 58.327 32.215 0 58.327-26.112 58.327-58.327 0-32.195-26.112-58.317-58.327-58.317zM686.1 952.504c-5.028 10.619-255.252-33.096-255.252-33.096l58.716-62.116c0 0-38.318-33.864-72.253-78.633-50.77-66.97-104.11-154.020-104.11-154.020s74.23 69.868 142.991 115.128c50.156 33.024 100.864 45.363 100.864 45.363s47.933-52.050 48.486-53.729c0.010 0 85.596 210.473 80.558 221.102zM572.703 798.167c-32.215 0-58.327 26.112-58.327 58.327 0 32.205 26.112 58.327 58.327 58.327 32.215 0 58.317-26.112 58.317-58.327 0-32.215-26.102-58.327-58.317-58.327zM733.317-49.009c47.872-46.612 288.42 131.123 241.152 192.86-21.914 28.631-40.714 69.151-50.463 97.044-45.138 129.106-46.674 137.605-69.253 197.54-5.96 15.831-7.793 78.438-37.612 87.419-3.697 39.322-12.861 76.964-48.23 76.984-6.185 25.836-13.926 48.742-25.272 63.754-25.672 34.028-77.947-5.55-77.947-5.55s6.339 10.65-4.731 46.223c-10.025 32.225-36.864 87.808-49.121 121.16-13.312 36.147-15.524 68.362-34.99 69.601-14.817 0.932-29.071-15.862-36.444-45.998-9.677-39.649 6.881-111.923 6.543-177.644-0.143-27.658 5.478-69.765 6.717-84.685 2.734-32.911-17.152-53.187-20.419-89.733-2.437-27.361-12.227-176.783-99.922-199.956-50.299-13.302-98.109 14.387-147.732 28.303-34.017 9.554-129.905 10.158-148.357-44.78-20.623-61.389 158.781-75.254 207.718-112.937 15.575-11.991 63.14-36.895 108.022-66.826 30.454-20.285 53.238-44.329 88.75-62.771 47.432-24.617 86.405-32.881 118.364-44.39 16.046-5.786 50.35-23.347 73.226-45.619z"/>
+<glyph unicode="" d="M459.52 683.756l-65.229 27.955c0 0 4.628 51.446 18.637 111.811 15.493 66.785 37.274 130.458 37.274 130.458s-47.35-60.447-93.184-121.139c-37.693-49.91-55.91-83.855-55.91-83.855l-65.229 18.637 65.229-167.721 158.413 83.855zM282.337 683.827c0 30.884 25.037 55.91 55.91 55.91 30.884 0 55.9-25.027 55.9-55.91 0-30.874-25.016-55.91-55.9-55.91-30.874 0-55.91 25.037-55.91 55.91zM124.078 516.035l83.855-18.637c0 0-11.203-81.644-15.913-135.301-7.055-80.241-5.96-178.084-5.96-178.084s24.607 94.556 57.344 166.369c23.88 52.367 67.031 109.752 67.031 109.752l65.229-18.637-73.974 158.413-177.613-83.876zM319.713 554.435c17.93-25.139 12.093-60.047-13.046-77.988-25.139-17.93-60.037-12.093-77.988 13.036-17.93 25.129-12.093 60.047 13.046 77.988 25.139 17.93 60.047 12.104 77.988-13.036zM512.256 410.87c0 0 48.64 10.424 61.532 86.272 0.573 3.348 5.222 21.678 4.157 35.748-0.758 9.964-7.281 17.449-7.281 17.449s-26.184-20.521-45.507-18.698c-15.237 1.434-25.917 3.174-34.294 26.778-7.475 21.002 1.188 56.668 1.188 56.668l-4.014 27.668c0.215 0.481-47.442 2.54-47.442 2.54s-54.579-10.179-93.553-4.157c-30.628 4.721-48.077 22.405-45.804 42.772 3.625 32.42 103.332 49.060 181.299 61.614 19.343 3.123 39.485 16.118 53.996 17.254 21.309 1.669 39.567-13.097 39.567-13.097s56.34 31.416 78.971 39.7c40.253 14.725 89.303-68.485 145.213-146.77 20.040-28.078 53.699-56.166 66.12-76.79 41.933-69.642 27.003-316.938 107.633-417.516 37.059-46.223-27.525-113.654-65.362-144.189-67.123-54.2-179.098-95.222-208.333-46.152-5.847 9.83-30.904 37.847-66.028 65.751-31.334 24.883-73.359 45.251-105.738 88.474-35.308 47.145-65.454 99.215-108.257 153.539-15.206 19.313-24.73 51.937-38.164 69.99-19.732 26.522-39.926 43.704-57.006 66.847-14.899 20.214-34.427 56.699-50.883 69.755-30.065 23.9-42.854 57.58-23.132 78.93 27.822 30.106 74.711 18.463 99.4 2.519 66.181-42.732 75.592-68.946 105.196-107.274 21.412-27.709 56.525-35.625 56.525-35.625z"/>
+<glyph unicode="" d="M0 832v-832h1024v832h-1024zM960 64h-896v704h896v-704zM704 608c0 53.019 42.981 96 96 96s96-42.981 96-96c0-53.019-42.981-96-96-96-53.019 0-96 42.981-96 96zM896 128h-768l192 512 256-320 128 96z"/>
+<glyph unicode="" d="M0 651.63l41.594-54.544c0 0 85.77 67.608 114.36 33.802 28.588-33.802 137.736-441.956 174.17-517.246 31.8-66.030 119.518-153.32 215.714-90.982 96.136 62.34 415.84 335.286 473.066 657.616 57.18 322.226-384.72 254.726-431.53-26.010 116.99 70.236 179.436-28.538 119.57-140.372-59.758-111.724-114.36-184.592-142.952-184.592-28.538 0-50.49 74.768-83.188 205.446-33.798 135.102-33.592 378.46-174.116 350.87-132.518-26.012-306.688-233.988-306.688-233.988z"/>
+<glyph unicode="" d="M1024 765.582c-37.676-16.708-78.164-28.002-120.66-33.080 43.372 26 76.686 67.17 92.372 116.23-40.596-24.078-85.556-41.56-133.41-50.98-38.32 40.83-92.922 66.34-153.346 66.34-116.022 0-210.088-94.058-210.088-210.078 0-16.466 1.858-32.5 5.44-47.878-174.6 8.764-329.402 92.4-433.018 219.506-18.084-31.028-28.446-67.116-28.446-105.618 0-72.888 37.088-137.192 93.46-174.866-34.438 1.092-66.832 10.542-95.154 26.278-0.020-0.876-0.020-1.756-0.020-2.642 0-101.788 72.418-186.696 168.522-206-17.626-4.8-36.188-7.372-55.348-7.372-13.538 0-26.698 1.32-39.528 3.772 26.736-83.46 104.32-144.206 196.252-145.896-71.9-56.35-162.486-89.934-260.916-89.934-16.958 0-33.68 0.994-50.116 2.94 92.972-59.61 203.402-94.394 322.042-94.394 386.422 0 597.736 320.124 597.736 597.744 0 9.108-0.206 18.168-0.61 27.18 41.056 29.62 76.672 66.62 104.836 108.748z"/>
+<glyph unicode="" d="M559.066 896c0 0-200.956 0-267.94 0-120.12 0-233.17-91.006-233.17-196.422 0-107.726 81.882-194.666 204.088-194.666 8.498 0 16.756 0.17 24.842 0.752-7.93-15.186-13.602-32.288-13.602-50.042 0-29.938 16.104-54.21 36.468-74.024-15.386 0-30.242-0.448-46.452-0.448-148.782 0.002-263.3-94.758-263.3-193.020 0-96.778 125.542-157.314 274.334-157.314 169.624 0 263.306 96.244 263.306 193.028 0 77.6-22.896 124.072-93.686 174.134-24.216 17.144-70.53 58.836-70.53 83.344 0 28.72 8.196 42.868 51.428 76.646 44.312 34.624 75.672 83.302 75.672 139.916 0 67.406-30.020 133.098-86.372 154.772h84.954l59.96 43.344zM465.48 240.542c2.126-8.972 3.284-18.206 3.284-27.628 0-78.2-50.392-139.31-194.974-139.31-102.842 0-177.116 65.104-177.116 143.3 0 76.642 92.126 140.444 194.964 139.332 24-0.254 46.368-4.116 66.67-10.69 55.826-38.826 95.876-60.762 107.172-105.004zM300.818 532.224c-69.038 2.064-134.636 77.226-146.552 167.86-11.916 90.666 34.37 160.042 103.388 157.99 69.010-2.074 134.638-74.814 146.558-165.458 11.906-90.66-34.39-162.458-103.394-160.392zM832 704v192h-64v-192h-192v-64h192v-192h64v192h192v64z"/>
+<glyph unicode="" d="M832 832h-640c-105.6 0-192-86.4-192-192v-384c0-105.6 86.4-192 192-192h640c105.6 0 192 86.4 192 192v384c0 105.6-86.4 192-192 192zM384 192v512l320-256-320-256z"/>
+<glyph unicode="" d="M0 768h1024v-64h-1024zM0 640h704v-64h-704zM0 512h1024v-64h-1024zM0 384h704v-64h-704zM0 256h1024v-64h-1024zM0 128h704v-64h-704z"/>
+<glyph unicode="" d="M896 768h-768v-640h768v640zM1024 896v0-896h-1024v896h1024zM832 192h-640v128l192 320 263-320 185 128v-64zM640 608c0 53.019 42.981 96 96 96s96-42.981 96-96c0-53.019-42.981-96-96-96-53.019 0-96 42.981-96 96z"/>
+<glyph unicode="" d="M225 512c123.712 0 224-100.29 224-224 0-123.712-100.288-224-224-224-123.712 0-224 100.288-224 224l-1 32c0 247.424 200.576 448 448 448v-128c-85.474 0-165.834-33.286-226.274-93.726-11.634-11.636-22.252-24.016-31.83-37.020 11.438 1.8 23.16 2.746 35.104 2.746zM801 512c123.71 0 224-100.29 224-224 0-123.712-100.29-224-224-224-123.71 0-224 100.288-224 224l-1 32c0 247.424 200.576 448 448 448v-128c-85.474 0-165.834-33.286-226.274-93.726-11.636-11.636-22.254-24.016-31.832-37.020 11.44 1.8 23.16 2.746 35.106 2.746z"/>
+<glyph unicode="" d="M992.262 88.604l-242.552 206.294c-25.074 22.566-51.89 32.926-73.552 31.926 57.256 67.068 91.842 154.078 91.842 249.176 0 212.078-171.922 384-384 384-212.076 0-384-171.922-384-384 0-212.078 171.922-384 384-384 95.098 0 182.108 34.586 249.176 91.844-1-21.662 9.36-48.478 31.926-73.552l206.294-242.552c35.322-39.246 93.022-42.554 128.22-7.356s31.892 92.898-7.354 128.22zM384 320c-141.384 0-256 114.616-256 256s114.616 256 256 256 256-114.616 256-256-114.614-256-256-256zM448 768h-128v-128h-128v-128h128v-128h128v128h128v128h-128z"/>
+<glyph unicode="" d="M992.262 88.604l-242.552 206.294c-25.074 22.566-51.89 32.926-73.552 31.926 57.256 67.068 91.842 154.078 91.842 249.176 0 212.078-171.922 384-384 384-212.076 0-384-171.922-384-384 0-212.078 171.922-384 384-384 95.098 0 182.108 34.586 249.176 91.844-1-21.662 9.36-48.478 31.926-73.552l206.294-242.552c35.322-39.246 93.022-42.554 128.22-7.356s31.892 92.898-7.354 128.22zM384 320c-141.384 0-256 114.616-256 256s114.616 256 256 256 256-114.616 256-256-114.614-256-256-256zM192 640h384v-128h-384z"/>
+<glyph unicode="" d="M384 896h640v-128h-640v128zM384 512h640v-128h-640v128zM384 128h640v-128h-640v128zM0 832c0 70.692 57.308 128 128 128s128-57.308 128-128c0-70.692-57.308-128-128-128-70.692 0-128 57.308-128 128zM0 448c0 70.692 57.308 128 128 128s128-57.308 128-128c0-70.692-57.308-128-128-128-70.692 0-128 57.308-128 128zM0 64c0 70.692 57.308 128 128 128s128-57.308 128-128c0-70.692-57.308-128-128-128-70.692 0-128 57.308-128 128z"/>
+<glyph unicode="" d="M320 768l704 192v-736c0-88.366-100.29-160-224-160s-224 71.634-224 160c0 88.366 100.29 160 224 160 34.358 0 66.902-5.532 96-15.406v329.588l-448-122.182v-480c0-88.366-100.288-160-224-160-123.712 0-224 71.634-224 160 0 88.366 100.288 160 224 160 34.358 0 66.902-5.532 96-15.406v527.406z"/>
+<glyph unicode="" d="M512 960c-176.732 0-320-143.268-320-320 0-320 320-704 320-704s320 384 320 704c0 176.732-143.27 320-320 320zM512 448c-106.040 0-192 85.96-192 192s85.96 192 192 192 192-85.96 192-192-85.96-192-192-192z"/>
+<glyph unicode="" d="M874.040 810.038c96.702-96.704 149.96-225.28 149.96-362.040s-53.258-265.334-149.96-362.038c-96.706-96.702-225.28-149.96-362.040-149.96-136.76 0-265.334 53.258-362.040 149.96-96.702 96.704-149.96 225.278-149.96 362.038 0 136.76 53.254 265.336 149.96 362.040 96.706 96.704 225.28 149.962 362.040 149.962 136.76 0 265.334-53.258 362.040-149.962zM828.784 131.214c63.058 63.060 104.986 141.608 122.272 227.062-13.474-19.836-26.362-27.194-34.344 17.206-8.22 72.39-74.708 26.148-116.516 51.86-44.004-29.658-142.906 57.662-126.098-40.824 25.934-44.422 140.008 59.45 83.148-34.542-36.274-65.616-132.642-210.932-120.106-286.258 1.582-109.744-112.134-22.884-151.314 13.52-26.356 72.92-8.982 200.374-77.898 236.086-74.802 3.248-139.004 10.046-167.994 93.67-17.446 59.828 18.564 148.894 82.678 162.644 93.85 58.966 127.374-69.054 215.39-71.434 27.328 28.594 101.816 37.686 107.992 69.75-57.75 10.19 73.268 48.558-5.528 70.382-43.47-5.112-71.478-45.074-48.368-78.958-84.238-19.642-86.936 121.904-167.91 77.258-2.058-70.59-132.222-22.886-45.036-8.572 29.956 13.088-48.86 51.016-6.28 44.124 20.916 1.136 91.332 25.812 72.276 42.402 39.21 24.34 72.16-58.29 110.538 1.882 27.708 46.266-11.62 54.808-46.35 31.356-19.58 21.924 34.57 69.276 82.332 89.738 15.918 6.82 31.122 10.536 42.746 9.484 24.058-27.792 68.55-32.606 70.878 3.342-59.582 28.534-125.276 43.608-193.292 43.608-97.622 0-190.47-31.024-267.308-88.39 20.65-9.46 32.372-21.238 12.478-36.296-15.456-46.054-78.17-107.876-133.224-99.124-28.586-49.296-47.412-103.606-55.46-160.528 46.112-15.256 56.744-45.45 46.836-55.55-23.496-20.488-37.936-49.53-45.376-81.322 15.010-91.836 58.172-176.476 125.27-243.576 84.616-84.614 197.118-131.214 316.784-131.214 119.664 0 232.168 46.6 316.784 131.214z"/>
+<glyph unicode="" d="M864 256c-45.16 0-85.92-18.738-115.012-48.83l-431.004 215.502c1.314 8.252 2.016 16.706 2.016 25.328s-0.702 17.076-2.016 25.326l431.004 215.502c29.092-30.090 69.852-48.828 115.012-48.828 88.366 0 160 71.634 160 160s-71.634 160-160 160-160-71.634-160-160c0-8.622 0.704-17.076 2.016-25.326l-431.004-215.504c-29.092 30.090-69.852 48.83-115.012 48.83-88.366 0-160-71.636-160-160 0-88.368 71.634-160 160-160 45.16 0 85.92 18.738 115.012 48.828l431.004-215.502c-1.312-8.25-2.016-16.704-2.016-25.326 0-88.368 71.634-160 160-160s160 71.632 160 160c0 88.364-71.634 160-160 160z"/>
+<glyph unicode="" d="M891.96 445.796c-18.086 0-35.348-3.52-51.064-9.856-10.506 114.358-110.29 204.060-232 204.060-29.786 0-58.682-5.63-84.318-15.164-9.96-3.702-12.578-7.52-12.578-14.916v-402.714c0-7.766 6.24-14.234 14.124-14.996 0.336-0.034 363.536-0.21 365.89-0.21 72.904 0 131.986 56.816 131.986 126.894s-59.134 126.902-132.040 126.902zM400 192h32l16 224.22-16 223.78h-32l-16-223.78zM304 192h-32l-16 162.75 16 157.25h32l16-160zM144 192h32l16 128-16 128h-32l-16-128zM16 256h32l16 64-16 64h-32l-16-64z"/>
+<glyph unicode="" d="M832 832h-640c-105.6 0-192-86.4-192-192v-384c0-105.6 86.4-192 192-192h640c105.6 0 192 86.4 192 192v384c0 105.6-86.4 192-192 192zM960 256c0-33.978-13.374-66.062-37.654-90.346-24.284-24.28-56.366-37.654-90.346-37.654h-640c-33.978 0-66.062 13.374-90.344 37.654-24.282 24.284-37.656 56.368-37.656 90.346v384c0 33.978 13.374 66.062 37.656 90.344s56.366 37.656 90.344 37.656h640c33.978 0 66.062-13.374 90.346-37.656 24.282-24.282 37.654-56.366 37.654-90.344v-384zM384 192l320 256-320 256z"/>
+<glyph unicode="" d="M576.032 512l-0.002-234.184c0-59.418-0.77-93.656 5.53-110.5 6.25-16.754 21.918-34.146 38.99-44.202 22.684-13.588 48.542-20.376 77.708-20.376 51.854 0 82.478 6.848 133.742 40.54v-153.944c-43.7-20.552-81.866-32.594-117.324-40.922-35.5-8.242-73.86-12.406-115.064-12.406-46.828 0-74.456 5.886-110.41 17.656-35.958 11.868-66.66 28.806-92.020 50.54-25.45 21.922-43.022 45.208-52.848 69.832-9.826 24.636-14.716 60.414-14.716 107.244v359.1h-137.426v145.006c40.208 13.042 85.164 31.788 113.78 56.152 28.754 24.45 51.766 53.706 69.106 87.944 17.392 34.146 29.348 77.712 35.872 130.516h165.084l-0.002-255.996h255.968v-192h-255.968z"/>
+<glyph unicode="" d="M819.58 146.372c-106.666 0.418-163.498 52.582-202.992 144.502l-11.298 25.082-97.37 222c-32.292 78.586-112.422 131.672-202.834 131.672-122.334 0-221.584-99.252-221.584-221.672 0-122.418 99.248-221.668 221.584-221.668 85.338 0 159.542 48.25 196.582 119l39.376-90.832c-55.912-68.17-140.876-111.584-235.954-111.584-168.51 0-305.090 136.502-305.090 305.086 0 168.504 136.58 305.172 305.084 305.172 127.166 0 229.836-67.918 282-188.252 3.956-9.336 55.158-129.086 99.826-231 27.668-63.082 51.26-104.918 127.756-107.504 75.168-2.582 126.756 43.168 126.756 101.008 0 56.5-39.336 70.082-105.75 92.164-119.382 39.334-181.006 78.834-181.006 173.502 0 92.336 62.832 153.918 164.914 153.918 66.496 0 114.506-29.586 147.76-88.586l-65.256-33.332c-24.59 34.414-51.708 48-86.172 48-47.912 0-82.126-33.336-82.126-77.666 0-62.922 56.294-72.422 135.006-98.254 105.958-34.5 155.208-73.92 155.208-172.336 0.002-103.336-88.672-178.588-204.42-178.42z"/>
+<glyph unicode="" d="M235.278 711.28c-50.792 0-94.484-16.806-131.084-50.416-38.086-35.852-57.14-80.66-57.14-134.436 0-35.852 10.376-69.462 31.294-100.832 18.674-29.126 38.18-47.806 63.778-56.024v-2.242c-25.598-10.448-36.966-36.59-36.966-78.418 0-32.124 11.372-56.022 36.966-71.708v-2.242c-70.654-23.146-102.992-66.094-102.992-128.83 0-54.534 23.748-94.488 70.066-119.878 36.59-20.172 83.578-30.254 140.346-30.254 138.17 0 207.406 57.882 207.406 173.664 0 72.446-53.322 116.882-160.128 133.316-24.646 3.726-43.286 12.696-55.982 26.89-9.708 9.708-14.542 19.418-14.542 29.126 0 27.632 14.958 43.696 44.82 48.176 45.558 6.714 82.728 27.824 111.486 63.296 28.75 35.48 43.13 77.118 43.13 124.916 0 14.938-4.518 30.996-10.488 48.172 19.418 4.488 33.050 8.594 43.292 12.332v115.392c-45.054-17.928-86.99-26.884-122.842-26.884-31.374 17.922-63.824 26.884-100.42 26.884zM247.602 152.23c62.744 0 94.104-19.042 94.104-57.14 0-40.336-28.754-60.492-86.264-60.492-65.724 0-98.586 19.422-98.586 58.254-0.002 39.59 30.244 59.378 90.746 59.378zM239.76 448c-47.054 0-70.586 25.764-70.586 77.308 0 55.262 23.532 82.906 70.586 82.906 22.402 0 39.958-8.596 52.652-25.768 10.458-15.69 15.69-34.36 15.69-56.022 0-52.278-22.786-78.424-68.342-78.424zM580.384 960c-21.656 0-40.14-8.214-55.454-24.648-15.314-16.436-22.97-36.216-22.97-59.376 0-22.41 7.658-41.82 22.97-58.258 15.308-16.434 33.792-24.64 55.454-24.64 20.91 0 39.028 8.208 54.34 24.64 15.312 16.438 22.96 35.848 22.96 58.258 0 23.16-7.648 42.944-22.96 59.376-15.318 16.434-33.43 24.648-54.34 24.648zM643.13 704.002h-126.606c1.496-14.336-0.64-36.042-0.64-71.14v-348.432c0-35.856 2.136-64.774 0.64-76.036h126.606c-1.5 16.376-5.394 44.668-5.394 82.758v343.946c-0.006 32.864 3.894 54.568 5.394 68.904zM922.336 315.8c-32.872 0-49.082 25.028-49.082 75.066v206.64h49.864c8.96 0 17.032 0.492 27.118-0.246 10.086-0.748 14.152-0.25 19.278-0.25v106.99h-96.258v47.616c0 17.922 2.816 34.302 5.054 44.542h-129.958c2.242-10.24 2.028-25.876 2.028-46.79v-45.368h-56.32v-106.988c15.364 2.24 29.090 3.356 38.796 3.356l17.524-1.118v-202.786c0-62.742 7.958-108.672 23.636-137.8 20.922-38.84 57.622-58.258 112.136-58.258 38.848 0 73.118 7.464 98.714 22.41v112.032c-20.474-12.7-39.382-19.048-62.53-19.048z"/>
+<glyph unicode="" d="M761.862-64c113.726 206.032 132.888 520.306-313.862 509.824v-253.824l-384 384 384 384v-248.372c534.962 13.942 594.57-472.214 313.862-775.628z"/>
+<glyph unicode="" d="M192 448h192v-64h-192v64zM192 320h192v-64h-192v64zM192 192h192v-64h-192v64zM903.432 760.57l-142.864 142.862c-31.112 31.112-92.568 56.568-136.568 56.568h-480c-44 0-80-36-80-80v-864c0-44 36-80 80-80h736c44 0 80 36 80 80v608c0 44-25.456 105.458-56.568 136.57zM858.178 715.314c3.13-3.13 6.25-6.974 9.28-11.314h-163.458v163.456c4.34-3.030 8.184-6.15 11.314-9.28l142.864-142.862zM896 16c0-8.672-7.328-16-16-16h-736c-8.672 0-16 7.328-16 16v864c0 8.672 7.328 16 16 16h480c4.832 0 10.254-0.61 16-1.704v-254.296h254.296c1.094-5.746 1.704-11.166 1.704-16v-608zM576 256h188.792c-14.826-73.032-79.386-128-156.792-128-88.366 0-160 71.634-160 160 0 77.406 54.97 141.966 128 156.792v-188.792zM672 512c88.366 0 160-71.634 160-160 0-10.958-1.108-21.66-3.208-32h-188.792v188.792c10.34 2.1 21.042 3.208 32 3.208z"/>
+<glyph unicode="" d="M459.33 300.168l-170.654-295.668h564.654l170.67 295.668zM975.612 384l-292.934 507.5h-341.348l292.956-507.5zM292.836 807.486l-292.836-507.318 170.676-295.668 292.832 507.324z"/>
+<glyph unicode="" d="M575.87-64h-191.87v512h-128v176.45l128 0.058-0.208 103.952c0 143.952 39.034 231.54 208.598 231.54h141.176v-176.484h-88.23c-66.032 0-69.206-24.656-69.206-70.684l-0.262-88.324h158.69l-18.704-176.45-139.854-0.058-0.13-512z"/>
+<glyph unicode="" d="M800 544c-70.58 0-128-57.42-128-128s57.42-128 128-128c70.58 0 128 57.42 128 128 0 70.58-57.42 128-128 128zM800 640v0c123.71 0 224-100.288 224-224 0-123.71-100.29-224-224-224-123.71 0-224 100.29-224 224 0 123.712 100.29 224 224 224zM0 416c0 123.712 100.288 224 224 224s224-100.288 224-224c0-123.712-100.288-224-224-224-123.712 0-224 100.288-224 224z"/>
+<glyph unicode="" d="M765.666 733.654c-6.882-9.804-64.486-87.23-200.556-138.228-62.704 115.206-132.206 209.85-142.782 224.012 28.776 6.936 58.798 10.64 89.674 10.64 97.23 0 186.088-36.47 253.664-96.424zM538.752 444.076c-197.352-63.796-302.372-237.678-311.042-252.522-61.116 67.842-98.374 157.576-98.374 255.858 0 3.916 0.078 7.81 0.196 11.696 16.744-0.342 202.18-2.732 393.278 54.458 10.952-21.44 21.416-43.202 31.024-64.94-5.064-1.426-10.092-2.94-15.082-4.55zM512 960c-282.77 0-512-229.23-512-512s229.23-512 512-512 512 229.23 512 512-229.23 512-512 512zM512.002 0c-247.030 0-448.002 200.97-448.002 448 0 247.028 200.972 448 448.002 448 247.032 0 447.998-200.972 447.998-448 0-247.030-200.966-448-447.998-448zM619.918 483.098c-7.946 18.66-16.482 37.198-25.388 55.428 141.484 57.762 205.61 139.964 213.676 150.878 53.292-65.116 85.566-148.052 86.412-238.372-12.618 2.668-139.13 28.328-266.458 12.316-2.682 6.552-5.426 13.134-8.242 19.75zM491.188 572.412c-178.996-47.532-336.65-46.848-353.766-46.622 24.814 118.742 104.836 217.432 211.892 267.926 10.088-13.834 78.472-108.594 141.874-221.304zM577.32 384.5c0.84 0.292 1.68 0.564 2.522 0.852 53.724-139.606 75.942-256.612 81.636-290.186-45.946-19.568-96.466-30.41-149.476-30.41-88.464 0-170.002 30.184-234.882 80.78 6.886 14.078 84.364 163.754 300.2 238.964zM651.464 403.036c119.916 19.192 225.266-12.232 238.36-16.36-17.060-106.474-78.172-198.428-164.020-256.472-3.922 23.276-24.288 135.294-74.34 272.832z"/>
+<glyph unicode="" d="M778.24 460.8c0-147.046-119.245-266.24-266.24-266.24-147.046 0-266.24 119.194-266.24 266.24 0 17.51 1.741 34.611 5.018 51.2h-179.098v-353.894c0-75.981 61.542-137.626 137.626-137.626h605.389c75.981 0 137.626 61.594 137.626 137.626v353.894h-179.046c3.226-16.589 4.966-33.69 4.966-51.2zM814.694 901.12h-605.389c-76.032 0-137.626-61.645-137.626-137.626v-149.094h222.925c48.179 68.096 127.59 112.64 217.395 112.64s169.216-44.544 217.395-112.64h222.925v149.094c0 75.981-61.645 137.626-137.626 137.626zM882.022 731.136c0-13.568-11.059-24.576-24.576-24.576h-73.677c-13.517 0-24.576 11.059-24.576 24.576v73.728c0 13.568 11.11 24.576 24.576 24.576h73.677c13.568 0 24.576-11.059 24.576-24.576v-73.728zM675.84 460.8c0 90.522-73.37 163.84-163.84 163.84s-163.84-73.318-163.84-163.84 73.37-163.84 163.84-163.84 163.84 73.318 163.84 163.84z"/>
+<glyph unicode="" d="M441.139 295.526c-26.88-140.954-59.699-276.122-156.979-346.726-30.003 213.094 44.083 373.094 78.49 542.976-58.675 98.816 7.066 297.574 130.867 248.576 152.32-60.262-131.891-367.258 58.88-405.606 199.219-40.038 280.525 345.6 157.030 470.989-178.483 181.146-519.578 4.198-477.594-255.027 10.189-63.386 75.674-82.637 26.163-170.086-114.227 25.293-148.326 115.405-143.923 235.52 7.066 196.557 176.64 334.182 346.726 353.229 215.142 24.115 417.024-78.95 444.826-281.293 31.386-228.352-97.024-475.699-327.117-457.882-62.31 4.762-88.474 35.635-137.37 65.331z"/>
+<glyph unicode="" d="M716.749 696.32l0.051-471.040-409.6 235.52z"/>
+<glyph unicode="" d="M747.52 665.549l-471.040 0.051 235.52-409.6z"/>
+<glyph unicode="" d="M276.48 256h471.040l-235.571 409.6z"/>
+<glyph unicode="" d="M307.251 696.32l-0.051-471.040 409.6 235.52z"/>
+<glyph unicode="" d="M768 163.84h-665.6v460.8h132.864c0 0 35.277 45.875 111.104 102.4h-295.168c-28.262 0-51.2-22.938-51.2-51.2v-563.2c0-28.314 22.938-51.2 51.2-51.2h768c28.314 0 51.2 22.886 51.2 51.2v191.795l-102.4-84.224v-56.371zM684.134 519.68v-181.76l339.866 266.291-339.866 255.949v-160.256c-412.774-0.051-412.774-408.064-412.774-408.064 116.838 191.898 188.723 227.84 412.774 227.84z"/>
+<glyph unicode="" d="M251.699 778.086c-82.432 0-149.299-68.762-149.299-153.6s66.867-153.6 149.299-153.6c149.248 0 49.766-296.96-149.299-296.96v-71.629c355.226 0 494.438 675.789 149.299 675.789zM681.728 778.086c-82.432 0-149.299-68.762-149.299-153.6s66.867-153.6 149.299-153.6c149.248 0 49.766-296.96-149.299-296.96v-71.629c355.277 0 494.49 675.789 149.299 675.789z"/>
+<glyph unicode="" d="M887.757 752.23c0 58.88-88.678 65.434-88.678 65.434l-208.333 13.107c0 0-4.454 56.73-46.592 76.39-42.086 19.61-88.371 13.67-121.6 13.312s-41.165-42.803-41.165-82.842 0.768-86.118 0.768-115.2c0-52.019-22.886-74.189-80.179-74.189h-118.118c-33.024 2.15-58.778-3.277-58.778-30.003 0-26.675 38.912-254.566 92.262-307.149 30.976-30.515 220.365-51.917 260.25-51.917s26.624 117.811 37.683 117.811 23.296-66.56 86.118-82.125c62.822-15.667 146.688-12.749 151.142-57.446 5.786-58.982 11.059-135.322-27.75-140.749l-87.808-3.482c-60.211 4.352-44.083 70.093-17.51 70.093s39.885 1.075 39.885 1.075l3.328 71.987c0 0-137.882 16.282-143.77-76.749-5.325-84.992 9.267-125.082 19.968-133.786 10.752-8.806 29.286-25.805 198.144-25.805 237.926 0 150.733 693.402 150.733 752.23zM790.528 444.365c-9.318-9.933-43.11 16.435-75.366 16.435-32.307 0-67.174-17.101-75.366-5.888-8.141 11.264 7.526 101.99 68.352 101.99s91.75-102.554 82.381-112.538zM297.114 759.501c0 11.622 3.021 152.781 3.021 152.781l-179.507-176.077c0 0 124.006 0 148.326 0 24.422 0 28.16 11.622 28.16 23.296z"/>
+<glyph unicode="" d="M903.454 441.796c-26.543-6.081-52.227-8.762-75.342-8.762-130.026 0-230.131 90.233-230.131 247.123 0 76.899 29.922 116.895 72.235 116.895 40.254 0 67.090-35.891 67.090-108.692 0-41.392-11.156-86.763-19.394-113.606 0 0 40.066-69.45 149.637-48.136 23.262 51.319 35.905 117.781 35.905 176.089 0 156.89-80.501 248.15-228.078 248.15-151.689 0-240.435-115.875-240.435-268.661 0-151.361 71.202-281.283 188.627-340.484-49.372-98.143-112.203-184.592-177.722-249.727-118.891 142.795-226.354 333.3-270.462 705.060h-175.384c81.025-619.156 322.514-816.307 386.365-854.159 36.114-21.537 67.216-20.511 100.237-2.039 51.843 29.293 207.512 183.943 293.793 365.093 36.219 0.098 79.733 4.231 123.059 13.956v121.9z" horiz-adv-x="903"/>
+<glyph unicode="" d="M1536.164 933.646c0-5.825-1.738-11.090-5.117-15.924-3.521-4.699-7.177-7.119-11.354-7.119-33.637-3.411-61.339-14.784-82.689-34.274-21.485-19.341-43.497-56.467-66.346-111.073l-348.57-826.872c-2.291-7.698-8.644-11.528-19.18-11.528-8.251 0-14.591 3.83-19.187 11.528l-195.493 430.357-224.83-430.357c-4.596-7.698-10.935-11.528-19.18-11.528-10.002 0-16.619 3.83-19.863 11.528l-342.5 826.872c-21.343 51.343-43.909 87.181-67.685 107.52-23.647 20.339-56.756 32.993-99.037 37.827-3.643 0-7.157 1.995-10.273 6.121-3.231 3.978-4.859 8.676-4.859 13.941 0 13.504 3.643 20.191 10.948 20.191 30.534 0 62.42-1.422 95.787-4.261 30.946-2.993 60.129-4.415 87.419-4.415 27.831 0 60.663 1.422 98.496 4.415 39.584 2.838 74.707 4.261 105.248 4.261 7.292 0 10.948-6.687 10.948-20.191 0-13.375-2.304-20.062-6.758-20.062-30.534-2.414-54.587-10.659-72.152-24.452-17.559-13.948-26.351-32.156-26.351-54.761 0-11.521 3.656-25.874 10.948-43.092l283.047-672.862 160.646 319.439-149.704 330.381c-26.878 58.88-49.039 96.997-66.334 114.053-17.295 16.934-43.497 27.451-78.634 31.294-3.238 0-6.218 1.995-9.198 6.121-2.961 3.971-4.441 8.676-4.441 13.935 0 13.51 3.102 20.197 9.59 20.197 30.528 0 58.494-1.422 84.027-4.261 24.587-2.993 50.803-4.415 78.634-4.415 27.29 0 56.209 1.422 86.743 4.415 31.48 2.838 62.42 4.261 92.955 4.261 7.292 0 10.948-6.687 10.948-20.191 0-13.375-2.163-20.062-6.745-20.062-61.081-4.403-91.616-22.611-91.616-54.755 0-14.36 7.022-36.688 21.214-66.849l99.031-211.623 98.509 193.568c13.639 27.297 20.519 50.339 20.519 69.108 0 44.096-30.528 67.563-91.609 70.543-5.542 0-8.245 6.687-8.245 20.062 0 4.834 1.365 9.391 4.068 13.652 2.826 4.415 5.542 6.546 8.232 6.546 21.89 0 48.782-1.422 80.674-4.261 30.528-2.993 55.643-4.415 75.242-4.415 14.051 0 34.86 1.281 62.156 3.694 34.583 3.27 63.617 4.982 86.866 4.982 5.419 0 8.097-5.696 8.097-17.211 0-15.357-4.988-23.042-14.984-23.042-35.542-3.843-64.177-14.224-85.791-30.998-21.626-16.786-48.653-54.909-80.938-114.349l-131.328-255.57 177.806-381.306 262.502 642.572c9.050 23.461 13.652 45.074 13.652 64.705 0 47.076-30.534 71.965-91.596 74.946-5.542 0-8.232 6.687-8.232 20.062 0 13.504 4.036 20.191 12.287 20.191 22.283 0 48.775-1.422 79.309-4.261 28.23-2.993 52.006-4.415 71.058-4.415 20.14 0 43.388 1.422 69.713 4.415 27.438 2.838 52.025 4.261 73.929 4.261 6.327 0 9.571-5.696 9.571-17.211z" horiz-adv-x="1536"/>
+<glyph unicode="" d="M403.359-73.143l403.246 513.349-403.246 510.495-403.359 0.156 403.223-510.841-400.709-513.159h400.845" horiz-adv-x="807"/>
+<glyph unicode="" d="M403.246 950.857l-403.246-513.349 403.246-510.495 403.359-0.156-403.223 510.841 400.709 513.159h-400.845" horiz-adv-x="807"/>
+<glyph unicode="" d="M554.857 240v-120.571q0-38.286-22.286-38.286-13.143 0-25.714 12.571v172q12.571 12.571 25.714 12.571 22.286 0 22.286-38.286zM748 239.428v-26.286h-51.429v26.286q0 38.857 25.714 38.857t25.714-38.857zM196 364h61.143v53.714h-178.286v-53.714h60v-325.143h57.143v325.143zM360.571 38.857h50.857v282.286h-50.857v-216q-17.143-24-32.571-24-10.286 0-12 12-0.571 1.714-0.571 20v208h-50.857v-223.429q0-28 4.571-41.714 6.857-21.143 33.143-21.143 27.429 0 58.286 34.857v-30.857zM605.714 123.428v112.571q0 41.714-5.143 56.571-9.714 32-40.571 32-28.571 0-53.143-30.857v124h-50.857v-378.857h50.857v27.429q25.714-31.429 53.143-31.429 30.857 0 40.571 31.429 5.143 15.429 5.143 57.143zM798.857 129.143v7.429h-52q0-29.143-1.143-34.857-4-20.571-22.857-20.571-26.286 0-26.286 39.429v49.714h102.286v58.857q0 45.143-15.429 66.286-22.286 29.143-60.571 29.143-38.857 0-61.143-29.143-16-21.143-16-66.286v-98.857q0-45.143 16.571-66.286 22.286-29.143 61.714-29.143 41.143 0 61.714 30.286 10.286 15.429 12 30.857 1.143 5.143 1.143 33.143zM451.429 650.857v120q0 39.429-24.571 39.429t-24.571-39.429v-120q0-40 24.571-40t24.571 40zM862.286 221.714q0-133.714-14.857-200-8-33.714-33.143-56.571t-58.286-26.286q-105.143-12-317.143-12t-317.143 12q-33.143 3.429-58.571 26.286t-32.857 56.571q-14.857 64-14.857 200 0 133.714 14.857 200 8 33.714 33.143 56.571t58.857 26.857q104.571 11.429 316.571 11.429t317.143-11.429q33.143-4 58.571-26.857t32.857-56.571q14.857-64 14.857-200zM292 950.857h58.286l-69.143-228v-154.857h-57.143v154.857q-8 42.286-34.857 121.143-21.143 58.857-37.143 106.857h60.571l40.571-150.286zM503.429 760.571v-100q0-46.286-16-67.429-21.143-29.143-60.571-29.143-38.286 0-60 29.143-16 21.714-16 67.429v100q0 45.714 16 66.857 21.714 29.143 60 29.143 39.429 0 60.571-29.143 16-21.143 16-66.857zM694.857 853.143v-285.143h-52v31.429q-30.286-35.429-58.857-35.429-26.286 0-33.714 21.143-4.571 13.714-4.571 42.857v225.143h52v-209.714q0-18.857 0.571-20 1.714-12.571 12-12.571 15.429 0 32.571 24.571v217.714h52z" horiz-adv-x="878"/>
+<glyph unicode="" d="M847.429 365.714l-335.429-335.429q-29.714-30.286-72.857-30.286t-73.429 30.286l-335.429 335.429q-30.286 30.286-30.286 73.143t30.286 73.143l335.429 335.429q30.286 30.286 73.143 30.286t73.143-30.286l151.429-151.429-227.429-228-107.429 107.429q-24 24-56.571 24-33.714 0-57.143-23.429l-68.571-69.143q-24-22.857-24-56.571 0-33.143 24-57.143l232-233.143q17.143-16 38.286-21.143l3.429-2.286h16q34.286 0 56.571 23.429l353.714 353.714 1.143-1.714q30.286-30.286 30.286-73.143t-30.286-73.143zM803.429 723.428l68.571-68.571q8-8.571 8-20.571t-8-20.571l-417.143-417.143q-9.714-8.571-21.143-8.571v0q-2.286 0-3.429 0.571-10.286 1.143-17.143 8l-232.571 233.143q-8 8.571-8 20.571t8 20l69.143 68.571q7.429 8.571 20 8.571t20.571-8.571l144-144 328 328.571q8.571 8.571 20.571 8.571t20.571-8.571z" horiz-adv-x="951"/>
+<glyph unicode="" d="M530.286 475.428q0 8-5.143 13.143t-13.143 5.143q-37.714 0-64.571-26.857t-26.857-64.571q0-8 5.143-13.143t13.143-5.143 13.143 5.143 5.143 13.143q0 22.857 16 38.857t38.857 16q8 0 13.143 5.143t5.143 13.143zM658.286 401.143q0-60.571-42.857-103.429t-103.429-42.857-103.429 42.857-42.857 103.429 42.857 103.429 103.429 42.857 103.429-42.857 42.857-103.429zM73.143 73.143h877.714v73.143h-877.714v-73.143zM731.429 401.143q0 90.857-64.286 155.143t-155.143 64.286-155.143-64.286-64.286-155.143 64.286-155.143 155.143-64.286 155.143 64.286 64.286 155.143zM146.286 768h219.429v73.143h-219.429v-73.143zM73.143 658.286h877.714v146.286h-473.143l-36.571-73.143h-368v-73.143zM1024 804.571v-731.429q0-30.286-21.429-51.714t-51.714-21.429h-877.714q-30.286 0-51.714 21.429t-21.429 51.714v731.429q0 30.286 21.429 51.714t51.714 21.429h877.714q30.286 0 51.714-21.429t21.429-51.714z"/>
+<glyph unicode="" d="M73.143 73.143h585.143v438.857h-237.714q-22.857 0-38.857 16t-16 38.857v237.714h-292.571v-731.429zM438.857 585.143h214.857q-5.714 16.571-12.571 23.429l-178.857 178.857q-6.857 6.857-23.429 12.571v-214.857zM731.429 566.857v-512q0-22.857-16-38.857t-38.857-16h-621.714q-22.857 0-38.857 16t-16 38.857v768q0 22.857 16 38.857t38.857 16h365.714q22.857 0 50.286-11.429t43.429-27.429l178.286-178.286q16-16 27.429-43.429t11.429-50.286z" horiz-adv-x="731"/>
+<glyph unicode="" d="M385.714 217.143q12 19.429 6.286 39.429t-25.714 28.571q-19.429 8-41.714 0.571t-34.286-26.286q-12.571-19.429-7.429-39.143t24.571-28.857 42.571-1.429 35.714 27.143zM439.429 286.286q4.571 7.429 2 15.143t-10 10.571q-8 2.857-16.286-0.286t-12.286-10.571q-9.714-17.714 7.429-25.714 8-2.857 16.571 0.286t12.571 10.571zM538.857 225.143q-25.714-58.286-90.286-85.714t-128-6.857q-61.143 19.429-84.286 72.286t3.714 107.143q26.857 53.143 86.571 79.429t120.286 10.857q63.429-16.571 90.571-68.286t1.429-108.857zM717.143 316.571q-5.143 54.857-50.857 97.143t-119.143 62.286-156.857 12q-127.429-13.143-211.143-80.857t-75.714-151.143q5.143-54.857 50.857-97.143t119.143-62.286 156.857-12q127.429 13.143 211.143 80.857t75.714 151.143zM893.143 314.286q0-38.857-21.143-79.714t-62.286-78.286-96.286-67.143-129.143-47.429-154.571-17.714-157.143 19.143-137.429 53.143-98 86.286-37.143 114q0 65.714 39.714 140t112.857 147.429q96.571 96.571 195.143 134.857t140.857-4q37.143-36.571 11.429-119.429-2.286-8-0.571-11.429t5.714-4 8.286 0.286 7.714 2l3.429 1.143q79.429 33.714 140.571 33.714t87.429-34.857q25.714-36 0-101.714-1.143-7.429-2.571-11.429t2.571-7.143 6.857-4.286 9.714-3.429q32.571-10.286 58.857-26.857t45.714-46.571 19.429-66.571zM850.857 670.857q24-26.857 31.143-62t-3.714-67.143q-4.571-13.143-16.857-19.429t-25.429-2.286q-13.143 4.571-19.429 16.857t-2.286 25.429q11.429 36-13.714 63.429t-61.143 20q-13.714-2.857-25.714 4.571t-14.286 21.143q-2.857 13.714 4.571 25.429t21.143 14.571q34.286 7.429 68-3.143t57.714-37.429zM954.286 764q49.714-54.857 64.286-127.143t-7.714-138q-5.143-15.429-19.429-22.857t-29.714-2.286-22.857 19.429-2.857 29.714q16 46.857 5.714 98.286t-45.714 90.286q-35.429 39.429-84.571 54.571t-98.857 4.857q-16-3.429-29.714 5.429t-17.143 24.857 5.429 29.429 24.857 16.857q70.286 14.857 139.429-6.571t118.857-76.857z"/>
+<glyph unicode="" d="M1024 438.857q0-14.857-10.857-25.714l-146.286-146.286q-10.857-10.857-25.714-10.857t-25.714 10.857-10.857 25.714v73.143h-585.143v-73.143q0-14.857-10.857-25.714t-25.714-10.857-25.714 10.857l-146.286 146.286q-10.857 10.857-10.857 25.714t10.857 25.714l146.286 146.286q10.857 10.857 25.714 10.857t25.714-10.857 10.857-25.714v-73.143h585.143v73.143q0 14.857 10.857 25.714t25.714 10.857 25.714-10.857l146.286-146.286q10.857-10.857 10.857-25.714z"/>
+<glyph unicode="" d="M402.286 768q0-14.857-10.857-25.714t-25.714-10.857h-73.143v-585.143h73.143q14.857 0 25.714-10.857t10.857-25.714-10.857-25.714l-146.286-146.286q-10.857-10.857-25.714-10.857t-25.714 10.857l-146.286 146.286q-10.857 10.857-10.857 25.714t10.857 25.714 25.714 10.857h73.143v585.143h-73.143q-14.857 0-25.714 10.857t-10.857 25.714 10.857 25.714l146.286 146.286q10.857 10.857 25.714 10.857t25.714-10.857l146.286-146.286q10.857-10.857 10.857-25.714z" horiz-adv-x="439"/>
+<glyph unicode="" d="M431.429 347.428q0-7.429-5.714-13.143l-189.714-189.714 82.286-82.286q10.857-10.857 10.857-25.714t-10.857-25.714-25.714-10.857h-256q-14.857 0-25.714 10.857t-10.857 25.714v256q0 14.857 10.857 25.714t25.714 10.857 25.714-10.857l82.286-82.286 189.714 189.714q5.714 5.714 13.143 5.714t13.143-5.714l65.143-65.143q5.714-5.714 5.714-13.143zM877.714 841.143v-256q0-14.857-10.857-25.714t-25.714-10.857-25.714 10.857l-82.286 82.286-189.714-189.714q-5.714-5.714-13.143-5.714t-13.143 5.714l-65.143 65.143q-5.714 5.714-5.714 13.143t5.714 13.143l189.714 189.714-82.286 82.286q-10.857 10.857-10.857 25.714t10.857 25.714 25.714 10.857h256q14.857 0 25.714-10.857t10.857-25.714z" horiz-adv-x="878"/>
+<glyph unicode="" d="M438.857 402.286v-256q0-14.857-10.857-25.714t-25.714-10.857-25.714 10.857l-82.286 82.286-189.714-189.714q-5.714-5.714-13.143-5.714t-13.143 5.714l-65.143 65.143q-5.714 5.714-5.714 13.143t5.714 13.143l189.714 189.714-82.286 82.286q-10.857 10.857-10.857 25.714t10.857 25.714 25.714 10.857h256q14.857 0 25.714-10.857t10.857-25.714zM870.286 786.286q0-7.429-5.714-13.143l-189.714-189.714 82.286-82.286q10.857-10.857 10.857-25.714t-10.857-25.714-25.714-10.857h-256q-14.857 0-25.714 10.857t-10.857 25.714v256q0 14.857 10.857 25.714t25.714 10.857 25.714-10.857l82.286-82.286 189.714 189.714q5.714 5.714 13.143 5.714t13.143-5.714l65.143-65.143q5.714-5.714 5.714-13.143z" horiz-adv-x="878"/>
+</font></defs></svg> \ No newline at end of file
diff --git a/www/wiki/extensions/ModernTimeline/resources/vendor/tl-icons.ttf b/www/wiki/extensions/ModernTimeline/resources/vendor/tl-icons.ttf
new file mode 100644
index 00000000..74eb8c4a
--- /dev/null
+++ b/www/wiki/extensions/ModernTimeline/resources/vendor/tl-icons.ttf
Binary files differ
diff --git a/www/wiki/extensions/ModernTimeline/resources/vendor/tl-icons.woff b/www/wiki/extensions/ModernTimeline/resources/vendor/tl-icons.woff
new file mode 100644
index 00000000..d065e98e
--- /dev/null
+++ b/www/wiki/extensions/ModernTimeline/resources/vendor/tl-icons.woff
Binary files differ