├── .babelrc ├── public ├── splash.png ├── catLanding.png ├── splash_ipad.png ├── splash_iphone6.png ├── splash_iphone6plus.png ├── 30799efa5bf74129468ad4e257551dc3.eot ├── 3b813c2ae0d04909a33a18d792912ee7.woff ├── 46e48ce0628835f68a7369d0254e4283.ttf ├── 4d9f3f9e5195e7b074bb63ba4ce42208.eot ├── 7500519de3d82e33d1587f8042e2afcb.woff ├── 894a2ede85a483bf9bedefd4db45cdb9.ttf ├── 94998475f6aea65f558494802416c1cf.ttf ├── a990f611f2305dc12965f186c2ef2690.eot ├── ba3dcd8903e3d0af5de7792777f8ae0d.woff ├── bc540e50b175a23a39a1b3bffc6e0785.png ├── dc81817def276b4f21395f7ea5e88dcd.woff ├── df7b648ce5356ea1ebce435b3459fd60.ttf ├── dfe56a876d0282555d1e2458e278060f.eot ├── e31fcf1885e371e19f5786c2bdfeae1b.ttf ├── ecdd509cadbf1ea78b8d2e31ec52328c.eot ├── fc78759e93a6cac50458610e3d9d63a0.woff ├── 2751ee43015f9884c3642f103b7f70c9.woff2 ├── 39b2c3031be6b4ea96e2e3e95d307814.woff2 ├── 574fd0b50367f886d359e8264938fc37.woff2 ├── 69f8a0617ac472f78e45841323a3df9e.woff2 ├── 954bbdeb86483e4ffea00c4591530ece.woff2 ├── Podington_Bear_-_Floating_In_Space.mp3 ├── ea463d8bf1904b3cece6052283b04971.svg ├── 7ef9009b51f2e3ced613809250f98a30.svg ├── f079d603854bcd795eaabf2ef7ae723b.svg ├── 3c546bf7e955aeaf180bca3d2743e204.svg ├── 0bffba416891219267efcf4e59c3c015.svg ├── 7ef525e40bfdaf62100e47dd4be50351.svg ├── manifest.json ├── breathe.appcache ├── index.html ├── 7d0026955ab24fa892b84b6df7cc6935.svg └── bundle.css ├── .eslintrc ├── src ├── assets │ ├── images │ │ ├── splash.png │ │ ├── breathingBG.png │ │ ├── catLanding.png │ │ ├── splash_ipad.png │ │ ├── png │ │ │ └── catLanding.png │ │ ├── splash_iphone6.png │ │ ├── splash_iphone6plus.png │ │ ├── breathingPage │ │ │ ├── Legs.svg │ │ │ ├── leftHand.svg │ │ │ ├── rightHand.svg │ │ │ ├── breathingBelly.svg │ │ │ ├── breathing-background.svg │ │ │ ├── twoHands.svg │ │ │ ├── BeathingHead.svg │ │ │ ├── newHands.svg │ │ │ ├── new legs.svg │ │ │ └── breathingCat.svg │ │ ├── blueMountains │ │ │ ├── mountainBack.svg │ │ │ ├── mountainFront2.svg │ │ │ └── mountainMiddle.svg │ │ ├── redMountains │ │ │ ├── mountainWelldoneFront.svg │ │ │ ├── mountainWelldoneMiddle.svg │ │ │ └── mountainWelldoneBack.svg │ │ ├── welldone │ │ │ ├── stars.svg │ │ │ └── banner.svg │ │ ├── GIFpage │ │ │ └── cat-GIF.svg │ │ └── icons │ │ │ ├── cat_icon_square_very_big.svg │ │ │ ├── cat_icon_round_big_no_border.svg │ │ │ ├── cat_icon_square_small.svg │ │ │ └── cat_icon_square_big.svg │ └── sounds │ │ └── Podington_Bear_-_Floating_In_Space.mp3 ├── templates │ ├── avatars │ │ ├── index.js │ │ ├── cat.html │ │ └── panda.html │ ├── index.js │ ├── credits.html │ ├── landing.html │ ├── welldone.html │ ├── avatar.html │ ├── breathing.html │ └── narration.html ├── css │ ├── partials │ │ ├── _fonts.scss │ │ ├── _colors.scss │ │ ├── _page.scss │ │ ├── _avatar.scss │ │ ├── _mountain-color.scss │ │ ├── _credits.scss │ │ ├── _buttons.scss │ │ ├── _landing.scss │ │ ├── _welldone.scss │ │ ├── _carousel.scss │ │ └── _breathing.scss │ └── style.scss ├── globalState.js ├── controllers │ ├── index.js │ ├── credits.js │ ├── welldone.js │ ├── narration.js │ ├── landing.js │ ├── avatar.js │ └── breathing.js ├── lib │ ├── audio.js │ ├── validation.js │ ├── transitions.js │ ├── background.js │ ├── breathingmenu.js │ ├── breathingtimer.js │ ├── carousel.js │ └── blacklist.js ├── manifest.json ├── index.js ├── index.html └── animations │ └── index.js ├── index.html ├── .gitignore ├── package.json ├── README.md └── webpack.config.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | presets: ['es2015'] 3 | } 4 | -------------------------------------------------------------------------------- /public/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CYPIAPT-LNDSE/Breathe-With-Me/HEAD/public/splash.png -------------------------------------------------------------------------------- /public/catLanding.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CYPIAPT-LNDSE/Breathe-With-Me/HEAD/public/catLanding.png -------------------------------------------------------------------------------- /public/splash_ipad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CYPIAPT-LNDSE/Breathe-With-Me/HEAD/public/splash_ipad.png -------------------------------------------------------------------------------- /public/splash_iphone6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CYPIAPT-LNDSE/Breathe-With-Me/HEAD/public/splash_iphone6.png -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "airbnb", 3 | "env": { 4 | "browser": true, 5 | "jquery": true, 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /public/splash_iphone6plus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CYPIAPT-LNDSE/Breathe-With-Me/HEAD/public/splash_iphone6plus.png -------------------------------------------------------------------------------- /src/assets/images/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CYPIAPT-LNDSE/Breathe-With-Me/HEAD/src/assets/images/splash.png -------------------------------------------------------------------------------- /src/assets/images/breathingBG.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CYPIAPT-LNDSE/Breathe-With-Me/HEAD/src/assets/images/breathingBG.png -------------------------------------------------------------------------------- /src/assets/images/catLanding.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CYPIAPT-LNDSE/Breathe-With-Me/HEAD/src/assets/images/catLanding.png -------------------------------------------------------------------------------- /src/assets/images/splash_ipad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CYPIAPT-LNDSE/Breathe-With-Me/HEAD/src/assets/images/splash_ipad.png -------------------------------------------------------------------------------- /src/assets/images/png/catLanding.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CYPIAPT-LNDSE/Breathe-With-Me/HEAD/src/assets/images/png/catLanding.png -------------------------------------------------------------------------------- /src/assets/images/splash_iphone6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CYPIAPT-LNDSE/Breathe-With-Me/HEAD/src/assets/images/splash_iphone6.png -------------------------------------------------------------------------------- /src/assets/images/splash_iphone6plus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CYPIAPT-LNDSE/Breathe-With-Me/HEAD/src/assets/images/splash_iphone6plus.png -------------------------------------------------------------------------------- /public/30799efa5bf74129468ad4e257551dc3.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CYPIAPT-LNDSE/Breathe-With-Me/HEAD/public/30799efa5bf74129468ad4e257551dc3.eot -------------------------------------------------------------------------------- /public/3b813c2ae0d04909a33a18d792912ee7.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CYPIAPT-LNDSE/Breathe-With-Me/HEAD/public/3b813c2ae0d04909a33a18d792912ee7.woff -------------------------------------------------------------------------------- /public/46e48ce0628835f68a7369d0254e4283.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CYPIAPT-LNDSE/Breathe-With-Me/HEAD/public/46e48ce0628835f68a7369d0254e4283.ttf -------------------------------------------------------------------------------- /public/4d9f3f9e5195e7b074bb63ba4ce42208.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CYPIAPT-LNDSE/Breathe-With-Me/HEAD/public/4d9f3f9e5195e7b074bb63ba4ce42208.eot -------------------------------------------------------------------------------- /public/7500519de3d82e33d1587f8042e2afcb.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CYPIAPT-LNDSE/Breathe-With-Me/HEAD/public/7500519de3d82e33d1587f8042e2afcb.woff -------------------------------------------------------------------------------- /public/894a2ede85a483bf9bedefd4db45cdb9.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CYPIAPT-LNDSE/Breathe-With-Me/HEAD/public/894a2ede85a483bf9bedefd4db45cdb9.ttf -------------------------------------------------------------------------------- /public/94998475f6aea65f558494802416c1cf.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CYPIAPT-LNDSE/Breathe-With-Me/HEAD/public/94998475f6aea65f558494802416c1cf.ttf -------------------------------------------------------------------------------- /public/a990f611f2305dc12965f186c2ef2690.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CYPIAPT-LNDSE/Breathe-With-Me/HEAD/public/a990f611f2305dc12965f186c2ef2690.eot -------------------------------------------------------------------------------- /public/ba3dcd8903e3d0af5de7792777f8ae0d.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CYPIAPT-LNDSE/Breathe-With-Me/HEAD/public/ba3dcd8903e3d0af5de7792777f8ae0d.woff -------------------------------------------------------------------------------- /public/bc540e50b175a23a39a1b3bffc6e0785.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CYPIAPT-LNDSE/Breathe-With-Me/HEAD/public/bc540e50b175a23a39a1b3bffc6e0785.png -------------------------------------------------------------------------------- /public/dc81817def276b4f21395f7ea5e88dcd.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CYPIAPT-LNDSE/Breathe-With-Me/HEAD/public/dc81817def276b4f21395f7ea5e88dcd.woff -------------------------------------------------------------------------------- /public/df7b648ce5356ea1ebce435b3459fd60.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CYPIAPT-LNDSE/Breathe-With-Me/HEAD/public/df7b648ce5356ea1ebce435b3459fd60.ttf -------------------------------------------------------------------------------- /public/dfe56a876d0282555d1e2458e278060f.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CYPIAPT-LNDSE/Breathe-With-Me/HEAD/public/dfe56a876d0282555d1e2458e278060f.eot -------------------------------------------------------------------------------- /public/e31fcf1885e371e19f5786c2bdfeae1b.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CYPIAPT-LNDSE/Breathe-With-Me/HEAD/public/e31fcf1885e371e19f5786c2bdfeae1b.ttf -------------------------------------------------------------------------------- /public/ecdd509cadbf1ea78b8d2e31ec52328c.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CYPIAPT-LNDSE/Breathe-With-Me/HEAD/public/ecdd509cadbf1ea78b8d2e31ec52328c.eot -------------------------------------------------------------------------------- /public/fc78759e93a6cac50458610e3d9d63a0.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CYPIAPT-LNDSE/Breathe-With-Me/HEAD/public/fc78759e93a6cac50458610e3d9d63a0.woff -------------------------------------------------------------------------------- /public/2751ee43015f9884c3642f103b7f70c9.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CYPIAPT-LNDSE/Breathe-With-Me/HEAD/public/2751ee43015f9884c3642f103b7f70c9.woff2 -------------------------------------------------------------------------------- /public/39b2c3031be6b4ea96e2e3e95d307814.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CYPIAPT-LNDSE/Breathe-With-Me/HEAD/public/39b2c3031be6b4ea96e2e3e95d307814.woff2 -------------------------------------------------------------------------------- /public/574fd0b50367f886d359e8264938fc37.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CYPIAPT-LNDSE/Breathe-With-Me/HEAD/public/574fd0b50367f886d359e8264938fc37.woff2 -------------------------------------------------------------------------------- /public/69f8a0617ac472f78e45841323a3df9e.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CYPIAPT-LNDSE/Breathe-With-Me/HEAD/public/69f8a0617ac472f78e45841323a3df9e.woff2 -------------------------------------------------------------------------------- /public/954bbdeb86483e4ffea00c4591530ece.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CYPIAPT-LNDSE/Breathe-With-Me/HEAD/public/954bbdeb86483e4ffea00c4591530ece.woff2 -------------------------------------------------------------------------------- /public/Podington_Bear_-_Floating_In_Space.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CYPIAPT-LNDSE/Breathe-With-Me/HEAD/public/Podington_Bear_-_Floating_In_Space.mp3 -------------------------------------------------------------------------------- /src/templates/avatars/index.js: -------------------------------------------------------------------------------- 1 | import cat from './cat.html'; 2 | import dog from './dog.html'; 3 | import panda from './panda.html'; 4 | 5 | export { cat, dog, panda }; 6 | -------------------------------------------------------------------------------- /src/assets/sounds/Podington_Bear_-_Floating_In_Space.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CYPIAPT-LNDSE/Breathe-With-Me/HEAD/src/assets/sounds/Podington_Bear_-_Floating_In_Space.mp3 -------------------------------------------------------------------------------- /src/css/partials/_fonts.scss: -------------------------------------------------------------------------------- 1 | $Lato: 'Lato', sans-serif; 2 | $Amatic: 'Amatic SC', cursive; 3 | $Open-sans: 'Open Sans Condensed', sans-serif; 4 | $Raleway: 'Raleway', sans-serif; 5 | -------------------------------------------------------------------------------- /src/css/partials/_colors.scss: -------------------------------------------------------------------------------- 1 | $background-landing: #CEF7F0; 2 | $background-intro: #5CA1C2; 3 | $background-start: #CADFFF; 4 | $buttons: #5CA1C2; 5 | $main-text: #1C6C89; 6 | $background-hello: #5CA1C2; 7 | $modal-menu: rgba(28, 108, 137, 76); 8 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/globalState.js: -------------------------------------------------------------------------------- 1 | export const getState = () => { 2 | return JSON.parse(localStorage.getItem('state')) || {}; 3 | }; 4 | 5 | export const saveState = (state) => { 6 | const newState = Object.assign({}, getState(), state); 7 | localStorage.setItem('state', JSON.stringify(newState)); 8 | }; 9 | -------------------------------------------------------------------------------- /src/css/partials/_page.scss: -------------------------------------------------------------------------------- 1 | .page { 2 | width: 100%; 3 | max-width: inherit; 4 | max-height: inherit; 5 | min-height: 100%; 6 | overflow: hidden; 7 | position: fixed; 8 | -webkit-tap-highlight-color: rgba(0,0,0,0); 9 | } 10 | 11 | .page:last-child(1) { 12 | z-index: 10000; 13 | } 14 | -------------------------------------------------------------------------------- /src/controllers/index.js: -------------------------------------------------------------------------------- 1 | import landing from './landing'; 2 | import avatar from './avatar'; 3 | import narration from './narration'; 4 | import breathe from './breathing'; 5 | import welldone from './welldone'; 6 | import credits from './credits'; 7 | 8 | export { landing, avatar, narration, breathe, welldone, credits }; 9 | -------------------------------------------------------------------------------- /src/templates/index.js: -------------------------------------------------------------------------------- 1 | import landing from './landing.html'; 2 | import avatar from './avatar.html'; 3 | import narration from './narration.html'; 4 | import breathe from './breathing.html'; 5 | import welldone from './welldone.html'; 6 | import credits from './credits.html'; 7 | 8 | export { landing, avatar, narration, breathe, welldone, credits }; 9 | -------------------------------------------------------------------------------- /src/assets/images/breathingPage/Legs.svg: -------------------------------------------------------------------------------- 1 | Legs 2 | -------------------------------------------------------------------------------- /src/controllers/credits.js: -------------------------------------------------------------------------------- 1 | import { getState } from '../globalState'; 2 | import { granimInstance } from '../lib/background'; 3 | 4 | const creditsCtrl = () => { 5 | const { granimState } = getState(); 6 | 7 | document.getElementById('back-icon').addEventListener('click', () => { 8 | granimInstance().changeState(granimState); 9 | }); 10 | }; 11 | 12 | export default creditsCtrl; 13 | -------------------------------------------------------------------------------- /src/css/partials/_avatar.scss: -------------------------------------------------------------------------------- 1 | .avatar { 2 | font-size: 1em; 3 | position: absolute; 4 | left: 0; 5 | right: 0; 6 | margin: 0 auto; 7 | height: 100%; 8 | text-align: center; 9 | opacity: 0; 10 | } 11 | 12 | #avatar-header { 13 | font-family: $Amatic; 14 | font-size: 2.5em; 15 | position: absolute; 16 | left: 0; 17 | right: 0; 18 | margin: 0 auto; 19 | top: 3%; 20 | } 21 | 22 | #landing-material-icon { 23 | font-size: 2.6rem!important; 24 | } 25 | -------------------------------------------------------------------------------- /src/css/partials/_mountain-color.scss: -------------------------------------------------------------------------------- 1 | #mountain1.dog { 2 | fill: #FFF4F4; 3 | } 4 | 5 | #mountain2.dog { 6 | fill: #F3D4D4; 7 | } 8 | 9 | #mountain3.dog { 10 | fill: #FF9C9C; 11 | } 12 | 13 | #mountain1.cat { 14 | fill: #CEF7F0; 15 | } 16 | 17 | #mountain2.cat { 18 | fill: #3FA1B3; 19 | } 20 | 21 | #mountain3.cat { 22 | fill: #1C6C86; 23 | } 24 | 25 | #mountain1.panda { 26 | fill: #CFDCFF; 27 | } 28 | 29 | #mountain2.panda { 30 | fill: #A685B4; 31 | } 32 | 33 | #mountain3.panda { 34 | fill: #4942A5; 35 | } 36 | -------------------------------------------------------------------------------- /public/ea463d8bf1904b3cece6052283b04971.svg: -------------------------------------------------------------------------------- 1 | mountainBack 2 | -------------------------------------------------------------------------------- /public/7ef9009b51f2e3ced613809250f98a30.svg: -------------------------------------------------------------------------------- 1 | mountain welldone front -------------------------------------------------------------------------------- /src/assets/images/blueMountains/mountainBack.svg: -------------------------------------------------------------------------------- 1 | mountainBack 2 | -------------------------------------------------------------------------------- /src/controllers/welldone.js: -------------------------------------------------------------------------------- 1 | import { welldoneToIntro, breathingToWelldone } from '../animations'; 2 | import { granimInstance } from '../lib/background'; 3 | import { getState } from '../globalState'; 4 | 5 | const welldoneCtrl = () => { 6 | const { granimState, name } = getState(); 7 | const startAgain = document.getElementById('start-again'); 8 | startAgain.addEventListener('click', () => { 9 | granimInstance().changeState(granimState); 10 | }); 11 | document.querySelector('.welldone-user').innerText = `${name}!`; 12 | }; 13 | 14 | export default welldoneCtrl; 15 | -------------------------------------------------------------------------------- /public/f079d603854bcd795eaabf2ef7ae723b.svg: -------------------------------------------------------------------------------- 1 | mountain welldone middle -------------------------------------------------------------------------------- /src/assets/images/redMountains/mountainWelldoneFront.svg: -------------------------------------------------------------------------------- 1 | mountain welldone front -------------------------------------------------------------------------------- /src/assets/images/redMountains/mountainWelldoneMiddle.svg: -------------------------------------------------------------------------------- 1 | mountain welldone middle -------------------------------------------------------------------------------- /src/templates/credits.html: -------------------------------------------------------------------------------- 1 |
2 |

Breathe with Me was first created during a hackathon hosted by the London and South East CYP IAPT Learning Collaborative in September 2016. The resulting app is a collaboration between the Learning Collaborative and Founders and Coders. For more information about the product and to provide feedback, please email

hack@annafreud.org

3 | arrow_back 4 |
5 | -------------------------------------------------------------------------------- /public/3c546bf7e955aeaf180bca3d2743e204.svg: -------------------------------------------------------------------------------- 1 | mountian front2 2 | -------------------------------------------------------------------------------- /src/assets/images/blueMountains/mountainFront2.svg: -------------------------------------------------------------------------------- 1 | mountian front2 2 | -------------------------------------------------------------------------------- /public/0bffba416891219267efcf4e59c3c015.svg: -------------------------------------------------------------------------------- 1 | mountain welldone back -------------------------------------------------------------------------------- /src/assets/images/redMountains/mountainWelldoneBack.svg: -------------------------------------------------------------------------------- 1 | mountain welldone back -------------------------------------------------------------------------------- /src/css/partials/_credits.scss: -------------------------------------------------------------------------------- 1 | #credits-icon { 2 | color: $main-text; 3 | bottom: 5px; 4 | right: 5px; 5 | position: absolute; 6 | z-index: 9999; 7 | opacity: 0.8; 8 | } 9 | 10 | .credits { 11 | opacity: 0; 12 | display: none; 13 | text-align: center; 14 | color: white; 15 | 16 | p { 17 | line-height: 2em; 18 | margin-top: 100px; 19 | margin-left: 20px; 20 | margin-right: 20px; 21 | 22 | } 23 | a { 24 | color: white; 25 | } 26 | } 27 | 28 | #back-icon { 29 | color: white; 30 | top: 5px; 31 | left: 5px; 32 | position: absolute; 33 | z-index: 9999; 34 | opacity: 0.8; 35 | } 36 | -------------------------------------------------------------------------------- /src/lib/audio.js: -------------------------------------------------------------------------------- 1 | const audio = document.getElementById('audio'); 2 | 3 | export const startAudio = () => { 4 | audio.play(); 5 | audio.volume = 1; 6 | }; 7 | 8 | export const toggleAudio = (e) => { 9 | if (e.target.textContent === 'volume_off') { 10 | e.target.textContent = 'volume_up'; 11 | audio.play(); 12 | } else { 13 | e.target.textContent = 'volume_off'; 14 | audio.pause(); 15 | } 16 | }; 17 | 18 | export const fadeoutMusic = (int) => { 19 | let vol = 1; 20 | const interval = int; 21 | 22 | const fadeout = setInterval(() => { 23 | if (vol >= 0.05) { 24 | vol -= 0.05; 25 | audio.volume = vol; 26 | } else { 27 | clearInterval(fadeout); 28 | audio.volume = 0; 29 | } 30 | }, interval); 31 | }; 32 | -------------------------------------------------------------------------------- /src/templates/landing.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | 5 | 6 |
7 |

Hello, What is your name?

8 | 9 |

Please enter a name!

10 | Submit 11 |
12 |
13 | -------------------------------------------------------------------------------- /src/controllers/narration.js: -------------------------------------------------------------------------------- 1 | import { getState } from '../globalState'; 2 | import { granimInstance } from '../lib/background'; 3 | 4 | const narrationCtrl = () => { 5 | const { name, granimState } = getState(); 6 | const mountains = $('.mountain'); 7 | 8 | granimInstance().changeState(granimState); 9 | 10 | switch (granimState) { 11 | case 'default-state': 12 | mountains.addClass('cat'); 13 | break; 14 | case 'dog-state': 15 | mountains.addClass('dog'); 16 | break; 17 | case 'panda-state': 18 | mountains.addClass('panda'); 19 | break; 20 | 21 | default: 22 | mountains.addClass('cat'); 23 | } 24 | 25 | document.querySelector('#landing-username').innerText = `Hi ${name}`; 26 | }; 27 | 28 | export default narrationCtrl; 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | **/.DS_Store 2 | # Logs 3 | logs 4 | *.log 5 | npm-debug.log* 6 | 7 | # Runtime data 8 | pids 9 | *.pid 10 | *.seed 11 | 12 | # Directory for instrumented libs generated by jscoverage/JSCover 13 | lib-cov 14 | 15 | # Coverage directory used by tools like istanbul 16 | coverage 17 | 18 | # nyc test coverage 19 | .nyc_output 20 | 21 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 22 | .grunt 23 | 24 | # node-waf configuration 25 | .lock-wscript 26 | 27 | # Compiled binary addons (http://nodejs.org/api/addons.html) 28 | build/Release 29 | 30 | # Dependency directories 31 | node_modules 32 | jspm_packages 33 | 34 | # Optional npm cache directory 35 | .npm 36 | 37 | # Optional REPL history 38 | .node_repl_history 39 | 40 | .sass-cache 41 | 42 | style.css.map 43 | 44 | 45 | style.min.css 46 | -------------------------------------------------------------------------------- /src/controllers/landing.js: -------------------------------------------------------------------------------- 1 | import isNameInvalid from '../lib/validation'; 2 | import { saveState, getState } from '../globalState'; 3 | 4 | const landingCtrl = () => { 5 | const nameInput = document.querySelector('#input-username') 6 | const { hasVisited, name, avatar } = getState(); 7 | if (name !== undefined) { 8 | nameInput.value = name; 9 | } 10 | 11 | const nameSubmitButton = document.getElementById('name-question-button'); 12 | 13 | nameSubmitButton.addEventListener('click', function (event) { 14 | event.preventDefault(); 15 | const name = document.querySelector('#input-username').value; 16 | if (isNameInvalid(name.trim().toLowerCase())) return; 17 | else { 18 | saveState({ name }); 19 | location.hash = this.attributes.getNamedItem('href').value; 20 | } 21 | }); 22 | }; 23 | 24 | export default landingCtrl; 25 | -------------------------------------------------------------------------------- /public/7ef525e40bfdaf62100e47dd4be50351.svg: -------------------------------------------------------------------------------- 1 | mountain middle 2 | -------------------------------------------------------------------------------- /src/assets/images/blueMountains/mountainMiddle.svg: -------------------------------------------------------------------------------- 1 | mountain middle 2 | -------------------------------------------------------------------------------- /src/css/partials/_buttons.scss: -------------------------------------------------------------------------------- 1 | 2 | .btn-flat{ 3 | border-radius: 23px!important; 4 | border: 2px solid white!important; 5 | color: white!important; 6 | margin-top: 10%; 7 | font-size: 16px!important; 8 | height: 40px; 9 | font-family: $Raleway; 10 | } 11 | 12 | .btn, .btn-large, .btn-flat { 13 | line-height: 30px!important; 14 | } 15 | 16 | .start { 17 | background-color: $background-start; 18 | background-size: cover; 19 | background-repeat: no-repeat; 20 | background-image: url('../assets/images/breathingBG.png'); 21 | text-align: center; 22 | } 23 | 24 | #landing-button { 25 | opacity: 0; 26 | z-index: 9999; 27 | position: absolute; 28 | left: 0; 29 | right: 0; 30 | margin: 0 auto; 31 | bottom: 15%; 32 | background-color: $main-text; 33 | } 34 | 35 | 36 | @media screen and (max-height: 600px) { 37 | #landing-button { 38 | bottom: 6%; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/templates/welldone.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | blinking stars 4 | banner says well done 5 |
6 | START AGAIN 7 | 8 | 9 | 10 | info_outline 11 |
12 | -------------------------------------------------------------------------------- /src/lib/validation.js: -------------------------------------------------------------------------------- 1 | import blacklist from '../lib/blacklist'; 2 | 3 | const notifyUser = (notification) => { 4 | const inputValidation = document.getElementById('input-validation'); 5 | inputValidation.textContent = notification; 6 | inputValidation.style.visibility = 'visible'; 7 | return true; 8 | }; 9 | 10 | const isNameInvalid = (name) => { 11 | const nameIsEmpty = !name.length; 12 | let nameIsBlacklisted = [name].some(word => !!blacklist[word]); 13 | if (!nameIsBlacklisted) nameIsBlacklisted = name.split(/\s/).some(word => !!blacklist[word]); 14 | 15 | if (name.length > 30) return notifyUser('Maximum 30 characters!'); 16 | else if (nameIsEmpty) return notifyUser('Please enter a name!'); 17 | else if (nameIsBlacklisted) return notifyUser('Please enter a different name!'); 18 | else { 19 | document.getElementById('input-validation').style.visibility = 'hidden'; 20 | return false; 21 | } 22 | }; 23 | 24 | export default isNameInvalid; 25 | -------------------------------------------------------------------------------- /src/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "lang": "en", 3 | "dir": "ltr", 4 | "description": "Help to relieve anxiety with the help of kitty", 5 | "short_name": "Breathe With Me", 6 | "name": "Breathe With Me", 7 | "start_url": "/", 8 | "icons": [{ 9 | "src": "./catLanding.png", 10 | "sizes": "128x128", 11 | "type": "image/png" 12 | }, { 13 | "src": "./catLanding.png", 14 | "sizes": "152x152", 15 | "type": "image/png" 16 | }, { 17 | "src": "./catLanding.png", 18 | "sizes": "144x144", 19 | "type": "image/png" 20 | }, { 21 | "src": "./catLanding.png", 22 | "sizes": "192x192", 23 | "type": "image/png" 24 | },{ 25 | "src": "./catLanding.png", 26 | "sizes": "384x384", 27 | "type": "image/png" 28 | }], 29 | "display": "standalone", 30 | "orientation": "portrait", 31 | "background_color": "#A5E2DA", 32 | "theme_color": "#1C6C86" 33 | } 34 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "lang": "en", 3 | "dir": "ltr", 4 | "description": "Help to relieve anxiety with the help of kitty", 5 | "short_name": "Breathe With Me", 6 | "name": "Breathe With Me", 7 | "start_url": "/", 8 | "icons": [{ 9 | "src": "./catLanding.png", 10 | "sizes": "128x128", 11 | "type": "image/png" 12 | }, { 13 | "src": "./catLanding.png", 14 | "sizes": "152x152", 15 | "type": "image/png" 16 | }, { 17 | "src": "./catLanding.png", 18 | "sizes": "144x144", 19 | "type": "image/png" 20 | }, { 21 | "src": "./catLanding.png", 22 | "sizes": "192x192", 23 | "type": "image/png" 24 | },{ 25 | "src": "./catLanding.png", 26 | "sizes": "384x384", 27 | "type": "image/png" 28 | }], 29 | "display": "standalone", 30 | "orientation": "portrait", 31 | "background_color": "#A5E2DA", 32 | "theme_color": "#1C6C86" 33 | } 34 | -------------------------------------------------------------------------------- /src/templates/avatar.html: -------------------------------------------------------------------------------- 1 |
2 | Select an animal 3 | 19 | pets 20 |
21 | -------------------------------------------------------------------------------- /src/lib/transitions.js: -------------------------------------------------------------------------------- 1 | import * as animations from '../animations/'; 2 | 3 | const viewTransition = (container, view, controller) => { 4 | const animateout = $(container).find('.page').data('animate-out'); 5 | const animatein = $(view).data('animate-in'); 6 | const resolve = Promise.resolve.bind(Promise); 7 | // Overlay is an empty div that prevents you from clicking items before they are mounted 8 | const overlay = $('#overlay'); 9 | 10 | return resolve() 11 | // Before add new page 12 | .then(() => { 13 | overlay.addClass('show'); 14 | return (animations[animateout] || resolve)(); 15 | }) 16 | .then(() => $(container).append(view)) 17 | // Binding event listeners to the view 18 | .then(() => controller && controller()) 19 | // Before remove the old view 20 | .then(() => (animations[animatein] || resolve)()) 21 | .then(() => { 22 | overlay.removeClass('show'); 23 | return $(container).html($(container).find('.page').last()); 24 | }); 25 | }; 26 | 27 | export default viewTransition; 28 | -------------------------------------------------------------------------------- /src/controllers/avatar.js: -------------------------------------------------------------------------------- 1 | import carousel from '../lib/carousel'; 2 | import { saveState } from '../globalState'; 3 | import * as avatars from '../templates/avatars'; 4 | 5 | const avatarCtrl = () => { 6 | const landingButton = document.getElementById('landing-button'); 7 | landingButton.addEventListener('click', () => { 8 | const avatarIndex = $('.active').index('.carousel_item'); 9 | let avatar; 10 | let granimState; 11 | switch (avatarIndex) { 12 | case 0: 13 | avatar = avatars.cat; 14 | granimState = 'default-state'; 15 | break; 16 | case 1: 17 | avatar = avatars.dog; 18 | granimState = 'dog-state'; 19 | break; 20 | case 2: 21 | avatar = avatars.panda; 22 | granimState = 'panda-state'; 23 | break; 24 | default: 25 | avatar = avatars.cat; 26 | granimState = 'default-state'; 27 | break; 28 | } 29 | saveState({ avatar, granimState }); 30 | }); 31 | const car = carousel(); 32 | car.init(); 33 | }; 34 | 35 | export default avatarCtrl; 36 | -------------------------------------------------------------------------------- /src/assets/images/breathingPage/leftHand.svg: -------------------------------------------------------------------------------- 1 | leftHand -------------------------------------------------------------------------------- /src/assets/images/breathingPage/rightHand.svg: -------------------------------------------------------------------------------- 1 | rightHand -------------------------------------------------------------------------------- /src/assets/images/breathingPage/breathingBelly.svg: -------------------------------------------------------------------------------- 1 | belly 2 | -------------------------------------------------------------------------------- /src/css/partials/_landing.scss: -------------------------------------------------------------------------------- 1 | #mountain1, 2 | #mountain2, 3 | #mountain3 { 4 | width: 100%; 5 | position: absolute; 6 | bottom: -80px; 7 | will-change: transform; 8 | } 9 | 10 | #mountain1 { 11 | z-index: 0; 12 | } 13 | 14 | #mountain2 { 15 | z-index: -1; 16 | } 17 | 18 | #mountain3 { 19 | z-index: -3; 20 | } 21 | 22 | .text-box { 23 | opacity: 0; 24 | color: white; 25 | position: absolute; 26 | left: 0; 27 | right: 0; 28 | margin: 2em auto 0; 29 | width: 60%; 30 | text-align: center; 31 | } 32 | 33 | .breathing-information { 34 | width: 95%; 35 | color: white; 36 | margin: 5px auto 0; 37 | text-align: center; 38 | font-size: 1.2em; 39 | } 40 | 41 | .breathing-start{ 42 | margin-top: 1em; 43 | font-size: 1em; 44 | z-index: -1; 45 | } 46 | 47 | #input-validation { 48 | font-size: 10px; 49 | margin-top: 0px; 50 | margin-bottom: 5px; 51 | visibility: hidden; 52 | } 53 | 54 | #start-breathing-cat-button { 55 | display: block; 56 | width: 110px; 57 | height: 36px; 58 | margin: 1.5em auto; 59 | } 60 | 61 | #alt-info-box { 62 | margin-top: 100px; 63 | } 64 | 65 | #input-username { 66 | border-bottom: 2px solid #ffffff; 67 | margin-bottom: 0px !important; 68 | } 69 | -------------------------------------------------------------------------------- /src/assets/images/breathingPage/breathing-background.svg: -------------------------------------------------------------------------------- 1 | Breathing background 2 | -------------------------------------------------------------------------------- /src/templates/breathing.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 8 | 17 |
18 |

19 |
${avatar}
20 |
21 | I'M FEELING BETTER 22 |
23 |
24 | -------------------------------------------------------------------------------- /src/lib/background.js: -------------------------------------------------------------------------------- 1 | import Granim from 'granim'; 2 | 3 | const canvas = document.getElementById('bg'); 4 | 5 | let instance; 6 | 7 | export const granimInstance = () => 8 | instance || (instance = new Granim({ 9 | element: '#bg', 10 | name: 'granim', 11 | elToSetClassOn: 'body', 12 | direction: 'top-bottom', 13 | isPausedWhenNotInView: false, 14 | opacity: [1, 1], 15 | stateTransitionSpeed: 4500, 16 | states: { 17 | 'default-state': { 18 | gradients: [ 19 | ['#d1f3ef', '#6c9eb4'], 20 | ['#9ccdff', '#5ca1c2'], 21 | ], 22 | transitionSpeed: 9000, 23 | loop: true, 24 | }, 25 | 'dog-state': { 26 | gradients: [ 27 | ['#FDEDB3', '#FD9C9D'], 28 | ['#F2D4D4', '#DFC4D8'], 29 | ], 30 | transitionSpeed: 9000, 31 | loop: true, 32 | }, 33 | 'panda-state': { 34 | gradients: [ 35 | ['#A685B4', '#9C90FF'], 36 | ['#CFDCFF', '#4942A5'], 37 | ], 38 | transitionSpeed: 9000, 39 | loop: true, 40 | }, 41 | 'dark-state': { 42 | gradients: [ 43 | ['#091a3e', '#494a97'], 44 | ], 45 | transitionSpeed: 9000, 46 | loop: true, 47 | }, 48 | }, 49 | })); 50 | 51 | 52 | export const resizeCanvas = () => { 53 | canvas.width = window.innerWidth; 54 | canvas.height = window.innerHeight; 55 | granimInstance().getDimensions(); 56 | }; 57 | -------------------------------------------------------------------------------- /src/css/style.scss: -------------------------------------------------------------------------------- 1 | @import 'partials/_colors.scss'; 2 | @import 'partials/fonts.scss'; 3 | @import 'partials/_mountain-color.scss'; 4 | @import 'partials/_avatar.scss'; 5 | @import 'partials/_breathing.scss'; 6 | @import 'partials/_landing.scss'; 7 | @import 'partials/_buttons.scss'; 8 | @import 'partials/_welldone.scss'; 9 | @import 'partials/_page.scss'; 10 | @import 'partials/_carousel.scss'; 11 | @import 'partials/_credits.scss'; 12 | 13 | html, body { 14 | height: 100%; 15 | min-height: 100%; 16 | padding: 0; 17 | margin: 0; 18 | } 19 | 20 | h1, h2, h3, h4, h5, h6 { 21 | font-family: $Amatic; 22 | } 23 | 24 | p { 25 | font-family: 'Raleway', sans-serif; 26 | } 27 | 28 | body { 29 | font-family: 'Raleway', sans-serif; 30 | color: $main-text; 31 | overflow: hidden; 32 | height: 100%; 33 | width: 100%; 34 | letter-spacing: 1.4px; 35 | background-color: transparent; 36 | } 37 | 38 | // helpers 39 | .hidden { 40 | visibility: hidden; 41 | } 42 | 43 | .animated { 44 | -webkit-transform: translate3d(0, 0, 0); 45 | will-change: transform; 46 | } 47 | 48 | #app { 49 | height: 100%; 50 | min-height: 100%; 51 | max-height: 700px; 52 | max-width: 414px; 53 | margin: 0 auto; 54 | background-size: cover; 55 | background-repeat: no-repeat; 56 | } 57 | 58 | #bg { 59 | position: absolute; 60 | left: 0; 61 | right: 0; 62 | top: 0; 63 | bottom: 0; 64 | } 65 | 66 | #overlay { 67 | position: absolute; 68 | z-index: 99999999; 69 | top: 0; 70 | bottom: 0; 71 | left: 0; 72 | right: 0; 73 | display: none; 74 | &.show { 75 | display: initial; 76 | } 77 | } 78 | 79 | @media screen and (min-height: 900px) { 80 | #app { 81 | margin-top: 100px; 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/css/partials/_welldone.scss: -------------------------------------------------------------------------------- 1 | .welldone { 2 | height: 100%; 3 | background-color: transparent; 4 | text-align: center; 5 | justify-content: center; 6 | align-items: center; 7 | flex-direction: column; 8 | } 9 | 10 | #welldone-banner { 11 | position: absolute; 12 | opacity: 0; 13 | top: 0; 14 | left: 20%; 15 | width: 60%; 16 | z-index: 50; 17 | } 18 | 19 | #welldone-stars { 20 | transform: scale(0); 21 | opacity: 0; 22 | position: absolute; 23 | z-index: 10; 24 | top: 5.5vh; 25 | left: 10%; 26 | width: 80%; 27 | } 28 | 29 | #start-again { 30 | border: 2px solid white !important; 31 | border-radius: 23px; 32 | color: white !important; 33 | left: 0; 34 | right: 0; 35 | bottom: 52%; 36 | width: 146px; 37 | height: 40px; 38 | text-align: center; 39 | line-height: 37px; 40 | font-family: $Raleway; 41 | margin: 0 auto; 42 | z-index: 50; 43 | position: absolute; 44 | } 45 | 46 | .welldone-user { 47 | z-index: 10; 48 | color: white; 49 | height: 30%; 50 | width: 60%; 51 | text-align: center; 52 | position: absolute; 53 | bottom: 45%; 54 | left: 20%; 55 | font-size: 2.5em; 56 | font-family: $Amatic; 57 | } 58 | 59 | .material-icons { 60 | color: white; 61 | } 62 | 63 | .welldone-mountain1, 64 | .welldone-mountain2, 65 | .welldone-mountain3{ 66 | left: 0; 67 | right: 0; 68 | width: 100%; 69 | bottom: -480px; 70 | position: absolute; 71 | margin: 0 auto; 72 | max-width: inherit; 73 | will-change: transform; 74 | } 75 | 76 | .welldone-mountain1 { 77 | z-index: 10; 78 | opacity: 0; 79 | } 80 | 81 | .welldone-mountain2 { 82 | z-index: 5; 83 | opacity: 0; 84 | } 85 | 86 | .welldone-mountain3 { 87 | z-index: 2; 88 | opacity: 0; 89 | } 90 | -------------------------------------------------------------------------------- /public/breathe.appcache: -------------------------------------------------------------------------------- 1 | CACHE MANIFEST 2 | # f46526f436966438f293 3 | 4 | bc540e50b175a23a39a1b3bffc6e0785.png 5 | ecdd509cadbf1ea78b8d2e31ec52328c.eot 6 | a990f611f2305dc12965f186c2ef2690.eot 7 | 4d9f3f9e5195e7b074bb63ba4ce42208.eot 8 | 30799efa5bf74129468ad4e257551dc3.eot 9 | dfe56a876d0282555d1e2458e278060f.eot 10 | e31fcf1885e371e19f5786c2bdfeae1b.ttf 11 | 46e48ce0628835f68a7369d0254e4283.ttf 12 | 894a2ede85a483bf9bedefd4db45cdb9.ttf 13 | df7b648ce5356ea1ebce435b3459fd60.ttf 14 | 94998475f6aea65f558494802416c1cf.ttf 15 | ea463d8bf1904b3cece6052283b04971.svg 16 | 3c546bf7e955aeaf180bca3d2743e204.svg 17 | 7ef525e40bfdaf62100e47dd4be50351.svg 18 | a50883a468e8135225246c36c66e3ae9.svg 19 | 91a5bede829a01dc1e936a82b6b58c00.svg 20 | 4567056d902c51f86d61770d9d60f59f.svg 21 | 0bffba416891219267efcf4e59c3c015.svg 22 | 7ef9009b51f2e3ced613809250f98a30.svg 23 | f079d603854bcd795eaabf2ef7ae723b.svg 24 | 4ef2d0f486deef23b3928cab2430e55b.svg 25 | 7d0026955ab24fa892b84b6df7cc6935.svg 26 | dc81817def276b4f21395f7ea5e88dcd.woff 27 | 39b2c3031be6b4ea96e2e3e95d307814.woff2 28 | 3b813c2ae0d04909a33a18d792912ee7.woff 29 | 69f8a0617ac472f78e45841323a3df9e.woff2 30 | fc78759e93a6cac50458610e3d9d63a0.woff 31 | 574fd0b50367f886d359e8264938fc37.woff2 32 | ba3dcd8903e3d0af5de7792777f8ae0d.woff 33 | 2751ee43015f9884c3642f103b7f70c9.woff2 34 | 7500519de3d82e33d1587f8042e2afcb.woff 35 | 954bbdeb86483e4ffea00c4591530ece.woff2 36 | bundle.js 37 | bundle.css 38 | 39 | CACHE: 40 | breathe.appcache 41 | manifest.json 42 | catLanding.png 43 | Podington_Bear_-_Floating_In_Space.mp3 44 | splash.png 45 | splash_iphone6.png 46 | splash_iphone6plus.png 47 | splash_ipad.png 48 | https://fonts.googleapis.com/css?family=Lato 49 | https://fonts.googleapis.com/icon?family=Material+Icons 50 | 51 | NETWORK: 52 | * 53 | 54 | SETTINGS: 55 | prefer-online 56 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import render from 'es6-template-render'; 2 | import * as controllers from './controllers'; 3 | import * as views from './templates'; 4 | import viewTransition from './lib/transitions'; 5 | import { resizeCanvas } from './lib/background'; 6 | import { getState } from './globalState'; 7 | 8 | // Hack to prevent select default on IOS 9 | 10 | document.addEventListener('touchstart', function () {}, true); 11 | 12 | const router = (route) => { 13 | const view = views[route] || views.landing; 14 | const controller = controllers[route] || controllers.landing; 15 | return { view, controller }; 16 | }; 17 | 18 | const App = document.querySelector('#app'); 19 | 20 | let currentView = undefined; 21 | 22 | const changeView = () => { 23 | const { name, avatar } = getState(); 24 | 25 | // views after '#' and '#avatar' require avatar state. 26 | if (avatar === undefined && ( 27 | location.hash === '#breathe' || location.hash === '#narration')) { 28 | location.hash = '#avatar'; 29 | return; 30 | } 31 | // redirect to '#' if name is missing 32 | if (name === undefined && location.hash !== '') { 33 | location.hash = '#'; 34 | return; 35 | } 36 | 37 | const { hash } = location; 38 | const route = hash.replace('#', ''); 39 | const { view, controller } = router(route); 40 | if (view === currentView) return; 41 | currentView = view; 42 | const viewRendered = render(view, getState() || {}); 43 | 44 | viewTransition(App, viewRendered, controller); 45 | }; 46 | 47 | window.addEventListener('hashchange', changeView); 48 | window.addEventListener('resize', resizeCanvas); 49 | 50 | 51 | window.addEventListener('load', () => setTimeout(() => { 52 | resizeCanvas(); 53 | 54 | const { name } = getState(); 55 | 56 | // if name state exist skip ahead to '#avatar' 57 | if (name !== undefined && location.hash !== '#avatar') { 58 | location.hash = '#avatar'; 59 | return; 60 | } 61 | 62 | changeView(); 63 | }, 0)); 64 | -------------------------------------------------------------------------------- /src/controllers/breathing.js: -------------------------------------------------------------------------------- 1 | import Granim from 'granim'; 2 | import { granimInstance } from '../lib/background'; 3 | import { getState } from '../globalState'; 4 | import { 5 | notifications, 6 | saveStateHasVisited, 7 | } from '../lib/breathingtimer'; 8 | import { 9 | breathe, 10 | headMovement, 11 | } from '../animations'; 12 | import breathingMenu from '../lib/breathingmenu'; 13 | import { 14 | toggleAudio, 15 | fadeoutMusic, 16 | startAudio, 17 | } from '../lib/audio'; 18 | 19 | const breatheCtrl = () => { 20 | const feelingBetterBtn = document.getElementById('feel-good-button'); 21 | const breathingPage = document.getElementsByClassName('breathing')[0]; 22 | const audioControl = document.getElementById('audio-controls'); 23 | const instructions = document.getElementById('breathing-info'); 24 | const exitModalButton = document.getElementById('exit-modal-button'); 25 | const settings = document.getElementById('breathing-settings'); 26 | 27 | feelingBetterBtn.addEventListener('click', () => { 28 | notifications.resetTick(); 29 | clearTimeout(breathingMenu.timer); 30 | granimInstance().changeState('dark-state'); 31 | fadeoutMusic(260); 32 | }); 33 | settings.addEventListener('click', () => { 34 | notifications.resetTick(); 35 | clearTimeout(breathingMenu.timer); 36 | fadeoutMusic(280); 37 | }); 38 | breathingPage.addEventListener('click', (e) => { 39 | breathingMenu.toggleBreathingMenu(e); 40 | }); 41 | instructions.addEventListener('click', () => { 42 | breathingMenu.toggleModal(); 43 | }); 44 | exitModalButton.addEventListener('click', () => { 45 | breathingMenu.toggleModal(); 46 | saveStateHasVisited(); 47 | }); 48 | audioControl.addEventListener('click', toggleAudio); 49 | 50 | breathe(); 51 | headMovement(); 52 | startAudio(); 53 | if (!getState().hasVisited) breathingMenu.toggleModal(); 54 | else { 55 | breathingMenu.setHideMenuTimer(7000); 56 | } 57 | }; 58 | 59 | export default breatheCtrl; 60 | -------------------------------------------------------------------------------- /src/assets/images/breathingPage/twoHands.svg: -------------------------------------------------------------------------------- 1 | twho hand breathing 2 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "breathing-with-kitty", 3 | "version": "1.0.0", 4 | "description": "App for kids to help reduce anxiety", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "webpack-dev-server --inline --host 0.0.0.0 --content-base public", 8 | "prebuild": "rm -rf public/*", 9 | "build": "webpack -p --optimize-minimize", 10 | "test": "-" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "git+https://github.com/FAC8/breathing-with-kitty.git" 15 | }, 16 | "keywords": [ 17 | "kids", 18 | "pwa", 19 | "animation" 20 | ], 21 | "author": "Sofia Pohjalainen, Bradley Reeder, Noga Inbar, Gabriel Perales, Kara de la Marck", 22 | "license": "ISC", 23 | "bugs": { 24 | "url": "https://github.com/FAC8/breathing-with-kitty/issues" 25 | }, 26 | "homepage": "https://github.com/FAC8/breathing-with-kitty#readme", 27 | "devDependencies": { 28 | "appcache-webpack-plugin": "^1.3.0", 29 | "autoprefixer": "^6.5.1", 30 | "babel": "^6.5.2", 31 | "babel-core": "^6.17.0", 32 | "babel-loader": "^6.2.5", 33 | "babel-polyfill": "^6.16.0", 34 | "babel-preset-es2015": "^6.16.0", 35 | "browser-sync": "^2.15.0", 36 | "copy-webpack-plugin": "^3.0.1", 37 | "css-loader": "^0.25.0", 38 | "es6-template-render": "1.X", 39 | "eslint": "^3.7.1", 40 | "eslint-config-airbnb": "^12.0.0", 41 | "eslint-plugin-import": "^2.0.0", 42 | "eslint-plugin-jsx-a11y": "^2.2.2", 43 | "eslint-plugin-react": "^6.3.0", 44 | "extract-text-webpack-plugin": "^1.0.1", 45 | "file-loader": "^0.9.0", 46 | "granim": "^1.0.6", 47 | "gsap": "^1.19.0", 48 | "html-loader": "^0.4.4", 49 | "html-webpack-plugin": "^2.22.0", 50 | "jquery": "^3.1.1", 51 | "materialize-css": "^0.97.7", 52 | "materialize-loader": "^1.1.1", 53 | "node-neat": "^1.7.2", 54 | "node-sass": "^3.10.1", 55 | "postcss-loader": "^1.0.0", 56 | "resolve-url-loader": "^1.6.0", 57 | "style-loader": "^0.13.1", 58 | "sw-precache": "^3.2.0", 59 | "sw-precache-webpack-plugin": "^0.5.1", 60 | "sw-toolbox": "^3.2.1", 61 | "template-string-loader": "0.0.3", 62 | "url-loader": "^0.5.7", 63 | "webpack": "^1.13.2", 64 | "webpack-dev-server": "^1.16.1", 65 | "webpack-sass-loader": "^2.1.1" 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Breathe With Me 2 | 3 | 4 | This app helps young people to manage anxiety, offers them support, and builds their resilience through the breathing techniques that appear as interactive animations. Children and young people will have the tools at their fingertips to reduce anxiety, monitor their wellbeing, and manage their mental state in between appointments and away from services. 5 | 6 | The application is expected to be used at home or on the go as needed. It may also be deployed in a therapy session. The Breathing with Kitty app is openly available and would be helpful to many individuals, not just those children currently accessing CAMHS. 7 | 8 | ## Why? 9 | 10 | According to Anxiety UK, as many as 1 in 6 young people will experience an anxiety problem at some point in their life. Anxiety is a feeling of fear or panic and young people will usually experience anxiety in three ways: 11 | 12 | * generalised anxiety disorder (GAD) 13 | * panic attacks 14 | * phobias 15 | 16 | GAD alone affects 1 in 25 people in the UK. Young people who have GAD worry a lot of the time and this anxiety makes doing everyday things difficult. 17 | 18 | ## Who? 19 | 20 | Breathing with Kitty is aimed at young people age 10-14 who are able to take self-directed learning. Although primarily aimed at those CYP accessing services, this app could also be used as a general wellbeing tool for CYP in the same age group. The app will be publicly available and therefore is a public asset to promote self-care and methods to reduce anxiety through focus on the breath. 21 | 22 | ## What? 23 | 24 | The prototype of Breathing with Kitty will focus on providing a solution for GAD (Generalised Anxiety Disorder). 25 | 26 | Features of the prototype will include: 27 | 28 | * Storage of name so that user is greeted at each login. 29 | * Instructions on how to practice deep abdominal breathing. 30 | * Animations of animals taking long slow inhalations and exhalations to which users will be directed to sync their own breathing. This forms the core of the animated game Breath With Me. 31 | * The option to choose between multiple avatars - a cat, dog, and panda - for the breathing exercise. 32 | * Soothing background music, with option to turn it off and on. 33 | 34 | ## Built With 35 | 36 | The product will be developed as a PWA for full accessibility. 37 | -------------------------------------------------------------------------------- /src/lib/breathingmenu.js: -------------------------------------------------------------------------------- 1 | import { getState } from '../globalState'; 2 | import { saveStateHasVisited } from './breathingtimer'; 3 | import { 4 | displayMenu, 5 | hideMenu, 6 | hideModal, 7 | showModal, 8 | } from '../animations/index'; 9 | 10 | const breathingMenu = {}; 11 | 12 | breathingMenu.timer = ''; 13 | 14 | breathingMenu.menuIsDisplayed = true; 15 | 16 | breathingMenu.modalIsDisplayed = false; 17 | 18 | breathingMenu.elementsThatResetTimer = [ 19 | 'audio-controls', 20 | 'breathing-menu', 21 | 'menu-options', 22 | 'feel-good-modal', 23 | ]; 24 | 25 | breathingMenu.elementsThatWontTriggerMenu = [ 26 | 'breathing-settings', 27 | 'breathing-info', 28 | 'feel-good-button', 29 | 'exit-modal-button', 30 | ]; 31 | 32 | breathingMenu.updateMenuState = function () { 33 | if (this.menuIsDisplayed) this.menuIsDisplayed = false; 34 | else this.menuIsDisplayed = true; 35 | }; 36 | 37 | breathingMenu.updateModalState = function () { 38 | if (this.modalIsDisplayed) this.modalIsDisplayed = false; 39 | else this.modalIsDisplayed = true; 40 | }; 41 | 42 | breathingMenu.setHideMenuTimer = function (time) { 43 | this.timer = setTimeout(() => { 44 | hideMenu(); 45 | this.updateMenuState(); 46 | }, time); 47 | }; 48 | 49 | breathingMenu.resetHideMenuTimer = function () { 50 | clearTimeout(this.timer); 51 | this.setHideMenuTimer(5000); 52 | }; 53 | 54 | breathingMenu.toggleBreathingMenu = function (e) { 55 | if (this.elementsThatWontTriggerMenu.includes(e.target.id)) return; 56 | else if (this.modalIsDisplayed) { 57 | this.toggleModal(); 58 | saveStateHasVisited(); 59 | } else if (!this.menuIsDisplayed) { 60 | displayMenu(); 61 | this.updateMenuState(); 62 | this.resetHideMenuTimer(); 63 | } else if (this.menuIsDisplayed && (this.elementsThatResetTimer.includes(e.target.id))) { 64 | this.resetHideMenuTimer(); 65 | } else if (this.menuIsDisplayed) { 66 | clearTimeout(this.timer); 67 | hideMenu(); 68 | this.updateMenuState(); 69 | } 70 | }; 71 | 72 | breathingMenu.toggleModal = function () { 73 | if (this.modalIsDisplayed) { 74 | hideModal(); 75 | this.setHideMenuTimer(5000); 76 | this.updateModalState(); 77 | } else if (!this.modalIsDisplayed) { 78 | this.updateModalState(); 79 | showModal(); 80 | clearTimeout(this.timer); 81 | } 82 | }; 83 | 84 | export default breathingMenu; 85 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | Breathe with me
-------------------------------------------------------------------------------- /src/css/partials/_carousel.scss: -------------------------------------------------------------------------------- 1 | 2 | .carousel-list { 3 | padding: 0; 4 | list-style-type: none; 5 | 6 | } 7 | .carousel_container { 8 | text-align: center; 9 | height: 100%; 10 | position: absolute; 11 | left: 0; 12 | right: 0; 13 | margin: 0 auto; 14 | top: 3%; 15 | width: 300px; 16 | overflow: hidden; 17 | } 18 | 19 | .carousel_items { 20 | position: absolute; 21 | top: 40px; 22 | width: 300px; 23 | height: 300px; 24 | opacity: 1; 25 | position: relative; 26 | cursor: default !important; 27 | } 28 | 29 | .carousel_item, .carousel_item img { 30 | float:left; 31 | text-align: center; 32 | border-radius: 50%; 33 | overflow: hidden; 34 | width: 300px; 35 | height: 300px; 36 | margin-bottom: 50px; 37 | will-change: transform; 38 | } 39 | 40 | @media screen and (max-height: 450px) { 41 | .carousel_container { 42 | width: 260px; 43 | } 44 | .carousel_items, .carousel_item, .carousel_item img { 45 | width: 260px; 46 | height: 260px; 47 | } 48 | } 49 | 50 | @media screen and (max-height: 620px) { 51 | .carousel_container { 52 | width: 280px; 53 | } 54 | .carousel_items, .carousel_item, .carousel_item img { 55 | width: 280px; 56 | height: 280px; 57 | } 58 | } 59 | 60 | @media screen and (min-height: 720px) { 61 | .carousel_container { 62 | width: 320px; 63 | } 64 | .carousel_items, .carousel_item, .carousel_item img { 65 | width: 320px; 66 | height: 320px; 67 | } 68 | } 69 | 70 | .nav_dot:hover { 71 | cursor: pointer; 72 | } 73 | 74 | .item_next { 75 | left: 900px; 76 | top: 0px; 77 | background-color: transparent; 78 | } 79 | 80 | .item_prev { 81 | display: none; 82 | left: 900px; 83 | top: 66px; 84 | background-color: transparent; 85 | } 86 | 87 | .nav_dots { 88 | position: relative; 89 | bottom: -2px; 90 | margin-left: auto; 91 | margin-right: auto; 92 | z-index: 5001; 93 | padding: 0px; 94 | margin-top: 20px; 95 | text-align: center; 96 | } 97 | 98 | .nav_dot { 99 | width: 6px; 100 | height: 6px; 101 | border: 1px solid $main-text; 102 | background-color: $main-text; 103 | border-radius: 50%; 104 | padding: 3px; 105 | margin: 5px; 106 | display: inline-block; 107 | } 108 | 109 | 110 | .grab { 111 | cursor: -webkit-grab; 112 | cursor: -moz-grab; 113 | } 114 | 115 | .grabbing { 116 | cursor: -webkit-grabbing; 117 | cursor: -moz-grabbing; 118 | } 119 | -------------------------------------------------------------------------------- /src/assets/images/breathingPage/BeathingHead.svg: -------------------------------------------------------------------------------- 1 | head 2 | -------------------------------------------------------------------------------- /src/templates/narration.html: -------------------------------------------------------------------------------- 1 |
2 | mountian front2 3 | mountain middle 4 | mountainBack 5 |
6 |

Hi

7 |

When I feel worried or upset, I take slow, deep breaths to make my belly move up and down. This helps me to feel more calm and relaxed.

8 | Whenever you're feeling anxious, find a place you can stand, sit or lie down comfortably. Place your hand on your belly and breathe with me. 9 |

10 | Start 11 |
12 |
13 | -------------------------------------------------------------------------------- /src/lib/breathingtimer.js: -------------------------------------------------------------------------------- 1 | import { TweenMax, TimelineMax } from 'gsap'; 2 | import { displayNotification } from '../animations/index'; 3 | import { saveState, getState } from '../globalState'; 4 | import breathingMenu from './breathingmenu'; 5 | 6 | export const notifications = {}; 7 | 8 | notifications.breathingTick = 0; 9 | 10 | notifications.resetTick = function () { 11 | notifications.breathingTick = 0; 12 | }; 13 | 14 | notifications.updateNotification = function (notification) { 15 | const message = document.getElementById('fading-message'); 16 | message.textContent = notification; 17 | }; 18 | 19 | notifications.onInhale = function () { 20 | if (breathingMenu.menuIsDisplayed || breathingMenu.modalIsDisplayed) return; 21 | 22 | notifications.breathingTick += 1; 23 | 24 | switch (notifications.breathingTick) { 25 | case 1: 26 | notifications.updateNotification('place one hand on your belly..'); 27 | displayNotification(); 28 | break; 29 | case 2: 30 | notifications.updateNotification('..and follow me..'); 31 | displayNotification(); 32 | break; 33 | case 3: 34 | notifications.updateNotification('breathe deeply and slowly..'); 35 | displayNotification(); 36 | break; 37 | case 5: 38 | notifications.updateNotification('as I grow, gently breathe in..'); 39 | displayNotification(); 40 | break; 41 | case 8: 42 | notifications.updateNotification('on inhale, feel your belly balloon out'); 43 | displayNotification(); 44 | break; 45 | case 11: 46 | notifications.updateNotification('this is how we practice deep breathing'); 47 | displayNotification(); 48 | break; 49 | case 12: 50 | notifications.updateNotification('..one continuous motion without pause..'); 51 | displayNotification(); 52 | break; 53 | case 13: 54 | notifications.updateNotification('..deeply and slowly..'); 55 | displayNotification(); 56 | break; 57 | case 14: 58 | notifications.updateNotification('..that\'s two minutes \n welldone, ' + getState().name + '!'); 59 | displayNotification(); 60 | break; 61 | case 15: 62 | notifications.updateNotification('..continue for as long as you like..'); 63 | displayNotification(); 64 | break; 65 | case 30: 66 | notifications.updateNotification('it\'s been 5 minutes \nwelldone!'); 67 | displayNotification(); 68 | break; 69 | default: break; 70 | } 71 | }; 72 | 73 | notifications.onExhale = function () { 74 | if (breathingMenu.menuIsDisplayed || breathingMenu.modalIsDisplayed) return; 75 | switch (notifications.breathingTick) { 76 | case 6: 77 | notifications.updateNotification('and out, without pause, as I shrink'); 78 | displayNotification(); 79 | break; 80 | case 9: 81 | notifications.updateNotification('..and shrink again as you exhale..'); 82 | displayNotification(); 83 | break; 84 | default: break; 85 | } 86 | }; 87 | 88 | export const saveStateHasVisited = () => { 89 | if (!getState().hasVisited) { 90 | saveState({ hasVisited: true }); 91 | } 92 | }; 93 | -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | Breathe with me 32 | 33 | 34 | 35 | 36 |
37 |
38 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /src/assets/images/breathingPage/newHands.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | leftHand + rightHand 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const webpack = require('webpack'); 3 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 4 | const CopyWebpackPlugin = require('copy-webpack-plugin'); 5 | const ExtractTextPlugin = require('extract-text-webpack-plugin'); 6 | const SWPrecacheWebpackPlugin = require('sw-precache-webpack-plugin'); 7 | const AppCachePlugin = require('appcache-webpack-plugin'); 8 | 9 | const entrypoints = [ 10 | 'babel-polyfill', 11 | 'materialize-loader', 12 | './src/index.js', 13 | './src/css/style.scss', 14 | ]; 15 | 16 | module.exports = { 17 | entry: entrypoints, 18 | output: { 19 | path: path.resolve(__dirname, 'public'), 20 | filename: 'bundle.js', 21 | }, 22 | resolve: { 23 | root: path.resolve('./node_modules'), 24 | alias: { 25 | TweenLite: path.resolve('node_modules', 'gsap/src/minified/TweenLite.min.js'), 26 | TweenMax: path.resolve('node_modules', 'gsap/src/minified/TweenMax.min.js'), 27 | TimelineLite: path.resolve('node_modules', 'gsap/src/minified/TimelineLite.min.js'), 28 | TimelineMax: path.resolve('node_modules', 'gsap/src/minified/TimelineMax.min.js'), 29 | CSSPlugin: path.resolve('node_modules', 'gsap/src/minified/plugins/CSSPlugin.min.js'), 30 | Draggable: path.resolve('node_modules', 'gsap/src/minified/utils/Draggable.min.js'), 31 | }, 32 | }, 33 | module: { 34 | loaders: [{ 35 | test: /\.js$/, 36 | loader: 'babel-loader', 37 | }, { 38 | test: /\.html$/, 39 | loaders: ['babel-loader', 'html'], 40 | }, { 41 | test: /\.scss$/, 42 | loader: ExtractTextPlugin.extract( 43 | 'style', ['css-loader', 'postcss-loader', 'sass-loader'], { publicPath: '/' } 44 | ), 45 | }, { 46 | test: /\.woff(2)?(\?v=[0-9]\.[0-9]\.[0-9])?$/, 47 | loader: 'url?limit=10000&mimetype=application/font-woff', 48 | }, { 49 | test: /\.(jpg|jpeg|gif|png|ttf|eot|svg|mp3)(\?v=[0-9]\.[0-9]\.[0-9])?$/, 50 | loader: 'file', 51 | }], 52 | }, 53 | postcss: function () { 54 | return [require('autoprefixer')]; 55 | }, 56 | plugins: [ 57 | new webpack.ProvidePlugin({ 58 | $: 'jquery', 59 | jQuery: 'jquery', 60 | }), 61 | new ExtractTextPlugin('bundle.css'), 62 | new AppCachePlugin({ 63 | cache: [ 64 | 'breathe.appcache', 65 | 'manifest.json', 66 | 'catLanding.png', 67 | 'Podington_Bear_-_Floating_In_Space.mp3', 68 | 'splash.png', 69 | 'splash_iphone6.png', 70 | 'splash_iphone6plus.png', 71 | 'splash_ipad.png', 72 | 'https://fonts.googleapis.com/css?family=Lato', 73 | 'https://fonts.googleapis.com/icon?family=Material+Icons', 74 | ], 75 | network: ['*'], 76 | fallback: [], 77 | settings: ['prefer-online'], 78 | exclude: [], 79 | output: 'breathe.appcache', 80 | }), 81 | new HtmlWebpackPlugin({ 82 | template: 'src/index.html', 83 | }), 84 | new CopyWebpackPlugin([ 85 | { from: 'src/assets/images/catLanding.png' }, 86 | { from: 'src/assets/sounds/Podington_Bear_-_Floating_In_Space.mp3' }, 87 | { from: 'src/assets/images/splash.png' }, 88 | { from: 'src/assets/images/splash_iphone6.png' }, 89 | { from: 'src/assets/images/splash_iphone6plus.png' }, 90 | { from: 'src/assets/images/splash_ipad.png' }, 91 | { from: 'src/manifest.json' }, 92 | ]), 93 | new SWPrecacheWebpackPlugin({ 94 | cacheId: 'pages-cache-v1', 95 | filename: 'service-worker.js', 96 | maximumFileSizeToCacheInBytes: 10000000, 97 | runtimeCaching: [{ 98 | handler: 'cacheFirst', 99 | urlPattern: /[.](mp3|json|png|js)$/, 100 | }, { 101 | handler: 'cacheFirst', 102 | urlPattern: /^https:\/\/fonts\.googleapis\.com/, 103 | }], 104 | }), 105 | ], 106 | }; 107 | -------------------------------------------------------------------------------- /src/assets/images/breathingPage/new legs.svg: -------------------------------------------------------------------------------- 1 | 2 | feet 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /src/lib/carousel.js: -------------------------------------------------------------------------------- 1 | /* global $ */ 2 | /* eslint-disable no-plusplus, func-names, import/no-extraneous-dependencies */ 3 | import { TweenLite, TweenMax, Power2 } from 'gsap'; 4 | import CSSPlugin from 'gsap/src/uncompressed/plugins/CSSPlugin'; 5 | import Draggable from 'gsap/src/uncompressed/utils/Draggable'; 6 | 7 | const carousel = function () { 8 | let activeID; 9 | const itemW = $('.carousel_item').width(); 10 | const carouselCount = $('.carousel_item').length; 11 | const $carouselItems = $('.carousel_items'); 12 | const $carouselItem = $('.carousel_item'); 13 | const landingButton = $('#landing-button'); 14 | const $navDots = $('.nav_dots'); 15 | const slideSpeed = 0.45; 16 | const slideMeth = Power2.EaseInOut; 17 | let swipeDir; 18 | let $navDot; 19 | 20 | function buttonColourChange(id) { 21 | const activeAvatar = id; 22 | 23 | switch (activeAvatar) { 24 | case 0: 25 | landingButton.css('background-color', '#1C6C86'); 26 | break; 27 | case 1: 28 | landingButton.css('background-color', '#FF9C9C'); 29 | break; 30 | case 2: 31 | landingButton.css('background-color', '#4942A5'); 32 | break; 33 | default: 34 | landingButton.css('background-color', '#1C6C86'); 35 | } 36 | } 37 | 38 | function slideDone(id) { 39 | activeID = id; 40 | 41 | $carouselItems.find('.active').removeClass('active'); 42 | $carouselItems.find('.carousel_item').eq(activeID).addClass('active').attr('id', `carousel_${id}`); 43 | 44 | $navDot.css({ backgroundColor: '#1C6C89' }); 45 | TweenMax.to($navDot, 0.35, { scale: 1, color: '#1C6C89' }); 46 | TweenMax.to($(`#dot_${activeID}`), 0.35, { scale: 1.5, backgroundColor: 'transparent', color: '#1C6C89' }); 47 | 48 | buttonColourChange(activeID); 49 | } 50 | 51 | function showSlide(id, skipAnimation = false) { 52 | activeID = id; 53 | if (activeID >= carouselCount - 1) activeID = carouselCount - 1; 54 | if (activeID <= 0) activeID = 0; 55 | 56 | const xTarget = ((activeID * itemW) * -1); 57 | const speed = skipAnimation ? 0 : slideSpeed; 58 | 59 | TweenMax.to($carouselItems, speed, { x: xTarget, ease: slideMeth, onComplete: slideDone(activeID) }); 60 | } 61 | 62 | function updateDirections() { 63 | swipeDir = this.getDirection('start'); 64 | } 65 | 66 | const setupDraggable = () => { 67 | Draggable.create($carouselItems, { 68 | type: 'x', 69 | edgeResistance: 0.90, 70 | dragResistance: 0.0, 71 | bounds: '.carousel_container', 72 | onDrag: updateDirections, 73 | onThrowUpdate: updateDirections, 74 | throwProps: true, 75 | onDragStart() {}, 76 | onDragEnd() { 77 | let slideId = activeID; 78 | if (swipeDir === 'left') { 79 | slideId++; 80 | } else if (swipeDir === 'right') { 81 | slideId--; 82 | } 83 | showSlide(slideId, false); 84 | }, 85 | }); 86 | }; 87 | 88 | function setupDots() { 89 | for (let i = 0; i < carouselCount; i++) { 90 | $navDots.append(``); 91 | } 92 | $navDot = $('.nav_dot'); 93 | 94 | $navDot.hover( 95 | function () { 96 | TweenMax.to($(this), 0.35, { scale: 1.5 }); 97 | }, function () { 98 | if ($(this).attr('id').split('_')[1] === activeID) return; 99 | TweenMax.to($(this), 0.35, { scale: 1.0 }); 100 | }); 101 | 102 | $navDot.click(function () { 103 | const dotID = $(this).attr('id').split('_')[1]; 104 | showSlide(dotID); 105 | }); 106 | } 107 | 108 | function init() { 109 | $carouselItems.css({ width: (itemW * carouselCount) + 'px' }); 110 | $navDots.css({ width: (25 * carouselCount) + 'px' }); 111 | 112 | setupDraggable(); 113 | setupDots(); 114 | showSlide(0, true); 115 | } 116 | 117 | return { 118 | init, 119 | }; 120 | }; 121 | 122 | export default carousel; 123 | -------------------------------------------------------------------------------- /src/assets/images/welldone/stars.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /public/7d0026955ab24fa892b84b6df7cc6935.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/assets/images/GIFpage/cat-GIF.svg: -------------------------------------------------------------------------------- 1 | kitty-GIF 2 | -------------------------------------------------------------------------------- /src/assets/images/breathingPage/breathingCat.svg: -------------------------------------------------------------------------------- 1 | Breathing cat whole -------------------------------------------------------------------------------- /src/assets/images/icons/cat_icon_square_very_big.svg: -------------------------------------------------------------------------------- 1 | cat icon square very big -------------------------------------------------------------------------------- /src/assets/images/icons/cat_icon_round_big_no_border.svg: -------------------------------------------------------------------------------- 1 | cat icon round big no border -------------------------------------------------------------------------------- /src/css/partials/_breathing.scss: -------------------------------------------------------------------------------- 1 | #breathing-menu { 2 | position: fixed; 3 | background-color: $modal-menu; 4 | height: 47px; 5 | width: 100%; 6 | z-index: 10; 7 | opacity: 0; 8 | max-width: inherit; 9 | &.full-screen-modal { 10 | height: 100%; 11 | } 12 | } 13 | 14 | #feel-good-modal { 15 | width: 100%; 16 | max-width: inherit; 17 | min-height: 100px; 18 | bottom: 0px; 19 | background-color: $modal-menu; 20 | opacity: 0; 21 | z-index: 10; 22 | position: fixed; 23 | text-align: center; 24 | } 25 | 26 | #feel-good-button { 27 | position: absolute; 28 | top: 50%; 29 | transform: translateY(-50%); 30 | left: 0; 31 | right: 0; 32 | margin: 0 auto; 33 | width: 200px; 34 | text-align: center; 35 | border-radius: 23px; 36 | border: 2px solid white; 37 | color: white; 38 | line-height: 40px; 39 | height: 40px; 40 | font-family: $Raleway; 41 | will-change: transform; 42 | transition: all 0.3s ease 0s; 43 | } 44 | 45 | #feel-good-button:active { 46 | background-color: white; 47 | color: $main-text; 48 | } 49 | 50 | #audio-controls { 51 | position: absolute; 52 | left: 0px; 53 | top: 0px; 54 | background: transparent; 55 | transform: translateY(50%); 56 | padding-left: 3%; 57 | z-index: 20; 58 | -webkit-tap-highlight-color: rgba(0,0,0,0); 59 | } 60 | 61 | #breathing-settings { 62 | position: absolute; 63 | right: 0px; 64 | top: 0px; 65 | background: transparent; 66 | transform: translateY(50%); 67 | padding-right: 3%; 68 | z-index: 20; 69 | -webkit-tap-highlight-color: rgba(0,0,0,0); 70 | } 71 | 72 | #breathing-info { 73 | position: relative; 74 | z-index: 999; 75 | display: block; 76 | text-align: center; 77 | margin: auto; 78 | background: transparent; 79 | transform: translateY(50%); 80 | z-index: 15; 81 | -webkit-tap-highlight-color: rgba(0,0,0,0); 82 | } 83 | 84 | #modal-breathing-instructions { 85 | font-family: 'Raleway', sans-serif; 86 | text-align: center; 87 | display: none; 88 | opacity: 0; 89 | color: white; 90 | padding: 2.2em; 91 | height: 100%; 92 | -webkit-transform: translate3d(0,0,0); 93 | 94 | p { 95 | font-family: $Raleway; 96 | font-size: 1.1em; 97 | text-align: center; 98 | padding-top: 10%; 99 | } 100 | } 101 | 102 | #exit-modal-button { 103 | display: block; 104 | position: absolute; 105 | bottom: 10px; 106 | left: 0; 107 | right: 0; 108 | text-align: center; 109 | font-size: 36px; 110 | -webkit-tap-highlight-color: rgba(0,0,0,0); 111 | } 112 | 113 | #modal-instructions { 114 | font-family: $Amatic; 115 | font-size: 2.5em; 116 | position: relative; 117 | display: block; 118 | text-align: center; 119 | margin: auto; 120 | } 121 | 122 | #fading-message { 123 | display: none; 124 | color: #1C6C89; 125 | position: fixed; 126 | opacity: 0; 127 | left: 0; 128 | right: 0; 129 | margin: auto; 130 | text-align: center; 131 | top: 5%; 132 | font-size: 13px; 133 | } 134 | 135 | .character { 136 | transform: scale(0.9); 137 | top: 8%; 138 | position: absolute; 139 | left: 0; 140 | right: 0; 141 | max-width: inherit; 142 | margin: 0 auto; 143 | will-change: transform; 144 | 145 | svg { 146 | display: block; 147 | position: absolute; 148 | left: 0; 149 | right: 0; 150 | max-width: inherit; 151 | margin: 0 auto; 152 | } 153 | } 154 | 155 | @media screen and (max-height: 595px) { 156 | .character { 157 | transform: scale(0.9); 158 | top: 7%; 159 | } 160 | .fading-message { 161 | top: 2%; 162 | } 163 | } 164 | 165 | @media screen and (min-height: 620px) { 166 | .character { 167 | transform: scale(1.02); 168 | top: 5%; 169 | } 170 | .fading-message { 171 | top: 3%; 172 | } 173 | } 174 | 175 | @media screen and (min-height: 640px) { 176 | .character { 177 | transform: scale(1.04); 178 | top: 5%; 179 | } 180 | .fading-message { 181 | top: 1%; 182 | } 183 | } 184 | 185 | @media screen and (min-height: 655px) { 186 | .character { 187 | transform: scale(1.15); 188 | top: 6%; 189 | } 190 | .fading-message { 191 | top: 3%; 192 | } 193 | } 194 | 195 | @media screen and (min-height: 675px) { 196 | .character { 197 | transform: scale(1.17); 198 | top: 6%; 199 | } 200 | .fading-message { 201 | top: 1.5%; 202 | } 203 | } 204 | 205 | @media screen and (min-height: 690px) { 206 | .character { 207 | transform: scale(1.19); 208 | top: 5%; 209 | } 210 | .fading-message { 211 | top: 1%; 212 | } 213 | } 214 | 215 | @media screen and (min-height: 710px) { 216 | .character { 217 | transform: scale(1.2); 218 | top: 4%; 219 | } 220 | .fading-message { 221 | top: 2%; 222 | } 223 | } 224 | 225 | @media screen and (min-height: 730px) { 226 | .character { 227 | transform: scale(1.23); 228 | top: 8%; 229 | } 230 | .fading-message { 231 | top: 2%; 232 | } 233 | } 234 | 235 | @media screen and (min-height: 760px) { 236 | .character { 237 | transform: scale(1.3); 238 | top: 6%; 239 | } 240 | .fading-message { 241 | top: 2%; 242 | } 243 | } 244 | 245 | @media screen and (min-height: 790px) { 246 | .character { 247 | transform: scale(1.4); 248 | top: 6%; 249 | } 250 | .fading-message { 251 | top: 2%; 252 | } 253 | } 254 | 255 | @media screen and (min-height: 840px) { 256 | .character { 257 | transform: scale(1.5); 258 | top: 5%; 259 | } 260 | .fading-message { 261 | top: 2%; 262 | } 263 | } 264 | 265 | @media screen and (min-height: 875px) { 266 | .character { 267 | transform: scale(1.55); 268 | top: 4%; 269 | } 270 | .fading-message { 271 | top: 2%; 272 | } 273 | #feel-good-button { 274 | bottom: 5.5%; 275 | } 276 | } 277 | 278 | @media screen and (min-height: 900px) { 279 | .character { 280 | transform: scale(1.6); 281 | top: 4%; 282 | } 283 | .fading-message { 284 | top: 2%; 285 | } 286 | #feel-good-button { 287 | bottom: 15%; 288 | } 289 | } 290 | 291 | .head { 292 | top: 33px; 293 | z-index: 10; 294 | will-change: transform; 295 | } 296 | 297 | .belly { 298 | z-index: 2; 299 | will-change: transform; 300 | } 301 | 302 | .hands { 303 | z-index: 30; 304 | } 305 | 306 | .legs { 307 | z-index: 1; 308 | } 309 | 310 | // cat 311 | 312 | #cat-belly { 313 | top: 178px; 314 | } 315 | 316 | #cat-legs { 317 | top: 429px; 318 | } 319 | 320 | #cat-hands { 321 | top: 251px; 322 | } 323 | 324 | // panda 325 | 326 | #panda-belly { 327 | top: 170px; 328 | } 329 | 330 | #panda-legs { 331 | top: 405px; 332 | } 333 | 334 | #panda-hands { 335 | top: 220px; 336 | } 337 | 338 | //dog 339 | 340 | #dog-belly { 341 | top: 183px; 342 | } 343 | 344 | #dog-legs { 345 | top: 430px; 346 | } 347 | 348 | #dog-hands { 349 | top: 254px; 350 | } 351 | -------------------------------------------------------------------------------- /src/assets/images/icons/cat_icon_square_small.svg: -------------------------------------------------------------------------------- 1 | cat icon square small -------------------------------------------------------------------------------- /src/assets/images/icons/cat_icon_square_big.svg: -------------------------------------------------------------------------------- 1 | cat icon square big -------------------------------------------------------------------------------- /src/lib/blacklist.js: -------------------------------------------------------------------------------- 1 | export default { 2 | '4r5e': 1, 3 | '5hit': 1, 4 | a55: 1, 5 | anal: 1, 6 | anus: 1, 7 | ar5e: 1, 8 | arrse: 1, 9 | arse: 1, 10 | ass: 1, 11 | 'ass-fucker': 1, 12 | asses: 1, 13 | assfucker: 1, 14 | assfukka: 1, 15 | asshole: 1, 16 | assholes: 1, 17 | asswhole: 1, 18 | a_s_s: 1, 19 | 'b!tch': 1, 20 | b00bs: 1, 21 | b17ch: 1, 22 | b1tch: 1, 23 | ballbag: 1, 24 | balls: 1, 25 | ballsack: 1, 26 | bastard: 1, 27 | beastial: 1, 28 | beastiality: 1, 29 | bellend: 1, 30 | bestial: 1, 31 | bestiality: 1, 32 | 'bi+ch': 1, 33 | biatch: 1, 34 | bitch: 1, 35 | bitcher: 1, 36 | bitchers: 1, 37 | bitches: 1, 38 | bitchin: 1, 39 | bitching: 1, 40 | bloody: 1, 41 | 'blow job': 1, 42 | blowjob: 1, 43 | blowjobs: 1, 44 | boiolas: 1, 45 | bollock: 1, 46 | bollok: 1, 47 | boner: 1, 48 | boob: 1, 49 | boobs: 1, 50 | booobs: 1, 51 | boooobs: 1, 52 | booooobs: 1, 53 | booooooobs: 1, 54 | breasts: 1, 55 | buceta: 1, 56 | bugger: 1, 57 | bum: 1, 58 | 'bunny fucker': 1, 59 | butt: 1, 60 | butthole: 1, 61 | buttmuch: 1, 62 | buttplug: 1, 63 | c0ck: 1, 64 | c0cksucker: 1, 65 | 'carpet muncher': 1, 66 | cawk: 1, 67 | chink: 1, 68 | cipa: 1, 69 | cl1t: 1, 70 | clit: 1, 71 | clitoris: 1, 72 | clits: 1, 73 | cnut: 1, 74 | cock: 1, 75 | 'cock-sucker': 1, 76 | cockface: 1, 77 | cockhead: 1, 78 | cockmunch: 1, 79 | cockmuncher: 1, 80 | cocks: 1, 81 | cocksuck: 1, 82 | cocksucked: 1, 83 | cocksucker: 1, 84 | cocksucking: 1, 85 | cocksucks: 1, 86 | cocksuka: 1, 87 | cocksukka: 1, 88 | cok: 1, 89 | cokmuncher: 1, 90 | coksucka: 1, 91 | coon: 1, 92 | cox: 1, 93 | crap: 1, 94 | cum: 1, 95 | cummer: 1, 96 | cumming: 1, 97 | cums: 1, 98 | cumshot: 1, 99 | cunilingus: 1, 100 | cunillingus: 1, 101 | cunnilingus: 1, 102 | cunt: 1, 103 | cuntlick: 1, 104 | cuntlicker: 1, 105 | cuntlicking: 1, 106 | cunts: 1, 107 | cyalis: 1, 108 | cyberfuc: 1, 109 | cyberfuck: 1, 110 | cyberfucked: 1, 111 | cyberfucker: 1, 112 | cyberfuckers: 1, 113 | cyberfucking: 1, 114 | d1ck: 1, 115 | damn: 1, 116 | dick: 1, 117 | dickhead: 1, 118 | dildo: 1, 119 | dildos: 1, 120 | dink: 1, 121 | dinks: 1, 122 | dirsa: 1, 123 | dlck: 1, 124 | 'dog-fucker': 1, 125 | doggin: 1, 126 | dogging: 1, 127 | donkeyribber: 1, 128 | doosh: 1, 129 | duche: 1, 130 | dyke: 1, 131 | ejaculate: 1, 132 | ejaculated: 1, 133 | ejaculates: 1, 134 | ejaculating: 1, 135 | ejaculatings: 1, 136 | ejaculation: 1, 137 | ejakulate: 1, 138 | 'f u c k': 1, 139 | 'f u c k e r': 1, 140 | f4nny: 1, 141 | fag: 1, 142 | fagging: 1, 143 | faggitt: 1, 144 | faggot: 1, 145 | faggs: 1, 146 | fagot: 1, 147 | fagots: 1, 148 | fags: 1, 149 | fanny: 1, 150 | fannyflaps: 1, 151 | fannyfucker: 1, 152 | fanyy: 1, 153 | fatass: 1, 154 | fcuk: 1, 155 | fcuker: 1, 156 | fcuking: 1, 157 | feck: 1, 158 | fecker: 1, 159 | felching: 1, 160 | fellate: 1, 161 | fellatio: 1, 162 | fingerfuck: 1, 163 | fingerfucked: 1, 164 | fingerfucker: 1, 165 | fingerfuckers: 1, 166 | fingerfucking: 1, 167 | fingerfucks: 1, 168 | fistfuck: 1, 169 | fistfucked: 1, 170 | fistfucker: 1, 171 | fistfuckers: 1, 172 | fistfucking: 1, 173 | fistfuckings: 1, 174 | fistfucks: 1, 175 | flange: 1, 176 | fook: 1, 177 | fooker: 1, 178 | fuck: 1, 179 | fucka: 1, 180 | fucked: 1, 181 | fucker: 1, 182 | fuckers: 1, 183 | fuckhead: 1, 184 | fuckheads: 1, 185 | fuckin: 1, 186 | fucking: 1, 187 | fuckings: 1, 188 | fuckingshitmotherfucker: 1, 189 | fuckme: 1, 190 | fucks: 1, 191 | fuckwhit: 1, 192 | fuckwit: 1, 193 | 'fudge packer': 1, 194 | fudgepacker: 1, 195 | fuk: 1, 196 | fuker: 1, 197 | fukker: 1, 198 | fukkin: 1, 199 | fuks: 1, 200 | fukwhit: 1, 201 | fukwit: 1, 202 | fux: 1, 203 | fux0r: 1, 204 | f_u_c_k: 1, 205 | gangbang: 1, 206 | gangbanged: 1, 207 | gangbangs: 1, 208 | gaylord: 1, 209 | gaysex: 1, 210 | goatse: 1, 211 | god: 1, 212 | 'god-dam': 1, 213 | 'god-damned': 1, 214 | goddamn: 1, 215 | goddamned: 1, 216 | hardcoresex: 1, 217 | hell: 1, 218 | heshe: 1, 219 | hoar: 1, 220 | hoare: 1, 221 | hoer: 1, 222 | homo: 1, 223 | hore: 1, 224 | horniest: 1, 225 | horny: 1, 226 | hotsex: 1, 227 | 'jack-off ': 1, 228 | jackoff: 1, 229 | jap: 1, 230 | 'jerk-off ': 1, 231 | jism: 1, 232 | jiz: 1, 233 | jizm: 1, 234 | jizz: 1, 235 | kawk: 1, 236 | knob: 1, 237 | knobead: 1, 238 | knobed: 1, 239 | knobend: 1, 240 | knobhead: 1, 241 | knobjocky: 1, 242 | knobjokey: 1, 243 | kock: 1, 244 | kondum: 1, 245 | kondums: 1, 246 | kum: 1, 247 | kummer: 1, 248 | kumming: 1, 249 | kums: 1, 250 | kunilingus: 1, 251 | 'l3i+ch': 1, 252 | l3itch: 1, 253 | labia: 1, 254 | lmfao: 1, 255 | lust: 1, 256 | lusting: 1, 257 | m0f0: 1, 258 | m0fo: 1, 259 | m45terbate: 1, 260 | ma5terb8: 1, 261 | ma5terbate: 1, 262 | masochist: 1, 263 | 'master-bate': 1, 264 | masterb8: 1, 265 | 'masterbat*': 1, 266 | masterbat3: 1, 267 | masterbate: 1, 268 | masterbation: 1, 269 | masterbations: 1, 270 | masturbate: 1, 271 | 'mo-fo': 1, 272 | mof0: 1, 273 | mofo: 1, 274 | mothafuck: 1, 275 | mothafucka: 1, 276 | mothafuckas: 1, 277 | mothafuckaz: 1, 278 | mothafucked: 1, 279 | mothafucker: 1, 280 | mothafuckers: 1, 281 | mothafuckin: 1, 282 | mothafucking: 1, 283 | mothafuckings: 1, 284 | mothafucks: 1, 285 | 'mother fucker': 1, 286 | motherfuck: 1, 287 | motherfucked: 1, 288 | motherfucker: 1, 289 | motherfuckers: 1, 290 | motherfuckin: 1, 291 | motherfucking: 1, 292 | motherfuckings: 1, 293 | motherfuckka: 1, 294 | motherfucks: 1, 295 | muff: 1, 296 | mutha: 1, 297 | muthafecker: 1, 298 | muthafuckker: 1, 299 | muther: 1, 300 | mutherfucker: 1, 301 | n1gga: 1, 302 | n1gger: 1, 303 | nazi: 1, 304 | nigg3r: 1, 305 | nigg4h: 1, 306 | nigga: 1, 307 | niggah: 1, 308 | niggas: 1, 309 | niggaz: 1, 310 | nigger: 1, 311 | niggers: 1, 312 | nob: 1, 313 | 'nob jokey': 1, 314 | nobhead: 1, 315 | nobjocky: 1, 316 | nobjokey: 1, 317 | numbnuts: 1, 318 | nutsack: 1, 319 | orgasim: 1, 320 | orgasims: 1, 321 | orgasm: 1, 322 | orgasms: 1, 323 | p0rn: 1, 324 | pawn: 1, 325 | pecker: 1, 326 | penis: 1, 327 | penisfucker: 1, 328 | phonesex: 1, 329 | phuck: 1, 330 | phuk: 1, 331 | phuked: 1, 332 | phuking: 1, 333 | phukked: 1, 334 | phukking: 1, 335 | phuks: 1, 336 | phuq: 1, 337 | pigfucker: 1, 338 | pimpis: 1, 339 | piss: 1, 340 | pissed: 1, 341 | pisser: 1, 342 | pissers: 1, 343 | pisses: 1, 344 | pissflaps: 1, 345 | pissin: 1, 346 | pissing: 1, 347 | pissoff: 1, 348 | poop: 1, 349 | porn: 1, 350 | porno: 1, 351 | pornography: 1, 352 | pornos: 1, 353 | prick: 1, 354 | pricks: 1, 355 | pron: 1, 356 | pube: 1, 357 | pusse: 1, 358 | pussi: 1, 359 | pussies: 1, 360 | pussy: 1, 361 | pussys: 1, 362 | rectum: 1, 363 | retard: 1, 364 | rimjaw: 1, 365 | rimming: 1, 366 | 's hit': 1, 367 | 's.o.b.': 1, 368 | sadist: 1, 369 | schlong: 1, 370 | screwing: 1, 371 | scroat: 1, 372 | scrote: 1, 373 | scrotum: 1, 374 | semen: 1, 375 | sex: 1, 376 | 'sh!+': 1, 377 | 'sh!t': 1, 378 | sh1t: 1, 379 | shag: 1, 380 | shagger: 1, 381 | shaggin: 1, 382 | shagging: 1, 383 | shemale: 1, 384 | 'shi+': 1, 385 | shit: 1, 386 | shitdick: 1, 387 | shite: 1, 388 | shited: 1, 389 | shitey: 1, 390 | shitfuck: 1, 391 | shitfull: 1, 392 | shithead: 1, 393 | shiting: 1, 394 | shitings: 1, 395 | shits: 1, 396 | shitted: 1, 397 | shitter: 1, 398 | shitters: 1, 399 | shitting: 1, 400 | shittings: 1, 401 | shitty: 1, 402 | skank: 1, 403 | slut: 1, 404 | sluts: 1, 405 | smegma: 1, 406 | smut: 1, 407 | snatch: 1, 408 | 'son-of-a-bitch': 1, 409 | spac: 1, 410 | spunk: 1, 411 | s_h_i_t: 1, 412 | t1tt1e5: 1, 413 | t1tties: 1, 414 | teets: 1, 415 | teez: 1, 416 | testical: 1, 417 | testicle: 1, 418 | tit: 1, 419 | titfuck: 1, 420 | tits: 1, 421 | titt: 1, 422 | tittie5: 1, 423 | tittiefucker: 1, 424 | titties: 1, 425 | tittyfuck: 1, 426 | tittywank: 1, 427 | titwank: 1, 428 | tosser: 1, 429 | turd: 1, 430 | tw4t: 1, 431 | twat: 1, 432 | twathead: 1, 433 | twatty: 1, 434 | twunt: 1, 435 | twunter: 1, 436 | v14gra: 1, 437 | v1gra: 1, 438 | vagina: 1, 439 | viagra: 1, 440 | vulva: 1, 441 | w00se: 1, 442 | wang: 1, 443 | wank: 1, 444 | wanker: 1, 445 | wanky: 1, 446 | whoar: 1, 447 | whore: 1, 448 | willies: 1, 449 | willy: 1, 450 | xrated: 1, 451 | xxx: 1, 452 | }; 453 | -------------------------------------------------------------------------------- /public/bundle.css: -------------------------------------------------------------------------------- 1 | #mountain1.dog{fill:#fff4f4}#mountain2.dog{fill:#f3d4d4}#mountain3.dog{fill:#ff9c9c}#mountain1.cat{fill:#cef7f0}#mountain2.cat{fill:#3fa1b3}#mountain3.cat{fill:#1c6c86}#mountain1.panda{fill:#cfdcff}#mountain2.panda{fill:#a685b4}#mountain3.panda{fill:#4942a5}.avatar{font-size:1em;height:100%;text-align:center;opacity:0}#avatar-header,.avatar{position:absolute;left:0;right:0;margin:0 auto}#avatar-header{font-family:Amatic SC,cursive;font-size:2.5em;top:3%}#landing-material-icon{font-size:2.6rem!important}#breathing-menu{position:fixed;background-color:#1c6c89;height:47px;width:100%;z-index:10;opacity:0;max-width:inherit}#breathing-menu.full-screen-modal{height:100%}#feel-good-modal{width:100%;max-width:inherit;min-height:100px;bottom:0;background-color:#1c6c89;opacity:0;z-index:10;position:fixed;text-align:center}#feel-good-button{position:absolute;top:50%;-webkit-transform:translateY(-50%);transform:translateY(-50%);left:0;right:0;margin:0 auto;width:200px;text-align:center;border-radius:23px;border:2px solid #fff;color:#fff;line-height:40px;height:40px;font-family:Raleway,sans-serif;will-change:transform;-webkit-transition:all .3s ease 0s;transition:all .3s ease 0s}#feel-good-button:active{background-color:#fff;color:#1c6c89}#audio-controls{left:0;padding-left:3%}#audio-controls,#breathing-settings{position:absolute;top:0;background:transparent;-webkit-transform:translateY(50%);transform:translateY(50%);z-index:20;-webkit-tap-highlight-color:transparent}#breathing-settings{right:0;padding-right:3%}#breathing-info{position:relative;z-index:999;display:block;text-align:center;margin:auto;background:transparent;-webkit-transform:translateY(50%);transform:translateY(50%);z-index:15;-webkit-tap-highlight-color:transparent}#modal-breathing-instructions{font-family:Raleway,sans-serif;text-align:center;display:none;opacity:0;color:#fff;padding:2.2em;height:100%;-webkit-transform:translateZ(0)}#modal-breathing-instructions p{font-family:Raleway,sans-serif;font-size:1.1em;text-align:center;padding-top:10%}#exit-modal-button{display:block;position:absolute;bottom:10px;left:0;right:0;text-align:center;font-size:36px;-webkit-tap-highlight-color:transparent}#modal-instructions{font-family:Amatic SC,cursive;font-size:2.5em;position:relative;display:block;text-align:center;margin:auto}#fading-message{display:none;color:#1c6c89;position:fixed;opacity:0;left:0;right:0;margin:auto;text-align:center;top:5%;font-size:13px}.character{-webkit-transform:scale(.9);transform:scale(.9);top:8%;will-change:transform}.character,.character svg{position:absolute;left:0;right:0;max-width:inherit;margin:0 auto}.character svg{display:block}@media screen and (max-height:595px){.character{-webkit-transform:scale(.9);transform:scale(.9);top:7%}.fading-message{top:2%}}@media screen and (min-height:620px){.character{-webkit-transform:scale(1.02);transform:scale(1.02);top:5%}.fading-message{top:3%}}@media screen and (min-height:640px){.character{-webkit-transform:scale(1.04);transform:scale(1.04);top:5%}.fading-message{top:1%}}@media screen and (min-height:655px){.character{-webkit-transform:scale(1.15);transform:scale(1.15);top:6%}.fading-message{top:3%}}@media screen and (min-height:675px){.character{-webkit-transform:scale(1.17);transform:scale(1.17);top:6%}.fading-message{top:1.5%}}@media screen and (min-height:690px){.character{-webkit-transform:scale(1.19);transform:scale(1.19);top:5%}.fading-message{top:1%}}@media screen and (min-height:710px){.character{-webkit-transform:scale(1.2);transform:scale(1.2);top:4%}.fading-message{top:2%}}@media screen and (min-height:730px){.character{-webkit-transform:scale(1.23);transform:scale(1.23);top:8%}.fading-message{top:2%}}@media screen and (min-height:760px){.character{-webkit-transform:scale(1.3);transform:scale(1.3);top:6%}.fading-message{top:2%}}@media screen and (min-height:790px){.character{-webkit-transform:scale(1.4);transform:scale(1.4);top:6%}.fading-message{top:2%}}@media screen and (min-height:840px){.character{-webkit-transform:scale(1.5);transform:scale(1.5);top:5%}.fading-message{top:2%}}@media screen and (min-height:875px){.character{-webkit-transform:scale(1.55);transform:scale(1.55);top:4%}.fading-message{top:2%}#feel-good-button{bottom:5.5%}}@media screen and (min-height:900px){.character{-webkit-transform:scale(1.6);transform:scale(1.6);top:4%}.fading-message{top:2%}#feel-good-button{bottom:15%}}.head{top:33px;z-index:10}.belly,.head{will-change:transform}.belly{z-index:2}.hands{z-index:30}.legs{z-index:1}#cat-belly{top:178px}#cat-legs{top:429px}#cat-hands{top:251px}#panda-belly{top:170px}#panda-legs{top:405px}#panda-hands{top:220px}#dog-belly{top:183px}#dog-legs{top:430px}#dog-hands{top:254px}#mountain1,#mountain2,#mountain3{width:100%;position:absolute;bottom:-80px;will-change:transform}#mountain1{z-index:0}#mountain2{z-index:-1}#mountain3{z-index:-3}.text-box{opacity:0;color:#fff;position:absolute;left:0;right:0;margin:2em auto 0;width:60%;text-align:center}.breathing-information{width:95%;color:#fff;margin:5px auto 0;text-align:center;font-size:1.2em}.breathing-start{margin-top:1em;font-size:1em;z-index:-1}#input-validation{font-size:10px;margin-top:0;margin-bottom:5px;visibility:hidden}#start-breathing-cat-button{display:block;width:110px;height:36px;margin:1.5em auto}#alt-info-box{margin-top:100px}#input-username{border-bottom:2px solid #fff;margin-bottom:0!important}.btn-flat{border-radius:23px!important;border:2px solid #fff!important;color:#fff!important;margin-top:10%;font-size:16px!important;height:40px;font-family:Raleway,sans-serif}.btn,.btn-flat,.btn-large{line-height:30px!important}.start{background-color:#cadfff;background-size:cover;background-repeat:no-repeat;background-image:url(/bc540e50b175a23a39a1b3bffc6e0785.png);text-align:center}#landing-button{opacity:0;z-index:9999;position:absolute;left:0;right:0;margin:0 auto;bottom:15%;background-color:#1c6c89}@media screen and (max-height:600px){#landing-button{bottom:6%}}.welldone{height:100%;background-color:transparent;text-align:center;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}#welldone-banner{position:absolute;opacity:0;top:0;left:20%;width:60%;z-index:50}#welldone-stars{-webkit-transform:scale(0);transform:scale(0);opacity:0;position:absolute;z-index:10;top:5.5vh;left:10%;width:80%}#start-again{border:2px solid #fff!important;border-radius:23px;color:#fff!important;left:0;right:0;bottom:52%;width:146px;height:40px;line-height:37px;font-family:Raleway,sans-serif;margin:0 auto;z-index:50}#start-again,.welldone-user{text-align:center;position:absolute}.welldone-user{z-index:10;color:#fff;height:30%;width:60%;bottom:45%;left:20%;font-size:2.5em;font-family:Amatic SC,cursive}.material-icons{color:#fff}.welldone-mountain1,.welldone-mountain2,.welldone-mountain3{left:0;right:0;width:100%;bottom:-480px;position:absolute;margin:0 auto;max-width:inherit;will-change:transform}.welldone-mountain1{z-index:10;opacity:0}.welldone-mountain2{z-index:5;opacity:0}.welldone-mountain3{z-index:2;opacity:0}.page{width:100%;max-width:inherit;max-height:inherit;min-height:100%;overflow:hidden;position:fixed;-webkit-tap-highlight-color:transparent}.page:last-child(1){z-index:10000}.carousel-list{padding:0;list-style-type:none}.carousel_container{text-align:center;height:100%;position:absolute;left:0;right:0;margin:0 auto;top:3%;width:300px;overflow:hidden}.carousel_items{position:absolute;top:40px;width:300px;height:300px;opacity:1;position:relative;cursor:default!important}.carousel_item,.carousel_item img{float:left;text-align:center;border-radius:50%;overflow:hidden;width:300px;height:300px;margin-bottom:50px;will-change:transform}@media screen and (max-height:450px){.carousel_container{width:260px}.carousel_item,.carousel_item img,.carousel_items{width:260px;height:260px}}@media screen and (max-height:620px){.carousel_container{width:280px}.carousel_item,.carousel_item img,.carousel_items{width:280px;height:280px}}@media screen and (min-height:720px){.carousel_container{width:320px}.carousel_item,.carousel_item img,.carousel_items{width:320px;height:320px}}.nav_dot:hover{cursor:pointer}.item_next{top:0}.item_next,.item_prev{left:900px;background-color:transparent}.item_prev{display:none;top:66px}.nav_dots{position:relative;bottom:-2px;margin-left:auto;margin-right:auto;z-index:5001;padding:0;margin-top:20px;text-align:center}.nav_dot{width:6px;height:6px;border:1px solid #1c6c89;background-color:#1c6c89;border-radius:50%;padding:3px;margin:5px;display:inline-block}.grab{cursor:-webkit-grab;cursor:-moz-grab}.grabbing{cursor:-webkit-grabbing;cursor:-moz-grabbing}#credits-icon{color:#1c6c89;bottom:5px;right:5px;position:absolute;z-index:9999;opacity:.8}.credits{opacity:0;display:none;text-align:center;color:#fff}.credits p{line-height:2em;margin-top:100px;margin-left:20px;margin-right:20px}.credits a{color:#fff}#back-icon{color:#fff;top:5px;left:5px;position:absolute;z-index:9999;opacity:.8}body,html{height:100%;min-height:100%;padding:0;margin:0}h1,h2,h3,h4,h5,h6{font-family:Amatic SC,cursive}body,p{font-family:Raleway,sans-serif}body{color:#1c6c89;overflow:hidden;height:100%;width:100%;letter-spacing:1.4px;background-color:transparent}.hidden{visibility:hidden}.animated{-webkit-transform:translateZ(0);will-change:transform}#app{height:100%;min-height:100%;max-height:700px;max-width:414px;margin:0 auto;background-size:cover;background-repeat:no-repeat}#bg,#overlay{position:absolute;left:0;right:0;top:0;bottom:0}#overlay{z-index:99999999;display:none}#overlay.show{display:initial}@media screen and (min-height:900px){#app{margin-top:100px}} -------------------------------------------------------------------------------- /src/animations/index.js: -------------------------------------------------------------------------------- 1 | import { TweenMax, TimelineMax, Power1, Elastic, Back } from 'gsap'; 2 | import { notifications } from '../lib/breathingtimer'; 3 | import { getState } from '../globalState'; 4 | 5 | const promisify = tl => 6 | new Promise(success => tl.addCallback(success)); 7 | 8 | export const intoAvatar = () => { 9 | const activeAvatar = document.querySelector('.carousel_item.active'); 10 | const tl = new TimelineMax(); 11 | tl 12 | .add(TweenMax.to('#avatar-header', 0.3, { opacity: 1 })) 13 | .add(TweenMax.fromTo('.nav_dots', 1, { x: -300, delay: 1 }, { x: 0, scale: 1.2, ease: Elastic.easeOut.config(0.5, 0.6) })); 14 | 15 | TweenMax.to('.avatar', 0.2, { opacity: 1 }); 16 | TweenMax.from(activeAvatar, 1, 17 | { delay: 0.2, opacity: 1, y: -350, ease: Elastic.easeOut.config(1, 1) }); 18 | TweenMax.to( 19 | '#landing-button', 0.8, 20 | { opacity: 1 }); 21 | 22 | return promisify(tl); 23 | }; 24 | 25 | export const outOfLanding = () => { 26 | const tl = new TimelineMax(); 27 | tl 28 | .add(TweenMax.to('.breathing-information', 0.2, { opacity: 0, display: 'none' })) 29 | .add(TweenMax.to('#alt-info-box', 0.2, { opacity: 0, display: 'none' })) 30 | .add(TweenMax.to('.page', 1, { visibility: 'hidden' })); 31 | 32 | return promisify(tl); 33 | }; 34 | 35 | export const outOfAvatar = () => { 36 | const activeAvatar = document.querySelector('.carousel_item.active'); 37 | const tl = new TimelineMax(); 38 | tl 39 | .add(TweenMax.to('.avatar', 0.3, { delay: 1, opacity: 0 })); 40 | TweenMax.to('#avatar-header', 0.3, { opacity: 0 }); 41 | TweenMax.to(activeAvatar, 1, { y: -400, ease: Elastic.easeIn.config(1, 1) }); 42 | TweenMax.to( 43 | '#landing-button', 0.8, 44 | { y: 300, ease: Elastic.easeIn.config(1, 1) }); 45 | return promisify(tl); 46 | }; 47 | 48 | export const startLanding = () => { 49 | TweenMax.from('#mountain1', 1.2, { delay: 0.3, y: 340, ease: Elastic.easeOut.config(0.5, 0.6) }); 50 | TweenMax.from('#mountain2', 1.2, { delay: 0.5, y: 400, ease: Elastic.easeOut.config(0.5, 0.6) }); 51 | TweenMax.from('#mountain3', 1.2, { delay: 0.9, y: 440, ease: Elastic.easeOut.config(0.5, 0.6) }); 52 | const tl = new TimelineMax(); 53 | tl.add(TweenMax.to('.text-box', 0.6, { delay: 1, opacity: 1 })); 54 | 55 | return promisify(tl); 56 | }; 57 | 58 | export const statNarration = () => { 59 | TweenMax.from('#mountain1', 1.2, { delay: 0.1, y: 340, ease: Elastic.easeOut.config(0.7, 0.7) }); 60 | TweenMax.from('#mountain2', 1.2, { delay: 0.4, y: 400, ease: Elastic.easeOut.config(0.7, 0.7) }); 61 | TweenMax.from('#mountain3', 1.2, { delay: 0.8, y: 440, ease: Elastic.easeOut.config(0.7, 0.7) }); 62 | const tl = new TimelineMax(); 63 | tl.add(TweenMax.to('.text-box', 0.6, { opacity: 1 })); 64 | 65 | return promisify(tl); 66 | }; 67 | 68 | export const exitLanding = () => { 69 | TweenMax.to('#mountain1', 1.2, { delay: 0.1, opacity: 1, y: 340, ease: Back.easeIn.config(1, 0.6) }); 70 | TweenMax.to('#mountain2', 1.2, { delay: 0.2, opacity: 1, y: 400, ease: Back.easeIn.config(1, 0.6) }); 71 | TweenMax.to('#mountain3', 1.2, { delay: 0.3, opacity: 1, y: 440, ease: Back.easeIn.config(1, 0.6) }); 72 | 73 | const tl = new TimelineMax(); 74 | tl 75 | 76 | .add(TweenMax.to('.breathing-information', 0.5, { css: { visibility: 'hidden', opacity: 0 } })) 77 | .add(TweenMax.to('#alt-info-box', 0.5, { css: { visibility: 'hidden', opacity: 0 } })) 78 | .add(TweenMax.to('.alt-intro', 1, { visibility: 'hidden' })); 79 | 80 | return promisify(tl); 81 | }; 82 | 83 | export const exitNarration = () => { 84 | TweenMax.to('#mountain1', 1.2, { delay: 0.1, opacity: 1, y: 340, ease: Back.easeIn.config(1, 0.6) }); 85 | TweenMax.to('#mountain2', 1.2, { delay: 0.2, opacity: 1, y: 400, ease: Back.easeIn.config(1, 0.6) }); 86 | TweenMax.to('#mountain3', 1.2, { delay: 0.3, opacity: 1, y: 440, ease: Back.easeIn.config(1, 0.6) }); 87 | 88 | const tl = new TimelineMax(); 89 | tl 90 | 91 | .add(TweenMax.to('.breathing-information', 0.5, { css: { visibility: 'hidden', opacity: 0 } })) 92 | .add(TweenMax.to('#alt-info-box', 0.5, { css: { visibility: 'hidden', opacity: 0 } })) 93 | .add(TweenMax.to('.alt-intro', 1, { visibility: 'hidden' })); 94 | 95 | return promisify(tl); 96 | }; 97 | 98 | export const infoToCatView = () => { 99 | const tl = new TimelineMax(); 100 | tl 101 | .add(TweenMax.fromTo('.character', 0.75, { css: { opacity: 0 } }, { css: { opacity: 1 } })) 102 | .to('#breathing-menu', 0.5, { opacity: 0.8 }, 1); 103 | if (getState().hasVisited) tl.to('#feel-good-modal', 0.5, { opacity: 0.8 }, 1); 104 | 105 | return promisify(tl); 106 | }; 107 | 108 | export const outOfBreathing = () => { 109 | const tl = new TimelineMax(); 110 | tl 111 | .to('#breathing-menu', 0.5, { y: -47 }, 0) 112 | .to('#feel-good-modal', 0.5, { y: 120 }, 0) 113 | .add(TweenMax.to('.sync-breath-text', 0.3, { css: { visibility: 'hidden', opacity: 0 } })) 114 | .add(TweenMax.to('.character', 0.5, { opacity: 0 })) 115 | .add(TweenMax.to('.breathing', 1, { visibility: 'hidden' })); 116 | 117 | return promisify(tl); 118 | }; 119 | 120 | export const breathingToWelldone = () => { 121 | const tl = new TimelineMax(); 122 | tl 123 | .add(TweenMax.to('#welldone-stars', 0.5, { scale: 1, opacity: 1 })) 124 | .add(TweenMax.to('#welldone-banner', 0.6, { delay: 0.4, y: 60, opacity: 1, ease: Back.easeOut.config(2) })) 125 | .add(TweenMax.to('.welldone-user', 0.2, { opacity: 1 })); 126 | 127 | TweenMax.to('.welldone-mountain1', 1.2, { delay: 0.1, opacity: 1, y: -370, ease: Elastic.easeOut.config(0.7, 0.7) }); 128 | TweenMax.to('.welldone-mountain2', 1.2, { delay: 0.4, opacity: 1, y: -390, ease: Elastic.easeOut.config(0.7, 0.7) }); 129 | TweenMax.to('.welldone-mountain3', 1.2, { delay: 0.8, opacity: 1, y: -419, ease: Elastic.easeOut.config(0.7, 0.7) }); 130 | 131 | return promisify(tl); 132 | }; 133 | 134 | export const welldoneToIntro = () => { 135 | TweenMax.to('.welldone-mountain1', 1.2, { delay: 0.1, opacity: 1, y: 370, ease: Back.easeIn.config(1, 0.6) }); 136 | TweenMax.to('.welldone-mountain2', 1.2, { delay: 0.2, opacity: 1, y: 390, ease: Back.easeIn.config(1, 0.6) }); 137 | TweenMax.to('.welldone-mountain3', 1.2, { delay: 0.3, opacity: 1, y: 419, ease: Back.easeIn.config(1, 0.6) }); 138 | 139 | const tl = new TimelineMax(); 140 | tl 141 | .add(TweenMax.to('#start-again', 0.5, { opacity: 0 })) 142 | .add(TweenMax.to('#welldone-stars', 0.3, { scale: 0 })) 143 | .add(TweenMax.to('#welldone-banner', 0.3, { y: -60, opacity: 0 })) 144 | .add(TweenMax.to('.welldone-user', 0.2, { opacity: 0 })) 145 | .add(TweenMax.to('#credits-icon', 0.5, { opacity: 0 })); 146 | 147 | return promisify(tl); 148 | }; 149 | 150 | export const breathe = () => { 151 | const feelingBetterBtn = document.getElementById('feel-good-button'); 152 | const settings = document.getElementById('breathing-settings'); 153 | 154 | const tl = new TimelineMax({ repeat: -1 }); 155 | tl 156 | .add(TweenMax.to('.belly', 5, { scale: 1.2, ease: Power1.easeInOut, visibility: true, onComplete: notifications.onExhale })) 157 | .add(TweenMax.to('.belly', 5, { scale: 1, ease: Power1.easeInOut, onComplete: notifications.onInhale })); 158 | 159 | feelingBetterBtn.addEventListener('click', () => { 160 | tl.pause(); 161 | }); 162 | settings.addEventListener('click', () => { 163 | tl.pause(); 164 | }); 165 | }; 166 | 167 | export const headMovement = () => 168 | TweenMax.fromTo('.head', 5, { y: -0 }, 169 | { y: -20, ease: Power1.easeInOut, repeat: -1, yoyo: true }); 170 | 171 | export const displayNotification = () => { 172 | const tl = new TimelineMax(); 173 | tl 174 | .add(TweenMax.to('#fading-message', 2, { display: 'block', opacity: 1 })) 175 | .add(TweenMax.delayedCall(3, () => { 176 | TweenMax.to('#fading-message', 2, { display: 'none', opacity: 0 }); 177 | })); 178 | }; 179 | 180 | export const showModal = () => { 181 | const tl = new TimelineMax(); 182 | tl 183 | .add(TweenMax.to('#feel-good-modal', 0.5, { y: 120 })) 184 | .add(TweenMax.to('#menu-options', 0.5, { opacity: 0, display: 'none' })) 185 | .add(TweenMax.to('#breathing-menu', 0.4, { css: { y: 0, className: 'full-screen-modal modal-active' } })) 186 | .add(TweenMax.to('#modal-breathing-instructions', 1, { display: 'block', opacity: 1 })); 187 | }; 188 | 189 | export const hideModal = () => { 190 | const tl = new TimelineMax(); 191 | tl 192 | .add(TweenMax.to('#modal-breathing-instructions', 1, { display: 'none', opacity: 0 })) 193 | .add(TweenMax.to('#breathing-menu', 0.4, { css: { className: '' } })) 194 | .add(TweenMax.to('#menu-options', 0.5, { opacity: 0.8, display: 'block' })) 195 | .add(TweenMax.to('#feel-good-modal', 0.5, { opacity: 0.8, y: 0, display: 'block' })); 196 | }; 197 | 198 | export const displayMenu = () => { 199 | TweenMax.to('#breathing-menu', 0.5, { y: 0 }); 200 | TweenMax.to('#feel-good-modal', 0.5, { y: 0 }); 201 | }; 202 | 203 | export const hideMenu = () => { 204 | TweenMax.to('#breathing-menu', 0.5, { y: -47 }); 205 | TweenMax.to('#feel-good-modal', 0.5, { y: 120 }); 206 | }; 207 | 208 | export const intoCredits = () => { 209 | const tl = new TimelineMax(); 210 | tl 211 | .add(TweenMax.to('.credits', 0.3, { opacity: 1, display: 'block' })); 212 | 213 | return promisify(tl); 214 | }; 215 | 216 | export const outOfCredits = () => { 217 | const tl = new TimelineMax(); 218 | tl 219 | .add(TweenMax.to('.credits', 0.3, { opacity: 0, display: 'none' })); 220 | 221 | return promisify(tl); 222 | }; 223 | -------------------------------------------------------------------------------- /src/templates/avatars/cat.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | head 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | hands 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | belly 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | legs2 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | -------------------------------------------------------------------------------- /src/templates/avatars/panda.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | head 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | hands 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | belly 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | legs 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /src/assets/images/welldone/banner.svg: -------------------------------------------------------------------------------- 1 | 2 | banner welldone 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | --------------------------------------------------------------------------------