fix: remove all unnecessary animations from all pages
- Remove dead code animations (animate-in, fade-in, slide-in, zoom-in, shake) from tailwindcss-animate plugin (not installed) - Remove decorative transition effects (transition-all, transition-colors) from all page components - Remove scale effects (active:scale-95) from buttons - Remove bounce animations (animate-bounce) from icons - Keep functional animate-spin on loading spinners as they provide essential loading feedback Affected pages: Dashboard, Timestamp, Jwt, JsonTools, Base64Converter, HtmlToMarkdown, MarkdownToHtml, QrCode, TextStatistics
This commit is contained in:
@@ -57,7 +57,7 @@ export default function Base64ConverterSection({ mode }: Base64ConverterSectionP
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full flex flex-col space-y-4 animate-in fade-in duration-300">
|
<div className="w-full flex flex-col space-y-4">
|
||||||
<div className="flex h-11 items-center px-1.5 bg-secondary/40 rounded-xl border border-border/60 w-fit">
|
<div className="flex h-11 items-center px-1.5 bg-secondary/40 rounded-xl border border-border/60 w-fit">
|
||||||
<SwitchButtonGroup
|
<SwitchButtonGroup
|
||||||
value={direction}
|
value={direction}
|
||||||
@@ -86,7 +86,7 @@ export default function Base64ConverterSection({ mode }: Base64ConverterSectionP
|
|||||||
}}
|
}}
|
||||||
onClick={() => fileInputRef.current?.click()}
|
onClick={() => fileInputRef.current?.click()}
|
||||||
className={cn(
|
className={cn(
|
||||||
'flex flex-col items-center justify-center min-h-[190px] border-2 border-dashed rounded-2xl p-8 cursor-pointer transition-all duration-300',
|
'flex flex-col items-center justify-center min-h-[190px] border-2 border-dashed rounded-2xl p-8 cursor-pointer',
|
||||||
isDragging
|
isDragging
|
||||||
? 'border-primary bg-primary/10'
|
? 'border-primary bg-primary/10'
|
||||||
: info
|
: info
|
||||||
@@ -107,7 +107,7 @@ export default function Base64ConverterSection({ mode }: Base64ConverterSectionP
|
|||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
<div className="w-9 h-9 border-3 border-primary/20 border-t-primary rounded-full animate-spin" />
|
<div className="w-9 h-9 border-3 border-primary/20 border-t-primary rounded-full animate-spin" />
|
||||||
) : info ? (
|
) : info ? (
|
||||||
<div className="flex flex-col items-center gap-1.5 text-center w-full animate-in fade-in duration-200">
|
<div className="flex flex-col items-center gap-1.5 text-center w-full">
|
||||||
{mode === 'image' && result && (
|
{mode === 'image' && result && (
|
||||||
<div className="relative p-1 border border-border bg-background rounded-lg shadow-sm mb-1 max-w-[180px] overflow-hidden">
|
<div className="relative p-1 border border-border bg-background rounded-lg shadow-sm mb-1 max-w-[180px] overflow-hidden">
|
||||||
<img
|
<img
|
||||||
@@ -117,9 +117,7 @@ export default function Base64ConverterSection({ mode }: Base64ConverterSectionP
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<Upload
|
<Upload className={cn('w-8 h-8 text-primary')} />
|
||||||
className={cn('w-8 h-8 text-primary', mode === 'file' && 'animate-bounce')}
|
|
||||||
/>
|
|
||||||
<span className="text-sm font-bold text-foreground/90 max-w-[280px] truncate">
|
<span className="text-sm font-bold text-foreground/90 max-w-[280px] truncate">
|
||||||
{info.name}
|
{info.name}
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ export default function FileMode() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full flex flex-col space-y-4 animate-in fade-in duration-300">
|
<div className="w-full flex flex-col space-y-4">
|
||||||
<div className="flex h-11 items-center px-1.5 bg-secondary/40 rounded-xl border border-border/60 w-fit">
|
<div className="flex h-11 items-center px-1.5 bg-secondary/40 rounded-xl border border-border/60 w-fit">
|
||||||
<SwitchButtonGroup
|
<SwitchButtonGroup
|
||||||
value={direction}
|
value={direction}
|
||||||
@@ -79,7 +79,7 @@ export default function FileMode() {
|
|||||||
}}
|
}}
|
||||||
onClick={() => fileInputRef.current?.click()}
|
onClick={() => fileInputRef.current?.click()}
|
||||||
className={cn(
|
className={cn(
|
||||||
'flex flex-col items-center justify-center min-h-[190px] border-2 border-dashed rounded-2xl p-8 cursor-pointer transition-all duration-300',
|
'flex flex-col items-center justify-center min-h-[190px] border-2 border-dashed rounded-2xl p-8 cursor-pointer',
|
||||||
isDragging
|
isDragging
|
||||||
? 'border-primary bg-primary/10'
|
? 'border-primary bg-primary/10'
|
||||||
: info
|
: info
|
||||||
@@ -100,7 +100,7 @@ export default function FileMode() {
|
|||||||
<div className="w-9 h-9 border-3 border-primary/20 border-t-primary rounded-full animate-spin" />
|
<div className="w-9 h-9 border-3 border-primary/20 border-t-primary rounded-full animate-spin" />
|
||||||
) : info ? (
|
) : info ? (
|
||||||
<div className="flex flex-col items-center gap-1.5 text-center">
|
<div className="flex flex-col items-center gap-1.5 text-center">
|
||||||
<Upload className="w-8 h-8 text-primary animate-bounce" />
|
<Upload className="w-8 h-8 text-primary" />
|
||||||
<span className="text-sm font-bold text-foreground/90 max-w-[280px] truncate">
|
<span className="text-sm font-bold text-foreground/90 max-w-[280px] truncate">
|
||||||
{info.name}
|
{info.name}
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ export default function ImageMode() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full flex flex-col space-y-4 animate-in fade-in duration-300">
|
<div className="w-full flex flex-col space-y-4">
|
||||||
<div className="flex h-11 items-center px-1.5 bg-secondary/40 rounded-xl border border-border/60 w-fit">
|
<div className="flex h-11 items-center px-1.5 bg-secondary/40 rounded-xl border border-border/60 w-fit">
|
||||||
<SwitchButtonGroup
|
<SwitchButtonGroup
|
||||||
value={direction}
|
value={direction}
|
||||||
@@ -82,7 +82,7 @@ export default function ImageMode() {
|
|||||||
}}
|
}}
|
||||||
onClick={() => fileInputRef.current?.click()}
|
onClick={() => fileInputRef.current?.click()}
|
||||||
className={cn(
|
className={cn(
|
||||||
'flex flex-col items-center justify-center min-h-[190px] border-2 border-dashed rounded-2xl p-8 cursor-pointer transition-all duration-300',
|
'flex flex-col items-center justify-center min-h-[190px] border-2 border-dashed rounded-2xl p-8 cursor-pointer',
|
||||||
isDragging
|
isDragging
|
||||||
? 'border-primary bg-primary/10'
|
? 'border-primary bg-primary/10'
|
||||||
: info
|
: info
|
||||||
@@ -103,7 +103,7 @@ export default function ImageMode() {
|
|||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
<div className="w-9 h-9 border-3 border-primary/20 border-t-primary rounded-full animate-spin" />
|
<div className="w-9 h-9 border-3 border-primary/20 border-t-primary rounded-full animate-spin" />
|
||||||
) : info ? (
|
) : info ? (
|
||||||
<div className="flex flex-col items-center gap-1.5 text-center animate-in fade-in duration-200 w-full">
|
<div className="flex flex-col items-center gap-1.5 text-center w-full">
|
||||||
{result && (
|
{result && (
|
||||||
<div className="relative p-1 border border-border bg-background rounded-lg shadow-sm mb-1 max-w-[180px] overflow-hidden">
|
<div className="relative p-1 border border-border bg-background rounded-lg shadow-sm mb-1 max-w-[180px] overflow-hidden">
|
||||||
<img
|
<img
|
||||||
@@ -233,7 +233,7 @@ export default function ImageMode() {
|
|||||||
onClear={resetAll}
|
onClear={resetAll}
|
||||||
/>
|
/>
|
||||||
{decoded && (
|
{decoded && (
|
||||||
<div className="animate-in slide-in-from-bottom-2 duration-300">
|
<div>
|
||||||
<DecodeResultPaper
|
<DecodeResultPaper
|
||||||
title={t('decodedImageOutput')}
|
title={t('decodedImageOutput')}
|
||||||
mimeType={decoded.mimeType}
|
mimeType={decoded.mimeType}
|
||||||
|
|||||||
@@ -93,7 +93,7 @@ export default function TextMode({ onSwitchToImageMode }: TextModeProps = {}) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full flex flex-col space-y-4 animate-in fade-in duration-300">
|
<div className="w-full flex flex-col space-y-4">
|
||||||
{/* 受控方向切流中枢 */}
|
{/* 受控方向切流中枢 */}
|
||||||
<div className="flex h-11 items-center px-1.5 bg-secondary/40 rounded-xl border border-border/60 w-fit">
|
<div className="flex h-11 items-center px-1.5 bg-secondary/40 rounded-xl border border-border/60 w-fit">
|
||||||
<SwitchButtonGroup
|
<SwitchButtonGroup
|
||||||
@@ -125,7 +125,7 @@ export default function TextMode({ onSwitchToImageMode }: TextModeProps = {}) {
|
|||||||
- 完美向全站 shadcn 暗黑生态看齐,采用标准的 bg-primary/10 混合变体。
|
- 完美向全站 shadcn 暗黑生态看齐,采用标准的 bg-primary/10 混合变体。
|
||||||
*/}
|
*/}
|
||||||
{showImageHint && (
|
{showImageHint && (
|
||||||
<div className="flex items-center justify-between p-3.5 rounded-xl bg-primary/10 border border-primary/20 animate-in slide-in-from-top-1 duration-200">
|
<div className="flex items-center justify-between p-3.5 rounded-xl bg-primary/10 border border-primary/20">
|
||||||
<span className="text-xs font-semibold text-primary tracking-tight">
|
<span className="text-xs font-semibold text-primary tracking-tight">
|
||||||
{t('imageDataUriHint')}
|
{t('imageDataUriHint')}
|
||||||
</span>
|
</span>
|
||||||
@@ -134,7 +134,7 @@ export default function TextMode({ onSwitchToImageMode }: TextModeProps = {}) {
|
|||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={onSwitchToImageMode}
|
onClick={onSwitchToImageMode}
|
||||||
className="h-7 rounded-md text-xs font-bold text-primary hover:text-primary hover:bg-primary/20 dark:hover:bg-primary/10 transition-colors px-2.5"
|
className="h-7 rounded-md text-xs font-bold text-primary hover:text-primary hover:bg-primary/20 dark:hover:bg-primary/10 px-2.5"
|
||||||
>
|
>
|
||||||
{t('switchToImageMode')}
|
{t('switchToImageMode')}
|
||||||
</Button>
|
</Button>
|
||||||
@@ -143,7 +143,7 @@ export default function TextMode({ onSwitchToImageMode }: TextModeProps = {}) {
|
|||||||
|
|
||||||
{/* 5. 编码/解码核心数据承载流卡片 */}
|
{/* 5. 编码/解码核心数据承载流卡片 */}
|
||||||
{output && (
|
{output && (
|
||||||
<div className="p-4 rounded-2xl bg-card border border-border shadow-sm flex flex-col space-y-3 animate-in slide-in-from-bottom-2 duration-300">
|
<div className="p-4 rounded-2xl bg-card border border-border shadow-sm flex flex-col space-y-3">
|
||||||
<div className="flex justify-between items-center select-none">
|
<div className="flex justify-between items-center select-none">
|
||||||
<span className="text-[10px] font-bold uppercase tracking-wider text-muted-foreground/90">
|
<span className="text-[10px] font-bold uppercase tracking-wider text-muted-foreground/90">
|
||||||
{outputLabel}
|
{outputLabel}
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ export default function Index() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="p-4 w-full flex flex-col space-y-4 min-h-[520px] select-none animate-in fade-in duration-300">
|
<div className="p-4 w-full flex flex-col space-y-4 min-h-[520px] select-none">
|
||||||
<SwitchButtonGroup
|
<SwitchButtonGroup
|
||||||
value={pageMode}
|
value={pageMode}
|
||||||
options={[
|
options={[
|
||||||
|
|||||||
@@ -46,7 +46,6 @@ export default function ToolCard({
|
|||||||
*/
|
*/
|
||||||
className={cn(
|
className={cn(
|
||||||
'group relative rounded-xl border border-border/70 bg-card text-card-foreground p-4 h-auto flex flex-col items-stretch justify-start gap-3 shadow-sm select-none box-border',
|
'group relative rounded-xl border border-border/70 bg-card text-card-foreground p-4 h-auto flex flex-col items-stretch justify-start gap-3 shadow-sm select-none box-border',
|
||||||
'transition-all duration-300 ease-[cubic-bezier(0.4,0,0.2,1)]',
|
|
||||||
'hover:bg-muted/30',
|
'hover:bg-muted/30',
|
||||||
'hover:border-[rgba(var(--tool-color),0.45)]',
|
'hover:border-[rgba(var(--tool-color),0.45)]',
|
||||||
'hover:shadow-[0_8px_24px_-8px_rgba(var(--tool-color),0.14)] dark:hover:shadow-[0_8px_30px_-10px_rgba(var(--tool-color),0.25)]',
|
'hover:shadow-[0_8px_24px_-8px_rgba(var(--tool-color),0.14)] dark:hover:shadow-[0_8px_30px_-10px_rgba(var(--tool-color),0.25)]',
|
||||||
@@ -60,7 +59,7 @@ export default function ToolCard({
|
|||||||
{/* 左侧圆形图标容器 */}
|
{/* 左侧圆形图标容器 */}
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
'flex items-center justify-center w-10 h-10 rounded-xl shrink-0 transition-colors duration-300',
|
'flex items-center justify-center w-10 h-10 rounded-xl shrink-0',
|
||||||
'bg-[rgba(var(--tool-color),0.08)] dark:bg-[rgba(var(--tool-color),0.12)]',
|
'bg-[rgba(var(--tool-color),0.08)] dark:bg-[rgba(var(--tool-color),0.12)]',
|
||||||
'text-[rgb(var(--tool-color))]',
|
'text-[rgb(var(--tool-color))]',
|
||||||
)}
|
)}
|
||||||
@@ -82,7 +81,7 @@ export default function ToolCard({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 右侧指示小箭头 */}
|
{/* 右侧指示小箭头 */}
|
||||||
<div className="text-muted-foreground/40 group-hover:text-[rgb(var(--tool-color))] p-1 shrink-0 transition-all duration-300 ease-in-out group-hover:translate-x-0.5">
|
<div className="text-muted-foreground/40 group-hover:text-[rgb(var(--tool-color))] p-1 shrink-0 group-hover:translate-x-0.5">
|
||||||
<ChevronRight className="h-4 w-4" />
|
<ChevronRight className="h-4 w-4" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ export default function DashboardPage() {
|
|||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
'grid grid-cols-1 sm:grid-cols-[repeat(auto-fill,minmax(290px,1fr))] auto-rows-auto gap-3.5 p-3.5 w-full h-auto',
|
'grid grid-cols-1 sm:grid-cols-[repeat(auto-fill,minmax(290px,1fr))] auto-rows-auto gap-3.5 p-3.5 w-full h-auto',
|
||||||
'animate-in fade-in duration-300 select-none',
|
'select-none',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{pageOrder.map((key) => {
|
{pageOrder.map((key) => {
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ export default function HtmlToMarkdownPage() {
|
|||||||
- 彻底清除多余的 container max-w-7xl 这种网页大边距,
|
- 彻底清除多余的 container max-w-7xl 这种网页大边距,
|
||||||
- 统一收拢为我们先前在 Dashboard 页、JSON 工具箱制定的 p-4 space-y-4 标准极客桌面规格。
|
- 统一收拢为我们先前在 Dashboard 页、JSON 工具箱制定的 p-4 space-y-4 标准极客桌面规格。
|
||||||
*/
|
*/
|
||||||
<div className="p-4 w-full flex flex-col space-y-4 select-none animate-in fade-in duration-300">
|
<div className="p-4 w-full flex flex-col space-y-4 select-none">
|
||||||
<div className="flex flex-col gap-4">
|
<div className="flex flex-col gap-4">
|
||||||
{/* 工具栏集成区 */}
|
{/* 工具栏集成区 */}
|
||||||
<div className="flex flex-col sm:flex-row gap-3 justify-between items-stretch sm:items-center bg-secondary/40 rounded-xl border border-border/60 px-1.5 py-1.5 sm:h-12">
|
<div className="flex flex-col sm:flex-row gap-3 justify-between items-stretch sm:items-center bg-secondary/40 rounded-xl border border-border/60 px-1.5 py-1.5 sm:h-12">
|
||||||
@@ -72,7 +72,7 @@ export default function HtmlToMarkdownPage() {
|
|||||||
size="sm"
|
size="sm"
|
||||||
onClick={handleDownload}
|
onClick={handleDownload}
|
||||||
disabled={!result.markdown}
|
disabled={!result.markdown}
|
||||||
className="h-8 rounded-md font-medium text-xs gap-1.5 shadow-sm active:scale-95 transition-all"
|
className="h-8 rounded-md font-medium text-xs gap-1.5 shadow-sm"
|
||||||
>
|
>
|
||||||
<Download className="h-3.5 w-3.5" />
|
<Download className="h-3.5 w-3.5" />
|
||||||
{t('download')}
|
{t('download')}
|
||||||
@@ -83,7 +83,7 @@ export default function HtmlToMarkdownPage() {
|
|||||||
variant="outline"
|
variant="outline"
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={handleClear}
|
onClick={handleClear}
|
||||||
className="h-8 rounded-md font-medium text-xs gap-1.5 shadow-sm active:scale-95 text-destructive hover:text-destructive hover:bg-destructive/5 dark:hover:bg-destructive/10 border-input/60 transition-all"
|
className="h-8 rounded-md font-medium text-xs gap-1.5 shadow-sm text-destructive hover:text-destructive hover:bg-destructive/5 dark:hover:bg-destructive/10 border-input/60"
|
||||||
>
|
>
|
||||||
<Trash2 className="h-3.5 w-3.5" />
|
<Trash2 className="h-3.5 w-3.5" />
|
||||||
{t('clear')}
|
{t('clear')}
|
||||||
@@ -95,7 +95,7 @@ export default function HtmlToMarkdownPage() {
|
|||||||
- 💡 核心修复点:将硬编码的 bg-red-50 实色,完美超进化为系统的全自适应透明色变体
|
- 💡 核心修复点:将硬编码的 bg-red-50 实色,完美超进化为系统的全自适应透明色变体
|
||||||
*/}
|
*/}
|
||||||
{error && (
|
{error && (
|
||||||
<div className="p-3.5 bg-destructive/10 border border-destructive/20 rounded-xl text-destructive text-xs font-semibold tracking-wide animate-in shake duration-300">
|
<div className="p-3.5 bg-destructive/10 border border-destructive/20 rounded-xl text-destructive text-xs font-semibold tracking-wide">
|
||||||
{error}
|
{error}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -114,7 +114,7 @@ export default function HtmlToMarkdownPage() {
|
|||||||
- 只要用户用鼠标点击了内部的 textarea,外层整块精巧的圆角大边框会一帧内亮起 primary 系统的深色呼吸发光环,
|
- 只要用户用鼠标点击了内部的 textarea,外层整块精巧的圆角大边框会一帧内亮起 primary 系统的深色呼吸发光环,
|
||||||
- 这种“全外包裹层框聚焦”的体验极大模仿了本地原生 IDE 的硬核专业体验!
|
- 这种“全外包裹层框聚焦”的体验极大模仿了本地原生 IDE 的硬核专业体验!
|
||||||
*/
|
*/
|
||||||
<div className="border border-border rounded-xl bg-card text-card-foreground shadow-sm overflow-hidden flex flex-col transition-all duration-200 focus-within:ring-1 focus-within:ring-ring focus-within:border-ring">
|
<div className="border border-border rounded-xl bg-card text-card-foreground shadow-sm overflow-hidden flex flex-col focus-within:ring-1 focus-within:ring-ring focus-within:border-ring">
|
||||||
{/* 卡片头部:改用标准的灰色 bg-muted/50 */}
|
{/* 卡片头部:改用标准的灰色 bg-muted/50 */}
|
||||||
<div className="flex h-9 items-center justify-between px-4 bg-muted/50 border-b border-border select-none">
|
<div className="flex h-9 items-center justify-between px-4 bg-muted/50 border-b border-border select-none">
|
||||||
<span className="text-[10px] font-bold text-muted-foreground/90 uppercase tracking-wider">
|
<span className="text-[10px] font-bold text-muted-foreground/90 uppercase tracking-wider">
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ export default function DiffNavigator({
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
'flex items-center justify-center gap-3 px-4 py-2 rounded-lg border border-border bg-muted/30 select-none animate-in fade-in duration-200',
|
'flex items-center justify-center gap-3 px-4 py-2 rounded-lg border border-border bg-muted/30 select-none',
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
@@ -60,9 +60,9 @@ export default function DiffNavigator({
|
|||||||
aria-label={t('jsonDiff:previousDiff')}
|
aria-label={t('jsonDiff:previousDiff')}
|
||||||
onClick={onPrev}
|
onClick={onPrev}
|
||||||
className={cn(
|
className={cn(
|
||||||
'p-1 rounded-md text-muted-foreground transition-all hover:bg-accent hover:text-foreground',
|
'p-1 rounded-md text-muted-foreground hover:bg-accent hover:text-foreground',
|
||||||
'focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring',
|
'focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring',
|
||||||
'disabled:pointer-events-none disabled:opacity-30 active:scale-95', // 4. 边界拦截:触顶时优雅淡化并锁死点击
|
'disabled:pointer-events-none disabled:opacity-30', // 4. 边界拦截:触顶时优雅淡化并锁死点击
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<ChevronLeft className="h-4 w-4" />
|
<ChevronLeft className="h-4 w-4" />
|
||||||
@@ -81,9 +81,9 @@ export default function DiffNavigator({
|
|||||||
aria-label={t('jsonDiff:nextDiff')}
|
aria-label={t('jsonDiff:nextDiff')}
|
||||||
onClick={onNext}
|
onClick={onNext}
|
||||||
className={cn(
|
className={cn(
|
||||||
'p-1 rounded-md text-muted-foreground transition-all hover:bg-accent hover:text-foreground',
|
'p-1 rounded-md text-muted-foreground hover:bg-accent hover:text-foreground',
|
||||||
'focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring',
|
'focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring',
|
||||||
'disabled:pointer-events-none disabled:opacity-30 active:scale-95', // 4. 边界拦截:触底时优雅淡化并锁死点击
|
'disabled:pointer-events-none disabled:opacity-30', // 4. 边界拦截:触底时优雅淡化并锁死点击
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<ChevronRight className="h-4 w-4" />
|
<ChevronRight className="h-4 w-4" />
|
||||||
|
|||||||
@@ -197,7 +197,7 @@ const UnifiedRow = ({ depth, type, text, active, multiline }: UnifiedRowProps) =
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
'flex items-start w-full font-mono py-0.5 select-text group transition-colors',
|
'flex items-start w-full font-mono py-0.5 select-text group',
|
||||||
currentTheme.bg,
|
currentTheme.bg,
|
||||||
currentTheme.text,
|
currentTheme.text,
|
||||||
// 4. 高亮定位条:不再使用生硬的蓝圆环,改为现代编辑器的“侧边左高亮带”设计,质感直接拉满
|
// 4. 高亮定位条:不再使用生硬的蓝圆环,改为现代编辑器的“侧边左高亮带”设计,质感直接拉满
|
||||||
|
|||||||
@@ -74,10 +74,7 @@ export default function JsonConvertSection({
|
|||||||
: null;
|
: null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div className={cn('w-full flex flex-col gap-4', className)} {...props}>
|
||||||
className={cn('w-full flex flex-col gap-4 animate-in fade-in duration-300', className)}
|
|
||||||
{...props}
|
|
||||||
>
|
|
||||||
{/* 输入区 */}
|
{/* 输入区 */}
|
||||||
<TextInputArea
|
<TextInputArea
|
||||||
placeholder={t(`jsonFormat:${pk}InputPlaceholder`)}
|
placeholder={t(`jsonFormat:${pk}InputPlaceholder`)}
|
||||||
@@ -93,7 +90,7 @@ export default function JsonConvertSection({
|
|||||||
|
|
||||||
{/* 4. 结果展示或状态引导卡片区 */}
|
{/* 4. 结果展示或状态引导卡片区 */}
|
||||||
{result && result.output ? (
|
{result && result.output ? (
|
||||||
<div className="relative rounded-xl border border-border bg-card text-card-foreground shadow-sm overflow-hidden animate-in slide-in-from-bottom-2 duration-300">
|
<div className="relative rounded-xl border border-border bg-card text-card-foreground shadow-sm overflow-hidden">
|
||||||
{/* 结果栏精致头部 */}
|
{/* 结果栏精致头部 */}
|
||||||
<div className="flex h-9 items-center justify-between px-4 border-b border-border bg-muted/50 select-none">
|
<div className="flex h-9 items-center justify-between px-4 border-b border-border bg-muted/50 select-none">
|
||||||
<div className="flex gap-4 items-center">
|
<div className="flex gap-4 items-center">
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ export default function JsonFormatSection() {
|
|||||||
: null;
|
: null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full flex flex-col gap-4 animate-in fade-in duration-300">
|
<div className="w-full flex flex-col gap-4">
|
||||||
{/* 工具控制栏 */}
|
{/* 工具控制栏 */}
|
||||||
<div className="flex h-10 items-center justify-between px-1.5 bg-secondary/40 rounded-xl border border-border/60">
|
<div className="flex h-10 items-center justify-between px-1.5 bg-secondary/40 rounded-xl border border-border/60">
|
||||||
<div className="flex gap-4 items-center w-full">
|
<div className="flex gap-4 items-center w-full">
|
||||||
@@ -95,7 +95,7 @@ export default function JsonFormatSection() {
|
|||||||
/>
|
/>
|
||||||
<Label
|
<Label
|
||||||
htmlFor="sort-keys-checkbox"
|
htmlFor="sort-keys-checkbox"
|
||||||
className="text-xs font-bold text-foreground/80 cursor-pointer tracking-tight group-hover:text-foreground transition-colors"
|
className="text-xs font-bold text-foreground/80 cursor-pointer tracking-tight group-hover:text-foreground"
|
||||||
>
|
>
|
||||||
{t('jsonFormat:sortKeys')}
|
{t('jsonFormat:sortKeys')}
|
||||||
</Label>
|
</Label>
|
||||||
@@ -118,7 +118,7 @@ export default function JsonFormatSection() {
|
|||||||
|
|
||||||
{/* 格式化结果流面板展示 */}
|
{/* 格式化结果流面板展示 */}
|
||||||
{result && result.formatted ? (
|
{result && result.formatted ? (
|
||||||
<div className="relative rounded-xl border border-border bg-card text-card-foreground shadow-sm overflow-hidden animate-in slide-in-from-bottom-2 duration-300">
|
<div className="relative rounded-xl border border-border bg-card text-card-foreground shadow-sm overflow-hidden">
|
||||||
{/* 结果栏头部 */}
|
{/* 结果栏头部 */}
|
||||||
<div className="flex h-9 items-center justify-between px-4 border-b border-border bg-muted/50 select-none">
|
<div className="flex h-9 items-center justify-between px-4 border-b border-border bg-muted/50 select-none">
|
||||||
<div className="flex gap-4 items-center">
|
<div className="flex gap-4 items-center">
|
||||||
|
|||||||
@@ -135,7 +135,7 @@ const NodeRow = React.memo(
|
|||||||
<div
|
<div
|
||||||
onClick={() => setOverride(expanded ? 'closed' : 'open')}
|
onClick={() => setOverride(expanded ? 'closed' : 'open')}
|
||||||
className={cn(
|
className={cn(
|
||||||
'group flex items-center gap-1 py-0.5 pr-2 text-xs font-mono select-none cursor-pointer rounded-sm transition-colors w-full h-[22px] leading-relaxed',
|
'group flex items-center gap-1 py-0.5 pr-2 text-xs font-mono select-none cursor-pointer rounded-sm w-full h-[22px] leading-relaxed',
|
||||||
theme.bg,
|
theme.bg,
|
||||||
isActive &&
|
isActive &&
|
||||||
'bg-primary/10 relative before:absolute before:left-0 before:top-0 before:bottom-0 before:w-0.5 before:bg-blue-500 rounded-none ring-0',
|
'bg-primary/10 relative before:absolute before:left-0 before:top-0 before:bottom-0 before:w-0.5 before:bg-blue-500 rounded-none ring-0',
|
||||||
@@ -207,7 +207,7 @@ const NodeRow = React.memo(
|
|||||||
<div
|
<div
|
||||||
ref={rowRef}
|
ref={rowRef}
|
||||||
className={cn(
|
className={cn(
|
||||||
'flex items-center gap-1 py-0.5 pr-2 font-mono text-xs w-full h-[22px] leading-relaxed rounded-sm transition-colors',
|
'flex items-center gap-1 py-0.5 pr-2 font-mono text-xs w-full h-[22px] leading-relaxed rounded-sm',
|
||||||
theme.bg,
|
theme.bg,
|
||||||
isActive &&
|
isActive &&
|
||||||
'bg-primary/10 relative before:absolute before:left-0 before:top-0 before:bottom-0 before:w-0.5 before:bg-blue-500 rounded-none ring-0',
|
'bg-primary/10 relative before:absolute before:left-0 before:top-0 before:bottom-0 before:w-0.5 before:bg-blue-500 rounded-none ring-0',
|
||||||
|
|||||||
@@ -113,7 +113,7 @@ export default function Index() {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="p-4 w-full flex flex-col space-y-4 min-h-[500px] select-none animate-in fade-in duration-300">
|
<div className="p-4 w-full flex flex-col space-y-4 min-h-[500px] select-none">
|
||||||
<SwitchButtonGroup
|
<SwitchButtonGroup
|
||||||
value={pageMode}
|
value={pageMode}
|
||||||
onChange={(v: PageMode) => setPageMode(v)}
|
onChange={(v: PageMode) => setPageMode(v)}
|
||||||
@@ -129,7 +129,7 @@ export default function Index() {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
{pageMode === 'diff' ? (
|
{pageMode === 'diff' ? (
|
||||||
<div className="flex flex-col space-y-4 animate-in fade-in duration-200">
|
<div className="flex flex-col space-y-4">
|
||||||
<div className="flex h-10 items-center justify-between px-1.5 bg-secondary/40 rounded-xl border border-border/60">
|
<div className="flex h-10 items-center justify-between px-1.5 bg-secondary/40 rounded-xl border border-border/60">
|
||||||
<SwitchButtonGroup
|
<SwitchButtonGroup
|
||||||
value={viewMode}
|
value={viewMode}
|
||||||
|
|||||||
+4
-6
@@ -17,9 +17,7 @@ interface SectionProps {
|
|||||||
const Section = ({ title, content, colorClass, bgClass, borderClass }: SectionProps) => {
|
const Section = ({ title, content, colorClass, bgClass, borderClass }: SectionProps) => {
|
||||||
const { t } = useLazyTranslation('jwt');
|
const { t } = useLazyTranslation('jwt');
|
||||||
return (
|
return (
|
||||||
<div
|
<div className={cn('p-4 rounded-xl border border-solid', bgClass, borderClass)}>
|
||||||
className={cn('p-4 rounded-xl border border-solid transition-colors', bgClass, borderClass)}
|
|
||||||
>
|
|
||||||
<div className="flex justify-between items-center mb-2 select-none">
|
<div className="flex justify-between items-center mb-2 select-none">
|
||||||
<span className={cn('text-xs font-bold tracking-wider uppercase', colorClass)}>
|
<span className={cn('text-xs font-bold tracking-wider uppercase', colorClass)}>
|
||||||
{title}
|
{title}
|
||||||
@@ -70,7 +68,7 @@ export default function Index() {
|
|||||||
}, [debouncedInput]);
|
}, [debouncedInput]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="p-4 w-full flex flex-col space-y-4 animate-in fade-in duration-300 select-none">
|
<div className="p-4 w-full flex flex-col space-y-4 select-none">
|
||||||
<div className="flex flex-col gap-4">
|
<div className="flex flex-col gap-4">
|
||||||
{/* 输入终端 */}
|
{/* 输入终端 */}
|
||||||
<TextInputArea
|
<TextInputArea
|
||||||
@@ -90,7 +88,7 @@ export default function Index() {
|
|||||||
|
|
||||||
{/* 解码看板结果展现 */}
|
{/* 解码看板结果展现 */}
|
||||||
{result && !result.error && (
|
{result && !result.error && (
|
||||||
<div className="flex flex-col gap-4 animate-in slide-in-from-bottom-2 duration-300">
|
<div className="flex flex-col gap-4">
|
||||||
{/* Header 分区:完美致敬 JWT.io 的鲜艳色彩,同时实现黑夜暗化自适应 */}
|
{/* Header 分区:完美致敬 JWT.io 的鲜艳色彩,同时实现黑夜暗化自适应 */}
|
||||||
<Section
|
<Section
|
||||||
title={t('jwt:headerTitle')}
|
title={t('jwt:headerTitle')}
|
||||||
@@ -110,7 +108,7 @@ export default function Index() {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Signature 签名区:完全对齐标准的 shadcn 骨架阶度 */}
|
{/* Signature 签名区:完全对齐标准的 shadcn 骨架阶度 */}
|
||||||
<div className="p-4 rounded-xl border border-border bg-secondary/40 shadow-sm transition-colors">
|
<div className="p-4 rounded-xl border border-border bg-secondary/40 shadow-sm">
|
||||||
<div className="flex justify-between items-center mb-2">
|
<div className="flex justify-between items-center mb-2">
|
||||||
<span className="text-xs font-bold tracking-wider text-muted-foreground/90 uppercase">
|
<span className="text-xs font-bold tracking-wider text-muted-foreground/90 uppercase">
|
||||||
{t('jwt:signatureTitle')}
|
{t('jwt:signatureTitle')}
|
||||||
|
|||||||
@@ -175,7 +175,7 @@ export default function MarkdownToHtmlPage() {
|
|||||||
const showPreview = previewMode !== 'html';
|
const showPreview = previewMode !== 'html';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="p-4 w-full flex flex-col space-y-4 select-none animate-in fade-in duration-300">
|
<div className="p-4 w-full flex flex-col space-y-4 select-none">
|
||||||
<div className="flex flex-col space-y-4">
|
<div className="flex flex-col space-y-4">
|
||||||
{/* 工具集成控制中枢 */}
|
{/* 工具集成控制中枢 */}
|
||||||
<div className="flex flex-col sm:flex-row gap-3 justify-between items-stretch sm:items-center bg-secondary/40 rounded-xl border border-border/60 px-1.5 py-1.5 sm:h-12">
|
<div className="flex flex-col sm:flex-row gap-3 justify-between items-stretch sm:items-center bg-secondary/40 rounded-xl border border-border/60 px-1.5 py-1.5 sm:h-12">
|
||||||
@@ -196,7 +196,7 @@ export default function MarkdownToHtmlPage() {
|
|||||||
variant="outline"
|
variant="outline"
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={handleClear}
|
onClick={handleClear}
|
||||||
className="h-8 rounded-md font-medium text-xs gap-1.5 shadow-sm active:scale-95 text-destructive hover:text-destructive hover:bg-destructive/5 dark:hover:bg-destructive/10 border-input/60 transition-all"
|
className="h-8 rounded-md font-medium text-xs gap-1.5 shadow-sm text-destructive hover:text-destructive hover:bg-destructive/5 dark:hover:bg-destructive/10 border-input/60"
|
||||||
>
|
>
|
||||||
<Trash2 className="h-3.5 w-3.5" />
|
<Trash2 className="h-3.5 w-3.5" />
|
||||||
{t('clear')}
|
{t('clear')}
|
||||||
@@ -206,7 +206,7 @@ export default function MarkdownToHtmlPage() {
|
|||||||
size="sm"
|
size="sm"
|
||||||
onClick={handlePrint}
|
onClick={handlePrint}
|
||||||
disabled={!result.html}
|
disabled={!result.html}
|
||||||
className="h-8 rounded-md font-medium text-xs gap-1.5 shadow-sm active:scale-95 transition-all"
|
className="h-8 rounded-md font-medium text-xs gap-1.5 shadow-sm"
|
||||||
>
|
>
|
||||||
<Printer className="h-3.5 w-3.5" />
|
<Printer className="h-3.5 w-3.5" />
|
||||||
{t('print')}
|
{t('print')}
|
||||||
@@ -216,7 +216,7 @@ export default function MarkdownToHtmlPage() {
|
|||||||
size="sm"
|
size="sm"
|
||||||
onClick={handleDownload}
|
onClick={handleDownload}
|
||||||
disabled={!result.html}
|
disabled={!result.html}
|
||||||
className="h-8 rounded-md font-medium text-xs gap-1.5 shadow-sm active:scale-95 transition-all"
|
className="h-8 rounded-md font-medium text-xs gap-1.5 shadow-sm"
|
||||||
>
|
>
|
||||||
<Download className="h-3.5 w-3.5" />
|
<Download className="h-3.5 w-3.5" />
|
||||||
{t('download')}
|
{t('download')}
|
||||||
@@ -228,7 +228,7 @@ export default function MarkdownToHtmlPage() {
|
|||||||
{error && (
|
{error && (
|
||||||
<div
|
<div
|
||||||
role="alert"
|
role="alert"
|
||||||
className="p-3.5 bg-destructive/10 border border-destructive/20 rounded-xl text-destructive text-xs font-semibold tracking-wide animate-in shake duration-300"
|
className="p-3.5 bg-destructive/10 border border-destructive/20 rounded-xl text-destructive text-xs font-semibold tracking-wide"
|
||||||
>
|
>
|
||||||
{error}
|
{error}
|
||||||
</div>
|
</div>
|
||||||
@@ -243,7 +243,7 @@ export default function MarkdownToHtmlPage() {
|
|||||||
>
|
>
|
||||||
{/* Markdown 输入翼终端 */}
|
{/* Markdown 输入翼终端 */}
|
||||||
{showInput && (
|
{showInput && (
|
||||||
<div className="border border-border rounded-xl bg-card text-card-foreground shadow-sm overflow-hidden flex flex-col transition-all duration-200 focus-within:ring-1 focus-within:ring-ring focus-within:border-ring animate-in fade-in">
|
<div className="border border-border rounded-xl bg-card text-card-foreground shadow-sm overflow-hidden flex flex-col focus-within:ring-1 focus-within:ring-ring focus-within:border-ring">
|
||||||
<div className="flex h-9 items-center justify-between px-4 bg-muted/50 border-b border-border select-none">
|
<div className="flex h-9 items-center justify-between px-4 bg-muted/50 border-b border-border select-none">
|
||||||
<span className="text-[10px] font-bold text-muted-foreground/90 uppercase tracking-wider">
|
<span className="text-[10px] font-bold text-muted-foreground/90 uppercase tracking-wider">
|
||||||
{t('inputLabel')}
|
{t('inputLabel')}
|
||||||
@@ -263,7 +263,7 @@ export default function MarkdownToHtmlPage() {
|
|||||||
|
|
||||||
{/* 实时 HTML/Iframe 预览翼终端 */}
|
{/* 实时 HTML/Iframe 预览翼终端 */}
|
||||||
{showPreview && (
|
{showPreview && (
|
||||||
<div className="border border-border rounded-xl bg-card text-card-foreground shadow-sm overflow-hidden flex flex-col animate-in fade-in">
|
<div className="border border-border rounded-xl bg-card text-card-foreground shadow-sm overflow-hidden flex flex-col">
|
||||||
<div className="flex h-9 items-center justify-between px-4 bg-muted/50 border-b border-border select-none">
|
<div className="flex h-9 items-center justify-between px-4 bg-muted/50 border-b border-border select-none">
|
||||||
<span className="text-[10px] font-bold text-muted-foreground/90 uppercase tracking-wider">
|
<span className="text-[10px] font-bold text-muted-foreground/90 uppercase tracking-wider">
|
||||||
{(previewMode as string) === 'html' ? t('htmlOutputLabel') : t('previewLabel')}
|
{(previewMode as string) === 'html' ? t('htmlOutputLabel') : t('previewLabel')}
|
||||||
|
|||||||
@@ -10,11 +10,11 @@ export default function GeneratePanel() {
|
|||||||
const { generatorState, setTextToEncode, downloadQrCode, copyQrCode } = useQrCodeContext();
|
const { generatorState, setTextToEncode, downloadQrCode, copyQrCode } = useQrCodeContext();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 w-full items-stretch select-none p-0.5 animate-in fade-in duration-300">
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 w-full items-stretch select-none p-0.5">
|
||||||
{/* 左翼:高性能受控输入翼终端 */}
|
{/* 左翼:高性能受控输入翼终端 */}
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
'border border-border rounded-xl bg-card text-card-foreground shadow-sm flex flex-col p-4 transition-all duration-200',
|
'border border-border rounded-xl bg-card text-card-foreground shadow-sm flex flex-col p-4',
|
||||||
'focus-within:ring-1 focus-within:ring-ring focus-within:border-ring',
|
'focus-within:ring-1 focus-within:ring-ring focus-within:border-ring',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ export default function ParsePanel() {
|
|||||||
/* 💡 统一大视觉轴:
|
/* 💡 统一大视觉轴:
|
||||||
- 追加 p-0.5 微隔离,配合 gap-6 建立与生成面板(GeneratePanel)绝对像素对齐的网格天平。
|
- 追加 p-0.5 微隔离,配合 gap-6 建立与生成面板(GeneratePanel)绝对像素对齐的网格天平。
|
||||||
*/
|
*/
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 w-full items-stretch select-none p-0.5 animate-in fade-in duration-300">
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 w-full items-stretch select-none p-0.5">
|
||||||
{/* 左翼:图片接收/拖拽/剪贴板上传终端 */}
|
{/* 左翼:图片接收/拖拽/剪贴板上传终端 */}
|
||||||
<div className="flex flex-col h-full">
|
<div className="flex flex-col h-full">
|
||||||
<ImageUploader
|
<ImageUploader
|
||||||
@@ -78,7 +78,7 @@ export default function ParsePanel() {
|
|||||||
{/* 右翼:高阶解析出码只读终端 */}
|
{/* 右翼:高阶解析出码只读终端 */}
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
'border border-border rounded-xl bg-card text-card-foreground shadow-sm flex flex-col p-4 transition-all duration-200',
|
'border border-border rounded-xl bg-card text-card-foreground shadow-sm flex flex-col p-4',
|
||||||
// 💡 视觉对称增强:加入相同的聚焦变量环联动,使双翼权重达成完美绝对平衡
|
// 💡 视觉对称增强:加入相同的聚焦变量环联动,使双翼权重达成完美绝对平衡
|
||||||
'focus-within:ring-1 focus-within:ring-ring focus-within:border-ring',
|
'focus-within:ring-1 focus-within:ring-ring focus-within:border-ring',
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ export default function Index() {
|
|||||||
- 彻底剥离破坏流式宽度的 max-w-[400px] 枷锁,开启标准的 w-full 全自适应包裹。
|
- 彻底剥离破坏流式宽度的 max-w-[400px] 枷锁,开启标准的 w-full 全自适应包裹。
|
||||||
- 替换为标准的 p-4 呼吸内边距配合 flex flex-col space-y-4,接管系统级重排!
|
- 替换为标准的 p-4 呼吸内边距配合 flex flex-col space-y-4,接管系统级重排!
|
||||||
*/}
|
*/}
|
||||||
<div className="p-4 w-full flex flex-col space-y-4 min-h-[500px] select-none animate-in fade-in duration-300">
|
<div className="p-4 w-full flex flex-col space-y-4 min-h-[500px] select-none">
|
||||||
{/* 流式中央控制切流卡:注入 sm 断点防御,防范单栏状态下发生变形 */}
|
{/* 流式中央控制切流卡:注入 sm 断点防御,防范单栏状态下发生变形 */}
|
||||||
<div className="w-full sm:w-fit pt-0.5">
|
<div className="w-full sm:w-fit pt-0.5">
|
||||||
<SwitchButtonGroup
|
<SwitchButtonGroup
|
||||||
@@ -40,11 +40,11 @@ export default function Index() {
|
|||||||
*/}
|
*/}
|
||||||
<div className="w-full pt-1.5">
|
<div className="w-full pt-1.5">
|
||||||
{qrCode.mode === 'generate' ? (
|
{qrCode.mode === 'generate' ? (
|
||||||
<div className="animate-in fade-in duration-200">
|
<div>
|
||||||
<GeneratePanel />
|
<GeneratePanel />
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="animate-in fade-in duration-200">
|
<div>
|
||||||
<ParsePanel />
|
<ParsePanel />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ export default function Index() {
|
|||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="p-4 w-full space-y-4 animate-in fade-in duration-300">
|
<div className="p-4 w-full space-y-4">
|
||||||
{/* 文本输入区域 */}
|
{/* 文本输入区域 */}
|
||||||
<TextInputArea
|
<TextInputArea
|
||||||
value={text}
|
value={text}
|
||||||
@@ -45,7 +45,6 @@ export default function Index() {
|
|||||||
key={item.label}
|
key={item.label}
|
||||||
className={cn(
|
className={cn(
|
||||||
'flex flex-col justify-center items-center p-4 text-center rounded-xl border border-border bg-card shadow-sm text-card-foreground',
|
'flex flex-col justify-center items-center p-4 text-center rounded-xl border border-border bg-card shadow-sm text-card-foreground',
|
||||||
'transition-all duration-200 ease-out',
|
|
||||||
'hover:-translate-y-0.5 hover:shadow-md hover:border-primary/50 focus-within:ring-1 focus-within:ring-ring',
|
'hover:-translate-y-0.5 hover:shadow-md hover:border-primary/50 focus-within:ring-1 focus-within:ring-ring',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ const LiveClock = React.memo(({ unit, onUseNow, className, ...props }: LiveClock
|
|||||||
type="button"
|
type="button"
|
||||||
onClick={handleUseNow}
|
onClick={handleUseNow}
|
||||||
title={t('timestamp:useNowTooltip')}
|
title={t('timestamp:useNowTooltip')}
|
||||||
className="flex h-7 w-7 items-center justify-center rounded-md border border-input bg-background text-muted-foreground shadow-sm transition-all hover:bg-accent hover:text-foreground active:scale-95 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"
|
className="flex h-7 w-7 items-center justify-center rounded-md border border-input bg-background text-muted-foreground shadow-sm hover:bg-accent hover:text-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"
|
||||||
>
|
>
|
||||||
<Clock className="w-3.5 h-3.5" />
|
<Clock className="w-3.5 h-3.5" />
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ const ResultView = React.memo(
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
'flex-1 flex items-center justify-center text-sm font-medium border border-dashed border-border/60 rounded-xl py-12 px-4 text-center text-muted-foreground bg-muted/20 min-h-[320px] animate-in fade-in duration-200',
|
'flex-1 flex items-center justify-center text-sm font-medium border border-dashed border-border/60 rounded-xl py-12 px-4 text-center text-muted-foreground bg-muted/20 min-h-[320px]',
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
@@ -61,13 +61,7 @@ const ResultView = React.memo(
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div className={cn('flex flex-col w-full', className)} {...props}>
|
||||||
className={cn(
|
|
||||||
'animate-in fade-in slide-in-from-bottom-2 duration-300 flex flex-col w-full',
|
|
||||||
className,
|
|
||||||
)}
|
|
||||||
{...props}
|
|
||||||
>
|
|
||||||
{/* 顶部小标签 */}
|
{/* 顶部小标签 */}
|
||||||
<span className="block text-muted-foreground/90 mb-2.5 text-xs font-semibold tracking-wider uppercase">
|
<span className="block text-muted-foreground/90 mb-2.5 text-xs font-semibold tracking-wider uppercase">
|
||||||
{t('timestamp:resultLabel')}
|
{t('timestamp:resultLabel')}
|
||||||
@@ -78,7 +72,7 @@ const ResultView = React.memo(
|
|||||||
对齐 shadcn 官方卡片风格,使用 bg-card、border-border 构筑多层级阴影。
|
对齐 shadcn 官方卡片风格,使用 bg-card、border-border 构筑多层级阴影。
|
||||||
核心数值直接拉粗为 text-foreground (在黑夜模式下会自动转为大气的纯白,完美避开刺眼强光)
|
核心数值直接拉粗为 text-foreground (在黑夜模式下会自动转为大气的纯白,完美避开刺眼强光)
|
||||||
*/}
|
*/}
|
||||||
<div className="bg-card text-card-foreground border border-border p-4 sm:p-5 rounded-xl relative mb-3.5 shadow-sm flex justify-between items-center gap-4 focus-within:ring-1 focus-within:ring-ring transition-all">
|
<div className="bg-card text-card-foreground border border-border p-4 sm:p-5 rounded-xl relative mb-3.5 shadow-sm flex justify-between items-center gap-4 focus-within:ring-1 focus-within:ring-ring">
|
||||||
<span className="font-mono font-extrabold text-foreground break-all text-xl sm:text-2xl tracking-tight leading-tight select-all tabular-nums">
|
<span className="font-mono font-extrabold text-foreground break-all text-xl sm:text-2xl tracking-tight leading-tight select-all tabular-nums">
|
||||||
{result}
|
{result}
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -70,17 +70,13 @@ export default function Index() {
|
|||||||
value={input}
|
value={input}
|
||||||
onChange={(e: { target: { value: string } }) => setInput(e.target.value)}
|
onChange={(e: { target: { value: string } }) => setInput(e.target.value)}
|
||||||
className={cn(
|
className={cn(
|
||||||
'font-mono font-semibold h-10 shadow-sm placeholder:text-muted-foreground/60 focus:bg-background transition-all',
|
'font-mono font-semibold h-10 shadow-sm placeholder:text-muted-foreground/60 focus:bg-background',
|
||||||
error && 'border-destructive focus-visible:ring-destructive',
|
error && 'border-destructive focus-visible:ring-destructive',
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* 错误自愈提示 */}
|
{/* 错误自愈提示 */}
|
||||||
{error && (
|
{error && <p className="text-destructive text-xs font-medium px-0.5">{error}</p>}
|
||||||
<p className="text-destructive text-xs font-medium px-0.5 animate-in fade-in slide-in-from-top-1 duration-150">
|
|
||||||
{error}
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 核心配置群:单位切换 + 时区选择紧凑横排 */}
|
{/* 核心配置群:单位切换 + 时区选择紧凑横排 */}
|
||||||
|
|||||||
Reference in New Issue
Block a user