fuzzyworld

north-star / commit

smooth rotation as test participant circles

0adcad67ad0bcc6af7118aa39f462f37fae82862 · Pavel Safronov · 2026-03-14 10:44:41 -0700 · parents 7fff149ae85a

smooth rotation as test participant circles
 src/app.js     | 142 ++++++++++++++++++++++++++++++++++++++++++++++++++++-----
 src/index.html |   2 +-
 src/style.css  |   2 +-
 3 files changed, 132 insertions(+), 14 deletions(-)

diff --git a/src/app.js b/src/app.js
index 323d724..eed120d 100644
--- a/src/app.js
+++ b/src/app.js
@@ -1,18 +1,136 @@
 const arrow = document.getElementById('arrow');
+let currentArrowAngle = null;
+const getShortestDelta = (fromAngle, toAngle) => ((toAngle - fromAngle + 540) % 360) - 180;
 
-let currentAngle = 0;
+/**
+ * @typedef {Object} Position
+ * @property {number} lat
+ * @property {number} lon
+ */
 
-document.addEventListener('mousemove', (e) => {
-    const rect = arrow.getBoundingClientRect();
-    const cx = rect.left + rect.width / 2;
-    const cy = rect.top + rect.height / 2;
-    const dx = e.clientX - cx;
-    const dy = e.clientY - cy;
+/**
+ * @typedef {Object} Participant
+ * @property {string} id
+ * @property {string} name
+ * @property {string} color
+ * @property {Position} position
+ * @property {number} timestamp
+ * 
+ * {
+ *  id: 'abc123',
+ *  name: 'Alice',
+ *  color: '#ff0000',
+ *  position: {
+ *    lat: 37.7749,
+ *    lon: -122.4194
+ *  },
+ *  timestamp: 1625247600000
+ * }
+ */
+
+/**
+ * @typedef {Object} AppState
+ * @property {string} roomId
+ * @property {string} selfId
+ * @property {Array<Participant>} participants
+ * 
+ * {
+ *  roomId: 'room123',
+ *  selfId: 'abc123',
+ *  participants: [
+ *    {
+ *      id: 'abc123',
+ *      name: 'Alice',
+ *      color: '#ff0000',
+ *      position: {
+ *        lat: 37.7749,
+ *        lon: -122.4194
+ *      },
+ *      timestamp: 1625247600000
+ *    },
+ *    {
+ *      id: 'def456',
+ *      name: 'Bob',
+ *      color: '#00ff00',
+ *      position: {
+ *        lat: 37.7749,
+ *        lon: -122.4194
+ *      },
+ *      timestamp: 1625247600000
+ *    }
+ *  ])
+ * }
+ */
+
+// Space Needle: 47.62055039884746, -122.34928833200904
+const selfPosition = {
+    lat: 47.62055039884746,
+    lon: -122.34928833200904
+};
+const getTestParticipantPosition = (angle) => {
+    const radius = 0.001; // ~111 meters
+    return {
+        lat: selfPosition.lat + radius * Math.cos(angle),
+        lon: selfPosition.lon + radius * Math.sin(angle)
+    };
+}
+
+const createId = () => Math.random().toString(36).substr(2, 9);
+const selfParticipant = {
+    id: createId(),
+    name: 'You',
+    color: '#a2b8ed',
+    position: selfPosition,
+    timestamp: Date.now()
+};
+const testParticipant = {
+    id: 'test',
+    name: 'Jane',
+    color: '#ff0000',
+    position: getTestParticipantPosition(0),
+    timestamp: Date.now()
+};
+
+const appState = {
+    roomId: createId(),
+    selfId: selfParticipant.id,
+    participants: [selfParticipant, testParticipant]
+};
+
+const render = () => {
+    const self = appState.participants.find((p) => p.id === appState.selfId);
+    const target = appState.participants.find((p) => p.id !== appState.selfId);
+
+    if (!self || !target) return;
+
+    // Rotate the arrow toward the other participant and use their color.
+    const dx = target.position.lon - self.position.lon;
+    const dy = target.position.lat - self.position.lat;
     const rawAngle = Math.atan2(dy, dx) * (180 / Math.PI) + 90;
 
-    // Find the shortest delta from the current accumulated angle
-    let delta = ((rawAngle - currentAngle) % 360 + 540) % 360 - 180;
-    currentAngle += delta;
+    if (currentArrowAngle === null) {
+        currentArrowAngle = rawAngle;
+    } else {
+        currentArrowAngle += getShortestDelta(currentArrowAngle, rawAngle);
+    }
+
+    arrow.style.transform = `rotate(${currentArrowAngle}deg)`;
+    arrow.style.color = target.color;
+
+    // set the background color to self color
+    document.body.style.backgroundColor = self.color;
+};
+
+// move the test participant in a circle around the self participant, updating every 1 seconds
+const moveTestParticipant = () => {
+    let angle = 0;
+    setInterval(() => {
+        angle += Math.PI / 30; // increment angle
+        testParticipant.position = getTestParticipantPosition(angle);
+        testParticipant.timestamp = Date.now();
+        render();
+    }, 100);
+    render();
+};
 
-    arrow.style.transform = `rotate(${currentAngle}deg)`;
-});
+moveTestParticipant();
\ No newline at end of file
diff --git a/src/index.html b/src/index.html
index 53a9992..b951e52 100644
--- a/src/index.html
+++ b/src/index.html
@@ -10,7 +10,7 @@
 </head>
 
 <body>
-    <div id="arrow">▲</div>
+    <div id="arrow">⇡</div>
     <script src="app.js"></script>
 </body>
 
diff --git a/src/style.css b/src/style.css
index 0e5eee4..7eb696e 100644
--- a/src/style.css
+++ b/src/style.css
@@ -7,7 +7,7 @@ body {
 body {
     display: grid;
     place-items: center;
-    background: #0f172a;
+    background: #a2b8ed;
     color: #f8fafc;
     font-family: system-ui, sans-serif;
     overflow: hidden;