@@ -54,36 +54,90 @@ export const TourManager: React.FC = () => {
5454
5555 // If position is null and we haven't found the target yet, retry after a short delay
5656 if ( ! position ) {
57- const retryCount = 5
58- const retryDelay = 100
57+ const retryCount = 10
58+ const retryDelay = 200
5959
6060 const retry = ( attempt : number ) => {
61- if ( attempt >= retryCount ) return
61+ if ( attempt >= retryCount ) {
62+ return
63+ }
6264
6365 setTimeout ( ( ) => {
6466 const retryPosition = calculateTargetPosition ( )
6567 if ( retryPosition ) {
6668 setTargetPosition ( retryPosition )
69+ // Auto-scroll to ensure target element is visible
70+ ensureTargetIsVisible ( )
6771 } else {
6872 retry ( attempt + 1 )
6973 }
7074 } , retryDelay )
7175 }
7276
7377 retry ( 0 )
78+ } else {
79+ // Auto-scroll to ensure target element is visible
80+ ensureTargetIsVisible ( )
7481 }
7582 }
7683
77- updatePosition ( )
84+ const ensureTargetIsVisible = ( ) => {
85+ const targetElement = document . getElementById ( currentStep . targetId )
86+ if ( targetElement ) {
87+ const rect = targetElement . getBoundingClientRect ( )
88+ const viewportHeight = window . innerHeight
89+ const margin = 100 // Give some margin
90+
91+ // Only scroll if element is significantly off-screen
92+ const isSignificantlyOffScreen =
93+ rect . top < margin ||
94+ rect . bottom > viewportHeight - margin ||
95+ rect . left < 0 ||
96+ rect . right > window . innerWidth
97+
98+ if ( isSignificantlyOffScreen ) {
99+ // Use a gentler scroll that doesn't center aggressively
100+ targetElement . scrollIntoView ( {
101+ behavior : 'smooth' ,
102+ block : 'nearest' ,
103+ inline : 'nearest' ,
104+ } )
105+ }
106+ }
107+ }
78108
79- // Update position on scroll/resize
80- const handleScroll = ( ) => updatePosition ( )
81- window . addEventListener ( 'scroll' , handleScroll )
82- window . addEventListener ( 'resize' , handleScroll )
109+ // Add a small delay to ensure DOM is ready
110+ setTimeout ( ( ) => {
111+ updatePosition ( )
112+ } , 100 )
113+
114+ // Update position on scroll/resize with throttling for better performance
115+ let throttleTimer : NodeJS . Timeout | null = null
116+ const handleScroll = ( ) => {
117+ if ( throttleTimer ) return
118+ throttleTimer = setTimeout ( ( ) => {
119+ updatePosition ( )
120+ throttleTimer = null
121+ } , 16 ) // ~60fps
122+ }
123+
124+ const handleResize = ( ) => {
125+ if ( throttleTimer ) return
126+ throttleTimer = setTimeout ( ( ) => {
127+ updatePosition ( )
128+ throttleTimer = null
129+ } , 16 ) // ~60fps
130+ }
131+
132+ window . addEventListener ( 'scroll' , handleScroll , { passive : true } )
133+ window . addEventListener ( 'resize' , handleResize , { passive : true } )
83134
84135 return ( ) => {
85136 window . removeEventListener ( 'scroll' , handleScroll )
86- window . removeEventListener ( 'resize' , handleScroll )
137+ window . removeEventListener ( 'resize' , handleResize )
138+ if ( throttleTimer ) {
139+ clearTimeout ( throttleTimer )
140+ }
87141 }
88142 } else {
89143 // Reset target position when tour is not active
@@ -132,20 +186,30 @@ export const TourManager: React.FC = () => {
132186 skipTour ,
133187 ] )
134188
135- // Calculate tooltip position
189+ // Calculate tooltip position with responsive design
136190 const getTooltipPosition = ( ) => {
137- if ( ! targetPosition || ! currentStep ) return { top : 0 , left : 0 }
191+ if ( ! targetPosition || ! currentStep ) return { top : 0 , left : 0 , width : 320 }
138192
139193 const placement = currentStep . placement || 'bottom'
140194 const offset = currentStep . offset || { x : 0 , y : 0 }
141- const tooltipWidth = 320
142- const tooltipHeight = 200 // Approximate height
143- const gap = 20
195+ const isMobile = window . innerWidth <= 768
196+ const gap = isMobile ? 12 : 20
197+
198+ // Responsive tooltip dimensions
199+ const tooltipWidth = isMobile ? Math . min ( 320 , window . innerWidth - 40 ) : 320
200+ const tooltipHeight = isMobile ? 180 : 200 // Slightly smaller on mobile
201+ const padding = isMobile ? 12 : 20
144202
145203 let top = 0
146204 let left = 0
147205
148- switch ( placement ) {
206+ // Only modify placement on very small screens (mobile)
207+ const effectivePlacement =
208+ isMobile && window . innerWidth <= 480 && placement === 'left'
209+ ? 'bottom'
210+ : placement
211+
212+ switch ( effectivePlacement ) {
149213 case 'top' :
150214 top = targetPosition . top - tooltipHeight - gap
151215 left = targetPosition . left + targetPosition . width / 2 - tooltipWidth / 2
@@ -164,18 +228,28 @@ export const TourManager: React.FC = () => {
164228 break
165229 }
166230
167- // Ensure tooltip stays within viewport
168- const padding = 20
169- top = Math . max (
170- padding ,
171- Math . min ( top , window . innerHeight - tooltipHeight - padding ) ,
172- )
173- left = Math . max (
174- padding ,
175- Math . min ( left , window . innerWidth - tooltipWidth - padding ) ,
176- )
177-
178- return { top : top + offset . y , left : left + offset . x }
231+ // Ensure tooltip stays within viewport - be more conservative on desktop
232+ const viewportHeight = window . innerHeight
233+ const viewportWidth = window . innerWidth
234+
235+ // Adjust if tooltip goes off-screen
236+ if ( top < padding ) {
237+ top = padding
238+ } else if ( top + tooltipHeight > viewportHeight - padding ) {
239+ top = viewportHeight - tooltipHeight - padding
240+ }
241+
242+ if ( left < padding ) {
243+ left = padding
244+ } else if ( left + tooltipWidth > viewportWidth - padding ) {
245+ left = viewportWidth - tooltipWidth - padding
246+ }
247+
248+ return {
249+ top : top + offset . y ,
250+ left : left + offset . x ,
251+ width : tooltipWidth ,
252+ }
179253 }
180254
181255 const handleNext = ( ) => {
@@ -236,21 +310,22 @@ export const TourManager: React.FC = () => {
236310 animate = { { opacity : 1 , y : 0 } }
237311 exit = { { opacity : 0 , y : 20 } }
238312 transition = { { duration : 0.3 , delay : 0.1 } }
239- className = "absolute z-[10000] w-80 rounded-lg bg-background-1 shadow-xl"
313+ className = "absolute z-[10000] rounded-lg bg-background-1 shadow-xl"
240314 style = { {
241315 top : tooltipPosition . top ,
242316 left : tooltipPosition . left ,
317+ width : tooltipPosition . width ,
243318 } }
244319 >
245320 { /* Header */ }
246- < div className = "flex items-center justify-between border-b border-white/10 p-4" >
247- < div className = "flex items-center gap-3" >
248- < div className = "flex h-8 w-8 items-center justify-center rounded-full bg-human-4" >
249- < span className = "text-sm font-bold text-white" >
321+ < div className = "flex items-center justify-between border-b border-white/10 p-3 sm:p- 4" >
322+ < div className = "flex items-center gap-2 sm:gap- 3" >
323+ < div className = "flex h-6 w-6 items-center justify-center rounded-full bg-human-4 sm:h-8 sm:w-8 " >
324+ < span className = "text-xs font-bold text-white sm:text-sm " >
250325 { tourState . currentStep + 1 }
251326 </ span >
252327 </ div >
253- < h3 className = "text-lg font-semibold text-primary" >
328+ < h3 className = "text-base font-semibold text-primary sm:text-lg " >
254329 { currentStep . title }
255330 </ h3 >
256331 </ div >
@@ -259,24 +334,26 @@ export const TourManager: React.FC = () => {
259334 className = "text-secondary hover:text-primary"
260335 title = "Skip tour"
261336 >
262- < span className = "material-symbols-outlined" > close</ span >
337+ < span className = "material-symbols-outlined text-lg sm:text-xl" >
338+ close
339+ </ span >
263340 </ button >
264341 </ div >
265342
266343 { /* Content */ }
267- < div className = "p-4" >
268- < p className = "text-sm leading-relaxed text-secondary" >
344+ < div className = "p-3 sm:p- 4" >
345+ < p className = "text-xs leading-relaxed text-secondary sm:text-sm " >
269346 { currentStep . description }
270347 </ p >
271348 </ div >
272349
273350 { /* Footer */ }
274- < div className = "flex items-center justify-between border-t border-white/10 p-4" >
351+ < div className = "flex items-center justify-between border-t border-white/10 p-3 sm:p- 4" >
275352 < div className = "flex items-center gap-1" >
276353 { tourState . steps . map ( ( _ , index ) => (
277354 < div
278355 key = { index }
279- className = { `h-2 w-2 rounded-full ${
356+ className = { `h-1.5 w-1.5 rounded-full sm:h-2 sm:w-2 ${
280357 index === tourState . currentStep
281358 ? 'bg-human-4'
282359 : index < tourState . currentStep
@@ -287,27 +364,27 @@ export const TourManager: React.FC = () => {
287364 ) ) }
288365 </ div >
289366
290- < div className = "flex items-center gap-2" >
367+ < div className = "flex items-center gap-1 sm:gap- 2" >
291368 < button
292369 onClick = { handlePrev }
293370 disabled = { tourState . currentStep === 0 }
294- className = "flex items-center gap-1 rounded bg-background-2 px-3 py-1 text-sm text-secondary transition-colors hover:bg-background-3 disabled:cursor-not-allowed disabled:opacity-50"
371+ className = "flex items-center gap-1 rounded bg-background-2 px-2 py-1 text-xs text-secondary transition-colors hover:bg-background-3 disabled:cursor-not-allowed disabled:opacity-50 sm:px-3 sm:text-sm "
295372 >
296- < span className = "material-symbols-outlined text-sm" >
373+ < span className = "material-symbols-outlined text-xs sm:text- sm" >
297374 arrow_back
298375 </ span >
299- Previous
376+ < span className = "hidden sm:inline" > Previous</ span >
300377 </ button >
301378
302379 < button
303380 onClick = { handleNext }
304- className = "flex items-center gap-1 rounded bg-human-4 px-3 py-1 text-sm text-white transition-colors hover:bg-human-4/80"
381+ className = "flex items-center gap-1 rounded bg-human-4 px-2 py-1 text-xs text-white transition-colors hover:bg-human-4/80 sm:px-3 sm:text-sm "
305382 >
306383 { tourState . currentStep === tourState . steps . length - 1
307384 ? 'Finish'
308385 : 'Next' }
309386 { tourState . currentStep < tourState . steps . length - 1 && (
310- < span className = "material-symbols-outlined text-sm" >
387+ < span className = "material-symbols-outlined text-xs sm:text- sm" >
311388 arrow_forward
312389 </ span >
313390 ) }
@@ -316,9 +393,12 @@ export const TourManager: React.FC = () => {
316393 </ div >
317394
318395 { /* Keyboard hints */ }
319- < div className = "border-t border-white/10 px-4 py-2" >
396+ < div className = "border-t border-white/10 px-3 py-2 sm:px-4 " >
320397 < p className = "text-xs text-secondary" >
321- Use arrow keys to navigate • Press Escape to skip
398+ < span className = "hidden sm:inline" >
399+ Use arrow keys to navigate • Press Escape to skip
400+ </ span >
401+ < span className = "sm:hidden" > Tap to navigate • Tap X to skip</ span >
322402 </ p >
323403 </ div >
324404 </ motion . div >
0 commit comments