fuzzyworld

north-star / commit

some refactoring

efc31a14c01976a7e5be3075e1ea4eb5beed3a6e · Pavel Safronov · 2026-03-16 00:40:56 -0700 · parents feaee58345bb

some refactoring
 src/app.ts             |  2 --
 src/friends-panel.ts   | 44 ++++++++++++++++++++++---
 src/public-settings.ts |  4 +++
 src/renderer.ts        | 88 +-------------------------------------------------
 4 files changed, 44 insertions(+), 94 deletions(-)

diff --git a/src/app.ts b/src/app.ts
index 5a072f6..2ff9dde 100644
--- a/src/app.ts
+++ b/src/app.ts
@@ -46,7 +46,6 @@ const FRIEND_RECENTLY_OFFLINE_MS = (() => {
 })();
 const HEADING_CORRECTION_STORAGE_KEY = 'friend-compass-heading-correction-deg';
 const LAST_ROOM_HASH_STORAGE_KEY = 'friend-compass-last-room-hash';
-const NORTH_MARKER_ID = 'north-marker';
 
 let headingCorrectionDeg = (() => {
     const parsed = Number(localStorage.getItem(HEADING_CORRECTION_STORAGE_KEY));
@@ -240,7 +239,6 @@ const friendsController = createFriendsPanelController({
     friendsToggle,
     requestRender,
     getReadableTextColor,
-    northMarkerId: NORTH_MARKER_ID,
     selfFriend,
     selfPosition,
     presenceThresholds: {
diff --git a/src/friends-panel.ts b/src/friends-panel.ts
index a05aa0e..0f72d66 100644
--- a/src/friends-panel.ts
+++ b/src/friends-panel.ts
@@ -1,12 +1,12 @@
 import { getFriendPresenceState, PRESENCE_EMOJI } from './utils';
 import type { PresenceThresholds } from './utils';
+import { PUBLIC_SETTINGS } from './public-settings';
 
 export const createFriendsPanelController = ({
     friendsPanel,
     friendsToggle,
     requestRender,
     getReadableTextColor,
-    northMarkerId,
     selfFriend,
     selfPosition,
     presenceThresholds
@@ -15,11 +15,16 @@ export const createFriendsPanelController = ({
     friendsToggle: HTMLElement | null;
     requestRender: () => void;
     getReadableTextColor: (color: string) => string;
-    northMarkerId: string;
     selfFriend: any;
     selfPosition: { lat: number; lon: number };
     presenceThresholds: PresenceThresholds;
 }) => {
+    const guardianAngelEnabled = PUBLIC_SETTINGS.features.guardianAngelEnabled;
+    const northMarkerId = 'north-marker';
+    const guardianAngelId = 'guardian-angel';
+    const GUARDIAN_ORBIT_RADIUS_METERS = 75;
+    const GUARDIAN_ORBIT_PERIOD_MS = 90 * 1000;
+
     const PARTICIPANT_VISIBILITY_STORAGE_KEY = 'friend-compass-friend-visibility';
     const PARTICIPANTS_PANEL_VISIBLE_STORAGE_KEY = 'friend-compass-friends-panel-visible';
     const PARTICIPANT_ORDER_STORAGE_KEY = 'friend-compass-friend-order';
@@ -49,11 +54,27 @@ export const createFriendsPanelController = ({
 
     const getFriendDefaultRank = (friendId) => {
         if (friendId === northMarkerId) return 0;
-        return 1;
+        if (guardianAngelEnabled && friendId === guardianAngelId) return 1;
+        return 2;
     };
 
     const shouldShowMapLink = (friendId) => {
-        return friendId !== northMarkerId;
+        return friendId !== northMarkerId && (!guardianAngelEnabled || friendId !== guardianAngelId);
+    };
+
+    const getGuardianAngelPosition = (nowMs: number) => {
+        const latRad = selfPosition.lat * (Math.PI / 180);
+        const metersPerDegLat = 111320;
+        const metersPerDegLon = Math.max(111320 * Math.cos(latRad), 1);
+        const theta = (nowMs / GUARDIAN_ORBIT_PERIOD_MS) * (2 * Math.PI);
+
+        const latOffsetDeg = (GUARDIAN_ORBIT_RADIUS_METERS * Math.cos(theta)) / metersPerDegLat;
+        const lonOffsetDeg = (GUARDIAN_ORBIT_RADIUS_METERS * Math.sin(theta)) / metersPerDegLon;
+
+        return {
+            lat: selfPosition.lat + latOffsetDeg,
+            lon: selfPosition.lon + lonOffsetDeg,
+        };
     };
 
     const ensureFriendOrder = (friendsToRender) => {
@@ -272,6 +293,17 @@ export const createFriendsPanelController = ({
             });
         }
 
+        if (guardianAngelEnabled && friendsToRender.findIndex((p) => p.id === guardianAngelId) === -1) {
+            const guardianPosition = getGuardianAngelPosition(Date.now());
+            friendsToRender.push({
+                id: guardianAngelId,
+                name: 'Guardian Angel',
+                color: '#f59e0b',
+                position: guardianPosition,
+                timestamp: Date.now()
+            });
+        }
+
         return sortFriendsForDisplay(friendsToRender);
     };
 
@@ -304,8 +336,10 @@ export const createFriendsPanelController = ({
             nameWrapper.style.color = getReadableTextColor(friend.color);
 
             const isNorth = friend.id === northMarkerId;
+            const isGuardianAngel = guardianAngelEnabled && friend.id === guardianAngelId;
+            const isSpecialMarker = isNorth || isGuardianAngel;
 
-            if (!isNorth) {
+            if (!isSpecialMarker) {
                 const presenceStatus = document.createElement('span');
                 presenceStatus.className = 'friend-presence-status';
                 presenceStatus.dataset.friendId = friend.id;
diff --git a/src/public-settings.ts b/src/public-settings.ts
index 6664542..50bf610 100644
--- a/src/public-settings.ts
+++ b/src/public-settings.ts
@@ -31,6 +31,10 @@ export const PUBLIC_SETTINGS = {
         /** Delay before showing "no compass data" status. */
         probeTimeoutMs: 2500
     },
+    features: {
+        /** Whether to render the synthetic Guardian Angel marker. */
+        guardianAngelEnabled: false
+    },
     room: {
         /** Length of generated room IDs. */
         idLength: 8
diff --git a/src/renderer.ts b/src/renderer.ts
index 7e9d7aa..88bc29a 100644
--- a/src/renderer.ts
+++ b/src/renderer.ts
@@ -3,81 +3,6 @@ import type { PresenceThresholds } from './utils';
 
 type ArrowElement = HTMLElement & { currentAngle: number | null };
 
-const OVERLAP_CLUSTER_DEG = 8;
-const OVERLAP_SPACING_PX = 16;
-
-const getAngleDistance = (a: number, b: number) => Math.abs(getShortestDelta(a, b));
-
-const buildOverlapOffsets = ({
-    friendsToRender,
-    selfFriend,
-    selfHeading,
-    isFriendVisible,
-}: {
-    friendsToRender: any[];
-    selfFriend: any;
-    selfHeading: number;
-    isFriendVisible: (friendId: string) => boolean;
-}) => {
-    const angleEntries = friendsToRender
-        .filter((target) => target.id !== selfFriend.id && isFriendVisible(target.id))
-        .map((target) => {
-            const dx = target.position.lon - selfFriend.position.lon;
-            const dy = target.position.lat - selfFriend.position.lat;
-            const rawAngle = Math.atan2(dx, dy) * (180 / Math.PI);
-            return {
-                id: target.id,
-                angle: normalizeAngle(rawAngle - selfHeading),
-            };
-        })
-        .sort((a, b) => a.angle - b.angle);
-
-    if (angleEntries.length === 0) return new Map<string, number>();
-
-    const clusters: Array<Array<{ id: string; angle: number }>> = [];
-    for (const entry of angleEntries) {
-        const currentCluster = clusters[clusters.length - 1];
-        if (!currentCluster) {
-            clusters.push([entry]);
-            continue;
-        }
-
-        const lastInCluster = currentCluster[currentCluster.length - 1];
-        if (getAngleDistance(lastInCluster.angle, entry.angle) <= OVERLAP_CLUSTER_DEG) {
-            currentCluster.push(entry);
-        } else {
-            clusters.push([entry]);
-        }
-    }
-
-    if (clusters.length > 1) {
-        const firstCluster = clusters[0];
-        const lastCluster = clusters[clusters.length - 1];
-        const firstAngle = firstCluster[0].angle;
-        const lastAngle = lastCluster[lastCluster.length - 1].angle;
-        if (getAngleDistance(firstAngle, lastAngle) <= OVERLAP_CLUSTER_DEG) {
-            clusters[0] = [...lastCluster, ...firstCluster];
-            clusters.pop();
-        }
-    }
-
-    const offsetById = new Map<string, number>();
-    for (const cluster of clusters) {
-        if (cluster.length === 1) {
-            offsetById.set(cluster[0].id, 0);
-            continue;
-        }
-
-        const stableCluster = [...cluster].sort((a, b) => a.id.localeCompare(b.id));
-        const midpoint = (stableCluster.length - 1) / 2;
-        stableCluster.forEach((entry, index) => {
-            offsetById.set(entry.id, (index - midpoint) * OVERLAP_SPACING_PX);
-        });
-    }
-
-    return offsetById;
-};
-
 export const renderArrows = ({
     content,
     arrow,
@@ -89,12 +14,6 @@ export const renderArrows = ({
     presenceThresholds,
 }) => {
     const now = Date.now();
-    const overlapOffsetById = buildOverlapOffsets({
-        friendsToRender,
-        selfFriend,
-        selfHeading,
-        isFriendVisible,
-    });
     const activeArrowIds = new Set(
         friendsToRender
             .filter((target) => target.id !== selfFriend.id)
@@ -148,12 +67,7 @@ export const renderArrows = ({
         existingArrow.currentAngle = currentArrowAngle;
         existingArrow.style.transform = `rotate(${currentArrowAngle}deg)`;
         existingArrow.style.color = getArrowDisplayColor(target.color);
-
-        const overlapOffset = overlapOffsetById.get(target.id) ?? 0;
-        const angleRad = currentArrowAngle * (Math.PI / 180);
-        const offsetX = overlapOffset * Math.cos(angleRad);
-        const offsetY = overlapOffset * Math.sin(angleRad);
-        existingArrow.style.translate = `calc(-50% + ${offsetX.toFixed(2)}px) calc(-50% + ${offsetY.toFixed(2)}px)`;
+        existingArrow.style.translate = '';
 
         const presenceState = getFriendPresenceState(target, now, presenceThresholds);
         existingArrow.classList.toggle('is-online', presenceState === 'online');