diff --git a/src/App.jsx b/src/App.jsx
index feec0c5..8931724 100644
--- a/src/App.jsx
+++ b/src/App.jsx
@@ -33,6 +33,7 @@ import ListResources from "./pages/ResourceHub/resourcehub/ListResources";
import Base64Image from "./pages/DevUtilities/devutilities/Base64Image";
import Base64Url from "./pages/DevUtilities/devutilities/Base64Url";
import BcryptGenerator from "./pages/DevUtilities/devutilities/BcryptGenerator";
+import BorderImageGenerator from "./pages/DevUtilities/devutilities/BorderImageGenerator";
import ChmodCalculator from "./pages/DevUtilities/devutilities/ChmodCalculator";
import ClipPathMaker from "./pages/DevUtilities/devutilities/ClipPathMaker";
import CodeSandbox from "./pages/DevUtilities/devutilities/CodeSandbox";
@@ -501,6 +502,10 @@ function AppInner({ toggleHUD, hudVisible }) {
path="/devutilities/css-gradient"
element={}
/>
+ }
+ />
}
diff --git a/src/config/sidebarSections.js b/src/config/sidebarSections.js
index c59199a..2e164a5 100644
--- a/src/config/sidebarSections.js
+++ b/src/config/sidebarSections.js
@@ -297,6 +297,12 @@ const SIDEBAR_SECTIONS = [
description: "Create beautiful CSS gradients with live preview",
path: "/devutilities/css-gradient",
},
+ {
+ label: "CSS Border-Image Generator",
+ description:
+ "Design custom sliced image borders and generate copy-ready CSS properties visually.",
+ path: "/devutilities/border-image",
+ },
{
label: "Glassmorphism Playground",
description: "Design glassmorphism effects with shadows and blur",
diff --git a/src/pages/DevUtilities/DevUtilities.jsx b/src/pages/DevUtilities/DevUtilities.jsx
index 1aa9d8e..a867b5f 100644
--- a/src/pages/DevUtilities/DevUtilities.jsx
+++ b/src/pages/DevUtilities/DevUtilities.jsx
@@ -964,6 +964,27 @@ const DevUtilities = () => {
),
},
+ {
+ title: "CSS Border-Image Generator",
+ description:
+ "Design custom sliced image borders and generate copy-ready CSS properties visually.",
+ path: "/devutilities/border-image",
+ icon: (
+
+ ),
+ },
{
title: "Password Generator",
description:
diff --git a/src/pages/DevUtilities/devutilities/BorderImageGenerator.jsx b/src/pages/DevUtilities/devutilities/BorderImageGenerator.jsx
new file mode 100644
index 0000000..d7666dc
--- /dev/null
+++ b/src/pages/DevUtilities/devutilities/BorderImageGenerator.jsx
@@ -0,0 +1,462 @@
+import { useRef, useState } from "react";
+import { Link } from "react-router-dom";
+import { useTheme } from "../../../context/ThemeContext";
+import { toast } from "sonner";
+import {
+ FaArrowLeft,
+ FaBorderStyle,
+ FaCopy,
+ FaImage,
+ FaLink,
+ FaUnlink,
+ FaUpload,
+} from "react-icons/fa";
+
+// Centers of the 8 outer cells of a 90x90 asset sliced into a 3x3 grid of 30px cells
+const CELL_CENTERS = [
+ [15, 15],
+ [45, 15],
+ [75, 15],
+ [15, 45],
+ [75, 45],
+ [15, 75],
+ [45, 75],
+ [75, 75],
+];
+
+const makeSvg = (shapes) =>
+ "data:image/svg+xml," +
+ encodeURIComponent(
+ ``
+ );
+
+const defaultAssets = [
+ {
+ name: "Diamonds",
+ slice: 30,
+ url: makeSvg(
+ CELL_CENTERS.map(
+ ([x, y]) =>
+ ``
+ ).join("")
+ ),
+ },
+ {
+ name: "Dots",
+ slice: 30,
+ url: makeSvg(
+ CELL_CENTERS.map(
+ ([x, y]) => ``
+ ).join("")
+ ),
+ },
+ {
+ name: "Frame",
+ slice: 30,
+ url: makeSvg(
+ `` +
+ `` +
+ `` +
+ `` +
+ ``
+ ),
+ },
+ {
+ name: "Stripes",
+ slice: 30,
+ url: makeSvg(
+ `` +
+ ``
+ ),
+ },
+];
+
+const repeatOptions = ["stretch", "repeat", "round", "space"];
+const sliceSides = ["top", "right", "bottom", "left"];
+
+export default function BorderImageGenerator() {
+ const { dark } = useTheme();
+ const fileInputRef = useRef(null);
+
+ const [source, setSource] = useState(defaultAssets[0].url);
+ const [sourceName, setSourceName] = useState(defaultAssets[0].name);
+ const [slices, setSlices] = useState({ top: 30, right: 30, bottom: 30, left: 30 });
+ const [linked, setLinked] = useState(true);
+ const [sliceUnit, setSliceUnit] = useState("");
+ const [fill, setFill] = useState(false);
+ const [repeatX, setRepeatX] = useState("round");
+ const [repeatY, setRepeatY] = useState("round");
+ const [borderWidth, setBorderWidth] = useState(20);
+ const [dragging, setDragging] = useState(false);
+
+ const theme = {
+ light: {
+ wrapper: "bg-[#F8F9FA] text-zinc-900",
+ heading: "text-zinc-900",
+ subtext: "text-zinc-500",
+ card: "bg-white border-zinc-200/85 shadow-sm",
+ input: "bg-zinc-50 border-zinc-200 text-zinc-900 focus:border-zinc-400 focus:outline-none",
+ secondaryBtn: "bg-white text-zinc-800 border-zinc-200 hover:bg-zinc-50 transition-all duration-200",
+ backLink: "bg-white border-neutral-200 text-neutral-600 hover:text-black hover:border-neutral-350",
+ codeBox: "bg-zinc-900 text-zinc-100 border-zinc-800",
+ dropzone: "border-zinc-300 bg-zinc-50/60 text-zinc-500",
+ dropzoneActive: "border-indigo-400 bg-indigo-50 text-indigo-600",
+ },
+ dark: {
+ wrapper: "bg-[#090A0F] text-zinc-100",
+ heading: "text-zinc-100",
+ subtext: "text-zinc-500",
+ card: "bg-zinc-900/50 border-zinc-800/85 backdrop-blur-md shadow-md",
+ input: "bg-zinc-900 border-zinc-700 text-zinc-100 focus:border-zinc-500 focus:outline-none",
+ secondaryBtn: "bg-zinc-800/50 text-zinc-300 border-zinc-700 hover:bg-zinc-700/50 transition-all duration-200",
+ backLink: "bg-zinc-800/80 border-zinc-700 text-zinc-300 hover:text-white hover:border-zinc-600",
+ codeBox: "bg-black/40 text-emerald-400 border-zinc-800/80 font-mono",
+ dropzone: "border-zinc-700 bg-zinc-900/40 text-zinc-500",
+ dropzoneActive: "border-indigo-500 bg-indigo-500/10 text-indigo-400",
+ },
+ };
+
+ const t = dark ? theme.dark : theme.light;
+
+ const updateSlice = (side, rawValue) => {
+ const value = Math.min(100, Math.max(0, Number(rawValue) || 0));
+ if (linked) {
+ setSlices({ top: value, right: value, bottom: value, left: value });
+ } else {
+ setSlices((prev) => ({ ...prev, [side]: value }));
+ }
+ };
+
+ const loadDefaultAsset = (asset) => {
+ setSource(asset.url);
+ setSourceName(asset.name);
+ setSlices({ top: asset.slice, right: asset.slice, bottom: asset.slice, left: asset.slice });
+ setSliceUnit("");
+ toast.success(`Loaded ${asset.name} asset`);
+ };
+
+ const loadFile = (file) => {
+ if (!file || !file.type.startsWith("image/")) {
+ toast.error("Please drop an image file (SVG, PNG, JPG, ...)");
+ return;
+ }
+ const reader = new FileReader();
+ reader.onload = () => {
+ setSource(reader.result);
+ setSourceName(file.name);
+ toast.success(`Loaded ${file.name}`);
+ };
+ reader.onerror = () => toast.error("Could not read the image file");
+ reader.readAsDataURL(file);
+ };
+
+ const sliceValue = `${sliceSides.map((s) => `${slices[s]}${sliceUnit}`).join(" ")}${fill ? " fill" : ""}`;
+ const repeatValue = repeatX === repeatY ? repeatX : `${repeatX} ${repeatY}`;
+
+ const buildCSS = (url) =>
+ [
+ `border-style: solid;`,
+ `border-width: ${borderWidth}px;`,
+ `border-image-source: url('${url}');`,
+ `border-image-slice: ${sliceValue};`,
+ `border-image-repeat: ${repeatValue};`,
+ ].join("\n");
+
+ const displayUrl = source.length > 72 ? `${source.slice(0, 72)}…` : source;
+
+ const copyCSS = () => {
+ navigator.clipboard.writeText(buildCSS(source));
+ toast.success("Copied CSS to clipboard!");
+ };
+
+ const previewStyle = {
+ borderStyle: "solid",
+ borderWidth: `${borderWidth}px`,
+ borderImageSource: `url('${source}')`,
+ borderImageSlice: sliceValue,
+ borderImageRepeat: repeatValue,
+ resize: "both",
+ overflow: "auto",
+ };
+
+ return (
+
+
CSS Border-Image Generator — DevTasks
+
+
+
+ {/* Header */}
+
+
+
+
+
+
+ CSS Border-Image Generator
+
+
+ Slice a border asset visually and export copy-paste border-image CSS.
+
+
+
+
+
+ {/* Left: Controls & Slice Guides */}
+
+ {/* Asset loader */}
+
+
+
+ Load Asset
+
+
+
+ {defaultAssets.map((asset) => (
+
+ ))}
+
+
+
{
+ e.preventDefault();
+ setDragging(true);
+ }}
+ onDragLeave={() => setDragging(false)}
+ onDrop={(e) => {
+ e.preventDefault();
+ setDragging(false);
+ loadFile(e.dataTransfer.files[0]);
+ }}
+ onClick={() => fileInputRef.current?.click()}
+ className={`rounded-2xl border-2 border-dashed p-5 text-center cursor-pointer transition-colors duration-200 ${
+ dragging ? t.dropzoneActive : t.dropzone
+ }`}
+ >
+
+
+ Drag & drop a custom border image here, or click to browse
+
+
+ Loaded: {sourceName}
+
+
{
+ loadFile(e.target.files[0]);
+ e.target.value = "";
+ }}
+ />
+
+
+
+ {/* Slice guides */}
+
+
+
+
+ Slice Guides
+
+
+
+
+
+
+
+
+ {sliceSides.map((side) => (
+
+ ))}
+
+
+
+
+
+ {/* Repeat & width */}
+
+
Repeat & Border Width
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ setBorderWidth(Number(e.target.value))}
+ className="flex-1 accent-indigo-500 h-1.5 bg-zinc-200 dark:bg-zinc-800 rounded-lg cursor-pointer"
+ />
+
+
+
+
+ {/* Right: Live Preview & Code Output */}
+
+
+
Live Preview
+
+ Drag the bottom-right corner of the box to resize it and see how the border adapts.
+
+
+
+
+ Resizable preview
+
+
+
+
+
+
CSS Code Output
+
+
+ {buildCSS(displayUrl)}
+
+
+
+
+ Long image URLs are truncated in the display above — the copied CSS always contains the
+ full border-image-source.
+
+
+
+
+
+
+ );
+}