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>
|
||||
|
||||
<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?.()}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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',
|
||||
),
|
||||
})}
|
||||
|
||||
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 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,
|
||||
};
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user