lilgotchi 0.0.0-stage → 3.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.md +21 -0
- package/README.md +69 -3
- package/dist/LICENSE.md +21 -0
- package/dist/README.md +69 -0
- package/dist/design/evolution/README.md +7 -0
- package/dist/design/evolution/cypunk.js +3 -0
- package/dist/design/evolution/cypunk.md +20 -0
- package/dist/design/evolution/index.html +20 -0
- package/dist/design/evolution/lilgotchi.js +3 -0
- package/dist/design/evolution/lilgotchi.md +20 -0
- package/dist/design/evolution/preview.css +3 -0
- package/dist/design/evolution/preview.js +47 -0
- package/dist/design/evolution/slice.js +3 -0
- package/dist/design/evolution/slice.md +14 -0
- package/dist/design/games/README.md +52 -0
- package/dist/design/games/bump.md +23 -0
- package/dist/design/games/catalog.md +24 -0
- package/dist/design/games/flag.md +19 -0
- package/dist/design/games/get.md +21 -0
- package/dist/design/games/heading.md +23 -0
- package/dist/design/games/hoops.md +17 -0
- package/dist/design/games/left-right.md +30 -0
- package/dist/design/games/memory.md +21 -0
- package/dist/design/games/polish-review.md +11 -0
- package/dist/design/games/release-checklist.md +52 -0
- package/dist/design/games/sprint.md +21 -0
- package/dist/docs/README.md +14 -0
- package/dist/docs/care.md +51 -0
- package/dist/docs/characters.md +51 -0
- package/dist/docs/credits.md +17 -0
- package/dist/docs/development.md +87 -0
- package/dist/docs/frame-contract.md +38 -0
- package/dist/docs/images/cypunk-family.jpg +0 -0
- package/dist/docs/images/lilgotchi-family.jpg +0 -0
- package/dist/docs/images/original-family.jpg +0 -0
- package/dist/docs/images/slice-family.jpg +0 -0
- package/dist/docs/install.md +41 -0
- package/dist/docs/release-review.md +47 -0
- package/dist/embed.js +21 -0
- package/dist/examples/cross-origin.html +7 -0
- package/dist/examples/curated.html +12 -0
- package/dist/examples/duplicate.html +3 -0
- package/dist/examples/install.html +11 -0
- package/dist/examples/launcher-review.html +23 -0
- package/dist/examples/launchers.html +10 -0
- package/dist/examples/nested/index.html +6 -0
- package/dist/examples/one-line.html +9 -0
- package/dist/index.html +100 -0
- package/dist/lilgotchi.js +437 -0
- package/dist/pets/NOTICE.md +19 -0
- package/dist/pets/README.md +7 -0
- package/dist/pets/arcade-catalog.js +52 -0
- package/dist/pets/arcade-geometry.js +18 -0
- package/dist/pets/arcade-opponents.js +16 -0
- package/dist/pets/arcade-renderer.js +116 -0
- package/dist/pets/arcade-sprites.js +64 -0
- package/dist/pets/arcade.js +393 -0
- package/dist/pets/away.js +20 -0
- package/dist/pets/boot.js +161 -0
- package/dist/pets/catalog.js +32 -0
- package/dist/pets/celebrations.js +54 -0
- package/dist/pets/classic-sprites.js +672 -0
- package/dist/pets/clock.js +3 -0
- package/dist/pets/colors.js +38 -0
- package/dist/pets/constants.js +20 -0
- package/dist/pets/custom-themes.css +80 -0
- package/dist/pets/cyberpunk-sprites.js +68 -0
- package/dist/pets/cypunk-original.js +2 -0
- package/dist/pets/embed.css +130 -0
- package/dist/pets/engine.js +38 -0
- package/dist/pets/evolution.js +46 -0
- package/dist/pets/focus.js +27 -0
- package/dist/pets/font.js +185 -0
- package/dist/pets/game.css +275 -0
- package/dist/pets/game.html +148 -0
- package/dist/pets/game.js +626 -0
- package/dist/pets/layout.js +53 -0
- package/dist/pets/lilgotchi-sprites.js +62 -0
- package/dist/pets/minigames/bump.js +52 -0
- package/dist/pets/minigames/flag.js +87 -0
- package/dist/pets/minigames/get.js +59 -0
- package/dist/pets/minigames/heading.js +117 -0
- package/dist/pets/minigames/hoops.js +96 -0
- package/dist/pets/minigames/memory.js +81 -0
- package/dist/pets/minigames/model.js +40 -0
- package/dist/pets/minigames/sprint.js +103 -0
- package/dist/pets/original-roster.js +46 -0
- package/dist/pets/original-sprites.js +3861 -0
- package/dist/pets/overlay-size.js +13 -0
- package/dist/pets/pixel-size.js +6 -0
- package/dist/pets/presentation.js +80 -0
- package/dist/pets/renderer.js +100 -0
- package/dist/pets/scenery.css +24 -0
- package/dist/pets/simulation.js +185 -0
- package/dist/pets/skins.js +14 -0
- package/dist/pets/slice-sprites.js +38 -0
- package/dist/pets/sound.js +33 -0
- package/dist/pets/status.js +28 -0
- package/dist/pets/storage.js +49 -0
- package/dist/pets/valid-state.js +26 -0
- package/dist/pets/wellbeing.js +73 -0
- package/dist/reference/source.json +8 -0
- package/dist/version.json +5 -0
- package/dist/widget/care-guide.js +50 -0
- package/dist/widget/care-monitor.js +52 -0
- package/dist/widget/color-controls.js +15 -0
- package/dist/widget/configurator.js +24 -0
- package/dist/widget/egg.svg +1 -0
- package/dist/widget/focus.js +6 -0
- package/dist/widget/gallery.js +17 -0
- package/dist/widget/inactive.js +24 -0
- package/dist/widget/install-snippet.js +14 -0
- package/dist/widget/launcher.js +63 -0
- package/dist/widget/pet-history.js +57 -0
- package/dist/widget/pet-label.js +12 -0
- package/dist/widget/save-store.js +198 -0
- package/dist/widget/scroll-lock.js +11 -0
- package/dist/widget/settings.js +48 -0
- package/dist/widget/shell.css +63 -0
- package/package.json +43 -6
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import { applyPetColors, cleanColors } from './colors.js';
|
|
2
|
+
import { selectedCharacter } from './storage.js';
|
|
3
|
+
import { TamagotchiApp } from './game.js';
|
|
4
|
+
import { trapOverlayTab } from './focus.js';
|
|
5
|
+
import { overlayContentHeight } from './overlay-size.js';
|
|
6
|
+
import { petType, validState, configureStorage } from './storage.js';
|
|
7
|
+
import { configurationActionId } from '../widget/settings.js';
|
|
8
|
+
|
|
9
|
+
const params=new URLSearchParams(location.search);
|
|
10
|
+
const embedded=params.get('embed')==='1';
|
|
11
|
+
let channel=null;
|
|
12
|
+
let saved=null;
|
|
13
|
+
let saveId=0;
|
|
14
|
+
let lastConfiguration='';
|
|
15
|
+
let configurationReady=false;
|
|
16
|
+
let customColors={};
|
|
17
|
+
const applyColors=()=>{applyPetColors(document.documentElement,selectedCharacter.id,customColors);window.app?.renderer.updateColors();window.app?.render();};
|
|
18
|
+
function publishConfiguration(){
|
|
19
|
+
if(!channel||!window.app||!configurationReady)return;
|
|
20
|
+
const configuration={vacation:window.app.state.vacationAt>0,scanlines:document.documentElement.classList.contains('scanlines'),eink:window.app.eink,muted:window.app.sound.muted,classic:petType==='classic'};
|
|
21
|
+
const key=JSON.stringify(configuration);
|
|
22
|
+
if(key===lastConfiguration)return;
|
|
23
|
+
lastConfiguration=key;channel.postMessage({type:'configuration',configuration});
|
|
24
|
+
}
|
|
25
|
+
document.documentElement.dataset.pet=petType;
|
|
26
|
+
document.documentElement.dataset.embedded=String(embedded);
|
|
27
|
+
if(petType!=='classic') {
|
|
28
|
+
const style=document.createElement('link');
|
|
29
|
+
style.rel='stylesheet'; style.href=new URL('./custom-themes.css',import.meta.url).href;
|
|
30
|
+
const loaded=new Promise(resolve=>{style.onload=resolve;style.onerror=resolve;});
|
|
31
|
+
document.head.append(style); await loaded;
|
|
32
|
+
document.getElementById('eink-row').hidden=true;
|
|
33
|
+
}
|
|
34
|
+
function start(initial, muted=true, eink=false,scanlines=false) {
|
|
35
|
+
for(const overlay of document.querySelectorAll('.overlay')) {
|
|
36
|
+
const title=overlay.querySelector('.overlay-title');
|
|
37
|
+
title.id=`${overlay.id}-title`;overlay.setAttribute('aria-labelledby',title.id);
|
|
38
|
+
}
|
|
39
|
+
saved=initial;
|
|
40
|
+
applyColors();
|
|
41
|
+
if(embedded) configureStorage({load:()=>saved,save:state=>{
|
|
42
|
+
saved=state;
|
|
43
|
+
channel.postMessage({type:'save',id:++saveId,state});
|
|
44
|
+
publishConfiguration();
|
|
45
|
+
}});
|
|
46
|
+
window.app=new TamagotchiApp({scanlines,onScanlinesChange:value=>{
|
|
47
|
+
if(channel){channel.postMessage({type:'preference',name:'scanlines',value});publishConfiguration();}
|
|
48
|
+
else try{localStorage.setItem('lilgotchi:scanlines',String(value));}catch{}
|
|
49
|
+
},eink:petType==='classic'&&eink,onThemeChange:value=>{
|
|
50
|
+
applyColors();
|
|
51
|
+
if(channel){channel.postMessage({type:'preference',name:'eink',value});publishConfiguration();}
|
|
52
|
+
else try{localStorage.setItem('lilgotchi:eink',String(value));}catch{}
|
|
53
|
+
}});
|
|
54
|
+
applyColors();
|
|
55
|
+
if(!muted) window.app.handleAction('mute');
|
|
56
|
+
document.getElementById('mute-btn').addEventListener('click',()=>{channel?.postMessage({type:'preference',name:'muted',value:window.app.sound.muted});publishConfiguration();});
|
|
57
|
+
window.app.state.save();
|
|
58
|
+
if(channel) {
|
|
59
|
+
window.app.render();
|
|
60
|
+
channel.postMessage({type:'ready'});
|
|
61
|
+
configurationReady=true;
|
|
62
|
+
publishConfiguration();
|
|
63
|
+
setInterval(()=>channel.postMessage({type:'care-status',state:JSON.parse(JSON.stringify(window.app.state))}),5000);
|
|
64
|
+
let lastHeight=0;
|
|
65
|
+
const resize=()=>{
|
|
66
|
+
const device=document.getElementById('device');
|
|
67
|
+
const overlay=document.querySelector('.overlay.show');
|
|
68
|
+
let height=Math.ceil(device.getBoundingClientRect().height+24);
|
|
69
|
+
if(overlay) {
|
|
70
|
+
height=Math.max(height,overlayContentHeight(overlay)+12);
|
|
71
|
+
}
|
|
72
|
+
height=Math.min(1200,Math.max(100,height));
|
|
73
|
+
if(height!==lastHeight){lastHeight=height;channel.postMessage({type:'resize',height});}
|
|
74
|
+
};
|
|
75
|
+
const sizes=new ResizeObserver(resize);
|
|
76
|
+
sizes.observe(document.getElementById('device'));
|
|
77
|
+
for(const overlay of document.querySelectorAll('.overlay')) {
|
|
78
|
+
sizes.observe(overlay);
|
|
79
|
+
for(const child of overlay.children)sizes.observe(child);
|
|
80
|
+
}
|
|
81
|
+
new MutationObserver(resize).observe(document.body,{subtree:true,childList:true,attributes:true,attributeFilter:['class','hidden']});
|
|
82
|
+
resize();
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
if(embedded) {
|
|
86
|
+
const expectedOrigin=params.get('parentOrigin');
|
|
87
|
+
const nonce=params.get('nonce');
|
|
88
|
+
const connect=event=>{
|
|
89
|
+
if(event.source!==parent || event.origin!==expectedOrigin || event.data?.type!=='lilgotchi:connect' || event.data.nonce!==nonce || !event.ports[0] || channel) return;
|
|
90
|
+
window.removeEventListener('message',connect);
|
|
91
|
+
channel=event.ports[0];
|
|
92
|
+
channel.onmessage=event=>{
|
|
93
|
+
const message=event.data;
|
|
94
|
+
if(!message || typeof message.type!=='string') return;
|
|
95
|
+
if(message.type==='colors'){customColors=cleanColors(message.colors);applyColors();}
|
|
96
|
+
if(message.type==='configure' && window.app){
|
|
97
|
+
const id=configurationActionId(message.action,petType==='classic');
|
|
98
|
+
if(id){document.getElementById(id)?.click();publishConfiguration();}
|
|
99
|
+
}
|
|
100
|
+
if(message.type==='snapshot' && Number.isSafeInteger(message.id) && window.app) {
|
|
101
|
+
if(message.closing){window.app.arcade?.pause();window.app.interruptGuess();}
|
|
102
|
+
channel.postMessage({type:'snapshot',id:message.id,state:JSON.parse(JSON.stringify(window.app.state))});
|
|
103
|
+
}
|
|
104
|
+
if(message.type==='visibility' && typeof message.visible==='boolean' && window.app){
|
|
105
|
+
window.app.panelVisible=message.visible;
|
|
106
|
+
if(!message.visible){window.app.arcade?.pause();window.app.interruptGuess();}
|
|
107
|
+
window.app.sound.suspended=!message.visible;
|
|
108
|
+
if(message.visible){window.app.processMissedTime();window.app.updateButtons();}
|
|
109
|
+
else {
|
|
110
|
+
window.app.interruptGuess();
|
|
111
|
+
const overlay=document.querySelector('.overlay.show');
|
|
112
|
+
if(overlay && overlay.id!=='death-overlay' && overlay.id!=='arcade-overlay')window.app.closeOverlay(overlay.id);
|
|
113
|
+
window.app.state.save();
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
if(message.type==='save-status') document.getElementById('save-status').textContent=String(message.text||'').slice(0,160);
|
|
117
|
+
};
|
|
118
|
+
customColors=cleanColors(event.data.colors);
|
|
119
|
+
const initial=event.data.state;
|
|
120
|
+
start(validState(initial)?initial:null,event.data.muted!==false,event.data.eink===true,event.data.scanlines===true);
|
|
121
|
+
};
|
|
122
|
+
window.addEventListener('message',connect);
|
|
123
|
+
} else {
|
|
124
|
+
let eink=false,scanlines=false;
|
|
125
|
+
try{eink=localStorage.getItem('lilgotchi:eink')==='true';scanlines=localStorage.getItem('lilgotchi:scanlines')==='true';}catch{}
|
|
126
|
+
start(null,true,eink,scanlines);
|
|
127
|
+
}
|
|
128
|
+
window.addEventListener('pagehide',()=>window.app?.state.save());
|
|
129
|
+
window.restorePetSession=data=>{
|
|
130
|
+
if(!window.app || !validState(data)) return;
|
|
131
|
+
window.app.cancelGame();
|
|
132
|
+
window.app.state.vacationAt=data.vacationAt||0;
|
|
133
|
+
for(const key of Object.keys(window.app.state)) if(Object.hasOwn(data,key)) window.app.state[key]=data[key];
|
|
134
|
+
window.app.processMissedTime(); window.app.updateButtons();
|
|
135
|
+
};
|
|
136
|
+
document.addEventListener('keydown',event=>{
|
|
137
|
+
const overlay=document.querySelector('.overlay.show');
|
|
138
|
+
if(event.key==='Escape') {
|
|
139
|
+
event.preventDefault();
|
|
140
|
+
if(overlay && overlay.id!=='death-overlay' && overlay.id!=='arcade-overlay') {
|
|
141
|
+
window.app.gameActive=false; window.app.closeOverlay(overlay.id);
|
|
142
|
+
} else if(channel) channel.postMessage({type:'close'});
|
|
143
|
+
else if(overlay?.id==='arcade-overlay')window.app.closeOverlay(overlay.id);
|
|
144
|
+
}
|
|
145
|
+
if(overlay)trapOverlayTab(event,overlay);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
// Frames cannot naturally chain a gesture to their containing drawer.
|
|
149
|
+
const scrollHost=delta=>channel?.postMessage({type:'scroll',delta:Math.max(-1000,Math.min(1000,delta))});
|
|
150
|
+
document.addEventListener('wheel',event=>{
|
|
151
|
+
if(!channel || event.ctrlKey)return;
|
|
152
|
+
event.preventDefault();
|
|
153
|
+
scrollHost(event.deltaY*(event.deltaMode===1?16:event.deltaMode===2?innerHeight:1));
|
|
154
|
+
},{passive:false});
|
|
155
|
+
let touchY=null;
|
|
156
|
+
document.addEventListener('touchstart',event=>{touchY=event.touches.length===1?event.touches[0].clientY:null;},{passive:true});
|
|
157
|
+
document.addEventListener('touchmove',event=>{
|
|
158
|
+
if(!channel || touchY===null || event.touches.length!==1)return;
|
|
159
|
+
const next=event.touches[0].clientY;
|
|
160
|
+
event.preventDefault();scrollHost(touchY-next);touchY=next;
|
|
161
|
+
},{passive:false});
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// Public choices share one identity map across the host and game frame.
|
|
2
|
+
export const catalog=Object.freeze([
|
|
3
|
+
{id:'slice',name:'Slice',skin:'pizza',character:'egg',stage:'egg'},
|
|
4
|
+
{id:'cyberpunk',name:'Cypunk',skin:'cyberpunk',character:'egg',stage:'egg'},
|
|
5
|
+
{id:'lilgotchi',name:'Lilgotchi',skin:'lilgotchi',character:'egg',stage:'egg'},
|
|
6
|
+
{id:'default',name:'Gen 1',skin:'classic',character:'egg',stage:'egg'},
|
|
7
|
+
{id:'original2',name:'Gen 2',skin:'classic',character:'egg',stage:'egg'},
|
|
8
|
+
].map(Object.freeze));
|
|
9
|
+
export const families=Object.freeze(catalog.map(choice=>choice.id));
|
|
10
|
+
export const legacyFamily=Object.freeze(Object.fromEntries(catalog.map(choice=>[choice.id,choice.skin])));
|
|
11
|
+
export const getCharacter=id=>catalog.find(choice=>choice.id===id);
|
|
12
|
+
export function allowedCharacters(value=null){
|
|
13
|
+
if(value===null || value.trim()==='all')return [...families];
|
|
14
|
+
const ids=[...new Set(value.trim().split(/[\s,]+/).filter(Boolean))];
|
|
15
|
+
if(!ids.length || ids.some(id=>!families.includes(id)))throw new TypeError('Invalid characters setting. Use all or a list of available character IDs.');
|
|
16
|
+
return ids;
|
|
17
|
+
}
|
|
18
|
+
export function requireAllowed(id,allowed){
|
|
19
|
+
if(!families.includes(id))throw new TypeError('Unknown character');
|
|
20
|
+
if(!allowed.includes(id))throw new TypeError('This character is not available on this site.');
|
|
21
|
+
return id;
|
|
22
|
+
}
|
|
23
|
+
export const initialCharacter=(saved,requested,allowed)=>saved ? (allowed.includes(saved)?saved:allowed[0]) : (allowed.includes(requested)?requested:allowed[0]);
|
|
24
|
+
export function frameCharacter(params){
|
|
25
|
+
const id=params.get('character');
|
|
26
|
+
if(id!==null){
|
|
27
|
+
const choice=getCharacter(id);
|
|
28
|
+
if(!choice || (params.has('pet') && params.get('pet')!==choice.skin))throw new TypeError('Invalid game character configuration');
|
|
29
|
+
return choice;
|
|
30
|
+
}
|
|
31
|
+
return getCharacter({classic:'default',pizza:'slice',cyberpunk:'cyberpunk',lilgotchi:'lilgotchi'}[params.get('pet')]||'slice');
|
|
32
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { characterName } from './skins.js';
|
|
2
|
+
|
|
3
|
+
const stages=new Set(['baby','child','teen','adult']);
|
|
4
|
+
export function growthEvent(previous,next) {
|
|
5
|
+
if(!previous||!next||next.isDead||!stages.has(next.stage))return null;
|
|
6
|
+
if(previous.character===next.character&&previous.stage===next.stage)return null;
|
|
7
|
+
return {
|
|
8
|
+
key:`${next.bornAt}:${next.stageStartTime}:${next.character}`,
|
|
9
|
+
character:next.character,
|
|
10
|
+
stage:next.stage,
|
|
11
|
+
hatched:previous.stage==='egg',
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// The card is outside the LCD and never steals focus or dismisses itself.
|
|
16
|
+
export class GrowthCelebration {
|
|
17
|
+
constructor(element,{nameFor=characterName}={}) {
|
|
18
|
+
this.element=element;
|
|
19
|
+
this.nameFor=nameFor;
|
|
20
|
+
this.dismissed=new Set();
|
|
21
|
+
this.current=null;
|
|
22
|
+
}
|
|
23
|
+
observe(previous,next) {
|
|
24
|
+
const event=growthEvent(previous,next);
|
|
25
|
+
if(event)this.show(event);
|
|
26
|
+
return event;
|
|
27
|
+
}
|
|
28
|
+
show(event) {
|
|
29
|
+
if(!event||this.dismissed.has(event.key)||this.current?.key===event.key||!this.element)return;
|
|
30
|
+
this.current=event;
|
|
31
|
+
const doc=this.element.ownerDocument;
|
|
32
|
+
const text=doc.createElement('p');
|
|
33
|
+
text.className='growth-message';
|
|
34
|
+
text.setAttribute('role','status');
|
|
35
|
+
text.textContent=`${event.hatched?'Just hatched':'A little growth'}! Meet ${this.nameFor(event.character)}, your ${event.stage} pet.`;
|
|
36
|
+
const close=doc.createElement('button');
|
|
37
|
+
close.type='button';close.textContent='Got it';
|
|
38
|
+
close.setAttribute('aria-label','Dismiss growth update');
|
|
39
|
+
close.addEventListener('click',()=>this.dismiss());
|
|
40
|
+
this.element.replaceChildren(text,close);
|
|
41
|
+
this.element.classList.add('growth-card');
|
|
42
|
+
this.element.hidden=false;
|
|
43
|
+
}
|
|
44
|
+
dismiss() {
|
|
45
|
+
if(this.current)this.dismissed.add(this.current.key);
|
|
46
|
+
if(this.element)this.element.hidden=true;
|
|
47
|
+
this.current=null;
|
|
48
|
+
}
|
|
49
|
+
clear() {
|
|
50
|
+
if(this.element){this.element.hidden=true;this.element.replaceChildren();}
|
|
51
|
+
this.current=null;
|
|
52
|
+
this.dismissed.clear();
|
|
53
|
+
}
|
|
54
|
+
}
|