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 { MarkdownLayoutProps } from 'astro';
|
||||||
import { type Lang, type FrontmatterProps } from '@/types';
|
import { type Lang, type FrontmatterProps } from '@/types';
|
||||||
import { defaultLang } from '@/i18n/ui';
|
import { defaultLang } from '@/i18n/ui';
|
||||||
import GlassHeader from '@/components/GlassHeader';
|
import GlassHeader from '@/components/GlassHeader';
|
||||||
import Footer from '@/components/Footer';
|
import Footer from '@/components/Footer';
|
||||||
import "../styles/global.css";
|
|
||||||
|
|
||||||
// Use Astro's MarkdownLayoutProps for proper type safety
|
// Use Astro's MarkdownLayoutProps for proper type safety
|
||||||
export type Props = MarkdownLayoutProps<FrontmatterProps>;
|
export type Props = MarkdownLayoutProps<FrontmatterProps>;
|
||||||
@@ -16,28 +16,9 @@ const { title, description } = frontmatter;
|
|||||||
const lang = Astro.currentLocale as Lang || defaultLang;
|
const lang = Astro.currentLocale as Lang || defaultLang;
|
||||||
---
|
---
|
||||||
|
|
||||||
<!doctype html>
|
<Layout title={title} description={description}>
|
||||||
<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 -->
|
<!-- Enhanced background with gradient overlay -->
|
||||||
<div class="fixed inset-0 -z-10 h-full w-full bg-background">
|
<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 -->
|
<!-- 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 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>
|
||||||
@@ -101,8 +82,7 @@ const lang = Astro.currentLocale as Lang || defaultLang;
|
|||||||
|
|
||||||
<!-- Footer -->
|
<!-- Footer -->
|
||||||
<Footer lang={lang} client:load />
|
<Footer lang={lang} client:load />
|
||||||
</body>
|
</Layout>
|
||||||
</html>
|
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
/**
|
/**
|
||||||
@@ -206,30 +186,6 @@ const lang = Astro.currentLocale as Lang || defaultLang;
|
|||||||
});
|
});
|
||||||
</script>
|
</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>
|
<style>
|
||||||
/* Additional styling for smooth transitions */
|
/* Additional styling for smooth transitions */
|
||||||
#toc-list a:hover {
|
#toc-list a:hover {
|
||||||
@@ -245,23 +201,4 @@ const lang = Astro.currentLocale as Lang || defaultLang;
|
|||||||
#toc-list a {
|
#toc-list a {
|
||||||
transition: all 0.2s ease;
|
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>
|
</style>
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
---
|
---
|
||||||
|
import Layout from './Layout.astro';
|
||||||
import { type Lang } from '@/i18n/utils';
|
import { type Lang } from '@/i18n/utils';
|
||||||
import { defaultLang } from '@/i18n/ui';
|
import { defaultLang } from '@/i18n/ui';
|
||||||
import GlassHeader from '@/components/GlassHeader';
|
import GlassHeader from '@/components/GlassHeader';
|
||||||
import Footer from '@/components/Footer';
|
import Footer from '@/components/Footer';
|
||||||
import "../styles/global.css";
|
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
title: string;
|
title: string;
|
||||||
@@ -14,30 +14,13 @@ const { title, description = 'Explore my latest thoughts on coding, tech trends,
|
|||||||
const lang = Astro.currentLocale as Lang || defaultLang;
|
const lang = Astro.currentLocale as Lang || defaultLang;
|
||||||
---
|
---
|
||||||
|
|
||||||
<!doctype html>
|
<Layout title={title} description={description}>
|
||||||
<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 -->
|
<!-- Enhanced background with gradient overlay for better visual consistency -->
|
||||||
<div class="fixed inset-0 -z-10 h-full w-full bg-background">
|
<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 -->
|
<!-- 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 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>
|
</div>
|
||||||
|
|
||||||
<!-- Glass Header with navigation -->
|
<!-- Glass Header with navigation -->
|
||||||
<GlassHeader lang={lang} client:load />
|
<GlassHeader lang={lang} client:load />
|
||||||
|
|
||||||
@@ -48,50 +31,4 @@ const lang = Astro.currentLocale as Lang || defaultLang;
|
|||||||
|
|
||||||
<!-- Footer -->
|
<!-- Footer -->
|
||||||
<Footer client:load />
|
<Footer client:load />
|
||||||
</body>
|
</Layout>
|
||||||
</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>
|
|
||||||
|
|
||||||
<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>
|
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
---
|
---
|
||||||
|
import Layout from './Layout.astro';
|
||||||
import type { MarkdownLayoutProps } from 'astro';
|
import type { MarkdownLayoutProps } from 'astro';
|
||||||
import { type Lang } from '@/i18n/utils';
|
import { type Lang } from '@/i18n/utils';
|
||||||
import { type FrontmatterProps } from '@/types';
|
import { type FrontmatterProps } from '@/types';
|
||||||
@@ -10,8 +11,6 @@ import TableOfContents from '@/components/layout/TableOfContents.astro';
|
|||||||
import BlogNavigation from '@/components/layout/BlogNavigation.astro';
|
import BlogNavigation from '@/components/layout/BlogNavigation.astro';
|
||||||
import PostMeta from '@/components/blog/PostMeta.astro';
|
import PostMeta from '@/components/blog/PostMeta.astro';
|
||||||
|
|
||||||
import "../styles/global.css";
|
|
||||||
|
|
||||||
// Use Astro's MarkdownLayoutProps for proper type safety
|
// Use Astro's MarkdownLayoutProps for proper type safety
|
||||||
export type Props = MarkdownLayoutProps<FrontmatterProps>;
|
export type Props = MarkdownLayoutProps<FrontmatterProps>;
|
||||||
|
|
||||||
@@ -36,32 +35,10 @@ const finalPublishDate = publishDate || date;
|
|||||||
const finalReadingTime = readTime ? parseInt(readTime.replace(/\D/g, '')) : undefined;
|
const finalReadingTime = readTime ? parseInt(readTime.replace(/\D/g, '')) : undefined;
|
||||||
---
|
---
|
||||||
|
|
||||||
<!doctype html>
|
<Layout title={title} description={description}>
|
||||||
<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 -->
|
<!-- Glass Header with navigation -->
|
||||||
<GlassHeader lang={lang} client:load />
|
<GlassHeader lang={lang} client:load />
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<!-- Main content with proper spacing for fixed header -->
|
<!-- Main content with proper spacing for fixed header -->
|
||||||
<div class="pt-16 sm:pt-20">
|
<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="container mx-auto px-4 sm:px-6 lg:px-8 py-6 sm:py-8">
|
||||||
@@ -126,53 +103,9 @@ const finalReadingTime = readTime ? parseInt(readTime.replace(/\D/g, '')) : unde
|
|||||||
|
|
||||||
<!-- Footer -->
|
<!-- Footer -->
|
||||||
<Footer lang={lang} client:load />
|
<Footer lang={lang} client:load />
|
||||||
</body>
|
</Layout>
|
||||||
</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>
|
|
||||||
|
|
||||||
<style>
|
<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 */
|
/* Enhanced prose styles for better readability */
|
||||||
.prose {
|
.prose {
|
||||||
line-height: 1.75;
|
line-height: 1.75;
|
||||||
|
|||||||
Reference in New Issue
Block a user