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/Blog.module.scss b/src/assets/styles/Blog.module.scss new file mode 100644 index 0000000..b593ec5 --- /dev/null +++ b/src/assets/styles/Blog.module.scss @@ -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; +} diff --git a/src/assets/styles/Blog.scss b/src/assets/styles/Blog.scss deleted file mode 100644 index db9a0de..0000000 --- a/src/assets/styles/Blog.scss +++ /dev/null @@ -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; -} \ No newline at end of file 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/BlogPost.module.scss b/src/assets/styles/BlogPost.module.scss new file mode 100644 index 0000000..bc73265 --- /dev/null +++ b/src/assets/styles/BlogPost.module.scss @@ -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; +} \ No newline at end of file diff --git a/src/assets/styles/Home.module.scss b/src/assets/styles/Home.module.scss index 38a6e83..14129a5 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/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/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/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/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/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/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/data/blogPostsData.ts b/src/data/blogPostsData.ts index 358f161..ee437ef 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/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..3e6d1be 100644 --- a/src/pages/Blog.tsx +++ b/src/pages/Blog.tsx @@ -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) => { 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 = () => {
- {/* Main Content */}
- {/* Featured Post */} {featuredPost && (!searchQuery || featuredPost.title.toLowerCase().includes(searchQuery.toLowerCase()) || featuredPost.excerpt.toLowerCase().includes(searchQuery.toLowerCase()) || @@ -56,7 +50,6 @@ export const Blog = () => { )} - {/* Regular Posts */}
{regularPosts.map(post => (
@@ -65,7 +58,6 @@ export const Blog = () => { ))}
- {/* No Posts Message */} {filteredPosts.length === 0 && (

No posts found matching your search.

@@ -78,34 +70,34 @@ export const Blog = () => {
)} - {/* Pagination */} - {filteredPosts.length > 0 && ( + {filteredPostPageCount > 1 && ( )}
- {/* 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 +130,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/BlogPost.tsx b/src/pages/BlogPost.tsx index 1d80e4d..ea6e401 100644 --- a/src/pages/BlogPost.tsx +++ b/src/pages/BlogPost.tsx @@ -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 ( +
    + {toc.map(item => ( +
  • + {item.title} + {item.children && item.children.length > 0 && ( + + )} +
  • + ))} +
+ ); +} + 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([]); + 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 += `

${lines[i].replace(/^#+\s*/, '')}

\n`; + toc.push({ title: lines[i].replace(/^#+\s*/, ''), id }); + id++; + } + else if (/^##\s/.test(lines[i])) { + newContent += `

${lines[i].replace(/^#+\s*/, '')}

\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 += `

${lines[i].replace(/^#+\s*/, '')}

\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 += '
    \n'; + + for (; i < lines.length; i++) { + if (/^\*\s/.test(lines[i])) { + newContent += `
  • ${lines[i].replace(/^\*\s/, '')}
  • \n`; + } + else { + break; + } + } + newContent += '
\n'; + } + else if (/^\-\s/.test(lines[i])) { + newContent += '
    \n'; + + for (; i < lines.length; i++) { + if (/^\-\s/.test(lines[i])) { + newContent += `
  1. ${lines[i].replace(/^\-\s/, '')}
  2. \n`; + } + else { + break; + } + } + newContent += '
\n'; + } + else { + newContent += `

${lines[i]}

\n`; + } + } + } + + newContent = newContent.replace(/\*\*(.*?)\*\*/g, '$1'); + newContent = newContent.replace(/__(.*?)__/g, '$1'); + newContent = newContent.replace(/\*(.*?)\*/g, '$1'); + newContent = newContent.replace(/_(.*?)_/g, '$1'); + newContent = newContent.replace(/`(.*?)`/g, '$1'); + newContent = newContent.replace(/!\[(.*?)\]\((.*?)\)/g, '$1'); + newContent = newContent.replace(/\[(.*?)\]\((.*?)\)/g, '$1'); + + const contentElement = document.getElementById('postContent'); + contentElement!.innerHTML = newContent; + setToc(toc); + 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

+
+
+
+ +
+
+ +
+
+
+ +
+ +
+
- -