|
37 | 37 | align-items: center; |
38 | 38 | justify-content: center; |
39 | 39 | z-index: 5; |
40 | | - transition: opacity 0.3s ease; |
| 40 | + transition: opacity 0.2s ease; |
41 | 41 | } |
42 | 42 |
|
43 | 43 | .code-overlay.hidden { |
|
55 | 55 | font-weight: 900; |
56 | 56 | color: var(--sl-color-text); |
57 | 57 | margin-bottom: 1.5rem; |
58 | | - text-shadow: 0 12px 60px var(--sl-color-bg), |
59 | | - 0 10px 50px var(--sl-color-bg), |
60 | | - 0 8px 40px var(--sl-color-bg), |
61 | | - 0 6px 30px var(--sl-color-bg), |
62 | | - 0 4px 20px var(--sl-color-bg), |
63 | | - 0 2px 12px var(--sl-color-bg), |
64 | | - 0 0 80px rgba(0, 123, 110, 1); |
| 58 | + /* Simplified text-shadow for better performance */ |
| 59 | + text-shadow: 0 2px 20px rgba(0, 123, 110, 0.8), |
| 60 | + 0 4px 40px var(--sl-color-bg); |
65 | 61 | } |
66 | 62 |
|
67 | 63 | .overlay-list { |
|
71 | 67 | margin-bottom: 2rem; |
72 | 68 | text-align: left; |
73 | 69 | font-weight: 600; |
74 | | - text-shadow: 0 12px 60px var(--sl-color-bg), |
75 | | - 0 10px 50px var(--sl-color-bg), |
76 | | - 0 8px 40px var(--sl-color-bg), |
77 | | - 0 6px 30px var(--sl-color-bg), |
78 | | - 0 4px 20px var(--sl-color-bg), |
79 | | - 0 2px 12px var(--sl-color-bg), |
80 | | - 0 0 80px rgba(0, 123, 110, 1); |
| 70 | + /* Simplified text-shadow for better performance */ |
| 71 | + text-shadow: 0 2px 20px rgba(0, 123, 110, 0.8), |
| 72 | + 0 4px 40px var(--sl-color-bg); |
81 | 73 | } |
82 | 74 |
|
83 | 75 | /* Base button styling following Starlight's pattern */ |
|
97 | 89 | padding: 0.9375rem 1.25rem; |
98 | 90 | text-decoration: none; |
99 | 91 |
|
100 | | - /* Glow and animation effects */ |
| 92 | + /* Static glow effect - no continuous animation for better performance */ |
101 | 93 | box-shadow: 0 8px 32px rgba(0, 123, 110, 0.4), |
102 | | - 0 0 60px rgba(0, 123, 110, 0.25); |
103 | | - animation: pulse-glow 2s ease-in-out infinite; |
| 94 | + 0 0 40px rgba(0, 123, 110, 0.2); |
104 | 95 | position: relative; |
105 | 96 | overflow: hidden; |
106 | 97 | pointer-events: auto; |
107 | 98 |
|
108 | 99 | /* Smooth transitions */ |
109 | | - transition: transform 0.5s cubic-bezier(0.4, 0, 0.2, 1), |
110 | | - box-shadow 0.5s cubic-bezier(0.4, 0, 0.2, 1); |
111 | | - } |
112 | | - |
113 | | - @keyframes pulse-glow { |
114 | | - 0%, 100% { |
115 | | - box-shadow: 0 8px 32px rgba(0, 123, 110, 0.4), |
116 | | - 0 0 60px rgba(0, 123, 110, 0.25); |
117 | | - } |
118 | | - 50% { |
119 | | - box-shadow: 0 8px 40px rgba(0, 123, 110, 0.55), |
120 | | - 0 0 80px rgba(0, 123, 110, 0.4); |
121 | | - } |
| 100 | + transition: transform 0.3s ease, |
| 101 | + box-shadow 0.3s ease; |
122 | 102 | } |
123 | 103 |
|
124 | 104 | .reveal-button::before { |
|
137 | 117 |
|
138 | 118 | .reveal-button.sl-button-primary:hover { |
139 | 119 | color: var(--sl-color-black); |
140 | | - transform: scale(1.08); |
141 | | - box-shadow: 0 8px 32px rgba(0, 123, 110, 0.4), |
142 | | - 0 0 60px rgba(0, 123, 110, 0.25); |
143 | | - animation: none; |
| 120 | + transform: scale(1.05); |
| 121 | + box-shadow: 0 8px 40px rgba(0, 123, 110, 0.5), |
| 122 | + 0 0 50px rgba(0, 123, 110, 0.3); |
144 | 123 | } |
145 | 124 |
|
146 | 125 | .reveal-button:hover::before { |
|
251 | 230 | .reveal-button::before, |
252 | 231 | .reset-overlay-button, |
253 | 232 | .scroll-top-button { |
254 | | - transition: none; |
255 | | - animation: none; |
| 233 | + transition: none !important; |
| 234 | + animation: none !important; |
256 | 235 | } |
257 | 236 | } |
258 | 237 |
|
|
275 | 254 | min-height: 0; |
276 | 255 | overflow: auto; /* The actual scroller */ |
277 | 256 | padding: 0; |
278 | | - scroll-behavior: smooth; |
279 | 257 | scrollbar-width: thin; |
280 | 258 | scrollbar-color: var(--sl-color-accent) var(--sl-color-gray-5); |
281 | 259 | } |
|
324 | 302 | } |
325 | 303 | } |
326 | 304 |
|
327 | | - // Global flag to track if auto-scroll animation is running |
328 | | - let isAutoScrolling = false; |
329 | | - |
330 | | - // Flag to track if programmatic scroll is happening (auto-scroll or scroll-to-top) |
| 305 | + // Flag to track if programmatic scroll is happening (scroll-to-top button) |
331 | 306 | let isProgrammaticScroll = false; |
332 | 307 |
|
333 | 308 | // Flag to track if manual scroll event has been sent (only send once) |
|
341 | 316 |
|
342 | 317 | if (!button || !overlay || !scrollableContainer || !scrollableInner) return; |
343 | 318 |
|
344 | | - button.addEventListener('click', async () => { |
| 319 | + button.addEventListener('click', () => { |
345 | 320 | // Track custom event in Plausible |
346 | 321 | if (typeof window.plausible !== 'undefined') { |
347 | 322 | window.plausible('home:reveal-code'); |
348 | 323 | } |
349 | 324 |
|
350 | | - const wait = (ms: number) => new Promise(resolve => setTimeout(resolve, ms)); |
351 | | - |
352 | | - // Helper to animate scroll with custom duration (cancellable on user interaction) |
353 | | - const animateScroll = (element: HTMLElement, targetScroll: number, duration: number) => { |
354 | | - return new Promise<void>((resolve) => { |
355 | | - const start = element.scrollTop; |
356 | | - const distance = targetScroll - start; |
357 | | - const startTime = performance.now(); |
358 | | - let animationId: number | null = null; |
359 | | - let cancelled = false; |
360 | | - |
361 | | - // Cancel animation if user manually scrolls |
362 | | - const cancelAnimation = () => { |
363 | | - cancelled = true; |
364 | | - isAutoScrolling = false; |
365 | | - if (animationId !== null) { |
366 | | - cancelAnimationFrame(animationId); |
367 | | - } |
368 | | - cleanup(); |
369 | | - resolve(); |
370 | | - }; |
371 | | - |
372 | | - // Listen for user scroll interactions |
373 | | - const handleWheel = () => cancelAnimation(); |
374 | | - const handleTouch = () => cancelAnimation(); |
375 | | - const handleClick = () => cancelAnimation(); |
376 | | - const handleKeydown = (e: KeyboardEvent) => { |
377 | | - if (['ArrowUp', 'ArrowDown', 'PageUp', 'PageDown', 'Home', 'End', ' '].includes(e.key)) { |
378 | | - cancelAnimation(); |
379 | | - } |
380 | | - }; |
381 | | - |
382 | | - element.addEventListener('wheel', handleWheel, { passive: true }); |
383 | | - element.addEventListener('touchstart', handleTouch, { passive: true }); |
384 | | - element.addEventListener('click', handleClick); |
385 | | - element.addEventListener('keydown', handleKeydown); |
386 | | - |
387 | | - const cleanup = () => { |
388 | | - element.removeEventListener('wheel', handleWheel); |
389 | | - element.removeEventListener('touchstart', handleTouch); |
390 | | - element.removeEventListener('click', handleClick); |
391 | | - element.removeEventListener('keydown', handleKeydown); |
392 | | - }; |
393 | | - |
394 | | - const scroll = (currentTime: number) => { |
395 | | - if (cancelled) return; |
396 | | - |
397 | | - const elapsed = currentTime - startTime; |
398 | | - const progress = Math.min(elapsed / duration, 1); |
399 | | - |
400 | | - // Ease-in-out quadratic for gentle smoothing |
401 | | - const eased = progress < 0.5 |
402 | | - ? 2 * progress * progress |
403 | | - : 1 - Math.pow(-2 * progress + 2, 2) / 2; |
404 | | - |
405 | | - element.scrollTop = start + distance * eased; |
406 | | - |
407 | | - if (progress < 1) { |
408 | | - animationId = requestAnimationFrame(scroll); |
409 | | - } else { |
410 | | - cleanup(); |
411 | | - isAutoScrolling = false; |
412 | | - resolve(); |
413 | | - } |
414 | | - }; |
415 | | - |
416 | | - animationId = requestAnimationFrame(scroll); |
417 | | - }); |
418 | | - }; |
419 | | - |
420 | | - // 1. Enable scrolling and start animation first |
421 | | - isAutoScrolling = true; |
422 | 325 | scrollableContainer.classList.add('scrollable-enabled'); |
423 | | - const scrollPromise = animateScroll(scrollableInner, scrollableInner.scrollHeight, 3000); |
| 326 | + overlay.style.opacity = '0'; |
424 | 327 |
|
425 | | - // Small delay so scroll starts accelerating before overlay fades |
426 | | - await wait(100); |
| 328 | + // Custom 3-second scroll animation |
| 329 | + const startScroll = scrollableInner.scrollTop; |
| 330 | + const targetScroll = scrollableInner.scrollHeight; |
| 331 | + const distance = targetScroll - startScroll; |
| 332 | + const duration = 3000; // 3 seconds |
| 333 | + const startTime = performance.now(); |
427 | 334 |
|
428 | | - // 2. Fade out overlay while scroll is already in motion |
429 | | - overlay.style.opacity = '0'; |
| 335 | + function easeOutCubic(t: number) { |
| 336 | + return 1 - Math.pow(1 - t, 3); |
| 337 | + } |
430 | 338 |
|
431 | | - // Wait for overlay fade, then hide it |
432 | | - await wait(300); |
433 | | - overlay.classList.add('hidden'); |
| 339 | + function animateScroll(currentTime: number) { |
| 340 | + const elapsed = currentTime - startTime; |
| 341 | + const progress = Math.min(elapsed / duration, 1); |
| 342 | + const easedProgress = easeOutCubic(progress); |
434 | 343 |
|
435 | | - // 3. Wait for scroll to complete |
436 | | - await scrollPromise; |
| 344 | + scrollableInner.scrollTop = startScroll + (distance * easedProgress); |
437 | 345 |
|
438 | | - // 4. Show reset button after scroll completes |
439 | | - const resetButton = document.querySelector('.reset-overlay-button') as HTMLElement; |
440 | | - if (resetButton) { |
441 | | - resetButton.style.display = 'block'; |
| 346 | + if (progress < 1) { |
| 347 | + requestAnimationFrame(animateScroll); |
| 348 | + } |
442 | 349 | } |
443 | 350 |
|
444 | | - // 5. Trigger scroll-to-top button visibility check now that animation is done |
445 | | - const scrollTopButton = document.querySelector('.scroll-top-button') as HTMLElement; |
446 | | - if (scrollTopButton && scrollableInner.scrollTop > 50) { |
447 | | - scrollTopButton.style.display = 'block'; |
448 | | - } |
| 351 | + requestAnimationFrame(animateScroll); |
| 352 | + |
| 353 | + setTimeout(() => overlay.classList.add('hidden'), 200); |
| 354 | + setTimeout(() => { |
| 355 | + const resetButton = document.querySelector('.reset-overlay-button') as HTMLElement; |
| 356 | + if (resetButton) resetButton.style.display = 'block'; |
| 357 | + }, 2000); |
449 | 358 | }); |
450 | 359 | }; |
451 | 360 |
|
|
457 | 366 |
|
458 | 367 | // Show/hide button based on scroll position |
459 | 368 | const updateButtonVisibility = () => { |
460 | | - // Don't show button during auto-scroll animation |
461 | | - if (isAutoScrolling) { |
462 | | - button.style.display = 'none'; |
463 | | - return; |
464 | | - } |
465 | | - |
466 | 369 | // Track manual scroll event (only once, and only if not programmatic) |
467 | 370 | if (!hasTrackedManualScroll && !isProgrammaticScroll) { |
468 | 371 | hasTrackedManualScroll = true; |
|
0 commit comments