feature/comparison-slider #19

Merged
ilia merged 129 commits from feature/comparison-slider into main 2026-02-02 09:23:46 +00:00
2 changed files with 39 additions and 31 deletions
Showing only changes of commit fbaf596fef - Show all commits

View File

@@ -204,37 +204,41 @@ export function createCharacterComparison<
/** /**
* precise calculation of character state based on global slider position. * precise calculation of character state based on global slider position.
* *
* @param lineIndex - Index of the line
* @param charIndex - Index of the character in the line * @param charIndex - Index of the character in the line
* @param lineData - The line data object
* @param sliderPos - Current slider position (0-100) * @param sliderPos - Current slider position (0-100)
* @param lineElement - The line element
* @param container - The container element
* @returns Object containing proximity (0-1) and isPast (boolean) * @returns Object containing proximity (0-1) and isPast (boolean)
*/ */
function getCharState( function getCharState(
lineIndex: number,
charIndex: number, charIndex: number,
lineData: LineData,
sliderPos: number, sliderPos: number,
lineElement?: HTMLElement,
container?: HTMLElement,
) { ) {
if (!containerWidth) return { proximity: 0, isPast: false }; if (!containerWidth || !container) {
return {
proximity: 0,
isPast: false,
};
}
const charElement = lineElement?.children[charIndex] as HTMLElement;
// Calculate the pixel position of the character relative to the CONTAINER if (!charElement) {
// 1. Find the left edge of the centered line return { proximity: 0, isPast: false };
const lineStartOffset = (containerWidth - lineData.width) / 2; }
// 2. Find the character's center relative to the line // Get the actual bounding box of the character
const charRelativePercent = (charIndex + 0.5) / lineData.text.length; const charRect = charElement.getBoundingClientRect();
const charPixelPos = lineStartOffset + (charRelativePercent * lineData.width); const containerRect = container.getBoundingClientRect();
// 3. Convert back to global percentage (0-100) // Calculate character center relative to container
const charGlobalPercent = (charPixelPos / containerWidth) * 100; const charCenter = charRect.left + (charRect.width / 2) - containerRect.left;
const charGlobalPercent = (charCenter / containerWidth) * 100;
const distance = Math.abs(sliderPos - charGlobalPercent); const distance = Math.abs(sliderPos - charGlobalPercent);
const range = 5;
// Proximity range: +/- 15% around the slider
const range = 15;
const proximity = Math.max(0, 1 - distance / range); const proximity = Math.max(0, 1 - distance / range);
const isPast = sliderPos > charGlobalPercent; const isPast = sliderPos > charGlobalPercent;
return { proximity, isPast }; return { proximity, isPast };

View File

@@ -68,6 +68,8 @@ const charComparison = createCharacterComparison(
() => sizeControl.value, () => sizeControl.value,
); );
let lineElements = $state<(HTMLElement | undefined)[]>([]);
/** Physics-based spring for smooth handle movement */ /** Physics-based spring for smooth handle movement */
const sliderSpring = new Spring(50, { const sliderSpring = new Spring(50, {
stiffness: 0.2, // Balanced for responsiveness stiffness: 0.2, // Balanced for responsiveness
@@ -138,12 +140,18 @@ $effect(() => {
{#snippet renderLine(line: LineData, lineIndex: number)} {#snippet renderLine(line: LineData, lineIndex: number)}
<div <div
bind:this={lineElements[lineIndex]}
class="relative flex w-full justify-center items-center whitespace-nowrap" class="relative flex w-full justify-center items-center whitespace-nowrap"
style:height={`${heightControl.value}em`} style:height={`${heightControl.value}em`}
style:line-height={`${heightControl.value}em`} style:line-height={`${heightControl.value}em`}
> >
{#each line.text.split('') as char, charIndex} {#each line.text.split('') as char, charIndex}
{@const { proximity, isPast } = charComparison.getCharState(lineIndex, charIndex, line, sliderPos)} {@const { proximity, isPast } = charComparison.getCharState(
charIndex,
sliderPos,
lineElements[lineIndex],
container,
),}
<!-- <!--
Single Character Span Single Character Span
- Font Family switches based on `isPast` - Font Family switches based on `isPast`
@@ -177,29 +185,25 @@ $effect(() => {
aria-label="Font comparison slider" aria-label="Font comparison slider"
onpointerdown={startDragging} onpointerdown={startDragging}
class=" class="
group relative w-full py-16 px-6 sm:py-24 sm:px-12 overflow-hidden group relative w-full py-16 px-0 sm:py-24 sm:px-0 overflow-hidden
bg-indigo-50 rounded-[2.5rem] border border-slate-100 shadow-2xl rounded-[2.5rem]
select-none touch-none cursor-ew-resize min-h-100 flex flex-col justify-center select-none touch-none cursor-ew-resize min-h-100 flex flex-col justify-center
backdrop-blur-lg bg-gradient-to-br from-gray-100/70 via-white/50 to-gray-100/60
border border-gray-300/40
shadow-[inset_0_4px_12px_0_rgba(0,0,0,0.12),inset_0_2px_4px_0_rgba(0,0,0,0.08),0_1px_2px_0_rgba(255,255,255,0.8)]
before:absolute before:inset-0 before:rounded-[2.5rem] before:p-[1px]
before:bg-gradient-to-br before:from-black/5 before:via-black/2 before:to-transparent
before:-z-10 before:blur-sm
" "
class:box-shadow={'-20px 20px 60px #bebebe, 20px -20px 60px #ffffff;'}
in:fly={{ y: 0, x: -50, duration: 300, easing: cubicOut, opacity: 0.2 }} in:fly={{ y: 0, x: -50, duration: 300, easing: cubicOut, opacity: 0.2 }}
> >
<!-- Background Gradient Accent -->
<div
class="
absolute inset-0 bg-linear-to-br
from-slate-50/50 via-white to-slate-100/50
opacity-50 pointer-events-none
"
>
</div>
<!-- Text Rendering Container --> <!-- Text Rendering Container -->
<div <div
class=" class="
relative flex flex-col items-center gap-4 relative flex flex-col items-center gap-4
text-4xl sm:text-5xl md:text-6xl lg:text-7xl font-bold leading-[1.15] text-4xl sm:text-5xl md:text-6xl lg:text-7xl font-bold leading-[1.15]
z-10 pointer-events-none text-center z-10 pointer-events-none text-center
drop-shadow-[0_3px_6px_rgba(255,255,255,0.9)]
" "
style:perspective="1000px" style:perspective="1000px"
> >