feat(ComparisonWrapper): remove props and add checks for fonts absence

This commit is contained in:
Ilia Mashkov
2026-01-26 12:54:01 +03:00
parent 31e4c64193
commit 75a9c16070

View File

@@ -9,38 +9,26 @@
- Responsive layout with Tailwind breakpoints for font sizing. - Responsive layout with Tailwind breakpoints for font sizing.
- Performance optimized using offscreen canvas for measurements and transform-based animations. - Performance optimized using offscreen canvas for measurements and transform-based animations.
--> -->
<script lang="ts" generics="T extends { name: string; id: string }"> <script lang="ts">
import { displayedFontsStore } from '$features/DisplayFont';
import { import {
createCharacterComparison, createCharacterComparison,
createTypographyControl, createTypographyControl,
} from '$shared/lib'; } from '$shared/lib';
import type { LineData } from '$shared/lib'; import type { LineData } from '$shared/lib';
import { cubicOut } from 'svelte/easing';
import { Spring } from 'svelte/motion'; import { Spring } from 'svelte/motion';
import { fly } from 'svelte/transition';
import CharacterSlot from './components/CharacterSlot.svelte'; import CharacterSlot from './components/CharacterSlot.svelte';
import ControlsWrapper from './components/ControlsWrapper.svelte'; import ControlsWrapper from './components/ControlsWrapper.svelte';
import Labels from './components/Labels.svelte'; import Labels from './components/Labels.svelte';
import SliderLine from './components/SliderLine.svelte'; import SliderLine from './components/SliderLine.svelte';
interface Props<T extends { name: string; id: string }> { // Displayed text
/** let text = $state('The quick brown fox jumps over the lazy dog...');
* First font definition ({name, id}) // Pair of fonts to compare
*/ const fontA = $derived(displayedFontsStore.fontA);
fontA: T; const fontB = $derived(displayedFontsStore.fontB);
/**
* Second font definition ({name, id})
*/
fontB: T;
/**
* Text to display and compare
*/
text?: string;
}
let {
fontA,
fontB,
text = $bindable('The quick brown fox jumps over the lazy dog'),
}: Props<T> = $props();
let container: HTMLElement | undefined = $state(); let container: HTMLElement | undefined = $state();
let controlsWrapperElement = $state<HTMLDivElement | null>(null); let controlsWrapperElement = $state<HTMLDivElement | null>(null);
@@ -98,7 +86,6 @@ function handleMove(e: PointerEvent) {
function startDragging(e: PointerEvent) { function startDragging(e: PointerEvent) {
if (e.target === controlsWrapperElement || controlsWrapperElement?.contains(e.target as Node)) { if (e.target === controlsWrapperElement || controlsWrapperElement?.contains(e.target as Node)) {
console.log('Pointer down on controls wrapper');
e.stopPropagation(); e.stopPropagation();
return; return;
} }
@@ -162,82 +149,87 @@ $effect(() => {
- Font Family switches based on `isPast` - Font Family switches based on `isPast`
- Transitions/Transforms provide the "morph" feel - Transitions/Transforms provide the "morph" feel
--> -->
<CharacterSlot {#if fontA && fontB}
{char} <CharacterSlot
{proximity} {char}
{isPast} {proximity}
weight={weightControl.value} {isPast}
size={sizeControl.value} weight={weightControl.value}
fontAName={fontA.name} size={sizeControl.value}
fontBName={fontB.name} fontAName={fontA.name}
/> fontBName={fontB.name}
/>
{/if}
{/each} {/each}
</div> </div>
{/snippet} {/snippet}
<!-- Hidden canvas used for text measurement by the helper --> {#if fontA && fontB}
<canvas bind:this={measureCanvas} class="hidden" width="1" height="1"></canvas> <!-- Hidden canvas used for text measurement by the helper -->
<canvas bind:this={measureCanvas} class="hidden" width="1" height="1"></canvas>
<div class="relative"> <div class="relative">
<div
bind:this={container}
role="slider"
tabindex="0"
aria-valuenow={Math.round(sliderPos)}
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
select-none touch-none cursor-ew-resize min-h-100 flex flex-col justify-center
"
class:box-shadow={'-20px 20px 60px #bebebe, 20px -20px 60px #ffffff;'}
>
<!-- Background Gradient Accent -->
<div <div
bind:this={container}
role="slider"
tabindex="0"
aria-valuenow={Math.round(sliderPos)}
aria-label="Font comparison slider"
onpointerdown={startDragging}
class=" class="
absolute inset-0 bg-linear-to-br group relative w-full py-16 px-6 sm:py-24 sm:px-12 overflow-hidden
from-slate-50/50 via-white to-slate-100/50 bg-indigo-50 rounded-[2.5rem] border border-slate-100 shadow-2xl
opacity-50 pointer-events-none select-none touch-none cursor-ew-resize min-h-100 flex flex-col justify-center
" "
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
"
style:perspective="1000px"
>
{#each charComparison.lines as line, lineIndex}
<div
class="relative w-full whitespace-nowrap"
style:height={`${heightControl.value}em`}
style:display="flex"
style:align-items="center"
style:justify-content="center"
>
{@render renderLine(line, lineIndex)}
</div>
{/each}
</div>
<SliderLine {sliderPos} {isDragging} />
</div> </div>
<!-- Text Rendering Container --> <Labels fontA={fontA} fontB={fontB} {sliderPos} />
<div <!-- Since there're slider controls inside we put them outside the main one -->
class=" <ControlsWrapper
relative flex flex-col items-center gap-4 bind:wrapper={controlsWrapperElement}
text-4xl sm:text-5xl md:text-6xl lg:text-7xl font-bold leading-[1.15] {sliderPos}
z-10 pointer-events-none text-center {isDragging}
" bind:text={text}
style:perspective="1000px" containerWidth={container?.clientWidth}
> weightControl={weightControl}
{#each charComparison.lines as line, lineIndex} sizeControl={sizeControl}
<div heightControl={heightControl}
class="relative w-full whitespace-nowrap" />
style:height={`${heightControl.value}em`}
style:display="flex"
style:align-items="center"
style:justify-content="center"
>
{@render renderLine(line, lineIndex)}
</div>
{/each}
</div>
<!-- Visual Components -->
<SliderLine {sliderPos} {isDragging} />
<Labels {fontA} {fontB} {sliderPos} />
</div> </div>
<!-- Since there're slider controls inside we put them outside the main one --> {/if}
<ControlsWrapper
bind:wrapper={controlsWrapperElement}
{sliderPos}
{isDragging}
bind:text={text}
containerWidth={container?.clientWidth}
weightControl={weightControl}
sizeControl={sizeControl}
heightControl={heightControl}
/>
</div>