(function(){ if (window.__denCritter) return; window.__denCritter = true; var st = document.createElement('style'); st.textContent = '.paw-trail{position:fixed;pointer-events:none;z-index:9999;animation:pawfade 1s linear forwards;filter:drop-shadow(0 0 3px currentColor);}' + '@keyframes pawfade{0%{opacity:.9;transform:scale(1);}100%{opacity:0;transform:scale(.5) translateY(10px);}}' + '.emote{position:fixed;pointer-events:none;z-index:9999;font-size:15px;animation:emoterise 1.2s ease-out forwards;}' + '.emote.say{font-size:12px;font-weight:bold;color:#c9b8ff;text-shadow:0 0 6px #8f7bff,1px 1px 0 #120a20;white-space:nowrap;font-family:"Comic Sans MS","Comic Sans",cursive;}' + '@keyframes emoterise{0%{opacity:0;transform:translateY(0) scale(.6);}15%{opacity:1;}100%{opacity:0;transform:translateY(-34px) scale(1.15);}}' + '.denbed{position:fixed;z-index:9998;pointer-events:none;animation:bedin .6s ease-out;transition:opacity .45s ease;}' + '@keyframes bedin{from{opacity:0;transform:translateY(10px);}to{opacity:1;transform:translateY(0);}}' + '.denbed .lump{animation:breathe 2.8s ease-in-out infinite;transform-origin:38px 24px;}' + '@keyframes breathe{0%,100%{transform:scaleY(1);}50%{transform:scaleY(1.17);}}' + 'body.denzoom *,body.denzoom *::before,body.denzoom *::after{animation-duration:.22s !important;}' + '.denhole{position:fixed;z-index:9997;pointer-events:none;transition:opacity .55s ease;animation:holein .5s ease-out;}' + '@keyframes holein{from{opacity:0;transform:scale(.6);}to{opacity:1;transform:scale(1);}}' + '.dentab{position:fixed;right:14px;bottom:0;text-align:left;z-index:10000;background:#1b1229;color:#8f7bff;border:2px ridge #6b5a8a;border-bottom:none;border-radius:8px 8px 0 0;font-family:"Courier New",monospace;font-size:11px;padding:3px 10px;cursor:pointer;opacity:.55;transition:opacity .2s;user-select:none;}' + '.dentab:hover{opacity:1;}' + '.denterm{position:fixed;right:8px;bottom:0;z-index:10000;width:300px;max-width:86vw;background:#0d0818;border:3px ridge #6b5a8a;border-bottom:none;border-radius:10px 10px 0 0;font-family:"Courier New",monospace;font-size:12px;padding:8px;display:none;text-align:left;}' + '.denterm.open{display:block;}' + '.denterm .dout{height:170px;overflow-y:auto;white-space:pre-wrap;word-break:break-word;color:#c9b8ff;}' + '.denterm .dline{display:flex;gap:6px;margin-top:5px;align-items:center;}' + '.denterm .dprompt{color:#ff6ec7;white-space:nowrap;}' + '.denterm input{flex:1;background:transparent;border:none;outline:none;color:#a8ffe0;font-family:inherit;font-size:12px;}' + '.denwx{position:fixed;top:0;left:0;right:0;bottom:0;pointer-events:none;z-index:9996;overflow:hidden;}' + '.denwx i{position:absolute;top:-24px;display:block;animation-name:denfall;animation-timing-function:linear;animation-fill-mode:forwards;}' + '.denwx i.r{width:2px;height:15px;background:linear-gradient(180deg,rgba(79,195,255,0),rgba(79,195,255,.85));border-radius:1px;}' + '.denwx i.s{color:#fff;font-size:12px;line-height:1;}' + '@keyframes denfall{from{transform:translateY(0);}to{transform:translateY(104vh);}}' + '.dentreat{position:fixed;z-index:9997;pointer-events:none;font-size:19px;line-height:1;animation:denbob 1.3s ease-in-out infinite;filter:drop-shadow(0 0 4px #ffb3de);}' + '@keyframes denbob{0%,100%{transform:translateY(0);}50%{transform:translateY(-5px);}}'; document.head.appendChild(st); var actx = null; function yipSound(){ try { if (!actx) actx = new (window.AudioContext || window.webkitAudioContext)(); if (actx.state === 'suspended') actx.resume(); var o = actx.createOscillator(), g = actx.createGain(); var t = actx.currentTime; o.type = 'sine'; o.frequency.setValueAtTime(700 + Math.random() * 200, t); o.frequency.exponentialRampToValueAtTime(1400 + Math.random() * 300, t + 0.07); o.frequency.exponentialRampToValueAtTime(600, t + 0.14); g.gain.setValueAtTime(0.12, t); g.gain.exponentialRampToValueAtTime(0.001, t + 0.16); o.connect(g); g.connect(actx.destination); o.start(t); o.stop(t + 0.18); } catch(e){} } window.denYip = yipSound; if (window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches) { window.denSay = function(){}; return; } var PAW = '' + '' + '' + '' + '' + '' + ''; var colors = ['#8f7bff', '#4fc3ff']; var spawnEdge = Math.floor(Math.random() * 4); var sx, sy; if (spawnEdge === 0) { sx = 70; sy = 80 + Math.random() * Math.max(window.innerHeight - 160, 1); } else if (spawnEdge === 1) { sx = window.innerWidth - 70; sy = 80 + Math.random() * Math.max(window.innerHeight - 160, 1); } else if (spawnEdge === 2) { sx = 80 + Math.random() * Math.max(window.innerWidth - 160, 1); sy = 70; } else { sx = 80 + Math.random() * Math.max(window.innerWidth - 160, 1); sy = window.innerHeight - 70; } var pos = { x: sx, y: sy }; var heading = Math.random() * Math.PI * 2; var target = { x: pos.x, y: pos.y }; var mode = 'rest'; var restUntil = performance.now() + 800; var petUntil = 0; var nextZzz = performance.now() + 99999; var lastEmote = 0; var side = 1, distSince = 0; var lastT = performance.now(); var WALK = 70, TROT = 150, RUN = 340; var STRIDE_WALK = 24, STRIDE_RUN = 38; var TURN = 4.0; var SLEEPY_AFTER = 600000; var lastActivity = performance.now(); var sleepPrepUntil = 0, wakeUntil = 0, suppressBumpUntil = 0; var asleep = false, bedEl = null, nextSleepZ = 0; var speedMult = 1; var PAW_LIFE = 1300; var weather = 'clear', wxTimer = null, wxLayer = null; var shelterPt = null, nextSulk = 0; var treatEl = null, treatPt = null; var fedAt = performance.now(); var lastScrollY = window.scrollY || window.pageYOffset || 0; var windedUntil = 0; var poiSay = null, lastPoi = null; var errandTarget = null, errandSay = null, errandRun = false, errandDeadline = 0; var mis = null, lastMischief = 0; var dout = null, dinp = null, dpr = null; var mem = { v: 0, pets: 0, treats: 0, name: null, last: 0, id: null }; try { var m0 = JSON.parse(localStorage.getItem('denmem')); if (m0 && typeof m0 === 'object') { mem.v = m0.v | 0; mem.pets = m0.pets | 0; mem.treats = m0.treats | 0; mem.name = (typeof m0.name === 'string' && m0.name) ? m0.name.slice(0, 16) : null; mem.last = m0.last || 0; if (typeof m0.id === 'string' && /^[a-f0-9]{16}$/.test(m0.id)) mem.id = m0.id; } } catch(e){} var prevLast = mem.last; mem.v++; mem.last = Date.now(); if (!mem.id) { var idb = ''; try { var ida = new Uint8Array(8); crypto.getRandomValues(ida); for (var ii = 0; ii < 8; ii++) idb += ('0' + ida[ii].toString(16)).slice(-2); } catch(e) { for (var ij = 0; ij < 16; ij++) idb += Math.floor(Math.random() * 16).toString(16); } mem.id = idb; } function saveMem(){ try { localStorage.setItem('denmem', JSON.stringify(mem)); } catch(e){} } saveMem(); function trust(){ return mem.pets + mem.treats; } function hungry(){ return performance.now() - fedAt > 240000; } function greeting(){ var away = prevLast ? (Date.now() - prevLast) : 0; if (mem.v <= 1) return '*yip* (hello)'; if (away > 604800000) return 'YIP!! (where were u)'; if (mem.name) return '*yip* (' + mem.name + ' missed u)'; if (mem.v >= 8) return 'yip (welcome home)'; return 'yip!! (u came back)'; } window.denSetName = function(n){ n = String(n || '').replace(/[^\w \-]/g, '').trim().slice(0, 16); if (!n) return null; mem.name = n; saveMem(); sayText('yip!! (i am ' + n + ')'); emote('\uD83D\uDC96', true); if (dpr) dpr.textContent = promptTxt(); return n; }; window.denGetName = function(){ return mem.name; }; function promptTxt(){ return (mem.name || 'guest') + '@den ~>'; } function denPhrase(n){ if (n <= 0) return 'just u + the kobold rn'; if (n === 1) return '1 other is here rn'; return n + ' others are here rn'; } function checkPresence(){ if (!window.fetch) return; fetch('/den.php?id=' + mem.id, { cache: 'no-store' }).then(function(r){ if (!r.ok) return null; return r.json(); }).then(function(j){ if (!j) return; var n = j.others | 0; window.denOthers = n; var els = document.querySelectorAll('[data-den-others]'); for (var i = 0; i < els.length; i++) els[i].textContent = denPhrase(n); }).catch(function(){}); } function pois(){ var out = []; var els = document.querySelectorAll('[data-den-poi]'); for (var i = 0; i < els.length; i++) { var r = els[i].getBoundingClientRect(); if (r.width < 10 || r.height < 10) continue; if (r.bottom < 40 || r.top > window.innerHeight - 40) continue; if (r.right < 40 || r.left > window.innerWidth - 40) continue; out.push({ el: els[i], r: r }); } return out; } function emote(ch, force){ var now = performance.now(); if (!force && now - lastEmote < 500) return; lastEmote = now; var el = document.createElement('span'); el.className = 'emote'; el.textContent = ch; el.style.left = (pos.x + (Math.random() * 16 - 8)) + 'px'; el.style.top = (pos.y - 18) + 'px'; document.body.appendChild(el); setTimeout(function(){ el.remove(); }, 1250); } function pick(arr){ return arr[Math.floor(Math.random() * arr.length)]; } function sayText(t){ var el = document.createElement('span'); el.className = 'emote say'; el.textContent = t; el.style.left = (pos.x - 10) + 'px'; el.style.top = (pos.y - 22) + 'px'; document.body.appendChild(el); setTimeout(function(){ el.remove(); }, 1250); } window.denSay = sayText; function makeBed(){ bedEl = document.createElement('div'); bedEl.className = 'denbed'; bedEl.innerHTML = '' + '' + '' + '' + '' + '' + ''; bedEl.style.left = (pos.x - 32) + 'px'; bedEl.style.top = (pos.y - 26) + 'px'; document.body.appendChild(bedEl); } function bump(){ var now = performance.now(); if (now < suppressBumpUntil) return; lastActivity = now; if (sleepPrepUntil) sleepPrepUntil = 0; if (asleep) { asleep = false; if (bedEl) { var b = bedEl; bedEl = null; b.style.opacity = '0'; setTimeout(function(){ b.remove(); }, 500); } emote('\u2728', true); sayText('...yip?'); wakeUntil = now + 900; } } window.addEventListener('scroll', function(){ var y = window.scrollY || window.pageYOffset || 0; var delta = y - lastScrollY; lastScrollY = y; if (asleep || sleepPrepUntil || !delta) return; pos.y -= delta; var pushed = false; if (pos.y < 18) { pos.y = 18; pushed = true; } else if (pos.y > window.innerHeight - 18) { pos.y = window.innerHeight - 18; pushed = true; } if (pushed && Math.abs(delta) > 45) windedUntil = performance.now() + 2500; if (treatEl && treatPt) { treatPt.y -= delta; treatEl.style.top = (treatPt.y - 10) + 'px'; if (treatPt.y < 0 || treatPt.y > window.innerHeight) { treatEl.remove(); treatEl = null; treatPt = null; if (mode === 'treat') { mode = 'wander'; target = pickWander(); } } } if (shelterPt) shelterPt.y -= delta; }, { passive: true }); document.addEventListener('keydown', bump); document.addEventListener('scroll', bump, { passive: true }); document.addEventListener('touchstart', bump, { passive: true }); document.addEventListener('mousemove', function(e){ target.x = e.clientX; target.y = e.clientY; bump(); if (mode === 'errand' || mode === 'mischief' || mode === 'treat') return; if (mode === 'shelter' && weather === 'rain') return; var now = performance.now(); if (now < petUntil || now < wakeUntil) return; if (mode !== 'chase') emote('\u2757'); mode = 'chase'; }); function petHim(){ var now = performance.now(); if (mode === 'mischief') returnItem(); petUntil = now + 800; mode = 'rest'; restUntil = now + 2000; nextZzz = now + 99999; emote(pick(['\uD83D\uDC95','\uD83D\uDC96','\uD83D\uDC97']), true); setTimeout(function(){ emote('\uD83D\uDC95', true); }, 240); yipSound(); mem.pets++; saveMem(); if (mem.pets === 10) setTimeout(function(){ sayText('yip!! (we are friends now)'); }, 420); else if (mem.pets === 25) setTimeout(function(){ sayText('yip (best friend)'); }, 420); else if (mem.pets === 50) setTimeout(function(){ sayText('yip!! (bonded 4 life)'); }, 420); else if (mem.pets === 100) setTimeout(function(){ sayText('yip (soulmates)'); }, 420); } document.addEventListener('click', function(e){ bump(); if (e.target.closest && e.target.closest('a,button,input,textarea,label,select')) return; if (Math.hypot(e.clientX - pos.x, e.clientY - pos.y) > 48) return; petHim(); }); function stealables(){ var out = []; var els = document.querySelectorAll('[data-den-steal]'); for (var i = 0; i < els.length; i++) { var r = els[i].getBoundingClientRect(); if (r.width < 8 || r.width > 220 || r.height < 8 || r.height > 90) continue; if (r.bottom < 40 || r.top > window.innerHeight - 60) continue; if (r.right < 40 || r.left > window.innerWidth - 60) continue; if (els[i].style.visibility === 'hidden') continue; out.push(els[i]); } return out; } function returnItem(){ if (mis && mis.clone) { mis.el.style.visibility = ''; mis.clone.remove(); } if (mis) { mis = null; mode = 'wander'; target = pickWander(); } } function startMischief(force){ var now = performance.now(); if (mis) return false; if (!force && now - lastMischief < 150000) return false; var cands = stealables(); if (!cands.length) return false; if (!force && Math.random() > 0.07) return false; var el = cands[Math.floor(Math.random() * cands.length)]; var r = el.getBoundingClientRect(); mis = { el: el, phase: 'togo', pt: clampPt(r.left + r.width / 2, r.top + r.height / 2), deadline: now + 18000, gift: trust() >= 25 }; lastMischief = now; mode = 'mischief'; return true; } function grabItem(now){ var r = mis.el.getBoundingClientRect(); mis.clone = document.createElement('div'); mis.clone.style.cssText = 'position:fixed;z-index:9998;pointer-events:none;margin:0;left:' + r.left + 'px;top:' + r.top + 'px;width:' + r.width + 'px;height:' + r.height + 'px;'; var inner = mis.el.cloneNode(true); inner.style.margin = '0'; mis.clone.appendChild(inner); document.body.appendChild(mis.clone); mis.el.style.visibility = 'hidden'; mis.cw = r.width / 2; mis.ch = r.height; if (mis.gift) { sayText('yip!'); } else { var aa = Math.random() * Math.PI * 2; mis.dropPt = clampPt(pos.x + Math.cos(aa) * 300, pos.y + Math.sin(aa) * 300); sayText(pick(['yip yip yip (mine now)', '(catch me)', 'yip!! (tax)'])); } mis.phase = 'carry'; } function spawnWx(){ if (!wxLayer) return; var rain = weather === 'rain'; var p = document.createElement('i'); p.className = rain ? 'r' : 's'; if (!rain) p.textContent = '\u2744'; p.style.left = (Math.random() * 100) + 'vw'; var dur = rain ? (0.55 + Math.random() * 0.35) : (3.2 + Math.random() * 3.4); p.style.animationDuration = dur + 's'; p.style.opacity = rain ? (0.3 + Math.random() * 0.45) : (0.5 + Math.random() * 0.5); wxLayer.appendChild(p); setTimeout(function(){ p.remove(); }, dur * 1000 + 300); } function setWeather(kind){ if (kind !== 'rain' && kind !== 'snow') kind = 'clear'; if (kind === weather) return; weather = kind; if (wxTimer) { clearInterval(wxTimer); wxTimer = null; } if (wxLayer) { var old = wxLayer; wxLayer = null; old.remove(); } PAW_LIFE = (kind === 'snow') ? 4200 : 1300; if (kind === 'clear') { shelterPt = null; if (mode === 'shelter') { mode = 'wander'; target = pickWander(); sayText('yip!! (it stopped)'); } return; } wxLayer = document.createElement('div'); wxLayer.className = 'denwx'; document.body.appendChild(wxLayer); wxTimer = setInterval(spawnWx, kind === 'rain' ? 85 : 240); if (asleep) bump(); if (kind === 'rain') { sayText('yip?! (wet)'); emote('\u2614', true); seekShelter(); } else { sayText('YIP!! (snow!!)'); emote('\u2744', true); speedMult = 1.6; setTimeout(function(){ if (speedMult === 1.6) speedMult = 1; }, 6000); } } window.denWeather = setWeather; function seekShelter(){ var ps = pois(); if (!ps.length) { shelterPt = null; return false; } var best = null, bd = Infinity; for (var i = 0; i < ps.length; i++) { var r = ps[i].r; var cx = r.left + r.width * 0.5; var cy = r.top + Math.min(r.height * 0.5, 45); var dd = Math.hypot(cx - pos.x, cy - pos.y); if (dd < bd) { bd = dd; best = { x: cx, y: cy }; } } if (!best) return false; shelterPt = clampPt(best.x, best.y); nextSulk = performance.now() + 2200; mode = 'shelter'; return true; } function dropTreat(x, y){ if (treatEl) { treatEl.remove(); treatEl = null; } treatPt = clampPt(x, y); treatEl = document.createElement('div'); treatEl.className = 'dentreat'; treatEl.textContent = '\uD83C\uDF56'; treatEl.style.left = (treatPt.x - 10) + 'px'; treatEl.style.top = (treatPt.y - 10) + 'px'; document.body.appendChild(treatEl); if (asleep) bump(); if (mis) returnItem(); emote('\u2757', true); mode = 'treat'; } window.denTreat = dropTreat; function clampPt(x, y){ return { x: Math.min(Math.max(x, 20), window.innerWidth - 20), y: Math.min(Math.max(y, 20), window.innerHeight - 20) }; } function resolvePoint(opts){ var el = opts.el; if (typeof el === 'string') el = document.querySelector(el); if (el && el.getBoundingClientRect) { var r = el.getBoundingClientRect(); if (r.width > 0) return clampPt(r.left + r.width * 0.5, r.top + Math.min(r.height * 0.4, 50)); } if (typeof opts.x === 'number' && typeof opts.y === 'number') return clampPt(opts.x, opts.y); return null; } window.denEvent = function(type, opts){ opts = opts || {}; var now = performance.now(); lastActivity = now; if (asleep) bump(); if (mode === 'mischief' && type !== 'mischief') returnItem(); if (type === 'mischief') { startMischief(true); return; } if (type === 'rain' || type === 'snow' || type === 'clear') { setWeather(type); return; } if (type === 'treat') { var tp = resolvePoint(opts); if (!tp) tp = clampPt(pos.x + (Math.random() * 220 - 110), pos.y + (Math.random() * 160 - 80)); dropTreat(tp.x, tp.y); return; } if (type === 'celebrate') { petUntil = now + 1100; mode = 'rest'; restUntil = now + 2800; nextZzz = now + 99999; emote('\u2728', true); setTimeout(function(){ emote('\uD83D\uDC95', true); }, 300); setTimeout(function(){ emote('\u2728', true); }, 650); setTimeout(function(){ sayText(opts.text || 'YIP!!'); }, 180); yipSound(); } else if (type === 'investigate') { var pt = resolvePoint(opts); if (!pt) return; errandTarget = pt; errandSay = opts.text || null; errandRun = !!opts.run; errandDeadline = now + 9000; mode = 'errand'; } else if (type === 'alarm') { emote('\u26A0\uFE0F', true); sayText(opts.text || 'YIP?!'); var aa = Math.random() * Math.PI * 2; errandTarget = clampPt(pos.x + Math.cos(aa) * 260, pos.y + Math.sin(aa) * 260); errandSay = null; errandRun = true; errandDeadline = now + 4000; mode = 'errand'; } else if (type === 'sleepy') { suppressBumpUntil = now + 800; sleepPrepUntil = now + 1400; sayText('*yawn*'); } else if (type === 'zoomies') { zoomies(); } else if (opts.text) { sayText(opts.text); } }; function zoomies(){ document.body.classList.add('denzoom'); speedMult = 7.5; var old = document.title; document.title = 'YIP YIP YIP YIP YIP'; sayText('YIP YIP YIP YIP'); setTimeout(function(){ document.body.classList.remove('denzoom'); speedMult = 1; document.title = old; }, 10000); } var kseq = ['ArrowUp','ArrowUp','ArrowDown','ArrowDown','ArrowLeft','ArrowRight','ArrowLeft','ArrowRight','b','a']; var kpos = 0; document.addEventListener('keydown', function(e){ var k = e.key.length === 1 ? e.key.toLowerCase() : e.key; kpos = (k === kseq[kpos]) ? kpos + 1 : (k === kseq[0] ? 1 : 0); if (kpos !== kseq.length) return; kpos = 0; zoomies(); }); var buf = ''; document.addEventListener('keydown', function(e){ var t = e.target; if (t && (t.tagName === 'INPUT' || t.tagName === 'TEXTAREA' || t.isContentEditable)) return; if (e.key.length !== 1) return; buf = (buf + e.key.toLowerCase()).slice(-12); if (buf.slice(-3) === 'yip') { sayText('yip!!'); buf = ''; } else if (buf.slice(-3) === 'owo') { sayText('*notices u*'); buf = ''; } else if (buf.slice(-3) === 'uwu') { sayText('yip (no)'); buf = ''; } else if (buf.slice(-6) === 'kobold') { emote('\uD83D\uDC95', true); sayText('yip!! (that is me)'); buf = ''; } else if (buf.slice(-6) === 'shrine') { sayText('YIP?! (how do u know about that)'); buf = ''; } else if (buf.slice(-7) === 'zoomies') { zoomies(); buf = ''; } else if (buf.slice(-5) === 'sleep') { suppressBumpUntil = performance.now() + 800; sleepPrepUntil = performance.now() + 1400; sayText('*yawn*'); buf = ''; } }); function angDiff(a, b){ var d = (b - a) % (Math.PI * 2); if (d > Math.PI) d -= Math.PI * 2; if (d < -Math.PI) d += Math.PI * 2; return d; } function driftLine(){ if (hungry() && Math.random() < 0.55) return pick(['yip (hungry)', '(food?)', 'yip? (snacks)']); if (mem.v >= 3 && trust() === 0 && Math.random() < 0.4) return pick(['yip...', '(is anyone there)', 'yip? (hello?)']); if (trust() >= 25 && Math.random() < 0.4) return pick(['yip (comfy)', 'uwu', 'yip yip (good day)']); return null; } function pickWander(){ poiSay = null; if (Math.random() < 0.45) { var ps = pois().filter(function(p){ return p.el !== lastPoi; }); if (ps.length) { var p = ps[Math.floor(Math.random() * ps.length)]; lastPoi = p.el; var txts = (p.el.getAttribute('data-den-poi') || '').split('|').filter(Boolean); if (txts.length) poiSay = txts[Math.floor(Math.random() * txts.length)]; return { x: Math.min(Math.max(p.r.left + p.r.width * (0.25 + Math.random() * 0.5), 20), window.innerWidth - 20), y: Math.min(Math.max(p.r.top + Math.min(p.r.height * 0.4, 60) + Math.random() * 20, 20), window.innerHeight - 20) }; } } var r = 60 + Math.random() * 120; var a = heading + (Math.random() - 0.5) * 2.6; return { x: Math.min(Math.max(pos.x + Math.cos(a) * r, 20), window.innerWidth - 20), y: Math.min(Math.max(pos.y + Math.sin(a) * r, 20), window.innerHeight - 20) }; } function drop(){ side = -side; var p = document.createElement('span'); p.className = 'paw-trail'; p.innerHTML = PAW; var c = colors[side > 0 ? 0 : 1]; p.querySelector('svg').style.fill = c; p.style.color = c; var deg = heading * 180 / Math.PI + 90; var ox = -Math.sin(heading) * 7 * side; var oy = Math.cos(heading) * 7 * side; p.style.left = (pos.x + ox - 7) + 'px'; p.style.top = (pos.y + oy - 7) + 'px'; p.querySelector('svg').style.transform = 'rotate(' + deg + 'deg) scaleX(' + side + ')'; p.querySelector('svg').style.display = 'block'; document.body.appendChild(p); setTimeout(function(){ p.remove(); }, PAW_LIFE); } function tick(now){ var dt = Math.min((now - lastT) / 1000, 0.05); lastT = now; var zoom = speedMult; if (asleep) { if (now > nextSleepZ) { emote('\uD83D\uDCA4', true); nextSleepZ = now + 2600; } requestAnimationFrame(tick); return; } if (sleepPrepUntil) { if (now < sleepPrepUntil) { heading += 7 * dt; var bs = WALK * 0.7 * dt; pos.x += Math.cos(heading) * bs; pos.y += Math.sin(heading) * bs; distSince += bs; if (distSince >= STRIDE_WALK * 0.8) { distSince = 0; drop(); } } else { sleepPrepUntil = 0; asleep = true; pos.x = Math.min(Math.max(pos.x, 44), window.innerWidth - 44); pos.y = Math.min(Math.max(pos.y, 40), window.innerHeight - 30); makeBed(); sayText('...zzz'); nextSleepZ = now + 1800; } requestAnimationFrame(tick); return; } if (now - lastActivity > SLEEPY_AFTER && now > petUntil) { sleepPrepUntil = now + 1400; sayText('*yawn*'); requestAnimationFrame(tick); return; } if (now < wakeUntil) { requestAnimationFrame(tick); return; } if (now < petUntil) { heading += 9 * dt; var st2 = WALK * 0.9 * zoom * dt; pos.x += Math.cos(heading) * st2; pos.y += Math.sin(heading) * st2; distSince += st2; if (distSince >= STRIDE_WALK * 0.8) { distSince = 0; drop(); } requestAnimationFrame(tick); return; } if (mode === 'mischief') { if (!mis) { mode = 'wander'; target = pickWander(); requestAnimationFrame(tick); return; } if (now > mis.deadline) { if (mis.phase === 'carry') { mis.phase = 'dropped'; mis.dropTime = now; mis.deadline = now + 12000; sayText(mis.gift ? '*wheeze* (u are fast. here)' : '*wheeze* (fine)'); } else { returnItem(); requestAnimationFrame(tick); return; } } if (mis.phase === 'togo' || mis.phase === 'carry') { var mdest; if (mis.phase === 'togo') mdest = mis.pt; else mdest = mis.gift ? clampPt(target.x, target.y) : mis.dropPt; var mx = mdest.x - pos.x, my = mdest.y - pos.y; var md = Math.hypot(mx, my); if (md < 16) { if (mis.phase === 'togo') { grabItem(now); } else { mis.phase = 'dropped'; mis.dropTime = now; if (mis.gift) { sayText(pick(['uwu (for u)', 'yip (i brought u this)', '(i love u)'])); emote('\uD83D\uDC95', true); setTimeout(function(){ emote('\uD83D\uDC96', true); }, 300); } else { sayText(pick(['yip (u can have it back)', '(hehe)', 'yip yip (a good crime)'])); } } } else { var mw = Math.atan2(my, mx); var mmx = TURN * dt * 2.2; heading += Math.max(-mmx, Math.min(mmx, angDiff(heading, mw))); var msp = (mis.phase === 'carry' && !mis.gift ? RUN : TROT) * zoom; var mst = (mis.phase === 'carry' && !mis.gift ? STRIDE_RUN : STRIDE_WALK); var mstep = Math.min(msp * dt, md); pos.x += Math.cos(heading) * mstep; pos.y += Math.sin(heading) * mstep; distSince += mstep; if (distSince >= mst) { distSince = 0; drop(); } } if (mis && mis.clone && mis.phase === 'carry') { mis.clone.style.left = (pos.x - mis.cw) + 'px'; mis.clone.style.top = (pos.y - mis.ch - 14) + 'px'; } } else if (mis.phase === 'dropped') { if (now > mis.dropTime + (mis.gift ? 4200 : 5200)) { var hr = mis.el.getBoundingClientRect(); mis.clone.style.transition = 'left .6s ease, top .6s ease, opacity .6s ease'; mis.clone.style.left = hr.left + 'px'; mis.clone.style.top = hr.top + 'px'; mis.phase = 'returning'; mis.retTime = now; if (!mis.gift) sayText('yip (fine. take it back)'); } } else if (mis.phase === 'returning') { if (now > mis.retTime + 700) { emote('\u2728', true); returnItem(); } } requestAnimationFrame(tick); return; } if (mode === 'shelter') { if (weather !== 'rain') { shelterPt = null; mode = 'wander'; target = pickWander(); } else if (!shelterPt) { if (!seekShelter()) { mode = 'wander'; target = pickWander(); } } else { var sx = shelterPt.x - pos.x, sy = shelterPt.y - pos.y; var sd = Math.hypot(sx, sy); if (sd < 14) { if (now > nextSulk) { nextSulk = now + 3200 + Math.random() * 2600; if (Math.random() < 0.5) emote(pick(['\u2614', '\uD83D\uDCA7'])); else sayText(pick(['yip... (waiting it out)', '(damp)', 'yip (i do not like this)'])); } } else { var sw = Math.atan2(sy, sx); var smx = TURN * dt * 2.2; heading += Math.max(-smx, Math.min(smx, angDiff(heading, sw))); var sspeed = (sd > 150 ? RUN : TROT) * speedMult; var sstep = Math.min(sspeed * dt, sd); pos.x += Math.cos(heading) * sstep; pos.y += Math.sin(heading) * sstep; distSince += sstep; if (distSince >= (sd > 150 ? STRIDE_RUN : STRIDE_WALK)) { distSince = 0; drop(); } } } requestAnimationFrame(tick); return; } if (mode === 'treat') { if (!treatPt || !treatEl) { mode = 'wander'; target = pickWander(); } else { var tx = treatPt.x - pos.x, ty = treatPt.y - pos.y; var td = Math.hypot(tx, ty); if (td < 15) { treatEl.remove(); treatEl = null; treatPt = null; fedAt = now; mem.treats++; saveMem(); emote('\uD83D\uDC96', true); sayText(pick(['*nom nom* yip!!', 'yip!! (thank u)', '(best day)'])); yipSound(); mode = 'rest'; restUntil = now + 2600; nextZzz = now + 99999; } else { var tw = Math.atan2(ty, tx); var tmx = TURN * dt * 2.4; heading += Math.max(-tmx, Math.min(tmx, angDiff(heading, tw))); var tstep = Math.min(RUN * speedMult * dt, td); pos.x += Math.cos(heading) * tstep; pos.y += Math.sin(heading) * tstep; distSince += tstep; if (distSince >= STRIDE_RUN) { distSince = 0; drop(); } } } requestAnimationFrame(tick); return; } if (mode === 'errand') { if (!errandTarget || now > errandDeadline) { errandTarget = null; mode = 'wander'; target = pickWander(); } else { var ex = errandTarget.x - pos.x, ey = errandTarget.y - pos.y; var ed = Math.hypot(ex, ey); if (ed < 16) { if (errandSay) sayText(errandSay); else emote(pick(['\uD83D\uDC43','\u2753'])); errandTarget = null; errandSay = null; mode = 'rest'; restUntil = now + 1500; nextZzz = now + 99999; } else { var ew = Math.atan2(ey, ex); var emx = TURN * dt * 2.2; heading += Math.max(-emx, Math.min(emx, angDiff(heading, ew))); var esp = (errandRun ? RUN : TROT) * zoom; var est = (errandRun ? STRIDE_RUN : STRIDE_WALK); var estep = Math.min(esp * dt, ed); pos.x += Math.cos(heading) * estep; pos.y += Math.sin(heading) * estep; distSince += estep; if (distSince >= est) { distSince = 0; drop(); } } } requestAnimationFrame(tick); return; } var dx = target.x - pos.x, dy = target.y - pos.y; var dist = Math.hypot(dx, dy); if (mode === 'chase') { if (dist < 26) { mode = 'rest'; restUntil = now + (trust() >= 25 ? 4200 : 2500); nextZzz = now + 1700; if (now < windedUntil) { windedUntil = 0; sayText('*wheeze* (u went so far)'); } else if (Math.random() < 0.5) emote(trust() >= 10 ? '\uD83D\uDC95' : '\u2728'); } } else if (mode === 'rest') { if (now > nextZzz) { emote('\uD83D\uDCA4'); nextZzz = now + 1600; } if (hungry() && now > nextSulk) { emote('\uD83C\uDF56'); nextSulk = now + 9000; } if (now > restUntil) { if (weather === 'rain' && seekShelter()) { /* back under cover */ } else if (!startMischief(false)) { target = pickWander(); mode = 'wander'; } } } else { if (dist < 14) { mode = 'rest'; nextZzz = now + 99999; if (poiSay) { restUntil = now + 1200 + Math.random() * 1600; sayText(poiSay); poiSay = null; } else { restUntil = now + 600 + Math.random() * 1600; var dl = driftLine(); if (dl) sayText(dl); else emote(pick(['\uD83D\uDC43','\u2753','\uD83C\uDF38','\u2728','\uD83D\uDC3E'])); } } } if (mode !== 'rest' && dist > 1) { var want = Math.atan2(dy, dx); var maxTurn = TURN * dt * (mode === 'chase' ? 2.2 : 1); heading += Math.max(-maxTurn, Math.min(maxTurn, angDiff(heading, want))); var running = (mode === 'chase' && dist > 160); var speed = (mode === 'chase' ? (running ? RUN : TROT) : WALK) * zoom; var stride = running ? STRIDE_RUN : STRIDE_WALK; var step = Math.min(speed * dt, dist); pos.x += Math.cos(heading) * step; pos.y += Math.sin(heading) * step; distSince += step; if (distSince >= stride) { distSince = 0; drop(); } } requestAnimationFrame(tick); } var tab = document.createElement('div'); tab.className = 'dentab'; tab.textContent = '>_ den'; var term = document.createElement('div'); term.className = 'denterm'; term.innerHTML = '
den shell v2 (now portable)\ntype "help"\n
' + '
'; function dprint(t){ dout.textContent += t + '\n'; dout.scrollTop = dout.scrollHeight; } function openTerm(){ term.classList.add('open'); tab.style.display = 'none'; dinp.focus(); } function closeTerm(){ term.classList.remove('open'); tab.style.display = ''; } var petSeen = 0; function runCmd(raw){ var lc = raw.toLowerCase(); if (lc.indexOf('name ') === 0) { var nn = window.denSetName(raw.slice(5)); dprint(nn ? 'the kobold accepts the name "' + nn + '". this is legally binding' : 'that name did not survive processing. try letters'); return; } if (lc === 'name') { dprint(mem.name ? 'the kobold is named "' + mem.name + '"' : 'the kobold has no name yet. try: name '); return; } if (lc.indexOf('sudo') === 0) { dprint('no.'); return; } if (lc.indexOf('rm ') === 0 || lc === 'rm') { dprint('rm: the hoard is load-bearing. request denied'); return; } switch (lc) { case 'help': dprint('commands: pet, treat, come, sit, fetch, rain, snow, sun, sleep, wake, zoomies, where, status, name , ls, whoami, pwd, neofetch, yip, clear, exit'); break; case 'pet': case 'pet kobold': petSeen++; petHim(); dprint(petSeen >= 5 ? '*yip yip yip* (maximum happiness. further pets stored for later)' : '*pets dispensed remotely* yip!'); break; case 'fetch': { if (startMischief(true)) { dprint(trust() >= 25 ? 'he goes to find u something' : 'oh no'); } else { dprint(mis ? 'his paws are full' : 'nothing here worth taking. sad'); } break; } case 'treat': { var tr = term.getBoundingClientRect(); dropTreat( Math.max(tr.left - 60, 40), Math.max(tr.top - 50, 40) ); dprint(hungry() ? 'he was waiting for this' : 'a treat appears. he has noticed'); break; } case 'rain': setWeather('rain'); dprint('it rains. he does not care for it'); break; case 'snow': setWeather('snow'); dprint('it snows. he cares for it a great deal'); break; case 'sun': case 'clear': setWeather('clear'); dprint('the sky clears'); break; case 'come': { var r = term.getBoundingClientRect(); target = { x: Math.min(Math.max(r.left + 40, 20), window.innerWidth - 20), y: Math.min(Math.max(r.top - 40, 20), window.innerHeight - 20) }; if (asleep) bump(); mode = 'chase'; dprint('he is on his way'); break; } case 'sit': mode = 'rest'; restUntil = performance.now() + 6000; nextZzz = performance.now() + 99999; sayText('yip (sitting)'); dprint('he sits. a good boy'); break; case 'sleep': suppressBumpUntil = performance.now() + 800; sleepPrepUntil = performance.now() + 1400; sayText('*yawn*'); dprint('bedtime initiated'); break; case 'wake': if (asleep) { bump(); dprint('he stirs'); } else dprint('he is already awake and probably watching u'); break; case 'zoomies': zoomies(); dprint('oh no'); break; case 'where': sayText('yip!! (here)'); emote('\u2728', true); dprint('listen for the yip'); break; case 'status': dprint('mode: ' + (asleep ? 'asleep' : mode) + ' | pets: ' + mem.pets + ' | treats: ' + mem.treats + ' | name: ' + (mem.name || '(unnamed)') + ' | visits: ' + mem.v + ' | weather: ' + weather + (hungry() ? ' | hungry' : '')); break; case 'ls': dprint('den/ hoard/ guestbook.txt do_not_open/ retopo_FINAL_v2/'); break; case 'whoami': dprint('a guest in the den'); break; case 'pwd': dprint('/home/den/u'); break; case 'yip': sayText('yip!!'); dprint('yip!!'); break; case 'neofetch': dprint(' /\\_/\\ guest@den\n ( o.o ) ----------\n > ^ < OS: DenOS (yes it is just apache)\n kobold btw Shell: den shell 2.0\n Uptime: emotionally? unclear'); break; case 'clear': dout.textContent = ''; break; case 'exit': dprint('u can check out any time u like...'); setTimeout(closeTerm, 600); break; default: dprint("fish: Unknown command: '" + raw.split(' ')[0].replace(/[^\w.-]/g, '') + "'"); } } function mountShell(){ document.body.appendChild(tab); document.body.appendChild(term); dout = term.querySelector('.dout'); dinp = term.querySelector('input'); dpr = term.querySelector('.dprompt'); dpr.textContent = promptTxt(); tab.addEventListener('click', openTerm); dinp.addEventListener('keydown', function(e){ if (e.key === 'Escape') { closeTerm(); return; } if (e.key !== 'Enter') return; var raw = dinp.value.trim(); dinp.value = ''; if (!raw) return; dprint(promptTxt() + ' ' + raw); runCmd(raw); }); } var hole = document.createElement('div'); hole.className = 'denhole'; hole.innerHTML = '' + '' + '' + '' + ''; hole.style.left = (pos.x - 28) + 'px'; hole.style.top = (pos.y - 17) + 'px'; function beginLife(){ mountShell(); checkPresence(); setInterval(checkPresence, 60000); document.body.appendChild(hole); setTimeout(function(){ emote('\u2728', true); sayText(greeting()); var cx = window.innerWidth / 2, cy = window.innerHeight / 2; var ang = Math.atan2(cy - pos.y, cx - pos.x); heading = ang; var r2 = 160 + Math.random() * 120; target = { x: Math.min(Math.max(pos.x + Math.cos(ang) * r2, 20), window.innerWidth - 20), y: Math.min(Math.max(pos.y + Math.sin(ang) * r2, 20), window.innerHeight - 20) }; poiSay = null; mode = 'wander'; }, 700); setTimeout(function(){ hole.style.opacity = '0'; setTimeout(function(){ hole.remove(); }, 600); }, 2600); setTimeout(function(){ var n = window.denOthers | 0; if (n > 0) sayText('yip (' + n + (n === 1 ? ' other is' : ' others are') + ' here... i can smell them)'); }, 9000); } if (document.body) beginLife(); else document.addEventListener('DOMContentLoaded', beginLife); requestAnimationFrame(tick); })();