@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,384 @@
1
+ ---
2
+ /** Verdant archive keeps filter state in the Core-owned query-string URLs. */
3
+ import PostCard from '../components/blog/PostCard.astro';
4
+ import Base from '../layouts/Base.astro';
5
+ import type { PageProps, ListPageData } from '@mintfolio/theme-api/astro';
6
+ import type { VerdantSettings } from '../settings';
7
+
8
+ interface Props extends PageProps<VerdantSettings, ListPageData> {}
9
+ const { theme, page } = Astro.props;
10
+ const posts = page.posts;
11
+ const BLOG_PAGE_SIZE = theme.settings.archivePageSize;
12
+ const [allTags, allCategories] = await Promise.all([theme.taxonomy.tags(), theme.taxonomy.categories()]);
13
+ ---
14
+
15
+ <Base theme={theme} page={page}>
16
+ <section class="blog-page">
17
+ <div class="container">
18
+ <nav class="page-nav">
19
+ <a href={theme.urls.home()} class="back-btn">
20
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="18" height="18">
21
+ <path d="M19 12H5M12 19l-7-7 7-7"/>
22
+ </svg>
23
+ 返回首页
24
+ </a>
25
+ </nav>
26
+
27
+ <header class="page-header">
28
+ <div>
29
+ <p class="page-eyebrow">NOTES & IDEAS</p>
30
+ <h1 class="page-title">全部文章<span aria-hidden="true">.</span></h1>
31
+ <p class="page-subtitle">把探索写下来,让灵感有迹可循。</p>
32
+ </div>
33
+ <div class="archive-count"><span aria-hidden="true">✳</span><strong>{posts.length}</strong><span>篇文章,持续积累中</span></div>
34
+ </header>
35
+
36
+ <div class="controls">
37
+ <div class="search-wrapper">
38
+ <svg class="search-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
39
+ <circle cx="11" cy="11" r="8"/>
40
+ <path d="M21 21l-4.35-4.35"/>
41
+ </svg>
42
+ <input type="search" id="search-input" class="search-input" placeholder="搜索文章..." aria-label="搜索文章" />
43
+ </div>
44
+ <div class="controls-right">
45
+ <div class="category-filter" id="category-filter" role="radiogroup" aria-label="按分类筛选">
46
+ <button class="category-btn active" role="radio" aria-checked="true" data-category="">全部分类</button>
47
+ {allCategories.map((category) => (
48
+ <button class="category-btn" role="radio" aria-checked="false" data-category={category.label}>{category.label}</button>
49
+ ))}
50
+ </div>
51
+ <div class="tags-filter-block" data-max-rows="3">
52
+ <div class="tags-filter" id="tags-filter" role="radiogroup" aria-label="按标签筛选">
53
+ <button class="tag-btn active" role="radio" aria-checked="true" data-tag="">全部</button>
54
+ {allTags.map(tag => (
55
+ <button class="tag-btn" role="radio" aria-checked="false" data-tag={tag.label}>{tag.label}</button>
56
+ ))}
57
+ </div>
58
+ <button class="tags-filter-toggle" id="blog-tags-filter-toggle" type="button" hidden aria-expanded="false">
59
+ 展开更多
60
+ </button>
61
+ </div>
62
+ </div>
63
+ </div>
64
+
65
+ <div id="posts-list" class="posts-list" data-page-size={BLOG_PAGE_SIZE}>
66
+ {posts.map((post, index) => (
67
+ <PostCard post={post} index={index} pageSize={BLOG_PAGE_SIZE} variant="blog" />
68
+ ))}
69
+ </div>
70
+
71
+ <p id="no-results" class="no-results" style="display: none;">没有找到匹配的文章</p>
72
+
73
+ <div class="load-more-wrap" id="load-more-wrap">
74
+ <button class="load-more-btn" id="load-more-btn">加载更多文章</button>
75
+ </div>
76
+ </div>
77
+ </section>
78
+ </Base>
79
+
80
+ <style>
81
+ .blog-page {
82
+ min-height: 100vh;
83
+ padding: 2rem 1.5rem 5rem;
84
+ background: radial-gradient(ellipse at 100% 0, var(--color-play-soft), transparent 600px), var(--color-bg);
85
+ }
86
+
87
+ .container {
88
+ max-width: var(--max-width);
89
+ margin: 0 auto;
90
+ }
91
+
92
+ .page-nav {
93
+ margin-bottom: 2rem;
94
+ }
95
+
96
+ .back-btn {
97
+ display: inline-flex;
98
+ align-items: center;
99
+ gap: 0.5rem;
100
+ color: var(--color-text-muted);
101
+ text-decoration: none;
102
+ font-size: 0.875rem;
103
+ transition: all var(--transition-fast);
104
+ padding: 0.5rem 1rem;
105
+ border-radius: var(--radius-md);
106
+ border: 1px solid var(--color-border);
107
+ background: var(--color-surface);
108
+ }
109
+
110
+ .back-btn:hover {
111
+ color: var(--color-secondary);
112
+ border-color: var(--color-secondary);
113
+ }
114
+
115
+ .page-header {
116
+ margin-bottom: 2.5rem;
117
+ text-align: left;
118
+ display: flex;
119
+ align-items: center;
120
+ justify-content: space-between;
121
+ gap: 2rem;
122
+ padding: 2rem 2.25rem;
123
+ background: var(--color-surface);
124
+ border: 1px solid var(--color-border);
125
+ border-radius: var(--radius-lg);
126
+ box-shadow: var(--shadow-offset);
127
+ }
128
+
129
+ .page-title {
130
+ font-size: clamp(2rem, 5vw, 3rem);
131
+ font-weight: 700;
132
+ color: var(--color-text-headline);
133
+ margin-bottom: 0.5rem;
134
+ font-family: var(--font-family);
135
+ letter-spacing: -.04em;
136
+ }
137
+
138
+ .page-subtitle {
139
+ font-size: 1rem;
140
+ color: var(--color-text-muted);
141
+ }
142
+
143
+ .controls {
144
+ display: flex;
145
+ flex-direction: column;
146
+ gap: 1rem;
147
+ margin-bottom: 2rem;
148
+ padding: 1.25rem;
149
+ background: var(--color-surface);
150
+ border: 1px solid var(--color-border);
151
+ border-radius: var(--radius-lg);
152
+ align-items: flex-start;
153
+ }
154
+
155
+ .controls-right {
156
+ display: flex;
157
+ flex-direction: column;
158
+ align-items: flex-start;
159
+ gap: 0.75rem;
160
+ width: 100%;
161
+ }
162
+
163
+ .category-filter {
164
+ display: flex;
165
+ flex-wrap: wrap;
166
+ gap: 0.5rem;
167
+ }
168
+
169
+ .category-btn {
170
+ padding: 0.35rem 0.75rem;
171
+ border: 1px solid var(--color-border);
172
+ border-radius: var(--radius-full);
173
+ background: var(--color-bg-alt);
174
+ color: var(--color-text-muted);
175
+ font-size: 0.78rem;
176
+ cursor: pointer;
177
+ transition: all var(--transition-fast);
178
+ }
179
+
180
+ .category-btn:hover {
181
+ border-color: var(--color-secondary);
182
+ color: var(--color-text);
183
+ }
184
+
185
+ .category-btn.active {
186
+ background: var(--color-primary);
187
+ border-color: var(--color-primary);
188
+ color: var(--color-on-primary);
189
+ }
190
+
191
+ .category-btn.hidden-by-filter,
192
+ .tag-btn.hidden-by-filter {
193
+ display: none;
194
+ }
195
+
196
+ @media (min-width: 600px) {
197
+ .controls {
198
+ flex-direction: row;
199
+ align-items: center;
200
+ justify-content: space-between;
201
+ }
202
+ }
203
+
204
+ .search-wrapper {
205
+ position: relative;
206
+ width: 100%;
207
+ max-width: 300px;
208
+ }
209
+
210
+ .search-icon {
211
+ position: absolute;
212
+ left: 0.875rem;
213
+ top: 50%;
214
+ transform: translateY(-50%);
215
+ width: 16px;
216
+ height: 16px;
217
+ color: var(--color-text-muted);
218
+ pointer-events: none;
219
+ }
220
+
221
+ .search-input {
222
+ width: 100%;
223
+ padding: 0.625rem 1rem 0.625rem 2.5rem;
224
+ border: 2px solid var(--color-border);
225
+ border-radius: var(--radius-md);
226
+ background: var(--color-surface);
227
+ color: var(--color-text);
228
+ font-size: 0.875rem;
229
+ transition: all var(--transition-base);
230
+ }
231
+
232
+ .search-input:focus {
233
+ outline: none;
234
+ border-color: var(--color-primary);
235
+ box-shadow: 0 0 0 3px var(--color-tag-bg);
236
+ }
237
+
238
+ .tags-filter-block {
239
+ width: 100%;
240
+ display: flex;
241
+ flex-direction: column;
242
+ align-items: flex-start;
243
+ gap: 0.6rem;
244
+ }
245
+
246
+ .tags-filter {
247
+ display: flex;
248
+ flex-wrap: wrap;
249
+ gap: 0.5rem;
250
+ width: 100%;
251
+ max-height: var(--expanded-height, none);
252
+ overflow: hidden;
253
+ transition: max-height var(--transition-base);
254
+ }
255
+
256
+ .tags-filter.is-collapsed {
257
+ max-height: var(--collapsed-height);
258
+ }
259
+
260
+ .tags-filter .tag-btn:focus-visible {
261
+ outline-offset: -3px;
262
+ }
263
+
264
+ .tags-filter-toggle {
265
+ display: inline-flex;
266
+ align-items: center;
267
+ padding: 0;
268
+ border: none;
269
+ background: transparent;
270
+ color: var(--color-text-muted);
271
+ font-size: 0.8rem;
272
+ font-weight: 600;
273
+ cursor: pointer;
274
+ transition: color var(--transition-fast);
275
+ }
276
+
277
+ .tags-filter-toggle:hover {
278
+ color: var(--color-secondary);
279
+ }
280
+
281
+ .tags-filter-toggle[hidden] {
282
+ display: none;
283
+ }
284
+
285
+ .tag-btn {
286
+ padding: 0.375rem 0.875rem;
287
+ border: 1px solid var(--color-border);
288
+ border-radius: var(--radius-full);
289
+ background: var(--color-surface);
290
+ color: var(--color-text-muted);
291
+ font-size: 0.8rem;
292
+ font-weight: 500;
293
+ text-decoration: none;
294
+ transition: all var(--transition-fast);
295
+ cursor: pointer;
296
+ }
297
+
298
+ .tag-btn:hover {
299
+ border-color: var(--color-secondary);
300
+ color: var(--color-text);
301
+ }
302
+
303
+ .tag-btn.active {
304
+ background: var(--color-secondary);
305
+ color: var(--color-bg);
306
+ border-color: var(--color-secondary);
307
+ }
308
+
309
+ .posts-list {
310
+ display: flex;
311
+ flex-direction: column;
312
+ gap: 1rem;
313
+ }
314
+
315
+ .no-results {
316
+ text-align: center;
317
+ color: var(--color-text-muted);
318
+ padding: 3rem;
319
+ }
320
+
321
+ .load-more-wrap {
322
+ display: flex;
323
+ justify-content: center;
324
+ margin-top: 1.5rem;
325
+ min-height: 2.75rem;
326
+ }
327
+
328
+ .load-more-wrap.hidden {
329
+ visibility: hidden;
330
+ pointer-events: none;
331
+ }
332
+
333
+ .load-more-btn {
334
+ padding: 0.65rem 1.2rem;
335
+ border-radius: var(--radius-md);
336
+ border: 1px solid var(--color-border);
337
+ background: var(--color-surface);
338
+ color: var(--color-text);
339
+ cursor: pointer;
340
+ transition: all var(--transition-fast);
341
+ font-size: 0.88rem;
342
+ }
343
+
344
+ .load-more-btn:hover {
345
+ border-color: var(--color-secondary);
346
+ color: var(--color-secondary);
347
+ transform: translateY(-1px);
348
+ }
349
+
350
+ @media (max-width: 600px) {
351
+ .search-wrapper { max-width: 100%; }
352
+
353
+ .controls-right {
354
+ align-items: stretch;
355
+ }
356
+ }
357
+
358
+ .page-eyebrow { font: .7rem var(--font-mono); letter-spacing: .16em; color: var(--color-primary); margin-bottom: .65rem; }
359
+
360
+ .page-title > span { color: var(--color-primary); }
361
+
362
+ .archive-count { flex-shrink: 0; text-align: center; display: grid; grid-template-columns: auto 1fr; align-items: center; gap: .2rem .8rem; transform: rotate(4deg); background: var(--color-play-soft); border: 1px solid var(--color-border); border-radius: 16px; padding: .8rem 1.3rem; }
363
+
364
+ .archive-count > span:first-child { color: var(--color-primary); font-size: 2.5rem; }
365
+
366
+ .archive-count strong { font: 700 2.75rem var(--font-display); }
367
+
368
+ .archive-count > span:last-child { grid-column: 1 / -1; font-size: .68rem; color: var(--color-text-muted); }
369
+
370
+ @media (max-width: 600px) {
371
+ .page-header { gap: .75rem; padding: 1.25rem; }
372
+ .page-subtitle { font-size: .8rem; }
373
+ .archive-count { padding: .6rem; grid-template-columns: 1fr; gap: 0; }
374
+ .archive-count > span:first-child { display: none; }
375
+ .archive-count strong { font-size: 2rem; }
376
+ .archive-count > span:last-child { max-width: 6em; font-size: .6rem; }
377
+ .page-title { font-size: 1.8rem; }
378
+ .controls { padding: 1rem; }
379
+ }
380
+ </style>
381
+
382
+ <script>
383
+ import '../scripts/blog';
384
+ </script>