├── banners
├── _banner-template
│ ├── assets
│ │ ├── css
│ │ │ ├── fonts
│ │ │ │ └── .keep
│ │ │ └── source.css
│ │ ├── img
│ │ │ ├── keyframes
│ │ │ │ └── .keep
│ │ │ └── replayBtn
│ │ │ │ └── replay.svg
│ │ └── js
│ │ │ └── script.js
│ └── index.html
└── _banner-support-files
│ ├── ad-platform
│ ├── EBLoader.js
│ ├── sizmek.md
│ └── doubleclick.md
│ └── controls
│ ├── lib
│ ├── create-stylesheet.min.js
│ └── dragdealer.min.js
│ ├── banner-controls.css
│ ├── banner-controls.js
│ └── _banners.js
├── gulpfile.js
├── tasks
│ ├── default.js
│ ├── preflight
│ │ ├── preflight-checklist.js
│ │ ├── directory-check.js
│ │ ├── zipfile-size-limit.js
│ │ ├── package-json.js
│ │ ├── directory-watch.js
│ │ ├── ad-platform.js
│ │ └── banner-fallback-image.js
│ ├── deploy.js
│ ├── _browserSync.js
│ ├── review.js
│ ├── helper
│ │ ├── clean.js
│ │ ├── copy.js
│ │ ├── ad-platform.js
│ │ ├── directory-update.js
│ │ ├── review-template.js
│ │ └── zipfiles.js
│ ├── watch.js
│ └── _modify-assets.js
├── index.js
├── config.js
└── utils.js
├── package.json
├── .gitignore
└── README.md
/banners/_banner-template/assets/css/fonts/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/banners/_banner-template/assets/img/keyframes/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/gulpfile.js/tasks/default.js:
--------------------------------------------------------------------------------
1 | /* TASK: Default -- Will only show `help` message; list all available tasks
2 | ------------------------------------------------------------------------------------------------- */
3 | module.exports = function(gulp, plugin, config, utils) {
4 | gulp.task('default', function(done) {
5 | plugin.sequence('help', done);
6 | });
7 | };
8 |
--------------------------------------------------------------------------------
/banners/_banner-support-files/ad-platform/EBLoader.js:
--------------------------------------------------------------------------------
1 | function loadServingSysScript(relativeUrl) {
2 | document.write("
7 | ```
8 |
9 | 2) Enable ad-platform specific init script.
10 | ```script-init
11 | function initEB() {
12 | if (!EB.isInitialized()) {
13 | EB.addEventListener(EBG.EventName.EB_INITIALIZED, startAd);
14 | }
15 | else {
16 | startAd();
17 | }
18 | }
19 |
20 | function startAd() {
21 | document.getElementById('clickthrough-button').addEventListener('click', clickthru, false);
22 | timeline.init();
23 | }
24 |
25 | var clickthru = function() {
26 | EB.clickthrough();
27 | };
28 |
29 | window.addEventListener('load', initEB);
30 | ```
31 |
--------------------------------------------------------------------------------
/gulpfile.js/tasks/_browserSync.js:
--------------------------------------------------------------------------------
1 | /* TASK: Update browser(s) when files change
2 | ------------------------------------------------------------------------------------------------- */
3 | module.exports = function(gulp, plugin, config, utils) {
4 | var browserSyncRewriteRules = [{
5 | match: //ig,
6 | fn: function(match) {
7 | return '';
8 | }
9 | }];
10 |
11 | gulp.task('browserSync', false, ['directory-update'], function() {
12 | plugin.browserSync({
13 | server: { baseDir: config.watchFolder },
14 | open: true,
15 | notify: false,
16 | rewriteRules: (plugin.flags.controls)? browserSyncRewriteRules : []
17 | });
18 | });
19 | };
20 |
--------------------------------------------------------------------------------
/gulpfile.js/tasks/review.js:
--------------------------------------------------------------------------------
1 | /* TASK: Review -- clean up folders/files, build review page
2 | ------------------------------------------------------------------------------------------------- */
3 | module.exports = function(gulp, plugin, config, utils) {
4 | gulp.task('review', 'build review page from banner directories', ['preflight:checklist', 'clean'], function(done) {
5 | config.watchFolder = config.folder.review;
6 |
7 | var queue = ['copy'];
8 | if (config.injectAdPlatform) {
9 | queue.push('inject:ad-platform');
10 | queue.push('zip:banners');
11 | }
12 |
13 | queue.push('review-template');
14 |
15 | if (plugin.flags.preview) {
16 | queue.push('browserSync');
17 | }
18 | queue.push(done);
19 |
20 | plugin.sequence.apply(this, queue);
21 | }, {
22 | options: {
23 | 'preview': 'open review page in browser'
24 | }
25 | });
26 | };
27 |
--------------------------------------------------------------------------------
/banners/_banner-support-files/ad-platform/doubleclick.md:
--------------------------------------------------------------------------------
1 | ## DoubleClick
2 |
3 |
4 | 1) Add script library from CDN to `index.html`.
5 | ```script-lib
6 |
7 | ```
8 |
9 | 2) Enable ad-platform specific init script.
10 | ```script-init
11 | // Initialize Enabler
12 | if (Enabler.isInitialized()) {
13 | init();
14 | }
15 | else {
16 | Enabler.addEventListener(studio.events.StudioEvent.INIT, init);
17 | }
18 |
19 | function init() {
20 | if (Enabler.isPageLoaded()) {
21 | startAd();
22 | }
23 | else {
24 | Enabler.addEventListener(studio.events.StudioEvent.PAGE_LOADED, startAd);
25 | }
26 | }
27 |
28 | function startAd() {
29 | document.getElementById('clickthrough-button').addEventListener('click', bgExitHandler, false);
30 | timeline.init();
31 | }
32 |
33 | var bgExitHandler = function() {
34 | Enabler.exit('HTML5_Background_Clickthrough');
35 | };
36 | ```
37 |
--------------------------------------------------------------------------------
/banners/_banner-template/assets/img/replayBtn/replay.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
17 |
--------------------------------------------------------------------------------
/gulpfile.js/tasks/helper/clean.js:
--------------------------------------------------------------------------------
1 | /* TASK:
2 | ------------------------------------------------------------------------------------------------- */
3 | module.exports = function(gulp, plugin, config, utils) {
4 | gulp.task('clean', false, function(done) {
5 | // [delete any hidden files, delete main folder]
6 | var reviewFolder = [config.folder.review + '/**/*.*', config.folder.review];
7 | var deployFolder = [config.folder.deploy + '/**/*.*', config.folder.deploy];
8 | plugin.del.sync([].concat(reviewFolder, deployFolder), {force: true});
9 | done();
10 | });
11 |
12 | gulp.task('clean:review-folder', false, function(done) {
13 | done();
14 | });
15 |
16 | gulp.task('clean:deploy-folder', false, ['move:deploy-files'], function(done) {
17 | plugin.del.sync([
18 | plugin.path.join(config.folder.deploy, 'banners'),
19 | plugin.path.join(config.folder.deploy, config.defaults.zipFolder)
20 | ], {force: true});
21 | done();
22 | });
23 |
24 | gulp.task('move:deploy-files', false, function(done) {
25 | var zipFiles = plugin.path.join(config.watchFolder, config.defaults.zipFolder, '*.zip');
26 | return gulp.src(zipFiles).pipe(gulp.dest(config.folder.deploy));
27 | });
28 | };
29 |
--------------------------------------------------------------------------------
/gulpfile.js/tasks/watch.js:
--------------------------------------------------------------------------------
1 | /* TASK: Review -- clean up folders/files, build review page
2 | ------------------------------------------------------------------------------------------------- */
3 | module.exports = function(gulp, plugin, config, utils) {
4 | gulp.task('watch', 'monitor files for changes', ['preflight:directory-watch', 'directory-update', 'styles', 'browserSync'], function(done) {
5 | gulp.watch([config.watchFolder + config.paths.html]).on('change', function() {
6 | plugin.sequence('html', plugin.browserSync.reload);
7 | });
8 | gulp.watch([config.watchFolder + config.paths.img]).on('change', function() {
9 | plugin.sequence('assets', plugin.browserSync.reload);
10 | });
11 | gulp.watch(config.watchFolder + config.paths.css.source).on('change', function() {
12 | plugin.sequence('styles', plugin.browserSync.reload);
13 | });
14 | gulp.watch(config.watchFolder + config.paths.js).on('change', function() {
15 | plugin.sequence('scripts', plugin.browserSync.reload);
16 | });
17 | }, {
18 | options: {
19 | 'folder': 'active directory to monitor (e.g. --folder 300x250)',
20 | 'controls': 'show banner controls; enables scrubbing through timeline'
21 | }
22 | });
23 | };
24 |
--------------------------------------------------------------------------------
/gulpfile.js/tasks/preflight/directory-check.js:
--------------------------------------------------------------------------------
1 | /* TASK: Check to make sure there are directories with dimensions in label
2 | ------------------------------------------------------------------------------------------------- */
3 | module.exports = function(gulp, plugin, config, utils) {
4 | gulp.task('preflight:directory-check', false, function() {
5 | var colors = plugin.gutil.colors;
6 |
7 | var errorTitle = '';
8 | var errorNote = '';
9 | var errors = [];
10 |
11 | var bannerList = utils.getBanners();
12 | if (!bannerList.length) {
13 | errorTitle = colors.bgRed.white.bold(' Empty Banner Directory ') + '\n\n';
14 | errors.push(colors.red('\u2718') + colors.bold(' Banners') + ': missing\n\n');
15 |
16 | var boilerplateExists = plugin.fs.exists('banners/' + config.defaults.folder);
17 | if (boilerplateExists) {
18 | errors.push('Copy ' + colors.cyan(config.defaults.folder) + ' and/or rename with banner size (e.g. 300x250)\n');
19 | errors.push('Then run `gulp watch --folder 300x250` to build banners\n');
20 | }
21 | }
22 |
23 | if (errors.length) {
24 | var msg = errorTitle + errors.join('') + errorNote + '\n';
25 | console.log(utils.message(msg));
26 | process.exit(1);
27 | }
28 | });
29 | };
30 |
--------------------------------------------------------------------------------
/gulpfile.js/tasks/helper/copy.js:
--------------------------------------------------------------------------------
1 | /* TASK: Copy Banners into Watch Folder
2 | ------------------------------------------------------------------------------------------------- */
3 | module.exports = function(gulp, plugin, config, utils) {
4 | gulp.task('copy', false, function(done) {
5 | // Copy banners into watch folder (only folders with dimensions in label)
6 | var bannerList = utils.getBanners();
7 | var bannerFolder = './' + config.watchFolder + '/banners/';
8 | var zipFolder = './' + config.watchFolder + '/' + config.defaults.zipFolder;
9 |
10 | bannerList.forEach(function(item) {
11 | var banner = bannerFolder + item;
12 | plugin.fs.copy('./banners/' + item, banner);
13 | // remove unnecessary files/folders within the `assets` folder
14 | plugin.fs.remove(banner + '/assets/_banner-support-files');
15 | plugin.fs.remove(banner + '/assets/css/source.css');
16 | plugin.fs.remove(banner + '/assets/img/keyframes');
17 | // remove the fonts folder, if empty
18 | var dirFonts = plugin.fs.inspectTree(banner + '/assets/css/fonts/');
19 | if (!dirFonts.size) { plugin.fs.remove(banner + '/assets/css/fonts/'); }
20 | });
21 |
22 | // if `--platform` flag declares a valid ad platform, duplicate `banners` folder and rename as `zip-folder`
23 | if (config.injectAdPlatform) { plugin.fs.copy(bannerFolder, zipFolder); }
24 |
25 | done();
26 | });
27 | };
28 |
--------------------------------------------------------------------------------
/gulpfile.js/tasks/preflight/zipfile-size-limit.js:
--------------------------------------------------------------------------------
1 | /* TASK: Make sure each banner size has a fallback image
2 | ------------------------------------------------------------------------------------------------- */
3 | module.exports = function(gulp, plugin, config, utils) {
4 | gulp.task('preflight:zipfile-size-limit', false, function() {
5 | var colors = plugin.gutil.colors;
6 | var errors = [];
7 |
8 | var errorTitle = colors.bgRed.white.bold(' IAB Zipfile Size Limit ') + '\n\n';
9 | var errorNote = colors.gray('\nIAB Maximum Filesize limit: ') + colors.white(utils.getReadableFileSize(config.defaults.zipFolderSizeLimit) + '\n');
10 |
11 | var zipFolderPath = plugin.path.join(config.watchFolder, config.defaults.zipFolder);
12 | plugin.fs.inspectTree(zipFolderPath).children.forEach(function(zipFile) {
13 | if (zipFile.type !== 'file') { return; }
14 | if (/(\w+)-all\(\d+\)/.test(zipFile.name)) { return; } // ignore larger collection zip file
15 |
16 | if (zipFile.size > config.defaults.zipFolderSizeLimit) {
17 | errors.push(colors.red('\u2718 ') + colors.bold(zipFile.name) + ': ' + colors.cyan(utils.getReadableFileSize(zipFile.size)) + '\n');
18 | }
19 | });
20 |
21 | if (errors.length) {
22 | var msg = errorTitle + errors.join('') + errorNote + '\n';
23 | console.log(utils.message(msg));
24 | if (config.defaults.zipFolderExceedFlag === 'error') { process.exit(1); }
25 | }
26 | });
27 | };
28 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "client-name",
3 | "title": "{{Project Name}}",
4 | "description": "",
5 | "author": "Brothers & Company",
6 | "version": "0.1.0",
7 | "private": true,
8 | "repository": {
9 | "type": "git",
10 | "url": "gitlab-repository"
11 | },
12 | "scripts": {
13 | "test": "echo \"Error: no test specified\" && exit 1",
14 | "postinstall": "find node_modules/ -name '*.info' -type f -delete"
15 | },
16 | "dependencies": {
17 | "autoprefixer": "^6.7.7",
18 | "cheerio": "^0.22.0",
19 | "del": "^2.2.2",
20 | "fs-jetpack": "^0.13.3",
21 | "gulp": "^3.9.1",
22 | "gulp-cssnano": "^2.1.2",
23 | "gulp-git": "^2.2.0",
24 | "gulp-header": "^1.8.8",
25 | "gulp-htmlmin": "^3.0.0",
26 | "gulp-load-plugins": "^1.5.0",
27 | "gulp-plumber": "^1.1.0",
28 | "gulp-postcss": "^6.4.0",
29 | "gulp-rename": "^1.2.2",
30 | "gulp-tap": "^0.4.2",
31 | "gulp-uglify": "^2.1.2",
32 | "gulp-util": "^3.0.8",
33 | "gulp-zip": "^4.0.0",
34 | "image-size": "^0.5.1",
35 | "merge-stream": "^1.0.1",
36 | "minimist": "^1.2.0",
37 | "postcss-advanced-variables": "^1.2.2",
38 | "postcss-cachebuster": "^0.1.4",
39 | "postcss-calc": "^5.3.1",
40 | "postcss-position-alt": "^0.6.3",
41 | "postcss-sorting": "^2.0.1",
42 | "require-dir": "^0.3.1",
43 | "run-sequence": "^1.2.2"
44 | },
45 | "devDependencies": {
46 | "browser-sync": "^2.4.0",
47 | "gulp-filter": "^5.0.0",
48 | "gulp-help": "^1.6.1",
49 | "gulp-notify": "^3.0.0",
50 | "gulp-size": "^2.1.0"
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/banners/_banner-support-files/controls/lib/create-stylesheet.min.js:
--------------------------------------------------------------------------------
1 | // create-stylesheet 0.2.1
2 | // Andrew Wakeling
3 | // create-stylesheet may be freely distributed under the MIT license.
4 | function insertEmptyStyleBefore(e,t){var l=document.createElement("style");l.setAttribute("type","text/css");var n=document.getElementsByTagName("head")[0];return e?n.insertBefore(l,e):n.appendChild(l),l.styleSheet&&l.styleSheet.disabled?(n.removeChild(l),t("Unable to add any more stylesheets because you have exceeded the maximum allowable stylesheets. See KB262161 for more information.")):void t(null,l)}function setStyleCss(e,t,l){try{return e.styleSheet?e.styleSheet.cssText=t:"textContent"in e?e.textContent=t:e.appendChild(document.createTextNode(t)),l(null)}catch(n){l(n)}}function removeStyleSheet(e){"STYLE"===e.tagName&&e.parentNode&&e.parentNode.removeChild(e)}function createStyleSheet(e,t){!_stylesheet.ignoreKB262161&&document.styleSheets.length>=31&&t("Unable to add any more stylesheets because you have exceeded the maximum allowable stylesheets. See KB262161 for more information."),insertEmptyStyleBefore(e.replace?e.replace.nextSibling:e.insertBefore,function(l,n){l?t(l):setStyleCss(n,e.css||"",function(l){l?(removeStyleSheet(n),t(l)):(e.replace&&removeStyleSheet(e.replace),t(null,n))})})}var _stylesheet={};_stylesheet.ignoreKB262161=!1,_stylesheet={appendStyleSheet:function(e,t){createStyleSheet({css:e},t)},insertStyleSheetBefore:function(e,t,l){createStyleSheet({insertBefore:e,css:t},l)},replaceStyleSheet:function(e,t,l){createStyleSheet({replace:e,css:t},l)},removeStyleSheet:removeStyleSheet};
5 |
--------------------------------------------------------------------------------
/gulpfile.js/tasks/preflight/package-json.js:
--------------------------------------------------------------------------------
1 | /* TASK: Make sure Package.json project meets minimum requirements before proceeding
2 | ------------------------------------------------------------------------------------------------- */
3 | module.exports = function(gulp, plugin, config, utils) {
4 | gulp.task('preflight:package.json', false, function() {
5 | var colors = plugin.gutil.colors;
6 |
7 | var errors = [];
8 | var errorTitle = colors.bgRed.white.bold(' package.json ') + '\n\nRequired Project Information:\n';
9 | var errorNote = '\nProject information will be displayed\non the generated review page.\nView ' + colors.cyan.italic('README.md') + ' for more details\n\n';
10 |
11 | if (config.pkg.name === config.defaults.name || !config.pkg.name.length || !config.pkg.name.match(/\b(\d{2}[-]?[A-Za-z]{3,}[-]?\d{4,})\b/)) {
12 | errors.push(colors.red('\u2718') + colors.bold(' name') + ': required format ' + colors.cyan('YY-aaa-9999') + '\n');
13 | errors.push(' - YY: 2-digit Year\n');
14 | errors.push(' - aaa: 3-digit Client Code\n');
15 | errors.push(' - 9999: 4-digit Job Code\n');
16 | }
17 |
18 | if (config.pkg.title === config.defaults.title || !config.pkg.title.length) {
19 | errors.push(colors.red('\u2718') + colors.bold(' title') + ': missing\n');
20 | }
21 |
22 | if (errors.length) {
23 | var msg = errorTitle + errors.join('') + errorNote;
24 | console.log(utils.message(msg));
25 | process.exit(1);
26 | }
27 | });
28 | };
29 |
--------------------------------------------------------------------------------
/gulpfile.js/index.js:
--------------------------------------------------------------------------------
1 | /* Project Packages
2 | ================================================================================================= */
3 | // Main Gulp Tasks
4 | var gulp = require('gulp-help')(require('gulp'), {'description': '', 'hideEmpty': true, 'hideDepsMessage': true});
5 | var tasks = require('require-dir')('./tasks', { recurse: true });
6 | var config = require('./config');
7 | var utils = require('./utils');
8 |
9 | /* Load all plugins
10 | ------------------------------------------------------------------------------------------------- */
11 | var plugins = require('gulp-load-plugins')({
12 | pattern: ['*', '!require-dir', '!minimist', '!run-sequence'],
13 | replaceString: /\b(gulp|postcss)[\-.]/,
14 | rename: {
15 | 'fs-jetpack': 'fs',
16 | 'gulp-util': 'gutil',
17 | 'merge-stream': 'merge',
18 | 'postcss-position-alt': 'position',
19 | 'postcss-advanced-variables': 'variables'
20 | }
21 | });
22 | // add additional plugin(s) that require parameters
23 | plugins.flags = require('minimist')(process.argv.slice(2));
24 | plugins.sequence = require('run-sequence').use(gulp);
25 | // add a built-in node module
26 | plugins.path = require('path');
27 |
28 | /* Instantiate & pass parameters to all found tasks
29 | ------------------------------------------------------------------------------------------------- */
30 | function iterate(tasks) {
31 | for (var task in tasks) {
32 | if (tasks[task] instanceof Function) {
33 | tasks[task](gulp, plugins, config, utils);
34 | }
35 | else {
36 | if (typeof tasks[task] === 'object') {
37 | iterate(tasks[task]);
38 | }
39 | }
40 | }
41 | }
42 | iterate(tasks);
43 |
--------------------------------------------------------------------------------
/gulpfile.js/tasks/_modify-assets.js:
--------------------------------------------------------------------------------
1 | /* TASK: Modify assets (html, images, styles, scripts) on change
2 | ------------------------------------------------------------------------------------------------- */
3 | module.exports = function(gulp, plugin, config, utils) {
4 | gulp.task('html', false, function(done) {
5 | return gulp
6 | .src(config.watchFolder + config.paths.html)
7 | .pipe(plugin.plumber({ errorHandler: utils.reportError }));
8 | });
9 |
10 | gulp.task('assets', false, function() {
11 | return gulp
12 | .src(config.watchFolder + config.paths.img)
13 | .pipe(plugin.plumber({ errorHandler: utils.reportError }));
14 | });
15 |
16 | gulp.task('styles', false, function() {
17 | var processors = [
18 | plugin.autoprefixer({ browsers: ['> 1%', 'last 3 versions', 'ie >= 9', 'ios >= 7', 'android >= 4.4']}),
19 | plugin.variables(),
20 | plugin.position(),
21 | plugin.calc(),
22 | plugin.sorting({ 'sort-order': 'zen' })
23 | ];
24 |
25 | return gulp
26 | .src(config.watchFolder + config.paths.css.source)
27 | .pipe(plugin.plumber({ errorHandler: utils.reportError }))
28 | .pipe(plugin.postcss(processors))
29 | .pipe(plugin.rename('style.css'))
30 | .pipe(plugin.header(config.defaults.css_header))
31 | .pipe(gulp.dest(config.watchFolder + config.paths.css.destination));
32 | });
33 |
34 | gulp.task('scripts', false, function() {
35 | return gulp
36 | .src(config.watchFolder + config.paths.script)
37 | .pipe(plugin.plumber({ errorHandler: utils.reportError }));
38 | });
39 | };
40 |
--------------------------------------------------------------------------------
/banners/_banner-template/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Ad Banner: {{width}}x{{height}}
9 |
10 |
11 |
12 |
13 |
14 |
40 |
41 |
42 |

43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
--------------------------------------------------------------------------------
/gulpfile.js/tasks/preflight/directory-watch.js:
--------------------------------------------------------------------------------
1 | /* TASK: Make sure folder name has banner dimensions and the flag is set
2 | ------------------------------------------------------------------------------------------------- */
3 | module.exports = function(gulp, plugin, config, utils) {
4 | gulp.task('preflight:directory-watch', false, function() {
5 | var colors = plugin.gutil.colors;
6 | var isDirectory = plugin.fs.exists('banners/' + plugin.flags.folder);
7 |
8 | var errors = [];
9 | var errorTitle = colors.bgRed.white.bold(' watch directory ') + '\n\n';
10 |
11 | // Flag isn't set. If the folder isn't declared, we can't monitor changes.
12 | if (!plugin.flags.folder) {
13 | errors.push(colors.red('\u2718') + colors.bold(' folder flag') + ': missing\n');
14 | errors.push(colors.gray('e.g. `gulp watch --folder 300x250`\n\n'));
15 | }
16 | // Flag is set but the directory is missing. Pick a directory to copy.
17 | else if (!isDirectory) {
18 | errors.push(colors.red('\u2718') + colors.bold(' directory') + ': missing\n');
19 | // Missing directory: copy the starter boilerplate folder
20 | var boilerplateExists = plugin.fs.exists('banners/' + config.defaults.folder);
21 | if (boilerplateExists) {
22 | errors.push('Copy ' + colors.cyan(config.defaults.folder) + ' and/or rename with banner size (e.g. ' + plugin.flags.folder + ')\n\n');
23 | }
24 | // Missing directory: no boilerplate folder to copy, try another folder?
25 | else {
26 | errors.push('Copy another directory and rename with banner size (e.g. ' + plugin.flags.folder + ')\n\n');
27 | }
28 | }
29 |
30 | if (errors.length) {
31 | var msg = errorTitle + errors.join('');
32 | console.log(utils.message(msg));
33 | process.exit(1);
34 | }
35 | });
36 | };
37 |
--------------------------------------------------------------------------------
/gulpfile.js/tasks/preflight/ad-platform.js:
--------------------------------------------------------------------------------
1 | /* TASK: Make sure each banner size has a fallback image
2 | ------------------------------------------------------------------------------------------------- */
3 | module.exports = function(gulp, plugin, config, utils) {
4 | gulp.task('preflight:ad-platform', false, function() {
5 | var colors = plugin.gutil.colors;
6 | var platform = (typeof plugin.flags.platform === 'string')? plugin.flags.platform.toLowerCase() : null;
7 | var availablePlatforms = utils.getAvailablePlatforms();
8 |
9 | var errors = [];
10 | var errorTitle = colors.bgRed.white.bold(' Ad Platform ') + '\n\n';
11 | var errorNote = ' - Available platforms: ' + colors.cyan(utils.formatAvailablePlatforms()) + '\n';
12 |
13 | // --platform flag missing
14 | if (typeof plugin.flags.platform === 'undefined') {
15 | errors.push(colors.red('\u2718 ') + colors.bold('--platform') + ' flag requires a value\n');
16 | }
17 | // --platform flag exists; must require a value
18 | else if (typeof plugin.flags.platform === 'boolean') {
19 | errors.push(colors.red('\u2718 ') + colors.bold('--platform') + ' flag requires a value\n');
20 | }
21 | // ad platform markdown file not found. Show available options
22 | else if (config.watchFolder === config.folder.deploy && platform === null) {
23 | errors.push(colors.red('\u2718 ') + 'Ad Platform required\n');
24 | }
25 | else if (!utils.formatAvailablePlatforms().includes(platform)) {
26 | errors.push(colors.red('\u2718 ') + 'Ad Platform not found: ' + colors.bold(platform) + '\n');
27 | }
28 | // everything is good, proceed.
29 | else {
30 | config.injectAdPlatform = true;
31 | }
32 |
33 | // ad platform optional w/ `review` task
34 | if (utils.getActiveTask(this) === 'review' && (!plugin.flags.platform && !platform)) {
35 | errors = []; // clear any errors
36 | config.injectAdPlatform = false;
37 | }
38 |
39 | if (errors.length) {
40 | var msg = errorTitle + errors.join('') + errorNote + '\n';
41 | console.log(utils.message(msg));
42 | process.exit(1);
43 | }
44 | });
45 | };
46 |
--------------------------------------------------------------------------------
/gulpfile.js/tasks/helper/ad-platform.js:
--------------------------------------------------------------------------------
1 | /* TASK:
2 | ------------------------------------------------------------------------------------------------- */
3 | module.exports = function(gulp, plugin, config, utils) {
4 | gulp.task('inject:ad-platform', false, function(done) {
5 | var platform = (typeof plugin.flags.platform === 'string')? plugin.flags.platform.toLowerCase() : null;
6 | var supportFiles = plugin.fs.cwd('./banners/_banner-support-files/ad-platform/');
7 |
8 | var getAdScript = function(type) {
9 | var markdown = supportFiles.read(plugin.flags.platform + '.md').toString();
10 | var adScriptRegEx = new RegExp('`{3}script-' + type + '([\\s\\S]+?)`{3}\\n', 'g');
11 | var adScriptStr = markdown.slice().match(adScriptRegEx)[0].replace(adScriptRegEx, '$1');
12 | return utils.trim(adScriptStr);
13 | };
14 |
15 | var injectPlatformScript = function() {
16 | return plugin.tap(function(file) {
17 | var filename = plugin.path.basename(file.relative);
18 |
19 | if (filename === 'index.html') {
20 | var adScriptLib = file.contents.toString().replace('', getAdScript('lib'));
21 | file.contents = new Buffer(adScriptLib);
22 | }
23 |
24 | if (filename === 'script.js') {
25 | var adScriptInit = file.contents.toString().replace('timeline.init();', getAdScript('init'));
26 | file.contents = new Buffer(adScriptInit);
27 |
28 | if (platform === 'sizmek') { // move init file into folder
29 | plugin.fs.copy(supportFiles.cwd() + '/EBLoader.js', plugin.path.parse(file.path).dir + '/EBLoader.js', { overwrite: true });
30 | }
31 | }
32 | });
33 | };
34 |
35 | if (config.injectAdPlatform) {
36 | return gulp
37 | .src([config.watchFolder + '/' + config.defaults.zipFolder + '/**/index.html', config.watchFolder + '/' + config.defaults.zipFolder + '/**/script.js'])
38 | .pipe(injectPlatformScript())
39 | .pipe(gulp.dest(config.watchFolder + '/' + config.defaults.zipFolder));
40 | }
41 | else {
42 | done();
43 | }
44 |
45 | });
46 | };
47 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Compiled source #
2 | ###################
3 | *.com
4 | *.class
5 | *.dll
6 | *.exe
7 | *.o
8 | *.so
9 |
10 | # Packages #
11 | ############
12 | # it's better to unpack these files and commit the raw source
13 | # git has its own built in compression methods
14 | *.7z
15 | *.dmg
16 | *.gz
17 | *.iso
18 | *.jar
19 | *.rar
20 | *.tar
21 | *.zip
22 |
23 | # Logs and databases #
24 | ######################
25 | *.log
26 | *.sql
27 | *.sqlite
28 |
29 | # OS generated files #
30 | ######################
31 | .DS_Store
32 | .DS_Store?
33 | ._*
34 | .Spotlight-V100
35 | .Trashes
36 | ehthumbs.db
37 | Thumbs.db
38 | .AppleDouble
39 | .LSOverride
40 |
41 | # Icon must end with two \r
42 | Icon
43 |
44 |
45 | # Drupal
46 | ######################
47 | # Ignore configuration files that may contain sensitive information.
48 | sites/*/*settings*.php
49 |
50 | # Ignore paths that contain generated content.
51 | files/
52 | sites/*/files
53 | sites/*/private
54 |
55 | # Ignore default text files
56 | robots.txt
57 | /CHANGELOG.txt
58 | /COPYRIGHT.txt
59 | /INSTALL*.txt
60 | /LICENSE.txt
61 | /MAINTAINERS.txt
62 | /UPGRADE.txt
63 | /README.txt
64 | sites/all/README.txt
65 | sites/all/modules/README.txt
66 | sites/all/themes/README.txt
67 | sites/*/.tmp
68 | sites/*/.DS_Store
69 |
70 | # Ignore everything but the "sites" folder ( for non core developer )
71 | .htaccess
72 | web.config
73 | authorize.php
74 | cron.php
75 | index.php
76 | install.php
77 | update.php
78 | xmlrpc.php
79 | /includes
80 | /misc
81 | /modules
82 | /profiles
83 | /scripts
84 | /themes
85 |
86 |
87 | # Jekyll
88 | ######################
89 | .jekyll-metadata
90 |
91 | # Logs and databases
92 | ######################
93 | *.log
94 | *.sql
95 | *.sqlite
96 |
97 | # Node
98 | ######################
99 | npm-debug.log*
100 | node_modules
101 | sites/*/node_modules
102 |
103 | # Optional npm cache directory
104 | .npm
105 |
106 | # Composer
107 | ######################
108 | composer.phar
109 | /vendor/
110 |
111 | # Sass
112 | ######################
113 | .sass-cache/
114 | *.css.map
115 |
116 | # Javascript
117 | ######################
118 | *.js.map
119 |
120 | # OSX
121 | ######################
122 | # Files that might appear in the root of a volume
123 | .DocumentRevisions-V100
124 | .fseventsd
125 | .Spotlight-V100
126 | .TemporaryItems
127 | .Trashes
128 | .VolumeIcon.icns
129 |
--------------------------------------------------------------------------------
/gulpfile.js/config.js:
--------------------------------------------------------------------------------
1 | var pkg = require('../package.json');
2 |
3 | /* Setup: Initial values from banner boilerplate
4 | --------------------------------------------------------------------------- */
5 | var defaults = {
6 | name: 'client-name', // YY-aaa-9999 (e.g. 15-car-7155); view README.md for formatting
7 | title: '{{Project Title}}', // Will be used on the review page template
8 | folder: '_banner-template', // starter boilerplate to build banners
9 | zipFolder: 'zip-folder', // name of folder containing zip files,
10 | zipFolderSizeLimit: 200 * 1000, // IAB filesize limit is 200KB; 1000 used for physical disk sizes; see: http://stackoverflow.com/a/8632634
11 | zipFolderExceedFlag: 'error', // `error` or `warn`; stop on `error`, continue on `warn`
12 | css_header: '/* Do not edit this file directly. Update styles in `source.css` only.\n' + Array(75).join('=') + ' */\n\n'
13 | };
14 |
15 | /* Setup: Project Formatting
16 | --------------------------------------------------------------------------- */
17 | var project = {
18 | year: pkg.name.split('-')[0],
19 | clientCode: pkg.name.split('-')[1].toUpperCase(),
20 | jobCode: pkg.name.split('-')[2],
21 | get name() {
22 | return this.year + this.clientCode + this.jobCode;
23 | },
24 | get title() {
25 | return this.clientCode + ' ' + this.jobCode + ' ' + pkg.title;
26 | },
27 | review_template: 'https://github.com/misega/HTML5-Banners-Review-Site'
28 | };
29 |
30 | /* Setup: File paths
31 | --------------------------------------------------------------------------- */
32 | var paths = {
33 | html: '/index.html',
34 | img: '/assets/img/**/*',
35 | css: {
36 | source: '/assets/css/source.css',
37 | destination: '/assets/css/'
38 | },
39 | js: '/assets/js/*.js'
40 | };
41 |
42 | /* Setup: Folders for distribution
43 | --------------------------------------------------------------------------- */
44 | var folder = {
45 | review: '_review',
46 | deploy: '_deploy',
47 | ad_platform: './banners/_banner-support-files/ad-platform/'
48 | };
49 |
50 | /*
51 | --------------------------------------------------------------------------- */
52 | module.exports = {
53 | pkg: pkg,
54 | defaults: defaults,
55 | project: project,
56 | paths: paths,
57 | folder: folder,
58 | watchFolder: '',
59 | injectAdPlatform: false,
60 | sizeRegExp: new RegExp('(\\d{2,}x\\d{2,})', 'g')
61 | };
62 |
--------------------------------------------------------------------------------
/gulpfile.js/tasks/helper/directory-update.js:
--------------------------------------------------------------------------------
1 | /* TASK: Update the index page and style page to show the correct dimensions
2 | --------------------------------------------------------------------------- */
3 | module.exports = function(gulp, plugin, config, utils) {
4 | gulp.task('directory-update', false, function(done) {
5 | if (!plugin.flags.folder) { done(); return; }
6 |
7 | var currentDirectory = plugin.fs.cwd('banners/' + plugin.flags.folder);
8 | config.watchFolder = currentDirectory.cwd();
9 |
10 | // The directory is available, let's update files to match the directory name
11 | var size = utils.getDimensions(plugin.flags.folder);
12 |
13 | // index.html -- set and to dimensions of folder
14 | var adSizeRegExp = new RegExp('content="width=({{width}}|\\d{2,}), height=({{height}}|\\d{2,})"', 'g');
15 | var titleRexExp = new RegExp('(.*)<\/title>', 'g');
16 | var indexContent = currentDirectory.read('index.html').toString();
17 | indexContent = indexContent.replace(adSizeRegExp, 'content="width=' + size.width + ', height=' + size.height + '"');
18 | indexContent = indexContent.replace(titleRexExp, 'Ad Banner: ' + size.width + 'x' + size.height + '');
19 | plugin.fs.write(currentDirectory.cwd() + '/index.html', indexContent);
20 |
21 | // source.css -- set css variable width/height, if it exists
22 | var source_css = config.paths.css.source.slice(1);
23 | if (!plugin.fs.exists(config.watchFolder + '/' + source_css)) { return; }
24 | var styleContent = currentDirectory.read(source_css).toString();
25 | styleContent = styleContent.replace(/\$width:\s?({{width}}|\d{2,}px);/g, '$width: ' + size.width + 'px;');
26 | styleContent = styleContent.replace(/\$height:\s?({{height}}|\d{2,}px);/g, '$height: ' + size.height + 'px;');
27 | plugin.fs.write(config.watchFolder + '/' + source_css, styleContent);
28 |
29 | /* Controls: Add folder, if flag is active; remove, if not
30 | --------------------------------------------------------------------------- */
31 | var controls = '_banner-support-files/controls';
32 | var controlsFolder = config.watchFolder + '/assets/' + controls;
33 | var hasControlsFolder = plugin.fs.exists(controlsFolder);
34 | if (!plugin.flags.controls && hasControlsFolder) {
35 | plugin.fs.remove(config.watchFolder + '/assets/_banner-support-files');
36 | }
37 |
38 | if (plugin.flags.controls && !hasControlsFolder) {
39 | plugin.fs.copy('./banners/' + controls, controlsFolder);
40 | }
41 |
42 | done();
43 | });
44 | };
45 |
--------------------------------------------------------------------------------
/gulpfile.js/tasks/preflight/banner-fallback-image.js:
--------------------------------------------------------------------------------
1 | /* TASK: Make sure each banner size has a fallback image
2 | ------------------------------------------------------------------------------------------------- */
3 | module.exports = function(gulp, plugin, config, utils) {
4 | gulp.task('preflight:banner-fallback-image', false, function() {
5 | var colors = plugin.gutil.colors;
6 | var errors = [];
7 |
8 | var errorTitle = colors.bgRed.white.bold(' Fallback Images ') + '\n\n';
9 | var errorNote = colors.gray('\nFix any issues with the fallback image(s) before proceeding\n');
10 | errorNote += colors.gray('Ensure each image is named') + ' ' + colors.cyan('fallback') + '\n';
11 |
12 | var bannerList = utils.getBanners();
13 | bannerList.forEach(function(banner) {
14 | var folder = 'banners/' + banner;
15 | var folderSize = utils.getDimensions(banner);
16 | var image = plugin.fs.find(folder, { matching: ['fallback.*'] });
17 |
18 | // 1) Find `fallback` image
19 | if (!image.length) { // no `fallback` image found
20 | errors.push(colors.red('\u2718 ') + colors.bold(banner) + ': missing fallback image\n');
21 | }
22 | else if (image.length > 1) { // multiple `fallback` images found
23 | errors.push(colors.red('\u2718 ') + colors.bold(image.length) + ' fallback images found in ' + colors.bold(banner) + ':\n');
24 | image.forEach(function(duplicate) {
25 | errors.push(colors.red('\u21B3 ') + colors.bold(duplicate) + '\n');
26 | });
27 | errors.push(colors.white('Choose one image and delete any others') + '\n\n');
28 | }
29 | else {
30 | var fallback = plugin.path.parse(image[0]);
31 | var dimensions = plugin.imageSize(image[0]);
32 |
33 | // 2) Check for correct mime type of `fallback` image
34 | if (!fallback.ext.match(/jpg|gif|png/)) {
35 | errors.push(colors.red('\u2718 ') + colors.bold(banner) + ': wrong mimetype \u2192 ' + colors.red(fallback.ext.toUpperCase()) + '\n');
36 | errors.push(' - Allowed mime-types: ' + colors.cyan('JPG, GIF, PNG') + '\n');
37 | }
38 |
39 | // 3) Check for correct dimensions
40 | if (folderSize.width !== dimensions.width || folderSize.height !== dimensions.height) {
41 | errors.push(colors.red('\u2718 ') + colors.bold(banner) + ': fallback image size doesn\'t match\n');
42 | errors.push(' - folder size: ' + colors.cyan(folderSize.formatted) + '\n');
43 | errors.push(' - image size: ' + colors.cyan(dimensions.width + 'x' + dimensions.height) + '\n');
44 | }
45 | }
46 | });
47 |
48 | if (errors.length) {
49 | var msg = errorTitle + errors.join('') + errorNote + '\n';
50 | console.log(utils.message(msg));
51 | process.exit(1);
52 | }
53 | });
54 | };
55 |
--------------------------------------------------------------------------------
/banners/_banner-template/assets/css/source.css:
--------------------------------------------------------------------------------
1 | $width: {{width}};
2 | $height: {{height}};
3 |
4 | *, *:before, *:after {
5 | box-sizing: border-box;
6 | }
7 |
8 | /* Base Styles
9 | --------------------------------------------------------------------------- */
10 | #ad {
11 | display: block;
12 | absolute: top left right bottom;
13 | margin: auto;
14 | width: $width;
15 | height: $height;
16 | background: #000;
17 | overflow: hidden;
18 | user-select: none;
19 | }
20 |
21 | .btn-overlay {
22 | display: block;
23 | absolute: top left z-index 50;
24 | width: 100%;
25 | height: 100%;
26 | background: transparent;
27 | border: none;
28 | outline: none;
29 | -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
30 | user-select: none;
31 | cursor: pointer;
32 | }
33 | .btn-overlay:before {
34 | content: '';
35 | absolute: top left right bottom z-index 99;
36 | border: 1px solid #666;
37 | }
38 |
39 | .banner {
40 | position: relative;
41 | height: 100%;
42 | background: #000;
43 | }
44 |
45 | /* Frame Base Style
46 | --------------------------------------------------------------------------- */
47 | .frame {
48 | absolute: top left right bottom;
49 |
50 | font-family: 'Helvetica Neue', Helvetica, sans-serif;
51 | color: #fff;
52 | text-transform: uppercase;
53 | letter-spacing: -1px;
54 | }
55 |
56 | /* Frame 1
57 | --------------------------------------------------------------------------- */
58 | .frame1-text {
59 | absolute: top 10px left 10px;
60 | }
61 |
62 | /* Frame 2
63 | --------------------------------------------------------------------------- */
64 | .frame2-text {
65 | absolute: top 10px left 10px;
66 | }
67 |
68 | /* Frame 3
69 | --------------------------------------------------------------------------- */
70 | .frame3-text {
71 | absolute: top 10px left 10px;
72 | }
73 |
74 | /* Frame 4
75 | --------------------------------------------------------------------------- */
76 | .frame4-text {
77 | absolute: top 10px left 10px;
78 | }
79 |
80 | /* Utility Styles
81 | --------------------------------------------------------------------------- */
82 | .u-hide {
83 | display: none;
84 | }
85 |
86 | /* Replay Btn
87 | --------------------------------------------------------------------------- */
88 | .ad-overlay {
89 | display: block;
90 | absolute: top left right bottom;
91 | margin: auto;
92 | width: $width;
93 | height: $height;
94 | overflow: hidden;
95 | user-select: none;
96 | }
97 |
98 | .replay-btn {
99 | absolute: top 10px right 10px;
100 | height: 16px;
101 | width: 16px;
102 | z-index: 55;
103 | cursor: pointer;
104 | }
105 |
106 | .replay-btn:hover img {
107 | -webkit-animation: rotate-animation 1s infinite linear;
108 | -moz-animation: rotate-animation 1s infinite linear;
109 | -o-animation: rotate-animation 1s infinite linear;
110 | animation: rotate-animation 1s infinite linear;
111 | }
112 |
113 | @keyframes rotate-animation {
114 | 100% {
115 | transform: rotate(360deg);
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/banners/_banner-support-files/controls/banner-controls.css:
--------------------------------------------------------------------------------
1 | .banner-controls {
2 | position: fixed;
3 | left: 0;
4 | right: 0;
5 | bottom: 0;
6 | margin: 0;
7 | padding: 0;
8 | list-style: none;
9 | height: 50px;
10 | font-family: Calibri, Candara, Segoe, "Segoe UI", Optima, Arial, sans-serif;
11 | white-space: nowrap;
12 | background: #111;
13 | cursor: default;
14 | opacity: 0.9;
15 | }
16 |
17 | .in-review .banner-controls {
18 | position: absolute;
19 | }
20 |
21 | .play-pause {
22 | float: left;
23 | width: 50px;
24 | height: 50px;
25 | }
26 |
27 | .btn-play-pause {
28 | display: block;
29 | padding: 0;
30 | width: 50px;
31 | height: 50px;
32 | background: #111;
33 | border: none;
34 | cursor: pointer;
35 | outline-style: none;
36 | box-shadow: none;
37 | }
38 |
39 | .icon-play-pause {
40 | display: inline-block;
41 | height: 0;
42 | margin: 0;
43 | border: 10px solid transparent;
44 | border-right: none;
45 | border-left: 20px solid #fff;
46 | transform: translate(2px, 2px);
47 | transition: all 100ms ease-in-out;
48 | }
49 |
50 | .icon-play-pause::before {
51 | content: '';
52 | width: 8px;
53 | height: 20px;
54 | background: #111;
55 | position: absolute;
56 | left: -14px;
57 | top: -10px;
58 | transform: scaleX(0);
59 | transition: all 100ms ease-in-out;
60 | }
61 |
62 | .play-pause.pause .icon-play-pause {
63 | height: 20px;
64 | border-top-width: 0px;
65 | border-bottom-width: 0px;
66 | }
67 |
68 | .play-pause.pause .icon-play-pause::before {
69 | top: 0;
70 | transform: scaleX(1.001);
71 | }
72 |
73 | .duration {
74 | float: left;
75 | width: 105px;
76 | height: 50px;
77 | font-size: 14px;
78 | line-height: 50px;
79 | color: #888;
80 | text-align: center;
81 | transition: color 250ms ease-out;
82 | }
83 |
84 | .duration span:first-child {
85 | color: #eee;
86 | }
87 |
88 | .duration:hover, .duration:hover span:first-child {
89 | color: #fff;
90 | }
91 |
92 | .progress {
93 | position: absolute;
94 | top: 0;
95 | right: 24px;
96 | bottom: 0;
97 | left: 170px;
98 | }
99 |
100 | .progress-bar {
101 | position: absolute;
102 | top: 12px;
103 | left: 0;
104 | right: 0;
105 | bottom: 12px;
106 | cursor: pointer;
107 | }
108 |
109 | .progress-bar::before {
110 | content: '';
111 | position: absolute;
112 | top: 10px;
113 | left: 0;
114 | right: 0;
115 | bottom: 10px;
116 | background: #000;
117 | }
118 |
119 | .progress-bar .total {
120 | position: absolute;
121 | top: 0;
122 | left: 0;
123 | right: 0;
124 | bottom: 0;
125 | }
126 |
127 | .progress-bar .total {
128 | top: 10px;
129 | bottom: 10px;
130 | width: 0;
131 | background: #0d3c55;
132 | }
133 |
134 | .progress-bar .thumb {
135 | position: absolute;
136 | top: 0;
137 | left: 0;
138 | bottom: 0;
139 | width: 25px;
140 | border-radius: 50%;
141 | background-color: #fff;
142 | box-shadow: 0 0 0 4px #111;
143 | transition: opacity 350ms ease-out;
144 | }
145 |
146 | .banner-controls:hover .progress-bar .thumb {
147 | opacity: 1;
148 | }
149 |
--------------------------------------------------------------------------------
/gulpfile.js/utils.js:
--------------------------------------------------------------------------------
1 | /* Setup
2 | --------------------------------------------------------------------------- */
3 | var fs = require('fs-jetpack');
4 | var path = require('path');
5 | var gutil = require('gulp-util');
6 | var config = require('./config.js');
7 |
8 | /* Global Utilities
9 | --------------------------------------------------------------------------- */
10 | var utils = {
11 | divider: function() {
12 | var cols = process.stdout.columns;
13 | return Array.apply(null, { length: cols }).join('-').slice(0, cols) + '\n';
14 | },
15 | message: function(msg) {
16 | return utils.divider() + msg + utils.divider();
17 | },
18 | trim: function(str) {
19 | return str.replace(/^\s+/, '').replace(/\s+$/, '');
20 | },
21 | getActiveTask: function(task) {
22 | return task.seq.slice(-1)[0];
23 | },
24 | getFolders: function(dir) {
25 | var folders = [];
26 | fs.inspectTree(dir).children.forEach(function(folder) {
27 | if (folder.type === 'dir') { folders.push(folder.name); }
28 | });
29 | return folders;
30 | },
31 | getReadableFileSize: function(size) {
32 | if (!size) { return 0; }
33 | var k = 1000; // 1000 used for physical disk sizes; see: http://stackoverflow.com/a/8632634
34 | var i = Math.floor(Math.log(size) / Math.log(k));
35 | return (size / Math.pow(k, i)).toFixed(2) * 1 + ' ' + ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'][i];
36 | },
37 | getFileSize: function(filePath) {
38 | var size = (fs.exists(filePath) && fs.inspectTree(filePath).size) || 0;
39 | return utils.getReadableFileSize(size);
40 | },
41 | getBanners: function() {
42 | var bannerList = [];
43 | // return only folders with dimensions in label
44 | var banners = utils.getFolders('banners');
45 | banners.forEach(function(item) {
46 | if (item.match(config.sizeRegExp)) {
47 | bannerList.push(item);
48 | }
49 | });
50 |
51 | return bannerList;
52 | },
53 | walkDirectory: function(dir, filelist) {
54 | var fso = fso || require('fs');
55 | var files = fso.readdirSync(dir);
56 | filelist = filelist || [];
57 |
58 | files.forEach(function(file) {
59 | if (fso.statSync(dir + '/' + file).isDirectory()) {
60 | filelist = utils.walkDirectory(dir + '/' + file, filelist);
61 | }
62 | else {
63 | if (!/(\.DS_Store|\.keep)/.test(file)) {
64 | filelist.push(fs.inspect(dir + '/' + file, {times: true}));
65 | }
66 | }
67 | });
68 |
69 | return filelist;
70 | },
71 | getDimensions: function(item) {
72 | var dimensions = item.match(config.sizeRegExp)[0].split('x');
73 | return {
74 | width: parseInt(dimensions[0], 10),
75 | height: parseInt(dimensions[1], 10),
76 | get formatted() {
77 | return this.width + 'x' + this.height;
78 | }
79 | };
80 | },
81 | getAvailablePlatforms: function() {
82 | var files = [];
83 | var supportFiles = fs.cwd(config.folder.ad_platform);
84 | fs.find(supportFiles.cwd(), { matching: ['*.md']}).forEach(function(file) {
85 | files.push(path.parse(file).name);
86 | });
87 | return files;
88 | },
89 | formatAvailablePlatforms: function() {
90 | var files = utils.getAvailablePlatforms();
91 | return files.join(', ').replace(/,\s(\w+)$/, ' or $1');
92 | },
93 | reportError: function(error) {
94 | gutil.log(gutil.colors.red(error.message));
95 | this.emit('end');
96 | }
97 | };
98 |
99 | /*
100 | --------------------------------------------------------------------------- */
101 | module.exports = utils;
102 |
--------------------------------------------------------------------------------
/gulpfile.js/tasks/helper/review-template.js:
--------------------------------------------------------------------------------
1 | /* TASK:
2 | ------------------------------------------------------------------------------------------------- */
3 | module.exports = function(gulp, plugin, config, utils) {
4 | var reviewTmpFolder = '';
5 |
6 | gulp.task('review-template', false, function(done) {
7 | reviewTmpFolder = config.watchFolder + '/.tmp';
8 | plugin.sequence('review-template:clone', 'review-template:update-directory', 'review-template:update-index', done);
9 | });
10 |
11 | /* Clone Review Page repository and set up in `review` directory
12 | --------------------------------------------------------------------------- */
13 | gulp.task('review-template:clone', false, function(done) {
14 | plugin.git.clone(config.project.review_template, {args: reviewTmpFolder}, function(err) {
15 | if (err) { console.log(err); }
16 | else { done(); }
17 | });
18 | });
19 |
20 | /* Move files around and delete unnecessary files/folders
21 | --------------------------------------------------------------------------- */
22 | gulp.task('review-template:update-directory', false, function(done) {
23 | // move files to `review` root
24 | var review = plugin.fs.cwd(reviewTmpFolder);
25 | review.move('./public/index.html', '../index.html');
26 | review.move('./public/assets', '../assets');
27 | // remove extra files
28 | review = review.cwd('../');
29 | plugin.del([review.cwd() + '/.tmp'], { force: true });
30 | // add banner controls for review
31 | var controls = '_banner-support-files/controls/';
32 | plugin.fs.copy('./banners/' + controls, config.watchFolder + '/assets/' + controls);
33 |
34 | done();
35 | });
36 |
37 | /* Update main review `index.html` page to show all banners and meta info
38 | --------------------------------------------------------------------------- */
39 | gulp.task('review-template:update-index', false, function(done) {
40 | var bannerHtml = [];
41 | var bannerList = utils.getBanners();
42 |
43 | var updateIndex = function() {
44 | return plugin.tap(function(file) {
45 | var contents = file.contents.toString('utf-8');
46 | var $ = plugin.cheerio.load(contents);
47 |
48 | var title = $('title').text().replace('{{title}}', config.project.title);
49 | $('title').text(title);
50 | $('h3.headline').text(config.project.title);
51 |
52 | // Update Tabs
53 | var tpl_tab = utils.trim($('#tab-item').html());
54 | var tpl_tab_single = utils.trim($('#tab-single-item').html());
55 |
56 | $(bannerList).each(function(i, banner) {
57 | var modifiedTime = utils.walkDirectory('./banners/' + banner).sort(function(a, b) { return b.modifyTime - a.modifyTime; });
58 | var modifiedDate = new Date(modifiedTime[0].modifyTime).toISOString();
59 | var zipFile = config.defaults.zipFolder + '/' + config.project.name + '_' + banner + '.zip';
60 |
61 | var tab = tpl_tab.replace('{{type}}', 'iframe').replace('{{modified}}', modifiedDate).replace('{{size}}', banner);
62 | if (config.injectAdPlatform) {
63 | tab = tab.replace('{{zipfile}}', zipFile).replace('{{filesize}}', utils.getFileSize(config.watchFolder + '/' + zipFile));
64 | }
65 | else {
66 | tab = tab.replace('{{filesize}}', utils.getFileSize(config.watchFolder + '/banners/' + banner));
67 | }
68 |
69 | bannerHtml.push(tab);
70 | });
71 | bannerHtml.push(tpl_tab_single.replace('{{size}}', bannerList[0]));
72 | $('.tabs').html(bannerHtml.join(''));
73 | $('.tabs').find('input[type="radio"]').first().attr('checked', true);
74 |
75 | // remove the static templates from the index page
76 | $('#tab-item, #tab-single-item').remove();
77 | // write all the changes to the page
78 | file.contents = new Buffer($.html());
79 | });
80 | };
81 |
82 | return gulp
83 | .src(config.watchFolder + '/index.html')
84 | .pipe(updateIndex())
85 | .pipe(gulp.dest(config.watchFolder));
86 | });
87 | };
88 |
--------------------------------------------------------------------------------
/banners/_banner-template/assets/js/script.js:
--------------------------------------------------------------------------------
1 | /* global TimelineMax, Power4, EB, EBG */
2 |
3 | // Broadcast Events shim
4 | // ====================================================================================================
5 | (function() {
6 | if (typeof window.CustomEvent === 'function') { return false; }
7 |
8 | function CustomEvent(event, params) {
9 | params = params || { bubbles: false, cancelable: false, detail: undefined };
10 | var evt = document.createEvent('CustomEvent');
11 | evt.initCustomEvent(event, params.bubbles, params.cancelable, params.detail);
12 | return evt;
13 | }
14 |
15 | CustomEvent.prototype = window.Event.prototype;
16 | window.CustomEvent = CustomEvent;
17 | })();
18 |
19 | // Timeline
20 | // ====================================================================================================
21 | var timeline = (function MasterTimeline() {
22 |
23 | var tl;
24 | var win = window;
25 |
26 | function doClickTag() { window.open(window.clickTag); }
27 |
28 | function initTimeline() {
29 | document.querySelector('#ad .banner').style.display = 'block';
30 | document.getElementById('ad').addEventListener('click', doClickTag);
31 | createTimeline();
32 | }
33 |
34 | function createTimeline() {
35 | tl = new TimelineMax({delay: 0.25, onStart: updateStart, onComplete: updateComplete, onUpdate: updateStats});
36 | // ---------------------------------------------------------------------------
37 |
38 | tl.add('frame1')
39 | .from('.frame1-text', 0.35, { scale: 4, opacity: 0, ease: Power4.easeInOut }, 'frame1');
40 |
41 | // ---------------------------------------------------------------------------
42 |
43 | tl.add('frame2', '+=1')
44 | .to('.frame1-text', 0.35, { opacity: 0, ease: Power4.easeInOut }, 'frame2')
45 | .from('.frame2-text', 0.35, { scale: 4, opacity: 0, ease: Power4.easeInOut }, 'frame2+=0.15');
46 |
47 | // ---------------------------------------------------------------------------
48 |
49 | tl.add('frame3', '+=1')
50 | .to('.frame2-text', 0.35, { opacity: 0, ease: Power4.easeInOut }, 'frame3')
51 | .from('.frame3-text', 0.35, { scale: 4, opacity: 0, ease: Power4.easeInOut }, 'frame3+=0.15');
52 |
53 | // ---------------------------------------------------------------------------
54 |
55 | tl.add('frame4', '+=1')
56 | .to('.frame3-text', 0.35, { opacity: 0, ease: Power4.easeInOut }, 'frame4')
57 | .from('.frame4-text', 0.35, { scale: 4, opacity: 0, ease: Power4.easeInOut }, 'frame4+=0.15')
58 | .to('.frame4-text', 2, { opacity: 0, ease: Power4.easeInOut }, 'frame4+=2');
59 |
60 | // ---------------------------------------------------------------------------
61 |
62 | // DEBUG:
63 | // tl.play('frame3'); // start playing at label:frame3
64 | // tl.pause('frame3'); // pause the timeline at label:frame3
65 | }
66 |
67 | function updateStart() {
68 | var start = new CustomEvent('start', {
69 | 'detail': { 'hasStarted': true }
70 | });
71 | win.dispatchEvent(start);
72 | }
73 |
74 | function updateComplete() {
75 | var complete = new CustomEvent('complete', {
76 | 'detail': { 'hasStopped': true }
77 | });
78 | win.dispatchEvent(complete);
79 | }
80 |
81 | function updateStats() {
82 | var statistics = new CustomEvent('stats', {
83 | 'detail': { 'totalTime': tl.totalTime(), 'totalProgress': tl.totalProgress(), 'totalDuration': tl.totalDuration()
84 | }
85 | });
86 | win.dispatchEvent(statistics);
87 | }
88 |
89 | function getTimeline() {
90 | return tl;
91 | }
92 |
93 | return {
94 | init: initTimeline,
95 | get: getTimeline
96 | };
97 |
98 | })();
99 |
100 | // Banner Init
101 | // ====================================================================================================
102 | timeline.init();
103 |
104 | (function () {
105 | var tl = timeline.get();
106 | tl.add('replayBtnFrame')
107 | .from('.replay-btn', 0.5, { opacity: 0, ease: Power4.easeInOut }, 'replayBtnFrame')
108 | ;
109 |
110 | var replayBtn = document.querySelector('.replay-btn');
111 |
112 | replayBtn.addEventListener('click', function (e) {
113 | e.preventDefault();
114 | tl.restart();
115 | });
116 | })();
117 |
--------------------------------------------------------------------------------
/gulpfile.js/tasks/helper/zipfiles.js:
--------------------------------------------------------------------------------
1 | /* TASK:
2 | ------------------------------------------------------------------------------------------------- */
3 | module.exports = function(gulp, plugin, config, utils) {
4 | gulp.task('zip:banners', false, ['zip:prepare', 'zip:compress'], function(done) {
5 | var srcFolder = plugin.path.join(config.watchFolder, config.defaults.zipFolder);
6 | var folders = utils.getFolders(srcFolder);
7 |
8 | // Remove directories. Only leave the compressed zip files
9 | folders.map(function(folder) {
10 | plugin.fs.remove(plugin.path.join(srcFolder, folder));
11 | });
12 |
13 | plugin.sequence('preflight:zipfile-size-limit', done);
14 | });
15 |
16 | gulp.task('zip:compress', false, function(done) {
17 | var srcFolder = plugin.path.join(config.watchFolder, config.defaults.zipFolder);
18 | var folders = utils.getFolders(srcFolder);
19 |
20 | // each folder will be zipped up
21 | var singleZip = folders.map(function(folder) {
22 | return gulp
23 | .src(plugin.path.join(srcFolder, folder, '/**/*'))
24 | .pipe(plugin.plumber({ errorHandler: config.reportError }))
25 | .pipe(plugin.zip(config.project.name + '_' + folder + '.zip'))
26 | .pipe(gulp.dest(srcFolder));
27 | });
28 |
29 | // if there is only one, there's no need to create a "group" zip too
30 | if (folders.length === 1) { return plugin.merge(singleZip); }
31 |
32 | // all folders will be grouped and zipped up into one file
33 | var groupZip = gulp
34 | .src(plugin.path.join(srcFolder, '/**/*'))
35 | .pipe(plugin.plumber({ errorHandler: config.reportError }))
36 | .pipe(plugin.zip(config.project.name + '-all(' + folders.length + ').zip'))
37 | .pipe(gulp.dest(srcFolder));
38 |
39 | return plugin.merge(singleZip, groupZip);
40 | });
41 |
42 | gulp.task('zip:prepare', false, function(done) {
43 | var srcFolder = plugin.path.join(config.watchFolder, config.defaults.zipFolder);
44 | var folders = utils.getFolders(srcFolder);
45 |
46 | // TODO: Add addition compression tasks
47 | // https://github.com/ben-eb/gulp-uncss
48 | // https://github.com/sindresorhus/gulp-imagemin
49 | // https://www.npmjs.com/package/gulp-unused-images -or-
50 | // https://www.npmjs.com/package/gulp-delete-unused-images
51 |
52 | var prepareFiles = folders.map(function(folder) {
53 | var dir = plugin.path.join(srcFolder, folder);
54 | // 1) Rename `index.html`
55 | plugin.fs.rename(dir + '/index.html', config.project.name + '_' + folder + '.html');
56 | var index = plugin.fs.find(dir, { matching: ['*.html'] })[0];
57 |
58 | // 2) Move `fallback` image to root level of each banner folder
59 | var image = plugin.fs.find(dir, { matching: ['fallback.{jpg,gif,png}'] });
60 | var fallback = plugin.path.parse(image[0]);
61 | plugin.fs.move(fallback.dir + '/' + fallback.base, dir + '/' + fallback.base);
62 |
63 | // 3) Rename `fallback` image to project.name
64 | plugin.fs.find(dir, { matching: ['fallback.{jpg,gif,png}'] }).forEach(function(image) {
65 | var ext = image.split('.').pop();
66 | plugin.fs.rename(image, config.project.name + '_' + folder + '.' + ext);
67 | });
68 |
69 | var jsFilter = plugin.filter([dir + '/assets/js/script.js'], { restore: true });
70 | var cssFilter = plugin.filter([dir + '/assets/css/style.css'], { restore: true });
71 | var htmlFilter = plugin.filter([index], { restore: true });
72 |
73 | return gulp
74 | .src(plugin.path.join(srcFolder, folder, '/**/*'))
75 | .pipe(plugin.plumber({ errorHandler: config.reportError }))
76 | // 4) Minify HTML
77 | .pipe(htmlFilter)
78 | .pipe(plugin.htmlmin({collapseWhitespace: true, removeComments: true}))
79 | .pipe(htmlFilter.restore)
80 | // 5) Minify CSS
81 | .pipe(cssFilter)
82 | .pipe(plugin.cssnano())
83 | .pipe(cssFilter.restore)
84 | // 6) Minify JS
85 | .pipe(jsFilter)
86 | .pipe(plugin.uglify())
87 | .pipe(jsFilter.restore)
88 | .pipe(gulp.dest(dir));
89 | });
90 |
91 | return plugin.merge(prepareFiles);
92 | });
93 | };
94 |
--------------------------------------------------------------------------------
/banners/_banner-support-files/controls/banner-controls.js:
--------------------------------------------------------------------------------
1 | /* global Promise, timeline, Dragdealer */
2 |
3 | // Bling.js
4 | // https://gist.github.com/paulirish/12fb951a8b893a454b32
5 | // ====================================================================================================
6 | window.$ = document.querySelector.bind(document);
7 | window.$$ = document.querySelectorAll.bind(document);
8 | Node.prototype.on = window.on = function(name, fn) {
9 | this.addEventListener(name, fn);
10 | };
11 | NodeList.prototype.__proto__ = Array.prototype;
12 | NodeList.prototype.on = NodeList.prototype.addEventListener = function(name, fn) {
13 | this.forEach(function(elem, i) {
14 | elem.on(name, fn);
15 | });
16 | };
17 |
18 | // ====================================================================================================
19 | var banner_controls = (function banner_controls() {
20 | var $element;
21 | var $window = window;
22 | var $iframe = $('.banner-content');
23 |
24 | var timelineReady = new Promise(function(resolve, reject) {
25 | (function waitForTimeline() {
26 | if ($window.timeline) { resolve(); }
27 | else { setTimeout(waitForTimeline, 250); }
28 | })();
29 | });
30 |
31 | var attachControls = function(selector) {
32 | var element = selector || 'body';
33 | $element = $(element);
34 | };
35 |
36 | /* Control HTML structure
37 | ----------------------------------------------------------------------- */
38 | var tml_controls = '' +
39 | '' +
40 | ' ' +
41 | ' - 00.00 / 00.00
' +
42 | ' - ' +
43 | '
' +
44 | '
' +
45 | '
' +
46 | '
' +
47 | ' ' +
48 | '
';
49 |
50 | function buildControls() {
51 | /* Cache Control Elements
52 | ----------------------------------------------------------------------- */
53 | var activeSlider = false;
54 | var hasCompleted = false;
55 | var timeline = $window.timeline.get();
56 |
57 | var $banner_controls = $('.banner-controls');
58 | if (!$banner_controls) {
59 | $element.insertAdjacentHTML('beforeend', tml_controls);
60 | $banner_controls = $('.banner-controls');
61 | }
62 |
63 | var $btnPlayPause = $banner_controls.querySelector('.btn-play-pause');
64 | var $timeCurrent = $banner_controls.querySelector('.time-current');
65 | var $timeTotal = $banner_controls.querySelector('.time-total');
66 |
67 | var $progressbar = $banner_controls.querySelector('.progress-bar');
68 | var $progressbarTotal = $banner_controls.querySelector('.total');
69 | var $progressbarBkgd = $banner_controls.querySelectorAll('.progress-bar, .thumb');
70 | var $progressbarHandle;
71 |
72 | /* Event Listeners: Play/Pause Button
73 | ----------------------------------------------------------------------- */
74 | $window.addEventListener('start', function(e) {
75 | if (e.detail.hasStarted) {
76 | $btnPlayPause.parentNode.classList.add('pause');
77 | }
78 | }, false);
79 |
80 | $window.addEventListener('complete', function(e) {
81 | if (e.detail.hasStopped) {
82 | $btnPlayPause.parentNode.classList.remove('pause');
83 | hasCompleted = true;
84 | }
85 | }, false);
86 |
87 | $btnPlayPause.addEventListener('click', function(e) {
88 | e.currentTarget.parentNode.classList.toggle('pause');
89 |
90 | if (hasCompleted) {
91 | hasCompleted = false;
92 | timeline.restart();
93 | return;
94 | }
95 |
96 | timeline.paused(!timeline.paused());
97 | }, false);
98 |
99 | /* Event Listeners: Progress Bar / Thumb
100 | ----------------------------------------------------------------------- */
101 | $progressbarHandle = new Dragdealer($progressbar, {
102 | animationCallback: function(x, y) {
103 | timeline.totalProgress(x);
104 | }
105 | });
106 |
107 | $progressbarBkgd.addEventListener('mousedown', function() {
108 | activeSlider = true;
109 | if (!timeline.paused()) { timeline.pause(); }
110 | $btnPlayPause.parentNode.classList.remove('pause');
111 | }, false);
112 |
113 | $progressbarBkgd.addEventListener('mouseup', function() {
114 | activeSlider = false;
115 | }, false);
116 |
117 | /* Event Listeners: Update Time Display and Progress Bar
118 | ----------------------------------------------------------------------- */
119 | $window.addEventListener('stats', function(e) {
120 | var duration = e.detail;
121 | $progressbarTotal.style.width = (duration.totalProgress * 100) + '%';
122 | $progressbarHandle.setValue(duration.totalProgress, 0, true);
123 | $timeCurrent.textContent = duration.totalTime.toFixed(2);
124 | $timeTotal.textContent = duration.totalDuration.toFixed(2);
125 | }, false);
126 | }
127 |
128 | /* Init
129 | --------------------------------------------------------------------------- */
130 | if ($iframe) {
131 | $iframe.addEventListener('load', function() {
132 | $window = $iframe.contentWindow;
133 | timelineReady.then(buildControls);
134 | }, false);
135 | }
136 | else {
137 | timelineReady.then(buildControls);
138 | }
139 |
140 | return {
141 | attachTo: attachControls
142 | };
143 | })();
144 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # HTML5 Banner Boilerplate
2 |
3 | -----
4 |
5 | Project boilerplate for creating HTML5 animated banners with [GSAP](http://greensock.com/gsap).
6 |
7 | [Download Banner Boilerplate](https://github.com/misega/HTML5-Banners/archive/master.zip)
8 |
9 | * Watch a specific banner folder for changes and update browser during development
10 | * Compile CSS variables and automatically adds vendor prefixes via `POSTCSS`
11 | * Verify each banner has a fallback image and is the appropriate size
12 | * Generate review site
13 | * Update all banners to platform-specific distribution (e.g. DoubleClick, Sizmek)
14 | * Minify files and assets then create directory of zip files for distribution
15 | * Verify zip file size does not exceed IAB maximum allowable file size
16 |
17 | ### In This Documentation
18 | 1. [Getting Started](#gettingstarted)
19 | 2. [File Structure](#filestructure)
20 | 3. [Resources](#resources)
21 | 4. [References](#references)
22 |
23 | ## Getting Started
24 |
25 | ### Dependencies
26 | Make sure these are installed first.
27 |
28 | * [Node.js](http://nodejs.org)
29 | * [Gulp](http://gulpjs.com) `sudo npm install -g gulp`
30 |
31 | ### Quick Start
32 | 1. Open `banners` folder.
33 | 2. Inside, rename `_banner-template` with the banner size (e.g. `300x250`)
34 | 3. In bash/terminal/command line, `cd` into your project directory.
35 | 4. Run `npm install` to install required files.
36 | 5. When it's done installing, run one of the tasks to get going:
37 |
38 | | Tasks | |
39 | |:----|----|
40 | | `gulp` | Will show all available tasks
41 | | `gulp watch`
`--folder`
`--controls` | Watch files for changes and update browser
_flag:_ folder to watch for file changes
_flag:_ controls to play/pause & scrub timeline
42 | | `gulp review`
`--preview`
`--platform` | Build review page; ready to push to review ftp
_flag:_ open review page in browser
_flag:_ add zip files to review site; ready for distribution
43 | | `gulp deploy`
`--platform` | Compress files and zip folders for distribution
_flag:_ ad platform distribution (`doubleclick`,`sizmek`)
44 |
45 | #### Update _package.json_
46 | * **name**: Project Code (format: YY-aaa-9999). Information used throughout the build/review/deploy process
47 | * **YY**: 2-digit Year
48 | * **aaa**: 3-digit Client Code
49 | * **9999**: 4-digit Job Code
50 | * **title**: Project Title. Added to the review site
51 |
52 | ## File Structure
53 |
54 | ```bash
55 | .
56 | ├── README.md
57 | ├── package.json # list of npm packages and some configurations
58 | ├── gulp.js/ # build configuration
59 | ├── node_modules/ # will be created with `npm install`
60 | └─┬ banners/ # directory to contain all banner sizes
61 | ├─┬ _banner-support-files/
62 | │ ├─┬ ad-platform/ # collection of platform-specific documentation
63 | │ │ ├── doubleclick.md # documentation; script blocks will be injected via `deploy` task
64 | │ │ ├── sizmek.md # documentation; script blocks will be injected via `deploy` task
65 | │ │ └── EBLoader.js # required Sizmek script; must load first before ad is displayed
66 | │ └─┬ controls/
67 | │ ├── _banners.js # installs required development assets
68 | │ ├── banner-controls.js # installs/instantiates control bar
69 | │ └── banner-controls.css # styles for control bar
70 | └─┬ _banner-template/ # initial banner setup; duplicate to customize
71 | ├── index.html # The ad file, with the ad configuration and init code
72 | ├── fallback.jpg # required; Formats accepted: JPG, GIF, PNG
73 | └─┬ assets/
74 | ├─┬ css/
75 | │ ├── style.css # compiled styles; will be created with `watch`, `review`, or `deploy` tasks
76 | │ ├── source.css # main styles; compiled by postcss into `style.css`
77 | │ └── fonts/ # local font files (optional)
78 | ├─┬ img/ # graphic files: jpg, gif, png, or svg
79 | │ └── keyframes/ # keyframe graphics from PSD for layout/placement; removed via `review` or `deploy` task
80 | └─┬ js/
81 | └── script.js # customized banner animation script
82 | ```
83 |
84 | ## Resources
85 | **Greensock/GSAP**
86 | [Greensock 101 - Greensock Tutorials for Beginners](https://ihatetomatoes.net/product/greensock-101/)
87 | [Greensock Cheat Sheet](https://ihatetomatoes.net/wp-content/uploads/2015/08/GreenSock-Cheatsheet-2.pdf) PDF
88 |
89 | ## References
90 | **IAB.com**
91 | [iab.com/guidelines](http://www.iab.com/guidelines/)
92 | [iab.com - HTML5 for Digital Advertising 2.0](http://www.iab.com/wp-content/uploads/2016/04/HTML5forDigitalAdvertising2.0.pdf) PDF
93 | [iab.com - Display Format Guidelines](http://www.iab.com/wp-content/uploads/2015/11/IAB_Display_Mobile_Creative_Guidelines_HTML5_2015.pdf) PDF
94 |
95 | **DoubleClick**
96 | [DoubleClick Starter Files](http://www.richmediagallery.com/tools/starterfiles)
97 | [DoubleClick Campaign Manager (DCM) HTML5 Validator](https://h5validator.appspot.com/dcm)
98 | [DoubleClick CDN/Shared Libraries](https://support.google.com/richmedia/answer/6307288)
99 | [Rich Media Gallery](http://www.richmediagallery.com/gallery)
100 |
101 | **Sizmek**
102 | [Sizmek Banner Formats](http://showcase.sizmek.com/formats)
103 | [Building Ads / Creating HTML5 Banners](https://support.sizmek.com/hc/en-us/categories/200103329--creative-Building-Ads-Creating-HTML5-Ads)
104 | [Sizmek CDN/Shared Libraries](https://support.sizmek.com/hc/en-us/articles/206136366--reference-glossary-HTML5-Shared-Libraries)
105 | [Centro uses Sizmek tags](http://www2.centro.net/html5)
106 |
107 |
108 | ## Roadmap
109 | - update to `gulp 4`
110 |
111 | ## TODO
112 |
113 | - `zipfiles.js`
114 | - minify image assets
115 | - `review` -- declare specific folders to use when building a review site
116 | - allow wildcard filter to select folder names
117 | - lint on `watch`: html, css, js
118 | - cleanup: find unused assets (css, js, images)
119 | - update controls to add features. See: [mojs-player](https://github.com/legomushroom/mojs-player)
120 |
--------------------------------------------------------------------------------
/banners/_banner-support-files/controls/lib/dragdealer.min.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Dragdealer.js 0.9.8
3 | * http://github.com/skidding/dragdealer
4 | *
5 | * (c) 2010+ Ovidiu Cherecheș
6 | * http://skidding.mit-license.org
7 | */
8 | !function(a,b){"function"==typeof define&&define.amd?define(b):a.Dragdealer=b()}(this,function(){function j(a){var b="Webkit Moz ms O".split(" "),c=document.documentElement.style;if(void 0!==c[a])return a;a=a.charAt(0).toUpperCase()+a.substr(1);for(var d=0;d0)return c[0]}else for(d=new RegExp("(^|\\s)"+b+"(\\s|$)"),c=a.getElementsByTagName("*"),e=0;e=1)for(var b=0;b<=this.options.steps-1;b++)a[b]=this.options.steps>1?b/(this.options.steps-1):0;return a},setWrapperOffset:function(){this.offset.wrapper=h.get(this.wrapper)},calculateBounds:function(){var a={top:this.options.top||0,bottom:-(this.options.bottom||0)+this.wrapper.offsetHeight,left:this.options.left||0,right:-(this.options.right||0)+this.wrapper.offsetWidth};return a.availWidth=a.right-a.left-this.handle.offsetWidth,a.availHeight=a.bottom-a.top-this.handle.offsetHeight,a},calculateValuePrecision:function(){var a=this.options.xPrecision||Math.abs(this.bounds.availWidth),b=this.options.yPrecision||Math.abs(this.bounds.availHeight);return[a?1/a:0,b?1/b:0]},bindMethods:function(){this.requestAnimationFrame="function"==typeof this.options.customRequestAnimationFrame?b(this.options.customRequestAnimationFrame,window):b(m,window),this.cancelAnimationFrame="function"==typeof this.options.customCancelAnimationFrame?b(this.options.customCancelAnimationFrame,window):b(n,window),this.animateWithRequestAnimationFrame=b(this.animateWithRequestAnimationFrame,this),this.animate=b(this.animate,this),this.onHandleMouseDown=b(this.onHandleMouseDown,this),this.onHandleTouchStart=b(this.onHandleTouchStart,this),this.onDocumentMouseMove=b(this.onDocumentMouseMove,this),this.onWrapperTouchMove=b(this.onWrapperTouchMove,this),this.onWrapperMouseDown=b(this.onWrapperMouseDown,this),this.onWrapperTouchStart=b(this.onWrapperTouchStart,this),this.onDocumentMouseUp=b(this.onDocumentMouseUp,this),this.onDocumentTouchEnd=b(this.onDocumentTouchEnd,this),this.onHandleClick=b(this.onHandleClick,this),this.onWindowResize=b(this.onWindowResize,this)},bindEventListeners:function(){c(this.handle,"mousedown",this.onHandleMouseDown),c(this.handle,"touchstart",this.onHandleTouchStart),c(document,"mousemove",this.onDocumentMouseMove),c(this.wrapper,"touchmove",this.onWrapperTouchMove),c(this.wrapper,"mousedown",this.onWrapperMouseDown),c(this.wrapper,"touchstart",this.onWrapperTouchStart),c(document,"mouseup",this.onDocumentMouseUp),c(document,"touchend",this.onDocumentTouchEnd),c(this.handle,"click",this.onHandleClick),c(window,"resize",this.onWindowResize),this.animate(!1,!0),this.interval=this.requestAnimationFrame(this.animateWithRequestAnimationFrame)},unbindEventListeners:function(){d(this.handle,"mousedown",this.onHandleMouseDown),d(this.handle,"touchstart",this.onHandleTouchStart),d(document,"mousemove",this.onDocumentMouseMove),d(this.wrapper,"touchmove",this.onWrapperTouchMove),d(this.wrapper,"mousedown",this.onWrapperMouseDown),d(this.wrapper,"touchstart",this.onWrapperTouchStart),d(document,"mouseup",this.onDocumentMouseUp),d(document,"touchend",this.onDocumentTouchEnd),d(this.handle,"click",this.onHandleClick),d(window,"resize",this.onWindowResize),this.cancelAnimationFrame(this.interval)},onHandleMouseDown:function(a){g.refresh(a),e(a),f(a),this.activity=!1,this.startDrag()},onHandleTouchStart:function(a){g.refresh(a),f(a),this.activity=!1,this.startDrag()},onDocumentMouseMove:function(a){g.refresh(a),this.dragging&&(this.activity=!0,e(a))},onWrapperTouchMove:function(a){return g.refresh(a),!this.activity&&this.draggingOnDisabledAxis()?(this.dragging&&this.stopDrag(),void 0):(e(a),this.activity=!0,void 0)},onWrapperMouseDown:function(a){g.refresh(a),e(a),this.startTap()},onWrapperTouchStart:function(a){g.refresh(a),e(a),this.startTap()},onDocumentMouseUp:function(){this.stopDrag(),this.stopTap()},onDocumentTouchEnd:function(){this.stopDrag(),this.stopTap()},onHandleClick:function(a){this.activity&&(e(a),f(a))},onWindowResize:function(){this.reflow()},enable:function(){this.disabled=!1,this.handle.className=this.handle.className.replace(/\s?disabled/g,"")},disable:function(){this.disabled=!0,this.handle.className+=" disabled"},reflow:function(){this.setWrapperOffset(),this.bounds=this.calculateBounds(),this.valuePrecision=this.calculateValuePrecision(),this.updateOffsetFromValue()},getStep:function(){return[this.getStepNumber(this.value.target[0]),this.getStepNumber(this.value.target[1])]},getValue:function(){return this.value.target},setStep:function(a,b,c){this.setValue(this.options.steps&&a>1?(a-1)/(this.options.steps-1):0,this.options.steps&&b>1?(b-1)/(this.options.steps-1):0,c)},setValue:function(a,b,c){this.setTargetValue([a,b||0]),c&&(this.groupCopy(this.value.current,this.value.target),this.updateOffsetFromValue(),this.callAnimationCallback())},startTap:function(){!this.disabled&&this.options.tapping&&(this.tapping=!0,this.setWrapperOffset(),this.setTargetValueByOffset([g.x-this.offset.wrapper[0]-this.handle.offsetWidth/2,g.y-this.offset.wrapper[1]-this.handle.offsetHeight/2]))},stopTap:function(){!this.disabled&&this.tapping&&(this.tapping=!1,this.setTargetValue(this.value.current))},startDrag:function(){this.disabled||(this.dragging=!0,this.setWrapperOffset(),this.offset.mouse=[g.x-h.get(this.handle)[0],g.y-h.get(this.handle)[1]],this.wrapper.className.match(this.options.activeClass)||(this.wrapper.className+=" "+this.options.activeClass),this.callDragStartCallback())},stopDrag:function(){if(!this.disabled&&this.dragging){this.dragging=!1;var a=this.groupClone(this.value.current);if(this.options.slide){var b=this.change;a[0]+=4*b[0],a[1]+=4*b[1]}this.setTargetValue(a),this.wrapper.className=this.wrapper.className.replace(" "+this.options.activeClass,""),this.callDragStopCallback()}},callAnimationCallback:function(){var a=this.value.current;this.options.snap&&this.options.steps>1&&(a=this.getClosestSteps(a)),this.groupCompare(a,this.value.prev)||("function"==typeof this.options.animationCallback&&this.options.animationCallback.call(this,a[0],a[1]),this.groupCopy(this.value.prev,a))},callTargetCallback:function(){"function"==typeof this.options.callback&&this.options.callback.call(this,this.value.target[0],this.value.target[1])},callDragStartCallback:function(){"function"==typeof this.options.dragStartCallback&&this.options.dragStartCallback.call(this,this.value.target[0],this.value.target[1])},callDragStopCallback:function(){"function"==typeof this.options.dragStopCallback&&this.options.dragStopCallback.call(this,this.value.target[0],this.value.target[1])},animateWithRequestAnimationFrame:function(a){a?(this.timeOffset=this.timeStamp?a-this.timeStamp:0,this.timeStamp=a):this.timeOffset=25,this.animate(),this.interval=this.requestAnimationFrame(this.animateWithRequestAnimationFrame)},animate:function(a,b){if(!a||this.dragging){if(this.dragging){var c=this.groupClone(this.value.target),d=[g.x-this.offset.wrapper[0]-this.offset.mouse[0],g.y-this.offset.wrapper[1]-this.offset.mouse[1]];this.setTargetValueByOffset(d,this.options.loose),this.change=[this.value.target[0]-c[0],this.value.target[1]-c[1]]}(this.dragging||b)&&this.groupCopy(this.value.current,this.value.target),(this.dragging||this.glide()||b)&&(this.updateOffsetFromValue(),this.callAnimationCallback())}},glide:function(){var a=[this.value.target[0]-this.value.current[0],this.value.target[1]-this.value.current[1]];return a[0]||a[1]?(Math.abs(a[0])>this.valuePrecision[0]||Math.abs(a[1])>this.valuePrecision[1]?(this.value.current[0]+=a[0]*Math.min(this.options.speed*this.timeOffset/25,1),this.value.current[1]+=a[1]*Math.min(this.options.speed*this.timeOffset/25,1)):this.groupCopy(this.value.current,this.value.target),!0):!1},updateOffsetFromValue:function(){this.offset.current=this.options.snap?this.getOffsetsByRatios(this.getClosestSteps(this.value.current)):this.getOffsetsByRatios(this.value.current),this.groupCompare(this.offset.current,this.offset.prev)||(this.renderHandlePosition(),this.groupCopy(this.offset.prev,this.offset.current))},renderHandlePosition:function(){var a="";return this.options.css3&&i.transform?(this.options.horizontal&&(a+="translateX("+this.offset.current[0]+"px)"),this.options.vertical&&(a+=" translateY("+this.offset.current[1]+"px)"),this.handle.style[i.transform]=a,void 0):(this.options.horizontal&&(this.handle.style.left=this.offset.current[0]+"px"),this.options.vertical&&(this.handle.style.top=this.offset.current[1]+"px"),void 0)},setTargetValue:function(a,b){var c=b?this.getLooseValue(a):this.getProperValue(a);this.groupCopy(this.value.target,c),this.offset.target=this.getOffsetsByRatios(c),this.callTargetCallback()},setTargetValueByOffset:function(a,b){var c=this.getRatiosByOffsets(a),d=b?this.getLooseValue(c):this.getProperValue(c);this.groupCopy(this.value.target,d),this.offset.target=this.getOffsetsByRatios(d)},getLooseValue:function(a){var b=this.getProperValue(a);return[b[0]+(a[0]-b[0])/4,b[1]+(a[1]-b[1])/4]},getProperValue:function(a){var b=this.groupClone(a);return b[0]=Math.max(b[0],0),b[1]=Math.max(b[1],0),b[0]=Math.min(b[0],1),b[1]=Math.min(b[1],1),(!this.dragging&&!this.tapping||this.options.snap)&&this.options.steps>1&&(b=this.getClosestSteps(b)),b},getRatiosByOffsets:function(a){return[this.getRatioByOffset(a[0],this.bounds.availWidth,this.bounds.left),this.getRatioByOffset(a[1],this.bounds.availHeight,this.bounds.top)]},getRatioByOffset:function(a,b,c){return b?(a-c)/b:0},getOffsetsByRatios:function(a){return[this.getOffsetByRatio(a[0],this.bounds.availWidth,this.bounds.left),this.getOffsetByRatio(a[1],this.bounds.availHeight,this.bounds.top)]},getOffsetByRatio:function(a,b,c){return Math.round(a*b)+c},getStepNumber:function(a){return this.getClosestStep(a)*(this.options.steps-1)+1},getClosestSteps:function(a){return[this.getClosestStep(a[0]),this.getClosestStep(a[1])]},getClosestStep:function(a){for(var b=0,c=1,d=0;d<=this.options.steps-1;d++)Math.abs(this.stepRatios[d]-a)g.yDiff||!this.options.vertical&&g.yDiff>g.xDiff}};for(var b=function(a,b){return function(){return a.apply(b,arguments)}},c=function(a,b,c){a.addEventListener?a.addEventListener(b,c,!1):a.attachEvent&&a.attachEvent("on"+b,c)},d=function(a,b,c){a.removeEventListener?a.removeEventListener(b,c,!1):a.detachEvent&&a.detachEvent("on"+b,c)},e=function(a){a||(a=window.event),a.preventDefault&&a.preventDefault(),a.returnValue=!1},f=function(a){a||(a=window.event),a.stopPropagation&&a.stopPropagation(),a.cancelBubble=!0},g={x:0,y:0,xDiff:0,yDiff:0,refresh:function(a){a||(a=window.event),"mousemove"==a.type?this.set(a):a.touches&&this.set(a.touches[0])},set:function(a){var b=this.x,c=this.y;a.clientX||a.clientY?(this.x=a.clientX,this.y=a.clientY):(a.pageX||a.pageY)&&(this.x=a.pageX-document.body.scrollLeft-document.documentElement.scrollLeft,this.y=a.pageY-document.body.scrollTop-document.documentElement.scrollTop),this.xDiff=Math.abs(this.x-b),this.yDiff=Math.abs(this.y-c)}},h={get:function(a){var b={left:0,top:0};return void 0!==a.getBoundingClientRect&&(b=a.getBoundingClientRect()),[b.left,b.top]}},i={transform:j("transform"),perspective:j("perspective"),backfaceVisibility:j("backfaceVisibility")},l=["webkit","moz"],m=window.requestAnimationFrame,n=window.cancelAnimationFrame,o=0;oc;c++)if(a[c]===b)return c;return-1}function b(a){var b=a._promiseCallbacks;return b||(b=a._promiseCallbacks={}),b}function c(a,b){return"onerror"===a?void rb.on("error",b):2!==arguments.length?rb[a]:void(rb[a]=b)}function d(a){return"function"==typeof a||"object"==typeof a&&null!==a}function e(a){return"function"==typeof a}function f(a){return"object"==typeof a&&null!==a}function g(){}function h(){setTimeout(function(){for(var a,b=0;bh;h++)u(e.resolve(a[h]),void 0,c,d);return f}function E(a,b){var c=this;if(a&&"object"==typeof a&&a.constructor===c)return a;var d=new c(k,b);return q(d,a),d}function F(a,b){var c=this,d=new c(k,b);return t(d,a),d}function G(){throw new TypeError("You must pass a resolver function as the first argument to the promise constructor")}function H(){throw new TypeError("Failed to construct 'Promise': Please use the 'new' operator, this object constructor cannot be called as a function.")}function I(a,b){this._id=Jb++,this._label=b,this._state=void 0,this._result=void 0,this._subscribers=[],rb.instrument&&xb("created",this),k!==a&&(e(a)||G(),this instanceof I||H(),z(this,a))}function J(){this.value=void 0}function K(a){try{return a.then}catch(b){return Lb.value=b,Lb}}function L(a,b,c){try{a.apply(b,c)}catch(d){return Lb.value=d,Lb}}function M(a,b){for(var c,d,e={},f=a.length,g=new Array(f),h=0;f>h;h++)g[h]=a[h];for(d=0;dd;d++)c[d-1]=a[d];return c}function O(a,b){return{then:function(c,d){return a.call(b,c,d)}}}function P(a,b){var c=function(){for(var c,d=this,e=arguments.length,f=new Array(e+1),g=!1,h=0;e>h;++h){if(c=arguments[h],!g){if(g=S(c),g===Mb){var i=new Kb(k);return t(i,Mb.value),i}g&&g!==!0&&(c=O(g,c))}f[h]=c}var j=new Kb(k);return f[e]=function(a,c){a?t(j,a):void 0===b?q(j,c):b===!0?q(j,N(arguments)):tb(b)?q(j,M(arguments,b)):q(j,c)},g?R(j,f,a,d):Q(j,f,a,d)};return c.__proto__=a,c}function Q(a,b,c,d){var e=L(c,d,b);return e===Lb&&t(a,e.value),a}function R(a,b,c,d){return Kb.all(b).then(function(b){var e=L(c,d,b);return e===Lb&&t(a,e.value),a})}function S(a){return a&&"object"==typeof a?a.constructor===Kb?!0:K(a):!1}function T(a,b){return Kb.all(a,b)}function U(a,b,c){this._superConstructor(a,b,!1,c)}function V(a,b){return new U(Kb,a,b).promise}function W(a,b){return Kb.race(a,b)}function X(a,b,c){this._superConstructor(a,b,!0,c)}function Y(a,b){return new Rb(Kb,a,b).promise}function Z(a,b,c){this._superConstructor(a,b,!1,c)}function $(a,b){return new Z(Kb,a,b).promise}function _(a){throw setTimeout(function(){throw a}),a}function ab(a){var b={};return b.promise=new Kb(function(a,c){b.resolve=a,b.reject=c},a),b}function bb(a,b,c){return Kb.all(a,c).then(function(a){if(!e(b))throw new TypeError("You must pass a function as map's second argument.");for(var d=a.length,f=new Array(d),g=0;d>g;g++)f[g]=b(a[g]);return Kb.all(f,c)})}function cb(a,b){return Kb.resolve(a,b)}function db(a,b){return Kb.reject(a,b)}function eb(a,b,c){return Kb.all(a,c).then(function(a){if(!e(b))throw new TypeError("You must pass a function as filter's second argument.");for(var d=a.length,f=new Array(d),g=0;d>g;g++)f[g]=b(a[g]);return Kb.all(f,c).then(function(b){for(var c=new Array(d),e=0,f=0;d>f;f++)b[f]&&(c[e]=a[f],e++);return c.length=e,c})})}function fb(a,b){gc[_b]=a,gc[_b+1]=b,_b+=2,2===_b&&Tb()}function gb(){var a=process.nextTick,b=process.versions.node.match(/^(?:(\d+)\.)?(?:(\d+)\.)?(\*|\d+)$/);return Array.isArray(b)&&"0"===b[1]&&"10"===b[2]&&(a=setImmediate),function(){a(lb)}}function hb(){return function(){vertxNext(lb)}}function ib(){var a=0,b=new dc(lb),c=document.createTextNode("");return b.observe(c,{characterData:!0}),function(){c.data=a=++a%2}}function jb(){var a=new MessageChannel;return a.port1.onmessage=lb,function(){a.port2.postMessage(0)}}function kb(){return function(){setTimeout(lb,1)}}function lb(){for(var a=0;_b>a;a+=2){var b=gc[a],c=gc[a+1];b(c),gc[a]=void 0,gc[a+1]=void 0}_b=0}function mb(){try{var a=require("vertx");return a.runOnLoop||a.runOnContext,hb()}catch(b){return kb()}}function nb(a,b){rb.async(a,b)}function ob(){rb.on.apply(rb,arguments)}function pb(){rb.off.apply(rb,arguments)}var qb={mixin:function(a){return a.on=this.on,a.off=this.off,a.trigger=this.trigger,a._promiseCallbacks=void 0,a},on:function(c,d){var e,f=b(this);e=f[c],e||(e=f[c]=[]),-1===a(e,d)&&e.push(d)},off:function(c,d){var e,f,g=b(this);return d?(e=g[c],f=a(e,d),void(-1!==f&&e.splice(f,1))):void(g[c]=[])},trigger:function(a,c){var d,e,f=b(this);if(d=f[a])for(var g=0;g1)throw new Error("Second argument not supported");if("object"!=typeof a)throw new TypeError("Argument must be an object");return g.prototype=a,new g},wb=[],xb=i,yb=void 0,zb=1,Ab=2,Bb=new w,Cb=new w,Db=B;B.prototype._validateInput=function(a){return tb(a)},B.prototype._validationError=function(){return new Error("Array Methods must be provided an Array")},B.prototype._init=function(){this._result=new Array(this.length)},B.prototype._enumerate=function(){for(var a=this.length,b=this.promise,c=this._input,d=0;b._state===yb&&a>d;d++)this._eachEntry(c[d],d)},B.prototype._eachEntry=function(a,b){var c=this._instanceConstructor;f(a)?a.constructor===c&&a._state!==yb?(a._onError=null,this._settledAt(a._state,b,a._result)):this._willSettleAt(c.resolve(a),b):(this._remaining--,this._result[b]=this._makeResult(zb,b,a))},B.prototype._settledAt=function(a,b,c){var d=this.promise;d._state===yb&&(this._remaining--,this._abortOnReject&&a===Ab?t(d,c):this._result[b]=this._makeResult(a,b,c)),0===this._remaining&&s(d,this._result)},B.prototype._makeResult=function(a,b,c){return c},B.prototype._willSettleAt=function(a,b){var c=this;u(a,void 0,function(a){c._settledAt(zb,b,a)},function(a){c._settledAt(Ab,b,a)})};var Eb=C,Fb=D,Gb=E,Hb=F,Ib="rsvp_"+ub()+"-",Jb=0,Kb=I;I.cast=Gb,I.all=Eb,I.race=Fb,I.resolve=Gb,I.reject=Hb,I.prototype={constructor:I,_guidKey:Ib,_onError:function(a){rb.async(function(b){setTimeout(function(){b._onError&&rb.trigger("error",a)},0)},this)},then:function(a,b,c){var d=this,e=d._state;if(e===zb&&!a||e===Ab&&!b)return rb.instrument&&xb("chained",this,this),this;d._onError=null;var f=new this.constructor(k,c),g=d._result;if(rb.instrument&&xb("chained",d,f),e){var h=arguments[e-1];rb.async(function(){y(e,f,h,g)})}else u(d,f,a,b);return f},"catch":function(a,b){return this.then(null,a,b)},"finally":function(a,b){var c=this.constructor;return this.then(function(b){return c.resolve(a()).then(function(){return b})},function(b){return c.resolve(a()).then(function(){throw b})},b)}};var Lb=new J,Mb=new J,Nb=P,Ob=T;U.prototype=vb(Db.prototype),U.prototype._superConstructor=Db,U.prototype._makeResult=A,U.prototype._validationError=function(){return new Error("allSettled must be called with an array")};var Pb=V,Qb=W,Rb=X;X.prototype=vb(Db.prototype),X.prototype._superConstructor=Db,X.prototype._init=function(){this._result={}},X.prototype._validateInput=function(a){return a&&"object"==typeof a},X.prototype._validationError=function(){return new Error("Promise.hash must be called with an object")},X.prototype._enumerate=function(){var a=this.promise,b=this._input,c=[];for(var d in b)a._state===yb&&b.hasOwnProperty(d)&&c.push({position:d,entry:b[d]});var e=c.length;this._remaining=e;for(var f,g=0;a._state===yb&&e>g;g++)f=c[g],this._eachEntry(f.entry,f.position)};var Sb=Y;Z.prototype=vb(Rb.prototype),Z.prototype._superConstructor=Db,Z.prototype._makeResult=A,Z.prototype._validationError=function(){return new Error("hashSettled must be called with an object")};var Tb,Ub=$,Vb=_,Wb=ab,Xb=bb,Yb=cb,Zb=db,$b=eb,_b=0,ac=fb,bc="undefined"!=typeof window?window:void 0,cc=bc||{},dc=cc.MutationObserver||cc.WebKitMutationObserver,ec="undefined"!=typeof process&&"[object process]"==={}.toString.call(process),fc="undefined"!=typeof Uint8ClampedArray&&"undefined"!=typeof importScripts&&"undefined"!=typeof MessageChannel,gc=new Array(1e3);if(Tb=ec?gb():dc?ib():fc?jb():void 0===bc&&"function"==typeof require?mb():kb(),rb.async=ac,"undefined"!=typeof window&&"object"==typeof window.__PROMISE_INSTRUMENTATION__){var hc=window.__PROMISE_INSTRUMENTATION__;c("instrument",!0);for(var ic in hc)hc.hasOwnProperty(ic)&&ob(ic,hc[ic])}var jc={race:Qb,Promise:Kb,allSettled:Pb,hash:Sb,hashSettled:Ub,denodeify:Nb,on:ob,off:pb,map:Xb,filter:$b,resolve:Yb,reject:Zb,all:Ob,rethrow:Vb,defer:Wb,EventTarget:qb,configure:c,async:nb};"function"==typeof define&&define.amd?define(function(){return jc}):"undefined"!=typeof module&&module.exports?module.exports=jc:"undefined"!=typeof this&&(this.RSVP=jc)}).call(this),function(a,b){"use strict";var c=b.head||b.getElementsByTagName("head")[0],d="basket-",e=5e3,f=[],g=function(a,b){try{return localStorage.setItem(d+a,JSON.stringify(b)),!0}catch(c){if(c.name.toUpperCase().indexOf("QUOTA")>=0){var e,f=[];for(e in localStorage)0===e.indexOf(d)&&f.push(JSON.parse(localStorage[e]));return f.length?(f.sort(function(a,b){return a.stamp-b.stamp}),basket.remove(f[0].key),g(a,b)):void 0}return}},h=function(a){var b=new RSVP.Promise(function(b,c){var d=new XMLHttpRequest;d.open("GET",a),d.onreadystatechange=function(){4===d.readyState&&(200===d.status||0===d.status&&d.responseText?b({content:d.responseText,type:d.getResponseHeader("content-type")}):c(new Error(d.statusText)))},setTimeout(function(){d.readyState<4&&d.abort()},basket.timeout),d.send()});return b},i=function(a){return h(a.url).then(function(b){var c=j(a,b);return a.skipCache||g(a.key,c),c})},j=function(a,b){var c=+new Date;return a.data=b.content,a.originalType=b.type,a.type=a.type||b.type,a.skipCache=a.skipCache||!1,a.stamp=c,a.expire=c+60*(a.expire||e)*60*1e3,a},k=function(a,b){return!a||a.expire-+new Date<0||b.unique!==a.unique||basket.isValidItem&&!basket.isValidItem(a,b)},l=function(a){var b,c,d;if(a.url)return a.key=a.key||a.url,b=basket.get(a.key),a.execute=a.execute!==!1,d=k(b,a),a.live||d?(a.unique&&(a.url+=(a.url.indexOf("?")>0?"&":"?")+"basket-unique="+a.unique),c=i(a),a.live&&!d&&(c=c.then(function(a){return a},function(){return b}))):(b.type=a.type||b.originalType,b.execute=a.execute,c=new RSVP.Promise(function(a){a(b)})),c},m=function(a){var d=b.createElement("script");d.defer=!0,d.text=a.data,c.appendChild(d)},n={"default":m},o=function(a){return a.type&&n[a.type]?n[a.type](a):n["default"](a)},p=function(a){return a.map(function(a){return a.execute&&o(a),a})},q=function(){var a,b,c=[];for(a=0,b=arguments.length;b>a;a++)c.push(l(arguments[a]));return RSVP.all(c)},r=function(){var a=q.apply(null,arguments),b=this.then(function(){return a}).then(p);return b.thenRequire=r,b};a.basket={require:function(){for(var a=0,b=arguments.length;b>a;a++)arguments[a].execute=arguments[a].execute!==!1,arguments[a].once&&f.indexOf(arguments[a].url)>=0?arguments[a].execute=!1:arguments[a].execute!==!1&&f.indexOf(arguments[a].url)<0&&f.push(arguments[a].url);var c=q.apply(null,arguments).then(p);return c.thenRequire=r,c},remove:function(a){return localStorage.removeItem(d+a),this},get:function(a){var b=localStorage.getItem(d+a);try{return JSON.parse(b||"false")}catch(c){return!1}},clear:function(a){var b,c,e=+new Date;for(b in localStorage)c=b.split(d)[1],c&&(!a||this.get(c).expire<=e)&&this.remove(c);return this},isValidItem:null,timeout:5e3,addHandler:function(a,b){Array.isArray(a)||(a=[a]),a.forEach(function(a){n[a]=b})},removeHandler:function(a){basket.addHandler(a,void 0)}},basket.clear(!0)}(this,document);
11 | // ====================================================================================================
12 | var path = 'assets/_banner-support-files/controls/';
13 |
14 | basket.require(
15 | { url: path + 'lib/create-stylesheet.min.js', unique: 'v0.2.1' },
16 | { url: path + 'lib/dragdealer.min.js', unique: 'v0.9.8' },
17 | { url: path + 'banner-controls.js', key: 'controls', unique: 'v0.1.0' },
18 | { url: path + 'banner-controls.css', key:'styles', execute: false }
19 | ).then(function(responses) {
20 | _stylesheet.appendStyleSheet(basket.get('styles').data, function(err, style) {
21 | if (err) { console.error(err); }
22 | else {
23 | var container = (document.querySelector('body').classList.contains('in-review'))? '.banner-container' : 'body';
24 | banner_controls.attachTo(container);
25 | }
26 | });
27 | });
28 |
--------------------------------------------------------------------------------