{
// Reset transform to get accurate measurements
img.style.transform = 'translateY(0)';
// Small delay to ensure layout is complete
setTimeout(() => {
// Force a reflow to ensure dimensions are accurate
void img.offsetHeight;
void container.offsetHeight;
// Get actual rendered image dimensions
// scrollHeight gives us the full content height including overflow
const imgHeight = img.scrollHeight || img.offsetHeight;
const containerHeight = container.offsetHeight;
// Calculate max scroll: only scroll until bottom of image is visible
// When translateY(-maxScroll), the bottom of the image aligns with bottom of container
// maxScroll = imgHeight - containerHeight
const maxScroll = Math.max(0, imgHeight - containerHeight);
// Only proceed if there's actually something to scroll
if (maxScroll <= 0 || maxScroll > imgHeight) {
return;
}
isScrolling = true;
scrollPosition = 0;
// Animation parameters
const duration = 2000; // 2 seconds to scroll down
const startTime = Date.now();
const startPosition = 0;
const endPosition = maxScroll; // Stop when bottom is visible
// Scroll down with easing
scrollAnimation = setInterval(() => {
const elapsed = Date.now() - startTime;
const progress = Math.min(elapsed / duration, 1);
const easedProgress = easeInOutCubic(progress);
scrollPosition = Math.min(
startPosition + (endPosition - startPosition) * easedProgress,
maxScroll // Never exceed max scroll
);
img.style.transform = `translateY(-${scrollPosition}px)`;
if (progress >= 1) {
clearInterval(scrollAnimation);
scrollPosition = maxScroll; // Ensure we're at the exact bottom
img.style.transform = `translateY(-${scrollPosition}px)`;
// Wait 1 second, then scroll back up
setTimeout(() => {
const scrollUpStartTime = Date.now();
const scrollUpStartPosition = maxScroll;
const scrollUpEndPosition = 0;
const scrollUpDuration = 2000; // 2 seconds to scroll back up
scrollAnimation = setInterval(() => {
const scrollUpElapsed = Date.now() - scrollUpStartTime;
const scrollUpProgress = Math.min(scrollUpElapsed / scrollUpDuration, 1);
const scrollUpEasedProgress = easeInOutCubic(scrollUpProgress);
scrollPosition = Math.max(
scrollUpStartPosition + (scrollUpEndPosition - scrollUpStartPosition) * scrollUpEasedProgress,
0 // Never go below 0
);
img.style.transform = `translateY(-${scrollPosition}px)`;
if (scrollUpProgress >= 1) {
clearInterval(scrollAnimation);
scrollPosition = 0;
scrollAnimation = null;
isScrolling = false;
img.style.transform = 'translateY(0)';
}
}, 16);
}, 1000);
}
}, 16);
}, 50); // Small delay to ensure layout is complete
};
if (img.complete) {
checkImage();
} else {
img.onload = checkImage;
}
}
"
@mouseleave="
if (scrollAnimation) {
clearInterval(scrollAnimation);
scrollAnimation = null;
}
const img = $el.querySelector('img');
if (img) {
scrollPosition = 0;
isScrolling = false;
img.style.transform = 'translateY(0)';
img.style.transition = 'transform 0.3s ease-out';
setTimeout(() => {
img.style.transition = '';
}, 300);
}
"
class="flex relative flex-col gap-2 px-3 py-2 mb-4 bg-gray-100 rounded-lg border border-gray-200 transition-all duration-300 ease-in-out cursor-pointer group break-inside-avoid"
>
About Intro
{
// Reset transform to get accurate measurements
img.style.transform = 'translateY(0)';
// Small delay to ensure layout is complete
setTimeout(() => {
// Force a reflow to ensure dimensions are accurate
void img.offsetHeight;
void container.offsetHeight;
// Get actual rendered image dimensions
// scrollHeight gives us the full content height including overflow
const imgHeight = img.scrollHeight || img.offsetHeight;
const containerHeight = container.offsetHeight;
// Calculate max scroll: only scroll until bottom of image is visible
// When translateY(-maxScroll), the bottom of the image aligns with bottom of container
// maxScroll = imgHeight - containerHeight
const maxScroll = Math.max(0, imgHeight - containerHeight);
// Only proceed if there's actually something to scroll
if (maxScroll <= 0 || maxScroll > imgHeight) {
return;
}
isScrolling = true;
scrollPosition = 0;
// Animation parameters
const duration = 2000; // 2 seconds to scroll down
const startTime = Date.now();
const startPosition = 0;
const endPosition = maxScroll; // Stop when bottom is visible
// Scroll down with easing
scrollAnimation = setInterval(() => {
const elapsed = Date.now() - startTime;
const progress = Math.min(elapsed / duration, 1);
const easedProgress = easeInOutCubic(progress);
scrollPosition = Math.min(
startPosition + (endPosition - startPosition) * easedProgress,
maxScroll // Never exceed max scroll
);
img.style.transform = `translateY(-${scrollPosition}px)`;
if (progress >= 1) {
clearInterval(scrollAnimation);
scrollPosition = maxScroll; // Ensure we're at the exact bottom
img.style.transform = `translateY(-${scrollPosition}px)`;
// Wait 1 second, then scroll back up
setTimeout(() => {
const scrollUpStartTime = Date.now();
const scrollUpStartPosition = maxScroll;
const scrollUpEndPosition = 0;
const scrollUpDuration = 2000; // 2 seconds to scroll back up
scrollAnimation = setInterval(() => {
const scrollUpElapsed = Date.now() - scrollUpStartTime;
const scrollUpProgress = Math.min(scrollUpElapsed / scrollUpDuration, 1);
const scrollUpEasedProgress = easeInOutCubic(scrollUpProgress);
scrollPosition = Math.max(
scrollUpStartPosition + (scrollUpEndPosition - scrollUpStartPosition) * scrollUpEasedProgress,
0 // Never go below 0
);
img.style.transform = `translateY(-${scrollPosition}px)`;
if (scrollUpProgress >= 1) {
clearInterval(scrollAnimation);
scrollPosition = 0;
scrollAnimation = null;
isScrolling = false;
img.style.transform = 'translateY(0)';
}
}, 16);
}, 1000);
}
}, 16);
}, 50); // Small delay to ensure layout is complete
};
if (img.complete) {
checkImage();
} else {
img.onload = checkImage;
}
}
"
@mouseleave="
if (scrollAnimation) {
clearInterval(scrollAnimation);
scrollAnimation = null;
}
const img = $el.querySelector('img');
if (img) {
scrollPosition = 0;
isScrolling = false;
img.style.transform = 'translateY(0)';
img.style.transition = 'transform 0.3s ease-out';
setTimeout(() => {
img.style.transition = '';
}, 300);
}
"
class="flex relative flex-col gap-2 px-3 py-2 mb-4 bg-gray-100 rounded-lg border border-gray-200 transition-all duration-300 ease-in-out cursor-pointer group break-inside-avoid"
>
Branding
{
// Reset transform to get accurate measurements
img.style.transform = 'translateY(0)';
// Small delay to ensure layout is complete
setTimeout(() => {
// Force a reflow to ensure dimensions are accurate
void img.offsetHeight;
void container.offsetHeight;
// Get actual rendered image dimensions
// scrollHeight gives us the full content height including overflow
const imgHeight = img.scrollHeight || img.offsetHeight;
const containerHeight = container.offsetHeight;
// Calculate max scroll: only scroll until bottom of image is visible
// When translateY(-maxScroll), the bottom of the image aligns with bottom of container
// maxScroll = imgHeight - containerHeight
const maxScroll = Math.max(0, imgHeight - containerHeight);
// Only proceed if there's actually something to scroll
if (maxScroll <= 0 || maxScroll > imgHeight) {
return;
}
isScrolling = true;
scrollPosition = 0;
// Animation parameters
const duration = 2000; // 2 seconds to scroll down
const startTime = Date.now();
const startPosition = 0;
const endPosition = maxScroll; // Stop when bottom is visible
// Scroll down with easing
scrollAnimation = setInterval(() => {
const elapsed = Date.now() - startTime;
const progress = Math.min(elapsed / duration, 1);
const easedProgress = easeInOutCubic(progress);
scrollPosition = Math.min(
startPosition + (endPosition - startPosition) * easedProgress,
maxScroll // Never exceed max scroll
);
img.style.transform = `translateY(-${scrollPosition}px)`;
if (progress >= 1) {
clearInterval(scrollAnimation);
scrollPosition = maxScroll; // Ensure we're at the exact bottom
img.style.transform = `translateY(-${scrollPosition}px)`;
// Wait 1 second, then scroll back up
setTimeout(() => {
const scrollUpStartTime = Date.now();
const scrollUpStartPosition = maxScroll;
const scrollUpEndPosition = 0;
const scrollUpDuration = 2000; // 2 seconds to scroll back up
scrollAnimation = setInterval(() => {
const scrollUpElapsed = Date.now() - scrollUpStartTime;
const scrollUpProgress = Math.min(scrollUpElapsed / scrollUpDuration, 1);
const scrollUpEasedProgress = easeInOutCubic(scrollUpProgress);
scrollPosition = Math.max(
scrollUpStartPosition + (scrollUpEndPosition - scrollUpStartPosition) * scrollUpEasedProgress,
0 // Never go below 0
);
img.style.transform = `translateY(-${scrollPosition}px)`;
if (scrollUpProgress >= 1) {
clearInterval(scrollAnimation);
scrollPosition = 0;
scrollAnimation = null;
isScrolling = false;
img.style.transform = 'translateY(0)';
}
}, 16);
}, 1000);
}
}, 16);
}, 50); // Small delay to ensure layout is complete
};
if (img.complete) {
checkImage();
} else {
img.onload = checkImage;
}
}
"
@mouseleave="
if (scrollAnimation) {
clearInterval(scrollAnimation);
scrollAnimation = null;
}
const img = $el.querySelector('img');
if (img) {
scrollPosition = 0;
isScrolling = false;
img.style.transform = 'translateY(0)';
img.style.transition = 'transform 0.3s ease-out';
setTimeout(() => {
img.style.transition = '';
}, 300);
}
"
class="flex relative flex-col gap-2 px-3 py-2 mb-4 bg-gray-100 rounded-lg border border-gray-200 transition-all duration-300 ease-in-out cursor-pointer group break-inside-avoid"
>
CTA
{
// Reset transform to get accurate measurements
img.style.transform = 'translateY(0)';
// Small delay to ensure layout is complete
setTimeout(() => {
// Force a reflow to ensure dimensions are accurate
void img.offsetHeight;
void container.offsetHeight;
// Get actual rendered image dimensions
// scrollHeight gives us the full content height including overflow
const imgHeight = img.scrollHeight || img.offsetHeight;
const containerHeight = container.offsetHeight;
// Calculate max scroll: only scroll until bottom of image is visible
// When translateY(-maxScroll), the bottom of the image aligns with bottom of container
// maxScroll = imgHeight - containerHeight
const maxScroll = Math.max(0, imgHeight - containerHeight);
// Only proceed if there's actually something to scroll
if (maxScroll <= 0 || maxScroll > imgHeight) {
return;
}
isScrolling = true;
scrollPosition = 0;
// Animation parameters
const duration = 2000; // 2 seconds to scroll down
const startTime = Date.now();
const startPosition = 0;
const endPosition = maxScroll; // Stop when bottom is visible
// Scroll down with easing
scrollAnimation = setInterval(() => {
const elapsed = Date.now() - startTime;
const progress = Math.min(elapsed / duration, 1);
const easedProgress = easeInOutCubic(progress);
scrollPosition = Math.min(
startPosition + (endPosition - startPosition) * easedProgress,
maxScroll // Never exceed max scroll
);
img.style.transform = `translateY(-${scrollPosition}px)`;
if (progress >= 1) {
clearInterval(scrollAnimation);
scrollPosition = maxScroll; // Ensure we're at the exact bottom
img.style.transform = `translateY(-${scrollPosition}px)`;
// Wait 1 second, then scroll back up
setTimeout(() => {
const scrollUpStartTime = Date.now();
const scrollUpStartPosition = maxScroll;
const scrollUpEndPosition = 0;
const scrollUpDuration = 2000; // 2 seconds to scroll back up
scrollAnimation = setInterval(() => {
const scrollUpElapsed = Date.now() - scrollUpStartTime;
const scrollUpProgress = Math.min(scrollUpElapsed / scrollUpDuration, 1);
const scrollUpEasedProgress = easeInOutCubic(scrollUpProgress);
scrollPosition = Math.max(
scrollUpStartPosition + (scrollUpEndPosition - scrollUpStartPosition) * scrollUpEasedProgress,
0 // Never go below 0
);
img.style.transform = `translateY(-${scrollPosition}px)`;
if (scrollUpProgress >= 1) {
clearInterval(scrollAnimation);
scrollPosition = 0;
scrollAnimation = null;
isScrolling = false;
img.style.transform = 'translateY(0)';
}
}, 16);
}, 1000);
}
}, 16);
}, 50); // Small delay to ensure layout is complete
};
if (img.complete) {
checkImage();
} else {
img.onload = checkImage;
}
}
"
@mouseleave="
if (scrollAnimation) {
clearInterval(scrollAnimation);
scrollAnimation = null;
}
const img = $el.querySelector('img');
if (img) {
scrollPosition = 0;
isScrolling = false;
img.style.transform = 'translateY(0)';
img.style.transition = 'transform 0.3s ease-out';
setTimeout(() => {
img.style.transition = '';
}, 300);
}
"
class="flex relative flex-col gap-2 px-3 py-2 mb-4 bg-gray-100 rounded-lg border border-gray-200 transition-all duration-300 ease-in-out cursor-pointer group break-inside-avoid"
>
Careers Callout
{
// Reset transform to get accurate measurements
img.style.transform = 'translateY(0)';
// Small delay to ensure layout is complete
setTimeout(() => {
// Force a reflow to ensure dimensions are accurate
void img.offsetHeight;
void container.offsetHeight;
// Get actual rendered image dimensions
// scrollHeight gives us the full content height including overflow
const imgHeight = img.scrollHeight || img.offsetHeight;
const containerHeight = container.offsetHeight;
// Calculate max scroll: only scroll until bottom of image is visible
// When translateY(-maxScroll), the bottom of the image aligns with bottom of container
// maxScroll = imgHeight - containerHeight
const maxScroll = Math.max(0, imgHeight - containerHeight);
// Only proceed if there's actually something to scroll
if (maxScroll <= 0 || maxScroll > imgHeight) {
return;
}
isScrolling = true;
scrollPosition = 0;
// Animation parameters
const duration = 2000; // 2 seconds to scroll down
const startTime = Date.now();
const startPosition = 0;
const endPosition = maxScroll; // Stop when bottom is visible
// Scroll down with easing
scrollAnimation = setInterval(() => {
const elapsed = Date.now() - startTime;
const progress = Math.min(elapsed / duration, 1);
const easedProgress = easeInOutCubic(progress);
scrollPosition = Math.min(
startPosition + (endPosition - startPosition) * easedProgress,
maxScroll // Never exceed max scroll
);
img.style.transform = `translateY(-${scrollPosition}px)`;
if (progress >= 1) {
clearInterval(scrollAnimation);
scrollPosition = maxScroll; // Ensure we're at the exact bottom
img.style.transform = `translateY(-${scrollPosition}px)`;
// Wait 1 second, then scroll back up
setTimeout(() => {
const scrollUpStartTime = Date.now();
const scrollUpStartPosition = maxScroll;
const scrollUpEndPosition = 0;
const scrollUpDuration = 2000; // 2 seconds to scroll back up
scrollAnimation = setInterval(() => {
const scrollUpElapsed = Date.now() - scrollUpStartTime;
const scrollUpProgress = Math.min(scrollUpElapsed / scrollUpDuration, 1);
const scrollUpEasedProgress = easeInOutCubic(scrollUpProgress);
scrollPosition = Math.max(
scrollUpStartPosition + (scrollUpEndPosition - scrollUpStartPosition) * scrollUpEasedProgress,
0 // Never go below 0
);
img.style.transform = `translateY(-${scrollPosition}px)`;
if (scrollUpProgress >= 1) {
clearInterval(scrollAnimation);
scrollPosition = 0;
scrollAnimation = null;
isScrolling = false;
img.style.transform = 'translateY(0)';
}
}, 16);
}, 1000);
}
}, 16);
}, 50); // Small delay to ensure layout is complete
};
if (img.complete) {
checkImage();
} else {
img.onload = checkImage;
}
}
"
@mouseleave="
if (scrollAnimation) {
clearInterval(scrollAnimation);
scrollAnimation = null;
}
const img = $el.querySelector('img');
if (img) {
scrollPosition = 0;
isScrolling = false;
img.style.transform = 'translateY(0)';
img.style.transition = 'transform 0.3s ease-out';
setTimeout(() => {
img.style.transition = '';
}, 300);
}
"
class="flex relative flex-col gap-2 px-3 py-2 mb-4 bg-gray-100 rounded-lg border border-gray-200 transition-all duration-300 ease-in-out cursor-pointer group break-inside-avoid"
>
Case Studies Large
{
// Reset transform to get accurate measurements
img.style.transform = 'translateY(0)';
// Small delay to ensure layout is complete
setTimeout(() => {
// Force a reflow to ensure dimensions are accurate
void img.offsetHeight;
void container.offsetHeight;
// Get actual rendered image dimensions
// scrollHeight gives us the full content height including overflow
const imgHeight = img.scrollHeight || img.offsetHeight;
const containerHeight = container.offsetHeight;
// Calculate max scroll: only scroll until bottom of image is visible
// When translateY(-maxScroll), the bottom of the image aligns with bottom of container
// maxScroll = imgHeight - containerHeight
const maxScroll = Math.max(0, imgHeight - containerHeight);
// Only proceed if there's actually something to scroll
if (maxScroll <= 0 || maxScroll > imgHeight) {
return;
}
isScrolling = true;
scrollPosition = 0;
// Animation parameters
const duration = 2000; // 2 seconds to scroll down
const startTime = Date.now();
const startPosition = 0;
const endPosition = maxScroll; // Stop when bottom is visible
// Scroll down with easing
scrollAnimation = setInterval(() => {
const elapsed = Date.now() - startTime;
const progress = Math.min(elapsed / duration, 1);
const easedProgress = easeInOutCubic(progress);
scrollPosition = Math.min(
startPosition + (endPosition - startPosition) * easedProgress,
maxScroll // Never exceed max scroll
);
img.style.transform = `translateY(-${scrollPosition}px)`;
if (progress >= 1) {
clearInterval(scrollAnimation);
scrollPosition = maxScroll; // Ensure we're at the exact bottom
img.style.transform = `translateY(-${scrollPosition}px)`;
// Wait 1 second, then scroll back up
setTimeout(() => {
const scrollUpStartTime = Date.now();
const scrollUpStartPosition = maxScroll;
const scrollUpEndPosition = 0;
const scrollUpDuration = 2000; // 2 seconds to scroll back up
scrollAnimation = setInterval(() => {
const scrollUpElapsed = Date.now() - scrollUpStartTime;
const scrollUpProgress = Math.min(scrollUpElapsed / scrollUpDuration, 1);
const scrollUpEasedProgress = easeInOutCubic(scrollUpProgress);
scrollPosition = Math.max(
scrollUpStartPosition + (scrollUpEndPosition - scrollUpStartPosition) * scrollUpEasedProgress,
0 // Never go below 0
);
img.style.transform = `translateY(-${scrollPosition}px)`;
if (scrollUpProgress >= 1) {
clearInterval(scrollAnimation);
scrollPosition = 0;
scrollAnimation = null;
isScrolling = false;
img.style.transform = 'translateY(0)';
}
}, 16);
}, 1000);
}
}, 16);
}, 50); // Small delay to ensure layout is complete
};
if (img.complete) {
checkImage();
} else {
img.onload = checkImage;
}
}
"
@mouseleave="
if (scrollAnimation) {
clearInterval(scrollAnimation);
scrollAnimation = null;
}
const img = $el.querySelector('img');
if (img) {
scrollPosition = 0;
isScrolling = false;
img.style.transform = 'translateY(0)';
img.style.transition = 'transform 0.3s ease-out';
setTimeout(() => {
img.style.transition = '';
}, 300);
}
"
class="flex relative flex-col gap-2 px-3 py-2 mb-4 bg-gray-100 rounded-lg border border-gray-200 transition-all duration-300 ease-in-out cursor-pointer group break-inside-avoid"
>
Case Study Block [Block]
{
// Reset transform to get accurate measurements
img.style.transform = 'translateY(0)';
// Small delay to ensure layout is complete
setTimeout(() => {
// Force a reflow to ensure dimensions are accurate
void img.offsetHeight;
void container.offsetHeight;
// Get actual rendered image dimensions
// scrollHeight gives us the full content height including overflow
const imgHeight = img.scrollHeight || img.offsetHeight;
const containerHeight = container.offsetHeight;
// Calculate max scroll: only scroll until bottom of image is visible
// When translateY(-maxScroll), the bottom of the image aligns with bottom of container
// maxScroll = imgHeight - containerHeight
const maxScroll = Math.max(0, imgHeight - containerHeight);
// Only proceed if there's actually something to scroll
if (maxScroll <= 0 || maxScroll > imgHeight) {
return;
}
isScrolling = true;
scrollPosition = 0;
// Animation parameters
const duration = 2000; // 2 seconds to scroll down
const startTime = Date.now();
const startPosition = 0;
const endPosition = maxScroll; // Stop when bottom is visible
// Scroll down with easing
scrollAnimation = setInterval(() => {
const elapsed = Date.now() - startTime;
const progress = Math.min(elapsed / duration, 1);
const easedProgress = easeInOutCubic(progress);
scrollPosition = Math.min(
startPosition + (endPosition - startPosition) * easedProgress,
maxScroll // Never exceed max scroll
);
img.style.transform = `translateY(-${scrollPosition}px)`;
if (progress >= 1) {
clearInterval(scrollAnimation);
scrollPosition = maxScroll; // Ensure we're at the exact bottom
img.style.transform = `translateY(-${scrollPosition}px)`;
// Wait 1 second, then scroll back up
setTimeout(() => {
const scrollUpStartTime = Date.now();
const scrollUpStartPosition = maxScroll;
const scrollUpEndPosition = 0;
const scrollUpDuration = 2000; // 2 seconds to scroll back up
scrollAnimation = setInterval(() => {
const scrollUpElapsed = Date.now() - scrollUpStartTime;
const scrollUpProgress = Math.min(scrollUpElapsed / scrollUpDuration, 1);
const scrollUpEasedProgress = easeInOutCubic(scrollUpProgress);
scrollPosition = Math.max(
scrollUpStartPosition + (scrollUpEndPosition - scrollUpStartPosition) * scrollUpEasedProgress,
0 // Never go below 0
);
img.style.transform = `translateY(-${scrollPosition}px)`;
if (scrollUpProgress >= 1) {
clearInterval(scrollAnimation);
scrollPosition = 0;
scrollAnimation = null;
isScrolling = false;
img.style.transform = 'translateY(0)';
}
}, 16);
}, 1000);
}
}, 16);
}, 50); // Small delay to ensure layout is complete
};
if (img.complete) {
checkImage();
} else {
img.onload = checkImage;
}
}
"
@mouseleave="
if (scrollAnimation) {
clearInterval(scrollAnimation);
scrollAnimation = null;
}
const img = $el.querySelector('img');
if (img) {
scrollPosition = 0;
isScrolling = false;
img.style.transform = 'translateY(0)';
img.style.transition = 'transform 0.3s ease-out';
setTimeout(() => {
img.style.transition = '';
}, 300);
}
"
class="flex relative flex-col gap-2 px-3 py-2 mb-4 bg-gray-100 rounded-lg border border-gray-200 transition-all duration-300 ease-in-out cursor-pointer group break-inside-avoid"
>
Contact [Block]
{
// Reset transform to get accurate measurements
img.style.transform = 'translateY(0)';
// Small delay to ensure layout is complete
setTimeout(() => {
// Force a reflow to ensure dimensions are accurate
void img.offsetHeight;
void container.offsetHeight;
// Get actual rendered image dimensions
// scrollHeight gives us the full content height including overflow
const imgHeight = img.scrollHeight || img.offsetHeight;
const containerHeight = container.offsetHeight;
// Calculate max scroll: only scroll until bottom of image is visible
// When translateY(-maxScroll), the bottom of the image aligns with bottom of container
// maxScroll = imgHeight - containerHeight
const maxScroll = Math.max(0, imgHeight - containerHeight);
// Only proceed if there's actually something to scroll
if (maxScroll <= 0 || maxScroll > imgHeight) {
return;
}
isScrolling = true;
scrollPosition = 0;
// Animation parameters
const duration = 2000; // 2 seconds to scroll down
const startTime = Date.now();
const startPosition = 0;
const endPosition = maxScroll; // Stop when bottom is visible
// Scroll down with easing
scrollAnimation = setInterval(() => {
const elapsed = Date.now() - startTime;
const progress = Math.min(elapsed / duration, 1);
const easedProgress = easeInOutCubic(progress);
scrollPosition = Math.min(
startPosition + (endPosition - startPosition) * easedProgress,
maxScroll // Never exceed max scroll
);
img.style.transform = `translateY(-${scrollPosition}px)`;
if (progress >= 1) {
clearInterval(scrollAnimation);
scrollPosition = maxScroll; // Ensure we're at the exact bottom
img.style.transform = `translateY(-${scrollPosition}px)`;
// Wait 1 second, then scroll back up
setTimeout(() => {
const scrollUpStartTime = Date.now();
const scrollUpStartPosition = maxScroll;
const scrollUpEndPosition = 0;
const scrollUpDuration = 2000; // 2 seconds to scroll back up
scrollAnimation = setInterval(() => {
const scrollUpElapsed = Date.now() - scrollUpStartTime;
const scrollUpProgress = Math.min(scrollUpElapsed / scrollUpDuration, 1);
const scrollUpEasedProgress = easeInOutCubic(scrollUpProgress);
scrollPosition = Math.max(
scrollUpStartPosition + (scrollUpEndPosition - scrollUpStartPosition) * scrollUpEasedProgress,
0 // Never go below 0
);
img.style.transform = `translateY(-${scrollPosition}px)`;
if (scrollUpProgress >= 1) {
clearInterval(scrollAnimation);
scrollPosition = 0;
scrollAnimation = null;
isScrolling = false;
img.style.transform = 'translateY(0)';
}
}, 16);
}, 1000);
}
}, 16);
}, 50); // Small delay to ensure layout is complete
};
if (img.complete) {
checkImage();
} else {
img.onload = checkImage;
}
}
"
@mouseleave="
if (scrollAnimation) {
clearInterval(scrollAnimation);
scrollAnimation = null;
}
const img = $el.querySelector('img');
if (img) {
scrollPosition = 0;
isScrolling = false;
img.style.transform = 'translateY(0)';
img.style.transition = 'transform 0.3s ease-out';
setTimeout(() => {
img.style.transition = '';
}, 300);
}
"
class="flex relative flex-col gap-2 px-3 py-2 mb-4 bg-gray-100 rounded-lg border border-gray-200 transition-all duration-300 ease-in-out cursor-pointer group break-inside-avoid"
>
Contact v2 [Block]
{
// Reset transform to get accurate measurements
img.style.transform = 'translateY(0)';
// Small delay to ensure layout is complete
setTimeout(() => {
// Force a reflow to ensure dimensions are accurate
void img.offsetHeight;
void container.offsetHeight;
// Get actual rendered image dimensions
// scrollHeight gives us the full content height including overflow
const imgHeight = img.scrollHeight || img.offsetHeight;
const containerHeight = container.offsetHeight;
// Calculate max scroll: only scroll until bottom of image is visible
// When translateY(-maxScroll), the bottom of the image aligns with bottom of container
// maxScroll = imgHeight - containerHeight
const maxScroll = Math.max(0, imgHeight - containerHeight);
// Only proceed if there's actually something to scroll
if (maxScroll <= 0 || maxScroll > imgHeight) {
return;
}
isScrolling = true;
scrollPosition = 0;
// Animation parameters
const duration = 2000; // 2 seconds to scroll down
const startTime = Date.now();
const startPosition = 0;
const endPosition = maxScroll; // Stop when bottom is visible
// Scroll down with easing
scrollAnimation = setInterval(() => {
const elapsed = Date.now() - startTime;
const progress = Math.min(elapsed / duration, 1);
const easedProgress = easeInOutCubic(progress);
scrollPosition = Math.min(
startPosition + (endPosition - startPosition) * easedProgress,
maxScroll // Never exceed max scroll
);
img.style.transform = `translateY(-${scrollPosition}px)`;
if (progress >= 1) {
clearInterval(scrollAnimation);
scrollPosition = maxScroll; // Ensure we're at the exact bottom
img.style.transform = `translateY(-${scrollPosition}px)`;
// Wait 1 second, then scroll back up
setTimeout(() => {
const scrollUpStartTime = Date.now();
const scrollUpStartPosition = maxScroll;
const scrollUpEndPosition = 0;
const scrollUpDuration = 2000; // 2 seconds to scroll back up
scrollAnimation = setInterval(() => {
const scrollUpElapsed = Date.now() - scrollUpStartTime;
const scrollUpProgress = Math.min(scrollUpElapsed / scrollUpDuration, 1);
const scrollUpEasedProgress = easeInOutCubic(scrollUpProgress);
scrollPosition = Math.max(
scrollUpStartPosition + (scrollUpEndPosition - scrollUpStartPosition) * scrollUpEasedProgress,
0 // Never go below 0
);
img.style.transform = `translateY(-${scrollPosition}px)`;
if (scrollUpProgress >= 1) {
clearInterval(scrollAnimation);
scrollPosition = 0;
scrollAnimation = null;
isScrolling = false;
img.style.transform = 'translateY(0)';
}
}, 16);
}, 1000);
}
}, 16);
}, 50); // Small delay to ensure layout is complete
};
if (img.complete) {
checkImage();
} else {
img.onload = checkImage;
}
}
"
@mouseleave="
if (scrollAnimation) {
clearInterval(scrollAnimation);
scrollAnimation = null;
}
const img = $el.querySelector('img');
if (img) {
scrollPosition = 0;
isScrolling = false;
img.style.transform = 'translateY(0)';
img.style.transition = 'transform 0.3s ease-out';
setTimeout(() => {
img.style.transition = '';
}, 300);
}
"
class="flex relative flex-col gap-2 px-3 py-2 mb-4 bg-gray-100 rounded-lg border border-gray-200 transition-all duration-300 ease-in-out cursor-pointer group break-inside-avoid"
>
Customers Hero
{
// Reset transform to get accurate measurements
img.style.transform = 'translateY(0)';
// Small delay to ensure layout is complete
setTimeout(() => {
// Force a reflow to ensure dimensions are accurate
void img.offsetHeight;
void container.offsetHeight;
// Get actual rendered image dimensions
// scrollHeight gives us the full content height including overflow
const imgHeight = img.scrollHeight || img.offsetHeight;
const containerHeight = container.offsetHeight;
// Calculate max scroll: only scroll until bottom of image is visible
// When translateY(-maxScroll), the bottom of the image aligns with bottom of container
// maxScroll = imgHeight - containerHeight
const maxScroll = Math.max(0, imgHeight - containerHeight);
// Only proceed if there's actually something to scroll
if (maxScroll <= 0 || maxScroll > imgHeight) {
return;
}
isScrolling = true;
scrollPosition = 0;
// Animation parameters
const duration = 2000; // 2 seconds to scroll down
const startTime = Date.now();
const startPosition = 0;
const endPosition = maxScroll; // Stop when bottom is visible
// Scroll down with easing
scrollAnimation = setInterval(() => {
const elapsed = Date.now() - startTime;
const progress = Math.min(elapsed / duration, 1);
const easedProgress = easeInOutCubic(progress);
scrollPosition = Math.min(
startPosition + (endPosition - startPosition) * easedProgress,
maxScroll // Never exceed max scroll
);
img.style.transform = `translateY(-${scrollPosition}px)`;
if (progress >= 1) {
clearInterval(scrollAnimation);
scrollPosition = maxScroll; // Ensure we're at the exact bottom
img.style.transform = `translateY(-${scrollPosition}px)`;
// Wait 1 second, then scroll back up
setTimeout(() => {
const scrollUpStartTime = Date.now();
const scrollUpStartPosition = maxScroll;
const scrollUpEndPosition = 0;
const scrollUpDuration = 2000; // 2 seconds to scroll back up
scrollAnimation = setInterval(() => {
const scrollUpElapsed = Date.now() - scrollUpStartTime;
const scrollUpProgress = Math.min(scrollUpElapsed / scrollUpDuration, 1);
const scrollUpEasedProgress = easeInOutCubic(scrollUpProgress);
scrollPosition = Math.max(
scrollUpStartPosition + (scrollUpEndPosition - scrollUpStartPosition) * scrollUpEasedProgress,
0 // Never go below 0
);
img.style.transform = `translateY(-${scrollPosition}px)`;
if (scrollUpProgress >= 1) {
clearInterval(scrollAnimation);
scrollPosition = 0;
scrollAnimation = null;
isScrolling = false;
img.style.transform = 'translateY(0)';
}
}, 16);
}, 1000);
}
}, 16);
}, 50); // Small delay to ensure layout is complete
};
if (img.complete) {
checkImage();
} else {
img.onload = checkImage;
}
}
"
@mouseleave="
if (scrollAnimation) {
clearInterval(scrollAnimation);
scrollAnimation = null;
}
const img = $el.querySelector('img');
if (img) {
scrollPosition = 0;
isScrolling = false;
img.style.transform = 'translateY(0)';
img.style.transition = 'transform 0.3s ease-out';
setTimeout(() => {
img.style.transition = '';
}, 300);
}
"
class="flex relative flex-col gap-2 px-3 py-2 mb-4 bg-gray-100 rounded-lg border border-gray-200 transition-all duration-300 ease-in-out cursor-pointer group break-inside-avoid"
>
Demo
{
// Reset transform to get accurate measurements
img.style.transform = 'translateY(0)';
// Small delay to ensure layout is complete
setTimeout(() => {
// Force a reflow to ensure dimensions are accurate
void img.offsetHeight;
void container.offsetHeight;
// Get actual rendered image dimensions
// scrollHeight gives us the full content height including overflow
const imgHeight = img.scrollHeight || img.offsetHeight;
const containerHeight = container.offsetHeight;
// Calculate max scroll: only scroll until bottom of image is visible
// When translateY(-maxScroll), the bottom of the image aligns with bottom of container
// maxScroll = imgHeight - containerHeight
const maxScroll = Math.max(0, imgHeight - containerHeight);
// Only proceed if there's actually something to scroll
if (maxScroll <= 0 || maxScroll > imgHeight) {
return;
}
isScrolling = true;
scrollPosition = 0;
// Animation parameters
const duration = 2000; // 2 seconds to scroll down
const startTime = Date.now();
const startPosition = 0;
const endPosition = maxScroll; // Stop when bottom is visible
// Scroll down with easing
scrollAnimation = setInterval(() => {
const elapsed = Date.now() - startTime;
const progress = Math.min(elapsed / duration, 1);
const easedProgress = easeInOutCubic(progress);
scrollPosition = Math.min(
startPosition + (endPosition - startPosition) * easedProgress,
maxScroll // Never exceed max scroll
);
img.style.transform = `translateY(-${scrollPosition}px)`;
if (progress >= 1) {
clearInterval(scrollAnimation);
scrollPosition = maxScroll; // Ensure we're at the exact bottom
img.style.transform = `translateY(-${scrollPosition}px)`;
// Wait 1 second, then scroll back up
setTimeout(() => {
const scrollUpStartTime = Date.now();
const scrollUpStartPosition = maxScroll;
const scrollUpEndPosition = 0;
const scrollUpDuration = 2000; // 2 seconds to scroll back up
scrollAnimation = setInterval(() => {
const scrollUpElapsed = Date.now() - scrollUpStartTime;
const scrollUpProgress = Math.min(scrollUpElapsed / scrollUpDuration, 1);
const scrollUpEasedProgress = easeInOutCubic(scrollUpProgress);
scrollPosition = Math.max(
scrollUpStartPosition + (scrollUpEndPosition - scrollUpStartPosition) * scrollUpEasedProgress,
0 // Never go below 0
);
img.style.transform = `translateY(-${scrollPosition}px)`;
if (scrollUpProgress >= 1) {
clearInterval(scrollAnimation);
scrollPosition = 0;
scrollAnimation = null;
isScrolling = false;
img.style.transform = 'translateY(0)';
}
}, 16);
}, 1000);
}
}, 16);
}, 50); // Small delay to ensure layout is complete
};
if (img.complete) {
checkImage();
} else {
img.onload = checkImage;
}
}
"
@mouseleave="
if (scrollAnimation) {
clearInterval(scrollAnimation);
scrollAnimation = null;
}
const img = $el.querySelector('img');
if (img) {
scrollPosition = 0;
isScrolling = false;
img.style.transform = 'translateY(0)';
img.style.transition = 'transform 0.3s ease-out';
setTimeout(() => {
img.style.transition = '';
}, 300);
}
"
class="flex relative flex-col gap-2 px-3 py-2 mb-4 bg-gray-100 rounded-lg border border-gray-200 transition-all duration-300 ease-in-out cursor-pointer group break-inside-avoid"
>
Explore More [Block]
{
// Reset transform to get accurate measurements
img.style.transform = 'translateY(0)';
// Small delay to ensure layout is complete
setTimeout(() => {
// Force a reflow to ensure dimensions are accurate
void img.offsetHeight;
void container.offsetHeight;
// Get actual rendered image dimensions
// scrollHeight gives us the full content height including overflow
const imgHeight = img.scrollHeight || img.offsetHeight;
const containerHeight = container.offsetHeight;
// Calculate max scroll: only scroll until bottom of image is visible
// When translateY(-maxScroll), the bottom of the image aligns with bottom of container
// maxScroll = imgHeight - containerHeight
const maxScroll = Math.max(0, imgHeight - containerHeight);
// Only proceed if there's actually something to scroll
if (maxScroll <= 0 || maxScroll > imgHeight) {
return;
}
isScrolling = true;
scrollPosition = 0;
// Animation parameters
const duration = 2000; // 2 seconds to scroll down
const startTime = Date.now();
const startPosition = 0;
const endPosition = maxScroll; // Stop when bottom is visible
// Scroll down with easing
scrollAnimation = setInterval(() => {
const elapsed = Date.now() - startTime;
const progress = Math.min(elapsed / duration, 1);
const easedProgress = easeInOutCubic(progress);
scrollPosition = Math.min(
startPosition + (endPosition - startPosition) * easedProgress,
maxScroll // Never exceed max scroll
);
img.style.transform = `translateY(-${scrollPosition}px)`;
if (progress >= 1) {
clearInterval(scrollAnimation);
scrollPosition = maxScroll; // Ensure we're at the exact bottom
img.style.transform = `translateY(-${scrollPosition}px)`;
// Wait 1 second, then scroll back up
setTimeout(() => {
const scrollUpStartTime = Date.now();
const scrollUpStartPosition = maxScroll;
const scrollUpEndPosition = 0;
const scrollUpDuration = 2000; // 2 seconds to scroll back up
scrollAnimation = setInterval(() => {
const scrollUpElapsed = Date.now() - scrollUpStartTime;
const scrollUpProgress = Math.min(scrollUpElapsed / scrollUpDuration, 1);
const scrollUpEasedProgress = easeInOutCubic(scrollUpProgress);
scrollPosition = Math.max(
scrollUpStartPosition + (scrollUpEndPosition - scrollUpStartPosition) * scrollUpEasedProgress,
0 // Never go below 0
);
img.style.transform = `translateY(-${scrollPosition}px)`;
if (scrollUpProgress >= 1) {
clearInterval(scrollAnimation);
scrollPosition = 0;
scrollAnimation = null;
isScrolling = false;
img.style.transform = 'translateY(0)';
}
}, 16);
}, 1000);
}
}, 16);
}, 50); // Small delay to ensure layout is complete
};
if (img.complete) {
checkImage();
} else {
img.onload = checkImage;
}
}
"
@mouseleave="
if (scrollAnimation) {
clearInterval(scrollAnimation);
scrollAnimation = null;
}
const img = $el.querySelector('img');
if (img) {
scrollPosition = 0;
isScrolling = false;
img.style.transform = 'translateY(0)';
img.style.transition = 'transform 0.3s ease-out';
setTimeout(() => {
img.style.transition = '';
}, 300);
}
"
class="flex relative flex-col gap-2 px-3 py-2 mb-4 bg-gray-100 rounded-lg border border-gray-200 transition-all duration-300 ease-in-out cursor-pointer group break-inside-avoid"
>
FAQ
{
// Reset transform to get accurate measurements
img.style.transform = 'translateY(0)';
// Small delay to ensure layout is complete
setTimeout(() => {
// Force a reflow to ensure dimensions are accurate
void img.offsetHeight;
void container.offsetHeight;
// Get actual rendered image dimensions
// scrollHeight gives us the full content height including overflow
const imgHeight = img.scrollHeight || img.offsetHeight;
const containerHeight = container.offsetHeight;
// Calculate max scroll: only scroll until bottom of image is visible
// When translateY(-maxScroll), the bottom of the image aligns with bottom of container
// maxScroll = imgHeight - containerHeight
const maxScroll = Math.max(0, imgHeight - containerHeight);
// Only proceed if there's actually something to scroll
if (maxScroll <= 0 || maxScroll > imgHeight) {
return;
}
isScrolling = true;
scrollPosition = 0;
// Animation parameters
const duration = 2000; // 2 seconds to scroll down
const startTime = Date.now();
const startPosition = 0;
const endPosition = maxScroll; // Stop when bottom is visible
// Scroll down with easing
scrollAnimation = setInterval(() => {
const elapsed = Date.now() - startTime;
const progress = Math.min(elapsed / duration, 1);
const easedProgress = easeInOutCubic(progress);
scrollPosition = Math.min(
startPosition + (endPosition - startPosition) * easedProgress,
maxScroll // Never exceed max scroll
);
img.style.transform = `translateY(-${scrollPosition}px)`;
if (progress >= 1) {
clearInterval(scrollAnimation);
scrollPosition = maxScroll; // Ensure we're at the exact bottom
img.style.transform = `translateY(-${scrollPosition}px)`;
// Wait 1 second, then scroll back up
setTimeout(() => {
const scrollUpStartTime = Date.now();
const scrollUpStartPosition = maxScroll;
const scrollUpEndPosition = 0;
const scrollUpDuration = 2000; // 2 seconds to scroll back up
scrollAnimation = setInterval(() => {
const scrollUpElapsed = Date.now() - scrollUpStartTime;
const scrollUpProgress = Math.min(scrollUpElapsed / scrollUpDuration, 1);
const scrollUpEasedProgress = easeInOutCubic(scrollUpProgress);
scrollPosition = Math.max(
scrollUpStartPosition + (scrollUpEndPosition - scrollUpStartPosition) * scrollUpEasedProgress,
0 // Never go below 0
);
img.style.transform = `translateY(-${scrollPosition}px)`;
if (scrollUpProgress >= 1) {
clearInterval(scrollAnimation);
scrollPosition = 0;
scrollAnimation = null;
isScrolling = false;
img.style.transform = 'translateY(0)';
}
}, 16);
}, 1000);
}
}, 16);
}, 50); // Small delay to ensure layout is complete
};
if (img.complete) {
checkImage();
} else {
img.onload = checkImage;
}
}
"
@mouseleave="
if (scrollAnimation) {
clearInterval(scrollAnimation);
scrollAnimation = null;
}
const img = $el.querySelector('img');
if (img) {
scrollPosition = 0;
isScrolling = false;
img.style.transform = 'translateY(0)';
img.style.transition = 'transform 0.3s ease-out';
setTimeout(() => {
img.style.transition = '';
}, 300);
}
"
class="flex relative flex-col gap-2 px-3 py-2 mb-4 bg-gray-100 rounded-lg border border-gray-200 transition-all duration-300 ease-in-out cursor-pointer group break-inside-avoid"
>
Feature
{
// Reset transform to get accurate measurements
img.style.transform = 'translateY(0)';
// Small delay to ensure layout is complete
setTimeout(() => {
// Force a reflow to ensure dimensions are accurate
void img.offsetHeight;
void container.offsetHeight;
// Get actual rendered image dimensions
// scrollHeight gives us the full content height including overflow
const imgHeight = img.scrollHeight || img.offsetHeight;
const containerHeight = container.offsetHeight;
// Calculate max scroll: only scroll until bottom of image is visible
// When translateY(-maxScroll), the bottom of the image aligns with bottom of container
// maxScroll = imgHeight - containerHeight
const maxScroll = Math.max(0, imgHeight - containerHeight);
// Only proceed if there's actually something to scroll
if (maxScroll <= 0 || maxScroll > imgHeight) {
return;
}
isScrolling = true;
scrollPosition = 0;
// Animation parameters
const duration = 2000; // 2 seconds to scroll down
const startTime = Date.now();
const startPosition = 0;
const endPosition = maxScroll; // Stop when bottom is visible
// Scroll down with easing
scrollAnimation = setInterval(() => {
const elapsed = Date.now() - startTime;
const progress = Math.min(elapsed / duration, 1);
const easedProgress = easeInOutCubic(progress);
scrollPosition = Math.min(
startPosition + (endPosition - startPosition) * easedProgress,
maxScroll // Never exceed max scroll
);
img.style.transform = `translateY(-${scrollPosition}px)`;
if (progress >= 1) {
clearInterval(scrollAnimation);
scrollPosition = maxScroll; // Ensure we're at the exact bottom
img.style.transform = `translateY(-${scrollPosition}px)`;
// Wait 1 second, then scroll back up
setTimeout(() => {
const scrollUpStartTime = Date.now();
const scrollUpStartPosition = maxScroll;
const scrollUpEndPosition = 0;
const scrollUpDuration = 2000; // 2 seconds to scroll back up
scrollAnimation = setInterval(() => {
const scrollUpElapsed = Date.now() - scrollUpStartTime;
const scrollUpProgress = Math.min(scrollUpElapsed / scrollUpDuration, 1);
const scrollUpEasedProgress = easeInOutCubic(scrollUpProgress);
scrollPosition = Math.max(
scrollUpStartPosition + (scrollUpEndPosition - scrollUpStartPosition) * scrollUpEasedProgress,
0 // Never go below 0
);
img.style.transform = `translateY(-${scrollPosition}px)`;
if (scrollUpProgress >= 1) {
clearInterval(scrollAnimation);
scrollPosition = 0;
scrollAnimation = null;
isScrolling = false;
img.style.transform = 'translateY(0)';
}
}, 16);
}, 1000);
}
}, 16);
}, 50); // Small delay to ensure layout is complete
};
if (img.complete) {
checkImage();
} else {
img.onload = checkImage;
}
}
"
@mouseleave="
if (scrollAnimation) {
clearInterval(scrollAnimation);
scrollAnimation = null;
}
const img = $el.querySelector('img');
if (img) {
scrollPosition = 0;
isScrolling = false;
img.style.transform = 'translateY(0)';
img.style.transition = 'transform 0.3s ease-out';
setTimeout(() => {
img.style.transition = '';
}, 300);
}
"
class="flex relative flex-col gap-2 px-3 py-2 mb-4 bg-gray-100 rounded-lg border border-gray-200 transition-all duration-300 ease-in-out cursor-pointer group break-inside-avoid"
>
Feature Grid
{
// Reset transform to get accurate measurements
img.style.transform = 'translateY(0)';
// Small delay to ensure layout is complete
setTimeout(() => {
// Force a reflow to ensure dimensions are accurate
void img.offsetHeight;
void container.offsetHeight;
// Get actual rendered image dimensions
// scrollHeight gives us the full content height including overflow
const imgHeight = img.scrollHeight || img.offsetHeight;
const containerHeight = container.offsetHeight;
// Calculate max scroll: only scroll until bottom of image is visible
// When translateY(-maxScroll), the bottom of the image aligns with bottom of container
// maxScroll = imgHeight - containerHeight
const maxScroll = Math.max(0, imgHeight - containerHeight);
// Only proceed if there's actually something to scroll
if (maxScroll <= 0 || maxScroll > imgHeight) {
return;
}
isScrolling = true;
scrollPosition = 0;
// Animation parameters
const duration = 2000; // 2 seconds to scroll down
const startTime = Date.now();
const startPosition = 0;
const endPosition = maxScroll; // Stop when bottom is visible
// Scroll down with easing
scrollAnimation = setInterval(() => {
const elapsed = Date.now() - startTime;
const progress = Math.min(elapsed / duration, 1);
const easedProgress = easeInOutCubic(progress);
scrollPosition = Math.min(
startPosition + (endPosition - startPosition) * easedProgress,
maxScroll // Never exceed max scroll
);
img.style.transform = `translateY(-${scrollPosition}px)`;
if (progress >= 1) {
clearInterval(scrollAnimation);
scrollPosition = maxScroll; // Ensure we're at the exact bottom
img.style.transform = `translateY(-${scrollPosition}px)`;
// Wait 1 second, then scroll back up
setTimeout(() => {
const scrollUpStartTime = Date.now();
const scrollUpStartPosition = maxScroll;
const scrollUpEndPosition = 0;
const scrollUpDuration = 2000; // 2 seconds to scroll back up
scrollAnimation = setInterval(() => {
const scrollUpElapsed = Date.now() - scrollUpStartTime;
const scrollUpProgress = Math.min(scrollUpElapsed / scrollUpDuration, 1);
const scrollUpEasedProgress = easeInOutCubic(scrollUpProgress);
scrollPosition = Math.max(
scrollUpStartPosition + (scrollUpEndPosition - scrollUpStartPosition) * scrollUpEasedProgress,
0 // Never go below 0
);
img.style.transform = `translateY(-${scrollPosition}px)`;
if (scrollUpProgress >= 1) {
clearInterval(scrollAnimation);
scrollPosition = 0;
scrollAnimation = null;
isScrolling = false;
img.style.transform = 'translateY(0)';
}
}, 16);
}, 1000);
}
}, 16);
}, 50); // Small delay to ensure layout is complete
};
if (img.complete) {
checkImage();
} else {
img.onload = checkImage;
}
}
"
@mouseleave="
if (scrollAnimation) {
clearInterval(scrollAnimation);
scrollAnimation = null;
}
const img = $el.querySelector('img');
if (img) {
scrollPosition = 0;
isScrolling = false;
img.style.transform = 'translateY(0)';
img.style.transition = 'transform 0.3s ease-out';
setTimeout(() => {
img.style.transition = '';
}, 300);
}
"
class="flex relative flex-col gap-2 px-3 py-2 mb-4 bg-gray-100 rounded-lg border border-gray-200 transition-all duration-300 ease-in-out cursor-pointer group break-inside-avoid"
>
Features Block [Block]
{
// Reset transform to get accurate measurements
img.style.transform = 'translateY(0)';
// Small delay to ensure layout is complete
setTimeout(() => {
// Force a reflow to ensure dimensions are accurate
void img.offsetHeight;
void container.offsetHeight;
// Get actual rendered image dimensions
// scrollHeight gives us the full content height including overflow
const imgHeight = img.scrollHeight || img.offsetHeight;
const containerHeight = container.offsetHeight;
// Calculate max scroll: only scroll until bottom of image is visible
// When translateY(-maxScroll), the bottom of the image aligns with bottom of container
// maxScroll = imgHeight - containerHeight
const maxScroll = Math.max(0, imgHeight - containerHeight);
// Only proceed if there's actually something to scroll
if (maxScroll <= 0 || maxScroll > imgHeight) {
return;
}
isScrolling = true;
scrollPosition = 0;
// Animation parameters
const duration = 2000; // 2 seconds to scroll down
const startTime = Date.now();
const startPosition = 0;
const endPosition = maxScroll; // Stop when bottom is visible
// Scroll down with easing
scrollAnimation = setInterval(() => {
const elapsed = Date.now() - startTime;
const progress = Math.min(elapsed / duration, 1);
const easedProgress = easeInOutCubic(progress);
scrollPosition = Math.min(
startPosition + (endPosition - startPosition) * easedProgress,
maxScroll // Never exceed max scroll
);
img.style.transform = `translateY(-${scrollPosition}px)`;
if (progress >= 1) {
clearInterval(scrollAnimation);
scrollPosition = maxScroll; // Ensure we're at the exact bottom
img.style.transform = `translateY(-${scrollPosition}px)`;
// Wait 1 second, then scroll back up
setTimeout(() => {
const scrollUpStartTime = Date.now();
const scrollUpStartPosition = maxScroll;
const scrollUpEndPosition = 0;
const scrollUpDuration = 2000; // 2 seconds to scroll back up
scrollAnimation = setInterval(() => {
const scrollUpElapsed = Date.now() - scrollUpStartTime;
const scrollUpProgress = Math.min(scrollUpElapsed / scrollUpDuration, 1);
const scrollUpEasedProgress = easeInOutCubic(scrollUpProgress);
scrollPosition = Math.max(
scrollUpStartPosition + (scrollUpEndPosition - scrollUpStartPosition) * scrollUpEasedProgress,
0 // Never go below 0
);
img.style.transform = `translateY(-${scrollPosition}px)`;
if (scrollUpProgress >= 1) {
clearInterval(scrollAnimation);
scrollPosition = 0;
scrollAnimation = null;
isScrolling = false;
img.style.transform = 'translateY(0)';
}
}, 16);
}, 1000);
}
}, 16);
}, 50); // Small delay to ensure layout is complete
};
if (img.complete) {
checkImage();
} else {
img.onload = checkImage;
}
}
"
@mouseleave="
if (scrollAnimation) {
clearInterval(scrollAnimation);
scrollAnimation = null;
}
const img = $el.querySelector('img');
if (img) {
scrollPosition = 0;
isScrolling = false;
img.style.transform = 'translateY(0)';
img.style.transition = 'transform 0.3s ease-out';
setTimeout(() => {
img.style.transition = '';
}, 300);
}
"
class="flex relative flex-col gap-2 px-3 py-2 mb-4 bg-gray-100 rounded-lg border border-gray-200 transition-all duration-300 ease-in-out cursor-pointer group break-inside-avoid"
>
Features V2
{
// Reset transform to get accurate measurements
img.style.transform = 'translateY(0)';
// Small delay to ensure layout is complete
setTimeout(() => {
// Force a reflow to ensure dimensions are accurate
void img.offsetHeight;
void container.offsetHeight;
// Get actual rendered image dimensions
// scrollHeight gives us the full content height including overflow
const imgHeight = img.scrollHeight || img.offsetHeight;
const containerHeight = container.offsetHeight;
// Calculate max scroll: only scroll until bottom of image is visible
// When translateY(-maxScroll), the bottom of the image aligns with bottom of container
// maxScroll = imgHeight - containerHeight
const maxScroll = Math.max(0, imgHeight - containerHeight);
// Only proceed if there's actually something to scroll
if (maxScroll <= 0 || maxScroll > imgHeight) {
return;
}
isScrolling = true;
scrollPosition = 0;
// Animation parameters
const duration = 2000; // 2 seconds to scroll down
const startTime = Date.now();
const startPosition = 0;
const endPosition = maxScroll; // Stop when bottom is visible
// Scroll down with easing
scrollAnimation = setInterval(() => {
const elapsed = Date.now() - startTime;
const progress = Math.min(elapsed / duration, 1);
const easedProgress = easeInOutCubic(progress);
scrollPosition = Math.min(
startPosition + (endPosition - startPosition) * easedProgress,
maxScroll // Never exceed max scroll
);
img.style.transform = `translateY(-${scrollPosition}px)`;
if (progress >= 1) {
clearInterval(scrollAnimation);
scrollPosition = maxScroll; // Ensure we're at the exact bottom
img.style.transform = `translateY(-${scrollPosition}px)`;
// Wait 1 second, then scroll back up
setTimeout(() => {
const scrollUpStartTime = Date.now();
const scrollUpStartPosition = maxScroll;
const scrollUpEndPosition = 0;
const scrollUpDuration = 2000; // 2 seconds to scroll back up
scrollAnimation = setInterval(() => {
const scrollUpElapsed = Date.now() - scrollUpStartTime;
const scrollUpProgress = Math.min(scrollUpElapsed / scrollUpDuration, 1);
const scrollUpEasedProgress = easeInOutCubic(scrollUpProgress);
scrollPosition = Math.max(
scrollUpStartPosition + (scrollUpEndPosition - scrollUpStartPosition) * scrollUpEasedProgress,
0 // Never go below 0
);
img.style.transform = `translateY(-${scrollPosition}px)`;
if (scrollUpProgress >= 1) {
clearInterval(scrollAnimation);
scrollPosition = 0;
scrollAnimation = null;
isScrolling = false;
img.style.transform = 'translateY(0)';
}
}, 16);
}, 1000);
}
}, 16);
}, 50); // Small delay to ensure layout is complete
};
if (img.complete) {
checkImage();
} else {
img.onload = checkImage;
}
}
"
@mouseleave="
if (scrollAnimation) {
clearInterval(scrollAnimation);
scrollAnimation = null;
}
const img = $el.querySelector('img');
if (img) {
scrollPosition = 0;
isScrolling = false;
img.style.transform = 'translateY(0)';
img.style.transition = 'transform 0.3s ease-out';
setTimeout(() => {
img.style.transition = '';
}, 300);
}
"
class="flex relative flex-col gap-2 px-3 py-2 mb-4 bg-gray-100 rounded-lg border border-gray-200 transition-all duration-300 ease-in-out cursor-pointer group break-inside-avoid"
>
Header [Block]
{
// Reset transform to get accurate measurements
img.style.transform = 'translateY(0)';
// Small delay to ensure layout is complete
setTimeout(() => {
// Force a reflow to ensure dimensions are accurate
void img.offsetHeight;
void container.offsetHeight;
// Get actual rendered image dimensions
// scrollHeight gives us the full content height including overflow
const imgHeight = img.scrollHeight || img.offsetHeight;
const containerHeight = container.offsetHeight;
// Calculate max scroll: only scroll until bottom of image is visible
// When translateY(-maxScroll), the bottom of the image aligns with bottom of container
// maxScroll = imgHeight - containerHeight
const maxScroll = Math.max(0, imgHeight - containerHeight);
// Only proceed if there's actually something to scroll
if (maxScroll <= 0 || maxScroll > imgHeight) {
return;
}
isScrolling = true;
scrollPosition = 0;
// Animation parameters
const duration = 2000; // 2 seconds to scroll down
const startTime = Date.now();
const startPosition = 0;
const endPosition = maxScroll; // Stop when bottom is visible
// Scroll down with easing
scrollAnimation = setInterval(() => {
const elapsed = Date.now() - startTime;
const progress = Math.min(elapsed / duration, 1);
const easedProgress = easeInOutCubic(progress);
scrollPosition = Math.min(
startPosition + (endPosition - startPosition) * easedProgress,
maxScroll // Never exceed max scroll
);
img.style.transform = `translateY(-${scrollPosition}px)`;
if (progress >= 1) {
clearInterval(scrollAnimation);
scrollPosition = maxScroll; // Ensure we're at the exact bottom
img.style.transform = `translateY(-${scrollPosition}px)`;
// Wait 1 second, then scroll back up
setTimeout(() => {
const scrollUpStartTime = Date.now();
const scrollUpStartPosition = maxScroll;
const scrollUpEndPosition = 0;
const scrollUpDuration = 2000; // 2 seconds to scroll back up
scrollAnimation = setInterval(() => {
const scrollUpElapsed = Date.now() - scrollUpStartTime;
const scrollUpProgress = Math.min(scrollUpElapsed / scrollUpDuration, 1);
const scrollUpEasedProgress = easeInOutCubic(scrollUpProgress);
scrollPosition = Math.max(
scrollUpStartPosition + (scrollUpEndPosition - scrollUpStartPosition) * scrollUpEasedProgress,
0 // Never go below 0
);
img.style.transform = `translateY(-${scrollPosition}px)`;
if (scrollUpProgress >= 1) {
clearInterval(scrollAnimation);
scrollPosition = 0;
scrollAnimation = null;
isScrolling = false;
img.style.transform = 'translateY(0)';
}
}, 16);
}, 1000);
}
}, 16);
}, 50); // Small delay to ensure layout is complete
};
if (img.complete) {
checkImage();
} else {
img.onload = checkImage;
}
}
"
@mouseleave="
if (scrollAnimation) {
clearInterval(scrollAnimation);
scrollAnimation = null;
}
const img = $el.querySelector('img');
if (img) {
scrollPosition = 0;
isScrolling = false;
img.style.transform = 'translateY(0)';
img.style.transition = 'transform 0.3s ease-out';
setTimeout(() => {
img.style.transition = '';
}, 300);
}
"
class="flex relative flex-col gap-2 px-3 py-2 mb-4 bg-gray-100 rounded-lg border border-gray-200 transition-all duration-300 ease-in-out cursor-pointer group break-inside-avoid"
>
Hero
{
// Reset transform to get accurate measurements
img.style.transform = 'translateY(0)';
// Small delay to ensure layout is complete
setTimeout(() => {
// Force a reflow to ensure dimensions are accurate
void img.offsetHeight;
void container.offsetHeight;
// Get actual rendered image dimensions
// scrollHeight gives us the full content height including overflow
const imgHeight = img.scrollHeight || img.offsetHeight;
const containerHeight = container.offsetHeight;
// Calculate max scroll: only scroll until bottom of image is visible
// When translateY(-maxScroll), the bottom of the image aligns with bottom of container
// maxScroll = imgHeight - containerHeight
const maxScroll = Math.max(0, imgHeight - containerHeight);
// Only proceed if there's actually something to scroll
if (maxScroll <= 0 || maxScroll > imgHeight) {
return;
}
isScrolling = true;
scrollPosition = 0;
// Animation parameters
const duration = 2000; // 2 seconds to scroll down
const startTime = Date.now();
const startPosition = 0;
const endPosition = maxScroll; // Stop when bottom is visible
// Scroll down with easing
scrollAnimation = setInterval(() => {
const elapsed = Date.now() - startTime;
const progress = Math.min(elapsed / duration, 1);
const easedProgress = easeInOutCubic(progress);
scrollPosition = Math.min(
startPosition + (endPosition - startPosition) * easedProgress,
maxScroll // Never exceed max scroll
);
img.style.transform = `translateY(-${scrollPosition}px)`;
if (progress >= 1) {
clearInterval(scrollAnimation);
scrollPosition = maxScroll; // Ensure we're at the exact bottom
img.style.transform = `translateY(-${scrollPosition}px)`;
// Wait 1 second, then scroll back up
setTimeout(() => {
const scrollUpStartTime = Date.now();
const scrollUpStartPosition = maxScroll;
const scrollUpEndPosition = 0;
const scrollUpDuration = 2000; // 2 seconds to scroll back up
scrollAnimation = setInterval(() => {
const scrollUpElapsed = Date.now() - scrollUpStartTime;
const scrollUpProgress = Math.min(scrollUpElapsed / scrollUpDuration, 1);
const scrollUpEasedProgress = easeInOutCubic(scrollUpProgress);
scrollPosition = Math.max(
scrollUpStartPosition + (scrollUpEndPosition - scrollUpStartPosition) * scrollUpEasedProgress,
0 // Never go below 0
);
img.style.transform = `translateY(-${scrollPosition}px)`;
if (scrollUpProgress >= 1) {
clearInterval(scrollAnimation);
scrollPosition = 0;
scrollAnimation = null;
isScrolling = false;
img.style.transform = 'translateY(0)';
}
}, 16);
}, 1000);
}
}, 16);
}, 50); // Small delay to ensure layout is complete
};
if (img.complete) {
checkImage();
} else {
img.onload = checkImage;
}
}
"
@mouseleave="
if (scrollAnimation) {
clearInterval(scrollAnimation);
scrollAnimation = null;
}
const img = $el.querySelector('img');
if (img) {
scrollPosition = 0;
isScrolling = false;
img.style.transform = 'translateY(0)';
img.style.transition = 'transform 0.3s ease-out';
setTimeout(() => {
img.style.transition = '';
}, 300);
}
"
class="flex relative flex-col gap-2 px-3 py-2 mb-4 bg-gray-100 rounded-lg border border-gray-200 transition-all duration-300 ease-in-out cursor-pointer group break-inside-avoid"
>
Image Hero [Block]
{
// Reset transform to get accurate measurements
img.style.transform = 'translateY(0)';
// Small delay to ensure layout is complete
setTimeout(() => {
// Force a reflow to ensure dimensions are accurate
void img.offsetHeight;
void container.offsetHeight;
// Get actual rendered image dimensions
// scrollHeight gives us the full content height including overflow
const imgHeight = img.scrollHeight || img.offsetHeight;
const containerHeight = container.offsetHeight;
// Calculate max scroll: only scroll until bottom of image is visible
// When translateY(-maxScroll), the bottom of the image aligns with bottom of container
// maxScroll = imgHeight - containerHeight
const maxScroll = Math.max(0, imgHeight - containerHeight);
// Only proceed if there's actually something to scroll
if (maxScroll <= 0 || maxScroll > imgHeight) {
return;
}
isScrolling = true;
scrollPosition = 0;
// Animation parameters
const duration = 2000; // 2 seconds to scroll down
const startTime = Date.now();
const startPosition = 0;
const endPosition = maxScroll; // Stop when bottom is visible
// Scroll down with easing
scrollAnimation = setInterval(() => {
const elapsed = Date.now() - startTime;
const progress = Math.min(elapsed / duration, 1);
const easedProgress = easeInOutCubic(progress);
scrollPosition = Math.min(
startPosition + (endPosition - startPosition) * easedProgress,
maxScroll // Never exceed max scroll
);
img.style.transform = `translateY(-${scrollPosition}px)`;
if (progress >= 1) {
clearInterval(scrollAnimation);
scrollPosition = maxScroll; // Ensure we're at the exact bottom
img.style.transform = `translateY(-${scrollPosition}px)`;
// Wait 1 second, then scroll back up
setTimeout(() => {
const scrollUpStartTime = Date.now();
const scrollUpStartPosition = maxScroll;
const scrollUpEndPosition = 0;
const scrollUpDuration = 2000; // 2 seconds to scroll back up
scrollAnimation = setInterval(() => {
const scrollUpElapsed = Date.now() - scrollUpStartTime;
const scrollUpProgress = Math.min(scrollUpElapsed / scrollUpDuration, 1);
const scrollUpEasedProgress = easeInOutCubic(scrollUpProgress);
scrollPosition = Math.max(
scrollUpStartPosition + (scrollUpEndPosition - scrollUpStartPosition) * scrollUpEasedProgress,
0 // Never go below 0
);
img.style.transform = `translateY(-${scrollPosition}px)`;
if (scrollUpProgress >= 1) {
clearInterval(scrollAnimation);
scrollPosition = 0;
scrollAnimation = null;
isScrolling = false;
img.style.transform = 'translateY(0)';
}
}, 16);
}, 1000);
}
}, 16);
}, 50); // Small delay to ensure layout is complete
};
if (img.complete) {
checkImage();
} else {
img.onload = checkImage;
}
}
"
@mouseleave="
if (scrollAnimation) {
clearInterval(scrollAnimation);
scrollAnimation = null;
}
const img = $el.querySelector('img');
if (img) {
scrollPosition = 0;
isScrolling = false;
img.style.transform = 'translateY(0)';
img.style.transition = 'transform 0.3s ease-out';
setTimeout(() => {
img.style.transition = '';
}, 300);
}
"
class="flex relative flex-col gap-2 px-3 py-2 mb-4 bg-gray-100 rounded-lg border border-gray-200 transition-all duration-300 ease-in-out cursor-pointer group break-inside-avoid"
>
Integrations
{
// Reset transform to get accurate measurements
img.style.transform = 'translateY(0)';
// Small delay to ensure layout is complete
setTimeout(() => {
// Force a reflow to ensure dimensions are accurate
void img.offsetHeight;
void container.offsetHeight;
// Get actual rendered image dimensions
// scrollHeight gives us the full content height including overflow
const imgHeight = img.scrollHeight || img.offsetHeight;
const containerHeight = container.offsetHeight;
// Calculate max scroll: only scroll until bottom of image is visible
// When translateY(-maxScroll), the bottom of the image aligns with bottom of container
// maxScroll = imgHeight - containerHeight
const maxScroll = Math.max(0, imgHeight - containerHeight);
// Only proceed if there's actually something to scroll
if (maxScroll <= 0 || maxScroll > imgHeight) {
return;
}
isScrolling = true;
scrollPosition = 0;
// Animation parameters
const duration = 2000; // 2 seconds to scroll down
const startTime = Date.now();
const startPosition = 0;
const endPosition = maxScroll; // Stop when bottom is visible
// Scroll down with easing
scrollAnimation = setInterval(() => {
const elapsed = Date.now() - startTime;
const progress = Math.min(elapsed / duration, 1);
const easedProgress = easeInOutCubic(progress);
scrollPosition = Math.min(
startPosition + (endPosition - startPosition) * easedProgress,
maxScroll // Never exceed max scroll
);
img.style.transform = `translateY(-${scrollPosition}px)`;
if (progress >= 1) {
clearInterval(scrollAnimation);
scrollPosition = maxScroll; // Ensure we're at the exact bottom
img.style.transform = `translateY(-${scrollPosition}px)`;
// Wait 1 second, then scroll back up
setTimeout(() => {
const scrollUpStartTime = Date.now();
const scrollUpStartPosition = maxScroll;
const scrollUpEndPosition = 0;
const scrollUpDuration = 2000; // 2 seconds to scroll back up
scrollAnimation = setInterval(() => {
const scrollUpElapsed = Date.now() - scrollUpStartTime;
const scrollUpProgress = Math.min(scrollUpElapsed / scrollUpDuration, 1);
const scrollUpEasedProgress = easeInOutCubic(scrollUpProgress);
scrollPosition = Math.max(
scrollUpStartPosition + (scrollUpEndPosition - scrollUpStartPosition) * scrollUpEasedProgress,
0 // Never go below 0
);
img.style.transform = `translateY(-${scrollPosition}px)`;
if (scrollUpProgress >= 1) {
clearInterval(scrollAnimation);
scrollPosition = 0;
scrollAnimation = null;
isScrolling = false;
img.style.transform = 'translateY(0)';
}
}, 16);
}, 1000);
}
}, 16);
}, 50); // Small delay to ensure layout is complete
};
if (img.complete) {
checkImage();
} else {
img.onload = checkImage;
}
}
"
@mouseleave="
if (scrollAnimation) {
clearInterval(scrollAnimation);
scrollAnimation = null;
}
const img = $el.querySelector('img');
if (img) {
scrollPosition = 0;
isScrolling = false;
img.style.transform = 'translateY(0)';
img.style.transition = 'transform 0.3s ease-out';
setTimeout(() => {
img.style.transition = '';
}, 300);
}
"
class="flex relative flex-col gap-2 px-3 py-2 mb-4 bg-gray-100 rounded-lg border border-gray-200 transition-all duration-300 ease-in-out cursor-pointer group break-inside-avoid"
>
Logo Cloud [Block]
{
// Reset transform to get accurate measurements
img.style.transform = 'translateY(0)';
// Small delay to ensure layout is complete
setTimeout(() => {
// Force a reflow to ensure dimensions are accurate
void img.offsetHeight;
void container.offsetHeight;
// Get actual rendered image dimensions
// scrollHeight gives us the full content height including overflow
const imgHeight = img.scrollHeight || img.offsetHeight;
const containerHeight = container.offsetHeight;
// Calculate max scroll: only scroll until bottom of image is visible
// When translateY(-maxScroll), the bottom of the image aligns with bottom of container
// maxScroll = imgHeight - containerHeight
const maxScroll = Math.max(0, imgHeight - containerHeight);
// Only proceed if there's actually something to scroll
if (maxScroll <= 0 || maxScroll > imgHeight) {
return;
}
isScrolling = true;
scrollPosition = 0;
// Animation parameters
const duration = 2000; // 2 seconds to scroll down
const startTime = Date.now();
const startPosition = 0;
const endPosition = maxScroll; // Stop when bottom is visible
// Scroll down with easing
scrollAnimation = setInterval(() => {
const elapsed = Date.now() - startTime;
const progress = Math.min(elapsed / duration, 1);
const easedProgress = easeInOutCubic(progress);
scrollPosition = Math.min(
startPosition + (endPosition - startPosition) * easedProgress,
maxScroll // Never exceed max scroll
);
img.style.transform = `translateY(-${scrollPosition}px)`;
if (progress >= 1) {
clearInterval(scrollAnimation);
scrollPosition = maxScroll; // Ensure we're at the exact bottom
img.style.transform = `translateY(-${scrollPosition}px)`;
// Wait 1 second, then scroll back up
setTimeout(() => {
const scrollUpStartTime = Date.now();
const scrollUpStartPosition = maxScroll;
const scrollUpEndPosition = 0;
const scrollUpDuration = 2000; // 2 seconds to scroll back up
scrollAnimation = setInterval(() => {
const scrollUpElapsed = Date.now() - scrollUpStartTime;
const scrollUpProgress = Math.min(scrollUpElapsed / scrollUpDuration, 1);
const scrollUpEasedProgress = easeInOutCubic(scrollUpProgress);
scrollPosition = Math.max(
scrollUpStartPosition + (scrollUpEndPosition - scrollUpStartPosition) * scrollUpEasedProgress,
0 // Never go below 0
);
img.style.transform = `translateY(-${scrollPosition}px)`;
if (scrollUpProgress >= 1) {
clearInterval(scrollAnimation);
scrollPosition = 0;
scrollAnimation = null;
isScrolling = false;
img.style.transform = 'translateY(0)';
}
}, 16);
}, 1000);
}
}, 16);
}, 50); // Small delay to ensure layout is complete
};
if (img.complete) {
checkImage();
} else {
img.onload = checkImage;
}
}
"
@mouseleave="
if (scrollAnimation) {
clearInterval(scrollAnimation);
scrollAnimation = null;
}
const img = $el.querySelector('img');
if (img) {
scrollPosition = 0;
isScrolling = false;
img.style.transform = 'translateY(0)';
img.style.transition = 'transform 0.3s ease-out';
setTimeout(() => {
img.style.transition = '';
}, 300);
}
"
class="flex relative flex-col gap-2 px-3 py-2 mb-4 bg-gray-100 rounded-lg border border-gray-200 transition-all duration-300 ease-in-out cursor-pointer group break-inside-avoid"
>
Logos
{
// Reset transform to get accurate measurements
img.style.transform = 'translateY(0)';
// Small delay to ensure layout is complete
setTimeout(() => {
// Force a reflow to ensure dimensions are accurate
void img.offsetHeight;
void container.offsetHeight;
// Get actual rendered image dimensions
// scrollHeight gives us the full content height including overflow
const imgHeight = img.scrollHeight || img.offsetHeight;
const containerHeight = container.offsetHeight;
// Calculate max scroll: only scroll until bottom of image is visible
// When translateY(-maxScroll), the bottom of the image aligns with bottom of container
// maxScroll = imgHeight - containerHeight
const maxScroll = Math.max(0, imgHeight - containerHeight);
// Only proceed if there's actually something to scroll
if (maxScroll <= 0 || maxScroll > imgHeight) {
return;
}
isScrolling = true;
scrollPosition = 0;
// Animation parameters
const duration = 2000; // 2 seconds to scroll down
const startTime = Date.now();
const startPosition = 0;
const endPosition = maxScroll; // Stop when bottom is visible
// Scroll down with easing
scrollAnimation = setInterval(() => {
const elapsed = Date.now() - startTime;
const progress = Math.min(elapsed / duration, 1);
const easedProgress = easeInOutCubic(progress);
scrollPosition = Math.min(
startPosition + (endPosition - startPosition) * easedProgress,
maxScroll // Never exceed max scroll
);
img.style.transform = `translateY(-${scrollPosition}px)`;
if (progress >= 1) {
clearInterval(scrollAnimation);
scrollPosition = maxScroll; // Ensure we're at the exact bottom
img.style.transform = `translateY(-${scrollPosition}px)`;
// Wait 1 second, then scroll back up
setTimeout(() => {
const scrollUpStartTime = Date.now();
const scrollUpStartPosition = maxScroll;
const scrollUpEndPosition = 0;
const scrollUpDuration = 2000; // 2 seconds to scroll back up
scrollAnimation = setInterval(() => {
const scrollUpElapsed = Date.now() - scrollUpStartTime;
const scrollUpProgress = Math.min(scrollUpElapsed / scrollUpDuration, 1);
const scrollUpEasedProgress = easeInOutCubic(scrollUpProgress);
scrollPosition = Math.max(
scrollUpStartPosition + (scrollUpEndPosition - scrollUpStartPosition) * scrollUpEasedProgress,
0 // Never go below 0
);
img.style.transform = `translateY(-${scrollPosition}px)`;
if (scrollUpProgress >= 1) {
clearInterval(scrollAnimation);
scrollPosition = 0;
scrollAnimation = null;
isScrolling = false;
img.style.transform = 'translateY(0)';
}
}, 16);
}, 1000);
}
}, 16);
}, 50); // Small delay to ensure layout is complete
};
if (img.complete) {
checkImage();
} else {
img.onload = checkImage;
}
}
"
@mouseleave="
if (scrollAnimation) {
clearInterval(scrollAnimation);
scrollAnimation = null;
}
const img = $el.querySelector('img');
if (img) {
scrollPosition = 0;
isScrolling = false;
img.style.transform = 'translateY(0)';
img.style.transition = 'transform 0.3s ease-out';
setTimeout(() => {
img.style.transition = '';
}, 300);
}
"
class="flex relative flex-col gap-2 px-3 py-2 mb-4 bg-gray-100 rounded-lg border border-gray-200 transition-all duration-300 ease-in-out cursor-pointer group break-inside-avoid"
>
Quote
{
// Reset transform to get accurate measurements
img.style.transform = 'translateY(0)';
// Small delay to ensure layout is complete
setTimeout(() => {
// Force a reflow to ensure dimensions are accurate
void img.offsetHeight;
void container.offsetHeight;
// Get actual rendered image dimensions
// scrollHeight gives us the full content height including overflow
const imgHeight = img.scrollHeight || img.offsetHeight;
const containerHeight = container.offsetHeight;
// Calculate max scroll: only scroll until bottom of image is visible
// When translateY(-maxScroll), the bottom of the image aligns with bottom of container
// maxScroll = imgHeight - containerHeight
const maxScroll = Math.max(0, imgHeight - containerHeight);
// Only proceed if there's actually something to scroll
if (maxScroll <= 0 || maxScroll > imgHeight) {
return;
}
isScrolling = true;
scrollPosition = 0;
// Animation parameters
const duration = 2000; // 2 seconds to scroll down
const startTime = Date.now();
const startPosition = 0;
const endPosition = maxScroll; // Stop when bottom is visible
// Scroll down with easing
scrollAnimation = setInterval(() => {
const elapsed = Date.now() - startTime;
const progress = Math.min(elapsed / duration, 1);
const easedProgress = easeInOutCubic(progress);
scrollPosition = Math.min(
startPosition + (endPosition - startPosition) * easedProgress,
maxScroll // Never exceed max scroll
);
img.style.transform = `translateY(-${scrollPosition}px)`;
if (progress >= 1) {
clearInterval(scrollAnimation);
scrollPosition = maxScroll; // Ensure we're at the exact bottom
img.style.transform = `translateY(-${scrollPosition}px)`;
// Wait 1 second, then scroll back up
setTimeout(() => {
const scrollUpStartTime = Date.now();
const scrollUpStartPosition = maxScroll;
const scrollUpEndPosition = 0;
const scrollUpDuration = 2000; // 2 seconds to scroll back up
scrollAnimation = setInterval(() => {
const scrollUpElapsed = Date.now() - scrollUpStartTime;
const scrollUpProgress = Math.min(scrollUpElapsed / scrollUpDuration, 1);
const scrollUpEasedProgress = easeInOutCubic(scrollUpProgress);
scrollPosition = Math.max(
scrollUpStartPosition + (scrollUpEndPosition - scrollUpStartPosition) * scrollUpEasedProgress,
0 // Never go below 0
);
img.style.transform = `translateY(-${scrollPosition}px)`;
if (scrollUpProgress >= 1) {
clearInterval(scrollAnimation);
scrollPosition = 0;
scrollAnimation = null;
isScrolling = false;
img.style.transform = 'translateY(0)';
}
}, 16);
}, 1000);
}
}, 16);
}, 50); // Small delay to ensure layout is complete
};
if (img.complete) {
checkImage();
} else {
img.onload = checkImage;
}
}
"
@mouseleave="
if (scrollAnimation) {
clearInterval(scrollAnimation);
scrollAnimation = null;
}
const img = $el.querySelector('img');
if (img) {
scrollPosition = 0;
isScrolling = false;
img.style.transform = 'translateY(0)';
img.style.transition = 'transform 0.3s ease-out';
setTimeout(() => {
img.style.transition = '';
}, 300);
}
"
class="flex relative flex-col gap-2 px-3 py-2 mb-4 bg-gray-100 rounded-lg border border-gray-200 transition-all duration-300 ease-in-out cursor-pointer group break-inside-avoid"
>
Quotes
{
// Reset transform to get accurate measurements
img.style.transform = 'translateY(0)';
// Small delay to ensure layout is complete
setTimeout(() => {
// Force a reflow to ensure dimensions are accurate
void img.offsetHeight;
void container.offsetHeight;
// Get actual rendered image dimensions
// scrollHeight gives us the full content height including overflow
const imgHeight = img.scrollHeight || img.offsetHeight;
const containerHeight = container.offsetHeight;
// Calculate max scroll: only scroll until bottom of image is visible
// When translateY(-maxScroll), the bottom of the image aligns with bottom of container
// maxScroll = imgHeight - containerHeight
const maxScroll = Math.max(0, imgHeight - containerHeight);
// Only proceed if there's actually something to scroll
if (maxScroll <= 0 || maxScroll > imgHeight) {
return;
}
isScrolling = true;
scrollPosition = 0;
// Animation parameters
const duration = 2000; // 2 seconds to scroll down
const startTime = Date.now();
const startPosition = 0;
const endPosition = maxScroll; // Stop when bottom is visible
// Scroll down with easing
scrollAnimation = setInterval(() => {
const elapsed = Date.now() - startTime;
const progress = Math.min(elapsed / duration, 1);
const easedProgress = easeInOutCubic(progress);
scrollPosition = Math.min(
startPosition + (endPosition - startPosition) * easedProgress,
maxScroll // Never exceed max scroll
);
img.style.transform = `translateY(-${scrollPosition}px)`;
if (progress >= 1) {
clearInterval(scrollAnimation);
scrollPosition = maxScroll; // Ensure we're at the exact bottom
img.style.transform = `translateY(-${scrollPosition}px)`;
// Wait 1 second, then scroll back up
setTimeout(() => {
const scrollUpStartTime = Date.now();
const scrollUpStartPosition = maxScroll;
const scrollUpEndPosition = 0;
const scrollUpDuration = 2000; // 2 seconds to scroll back up
scrollAnimation = setInterval(() => {
const scrollUpElapsed = Date.now() - scrollUpStartTime;
const scrollUpProgress = Math.min(scrollUpElapsed / scrollUpDuration, 1);
const scrollUpEasedProgress = easeInOutCubic(scrollUpProgress);
scrollPosition = Math.max(
scrollUpStartPosition + (scrollUpEndPosition - scrollUpStartPosition) * scrollUpEasedProgress,
0 // Never go below 0
);
img.style.transform = `translateY(-${scrollPosition}px)`;
if (scrollUpProgress >= 1) {
clearInterval(scrollAnimation);
scrollPosition = 0;
scrollAnimation = null;
isScrolling = false;
img.style.transform = 'translateY(0)';
}
}, 16);
}, 1000);
}
}, 16);
}, 50); // Small delay to ensure layout is complete
};
if (img.complete) {
checkImage();
} else {
img.onload = checkImage;
}
}
"
@mouseleave="
if (scrollAnimation) {
clearInterval(scrollAnimation);
scrollAnimation = null;
}
const img = $el.querySelector('img');
if (img) {
scrollPosition = 0;
isScrolling = false;
img.style.transform = 'translateY(0)';
img.style.transition = 'transform 0.3s ease-out';
setTimeout(() => {
img.style.transition = '';
}, 300);
}
"
class="flex relative flex-col gap-2 px-3 py-2 mb-4 bg-gray-100 rounded-lg border border-gray-200 transition-all duration-300 ease-in-out cursor-pointer group break-inside-avoid"
>
Quotes Block
{
// Reset transform to get accurate measurements
img.style.transform = 'translateY(0)';
// Small delay to ensure layout is complete
setTimeout(() => {
// Force a reflow to ensure dimensions are accurate
void img.offsetHeight;
void container.offsetHeight;
// Get actual rendered image dimensions
// scrollHeight gives us the full content height including overflow
const imgHeight = img.scrollHeight || img.offsetHeight;
const containerHeight = container.offsetHeight;
// Calculate max scroll: only scroll until bottom of image is visible
// When translateY(-maxScroll), the bottom of the image aligns with bottom of container
// maxScroll = imgHeight - containerHeight
const maxScroll = Math.max(0, imgHeight - containerHeight);
// Only proceed if there's actually something to scroll
if (maxScroll <= 0 || maxScroll > imgHeight) {
return;
}
isScrolling = true;
scrollPosition = 0;
// Animation parameters
const duration = 2000; // 2 seconds to scroll down
const startTime = Date.now();
const startPosition = 0;
const endPosition = maxScroll; // Stop when bottom is visible
// Scroll down with easing
scrollAnimation = setInterval(() => {
const elapsed = Date.now() - startTime;
const progress = Math.min(elapsed / duration, 1);
const easedProgress = easeInOutCubic(progress);
scrollPosition = Math.min(
startPosition + (endPosition - startPosition) * easedProgress,
maxScroll // Never exceed max scroll
);
img.style.transform = `translateY(-${scrollPosition}px)`;
if (progress >= 1) {
clearInterval(scrollAnimation);
scrollPosition = maxScroll; // Ensure we're at the exact bottom
img.style.transform = `translateY(-${scrollPosition}px)`;
// Wait 1 second, then scroll back up
setTimeout(() => {
const scrollUpStartTime = Date.now();
const scrollUpStartPosition = maxScroll;
const scrollUpEndPosition = 0;
const scrollUpDuration = 2000; // 2 seconds to scroll back up
scrollAnimation = setInterval(() => {
const scrollUpElapsed = Date.now() - scrollUpStartTime;
const scrollUpProgress = Math.min(scrollUpElapsed / scrollUpDuration, 1);
const scrollUpEasedProgress = easeInOutCubic(scrollUpProgress);
scrollPosition = Math.max(
scrollUpStartPosition + (scrollUpEndPosition - scrollUpStartPosition) * scrollUpEasedProgress,
0 // Never go below 0
);
img.style.transform = `translateY(-${scrollPosition}px)`;
if (scrollUpProgress >= 1) {
clearInterval(scrollAnimation);
scrollPosition = 0;
scrollAnimation = null;
isScrolling = false;
img.style.transform = 'translateY(0)';
}
}, 16);
}, 1000);
}
}, 16);
}, 50); // Small delay to ensure layout is complete
};
if (img.complete) {
checkImage();
} else {
img.onload = checkImage;
}
}
"
@mouseleave="
if (scrollAnimation) {
clearInterval(scrollAnimation);
scrollAnimation = null;
}
const img = $el.querySelector('img');
if (img) {
scrollPosition = 0;
isScrolling = false;
img.style.transform = 'translateY(0)';
img.style.transition = 'transform 0.3s ease-out';
setTimeout(() => {
img.style.transition = '';
}, 300);
}
"
class="flex relative flex-col gap-2 px-3 py-2 mb-4 bg-gray-100 rounded-lg border border-gray-200 transition-all duration-300 ease-in-out cursor-pointer group break-inside-avoid"
>
Stats Block [Block]
{
// Reset transform to get accurate measurements
img.style.transform = 'translateY(0)';
// Small delay to ensure layout is complete
setTimeout(() => {
// Force a reflow to ensure dimensions are accurate
void img.offsetHeight;
void container.offsetHeight;
// Get actual rendered image dimensions
// scrollHeight gives us the full content height including overflow
const imgHeight = img.scrollHeight || img.offsetHeight;
const containerHeight = container.offsetHeight;
// Calculate max scroll: only scroll until bottom of image is visible
// When translateY(-maxScroll), the bottom of the image aligns with bottom of container
// maxScroll = imgHeight - containerHeight
const maxScroll = Math.max(0, imgHeight - containerHeight);
// Only proceed if there's actually something to scroll
if (maxScroll <= 0 || maxScroll > imgHeight) {
return;
}
isScrolling = true;
scrollPosition = 0;
// Animation parameters
const duration = 2000; // 2 seconds to scroll down
const startTime = Date.now();
const startPosition = 0;
const endPosition = maxScroll; // Stop when bottom is visible
// Scroll down with easing
scrollAnimation = setInterval(() => {
const elapsed = Date.now() - startTime;
const progress = Math.min(elapsed / duration, 1);
const easedProgress = easeInOutCubic(progress);
scrollPosition = Math.min(
startPosition + (endPosition - startPosition) * easedProgress,
maxScroll // Never exceed max scroll
);
img.style.transform = `translateY(-${scrollPosition}px)`;
if (progress >= 1) {
clearInterval(scrollAnimation);
scrollPosition = maxScroll; // Ensure we're at the exact bottom
img.style.transform = `translateY(-${scrollPosition}px)`;
// Wait 1 second, then scroll back up
setTimeout(() => {
const scrollUpStartTime = Date.now();
const scrollUpStartPosition = maxScroll;
const scrollUpEndPosition = 0;
const scrollUpDuration = 2000; // 2 seconds to scroll back up
scrollAnimation = setInterval(() => {
const scrollUpElapsed = Date.now() - scrollUpStartTime;
const scrollUpProgress = Math.min(scrollUpElapsed / scrollUpDuration, 1);
const scrollUpEasedProgress = easeInOutCubic(scrollUpProgress);
scrollPosition = Math.max(
scrollUpStartPosition + (scrollUpEndPosition - scrollUpStartPosition) * scrollUpEasedProgress,
0 // Never go below 0
);
img.style.transform = `translateY(-${scrollPosition}px)`;
if (scrollUpProgress >= 1) {
clearInterval(scrollAnimation);
scrollPosition = 0;
scrollAnimation = null;
isScrolling = false;
img.style.transform = 'translateY(0)';
}
}, 16);
}, 1000);
}
}, 16);
}, 50); // Small delay to ensure layout is complete
};
if (img.complete) {
checkImage();
} else {
img.onload = checkImage;
}
}
"
@mouseleave="
if (scrollAnimation) {
clearInterval(scrollAnimation);
scrollAnimation = null;
}
const img = $el.querySelector('img');
if (img) {
scrollPosition = 0;
isScrolling = false;
img.style.transform = 'translateY(0)';
img.style.transition = 'transform 0.3s ease-out';
setTimeout(() => {
img.style.transition = '';
}, 300);
}
"
class="flex relative flex-col gap-2 px-3 py-2 mb-4 bg-gray-100 rounded-lg border border-gray-200 transition-all duration-300 ease-in-out cursor-pointer group break-inside-avoid"
>
Team Members [Block]
{
// Reset transform to get accurate measurements
img.style.transform = 'translateY(0)';
// Small delay to ensure layout is complete
setTimeout(() => {
// Force a reflow to ensure dimensions are accurate
void img.offsetHeight;
void container.offsetHeight;
// Get actual rendered image dimensions
// scrollHeight gives us the full content height including overflow
const imgHeight = img.scrollHeight || img.offsetHeight;
const containerHeight = container.offsetHeight;
// Calculate max scroll: only scroll until bottom of image is visible
// When translateY(-maxScroll), the bottom of the image aligns with bottom of container
// maxScroll = imgHeight - containerHeight
const maxScroll = Math.max(0, imgHeight - containerHeight);
// Only proceed if there's actually something to scroll
if (maxScroll <= 0 || maxScroll > imgHeight) {
return;
}
isScrolling = true;
scrollPosition = 0;
// Animation parameters
const duration = 2000; // 2 seconds to scroll down
const startTime = Date.now();
const startPosition = 0;
const endPosition = maxScroll; // Stop when bottom is visible
// Scroll down with easing
scrollAnimation = setInterval(() => {
const elapsed = Date.now() - startTime;
const progress = Math.min(elapsed / duration, 1);
const easedProgress = easeInOutCubic(progress);
scrollPosition = Math.min(
startPosition + (endPosition - startPosition) * easedProgress,
maxScroll // Never exceed max scroll
);
img.style.transform = `translateY(-${scrollPosition}px)`;
if (progress >= 1) {
clearInterval(scrollAnimation);
scrollPosition = maxScroll; // Ensure we're at the exact bottom
img.style.transform = `translateY(-${scrollPosition}px)`;
// Wait 1 second, then scroll back up
setTimeout(() => {
const scrollUpStartTime = Date.now();
const scrollUpStartPosition = maxScroll;
const scrollUpEndPosition = 0;
const scrollUpDuration = 2000; // 2 seconds to scroll back up
scrollAnimation = setInterval(() => {
const scrollUpElapsed = Date.now() - scrollUpStartTime;
const scrollUpProgress = Math.min(scrollUpElapsed / scrollUpDuration, 1);
const scrollUpEasedProgress = easeInOutCubic(scrollUpProgress);
scrollPosition = Math.max(
scrollUpStartPosition + (scrollUpEndPosition - scrollUpStartPosition) * scrollUpEasedProgress,
0 // Never go below 0
);
img.style.transform = `translateY(-${scrollPosition}px)`;
if (scrollUpProgress >= 1) {
clearInterval(scrollAnimation);
scrollPosition = 0;
scrollAnimation = null;
isScrolling = false;
img.style.transform = 'translateY(0)';
}
}, 16);
}, 1000);
}
}, 16);
}, 50); // Small delay to ensure layout is complete
};
if (img.complete) {
checkImage();
} else {
img.onload = checkImage;
}
}
"
@mouseleave="
if (scrollAnimation) {
clearInterval(scrollAnimation);
scrollAnimation = null;
}
const img = $el.querySelector('img');
if (img) {
scrollPosition = 0;
isScrolling = false;
img.style.transform = 'translateY(0)';
img.style.transition = 'transform 0.3s ease-out';
setTimeout(() => {
img.style.transition = '';
}, 300);
}
"
class="flex relative flex-col gap-2 px-3 py-2 mb-4 bg-gray-100 rounded-lg border border-gray-200 transition-all duration-300 ease-in-out cursor-pointer group break-inside-avoid"
>
Thank you [Block]
{
// Reset transform to get accurate measurements
img.style.transform = 'translateY(0)';
// Small delay to ensure layout is complete
setTimeout(() => {
// Force a reflow to ensure dimensions are accurate
void img.offsetHeight;
void container.offsetHeight;
// Get actual rendered image dimensions
// scrollHeight gives us the full content height including overflow
const imgHeight = img.scrollHeight || img.offsetHeight;
const containerHeight = container.offsetHeight;
// Calculate max scroll: only scroll until bottom of image is visible
// When translateY(-maxScroll), the bottom of the image aligns with bottom of container
// maxScroll = imgHeight - containerHeight
const maxScroll = Math.max(0, imgHeight - containerHeight);
// Only proceed if there's actually something to scroll
if (maxScroll <= 0 || maxScroll > imgHeight) {
return;
}
isScrolling = true;
scrollPosition = 0;
// Animation parameters
const duration = 2000; // 2 seconds to scroll down
const startTime = Date.now();
const startPosition = 0;
const endPosition = maxScroll; // Stop when bottom is visible
// Scroll down with easing
scrollAnimation = setInterval(() => {
const elapsed = Date.now() - startTime;
const progress = Math.min(elapsed / duration, 1);
const easedProgress = easeInOutCubic(progress);
scrollPosition = Math.min(
startPosition + (endPosition - startPosition) * easedProgress,
maxScroll // Never exceed max scroll
);
img.style.transform = `translateY(-${scrollPosition}px)`;
if (progress >= 1) {
clearInterval(scrollAnimation);
scrollPosition = maxScroll; // Ensure we're at the exact bottom
img.style.transform = `translateY(-${scrollPosition}px)`;
// Wait 1 second, then scroll back up
setTimeout(() => {
const scrollUpStartTime = Date.now();
const scrollUpStartPosition = maxScroll;
const scrollUpEndPosition = 0;
const scrollUpDuration = 2000; // 2 seconds to scroll back up
scrollAnimation = setInterval(() => {
const scrollUpElapsed = Date.now() - scrollUpStartTime;
const scrollUpProgress = Math.min(scrollUpElapsed / scrollUpDuration, 1);
const scrollUpEasedProgress = easeInOutCubic(scrollUpProgress);
scrollPosition = Math.max(
scrollUpStartPosition + (scrollUpEndPosition - scrollUpStartPosition) * scrollUpEasedProgress,
0 // Never go below 0
);
img.style.transform = `translateY(-${scrollPosition}px)`;
if (scrollUpProgress >= 1) {
clearInterval(scrollAnimation);
scrollPosition = 0;
scrollAnimation = null;
isScrolling = false;
img.style.transform = 'translateY(0)';
}
}, 16);
}, 1000);
}
}, 16);
}, 50); // Small delay to ensure layout is complete
};
if (img.complete) {
checkImage();
} else {
img.onload = checkImage;
}
}
"
@mouseleave="
if (scrollAnimation) {
clearInterval(scrollAnimation);
scrollAnimation = null;
}
const img = $el.querySelector('img');
if (img) {
scrollPosition = 0;
isScrolling = false;
img.style.transform = 'translateY(0)';
img.style.transition = 'transform 0.3s ease-out';
setTimeout(() => {
img.style.transition = '';
}, 300);
}
"
class="flex relative flex-col gap-2 px-3 py-2 mb-4 bg-gray-100 rounded-lg border border-gray-200 transition-all duration-300 ease-in-out cursor-pointer group break-inside-avoid"
>
Video