initial commit

This commit is contained in:
Jiao77
2026-03-01 09:13:24 +08:00
commit 72baa341cc
43 changed files with 12560 additions and 0 deletions

View File

@@ -0,0 +1,224 @@
---
import '../styles/global.css';
interface Props {
title: string;
description?: string;
image?: string;
canonicalURL?: string;
}
const {
title,
description = 'NovaBlog - 一个极简、高效的程序员博客系统',
image = '/og-default.png',
canonicalURL = Astro.url.href,
} = Astro.props;
const { site } = Astro;
const socialImageURL = image.startsWith('http') ? image : new URL(image, site).href;
---
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="generator" content={Astro.generator} />
<!-- SEO Meta Tags -->
<title>{title}</title>
<meta name="description" content={description} />
<link rel="canonical" href={canonicalURL} />
<!-- Open Graph -->
<meta property="og:type" content="website" />
<meta property="og:url" content={canonicalURL} />
<meta property="og:title" content={title} />
<meta property="og:description" content={description} />
<meta property="og:image" content={socialImageURL} />
<!-- Twitter Card -->
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content={title} />
<meta name="twitter:description" content={description} />
<meta name="twitter:image" content={socialImageURL} />
<!-- Favicon -->
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
<!-- Preload Fonts -->
<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@400;500;600;700&family=JetBrains+Mono:wght@400;500&family=Noto+Serif+SC:wght@400;600&display=swap"
rel="stylesheet"
/>
<!-- Dark Mode Script (防止闪烁) -->
<script is:inline>
const theme = (() => {
if (typeof localStorage !== 'undefined' && localStorage.getItem('theme')) {
return localStorage.getItem('theme');
}
if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
return 'dark';
}
return 'light';
})();
if (theme === 'dark') {
document.documentElement.classList.add('dark');
} else {
document.documentElement.classList.remove('dark');
}
</script>
</head>
<body class="min-h-screen flex flex-col bg-background text-foreground">
<!-- Header -->
<header class="sticky top-0 z-50 glass border-b border-border">
<nav class="content-width h-16 flex items-center justify-between">
<!-- Logo -->
<a href="/" class="flex items-center gap-2 text-xl font-bold hover:text-primary-500 transition-colors">
<svg class="w-8 h-8" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="32" height="32" rx="8" fill="url(#logo-gradient)" />
<path
d="M8 12L16 8L24 12V20L16 24L8 20V12Z"
stroke="white"
stroke-width="2"
stroke-linejoin="round"
/>
<circle cx="16" cy="16" r="3" fill="white" />
<defs>
<linearGradient id="logo-gradient" x1="0" y1="0" x2="32" y2="32" gradientUnits="userSpaceOnUse">
<stop stop-color="#0ea5e9" />
<stop offset="1" stop-color="#8b5cf6" />
</linearGradient>
</defs>
</svg>
<span class="gradient-text">NovaBlog</span>
</a>
<!-- Navigation Links -->
<div class="hidden md:flex items-center gap-6">
<a href="/" class="text-foreground/70 hover:text-foreground transition-colors">首页</a>
<a href="/blog" class="text-foreground/70 hover:text-foreground transition-colors">博客</a>
<a href="/tags" class="text-foreground/70 hover:text-foreground transition-colors">标签</a>
<a href="/about" class="text-foreground/70 hover:text-foreground transition-colors">关于</a>
</div>
<!-- Actions -->
<div class="flex items-center gap-3">
<!-- Login Link -->
<a href="/login" class="btn-ghost px-3 py-1.5 rounded-lg text-sm font-medium">
登录
</a>
<!-- Theme Toggle -->
<button
id="theme-toggle"
class="btn-ghost p-2 rounded-lg"
aria-label="切换主题"
>
<svg class="w-5 h-5 hidden dark:block" fill="currentColor" viewBox="0 0 20 20">
<path
d="M10 2a1 1 0 011 1v1a1 1 0 11-2 0V3a1 1 0 011-1zm4 8a4 4 0 11-8 0 4 4 0 018 0zm-.464 4.95l.707.707a1 1 0 001.414-1.414l-.707-.707a1 1 0 00-1.414 1.414zm2.12-10.607a1 1 0 010 1.414l-.706.707a1 1 0 11-1.414-1.414l.707-.707a1 1 0 011.414 0zM17 11a1 1 0 100-2h-1a1 1 0 100 2h1zm-7 4a1 1 0 011 1v1a1 1 0 11-2 0v-1a1 1 0 011-1zM5.05 6.464A1 1 0 106.465 5.05l-.708-.707a1 1 0 00-1.414 1.414l.707.707zm1.414 8.486l-.707.707a1 1 0 01-1.414-1.414l.707-.707a1 1 0 011.414 1.414zM4 11a1 1 0 100-2H3a1 1 0 000 2h1z"
/>
</svg>
<svg class="w-5 h-5 block dark:hidden" fill="currentColor" viewBox="0 0 20 20">
<path
d="M17.293 13.293A8 8 0 016.707 2.707a8.001 8.001 0 1010.586 10.586z"
/>
</svg>
</button>
<!-- Mobile Menu Toggle -->
<button
id="mobile-menu-toggle"
class="btn-ghost p-2 rounded-lg md:hidden"
aria-label="打开菜单"
>
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16" />
</svg>
</button>
</div>
</nav>
<!-- Mobile Menu -->
<div id="mobile-menu" class="hidden md:hidden border-t border-border">
<div class="content-width py-4 flex flex-col gap-3">
<a href="/" class="text-foreground/70 hover:text-foreground transition-colors py-2">首页</a>
<a href="/blog" class="text-foreground/70 hover:text-foreground transition-colors py-2">博客</a>
<a href="/tags" class="text-foreground/70 hover:text-foreground transition-colors py-2">标签</a>
<a href="/about" class="text-foreground/70 hover:text-foreground transition-colors py-2">关于</a>
</div>
</div>
</header>
<!-- Main Content -->
<main class="flex-1">
<slot />
</main>
<!-- Footer -->
<footer class="border-t border-border bg-muted/30">
<div class="content-width py-12">
<div class="grid grid-cols-1 md:grid-cols-3 gap-8">
<!-- About -->
<div>
<h3 class="font-semibold text-lg mb-4">NovaBlog</h3>
<p class="text-foreground/60 text-sm leading-relaxed">
一个极简、高效的程序员博客系统。支持 MDX、Typst 学术排版,静态渲染 + 轻量级微服务架构。
</p>
</div>
<!-- Links -->
<div>
<h3 class="font-semibold text-lg mb-4">快速链接</h3>
<ul class="space-y-2 text-sm">
<li><a href="/blog" class="text-foreground/60 hover:text-primary-500 transition-colors">全部文章</a></li>
<li><a href="/tags" class="text-foreground/60 hover:text-primary-500 transition-colors">标签分类</a></li>
<li><a href="/about" class="text-foreground/60 hover:text-primary-500 transition-colors">关于我</a></li>
<li><a href="/rss.xml" class="text-foreground/60 hover:text-primary-500 transition-colors">RSS 订阅</a></li>
</ul>
</div>
<!-- Tech Stack -->
<div>
<h3 class="font-semibold text-lg mb-4">技术栈</h3>
<ul class="space-y-2 text-sm text-foreground/60">
<li>前端: Astro + Vue 3 + Tailwind CSS</li>
<li>后端: Go + Gin + SQLite</li>
<li>部署: Docker + Nginx</li>
</ul>
</div>
</div>
<!-- Copyright -->
<div class="mt-8 pt-8 border-t border-border text-center text-sm text-foreground/40">
<p>&copy; {new Date().getFullYear()} NovaBlog. All rights reserved.</p>
</div>
</div>
</footer>
<!-- Theme Toggle Script -->
<script>
const themeToggle = document.getElementById('theme-toggle');
const mobileMenuToggle = document.getElementById('mobile-menu-toggle');
const mobileMenu = document.getElementById('mobile-menu');
// Theme toggle
themeToggle?.addEventListener('click', () => {
const isDark = document.documentElement.classList.toggle('dark');
localStorage.setItem('theme', isDark ? 'dark' : 'light');
});
// Mobile menu toggle
mobileMenuToggle?.addEventListener('click', () => {
mobileMenu?.classList.toggle('hidden');
});
</script>
</body>
</html>