diff --git a/README.md b/README.md
index 1e66186..bce7f4e 100644
--- a/README.md
+++ b/README.md
@@ -1,21 +1,12 @@
-# Next.js template
+# Image Optimizer
-This is a Next.js template with shadcn/ui.
+# Description
+Image Optimizer is a web tool that offers different options to compress, resize, and optimize your images.
-## Adding components
+# Tech Stack
-To add components to your app, run the following command:
+
No image loaded. Redirecting…
-
- return (
-
-
-
- {url ? (
-
- ) : (
-
Preparing image…
- )}
-
+ return url === null ? null : (
+
+
+
)
}
diff --git a/components/image-diff-viewer.tsx b/components/image-diff-viewer.tsx
new file mode 100644
index 0000000..8b06eb2
--- /dev/null
+++ b/components/image-diff-viewer.tsx
@@ -0,0 +1,45 @@
+"use client"
+import React from "react"
+import {
+ ReactCompareSlider,
+ ReactCompareSliderImage,
+ ReactCompareSliderHandle,
+} from "react-compare-slider"
+
+type ImageDiffViewerProps = {
+ beforeSrc: string
+ afterSrc: string
+ alt?: string
+ className?: string
+}
+
+export default function ImageDiffViewer({
+ beforeSrc,
+ afterSrc,
+ alt = "",
+ className = "",
+}: ImageDiffViewerProps) {
+ if (!beforeSrc || !afterSrc) return null
+
+ return (
+
+
+ }
+ itemTwo={
+
+ }
+ handle={}
+ />
+
+ )
+}
diff --git a/components/image-edit-settings.tsx b/components/image-edit-settings.tsx
new file mode 100644
index 0000000..583fa2f
--- /dev/null
+++ b/components/image-edit-settings.tsx
@@ -0,0 +1,7 @@
+export default function ImageEditSettings() {
+ return (
+
+
+
+ )
+}
\ No newline at end of file
diff --git a/components/image-upload.tsx b/components/image-upload.tsx
index 5224824..df257de 100644
--- a/components/image-upload.tsx
+++ b/components/image-upload.tsx
@@ -17,7 +17,7 @@ export default function ImageUpload() {
const inputRef = useRef
(null)
const timersRef = useRef([])
const router = useRouter()
- const setImageFile = useImageStore((s) => s.setFile)
+ const setOriginalFile = useImageStore((s) => s.setOriginalFile)
const errorWaitTime = 2000 // ms
const successWaitTime = 1500 // ms
@@ -82,7 +82,7 @@ export default function ImageUpload() {
}
try {
- setImageFile(file)
+ setOriginalFile(file)
if (inputRef.current) inputRef.current.value = ""
setStatus("success")
pushTimeout(() => {
diff --git a/data/imageStore.ts b/data/imageStore.ts
index 6c098f4..f713c94 100644
--- a/data/imageStore.ts
+++ b/data/imageStore.ts
@@ -1,29 +1,96 @@
import create from "zustand"
type ImageState = {
+ // Working copy (modifiable by editors/libraries)
file: File | null
url: string | null
- setFile: (file: File) => void
+
+ // Original uploaded file (kept immutable)
+ originalFile: File | null
+ originalUrl: string | null
+
+ // Set the original file (called at upload time). This will also
+ // create a working copy so editors can modify `file` without
+ // mutating the original.
+ setOriginalFile: (file: File) => void
+
+ // Replace the working copy (e.g. when a library returns a modified Blob)
+ setWorkingFile: (fileOrBlob: File | Blob) => void
+
+ // Reset the working copy back to the original
+ resetWorking: () => void
+
clear: () => void
+
+ // Helpers to get ArrayBuffers from working/original
getArrayBuffer: () => Promise
+ getOriginalArrayBuffer: () => Promise
}
export const useImageStore = create((set, get) => ({
file: null,
url: null,
+ originalFile: null,
+ originalUrl: null,
+
+ setOriginalFile: (file: File) => {
+ // revoke previous original URL if present
+ const prevOriginal = get().originalUrl
+ if (prevOriginal) URL.revokeObjectURL(prevOriginal)
+ const originalUrl = URL.createObjectURL(file)
+
+ // revoke previous working URL if present
+ const prevWorking = get().url
+ if (prevWorking) URL.revokeObjectURL(prevWorking)
+
+ // create a new working copy (so modifications don't touch the original)
+ const workingCopy = new File([file], file.name, { type: file.type })
+ const workingUrl = URL.createObjectURL(workingCopy)
+
+ set({ originalFile: file, originalUrl, file: workingCopy, url: workingUrl })
+ },
- setFile: (file: File) => {
- // Revoke any previous object URL to avoid memory leaks
+ setWorkingFile: (fileOrBlob: File | Blob) => {
const prev = get().url
if (prev) URL.revokeObjectURL(prev)
- const url = URL.createObjectURL(file)
- set({ file, url })
+
+ let newFile: File
+ if (fileOrBlob instanceof File) {
+ newFile = fileOrBlob
+ } else {
+ const name = get().originalFile?.name ?? "edited"
+ const type =
+ (fileOrBlob as Blob).type ||
+ get().originalFile?.type ||
+ "application/octet-stream"
+ newFile = new File([fileOrBlob], name, { type })
+ }
+
+ const url = URL.createObjectURL(newFile)
+ set({ file: newFile, url })
+ },
+
+ resetWorking: () => {
+ const original = get().originalFile
+ const prev = get().url
+ if (prev) URL.revokeObjectURL(prev)
+ if (!original) {
+ set({ file: null, url: null })
+ return
+ }
+ const workingCopy = new File([original], original.name, {
+ type: original.type,
+ })
+ const workingUrl = URL.createObjectURL(workingCopy)
+ set({ file: workingCopy, url: workingUrl })
},
clear: () => {
+ const prevOriginal = get().originalUrl
+ if (prevOriginal) URL.revokeObjectURL(prevOriginal)
const prev = get().url
if (prev) URL.revokeObjectURL(prev)
- set({ file: null, url: null })
+ set({ file: null, url: null, originalFile: null, originalUrl: null })
},
getArrayBuffer: async () => {
@@ -31,4 +98,10 @@ export const useImageStore = create((set, get) => ({
if (!f) return null
return f.arrayBuffer()
},
+
+ getOriginalArrayBuffer: async () => {
+ const f = get().originalFile
+ if (!f) return null
+ return f.arrayBuffer()
+ },
}))
diff --git a/package-lock.json b/package-lock.json
index 4d7a4d6..250eadd 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -14,11 +14,13 @@
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"framer-motion": "^12.40.0",
+ "i": "^0.3.7",
"lucide-react": "^1.17.0",
"next": "16.2.6",
"next-themes": "^0.4.6",
"radix-ui": "^1.4.3",
"react": "19.2.4",
+ "react-compare-slider": "^4.0.0",
"react-dom": "19.2.4",
"shadcn": "^4.8.2",
"tailwind-merge": "^3.6.0",
@@ -457,9 +459,9 @@
}
},
"node_modules/@dotenvx/dotenvx": {
- "version": "1.69.1",
- "resolved": "https://registry.npmjs.org/@dotenvx/dotenvx/-/dotenvx-1.69.1.tgz",
- "integrity": "sha512-kwQB5KcAegxw/+NGUgXAo5ovyOSjlMhoXSSnSEpDhoHJwzMcMO0HE1U0VCYZ7jbAeCMGamed9XdWzOA5ixtTNg==",
+ "version": "1.69.2",
+ "resolved": "https://registry.npmjs.org/@dotenvx/dotenvx/-/dotenvx-1.69.2.tgz",
+ "integrity": "sha512-60Vh31k5bAAPq57Isx+Iopl2FrZQzZyUc4kh/OESgXiwhrKXqdxEn3JGTGSawng1DkkeC4jofFq1VpPJqNQx4w==",
"license": "BSD-3-Clause",
"dependencies": {
"commander": "^11.1.0",
@@ -1041,6 +1043,9 @@
"cpu": [
"arm"
],
+ "libc": [
+ "glibc"
+ ],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
@@ -1057,6 +1062,9 @@
"cpu": [
"arm64"
],
+ "libc": [
+ "glibc"
+ ],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
@@ -1073,6 +1081,9 @@
"cpu": [
"ppc64"
],
+ "libc": [
+ "glibc"
+ ],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
@@ -1089,6 +1100,9 @@
"cpu": [
"riscv64"
],
+ "libc": [
+ "glibc"
+ ],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
@@ -1105,6 +1119,9 @@
"cpu": [
"s390x"
],
+ "libc": [
+ "glibc"
+ ],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
@@ -1121,6 +1138,9 @@
"cpu": [
"x64"
],
+ "libc": [
+ "glibc"
+ ],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
@@ -1137,6 +1157,9 @@
"cpu": [
"arm64"
],
+ "libc": [
+ "musl"
+ ],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
@@ -1153,6 +1176,9 @@
"cpu": [
"x64"
],
+ "libc": [
+ "musl"
+ ],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
@@ -1169,6 +1195,9 @@
"cpu": [
"arm"
],
+ "libc": [
+ "glibc"
+ ],
"license": "Apache-2.0",
"optional": true,
"os": [
@@ -1191,6 +1220,9 @@
"cpu": [
"arm64"
],
+ "libc": [
+ "glibc"
+ ],
"license": "Apache-2.0",
"optional": true,
"os": [
@@ -1213,6 +1245,9 @@
"cpu": [
"ppc64"
],
+ "libc": [
+ "glibc"
+ ],
"license": "Apache-2.0",
"optional": true,
"os": [
@@ -1235,6 +1270,9 @@
"cpu": [
"riscv64"
],
+ "libc": [
+ "glibc"
+ ],
"license": "Apache-2.0",
"optional": true,
"os": [
@@ -1257,6 +1295,9 @@
"cpu": [
"s390x"
],
+ "libc": [
+ "glibc"
+ ],
"license": "Apache-2.0",
"optional": true,
"os": [
@@ -1279,6 +1320,9 @@
"cpu": [
"x64"
],
+ "libc": [
+ "glibc"
+ ],
"license": "Apache-2.0",
"optional": true,
"os": [
@@ -1301,6 +1345,9 @@
"cpu": [
"arm64"
],
+ "libc": [
+ "musl"
+ ],
"license": "Apache-2.0",
"optional": true,
"os": [
@@ -1323,6 +1370,9 @@
"cpu": [
"x64"
],
+ "libc": [
+ "musl"
+ ],
"license": "Apache-2.0",
"optional": true,
"os": [
@@ -1415,25 +1465,25 @@
}
},
"node_modules/@inquirer/ansi": {
- "version": "2.0.6",
- "resolved": "https://registry.npmjs.org/@inquirer/ansi/-/ansi-2.0.6.tgz",
- "integrity": "sha512-I/INw4sHGlVZ/afZOckpLiDP9SmbMl1g/GCqeHjLw1Afw/0PlRs2tRFgTGWmdI0hoNuWZn3y2iHNmG1vyECyQQ==",
+ "version": "2.0.7",
+ "resolved": "https://registry.npmjs.org/@inquirer/ansi/-/ansi-2.0.7.tgz",
+ "integrity": "sha512-3eTuUO1vH2cZm2ZKHeQxnOqlTi9EfZDGgIe3BL3I4u+rJHocr9Fz86M4fjYABPvFnQG/gGK551HqDiIcETwU6Q==",
"license": "MIT",
"engines": {
- "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0"
+ "node": ">=23.5.0 || ^22.13.0 || ^20.17.0"
}
},
"node_modules/@inquirer/confirm": {
- "version": "6.1.0",
- "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-6.1.0.tgz",
- "integrity": "sha512-USpeB76eqK7yGricDlGAupxWlp4a59qpeZOoNWaxO/nJln7agpJveyNkQ1d5u8YXG6TOqxZtQpKPORQQDrdVsA==",
+ "version": "6.1.1",
+ "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-6.1.1.tgz",
+ "integrity": "sha512-eb8DBZcz/2qHWQda4rk2JiQk5h9QV/cVHi1yjt0f69WFZMRFn0sJTye3EAP8icut8UDMjQPsaH5KbcOogefrFQ==",
"license": "MIT",
"dependencies": {
- "@inquirer/core": "^11.2.0",
- "@inquirer/type": "^4.0.6"
+ "@inquirer/core": "^11.2.1",
+ "@inquirer/type": "^4.0.7"
},
"engines": {
- "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0"
+ "node": ">=23.5.0 || ^22.13.0 || ^20.17.0"
},
"peerDependencies": {
"@types/node": ">=18"
@@ -1445,21 +1495,21 @@
}
},
"node_modules/@inquirer/core": {
- "version": "11.2.0",
- "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-11.2.0.tgz",
- "integrity": "sha512-joR1YS2sI0us+9d0I8ViqFbrRLONO8CFTuyvBX4ZVBSch+VsZiugUABdrhBXXJR1VyEzvpz5SQCix3keETQ58g==",
+ "version": "11.2.1",
+ "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-11.2.1.tgz",
+ "integrity": "sha512-Qd6GJT1yVyrZZCfN8W2qKF5ApmqryXRhRKCuip8h01x2w/esJQ2XIYc6f9abMIHgKQdBfFTSOdbHRLAhuM09UA==",
"license": "MIT",
"dependencies": {
- "@inquirer/ansi": "^2.0.6",
- "@inquirer/figures": "^2.0.6",
- "@inquirer/type": "^4.0.6",
+ "@inquirer/ansi": "^2.0.7",
+ "@inquirer/figures": "^2.0.7",
+ "@inquirer/type": "^4.0.7",
"cli-width": "^4.1.0",
"fast-wrap-ansi": "^0.2.0",
- "mute-stream": "^4.0.0",
+ "mute-stream": "^3.0.0",
"signal-exit": "^4.1.0"
},
"engines": {
- "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0"
+ "node": ">=23.5.0 || ^22.13.0 || ^20.17.0"
},
"peerDependencies": {
"@types/node": ">=18"
@@ -1471,21 +1521,21 @@
}
},
"node_modules/@inquirer/figures": {
- "version": "2.0.6",
- "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-2.0.6.tgz",
- "integrity": "sha512-dsZgQtH2t5Q6ah3aPbZbeEZAxsD9qQu0DXf01AltuEfRTm+NoLN6+rLVbr+4edeEbNCp/wBNM6mALRWtsQpfkw==",
+ "version": "2.0.7",
+ "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-2.0.7.tgz",
+ "integrity": "sha512-aJ8TBPOGB6f/2qziPfElISTCEd5XOYTFckA2SGjhNmiKzfK/u4ot3v0DUzGVdUnKjN10EqnnEPck36BkyfLnJw==",
"license": "MIT",
"engines": {
- "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0"
+ "node": ">=23.5.0 || ^22.13.0 || ^20.17.0"
}
},
"node_modules/@inquirer/type": {
- "version": "4.0.6",
- "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-4.0.6.tgz",
- "integrity": "sha512-J+9tdxOskuYuGjsvGaq00AamhDgjR7anhEW2dP4QdQpFCMPngCeC/bCYWQ5NsMWZRdsy53is7kAHb/+7cwDk2g==",
+ "version": "4.0.7",
+ "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-4.0.7.tgz",
+ "integrity": "sha512-t28inv14nMQ1PhKpsJPY+kEs/c00qzeCOS2gTNRyTjG5d6qsVA2fItxW4hkvGZ5lvanGLdtCzVIx5dwdRpN1+g==",
"license": "MIT",
"engines": {
- "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0"
+ "node": ">=23.5.0 || ^22.13.0 || ^20.17.0"
},
"peerDependencies": {
"@types/node": ">=18"
@@ -1700,6 +1750,9 @@
"cpu": [
"arm64"
],
+ "libc": [
+ "glibc"
+ ],
"license": "MIT",
"optional": true,
"os": [
@@ -1716,6 +1769,9 @@
"cpu": [
"arm64"
],
+ "libc": [
+ "musl"
+ ],
"license": "MIT",
"optional": true,
"os": [
@@ -1732,6 +1788,9 @@
"cpu": [
"x64"
],
+ "libc": [
+ "glibc"
+ ],
"license": "MIT",
"optional": true,
"os": [
@@ -1748,6 +1807,9 @@
"cpu": [
"x64"
],
+ "libc": [
+ "musl"
+ ],
"license": "MIT",
"optional": true,
"os": [
@@ -3560,6 +3622,9 @@
"arm64"
],
"dev": true,
+ "libc": [
+ "glibc"
+ ],
"license": "MIT",
"optional": true,
"os": [
@@ -3577,6 +3642,9 @@
"arm64"
],
"dev": true,
+ "libc": [
+ "musl"
+ ],
"license": "MIT",
"optional": true,
"os": [
@@ -3594,6 +3662,9 @@
"x64"
],
"dev": true,
+ "libc": [
+ "glibc"
+ ],
"license": "MIT",
"optional": true,
"os": [
@@ -3611,6 +3682,9 @@
"x64"
],
"dev": true,
+ "libc": [
+ "musl"
+ ],
"license": "MIT",
"optional": true,
"os": [
@@ -4256,6 +4330,9 @@
"arm64"
],
"dev": true,
+ "libc": [
+ "glibc"
+ ],
"license": "MIT",
"optional": true,
"os": [
@@ -4270,6 +4347,9 @@
"arm64"
],
"dev": true,
+ "libc": [
+ "musl"
+ ],
"license": "MIT",
"optional": true,
"os": [
@@ -4284,6 +4364,9 @@
"loong64"
],
"dev": true,
+ "libc": [
+ "glibc"
+ ],
"license": "MIT",
"optional": true,
"os": [
@@ -4298,6 +4381,9 @@
"loong64"
],
"dev": true,
+ "libc": [
+ "musl"
+ ],
"license": "MIT",
"optional": true,
"os": [
@@ -4312,6 +4398,9 @@
"ppc64"
],
"dev": true,
+ "libc": [
+ "glibc"
+ ],
"license": "MIT",
"optional": true,
"os": [
@@ -4326,6 +4415,9 @@
"riscv64"
],
"dev": true,
+ "libc": [
+ "glibc"
+ ],
"license": "MIT",
"optional": true,
"os": [
@@ -4340,6 +4432,9 @@
"riscv64"
],
"dev": true,
+ "libc": [
+ "musl"
+ ],
"license": "MIT",
"optional": true,
"os": [
@@ -4354,6 +4449,9 @@
"s390x"
],
"dev": true,
+ "libc": [
+ "glibc"
+ ],
"license": "MIT",
"optional": true,
"os": [
@@ -4368,6 +4466,9 @@
"x64"
],
"dev": true,
+ "libc": [
+ "glibc"
+ ],
"license": "MIT",
"optional": true,
"os": [
@@ -4382,6 +4483,9 @@
"x64"
],
"dev": true,
+ "libc": [
+ "musl"
+ ],
"license": "MIT",
"optional": true,
"os": [
@@ -4858,9 +4962,9 @@
"license": "MIT"
},
"node_modules/baseline-browser-mapping": {
- "version": "2.10.32",
- "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.32.tgz",
- "integrity": "sha512-wbPvpyjJPC0zdfdKXxqEL3Ea+bOMD/87X4lftiJkkaBiuG6ALQy1SLmEd7BSmVCuwCQsBrCamgBoLyfFDD1EPg==",
+ "version": "2.10.33",
+ "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.33.tgz",
+ "integrity": "sha512-bA6+tcSLpz2tIEdDXZPpPTIuxBcC4+w6SieaYyfigIa4h8GlFxbA17v22Vx3JUtuZQj9SgOsnbK+aTBzyDyEuw==",
"license": "Apache-2.0",
"bin": {
"baseline-browser-mapping": "dist/cli.cjs"
@@ -5616,9 +5720,9 @@
"license": "MIT"
},
"node_modules/electron-to-chromium": {
- "version": "1.5.363",
- "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.363.tgz",
- "integrity": "sha512-VjUKPyWzGnT1fujlkEGC/BvN70Hh70KXtAqcmniXviYlJC/ivcT+BWGPyxWVbJZLfvtKR6dqg1L7T7pgAMBtWA==",
+ "version": "1.5.364",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.364.tgz",
+ "integrity": "sha512-G/dYE3+AYhyHwzTwg8UbnXf7zqMERYh7l2jJ3QujhFsH8agSYwtnGAR2aZ7f0AakIKJXd5En/Hre4igIUrdlYw==",
"license": "ISC"
},
"node_modules/emoji-regex": {
@@ -7159,6 +7263,14 @@
"node": ">=18.18.0"
}
},
+ "node_modules/i": {
+ "version": "0.3.7",
+ "resolved": "https://registry.npmjs.org/i/-/i-0.3.7.tgz",
+ "integrity": "sha512-FYz4wlXgkQwIPqhzC5TdNMLSE5+GS1IIDJZY/1ZiEPCT2S3COUVZeT5OW4BmW4r5LHLQuOosSwsvnroG9GR59Q==",
+ "engines": {
+ "node": ">=0.4"
+ }
+ },
"node_modules/iconv-lite": {
"version": "0.7.2",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz",
@@ -8173,6 +8285,9 @@
"arm64"
],
"dev": true,
+ "libc": [
+ "glibc"
+ ],
"license": "MPL-2.0",
"optional": true,
"os": [
@@ -8194,6 +8309,9 @@
"arm64"
],
"dev": true,
+ "libc": [
+ "musl"
+ ],
"license": "MPL-2.0",
"optional": true,
"os": [
@@ -8215,6 +8333,9 @@
"x64"
],
"dev": true,
+ "libc": [
+ "glibc"
+ ],
"license": "MPL-2.0",
"optional": true,
"os": [
@@ -8236,6 +8357,9 @@
"x64"
],
"dev": true,
+ "libc": [
+ "musl"
+ ],
"license": "MPL-2.0",
"optional": true,
"os": [
@@ -8606,12 +8730,12 @@
}
},
"node_modules/mute-stream": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-4.0.0.tgz",
- "integrity": "sha512-gSrprq0fJ3EiOErzjdIZrjysVVmJ4uu1QWfCDss5LypA5OXvrMje5Ym5z6V6RLyJ2eF87lasX7t6a0AnFvZblg==",
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-3.0.0.tgz",
+ "integrity": "sha512-dkEJPVvun4FryqBmZ5KhDo0K9iDXAwn08tMLDinNdRBNPcYEDiWYysLcc6k3mjTMlbP9KyylvRpd4wFtwrT9rw==",
"license": "ISC",
"engines": {
- "node": "^22.22.2 || ^24.15.0 || >=26.0.0"
+ "node": "^20.17.0 || >=22.9.0"
}
},
"node_modules/nanoid": {
@@ -9672,6 +9796,19 @@
"node": ">=0.10.0"
}
},
+ "node_modules/react-compare-slider": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/react-compare-slider/-/react-compare-slider-4.0.0.tgz",
+ "integrity": "sha512-XNpwfWa8OHvJZIsC+ywhjpRWX8VCY8fIavQ82R63Tever1pQndhicr0ecDuU69GjA+YjesoV7LX3QnKsECtMDw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=20.0.0"
+ },
+ "peerDependencies": {
+ "react": ">=16.8",
+ "react-dom": ">=16.8"
+ }
+ },
"node_modules/react-dom": {
"version": "19.2.4",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz",
@@ -10157,9 +10294,9 @@
"license": "ISC"
},
"node_modules/shadcn": {
- "version": "4.8.2",
- "resolved": "https://registry.npmjs.org/shadcn/-/shadcn-4.8.2.tgz",
- "integrity": "sha512-pt3KneOg6LGYKNAdoTVf/lpVcf7t2MlV+Ll2Xc3lIIYN3ph4ajrjU+CcG6OVSgO5ubbLZj+9j5oMA9Lqg7o8KA==",
+ "version": "4.8.3",
+ "resolved": "https://registry.npmjs.org/shadcn/-/shadcn-4.8.3.tgz",
+ "integrity": "sha512-cnP485rIqtDb8waOp+IKUIfifVf64/PCd5VX/nnLeIP+qZ3yS4r6FOQtOdQkoQEvomQUsJS2OHr5CTzKWw0wKQ==",
"license": "MIT",
"dependencies": {
"@babel/core": "^7.28.0",
@@ -10860,21 +10997,21 @@
}
},
"node_modules/tldts": {
- "version": "7.4.0",
- "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.4.0.tgz",
- "integrity": "sha512-yHBe+zVfzNZ3QfTPW/Z6KK1G2t340gFjMHqI/4KKSt/abzYydzuCnpqdaF5gCCABby+9Yfbj59oR5F2Fd5CBzg==",
+ "version": "7.4.2",
+ "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.4.2.tgz",
+ "integrity": "sha512-kCwffuaH8ntKtygnWe1b4BJKWiCUH30n5KfoTr6IchcXOwR7chAOFJxFrH3vjANafUYrIA4a7SDL+nn7SiR4Sw==",
"license": "MIT",
"dependencies": {
- "tldts-core": "^7.4.0"
+ "tldts-core": "^7.4.2"
},
"bin": {
"tldts": "bin/cli.js"
}
},
"node_modules/tldts-core": {
- "version": "7.4.1",
- "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.4.1.tgz",
- "integrity": "sha512-sc2nGvGbixlJRHwTh/qQdPXTxJU1UDJboGPQm4d/01YUJ9r/u6aeIulQvEaxUlvKDN7hb1qCLjax+jhVAPLa/g==",
+ "version": "7.4.2",
+ "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.4.2.tgz",
+ "integrity": "sha512-nwEyF4vl4RSJjwSjBUmOSxc3BFPoIFdlRthJ6e+5v9P3bHNsoD06UjuqMUspqp7vsEZ1beaHi1km+optiE17yA==",
"license": "MIT"
},
"node_modules/to-regex-range": {
diff --git a/package.json b/package.json
index 1a850fe..eea53b0 100644
--- a/package.json
+++ b/package.json
@@ -15,11 +15,13 @@
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"framer-motion": "^12.40.0",
+ "i": "^0.3.7",
"lucide-react": "^1.17.0",
"next": "16.2.6",
"next-themes": "^0.4.6",
"radix-ui": "^1.4.3",
"react": "19.2.4",
+ "react-compare-slider": "^4.0.0",
"react-dom": "19.2.4",
"shadcn": "^4.8.2",
"tailwind-merge": "^3.6.0",
diff --git a/public/hackerman.png b/public/hackerman.png
new file mode 100644
index 0000000..6854661
Binary files /dev/null and b/public/hackerman.png differ