From 77ba446d4da35b58666a01fbdd7723cd8ee84d07 Mon Sep 17 00:00:00 2001 From: seccodesmith Date: Thu, 22 May 2025 15:05:24 +0200 Subject: [PATCH 1/7] feat: add Blog and BlogCard styles, refactor components to use CSS modules, and remove unused CSS files --- .../styles/{Blog.scss => Blog.module.scss} | 245 +++++-------- src/assets/styles/BlogCard.module.scss | 95 +++++ src/assets/styles/Home.module.scss | 2 +- src/components/BlogCard.tsx | 29 +- src/index.css | 210 ----------- src/pages/Blog.module.css | 342 ------------------ src/pages/Blog.tsx | 71 ++-- src/pages/Home.tsx | 9 - 8 files changed, 229 insertions(+), 774 deletions(-) rename src/assets/styles/{Blog.scss => Blog.module.scss} (50%) create mode 100644 src/assets/styles/BlogCard.module.scss delete mode 100644 src/index.css delete mode 100644 src/pages/Blog.module.css diff --git a/src/assets/styles/Blog.scss b/src/assets/styles/Blog.module.scss similarity index 50% rename from src/assets/styles/Blog.scss rename to src/assets/styles/Blog.module.scss index db9a0de..154a440 100644 --- a/src/assets/styles/Blog.scss +++ b/src/assets/styles/Blog.module.scss @@ -1,7 +1,8 @@ @use "./variables.scss" as *; + /* Page Header */ -.page-header { +.pageHeader { padding-top: 7rem; padding-bottom: 3rem; background-color: $secondary-dark; @@ -9,7 +10,7 @@ overflow: hidden; } -.page-header::before { +.pageHeader::before { content: ""; position: absolute; width: 100%; @@ -22,14 +23,14 @@ z-index: 0; } -.page-title { +.pageTitle { font-size: 3rem; margin-bottom: 1rem; position: relative; display: inline-block; } -.page-title::after { +.pageTitle::after { content: ""; position: absolute; width: 60px; @@ -41,153 +42,89 @@ box-shadow: $glow-effect; } -.page-subtitle { +.pageSubtitle { font-size: 1.2rem; color: $text-ash; } -/* Card styling */ -.card { - background-color: $card-bg; - border: 1px solid $card-border; - border-radius: 8px; - transition: all 0.3s ease; - margin-bottom: 1.5rem; - overflow: hidden; -} - -.card:hover { - transform: translateY(-5px); - border-color: rgba(255, 100, 0, 0.3); - box-shadow: 0 10px 30px rgba(0, 0, 0, 0.4); -} - -.card-img-top { - height: 200px; - object-fit: cover; -} - -.featured-img { - height: 300px; -} - -.card-title { - font-size: 1.5rem; - margin-bottom: 0.75rem; -} - -.card-title a { - color: $text-light; - text-decoration: none; - transition: color 0.3s; -} - -.card-title a:hover { - color: $accent-fire; -} - -.card-text { - color: $text-ash; - font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; - line-height: 1.6; -} - -.featured-card .card-title { - font-size: 1.8rem; -} - -.card-footer { - background-color: rgba(0, 0, 0, 0.2); - border-top: 1px solid $card-border; -} - -/* Category badges */ -.category-badge { - position: absolute; - top: 1rem; - right: 1rem; - background-color: rgba(139, 0, 0, 0.9); - color: $text-light; - padding: 0.25rem 0.75rem; - border-radius: 4px; - font-size: 0.8rem; - font-weight: 600; - text-transform: uppercase; - letter-spacing: 1px; -} - -.featured-badge { - position: absolute; - top: 1rem; - left: 1rem; - background: linear-gradient(45deg, $accent-dragon, $accent-fire); - color: $text-light; - padding: 0.25rem 0.75rem; - border-radius: 4px; - font-size: 0.8rem; - font-weight: 600; - text-transform: uppercase; - letter-spacing: 1px; - display: flex; - align-items: center; - gap: 5px; -} /* Meta items */ -.meta-item { - display: inline-flex; - align-items: center; - gap: 6px; - margin-right: 1rem; - color: $text-ash; - font-size: 0.9rem; +@mixin metaItem { + .metaItem { + display: inline-flex; + align-items: center; + gap: 6px; + margin-right: 1rem; + color: $text-ash; + font-size: 0.9rem; + } } -.meta-icon { - color: $accent-fire; + +@mixin metaIcon { + .metaIcon { + font-size: 1.2rem; + color: $text-ash; + transition: all 0.3s ease; + } + + .metaIcon:hover { + color: $accent-fire; + } } + /* Read more link */ -.read-more { - display: inline-block; - color: $accent-fire; - text-decoration: none; - font-weight: 600; - transition: all 0.3s ease; - position: relative; -} -.read-more::after { - content: "→"; - margin-left: 5px; - transition: transform 0.3s ease; -} +@mixin readMore { + .readMore { + display: inline-block; + color: $accent-fire; + text-decoration: none; + font-weight: 600; + transition: all 0.3s ease; + position: relative; + } -.read-more:hover::after { - transform: translateX(5px); + .readMore::after { + content: "→"; + margin-left: 5px; + transition: transform 0.3s ease; + } + + .readMore:hover::after { + transform: translateX(5px); + } } /* Tags */ -.post-tag { - display: inline-block; - background-color: rgba(255, 100, 0, 0.1); - color: $accent-fire; - padding: 0.25rem 0.5rem; - border-radius: 4px; - font-size: 0.8rem; - font-weight: 500; - margin-right: 0.5rem; - margin-bottom: 0.5rem; - transition: all 0.3s ease; +@mixin postTag { + .postTag { + display: inline-block; + background-color: rgba(255, 100, 0, 0.1); + color: $accent-fire; + padding: 0.25rem 0.5rem; + border-radius: 4px; + font-size: 0.8rem; + font-weight: 500; + margin-right: 0.5rem; + margin-bottom: 0.5rem; + transition: all 0.3s ease; + } + + .postTag:hover { + background-color: rgba(255, 100, 0, 0.2); + transform: translateY(-2px); + } } -.post-tag:hover { - background-color: rgba(255, 100, 0, 0.2); - transform: translateY(-2px); -} +@include metaItem; +@include metaIcon; +@include readMore; +@include postTag; /* Sidebar */ -.sidebar-widget { +.sidebarWidget { background-color: $card-bg; border-radius: 8px; padding: 1.5rem; @@ -195,7 +132,7 @@ border: 1px solid $card-border; } -.widget-title { +.widgetTitle { font-size: 1.2rem; margin-bottom: 1.25rem; padding-bottom: 0.625rem; @@ -203,7 +140,7 @@ position: relative; } -.widget-title::after { +.widgetTitle::after { content: ""; position: absolute; width: 40px; @@ -213,40 +150,40 @@ left: 0; } -.search-form .form-control { +.searchForm .formControl { background-color: rgba(255, 255, 255, 0.05); border: 1px solid $card-border; color: $text-light; } -.search-form .form-control:focus { +.searchForm .formControl:focus { box-shadow: 0 0 0 0.25rem rgba(255, 100, 0, 0.25); border-color: $accent-fire; } -.search-form .btn { +.searchForm .btn { background-color: $accent-fire; border-color: $accent-fire; } -.search-form .btn:hover { +.searchForm .btn:hover { background-color: $accent-dragon; border-color: $accent-dragon; } /* Lists in sidebar */ -.list-group-item { +.listGroupItem { background-color: transparent; border-color: rgba(255, 255, 255, 0.05); color: $text-ash; transition: all 0.3s ease; } -.list-group-item:hover { +.listGroupItem:hover { background-color: rgba(255, 100, 0, 0.1); } -.list-group-item a { +.listGroupItem a { color: $text-ash; text-decoration: none; display: flex; @@ -254,11 +191,11 @@ align-items: center; } -.list-group-item a:hover { +.listGroup-item a:hover { color: $accent-fire; } -.category-count { +.categoryCount { background-color: rgba(255, 100, 0, 0.1); color: $accent-fire; padding: 2px 8px; @@ -267,7 +204,7 @@ } /* Recent posts in sidebar */ -.recent-post-item { +.recentPostItem { display: flex; align-items: center; gap: 10px; @@ -276,65 +213,65 @@ border-bottom: 1px solid rgba(255, 255, 255, 0.05); } -.recent-post-item:last-child { +.recentPostItem:last-child { margin-bottom: 0; padding-bottom: 0; border-bottom: none; } -.recent-post-thumb { +.recentPostThumb { width: 60px; height: 60px; border-radius: 4px; object-fit: cover; } -.recent-post-info { +.recentPostInfo { flex-grow: 1; } -.recent-post-title { +.recentPostTitle { font-size: 0.9rem; line-height: 1.4; margin-bottom: 5px; } -.recent-post-title a { +.recentPostTitle a { color: $text-ash; text-decoration: none; transition: color 0.3s; } -.recent-post-title a:hover { +.recentPostTitle a:hover { color: $accent-fire; } -.recent-post-date { +.recentPostDate { font-size: 0.8rem; color: rgba(255, 255, 255, 0.5); } /* Pagination */ -.page-item .page-link { +.pageItem .pageLink { background-color: $card-bg; border-color: $card-border; color: $text-ash; } -.page-item .page-link:hover { +.pageItem .pageLink:hover { background-color: rgba(255, 100, 0, 0.1); color: $accent-fire; border-color: $accent-fire; } -.page-item.active .page-link { +.pageItem.active .pageLink { background-color: $accent-fire; border-color: $accent-fire; color: $text-light; } /* Binary background effect */ -.binary-line { +.binaryLine { position: absolute; font-family: 'Fira Code', monospace; color: $accent-fire; diff --git a/src/assets/styles/BlogCard.module.scss b/src/assets/styles/BlogCard.module.scss new file mode 100644 index 0000000..954b4db --- /dev/null +++ b/src/assets/styles/BlogCard.module.scss @@ -0,0 +1,95 @@ +@use "./variables.scss" as *; +@use "./Blog.module.scss" as Blog; + +@include Blog.metaItem; +@include Blog.metaIcon; +@include Blog.readMore; +@include Blog.postTag; + + +/* Card styling */ +.card { + background-color: $card-bg; + border: 1px solid $card-border; + border-radius: 8px; + transition: all 0.3s ease; + margin-bottom: 1.5rem; + overflow: hidden; +} + +.card:hover { + transform: translateY(-5px); + border-color: rgba(255, 100, 0, 0.3); + box-shadow: 0 10px 30px rgba(0, 0, 0, 0.4); +} + +.cardImgTop { + height: 200px; + object-fit: cover; +} + +.featuredImg { + height: 300px; +} + +.cardTitle { + font-size: 1.5rem; + margin-bottom: 0.75rem; +} + +.cardTitle a { + color: $text-light; + text-decoration: none; + transition: color 0.3s; +} + +.cardTitle a:hover { + color: $accent-fire; +} + +.cardText { + color: $text-ash; + font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; + line-height: 1.6; +} + +.featuredCard .cardTitle { + font-size: 1.8rem; +} + +.cardFooter { + background-color: rgba(0, 0, 0, 0.2); + border-top: 1px solid $card-border; +} + +/* Category badges */ +.categoryBadge { + position: absolute; + top: 1rem; + right: 1rem; + background-color: rgba(139, 0, 0, 0.9); + color: $text-light; + padding: 0.25rem 0.75rem; + border-radius: 4px; + font-size: 0.8rem; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 1px; +} + +.featuredBadge { + position: absolute; + top: 1rem; + left: 1rem; + background: linear-gradient(45deg, $accent-dragon, $accent-fire); + color: $text-light; + padding: 0.25rem 0.75rem; + border-radius: 4px; + font-size: 0.8rem; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 1px; + display: flex; + align-items: center; + gap: 5px; +} diff --git a/src/assets/styles/Home.module.scss b/src/assets/styles/Home.module.scss index 1bcdaca..965247c 100644 --- a/src/assets/styles/Home.module.scss +++ b/src/assets/styles/Home.module.scss @@ -73,7 +73,7 @@ color: $accent-fire; font-size: 1rem; animation: fall linear infinite; - opacity: 0.7; + opacity: 0.8; } @keyframes fall { diff --git a/src/components/BlogCard.tsx b/src/components/BlogCard.tsx index 23eb7d4..d9f34bd 100644 --- a/src/components/BlogCard.tsx +++ b/src/components/BlogCard.tsx @@ -1,4 +1,5 @@ import { Link } from 'react-router-dom'; +import styles from '@styles/BlogCard.module.scss'; export interface BlogPostProps { id: string; @@ -22,43 +23,43 @@ interface BlogCardProps { export const BlogCard: React.FC = ({ post }) => { return ( -
+
- {post.title} + {post.title}
{post.category}
{post.featured && ( -
+
Featured
)}
-
-

+
+

{post.title}

-
-
+
+
{post.date}
-
- +
+ {post.author}
-
- +
+ {post.commentCount} Comments
-

+

{post.excerpt}

- Continue Reading + Continue Reading
{post.tags.map((tag, index) => ( - {tag} + {tag} ))}
diff --git a/src/index.css b/src/index.css deleted file mode 100644 index 62c1518..0000000 --- a/src/index.css +++ /dev/null @@ -1,210 +0,0 @@ -:root { - --primary-dark: #0a0a0a; - --secondary-dark: #1a1a1a; - --accent-fire: #ff6400; - --accent-ember: #ff4500; - --accent-dragon: #8B0000; - --text-light: #f5f5f5; - --text-ash: #aaaaaa; - --glow-effect: 0 0 10px var(--accent-fire), 0 0 20px rgba(255, 100, 0, 0.4); - --card-bg: #141414; - --card-border: #2a2a2a; -} - -body { - background-color: var(--primary-dark); - color: var(--text-light); - background-image: url('assets/images/background.png'); - background-repeat: round; - background-size: 300px 300px; - background-position: 0 0; - position: relative; - overflow-x: hidden; - font-family: 'Cinzel', serif; -} - -body::before { - content: ""; - position: fixed; - top: 0; - left: 0; - width: 100%; - height: 100%; - background: radial-gradient(circle at 50% 50%, rgba(139, 0, 0, 0.08), rgba(10, 10, 10, 0.4)); - pointer-events: none; - z-index: -1; -} - -/* Bootstrap Overrides */ -.navbar { - background-color: rgba(10, 10, 10, 0.95) !important; - border-bottom: 1px solid rgba(255, 100, 0, 0.3); -} - -.navbar-brand { - display: flex; - align-items: center; - gap: 12px; -} - -.navbar-toggler { - border: 1px solid rgba(255, 100, 0, 0.5); - color: var(--accent-fire); -} - -.navbar-toggler-icon { - background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%28255, 100, 0, 0.8%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e"); -} - -.nav-link { - color: var(--text-light) !important; - position: relative; - padding: 0.5rem 1rem; - transition: color 0.3s; -} - -.nav-link:hover, -.nav-link.active { - color: var(--accent-fire) !important; -} - -.nav-link::after { - content: ""; - position: absolute; - width: 0; - height: 2px; - bottom: 0; - left: 0; - background: linear-gradient(to right, var(--accent-fire), transparent); - transition: width 0.3s ease; -} - -.nav-link:hover::after, -.nav-link.active::after { - width: 100%; -} - -.logo-image{ - height: 2rem; -} -/* Logo styling */ -.logo-icon { - height: 2rem; - position: relative; - display: inline-flex; - align-items: center; - justify-content: center; -} - -.logo-text { - font-weight: 700; - font-size: 1.5rem; - background: linear-gradient(to right, var(--accent-fire), var(--text-light)); - -webkit-background-clip: text; - background-clip: text; - color: transparent; - letter-spacing: 1px; -} - - - -@keyframes fall { - 0% { - transform: translateY(-100%); - } - - 100% { - transform: translateY(100vh); - } -} - - - -@keyframes rise { - 0% { - transform: translateY(0) translateX(0); - opacity: 0; - } - - 10% { - opacity: 0.5; - } - - 90% { - opacity: 0.5; - } - - 100% { - transform: translateY(-100px) translateX(var(--x)); - opacity: 0; - } -} - -/* Footer */ -.footer { - background-color: var(--primary-dark); - padding: 3rem 0; - border-top: 1px solid rgba(255, 100, 0, 0.2); - margin-top: 5rem; -} - -.social-links { - display: flex; - justify-content: center; - gap: 20px; - margin-bottom: 1.5rem; -} - -.social-icon { - font-size: 1.5rem; - color: var(--text-ash); - transition: all 0.3s ease; -} - -.social-icon:hover { - color: var(--accent-fire); - transform: translateY(-5px); -} - -.copyright { - color: var(--text-ash); - font-size: 0.9rem; - margin-top: 1rem; - text-align: center; -} - -/* Responsive adjustments */ -@media (max-width: 992px) { - .title-main { - font-size: 3rem; - } -} - -@media (max-width: 768px) { - .title-main { - font-size: 2.5rem; - } - - .subtitle { - font-size: 1.2rem; - } - - .binary-line { - font-size: 0.8rem; - } -} - -@media (max-width: 576px) { - .title-main { - font-size: 2rem; - } - - .subtitle { - font-size: 1rem; - } - - .btn-primary { - padding: 0.6rem 1.5rem; - font-size: 0.9rem; - } -} \ No newline at end of file diff --git a/src/pages/Blog.module.css b/src/pages/Blog.module.css deleted file mode 100644 index 9419c93..0000000 --- a/src/pages/Blog.module.css +++ /dev/null @@ -1,342 +0,0 @@ -/* Page Header */ -.page-header { - padding-top: 7rem; - padding-bottom: 3rem; - background-color: var(--secondary-dark); - position: relative; - overflow: hidden; -} - -.page-header::before { - content: ""; - position: absolute; - width: 100%; - height: 100%; - top: 0; - left: 0; - background-image: url('/api/placeholder/1200/400'); - opacity: 0.15; - filter: brightness(0.3); - z-index: 0; -} - -.page-title { - font-size: 3rem; - margin-bottom: 1rem; - position: relative; - display: inline-block; -} - -.page-title::after { - content: ""; - position: absolute; - width: 60px; - height: 3px; - background: var(--accent-fire); - bottom: -10px; - left: 50%; - transform: translateX(-50%); - box-shadow: var(--glow-effect); -} - -.page-subtitle { - font-size: 1.2rem; - color: var(--text-ash); -} - -/* Card styling */ -.card { - background-color: var(--card-bg); - border: 1px solid var(--card-border); - border-radius: 8px; - transition: all 0.3s ease; - margin-bottom: 1.5rem; - overflow: hidden; -} - -.card:hover { - transform: translateY(-5px); - border-color: rgba(255, 100, 0, 0.3); - box-shadow: 0 10px 30px rgba(0, 0, 0, 0.4); -} - -.card-img-top { - height: 200px; - object-fit: cover; -} - -.featured-img { - height: 300px; -} - -.card-title { - font-size: 1.5rem; - margin-bottom: 0.75rem; -} - -.card-title a { - color: var(--text-light); - text-decoration: none; - transition: color 0.3s; -} - -.card-title a:hover { - color: var(--accent-fire); -} - -.card-text { - color: var(--text-ash); - font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; - line-height: 1.6; -} - -.featured-card .card-title { - font-size: 1.8rem; -} - -.card-footer { - background-color: rgba(0, 0, 0, 0.2); - border-top: 1px solid var(--card-border); -} - -/* Category badges */ -.category-badge { - position: absolute; - top: 1rem; - right: 1rem; - background-color: rgba(139, 0, 0, 0.9); - color: var(--text-light); - padding: 0.25rem 0.75rem; - border-radius: 4px; - font-size: 0.8rem; - font-weight: 600; - text-transform: uppercase; - letter-spacing: 1px; -} - -.featured-badge { - position: absolute; - top: 1rem; - left: 1rem; - background: linear-gradient(45deg, var(--accent-dragon), var(--accent-fire)); - color: var(--text-light); - padding: 0.25rem 0.75rem; - border-radius: 4px; - font-size: 0.8rem; - font-weight: 600; - text-transform: uppercase; - letter-spacing: 1px; - display: flex; - align-items: center; - gap: 5px; -} - -/* Meta items */ -.meta-item { - display: inline-flex; - align-items: center; - gap: 6px; - margin-right: 1rem; - color: var(--text-ash); - font-size: 0.9rem; -} - -.meta-icon { - color: var(--accent-fire); -} - -/* Read more link */ -.read-more { - display: inline-block; - color: var(--accent-fire); - text-decoration: none; - font-weight: 600; - transition: all 0.3s ease; - position: relative; -} - -.read-more::after { - content: "→"; - margin-left: 5px; - transition: transform 0.3s ease; -} - -.read-more:hover::after { - transform: translateX(5px); -} - -/* Tags */ -.post-tag { - display: inline-block; - background-color: rgba(255, 100, 0, 0.1); - color: var(--accent-fire); - padding: 0.25rem 0.5rem; - border-radius: 4px; - font-size: 0.8rem; - font-weight: 500; - margin-right: 0.5rem; - margin-bottom: 0.5rem; - transition: all 0.3s ease; -} - -.post-tag:hover { - background-color: rgba(255, 100, 0, 0.2); - transform: translateY(-2px); -} - -/* Sidebar */ -.sidebar-widget { - background-color: var(--card-bg); - border-radius: 8px; - padding: 1.5rem; - margin-bottom: 1.5rem; - border: 1px solid var(--card-border); -} - -.widget-title { - font-size: 1.2rem; - margin-bottom: 1.25rem; - padding-bottom: 0.625rem; - border-bottom: 1px solid rgba(255, 255, 255, 0.1); - position: relative; -} - -.widget-title::after { - content: ""; - position: absolute; - width: 40px; - height: 2px; - background: var(--accent-fire); - bottom: -1px; - left: 0; -} - -.search-form .form-control { - background-color: rgba(255, 255, 255, 0.05); - border: 1px solid var(--card-border); - color: var(--text-light); -} - -.search-form .form-control:focus { - box-shadow: 0 0 0 0.25rem rgba(255, 100, 0, 0.25); - border-color: var(--accent-fire); -} - -.search-form .btn { - background-color: var(--accent-fire); - border-color: var(--accent-fire); -} - -.search-form .btn:hover { - background-color: var(--accent-dragon); - border-color: var(--accent-dragon); -} - -/* Lists in sidebar */ -.list-group-item { - background-color: transparent; - border-color: rgba(255, 255, 255, 0.05); - color: var(--text-ash); - transition: all 0.3s ease; -} - -.list-group-item:hover { - background-color: rgba(255, 100, 0, 0.1); -} - -.list-group-item a { - color: var(--text-ash); - text-decoration: none; - display: flex; - justify-content: space-between; - align-items: center; -} - -.list-group-item a:hover { - color: var(--accent-fire); -} - -.category-count { - background-color: rgba(255, 100, 0, 0.1); - color: var(--accent-fire); - padding: 2px 8px; - border-radius: 10px; - font-size: 0.8rem; -} - -/* Recent posts in sidebar */ -.recent-post-item { - display: flex; - align-items: center; - gap: 10px; - margin-bottom: 1rem; - padding-bottom: 1rem; - border-bottom: 1px solid rgba(255, 255, 255, 0.05); -} - -.recent-post-item:last-child { - margin-bottom: 0; - padding-bottom: 0; - border-bottom: none; -} - -.recent-post-thumb { - width: 60px; - height: 60px; - border-radius: 4px; - object-fit: cover; -} - -.recent-post-info { - flex-grow: 1; -} - -.recent-post-title { - font-size: 0.9rem; - line-height: 1.4; - margin-bottom: 5px; -} - -.recent-post-title a { - color: var(--text-ash); - text-decoration: none; - transition: color 0.3s; -} - -.recent-post-title a:hover { - color: var(--accent-fire); -} - -.recent-post-date { - font-size: 0.8rem; - color: rgba(255, 255, 255, 0.5); -} - -/* Pagination */ -.page-item .page-link { - background-color: var(--card-bg); - border-color: var(--card-border); - color: var(--text-ash); -} - -.page-item .page-link:hover { - background-color: rgba(255, 100, 0, 0.1); - color: var(--accent-fire); - border-color: var(--accent-fire); -} - -.page-item.active .page-link { - background-color: var(--accent-fire); - border-color: var(--accent-fire); - color: var(--text-light); -} - -/* Binary background effect */ -.binary-line { - position: absolute; - font-family: 'Fira Code', monospace; - color: var(--accent-fire); - font-size: 0.8rem; - opacity: 0.15; - pointer-events: none; -} \ No newline at end of file diff --git a/src/pages/Blog.tsx b/src/pages/Blog.tsx index 20bd874..8e2b5da 100644 --- a/src/pages/Blog.tsx +++ b/src/pages/Blog.tsx @@ -3,13 +3,11 @@ import { PageHeader } from '../components/PageHeader'; import { BlogCard } from '../components/BlogCard'; import { blogPostsData } from '../data/blogPostsData'; -import '@styles/_variables.scss'; -import '@styles/Blog.scss'; +import style from '@styles/Blog.module.scss'; export const Blog = () => { const [searchQuery, setSearchQuery] = useState(''); - // Filter blog posts based on search query const filteredPosts = searchQuery ? blogPostsData.filter(post => post.title.toLowerCase().includes(searchQuery.toLowerCase()) || @@ -18,21 +16,16 @@ export const Blog = () => { ) : blogPostsData; - // Get the featured post const featuredPost = blogPostsData.find(post => post.featured); - // Get regular posts (non-featured) const regularPosts = filteredPosts.filter(post => !post.featured); - // Handle search input change const handleSearchChange = (e: React.ChangeEvent) => { setSearchQuery(e.target.value); }; - // Generate a list of unique categories const categories = Array.from(new Set(blogPostsData.map(post => post.category))); - // Generate a list of unique tags const allTags = blogPostsData.flatMap(post => post.tags); const uniqueTags = Array.from(new Set(allTags)); @@ -45,9 +38,7 @@ export const Blog = () => {
- {/* Main Content */}
- {/* Featured Post */} {featuredPost && (!searchQuery || featuredPost.title.toLowerCase().includes(searchQuery.toLowerCase()) || featuredPost.excerpt.toLowerCase().includes(searchQuery.toLowerCase()) || @@ -56,7 +47,6 @@ export const Blog = () => { )} - {/* Regular Posts */}
{regularPosts.map(post => (
@@ -65,7 +55,6 @@ export const Blog = () => { ))}
- {/* No Posts Message */} {filteredPosts.length === 0 && (

No posts found matching your search.

@@ -78,20 +67,19 @@ export const Blog = () => {
)} - {/* Pagination */} {filteredPosts.length > 0 && (
- {/* Sidebar */} -
- {/* Search Widget */} -
-

Search

-
+
+
+

Search

+
{ value={searchQuery} onChange={handleSearchChange} /> -
- {/* Categories Widget */} -
-

Categories

+
+

Categories

{categories.map((category, index) => { const count = blogPostsData.filter(post => post.category === category).length; return ( -
+ ); @@ -139,32 +124,30 @@ export const Blog = () => {
- {/* Recent Posts Widget */} -
-

Recent Posts

+
+

Recent Posts

{blogPostsData.slice(0, 3).map(post => ( -
- {post.title} -
-

+
+ {post.title} +
+

{post.title}

- {post.date} + {post.date}
))}

- {/* Tags Widget */} -
-

Popular Tags

+
+

Popular Tags

{uniqueTags.map((tag, index) => ( setSearchQuery(tag)} style={{ cursor: 'pointer' }} > diff --git a/src/pages/Home.tsx b/src/pages/Home.tsx index 508dfbe..194bb4d 100644 --- a/src/pages/Home.tsx +++ b/src/pages/Home.tsx @@ -36,7 +36,6 @@ export const Home = () => { const particlesContainer = document.getElementById('particles'); if (!particlesContainer) return; - // Clear existing particles particlesContainer.innerHTML = ''; const particlesCount = 50; @@ -46,29 +45,24 @@ export const Home = () => { const particle = document.createElement('div'); particle.className = styles.particle; - // Random position at the bottom of the hero section const posX = Math.random() * window.innerWidth; const posY = window.innerHeight - 100 + (Math.random() * 50); particle.style.left = `${posX}px`; particle.style.top = `${posY}px`; - // Random size const size = Math.random() * 3 + 1; particle.style.width = `${size}px`; particle.style.height = `${size}px`; - // Random drift const xDrift = (Math.random() - 0.5) * 100; particle.style.setProperty('--x', `${xDrift}px`); - // Random animation duration const duration = Math.random() * 3 + 2; particle.style.animationDuration = `${duration}s`; particlesContainer.appendChild(particle); - // Remove particle after animation completes setTimeout(() => { particle.remove(); }, duration * 1000); @@ -79,7 +73,6 @@ export const Home = () => { createBinaryBg(); createParticles(); - // Create new particles periodically const particleInterval = setInterval(() => { const particlesContainer = document.getElementById('particles'); if (particlesContainer && particlesContainer.childElementCount < 30) { @@ -95,7 +88,6 @@ export const Home = () => { return ( <> - {/* Hero Section */}
@@ -113,7 +105,6 @@ export const Home = () => {
- {/* Skills Section */}
-- 2.52.0 From b6894fac9b97d920b7c6b4b42de07d5199e01f0f Mon Sep 17 00:00:00 2001 From: seccodesmith Date: Thu, 22 May 2025 15:44:26 +0200 Subject: [PATCH 2/7] feat: refactor PageHeader component to use CSS modules and remove unused styles from Blog module --- src/assets/styles/Blog.module.scss | 58 ------------------------ src/assets/styles/PageHeader.module.scss | 58 ++++++++++++++++++++++++ src/components/PageHeader.tsx | 18 ++++---- src/pages/Blog.tsx | 6 +-- 4 files changed, 71 insertions(+), 69 deletions(-) create mode 100644 src/assets/styles/PageHeader.module.scss diff --git a/src/assets/styles/Blog.module.scss b/src/assets/styles/Blog.module.scss index 154a440..b593ec5 100644 --- a/src/assets/styles/Blog.module.scss +++ b/src/assets/styles/Blog.module.scss @@ -1,53 +1,5 @@ @use "./variables.scss" as *; - -/* Page Header */ -.pageHeader { - padding-top: 7rem; - padding-bottom: 3rem; - background-color: $secondary-dark; - position: relative; - overflow: hidden; -} - -.pageHeader::before { - content: ""; - position: absolute; - width: 100%; - height: 100%; - top: 0; - left: 0; - background-image: url('/api/placeholder/1200/400'); - opacity: 0.15; - filter: brightness(0.3); - z-index: 0; -} - -.pageTitle { - font-size: 3rem; - margin-bottom: 1rem; - position: relative; - display: inline-block; -} - -.pageTitle::after { - content: ""; - position: absolute; - width: 60px; - height: 3px; - background: $accent-fire; - bottom: -10px; - left: 50%; - transform: translateX(-50%); - box-shadow: $glow-effect; -} - -.pageSubtitle { - font-size: 1.2rem; - color: $text-ash; -} - - /* Meta items */ @mixin metaItem { .metaItem { @@ -269,13 +221,3 @@ border-color: $accent-fire; color: $text-light; } - -/* Binary background effect */ -.binaryLine { - position: absolute; - font-family: 'Fira Code', monospace; - color: $accent-fire; - font-size: 0.8rem; - opacity: 0.15; - pointer-events: none; -} \ No newline at end of file diff --git a/src/assets/styles/PageHeader.module.scss b/src/assets/styles/PageHeader.module.scss new file mode 100644 index 0000000..7323e42 --- /dev/null +++ b/src/assets/styles/PageHeader.module.scss @@ -0,0 +1,58 @@ +@use "./variables.scss" as *; + + +/* Binary background effect */ +.binaryLine { + position: absolute; + font-family: 'Fira Code', monospace; + color: $accent-fire; + font-size: 0.8rem; + opacity: 0.15; + pointer-events: none; +} + +/* Page Header */ +.pageHeader { + padding-top: 7rem; + padding-bottom: 3rem; + background-color: $secondary-dark; + position: relative; + overflow: hidden; +} + +.pageHeader::before { + content: ""; + position: absolute; + width: 100%; + height: 100%; + top: 0; + left: 0; + background-image: url('/api/placeholder/1200/400'); + opacity: 0.15; + filter: brightness(0.3); + z-index: 0; +} + +.pageTitle { + font-size: 3rem; + margin-bottom: 1rem; + position: relative; + display: inline-block; +} + +.pageTitle::after { + content: ""; + position: absolute; + width: 60px; + height: 3px; + background: $accent-fire; + bottom: -10px; + left: 50%; + transform: translateX(-50%); + box-shadow: $glow-effect; +} + +.pageSubtitle { + font-size: 1.2rem; + color: $text-ash; +} \ No newline at end of file diff --git a/src/components/PageHeader.tsx b/src/components/PageHeader.tsx index 474f396..a52935a 100644 --- a/src/components/PageHeader.tsx +++ b/src/components/PageHeader.tsx @@ -1,3 +1,5 @@ +import style from '@styles/PageHeader.module.scss' + interface PageHeaderProps { title: string; subtitle?: string; @@ -5,17 +7,17 @@ interface PageHeaderProps { export const PageHeader: React.FC = ({ title, subtitle }) => { return ( -
-
-

{title}

- {subtitle &&

{subtitle}

} +
+
+

{title}

+ {subtitle &&

{subtitle}

} {/* Binary background effect */}
-
01001100 01101111 01110010 01100101 01101101
-
01001001 01110000 01110011 01110101 01101101
-
01000100 01101111 01101100 01101111 01110010
-
01010011 01101001 01110100 00100000 01000001 01101101 01100101 01110100
+
01001100 01101111 01110010 01100101 01101101
+
01001001 01110000 01110011 01110101 01101101
+
01000100 01101111 01101100 01101111 01110010
+
01010011 01101001 01110100 00100000 01000001 01101101 01100101 01110100
diff --git a/src/pages/Blog.tsx b/src/pages/Blog.tsx index 8e2b5da..bbad834 100644 --- a/src/pages/Blog.tsx +++ b/src/pages/Blog.tsx @@ -75,9 +75,9 @@ export const Blog = () => { -
  • 1
  • -
  • 2
  • -
  • 3
  • +
  • 1
  • +
  • 2
  • +
  • 3
  • -- 2.52.0 From 142772a08aab263501790243000cad288a98ae40 Mon Sep 17 00:00:00 2001 From: seccodesmith Date: Thu, 22 May 2025 23:01:16 +0200 Subject: [PATCH 3/7] feat: add BlogPost styles using CSS modules, refactor BlogPost component for improved styling and structure --- src/assets/styles/BlogPost.module.scss | 815 +++++++++++++++++++++++++ src/assets/styles/Main.scss | 1 - src/assets/styles/_variables.scss | 1 + src/assets/styles/index.scss | 1 - src/components/ContactForm.tsx | 2 +- src/pages/BlogPost.tsx | 207 +++---- 6 files changed, 916 insertions(+), 111 deletions(-) create mode 100644 src/assets/styles/BlogPost.module.scss delete mode 100644 src/assets/styles/Main.scss diff --git a/src/assets/styles/BlogPost.module.scss b/src/assets/styles/BlogPost.module.scss new file mode 100644 index 0000000..b0be63d --- /dev/null +++ b/src/assets/styles/BlogPost.module.scss @@ -0,0 +1,815 @@ +@use './variables' as *; +@use './Blog.module.scss' as Blog; + +* { + margin: 0; + padding: 0; + box-sizing: border-box; + font-family: 'Cinzel', serif; +} + +.postContainer { + max-width: 1200px; + margin: 0 auto; + padding: 120px 20px 60px; + display: grid; + grid-template-columns: 1fr 300px; + gap: 40px; +} + +.postMain { + background-color: $card-bg; + border-radius: 8px; + overflow: hidden; + border: 1px solid $card-border; + box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2); +} + +.postHeader { + position: relative; +} + +.postFeaturedImage { + height: 400px; + background: url('/api/placeholder/900/400') center/cover no-repeat; + position: relative; +} + +.postFeaturedImage::after { + content: ""; + position: absolute; + bottom: 0; + left: 0; + width: 100%; + height: 100px; + background: linear-gradient(to top, $card-bg, transparent); +} + +.postCategory { + position: absolute; + top: 20px; + left: 20px; + background: rgba(139, 0, 0, 0.9); + color: $text-light; + padding: 5px 15px; + border-radius: 4px; + font-size: 0.8rem; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 1px; + backdrop-filter: blur(5px); + z-index: 1; +} + +.postInfo { + padding: 30px 40px; +} + +.postTitle { + font-size: 2.5rem; + margin-bottom: 20px; + line-height: 1.3; +} + +.postMeta { + display: flex; + align-items: center; + flex-wrap: wrap; + gap: 20px; + font-size: 0.9rem; + color: $text-ash; + margin-bottom: 30px; + padding-bottom: 20px; + border-bottom: 1px solid $card-border; +} + +.metaItem { + display: flex; + align-items: center; + gap: 6px; +} + +.metaIcon { + color: $accent-fire; +} + +.postContent { + padding: 0 40px 40px; + font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; + line-height: 1.8; + color: #e0e0e0; +} + +.postContent p { + margin-bottom: 20px; +} + +.postContent h2 { + font-size: 1.8rem; + margin: 40px 0 20px; + padding-bottom: 10px; + border-bottom: 1px solid $card-border; + font-family: 'Cinzel', serif; + color: $text-light; + position: relative; +} + +.postContent h2::before { + content: "//"; + margin-right: 10px; + color: $accent-fire; + font-weight: normal; + font-family: 'Fira Code', monospace; +} + +.postContent h3 { + font-size: 1.4rem; + margin: 30px 0 15px; + font-family: 'Cinzel', serif; + color: $text-light; +} + +.postContent ul, +.postContent ol { + margin: 0 0 20px 20px; + padding-left: 20px; +} + +.postContent li { + margin-bottom: 10px; +} + +.postContent a { + color: $accent-fire; + text-decoration: none; + border-bottom: 1px dotted $accent-fire; + transition: all 0.3s ease; +} + +.postContent a:hover { + border-bottom-style: solid; +} + +.postContent blockquote { + margin: 30px 0; + padding: 20px 30px; + border-left: 4px solid $accent-fire; + background-color: rgba(255, 100, 0, 0.05); + font-style: italic; + color: $text-ash; + position: relative; +} + +.postContent blockquote::before { + content: "\""; + position: absolute; + top: 10px; + left: 10px; + font-size: 3rem; + color: rgba(255, 100, 0, 0.2); + font-family: Georgia, serif; +} + +.postContent img { + max-width: 100%; + height: auto; + border-radius: 8px; + margin: 20px 0; + display: block; +} + +.postContent pre { + margin: 20px 0; + border-radius: 8px; + overflow-x: auto; + background-color: $code-bg; + font-family: 'Fira Code', monospace; + box-shadow: 0 4px 15px rgba(0, 0, 0, 0.3); + border: 1px solid $card-border; +} + +.postContent code { + font-family: 'Fira Code', monospace; + font-size: 0.9rem; + padding: 0 4px; + background-color: rgba(255, 255, 255, 0.05); + border-radius: 3px; + color: $text-light; +} + +.postContent pre code { + padding: 0; + background-color: transparent; + border-radius: 0; +} + +.postContent .highlight { + margin: 30px 0; + position: relative; +} + +.postContent .highlight::before { + content: attr(data-language); + position: absolute; + top: 0; + right: 0; + background-color: $accent-fire; + color: $text-light; + padding: 2px 10px; + font-size: 0.7rem; + border-radius: 0 8px 0 8px; + font-family: 'Fira Code', monospace; + text-transform: uppercase; + z-index: 1; +} + +.codeTitle { + background-color: rgba(255, 100, 0, 0.1); + padding: 10px 15px; + border-radius: 8px 8px 0 0; + font-family: 'Fira Code', monospace; + font-size: 0.9rem; + color: $accent-fire; + border-bottom: 1px solid rgba(255, 100, 0, 0.2); + display: flex; + align-items: center; + gap: 10px; +} + +.postTags { + display: flex; + flex-wrap: wrap; + gap: 10px; + margin-top: 40px; + padding-top: 20px; + border-top: 1px solid $card-border; +} + +.postTag { + background-color: rgba(255, 100, 0, 0.1); + color: $accent-fire; + padding: 5px 12px; + border-radius: 4px; + font-size: 0.8rem; + font-weight: 500; + transition: all 0.3s ease; +} + +.postTag:hover { + background-color: rgba(255, 100, 0, 0.2); + transform: translateY(-2px); +} + +.shareSection { + margin-top: 40px; + padding-top: 20px; + border-top: 1px solid $card-border; +} + +.shareTitle { + font-size: 1.2rem; + margin-bottom: 15px; +} + +.shareButtons { + display: flex; + gap: 15px; +} + +.shareButton { + width: 40px; + height: 40px; + border-radius: 50%; + background-color: rgba(255, 255, 255, 0.05); + display: flex; + align-items: center; + justify-content: center; + color: $text-ash; + transition: all 0.3s ease; +} + +.shareButton:hover { + background-color: $accent-fire; + color: $text-light; + transform: translateY(-3px); +} + +.authorSection { + margin-top: 40px; + padding: 30px; + background-color: rgba(255, 255, 255, 0.02); + border-radius: 8px; + display: flex; + gap: 20px; + align-items: center; +} + +.authorAvatar { + width: 80px; + height: 80px; + border-radius: 50%; + overflow: hidden; + border: 3px solid rgba(255, 100, 0, 0.3); + flex-shrink: 0; +} + +.authorAvatar img { + width: 100%; + height: 100%; + object-fit: cover; +} + +.authorInfo { + flex-grow: 1; +} + +.authorName { + font-size: 1.2rem; + margin-bottom: 10px; +} + +.authorBio { + color: $text-ash; + font-size: 0.9rem; + line-height: 1.6; +} + +.authorSocial { + display: flex; + gap: 15px; + margin-top: 15px; +} + +.authorSocial a { + color: $text-ash; + transition: all 0.3s ease; +} + +.authorSocial a:hover { + color: $accent-fire; +} + +.commentsSection { + margin-top: 60px; +} + +.commentsTitle { + font-size: 1.5rem; + margin-bottom: 30px; + position: relative; + display: inline-block; +} + +.commentsTitle::after { + content: ""; + position: absolute; + width: 40px; + height: 2px; + background: $accent-fire; + bottom: -10px; + left: 0; +} + +.comment { + padding: 20px 0; + border-bottom: 1px solid $card-border; + display: flex; + gap: 20px; +} + +.commentAvatar { + width: 50px; + height: 50px; + border-radius: 50%; + overflow: hidden; + flex-shrink: 0; +} + +.commentAvatar img { + width: 100%; + height: 100%; + object-fit: cover; +} + +.commentContent { + flex-grow: 1; +} + +.commentAuthor { + font-weight: 600; + margin-bottom: 5px; +} + +.commentDate { + font-size: 0.8rem; + color: $text-ash; + margin-bottom: 10px; +} + +.commentText { + color: #e0e0e0; + line-height: 1.6; + font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; +} + +.commentReply { + margin-top: 10px; + color: $accent-fire; + text-decoration: none; + font-size: 0.9rem; + display: inline-block; +} + +.commentForm { + margin-top: 40px; +} + +.formTitle { + font-size: 1.3rem; + margin-bottom: 20px; +} + +.formGroup { + margin-bottom: 20px; +} + +.formControl { + width: 100%; + background-color: rgba(255, 255, 255, 0.05); + border: 1px solid $card-border; + border-radius: 4px; + padding: 12px 15px; + color: $text-light; + font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; + resize: vertical; + transition: all 0.3s ease; +} + +.formControl:focus { + outline: none; + border-color: $accent-fire; + box-shadow: 0 0 0 2px rgba(255, 100, 0, 0.2); +} + +.formRow { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 20px; +} + +.submitButton { + background-color: $accent-fire; + color: $text-light; + border: none; + border-radius: 4px; + padding: 12px 25px; + font-weight: 600; + cursor: pointer; + transition: all 0.3s ease; + font-family: 'Cinzel', serif; + letter-spacing: 1px; +} + +.submitButton:hover { + background-color: $accent-dragon; + transform: translateY(-2px); +} + +.postSidebar { + position: sticky; + top: 120px; + align-self: start; +} + +.sidebarWidget { + background-color: $card-bg; + border-radius: 8px; + padding: 25px; + margin-bottom: 30px; + border: 1px solid $card-border; +} + +.widgetTitle { + font-size: 1.2rem; + margin-bottom: 20px; + padding-bottom: 10px; + border-bottom: 1px solid rgba(255, 255, 255, 0.1); + position: relative; +} + +.widgetTitle::after { + content: ""; + position: absolute; + width: 40px; + height: 2px; + background: $accent-fire; + bottom: -1px; + left: 0; +} + +.tocList { + list-style: none; +} + +.tocItem { + margin-bottom: 12px; + padding-left: 15px; + position: relative; +} + +.tocItem::before { + content: ""; + position: absolute; + left: 0; + top: 10px; + width: 5px; + height: 5px; + border-radius: 50%; + background-color: $accent-fire; +} + +.tocLink { + color: $text-ash; + text-decoration: none; + transition: all 0.3s ease; + font-size: 0.9rem; + line-height: 1.5; + display: block; +} + +.tocLink:hover { + color: $accent-fire; + transform: translateX(5px); +} + +.tocSubitem { + margin-top: 8px; + padding-left: 15px; +} + +.relatedPostsList { + list-style: none; +} + +.relatedPostItem { + margin-bottom: 15px; + padding-bottom: 15px; + border-bottom: 1px solid rgba(255, 255, 255, 0.05); +} + +.relatedPostItem:last-child { + border-bottom: none; + margin-bottom: 0; + padding-bottom: 0; +} + +.relatedPostLink { + display: flex; + align-items: center; + gap: 10px; + text-decoration: none; + color: $text-ash; + transition: color 0.3s ease; +} + +.relatedPostLink:hover { + color: $accent-fire; +} + +.relatedPostThumb { + width: 60px; + height: 60px; + border-radius: 4px; + background-size: cover; + background-position: center; + flex-shrink: 0; +} + +.relatedPostInfo { + flex-grow: 1; +} + +.relatedPostTitle { + font-size: 0.9rem; + line-height: 1.4; + margin-bottom: 5px; +} + +.relatedPostDate { + font-size: 0.8rem; + color: rgba(255, 255, 255, 0.5); +} + +.footer { + background-color: $primary-dark; + padding: 50px 5% 30px; + text-align: center; + position: relative; + border-top: 1px solid rgba(255, 100, 0, 0.2); + margin-top: 80px; +} + +.footerContent { + max-width: 800px; + margin: 0 auto; +} + +.socialLinks { + display: flex; + justify-content: center; + gap: 20px; + margin-bottom: 30px; +} + +.socialIcon { + font-size: 1.5rem; + color: $text-ash; + transition: all 0.3s ease; +} + +.socialIcon:hover { + color: $accent-fire; + transform: translateY(-5px); +} + +.copyright { + color: $text-ash; + font-size: 0.9rem; + margin-top: 20px; +} + +.binaryLine { + position: absolute; + font-family: 'Fira Code', monospace; + color: $accent-fire; + font-size: 0.8rem; + opacity: 0.15; + pointer-events: none; +} + +@media (max-width: 992px) { + .postContainer { + grid-template-columns: 1fr; + } + + .postSidebar { + position: static; + order: -1; + } + + .postFeaturedImage { + height: 300px; + } + + .postTitle { + font-size: 2rem; + } + + .postInfo, + .postContent { + padding: 20px; + } +} + +@media (max-width: 768px) { + .navbar { + flex-direction: column; + gap: 15px; + } + + .navLinks { + width: 100%; + justify-content: space-around; + font-size: 0.9rem; + } + + .postFeaturedImage { + height: 250px; + } + + .postTitle { + font-size: 1.8rem; + } + + .formRow { + grid-template-columns: 1fr; + gap: 15px; + } + + .authorSection { + flex-direction: column; + text-align: center; + align-items: center; + } + + .authorSocial { + justify-content: center; + } +} + +@media (max-width: 480px) { + .navLinks { + flex-wrap: wrap; + gap: 10px; + } + + .postFeaturedImage { + height: 200px; + } + + .postTitle { + font-size: 1.5rem; + } + + .postMeta { + gap: 10px; + } + + .comment { + flex-direction: column; + align-items: flex-start; + } + + .commentAvatar { + margin-bottom: 10px; + } +} + +/* Custom scrollbar */ +::-webkit-scrollbar { + width: 10px; + height: 10px; +} + +::-webkit-scrollbar-track { + background: $primary-dark; +} + +::-webkit-scrollbar-thumb { + background: $card-border; + border-radius: 5px; +} + +::-webkit-scrollbar-thumb:hover { + background: $accent-fire; +} + +/* Code line numbers */ +.hljsLnNumbers { + user-select: none; + text-align: center; + color: rgba(255, 255, 255, 0.3); + border-right: 1px solid rgba(255, 255, 255, 0.1); + vertical-align: top; + padding-right: 10px !important; +} + +.hljsLnCode { + padding-left: 10px !important; +} + +/* Tooltips */ +.tooltip { + position: relative; + display: inline-block; + color: $accent-fire; + text-decoration: underline; + text-decoration-style: dotted; + cursor: help; +} + +.tooltip .tooltipText { + visibility: hidden; + width: 200px; + background-color: $card-bg; + color: $text-light; + text-align: center; + border-radius: 6px; + padding: 10px; + position: absolute; + z-index: 1; + bottom: 125%; + left: 50%; + transform: translateX(-50%); + opacity: 0; + transition: opacity 0.3s; + border: 1px solid $card-border; + font-size: 0.8rem; + box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3); +} + +.tooltip .tooltipText::after { + content: ""; + position: absolute; + top: 100%; + left: 50%; + margin-left: -5px; + border-width: 5px; + border-style: solid; + border-color: $card-border transparent transparent transparent; +} + +.tooltip:hover .tooltipText { + visibility: visible; + opacity: 1; +} \ No newline at end of file diff --git a/src/assets/styles/Main.scss b/src/assets/styles/Main.scss deleted file mode 100644 index 8b13789..0000000 --- a/src/assets/styles/Main.scss +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/assets/styles/_variables.scss b/src/assets/styles/_variables.scss index f832b44..b147616 100644 --- a/src/assets/styles/_variables.scss +++ b/src/assets/styles/_variables.scss @@ -8,6 +8,7 @@ $text-light: #f5f5f5; $text-ash: #aaaaaa; $card-bg: #141414; $card-border: #2a2a2a; +$code-bg: #1e1e1e; // Effects $glow-effect: 0 0 10px $accent-fire, 0 0 20px rgba(255, 100, 0, 0.4); diff --git a/src/assets/styles/index.scss b/src/assets/styles/index.scss index 593092d..aa62ba6 100644 --- a/src/assets/styles/index.scss +++ b/src/assets/styles/index.scss @@ -103,7 +103,6 @@ body::before { background-color: $primary-dark; padding: 3rem 0; border-top: 1px solid rgba(255, 100, 0, 0.2); - margin-top: 5rem; } .social-links { diff --git a/src/components/ContactForm.tsx b/src/components/ContactForm.tsx index d26dc8d..792eb1c 100644 --- a/src/components/ContactForm.tsx +++ b/src/components/ContactForm.tsx @@ -12,7 +12,7 @@ export const ContactForm = () => { const [isSubmitting, setIsSubmitting] = useState(false); const [showSuccess, setShowSuccess] = useState(false); - const [showError, setShowError] = useState(false); + const [showError, ] = useState(false); const handleChange = (e: React.ChangeEvent) => { const { name, value } = e.target; diff --git a/src/pages/BlogPost.tsx b/src/pages/BlogPost.tsx index 1d80e4d..4209cce 100644 --- a/src/pages/BlogPost.tsx +++ b/src/pages/BlogPost.tsx @@ -4,162 +4,153 @@ import { blogPostsData } from '../data/blogPostsData'; import { NotFound } from './NotFound'; +import style from '@styles/BlogPost.module.scss'; + + export const BlogPost = () => { const { slug } = useParams<{ slug: string }>(); const [post, setPost] = useState(blogPostsData.find(post => post.slug === slug)); const [relatedPosts, setRelatedPosts] = useState(blogPostsData.slice(0, 3)); - + useEffect(() => { // Find the post by slug const currentPost = blogPostsData.find(post => post.slug === slug); - + if (currentPost) { // Set the post setPost(currentPost); - + // Find related posts (same category or shared tags) const related = blogPostsData .filter(p => p.id !== currentPost.id) - .filter(p => - p.category === currentPost.category || + .filter(p => + p.category === currentPost.category || p.tags.some(tag => currentPost.tags.includes(tag)) ) .slice(0, 3); - + setRelatedPosts(related); - + // Scroll to top when new post is loaded window.scrollTo(0, 0); } }, [slug]); - + if (!post) { return ; } - + return ( -
    -
    +
    +
    {/* Post Header */} -
    -
    -
    {post.category}
    +
    +
    +
    {post.category}
    - + {/* Post Info */} -
    -

    {post.title}

    -
    -
    - +
    +

    {post.title}

    +
    +
    + {post.date}
    -
    - +
    + {post.author}
    -
    - +
    + {post.readTime}
    -
    - +
    + {post.commentCount} Comments
    - - {/* Post Content */} -
    - - {/* Post Tags */} -
    - {post.tags.map((tag, index) => ( - {tag} - ))} -
    - - {/* Share Section */} -
    -

    Share this arcane knowledge

    -
    - - - - - + +
    +
    + {/* Post Tags */} +
    + {post.tags.map((tag, index) => ( + {tag} + ))}
    -
    - - {/* Author Info */} -
    -
    - SecCodeSmith -
    -
    -

    SecCodeSmith

    -

    Digital blacksmith forging embedded systems and IoT solutions in the fires of innovation. Specializes in low-level firmware, communication protocols, and hardware-software integration for resource-constrained devices.

    -
    - - - - + + {/* Share Section */} +
    +

    Share this arcane knowledge

    +
    + + + + +
    -
    - - {/* Comments Section */} -
    -

    Discussions ({post.commentCount})

    - - {/* Sample Comments */} -
    -
    - User + + {/* Author Info */} +
    +
    + SecCodeSmith
    -
    -

    Michael Chen

    -
    May 5, 2025 at 15:42
    -

    Excellent article! I've been struggling with I2C bus lockups in my weather station project. The recovery function you provided solved the issue. Have you considered adding a timeout mechanism to the SPI transfers for error detection?

    - Reply -
    -
    - -
    -
    - User -
    -
    -

    SecCodeSmith

    -
    May 5, 2025 at 16:10
    -

    @Michael - Good point about the timeout! For SPI transfers, you can implement a timeout using either the HAL Timeout parameter or a hardware timer. For maximum reliability, I prefer using the hardware timer approach as it continues to function even if the main code gets stuck. I'll cover this in a future article on error handling.

    - Reply -
    -
    - - {/* Comment Form */} -
    -

    Leave your mark

    -
    -
    -
    - -
    -
    - -
    +
    +

    SecCodeSmith

    +

    Digital blacksmith forging embedded systems and IoT solutions in the fires of innovation. Specializes in low-level firmware, communication protocols, and hardware-software integration for resource-constrained devices.

    +
    + + + +
    -
    - +
    +
    + + {/* Comments Section */} +
    +

    Discussions ({post.commentCount})

    + + {/* Sample Comments */} +
    +
    + User
    - - +
    +

    Michael Chen

    +
    May 5, 2025 at 15:42
    +

    Excellent article! I've been struggling with I2C bus lockups in my weather station project. The recovery function you provided solved the issue. Have you considered adding a timeout mechanism to the SPI transfers for error detection?

    + Reply +
    +
    + + {/* Comment Form */} +
    +

    Leave your mark

    +
    +
    +
    + +
    +
    + +
    +
    +
    + +
    + +
    +
    - +
  • - + {/* Related Posts */}

    Related Scrolls

    -- 2.52.0 From 4cc133fffa4f938338b2b51ce05cdf8b79171c04 Mon Sep 17 00:00:00 2001 From: seccodesmith Date: Fri, 23 May 2025 09:49:37 +0200 Subject: [PATCH 4/7] feat: enhance BlogPost component with dedent function, update styles, and improve content formatting --- index.html | 2 + src/assets/styles/BlogPost.module.scss | 3 +- src/data/blogPostsData.ts | 18 +++--- src/pages/BlogPost.tsx | 77 +++++++++++++++----------- 4 files changed, 56 insertions(+), 44 deletions(-) diff --git a/index.html b/index.html index 2750a29..73df5ec 100644 --- a/index.html +++ b/index.html @@ -12,6 +12,8 @@ + + SecCodeSmith - Code Forged in Digital Fire diff --git a/src/assets/styles/BlogPost.module.scss b/src/assets/styles/BlogPost.module.scss index b0be63d..bc73265 100644 --- a/src/assets/styles/BlogPost.module.scss +++ b/src/assets/styles/BlogPost.module.scss @@ -142,7 +142,6 @@ .postContent a { color: $accent-fire; text-decoration: none; - border-bottom: 1px dotted $accent-fire; transition: all 0.3s ease; } @@ -439,7 +438,7 @@ border: 1px solid $card-border; border-radius: 4px; padding: 12px 15px; - color: $text-light; + color: $text-light !important; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; resize: vertical; transition: all 0.3s ease; diff --git a/src/data/blogPostsData.ts b/src/data/blogPostsData.ts index 358f161..98e3683 100644 --- a/src/data/blogPostsData.ts +++ b/src/data/blogPostsData.ts @@ -15,19 +15,17 @@ export const blogPostsData: BlogPostProps[] = [ featured: true, tags: ["STM32", "Embedded", "IoT", "Communication Protocols"], content: ` -

    In the digital forge where hardware meets software, the STM32 microcontroller family stands as a powerful conduit for summoning digital entities into our physical realm. Today, we shall delve into advanced techniques for establishing robust communication between these entities through the mystical arts of embedded systems programming.

    + In the digital forge where hardware meets software, the STM32 microcontroller family stands as a powerful conduit for summoning digital entities into our physical realm. Today, we shall delve into advanced techniques for establishing robust communication between these entities through the mystical arts of embedded systems programming. -

    The realm of IoT demands reliable, efficient communication protocols that can withstand the chaotic nature of real-world environments. Through careful implementation and optimization of the STM32's communication peripherals, we can craft resilient connections that bridge the gap between digital and physical domains.

    + The realm of IoT demands reliable, efficient communication protocols that can withstand the chaotic nature of real-world environments. Through careful implementation and optimization of the STM32's communication peripherals, we can craft resilient connections that bridge the gap between digital and physical domains. -

    The Three Elemental Protocols: I2C, SPI, and UART

    + ## The Three Elemental Protocols: I2C, SPI, and UART -

    Before we forge ahead into advanced territory, let us briefly recall the elemental forces at our disposal. Each protocol serves as a channel through which our digital entities communicate, each with its unique strengths and limitations:

    - -
      -
    • I2C (Inter-Integrated Circuit) - A two-wire interface requiring minimal physical connections, ideal for communication with multiple peripheral devices over short distances.
    • -
    • SPI (Serial Peripheral Interface) - A faster, full-duplex protocol with dedicated select lines for each peripheral, allowing simultaneous transmission and reception of data.
    • -
    • UART (Universal Asynchronous Receiver-Transmitter) - A simple two-wire asynchronous interface, perfect for direct device-to-device communication without a shared clock.
    • -
    + Before we forge ahead into advanced territory, let us briefly recall the elemental forces at our disposal. Each protocol serves as a channel through which our digital entities communicate, each with its unique strengths and limitations: + * **I2C (Inter-Integrated Circuit)** - A two-wire interface requiring minimal physical connections, ideal for communication with multiple peripheral devices over short distances. + * **SPI (Serial Peripheral Interface)** - A faster, full-duplex protocol with dedicated select lines for each peripheral, allowing simultaneous transmission and reception of data. + * **UART (Universal Asynchronous Receiver-Transmitter)** - A simple two-wire asynchronous interface, perfect for direct device-to-device communication without a shared clock. + ` }, { diff --git a/src/pages/BlogPost.tsx b/src/pages/BlogPost.tsx index 4209cce..f5cc3cd 100644 --- a/src/pages/BlogPost.tsx +++ b/src/pages/BlogPost.tsx @@ -6,6 +6,22 @@ import { NotFound } from './NotFound'; import style from '@styles/BlogPost.module.scss'; +function dedent(str: string): string { + const lines = str.split('\n'); + while (lines.length > 0 && lines[0].trim() === '') { + lines.shift(); + } + while (lines.length > 0 && lines[lines.length - 1].trim() === '') { + lines.pop(); + } + + const indents = lines + .filter(line => line.trim().length > 0) + .map(line => line.match(/^ */)![0].length); + const minIndent = indents.length ? Math.min(...indents) : 0; + + return lines.map(line => line.slice(minIndent)).join('\n'); +} export const BlogPost = () => { const { slug } = useParams<{ slug: string }>(); @@ -13,14 +29,12 @@ export const BlogPost = () => { const [relatedPosts, setRelatedPosts] = useState(blogPostsData.slice(0, 3)); useEffect(() => { - // Find the post by slug const currentPost = blogPostsData.find(post => post.slug === slug); - + if (currentPost) { - // Set the post setPost(currentPost); - // Find related posts (same category or shared tags) + const related = blogPostsData .filter(p => p.id !== currentPost.id) .filter(p => @@ -31,7 +45,6 @@ export const BlogPost = () => { setRelatedPosts(related); - // Scroll to top when new post is loaded window.scrollTo(0, 0); } }, [slug]); @@ -134,14 +147,14 @@ export const BlogPost = () => {
    - +
    - +
    - +
    @@ -151,25 +164,25 @@ export const BlogPost = () => { -