forked from ParadigmEducation/snowflake-notes
feat(ui): 跟随系统主题并可显式覆盖;美化主题选择器;引入全局样式变量与排版样式
This commit is contained in:
@@ -20,12 +20,14 @@
|
||||
- 通用组件:`src/components/`(如 `Img.astro` 支持外部图床)
|
||||
- 内容扩展抽象:`src/lib/content/`(为未来 Typst 等格式预留)
|
||||
- 标签规范:根目录 `tags.config.yaml`(建议统一文章标签使用)
|
||||
- 全局样式:`src/styles/theme.css`, `src/styles/prose.css`(支持浅/深色与容器宽度等变量)
|
||||
|
||||
## 配置与类型
|
||||
|
||||
- `PUBLIC_MEDIA_BASE_URL`:外部图床前缀(避免图片入库)
|
||||
- `PUBLIC_SITE_URL`:站点地址(可选,用于 canonical 等)
|
||||
- 修改内容集合(src/content/config.ts)后,需在本机运行 `pnpm astro sync`(Node >= 18.20.8,建议 >= 20),以生成 `.astro/types.d.ts`,否则类型可能退化为 any。
|
||||
- 主题:`PUBLIC_THEME`(可选:`light`/`dark`/`system`,默认 `system`)
|
||||
|
||||
## 约定
|
||||
|
||||
|
||||
@@ -4,25 +4,50 @@ interface Props {
|
||||
}
|
||||
const { title } = Astro.props;
|
||||
const site = import.meta.env.PUBLIC_SITE_URL ?? '';
|
||||
const theme = import.meta.env.PUBLIC_THEME ?? 'system'; // 'light' | 'dark' | 'system'
|
||||
---
|
||||
<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<html lang="zh-CN" data-default-theme={theme}>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>{title ? `${title} | 学习博客` : '学习博客'}</title>
|
||||
<link rel="canonical" href={site} />
|
||||
<link rel="stylesheet" href="/src/styles/theme.css" />
|
||||
<link rel="stylesheet" href="/src/styles/prose.css" />
|
||||
</head>
|
||||
<body>
|
||||
<header style="padding: 12px; border-bottom: 1px solid #eee">
|
||||
<header class="site-header container">
|
||||
<a href="/">首页</a>
|
||||
<span style="margin: 0 8px">|</span>
|
||||
<a href="/posts">文章</a>
|
||||
<div style="flex:1"></div>
|
||||
<form method="dialog" class="select-wrap">
|
||||
<select name="theme" aria-label="主题" id="theme-select" class="theme-select">
|
||||
<option value="system" selected={theme === 'system'}>跟随系统</option>
|
||||
<option value="light" selected={theme === 'light'}>浅色</option>
|
||||
<option value="dark" selected={theme === 'dark'}>深色</option>
|
||||
</select>
|
||||
</form>
|
||||
</header>
|
||||
<main style="padding: 16px">
|
||||
<main class="container prose">
|
||||
<slot />
|
||||
</main>
|
||||
|
||||
<script>
|
||||
const sel = document.getElementById('theme-select');
|
||||
const apply = (val) => {
|
||||
const root = document.documentElement;
|
||||
if (val === 'dark') root.setAttribute('data-theme', 'dark');
|
||||
else if (val === 'light') root.setAttribute('data-theme', 'light');
|
||||
else root.removeAttribute('data-theme'); // system: 交给 prefers-color-scheme
|
||||
localStorage.setItem('blog-theme', val);
|
||||
};
|
||||
sel?.addEventListener('change', (e) => apply(e.target.value));
|
||||
// 初始化优先级:localStorage > PUBLIC_THEME > system
|
||||
const saved = localStorage.getItem('blog-theme');
|
||||
const defaultTheme = document.documentElement.getAttribute('data-default-theme') || 'system';
|
||||
apply(saved || defaultTheme);
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
.container {
|
||||
max-width: var(--container-max);
|
||||
margin: 0 auto;
|
||||
padding: var(--space-6) var(--space-4);
|
||||
}
|
||||
|
||||
body {
|
||||
background: var(--color-bg);
|
||||
color: var(--color-text);
|
||||
font-family: var(--font-sans);
|
||||
line-height: var(--line-height);
|
||||
font-size: var(--text-base);
|
||||
}
|
||||
|
||||
a { color: var(--color-primary); }
|
||||
|
||||
header.site-header {
|
||||
display: flex;
|
||||
gap: var(--space-4);
|
||||
align-items: center;
|
||||
padding: var(--space-3) var(--space-4);
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
select.theme-select {
|
||||
appearance: none;
|
||||
background: var(--color-bg);
|
||||
color: var(--color-text);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: 6px;
|
||||
padding: 6px 28px 6px 10px;
|
||||
position: relative;
|
||||
}
|
||||
select.theme-select:focus { outline: 2px solid color-mix(in oklab, var(--color-primary) 30%, transparent) }
|
||||
.select-wrap { position: relative; }
|
||||
.select-wrap::after {
|
||||
content: '▾';
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
color: var(--color-muted);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.prose {
|
||||
line-height: var(--line-height);
|
||||
}
|
||||
.prose h1, .prose h2, .prose h3 {
|
||||
margin: var(--space-6) 0 var(--space-3);
|
||||
}
|
||||
.prose p { margin: var(--space-3) 0; }
|
||||
.prose ul, .prose ol { padding-left: var(--space-6); }
|
||||
.prose code {
|
||||
font-family: var(--font-mono);
|
||||
background: color-mix(in oklab, var(--color-border) 60%, transparent);
|
||||
padding: 0 0.25em;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.prose pre {
|
||||
padding: var(--space-3);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius);
|
||||
overflow: auto;
|
||||
}
|
||||
.prose blockquote {
|
||||
margin: var(--space-4) 0;
|
||||
padding-left: var(--space-4);
|
||||
border-left: 3px solid var(--color-border);
|
||||
color: var(--color-muted);
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
:root {
|
||||
/* Colors */
|
||||
--color-bg: #ffffff;
|
||||
--color-text: #1f2937;
|
||||
--color-muted: #6b7280;
|
||||
--color-primary: #2563eb;
|
||||
--color-border: #e5e7eb;
|
||||
|
||||
/* Layout */
|
||||
--container-max: 860px;
|
||||
--radius: 8px;
|
||||
--space-1: 4px;
|
||||
--space-2: 8px;
|
||||
--space-3: 12px;
|
||||
--space-4: 16px;
|
||||
--space-6: 24px;
|
||||
--space-8: 32px;
|
||||
|
||||
/* Typography */
|
||||
--font-sans: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Noto Sans, Ubuntu, Cantarell, Helvetica Neue, Arial, "Apple Color Emoji", "Segoe UI Emoji";
|
||||
--font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
||||
--text-base: 16px;
|
||||
--line-height: 1.65;
|
||||
}
|
||||
|
||||
/* Follow system when no override is set */
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--color-bg: #0b1020;
|
||||
--color-text: #e5e7eb;
|
||||
--color-muted: #9ca3af;
|
||||
--color-primary: #60a5fa;
|
||||
--color-border: #1f2937;
|
||||
}
|
||||
}
|
||||
|
||||
/* Explicit overrides take precedence */
|
||||
[data-theme="dark"] {
|
||||
--color-bg: #0b1020;
|
||||
--color-text: #e5e7eb;
|
||||
--color-muted: #9ca3af;
|
||||
--color-primary: #60a5fa;
|
||||
--color-border: #1f2937;
|
||||
}
|
||||
|
||||
[data-theme="light"] {
|
||||
--color-bg: #ffffff;
|
||||
--color-text: #1f2937;
|
||||
--color-muted: #6b7280;
|
||||
--color-primary: #2563eb;
|
||||
--color-border: #e5e7eb;
|
||||
}
|
||||
Reference in New Issue
Block a user