feature/responsive #22

Merged
ilia merged 49 commits from feature/responsive into main 2026-02-09 06:49:25 +00:00
6 changed files with 379 additions and 280 deletions
Showing only changes of commit 152be85e34 - Show all commits

View File

@@ -3,6 +3,10 @@ import {
fetchFontsByIds,
unifiedFontStore,
} from '$entities/Font';
import {
DEFAULT_TYPOGRAPHY_CONTROLS_DATA,
createTypographyControlManager,
} from '$features/SetupFont';
import { createPersistentStore } from '$shared/lib';
/**
@@ -30,6 +34,7 @@ class ComparisonStore {
#fontB = $state<UnifiedFont | undefined>();
#sampleText = $state('The quick brown fox jumps over the lazy dog');
#isRestoring = $state(true);
#typography = createTypographyControlManager(DEFAULT_TYPOGRAPHY_CONTROLS_DATA, 'glyphdiff:comparison:typography');
constructor() {
this.restoreFromStorage();
@@ -102,6 +107,9 @@ class ComparisonStore {
}
// --- Getters & Setters ---
get typography() {
return this.#typography;
}
get fontA() {
return this.#fontA;
@@ -149,6 +157,13 @@ class ComparisonStore {
this.restoreFromStorage();
}
}
resetAll() {
this.#fontA = undefined;
this.#fontB = undefined;
storage.clear();
this.#typography.reset();
}
}
export const comparisonStore = new ComparisonStore();

View File

@@ -14,14 +14,17 @@ import {
createCharacterComparison,
createTypographyControl,
} from '$shared/lib';
import type { LineData } from '$shared/lib';
import type {
LineData,
ResponsiveManager,
} from '$shared/lib';
import { Loader } from '$shared/ui';
import { comparisonStore } from '$widgets/ComparisonSlider/model';
import { getContext } from 'svelte';
import { Spring } from 'svelte/motion';
import { fade } from 'svelte/transition';
import CharacterSlot from './components/CharacterSlot.svelte';
import ControlsWrapper from './components/ControlsWrapper.svelte';
import Labels from './components/Labels.svelte';
import Controls from './components/Controls.svelte';
import SliderLine from './components/SliderLine.svelte';
// Pair of fonts to compare
@@ -30,31 +33,13 @@ const fontB = $derived(comparisonStore.fontB);
const isLoading = $derived(comparisonStore.isLoading || !comparisonStore.isReady);
let container: HTMLElement | undefined = $state();
let controlsWrapperElement = $state<HTMLDivElement | null>(null);
let measureCanvas: HTMLCanvasElement | undefined = $state();
let container = $state<HTMLElement>();
let typographyControls = $state<HTMLDivElement | null>(null);
let measureCanvas = $state<HTMLCanvasElement>();
let isDragging = $state(false);
const typography = $derived(comparisonStore.typography);
const weightControl = createTypographyControl({
min: 100,
max: 700,
step: 100,
value: 400,
});
const heightControl = createTypographyControl({
min: 1,
max: 2,
step: 0.05,
value: 1.2,
});
const sizeControl = createTypographyControl({
min: 1,
max: 112,
step: 1,
value: 64,
});
const responsive = getContext<ResponsiveManager>('responsive');
/**
* Encapsulated helper for text splitting, measuring, and character proximity calculations.
@@ -64,8 +49,8 @@ const charComparison = createCharacterComparison(
() => comparisonStore.text,
() => fontA,
() => fontB,
() => weightControl.value,
() => sizeControl.value,
() => typography.weight,
() => typography.renderedSize,
);
let lineElements = $state<(HTMLElement | undefined)[]>([]);
@@ -88,8 +73,8 @@ function handleMove(e: PointerEvent) {
function startDragging(e: PointerEvent) {
if (
e.target === controlsWrapperElement
|| controlsWrapperElement?.contains(e.target as Node)
e.target === typographyControls
|| typographyControls?.contains(e.target as Node)
) {
e.stopPropagation();
return;
@@ -99,6 +84,30 @@ function startDragging(e: PointerEvent) {
handleMove(e);
}
/**
* Sets the multiplier for slider font size based on the current responsive state
*/
$effect(() => {
if (!responsive) {
return;
}
switch (true) {
case responsive.isMobile:
typography.multiplier = 0.5;
break;
case responsive.isTablet:
typography.multiplier = 0.75;
break;
case responsive.isDesktop:
typography.multiplier = 1;
break;
default:
typography.multiplier = 1;
break;
}
});
$effect(() => {
if (isDragging) {
window.addEventListener('pointermove', handleMove);
@@ -115,9 +124,9 @@ $effect(() => {
$effect(() => {
// React on text and typography settings changes
const _text = comparisonStore.text;
const _weight = weightControl.value;
const _size = sizeControl.value;
const _height = heightControl.value;
const _weight = typography.weight;
const _size = typography.renderedSize;
const _height = typography.height;
if (container && measureCanvas && fontA && fontB) {
// Using rAF to ensure DOM is ready/stabilized
@@ -143,8 +152,8 @@ $effect(() => {
<div
bind:this={lineElements[index]}
class="relative flex w-full justify-center items-center whitespace-nowrap"
style:height={`${heightControl.value}em`}
style:line-height={`${heightControl.value}em`}
style:height={`${typography.height}em`}
style:line-height={`${typography.height}em`}
>
{#each line.text.split('') as char, charIndex}
{@const { proximity, isPast } = charComparison.getCharState(charIndex, sliderPos, lineElements[index], container)}
@@ -158,8 +167,8 @@ $effect(() => {
{char}
{proximity}
{isPast}
weight={weightControl.value}
size={sizeControl.value}
weight={typography.weight}
size={typography.renderedSize}
fontAName={fontA.name}
fontBName={fontB.name}
/>
@@ -182,12 +191,12 @@ $effect(() => {
class="
group relative w-full py-8 px-4 sm:py-12 sm:px-8 md:py-16 md:px-12 lg:py-20 lg:px-24 overflow-hidden
rounded-xl sm:rounded-2xl md:rounded-[2.5rem]
select-none touch-none cursor-ew-resize min-h-[300px] sm:min-h-[400px] md:min-h-[500px] flex flex-col justify-center
backdrop-blur-lg bg-gradient-to-br from-gray-100/70 via-white/50 to-gray-100/60
select-none touch-none cursor-ew-resize min-h-72 sm:min-h-96 flex flex-col justify-center
backdrop-blur-lg bg-linear-to-br from-gray-100/70 via-white/50 to-gray-100/60
border border-gray-300/40
shadow-[inset_0_4px_12px_0_rgba(0,0,0,0.12),inset_0_2px_4px_0_rgba(0,0,0,0.08),0_1px_2px_0_rgba(255,255,255,0.8)]
before:absolute before:inset-0 before:rounded-xl sm:before:rounded-2xl md:before:rounded-[2.5rem] before:p-[1px]
before:bg-gradient-to-br before:from-black/5 before:via-black/2 before:to-transparent
before:absolute before:inset-0 before:rounded-xl sm:before:rounded-2xl md:before:rounded-[2.5rem] before:p-px
before:bg-linear-to-br before:from-black/5 before:via-black/2 before:to-transparent
before:-z-10 before:blur-sm
"
>
@@ -209,7 +218,7 @@ $effect(() => {
{#each charComparison.lines as line, lineIndex}
<div
class="relative w-full whitespace-nowrap"
style:height={`${heightControl.value}em`}
style:height={`${typography.height}em`}
style:display="flex"
style:align-items="center"
style:justify-content="center"
@@ -222,19 +231,6 @@ $effect(() => {
<SliderLine {sliderPos} {isDragging} />
{/if}
</div>
{#if fontA && fontB && !isLoading}
<Labels {fontA} {fontB} {sliderPos} weight={weightControl.value} />
<!-- Since there're slider controls inside we put them outside the main one -->
<ControlsWrapper
bind:wrapper={controlsWrapperElement}
{sliderPos}
{isDragging}
bind:text={comparisonStore.text}
containerWidth={container?.clientWidth}
{weightControl}
{sizeControl}
{heightControl}
/>
{/if}
<Controls {sliderPos} {isDragging} {typographyControls} {container} />
</div>

View File

@@ -0,0 +1,70 @@
<script lang="ts">
import type { ResponsiveManager } from '$shared/lib';
import {
Drawer,
IconButton,
} from '$shared/ui';
import SlidersIcon from '@lucide/svelte/icons/sliders-vertical';
import { getContext } from 'svelte';
import { comparisonStore } from '../../../model';
import SelectComparedFonts from './SelectComparedFonts.svelte';
import TypographyControls from './TypographyControls.svelte';
interface Props {
sliderPos: number;
isDragging: boolean;
typographyControls?: HTMLDivElement | null;
container: HTMLElement;
}
let { sliderPos, isDragging, typographyControls = $bindable<HTMLDivElement | null>(null), container }: Props = $props();
const fontA = $derived(comparisonStore.fontA);
const fontB = $derived(comparisonStore.fontB);
const isLoading = $derived(comparisonStore.isLoading || !comparisonStore.isReady);
const responsive = getContext<ResponsiveManager>('responsive');
</script>
{#if responsive.isMobile}
<Drawer>
{#snippet trigger({ isOpen, onClick })}
<IconButton class="absolute right-3 top-3" onclick={onClick}>
{#snippet icon({ className })}
<SlidersIcon class={className} />
{/snippet}
</IconButton>
{/snippet}
{#snippet content({ isOpen })}
<div class="px-2 py-4">
<SelectComparedFonts {sliderPos} />
</div>
<TypographyControls
{sliderPos}
{isDragging}
isActive={isOpen}
bind:wrapper={typographyControls}
containerWidth={container?.clientWidth}
staticPosition
/>
{/snippet}
</Drawer>
{:else}
{#if !isLoading}
<div class="absolute top-3 sm:top-6 left-3 sm:left-6">
<TypographyControls
{sliderPos}
{isDragging}
bind:wrapper={typographyControls}
containerWidth={container?.clientWidth}
/>
</div>
{/if}
{#if !isLoading}
<div class="absolute bottom-3 sm:bottom-6 md:bottom-8 inset-x-3 sm:inset-x-6 md:inset-x-12">
<SelectComparedFonts {sliderPos} />
</div>
{/if}
{/if}

View File

@@ -1,177 +0,0 @@
<!--
Component: ControlsWrapper
Wrapper for the controls of the slider.
- Input to change the text
- Three combo controls with inputs and sliders for font-weight, font-size, and line-height
-->
<script lang="ts">
import type { TypographyControl } from '$shared/lib';
import { Input } from '$shared/shadcn/ui/input';
import { cn } from '$shared/shadcn/utils/shadcn-utils';
import { ComboControlV2 } from '$shared/ui';
import { ExpandableWrapper } from '$shared/ui';
import AArrowUP from '@lucide/svelte/icons/a-arrow-up';
import { Spring } from 'svelte/motion';
import { fade } from 'svelte/transition';
interface Props {
/**
* Ref
*/
wrapper?: HTMLDivElement | null;
/**
* Slider position
*/
sliderPos: number;
/**
* Whether slider is being dragged
*/
isDragging: boolean;
/**
* Text to display
*/
text: string;
/**
* Container width
*/
containerWidth: number;
/**
* Weight control
*/
weightControl: TypographyControl;
/**
* Size control
*/
sizeControl: TypographyControl;
/**
* Height control
*/
heightControl: TypographyControl;
}
let {
sliderPos,
isDragging,
wrapper = $bindable(null),
text = $bindable(),
containerWidth = 0,
weightControl,
sizeControl,
heightControl,
}: Props = $props();
let panelWidth = $derived(wrapper?.clientWidth ?? 0);
const margin = 24;
let side = $state<'left' | 'right'>('left');
// Unified active state for the entire wrapper
let isActive = $state(false);
let timeoutId = $state<ReturnType<typeof setTimeout> | null>(null);
const xSpring = new Spring(0, {
stiffness: 0.14, // Lower is slower
damping: 0.5, // Settle
});
const rotateSpring = new Spring(0, {
stiffness: 0.12,
damping: 0.55,
});
function handleInputFocus() {
isActive = true;
}
// Movement Logic
$effect(() => {
if (containerWidth === 0 || panelWidth === 0) return;
const sliderX = (sliderPos / 100) * containerWidth;
const buffer = 40;
const leftTrigger = margin + panelWidth + buffer;
const rightTrigger = containerWidth - (margin + panelWidth + buffer);
if (side === 'left' && sliderX < leftTrigger) {
side = 'right';
} else if (side === 'right' && sliderX > rightTrigger) {
side = 'left';
}
});
$effect(() => {
const targetX = side === 'right' ? containerWidth - panelWidth - margin * 2 : 0;
if (containerWidth > 0 && panelWidth > 0) {
// On side change set the position and the rotation
xSpring.target = targetX;
rotateSpring.target = side === 'right' ? 3.5 : -3.5;
timeoutId = setTimeout(() => {
rotateSpring.target = 0;
}, 600);
}
return () => {
if (timeoutId) {
clearTimeout(timeoutId);
}
};
});
</script>
<div
class="absolute top-6 left-6 z-50 will-change-transform"
style:transform="
translateX({xSpring.current}px)
rotateZ({rotateSpring.current}deg)
"
in:fade={{ duration: 300, delay: 300 }}
out:fade={{ duration: 300, delay: 300 }}
>
<ExpandableWrapper
bind:element={wrapper}
bind:expanded={isActive}
disabled={isDragging}
aria-label="Font controls"
rotation={side === 'right' ? 'counterclockwise' : 'clockwise'}
class={cn(
'transition-opacity flex items-top gap-1.5',
panelWidth === 0 ? 'opacity-0' : 'opacity-100',
)}
>
{#snippet badge()}
<div
class={cn(
'animate-nudge relative transition-all',
side === 'left' ? 'order-2' : 'order-0',
isActive ? 'opacity-0' : 'opacity-100',
isDragging && 'opacity-80 grayscale-[0.2]',
)}
>
<AArrowUP class={cn('size-3', 'stroke-indigo-600')} />
</div>
{/snippet}
{#snippet visibleContent()}
<div class="relative px-2 py-1">
<Input
bind:value={text}
disabled={isDragging}
onfocusin={handleInputFocus}
class={cn(
isActive
? 'h-7 sm:h-8 text-[11px] sm:text-xs text-center bg-white/40 border-none rounded-lg focus-visible:ring-indigo-500/50 text-slate-900'
: 'bg-transparent shadow-none border-none p-0 h-auto text-sm sm:text-base font-medium focus-visible:ring-0 text-slate-900/50',
' placeholder:text-slate-400 selection:bg-indigo-100 flex-1 transition-all duration-350 w-44 sm:w-56',
)}
placeholder="The quick brown fox..."
/>
</div>
{/snippet}
{#snippet hiddenContent()}
<div class="flex flex-col sm:flex-row justify-between items-center-safe gap-2 sm:gap-0">
<ComboControlV2 control={weightControl} />
<ComboControlV2 control={sizeControl} />
<ComboControlV2 control={heightControl} />
</div>
{/snippet}
</ExpandableWrapper>
</div>

View File

@@ -1,13 +1,14 @@
<!--
Component: Labels
Displays labels for font selection in the comparison slider.
Component: SelectComparedFonts
Displays selects that change the compared fonts
-->
<script lang="ts" generics="T extends UnifiedFont">
<script lang="ts">
import {
FontVirtualList,
type UnifiedFont,
unifiedFontStore,
} from '$entities/Font';
import { getFontUrl } from '$entities/Font/lib';
import FontApplicator from '$entities/Font/ui/FontApplicator/FontApplicator.svelte';
import {
Content as SelectContent,
@@ -19,23 +20,19 @@ import { cn } from '$shared/shadcn/utils/shadcn-utils';
import { comparisonStore } from '$widgets/ComparisonSlider/model';
import { fade } from 'svelte/transition';
interface Props<T> {
/**
* First font to compare
*/
fontA: T;
/**
* Second font to compare
*/
fontB: T;
interface Props {
/**
* Position of the slider
*/
sliderPos: number;
weight: number;
}
let { fontA, fontB, sliderPos, weight }: Props<T> = $props();
let { sliderPos }: Props = $props();
const typography = $derived(comparisonStore.typography);
const fontA = $derived(comparisonStore.fontA);
const fontAUrl = $derived(fontA && getFontUrl(fontA, typography.weight));
const fontB = $derived(comparisonStore.fontB);
const fontBUrl = $derived(fontB && getFontUrl(fontB, typography.weight));
const fontList = $derived(unifiedFontStore.fonts);
@@ -51,11 +48,10 @@ function selectFontB(font: UnifiedFont) {
</script>
{#snippet fontSelector(
name: string,
id: string,
url: string,
font: UnifiedFont,
fonts: UnifiedFont[],
selectFont: (font: UnifiedFont) => void,
url: string,
onSelect: (f: UnifiedFont) => void,
align: 'start' | 'end',
)}
<div
@@ -74,15 +70,15 @@ function selectFontB(font: UnifiedFont) {
)}
>
<div class="text-left flex-1 min-w-0">
<FontApplicator {name} {id} {url}>
{name}
<FontApplicator name={font.name} id={font.id} {url}>
{font.name}
</FontApplicator>
</div>
</SelectTrigger>
<SelectContent
class={cn(
'bg-white/95 backdrop-blur-xl border border-gray-300/50 shadow-xl',
'w-44 sm:w-52 max-h-[240px] sm:max-h-[280px] overflow-hidden rounded-lg',
'w-44 sm:w-52 max-h-60 sm:max-h-64 overflow-hidden rounded-lg',
)}
side="top"
{align}
@@ -90,16 +86,20 @@ function selectFontB(font: UnifiedFont) {
size="small"
>
<div class="p-1 sm:p-1.5">
<FontVirtualList items={fonts} {weight}>
{#snippet children({ item: font })}
{@const handleClick = () => selectFont(font)}
<FontVirtualList items={fonts} weight={typography.weight}>
{#snippet children({ item: fontListItem })}
{@const handleClick = () => onSelect(fontListItem)}
<SelectItem
value={font.id}
class="data-[highlighted]:bg-gray-100 font-mono text-[10px] sm:text-[11px] px-2 sm:px-3 py-2 sm:py-2.5 rounded-md cursor-pointer transition-colors"
value={fontListItem.id}
class="data-highlighted:bg-gray-100 font-mono text-[10px] sm:text-[11px] px-2 sm:px-3 py-2 sm:py-2.5 rounded-md cursor-pointer transition-colors"
onclick={handleClick}
>
<FontApplicator name={font.name} id={font.id} url={font.styles.regular!}>
{font.name}
<FontApplicator
name={fontListItem.name}
id={fontListItem.id}
url={getFontUrl(fontListItem, typography.weight) ?? ''}
>
{fontListItem.name}
</FontApplicator>
</SelectItem>
{/snippet}
@@ -110,7 +110,7 @@ function selectFontB(font: UnifiedFont) {
</div>
{/snippet}
<div class="absolute bottom-4 sm:bottom-6 md:bottom-8 inset-x-4 sm:inset-x-6 md:inset-x-12 flex justify-between items-end pointer-events-none z-20">
<div class="flex justify-between items-end pointer-events-none z-20">
<div
class="flex flex-col gap-1.5 sm:gap-2 transition-all duration-500 items-start"
style:opacity={sliderPos < 20 ? 0 : 1}
@@ -123,14 +123,9 @@ function selectFontB(font: UnifiedFont) {
ch_01
</span>
</div>
{@render fontSelector(
fontB.name,
fontB.id,
fontB.styles.regular!,
fontList,
selectFontB,
'start',
)}
{#if fontB && fontBUrl}
{@render fontSelector(fontB, fontList, fontBUrl, selectFontB, 'start')}
{/if}
</div>
<div
@@ -145,13 +140,8 @@ function selectFontB(font: UnifiedFont) {
<div class="w-px h-2 sm:h-2.5 bg-gray-300/60"></div>
<div class="w-1.5 h-1.5 rounded-full bg-gray-900 shadow-[0_0_6px_rgba(0,0,0,0.4)]"></div>
</div>
{@render fontSelector(
fontA.name,
fontA.id,
fontA.styles.regular!,
fontList,
selectFontA,
'end',
)}
{#if fontA && fontAUrl}
{@render fontSelector(fontA, fontList, fontAUrl, selectFontA, 'end')}
{/if}
</div>
</div>

View File

@@ -0,0 +1,205 @@
<!--
Component: TypographyControls
Wrapper for the controls of the slider.
- Input to change the text
- Three combo controls with inputs and sliders for font-weight, font-size, and line-height
-->
<script lang="ts">
import { cn } from '$shared/shadcn/utils/shadcn-utils';
import {
ComboControlV2,
ExpandableWrapper,
Input,
} from '$shared/ui';
import { comparisonStore } from '$widgets/ComparisonSlider/model';
import AArrowUP from '@lucide/svelte/icons/a-arrow-up';
import { Spring } from 'svelte/motion';
import { fade } from 'svelte/transition';
interface Props {
/**
* Ref
*/
wrapper?: HTMLDivElement | null;
/**
* Slider position
*/
sliderPos: number;
/**
* Whether slider is being dragged
*/
isDragging: boolean;
/** */
isActive?: boolean;
/**
* Container width
*/
containerWidth: number;
/**
* Reduced animations flag
*/
staticPosition?: boolean;
}
let {
sliderPos,
isDragging,
isActive = $bindable(false),
wrapper = $bindable(null),
containerWidth = 0,
staticPosition = false,
}: Props = $props();
const typography = $derived(comparisonStore.typography);
const panelWidth = $derived(wrapper?.clientWidth ?? 0);
const margin = 24;
let side = $state<'left' | 'right'>('left');
// Unified active state for the entire wrapper
let timeoutId = $state<ReturnType<typeof setTimeout> | null>(null);
const xSpring = new Spring(0, {
stiffness: 0.14, // Lower is slower
damping: 0.5, // Settle
});
const rotateSpring = new Spring(0, {
stiffness: 0.12,
damping: 0.55,
});
function handleInputFocus() {
isActive = true;
}
// Movement Logic
$effect(() => {
if (containerWidth === 0 || panelWidth === 0 || staticPosition) return;
const sliderX = (sliderPos / 100) * containerWidth;
const buffer = 40;
const leftTrigger = margin + panelWidth + buffer;
const rightTrigger = containerWidth - (margin + panelWidth + buffer);
if (side === 'left' && sliderX < leftTrigger) {
side = 'right';
} else if (side === 'right' && sliderX > rightTrigger) {
side = 'left';
}
});
$effect(() => {
const targetX = side === 'right' ? containerWidth - panelWidth - margin * 2 : 0;
if (containerWidth > 0 && panelWidth > 0) {
// On side change set the position and the rotation
xSpring.target = targetX;
rotateSpring.target = side === 'right' ? 3.5 : -3.5;
timeoutId = setTimeout(() => {
rotateSpring.target = 0;
}, 600);
}
return () => {
if (timeoutId) {
clearTimeout(timeoutId);
}
};
});
</script>
<div
class="z-50 will-change-transform"
style:transform="
translateX({xSpring.current}px)
rotateZ({rotateSpring.current}deg)
"
in:fade={{ duration: 300, delay: 300 }}
out:fade={{ duration: 300, delay: 300 }}
>
{#if staticPosition}
<div class="flex flex-col gap-6 px-2 py-4">
<Input
class="p-6"
bind:value={comparisonStore.text}
disabled={isDragging}
onfocusin={handleInputFocus}
placeholder="The quick brown fox..."
/>
{#if typography.weightControl && typography.sizeControl && typography.heightControl}
<div class="flex flex-col justify-between items-center-safe gap-6">
<ComboControlV2 control={typography.weightControl} orientation="horizontal" />
<ComboControlV2 control={typography.sizeControl} orientation="horizontal" />
<ComboControlV2 control={typography.heightControl} orientation="horizontal" />
</div>
{/if}
</div>
{:else}
<ExpandableWrapper
bind:element={wrapper}
bind:expanded={isActive}
disabled={isDragging}
aria-label="Font controls"
rotation={side === 'right' ? 'counterclockwise' : 'clockwise'}
class={cn(
'transition-opacity flex items-top gap-1.5',
panelWidth === 0 ? 'opacity-0' : 'opacity-100',
)}
containerClassName={cn(!isActive && 'p-2 sm:p-0')}
>
{#snippet badge()}
<div
class={cn(
'animate-nudge relative transition-all',
side === 'left' ? 'order-2' : 'order-0',
isActive ? 'opacity-0' : 'opacity-100',
isDragging && 'opacity-80 grayscale-[0.2]',
)}
>
<AArrowUP class={cn('size-3', 'stroke-indigo-600')} />
</div>
{/snippet}
{#snippet visibleContent()}
<div class="relative">
<!--
<Input
bind:value={comparisonStore.text}
disabled={isDragging}
onfocusin={handleInputFocus}
class={cn(
isActive
? 'h-7 sm:h-8 text-[11px] sm:text-xs text-center bg-white/40 border-none rounded-lg focus-visible:ring-indigo-500/50 text-slate-900'
: 'bg-transparent shadow-none border-none p-0 h-auto text-sm sm:text-base font-medium focus-visible:ring-0 text-slate-900/50',
' placeholder:text-slate-400 selection:bg-indigo-100 flex-1 transition-all duration-350 w-44 sm:w-56',
)}
placeholder="The quick brown fox..."
/>
-->
<Input
class={cn(
'pl-1 sm:pl-3 pr-1 sm:pr-3',
'h-6 sm:h-8 md:h-10',
'rounded-lg',
isActive
? 'h-7 sm:h-8 text-[0.825rem]'
: 'bg-transparent shadow-none border-none p-0 h-auto text-sm sm:text-base font-medium focus-visible:ring-0 text-slate-900/50',
)}
bind:value={comparisonStore.text}
disabled={isDragging}
onfocusin={handleInputFocus}
placeholder="The quick brown fox..."
/>
</div>
{/snippet}
{#snippet hiddenContent()}
{#if typography.weightControl && typography.sizeControl && typography.heightControl}
<div class="flex flex-row justify-between items-center-safe gap-2 sm:gap-0">
<ComboControlV2 control={typography.weightControl} />
<ComboControlV2 control={typography.sizeControl} />
<ComboControlV2 control={typography.heightControl} />
</div>
{/if}
{/snippet}
</ExpandableWrapper>
{/if}
</div>