Compare commits

...

8 Commits

Author SHA1 Message Date
Ilia Mashkov
ef48d9815c feat(Page): add Section wrappers to page widgets 2026-01-30 17:46:21 +03:00
Ilia Mashkov
818dfdb55e feat(Layout): increase bottom gap for TypographyMenu 2026-01-30 17:45:08 +03:00
Ilia Mashkov
42e1271647 feat(ContentEditable): add comments 2026-01-30 17:44:18 +03:00
Ilia Mashkov
8ef9226dd2 feat(IconButton): slightlyu change the style 2026-01-30 17:43:46 +03:00
Ilia Mashkov
f0c0a9de45 feat(ComparisonSlider): move in/out animation to Section component 2026-01-30 17:43:19 +03:00
Ilia Mashkov
730eba138d feat(FontSampler): refactor styles of FontSampler component 2026-01-30 17:42:06 +03:00
Ilia Mashkov
18f265974e chore: add import/export 2026-01-30 17:40:26 +03:00
Ilia Mashkov
705723b009 feat(Section): create a section wrapper for a page 2026-01-30 17:40:11 +03:00
9 changed files with 168 additions and 40 deletions

View File

@@ -41,7 +41,7 @@ let { children }: Props = $props();
<header></header>
<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>
<TypographyMenu />
{@render children?.()}

View File

