Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
183 changes: 43 additions & 140 deletions pkgs/website/src/components/CodeOverlay.astro
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
align-items: center;
justify-content: center;
z-index: 5;
transition: opacity 0.3s ease;
transition: opacity 0.2s ease;
}

.code-overlay.hidden {
Expand All @@ -55,13 +55,9 @@
font-weight: 900;
color: var(--sl-color-text);
margin-bottom: 1.5rem;
text-shadow: 0 12px 60px var(--sl-color-bg),
0 10px 50px var(--sl-color-bg),
0 8px 40px var(--sl-color-bg),
0 6px 30px var(--sl-color-bg),
0 4px 20px var(--sl-color-bg),
0 2px 12px var(--sl-color-bg),
0 0 80px rgba(0, 123, 110, 1);
/* Simplified text-shadow for better performance */
text-shadow: 0 2px 20px rgba(0, 123, 110, 0.8),
0 4px 40px var(--sl-color-bg);
}

.overlay-list {
Expand All @@ -71,13 +67,9 @@
margin-bottom: 2rem;
text-align: left;
font-weight: 600;
text-shadow: 0 12px 60px var(--sl-color-bg),
0 10px 50px var(--sl-color-bg),
0 8px 40px var(--sl-color-bg),
0 6px 30px var(--sl-color-bg),
0 4px 20px var(--sl-color-bg),
0 2px 12px var(--sl-color-bg),
0 0 80px rgba(0, 123, 110, 1);
/* Simplified text-shadow for better performance */
text-shadow: 0 2px 20px rgba(0, 123, 110, 0.8),
0 4px 40px var(--sl-color-bg);
}

/* Base button styling following Starlight's pattern */
Expand All @@ -97,28 +89,16 @@
padding: 0.9375rem 1.25rem;
text-decoration: none;

/* Glow and animation effects */
/* Static glow effect - no continuous animation for better performance */
box-shadow: 0 8px 32px rgba(0, 123, 110, 0.4),
0 0 60px rgba(0, 123, 110, 0.25);
animation: pulse-glow 2s ease-in-out infinite;
0 0 40px rgba(0, 123, 110, 0.2);
position: relative;
overflow: hidden;
pointer-events: auto;

/* Smooth transitions */
transition: transform 0.5s cubic-bezier(0.4, 0, 0.2, 1),
box-shadow 0.5s cubic-bezier(0.4, 0, 0.2, 1);
}

@keyframes pulse-glow {
0%, 100% {
box-shadow: 0 8px 32px rgba(0, 123, 110, 0.4),
0 0 60px rgba(0, 123, 110, 0.25);
}
50% {
box-shadow: 0 8px 40px rgba(0, 123, 110, 0.55),
0 0 80px rgba(0, 123, 110, 0.4);
}
transition: transform 0.3s ease,
box-shadow 0.3s ease;
}

.reveal-button::before {
Expand All @@ -137,10 +117,9 @@

.reveal-button.sl-button-primary:hover {
color: var(--sl-color-black);
transform: scale(1.08);
box-shadow: 0 8px 32px rgba(0, 123, 110, 0.4),
0 0 60px rgba(0, 123, 110, 0.25);
animation: none;
transform: scale(1.05);
box-shadow: 0 8px 40px rgba(0, 123, 110, 0.5),
0 0 50px rgba(0, 123, 110, 0.3);
}

.reveal-button:hover::before {
Expand Down Expand Up @@ -251,8 +230,8 @@
.reveal-button::before,
.reset-overlay-button,
.scroll-top-button {
transition: none;
animation: none;
transition: none !important;
animation: none !important;
}
}

Expand All @@ -275,7 +254,6 @@
min-height: 0;
overflow: auto; /* The actual scroller */
padding: 0;
scroll-behavior: smooth;
scrollbar-width: thin;
scrollbar-color: var(--sl-color-accent) var(--sl-color-gray-5);
}
Expand Down Expand Up @@ -324,10 +302,7 @@
}
}

// Global flag to track if auto-scroll animation is running
let isAutoScrolling = false;

// Flag to track if programmatic scroll is happening (auto-scroll or scroll-to-top)
// Flag to track if programmatic scroll is happening (scroll-to-top button)
let isProgrammaticScroll = false;

// Flag to track if manual scroll event has been sent (only send once)
Expand All @@ -341,111 +316,45 @@

if (!button || !overlay || !scrollableContainer || !scrollableInner) return;

