mirror of
https://github.com/snapotter-hq/SnapOtter.git
synced 2026-08-03 07:46:42 +02:00
fix(a11y): WCAG AA contrast retune for the Otter Orange palette (#567)
Fixes #557. Vivid fill, ink label: brand #E07832 stays on fills while primary-foreground flips to #1A1814 (5.83:1); new theme-aware ink tokens carry orange, destructive, and success text roles; opacity-modified text purged; landing, demo, and the docs fund button retuned. Guarded by a CSS-parsing unit contrast test, rebuilt axe baselines with zero contrast entries, a new landing axe smoke, and fully regenerated darwin visual baselines.
This commit is contained in:
@@ -208,7 +208,7 @@ export function AiCanvasExpandSettings() {
|
||||
</p>
|
||||
)}
|
||||
|
||||
{error && <p className="text-xs text-red-500">{error}</p>}
|
||||
{error && <p className="text-xs text-destructive-ink">{error}</p>}
|
||||
|
||||
{processing ? (
|
||||
<ProgressCard
|
||||
@@ -237,7 +237,7 @@ export function AiCanvasExpandSettings() {
|
||||
href={downloadUrl}
|
||||
download
|
||||
data-testid="ai-canvas-expand-download"
|
||||
className="w-full py-2.5 rounded-lg border border-primary text-primary font-medium flex items-center justify-center gap-2 hover:bg-primary/5"
|
||||
className="w-full py-2.5 rounded-lg border border-primary text-primary-ink font-medium flex items-center justify-center gap-2 hover:bg-primary/5"
|
||||
>
|
||||
<Download className="h-4 w-4" />
|
||||
Download
|
||||
|
||||
@@ -65,7 +65,7 @@ export function AspectPadSettings() {
|
||||
<p className="text-[10px] text-muted-foreground mt-0.5">{s.colorHint}</p>
|
||||
</div>
|
||||
|
||||
{error && <p className="text-xs text-red-500">{error}</p>}
|
||||
{error && <p className="text-xs text-destructive-ink">{error}</p>}
|
||||
|
||||
{processing ? (
|
||||
<ProgressCard
|
||||
|
||||
@@ -46,7 +46,7 @@ export function AudioChannelsSettings() {
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{error && <p className="text-xs text-red-500">{error}</p>}
|
||||
{error && <p className="text-xs text-destructive-ink">{error}</p>}
|
||||
|
||||
{processing ? (
|
||||
<ProgressCard
|
||||
|
||||
@@ -86,7 +86,7 @@ export function AudioMetadataSettings() {
|
||||
/>
|
||||
</div>
|
||||
|
||||
{error && <p className="text-xs text-red-500">{error}</p>}
|
||||
{error && <p className="text-xs text-destructive-ink">{error}</p>}
|
||||
|
||||
{processing ? (
|
||||
<ProgressCard
|
||||
|
||||
@@ -44,7 +44,7 @@ export function AudioSpeedSettings() {
|
||||
/>
|
||||
</div>
|
||||
|
||||
{error && <p className="text-xs text-red-500">{error}</p>}
|
||||
{error && <p className="text-xs text-destructive-ink">{error}</p>}
|
||||
|
||||
{processing ? (
|
||||
<ProgressCard
|
||||
|
||||
@@ -133,7 +133,7 @@ export function BarcodeGenerateSettings() {
|
||||
{ts.includeText}
|
||||
</label>
|
||||
|
||||
{error && <p className="text-xs text-red-500">{error}</p>}
|
||||
{error && <p className="text-xs text-destructive-ink">{error}</p>}
|
||||
|
||||
{/* Result preview */}
|
||||
{resultUrl && (
|
||||
@@ -147,7 +147,7 @@ export function BarcodeGenerateSettings() {
|
||||
href={resultUrl}
|
||||
download="barcode.png"
|
||||
data-testid="barcode-generate-download"
|
||||
className="w-full py-2.5 rounded-lg border border-primary text-primary font-medium flex items-center justify-center gap-2 hover:bg-primary/5"
|
||||
className="w-full py-2.5 rounded-lg border border-primary text-primary-ink font-medium flex items-center justify-center gap-2 hover:bg-primary/5"
|
||||
>
|
||||
<Download className="h-4 w-4" />
|
||||
{ts.download}
|
||||
|
||||
@@ -45,11 +45,11 @@ const FORMAT_LABELS: Record<string, string> = {
|
||||
/** Badge colors by barcode family. */
|
||||
function getBadgeColor(type: string): string {
|
||||
if (type.includes("QR") || type === "Aztec" || type === "DataMatrix" || type === "MaxiCode")
|
||||
return "bg-primary/15 text-primary-dark dark:text-primary-light";
|
||||
return "bg-primary/15 text-primary-ink";
|
||||
if (type.includes("EAN") || type.includes("UPC") || type.includes("DataBar"))
|
||||
return "bg-green-500/15 text-green-600 dark:text-green-400";
|
||||
return "bg-green-500/15 text-success-ink";
|
||||
if (type === "PDF417") return "bg-purple-500/15 text-purple-600 dark:text-purple-400";
|
||||
return "bg-amber-500/15 text-amber-600 dark:text-amber-400";
|
||||
return "bg-amber-500/15 text-amber-700 dark:text-amber-400";
|
||||
}
|
||||
|
||||
function SectionLabel({ children }: { children: React.ReactNode }) {
|
||||
@@ -264,7 +264,7 @@ export function BarcodeReadSettings() {
|
||||
</label>
|
||||
|
||||
{/* Error */}
|
||||
{error && <p className="text-xs text-red-500">{error}</p>}
|
||||
{error && <p className="text-xs text-destructive-ink">{error}</p>}
|
||||
|
||||
{/* Process button / progress */}
|
||||
{processing ? (
|
||||
@@ -360,7 +360,7 @@ export function BarcodeReadSettings() {
|
||||
title="Copy value"
|
||||
>
|
||||
{copiedIndex === idx ? (
|
||||
<Check className="h-3.5 w-3.5 text-green-500" />
|
||||
<Check className="h-3.5 w-3.5 text-success-ink" />
|
||||
) : (
|
||||
<Copy className="h-3.5 w-3.5" />
|
||||
)}
|
||||
|
||||
@@ -1374,7 +1374,7 @@ export function BeautifySettings({
|
||||
onBackgroundImage={handleBgImageChange}
|
||||
/>
|
||||
|
||||
{displayError && <p className="text-xs text-red-500">{displayError}</p>}
|
||||
{displayError && <p className="text-xs text-destructive-ink">{displayError}</p>}
|
||||
|
||||
{processing ? (
|
||||
<ProgressCard
|
||||
@@ -1405,7 +1405,7 @@ export function BeautifySettings({
|
||||
href={displayDownloadUrl}
|
||||
download
|
||||
data-testid="beautify-download"
|
||||
className="w-full py-2.5 rounded-lg border border-primary text-primary font-medium flex items-center justify-center gap-2 hover:bg-primary/5"
|
||||
className="w-full py-2.5 rounded-lg border border-primary text-primary-ink font-medium flex items-center justify-center gap-2 hover:bg-primary/5"
|
||||
>
|
||||
<Download className="h-4 w-4" />
|
||||
Download
|
||||
|
||||
@@ -92,7 +92,7 @@ export function BlurBackgroundSettings() {
|
||||
onClick={() => setOutputFormat(fmt)}
|
||||
className={`rounded-lg border py-2 px-2 text-xs font-medium uppercase transition-colors ${
|
||||
outputFormat === fmt
|
||||
? "border-primary bg-primary/10 text-primary"
|
||||
? "border-primary bg-primary/10 text-primary-ink"
|
||||
: "border-border text-muted-foreground hover:border-primary/50"
|
||||
}`}
|
||||
>
|
||||
|
||||
@@ -114,9 +114,9 @@ export function BlurFacesSettings() {
|
||||
<div className="space-y-4">
|
||||
<BlurFacesControls onChange={setSettings} />
|
||||
|
||||
{error && <p className="text-xs text-red-500">{error}</p>}
|
||||
{error && <p className="text-xs text-destructive-ink">{error}</p>}
|
||||
|
||||
{warning && <p className="text-xs text-amber-600 dark:text-amber-400">{warning}</p>}
|
||||
{warning && <p className="text-xs text-amber-700 dark:text-amber-400">{warning}</p>}
|
||||
|
||||
{/* Size info */}
|
||||
{originalSize != null && processedSize != null && (
|
||||
@@ -153,7 +153,7 @@ export function BlurFacesSettings() {
|
||||
href={downloadUrl}
|
||||
download
|
||||
data-testid="blur-faces-download"
|
||||
className="w-full py-2.5 rounded-lg border border-primary text-primary font-medium flex items-center justify-center gap-2 hover:bg-primary/5"
|
||||
className="w-full py-2.5 rounded-lg border border-primary text-primary-ink font-medium flex items-center justify-center gap-2 hover:bg-primary/5"
|
||||
>
|
||||
<Download className="h-4 w-4" />
|
||||
{t.common.download}
|
||||
|
||||
@@ -65,7 +65,7 @@ export function BlurPadSettings() {
|
||||
/>
|
||||
</div>
|
||||
|
||||
{error && <p className="text-xs text-red-500">{error}</p>}
|
||||
{error && <p className="text-xs text-destructive-ink">{error}</p>}
|
||||
|
||||
{processing ? (
|
||||
<ProgressCard
|
||||
|
||||
@@ -47,7 +47,7 @@ export function BookletPdfSettings() {
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{error && <p className="text-xs text-red-500">{error}</p>}
|
||||
{error && <p className="text-xs text-destructive-ink">{error}</p>}
|
||||
|
||||
{processing ? (
|
||||
<ProgressCard
|
||||
|
||||
@@ -613,7 +613,7 @@ export function BorderSettings({
|
||||
<form onSubmit={handleSubmit} className="space-y-4">
|
||||
<BorderControls onChange={handleSettingsChange} onImageStyle={onImageStyle} />
|
||||
|
||||
{error && <p className="text-xs text-red-500">{error}</p>}
|
||||
{error && <p className="text-xs text-destructive-ink">{error}</p>}
|
||||
|
||||
{processing ? (
|
||||
<ProgressCard
|
||||
@@ -640,7 +640,7 @@ export function BorderSettings({
|
||||
href={downloadUrl}
|
||||
download
|
||||
data-testid="border-download"
|
||||
className="w-full py-2.5 rounded-lg border border-primary text-primary font-medium flex items-center justify-center gap-2 hover:bg-primary/5"
|
||||
className="w-full py-2.5 rounded-lg border border-primary text-primary-ink font-medium flex items-center justify-center gap-2 hover:bg-primary/5"
|
||||
>
|
||||
<Download className="h-4 w-4" />
|
||||
Download
|
||||
|
||||
@@ -119,7 +119,7 @@ export function BulkRenameSettings() {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{error && <p className="text-xs text-red-500">{error}</p>}
|
||||
{error && <p className="text-xs text-destructive-ink">{error}</p>}
|
||||
|
||||
<button
|
||||
type="button"
|
||||
@@ -135,7 +135,7 @@ export function BulkRenameSettings() {
|
||||
</button>
|
||||
|
||||
{downloadReady && (
|
||||
<p className="text-xs text-green-600 flex items-center gap-1">
|
||||
<p className="text-xs text-success-ink flex items-center gap-1">
|
||||
<Download className="h-3 w-3" /> {t.toolSettings["bulk-rename"].zipDownloaded}
|
||||
</p>
|
||||
)}
|
||||
|
||||
@@ -38,7 +38,7 @@ export function BurnSubtitlesSettings() {
|
||||
|
||||
<p className="text-[10px] text-muted-foreground">{s.hint}</p>
|
||||
|
||||
{error && <p className="text-xs text-red-500">{error}</p>}
|
||||
{error && <p className="text-xs text-destructive-ink">{error}</p>}
|
||||
|
||||
{processing ? (
|
||||
<ProgressCard
|
||||
|
||||
@@ -44,7 +44,7 @@ export function ChangeFpsSettings() {
|
||||
/>
|
||||
</div>
|
||||
|
||||
{error && <p className="text-xs text-red-500">{error}</p>}
|
||||
{error && <p className="text-xs text-destructive-ink">{error}</p>}
|
||||
|
||||
{processing ? (
|
||||
<ProgressCard
|
||||
|
||||
@@ -116,7 +116,7 @@ export function ChartMakerSettings() {
|
||||
/>
|
||||
</div>
|
||||
|
||||
{error && <p className="text-xs text-red-500">{error}</p>}
|
||||
{error && <p className="text-xs text-destructive-ink">{error}</p>}
|
||||
|
||||
{processing ? (
|
||||
<ProgressCard
|
||||
@@ -143,7 +143,7 @@ export function ChartMakerSettings() {
|
||||
href={downloadUrl}
|
||||
download
|
||||
data-testid="chart-maker-download"
|
||||
className="w-full py-2.5 rounded-lg border border-primary text-primary font-medium flex items-center justify-center gap-2 hover:bg-primary/5"
|
||||
className="w-full py-2.5 rounded-lg border border-primary text-primary-ink font-medium flex items-center justify-center gap-2 hover:bg-primary/5"
|
||||
>
|
||||
<Download className="h-4 w-4" />
|
||||
{t.common.download}
|
||||
|
||||
@@ -238,7 +238,7 @@ export function CircleCropSettings() {
|
||||
/>
|
||||
</div>
|
||||
|
||||
{error && <p className="text-xs text-red-500">{error}</p>}
|
||||
{error && <p className="text-xs text-destructive-ink">{error}</p>}
|
||||
|
||||
{processing ? (
|
||||
<ProgressCard
|
||||
@@ -268,7 +268,7 @@ export function CircleCropSettings() {
|
||||
href={downloadUrl}
|
||||
download
|
||||
data-testid="circle-crop-download"
|
||||
className="w-full py-2.5 rounded-lg border border-primary text-primary font-medium flex items-center justify-center gap-2 hover:bg-primary/5"
|
||||
className="w-full py-2.5 rounded-lg border border-primary text-primary-ink font-medium flex items-center justify-center gap-2 hover:bg-primary/5"
|
||||
>
|
||||
<Download className="h-4 w-4" />
|
||||
{t.common.download}
|
||||
|
||||
@@ -137,7 +137,7 @@ function UploadArea() {
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleClick}
|
||||
className="flex items-center gap-2 px-6 py-2.5 rounded-lg border border-primary text-primary hover:bg-primary/5 transition-colors text-sm font-medium"
|
||||
className="flex items-center gap-2 px-6 py-2.5 rounded-lg border border-primary text-primary-ink hover:bg-primary/5 transition-colors text-sm font-medium"
|
||||
>
|
||||
<Upload className="h-4 w-4" />
|
||||
Upload images for collage
|
||||
@@ -520,7 +520,7 @@ function CollageCell({
|
||||
className={cn(
|
||||
"flex items-center gap-1 rounded px-1.5 py-1 text-[11px] font-medium backdrop-blur-sm transition-colors",
|
||||
transform.objectFit === "contain"
|
||||
? "bg-primary/80 text-white hover:bg-primary/90"
|
||||
? "bg-primary/80 text-primary-foreground hover:bg-primary/90"
|
||||
: "bg-black/50 text-white hover:bg-black/70",
|
||||
)}
|
||||
title={transform.objectFit === "cover" ? "Fit entire image" : "Fill cell"}
|
||||
|
||||
@@ -361,7 +361,7 @@ export function CollageSettings() {
|
||||
</CollapsibleSection>
|
||||
|
||||
{/* Error */}
|
||||
{error && <p className="text-xs text-red-500">{error}</p>}
|
||||
{error && <p className="text-xs text-destructive-ink">{error}</p>}
|
||||
|
||||
{/* Actions */}
|
||||
<button
|
||||
@@ -380,7 +380,7 @@ export function CollageSettings() {
|
||||
href={resultUrl}
|
||||
download
|
||||
data-testid="collage-download"
|
||||
className="w-full py-2.5 rounded-lg border border-primary text-primary font-medium flex items-center justify-center gap-2 hover:bg-primary/5"
|
||||
className="w-full py-2.5 rounded-lg border border-primary text-primary-ink font-medium flex items-center justify-center gap-2 hover:bg-primary/5"
|
||||
>
|
||||
<Download className="h-4 w-4" />
|
||||
Download Collage
|
||||
|
||||
@@ -121,7 +121,7 @@ export function ColorBlindnessSettings() {
|
||||
)}
|
||||
</div>
|
||||
|
||||
{error && <p className="text-xs text-red-500">{error}</p>}
|
||||
{error && <p className="text-xs text-destructive-ink">{error}</p>}
|
||||
|
||||
{originalSize != null && processedSize != null && (
|
||||
<div className="text-xs text-muted-foreground space-y-0.5">
|
||||
@@ -158,7 +158,7 @@ export function ColorBlindnessSettings() {
|
||||
href={downloadUrl}
|
||||
download
|
||||
data-testid="color-blindness-download"
|
||||
className="w-full py-2.5 rounded-lg border border-primary text-primary font-medium flex items-center justify-center gap-2 hover:bg-primary/5"
|
||||
className="w-full py-2.5 rounded-lg border border-primary text-primary-ink font-medium flex items-center justify-center gap-2 hover:bg-primary/5"
|
||||
>
|
||||
<Download className="h-4 w-4" />
|
||||
{t.common.download}
|
||||
|
||||
@@ -130,7 +130,7 @@ export function ColorPaletteSettings() {
|
||||
{processing ? ts.extracting : ts.submit}
|
||||
</button>
|
||||
|
||||
{error && <p className="text-xs text-red-500">{error}</p>}
|
||||
{error && <p className="text-xs text-destructive-ink">{error}</p>}
|
||||
|
||||
{colors.length > 0 && (
|
||||
<div className="space-y-3">
|
||||
@@ -157,7 +157,7 @@ export function ColorPaletteSettings() {
|
||||
data-testid="color-palette-copy-css"
|
||||
>
|
||||
{copiedExport === "css" ? (
|
||||
<Check className="h-3 w-3 text-green-500" />
|
||||
<Check className="h-3 w-3 text-success-ink" />
|
||||
) : (
|
||||
<ClipboardCopy className="h-3 w-3" />
|
||||
)}
|
||||
@@ -170,7 +170,7 @@ export function ColorPaletteSettings() {
|
||||
data-testid="color-palette-copy-json"
|
||||
>
|
||||
{copiedExport === "json" ? (
|
||||
<Check className="h-3 w-3 text-green-500" />
|
||||
<Check className="h-3 w-3 text-success-ink" />
|
||||
) : (
|
||||
<ClipboardCopy className="h-3 w-3" />
|
||||
)}
|
||||
@@ -196,7 +196,7 @@ export function ColorPaletteSettings() {
|
||||
{color}
|
||||
</span>
|
||||
{copiedIdx === i ? (
|
||||
<Check className="h-3 w-3 text-green-500 shrink-0" />
|
||||
<Check className="h-3 w-3 text-success-ink shrink-0" />
|
||||
) : (
|
||||
<Copy className="h-3 w-3 text-muted-foreground shrink-0" />
|
||||
)}
|
||||
|
||||
@@ -275,7 +275,9 @@ export function ColorControls({
|
||||
>
|
||||
{channelsOpen ? <ChevronDown className="h-3 w-3" /> : <ChevronRight className="h-3 w-3" />}
|
||||
Color Channels
|
||||
{hasChannelChanges && <span className="ms-auto text-primary text-[10px]">modified</span>}
|
||||
{hasChannelChanges && (
|
||||
<span className="ms-auto text-primary-ink text-[10px]">modified</span>
|
||||
)}
|
||||
</button>
|
||||
{channelsOpen && (
|
||||
<div className="space-y-2 ps-1">
|
||||
@@ -285,7 +287,7 @@ export function ColorControls({
|
||||
onChange={setRed}
|
||||
min={0}
|
||||
max={200}
|
||||
color="text-red-500"
|
||||
color="text-destructive-ink"
|
||||
/>
|
||||
<SliderControl
|
||||
label="Green"
|
||||
@@ -293,7 +295,7 @@ export function ColorControls({
|
||||
onChange={setGreen}
|
||||
min={0}
|
||||
max={200}
|
||||
color="text-green-500"
|
||||
color="text-success-ink"
|
||||
/>
|
||||
<SliderControl
|
||||
label="Blue"
|
||||
@@ -301,7 +303,7 @@ export function ColorControls({
|
||||
onChange={setBlue}
|
||||
min={0}
|
||||
max={200}
|
||||
color="text-blue-500"
|
||||
color="text-blue-700 dark:text-blue-400"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
@@ -379,7 +381,7 @@ export function ColorSettings({ toolId, onPreviewFilter }: ColorSettingsProps) {
|
||||
<form onSubmit={handleSubmit} className="space-y-3">
|
||||
<ColorControls toolId={toolId} onChange={setSettings} onPreviewFilter={onPreviewFilter} />
|
||||
|
||||
{error && <p className="text-xs text-red-500">{error}</p>}
|
||||
{error && <p className="text-xs text-destructive-ink">{error}</p>}
|
||||
|
||||
{originalSize != null && processedSize != null && (
|
||||
<div className="text-xs text-muted-foreground space-y-0.5">
|
||||
@@ -414,7 +416,7 @@ export function ColorSettings({ toolId, onPreviewFilter }: ColorSettingsProps) {
|
||||
href={downloadUrl}
|
||||
download
|
||||
data-testid="adjust-colors-download"
|
||||
className="w-full py-2.5 rounded-lg border border-primary text-primary font-medium flex items-center justify-center gap-2 hover:bg-primary/5"
|
||||
className="w-full py-2.5 rounded-lg border border-primary text-primary-ink font-medium flex items-center justify-center gap-2 hover:bg-primary/5"
|
||||
>
|
||||
<Download className="h-4 w-4" />
|
||||
Download
|
||||
|
||||
@@ -104,7 +104,7 @@ export function ColorizeSettings() {
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{error && <p className="text-xs text-red-500">{error}</p>}
|
||||
{error && <p className="text-xs text-destructive-ink">{error}</p>}
|
||||
|
||||
{originalSize != null && processedSize != null && (
|
||||
<div className="text-xs text-muted-foreground space-y-0.5">
|
||||
@@ -140,7 +140,7 @@ export function ColorizeSettings() {
|
||||
href={downloadUrl}
|
||||
download
|
||||
data-testid="colorize-download"
|
||||
className="w-full py-2.5 rounded-lg border border-primary text-primary font-medium flex items-center justify-center gap-2 hover:bg-primary/5"
|
||||
className="w-full py-2.5 rounded-lg border border-primary text-primary-ink font-medium flex items-center justify-center gap-2 hover:bg-primary/5"
|
||||
>
|
||||
<Download className="h-4 w-4" />
|
||||
Download
|
||||
|
||||
@@ -72,7 +72,7 @@ export function CompareSettings() {
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{error && <p className="text-xs text-red-500">{error}</p>}
|
||||
{error && <p className="text-xs text-destructive-ink">{error}</p>}
|
||||
|
||||
{similarity !== null && (
|
||||
<div className="p-3 rounded-lg bg-muted">
|
||||
@@ -101,7 +101,7 @@ export function CompareSettings() {
|
||||
href={downloadUrl}
|
||||
download
|
||||
data-testid="compare-download"
|
||||
className="w-full py-2.5 rounded-lg border border-primary text-primary font-medium flex items-center justify-center gap-2 hover:bg-primary/5"
|
||||
className="w-full py-2.5 rounded-lg border border-primary text-primary-ink font-medium flex items-center justify-center gap-2 hover:bg-primary/5"
|
||||
>
|
||||
<Download className="h-4 w-4" />
|
||||
Download Diff Image
|
||||
|
||||
@@ -151,7 +151,7 @@ export function ComposeSettings() {
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{error && <p className="text-xs text-red-500">{error}</p>}
|
||||
{error && <p className="text-xs text-destructive-ink">{error}</p>}
|
||||
|
||||
{originalSize != null && processedSize != null && (
|
||||
<div className="text-xs text-muted-foreground space-y-0.5">
|
||||
@@ -176,7 +176,7 @@ export function ComposeSettings() {
|
||||
href={downloadUrl}
|
||||
download
|
||||
data-testid="compose-download"
|
||||
className="w-full py-2.5 rounded-lg border border-primary text-primary font-medium flex items-center justify-center gap-2 hover:bg-primary/5"
|
||||
className="w-full py-2.5 rounded-lg border border-primary text-primary-ink font-medium flex items-center justify-center gap-2 hover:bg-primary/5"
|
||||
>
|
||||
<Download className="h-4 w-4" />
|
||||
Download
|
||||
|
||||
@@ -51,15 +51,15 @@ export function CompressPdfSettings() {
|
||||
<p className="text-[11px] text-muted-foreground">{s.bestEffortHint}</p>
|
||||
)}
|
||||
|
||||
{error && <p className="text-xs text-red-500">{error}</p>}
|
||||
{error && <p className="text-xs text-destructive-ink">{error}</p>}
|
||||
|
||||
{targetMet === false && (
|
||||
<p className="text-xs text-amber-600">
|
||||
<p className="text-xs text-amber-700 dark:text-amber-400">
|
||||
{format(s.targetMissed, { target: targetLabel, size: achievedLabel })}
|
||||
</p>
|
||||
)}
|
||||
{targetMet === true && (
|
||||
<p className="text-xs text-green-600">
|
||||
<p className="text-xs text-success-ink">
|
||||
{format(s.targetReached, { target: targetLabel, size: achievedLabel })}
|
||||
</p>
|
||||
)}
|
||||
|
||||
@@ -206,7 +206,7 @@ export function CompressSettings() {
|
||||
<CompressControls onChange={setSettings} />
|
||||
|
||||
{/* Error */}
|
||||
{error && <p className="text-xs text-red-500">{error}</p>}
|
||||
{error && <p className="text-xs text-destructive-ink">{error}</p>}
|
||||
|
||||
{/* Size info */}
|
||||
{originalSize != null && processedSize != null && (
|
||||
@@ -255,7 +255,7 @@ export function CompressSettings() {
|
||||
href={downloadUrl}
|
||||
download
|
||||
data-testid="compress-download"
|
||||
className="w-full py-2.5 rounded-lg border border-primary text-primary font-medium flex items-center justify-center gap-2 hover:bg-primary/5"
|
||||
className="w-full py-2.5 rounded-lg border border-primary text-primary-ink font-medium flex items-center justify-center gap-2 hover:bg-primary/5"
|
||||
>
|
||||
<Download className="h-4 w-4" />
|
||||
{t.common.download}
|
||||
|
||||
@@ -65,7 +65,7 @@ export function CompressVideoSettings() {
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{error && <p className="text-xs text-red-500">{error}</p>}
|
||||
{error && <p className="text-xs text-destructive-ink">{error}</p>}
|
||||
|
||||
{processing ? (
|
||||
<ProgressCard
|
||||
|
||||
@@ -197,7 +197,7 @@ export function ContentAwareResizeSettings() {
|
||||
<form onSubmit={handleSubmit} className="space-y-4">
|
||||
<ContentAwareResizeControls onChange={handleSettingsChange} />
|
||||
|
||||
{error && <p className="text-xs text-red-500">{error}</p>}
|
||||
{error && <p className="text-xs text-destructive-ink">{error}</p>}
|
||||
|
||||
{processing ? (
|
||||
<ProgressCard
|
||||
@@ -226,7 +226,7 @@ export function ContentAwareResizeSettings() {
|
||||
href={downloadUrl}
|
||||
download
|
||||
data-testid="content-aware-resize-download"
|
||||
className="w-full py-2.5 rounded-lg border border-primary text-primary font-medium flex items-center justify-center gap-2 hover:bg-primary/5"
|
||||
className="w-full py-2.5 rounded-lg border border-primary text-primary-ink font-medium flex items-center justify-center gap-2 hover:bg-primary/5"
|
||||
>
|
||||
<Download className="h-4 w-4" />
|
||||
Download
|
||||
|
||||
@@ -130,7 +130,7 @@ export function ConversionPresetSettings() {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{error && <p className="text-xs text-red-500">{error}</p>}
|
||||
{error && <p className="text-xs text-destructive-ink">{error}</p>}
|
||||
|
||||
{processing ? (
|
||||
<ProgressCard
|
||||
@@ -159,7 +159,7 @@ export function ConversionPresetSettings() {
|
||||
href={downloadUrl}
|
||||
download
|
||||
data-testid="preset-download"
|
||||
className="w-full py-2.5 rounded-lg border border-primary text-primary font-medium flex items-center justify-center gap-2 hover:bg-primary/5"
|
||||
className="w-full py-2.5 rounded-lg border border-primary text-primary-ink font-medium flex items-center justify-center gap-2 hover:bg-primary/5"
|
||||
>
|
||||
<Download className="h-4 w-4" />
|
||||
{t.common.download}
|
||||
|
||||
@@ -128,7 +128,7 @@ export function ConvertAudioSettings() {
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{error && <p className="text-xs text-red-500">{error}</p>}
|
||||
{error && <p className="text-xs text-destructive-ink">{error}</p>}
|
||||
|
||||
{processing ? (
|
||||
<ProgressCard
|
||||
|
||||
@@ -64,7 +64,7 @@ export function ConvertDocumentSettings() {
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{error && <p className="text-xs text-red-500">{error}</p>}
|
||||
{error && <p className="text-xs text-destructive-ink">{error}</p>}
|
||||
|
||||
{processing ? (
|
||||
<ProgressCard
|
||||
|
||||
@@ -62,7 +62,7 @@ export function ConvertPresentationSettings() {
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{error && <p className="text-xs text-red-500">{error}</p>}
|
||||
{error && <p className="text-xs text-destructive-ink">{error}</p>}
|
||||
|
||||
{processing ? (
|
||||
<ProgressCard
|
||||
|
||||
@@ -174,7 +174,7 @@ export function ConvertSettings() {
|
||||
<ConvertControls onChange={setSettings} />
|
||||
|
||||
{/* Error */}
|
||||
{error && <p className="text-xs text-red-500">{error}</p>}
|
||||
{error && <p className="text-xs text-destructive-ink">{error}</p>}
|
||||
|
||||
{/* Size info */}
|
||||
{originalSize != null && processedSize != null && (
|
||||
@@ -213,7 +213,7 @@ export function ConvertSettings() {
|
||||
href={downloadUrl}
|
||||
download
|
||||
data-testid="convert-download"
|
||||
className="w-full py-2.5 rounded-lg border border-primary text-primary font-medium flex items-center justify-center gap-2 hover:bg-primary/5"
|
||||
className="w-full py-2.5 rounded-lg border border-primary text-primary-ink font-medium flex items-center justify-center gap-2 hover:bg-primary/5"
|
||||
>
|
||||
<Download className="h-4 w-4" />
|
||||
{t.common.download}
|
||||
|
||||
@@ -65,7 +65,7 @@ export function ConvertSpreadsheetSettings() {
|
||||
|
||||
<p className="text-[10px] text-muted-foreground">{s.hint}</p>
|
||||
|
||||
{error && <p className="text-xs text-red-500">{error}</p>}
|
||||
{error && <p className="text-xs text-destructive-ink">{error}</p>}
|
||||
|
||||
{processing ? (
|
||||
<ProgressCard
|
||||
|
||||
@@ -64,7 +64,7 @@ export function ConvertVideoSettings() {
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{error && <p className="text-xs text-red-500">{error}</p>}
|
||||
{error && <p className="text-xs text-destructive-ink">{error}</p>}
|
||||
|
||||
{processing ? (
|
||||
<ProgressCard
|
||||
|
||||
@@ -17,7 +17,7 @@ export function CreateZipSettings() {
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
{error && <p className="text-xs text-red-500">{error}</p>}
|
||||
{error && <p className="text-xs text-destructive-ink">{error}</p>}
|
||||
|
||||
{processing ? (
|
||||
<ProgressCard
|
||||
|
||||
@@ -44,7 +44,7 @@ export function CropPdfSettings() {
|
||||
/>
|
||||
</div>
|
||||
|
||||
{error && <p className="text-xs text-red-500">{error}</p>}
|
||||
{error && <p className="text-xs text-destructive-ink">{error}</p>}
|
||||
|
||||
{processing ? (
|
||||
<ProgressCard
|
||||
|
||||
@@ -365,7 +365,7 @@ export function CropSettings({
|
||||
</label>
|
||||
|
||||
{/* Error */}
|
||||
{error && <p className="text-xs text-red-500">{error}</p>}
|
||||
{error && <p className="text-xs text-destructive-ink">{error}</p>}
|
||||
|
||||
{/* Process */}
|
||||
{processing ? (
|
||||
@@ -396,7 +396,7 @@ export function CropSettings({
|
||||
href={downloadUrl}
|
||||
download
|
||||
data-testid="crop-download"
|
||||
className="w-full py-2.5 rounded-lg border border-primary text-primary font-medium flex items-center justify-center gap-2 hover:bg-primary/5"
|
||||
className="w-full py-2.5 rounded-lg border border-primary text-primary-ink font-medium flex items-center justify-center gap-2 hover:bg-primary/5"
|
||||
>
|
||||
<Download className="h-4 w-4" />
|
||||
{t.common.download}
|
||||
|
||||
@@ -88,7 +88,7 @@ export function CropVideoSettings() {
|
||||
/>
|
||||
</div>
|
||||
|
||||
{error && <p className="text-xs text-red-500">{error}</p>}
|
||||
{error && <p className="text-xs text-destructive-ink">{error}</p>}
|
||||
|
||||
{processing ? (
|
||||
<ProgressCard
|
||||
|
||||
@@ -48,7 +48,7 @@ export function CsvExcelSettings() {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{error && <p className="text-xs text-red-500">{error}</p>}
|
||||
{error && <p className="text-xs text-destructive-ink">{error}</p>}
|
||||
|
||||
{processing ? (
|
||||
<ProgressCard
|
||||
|
||||
@@ -38,7 +38,7 @@ export function CsvJsonSettings() {
|
||||
{s.pretty}
|
||||
</label>
|
||||
|
||||
{error && <p className="text-xs text-red-500">{error}</p>}
|
||||
{error && <p className="text-xs text-destructive-ink">{error}</p>}
|
||||
|
||||
{processing ? (
|
||||
<ProgressCard
|
||||
|
||||
@@ -185,7 +185,7 @@ export function DuotoneSettings({ onImageStyle, onImageOverlay }: DuotoneSetting
|
||||
/>
|
||||
</div>
|
||||
|
||||
{error && <p className="text-xs text-red-500">{error}</p>}
|
||||
{error && <p className="text-xs text-destructive-ink">{error}</p>}
|
||||
|
||||
{processing ? (
|
||||
<ProgressCard
|
||||
@@ -214,7 +214,7 @@ export function DuotoneSettings({ onImageStyle, onImageOverlay }: DuotoneSetting
|
||||
href={downloadUrl}
|
||||
download
|
||||
data-testid="duotone-download"
|
||||
className="w-full py-2.5 rounded-lg border border-primary text-primary font-medium flex items-center justify-center gap-2 hover:bg-primary/5"
|
||||
className="w-full py-2.5 rounded-lg border border-primary text-primary-ink font-medium flex items-center justify-center gap-2 hover:bg-primary/5"
|
||||
>
|
||||
<Download className="h-4 w-4" />
|
||||
{t.common.download}
|
||||
|
||||
@@ -587,9 +587,9 @@ export function EditMetadataSettings() {
|
||||
<div className="space-y-2.5">
|
||||
{gpsCoords && (
|
||||
<div className="flex items-start gap-2 px-2.5 py-2 rounded-md bg-amber-500/10 border border-amber-500/20">
|
||||
<MapPin className="h-3.5 w-3.5 text-amber-500 shrink-0 mt-0.5" />
|
||||
<MapPin className="h-3.5 w-3.5 text-amber-700 dark:text-amber-400 shrink-0 mt-0.5" />
|
||||
<div className="flex-1 min-w-0">
|
||||
<p className="text-[11px] text-amber-600 dark:text-amber-400 font-medium">
|
||||
<p className="text-[11px] text-amber-700 dark:text-amber-400 font-medium">
|
||||
Location data found
|
||||
</p>
|
||||
<p className="text-[10px] text-muted-foreground font-mono">
|
||||
@@ -659,13 +659,13 @@ export function EditMetadataSettings() {
|
||||
{form.keywords.map((kw) => (
|
||||
<span
|
||||
key={kw}
|
||||
className="inline-flex items-center gap-1 px-2 py-0.5 rounded-full bg-primary/10 text-primary text-[11px]"
|
||||
className="inline-flex items-center gap-1 px-2 py-0.5 rounded-full bg-primary/10 text-primary-ink text-[11px]"
|
||||
>
|
||||
{kw}
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => removeKeyword(kw)}
|
||||
className="hover:text-red-500"
|
||||
className="hover:text-destructive-ink"
|
||||
>
|
||||
<X className="h-2.5 w-2.5" />
|
||||
</button>
|
||||
@@ -783,7 +783,7 @@ export function EditMetadataSettings() {
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => deleteTemplate(t.name)}
|
||||
className="p-1 text-muted-foreground hover:text-red-500"
|
||||
className="p-1 text-muted-foreground hover:text-destructive-ink"
|
||||
>
|
||||
<X className="h-3 w-3" />
|
||||
</button>
|
||||
@@ -820,7 +820,7 @@ export function EditMetadataSettings() {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{error && <p className="text-xs text-red-500">{error}</p>}
|
||||
{error && <p className="text-xs text-destructive-ink">{error}</p>}
|
||||
|
||||
{/* Changes summary */}
|
||||
{hasFile && changes.total > 0 && !processing && (
|
||||
@@ -866,7 +866,7 @@ export function EditMetadataSettings() {
|
||||
href={downloadUrl}
|
||||
download
|
||||
data-testid="edit-metadata-download"
|
||||
className="w-full py-2.5 rounded-lg border border-primary text-primary font-medium flex items-center justify-center gap-2 hover:bg-primary/5"
|
||||
className="w-full py-2.5 rounded-lg border border-primary text-primary-ink font-medium flex items-center justify-center gap-2 hover:bg-primary/5"
|
||||
>
|
||||
<Download className="h-4 w-4" />
|
||||
Download
|
||||
|
||||
@@ -35,7 +35,7 @@ export function EmbedSubtitlesSettings() {
|
||||
<p className="text-[10px] text-muted-foreground mt-0.5">{s.languageHint}</p>
|
||||
</div>
|
||||
|
||||
{error && <p className="text-xs text-red-500">{error}</p>}
|
||||
{error && <p className="text-xs text-destructive-ink">{error}</p>}
|
||||
|
||||
{processing ? (
|
||||
<ProgressCard
|
||||
|
||||
@@ -172,7 +172,7 @@ export function EnhanceFacesSettings() {
|
||||
<EnhanceFacesControls onChange={setSettings} />
|
||||
|
||||
{/* Error */}
|
||||
{error && <p className="text-xs text-red-500">{error}</p>}
|
||||
{error && <p className="text-xs text-destructive-ink">{error}</p>}
|
||||
|
||||
{/* Size info */}
|
||||
{originalSize != null && processedSize != null && (
|
||||
@@ -209,7 +209,7 @@ export function EnhanceFacesSettings() {
|
||||
href={downloadUrl}
|
||||
download
|
||||
data-testid="enhance-faces-download"
|
||||
className="w-full py-2.5 rounded-lg border border-primary text-primary font-medium flex items-center justify-center gap-2 hover:bg-primary/5"
|
||||
className="w-full py-2.5 rounded-lg border border-primary text-primary-ink font-medium flex items-center justify-center gap-2 hover:bg-primary/5"
|
||||
>
|
||||
<Download className="h-4 w-4" />
|
||||
Download
|
||||
|
||||
@@ -49,7 +49,7 @@ export function EpubConvertSettings() {
|
||||
|
||||
<p className="text-[10px] text-muted-foreground">{s.hint}</p>
|
||||
|
||||
{error && <p className="text-xs text-red-500">{error}</p>}
|
||||
{error && <p className="text-xs text-destructive-ink">{error}</p>}
|
||||
|
||||
{processing ? (
|
||||
<ProgressCard
|
||||
|
||||
@@ -580,7 +580,7 @@ export function EraseObjectSettings({
|
||||
)}
|
||||
|
||||
{/* Error */}
|
||||
{error && <p className="text-xs text-red-500">{error}</p>}
|
||||
{error && <p className="text-xs text-destructive-ink">{error}</p>}
|
||||
|
||||
{/* Size info */}
|
||||
{currentEntry?.originalSize != null &&
|
||||
@@ -621,7 +621,7 @@ export function EraseObjectSettings({
|
||||
href={currentEntry.processedUrl}
|
||||
download
|
||||
data-testid="erase-object-download"
|
||||
className="w-full py-2.5 rounded-lg border border-primary text-primary font-medium flex items-center justify-center gap-2 hover:bg-primary/5"
|
||||
className="w-full py-2.5 rounded-lg border border-primary text-primary-ink font-medium flex items-center justify-center gap-2 hover:bg-primary/5"
|
||||
>
|
||||
<Download className="h-4 w-4" />
|
||||
Download
|
||||
|
||||
@@ -25,7 +25,7 @@ export function ExcelToPdfSettings() {
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
{error && <p className="text-xs text-red-500">{error}</p>}
|
||||
{error && <p className="text-xs text-destructive-ink">{error}</p>}
|
||||
|
||||
{processing ? (
|
||||
<ProgressCard
|
||||
|
||||
@@ -46,7 +46,7 @@ export function ExtractAudioSettings() {
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{error && <p className="text-xs text-red-500">{error}</p>}
|
||||
{error && <p className="text-xs text-destructive-ink">{error}</p>}
|
||||
|
||||
{processing ? (
|
||||
<ProgressCard
|
||||
|
||||
@@ -42,7 +42,7 @@ export function ExtractPagesSettings() {
|
||||
<p className="text-[10px] text-muted-foreground mt-0.5">{s.rangeHint}</p>
|
||||
</div>
|
||||
|
||||
{error && <p className="text-xs text-red-500">{error}</p>}
|
||||
{error && <p className="text-xs text-destructive-ink">{error}</p>}
|
||||
|
||||
{processing ? (
|
||||
<ProgressCard
|
||||
|
||||
@@ -25,7 +25,7 @@ export function ExtractSubtitlesSettings() {
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
{error && <p className="text-xs text-red-500">{error}</p>}
|
||||
{error && <p className="text-xs text-destructive-ink">{error}</p>}
|
||||
|
||||
{processing ? (
|
||||
<ProgressCard
|
||||
|
||||
@@ -25,7 +25,7 @@ export function ExtractZipSettings() {
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
{error && <p className="text-xs text-red-500">{error}</p>}
|
||||
{error && <p className="text-xs text-destructive-ink">{error}</p>}
|
||||
|
||||
{processing ? (
|
||||
<ProgressCard
|
||||
|
||||
@@ -61,7 +61,7 @@ export function FadeAudioSettings() {
|
||||
/>
|
||||
</div>
|
||||
|
||||
{error && <p className="text-xs text-red-500">{error}</p>}
|
||||
{error && <p className="text-xs text-destructive-ink">{error}</p>}
|
||||
|
||||
{processing ? (
|
||||
<ProgressCard
|
||||
|
||||
@@ -351,7 +351,7 @@ export function FaviconSettings() {
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{error && <p className="text-xs text-red-500">{error}</p>}
|
||||
{error && <p className="text-xs text-destructive-ink">{error}</p>}
|
||||
|
||||
{busy ? (
|
||||
<ProgressCard
|
||||
@@ -385,7 +385,7 @@ export function FaviconSettings() {
|
||||
href={downloadUrl}
|
||||
download="favicons.zip"
|
||||
data-testid="favicon-download"
|
||||
className="w-full py-2.5 rounded-lg border border-primary text-primary font-medium flex items-center justify-center gap-2 hover:bg-primary/5"
|
||||
className="w-full py-2.5 rounded-lg border border-primary text-primary-ink font-medium flex items-center justify-center gap-2 hover:bg-primary/5"
|
||||
>
|
||||
<Download className="h-4 w-4" />
|
||||
Download Favicons ZIP
|
||||
|
||||
@@ -27,19 +27,21 @@ function OverviewGrid() {
|
||||
<div className="w-px h-4 bg-border" />
|
||||
<div className="flex items-center gap-1.5">
|
||||
<span className="text-muted-foreground">Duplicate groups:</span>
|
||||
<span className="text-yellow-500 font-semibold">{results.duplicateGroups.length}</span>
|
||||
<span className="text-amber-700 dark:text-amber-400 font-semibold">
|
||||
{results.duplicateGroups.length}
|
||||
</span>
|
||||
</div>
|
||||
<div className="w-px h-4 bg-border" />
|
||||
<div className="flex items-center gap-1.5">
|
||||
<span className="text-muted-foreground">Unique:</span>
|
||||
<span className="text-green-500 font-semibold">{results.uniqueImages}</span>
|
||||
<span className="text-success-ink font-semibold">{results.uniqueImages}</span>
|
||||
</div>
|
||||
{results.spaceSaveable > 0 && (
|
||||
<>
|
||||
<div className="w-px h-4 bg-border" />
|
||||
<div className="flex items-center gap-1.5">
|
||||
<span className="text-muted-foreground">Space saveable:</span>
|
||||
<span className="text-primary font-semibold">
|
||||
<span className="text-primary-ink font-semibold">
|
||||
{formatFileSize(results.spaceSaveable)}
|
||||
</span>
|
||||
</div>
|
||||
@@ -50,7 +52,7 @@ function OverviewGrid() {
|
||||
{results.duplicateGroups.length === 0 ? (
|
||||
<div className="flex flex-col items-center justify-center py-16 gap-3">
|
||||
<div className="w-12 h-12 rounded-full bg-green-100 dark:bg-green-900/30 flex items-center justify-center">
|
||||
<Search className="h-6 w-6 text-green-600 dark:text-green-400" />
|
||||
<Search className="h-6 w-6 text-success-ink" />
|
||||
</div>
|
||||
<p className="text-sm font-medium text-foreground">No duplicates found</p>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
@@ -147,7 +149,7 @@ function DetailComparison() {
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setViewMode("overview")}
|
||||
className="flex items-center gap-1 text-xs text-primary hover:text-primary/80"
|
||||
className="flex items-center gap-1 text-xs text-primary-ink hover:text-primary-ink-strong"
|
||||
>
|
||||
<ArrowLeft className="h-3.5 w-3.5" />
|
||||
Back to Overview
|
||||
@@ -173,7 +175,7 @@ function DetailComparison() {
|
||||
<ChevronRight className="h-3.5 w-3.5" />
|
||||
</button>
|
||||
</div>
|
||||
<span className="text-xs font-medium text-yellow-500">
|
||||
<span className="text-xs font-medium text-amber-700 dark:text-amber-400">
|
||||
{Math.max(...group.files.map((f) => f.similarity))}% Similar
|
||||
</span>
|
||||
</div>
|
||||
@@ -228,7 +230,7 @@ function DetailComparison() {
|
||||
<span className="text-foreground text-end">{file.format.toUpperCase()}</span>
|
||||
<span className="text-muted-foreground">Similarity</span>
|
||||
<span
|
||||
className={`text-end font-medium ${file.similarity === 100 ? "text-green-500" : "text-yellow-500"}`}
|
||||
className={`text-end font-medium ${file.similarity === 100 ? "text-success-ink" : "text-amber-700 dark:text-amber-400"}`}
|
||||
>
|
||||
{file.similarity}%
|
||||
</span>
|
||||
|
||||
@@ -276,7 +276,7 @@ export function FindDuplicatesSettings() {
|
||||
|
||||
{activeDesc && <p className="text-[10px] text-muted-foreground">{activeDesc}</p>}
|
||||
|
||||
{error && <p className="text-xs text-red-500">{error}</p>}
|
||||
{error && <p className="text-xs text-destructive-ink">{error}</p>}
|
||||
|
||||
{/* Scan button + progress */}
|
||||
{!results && (
|
||||
@@ -317,16 +317,18 @@ export function FindDuplicatesSettings() {
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span className="text-muted-foreground">Duplicate groups</span>
|
||||
<span className="text-yellow-500 font-medium">{results.duplicateGroups.length}</span>
|
||||
<span className="text-amber-700 dark:text-amber-400 font-medium">
|
||||
{results.duplicateGroups.length}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span className="text-muted-foreground">Unique images</span>
|
||||
<span className="text-green-500 font-medium">{results.uniqueImages}</span>
|
||||
<span className="text-success-ink font-medium">{results.uniqueImages}</span>
|
||||
</div>
|
||||
{results.spaceSaveable > 0 && (
|
||||
<div className="flex justify-between">
|
||||
<span className="text-muted-foreground">Space saveable</span>
|
||||
<span className="text-primary font-medium">
|
||||
<span className="text-primary-ink font-medium">
|
||||
{formatFileSize(results.spaceSaveable)}
|
||||
</span>
|
||||
</div>
|
||||
@@ -334,14 +336,16 @@ export function FindDuplicatesSettings() {
|
||||
{results.skippedFiles && results.skippedFiles.length > 0 && (
|
||||
<div className="flex justify-between">
|
||||
<span className="text-muted-foreground">Skipped</span>
|
||||
<span className="text-orange-500 font-medium">{results.skippedFiles.length}</span>
|
||||
<span className="text-amber-700 dark:text-amber-400 font-medium">
|
||||
{results.skippedFiles.length}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{results.skippedFiles && results.skippedFiles.length > 0 && (
|
||||
<details className="text-xs">
|
||||
<summary className="text-orange-500 cursor-pointer">
|
||||
<summary className="text-amber-700 dark:text-amber-400 cursor-pointer">
|
||||
{results.skippedFiles.length} file{results.skippedFiles.length > 1 ? "s" : ""} could
|
||||
not be analyzed
|
||||
</summary>
|
||||
@@ -369,7 +373,7 @@ export function FindDuplicatesSettings() {
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleDownloadUnique}
|
||||
className="w-full py-2.5 rounded-lg border border-primary text-primary font-medium flex items-center justify-center gap-2 hover:bg-primary/5"
|
||||
className="w-full py-2.5 rounded-lg border border-primary text-primary-ink font-medium flex items-center justify-center gap-2 hover:bg-primary/5"
|
||||
>
|
||||
<Download className="h-4 w-4" />
|
||||
Download Unique Only
|
||||
|
||||
@@ -25,7 +25,7 @@ export function FlattenPdfSettings() {
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
{error && <p className="text-xs text-red-500">{error}</p>}
|
||||
{error && <p className="text-xs text-destructive-ink">{error}</p>}
|
||||
|
||||
{processing ? (
|
||||
<ProgressCard
|
||||
|
||||
@@ -45,7 +45,7 @@ export function GifToVideoSettings() {
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{error && <p className="text-xs text-red-500">{error}</p>}
|
||||
{error && <p className="text-xs text-destructive-ink">{error}</p>}
|
||||
|
||||
{processing ? (
|
||||
<ProgressCard
|
||||
|
||||
@@ -194,7 +194,7 @@ export function GifToolsControls({ settings: initialSettings, onChange }: GifToo
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<span className="text-amber-500 font-medium">Static image</span>
|
||||
<span className="text-amber-700 dark:text-amber-400 font-medium">Static image</span>
|
||||
<span className="text-border">|</span>
|
||||
<span>
|
||||
{info.width}x{info.height}
|
||||
@@ -673,7 +673,7 @@ export function GifToolsSettings() {
|
||||
<div className="space-y-4">
|
||||
<GifToolsControls onChange={setSettings} />
|
||||
|
||||
{error && <p className="text-xs text-red-500">{error}</p>}
|
||||
{error && <p className="text-xs text-destructive-ink">{error}</p>}
|
||||
|
||||
{originalSize != null && processedSize != null && (
|
||||
<div className="text-xs text-muted-foreground space-y-0.5">
|
||||
@@ -717,7 +717,7 @@ export function GifToolsSettings() {
|
||||
href={downloadUrl}
|
||||
download
|
||||
data-testid="gif-tools-download"
|
||||
className="w-full py-2.5 rounded-lg border border-primary text-primary font-medium flex items-center justify-center gap-2 hover:bg-primary/5"
|
||||
className="w-full py-2.5 rounded-lg border border-primary text-primary-ink font-medium flex items-center justify-center gap-2 hover:bg-primary/5"
|
||||
>
|
||||
<Download className="h-4 w-4" />
|
||||
Download
|
||||
|
||||
@@ -106,7 +106,7 @@ export function GifWebpSettings() {
|
||||
/>
|
||||
</div>
|
||||
|
||||
{error && <p className="text-xs text-red-500">{error}</p>}
|
||||
{error && <p className="text-xs text-destructive-ink">{error}</p>}
|
||||
|
||||
{processing ? (
|
||||
<ProgressCard
|
||||
@@ -136,7 +136,7 @@ export function GifWebpSettings() {
|
||||
href={downloadUrl}
|
||||
download
|
||||
data-testid="gif-webp-download"
|
||||
className="w-full py-2.5 rounded-lg border border-primary text-primary font-medium flex items-center justify-center gap-2 hover:bg-primary/5"
|
||||
className="w-full py-2.5 rounded-lg border border-primary text-primary-ink font-medium flex items-center justify-center gap-2 hover:bg-primary/5"
|
||||
>
|
||||
<Download className="h-4 w-4" />
|
||||
{t.common.download}
|
||||
|
||||
@@ -25,7 +25,7 @@ export function GrayscalePdfSettings() {
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
{error && <p className="text-xs text-red-500">{error}</p>}
|
||||
{error && <p className="text-xs text-destructive-ink">{error}</p>}
|
||||
|
||||
{processing ? (
|
||||
<ProgressCard
|
||||
|
||||
@@ -91,7 +91,7 @@ export function HistogramSettings() {
|
||||
Generates an RGB histogram chart showing the color distribution of the image.
|
||||
</p>
|
||||
|
||||
{error && <p className="text-xs text-red-500">{error}</p>}
|
||||
{error && <p className="text-xs text-destructive-ink">{error}</p>}
|
||||
|
||||
{processing ? (
|
||||
<ProgressCard
|
||||
@@ -199,7 +199,7 @@ export function HistogramSettings() {
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
<p className="col-span-2 text-[9px] text-muted-foreground/60 mt-0.5">
|
||||
<p className="col-span-2 text-[9px] text-muted-foreground mt-0.5">
|
||||
mean / median / stdev
|
||||
</p>
|
||||
</div>
|
||||
@@ -212,7 +212,7 @@ export function HistogramSettings() {
|
||||
href={downloadUrl}
|
||||
download
|
||||
data-testid="histogram-download"
|
||||
className="w-full py-2.5 rounded-lg border border-primary text-primary font-medium flex items-center justify-center gap-2 hover:bg-primary/5"
|
||||
className="w-full py-2.5 rounded-lg border border-primary text-primary-ink font-medium flex items-center justify-center gap-2 hover:bg-primary/5"
|
||||
>
|
||||
<Download className="h-4 w-4" />
|
||||
{t.common.download}
|
||||
|
||||
@@ -27,7 +27,7 @@ export function HtmlToPdfSettings() {
|
||||
<div className="space-y-4">
|
||||
<p className="text-[10px] text-muted-foreground">{s.hint}</p>
|
||||
|
||||
{error && <p className="text-xs text-red-500">{error}</p>}
|
||||
{error && <p className="text-xs text-destructive-ink">{error}</p>}
|
||||
|
||||
{processing ? (
|
||||
<ProgressCard
|
||||
|
||||
@@ -380,7 +380,7 @@ export function ImageEnhancementControls({
|
||||
onClick={() => toggleKey && toggleCorrection(toggleKey)}
|
||||
className={`inline-flex items-center gap-1 text-[11px] px-2 py-1 rounded-full transition-colors ${
|
||||
isEnabled
|
||||
? "bg-amber-500/15 text-amber-600 dark:text-amber-400"
|
||||
? "bg-amber-500/15 text-amber-700 dark:text-amber-400"
|
||||
: "bg-muted text-muted-foreground line-through"
|
||||
}`}
|
||||
>
|
||||
@@ -462,7 +462,7 @@ export function ImageEnhancementSettings({
|
||||
<form onSubmit={handleSubmit} className="space-y-3">
|
||||
<ImageEnhancementControls onChange={setSettings} onPreviewFilter={onPreviewFilter} />
|
||||
|
||||
{error && <p className="text-xs text-red-500">{error}</p>}
|
||||
{error && <p className="text-xs text-destructive-ink">{error}</p>}
|
||||
|
||||
{originalSize != null && processedSize != null && (
|
||||
<div className="text-xs text-muted-foreground space-y-0.5">
|
||||
@@ -499,7 +499,7 @@ export function ImageEnhancementSettings({
|
||||
href={downloadUrl}
|
||||
download
|
||||
data-testid="image-enhancement-download"
|
||||
className="w-full py-2.5 rounded-lg border border-primary text-primary font-medium flex items-center justify-center gap-2 hover:bg-primary/5"
|
||||
className="w-full py-2.5 rounded-lg border border-primary text-primary-ink font-medium flex items-center justify-center gap-2 hover:bg-primary/5"
|
||||
>
|
||||
<Download className="h-4 w-4" />
|
||||
Download
|
||||
|
||||
@@ -270,7 +270,7 @@ export function ImagePadSettings({ onImageStyle, onImageOverlay }: ImagePadSetti
|
||||
/>
|
||||
</div>
|
||||
|
||||
{error && <p className="text-xs text-red-500">{error}</p>}
|
||||
{error && <p className="text-xs text-destructive-ink">{error}</p>}
|
||||
|
||||
{processing ? (
|
||||
<ProgressCard
|
||||
@@ -299,7 +299,7 @@ export function ImagePadSettings({ onImageStyle, onImageOverlay }: ImagePadSetti
|
||||
href={downloadUrl}
|
||||
download
|
||||
data-testid="image-pad-download"
|
||||
className="w-full py-2.5 rounded-lg border border-primary text-primary font-medium flex items-center justify-center gap-2 hover:bg-primary/5"
|
||||
className="w-full py-2.5 rounded-lg border border-primary text-primary-ink font-medium flex items-center justify-center gap-2 hover:bg-primary/5"
|
||||
>
|
||||
<Download className="h-4 w-4" />
|
||||
{t.common.download}
|
||||
|
||||
@@ -117,7 +117,9 @@ function FileResult({ result }: { result: Base64Result }) {
|
||||
<p className="text-[11px] text-muted-foreground">
|
||||
{result.width}x{result.height} · {formatBytes(result.originalSize)} →{" "}
|
||||
{formatBytes(result.encodedSize)}{" "}
|
||||
<span className={result.overheadPercent > 50 ? "text-amber-500" : ""}>
|
||||
<span
|
||||
className={result.overheadPercent > 50 ? "text-amber-700 dark:text-amber-400" : ""}
|
||||
>
|
||||
(+{result.overheadPercent}%)
|
||||
</span>
|
||||
</p>
|
||||
@@ -133,7 +135,7 @@ function FileResult({ result }: { result: Base64Result }) {
|
||||
onClick={() => setActiveTab(t.id)}
|
||||
className={`px-3 py-1.5 text-xs whitespace-nowrap transition-colors ${
|
||||
activeTab === t.id
|
||||
? "text-primary border-b-2 border-primary font-medium"
|
||||
? "text-primary-ink border-b-2 border-primary font-medium"
|
||||
: "text-muted-foreground hover:text-foreground"
|
||||
}`}
|
||||
>
|
||||
@@ -308,8 +310,10 @@ export function ImageToBase64Results() {
|
||||
) : currentError ? (
|
||||
<div className="flex items-center justify-center h-full">
|
||||
<div className="text-center">
|
||||
<p className="text-sm text-red-500 font-medium mb-1">{currentError.filename}</p>
|
||||
<p className="text-xs text-red-500/80">{currentError.error}</p>
|
||||
<p className="text-sm text-destructive-ink font-medium mb-1">
|
||||
{currentError.filename}
|
||||
</p>
|
||||
<p className="text-xs text-destructive-ink">{currentError.error}</p>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
|
||||
@@ -173,7 +173,7 @@ export function ImageToBase64Settings() {
|
||||
: `Convert to Base64${files.length > 1 ? ` (${files.length})` : ""}`}
|
||||
</button>
|
||||
|
||||
{error && <p className="text-xs text-red-500">{error}</p>}
|
||||
{error && <p className="text-xs text-destructive-ink">{error}</p>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -97,7 +97,7 @@ function PdfPagePreview({
|
||||
{/* Empty state placeholder */}
|
||||
{!imgUrl && (
|
||||
<div className="absolute inset-0 flex items-center justify-center">
|
||||
<span className="text-[10px] text-muted-foreground/40">No image</span>
|
||||
<span className="text-[10px] text-muted-foreground">No image</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@@ -365,7 +365,7 @@ export function ImageToPdfSettings() {
|
||||
imgUrl={entries[selectedIndex]?.blobUrl ?? entries[0]?.blobUrl ?? null}
|
||||
/>
|
||||
|
||||
{error && <p className="text-xs text-red-500">{error}</p>}
|
||||
{error && <p className="text-xs text-destructive-ink">{error}</p>}
|
||||
|
||||
{busy ? (
|
||||
<ProgressCard
|
||||
@@ -397,7 +397,7 @@ export function ImageToPdfSettings() {
|
||||
href={downloadUrl}
|
||||
download
|
||||
data-testid="image-to-pdf-download"
|
||||
className="w-full py-2.5 rounded-lg border border-primary text-primary font-medium flex items-center justify-center gap-2 hover:bg-primary/5"
|
||||
className="w-full py-2.5 rounded-lg border border-primary text-primary-ink font-medium flex items-center justify-center gap-2 hover:bg-primary/5"
|
||||
>
|
||||
<Download className="h-4 w-4" />
|
||||
{collate ? "Download PDF" : "Download ZIP"}
|
||||
@@ -409,8 +409,8 @@ export function ImageToPdfSettings() {
|
||||
data-testid="image-to-pdf-compression-result"
|
||||
className={`text-xs px-3 py-2 rounded ${
|
||||
compressionResult.targetMet
|
||||
? "bg-green-500/10 text-green-600"
|
||||
: "bg-amber-500/10 text-amber-600"
|
||||
? "bg-green-500/10 text-success-ink"
|
||||
: "bg-amber-500/10 text-amber-700 dark:text-amber-400"
|
||||
}`}
|
||||
>
|
||||
{compressionResult.targetMet ? (
|
||||
|
||||
@@ -72,7 +72,7 @@ export function ImagesToVideoSettings() {
|
||||
/>
|
||||
</div>
|
||||
|
||||
{error && <p className="text-xs text-red-500">{error}</p>}
|
||||
{error && <p className="text-xs text-destructive-ink">{error}</p>}
|
||||
|
||||
{processing ? (
|
||||
<ProgressCard
|
||||
|
||||
@@ -138,7 +138,7 @@ export function InfoSettings() {
|
||||
{processing ? ts.reading : ts.readInfo}
|
||||
</button>
|
||||
|
||||
{error && <p className="text-xs text-red-500">{error}</p>}
|
||||
{error && <p className="text-xs text-destructive-ink">{error}</p>}
|
||||
|
||||
{info && (
|
||||
<div className="space-y-3">
|
||||
|
||||
@@ -38,7 +38,7 @@ export function JsonXmlSettings() {
|
||||
{s.pretty}
|
||||
</label>
|
||||
|
||||
{error && <p className="text-xs text-red-500">{error}</p>}
|
||||
{error && <p className="text-xs text-destructive-ink">{error}</p>}
|
||||
|
||||
{processing ? (
|
||||
<ProgressCard
|
||||
|
||||
@@ -25,7 +25,7 @@ export function LinearizePdfSettings() {
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
{error && <p className="text-xs text-red-500">{error}</p>}
|
||||
{error && <p className="text-xs text-destructive-ink">{error}</p>}
|
||||
|
||||
{processing ? (
|
||||
<ProgressCard
|
||||
|
||||
@@ -156,7 +156,7 @@ export function LqipPlaceholderSettings() {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{error && <p className="text-xs text-red-500">{error}</p>}
|
||||
{error && <p className="text-xs text-destructive-ink">{error}</p>}
|
||||
|
||||
{processing ? (
|
||||
<ProgressCard
|
||||
@@ -188,7 +188,7 @@ export function LqipPlaceholderSettings() {
|
||||
href={downloadUrl}
|
||||
download
|
||||
data-testid="lqip-placeholder-download"
|
||||
className="w-full py-2.5 rounded-lg border border-primary text-primary font-medium flex items-center justify-center gap-2 hover:bg-primary/5"
|
||||
className="w-full py-2.5 rounded-lg border border-primary text-primary-ink font-medium flex items-center justify-center gap-2 hover:bg-primary/5"
|
||||
>
|
||||
<Download className="h-4 w-4" />
|
||||
{t.common.download}
|
||||
@@ -267,7 +267,7 @@ function CopyBlock({
|
||||
onClick={onCopy}
|
||||
className="flex items-center gap-1 text-xs text-muted-foreground hover:text-foreground"
|
||||
>
|
||||
{copied ? <Check className="h-3 w-3 text-green-500" /> : <Copy className="h-3 w-3" />}
|
||||
{copied ? <Check className="h-3 w-3 text-success-ink" /> : <Copy className="h-3 w-3" />}
|
||||
Copy
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -25,7 +25,7 @@ export function MarkdownToDocxSettings() {
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
{error && <p className="text-xs text-red-500">{error}</p>}
|
||||
{error && <p className="text-xs text-destructive-ink">{error}</p>}
|
||||
|
||||
{processing ? (
|
||||
<ProgressCard
|
||||
|
||||
@@ -27,7 +27,7 @@ export function MarkdownToHtmlSettings() {
|
||||
<div className="space-y-4">
|
||||
<p className="text-[10px] text-muted-foreground">{s.hint}</p>
|
||||
|
||||
{error && <p className="text-xs text-red-500">{error}</p>}
|
||||
{error && <p className="text-xs text-destructive-ink">{error}</p>}
|
||||
|
||||
{processing ? (
|
||||
<ProgressCard
|
||||
|
||||
@@ -27,7 +27,7 @@ export function MarkdownToPdfSettings() {
|
||||
<div className="space-y-4">
|
||||
<p className="text-[10px] text-muted-foreground">{s.hint}</p>
|
||||
|
||||
{error && <p className="text-xs text-red-500">{error}</p>}
|
||||
{error && <p className="text-xs text-destructive-ink">{error}</p>}
|
||||
|
||||
{processing ? (
|
||||
<ProgressCard
|
||||
|
||||
@@ -452,7 +452,7 @@ function ErrorView() {
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => window.location.reload()}
|
||||
className="text-xs text-primary hover:underline"
|
||||
className="text-xs text-primary-ink hover:underline"
|
||||
>
|
||||
Retry
|
||||
</button>
|
||||
|
||||
@@ -41,7 +41,7 @@ export function MergeAudioSettings() {
|
||||
|
||||
<p className="text-[10px] text-muted-foreground">{s["order-hint"]}</p>
|
||||
|
||||
{error && <p className="text-xs text-red-500">{error}</p>}
|
||||
{error && <p className="text-xs text-destructive-ink">{error}</p>}
|
||||
|
||||
{processing ? (
|
||||
<ProgressCard
|
||||
|
||||
@@ -17,7 +17,7 @@ export function MergeCsvsSettings() {
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
{error && <p className="text-xs text-red-500">{error}</p>}
|
||||
{error && <p className="text-xs text-destructive-ink">{error}</p>}
|
||||
|
||||
{processing ? (
|
||||
<ProgressCard
|
||||
|
||||
@@ -24,7 +24,7 @@ export function MergePdfSettings() {
|
||||
</p>
|
||||
)}
|
||||
|
||||
{error && <p className="text-xs text-red-500">{error}</p>}
|
||||
{error && <p className="text-xs text-destructive-ink">{error}</p>}
|
||||
|
||||
{processing ? (
|
||||
<ProgressCard
|
||||
|
||||
@@ -19,7 +19,7 @@ export function MergeVideosSettings() {
|
||||
<div className="space-y-4">
|
||||
<p className="text-[10px] text-muted-foreground">{s.hint}</p>
|
||||
|
||||
{error && <p className="text-xs text-red-500">{error}</p>}
|
||||
{error && <p className="text-xs text-destructive-ink">{error}</p>}
|
||||
|
||||
{processing ? (
|
||||
<ProgressCard
|
||||
|
||||
@@ -25,7 +25,7 @@ export function MuteVideoSettings() {
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
{error && <p className="text-xs text-red-500">{error}</p>}
|
||||
{error && <p className="text-xs text-destructive-ink">{error}</p>}
|
||||
|
||||
{processing ? (
|
||||
<ProgressCard
|
||||
|
||||
@@ -46,7 +46,7 @@ export function NoiseReductionSettings() {
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{error && <p className="text-xs text-red-500">{error}</p>}
|
||||
{error && <p className="text-xs text-destructive-ink">{error}</p>}
|
||||
|
||||
{processing ? (
|
||||
<ProgressCard
|
||||
|
||||
@@ -240,14 +240,14 @@ export function NoiseRemovalSettings() {
|
||||
|
||||
{/* GIF + AI tier warning */}
|
||||
{isGif && isAiTier && (
|
||||
<p className="text-xs text-amber-500">
|
||||
<p className="text-xs text-amber-700 dark:text-amber-400">
|
||||
AI denoising on GIF files processes only the first frame. For animated GIFs, use the Quick
|
||||
or Balanced tier.
|
||||
</p>
|
||||
)}
|
||||
|
||||
{/* Error */}
|
||||
{error && <p className="text-xs text-red-500">{error}</p>}
|
||||
{error && <p className="text-xs text-destructive-ink">{error}</p>}
|
||||
|
||||
{/* Size info */}
|
||||
{originalSize != null && processedSize != null && (
|
||||
@@ -286,7 +286,7 @@ export function NoiseRemovalSettings() {
|
||||
href={downloadUrl}
|
||||
download
|
||||
data-testid="noise-removal-download"
|
||||
className="w-full py-2.5 rounded-lg border border-primary text-primary font-medium flex items-center justify-center gap-2 hover:bg-primary/5"
|
||||
className="w-full py-2.5 rounded-lg border border-primary text-primary-ink font-medium flex items-center justify-center gap-2 hover:bg-primary/5"
|
||||
>
|
||||
<Download className="h-4 w-4" />
|
||||
Download
|
||||
|
||||
@@ -27,7 +27,7 @@ export function NormalizeAudioSettings() {
|
||||
<div className="space-y-4">
|
||||
<p className="text-[10px] text-muted-foreground">{s.hint}</p>
|
||||
|
||||
{error && <p className="text-xs text-red-500">{error}</p>}
|
||||
{error && <p className="text-xs text-destructive-ink">{error}</p>}
|
||||
|
||||
{processing ? (
|
||||
<ProgressCard
|
||||
|
||||
@@ -50,7 +50,7 @@ export function NupPdfSettings() {
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{error && <p className="text-xs text-red-500">{error}</p>}
|
||||
{error && <p className="text-xs text-destructive-ink">{error}</p>}
|
||||
|
||||
{processing ? (
|
||||
<ProgressCard
|
||||
|
||||
@@ -98,7 +98,7 @@ export function OcrQualityControl({
|
||||
onClick={() => onChange(option.value)}
|
||||
className={`rounded-lg border px-2 py-2 text-xs font-medium transition-colors disabled:cursor-not-allowed disabled:opacity-50 ${
|
||||
quality === option.value
|
||||
? "border-primary bg-primary/10 text-primary"
|
||||
? "border-primary bg-primary/10 text-primary-ink"
|
||||
: "border-border text-muted-foreground hover:border-primary/50"
|
||||
}`}
|
||||
>
|
||||
|
||||
@@ -333,7 +333,7 @@ export function OcrSettings() {
|
||||
>
|
||||
{langOpen ? <ChevronDown className="h-3 w-3" /> : <ChevronRight className="h-3 w-3" />}
|
||||
Language
|
||||
<span className="ms-auto text-primary text-[10px] normal-case font-normal">
|
||||
<span className="ms-auto text-primary-ink text-[10px] normal-case font-normal">
|
||||
{langLabel}
|
||||
</span>
|
||||
</button>
|
||||
@@ -353,7 +353,7 @@ export function OcrSettings() {
|
||||
</div>
|
||||
|
||||
{/* Error */}
|
||||
{error && <p className="text-xs text-red-500">{error}</p>}
|
||||
{error && <p className="text-xs text-destructive-ink">{error}</p>}
|
||||
|
||||
{/* Process button / progress */}
|
||||
{processing ? (
|
||||
|
||||
@@ -338,7 +338,7 @@ export function OptimizeForWebSettings() {
|
||||
{savings != null && (
|
||||
<div
|
||||
className={`text-sm font-semibold ${
|
||||
Number(savings) > 0 ? "text-green-500" : "text-red-500"
|
||||
Number(savings) > 0 ? "text-success-ink" : "text-destructive-ink"
|
||||
}`}
|
||||
>
|
||||
{Number(savings) > 0 ? `${savings}% smaller` : `${Math.abs(Number(savings))}% larger`}
|
||||
@@ -348,7 +348,7 @@ export function OptimizeForWebSettings() {
|
||||
)}
|
||||
|
||||
{/* Error */}
|
||||
{error && <p className="text-xs text-red-500">{error}</p>}
|
||||
{error && <p className="text-xs text-destructive-ink">{error}</p>}
|
||||
|
||||
{/* Process / Download */}
|
||||
{processing ? (
|
||||
@@ -374,7 +374,7 @@ export function OptimizeForWebSettings() {
|
||||
<a
|
||||
href={downloadUrl}
|
||||
download
|
||||
className="w-full py-2.5 rounded-lg border border-primary text-primary font-medium flex items-center justify-center gap-2 hover:bg-primary/5"
|
||||
className="w-full py-2.5 rounded-lg border border-primary text-primary-ink font-medium flex items-center justify-center gap-2 hover:bg-primary/5"
|
||||
>
|
||||
<Download className="h-4 w-4" />
|
||||
Download
|
||||
|
||||
@@ -42,7 +42,7 @@ export function OrganizePdfSettings() {
|
||||
<p className="text-[10px] text-muted-foreground mt-0.5">{s.orderHint}</p>
|
||||
</div>
|
||||
|
||||
{error && <p className="text-xs text-red-500">{error}</p>}
|
||||
{error && <p className="text-xs text-destructive-ink">{error}</p>}
|
||||
|
||||
{processing ? (
|
||||
<ProgressCard
|
||||
|
||||
@@ -320,7 +320,7 @@ function CountryOption({
|
||||
type="button"
|
||||
onClick={onSelect}
|
||||
className={`w-full flex items-center gap-2 px-3 py-1.5 text-xs transition-colors ${
|
||||
selected ? "bg-primary/10 text-primary" : "text-foreground hover:bg-muted"
|
||||
selected ? "bg-primary/10 text-primary-ink" : "text-foreground hover:bg-muted"
|
||||
}`}
|
||||
>
|
||||
<span>{spec.flag}</span>
|
||||
@@ -622,7 +622,7 @@ export function PassportPhotoSettings() {
|
||||
setSearchQuery("");
|
||||
}}
|
||||
className={`w-full flex items-center gap-2 px-3 py-1.5 text-xs transition-colors border-b border-border ${
|
||||
isCustom ? "bg-primary/10 text-primary" : "text-foreground hover:bg-muted"
|
||||
isCustom ? "bg-primary/10 text-primary-ink" : "text-foreground hover:bg-muted"
|
||||
}`}
|
||||
>
|
||||
<span>{"\u2699\uFE0F"}</span>
|
||||
@@ -690,7 +690,7 @@ export function PassportPhotoSettings() {
|
||||
onClick={() => setDocumentType(type)}
|
||||
className={`py-2 px-2 rounded-lg border text-xs font-medium transition-colors capitalize ${
|
||||
documentType === type
|
||||
? "border-primary bg-primary/10 text-primary"
|
||||
? "border-primary bg-primary/10 text-primary-ink"
|
||||
: "border-border text-muted-foreground hover:border-primary/50"
|
||||
}`}
|
||||
>
|
||||
@@ -803,7 +803,7 @@ export function PassportPhotoSettings() {
|
||||
}}
|
||||
className={`py-1.5 px-2.5 rounded-lg border text-xs font-medium transition-colors ${
|
||||
maxFileSizeKb === preset.value
|
||||
? "border-primary bg-primary/10 text-primary"
|
||||
? "border-primary bg-primary/10 text-primary-ink"
|
||||
: "border-border text-muted-foreground hover:border-primary/50"
|
||||
}`}
|
||||
>
|
||||
@@ -838,9 +838,9 @@ export function PassportPhotoSettings() {
|
||||
</div>
|
||||
|
||||
{/* Errors */}
|
||||
{analyzeError && <p className="text-xs text-red-500">{analyzeError}</p>}
|
||||
{generateError && <p className="text-xs text-red-500">{generateError}</p>}
|
||||
{error && <p className="text-xs text-red-500">{error}</p>}
|
||||
{analyzeError && <p className="text-xs text-destructive-ink">{analyzeError}</p>}
|
||||
{generateError && <p className="text-xs text-destructive-ink">{generateError}</p>}
|
||||
{error && <p className="text-xs text-destructive-ink">{error}</p>}
|
||||
|
||||
{/* Analyze progress */}
|
||||
{analyzing && (
|
||||
@@ -1190,11 +1190,13 @@ export function PassportPhotoPreview() {
|
||||
{complianceChecks.map((check) => (
|
||||
<div key={check.label} className="flex items-center gap-1.5 text-xs">
|
||||
{check.pass ? (
|
||||
<Check className="h-3.5 w-3.5 text-emerald-500 shrink-0" />
|
||||
<Check className="h-3.5 w-3.5 text-success-ink shrink-0" />
|
||||
) : (
|
||||
<X className="h-3.5 w-3.5 text-red-500 shrink-0" />
|
||||
<X className="h-3.5 w-3.5 text-destructive-ink shrink-0" />
|
||||
)}
|
||||
<span className={check.pass ? "text-foreground" : "text-red-400"}>{check.label}</span>
|
||||
<span className={check.pass ? "text-foreground" : "text-destructive-ink"}>
|
||||
{check.label}
|
||||
</span>
|
||||
{!check.pass && (
|
||||
<span className="text-muted-foreground text-[10px]">- {check.detail}</span>
|
||||
)}
|
||||
@@ -1205,7 +1207,7 @@ export function PassportPhotoPreview() {
|
||||
|
||||
{/* Generated result notification */}
|
||||
{generateResult && (
|
||||
<div className="flex items-center gap-2 text-xs text-emerald-600 bg-emerald-50 dark:bg-emerald-900/20 dark:text-emerald-400 px-3 py-2 rounded-lg shrink-0">
|
||||
<div className="flex items-center gap-2 text-xs text-success-ink bg-emerald-50 dark:bg-emerald-900/20 px-3 py-2 rounded-lg shrink-0">
|
||||
<Check className="h-4 w-4" />
|
||||
Photo generated. Download from the left panel.
|
||||
</div>
|
||||
|
||||
@@ -91,7 +91,7 @@ export function PdfMetadataSettings() {
|
||||
/>
|
||||
</div>
|
||||
|
||||
{error && <p className="text-xs text-red-500">{error}</p>}
|
||||
{error && <p className="text-xs text-destructive-ink">{error}</p>}
|
||||
|
||||
{processing ? (
|
||||
<ProgressCard
|
||||
|
||||
@@ -66,7 +66,7 @@ export function PdfPageNumbersSettings() {
|
||||
/>
|
||||
</div>
|
||||
|
||||
{error && <p className="text-xs text-red-500">{error}</p>}
|
||||
{error && <p className="text-xs text-destructive-ink">{error}</p>}
|
||||
|
||||
{processing ? (
|
||||
<ProgressCard
|
||||
|
||||
@@ -163,7 +163,7 @@ export function PdfToImagePreview() {
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => store.selectAllPages()}
|
||||
className="text-xs text-primary hover:underline"
|
||||
className="text-xs text-primary-ink hover:underline"
|
||||
>
|
||||
Select All
|
||||
</button>
|
||||
@@ -172,7 +172,7 @@ export function PdfToImagePreview() {
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => store.deselectAllPages()}
|
||||
className="text-xs text-primary hover:underline"
|
||||
className="text-xs text-primary-ink hover:underline"
|
||||
>
|
||||
Deselect All
|
||||
</button>
|
||||
|
||||
@@ -213,7 +213,7 @@ export function PdfToImageSettings() {
|
||||
</div>
|
||||
|
||||
{/* Error */}
|
||||
{store.error && <p className="text-xs text-red-500">{store.error}</p>}
|
||||
{store.error && <p className="text-xs text-destructive-ink">{store.error}</p>}
|
||||
|
||||
{/* Convert button */}
|
||||
<button
|
||||
@@ -235,7 +235,7 @@ export function PdfToImageSettings() {
|
||||
href={store.zipUrl}
|
||||
download="pdf-pages.zip"
|
||||
data-testid="pdf-to-image-download"
|
||||
className="w-full py-2.5 rounded-lg border border-primary text-primary font-medium flex items-center justify-center gap-2 hover:bg-primary/5"
|
||||
className="w-full py-2.5 rounded-lg border border-primary text-primary-ink font-medium flex items-center justify-center gap-2 hover:bg-primary/5"
|
||||
>
|
||||
<Download className="h-4 w-4" />
|
||||
Download All (ZIP)
|
||||
|
||||
@@ -25,7 +25,7 @@ export function PdfToTextSettings() {
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
{error && <p className="text-xs text-red-500">{error}</p>}
|
||||
{error && <p className="text-xs text-destructive-ink">{error}</p>}
|
||||
|
||||
{processing ? (
|
||||
<ProgressCard
|
||||
|
||||
@@ -27,7 +27,7 @@ export function PdfToWordSettings() {
|
||||
<div className="space-y-4">
|
||||
<p className="text-[10px] text-muted-foreground">{s.hint}</p>
|
||||
|
||||
{error && <p className="text-xs text-red-500">{error}</p>}
|
||||
{error && <p className="text-xs text-destructive-ink">{error}</p>}
|
||||
|
||||
{processing ? (
|
||||
<ProgressCard
|
||||
|
||||
@@ -25,7 +25,7 @@ export function PdfaConvertSettings() {
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
{error && <p className="text-xs text-red-500">{error}</p>}
|
||||
{error && <p className="text-xs text-destructive-ink">{error}</p>}
|
||||
|
||||
{processing ? (
|
||||
<ProgressCard
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user