mirror of
https://github.com/snapotter-hq/SnapOtter.git
synced 2026-08-03 07:46:42 +02:00
Merge branch 'feat/web-otter-orange-design'
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { FileUp, Upload } from "lucide-react";
|
||||
import { FileImage, FileUp, Upload } from "lucide-react";
|
||||
import { type DragEvent, useCallback, useEffect, useState } from "react";
|
||||
import { useTranslation } from "@/contexts/i18n-context";
|
||||
import { useUrlImport } from "@/hooks/use-url-import";
|
||||
|
||||
@@ -258,7 +258,7 @@ export function CropOverlay() {
|
||||
keepRatio={false}
|
||||
anchorSize={8}
|
||||
anchorStroke="#ffffff"
|
||||
anchorFill="#3b82f6"
|
||||
anchorFill="#E07832"
|
||||
anchorCornerRadius={1}
|
||||
borderStroke="#ffffff"
|
||||
borderStrokeWidth={1}
|
||||
|
||||
@@ -469,10 +469,10 @@ export function MoveToolTransformer({
|
||||
flipEnabled
|
||||
keepRatio={false}
|
||||
anchorSize={8}
|
||||
anchorStroke="#3b82f6"
|
||||
anchorStroke="#E07832"
|
||||
anchorFill="#ffffff"
|
||||
anchorCornerRadius={2}
|
||||
borderStroke="#3b82f6"
|
||||
borderStroke="#E07832"
|
||||
borderStrokeWidth={1}
|
||||
padding={2}
|
||||
/>
|
||||
|
||||
@@ -793,7 +793,7 @@ export function ActiveSelectionPreview({
|
||||
}) {
|
||||
if (type === "lasso" && points.length >= 4) {
|
||||
return (
|
||||
<Line points={points} stroke="#3b82f6" strokeWidth={1} dash={[4, 4]} listening={false} />
|
||||
<Line points={points} stroke="#E07832" strokeWidth={1} dash={[4, 4]} listening={false} />
|
||||
);
|
||||
}
|
||||
|
||||
@@ -812,7 +812,7 @@ export function ActiveSelectionPreview({
|
||||
y={y + h / 2}
|
||||
radiusX={w / 2}
|
||||
radiusY={h / 2}
|
||||
stroke="#3b82f6"
|
||||
stroke="#E07832"
|
||||
strokeWidth={1}
|
||||
dash={[4, 4]}
|
||||
listening={false}
|
||||
@@ -826,7 +826,7 @@ export function ActiveSelectionPreview({
|
||||
y={y}
|
||||
width={w}
|
||||
height={h}
|
||||
stroke="#3b82f6"
|
||||
stroke="#E07832"
|
||||
strokeWidth={1}
|
||||
dash={[4, 4]}
|
||||
listening={false}
|
||||
|
||||
@@ -295,10 +295,10 @@ export function TransformToolTransformer({
|
||||
300, 315, 330, 345,
|
||||
]}
|
||||
anchorSize={8}
|
||||
anchorStroke="#3b82f6"
|
||||
anchorStroke="#E07832"
|
||||
anchorFill="#ffffff"
|
||||
anchorCornerRadius={2}
|
||||
borderStroke="#3b82f6"
|
||||
borderStroke="#E07832"
|
||||
borderStrokeWidth={1}
|
||||
borderDash={[4, 4]}
|
||||
padding={0}
|
||||
|
||||
@@ -188,8 +188,8 @@ export function FileDetails({ mobile = false }: FileDetailsProps) {
|
||||
{/* Details card */}
|
||||
<div className="flex-1">
|
||||
<div className="rounded-lg border border-border overflow-hidden">
|
||||
<div className="bg-blue-500/10 border-b border-border px-3 py-2">
|
||||
<h4 className="text-sm font-semibold text-blue-600 dark:text-blue-400">
|
||||
<div className="bg-primary/10 border-b border-border px-3 py-2">
|
||||
<h4 className="text-sm font-semibold text-primary-dark dark:text-primary-light">
|
||||
{t.files.fileDetailsHeading}
|
||||
</h4>
|
||||
</div>
|
||||
|
||||
@@ -119,10 +119,7 @@ export function ToolPanel() {
|
||||
return (
|
||||
<div key={modality.id} className={isLast ? "" : "mb-2"}>
|
||||
{idx > 0 && <hr className="border-border mb-4" />}
|
||||
<div
|
||||
className="flex items-center gap-2 mt-4 mb-3 pl-2.5 border-l-[3px] rounded-sm"
|
||||
style={{ borderColor: modality.color }}
|
||||
>
|
||||
<div className="flex items-center gap-2 mt-4 mb-3 pl-1">
|
||||
{ModalityIcon && (
|
||||
<span className="shrink-0" style={{ color: modality.color }}>
|
||||
<ModalityIcon className="h-4.5 w-4.5" />
|
||||
|
||||
@@ -47,7 +47,7 @@ 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-blue-500/15 text-blue-600 dark:text-blue-400";
|
||||
return "bg-primary/15 text-primary-dark dark:text-primary-light";
|
||||
if (type.includes("EAN") || type.includes("UPC") || type.includes("DataBar"))
|
||||
return "bg-green-500/15 text-green-600 dark:text-green-400";
|
||||
if (type === "PDF417") return "bg-purple-500/15 text-purple-600 dark:text-purple-400";
|
||||
|
||||
@@ -459,7 +459,7 @@ function CollageCell({
|
||||
className={cn(
|
||||
"relative overflow-hidden transition-shadow",
|
||||
isSelected && "ring-2 ring-primary ring-offset-1",
|
||||
isOver && !isSelected && "ring-2 ring-blue-500",
|
||||
isOver && !isSelected && "ring-2 ring-primary",
|
||||
!image && "border-2 border-dashed border-border/50",
|
||||
image && !isSelected && "cursor-grab",
|
||||
image && isSelected && "cursor-grab active:cursor-grabbing",
|
||||
@@ -521,7 +521,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-blue-500/80 text-white hover:bg-blue-500/90"
|
||||
? "bg-primary/80 text-white hover:bg-primary/90"
|
||||
: "bg-black/50 text-white hover:bg-black/70",
|
||||
)}
|
||||
title={transform.objectFit === "cover" ? "Fit entire image" : "Fill cell"}
|
||||
|
||||
@@ -29,14 +29,17 @@ export function DocumentView() {
|
||||
|
||||
/* C+D: cancel in-flight renders and destroy the doc proxy on cleanup. */
|
||||
useEffect(() => {
|
||||
if (!src || !canvasRef.current) return;
|
||||
if (!canvasRef.current) return;
|
||||
const file = entry?.file;
|
||||
if (!file && !src) return;
|
||||
let cancelled = false;
|
||||
let doc: pdfjs.PDFDocumentProxy | undefined;
|
||||
let renderTask: pdfjs.RenderTask | undefined;
|
||||
|
||||
(async () => {
|
||||
try {
|
||||
doc = await pdfjs.getDocument({ url: src }).promise;
|
||||
const source = file ? { data: new Uint8Array(await file.arrayBuffer()) } : { url: src! };
|
||||
doc = await pdfjs.getDocument(source).promise;
|
||||
if (cancelled) return;
|
||||
setPageCount(doc.numPages);
|
||||
const pdfPage = await doc.getPage(Math.min(page, doc.numPages));
|
||||
@@ -60,7 +63,7 @@ export function DocumentView() {
|
||||
renderTask?.cancel();
|
||||
doc?.loadingTask.destroy();
|
||||
};
|
||||
}, [src, page]);
|
||||
}, [src, page, entry?.file]);
|
||||
|
||||
if (!entry) return null;
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Download, FileUp, Loader2, X } from "lucide-react";
|
||||
import { useCallback, useRef } from "react";
|
||||
import { Download, Loader2 } from "lucide-react";
|
||||
import { useEffect } from "react";
|
||||
import { useTranslation } from "@/contexts/i18n-context";
|
||||
import { format } from "@/lib/format";
|
||||
import { useFileStore } from "@/stores/file-store";
|
||||
import { usePdfToImageStore } from "@/stores/pdf-to-image-store";
|
||||
|
||||
const FORMAT_OPTIONS = [
|
||||
@@ -41,57 +41,22 @@ const LOSSY_FORMATS = ["jpg", "webp", "avif", "heic", "heif", "jxl"];
|
||||
export function PdfToImageSettings() {
|
||||
const { t } = useTranslation();
|
||||
const store = usePdfToImageStore();
|
||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||
const file = useFileStore((s) => s.entries[s.selectedIndex]?.file);
|
||||
|
||||
useEffect(() => {
|
||||
if (file && file !== store.file) {
|
||||
store.setFile(file);
|
||||
store.loadPreview(file);
|
||||
}
|
||||
}, [file, store]);
|
||||
|
||||
const isLossy = LOSSY_FORMATS.includes(store.format);
|
||||
|
||||
const handleFileChange = useCallback(
|
||||
(files: FileList | null) => {
|
||||
const pdfFile = files?.[0];
|
||||
if (!pdfFile) return;
|
||||
store.setFile(pdfFile);
|
||||
store.loadPreview(pdfFile);
|
||||
},
|
||||
[store],
|
||||
);
|
||||
|
||||
const handleDrop = useCallback(
|
||||
(e: React.DragEvent) => {
|
||||
e.preventDefault();
|
||||
handleFileChange(e.dataTransfer.files);
|
||||
},
|
||||
[handleFileChange],
|
||||
);
|
||||
|
||||
const handleRemoveFile = useCallback(() => {
|
||||
store.setFile(null);
|
||||
if (fileInputRef.current) fileInputRef.current.value = "";
|
||||
}, [store]);
|
||||
|
||||
const selectedCount = store.selectedPages.size;
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
{/* PDF upload area */}
|
||||
{!store.file ? (
|
||||
<button
|
||||
type="button"
|
||||
onDragOver={(e) => e.preventDefault()}
|
||||
onDrop={handleDrop}
|
||||
onClick={() => fileInputRef.current?.click()}
|
||||
className="border-2 border-dashed border-border rounded-lg p-6 text-center cursor-pointer hover:border-primary/50 transition-colors w-full"
|
||||
>
|
||||
<FileUp className="h-8 w-8 mx-auto mb-2 text-muted-foreground" />
|
||||
<p className="text-sm text-muted-foreground">{t.toolSettings["pdf-to-image"].dropPdf}</p>
|
||||
<input
|
||||
ref={fileInputRef}
|
||||
type="file"
|
||||
accept="application/pdf"
|
||||
className="hidden"
|
||||
onChange={(e) => handleFileChange(e.target.files)}
|
||||
/>
|
||||
</button>
|
||||
) : (
|
||||
{/* PDF info */}
|
||||
{store.file && (
|
||||
<div className="flex items-center gap-2 p-3 rounded-lg bg-muted">
|
||||
<div className="flex-1 min-w-0">
|
||||
<p className="text-sm font-medium truncate">{store.file.name}</p>
|
||||
@@ -106,13 +71,6 @@ export function PdfToImageSettings() {
|
||||
) : null}
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleRemoveFile}
|
||||
className="p-1 hover:bg-background rounded"
|
||||
>
|
||||
<X className="h-4 w-4 text-muted-foreground" />
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
@@ -78,7 +78,7 @@ export const TOOL_DISPLAY_MODES: Record<string, DisplayMode> = {
|
||||
favicon: "before-after",
|
||||
"image-to-pdf": "before-after",
|
||||
"optimize-for-web": "before-after",
|
||||
"pdf-to-image": "no-dropzone",
|
||||
"pdf-to-image": "custom-results",
|
||||
|
||||
// Adjustments extra
|
||||
"replace-color": "before-after",
|
||||
|
||||
@@ -219,10 +219,7 @@ export function FullscreenGridPage() {
|
||||
}>;
|
||||
return (
|
||||
<section key={section.modality.id}>
|
||||
<div
|
||||
className="flex items-center gap-3 mb-5 pb-3 border-b-2"
|
||||
style={{ borderColor: section.modality.color }}
|
||||
>
|
||||
<div className="flex items-center gap-3 mb-5 pb-3 border-b border-border">
|
||||
{SectionIcon && (
|
||||
<div
|
||||
className="p-2 rounded-lg"
|
||||
@@ -268,10 +265,7 @@ export function FullscreenGridPage() {
|
||||
? (ICON_MAP[activeMod.icon] as React.ComponentType<{ className?: string }>)
|
||||
: null;
|
||||
return activeMod ? (
|
||||
<div
|
||||
className="flex items-center gap-3 mb-6 pb-3 border-b-2"
|
||||
style={{ borderColor: activeMod.color }}
|
||||
>
|
||||
<div className="flex items-center gap-3 mb-6 pb-3 border-b border-border">
|
||||
{ModIcon && (
|
||||
<div
|
||||
className="p-2 rounded-lg"
|
||||
@@ -331,10 +325,7 @@ function CategoryCard({
|
||||
(ICON_MAP[category.icon] as React.ComponentType<{ className?: string }>) ?? LayoutGrid;
|
||||
|
||||
return (
|
||||
<div
|
||||
className="rounded-xl border border-border bg-card overflow-hidden shadow-sm hover:shadow-md transition-shadow"
|
||||
style={accentColor ? { borderLeftWidth: 3, borderLeftColor: accentColor } : undefined}
|
||||
>
|
||||
<div className="rounded-xl border border-border bg-card overflow-hidden" style={undefined}>
|
||||
{/* Header */}
|
||||
<div
|
||||
className="px-4 py-3 flex items-center gap-3"
|
||||
|
||||
@@ -5,8 +5,8 @@ import {
|
||||
TOOL_BUNDLE_MAP,
|
||||
TOOLS,
|
||||
} from "@snapotter/shared";
|
||||
import { Clock, Download, FileArchive, Loader2 } from "lucide-react";
|
||||
import { useCallback, useEffect, useMemo } from "react";
|
||||
import { Clock, Download, FileArchive, LayoutGrid, Loader2 } from "lucide-react";
|
||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import { useLocation, useNavigate } from "react-router-dom";
|
||||
import { ImageViewer } from "@/components/common/image-viewer";
|
||||
import { MultiImageViewer } from "@/components/common/multi-image-viewer";
|
||||
@@ -17,12 +17,12 @@ import { useTranslation } from "@/contexts/i18n-context";
|
||||
import { useMobile } from "@/hooks/use-mobile";
|
||||
import { ICON_MAP } from "@/lib/icon-map";
|
||||
import { getCategoryName, getModalityName, getToolName } from "@/lib/tool-i18n";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { useFeaturesStore } from "@/stores/features-store";
|
||||
import { useFileStore } from "@/stores/file-store";
|
||||
import { useSettingsStore } from "@/stores/settings-store";
|
||||
|
||||
// Tools shown prominently as "quick actions" at the top
|
||||
const QUICK_ACTION_IDS = ["resize", "compress", "convert", "remove-background"];
|
||||
|
||||
let hasAppliedDefaultRedirect = false;
|
||||
|
||||
@@ -96,6 +96,9 @@ export function HomePage() {
|
||||
[setFiles],
|
||||
);
|
||||
|
||||
const [modalityFilter, setModalityFilter] = useState<string>("all");
|
||||
const TAB_MODALITIES = useMemo(() => MODALITIES.filter((m) => m.id !== "file"), []);
|
||||
|
||||
const hasFile = files.length > 0;
|
||||
|
||||
// If no file uploaded, show default layout (tool panel + dropzone)
|
||||
@@ -127,54 +130,6 @@ export function HomePage() {
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Quick action buttons - horizontal scroll */}
|
||||
<div className="flex overflow-x-auto gap-2 px-4 py-3 border-b border-border scrollbar-none">
|
||||
{QUICK_ACTION_IDS.map((id) => {
|
||||
const tool = TOOLS.find((t) => t.id === id);
|
||||
if (!tool) return null;
|
||||
const Icon =
|
||||
(ICON_MAP[tool.icon] as React.ComponentType<{ className?: string }>) ??
|
||||
ICON_MAP.FileImage;
|
||||
const status = getToolStatus(id);
|
||||
return (
|
||||
<button
|
||||
key={id}
|
||||
type="button"
|
||||
onClick={() => navigate(tool.route)}
|
||||
className="flex items-center gap-2 px-3 py-2 rounded-xl border border-border hover:border-primary hover:bg-primary/5 transition-colors shrink-0"
|
||||
>
|
||||
<div className="p-1 rounded-lg bg-primary/10 text-primary">
|
||||
<Icon className="h-4 w-4" />
|
||||
</div>
|
||||
<span className="text-xs font-medium text-foreground whitespace-nowrap">
|
||||
{getToolName(t, tool.id, tool.name)}
|
||||
</span>
|
||||
{status === "not_installed" && (
|
||||
<>
|
||||
<Download className="h-3.5 w-3.5 text-muted-foreground" aria-hidden="true" />
|
||||
<span className="sr-only">{t.a11y.notInstalled}</span>
|
||||
</>
|
||||
)}
|
||||
{status === "queued" && (
|
||||
<>
|
||||
<Clock className="h-3.5 w-3.5 text-muted-foreground" aria-hidden="true" />
|
||||
<span className="sr-only">{t.a11y.queued}</span>
|
||||
</>
|
||||
)}
|
||||
{status === "installing" && (
|
||||
<>
|
||||
<Loader2
|
||||
className="h-3.5 w-3.5 text-muted-foreground animate-spin"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<span className="sr-only">{t.a11y.installing}</span>
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
{/* Full-width file preview */}
|
||||
<div className="flex-1 flex items-center justify-center p-4 min-h-0">
|
||||
{files.length > 1 ? (
|
||||
@@ -244,72 +199,52 @@ export function HomePage() {
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Quick actions */}
|
||||
<div className="p-4 border-b border-border">
|
||||
<h3 className="text-xs font-semibold uppercase text-muted-foreground tracking-wider mb-3">
|
||||
{t.homePage.quickActions}
|
||||
</h3>
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
{QUICK_ACTION_IDS.map((id) => {
|
||||
const tool = TOOLS.find((t) => t.id === id);
|
||||
if (!tool) return null;
|
||||
const Icon =
|
||||
(ICON_MAP[tool.icon] as React.ComponentType<{ className?: string }>) ??
|
||||
ICON_MAP.FileImage;
|
||||
const status = getToolStatus(id);
|
||||
return (
|
||||
<button
|
||||
key={id}
|
||||
type="button"
|
||||
onClick={() => navigate(tool.route)}
|
||||
className="flex items-center gap-2 p-3 rounded-xl border border-border hover:border-primary hover:bg-primary/5 transition-colors text-start"
|
||||
>
|
||||
<div className="p-1.5 rounded-lg bg-primary/10 text-primary">
|
||||
<Icon className="h-4 w-4" />
|
||||
</div>
|
||||
<span className="text-xs font-medium text-foreground">
|
||||
{getToolName(t, tool.id, tool.name)}
|
||||
</span>
|
||||
{status === "not_installed" && (
|
||||
<>
|
||||
<Download
|
||||
className="h-3.5 w-3.5 text-muted-foreground ms-auto"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<span className="sr-only">{t.a11y.notInstalled}</span>
|
||||
</>
|
||||
)}
|
||||
{status === "queued" && (
|
||||
<>
|
||||
<Clock
|
||||
className="h-3.5 w-3.5 text-muted-foreground ms-auto"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<span className="sr-only">{t.a11y.queued}</span>
|
||||
</>
|
||||
)}
|
||||
{status === "installing" && (
|
||||
<>
|
||||
<Loader2
|
||||
className="h-3.5 w-3.5 text-muted-foreground ms-auto animate-spin"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<span className="sr-only">{t.a11y.installing}</span>
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
{/* Modality filter tabs */}
|
||||
<div className="flex flex-wrap gap-1 px-4 pt-3 pb-2 border-b border-border sticky top-0 bg-background z-10">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setModalityFilter("all")}
|
||||
className={cn(
|
||||
"flex items-center gap-1 px-2 py-1 rounded-md text-xs font-medium transition-colors",
|
||||
modalityFilter === "all"
|
||||
? "bg-primary/10 text-primary"
|
||||
: "text-muted-foreground hover:text-foreground hover:bg-muted",
|
||||
)}
|
||||
>
|
||||
<LayoutGrid className="h-3.5 w-3.5" />
|
||||
<span>All</span>
|
||||
</button>
|
||||
{TAB_MODALITIES.map((m) => {
|
||||
const Icon = ICON_MAP[m.icon] as React.ComponentType<{ className?: string }>;
|
||||
const isActive = modalityFilter === m.id;
|
||||
const label = m.id === "document" ? "Docs" : m.name;
|
||||
return (
|
||||
<button
|
||||
key={m.id}
|
||||
type="button"
|
||||
onClick={() => setModalityFilter(m.id)}
|
||||
className={cn(
|
||||
"flex items-center gap-1 px-2 py-1 rounded-md text-xs font-medium transition-colors",
|
||||
isActive
|
||||
? "bg-primary/10 text-primary"
|
||||
: "text-muted-foreground hover:text-foreground hover:bg-muted",
|
||||
)}
|
||||
>
|
||||
{Icon && <Icon className="h-3.5 w-3.5" />}
|
||||
<span>{label}</span>
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
{/* All tools by modality and category */}
|
||||
{/* Tools by modality and category */}
|
||||
<div className="p-4">
|
||||
<h3 className="text-xs font-semibold uppercase text-muted-foreground tracking-wider mb-3">
|
||||
{t.homePage.allTools}
|
||||
</h3>
|
||||
{MODALITIES.filter((m) => {
|
||||
if (m.id === "file") return false;
|
||||
if (modalityFilter !== "all") {
|
||||
if (modalityFilter === "document") return m.id === "document";
|
||||
return m.id === modalityFilter;
|
||||
}
|
||||
const key = m.id;
|
||||
return TOOLS.some(
|
||||
(tool) => tool.modality === key || (key === "document" && tool.modality === "file"),
|
||||
|
||||
@@ -191,7 +191,7 @@ export const useEditorStore = create<EditorState & EditorStateExtensions>()(
|
||||
editingTextId: null,
|
||||
|
||||
// --- Shape settings ---
|
||||
shapeFill: "#3b82f6" as string | null,
|
||||
shapeFill: "#E07832" as string | null,
|
||||
shapeFillOpacity: 1,
|
||||
shapeStroke: "#000000" as string | null,
|
||||
shapeStrokeOpacity: 1,
|
||||
|
||||
@@ -10,31 +10,35 @@
|
||||
}
|
||||
|
||||
@theme {
|
||||
--color-primary: #2563eb;
|
||||
--color-primary: #E07832;
|
||||
--color-primary-foreground: #ffffff;
|
||||
--color-background: #ffffff;
|
||||
--color-foreground: #0f172a;
|
||||
--color-muted: #f1f5f9;
|
||||
--color-muted-foreground: #64748b;
|
||||
--color-border: #e2e8f0;
|
||||
--color-primary-light: #F09550;
|
||||
--color-primary-dark: #C06520;
|
||||
--color-primary-subtle: #FFF5ED;
|
||||
--color-background: #FAFAF7;
|
||||
--color-foreground: #1A1814;
|
||||
--color-muted: #F3EEE6;
|
||||
--color-muted-foreground: #6B6560;
|
||||
--color-border: #DDD6CC;
|
||||
--color-card: #ffffff;
|
||||
--color-card-foreground: #0f172a;
|
||||
--color-sidebar: #f8fafc;
|
||||
--color-sidebar-foreground: #334155;
|
||||
--color-accent: #3b82f6;
|
||||
--color-destructive: #ef4444;
|
||||
--color-card-foreground: #1A1814;
|
||||
--color-sidebar: #F3EEE6;
|
||||
--color-sidebar-foreground: #3D3832;
|
||||
--color-accent: #F09550;
|
||||
--color-destructive: #D44C47;
|
||||
--color-success: #2D9D78;
|
||||
}
|
||||
|
||||
.dark {
|
||||
--color-background: #0f172a;
|
||||
--color-foreground: #f8fafc;
|
||||
--color-muted: #1e293b;
|
||||
--color-muted-foreground: #94a3b8;
|
||||
--color-border: #334155;
|
||||
--color-card: #1e293b;
|
||||
--color-card-foreground: #f8fafc;
|
||||
--color-sidebar: #1e293b;
|
||||
--color-sidebar-foreground: #cbd5e1;
|
||||
--color-background: #1A1210;
|
||||
--color-foreground: #F0EBE4;
|
||||
--color-muted: #262018;
|
||||
--color-muted-foreground: #9A908A;
|
||||
--color-border: #382E26;
|
||||
--color-card: #262018;
|
||||
--color-card-foreground: #F0EBE4;
|
||||
--color-sidebar: #262018;
|
||||
--color-sidebar-foreground: #C5BAB0;
|
||||
}
|
||||
|
||||
input[type="range"] {
|
||||
|
||||
Reference in New Issue
Block a user