From 3d217be3ce3a28828a3ef65260b6b2d4ab7b8f02 Mon Sep 17 00:00:00 2001 From: matthewmeeks Date: Wed, 29 Nov 2023 15:30:27 -0500 Subject: [PATCH] add blob proto --- blob/index.html | 34 +++++++++++++ blob/script.js | 124 ++++++++++++++++++++++++++++++++++++++++++++++++ blob/styles.css | 112 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 270 insertions(+) create mode 100644 blob/index.html create mode 100644 blob/script.js create mode 100644 blob/styles.css diff --git a/blob/index.html b/blob/index.html new file mode 100644 index 0000000..a420abb --- /dev/null +++ b/blob/index.html @@ -0,0 +1,34 @@ + + + + + Blob + + + + + +
+ +
+ + + + diff --git a/blob/script.js b/blob/script.js new file mode 100644 index 0000000..ff48072 --- /dev/null +++ b/blob/script.js @@ -0,0 +1,124 @@ +const states = {}; + +let scale = 1.0; +const viewportElem = document.getElementById("container"); + +function setScale(v) { + scale = v; + viewportElem.style.scale = v; +} + +visualViewport.onresize = () => { + const sx = document.body.clientWidth / 1280; + const sy = document.body.clientHeight / 720; + setScale(Math.min(sx, sy)); +} +visualViewport.onresize(); + +function spawnCharacter(id, state) { + const tmpl = document.getElementById("char-tmpl"); + const elemFrag = tmpl.content.cloneNode(true); + const charElem = elemFrag.querySelector(".character"); + charElem.id = `char-${id}`; + charElem.style.setProperty("--hue", state.hue); + charElem.style.setProperty("--iris-hue", state.irisHue); + viewportElem.appendChild(elemFrag); + blinkLoop(id); + states[id] = structuredClone(state); +} + +function getCharElem(id) { + const elem = document.getElementById(`char-${id}`); + if (!elem) throw new Error("Could not find element for character with ID " + id); + return elem; +} + +function openEyes(id) { + getCharElem(id).style.setProperty("--eye-lid-state", 0.1); +} + +function closeEyes(id) { + getCharElem(id).style.setProperty("--eye-lid-state", 1); +} + +function blink(id) { + closeEyes(id); + setTimeout(() => openEyes(id), 150); +} + +function blinkLoop(id) { + blink(id); + setTimeout(() => blinkLoop(id), Math.random() * 10000); +} + +function setPosition(id, x, y) { + const elem = getCharElem(id); + elem.style.setProperty("--pos-x", `${x}px`); + elem.style.setProperty("--pos-y", `${y - 30}px`); + elem.style.zIndex = Math.round(y); +} + +function createState(pos, rot, hue, irisHue) { + return { + x: pos[0], + y: pos[1], + tx: pos[0], + ty: pos[1], + rot, + hue, + irisHue + }; +} + + +const localId = 0; +// spawnCharacter(localId, createState([150, 150], 0, 120, Math.random() * 360)); + +for (let i = 0; i < 10; i++) { + spawnCharacter(i, createState([Math.random() * 1280, Math.random() * 720], Math.random() * 360, Math.random() * 360, Math.random() * 360)); +} + +let prevEt = 0; +const speed = 0.1; +function updateOuter() { + requestAnimationFrame((et) => { + const dt = et - prevEt; + + for (const idStr in states) { + const id = Number(idStr); + // console.log(id); + const state = states[idStr]; + let dx = state.tx - state.x; + let dy = state.ty - state.y; + + let dl = Math.sqrt(dx * dx + dy * dy); + + if (dl <= speed * dt) { + state.x = state.tx; + state.y = state.ty; + + if (id !== localId && Math.random() > 0.99) { + state.tx = Math.random() * 1280; + state.ty = Math.random() * 720; + } + } else { + state.x += dx / dl * dt * speed; + state.y += dy / dl * dt * speed; + state.rot += dt * 0.3 * (dx / dl); + getCharElem(id).style.setProperty("--rotation", `${state.rot}deg`); + } + setPosition(id, state.x, state.y); + } + + prevEt = et; + updateOuter(); + }); +} + +updateOuter(); + +document.onclick = (ev) => { + console.log(ev); + states[localId].tx = ev.x / scale; + states[localId].ty = ev.y / scale; +} diff --git a/blob/styles.css b/blob/styles.css new file mode 100644 index 0000000..87fc16b --- /dev/null +++ b/blob/styles.css @@ -0,0 +1,112 @@ +html, +:root, +body { + width: 100%; + height: 100%; +} + +.container { + width: 1280px; + height: 720px; + background: orange; + overflow: hidden; + transform-origin: top left; +} + +.character { + position: absolute; + translate: var(--pos-x) var(--pos-y); +} + +.body { + box-sizing: border-box; + width: 80px; + height: 80px; + position: relative; + translate: -50% -50%; +} + +.shadow { + left: -60px; + bottom: 40px; + width: 95px; + height: 30px; + background: #0007; + position: absolute; + border-radius: 50%; +} + +.body::after { + content: ""; + + rotate: -30deg; + position: absolute; + inset: 0; + border-left: solid 10px #0007; + border-right: solid 5px #fff7; + border-radius: 50%; + + background: hsl(var(--hue) 100% 50%); +} + +.left-eye, +.right-eye { + background: white; + position: absolute; + width: 20px; + height: 20px; + top: 20px; + border-radius: 50%; +} + +.left-eye::after, +.right-eye::after { + --lid-color: hsl(var(--hue) 85% 45%); + content: ""; + position: absolute; + inset: 0; + box-sizing: border-box; + border-top: solid calc(var(--eye-lid-state, 0) * 10px) var(--lid-color); + border-bottom: solid calc(var(--eye-lid-state, 0) * 10px) var(--lid-color); + border-radius: 50%; + transition: border 0.1s; +} + +.left-eye { + left: 15px; +} + +.right-eye { + left: 45px; +} + +.iris { + background: hsl(var(--iris-hue) 50% 60%); + width: 12px; + height: 12px; + position: absolute; + top: 4px; + left: 4px; + border-radius: 50%; +} + +.pupil { + background: black; + width: 6px; + height: 6px; + position: absolute; + top: 3px; + left: 3px; + border-radius: 50%; +} + +.face { + translate: -50% -50%; + position: absolute; + inset: 0; + rotate: var(--rotation); +} + +body { + margin: 0; +}