refactor(ascii): align layout and UX with Calculate blueprint

Rewrites all four ASCII tool components to share the same design
language and spatial structure as the Calculator & Grapher tool.

Layout
- New responsive 2/5–3/5 grid (was fixed 2+1 col); matches Calculate
- Left panel: text input card + font selector filling remaining height
- Right panel: preview as the dominant full-height element
- Mobile: tabbed Editor / Preview switcher (same pattern as Calculator)

TextInput
- Replace shadcn Textarea with native <textarea>
- Glass border pattern (border-border/40, focus:border-primary/50)
- Monospace font, consistent counter styling

FontSelector
- Replace Card + shadcn Tabs + Button + Input + Empty with native elements
- Glass panel (glass rounded-xl) matching Calculate panel style
- Custom tab strip mirrors Calculator mobile tab pattern
- Native search input with glass border
- Font list items: border-l-2 left accent for selected state,
  hover:bg-primary/8, rose heart for favorites
- Auto-scrolls selected item into view on external changes
- Simplified empty state to single italic line

FontPreview
- Replace Card + Button + Badge + ToggleGroup + Tooltip + Empty
- Glass panel with header row (label + font tag + action buttons)
- Controls row: native toggle buttons with primary/10 active state
- Terminal window: dark #06060e background, macOS-style chrome
  (rose/amber/emerald dots), font name watermark — the hero element
- PNG export captures entire terminal including chrome at 2x
- Inline skeleton loader with pulse animation replaces Skeleton import

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-01 07:46:21 +01:00
parent d161aeba72
commit 141ab1f4e3
4 changed files with 419 additions and 396 deletions

View File

@@ -2,7 +2,6 @@
import * as React from 'react';
import { cn } from '@/lib/utils';
import { Textarea } from '@/components/ui/textarea';
export interface TextInputProps {
value: string;
@@ -14,14 +13,17 @@ export interface TextInputProps {
export function TextInput({ value, onChange, placeholder, className }: TextInputProps) {
return (
<div className={cn('relative', className)}>
<Textarea
<textarea
value={value}
onChange={(e) => onChange(e.target.value)}
placeholder={placeholder || 'Type something...'}
className="h-32 resize-none"
placeholder={placeholder || 'Type something'}
rows={4}
maxLength={100}
className="w-full bg-transparent resize-none font-mono text-sm outline-none text-foreground placeholder:text-muted-foreground/35 border border-border/40 rounded-lg px-3 py-2.5 focus:border-primary/50 transition-colors"
spellCheck={false}
autoComplete="off"
/>
<div className="absolute bottom-2 right-2 text-xs text-muted-foreground">
<div className="absolute bottom-3 right-3 text-[10px] text-muted-foreground/35 font-mono pointer-events-none tabular-nums">
{value.length}/100
</div>
</div>