├── .babelrc
├── demo
├── context_files
│ ├── 7cf07a7ced
│ ├── share_gplus_icon-abe9b8552dcfd4bba6e00defc212b02c.png
│ ├── share_mail_icon-38578711bfb1c368b6b1d98ba54832ea.png
│ ├── share_facebook_icon-eadfd16e764c1a62d7457c5e21317afc.png
│ ├── share_twitter_icon-81aaa8b03b4dc324fef98cda701ab7ca.png
│ ├── child-reading-1-small-6a1078b2e35dfe551934a991da95d9a7.jpg
│ ├── child-reading-2-small-62e716d1fd7193378aede70aa684251d.jpg
│ ├── child-reading-3-small-d7251f13cd2a1850594158c45dbae87b.jpg
│ ├── child-reading-4-small-f74ff4eb69847b4e51d47a6f8cd01e5f.jpg
│ ├── child-reading-5-small-4b840e6bb3e6a7cc74bbff0978552310.jpg
│ ├── child-reading-6-small-d02229fcb7ebf3f2f2d7d6a366b4c3ee.jpg
│ ├── child-reading-7-small-1e45e7bb06b4e95e38a78a83d17bbe58.jpg
│ ├── child-reading-8-small-ac1a82e71f9a5f3a84f88e1f7c30aabd.jpg
│ ├── share_pinterest_icon-a1d103f1c4af9daa0118b462ad1120f2.png
│ ├── child-reading-10-small-453ed4fe1670ffd06769281ce333c323.jpg
│ ├── child-reading-11-small-b932b48beb75801bedd0686bdcc35a79.jpg
│ ├── child-reading-12-small-e484eff0f7169f82e9b6e206f678dbca.jpg
│ ├── child-reading-13-small-d73de078b3fcadcd89457190d0ce347d.jpg
│ ├── child-reading-14-small-ad8ca95699d6cccdbf71be2a9bacbbe5.jpg
│ ├── child-reading-15-small-42b19af3e0071fa35bbdf4c7fc2745ed.jpg
│ ├── iframe_api
│ ├── arrow-down-ccw-long-107x91-395794252ef42cab546eee9367537b1f.svg
│ ├── logo-white-148x35-2c2c183a239c3899f64d6830c7804c27.svg
│ ├── reviewer1-60x60-170cdd32068bb7d438e75cdf65ffb253.svg
│ ├── logo-148x35-b01f90c07210321b26ec9a5873d318cc.svg
│ ├── reviewer3-60x60-a1fdb6fec3124963ec75c637651e8951.svg
│ ├── reviewer5-60x60-a98e7bb98960eed512c973e3bd1fe868.svg
│ ├── picturefill-604f49d58f8ccbee24a50d6824ab3ed9.js
│ ├── reviewer2-60x60-94b4f7c02575eab6ebb04ee2812782e9.svg
│ ├── reviewer6-60x60-35c5fb0448ac74fa78aae224901e448f.svg
│ ├── reviewer4-60x60-bcef0cef3fddd3d7c6688b46c2377597.svg
│ ├── shared-head-1776f49ac825f6a064ce575a069c6b5f.js
│ ├── age-80x80-be860c1290f072f516be79350bd3875c.svg
│ └── shipping-80x80-e3250595ab9e529e70b8cb2b052b7566.svg
├── index.html
├── iframe.html
└── actuallytwh.json
├── src
├── js
│ ├── helpers
│ │ ├── blankLetterCheck.js
│ │ ├── isMobile.js
│ │ ├── getCentralizedCharCount.js
│ │ ├── handleReplace.js
│ │ └── preload.js
│ ├── steps
│ │ ├── generateHtml.js
│ │ ├── calculateMonkey.js
│ │ ├── generateBaseWrapperElement.js
│ │ ├── slider
│ │ │ ├── init.js
│ │ │ └── generateHtml.js
│ │ ├── spread
│ │ │ ├── insertSpread.js
│ │ │ └── getData.js
│ │ ├── insertHtml.js
│ │ ├── generatePlatformUrls.js
│ │ ├── generateBaseElement.js
│ │ ├── checkLanguageChange.js
│ │ ├── initMonkey.js
│ │ ├── generateUrls.js
│ │ ├── getData.js
│ │ └── letters
│ │ │ ├── generateHtml.js
│ │ │ ├── generateOverlay.js
│ │ │ └── generateCharPicker.js
│ ├── monkey.js
│ └── monkeys
│ │ ├── mobile.js
│ │ └── desktop.js
├── imgs
│ ├── loading-32x32.gif
│ ├── tooltip-arrow-85x47.png
│ └── arrow-3-50x35.svg
├── scss
│ ├── styles.scss
│ └── monkey
│ │ ├── _ie8.scss
│ │ ├── _book.scss
│ │ ├── _char-picker.scss
│ │ ├── _mobile.scss
│ │ └── _letters.scss
├── partials
│ └── partial.erb.html
└── en.yml
├── .eslintrc
├── .gitignore
├── .travis.yml
├── .editorconfig
├── README.md
├── test
├── tests.spec.js
├── index.html
├── _helpers.js
├── helpers.spec.js
├── desktop.spec.js
├── mobile.spec.js
└── loading.spec.js
├── package.json
└── gulpfile.js
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["es2015-lmn"]
3 | }
4 |
--------------------------------------------------------------------------------
/demo/context_files/7cf07a7ced:
--------------------------------------------------------------------------------
1 | NREUM.setToken({'stn':1})
--------------------------------------------------------------------------------
/src/js/helpers/blankLetterCheck.js:
--------------------------------------------------------------------------------
1 | module.exports = /^[\s\-\'\’]$/;
2 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./node_modules/lmn-gulp-tasks/.eslintrc"
3 | }
4 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | build
3 | demo/build
4 | demo/partials
5 | *.log
6 | .sass-cache
7 | .DS_Store
--------------------------------------------------------------------------------
/src/imgs/loading-32x32.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lostmyname/monkey/HEAD/src/imgs/loading-32x32.gif
--------------------------------------------------------------------------------
/src/imgs/tooltip-arrow-85x47.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lostmyname/monkey/HEAD/src/imgs/tooltip-arrow-85x47.png
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | sudo: false
2 | language: node_js
3 | node_js:
4 | - '4.3.2'
5 | cache:
6 | directories:
7 | - node_modules
8 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | end_of_line = lf
5 | insert_final_newline = true
6 | indent_style = space
7 | indent_size = 2
--------------------------------------------------------------------------------
/demo/context_files/share_gplus_icon-abe9b8552dcfd4bba6e00defc212b02c.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lostmyname/monkey/HEAD/demo/context_files/share_gplus_icon-abe9b8552dcfd4bba6e00defc212b02c.png
--------------------------------------------------------------------------------
/demo/context_files/share_mail_icon-38578711bfb1c368b6b1d98ba54832ea.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lostmyname/monkey/HEAD/demo/context_files/share_mail_icon-38578711bfb1c368b6b1d98ba54832ea.png
--------------------------------------------------------------------------------
/demo/context_files/share_facebook_icon-eadfd16e764c1a62d7457c5e21317afc.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lostmyname/monkey/HEAD/demo/context_files/share_facebook_icon-eadfd16e764c1a62d7457c5e21317afc.png
--------------------------------------------------------------------------------
/demo/context_files/share_twitter_icon-81aaa8b03b4dc324fef98cda701ab7ca.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lostmyname/monkey/HEAD/demo/context_files/share_twitter_icon-81aaa8b03b4dc324fef98cda701ab7ca.png
--------------------------------------------------------------------------------
/demo/context_files/child-reading-1-small-6a1078b2e35dfe551934a991da95d9a7.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lostmyname/monkey/HEAD/demo/context_files/child-reading-1-small-6a1078b2e35dfe551934a991da95d9a7.jpg
--------------------------------------------------------------------------------
/demo/context_files/child-reading-2-small-62e716d1fd7193378aede70aa684251d.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lostmyname/monkey/HEAD/demo/context_files/child-reading-2-small-62e716d1fd7193378aede70aa684251d.jpg
--------------------------------------------------------------------------------
/demo/context_files/child-reading-3-small-d7251f13cd2a1850594158c45dbae87b.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lostmyname/monkey/HEAD/demo/context_files/child-reading-3-small-d7251f13cd2a1850594158c45dbae87b.jpg
--------------------------------------------------------------------------------
/demo/context_files/child-reading-4-small-f74ff4eb69847b4e51d47a6f8cd01e5f.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lostmyname/monkey/HEAD/demo/context_files/child-reading-4-small-f74ff4eb69847b4e51d47a6f8cd01e5f.jpg
--------------------------------------------------------------------------------
/demo/context_files/child-reading-5-small-4b840e6bb3e6a7cc74bbff0978552310.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lostmyname/monkey/HEAD/demo/context_files/child-reading-5-small-4b840e6bb3e6a7cc74bbff0978552310.jpg
--------------------------------------------------------------------------------
/demo/context_files/child-reading-6-small-d02229fcb7ebf3f2f2d7d6a366b4c3ee.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lostmyname/monkey/HEAD/demo/context_files/child-reading-6-small-d02229fcb7ebf3f2f2d7d6a366b4c3ee.jpg
--------------------------------------------------------------------------------
/demo/context_files/child-reading-7-small-1e45e7bb06b4e95e38a78a83d17bbe58.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lostmyname/monkey/HEAD/demo/context_files/child-reading-7-small-1e45e7bb06b4e95e38a78a83d17bbe58.jpg
--------------------------------------------------------------------------------
/demo/context_files/child-reading-8-small-ac1a82e71f9a5f3a84f88e1f7c30aabd.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lostmyname/monkey/HEAD/demo/context_files/child-reading-8-small-ac1a82e71f9a5f3a84f88e1f7c30aabd.jpg
--------------------------------------------------------------------------------
/demo/context_files/share_pinterest_icon-a1d103f1c4af9daa0118b462ad1120f2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lostmyname/monkey/HEAD/demo/context_files/share_pinterest_icon-a1d103f1c4af9daa0118b462ad1120f2.png
--------------------------------------------------------------------------------
/demo/context_files/child-reading-10-small-453ed4fe1670ffd06769281ce333c323.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lostmyname/monkey/HEAD/demo/context_files/child-reading-10-small-453ed4fe1670ffd06769281ce333c323.jpg
--------------------------------------------------------------------------------
/demo/context_files/child-reading-11-small-b932b48beb75801bedd0686bdcc35a79.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lostmyname/monkey/HEAD/demo/context_files/child-reading-11-small-b932b48beb75801bedd0686bdcc35a79.jpg
--------------------------------------------------------------------------------
/demo/context_files/child-reading-12-small-e484eff0f7169f82e9b6e206f678dbca.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lostmyname/monkey/HEAD/demo/context_files/child-reading-12-small-e484eff0f7169f82e9b6e206f678dbca.jpg
--------------------------------------------------------------------------------
/demo/context_files/child-reading-13-small-d73de078b3fcadcd89457190d0ce347d.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lostmyname/monkey/HEAD/demo/context_files/child-reading-13-small-d73de078b3fcadcd89457190d0ce347d.jpg
--------------------------------------------------------------------------------
/demo/context_files/child-reading-14-small-ad8ca95699d6cccdbf71be2a9bacbbe5.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lostmyname/monkey/HEAD/demo/context_files/child-reading-14-small-ad8ca95699d6cccdbf71be2a9bacbbe5.jpg
--------------------------------------------------------------------------------
/demo/context_files/child-reading-15-small-42b19af3e0071fa35bbdf4c7fc2745ed.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lostmyname/monkey/HEAD/demo/context_files/child-reading-15-small-42b19af3e0071fa35bbdf4c7fc2745ed.jpg
--------------------------------------------------------------------------------
/src/scss/styles.scss:
--------------------------------------------------------------------------------
1 | @import "chameleon-sass/assets/stylesheets/chameleon";
2 | @import "heidelberg/sass/heidelberg/heidelberg-config";
3 | @import "heidelberg/sass/heidelberg/heidelberg-plugin";
4 |
5 | @import "monkey/book";
6 | @import "monkey/letters";
7 | @import "monkey/char-picker";
8 | @import "monkey/mobile";
9 | @import "monkey/ie8";
10 |
--------------------------------------------------------------------------------
/demo/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Monkey: new widget
6 |
7 |
8 | See monkey in context at partial.html, or the iframe API on iframe.html
9 | Run tests here, or without slow tests.
10 |
11 |
12 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Monkey [](https://travis-ci.org/Lostmyname/monkey)
2 |
3 | 
4 |
5 | Monkey is the new version of the widget which won't use an iframe.
6 |
7 | Use context.html for development and tests.html for testing. index.html doesn't
8 | do much.
9 |
10 | ## Installation
11 |
12 | Run `npm install` and then `gulp` to generate the CSS and start browser-sync.
13 |
--------------------------------------------------------------------------------
/src/js/helpers/isMobile.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | /**
4 | * Returns whether browser is a mobile or not. Tests for touch support and
5 | * screen width.
6 | *
7 | * @returns {boolean} True if mobile.
8 | */
9 | module.exports = function () {
10 |
11 | // If doesn't support touch
12 | if (!('ontouchstart' in window) && !navigator.msMaxTouchPoints) {
13 | return false;
14 | }
15 |
16 | if (!window.matchMedia) {
17 | return false;
18 | }
19 |
20 | return window.matchMedia('(max-width: 777px)').matches;
21 | };
22 |
--------------------------------------------------------------------------------
/test/tests.spec.js:
--------------------------------------------------------------------------------
1 | /* global $, Monkey */
2 |
3 | 'use strict';
4 |
5 | describe('Tests', function () {
6 | it('should function', function () {
7 | (10).should.be.above(2);
8 | });
9 |
10 | it('should have everything they need', function () {
11 | Monkey.should.be.type('function');
12 | $.should.be.type('function');
13 | });
14 |
15 | it('should have jqPromise test', function () {
16 | var promise = $.Deferred().promise();
17 | promise.should.be.a.jqPromise;
18 | $.should.not.be.a.jqPromise;
19 | });
20 | });
21 |
--------------------------------------------------------------------------------
/src/scss/monkey/_ie8.scss:
--------------------------------------------------------------------------------
1 | .ie8 {
2 | #letters-container {
3 | margin-bottom: 1.5em;
4 | }
5 | .overlay__buttons {
6 | .button {
7 | padding:8px 16px;
8 | }
9 |
10 | .button + .button {
11 | margin-left: 1em;
12 | }
13 | }
14 | .character-picker .char-container .img-container {
15 | &:before {
16 | display: none;
17 | }
18 | &:active:before {
19 | display: block;
20 | }
21 | }
22 | .change-character {
23 | visibility: hidden;
24 | }
25 | .letter-active .change-character {
26 | visibility: visible;
27 | }
28 | }
--------------------------------------------------------------------------------
/src/js/steps/generateHtml.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | /**
4 | * Generate HTML for pages from list of URLs.
5 | *
6 | * Varies depending on the browser.
7 | *
8 | * @todo: Templating?
9 | */
10 | module.exports = function (lang) {
11 | return function (data) {
12 | // Runs the generateHtml method, using data.monkeyType to determine whether
13 | // it should be desktop or mobile initialisation. This adds the Monkey
14 | // instance to the page, within the predefined container element.
15 | data.html = this.monkeys[data.monkeyType].generateHtml(data, lang);
16 |
17 | return data;
18 | }.bind(this);
19 | };
20 |
--------------------------------------------------------------------------------
/src/js/steps/calculateMonkey.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | /**
4 | * Decide whether future stuff should be desktop or mobile.
5 | *
6 | * @param {string} monkeyType Either "auto", "mobile" or "desktop". Defaults
7 | * to auto and will calculate whether mobile or desktop is better.
8 | */
9 | module.exports = function (monkeyType) {
10 | var isMobile = this.helpers.isMobile;
11 |
12 | return function (data) {
13 | if (typeof monkeyType === 'undefined' || monkeyType === 'auto') {
14 | data.monkeyType = isMobile() ? 'mobile' : 'desktop';
15 | } else {
16 | data.monkeyType = monkeyType;
17 | }
18 | return data;
19 | };
20 | };
21 |
--------------------------------------------------------------------------------
/demo/context_files/iframe_api:
--------------------------------------------------------------------------------
1 |
2 | if (!window['YT']) {var YT = {loading: 0,loaded: 0};}if (!window['YTConfig']) {var YTConfig = {'host': 'http://www.youtube.com'};}if (!YT.loading) {YT.loading = 1;(function(){var l = [];YT.ready = function(f) {if (YT.loaded) {f();} else {l.push(f);}};window.onYTReady = function() {YT.loaded = 1;for (var i = 0; i < l.length; i++) {try {l[i]();} catch (e) {}}};YT.setConfig = function(c) {for (var k in c) {if (c.hasOwnProperty(k)) {YTConfig[k] = c[k];}}};var a = document.createElement('script');a.id = 'www-widgetapi-script';a.src = 'https:' + '//s.ytimg.com/yts/jsbin/www-widgetapi-vflHCEcaj/www-widgetapi.js';a.async = true;var b = document.getElementsByTagName('script')[0];b.parentNode.insertBefore(a, b);})();}
--------------------------------------------------------------------------------
/src/js/steps/generateBaseWrapperElement.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | var $ = require('jquery');
3 |
4 | /**
5 | * Generate base HTML element for all of monkey.
6 | *
7 | * Varies depending on the browser.
8 | *
9 | */
10 | module.exports = function ($monkeyContainer) {
11 | return function (data) {
12 | var singleSpreadClass = data.spreads === 'single' ? 'single-spreads' : '';
13 |
14 | var classes = data.monkeyType === 'mobile' ?
15 | 'monkey-wrapper mobile ' + singleSpreadClass :
16 | 'positioned-relative pos-relative monkey-wrapper desktop';
17 |
18 | data.$monkeyWrapper = $('').addClass(classes);
19 | data.$monkeyWrapper.appendTo($monkeyContainer);
20 |
21 | return data;
22 | };
23 | };
24 |
--------------------------------------------------------------------------------
/src/js/helpers/getCentralizedCharCount.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | /**
4 | * Returns the number of characters that can be centered on the screen on
5 | * mobile devices, respective to the mobile window width.
6 | *
7 | * @returns {integer} Number of characters, defaults to 5..
8 | */
9 | module.exports = function () {
10 | var windowWidth = window.innerWidth ||
11 | document.documentElement.clientWidth ||
12 | document.body.clientWidth;
13 | // We start with a 5 character minimum, then for every 50px after 420px window
14 | // width, we add a character.
15 | var numOfChars = 5;
16 | if (window.innerWidth >= 420) {
17 | numOfChars = Math.round(parseFloat(((windowWidth - 420) / 50) + 5));
18 | }
19 | return numOfChars;
20 | };
21 |
--------------------------------------------------------------------------------
/src/js/steps/slider/init.js:
--------------------------------------------------------------------------------
1 | export default function ($events) {
2 | return function (data) {
3 | if (!data.sliderElement) {
4 | return data;
5 | }
6 |
7 | var $slider = data.sliderElement.find('input');
8 | var letters = data.letters.length;
9 | var sliderActive = false;
10 |
11 | $events.on('letterChange', function (e, page) {
12 | if (sliderActive) {
13 | return;
14 | }
15 | $slider.val(200 * page / letters);
16 | });
17 |
18 | $slider.on('mousedown', () => sliderActive = true);
19 |
20 | $slider.on('mouseup', function () {
21 | setTimeout(() => sliderActive = false, 2000);
22 | });
23 |
24 | $slider.on('input', function () {
25 | data.turnToPage(Math.round(letters * $slider.val() / 200));
26 | });
27 |
28 | return data;
29 | };
30 | }
31 |
--------------------------------------------------------------------------------
/src/js/steps/spread/insertSpread.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | /**
3 | * Inserts a new spread into the Heidelberg instance.
4 | *
5 | * @return {function}
6 | */
7 | module.exports = function () {
8 | /**
9 | * Takes the data object and the URL of the missing spread, and combines the
10 | * query string to compress/optimize the image, before adding the spread to
11 | * the Heidelberg.
12 | * @param {object} monkeyData The data object passed through the Promise chain
13 | * @param {string} spreadUrl The URL of the new spread image
14 | * @return {null}
15 | */
16 | return function (monkeyData, spreadUrl) {
17 | spreadUrl += monkeyData.queryString;
18 |
19 | monkeyData.html.find('.page-spreadMissing')
20 | .removeClass('page-spreadMissing')
21 | .addClass('page-spread')
22 | .find('img')
23 | .attr('src', spreadUrl);
24 | };
25 | };
26 |
--------------------------------------------------------------------------------
/src/js/helpers/handleReplace.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var lastReplacements = null;
4 |
5 | /**
6 | * Templating sort of? Takes a string and an object and makes replacements.
7 | *
8 | * If replacements is {foo: 'bar'}, `{{ foo }}` will be replaced with `bar`.
9 | *
10 | * @param {string} string String to do replacements on.
11 | * @param {object} [replacements] Object of replacements. If ommitted, will use
12 | * object from last time.
13 | * @returns {string} The string with replacements made.
14 | */
15 | module.exports = function (string, replacements) {
16 | if (typeof replacements !== 'object') {
17 | replacements = lastReplacements;
18 | } else {
19 | lastReplacements = replacements;
20 | }
21 |
22 | return string.replace(/\{\{\s*([a-z]+)\s*\}\}/g, function (full, item) {
23 | return replacements.hasOwnProperty(item) ? replacements[item] : full;
24 | });
25 | };
26 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "lmn-monkey",
3 | "version": "11.0.3",
4 | "description": "",
5 | "main": "src/js/monkey.js",
6 | "dependencies": {
7 | "babel-preset-es2015": "^6.5.0",
8 | "babel-preset-es2015-lmn": "^1.0.0",
9 | "babelify": "^7.2.0",
10 | "chameleon-sass": "^0.5.9",
11 | "diacritics": "^1.2.1",
12 | "heidelberg": "^1.1.0",
13 | "jquery": "^1.11.3",
14 | "lang": "^0.1.1",
15 | "nums": "^1.0.0"
16 | },
17 | "devDependencies": {
18 | "browser-sync": "^2.7.12",
19 | "gulp": "^3.8.8",
20 | "lmn-gulp-tasks": "^7.0.0",
21 | "lmn.jester.theme.default": "^1.0.0",
22 | "mocha": "^2.1.0",
23 | "phantomjs": "^1.9.16",
24 | "serve": "^1.4.0",
25 | "should": "^7.1.0"
26 | },
27 | "author": "Callum Macrae",
28 | "browserify": {
29 | "transform": [
30 | "babelify"
31 | ]
32 | },
33 | "scripts": {
34 | "test": "gulp js-quality --fail"
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/js/steps/slider/generateHtml.js:
--------------------------------------------------------------------------------
1 | import $ from 'jquery';
2 |
3 | /**
4 | * Generate HTML for slider.
5 | *
6 | * @param {object} options Options passed to monkey.
7 | */
8 | export default function (options) {
9 | return function (data) {
10 | var $sliderContainer = $('', {
11 | id: 'slider-container',
12 | class: 'row aligned-center md-mar-b',
13 | 'data-key': 'monkey-slider'
14 | });
15 |
16 | $('').appendTo($sliderContainer)
17 | .addClass('no-mar')
18 | .text(options.lang.bookFor);
19 |
20 | $('').appendTo($sliderContainer)
21 | .addClass('h5')
22 | .text(options.lang.bookTo);
23 |
24 | var $sliderInnerContainer = $('').appendTo($sliderContainer)
25 | .addClass('col col-lg-6 col-lg-offset-3 col-sm-12 col-sm-offset-0');
26 |
27 | $('', { type: 'range' })
28 | .appendTo($sliderInnerContainer);
29 |
30 | var $book = data.monkeyContainer;
31 |
32 | data.sliderElement = $sliderContainer.appendTo($book);
33 |
34 | return data;
35 | };
36 | }
37 |
--------------------------------------------------------------------------------
/src/js/steps/insertHtml.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var $ = require('jquery');
4 |
5 | /**
6 | * Inserts HTML into specified container. More specifically, it appends the
7 | * Heidelberg instance into the container element (usually .lmn-book).
8 | *
9 | * @param {string|HTMLElement|jQuery} monkeyContainer The container.
10 | */
11 | module.exports = function (monkeyContainer) {
12 | var $container = $(monkeyContainer);
13 |
14 | return function (data) {
15 | /**
16 | * We want to remove the loading gif here, and then show the 'Tap to
17 | * Preview' label. We do this because it's nonsensical to have the label
18 | * appear when there's nothing to tap.
19 | */
20 | $container.find('.loader-img').remove();
21 | $container.next('.lmn-book__label').addClass('js--show-label');
22 | $container.next('.lmn-book__label').removeAttr('style');
23 |
24 | // Append the Heidelberg to the container, and attach the container to the
25 | // data attribute that's passed through the Promise chain.
26 | $container.append(data.html);
27 |
28 | data.container = $container;
29 |
30 | return data;
31 | };
32 | };
33 |
--------------------------------------------------------------------------------
/test/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Monkey: tests
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
38 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/src/partials/partial.erb.html:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 | <%= image_tag('loading-32x32.gif', { width: 32, class: 'padded-bottom' }) %>
11 |
<%= t("loading") %>
12 |
13 |
14 |
15 |
16 | <%= t("preview") %>
17 | <%= image_tag('arrow-3-50x35.svg', { class: 'arrow-image' }) %>
18 |
19 |
20 |
--------------------------------------------------------------------------------
/src/js/steps/generatePlatformUrls.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var $ = require('jquery');
4 | /**
5 | * Generates the image URLs from Imgix, which are prepped and optimized for the
6 | * users's screen size and device resolution.
7 | *
8 | * @param {int} preload Number of images to preload on initialisation.
9 | * @return {data} The data object passed through the Promise chain.
10 | */
11 | module.exports = function (options) {
12 | var helpers = this.helpers;
13 |
14 | /**
15 | * Takes data and turns letters into URLs.
16 | */
17 | return function (data) {
18 | var width = Math.round((data.$monkeyWrapper.width() / 2) * (window.devicePixelRatio > 1 ? 2 : 1));
19 | // data.urls is a list of the raw image URLs, not resized or compressed.
20 | data.urls = $.map(data.letters, function (letterData) {
21 | var url = (letterData.type === 'static')
22 | ? `${letterData.url}?w=${width}`
23 | : `${options.server}${letterData.url}&width=${width}`;
24 |
25 | return url;
26 | });
27 |
28 | return helpers.preload(data.urls.slice(0, options.preload))
29 | .then(function () {
30 | return data;
31 | });
32 | };
33 | };
34 |
--------------------------------------------------------------------------------
/src/js/helpers/preload.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var $ = require('jquery');
4 |
5 | /**
6 | * Preload images and fire a callback / promise when they're loaded.
7 | *
8 | * @param {string|string[]} images String(s) containing URL(s) to preload.
9 | * @param {function} [callback] Optional callback. Promises are better!
10 | * @returns {promise} Promise which will be resolved when all the images
11 | * have loaded.
12 | */
13 | module.exports = function preload(images, callback) {
14 | var defer = $.Deferred();
15 |
16 | if (!$.isArray(images)) {
17 | images = [images];
18 | }
19 |
20 | var toLoad = images.length;
21 |
22 | var $images = $.map(images, function (image) {
23 | var $image = $('
').attr('src', image);
24 |
25 | $image.on('load', function () {
26 | if (--toLoad === 0) {
27 | defer.resolve($images);
28 | }
29 | });
30 |
31 | return $image;
32 | });
33 |
34 | // Convert array of jQuery objects into jQuery object
35 | $images = $($images).map(function () {
36 | return this.toArray();
37 | });
38 |
39 | if (typeof callback === 'function') {
40 | defer.then(callback);
41 | }
42 |
43 | return defer.promise();
44 | };
45 |
--------------------------------------------------------------------------------
/src/js/steps/generateBaseElement.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | /**
4 | * Generate base HTML element for all of monkey.
5 | *
6 | * Varies depending on the browser.
7 | *
8 | */
9 | module.exports = function (monkeyContainer, options) {
10 | return function (data) {
11 | // Attach a cloned version of the loading gif to the data object, so we can
12 | // use it later.
13 | data.loading = monkeyContainer.find('.loader-img').clone();
14 | // Remove all DOM elements within the container (needed for
15 | // re-initialisation when changing name in the edit screen)
16 | monkeyContainer.empty();
17 |
18 | // If replaceMonkey is true (which means a user has changed the name/gender/
19 | // language of a book on the product page) then append the loading gif to
20 | // the container – so the user has visible feedback that something is
21 | // loading in.
22 | if (options.replaceMonkey) {
23 | data.loading = data.loading.appendTo(monkeyContainer);
24 | }
25 | // Add either a desktop/mobile CSS class to the container, and attach the
26 | // container element to the data object.
27 | monkeyContainer.addClass(data.monkeyType);
28 | data.monkeyContainer = monkeyContainer;
29 |
30 | return data;
31 | };
32 | };
33 |
--------------------------------------------------------------------------------
/src/js/steps/checkLanguageChange.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | /**
4 | * Check to see whether we need to show the language overlay
5 | *
6 | * @param {string|HTMLElement|jQuery} monkeyContainer The container.
7 | */
8 | module.exports = function ($monkeyContainer, options) {
9 |
10 | // We need to check whether we have to show the language overlay, and we do
11 | // this by seeing whether the current language is different to the book's
12 | // language when it was initialized, whether we're switching between locales
13 | // which have the character picker and therefore
14 | // don't need the language overlay, and finally, if the book has characters
15 | // that have been changed before the language change. Phew.
16 | if (options.book.locale !== $monkeyContainer.data('locale')) {
17 | delete options.book.characterSelection;
18 | $monkeyContainer.data('character-selection', null);
19 | options.clearSelection = true;
20 | if (!options.showCharPicker && $monkeyContainer.data('changedChars')) {
21 | options.showLanguageOverlay = true;
22 | }
23 | $monkeyContainer.data('locale', options.book.locale);
24 | }
25 | if (options.book.locale !== $monkeyContainer.data('first-book-locale')) {
26 | delete options.book.comparisonBooks;
27 | }
28 | return options;
29 | };
30 |
--------------------------------------------------------------------------------
/gulpfile.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var browserSync = require('browser-sync');
4 | var gulp = require('gulp');
5 | var getLmnTask = require('lmn-gulp-tasks');
6 |
7 | var jsOpts = {
8 | src: './src/js/monkey.js',
9 | dest: './demo/build/bundle.js'
10 | };
11 | var jsOptsWatch = { src: jsOpts.src, dest: jsOpts.dest, watch: true };
12 |
13 | gulp.task('js', getLmnTask('browserify', jsOpts));
14 | gulp.task('js-watch', getLmnTask('browserify', jsOptsWatch));
15 |
16 | gulp.task('js-quality', getLmnTask('js-quality', {
17 | src: './src/js/**/*.js'
18 | }));
19 |
20 | gulp.task('scss', getLmnTask('scss', {
21 | src: './src/scss/*.{sass,scss}',
22 | dest: './demo/build',
23 | minify: false
24 | }));
25 |
26 | gulp.task('build', ['js', 'scss']);
27 | gulp.task('default', ['build', 'js-watch'], function () {
28 | var config = {
29 | server: {
30 | baseDir: '.'
31 | },
32 | startPath: '/demo/index.html'
33 | };
34 |
35 | browserSync.init([
36 | 'demo/build/**/*.css',
37 | 'demo/build/**/*.js',
38 | 'demo/**/*.html',
39 | 'src/imgs/**/*',
40 | 'test/**/*.js'
41 | ], config);
42 |
43 | gulp.watch('./src/scss/**/*.{sass,scss}', ['scss']);
44 | gulp.watch('./src/partials/partial.erb.html', ['html']);
45 | gulp.watch('./demo/base.erb.html', ['html']);
46 | });
47 |
--------------------------------------------------------------------------------
/test/_helpers.js:
--------------------------------------------------------------------------------
1 | /* global $, Should */
2 | /* jshint unused: false */
3 |
4 | 'use strict';
5 |
6 | Should.Assertion.add('jqPromise', function () {
7 | this.params = { operator: 'to be jQuery Deferred / Promise' };
8 |
9 | this.obj.should.be.type('object');
10 | this.obj.then.should.be.type('function');
11 | $.Deferred().then.toString().should.equal(this.obj.then.toString());
12 | }, true);
13 |
14 | Should.Assertion.add('jQuery', function () {
15 | this.params = { operator: 'to be jQuery object' };
16 |
17 | this.jquery.should.be.type('string');
18 | this.jquery.should.equal($.fn.jquery);
19 | });
20 |
21 | function changeMonkeyType(type) {
22 | return function (data) {
23 | data.monkeyType = type;
24 | return data;
25 | };
26 | }
27 |
28 | function getRandomImage() {
29 | var img = '//lmn-assets.imgix.net/widget/en-GB/v2/girl/R_Robot_s_Page_2.jpg?h=1000&dpr=1&q=60';
30 | return img.replace('1000', 200 + Math.floor(Math.random() * 100));
31 | }
32 |
33 | var options = {
34 | book: {
35 | name: 'Heidelberg',
36 | gender: 'girl',
37 | locale: 'en-GB'
38 | },
39 | clearSelection: true,
40 | lang: {
41 | bookFor: 'A personalised book made for',
42 | noAltText: 'No alternative text for this page, sorry.'
43 | },
44 | server: 'https://chameleon.lostmy.name/preview.json?callback=?',
45 | icons: true
46 | };
47 |
--------------------------------------------------------------------------------
/src/en.yml:
--------------------------------------------------------------------------------
1 | en:
2 | component.monkey:
3 | loading: We're just putting your personalised book together
4 | preview: Tap to preview your book
5 | bookFor: 'A personalised book made for'
6 | noAltText: 'No alternative text for this page, sorry.'
7 | char_picker:
8 | title:
9 | remaining: 'Choose another character for'
10 | # not_remaining: 'No %{letter} characters are available'
11 | not_remaining:
12 | part_1: 'No other'
13 | part_2: 'characters are available.'
14 | change: 'Change'
15 | buttons:
16 | close: 'Close'
17 | select: 'Select'
18 | in_use: 'In Use'
19 | overlay:
20 | nounTypes:
21 | singular:
22 | title: 'Look, new character!'
23 | intro: 'had some of the same characters in their books. Would you like to add these new ones instead?'
24 | plural:
25 | title: 'Look, new characters!'
26 | intro: 'had some of the same characters in their books. Would you like to add these new ones instead?'
27 | buttons:
28 | yes_please: 'Yes Please!'
29 | no_thanks: 'No Thanks!'
30 | language:
31 | title: "You've changed language!"
32 | copy: 'Unfortunately, the character picker is not yet available for this language'
33 | buttons:
34 | okay: "Okay, thanks!"
35 |
36 |
37 |
--------------------------------------------------------------------------------
/demo/iframe.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Lost My Name: Monkey iframe API
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |

15 |
We're just putting your personalised book together
16 |
17 |
18 |
19 |
20 |
24 |
25 |
26 |
51 |
52 |
53 |
54 |
--------------------------------------------------------------------------------
/demo/context_files/arrow-down-ccw-long-107x91-395794252ef42cab546eee9367537b1f.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/js/steps/spread/getData.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var $ = require('jquery');
4 |
5 | // @todo this needs documenting.
6 |
7 | module.exports = function (monkeyData, options) {
8 | var $monkey = monkeyData.html.find('Heidelberg-Book');
9 | var defer = $.Deferred();
10 | var resolved = false;
11 |
12 | var consts = {
13 | INITIAL_DELAY: 3000,
14 | INTERVAL: 5000
15 | };
16 |
17 | setTimeout(function () {
18 | makeRequest();
19 |
20 | var interval = setInterval(function () {
21 | if (resolved) {
22 | clearInterval(interval);
23 | return;
24 | }
25 |
26 | makeRequest();
27 | }, consts.INTERVAL);
28 | }, consts.INITIAL_DELAY);
29 |
30 | var halfDone = false;
31 |
32 | $monkey.on('scroll.monkeyPoll', function () {
33 | if (resolved) {
34 | $monkey.off('scroll.monkeyPoll');
35 | return;
36 | }
37 |
38 | if (!halfDone && $monkey.scrollLeft() > $monkey.children('div').width() / 2) {
39 | halfDone = true;
40 | makeRequest();
41 | }
42 |
43 | var A_BIT = 1500; // pixels
44 | if ($monkey.scrollLeft() > $monkey.children('div').width() - A_BIT) {
45 | $monkey.off('scroll.monkeyPoll');
46 | makeRequest();
47 | }
48 | });
49 |
50 | return defer.promise();
51 |
52 | function makeRequest() {
53 | $.getJSON(options.server, { book: options.book })
54 | .then(function (data) {
55 | $.each(data.book.letters, function (i, letter) {
56 | if (letter.type === 'spread' && letter.ready) {
57 | resolved = true;
58 | defer.resolve(monkeyData, letter.url);
59 | }
60 | });
61 | });
62 | }
63 | };
64 |
--------------------------------------------------------------------------------
/src/js/steps/initMonkey.js:
--------------------------------------------------------------------------------
1 | /* global analytics */
2 |
3 | 'use strict';
4 |
5 | /**
6 | * Initializes Monkey by adding a turnToPage method, which differs depending
7 | * whether you're on a mobile device or desktop. This function is defined in
8 | * /src/js/monkeys/mobile.js and /src/js/monkeys/desktop.js respectively.
9 | *
10 | * @param {object} $events The global Monkey events object, used to pass custom
11 | * events to it.
12 | * @param {object} options The options that Monkey was initialized with.
13 | * @return {data} The data object passed through the Promise chain.
14 | */
15 | module.exports = function ($events, options) {
16 | return function (data) {
17 | // Adds the turnToPage method, using data.monkeyType to determine whether
18 | // it should be desktop or mobile initialisation.
19 | if (options.clearSelection) {
20 | $events.trigger('clearCharSelection');
21 | }
22 | data.turnToPage = this.monkeys[data.monkeyType].init(data, $events, options);
23 |
24 | data.monkeyContainer.addClass('ready');
25 |
26 | // Track errors on image load
27 | var $images = data.monkeyContainer.find('[class*="page"] img');
28 | $images.on('error', function () {
29 | if (window.analytics) {
30 | analytics.track('Broken image in monkey', {
31 | src: this.src,
32 | pageID: this.getAttribute('data-id')
33 | });
34 | }
35 | });
36 |
37 | var remaining = $images.filter(function () {
38 | return !this.complete;
39 | }).length;
40 |
41 | $images.on('load', function () {
42 | remaining--;
43 |
44 | if (!remaining && window.analytics && options.platformAPI) {
45 | analytics.track('Monkey images all loaded');
46 | }
47 | });
48 |
49 | return data;
50 | }.bind(this);
51 | };
52 |
--------------------------------------------------------------------------------
/src/js/steps/generateUrls.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var $ = require('jquery');
4 | /**
5 | * Generates the image URLs from Imgix, which are prepped and optimized for the
6 | * users's screen size and device resolution.
7 | *
8 | * @param {int} preload Number of images to preload on initialisation.
9 | * @return {data} The data object passed through the Promise chain.
10 | */
11 | module.exports = function (options) {
12 | var monkeys = this.monkeys;
13 | var helpers = this.helpers;
14 |
15 | var quality = {
16 | desktop: 60,
17 | mobile: 60
18 | };
19 |
20 | /**
21 | * Takes data and turns letters into URLs.
22 | */
23 | return function (data) {
24 | var width;
25 | var size = monkeys[data.monkeyType].calculateSize(data);
26 | var dpr = window.devicePixelRatio > 1 ? 2 : 1;
27 |
28 | if (data.spreads === 'single') {
29 | size /= 2;
30 | }
31 |
32 | if (options.dprNotSupported) {
33 | width = 'w=' + Math.round(size * dpr);
34 | } else {
35 | width = 'w=' + size + '&dpr=' + dpr;
36 | }
37 |
38 | var queryString = width + '&q=' + quality[data.monkeyType];
39 |
40 | // @todo: Is this used?
41 | data.queryString = '?' + queryString;
42 |
43 | // data.urls is a list of the raw image URLs, not resized or compressed.
44 | data.urls = $.map(data.letters, function (letterData) {
45 | // Change the type of letter if we've not got the spread for it, so we
46 | // can try and add it at a later stage.
47 | if (letterData.type === 'spread' && !letterData.ready) {
48 | letterData.type = 'spreadMissing';
49 | data.needsSpread = true;
50 | }
51 |
52 | var separator = letterData.url.indexOf('?') === -1 ? '?' : '&';
53 | return letterData.url + separator + queryString;
54 | });
55 |
56 | return helpers.preload(data.urls.slice(0, options.preload))
57 | .then(function () {
58 | return data;
59 | });
60 | };
61 | };
62 |
--------------------------------------------------------------------------------
/src/js/steps/getData.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var $ = require('jquery');
4 |
5 | /**
6 | * Gets the data from the server.
7 | *
8 | * @returns {object} (via promise) Object with a number of properties such as
9 | * name and gender, and then a letters property containing
10 | * information on the pages. Seriously, just use a debugger.
11 | */
12 | module.exports = function (options) {
13 | var returnPromise;
14 | var url = options.serverPath
15 | ? trimTrailingSlash(options.server) + '/' + trimLeadingSlash(options.serverPath)
16 | : options.server;
17 |
18 | if (!options.platformAPI) {
19 | returnPromise = $.getJSON(options.server, { book: options.book })
20 | .then(function (data) {
21 | if (!data) {
22 | data = {};
23 | }
24 | if (!data.book) {
25 | data.book = {};
26 | }
27 | if (!data.book.spreads) {
28 | data.book.spreads = 'double';
29 | }
30 |
31 | return data.book;
32 | });
33 | } else {
34 | returnPromise = $.ajax({
35 | dataType: 'JSON',
36 | url,
37 | data: options.book
38 | })
39 | .then(function (data) {
40 | var transformedData = {
41 | book: {
42 | spreads: 'single',
43 | letters: generatePlatformURLs(data, options)
44 | }
45 | };
46 | return transformedData.book;
47 | });
48 | }
49 | return returnPromise;
50 | };
51 |
52 | function generatePlatformURLs(data, options) {
53 | var dynamicPages = data.images.map(function (image) {
54 | return {
55 | url: image.url,
56 | id: image.id,
57 | type: 'story'
58 | };
59 | });
60 |
61 | return [].concat(options.frontPages || [], dynamicPages, options.backPages || []);
62 | }
63 |
64 | function trimTrailingSlash(str) {
65 | return str.replace(/\/+$/, '');
66 | }
67 |
68 | function trimLeadingSlash(str) {
69 | return str.replace(/^\//, '');
70 | }
71 |
--------------------------------------------------------------------------------
/src/scss/monkey/_book.scss:
--------------------------------------------------------------------------------
1 | .lmn-book {
2 | margin:0 auto;
3 | max-width:1280px;
4 | }
5 |
6 | .Heidelberg-Page.is-active {
7 | .js--active-overlay & {
8 | transform: none;
9 | }
10 | }
11 |
12 | $aspect-ratio: percentage(387 / 1054);
13 |
14 | .monkey-wrapper {
15 | height:auto;
16 | padding-top: $aspect-ratio;
17 | }
18 |
19 | .monkey-overlay {
20 | @include container(700px, 20px);
21 | margin-top: -$aspect-ratio;
22 | padding-top: $aspect-ratio/4;
23 |
24 | & ~ .Heidelberg-Book {
25 | display: none;
26 | }
27 |
28 | .desktop & {
29 | margin-top: -$aspect-ratio;
30 | }
31 |
32 | .mobile & {
33 | margin-top: 0;
34 | }
35 | }
36 |
37 | .Heidelberg-Book {
38 | position: absolute;
39 | left: 0;
40 | transition: left 0.7s;
41 | top:0;
42 | width:100%;
43 |
44 | &.at-front-cover {
45 | left: -25%;
46 | .Heidelberg-Page.is-active:nth-child(odd):hover {
47 | transform: rotateY(-15deg);
48 | }
49 | }
50 |
51 | &.at-rear-cover {
52 | left: 25%;
53 | .Heidelberg-Page.is-active:nth-child(even):hover {
54 | transform: rotateY(15deg);
55 | }
56 | .Heidelberg-Page.is-active:nth-child(odd) {
57 | opacity: 0;
58 | }
59 | }
60 | }
61 |
62 | .Heidelberg-Page {
63 | .Heidelberg-HiddenCover + & {
64 | background: none;
65 | }
66 | .Heidelberg-HiddenCover + & .Heidelberg-Spread {
67 | background-image: none !important;
68 | > img {
69 | visibility: hidden !important;
70 | }
71 | }
72 |
73 | img {
74 | height: auto;
75 | width: 100%;
76 | display: block;
77 | }
78 | }
79 |
80 | .last-page {
81 | position: absolute;
82 | z-index: 2;
83 | width: 100%;
84 | > img {
85 | width: 50%;
86 | margin-left: 50%;
87 | }
88 | }
89 |
90 |
91 | .lmn-book + .italic {
92 | display: none;
93 | }
94 |
95 | @media (min-width:993px) {
96 | .lmn-book + .italic.js--show-label {
97 | display: block;
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/test/helpers.spec.js:
--------------------------------------------------------------------------------
1 | /* global $, getRandomImage, Monkey */
2 |
3 | 'use strict';
4 |
5 | describe('Monkey helpers', function () {
6 | var helpers = Monkey.helpers;
7 |
8 | it('should support string replacements', function () {
9 | var handleReplace = helpers.handleReplace;
10 | var replacements = { foo: 'bar', hello: 'world', empty: '' };
11 |
12 | var replaces = {
13 | '{{ foo }}': 'bar',
14 | ' {{hello}}': ' world',
15 | '{{ nope }}': '{{ nope }}',
16 | '{{ empty}}': '',
17 | '{ {test} }': '{ {test} }'
18 | };
19 |
20 | $.each(replaces, function (input, output) {
21 | handleReplace(input, replacements).should.equal(output);
22 | });
23 | });
24 |
25 | it('should remember previous replacements object', function () {
26 | var handleReplace = helpers.handleReplace;
27 | handleReplace('{{ foo }}').should.equal('bar');
28 | });
29 |
30 | it('should correctly detect mobile', function () {
31 | helpers.isMobile().should.be.Boolean;
32 | });
33 |
34 | it('should preload multiple images (slow)', function () {
35 | var randomImages = [getRandomImage(), getRandomImage()];
36 | return helpers.preload(randomImages)
37 | .then(function ($images) {
38 | $images.should.be.a.jqObject;
39 | $images.length.should.equal(2);
40 | $images.eq(0).attr('src').should.equal(randomImages[0]);
41 | $images.eq(1).attr('src').should.equal(randomImages[1]);
42 | });
43 | });
44 |
45 | var randomImage = getRandomImage();
46 | it('should preload single image (slow)', function () {
47 | return helpers.preload(randomImage)
48 | .then(function ($images) {
49 | $images.should.be.a.jqObject;
50 | $images.length.should.equal(1);
51 | $images.attr('src').should.equal(randomImage);
52 | });
53 | });
54 |
55 | // This is only actually slow if above tests are ommitted
56 | it('should accept callbacks (slow)', function (cb) {
57 | helpers.preload(randomImage, function ($images) {
58 | $images.should.be.a.jqObject;
59 | $images.length.should.equal(1);
60 | $images.attr('src').should.equal(randomImage);
61 | cb();
62 | });
63 | });
64 | });
65 |
--------------------------------------------------------------------------------
/src/imgs/arrow-3-50x35.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/demo/context_files/logo-white-148x35-2c2c183a239c3899f64d6830c7804c27.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/demo/context_files/reviewer1-60x60-170cdd32068bb7d438e75cdf65ffb253.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/demo/context_files/logo-148x35-b01f90c07210321b26ec9a5873d318cc.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/desktop.spec.js:
--------------------------------------------------------------------------------
1 | /* global $, Monkey, options */
2 |
3 | 'use strict';
4 |
5 | describe('Using monkey on desktop', function () {
6 | var data, monkey;
7 | var $container = $('').attr('data-key', 'lmn-book');
8 |
9 | before(function () {
10 | this.timeout(4000);
11 |
12 | monkey = new Monkey($container, {
13 | monkeyType: 'desktop',
14 | book: options.book
15 | });
16 |
17 | return monkey.promise.then(function (dataa) {
18 | $container.appendTo('body');
19 | data = dataa;
20 | });
21 | });
22 |
23 | it('should be initiated', function () {
24 | $container.children().length.should.equal(2);
25 | });
26 |
27 | it('should change page when clicked', function () {
28 | var $active = $container.find('.is-active').eq(1);
29 |
30 | $active.click();
31 |
32 | $container.find('.is-active').get(1).should.not.equal($active.get(0));
33 | });
34 |
35 | // @todo: fix in IE
36 | it('should change letters when page is changed (noIE)', function () {
37 | var letterActive = $container.find('.letter-active')[0];
38 |
39 | for (var i = 0; i < 7; i++) {
40 | $container.find('.is-active').eq(1).click();
41 | }
42 |
43 | var $newActive = $container.find('.letter-active');
44 |
45 | $newActive.eq(0).index().should.equal(3);
46 | $newActive[0].should.not.equal(letterActive);
47 | });
48 |
49 | it('should fire event when clicked', function (cb) {
50 | monkey.$events.on('pageTurn', function () { cb(); });
51 | $container.find('.is-active').eq(1).click();
52 | });
53 |
54 | after(function () {
55 | $container.remove();
56 | });
57 |
58 | describe('Special Characters for separating names', function () {
59 | var data, monkey;
60 | var $container = $('').attr('data-key', 'lmn-book');
61 |
62 | before(function () {
63 | options.book.name = 'Mary Mary-Jane';
64 | this.timeout(4000);
65 |
66 | monkey = new Monkey($container, {
67 | monkeyType: 'desktop',
68 | book: options.book,
69 | server: 'https://chameleon.lostmy.name/preview.json?callback=?',
70 | icons: true
71 | });
72 |
73 | return monkey.promise.then(function (dataa) {
74 | $container.appendTo('body');
75 | data = dataa;
76 | });
77 | });
78 |
79 | it('should not make hyphens in names clickable', function () {
80 | $container.find('.nonclickable.special-char .char:contains(-)').length.should.equal(1);
81 | });
82 |
83 | it('should not make spaces in names clickable', function () {
84 | $container.find('.nonclickable.special-char .char:contains( )').length.should.equal(1);
85 | });
86 |
87 | after(function () {
88 | $container.remove();
89 | });
90 | });
91 | });
92 |
--------------------------------------------------------------------------------
/src/scss/monkey/_char-picker.scss:
--------------------------------------------------------------------------------
1 | .picker-container {
2 | margin: 0 auto;
3 | }
4 | .character-picker {
5 | display: block;
6 | margin-top:28px;
7 | padding: 15px 15px 0 15px;
8 | background-color: #FFF;
9 | border: 2px solid #e6e6e6;
10 | border-radius: 5px;
11 | box-shadow:0px 1px 2px 1px rgba(0,0,0,0.2);
12 | font-weight: normal;
13 | z-index: 12000000000;
14 | color: #707070;
15 | min-width: 310px;
16 | opacity: 0;
17 | top:100%;
18 | left:50%;
19 | transform:scale(1.05);
20 | transition: visibility 0s ease .4s, opacity .4s ease, transform .4s ease;
21 | visibility: hidden;
22 |
23 | .tooltip-arrow {
24 | left:50%;
25 | margin-top: -32px;
26 | height: auto;
27 | width: 30px;
28 | position: absolute;
29 | margin-left: -15px;
30 | }
31 | .char-container {
32 | border-top: 1px solid #d8d4c7;
33 | .img-container {
34 | background: none;
35 | border: none;
36 | display: block;
37 | padding: 15px 0;
38 | position: relative;
39 | text-align: left;
40 | height: 70px;
41 | width:100%;
42 | -webkit-tap-highlight-color: rgba(0,0,0,0);
43 | -webkit-tap-highlight-color: transparent;
44 |
45 | &:focus,
46 | &:active {
47 | outline: none;
48 | }
49 |
50 | &:before {
51 | background: #f1f1f1;
52 | border-radius:4px;
53 | bottom:8px;
54 | content:"";
55 | left:-8px;
56 | opacity:0;
57 | position: absolute;
58 | right:-8px;
59 | top:8px;
60 | transition: .25s ease;
61 | }
62 |
63 | &:active,
64 | &:focus {
65 | &:before {
66 | opacity:1;
67 | }
68 | }
69 | }
70 | .img-container + .img-container {
71 | border-top:1px solid #d8d4c7;
72 | }
73 | img {
74 | height: 40px;
75 | border-radius: 5px;
76 | margin-right: 10px;
77 | position: absolute;
78 | border: 1px solid #d8d4c7;
79 | margin-top: -20px;
80 | left:0;
81 | top:50%;
82 | width:40px;
83 | }
84 | img:last-child {
85 | margin-right: 0;
86 | }
87 | .character-name {
88 | font-size: 1.1rem;
89 | padding: 0 100px 0 50px;
90 | position: relative;
91 | text-transform: uppercase;
92 | }
93 | .button {
94 | font-size: 0.8rem;
95 | margin:-15px 0 0;
96 | padding:8px;
97 | padding: .5rem 0.5rem;
98 | position: absolute;
99 | right:0;
100 | top:50%;
101 | }
102 | }
103 | .title {
104 | color: #f06362;
105 | font-family: "freight-text-pro", serif;
106 | font-style: italic;
107 | font-size: 18px;
108 | padding:4px 0 16px;
109 | }
110 | hr {
111 | width: 100%;
112 | height: 1px;
113 | }
114 | }
115 | .character-picker.active {
116 | display: inline;
117 | }
118 |
119 | .character-picker--active {
120 | opacity: 1;
121 | transform: none;
122 | transition-delay:0s, 0s, 0s;
123 | visibility: visible;
124 | .tooltip-arrow {
125 | display: block;
126 | }
127 | }
--------------------------------------------------------------------------------
/test/mobile.spec.js:
--------------------------------------------------------------------------------
1 | /* global $, Monkey, options */
2 |
3 | 'use strict';
4 |
5 | describe('Using monkey on mobile', function () {
6 | var $monkey, monkey;
7 | var $container = $('').attr('data-key', 'lmn-book');
8 |
9 | before(function () {
10 | monkey = new Monkey($container, {
11 | monkeyType: 'mobile',
12 | book: {
13 | name: 'Tal',
14 | gender: 'boy',
15 | locale: 'en-GB'
16 | }
17 | });
18 |
19 | return monkey.promise.then(function (data) {
20 | data.html.trigger('touchstart');
21 |
22 | $monkey = $container.find('.monkey-wrapper');
23 | $container.appendTo('body');
24 | });
25 | });
26 |
27 | it('should be initiated', function () {
28 | $container.children().length.should.equal(2);
29 | });
30 |
31 | it('should scroll', function () {
32 | $monkey.scrollLeft(100);
33 | $monkey.scrollLeft().should.equal(100);
34 | });
35 |
36 | it('should change letters when page is changed', function (done) {
37 | $monkey
38 | .scrollLeft($monkey.find('.landscape-images-inner').width() / 2)
39 | .trigger('scroll');
40 |
41 | setTimeout(function () {
42 | $container.find('.letter-active').index().should.be.within(2, 4);
43 | done();
44 | }, 300);
45 | });
46 |
47 | it('should fire event when scrolled', function (cb) {
48 | this.timeout(500); // If it isn't fired in this time, it won't be
49 | monkey.$events.on('halfway', function () {
50 | cb();
51 | });
52 | $monkey.scrollLeft($monkey.find('.landscape-images-inner').width() / 1.5).trigger('scroll');
53 | });
54 |
55 | after(function () {
56 | $container.remove();
57 | });
58 | });
59 |
60 | describe('Using TJH monkey on mobile', function () {
61 | var $monkey, monkey, monkeyData;
62 | var $container = $('')
63 | .attr('id', 'monkey')
64 | .attr('data-key', 'tjh-book');
65 |
66 | before(function () {
67 | monkey = new Monkey($container, {
68 | monkeyType: 'mobile',
69 | letters: false,
70 | platformAPI: true,
71 | server: 'https://prod1-platform.lostmy.name',
72 | serverPath: 'product-builder/tjh/images',
73 | book: {
74 | name: 'Tal',
75 | gender: 'boy',
76 | locale: 'en-gb',
77 | inscription: 'test',
78 | country_code: 'gb', // eslint-disable-line camelcase
79 | address: '',
80 | door_number: '10', // eslint-disable-line camelcase
81 | lat: '51.171223',
82 | lng: '-1.789289',
83 | phototype: 'type-ii'
84 | }
85 | });
86 |
87 | return monkey.promise.then(function (data) {
88 | data.animateToPage = true;
89 | data.html.trigger('touchstart');
90 | monkeyData = data;
91 | $monkey = $container.find('.monkey-wrapper');
92 | $container.appendTo('body');
93 | });
94 | });
95 |
96 | it('should be initiated', function () {
97 | $container.children().length.should.equal(1);
98 | });
99 |
100 | it('should have the animateToPage value set to true', function () {
101 | monkeyData.animateToPage.should.equal(true);
102 | });
103 |
104 | it('should change the page with the turnToPage function', function (done) {
105 | monkey.turnToPage(5);
106 | setTimeout(function () {
107 | if ($monkey.scrollLeft() > 0) {
108 | done();
109 | }
110 | }, 500);
111 | });
112 | });
113 |
--------------------------------------------------------------------------------
/src/scss/monkey/_mobile.scss:
--------------------------------------------------------------------------------
1 | .tooltip-arrow {
2 | display: none;
3 | }
4 |
5 | .monkey.mobile {
6 | overflow: visible;
7 | }
8 |
9 | .monkey-wrapper.single-spreads .page:nth-child(odd) {
10 | margin-right: 10px;
11 | }
12 |
13 | .monkey-wrapper.mobile {
14 | -webkit-overflow-scrolling: touch;
15 | overflow: scroll;
16 | padding:0;
17 | &::-webkit-scrollbar {
18 | display:none;
19 | }
20 | &.portrait {
21 | margin-bottom: -5px;
22 | }
23 | }
24 | .mobile.monkey.monkey-icons {
25 | #letters {
26 | margin-left: -250px;
27 | width: 700px;
28 | }
29 | .letter-spans {
30 | height: 94px;
31 | overflow-x: scroll;
32 | }
33 | }
34 |
35 | @keyframes name-agitator {
36 | 0% {
37 | transform: translateX(-8px);
38 | }
39 | 6% {
40 | transform: translateX(0);
41 | }
42 | 12% {
43 | transform: translateX(-8px);
44 | }
45 | 18% {
46 | transform: translateX(0);
47 | }
48 | 94% {
49 | transform: translateX(0);
50 | }
51 | 100% {
52 | transform: translateX(-8px);
53 | }
54 | }
55 | .letter-spans--agitated {
56 | animation: name-agitator 3s .2s infinite ease;
57 | }
58 |
59 | .landscape .lmn-book {
60 | position: fixed;
61 | top: 0;
62 | left: 0;
63 | right: 0;
64 | bottom: 0;
65 | z-index: 40;
66 | padding-top: 0;
67 | margin-top: 0;
68 | background-color: #f6f5f1;
69 | }
70 |
71 | .landscape-images {
72 | .landscape-images-inner {
73 | vertical-align: top;
74 | clear: both;
75 | }
76 | }
77 |
78 | .page {
79 | position: relative;
80 | float: left;
81 | min-width: 490px;
82 | padding-right: 10px;
83 | overflow: hidden;
84 |
85 | .landscape & {
86 | background: #333333;
87 | }
88 | }
89 |
90 | .page-first {
91 | padding-right: 0;
92 | margin-right: 10px;
93 |
94 | img {
95 | position: relative;
96 | left: -100%;
97 | }
98 | }
99 |
100 | .page-halfwidth {
101 | min-width: 245px;
102 | }
103 |
104 | #letters-container {
105 | margin-left:0;
106 | margin-right: 0;
107 | }
108 |
109 | // Mobile overlay
110 |
111 | .js--active-overlay {
112 | &.monkey.mobile {
113 | .landscape-images {
114 | display: none;
115 | }
116 | .overlay {
117 | height:auto;
118 | position: relative;
119 | }
120 | .overlay__inner {
121 | position: relative;
122 | }
123 | }
124 | }
125 |
126 | // Mobile Char Picker
127 |
128 | .mobile .picker-container {
129 | position: relative;
130 | }
131 | .mobile {
132 | .character-picker {
133 | left:2.5%;
134 | margin-top: -4px;
135 | min-width:0;
136 | top:0;
137 | width:95%;
138 | }
139 | .character-picker--no-arrow {
140 | margin-top:-24px;
141 |
142 | .tooltip-arrow {
143 | display: none;
144 | }
145 | }
146 | }
147 |
148 | .picker-container__bg {
149 | background:black;
150 | height:100%;
151 | left:0;
152 | opacity:0;
153 | position: fixed;
154 | top:0;
155 | transition: visibility 0s ease .2s, opacity .2s ease;
156 | visibility: hidden;
157 | width:100%;
158 | z-index:3;
159 | }
160 |
161 | .picker-container__bg--active {
162 | opacity: .6;
163 | transition-delay:0s, 0s;
164 | visibility: visible;
165 | }
166 |
167 | .letter-spans {
168 | //overflow: hidden !important;
169 | }
170 |
171 | .character-picker__close {
172 | left:0;
173 | margin-top: 16px;
174 | position: absolute;
175 | top:100%;
176 | width:100%;
177 | }
178 |
--------------------------------------------------------------------------------
/demo/context_files/reviewer3-60x60-a1fdb6fec3124963ec75c637651e8951.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/demo/context_files/reviewer5-60x60-a98e7bb98960eed512c973e3bd1fe868.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/demo/context_files/picturefill-604f49d58f8ccbee24a50d6824ab3ed9.js:
--------------------------------------------------------------------------------
1 | window.matchMedia||(window.matchMedia=function(){"use strict";var e=window.styleMedia||window.media;if(!e){var t=document.createElement("style"),r=document.getElementsByTagName("script")[0],n=null;t.type="text/css",t.id="matchmediajs-test",r.parentNode.insertBefore(t,r),n="getComputedStyle"in window&&window.getComputedStyle(t,null)||t.currentStyle,e={matchMedium:function(e){var r="@media "+e+"{ #matchmediajs-test { width: 1px; } }";return t.styleSheet?t.styleSheet.cssText=r:t.textContent=r,"1px"===n.width}}}return function(t){return{matches:e.matchMedium(t||"all"),media:t||"all"}}}()),function(e,t){"use strict";function r(e){var t,r,n,s,o,a=e||{};t=a.elements||i.getAllElements();for(var c=0,l=t.length;l>c;c++)if(r=t[c],n=r.parentNode,s=void 0,o=void 0,r[i.ns]||(r[i.ns]={}),a.reevaluate||!r[i.ns].evaluated){if("PICTURE"===n.nodeName.toUpperCase()){if(i.removeVideoShim(n),s=i.getMatch(r,n),s===!1)continue}else s=void 0;("PICTURE"===n.nodeName.toUpperCase()||r.srcset&&!i.srcsetSupported||!i.sizesSupported&&r.srcset&&r.srcset.indexOf("w")>-1)&&i.dodgeSrcset(r),s?(o=i.processSourceSet(s),i.applyBestCandidate(o,r)):(o=i.processSourceSet(r),(void 0===r.srcset||r[i.ns].srcset)&&i.applyBestCandidate(o,r)),r[i.ns].evaluated=!0}}function n(){r();var n=setInterval(function(){return r(),/^loaded|^i|^c/.test(t.readyState)?void clearInterval(n):void 0},250);if(e.addEventListener){var i;e.addEventListener("resize",function(){e._picturefillWorking||(e._picturefillWorking=!0,e.clearTimeout(i),i=e.setTimeout(function(){r({reevaluate:!0}),e._picturefillWorking=!1},60))},!1)}}if(e.HTMLPictureElement)return void(e.picturefill=function(){});t.createElement("picture");var i={};i.ns="picturefill",i.srcsetSupported="srcset"in t.createElement("img"),i.sizesSupported=e.HTMLImageElement.sizes,i.trim=function(e){return e.trim?e.trim():e.replace(/^\s+|\s+$/g,"")},i.endsWith=function(e,t){return e.endsWith?e.endsWith(t):-1!==e.indexOf(t,e.length-t.length)},i.matchesMedia=function(t){return e.matchMedia&&e.matchMedia(t).matches},i.getDpr=function(){return e.devicePixelRatio||1},i.getWidthFromLength=function(e){return e=e&&(parseFloat(e)>0||e.indexOf("calc(")>-1)?e:"100vw",e=e.replace("vw","%"),i.lengthEl||(i.lengthEl=t.createElement("div"),t.documentElement.insertBefore(i.lengthEl,t.documentElement.firstChild)),i.lengthEl.style.cssText="position: absolute; left: 0; width: "+e+";",i.lengthEl.offsetWidth<=0&&(i.lengthEl.style.cssText="width: 100%;"),i.lengthEl.offsetWidth},i.types={},i.types["image/jpeg"]=!0,i.types["image/gif"]=!0,i.types["image/png"]=!0,i.types["image/svg+xml"]=t.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#Image","1.1"),i.types["image/webp"]=function(){var t=new e.Image,n="image/webp";t.onerror=function(){i.types[n]=!1,r()},t.onload=function(){i.types[n]=1===t.width,r()},t.src="data:image/webp;base64,UklGRh4AAABXRUJQVlA4TBEAAAAvAAAAAAfQ//73v/+BiOh/AAA="},i.verifyTypeSupport=function(e){var t=e.getAttribute("type");return null===t||""===t?!0:"function"==typeof i.types[t]?(i.types[t](),"pending"):i.types[t]},i.parseSize=function(e){var t=/(\([^)]+\))?\s*(.+)/g.exec(e);return{media:t&&t[1],length:t&&t[2]}},i.findWidthFromSourceSize=function(e){for(var t,r=i.trim(e).split(/\s*,\s*/),n=0,s=r.length;s>n;n++){var o=r[n],a=i.parseSize(o),c=a.length,l=a.media;if(c&&(!l||i.matchesMedia(l))){t=c;break}}return i.getWidthFromLength(t)},i.parseSrcset=function(e){for(var t=[];""!==e;){e=e.replace(/^\s+/g,"");var r,n=e.search(/\s/g),i=null;if(-1!==n){r=e.slice(0,n);var s=r[r.length-1];if((","===s||""===r)&&(r=r.replace(/,+$/,""),i=""),e=e.slice(n+1),null===i){var o=e.indexOf(",");-1!==o?(i=e.slice(0,o),e=e.slice(o+1)):(i=e,e="")}}else r=e,e="";(r||i)&&t.push({url:r,descriptor:i})}return t},i.parseDescriptor=function(e,t){var r,n=t||"100vw",s=e&&e.replace(/(^\s+|\s+$)/g,""),o=i.findWidthFromSourceSize(n);if(s)for(var a=s.split(" "),c=a.length+1;c>=0;c--)if(void 0!==a[c]){var l=a[c],u=l&&l.slice(l.length-1);if("h"!==u&&"w"!==u||i.sizesSupported){if("x"===u){var d=l&&parseFloat(l,10);r=d&&!isNaN(d)?d:1}}else r=parseFloat(parseInt(l,10)/o)}return r||1},i.getCandidatesFromSourceSet=function(e,t){for(var r=i.parseSrcset(e),n=[],s=0,o=r.length;o>s;s++){var a=r[s];n.push({url:a.url,resolution:i.parseDescriptor(a.descriptor,t)})}return n},i.dodgeSrcset=function(e){e.srcset&&(e[i.ns].srcset=e.srcset,e.removeAttribute("srcset"))},i.processSourceSet=function(e){var t=e.getAttribute("srcset"),r=e.getAttribute("sizes"),n=[];return"IMG"===e.nodeName.toUpperCase()&&e[i.ns]&&e[i.ns].srcset&&(t=e[i.ns].srcset),t&&(n=i.getCandidatesFromSourceSet(t,r)),n},i.applyBestCandidate=function(e,t){var r,n,s;e.sort(i.ascendingSort),n=e.length,s=e[n-1];for(var o=0;n>o;o++)if(r=e[o],r.resolution>=i.getDpr()){s=r;break}s&&!i.endsWith(t.src,s.url)&&(t.src=s.url,t.currentSrc=t.src)},i.ascendingSort=function(e,t){return e.resolution-t.resolution},i.removeVideoShim=function(e){var t=e.getElementsByTagName("video");if(t.length){for(var r=t[0],n=r.getElementsByTagName("source");n.length;)e.insertBefore(n[0],r);r.parentNode.removeChild(r)}},i.getAllElements=function(){for(var e=[],r=t.getElementsByTagName("img"),n=0,s=r.length;s>n;n++){var o=r[n];("PICTURE"===o.parentNode.nodeName.toUpperCase()||null!==o.getAttribute("srcset")||o[i.ns]&&null!==o[i.ns].srcset)&&e.push(o)}return e},i.getMatch=function(e,t){for(var r,n=t.childNodes,s=0,o=n.length;o>s;s++){var a=n[s];if(1===a.nodeType){if(a===e)return r;if("SOURCE"===a.nodeName.toUpperCase()){null!==a.getAttribute("src")&&void 0!==typeof console&&console.warn("The `src` attribute is invalid on `picture` `source` element; instead, use `srcset`.");var c=a.getAttribute("media");if(a.getAttribute("srcset")&&(!c||i.matchesMedia(c))){var l=i.verifyTypeSupport(a);if(l===!0){r=a;break}if("pending"===l)return!1}}}}return r},n(),r._=i,"object"==typeof module&&"object"==typeof module.exports?module.exports=r:"function"==typeof define&&define.amd?define(function(){return r}):"object"==typeof e&&(e.picturefill=r)}(this,this.document);
--------------------------------------------------------------------------------
/demo/actuallytwh.json:
--------------------------------------------------------------------------------
1 | {
2 | "book":{
3 | "spreads": "single",
4 | "letters":[
5 | {
6 | "url":"//twh-playground-staging.imgix.net/e9c0b8f1-d528-4186-ab88-548c5dbe8947.pdf?page=3",
7 | "type":"story"
8 | },
9 | {
10 | "url":"//twh-playground-staging.imgix.net/e9c0b8f1-d528-4186-ab88-548c5dbe8947.pdf?page=1",
11 | "type":"story"
12 | },
13 | {
14 | "url":"//twh-playground-staging.imgix.net/e9c0b8f1-d528-4186-ab88-548c5dbe8947.pdf?page=2",
15 | "type":"story"
16 | },
17 | {
18 | "url":"//twh-playground-staging.imgix.net/e9c0b8f1-d528-4186-ab88-548c5dbe8947.pdf?page=3",
19 | "type":"story"
20 | },
21 | {
22 | "url":"//twh-playground-staging.imgix.net/e9c0b8f1-d528-4186-ab88-548c5dbe8947.pdf?page=4",
23 | "type":"story"
24 | },
25 | {
26 | "url":"//twh-playground-staging.imgix.net/e9c0b8f1-d528-4186-ab88-548c5dbe8947.pdf?page=5",
27 | "type":"story"
28 | },
29 | {
30 | "url":"//twh-playground-staging.imgix.net/e9c0b8f1-d528-4186-ab88-548c5dbe8947.pdf?page=6",
31 | "type":"story"
32 | },
33 | {
34 | "url":"//twh-playground-staging.imgix.net/e9c0b8f1-d528-4186-ab88-548c5dbe8947.pdf?page=7",
35 | "type":"story"
36 | },
37 | {
38 | "url":"//twh-playground-staging.imgix.net/e9c0b8f1-d528-4186-ab88-548c5dbe8947.pdf?page=8",
39 | "type":"story"
40 | },
41 | {
42 | "url":"//twh-playground-staging.imgix.net/e9c0b8f1-d528-4186-ab88-548c5dbe8947.pdf?page=9",
43 | "type":"story"
44 | },
45 | {
46 | "url":"//twh-playground-staging.imgix.net/e9c0b8f1-d528-4186-ab88-548c5dbe8947.pdf?page=10",
47 | "type":"story"
48 | },
49 | {
50 | "url":"//twh-playground-staging.imgix.net/e9c0b8f1-d528-4186-ab88-548c5dbe8947.pdf?page=11",
51 | "type":"story"
52 | },
53 | {
54 | "url":"//twh-playground-staging.imgix.net/e9c0b8f1-d528-4186-ab88-548c5dbe8947.pdf?page=12",
55 | "type":"story"
56 | },
57 | {
58 | "url":"//twh-playground-staging.imgix.net/e9c0b8f1-d528-4186-ab88-548c5dbe8947.pdf?page=13",
59 | "type":"story"
60 | },
61 | {
62 | "url":"//twh-playground-staging.imgix.net/e9c0b8f1-d528-4186-ab88-548c5dbe8947.pdf?page=14",
63 | "type":"story"
64 | },
65 | {
66 | "url":"//twh-playground-staging.imgix.net/e9c0b8f1-d528-4186-ab88-548c5dbe8947.pdf?page=15",
67 | "type":"story"
68 | },
69 | {
70 | "url":"//twh-playground-staging.imgix.net/e9c0b8f1-d528-4186-ab88-548c5dbe8947.pdf?page=16",
71 | "type":"story"
72 | },
73 | {
74 | "url":"//twh-playground-staging.imgix.net/e9c0b8f1-d528-4186-ab88-548c5dbe8947.pdf?page=17",
75 | "type":"story"
76 | },
77 | {
78 | "url":"//twh-playground-staging.imgix.net/e9c0b8f1-d528-4186-ab88-548c5dbe8947.pdf?page=18",
79 | "type":"story"
80 | },
81 | {
82 | "url":"//twh-playground-staging.imgix.net/e9c0b8f1-d528-4186-ab88-548c5dbe8947.pdf?page=19",
83 | "type":"story"
84 | },
85 | {
86 | "url":"//twh-playground-staging.imgix.net/e9c0b8f1-d528-4186-ab88-548c5dbe8947.pdf?page=20",
87 | "type":"story"
88 | },
89 | {
90 | "url":"//twh-playground-staging.imgix.net/e9c0b8f1-d528-4186-ab88-548c5dbe8947.pdf?page=21",
91 | "type":"story"
92 | },
93 | {
94 | "url":"//twh-playground-staging.imgix.net/e9c0b8f1-d528-4186-ab88-548c5dbe8947.pdf?page=22",
95 | "type":"story"
96 | },
97 | {
98 | "url":"//twh-playground-staging.imgix.net/e9c0b8f1-d528-4186-ab88-548c5dbe8947.pdf?page=23",
99 | "type":"story"
100 | },
101 | {
102 | "url":"//twh-playground-staging.imgix.net/e9c0b8f1-d528-4186-ab88-548c5dbe8947.pdf?page=24",
103 | "type":"story"
104 | },
105 | {
106 | "url":"//twh-playground-staging.imgix.net/e9c0b8f1-d528-4186-ab88-548c5dbe8947.pdf?page=25",
107 | "type":"story"
108 | },
109 | {
110 | "url":"//twh-playground-staging.imgix.net/e9c0b8f1-d528-4186-ab88-548c5dbe8947.pdf?page=26",
111 | "type":"story"
112 | },
113 | {
114 | "url":"//twh-playground-staging.imgix.net/e9c0b8f1-d528-4186-ab88-548c5dbe8947.pdf?page=27",
115 | "type":"story"
116 | },
117 | {
118 | "url":"//twh-playground-staging.imgix.net/e9c0b8f1-d528-4186-ab88-548c5dbe8947.pdf?page=28",
119 | "type":"story"
120 | },
121 | {
122 | "url":"//twh-playground-staging.imgix.net/e9c0b8f1-d528-4186-ab88-548c5dbe8947.pdf?page=29",
123 | "type":"story"
124 | },
125 | {
126 | "url":"//twh-playground-staging.imgix.net/e9c0b8f1-d528-4186-ab88-548c5dbe8947.pdf?page=30",
127 | "type":"story"
128 | },
129 | {
130 | "url":"//twh-playground-staging.imgix.net/e9c0b8f1-d528-4186-ab88-548c5dbe8947.pdf?page=31",
131 | "type":"story"
132 | },
133 | {
134 | "url":"//twh-playground-staging.imgix.net/e9c0b8f1-d528-4186-ab88-548c5dbe8947.pdf?page=32",
135 | "type":"story"
136 | },
137 | {
138 | "url":"//twh-playground-staging.imgix.net/e9c0b8f1-d528-4186-ab88-548c5dbe8947.pdf?page=33",
139 | "type":"story"
140 | },
141 | {
142 | "url":"//twh-playground-staging.imgix.net/e9c0b8f1-d528-4186-ab88-548c5dbe8947.pdf?page=34",
143 | "type":"story"
144 | },
145 | {
146 | "url":"//twh-playground-staging.imgix.net/e9c0b8f1-d528-4186-ab88-548c5dbe8947.pdf?page=35",
147 | "type":"story"
148 | },
149 | {
150 | "url":"//twh-playground-staging.imgix.net/e9c0b8f1-d528-4186-ab88-548c5dbe8947.pdf?page=36",
151 | "type":"story"
152 | },
153 | {
154 | "url":"//twh-playground-staging.imgix.net/e9c0b8f1-d528-4186-ab88-548c5dbe8947.pdf?page=37",
155 | "type":"story"
156 | }
157 | ]
158 | }
159 | }
160 |
--------------------------------------------------------------------------------
/demo/context_files/reviewer2-60x60-94b4f7c02575eab6ebb04ee2812782e9.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/demo/context_files/reviewer6-60x60-35c5fb0448ac74fa78aae224901e448f.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/js/monkey.js:
--------------------------------------------------------------------------------
1 | (function (Monkey) {
2 | if (typeof module !== 'undefined' && module.exports) {
3 | module.exports = Monkey;
4 | }
5 |
6 | if (typeof window !== 'undefined') {
7 | window.Monkey = Monkey;
8 | }
9 | })((function () {
10 | var $ = require('jquery');
11 | var lang = require('lang');
12 |
13 | /**
14 | * Initiate monkey; generate it, and then insert it into the page.
15 | *
16 | * @param {string|HTMLElement|jQuery} monkeyContainer Container for monkey.
17 | * @param {object} options Options! Read the code. Sorry.
18 | */
19 | function Monkey(monkeyContainer, options) {
20 | var $monkeyContainer = $(monkeyContainer);
21 |
22 | this.options = options = $.extend({
23 | preload: 3, // Number of pages to preload
24 | letters: true, // Display letters? true, false, or selector
25 | slider: false, // Display slider? true, false, or selector
26 | icons: $monkeyContainer.data('icons'), // Display character icons under letters? true, false
27 | monkeyType: 'auto', // auto, desktop, mobile
28 | animateName: true,
29 | perPage: 4,
30 | replaceMonkey: false,
31 | canClose: false,
32 | showCharPicker: $monkeyContainer.data('show-picker'),
33 | showOverlay: $monkeyContainer.data('show-overlay'),
34 | server: 'https://chameleon.lostmy.name/preview.json?callback=?',
35 | dprNotSupported: false,
36 |
37 | book: {
38 | name: $monkeyContainer.data('name'),
39 | gender: $monkeyContainer.data('gender'),
40 | locale: $monkeyContainer.data('locale'),
41 | characterSelection: $monkeyContainer.data('character-selection')
42 | },
43 |
44 | lang: {
45 | bookFor: lang('monkey.bookFor'),
46 | noAltText: lang('monkey.noAltText')
47 | }
48 | }, options);
49 |
50 | if ($monkeyContainer.data('first-book-name')) {
51 | options.book.comparisonBooks = [
52 | {
53 | name: $monkeyContainer.data('first-book-name'),
54 | gender: $monkeyContainer.data('first-book-gender'),
55 | locale: $monkeyContainer.data('first-book-locale'),
56 | characterSelection: $monkeyContainer.data('first-book-character-selection')
57 | }
58 | ];
59 | }
60 | this.$events = $({});
61 |
62 | var promise = Monkey._getData(options)
63 | .then(Monkey._calculateMonkey(options.monkeyType))
64 | .then(Monkey._checkLanguageChange($monkeyContainer, options))
65 | .then((data) => {
66 | data.monkeyContainer = $monkeyContainer;
67 | return data;
68 | });
69 |
70 | if (options.letters || this.options.platformAPI) {
71 | promise = promise
72 | .then(Monkey._generateBaseElement($monkeyContainer, options));
73 | }
74 |
75 | promise = promise
76 | .then(Monkey._generateBaseWrapperElement($monkeyContainer, options));
77 |
78 | var generateUrls = (this.options.platformAPI)
79 | ? Monkey._generatePlatformUrls(options)
80 | : Monkey._generateUrls(options);
81 |
82 | promise = promise
83 | .then(generateUrls)
84 | .then(Monkey._generateHtml(options.lang));
85 |
86 | if (options.letters) {
87 | promise = promise
88 | .then(Monkey.letters._generateHtml(options));
89 |
90 | if (options.showCharPicker) {
91 | promise = promise
92 | .then(Monkey.letters._generateCharPicker(
93 | options,
94 | $monkeyContainer
95 | )
96 | );
97 | }
98 | promise = promise.then(Monkey.letters._init(this.$events, options, $monkeyContainer));
99 | }
100 |
101 | if (options.slider) {
102 | promise = promise
103 | .then(Monkey.slider._generateHtml(options))
104 | .then(Monkey.slider._init(this.$events));
105 | }
106 |
107 | promise = promise
108 | .then(Monkey._insertHtml($monkeyContainer))
109 | .then(Monkey._initMonkey(this.$events, options));
110 |
111 | if (options.showOverlay) {
112 | promise = promise
113 | .then(Monkey.letters._generateOverlay(options, this.$events));
114 | }
115 |
116 | promise = promise
117 | .then((data) => {
118 | // exposed turnToPage method
119 | this.turnToPage = data.turnToPage;
120 |
121 | if (data.needsSpread) {
122 | Monkey.spread._getData(data, options)
123 | .then(Monkey.spread._insertSpread());
124 | }
125 |
126 | return data;
127 | });
128 |
129 |
130 | this.promise = promise;
131 | }
132 |
133 | Monkey._getData = require('./steps/getData');
134 | Monkey._generateBaseElement = require('./steps/generateBaseElement');
135 | Monkey._generateBaseWrapperElement = require('./steps/generateBaseWrapperElement');
136 | Monkey._calculateMonkey = require('./steps/calculateMonkey');
137 | Monkey._generateUrls = require('./steps/generateUrls');
138 | Monkey._generatePlatformUrls = require('./steps/generatePlatformUrls');
139 | Monkey._generateHtml = require('./steps/generateHtml');
140 | Monkey._insertHtml = require('./steps/insertHtml');
141 | Monkey._initMonkey = require('./steps/initMonkey');
142 | Monkey._checkLanguageChange = require('./steps/checkLanguageChange');
143 |
144 | Monkey.spread = {};
145 | Monkey.spread.Monkey = Monkey;
146 | Monkey.spread._getData = require('./steps/spread/getData');
147 | Monkey.spread._insertSpread = require('./steps/spread/insertSpread');
148 |
149 | Monkey.letters = {};
150 | Monkey.letters.Monkey = Monkey;
151 | Monkey.letters._generateHtml = require('./steps/letters/generateHtml');
152 | Monkey.letters._generateCharPicker = require('./steps/letters/generateCharPicker');
153 | Monkey.letters._init = require('./steps/letters/init');
154 | Monkey.letters._generateOverlay = require('./steps/letters/generateOverlay');
155 |
156 | Monkey.slider = {};
157 | Monkey.slider.Monkey = Monkey;
158 | Monkey.slider._generateHtml = require('./steps/slider/generateHtml');
159 | Monkey.slider._init = require('./steps/slider/init');
160 |
161 | Monkey.helpers = {};
162 | Monkey.helpers.Monkey = Monkey;
163 | Monkey.helpers.handleReplace = require('./helpers/handleReplace');
164 | Monkey.helpers.isMobile = require('./helpers/isMobile');
165 | Monkey.helpers.preload = require('./helpers/preload');
166 |
167 | Monkey.monkeys = {};
168 | Monkey.monkeys.mobile = require('./monkeys/mobile');
169 | Monkey.monkeys.mobile.Monkey = Monkey;
170 | Monkey.monkeys.desktop = require('./monkeys/desktop');
171 | Monkey.monkeys.desktop.Monkey = Monkey;
172 |
173 | return Monkey;
174 | })());
175 |
--------------------------------------------------------------------------------
/src/js/monkeys/mobile.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var $ = require('jquery');
4 |
5 | var mobile = module.exports = {};
6 | var $window = $(window);
7 | var numberOfMonkeys = 0;
8 | var widthModifier = 1.5;
9 |
10 | mobile.calculateSize = function () {
11 | var width = $window.width() * widthModifier;
12 | return Math.round(width);
13 | };
14 |
15 | /**
16 | * Adds the HTML to the page
17 | * @param {object} data The data object
18 | * @param {object} lang Default language object
19 | * @return {HTMLElement} The monkey wrapper.
20 | */
21 | mobile.generateHtml = function (data, lang) {
22 | var $images = $('')
23 | .appendTo(data.$monkeyWrapper)
24 | .addClass('landscape-images');
25 | var $inner = $('')
26 | .appendTo($images)
27 | .addClass('landscape-images-inner');
28 |
29 | // For each image URL we get passed, create the image and add it to the page.
30 | $.each(data.urls, function (i, url) {
31 | var $page = $('')
32 | .appendTo($inner)
33 | .addClass('page page-' + data.letters[i].type + ' Page-' + i);
34 |
35 | if (data.spreads === 'single') {
36 | $page.addClass('page-halfwidth');
37 | } else if (i === 0) {
38 | $page.addClass('page-first page-halfwidth');
39 | } else if (i === data.urls.length - 1) {
40 | $page.addClass('page-halfwidth');
41 | }
42 |
43 | $('
', {
44 | src: url,
45 | alt: data.letters[i].text || lang.noAltText
46 | }).appendTo($page);
47 | });
48 |
49 | return data.$monkeyWrapper;
50 | };
51 |
52 | // @todo document this
53 | mobile.init = function (data, $events) {
54 | var windowLeft = 0;
55 | var maxProgress = 0;
56 | var currentIndex = 0;
57 | ++numberOfMonkeys;
58 |
59 | var $monkey = data.html;
60 | var $monkeyParent = $monkey.parents('#monkey');
61 | $monkeyParent.addClass('mobile');
62 | var hasSingleSpreads = data.spreads === 'single';
63 | var RATIO = this.Monkey.IMAGE_RATIO;
64 | data.animateToPage = false;
65 |
66 | $window.on('orientationchange resize', setWidths);
67 | var $page = $('.page');
68 | var numOfPages = $page.length;
69 |
70 | setTimeout(setWidths);
71 |
72 | function setWidths() {
73 | var width = $window.width() * widthModifier;
74 | var height = Math.ceil(width / RATIO);
75 | $('.landscape-images-inner').width(width * (numOfPages + 1));
76 |
77 | $page.children('img').css({
78 | height: height,
79 | width: Math.ceil(width)
80 | });
81 |
82 | $('.page-halfwidth')
83 | .css('width', Math.ceil(width / 2))
84 | .find('img').css('height', height);
85 |
86 | if (data.spreads === 'single') {
87 | $('.page-halfwidth img').css('width', Math.ceil(width / 2));
88 | }
89 |
90 | $monkey.scrollLeft($monkey.find('img').width() * windowLeft);
91 | }
92 |
93 | $monkey.on('scroll', function () {
94 | var width = $window.width() * widthModifier;
95 | if (hasSingleSpreads) {
96 | width = (width / 2) + 6;
97 | }
98 | var scrollLeft = $monkey.scrollLeft();
99 | var progress = scrollLeft / $monkey.find('.landscape-images-inner').width();
100 | var index = (Math.floor(scrollLeft / width) * width) / (width * numOfPages) * numOfPages;
101 |
102 | if (progress > maxProgress) {
103 | maxProgress = progress;
104 | $events.trigger('bookprogress', {
105 | progress: progress
106 | });
107 | }
108 |
109 | if (currentIndex !== index) {
110 | $events.trigger('pageTurn', {
111 | monkeyType: $monkeyParent.parent().attr('data-book'),
112 | index: index
113 | });
114 | currentIndex = index;
115 | }
116 |
117 | windowLeft = scrollLeft / $monkey.find('img').width();
118 |
119 | if (scrollLeft / $monkey.find('.landscape-images-inner').width() > 0.5) {
120 | $events.trigger('halfway');
121 | }
122 |
123 | if ($monkey.find('.page:last').position().left < $(window).width()) {
124 | $events.trigger('finished');
125 | }
126 | });
127 |
128 | data.swapPage = function (index, character) {
129 | var pageNumModifier = 3;
130 | var page = Number(index) * 2 + pageNumModifier;
131 | var page1El = $('.Page-' + page).find('img');
132 | var page2El = $('.Page-' + (page + 1)).find('img');
133 | page1El.attr({ src: character.url1 + data.queryString });
134 | page2El.attr({ src: character.url2 + data.queryString });
135 | };
136 |
137 | // If we're animating the pageTurn (TJH), we don't want the harassment to execute
138 | // on any rendering of the book after the first.
139 | if ((data.animateToPage && numberOfMonkeys < 2) || !data.animateToPage) {
140 | this.harass(data);
141 | }
142 |
143 | return this.letterHandler(data, $events);
144 | };
145 |
146 | // @todo document this
147 | mobile.harass = function (data) {
148 | // Taken from jQuery Easing: http://gsgd.co.uk/sandbox/jquery/easing/
149 | var easing = 'easeInOutQuad';
150 | $.easing[easing] = function (x, t, b, c, d) {
151 | if ((t /= d / 2) < 1) {
152 | return c / 2 * t * t + b;
153 | }
154 | return -c / 2 * ((--t) * (t - 2) - 1) + b;
155 | };
156 |
157 | data.container.one('mousedown touchstart pointerdown', function () {
158 | data.html.stop();
159 | });
160 |
161 | var animSpeed = 800;
162 |
163 | function scroll() {
164 | data.html.animate({ scrollLeft: 10 }, animSpeed, easing, function () {
165 | data.html.animate({ scrollLeft: 0 }, animSpeed, easing, scroll);
166 | });
167 | }
168 |
169 | scroll();
170 | };
171 |
172 | // @todo document this.
173 | mobile.letterHandler = function (data, $events) {
174 | var $monkey = data.html;
175 | var $pages = $monkey.find('.page');
176 |
177 | var page = -1;
178 |
179 | $monkey.on('scroll', function () {
180 | var currentPage = 0;
181 |
182 | $pages.each(function (i) {
183 | if ($(this).offset().left >= -$window.width()) {
184 | currentPage = i;
185 |
186 | return false;
187 | }
188 | });
189 |
190 | if (currentPage !== page && data.canSetUpMobileScrollListener === true) {
191 | page = currentPage;
192 | $events.trigger('letterChange', page);
193 | }
194 | });
195 |
196 | // Do it on the next free cycle to ensure the event has been added
197 | setTimeout(function () {
198 | $monkey.triggerHandler('scroll');
199 | });
200 |
201 | // index is the letter index
202 | return function turnToPage(index) {
203 | if (index !== 0) {
204 | data.html.stop();
205 | }
206 | var $pages = data.html.find('.page');
207 | var factor = data.spreads === 'single' ? index : (index * 2 + 1);
208 | var $page = $pages.eq(factor);
209 | var offset = $page.offset().left - $page.parent().offset().left;
210 | var INITIAL_SPEED = 500;
211 | if (data.animateToPage) {
212 | $monkey.animate({
213 | scrollLeft: offset
214 | }, INITIAL_SPEED * Math.sqrt(factor), 'easeInOutQuad');
215 | } else {
216 | $monkey.scrollLeft(offset);
217 | }
218 | };
219 | };
220 |
--------------------------------------------------------------------------------
/src/scss/monkey/_letters.scss:
--------------------------------------------------------------------------------
1 | .letter-spans {
2 | height: 40px;
3 | margin: auto;
4 | }
5 |
6 | #letters {
7 | font-size: 1.6em;
8 | line-height: 1.2;
9 | margin: auto;
10 | position: relative;
11 | display: table;
12 | .letter {
13 | cursor: pointer;
14 | margin: 0px 3px;
15 | border-bottom: none;
16 | display: inline;
17 | float: left;
18 | vertical-align: top;
19 | }
20 | .letter-active {
21 | color: #00b69d;
22 | border-bottom: 2px solid;
23 | }
24 | #letters-container {
25 | height: 25px;
26 | }
27 | }
28 |
29 | #letters-container {
30 | position: relative;
31 | transform: translate(0,0);
32 | z-index:3;
33 | }
34 |
35 | .monkey-icons #letters {
36 | font-size: 1.2em;
37 |
38 | .letter {
39 | border-radius:2px;
40 | padding: 2px 4px 0px 4px;
41 | display: block;
42 | margin: 0 1px 12px 0;
43 | position: relative;
44 | transition: background .25s ease;
45 | -webkit-tap-highlight-color: rgba(0,0,0,0);
46 | -webkit-tap-highlight-color: transparent;
47 |
48 | .char {
49 | border-radius: 50%;
50 | font-weight: bold;
51 | width: 28px;
52 | height: 28px;
53 | line-height: 28px;
54 | text-align: center;
55 | margin: 0 auto 6px auto;
56 | transition: .25s ease;
57 | }
58 | }
59 | .letter.changed {
60 | background-color: #ffc600;
61 | border-radius: 5px;
62 | }
63 | .letter:hover,
64 | .letter:focus{
65 | .char {
66 | background-color: #f1f1f1;
67 | color: #333;
68 |
69 | }
70 | .character-card {
71 | box-shadow: 0px 1px 2px 0px rgba(0,0,0,0.2);
72 | }
73 | }
74 | .letter:active {
75 | background:#f1f1f1;
76 | }
77 | .change-character {
78 | display: block;
79 | font-size: 10px;
80 | font-weight: bold;
81 | left:-1px;
82 | opacity: 0;
83 | padding:0 4px;
84 | position: absolute;
85 | text-transform: uppercase;
86 | transition: opacity .25s ease;
87 |
88 | top:100%;
89 |
90 | .letter:hover &,
91 | .letter:focus & {
92 | text-decoration: underline;
93 | }
94 | }
95 | .letter-active, .letter-active:hover {
96 | border-bottom: none;
97 | .char {
98 | color: #FFF;
99 | background-color: #f06263;
100 | }
101 | .character-card {
102 | box-shadow: 0px 1px 2px 0px rgba(0,0,0,0.2);
103 | }
104 | }
105 | .letter-active {
106 | .change-character {
107 | opacity: 1;
108 | transition-delay:.5s;
109 | }
110 | }
111 | .letter[data-type="bridge"] {
112 | display: none;
113 | }
114 | }
115 | .monkey-icons #letters {
116 | .letter:first-child,
117 | .letter:last-child,
118 | .letter--cover {
119 | color: black;
120 | display: inline-block;
121 | height:auto;
122 | width: 25px;
123 | font-size: 1.5rem;
124 | line-height: 16px;
125 | padding-left:0;
126 | padding-right: 0;
127 |
128 | .char {
129 | height:16px;
130 | line-height:16px;
131 | margin:7px 5px 5px 4px;
132 | width:16px;
133 | }
134 | }
135 | .letter--cover {
136 | .char {
137 | overflow: hidden;
138 | position: relative;
139 | text-indent:200%;
140 | white-space:nowrap;
141 |
142 | &:before {
143 | background:#000;
144 | border-radius:50%;
145 | content:"";
146 | height:8px;
147 | left:50%;
148 | margin:-4px 0 0 -4px;
149 | position: absolute;
150 | top:50%;
151 | transition: background .25s ease;
152 | width:8px;
153 | }
154 | }
155 |
156 | &.letter-active .char:before {
157 | background:#fff;
158 | height:6px;
159 | margin:-3px 0 0 -3px;
160 | width:6px;
161 | }
162 | }
163 | }
164 | .character-card {
165 | display: inline-block;
166 | height: 40px;
167 | background-color: white;
168 | border-radius: 4px;
169 | border: 1px #e0e0e0 solid;
170 | overflow: hidden;
171 | position: relative;
172 | img {
173 | background:#fff;
174 | display: block;
175 | margin-top: 0;
176 | width: 38px;
177 | // transition: .25s ease-in;
178 | height: 38px;
179 | }
180 | }
181 |
182 | @keyframes old-thumb {
183 | 0% {
184 | opacity:1;
185 | transform: scale(1);
186 | }
187 | 100% {
188 | opacity:0;
189 | transform: scale(0.5);
190 | }
191 | }
192 | @keyframes new-thumb {
193 | 0% {
194 | opacity:0;
195 | transform: scale(1.5);
196 | }
197 | 100% {
198 | opacity:1;
199 | transform: none;
200 | }
201 | }
202 | .character-card__image--old {
203 | animation: old-thumb .3s 1 ease both;
204 | }
205 |
206 | .character-card__image--new {
207 | animation: new-thumb .3s .1s 1 ease both;
208 | left:0;
209 | position: absolute;
210 | top:0;
211 | }
212 | .monkey-icons {
213 | .letter-spans {
214 | margin-top: 10px;
215 | height: 82px;
216 | }
217 | }
218 | .letter--cloned {
219 |
220 | border-radius:2px;
221 | padding: 2px 4px 0px 4px;
222 | display: block;
223 | margin: 0 1px 12px 0;
224 | position: relative;
225 | transition: background .25s ease;
226 |
227 | .char {
228 | color: #FFF;
229 | background-color: #f06263;
230 | border-radius: 50%;
231 | font-weight: bold;
232 | width: 28px;
233 | height: 28px;
234 | line-height: 28px;
235 | text-align: center;
236 | margin: 0 auto 6px auto;
237 | transition: .25s ease;
238 | }
239 | .change-character {
240 | display: none;
241 | }
242 | }
243 |
244 | .overlay {
245 | background: #fff;
246 | bottom:0;
247 | height:100%;
248 | left:0;
249 | position: absolute;
250 | transform:translate(0,0);
251 | width:100%;
252 | z-index:5;
253 | }
254 |
255 | @media (min-width: 1025px) {
256 | .overlay {
257 | left:2.5rem;
258 | right:2.5rem;
259 | width: auto;
260 | }
261 | }
262 | .overlay__inner {
263 | height:100%;
264 | left:0;
265 | top:0;
266 | position: absolute;
267 | width:100%;
268 | z-index:1;
269 |
270 | .row {
271 | margin:0;
272 | width:100%;
273 | }
274 |
275 | .col {
276 | max-width:38em;
277 | }
278 |
279 | .row > .col {
280 | margin: 0 auto;
281 | }
282 | }
283 |
284 | // Browser-proof version (IE8 + 9)
285 |
286 | .overlay__inner {
287 | &:before {
288 | content:"";
289 | display: inline-block;
290 |
291 | height:100%;
292 | margin-left: -1px;
293 | vertical-align: middle;
294 |
295 | width:1px;
296 | }
297 |
298 | .row {
299 | display: inline-block;
300 | max-width:99%;
301 | vertical-align: middle;
302 | }
303 | }
304 |
305 | .overlay__buttons {
306 | padding-top: 1em;
307 |
308 | .desktop & {
309 | padding-bottom: 1.5em;
310 | }
311 | }
312 |
313 | .landscape #letters-container {
314 | position: absolute;
315 | color: #f6f5f1;
316 | #letters {
317 | position: fixed;
318 | z-index: 100;
319 | left: 50%;
320 | padding: 5px 10px;
321 | background-color: rgba(#333333, 0.6);
322 | border-radius: 5px;
323 | color: white;
324 | transform: translate(-50%, 0);
325 | .letter-active {
326 | color: #ff5227;
327 | }
328 | }
329 | }
330 |
--------------------------------------------------------------------------------
/src/js/steps/letters/generateHtml.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var $ = require('jquery');
4 | var blankLetterCheck = require('../../helpers/blankLetterCheck');
5 | // var isMobile = require('../../helpers/isMobile')();
6 |
7 | // The minimum length for a name is 4 not including a space or -
8 | var shortNameMinimumLength = 4;
9 |
10 | /**
11 | * Generate HTML for letters.
12 | *
13 | * @param {object} options Options object from monkey.
14 | */
15 | module.exports = function (options) {
16 | var defer = $.Deferred();
17 | return function (data) {
18 | if (!data.name) {
19 | return data;
20 | }
21 |
22 | var $lettersContainer = $('');
23 | $lettersContainer.attr({
24 | id: 'letters-container',
25 | class: 'aligned-center row leaded md-mar-b',
26 | 'data-key': 'monkey-letters'
27 | });
28 |
29 | var $hiddenName = $('', {
30 | class: 'for-screen-reader'
31 | }).text(' ' + data.name.toLowerCase());
32 |
33 | $('').appendTo($lettersContainer)
34 | .addClass('no-mar spaced-none')
35 | .text(options.lang.bookFor)
36 | .append($hiddenName);
37 |
38 | var $letterSpanContainer = $('')
39 | .appendTo($lettersContainer)
40 | .addClass('letter-spans')
41 | .attr('aria-hidden', 'true');
42 |
43 | var $letters = $('')
44 | .appendTo($letterSpanContainer)
45 | .addClass('strong')
46 | .attr('id', 'letters');
47 |
48 | // This checks for Eszett character and replaces it with double S's
49 | // Removes apostrophes
50 | var letters = data.name
51 | .replace(/ß/g, 'SS')
52 | .replace(/\'/g, '')
53 | .split('');
54 |
55 | // Get the total letters, by finding just the first part of each letter
56 | var dataLetters = $(data.letters).filter(function (i, letter) {
57 | return letter.part === 1;
58 | });
59 |
60 | // If name short, add blank letter for extra story
61 | var paddedLetters = $.map(letters, function (el) {
62 | if (!blankLetterCheck.test(el)) {
63 | return el;
64 | }
65 | });
66 | if (paddedLetters.length <= shortNameMinimumLength) {
67 | letters.splice(-1, 0, '');
68 | }
69 | var combinedLetters = data.combinedLetters = combineLetters(letters, dataLetters);
70 |
71 | // Whether we should show the duplicate letters modal if more than one
72 | // book has been created. We return an integer if true because we need to
73 | // change the wording on the overlay if it's a single letter, or multiple.
74 | var determineIfDuplicate = function () {
75 | var outcome = false;
76 | $(combinedLetters).each(function (i, letter) {
77 | if (letter.changed) {
78 | outcome++;
79 | }
80 | });
81 | return outcome && options.book.comparisonBooks !== undefined;
82 | };
83 |
84 | var hasLetterChanged = function (letter) {
85 | return letter.selected !== letter.default_character
86 | && options.showOverlay && determineIfDuplicate();
87 | };
88 |
89 | data.shouldShowDuplicateModal = determineIfDuplicate();
90 |
91 | var loadLetterCards = function () {
92 | $(combinedLetters).each(function (i, letter) {
93 | var $letterDiv = $('');
94 | if (blankLetterCheck.test(letter.letter)) {
95 | $letterDiv.addClass('special-char nonclickable');
96 | }
97 |
98 | // jscs:disable requireCamelCaseOrUpperCaseIdentifiers
99 | // Create the letter HTML, and add a whole bunch of data attributes
100 | // that we can access for targeting the appropriate letter when using
101 | // the character picker.
102 | $letterDiv.appendTo($letters)
103 | .addClass('letter')
104 | .attr('data-letter', letter.letter)
105 | .attr('data-character', letter.default_character)
106 | .attr('data-selected-character', letter.selected)
107 | .attr('data-type', letter.type)
108 | .after(' ');
109 | if (letter.thumbnail && letter.thumbnail.indexOf('helper') !== -1) {
110 | $letterDiv.attr('data-helper-character', true);
111 | }
112 | // If we're showing the duplicate letter overlay, and this is one of the
113 | // duplicate letters, we add a class to show visually that this is a
114 | // changed letter.
115 | if (hasLetterChanged(letter)) {
116 | $letterDiv.addClass('changed');
117 | }
118 | if (letter.selected !== letter.default_character) {
119 | data.monkeyContainer.data('changedChars', true);
120 | }
121 | // jscs:enable requireCamelCaseOrUpperCaseIdentifiers
122 |
123 | var $letterSpan = $('')
124 | .toggleClass('char', letter.letter !== '')
125 | .text(letter.letter || '');
126 | $letterSpan.appendTo($letterDiv);
127 |
128 | if (options.icons && letter.thumbnail) {
129 | var $characterCard = $('');
130 | $characterCard.appendTo($letterDiv)
131 | .addClass('character-card');
132 |
133 | var $icon = $('
')
134 | .attr('src', letter.thumbnail)
135 | .attr('height', 38)
136 | .attr('width', 38);
137 | $icon.appendTo($characterCard);
138 | }
139 | defer.resolve();
140 | });
141 | return defer.promise();
142 | };
143 |
144 | return loadLetterCards()
145 | .then(function () {
146 | // After we've loaded the name, we add the two circles either side of
147 | // the name for the front and back covers.
148 | $('').html('•
')
149 | .prependTo($letters)
150 | .addClass('letter letter--cover')
151 | .after(' ')
152 | .clone().appendTo($letters);
153 |
154 | var $book = false;
155 | // if (typeof options.letters !== 'boolean') {
156 | // $book = $(options.letters);
157 | // }
158 | if (!$book || !$book.length) {
159 | $book = data.monkeyContainer;
160 | }
161 | // We replicate some functionality from js/generateBaseElement.js here,
162 | // removing all current content within the container, adding the
163 | // letters element to the DOM (and saving it to the data object), and
164 | // adding the loading gif in whilst the book loads.
165 | // $book.empty();
166 | data.lettersElement = $lettersContainer.prependTo($book);
167 | // data.loading = data.loading.appendTo($book);
168 |
169 | if (options.icons) {
170 | $lettersContainer.parents('#monkey').addClass('monkey-icons');
171 | }
172 | return data;
173 | });
174 | };
175 | };
176 | /**
177 | * We combine letters from the name passed through to Monkey as an option,
178 | * with the letters array brought back from the server.
179 | * @param {array} splitLetters The letters in the options
180 | * @param {array} dataLetters The letters from the server
181 | * @return {array} A combined array of both letters, mapped.
182 | */
183 | function combineLetters(splitLetters, dataLetters) {
184 | var offset = 0;
185 | return $.map(splitLetters, function (val, i) {
186 | var idx = i;
187 | if (splitLetters.length > 5) {
188 | idx = i - offset;
189 | }
190 | dataLetters[idx].changed =
191 | dataLetters[idx].selected !== dataLetters[idx].default_character;
192 |
193 | if (blankLetterCheck.test(val)) {
194 | offset++;
195 | return { letter: val };
196 | }
197 |
198 | dataLetters[idx].letter = val;
199 | return dataLetters[idx];
200 | });
201 | }
202 |
--------------------------------------------------------------------------------
/demo/context_files/reviewer4-60x60-bcef0cef3fddd3d7c6688b46c2377597.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/js/steps/letters/generateOverlay.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var $ = require('jquery');
4 | var isMobile = require('../../helpers/isMobile')();
5 | var lang = require('lang');
6 |
7 |
8 | /**
9 | * Generate HTML for the overlay. This contains the code for both the duplicate
10 | * letter overlay, and the language overlay.
11 | *
12 | * @param {string|boolean} [selector] Selector or element to insert letters into.
13 | * @param {object} lang Object containing language stuff.
14 | */
15 | module.exports = function (options, $events) {
16 | var defer = $.Deferred();
17 | return function (data) {
18 | function loadOverlay() {
19 | var $overlay = $('');
20 | var $monkeyContainer = data.monkeyContainer;
21 | var classes = {
22 | overlayActive: 'js--active-overlay'
23 | };
24 |
25 | var singularOrPlural = data.shouldShowDuplicateModal === 1 ?
26 | 'singular' :
27 | 'plural';
28 |
29 | // Trigger an event which we can use in Eagle to hide the book form.
30 | $events.trigger('overlayActive', $monkeyContainer);
31 |
32 | $monkeyContainer.addClass(classes.overlayActive);
33 |
34 | $overlay.prependTo($monkeyContainer.find('.monkey-wrapper'))
35 | .addClass('monkey-overlay');
36 |
37 | // We want to hide the 'Tap to Preview' label when the overlay is visible
38 | // so the user only focuses on the content within the overlay, and because
39 | // it's nonsensical to have a label to 'Tap to Preview' when you, at that
40 | // stage, can't.
41 | $monkeyContainer
42 | .next('.italic')
43 | .hide();
44 |
45 | var $overlayContent = $('')
46 | .addClass('row');
47 | $overlayContent.appendTo($overlay);
48 |
49 | var overlayTitle, overlayText;
50 | var comparisonName = '';
51 | if (typeof options.book.comparisonBooks !== 'undefined') {
52 | comparisonName = options.book.comparisonBooks[0].name;
53 | }
54 |
55 | // Here's where we change the content of the overlay dependant on which
56 | // overlay is showing. If we need more overlays for whatever reason, we
57 | // should probably change how this is done as it's not entirely scalable.
58 | if (options.showLanguageOverlay === true && $monkeyContainer.data('changedChars')) {
59 | overlayTitle = lang('monkey.language.title');
60 | overlayText = lang('monkey.language.copy');
61 | } else if (comparisonName !== '') {
62 | overlayTitle = lang('monkey.overlay.nounTypes.' + singularOrPlural + '.title');
63 | overlayText = comparisonName.toUpperCase() + ' & ' +
64 | data.name.toUpperCase() + ' ' +
65 | lang('monkey.overlay.nounTypes.' + singularOrPlural + '.intro');
66 | }
67 |
68 | var $titleBox = $('')
69 | .text(overlayTitle);
70 | $titleBox.appendTo($overlayContent);
71 |
72 |
73 | var $messageBox = $('')
74 | .text(overlayText)
75 | .addClass('overlay__copy col');
76 | $messageBox.appendTo($overlayContent);
77 |
78 | var $buttonContainer = $('')
79 | .addClass('col overlay__buttons');
80 |
81 | var $yesButton = $('')
82 | .text(lang('monkey.overlay.buttons.yes_please'))
83 | .addClass('button button--primary col md-mar-t-on-sm sm-mar md-mar-t-on-xs no-mar-on-xs')
84 | .addClass('spaced-left-small-on-tablet-up block-on-fablet-down widest-on-fablet-down')
85 | .addClass('spaced-bottom-small-on-fablet-down');
86 |
87 | var $noButton = $('')
88 | .text(lang('monkey.overlay.buttons.no_thanks'))
89 | .addClass('button col md-mar-t-on-sm sm-mar md-mar-t-on-xs no-mar-on-xs')
90 | .addClass('spaced-right-small-on-tablet-up block-on-fablet-down widest-on-fablet-down')
91 | .addClass('spaced-bottom-small-on-fablet-down');
92 |
93 | var $okayButton = $('')
94 | .text(lang('monkey.language.buttons.okay'))
95 | .addClass('button col md-mar-t-on-sm sm-mar md-mar-t-on-xs no-mar-on-xs');
96 |
97 | $buttonContainer.appendTo($overlayContent);
98 | // We want to show just an 'Okay' button for the language overlay, but
99 | // a Yes/No choice if it's the duplicate letters overlay.
100 | if (options.showLanguageOverlay === true) {
101 | $okayButton.appendTo($buttonContainer, function () {
102 | defer.resolve();
103 | });
104 | $okayButton.on('click', function () {
105 | closeOverlay(true);
106 | });
107 | } else {
108 | if (isMobile) {
109 | $yesButton.appendTo($buttonContainer);
110 | $noButton.appendTo($buttonContainer, function () {
111 | defer.resolve();
112 | });
113 | } else {
114 | $noButton.appendTo($buttonContainer);
115 | $yesButton.appendTo($buttonContainer, function () {
116 | defer.resolve();
117 | });
118 | }
119 |
120 | $yesButton.on('click', function () {
121 | data.editCharacters = true;
122 | $monkeyContainer.data('changedChars', true);
123 | closeOverlay(true);
124 | });
125 |
126 | $noButton.on('click', function () {
127 | revertCharsToOriginal(closeOverlay);
128 | $monkeyContainer.data('changedChars', false);
129 | });
130 | }
131 |
132 | /**
133 | * Closes the active overlay, triggers the close event, shows the labels,
134 | * scrolls the letters back to the starting point if we're viewing a
135 | * duplicate letter modal.
136 | * @param {boolean} updateChars Whether we want to update the characters
137 | * @return {null}
138 | */
139 | function closeOverlay(updateChars) {
140 | $events.trigger('overlayClosed', $monkeyContainer);
141 | if (updateChars) {
142 | data.updateCharSelection();
143 | }
144 | $monkeyContainer
145 | .next('.italic')
146 | .removeAttr('style');
147 | $('.letter').removeClass('changed');
148 | $('.letter-spans').animate({
149 | scrollLeft: 0
150 | }, 800, function () {
151 | $events.trigger('letterChange', 0);
152 | });
153 | $monkeyContainer
154 | .removeClass(classes.overlayActive)
155 | .css({ minHeight: 0 });
156 | $overlay.fadeOut(250, function () {
157 | $(this).remove();
158 | });
159 |
160 | }
161 |
162 | /**
163 | * If we're displaying the duplicate display, and the user wants to not
164 | * use the new characters we've selected for them, this function resets
165 | * the character selection to the letter defaults.
166 | * @param {Function} callback callback function (usually closeOverlay())
167 | * @return {null}
168 | */
169 | function revertCharsToOriginal(callback) {
170 | data.combinedLetters
171 | .map((letter, index) => Object.assign({}, letter, { index }))
172 | .filter(letter => letter.changed === true)
173 | .reduce(function (arr, letter) {
174 | var characters = letter.characters
175 | .map(character => Object.assign({}, character, { pageIndex: letter.index }))
176 | .filter(character => character.character === letter.default_character);
177 |
178 | return [...arr, ...characters];
179 | }, [])
180 | .forEach(function (character) {
181 | var $letter = $(`.letter[data-letter=${character.letter}][data-character=${character.character}]`);
182 | data.changeCharacter(character.pageIndex, character, $letter, false);
183 | });
184 |
185 | callback(false);
186 | }
187 | return defer.promise();
188 | }
189 | if (data.shouldShowDuplicateModal !== false || options.showLanguageOverlay === true) {
190 | loadOverlay()
191 | .then(function () {
192 | return data;
193 | });
194 | }
195 | return data;
196 | };
197 | };
198 |
--------------------------------------------------------------------------------
/src/js/monkeys/desktop.js:
--------------------------------------------------------------------------------
1 |
2 | 'use strict';
3 |
4 | var $ = require('jquery');
5 | var Heidelberg = require('heidelberg');
6 | var nums = require('nums');
7 |
8 | (function () {
9 | var lastTime = 0;
10 | var vendors = ['ms', 'moz', 'webkit', 'o'];
11 | for (var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
12 | window.requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame'];
13 | window.cancelAnimationFrame = window[vendors[x]+'CancelAnimationFrame']
14 | || window[vendors[x]+'CancelRequestAnimationFrame'];
15 | }
16 |
17 | if (!window.requestAnimationFrame) {
18 | window.requestAnimationFrame = function (callback) {
19 | var currTime = new Date().getTime();
20 | var timeToCall = Math.max(0, 16 - (currTime - lastTime));
21 | var id = window.setTimeout(function () {
22 | callback(currTime + timeToCall);
23 | }, timeToCall);
24 | lastTime = currTime + timeToCall;
25 | return id;
26 | };
27 | }
28 |
29 | if (!window.cancelAnimationFrame) {
30 | window.cancelAnimationFrame = function (id) {
31 | clearTimeout(id);
32 | };
33 | }
34 | })();
35 |
36 | var desktop = module.exports = {};
37 |
38 | desktop.calculateSize = function (data) {
39 | return Math.round(data.$monkeyWrapper.width());
40 | };
41 |
42 | /**
43 | * Adds the HTML to the page
44 | * @param {object} data The data object
45 | * @param {object} lang Default language object
46 | * @return {HTMLElement} The monkey wrapper.
47 | */
48 | desktop.generateHtml = function (data, lang) {
49 | var $monkey = $('').addClass('Heidelberg-Book with-Spreads pos-absolute positioned-absolute');
50 |
51 | if (data.spreads === 'double') {
52 | $monkey.addClass('with-Spreads');
53 | }
54 |
55 | $('', {
56 | href: '#skip-preview',
57 | className: 'for-screen-reader'
58 | })
59 | .text('Skip book preview')
60 | .appendTo(data.$monkeyWrapper);
61 |
62 | $.each(data.urls, function (i, url) {
63 | var $img = $('
', {
64 | src: url,
65 | alt: data.letters[i].text || lang.noAltText
66 | });
67 |
68 | $('')
69 | .addClass('Heidelberg-' + (data.spreads === 'single' ? 'Page' : 'Spread'))
70 | .addClass('page-' + data.letters[i].type + ' Page-' + i)
71 | .append($img)
72 | .appendTo($monkey);
73 |
74 | $img.attr('data-id', data.letters[i].id);
75 | });
76 | $monkey.appendTo(data.$monkeyWrapper);
77 |
78 | $('', { id: 'skip-preview' }).appendTo(data.$monkeyWrapper);
79 |
80 | return data.$monkeyWrapper;
81 | };
82 |
83 | // @todo document this.
84 | desktop.init = function (data, $events, options) {
85 | var maxBookProgress = 0;
86 | var bookNavType;
87 | if (options.showCharPicker) {
88 | bookNavType = 'characterPicker';
89 | } else if (options.icons) {
90 | bookNavType = 'icons';
91 | } else {
92 | bookNavType = 'original';
93 | }
94 |
95 | data.drawPage;
96 |
97 | data.heidelberg = new Heidelberg(data.html.find('.Heidelberg-Book'), {
98 | arrowKeys: false,
99 | hasSpreads: (data.spreads === 'double'),
100 | limitPageTurns: false,
101 | canClose: options.canClose || false
102 | });
103 |
104 | data.swapPage = function (index, character) {
105 | var pageNumModifier = 3;
106 | var page = Number(index) * 2 + pageNumModifier;
107 | var $newImage = $('
')
108 | .attr('src', character.url1 + data.queryString);
109 | var $newImage2 = $('
')
110 | .attr('src', character.url2 + data.queryString);
111 |
112 | var newPage1 = $('')
113 | .addClass('Heidelberg-Spread page- Page-' + page)
114 | .append($newImage.clone());
115 |
116 | var newPage2 = $('')
117 | .addClass('Heidelberg-Spread page- Page-' + (page + 1))
118 | .append($newImage2.clone());
119 |
120 | var page1El = data.monkeyContainer.find('.Page-' + page);
121 | var page2El = data.monkeyContainer.find('.Page-' + (page + 1));
122 | page1El.replaceWith(newPage1);
123 | page2El.replaceWith(newPage2);
124 |
125 | };
126 |
127 | data.heidelberg.el.addClass('at-front-cover');
128 |
129 | $(data.heidelberg).on('pageTurn.heidelberg', function (e, $el, els) {
130 | var index = els.pages.index(els.pagesTarget);
131 | var bookProgress = index / els.pages.length;
132 | if (bookProgress > maxBookProgress) {
133 | maxBookProgress = bookProgress;
134 | $events.trigger('bookprogress', {
135 | progress: maxBookProgress,
136 | bookNavType: bookNavType });
137 | }
138 | $events.trigger('pageTurn', {
139 | monkeyType: data.container.parent().attr('data-book'),
140 | index: (index + 1) / 2
141 | });
142 |
143 | $el.toggleClass('at-front-cover', !index);
144 | $el.toggleClass('at-rear-cover', index === els.pages.length - (options.perPage / 2));
145 |
146 | if (index / els.pages.length > 0.5) {
147 | $events.trigger('halfway');
148 | }
149 |
150 | if (index === els.pages.length - (options.perPage / 2)) {
151 | $events.trigger('finished');
152 | }
153 | });
154 |
155 | return this.letterHandler(data, $events, options);
156 | };
157 |
158 | desktop.letterHandler = function (data, $events, options) {
159 | var fireEvent = true;
160 | var lastIndex = 0;
161 |
162 | $(data.heidelberg).on('pageTurn.heidelberg', function (e, el, els) {
163 | var newLastIndex = (els.pagesTarget.index() - 3) / 4;
164 | if (options.perPage === 2) {
165 | newLastIndex = (els.pagesTarget.index() + 1) / 2;
166 | }
167 | if (fireEvent) {
168 | // buddy ignore:start
169 | var index = (els.pagesTarget.index() - 1) / 2;
170 | $events.trigger('letterChange', index);
171 | lastIndex = newLastIndex;
172 | // buddy ignore:end
173 | }
174 | });
175 |
176 | // Do it on the next free cycle to ensure the event has been added
177 | setTimeout(function () {
178 | $events.trigger('letterChange', 0);
179 | });
180 |
181 | return function turnToPage(index) {
182 | fireEvent = false;
183 | // If already on the page, flip to the other page in the pair
184 | if (index === lastIndex) {
185 | index += index % 1 ? -0.5 : 0.5;
186 | }
187 |
188 | var PER_PAGE = options.perPage;
189 | var indexes = nums((lastIndex + 1) * PER_PAGE, (index + 1) * PER_PAGE);
190 | var doubleSpeed = (indexes.length > 10);
191 | index = Math.round(index);
192 |
193 | // If index ends .5, doubleSpeed doesn't work. Tbh I'm not sure why.
194 | // @todo: Think of a proper fix
195 | if (index % 1 !== 0) {
196 | doubleSpeed = false;
197 | }
198 |
199 | var DEFAULT_TIME = 30;
200 | var time = DEFAULT_TIME / (doubleSpeed ? 2 : 1);
201 |
202 | var currentTime = Date.now();
203 | var indexObject = [];
204 | /*
205 | * Loop through the indexes array to create a new array containing objects of the page index to turn to,
206 | * the time from the start when the pageTurn should occur, and a flag to determine whether the event has fired.
207 | */
208 | $.each(indexes, function (i, index) {
209 | // Happen only every 2 or 4 times
210 | if (!options.slider && index % (PER_PAGE / (doubleSpeed ? 2 : 1))) {
211 | return;
212 | }
213 | indexObject.push({
214 | pageIndex: Math.round(index) -1,
215 | time: i * time,
216 | hasFired: false
217 | });
218 | });
219 |
220 | /**
221 | * A rAF animation to turn the pages of the book, as the setTimeout method wasn't being very performant
222 | * @param {object} manifest The object of indexes & times for when to fire the pages
223 | * @return {null}
224 | */
225 | function turnPages(manifest) {
226 | var newTime = Date.now();
227 | var canRun = true;
228 | $.each(manifest, function (i, obj) {
229 | if (newTime - currentTime > obj.time && !obj.hasFired) {
230 | data.heidelberg.turnPage(obj.pageIndex);
231 | // This ensures that the event is only fired for the last page turn
232 | if (i === manifest.length - 2) {
233 | fireEvent = true;
234 | }
235 | // If we're on the final page, we want to cancel the animation frame so it's not running constantly,
236 | // which could cause issues when trying to run this function again if you turn pages more than once.
237 | if (i === manifest.length - 1) {
238 | canRun = false;
239 | cancelAnimationFrame(data.drawPage);
240 | }
241 | obj.hasFired = true;
242 | }
243 | });
244 | if (canRun) {
245 | data.drawPage = requestAnimationFrame(turnPages.bind(this, manifest));
246 | }
247 | }
248 |
249 | turnPages(indexObject);
250 | };
251 | };
252 |
--------------------------------------------------------------------------------
/demo/context_files/shared-head-1776f49ac825f6a064ce575a069c6b5f.js:
--------------------------------------------------------------------------------
1 | /*! modernizr 3.0.0pre (Custom Build) | MIT */
2 | !function(e,t,n){function r(e,t){return typeof e===t}function i(){var e,t,n,i,s,o,u;for(var a in m){if(e=[],t=m[a],t.name&&(e.push(t.name.toLowerCase()),t.options&&t.options.aliases&&t.options.aliases.length))for(n=0;n',e,""].join(""),l.id=f,(c.fake?c:l).innerHTML+=i,c.appendChild(l),c.fake&&(c.style.background="",c.style.overflow="hidden",u=E.style.overflow,E.style.overflow="hidden",E.appendChild(c)),s=t(l,e),c.fake?(c.parentNode.removeChild(c),E.style.overflow=u,E.offsetHeight):l.parentNode.removeChild(l),!!s}function l(e){return e.replace(/([A-Z])/g,function(e,t){return"-"+t.toLowerCase()}).replace(/^ms-/,"-ms-")}function c(t,r){var i=t.length;if("CSS"in e&&"supports"in e.CSS){for(;i--;)if(e.CSS.supports(l(t[i]),r))return!0;return!1}if("CSSSupportsRule"in e){for(var s=[];i--;)s.push("("+l(t[i])+":"+r+")");return s=s.join(" or "),f("@supports ("+s+") { #modernizr { position: absolute; } }",function(t){return"absolute"==(e.getComputedStyle?getComputedStyle(t,null):t.currentStyle).position})}return n}function h(e,t,i,s){function o(){f&&(delete N.style,delete N.modElem)}if(s=r(s,"undefined")?!1:s,!r(i,"undefined")){var a=c(e,i);if(!r(a,"undefined"))return a}var f,l,h,p;N.style||(f=!0,N.modElem=x("modernizr"),N.style=N.modElem.style);for(l in e)if(h=e[l],p=N.style[h],!u(h,"-")&&N.style[h]!==n){if(s||r(i,"undefined"))return o(),"pfx"==t?h:!0;try{N.style[h]=i}catch(d){}if(N.style[h]!=p)return o(),"pfx"==t?h:!0}return o(),!1}function p(e,t,n){var i;for(var s in e)if(e[s]in t)return n===!1?e[s]:(i=t[e[s]],r(i,"function")&&"bind"in i?i.bind(n||t):i);return!1}function d(e,t,n,i,s){var o=e.charAt(0).toUpperCase()+e.slice(1),u=(e+" "+O.join(o+" ")+o).split(" ");return r(t,"string")||r(t,"undefined")?h(u,t,i,s):(u=(e+" "+M.join(o+" ")+o).split(" "),p(u,t,n))}function v(e,t,r){return d(e,n,n,t,r)}var m=[],g={_version:"v3.0.0pre",_config:{classPrefix:"",enableClasses:!0,usePrefixes:!0},_q:[],on:function(e,t){setTimeout(function(){t(this[e])},0)},addTest:function(e,t,n){m.push({name:e,fn:t,options:n})},addAsyncTest:function(e){m.push({name:null,fn:e})}},y=function(){};y.prototype=g,y=new y;var b,w=[],E=t.documentElement;!function(){var e={}.hasOwnProperty;b=r(e,"undefined")||r(e.call,"undefined")?function(e,t){return t in e&&r(e.constructor.prototype[t],"undefined")}:function(t,n){return e.call(t,n)}}(),g._l={},g.on=function(e,t){this._l[e]||(this._l[e]=[]),this._l[e].push(t),y.hasOwnProperty(e)&&setTimeout(function(){y._trigger(e,y[e])},0)},g._trigger=function(e,t){if(this._l[e]){var n=this._l[e];setTimeout(function(){var e,r;for(e=0;e",r.insertBefore(n.lastChild,r.firstChild)}function r(){var e=S.elements;return"string"==typeof e?e.split(" "):e}function i(e){var t=x[e[w]];return t||(t={},E++,e[w]=E,x[E]=t),t}function s(e,n,r){if(n||(n=t),v)return n.createElement(e);r||(r=i(n));var s;return s=r.cache[e]?r.cache[e].cloneNode():b.test(e)?(r.cache[e]=r.createElem(e)).cloneNode():r.createElem(e),s.canHaveChildren&&!y.test(e)?r.frag.appendChild(s):s}function o(e,n){if(e||(e=t),v)return e.createDocumentFragment();n=n||i(e);for(var s=n.frag.cloneNode(),o=0,u=r(),a=u.length;a>o;o++)s.createElement(u[o]);return s}function u(e,t){t.cache||(t.cache={},t.createElem=e.createElement,t.createFrag=e.createDocumentFragment,t.frag=t.createFrag()),e.createElement=function(n){return S.shivMethods?s(n,e,t):t.createElem(n)},e.createDocumentFragment=Function("h,f","return function(){var n=f.cloneNode(),c=n.createElement;h.shivMethods&&("+r().join().replace(/\w+/g,function(e){return t.createElem(e),t.frag.createElement(e),'c("'+e+'")'})+");return n}")(S,t.frag)}function a(e){e||(e=t);var r=i(e);return!S.shivCSS||d||r.hasCSS||(r.hasCSS=!!n(e,"article,aside,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}mark{background:#FF0;color:#000}")),v||u(e,r),e}function f(e){for(var t,n=e.getElementsByTagName("*"),i=n.length,s=RegExp("^(?:"+r().join("|")+")$","i"),o=[];i--;)t=n[i],s.test(t.nodeName)&&o.push(t.applyElement(l(t)));return o}function l(e){for(var t,n=e.attributes,r=n.length,i=e.ownerDocument.createElement(N+":"+e.nodeName);r--;)t=n[r],t.specified&&i.setAttribute(t.nodeName,t.nodeValue);return i.style.cssText=e.style.cssText,i}function c(e){for(var t,n=e.split("{"),i=n.length,s=RegExp("(^|[\\s,>+~])("+r().join("|")+")(?=[[\\s,>+~#.:]|$)","gi"),o="$1"+N+"\\:$2";i--;)t=n[i]=n[i].split("}"),t[t.length-1]=t[t.length-1].replace(s,o),n[i]=t.join("}");return n.join("{")}function h(e){for(var t=e.length;t--;)e[t].removeNode()}function p(e){function t(){clearTimeout(o._removeSheetTimer),r&&r.removeNode(!0),r=null}var r,s,o=i(e),u=e.namespaces,a=e.parentWindow;return!C||e.printShived?e:("undefined"==typeof u[N]&&u.add(N),a.attachEvent("onbeforeprint",function(){t();for(var i,o,u,a=e.styleSheets,l=[],h=a.length,p=Array(h);h--;)p[h]=a[h];for(;u=p.pop();)if(!u.disabled&&T.test(u.media)){try{i=u.imports,o=i.length}catch(d){o=0}for(h=0;o>h;h++)p.push(i[h]);try{l.push(u.cssText)}catch(d){}}l=c(l.reverse().join("")),s=f(e),r=n(e,l)}),a.attachEvent("onafterprint",function(){h(s),clearTimeout(o._removeSheetTimer),o._removeSheetTimer=setTimeout(t,500)}),e.printShived=!0,e)}var d,v,m="3.6.2",g=e.html5||{},y=/^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i,b=/^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i,w="_html5shiv",E=0,x={};!function(){try{var e=t.createElement("a");e.innerHTML="",d="hidden"in e,v=1==e.childNodes.length||function(){t.createElement("a");var e=t.createDocumentFragment();return"undefined"==typeof e.cloneNode||"undefined"==typeof e.createDocumentFragment||"undefined"==typeof e.createElement}()}catch(n){d=!0,v=!0}}(),S={elements:g.elements||"abbr article aside audio bdi canvas data datalist details figcaption figure footer header hgroup main mark meter nav output progress section summary time video",version:m,shivCSS:g.shivCSS!==!1,supportsUnknownElements:v,shivMethods:g.shivMethods!==!1,type:"default",shivDocument:a,createElement:s,createDocumentFragment:o},e.html5=S,a(t);var T=/^$|\b(?:all|print)\b/,N="html5shiv",C=!v&&function(){var n=t.documentElement;return"undefined"!=typeof t.namespaces&&"undefined"!=typeof t.parentWindow&&"undefined"!=typeof n.applyElement&&"undefined"!=typeof n.removeNode&&"undefined"!=typeof e.attachEvent}();S.type+=" print",S.shivPrint=p,p(t)}(this,t);var x=function(){return t.createElement.apply(t,arguments)},T={elem:x("modernizr")};y._q.push(function(){delete T.elem});var N={style:T.elem.style};y._q.unshift(function(){delete N.style});var C=(g.testProp=function(e,t,r){return h([e],n,t,r)},w.slice);Function.prototype.bind||(Function.prototype.bind=function(e){var t=this;if("function"!=typeof t)throw new TypeError;var n=C.call(arguments,1),r=function(){if(this instanceof r){var i=function(){};i.prototype=t.prototype;var s=new i,o=t.apply(s,n.concat(C.call(arguments)));return Object(o)===o?o:s}return t.apply(e,n.concat(C.call(arguments)))};return r});var k=function(){var t=e.matchMedia||e.msMatchMedia;return t?function(e){var n=t(e);return n&&n.matches||!1}:function(t){var n=!1;return f("@media "+t+" { #modernizr { position: absolute; } }",function(t){n="absolute"==(e.getComputedStyle?e.getComputedStyle(t,null):t.currentStyle).position}),n}}(),L=g.mq=k;y.addTest("mediaqueries",L("only all"));var A="Webkit Moz O ms",O=g._config.usePrefixes?A.split(" "):[];g._cssomPrefixes=O;var M=g._config.usePrefixes?A.toLowerCase().split(" "):[];g._domPrefixes=M,g.testAllProps=d,g.testAllProps=v,y.addTest("csstransforms",v("transform","scale(1)",!0));var _=g.testStyles=f;y.addTest("csstransforms3d",function(){var e=!!v("perspective","1px",!0);return e&&"webkitPerspective"in E.style&&_("@media (transform-3d),(-webkit-transform-3d){#modernizr{left:9px;position:absolute;height:5px;margin:0;padding:0;border:0}}",function(t){e=9===t.offsetLeft&&5===t.offsetHeight}),e}),y.addTest("preserve3d",v("transformStyle","preserve-3d")),y.addTest("csstransitions",v("transition","all",!0)),y.addTest("placeholder","placeholder"in x("input")&&"placeholder"in x("textarea")),i(),s(w),delete g.addTest,delete g.addAsyncTest;for(var D=0;D')
32 | .addClass('tooltip-arrow')
33 | .attr('src', 'https://s3-eu-west-1.amazonaws.com/lmn-cdn-assets/widget/tooltip-arrow-85x47.png');
34 |
35 | var $monkeyContainer = monkeyContainer;
36 |
37 | // The mobile character picker is located differently to the desktop picker.
38 | // This is due to the fact we've got scrolling on the letters which means
39 | // that if we put the character picker within the letter – as we do on
40 | // desktop – it'll get cut off due to the overflow propert on the letter
41 | // parent scrolling element.
42 | if (isMobile) {
43 | var $pickerBg = $('')
44 | .addClass('picker-container__bg');
45 | $pickerBg.appendTo($monkeyContainer);
46 |
47 | var $pickerContainer = $('')
48 | .addClass('picker-container')
49 | .appendTo($monkeyContainer);
50 | }
51 | // Returns an array like ["J", "O", "N", "A", "T", "H", "A", "N"]
52 | var allCharacters = $.map(data.combinedLetters, function (el) {
53 | return el.selected || '';
54 | });
55 |
56 | var loadLetterPicker = function () {
57 | var currentDisplayPage = 0;
58 | $(data.combinedLetters).each(function (i, letter) {
59 |
60 | // jscs:disable requireCamelCaseOrUpperCaseIdentifiers
61 | // If the character is a bridge (Tornado, Rainbow, Hole, etc) then it
62 | // won't have a letter to search for, so we find the bridge letter.
63 | var $letterDiv = letter.type !== 'bridge' ?
64 | $monkeyContainer.find('.letter[data-letter="' + letter.letter + '"]' +
65 | '[data-character="' + letter.default_character + '"]') :
66 | $monkeyContainer.find('.letter[data-letter=""][data-type="bridge"]');
67 | // jscs:enable requireCamelCaseOrUpperCaseIdentifiers
68 |
69 | // Store each letter which is currently being used
70 | var usedCharacters = data.combinedLetters.map(function (letter) {
71 | return letter.selected;
72 | });
73 | var $toolTip = $('');
74 | $toolTip
75 | .addClass('character-picker pos-absolute positioned-absolute');
76 |
77 | // If this is the mobile picker, attach it to the picker container div
78 | // we created earlier, and add a close button. We also change the picker
79 | // depending on whether the name is centered, or scrolled on mobile. If
80 | // the name is centered, we remove the tooltip arrow, and shift the
81 | // picker up a little to better connect it to the current letter. This
82 | // is done by adding the .character-picker--no-arrow class to the
83 | // picker. On orientation change, we also perform this check as the name
84 | // could center or scroll depending on the length and the screen width.
85 | if (isMobile && letter.letter !== '-' && letter.letter !== ' ') {
86 | $toolTip.appendTo($pickerContainer);
87 | var $closeButton = $('