@astro-minimax/core 0.1.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.
- package/README.md +29 -0
- package/package.json +41 -0
- package/src/assets/icons/IconArchive.svg +1 -0
- package/src/assets/icons/IconArrowLeft.svg +1 -0
- package/src/assets/icons/IconArrowNarrowUp.svg +1 -0
- package/src/assets/icons/IconArrowRight.svg +1 -0
- package/src/assets/icons/IconArticle.svg +1 -0
- package/src/assets/icons/IconBrandX.svg +1 -0
- package/src/assets/icons/IconCalendar.svg +1 -0
- package/src/assets/icons/IconChevronLeft.svg +1 -0
- package/src/assets/icons/IconChevronRight.svg +1 -0
- package/src/assets/icons/IconEdit.svg +1 -0
- package/src/assets/icons/IconFacebook.svg +1 -0
- package/src/assets/icons/IconGitHub.svg +1 -0
- package/src/assets/icons/IconHash.svg +1 -0
- package/src/assets/icons/IconHome.svg +1 -0
- package/src/assets/icons/IconLinkedin.svg +1 -0
- package/src/assets/icons/IconMail.svg +1 -0
- package/src/assets/icons/IconMenuDeep.svg +1 -0
- package/src/assets/icons/IconMoon.svg +1 -0
- package/src/assets/icons/IconPinterest.svg +1 -0
- package/src/assets/icons/IconProject.svg +1 -0
- package/src/assets/icons/IconRss.svg +1 -0
- package/src/assets/icons/IconSearch.svg +1 -0
- package/src/assets/icons/IconSeries.svg +1 -0
- package/src/assets/icons/IconSunHigh.svg +1 -0
- package/src/assets/icons/IconTag.svg +1 -0
- package/src/assets/icons/IconTelegram.svg +1 -0
- package/src/assets/icons/IconUser.svg +1 -0
- package/src/assets/icons/IconWhatsapp.svg +1 -0
- package/src/assets/icons/IconX.svg +1 -0
- package/src/components/ai/AIChatWidget.astro +377 -0
- package/src/components/blog/Comments.astro +527 -0
- package/src/components/blog/Copyright.astro +152 -0
- package/src/components/blog/EditPost.astro +59 -0
- package/src/components/blog/FloatingTOC.astro +260 -0
- package/src/components/blog/InlineTOC.astro +223 -0
- package/src/components/blog/PostActions.astro +306 -0
- package/src/components/blog/RelatedPosts.astro +60 -0
- package/src/components/blog/SeriesNav.astro +176 -0
- package/src/components/blog/ShareLinks.astro +26 -0
- package/src/components/nav/BackButton.astro +37 -0
- package/src/components/nav/BackToTopButton.astro +223 -0
- package/src/components/nav/Breadcrumb.astro +57 -0
- package/src/components/nav/FloatingActions.astro +206 -0
- package/src/components/nav/Footer.astro +107 -0
- package/src/components/nav/Header.astro +252 -0
- package/src/components/nav/Pagination.astro +45 -0
- package/src/components/social/Socials.astro +19 -0
- package/src/components/social/Sponsors.astro +34 -0
- package/src/components/social/Sponsorship.astro +44 -0
- package/src/components/ui/Alert.astro +28 -0
- package/src/components/ui/Card.astro +206 -0
- package/src/components/ui/Collapse.astro +82 -0
- package/src/components/ui/ColorPreview.astro +29 -0
- package/src/components/ui/Datetime.astro +61 -0
- package/src/components/ui/GithubCard.astro +191 -0
- package/src/components/ui/LinkButton.astro +21 -0
- package/src/components/ui/Tag.astro +37 -0
- package/src/components/ui/TagCloud.astro +69 -0
- package/src/components/ui/Timeline.astro +39 -0
- package/src/layouts/AboutLayout.astro +24 -0
- package/src/layouts/Layout.astro +329 -0
- package/src/layouts/Main.astro +42 -0
- package/src/layouts/PostDetails.astro +445 -0
- package/src/plugins/rehype-autolink-headings.ts +46 -0
- package/src/plugins/rehype-external-links.ts +35 -0
- package/src/plugins/rehype-table-scroll.ts +35 -0
- package/src/plugins/remark-add-zoomable.ts +28 -0
- package/src/plugins/remark-reading-time.ts +18 -0
- package/src/plugins/shiki-transformers.ts +212 -0
- package/src/scripts/lightbox.ts +63 -0
- package/src/scripts/reading-position.ts +56 -0
- package/src/scripts/theme-utils.ts +19 -0
- package/src/scripts/theme.ts +179 -0
- package/src/scripts/web-vitals.ts +96 -0
- package/src/styles/code-blocks.css +194 -0
- package/src/styles/components.css +252 -0
- package/src/styles/global.css +403 -0
- package/src/styles/typography.css +149 -0
- package/src/types.ts +89 -0
- package/src/utils/generateOgImages.ts +38 -0
- package/src/utils/getCategoryPath.ts +23 -0
- package/src/utils/getPath.ts +52 -0
- package/src/utils/getPostsByCategory.ts +17 -0
- package/src/utils/getPostsByGroupCondition.ts +25 -0
- package/src/utils/getPostsByLang.ts +27 -0
- package/src/utils/getPostsByTag.ts +10 -0
- package/src/utils/getReadingTime.ts +33 -0
- package/src/utils/getRelatedPosts.ts +59 -0
- package/src/utils/getSeriesData.ts +57 -0
- package/src/utils/getSortedPosts.ts +18 -0
- package/src/utils/getTagsWithCount.ts +38 -0
- package/src/utils/getUniqueCategories.ts +81 -0
- package/src/utils/getUniqueTags.ts +23 -0
- package/src/utils/i18n.ts +249 -0
- package/src/utils/loadGoogleFont.ts +38 -0
- package/src/utils/og-templates/post.js +229 -0
- package/src/utils/og-templates/site.js +128 -0
- package/src/utils/pathUtils.ts +17 -0
- package/src/utils/postFilter.ts +11 -0
- package/src/utils/slugify.ts +23 -0
- package/src/utils/toc.ts +27 -0
|
@@ -0,0 +1,403 @@
|
|
|
1
|
+
@import "tailwindcss";
|
|
2
|
+
@import "./typography.css";
|
|
3
|
+
@import "./code-blocks.css";
|
|
4
|
+
@import "./components.css";
|
|
5
|
+
|
|
6
|
+
@custom-variant dark (&:where([data-theme=dark], [data-theme=dark] *));
|
|
7
|
+
|
|
8
|
+
:root,
|
|
9
|
+
html[data-theme="light"] {
|
|
10
|
+
--background: #f7f3eb;
|
|
11
|
+
--surface: rgba(255, 255, 255, 0.82);
|
|
12
|
+
--surface-strong: rgba(255, 252, 247, 0.95);
|
|
13
|
+
--card: #efe8dc;
|
|
14
|
+
--foreground: #152334;
|
|
15
|
+
--foreground-soft: #5d6b80;
|
|
16
|
+
--muted: #e3ddd2;
|
|
17
|
+
--border: rgba(21, 35, 52, 0.12);
|
|
18
|
+
--accent: #0f766e;
|
|
19
|
+
--accent-soft: rgba(15, 118, 110, 0.14);
|
|
20
|
+
--shadow-elevated: rgba(15, 23, 42, 0.18);
|
|
21
|
+
--hero-orb-a: rgba(15, 118, 110, 0.22);
|
|
22
|
+
--hero-orb-b: rgba(245, 158, 11, 0.18);
|
|
23
|
+
|
|
24
|
+
--series-glow: rgba(15, 118, 110, 0.08);
|
|
25
|
+
--series-border: rgba(15, 118, 110, 0.22);
|
|
26
|
+
--series-bg-from: rgba(255, 255, 255, 0.7);
|
|
27
|
+
--series-bg-to: rgba(239, 232, 220, 0.5);
|
|
28
|
+
--series-pulse: rgba(15, 118, 110, 0.6);
|
|
29
|
+
|
|
30
|
+
--viz-bg: rgba(248, 250, 252, 0.8);
|
|
31
|
+
--viz-border: rgba(21, 35, 52, 0.1);
|
|
32
|
+
--viz-btn-bg: rgba(255, 255, 255, 0.9);
|
|
33
|
+
--viz-btn-hover: rgba(15, 118, 110, 0.1);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
html[data-theme="dark"] {
|
|
37
|
+
--background: #0f172a;
|
|
38
|
+
--surface: rgba(15, 23, 42, 0.76);
|
|
39
|
+
--surface-strong: rgba(15, 23, 42, 0.92);
|
|
40
|
+
--card: #1e293b;
|
|
41
|
+
--foreground: #e7edf7;
|
|
42
|
+
--foreground-soft: #98a7be;
|
|
43
|
+
--muted: #223046;
|
|
44
|
+
--border: rgba(148, 163, 184, 0.18);
|
|
45
|
+
--accent: #5eead4;
|
|
46
|
+
--accent-soft: rgba(94, 234, 212, 0.16);
|
|
47
|
+
--shadow-elevated: rgba(2, 6, 23, 0.6);
|
|
48
|
+
--hero-orb-a: rgba(94, 234, 212, 0.18);
|
|
49
|
+
--hero-orb-b: rgba(251, 191, 36, 0.12);
|
|
50
|
+
|
|
51
|
+
--series-glow: rgba(94, 234, 212, 0.12);
|
|
52
|
+
--series-border: rgba(94, 234, 212, 0.3);
|
|
53
|
+
--series-bg-from: rgba(13, 27, 42, 0.9);
|
|
54
|
+
--series-bg-to: rgba(30, 41, 59, 0.6);
|
|
55
|
+
--series-pulse: rgba(94, 234, 212, 0.8);
|
|
56
|
+
|
|
57
|
+
--viz-bg: rgba(30, 41, 59, 0.5);
|
|
58
|
+
--viz-border: rgba(100, 116, 139, 0.2);
|
|
59
|
+
--viz-btn-bg: rgba(30, 41, 59, 0.9);
|
|
60
|
+
--viz-btn-hover: rgba(94, 234, 212, 0.15);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
@theme inline {
|
|
64
|
+
--font-app:
|
|
65
|
+
ui-monospace, "Cascadia Code", "Source Code Pro", Menlo, Monaco, Consolas,
|
|
66
|
+
monospace;
|
|
67
|
+
--color-background: var(--background);
|
|
68
|
+
--color-foreground: var(--foreground);
|
|
69
|
+
--color-foreground-soft: var(--foreground-soft);
|
|
70
|
+
--color-accent: var(--accent);
|
|
71
|
+
--color-card: var(--card);
|
|
72
|
+
--color-surface: var(--surface);
|
|
73
|
+
--color-muted: var(--muted);
|
|
74
|
+
--color-border: var(--border);
|
|
75
|
+
--color-shadow-elevated: var(--shadow-elevated);
|
|
76
|
+
--color-series-glow: var(--series-glow);
|
|
77
|
+
--color-series-border: var(--series-border);
|
|
78
|
+
--color-series-pulse: var(--series-pulse);
|
|
79
|
+
--color-viz-bg: var(--viz-bg);
|
|
80
|
+
--color-viz-border: var(--viz-border);
|
|
81
|
+
--color-viz-btn-bg: var(--viz-btn-bg);
|
|
82
|
+
--color-viz-btn-hover: var(--viz-btn-hover);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
@layer base {
|
|
86
|
+
* {
|
|
87
|
+
@apply border-border outline-accent/75;
|
|
88
|
+
scrollbar-width: auto;
|
|
89
|
+
scrollbar-color: var(--color-muted) transparent;
|
|
90
|
+
}
|
|
91
|
+
html {
|
|
92
|
+
@apply overflow-y-scroll scroll-smooth;
|
|
93
|
+
}
|
|
94
|
+
body {
|
|
95
|
+
@apply flex min-h-svh flex-col bg-background font-app text-foreground selection:bg-accent/75 selection:text-background;
|
|
96
|
+
transition:
|
|
97
|
+
background-color 0.2s cubic-bezier(0.4, 0, 0.2, 1),
|
|
98
|
+
color 0.2s cubic-bezier(0.4, 0, 0.2, 1);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
header,
|
|
102
|
+
footer,
|
|
103
|
+
main {
|
|
104
|
+
transition:
|
|
105
|
+
background-color 0.2s cubic-bezier(0.4, 0, 0.2, 1),
|
|
106
|
+
color 0.2s cubic-bezier(0.4, 0, 0.2, 1),
|
|
107
|
+
border-color 0.2s cubic-bezier(0.4, 0, 0.2, 1);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
a,
|
|
111
|
+
button {
|
|
112
|
+
@apply outline-offset-1 outline-accent focus-visible:no-underline focus-visible:outline-2 focus-visible:outline-dashed;
|
|
113
|
+
}
|
|
114
|
+
button:not(:disabled),
|
|
115
|
+
[role="button"]:not(:disabled) {
|
|
116
|
+
cursor: pointer;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
@utility max-w-app {
|
|
121
|
+
@apply max-w-3xl;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
@utility app-layout {
|
|
125
|
+
@apply mx-auto w-full max-w-app px-4;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
.active-nav {
|
|
129
|
+
@apply underline decoration-wavy decoration-2 underline-offset-8;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
:target {
|
|
133
|
+
scroll-margin-block: 1rem;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/* ===== Reading Mode ===== */
|
|
137
|
+
body.reading-mode header,
|
|
138
|
+
body.reading-mode footer,
|
|
139
|
+
body.reading-mode [id="back-button"],
|
|
140
|
+
body.reading-mode .floating-toc-container,
|
|
141
|
+
body.reading-mode #sticky-header {
|
|
142
|
+
display: none !important;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
body.reading-mode main {
|
|
146
|
+
max-width: 65ch;
|
|
147
|
+
margin-inline: auto;
|
|
148
|
+
padding-top: 2rem;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
body.reading-mode .app-prose {
|
|
152
|
+
font-size: 1.125rem;
|
|
153
|
+
line-height: 1.85;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
body.reading-mode .post-actions,
|
|
157
|
+
body.reading-mode .share-links,
|
|
158
|
+
body.reading-mode [data-pagefind-ignore] {
|
|
159
|
+
display: none !important;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/* ===== Dark Mode h5/h6 Override ===== */
|
|
163
|
+
html[data-theme="dark"] .app-prose h5,
|
|
164
|
+
html[data-theme="dark"] .app-prose h6,
|
|
165
|
+
html[data-theme="dark"] .prose h5,
|
|
166
|
+
html[data-theme="dark"] .prose h6 {
|
|
167
|
+
color: #f0f6fc !important;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/* ===== View Transitions - Circular Expansion ===== */
|
|
171
|
+
::view-transition-old(root),
|
|
172
|
+
::view-transition-new(root) {
|
|
173
|
+
animation: none;
|
|
174
|
+
mix-blend-mode: normal;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
::view-transition-old(root) {
|
|
178
|
+
z-index: 1;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
::view-transition-new(root) {
|
|
182
|
+
z-index: 9999;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
html.dark-transition::view-transition-old(root) {
|
|
186
|
+
z-index: 9999;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
html.dark-transition::view-transition-new(root) {
|
|
190
|
+
z-index: 1;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
@keyframes circle-expand {
|
|
194
|
+
0% {
|
|
195
|
+
clip-path: circle(0% at var(--theme-x, 50%) var(--theme-y, 50%));
|
|
196
|
+
opacity: 0.9;
|
|
197
|
+
}
|
|
198
|
+
50% {
|
|
199
|
+
opacity: 1;
|
|
200
|
+
}
|
|
201
|
+
100% {
|
|
202
|
+
clip-path: circle(150% at var(--theme-x, 50%) var(--theme-y, 50%));
|
|
203
|
+
opacity: 1;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
html.theme-transition::view-transition-new(root) {
|
|
208
|
+
animation: circle-expand 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
html.theme-transition.dark-transition::view-transition-old(root) {
|
|
212
|
+
animation: circle-expand 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
html.theme-transition.dark-transition::view-transition-new(root) {
|
|
216
|
+
animation: none;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
html.no-view-transitions {
|
|
220
|
+
--transition-duration: 0.25s;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
html.no-view-transitions body,
|
|
224
|
+
html.no-view-transitions header,
|
|
225
|
+
html.no-view-transitions footer,
|
|
226
|
+
html.no-view-transitions main {
|
|
227
|
+
transition:
|
|
228
|
+
background-color var(--transition-duration) cubic-bezier(0.4, 0, 0.2, 1),
|
|
229
|
+
color var(--transition-duration) cubic-bezier(0.4, 0, 0.2, 1),
|
|
230
|
+
border-color var(--transition-duration) cubic-bezier(0.4, 0, 0.2, 1);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/* ===== Theme Toggle Button ===== */
|
|
234
|
+
#theme-btn {
|
|
235
|
+
position: relative;
|
|
236
|
+
overflow: hidden;
|
|
237
|
+
transition:
|
|
238
|
+
transform 0.2s cubic-bezier(0.34, 1.56, 0.64, 1),
|
|
239
|
+
box-shadow 0.2s ease-out;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
#theme-btn::after {
|
|
243
|
+
content: "";
|
|
244
|
+
position: absolute;
|
|
245
|
+
inset: -2px;
|
|
246
|
+
border-radius: 50%;
|
|
247
|
+
opacity: 0;
|
|
248
|
+
transition: opacity 0.3s ease-out;
|
|
249
|
+
pointer-events: none;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
html:not([data-theme="dark"]) #theme-btn::after {
|
|
253
|
+
box-shadow: 0 0 20px 4px rgba(0, 108, 172, 0.3);
|
|
254
|
+
}
|
|
255
|
+
html[data-theme="dark"] #theme-btn::after {
|
|
256
|
+
box-shadow: 0 0 20px 4px rgba(255, 107, 1, 0.3);
|
|
257
|
+
}
|
|
258
|
+
#theme-btn:hover::after {
|
|
259
|
+
opacity: 1;
|
|
260
|
+
}
|
|
261
|
+
#theme-btn:active {
|
|
262
|
+
transform: scale(0.92);
|
|
263
|
+
}
|
|
264
|
+
#theme-btn::before {
|
|
265
|
+
content: "";
|
|
266
|
+
position: absolute;
|
|
267
|
+
top: 50%;
|
|
268
|
+
left: 50%;
|
|
269
|
+
width: 0;
|
|
270
|
+
height: 0;
|
|
271
|
+
border-radius: 50%;
|
|
272
|
+
transform: translate(-50%, -50%);
|
|
273
|
+
transition:
|
|
274
|
+
width 0.4s cubic-bezier(0.4, 0, 0.2, 1),
|
|
275
|
+
height 0.4s cubic-bezier(0.4, 0, 0.2, 1),
|
|
276
|
+
opacity 0.4s ease-out;
|
|
277
|
+
opacity: 0;
|
|
278
|
+
}
|
|
279
|
+
#theme-btn:active::before {
|
|
280
|
+
width: 200%;
|
|
281
|
+
height: 200%;
|
|
282
|
+
opacity: 0.15;
|
|
283
|
+
}
|
|
284
|
+
html:not([data-theme="dark"]) #theme-btn:active::before {
|
|
285
|
+
background: radial-gradient(circle, rgba(0, 108, 172, 0.3) 0%, transparent 70%);
|
|
286
|
+
}
|
|
287
|
+
html[data-theme="dark"] #theme-btn:active::before {
|
|
288
|
+
background: radial-gradient(circle, rgba(255, 107, 1, 0.3) 0%, transparent 70%);
|
|
289
|
+
}
|
|
290
|
+
#theme-btn svg {
|
|
291
|
+
transition:
|
|
292
|
+
transform 0.3s cubic-bezier(0.34, 1.56, 0.64, 1),
|
|
293
|
+
opacity 0.3s ease-out,
|
|
294
|
+
color 0.3s ease-out;
|
|
295
|
+
}
|
|
296
|
+
#theme-btn .icon-moon {
|
|
297
|
+
transform: scale(1) rotate(0deg);
|
|
298
|
+
}
|
|
299
|
+
#theme-btn .icon-sun {
|
|
300
|
+
transform: scale(0) rotate(-180deg);
|
|
301
|
+
}
|
|
302
|
+
html[data-theme="dark"] #theme-btn .icon-moon {
|
|
303
|
+
transform: scale(0) rotate(180deg);
|
|
304
|
+
}
|
|
305
|
+
html[data-theme="dark"] #theme-btn .icon-sun {
|
|
306
|
+
transform: scale(1) rotate(0deg);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/* ===== Reduced Motion ===== */
|
|
310
|
+
@media (prefers-reduced-motion: reduce) {
|
|
311
|
+
*,
|
|
312
|
+
*::before,
|
|
313
|
+
*::after {
|
|
314
|
+
animation-duration: 0.01ms !important;
|
|
315
|
+
animation-iteration-count: 1 !important;
|
|
316
|
+
transition-duration: 0.01ms !important;
|
|
317
|
+
}
|
|
318
|
+
#theme-btn:active {
|
|
319
|
+
transform: scale(0.95);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
html.theme-transition {
|
|
324
|
+
--page-scale: 0.995;
|
|
325
|
+
}
|
|
326
|
+
html.theme-transition body {
|
|
327
|
+
overflow-x: hidden;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/* ===== Print Styles ===== */
|
|
331
|
+
@media print {
|
|
332
|
+
body {
|
|
333
|
+
background: #fff !important;
|
|
334
|
+
color: #000 !important;
|
|
335
|
+
font-size: 12pt;
|
|
336
|
+
}
|
|
337
|
+
header,
|
|
338
|
+
footer,
|
|
339
|
+
nav,
|
|
340
|
+
#floating-actions,
|
|
341
|
+
#ai-chat-panel,
|
|
342
|
+
#ai-chat-toggle-fab,
|
|
343
|
+
#sticky-header,
|
|
344
|
+
#back-button,
|
|
345
|
+
.floating-toc-container,
|
|
346
|
+
.post-actions,
|
|
347
|
+
.share-links,
|
|
348
|
+
.code-copy,
|
|
349
|
+
.code-collapse-toggle,
|
|
350
|
+
.code-collapse-fade,
|
|
351
|
+
#lightbox-overlay,
|
|
352
|
+
[data-pagefind-ignore] {
|
|
353
|
+
display: none !important;
|
|
354
|
+
}
|
|
355
|
+
main {
|
|
356
|
+
max-width: 100% !important;
|
|
357
|
+
padding: 0 !important;
|
|
358
|
+
margin: 0 !important;
|
|
359
|
+
}
|
|
360
|
+
a {
|
|
361
|
+
color: #000 !important;
|
|
362
|
+
text-decoration: underline;
|
|
363
|
+
}
|
|
364
|
+
a[href]::after {
|
|
365
|
+
content: " (" attr(href) ")";
|
|
366
|
+
font-size: 0.8em;
|
|
367
|
+
color: #666;
|
|
368
|
+
}
|
|
369
|
+
a[href^="#"]::after,
|
|
370
|
+
a[href^="javascript"]::after {
|
|
371
|
+
content: "";
|
|
372
|
+
}
|
|
373
|
+
pre {
|
|
374
|
+
white-space: pre-wrap !important;
|
|
375
|
+
border: 1px solid #ccc !important;
|
|
376
|
+
background: #f5f5f5 !important;
|
|
377
|
+
page-break-inside: avoid;
|
|
378
|
+
}
|
|
379
|
+
img {
|
|
380
|
+
max-width: 100% !important;
|
|
381
|
+
page-break-inside: avoid;
|
|
382
|
+
}
|
|
383
|
+
h1,
|
|
384
|
+
h2,
|
|
385
|
+
h3,
|
|
386
|
+
h4 {
|
|
387
|
+
page-break-after: avoid;
|
|
388
|
+
color: #000 !important;
|
|
389
|
+
}
|
|
390
|
+
.app-prose {
|
|
391
|
+
max-width: 100% !important;
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
.card,
|
|
396
|
+
[class*="card"],
|
|
397
|
+
article,
|
|
398
|
+
.prose {
|
|
399
|
+
transition:
|
|
400
|
+
background-color 0.35s cubic-bezier(0.4, 0, 0.2, 1),
|
|
401
|
+
border-color 0.35s cubic-bezier(0.4, 0, 0.2, 1),
|
|
402
|
+
box-shadow 0.35s cubic-bezier(0.4, 0, 0.2, 1);
|
|
403
|
+
}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
@plugin "@tailwindcss/typography";
|
|
2
|
+
|
|
3
|
+
@layer base {
|
|
4
|
+
/* ===== Override default Tailwind Typography styles ===== */
|
|
5
|
+
.app-prose {
|
|
6
|
+
@apply prose;
|
|
7
|
+
|
|
8
|
+
/* 所有标题使用 foreground 颜色 */
|
|
9
|
+
h1,
|
|
10
|
+
h2,
|
|
11
|
+
h3,
|
|
12
|
+
h4,
|
|
13
|
+
h5,
|
|
14
|
+
h6,
|
|
15
|
+
th {
|
|
16
|
+
@apply mb-3 text-foreground;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
h3 {
|
|
20
|
+
@apply italic;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
p,
|
|
24
|
+
strong,
|
|
25
|
+
ol,
|
|
26
|
+
ul,
|
|
27
|
+
figcaption,
|
|
28
|
+
table,
|
|
29
|
+
code {
|
|
30
|
+
@apply text-foreground;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
a {
|
|
34
|
+
@apply wrap-break-word text-foreground underline decoration-dashed underline-offset-4 hover:text-accent focus-visible:no-underline;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
ul {
|
|
38
|
+
@apply overflow-x-clip;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
li {
|
|
42
|
+
@apply marker:text-accent;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
hr {
|
|
46
|
+
@apply border-border;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
img {
|
|
50
|
+
@apply mx-auto border border-border;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
figcaption {
|
|
54
|
+
@apply opacity-75;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
table {
|
|
58
|
+
th,
|
|
59
|
+
td {
|
|
60
|
+
@apply border border-border p-2;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
th {
|
|
64
|
+
@apply py-1.5;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
code {
|
|
68
|
+
@apply break-all sm:break-normal;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
code {
|
|
73
|
+
@apply rounded bg-muted/75 p-1 wrap-break-word text-foreground before:content-none after:content-none;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
.astro-code code {
|
|
77
|
+
@apply flex-[1_0_100%] bg-inherit p-0;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
blockquote {
|
|
81
|
+
@apply border-s-accent/80 wrap-break-word opacity-80;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
.heading-anchor {
|
|
85
|
+
@apply mr-1.5 text-accent/50 no-underline opacity-0 transition-opacity;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
h1:hover .heading-anchor,
|
|
89
|
+
h2:hover .heading-anchor,
|
|
90
|
+
h3:hover .heading-anchor,
|
|
91
|
+
h4:hover .heading-anchor,
|
|
92
|
+
h5:hover .heading-anchor,
|
|
93
|
+
h6:hover .heading-anchor,
|
|
94
|
+
.heading-anchor:focus {
|
|
95
|
+
@apply opacity-100;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
details {
|
|
99
|
+
@apply inline-block cursor-pointer text-foreground select-none [&_p]:hidden [&_ul]:my-0!;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
summary {
|
|
103
|
+
@apply focus-visible:no-underline focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-accent focus-visible:outline-dashed;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
pre {
|
|
107
|
+
@apply focus-visible:border-transparent focus-visible:outline-2 focus-visible:outline-accent focus-visible:outline-dashed;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/* ===== Code Blocks & Syntax Highlighting ===== */
|
|
112
|
+
.astro-code {
|
|
113
|
+
@apply bg-background text-(--shiki-light);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
.astro-code span {
|
|
117
|
+
@apply text-(--shiki-light);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
html[data-theme="dark"] .astro-code {
|
|
121
|
+
@apply bg-background text-(--shiki-dark);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
html[data-theme="dark"] .astro-code span {
|
|
125
|
+
@apply text-(--shiki-dark);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/* Shiki transformer styles: diff, highlight, word-highlight */
|
|
129
|
+
.astro-code .line.diff.add {
|
|
130
|
+
@apply relative inline-block w-full bg-green-400/20;
|
|
131
|
+
}
|
|
132
|
+
.astro-code .line.diff.add::after {
|
|
133
|
+
content: "+";
|
|
134
|
+
@apply absolute left-1 text-green-500;
|
|
135
|
+
}
|
|
136
|
+
.astro-code .line.diff.remove {
|
|
137
|
+
@apply relative inline-block w-full bg-red-500/20;
|
|
138
|
+
}
|
|
139
|
+
.astro-code .line.diff.remove::after {
|
|
140
|
+
content: "-";
|
|
141
|
+
@apply absolute left-1 text-red-500;
|
|
142
|
+
}
|
|
143
|
+
.astro-code .line.highlighted {
|
|
144
|
+
@apply inline-block w-full bg-slate-400/20;
|
|
145
|
+
}
|
|
146
|
+
.astro-code .highlighted-word {
|
|
147
|
+
@apply rounded-sm border border-border px-0.5 py-px;
|
|
148
|
+
}
|
|
149
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @astro-minimax/core - Type definitions
|
|
3
|
+
*
|
|
4
|
+
* Consumer projects must provide a `src/config.ts` that exports a `SITE` object
|
|
5
|
+
* conforming to the `SiteConfig` interface below.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export interface NavItem {
|
|
9
|
+
key: string;
|
|
10
|
+
enabled: boolean;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface EditPostConfig {
|
|
14
|
+
enabled: boolean;
|
|
15
|
+
text: string;
|
|
16
|
+
url: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface FeaturesConfig {
|
|
20
|
+
tags?: boolean;
|
|
21
|
+
categories?: boolean;
|
|
22
|
+
series?: boolean;
|
|
23
|
+
archives?: boolean;
|
|
24
|
+
friends?: boolean;
|
|
25
|
+
projects?: boolean;
|
|
26
|
+
search?: boolean;
|
|
27
|
+
darkMode?: boolean;
|
|
28
|
+
ai?: boolean;
|
|
29
|
+
waline?: boolean;
|
|
30
|
+
sponsor?: boolean;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface WalineConfig {
|
|
34
|
+
serverURL: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface AiConfig {
|
|
38
|
+
apiKey?: string;
|
|
39
|
+
baseURL?: string;
|
|
40
|
+
model?: string;
|
|
41
|
+
systemPrompt?: string;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface SponsorItem {
|
|
45
|
+
platform: string;
|
|
46
|
+
qrcode: string;
|
|
47
|
+
hint: string;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface SponsorConfig {
|
|
51
|
+
items: SponsorItem[];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export interface CopyrightConfig {
|
|
55
|
+
license: string;
|
|
56
|
+
url: string;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export interface UmamiConfig {
|
|
60
|
+
websiteId: string;
|
|
61
|
+
src: string;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export interface SiteConfig {
|
|
65
|
+
website: string;
|
|
66
|
+
author: string;
|
|
67
|
+
profile?: string;
|
|
68
|
+
desc: string;
|
|
69
|
+
title: string;
|
|
70
|
+
ogImage?: string;
|
|
71
|
+
postPerIndex?: number;
|
|
72
|
+
postPerPage?: number;
|
|
73
|
+
scheduledPostMargin?: number;
|
|
74
|
+
showBackButton?: boolean;
|
|
75
|
+
showArchives?: boolean;
|
|
76
|
+
startDate?: string;
|
|
77
|
+
editPost?: EditPostConfig;
|
|
78
|
+
dynamicOgImage?: boolean;
|
|
79
|
+
dir?: "ltr" | "rtl";
|
|
80
|
+
lang?: string;
|
|
81
|
+
timezone?: string;
|
|
82
|
+
features?: FeaturesConfig;
|
|
83
|
+
nav?: { items: NavItem[] };
|
|
84
|
+
umami?: UmamiConfig;
|
|
85
|
+
waline?: WalineConfig;
|
|
86
|
+
ai?: AiConfig;
|
|
87
|
+
sponsor?: SponsorConfig;
|
|
88
|
+
copyright?: CopyrightConfig;
|
|
89
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { type CollectionEntry } from "astro:content";
|
|
2
|
+
import postOgImage from "./og-templates/post";
|
|
3
|
+
import siteOgImage from "./og-templates/site";
|
|
4
|
+
|
|
5
|
+
// Lazy-load resvg only when needed (build time)
|
|
6
|
+
// This allows the module to be externalized for Cloudflare Workers
|
|
7
|
+
let ResvgClass: typeof import("@resvg/resvg-js").Resvg | null = null;
|
|
8
|
+
|
|
9
|
+
async function getResvg() {
|
|
10
|
+
if (!ResvgClass) {
|
|
11
|
+
try {
|
|
12
|
+
const module = await import("@resvg/resvg-js");
|
|
13
|
+
ResvgClass = module.Resvg;
|
|
14
|
+
} catch {
|
|
15
|
+
throw new Error(
|
|
16
|
+
"@resvg/resvg-js is not available. OG images must be prerendered at build time."
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return ResvgClass;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async function svgBufferToPngBuffer(svg: string) {
|
|
24
|
+
const Resvg = await getResvg();
|
|
25
|
+
const resvg = new Resvg(svg);
|
|
26
|
+
const pngData = resvg.render();
|
|
27
|
+
return pngData.asPng();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export async function generateOgImageForPost(post: CollectionEntry<"blog">) {
|
|
31
|
+
const svg = await postOgImage(post);
|
|
32
|
+
return svgBufferToPngBuffer(svg);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export async function generateOgImageForSite() {
|
|
36
|
+
const svg = await siteOgImage();
|
|
37
|
+
return svgBufferToPngBuffer(svg);
|
|
38
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build category URL path segments for routing.
|
|
3
|
+
* "教程/Rust" → ["教程", "Rust"] for page 1, ["教程", "Rust", "2"] for page 2
|
|
4
|
+
*/
|
|
5
|
+
export function getCategoryPathSegments(category: string, page = 1): string[] {
|
|
6
|
+
const parts = category.split("/");
|
|
7
|
+
if (page <= 1) return parts;
|
|
8
|
+
return [...parts, String(page)];
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Build full category URL for links (each segment encoded).
|
|
13
|
+
* "教程/Rust" → "/zh/categories/教程/Rust/" or "/zh/categories/教程/Rust/2/"
|
|
14
|
+
*/
|
|
15
|
+
export function getCategoryUrl(
|
|
16
|
+
lang: string,
|
|
17
|
+
category: string,
|
|
18
|
+
page = 1
|
|
19
|
+
): string {
|
|
20
|
+
const segments = getCategoryPathSegments(category, page);
|
|
21
|
+
const encoded = segments.map((p) => encodeURIComponent(p)).join("/");
|
|
22
|
+
return `/${lang}/categories/${encoded}/`;
|
|
23
|
+
}
|