├── .gitignore ├── docs ├── baloo.woff2 ├── button.mp3 ├── button.wav ├── index.html ├── script.js └── style.css ├── package.json ├── prototype ├── animations.html ├── prototype.html ├── animation.js ├── prototype.js └── animations.css ├── README.md ├── scripts └── pack.js └── assets └── rigFull.svg /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | -------------------------------------------------------------------------------- /docs/baloo.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bsawyer/tamagotchi/HEAD/docs/baloo.woff2 -------------------------------------------------------------------------------- /docs/button.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bsawyer/tamagotchi/HEAD/docs/button.mp3 -------------------------------------------------------------------------------- /docs/button.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bsawyer/tamagotchi/HEAD/docs/button.wav -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tamagotchi", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "directories": { 7 | "doc": "docs" 8 | }, 9 | "scripts": { 10 | "test": "echo \"Error: no test specified\" && exit 1" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "git+https://github.com/bsawyer/tamagotchi.git" 15 | }, 16 | "keywords": [], 17 | "author": "", 18 | "license": "ISC", 19 | "bugs": { 20 | "url": "https://github.com/bsawyer/tamagotchi/issues" 21 | }, 22 | "homepage": "https://github.com/bsawyer/tamagotchi#readme", 23 | "devDependencies": { 24 | "puppeteer": "^2.0.0" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /prototype/animations.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | animation prototype 7 | 8 | 9 | 10 | 11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /prototype/prototype.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | prototype 7 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Tamagotchi 2 | 3 | An html game inspired by [Tamagotchi](https://en.wikipedia.org/wiki/Tamagotchi) the handheld digital pet 4 | 5 | ## Development 6 | 7 | ### Setup 8 | 9 | Install dev dependencies 10 | 11 | ``` 12 | ~ npm i 13 | ``` 14 | 15 | ### Figma 16 | 17 | The [figma designs](https://www.figma.com/file/l7scjUYTK6q8G982lRL8ct/tamagotchi-clone?type=design&mode=design&t=KqeiXNbpnqQO5XIt-1) are consolidated into a single page called "Rig" 18 | 19 | Within the page, groups follow a naming convention to encapsulate what svg shapes will be animated with css e.g. 20 | 21 | > with the group name 22 | `rigHands:armatures:rotate` 23 | all the child shapes of this group can be targeted for animating with the css selector `.armature-right-hand.armature-rotate` 24 | 25 | The page is exported from Figma to `assets/rigFull.svg` and then converted to `docs/svg.js` by running 26 | 27 | ``` 28 | ~ node ./scripts/pack.js 29 | ``` 30 | 31 | ### Game 32 | 33 | The game script is in `docs/script.js` and can played by opening `docs/index.html` in a browser 34 | 35 | To see live stats append `?debug` to the url -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | Tamagotchi 11 | 12 | 13 | 14 | 15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /scripts/pack.js: -------------------------------------------------------------------------------- 1 | const puppeteer = require('puppeteer'); 2 | const fs = require('fs'); 3 | const path = require('path'); 4 | 5 | const state = {}; 6 | const SOURCE_FILE = '../assets/rigFull.svg'; 7 | const OUTPUT_FILE = `../docs/svg.js`; 8 | 9 | function pack(){ 10 | console.log(`packing ${new Date().toLocaleTimeString('en-US')}...`) 11 | puppeteer 12 | .launch() 13 | .then(browser => { 14 | state.browser = browser; 15 | return browser.newPage(); 16 | }) 17 | .then(page => { 18 | state.page = page; 19 | return page; 20 | }) 21 | .then(page => page.goto('file://' + path.resolve(__dirname, SOURCE_FILE))) 22 | .then(response => { 23 | return state.page.evaluate(()=>{ 24 | const attributes = ['fill', 'opacity']; 25 | const data = []; 26 | const svg = document.querySelector('svg'); 27 | 28 | attributes.forEach(a => { 29 | Array.prototype.forEach.call(document.querySelectorAll(`#rigFull>g:not([id=rigIcons]) [${a}]`), (e)=>{ 30 | e.removeAttribute(a); 31 | }); 32 | }); 33 | 34 | parseChildren(svg, data); 35 | 36 | function parseChildren(target, children){ 37 | Array.prototype.slice.call(target.children).forEach(child => parseChild(child, children)) 38 | } 39 | 40 | function parseChild(child, children){ 41 | let name = child.getAttribute('id'); 42 | let armatures = []; 43 | if(name.indexOf(':armatures:') !== -1){ 44 | name = name.split(':armatures:'); 45 | armatures = name[1].split(','); 46 | name = name[0]; 47 | } 48 | if(name.indexOf('rig') === 0 && name.indexOf('right') !== 0){ 49 | name = name.replace('rig', ''); 50 | name = name.split(/(?=[A-Z])/).join('-').toLowerCase(); 51 | const c = { 52 | name, 53 | type: 'rig', 54 | armatures, 55 | children: [] 56 | }; 57 | children.push(c); 58 | parseChildren(child, c.children); 59 | }else{ 60 | name = name.split(/(?=[A-Z])/).join('-').toLowerCase(); 61 | // child.removeAttribute('id'); 62 | child.setAttribute('id', name); 63 | children.push({ 64 | name, 65 | type: 'svg', 66 | armatures, 67 | svg: child.outerHTML 68 | }); 69 | } 70 | } 71 | return { 72 | viewBox: svg.getAttribute('viewBox'), 73 | data 74 | }; 75 | }); 76 | }) 77 | .then(data => { 78 | return fs.writeFileSync(path.resolve(__dirname, OUTPUT_FILE), `window.SVG_DATA = ${JSON.stringify(data, null, 2)};`); 79 | }) 80 | .then(() => state.browser.close()); 81 | } 82 | 83 | fs.watchFile(path.resolve(__dirname, SOURCE_FILE), (eventType, filename) => { 84 | if (filename) { 85 | pack(); 86 | } 87 | }); 88 | 89 | pack(); 90 | -------------------------------------------------------------------------------- /prototype/animation.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | function init(state){ 4 | renderRigs(state, document.querySelector('.canvas')); 5 | } 6 | 7 | function renderRigs(rigs, target){ 8 | rigs.forEach(r => renderRig(r, r.name, target)); 9 | } 10 | 11 | function renderRig(rig, name, target){ 12 | const element = document.createElement('div'); 13 | element.classList.add('rig'); 14 | element.classList.add(`rig-${name}`); 15 | if(target){ 16 | target.appendChild(element); 17 | } 18 | target = element; 19 | if(rig.armatures){ 20 | rig.armatures.forEach(a => { 21 | const div = document.createElement('div'); 22 | div.classList.add('armature'); 23 | div.classList.add(`armature-${a}`); 24 | target.appendChild(div); 25 | target = div; 26 | }); 27 | } 28 | if(rig.symbol){ 29 | const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); 30 | const use = document.createElementNS('http://www.w3.org/2000/svg', 'use'); 31 | use.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', `#symbol-${name}`); 32 | svg.appendChild(use); 33 | target.appendChild(svg); 34 | } 35 | rig.element = element; 36 | rig.rigs && renderRigs(rig.rigs, target); 37 | } 38 | 39 | function unpack(){ 40 | const svg = document.querySelector('svg'); 41 | const canvas = document.querySelector('.canvas'); 42 | window.svgData.data.forEach(d => unpackSVG(d, {svg, canvas, target: canvas})) 43 | } 44 | 45 | function unpackSVG(data, state){ 46 | let target = state.target; 47 | if(data.type === 'rig'){ 48 | const element = document.createElement('div'); 49 | element.classList.add('rig'); 50 | element.classList.add(`rig-${data.name}`); 51 | target.appendChild(element); 52 | let t = element; 53 | data.armatures.forEach(a => { 54 | const div = document.createElement('div'); 55 | div.classList.add('armature', `armature-${data.name}`, `armature-${a}`); 56 | t.appendChild(div); 57 | t = div; 58 | }); 59 | state.target = t; 60 | data.children.forEach(child => unpackSVG(child, state)); 61 | state.target = target; 62 | }else{ 63 | data.armatures.forEach(a => { 64 | const div = document.createElement('div'); 65 | div.classList.add('armature', `armature-${data.name}`, `armature-${a}`); 66 | target.appendChild(div); 67 | target = div; 68 | }); 69 | const symbol = document.createElementNS('http://www.w3.org/2000/svg', 'symbol'); 70 | if(data.name.indexOf('icon') !== -1){ 71 | symbol.setAttribute('id', `symbol-${data.name}`); 72 | symbol.setAttribute('viewBox', '0 0 64 64'); 73 | }else{ 74 | symbol.setAttribute('id', `symbol-${data.name}`); 75 | symbol.setAttribute('viewBox', window.svgData.viewBox); 76 | } 77 | 78 | symbol.innerHTML = data.svg; 79 | state.svg.appendChild(symbol); 80 | const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); 81 | const use = document.createElementNS('http://www.w3.org/2000/svg', 'use'); 82 | svg.classList.add(`symbol-${name}`); //for fill color 83 | use.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', `#symbol-${data.name}`); 84 | svg.appendChild(use); 85 | target.appendChild(svg); 86 | } 87 | } 88 | 89 | const controls = [ 90 | 'sleep', 91 | 'awake', 92 | 'eat', 93 | 'bathroom', 94 | 'play', 95 | 'relax', 96 | 'create' 97 | ]; 98 | let activeControl = 0; 99 | function createControls(){ 100 | const rows = Array.prototype.slice.call(document.querySelectorAll('.row')); 101 | controls.forEach((c,i)=> { 102 | const ctrl = document.createElement('div'); 103 | ctrl.classList.add('ctrl-btn', `ctrl-btn--${c}`); 104 | if(i === activeControl){ 105 | document.querySelector('.rig-full').classList.add(`rig--${c}`); 106 | ctrl.classList.add('active'); 107 | } 108 | ctrl.addEventListener('click', ()=>{ 109 | document.querySelector('.rig-full').classList.remove(`rig--${controls[activeControl]}`); 110 | document.querySelector('.rig-full').classList.add(`rig--${c}`); 111 | document.querySelector('.active').classList.remove('active'); 112 | ctrl.classList.add('active'); 113 | activeControl = controls.indexOf(c); 114 | }); 115 | const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); 116 | const use = document.createElementNS('http://www.w3.org/2000/svg', 'use'); 117 | svg.classList.add(`btn-symbol-${c}`); 118 | use.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', `#symbol-${c}-icon`); 119 | svg.appendChild(use); 120 | ctrl.appendChild(svg); 121 | rows[i < 4 ? 0 : 1].appendChild(ctrl); 122 | }); 123 | } 124 | 125 | unpack(); 126 | createControls(); 127 | 128 | document.querySelector('.rig-full').classList.add('rig--idle'); 129 | // document.querySelector('.rig-face').classList.add('rig--play'); 130 | -------------------------------------------------------------------------------- /prototype/prototype.js: -------------------------------------------------------------------------------- 1 | const STATE = {}; 2 | 3 | // -------- Values -------- 4 | class Resource{ 5 | constructor({ 6 | min = 0, 7 | max = 1, 8 | count = 0 9 | }={}){ 10 | this.min = min; 11 | this.max = max; 12 | this.count = count; 13 | } 14 | } 15 | 16 | class Experience extends Resource{ 17 | constructor(){ 18 | super({ 19 | max: -1, 20 | count: 0 21 | }) 22 | } 23 | } 24 | 25 | class Concentration extends Resource{ 26 | constructor(){ 27 | super({ 28 | max: 10, 29 | count: 10 30 | }) 31 | } 32 | } 33 | 34 | class Energy extends Resource{ 35 | constructor(){ 36 | super({ 37 | max: 10, 38 | count: 10 39 | }) 40 | } 41 | } 42 | 43 | class Ideas extends Resource{ 44 | constructor(){ 45 | super({ 46 | max: 10, 47 | count: 5 48 | }) 49 | } 50 | } 51 | 52 | class Waste extends Resource{ 53 | constructor(){ 54 | super({ 55 | max: 10, 56 | count: 0 57 | }) 58 | } 59 | } 60 | 61 | class Fatigue extends Resource{ 62 | constructor(){ 63 | super({ 64 | max: 10, 65 | count: 0 66 | }) 67 | } 68 | } 69 | 70 | class Mood { 71 | constructor({ 72 | ranges = {}, 73 | emoji 74 | }={}){ 75 | this.ranges = ranges; 76 | this.emoji = emoji; 77 | } 78 | } 79 | 80 | class Pain extends Mood { 81 | constructor(){ 82 | super({ 83 | emoji: '😖', 84 | ranges: { 85 | waste: [8, 10], 86 | energy: [0, 2] 87 | } 88 | }); 89 | } 90 | } 91 | 92 | class Frustration extends Mood { 93 | constructor(){ 94 | super({ 95 | emoji: '😩', 96 | ranges: { 97 | energy: [2, 5], 98 | fatigue: [9, 10] 99 | } 100 | }); 101 | } 102 | } 103 | 104 | class Restless extends Mood { 105 | constructor(){ 106 | super({ 107 | emoji: '😠', 108 | ranges: { 109 | waste: [6, 8], 110 | concentration: [0, 2] 111 | } 112 | }); 113 | } 114 | } 115 | 116 | class Tired extends Mood { 117 | constructor(){ 118 | super({ 119 | emoji: '😔', 120 | ranges: { 121 | fatigue: [6, 9] 122 | } 123 | }); 124 | } 125 | } 126 | 127 | class Sad extends Mood { 128 | constructor(){ 129 | super({ 130 | emoji: '😪', 131 | ranges: { 132 | ideas: [0, 2] 133 | } 134 | }); 135 | } 136 | } 137 | 138 | class Ecstatic extends Mood { 139 | constructor(){ 140 | super({ 141 | emoji: '🥰', 142 | ranges: { 143 | waste: [0, 1], 144 | fatigue: [0, 1], 145 | ideas: [9, 10], 146 | energy: [9, 10] 147 | } 148 | }); 149 | } 150 | } 151 | 152 | class Happy extends Mood { 153 | constructor(){ 154 | super({ 155 | emoji: '😊', 156 | ranges: { 157 | concentration: [5, 10], 158 | energy: [5, 10], 159 | fatigue: [0, 5], 160 | ideas: [5, 10], 161 | waste: [0, 5] 162 | } 163 | }); 164 | } 165 | } 166 | 167 | const TIME_FACTOR = 5; // 1 second * X 168 | // cost and gain are calculated per second 169 | class Action{ 170 | constructor({ 171 | duration = 1000, 172 | cost = {}, 173 | gain = {}, 174 | start 175 | }={}){ 176 | this.duration = duration; 177 | this.cost = cost; 178 | this.gain = gain; 179 | this.start = start || new Date(); 180 | } 181 | } 182 | 183 | class Awake extends Action{ 184 | constructor(){ 185 | super({ 186 | duration: -1, 187 | cost: { 188 | energy: 1 / TIME_FACTOR 189 | }, 190 | gain: { 191 | experience: 1 / TIME_FACTOR, 192 | fatigue: 0.5 / TIME_FACTOR 193 | } 194 | }); 195 | } 196 | } 197 | const SLEEP_DURATION = scaleTime(9, TIME_FACTOR); // seconds 198 | class Sleep extends Action{ 199 | constructor(){ 200 | super({ 201 | duration: SLEEP_DURATION * 1000, 202 | cost: { 203 | fatigue: 10 / SLEEP_DURATION, 204 | energy: 2 / SLEEP_DURATION 205 | }, 206 | gain: { 207 | concentration: 10 / SLEEP_DURATION 208 | } 209 | }); 210 | } 211 | } 212 | const EAT_DURATION = scaleTime(2, TIME_FACTOR); // seconds 213 | class Eat extends Action{ 214 | constructor(){ 215 | super({ 216 | duration: EAT_DURATION * 1000, 217 | cost: { 218 | concentration: 1 / EAT_DURATION 219 | }, 220 | gain: { 221 | energy: 10 / EAT_DURATION, 222 | waste: 4.5 / EAT_DURATION 223 | } 224 | }) 225 | } 226 | } 227 | const BATHROOM_DURATION = scaleTime(1.5, TIME_FACTOR); // seconds 228 | class Bathroom extends Action{ 229 | constructor(){ 230 | super({ 231 | duration: BATHROOM_DURATION * 1000, 232 | cost: { 233 | concentration: 5 / BATHROOM_DURATION, 234 | waste: 10 / BATHROOM_DURATION 235 | }, 236 | gain: { 237 | ideas: 1 / BATHROOM_DURATION, 238 | fatigue: 1 / BATHROOM_DURATION, 239 | } 240 | }); 241 | } 242 | } 243 | class Play extends Action{ 244 | constructor(){ 245 | super({ 246 | duration: -1, 247 | cost: { 248 | energy: 1 / TIME_FACTOR, 249 | concentration: 1 / TIME_FACTOR 250 | }, 251 | gain: { 252 | experience: 5 / TIME_FACTOR, 253 | ideas: 1 / TIME_FACTOR 254 | } 255 | }); 256 | } 257 | } 258 | class Create extends Action{ 259 | constructor(){ 260 | super({ 261 | duration: -1, 262 | cost: { 263 | ideas: 1 / TIME_FACTOR, 264 | concentration: 1 / TIME_FACTOR 265 | }, 266 | gain: { 267 | experience: 50 / TIME_FACTOR 268 | } 269 | }); 270 | } 271 | } 272 | class Observe extends Action{ 273 | constructor(){ 274 | super({ 275 | duration: -1, 276 | cost: { 277 | ideas: 0.5 / TIME_FACTOR 278 | }, 279 | gain: { 280 | concentration: 1 / TIME_FACTOR, 281 | energy: 1.5 / TIME_FACTOR 282 | } 283 | }); 284 | } 285 | } 286 | class Creature{ 287 | constructor(creature = {}){ 288 | this.actions = []; 289 | this.moods = {}; 290 | resourceKeys.forEach(k => this[k] = creature[k] && Object.assign({}, creature[k]) || new resources[k]()); 291 | moodKeys.forEach(k => this.moods[k] = creature.moods && creature.moods[k] || new moods[k]()); 292 | } 293 | } 294 | 295 | class UI{ 296 | constructor(state){ 297 | this.actions = actionKeys.map(k => { 298 | const btn = document.createElement('button'); 299 | btn.textContent = k; 300 | btn.setAttribute('data-action', k); 301 | return btn; 302 | }); 303 | this.resources = {}; 304 | resourceKeys.forEach(k => { 305 | const p = document.createElement('progress'); 306 | const d = document.createElement('div'); 307 | const l = document.createElement('label'); 308 | p.setAttribute('max', state.creature[k].max === -1 ? 1000 : state.creature[k].max); 309 | p.setAttribute('data-resource', k); 310 | p.setAttribute('id', k); 311 | l.setAttribute('for', k); 312 | l.textContent = k; 313 | p.value = state.creature[k].count; 314 | d.appendChild(l); 315 | d.appendChild(p); 316 | this.resources[k] = d; 317 | }); 318 | const pauseBtn = document.createElement('button'); 319 | pauseBtn.textContent = 'pause'; 320 | this.pauseBtn = pauseBtn; 321 | 322 | const playBtn = document.createElement('button'); 323 | playBtn.textContent = 'play'; 324 | this.playBtn = playBtn; 325 | 326 | const mood = document.createElement('div'); 327 | mood.classList.add('emoji'); 328 | this.mood = mood; 329 | 330 | const action = document.createElement('div'); 331 | action.classList.add('emoji'); 332 | action.classList.add('emoji-action'); 333 | this.action = action; 334 | } 335 | } 336 | 337 | class Game{ 338 | constructor(){ 339 | this.playing = false; 340 | } 341 | } 342 | 343 | const resources = { 344 | 'concentration': Concentration, 345 | 'energy': Energy, 346 | 'experience': Experience, 347 | 'fatigue': Fatigue, 348 | 'ideas': Ideas, 349 | 'waste': Waste 350 | }; 351 | 352 | const resourceKeys = Object.keys(resources); 353 | 354 | const actions = { 355 | 'awake': Awake, 356 | 'bathroom': Bathroom, 357 | 'create': Create, 358 | 'eat': Eat, 359 | 'observe': Observe, 360 | 'play': Play, 361 | 'sleep': Sleep 362 | }; 363 | 364 | const emojiAction = { 365 | 'awake': '', 366 | 'bathroom': '🚽', 367 | 'create': '🖍️', 368 | 'eat': '🍌', 369 | 'play': '🧸', 370 | 'sleep': '💤', 371 | 'observe': '🔭' 372 | }; 373 | 374 | const actionKeys = Object.keys(actions); 375 | 376 | const wakingActions = [ 377 | Eat, 378 | Bathroom, 379 | Play, 380 | Observe 381 | ]; 382 | 383 | const moods = { 384 | 'pain': Pain, 385 | 'frustration': Frustration, 386 | 'restless': Restless, 387 | 'tired': Tired, 388 | 'sad': Sad, 389 | 'happy': Happy, 390 | 'ecstatic': Ecstatic 391 | }; 392 | 393 | const moodKeys = [ 394 | 'pain', 395 | 'frustration', 396 | 'restless', 397 | 'tired', 398 | 'sad', 399 | 'ecstatic', 400 | 'happy' 401 | ]; 402 | 403 | // -------- Policies -------- 404 | function isSleeping(state){ 405 | return state.creature.actions.some(a => a instanceof Sleep); 406 | } 407 | 408 | function isAwake(state){ 409 | return state.creature.actions.some(a => a instanceof Awake); 410 | } 411 | 412 | function canConcentrate(state, diff){ 413 | diff = diff || calculateResources(state).diff; 414 | return diff.concentration.count > diff.concentration.min; 415 | } 416 | 417 | function isFatigued(state, diff){ 418 | diff = diff || calculateResources(state).diff; 419 | return diff.fatigue.count >= diff.fatigue.max; 420 | } 421 | 422 | function hasEnergy(state, diff){ 423 | diff = diff || calculateResources(state).diff; 424 | return diff.energy.count > diff.energy.min; 425 | } 426 | 427 | function canEat(state){ 428 | const diff = calculateResources(state).diff; 429 | return isAwake(state) && !isFatigued(state, diff) && canConcentrate(state, diff) && diff.energy.count < diff.energy.max; 430 | } 431 | 432 | function canBathroom(state){ 433 | const diff = calculateResources(state).diff; 434 | return isAwake(state) && !isFatigued(state, diff) && canConcentrate(state, diff) && diff.waste.count > diff.waste.min; 435 | } 436 | 437 | function canPlay(state){ 438 | const diff = calculateResources(state).diff; 439 | return isAwake(state) && canConcentrate(state, diff) && hasEnergy(state, diff) && diff.ideas.count < diff.ideas.max; 440 | } 441 | 442 | function canCreate(state){ 443 | const diff = calculateResources(state).diff; 444 | return isAwake(state) && canConcentrate(state, diff) && diff.ideas.count > diff.ideas.min; 445 | } 446 | 447 | function canObserve(state){ 448 | const diff = calculateResources(state).diff; 449 | return isAwake(state) && diff.concentration.count < diff.concentration.max && diff.ideas.count > diff.ideas.min; 450 | } 451 | 452 | function isPerformingWaking(state){ 453 | return state.creature.actions.some(a => wakingActions.some(p => a instanceof p)); 454 | } 455 | 456 | function isPerforming(state, action){ 457 | return state.creature.actions.some( a => a instanceof actions[action]); 458 | } 459 | 460 | function canPerformAction(state, action){ 461 | if(action === 'awake'){ 462 | return isSleeping(state) || !state.creature.actions.length; 463 | } 464 | if(action === 'sleep'){ 465 | return isAwake(state); 466 | } 467 | if(action === 'eat'){ 468 | return canEat(state); 469 | } 470 | if(action === 'bathroom'){ 471 | return canBathroom(state); 472 | } 473 | if(action === 'play'){ 474 | return canPlay(state); 475 | } 476 | if(action === 'create'){ 477 | return canCreate(state); 478 | } 479 | if(action === 'observe'){ 480 | return canObserve(state); 481 | } 482 | return true; 483 | } 484 | 485 | function canCompleteAction(state, action){ 486 | const checkDate = new Date(); 487 | if(action.duration !== -1){ 488 | if(checkDate - action.start >= action.duration){ 489 | return true; 490 | } 491 | } 492 | if(action instanceof Eat){ 493 | return !canEat(state); 494 | } 495 | if(action instanceof Bathroom){ 496 | return !canBathroom(state); 497 | } 498 | if(action instanceof Play){ 499 | return !canPlay(state); 500 | } 501 | if(action instanceof Create){ 502 | return !canCreate(state); 503 | } 504 | if(action instanceof Observe){ 505 | return !canObserve(state); 506 | } 507 | return false; 508 | } 509 | 510 | // -------- Services --------- 511 | function init(state){ 512 | state.creature = new Creature(); 513 | state.ui = new UI(state); 514 | state.game = new Game(); 515 | state.ui.actions.forEach(a => { 516 | document.body.appendChild(a); 517 | a.addEventListener('click', evt => actionOnClick(state, evt)) 518 | }); 519 | resourceKeys.forEach(k => { 520 | document.body.appendChild(state.ui.resources[k]); 521 | }); 522 | state.ui.pauseBtn.addEventListener('click', evt => state.game.playing = false); 523 | state.ui.playBtn.addEventListener('click', evt => { 524 | state.game.playing = true; 525 | gameTick(state); 526 | }); 527 | document.body.appendChild(state.ui.playBtn); 528 | document.body.appendChild(state.ui.pauseBtn); 529 | document.body.appendChild(state.ui.mood); 530 | document.body.appendChild(state.ui.action); 531 | } 532 | 533 | function updateUI(state){ 534 | const diff = calculateResources(state).diff; 535 | resourceKeys.forEach(k => { 536 | state.ui.resources[k].querySelector('progress').value = diff[k].count; 537 | }); 538 | const mood = getMood(state, diff); 539 | if(!state.mood || state.mood.name !== mood.name){ 540 | state.mood = mood; 541 | } 542 | state.ui.action.classList.add('hide'); 543 | state.ui.actions.forEach(a => { 544 | const action = a.getAttribute('data-action'); 545 | a.classList.toggle('active', isPerforming(state, action)); 546 | if(isPerforming(state, action) && action !== 'awake'){ 547 | if(state.ui.action.textContent !== emojiAction[action]){ 548 | state.ui.action.textContent = `${emojiAction[action]}`; 549 | } 550 | state.ui.action.classList.remove('hide'); 551 | } 552 | }); 553 | // state.ui.mood.textContent = `${mood.mood.emoji} : ${mood.name} : ${mood.reason}`; 554 | state.ui.mood.textContent = `${mood.mood.emoji}`; 555 | } 556 | 557 | function gameTick(state){ 558 | checkActions(state); 559 | updateUI(state); 560 | if(state.game.playing){ 561 | requestAnimationFrame(() => gameTick(state)); 562 | } 563 | } 564 | 565 | function completeWakingActions(state){ 566 | state.creature.actions.forEach(a => { 567 | if(wakingActions.some(p => a instanceof p)){ 568 | completeAction(state, a); 569 | } 570 | }) 571 | } 572 | 573 | function performAction(state, action){ 574 | if(action === 'awake' && !!state.creature.actions.length){ 575 | // completeSleepingActions(state) 576 | completeAction(state, state.creature.actions.find(a => a instanceof Sleep)); 577 | } 578 | if(action === 'sleep'){ 579 | completeWakingActions(state); 580 | completeAction(state, state.creature.actions.find(a => a instanceof Awake)); 581 | } 582 | if(action === 'eat' || action === 'bathroom' || action === 'play' || action === 'observe'){ 583 | completeWakingActions(state); 584 | } 585 | state.creature.actions.push(new actions[action]()); 586 | } 587 | 588 | function checkActions(state){ 589 | state.creature.actions.slice().forEach(a => { 590 | if(canCompleteAction(state, a)){ 591 | if(a instanceof Sleep){ 592 | performAction(state, 'awake'); 593 | return; 594 | } 595 | if(a instanceof Awake){ 596 | performAction(state, 'sleep'); 597 | return; 598 | } 599 | completeAction(state, a); 600 | } 601 | }); 602 | } 603 | 604 | function completeAction(state, action){ 605 | const calc = calculateResources(state); 606 | const diff = calc.diff; 607 | updateResources(state, diff); 608 | state.creature.actions.splice(state.creature.actions.indexOf(action), 1); 609 | state.creature.actions.forEach(a => { 610 | const elapsedSeconds = calc.calcTime - a.start; 611 | a.start = new Date(); 612 | if(a.duration !== -1){ 613 | a.duration = a.duration - elapsedSeconds; 614 | if(a.duration < 0){ 615 | a.duration = 0; 616 | } 617 | } 618 | }); 619 | } 620 | 621 | function actionOnClick(state, evt){ 622 | const action = evt.currentTarget.getAttribute('data-action'); 623 | if(isPerforming(state, action) && (action === 'play' || action === 'eat' || action === 'create' || action === 'observe')){ 624 | completeAction(state, state.creature.actions.find(a => a instanceof actions[action])); 625 | return; 626 | } 627 | if(canPerformAction(state, action)){ 628 | performAction(state, action); 629 | } 630 | } 631 | 632 | function calculateResources(state){ 633 | const diff = new Creature(state.creature); 634 | const calcTime = new Date(); 635 | state.creature.actions.forEach(a => { 636 | const elapsedSeconds = Math.min((calcTime - a.start) / 1000); 637 | Object.keys(a.cost).forEach( r => { 638 | const val = diff[r].count - (a.cost[r] * elapsedSeconds); 639 | diff[r].count = val < state.creature[r].min ? state.creature[r].min : val; 640 | }); 641 | Object.keys(a.gain).forEach( r => { 642 | const val = diff[r].count + (a.gain[r] * elapsedSeconds); 643 | diff[r].count = val > state.creature[r].max && state.creature[r].max !== -1 ? state.creature[r].max : val; 644 | }); 645 | }); 646 | return { 647 | diff, 648 | calcTime 649 | }; 650 | } 651 | 652 | function updateResources(state, diff){ 653 | resourceKeys.forEach(k => { 654 | state.creature[k].count = diff[k].count; 655 | }); 656 | } 657 | 658 | function getMood(state, diff){ 659 | const activeMoods = moodKeys 660 | .map(k => { 661 | let mood; 662 | let reason; 663 | Object.keys(state.creature.moods[k].ranges) 664 | .some(r => { 665 | if(rangeContains(state.creature.moods[k].ranges[r], diff[r].count)){ 666 | mood = state.creature.moods[k]; 667 | reason = r; 668 | return true; 669 | } 670 | }); 671 | if(mood){ 672 | return { 673 | mood, 674 | reason, 675 | name: k 676 | } 677 | } 678 | return null; 679 | }) 680 | .filter(m => m !== null) 681 | if(activeMoods.length){ 682 | return activeMoods[0]; 683 | } 684 | return { 685 | name: 'default', 686 | mood: { 687 | emoji: '🤪🤔😃' 688 | }, 689 | reason: 'no other match' 690 | }; 691 | } 692 | 693 | function rangeContains(r, n){ 694 | return n >= r[0] && n <= r[1]; 695 | } 696 | 697 | function scaleTime(seconds, factor){ 698 | return seconds * factor; 699 | } 700 | 701 | init(STATE); 702 | -------------------------------------------------------------------------------- /prototype/animations.css: -------------------------------------------------------------------------------- 1 | /* ------- Colors ------- */ 2 | :root{ 3 | --fur: #DE9E53; 4 | --fur-shadow: #BB6D12; 5 | --face: #F0CEA6; 6 | --eyes: #363636; 7 | --mouth: #363636; 8 | --tongue: #FFAFAF; 9 | --nose: #FFAFAF; 10 | --background: #FFFFFF; 11 | --shadow: #E6EBF3; 12 | } 13 | 14 | /* ------- Base ------- */ 15 | :root { 16 | font-size: 1vh; 17 | } 18 | html{ 19 | height: 100%; 20 | } 21 | body{ 22 | height: 100%; 23 | margin: 0; 24 | font-size: 10rem; 25 | } 26 | .stage{ 27 | height: 100%; 28 | display: flex; 29 | align-items: center; 30 | justify-content: space-around; 31 | flex-direction: column; 32 | background-color: #F2D7BF; 33 | } 34 | .canvas{ 35 | width: calc(25rem * 1.5); 36 | height: calc(30rem * 1.5); 37 | position: relative; 38 | } 39 | .canvas svg{ 40 | width: 100%; 41 | height: 100%; 42 | } 43 | .controls{ 44 | 45 | } 46 | .row{ 47 | display: flex; 48 | } 49 | .ctrl-btn{ 50 | width: 6.4rem; 51 | height: 6.4rem; 52 | padding: 1rem; 53 | margin: 1rem; 54 | display: flex; 55 | background-color: #FFF5D3; 56 | border-radius: 2rem; 57 | transform: rotate(5deg); 58 | box-shadow: .4rem .7rem 0px #FBE9C0; 59 | } 60 | 61 | .ctrl-btn svg{ 62 | width: 100%; 63 | height: 100%; 64 | } 65 | .ctrl-btn--sleep{ 66 | background-color: #31288e; 67 | border-radius: 2rem; 68 | transform: rotate(-8deg); 69 | box-shadow: -.5rem .5rem 0px #040221; 70 | } 71 | .ctrl-btn--awake{ 72 | background-color: #9ac9ff; 73 | border-radius: 2rem; 74 | transform: rotate(-5deg); 75 | box-shadow: -.4rem .7rem 0px #5da0ef; 76 | } 77 | .ctrl-btn--sleep.active, 78 | .ctrl-btn--awake.active{ 79 | display:none; 80 | } 81 | /* ------- Rig ------- */ 82 | .rig, .armature{ 83 | position: absolute; 84 | left: 0; 85 | top: 0; 86 | width: 100%; 87 | height: 100%; 88 | } 89 | .armature{ 90 | transition: transform 1s ease-in-out; 91 | } 92 | .armature-right-hand{ 93 | transform-origin: calc(4.7rem * 1.5) calc(17.8rem * 1.5); 94 | } 95 | .armature-left-hand{ 96 | transform-origin: calc(20.2rem * 1.5) calc(17.8rem * 1.5); 97 | } 98 | .armature-right-foot{ 99 | transform-origin: calc(8.1rem * 1.5) calc(23.6rem * 1.5); 100 | } 101 | .armature-left-foot{ 102 | transform-origin: calc(17.1rem * 1.5) calc(23.6rem * 1.5); 103 | } 104 | .armature-feet{ 105 | transform-origin: calc(12.4rem * 1.5) calc(23.6rem * 1.5); 106 | } 107 | .armature-shadow{ 108 | transform-origin: calc(12.4rem * 1.5) calc(26rem * 1.5); 109 | } 110 | .armature-face{ 111 | transform-origin: calc(12.4rem * 1.5) calc(15rem * 1.5); 112 | } 113 | .armature-left-tear{ 114 | transform-origin: calc(17.4rem * 1.5) calc(15.3rem * 1.5); 115 | } 116 | .rig-icons{ 117 | display: none; 118 | } 119 | .rig-face .armature-display, 120 | .rig-mug .armature-display, 121 | .rig-yarn .armature-display, 122 | .rig-ping-pong .armature-display, 123 | .rig-cookie .armature-display, 124 | .rig-moon .armature-display, 125 | .rig-hearts .armature-display, 126 | .rig-clouds .armature-display{ 127 | opacity: 0; 128 | } 129 | .rig-accessories .armature-ears-small.armature-display, 130 | .rig-accessories .armature-ears-big.armature-display{ 131 | opacity: 0; 132 | } 133 | 134 | /* ------- Animations ------- */ 135 | .rig--happy .armature-eyes-open.armature-display, 136 | .rig--happy .armature-small-smile.armature-display{ 137 | opacity: 1; 138 | } 139 | .rig--pain .armature-eyes-wincing-big.armature-display, 140 | .rig--pain .armature-big-frown.armature-display{ 141 | opacity: 1; 142 | } 143 | .rig--frustration .armature-eyes-wincing-small.armature-display, 144 | .rig--frustration .armature-medium-frown.armature-display, 145 | .rig--frustration .armature-eyebrows-big-up.armature-display{ 146 | opacity: 1; 147 | } 148 | .rig--tired .armature-eyes-closed.armature-display, 149 | .rig--tired .armature-medium.armature-display, 150 | .rig--tired .armature-eyebrows-small-up.armature-display{ 151 | opacity: 1; 152 | } 153 | .rig--restless .armature-eyes-concentrated.armature-display, 154 | .rig--restless .armature-small.armature-display, 155 | .rig--restless .armature-eyebrows-serious.armature-display{ 156 | opacity: 1; 157 | } 158 | .rig--sad .armature-eyes-closed.armature-display, 159 | .rig--sad .armature-small-frown.armature-display, 160 | .rig--sad .armature-eyebrows-small-up.armature-display{ 161 | opacity: 1; 162 | } 163 | .rig--ecstatic .armature-eyes-happy-closed.armature-display, 164 | .rig--ecstatic .armature-big-smile.armature-display, 165 | .rig--ecstatic .armature-cheeks-happy.armature-display{ 166 | opacity: 1; 167 | } 168 | .rig--bathroom .armature-eyes-wincing-big.armature-display, 169 | .rig--bathroom .armature-grimmice.armature-display{ 170 | opacity: 1; 171 | } 172 | .rig--sleep .armature-eyes-closed.armature-display, 173 | .rig--sleep .armature-medium.armature-display, 174 | .rig--sleep .rig-moon .armature-display{ 175 | opacity: 1; 176 | } 177 | .rig--sleep .armature-body.armature-rotate, 178 | .rig--sleep .armature-hair-face.armature-rotate, 179 | .rig--sleep .armature-hair-body.armature-rotate, 180 | .rig--sleep .armature-ears-small.armature-rotate, 181 | .rig--sleep .armature-ears-medium.armature-rotate, 182 | .rig--sleep .armature-ears-big.armature-rotate{ 183 | transform: rotate(-10deg); 184 | } 185 | .rig--play .armature-eyes-concentrated.armature-display, 186 | .rig--play .armature-side-tongue.armature-display{ 187 | opacity: 1; 188 | } 189 | .rig--relax .armature-eyes-happy-closed.armature-display, 190 | .rig--relax .armature-small-smile.armature-display, 191 | .rig--relax .rig-mug .armature-display{ 192 | opacity: 1; 193 | } 194 | .rig--create .armature-eyes-open.armature-display, 195 | .rig--create .armature-small-smile.armature-display, 196 | .rig--create .rig-yarn .armature-display{ 197 | opacity: 1; 198 | } 199 | .rig--ecstatic .armature-hearts.armature-display{ 200 | animation-name: fadeIn; 201 | animation-duration: 3s; 202 | animation-timing-function: ease-in-out; 203 | animation-delay: 0s; 204 | animation-iteration-count: infinite; 205 | animation-direction: normal; 206 | animation-fill-mode: none; 207 | animation-play-state: running; 208 | } 209 | .rig--ecstatic .armature-hearts .armature-translate{ 210 | animation-name: slideUp; 211 | animation-duration: 3s; 212 | animation-timing-function: ease-in; 213 | animation-delay: 0s; 214 | animation-iteration-count: infinite; 215 | animation-direction: normal; 216 | animation-fill-mode: none; 217 | animation-play-state: running; 218 | } 219 | .rig--bathroom .armature-clouds.armature-display{ 220 | animation-name: fadeIn; 221 | animation-duration: 3s; 222 | animation-timing-function: ease-in-out; 223 | animation-delay: 0s; 224 | animation-iteration-count: infinite; 225 | animation-direction: normal; 226 | animation-fill-mode: none; 227 | animation-play-state: running; 228 | } 229 | .rig--bathroom .armature-clouds .armature-translate{ 230 | animation-name: slideUp; 231 | animation-duration: 3s; 232 | animation-timing-function: ease-in; 233 | animation-delay: 0s; 234 | animation-iteration-count: infinite; 235 | animation-direction: normal; 236 | animation-fill-mode: none; 237 | animation-play-state: running; 238 | } 239 | .rig--sad .armature-left-tear.armature-display{ 240 | animation-name: fadeIn; 241 | animation-duration: 3s; 242 | animation-timing-function: ease-out; 243 | animation-delay: 0s; 244 | animation-iteration-count: infinite; 245 | animation-direction: normal; 246 | animation-fill-mode: none; 247 | animation-play-state: running; 248 | } 249 | .rig--sad .armature-left-tear.armature-translate{ 250 | animation-name: slideDown; 251 | animation-duration: 3s; 252 | animation-timing-function: ease-in; 253 | animation-delay: 0s; 254 | animation-iteration-count: infinite; 255 | animation-direction: normal; 256 | animation-fill-mode: none; 257 | animation-play-state: running; 258 | } 259 | .rig--sad .armature-left-tear.armature-scale{ 260 | animation-name: scaleUp; 261 | animation-duration: 3s; 262 | animation-timing-function: ease-out; 263 | animation-delay: 0s; 264 | animation-iteration-count: infinite; 265 | animation-direction: normal; 266 | animation-fill-mode: none; 267 | animation-play-state: running; 268 | } 269 | .rig--restless .armature-body.armature-rotate, 270 | .rig--restless .armature-hair-body.armature-rotate, 271 | .rig--restless .armature-hair-face.armature-rotate, 272 | .rig--restless .armature-ears-small.armature-rotate, 273 | .rig--restless .armature-ears-medium.armature-rotate, 274 | .rig--restless .armature-ears-big.armature-rotate, 275 | .rig--restless .armature-hands.armature-rotate{ 276 | animation-name: tilt; 277 | animation-duration: 5s; 278 | animation-timing-function: ease-out; 279 | animation-delay: 0s; 280 | animation-iteration-count: infinite; 281 | animation-direction: normal; 282 | animation-fill-mode: none; 283 | animation-play-state: running; 284 | } 285 | .rig--restless .armature-left-foot.armature-translate{ 286 | animation-name: shiftLeft; 287 | animation-duration: 5s; 288 | animation-timing-function: ease-in-out; 289 | animation-delay: 0s; 290 | animation-iteration-count: infinite; 291 | animation-direction: normal; 292 | animation-fill-mode: none; 293 | animation-play-state: running; 294 | } 295 | .rig--restless .armature-right-foot.armature-translate{ 296 | animation-name: shiftRight; 297 | animation-duration: 5s; 298 | animation-timing-function: ease-in-out; 299 | animation-delay: 0s; 300 | animation-iteration-count: infinite; 301 | animation-direction: normal; 302 | animation-fill-mode: none; 303 | animation-play-state: running; 304 | } 305 | .rig--restless .armature-left-foot.armature-rotate{ 306 | animation-name: rotateLeft; 307 | animation-duration: 5s; 308 | animation-timing-function: ease-in-out; 309 | animation-delay: 0s; 310 | animation-iteration-count: infinite; 311 | animation-direction: normal; 312 | animation-fill-mode: none; 313 | animation-play-state: running; 314 | } 315 | .rig--restless .armature-right-foot.armature-rotate{ 316 | animation-name: rotateRight; 317 | animation-duration: 5s; 318 | animation-timing-function: ease-in-out; 319 | animation-delay: 0s; 320 | animation-iteration-count: infinite; 321 | animation-direction: normal; 322 | animation-fill-mode: none; 323 | animation-play-state: running; 324 | } 325 | .armature-eyebrows-big-up.armature-translate{ 326 | transform-origin: center; 327 | animation-name: wince; 328 | animation-duration: 7s; 329 | animation-timing-function: ease-out; 330 | animation-delay: 0s; 331 | animation-iteration-count: infinite; 332 | animation-direction: normal; 333 | animation-fill-mode: none; 334 | animation-play-state: running; 335 | } 336 | .rig--idle .rig--happy .armature-eyes-open.armature-display{ 337 | animation-name: blinkOut; 338 | animation-duration: 6s; 339 | animation-timing-function: linear; 340 | animation-delay: 0s; 341 | animation-iteration-count: infinite; 342 | animation-direction: normal; 343 | animation-fill-mode: none; 344 | animation-play-state: running; 345 | } 346 | .rig--idle .rig--happy .armature-eyes-closed.armature-display{ 347 | animation-name: blinkIn; 348 | animation-duration: 6s; 349 | animation-timing-function: linear; 350 | animation-delay: 0s; 351 | animation-iteration-count: infinite; 352 | animation-direction: normal; 353 | animation-fill-mode: none; 354 | animation-play-state: running; 355 | } 356 | .rig--idle .armature-body.armature-translate{ 357 | transform-origin: center; 358 | animation-name: float; 359 | animation-duration: 3s; 360 | animation-timing-function: ease-in-out; 361 | animation-delay: 0s; 362 | animation-iteration-count: infinite; 363 | animation-direction: normal; 364 | animation-fill-mode: none; 365 | animation-play-state: running; 366 | } 367 | .rig--idle .armature-shadow.armature-scale{ 368 | animation-name: floatShadow; 369 | animation-duration: 3s; 370 | animation-timing-function: ease-in-out; 371 | animation-delay: 0s; 372 | animation-iteration-count: infinite; 373 | animation-direction: normal; 374 | animation-fill-mode: none; 375 | animation-play-state: running; 376 | } 377 | .rig--idle .armature-hair-face.armature-translate{ 378 | transform-origin: center; 379 | animation-name: float; 380 | animation-duration: 3s; 381 | animation-timing-function: ease-in-out; 382 | animation-delay: 0.1s; 383 | animation-iteration-count: infinite; 384 | animation-direction: normal; 385 | animation-fill-mode: none; 386 | animation-play-state: running; 387 | } 388 | .rig--idle .armature-hair-body.armature-translate, 389 | .rig--idle .armature-ears-small.armature-translate, 390 | .rig--idle .armature-ears-big.armature-translate, 391 | .rig--idle .armature-ears-medium.armature-translate{ 392 | transform-origin: center; 393 | animation-name: float; 394 | animation-duration: 3s; 395 | animation-timing-function: ease-in-out; 396 | animation-delay: 0.2s; 397 | animation-iteration-count: infinite; 398 | animation-direction: normal; 399 | animation-fill-mode: none; 400 | animation-play-state: running; 401 | } 402 | .rig--idle .armature-left-hand.armature-translate, 403 | .rig--idle .armature-right-hand.armature-translate{ 404 | transform-origin: center; 405 | animation-name: float; 406 | animation-duration: 3s; 407 | animation-timing-function: ease-in-out; 408 | animation-delay: 0.3s; 409 | animation-iteration-count: infinite; 410 | animation-direction: normal; 411 | animation-fill-mode: none; 412 | animation-play-state: running; 413 | } 414 | .rig--idle .armature-eyes-open.armature-translate{ 415 | animation-name: leftRight; 416 | animation-duration: 5s; 417 | animation-timing-function: ease-in-out; 418 | animation-delay: .3s; 419 | animation-iteration-count: infinite; 420 | animation-direction: normal; 421 | animation-fill-mode: none; 422 | animation-play-state: running; 423 | } 424 | .rig--shake .armature-face.armature-translate{ 425 | animation-name: shake; 426 | animation-duration: 1s; 427 | animation-timing-function: ease-out; 428 | animation-delay: 0s; 429 | animation-iteration-count: 1; 430 | animation-direction: normal; 431 | animation-fill-mode: none; 432 | animation-play-state: running; 433 | } 434 | .rig--shake .armature-face.armature-scale{ 435 | animation-name: shakeSquish; 436 | animation-duration: 1s; 437 | animation-timing-function: ease-in-out; 438 | animation-delay: 0s; 439 | animation-iteration-count: 1; 440 | animation-direction: normal; 441 | animation-fill-mode: none; 442 | animation-play-state: running; 443 | } 444 | 445 | /* ------- Keyframes ------- */ 446 | @keyframes float { 447 | 0%{ 448 | transform: translateY(0); 449 | } 450 | 42%{ 451 | transform: translateY(.77rem); 452 | } 453 | 100%{ 454 | transform: translateY(0); 455 | } 456 | } 457 | @keyframes floatShadow { 458 | 0%{ 459 | transform: scale(1); 460 | } 461 | 42%{ 462 | transform: scale(0.9); 463 | } 464 | 100%{ 465 | transform: scale(1); 466 | } 467 | } 468 | @keyframes blinkIn { 469 | 12%{ 470 | opacity: 0; 471 | } 472 | 13%{ 473 | opacity: 1; 474 | } 475 | 14%{ 476 | opacity: 1; 477 | } 478 | 15%{ 479 | opacity: 0; 480 | } 481 | 40%{ 482 | opacity: 0; 483 | } 484 | 41%{ 485 | opacity: 1; 486 | } 487 | 42%{ 488 | opacity: 1; 489 | } 490 | 43%{ 491 | opacity: 0; 492 | } 493 | } 494 | @keyframes blinkOut { 495 | 12%{ 496 | opacity: 1; 497 | } 498 | 13%{ 499 | opacity: 0; 500 | } 501 | 14%{ 502 | opacity: 0; 503 | } 504 | 15%{ 505 | opacity: 1; 506 | } 507 | 40%{ 508 | opacity: 1; 509 | } 510 | 41%{ 511 | opacity: 0; 512 | } 513 | 42%{ 514 | opacity: 0; 515 | } 516 | 43%{ 517 | opacity: 1; 518 | } 519 | } 520 | @keyframes wince { 521 | 0%{ 522 | transform: translateY(0); 523 | } 524 | 42%{ 525 | transform: translateY(0); 526 | } 527 | 50%{ 528 | transform: translateY(.6rem); 529 | } 530 | 80%{ 531 | transform: translateY(.6rem); 532 | } 533 | 90%{ 534 | transform: translateY(0); 535 | } 536 | } 537 | @keyframes leftRight { 538 | 0%{ 539 | transform: translateX(0); 540 | } 541 | 29%{ 542 | transform: translateX(0); 543 | } 544 | 30%{ 545 | transform: translateY(-.2rem) translateX(.2rem); 546 | } 547 | 38%{ 548 | transform: translateY(-.2rem) translateX(.2rem); 549 | } 550 | 40%{ 551 | transform: translateY(-.2rem) translateX(-.2rem); 552 | } 553 | 50%{ 554 | transform: translateY(-.2rem) translateX(-.2rem); 555 | } 556 | 52%{ 557 | transform: translateY(0); 558 | } 559 | } 560 | @keyframes shake { 561 | 0%{ 562 | transform: translateX(0); 563 | } 564 | 25%{ 565 | transform: translateX(-.4rem) translateY(-.4rem); 566 | } 567 | 50%{ 568 | transform: translateX(.4rem) translateY(-.4rem); 569 | } 570 | 75%{ 571 | transform: translateX(-.4rem) translateY(-.4rem); 572 | } 573 | 100%{ 574 | transform: translateX(0) translateY(0); 575 | } 576 | } 577 | @keyframes shakeSquish { 578 | 0%{ 579 | transform: scaleY(1); 580 | } 581 | 15%{ 582 | transform: scaleY(0.96); 583 | } 584 | 65%{ 585 | transform: scaleY(0.96); 586 | } 587 | 100%{ 588 | transform: scaleY(1); 589 | } 590 | } 591 | @keyframes tilt { 592 | 0%{ 593 | transform: rotate(0); 594 | } 595 | 34%{ 596 | transform: rotate(0); 597 | } 598 | 42%{ 599 | transform: rotate(-3deg); 600 | } 601 | 62%{ 602 | transform: rotate(3deg); 603 | } 604 | 72%{ 605 | transform: rotate(0); 606 | } 607 | 100%{ 608 | transform: rotate(0); 609 | } 610 | } 611 | @keyframes shiftLeft { 612 | 0%{ 613 | transform: translateY(0); 614 | } 615 | 34%{ 616 | transform: translateY(0); 617 | } 618 | 42%{ 619 | transform: translateY(-.5rem); 620 | } 621 | 52%{ 622 | transform: translateY(0); 623 | } 624 | 100%{ 625 | transform: translateY(0); 626 | } 627 | } 628 | @keyframes shiftRight { 629 | 0%{ 630 | transform: translateY(0); 631 | } 632 | 50%{ 633 | transform: translateY(0); 634 | } 635 | 62%{ 636 | transform: translateY(-.5rem); 637 | } 638 | 72%{ 639 | transform: translateY(0); 640 | } 641 | 100%{ 642 | transform: translateY(0); 643 | } 644 | } 645 | @keyframes rotateLeft { 646 | 0%{ 647 | transform: rotate(0); 648 | } 649 | 30%{ 650 | transform: rotate(0); 651 | } 652 | 42%{ 653 | transform: rotate(-10deg); 654 | } 655 | 52%{ 656 | transform: rotate(0); 657 | } 658 | 100%{ 659 | transform: rotate(0); 660 | } 661 | } 662 | @keyframes rotateRight { 663 | 0%{ 664 | transform: rotate(0); 665 | } 666 | 50%{ 667 | transform: rotate(0); 668 | } 669 | 62%{ 670 | transform: rotate(10deg); 671 | } 672 | 72%{ 673 | transform: rotate(0); 674 | } 675 | 100%{ 676 | transform: rotate(0); 677 | } 678 | } 679 | @keyframes fadeIn { 680 | 0%{ 681 | opacity: 0; 682 | } 683 | 20%{ 684 | opacity: 1; 685 | } 686 | 96%{ 687 | opacity: 1; 688 | } 689 | 100%{ 690 | opacity: 0; 691 | } 692 | } 693 | @keyframes slideDown { 694 | 0%{ 695 | transform: translateY(-1rem); 696 | } 697 | 100%{ 698 | transform: translateY(1.5rem); 699 | } 700 | } 701 | @keyframes scaleUp { 702 | 0%{ 703 | transform: scale(0); 704 | } 705 | 40%{ 706 | transform: scale(1); 707 | } 708 | } 709 | @keyframes slideUp { 710 | 0%{ 711 | transform: translateY(2rem); 712 | } 713 | 100%{ 714 | transform: translateY(-2rem); 715 | } 716 | } 717 | -------------------------------------------------------------------------------- /docs/script.js: -------------------------------------------------------------------------------- 1 | const STATE = {}; 2 | 3 | // -------- Values -------- 4 | class Resource{ 5 | constructor({ 6 | min = 0, 7 | max = 1, 8 | count = 0 9 | }={}){ 10 | this.min = min; 11 | this.max = max; 12 | this.count = count; 13 | } 14 | } 15 | 16 | class Experience extends Resource{ 17 | constructor(){ 18 | super({ 19 | max: -1, 20 | count: 0 21 | }) 22 | } 23 | } 24 | 25 | class Concentration extends Resource{ 26 | constructor(){ 27 | super({ 28 | max: 10, 29 | count: 10 30 | }) 31 | } 32 | } 33 | 34 | class Energy extends Resource{ 35 | constructor(){ 36 | super({ 37 | max: 10, 38 | count: 10 39 | }) 40 | } 41 | } 42 | 43 | class Ideas extends Resource{ 44 | constructor(){ 45 | super({ 46 | max: 10, 47 | count: 5 48 | }) 49 | } 50 | } 51 | 52 | class Waste extends Resource{ 53 | constructor(){ 54 | super({ 55 | max: 10, 56 | count: 0 57 | }) 58 | } 59 | } 60 | 61 | class Fatigue extends Resource{ 62 | constructor(){ 63 | super({ 64 | max: 10, 65 | count: 0 66 | }) 67 | } 68 | } 69 | 70 | class Mood { 71 | constructor({ 72 | ranges = {}, 73 | emoji 74 | }={}){ 75 | this.ranges = ranges; 76 | this.emoji = emoji; 77 | } 78 | } 79 | 80 | class Pain extends Mood { 81 | constructor(){ 82 | super({ 83 | emoji: '😖', 84 | ranges: { 85 | waste: [8, 10], 86 | energy: [0, 2] 87 | } 88 | }); 89 | } 90 | } 91 | 92 | class Frustration extends Mood { 93 | constructor(){ 94 | super({ 95 | emoji: '😩', 96 | ranges: { 97 | energy: [2, 5], 98 | fatigue: [9, 10] 99 | } 100 | }); 101 | } 102 | } 103 | 104 | class Restless extends Mood { 105 | constructor(){ 106 | super({ 107 | emoji: '😠', 108 | ranges: { 109 | waste: [6, 8], 110 | concentration: [0, 2] 111 | } 112 | }); 113 | } 114 | } 115 | 116 | class Tired extends Mood { 117 | constructor(){ 118 | super({ 119 | emoji: '😔', 120 | ranges: { 121 | fatigue: [6, 9] 122 | } 123 | }); 124 | } 125 | } 126 | 127 | class Sad extends Mood { 128 | constructor(){ 129 | super({ 130 | emoji: '😪', 131 | ranges: { 132 | ideas: [0, 2] 133 | } 134 | }); 135 | } 136 | } 137 | 138 | class Ecstatic extends Mood { 139 | constructor(){ 140 | super({ 141 | emoji: '🥰', 142 | ranges: { 143 | waste: [0, 1], 144 | fatigue: [0, 1], 145 | ideas: [9, 10], 146 | energy: [9, 10] 147 | } 148 | }); 149 | } 150 | } 151 | 152 | class Happy extends Mood { 153 | constructor(){ 154 | super({ 155 | emoji: '😊', 156 | ranges: { 157 | concentration: [5, 10], 158 | energy: [5, 10], 159 | fatigue: [0, 5], 160 | ideas: [5, 10], 161 | waste: [0, 5] 162 | } 163 | }); 164 | } 165 | } 166 | 167 | const TIME_FACTOR = 5; // 1 second * X 168 | // cost and gain are calculated per second 169 | class Action{ 170 | constructor({ 171 | type, 172 | duration = 1000, 173 | cost = {}, 174 | gain = {}, 175 | start 176 | }={}){ 177 | this.type = type; 178 | this.duration = duration; 179 | this.cost = cost; 180 | this.gain = gain; 181 | this.start = start ? new Date(start) : new Date(); 182 | } 183 | } 184 | 185 | class Awake extends Action{ 186 | constructor({start}={}){ 187 | super({ 188 | start, 189 | type: 'awake', 190 | duration: -1, 191 | cost: { 192 | energy: 1 / TIME_FACTOR 193 | }, 194 | gain: { 195 | fatigue: 0.5 / TIME_FACTOR 196 | } 197 | }); 198 | } 199 | } 200 | const SLEEP_DURATION = scaleTime(9, TIME_FACTOR); // seconds 201 | class Sleep extends Action{ 202 | constructor({start}={}){ 203 | super({ 204 | start, 205 | type: 'sleep', 206 | duration: SLEEP_DURATION * 1000, 207 | cost: { 208 | fatigue: 10 / SLEEP_DURATION, 209 | energy: 2 / SLEEP_DURATION 210 | }, 211 | gain: { 212 | experience: 1000 / SLEEP_DURATION, 213 | concentration: 10 / SLEEP_DURATION 214 | } 215 | }); 216 | } 217 | } 218 | const EAT_DURATION = scaleTime(2, TIME_FACTOR); // seconds 219 | class Eat extends Action{ 220 | constructor({start}={}){ 221 | super({ 222 | start, 223 | type: 'eat', 224 | duration: EAT_DURATION * 1000, 225 | cost: { 226 | concentration: 1 / EAT_DURATION 227 | }, 228 | gain: { 229 | experience: 100 / EAT_DURATION, 230 | energy: 10 / EAT_DURATION, 231 | waste: 4.5 / EAT_DURATION 232 | } 233 | }) 234 | } 235 | } 236 | const BATHROOM_DURATION = scaleTime(1.5, TIME_FACTOR); // seconds 237 | class Bathroom extends Action{ 238 | constructor({start}={}){ 239 | super({ 240 | start, 241 | type: 'bathroom', 242 | duration: BATHROOM_DURATION * 1000, 243 | cost: { 244 | concentration: 5 / BATHROOM_DURATION, 245 | waste: 10 / BATHROOM_DURATION 246 | }, 247 | gain: { 248 | experience: 100 / BATHROOM_DURATION, 249 | ideas: 1 / BATHROOM_DURATION, 250 | fatigue: 1 / BATHROOM_DURATION, 251 | } 252 | }); 253 | } 254 | } 255 | class Play extends Action{ 256 | constructor({start}={}){ 257 | super({ 258 | start, 259 | type: 'play', 260 | duration: -1, 261 | cost: { 262 | energy: 1 / TIME_FACTOR, 263 | concentration: 1 / TIME_FACTOR 264 | }, 265 | gain: { 266 | experience: 10 / TIME_FACTOR, 267 | experience: 5 / TIME_FACTOR, 268 | ideas: 1 / TIME_FACTOR 269 | } 270 | }); 271 | } 272 | } 273 | class Create extends Action{ 274 | constructor({start}={}){ 275 | super({ 276 | start, 277 | type: 'create', 278 | duration: -1, 279 | cost: { 280 | ideas: 1 / TIME_FACTOR, 281 | concentration: 1 / TIME_FACTOR 282 | }, 283 | gain: { 284 | experience: 500 / TIME_FACTOR 285 | } 286 | }); 287 | } 288 | } 289 | class Observe extends Action{ 290 | constructor({start}={}){ 291 | super({ 292 | start, 293 | type: 'observe', 294 | duration: -1, 295 | cost: { 296 | ideas: 0.5 / TIME_FACTOR, 297 | fatigue: 0.5 / TIME_FACTOR 298 | }, 299 | gain: { 300 | concentration: 1 / TIME_FACTOR, 301 | energy: 1.5 / TIME_FACTOR, 302 | experience: 250 / TIME_FACTOR 303 | } 304 | }); 305 | } 306 | } 307 | class Creature{ 308 | constructor(creature = {}){ 309 | this.actions = creature.actions ? creature.actions.map(a => new actions[a.type](a)) : []; 310 | this.moods = creature.moods || {}; 311 | this.mood = creature.mood || 'happy'; 312 | resourceKeys.forEach(k => { 313 | this[k] = creature[k] && Object.assign({}, creature[k]) || new resources[k]() 314 | }); 315 | moodKeys.forEach(k => this.moods[k] = creature.moods && creature.moods[k] || new moods[k]()); 316 | } 317 | } 318 | 319 | class UI{ 320 | constructor(ui = {}){ 321 | this.stage = document.querySelector('.stage'); 322 | this.editing = false; 323 | if(ui.editorOptionsSelectedIndex){ 324 | this.editorOptionsSelectedIndex = ui.editorOptionsSelectedIndex; 325 | }else{ 326 | this.editorOptionsSelectedIndex = {}; 327 | editableOptionKeys.forEach(k => this.editorOptionsSelectedIndex[k] = -1); 328 | } 329 | 330 | this.editable = editableOptionKeys.map(k => { 331 | const btnAnimate = document.createElement('div'); 332 | btnAnimate.classList.add('button-animate'); 333 | const btn = document.createElement('div'); 334 | btn.classList.add('button'); 335 | if(editableOptions[k].icon){ 336 | btn.appendChild(createSvgIcon(editableOptions[k].icon)); 337 | }else{ 338 | const circle = document.createElement('div'); 339 | circle.classList.add('color-circle'); 340 | btn.appendChild(circle); 341 | } 342 | btn.setAttribute('data-edit', k); 343 | btnAnimate.appendChild(btn); 344 | return btnAnimate; 345 | }); 346 | this.actions = actionKeys.map(k => { 347 | const btnAnimate = document.createElement('div'); 348 | btnAnimate.classList.add('button-animate'); 349 | const btn = document.createElement('div'); 350 | btn.classList.add('button'); 351 | btnAnimate.classList.add('button-action--' + k); 352 | btn.appendChild(createSvgIcon(actionIconMap[k])); 353 | btnAnimate.setAttribute('data-action', k); 354 | btnAnimate.appendChild(btn); 355 | return btnAnimate; 356 | }); 357 | this.resources = {}; 358 | resourceKeys.forEach(k => { 359 | const p = document.createElement('progress'); 360 | const d = document.createElement('div'); 361 | const l = document.createElement('label'); 362 | p.setAttribute('data-resource', k); 363 | p.setAttribute('id', k + 'Progress'); 364 | l.setAttribute('for', k + 'Progress'); 365 | l.textContent = k; 366 | d.appendChild(l); 367 | d.appendChild(p); 368 | d.classList.add('debug-stat'); 369 | this.resources[k] = d; 370 | }); 371 | this.debug = document.querySelector('.debug'); 372 | } 373 | toJSON(){ 374 | return { 375 | editorOptionsSelectedIndex: this.editorOptionsSelectedIndex 376 | } 377 | } 378 | } 379 | 380 | class Canvas{ 381 | constructor(svgData){ 382 | this.svgData = svgData; 383 | } 384 | toJSON(){ 385 | return {}; 386 | } 387 | } 388 | 389 | class Game{ 390 | constructor({ 391 | playing = true 392 | } = {}){ 393 | this.playing = playing; 394 | } 395 | } 396 | 397 | const resources = { 398 | 'concentration': Concentration, 399 | 'energy': Energy, 400 | 'experience': Experience, 401 | 'fatigue': Fatigue, 402 | 'ideas': Ideas, 403 | 'waste': Waste 404 | }; 405 | 406 | const resourceKeys = Object.keys(resources); 407 | 408 | const actions = { 409 | 'awake': Awake, 410 | 'sleep': Sleep, 411 | 'bathroom': Bathroom, 412 | 'create': Create, 413 | 'eat': Eat, 414 | 'observe': Observe, 415 | 'play': Play 416 | }; 417 | 418 | const emojiAction = { 419 | 'awake': '', 420 | 'bathroom': '🚽', 421 | 'create': '🖍️', 422 | 'eat': '🍌', 423 | 'play': '🧸', 424 | 'sleep': '💤', 425 | 'observe': '🔭' 426 | }; 427 | 428 | const actionKeys = [ 429 | 'awake', 430 | 'sleep', 431 | 'bathroom', 432 | 'eat', 433 | 'play', 434 | 'create', 435 | 'observe' 436 | ]; 437 | 438 | const actionIconMap = { 439 | 'awake': 'symbol-sun-icon', 440 | 'bathroom': 'symbol-fart-cloud-icon', 441 | 'create': 'symbol-yarn-ball-icon', 442 | 'eat': 'symbol-cookie-icon', 443 | 'observe': 'symbol-mug-icon', 444 | 'play': 'symbol-ping-pong-icon', 445 | 'sleep': 'symbol-moon-icon' 446 | }; 447 | 448 | const wakingActions = [ 449 | Eat, 450 | Bathroom, 451 | Create, 452 | Play, 453 | Observe 454 | ]; 455 | 456 | const moods = { 457 | 'pain': Pain, 458 | 'frustration': Frustration, 459 | 'restless': Restless, 460 | 'tired': Tired, 461 | 'sad': Sad, 462 | 'happy': Happy, 463 | 'ecstatic': Ecstatic 464 | }; 465 | 466 | const moodKeys = [ 467 | 'pain', 468 | 'frustration', 469 | 'restless', 470 | 'tired', 471 | 'sad', 472 | 'ecstatic', 473 | 'happy' 474 | ]; 475 | 476 | const editableOptions = { 477 | earType: { 478 | icon: 'symbol-ears-icon', 479 | options: [ 480 | 'ears--small', 481 | 'ears--medium', 482 | 'ears--big' 483 | ] 484 | }, 485 | furType: { 486 | icon: 'symbol-fur-icon', 487 | options: [ 488 | 'body--fur', 489 | 'face--fur', 490 | ['body--fur', 'face--fur'] 491 | ] 492 | }, 493 | noseType: { 494 | icon: 'symbol-nose-icon', 495 | options: [ 496 | 'nose--triangle', 497 | 'nose--round' 498 | ] 499 | }, 500 | furColor: { 501 | options: [ 502 | 'fur--white', 503 | 'fur--green' 504 | ] 505 | }, 506 | faceColor: { 507 | options: [ 508 | 'face--white', 509 | 'face--green' 510 | ] 511 | } 512 | }; 513 | 514 | const rigModifiers = { 515 | happy: { 516 | full: ['idle'], 517 | face: ['happy'] 518 | }, 519 | pain: { 520 | full: ['idle'], 521 | face: ['pain'] 522 | }, 523 | frustration: { 524 | full: ['idle'], 525 | face: ['frustration'] 526 | }, 527 | tired: { 528 | full: ['idle'], 529 | face: ['tired'] 530 | }, 531 | restless: { 532 | full: ['restless', 'idle'], 533 | face: ['restless'] 534 | }, 535 | sad: { 536 | full: ['idle'], 537 | face: ['sad'] 538 | }, 539 | ecstatic: { 540 | full: ['ecstatic', 'idle'], 541 | face: ['ecstatic'] 542 | }, 543 | sleep: { 544 | full: ['sleep', 'idle'], 545 | face: ['sleep'] 546 | }, 547 | bathroom: { 548 | full: ['bathroom'], 549 | face: ['bathroom'] 550 | }, 551 | eat: { 552 | full: ['eat'], 553 | face: ['eat'] 554 | }, 555 | play: { 556 | full: ['play', 'idle'], 557 | face: ['play'] 558 | }, 559 | create: { 560 | full: ['create'], 561 | face: ['create'] 562 | }, 563 | observe: { 564 | full: ['relax', 'idle'], 565 | face: ['relax'] 566 | } 567 | }; 568 | 569 | const editableOptionKeys = Object.keys(editableOptions); 570 | 571 | const soundElement = document.getElementById('buttonSound'); 572 | let stopTime = 1; 573 | 574 | soundElement.addEventListener('timeupdate', ()=>{ 575 | if(soundElement.currentTime >= stopTime){ 576 | soundElement.pause(); 577 | } 578 | }, false); 579 | 580 | const sounds = { 581 | button: [0.9, 2] 582 | }; 583 | 584 | // -------- Policies -------- 585 | function isSleeping(state){ 586 | return state.creature.actions.some(a => a instanceof Sleep); 587 | } 588 | 589 | function isAwake(state){ 590 | return state.creature.actions.some(a => a instanceof Awake); 591 | } 592 | 593 | function canConcentrate(state, diff){ 594 | diff = diff || calculateResources(state).diff; 595 | return diff.concentration.count > diff.concentration.min; 596 | } 597 | 598 | function isFatigued(state, diff){ 599 | diff = diff || calculateResources(state).diff; 600 | return diff.fatigue.count >= diff.fatigue.max; 601 | } 602 | 603 | function hasFatigue(state, diff){ 604 | diff = diff || calculateResources(state).diff; 605 | return diff.fatigue.count >= diff.fatigue.min; 606 | } 607 | 608 | function hasEnergy(state, diff){ 609 | diff = diff || calculateResources(state).diff; 610 | return diff.energy.count > diff.energy.min; 611 | } 612 | 613 | function hasIdeas(state, diff){ 614 | diff = diff || calculateResources(state).diff; 615 | return diff.ideas.count > diff.ideas.min; 616 | } 617 | 618 | function canSleep(state){ 619 | const diff = calculateResources(state).diff; 620 | return isAwake(state) && (hasFatigue(state, diff) || needsConcentration(state, diff)); 621 | } 622 | 623 | function canEat(state){ 624 | const diff = calculateResources(state).diff; 625 | return isAwake(state) && !isFatigued(state, diff) && canConcentrate(state, diff) && diff.energy.count < diff.energy.max; 626 | } 627 | 628 | function canBathroom(state){ 629 | const diff = calculateResources(state).diff; 630 | return isAwake(state) && !isFatigued(state, diff) && canConcentrate(state, diff) && diff.waste.count > diff.waste.min; 631 | } 632 | 633 | function canPlay(state){ 634 | const diff = calculateResources(state).diff; 635 | return isAwake(state) && canConcentrate(state, diff) && hasEnergy(state, diff) && diff.ideas.count < diff.ideas.max; 636 | } 637 | 638 | function canCreate(state){ 639 | const diff = calculateResources(state).diff; 640 | return isAwake(state) && canConcentrate(state, diff) && hasIdeas(state, diff); 641 | } 642 | 643 | function needsConcentration(state, diff){ 644 | diff = diff || calculateResources(state).diff; 645 | return diff.concentration.count < diff.concentration.max; 646 | } 647 | 648 | function canObserve(state){ 649 | const diff = calculateResources(state).diff; 650 | return isAwake(state) && needsConcentration(state, diff) && hasIdeas(state, diff); 651 | } 652 | 653 | function isPerformingWaking(state){ 654 | return state.creature.actions.some(a => wakingActions.some(p => a instanceof p)); 655 | } 656 | 657 | function isPerforming(state, action){ 658 | return state.creature.actions.some(a => a instanceof actions[action]); 659 | } 660 | 661 | function isPerformingAction(state){ 662 | return !!state.creature.actions.length; 663 | } 664 | 665 | function canPerformAction(state, action){ 666 | if(action === 'awake'){ 667 | return isSleeping(state) || !state.creature.actions.length; 668 | } 669 | if(action === 'sleep'){ 670 | return canSleep(state); 671 | } 672 | if(action === 'eat'){ 673 | return canEat(state); 674 | } 675 | if(action === 'bathroom'){ 676 | return canBathroom(state); 677 | } 678 | if(action === 'play'){ 679 | return canPlay(state); 680 | } 681 | if(action === 'create'){ 682 | return canCreate(state); 683 | } 684 | if(action === 'observe'){ 685 | return canObserve(state); 686 | } 687 | return true; 688 | } 689 | 690 | function canCompleteAction(state, action){ 691 | const checkDate = new Date(); 692 | if(action.duration !== -1){ 693 | if(checkDate - action.start >= action.duration){ 694 | return true; 695 | } 696 | } 697 | if(action instanceof Eat){ 698 | return !canEat(state); 699 | } 700 | if(action instanceof Bathroom){ 701 | return !canBathroom(state); 702 | } 703 | if(action instanceof Play){ 704 | return !canPlay(state); 705 | } 706 | if(action instanceof Create){ 707 | return !canCreate(state); 708 | } 709 | if(action instanceof Observe){ 710 | return !canObserve(state); 711 | } 712 | return false; 713 | } 714 | 715 | function isDebug(w){ 716 | return w.location.search.indexOf('debug') !== -1 717 | } 718 | 719 | // -------- Services --------- 720 | function init(state){ 721 | state.creature = new Creature(state.creature); 722 | state.game = new Game(state.game); 723 | state.ui = new UI(state.ui); 724 | state.canvas = new Canvas(); 725 | state.sound = true; 726 | initCanvas(state); 727 | initUI(state); 728 | initGame(state); 729 | initCreature(state); 730 | } 731 | 732 | function initCreature(state){ 733 | checkRigModifiers(state); 734 | } 735 | 736 | function initCanvas(state){ 737 | unpack(state); 738 | } 739 | 740 | function initUI(state){ 741 | if(isDebug(window)){ 742 | resourceKeys.forEach(k => { 743 | const p = state.ui.resources[k].querySelector('progress'); 744 | p.value = state.creature[k].count; 745 | p.setAttribute('max', state.creature[k].max === -1 ? 100000 : state.creature[k].max); 746 | state.ui.debug.appendChild(state.ui.resources[k]) 747 | }); 748 | state.ui.debug.classList.remove('hide'); 749 | } 750 | state.ui.gameActions = document.querySelector('.game'); 751 | state.ui.editorActions = document.querySelector('.editor'); 752 | state.ui.actionsContainer = document.querySelector('.actions'); 753 | state.ui.actions.forEach(b => { 754 | buttonClick(state, b, n => actionOnClick(state, n)); 755 | state.ui.gameActions.appendChild(b); 756 | }); 757 | state.ui.editable.forEach(b => { 758 | buttonClick(state, b, n => changeSelectedEditorOption(state, n.querySelector('.button').getAttribute('data-edit'))); 759 | state.ui.editorActions.appendChild(b); 760 | }); 761 | touchClick(state.canvas.target); 762 | state.canvas.target.addEventListener('click', evt => { 763 | state.ui.editing = !state.ui.editing; 764 | updateUI(state); 765 | }); 766 | editableOptionKeys.forEach(k => toggleEditableClasses(state, k, -1)); 767 | state.score = document.querySelector('.score'); 768 | const span = document.createElement('span'); 769 | state.scoreValue = span; 770 | state.score.appendChild(span); 771 | state.score.appendChild(createSvgIcon('symbol-star-icon')); 772 | } 773 | 774 | function changeSelectedEditorOption(state, editableOption){ 775 | const previousIndex = state.ui.editorOptionsSelectedIndex[editableOption]; 776 | if(previousIndex === -1){ 777 | state.ui.editorOptionsSelectedIndex[editableOption] = 0; 778 | }else{ 779 | if(previousIndex === editableOptions[editableOption].options.length - 1){ 780 | state.ui.editorOptionsSelectedIndex[editableOption] = -1; 781 | }else{ 782 | state.ui.editorOptionsSelectedIndex[editableOption] = state.ui.editorOptionsSelectedIndex[editableOption] + 1; 783 | } 784 | } 785 | toggleEditableClasses(state, editableOption, previousIndex); 786 | saveState(state); 787 | } 788 | 789 | function toggleEditableClasses(state, editableOption, prev){ 790 | requestAnimationFrame(()=>{ 791 | let i = state.ui.editorOptionsSelectedIndex[editableOption]; 792 | if(prev !== -1){ 793 | if(Array.isArray(editableOptions[editableOption].options[prev])){ 794 | editableOptions[editableOption].options[prev].forEach(c => state.ui.stage.classList.remove(c)); 795 | }else{ 796 | state.ui.stage.classList.remove(editableOptions[editableOption].options[prev]); 797 | } 798 | } 799 | if(i !== -1){ 800 | if(Array.isArray(editableOptions[editableOption].options[i])){ 801 | editableOptions[editableOption].options[i].forEach(c => state.ui.stage.classList.add(c)); 802 | }else{ 803 | state.ui.stage.classList.add(editableOptions[editableOption].options[i]); 804 | } 805 | } 806 | }); 807 | } 808 | 809 | function updateUI(state){ 810 | const diff = calculateResources(state).diff; 811 | resourceKeys.forEach(k => { 812 | state.ui.resources[k].querySelector('progress').value = diff[k].count; 813 | }); 814 | const mood = getMood(state, diff); 815 | if(!state.mood || state.mood.name !== mood.name){ 816 | state.mood = mood; 817 | } 818 | state.ui.actions.forEach(a => { 819 | const action = a.getAttribute('data-action'); 820 | a.classList.toggle('performing', isPerforming(state, action)); 821 | }); 822 | if(state.ui.editing && !state.ui.actionsContainer.classList.contains('actions--editing')){ 823 | state.ui.actionsContainer.classList.add('actions--editing'); 824 | } 825 | if(!state.ui.editing && state.ui.actionsContainer.classList.contains('actions--editing')){ 826 | state.ui.actionsContainer.classList.remove('actions--editing'); 827 | } 828 | if(isAwake(state)){ 829 | if(!state.ui.actions[0].classList.contains('hide')){ 830 | state.ui.actions[0].classList.add('hide'); 831 | } 832 | if(state.ui.actions[1].classList.contains('hide')){ 833 | state.ui.actions[1].classList.remove('hide'); 834 | } 835 | }else{ 836 | if(state.ui.actions[0].classList.contains('hide')){ 837 | state.ui.actions[0].classList.remove('hide'); 838 | } 839 | if(!state.ui.actions[1].classList.contains('hide')){ 840 | state.ui.actions[1].classList.add('hide'); 841 | } 842 | } 843 | let score = Math.ceil(diff.experience.count / 1000); 844 | if(score != state.scoreValue.textContent){ 845 | state.scoreValue.textContent = score; 846 | } 847 | if(isDebug(window)){ 848 | resourceKeys.forEach(k => { 849 | const p = state.ui.resources[k].querySelector('progress'); 850 | p.value = diff[k].count; 851 | }); 852 | } 853 | } 854 | 855 | function initGame(state){ 856 | if(!state.game.playing){ 857 | state.game.playing = true; 858 | } 859 | if(!state.creature.actions.length){ 860 | performAction(state, 'awake'); 861 | } 862 | gameTick(state); 863 | } 864 | 865 | function debounceTick(fn){ 866 | requestAnimationFrame(fn); 867 | } 868 | 869 | let saveTimeout; 870 | function debounceSave(state){ 871 | if(saveTimeout) return; 872 | saveTimeout = setTimeout(()=>{ 873 | saveState(state); 874 | saveTimeout = null; 875 | }, 5000); 876 | } 877 | 878 | function gameTick(state){ 879 | stateChange(state); 880 | if(state.game.playing){ 881 | debounceTick(() => gameTick(state)); 882 | } 883 | } 884 | 885 | function stateChange(state){ 886 | checkActions(state); 887 | checkMood(state); 888 | updateUI(state); 889 | debounceSave(state); 890 | } 891 | 892 | function checkMood(state){ 893 | const calc = calculateResources(state); 894 | const diff = calc.diff; 895 | const mood = getMood(state, diff); 896 | if(state.creature.mood === mood.name){ 897 | return; 898 | } 899 | moodChange(state, state.creature.mood, mood.name); 900 | } 901 | 902 | function moodChange(state, previousMood, mood){ 903 | state.creature.mood = mood; 904 | checkRigModifiers(state); 905 | } 906 | 907 | function getPerformingAction(state){ 908 | return state.creature.actions.reduce((prev, curr)=>{ 909 | if(wakingActions.some(p => curr instanceof p)){ 910 | return curr; 911 | } 912 | if(curr instanceof Sleep){ 913 | return curr; 914 | } 915 | return prev; 916 | }); 917 | } 918 | 919 | function checkRigModifiers(state){ 920 | const modifiers = []; 921 | const replace = {}; 922 | modifiers.push(rigModifiers[state.creature.mood]); 923 | if(isPerformingAction(state)){ 924 | modifiers.push(rigModifiers[getPerformingAction(state).type]); 925 | } 926 | modifiers.forEach(m => { 927 | Object.assign(replace, m); 928 | }); 929 | replaceRigModifiers(state, replace); 930 | } 931 | 932 | function getRigModifiers(element){ 933 | return Array.prototype.slice.call(element.classList) 934 | .filter(c => c.indexOf('rig--') === 0) 935 | .map(c => c.replace('rig--', '')); 936 | } 937 | 938 | function replaceRigModifiers(state, replace){ 939 | Object.keys(replace).forEach(k => { 940 | const element = state.canvas.target.querySelector(`.rig-${k}`); 941 | const currentRigModifiers = getRigModifiers(element); 942 | 943 | currentRigModifiers.forEach(c => { 944 | if(replace[k].indexOf(c) === -1){ 945 | element.classList.remove(`rig--${c}`); 946 | } 947 | }); 948 | replace[k].forEach(c => element.classList.add(`rig--${c}`)); 949 | }); 950 | } 951 | 952 | function checkActions(state){ 953 | state.creature.actions.slice().forEach(a => { 954 | if(canCompleteAction(state, a)){ 955 | if(a instanceof Sleep){ 956 | performAction(state, 'awake'); 957 | return; 958 | } 959 | if(a instanceof Awake){ 960 | performAction(state, 'sleep'); 961 | return; 962 | } 963 | completeAction(state, a); 964 | } 965 | }); 966 | } 967 | 968 | function performAction(state, action){ 969 | if(action === 'awake' && isPerformingAction(state)){ 970 | // completeSleepingActions(state) 971 | completeAction(state, state.creature.actions.find(a => a instanceof Sleep)); 972 | } 973 | if(action === 'sleep'){ 974 | completeAction(state, state.creature.actions.find(a => a instanceof Awake)); 975 | } 976 | completeWakingActions(state); 977 | state.creature.actions.push(new actions[action]()); 978 | checkRigModifiers(state); 979 | } 980 | 981 | function completeWakingActions(state){ 982 | state.creature.actions.forEach(a => { 983 | if(wakingActions.some(p => a instanceof p)){ 984 | completeAction(state, a); 985 | } 986 | }); 987 | } 988 | 989 | function completeAction(state, action){ 990 | const calc = calculateResources(state); 991 | const diff = calc.diff; 992 | const actionType = actionKeys.find(k => action instanceof actions[k]); 993 | updateResources(state, diff); 994 | state.creature.actions.splice(state.creature.actions.indexOf(action), 1); 995 | state.creature.actions.forEach(a => { 996 | const elapsedSeconds = calc.calcTime - a.start; 997 | a.start = new Date(); 998 | if(a.duration !== -1){ 999 | a.duration = a.duration - elapsedSeconds; 1000 | if(a.duration < 0){ 1001 | a.duration = 0; 1002 | } 1003 | } 1004 | }); 1005 | checkRigModifiers(state); 1006 | } 1007 | 1008 | function actionOnClick(state, node){ 1009 | const action = node.getAttribute('data-action'); 1010 | if(canPerformAction(state, action)){ 1011 | performAction(state, action); //performing action completes actions if necesary 1012 | }else{ 1013 | shakeHead(state); 1014 | } 1015 | } 1016 | 1017 | function shakeHead(state){ 1018 | const full = state.canvas.target.querySelector('.rig-full'); 1019 | full.addEventListener('animationend', ()=>{ 1020 | requestAnimationFrame(()=>{ 1021 | full.classList.remove('rig--shake'); 1022 | }); 1023 | }, {once: true}); 1024 | requestAnimationFrame(()=>{ 1025 | full.classList.add('rig--shake'); 1026 | // playSound(state, 'shake'); 1027 | }); 1028 | } 1029 | 1030 | function playSound(state, sound){ 1031 | if(state.sound){ 1032 | soundElement.currentTime = sounds[sound][0]; 1033 | stopTime = sounds[sound][1]; 1034 | soundElement.play(); 1035 | } 1036 | } 1037 | 1038 | function calculateResources(state){ 1039 | const diff = new Creature(state.creature); 1040 | const calcTime = new Date(); 1041 | state.creature.actions.forEach(a => { 1042 | const elapsedSeconds = Math.min((calcTime - a.start) / 1000); 1043 | Object.keys(a.cost).forEach( r => { 1044 | const val = diff[r].count - (a.cost[r] * elapsedSeconds); 1045 | diff[r].count = val < state.creature[r].min ? state.creature[r].min : val; 1046 | }); 1047 | Object.keys(a.gain).forEach( r => { 1048 | const val = diff[r].count + (a.gain[r] * elapsedSeconds); 1049 | diff[r].count = val > state.creature[r].max && state.creature[r].max !== -1 ? state.creature[r].max : val; 1050 | }); 1051 | }); 1052 | return { 1053 | diff, 1054 | calcTime 1055 | }; 1056 | } 1057 | 1058 | function updateResources(state, diff){ 1059 | resourceKeys.forEach(k => { 1060 | state.creature[k].count = diff[k].count; 1061 | }); 1062 | } 1063 | 1064 | function getMood(state, diff){ 1065 | const activeMoods = moodKeys 1066 | .map(k => { 1067 | let mood; 1068 | let reason; 1069 | Object.keys(state.creature.moods[k].ranges) 1070 | .some(r => { 1071 | if(rangeContains(state.creature.moods[k].ranges[r], diff[r].count)){ 1072 | mood = state.creature.moods[k]; 1073 | reason = r; 1074 | return true; 1075 | } 1076 | }); 1077 | if(mood){ 1078 | return { 1079 | mood, 1080 | reason, 1081 | name: k 1082 | } 1083 | } 1084 | return null; 1085 | }) 1086 | .filter(m => m !== null) 1087 | if(activeMoods.length){ 1088 | return activeMoods[0]; 1089 | } 1090 | return { 1091 | name: 'default', 1092 | mood: { 1093 | emoji: '🤪🤔😃' 1094 | }, 1095 | reason: 'no other match' 1096 | }; 1097 | } 1098 | 1099 | function rangeContains(r, n){ 1100 | return n >= r[0] && n <= r[1]; 1101 | } 1102 | 1103 | function scaleTime(seconds, factor){ 1104 | return seconds * factor; 1105 | } 1106 | 1107 | function unpack(state){ 1108 | const svg = document.querySelector('svg'); 1109 | const canvas = document.querySelector('.canvas'); 1110 | state.canvas.svg = svg; 1111 | state.canvas.target = canvas; 1112 | state.canvas.viewBox = window.SVG_DATA.viewBox; 1113 | window.SVG_DATA.data.forEach(d => unpackSVG(state, d)); 1114 | } 1115 | 1116 | function unpackSVG(state, data){ 1117 | let target = state.canvas.target; 1118 | if(data.type === 'rig'){ 1119 | const element = document.createElement('div'); 1120 | element.classList.add('rig'); 1121 | element.classList.add(`rig-${data.name}`); 1122 | if(data.name !== 'icons'){ 1123 | target.appendChild(element); 1124 | } 1125 | let t = element; 1126 | data.armatures.forEach(a => { 1127 | const div = document.createElement('div'); 1128 | div.classList.add('armature', `armature-${data.name}`, `armature-${a}`); 1129 | t.appendChild(div); 1130 | t = div; 1131 | }); 1132 | state.canvas.target = t; 1133 | data.children.forEach(child => unpackSVG(state, child)); 1134 | state.canvas.target = target; 1135 | }else{ 1136 | data.armatures.forEach(a => { 1137 | const div = document.createElement('div'); 1138 | div.classList.add('armature', `armature-${data.name}`, `armature-${a}`); 1139 | target.appendChild(div); 1140 | target = div; 1141 | }); 1142 | const symbol = document.createElementNS('http://www.w3.org/2000/svg', 'symbol'); 1143 | if(data.name.indexOf('icon') !== -1){ 1144 | symbol.setAttribute('id', `symbol-${data.name}`); 1145 | symbol.setAttribute('viewBox', '0 0 64 64'); 1146 | symbol.innerHTML = data.svg; 1147 | state.canvas.svg.appendChild(symbol); 1148 | return; 1149 | } 1150 | const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); 1151 | svg.setAttribute('id', `svg-${data.name}`); 1152 | svg.setAttribute('viewBox', state.canvas.viewBox); 1153 | svg.classList.add(`svg-${data.name}`); 1154 | // const use = document.createElementNS('http://www.w3.org/2000/svg', 'use'); 1155 | // svg.classList.add(`symbol-${data.name}`); //for fill color 1156 | // use.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', `#symbol-${data.name}`); 1157 | // svg.appendChild(use); 1158 | svg.innerHTML = data.svg; 1159 | 1160 | target.appendChild(svg); 1161 | } 1162 | } 1163 | 1164 | function buttonClick(state, node, handler){ 1165 | touchClick(node); 1166 | node.addEventListener('click', evt => { 1167 | playSound(state, 'button'); 1168 | handler(node); 1169 | requestAnimationFrame(()=>{ 1170 | node.classList.add('active'); 1171 | }); 1172 | node.addEventListener('animationiteration', ()=>{ 1173 | requestAnimationFrame(()=>{ 1174 | node.classList.remove('active'); 1175 | }); 1176 | }, {once: true}); 1177 | }); 1178 | } 1179 | 1180 | function touchClick(node){ 1181 | if(node.touchClick) return; 1182 | const data = { 1183 | startX: 0, 1184 | startY: 0, 1185 | moved: false 1186 | }; 1187 | node.addEventListener('touchstart', (evt)=>{ 1188 | if(evt.touches.length > 1) return; 1189 | data.startX = evt.touches[0].pageX; 1190 | data.startY = evt.touches[0].pageY; 1191 | }, {passive: true}); 1192 | node.addEventListener('touchmove', (evt)=>{ 1193 | if(data.moved || evt.touches.length > 1) return; 1194 | if(Math.abs(evt.touches[0].pageX - data.startX) > TOUCH_PADDING || Math.abs(evt.touches[0].pageY - data.startY) > TOUCH_PADDING){ 1195 | data.moved = true; 1196 | } 1197 | }, {passive: true}); 1198 | node.addEventListener('touchend', (evt)=>{ 1199 | evt.preventDefault(); 1200 | if(evt.touches.length > 1) return; 1201 | if(!data.moved && node.contains(evt.target)){ 1202 | node.focus(); // to hide keyboard? 1203 | dispatchClick(node, evt); 1204 | } 1205 | data.startX = 0; 1206 | data.startY = 0; 1207 | data.moved = false; 1208 | }); 1209 | } 1210 | 1211 | function dispatchClick(node, evt){ 1212 | const event = new MouseEvent('click', { 1213 | view: window, 1214 | bubbles: true, 1215 | cancelable: true 1216 | }); 1217 | event.origin = evt; 1218 | node.dispatchEvent(event); 1219 | } 1220 | 1221 | function createSvgIcon(name){ 1222 | const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); 1223 | svg.classList.add(`symbol-icon-${name}`); 1224 | const use = document.createElementNS('http://www.w3.org/2000/svg', 'use'); 1225 | use.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', `#${name}`); 1226 | svg.appendChild(use); 1227 | return svg; 1228 | } 1229 | 1230 | function getState(state){ 1231 | return Object.assign({}, state, JSON.parse(localStorage.getItem('state') || '{}')); 1232 | } 1233 | 1234 | function saveState(state){ 1235 | localStorage.setItem('state', JSON.stringify(state)); 1236 | } 1237 | 1238 | init(getState(STATE)); 1239 | -------------------------------------------------------------------------------- /docs/style.css: -------------------------------------------------------------------------------- 1 | /* latin */ 2 | @font-face { 3 | font-family: 'Baloo'; 4 | font-style: normal; 5 | font-weight: 400; 6 | font-display: swap; 7 | src: local('Baloo Regular'), local('Baloo-Regular'), url(baloo.woff2) format('woff2'); 8 | unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; 9 | } 10 | 11 | /* ------- Colors ------- */ 12 | :root{ 13 | --color: #FFF8DF; 14 | --background-color: #F2D7BF; 15 | --shadow: #DBBA92; 16 | --button-color: #DBBA92; 17 | --button-background-color: #FFF8DF; 18 | --button-shadow: #FCF0C4; 19 | --star: #FFED90; 20 | --moon-button-background-color: #231E5A; 21 | --moon-button-shadow: #030217; 22 | --sun-button-background-color: #ADD3FF; 23 | --sun-button-shadow: #569BEB; 24 | --fur: #E49B45; 25 | --fur-shadow: #C57415; 26 | --face-hair: #FFBB6A; 27 | --face: #FFD7A6; 28 | --eyes: #363636; 29 | --mouth: #363636; 30 | --tongue: #FFAFAF; 31 | --cheeks: #FFAFAF; 32 | --nose: #363636; 33 | --tear: #91a9ff; 34 | --moon-light: #EBF0E1; 35 | --moon-dark: #BCC1B2; 36 | --moon-crater-light: #DADFD0; 37 | --moon-crater-dark: #A4AB96; 38 | --sun-outside: #FFE9B1; 39 | --sun-middle: #FDF7E9; 40 | --sun-center: #FFFFFF; 41 | --fart-cloud: #A0C633; 42 | --hearts: #ff7373; 43 | --cookie-light: #EEA045; 44 | --cookie-dark: #E79536; 45 | --cookie-chip: #7D3535; 46 | --ping-pong-paddle-light: #CB2626; 47 | --ping-pong-paddle-dark: #9B1010; 48 | --ping-pong-paddle-handle: #BD7B55; 49 | --ping-pong-ball: #FFFFFF; 50 | --coffee-mug-light: #F26B41; 51 | --coffee-mug-dark: #E05428; 52 | --coffee-mug-steam: #FFFFFF; 53 | --yarn-light: #2C58F2; 54 | --yarn-medium: #1740CE; 55 | --yarn-dark: #0B2DA5; 56 | --yarn-needles: #7E8087; 57 | } 58 | 59 | .fur--white{ 60 | --fur: #FFFFFF; 61 | --fur-shadow: #ccced4; 62 | --face-hair: #FFFFFF; 63 | } 64 | 65 | [data-edit=faceColor] .color-circle{ 66 | background-color: var(--face); 67 | } 68 | 69 | [data-edit=furColor] .color-circle{ 70 | background-color: var(--fur); 71 | } 72 | 73 | .fur--green{ 74 | --fur: #4c475e; 75 | --fur-shadow: #322f3d; 76 | --face-hair: #9676c3; 77 | } 78 | 79 | .fur--default{ 80 | 81 | } 82 | 83 | .face--white{ 84 | --face: #FFFFFF; 85 | --nose: #FFAFAF; 86 | } 87 | 88 | .face--green{ 89 | --face: #ffd3fd; 90 | --nose: #e674fd; 91 | } 92 | 93 | .face--default{ 94 | 95 | } 96 | 97 | /* ------- Base ------- */ 98 | :root { 99 | font-size: 1vh; 100 | } 101 | 102 | * { 103 | box-sizing: border-box; 104 | -webkit-tap-highlight-color: rgba(0,0,0,0); 105 | overflow: -moz-scrollbars-none; 106 | -ms-overflow-style: none; 107 | } 108 | 109 | ::-webkit-scrollbar { 110 | display: none; 111 | } 112 | 113 | html{ 114 | height: 100%; 115 | font-family: 'Baloo'; 116 | } 117 | 118 | body{ 119 | height: 100%; 120 | margin: 0; 121 | font-size: 10rem; 122 | color: var(--color); 123 | background-color: var(--background-color); 124 | -webkit-touch-callout: none; 125 | -webkit-text-size-adjust: none; 126 | -webkit-user-select: none; 127 | user-select: none; 128 | overflow: hidden; 129 | } 130 | 131 | .menu{ 132 | height: 100%; 133 | display: flex; 134 | flex-direction: column; 135 | justify-content: center; 136 | align-items: center; 137 | } 138 | 139 | .game, 140 | .editor{ 141 | width: 35rem; 142 | display: flex; 143 | flex-wrap: wrap; 144 | justify-content: space-between; 145 | align-items: center; 146 | height: 25rem; 147 | } 148 | 149 | .actions .editor, 150 | .actions--editing .game{ 151 | display: none; 152 | } 153 | 154 | .actions--editing .editor{ 155 | display: flex; 156 | } 157 | 158 | .button{ 159 | width: 10rem; 160 | height: 10rem; 161 | padding: 1rem; 162 | color: var(--button-color); 163 | background-color: var(--button-background-color); 164 | box-shadow: -.2rem 1rem var(--button-shadow); 165 | border-radius: 3rem; 166 | font-size: 5rem; 167 | cursor: pointer; 168 | padding: 1rem; 169 | display: flex; 170 | justify-content: center; 171 | align-items: center; 172 | } 173 | 174 | .button svg{ 175 | width: 100%; 176 | height: 100%; 177 | } 178 | 179 | .button-animate{ 180 | animation-name: button; 181 | animation-duration: 400ms; 182 | animation-timing-function: ease-in-out; 183 | animation-delay: 0s; 184 | animation-iteration-count: infinite; 185 | animation-direction: normal; 186 | animation-fill-mode: none; 187 | animation-play-state: paused; 188 | } 189 | 190 | .performing .button-action--awake{ 191 | display: none; 192 | } 193 | 194 | .performing .button-action--sleep{ 195 | display: none; 196 | } 197 | 198 | .button-action--awake .button{ 199 | background-color: var(--sun-button-background-color); 200 | box-shadow: -.2rem 1rem var(--sun-button-shadow); 201 | } 202 | 203 | .button-action--sleep .button{ 204 | background-color: var(--moon-button-background-color); 205 | box-shadow: -.2rem 1rem var(--moon-button-shadow); 206 | } 207 | 208 | .color-circle{ 209 | border: .4rem solid #969696; 210 | width: 4rem; 211 | height: 4rem; 212 | border-radius: 100%; 213 | } 214 | 215 | .active{ 216 | animation-play-state: running; 217 | } 218 | 219 | .button-animate:nth-child(1) .button{ 220 | transform: rotate(5deg); 221 | } 222 | 223 | .button-animate:nth-child(2) .button{ 224 | transform: rotate(-5deg); 225 | } 226 | 227 | .button-animate:nth-child(3) .button{ 228 | transform: rotate(8deg); 229 | } 230 | 231 | .button-animate:nth-child(4) .button{ 232 | transform: rotate(-3deg); 233 | } 234 | 235 | .button-animate:nth-child(5) .button{ 236 | transform: rotate(8deg); 237 | } 238 | 239 | .button-animate:nth-child(6) .button{ 240 | transform: rotate(-5deg); 241 | } 242 | 243 | .game .button-animate:nth-child(2) .button{ 244 | transform: rotate(5deg); 245 | } 246 | 247 | .editor .button-animate:nth-child(5){ 248 | margin-right: 12.5rem; 249 | } 250 | 251 | .debug{ 252 | display: block; 253 | position: fixed; 254 | top: 0; 255 | right: 0; 256 | padding: 1rem; 257 | color: #fff; 258 | background: rgba(0, 0, 0, 0.9); 259 | font-family: monospace; 260 | font-size: 16px; 261 | } 262 | 263 | .debug-stat{ 264 | display: flex; 265 | justify-content: space-between; 266 | align-items: center; 267 | } 268 | 269 | progress { 270 | -webkit-appearance: none; 271 | -moz-appearance: none; 272 | appearance: none; 273 | border: none; 274 | width: 20rem; 275 | height: 1rem; 276 | border-radius: 1rem; 277 | background-color: #4e4e4e; 278 | color: #1458f0; 279 | } 280 | 281 | progress::-webkit-progress-bar { 282 | background-color: #4e4e4e; 283 | border-radius: 1rem; 284 | } 285 | 286 | progress::-webkit-progress-value { 287 | background-color: #1458f0; 288 | border-radius: 1rem; 289 | } 290 | 291 | progress::-moz-progress-bar { 292 | background-color: #1458f0; 293 | border-radius: 1rem; 294 | } 295 | 296 | .debug label{ 297 | padding: 1rem; 298 | } 299 | 300 | .stage{ 301 | height: 100%; 302 | display: flex; 303 | align-items: center; 304 | justify-content: center; 305 | flex-direction: column; 306 | } 307 | 308 | .canvas{ 309 | width: calc(25rem * 1.5); 310 | height: calc(30rem * 1.5); 311 | position: relative; 312 | } 313 | 314 | .canvas svg{ 315 | width: 100%; 316 | height: 100%; 317 | } 318 | 319 | .hide{ 320 | display: none; 321 | } 322 | 323 | .controls{ 324 | display: flex; 325 | font-size: 6rem; 326 | } 327 | 328 | .score{ 329 | display: flex; 330 | justify-content: center; 331 | } 332 | 333 | .score svg{ 334 | width: 8.5rem; 335 | height: 8.5rem; 336 | } 337 | 338 | /* ------- Rig ------- */ 339 | .rig, .armature{ 340 | position: absolute; 341 | left: 0; 342 | top: 0; 343 | width: 100%; 344 | height: 100%; 345 | } 346 | 347 | .armature{ 348 | transition: transform 1s ease-in-out; 349 | } 350 | 351 | .armature-right-hand, 352 | .armature-paddle, 353 | .armature-right-needle{ 354 | transform-origin: calc(4.7rem * 1.5) calc(17.8rem * 1.5); 355 | } 356 | 357 | .armature-left-hand, 358 | .armature-cookie, 359 | .armature-left-needle{ 360 | transform-origin: calc(20.2rem * 1.5) calc(17.8rem * 1.5); 361 | } 362 | 363 | .armature-right-foot{ 364 | transform-origin: calc(8.1rem * 1.5) calc(23.6rem * 1.5); 365 | } 366 | 367 | .armature-left-foot{ 368 | transform-origin: calc(17.1rem * 1.5) calc(23.6rem * 1.5); 369 | } 370 | 371 | .armature-feet{ 372 | transform-origin: calc(12.4rem * 1.5) calc(23.6rem * 1.5); 373 | } 374 | 375 | .armature-shadow{ 376 | transform-origin: calc(12.4rem * 1.5) calc(26rem * 1.5); 377 | } 378 | 379 | .armature-face{ 380 | transform-origin: calc(12.4rem * 1.5) calc(15rem * 1.5); 381 | } 382 | 383 | .armature-left-tear{ 384 | transform-origin: calc(17.4rem * 1.5) calc(15.3rem * 1.5); 385 | } 386 | 387 | .armature-right-cloud{ 388 | transform-origin: calc(9rem * 1.5) calc(9.6rem * 1.5); 389 | } 390 | 391 | .rig-icons{ 392 | display: none; 393 | } 394 | 395 | .rig-face .armature-display, 396 | .rig-mug .armature-display, 397 | .rig-yarn .armature-display, 398 | .rig-ping-pong .armature-display, 399 | .rig-cookie .armature-display, 400 | .rig-moon .armature-display, 401 | .rig-hearts .armature-display, 402 | .rig-clouds .armature-display{ 403 | opacity: 0; 404 | } 405 | 406 | .rig-accessories .armature-display{ 407 | opacity: 0; 408 | } 409 | 410 | .body--fur .rig-accessories .armature-hair-body{ 411 | opacity: 1; 412 | } 413 | 414 | .face--fur .rig-accessories .armature-hair-face{ 415 | opacity: 1; 416 | } 417 | 418 | .ears--small .rig-accessories .armature-ears-small{ 419 | opacity: 1; 420 | } 421 | 422 | .ears--medium .rig-accessories .armature-ears-medium{ 423 | opacity: 1; 424 | } 425 | 426 | .ears--big .rig-accessories .armature-ears-big{ 427 | opacity: 1; 428 | } 429 | 430 | .nose--round .armature-nose-round{ 431 | opacity: 1; 432 | } 433 | 434 | .nose--triangle .armature-nose-triangle{ 435 | opacity: 1; 436 | } 437 | 438 | .rig-shadow{ 439 | fill: var(--shadow); 440 | } 441 | 442 | .rig-hearts{ 443 | fill: var(--hearts); 444 | } 445 | 446 | .rig-clouds{ 447 | fill: var(--fart-cloud); 448 | } 449 | 450 | .rig-moon [id=moonDark]{ 451 | fill: var(--moon-dark); 452 | } 453 | 454 | .rig-moon [id=moonLight]{ 455 | fill: var(--moon-light); 456 | } 457 | 458 | .rig-moon [id=craterDark]{ 459 | fill: var(--moon-crater-dark); 460 | } 461 | 462 | .rig-moon [id=craterLight]{ 463 | fill: var(--moon-crater-light); 464 | } 465 | 466 | .rig-face { 467 | fill: var(--eyes); 468 | } 469 | 470 | .rig-nose{ 471 | fill: var(--nose); 472 | } 473 | 474 | .rig-face .svg-face{ 475 | fill: var(--face); 476 | } 477 | 478 | .svg-body{ 479 | fill: var(--fur); 480 | } 481 | 482 | .rig-accessories{ 483 | fill: var(--fur); 484 | } 485 | 486 | .rig-accessories .svg-hair-face{ 487 | fill: var(--face-hair); 488 | } 489 | 490 | .rig-feet{ 491 | fill: var(--fur); 492 | } 493 | 494 | .rig-cookie [id=cookieLight]{ 495 | fill: var(--cookie-light); 496 | } 497 | 498 | .rig-cookie [id=cookieDark]{ 499 | fill: var(--cookie-dark); 500 | } 501 | 502 | .svg-left-tear{ 503 | fill: var(--tear); 504 | } 505 | 506 | .rig-cookie [id=chips]{ 507 | fill: var(--cookie-chip); 508 | } 509 | 510 | .rig-ping-pong [id^=face]{ 511 | fill: var(--ping-pong-paddle-light); 512 | } 513 | 514 | .rig-ping-pong [id=side]{ 515 | fill: var(--ping-pong-paddle-dark); 516 | } 517 | 518 | .rig-ping-pong [id=handle]{ 519 | fill: var(--ping-pong-paddle-handle); 520 | } 521 | 522 | .svg-ball{ 523 | fill: var(--ping-pong-ball); 524 | } 525 | 526 | .rig-yarn{ 527 | fill: var(--yarn-light); 528 | } 529 | 530 | .svg-middle-yarn{ 531 | fill: var(--yarn-medium); 532 | } 533 | 534 | .rig-yarn-ball [id=yarnBallRight]{ 535 | fill: var(--yarn-medium); 536 | } 537 | 538 | .rig-yarn-ball [id=yarnBallBottom]{ 539 | fill: var(--yarn-dark); 540 | } 541 | 542 | .rig-yarn-ball [id=yarnBallTail]{ 543 | fill: var(--yarn-dark); 544 | } 545 | 546 | .svg-right-needle, 547 | .svg-left-needle{ 548 | fill: var(--yarn-needles); 549 | } 550 | 551 | .rig-mug{ 552 | fill: var(--coffee-mug-light); 553 | } 554 | 555 | .rig-steam{ 556 | fill: var(--coffee-mug-steam); 557 | } 558 | 559 | .rig-mug [id^=shadow]{ 560 | fill: var(--coffee-mug-dark); 561 | } 562 | 563 | .svg-inside-mug{ 564 | fill: var(--coffee-mug-dark); 565 | } 566 | 567 | .rig-hands{ 568 | fill: var(--fur-shadow); 569 | } 570 | 571 | .svg-cheeks-happy{ 572 | fill: var(--cheeks); 573 | } 574 | 575 | .svg-big-smile [id=bigSmileTongue]{ 576 | fill: var(--tongue); 577 | } 578 | 579 | .svg-side-tongue [id=tongue]{ 580 | fill: var(--tongue); 581 | } 582 | 583 | /* ------- Animations ------- */ 584 | .rig--happy .armature-eyes-open.armature-display, 585 | .rig--happy .armature-small-smile.armature-display{ 586 | opacity: 1; 587 | } 588 | 589 | .rig--pain .armature-eyes-wincing-big.armature-display, 590 | .rig--pain .armature-big-frown.armature-display{ 591 | opacity: 1; 592 | } 593 | 594 | .rig--frustration .armature-eyes-wincing-small.armature-display, 595 | .rig--frustration .armature-medium-frown.armature-display, 596 | .rig--frustration .armature-eyebrows-big-up.armature-display{ 597 | opacity: 1; 598 | } 599 | 600 | .rig--tired .armature-eyes-closed.armature-display, 601 | .rig--tired .armature-medium.armature-display, 602 | .rig--tired .armature-eyebrows-small-up.armature-display{ 603 | opacity: 1; 604 | } 605 | 606 | .rig--restless .armature-eyes-concentrated.armature-display, 607 | .rig--restless .armature-small.armature-display, 608 | .rig--restless .armature-eyebrows-serious.armature-display{ 609 | opacity: 1; 610 | } 611 | 612 | .rig--sad .armature-eyes-closed.armature-display, 613 | .rig--sad .armature-small-frown.armature-display, 614 | .rig--sad .armature-eyebrows-small-up.armature-display{ 615 | opacity: 1; 616 | } 617 | 618 | .rig--ecstatic .armature-eyes-happy-closed.armature-display, 619 | .rig--ecstatic .armature-big-smile.armature-display, 620 | .rig--ecstatic .armature-cheeks-happy.armature-display{ 621 | opacity: 1; 622 | } 623 | 624 | .rig--bathroom .armature-eyes-wincing-big.armature-display, 625 | .rig--bathroom .armature-grimmice.armature-display{ 626 | opacity: 1; 627 | } 628 | 629 | .rig--sleep .armature-eyes-closed.armature-display, 630 | .rig--sleep .armature-medium.armature-display, 631 | .rig--sleep .rig-moon .armature-display{ 632 | opacity: 1; 633 | } 634 | 635 | .rig--sleep .armature-body.armature-rotate, 636 | .rig--sleep .armature-hair-face.armature-rotate, 637 | .rig--sleep .armature-hair-body.armature-rotate, 638 | .rig--sleep .armature-ears-small.armature-rotate, 639 | .rig--sleep .armature-ears-medium.armature-rotate, 640 | .rig--sleep .armature-ears-big.armature-rotate{ 641 | transform: rotate(-10deg); 642 | } 643 | 644 | .rig--sleep .armature-hands.armature-rotate{ 645 | transform: rotate(-5deg); 646 | transition-delay: 200ms; 647 | } 648 | 649 | .rig--play .armature-eyes-concentrated.armature-display, 650 | .rig--play .armature-side-tongue.armature-display, 651 | .rig--play .rig-ping-pong .armature-display{ 652 | opacity: 1; 653 | } 654 | 655 | .rig--relax .armature-eyes-happy-closed.armature-display, 656 | .rig--relax .armature-small-smile.armature-display, 657 | .rig--relax .rig-mug .armature-display{ 658 | opacity: 1; 659 | } 660 | 661 | .rig--create .armature-eyes-open.armature-display, 662 | .rig--create .armature-small-smile.armature-display, 663 | .rig--create .rig-yarn .armature-display{ 664 | opacity: 1; 665 | } 666 | 667 | .rig--eat .armature-eyes-closed.armature-display, 668 | .rig--eat .armature-big-smile.armature-display, 669 | .rig--eat .rig-cookie .armature-display{ 670 | opacity: 1; 671 | } 672 | 673 | .rig-moon .armature-translate{ 674 | transition: transform 800ms; 675 | transition-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1.275); 676 | transform: translateX(6rem) translateY(4rem); 677 | } 678 | 679 | .rig--sleep .rig-moon .armature-translate{ 680 | transform: translateX(0) translateY(0); 681 | } 682 | 683 | .rig--ecstatic .armature-hearts.armature-display{ 684 | animation-name: fadeIn; 685 | animation-duration: 3s; 686 | animation-timing-function: ease-in-out; 687 | animation-delay: 0s; 688 | animation-iteration-count: infinite; 689 | animation-direction: normal; 690 | animation-fill-mode: none; 691 | animation-play-state: running; 692 | } 693 | 694 | .rig--ecstatic .armature-hearts .armature-translate{ 695 | animation-name: slideUp; 696 | animation-duration: 3s; 697 | animation-timing-function: ease-in; 698 | animation-delay: 0s; 699 | animation-iteration-count: infinite; 700 | animation-direction: normal; 701 | animation-fill-mode: none; 702 | animation-play-state: running; 703 | } 704 | 705 | .rig--bathroom .armature-clouds.armature-display{ 706 | animation-name: fadeIn; 707 | animation-duration: 3s; 708 | animation-timing-function: ease-in-out; 709 | animation-delay: 0s; 710 | animation-iteration-count: infinite; 711 | animation-direction: normal; 712 | animation-fill-mode: none; 713 | animation-play-state: running; 714 | } 715 | 716 | .rig--bathroom .armature-clouds .armature-translate{ 717 | animation-name: slideUp; 718 | animation-duration: 3s; 719 | animation-timing-function: ease-in; 720 | animation-delay: 0s; 721 | animation-iteration-count: infinite; 722 | animation-direction: normal; 723 | animation-fill-mode: none; 724 | animation-play-state: running; 725 | } 726 | 727 | .rig--bathroom .armature-right-cloud.armature-rotate{ 728 | animation-name: littleSpin; 729 | animation-duration: 3s; 730 | animation-timing-function: ease-in; 731 | animation-delay: 0s; 732 | animation-iteration-count: infinite; 733 | animation-direction: normal; 734 | animation-fill-mode: none; 735 | animation-play-state: running; 736 | } 737 | 738 | .rig--bathroom .rig-body .armature-translate{ 739 | animation-name: strain; 740 | animation-duration: 1.5s; 741 | animation-timing-function: ease-in; 742 | animation-delay: 0s; 743 | animation-iteration-count: infinite; 744 | animation-direction: normal; 745 | animation-fill-mode: none; 746 | animation-play-state: running; 747 | } 748 | 749 | .rig--sad .armature-left-tear.armature-display{ 750 | animation-name: fadeIn; 751 | animation-duration: 3s; 752 | animation-timing-function: ease-out; 753 | animation-delay: 0s; 754 | animation-iteration-count: infinite; 755 | animation-direction: normal; 756 | animation-fill-mode: none; 757 | animation-play-state: running; 758 | } 759 | 760 | .rig--sad .armature-left-tear.armature-translate{ 761 | animation-name: slideDown; 762 | animation-duration: 3s; 763 | animation-timing-function: ease-in; 764 | animation-delay: 0s; 765 | animation-iteration-count: infinite; 766 | animation-direction: normal; 767 | animation-fill-mode: none; 768 | animation-play-state: running; 769 | } 770 | 771 | .rig--sad .armature-left-tear.armature-scale{ 772 | animation-name: scaleUp; 773 | animation-duration: 3s; 774 | animation-timing-function: ease-out; 775 | animation-delay: 0s; 776 | animation-iteration-count: infinite; 777 | animation-direction: normal; 778 | animation-fill-mode: none; 779 | animation-play-state: running; 780 | } 781 | 782 | .rig--restless .armature-body.armature-rotate, 783 | .rig--restless .armature-hair-body.armature-rotate, 784 | .rig--restless .armature-hair-face.armature-rotate, 785 | .rig--restless .armature-ears-small.armature-rotate, 786 | .rig--restless .armature-ears-medium.armature-rotate, 787 | .rig--restless .armature-ears-big.armature-rotate, 788 | .rig--restless .armature-hands.armature-rotate{ 789 | animation-name: tilt; 790 | animation-duration: 5s; 791 | animation-timing-function: ease-out; 792 | animation-delay: 0s; 793 | animation-iteration-count: infinite; 794 | animation-direction: normal; 795 | animation-fill-mode: none; 796 | animation-play-state: running; 797 | } 798 | 799 | .rig--restless .armature-left-foot.armature-translate{ 800 | animation-name: shiftLeft; 801 | animation-duration: 5s; 802 | animation-timing-function: ease-in-out; 803 | animation-delay: 0s; 804 | animation-iteration-count: infinite; 805 | animation-direction: normal; 806 | animation-fill-mode: none; 807 | animation-play-state: running; 808 | } 809 | 810 | .rig--restless .armature-right-foot.armature-translate{ 811 | animation-name: shiftRight; 812 | animation-duration: 5s; 813 | animation-timing-function: ease-in-out; 814 | animation-delay: 0s; 815 | animation-iteration-count: infinite; 816 | animation-direction: normal; 817 | animation-fill-mode: none; 818 | animation-play-state: running; 819 | } 820 | 821 | .rig--restless .armature-left-foot.armature-rotate{ 822 | animation-name: rotateLeft; 823 | animation-duration: 5s; 824 | animation-timing-function: ease-in-out; 825 | animation-delay: 0s; 826 | animation-iteration-count: infinite; 827 | animation-direction: normal; 828 | animation-fill-mode: none; 829 | animation-play-state: running; 830 | } 831 | 832 | .rig--restless .armature-right-foot.armature-rotate{ 833 | animation-name: rotateRight; 834 | animation-duration: 5s; 835 | animation-timing-function: ease-in-out; 836 | animation-delay: 0s; 837 | animation-iteration-count: infinite; 838 | animation-direction: normal; 839 | animation-fill-mode: none; 840 | animation-play-state: running; 841 | } 842 | 843 | .armature-eyebrows-big-up.armature-translate{ 844 | transform-origin: center; 845 | animation-name: wince; 846 | animation-duration: 7s; 847 | animation-timing-function: ease-out; 848 | animation-delay: 0s; 849 | animation-iteration-count: infinite; 850 | animation-direction: normal; 851 | animation-fill-mode: none; 852 | animation-play-state: running; 853 | } 854 | 855 | .rig--idle .rig--happy .armature-eyes-open.armature-display{ 856 | animation-name: blinkOut; 857 | animation-duration: 6s; 858 | animation-timing-function: linear; 859 | animation-delay: 0s; 860 | animation-iteration-count: infinite; 861 | animation-direction: normal; 862 | animation-fill-mode: none; 863 | animation-play-state: running; 864 | } 865 | 866 | .rig--idle .rig--happy .armature-eyes-closed.armature-display{ 867 | animation-name: blinkIn; 868 | animation-duration: 6s; 869 | animation-timing-function: linear; 870 | animation-delay: 0s; 871 | animation-iteration-count: infinite; 872 | animation-direction: normal; 873 | animation-fill-mode: none; 874 | animation-play-state: running; 875 | } 876 | 877 | .rig--idle .armature-body.armature-translate{ 878 | transform-origin: center; 879 | animation-name: float; 880 | animation-duration: 3s; 881 | animation-timing-function: ease-in-out; 882 | animation-delay: 0s; 883 | animation-iteration-count: infinite; 884 | animation-direction: normal; 885 | animation-fill-mode: none; 886 | animation-play-state: running; 887 | } 888 | 889 | .rig--idle .armature-shadow.armature-scale{ 890 | animation-name: floatShadow; 891 | animation-duration: 3s; 892 | animation-timing-function: ease-in-out; 893 | animation-delay: 0s; 894 | animation-iteration-count: infinite; 895 | animation-direction: normal; 896 | animation-fill-mode: none; 897 | animation-play-state: running; 898 | } 899 | 900 | .rig--idle .armature-hair-face.armature-translate{ 901 | transform-origin: center; 902 | animation-name: float; 903 | animation-duration: 3s; 904 | animation-timing-function: ease-in-out; 905 | animation-delay: 0.1s; 906 | animation-iteration-count: infinite; 907 | animation-direction: normal; 908 | animation-fill-mode: none; 909 | animation-play-state: running; 910 | } 911 | 912 | .rig--idle .armature-hair-body.armature-translate, 913 | .rig--idle .armature-ears-small.armature-translate, 914 | .rig--idle .armature-ears-big.armature-translate, 915 | .rig--idle .armature-ears-medium.armature-translate{ 916 | transform-origin: center; 917 | animation-name: float; 918 | animation-duration: 3s; 919 | animation-timing-function: ease-in-out; 920 | animation-delay: 0.2s; 921 | animation-iteration-count: infinite; 922 | animation-direction: normal; 923 | animation-fill-mode: none; 924 | animation-play-state: running; 925 | } 926 | 927 | .rig--idle .armature-left-hand.armature-translate, 928 | .rig--idle .armature-right-hand.armature-translate, 929 | .rig--idle .armature-mug.armature-translate{ 930 | transform-origin: center; 931 | animation-name: float; 932 | animation-duration: 3s; 933 | animation-timing-function: ease-in-out; 934 | animation-delay: 0.3s; 935 | animation-iteration-count: infinite; 936 | animation-direction: normal; 937 | animation-fill-mode: none; 938 | animation-play-state: running; 939 | } 940 | 941 | /* eat */ 942 | /* .rig--eat .armature-shadow.armature-scale{ 943 | animation-name: floatShadow; 944 | animation-duration: .2s; 945 | animation-timing-function: linear; 946 | animation-delay: 0s; 947 | animation-iteration-count: infinite; 948 | animation-direction: normal; 949 | animation-fill-mode: none; 950 | animation-play-state: running; 951 | } */ 952 | 953 | .rig--eat .armature-body.armature-translate, 954 | .rig--eat .armature-hair-face.armature-translate, 955 | .rig--eat .armature-hair-body.armature-translate, 956 | .rig--eat .armature-ears-small.armature-translate, 957 | .rig--eat .armature-ears-big.armature-translate, 958 | .rig--eat .armature-ears-medium.armature-translate, 959 | .rig--eat .armature-left-hand.armature-translate, 960 | .rig--eat .armature-right-hand.armature-translate, 961 | .rig--eat .armature-mug.armature-translate, 962 | .rig--eat .armature-cookie.armature-translate{ 963 | transform-origin: center; 964 | animation-name: eatJiggle; 965 | animation-duration: 1s; 966 | animation-timing-function: linear; 967 | animation-delay: 0s; 968 | animation-iteration-count: infinite; 969 | animation-direction: normal; 970 | animation-fill-mode: none; 971 | animation-play-state: running; 972 | } 973 | 974 | .rig--eat .armature-cookie.armature-rotate, 975 | .rig--eat .armature-left-hand.armature-rotate{ 976 | animation-name: eatCookie; 977 | animation-duration: 4s; 978 | animation-timing-function: ease-in; 979 | animation-delay: 0s; 980 | animation-iteration-count: infinite; 981 | animation-direction: normal; 982 | animation-fill-mode: none; 983 | animation-play-state: running; 984 | } 985 | 986 | .rig--eat .armature-big-smile.armature-scale{ 987 | animation-name: mouthClose; 988 | animation-duration: 1s; 989 | animation-timing-function: ease-in; 990 | animation-delay: 0s; 991 | animation-iteration-count: infinite; 992 | animation-direction: normal; 993 | animation-fill-mode: none; 994 | animation-play-state: running; 995 | } 996 | /* end eat */ 997 | 998 | .rig--idle .armature-eyes-open.armature-translate{ 999 | animation-name: leftRight; 1000 | animation-duration: 5s; 1001 | animation-timing-function: ease-in-out; 1002 | animation-delay: .3s; 1003 | animation-iteration-count: infinite; 1004 | animation-direction: normal; 1005 | animation-fill-mode: none; 1006 | animation-play-state: running; 1007 | } 1008 | 1009 | .rig--shake .armature-face.armature-translate{ 1010 | animation-name: shake; 1011 | animation-duration: 1s; 1012 | animation-timing-function: ease-out; 1013 | animation-delay: 0s; 1014 | animation-iteration-count: 1; 1015 | animation-direction: normal; 1016 | animation-fill-mode: none; 1017 | animation-play-state: running; 1018 | } 1019 | 1020 | .rig--shake .armature-face.armature-scale{ 1021 | animation-name: shakeSquish; 1022 | animation-duration: 1s; 1023 | animation-timing-function: ease-in-out; 1024 | animation-delay: 0s; 1025 | animation-iteration-count: 1; 1026 | animation-direction: normal; 1027 | animation-fill-mode: none; 1028 | animation-play-state: running; 1029 | } 1030 | 1031 | .rig--play .armature-paddle.armature-rotate, 1032 | .rig--play .armature-right-hand.armature-rotate{ 1033 | animation-name: playPaddle; 1034 | animation-duration: 1s; 1035 | animation-timing-function: ease-in; 1036 | animation-delay: -.4s; 1037 | animation-iteration-count: infinite; 1038 | animation-direction: normal; 1039 | animation-fill-mode: none; 1040 | animation-play-state: running; 1041 | } 1042 | 1043 | .rig--play .armature-ball.armature-translate{ 1044 | /* transform: translateY(-15rem); */ 1045 | animation-name: playBall; 1046 | animation-duration: 1s; 1047 | animation-timing-function: cubic-bezier(0.250, 0.460, 0.450, 0.940); 1048 | animation-delay: 0s; 1049 | animation-iteration-count: infinite; 1050 | animation-direction: normal; 1051 | animation-fill-mode: none; 1052 | animation-play-state: running; 1053 | } 1054 | 1055 | .rig--create .armature-left-needle.armature-rotate, 1056 | .rig--create .armature-left-hand.armature-rotate{ 1057 | animation-name: knitLeft; 1058 | animation-duration: 1s; 1059 | animation-timing-function: ease-in; 1060 | animation-delay: 0s; 1061 | animation-iteration-count: infinite; 1062 | animation-direction: normal; 1063 | animation-fill-mode: none; 1064 | animation-play-state: running; 1065 | } 1066 | 1067 | .rig--create .armature-right-needle.armature-rotate, 1068 | .rig--create .armature-right-hand.armature-rotate{ 1069 | transform: rotate(-5deg); 1070 | animation-name: knitRight; 1071 | animation-duration: 1s; 1072 | animation-timing-function: ease-in; 1073 | animation-delay: .5s; 1074 | animation-iteration-count: infinite; 1075 | animation-direction: normal; 1076 | animation-fill-mode: none; 1077 | animation-play-state: running; 1078 | } 1079 | 1080 | .rig--create .armature-middle-yarn.armature-rotate{ 1081 | animation-name: knit; 1082 | animation-duration: 1s; 1083 | animation-timing-function: ease-in; 1084 | animation-delay: 0s; 1085 | animation-iteration-count: infinite; 1086 | animation-direction: normal; 1087 | animation-fill-mode: none; 1088 | animation-play-state: running; 1089 | } 1090 | 1091 | .rig--create .armature-left-yarn.armature-translate{ 1092 | animation-name: upAndDown; 1093 | animation-duration: 1s; 1094 | animation-timing-function: ease-in; 1095 | animation-delay: 0s; 1096 | animation-iteration-count: infinite; 1097 | animation-direction: normal; 1098 | animation-fill-mode: none; 1099 | animation-play-state: running; 1100 | } 1101 | 1102 | .rig--create .armature-right-yarn.armature-translate{ 1103 | animation-name: upAndDown; 1104 | animation-duration: 1s; 1105 | animation-timing-function: ease-in; 1106 | animation-delay: .5s; 1107 | animation-iteration-count: infinite; 1108 | animation-direction: normal; 1109 | animation-fill-mode: none; 1110 | animation-play-state: running; 1111 | } 1112 | 1113 | /* ------- Keyframes ------- */ 1114 | @keyframes button { 1115 | 0%{ 1116 | transform: scale(1); 1117 | } 1118 | 30%{ 1119 | transform: scale(.9); 1120 | } 1121 | 100%{ 1122 | transform: scale(1); 1123 | } 1124 | } 1125 | 1126 | @keyframes float { 1127 | 0%{ 1128 | transform: translateY(0); 1129 | } 1130 | 42%{ 1131 | transform: translateY(.77rem); 1132 | } 1133 | 100%{ 1134 | transform: translateY(0); 1135 | } 1136 | } 1137 | 1138 | @keyframes floatShadow { 1139 | 0%{ 1140 | transform: scale(1); 1141 | } 1142 | 42%{ 1143 | transform: scale(0.9); 1144 | } 1145 | 100%{ 1146 | transform: scale(1); 1147 | } 1148 | } 1149 | 1150 | @keyframes blinkIn { 1151 | 12%{ 1152 | opacity: 0; 1153 | } 1154 | 13%{ 1155 | opacity: 1; 1156 | } 1157 | 14%{ 1158 | opacity: 1; 1159 | } 1160 | 15%{ 1161 | opacity: 0; 1162 | } 1163 | 40%{ 1164 | opacity: 0; 1165 | } 1166 | 41%{ 1167 | opacity: 1; 1168 | } 1169 | 42%{ 1170 | opacity: 1; 1171 | } 1172 | 43%{ 1173 | opacity: 0; 1174 | } 1175 | } 1176 | 1177 | @keyframes blinkOut { 1178 | 12%{ 1179 | opacity: 1; 1180 | } 1181 | 13%{ 1182 | opacity: 0; 1183 | } 1184 | 14%{ 1185 | opacity: 0; 1186 | } 1187 | 15%{ 1188 | opacity: 1; 1189 | } 1190 | 40%{ 1191 | opacity: 1; 1192 | } 1193 | 41%{ 1194 | opacity: 0; 1195 | } 1196 | 42%{ 1197 | opacity: 0; 1198 | } 1199 | 43%{ 1200 | opacity: 1; 1201 | } 1202 | } 1203 | 1204 | @keyframes wince { 1205 | 0%{ 1206 | transform: translateY(0); 1207 | } 1208 | 42%{ 1209 | transform: translateY(0); 1210 | } 1211 | 50%{ 1212 | transform: translateY(.6rem); 1213 | } 1214 | 80%{ 1215 | transform: translateY(.6rem); 1216 | } 1217 | 90%{ 1218 | transform: translateY(0); 1219 | } 1220 | } 1221 | 1222 | @keyframes leftRight { 1223 | 0%{ 1224 | transform: translateX(0); 1225 | } 1226 | 29%{ 1227 | transform: translateX(0); 1228 | } 1229 | 30%{ 1230 | transform: translateY(-.2rem) translateX(.2rem); 1231 | } 1232 | 38%{ 1233 | transform: translateY(-.2rem) translateX(.2rem); 1234 | } 1235 | 40%{ 1236 | transform: translateY(-.2rem) translateX(-.2rem); 1237 | } 1238 | 50%{ 1239 | transform: translateY(-.2rem) translateX(-.2rem); 1240 | } 1241 | 52%{ 1242 | transform: translateY(0); 1243 | } 1244 | } 1245 | 1246 | @keyframes shake { 1247 | 0%{ 1248 | transform: translateX(0); 1249 | } 1250 | 25%{ 1251 | transform: translateX(-.4rem); 1252 | } 1253 | 50%{ 1254 | transform: translateX(.4rem); 1255 | } 1256 | 75%{ 1257 | transform: translateX(-.4rem); 1258 | } 1259 | 100%{ 1260 | transform: translateX(0) translateY(0); 1261 | } 1262 | } 1263 | 1264 | @keyframes shakeSquish { 1265 | 0%{ 1266 | transform: scaleY(1); 1267 | } 1268 | 15%{ 1269 | transform: scaleY(1.04); 1270 | } 1271 | 65%{ 1272 | transform: scaleY(1.04); 1273 | } 1274 | 100%{ 1275 | transform: scaleY(1); 1276 | } 1277 | } 1278 | 1279 | @keyframes tilt { 1280 | 0%{ 1281 | transform: rotate(0); 1282 | } 1283 | 34%{ 1284 | transform: rotate(0); 1285 | } 1286 | 42%{ 1287 | transform: rotate(-3deg); 1288 | } 1289 | 62%{ 1290 | transform: rotate(3deg); 1291 | } 1292 | 72%{ 1293 | transform: rotate(0); 1294 | } 1295 | 100%{ 1296 | transform: rotate(0); 1297 | } 1298 | } 1299 | 1300 | @keyframes shiftLeft { 1301 | 0%{ 1302 | transform: translateY(0); 1303 | } 1304 | 34%{ 1305 | transform: translateY(0); 1306 | } 1307 | 42%{ 1308 | transform: translateY(-.5rem); 1309 | } 1310 | 52%{ 1311 | transform: translateY(0); 1312 | } 1313 | 100%{ 1314 | transform: translateY(0); 1315 | } 1316 | } 1317 | 1318 | @keyframes shiftRight { 1319 | 0%{ 1320 | transform: translateY(0); 1321 | } 1322 | 50%{ 1323 | transform: translateY(0); 1324 | } 1325 | 62%{ 1326 | transform: translateY(-.5rem); 1327 | } 1328 | 72%{ 1329 | transform: translateY(0); 1330 | } 1331 | 100%{ 1332 | transform: translateY(0); 1333 | } 1334 | } 1335 | 1336 | @keyframes rotateLeft { 1337 | 0%{ 1338 | transform: rotate(0); 1339 | } 1340 | 30%{ 1341 | transform: rotate(0); 1342 | } 1343 | 42%{ 1344 | transform: rotate(-10deg); 1345 | } 1346 | 52%{ 1347 | transform: rotate(0); 1348 | } 1349 | 100%{ 1350 | transform: rotate(0); 1351 | } 1352 | } 1353 | 1354 | @keyframes rotateRight { 1355 | 0%{ 1356 | transform: rotate(0); 1357 | } 1358 | 50%{ 1359 | transform: rotate(0); 1360 | } 1361 | 62%{ 1362 | transform: rotate(10deg); 1363 | } 1364 | 72%{ 1365 | transform: rotate(0); 1366 | } 1367 | 100%{ 1368 | transform: rotate(0); 1369 | } 1370 | } 1371 | 1372 | @keyframes fadeIn { 1373 | 0%{ 1374 | opacity: 0; 1375 | } 1376 | 20%{ 1377 | opacity: 1; 1378 | } 1379 | 96%{ 1380 | opacity: 1; 1381 | } 1382 | 100%{ 1383 | opacity: 0; 1384 | } 1385 | } 1386 | 1387 | @keyframes slideDown { 1388 | 0%{ 1389 | transform: translateY(-1rem); 1390 | } 1391 | 100%{ 1392 | transform: translateY(1.5rem); 1393 | } 1394 | } 1395 | 1396 | @keyframes scaleUp { 1397 | 0%{ 1398 | transform: scale(0); 1399 | } 1400 | 40%{ 1401 | transform: scale(1); 1402 | } 1403 | } 1404 | 1405 | @keyframes slideUp { 1406 | 0%{ 1407 | transform: translateY(2rem); 1408 | } 1409 | 100%{ 1410 | transform: translateY(-2rem); 1411 | } 1412 | } 1413 | 1414 | @keyframes strain{ 1415 | 0%{ 1416 | transform: translateX(.1rem) translateY(.1rem); 1417 | } 1418 | 34%{ 1419 | transform: translateX(-.1rem) translateY(-.1rem); 1420 | } 1421 | 42%{ 1422 | transform: translateX(.1rem) translateY(.1rem); 1423 | } 1424 | 62%{ 1425 | transform: translateX(-.1rem) translateY(-.1rem); 1426 | } 1427 | 72%{ 1428 | transform: translateX(.1rem) translateY(.1rem); 1429 | } 1430 | 100%{ 1431 | transform: translateX(-.1rem) translateY(-.1rem); 1432 | } 1433 | } 1434 | 1435 | @keyframes playPaddle{ 1436 | 0%{ 1437 | transform: rotate(-40deg); 1438 | } 1439 | 50%{ 1440 | transform: rotate(5deg); 1441 | } 1442 | 100%{ 1443 | transform: rotate(-40deg); 1444 | } 1445 | } 1446 | 1447 | @keyframes playBall{ 1448 | 0%{ 1449 | transform: translateY(-15rem); 1450 | } 1451 | 50%{ 1452 | transform: translateY(-3rem); 1453 | } 1454 | 100%{ 1455 | transform: translateY(-15rem); 1456 | } 1457 | } 1458 | 1459 | @keyframes eatJiggle { 1460 | 0%{ 1461 | transform: translateY(0); 1462 | } 1463 | 40%{ 1464 | transform: translateY(.5rem); 1465 | } 1466 | 90%{ 1467 | transform: translateY(0); 1468 | } 1469 | 100%{ 1470 | transform: translateY(0); 1471 | } 1472 | } 1473 | 1474 | @keyframes eatCookie{ 1475 | 0%{ 1476 | transform: rotate(-25deg); 1477 | } 1478 | 10%{ 1479 | transform: rotate(14deg); 1480 | } 1481 | 90%{ 1482 | transform: rotate(14deg); 1483 | } 1484 | 100%{ 1485 | transform: rotate(-25deg); 1486 | } 1487 | } 1488 | 1489 | @keyframes mouthClose { 1490 | 0%{ 1491 | transform: scaleY(1); 1492 | } 1493 | 50%{ 1494 | transform: scaleY(.5); 1495 | } 1496 | 100%{ 1497 | transform: scaleY(1); 1498 | } 1499 | } 1500 | 1501 | @keyframes knitLeft { 1502 | 0%{ 1503 | transform: rotate(0); 1504 | } 1505 | 50%{ 1506 | transform: rotate(-5deg); 1507 | } 1508 | 100%{ 1509 | transform: rotate(0); 1510 | } 1511 | } 1512 | 1513 | @keyframes knitRight { 1514 | 0%{ 1515 | transform: rotate(-5deg); 1516 | } 1517 | 50%{ 1518 | transform: rotate(0deg); 1519 | } 1520 | 100%{ 1521 | transform: rotate(-5deg); 1522 | } 1523 | } 1524 | 1525 | @keyframes knit { 1526 | 0%{ 1527 | transform: rotate(0); 1528 | } 1529 | 50%{ 1530 | transform: rotate(8deg); 1531 | } 1532 | 100%{ 1533 | transform: rotate(0); 1534 | } 1535 | } 1536 | 1537 | @keyframes upAndDown { 1538 | 0%{ 1539 | transform: translateY(0rem); 1540 | } 1541 | 50%{ 1542 | transform: translateY(.75rem); 1543 | } 1544 | 100%{ 1545 | transform: translateY(0rem); 1546 | } 1547 | } 1548 | 1549 | @keyframes littleSpin { 1550 | 0%{ 1551 | transform: rotate(0); 1552 | } 1553 | 100%{ 1554 | transform: rotate(25deg); 1555 | } 1556 | } 1557 | -------------------------------------------------------------------------------- /assets/rigFull.svg: -------------------------------------------------------------------------------- 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 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 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 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | --------------------------------------------------------------------------------