Skip to content

Commit 0e70d0e

Browse files
committed
app: Add blogs
1 parent e96c233 commit 0e70d0e

3 files changed

Lines changed: 279 additions & 48 deletions

File tree

README.md

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,16 @@
11
# shield44's Portfolio
22

3-
A modern, responsive portfolio website built with Next.js, React, TypeScript, and Tailwind CSS.
3+
A bold, animated portfolio site built with Next.js, React, TypeScript, and Tailwind CSS, plus a dedicated Blogs page for ROM dev notes.
44

55
## 🚀 Features
66

7-
- **Modern Design**: Clean, professional UI with gradient backgrounds and animations
8-
- **Responsive Navbar**: Cool animated navbar that works on all devices
9-
- **Mobile-First**: Fully responsive design with mobile hamburger menu
7+
- **Animated UI**: Gradient backgrounds, glow effects, and Framer Motion transitions
8+
- **Responsive Navbar**: Smooth scrolling for sections + a dedicated Blogs route
109
- **Live Clock**: Real-time clock display in the navbar
11-
- **Smooth Animations**: Framer Motion powered animations throughout
12-
- **Interactive Components**: Hover effects, transitions, and user interactions
13-
- **Project Showcase**: Comprehensive display of ROM development projects
14-
- **Contact Form**: Working contact form with Formspree integration
15-
- **Social Links**: Links to all social profiles and platforms
10+
- **Project Showcase**: Detailed ROM development highlights and download links
11+
- **Videos & Sponsor**: Media section and support options
12+
- **Contact & Socials**: Contact form and social profile links
13+
- **Blogs Page**: Template-driven blog cards aligned with the main UI
1614

1715
## 🛠️ Tech Stack
1816

@@ -31,6 +29,19 @@ A modern, responsive portfolio website built with Next.js, React, TypeScript, an
3129
- **Videos**: Manim and Blender animation showcase
3230
- **Sponsor**: Support options with animated elements
3331
- **Contact**: Contact form and social media links
32+
- **Blogs**: Separate page for blog templates and future posts
33+
34+
## ✍️ Editing Blog Templates
35+
36+
Edit the blog templates in [src/app/blogs/page.tsx](src/app/blogs/page.tsx). Each entry includes:
37+
38+
- `title`: Post headline
39+
- `excerpt`: Short teaser
40+
- `date`: Publish date string
41+
- `readTime`: Estimated reading time
42+
- `tags`: Label chips
43+
- `href`: Link to the post (can be `#` until published)
44+
- `status`: Draft/Template/Published
3445

3546
## 🚀 Development
3647

