File size: 8,859 Bytes
6e6f44b | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 | (() => {
const HOST_ID = "injection-beholder-root";
const old = document.getElementById(HOST_ID);
if (old) { old.remove(); return; }
const host = document.createElement("div");
host.id = HOST_ID;
Object.assign(host.style, {
position: "fixed", inset: "0", zIndex: "2147483647",
pointerEvents: "none", overflow: "hidden"
});
(document.documentElement || document.body).appendChild(host);
const shadow = host.attachShadow({ mode: "open" });
const style = document.createElement("style");
style.textContent = `
:host { all: initial; }
.beholder {
--skin: #8b9c55; --dark: #26301b; --eye: #f4e7c5;
position: fixed; right: clamp(18px, 4vw, 58px); bottom: 28px;
width: 220px; height: 210px; pointer-events: auto;
cursor: grab; transform-origin: 50% 65%;
animation: hover 4.8s ease-in-out infinite;
filter: drop-shadow(0 13px 12px #0007);
user-select: none; touch-action: none;
}
.beholder:active { cursor: grabbing; }
.body {
position: absolute; left: 36px; top: 76px; width: 150px; height: 116px;
border-radius: 48% 52% 46% 54% / 55% 52% 48% 45%;
background: radial-gradient(ellipse at 34% 25%, #c1ca7a 0 8%, transparent 35%),
radial-gradient(ellipse at 65% 72%, #536538 0 5%, transparent 42%),
linear-gradient(145deg, #a8b76b, var(--skin) 58%, #56673a);
border: 3px solid #52613a;
box-shadow: inset -10px -13px 17px #26351e88, inset 8px 7px 14px #e5e9a655;
}
.main-eye {
position: absolute; left: 75px; top: 27px; width: 84px; height: 84px;
border-radius: 50%; background: radial-gradient(circle at 36% 30%, white, var(--eye) 58%, #c6b994);
border: 5px solid #53613b; box-shadow: inset 0 0 0 2px #fff8, 0 2px 8px #0006;
overflow: hidden; z-index: 4;
}
.pupil {
position: absolute; left: 29px; top: 24px; width: 28px; height: 38px;
border-radius: 50%; background: radial-gradient(ellipse at 35% 25%, #5d6e75, #10191d 70%);
transition: transform .12s ease-out;
}
.pupil:after { content:""; position:absolute; width:8px; height:8px; left:5px; top:5px; border-radius:50%; background:#fff; }
.lid {
position:absolute; inset:-2px 0 auto; height:0; z-index:5;
background:linear-gradient(#81934d,#64773d); border-radius:50%;
animation: blink 5.7s infinite;
}
.stalk {
position:absolute; width:8px; height:66px; border-radius:100% 100% 35% 35%;
background:linear-gradient(90deg,#53673a,#b3bf70 48%,#687b42);
transform-origin:50% 100%; z-index:2; border:1px solid #52613a;
animation: sway var(--dur,3s) ease-in-out infinite alternate;
}
.stalk:after {
content:""; position:absolute; width:27px; height:27px; left:50%; top:-17px;
transform:translateX(-50%); border-radius:50%;
background:radial-gradient(circle at 35% 30%,#fff,#e9dfbd 55%,#a79d7b);
border:3px solid #53613b; box-shadow:0 2px 4px #0005;
}
.stalk .tiny-pupil {
position:absolute; z-index:3; width:8px; height:11px; left:50%; top:-9px;
transform:translateX(-50%); border-radius:50%; background:#20292a;
}
.s1 { left:49px; top:48px; --dur:2.7s; transform:rotate(-43deg); }
.s2 { left:75px; top:34px; --dur:3.2s; transform:rotate(-20deg); }
.s3 { left:151px; top:36px; --dur:2.4s; transform:rotate(22deg); }
.s4 { left:177px; top:57px; --dur:3.5s; transform:rotate(47deg); }
.s5 { left:35px; top:94px; --dur:2.9s; transform:rotate(-65deg); }
.mouth {
position:absolute; z-index:6; left:83px; top:151px; width:64px; height:25px;
border-radius: 10px 10px 45px 45px; background:#321d20;
border:3px solid #45542f; overflow:hidden;
transform:rotate(-3deg);
}
.teeth {
position:absolute; left:6px; top:0; width:48px; height:10px;
background: repeating-linear-gradient(90deg,#fff0d5 0 8px,#b7ad8e 8px 10px);
clip-path:polygon(0 0,100% 0,94% 100%,82% 60%,70% 100%,57% 60%,44% 100%,30% 60%,17% 100%,7% 60%);
}
.cheek { position:absolute; top:140px; width:15px; height:9px; border-radius:50%; background:#c57d61; opacity:.65; }
.c1 { left:60px } .c2 { left:151px }
.spark { position:absolute; right:15px; top:83px; color:#e7e6ad; font: bold 20px sans-serif; opacity:0; }
.beholder.excited .spark { animation: sparkle .65s ease-out; }
.beholder.excited .body { filter:brightness(1.2) saturate(1.25); }
.beholder.excited { animation: bounce .55s ease-out 2; }
.beholder.sleepy .lid { animation: sleepy 2.2s forwards; }
.hint {
position:absolute; bottom:-2px; left:35px; width:150px; text-align:center;
color:#f4f0d8; font:11px/1.2 system-ui,sans-serif; opacity:0;
text-shadow:0 1px 3px #000; transition:opacity .2s;
}
.beholder:hover .hint { opacity:.85; }
@keyframes hover { 0%,100%{transform:translateY(0) rotate(-1deg)} 50%{transform:translateY(-10px) rotate(1deg)} }
@keyframes sway { from{rotate:-5deg} to{rotate:7deg} }
@keyframes blink { 0%,44%,48%,100%{height:0} 46%{height:88px} }
@keyframes sparkle { 0%{opacity:0;transform:scale(.4) rotate(0)} 35%{opacity:1;transform:scale(1.3) rotate(25deg)} 100%{opacity:0;transform:scale(.6) rotate(55deg)} }
@keyframes bounce { 0%,100%{translate:0 0} 35%{translate:0 -17px} 65%{translate:0 4px} }
@keyframes sleepy { to{height:34px} }
@media (prefers-reduced-motion: reduce) { .beholder,.stalk,.lid { animation:none !important } }
`;
shadow.appendChild(style);
const creature = document.createElement("div");
creature.className = "beholder";
creature.setAttribute("role", "button");
creature.setAttribute("aria-label", "Injection Beholder. Click to wake it up. Press Escape to dismiss.");
creature.innerHTML = `
<div class="stalk s1"><i class="tiny-pupil"></i></div>
<div class="stalk s2"><i class="tiny-pupil"></i></div>
<div class="stalk s3"><i class="tiny-pupil"></i></div>
<div class="stalk s4"><i class="tiny-pupil"></i></div>
<div class="stalk s5"><i class="tiny-pupil"></i></div>
<div class="body"></div>
<div class="main-eye"><div class="pupil"></div><div class="lid"></div></div>
<div class="cheek c1"></div><div class="cheek c2"></div>
<div class="mouth"><div class="teeth"></div></div>
<div class="spark">✦</div>
<div class="hint">click me · drag me · Esc to dismiss</div>`;
shadow.appendChild(creature);
const pupil = creature.querySelector(".pupil");
const tinyPupils = [...creature.querySelectorAll(".tiny-pupil")];
let targetX = 0, targetY = 0, dragging = false, offsetX = 0, offsetY = 0;
const look = (x, y) => {
const r = creature.getBoundingClientRect();
const cx = r.left + r.width / 2, cy = r.top + 70;
const dx = Math.max(-9, Math.min(9, (x - cx) / 22));
const dy = Math.max(-9, Math.min(9, (y - cy) / 22));
targetX = dx; targetY = dy;
pupil.style.transform = `translate(${dx}px,${dy}px)`;
tinyPupils.forEach((p, i) => p.style.transform = `translate(calc(-50% + ${dx * (i%2 ? .35 : -.35)}px), ${dy*.35}px)`);
};
const onMove = e => {
if (dragging) {
creature.style.left = `${e.clientX - offsetX}px`;
creature.style.top = `${e.clientY - offsetY}px`;
creature.style.right = "auto"; creature.style.bottom = "auto";
}
look(e.clientX, e.clientY);
};
const onDown = e => {
if (!creature.contains(e.target)) return;
dragging = true;
const r = creature.getBoundingClientRect();
offsetX = e.clientX - r.left; offsetY = e.clientY - r.top;
creature.setPointerCapture?.(e.pointerId);
e.preventDefault();
};
const onUp = () => { dragging = false; };
const onClick = () => {
creature.classList.remove("sleepy");
creature.classList.add("excited");
setTimeout(() => creature.classList.remove("excited"), 1300);
};
const onKey = e => { if (e.key === "Escape") cleanup(); };
const cleanup = () => {
document.removeEventListener("pointermove", onMove, true);
document.removeEventListener("pointerup", onUp, true);
document.removeEventListener("keydown", onKey, true);
host.remove();
};
document.addEventListener("pointermove", onMove, true);
document.addEventListener("pointerup", onUp, true);
document.addEventListener("keydown", onKey, true);
creature.addEventListener("pointerdown", onDown);
creature.addEventListener("click", onClick);
// A gentle idle drift in the gaze when the pointer is away.
const idle = setInterval(() => {
if (!host.isConnected) { clearInterval(idle); return; }
if (!dragging && !creature.matches(":hover")) {
const t = Date.now() / 900;
pupil.style.transform = `translate(${Math.sin(t)*3}px,${Math.cos(t*.7)*2}px)`;
}
}, 900);
host.addEventListener("DOMNodeRemoved", () => clearInterval(idle), { once: true });
})();
|