feat: Implement theme toggler and update related components
This commit is contained in:
parent
b392c30aba
commit
e66e605479
|
@ -7,7 +7,7 @@ import tailwind from '@astrojs/tailwind';
|
|||
|
||||
// https://astro.build/config
|
||||
export default defineConfig({
|
||||
site: 'https://laforceit.blog',
|
||||
site: 'https://laforceit-blog.pages.dev', // Your current Cloudflare site
|
||||
output: 'static',
|
||||
// adapter: cloudflare(), // Commented out for local development
|
||||
integrations: [
|
||||
|
@ -17,10 +17,14 @@ export default defineConfig({
|
|||
],
|
||||
markdown: {
|
||||
shikiConfig: {
|
||||
theme: 'dracula',
|
||||
theme: 'one-dark-pro',
|
||||
wrap: true
|
||||
},
|
||||
remarkPlugins: [],
|
||||
rehypePlugins: []
|
||||
},
|
||||
compressHTML: false, // Disable HTML compression to avoid parsing errors
|
||||
build: {
|
||||
format: 'file', // Use 'file' instead of 'directory' format
|
||||
}
|
||||
});
|
|
@ -379,6 +379,70 @@ const navItems = [
|
|||
</style>
|
||||
|
||||
<script>
|
||||
|
||||
// Handle mobile menu toggle
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const menuBtn = document.getElementById('mobile-menu-btn');
|
||||
const mainNav = document.querySelector('.main-nav');
|
||||
const header = document.querySelector('.site-header');
|
||||
|
||||
if (menuBtn && mainNav) {
|
||||
menuBtn.addEventListener('click', () => {
|
||||
mainNav.classList.toggle('active');
|
||||
menuBtn.classList.toggle('mobile-menu-active');
|
||||
});
|
||||
}
|
||||
|
||||
// Header scroll effect
|
||||
window.addEventListener('scroll', () => {
|
||||
if (window.scrollY > 50) {
|
||||
header?.classList.add('scrolled');
|
||||
} else {
|
||||
header?.classList.remove('scrolled');
|
||||
}
|
||||
});
|
||||
|
||||
// Theme toggle functionality
|
||||
const themeToggle = document.getElementById('theme-toggle');
|
||||
|
||||
if (themeToggle) {
|
||||
themeToggle.addEventListener('click', () => {
|
||||
document.documentElement.classList.toggle('light-mode');
|
||||
|
||||
// Store preference in localStorage
|
||||
const isLightMode = document.documentElement.classList.contains('light-mode');
|
||||
localStorage.setItem('theme', isLightMode ? 'light' : 'dark');
|
||||
});
|
||||
}
|
||||
|
||||
// Add interactive network nodes animation
|
||||
const header_el = document.querySelector('.site-header');
|
||||
|
||||
if (header_el) {
|
||||
// Create animated nodes
|
||||
for (let i = 0; i < 5; i++) {
|
||||
const node = document.createElement('div');
|
||||
node.className = 'nav-node';
|
||||
node.style.left = `${Math.random() * 100}%`;
|
||||
node.style.animationDelay = `${Math.random() * 5}s`;
|
||||
node.style.animationDuration = `${5 + Math.random() * 5}s`;
|
||||
header_el.appendChild(node);
|
||||
}
|
||||
}
|
||||
|
||||
// Theme toggle functionality
|
||||
const themeToggle = document.getElementById('theme-toggle');
|
||||
|
||||
if (themeToggle) {
|
||||
themeToggle.addEventListener('click', () => {
|
||||
document.documentElement.classList.toggle('light-mode');
|
||||
|
||||
// Store preference in localStorage
|
||||
const isLightMode = document.documentElement.classList.contains('light-mode');
|
||||
localStorage.setItem('theme', isLightMode ? 'light' : 'dark');
|
||||
});
|
||||
}
|
||||
|
||||
// Handle mobile menu toggle
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const menuBtn = document.getElementById('mobile-menu-btn');
|
||||
|
|
|
@ -0,0 +1,93 @@
|
|||
---
|
||||
// ThemeToggler.astro
|
||||
// A component to toggle between light and dark themes
|
||||
---
|
||||
|
||||
<button id="theme-toggle" aria-label="Toggle dark mode" class="theme-toggle">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="sun-icon">
|
||||
<circle cx="12" cy="12" r="5"></circle>
|
||||
<line x1="12" y1="1" x2="12" y2="3"></line>
|
||||
<line x1="12" y1="21" x2="12" y2="23"></line>
|
||||
<line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line>
|
||||
<line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line>
|
||||
<line x1="1" y1="12" x2="3" y2="12"></line>
|
||||
<line x1="21" y1="12" x2="23" y2="12"></line>
|
||||
<line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line>
|
||||
<line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line>
|
||||
</svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="moon-icon">
|
||||
<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<style>
|
||||
.theme-toggle {
|
||||
background: none;
|
||||
border: none;
|
||||
padding: 0.25rem;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: var(--text-secondary);
|
||||
cursor: pointer;
|
||||
transition: color 0.3s ease, background-color 0.3s ease;
|
||||
position: relative;
|
||||
width: 34px;
|
||||
height: 34px;
|
||||
}
|
||||
|
||||
.theme-toggle:hover {
|
||||
color: var(--text-primary);
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.sun-icon, .moon-icon {
|
||||
position: absolute;
|
||||
transition: transform 0.5s ease, opacity 0.5s ease;
|
||||
}
|
||||
|
||||
html:not(.dark) .sun-icon {
|
||||
opacity: 1;
|
||||
transform: rotate(0);
|
||||
}
|
||||
|
||||
html:not(.dark) .moon-icon {
|
||||
opacity: 0;
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
html.dark .sun-icon {
|
||||
opacity: 0;
|
||||
transform: rotate(-90deg);
|
||||
}
|
||||
|
||||
html.dark .moon-icon {
|
||||
opacity: 1;
|
||||
transform: rotate(0);
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
// Theme toggling logic
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const themeToggle = document.getElementById('theme-toggle');
|
||||
|
||||
// Function to set theme
|
||||
const setTheme = (isDark) => {
|
||||
if (isDark) {
|
||||
document.documentElement.classList.add('dark');
|
||||
localStorage.setItem('theme', 'dark');
|
||||
} else {
|
||||
document.documentElement.classList.remove('dark');
|
||||
localStorage.setItem('theme', 'light');
|
||||
}
|
||||
};
|
||||
|
||||
// Theme toggle click handler
|
||||
themeToggle?.addEventListener('click', () => {
|
||||
const isDark = document.documentElement.classList.contains('dark');
|
||||
setTheme(!isDark);
|
||||
});
|
||||
});
|
||||
</script>
|
|
@ -23,6 +23,18 @@ const {
|
|||
<title>{title}</title>
|
||||
<meta name="description" content={description} />
|
||||
|
||||
<!-- Theme initialization - Must be inline -->
|
||||
<script is:inline>
|
||||
// Initialize theme before page loads to prevent flash
|
||||
const savedTheme = localStorage.getItem('theme');
|
||||
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
|
||||
if (savedTheme === 'light' || (!savedTheme && !prefersDark)) {
|
||||
document.documentElement.classList.add('light-mode');
|
||||
} else {
|
||||
document.documentElement.classList.remove('light-mode');
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- OpenGraph/Social Media Meta Tags -->
|
||||
<meta property="og:title" content={title} />
|
||||
<meta property="og:description" content={description} />
|
||||
|
@ -44,6 +56,9 @@ const {
|
|||
<!-- Favicon -->
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||
|
||||
<!-- Theme CSS -->
|
||||
<link rel="stylesheet" href="/styles/theme.css" />
|
||||
|
||||
<!-- Cytoscape Library for Knowledge Graph -->
|
||||
<script src="https://unpkg.com/cytoscape@3.25.0/dist/cytoscape.min.js" is:inline></script>
|
||||
|
||||
|
|
|
@ -1,255 +1,246 @@
|
|||
import { getCollection } from 'astro:content';
|
||||
---
|
||||
// src/pages/tag/[tag].astro
|
||||
// Dynamic route for tag pages
|
||||
|
||||
import BaseLayout from '../../layouts/BaseLayout.astro';
|
||||
import { getCollection } from 'astro:content';
|
||||
|
||||
export async function getStaticPaths() {
|
||||
const allPosts = await getCollection('posts');
|
||||
|
||||
// Get all unique tags from all posts
|
||||
const uniqueTags = [...new Set(allPosts.flatMap(post => post.data.tags || []))];
|
||||
|
||||
// Create a page for each tag
|
||||
return uniqueTags.map(tag => {
|
||||
// Filter posts that have this tag
|
||||
const filteredPosts = allPosts.filter(post =>
|
||||
post.data.tags && post.data.tags.includes(tag)
|
||||
);
|
||||
|
||||
const allPosts = await getCollection('blog');
|
||||
const uniqueTags = [...new Set(allPosts.map((post) => post.data.tags).flat())];
|
||||
|
||||
return uniqueTags.map((tag) => {
|
||||
const filteredPosts = allPosts.filter((post) => post.data.tags.includes(tag));
|
||||
return {
|
||||
params: { tag },
|
||||
props: { posts: filteredPosts, tag },
|
||||
props: { posts: filteredPosts },
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
const { posts, tag } = Astro.props;
|
||||
const { tag } = Astro.params;
|
||||
const { posts } = Astro.props;
|
||||
|
||||
// Sort posts by date
|
||||
// Format date
|
||||
const formatDate = (dateStr) => {
|
||||
const date = new Date(dateStr);
|
||||
return date.toLocaleDateString('en-US', { year: 'numeric', month: 'short', day: 'numeric' });
|
||||
};
|
||||
|
||||
// Sort posts by date (newest first)
|
||||
const sortedPosts = posts.sort((a, b) => {
|
||||
const dateA = a.data.pubDate ? new Date(a.data.pubDate) : new Date(0);
|
||||
const dateB = b.data.pubDate ? new Date(b.data.pubDate) : new Date(0);
|
||||
const dateA = new Date(a.data.pubDate);
|
||||
const dateB = new Date(b.data.pubDate);
|
||||
return dateB.getTime() - dateA.getTime();
|
||||
});
|
||||
---
|
||||
|
||||
<BaseLayout title={`Posts tagged with "${tag}" | LaForce IT Blog`} description={`Articles and guides related to ${tag}`}>
|
||||
<main class="container">
|
||||
<section class="tag-header">
|
||||
<h1 class="tag-title">Posts tagged with <span>#{tag}</span></h1>
|
||||
<p class="tag-description">
|
||||
Browse all {sortedPosts.length} articles related to this topic
|
||||
</p>
|
||||
<a href="/tags" class="tag-link">View all tags</a>
|
||||
</section>
|
||||
<div class="container tag-page">
|
||||
<header class="tag-hero">
|
||||
<h1>Posts tagged with <span class="tag-highlight">{tag}</span></h1>
|
||||
<p>Explore {sortedPosts.length} {sortedPosts.length === 1 ? 'article' : 'articles'} related to {tag}</p>
|
||||
</header>
|
||||
|
||||
<div class="blog-grid">
|
||||
<div class="posts-grid">
|
||||
{sortedPosts.map((post) => (
|
||||
<article class="post-card">
|
||||
{/* Temporarily removed conditional image rendering for debugging */}
|
||||
<!-- Simplified image rendering that works reliably -->
|
||||
<img
|
||||
width={720}
|
||||
height={360}
|
||||
src="/images/placeholders/default.jpg"
|
||||
src={post.data.heroImage || "/images/placeholders/default.jpg"}
|
||||
alt=""
|
||||
class="post-image"
|
||||
/>
|
||||
<div class="post-content">
|
||||
<div class="post-meta">
|
||||
<time datetime={post.data.pubDate ? new Date(post.data.pubDate).toISOString() : ''}>
|
||||
{post.data.pubDate ? new Date(post.data.pubDate).toLocaleDateString('en-us', {
|
||||
year: 'numeric',
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
}) : 'No date'}
|
||||
</time>
|
||||
{post.data.category && (
|
||||
<span class="post-category">
|
||||
{post.data.category}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<h3 class="post-title">
|
||||
<time datetime={post.data.pubDate}>{formatDate(post.data.pubDate)}</time>
|
||||
<h2 class="post-title">
|
||||
<a href={`/posts/${post.slug}/`}>{post.data.title}</a>
|
||||
{post.data.draft && <span class="draft-badge">Draft</span>}
|
||||
</h3>
|
||||
</h2>
|
||||
<p class="post-excerpt">{post.data.description}</p>
|
||||
<div class="post-footer">
|
||||
<span class="post-read-time">{post.data.readTime || '5 min read'}</span>
|
||||
<a href={`/posts/${post.slug}/`} class="read-more">Read More</a>
|
||||
<div class="post-meta">
|
||||
<span class="reading-time">{post.data.minutesRead || '5 min'} read</span>
|
||||
<ul class="post-tags">
|
||||
{post.data.tags.map((tagName) => (
|
||||
<li>
|
||||
<a href={`/tag/${tagName}`} class={tagName === tag ? 'current-tag' : ''}>
|
||||
{tagName}
|
||||
</a>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
))}
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<a href="/tags" class="all-tags-link">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<line x1="19" y1="12" x2="5" y2="12"></line>
|
||||
<polyline points="12 19 5 12 12 5"></polyline>
|
||||
</svg>
|
||||
View all tags
|
||||
</a>
|
||||
</div>
|
||||
</BaseLayout>
|
||||
|
||||
<style>
|
||||
.tag-header {
|
||||
margin: 3rem 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.tag-title {
|
||||
font-size: clamp(1.8rem, 4vw, 2.5rem);
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.tag-title span {
|
||||
background: linear-gradient(90deg, var(--accent-primary), var(--accent-tertiary));
|
||||
-webkit-background-clip: text;
|
||||
background-clip: text;
|
||||
color: transparent;
|
||||
font-family: 'JetBrains Mono', monospace;
|
||||
}
|
||||
|
||||
.tag-description {
|
||||
color: var(--text-secondary);
|
||||
font-size: 1.1rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.tag-link {
|
||||
display: inline-block;
|
||||
margin-top: 1rem;
|
||||
color: var(--accent-primary);
|
||||
text-decoration: none;
|
||||
font-family: 'JetBrains Mono', monospace;
|
||||
font-size: 0.9rem;
|
||||
border-bottom: 1px dashed var(--accent-primary);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.tag-link:hover {
|
||||
border-bottom: 1px solid var(--accent-primary);
|
||||
}
|
||||
|
||||
.draft-badge {
|
||||
display: inline-block;
|
||||
margin-left: 0.5rem;
|
||||
padding: 0.25rem 0.5rem;
|
||||
background-color: rgba(226, 232, 240, 0.2);
|
||||
color: #94a3b8;
|
||||
font-size: 0.75rem;
|
||||
border-radius: 0.25rem;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
/* Include styles from blog index if needed, like .blog-grid, .post-card etc. */
|
||||
.blog-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
|
||||
gap: 2rem;
|
||||
margin: 2rem 0 4rem;
|
||||
}
|
||||
|
||||
.post-card {
|
||||
background: var(--card-bg);
|
||||
border-radius: 10px;
|
||||
border: 1px solid var(--card-border);
|
||||
overflow: hidden;
|
||||
transition: all 0.3s ease;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.post-card:hover {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: 0 10px 30px rgba(6, 182, 212, 0.1);
|
||||
border-color: rgba(56, 189, 248, 0.4);
|
||||
}
|
||||
|
||||
.post-card::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: linear-gradient(135deg, rgba(6, 182, 212, 0.05), rgba(139, 92, 246, 0.05));
|
||||
z-index: -1;
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s ease;
|
||||
}
|
||||
|
||||
.post-card:hover::before {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.post-image {
|
||||
width: 100%;
|
||||
height: 200px;
|
||||
object-fit: cover;
|
||||
border-bottom: 1px solid var(--card-border);
|
||||
}
|
||||
|
||||
.post-content {
|
||||
padding: 1.5rem;
|
||||
}
|
||||
|
||||
.post-meta {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 1rem;
|
||||
font-size: 0.85rem;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.post-category {
|
||||
background: rgba(6, 182, 212, 0.1);
|
||||
color: var(--accent-primary);
|
||||
padding: 0.25rem 0.5rem;
|
||||
border-radius: 4px;
|
||||
font-family: 'JetBrains Mono', monospace;
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
|
||||
.post-title {
|
||||
font-size: 1.25rem;
|
||||
margin-bottom: 0.75rem;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.post-title a {
|
||||
color: var(--text-primary);
|
||||
text-decoration: none;
|
||||
transition: color 0.3s ease;
|
||||
}
|
||||
|
||||
.post-title a:hover {
|
||||
color: var(--accent-primary);
|
||||
}
|
||||
|
||||
.post-excerpt {
|
||||
color: var(--text-secondary);
|
||||
font-size: 0.9rem;
|
||||
margin-bottom: 1.5rem;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 3;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.post-footer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
color: var(--text-secondary);
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.read-more {
|
||||
color: var(--accent-primary);
|
||||
text-decoration: none;
|
||||
font-weight: 500;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.25rem;
|
||||
transition: color 0.3s ease;
|
||||
}
|
||||
|
||||
.read-more:hover {
|
||||
color: var(--accent-secondary);
|
||||
}
|
||||
|
||||
.read-more::after {
|
||||
content: '→';
|
||||
}
|
||||
</style>
|
||||
.tag-page {
|
||||
padding-top: 2rem;
|
||||
padding-bottom: 4rem;
|
||||
}
|
||||
|
||||
.tag-hero {
|
||||
text-align: center;
|
||||
margin-bottom: 3rem;
|
||||
animation: fadeIn 0.5s ease-out;
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from { opacity: 0; transform: translateY(10px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
|
||||
.tag-hero h1 {
|
||||
font-size: var(--font-size-3xl);
|
||||
margin-bottom: 0.5rem;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.tag-highlight {
|
||||
background: linear-gradient(90deg, var(--accent-primary), var(--accent-secondary));
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
color: transparent;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.tag-hero p {
|
||||
color: var(--text-secondary);
|
||||
font-size: var(--font-size-lg);
|
||||
}
|
||||
|
||||
.posts-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
||||
gap: 2rem;
|
||||
margin-bottom: 3rem;
|
||||
}
|
||||
|
||||
.post-card {
|
||||
background: var(--card-bg);
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
border: 1px solid var(--border-primary);
|
||||
transition: transform 0.3s ease, box-shadow 0.3s ease;
|
||||
animation: fadeIn 0.5s ease-out forwards;
|
||||
animation-delay: calc(var(--animation-order, 0) * 0.1s);
|
||||
opacity: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.post-card:hover {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
|
||||
border-color: var(--accent-primary);
|
||||
}
|
||||
|
||||
.post-image {
|
||||
width: 100%;
|
||||
height: 200px;
|
||||
object-fit: cover;
|
||||
border-bottom: 1px solid var(--border-primary);
|
||||
}
|
||||
|
||||
.post-content {
|
||||
padding: 1.5rem;
|
||||
flex-grow: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.post-content time {
|
||||
color: var(--text-tertiary);
|
||||
font-size: var(--font-size-sm);
|
||||
font-family: var(--font-mono);
|
||||
}
|
||||
|
||||
.post-title {
|
||||
font-size: var(--font-size-xl);
|
||||
margin: 0.5rem 0 1rem;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.post-title a {
|
||||
color: var(--text-primary);
|
||||
text-decoration: none;
|
||||
transition: color 0.2s ease;
|
||||
}
|
||||
|
||||
.post-title a:hover {
|
||||
color: var(--accent-primary);
|
||||
}
|
||||
|
||||
.post-excerpt {
|
||||
color: var(--text-secondary);
|
||||
font-size: var(--font-size-md);
|
||||
margin-bottom: 1.5rem;
|
||||
line-height: 1.6;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.post-meta {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.75rem;
|
||||
margin-top: auto;
|
||||
}
|
||||
|
||||
.reading-time {
|
||||
color: var(--text-tertiary);
|
||||
font-size: var(--font-size-sm);
|
||||
font-family: var(--font-mono);
|
||||
}
|
||||
|
||||
.post-tags {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.5rem;
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.post-tags li a {
|
||||
display: block;
|
||||
padding: 0.25rem 0.75rem;
|
||||
background: rgba(56, 189, 248, 0.1);
|
||||
border-radius: 20px;
|
||||
color: var(--accent-primary);
|
||||
font-size: var(--font-size-xs);
|
||||
text-decoration: none;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.post-tags li a:hover {
|
||||
background: rgba(56, 189, 248, 0.2);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.post-tags li a.current-tag {
|
||||
background: var(--accent-primary);
|
||||
color: var(--bg-primary);
|
||||
}
|
||||
|
||||
.all-tags-link {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
margin: 0 auto;
|
||||
padding: 0.75rem 1.5rem;
|
||||
background: var(--bg-secondary);
|
||||
border: 1px solid var(--border-primary);
|
||||
border-radius: 30px;
|
|
@ -0,0 +1,82 @@
|
|||
/* Theme Variables - Dark/Light Mode Support */
|
||||
|
||||
/* Dark theme (default) */
|
||||
html {
|
||||
/* Keep the default dark theme as defined in BaseLayout */
|
||||
}
|
||||
|
||||
/* Light theme */
|
||||
html.light-mode {
|
||||
/* Primary Colors */
|
||||
--bg-primary: #f8fafc;
|
||||
--bg-secondary: #f1f5f9;
|
||||
--bg-tertiary: #e2e8f0;
|
||||
--bg-code: #f1f5f9;
|
||||
--text-primary: #0f172a;
|
||||
--text-secondary: #334155;
|
||||
--text-tertiary: #64748b;
|
||||
|
||||
/* Accent Colors remain the same for brand consistency */
|
||||
|
||||
/* Glow Effects - lighter for light mode */
|
||||
--glow-primary: rgba(6, 182, 212, 0.1);
|
||||
--glow-secondary: rgba(59, 130, 246, 0.1);
|
||||
--glow-tertiary: rgba(139, 92, 246, 0.1);
|
||||
|
||||
/* Border Colors */
|
||||
--border-primary: rgba(0, 0, 0, 0.1);
|
||||
--border-secondary: rgba(0, 0, 0, 0.05);
|
||||
|
||||
/* Card Background */
|
||||
--card-bg: rgba(255, 255, 255, 0.8);
|
||||
--card-border: rgba(56, 189, 248, 0.3); /* Slightly stronger border */
|
||||
|
||||
/* UI Element Colors */
|
||||
--ui-element: #e2e8f0;
|
||||
--ui-element-hover: #cbd5e1;
|
||||
}
|
||||
|
||||
/* Background adjustments for light mode */
|
||||
html.light-mode body {
|
||||
background-image:
|
||||
radial-gradient(circle at 20% 35%, rgba(6, 182, 212, 0.05) 0%, transparent 50%),
|
||||
radial-gradient(circle at 75% 15%, rgba(59, 130, 246, 0.05) 0%, transparent 45%),
|
||||
radial-gradient(circle at 85% 70%, rgba(139, 92, 246, 0.05) 0%, transparent 40%);
|
||||
}
|
||||
|
||||
/* Adding light mode grid overlay */
|
||||
html.light-mode body::before {
|
||||
background-image:
|
||||
linear-gradient(rgba(15, 23, 42, 0.03) 1px, transparent 1px),
|
||||
linear-gradient(90deg, rgba(15, 23, 42, 0.03) 1px, transparent 1px);
|
||||
}
|
||||
|
||||
/* Theme transition for smooth switching */
|
||||
html, body, * {
|
||||
transition:
|
||||
background-color 0.3s ease,
|
||||
color 0.3s ease,
|
||||
border-color 0.3s ease,
|
||||
box-shadow 0.3s ease;
|
||||
}
|
||||
|
||||
/* Knowledge Graph light mode adjustments */
|
||||
html.light-mode .graph-container {
|
||||
background: rgba(248, 250, 252, 0.6);
|
||||
}
|
||||
|
||||
html.light-mode .graph-loading {
|
||||
background: rgba(241, 245, 249, 0.7);
|
||||
}
|
||||
|
||||
html.light-mode .graph-filters {
|
||||
background: rgba(241, 245, 249, 0.7);
|
||||
}
|
||||
|
||||
html.light-mode .graph-legend {
|
||||
background: rgba(241, 245, 249, 0.7);
|
||||
}
|
||||
|
||||
html.light-mode .node-details {
|
||||
background: rgba(248, 250, 252, 0.9);
|
||||
}
|
Loading…
Reference in New Issue