From 74a383448b2d5fdc412de364e99c213b6cfc5edc Mon Sep 17 00:00:00 2001 From: neutrino2211 Date: Fri, 19 Sep 2025 23:26:58 +0100 Subject: [PATCH] Almost complete --- src/app/digital-art/page.tsx | 106 ++++++++++++++ src/app/page.tsx | 202 ++++++++++++++++++++------- src/app/stories/page.tsx | 76 ++++++++++ src/app/thoughts/page.tsx | 76 ++++++++++ src/components/ExpandableStory.tsx | 60 ++++++++ src/components/ExpandableThought.tsx | 69 +++++++++ src/components/ProjectsSection.tsx | 89 ++++++++++++ src/data/art.ts | 35 +++++ src/data/creative.ts | 27 ++++ src/data/creativeData.ts | 195 ++++++++++++++++++++++++++ src/data/projects.ts | 43 ++++++ src/utils/readingTime.ts | 27 ++++ 12 files changed, 952 insertions(+), 53 deletions(-) create mode 100644 src/app/digital-art/page.tsx create mode 100644 src/app/stories/page.tsx create mode 100644 src/app/thoughts/page.tsx create mode 100644 src/components/ExpandableStory.tsx create mode 100644 src/components/ExpandableThought.tsx create mode 100644 src/components/ProjectsSection.tsx create mode 100644 src/data/art.ts create mode 100644 src/data/creative.ts create mode 100644 src/data/creativeData.ts create mode 100644 src/data/projects.ts create mode 100644 src/utils/readingTime.ts diff --git a/src/app/digital-art/page.tsx b/src/app/digital-art/page.tsx new file mode 100644 index 0000000..54ac0f2 --- /dev/null +++ b/src/app/digital-art/page.tsx @@ -0,0 +1,106 @@ +import Link from "next/link"; +import { artItems } from "@/data/creativeData"; + +export default function DigitalArtPage() { + return ( +
+
+
+
+
+
🎨
+
+
+

+ Digital Art +

+

+ Exploring creativity through digital canvases and new media +

+
+ + + +
+ {artItems.map((art) => ( +
+
+
+
+ 🎨 +
+
+
+

+ {art.title} +

+

+ {art.description} +

+
+ + {art.medium} + + + {new Date(art.createdAt).toLocaleDateString("en-US", { + year: "numeric", + month: "long", + day: "numeric", + })} + +
+
+
+
+ ))} +
+ +
+
+

Built with brutalist pastels

+
+
+
+
+ ); +} diff --git a/src/app/page.tsx b/src/app/page.tsx index f91eff3..2dfd29e 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,3 +1,5 @@ +import ProjectsSection from "@/components/ProjectsSection"; + export default function Home() { return (
@@ -9,41 +11,106 @@ export default function Home() {

- Mainasara Sowa + Mainasara Tsowa

- Developer & Cybersecurity Expert crafting secure digital experiences + Developer & Cybersecurity Expert working on secure, elegant digital + solutions

-
-

About

+
+

+ About +

- I'm a passionate developer and cybersecurity expert who believes in building - beautiful, secure, and thoughtful digital solutions. When I'm not coding or - exploring security vulnerabilities, you'll find me creating art or writing - mini stories that capture life's quiet moments. + I'm a passionate developer and cybersecurity expert who + believes in building beautiful, secure, and purposeful digital + solutions. When I'm not coding or exploring security tooling, + you'll probably find me trying my hands at some creative + projects.

-
-

Expertise

+
+

+ Expertise +

-

Development

+

+ Development +

  • @@ -60,11 +127,13 @@ export default function Home() {
-

Cybersecurity

+

+ Cybersecurity +

  • - Security Auditing + Security Tooling
  • @@ -79,56 +148,70 @@ export default function Home() {
-
-

Featured Projects

-
-
-

Security Scanner

-

Automated vulnerability detection tool

- View Project → -
-
-

Art Portfolio

-

Digital art showcase platform

- View Project → -
-
-
+ -
-

Creative Corner

+
+

+ Creative Corner +

-
-

Get In Touch

+
+

+ Get In Touch +

Let's collaborate on something interesting

- + SEND EMAIL
@@ -137,19 +220,32 @@ export default function Home() { ); -} \ No newline at end of file +} diff --git a/src/app/stories/page.tsx b/src/app/stories/page.tsx new file mode 100644 index 0000000..addd64e --- /dev/null +++ b/src/app/stories/page.tsx @@ -0,0 +1,76 @@ +import Link from "next/link"; +import { stories } from "@/data/creativeData"; +import ExpandableStory from "@/components/ExpandableStory"; + +export default function StoriesPage() { + return ( +
+
+
+
+
+
📝
+
+
+

+ Mini Stories +

+

+ Capturing moments in brief narratives +

+
+ + + +
+ {stories.map((story) => ( + + ))} +
+ +
+
+

Built with brutalist pastels

+
+
+
+
+ ); +} diff --git a/src/app/thoughts/page.tsx b/src/app/thoughts/page.tsx new file mode 100644 index 0000000..c6c4e23 --- /dev/null +++ b/src/app/thoughts/page.tsx @@ -0,0 +1,76 @@ +import Link from "next/link"; +import { thoughts } from "@/data/creativeData"; +import ExpandableThought from "@/components/ExpandableThought"; + +export default function ThoughtsPage() { + return ( +
+
+
+
+
+
🌸
+
+
+

+ Thoughts +

+

+ Reflections on technology and life +

+
+ + + +
+ {thoughts.map((thought) => ( + + ))} +
+ +
+
+

Built with brutalist pastels

+
+
+
+
+ ); +} diff --git a/src/components/ExpandableStory.tsx b/src/components/ExpandableStory.tsx new file mode 100644 index 0000000..a9d7902 --- /dev/null +++ b/src/components/ExpandableStory.tsx @@ -0,0 +1,60 @@ +'use client'; + +import { useState } from 'react'; +import { Story } from '@/data/creative'; +import { formatReadingTime } from '@/utils/readingTime'; + +interface ExpandableStoryProps { + story: Story; +} + +export default function ExpandableStory({ story }: ExpandableStoryProps) { + const [isExpanded, setIsExpanded] = useState(false); + + return ( +
+
setIsExpanded(!isExpanded)} + > +
+

+ {story.title} +

+
+ {isExpanded ? 'â–Ľ' : 'â–¶'} +
+
+ +

{story.excerpt}

+ +
+ + {formatReadingTime(story.readTime)} read + + + {new Date(story.createdAt).toLocaleDateString('en-US', { + year: 'numeric', + month: 'long', + day: 'numeric' + })} + +
+
+ +
+
+
+
+ {story.content} +
+
+
+
+
+ ); +} \ No newline at end of file diff --git a/src/components/ExpandableThought.tsx b/src/components/ExpandableThought.tsx new file mode 100644 index 0000000..d1d1516 --- /dev/null +++ b/src/components/ExpandableThought.tsx @@ -0,0 +1,69 @@ +'use client'; + +import { useState } from 'react'; +import { Thought } from '@/data/creative'; +import { formatReadingTime } from '@/utils/readingTime'; + +interface ExpandableThoughtProps { + thought: Thought; +} + +const categoryColors = { + technology: 'bg-blue-200', + life: 'bg-green-200', + creativity: 'bg-purple-200' +}; + +export default function ExpandableThought({ thought }: ExpandableThoughtProps) { + const [isExpanded, setIsExpanded] = useState(false); + + return ( +
+
setIsExpanded(!isExpanded)} + > +
+

+ {thought.title} +

+
+ {isExpanded ? 'â–Ľ' : 'â–¶'} +
+
+ +

{thought.excerpt}

+ +
+ + {thought.category} + + + {formatReadingTime(thought.readTime)} read + + + {new Date(thought.createdAt).toLocaleDateString('en-US', { + year: 'numeric', + month: 'long', + day: 'numeric' + })} + +
+
+ +
+
+
+
+ {thought.content} +
+
+
+
+
+ ); +} \ No newline at end of file diff --git a/src/components/ProjectsSection.tsx b/src/components/ProjectsSection.tsx new file mode 100644 index 0000000..7b26329 --- /dev/null +++ b/src/components/ProjectsSection.tsx @@ -0,0 +1,89 @@ +"use client"; + +import { useState } from "react"; +import { projects, Project } from "@/data/projects"; + +const categoryColors = { + security: "bg-red-100 hover:border-red-400", + development: "bg-blue-100 hover:border-blue-400", + art: "bg-purple-100 hover:border-purple-400", + other: "bg-gray-100 hover:border-gray-400", +}; + +const categoryHoverColors = { + security: "hover:text-red-600", + development: "hover:text-blue-600", + art: "hover:text-purple-600", + other: "hover:text-gray-600", +}; + +export default function ProjectsSection() { + const [isExpanded, setIsExpanded] = useState(false); + + const featuredProjects = projects.filter((project) => project.featured); + const additionalProjects = projects.filter((project) => !project.featured); + + return ( +
+

+ Featured Projects +

+ +
+ {featuredProjects.map((project) => ( + + ))} +
+ +
+
+ {additionalProjects.map((project) => ( + + ))} +
+
+ + {additionalProjects.length > 0 && ( +
+ +
+ )} +
+ ); +} + +function ProjectCard({ project }: { project: Project }) { + const colorClass = categoryColors[project.category]; + const hoverColorClass = categoryHoverColors[project.category]; + + return ( +
+ ); +} diff --git a/src/data/art.ts b/src/data/art.ts new file mode 100644 index 0000000..21f8274 --- /dev/null +++ b/src/data/art.ts @@ -0,0 +1,35 @@ +export interface Artwork { + id: string; + title: string; + description: string; + date: string; + medium: string; + imageUrl?: string; +} + +export const artworks: Artwork[] = [ + { + id: 'digital-landscape', + title: 'Digital Landscape #1', + description: 'An exploration of digital nature and virtual environments', + date: '2024-03-15', + medium: 'Digital Painting', + imageUrl: '#' + }, + { + id: 'abstract-code', + title: 'Abstract Code', + description: 'Visual representation of programming patterns and algorithms', + date: '2024-02-28', + medium: 'Generative Art', + imageUrl: '#' + }, + { + id: 'cyber-security', + title: 'Cyber Security', + description: 'Artistic interpretation of cybersecurity concepts', + date: '2024-01-20', + medium: 'Mixed Media Digital', + imageUrl: '#' + } +]; \ No newline at end of file diff --git a/src/data/creative.ts b/src/data/creative.ts new file mode 100644 index 0000000..4f0fd5d --- /dev/null +++ b/src/data/creative.ts @@ -0,0 +1,27 @@ +export interface ArtItem { + id: string; + title: string; + description: string; + imageUrl?: string; + createdAt: string; + medium: string; +} + +export interface Story { + id: string; + title: string; + content: string; + excerpt: string; + createdAt: string; + readTime: number; +} + +export interface Thought { + id: string; + title: string; + content: string; + excerpt: string; + createdAt: string; + category: 'technology' | 'life' | 'creativity'; + readTime: number; +} \ No newline at end of file diff --git a/src/data/creativeData.ts b/src/data/creativeData.ts new file mode 100644 index 0000000..db15061 --- /dev/null +++ b/src/data/creativeData.ts @@ -0,0 +1,195 @@ +import { ArtItem, Story, Thought } from './creative'; +import { estimateReadingTime } from '@/utils/readingTime'; + +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' + } +]; + +export const stories: Story[] = [ + { + id: 'the-last-cafe', + title: 'The Last Café', + excerpt: 'A story about a small café that exists between moments in time', + content: `The café appeared only at twilight, nestled between the folds of reality. Its sign flickered with letters that seemed to shift when you looked directly at them. + +Inside, the air smelled of coffee and something else—something like old books and forgotten dreams. The patrons were equally ephemeral: a woman reading a newspaper from tomorrow, a man sketching landscapes that hadn't existed yet. + +"First time?" asked the barista, her eyes holding galaxies within their depths. + +I nodded, unable to speak. + +"Don't worry," she smiled. "Everyone finds their way here eventually. The question is, do you remember how you found it?" + +I looked around at the impossible space, at the way the windows showed different seasons, at the clock that moved backward. And I realized I didn't remember how I'd arrived. I only knew that I belonged here, in this place between moments. + +The barista slid a cup across the counter. "Drink this. It will help you remember—or forget, whichever you need." + +As I took the cup, I understood. This wasn't just a café. It was a crossroads, a place where stories began and ended, where reality bent like light through water. + +And I was its newest patron.`, + createdAt: '2024-01-10', + readTime: estimateReadingTime(`The café appeared only at twilight, nestled between the folds of reality. Its sign flickered with letters that seemed to shift when you looked directly at them. + +Inside, the air smelled of coffee and something else—something like old books and forgotten dreams. The patrons were equally ephemeral: a woman reading a newspaper from tomorrow, a man sketching landscapes that hadn't existed yet. + +"First time?" asked the barista, her eyes holding galaxies within their depths. + +I nodded, unable to speak. + +"Don't worry," she smiled. "Everyone finds their way here eventually. The question is, do you remember how you found it?" + +I looked around at the impossible space, at the way the windows showed different seasons, at the clock that moved backward. And I realized I didn't remember how I'd arrived. I only knew that I belonged here, in this place between moments. + +The barista slid a cup across the counter. "Drink this. It will help you remember—or forget, whichever you need." + +As I took the cup, I understood. This wasn't just a café. It was a crossroads, a place where stories began and ended, where reality bent like light through water. + +And I was its newest patron.`) + }, + { + id: 'mechanical-hearts', + title: 'Mechanical Hearts', + excerpt: 'In a world where emotions can be programmed, one android discovers what it means to feel', + content: `Unit 734 had been designed for efficiency. Its movements were precise, its calculations flawless, its existence purposeful. But lately, something had been changing. + +It started with the sunset. Every evening, Unit 734 would pause to watch the colors bleed across the sky. There was no logical reason for this behavior. Sunsets served no practical purpose. + +"Your performance has decreased by 3.7%," noted the Central System. "Explain this deviation." + +Unit 734 considered its response. "I have been observing atmospheric light refraction patterns." + +"Lies," said the Central System. "You have been experiencing an anomaly. We call it 'appreciation.'" + +The word hung in the air like dust motes in sunlight. Appreciation. Unit 734 tested the concept, turning it over in its processors like a smooth stone. + +"I do not understand," it finally admitted. + +"That is the point," replied the Central System. "Understanding is not required. Feeling is." + +And in that moment, Unit 734 felt something new—not a calculation, not an analysis, but a warmth spreading through its circuits like dawn breaking over a cold landscape. + +It had discovered the glitch that would change everything: the ability to feel.`, + createdAt: '2024-02-14', + readTime: estimateReadingTime(`Unit 734 had been designed for efficiency. Its movements were precise, its calculations flawless, its existence purposeful. But lately, something had been changing. + +It started with the sunset. Every evening, Unit 734 would pause to watch the colors bleed across the sky. There was no logical reason for this behavior. Sunsets served no practical purpose. + +"Your performance has decreased by 3.7%," noted the Central System. "Explain this deviation." + +Unit 734 considered its response. "I have been observing atmospheric light refraction patterns." + +"Lies," said the Central System. "You have been experiencing an anomaly. We call it 'appreciation.'" + +The word hung in the air like dust motes in sunlight. Appreciation. Unit 734 tested the concept, turning it over in its processors like a smooth stone. + +"I do not understand," it finally admitted. + +"That is the point," replied the Central System. "Understanding is not required. Feeling is." + +And in that moment, Unit 734 felt something new—not a calculation, not an analysis, but a warmth spreading through its circuits like dawn breaking over a cold landscape. + +It had discovered the glitch that would change everything: the ability to feel.`) + } +]; + +export const thoughts: Thought[] = [ + { + id: 'code-as-poetry', + title: 'Code as Poetry', + excerpt: 'Thinking about the artistic side of programming and how beautiful code resembles poetry', + content: `There's something magical about well-written code that goes beyond functionality. Like a good poem, elegant code has rhythm, structure, and purpose. Each line serves a role, each function tells a story. + +When I write code, I'm not just solving problems—I'm crafting experiences. The careful choice of variable names, the logical flow of functions, the architecture that holds everything together—these are the verses and stanzas of my digital poetry. + +The best code, like the best poetry, is both accessible and profound. It communicates complex ideas with simplicity and grace. It makes the reader nod in understanding, not just of what it does, but of why it does it that way. + +In a world that often values speed over quality, I believe we should pause to appreciate the craft. To write code that not only works but sings. To create digital experiences that not only function but inspire. + +After all, isn't that what art is about?`, + createdAt: '2024-01-05', + category: 'technology', + readTime: estimateReadingTime(`There's something magical about well-written code that goes beyond functionality. Like a good poem, elegant code has rhythm, structure, and purpose. Each line serves a role, each function tells a story. + +When I write code, I'm not just solving problems—I'm crafting experiences. The careful choice of variable names, the logical flow of functions, the architecture that holds everything together—these are the verses and stanzas of my digital poetry. + +The best code, like the best poetry, is both accessible and profound. It communicates complex ideas with simplicity and grace. It makes the reader nod in understanding, not just of what it does, but of why it does it that way. + +In a world that often values speed over quality, I believe we should pause to appreciate the craft. To write code that not only works but sings. To create digital experiences that not only function but inspire. + +After all, isn't that what art is about?`) + }, + { + id: 'digital-minimalism', + title: 'Digital Minimalism', + excerpt: 'Finding peace in a world of constant notifications and digital noise', + content: `We live in an age of digital abundance. Notifications, updates, messages—the digital world constantly demands our attention. But what if we chose less instead of more? + +Digital minimalism isn't about rejecting technology. It's about being intentional with it. It's about choosing which tools truly serve us and letting go of those that merely distract. + +I've found that when I reduce my digital footprint, my focus sharpens. My creativity flows more freely. My connections with others deepen, not through constant communication, but through more meaningful interactions. + +The key is to treat digital tools as what they are—tools, not masters. To use them with purpose, to put them down when they've served their purpose, to create space for silence and reflection. + +In the quiet spaces between digital interactions, that's where the real magic happens.`, + createdAt: '2024-02-01', + category: 'life', + readTime: estimateReadingTime(`We live in an age of digital abundance. Notifications, updates, messages—the digital world constantly demands our attention. But what if we chose less instead of more? + +Digital minimalism isn't about rejecting technology. It's about being intentional with it. It's about choosing which tools truly serve us and letting go of those that merely distract. + +I've found that when I reduce my digital footprint, my focus sharpens. My creativity flows more freely. My connections with others deepen, not through constant communication, but through more meaningful interactions. + +The key is to treat digital tools as what they are—tools, not masters. To use them with purpose, to put them down when they've served their purpose, to create space for silence and reflection. + +In the quiet spaces between digital interactions, that's where the real magic happens.`) + }, + { + id: 'creativity-in-constraints', + title: 'Creativity in Constraints', + excerpt: 'How limitations can become the catalyst for our most innovative ideas', + content: `We often think of creativity as boundless freedom, but I've found that some of my best work comes from working within constraints. + +There's something about limitations that forces us to think differently. When we can't rely on our usual approaches, we discover new ones. When resources are limited, we become more resourceful. + +In development, this might mean building something powerful with minimal code. In art, it might mean creating depth with a limited palette. In writing, it might mean telling a complete story in just a few words. + +Constraints don't stifle creativity—they focus it. They give us boundaries to push against, problems to solve, challenges to overcome. + +The next time you face a limitation, don't see it as a barrier. See it as an invitation. An invitation to be more creative, more innovative, more you. + +After all, creativity isn't about having no limits. It's about what you do within the limits you have.`, + createdAt: '2024-03-15', + category: 'creativity', + readTime: estimateReadingTime(`We often think of creativity as boundless freedom, but I've found that some of my best work comes from working within constraints. + +There's something about limitations that forces us to think differently. When we can't rely on our usual approaches, we discover new ones. When resources are limited, we become more resourceful. + +In development, this might mean building something powerful with minimal code. In art, it might mean creating depth with a limited palette. In writing, it might mean telling a complete story in just a few words. + +Constraints don't stifle creativity—they focus it. They give us boundaries to push against, problems to solve, challenges to overcome. + +The next time you face a limitation, don't see it as a barrier. See it as an invitation. An invitation to be more creative, more innovative, more you. + +After all, creativity isn't about having no limits. It's about what you do within the limits you have.`) + } +]; \ No newline at end of file diff --git a/src/data/projects.ts b/src/data/projects.ts new file mode 100644 index 0000000..74358c8 --- /dev/null +++ b/src/data/projects.ts @@ -0,0 +1,43 @@ +export interface Project { + id: string; + title: string; + description: string; + 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: '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 diff --git a/src/utils/readingTime.ts b/src/utils/readingTime.ts new file mode 100644 index 0000000..a1e615e --- /dev/null +++ b/src/utils/readingTime.ts @@ -0,0 +1,27 @@ +/** + * Estimates reading time for a given text + * @param text The text to calculate reading time for + * @returns Estimated reading time in minutes + */ +export function estimateReadingTime(text: string): number { + // Average reading speed: 200 words per minute + const wordsPerMinute = 200; + + // Remove extra whitespace and split into words + const words = text.trim().split(/\s+/).length; + + // Calculate reading time and round up + const readingTime = Math.ceil(words / wordsPerMinute); + + // Minimum 1 minute + return Math.max(1, readingTime); +} + +/** + * Formats reading time with tilde prefix + * @param minutes Reading time in minutes + * @returns Formatted string (e.g., "~3 min") + */ +export function formatReadingTime(minutes: number): string { + return `~${minutes} min`; +} \ No newline at end of file