← back to Crazy News Channel Shadowman
Add googly eyes (/eyes skill) pinned bottom-right above the ticker, pointer-events none
2d4bee03b2f6fad6ed0cb8cd19fd9f77ca8a4e6f · 2026-09-24 15:37:06 -0700 · Steve Abrams
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KLwoGhVdenyRTtTeMnGHSU
Files touched
A assets/eyes.jsM index.html
Diff
commit 2d4bee03b2f6fad6ed0cb8cd19fd9f77ca8a4e6f
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Thu Sep 24 15:37:06 2026 -0700
Add googly eyes (/eyes skill) pinned bottom-right above the ticker, pointer-events none
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KLwoGhVdenyRTtTeMnGHSU
---
assets/eyes.js | 61 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
index.html | 4 ++++
2 files changed, 65 insertions(+)
diff --git a/assets/eyes.js b/assets/eyes.js
new file mode 100644
index 0000000..4b5758f
--- /dev/null
+++ b/assets/eyes.js
@@ -0,0 +1,61 @@
+/* eyes.js — zero-dependency "googly eyes follow the mouse" drop-in.
+ * Usage: <div data-eyes></div><script src="eyes.js"></script>
+ * Options (data-attributes on the host):
+ * data-eye-size="200" eye diameter px (default 200)
+ * data-pupil-ratio="0.38" pupil diameter / eye diameter
+ * data-gap="8" px between eyes
+ * data-eye-color="#FAF0DF" data-pupil-color="#000"
+ * data-corner="bottom-right" pin the eyes fixed in a viewport corner
+ * (top-left|top-right|bottom-left|bottom-right)
+ * data-offset-x="16px" data-offset-y="16px" inset from that corner (any CSS length/calc)
+ * data-z="25" z-index when pinned
+ * The pupil centre is clamped to (R_eye - R_pupil) so it never leaves the eye. */
+(function () {
+ var CSS = '.eyes-rig{display:flex;justify-content:center;align-items:center}' +
+ '.eyes-rig .eye{position:relative;border-radius:50%;overflow:hidden;flex:none}' +
+ '.eyes-rig .pupil{position:absolute;left:50%;top:50%;border-radius:50%;' +
+ 'transition:transform .08s linear;will-change:transform}' +
+ '.eyes-rig.eyes-pinned{position:fixed;pointer-events:none;filter:drop-shadow(0 2px 4px rgba(0,0,0,.35))}' +
+ '@media (prefers-reduced-motion:reduce){.eyes-rig .pupil{transition:none}}';
+ if (!document.getElementById('eyes-rig-css')) {
+ var s = document.createElement('style'); s.id = 'eyes-rig-css'; s.textContent = CSS;
+ document.head.appendChild(s);
+ }
+ var pupils = [];
+ function build(host) {
+ if (host.__eyes) return; host.__eyes = true;
+ var d = host.dataset, size = +d.eyeSize || 200, ratio = +d.pupilRatio || 0.38;
+ var p = size * ratio;
+ host.classList.add('eyes-rig'); host.style.gap = (d.gap || 8) + 'px';
+ host.setAttribute('aria-hidden', 'true');
+ if (d.corner) {
+ var c = d.corner.split('-'), st = host.style;
+ host.classList.add('eyes-pinned'); st.zIndex = d.z || 25;
+ st[c[0] === 'top' ? 'top' : 'bottom'] = d.offsetY || '16px';
+ st[c[1] === 'left' ? 'left' : 'right'] = d.offsetX || '16px';
+ }
+ for (var i = 0; i < 2; i++) {
+ var eye = document.createElement('div'); eye.className = 'eye';
+ eye.style.cssText = 'width:' + size + 'px;height:' + size + 'px;background:' + (d.eyeColor || 'var(--eye-white,#FAF0DF)');
+ var pu = document.createElement('div'); pu.className = 'pupil';
+ pu.style.cssText = 'width:' + p + 'px;height:' + p + 'px;margin:' + (-p / 2) + 'px 0 0 ' + (-p / 2) + 'px;background:' + (d.pupilColor || 'var(--pupil,#000)');
+ eye.appendChild(pu); host.appendChild(eye);
+ pupils.push({ eye: eye, pupil: pu, max: (size - p) / 2 - 2 });
+ }
+ }
+ function look(x, y) {
+ pupils.forEach(function (o) {
+ var r = o.eye.getBoundingClientRect();
+ var dx = x - (r.left + r.width / 2), dy = y - (r.top + r.height / 2);
+ var dist = Math.hypot(dx, dy), k = dist > o.max ? o.max / dist : 1;
+ o.pupil.style.transform = 'translate(' + dx * k + 'px,' + dy * k + 'px)';
+ });
+ }
+ function init() {
+ document.querySelectorAll('[data-eyes]').forEach(build);
+ addEventListener('pointermove', function (e) { look(e.clientX, e.clientY); }, { passive: true });
+ addEventListener('touchmove', function (e) { var t = e.touches[0]; if (t) look(t.clientX, t.clientY); }, { passive: true });
+ }
+ window.Eyes = { mount: function (el) { build(el); } };
+ document.readyState === 'loading' ? document.addEventListener('DOMContentLoaded', init) : init();
+})();
diff --git a/index.html b/index.html
index 7471a47..1c50fd4 100644
--- a/index.html
+++ b/index.html
@@ -2268,5 +2268,9 @@ function init() {
document.addEventListener("DOMContentLoaded", init);
</script>
+<!-- Googly eyes that follow the cursor, fixed bottom-right just above the ticker + anchor caption (/eyes skill) -->
+<div data-eyes data-corner="bottom-right" data-eye-size="48" data-gap="4" data-pupil-ratio="0.42"
+ data-offset-x="16px" data-offset-y="calc(3rem + 2.4rem + 10px)" data-z="25"></div>
+<script src="assets/eyes.js"></script>
</body>
</html>
← bd41b0b P24 cartoon batch: build one at a time (no parallel rate-lim
·
back to Crazy News Channel Shadowman
·
Add make-cartoon-shorts.mjs: turn each P24 cartoon into a ve ce98d61 →