| 1 |
(() => {
|
| 2 |
: class="tok-val">cambia lo que quieras
|
| 3 |
class="tok-kw">const CONFIG = {
|
| 4 |
class="tok-prop">dulce: class="tok-val">"🍬", |
| 5 |
class="tok-prop">fantasma: class="tok-val">"👻", |
| 6 |
class="tok-prop">tamanoDulce: class="tok-val">22,
|
| 7 |
class="tok-prop">tamanoFantasma: class="tok-val">40,
|
| 8 |
class="tok-prop">velocidad: class="tok-val">0.05, |
| 9 |
class="tok-prop">bocados: class="tok-val">5,
|
| 10 |
class="tok-prop">miraALaIzquierda: class="tok-val">true, |
| 11 |
class="tok-prop">segundosParaDormir: class="tok-val">5,
|
| 12 |
class="tok-prop">huellas: class="tok-val">"🦇", |
| 13 |
class="tok-prop">filtroHuellas: class="tok-val">"grayscale(1) brightness(.4)",
|
| 14 |
class="tok-prop">explosion: class="tok-val">"🎃", |
| 15 |
class="tok-prop">distanciaBoca: class="tok-val">4, |
| 16 |
class="tok-prop">inicial: class="tok-val">3, |
| 17 |
class="tok-prop">porClic: class="tok-val">5, |
| 18 |
class="tok-prop">maximo: class="tok-val">20, |
| 19 |
};
|
| 20 |
|
| 21 |
|
| 22 |
class="tok-kw">if (matchMedia("(class="tok-prop">hover: class="tok-val">none)").matches) class="tok-kw">return;
|
| 23 |
class="tok-kw">if (matchMedia("(class="tok-prop">prefers-reduced-motion: class="tok-val">reduce)").matches) class="tok-kw">return;
|
| 24 |
|
| 25 |
|
| 26 |
class="tok-kw">const style = document.createElement("style");
|
| 27 |
style.textContent = `
|
| 28 |
*, *::before, *::after { class="tok-prop">cursor: class="tok-val">none !important; }
|
| 29 |
.cc-item { class="tok-prop">position: class="tok-val">fixed; class="tok-prop">top: class="tok-val">0; class="tok-prop">left: class="tok-val">0; class="tok-prop">pointer-events: class="tok-val">none; class="tok-prop">z-index: class="tok-val">99999; }
|
| 30 |
#cc-dulce { class="tok-prop">font-size: class="tok-val">${CONFIG.tamanoDulce}px; class="tok-prop">z-index: class="tok-val">100000; }
|
| 31 |
#cc-fantasma { class="tok-prop">font-size: class="tok-val">${CONFIG.tamanoFantasma}px; }
|
| 32 |
.cc-item span { class="tok-prop">display: class="tok-val">inline-block; }
|
| 33 |
.cc-fx { class="tok-prop">position: class="tok-val">fixed; class="tok-prop">pointer-events: class="tok-val">none; class="tok-prop">z-index: class="tok-val">99998; class="tok-prop">font-size: class="tok-val">18px; }
|
| 34 |
#cc-fantasma.moving .flota { class="tok-prop">animation: class="tok-val">cc-flota .6s ease-in-out infinite; }
|
| 35 |
#cc-fantasma.eating .flota { class="tok-prop">animation: class="tok-val">cc-munch .2s ease-in-out infinite; }
|
| 36 |
#cc-fantasma.sleeping .flota { class="tok-prop">transform: class="tok-val">scale(1, .8) translateY(4px); class="tok-prop">opacity: class="tok-val">.7; }
|
| 37 |
#cc-fantasma.jump .flota { class="tok-prop">animation: class="tok-val">cc-big .5s ease-out; }
|
| 38 |
#cc-counter { class="tok-prop">position: class="tok-val">fixed; class="tok-prop">top: class="tok-val">12px; class="tok-prop">right: class="tok-val">16px; class="tok-prop">font: class="tok-val">700 18px system-ui, sans-serif; class="tok-prop">z-index: class="tok-val">100001; class="tok-prop">pointer-events: class="tok-val">none;
|
| 39 |
class="tok-prop">color: class="tok-val">#fff; class="tok-prop">background: class="tok-val">rgba(0,0,0,.6); class="tok-prop">padding: class="tok-val">4px 12px; class="tok-prop">border-radius: class="tok-val">999px; }
|
| 40 |
#cc-dulce.empty { class="tok-prop">opacity: class="tok-val">0; }
|
| 41 |
#cc-fantasma.hungry .flota { class="tok-prop">opacity: class="tok-val">.45; }
|
| 42 |
@keyframes cc-flota { 50% { class="tok-prop">transform: class="tok-val">translateY(-12px) rotate(-8deg); } }
|
| 43 |
@keyframes cc-munch { 50% { class="tok-prop">transform: class="tok-val">scale(1.15, .9); } }
|
| 44 |
@keyframes cc-big { 40% { class="tok-prop">transform: class="tok-val">translateY(-50px) rotate(-20deg); } }
|
| 45 |
@keyframes cc-fade { to { class="tok-prop">opacity: class="tok-val">0; } }
|
| 46 |
@keyframes cc-zzz { to { class="tok-prop">opacity: class="tok-val">0; class="tok-prop">transform: class="tok-val">translate(14px, -40px); } }
|
| 47 |
@keyframes cc-pop { to { class="tok-prop">opacity: class="tok-val">0; class="tok-prop">transform: class="tok-val">translate(class="tok-kw">var(--dx), class="tok-kw">var(--dy)); } }
|
| 48 |
`;
|
| 49 |
document.head.appendChild(style);
|
| 50 |
|
| 51 |
|
| 52 |
class="tok-kw">const dulce = document.createElement("div");
|
| 53 |
class="tok-kw">const fantasma = document.createElement("div");
|
| 54 |
dulce.className = fantasma.className = "cc-item";
|
| 55 |
dulce.id = "cc-dulce";
|
| 56 |
fantasma.id = "cc-fantasma";
|
| 57 |
dulce.innerHTML = `class="tok-tag"><spanclass="tok-attr">class="tok-tag">>${CONFIG.dulce}class="tok-tag"></spanclass="tok-attr">class="tok-tag">>`;
|
| 58 |
fantasma.innerHTML = `class="tok-tag"><spanclass="tok-attr"> class="flota"class="tok-tag">>${CONFIG.fantasma}class="tok-tag"></spanclass="tok-attr">class="tok-tag">>`;
|
| 59 |
document.body.append(dulce, fantasma);
|
| 60 |
class="tok-kw">const trozo = dulce.firstChild;
|
| 61 |
|
| 62 |
|
| 63 |
class="tok-kw">const contador = document.createElement("div");
|
| 64 |
contador.id = "cc-counter";
|
| 65 |
document.body.appendChild(contador);
|
| 66 |
|
| 67 |
class="tok-kw">let dulces = CONFIG.inicial;
|
| 68 |
class="tok-kw">const actualizarContador = () => {
|
| 69 |
contador.textContent = `${CONFIG.dulce} x${dulces}`;
|
| 70 |
dulce.classList.toggle("empty", dulces === 0);
|
| 71 |
fantasma.classList.toggle("hungry", dulces === 0);
|
| 72 |
};
|
| 73 |
actualizarContador();
|
| 74 |
|
| 75 |
|
| 76 |
class="tok-kw">function fx(text, x, y, anim, extra = "") {
|
| 77 |
class="tok-kw">const el = document.createElement("div");
|
| 78 |
el.className = "cc-fx";
|
| 79 |
el.textContent = text;
|
| 80 |
el.style.cssText = `class="tok-prop">left:class="tok-val">${x}px; class="tok-prop">top:class="tok-val">${y}px; class="tok-prop">animation:class="tok-val">${anim}; ${extra}`;
|
| 81 |
el.addEventListener("animationend", () => el.remove());
|
| 82 |
document.body.appendChild(el);
|
| 83 |
}
|
| 84 |
|
| 85 |
class="tok-kw">let mx = innerWidth / 2, my = innerHeight / 2;
|
| 86 |
class="tok-kw">let ox = mx - 200, oy = my; |
| 87 |
class="tok-kw">let bocadosDados = 0, ultimoBocado = 0, ultimaZ = 0, caminado = 0;
|
| 88 |
class="tok-kw">let ultimoMovimiento = performance.now();
|
| 89 |
|
| 90 |
class="tok-kw">const ponerDulce = () =>
|
| 91 |
(dulce.style.transform = `translate(${mx}px, ${my}px) translate(-50%, -50%)`);
|
| 92 |
ponerDulce();
|
| 93 |
|
| 94 |
class="tok-kw">function jump() {
|
| 95 |
fantasma.classList.remove("jump");
|
| 96 |
void fantasma.offsetWidth; |
| 97 |
fantasma.classList.add("jump");
|
| 98 |
setTimeout(() => fantasma.classList.remove("jump"), 500);
|
| 99 |
}
|
| 100 |
|
| 101 |
addEventListener("mousemove", (e) => {
|
| 102 |
mx = e.clientX; my = e.clientY;
|
| 103 |
ultimoMovimiento = performance.now();
|
| 104 |
ponerDulce();
|
| 105 |
class="tok-kw">if (fantasma.classList.contains("sleeping")) jump(); |
| 106 |
});
|
| 107 |
|
| 108 |
addEventListener("click", (e) => {
|
| 109 |
jump();
|
| 110 |
class="tok-kw">if (dulces === 0) { bocadosDados = 0; trozo.style.clipPath = "none"; }
|
| 111 |
dulces = Math.min(dulces + CONFIG.porClic, CONFIG.maximo);
|
| 112 |
actualizarContador();
|
| 113 |
class="tok-kw">for (class="tok-kw">let i = 0; i < CONFIG.porClic; i++) {
|
| 114 |
class="tok-kw">const a = (Math.PI * 2 * i) / CONFIG.porClic;
|
| 115 |
fx(CONFIG.explosion, e.clientX, e.clientY, "cc-pop .8s ease-out forwards",
|
| 116 |
`class="tok-prop">--dx:class="tok-val">${Math.cos(a) * 50}px; class="tok-prop">--dy:class="tok-val">${Math.sin(a) * 50}px;`);
|
| 117 |
}
|
| 118 |
});
|
| 119 |
|
| 120 |
class="tok-kw">function loop(t) {
|
| 121 |
class="tok-kw">const dx = mx - ox, dy = my - oy;
|
| 122 |
class="tok-kw">const dist = Math.hypot(dx, dy);
|
| 123 |
class="tok-kw">const cerca = dist < CONFIG.distanciaBoca + 15;
|
| 124 |
|
| 125 |
|
| 126 |
class="tok-kw">const d = dist || 1;
|
| 127 |
class="tok-kw">const tx = mx - (dx / d) * CONFIG.distanciaBoca;
|
| 128 |
class="tok-kw">const ty = my - (dy / d) * CONFIG.distanciaBoca;
|
| 129 |
ox += (tx - ox) * CONFIG.velocidad;
|
| 130 |
oy += (ty - oy) * CONFIG.velocidad;
|
| 131 |
|
| 132 |
class="tok-kw">const quieto = t - ultimoMovimiento > CONFIG.segundosParaDormir * 1000;
|
| 133 |
class="tok-kw">const durmiendo = quieto && cerca;
|
| 134 |
class="tok-kw">const comiendo = cerca && !durmiendo && dulces > 0 && bocadosDados < CONFIG.bocados;
|
| 135 |
|
| 136 |
fantasma.classList.toggle("sleeping", durmiendo);
|
| 137 |
fantasma.classList.toggle("eating", comiendo);
|
| 138 |
fantasma.classList.toggle("moving", !cerca);
|
| 139 |
|
| 140 |
|
| 141 |
class="tok-kw">if (!cerca) {
|
| 142 |
caminado += Math.hypot(tx - ox, ty - oy) * CONFIG.velocidad;
|
| 143 |
class="tok-kw">if (caminado > 45) {
|
| 144 |
caminado = 0;
|
| 145 |
fx(CONFIG.huellas, ox, oy + 14, "cc-fade 1.5s forwards",
|
| 146 |
`class="tok-prop">transform: class="tok-val">translate(-50%, -50%) rotate(${Math.random() * 40 - 20}deg); class="tok-prop">filter: class="tok-val">${CONFIG.filtroHuellas};`);
|
| 147 |
}
|
| 148 |
}
|
| 149 |
|
| 150 |
|
| 151 |
class="tok-kw">if (durmiendo && t - ultimaZ > 900) {
|
| 152 |
ultimaZ = t;
|
| 153 |
fx("z", ox + 10, oy - 20, "cc-zzz 1.5s forwards");
|
| 154 |
}
|
| 155 |
|
| 156 |
|
| 157 |
class="tok-kw">if (comiendo && t - ultimoBocado > 450) {
|
| 158 |
bocadosDados++;
|
| 159 |
ultimoBocado = t;
|
| 160 |
trozo.style.clipPath = `inset(0 0 0 ${(bocadosDados / CONFIG.bocados) * 100}%)`;
|
| 161 |
class="tok-kw">if (bocadosDados >= CONFIG.bocados) {
|
| 162 |
dulces--;
|
| 163 |
actualizarContador();
|
| 164 |
class="tok-kw">if (dulces > 0) {
|
| 165 |
setTimeout(() => { bocadosDados = 0; trozo.style.clipPath = "none"; }, 400);
|
| 166 |
}
|
| 167 |
}
|
| 168 |
}
|
| 169 |
|
| 170 |
|
| 171 |
class="tok-kw">if (dulces === 0 && t - ultimaZ > 1200) {
|
| 172 |
ultimaZ = t;
|
| 173 |
fx(`${CONFIG.dulce}?`, ox + 10, oy - 24, "cc-zzz 1.5s forwards");
|
| 174 |
}
|
| 175 |
|
| 176 |
class="tok-kw">const flip = (mx < ox) === CONFIG.miraALaIzquierda ? 1 : -1;
|
| 177 |
fantasma.style.transform =
|
| 178 |
`translate(${ox}px, ${oy}px) translate(-50%, -50%) scaleX(${flip})`;
|
| 179 |
requestAnimationFrame(loop);
|
| 180 |
}
|
| 181 |
requestAnimationFrame(loop);
|
| 182 |
})(); |