fuzzyworld

north-star / commit

add enable compass logic

49e9f35c415523cb72bdeff9337a95a56e43e255 · Pavel Safronov · 2026-03-14 21:28:48 -0700 · parents d66a6d5521e1

add enable compass logic
 src/app.js     | 53 ++++++++++++++++++++++++++++++++++++++++++++++++++++-
 src/index.html |  2 +-
 src/style.css  |  5 +++++
 3 files changed, 58 insertions(+), 2 deletions(-)

diff --git a/src/app.js b/src/app.js
index fe06c90..ccf2c89 100644
--- a/src/app.js
+++ b/src/app.js
@@ -126,6 +126,34 @@ if (createRoomLink) {
     createRoomLink.href = BASE_ROOM_URL;
 }
 
+const requiresExplicitCompassPermission =
+    typeof DeviceOrientationEvent !== 'undefined' &&
+    typeof DeviceOrientationEvent.requestPermission === 'function';
+let compassEnabled = !requiresExplicitCompassPermission;
+
+const updateCompassButtonState = () => {
+    if (!calibrateHeadingLink) return;
+
+    calibrateHeadingLink.textContent = compassEnabled ? 'Calibrate North' : 'Enable Compass';
+    calibrateHeadingLink.classList.toggle('compass-required', !compassEnabled);
+};
+
+const requestCompassPermission = async () => {
+    if (!requiresExplicitCompassPermission) {
+        return true;
+    }
+
+    try {
+        const permissionState = await DeviceOrientationEvent.requestPermission();
+        return permissionState === 'granted';
+    } catch (err) {
+        console.error('Failed to request compass permission', err);
+        return false;
+    }
+};
+
+updateCompassButtonState();
+
 let inviteStatusTimer = null;
 const setInviteStatus = (message, isError = false) => {
     if (!inviteRoomStatus) return;
@@ -228,9 +256,22 @@ if (joinRoomLink) {
 }
 
 if (calibrateHeadingLink) {
-    calibrateHeadingLink.addEventListener('click', (event) => {
+    calibrateHeadingLink.addEventListener('click', async (event) => {
         event.preventDefault();
 
+        if (!compassEnabled) {
+            const granted = await requestCompassPermission();
+            if (!granted) {
+                alert('Compass permission was denied. Enable Motion & Orientation Access in Safari settings and try again.');
+                return;
+            }
+
+            compassEnabled = true;
+            updateCompassButtonState();
+            setInviteStatus('Compass enabled');
+            return;
+        }
+
         if (lastRawHeading === null) {
             alert('Heading sensor is not ready yet. Try again in a moment.');
             return;
@@ -549,6 +590,11 @@ window.addEventListener('deviceorientationabsolute', (event) => {
     const heading = getHeadingFromEvent(event);
     if (heading === null) return;
 
+    if (!compassEnabled) {
+        compassEnabled = true;
+        updateCompassButtonState();
+    }
+
     hasAbsoluteOrientation = true;
     lastRawHeading = heading;
     selfHeading = normalizeAngle(heading + headingCorrectionDeg);
@@ -561,6 +607,11 @@ window.addEventListener('deviceorientation', (event) => {
     const heading = getHeadingFromEvent(event);
     if (heading === null) return;
 
+    if (!compassEnabled) {
+        compassEnabled = true;
+        updateCompassButtonState();
+    }
+
     lastRawHeading = heading;
     selfHeading = normalizeAngle(heading + headingCorrectionDeg);
     requestRender();
diff --git a/src/index.html b/src/index.html
index ced7d48..ddf2aab 100644
--- a/src/index.html
+++ b/src/index.html
@@ -28,7 +28,7 @@
     </div>
     <div id="content">
         <div class="panel-actions">
-            <button id="calibrate-heading-link" class="participants-toggle" type="button">Calibrate North</button>
+            <button id="calibrate-heading-link" class="participants-toggle" type="button">Enable Compass</button>
             <button id="participants-toggle" class="participants-toggle" type="button" aria-expanded="true">Hide
                 Participants</button>
         </div>
diff --git a/src/style.css b/src/style.css
index c3cad48..ed05958 100644
--- a/src/style.css
+++ b/src/style.css
@@ -179,6 +179,11 @@ a {
     font-weight: 700;
 }
 
+.participants-toggle.compass-required {
+    border: 2px solid #ef4444;
+    box-shadow: 0 0 0 1px rgba(239, 68, 68, 0.35);
+}
+
 .participants-panel {
     position: absolute;
     right: 10px;