Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 63 additions & 0 deletions cpsquad/app/blogs/BlogCard.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
"use client";

import { motion } from "framer-motion";

const BlogCard = ({ blog, index, onReadMore }) => {
return (
<motion.article
initial={{ opacity: 0, y: 30 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6, delay: index * 0.1 }}
className="bg-[#1a1a1a] border border-gray-800 rounded-lg overflow-hidden hover:border-green-500 transition-all duration-300 group"
>
<div className="aspect-video bg-gradient-to-br from-green-500/20 to-blue-500/20 relative overflow-hidden">
<div className="absolute inset-0 bg-gradient-to-t from-black/60 to-transparent" />
<div className="absolute bottom-4 left-4">
<span className="text-xs bg-green-500 text-black px-2 py-1 rounded font-semibold">
{blog.category}
</span>
</div>
</div>

<div className="p-6">
<div className="flex items-center gap-3 text-sm text-gray-400 mb-3">
<span>{blog.date}</span>
<span>•</span>
<span>{blog.readTime}</span>
<span>•</span>
<span>{blog.author}</span>
</div>

<h3 className="text-xl font-bold text-white mb-3 group-hover:text-green-400 transition-colors">
{blog.title}
</h3>

<p className="text-gray-400 mb-4 line-clamp-3">
{blog.excerpt}
</p>

<div className="flex items-center justify-between">
<div className="flex flex-wrap gap-2">
{blog.tags.map((tag, tagIndex) => (
<span
key={tagIndex}
className="text-xs bg-gray-800 text-gray-300 px-2 py-1 rounded"
>
{tag}
</span>
))}
</div>

<button
onClick={() => onReadMore(blog)}
className="text-green-400 hover:text-green-300 font-semibold text-sm transition-colors whitespace-nowrap cursor-pointer"
>
Read More →
</button>
</div>
</div>
</motion.article>
);
};

export default BlogCard;
139 changes: 139 additions & 0 deletions cpsquad/app/blogs/BlogPost.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
"use client";

import { motion } from "framer-motion";

const BlogPost = ({ blog, onBack }) => {
return (
<div className="font-sans bg-[#0a0a0a] text-white min-h-screen">
{/* Header */}
<div className="pt-20 pb-8 px-8">
<div className="max-w-4xl mx-auto">
<button
onClick={onBack}
className="flex items-center gap-2 text-green-400 hover:text-green-300 mb-8 transition-colors"
>
← Back to Blogs
</button>

<motion.div
initial={{ opacity: 0, y: 30 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.8 }}
>
<div className="mb-6">
<span className="text-sm bg-green-500 text-black px-3 py-1 rounded-full font-semibold">
{blog.category}
</span>
</div>

<h1 className="text-4xl md:text-6xl font-bold mb-6 tracking-tight">
{blog.title}
</h1>

<div className="flex items-center gap-4 text-gray-400 mb-8">
<span>By {blog.author}</span>
<span>•</span>
<span>{blog.date}</span>
<span>•</span>
<span>{blog.readTime}</span>
</div>

<div className="flex flex-wrap gap-2 mb-8">
{blog.tags.map((tag, index) => (
<span
key={index}
className="text-sm bg-gray-800 text-gray-300 px-3 py-1 rounded-full"
>
{tag}
</span>
))}
</div>
</motion.div>
</div>
</div>

{/* Content */}
<div className="px-8 pb-20">
<div className="max-w-4xl mx-auto">
<motion.div
initial={{ opacity: 0, y: 30 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.8, delay: 0.2 }}
className="blog-content"
dangerouslySetInnerHTML={{ __html: blog.content }}
/>
</div>
</div>

{/* Navigation */}
<div className="px-8 py-8 bg-[#111111]">
<div className="max-w-4xl mx-auto">
<button
onClick={onBack}
className="px-6 py-3 bg-green-500 hover:bg-green-600 text-black font-semibold rounded transition-colors"
>
← Back to All Blogs
</button>
</div>
</div>

<style jsx>{`
.blog-content {
color: #e5e5e5;
line-height: 1.8;
font-size: 1.125rem;
}
.blog-content h2 {
color: #ffffff;
font-size: 1.875rem;
font-weight: 700;
margin: 2rem 0 1rem 0;
border-bottom: 2px solid #22c55e;
padding-bottom: 0.5rem;
}
.blog-content h3 {
color: #22c55e;
font-size: 1.5rem;
font-weight: 600;
margin: 1.5rem 0 0.75rem 0;
}
.blog-content p {
margin-bottom: 1.5rem;
}
.blog-content ul, .blog-content ol {
margin: 1.5rem 0;
padding-left: 2rem;
}
.blog-content li {
margin-bottom: 0.5rem;
}
.blog-content strong {
color: #22c55e;
font-weight: 600;
}
.blog-content pre {
background: #1a1a1a;
border: 1px solid #374151;
border-radius: 0.5rem;
padding: 1rem;
margin: 1.5rem 0;
overflow-x: auto;
font-family: 'Fira Code', monospace;
}
.blog-content code {
background: #1a1a1a;
color: #22c55e;
padding: 0.25rem 0.5rem;
border-radius: 0.25rem;
font-family: 'Fira Code', monospace;
}
.blog-content pre code {
background: transparent;
padding: 0;
}
`}</style>
</div>
);
};

export default BlogPost;
17 changes: 17 additions & 0 deletions cpsquad/app/blogs/FilterButton.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
const FilterButton = ({ filter, currentFilter, onClick, children }) => {
const isActive = currentFilter === filter;
return (
<button
onClick={() => onClick(filter)}
className={`px-4 py-2 rounded-full font-semibold transition-all duration-300 ${
isActive
? "bg-green-500 text-black"
: "bg-gray-800 text-gray-300 hover:bg-gray-700 hover:text-white"
}`}
>
{children}
</button>
);
};

export default FilterButton;
Loading