[object Object]

← back to Crazy News Channel

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

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  ·  Add make-cartoon-shorts.mjs: turn each P24 cartoon into a ve ce98d61 →