feature/comparison-slider #19
@@ -204,37 +204,41 @@ export function createCharacterComparison<
|
||||
/**
|
||||
* 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 lineData - The line data object
|
||||
* @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)
|
||||
*/
|
||||
function getCharState(
|
||||
lineIndex: number,
|
||||
charIndex: number,
|
||||
lineData: LineData,
|
||||
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
|
||||
// 1. Find the left edge of the centered line
|
||||
const lineStartOffset = (containerWidth - lineData.width) / 2;
|
||||
if (!charElement) {
|
||||
return { proximity: 0, isPast: false };
|
||||
}
|
||||
|
||||
// 2. Find the character's center relative to the line
|
||||
const charRelativePercent = (charIndex + 0.5) / lineData.text.length;
|
||||
const charPixelPos = lineStartOffset + (charRelativePercent * lineData.width);
|
||||
// Get the actual bounding box of the character
|
||||
const charRect = charElement.getBoundingClientRect();
|
||||
const containerRect = container.getBoundingClientRect();
|
||||
|
||||
// 3. Convert back to global percentage (0-100)
|
||||
const charGlobalPercent = (charPixelPos / containerWidth) * 100;
|
||||
// Calculate character center relative to container
|
||||
const charCenter = charRect.left + (charRect.width / 2) - containerRect.left;
|
||||
const charGlobalPercent = (charCenter / containerWidth) * 100;
|
||||
|
||||
const distance = Math.abs(sliderPos - charGlobalPercent);
|
||||
|
||||
// Proximity range: +/- 15% around the slider
|
||||
const range = 15;
|
||||
const range = 5;
|
||||
const proximity = Math.max(0, 1 - distance / range);
|
||||
|
||||
const isPast = sliderPos > charGlobalPercent;
|
||||
|
||||
return { proximity, isPast };
|
||||
|
||||
@@ -68,6 +68,8 @@ const charComparison = createCharacterComparison(
|
||||
() => sizeControl.value,
|
||||
);
|
||||
|
||||
let lineElements = $state<(HTMLElement | undefined)[]>([]);
|
||||
|
||||
/** Physics-based spring for smooth handle movement */
|
||||
const sliderSpring = new Spring(50, {
|
||||
stiffness: 0.2, // Balanced for responsiveness
|
||||
@@ -138,12 +140,18 @@ $effect(() => {
|
||||
|
||||
{#snippet renderLine(line: LineData, lineIndex: number)}
|
||||
<div
|
||||
bind:this={lineElements[lineIndex]}
|
||||
class="relative flex w-full justify-center items-center whitespace-nowrap"
|
||||
style:height={`${heightControl.value}em`}
|
||||
style:line-height={`${heightControl.value}em`}
|
||||
>
|
||||
{#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
|
||||
- Font Family switches based on `isPast`
|
||||
@@ -177,29 +185,25 @@ $effect(() => {
|
||||
aria-label="Font comparison slider"
|
||||
onpointerdown={startDragging}
|
||||
class="
|
||||
group relative w-full py-16 px-6 sm:py-24 sm:px-12 overflow-hidden
|
||||
bg-indigo-50 rounded-[2.5rem] border border-slate-100 shadow-2xl
|
||||
group relative w-full py-16 px-0 sm:py-24 sm:px-0 overflow-hidden
|
||||
rounded-[2.5rem]
|
||||
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 }}
|
||||
>
|
||||
<!-- 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 -->
|
||||
<div
|
||||
class="
|
||||
relative flex flex-col items-center gap-4
|
||||
text-4xl sm:text-5xl md:text-6xl lg:text-7xl font-bold leading-[1.15]
|
||||
z-10 pointer-events-none text-center
|
||||
drop-shadow-[0_3px_6px_rgba(255,255,255,0.9)]
|
||||
"
|
||||
style:perspective="1000px"
|
||||
>
|
||||
|
||||
Reference in New Issue
Block a user