Remove AI generated content

This commit is contained in:
neutrino2211
2025-10-10 07:10:54 +01:00
parent d1b0577b69
commit 5d54506fe5
14 changed files with 443 additions and 244 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

View File

@@ -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 (
<div className="min-h-screen bg-gradient-to-br from-purple-50 via-pink-50 to-yellow-50 font-mono">
<div className="max-w-4xl mx-auto px-6 py-12">
<header className="text-center mb-16">
<div className="mb-8">
<div className="w-32 h-32 mx-auto bg-black rounded-full flex items-center justify-center border-4 border-purple-300">
<div className="text-4xl font-bold text-purple-300">🎨</div>
</div>
</div>
<h1 className="text-4xl md:text-5xl font-bold text-gray-900 mb-4 uppercase tracking-wide">
Digital Art
</h1>
<p className="text-lg text-gray-700 max-w-2xl mx-auto leading-relaxed font-semibold">
Exploring creativity through digital canvases and new media
</p>
</header>
<nav className="mb-16">
<div className="bg-white border-4 border-black rounded-none p-6 shadow-brutal">
<ul className="flex flex-wrap justify-center gap-4 md:gap-8">
<li>
<Link
href="/"
className="text-black font-bold hover:bg-green-200 px-4 py-2 transition-colors border-2 border-black hover:border-green-400"
>
Home
</Link>
</li>
<li>
<Link
href="/digital-art"
className="border-purple-200 bg-purple-300 px-4 py-2 border-2 text-black"
>
Digital Art
</Link>
</li>
<li>
<Link
href="/stories"
className="text-black font-bold hover:bg-yellow-200 px-4 py-2 transition-colors border-2 border-black hover:border-yellow-400"
>
Stories
</Link>
</li>
<li>
<Link
href="/thoughts"
className="text-black font-bold hover:bg-teal-200 px-4 py-2 transition-colors border-2 border-black hover:border-teal-400"
>
Thoughts
</Link>
</li>
</ul>
</div>
</nav>
<main className="space-y-8">
{artItems.map((art) => (
<article
key={art.id}
className="bg-white border-4 border-black rounded-none p-8 shadow-brutal"
>
<div className="flex flex-col md:flex-row gap-8">
<div className="md:w-1/3">
<div className="bg-purple-100 border-2 border-black rounded-none w-full h-48 flex items-center justify-center">
<span className="text-6xl">🎨</span>
</div>
</div>
<div className="md:w-2/3">
<h2 className="text-2xl font-bold text-gray-900 mb-4 uppercase">
{art.title}
</h2>
<p className="text-gray-800 mb-4 leading-relaxed">
{art.description}
</p>
<div className="flex flex-wrap gap-4 text-sm">
<span className="bg-purple-200 text-black px-3 py-1 border-2 border-black font-bold">
{art.medium}
</span>
<span className="bg-gray-200 text-black px-3 py-1 border-2 border-black font-bold">
{new Date(art.createdAt).toLocaleDateString("en-US", {
year: "numeric",
month: "long",
day: "numeric",
})}
</span>
</div>
</div>
</div>
</article>
))}
</main>
<footer className="mt-20 text-center">
<div className="bg-black text-white p-4 font-bold">
<p className="text-sm uppercase">Built with brutalist pastels</p>
</div>
</footer>
</div>
</div>
);
return <DigitalArt />;
}

View File

@@ -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",
},
};

View File

