├── .gitignore
├── Gruntfile.js
├── LICENSE
├── README.md
├── build
├── demo.html
├── equirectangular-screenshot.jpg
└── overpass-clip.mp4
├── demo
├── build
└── js
│ ├── jquery-1.7.2.min.js
│ └── three.min.js
├── package.json
└── src
├── css
├── font-awesome.less
├── fonts
│ ├── FontAwesome.otf
│ ├── fontawesome-webfont.eot
│ ├── fontawesome-webfont.svg
│ ├── fontawesome-webfont.ttf
│ └── fontawesome-webfont.woff
└── valiant360.less
├── lib
├── Detector.js
├── Three.js
├── modernizr-latest.js
├── require.js
└── stats.min.js
└── valiant.jquery.js
/.gitignore:
--------------------------------------------------------------------------------
1 | videos/*
2 | node_modules/*
3 | css/main.css
4 | build/*
--------------------------------------------------------------------------------
/Gruntfile.js:
--------------------------------------------------------------------------------
1 | /*global module:false*/
2 | module.exports = function(grunt) {
3 |
4 | // Project configuration.
5 | grunt.initConfig({
6 | // Metadata.
7 | pkg: grunt.file.readJSON('package.json'),
8 | banner: '/*! <%= pkg.title || pkg.name %> - v<%= pkg.version %> - <%= grunt.template.today("yyyy-mm-dd") %>\n' +
9 | ' <%= pkg.homepage ? "* " + pkg.homepage + "\\n" : "" %>' +
10 | ' * Copyright (c) <%= grunt.template.today("yyyy") %> <%= pkg.author %>;' +
11 | ' Licensed <%= pkg.license %> */\n\n',
12 | // Task configuration.
13 | concat: {
14 | options: {
15 | banner: '<%= banner %>'
16 | },
17 | dist: {
18 | src: ['src/lib/Detector.js', 'src/valiant.jquery.js'],
19 | dest: 'build/<%= pkg.name %>.js'
20 | }
21 | },
22 | copy: {
23 | build: {
24 | files: [
25 | { expand: true, cwd: 'src/css/fonts/', src: ["*"], dest: "build/css/fonts/" },
26 | { expand: true, cwd: 'demo/js', src: ["three.min.js", "jquery-1.7.2.min.js"], dest: "build/js" },
27 | { src: ['README.md'], dest: "build/README.md" }
28 | ]
29 | },
30 | to_site: {
31 | files: [
32 | { expand: true, cwd: 'build/', src: ["**"], dest: "../flimshaw.github.io/Valiant360/build" },
33 | { expand: true, cwd: 'dist/', src: ["<%= pkg.name %>_<%= pkg.version %>.zip"], dest: "../flimshaw.github.io/Valiant360/download/" }
34 | ]
35 | }
36 | },
37 | bump: {
38 | options: {
39 | commit: false,
40 | push: false
41 | }
42 | },
43 | uglify: {
44 | options: {
45 | banner: '<%= banner %>'
46 | },
47 | dist: {
48 | src: '<%= concat.dist.dest %>',
49 | dest: 'build/<%= pkg.name %>.min.js'
50 | }
51 | },
52 | jshint: {
53 | options: {
54 | curly: true,
55 | eqeqeq: true,
56 | immed: true,
57 | latedef: true,
58 | newcap: true,
59 | noarg: true,
60 | sub: true,
61 | undef: true,
62 | unused: true,
63 | boss: true,
64 | eqnull: true,
65 | laxcomma: true,
66 | multistr: true,
67 | browser: true,
68 | globals: {
69 | jQuery: true,
70 | console: true,
71 | THREE: true,
72 | Detector: true,
73 | requestAnimationFrame: true
74 | }
75 | },
76 | lib: {
77 | src: 'src/valiantRefactor.js'
78 | },
79 | gruntfile: {
80 | src: 'Gruntfile.js'
81 | },
82 | lib_test: {
83 | src: ['lib/**/*.js', 'test/**/*.js']
84 | }
85 | },
86 | qunit: {
87 | files: ['test/**/*.html']
88 | },
89 | requirejs: {
90 | compile: {
91 | options: {
92 | baseUrl: "src/",
93 | mainConfigFile: "src/main.js",
94 | name: "main", // assumes a production build using almond
95 | out: "demo/valiant360.min.js"
96 | }
97 | }
98 | },
99 | watch: {
100 | gruntfile: {
101 | files: '<%= jshint.gruntfile.src %>',
102 | tasks: ['jshint:gruntfile']
103 | },
104 | lib_test: {
105 | files: '<%= jshint.lib_test.src %>',
106 | tasks: ['jshint:lib_test', 'qunit']
107 | },
108 | main_css: {
109 | files: 'src/css/*.less',
110 | tasks: ['less']
111 | },
112 | dev_js: {
113 | files: ['src/valiant.jquery.js', 'src/valiantRefactor.js'],
114 | tasks: ['jshint:lib', 'concat']
115 | }
116 | },
117 | // gzip assets 1-to-1 for production
118 | compress: {
119 | main: {
120 | options: {
121 | archive: 'dist/<%= pkg.name %>_<%= pkg.version %>.zip'
122 | },
123 | files: [
124 | { expand: true, cwd: 'build/', src: ['**/*'], dest: './valiant360' }
125 | ]
126 | }
127 | },
128 | rsync: {
129 | options: {
130 | args: ["--progress"],
131 | exclude: [".git*","*.scss","node_modules"],
132 | recursive: true
133 | },
134 | demo: {
135 | options: {
136 | src: "build/demo/",
137 | dest: "../flimshaw.github.io/Valiant360/demo"
138 | }
139 | }
140 | },
141 | less: {
142 | development: {
143 | options: {
144 | paths: ["src/css"],
145 | cleancss: true
146 | },
147 | files: {
148 | "build/css/valiant360.css": "src/css/valiant360.less"
149 | }
150 | },
151 | }
152 | });
153 |
154 | // These plugins provide necessary tasks.
155 | grunt.loadNpmTasks('grunt-contrib-compress');
156 | grunt.loadNpmTasks('grunt-contrib-concat');
157 | grunt.loadNpmTasks('grunt-contrib-uglify');
158 | grunt.loadNpmTasks('grunt-contrib-qunit');
159 | grunt.loadNpmTasks('grunt-contrib-jshint');
160 | grunt.loadNpmTasks('grunt-contrib-watch');
161 | grunt.loadNpmTasks('grunt-contrib-less');
162 | grunt.loadNpmTasks('grunt-contrib-copy');
163 | grunt.loadNpmTasks('grunt-contrib-requirejs');
164 | grunt.loadNpmTasks('grunt-rsync');
165 | grunt.loadNpmTasks('grunt-bump');
166 |
167 | // Default task.
168 | grunt.registerTask('default', ['jshint', 'less']);
169 | grunt.registerTask('build', ['default', 'copy:build', 'concat', 'uglify']);
170 | grunt.registerTask('deploy', ['default', 'copy:build', 'concat', 'uglify', 'compress', 'copy:to_site']);
171 |
172 | };
173 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License
2 |
3 | Copyright (c) 2014 Charlie Hoey
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in
13 | all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | THE SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## Valiant 360 (beta)
2 |
3 | ### A browser-based video player for 360 degree panorama videos and photos.
4 |
5 | [Example](http://flimshaw.github.io/Valiant360) - [Development Log](https://github.com/flimshaw/Valiant360/wiki/Development-log)
6 |
7 |
8 | #### About
9 |
10 | The aim of this project is to provide a free, minimalist 360 degree video WebGL player for modern browsers. It is implemented as a jQuery plugin, with a limited interface for controlling video playback, and mouse/scrollwheel controls for zooming and panning.
11 |
12 | There is currently no mobile support, but as Chrome and Safari mobile editions enable WebGL, this should be forwards-compatible with them.
13 |
14 | #### Usage
15 |
16 | See the [demo folder](https://github.com/flimshaw/Valiant360/tree/master/demo) or the [example](http://flimshaw.github.io/Valiant360). Moving the mouse will pan the camera, and the scroll wheel will zoom in and out.
17 |
18 | **Markup**
19 |
20 | On the HTML side, create a div to act as your container, and add a data-video-src attribute pointing to the video file you wish to play.
21 |
22 | ```
23 |
24 | ```
25 |
26 | Or, if you wish to use it to view a photo (note: currently must be powers-of-2 resolution (ie. 2048x1024):
27 | ```
28 |
29 | ```
30 |
31 | **Javascript**
32 |
33 | More detailed api documentation pending, for now the below explains about all you can do.
34 |
35 | ```
36 | // initialize plugin, default options shown
37 | $('.valiantContainer').Valiant360({
38 | crossOrigin: 'anonymous', // valid keywords: 'anonymous' or 'use-credentials'
39 | clickAndDrag: false, // use click-and-drag camera controls
40 | keyboardControls: true, // use keyboard controls (move by arrows)
41 | flatProjection: false, // map image to appear flat (often more distorted)
42 | fov: 35, // initial field of view
43 | fovMin: 3, // min field of view allowed
44 | fovMax: 100, // max field of view allowed
45 | hideControls: false, // hide player controls
46 | lon: 0, // initial lon for camera angle
47 | lat: 0, // initial lat for camera angle
48 | loop: "loop", // video loops by default
49 | muted: true, // video muted by default
50 | volume: 0.5, // video volume by default
51 | autoplay: true // video autoplays by default
52 | });
53 |
54 | // play video
55 | $('.valiantContainer').Valiant360('play');
56 |
57 | // pause video
58 | $('.valiantContainer').Valiant360('pause');
59 |
60 | // load new video file
61 | $('.valiantContainer').Valiant360('loadVideo', 'path/to/file.mp4');
62 |
63 | // load new photo file
64 | $('.valiantContainer').Valiant360('loadPhoto', 'path/to/file.jpg');
65 |
66 | // destroy Valiant360 processing/resources (however, will not remove element from the dom. That is left up to you)
67 | $('.valiantContainer').Valiant360('destroy');
68 |
69 | ```
70 |
71 | #### A note on the crossOrigin CORS option
72 | Allows images and videos to be served from a domain separate to where Valiant360 is hosted (eg a CDN). **If a crossOrigin keyword is not specified, anonymous is used**.
73 |
74 | This option will allow Valiant360 to grab cross-domain assets for Chrome and Firefox, however at time of writing Safari throws the error: `[Error] SecurityError: DOM Exception 18: An attempt was made to break through the security policy of the user agent.`
75 |
76 | Cross-domain tested on Mac OSX Yosemite: Chrome v43.0.2357.130, Chrome Canary v45.0.2449.0, Firefox v39.0, Safari v8.0.6.
77 |
78 | For further explanation on these CORS keywords, see:
79 | * [MDN CORS settings attributes](https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_settings_attributes)
80 | * [WHATWG CORS settings attributes](https://html.spec.whatwg.org/multipage/infrastructure.html#cors-settings-attribute)
81 |
82 | #### 3rd party libraries and their licenses
83 |
84 | The following assets are used in this tool's creation.
85 |
86 | + [JQuery 1.7.2+](http://jquery.com) (MIT License)
87 | + [Three.js](http://threejs.org/) + Detector (MIT License)
88 | + [Font Awesome](http://fortawesome.github.io/Font-Awesome/) (MIT License)
--------------------------------------------------------------------------------
/build/demo.html:
--------------------------------------------------------------------------------
1 |
11 |
12 |
13 |
14 |
15 | Valiant 360 Pano Player
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
39 |
40 |
50 |
51 |
52 |
--------------------------------------------------------------------------------
/build/equirectangular-screenshot.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flimshaw/Valiant360/79f1b528886ab580e1624ef3b5857dbd541d62c4/build/equirectangular-screenshot.jpg
--------------------------------------------------------------------------------
/build/overpass-clip.mp4:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flimshaw/Valiant360/79f1b528886ab580e1624ef3b5857dbd541d62c4/build/overpass-clip.mp4
--------------------------------------------------------------------------------
/demo/build:
--------------------------------------------------------------------------------
1 | ../build
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "jquery.valiant360",
3 | "description": "jQuery plugin for playing 360 degree videos using WebGL",
4 | "homepage": "http://flimshaw.github.io/Valiant360",
5 | "version": "0.4.3",
6 | "author": "Charlie Hoey ",
7 | "license": "MIT",
8 | "engines": {
9 | "node": ">= 0.10.0"
10 | },
11 | "devDependencies": {
12 | "grunt": "~0.4.2",
13 | "grunt-contrib-jshint": "~0.7.2",
14 | "grunt-contrib-watch": "~0.5.3",
15 | "grunt-contrib-qunit": "~0.3.0",
16 | "grunt-contrib-concat": "~0.3.0",
17 | "grunt-contrib-uglify": "~0.2.7",
18 | "grunt-contrib-less": "~0.8.3",
19 | "grunt-contrib-copy": "~0.5.0",
20 | "grunt-contrib-requirejs": "~0.4.1",
21 | "grunt-rsync": "~0.2.1",
22 | "grunt-bump": "0.0.13",
23 | "grunt-contrib-compress": "~0.6.0",
24 | "grunt-lib-contrib": "~0.6.1",
25 | "less": "~1.5.1"
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/css/font-awesome.less:
--------------------------------------------------------------------------------
1 | /*!
2 | * Font Awesome 4.0.3 by @davegandy - http://fontawesome.io - @fontawesome
3 | * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License)
4 | */
5 | /* FONT PATH
6 | * -------------------------- */
7 | @font-face {
8 | font-family: 'FontAwesome';
9 | src: url('fonts/fontawesome-webfont.eot?v=4.0.3');
10 | src: url('fonts/fontawesome-webfont.eot?#iefix&v=4.0.3') format('embedded-opentype'), url('fonts/fontawesome-webfont.woff?v=4.0.3') format('woff'), url('fonts/fontawesome-webfont.ttf?v=4.0.3') format('truetype'), url('fonts/fontawesome-webfont.svg?v=4.0.3#fontawesomeregular') format('svg');
11 | font-weight: normal;
12 | font-style: normal;
13 | }
14 | .fa {
15 | display: inline-block;
16 | font-family: FontAwesome;
17 | font-style: normal;
18 | font-weight: normal;
19 | line-height: 1;
20 | -webkit-font-smoothing: antialiased;
21 | -moz-osx-font-smoothing: grayscale;
22 | }
23 | /* makes the font 33% larger relative to the icon container */
24 | .fa-lg {
25 | font-size: 1.3333333333333333em;
26 | line-height: 0.75em;
27 | vertical-align: -15%;
28 | }
29 | .fa-2x {
30 | font-size: 2em;
31 | }
32 | .fa-3x {
33 | font-size: 3em;
34 | }
35 | .fa-4x {
36 | font-size: 4em;
37 | }
38 | .fa-5x {
39 | font-size: 5em;
40 | }
41 | .fa-fw {
42 | width: 1.2857142857142858em;
43 | text-align: center;
44 | }
45 | .fa-ul {
46 | padding-left: 0;
47 | margin-left: 2.142857142857143em;
48 | list-style-type: none;
49 | }
50 | .fa-ul > li {
51 | position: relative;
52 | }
53 | .fa-li {
54 | position: absolute;
55 | left: -2.142857142857143em;
56 | width: 2.142857142857143em;
57 | top: 0.14285714285714285em;
58 | text-align: center;
59 | }
60 | .fa-li.fa-lg {
61 | left: -1.8571428571428572em;
62 | }
63 | .fa-border {
64 | padding: .2em .25em .15em;
65 | border: solid 0.08em #eeeeee;
66 | border-radius: .1em;
67 | }
68 | .pull-right {
69 | float: right;
70 | }
71 | .pull-left {
72 | float: left;
73 | }
74 | .fa.pull-left {
75 | margin-right: .3em;
76 | }
77 | .fa.pull-right {
78 | margin-left: .3em;
79 | }
80 | .fa-spin {
81 | -webkit-animation: spin 2s infinite linear;
82 | -moz-animation: spin 2s infinite linear;
83 | -o-animation: spin 2s infinite linear;
84 | animation: spin 2s infinite linear;
85 | }
86 | @-moz-keyframes spin {
87 | 0% {
88 | -moz-transform: rotate(0deg);
89 | }
90 | 100% {
91 | -moz-transform: rotate(359deg);
92 | }
93 | }
94 | @-webkit-keyframes spin {
95 | 0% {
96 | -webkit-transform: rotate(0deg);
97 | }
98 | 100% {
99 | -webkit-transform: rotate(359deg);
100 | }
101 | }
102 | @-o-keyframes spin {
103 | 0% {
104 | -o-transform: rotate(0deg);
105 | }
106 | 100% {
107 | -o-transform: rotate(359deg);
108 | }
109 | }
110 | @-ms-keyframes spin {
111 | 0% {
112 | -ms-transform: rotate(0deg);
113 | }
114 | 100% {
115 | -ms-transform: rotate(359deg);
116 | }
117 | }
118 | @keyframes spin {
119 | 0% {
120 | transform: rotate(0deg);
121 | }
122 | 100% {
123 | transform: rotate(359deg);
124 | }
125 | }
126 | .fa-rotate-90 {
127 | filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=1);
128 | -webkit-transform: rotate(90deg);
129 | -moz-transform: rotate(90deg);
130 | -ms-transform: rotate(90deg);
131 | -o-transform: rotate(90deg);
132 | transform: rotate(90deg);
133 | }
134 | .fa-rotate-180 {
135 | filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=2);
136 | -webkit-transform: rotate(180deg);
137 | -moz-transform: rotate(180deg);
138 | -ms-transform: rotate(180deg);
139 | -o-transform: rotate(180deg);
140 | transform: rotate(180deg);
141 | }
142 | .fa-rotate-270 {
143 | filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=3);
144 | -webkit-transform: rotate(270deg);
145 | -moz-transform: rotate(270deg);
146 | -ms-transform: rotate(270deg);
147 | -o-transform: rotate(270deg);
148 | transform: rotate(270deg);
149 | }
150 | .fa-flip-horizontal {
151 | filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1);
152 | -webkit-transform: scale(-1, 1);
153 | -moz-transform: scale(-1, 1);
154 | -ms-transform: scale(-1, 1);
155 | -o-transform: scale(-1, 1);
156 | transform: scale(-1, 1);
157 | }
158 | .fa-flip-vertical {
159 | filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1);
160 | -webkit-transform: scale(1, -1);
161 | -moz-transform: scale(1, -1);
162 | -ms-transform: scale(1, -1);
163 | -o-transform: scale(1, -1);
164 | transform: scale(1, -1);
165 | }
166 | .fa-stack {
167 | position: relative;
168 | display: inline-block;
169 | width: 2em;
170 | height: 2em;
171 | line-height: 2em;
172 | vertical-align: middle;
173 | }
174 | .fa-stack-1x,
175 | .fa-stack-2x {
176 | position: absolute;
177 | left: 0;
178 | width: 100%;
179 | text-align: center;
180 | }
181 | .fa-stack-1x {
182 | line-height: inherit;
183 | }
184 | .fa-stack-2x {
185 | font-size: 2em;
186 | }
187 | .fa-inverse {
188 | color: #ffffff;
189 | }
190 | /* Font Awesome uses the Unicode Private Use Area (PUA) to ensure screen
191 | readers do not read off random characters that represent icons */
192 | .fa-glass:before {
193 | content: "\f000";
194 | }
195 | .fa-music:before {
196 | content: "\f001";
197 | }
198 | .fa-search:before {
199 | content: "\f002";
200 | }
201 | .fa-envelope-o:before {
202 | content: "\f003";
203 | }
204 | .fa-heart:before {
205 | content: "\f004";
206 | }
207 | .fa-star:before {
208 | content: "\f005";
209 | }
210 | .fa-star-o:before {
211 | content: "\f006";
212 | }
213 | .fa-user:before {
214 | content: "\f007";
215 | }
216 | .fa-film:before {
217 | content: "\f008";
218 | }
219 | .fa-th-large:before {
220 | content: "\f009";
221 | }
222 | .fa-th:before {
223 | content: "\f00a";
224 | }
225 | .fa-th-list:before {
226 | content: "\f00b";
227 | }
228 | .fa-check:before {
229 | content: "\f00c";
230 | }
231 | .fa-times:before {
232 | content: "\f00d";
233 | }
234 | .fa-search-plus:before {
235 | content: "\f00e";
236 | }
237 | .fa-search-minus:before {
238 | content: "\f010";
239 | }
240 | .fa-power-off:before {
241 | content: "\f011";
242 | }
243 | .fa-signal:before {
244 | content: "\f012";
245 | }
246 | .fa-gear:before,
247 | .fa-cog:before {
248 | content: "\f013";
249 | }
250 | .fa-trash-o:before {
251 | content: "\f014";
252 | }
253 | .fa-home:before {
254 | content: "\f015";
255 | }
256 | .fa-file-o:before {
257 | content: "\f016";
258 | }
259 | .fa-clock-o:before {
260 | content: "\f017";
261 | }
262 | .fa-road:before {
263 | content: "\f018";
264 | }
265 | .fa-download:before {
266 | content: "\f019";
267 | }
268 | .fa-arrow-circle-o-down:before {
269 | content: "\f01a";
270 | }
271 | .fa-arrow-circle-o-up:before {
272 | content: "\f01b";
273 | }
274 | .fa-inbox:before {
275 | content: "\f01c";
276 | }
277 | .fa-play-circle-o:before {
278 | content: "\f01d";
279 | }
280 | .fa-rotate-right:before,
281 | .fa-repeat:before {
282 | content: "\f01e";
283 | }
284 | .fa-refresh:before {
285 | content: "\f021";
286 | }
287 | .fa-list-alt:before {
288 | content: "\f022";
289 | }
290 | .fa-lock:before {
291 | content: "\f023";
292 | }
293 | .fa-flag:before {
294 | content: "\f024";
295 | }
296 | .fa-headphones:before {
297 | content: "\f025";
298 | }
299 | .fa-volume-off:before {
300 | content: "\f026";
301 | }
302 | .fa-volume-down:before {
303 | content: "\f027";
304 | }
305 | .fa-volume-up:before {
306 | content: "\f028";
307 | }
308 | .fa-qrcode:before {
309 | content: "\f029";
310 | }
311 | .fa-barcode:before {
312 | content: "\f02a";
313 | }
314 | .fa-tag:before {
315 | content: "\f02b";
316 | }
317 | .fa-tags:before {
318 | content: "\f02c";
319 | }
320 | .fa-book:before {
321 | content: "\f02d";
322 | }
323 | .fa-bookmark:before {
324 | content: "\f02e";
325 | }
326 | .fa-print:before {
327 | content: "\f02f";
328 | }
329 | .fa-camera:before {
330 | content: "\f030";
331 | }
332 | .fa-font:before {
333 | content: "\f031";
334 | }
335 | .fa-bold:before {
336 | content: "\f032";
337 | }
338 | .fa-italic:before {
339 | content: "\f033";
340 | }
341 | .fa-text-height:before {
342 | content: "\f034";
343 | }
344 | .fa-text-width:before {
345 | content: "\f035";
346 | }
347 | .fa-align-left:before {
348 | content: "\f036";
349 | }
350 | .fa-align-center:before {
351 | content: "\f037";
352 | }
353 | .fa-align-right:before {
354 | content: "\f038";
355 | }
356 | .fa-align-justify:before {
357 | content: "\f039";
358 | }
359 | .fa-list:before {
360 | content: "\f03a";
361 | }
362 | .fa-dedent:before,
363 | .fa-outdent:before {
364 | content: "\f03b";
365 | }
366 | .fa-indent:before {
367 | content: "\f03c";
368 | }
369 | .fa-video-camera:before {
370 | content: "\f03d";
371 | }
372 | .fa-picture-o:before {
373 | content: "\f03e";
374 | }
375 | .fa-pencil:before {
376 | content: "\f040";
377 | }
378 | .fa-map-marker:before {
379 | content: "\f041";
380 | }
381 | .fa-adjust:before {
382 | content: "\f042";
383 | }
384 | .fa-tint:before {
385 | content: "\f043";
386 | }
387 | .fa-edit:before,
388 | .fa-pencil-square-o:before {
389 | content: "\f044";
390 | }
391 | .fa-share-square-o:before {
392 | content: "\f045";
393 | }
394 | .fa-check-square-o:before {
395 | content: "\f046";
396 | }
397 | .fa-arrows:before {
398 | content: "\f047";
399 | }
400 | .fa-step-backward:before {
401 | content: "\f048";
402 | }
403 | .fa-fast-backward:before {
404 | content: "\f049";
405 | }
406 | .fa-backward:before {
407 | content: "\f04a";
408 | }
409 | .fa-play:before {
410 | content: "\f04b";
411 | }
412 | .fa-pause:before {
413 | content: "\f04c";
414 | }
415 | .fa-stop:before {
416 | content: "\f04d";
417 | }
418 | .fa-forward:before {
419 | content: "\f04e";
420 | }
421 | .fa-fast-forward:before {
422 | content: "\f050";
423 | }
424 | .fa-step-forward:before {
425 | content: "\f051";
426 | }
427 | .fa-eject:before {
428 | content: "\f052";
429 | }
430 | .fa-chevron-left:before {
431 | content: "\f053";
432 | }
433 | .fa-chevron-right:before {
434 | content: "\f054";
435 | }
436 | .fa-plus-circle:before {
437 | content: "\f055";
438 | }
439 | .fa-minus-circle:before {
440 | content: "\f056";
441 | }
442 | .fa-times-circle:before {
443 | content: "\f057";
444 | }
445 | .fa-check-circle:before {
446 | content: "\f058";
447 | }
448 | .fa-question-circle:before {
449 | content: "\f059";
450 | }
451 | .fa-info-circle:before {
452 | content: "\f05a";
453 | }
454 | .fa-crosshairs:before {
455 | content: "\f05b";
456 | }
457 | .fa-times-circle-o:before {
458 | content: "\f05c";
459 | }
460 | .fa-check-circle-o:before {
461 | content: "\f05d";
462 | }
463 | .fa-ban:before {
464 | content: "\f05e";
465 | }
466 | .fa-arrow-left:before {
467 | content: "\f060";
468 | }
469 | .fa-arrow-right:before {
470 | content: "\f061";
471 | }
472 | .fa-arrow-up:before {
473 | content: "\f062";
474 | }
475 | .fa-arrow-down:before {
476 | content: "\f063";
477 | }
478 | .fa-mail-forward:before,
479 | .fa-share:before {
480 | content: "\f064";
481 | }
482 | .fa-expand:before {
483 | content: "\f065";
484 | }
485 | .fa-compress:before {
486 | content: "\f066";
487 | }
488 | .fa-plus:before {
489 | content: "\f067";
490 | }
491 | .fa-minus:before {
492 | content: "\f068";
493 | }
494 | .fa-asterisk:before {
495 | content: "\f069";
496 | }
497 | .fa-exclamation-circle:before {
498 | content: "\f06a";
499 | }
500 | .fa-gift:before {
501 | content: "\f06b";
502 | }
503 | .fa-leaf:before {
504 | content: "\f06c";
505 | }
506 | .fa-fire:before {
507 | content: "\f06d";
508 | }
509 | .fa-eye:before {
510 | content: "\f06e";
511 | }
512 | .fa-eye-slash:before {
513 | content: "\f070";
514 | }
515 | .fa-warning:before,
516 | .fa-exclamation-triangle:before {
517 | content: "\f071";
518 | }
519 | .fa-plane:before {
520 | content: "\f072";
521 | }
522 | .fa-calendar:before {
523 | content: "\f073";
524 | }
525 | .fa-random:before {
526 | content: "\f074";
527 | }
528 | .fa-comment:before {
529 | content: "\f075";
530 | }
531 | .fa-magnet:before {
532 | content: "\f076";
533 | }
534 | .fa-chevron-up:before {
535 | content: "\f077";
536 | }
537 | .fa-chevron-down:before {
538 | content: "\f078";
539 | }
540 | .fa-retweet:before {
541 | content: "\f079";
542 | }
543 | .fa-shopping-cart:before {
544 | content: "\f07a";
545 | }
546 | .fa-folder:before {
547 | content: "\f07b";
548 | }
549 | .fa-folder-open:before {
550 | content: "\f07c";
551 | }
552 | .fa-arrows-v:before {
553 | content: "\f07d";
554 | }
555 | .fa-arrows-h:before {
556 | content: "\f07e";
557 | }
558 | .fa-bar-chart-o:before {
559 | content: "\f080";
560 | }
561 | .fa-twitter-square:before {
562 | content: "\f081";
563 | }
564 | .fa-facebook-square:before {
565 | content: "\f082";
566 | }
567 | .fa-camera-retro:before {
568 | content: "\f083";
569 | }
570 | .fa-key:before {
571 | content: "\f084";
572 | }
573 | .fa-gears:before,
574 | .fa-cogs:before {
575 | content: "\f085";
576 | }
577 | .fa-comments:before {
578 | content: "\f086";
579 | }
580 | .fa-thumbs-o-up:before {
581 | content: "\f087";
582 | }
583 | .fa-thumbs-o-down:before {
584 | content: "\f088";
585 | }
586 | .fa-star-half:before {
587 | content: "\f089";
588 | }
589 | .fa-heart-o:before {
590 | content: "\f08a";
591 | }
592 | .fa-sign-out:before {
593 | content: "\f08b";
594 | }
595 | .fa-linkedin-square:before {
596 | content: "\f08c";
597 | }
598 | .fa-thumb-tack:before {
599 | content: "\f08d";
600 | }
601 | .fa-external-link:before {
602 | content: "\f08e";
603 | }
604 | .fa-sign-in:before {
605 | content: "\f090";
606 | }
607 | .fa-trophy:before {
608 | content: "\f091";
609 | }
610 | .fa-github-square:before {
611 | content: "\f092";
612 | }
613 | .fa-upload:before {
614 | content: "\f093";
615 | }
616 | .fa-lemon-o:before {
617 | content: "\f094";
618 | }
619 | .fa-phone:before {
620 | content: "\f095";
621 | }
622 | .fa-square-o:before {
623 | content: "\f096";
624 | }
625 | .fa-bookmark-o:before {
626 | content: "\f097";
627 | }
628 | .fa-phone-square:before {
629 | content: "\f098";
630 | }
631 | .fa-twitter:before {
632 | content: "\f099";
633 | }
634 | .fa-facebook:before {
635 | content: "\f09a";
636 | }
637 | .fa-github:before {
638 | content: "\f09b";
639 | }
640 | .fa-unlock:before {
641 | content: "\f09c";
642 | }
643 | .fa-credit-card:before {
644 | content: "\f09d";
645 | }
646 | .fa-rss:before {
647 | content: "\f09e";
648 | }
649 | .fa-hdd-o:before {
650 | content: "\f0a0";
651 | }
652 | .fa-bullhorn:before {
653 | content: "\f0a1";
654 | }
655 | .fa-bell:before {
656 | content: "\f0f3";
657 | }
658 | .fa-certificate:before {
659 | content: "\f0a3";
660 | }
661 | .fa-hand-o-right:before {
662 | content: "\f0a4";
663 | }
664 | .fa-hand-o-left:before {
665 | content: "\f0a5";
666 | }
667 | .fa-hand-o-up:before {
668 | content: "\f0a6";
669 | }
670 | .fa-hand-o-down:before {
671 | content: "\f0a7";
672 | }
673 | .fa-arrow-circle-left:before {
674 | content: "\f0a8";
675 | }
676 | .fa-arrow-circle-right:before {
677 | content: "\f0a9";
678 | }
679 | .fa-arrow-circle-up:before {
680 | content: "\f0aa";
681 | }
682 | .fa-arrow-circle-down:before {
683 | content: "\f0ab";
684 | }
685 | .fa-globe:before {
686 | content: "\f0ac";
687 | }
688 | .fa-wrench:before {
689 | content: "\f0ad";
690 | }
691 | .fa-tasks:before {
692 | content: "\f0ae";
693 | }
694 | .fa-filter:before {
695 | content: "\f0b0";
696 | }
697 | .fa-briefcase:before {
698 | content: "\f0b1";
699 | }
700 | .fa-arrows-alt:before {
701 | content: "\f0b2";
702 | }
703 | .fa-group:before,
704 | .fa-users:before {
705 | content: "\f0c0";
706 | }
707 | .fa-chain:before,
708 | .fa-link:before {
709 | content: "\f0c1";
710 | }
711 | .fa-cloud:before {
712 | content: "\f0c2";
713 | }
714 | .fa-flask:before {
715 | content: "\f0c3";
716 | }
717 | .fa-cut:before,
718 | .fa-scissors:before {
719 | content: "\f0c4";
720 | }
721 | .fa-copy:before,
722 | .fa-files-o:before {
723 | content: "\f0c5";
724 | }
725 | .fa-paperclip:before {
726 | content: "\f0c6";
727 | }
728 | .fa-save:before,
729 | .fa-floppy-o:before {
730 | content: "\f0c7";
731 | }
732 | .fa-square:before {
733 | content: "\f0c8";
734 | }
735 | .fa-bars:before {
736 | content: "\f0c9";
737 | }
738 | .fa-list-ul:before {
739 | content: "\f0ca";
740 | }
741 | .fa-list-ol:before {
742 | content: "\f0cb";
743 | }
744 | .fa-strikethrough:before {
745 | content: "\f0cc";
746 | }
747 | .fa-underline:before {
748 | content: "\f0cd";
749 | }
750 | .fa-table:before {
751 | content: "\f0ce";
752 | }
753 | .fa-magic:before {
754 | content: "\f0d0";
755 | }
756 | .fa-truck:before {
757 | content: "\f0d1";
758 | }
759 | .fa-pinterest:before {
760 | content: "\f0d2";
761 | }
762 | .fa-pinterest-square:before {
763 | content: "\f0d3";
764 | }
765 | .fa-google-plus-square:before {
766 | content: "\f0d4";
767 | }
768 | .fa-google-plus:before {
769 | content: "\f0d5";
770 | }
771 | .fa-money:before {
772 | content: "\f0d6";
773 | }
774 | .fa-caret-down:before {
775 | content: "\f0d7";
776 | }
777 | .fa-caret-up:before {
778 | content: "\f0d8";
779 | }
780 | .fa-caret-left:before {
781 | content: "\f0d9";
782 | }
783 | .fa-caret-right:before {
784 | content: "\f0da";
785 | }
786 | .fa-columns:before {
787 | content: "\f0db";
788 | }
789 | .fa-unsorted:before,
790 | .fa-sort:before {
791 | content: "\f0dc";
792 | }
793 | .fa-sort-down:before,
794 | .fa-sort-asc:before {
795 | content: "\f0dd";
796 | }
797 | .fa-sort-up:before,
798 | .fa-sort-desc:before {
799 | content: "\f0de";
800 | }
801 | .fa-envelope:before {
802 | content: "\f0e0";
803 | }
804 | .fa-linkedin:before {
805 | content: "\f0e1";
806 | }
807 | .fa-rotate-left:before,
808 | .fa-undo:before {
809 | content: "\f0e2";
810 | }
811 | .fa-legal:before,
812 | .fa-gavel:before {
813 | content: "\f0e3";
814 | }
815 | .fa-dashboard:before,
816 | .fa-tachometer:before {
817 | content: "\f0e4";
818 | }
819 | .fa-comment-o:before {
820 | content: "\f0e5";
821 | }
822 | .fa-comments-o:before {
823 | content: "\f0e6";
824 | }
825 | .fa-flash:before,
826 | .fa-bolt:before {
827 | content: "\f0e7";
828 | }
829 | .fa-sitemap:before {
830 | content: "\f0e8";
831 | }
832 | .fa-umbrella:before {
833 | content: "\f0e9";
834 | }
835 | .fa-paste:before,
836 | .fa-clipboard:before {
837 | content: "\f0ea";
838 | }
839 | .fa-lightbulb-o:before {
840 | content: "\f0eb";
841 | }
842 | .fa-exchange:before {
843 | content: "\f0ec";
844 | }
845 | .fa-cloud-download:before {
846 | content: "\f0ed";
847 | }
848 | .fa-cloud-upload:before {
849 | content: "\f0ee";
850 | }
851 | .fa-user-md:before {
852 | content: "\f0f0";
853 | }
854 | .fa-stethoscope:before {
855 | content: "\f0f1";
856 | }
857 | .fa-suitcase:before {
858 | content: "\f0f2";
859 | }
860 | .fa-bell-o:before {
861 | content: "\f0a2";
862 | }
863 | .fa-coffee:before {
864 | content: "\f0f4";
865 | }
866 | .fa-cutlery:before {
867 | content: "\f0f5";
868 | }
869 | .fa-file-text-o:before {
870 | content: "\f0f6";
871 | }
872 | .fa-building-o:before {
873 | content: "\f0f7";
874 | }
875 | .fa-hospital-o:before {
876 | content: "\f0f8";
877 | }
878 | .fa-ambulance:before {
879 | content: "\f0f9";
880 | }
881 | .fa-medkit:before {
882 | content: "\f0fa";
883 | }
884 | .fa-fighter-jet:before {
885 | content: "\f0fb";
886 | }
887 | .fa-beer:before {
888 | content: "\f0fc";
889 | }
890 | .fa-h-square:before {
891 | content: "\f0fd";
892 | }
893 | .fa-plus-square:before {
894 | content: "\f0fe";
895 | }
896 | .fa-angle-double-left:before {
897 | content: "\f100";
898 | }
899 | .fa-angle-double-right:before {
900 | content: "\f101";
901 | }
902 | .fa-angle-double-up:before {
903 | content: "\f102";
904 | }
905 | .fa-angle-double-down:before {
906 | content: "\f103";
907 | }
908 | .fa-angle-left:before {
909 | content: "\f104";
910 | }
911 | .fa-angle-right:before {
912 | content: "\f105";
913 | }
914 | .fa-angle-up:before {
915 | content: "\f106";
916 | }
917 | .fa-angle-down:before {
918 | content: "\f107";
919 | }
920 | .fa-desktop:before {
921 | content: "\f108";
922 | }
923 | .fa-laptop:before {
924 | content: "\f109";
925 | }
926 | .fa-tablet:before {
927 | content: "\f10a";
928 | }
929 | .fa-mobile-phone:before,
930 | .fa-mobile:before {
931 | content: "\f10b";
932 | }
933 | .fa-circle-o:before {
934 | content: "\f10c";
935 | }
936 | .fa-quote-left:before {
937 | content: "\f10d";
938 | }
939 | .fa-quote-right:before {
940 | content: "\f10e";
941 | }
942 | .fa-spinner:before {
943 | content: "\f110";
944 | }
945 | .fa-circle:before {
946 | content: "\f111";
947 | }
948 | .fa-mail-reply:before,
949 | .fa-reply:before {
950 | content: "\f112";
951 | }
952 | .fa-github-alt:before {
953 | content: "\f113";
954 | }
955 | .fa-folder-o:before {
956 | content: "\f114";
957 | }
958 | .fa-folder-open-o:before {
959 | content: "\f115";
960 | }
961 | .fa-smile-o:before {
962 | content: "\f118";
963 | }
964 | .fa-frown-o:before {
965 | content: "\f119";
966 | }
967 | .fa-meh-o:before {
968 | content: "\f11a";
969 | }
970 | .fa-gamepad:before {
971 | content: "\f11b";
972 | }
973 | .fa-keyboard-o:before {
974 | content: "\f11c";
975 | }
976 | .fa-flag-o:before {
977 | content: "\f11d";
978 | }
979 | .fa-flag-checkered:before {
980 | content: "\f11e";
981 | }
982 | .fa-terminal:before {
983 | content: "\f120";
984 | }
985 | .fa-code:before {
986 | content: "\f121";
987 | }
988 | .fa-reply-all:before {
989 | content: "\f122";
990 | }
991 | .fa-mail-reply-all:before {
992 | content: "\f122";
993 | }
994 | .fa-star-half-empty:before,
995 | .fa-star-half-full:before,
996 | .fa-star-half-o:before {
997 | content: "\f123";
998 | }
999 | .fa-location-arrow:before {
1000 | content: "\f124";
1001 | }
1002 | .fa-crop:before {
1003 | content: "\f125";
1004 | }
1005 | .fa-code-fork:before {
1006 | content: "\f126";
1007 | }
1008 | .fa-unlink:before,
1009 | .fa-chain-broken:before {
1010 | content: "\f127";
1011 | }
1012 | .fa-question:before {
1013 | content: "\f128";
1014 | }
1015 | .fa-info:before {
1016 | content: "\f129";
1017 | }
1018 | .fa-exclamation:before {
1019 | content: "\f12a";
1020 | }
1021 | .fa-superscript:before {
1022 | content: "\f12b";
1023 | }
1024 | .fa-subscript:before {
1025 | content: "\f12c";
1026 | }
1027 | .fa-eraser:before {
1028 | content: "\f12d";
1029 | }
1030 | .fa-puzzle-piece:before {
1031 | content: "\f12e";
1032 | }
1033 | .fa-microphone:before {
1034 | content: "\f130";
1035 | }
1036 | .fa-microphone-slash:before {
1037 | content: "\f131";
1038 | }
1039 | .fa-shield:before {
1040 | content: "\f132";
1041 | }
1042 | .fa-calendar-o:before {
1043 | content: "\f133";
1044 | }
1045 | .fa-fire-extinguisher:before {
1046 | content: "\f134";
1047 | }
1048 | .fa-rocket:before {
1049 | content: "\f135";
1050 | }
1051 | .fa-maxcdn:before {
1052 | content: "\f136";
1053 | }
1054 | .fa-chevron-circle-left:before {
1055 | content: "\f137";
1056 | }
1057 | .fa-chevron-circle-right:before {
1058 | content: "\f138";
1059 | }
1060 | .fa-chevron-circle-up:before {
1061 | content: "\f139";
1062 | }
1063 | .fa-chevron-circle-down:before {
1064 | content: "\f13a";
1065 | }
1066 | .fa-html5:before {
1067 | content: "\f13b";
1068 | }
1069 | .fa-css3:before {
1070 | content: "\f13c";
1071 | }
1072 | .fa-anchor:before {
1073 | content: "\f13d";
1074 | }
1075 | .fa-unlock-alt:before {
1076 | content: "\f13e";
1077 | }
1078 | .fa-bullseye:before {
1079 | content: "\f140";
1080 | }
1081 | .fa-ellipsis-h:before {
1082 | content: "\f141";
1083 | }
1084 | .fa-ellipsis-v:before {
1085 | content: "\f142";
1086 | }
1087 | .fa-rss-square:before {
1088 | content: "\f143";
1089 | }
1090 | .fa-play-circle:before {
1091 | content: "\f144";
1092 | }
1093 | .fa-ticket:before {
1094 | content: "\f145";
1095 | }
1096 | .fa-minus-square:before {
1097 | content: "\f146";
1098 | }
1099 | .fa-minus-square-o:before {
1100 | content: "\f147";
1101 | }
1102 | .fa-level-up:before {
1103 | content: "\f148";
1104 | }
1105 | .fa-level-down:before {
1106 | content: "\f149";
1107 | }
1108 | .fa-check-square:before {
1109 | content: "\f14a";
1110 | }
1111 | .fa-pencil-square:before {
1112 | content: "\f14b";
1113 | }
1114 | .fa-external-link-square:before {
1115 | content: "\f14c";
1116 | }
1117 | .fa-share-square:before {
1118 | content: "\f14d";
1119 | }
1120 | .fa-compass:before {
1121 | content: "\f14e";
1122 | }
1123 | .fa-toggle-down:before,
1124 | .fa-caret-square-o-down:before {
1125 | content: "\f150";
1126 | }
1127 | .fa-toggle-up:before,
1128 | .fa-caret-square-o-up:before {
1129 | content: "\f151";
1130 | }
1131 | .fa-toggle-right:before,
1132 | .fa-caret-square-o-right:before {
1133 | content: "\f152";
1134 | }
1135 | .fa-euro:before,
1136 | .fa-eur:before {
1137 | content: "\f153";
1138 | }
1139 | .fa-gbp:before {
1140 | content: "\f154";
1141 | }
1142 | .fa-dollar:before,
1143 | .fa-usd:before {
1144 | content: "\f155";
1145 | }
1146 | .fa-rupee:before,
1147 | .fa-inr:before {
1148 | content: "\f156";
1149 | }
1150 | .fa-cny:before,
1151 | .fa-rmb:before,
1152 | .fa-yen:before,
1153 | .fa-jpy:before {
1154 | content: "\f157";
1155 | }
1156 | .fa-ruble:before,
1157 | .fa-rouble:before,
1158 | .fa-rub:before {
1159 | content: "\f158";
1160 | }
1161 | .fa-won:before,
1162 | .fa-krw:before {
1163 | content: "\f159";
1164 | }
1165 | .fa-bitcoin:before,
1166 | .fa-btc:before {
1167 | content: "\f15a";
1168 | }
1169 | .fa-file:before {
1170 | content: "\f15b";
1171 | }
1172 | .fa-file-text:before {
1173 | content: "\f15c";
1174 | }
1175 | .fa-sort-alpha-asc:before {
1176 | content: "\f15d";
1177 | }
1178 | .fa-sort-alpha-desc:before {
1179 | content: "\f15e";
1180 | }
1181 | .fa-sort-amount-asc:before {
1182 | content: "\f160";
1183 | }
1184 | .fa-sort-amount-desc:before {
1185 | content: "\f161";
1186 | }
1187 | .fa-sort-numeric-asc:before {
1188 | content: "\f162";
1189 | }
1190 | .fa-sort-numeric-desc:before {
1191 | content: "\f163";
1192 | }
1193 | .fa-thumbs-up:before {
1194 | content: "\f164";
1195 | }
1196 | .fa-thumbs-down:before {
1197 | content: "\f165";
1198 | }
1199 | .fa-youtube-square:before {
1200 | content: "\f166";
1201 | }
1202 | .fa-youtube:before {
1203 | content: "\f167";
1204 | }
1205 | .fa-xing:before {
1206 | content: "\f168";
1207 | }
1208 | .fa-xing-square:before {
1209 | content: "\f169";
1210 | }
1211 | .fa-youtube-play:before {
1212 | content: "\f16a";
1213 | }
1214 | .fa-dropbox:before {
1215 | content: "\f16b";
1216 | }
1217 | .fa-stack-overflow:before {
1218 | content: "\f16c";
1219 | }
1220 | .fa-instagram:before {
1221 | content: "\f16d";
1222 | }
1223 | .fa-flickr:before {
1224 | content: "\f16e";
1225 | }
1226 | .fa-adn:before {
1227 | content: "\f170";
1228 | }
1229 | .fa-bitbucket:before {
1230 | content: "\f171";
1231 | }
1232 | .fa-bitbucket-square:before {
1233 | content: "\f172";
1234 | }
1235 | .fa-tumblr:before {
1236 | content: "\f173";
1237 | }
1238 | .fa-tumblr-square:before {
1239 | content: "\f174";
1240 | }
1241 | .fa-long-arrow-down:before {
1242 | content: "\f175";
1243 | }
1244 | .fa-long-arrow-up:before {
1245 | content: "\f176";
1246 | }
1247 | .fa-long-arrow-left:before {
1248 | content: "\f177";
1249 | }
1250 | .fa-long-arrow-right:before {
1251 | content: "\f178";
1252 | }
1253 | .fa-apple:before {
1254 | content: "\f179";
1255 | }
1256 | .fa-windows:before {
1257 | content: "\f17a";
1258 | }
1259 | .fa-android:before {
1260 | content: "\f17b";
1261 | }
1262 | .fa-linux:before {
1263 | content: "\f17c";
1264 | }
1265 | .fa-dribbble:before {
1266 | content: "\f17d";
1267 | }
1268 | .fa-skype:before {
1269 | content: "\f17e";
1270 | }
1271 | .fa-foursquare:before {
1272 | content: "\f180";
1273 | }
1274 | .fa-trello:before {
1275 | content: "\f181";
1276 | }
1277 | .fa-female:before {
1278 | content: "\f182";
1279 | }
1280 | .fa-male:before {
1281 | content: "\f183";
1282 | }
1283 | .fa-gittip:before {
1284 | content: "\f184";
1285 | }
1286 | .fa-sun-o:before {
1287 | content: "\f185";
1288 | }
1289 | .fa-moon-o:before {
1290 | content: "\f186";
1291 | }
1292 | .fa-archive:before {
1293 | content: "\f187";
1294 | }
1295 | .fa-bug:before {
1296 | content: "\f188";
1297 | }
1298 | .fa-vk:before {
1299 | content: "\f189";
1300 | }
1301 | .fa-weibo:before {
1302 | content: "\f18a";
1303 | }
1304 | .fa-renren:before {
1305 | content: "\f18b";
1306 | }
1307 | .fa-pagelines:before {
1308 | content: "\f18c";
1309 | }
1310 | .fa-stack-exchange:before {
1311 | content: "\f18d";
1312 | }
1313 | .fa-arrow-circle-o-right:before {
1314 | content: "\f18e";
1315 | }
1316 | .fa-arrow-circle-o-left:before {
1317 | content: "\f190";
1318 | }
1319 | .fa-toggle-left:before,
1320 | .fa-caret-square-o-left:before {
1321 | content: "\f191";
1322 | }
1323 | .fa-dot-circle-o:before {
1324 | content: "\f192";
1325 | }
1326 | .fa-wheelchair:before {
1327 | content: "\f193";
1328 | }
1329 | .fa-vimeo-square:before {
1330 | content: "\f194";
1331 | }
1332 | .fa-turkish-lira:before,
1333 | .fa-try:before {
1334 | content: "\f195";
1335 | }
1336 | .fa-plus-square-o:before {
1337 | content: "\f196";
1338 | }
1339 |
--------------------------------------------------------------------------------
/src/css/fonts/FontAwesome.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flimshaw/Valiant360/79f1b528886ab580e1624ef3b5857dbd541d62c4/src/css/fonts/FontAwesome.otf
--------------------------------------------------------------------------------
/src/css/fonts/fontawesome-webfont.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flimshaw/Valiant360/79f1b528886ab580e1624ef3b5857dbd541d62c4/src/css/fonts/fontawesome-webfont.eot
--------------------------------------------------------------------------------
/src/css/fonts/fontawesome-webfont.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flimshaw/Valiant360/79f1b528886ab580e1624ef3b5857dbd541d62c4/src/css/fonts/fontawesome-webfont.ttf
--------------------------------------------------------------------------------
/src/css/fonts/fontawesome-webfont.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flimshaw/Valiant360/79f1b528886ab580e1624ef3b5857dbd541d62c4/src/css/fonts/fontawesome-webfont.woff
--------------------------------------------------------------------------------
/src/css/valiant360.less:
--------------------------------------------------------------------------------
1 | /*!
2 | * Valiant360 panorama video player jquery plugin
3 | *
4 | * Copyright (c) 2014 Charlie Hoey <@flimshaw>
5 | *
6 | * Released under the MIT license:
7 | * http://www.opensource.org/licenses/mit-license.php
8 | */
9 | @import 'font-awesome.less';
10 |
11 | .transition (@transition) {
12 | -webkit-transition: @transition;
13 | -moz-transition: @transition;
14 | -ms-transition: @transition;
15 | -o-transition: @transition;
16 | }
17 |
18 | .box-sizing (@sizing) {
19 | box-sizing: @sizing;
20 | -moz-box-sizing: @sizing;
21 | -webkit-box-sizing: @sizing;
22 | }
23 |
24 | .Valiant360_default {
25 | position: relative;
26 | canvas {
27 | width: 100%;
28 | height: 100%;
29 | position: absolute;
30 | }
31 | .controlsWrapper {
32 | position: absolute;
33 | height: 0;
34 | width: 100%;
35 | bottom:0;
36 | overflow:hidden;
37 | .transition(all .5s ease-in-out);
38 | }
39 | .controls {
40 | overflow: hidden;
41 | position: absolute;
42 | bottom: 0;
43 | background-color:rgba(0,0,0,.75);
44 | height: 40px;
45 | width: 100%;
46 | .box-sizing(border-box);
47 | font-size: 50%;
48 | padding: 0 10px;
49 | .button {
50 | width: 40px;
51 | text-align: center;
52 | margin: 0;
53 | padding: 0;
54 | line-height: 40px;
55 | opacity: .5;
56 | font-size: 3em;
57 | color: #fff;
58 | text-decoration: none;
59 | .transition(all .25s ease-in-out);
60 | }
61 | .button:hover {
62 | opacity: 1;
63 | }
64 | .fullscreenButton {
65 | float: right;
66 | }
67 | .timeLabel{
68 | color: #fff;
69 | font-size: 2em;
70 | height: 100%;
71 | line-height:40px;
72 | padding:0 10px;
73 | }
74 |
75 | .audioControl{
76 | display: inline-block;
77 | }
78 |
79 | .audioControl:hover{
80 | padding-right:10px;
81 | .volumeControl{
82 | width:80px;
83 | .volumeCursor{
84 | display:block;
85 | }
86 | }
87 | }
88 |
89 | .volumeControl:hover {
90 | opacity: 1;
91 | width:80px;
92 | }
93 |
94 | .volumeControl{
95 | width: 0;
96 | height:26px;
97 | position:relative;
98 | top:5px;
99 | opacity: .5;
100 | display: inline-block;
101 | cursor:pointer;
102 | .transition(all .25s ease-in-out);
103 | .volumeBar{
104 | position:absolute;
105 | top:11px;
106 | .volumeProgress {
107 | height: 4px;
108 | width: 100%;
109 | position:absolute;
110 | background-color: #fff;
111 | }
112 | .volumeCursor{
113 | height: 12px;
114 | width: 12px;
115 | display:none;
116 | border-radius:50%;
117 | background-color: #fff;
118 | position:absolute;
119 | right:-6px;
120 | top:-4px;
121 | }
122 | }
123 | }
124 |
125 | .volumeControl:before {
126 | content:'';
127 | width:100%;
128 | background-color: #444;
129 | height: 4px;
130 | width: 100%;
131 | position:absolute;
132 | top:11px;
133 | }
134 |
135 | }
136 | .loading {
137 | position: absolute;
138 | z-index:10;
139 | height:100%;
140 | width:100%;
141 | display:none;
142 | overflow: hidden;
143 | .icon{
144 | margin: 25% auto;
145 | width: 32px;
146 | height: 32px;
147 | font-size: 32px;
148 | color: #fff;
149 | }
150 | .waiting-icon{
151 | text-indent: -9999em;
152 | border-radius: 50%;
153 | background: -moz-linear-gradient(left, #ffffff 10%, rgba(255, 255, 255, 0) 42%);
154 | background: -webkit-linear-gradient(left, #ffffff 10%, rgba(255, 255, 255, 0) 42%);
155 | background: -o-linear-gradient(left, #ffffff 10%, rgba(255, 255, 255, 0) 42%);
156 | background: -ms-linear-gradient(left, #ffffff 10%, rgba(255, 255, 255, 0) 42%);
157 | background: linear-gradient(to right, #ffffff 10%, rgba(255, 255, 255, 0) 42%);
158 | position: relative;
159 | -webkit-animation: load3 1.4s infinite linear;
160 | animation: valiant360_default_waiting_load 1.4s infinite linear;
161 | -webkit-transform: translateZ(0);
162 | -ms-transform: translateZ(0);
163 | transform: translateZ(0);
164 | }
165 | }
166 | .timeTooltip{
167 | display:none;
168 | position: absolute;
169 | bottom: 50px;
170 | height: 25px;
171 | line-height: 25px;
172 | padding:5px;
173 | border-radius: 4px;
174 | background-color:rgba(0,0,0,.75);
175 | color: #fff;
176 | }
177 | }
178 |
179 | .Valiant360_default:hover {
180 | .controlsWrapper {
181 | height: 48px;
182 | }
183 | }
184 |
185 | .Valiant360_default.fullscreen {
186 | width: 100%!important;
187 | height: 100%!important;
188 | }
189 |
190 | .Valiant360_default .valiant-progress-bar {
191 | width: 100%;
192 | height: 8px;
193 | overflow: hidden;
194 | }
195 | .Valiant360_default .valiant-progress-bar:hover {
196 | cursor: pointer;
197 | }
198 | .Valiant360_default .valiant-progress-bar > div {
199 | height: 100%;
200 | float:left;
201 | background-color: #444;
202 | }
203 | .Valiant360_default .valiant-progress-bar > div:first-child {
204 | background-color: red;
205 | }
206 |
207 | .Valiant360_default .loading .waiting-icon:before {
208 | width: 50%;
209 | height: 50%;
210 | background: #ffffff;
211 | border-radius: 100% 0 0 0;
212 | position: absolute;
213 | top: 0;
214 | left: 0;
215 | content: '';
216 | }
217 |
218 | .Valiant360_default .loading .waiting-icon:after {
219 | background: #000;
220 | width: 75%;
221 | height: 75%;
222 | border-radius: 50%;
223 | content: '';
224 | margin: auto;
225 | position: absolute;
226 | top: 0;
227 | left: 0;
228 | bottom: 0;
229 | right: 0;
230 | }
231 |
232 | @-webkit-keyframes valiant360_default_waiting_load {
233 | 0% {
234 | -webkit-transform: rotate(0deg);
235 | transform: rotate(0deg);
236 | }
237 | 100% {
238 | -webkit-transform: rotate(360deg);
239 | transform: rotate(360deg);
240 | }
241 | }
242 | @keyframes valiant360_default_waiting_load {
243 | 0% {
244 | -webkit-transform: rotate(0deg);
245 | transform: rotate(0deg);
246 | }
247 | 100% {
248 | -webkit-transform: rotate(360deg);
249 | transform: rotate(360deg);
250 | }
251 | }
252 |
--------------------------------------------------------------------------------
/src/lib/Detector.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @author alteredq / http://alteredqualia.com/
3 | * @author mr.doob / http://mrdoob.com/
4 | */
5 |
6 | var Detector = {
7 |
8 | canvas: !! window.CanvasRenderingContext2D,
9 | webgl: ( function () { try { var canvas = document.createElement( 'canvas' ); return !! window.WebGLRenderingContext && ( canvas.getContext( 'webgl' ) || canvas.getContext( 'experimental-webgl' ) ); } catch( e ) { return false; } } )(),
10 | workers: !! window.Worker,
11 | fileapi: window.File && window.FileReader && window.FileList && window.Blob,
12 |
13 | getWebGLErrorMessage: function () {
14 |
15 | var element = document.createElement( 'div' );
16 | element.id = 'webgl-error-message';
17 | element.style.fontFamily = 'monospace';
18 | element.style.fontSize = '13px';
19 | element.style.fontWeight = 'normal';
20 | element.style.textAlign = 'center';
21 | element.style.background = '#fff';
22 | element.style.color = '#000';
23 | element.style.padding = '1.5em';
24 | element.style.width = '400px';
25 | element.style.margin = '5em auto 0';
26 |
27 | if ( ! this.webgl ) {
28 |
29 | element.innerHTML = window.WebGLRenderingContext ? [
30 | 'Your graphics card does not seem to support WebGL.
',
31 | 'Find out how to get it here.'
32 | ].join( '\n' ) : [
33 | 'Your browser does not seem to support WebGL.
',
34 | 'Find out how to get it here.'
35 | ].join( '\n' );
36 |
37 | }
38 |
39 | return element;
40 |
41 | },
42 |
43 | addGetWebGLMessage: function ( parameters ) {
44 |
45 | var parent, id, element;
46 |
47 | parameters = parameters || {};
48 |
49 | parent = parameters.parent !== undefined ? parameters.parent : document.body;
50 | id = parameters.id !== undefined ? parameters.id : 'oldie';
51 |
52 | element = Detector.getWebGLErrorMessage();
53 | element.id = id;
54 |
55 | parent.appendChild( element );
56 |
57 | }
58 |
59 | };
--------------------------------------------------------------------------------
/src/lib/modernizr-latest.js:
--------------------------------------------------------------------------------
1 | /*!
2 | * Modernizr v2.7.1
3 | * www.modernizr.com
4 | *
5 | * Copyright (c) Faruk Ates, Paul Irish, Alex Sexton
6 | * Available under the BSD and MIT licenses: www.modernizr.com/license/
7 | */
8 |
9 | /*
10 | * Modernizr tests which native CSS3 and HTML5 features are available in
11 | * the current UA and makes the results available to you in two ways:
12 | * as properties on a global Modernizr object, and as classes on the
13 | * element. This information allows you to progressively enhance
14 | * your pages with a granular level of control over the experience.
15 | *
16 | * Modernizr has an optional (not included) conditional resource loader
17 | * called Modernizr.load(), based on Yepnope.js (yepnopejs.com).
18 | * To get a build that includes Modernizr.load(), as well as choosing
19 | * which tests to include, go to www.modernizr.com/download/
20 | *
21 | * Authors Faruk Ates, Paul Irish, Alex Sexton
22 | * Contributors Ryan Seddon, Ben Alman
23 | */
24 |
25 | window.Modernizr = (function( window, document, undefined ) {
26 |
27 | var version = '2.7.1',
28 |
29 | Modernizr = {},
30 |
31 | /*>>cssclasses*/
32 | // option for enabling the HTML classes to be added
33 | enableClasses = true,
34 | /*>>cssclasses*/
35 |
36 | docElement = document.documentElement,
37 |
38 | /**
39 | * Create our "modernizr" element that we do most feature tests on.
40 | */
41 | mod = 'modernizr',
42 | modElem = document.createElement(mod),
43 | mStyle = modElem.style,
44 |
45 | /**
46 | * Create the input element for various Web Forms feature tests.
47 | */
48 | inputElem /*>>inputelem*/ = document.createElement('input') /*>>inputelem*/ ,
49 |
50 | /*>>smile*/
51 | smile = ':)',
52 | /*>>smile*/
53 |
54 | toString = {}.toString,
55 |
56 | // TODO :: make the prefixes more granular
57 | /*>>prefixes*/
58 | // List of property values to set for css tests. See ticket #21
59 | prefixes = ' -webkit- -moz- -o- -ms- '.split(' '),
60 | /*>>prefixes*/
61 |
62 | /*>>domprefixes*/
63 | // Following spec is to expose vendor-specific style properties as:
64 | // elem.style.WebkitBorderRadius
65 | // and the following would be incorrect:
66 | // elem.style.webkitBorderRadius
67 |
68 | // Webkit ghosts their properties in lowercase but Opera & Moz do not.
69 | // Microsoft uses a lowercase `ms` instead of the correct `Ms` in IE8+
70 | // erik.eae.net/archives/2008/03/10/21.48.10/
71 |
72 | // More here: github.com/Modernizr/Modernizr/issues/issue/21
73 | omPrefixes = 'Webkit Moz O ms',
74 |
75 | cssomPrefixes = omPrefixes.split(' '),
76 |
77 | domPrefixes = omPrefixes.toLowerCase().split(' '),
78 | /*>>domprefixes*/
79 |
80 | /*>>ns*/
81 | ns = {'svg': 'http://www.w3.org/2000/svg'},
82 | /*>>ns*/
83 |
84 | tests = {},
85 | inputs = {},
86 | attrs = {},
87 |
88 | classes = [],
89 |
90 | slice = classes.slice,
91 |
92 | featureName, // used in testing loop
93 |
94 |
95 | /*>>teststyles*/
96 | // Inject element with style element and some CSS rules
97 | injectElementWithStyles = function( rule, callback, nodes, testnames ) {
98 |
99 | var style, ret, node, docOverflow,
100 | div = document.createElement('div'),
101 | // After page load injecting a fake body doesn't work so check if body exists
102 | body = document.body,
103 | // IE6 and 7 won't return offsetWidth or offsetHeight unless it's in the body element, so we fake it.
104 | fakeBody = body || document.createElement('body');
105 |
106 | if ( parseInt(nodes, 10) ) {
107 | // In order not to give false positives we create a node for each test
108 | // This also allows the method to scale for unspecified uses
109 | while ( nodes-- ) {
110 | node = document.createElement('div');
111 | node.id = testnames ? testnames[nodes] : mod + (nodes + 1);
112 | div.appendChild(node);
113 | }
114 | }
115 |
116 | // '].join('');
122 | div.id = mod;
123 | // IE6 will false positive on some tests due to the style element inside the test div somehow interfering offsetHeight, so insert it into body or fakebody.
124 | // Opera will act all quirky when injecting elements in documentElement when page is served as xml, needs fakebody too. #270
125 | (body ? div : fakeBody).innerHTML += style;
126 | fakeBody.appendChild(div);
127 | if ( !body ) {
128 | //avoid crashing IE8, if background image is used
129 | fakeBody.style.background = '';
130 | //Safari 5.13/5.1.4 OSX stops loading if ::-webkit-scrollbar is used and scrollbars are visible
131 | fakeBody.style.overflow = 'hidden';
132 | docOverflow = docElement.style.overflow;
133 | docElement.style.overflow = 'hidden';
134 | docElement.appendChild(fakeBody);
135 | }
136 |
137 | ret = callback(div, rule);
138 | // If this is done after page load we don't want to remove the body so check if body exists
139 | if ( !body ) {
140 | fakeBody.parentNode.removeChild(fakeBody);
141 | docElement.style.overflow = docOverflow;
142 | } else {
143 | div.parentNode.removeChild(div);
144 | }
145 |
146 | return !!ret;
147 |
148 | },
149 | /*>>teststyles*/
150 |
151 | /*>>mq*/
152 | // adapted from matchMedia polyfill
153 | // by Scott Jehl and Paul Irish
154 | // gist.github.com/786768
155 | testMediaQuery = function( mq ) {
156 |
157 | var matchMedia = window.matchMedia || window.msMatchMedia;
158 | if ( matchMedia ) {
159 | return matchMedia(mq).matches;
160 | }
161 |
162 | var bool;
163 |
164 | injectElementWithStyles('@media ' + mq + ' { #' + mod + ' { position: absolute; } }', function( node ) {
165 | bool = (window.getComputedStyle ?
166 | getComputedStyle(node, null) :
167 | node.currentStyle)['position'] == 'absolute';
168 | });
169 |
170 | return bool;
171 |
172 | },
173 | /*>>mq*/
174 |
175 |
176 | /*>>hasevent*/
177 | //
178 | // isEventSupported determines if a given element supports the given event
179 | // kangax.github.com/iseventsupported/
180 | //
181 | // The following results are known incorrects:
182 | // Modernizr.hasEvent("webkitTransitionEnd", elem) // false negative
183 | // Modernizr.hasEvent("textInput") // in Webkit. github.com/Modernizr/Modernizr/issues/333
184 | // ...
185 | isEventSupported = (function() {
186 |
187 | var TAGNAMES = {
188 | 'select': 'input', 'change': 'input',
189 | 'submit': 'form', 'reset': 'form',
190 | 'error': 'img', 'load': 'img', 'abort': 'img'
191 | };
192 |
193 | function isEventSupported( eventName, element ) {
194 |
195 | element = element || document.createElement(TAGNAMES[eventName] || 'div');
196 | eventName = 'on' + eventName;
197 |
198 | // When using `setAttribute`, IE skips "unload", WebKit skips "unload" and "resize", whereas `in` "catches" those
199 | var isSupported = eventName in element;
200 |
201 | if ( !isSupported ) {
202 | // If it has no `setAttribute` (i.e. doesn't implement Node interface), try generic element
203 | if ( !element.setAttribute ) {
204 | element = document.createElement('div');
205 | }
206 | if ( element.setAttribute && element.removeAttribute ) {
207 | element.setAttribute(eventName, '');
208 | isSupported = is(element[eventName], 'function');
209 |
210 | // If property was created, "remove it" (by setting value to `undefined`)
211 | if ( !is(element[eventName], 'undefined') ) {
212 | element[eventName] = undefined;
213 | }
214 | element.removeAttribute(eventName);
215 | }
216 | }
217 |
218 | element = null;
219 | return isSupported;
220 | }
221 | return isEventSupported;
222 | })(),
223 | /*>>hasevent*/
224 |
225 | // TODO :: Add flag for hasownprop ? didn't last time
226 |
227 | // hasOwnProperty shim by kangax needed for Safari 2.0 support
228 | _hasOwnProperty = ({}).hasOwnProperty, hasOwnProp;
229 |
230 | if ( !is(_hasOwnProperty, 'undefined') && !is(_hasOwnProperty.call, 'undefined') ) {
231 | hasOwnProp = function (object, property) {
232 | return _hasOwnProperty.call(object, property);
233 | };
234 | }
235 | else {
236 | hasOwnProp = function (object, property) { /* yes, this can give false positives/negatives, but most of the time we don't care about those */
237 | return ((property in object) && is(object.constructor.prototype[property], 'undefined'));
238 | };
239 | }
240 |
241 | // Adapted from ES5-shim https://github.com/kriskowal/es5-shim/blob/master/es5-shim.js
242 | // es5.github.com/#x15.3.4.5
243 |
244 | if (!Function.prototype.bind) {
245 | Function.prototype.bind = function bind(that) {
246 |
247 | var target = this;
248 |
249 | if (typeof target != "function") {
250 | throw new TypeError();
251 | }
252 |
253 | var args = slice.call(arguments, 1),
254 | bound = function () {
255 |
256 | if (this instanceof bound) {
257 |
258 | var F = function(){};
259 | F.prototype = target.prototype;
260 | var self = new F();
261 |
262 | var result = target.apply(
263 | self,
264 | args.concat(slice.call(arguments))
265 | );
266 | if (Object(result) === result) {
267 | return result;
268 | }
269 | return self;
270 |
271 | } else {
272 |
273 | return target.apply(
274 | that,
275 | args.concat(slice.call(arguments))
276 | );
277 |
278 | }
279 |
280 | };
281 |
282 | return bound;
283 | };
284 | }
285 |
286 | /**
287 | * setCss applies given styles to the Modernizr DOM node.
288 | */
289 | function setCss( str ) {
290 | mStyle.cssText = str;
291 | }
292 |
293 | /**
294 | * setCssAll extrapolates all vendor-specific css strings.
295 | */
296 | function setCssAll( str1, str2 ) {
297 | return setCss(prefixes.join(str1 + ';') + ( str2 || '' ));
298 | }
299 |
300 | /**
301 | * is returns a boolean for if typeof obj is exactly type.
302 | */
303 | function is( obj, type ) {
304 | return typeof obj === type;
305 | }
306 |
307 | /**
308 | * contains returns a boolean for if substr is found within str.
309 | */
310 | function contains( str, substr ) {
311 | return !!~('' + str).indexOf(substr);
312 | }
313 |
314 | /*>>testprop*/
315 |
316 | // testProps is a generic CSS / DOM property test.
317 |
318 | // In testing support for a given CSS property, it's legit to test:
319 | // `elem.style[styleName] !== undefined`
320 | // If the property is supported it will return an empty string,
321 | // if unsupported it will return undefined.
322 |
323 | // We'll take advantage of this quick test and skip setting a style
324 | // on our modernizr element, but instead just testing undefined vs
325 | // empty string.
326 |
327 | // Because the testing of the CSS property names (with "-", as
328 | // opposed to the camelCase DOM properties) is non-portable and
329 | // non-standard but works in WebKit and IE (but not Gecko or Opera),
330 | // we explicitly reject properties with dashes so that authors
331 | // developing in WebKit or IE first don't end up with
332 | // browser-specific content by accident.
333 |
334 | function testProps( props, prefixed ) {
335 | for ( var i in props ) {
336 | var prop = props[i];
337 | if ( !contains(prop, "-") && mStyle[prop] !== undefined ) {
338 | return prefixed == 'pfx' ? prop : true;
339 | }
340 | }
341 | return false;
342 | }
343 | /*>>testprop*/
344 |
345 | // TODO :: add testDOMProps
346 | /**
347 | * testDOMProps is a generic DOM property test; if a browser supports
348 | * a certain property, it won't return undefined for it.
349 | */
350 | function testDOMProps( props, obj, elem ) {
351 | for ( var i in props ) {
352 | var item = obj[props[i]];
353 | if ( item !== undefined) {
354 |
355 | // return the property name as a string
356 | if (elem === false) return props[i];
357 |
358 | // let's bind a function
359 | if (is(item, 'function')){
360 | // default to autobind unless override
361 | return item.bind(elem || obj);
362 | }
363 |
364 | // return the unbound function or obj or value
365 | return item;
366 | }
367 | }
368 | return false;
369 | }
370 |
371 | /*>>testallprops*/
372 | /**
373 | * testPropsAll tests a list of DOM properties we want to check against.
374 | * We specify literally ALL possible (known and/or likely) properties on
375 | * the element including the non-vendor prefixed one, for forward-
376 | * compatibility.
377 | */
378 | function testPropsAll( prop, prefixed, elem ) {
379 |
380 | var ucProp = prop.charAt(0).toUpperCase() + prop.slice(1),
381 | props = (prop + ' ' + cssomPrefixes.join(ucProp + ' ') + ucProp).split(' ');
382 |
383 | // did they call .prefixed('boxSizing') or are we just testing a prop?
384 | if(is(prefixed, "string") || is(prefixed, "undefined")) {
385 | return testProps(props, prefixed);
386 |
387 | // otherwise, they called .prefixed('requestAnimationFrame', window[, elem])
388 | } else {
389 | props = (prop + ' ' + (domPrefixes).join(ucProp + ' ') + ucProp).split(' ');
390 | return testDOMProps(props, prefixed, elem);
391 | }
392 | }
393 | /*>>testallprops*/
394 |
395 |
396 | /**
397 | * Tests
398 | * -----
399 | */
400 |
401 | // The *new* flexbox
402 | // dev.w3.org/csswg/css3-flexbox
403 |
404 | tests['flexbox'] = function() {
405 | return testPropsAll('flexWrap');
406 | };
407 |
408 | // The *old* flexbox
409 | // www.w3.org/TR/2009/WD-css3-flexbox-20090723/
410 |
411 | tests['flexboxlegacy'] = function() {
412 | return testPropsAll('boxDirection');
413 | };
414 |
415 | // On the S60 and BB Storm, getContext exists, but always returns undefined
416 | // so we actually have to call getContext() to verify
417 | // github.com/Modernizr/Modernizr/issues/issue/97/
418 |
419 | tests['canvas'] = function() {
420 | var elem = document.createElement('canvas');
421 | return !!(elem.getContext && elem.getContext('2d'));
422 | };
423 |
424 | tests['canvastext'] = function() {
425 | return !!(Modernizr['canvas'] && is(document.createElement('canvas').getContext('2d').fillText, 'function'));
426 | };
427 |
428 | // webk.it/70117 is tracking a legit WebGL feature detect proposal
429 |
430 | // We do a soft detect which may false positive in order to avoid
431 | // an expensive context creation: bugzil.la/732441
432 |
433 | tests['webgl'] = function() {
434 | return !!window.WebGLRenderingContext;
435 | };
436 |
437 | /*
438 | * The Modernizr.touch test only indicates if the browser supports
439 | * touch events, which does not necessarily reflect a touchscreen
440 | * device, as evidenced by tablets running Windows 7 or, alas,
441 | * the Palm Pre / WebOS (touch) phones.
442 | *
443 | * Additionally, Chrome (desktop) used to lie about its support on this,
444 | * but that has since been rectified: crbug.com/36415
445 | *
446 | * We also test for Firefox 4 Multitouch Support.
447 | *
448 | * For more info, see: modernizr.github.com/Modernizr/touch.html
449 | */
450 |
451 | tests['touch'] = function() {
452 | var bool;
453 |
454 | if(('ontouchstart' in window) || window.DocumentTouch && document instanceof DocumentTouch) {
455 | bool = true;
456 | } else {
457 | injectElementWithStyles(['@media (',prefixes.join('touch-enabled),('),mod,')','{#modernizr{top:9px;position:absolute}}'].join(''), function( node ) {
458 | bool = node.offsetTop === 9;
459 | });
460 | }
461 |
462 | return bool;
463 | };
464 |
465 |
466 | // geolocation is often considered a trivial feature detect...
467 | // Turns out, it's quite tricky to get right:
468 | //
469 | // Using !!navigator.geolocation does two things we don't want. It:
470 | // 1. Leaks memory in IE9: github.com/Modernizr/Modernizr/issues/513
471 | // 2. Disables page caching in WebKit: webk.it/43956
472 | //
473 | // Meanwhile, in Firefox < 8, an about:config setting could expose
474 | // a false positive that would throw an exception: bugzil.la/688158
475 |
476 | tests['geolocation'] = function() {
477 | return 'geolocation' in navigator;
478 | };
479 |
480 |
481 | tests['postmessage'] = function() {
482 | return !!window.postMessage;
483 | };
484 |
485 |
486 | // Chrome incognito mode used to throw an exception when using openDatabase
487 | // It doesn't anymore.
488 | tests['websqldatabase'] = function() {
489 | return !!window.openDatabase;
490 | };
491 |
492 | // Vendors had inconsistent prefixing with the experimental Indexed DB:
493 | // - Webkit's implementation is accessible through webkitIndexedDB
494 | // - Firefox shipped moz_indexedDB before FF4b9, but since then has been mozIndexedDB
495 | // For speed, we don't test the legacy (and beta-only) indexedDB
496 | tests['indexedDB'] = function() {
497 | return !!testPropsAll("indexedDB", window);
498 | };
499 |
500 | // documentMode logic from YUI to filter out IE8 Compat Mode
501 | // which false positives.
502 | tests['hashchange'] = function() {
503 | return isEventSupported('hashchange', window) && (document.documentMode === undefined || document.documentMode > 7);
504 | };
505 |
506 | // Per 1.6:
507 | // This used to be Modernizr.historymanagement but the longer
508 | // name has been deprecated in favor of a shorter and property-matching one.
509 | // The old API is still available in 1.6, but as of 2.0 will throw a warning,
510 | // and in the first release thereafter disappear entirely.
511 | tests['history'] = function() {
512 | return !!(window.history && history.pushState);
513 | };
514 |
515 | tests['draganddrop'] = function() {
516 | var div = document.createElement('div');
517 | return ('draggable' in div) || ('ondragstart' in div && 'ondrop' in div);
518 | };
519 |
520 | // FF3.6 was EOL'ed on 4/24/12, but the ESR version of FF10
521 | // will be supported until FF19 (2/12/13), at which time, ESR becomes FF17.
522 | // FF10 still uses prefixes, so check for it until then.
523 | // for more ESR info, see: mozilla.org/en-US/firefox/organizations/faq/
524 | tests['websockets'] = function() {
525 | return 'WebSocket' in window || 'MozWebSocket' in window;
526 | };
527 |
528 |
529 | // css-tricks.com/rgba-browser-support/
530 | tests['rgba'] = function() {
531 | // Set an rgba() color and check the returned value
532 |
533 | setCss('background-color:rgba(150,255,150,.5)');
534 |
535 | return contains(mStyle.backgroundColor, 'rgba');
536 | };
537 |
538 | tests['hsla'] = function() {
539 | // Same as rgba(), in fact, browsers re-map hsla() to rgba() internally,
540 | // except IE9 who retains it as hsla
541 |
542 | setCss('background-color:hsla(120,40%,100%,.5)');
543 |
544 | return contains(mStyle.backgroundColor, 'rgba') || contains(mStyle.backgroundColor, 'hsla');
545 | };
546 |
547 | tests['multiplebgs'] = function() {
548 | // Setting multiple images AND a color on the background shorthand property
549 | // and then querying the style.background property value for the number of
550 | // occurrences of "url(" is a reliable method for detecting ACTUAL support for this!
551 |
552 | setCss('background:url(https://),url(https://),red url(https://)');
553 |
554 | // If the UA supports multiple backgrounds, there should be three occurrences
555 | // of the string "url(" in the return value for elemStyle.background
556 |
557 | return (/(url\s*\(.*?){3}/).test(mStyle.background);
558 | };
559 |
560 |
561 |
562 | // this will false positive in Opera Mini
563 | // github.com/Modernizr/Modernizr/issues/396
564 |
565 | tests['backgroundsize'] = function() {
566 | return testPropsAll('backgroundSize');
567 | };
568 |
569 | tests['borderimage'] = function() {
570 | return testPropsAll('borderImage');
571 | };
572 |
573 |
574 | // Super comprehensive table about all the unique implementations of
575 | // border-radius: muddledramblings.com/table-of-css3-border-radius-compliance
576 |
577 | tests['borderradius'] = function() {
578 | return testPropsAll('borderRadius');
579 | };
580 |
581 | // WebOS unfortunately false positives on this test.
582 | tests['boxshadow'] = function() {
583 | return testPropsAll('boxShadow');
584 | };
585 |
586 | // FF3.0 will false positive on this test
587 | tests['textshadow'] = function() {
588 | return document.createElement('div').style.textShadow === '';
589 | };
590 |
591 |
592 | tests['opacity'] = function() {
593 | // Browsers that actually have CSS Opacity implemented have done so
594 | // according to spec, which means their return values are within the
595 | // range of [0.0,1.0] - including the leading zero.
596 |
597 | setCssAll('opacity:.55');
598 |
599 | // The non-literal . in this regex is intentional:
600 | // German Chrome returns this value as 0,55
601 | // github.com/Modernizr/Modernizr/issues/#issue/59/comment/516632
602 | return (/^0.55$/).test(mStyle.opacity);
603 | };
604 |
605 |
606 | // Note, Android < 4 will pass this test, but can only animate
607 | // a single property at a time
608 | // daneden.me/2011/12/putting-up-with-androids-bullshit/
609 | tests['cssanimations'] = function() {
610 | return testPropsAll('animationName');
611 | };
612 |
613 |
614 | tests['csscolumns'] = function() {
615 | return testPropsAll('columnCount');
616 | };
617 |
618 |
619 | tests['cssgradients'] = function() {
620 | /**
621 | * For CSS Gradients syntax, please see:
622 | * webkit.org/blog/175/introducing-css-gradients/
623 | * developer.mozilla.org/en/CSS/-moz-linear-gradient
624 | * developer.mozilla.org/en/CSS/-moz-radial-gradient
625 | * dev.w3.org/csswg/css3-images/#gradients-
626 | */
627 |
628 | var str1 = 'background-image:',
629 | str2 = 'gradient(linear,left top,right bottom,from(#9f9),to(white));',
630 | str3 = 'linear-gradient(left top,#9f9, white);';
631 |
632 | setCss(
633 | // legacy webkit syntax (FIXME: remove when syntax not in use anymore)
634 | (str1 + '-webkit- '.split(' ').join(str2 + str1) +
635 | // standard syntax // trailing 'background-image:'
636 | prefixes.join(str3 + str1)).slice(0, -str1.length)
637 | );
638 |
639 | return contains(mStyle.backgroundImage, 'gradient');
640 | };
641 |
642 |
643 | tests['cssreflections'] = function() {
644 | return testPropsAll('boxReflect');
645 | };
646 |
647 |
648 | tests['csstransforms'] = function() {
649 | return !!testPropsAll('transform');
650 | };
651 |
652 |
653 | tests['csstransforms3d'] = function() {
654 |
655 | var ret = !!testPropsAll('perspective');
656 |
657 | // Webkit's 3D transforms are passed off to the browser's own graphics renderer.
658 | // It works fine in Safari on Leopard and Snow Leopard, but not in Chrome in
659 | // some conditions. As a result, Webkit typically recognizes the syntax but
660 | // will sometimes throw a false positive, thus we must do a more thorough check:
661 | if ( ret && 'webkitPerspective' in docElement.style ) {
662 |
663 | // Webkit allows this media query to succeed only if the feature is enabled.
664 | // `@media (transform-3d),(-webkit-transform-3d){ ... }`
665 | injectElementWithStyles('@media (transform-3d),(-webkit-transform-3d){#modernizr{left:9px;position:absolute;height:3px;}}', function( node, rule ) {
666 | ret = node.offsetLeft === 9 && node.offsetHeight === 3;
667 | });
668 | }
669 | return ret;
670 | };
671 |
672 |
673 | tests['csstransitions'] = function() {
674 | return testPropsAll('transition');
675 | };
676 |
677 |
678 | /*>>fontface*/
679 | // @font-face detection routine by Diego Perini
680 | // javascript.nwbox.com/CSSSupport/
681 |
682 | // false positives:
683 | // WebOS github.com/Modernizr/Modernizr/issues/342
684 | // WP7 github.com/Modernizr/Modernizr/issues/538
685 | tests['fontface'] = function() {
686 | var bool;
687 |
688 | injectElementWithStyles('@font-face {font-family:"font";src:url("https://")}', function( node, rule ) {
689 | var style = document.getElementById('smodernizr'),
690 | sheet = style.sheet || style.styleSheet,
691 | cssText = sheet ? (sheet.cssRules && sheet.cssRules[0] ? sheet.cssRules[0].cssText : sheet.cssText || '') : '';
692 |
693 | bool = /src/i.test(cssText) && cssText.indexOf(rule.split(' ')[0]) === 0;
694 | });
695 |
696 | return bool;
697 | };
698 | /*>>fontface*/
699 |
700 | // CSS generated content detection
701 | tests['generatedcontent'] = function() {
702 | var bool;
703 |
704 | injectElementWithStyles(['#',mod,'{font:0/0 a}#',mod,':after{content:"',smile,'";visibility:hidden;font:3px/1 a}'].join(''), function( node ) {
705 | bool = node.offsetHeight >= 3;
706 | });
707 |
708 | return bool;
709 | };
710 |
711 |
712 |
713 | // These tests evaluate support of the video/audio elements, as well as
714 | // testing what types of content they support.
715 | //
716 | // We're using the Boolean constructor here, so that we can extend the value
717 | // e.g. Modernizr.video // true
718 | // Modernizr.video.ogg // 'probably'
719 | //
720 | // Codec values from : github.com/NielsLeenheer/html5test/blob/9106a8/index.html#L845
721 | // thx to NielsLeenheer and zcorpan
722 |
723 | // Note: in some older browsers, "no" was a return value instead of empty string.
724 | // It was live in FF3.5.0 and 3.5.1, but fixed in 3.5.2
725 | // It was also live in Safari 4.0.0 - 4.0.4, but fixed in 4.0.5
726 |
727 | tests['video'] = function() {
728 | var elem = document.createElement('video'),
729 | bool = false;
730 |
731 | // IE9 Running on Windows Server SKU can cause an exception to be thrown, bug #224
732 | try {
733 | if ( bool = !!elem.canPlayType ) {
734 | bool = new Boolean(bool);
735 | bool.ogg = elem.canPlayType('video/ogg; codecs="theora"') .replace(/^no$/,'');
736 |
737 | // Without QuickTime, this value will be `undefined`. github.com/Modernizr/Modernizr/issues/546
738 | bool.h264 = elem.canPlayType('video/mp4; codecs="avc1.42E01E"') .replace(/^no$/,'');
739 |
740 | bool.webm = elem.canPlayType('video/webm; codecs="vp8, vorbis"').replace(/^no$/,'');
741 | }
742 |
743 | } catch(e) { }
744 |
745 | return bool;
746 | };
747 |
748 | tests['audio'] = function() {
749 | var elem = document.createElement('audio'),
750 | bool = false;
751 |
752 | try {
753 | if ( bool = !!elem.canPlayType ) {
754 | bool = new Boolean(bool);
755 | bool.ogg = elem.canPlayType('audio/ogg; codecs="vorbis"').replace(/^no$/,'');
756 | bool.mp3 = elem.canPlayType('audio/mpeg;') .replace(/^no$/,'');
757 |
758 | // Mimetypes accepted:
759 | // developer.mozilla.org/En/Media_formats_supported_by_the_audio_and_video_elements
760 | // bit.ly/iphoneoscodecs
761 | bool.wav = elem.canPlayType('audio/wav; codecs="1"') .replace(/^no$/,'');
762 | bool.m4a = ( elem.canPlayType('audio/x-m4a;') ||
763 | elem.canPlayType('audio/aac;')) .replace(/^no$/,'');
764 | }
765 | } catch(e) { }
766 |
767 | return bool;
768 | };
769 |
770 |
771 | // In FF4, if disabled, window.localStorage should === null.
772 |
773 | // Normally, we could not test that directly and need to do a
774 | // `('localStorage' in window) && ` test first because otherwise Firefox will
775 | // throw bugzil.la/365772 if cookies are disabled
776 |
777 | // Also in iOS5 Private Browsing mode, attempting to use localStorage.setItem
778 | // will throw the exception:
779 | // QUOTA_EXCEEDED_ERRROR DOM Exception 22.
780 | // Peculiarly, getItem and removeItem calls do not throw.
781 |
782 | // Because we are forced to try/catch this, we'll go aggressive.
783 |
784 | // Just FWIW: IE8 Compat mode supports these features completely:
785 | // www.quirksmode.org/dom/html5.html
786 | // But IE8 doesn't support either with local files
787 |
788 | tests['localstorage'] = function() {
789 | try {
790 | localStorage.setItem(mod, mod);
791 | localStorage.removeItem(mod);
792 | return true;
793 | } catch(e) {
794 | return false;
795 | }
796 | };
797 |
798 | tests['sessionstorage'] = function() {
799 | try {
800 | sessionStorage.setItem(mod, mod);
801 | sessionStorage.removeItem(mod);
802 | return true;
803 | } catch(e) {
804 | return false;
805 | }
806 | };
807 |
808 |
809 | tests['webworkers'] = function() {
810 | return !!window.Worker;
811 | };
812 |
813 |
814 | tests['applicationcache'] = function() {
815 | return !!window.applicationCache;
816 | };
817 |
818 |
819 | // Thanks to Erik Dahlstrom
820 | tests['svg'] = function() {
821 | return !!document.createElementNS && !!document.createElementNS(ns.svg, 'svg').createSVGRect;
822 | };
823 |
824 | // specifically for SVG inline in HTML, not within XHTML
825 | // test page: paulirish.com/demo/inline-svg
826 | tests['inlinesvg'] = function() {
827 | var div = document.createElement('div');
828 | div.innerHTML = '';
829 | return (div.firstChild && div.firstChild.namespaceURI) == ns.svg;
830 | };
831 |
832 | // SVG SMIL animation
833 | tests['smil'] = function() {
834 | return !!document.createElementNS && /SVGAnimate/.test(toString.call(document.createElementNS(ns.svg, 'animate')));
835 | };
836 |
837 | // This test is only for clip paths in SVG proper, not clip paths on HTML content
838 | // demo: srufaculty.sru.edu/david.dailey/svg/newstuff/clipPath4.svg
839 |
840 | // However read the comments to dig into applying SVG clippaths to HTML content here:
841 | // github.com/Modernizr/Modernizr/issues/213#issuecomment-1149491
842 | tests['svgclippaths'] = function() {
843 | return !!document.createElementNS && /SVGClipPath/.test(toString.call(document.createElementNS(ns.svg, 'clipPath')));
844 | };
845 |
846 | /*>>webforms*/
847 | // input features and input types go directly onto the ret object, bypassing the tests loop.
848 | // Hold this guy to execute in a moment.
849 | function webforms() {
850 | /*>>input*/
851 | // Run through HTML5's new input attributes to see if the UA understands any.
852 | // We're using f which is the element created early on
853 | // Mike Taylr has created a comprehensive resource for testing these attributes
854 | // when applied to all input types:
855 | // miketaylr.com/code/input-type-attr.html
856 | // spec: www.whatwg.org/specs/web-apps/current-work/multipage/the-input-element.html#input-type-attr-summary
857 |
858 | // Only input placeholder is tested while textarea's placeholder is not.
859 | // Currently Safari 4 and Opera 11 have support only for the input placeholder
860 | // Both tests are available in feature-detects/forms-placeholder.js
861 | Modernizr['input'] = (function( props ) {
862 | for ( var i = 0, len = props.length; i < len; i++ ) {
863 | attrs[ props[i] ] = !!(props[i] in inputElem);
864 | }
865 | if (attrs.list){
866 | // safari false positive's on datalist: webk.it/74252
867 | // see also github.com/Modernizr/Modernizr/issues/146
868 | attrs.list = !!(document.createElement('datalist') && window.HTMLDataListElement);
869 | }
870 | return attrs;
871 | })('autocomplete autofocus list placeholder max min multiple pattern required step'.split(' '));
872 | /*>>input*/
873 |
874 | /*>>inputtypes*/
875 | // Run through HTML5's new input types to see if the UA understands any.
876 | // This is put behind the tests runloop because it doesn't return a
877 | // true/false like all the other tests; instead, it returns an object
878 | // containing each input type with its corresponding true/false value
879 |
880 | // Big thanks to @miketaylr for the html5 forms expertise. miketaylr.com/
881 | Modernizr['inputtypes'] = (function(props) {
882 |
883 | for ( var i = 0, bool, inputElemType, defaultView, len = props.length; i < len; i++ ) {
884 |
885 | inputElem.setAttribute('type', inputElemType = props[i]);
886 | bool = inputElem.type !== 'text';
887 |
888 | // We first check to see if the type we give it sticks..
889 | // If the type does, we feed it a textual value, which shouldn't be valid.
890 | // If the value doesn't stick, we know there's input sanitization which infers a custom UI
891 | if ( bool ) {
892 |
893 | inputElem.value = smile;
894 | inputElem.style.cssText = 'position:absolute;visibility:hidden;';
895 |
896 | if ( /^range$/.test(inputElemType) && inputElem.style.WebkitAppearance !== undefined ) {
897 |
898 | docElement.appendChild(inputElem);
899 | defaultView = document.defaultView;
900 |
901 | // Safari 2-4 allows the smiley as a value, despite making a slider
902 | bool = defaultView.getComputedStyle &&
903 | defaultView.getComputedStyle(inputElem, null).WebkitAppearance !== 'textfield' &&
904 | // Mobile android web browser has false positive, so must
905 | // check the height to see if the widget is actually there.
906 | (inputElem.offsetHeight !== 0);
907 |
908 | docElement.removeChild(inputElem);
909 |
910 | } else if ( /^(search|tel)$/.test(inputElemType) ){
911 | // Spec doesn't define any special parsing or detectable UI
912 | // behaviors so we pass these through as true
913 |
914 | // Interestingly, opera fails the earlier test, so it doesn't
915 | // even make it here.
916 |
917 | } else if ( /^(url|email)$/.test(inputElemType) ) {
918 | // Real url and email support comes with prebaked validation.
919 | bool = inputElem.checkValidity && inputElem.checkValidity() === false;
920 |
921 | } else {
922 | // If the upgraded input compontent rejects the :) text, we got a winner
923 | bool = inputElem.value != smile;
924 | }
925 | }
926 |
927 | inputs[ props[i] ] = !!bool;
928 | }
929 | return inputs;
930 | })('search tel url email datetime date month week time datetime-local number range color'.split(' '));
931 | /*>>inputtypes*/
932 | }
933 | /*>>webforms*/
934 |
935 |
936 | // End of test definitions
937 | // -----------------------
938 |
939 |
940 |
941 | // Run through all tests and detect their support in the current UA.
942 | // todo: hypothetically we could be doing an array of tests and use a basic loop here.
943 | for ( var feature in tests ) {
944 | if ( hasOwnProp(tests, feature) ) {
945 | // run the test, throw the return value into the Modernizr,
946 | // then based on that boolean, define an appropriate className
947 | // and push it into an array of classes we'll join later.
948 | featureName = feature.toLowerCase();
949 | Modernizr[featureName] = tests[feature]();
950 |
951 | classes.push((Modernizr[featureName] ? '' : 'no-') + featureName);
952 | }
953 | }
954 |
955 | /*>>webforms*/
956 | // input tests need to run.
957 | Modernizr.input || webforms();
958 | /*>>webforms*/
959 |
960 |
961 | /**
962 | * addTest allows the user to define their own feature tests
963 | * the result will be added onto the Modernizr object,
964 | * as well as an appropriate className set on the html element
965 | *
966 | * @param feature - String naming the feature
967 | * @param test - Function returning true if feature is supported, false if not
968 | */
969 | Modernizr.addTest = function ( feature, test ) {
970 | if ( typeof feature == 'object' ) {
971 | for ( var key in feature ) {
972 | if ( hasOwnProp( feature, key ) ) {
973 | Modernizr.addTest( key, feature[ key ] );
974 | }
975 | }
976 | } else {
977 |
978 | feature = feature.toLowerCase();
979 |
980 | if ( Modernizr[feature] !== undefined ) {
981 | // we're going to quit if you're trying to overwrite an existing test
982 | // if we were to allow it, we'd do this:
983 | // var re = new RegExp("\\b(no-)?" + feature + "\\b");
984 | // docElement.className = docElement.className.replace( re, '' );
985 | // but, no rly, stuff 'em.
986 | return Modernizr;
987 | }
988 |
989 | test = typeof test == 'function' ? test() : test;
990 |
991 | if (typeof enableClasses !== "undefined" && enableClasses) {
992 | docElement.className += ' ' + (test ? '' : 'no-') + feature;
993 | }
994 | Modernizr[feature] = test;
995 |
996 | }
997 |
998 | return Modernizr; // allow chaining.
999 | };
1000 |
1001 |
1002 | // Reset modElem.cssText to nothing to reduce memory footprint.
1003 | setCss('');
1004 | modElem = inputElem = null;
1005 |
1006 | /*>>shiv*/
1007 | /**
1008 | * @preserve HTML5 Shiv prev3.7.1 | @afarkas @jdalton @jon_neal @rem | MIT/GPL2 Licensed
1009 | */
1010 | ;(function(window, document) {
1011 | /*jshint evil:true */
1012 | /** version */
1013 | var version = '3.7.0';
1014 |
1015 | /** Preset options */
1016 | var options = window.html5 || {};
1017 |
1018 | /** Used to skip problem elements */
1019 | var reSkip = /^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i;
1020 |
1021 | /** Not all elements can be cloned in IE **/
1022 | var saveClones = /^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i;
1023 |
1024 | /** Detect whether the browser supports default html5 styles */
1025 | var supportsHtml5Styles;
1026 |
1027 | /** Name of the expando, to work with multiple documents or to re-shiv one document */
1028 | var expando = '_html5shiv';
1029 |
1030 | /** The id for the the documents expando */
1031 | var expanID = 0;
1032 |
1033 | /** Cached data for each document */
1034 | var expandoData = {};
1035 |
1036 | /** Detect whether the browser supports unknown elements */
1037 | var supportsUnknownElements;
1038 |
1039 | (function() {
1040 | try {
1041 | var a = document.createElement('a');
1042 | a.innerHTML = '';
1043 | //if the hidden property is implemented we can assume, that the browser supports basic HTML5 Styles
1044 | supportsHtml5Styles = ('hidden' in a);
1045 |
1046 | supportsUnknownElements = a.childNodes.length == 1 || (function() {
1047 | // assign a false positive if unable to shiv
1048 | (document.createElement)('a');
1049 | var frag = document.createDocumentFragment();
1050 | return (
1051 | typeof frag.cloneNode == 'undefined' ||
1052 | typeof frag.createDocumentFragment == 'undefined' ||
1053 | typeof frag.createElement == 'undefined'
1054 | );
1055 | }());
1056 | } catch(e) {
1057 | // assign a false positive if detection fails => unable to shiv
1058 | supportsHtml5Styles = true;
1059 | supportsUnknownElements = true;
1060 | }
1061 |
1062 | }());
1063 |
1064 | /*--------------------------------------------------------------------------*/
1065 |
1066 | /**
1067 | * Creates a style sheet with the given CSS text and adds it to the document.
1068 | * @private
1069 | * @param {Document} ownerDocument The document.
1070 | * @param {String} cssText The CSS text.
1071 | * @returns {StyleSheet} The style element.
1072 | */
1073 | function addStyleSheet(ownerDocument, cssText) {
1074 | var p = ownerDocument.createElement('p'),
1075 | parent = ownerDocument.getElementsByTagName('head')[0] || ownerDocument.documentElement;
1076 |
1077 | p.innerHTML = 'x';
1078 | return parent.insertBefore(p.lastChild, parent.firstChild);
1079 | }
1080 |
1081 | /**
1082 | * Returns the value of `html5.elements` as an array.
1083 | * @private
1084 | * @returns {Array} An array of shived element node names.
1085 | */
1086 | function getElements() {
1087 | var elements = html5.elements;
1088 | return typeof elements == 'string' ? elements.split(' ') : elements;
1089 | }
1090 |
1091 | /**
1092 | * Returns the data associated to the given document
1093 | * @private
1094 | * @param {Document} ownerDocument The document.
1095 | * @returns {Object} An object of data.
1096 | */
1097 | function getExpandoData(ownerDocument) {
1098 | var data = expandoData[ownerDocument[expando]];
1099 | if (!data) {
1100 | data = {};
1101 | expanID++;
1102 | ownerDocument[expando] = expanID;
1103 | expandoData[expanID] = data;
1104 | }
1105 | return data;
1106 | }
1107 |
1108 | /**
1109 | * returns a shived element for the given nodeName and document
1110 | * @memberOf html5
1111 | * @param {String} nodeName name of the element
1112 | * @param {Document} ownerDocument The context document.
1113 | * @returns {Object} The shived element.
1114 | */
1115 | function createElement(nodeName, ownerDocument, data){
1116 | if (!ownerDocument) {
1117 | ownerDocument = document;
1118 | }
1119 | if(supportsUnknownElements){
1120 | return ownerDocument.createElement(nodeName);
1121 | }
1122 | if (!data) {
1123 | data = getExpandoData(ownerDocument);
1124 | }
1125 | var node;
1126 |
1127 | if (data.cache[nodeName]) {
1128 | node = data.cache[nodeName].cloneNode();
1129 | } else if (saveClones.test(nodeName)) {
1130 | node = (data.cache[nodeName] = data.createElem(nodeName)).cloneNode();
1131 | } else {
1132 | node = data.createElem(nodeName);
1133 | }
1134 |
1135 | // Avoid adding some elements to fragments in IE < 9 because
1136 | // * Attributes like `name` or `type` cannot be set/changed once an element
1137 | // is inserted into a document/fragment
1138 | // * Link elements with `src` attributes that are inaccessible, as with
1139 | // a 403 response, will cause the tab/window to crash
1140 | // * Script elements appended to fragments will execute when their `src`
1141 | // or `text` property is set
1142 | return node.canHaveChildren && !reSkip.test(nodeName) && !node.tagUrn ? data.frag.appendChild(node) : node;
1143 | }
1144 |
1145 | /**
1146 | * returns a shived DocumentFragment for the given document
1147 | * @memberOf html5
1148 | * @param {Document} ownerDocument The context document.
1149 | * @returns {Object} The shived DocumentFragment.
1150 | */
1151 | function createDocumentFragment(ownerDocument, data){
1152 | if (!ownerDocument) {
1153 | ownerDocument = document;
1154 | }
1155 | if(supportsUnknownElements){
1156 | return ownerDocument.createDocumentFragment();
1157 | }
1158 | data = data || getExpandoData(ownerDocument);
1159 | var clone = data.frag.cloneNode(),
1160 | i = 0,
1161 | elems = getElements(),
1162 | l = elems.length;
1163 | for(;i>shiv*/
1309 |
1310 | // Assign private properties to the return object with prefix
1311 | Modernizr._version = version;
1312 |
1313 | // expose these for the plugin API. Look in the source for how to join() them against your input
1314 | /*>>prefixes*/
1315 | Modernizr._prefixes = prefixes;
1316 | /*>>prefixes*/
1317 | /*>>domprefixes*/
1318 | Modernizr._domPrefixes = domPrefixes;
1319 | Modernizr._cssomPrefixes = cssomPrefixes;
1320 | /*>>domprefixes*/
1321 |
1322 | /*>>mq*/
1323 | // Modernizr.mq tests a given media query, live against the current state of the window
1324 | // A few important notes:
1325 | // * If a browser does not support media queries at all (eg. oldIE) the mq() will always return false
1326 | // * A max-width or orientation query will be evaluated against the current state, which may change later.
1327 | // * You must specify values. Eg. If you are testing support for the min-width media query use:
1328 | // Modernizr.mq('(min-width:0)')
1329 | // usage:
1330 | // Modernizr.mq('only screen and (max-width:768)')
1331 | Modernizr.mq = testMediaQuery;
1332 | /*>>mq*/
1333 |
1334 | /*>>hasevent*/
1335 | // Modernizr.hasEvent() detects support for a given event, with an optional element to test on
1336 | // Modernizr.hasEvent('gesturestart', elem)
1337 | Modernizr.hasEvent = isEventSupported;
1338 | /*>>hasevent*/
1339 |
1340 | /*>>testprop*/
1341 | // Modernizr.testProp() investigates whether a given style property is recognized
1342 | // Note that the property names must be provided in the camelCase variant.
1343 | // Modernizr.testProp('pointerEvents')
1344 | Modernizr.testProp = function(prop){
1345 | return testProps([prop]);
1346 | };
1347 | /*>>testprop*/
1348 |
1349 | /*>>testallprops*/
1350 | // Modernizr.testAllProps() investigates whether a given style property,
1351 | // or any of its vendor-prefixed variants, is recognized
1352 | // Note that the property names must be provided in the camelCase variant.
1353 | // Modernizr.testAllProps('boxSizing')
1354 | Modernizr.testAllProps = testPropsAll;
1355 | /*>>testallprops*/
1356 |
1357 |
1358 | /*>>teststyles*/
1359 | // Modernizr.testStyles() allows you to add custom styles to the document and test an element afterwards
1360 | // Modernizr.testStyles('#modernizr { position:absolute }', function(elem, rule){ ... })
1361 | Modernizr.testStyles = injectElementWithStyles;
1362 | /*>>teststyles*/
1363 |
1364 |
1365 | /*>>prefixed*/
1366 | // Modernizr.prefixed() returns the prefixed or nonprefixed property name variant of your input
1367 | // Modernizr.prefixed('boxSizing') // 'MozBoxSizing'
1368 |
1369 | // Properties must be passed as dom-style camelcase, rather than `box-sizing` hypentated style.
1370 | // Return values will also be the camelCase variant, if you need to translate that to hypenated style use:
1371 | //
1372 | // str.replace(/([A-Z])/g, function(str,m1){ return '-' + m1.toLowerCase(); }).replace(/^ms-/,'-ms-');
1373 |
1374 | // If you're trying to ascertain which transition end event to bind to, you might do something like...
1375 | //
1376 | // var transEndEventNames = {
1377 | // 'WebkitTransition' : 'webkitTransitionEnd',
1378 | // 'MozTransition' : 'transitionend',
1379 | // 'OTransition' : 'oTransitionEnd',
1380 | // 'msTransition' : 'MSTransitionEnd',
1381 | // 'transition' : 'transitionend'
1382 | // },
1383 | // transEndEventName = transEndEventNames[ Modernizr.prefixed('transition') ];
1384 |
1385 | Modernizr.prefixed = function(prop, obj, elem){
1386 | if(!obj) {
1387 | return testPropsAll(prop, 'pfx');
1388 | } else {
1389 | // Testing DOM property e.g. Modernizr.prefixed('requestAnimationFrame', window) // 'mozRequestAnimationFrame'
1390 | return testPropsAll(prop, obj, elem);
1391 | }
1392 | };
1393 | /*>>prefixed*/
1394 |
1395 |
1396 | /*>>cssclasses*/
1397 | // Remove "no-js" class from element, if it exists:
1398 | docElement.className = docElement.className.replace(/(^|\s)no-js(\s|$)/, '$1$2') +
1399 |
1400 | // Add the new classes to the element.
1401 | (enableClasses ? ' js ' + classes.join(' ') : '');
1402 | /*>>cssclasses*/
1403 |
1404 | return Modernizr;
1405 |
1406 | })(this, this.document);
1407 |
--------------------------------------------------------------------------------
/src/lib/require.js:
--------------------------------------------------------------------------------
1 | /*
2 | RequireJS 2.1.10 Copyright (c) 2010-2014, The Dojo Foundation All Rights Reserved.
3 | Available via the MIT or new BSD license.
4 | see: http://github.com/jrburke/requirejs for details
5 | */
6 | var requirejs,require,define;
7 | (function(ca){function G(b){return"[object Function]"===N.call(b)}function H(b){return"[object Array]"===N.call(b)}function v(b,c){if(b){var d;for(d=0;dthis.depCount&&!this.defined){if(G(c)){if(this.events.error&&this.map.isDefine||h.onError!==da)try{f=i.execCb(b,c,e,f)}catch(d){a=d}else f=i.execCb(b,c,e,f);this.map.isDefine&&void 0===f&&((e=this.module)?f=e.exports:this.usingExports&&
19 | (f=this.exports));if(a)return a.requireMap=this.map,a.requireModules=this.map.isDefine?[this.map.id]:null,a.requireType=this.map.isDefine?"define":"require",w(this.error=a)}else f=c;this.exports=f;if(this.map.isDefine&&!this.ignore&&(p[b]=f,h.onResourceLoad))h.onResourceLoad(i,this.map,this.depMaps);y(b);this.defined=!0}this.defining=!1;this.defined&&!this.defineEmitted&&(this.defineEmitted=!0,this.emit("defined",this.exports),this.defineEmitComplete=!0)}}else this.fetch()}},callPlugin:function(){var a=
20 | this.map,b=a.id,d=m(a.prefix);this.depMaps.push(d);r(d,"defined",t(this,function(f){var d,g;g=j(ba,this.map.id);var J=this.map.name,u=this.map.parentMap?this.map.parentMap.name:null,p=i.makeRequire(a.parentMap,{enableBuildCallback:!0});if(this.map.unnormalized){if(f.normalize&&(J=f.normalize(J,function(a){return c(a,u,!0)})||""),f=m(a.prefix+"!"+J,this.map.parentMap),r(f,"defined",t(this,function(a){this.init([],function(){return a},null,{enabled:!0,ignore:!0})})),g=j(k,f.id)){this.depMaps.push(f);
21 | if(this.events.error)g.on("error",t(this,function(a){this.emit("error",a)}));g.enable()}}else g?(this.map.url=i.nameToUrl(g),this.load()):(d=t(this,function(a){this.init([],function(){return a},null,{enabled:!0})}),d.error=t(this,function(a){this.inited=!0;this.error=a;a.requireModules=[b];B(k,function(a){0===a.map.id.indexOf(b+"_unnormalized")&&y(a.map.id)});w(a)}),d.fromText=t(this,function(f,c){var g=a.name,J=m(g),k=O;c&&(f=c);k&&(O=!1);q(J);s(l.config,b)&&(l.config[g]=l.config[b]);try{h.exec(f)}catch(j){return w(C("fromtexteval",
22 | "fromText eval for "+b+" failed: "+j,j,[b]))}k&&(O=!0);this.depMaps.push(J);i.completeLoad(g);p([g],d)}),f.load(a.name,p,d,l))}));i.enable(d,this);this.pluginMaps[d.id]=d},enable:function(){W[this.map.id]=this;this.enabling=this.enabled=!0;v(this.depMaps,t(this,function(a,b){var c,f;if("string"===typeof a){a=m(a,this.map.isDefine?this.map:this.map.parentMap,!1,!this.skipMap);this.depMaps[b]=a;if(c=j(K,a.id)){this.depExports[b]=c(this);return}this.depCount+=1;r(a,"defined",t(this,function(a){this.defineDep(b,
23 | a);this.check()}));this.errback&&r(a,"error",t(this,this.errback))}c=a.id;f=k[c];!s(K,c)&&(f&&!f.enabled)&&i.enable(a,this)}));B(this.pluginMaps,t(this,function(a){var b=j(k,a.id);b&&!b.enabled&&i.enable(a,this)}));this.enabling=!1;this.check()},on:function(a,b){var c=this.events[a];c||(c=this.events[a]=[]);c.push(b)},emit:function(a,b){v(this.events[a],function(a){a(b)});"error"===a&&delete this.events[a]}};i={config:l,contextName:b,registry:k,defined:p,urlFetched:T,defQueue:A,Module:$,makeModuleMap:m,
24 | nextTick:h.nextTick,onError:w,configure:function(a){a.baseUrl&&"/"!==a.baseUrl.charAt(a.baseUrl.length-1)&&(a.baseUrl+="/");var b=l.shim,c={paths:!0,bundles:!0,config:!0,map:!0};B(a,function(a,b){c[b]?(l[b]||(l[b]={}),V(l[b],a,!0,!0)):l[b]=a});a.bundles&&B(a.bundles,function(a,b){v(a,function(a){a!==b&&(ba[a]=b)})});a.shim&&(B(a.shim,function(a,c){H(a)&&(a={deps:a});if((a.exports||a.init)&&!a.exportsFn)a.exportsFn=i.makeShimExports(a);b[c]=a}),l.shim=b);a.packages&&v(a.packages,function(a){var b,
25 | a="string"===typeof a?{name:a}:a;b=a.name;a.location&&(l.paths[b]=a.location);l.pkgs[b]=a.name+"/"+(a.main||"main").replace(ja,"").replace(R,"")});B(k,function(a,b){!a.inited&&!a.map.unnormalized&&(a.map=m(b))});if(a.deps||a.callback)i.require(a.deps||[],a.callback)},makeShimExports:function(a){return function(){var b;a.init&&(b=a.init.apply(ca,arguments));return b||a.exports&&ea(a.exports)}},makeRequire:function(a,e){function g(f,c,d){var j,l;e.enableBuildCallback&&(c&&G(c))&&(c.__requireJsBuild=
26 | !0);if("string"===typeof f){if(G(c))return w(C("requireargs","Invalid require call"),d);if(a&&s(K,f))return K[f](k[a.id]);if(h.get)return h.get(i,f,a,g);j=m(f,a,!1,!0);j=j.id;return!s(p,j)?w(C("notloaded",'Module name "'+j+'" has not been loaded yet for context: '+b+(a?"":". Use require([])"))):p[j]}M();i.nextTick(function(){M();l=q(m(null,a));l.skipMap=e.skipMap;l.init(f,c,d,{enabled:!0});D()});return g}e=e||{};V(g,{isBrowser:z,toUrl:function(b){var e,d=b.lastIndexOf("."),g=b.split("/")[0];if(-1!==
27 | d&&(!("."===g||".."===g)||1g.attachEvent.toString().indexOf("[native code"))&&!Z?(O=!0,g.attachEvent("onreadystatechange",b.onScriptLoad)):
34 | (g.addEventListener("load",b.onScriptLoad,!1),g.addEventListener("error",b.onScriptError,!1)),g.src=d,M=g,D?y.insertBefore(g,D):y.appendChild(g),M=null,g;if(fa)try{importScripts(d),b.completeLoad(c)}catch(j){b.onError(C("importscripts","importScripts failed for "+c+" at "+d,j,[c]))}};z&&!r.skipDataMain&&U(document.getElementsByTagName("script"),function(b){y||(y=b.parentNode);if(L=b.getAttribute("data-main"))return q=L,r.baseUrl||(E=q.split("/"),q=E.pop(),Q=E.length?E.join("/")+"/":"./",r.baseUrl=
35 | Q),q=q.replace(R,""),h.jsExtRegExp.test(q)&&(q=L),r.deps=r.deps?r.deps.concat(q):[q],!0});define=function(b,c,d){var g,h;"string"!==typeof b&&(d=c,c=b,b=null);H(c)||(d=c,c=null);!c&&G(d)&&(c=[],d.length&&(d.toString().replace(la,"").replace(ma,function(b,d){c.push(d)}),c=(1===d.length?["require"]:["require","exports","module"]).concat(c)));if(O){if(!(g=M))P&&"interactive"===P.readyState||U(document.getElementsByTagName("script"),function(b){if("interactive"===b.readyState)return P=b}),g=P;g&&(b||
36 | (b=g.getAttribute("data-requiremodule")),h=F[g.getAttribute("data-requirecontext")])}(h?h.defQueue:S).push([b,c,d])};define.amd={jQuery:!0};h.exec=function(b){return eval(b)};h(r)}})(this);
37 |
--------------------------------------------------------------------------------
/src/lib/stats.min.js:
--------------------------------------------------------------------------------
1 | // stats.js - http://github.com/mrdoob/stats.js
2 | var Stats=function(){var l=Date.now(),m=l,g=0,n=Infinity,o=0,h=0,p=Infinity,q=0,r=0,s=0,f=document.createElement("div");f.id="stats";f.addEventListener("mousedown",function(b){b.preventDefault();t(++s%2)},!1);f.style.cssText="width:80px;opacity:0.9;cursor:pointer";var a=document.createElement("div");a.id="fps";a.style.cssText="padding:0 0 3px 3px;text-align:left;background-color:#002";f.appendChild(a);var i=document.createElement("div");i.id="fpsText";i.style.cssText="color:#0ff;font-family:Helvetica,Arial,sans-serif;font-size:9px;font-weight:bold;line-height:15px";
3 | i.innerHTML="FPS";a.appendChild(i);var c=document.createElement("div");c.id="fpsGraph";c.style.cssText="position:relative;width:74px;height:30px;background-color:#0ff";for(a.appendChild(c);74>c.children.length;){var j=document.createElement("span");j.style.cssText="width:1px;height:30px;float:left;background-color:#113";c.appendChild(j)}var d=document.createElement("div");d.id="ms";d.style.cssText="padding:0 0 3px 3px;text-align:left;background-color:#020;display:none";f.appendChild(d);var k=document.createElement("div");
4 | k.id="msText";k.style.cssText="color:#0f0;font-family:Helvetica,Arial,sans-serif;font-size:9px;font-weight:bold;line-height:15px";k.innerHTML="MS";d.appendChild(k);var e=document.createElement("div");e.id="msGraph";e.style.cssText="position:relative;width:74px;height:30px;background-color:#0f0";for(d.appendChild(e);74>e.children.length;)j=document.createElement("span"),j.style.cssText="width:1px;height:30px;float:left;background-color:#131",e.appendChild(j);var t=function(b){s=b;switch(s){case 0:a.style.display=
5 | "block";d.style.display="none";break;case 1:a.style.display="none",d.style.display="block"}};return{REVISION:11,domElement:f,setMode:t,begin:function(){l=Date.now()},end:function(){var b=Date.now();g=b-l;n=Math.min(n,g);o=Math.max(o,g);k.textContent=g+" MS ("+n+"-"+o+")";var a=Math.min(30,30-30*(g/200));e.appendChild(e.firstChild).style.height=a+"px";r++;b>m+1E3&&(h=Math.round(1E3*r/(b-m)),p=Math.min(p,h),q=Math.max(q,h),i.textContent=h+" FPS ("+p+"-"+q+")",a=Math.min(30,30-30*(h/100)),c.appendChild(c.firstChild).style.height=
6 | a+"px",m=b,r=0);return b},update:function(){l=this.end()}}};
7 |
--------------------------------------------------------------------------------
/src/valiant.jquery.js:
--------------------------------------------------------------------------------
1 | /*!
2 | * Valiant360 panorama video player/photo viewer jquery plugin
3 | *
4 | * Copyright (c) 2014 Charlie Hoey <@flimshaw>
5 | *
6 | * Released under the MIT license:
7 | * http://www.opensource.org/licenses/mit-license.php
8 | *
9 | * Jquery plugin pattern based on https://github.com/jquery-boilerplate/jquery-patterns/blob/master/patterns/jquery.basic.plugin-boilerplate.js
10 | */
11 |
12 | /* REQUIREMENTS:
13 |
14 | jQuery 1.7.2 or greater
15 | three.js r65 or higher
16 |
17 | */
18 |
19 |
20 | /*!
21 | * jQuery lightweight plugin boilerplate
22 | * Original author: @ajpiano
23 | * Further changes, comments: @addyosmani
24 | * Licensed under the MIT license
25 | */
26 |
27 | // the semi-colon before the function invocation is a safety
28 | // net against concatenated scripts and/or other plugins
29 | // that are not closed properly.
30 | ;(function ( $, THREE, Detector, window, document, undefined ) {
31 |
32 | // undefined is used here as the undefined global
33 | // variable in ECMAScript 3 and is mutable (i.e. it can
34 | // be changed by someone else). undefined isn't really
35 | // being passed in so we can ensure that its value is
36 | // truly undefined. In ES5, undefined can no longer be
37 | // modified.
38 |
39 | // window and document are passed through as local
40 | // variables rather than as globals, because this (slightly)
41 | // quickens the resolution process and can be more
42 | // efficiently minified (especially when both are
43 | // regularly referenced in your plugin).
44 |
45 | // Create the defaults once
46 | var pluginName = "Valiant360",
47 | plugin, // will hold reference to instantiated Plugin
48 | defaults = {
49 | crossOrigin: 'anonymous',
50 | clickAndDrag: false,
51 | keyboardControls: true,
52 | fov: 35,
53 | fovMin: 3,
54 | fovMax: 100,
55 | hideControls: false,
56 | lon: 0,
57 | lat: 0,
58 | loop: "loop",
59 | muted: true,
60 | volume: 0.5,
61 | debug: false,
62 | flatProjection: false,
63 | autoplay: true
64 | };
65 |
66 | // The actual plugin constructor
67 | function Plugin( element, options ) {
68 | this.element = element;
69 |
70 | // jQuery has an extend method that merges the
71 | // contents of two or more objects, storing the
72 | // result in the first object. The first object
73 | // is generally empty because we don't want to alter
74 | // the default options for future instances of the plugin
75 | this.options = $.extend( {}, defaults, options) ;
76 |
77 | this._defaults = defaults;
78 | this._name = pluginName;
79 |
80 | this.init();
81 | }
82 |
83 | Plugin.prototype = {
84 |
85 | init: function() {
86 | // Place initialization logic here
87 | // You already have access to the DOM element and
88 | // the options via the instance, e.g. this.element
89 | // and this.options
90 | // you can add more functions like the one below and
91 | // call them like so: this.yourOtherFunction(this.element, this.options).
92 |
93 | // instantiate some local variables we're going to need
94 | this._time = new Date().getTime();
95 | this._controls = {};
96 | this._id = this.generateUUID();
97 |
98 | this._requestAnimationId = ''; // used to cancel requestAnimationFrame on destroy
99 | this._isVideo = false;
100 | this._isPhoto = false;
101 | this._isFullscreen = false;
102 | this._mouseDown = false;
103 | this._dragStart = {};
104 |
105 | this._lat = this.options.lat;
106 | this._lon = this.options.lon;
107 | this._fov = this.options.fov;
108 |
109 | // save our original height and width for returning from fullscreen
110 | this._originalWidth = $(this.element).find('canvas').width();
111 | this._originalHeight = $(this.element).find('canvas').height();
112 |
113 | // add a class to our element so it inherits the appropriate styles
114 | $(this.element).addClass('Valiant360_default');
115 |
116 | // add tabindex attribute to enable the focus on the element (required for keyboard controls)
117 | if(this.options.keyboardControls && !$(this.element).attr("tabindex")){
118 | $(this.element).attr("tabindex", "1");
119 | }
120 |
121 | this.createMediaPlayer();
122 | this.createControls();
123 |
124 | },
125 |
126 | generateUUID: function(){
127 | var d = new Date().getTime();
128 | var uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
129 | var r = (d + Math.random()*16)%16 | 0;
130 | d = Math.floor(d/16);
131 | return (c==='x' ? r : (r&0x7|0x8)).toString(16);
132 | });
133 | return uuid;
134 | },
135 |
136 | createMediaPlayer: function() {
137 |
138 | // make a self reference we can pass to our callbacks
139 | var self = this;
140 |
141 | // create a local THREE.js scene
142 | this._scene = new THREE.Scene();
143 |
144 | // create ThreeJS camera
145 | this._camera = new THREE.PerspectiveCamera(this._fov, $(this.element).width() / $(this.element).height(), 0.1, 1000);
146 | this._camera.setLens(this._fov);
147 |
148 | // create ThreeJS renderer and append it to our object
149 | this._renderer = Detector.webgl? new THREE.WebGLRenderer(): new THREE.CanvasRenderer();
150 | this._renderer.setSize( $(this.element).width(), $(this.element).height() );
151 | this._renderer.autoClear = false;
152 | this._renderer.setClearColor( 0x333333, 1 );
153 |
154 | // append the rendering element to this div
155 | $(this.element).append(this._renderer.domElement);
156 |
157 | var createAnimation = function () {
158 | self._texture.generateMipmaps = false;
159 | self._texture.minFilter = THREE.LinearFilter;
160 | self._texture.magFilter = THREE.LinearFilter;
161 | self._texture.format = THREE.RGBFormat;
162 |
163 | // create ThreeJS mesh sphere onto which our texture will be drawn
164 | self._mesh = new THREE.Mesh( new THREE.SphereGeometry( 500, 80, 50 ), new THREE.MeshBasicMaterial( { map: self._texture } ) );
165 | self._mesh.scale.x = -1; // mirror the texture, since we're looking from the inside out
166 | self._scene.add(self._mesh);
167 |
168 | self.animate();
169 | };
170 |
171 | // figure out our texturing situation, based on what our source is
172 | if( $(this.element).attr('data-photo-src') ) {
173 | this._isPhoto = true;
174 | THREE.ImageUtils.crossOrigin = this.options.crossOrigin;
175 | this._texture = THREE.ImageUtils.loadTexture( $(this.element).attr('data-photo-src') );
176 | createAnimation();
177 | } else {
178 | this._isVideo = true;
179 |
180 | // create loading overlay
181 | var loadingHTML = '';
185 | $(this.element).append(loadingHTML);
186 | this.showWaiting();
187 |
188 | // create off-dom video player
189 | this._video = document.createElement( 'video' );
190 | this._video.setAttribute('crossorigin', this.options.crossOrigin);
191 | this._video.style.display = 'none';
192 | $(this.element).append( this._video );
193 | this._video.loop = this.options.loop;
194 | this._video.muted = this.options.muted;
195 | this._video.volume = this.options.volume;
196 |
197 | // attach video player event listeners
198 | this._video.addEventListener("ended", function() {
199 |
200 | });
201 |
202 | // Progress Meter
203 | this._video.addEventListener("progress", function() {
204 | var percent = null;
205 | if (self._video && self._video.buffered && self._video.buffered.length > 0 && self._video.buffered.end && self._video.duration) {
206 | percent = self._video.buffered.end(0) / self._video.duration;
207 | }
208 | // Some browsers (e.g., FF3.6 and Safari 5) cannot calculate target.bufferered.end()
209 | // to be anything other than 0. If the byte count is available we use this instead.
210 | // Browsers that support the else if do not seem to have the bufferedBytes value and
211 | // should skip to there. Tested in Safari 5, Webkit head, FF3.6, Chrome 6, IE 7/8.
212 | else if (self._video && self._video.bytesTotal !== undefined && self._video.bytesTotal > 0 && self._video.bufferedBytes !== undefined) {
213 | percent = self._video.bufferedBytes / self._video.bytesTotal;
214 | }
215 |
216 | // Someday we can have a loading animation for videos
217 | var cpct = Math.round(percent * 100);
218 | if(cpct === 100) {
219 | // do something now that we are done
220 | } else {
221 | // do something with this percentage info (cpct)
222 | }
223 | });
224 | // Error listener
225 | this._video.addEventListener('error', function (event) {
226 | console.error(self._video.error);
227 | self.showError();
228 | });
229 |
230 | this._video.addEventListener("timeupdate", function() {
231 | if (this.paused === false){
232 | var percent = this.currentTime * 100 / this.duration;
233 | $(self.element).find('.controlsWrapper > .valiant-progress-bar')[0].children[0].setAttribute("style", "width:" + percent + "%;");
234 | $(self.element).find('.controlsWrapper > .valiant-progress-bar')[0].children[1].setAttribute("style", "width:" + (100 - percent) + "%;");
235 | //Update time label
236 | var durMin = Math.floor(this.duration / 60);
237 | var durSec = Math.floor(this.duration - (durMin * 60));
238 | var timeMin = Math.floor(this.currentTime / 60);
239 | var timeSec = Math.floor(this.currentTime - (timeMin * 60));
240 | var duration = durMin + ':' + (durSec < 10 ? '0' + durSec : durSec);
241 | var currentTime = timeMin + ':' + (timeSec < 10 ? '0' + timeSec : timeSec);
242 | $(self.element).find('.controls .timeLabel').html(currentTime+' / '+duration);
243 | }
244 | });
245 |
246 | // IE 11 and previous not supports THREE.Texture([video]), we must create a canvas that draws the video and use that to create the Texture
247 | var isIE = navigator.appName == 'Microsoft Internet Explorer' || !!(navigator.userAgent.match(/Trident/) || navigator.userAgent.match(/rv 11/));
248 | if (isIE) {
249 | this._videocanvas = document.createElement('canvas');
250 | this._texture = new THREE.Texture(this._videocanvas);
251 | // set canvas size = video size when known
252 | this._video.addEventListener('loadedmetadata', function () {
253 | self._videocanvas.width = self._video.videoWidth;
254 | self._videocanvas.height = self._video.videoHeight;
255 | createAnimation();
256 | });
257 | }else{
258 | this._texture = new THREE.Texture( this._video );
259 | }
260 |
261 | //force browser caching of the video to solve rendering errors with big videos
262 | var xhr = new XMLHttpRequest();
263 | xhr.open('GET', $(this.element).attr('data-video-src'), true);
264 | xhr.responseType = 'blob';
265 | xhr.onload = function (e) {
266 | if (this.status === 200) {
267 | var vid = (window.webkitURL ? webkitURL : URL).createObjectURL(this.response);
268 | //Video Play Listener, fires after video loads
269 | $(self._video).bind("canplaythrough", function () {
270 | if (self.options.autoplay === true) {
271 | self.hideWaiting();
272 | self.play();
273 | self._videoReady = true;
274 | }
275 | });
276 |
277 | // set the video src and begin loading
278 | self._video.src = vid;
279 | }
280 | };
281 | xhr.onreadystatechange = function (oEvent) {
282 | if (xhr.readyState === 4) {
283 | if (xhr.status !== 200) {
284 | console.error('Video error: status ' + xhr.status);
285 | self.showError();
286 | }
287 | }
288 | };
289 | xhr.send();
290 |
291 | if(!isIE){
292 | createAnimation();
293 | }
294 | }
295 | },
296 |
297 | // creates div and buttons for onscreen video controls
298 | createControls: function() {
299 |
300 | var muteControl = this.options.muted ? 'fa-volume-off' : 'fa-volume-up';
301 | var playPauseControl = this.options.autoplay ? 'fa-pause' : 'fa-play';
302 |
303 | var controlsHTML = ' \
304 | \
305 |
\
308 |
\
309 |
\
310 |
\
319 |
\
320 |
\
321 |
\
322 |
\
323 | ';
324 |
325 | $(this.element).append(controlsHTML, true);
326 | $(this.element).append('00:00
', true);
327 |
328 | // hide controls if option is set
329 | if(this.options.hideControls) {
330 | $(this.element).find('.controls').hide();
331 | }
332 |
333 | // wire up controller events to dom elements
334 | this.attachControlEvents();
335 | },
336 |
337 | attachControlEvents: function() {
338 |
339 | // create a self var to pass to our controller functions
340 | var self = this;
341 |
342 | this.element.addEventListener( 'mousemove', this.onMouseMove.bind(this), false );
343 | this.element.addEventListener( 'touchmove', this.onMouseMove.bind(this), false );
344 | this.element.addEventListener( 'mousewheel', this.onMouseWheel.bind(this), false );
345 | this.element.addEventListener( 'DOMMouseScroll', this.onMouseWheel.bind(this), false );
346 | this.element.addEventListener( 'mousedown', this.onMouseDown.bind(this), false);
347 | this.element.addEventListener( 'touchstart', this.onMouseDown.bind(this), false);
348 | this.element.addEventListener( 'mouseup', this.onMouseUp.bind(this), false);
349 | this.element.addEventListener( 'touchend', this.onMouseUp.bind(this), false);
350 |
351 | if(this.options.keyboardControls){
352 | this.element.addEventListener('keydown',this.onKeyDown.bind(this), false);
353 | this.element.addEventListener('keyup',this.onKeyUp.bind(this), false);
354 | // Used custom press event because for the arrow buttons is not throws the 'keypress' event
355 | this.element.addEventListener('keyArrowPress',this.onKeyArrowPress.bind(this), false);
356 | this.element.addEventListener('click',function () {
357 | $(self.element).focus();
358 | },false);
359 | }
360 |
361 | $(self.element).find('.controlsWrapper > .valiant-progress-bar')[0].addEventListener("click", this.onProgressClick.bind(this), false);
362 | $(self.element).find('.controlsWrapper > .valiant-progress-bar')[0].addEventListener("mousemove", this.onProgressMouseMove.bind(this), false);
363 | $(self.element).find('.controlsWrapper > .valiant-progress-bar')[0].addEventListener("mouseout", this.onProgressMouseOut.bind(this), false);
364 |
365 | $(document).on('webkitfullscreenchange mozfullscreenchange fullscreenchange MSFullscreenChange',this.fullscreen.bind(this));
366 |
367 | $(window).resize(function() {
368 | self.resizeGL($(self.element).width(), $(self.element).height());
369 | });
370 |
371 | // Player Controls
372 | $(this.element).find('.playButton').click(function(e) {
373 | e.preventDefault();
374 | if($(this).hasClass('fa-pause')) {
375 | $(this).removeClass('fa-pause').addClass('fa-play');
376 | self.pause();
377 | } else {
378 | $(this).removeClass('fa-play').addClass('fa-pause');
379 | self.play();
380 | }
381 | });
382 |
383 | $(this.element).find(".fullscreenButton").click(function(e) {
384 | e.preventDefault();
385 | var elem = $(self.element)[0];
386 | if($(this).hasClass('fa-expand')) {
387 | if (elem.requestFullscreen) {
388 | elem.requestFullscreen();
389 | } else if (elem.msRequestFullscreen) {
390 | elem.msRequestFullscreen();
391 | } else if (elem.mozRequestFullScreen) {
392 | elem.mozRequestFullScreen();
393 | } else if (elem.webkitRequestFullscreen) {
394 | elem.webkitRequestFullscreen();
395 | }
396 | } else {
397 | if (elem.requestFullscreen) {
398 | document.exitFullscreen();
399 | } else if (elem.msRequestFullscreen) {
400 | document.msExitFullscreen();
401 | } else if (elem.mozRequestFullScreen) {
402 | document.mozCancelFullScreen();
403 | } else if (elem.webkitRequestFullscreen) {
404 | document.webkitExitFullscreen();
405 | }
406 | }
407 | });
408 |
409 | $(this.element).find(".muteButton").click(function(e) {
410 | e.preventDefault();
411 | if($(this).hasClass('fa-volume-off')) {
412 | $(this).removeClass('fa-volume-off').addClass('fa-volume-up');
413 | self._video.muted = false;
414 | } else {
415 | $(this).removeClass('fa-volume-up').addClass('fa-volume-off');
416 | self._video.muted = true;
417 | }
418 | });
419 |
420 | $(this.element).find('.controlsWrapper .volumeControl')
421 | .mousedown(this.onVolumeMouseDown.bind(this))
422 | .mouseup(this.onVolumeMouseUp.bind(this))
423 | .mouseleave(this.onVolumeMouseUp.bind(this))
424 | .mousemove(this.onVolumeMouseMove.bind(this));
425 |
426 | $(this._video).on('volumechange',this.onVolumeChange.bind(this));
427 | },
428 |
429 | onMouseMove: function(event) {
430 | this._onPointerDownPointerX = event.clientX;
431 | this._onPointerDownPointerY = -event.clientY;
432 |
433 | this.relativeX = event.pageX - $(this.element).find('canvas').offset().left;
434 |
435 | this._onPointerDownLon = this._lon;
436 | this._onPointerDownLat = this._lat;
437 |
438 | var x, y;
439 |
440 | if(this.options.clickAndDrag) {
441 | if(this._mouseDown) {
442 | x = event.pageX - this._dragStart.x;
443 | y = event.pageY - this._dragStart.y;
444 | this._dragStart.x = event.pageX;
445 | this._dragStart.y = event.pageY;
446 | this._lon += x;
447 | this._lat -= y;
448 | }
449 | } else {
450 | x = event.pageX - $(this.element).find('canvas').offset().left;
451 | y = event.pageY - $(this.element).find('canvas').offset().top;
452 | this._lon = ( x / $(this.element).find('canvas').width() ) * 430 - 225;
453 | this._lat = ( y / $(this.element).find('canvas').height() ) * -180 + 90;
454 | }
455 | },
456 |
457 | onMouseWheel: function(event) {
458 |
459 | var wheelSpeed = -0.01;
460 |
461 | // WebKit
462 | if ( event.wheelDeltaY ) {
463 | this._fov -= event.wheelDeltaY * wheelSpeed;
464 | // Opera / Explorer 9
465 | } else if ( event.wheelDelta ) {
466 | this._fov -= event.wheelDelta * wheelSpeed;
467 | // Firefox
468 | } else if ( event.detail ) {
469 | this._fov += event.detail * 1.0;
470 | }
471 |
472 | if(this._fov < this.options.fovMin) {
473 | this._fov = this.options.fovMin;
474 | } else if(this._fov > this.options.fovMax) {
475 | this._fov = this.options.fovMax;
476 | }
477 |
478 | this._camera.setLens(this._fov);
479 | event.preventDefault();
480 | },
481 |
482 | onMouseDown: function(event) {
483 | this._mouseDown = true;
484 | this._dragStart.x = event.pageX;
485 | this._dragStart.y = event.pageY;
486 | },
487 |
488 | onProgressClick: function(event) {
489 | if(this._isVideo && this._video.readyState === this._video.HAVE_ENOUGH_DATA) {
490 | var percent = this.relativeX / $(this.element).find('canvas').width() * 100;
491 | $(this.element).find('.controlsWrapper > .valiant-progress-bar')[0].children[0].setAttribute("style", "width:" + percent + "%;");
492 | $(this.element).find('.controlsWrapper > .valiant-progress-bar')[0].children[1].setAttribute("style", "width:" + (100 - percent) + "%;");
493 | this._video.currentTime = this._video.duration * percent / 100;
494 | }
495 | },
496 |
497 | onProgressMouseMove: function(event){
498 | var percent = this.relativeX / $(this.element).find('canvas').width() * 100;
499 | if(percent){
500 | var tooltip = $(this.element).find('.timeTooltip');
501 | var tooltipLeft = (this.relativeX - (tooltip.width()/2));
502 | tooltipLeft = tooltipLeft <0? 0:Math.min(tooltipLeft,$(this.element).find('canvas').width() - tooltip.outerWidth());
503 | tooltip.css({ left: tooltipLeft + 'px' });
504 | tooltip.show();
505 | var time = (percent / 100) * this._video.duration;
506 | var timeMin = Math.floor(time / 60);
507 | var timeSec = Math.floor(time - (timeMin * 60));
508 | tooltip.html(timeMin + ':' + (timeSec < 10 ? '0' + timeSec : timeSec));
509 | }
510 | },
511 |
512 | onProgressMouseOut: function (event) {
513 | $(this.element).find('.timeTooltip').hide();
514 | },
515 |
516 | onMouseUp: function(event) {
517 | this._mouseDown = false;
518 | },
519 |
520 | onKeyDown: function(event) {
521 | var keyCode = event.keyCode;
522 | if (keyCode >= 37 && keyCode <= 40) {
523 | event.preventDefault();
524 | this._keydown = true;
525 | var pressEvent = document.createEvent('CustomEvent');
526 | pressEvent.initCustomEvent("keyArrowPress",true,true,{'keyCode':keyCode});
527 | this.element.dispatchEvent(pressEvent);
528 | }
529 | },
530 |
531 | onKeyUp: function (event) {
532 | var keyCode = event.keyCode;
533 | if (keyCode >= 37 && keyCode <= 40) {
534 | event.preventDefault();
535 | this._keydown = false;
536 | }
537 | },
538 |
539 | onKeyArrowPress: function (event) {
540 | if (this._keydown) {
541 | var keyCode = event.detail? event.detail.keyCode:null;
542 | var offset = 3;
543 | var pressDelay = 50;
544 | var element = this.element;
545 | event.preventDefault();
546 | switch (keyCode) {
547 | //Arrow left
548 | case 37: this._lon -= offset;
549 | break;
550 | //Arrow right
551 | case 39: this._lon += offset;
552 | break;
553 | //Arrow up
554 | case 38: this._lat += offset;
555 | break;
556 | //Arrow down
557 | case 40: this._lat -= offset;
558 | break;
559 | }
560 | setTimeout(function () {
561 | var pressEvent = document.createEvent('CustomEvent');
562 | pressEvent.initCustomEvent("keyArrowPress",true,true,{'keyCode':keyCode});
563 | element.dispatchEvent(pressEvent);
564 | },
565 | pressDelay);
566 | }
567 | },
568 |
569 | onVolumeMouseDown: function(event){
570 | event.preventDefault();
571 | this._volumeMouseDown = true;
572 | this.onVolumeMouseMove(event);
573 | },
574 |
575 | onVolumeMouseUp: function(event){
576 | event.preventDefault();
577 | this._volumeMouseDown = false;
578 | },
579 |
580 | onVolumeMouseMove: function(event){
581 | event.preventDefault();
582 | if(this._volumeMouseDown){
583 | var volumeControl = $(this.element).find('.controlsWrapper .volumeControl');
584 | var percent = (this.relativeX - volumeControl.offset().left + (volumeControl.find('.volumeBar > .volumeCursor').width()/2)) / volumeControl.width() * 100;
585 | if(percent>=0 && percent<=100){
586 | this._video.volume = percent/100;
587 | }
588 | }
589 | },
590 |
591 | onVolumeChange: function(event){
592 | //change volume cursor value
593 | var percent = this._video.muted==true && !this._volumeMouseDown? 0:(this._video.volume * 100);
594 | $(this.element).find('.controlsWrapper .volumeControl > .volumeBar').css({width: percent+"%"});
595 |
596 | //change mute button
597 | var muteButton = $(this.element).find(".muteButton");
598 | if((percent>0 && muteButton.hasClass('fa-volume-off')) || (percent==0 && muteButton.hasClass('fa-volume-up'))){
599 | muteButton.click();
600 | }
601 | },
602 |
603 | animate: function() {
604 | // set our animate function to fire next time a frame is ready
605 | this._requestAnimationId = requestAnimationFrame( this.animate.bind(this) );
606 |
607 | if( this._isVideo ) {
608 | if ( this._video.readyState === this._video.HAVE_ENOUGH_DATA) {
609 | if(this._videocanvas) {
610 | this._videocanvas.getContext('2d').drawImage(this._video, 0, 0, this._videocanvas.width, this._videocanvas.height);
611 | }
612 | if(typeof(this._texture) !== "undefined" ) {
613 | var ct = new Date().getTime();
614 | if(ct - this._time >= 30) {
615 | this._texture.needsUpdate = true;
616 | this._time = ct;
617 | }
618 | }
619 | }
620 | }
621 |
622 | this.render();
623 | },
624 |
625 | render: function() {
626 | this._lat = Math.max( - 85, Math.min( 85, this._lat ) );
627 | this._phi = ( 90 - this._lat ) * Math.PI / 180;
628 | this._theta = this._lon * Math.PI / 180;
629 |
630 | var cx = 500 * Math.sin( this._phi ) * Math.cos( this._theta );
631 | var cy = 500 * Math.cos( this._phi );
632 | var cz = 500 * Math.sin( this._phi ) * Math.sin( this._theta );
633 |
634 | this._camera.lookAt(new THREE.Vector3(cx, cy, cz));
635 |
636 | // distortion
637 | if(this.options.flatProjection) {
638 | this._camera.position.x = 0;
639 | this._camera.position.y = 0;
640 | this._camera.position.z = 0;
641 | } else {
642 | this._camera.position.x = - cx;
643 | this._camera.position.y = - cy;
644 | this._camera.position.z = - cz;
645 | }
646 |
647 | this._renderer.clear();
648 | this._renderer.render( this._scene, this._camera );
649 | },
650 |
651 | // Video specific functions, exposed to controller
652 | play: function() {
653 | //code to play media
654 | this._video.play();
655 | },
656 |
657 | pause: function() {
658 | //code to stop media
659 | this._video.pause();
660 | },
661 |
662 | loadVideo: function(videoFile) {
663 | this._video.src = videoFile;
664 | },
665 | unloadVideo: function() {
666 | // overkill unloading to avoid dreaded video 'pending' bug in Chrome. See https://code.google.com/p/chromium/issues/detail?id=234779
667 | this.pause();
668 | this._video.src = '';
669 | this._video.removeAttribute('src');
670 | },
671 | loadPhoto: function(photoFile) {
672 | this._texture = THREE.ImageUtils.loadTexture( photoFile );
673 | },
674 |
675 | fullscreen: function() {
676 | if($(this.element).find('a.fa-expand').length > 0) {
677 | this.resizeGL(screen.width, screen.height);
678 |
679 | $(this.element).addClass('fullscreen');
680 | $(this.element).find('a.fa-expand').removeClass('fa-expand').addClass('fa-compress');
681 |
682 | this._isFullscreen = true;
683 | } else {
684 | this.resizeGL(this._originalWidth, this._originalHeight);
685 |
686 | $(this.element).removeClass('fullscreen');
687 | $(this.element).find('a.fa-compress').removeClass('fa-compress').addClass('fa-expand');
688 |
689 | this._isFullscreen = false;
690 | }
691 | },
692 |
693 | resizeGL: function(w, h) {
694 | this._renderer.setSize(w, h);
695 | this._camera.aspect = w / h;
696 | this._camera.updateProjectionMatrix();
697 | },
698 |
699 | showWaiting: function () {
700 | var loading = $(this.element).find('.loading');
701 | loading.find('.waiting-icon').show();
702 | loading.find('.error-icon').hide();
703 | loading.show();
704 | },
705 |
706 | hideWaiting: function () {
707 | $(this.element).find('.loading').hide();
708 | },
709 |
710 | showError: function () {
711 | var loading = $(this.element).find('.loading');
712 | loading.find('.waiting-icon').hide();
713 | loading.find('.error-icon').show();
714 | loading.show();
715 | },
716 |
717 | destroy: function() {
718 | window.cancelAnimationFrame(this._requestAnimationId);
719 | this._requestAnimationId = '';
720 | this._texture.dispose();
721 | this._scene.remove(this._mesh);
722 | if(this._isVideo) {
723 | this.unloadVideo();
724 | }
725 | $(this._renderer.domElement).remove();
726 | }
727 | };
728 |
729 | $.fn[pluginName] = function ( options ) {
730 | return this.each(function () {
731 | if(typeof options === 'object' || !options) {
732 | // A really lightweight plugin wrapper around the constructor,
733 | // preventing against multiple instantiations
734 | this.plugin = new Plugin(this, options);
735 | if (!$.data(this, "plugin_" + pluginName)) {
736 | $.data(this, "plugin_" + pluginName, this.plugin);
737 | }
738 | } else if(this.plugin[options]) {
739 | // Allows plugin methods to be called
740 | return this.plugin[options].apply(this.plugin, Array.prototype.slice.call(arguments, 1))
741 | }
742 | });
743 | };
744 |
745 | })( jQuery, THREE, Detector, window, document );
746 |
--------------------------------------------------------------------------------