laforceit-blog/src/layouts/BlogPostLayout.astro

346 lines
9.8 KiB
Plaintext

---
import BaseLayout from './BaseLayout.astro';
import Header from '../components/Header.astro';
import Footer from '../components/Footer.astro';
import Newsletter from '../components/Newsletter.astro';
interface Props {
frontmatter: {
title: string;
description?: string;
pubDate: Date;
updatedDate?: Date;
heroImage?: string;
category?: string; // Keep category for potential filtering, but don't display in header
tags?: string[];
readTime?: string;
draft?: boolean;
author?: string; // Keep author field if needed elsewhere
// Add other potential frontmatter fields as optional
github?: string;
live?: string;
technologies?: string[];
}
}
const { frontmatter } = Astro.props;
const formattedPubDate = frontmatter.pubDate ? new Date(frontmatter.pubDate).toLocaleDateString('en-us', {
year: 'numeric',
month: 'long',
day: 'numeric',
}) : 'N/A';
const formattedUpdatedDate = frontmatter.updatedDate ? new Date(frontmatter.updatedDate).toLocaleDateString('en-us', {
year: 'numeric',
month: 'long',
day: 'numeric',
}) : null;
// Default image if heroImage is missing
const displayImage = frontmatter.heroImage || '/images/placeholders/default.jpg';
---
<BaseLayout title={frontmatter.title} description={frontmatter.description} image={displayImage}>
<Header slot="header" />
<div class="blog-post-container">
<article class="blog-post">
<header class="blog-post-header">
{/* Display Draft Badge First */}
{frontmatter.draft && <span class="draft-badge mb-4">DRAFT</span>}
{/* Title (Smaller) */}
<h1 class="blog-post-title mb-2">{frontmatter.title}</h1>
{/* Description */}
{frontmatter.description && <p class="blog-post-description mb-4">{frontmatter.description}</p>}
{/* Metadata (Date, Read Time) */}
<div class="blog-post-meta mb-4">
<span class="blog-post-date">Published {formattedPubDate}</span>
{formattedUpdatedDate && (
<span class="blog-post-updated">(Updated {formattedUpdatedDate})</span>
)}
{frontmatter.readTime && <span class="blog-post-read-time">{frontmatter.readTime}</span>}
{/* Category removed from display here */}
</div>
{/* Tags */}
{frontmatter.tags && frontmatter.tags.length > 0 && (
<div class="blog-post-tags">
{frontmatter.tags.map((tag) => (
<a href={`/tag/${tag}`} class="blog-post-tag">#{tag}</a>
))}
</div>
)}
</header>
{/* Display Hero Image */}
{displayImage && (
<div class="blog-post-hero">
<img src={displayImage.startsWith('/') ? displayImage : `/${displayImage}`} alt={frontmatter.title} width="1024" height="512" loading="lazy" />
</div>
)}
{/* Main Content Area */}
<div class="blog-post-content prose prose-invert max-w-none">
<slot /> {/* Renders the actual markdown content */}
</div>
{/* Future Feature Placeholders remain commented out */}
{/* ... */}
</article>
{/* Sidebar */}
<aside class="blog-post-sidebar">
{/* Author Card Updated */}
<div class="sidebar-card author-card">
<div class="author-avatar">
<img src="/images/avatar.jpg" alt="LaForceIT Tech Blogs" />
</div>
<div class="author-info">
<h3>LaForceIT.com Tech Blogs</h3>
<p>For Home Labbers, Technologists & Engineers</p>
</div>
<p class="author-bio">
Exploring enterprise-grade infrastructure, automation, Kubernetes, and zero-trust networking in the home lab and beyond.
</p>
{/* Social links removed */}
</div>
{/* Table of Contents Card */}
<div class="sidebar-card toc-card">
<h3>Table of Contents</h3>
<nav class="toc-container" id="toc">
<p class="text-sm text-gray-400">Loading TOC...</p>
</nav>
</div>
{/* Future Feature Placeholders remain commented out */}
{/* ... */}
</aside>
</div>
<Newsletter />
<Footer slot="footer" />
</BaseLayout>
{/* Script for Table of Contents Generation (Unchanged) */}
<script>
function generateToc() {
const tocContainer = document.getElementById('toc');
const contentArea = document.querySelector('.blog-post-content');
if (!tocContainer || !contentArea) return;
const headings = contentArea.querySelectorAll('h2, h3');
if (headings.length > 0) {
const tocList = document.createElement('ul');
tocList.className = 'toc-list';
headings.forEach((heading) => {
let id = heading.id;
if (!id) {
id = heading.textContent?.toLowerCase().replace(/[^\w\s-]/g, '').replace(/\s+/g, '-').replace(/--+/g, '-') || `heading-${Math.random().toString(36).substring(7)}`;
heading.id = id;
}
const listItem = document.createElement('li');
listItem.className = `toc-item toc-${heading.tagName.toLowerCase()}`;
const link = document.createElement('a');
link.href = `#${id}`;
link.textContent = heading.textContent;
link.addEventListener('click', (e) => {
e.preventDefault();
document.getElementById(id)?.scrollIntoView({ behavior: 'smooth' });
});
listItem.appendChild(link);
tocList.appendChild(listItem);
});
tocContainer.innerHTML = '';
tocContainer.appendChild(tocList);
} else {
tocContainer.innerHTML = '<p class="text-sm text-gray-400">No sections found.</p>';
}
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', generateToc);
} else {
generateToc();
}
</script>
{/* Styles Updated */}
<style>
.draft-badge {
display: inline-block;
margin-bottom: 1rem;
padding: 0.25rem 0.75rem;
background-color: rgba(234, 179, 8, 0.2);
color: #ca8a04;
font-size: 0.8rem;
border-radius: 0.25rem;
font-weight: 600;
font-family: 'JetBrains Mono', monospace;
}
.blog-post-container {
display: grid;
/* Adjusted grid for wider TOC/Sidebar */
grid-template-columns: 7fr 2fr;
gap: 3rem; /* Wider gap */
max-width: 1400px; /* Wider max width */
margin: 2rem auto;
padding: 0 1.5rem;
}
.blog-post-header {
margin-bottom: 2.5rem;
border-bottom: 1px solid var(--card-border);
padding-bottom: 1.5rem;
}
.blog-post-title {
/* Made title slightly smaller */
font-size: clamp(1.8rem, 4vw, 2.5rem);
line-height: 1.25; /* Adjusted line height */
margin-bottom: 0.75rem; /* Adjusted margin */
color: var(--text-primary);
}
.blog-post-description {
font-size: 1.1rem;
color: var(--text-secondary);
margin-bottom: 1.5rem; /* Increased margin */
max-width: 75ch; /* Adjusted width */
}
.blog-post-meta {
display: flex;
flex-wrap: wrap;
gap: 0.5rem 1.5rem;
margin-bottom: 1.5rem; /* Increased margin */
font-size: 0.85rem;
color: var(--text-secondary);
}
/* Removed .blog-post-category style */
.blog-post-tags {
display: flex;
flex-wrap: wrap;
gap: 0.75rem;
margin-top: 0rem; /* Removed top margin */
}
.blog-post-tag {
color: var(--accent-secondary);
text-decoration: none;
font-size: 0.85rem;
transition: color 0.3s ease;
font-family: 'JetBrains Mono', monospace;
background-color: rgba(59, 130, 246, 0.1);
padding: 0.2rem 0.6rem;
border-radius: 4px;
}
.blog-post-tag:hover {
color: var(--accent-primary);
background-color: rgba(6, 182, 212, 0.15);
}
.blog-post-hero {
width: 100%;
margin-bottom: 2.5rem;
border-radius: 8px;
overflow: hidden;
border: 1px solid var(--card-border);
background-color: var(--bg-secondary);
}
.blog-post-hero img {
width: 100%;
height: auto;
display: block;
}
.blog-post-content {
/* Styles inherited from prose */
}
.blog-post-sidebar {
position: sticky;
top: 2rem;
align-self: start;
height: calc(100vh - 4rem);
overflow-y: auto;
}
.sidebar-card {
background: var(--card-bg);
border: 1px solid var(--card-border);
border-radius: 10px;
padding: 1.5rem;
margin-bottom: 1.5rem;
}
.author-card {
text-align: center;
}
.author-avatar {
width: 80px;
height: 80px;
border-radius: 50%;
overflow: hidden;
margin: 0 auto 1rem;
border: 2px solid var(--accent-primary);
background-color: var(--bg-secondary);
}
.author-avatar img {
width: 100%;
height: 100%;
object-fit: cover;
}
.author-info h3 {
margin-bottom: 0.25rem;
color: var(--text-primary);
font-size: 1.1rem;
}
.author-info p { /* Target the subtitle */
color: var(--text-secondary);
margin-bottom: 1rem;
font-size: 0.9rem;
}
.author-bio { /* Target the main bio */
font-size: 0.9rem;
margin-bottom: 0; /* Remove bottom margin */
color: var(--text-secondary);
text-align: left;
}
/* Social links removed */
.toc-card h3 {
margin-bottom: 1rem;
color: var(--text-primary);
}
.toc-list {
list-style: none;
padding: 0;
margin: 0;
max-height: 60vh;
overflow-y: auto;
}
.toc-item {
margin-bottom: 0.9rem; /* Increased spacing */
}
.toc-item a {
color: var(--text-secondary);
text-decoration: none;
transition: color 0.3s ease;
font-size: 0.9rem;
display: block;
padding-left: 0;
line-height: 1.4; /* Improve readability */
}
.toc-item a:hover {
color: var(--accent-primary);
}
.toc-h3 a {
padding-left: 1.5rem; /* Increased indent */
font-size: 0.85rem;
opacity: 0.9;
}
@media (max-width: 1024px) {
.blog-post-container {
grid-template-columns: 1fr; /* Stack on smaller screens */
}
.blog-post-sidebar {
display: none;
}
}
</style>