@@ -45,6 +56,11 @@ yarn dev
4556
yarn build
4657
```
4758

59+
## 🔗 Routes
60+
61+
- `/` Main portfolio site
62+
- `/blogs` Blog templates page
63+
4864
## 📦 Deployment
4965

5066
The site automatically deploys to GitHub Pages when changes are pushed to the main branch.

src/app/blogs/page.tsx

Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
'use client'
2+
3+
import Link from 'next/link'
4+
import { motion } from 'framer-motion'
5+
6+
const blogPosts = [
7+
{
8+
title: 'Building My First ROM: Notes From the Trenches',
9+
excerpt:
10+
'A practical walkthrough of device trees, vendor blobs, and the hard-won lessons from my first official build.',
11+
date: '2026-02-12',
12+
readTime: '6 min read',
13+
tags: ['Android', 'ROM', 'Build'],
14+
href: '#',
15+
status: 'Draft'
16+
},
17+
{
18+
title: 'Kernel Tweaks That Actually Matter',
19+
excerpt:
20+
'The handful of kernel changes that made a measurable difference for battery and stability.',
21+
date: '2026-01-28',
22+
readTime: '4 min read',
23+
tags: ['Kernel', 'Performance'],
24+
href: '#',
25+
status: 'Template'
26+
},
27+
{
28+
title: 'Releasing Builds Without Burning Out',
29+
excerpt:
30+
'A lightweight release checklist and cadence that keeps things sustainable.',
31+
date: '2025-12-18',
32+
readTime: '5 min read',
33+
tags: ['Workflow', 'Release'],
34+
href: '#',
35+
status: 'Template'
36+
}
37+
]
38+
39+
const containerVariants = {
40+
hidden: { opacity: 0 },
41+
visible: {
42+
opacity: 1,
43+
transition: {Building My First ROM: Notes From the Trenches
44+
staggerChildren: 0.15,
45+
delayChildren: 0.2
46+
}
47+
}
48+
}
49+
50+
const itemVariants = {
51+
hidden: { opacity: 0, y: 30 },
52+
visible: { opacity: 1, y: 0, transition: { duration: 0.5 } }
53+
}
54+
55+
export default function BlogsPage() {
56+
return (
57+
<main className="min-h-screen pt-24 pb-20 px-4">
58+
<div className="max-w-6xl mx-auto">
59+
<motion.div
60+
className="text-center mb-14"
61+
initial="hidden"
62+
animate="visible"
63+
variants={containerVariants}
64+
>
65+
<motion.p
66+
className="text-primary uppercase tracking-[0.3em] text-xs md:text-sm mb-4"
67+
variants={itemVariants}
68+
>
69+
Field Notes
70+
</motion.p>
71+
<motion.h1
72+
className="text-4xl md:text-6xl font-bold bg-gradient-to-r from-primary to-blue-400 bg-clip-text text-transparent"
73+
variants={itemVariants}
74+
>
75+
Blogs & Build Logs
76+
</motion.h1>
77+
<motion.p
78+
className="text-gray-300 max-w-2xl mx-auto mt-5"
79+
variants={itemVariants}
80+
>
81+
A focused space for ROM dev insights, release notes, and personal experiments. Edit the templates below to publish new posts.
82+
</motion.p>
83+
</motion.div>
84+
85+
<motion.div
86+
className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6"
87+
initial="hidden"
88+
animate="visible"
89+
variants={containerVariants}
90+
>
91+
{blogPosts.map((post) => (
92+
<motion.article
93+
key={post.title}
94+
className="bg-black/40 backdrop-blur-sm border border-white/10 rounded-xl p-6 hover:border-primary/50 transition-all duration-300 card-glow group"
95+
variants={itemVariants}
96+
whileHover={{ y: -6, scale: 1.01 }}
97+
>
98+
<div className="flex items-center justify-between text-xs text-gray-400 mb-3">
99+
<span>{post.date}</span>
100+
<span>{post.readTime}</span>
101+
</div>
102+
<h2 className="text-lg font-semibold text-primary group-hover:text-blue-400 transition-colors duration-300">
103+
{post.title}
104+
</h2>
105+
<p className="text-gray-400 text-sm mt-3 leading-relaxed">
106+
{post.excerpt}
107+
</p>
108+
<div className="flex flex-wrap gap-2 mt-4">
109+
{post.tags.map((tag) => (
110+
<span
111+
key={tag}
112+
className="px-2 py-1 bg-primary/10 border border-primary/30 text-primary text-[11px] rounded-full"
113+
>
114+
{tag}
115+
</span>
116+
))}
117+
</div>
118+
<div className="flex items-center justify-between mt-6">
119+
<span className="text-[11px] uppercase tracking-[0.2em] text-gray-400">
120+
{post.status}
121+
</span>
122+
<a
123+
href={post.href}
124+
className="text-sm text-primary hover:text-blue-400 transition-colors duration-300"
125+
>
126+
Read more →
127+
</a>
128+
</div>
129+
</motion.article>
130+
))}
131+
</motion.div>
132+
133+
<motion.div
134+
className="mt-16 bg-black/40 border border-white/10 rounded-2xl p-8 md:p-10 text-center"
135+
initial={{ opacity: 0, y: 30 }}
136+
animate={{ opacity: 1, y: 0 }}
137+
transition={{ duration: 0.6, delay: 0.2 }}
138+
>
139+
<h3 className="text-2xl md:text-3xl font-semibold text-white">
140+
Want to add a new post?
141+
</h3>
142+
<p className="text-gray-300 mt-3">
143+
Duplicate an entry in the blog template array and update the title, excerpt, tags, and link.
144+
</p>
145+
<div className="mt-6 flex flex-col sm:flex-row gap-4 justify-center">
146+
<Link
147+
href="/"
148+
className="px-6 py-3 rounded-full border border-primary text-primary hover:bg-primary hover:text-black transition-all duration-300"
149+
>
150+
Back to Home
151+
</Link>
152+
<a
153+
href="https://github.com/shield44"
154+
className="px-6 py-3 rounded-full bg-gradient-to-r from-primary to-blue-500 text-black font-semibold hover:shadow-lg hover:shadow-primary/40 transition-all duration-300"
155+
target="_blank"
156+
rel="noopener noreferrer"
157+
>
158+
View GitHub
159+
</a>
160+
</div>
161+
</motion.div>
162+
</div>
163+
</main>
164+
)
165+
}

src/components/Navbar.tsx

Lines changed: 89 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
'use client'
22

33
import { useState, useEffect } from 'react'
4+
import Link from 'next/link'
45
import { motion, AnimatePresence } from 'framer-motion'
56
import { Menu, X, Clock } from 'lucide-react'
67

@@ -36,7 +37,8 @@ const Navbar = () => {
3637
{ href: '#projects', label: 'Projects' },
3738
{ href: '#about', label: 'About me' },
3839
{ href: '#contact', label: 'Contact' },
39-
{ href: '#sponsor', label: 'Sponsor' }
40+
{ href: '#sponsor', label: 'Sponsor' },
41+
{ href: '/blogs', label: 'Blogs', external: true }
4042
]
4143

4244
const handleMenuClick = (href: string) => {
@@ -48,6 +50,8 @@ const Navbar = () => {
4850
}
4951
}
5052

53+
const isHashLink = (href: string) => href.startsWith('#')
54+
5155
return (
5256
<>
5357
{/* Neon Clock */}
@@ -86,27 +90,50 @@ const Navbar = () => {
8690
{/* Desktop Menu */}
8791
<div className="hidden md:block">
8892
<div className="ml-10 flex items-baseline space-x-8">
89-
{menuItems.map((item, index) => (
90-
<motion.a
91-
key={item.href}
92-
href={item.href}
93-
onClick={(e) => {
94-
e.preventDefault()
95-
handleMenuClick(item.href)
96-
}}
97-
className="text-gray-300 hover:text-primary px-3 py-2 text-sm font-medium transition-all duration-300 relative group cursor-pointer"
98-
initial={{ opacity: 0, y: -20 }}
99-
animate={{ opacity: 1, y: 0 }}
100-
transition={{ duration: 0.5, delay: 0.1 * index }}
101-
whileHover={{ scale: 1.05 }}
102-
>
103-
{item.label}
104-
<motion.div
105-
className="absolute bottom-0 left-0 w-0 h-0.5 bg-gradient-to-r from-primary to-blue-400 group-hover:w-full transition-all duration-300"
106-
whileHover={{ width: '100%' }}
107-
/>
108-
</motion.a>
109-
))}
93+
{menuItems.map((item, index) => {
94+
const linkClasses = "text-gray-300 hover:text-primary px-3 py-2 text-sm font-medium transition-all duration-300 relative group cursor-pointer"
95+
if (isHashLink(item.href)) {
96+
return (
97+
<motion.a
98+
key={item.href}
99+
href={item.href}
100+
onClick={(e) => {
101+
e.preventDefault()
102+
handleMenuClick(item.href)
103+
}}
104+
className={linkClasses}
105+
initial={{ opacity: 0, y: -20 }}
106+
animate={{ opacity: 1, y: 0 }}
107+
transition={{ duration: 0.5, delay: 0.1 * index }}
108+
whileHover={{ scale: 1.05 }}
109+
>
110+
{item.label}
111+
<motion.div
112+
className="absolute bottom-0 left-0 w-0 h-0.5 bg-gradient-to-r from-primary to-blue-400 group-hover:w-full transition-all duration-300"
113+
whileHover={{ width: '100%' }}
114+
/>
115+
</motion.a>
116+
)
117+
}
118+
119+
return (
120+
<motion.div
121+
key={item.href}
122+
initial={{ opacity: 0, y: -20 }}
123+
animate={{ opacity: 1, y: 0 }}
124+
transition={{ duration: 0.5, delay: 0.1 * index }}
125+
whileHover={{ scale: 1.05 }}
126+
>
127+
<Link
128+
href={item.href}
129+
className={linkClasses}
130+
>
131+
{item.label}
132+
<span className="absolute bottom-0 left-0 w-0 h-0.5 bg-gradient-to-r from-primary to-blue-400 group-hover:w-full transition-all duration-300" />
133+
</Link>
134+
</motion.div>
135+
)
136+
})}
110137
</div>
111138
</div>
112139

@@ -156,23 +183,46 @@ const Navbar = () => {
156183
transition={{ duration: 0.3 }}
157184
>
158185
<div className="px-2 pt-2 pb-3 space-y-1 sm:px-3">
159-
{menuItems.map((item, index) => (
160-
<motion.a
161-
key={item.href}
162-
href={item.href}
163-
onClick={(e) => {
164-
e.preventDefault()
165-
handleMenuClick(item.href)
166-
}}
167-
className="text-gray-300 hover:text-primary hover:bg-gray-700/50 block px-3 py-2 text-base font-medium rounded-md transition-colors duration-200 cursor-pointer"
168-
initial={{ opacity: 0, x: -20 }}
169-
animate={{ opacity: 1, x: 0 }}
170-
transition={{ duration: 0.3, delay: 0.1 * index }}
171-
whileHover={{ x: 10 }}
172-
>
173-
{item.label}
174-
</motion.a>
175-
))}
186+
{menuItems.map((item, index) => {
187+
const linkClasses = "text-gray-300 hover:text-primary hover:bg-gray-700/50 block px-3 py-2 text-base font-medium rounded-md transition-colors duration-200 cursor-pointer"
188+
if (isHashLink(item.href)) {
189+
return (
190+
<motion.a
191+
key={item.href}
192+
href={item.href}
193+
onClick={(e) => {
194+
e.preventDefault()
195+
handleMenuClick(item.href)
196+
}}
197+
className={linkClasses}
198+
initial={{ opacity: 0, x: -20 }}
199+
animate={{ opacity: 1, x: 0 }}
200+
transition={{ duration: 0.3, delay: 0.1 * index }}
201+
whileHover={{ x: 10 }}
202+
>
203+
{item.label}
204+
</motion.a>
205+
)
206+
}
207+
208+
return (
209+
<motion.div
210+
key={item.href}
211+
initial={{ opacity: 0, x: -20 }}
212+
animate={{ opacity: 1, x: 0 }}
213+
transition={{ duration: 0.3, delay: 0.1 * index }}
214+
whileHover={{ x: 10 }}
215+
>
216+
<Link
217+
href={item.href}
218+
className={linkClasses}
219+
onClick={() => setIsOpen(false)}
220+
>
221+
{item.label}
222+
</Link>
223+
</motion.div>
224+
)
225+
})}
176226
</div>
177227
</motion.div>
178228
)}

0 commit comments

Comments
 (0)