feat(KnowledgeGraph): Add fullscreen mode with content panel
This commit is contained in:
parent
3b5d2fac2c
commit
336238e758
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,222 @@
|
||||||
|
---
|
||||||
|
// MiniGraph.astro - A standalone mini knowledge graph component
|
||||||
|
// This component is designed to work independently from the blog structure
|
||||||
|
|
||||||
|
// Define props interface
|
||||||
|
interface Props {
|
||||||
|
slug: string; // Current post slug
|
||||||
|
title: string; // Current post title
|
||||||
|
tags?: string[]; // Current post tags
|
||||||
|
category?: string; // Current post category
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract props with defaults
|
||||||
|
const {
|
||||||
|
slug,
|
||||||
|
title,
|
||||||
|
tags = [],
|
||||||
|
category = "Uncategorized"
|
||||||
|
} = Astro.props;
|
||||||
|
|
||||||
|
// Generate unique ID for the graph container
|
||||||
|
const graphId = `graph-${Math.random().toString(36).substring(2, 8)}`;
|
||||||
|
|
||||||
|
// Prepare simple graph data for just the post and its tags
|
||||||
|
const nodes = [
|
||||||
|
// Current post node
|
||||||
|
{
|
||||||
|
id: slug,
|
||||||
|
label: title,
|
||||||
|
type: "post"
|
||||||
|
},
|
||||||
|
// Tag nodes
|
||||||
|
...tags.map(tag => ({
|
||||||
|
id: `tag-${tag}`,
|
||||||
|
label: tag,
|
||||||
|
type: "tag"
|
||||||
|
}))
|
||||||
|
];
|
||||||
|
|
||||||
|
// Create edges connecting post to tags
|
||||||
|
const edges = tags.map(tag => ({
|
||||||
|
source: slug,
|
||||||
|
target: `tag-${tag}`,
|
||||||
|
type: "post-tag"
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Prepare graph data object
|
||||||
|
const graphData = { nodes, edges };
|
||||||
|
---
|
||||||
|
|
||||||
|
<!-- Super simple HTML structure -->
|
||||||
|
<div class="knowledge-graph-wrapper">
|
||||||
|
<h4 class="graph-title">Post Connections</h4>
|
||||||
|
<div id={graphId} class="mini-graph-container"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Minimal CSS -->
|
||||||
|
<style>
|
||||||
|
.knowledge-graph-wrapper {
|
||||||
|
width: 100%;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.graph-title {
|
||||||
|
font-size: 1rem;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
color: var(--text-primary, #e2e8f0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mini-graph-container {
|
||||||
|
width: 100%;
|
||||||
|
height: 200px;
|
||||||
|
border-radius: 8px;
|
||||||
|
overflow: hidden;
|
||||||
|
border: 1px solid var(--card-border, rgba(56, 189, 248, 0.2));
|
||||||
|
background: rgba(15, 23, 42, 0.2);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<!-- Standalone initialization script -->
|
||||||
|
<script define:vars={{ graphId, graphData }}>
|
||||||
|
// Wait for page to fully load
|
||||||
|
window.addEventListener('load', function() {
|
||||||
|
// Retry initialization multiple times in case Cytoscape or the DOM isn't ready yet
|
||||||
|
let retries = 0;
|
||||||
|
const maxRetries = 5;
|
||||||
|
const retryInterval = 500; // ms
|
||||||
|
|
||||||
|
function initGraph() {
|
||||||
|
// Ensure Cytoscape is loaded
|
||||||
|
if (typeof cytoscape === 'undefined') {
|
||||||
|
console.warn(`[MiniGraph] Cytoscape not loaded, retry ${retries+1}/${maxRetries}...`);
|
||||||
|
if (retries < maxRetries) {
|
||||||
|
retries++;
|
||||||
|
setTimeout(initGraph, retryInterval);
|
||||||
|
} else {
|
||||||
|
console.error("[MiniGraph] Cytoscape library not available after multiple attempts.");
|
||||||
|
const container = document.getElementById(graphId);
|
||||||
|
if (container) {
|
||||||
|
container.innerHTML = '<div style="padding:10px;color:#a0aec0;text-align:center;">Graph library not loaded</div>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify container exists
|
||||||
|
const container = document.getElementById(graphId);
|
||||||
|
if (!container) {
|
||||||
|
console.error(`[MiniGraph] Container #${graphId} not found.`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Check if we have any nodes
|
||||||
|
if (graphData.nodes.length === 0) {
|
||||||
|
container.innerHTML = '<div style="padding:10px;color:#a0aec0;text-align:center;">No connections</div>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize Cytoscape
|
||||||
|
const cy = cytoscape({
|
||||||
|
container,
|
||||||
|
elements: [
|
||||||
|
...graphData.nodes.map(node => ({
|
||||||
|
data: {
|
||||||
|
id: node.id,
|
||||||
|
label: node.label,
|
||||||
|
type: node.type
|
||||||
|
}
|
||||||
|
})),
|
||||||
|
...graphData.edges.map((edge, index) => ({
|
||||||
|
data: {
|
||||||
|
id: `e${index}`,
|
||||||
|
source: edge.source,
|
||||||
|
target: edge.target,
|
||||||
|
type: edge.type
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
],
|
||||||
|
style: [
|
||||||
|
// Base node style
|
||||||
|
{
|
||||||
|
selector: 'node',
|
||||||
|
style: {
|
||||||
|
'background-color': '#3B82F6',
|
||||||
|
'label': 'data(label)',
|
||||||
|
'width': 20,
|
||||||
|
'height': 20,
|
||||||
|
'font-size': '8px',
|
||||||
|
'color': '#E2E8F0',
|
||||||
|
'text-valign': 'bottom',
|
||||||
|
'text-halign': 'center',
|
||||||
|
'text-margin-y': 5,
|
||||||
|
'text-wrap': 'ellipsis',
|
||||||
|
'text-max-width': '60px'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// Post node style
|
||||||
|
{
|
||||||
|
selector: 'node[type="post"]',
|
||||||
|
style: {
|
||||||
|
'background-color': '#06B6D4',
|
||||||
|
'width': 30,
|
||||||
|
'height': 30,
|
||||||
|
'font-size': '9px',
|
||||||
|
'text-max-width': '80px'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// Tag node style
|
||||||
|
{
|
||||||
|
selector: 'node[type="tag"]',
|
||||||
|
style: {
|
||||||
|
'background-color': '#10B981',
|
||||||
|
'shape': 'diamond',
|
||||||
|
'width': 18,
|
||||||
|
'height': 18
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// Edge style
|
||||||
|
{
|
||||||
|
selector: 'edge',
|
||||||
|
style: {
|
||||||
|
'width': 1,
|
||||||
|
'line-color': 'rgba(16, 185, 129, 0.6)',
|
||||||
|
'line-style': 'dashed',
|
||||||
|
'curve-style': 'bezier',
|
||||||
|
'opacity': 0.7
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
// Simple layout for small space
|
||||||
|
layout: {
|
||||||
|
name: 'concentric',
|
||||||
|
concentric: function(node) {
|
||||||
|
return node.data('type') === 'post' ? 10 : 1;
|
||||||
|
},
|
||||||
|
levelWidth: function() { return 1; },
|
||||||
|
minNodeSpacing: 50,
|
||||||
|
animate: false
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Make nodes clickable
|
||||||
|
cy.on('tap', 'node[type="tag"]', function(evt) {
|
||||||
|
const node = evt.target;
|
||||||
|
const tagName = node.data('label');
|
||||||
|
window.location.href = `/tag/${tagName}`;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Fit graph to container
|
||||||
|
cy.fit(undefined, 20);
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[MiniGraph] Error initializing graph:', error);
|
||||||
|
container.innerHTML = '<div style="padding:10px;color:#a0aec0;text-align:center;">Error loading graph</div>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start initialization attempt
|
||||||
|
initGraph();
|
||||||
|
});
|
||||||
|
</script>
|
File diff suppressed because it is too large
Load Diff
|
@ -2,8 +2,7 @@
|
||||||
import BaseLayout from './BaseLayout.astro';
|
import BaseLayout from './BaseLayout.astro';
|
||||||
import Header from '../components/Header.astro';
|
import Header from '../components/Header.astro';
|
||||||
import Footer from '../components/Footer.astro';
|
import Footer from '../components/Footer.astro';
|
||||||
import Newsletter from '../components/Newsletter.astro';
|
import MiniKnowledgeGraph from '../components/MiniKnowledgeGraph.astro'; // Restore original or keep if needed
|
||||||
import MiniKnowledgeGraph from '../components/MiniKnowledgeGraph.astro';
|
|
||||||
import { getCollection } from 'astro:content';
|
import { getCollection } from 'astro:content';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
@ -18,16 +17,68 @@ interface Props {
|
||||||
readTime?: string;
|
readTime?: string;
|
||||||
draft?: boolean;
|
draft?: boolean;
|
||||||
author?: string;
|
author?: string;
|
||||||
github?: string;
|
// Field for explicitly related posts
|
||||||
live?: string;
|
related_posts?: string[];
|
||||||
technologies?: string[];
|
},
|
||||||
related_posts?: string[]; // Explicit related posts by slug
|
slug: string // Add slug to props
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const { frontmatter } = Astro.props;
|
const { frontmatter, slug } = Astro.props;
|
||||||
|
|
||||||
// Format dates
|
// Get all posts for finding related content
|
||||||
|
const allPosts = await getCollection('posts');
|
||||||
|
|
||||||
|
// Create a currentPost object that matches the structure expected by MiniKnowledgeGraph
|
||||||
|
const currentPost = {
|
||||||
|
slug: slug,
|
||||||
|
data: frontmatter
|
||||||
|
};
|
||||||
|
|
||||||
|
// Find related posts - first from explicitly defined related_posts
|
||||||
|
const explicitRelatedPosts = frontmatter.related_posts
|
||||||
|
? allPosts.filter(post =>
|
||||||
|
frontmatter.related_posts?.includes(post.slug) &&
|
||||||
|
post.slug !== slug
|
||||||
|
)
|
||||||
|
: [];
|
||||||
|
|
||||||
|
// Then find posts with shared tags (if we need more related posts)
|
||||||
|
const MAX_RELATED_POSTS = 3;
|
||||||
|
let relatedPostsByTags = [];
|
||||||
|
|
||||||
|
if (explicitRelatedPosts.length < MAX_RELATED_POSTS && frontmatter.tags && frontmatter.tags.length > 0) {
|
||||||
|
// Create a map of posts by tags for efficient lookup
|
||||||
|
const postsByTag = new Map();
|
||||||
|
frontmatter.tags.forEach(tag => {
|
||||||
|
postsByTag.set(tag, allPosts.filter(post =>
|
||||||
|
post.slug !== slug &&
|
||||||
|
post.data.tags?.includes(tag) &&
|
||||||
|
!explicitRelatedPosts.some(p => p.slug === post.slug)
|
||||||
|
));
|
||||||
|
});
|
||||||
|
|
||||||
|
// Score posts by number of shared tags
|
||||||
|
const scoredPosts = new Map();
|
||||||
|
|
||||||
|
postsByTag.forEach((posts, tag) => {
|
||||||
|
posts.forEach(post => {
|
||||||
|
const currentScore = scoredPosts.get(post.slug) || 0;
|
||||||
|
scoredPosts.set(post.slug, currentScore + 1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Convert to array, sort by score, and take what we need
|
||||||
|
relatedPostsByTags = Array.from(scoredPosts.entries())
|
||||||
|
.sort((a, b) => b[1] - a[1])
|
||||||
|
.slice(0, MAX_RELATED_POSTS - explicitRelatedPosts.length)
|
||||||
|
.map(([slug]) => allPosts.find(post => post.slug === slug))
|
||||||
|
.filter(Boolean); // Remove any undefined entries
|
||||||
|
}
|
||||||
|
|
||||||
|
// Combine explicit and tag-based related posts
|
||||||
|
const relatedPosts = [...explicitRelatedPosts, ...relatedPostsByTags];
|
||||||
|
|
||||||
|
// Format date
|
||||||
const formattedPubDate = frontmatter.pubDate ? new Date(frontmatter.pubDate).toLocaleDateString('en-us', {
|
const formattedPubDate = frontmatter.pubDate ? new Date(frontmatter.pubDate).toLocaleDateString('en-us', {
|
||||||
year: 'numeric',
|
year: 'numeric',
|
||||||
month: 'long',
|
month: 'long',
|
||||||
|
@ -42,58 +93,6 @@ const formattedUpdatedDate = frontmatter.updatedDate ? new Date(frontmatter.upda
|
||||||
|
|
||||||
// Default image if heroImage is missing
|
// Default image if heroImage is missing
|
||||||
const displayImage = frontmatter.heroImage || '/images/placeholders/default.jpg';
|
const displayImage = frontmatter.heroImage || '/images/placeholders/default.jpg';
|
||||||
|
|
||||||
// Get related posts for MiniKnowledgeGraph
|
|
||||||
// First get all posts
|
|
||||||
const allPosts = await getCollection('posts').catch(() => []);
|
|
||||||
|
|
||||||
// Find the current post in collection
|
|
||||||
const currentPost = allPosts.find(post =>
|
|
||||||
post.data.title === frontmatter.title ||
|
|
||||||
post.slug === frontmatter.title.toLowerCase().replace(/\s+/g, '-')
|
|
||||||
);
|
|
||||||
|
|
||||||
// Get related posts - first from explicit frontmatter relation, then by tag similarity
|
|
||||||
let relatedPosts = [];
|
|
||||||
|
|
||||||
// If related_posts is specified in frontmatter, use those first
|
|
||||||
if (frontmatter.related_posts && frontmatter.related_posts.length > 0) {
|
|
||||||
const explicitRelatedPosts = allPosts.filter(post =>
|
|
||||||
frontmatter.related_posts.includes(post.slug)
|
|
||||||
);
|
|
||||||
relatedPosts = [...explicitRelatedPosts];
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we need more related posts, find them by tags
|
|
||||||
if (relatedPosts.length < 3 && frontmatter.tags && frontmatter.tags.length > 0) {
|
|
||||||
// Calculate tag similarity score for each post
|
|
||||||
const tagSimilarityPosts = allPosts
|
|
||||||
.filter(post =>
|
|
||||||
// Filter out current post and already included related posts
|
|
||||||
post.data.title !== frontmatter.title &&
|
|
||||||
!relatedPosts.some(rp => rp.slug === post.slug)
|
|
||||||
)
|
|
||||||
.map(post => {
|
|
||||||
// Count matching tags
|
|
||||||
const postTags = post.data.tags || [];
|
|
||||||
const matchingTags = postTags.filter(tag =>
|
|
||||||
frontmatter.tags.includes(tag)
|
|
||||||
);
|
|
||||||
return {
|
|
||||||
post,
|
|
||||||
score: matchingTags.length
|
|
||||||
};
|
|
||||||
})
|
|
||||||
.filter(item => item.score > 0) // Only consider posts with at least one matching tag
|
|
||||||
.sort((a, b) => b.score - a.score) // Sort by score descending
|
|
||||||
.map(item => item.post); // Extract just the post
|
|
||||||
|
|
||||||
// Add tag-related posts to fill up to 3 related posts
|
|
||||||
relatedPosts = [...relatedPosts, ...tagSimilarityPosts.slice(0, 3 - relatedPosts.length)];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Limit to 3 related posts
|
|
||||||
relatedPosts = relatedPosts.slice(0, 3);
|
|
||||||
---
|
---
|
||||||
|
|
||||||
<BaseLayout title={frontmatter.title} description={frontmatter.description} image={displayImage}>
|
<BaseLayout title={frontmatter.title} description={frontmatter.description} image={displayImage}>
|
||||||
|
@ -102,10 +101,10 @@ relatedPosts = relatedPosts.slice(0, 3);
|
||||||
<div class="blog-post-container">
|
<div class="blog-post-container">
|
||||||
<article class="blog-post">
|
<article class="blog-post">
|
||||||
<header class="blog-post-header">
|
<header class="blog-post-header">
|
||||||
{/* Display Draft Badge First */}
|
{/* Display Draft Badge if needed */}
|
||||||
{frontmatter.draft && <span class="draft-badge mb-4">DRAFT</span>}
|
{frontmatter.draft && <span class="draft-badge mb-4">DRAFT</span>}
|
||||||
|
|
||||||
{/* Title (Smaller) */}
|
{/* Title */}
|
||||||
<h1 class="blog-post-title mb-2">{frontmatter.title}</h1>
|
<h1 class="blog-post-title mb-2">{frontmatter.title}</h1>
|
||||||
|
|
||||||
{/* Description */}
|
{/* Description */}
|
||||||
|
@ -118,6 +117,7 @@ relatedPosts = relatedPosts.slice(0, 3);
|
||||||
<span class="blog-post-updated">(Updated {formattedUpdatedDate})</span>
|
<span class="blog-post-updated">(Updated {formattedUpdatedDate})</span>
|
||||||
)}
|
)}
|
||||||
{frontmatter.readTime && <span class="blog-post-read-time">{frontmatter.readTime}</span>}
|
{frontmatter.readTime && <span class="blog-post-read-time">{frontmatter.readTime}</span>}
|
||||||
|
{frontmatter.category && <span class="blog-post-category">{frontmatter.category}</span>}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Tags */}
|
{/* Tags */}
|
||||||
|
@ -130,18 +130,6 @@ relatedPosts = relatedPosts.slice(0, 3);
|
||||||
)}
|
)}
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
{/* Content Connections Graph - only show if we have the current post and related content */}
|
|
||||||
{currentPost && (frontmatter.tags?.length > 0 || relatedPosts.length > 0) && (
|
|
||||||
<div class="content-connections">
|
|
||||||
<h3 class="section-subtitle">Content Connections</h3>
|
|
||||||
<MiniKnowledgeGraph
|
|
||||||
currentPost={currentPost}
|
|
||||||
relatedPosts={relatedPosts}
|
|
||||||
height="250px"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Display Hero Image */}
|
{/* Display Hero Image */}
|
||||||
{displayImage && (
|
{displayImage && (
|
||||||
<div class="blog-post-hero">
|
<div class="blog-post-hero">
|
||||||
|
@ -149,106 +137,199 @@ relatedPosts = relatedPosts.slice(0, 3);
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* Content Connections - Knowledge Graph */}
|
||||||
|
<div class="content-connections">
|
||||||
|
<h3 class="connections-title">Post Connections</h3>
|
||||||
|
<MiniKnowledgeGraph currentPost={currentPost} relatedPosts={relatedPosts} />
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Main Content Area */}
|
{/* Main Content Area */}
|
||||||
<div class="blog-post-content prose prose-invert max-w-none">
|
<div class="blog-post-content prose prose-invert max-w-none">
|
||||||
<slot /> {/* Renders the actual markdown content */}
|
<slot /> {/* Renders the actual markdown content */}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Related Posts Section */}
|
||||||
|
{relatedPosts.length > 0 && (
|
||||||
|
<div class="related-posts-section">
|
||||||
|
<h3 class="related-title">Related Content</h3>
|
||||||
|
<div class="related-posts-grid">
|
||||||
|
{relatedPosts.map((post) => (
|
||||||
|
<a href={`/posts/${post.slug}/`} class="related-post-card">
|
||||||
|
<div class="related-post-content">
|
||||||
|
<h4>{post.data.title}</h4>
|
||||||
|
<p>{post.data.description ?
|
||||||
|
(post.data.description.length > 100 ?
|
||||||
|
post.data.description.substring(0, 100) + '...' :
|
||||||
|
post.data.description) :
|
||||||
|
'Read more about this related topic.'}</p>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</article>
|
</article>
|
||||||
|
|
||||||
{/* Sidebar */}
|
{/* Sidebar */}
|
||||||
<aside class="blog-post-sidebar">
|
<aside class="blog-post-sidebar">
|
||||||
{/* Author Card Updated */}
|
{/* Author Card */}
|
||||||
<div class="sidebar-card author-card">
|
<div class="sidebar-card author-card">
|
||||||
<div class="author-avatar">
|
<div class="author-avatar">
|
||||||
<img src="/images/avatar.jpg" alt="LaForceIT Tech Blogs" />
|
<div class="avatar-placeholder">DL</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="author-info">
|
<div class="author-info">
|
||||||
<h3>LaForceIT.com Tech Blogs</h3>
|
<h3>Daniel LaForce</h3>
|
||||||
<p>For Home Labbers, Technologists & Engineers</p>
|
<p>Infrastructure & DevOps Engineer</p>
|
||||||
</div>
|
</div>
|
||||||
<p class="author-bio">
|
<p class="author-bio">
|
||||||
Exploring enterprise-grade infrastructure, automation, Kubernetes, and zero-trust networking in the home lab and beyond.
|
Exploring enterprise-grade infrastructure, automation, Kubernetes, and self-hosted solutions for the modern home lab.
|
||||||
</p>
|
</p>
|
||||||
|
<div class="author-links">
|
||||||
|
<a href="https://github.com/keyargo" target="_blank" rel="noopener noreferrer" class="author-link github">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<path d="M9 19c-5 1.5-5-2.5-7-3m14 6v-3.87a3.37 3.37 0 0 0-.94-2.61c3.14-.35 6.44-1.54 6.44-7A5.44 5.44 0 0 0 20 4.77 5.07 5.07 0 0 0 19.91 1S18.73.65 16 2.48a13.38 13.38 0 0 0-7 0C6.27.65 5.09 1 5.09 1A5.07 5.07 0 0 0 5 4.77a5.44 5.44 0 0 0-1.5 3.78c0 5.42 3.3 6.61 6.44 7A3.37 3.37 0 0 0 9 18.13V22"></path>
|
||||||
|
</svg>
|
||||||
|
GitHub
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Table of Contents Card */}
|
{/* Table of Contents Card */}
|
||||||
<div class="sidebar-card toc-card">
|
<div class="sidebar-card toc-card">
|
||||||
<h3>Table of Contents</h3>
|
<h3>Table of Contents</h3>
|
||||||
<nav class="toc-container" id="toc">
|
<nav class="toc-container" id="toc">
|
||||||
<p class="text-sm text-gray-400">Loading TOC...</p>
|
<p class="text-sm text-gray-400">Loading Table of Contents...</p>
|
||||||
</nav>
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Related Posts */}
|
|
||||||
{relatedPosts.length > 0 && (
|
|
||||||
<div class="sidebar-card related-posts-card">
|
|
||||||
<h3>Related Articles</h3>
|
|
||||||
<div class="related-posts">
|
|
||||||
{relatedPosts.map(post => (
|
|
||||||
<a href={`/posts/${post.slug}/`} class="related-post-link">
|
|
||||||
<h4>{post.data.title}</h4>
|
|
||||||
{post.data.tags && post.data.tags.length > 0 && (
|
|
||||||
<div class="related-post-tags">
|
|
||||||
{post.data.tags.slice(0, 2).map(tag => (
|
|
||||||
<span class="related-tag">{tag}</span>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</a>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</aside>
|
</aside>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Newsletter />
|
|
||||||
<Footer slot="footer" />
|
<Footer slot="footer" />
|
||||||
</BaseLayout>
|
</BaseLayout>
|
||||||
|
|
||||||
{/* Script for Table of Contents Generation (Unchanged) */}
|
|
||||||
<script>
|
<script>
|
||||||
function generateToc() {
|
// Table of Contents Generator
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
const tocContainer = document.getElementById('toc');
|
const tocContainer = document.getElementById('toc');
|
||||||
const contentArea = document.querySelector('.blog-post-content');
|
const contentArea = document.querySelector('.blog-post-content');
|
||||||
|
|
||||||
if (!tocContainer || !contentArea) return;
|
if (!tocContainer || !contentArea) return;
|
||||||
|
|
||||||
|
// Get all headings (h2, h3) from the content
|
||||||
const headings = contentArea.querySelectorAll('h2, h3');
|
const headings = contentArea.querySelectorAll('h2, h3');
|
||||||
if (headings.length > 0) {
|
|
||||||
const tocList = document.createElement('ul');
|
if (headings.length === 0) {
|
||||||
tocList.className = 'toc-list';
|
tocContainer.innerHTML = '<p class="toc-empty">No sections found in this article.</p>';
|
||||||
headings.forEach((heading) => {
|
return;
|
||||||
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') {
|
// Create the TOC list
|
||||||
document.addEventListener('DOMContentLoaded', generateToc);
|
const tocList = document.createElement('ul');
|
||||||
} else {
|
tocList.className = 'toc-list';
|
||||||
generateToc();
|
|
||||||
}
|
headings.forEach((heading, index) => {
|
||||||
|
// Add ID to heading if it doesn't have one
|
||||||
|
if (!heading.id) {
|
||||||
|
heading.id = `heading-${index}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create list item
|
||||||
|
const listItem = document.createElement('li');
|
||||||
|
listItem.className = `toc-item toc-${heading.tagName.toLowerCase()}`;
|
||||||
|
|
||||||
|
// Create link
|
||||||
|
const link = document.createElement('a');
|
||||||
|
link.href = `#${heading.id}`;
|
||||||
|
link.textContent = heading.textContent;
|
||||||
|
link.addEventListener('click', (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
document.getElementById(heading.id)?.scrollIntoView({
|
||||||
|
behavior: 'smooth',
|
||||||
|
block: 'start'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add to list
|
||||||
|
listItem.appendChild(link);
|
||||||
|
tocList.appendChild(listItem);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Replace loading message with the TOC
|
||||||
|
tocContainer.innerHTML = '';
|
||||||
|
tocContainer.appendChild(tocList);
|
||||||
|
|
||||||
|
// Add smooth scrolling for all links pointing to headings
|
||||||
|
document.querySelectorAll('a[href^="#heading-"]').forEach(anchor => {
|
||||||
|
anchor.addEventListener('click', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
const targetId = this.getAttribute('href');
|
||||||
|
document.querySelector(targetId)?.scrollIntoView({
|
||||||
|
behavior: 'smooth',
|
||||||
|
block: 'start'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style is:global>
|
||||||
|
/* Table of Contents Styles */
|
||||||
|
.toc-list {
|
||||||
|
list-style: none;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toc-item {
|
||||||
|
margin-bottom: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toc-item a {
|
||||||
|
color: var(--text-secondary);
|
||||||
|
text-decoration: none;
|
||||||
|
transition: color 0.2s ease;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toc-item a:hover {
|
||||||
|
color: var(--accent-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.toc-h3 {
|
||||||
|
padding-left: 1rem;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toc-empty {
|
||||||
|
color: var(--text-tertiary);
|
||||||
|
font-style: italic;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
.blog-post-container {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 7fr 3fr;
|
||||||
|
gap: 2rem;
|
||||||
|
max-width: 1200px;
|
||||||
|
margin: 2rem auto;
|
||||||
|
padding: 0 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blog-post {
|
||||||
|
background: var(--card-bg);
|
||||||
|
border-radius: 12px;
|
||||||
|
border: 1px solid var(--card-border);
|
||||||
|
overflow: hidden;
|
||||||
|
padding: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blog-post-header {
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
.draft-badge {
|
.draft-badge {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
margin-bottom: 1rem;
|
margin-bottom: 1rem;
|
||||||
|
@ -258,116 +339,162 @@ relatedPosts = relatedPosts.slice(0, 3);
|
||||||
font-size: 0.8rem;
|
font-size: 0.8rem;
|
||||||
border-radius: 0.25rem;
|
border-radius: 0.25rem;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
font-family: 'JetBrains Mono', monospace;
|
font-family: var(--font-mono);
|
||||||
}
|
|
||||||
.blog-post-container {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: minmax(0, 1fr) 300px;
|
|
||||||
gap: 2rem;
|
|
||||||
max-width: 1200px;
|
|
||||||
margin: 0 auto;
|
|
||||||
padding: 2rem 1rem;
|
|
||||||
}
|
|
||||||
.blog-post-header {
|
|
||||||
margin-bottom: 2.5rem;
|
|
||||||
border-bottom: 1px solid var(--card-border);
|
|
||||||
padding-bottom: 1.5rem;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.blog-post-title {
|
.blog-post-title {
|
||||||
/* Made title slightly smaller */
|
font-size: clamp(1.8rem, 4vw, 2.5rem);
|
||||||
font-size: clamp(1.8rem, 4vw, 2.5rem);
|
line-height: 1.2;
|
||||||
line-height: 1.25; /* Adjusted line height */
|
margin-bottom: 0.75rem;
|
||||||
margin-bottom: 0.75rem; /* Adjusted margin */
|
|
||||||
color: var(--text-primary);
|
color: var(--text-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.blog-post-description {
|
.blog-post-description {
|
||||||
font-size: 1.1rem;
|
font-size: 1.1rem;
|
||||||
color: var(--text-secondary);
|
color: var(--text-secondary);
|
||||||
margin-bottom: 1.5rem; /* Increased margin */
|
margin-bottom: 1.5rem;
|
||||||
max-width: 75ch; /* Adjusted width */
|
max-width: 75ch;
|
||||||
}
|
}
|
||||||
.blog-post-meta {
|
|
||||||
|
.blog-post-meta {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
gap: 0.5rem 1.5rem;
|
gap: 0.5rem 1.5rem;
|
||||||
margin-bottom: 1.5rem; /* Increased margin */
|
margin-bottom: 1.5rem;
|
||||||
font-size: 0.85rem;
|
font-size: 0.85rem;
|
||||||
color: var(--text-secondary);
|
color: var(--text-secondary);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.blog-post-category {
|
||||||
|
padding: 0.25rem 0.75rem;
|
||||||
|
background: rgba(6, 182, 212, 0.1);
|
||||||
|
border-radius: 2rem;
|
||||||
|
font-family: var(--font-mono);
|
||||||
|
}
|
||||||
|
|
||||||
.blog-post-tags {
|
.blog-post-tags {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
gap: 0.75rem;
|
gap: 0.75rem;
|
||||||
margin-top: 0rem; /* Removed top margin */
|
margin-bottom: 1.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.blog-post-tag {
|
.blog-post-tag {
|
||||||
color: var(--accent-secondary);
|
color: var(--accent-secondary);
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
font-size: 0.85rem;
|
font-size: 0.85rem;
|
||||||
transition: color 0.3s ease;
|
transition: color 0.3s ease;
|
||||||
font-family: 'JetBrains Mono', monospace;
|
font-family: var(--font-mono);
|
||||||
background-color: rgba(59, 130, 246, 0.1);
|
background-color: rgba(59, 130, 246, 0.1);
|
||||||
padding: 0.2rem 0.6rem;
|
padding: 0.2rem 0.6rem;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.blog-post-tag:hover {
|
.blog-post-tag:hover {
|
||||||
color: var(--accent-primary);
|
color: var(--accent-primary);
|
||||||
background-color: rgba(6, 182, 212, 0.15);
|
background-color: rgba(6, 182, 212, 0.15);
|
||||||
|
transform: translateY(-2px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.blog-post-hero {
|
.blog-post-hero {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin-bottom: 2.5rem;
|
margin-bottom: 2rem;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
border: 1px solid var(--card-border);
|
border: 1px solid var(--card-border);
|
||||||
background-color: var(--bg-secondary);
|
background-color: var(--bg-secondary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.blog-post-hero img {
|
.blog-post-hero img {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: auto;
|
height: auto;
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Content Connections Graph */
|
/* Content Connections - Knowledge Graph */
|
||||||
.content-connections {
|
.content-connections {
|
||||||
margin: 1.5rem 0 2rem;
|
margin: 2rem 0;
|
||||||
border-radius: 10px;
|
padding: 1.5rem;
|
||||||
border: 1px solid var(--card-border, #334155);
|
background: var(--bg-secondary);
|
||||||
background: rgba(15, 23, 42, 0.2);
|
border-radius: 8px;
|
||||||
overflow: hidden;
|
border: 1px solid var(--card-border);
|
||||||
}
|
}
|
||||||
|
|
||||||
.section-subtitle {
|
.connections-title {
|
||||||
font-size: 1.2rem;
|
font-size: 1.2rem;
|
||||||
font-weight: 600;
|
margin-bottom: 1rem;
|
||||||
color: var(--text-primary, #e2e8f0);
|
color: var(--text-primary);
|
||||||
padding: 1rem 1.5rem;
|
|
||||||
margin: 0;
|
|
||||||
background: rgba(15, 23, 42, 0.5);
|
|
||||||
border-bottom: 1px solid var(--card-border, #334155);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.blog-post-content {
|
/* Related Posts Section */
|
||||||
/* Styles inherited from prose */
|
.related-posts-section {
|
||||||
|
margin-top: 3rem;
|
||||||
|
padding-top: 2rem;
|
||||||
|
border-top: 1px solid var(--card-border);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.related-title {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
color: var(--text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.related-posts-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
||||||
|
gap: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.related-post-card {
|
||||||
|
background: var(--bg-secondary);
|
||||||
|
border-radius: 8px;
|
||||||
|
border: 1px solid var(--card-border);
|
||||||
|
padding: 1.5rem;
|
||||||
|
text-decoration: none;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.related-post-card:hover {
|
||||||
|
transform: translateY(-3px);
|
||||||
|
border-color: var(--accent-primary);
|
||||||
|
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.related-post-content h4 {
|
||||||
|
color: var(--text-primary);
|
||||||
|
font-size: 1.1rem;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.related-post-content p {
|
||||||
|
color: var(--text-secondary);
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Sidebar */
|
||||||
.blog-post-sidebar {
|
.blog-post-sidebar {
|
||||||
position: sticky;
|
position: sticky;
|
||||||
top: 2rem;
|
top: 2rem;
|
||||||
align-self: start;
|
align-self: start;
|
||||||
height: calc(100vh - 4rem);
|
height: calc(100vh - 4rem);
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar-card {
|
.sidebar-card {
|
||||||
background: var(--card-bg);
|
background: var(--card-bg);
|
||||||
|
border-radius: 12px;
|
||||||
border: 1px solid var(--card-border);
|
border: 1px solid var(--card-border);
|
||||||
border-radius: 10px;
|
|
||||||
padding: 1.5rem;
|
padding: 1.5rem;
|
||||||
margin-bottom: 1.5rem;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Author Card */
|
||||||
.author-card {
|
.author-card {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.author-avatar {
|
.author-avatar {
|
||||||
width: 80px;
|
width: 80px;
|
||||||
height: 80px;
|
height: 80px;
|
||||||
|
@ -375,117 +502,94 @@ relatedPosts = relatedPosts.slice(0, 3);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
margin: 0 auto 1rem;
|
margin: 0 auto 1rem;
|
||||||
border: 2px solid var(--accent-primary);
|
border: 2px solid var(--accent-primary);
|
||||||
background-color: var(--bg-secondary);
|
background-color: var(--bg-secondary);
|
||||||
}
|
}
|
||||||
.author-avatar img {
|
|
||||||
|
.avatar-placeholder {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
object-fit: cover;
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 1.5rem;
|
||||||
|
font-weight: bold;
|
||||||
|
color: var(--accent-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.author-info h3 {
|
.author-info h3 {
|
||||||
margin-bottom: 0.25rem;
|
margin-bottom: 0.25rem;
|
||||||
color: var(--text-primary);
|
color: var(--text-primary);
|
||||||
font-size: 1.1rem;
|
font-size: 1.2rem;
|
||||||
}
|
}
|
||||||
.author-info p { /* Target the subtitle */
|
|
||||||
|
.author-info p {
|
||||||
color: var(--text-secondary);
|
color: var(--text-secondary);
|
||||||
margin-bottom: 1rem;
|
margin-bottom: 1rem;
|
||||||
font-size: 0.9rem;
|
font-size: 0.9rem;
|
||||||
}
|
}
|
||||||
.author-bio { /* Target the main bio */
|
|
||||||
|
.author-bio {
|
||||||
font-size: 0.9rem;
|
font-size: 0.9rem;
|
||||||
margin-bottom: 0; /* Remove bottom margin */
|
margin-bottom: 1.5rem;
|
||||||
color: var(--text-secondary);
|
color: var(--text-secondary);
|
||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
/* Social links removed */
|
|
||||||
|
|
||||||
|
.author-links {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.author-link {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
background: rgba(226, 232, 240, 0.05);
|
||||||
|
border-radius: 8px;
|
||||||
|
color: var(--text-primary);
|
||||||
|
text-decoration: none;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.author-link:hover {
|
||||||
|
background: rgba(226, 232, 240, 0.1);
|
||||||
|
transform: translateY(-2px);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Table of Contents */
|
||||||
.toc-card h3 {
|
.toc-card h3 {
|
||||||
margin-bottom: 1rem;
|
margin-bottom: 1rem;
|
||||||
color: var(--text-primary);
|
color: var(--text-primary);
|
||||||
}
|
}
|
||||||
.toc-list {
|
|
||||||
list-style: none;
|
.toc-container {
|
||||||
padding: 0;
|
max-height: 500px;
|
||||||
margin: 0;
|
|
||||||
max-height: 60vh;
|
|
||||||
overflow-y: auto;
|
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Related Posts */
|
|
||||||
.related-posts-card h3 {
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
color: var(--text-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.related-posts {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.related-post-link {
|
|
||||||
display: block;
|
|
||||||
padding: 0.75rem;
|
|
||||||
border-radius: 8px;
|
|
||||||
border: 1px solid var(--border-primary);
|
|
||||||
background: rgba(255, 255, 255, 0.03);
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.related-post-link:hover {
|
|
||||||
background: rgba(6, 182, 212, 0.05);
|
|
||||||
border-color: var(--accent-primary);
|
|
||||||
transform: translateY(-2px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.related-post-link h4 {
|
|
||||||
margin: 0 0 0.5rem;
|
|
||||||
font-size: 0.95rem;
|
|
||||||
color: var(--text-primary);
|
|
||||||
line-height: 1.3;
|
|
||||||
}
|
|
||||||
|
|
||||||
.related-post-tags {
|
|
||||||
display: flex;
|
|
||||||
gap: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.related-tag {
|
|
||||||
font-size: 0.7rem;
|
|
||||||
padding: 0.1rem 0.4rem;
|
|
||||||
border-radius: 3px;
|
|
||||||
background: rgba(16, 185, 129, 0.1);
|
|
||||||
color: var(--text-secondary);
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 1024px) {
|
@media (max-width: 1024px) {
|
||||||
.blog-post-container {
|
.blog-post-container {
|
||||||
grid-template-columns: 1fr; /* Stack on smaller screens */
|
grid-template-columns: 1fr;
|
||||||
}
|
}
|
||||||
|
|
||||||
.blog-post-sidebar {
|
.blog-post-sidebar {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.blog-post-title {
|
||||||
|
font-size: 1.8rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.related-posts-grid {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blog-post {
|
||||||
|
padding: 1.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
Loading…
Reference in New Issue