@mintfolio/theme-verdant 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/LICENSE +674 -0
  2. package/README.md +66 -0
  3. package/THIRD_PARTY_NOTICES.md +11 -0
  4. package/assets/favicon.ico +0 -0
  5. package/assets/favicon.svg +9 -0
  6. package/assets/fonts/Inter-300.woff2 +0 -0
  7. package/assets/fonts/Inter-400.woff2 +0 -0
  8. package/assets/fonts/Inter-500.woff2 +0 -0
  9. package/assets/fonts/Inter-600.woff2 +0 -0
  10. package/assets/fonts/Inter-700.woff2 +0 -0
  11. package/assets/fonts/JetBrainsMono-400.woff2 +0 -0
  12. package/assets/fonts/JetBrainsMono-500.woff2 +0 -0
  13. package/assets/fonts/PlayfairDisplay-400.woff2 +0 -0
  14. package/assets/fonts/PlayfairDisplay-500.woff2 +0 -0
  15. package/assets/fonts/PlayfairDisplay-600.woff2 +0 -0
  16. package/assets/fonts/PlayfairDisplay-700.woff2 +0 -0
  17. package/components/base/Contact.astro +173 -0
  18. package/components/base/Hero.astro +228 -0
  19. package/components/base/Navigation.astro +288 -0
  20. package/components/base/Projects.astro +207 -0
  21. package/components/base/Skills.astro +158 -0
  22. package/components/blog/ArticleProse.astro +217 -0
  23. package/components/blog/CodeBlockEnhancer.astro +310 -0
  24. package/components/blog/PostCard.astro +98 -0
  25. package/components/blog/ProtectedArticle.astro +89 -0
  26. package/config/theme-verdant.config.mjs +74 -0
  27. package/layouts/Base.astro +460 -0
  28. package/lib/palettes.ts +10 -0
  29. package/licenses/Inter-OFL.txt +92 -0
  30. package/licenses/JetBrainsMono-OFL.txt +93 -0
  31. package/licenses/PlayfairDisplay-OFL.txt +191 -0
  32. package/package.json +68 -0
  33. package/pages/archive.astro +384 -0
  34. package/pages/home.astro +2049 -0
  35. package/pages/page.astro +434 -0
  36. package/pages/post.astro +1029 -0
  37. package/scripts/article.ts +42 -0
  38. package/scripts/articleOrigin.ts +31 -0
  39. package/scripts/articleTables.ts +15 -0
  40. package/scripts/blog.ts +26 -0
  41. package/scripts/codeBlocks.ts +172 -0
  42. package/scripts/hero.ts +173 -0
  43. package/scripts/home.ts +12 -0
  44. package/scripts/homePanels.ts +100 -0
  45. package/scripts/hotContent.ts +16 -0
  46. package/scripts/lifecycle.ts +3 -0
  47. package/scripts/lightbox.ts +14 -0
  48. package/scripts/postFilters.ts +101 -0
  49. package/scripts/postList.ts +132 -0
  50. package/scripts/progress.ts +29 -0
  51. package/scripts/protectedArticle.ts +49 -0
  52. package/scripts/protectedToc.ts +29 -0
  53. package/scripts/site.ts +145 -0
  54. package/scripts/tagFold.ts +61 -0
  55. package/scripts/theme.ts +102 -0
  56. package/scripts/toc.ts +149 -0
  57. package/settings.ts +21 -0
  58. package/styles/fonts.css +120 -0
  59. package/styles/global.css +146 -0
  60. package/styles/themes/happyhues/effects.css +62 -0
  61. package/styles/themes/happyhues/index.css +2 -0
  62. package/styles/themes/happyhues/theme.css +225 -0
  63. package/theme.mjs +60 -0
  64. package/utils/blogNavigation.ts +3 -0
  65. package/utils/homeReturnHint.ts +34 -0
  66. package/utils/storage.ts +16 -0
  67. package/utils/types.ts +8 -0
