Fixing-blog #6

Merged
seccodesmith merged 8 commits from Fixing-blog into main 2025-05-23 10:01:38 +00:00
18 changed files with 1497 additions and 1130 deletions
+2
View File
@@ -12,6 +12,8 @@
<link href="https://fonts.googleapis.com/css2?family=Cinzel:wght@400;700&family=Fira+Code:wght@400;600&display=swap" rel="stylesheet">
<!-- Bootstrap CSS -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/css/bootstrap.min.css" rel="stylesheet">
<!-- highlight CSS -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/highlight.min.js" integrity="sha512-EBLzUL8XLl+va/zAsmXwS7Z2B1F9HUHkZwyS/VKwh3S7T/U0nF4BaU29EP/ZSf6zgiIxYAnKLu6bJ8dqpmX5uw==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<title>SecCodeSmith - Code Forged in Digital Fire</title>
<link rel="favicon" href="public/favicon.ico" type="image/x-icon">
+223
View File
@@ -0,0 +1,223 @@
@use "./variables.scss" as *;
/* Meta items */
@mixin metaItem {
.metaItem {
display: inline-flex;
align-items: center;
gap: 6px;
margin-right: 1rem;
color: $text-ash;
font-size: 0.9rem;
}
}
@mixin metaIcon {
.metaIcon {
font-size: 1.2rem;
color: $text-ash;
transition: all 0.3s ease;
}
.metaIcon:hover {
color: $accent-fire;
}
}
/* Read more link */
@mixin readMore {
.readMore {
display: inline-block;
color: $accent-fire;
text-decoration: none;
font-weight: 600;
transition: all 0.3s ease;
position: relative;
}
.readMore::after {
content: "";
margin-left: 5px;
transition: transform 0.3s ease;
}
.readMore:hover::after {
transform: translateX(5px);
}
}
/* Tags */
@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);
}
}
@include metaItem;
@include metaIcon;
@include readMore;
@include postTag;
/* Sidebar */
.sidebarWidget {
background-color: $card-bg;
border-radius: 8px;
padding: 1.5rem;
margin-bottom: 1.5rem;
border: 1px solid $card-border;
}
.widgetTitle {
font-size: 1.2rem;
margin-bottom: 1.25rem;
padding-bottom: 0.625rem;
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;
}
.searchForm .formControl {
background-color: rgba(255, 255, 255, 0.05);
border: 1px solid $card-border;
color: $text-light;
}
.searchForm .formControl:focus {
box-shadow: 0 0 0 0.25rem rgba(255, 100, 0, 0.25);
border-color: $accent-fire;
}
.searchForm .btn {
background-color: $accent-fire;
border-color: $accent-fire;
}
.searchForm .btn:hover {
background-color: $accent-dragon;
border-color: $accent-dragon;
}
/* Lists in sidebar */
.listGroupItem {
background-color: transparent;
border-color: rgba(255, 255, 255, 0.05);
color: $text-ash;
transition: all 0.3s ease;
}
.listGroupItem:hover {
background-color: rgba(255, 100, 0, 0.1);
}
.listGroupItem a {
color: $text-ash;
text-decoration: none;
display: flex;
justify-content: space-between;
align-items: center;
}
.listGroup-item a:hover {
color: $accent-fire;
}
.categoryCount {
background-color: rgba(255, 100, 0, 0.1);
color: $accent-fire;
padding: 2px 8px;
border-radius: 10px;
font-size: 0.8rem;
}
/* Recent posts in sidebar */
.recentPostItem {
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 1rem;
padding-bottom: 1rem;
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
}
.recentPostItem:last-child {
margin-bottom: 0;
padding-bottom: 0;
border-bottom: none;
}
.recentPostThumb {
width: 60px;
height: 60px;
border-radius: 4px;
object-fit: cover;
}
.recentPostInfo {
flex-grow: 1;
}
.recentPostTitle {
font-size: 0.9rem;
line-height: 1.4;
margin-bottom: 5px;
}
.recentPostTitle a {
color: $text-ash;
text-decoration: none;
transition: color 0.3s;
}
.recentPostTitle a:hover {
color: $accent-fire;
}
.recentPostDate {
font-size: 0.8rem;
color: rgba(255, 255, 255, 0.5);
}
/* Pagination */
.pageItem .pageLink {
background-color: $card-bg;
border-color: $card-border;
color: $text-ash;
}
.pageItem .pageLink:hover {
background-color: rgba(255, 100, 0, 0.1);
color: $accent-fire;
border-color: $accent-fire;
}
.pageItem.active .pageLink {
background-color: $accent-fire;
border-color: $accent-fire;
color: $text-light;
}
-344
View File
@@ -1,344 +0,0 @@
@use "./variables.scss" as *;
/* Page Header */
.page-header {
padding-top: 7rem;
padding-bottom: 3rem;
background-color: $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: $accent-fire;
bottom: -10px;
left: 50%;
transform: translateX(-50%);
box-shadow: $glow-effect;
}
.page-subtitle {
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;
}
.meta-icon {
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;
}
.read-more: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;
}
.post-tag:hover {
background-color: rgba(255, 100, 0, 0.2);
transform: translateY(-2px);
}
/* Sidebar */
.sidebar-widget {
background-color: $card-bg;
border-radius: 8px;
padding: 1.5rem;
margin-bottom: 1.5rem;
border: 1px solid $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: $accent-fire;
bottom: -1px;
left: 0;
}
.search-form .form-control {
background-color: rgba(255, 255, 255, 0.05);
border: 1px solid $card-border;
color: $text-light;
}
.search-form .form-control:focus {
box-shadow: 0 0 0 0.25rem rgba(255, 100, 0, 0.25);
border-color: $accent-fire;
}
.search-form .btn {
background-color: $accent-fire;
border-color: $accent-fire;
}
.search-form .btn:hover {
background-color: $accent-dragon;
border-color: $accent-dragon;
}
/* Lists in sidebar */
.list-group-item {
background-color: transparent;
border-color: rgba(255, 255, 255, 0.05);
color: $text-ash;
transition: all 0.3s ease;
}
.list-group-item:hover {
background-color: rgba(255, 100, 0, 0.1);
}
.list-group-item a {
color: $text-ash;
text-decoration: none;
display: flex;
justify-content: space-between;
align-items: center;
}
.list-group-item a:hover {
color: $accent-fire;
}
.category-count {
background-color: rgba(255, 100, 0, 0.1);
color: $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: $text-ash;
text-decoration: none;
transition: color 0.3s;
}
.recent-post-title a:hover {
color: $accent-fire;
}
.recent-post-date {
font-size: 0.8rem;
color: rgba(255, 255, 255, 0.5);
}
/* Pagination */
.page-item .page-link {
background-color: $card-bg;
border-color: $card-border;
color: $text-ash;
}
.page-item .page-link:hover {
background-color: rgba(255, 100, 0, 0.1);
color: $accent-fire;
border-color: $accent-fire;
}
.page-item.active .page-link {
background-color: $accent-fire;
border-color: $accent-fire;
color: $text-light;
}
/* Binary background effect */
.binary-line {
position: absolute;
font-family: 'Fira Code', monospace;
color: $accent-fire;
font-size: 0.8rem;
opacity: 0.15;
pointer-events: none;
}
+95
View File
@@ -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;
}
+814
View File
@@ -0,0 +1,814 @@
@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;
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 !important;
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;
}
+1 -1
View File
@@ -73,7 +73,7 @@
color: $accent-fire;
font-size: 1rem;
animation: fall linear infinite;
opacity: 0.7;
opacity: 0.8;
}
@keyframes fall {
-1
View File
@@ -1 +0,0 @@
+58
View File
@@ -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;
}
+1
View File
@@ -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);
+15 -14
View File
@@ -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<BlogCardProps> = ({ post }) => {
return (
<div className={`card ${post.featured ? 'featured-card' : ''}`}>
<div className={`card ${styles.card} ${post.featured ? styles.featuredCard : ''}`}>
<div className="position-relative">
<img src={post.image} className={`card-img-top ${post.featured ? 'featured-img' : ''}`} alt={post.title} />
<img src={post.image} className={`card-img-top ${post.featured ? styles.featuredImg : ''}`} alt={post.title} />
<div className="category-badge">{post.category}</div>
{post.featured && (
<div className="featured-badge">
<div className={`featured-badge ${styles.featuredBadge}`}>
<i className="fas fa-star"></i>
<span>Featured</span>
</div>
)}
</div>
<div className="card-body">
<h2 className="card-title">
<div className={`card-body ${styles.cardBody}`}>
<h2 className={`card-title ${styles.cardTitle}`}>
<Link to={`/blog/${post.slug}`}>{post.title}</Link>
</h2>
<div className="d-flex meta-items mb-3">
<div className="meta-item">
<div className={`d-flex meta-items mb-3`}>
<div className={`meta-item ${styles.metaItem}`}>
<i className="fas fa-calendar-alt meta-icon"></i>
<span>{post.date}</span>
</div>
<div className="meta-item">
<i className="fas fa-user meta-icon"></i>
<div className={`meta-item ${styles.metaItem}`}>
<i className={`fas fa-user meta-icon ${styles.metaIcon}`}></i>
<span>{post.author}</span>
</div>
<div className="meta-item">
<i className="fas fa-comments meta-icon"></i>
<div className={`meta-item ${styles.metaItem}`}>
<i className={`fas fa-comments meta-icon ${styles.metaIcon}`}></i>
<span>{post.commentCount} Comments</span>
</div>
</div>
<p className="card-text">
<p className={`card-text ${styles.cardText}`}>
{post.excerpt}
</p>
<Link to={`/blog/${post.slug}`} className="read-more">Continue Reading</Link>
<Link to={`/blog/${post.slug}`} className={`read-more ${styles.readMore}`}>Continue Reading</Link>
<div className="mt-3">
{post.tags.map((tag, index) => (
<span key={index} className="post-tag">{tag}</span>
<span key={index} className={`post-tag ${styles.postTag}`}>{tag}</span>
))}
</div>
</div>
+1 -1
View File
@@ -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<HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement>) => {
const { name, value } = e.target;
+10 -8
View File
@@ -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<PageHeaderProps> = ({ title, subtitle }) => {
return (
<section className="page-header text-center">
<div className="container position-relative">
<h1 className="page-title">{title}</h1>
{subtitle && <p className="page-subtitle mt-4">{subtitle}</p>}
<section className={`text-center ${style.pageHeader}`}>
<div className={`container position-relative`}>
<h1 className={style.pageTitle}>{title}</h1>
{subtitle && <p className={`mt-4 ${style.pageSubtitle}`}>{subtitle}</p>}
{/* Binary background effect */}
<div className="binary-bg">
<div className="binary-line" style={{ top: '20%', left: '10%' }}>01001100 01101111 01110010 01100101 01101101</div>
<div className="binary-line" style={{ bottom: '30%', right: '15%' }}>01001001 01110000 01110011 01110101 01101101</div>
<div className="binary-line" style={{ top: '50%', left: '80%' }}>01000100 01101111 01101100 01101111 01110010</div>
<div className="binary-line" style={{ bottom: '10%', left: '40%' }}>01010011 01101001 01110100 00100000 01000001 01101101 01100101 01110100</div>
<div className={style.binaryLine} style={{ top: '20%', left: '10%' }}>01001100 01101111 01110010 01100101 01101101</div>
<div className={style.binaryLine} style={{ bottom: '30%', right: '15%' }}>01001001 01110000 01110011 01110101 01101101</div>
<div className={style.binaryLine} style={{ top: '50%', left: '80%' }}>01000100 01101111 01101100 01101111 01110010</div>
<div className={style.binaryLine} style={{ bottom: '10%', left: '40%' }}>01010011 01101001 01110100 00100000 01000001 01101101 01100101 01110100</div>
</div>
</div>
</section>
+8 -10
View File
@@ -15,19 +15,17 @@ export const blogPostsData: BlogPostProps[] = [
featured: true,
tags: ["STM32", "Embedded", "IoT", "Communication Protocols"],
content: `
<p>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.</p>
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.
<p>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.</p>
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.
<h2>The Three Elemental Protocols: I2C, SPI, and UART</h2>
# The Three Elemental Protocols: I2C, SPI, and UART
<p>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:</p>
<ul>
<li><strong>I2C (Inter-Integrated Circuit)</strong> - A two-wire interface requiring minimal physical connections, ideal for communication with multiple peripheral devices over short distances.</li>
<li><strong>SPI (Serial Peripheral Interface)</strong> - A faster, full-duplex protocol with dedicated select lines for each peripheral, allowing simultaneous transmission and reception of data.</li>
<li><strong>UART (Universal Asynchronous Receiver-Transmitter)</strong> - A simple two-wire asynchronous interface, perfect for direct device-to-device communication without a shared clock.</li>
</ul>
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.
`
},
{
-210
View File
@@ -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;
}
}
-342
View File
@@ -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;
}
+37 -48
View File
@@ -3,13 +3,12 @@ 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('');
const [currentPage, setCurrentPage] = useState(1);
// Filter blog posts based on search query
const filteredPosts = searchQuery
? blogPostsData.filter(post =>
post.title.toLowerCase().includes(searchQuery.toLowerCase()) ||
@@ -17,22 +16,19 @@ export const Blog = () => {
post.tags.some(tag => tag.toLowerCase().includes(searchQuery.toLowerCase()))
)
: blogPostsData;
// Get the featured post
const filteredPostPageCount = Math.ceil(filteredPosts.length / 6);
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<HTMLInputElement>) => {
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 +41,7 @@ export const Blog = () => {
<div className="container mt-5">
<div className="row">
{/* Main Content */}
<div className="col-lg-8">
{/* Featured Post */}
{featuredPost && (!searchQuery ||
featuredPost.title.toLowerCase().includes(searchQuery.toLowerCase()) ||
featuredPost.excerpt.toLowerCase().includes(searchQuery.toLowerCase()) ||
@@ -56,7 +50,6 @@ export const Blog = () => {
<BlogCard post={featuredPost} />
)}
{/* Regular Posts */}
<div className="row">
{regularPosts.map(post => (
<div className="col-md-6" key={post.id}>
@@ -65,7 +58,6 @@ export const Blog = () => {
))}
</div>
{/* No Posts Message */}
{filteredPosts.length === 0 && (
<div className="text-center mt-5">
<h3>No posts found matching your search.</h3>
@@ -78,34 +70,34 @@ export const Blog = () => {
</div>
)}
{/* Pagination */}
{filteredPosts.length > 0 && (
{filteredPostPageCount > 1 && (
<nav className="mt-5" aria-label="Blog pagination">
<ul className="pagination justify-content-center">
<li className="page-item">
<a className="page-link" href="#" aria-label="Previous">
{(currentPage > 1) && ( <li className={`page-item ${style.pageItem}`}>
<a className={`page-link ${style.pageLink}`} href="#" aria-label="Previous">
<i className="fas fa-angle-left"></i>
</a>
</li>
<li className="page-item active"><a className="page-link" href="#">1</a></li>
<li className="page-item"><a className="page-link" href="#">2</a></li>
<li className="page-item"><a className="page-link" href="#">3</a></li>
<li className="page-item">
<a className="page-link" href="#" aria-label="Next">
</li>)}
{Array.from({ length: filteredPostPageCount }, (_, index) => (
<li className={`page-item ${style.pageItem}`} key={index}>
<a className={`page-link ${style.pageLink}`} href="#">{index + 1}</a>
</li>
))}
{(currentPage < filteredPostPageCount) && ( <li className={`page-item ${style.pageItem}`}>
<a className={`page-link ${style.pageLink}`} href="#" aria-label="Next">
<i className="fas fa-angle-right"></i>
</a>
</li>
</li>)}
</ul>
</nav>
)}
</div>
{/* Sidebar */}
<div className="col-lg-4 sidebar">
{/* Search Widget */}
<div className="sidebar-widget">
<h3 className="widget-title">Search</h3>
<form className="search-form">
<div className={`col-lg-4 sidebar`}>
<div className={`sidebar-widget ${style.sidebarWidget}`}>
<h3 className={`widget-title ${style.widgetTitle}`}>Search</h3>
<form className={`search-form ${style.searchForm}`}>
<div className="input-group">
<input
type="text"
@@ -114,24 +106,23 @@ export const Blog = () => {
value={searchQuery}
onChange={handleSearchChange}
/>
<button className="btn btn-primary" type="submit">
<button className={`btn btn-primary ${style.btn}`} type="submit">
<i className="fas fa-search"></i>
</button>
</div>
</form>
</div>
{/* Categories Widget */}
<div className="sidebar-widget">
<h3 className="widget-title">Categories</h3>
<div className={`sidebar-widget ${style.sidebarWidget}`}>
<h3 className={`widget-title ${style.widgetTitle}`}>Categories</h3>
<div className="list-group">
{categories.map((category, index) => {
const count = blogPostsData.filter(post => post.category === category).length;
return (
<div className="list-group-item" key={index}>
<div className={`list-group-item ${style.listGroupItem}`} key={index}>
<a href="#" onClick={() => setSearchQuery(category)}>
<span>{category}</span>
<span className="category-count">{count}</span>
<span className={`category-count ${style.categoryCount}`}>{count}</span>
</a>
</div>
);
@@ -139,32 +130,30 @@ export const Blog = () => {
</div>
</div>
{/* Recent Posts Widget */}
<div className="sidebar-widget">
<h3 className="widget-title">Recent Posts</h3>
<div className={`sidebar-widget ${style.sidebarWidget}`}>
<h3 className={`widget-title ${style.widgetTitle}`}>Recent Posts</h3>
<div className="recent-posts">
{blogPostsData.slice(0, 3).map(post => (
<div className="recent-post-item" key={post.id}>
<img src={post.image} className="recent-post-thumb" alt={post.title} />
<div className="recent-post-info">
<h4 className="recent-post-title">
<div className={`recent-post-item ${style.recentPostItem}`} key={post.id}>
<img src={post.image} className={`recent-post-thumb ${style.recentPostThumb}`} alt={post.title} />
<div className={style.recentPostInfo}>
<h4 className={style.recentPostTitle}>
<a href={`/blog/${post.slug}`}>{post.title}</a>
</h4>
<span className="recent-post-date">{post.date}</span>
<span className={style.recentPostDate}>{post.date}</span>
</div>
</div>
))}
</div>
</div>
{/* Tags Widget */}
<div className="sidebar-widget">
<h3 className="widget-title">Popular Tags</h3>
<div className={`sidebar-widget ${style.sidebarWidget}`}>
<h3 className={`widget-title ${style.widgetTitle}`}>Popular Tags</h3>
<div>
{uniqueTags.map((tag, index) => (
<span
key={index}
className="post-tag"
className={style.postTag}
onClick={() => setSearchQuery(tag)}
style={{ cursor: 'pointer' }}
>
+232 -142
View File
@@ -4,198 +4,288 @@ import { blogPostsData } from '../data/blogPostsData';
import { NotFound } from './NotFound';
import style from '@styles/BlogPost.module.scss';
interface TableOfContentsItem {
title: string;
id: number;
children?: TableOfContentsItem[];
}
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');
}
const TableOfContents = ({ toc }: { toc: TableOfContentsItem[] }) => {
return (
<ul className={style.tocList}>
{toc.map(item => (
<li key={item.id} className={style.tocItem}>
<a href={`#${item.id}`} className={style.tocLink}>{item.title}</a>
{item.children && item.children.length > 0 && (
<TableOfContents toc={item.children} />
)}
</li>
))}
</ul>
);
}
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));
const [toc, setToc] = useState<TableOfContentsItem[]>([]);
useEffect(() => {
// Find the post by slug
const currentPost = blogPostsData.find(post => post.slug === slug);
let id = 0;
const toc: TableOfContentsItem[] = [];
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
let newContent = '';
if (currentPost.content) {
const content = dedent(currentPost.content);
const lines = content.split('\n');
for (let i = 0; i < lines.length; i++) {
if (/^#\s/.test(lines[i])) {
newContent += `<h2 id="${id}">${lines[i].replace(/^#+\s*/, '')}</h2> \n`;
toc.push({ title: lines[i].replace(/^#+\s*/, ''), id });
id++;
}
else if (/^##\s/.test(lines[i])) {
newContent += `<h3 id="${id}">${lines[i].replace(/^#+\s*/, '')}</h3> \n`;
if (toc.length > 0) {
const parent = toc[toc.length - 1];
if (!parent.children) {
parent.children = [];
}
parent.children.push({ title: lines[i].replace(/^#+\s*/, ''), id });
}
id++;
}
else if (/^###\s/.test(lines[i])) {
newContent += `<h4 id="${id}">${lines[i].replace(/^#+\s*/, '')}</h4> \n`;
if (toc.length > 0) {
const h2Parent = toc[toc.length - 1];
if (h2Parent.children && h2Parent.children.length > 0) {
const h3Parent = h2Parent.children[h2Parent.children.length - 1];
if (!h3Parent.children) {
h3Parent.children = [];
}
h3Parent.children.push({ title: lines[i].replace(/^#+\s*/, ''), id });
}
}
id++;
}
else if (/^\*\s/.test(lines[i])) {
newContent += '<ul>\n';
for (; i < lines.length; i++) {
if (/^\*\s/.test(lines[i])) {
newContent += `<li>${lines[i].replace(/^\*\s/, '')}</li>\n`;
}
else {
break;
}
}
newContent += '</ul>\n';
}
else if (/^\-\s/.test(lines[i])) {
newContent += '<ol>\n';
for (; i < lines.length; i++) {
if (/^\-\s/.test(lines[i])) {
newContent += `<li>${lines[i].replace(/^\-\s/, '')}</li>\n`;
}
else {
break;
}
}
newContent += '</ol>\n';
}
else {
newContent += `<p>${lines[i]}</p>\n`;
}
}
}
newContent = newContent.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>');
newContent = newContent.replace(/__(.*?)__/g, '<strong>$1</strong>');
newContent = newContent.replace(/\*(.*?)\*/g, '<em>$1</em>');
newContent = newContent.replace(/_(.*?)_/g, '<em>$1</em>');
newContent = newContent.replace(/`(.*?)`/g, '<code>$1</code>');
newContent = newContent.replace(/!\[(.*?)\]\((.*?)\)/g, '<img alt="$1" src="$2" />');
newContent = newContent.replace(/\[(.*?)\]\((.*?)\)/g, '<a href="$2">$1</a>');
const contentElement = document.getElementById('postContent');
contentElement!.innerHTML = newContent;
setToc(toc);
window.scrollTo(0, 0);
}
}, [slug]);
if (!post) {
return <NotFound />;
}
return (
<div className="post-container">
<main className="post-main">
<div className={style.postContainer}>
<main className={style.postMain}>
<article>
{/* Post Header */}
<div className="post-header">
<div className="post-featured-image" style={{ backgroundImage: `url(${post.image})` }}></div>
<div className="post-category">{post.category}</div>
<div className={style.postHeader}>
<div className={style.postFeaturedImage} style={{ backgroundImage: `url(${post.image})` }}></div>
<div className={style.postCategory}>{post.category}</div>
</div>
{/* Post Info */}
<div className="post-info">
<h1 className="post-title">{post.title}</h1>
<div className="post-meta">
<div className="meta-item">
<i className="fas fa-calendar-alt meta-icon"></i>
<div className={style.postInfo}>
<h1 className={style.postTitle}>{post.title}</h1>
<div className={style.postMeta}>
<div className={style.metaItem}>
<i className={`fas fa-calendar-alt ${style.metaIcon}`}></i>
<span>{post.date}</span>
</div>
<div className="meta-item">
<i className="fas fa-user meta-icon"></i>
<div className={style.metaItem}>
<i className={`fas fa-user ${style.metaIcon}`}></i>
<span>{post.author}</span>
</div>
<div className="meta-item">
<i className="fas fa-clock meta-icon"></i>
<div className={style.metaItem}>
<i className={`fas fa-clock ${style.metaIcon}`}></i>
<span>{post.readTime}</span>
</div>
<div className="meta-item">
<i className="fas fa-comments meta-icon"></i>
<div className={style.metaItem}>
<i className={`fas fa-comments ${style.metaIcon}`}></i>
<span>{post.commentCount} Comments</span>
</div>
</div>
</div>
{/* Post Content */}
<div className="post-content" dangerouslySetInnerHTML={{ __html: post.content }}></div>
{/* Post Tags */}
<div className="post-tags">
{post.tags.map((tag, index) => (
<span key={index} className="post-tag">{tag}</span>
))}
<div className={style.postContent} id="postContent">
</div>
{/* Share Section */}
<div className="share-section">
<h3 className="share-title">Share this arcane knowledge</h3>
<div className="share-buttons">
<a href="#" className="share-button"><i className="fab fa-twitter"></i></a>
<a href="#" className="share-button"><i className="fab fa-facebook-f"></i></a>
<a href="#" className="share-button"><i className="fab fa-linkedin-in"></i></a>
<a href="#" className="share-button"><i className="fab fa-reddit-alien"></i></a>
<a href="#" className="share-button"><i className="far fa-envelope"></i></a>
<div className={style.postContent}>
{/* Post Tags */}
<div className={style.postTags}>
{post.tags.map((tag, index) => (
<span key={index} className={style.postTag}>{tag}</span>
))}
</div>
</div>
{/* Author Info */}
<div className="author-section">
<div className="author-avatar">
<img src="/images/author.jpg" alt="SecCodeSmith" />
</div>
<div className="author-info">
<h3 className="author-name">SecCodeSmith</h3>
<p className="author-bio">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.</p>
<div className="author-social">
<a href="#"><i className="fab fa-github"></i></a>
<a href="#"><i className="fab fa-twitter"></i></a>
<a href="#"><i className="fab fa-linkedin-in"></i></a>
<a href="#"><i className="fab fa-stack-overflow"></i></a>
{/* Share Section */}
<div className={style.shareSection}>
<h3 className={style.shareTitle}>Share this arcane knowledge</h3>
<div className={style.shareButtons}>
<a href="#" className={style.shareButton}><i className="fab fa-twitter"></i></a>
<a href="#" className={style.shareButton}><i className="fab fa-facebook-f"></i></a>
<a href="#" className={style.shareButton}><i className="fab fa-linkedin-in"></i></a>
<a href="#" className={style.shareButton}><i className="fab fa-reddit-alien"></i></a>
<a href="#" className={style.shareButton}><i className="far fa-envelope"></i></a>
</div>
</div>
</div>
{/* Comments Section */}
<div className="comments-section">
<h3 className="comments-title">Discussions ({post.commentCount})</h3>
{/* Sample Comments */}
<div className="comment">
<div className="comment-avatar">
<img src="/images/user-1.jpg" alt="User" />
{/* Author Info */}
<div className={style.authorSection}>
<div className={style.authorAvatar}>
<img src="/images/author.jpg" alt="SecCodeSmith" />
</div>
<div className="comment-content">
<h4 className="comment-author">Michael Chen</h4>
<div className="comment-date">May 5, 2025 at 15:42</div>
<p className="comment-text">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?</p>
<a href="#" className="comment-reply">Reply</a>
</div>
</div>
<div className="comment">
<div className="comment-avatar">
<img src="/images/author.jpg" alt="User" />
</div>
<div className="comment-content">
<h4 className="comment-author">SecCodeSmith</h4>
<div className="comment-date">May 5, 2025 at 16:10</div>
<p className="comment-text">@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.</p>
<a href="#" className="comment-reply">Reply</a>
</div>
</div>
{/* Comment Form */}
<div className="comment-form">
<h3 className="form-title">Leave your mark</h3>
<form>
<div className="form-row">
<div className="form-group">
<input type="text" className="form-control" placeholder="Your Name" />
</div>
<div className="form-group">
<input type="email" className="form-control" placeholder="Your Email" />
</div>
<div className={style.authorInfo}>
<h3 className={style.authorName}>SecCodeSmith</h3>
<p className={style.authorBio}>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.</p>
<div className={style.authorSocial}>
<a href="#"><i className="fab fa-github"></i></a>
<a href="#"><i className="fab fa-twitter"></i></a>
<a href="#"><i className="fab fa-linkedin-in"></i></a>
<a href="#"><i className="fab fa-stack-overflow"></i></a>
</div>
<div className="form-group">
<textarea className="form-control" rows={5} placeholder="Your Comment"></textarea>
</div>
</div>
{/* Comments Section */}
<div className={style.commentsSection}>
<h3 className={style.commentsTitle}>Discussions ({post.commentCount})</h3>
{/* Sample Comments */}
<div className={style.comment}>
<div className={style.commentAvatar}>
<img src="/images/user-1.jpg" alt="User" />
</div>
<button type="submit" className="submit-button">Post Comment</button>
</form>
<div className={style.commentContent}>
<h4 className={style.commentAuthor}>Michael Chen</h4>
<div className={style.commentDate}>May 5, 2025 at 15:42</div>
<p className={style.commentText}>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?</p>
<a href="#" className={style.commentReply}>Reply</a>
</div>
</div>
{/* Comment Form */}
<div className={`${style.commentForm} comment-form`}>
<h3 className={style.formTitle}>Leave your mark</h3>
<form>
<div className={`form-row ${style.formRow}`}>
<div className={`form-group ${style.formGroup}`}>
<input type="text" className={`${style.formControl}`} placeholder="Your Name" />
</div>
<div className={`form-group ${style.formGroup}`}>
<input type="email" className={`${style.formControl}`} placeholder="Your Email" />
</div>
</div>
<div className={`form-group ${style.formGroup}`}>
<textarea className={`${style.formControl}`} rows={5} placeholder="Your Comment"></textarea>
</div>
<button type="submit" className={`submit-button ${style.submitButton}`}>Post Comment</button>
</form>
</div>
</div>
</div>
</article>
</main>
<aside className="post-sidebar">
<aside className={style.postSidebar}>
{/* Table of Contents */}
<div className="sidebar-widget">
<h3 className="widget-title">Table of Contents</h3>
<ul className="toc-list">
<li className="toc-item">
<a href="#" className="toc-link">Introduction</a>
</li>
<li className="toc-item">
<a href="#" className="toc-link">The Three Elemental Protocols</a>
</li>
<li className="toc-item">
<a href="#" className="toc-link">Optimizing I2C for Reliable Field Operations</a>
<ul className="toc-list">
<li className="toc-subitem">
<a href="#" className="toc-link">Hardware Incantations</a>
</li>
<li className="toc-subitem">
<a href="#" className="toc-link">Software Rituals</a>
</li>
</ul>
</li>
</ul>
<div className={style.sidebarWidget}>
<h3 className={style.widgetTitle}>Table of Contents</h3>
<TableOfContents toc={toc} />
</div>
{/* Related Posts */}
<div className="sidebar-widget">
<h3 className="widget-title">Related Scrolls</h3>
<ul className="related-posts-list">
<div className={style.sidebarWidget}>
<h3 className={style.widgetTitle}>Related Scrolls</h3>
<ul className={style.relatedPostsList}>
{relatedPosts.map(relatedPost => (
<li className="related-post-item" key={relatedPost.id}>
<Link to={`/blog/${relatedPost.slug}`} className="related-post-link">
<div className="related-post-thumb" style={{ backgroundImage: `url(${relatedPost.image})` }}></div>
<div className="related-post-info">
<h4 className="related-post-title">{relatedPost.title}</h4>
<span className="related-post-date">{relatedPost.date}</span>
<li className={style.relatedPostItem} key={relatedPost.id}>
<Link to={`/blog/${relatedPost.slug}`} className={style.relatedPostLink}>
<div className={style.relatedPostThumb} style={{ backgroundImage: `url(${relatedPost.image})` }}></div>
<div className={style.relatedPostInfo}>
<h4 className={style.relatedPostTitle}>{relatedPost.title}</h4>
<span className={style.relatedPostDate}>{relatedPost.date}</span>
</div>
</Link>
</li>
-9
View File
@@ -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 */}
<section className={styles.hero} id="home">
<div id="binary-bg" className={styles.binaryBg}></div>
<div className={`container text-center ${styles.heroContainer}`}>
@@ -113,7 +105,6 @@ export const Home = () => {
<div id="particles" className={`particles ${styles.particles}`}></div>
</section>
{/* Skills Section */}
<section id="skills" className={`skills-section ${styles.skillsSection}`}>
<div className="container">
<div className="row justify-content-center mb-5">