93 lines
2.4 KiB
Svelte
93 lines
2.4 KiB
Svelte
<!--
|
|
Component: Character
|
|
Renders a single character with morphing animation
|
|
-->
|
|
<script lang="ts">
|
|
import { typographySettingsStore } from '$features/SetupFont';
|
|
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(typographySettingsStore);
|
|
|
|
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',
|
|
'transition-colors duration-300',
|
|
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>
|