@@ -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() {
<nav className="mb-16">
<div className="bg-white border-4 border-black rounded-none p-6 shadow-brutal">
<ul className="flex flex-wrap justify-center gap-4 md:gap-8">
<li>
<a
href="#about"
className="text-black font-bold hover:bg-green-200 px-4 py-2 transition-colors border-2 border-black hover:border-green-400"
>
About
</a>
</li>
<li>
<a
href="#expertise"
className="text-black font-bold hover:bg-teal-200 px-4 py-2 transition-colors border-2 border-black hover:border-teal-400"
>
Expertise
</a>
</li>
<li>
<a
href="#projects"
className="text-black font-bold hover:bg-blue-200 px-4 py-2 transition-colors border-2 border-black hover:border-blue-400"
>
Projects
</a>
</li>
<li>
<a
href="#contact"
className="text-black font-bold hover:bg-yellow-200 px-4 py-2 transition-colors border-2 border-black hover:border-yellow-400"
>
Contact
</a>
</li>
</ul>
</div>
<div className="bg-white border-4 border-black rounded-none p-6 shadow-brutal mt-6">
<ul className="flex flex-wrap justify-center gap-4 md:gap-8">
<li>
{/*<li>
<a
href="https://blog.mainasara.dev"
className="text-black font-bold hover:bg-slate-200 px-4 py-2 transition-colors border-2 border-black hover:border-slate-400"
>
Blog
</a>
</li>
</li>*/}
<li>
<a
href="/digital-art"
@@ -201,7 +168,7 @@ export default function Home() {
Digital Art
</h3>
<p className="text-gray-800 text-sm">
Exploring creativity through digital canvases
Someone messing around with colors and shapes.
</p>
</a>
<a
@@ -213,7 +180,7 @@ export default function Home() {
Mini Stories
</h3>
<p className="text-gray-800 text-sm">
Capturing moments in brief narratives
I like good narratives, so I try to imitate them.
</p>
</a>
<a
@@ -225,7 +192,7 @@ export default function Home() {
Thoughts
</h3>
<p className="text-gray-800 text-sm">
Reflections on technology and life
Rants on technology, maybe something else.
</p>
</a>
</div>

157
src/components/Art.tsx Normal file
View File

@@ -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 (
<div className="min-h-screen bg-gradient-to-br from-purple-50 via-pink-50 to-yellow-50 font-mono">
<div className="max-w-4xl mx-auto px-6 py-12">
<header className="text-center mb-16">
<div className="mb-8">
<div className="w-32 h-32 mx-auto bg-black rounded-full flex items-center justify-center border-4 border-purple-300">
<div className="text-4xl font-bold text-purple-300">🎨</div>
</div>
</div>
<h1 className="text-4xl md:text-5xl font-bold text-gray-900 mb-4 uppercase tracking-wide">
Digital Art
</h1>
<p className="text-lg text-gray-700 max-w-2xl mx-auto leading-relaxed font-semibold">
Exploring creativity through digital canvases and new media
</p>
</header>
<nav className="mb-16">
<div className="bg-white border-4 border-black rounded-none p-6 shadow-brutal">
<ul className="flex flex-wrap justify-center gap-4 md:gap-8">
<li>
<Link
href="/"
className="text-black font-bold hover:bg-green-200 px-4 py-2 transition-colors border-2 border-black hover:border-green-400"
>
Home
</Link>
</li>
<li>
<Link
href="/digital-art"
className="border-purple-200 bg-purple-300 px-4 py-2 border-2 text-black"
>
Digital Art
</Link>
</li>
<li>
<Link
href="/stories"
className="text-black font-bold hover:bg-yellow-200 px-4 py-2 transition-colors border-2 border-black hover:border-yellow-400"
>
Stories
</Link>
</li>
<li>
<Link
href="/thoughts"
className="text-black font-bold hover:bg-teal-200 px-4 py-2 transition-colors border-2 border-black hover:border-teal-400"
>
Thoughts
</Link>
</li>
</ul>
</div>
</nav>
<main className="space-y-8">
{artItems.map((art) => (
<article
key={art.id}
className="bg-white border-4 border-black rounded-none p-8 shadow-brutal cursor-pointer hover:shadow-brutal-hover transition-shadow"
onClick={() => handleArtClick(art)}
>
<div className="flex flex-col md:flex-row gap-8">
<div className="md:w-1/3">
<div className="bg-purple-100 border-2 border-black rounded-none w-full h-48 flex items-center justify-center">
<img
src={
art.type === "collection"
? art.images![0].url
: art.imageUrl
}
alt={art.title}
className="w-full h-full object-cover"
/>
</div>
</div>
<div className="md:w-2/3">
<h2 className="text-2xl font-bold text-gray-900 mb-4 uppercase">
{art.title}
</h2>
<p className="text-gray-800 mb-4 leading-relaxed">
{art.description}
</p>
<div className="flex flex-wrap gap-4 text-sm">
<span className="bg-purple-200 text-black px-3 py-1 border-2 border-black font-bold">
{art.medium}
</span>
<span className="bg-gray-200 text-black px-3 py-1 border-2 border-black font-bold">
{art.type === "collection" ? "Collection" : "Single"}
</span>
<span className="bg-gray-200 text-black px-3 py-1 border-2 border-black font-bold">
{new Date(art.createdAt).toLocaleDateString("en-US", {
year: "numeric",
month: "long",
day: "numeric",
})}
</span>
{art.type === "collection" && art.images && (
<span className="bg-blue-200 text-black px-3 py-1 border-2 border-black font-bold">
{art.images.length} images
</span>
)}
</div>
</div>
</div>
</article>
))}
</main>
<footer className="mt-20 text-center">
<div className="bg-black text-white p-4 font-bold">
<p className="text-sm uppercase">Built with brutalist pastels</p>
</div>
</footer>
</div>
<ArtModal
isOpen={isModalOpen}
onClose={handleCloseModal}
artItem={selectedArt}
currentImageIndex={currentImageIndex}
onImageChange={handleImageChange}
/>
</div>
);
}

160
src/components/ArtModal.tsx Normal file
View File

@@ -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 (
<div
className="fixed inset-0 bg-black bg-opacity-75 flex items-center justify-center z-50 p-4"
onClick={handleBackdropClick}
>
<div className="max-w-4xl w-full max-h-[90vh] overflow-y-auto">
<div className="p-6">
<div className="flex justify-between items-start mb-6">
<div>
<h2 className="text-3xl font-bold text-gray-900 mb-2 uppercase">
{artItem.title}
</h2>
<p className="text-gray-700 mb-4">{artItem.description}</p>
<div className="flex gap-4 text-sm">
<span className="text-purple-200 px-3 py-1 border-2 border-black font-bold">
{artItem.medium}
</span>
<span className="text-gray-200 px-3 py-1 border-2 border-black font-bold">
{artItem.type === "collection" ? "Collection" : "Single"}
</span>
<span className="text-gray-200 px-3 py-1 border-2 border-black font-bold">
{new Date(artItem.createdAt).toLocaleDateString("en-US", {
year: "numeric",
month: "long",
day: "numeric",
})}
</span>
</div>
</div>
<button
onClick={onClose}
className="text-2xl font-bold text-white w-10 h-10 flex items-center justify-center"
>
×
</button>
</div>
{currentImage && (
<div className="relative">
<div className="w-full h-96 flex items-center justify-center mb-4">
<img
src={currentImage.url}
alt={currentImage.title}
className="max-w-full max-h-full"
/>
</div>
{artItem.type === "collection" &&
artItem.images &&
artItem.images.length > 1 && (
<>
<button
onClick={handlePrevImage}
className="absolute left-2 top-1/2 transform -translate-y-1/2 px-3 py-1 font-bold"
>
</button>
<button
onClick={handleNextImage}
className="absolute right-2 top-1/2 transform -translate-y-1/2 px-3 py-1 font-bold"
>
</button>
</>
)}
<div className="text-center">
<h3 className="text-xl font-bold text-gray-900 mb-2">
{currentImage.title || `Image ${currentImageIndex + 1}`}
</h3>
{currentImage.description && (
<p className="text-gray-700">{currentImage.description}</p>
)}
</div>
</div>
)}
{artItem.type === "collection" &&
artItem.images &&
artItem.images.length > 1 && (
<div className="mt-6">
<h4 className="text-lg font-bold mb-3 text-center">
Collection Preview
</h4>
<div className="flex gap-2 overflow-x-auto pb-2 justify-center">
{artItem.images.map((image, index) => (
<button
key={image.id}
onClick={() => 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`}
>
<img
src={image.url}
alt={image.title}
className="max-w-full max-h-full"
/>
</button>
))}
</div>
</div>
)}
</div>
</div>
</div>
);
}

45
src/data/artData.ts Normal file
View File

@@ -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?",
},
],
},
];

View File

@@ -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 {

View File

@@ -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<Story[]> {
@@ -33,4 +9,4 @@ export async function getStories(): Promise<Story[]> {
// Load thoughts from MDX files
export async function getThoughts(): Promise<Thought[]> {
return await loadThoughts();
}
}

View File

@@ -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
}
];
];