-
+
-
+
-
+
{result && (
-
+
{t('clamp.slope')}: {result.slopeVw}vw
{t('clamp.intercept')}: {result.interceptRem}rem
-
+
)}
-
-
-
-
+
{result ? (
{t('clamp.invalid')}
)}
-
+
{result &&
}
diff --git a/apps/desktop-ui/src/components/css-generators/css-generators-layout.tsx b/apps/desktop-ui/src/components/css-generators/css-generators-layout.tsx
index fa82f85b..a80a6315 100644
--- a/apps/desktop-ui/src/components/css-generators/css-generators-layout.tsx
+++ b/apps/desktop-ui/src/components/css-generators/css-generators-layout.tsx
@@ -4,9 +4,7 @@ import React from 'react'
import { useTranslations } from 'next-intl'
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
import { IconPalette } from '@tabler/icons-react'
-import { ToolPageHeader } from '@/components/tools/tool-page-header'
-import { RevealItem } from '@/components/dashboard/dashboard-reveal'
-import { CATEGORY_ACCENT } from '@/components/dashboard/types'
+import { ToolShell } from '@/components/tools/tool-shell'
import { BoxShadowTab } from './box-shadow-tab'
import { BorderRadiusTab } from './border-radius-tab'
import { ClampTab } from './clamp-tab'
@@ -15,18 +13,7 @@ export function CssGeneratorsLayout() {
const t = useTranslations('CssGenerators')
return (
-
-
-
-
-
-
-
+
{t('tabs.boxShadow')}
@@ -43,6 +30,6 @@ export function CssGeneratorsLayout() {
-
+
)
}
diff --git a/apps/desktop-ui/src/components/css-gradient-builder/gradient-layout.tsx b/apps/desktop-ui/src/components/css-gradient-builder/gradient-layout.tsx
index 0b544e4b..2c316325 100644
--- a/apps/desktop-ui/src/components/css-gradient-builder/gradient-layout.tsx
+++ b/apps/desktop-ui/src/components/css-gradient-builder/gradient-layout.tsx
@@ -3,7 +3,6 @@
import { useState, useMemo, useCallback } from 'react';
import { useCopyToClipboard } from '@/hooks/use-copy-to-clipboard';
import { useTranslations } from 'next-intl';
-import { Card } from '@/components/ui/card';
import { Label } from '@/components/ui/label';
import { Input } from '@/components/ui/input';
import { Button } from '@/components/ui/button';
@@ -19,9 +18,7 @@ import { Check, Copy, Plus, Trash, Dices, ArrowLeftRight, Download, Wand2 } from
import { IconColorSwatch } from '@tabler/icons-react';
import { cn } from '@/lib/utils';
import { ScrollArea, ScrollBar } from '@/components/ui/scroll-area';
-import { ToolPageHeader } from '@/components/tools/tool-page-header';
-import { RevealItem } from '@/components/dashboard/dashboard-reveal';
-import { CATEGORY_ACCENT } from '@/components/dashboard/types';
+import { ToolShell } from '@/components/tools/tool-shell';
type GradientType = 'linear' | 'radial' | 'conic';
@@ -165,21 +162,18 @@ export function GradientLayout() {
};
return (
-
-
-
-
+
{/* Left Column: Preview + Settings */}
-
+
@@ -187,9 +181,9 @@ export function GradientLayout() {
className="w-full h-48 sm:h-72 rounded-xl border border-border shadow-inner transition-all duration-300 ease-out z-10"
style={{ background: cssValue }}
/>
-
+
-
+
)}
-
+
-
+
-
+
{/* Right Column: Stops & Presets */}
-
+
-
+
-
+
-
+
-
+
);
}
diff --git a/apps/desktop-ui/src/components/exif-viewer/exif-viewer-layout.tsx b/apps/desktop-ui/src/components/exif-viewer/exif-viewer-layout.tsx
index 07d8f35c..9511b994 100644
--- a/apps/desktop-ui/src/components/exif-viewer/exif-viewer-layout.tsx
+++ b/apps/desktop-ui/src/components/exif-viewer/exif-viewer-layout.tsx
@@ -8,9 +8,7 @@ import { toast } from 'sonner'
import { useCopyToClipboard } from '@/hooks/use-copy-to-clipboard'
import { Badge } from '@/components/ui/badge'
import { Button } from '@/components/ui/button'
-import { Card } from '@/components/ui/card'
import { Checkbox } from '@/components/ui/checkbox'
-import { Label } from '@/components/ui/label'
import { ScrollArea } from '@/components/ui/scroll-area'
import {
Collapsible,
@@ -34,9 +32,8 @@ import {
Wrench,
} from 'lucide-react'
import { IconPhotoSearch } from '@tabler/icons-react'
-import { ToolPageHeader } from '@/components/tools/tool-page-header'
-import { RevealItem } from '@/components/dashboard/dashboard-reveal'
-import { CATEGORY_ACCENT } from '@/components/dashboard/types'
+import { ToolShell } from '@/components/tools/tool-shell'
+import { IOPanel } from '@/components/tools/io-panel'
import {
decimalToDms,
formatBytes,
@@ -92,7 +89,7 @@ function MetadataCard({
}) {
if (entries.length === 0) return null
return (
-
+
{icon}
{title}
@@ -105,7 +102,7 @@ function MetadataCard({
))}
-
+
)
}
@@ -222,121 +219,113 @@ export function ExifViewerLayout() {
if (!image) {
return (
-
-
-
-
-
-
inputRef.current?.click()}
+
+ {
e.preventDefault()
setDragOver(true)
}}
onDragLeave={() => setDragOver(false)}
onDrop={onDrop}
- role="button"
- tabIndex={0}
- onKeyDown={(e) => {
- if (e.key === 'Enter' || e.key === ' ') inputRef.current?.click()
- }}
+ className={`flex-1 transition-colors ${dragOver ? 'border-primary bg-primary/5' : ''}`}
>
-
-
-
-
-
{t('dropTitle')}
-
{t('dropHint')}
-
-
- {t('formats')}
- {
- const file = e.target.files?.[0]
- if (file) void loadFile(file)
- e.target.value = ''
+ inputRef.current?.click()}
+ role="button"
+ tabIndex={0}
+ onKeyDown={(e) => {
+ if (e.key === 'Enter' || e.key === ' ') inputRef.current?.click()
}}
- />
-
-
+ >
+
+
+
+
+
{t('dropTitle')}
+
{t('dropHint')}
+
+
+ {t('formats')}
+ {
+ const file = e.target.files?.[0]
+ if (file) void loadFile(file)
+ e.target.value = ''
+ }}
+ />
+
+
+
)
}
return (
-
-
-
-
-
-
+
-
- {/* eslint-disable-next-line @next/next/no-img-element */}
-
{
- const el = e.currentTarget
- if (el.naturalWidth) setDimensions({ w: el.naturalWidth, h: el.naturalHeight })
- }}
- onError={(e) => {
- e.currentTarget.style.visibility = 'hidden'
- }}
- />
-
-
-
- {t('facts.name')}
- - {image.file.name}
-
-
-
- {t('facts.type')}
- - {image.file.type || '—'}
-
-
-
- {t('facts.dimensions')}
- - {dimensions ? `${dimensions.w} × ${dimensions.h}` : '—'}
-
-
-
- {t('facts.size')}
- - {formatBytes(image.file.size)}
-
-
-
- {gps && (
-
-
- {t('gpsWarning')}
-
- )}
-
+
+ {gps && (
+
+
+ {t('gpsWarning')}
+
+ )}
+
+ >
+ }
+ >
+
+ {/* eslint-disable-next-line @next/next/no-img-element */}
+

