upgrade to different layout
This commit is contained in:
18
src/layouts/Base.astro
Normal file
18
src/layouts/Base.astro
Normal file
@@ -0,0 +1,18 @@
|
||||
---
|
||||
import Layout from './Layout.astro';
|
||||
|
||||
import directus from "../../lib/directus"
|
||||
import { readSingleton } from "@directus/sdk";
|
||||
|
||||
const global = await directus.request(readSingleton("global"));
|
||||
|
||||
export interface Props {
|
||||
title: string;
|
||||
description?: string;
|
||||
}
|
||||
|
||||
---
|
||||
|
||||
<Layout title={global.title} description={global.description}>
|
||||
<slot />
|
||||
</Layout>
|
@@ -1,113 +1,61 @@
|
||||
---
|
||||
import MainHead from '../components/MainHead.astro';
|
||||
import Nav from '../components/Nav.astro';
|
||||
import Footer from '../components/Footer.astro';
|
||||
import Layout from './Layout.astro';
|
||||
|
||||
interface Props {
|
||||
title?: string | undefined;
|
||||
description?: string | undefined;
|
||||
import directus from "../../lib/directus"
|
||||
import { readSingleton } from "@directus/sdk";
|
||||
|
||||
const global = await directus.request(readSingleton("global"));
|
||||
|
||||
export interface Props {
|
||||
title: string;
|
||||
description?: string;
|
||||
}
|
||||
|
||||
const { title, description } = Astro.props;
|
||||
---
|
||||
|
||||
<html lang="en">
|
||||
<head>
|
||||
<MainHead title={title} description={description} />
|
||||
</head>
|
||||
<body>
|
||||
<div class="stack backgrounds">
|
||||
<Nav />
|
||||
<slot />
|
||||
<Footer />
|
||||
</div>
|
||||
<Layout title={global.title} description={global.description}>
|
||||
<slot />
|
||||
</Layout>
|
||||
|
||||
<script>
|
||||
addEventListener('load', () => document.documentElement.classList.add('loaded'));
|
||||
</script>
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const themeToggle = document.getElementById('theme-toggle');
|
||||
|
||||
<style>
|
||||
:root {
|
||||
--_placeholder-bg: linear-gradient(transparent, transparent);
|
||||
--bg-image-main: url('/assets/backgrounds/bg-main-light-800w.jpg');
|
||||
--bg-image-main-curves: url('/assets/backgrounds/bg-main-light.svg');
|
||||
--bg-image-subtle-1: var(--_placeholder-bg);
|
||||
--bg-image-subtle-2: var(--_placeholder-bg);
|
||||
--bg-image-footer: var(--_placeholder-bg);
|
||||
--bg-svg-blend-mode: overlay;
|
||||
--bg-blend-mode: darken;
|
||||
--bg-image-aspect-ratio: 2.25;
|
||||
--bg-scale: 1.68;
|
||||
--bg-aspect-ratio: calc(var(--bg-image-aspect-ratio) / var(--bg-scale));
|
||||
--bg-gradient-size: calc(var(--bg-scale) * 100%);
|
||||
}
|
||||
if (themeToggle) {
|
||||
themeToggle.addEventListener('click', () => {
|
||||
document.documentElement.classList.add('theme-switching');
|
||||
|
||||
:root.theme-dark {
|
||||
--bg-image-main: url('/assets/backgrounds/bg-main-dark-800w.jpg');
|
||||
--bg-image-main-curves: url('/assets/backgrounds/bg-main-dark.svg');
|
||||
--bg-svg-blend-mode: darken;
|
||||
--bg-blend-mode: lighten;
|
||||
}
|
||||
const rippleElements = document.querySelectorAll('.theme-ripple');
|
||||
rippleElements.forEach(el => {
|
||||
el.classList.add('ripple-active');
|
||||
setTimeout(() => {
|
||||
el.classList.remove('ripple-active');
|
||||
}, 600);
|
||||
});
|
||||
|
||||
:root.loaded {
|
||||
--bg-image-subtle-1: url('/assets/backgrounds/bg-subtle-1-light-800w.jpg');
|
||||
--bg-image-subtle-2: url('/assets/backgrounds/bg-subtle-2-light-800w.jpg');
|
||||
--bg-image-footer: url('/assets/backgrounds/bg-footer-light-800w.jpg');
|
||||
}
|
||||
const event = new CustomEvent('themeChange', {
|
||||
detail: {
|
||||
theme: document.documentElement.classList.contains('dark') ? 'dark' : 'light'
|
||||
}
|
||||
});
|
||||
document.dispatchEvent(event);
|
||||
|
||||
:root.loaded.theme-dark {
|
||||
--bg-image-subtle-1: url('/assets/backgrounds/bg-subtle-1-dark-800w.jpg');
|
||||
--bg-image-subtle-2: url('/assets/backgrounds/bg-subtle-2-dark-800w.jpg');
|
||||
--bg-image-footer: url('/assets/backgrounds/bg-footer-dark-800w.jpg');
|
||||
}
|
||||
setTimeout(() => {
|
||||
document.documentElement.classList.remove('theme-switching');
|
||||
}, 600);
|
||||
});
|
||||
}
|
||||
|
||||
@media (min-width: 50em) {
|
||||
:root {
|
||||
--bg-scale: 1;
|
||||
--bg-image-main: url('/assets/backgrounds/bg-main-light-1440w.jpg');
|
||||
}
|
||||
const socialLinks = document.querySelectorAll('.social-link');
|
||||
socialLinks.forEach(link => {
|
||||
|
||||
:root.theme-dark {
|
||||
--bg-image-main: url('/assets/backgrounds/bg-main-dark-1440w.jpg');
|
||||
}
|
||||
link.addEventListener('mouseenter', () => {
|
||||
link.classList.add('hover-active');
|
||||
});
|
||||
|
||||
:root.loaded {
|
||||
--bg-image-subtle-1: url('/assets/backgrounds/bg-subtle-1-light-1440w.jpg');
|
||||
--bg-image-subtle-2: url('/assets/backgrounds/bg-subtle-2-light-1440w.jpg');
|
||||
--bg-image-footer: url('/assets/backgrounds/bg-footer-light-1440w.jpg');
|
||||
}
|
||||
|
||||
:root.loaded.theme-dark {
|
||||
--bg-image-subtle-1: url('/assets/backgrounds/bg-subtle-1-dark-1440w.jpg');
|
||||
--bg-image-subtle-2: url('/assets/backgrounds/bg-subtle-2-dark-1440w.jpg');
|
||||
--bg-image-footer: url('/assets/backgrounds/bg-footer-dark-1440w.jpg');
|
||||
}
|
||||
}
|
||||
|
||||
.backgrounds {
|
||||
min-height: 100%;
|
||||
isolation: isolate;
|
||||
background:
|
||||
url('/assets/backgrounds/noise.png') top center/220px repeat,
|
||||
var(--bg-image-footer) bottom center/var(--bg-gradient-size) no-repeat,
|
||||
var(--bg-image-main-curves) top center/var(--bg-gradient-size) no-repeat,
|
||||
var(--bg-image-main) top center/var(--bg-gradient-size) no-repeat,
|
||||
var(--gray-999);
|
||||
background-blend-mode:
|
||||
overlay,
|
||||
var(--bg-blend-mode),
|
||||
var(--bg-svg-blend-mode),
|
||||
normal,
|
||||
normal;
|
||||
}
|
||||
|
||||
@media (forced-colors: active) {
|
||||
.backgrounds {
|
||||
background: none;
|
||||
background-blend-mode: none;
|
||||
--bg-gradient-size: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</body>
|
||||
</html>
|
||||
link.addEventListener('mouseleave', () => {
|
||||
link.classList.remove('hover-active');
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
381
src/layouts/BlogPost.astro
Normal file
381
src/layouts/BlogPost.astro
Normal file
@@ -0,0 +1,381 @@
|
||||
---
|
||||
import Layout from './Layout.astro';
|
||||
import FormattedDate from '../components/FormattedDate.astro';
|
||||
import ShareButtons from '../components/ShareButtons.astro';
|
||||
import TagList from '../components/TagList.astro';
|
||||
import './styles/markdown.css';
|
||||
|
||||
import directus from "../../lib/directus"
|
||||
import { readItems } from "@directus/sdk";
|
||||
|
||||
export async function getStaticPaths() {
|
||||
const posts = await directus.request(readItems("posts", {
|
||||
fields: ['*'],
|
||||
}));
|
||||
return posts.map((post) => ({ params: { slug: post.slug }, props: post }));
|
||||
}
|
||||
|
||||
const post = Astro.props;
|
||||
const published_date: string = post.published_date.toLocaleString();
|
||||
|
||||
let canonicalURL;
|
||||
try {
|
||||
canonicalURL = new URL(Astro.url.pathname, Astro.site || process.env.SITE_URL);
|
||||
} catch (error) {
|
||||
console.error('Error creating canonical URL:', error);
|
||||
canonicalURL = new URL("https://www.example.com");
|
||||
}
|
||||
|
||||
---
|
||||
|
||||
<Layout title={post.title} description={post.description}>
|
||||
<article class="prose dark:prose-invert prose-zinc lg:prose-lg mx-auto max-w-4xl">
|
||||
<div class="mb-12">
|
||||
<h1 class="mb-4 text-4xl font-bold tracking-tight text-zinc-900 dark:text-zinc-100 sm:text-5xl">
|
||||
{post.title}
|
||||
</h1>
|
||||
|
||||
<div class="flex items-center gap-x-4 text-sm text-zinc-500 dark:text-zinc-400 mb-6">
|
||||
<FormattedDate date={published_date} />
|
||||
</div>
|
||||
|
||||
<TagList tags={post.tags} class="mt-2" />
|
||||
</div>
|
||||
|
||||
<!-- Hero image -->
|
||||
{post.image && (
|
||||
<div class="relative mb-8 sm:mb-12 overflow-hidden rounded-xl shadow-lg">
|
||||
<div class="aspect-[16/9] w-full">
|
||||
<img
|
||||
src={`${process.env.DIRECTUS_URL ?? "https://directus.alexlebens.dev"}/assets/${post.image}?width=500`}
|
||||
alt={post.image_alt}
|
||||
class="w-full h-full object-cover"
|
||||
loading="eager"
|
||||
/>
|
||||
</div>
|
||||
<div class="absolute inset-0 bg-gradient-to-t from-black/30 to-transparent"></div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div class="markdown-content">
|
||||
<slot />
|
||||
</div>
|
||||
|
||||
<!-- Add the like button after the content -->
|
||||
<div class="mt-12 pt-8 border-t border-zinc-200 dark:border-zinc-800">
|
||||
<div class="flex flex-col sm:flex-row items-center justify-between gap-6">
|
||||
<ShareButtons url={canonicalURL.toString()} title={post.title} /> <!-- Convert URL to string -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{post.updated_date && (
|
||||
<div class="mt-8 text-sm text-zinc-500 dark:text-zinc-400 italic">
|
||||
Last updated on <FormattedDate date={post.updated_date} />
|
||||
</div>
|
||||
)}
|
||||
</article>
|
||||
|
||||
<slot name="after-article" />
|
||||
</Layout>
|
||||
|
||||
<script>
|
||||
// Blog post SPA transitions
|
||||
function setupBlogPostTransitions() {
|
||||
// Animate article entrance
|
||||
const article = document.querySelector('article');
|
||||
if (article) {
|
||||
article.classList.add('article-entering');
|
||||
|
||||
// Remove class after animation completes
|
||||
setTimeout(() => {
|
||||
article.classList.remove('article-entering');
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
// Ensure consistent code block styling
|
||||
function updateCodeBlockStyles() {
|
||||
document.querySelectorAll('pre').forEach(pre => {
|
||||
// Force the background color with !important for both light and dark mode
|
||||
pre.setAttribute('style', 'background-color: #1e293b !important');
|
||||
|
||||
// Also apply to any nested code elements
|
||||
const codeElements = pre.querySelectorAll('code');
|
||||
codeElements.forEach(code => {
|
||||
code.setAttribute('style', 'background-color: transparent !important; color: #e5e7eb !important;');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Initial application
|
||||
updateCodeBlockStyles();
|
||||
|
||||
// Watch for theme changes
|
||||
const observer = new MutationObserver(() => {
|
||||
updateCodeBlockStyles();
|
||||
});
|
||||
|
||||
observer.observe(document.documentElement, { attributes: true, attributeFilter: ['class'] });
|
||||
|
||||
// Also run on any content changes that might add new code blocks
|
||||
const contentObserver = new MutationObserver((mutations) => {
|
||||
for (const mutation of mutations) {
|
||||
if (mutation.addedNodes.length) {
|
||||
updateCodeBlockStyles();
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
contentObserver.observe(document.body, { childList: true, subtree: true });
|
||||
|
||||
// Clean up observers when navigating away
|
||||
document.addEventListener('spa-navigation-start', () => {
|
||||
observer.disconnect();
|
||||
contentObserver.disconnect();
|
||||
});
|
||||
|
||||
// Remove the parallax effect for hero image
|
||||
|
||||
// Handle prev/next navigation links
|
||||
const navLinks = document.querySelectorAll('.blog-nav-link');
|
||||
navLinks.forEach(link => {
|
||||
if (!link.hasAttribute('data-spa-handled')) {
|
||||
link.setAttribute('data-spa-handled', 'true');
|
||||
|
||||
link.addEventListener('mouseenter', () => {
|
||||
link.classList.add('nav-link-hover');
|
||||
});
|
||||
|
||||
link.addEventListener('mouseleave', () => {
|
||||
link.classList.remove('nav-link-hover');
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Animate headings when they enter the viewport
|
||||
const animateHeadings = () => {
|
||||
const headings = document.querySelectorAll('article h2, article h3');
|
||||
|
||||
const observer = new IntersectionObserver((entries) => {
|
||||
entries.forEach(entry => {
|
||||
if (entry.isIntersecting) {
|
||||
entry.target.classList.add('heading-visible');
|
||||
observer.unobserve(entry.target);
|
||||
}
|
||||
});
|
||||
}, {
|
||||
threshold: 0.2,
|
||||
rootMargin: '0px 0px -100px 0px'
|
||||
});
|
||||
|
||||
headings.forEach(heading => {
|
||||
heading.classList.add('heading-animated');
|
||||
observer.observe(heading);
|
||||
});
|
||||
|
||||
return observer;
|
||||
};
|
||||
|
||||
// Initialize heading animations
|
||||
const headingObserver = animateHeadings();
|
||||
|
||||
// Enhance code blocks with syntax highlighting and copy button
|
||||
function enhanceCodeBlocks() {
|
||||
const codeBlocks = document.querySelectorAll('pre code');
|
||||
|
||||
codeBlocks.forEach(codeBlock => {
|
||||
// Skip if already processed
|
||||
if (codeBlock.parentElement.classList.contains('enhanced')) return;
|
||||
|
||||
// Mark as enhanced
|
||||
codeBlock.parentElement.classList.add('enhanced');
|
||||
|
||||
// Create copy button
|
||||
const copyButton = document.createElement('button');
|
||||
copyButton.className = 'copy-code-button';
|
||||
copyButton.innerHTML = `
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path d="M8 3a1 1 0 011-1h2a1 1 0 110 2H9a1 1 0 01-1-1z" />
|
||||
<path d="M6 3a2 2 0 00-2 2v11a2 2 0 002 2h8a2 2 0 002-2V5a2 2 0 00-2-2 3 3 0 01-3 3H9a3 3 0 01-3-3z" />
|
||||
</svg>
|
||||
`;
|
||||
|
||||
// Add copy functionality
|
||||
copyButton.addEventListener('click', () => {
|
||||
const code = codeBlock.textContent;
|
||||
navigator.clipboard.writeText(code);
|
||||
|
||||
// Show copied feedback
|
||||
copyButton.innerHTML = `
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path fill-rule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clip-rule="evenodd" />
|
||||
</svg>
|
||||
`;
|
||||
|
||||
setTimeout(() => {
|
||||
copyButton.innerHTML = `
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path d="M8 3a1 1 0 011-1h2a1 1 0 110 2H9a1 1 0 01-1-1z" />
|
||||
<path d="M6 3a2 2 0 00-2 2v11a2 2 0 002 2h8a2 2 0 002-2V5a2 2 0 00-2-2 3 3 0 01-3 3H9a3 3 0 01-3-3z" />
|
||||
</svg>
|
||||
`;
|
||||
}, 2000);
|
||||
});
|
||||
|
||||
// Add copy button to pre element
|
||||
codeBlock.parentElement.appendChild(copyButton);
|
||||
|
||||
// Fix line numbers implementation
|
||||
const codeText = codeBlock.textContent;
|
||||
const lines = codeText.split('\n');
|
||||
|
||||
const lineNumbers = document.createElement('div');
|
||||
lineNumbers.className = 'line-numbers';
|
||||
|
||||
// Always include all lines, including empty ones
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
const lineNumber = document.createElement('span');
|
||||
lineNumber.textContent = i + 1;
|
||||
lineNumbers.appendChild(lineNumber);
|
||||
}
|
||||
|
||||
codeBlock.parentElement.classList.add('with-line-numbers');
|
||||
codeBlock.parentElement.insertBefore(lineNumbers, codeBlock);
|
||||
|
||||
// Fix language label detection and display
|
||||
const className = codeBlock.className;
|
||||
const languageMatch = className.match(/language-(\w+)/);
|
||||
|
||||
if (languageMatch && languageMatch[1]) {
|
||||
const language = languageMatch[1];
|
||||
|
||||
// Add language label at top right
|
||||
const languageLabel = document.createElement('div');
|
||||
languageLabel.className = 'language-label';
|
||||
languageLabel.textContent = language;
|
||||
codeBlock.parentElement.appendChild(languageLabel);
|
||||
|
||||
// Add language badge at bottom right with markdown syntax
|
||||
const languageBadge = document.createElement('div');
|
||||
languageBadge.className = 'language-badge';
|
||||
languageBadge.textContent = `\`\`\`${language}`;
|
||||
languageBadge.style.position = 'absolute';
|
||||
languageBadge.style.bottom = '0.5rem';
|
||||
languageBadge.style.right = '0.5rem';
|
||||
languageBadge.style.fontSize = '0.7rem';
|
||||
languageBadge.style.padding = '0.1rem 0.3rem';
|
||||
languageBadge.style.backgroundColor = 'rgba(75, 85, 99, 0.7)';
|
||||
languageBadge.style.color = '#e5e7eb';
|
||||
languageBadge.style.borderRadius = '0.25rem';
|
||||
languageBadge.style.fontFamily = 'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace';
|
||||
languageBadge.style.zIndex = '10';
|
||||
codeBlock.parentElement.appendChild(languageBadge);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Enhance tables with better styling
|
||||
function enhanceTables() {
|
||||
const tables = document.querySelectorAll('.markdown-content table');
|
||||
|
||||
tables.forEach(table => {
|
||||
if (table.classList.contains('enhanced-table')) return;
|
||||
|
||||
table.classList.add('enhanced-table');
|
||||
|
||||
// Wrap table in responsive container
|
||||
const wrapper = document.createElement('div');
|
||||
wrapper.className = 'table-container';
|
||||
table.parentNode.insertBefore(wrapper, table);
|
||||
wrapper.appendChild(table);
|
||||
|
||||
// Add zebra striping to rows
|
||||
const rows = table.querySelectorAll('tbody tr');
|
||||
rows.forEach((row, index) => {
|
||||
if (index % 2 === 0) {
|
||||
row.classList.add('even-row');
|
||||
} else {
|
||||
row.classList.add('odd-row');
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Enhance blockquotes with icons
|
||||
function enhanceBlockquotes() {
|
||||
const blockquotes = document.querySelectorAll('.markdown-content blockquote');
|
||||
|
||||
blockquotes.forEach(blockquote => {
|
||||
if (blockquote.classList.contains('enhanced-quote')) return;
|
||||
|
||||
blockquote.classList.add('enhanced-quote');
|
||||
|
||||
// Add quote icon
|
||||
const icon = document.createElement('div');
|
||||
icon.className = 'quote-icon';
|
||||
icon.innerHTML = `
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M7.5 8.25h9m-9 3H12m-9.75 1.51c0 1.6 1.123 2.994 2.707 3.227 1.129.166 2.27.293 3.423.379.35.026.67.21.865.501L12 21l2.755-4.133a1.14 1.14 0 01.865-.501 48.172 48.172 0 003.423-.379c1.584-.233 2.707-1.626 2.707-3.228V6.741c0-1.602-1.123-2.995-2.707-3.228A48.394 48.394 0 0012 3c-2.392 0-4.744.175-7.043.513C3.373 3.746 2.25 5.14 2.25 6.741v6.018z" />
|
||||
</svg>
|
||||
`;
|
||||
|
||||
blockquote.insertBefore(icon, blockquote.firstChild);
|
||||
});
|
||||
}
|
||||
|
||||
// Run all enhancements
|
||||
enhanceCodeBlocks();
|
||||
enhanceTables();
|
||||
enhanceBlockquotes();
|
||||
|
||||
// Clean up observers when navigating away
|
||||
document.addEventListener('spa-navigation-start', () => {
|
||||
if (headingObserver) {
|
||||
headingObserver.disconnect();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Initialize on first load
|
||||
document.addEventListener('DOMContentLoaded', setupBlogPostTransitions);
|
||||
|
||||
// Re-initialize when content changes via Astro's view transitions
|
||||
document.addEventListener('astro:page-load', setupBlogPostTransitions);
|
||||
|
||||
// For compatibility with custom transition system
|
||||
document.addEventListener('page-transition-complete', setupBlogPostTransitions);
|
||||
|
||||
// Also initialize when SPA navigation completes
|
||||
document.addEventListener('spa-navigation-complete', setupBlogPostTransitions);
|
||||
</script>
|
||||
|
||||
<style>
|
||||
/* Enhanced hero image styling */
|
||||
article img:first-of-type {
|
||||
border-radius: 1rem;
|
||||
box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.1), 0 8px 10px -6px rgba(0, 0, 0, 0.1);
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
article img:first-of-type:hover {
|
||||
transform: scale(1.01);
|
||||
}
|
||||
|
||||
/* Article entrance animation */
|
||||
.article-entering {
|
||||
animation: article-fade-in 0.8s ease-out forwards;
|
||||
}
|
||||
|
||||
@keyframes article-fade-in {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(10px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
/* Rest of the styles remain unchanged... */
|
||||
</style>
|
317
src/layouts/Layout.astro
Normal file
317
src/layouts/Layout.astro
Normal file
@@ -0,0 +1,317 @@
|
||||
---
|
||||
import Navigation from '../components/Navigation.astro';
|
||||
import Footer from '../components/Footer.astro';
|
||||
import Background from '../components/Background.astro';
|
||||
import '../styles/global.css';
|
||||
|
||||
interface Props {
|
||||
title?: string | undefined;
|
||||
description?: string | undefined;
|
||||
}
|
||||
|
||||
const { title, description } = Astro.props;
|
||||
---
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width" />
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||
<meta name="generator" content={Astro.generator} />
|
||||
<meta name="description" content={description} />
|
||||
<title>{title}</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
|
||||
</head>
|
||||
<body class="bg-white dark:bg-zinc-900 text-zinc-900 dark:text-zinc-100 min-h-screen flex flex-col">
|
||||
<!-- Page transition overlay - for smooth transitions between pages -->
|
||||
<div id="page-transition" class="fixed inset-0 z-40 bg-white dark:bg-zinc-900 opacity-0 pointer-events-none transition-opacity duration-300 flex items-center justify-center">
|
||||
<div class="transition-spinner"></div>
|
||||
</div>
|
||||
|
||||
<!-- Background component with dot pattern and ambient glow -->
|
||||
<Background />
|
||||
|
||||
<div class="max-w-3xl mx-auto px-4 sm:px-6 w-full flex-grow">
|
||||
<Navigation />
|
||||
<main class="py-12">
|
||||
<slot />
|
||||
</main>
|
||||
</div>
|
||||
<Footer />
|
||||
|
||||
<script>
|
||||
// SPA transition system with history API
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const pageTransition = document.getElementById('page-transition');
|
||||
const mainContent = document.querySelector('main');
|
||||
|
||||
// Initialize content with entrance animation
|
||||
if (mainContent) {
|
||||
mainContent.classList.add('content-entering');
|
||||
setTimeout(() => {
|
||||
mainContent.classList.remove('content-entering');
|
||||
}, 800);
|
||||
}
|
||||
|
||||
// Function to load content via fetch
|
||||
async function loadContent(url) {
|
||||
try {
|
||||
// Show transition overlay
|
||||
if (pageTransition) {
|
||||
pageTransition.classList.remove('opacity-0', 'pointer-events-none');
|
||||
pageTransition.classList.add('opacity-100');
|
||||
}
|
||||
|
||||
// Fade out current content
|
||||
if (mainContent) {
|
||||
mainContent.style.opacity = '0';
|
||||
mainContent.style.transform = 'translateY(10px)';
|
||||
}
|
||||
|
||||
// Fetch the new page content
|
||||
const response = await fetch(url);
|
||||
if (!response.ok) throw new Error(`Failed to fetch ${url}`);
|
||||
const html = await response.text();
|
||||
|
||||
// Create a temporary element to parse the HTML
|
||||
const parser = new DOMParser();
|
||||
const doc = parser.parseFromString(html, 'text/html');
|
||||
|
||||
// Extract the main content
|
||||
const newContent = doc.querySelector('main');
|
||||
if (!newContent) throw new Error('Could not find main content in the fetched page');
|
||||
|
||||
// Extract the title
|
||||
const newTitle = doc.querySelector('title');
|
||||
if (newTitle) {
|
||||
document.title = newTitle.textContent;
|
||||
}
|
||||
|
||||
// Extract meta description
|
||||
const newDescription = doc.querySelector('meta[name="description"]');
|
||||
if (newDescription) {
|
||||
const currentDescription = document.querySelector('meta[name="description"]');
|
||||
if (currentDescription) {
|
||||
currentDescription.setAttribute('content', newDescription.getAttribute('content') || '');
|
||||
}
|
||||
}
|
||||
|
||||
// Wait a bit for transition effect
|
||||
await new Promise(resolve => setTimeout(resolve, 300));
|
||||
|
||||
// Replace the content
|
||||
if (mainContent && newContent) {
|
||||
mainContent.innerHTML = newContent.innerHTML;
|
||||
|
||||
// Run scripts in the new content
|
||||
Array.from(newContent.querySelectorAll('script')).forEach(oldScript => {
|
||||
const newScript = document.createElement('script');
|
||||
Array.from(oldScript.attributes).forEach(attr => {
|
||||
newScript.setAttribute(attr.name, attr.value);
|
||||
});
|
||||
newScript.textContent = oldScript.textContent;
|
||||
if (oldScript.parentNode) {
|
||||
mainContent.appendChild(newScript);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Fade in new content with animation
|
||||
if (mainContent) {
|
||||
mainContent.style.opacity = '0';
|
||||
mainContent.style.transform = 'translateY(10px)';
|
||||
|
||||
setTimeout(() => {
|
||||
mainContent.style.transition = 'opacity 0.5s ease, transform 0.5s ease';
|
||||
mainContent.style.opacity = '1';
|
||||
mainContent.style.transform = 'translateY(0)';
|
||||
|
||||
// Add entrance animation class
|
||||
mainContent.classList.add('content-entering');
|
||||
setTimeout(() => {
|
||||
mainContent.classList.remove('content-entering');
|
||||
}, 800);
|
||||
}, 50);
|
||||
}
|
||||
|
||||
// Hide transition overlay
|
||||
if (pageTransition) {
|
||||
setTimeout(() => {
|
||||
pageTransition.classList.add('opacity-0', 'pointer-events-none');
|
||||
pageTransition.classList.remove('opacity-100');
|
||||
}, 200);
|
||||
}
|
||||
|
||||
// Dispatch custom event for content loaded
|
||||
document.dispatchEvent(new CustomEvent('spa-content-loaded', {
|
||||
detail: { url }
|
||||
}));
|
||||
|
||||
// Scroll to top or to saved position
|
||||
window.scrollTo(0, 0);
|
||||
|
||||
// Re-attach event listeners to new content
|
||||
attachLinkListeners();
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error loading content:', error);
|
||||
|
||||
// Fallback to traditional navigation on error
|
||||
window.location.href = url;
|
||||
}
|
||||
}
|
||||
|
||||
// Function to attach event listeners to all links
|
||||
function attachLinkListeners() {
|
||||
document.querySelectorAll('a').forEach(link => {
|
||||
// Skip links that are already handled, anchor links, external links, or have special attributes
|
||||
if (
|
||||
link.hasAttribute('data-spa-handled') ||
|
||||
!link.href.startsWith(window.location.origin) ||
|
||||
link.href.includes('#') ||
|
||||
link.hasAttribute('target') ||
|
||||
link.hasAttribute('download') ||
|
||||
link.getAttribute('rel') === 'external' ||
|
||||
link.getAttribute('rel') === 'nofollow'
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Mark as handled to avoid duplicate listeners
|
||||
link.setAttribute('data-spa-handled', 'true');
|
||||
|
||||
link.addEventListener('click', (e) => {
|
||||
// Don't handle if modifier keys are pressed (for opening in new tab, etc.)
|
||||
if (e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) {
|
||||
return;
|
||||
}
|
||||
|
||||
e.preventDefault();
|
||||
const targetHref = link.href;
|
||||
|
||||
// Don't transition if clicking the current page
|
||||
if (targetHref === window.location.href) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Update browser history
|
||||
window.history.pushState({ path: targetHref }, '', targetHref);
|
||||
|
||||
// Load the new content
|
||||
loadContent(targetHref);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Initial attachment of link listeners
|
||||
attachLinkListeners();
|
||||
|
||||
// Handle browser back/forward navigation
|
||||
window.addEventListener('popstate', (e) => {
|
||||
if (e.state && e.state.path) {
|
||||
loadContent(e.state.path);
|
||||
} else {
|
||||
loadContent(window.location.href);
|
||||
}
|
||||
});
|
||||
|
||||
// Check RSS feed availability
|
||||
const checkAndGenerateRSS = async () => {
|
||||
try {
|
||||
const response = await fetch('/rss.xml');
|
||||
if (!response.ok) {
|
||||
console.warn('RSS feed not found. Please generate it using an RSS plugin for Astro.');
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('Could not check RSS feed status.');
|
||||
}
|
||||
};
|
||||
|
||||
// Check RSS feed availability
|
||||
checkAndGenerateRSS();
|
||||
});
|
||||
|
||||
// Theme handling with transition effects
|
||||
function setupThemeHandling() {
|
||||
// Apply theme from localStorage or system preference
|
||||
const theme = localStorage.getItem('theme');
|
||||
if (theme === 'dark' || (!theme && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
|
||||
document.documentElement.classList.add('dark');
|
||||
} else {
|
||||
document.documentElement.classList.remove('dark');
|
||||
}
|
||||
|
||||
// Listen for theme changes
|
||||
document.addEventListener('themeChanged', () => {
|
||||
// Add transition class to body
|
||||
document.body.classList.add('theme-transitioning');
|
||||
|
||||
// Remove class after transition completes
|
||||
setTimeout(() => {
|
||||
document.body.classList.remove('theme-transitioning');
|
||||
}, 500);
|
||||
});
|
||||
}
|
||||
|
||||
// Initialize theme handling
|
||||
document.addEventListener('DOMContentLoaded', setupThemeHandling);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
<style>
|
||||
/* Page transition effects */
|
||||
#page-transition {
|
||||
transition: opacity 0.3s ease;
|
||||
backdrop-filter: blur(4px);
|
||||
}
|
||||
|
||||
/* Transition spinner animation */
|
||||
.transition-spinner {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
border: 2px solid rgba(0, 0, 0, 0.1);
|
||||
border-radius: 50%;
|
||||
border-top-color: #3b82f6;
|
||||
animation: spin 0.7s linear infinite;
|
||||
}
|
||||
|
||||
:global(.dark) .transition-spinner {
|
||||
border-color: rgba(255, 255, 255, 0.1);
|
||||
border-top-color: #60a5fa;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
/* Content entrance animation */
|
||||
main {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
transition: opacity 0.5s ease, transform 0.5s ease;
|
||||
}
|
||||
|
||||
main.content-entering {
|
||||
animation: content-fade-in 0.6s ease forwards;
|
||||
}
|
||||
|
||||
@keyframes content-fade-in {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(10px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
/* Theme transition effect */
|
||||
body.theme-transitioning * {
|
||||
transition-duration: 0.3s !important;
|
||||
}
|
||||
</style>
|
25
src/layouts/TransitionLayout.astro
Normal file
25
src/layouts/TransitionLayout.astro
Normal file
@@ -0,0 +1,25 @@
|
||||
---
|
||||
import { ViewTransitions } from 'astro:transitions';
|
||||
import BaseLayout from './BaseLayout.astro';
|
||||
|
||||
const { title, description } = Astro.props;
|
||||
---
|
||||
|
||||
<BaseLayout title={title} description={description}>
|
||||
<ViewTransitions fallback="swap" />
|
||||
|
||||
<div transition:animate="slide">
|
||||
<slot />
|
||||
</div>
|
||||
</BaseLayout>
|
||||
|
||||
<style>
|
||||
/* Custom transition styles */
|
||||
::view-transition-old(root) {
|
||||
animation: 0.5s cubic-bezier(0.76, 0, 0.24, 1) both slide-to-left;
|
||||
}
|
||||
|
||||
::view-transition-new(root) {
|
||||
animation: 0.5s cubic-bezier(0.76, 0, 0.24, 1) both slide-from-right;
|
||||
}
|
||||
</style>
|
851
src/layouts/styles/markdown.css
Normal file
851
src/layouts/styles/markdown.css
Normal file
@@ -0,0 +1,851 @@
|
||||
/* Article entrance animation */
|
||||
article {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
article.article-entering {
|
||||
animation: article-fade-in 0.8s ease forwards;
|
||||
}
|
||||
|
||||
@keyframes article-fade-in {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(20px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
/* Hero image hover effect */
|
||||
article img {
|
||||
transition: transform 0.7s cubic-bezier(0.33, 1, 0.68, 1);
|
||||
}
|
||||
|
||||
/* Heading animations */
|
||||
article .heading-animated {
|
||||
opacity: 0;
|
||||
transform: translateY(10px);
|
||||
transition: opacity 0.5s ease, transform 0.5s ease;
|
||||
}
|
||||
|
||||
article .heading-visible {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
/* Navigation link hover effect */
|
||||
.blog-nav-link {
|
||||
transition: transform 0.3s ease, box-shadow 0.3s ease;
|
||||
}
|
||||
|
||||
.blog-nav-link.nav-link-hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 10px 20px -10px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
/* Ensure dark mode compatibility */
|
||||
:global(.dark) .blog-nav-link.nav-link-hover {
|
||||
box-shadow: 0 10px 20px -10px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
/* Enhanced Markdown Content Styling */
|
||||
.markdown-content {
|
||||
font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
|
||||
line-height: 1.7;
|
||||
color: #374151;
|
||||
}
|
||||
|
||||
.dark .markdown-content {
|
||||
color: #e5e7eb;
|
||||
}
|
||||
|
||||
/* Headings */
|
||||
.markdown-content h1 {
|
||||
font-size: 2.5rem;
|
||||
font-weight: 800;
|
||||
margin-top: 2.5rem;
|
||||
margin-bottom: 1.5rem;
|
||||
line-height: 1.2;
|
||||
color: #111827;
|
||||
border-bottom: 1px solid #e5e7eb;
|
||||
padding-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.dark .markdown-content h1 {
|
||||
color: #f9fafb;
|
||||
border-bottom-color: #374151;
|
||||
}
|
||||
|
||||
.markdown-content h2 {
|
||||
font-size: 2rem;
|
||||
font-weight: 700;
|
||||
margin-top: 2.5rem;
|
||||
margin-bottom: 1rem;
|
||||
line-height: 1.3;
|
||||
color: #111827;
|
||||
border-bottom: 1px solid #e5e7eb;
|
||||
padding-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.dark .markdown-content h2 {
|
||||
color: #f9fafb;
|
||||
border-bottom-color: #374151;
|
||||
}
|
||||
|
||||
.markdown-content h3 {
|
||||
font-size: 1.5rem;
|
||||
font-weight: 600;
|
||||
margin-top: 2rem;
|
||||
margin-bottom: 1rem;
|
||||
line-height: 1.4;
|
||||
color: #111827;
|
||||
}
|
||||
|
||||
.dark .markdown-content h3 {
|
||||
color: #f9fafb;
|
||||
}
|
||||
|
||||
.markdown-content h4 {
|
||||
font-size: 1.25rem;
|
||||
font-weight: 600;
|
||||
margin-top: 1.5rem;
|
||||
margin-bottom: 0.75rem;
|
||||
line-height: 1.5;
|
||||
color: #111827;
|
||||
}
|
||||
|
||||
.dark .markdown-content h4 {
|
||||
color: #f9fafb;
|
||||
}
|
||||
|
||||
.markdown-content h5 {
|
||||
font-size: 1.125rem;
|
||||
font-weight: 600;
|
||||
margin-top: 1.5rem;
|
||||
margin-bottom: 0.75rem;
|
||||
line-height: 1.5;
|
||||
color: #111827;
|
||||
}
|
||||
|
||||
.dark .markdown-content h5 {
|
||||
color: #f9fafb;
|
||||
}
|
||||
|
||||
.markdown-content h6 {
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
margin-top: 1.5rem;
|
||||
margin-bottom: 0.75rem;
|
||||
line-height: 1.5;
|
||||
color: #111827;
|
||||
}
|
||||
|
||||
.dark .markdown-content h6 {
|
||||
color: #f9fafb;
|
||||
}
|
||||
|
||||
/* Paragraphs */
|
||||
.markdown-content p {
|
||||
margin-top: 1.25rem;
|
||||
margin-bottom: 1.25rem;
|
||||
}
|
||||
|
||||
/* Links */
|
||||
.markdown-content a {
|
||||
color: #2563eb;
|
||||
text-decoration: none;
|
||||
border-bottom: 1px solid transparent;
|
||||
transition: border-color 0.2s ease, color 0.2s ease;
|
||||
}
|
||||
|
||||
.markdown-content a:hover {
|
||||
color: #1d4ed8;
|
||||
border-bottom-color: #1d4ed8;
|
||||
}
|
||||
|
||||
.dark .markdown-content a {
|
||||
color: #3b82f6;
|
||||
}
|
||||
|
||||
.dark .markdown-content a:hover {
|
||||
color: #60a5fa;
|
||||
border-bottom-color: #60a5fa;
|
||||
}
|
||||
|
||||
/* Bold text styling - enhanced */
|
||||
.markdown-content strong {
|
||||
font-weight: 700;
|
||||
color: #0f766e;
|
||||
background: linear-gradient(to bottom, transparent 60%, rgba(20, 184, 166, 0.2) 40%);
|
||||
padding: 0 0.2em;
|
||||
border-radius: 0.2em;
|
||||
}
|
||||
|
||||
.dark .markdown-content strong {
|
||||
color: #14b8a6;
|
||||
background: linear-gradient(to bottom, transparent 60%, rgba(20, 184, 166, 0.15) 40%);
|
||||
}
|
||||
|
||||
/* Lists */
|
||||
.markdown-content ul,
|
||||
.markdown-content ol {
|
||||
margin-top: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
padding-left: 1.5rem;
|
||||
}
|
||||
|
||||
.markdown-content ul {
|
||||
list-style-type: disc;
|
||||
}
|
||||
|
||||
.markdown-content ol {
|
||||
list-style-type: decimal;
|
||||
}
|
||||
|
||||
.markdown-content li {
|
||||
margin-top: 0.5rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.markdown-content li > ul,
|
||||
.markdown-content li > ol {
|
||||
margin-top: 0.25rem;
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
/* Blockquotes */
|
||||
.markdown-content blockquote {
|
||||
border-left: 4px solid #3b82f6;
|
||||
padding: 1rem 1.5rem;
|
||||
margin: 1.5rem 0;
|
||||
background-color: #f3f4f6;
|
||||
border-radius: 0.375rem;
|
||||
font-style: italic;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.dark .markdown-content blockquote {
|
||||
background-color: #1f2937;
|
||||
border-left-color: #60a5fa;
|
||||
}
|
||||
|
||||
.markdown-content blockquote p {
|
||||
margin-top: 0.5rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.markdown-content blockquote .quote-icon {
|
||||
position: absolute;
|
||||
top: 0.5rem;
|
||||
right: 0.5rem;
|
||||
opacity: 0.1;
|
||||
color: #3b82f6;
|
||||
}
|
||||
|
||||
.dark .markdown-content blockquote .quote-icon {
|
||||
color: #60a5fa;
|
||||
}
|
||||
|
||||
/* Code blocks */
|
||||
.markdown-content pre {
|
||||
margin: 1.5rem 0;
|
||||
padding: 1rem;
|
||||
background-color: #1e293b !important;
|
||||
border-radius: 0.5rem;
|
||||
overflow-x: auto;
|
||||
position: relative;
|
||||
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
/* Dark mode code blocks - ensure consistency */
|
||||
.dark .markdown-content pre {
|
||||
background-color: #1e293b !important;
|
||||
}
|
||||
|
||||
.markdown-content pre code {
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.7;
|
||||
color: #e5e7eb !important;
|
||||
background-color: transparent !important;
|
||||
padding: 0;
|
||||
border-radius: 0;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.dark .markdown-content pre code {
|
||||
color: #e5e7eb !important;
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
||||
.markdown-content pre.with-line-numbers {
|
||||
padding-left: 3.5rem;
|
||||
}
|
||||
|
||||
.markdown-content pre code {
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.7;
|
||||
color: #e5e7eb;
|
||||
background-color: transparent;
|
||||
padding: 0;
|
||||
border-radius: 0;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.markdown-content .line-numbers {
|
||||
position: absolute;
|
||||
top: 1rem;
|
||||
left: 0;
|
||||
width: 2.5rem;
|
||||
text-align: right;
|
||||
padding-right: 0.75rem;
|
||||
color: #6b7280;
|
||||
user-select: none;
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.7;
|
||||
border-right: 1px solid #4b5563;
|
||||
height: calc(100% - 2rem);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.markdown-content .line-numbers span {
|
||||
display: block;
|
||||
height: 1.7em;
|
||||
}
|
||||
|
||||
.markdown-content .copy-code-button {
|
||||
position: absolute;
|
||||
top: 0.25rem;
|
||||
right: 0.25rem;
|
||||
background-color: #4b5563;
|
||||
color: #e5e7eb;
|
||||
border: none;
|
||||
border-radius: 0.25rem;
|
||||
padding: 0.15rem;
|
||||
cursor: pointer;
|
||||
opacity: 0.6;
|
||||
transition: opacity 0.2s ease;
|
||||
z-index: 10;
|
||||
width: 1.25rem;
|
||||
height: 1.25rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.markdown-content .copy-code-button:hover {
|
||||
opacity: 1;
|
||||
background-color: #6b7280;
|
||||
}
|
||||
|
||||
.markdown-content .copy-code-button svg {
|
||||
width: 0.875rem;
|
||||
height: 0.875rem;
|
||||
}
|
||||
|
||||
/* Language label */
|
||||
.markdown-content .language-label {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 2.5rem;
|
||||
background-color: #4b5563;
|
||||
color: #e5e7eb;
|
||||
font-size: 0.65rem;
|
||||
padding: 0.125rem 0.375rem;
|
||||
border-bottom-left-radius: 0.25rem;
|
||||
border-bottom-right-radius: 0.25rem;
|
||||
text-transform: uppercase;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.05em;
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
||||
opacity: 0.8;
|
||||
transition: opacity 0.2s ease;
|
||||
z-index: 5;
|
||||
}
|
||||
|
||||
.markdown-content pre:hover .language-label {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* Language badge at bottom right */
|
||||
.markdown-content .language-badge {
|
||||
position: absolute;
|
||||
bottom: 0.5rem;
|
||||
right: 0.5rem;
|
||||
font-size: 0.7rem;
|
||||
padding: 0.1rem 0.3rem;
|
||||
background-color: rgba(75, 85, 99, 0.7);
|
||||
color: #e5e7eb;
|
||||
border-radius: 0.25rem;
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
||||
opacity: 0.8;
|
||||
transition: opacity 0.2s ease;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.markdown-content pre:hover .language-badge {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* Inline code */
|
||||
.markdown-content code:not(pre code) {
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
||||
font-size: 0.875em;
|
||||
color: #ef4444;
|
||||
background-color: #f3f4f6;
|
||||
padding: 0.2em 0.4em;
|
||||
border-radius: 0.25rem;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.dark .markdown-content code:not(pre code) {
|
||||
color: #f87171;
|
||||
background-color: #1f2937;
|
||||
}
|
||||
|
||||
/* Tables */
|
||||
.markdown-content .table-container {
|
||||
overflow-x: auto;
|
||||
margin: 1.5rem 0;
|
||||
border-radius: 0.5rem;
|
||||
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.markdown-content table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
text-align: left;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
.markdown-content table th {
|
||||
background-color: #f3f4f6;
|
||||
color: #111827;
|
||||
font-weight: 600;
|
||||
padding: 0.75rem 1rem;
|
||||
border-bottom: 2px solid #e5e7eb;
|
||||
}
|
||||
|
||||
.dark .markdown-content table th {
|
||||
background-color: #1f2937;
|
||||
color: #f9fafb;
|
||||
border-bottom-color: #374151;
|
||||
}
|
||||
|
||||
.markdown-content table td {
|
||||
padding: 0.75rem 1rem;
|
||||
border-bottom: 1px solid #e5e7eb;
|
||||
}
|
||||
|
||||
.dark .markdown-content table td {
|
||||
border-bottom-color: #374151;
|
||||
}
|
||||
|
||||
.markdown-content table tr.even-row {
|
||||
background-color: #f9fafb;
|
||||
}
|
||||
|
||||
.dark .markdown-content table tr.even-row {
|
||||
background-color: #111827;
|
||||
}
|
||||
|
||||
.markdown-content table tr.odd-row {
|
||||
background-color: #ffffff;
|
||||
}
|
||||
|
||||
.dark .markdown-content table tr.odd-row {
|
||||
background-color: #1f2937;
|
||||
}
|
||||
|
||||
.markdown-content table tr:last-child td {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
/* Images */
|
||||
.markdown-content img {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
border-radius: 0.5rem;
|
||||
margin: 1.5rem 0;
|
||||
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
/* Horizontal rule */
|
||||
.markdown-content hr {
|
||||
border: 0;
|
||||
height: 1px;
|
||||
background-color: #e5e7eb;
|
||||
margin: 2rem 0;
|
||||
}
|
||||
|
||||
.dark .markdown-content hr {
|
||||
background-color: #374151;
|
||||
}
|
||||
|
||||
/* Task lists */
|
||||
.markdown-content ul li[data-task-list-item] {
|
||||
list-style-type: none;
|
||||
position: relative;
|
||||
padding-left: 1.5rem;
|
||||
}
|
||||
|
||||
.markdown-content ul li[data-task-list-item]::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0.25rem;
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
border: 1px solid #9ca3af;
|
||||
border-radius: 0.25rem;
|
||||
}
|
||||
|
||||
.markdown-content ul li[data-task-list-item][data-checked]::before {
|
||||
background-color: #3b82f6;
|
||||
border-color: #3b82f6;
|
||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 24 24' stroke='%23ffffff'%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M5 13l4 4L19 7'%3E%3C/path%3E%3C/svg%3E");
|
||||
background-size: 0.75rem;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
/* Footnotes */
|
||||
.markdown-content .footnotes {
|
||||
margin-top: 2rem;
|
||||
padding-top: 1rem;
|
||||
border-top: 1px solid #e5e7eb;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
.dark .markdown-content .footnotes {
|
||||
border-top-color: #374151;
|
||||
}
|
||||
|
||||
.markdown-content .footnotes ol {
|
||||
padding-left: 1rem;
|
||||
}
|
||||
|
||||
.markdown-content .footnotes li {
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.markdown-content .footnote-backref {
|
||||
font-size: 0.75rem;
|
||||
vertical-align: super;
|
||||
}
|
||||
|
||||
/* Definition lists */
|
||||
.markdown-content dl {
|
||||
margin: 1.5rem 0;
|
||||
}
|
||||
|
||||
.markdown-content dt {
|
||||
font-weight: 600;
|
||||
color: #111827;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.dark .markdown-content dt {
|
||||
color: #f9fafb;
|
||||
}
|
||||
|
||||
.markdown-content dd {
|
||||
margin-left: 1.5rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
/* Callouts and admonitions */
|
||||
.markdown-content .callout {
|
||||
margin: 1.5rem 0;
|
||||
padding: 1rem;
|
||||
border-radius: 0.5rem;
|
||||
border-left: 4px solid;
|
||||
background-color: #f3f4f6;
|
||||
}
|
||||
|
||||
.dark .markdown-content .callout {
|
||||
background-color: #1f2937;
|
||||
}
|
||||
|
||||
.markdown-content .callout.info {
|
||||
border-left-color: #3b82f6;
|
||||
}
|
||||
|
||||
.markdown-content .callout.warning {
|
||||
border-left-color: #f59e0b;
|
||||
background-color: rgba(245, 158, 11, 0.1);
|
||||
}
|
||||
|
||||
.dark .markdown-content .callout.warning {
|
||||
background-color: rgba(245, 158, 11, 0.05);
|
||||
}
|
||||
|
||||
.markdown-content .callout.danger {
|
||||
border-left-color: #ef4444;
|
||||
background-color: rgba(239, 68, 68, 0.1);
|
||||
}
|
||||
|
||||
.dark .markdown-content .callout.danger {
|
||||
background-color: rgba(239, 68, 68, 0.05);
|
||||
}
|
||||
|
||||
.markdown-content .callout.tip {
|
||||
border-left-color: #10b981;
|
||||
background-color: rgba(16, 185, 129, 0.1);
|
||||
}
|
||||
|
||||
.dark .markdown-content .callout.tip {
|
||||
background-color: rgba(16, 185, 129, 0.05);
|
||||
}
|
||||
|
||||
/* Code syntax highlighting - Light theme */
|
||||
.markdown-content .token.comment,
|
||||
.markdown-content .token.prolog,
|
||||
.markdown-content .token.doctype,
|
||||
.markdown-content .token.cdata {
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.markdown-content .token.punctuation {
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.markdown-content .token.namespace {
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.markdown-content .token.property,
|
||||
.markdown-content .token.tag,
|
||||
.markdown-content .token.boolean,
|
||||
.markdown-content .token.number,
|
||||
.markdown-content .token.constant,
|
||||
.markdown-content .token.symbol {
|
||||
color: #ef4444;
|
||||
}
|
||||
|
||||
.markdown-content .token.selector,
|
||||
.markdown-content .token.attr-name,
|
||||
.markdown-content .token.string,
|
||||
.markdown-content .token.char,
|
||||
.markdown-content .token.builtin {
|
||||
color: #10b981;
|
||||
}
|
||||
|
||||
.markdown-content .token.operator,
|
||||
.markdown-content .token.entity,
|
||||
.markdown-content .token.url,
|
||||
.markdown-content .language-css .token.string,
|
||||
.markdown-content .style .token.string {
|
||||
color: #9333ea;
|
||||
}
|
||||
|
||||
.markdown-content .token.atrule,
|
||||
.markdown-content .token.attr-value,
|
||||
.markdown-content .token.keyword {
|
||||
color: #3b82f6;
|
||||
}
|
||||
|
||||
.markdown-content .token.function,
|
||||
.markdown-content .token.class-name {
|
||||
color: #f59e0b;
|
||||
}
|
||||
|
||||
.markdown-content .token.regex,
|
||||
.markdown-content .token.important,
|
||||
.markdown-content .token.variable {
|
||||
color: #ec4899;
|
||||
}
|
||||
|
||||
.markdown-content .token.important,
|
||||
.markdown-content .token.bold {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.markdown-content .token.italic {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.markdown-content .token.entity {
|
||||
cursor: help;
|
||||
}
|
||||
|
||||
/* Responsive adjustments */
|
||||
@media (max-width: 640px) {
|
||||
.markdown-content h1 {
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
.markdown-content h2 {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.markdown-content h3 {
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
.markdown-content pre {
|
||||
padding: 0.75rem;
|
||||
}
|
||||
|
||||
.markdown-content pre.with-line-numbers {
|
||||
padding-left: 3rem;
|
||||
}
|
||||
|
||||
.markdown-content .line-numbers {
|
||||
width: 2rem;
|
||||
}
|
||||
|
||||
.markdown-content blockquote {
|
||||
padding: 0.75rem 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
/* Print styles */
|
||||
@media print {
|
||||
.markdown-content {
|
||||
font-size: 12pt;
|
||||
}
|
||||
|
||||
.markdown-content pre,
|
||||
.markdown-content code {
|
||||
font-size: 10pt;
|
||||
}
|
||||
|
||||
.markdown-content a {
|
||||
color: #000 !important;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.markdown-content blockquote {
|
||||
border-left: 2pt solid #000;
|
||||
padding: 0.5cm 1cm;
|
||||
background: none !important;
|
||||
}
|
||||
|
||||
.markdown-content img {
|
||||
max-width: 100% !important;
|
||||
page-break-inside: avoid;
|
||||
}
|
||||
|
||||
.markdown-content h2,
|
||||
.markdown-content h3,
|
||||
.markdown-content h4 {
|
||||
page-break-after: avoid;
|
||||
}
|
||||
|
||||
.markdown-content p,
|
||||
.markdown-content h2,
|
||||
.markdown-content h3 {
|
||||
orphans: 3;
|
||||
widows: 3;
|
||||
}
|
||||
}
|
||||
|
||||
/* Additional elements */
|
||||
.markdown-content details {
|
||||
margin: 1.5rem 0;
|
||||
padding: 0.5rem 1rem;
|
||||
background-color: #f3f4f6;
|
||||
border-radius: 0.5rem;
|
||||
border: 1px solid #e5e7eb;
|
||||
}
|
||||
|
||||
.dark .markdown-content details {
|
||||
background-color: #1f2937;
|
||||
border-color: #374151;
|
||||
}
|
||||
|
||||
.markdown-content details summary {
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
padding: 0.5rem 0;
|
||||
}
|
||||
|
||||
.markdown-content details[open] summary {
|
||||
margin-bottom: 0.5rem;
|
||||
border-bottom: 1px solid #e5e7eb;
|
||||
}
|
||||
|
||||
.dark .markdown-content details[open] summary {
|
||||
border-bottom-color: #374151;
|
||||
}
|
||||
|
||||
/* Keyboard shortcuts */
|
||||
.markdown-content kbd {
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
||||
font-size: 0.8em;
|
||||
padding: 0.2em 0.4em;
|
||||
margin: 0 0.1em;
|
||||
background-color: #f3f4f6;
|
||||
border: 1px solid #d1d5db;
|
||||
border-radius: 0.25rem;
|
||||
box-shadow: 0 1px 0 #d1d5db;
|
||||
}
|
||||
|
||||
.dark .markdown-content kbd {
|
||||
background-color: #1f2937;
|
||||
border-color: #4b5563;
|
||||
box-shadow: 0 1px 0 #4b5563;
|
||||
}
|
||||
|
||||
/* Abbreviations */
|
||||
.markdown-content abbr {
|
||||
cursor: help;
|
||||
text-decoration: underline dotted;
|
||||
}
|
||||
|
||||
/* Highlight text */
|
||||
.markdown-content mark {
|
||||
background-color: #fef3c7;
|
||||
color: #92400e;
|
||||
padding: 0.1em 0.2em;
|
||||
border-radius: 0.25rem;
|
||||
}
|
||||
|
||||
.dark .markdown-content mark {
|
||||
background-color: rgba(254, 243, 199, 0.2);
|
||||
color: #fbbf24;
|
||||
}
|
||||
|
||||
/* Subscript and superscript */
|
||||
.markdown-content sub,
|
||||
.markdown-content sup {
|
||||
font-size: 0.75em;
|
||||
line-height: 0;
|
||||
position: relative;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
.markdown-content sup {
|
||||
top: -0.5em;
|
||||
}
|
||||
|
||||
.markdown-content sub {
|
||||
bottom: -0.25em;
|
||||
}
|
||||
|
||||
/* Diagrams and charts */
|
||||
.markdown-content .mermaid {
|
||||
margin: 1.5rem 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* Math equations */
|
||||
.markdown-content .math {
|
||||
overflow-x: auto;
|
||||
margin: 1.5rem 0;
|
||||
}
|
||||
|
||||
/* Embedded content */
|
||||
.markdown-content iframe {
|
||||
max-width: 100%;
|
||||
margin: 1.5rem 0;
|
||||
border-radius: 0.5rem;
|
||||
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
|
||||
}
|
Reference in New Issue
Block a user