button.addEventListener('click', async () => {
button.addEventListener('click', () => {
// Track custom event in Plausible
if (typeof window.plausible !== 'undefined') {
window.plausible('home:reveal-code');
}

const wait = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));

// Helper to animate scroll with custom duration (cancellable on user interaction)
const animateScroll = (element: HTMLElement, targetScroll: number, duration: number) => {
return new Promise<void>((resolve) => {
const start = element.scrollTop;
const distance = targetScroll - start;
const startTime = performance.now();
let animationId: number | null = null;
let cancelled = false;

// Cancel animation if user manually scrolls
const cancelAnimation = () => {
cancelled = true;
isAutoScrolling = false;
if (animationId !== null) {
cancelAnimationFrame(animationId);
}
cleanup();
resolve();
};

// Listen for user scroll interactions
const handleWheel = () => cancelAnimation();
const handleTouch = () => cancelAnimation();
const handleClick = () => cancelAnimation();
const handleKeydown = (e: KeyboardEvent) => {
if (['ArrowUp', 'ArrowDown', 'PageUp', 'PageDown', 'Home', 'End', ' '].includes(e.key)) {
cancelAnimation();
}
};

element.addEventListener('wheel', handleWheel, { passive: true });
element.addEventListener('touchstart', handleTouch, { passive: true });
element.addEventListener('click', handleClick);
element.addEventListener('keydown', handleKeydown);

const cleanup = () => {
element.removeEventListener('wheel', handleWheel);
element.removeEventListener('touchstart', handleTouch);
element.removeEventListener('click', handleClick);
element.removeEventListener('keydown', handleKeydown);
};

const scroll = (currentTime: number) => {
if (cancelled) return;

const elapsed = currentTime - startTime;
const progress = Math.min(elapsed / duration, 1);

// Ease-in-out quadratic for gentle smoothing
const eased = progress < 0.5
? 2 * progress * progress
: 1 - Math.pow(-2 * progress + 2, 2) / 2;

element.scrollTop = start + distance * eased;

if (progress < 1) {
animationId = requestAnimationFrame(scroll);
} else {
cleanup();
isAutoScrolling = false;
resolve();
}
};

animationId = requestAnimationFrame(scroll);
});
};

// 1. Enable scrolling and start animation first
isAutoScrolling = true;
scrollableContainer.classList.add('scrollable-enabled');
const scrollPromise = animateScroll(scrollableInner, scrollableInner.scrollHeight, 3000);
overlay.style.opacity = '0';

// Small delay so scroll starts accelerating before overlay fades
await wait(100);
// Custom 3-second scroll animation
const startScroll = scrollableInner.scrollTop;
const targetScroll = scrollableInner.scrollHeight;
const distance = targetScroll - startScroll;
const duration = 3000; // 3 seconds
const startTime = performance.now();

// 2. Fade out overlay while scroll is already in motion
overlay.style.opacity = '0';
function easeOutCubic(t: number) {
return 1 - Math.pow(1 - t, 3);
}

// Wait for overlay fade, then hide it
await wait(300);
overlay.classList.add('hidden');
function animateScroll(currentTime: number) {
const elapsed = currentTime - startTime;
const progress = Math.min(elapsed / duration, 1);
const easedProgress = easeOutCubic(progress);

// 3. Wait for scroll to complete
await scrollPromise;
scrollableInner.scrollTop = startScroll + (distance * easedProgress);

// 4. Show reset button after scroll completes
const resetButton = document.querySelector('.reset-overlay-button') as HTMLElement;
if (resetButton) {
resetButton.style.display = 'block';
if (progress < 1) {
requestAnimationFrame(animateScroll);
}
}

// 5. Trigger scroll-to-top button visibility check now that animation is done
const scrollTopButton = document.querySelector('.scroll-top-button') as HTMLElement;
if (scrollTopButton && scrollableInner.scrollTop > 50) {
scrollTopButton.style.display = 'block';
}
requestAnimationFrame(animateScroll);

setTimeout(() => overlay.classList.add('hidden'), 200);
setTimeout(() => {
const resetButton = document.querySelector('.reset-overlay-button') as HTMLElement;
if (resetButton) resetButton.style.display = 'block';
}, 2000);
});
};

Expand All @@ -457,12 +366,6 @@