{
+ const el = e.currentTarget
+ if (el.naturalWidth) setDimensions({ w: el.naturalWidth, h: el.naturalHeight })
+ }}
+ onError={(e) => {
+ e.currentTarget.style.visibility = 'hidden'
+ }}
+ />
+
+
+
- {t('facts.name')}
+ - {image.file.name}
+
+
+
- {t('facts.type')}
+ - {image.file.type || '—'}
+
+
+
- {t('facts.dimensions')}
+ - {dimensions ? `${dimensions.w} × ${dimensions.h}` : '—'}
+
+
+
- {t('facts.size')}
+ - {formatBytes(image.file.size)}
+
+
-
+
{parsing ? (
-
{t('parsing')}
+
+ {t('parsing')}
+
) : hasMetadata && groups ? (
{gps && (
-
+
@@ -391,7 +380,7 @@ export function ExifViewerLayout() {
{t('openMap')}
-
+
)}
) : (
-
+
{t('noMetadata')}
-
+
)}
{tags && hasMetadata && (
-
+
)}
-
+
{t('remove.title')}
@@ -499,9 +488,9 @@ export function ExifViewerLayout() {
)}
-
+
-
+
)
}
diff --git a/apps/desktop-ui/src/components/favicon-generator/favicon-generator-layout.tsx b/apps/desktop-ui/src/components/favicon-generator/favicon-generator-layout.tsx
index 90a0e8f3..b6eb7286 100644
--- a/apps/desktop-ui/src/components/favicon-generator/favicon-generator-layout.tsx
+++ b/apps/desktop-ui/src/components/favicon-generator/favicon-generator-layout.tsx
@@ -7,7 +7,6 @@ import { saveAs } from 'file-saver'
import { toast } from 'sonner'
import { useCopyToClipboard } from '@/hooks/use-copy-to-clipboard'
import { Button } from '@/components/ui/button'
-import { Card } from '@/components/ui/card'
import { Input } from '@/components/ui/input'
import { Label } from '@/components/ui/label'
import { Slider } from '@/components/ui/slider'
@@ -15,9 +14,7 @@ import { ScrollArea } from '@/components/ui/scroll-area'
import { ToggleGroup, ToggleGroupItem } from '@/components/ui/toggle-group'
import { Check, Copy, Download, Package, Trash2, Upload } from 'lucide-react'
import { IconFavicon } from '@tabler/icons-react'
-import { ToolPageHeader } from '@/components/tools/tool-page-header'
-import { RevealItem } from '@/components/dashboard/dashboard-reveal'
-import { CATEGORY_ACCENT } from '@/components/dashboard/types'
+import { ToolShell } from '@/components/tools/tool-shell'
import {
ICON_SIZES,
ICO_SIZES,
@@ -196,19 +193,9 @@ export function FaviconGeneratorLayout() {
if (!source) {
return (
-
-
-
-
-
-
+ inputRef.current?.click()}
@@ -245,26 +232,15 @@ export function FaviconGeneratorLayout() {
e.target.value = ''
}}
/>
-
-
+
+
)
}
return (
-
-
-
-
-
-
+
-
+
{sourceName}
@@ -351,11 +327,11 @@ export function FaviconGeneratorLayout() {
{t('downloadZip')}
-
+
-
+
{t('previewLabel')}
@@ -382,9 +358,9 @@ export function FaviconGeneratorLayout() {
))}
-
+
-
+
site.webmanifest
{manifestSnippet}
-
+
-
+
{t('headSnippet')}
@@ -414,10 +390,10 @@ export function FaviconGeneratorLayout() {
{headSnippet}
-
+
-
+
)
}
diff --git a/apps/desktop-ui/src/components/image-compressor/image-compressor-layout.tsx b/apps/desktop-ui/src/components/image-compressor/image-compressor-layout.tsx
index 17ec3551..266e41e7 100644
--- a/apps/desktop-ui/src/components/image-compressor/image-compressor-layout.tsx
+++ b/apps/desktop-ui/src/components/image-compressor/image-compressor-layout.tsx
@@ -12,7 +12,6 @@ import {
Loader2,
} from 'lucide-react';
import { Button } from '@/components/ui/button';
-import { Card } from '@/components/ui/card';
import { Label } from '@/components/ui/label';
import { Slider } from '@/components/ui/slider';
import {
@@ -25,9 +24,8 @@ import {
import { IconArrowsDiagonalMinimize2 } from '@tabler/icons-react';
import { cn } from '@/lib/utils';
import { useDebouncedCallback } from 'use-debounce';
-import { ToolPageHeader } from '@/components/tools/tool-page-header';
-import { RevealItem } from '@/components/dashboard/dashboard-reveal';
-import { CATEGORY_ACCENT } from '@/components/dashboard/types';
+import { ToolShell } from '@/components/tools/tool-shell';
+import { ToolPanels, IOPanel } from '@/components/tools/io-panel';
const MAX_FILE_BYTES = 20 * 1024 * 1024;
const MAX_CANVAS_EDGE = 8192;
@@ -251,46 +249,43 @@ export function ImageCompressorLayout() {
? Math.round((1 - compressedBytes / originalBytes) * 1000) / 10
: null;
- return (
-
-
-
-
-
-
- {
- const file = e.target.files?.[0];
- if (file) loadFile(file);
- e.target.value = '';
- }}
- />
-
-
-
-
+ const toolbar = (
+
+
+ {
+ const file = e.target.files?.[0];
+ if (file) loadFile(file);
+ e.target.value = '';
+ }}
+ />
+
+
+ );
-
+ return (
+
+
@@ -344,7 +339,7 @@ export function ImageCompressorLayout() {
)}
)}
-
+
{isMobile && (
@@ -365,85 +360,89 @@ export function ImageCompressorLayout() {
)}
-
- {(!isMobile || mobileTab === 'original') &&
{
- e.preventDefault();
- setIsDragging(true);
- }}
- onDragLeave={() => setIsDragging(false)}
- >
-
-
-
-
-
- {sourceUrl && (
-
+
+ {(!isMobile || mobileTab === 'original') && (
+ {
+ e.preventDefault();
+ setIsDragging(true);
+ }}
+ onDragLeave={() => setIsDragging(false)}
+ className={cn(
+ 'min-h-[220px] flex-1 transition-colors',
+ isDragging && 'border-primary/50 bg-primary/5',
+ !sourceUrl && 'border-dashed'
)}
-
- !sourceUrl && fileInputRef.current?.click()}
+ label={
+ <>
+
+ {t('panels.original')}
+ >
+ }
+ actions={
+ sourceUrl ? (
+
+ ) : null
+ }
+ bodyClassName="flex flex-col"
>
- {sourceUrl ? (
- // eslint-disable-next-line @next/next/no-img-element
-

- ) : (
-
-
-
-
-
-
{t('dropHere')}
-
{t('placeholders.uploadImage')}
+
!sourceUrl && fileInputRef.current?.click()}
+ >
+ {sourceUrl ? (
+ // eslint-disable-next-line @next/next/no-img-element
+

+ ) : (
+
+
+
+
+
+
{t('dropHere')}
+
{t('placeholders.uploadImage')}
+
-
- )}
- {isDragging && (
-
-
-
-
{t('dropHere')}
+ )}
+ {isDragging && (
+
+ )}
+
+ {error && (
+
)}
-
- {error && (
-
- )}
- }
+
+ )}
- {(!isMobile || mobileTab === 'compressed') &&
-
-
-
-
-
+ {(!isMobile || mobileTab === 'compressed') && (
+
+ {processing ? : }
+ {t('download')}
+
+ }
+ bodyClassName="flex items-center justify-center p-4"
+ >
{processing && !compressedUrl && (
@@ -456,9 +455,9 @@ export function ImageCompressorLayout() {
) : !sourceUrl ? (
{t('emptyCompressed')}
) : null}
-
- }
-
-
+
+ )}
+
+
);
}
diff --git a/apps/desktop-ui/src/components/keycode-inspector/keycode-inspector-layout.tsx b/apps/desktop-ui/src/components/keycode-inspector/keycode-inspector-layout.tsx
index 1c192e84..e42f320f 100644
--- a/apps/desktop-ui/src/components/keycode-inspector/keycode-inspector-layout.tsx
+++ b/apps/desktop-ui/src/components/keycode-inspector/keycode-inspector-layout.tsx
@@ -5,13 +5,11 @@ import { useTranslations } from 'next-intl'
import { useCopyToClipboard } from '@/hooks/use-copy-to-clipboard'
import { Badge } from '@/components/ui/badge'
import { Button } from '@/components/ui/button'
-import { Card } from '@/components/ui/card'
import { Label } from '@/components/ui/label'
import { Copy, Keyboard, Trash2 } from 'lucide-react'
import { IconKeyboard } from '@tabler/icons-react'
-import { ToolPageHeader } from '@/components/tools/tool-page-header'
-import { RevealItem } from '@/components/dashboard/dashboard-reveal'
-import { CATEGORY_ACCENT } from '@/components/dashboard/types'
+import { ToolShell } from '@/components/tools/tool-shell'
+import { IOPanel } from '@/components/tools/io-panel'
import {
KEY_HISTORY_LIMIT,
isPreventable,
@@ -48,7 +46,7 @@ function FieldCard({
deprecatedLabel?: string
}) {
return (
-
+
{deprecated && (
@@ -61,7 +59,7 @@ function FieldCard({
)}
{value}
-
+
)
}
@@ -93,156 +91,137 @@ export function KeycodeInspectorLayout() {
}
return (
-
-
-
-
-
-
-
-
-
-
- {/* Capture area / hero */}
-
setFocused(true)}
- onBlur={() => setFocused(false)}
- className={`flex min-h-[220px] cursor-pointer flex-col items-center justify-center gap-3 rounded-xl border-2 border-dashed p-8 text-center outline-none transition-colors ${
- focused ? 'border-primary/60 bg-primary/5' : 'border-border/60 bg-muted/20 hover:border-border'
- }`}
- >
- {current ? (
- <>
-
- {displayKey(current.key, t('hero.spaceKey'))}
-
-
{t('hero.keyLabel')}
-
- {MODIFIERS.map((m) => (
-
- {m.label}
-
- ))}
- {current.repeat && {t('hero.repeat')}}
-
-
- >
- ) : (
- <>
-
-
{t('capture.prompt')}
-
- {focused ? t('capture.ready') : t('capture.clickToFocus')}
-
- >
- )}
-
-
- {/* Field cards */}
- {current && (
-
-
-
-
-
- {t(`location.${locationName(current.location)}`)}{' '}
- ({current.location})
-
- }
- />
-
+
+
+
+ {/* Capture area / hero */}
+
setFocused(true)}
+ onBlur={() => setFocused(false)}
+ className={`flex min-h-[220px] cursor-pointer flex-col items-center justify-center gap-3 rounded-xl border-2 border-dashed p-8 text-center outline-none transition-colors ${
+ focused ? 'border-primary/60 bg-primary/5' : 'border-border/60 bg-muted/20 hover:border-border'
+ }`}
+ >
+ {current ? (
+ <>
+
+ {displayKey(current.key, t('hero.spaceKey'))}
+
+
{t('hero.keyLabel')}
+
+ {MODIFIERS.map((m) => (
+
+ {m.label}
+
+ ))}
+ {current.repeat && {t('hero.repeat')}}
+
+
+ >
+ ) : (
+ <>
+
+
{t('capture.prompt')}
+
+ {focused ? t('capture.ready') : t('capture.clickToFocus')}
+
+ >
)}
- {/* History */}
-
-
-
-
-
-
- {history.length === 0 ? (
-
{t('history.empty')}
- ) : (
-
- {history.map((entry) => (
- - setCurrent(entry.event)}
- className="flex cursor-pointer items-center justify-between gap-2 px-4 py-2 text-sm transition-colors hover:bg-muted/50"
- >
-
-
- {displayKey(entry.event.key, t('hero.spaceKey'))}
-
- {entry.event.code}
-
-
- {entry.event.keyCode}
-
-
-
- ))}
-
- )}
+ {/* Field cards */}
+ {current && (
+
+
+
+
+
+ {t(`location.${locationName(current.location)}`)}{' '}
+ ({current.location})
+
+ }
+ />
-
+ )}
+
+ {/* History */}
+
+
+
+ }
+ >
+ {history.length === 0 ? (
+ {t('history.empty')}
+ ) : (
+
+ {history.map((entry) => (
+ - setCurrent(entry.event)}
+ className="flex cursor-pointer items-center justify-between gap-2 px-4 py-2 text-sm transition-colors hover:bg-muted/50"
+ >
+
+
+ {displayKey(entry.event.key, t('hero.spaceKey'))}
+
+ {entry.event.code}
+
+
+ {entry.event.keyCode}
+
+
+
+ ))}
+
+ )}
+
-
+
)
}
diff --git a/apps/desktop-ui/src/components/svg-optimizer/svg-optimizer-layout.tsx b/apps/desktop-ui/src/components/svg-optimizer/svg-optimizer-layout.tsx
index e1fd007f..75e3b95a 100644
--- a/apps/desktop-ui/src/components/svg-optimizer/svg-optimizer-layout.tsx
+++ b/apps/desktop-ui/src/components/svg-optimizer/svg-optimizer-layout.tsx
@@ -4,17 +4,14 @@ import { useCallback, useEffect, useMemo, useState } from 'react'
import { useTranslations } from 'next-intl'
import { useDebouncedCallback } from 'use-debounce'
import { useCopyToClipboard } from '@/hooks/use-copy-to-clipboard'
-import { Card } from '@/components/ui/card'
import { Label } from '@/components/ui/label'
import { Button } from '@/components/ui/button'
-import { Textarea } from '@/components/ui/textarea'
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert'
import { Check, Copy, Download, Trash2, Sparkles } from 'lucide-react'
import { IconFileTypeSvg } from '@tabler/icons-react'
import { cn } from '@/lib/utils'
-import { ToolPageHeader } from '@/components/tools/tool-page-header'
-import { RevealItem } from '@/components/dashboard/dashboard-reveal'
-import { CATEGORY_ACCENT } from '@/components/dashboard/types'
+import { ToolShell } from '@/components/tools/tool-shell'
+import { ToolPanels, IOPanel, ToolTextArea } from '@/components/tools/io-panel'
import { utf8ByteLength } from '@/lib/svg-optimize'
import { useSvgOptimizeWorker } from '@/hooks/use-svg-optimize-worker'
@@ -86,36 +83,27 @@ export function SvgOptimizerLayout() {
}
return (
-
-
-
-
-
-
-
-
-
-
+
+
+
{t('chars', { count: input.length, bytes: beforeBytes })}
+ }
+ >
+
+ setInput(e.target.value)}
+ placeholder={t('placeholder')}
+ autoComplete="off"
+ />
-