5050 align-items : center;
5151 justify-content : center;
5252 position : relative;
53+ overflow : hidden;
5354 }
5455
56+ /* Horizon view - like looking at the ocean */
5557 # compass {
56- width : 300px ;
57- height : 300px ;
58- border-radius : 50% ;
59- background : rgba (0 , 0 , 0 , 0.5 );
60- border : 5px solid rgba (255 , 255 , 255 , 0.8 );
58+ width : 100% ;
59+ height : 100% ;
6160 position : relative;
61+ display : flex;
62+ flex-direction : column;
63+ justify-content : center;
64+ align-items : center;
65+ }
66+
67+ /* Compass strip that scrolls horizontally */
68+ # compass-strip {
69+ position : absolute;
70+ top : 50% ;
71+ left : 0 ;
72+ right : 0 ;
73+ height : 80px ;
74+ background : rgba (0 , 0 , 0 , 0.6 );
75+ border-top : 2px solid rgba (255 , 255 , 255 , 0.5 );
76+ border-bottom : 2px solid rgba (255 , 255 , 255 , 0.5 );
77+ display : flex;
78+ align-items : center;
79+ overflow : hidden;
80+ }
81+
82+ # compass-tape {
83+ display : flex;
84+ position : absolute;
85+ height : 100% ;
6286 transition : transform 0.1s ease-out;
87+ white-space : nowrap;
6388 }
6489
65- # compass-arrow {
90+ .compass-mark {
91+ display : inline-flex;
92+ flex-direction : column;
93+ align-items : center;
94+ justify-content : center;
95+ width : 60px ;
96+ font-weight : bold;
97+ color : rgba (255 , 255 , 255 , 0.8 );
98+ }
99+
100+ .compass-mark .major {
101+ font-size : 20px ;
102+ color : # FFD700 ;
103+ }
104+
105+ /* Center crosshair */
106+ # crosshair {
66107 position : absolute;
67108 top : 50% ;
68109 left : 50% ;
69- transform : translate (-50% , -50% ) rotate (0deg );
70- width : 0 ;
71- height : 0 ;
72- border-left : 20px solid transparent;
73- border-right : 20px solid transparent;
74- border-bottom : 120px solid # FF4444 ;
75- transition : transform 0.1s ease-out;
110+ transform : translate (-50% , -50% );
111+ width : 40px ;
112+ height : 40px ;
113+ pointer-events : none;
114+ z-index : 100 ;
76115 }
77116
78- # compass-arrow ::after {
117+ # crosshair ::before ,
118+ # crosshair ::after {
79119 content : '' ;
80120 position : absolute;
81- top : 120px ;
82- left : -20px ;
83- width : 0 ;
84- height : 0 ;
85- border-left : 20px solid transparent;
86- border-right : 20px solid transparent;
87- border-top : 120px solid # FFFFFF ;
121+ background : # FF4444 ;
88122 }
89123
90- .compass-label {
91- position : absolute;
92- font-weight : bold;
93- font-size : 20px ;
94- color : white;
124+ # crosshair ::before {
125+ left : 50% ;
126+ top : 0 ;
127+ width : 3px ;
128+ height : 100% ;
129+ margin-left : -1.5px ;
95130 }
96131
97- .compass-label .n { top : 10px ; left : 50% ; transform : translateX (-50% ); }
98- .compass-label .e { right : 10px ; top : 50% ; transform : translateY (-50% ); }
99- .compass-label .s { bottom : 10px ; left : 50% ; transform : translateX (-50% ); }
100- .compass-label .w { left : 10px ; top : 50% ; transform : translateY (-50% ); }
132+ # crosshair ::after {
133+ top : 50% ;
134+ left : 0 ;
135+ height : 3px ;
136+ width : 100% ;
137+ margin-top : -1.5px ;
138+ }
101139
102140 # heading-display {
103141 position : absolute;
104- top : 50 % ;
142+ top : 20 % ;
105143 left : 50% ;
106- transform : translate ( -50 % , -50% );
107- font-size : 48 px ;
144+ transform : translateX ( -50% );
145+ font-size : 64 px ;
108146 font-weight : bold;
109- text-shadow : 2 px 2 px 4 px rgba (0 , 0 , 0 , 0.5 );
147+ text-shadow : 3 px 3 px 6 px rgba (0 , 0 , 0 , 0.8 );
110148 z-index : 10 ;
149+ color : # FFD700 ;
111150 }
112151
113152 # destination {
@@ -186,12 +225,11 @@ <h1>🌊 Ocean Crosser</h1>
186225
187226 < div id ="compass-container ">
188227 < div id ="compass ">
189- < div class ="compass-label n "> N</ div >
190- < div class ="compass-label e "> E</ div >
191- < div class ="compass-label s "> S</ div >
192- < div class ="compass-label w "> W</ div >
193- < div id ="compass-arrow "> </ div >
194228 < div id ="heading-display "> --°</ div >
229+ < div id ="crosshair "> </ div >
230+ < div id ="compass-strip ">
231+ < div id ="compass-tape "> </ div >
232+ </ div >
195233 </ div >
196234 </ div >
197235
@@ -314,13 +352,15 @@ <h1>🌊 Ocean Crosser</h1>
314352 // State
315353 let userLocation = null ;
316354 let currentHeading = 0 ;
355+ let smoothedHeading = 0 ;
317356 let isCompassActive = false ;
318357 let debugMode = true ; // Set to true to see debug info
358+ const SMOOTHING_FACTOR = 0.15 ; // Lower = smoother but slower response
319359
320360 // DOM elements
321361 const statusEl = document . getElementById ( 'status' ) ;
322362 const headingDisplayEl = document . getElementById ( 'heading-display' ) ;
323- const compassArrowEl = document . getElementById ( 'compass-arrow ' ) ;
363+ const compassTapeEl = document . getElementById ( 'compass-tape ' ) ;
324364 const destinationNameEl = document . getElementById ( 'destination-name' ) ;
325365 const distanceEl = document . getElementById ( 'distance' ) ;
326366 const locationInfoEl = document . getElementById ( 'location-info' ) ;
@@ -331,6 +371,33 @@ <h1>🌊 Ocean Crosser</h1>
331371 debugEl . style . display = 'block' ;
332372 }
333373
374+ // Create compass tape with cardinal directions
375+ function createCompassTape ( ) {
376+ const directions = [
377+ { deg : 0 , label : 'N' , major : true } ,
378+ { deg : 45 , label : 'NE' , major : false } ,
379+ { deg : 90 , label : 'E' , major : true } ,
380+ { deg : 135 , label : 'SE' , major : false } ,
381+ { deg : 180 , label : 'S' , major : true } ,
382+ { deg : 225 , label : 'SW' , major : false } ,
383+ { deg : 270 , label : 'W' , major : true } ,
384+ { deg : 315 , label : 'NW' , major : false }
385+ ] ;
386+
387+ // Create tape 3 times to allow seamless wrapping
388+ for ( let repeat = 0 ; repeat < 3 ; repeat ++ ) {
389+ directions . forEach ( dir => {
390+ const mark = document . createElement ( 'div' ) ;
391+ mark . className = dir . major ? 'compass-mark major' : 'compass-mark' ;
392+ mark . textContent = dir . label ;
393+ mark . dataset . degree = dir . deg ;
394+ compassTapeEl . appendChild ( mark ) ;
395+ } ) ;
396+ }
397+ }
398+
399+ createCompassTape ( ) ;
400+
334401 // Utility functions
335402 function log ( message ) {
336403 console . log ( message ) ;
@@ -348,6 +415,18 @@ <h1>🌊 Ocean Crosser</h1>
348415 return radians * 180 / Math . PI ;
349416 }
350417
418+ // Smooth heading changes to reduce jitter
419+ function smoothHeading ( newHeading , oldHeading ) {
420+ // Handle angle wrapping (359° to 1° transition)
421+ let diff = newHeading - oldHeading ;
422+ if ( diff > 180 ) diff -= 360 ;
423+ if ( diff < - 180 ) diff += 360 ;
424+
425+ let smoothed = oldHeading + ( diff * SMOOTHING_FACTOR ) ;
426+ smoothed = ( smoothed + 360 ) % 360 ; // Normalize to 0-360
427+ return smoothed ;
428+ }
429+
351430 // Calculate distance between two points using Haversine formula
352431 function calculateDistance ( lat1 , lon1 , lat2 , lon2 ) {
353432 const R = 6371 ; // Earth's radius in km
@@ -479,8 +558,10 @@ <h1>🌊 Ocean Crosser</h1>
479558 }
480559
481560 if ( heading !== null ) {
482- currentHeading = Math . round ( heading ) ;
483- updateDisplay ( currentHeading ) ;
561+ currentHeading = heading ;
562+ // Apply smoothing to reduce jitter
563+ smoothedHeading = orientationEventCount === 1 ? heading : smoothHeading ( heading , smoothedHeading ) ;
564+ updateDisplay ( Math . round ( smoothedHeading ) ) ;
484565 } else {
485566 if ( orientationEventCount === 1 ) log ( 'Warning: No valid heading data available' ) ;
486567 }
@@ -497,19 +578,26 @@ <h1>🌊 Ocean Crosser</h1>
497578 absoluteEventCount ++ ;
498579
499580 if ( event . absolute && event . alpha !== null ) {
500- currentHeading = Math . round ( 360 - event . alpha ) ;
501- updateDisplay ( currentHeading ) ;
581+ let heading = 360 - event . alpha ;
582+ currentHeading = heading ;
583+ // Apply smoothing to reduce jitter
584+ smoothedHeading = absoluteEventCount === 1 ? heading : smoothHeading ( heading , smoothedHeading ) ;
585+ updateDisplay ( Math . round ( smoothedHeading ) ) ;
502586 }
503587 }
504588
505589 // Update display with current heading
506590 function updateDisplay ( heading ) {
507- // Update compass arrow rotation
508- compassArrowEl . style . transform = `translate(-50%, -50%) rotate(${ heading } deg)` ;
509-
510591 // Update heading display
511592 headingDisplayEl . textContent = `${ heading } °` ;
512593
594+ // Move compass tape (negative because we want the tape to move opposite of heading)
595+ // 60px per direction mark, 8 marks = 480px for 360°
596+ // Middle repeat starts at -480px (8 marks * 60px)
597+ const pixelsPerDegree = 480 / 360 ; // 1.333px per degree
598+ const offset = - heading * pixelsPerDegree - 480 ; // -480 to start at middle repeat
599+ compassTapeEl . style . transform = `translateX(${ offset } px)` ;
600+
513601 // Find destination in this direction
514602 const destination = findDestinationInDirection (
515603 userLocation . lat ,
@@ -520,7 +608,7 @@ <h1>🌊 Ocean Crosser</h1>
520608 if ( destination ) {
521609 destinationNameEl . textContent = destination . name ;
522610 distanceEl . textContent = `${ formatDistance ( destination . distance ) } away` ;
523- locationInfoEl . textContent = `Bearing: ${ Math . round ( destination . bearing ) } ° | Match: ${ ( 30 - destination . angleDiff ) . toFixed ( 0 ) } % accuracy ` ;
611+ locationInfoEl . textContent = `Bearing: ${ Math . round ( destination . bearing ) } ° | Match accuracy : ${ Math . round ( ( 30 - destination . angleDiff ) / 30 * 100 ) } %` ;
524612 statusEl . textContent = 'Tracking...' ;
525613 } else {
526614 destinationNameEl . textContent = 'Open Ocean' ;
0 commit comments