├── .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 |
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 |
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 |
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 |
--------------------------------------------------------------------------------