// Show/hide button based on scroll position
const updateButtonVisibility = () => {
// Don't show button during auto-scroll animation
if (isAutoScrolling) {
button.style.display = 'none';
return;
}

// Track manual scroll event (only once, and only if not programmatic)
if (!hasTrackedManualScroll && !isProgrammaticScroll) {
hasTrackedManualScroll = true;
Expand Down
58 changes: 58 additions & 0 deletions pkgs/website/src/components/SlowScroll.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
<script is:inline>
// Slow scroll configuration - change DURATION to adjust speed
const DURATION = 3000; // milliseconds for scroll animation

function setupSlowScroll() {
const scrollable = document.querySelector('.scrollable-inner');
console.log('SlowScroll: scrollable element found:', !!scrollable);
if (!scrollable) return;

let animationId = null;
let startY = scrollable.scrollTop;
let targetY = scrollable.scrollTop;
let startTime = null;

function easeOutCubic(t) {
return 1 - Math.pow(1 - t, 3);
}

function animate(currentTime) {
if (!startTime) startTime = currentTime;
const elapsed = currentTime - startTime;
const progress = Math.min(elapsed / DURATION, 1);

const easedProgress = easeOutCubic(progress);
scrollable.scrollTop = startY + (targetY - startY) * easedProgress;

if (progress < 1) {
animationId = requestAnimationFrame(animate);
} else {
animationId = null;
startY = scrollable.scrollTop;
}
}

scrollable.addEventListener('wheel', (e) => {
console.log('SlowScroll: wheel event captured');
e.preventDefault();
e.stopPropagation();

Comment on lines +35 to +39
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The preventDefault() and stopPropagation() calls on wheel events contradict the PR's stated goal of "replacing complex custom scroll animation with native smooth scrolling." This implementation actually disables native scrolling behavior completely, which can cause accessibility issues for users with assistive technologies or non-standard input devices.

Consider either:

  1. Removing this entire SlowScroll component if native scrolling is the intended approach
  2. Making this behavior optional with a preference setting
  3. Using CSS scroll-behavior: smooth instead for a standards-compliant approach

This custom scroll implementation also lacks touch event handling, which would break scrolling on mobile devices.

Suggested change
scrollable.addEventListener('wheel', (e) => {
console.log('SlowScroll: wheel event captured');
e.preventDefault();
e.stopPropagation();
scrollable.style.scrollBehavior = 'smooth';
// Native smooth scrolling is now enabled via CSS
// This preserves accessibility and works on all devices

Spotted by Graphite Agent

Fix in Graphite


Is this helpful? React 👍 or 👎 to let us know.

targetY += e.deltaY;
targetY = Math.max(0, Math.min(targetY, scrollable.scrollHeight - scrollable.clientHeight));

if (animationId === null) {
startY = scrollable.scrollTop;
startTime = null;
animationId = requestAnimationFrame(animate);
}
}, { passive: false, capture: true });

console.log('SlowScroll: wheel listener attached');
}

if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', setupSlowScroll);
} else {
setupSlowScroll();
}
</script>
26 changes: 17 additions & 9 deletions pkgs/website/src/components/TestimonialCarousel.astro
Original file line number Diff line number Diff line change
Expand Up @@ -94,29 +94,29 @@ const duplicatedTestimonials = [...testimonials, ...testimonials];
gap: 2rem;
animation: scroll-left 120s linear infinite;
width: fit-content;
/* Performance optimizations */
will-change: transform;
transform: translateZ(0); /* Force GPU acceleration */
}

@keyframes scroll-left {
0% {
transform: translateX(0);
transform: translate3d(0, 0, 0);
}
100% {
transform: translateX(-50%);
transform: translate3d(-50%, 0, 0);
}
}

.testimonial-card {
min-width: 400px;
max-width: 400px;
padding: 1.5rem;
background: linear-gradient(
135deg,
color-mix(in srgb, var(--sl-color-accent-high) 4%, transparent) 0%,
color-mix(in srgb, var(--sl-color-accent-high) 1%, transparent) 100%
);
border: 1px solid color-mix(in srgb, var(--sl-color-accent-high) 16%, transparent);
/* Simplified gradient for better performance */
background: rgba(0, 123, 110, 0.04);
border: 1px solid rgba(0, 123, 110, 0.16);
border-radius: 8px;
box-shadow: 0 4px 12px color-mix(in srgb, var(--sl-color-accent-high) 3%, transparent);
box-shadow: 0 4px 12px rgba(0, 123, 110, 0.03);
flex-shrink: 0;
display: flex;
flex-direction: column;
Expand Down Expand Up @@ -181,4 +181,12 @@ const duplicatedTestimonials = [...testimonials, ...testimonials];
font-size: 0.9rem;
}
}

/* Disable animation for users who prefer reduced motion */
@media (prefers-reduced-motion: reduce) {
.testimonial-track {
animation: none !important;
will-change: auto !important;
}
}
</style>
Loading