diff --git a/.eslintrc.json b/.eslintrc.json
new file mode 100644
index 0000000..bffb357
--- /dev/null
+++ b/.eslintrc.json
@@ -0,0 +1,3 @@
+{
+ "extends": "next/core-web-vitals"
+}
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..8f322f0
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,35 @@
+# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
+
+# dependencies
+/node_modules
+/.pnp
+.pnp.js
+
+# testing
+/coverage
+
+# next.js
+/.next/
+/out/
+
+# production
+/build
+
+# misc
+.DS_Store
+*.pem
+
+# debug
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+
+# local env files
+.env*.local
+
+# vercel
+.vercel
+
+# typescript
+*.tsbuildinfo
+next-env.d.ts
diff --git a/app/about/page.tsx b/app/about/page.tsx
new file mode 100644
index 0000000..5e32039
--- /dev/null
+++ b/app/about/page.tsx
@@ -0,0 +1,65 @@
+import { Card } from "@/components/ui/card";
+import { Frame, Sparkles, Shield, Zap } from "lucide-react";
+
+export default function About() {
+ return (
+
+
+
About SAAZ FRAMER
+
+ SAAZ FRAMER is a professional image framing tool designed for photographers, artists, and creative professionals who want to enhance their work with beautiful frames and watermarks.
+
+
+
+
+
+
+
+
+
Professional Quality
+
+ Create stunning, high-quality framed images that stand out in any portfolio or gallery.
+
+
+
+
+
+
+
+
+
+
Protect Your Work
+
+ Add custom watermarks to protect your intellectual property while maintaining image quality.
+
+
+
+
+
+
+
+
+
+
Fast & Easy
+
+ Intuitive interface designed for efficiency, letting you create beautiful frames in minutes.
+
+
+
+
+
+
+
+
+
+
Custom Frames
+
+ Use our premium frame collection or upload your own custom frames for unique results.
+
+
+
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/app/admin/page.tsx b/app/admin/page.tsx
new file mode 100644
index 0000000..316e7c2
--- /dev/null
+++ b/app/admin/page.tsx
@@ -0,0 +1,134 @@
+"use client";
+
+import { useState } from "react";
+import { Upload, Settings, Save } from "lucide-react";
+import { Button } from "@/components/ui/button";
+import { Card } from "@/components/ui/card";
+import { Input } from "@/components/ui/input";
+import { Label } from "@/components/ui/label";
+import { Separator } from "@/components/ui/separator";
+import { Slider } from "@/components/ui/slider";
+import { toast } from "sonner";
+
+export default function Admin() {
+ const [frameImage, setFrameImage] = useState(null);
+ const [opacity, setOpacity] = useState([0.8]);
+ const [scale, setScale] = useState([1]);
+
+ const handleFrameUpload = (e: React.ChangeEvent) => {
+ const file = e.target.files?.[0];
+ if (!file) return;
+
+ if (!file.type.startsWith("image/")) {
+ toast.error("Please upload an image file");
+ return;
+ }
+
+ const reader = new FileReader();
+ reader.onload = (event) => {
+ setFrameImage(event.target?.result as string);
+ toast.success("Frame uploaded successfully");
+ };
+ reader.readAsDataURL(file);
+ };
+
+ const saveSettings = () => {
+ // Save settings to localStorage
+ const settings = {
+ frameImage,
+ opacity: opacity[0],
+ scale: scale[0],
+ };
+ localStorage.setItem("saazFramerSettings", JSON.stringify(settings));
+ toast.success("Settings saved successfully");
+ };
+
+ return (
+
+
+
Admin Settings
+
+
+ {/* Left Column - Settings */}
+
+
Frame Settings
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {/* Right Column - Preview */}
+
+
Frame Preview
+
+
+ {!frameImage ? (
+
+
+
+ Upload a frame to see preview
+
+
+ ) : (
+
+
+
+ )}
+
+
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/app/contact/page.tsx b/app/contact/page.tsx
new file mode 100644
index 0000000..d19d21c
--- /dev/null
+++ b/app/contact/page.tsx
@@ -0,0 +1,102 @@
+"use client";
+
+import { Card } from "@/components/ui/card";
+import { Button } from "@/components/ui/button";
+import { Input } from "@/components/ui/input";
+import { Label } from "@/components/ui/label";
+import { Textarea } from "@/components/ui/textarea";
+import { toast } from "sonner";
+import { Mail, MessageSquare, Send } from "lucide-react";
+
+export default function Contact() {
+ const handleSubmit = (e: React.FormEvent) => {
+ e.preventDefault();
+ const formData = new FormData(e.target as HTMLFormElement);
+ const data = {
+ name: formData.get('name'),
+ email: formData.get('email'),
+ message: formData.get('message'),
+ };
+
+ // Send email using mailto link
+ const subject = encodeURIComponent('SAAZ Framer');
+ const body = encodeURIComponent(`
+Name: ${data.name}
+Email: ${data.email}
+
+Message:
+${data.message}
+ `);
+ window.location.href = `mailto:sahalbuac@gmail.com?subject=${subject}&body=${body}`;
+
+ toast.success("Opening your email client...");
+ };
+
+ return (
+
+
+
Contact Us
+
+ Have questions or feedback? We'd love to hear from you. Send us a message and we'll respond as soon as possible.
+
+ );
+}
\ No newline at end of file
diff --git a/app/globals.css b/app/globals.css
new file mode 100644
index 0000000..20b1c1d
--- /dev/null
+++ b/app/globals.css
@@ -0,0 +1,82 @@
+@tailwind base;
+@tailwind components;
+@tailwind utilities;
+
+:root {
+ --foreground-rgb: 0, 0, 0;
+ --background-start-rgb: 214, 219, 220;
+ --background-end-rgb: 255, 255, 255;
+}
+
+@media (prefers-color-scheme: dark) {
+ :root {
+ --foreground-rgb: 255, 255, 255;
+ --background-start-rgb: 0, 0, 0;
+ --background-end-rgb: 0, 0, 0;
+ }
+}
+
+@layer base {
+ :root {
+ --background: 0 0% 100%;
+ --foreground: 0 0% 3.9%;
+ --card: 0 0% 100%;
+ --card-foreground: 0 0% 3.9%;
+ --popover: 0 0% 100%;
+ --popover-foreground: 0 0% 3.9%;
+ --primary: 0 0% 9%;
+ --primary-foreground: 0 0% 98%;
+ --secondary: 0 0% 96.1%;
+ --secondary-foreground: 0 0% 9%;
+ --muted: 0 0% 96.1%;
+ --muted-foreground: 0 0% 45.1%;
+ --accent: 0 0% 96.1%;
+ --accent-foreground: 0 0% 9%;
+ --destructive: 0 84.2% 60.2%;
+ --destructive-foreground: 0 0% 98%;
+ --border: 0 0% 89.8%;
+ --input: 0 0% 89.8%;
+ --ring: 0 0% 3.9%;
+ --chart-1: 12 76% 61%;
+ --chart-2: 173 58% 39%;
+ --chart-3: 197 37% 24%;
+ --chart-4: 43 74% 66%;
+ --chart-5: 27 87% 67%;
+ --radius: 0.5rem;
+ }
+ .dark {
+ --background: 0 0% 3.9%;
+ --foreground: 0 0% 98%;
+ --card: 0 0% 3.9%;
+ --card-foreground: 0 0% 98%;
+ --popover: 0 0% 3.9%;
+ --popover-foreground: 0 0% 98%;
+ --primary: 0 0% 98%;
+ --primary-foreground: 0 0% 9%;
+ --secondary: 0 0% 14.9%;
+ --secondary-foreground: 0 0% 98%;
+ --muted: 0 0% 14.9%;
+ --muted-foreground: 0 0% 63.9%;
+ --accent: 0 0% 14.9%;
+ --accent-foreground: 0 0% 98%;
+ --destructive: 0 62.8% 30.6%;
+ --destructive-foreground: 0 0% 98%;
+ --border: 0 0% 14.9%;
+ --input: 0 0% 14.9%;
+ --ring: 0 0% 83.1%;
+ --chart-1: 220 70% 50%;
+ --chart-2: 160 60% 45%;
+ --chart-3: 30 80% 55%;
+ --chart-4: 280 65% 60%;
+ --chart-5: 340 75% 55%;
+ }
+}
+
+@layer base {
+ * {
+ @apply border-border;
+ }
+ body {
+ @apply bg-background text-foreground;
+ }
+}
diff --git a/app/how-to-use/page.tsx b/app/how-to-use/page.tsx
new file mode 100644
index 0000000..cb8a22e
--- /dev/null
+++ b/app/how-to-use/page.tsx
@@ -0,0 +1,131 @@
+import { Card } from "@/components/ui/card";
+import { Frame, Upload, Type, Download, Settings, Image as ImageIcon, Crop, Palette, Share } from "lucide-react";
+
+const steps = [
+ {
+ icon: Upload,
+ title: "Upload Your Frame Template",
+ description: "Start by uploading your frame template. Use a high-resolution PNG with transparent background for best results.",
+ tips: [
+ "Use PNG format for transparency",
+ "Recommended size: 2000x2000 pixels",
+ "Keep file size under 5MB for optimal performance"
+ ]
+ },
+ {
+ icon: ImageIcon,
+ title: "Select Your Images",
+ description: "Choose the photos you want to frame. You can upload multiple images at once for batch processing.",
+ tips: [
+ "Supports JPG, PNG, and WebP formats",
+ "No file size limitation",
+ "Maintains original image quality"
+ ]
+ },
+ {
+ icon: Crop,
+ title: "Crop and Adjust",
+ description: "Use our intuitive cropping tool to perfectly position your images within the frame.",
+ tips: [
+ "Maintains aspect ratio automatically",
+ "Drag to reposition the crop area",
+ "Preview before applying changes"
+ ]
+ },
+ {
+ icon: Settings,
+ title: "Customize Frame Settings",
+ description: "Fine-tune the frame appearance by adjusting opacity and other parameters to achieve the perfect look.",
+ tips: [
+ "Adjust frame opacity",
+ "Preview changes in real-time",
+ "Save settings for future use"
+ ]
+ },
+ {
+ icon: Download,
+ title: "Export Your Creation",
+ description: "Download your framed images in high quality, ready for sharing or printing.",
+ tips: [
+ "Exports in high-resolution JPEG",
+ "Batch download all images",
+ "Maintains original image quality"
+ ]
+ },
+ {
+ icon: Share,
+ title: "Share Your Work",
+ description: "Share your beautifully framed images directly on social media or with your clients.",
+ tips: [
+ "Perfect for Instagram posts",
+ "Ideal for client galleries",
+ "Professional portfolio presentation"
+ ]
+ }
+];
+
+export default function HowToUse() {
+ return (
+
+
+
+
How to Use SAAZ FRAMER
+
+ Create professionally framed images in minutes with our easy-to-use tool.
+ Follow these simple steps to get started.
+
+
+
+
+ {steps.map((step, index) => (
+
+
+
+
+
+
+
+
+
+ {index + 1}. {step.title}
+
+
{step.description}
+
+
Pro Tips:
+
+ {step.tips.map((tip, tipIndex) => (
+
{tip}
+ ))}
+
+
+
+
+
+ ))}
+
+
+
+
+
+
+
+
+
Need Help?
+
+ If you need additional assistance or have specific questions, don't hesitate to reach out.
+ We're here to help you create the perfect framed images.
+
+ );
+}
\ No newline at end of file
diff --git a/app/layout.tsx b/app/layout.tsx
new file mode 100644
index 0000000..dbaebf3
--- /dev/null
+++ b/app/layout.tsx
@@ -0,0 +1,38 @@
+import './globals.css';
+import type { Metadata } from 'next';
+import { Inter } from 'next/font/google';
+import { ThemeProvider } from '@/components/theme-provider';
+import { Toaster } from '@/components/ui/sonner';
+import { Navbar } from '@/components/layout/navbar';
+import { HelpButton } from '@/components/help-button';
+
+const inter = Inter({ subsets: ['latin'] });
+
+export const metadata: Metadata = {
+ title: 'SAAZ FRAMER - Professional Image Framing Tool',
+ description: 'Create beautifully framed images with custom overlays and watermarks',
+};
+
+export default function RootLayout({
+ children,
+}: {
+ children: React.ReactNode;
+}) {
+ return (
+
+
+
+
+ {children}
+
+
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/app/page.tsx b/app/page.tsx
new file mode 100644
index 0000000..4dfcf01
--- /dev/null
+++ b/app/page.tsx
@@ -0,0 +1,99 @@
+"use client";
+
+import { Frame, Palette } from 'lucide-react';
+import { useRouter } from 'next/navigation';
+import { Button } from '@/components/ui/button';
+
+export default function Home() {
+ const router = useRouter();
+
+ return (
+
+
+
+
+
+
+
+
+ SAAZ FRAMER
+
+
+
+ Create stunning framed images with professional overlays, custom text, and watermarks.
+ Perfect for photographers, artists, and creative professionals.
+
+
+
+
+
+
+
Our Features
+
+
+
+
+
+
Professional Frames
+
Create stunning frames for your images with our professional tools
+
+
+
+
+
+
Batch Processing
+
Frame multiple images at once with the same template
+
+
+
+
+
+
Easy Export
+
Download your framed images in high quality JPEG format
+
+
+
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/components.json b/components.json
new file mode 100644
index 0000000..c597462
--- /dev/null
+++ b/components.json
@@ -0,0 +1,20 @@
+{
+ "$schema": "https://ui.shadcn.com/schema.json",
+ "style": "default",
+ "rsc": true,
+ "tsx": true,
+ "tailwind": {
+ "config": "tailwind.config.ts",
+ "css": "app/globals.css",
+ "baseColor": "neutral",
+ "cssVariables": true,
+ "prefix": ""
+ },
+ "aliases": {
+ "components": "@/components",
+ "utils": "@/lib/utils",
+ "ui": "@/components/ui",
+ "lib": "@/lib",
+ "hooks": "@/hooks"
+ }
+}
diff --git a/components/help-button.tsx b/components/help-button.tsx
new file mode 100644
index 0000000..0dbc685
--- /dev/null
+++ b/components/help-button.tsx
@@ -0,0 +1,79 @@
+"use client";
+
+import { HelpCircle } from "lucide-react";
+import { Button } from "@/components/ui/button";
+import {
+ Dialog,
+ DialogContent,
+ DialogHeader,
+ DialogTitle,
+ DialogTrigger,
+} from "@/components/ui/dialog";
+import { Card } from "@/components/ui/card";
+import { Frame, Upload, Type, Download, Settings } from "lucide-react";
+
+const steps = [
+ {
+ icon: Upload,
+ title: "Upload Your Image",
+ description: "Start by uploading the image you want to frame. We support most common image formats including JPG, PNG, and WebP."
+ },
+ {
+ icon: Frame,
+ title: "Choose Your Frame",
+ description: "Select from our collection of frames or upload your own custom frame overlay to perfectly complement your image."
+ },
+ {
+ icon: Settings,
+ title: "Customize",
+ description: "Adjust frame position, scale, and opacity. Add custom watermarks and fine-tune every aspect of your composition."
+ },
+ {
+ icon: Type,
+ title: "Add Watermark",
+ description: "Protect your work by adding a custom watermark. Control its position, size, and rotation to match your style."
+ },
+ {
+ icon: Download,
+ title: "Download",
+ description: "Once satisfied with your creation, download the final image in high quality, ready for sharing or printing."
+ }
+];
+
+export function HelpButton() {
+ return (
+
+ );
+}
\ No newline at end of file
diff --git a/components/image-cropper.tsx b/components/image-cropper.tsx
new file mode 100644
index 0000000..1fdeb9a
--- /dev/null
+++ b/components/image-cropper.tsx
@@ -0,0 +1,124 @@
+"use client";
+
+import { useState, useRef } from 'react';
+import ReactCrop, { type Crop } from 'react-image-crop';
+import 'react-image-crop/dist/ReactCrop.css';
+import { Button } from '@/components/ui/button';
+import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog';
+import { Check, X } from 'lucide-react';
+import { Progress } from '@/components/ui/progress';
+
+interface ImageCropperProps {
+ imageUrl: string;
+ aspect?: number;
+ onCropComplete: (croppedImageUrl: string) => void;
+ isOpen: boolean;
+ onClose: () => void;
+ totalImages?: number;
+ currentImage?: number;
+}
+
+export function ImageCropper({
+ imageUrl,
+ aspect,
+ onCropComplete,
+ isOpen,
+ onClose,
+ totalImages,
+ currentImage,
+}: ImageCropperProps) {
+ const [crop, setCrop] = useState({
+ unit: '%',
+ width: 90,
+ height: aspect ? 90 / aspect : 90,
+ x: 5,
+ y: 5,
+ });
+
+ const imageRef = useRef(null);
+
+ const getCroppedImg = () => {
+ if (!imageRef.current) return;
+
+ const canvas = document.createElement('canvas');
+ const image = imageRef.current;
+ const scaleX = image.naturalWidth / image.width;
+ const scaleY = image.naturalHeight / image.height;
+
+ canvas.width = crop.width * scaleX;
+ canvas.height = crop.height * scaleY;
+
+ const ctx = canvas.getContext('2d');
+ if (!ctx) return;
+
+ ctx.drawImage(
+ image,
+ crop.x * scaleX,
+ crop.y * scaleY,
+ crop.width * scaleX,
+ crop.height * scaleY,
+ 0,
+ 0,
+ crop.width * scaleX,
+ crop.height * scaleY
+ );
+
+ const croppedImageUrl = canvas.toDataURL('image/jpeg', 0.9);
+ onCropComplete(croppedImageUrl);
+ };
+
+ return (
+
+ );
+}
\ No newline at end of file
diff --git a/components/layout/navbar.tsx b/components/layout/navbar.tsx
new file mode 100644
index 0000000..ed49b8c
--- /dev/null
+++ b/components/layout/navbar.tsx
@@ -0,0 +1,42 @@
+"use client";
+
+import { Frame } from "lucide-react";
+import Link from "next/link";
+import { usePathname } from "next/navigation";
+import { cn } from "@/lib/utils";
+
+const navigation = [
+ { name: "Home", href: "/" },
+ { name: "Frame", href: "/editor" },
+ { name: "About", href: "/about" },
+ { name: "Contact", href: "/contact" },
+];
+
+export function Navbar() {
+ const pathname = usePathname();
+
+ return (
+
+