feat(ComparisonView): add redesigned font comparison widget
This commit is contained in:
90
src/widgets/ComparisonView/ui/Character/Character.svelte
Normal file
90
src/widgets/ComparisonView/ui/Character/Character.svelte
Normal file
@@ -0,0 +1,90 @@
|
||||
<!--
|
||||
Component: Character
|
||||
Renders a single character with morphing animation
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { cn } from '$shared/shadcn/utils/shadcn-utils';
|
||||
import { comparisonStore } from '../../model';
|
||||
|
||||
interface Props {
|
||||
/**
|
||||
* Character
|
||||
*/
|
||||
char: string;
|
||||
/**
|
||||
* Proximity value
|
||||
*/
|
||||
proximity: number;
|
||||
/**
|
||||
* Past state
|
||||
*/
|
||||
isPast: boolean;
|
||||
}
|
||||
|
||||
let { char, proximity, isPast }: Props = $props();
|
||||
|
||||
const fontA = $derived(comparisonStore.fontA);
|
||||
const fontB = $derived(comparisonStore.fontB);
|
||||
const typography = $derived(comparisonStore.typography);
|
||||
|
||||
let slot = $state<0 | 1>(0);
|
||||
let slotFonts = $state<[string, string]>(['', '']);
|
||||
|
||||
const displayChar = $derived(char === ' ' ? '\u00A0' : char);
|
||||
const targetFont = $derived(isPast ? fontA?.name ?? '' : fontB?.name ?? '');
|
||||
|
||||
$effect(() => {
|
||||
if (!targetFont || slotFonts[slot] === targetFont) return;
|
||||
const next = slot === 0 ? 1 : 0;
|
||||
slotFonts[next] = targetFont;
|
||||
slot = next;
|
||||
});
|
||||
</script>
|
||||
|
||||
{#if fontA && fontB}
|
||||
<span
|
||||
class="char-wrap"
|
||||
style:font-size="{typography.renderedSize}px"
|
||||
style:will-change={proximity > 0 ? 'transform' : 'auto'}
|
||||
>
|
||||
{#each [0, 1] as s (s)}
|
||||
<span
|
||||
class={cn(
|
||||
'char-inner',
|
||||
isPast
|
||||
? 'text-swiss-black/75 dark:text-brand/75'
|
||||
: 'text-neutral-950 dark:text-white',
|
||||
)}
|
||||
style:font-family={slotFonts[s]}
|
||||
style:font-weight={typography.weight}
|
||||
style:opacity={slot === s ? '1' : '0'}
|
||||
style:position={slot === s ? 'relative' : 'absolute'}
|
||||
aria-hidden={slot !== s ? true : undefined}
|
||||
>
|
||||
{displayChar}
|
||||
</span>
|
||||
{/each}
|
||||
</span>
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
.char-wrap {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.char-inner {
|
||||
top: 0;
|
||||
left: 0;
|
||||
backface-visibility: hidden;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
font-synthesis: none;
|
||||
text-rendering: geometricPrecision;
|
||||
font-optical-sizing: auto;
|
||||
transition:
|
||||
opacity 0.1s ease-out,
|
||||
color 0.2s ease-out,
|
||||
transform 0.2s cubic-bezier(0.34, 1.56, 0.64, 1);
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user