diff --git a/public/media/art/screensaver/Artboard – 2.png b/public/media/art/screensaver/Artboard – 2.png
new file mode 100644
index 0000000..0040bf4
Binary files /dev/null and b/public/media/art/screensaver/Artboard – 2.png differ
diff --git a/public/media/art/screensaver/Artboard – 4.png b/public/media/art/screensaver/Artboard – 4.png
new file mode 100644
index 0000000..bb86592
Binary files /dev/null and b/public/media/art/screensaver/Artboard – 4.png differ
diff --git a/public/media/art/screensaver/ScreenSaver-1.png b/public/media/art/screensaver/ScreenSaver-1.png
new file mode 100644
index 0000000..b329a9b
Binary files /dev/null and b/public/media/art/screensaver/ScreenSaver-1.png differ
diff --git a/public/media/art/screensaver/ScreenSaver-2.png b/public/media/art/screensaver/ScreenSaver-2.png
new file mode 100644
index 0000000..c197fa9
Binary files /dev/null and b/public/media/art/screensaver/ScreenSaver-2.png differ
diff --git a/public/media/art/screensaver/ScreenSaver-3.png b/public/media/art/screensaver/ScreenSaver-3.png
new file mode 100644
index 0000000..883a125
Binary files /dev/null and b/public/media/art/screensaver/ScreenSaver-3.png differ
diff --git a/src/app/digital-art/page.tsx b/src/app/digital-art/page.tsx
index d9a872e..6f1d2a8 100644
--- a/src/app/digital-art/page.tsx
+++ b/src/app/digital-art/page.tsx
@@ -1,121 +1,23 @@
+import DigitalArt from "@/components/Art";
import type { Metadata } from "next";
-import Link from "next/link";
-import { artItems } from "@/data/creativeData";
export const metadata: Metadata = {
title: "Digital Art",
- description: "Explore my digital art collection featuring creative works through digital canvases and new media. Discover the intersection of technology and artistic expression.",
+ description:
+ "Explore my digital art collection featuring creative works through digital canvases and new media. Discover the intersection of technology and artistic expression.",
openGraph: {
title: "Digital Art | Mainasara Tsowa",
- description: "Explore my digital art collection featuring creative works through digital canvases and new media.",
+ description:
+ "Explore my digital art collection featuring creative works through digital canvases and new media.",
url: "https://mainasara.dev/digital-art",
},
twitter: {
title: "Digital Art | Mainasara Tsowa",
- description: "Explore my digital art collection featuring creative works through digital canvases and new media.",
+ description:
+ "Explore my digital art collection featuring creative works through digital canvases and new media.",
},
};
export default function DigitalArtPage() {
- return (
-
-
-
-
-
-
-
-
-
- Home
-
-
-
-
- Digital Art
-
-
-
-
- Stories
-
-
-
-
- Thoughts
-
-
-
-
-
-
-
- {artItems.map((art) => (
-
-
-
-
-
- {art.title}
-
-
- {art.description}
-
-
-
- {art.medium}
-
-
- {new Date(art.createdAt).toLocaleDateString("en-US", {
- year: "numeric",
- month: "long",
- day: "numeric",
- })}
-
-
-
-
-
- ))}
-
-
-
-
-
- );
+ return ;
}
diff --git a/src/app/layout.tsx b/src/app/layout.tsx
index 0755d55..b4ce7a8 100644
--- a/src/app/layout.tsx
+++ b/src/app/layout.tsx
@@ -13,60 +13,67 @@ const geistMono = Geist_Mono({
});
export const metadata: Metadata = {
- metadataBase: new URL('https://mainasara.dev'),
+ metadataBase: new URL("https://mainasara.dev"),
title: {
- default: 'Mainasara Tsowa - Developer & Cybersecurity Expert',
- template: '%s | Mainasara Tsowa'
+ default: "Mainasara Tsowa - Developer & Cybersecurity Expert",
+ template: "%s | Mainasara Tsowa",
},
- description: 'Passionate developer and cybersecurity expert building secure, elegant digital solutions. Explore projects, creative writing, and technical insights.',
- keywords: ['developer', 'cybersecurity', 'full-stack', 'web development', 'security', 'programming', 'digital art', 'creative writing'],
- authors: [{ name: 'Mainasara Tsowa' }],
- creator: 'Mainasara Tsowa',
- publisher: 'Mainasara Tsowa',
- robots: 'index, follow',
+ description:
+ "Passionate developer and cybersecurity expert building secure, elegant digital solutions. Explore projects, creative writing, and technical insights.",
+ keywords: [
+ "developer",
+ "cybersecurity",
+ "full-stack",
+ "web development",
+ "security",
+ "programming",
+ "digital art",
+ "creative writing",
+ ],
+ authors: [{ name: "Mainasara Tsowa" }],
+ creator: "Mainasara Tsowa",
+ publisher: "Mainasara Tsowa",
+ robots: "index, follow",
openGraph: {
- title: 'Mainasara Tsowa - Developer & Cybersecurity Expert',
- description: 'Passionate developer and cybersecurity expert building secure, elegant digital solutions.',
- url: 'https://mainasara.dev',
- siteName: 'Mainasara Tsowa',
- locale: 'en_US',
- type: 'website',
+ title: "Mainasara Tsowa - Developer & Cybersecurity Expert",
+ description:
+ "Passionate developer and cybersecurity expert building secure, elegant digital solutions.",
+ url: "https://mainasara.dev",
+ siteName: "Mainasara Tsowa",
+ locale: "en_US",
+ type: "website",
images: [
{
- url: '/media/me/me.jpeg',
+ url: "/media/me/me.jpeg",
width: 800,
height: 800,
- alt: 'Mainasara Tsowa',
+ alt: "Mainasara Tsowa",
},
],
},
twitter: {
- card: 'summary_large_image',
- title: 'Mainasara Tsowa - Developer & Cybersecurity Expert',
- description: 'Passionate developer and cybersecurity expert building secure, elegant digital solutions.',
- images: ['/media/me/me.jpeg'],
- creator: '@mainasara',
+ card: "summary_large_image",
+ title: "Mainasara Tsowa - Developer & Cybersecurity Expert",
+ description:
+ "Passionate developer and cybersecurity expert building secure, elegant digital solutions.",
+ images: ["/media/me/me.jpeg"],
+ creator: "@neutrino2211",
},
alternates: {
- canonical: 'https://mainasara.dev',
+ canonical: "https://mainasara.dev",
},
icons: {
- icon: [
- { url: '/favicon.ico' },
- { url: '/icon.png', type: 'image/png' },
- ],
- apple: [
- { url: '/apple-icon.png' },
- ],
+ icon: [{ url: "/favicon.ico" }, { url: "/icon.png", type: "image/png" }],
+ apple: [{ url: "/apple-icon.png" }],
},
- manifest: '/manifest.json',
+ manifest: "/manifest.json",
verification: {
- google: 'your-google-site-verification-code',
+ google: "your-google-site-verification-code",
},
- category: 'technology',
+ category: "technology",
other: {
- 'twitter:site': '@mainasara',
- 'twitter:creator': '@mainasara',
+ "twitter:site": "@neutrino2211",
+ "twitter:creator": "@neutrino2211",
},
};
diff --git a/src/app/page.tsx b/src/app/page.tsx
index 6b5d830..cac7edd 100644
--- a/src/app/page.tsx
+++ b/src/app/page.tsx
@@ -4,15 +4,18 @@ import Image from "next/image";
export const metadata: Metadata = {
title: "Home",
- description: "Welcome to my personal portfolio. I'm Mainasara Tsowa, a developer and cybersecurity expert passionate about building secure, elegant digital solutions.",
+ description:
+ "Welcome to my personal portfolio. I'm Mainasara Tsowa, a developer and cybersecurity expert passionate about building secure, elegant digital solutions.",
openGraph: {
title: "Home | Mainasara Tsowa",
- description: "Welcome to my personal portfolio. Developer and cybersecurity expert building secure, elegant digital solutions.",
+ description:
+ "Welcome to my personal portfolio. Developer and cybersecurity expert building secure, elegant digital solutions.",
url: "https://mainasara.dev",
},
twitter: {
title: "Home | Mainasara Tsowa",
- description: "Welcome to my personal portfolio. Developer and cybersecurity expert building secure, elegant digital solutions.",
+ description:
+ "Welcome to my personal portfolio. Developer and cybersecurity expert building secure, elegant digital solutions.",
},
};
@@ -44,50 +47,14 @@ export default function Home() {
-
diff --git a/src/components/Art.tsx b/src/components/Art.tsx
new file mode 100644
index 0000000..30dd33a
--- /dev/null
+++ b/src/components/Art.tsx
@@ -0,0 +1,157 @@
+"use client";
+
+import Link from "next/link";
+import { artItems } from "@/data/artData";
+import { useState } from "react";
+import ArtModal from "@/components/ArtModal";
+
+export default function DigitalArt() {
+ const [selectedArt, setSelectedArt] = useState<(typeof artItems)[0] | null>(
+ null,
+ );
+ const [currentImageIndex, setCurrentImageIndex] = useState(0);
+ const [isModalOpen, setIsModalOpen] = useState(false);
+
+ const handleArtClick = (artItem: (typeof artItems)[0]) => {
+ setSelectedArt(artItem);
+ setCurrentImageIndex(0);
+ setIsModalOpen(true);
+ };
+
+ const handleCloseModal = () => {
+ setIsModalOpen(false);
+ setSelectedArt(null);
+ setCurrentImageIndex(0);
+ };
+
+ const handleImageChange = (index: number) => {
+ setCurrentImageIndex(index);
+ };
+
+ return (
+
+
+
+
+
+
+
+
+
+ Home
+
+
+
+
+ Digital Art
+
+
+
+
+ Stories
+
+
+
+
+ Thoughts
+
+
+
+
+
+
+
+ {artItems.map((art) => (
+ handleArtClick(art)}
+ >
+
+
+
+
+
+
+
+
+ {art.title}
+
+
+ {art.description}
+
+
+
+ {art.medium}
+
+
+ {art.type === "collection" ? "Collection" : "Single"}
+
+
+ {new Date(art.createdAt).toLocaleDateString("en-US", {
+ year: "numeric",
+ month: "long",
+ day: "numeric",
+ })}
+
+ {art.type === "collection" && art.images && (
+
+ {art.images.length} images
+
+ )}
+
+
+
+
+ ))}
+
+
+
+
+
+
+
+ );
+}
diff --git a/src/components/ArtModal.tsx b/src/components/ArtModal.tsx
new file mode 100644
index 0000000..3816f3b
--- /dev/null
+++ b/src/components/ArtModal.tsx
@@ -0,0 +1,160 @@
+"use client";
+
+import React from "react";
+import { ArtItem } from "@/data/creative";
+
+interface ArtModalProps {
+ isOpen: boolean;
+ onClose: () => void;
+ artItem: ArtItem | null;
+ currentImageIndex: number;
+ onImageChange: (index: number) => void;
+}
+
+export default function ArtModal({
+ isOpen,
+ onClose,
+ artItem,
+ currentImageIndex,
+ onImageChange,
+}: ArtModalProps) {
+ if (!isOpen || !artItem) return null;
+
+ const handleBackdropClick = (e: React.MouseEvent) => {
+ if (e.target === e.currentTarget) {
+ onClose();
+ }
+ };
+
+ const handlePrevImage = () => {
+ if (artItem.images && artItem.images.length > 1) {
+ const newIndex =
+ currentImageIndex > 0
+ ? currentImageIndex - 1
+ : artItem.images.length - 1;
+ onImageChange(newIndex);
+ }
+ };
+
+ const handleNextImage = () => {
+ if (artItem.images && artItem.images.length > 1) {
+ const newIndex =
+ currentImageIndex < artItem.images.length - 1
+ ? currentImageIndex + 1
+ : 0;
+ onImageChange(newIndex);
+ }
+ };
+
+ const currentImage = artItem.images?.[currentImageIndex];
+
+ return (
+
+
+
+
+
+
+ {artItem.title}
+
+
{artItem.description}
+
+
+ {artItem.medium}
+
+
+ {artItem.type === "collection" ? "Collection" : "Single"}
+
+
+ {new Date(artItem.createdAt).toLocaleDateString("en-US", {
+ year: "numeric",
+ month: "long",
+ day: "numeric",
+ })}
+
+
+
+
+ ×
+
+
+
+ {currentImage && (
+
+
+
+
+
+ {artItem.type === "collection" &&
+ artItem.images &&
+ artItem.images.length > 1 && (
+ <>
+
+ ←
+
+
+ →
+
+ >
+ )}
+
+
+
+ {currentImage.title || `Image ${currentImageIndex + 1}`}
+
+ {currentImage.description && (
+
{currentImage.description}
+ )}
+
+
+ )}
+
+ {artItem.type === "collection" &&
+ artItem.images &&
+ artItem.images.length > 1 && (
+
+
+ Collection Preview
+
+
+ {artItem.images.map((image, index) => (
+
onImageChange(index)}
+ className={`flex-shrink-0 w-20 h-20 border-2 ${
+ index === currentImageIndex
+ ? "border-purple-500"
+ : "border-black"
+ } rounded-none flex items-center justify-center`}
+ >
+
+
+ ))}
+
+
+ )}
+
+
+
+ );
+}
diff --git a/src/data/artData.ts b/src/data/artData.ts
new file mode 100644
index 0000000..e73c259
--- /dev/null
+++ b/src/data/artData.ts
@@ -0,0 +1,45 @@
+import { ArtItem } from "./creative";
+
+export const artItems: ArtItem[] = [
+ {
+ id: "screen-saver",
+ title: "Glassy Screen Saver",
+ description:
+ "Have a set of beautifully translucent glass shards accompany you through the day.",
+ createdAt: "2023-01-16",
+ medium: "Digital Abstract Art",
+ type: "collection",
+ images: [
+ {
+ id: "artboard-2",
+ url: "/media/art/screensaver/Artboard – 2.png",
+ title: "Flow",
+ description: "A calm set of fluttery glass shards",
+ },
+ {
+ id: "artboard-4",
+ url: "/media/art/screensaver/Artboard – 4.png",
+ title: "It Cuts",
+ description: "Sharp, daring and a clear warning.",
+ },
+ {
+ id: "screensaver-1",
+ url: "/media/art/screensaver/ScreenSaver-1.png",
+ title: "Unoffensive",
+ description: "Yes, it can cut. But it does not want to.",
+ },
+ {
+ id: "screensaver-2",
+ url: "/media/art/screensaver/ScreenSaver-2.png",
+ title: "Rebel",
+ description: "These shards are not afraid to scare you.",
+ },
+ {
+ id: "screensaver-3",
+ url: "/media/art/screensaver/ScreenSaver-3.png",
+ title: "Stained",
+ description: "What if our calm friend from earlier was stained?",
+ },
+ ],
+ },
+];
diff --git a/src/data/creative.ts b/src/data/creative.ts
index 4f0fd5d..060734c 100644
--- a/src/data/creative.ts
+++ b/src/data/creative.ts
@@ -5,6 +5,15 @@ export interface ArtItem {
imageUrl?: string;
createdAt: string;
medium: string;
+ type: 'collection' | 'single';
+ images?: ArtImage[];
+}
+
+export interface ArtImage {
+ id: string;
+ url: string;
+ title?: string;
+ description?: string;
}
export interface Story {
diff --git a/src/data/creativeData.ts b/src/data/creativeData.ts
index 39154f2..32505e9 100644
--- a/src/data/creativeData.ts
+++ b/src/data/creativeData.ts
@@ -1,29 +1,5 @@
-import { ArtItem, Story, Thought } from './creative';
-import { loadStories, loadThoughts } from '@/utils/mdxLoader';
-
-export const artItems: ArtItem[] = [
- {
- id: 'digital-landscapes',
- title: 'Digital Landscapes',
- description: 'Exploring the intersection of nature and technology through vibrant digital canvases',
- createdAt: '2024-01-15',
- medium: 'Digital Painting'
- },
- {
- id: 'abstract-emotions',
- title: 'Abstract Emotions',
- description: 'A series exploring human emotions through color and form',
- createdAt: '2024-02-20',
- medium: 'Mixed Media'
- },
- {
- id: 'cyberpunk-dreams',
- title: 'Cyberpunk Dreams',
- description: 'Futuristic cityscapes blending retro aesthetics with modern technology',
- createdAt: '2024-03-10',
- medium: '3D Render'
- }
-];
+import { Story, Thought } from "./creative";
+import { loadStories, loadThoughts } from "@/utils/mdxLoader";
// Load stories from MDX files
export async function getStories(): Promise {
@@ -33,4 +9,4 @@ export async function getStories(): Promise {
// Load thoughts from MDX files
export async function getThoughts(): Promise {
return await loadThoughts();
-}
\ No newline at end of file
+}
diff --git a/src/data/projects.ts b/src/data/projects.ts
index 74358c8..ff07129 100644
--- a/src/data/projects.ts
+++ b/src/data/projects.ts
@@ -2,42 +2,18 @@ export interface Project {
id: string;
title: string;
description: string;
- category: 'security' | 'development' | 'art' | 'other';
+ category: "security" | "development" | "art" | "other";
link?: string;
featured: boolean;
}
export const projects: Project[] = [
{
- id: 'security-scanner',
- title: 'Security Scanner',
- description: 'Automated vulnerability detection tool',
- category: 'security',
- link: '#',
- featured: true
+ id: "valradar",
+ title: "Valradar",
+ description: "OSINT and general purpose multiprocessing framework.",
+ category: "security",
+ link: "https://github.com/neutrino2211/valradar",
+ featured: true,
},
- {
- id: 'art-portfolio',
- title: 'Art Portfolio',
- description: 'Digital art showcase platform',
- category: 'art',
- link: '#',
- featured: true
- },
- {
- id: 'api-gateway',
- title: 'API Gateway',
- description: 'Secure microservices gateway with authentication',
- category: 'development',
- link: '#',
- featured: false
- },
- {
- id: 'threat-detector',
- title: 'Threat Detector',
- description: 'Machine learning-based threat detection system',
- category: 'security',
- link: '#',
- featured: false
- }
-];
\ No newline at end of file
+];