@@ -0,0 +1,225 @@
1
+ :root {
2
+ --font-display: 'Playfair Display', 'Noto Sans SC', 'PingFang SC', 'Microsoft YaHei', serif;
3
+ --font-family: 'Inter', 'Noto Sans SC', 'PingFang SC', 'Microsoft YaHei', system-ui, sans-serif;
4
+ --font-mono: 'JetBrains Mono', 'Fira Code', 'Cascadia Code', monospace;
5
+ --section-padding: 5rem 1.5rem;
6
+ --max-width: 1200px;
7
+ --radius-sm: 10px;
8
+ --radius-md: 18px;
9
+ --radius-lg: 24px;
10
+ --radius-full: 9999px;
11
+ --transition-fast: 150ms ease;
12
+ --transition-base: 250ms ease;
13
+ --transition-slow: 400ms ease;
14
+ }
15
+
16
+ /* Primary is for readable actions; accent and play add decorative contrast. */
17
+
18
+ /* 1. 青柠薄荷 */
19
+ :root, [data-theme="light-1"] {
20
+ --color-bg: #f6f8ef;
21
+ --color-surface: #fffef9;
22
+ --color-text: #243d36;
23
+ --color-text-muted: #556660;
24
+ --color-primary: #08765f;
25
+ --color-accent: #ff795f;
26
+ --color-play: #e1ee79;
27
+ }
28
+
29
+ [data-theme="dark-1"] {
30
+ --color-bg: #132622;
31
+ --color-surface: #1c342e;
32
+ --color-text: #edf4e5;
33
+ --color-text-muted: #adc5b9;
34
+ --color-primary: #73e0b7;
35
+ --color-accent: #ff9983;
36
+ --color-play: #d5e67b;
37
+ }
38
+
39
+ /* 2. 橘子汽水 */
40
+ [data-theme="light-2"] {
41
+ --color-bg: #fff7ee;
42
+ --color-surface: #fffdfa;
43
+ --color-text: #45302b;
44
+ --color-text-muted: #6e564e;
45
+ --color-primary: #b7441c;
46
+ --color-accent: #526ce0;
47
+ --color-play: #ffc65c;
48
+ }
49
+
50
+ [data-theme="dark-2"] {
51
+ --color-bg: #2b211e;
52
+ --color-surface: #3a2d27;
53
+ --color-text: #fff1e3;
54
+ --color-text-muted: #d4b9a8;
55
+ --color-primary: #ffad83;
56
+ --color-accent: #9daeff;
57
+ --color-play: #ffd277;
58
+ }
59
+
60
+ /* 3. 晴空海盐 */
61
+ [data-theme="light-3"] {
62
+ --color-bg: #f5f6ff;
63
+ --color-surface: #fdfdff;
64
+ --color-text: #283451;
65
+ --color-text-muted: #5e6981;
66
+ --color-primary: #3454cd;
67
+ --color-accent: #ff9163;
68
+ --color-play: #cddd76;
69
+ }
70
+
71
+ [data-theme="dark-3"] {
72
+ --color-bg: #182035;
73
+ --color-surface: #232e48;
74
+ --color-text: #eef2ff;
75
+ --color-text-muted: #b5c0dc;
76
+ --color-primary: #a5baff;
77
+ --color-accent: #ffa779;
78
+ --color-play: #d6e596;
79
+ }
80
+
81
+ /* 4. 葡萄跳跳糖 */
82
+ [data-theme="light-4"] {
83
+ --color-bg: #faf5ff;
84
+ --color-surface: #fffaff;
85
+ --color-text: #3f3052;
86
+ --color-text-muted: #746180;
87
+ --color-primary: #7441bc;
88
+ --color-accent: #bbd94f;
89
+ --color-play: #ffadca;
90
+ }
91
+
92
+ [data-theme="dark-4"] {
93
+ --color-bg: #251e33;
94
+ --color-surface: #342a46;
95
+ --color-text: #f5edff;
96
+ --color-text-muted: #c6b3d9;
97
+ --color-primary: #d0acff;
98
+ --color-accent: #c9e477;
99
+ --color-play: #f7aaca;
100
+ }
101
+
102
+ /* 5. 莓果奶昔 */
103
+ [data-theme="light-5"] {
104
+ --color-bg: #fff5f6;
105
+ --color-surface: #fffafb;
106
+ --color-text: #4d2f3b;
107
+ --color-text-muted: #805e6b;
108
+ --color-primary: #ba375e;
109
+ --color-accent: #73cec2;
110
+ --color-play: #f5c75c;
111
+ }
112
+
113
+ [data-theme="dark-5"] {
114
+ --color-bg: #2d202a;
115
+ --color-surface: #402d38;
116
+ --color-text: #ffeef3;
117
+ --color-text-muted: #dec0ce;
118
+ --color-primary: #ffabc7;
119
+ --color-accent: #8ad9c9;
120
+ --color-play: #f6d47e;
121
+ }
122
+
123
+ /* 6. 向日葵 */
124
+ [data-theme="light-6"] {
125
+ --color-bg: #fff9e9;
126
+ --color-surface: #fffdf6;
127
+ --color-text: #473d26;
128
+ --color-text-muted: #6b5d43;
129
+ --color-primary: #91600c;
130
+ --color-accent: #4364cc;
131
+ --color-play: #f2c94c;
132
+ }
133
+
134
+ [data-theme="dark-6"] {
135
+ --color-bg: #29251b;
136
+ --color-surface: #393326;
137
+ --color-text: #fff4d9;
138
+ --color-text-muted: #cdc0a0;
139
+ --color-primary: #edcc75;
140
+ --color-accent: #9bb3ff;
141
+ --color-play: #ffb880;
142
+ }
143
+
144
+ /* 7. 海岛假日 */
145
+ [data-theme="light-7"] {
146
+ --color-bg: #eff9fb;
147
+ --color-surface: #faffff;
148
+ --color-text: #253f47;
149
+ --color-text-muted: #4c6871;
150
+ --color-primary: #087387;
151
+ --color-accent: #f48652;
152
+ --color-play: #aadad0;
153
+ }
154
+
155
+ [data-theme="dark-7"] {
156
+ --color-bg: #15272e;
157
+ --color-surface: #203840;
158
+ --color-text: #e7f5f5;
159
+ --color-text-muted: #afc7cc;
160
+ --color-primary: #80dbe4;
161
+ --color-accent: #ffa875;
162
+ --color-play: #addbd1;
163
+ }
164
+
165
+ /* 8. 午夜游乐场 */
166
+ [data-theme="light-8"] {
167
+ --color-bg: #f5f5f2;
168
+ --color-surface: #fdfdfa;
169
+ --color-text: #30394b;
170
+ --color-text-muted: #5c6370;
171
+ --color-primary: #394860;
172
+ --color-accent: #ffbb3b;
173
+ --color-play: #a4b8ec;
174
+ }
175
+
176
+ [data-theme="dark-8"] {
177
+ --color-bg: #1a202c;
178
+ --color-surface: #282f3e;
179
+ --color-text: #f0f1ee;
180
+ --color-text-muted: #b7bfce;
181
+ --color-primary: #c3d0ee;
182
+ --color-accent: #ffcb67;
183
+ --color-play: #9db5ef;
184
+ }
185
+
186
+ :root {
187
+ color-scheme: light;
188
+ --color-on-primary: #ffffff;
189
+ --color-secondary: var(--color-text);
190
+ --color-text-headline: var(--color-text);
191
+ --color-bg-alt: color-mix(in srgb, var(--color-primary) 7%, var(--color-bg));
192
+ --color-border: color-mix(in srgb, var(--color-text) 19%, var(--color-surface));
193
+ --color-tag-bg: color-mix(in srgb, var(--color-primary) 10%, var(--color-surface));
194
+ --color-tag-text: var(--color-primary);
195
+ --color-accent-soft: color-mix(in srgb, var(--color-accent) 22%, var(--color-surface));
196
+ --color-play-soft: color-mix(in srgb, var(--color-play) 32%, var(--color-surface));
197
+ --shadow-sm: 0 2px 0 color-mix(in srgb, var(--color-text) 7%, transparent);
198
+ --shadow-md: 0 8px 24px color-mix(in srgb, var(--color-text) 8%, transparent);
199
+ --shadow-lg: 0 18px 48px color-mix(in srgb, var(--color-text) 12%, transparent);
200
+ --shadow-offset: 6px 6px 0 var(--color-accent-soft);
201
+ }
202
+
203
+ [data-theme^="dark-"] {
204
+ color-scheme: dark;
205
+ --color-on-primary: #18252d;
206
+ --color-accent-soft: color-mix(in srgb, var(--color-accent) 17%, var(--color-surface));
207
+ --color-play-soft: color-mix(in srgb, var(--color-play) 17%, var(--color-surface));
208
+ --shadow-sm: 0 2px 0 #00000024;
209
+ --shadow-md: 0 8px 24px #00000024;
210
+ --shadow-lg: 0 18px 48px #00000038;
211
+ }
212
+
213
+ @media (prefers-color-scheme: dark) {
214
+ :root:not([data-theme]) {
215
+ color-scheme: dark;
216
+ --color-bg: #132622;
217
+ --color-surface: #1c342e;
218
+ --color-text: #edf4e5;
219
+ --color-text-muted: #adc5b9;
220
+ --color-primary: #73e0b7;
221
+ --color-accent: #ff9983;
222
+ --color-play: #d5e67b;
223
+ --color-on-primary: #18252d;
224
+ }
225
+ }
package/theme.mjs ADDED
@@ -0,0 +1,60 @@
1
+ // @ts-check
2
+ import { defineTheme } from '@mintfolio/theme-api';
3
+
4
+ /** @param {string} label @param {string} [value] @returns {import('@mintfolio/theme-api').StringSetting} */
5
+ const text = (label, value = '') => ({ type: 'string', label, default: value });
6
+ /** @param {string} label @param {boolean} value @returns {import('@mintfolio/theme-api').BooleanSetting} */
7
+ const flag = (label, value) => ({ type: 'boolean', label, default: value });
8
+
9
+ /** All layout, typography, cards, reading tools, and palette controls belong to this theme. */
10
+ export default defineTheme({
11
+ manifest: {
12
+ id: 'verdant',
13
+ name: 'Verdant',
14
+ version: '0.2.0',
15
+ author: 'Mintfolio contributors',
16
+ description: 'Portfolio hero, article cards, reading tools, and eight visitor-selectable palettes.',
17
+ engine: '^1.0.0',
18
+ },
19
+ capabilities: { encryptedPosts: true, search: true, tags: true, categories: true, toc: true, darkMode: true },
20
+ build: { react: true, tailwind: true },
21
+ pages: {
22
+ home: './pages/home.astro',
23
+ post: './pages/post.astro',
24
+ archive: './pages/archive.astro',
25
+ page: './pages/page.astro',
26
+ },
27
+ settings: {
28
+ initialMode: { type: 'select', label: '初始明暗模式', default: 'auto', options: ['auto', 'light', 'dark'] },
29
+ initialPalette: { type: 'select', label: '初始配色', default: '1', options: ['1', '2', '3', '4', '5', '6', '7', '8'] },
30
+ homePageSize: { type: 'number', label: '首页初始文章数', default: 6, min: 1, max: 100 },
31
+ archivePageSize: { type: 'number', label: '归档初始文章数', default: 8, min: 1, max: 100 },
32
+ analyticsId: text('Google Analytics ID'),
33
+ sidebar: {
34
+ type: 'object', label: '侧边栏', default: {}, properties: {
35
+ sections: { type: 'object', label: '显示区域', default: {}, properties: {
36
+ contact: flag('联系方式', true), activity: flag('最近动态', true), tools: flag('推荐工具', true),
37
+ } },
38
+ tools: { type: 'array', label: '推荐工具', default: [], items: {
39
+ type: 'object', label: '工具', default: {}, properties: { name: text('名称'), description: text('简介'), url: text('地址') },
40
+ } },
41
+ quote: { type: 'object', label: '引言', default: {}, properties: {
42
+ enabled: flag('显示引言', false), text: text('内容'), author: text('作者'),
43
+ } },
44
+ },
45
+ },
46
+ home: { type: 'object', label: '首页', default: {}, properties: {
47
+ hotContent: { type: 'object', label: '推荐文章', default: {}, properties: {
48
+ enabled: flag('显示推荐文章', false),
49
+ items: { type: 'array', label: '文章列表', default: [], items: {
50
+ type: 'object', label: '文章', default: {}, properties: { postId: text('文章 ID'), image: text('图片地址') },
51
+ } },
52
+ } },
53
+ } },
54
+ article: { type: 'object', label: '文章', default: {}, properties: {
55
+ footerImage: { type: 'object', label: '文末插图', default: {}, properties: {
56
+ enabled: flag('显示文末插图', false), src: text('图片地址(为空时使用站点头像)'),
57
+ } },
58
+ } },
59
+ },
60
+ });
@@ -0,0 +1,3 @@
1
+ /** Verdant's existing storage key is presentation navigation state. */
2
+ export const ARTICLE_BACK_NAV_STORAGE_KEY = 'article-back-target';
3
+ export { resolveStoredArticleBackHref, shouldPersistArticleBackHref } from '@mintfolio/core/client';
@@ -0,0 +1,34 @@
1
+ /**
2
+ * @fileoverview Home return hint utilities for managing the "return to hero" hint display state.
3
+ * Controls when to show the hint button that appears when the hero section is collapsed on the home page.
4
+ */
5
+
6
+ /**
7
+ * Local storage key for tracking whether the home return hint has been dismissed.
8
+ * Once dismissed, the hint won't show again on subsequent visits.
9
+ */
10
+ export const HOME_RETURN_HINT_STORAGE_KEY = 'home-return-hint-dismissed';
11
+
12
+ /**
13
+ * Determines whether the home return hint should be displayed.
14
+ *
15
+ * The hint is shown only once per user - after they see it and it auto-dismisses,
16
+ * the value is set to '1' in localStorage and the hint won't appear again.
17
+ *
18
+ * @param storageValue - The current value from localStorage (null if not set)
19
+ * @returns True if the hint should be shown (not yet dismissed)
20
+ *
21
+ * @example
22
+ * ```ts
23
+ * const showHint = shouldShowHomeReturnHint(
24
+ * localStorage.getItem('home-return-hint-dismissed')
25
+ * );
26
+ * if (showHint) {
27
+ * // Display the hint and mark as dismissed
28
+ * localStorage.setItem('home-return-hint-dismissed', '1');
29
+ * }
30
+ * ```
31
+ */
32
+ export function shouldShowHomeReturnHint(storageValue: string | null): boolean {
33
+ return storageValue !== '1';
34
+ }
@@ -0,0 +1,16 @@
1
+ /** Privacy settings may deny storage; navigation and theme controls must still work. */
2
+ export function readStorage(kind: 'local' | 'session', key: string): string | null {
3
+ try {
4
+ return window[`${kind}Storage`].getItem(key);
5
+ } catch {
6
+ return null;
7
+ }
8
+ }
9
+
10
+ export function writeStorage(kind: 'local' | 'session', key: string, value: string): void {
11
+ try {
12
+ window[`${kind}Storage`].setItem(key, value);
13
+ } catch {
14
+ /* Optional persistence. */
15
+ }
16
+ }
package/utils/types.ts ADDED
@@ -0,0 +1,8 @@
1
+ import type { SearchEntry } from '@mintfolio/theme-api';
2
+
3
+ export type { PostFilters } from '@mintfolio/theme-api';
4
+
5
+ /** The Verdant Theme attaches a card element to the SDK's public search entry. */
6
+ export interface PostItem extends SearchEntry {
7
+ element: HTMLElement;
8
+ }