@@ -9,10 +9,10 @@ import { displayedFontsStore } from '../../model';
import FontSampler from '../FontSampler/FontSampler.svelte';
</script>
<div class="grid gap-2 grid-cols-[repeat(auto-fit,minmax(500px,1fr))]">
{#each displayedFontsStore.fonts as font (font.id)}
<div class="grid gap-2 grid-cols-[repeat(auto-fit,minmax(500px,1fr))] will-change-tranform transition-transform content">
{#each displayedFontsStore.fonts as font, index (font.id)}
<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>
{/each}
</div>

View File

@@ -9,12 +9,11 @@ import {
selectedFontsStore,
} from '$entities/Font';
import { controlManager } from '$features/SetupFont';
import { cn } from '$shared/shadcn/utils/shadcn-utils';
import {
ContentEditable,
IconButton,
} from '$shared/ui';
import MinusIcon from '@lucide/svelte/icons/minus';
import XIcon from '@lucide/svelte/icons/x';
interface Props {
/**
@@ -25,6 +24,10 @@ interface Props {
* Text to display
*/
text: string;
/**
* Index of the font sampler
*/
index?: number;
/**
* Font settings
*/
@@ -36,6 +39,7 @@ interface Props {
let {
font,
text = $bindable(),
index = 0,
...restProps
}: Props = $props();
@@ -51,23 +55,37 @@ function removeSample() {
<div
class="
w-full h-full rounded-xl
bg-white border border-slate-200
shadow-sm dark:border-slate-800 dark:bg-slate-950
w-full h-full rounded-2xl
flex flex-col
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}
>
<div class="mx-3 p-1.5 pr-0 border-b border-slate-200 flex items-center justify-between">
<span class="text-[0.5rem] font-mono uppercase tracking-widest text-slate-900">
{font.name}
</span>
<IconButton onclick={removeSample}>
<div class="px-6 py-3 border-b border-gray-200/60 flex items-center justify-between">
<div class="flex items-center gap-2.5">
<span class="font-mono text-[9px] uppercase tracking-[0.25em] text-gray-500 font-medium">
typeface_{String(index).padStart(3, '0')}
</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 })}
<MinusIcon class={cn(className, 'stroke-red-500 group-hover:stroke-red-500')} />
<XIcon class={className} />
{/snippet}
</IconButton>
</div>
<div class="p-6">
<div class="p-8 relative z-10">
<FontApplicator id={font.id} name={font.name}>
<ContentEditable
bind:text={text}
@@ -78,4 +96,22 @@ function removeSample() {
/>
</FontApplicator>
</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>

View File

@@ -1,21 +1,18 @@
<!--
Component: Page
Description: The main page component of the application.
-->
<script lang="ts">
import { appliedFontsManager } from '$entities/Font';
import { displayedFontsStore } from '$features/DisplayFont';
import FontDisplay from '$features/DisplayFont/ui/FontDisplay/FontDisplay.svelte';
import { controlManager } from '$features/SetupFont';
import { cn } from '$shared/shadcn/utils/shadcn-utils';
import { Section } from '$shared/ui';
import ComparisonSlider from '$widgets/ComparisonSlider/ui/ComparisonSlider/ComparisonSlider.svelte';
import { FontSearch } from '$widgets/FontSearch';
import { cubicOut } from 'svelte/easing';
import { Spring } from 'svelte/motion';
import type {
SlideParams,
TransitionConfig,
} from 'svelte/transition';
/**
* Page Component
*/
import LineSquiggleIcon from '@lucide/svelte/icons/line-squiggle';
import ScanEyeIcon from '@lucide/svelte/icons/scan-eye';
let searchContainer: HTMLElement;
@@ -53,13 +50,33 @@ $effect(() => {
</div>
{/key}
<div class="my-6">
<ComparisonSlider />
</div>
{#if displayedFontsStore.fonts.length > 1}
<Section class="my-12 gap-8" index={1}>
{#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">
<FontDisplay />
</div>
{#if displayedFontsStore.hasAnyFonts}
<Section class="my-12 gap-8" index={2}>
{#snippet icon({ className })}
<LineSquiggleIcon class={className} />
{/snippet}
{#snippet title({ className })}
<h2 class={className}>
Sample<br>Set
</h2>
{/snippet}
<FontDisplay />
</Section>
{/if}
</div>
<style>

View File

@@ -5,14 +5,20 @@
<script lang="ts">
interface Props {
/**
* Visible text
* Visible text (bindable)
*/
text: string;
/**
* Font settings
* Font size in pixels
*/
fontSize?: number;
/**
* Line height
*/
lineHeight?: number;
/**
* Letter spacing in pixels
*/
letterSpacing?: number;
}
@@ -53,7 +59,7 @@ function handleInput(e: Event) {
w-full min-h-[1.2em] outline-none transition-all duration-200
empty:before:content-[attr(data-placeholder)] empty:before:text-slate-400
selection:bg-indigo-100 selection:text-indigo-900
caret-indigo-500
caret-indigo-500 focus:outline-none
"
style:font-size="{fontSize}px"
style:line-height={lineHeight}

View File

@@ -43,7 +43,7 @@ let { rotation = 'clockwise', icon, ...rest }: Props = $props();
>
{@render icon({
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',
),
})}

View 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>

View File

@@ -11,6 +11,7 @@ import ContentEditable from './ContentEditable/ContentEditable.svelte';
import ExpandableWrapper from './ExpandableWrapper/ExpandableWrapper.svelte';
import IconButton from './IconButton/IconButton.svelte';
import SearchBar from './SearchBar/SearchBar.svelte';
import Section from './Section/Section.svelte';
import VirtualList from './VirtualList/VirtualList.svelte';
export {
@@ -21,5 +22,6 @@ export {
ExpandableWrapper,
IconButton,
SearchBar,
Section,
VirtualList,
};

View File

@@ -16,9 +16,7 @@ import {
createTypographyControl,
} from '$shared/lib';
import type { LineData } from '$shared/lib';
import { cubicOut } from 'svelte/easing';
import { Spring } from 'svelte/motion';
import { fly } from 'svelte/transition';
import CharacterSlot from './components/CharacterSlot.svelte';
import ControlsWrapper from './components/ControlsWrapper.svelte';
import Labels from './components/Labels.svelte';
@@ -180,7 +178,7 @@ $effect(() => {
aria-label="Font comparison slider"
onpointerdown={startDragging}
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]
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
@@ -190,7 +188,6 @@ $effect(() => {
before:bg-gradient-to-br before:from-black/5 before:via-black/2 before:to-transparent
before:-z-10 before:blur-sm
"
in:fly={{ y: 0, x: -50, duration: 300, easing: cubicOut, opacity: 0.2 }}
>
<!-- Text Rendering Container -->
<div