refactor(layouts): extract common layout components to base template
Move shared HTML structure, head metadata, and theme scripts to Layout.astro Remove duplicate code from BlogLayout, BlogPostLayout, and AboutLayout Keep page-specific content and styling in individual layout files
This commit is contained in:
@@ -1,10 +1,10 @@
|
||||
---
|
||||
import Layout from './Layout.astro';
|
||||
import type { MarkdownLayoutProps } from 'astro';
|
||||
import { type Lang, type FrontmatterProps } from '@/types';
|
||||
import { defaultLang } from '@/i18n/ui';
|
||||
import GlassHeader from '@/components/GlassHeader';
|
||||
import Footer from '@/components/Footer';
|
||||
import "../styles/global.css";
|
||||
|
||||
// Use Astro's MarkdownLayoutProps for proper type safety
|
||||
export type Props = MarkdownLayoutProps<FrontmatterProps>;
|
||||
@@ -16,93 +16,73 @@ const { title, description } = frontmatter;
|
||||
const lang = Astro.currentLocale as Lang || defaultLang;
|
||||
---
|
||||
|
||||
<!doctype html>
|
||||
<html lang={lang}>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="description" content={description} />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||
<meta name="generator" content={Astro.generator} />
|
||||
<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="min-h-screen bg-background font-sans antialiased selection:bg-purple-500/20 selection:text-purple-500">
|
||||
<!-- Enhanced background with gradient overlay -->
|
||||
<div class="fixed inset-0 -z-10 h-full w-full bg-background">
|
||||
<!-- Base radial gradient -->
|
||||
<div class="absolute inset-0 bg-[radial-gradient(ellipse_80%_80%_at_50%_-20%,rgba(120,119,198,0.3),rgba(255,255,255,0))] dark:bg-[radial-gradient(ellipse_80%_80%_at_50%_-20%,rgba(120,119,198,0.2),rgba(0,0,0,0))]">
|
||||
</div>
|
||||
<!-- Additional subtle gradient for about page -->
|
||||
<div class="absolute inset-0 bg-gradient-to-br from-purple-50/30 via-transparent to-blue-50/20 dark:from-purple-950/20 dark:via-transparent dark:to-blue-950/10">
|
||||
</div>
|
||||
<Layout title={title} description={description}>
|
||||
<!-- Enhanced background with gradient overlay -->
|
||||
<div class="fixed inset-0 -z-10 h-full w-full bg-background">
|
||||
<!-- Additional subtle gradient for about page -->
|
||||
<div class="absolute inset-0 bg-gradient-to-br from-purple-50/30 via-transparent to-blue-50/20 dark:from-purple-950/20 dark:via-transparent dark:to-blue-950/10">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Glass Header with navigation -->
|
||||
<GlassHeader lang={lang} client:load />
|
||||
<!-- Glass Header with navigation -->
|
||||
<GlassHeader lang={lang} client:load />
|
||||
|
||||
<!-- Main content with proper spacing for fixed header -->
|
||||
<div class="pt-16 sm:pt-20">
|
||||
<div class="container mx-auto px-4 sm:px-6 lg:px-8 py-6 sm:py-8">
|
||||
<div class="max-w-7xl mx-auto">
|
||||
<div class="grid grid-cols-1 xl:grid-cols-4 gap-6 lg:gap-8">
|
||||
<!-- Main Content -->
|
||||
<main class="xl:col-span-3 order-2 xl:order-1">
|
||||
<!-- About page header -->
|
||||
<header class="mb-10">
|
||||
<h1 class="text-3xl sm:text-4xl lg:text-5xl font-bold text-foreground mb-6 leading-tight tracking-tight">
|
||||
✨ {title}
|
||||
</h1>
|
||||
<!-- Main content with proper spacing for fixed header -->
|
||||
<div class="pt-16 sm:pt-20">
|
||||
<div class="container mx-auto px-4 sm:px-6 lg:px-8 py-6 sm:py-8">
|
||||
<div class="max-w-7xl mx-auto">
|
||||
<div class="grid grid-cols-1 xl:grid-cols-4 gap-6 lg:gap-8">
|
||||
<!-- Main Content -->
|
||||
<main class="xl:col-span-3 order-2 xl:order-1">
|
||||
<!-- About page header -->
|
||||
<header class="mb-10">
|
||||
<h1 class="text-3xl sm:text-4xl lg:text-5xl font-bold text-foreground mb-6 leading-tight tracking-tight">
|
||||
✨ {title}
|
||||
</h1>
|
||||
|
||||
{description && (
|
||||
<p class="text-lg sm:text-xl text-muted-foreground mb-8 leading-relaxed max-w-4xl">
|
||||
📝 {description}
|
||||
</p>
|
||||
)}
|
||||
</header>
|
||||
{description && (
|
||||
<p class="text-lg sm:text-xl text-muted-foreground mb-8 leading-relaxed max-w-4xl">
|
||||
📝 {description}
|
||||
</p>
|
||||
)}
|
||||
</header>
|
||||
|
||||
<!-- About page content with typography styles -->
|
||||
<article class="prose prose-lg dark:prose-invert max-w-none prose-headings:scroll-mt-24 prose-headings:font-bold prose-headings:tracking-tight prose-h1:text-2xl sm:prose-h1:text-3xl prose-h2:text-xl sm:prose-h2:text-2xl prose-h3:text-lg sm:prose-h3:text-xl prose-h4:text-base sm:prose-h4:text-lg prose-p:text-base sm:prose-p:text-lg prose-p:leading-relaxed prose-p:mb-6 prose-a:text-primary prose-a:no-underline hover:prose-a:underline prose-strong:text-foreground prose-code:text-sm prose-code:px-2 prose-code:py-1 prose-code:rounded prose-pre:bg-muted prose-pre:border prose-pre:text-sm prose-blockquote:border-l-primary prose-blockquote:bg-muted/30 prose-blockquote:text-base prose-li:text-base sm:prose-li:text-lg prose-li:leading-relaxed">
|
||||
<slot />
|
||||
</article>
|
||||
</main>
|
||||
<!-- About page content with typography styles -->
|
||||
<article class="prose prose-lg dark:prose-invert max-w-none prose-headings:scroll-mt-24 prose-headings:font-bold prose-headings:tracking-tight prose-h1:text-2xl sm:prose-h1:text-3xl prose-h2:text-xl sm:prose-h2:text-2xl prose-h3:text-lg sm:prose-h3:text-xl prose-h4:text-base sm:prose-h4:text-lg prose-p:text-base sm:prose-p:text-lg prose-p:leading-relaxed prose-p:mb-6 prose-a:text-primary prose-a:no-underline hover:prose-a:underline prose-strong:text-foreground prose-code:text-sm prose-code:px-2 prose-code:py-1 prose-code:rounded prose-pre:bg-muted prose-pre:border prose-pre:text-sm prose-blockquote:border-l-primary prose-blockquote:bg-muted/30 prose-blockquote:text-base prose-li:text-base sm:prose-li:text-lg prose-li:leading-relaxed">
|
||||
<slot />
|
||||
</article>
|
||||
</main>
|
||||
|
||||
<!-- Sidebar with Table of Contents -->
|
||||
<aside class="xl:col-span-1 order-1 xl:order-2">
|
||||
<div class="xl:sticky xl:top-24 space-y-6 sm:space-y-8 xl:space-y-12 xl:max-h-[calc(100vh-6rem)] xl:overflow-y-auto">
|
||||
<!-- Table of Contents -->
|
||||
<div class="max-xl:hidden">
|
||||
<div id="nav-content" class="sticky xl:w-72 w-full top-14">
|
||||
<div class="flex flex-col gap-3 p-4">
|
||||
<h3 class="dark:text-zinc-200 text-blacktext font-bold tracking-wide text-sm sm:text-base uppercase flex items-center">
|
||||
<svg class="w-4 h-4 sm:w-5 sm:h-5 mr-2 text-purple-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 10h16M4 14h16M4 18h16" />
|
||||
</svg>
|
||||
{lang === 'zh' ? '目录' : 'Table of Contents'}
|
||||
</h3>
|
||||
<div class="text-neutral-500 dark:text-neutral-300 pr-4">
|
||||
<ul id="toc-list" class="leading-relaxed text-sm sm:text-base border-l dark:border-neutral-500/20 border-blacktext/20 mt-4">
|
||||
</ul>
|
||||
</div>
|
||||
<!-- Sidebar with Table of Contents -->
|
||||
<aside class="xl:col-span-1 order-1 xl:order-2">
|
||||
<div class="xl:sticky xl:top-24 space-y-6 sm:space-y-8 xl:space-y-12 xl:max-h-[calc(100vh-6rem)] xl:overflow-y-auto">
|
||||
<!-- Table of Contents -->
|
||||
<div class="max-xl:hidden">
|
||||
<div id="nav-content" class="sticky xl:w-72 w-full top-14">
|
||||
<div class="flex flex-col gap-3 p-4">
|
||||
<h3 class="dark:text-zinc-200 text-blacktext font-bold tracking-wide text-sm sm:text-base uppercase flex items-center">
|
||||
<svg class="w-4 h-4 sm:w-5 sm:h-5 mr-2 text-purple-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 10h16M4 14h16M4 18h16" />
|
||||
</svg>
|
||||
{lang === 'zh' ? '目录' : 'Table of Contents'}
|
||||
</h3>
|
||||
<div class="text-neutral-500 dark:text-neutral-300 pr-4">
|
||||
<ul id="toc-list" class="leading-relaxed text-sm sm:text-base border-l dark:border-neutral-500/20 border-blacktext/20 mt-4">
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Footer -->
|
||||
<Footer lang={lang} client:load />
|
||||
</body>
|
||||
</html>
|
||||
<!-- Footer -->
|
||||
<Footer lang={lang} client:load />
|
||||
</Layout>
|
||||
|
||||
<script>
|
||||
/**
|
||||
@@ -206,30 +186,6 @@ const lang = Astro.currentLocale as Lang || defaultLang;
|
||||
});
|
||||
</script>
|
||||
|
||||
<script is:inline>
|
||||
const getThemePreference = () => {
|
||||
if (typeof localStorage !== "undefined" && localStorage.getItem("theme")) {
|
||||
return localStorage.getItem("theme");
|
||||
}
|
||||
return window.matchMedia("(prefers-color-scheme: dark)").matches
|
||||
? "dark"
|
||||
: "light";
|
||||
};
|
||||
const isDark = getThemePreference() === "dark";
|
||||
document.documentElement.classList[isDark ? "add" : "remove"]("dark");
|
||||
|
||||
if (typeof localStorage !== "undefined") {
|
||||
const observer = new MutationObserver(() => {
|
||||
const isDark = document.documentElement.classList.contains("dark");
|
||||
localStorage.setItem("theme", isDark ? "dark" : "light");
|
||||
});
|
||||
observer.observe(document.documentElement, {
|
||||
attributes: true,
|
||||
attributeFilter: ["class"],
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
/* Additional styling for smooth transitions */
|
||||
#toc-list a:hover {
|
||||
@@ -245,23 +201,4 @@ const lang = Astro.currentLocale as Lang || defaultLang;
|
||||
#toc-list a {
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
|
||||
:root {
|
||||
--transition-standard: 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
body {
|
||||
transition:
|
||||
background-color var(--transition-standard),
|
||||
color var(--transition-standard);
|
||||
}
|
||||
</style>
|
||||
@@ -1,9 +1,9 @@
|
||||
---
|
||||
import Layout from './Layout.astro';
|
||||
import { type Lang } from '@/i18n/utils';
|
||||
import { defaultLang } from '@/i18n/ui';
|
||||
import GlassHeader from '@/components/GlassHeader';
|
||||
import Footer from '@/components/Footer';
|
||||
import "../styles/global.css";
|
||||
|
||||
export interface Props {
|
||||
title: string;
|
||||
@@ -14,84 +14,21 @@ const { title, description = 'Explore my latest thoughts on coding, tech trends,
|
||||
const lang = Astro.currentLocale as Lang || defaultLang;
|
||||
---
|
||||
|
||||
<!doctype html>
|
||||
<html lang={lang}>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="description" content={description} />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||
<meta name="generator" content={Astro.generator} />
|
||||
<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="min-h-screen bg-background font-sans antialiased selection:bg-purple-500/20 selection:text-purple-500">
|
||||
<!-- Enhanced background with gradient overlay for better visual consistency -->
|
||||
<div class="fixed inset-0 -z-10 h-full w-full bg-background">
|
||||
<!-- Base radial gradient -->
|
||||
<div class="absolute inset-0 bg-[radial-gradient(ellipse_80%_80%_at_50%_-20%,rgba(120,119,198,0.3),rgba(255,255,255,0))] dark:bg-[radial-gradient(ellipse_80%_80%_at_50%_-20%,rgba(120,119,198,0.2),rgba(0,0,0,0))]"></div>
|
||||
<!-- Additional subtle gradient for blog pages -->
|
||||
<div class="absolute inset-0 bg-gradient-to-br from-purple-50/30 via-transparent to-blue-50/20 dark:from-purple-950/20 dark:via-transparent dark:to-blue-950/10"></div>
|
||||
</div>
|
||||
<!-- Glass Header with navigation -->
|
||||
<GlassHeader lang={lang} client:load />
|
||||
<Layout title={title} description={description}>
|
||||
<!-- Enhanced background with gradient overlay for better visual consistency -->
|
||||
<div class="fixed inset-0 -z-10 h-full w-full bg-background">
|
||||
<!-- Additional subtle gradient for blog pages -->
|
||||
<div class="absolute inset-0 bg-gradient-to-br from-purple-50/30 via-transparent to-blue-50/20 dark:from-purple-950/20 dark:via-transparent dark:to-blue-950/10"></div>
|
||||
</div>
|
||||
|
||||
<!-- Main content with proper spacing for fixed header -->
|
||||
<div class="pt-16">
|
||||
<slot />
|
||||
</div>
|
||||
<!-- Glass Header with navigation -->
|
||||
<GlassHeader lang={lang} client:load />
|
||||
|
||||
<!-- Footer -->
|
||||
<Footer client:load />
|
||||
</body>
|
||||
</html>
|
||||
<!-- Main content with proper spacing for fixed header -->
|
||||
<div class="pt-16">
|
||||
<slot />
|
||||
</div>
|
||||
|
||||
<script is:inline>
|
||||
const getThemePreference = () => {
|
||||
if (typeof localStorage !== "undefined" && localStorage.getItem("theme")) {
|
||||
return localStorage.getItem("theme");
|
||||
}
|
||||
return window.matchMedia("(prefers-color-scheme: dark)").matches
|
||||
? "dark"
|
||||
: "light";
|
||||
};
|
||||
const isDark = getThemePreference() === "dark";
|
||||
document.documentElement.classList[isDark ? "add" : "remove"]("dark");
|
||||
|
||||
if (typeof localStorage !== "undefined") {
|
||||
const observer = new MutationObserver(() => {
|
||||
const isDark = document.documentElement.classList.contains("dark");
|
||||
localStorage.setItem("theme", isDark ? "dark" : "light");
|
||||
});
|
||||
observer.observe(document.documentElement, {
|
||||
attributes: true,
|
||||
attributeFilter: ["class"],
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
html,
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
|
||||
:root {
|
||||
--transition-standard: 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
body {
|
||||
transition:
|
||||
background-color var(--transition-standard),
|
||||
color var(--transition-standard);
|
||||
}
|
||||
</style>
|
||||
<!-- Footer -->
|
||||
<Footer client:load />
|
||||
</Layout>
|
||||
@@ -1,4 +1,5 @@
|
||||
---
|
||||
import Layout from './Layout.astro';
|
||||
import type { MarkdownLayoutProps } from 'astro';
|
||||
import { type Lang } from '@/i18n/utils';
|
||||
import { type FrontmatterProps } from '@/types';
|
||||
@@ -10,8 +11,6 @@ import TableOfContents from '@/components/layout/TableOfContents.astro';
|
||||
import BlogNavigation from '@/components/layout/BlogNavigation.astro';
|
||||
import PostMeta from '@/components/blog/PostMeta.astro';
|
||||
|
||||
import "../styles/global.css";
|
||||
|
||||
// Use Astro's MarkdownLayoutProps for proper type safety
|
||||
export type Props = MarkdownLayoutProps<FrontmatterProps>;
|
||||
|
||||
@@ -36,143 +35,77 @@ const finalPublishDate = publishDate || date;
|
||||
const finalReadingTime = readTime ? parseInt(readTime.replace(/\D/g, '')) : undefined;
|
||||
---
|
||||
|
||||
<!doctype html>
|
||||
<html lang={lang}>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="description" content={description} />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||
<meta name="generator" content={Astro.generator} />
|
||||
<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="min-h-screen bg-background font-sans antialiased selection:bg-purple-500/20 selection:text-purple-500">
|
||||
<div
|
||||
class="fixed inset-0 -z-10 h-full w-full bg-background bg-[radial-gradient(ellipse_80%_80%_at_50%_-20%,rgba(120,119,198,0.3),rgba(255,255,255,0))]"
|
||||
>
|
||||
</div>
|
||||
<!-- Glass Header with navigation -->
|
||||
<GlassHeader lang={lang} client:load />
|
||||
<Layout title={title} description={description}>
|
||||
<!-- Glass Header with navigation -->
|
||||
<GlassHeader lang={lang} client:load />
|
||||
|
||||
<!-- Main content with proper spacing for fixed header -->
|
||||
<div class="pt-16 sm:pt-20">
|
||||
<div class="container mx-auto px-4 sm:px-6 lg:px-8 py-6 sm:py-8">
|
||||
<div class="max-w-7xl mx-auto">
|
||||
<div class="grid grid-cols-1 xl:grid-cols-4 gap-6 lg:gap-8">
|
||||
<!-- Main Content -->
|
||||
<main class="xl:col-span-3 order-2 xl:order-1">
|
||||
|
||||
<!-- Blog post header -->
|
||||
<header class="mb-10">
|
||||
<h1 class="text-3xl sm:text-4xl lg:text-5xl font-bold text-foreground mb-6 leading-tight tracking-tight">
|
||||
{title}
|
||||
</h1>
|
||||
|
||||
<!-- Main content with proper spacing for fixed header -->
|
||||
<div class="pt-16 sm:pt-20">
|
||||
<div class="container mx-auto px-4 sm:px-6 lg:px-8 py-6 sm:py-8">
|
||||
<div class="max-w-7xl mx-auto">
|
||||
<div class="grid grid-cols-1 xl:grid-cols-4 gap-6 lg:gap-8">
|
||||
<!-- Main Content -->
|
||||
<main class="xl:col-span-3 order-2 xl:order-1">
|
||||
{description && (
|
||||
<p class="text-lg sm:text-xl text-muted-foreground mb-8 leading-relaxed max-w-4xl">
|
||||
{description}
|
||||
</p>
|
||||
)}
|
||||
|
||||
<!-- Blog post header -->
|
||||
<header class="mb-10">
|
||||
<h1 class="text-3xl sm:text-4xl lg:text-5xl font-bold text-foreground mb-6 leading-tight tracking-tight">
|
||||
{title}
|
||||
</h1>
|
||||
<PostMeta
|
||||
lang={lang}
|
||||
publishDate={finalPublishDate}
|
||||
readingTime={finalReadingTime}
|
||||
tags={tags}
|
||||
tagId={tagId}
|
||||
category={category}
|
||||
categoryId={categoryId}
|
||||
className="justify-start"
|
||||
/>
|
||||
</header>
|
||||
|
||||
{description && (
|
||||
<p class="text-lg sm:text-xl text-muted-foreground mb-8 leading-relaxed max-w-4xl">
|
||||
{description}
|
||||
</p>
|
||||
)}
|
||||
<!-- Blog post content with typography styles -->
|
||||
<article class="prose prose-lg dark:prose-invert max-w-none prose-headings:scroll-mt-24 prose-headings:font-bold prose-headings:tracking-tight prose-h1:text-2xl sm:prose-h1:text-3xl prose-h2:text-xl sm:prose-h2:text-2xl prose-h3:text-lg sm:prose-h3:text-xl prose-h4:text-base sm:prose-h4:text-lg prose-p:text-base sm:prose-p:text-lg prose-p:leading-relaxed prose-p:mb-6 prose-a:text-primary prose-a:no-underline hover:prose-a:underline prose-strong:text-foreground prose-code:text-sm prose-code:px-2 prose-code:py-1 prose-code:rounded prose-pre:bg-muted prose-pre:border prose-pre:text-sm prose-blockquote:border-l-primary prose-blockquote:bg-muted/30 prose-blockquote:text-base prose-li:text-base sm:prose-li:text-lg prose-li:leading-relaxed">
|
||||
<slot />
|
||||
</article>
|
||||
|
||||
<PostMeta
|
||||
lang={lang}
|
||||
publishDate={finalPublishDate}
|
||||
readingTime={finalReadingTime}
|
||||
tags={tags}
|
||||
tagId={tagId}
|
||||
category={category}
|
||||
categoryId={categoryId}
|
||||
className="justify-start"
|
||||
/>
|
||||
</header>
|
||||
<!-- Blog Navigation -->
|
||||
<div class="mt-8 sm:mt-12">
|
||||
<BlogNavigation />
|
||||
</div>
|
||||
|
||||
<!-- Blog post content with typography styles -->
|
||||
<article class="prose prose-lg dark:prose-invert max-w-none prose-headings:scroll-mt-24 prose-headings:font-bold prose-headings:tracking-tight prose-h1:text-2xl sm:prose-h1:text-3xl prose-h2:text-xl sm:prose-h2:text-2xl prose-h3:text-lg sm:prose-h3:text-xl prose-h4:text-base sm:prose-h4:text-lg prose-p:text-base sm:prose-p:text-lg prose-p:leading-relaxed prose-p:mb-6 prose-a:text-primary prose-a:no-underline hover:prose-a:underline prose-strong:text-foreground prose-code:text-sm prose-code:px-2 prose-code:py-1 prose-code:rounded prose-pre:bg-muted prose-pre:border prose-pre:text-sm prose-blockquote:border-l-primary prose-blockquote:bg-muted/30 prose-blockquote:text-base prose-li:text-base sm:prose-li:text-lg prose-li:leading-relaxed">
|
||||
<slot />
|
||||
</article>
|
||||
|
||||
<!-- Blog Navigation -->
|
||||
<div class="mt-8 sm:mt-12">
|
||||
<BlogNavigation />
|
||||
<!-- Author Card moved to bottom with enhanced styling -->
|
||||
<div class="mt-10 sm:mt-16 border-t border-border pt-10">
|
||||
<div class="bg-card/30 backdrop-blur-sm rounded-2xl border border-border p-6 shadow-sm hover:shadow-md transition-all duration-300">
|
||||
<AuthorCard lang={lang} />
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<!-- Author Card moved to bottom with enhanced styling -->
|
||||
<div class="mt-10 sm:mt-16 border-t border-border pt-10">
|
||||
<div class="bg-card/30 backdrop-blur-sm rounded-2xl border border-border p-6 shadow-sm hover:shadow-md transition-all duration-300">
|
||||
<AuthorCard lang={lang} />
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<!-- Sidebar with Table of Contents -->
|
||||
<aside class="xl:col-span-1 order-1 xl:order-2 md:mb-0 mb-6">
|
||||
<div class="xl:sticky xl:top-24">
|
||||
<!-- Table of Contents without height restriction -->
|
||||
<TableOfContents lang={lang} />
|
||||
</div>
|
||||
</aside>
|
||||
</div>
|
||||
<!-- Sidebar with Table of Contents -->
|
||||
<aside class="xl:col-span-1 order-1 xl:order-2 md:mb-0 mb-6">
|
||||
<div class="xl:sticky xl:top-24">
|
||||
<!-- Table of Contents without height restriction -->
|
||||
<TableOfContents lang={lang} />
|
||||
</div>
|
||||
</aside>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Footer -->
|
||||
<Footer lang={lang} client:load />
|
||||
</body>
|
||||
</html>
|
||||
|
||||
<script is:inline>
|
||||
const getThemePreference = () => {
|
||||
if (typeof localStorage !== "undefined" && localStorage.getItem("theme")) {
|
||||
return localStorage.getItem("theme");
|
||||
}
|
||||
return window.matchMedia("(prefers-color-scheme: dark)").matches
|
||||
? "dark"
|
||||
: "light";
|
||||
};
|
||||
const isDark = getThemePreference() === "dark";
|
||||
document.documentElement.classList[isDark ? "add" : "remove"]("dark");
|
||||
|
||||
if (typeof localStorage !== "undefined") {
|
||||
const observer = new MutationObserver(() => {
|
||||
const isDark = document.documentElement.classList.contains("dark");
|
||||
localStorage.setItem("theme", isDark ? "dark" : "light");
|
||||
});
|
||||
observer.observe(document.documentElement, {
|
||||
attributes: true,
|
||||
attributeFilter: ["class"],
|
||||
});
|
||||
}
|
||||
</script>
|
||||
<!-- Footer -->
|
||||
<Footer lang={lang} client:load />
|
||||
</Layout>
|
||||
|
||||
<style>
|
||||
html,
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
|
||||
:root {
|
||||
--transition-standard: 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
body {
|
||||
transition:
|
||||
background-color var(--transition-standard),
|
||||
color var(--transition-standard);
|
||||
}
|
||||
|
||||
/* Enhanced prose styles for better readability */
|
||||
.prose {
|
||||
line-height: 1.75;
|
||||
|
||||
Reference in New Issue
Block a user