Compare commits
8 Commits
75ea5ab382
...
ef48d9815c
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ef48d9815c | ||
|
|
818dfdb55e | ||
|
|
42e1271647 | ||
|
|
8ef9226dd2 | ||
|
|
f0c0a9de45 | ||
|
|
730eba138d | ||
|
|
18f265974e | ||
|
|
705723b009 |
@@ -41,7 +41,7 @@ let { children }: Props = $props();
|
|||||||
<header></header>
|
<header></header>
|
||||||
|
|
||||||
<ScrollArea class="h-screen w-screen">
|
<ScrollArea class="h-screen w-screen">
|
||||||
<main class="flex-1 h-full w-full max-w-6xl mx-auto px-4 py-6 md:px-8 lg:py-10 relative">
|
<main class="flex-1 h-full w-full max-w-6xl mx-auto px-4 pt-6 pb-10 md:px-8 lg:pt-10 lg:pb-20 relative">
|
||||||
<TooltipProvider>
|
<TooltipProvider>
|
||||||
<TypographyMenu />
|
<TypographyMenu />
|
||||||
{@render children?.()}
|
{@render children?.()}
|
||||||
|
|||||||
@@ -9,10 +9,10 @@ import { displayedFontsStore } from '../../model';
|
|||||||
import FontSampler from '../FontSampler/FontSampler.svelte';
|
import FontSampler from '../FontSampler/FontSampler.svelte';
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="grid gap-2 grid-cols-[repeat(auto-fit,minmax(500px,1fr))]">
|
<div class="grid gap-2 grid-cols-[repeat(auto-fit,minmax(500px,1fr))] will-change-tranform transition-transform content">
|
||||||
{#each displayedFontsStore.fonts as font (font.id)}
|
{#each displayedFontsStore.fonts as font, index (font.id)}
|
||||||
<div animate:flip={{ delay: 0, duration: 400, easing: quintOut }}>
|
<div animate:flip={{ delay: 0, duration: 400, easing: quintOut }}>
|
||||||
<FontSampler font={font} bind:text={displayedFontsStore.text} />
|
<FontSampler font={font} bind:text={displayedFontsStore.text} index={index} />
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -9,12 +9,11 @@ import {
|
|||||||
selectedFontsStore,
|
selectedFontsStore,
|
||||||
} from '$entities/Font';
|
} from '$entities/Font';
|
||||||
import { controlManager } from '$features/SetupFont';
|
import { controlManager } from '$features/SetupFont';
|
||||||
import { cn } from '$shared/shadcn/utils/shadcn-utils';
|
|
||||||
import {
|
import {
|
||||||
ContentEditable,
|
ContentEditable,
|
||||||
IconButton,
|
IconButton,
|
||||||
} from '$shared/ui';
|
} from '$shared/ui';
|
||||||
import MinusIcon from '@lucide/svelte/icons/minus';
|
import XIcon from '@lucide/svelte/icons/x';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
/**
|
/**
|
||||||
@@ -25,6 +24,10 @@ interface Props {
|
|||||||
* Text to display
|
* Text to display
|
||||||
*/
|
*/
|
||||||
text: string;
|
text: string;
|
||||||
|
/**
|
||||||
|
* Index of the font sampler
|
||||||
|
*/
|
||||||
|
index?: number;
|
||||||
/**
|
/**
|
||||||
* Font settings
|
* Font settings
|
||||||
*/
|
*/
|
||||||
@@ -36,6 +39,7 @@ interface Props {
|
|||||||
let {
|
let {
|
||||||
font,
|
font,
|
||||||
text = $bindable(),
|
text = $bindable(),
|
||||||
|
index = 0,
|
||||||
...restProps
|
...restProps
|
||||||
}: Props = $props();
|
}: Props = $props();
|
||||||
|
|
||||||
@@ -51,23 +55,37 @@ function removeSample() {
|
|||||||
|
|
||||||
<div
|
<div
|
||||||
class="
|
class="
|
||||||
w-full h-full rounded-xl
|
w-full h-full rounded-2xl
|
||||||
bg-white border border-slate-200
|
flex flex-col
|
||||||
shadow-sm dark:border-slate-800 dark:bg-slate-950
|
backdrop-blur-md bg-white/80
|
||||||
|
border border-gray-300/50
|
||||||
|
shadow-[0_1px_3px_rgba(0,0,0,0.04)]
|
||||||
|
relative overflow-hidden
|
||||||
"
|
"
|
||||||
style:font-weight={fontWeight}
|
style:font-weight={fontWeight}
|
||||||
>
|
>
|
||||||
<div class="mx-3 p-1.5 pr-0 border-b border-slate-200 flex items-center justify-between">
|
<div class="px-6 py-3 border-b border-gray-200/60 flex items-center justify-between">
|
||||||
<span class="text-[0.5rem] font-mono uppercase tracking-widest text-slate-900">
|
<div class="flex items-center gap-2.5">
|
||||||
{font.name}
|
<span class="font-mono text-[9px] uppercase tracking-[0.25em] text-gray-500 font-medium">
|
||||||
</span>
|
typeface_{String(index).padStart(3, '0')}
|
||||||
<IconButton onclick={removeSample}>
|
</span>
|
||||||
|
<div class="w-px h-2.5 bg-gray-300/60"></div>
|
||||||
|
<span class="font-mono text-[10px] tracking-[0.15em] font-bold uppercase text-gray-900">
|
||||||
|
{font.name}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<IconButton
|
||||||
|
onclick={removeSample}
|
||||||
|
class="w-5 h-5 rounded-full hover:bg-transparent flex items-center justify-center transition-colors group translate-x-1/2 cursor-pointer"
|
||||||
|
>
|
||||||
{#snippet icon({ className })}
|
{#snippet icon({ className })}
|
||||||
<MinusIcon class={cn(className, 'stroke-red-500 group-hover:stroke-red-500')} />
|
<XIcon class={className} />
|
||||||
{/snippet}
|
{/snippet}
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</div>
|
</div>
|
||||||
<div class="p-6">
|
|
||||||
|
<div class="p-8 relative z-10">
|
||||||
<FontApplicator id={font.id} name={font.name}>
|
<FontApplicator id={font.id} name={font.name}>
|
||||||
<ContentEditable
|
<ContentEditable
|
||||||
bind:text={text}
|
bind:text={text}
|
||||||
@@ -78,4 +96,22 @@ function removeSample() {
|
|||||||
/>
|
/>
|
||||||
</FontApplicator>
|
</FontApplicator>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="px-6 py-2 border-t border-gray-200/40 w-full flex gap-4 bg-gray-50/30 mt-auto">
|
||||||
|
<span class="text-[8px] font-mono text-gray-400 uppercase tracking-wider ml-auto">
|
||||||
|
SZ:{fontSize}PX
|
||||||
|
</span>
|
||||||
|
<div class="w-px h-2.5 self-center bg-gray-300/40"></div>
|
||||||
|
<span class="text-[8px] font-mono text-gray-400 uppercase tracking-wider">
|
||||||
|
WGT:{fontWeight}
|
||||||
|
</span>
|
||||||
|
<div class="w-px h-2.5 self-center bg-gray-300/40"></div>
|
||||||
|
<span class="text-[8px] font-mono text-gray-400 uppercase tracking-wider">
|
||||||
|
LH:{lineHeight?.toFixed(2)}
|
||||||
|
</span>
|
||||||
|
<div class="w-px h-2.5 self-center bg-gray-300/40"></div>
|
||||||
|
<span class="text-[8px] font-mono text-gray-400 uppercase tracking-wider">
|
||||||
|
LTR:{letterSpacing}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,21 +1,18 @@
|
|||||||
|
<!--
|
||||||
|
Component: Page
|
||||||
|
Description: The main page component of the application.
|
||||||
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { appliedFontsManager } from '$entities/Font';
|
import { appliedFontsManager } from '$entities/Font';
|
||||||
import { displayedFontsStore } from '$features/DisplayFont';
|
import { displayedFontsStore } from '$features/DisplayFont';
|
||||||
import FontDisplay from '$features/DisplayFont/ui/FontDisplay/FontDisplay.svelte';
|
import FontDisplay from '$features/DisplayFont/ui/FontDisplay/FontDisplay.svelte';
|
||||||
import { controlManager } from '$features/SetupFont';
|
import { controlManager } from '$features/SetupFont';
|
||||||
import { cn } from '$shared/shadcn/utils/shadcn-utils';
|
import { cn } from '$shared/shadcn/utils/shadcn-utils';
|
||||||
|
import { Section } from '$shared/ui';
|
||||||
import ComparisonSlider from '$widgets/ComparisonSlider/ui/ComparisonSlider/ComparisonSlider.svelte';
|
import ComparisonSlider from '$widgets/ComparisonSlider/ui/ComparisonSlider/ComparisonSlider.svelte';
|
||||||
import { FontSearch } from '$widgets/FontSearch';
|
import { FontSearch } from '$widgets/FontSearch';
|
||||||
import { cubicOut } from 'svelte/easing';
|
import LineSquiggleIcon from '@lucide/svelte/icons/line-squiggle';
|
||||||
import { Spring } from 'svelte/motion';
|
import ScanEyeIcon from '@lucide/svelte/icons/scan-eye';
|
||||||
import type {
|
|
||||||
SlideParams,
|
|
||||||
TransitionConfig,
|
|
||||||
} from 'svelte/transition';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Page Component
|
|
||||||
*/
|
|
||||||
|
|
||||||
let searchContainer: HTMLElement;
|
let searchContainer: HTMLElement;
|
||||||
|
|
||||||
@@ -53,13 +50,33 @@ $effect(() => {
|
|||||||
</div>
|
</div>
|
||||||
{/key}
|
{/key}
|
||||||
|
|
||||||
<div class="my-6">
|
{#if displayedFontsStore.fonts.length > 1}
|
||||||
<ComparisonSlider />
|
<Section class="my-12 gap-8" index={1}>
|
||||||
</div>
|
{#snippet icon({ className })}
|
||||||
|
<ScanEyeIcon class={className} />
|
||||||
|
{/snippet}
|
||||||
|
{#snippet title({ className })}
|
||||||
|
<h1 class={className}>
|
||||||
|
Optical<br>Comparator
|
||||||
|
</h1>
|
||||||
|
{/snippet}
|
||||||
|
<ComparisonSlider />
|
||||||
|
</Section>
|
||||||
|
{/if}
|
||||||
|
|
||||||
<div class="will-change-tranform transition-transform content my-2">
|
{#if displayedFontsStore.hasAnyFonts}
|
||||||
<FontDisplay />
|
<Section class="my-12 gap-8" index={2}>
|
||||||
</div>
|
{#snippet icon({ className })}
|
||||||
|
<LineSquiggleIcon class={className} />
|
||||||
|
{/snippet}
|
||||||
|
{#snippet title({ className })}
|
||||||
|
<h2 class={className}>
|
||||||
|
Sample<br>Set
|
||||||
|
</h2>
|
||||||
|
{/snippet}
|
||||||
|
<FontDisplay />
|
||||||
|
</Section>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
|||||||
@@ -5,14 +5,20 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
interface Props {
|
interface Props {
|
||||||
/**
|
/**
|
||||||
* Visible text
|
* Visible text (bindable)
|
||||||
*/
|
*/
|
||||||
text: string;
|
text: string;
|
||||||
/**
|
/**
|
||||||
* Font settings
|
* Font size in pixels
|
||||||
*/
|
*/
|
||||||
fontSize?: number;
|
fontSize?: number;
|
||||||
|
/**
|
||||||
|
* Line height
|
||||||
|
*/
|
||||||
lineHeight?: number;
|
lineHeight?: number;
|
||||||
|
/**
|
||||||
|
* Letter spacing in pixels
|
||||||
|
*/
|
||||||
letterSpacing?: number;
|
letterSpacing?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -53,7 +59,7 @@ function handleInput(e: Event) {
|
|||||||
w-full min-h-[1.2em] outline-none transition-all duration-200
|
w-full min-h-[1.2em] outline-none transition-all duration-200
|
||||||
empty:before:content-[attr(data-placeholder)] empty:before:text-slate-400
|
empty:before:content-[attr(data-placeholder)] empty:before:text-slate-400
|
||||||
selection:bg-indigo-100 selection:text-indigo-900
|
selection:bg-indigo-100 selection:text-indigo-900
|
||||||
caret-indigo-500
|
caret-indigo-500 focus:outline-none
|
||||||
"
|
"
|
||||||
style:font-size="{fontSize}px"
|
style:font-size="{fontSize}px"
|
||||||
style:line-height={lineHeight}
|
style:line-height={lineHeight}
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ let { rotation = 'clockwise', icon, ...rest }: Props = $props();
|
|||||||
>
|
>
|
||||||
{@render icon({
|
{@render icon({
|
||||||
className: cn(
|
className: cn(
|
||||||
'size-4 transition-all duration-200 stroke-slate-600/50 group-hover:stroke-indigo-500 group-hover:scale-110 group-hover:stroke-3 group-active:scale-90 group-disabled:stroke-transparent',
|
'size-4 transition-all duration-200 stroke-[1.5] stroke-gray-500 group-hover:stroke-gray-900 group-hover:scale-110 group-hover:stroke-3 group-active:scale-90 group-disabled:stroke-transparent',
|
||||||
rotation === 'clockwise' ? 'group-active:rotate-6' : 'group-active:-rotate-6',
|
rotation === 'clockwise' ? 'group-active:rotate-6' : 'group-active:-rotate-6',
|
||||||
),
|
),
|
||||||
})}
|
})}
|
||||||
|
|||||||
70
src/shared/ui/Section/Section.svelte
Normal file
70
src/shared/ui/Section/Section.svelte
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
<!--
|
||||||
|
Component: Section
|
||||||
|
Provides a container for a page widget with snippets for a title
|
||||||
|
-->
|
||||||
|
<script lang="ts">
|
||||||
|
import { cn } from '$shared/shadcn/utils/shadcn-utils';
|
||||||
|
import type { Snippet } from 'svelte';
|
||||||
|
import { cubicOut } from 'svelte/easing';
|
||||||
|
import type { HTMLAttributes } from 'svelte/elements';
|
||||||
|
import {
|
||||||
|
type FlyParams,
|
||||||
|
fly,
|
||||||
|
} from 'svelte/transition';
|
||||||
|
|
||||||
|
interface Props extends Omit<HTMLAttributes<HTMLElement>, 'title'> {
|
||||||
|
/**
|
||||||
|
* Additional CSS classes to apply to the section container.
|
||||||
|
*/
|
||||||
|
class?: string;
|
||||||
|
/**
|
||||||
|
* Snippet for a title itself
|
||||||
|
*/
|
||||||
|
title?: Snippet<[{ className?: string }]>;
|
||||||
|
/**
|
||||||
|
* Snippet for a title icon
|
||||||
|
*/
|
||||||
|
icon?: Snippet<[{ className?: string }]>;
|
||||||
|
/**
|
||||||
|
* Index of the section
|
||||||
|
*/
|
||||||
|
index?: number;
|
||||||
|
/**
|
||||||
|
* Snippet for the section content
|
||||||
|
*/
|
||||||
|
children?: Snippet;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { class: className, title, icon, index, children }: Props = $props();
|
||||||
|
|
||||||
|
const flyParams: FlyParams = { y: 0, x: -50, duration: 300, easing: cubicOut, opacity: 0.2 };
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<section
|
||||||
|
class={cn(
|
||||||
|
'flex flex-col',
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
in:fly={flyParams}
|
||||||
|
out:fly={flyParams}
|
||||||
|
>
|
||||||
|
<div class="flex flex-col gap-2">
|
||||||
|
<div class="flex items-center gap-3 opacity-60">
|
||||||
|
{#if icon}
|
||||||
|
{@render icon({ className: 'size-4 stroke-gray-900 stroke-1' })}
|
||||||
|
<div class="w-px h-3 bg-gray-400/50"></div>
|
||||||
|
{/if}
|
||||||
|
{#if typeof index === 'number'}
|
||||||
|
<span class="font-mono text-[10px] uppercase tracking-[0.2em] text-gray-600">
|
||||||
|
Component_{String(index).padStart(3, '0')}
|
||||||
|
</span>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{#if title}
|
||||||
|
{@render title({ className: 'text-5xl md:text-6xl font-semibold tracking-tighter text-gray-900 leading-[0.9]' })}
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{@render children?.()}
|
||||||
|
</section>
|
||||||
@@ -11,6 +11,7 @@ import ContentEditable from './ContentEditable/ContentEditable.svelte';
|
|||||||
import ExpandableWrapper from './ExpandableWrapper/ExpandableWrapper.svelte';
|
import ExpandableWrapper from './ExpandableWrapper/ExpandableWrapper.svelte';
|
||||||
import IconButton from './IconButton/IconButton.svelte';
|
import IconButton from './IconButton/IconButton.svelte';
|
||||||
import SearchBar from './SearchBar/SearchBar.svelte';
|
import SearchBar from './SearchBar/SearchBar.svelte';
|
||||||
|
import Section from './Section/Section.svelte';
|
||||||
import VirtualList from './VirtualList/VirtualList.svelte';
|
import VirtualList from './VirtualList/VirtualList.svelte';
|
||||||
|
|
||||||
export {
|
export {
|
||||||
@@ -21,5 +22,6 @@ export {
|
|||||||
ExpandableWrapper,
|
ExpandableWrapper,
|
||||||
IconButton,
|
IconButton,
|
||||||
SearchBar,
|
SearchBar,
|
||||||
|
Section,
|
||||||
VirtualList,
|
VirtualList,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -16,9 +16,7 @@ import {
|
|||||||
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';
|
||||||
@@ -180,7 +178,7 @@ $effect(() => {
|
|||||||
aria-label="Font comparison slider"
|
aria-label="Font comparison slider"
|
||||||
onpointerdown={startDragging}
|
onpointerdown={startDragging}
|
||||||
class="
|
class="
|
||||||
group relative w-full py-16 px-0 sm:py-24 sm:px-0 overflow-hidden
|
group relative w-full py-16 px-24 sm:py-24 sm:px-24 overflow-hidden
|
||||||
rounded-[2.5rem]
|
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
|
backdrop-blur-lg bg-gradient-to-br from-gray-100/70 via-white/50 to-gray-100/60
|
||||||
@@ -190,7 +188,6 @@ $effect(() => {
|
|||||||
before:bg-gradient-to-br before:from-black/5 before:via-black/2 before:to-transparent
|
before:bg-gradient-to-br before:from-black/5 before:via-black/2 before:to-transparent
|
||||||
before:-z-10 before:blur-sm
|
before:-z-10 before:blur-sm
|
||||||
"
|
"
|
||||||
in:fly={{ y: 0, x: -50, duration: 300, easing: cubicOut, opacity: 0.2 }}
|
|
||||||
>
|
>
|
||||||
<!-- Text Rendering Container -->
|
<!-- Text Rendering Container -->
|
||||||
<div
|
<div
|
||||||
|
|||||||
Reference in New Issue
Block a user