@anglefeint/astro-theme 0.1.37 → 0.1.39
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 +1 -1
- package/package.json +1 -1
- package/src/components/shared/CommonHeader.astro +222 -216
- package/src/components/shared/SocialMenu.astro +29 -29
- package/src/components/shared/ThemeFrame.astro +65 -64
- package/src/config/theme.ts +2 -0
- package/src/i18n/messages.ts +138 -5
- package/src/layouts/BlogPost.astro +71 -17
- package/src/scripts/about/modals.js +1 -1
package/README.md
CHANGED
|
@@ -54,7 +54,7 @@ This package reads site-specific config from alias imports:
|
|
|
54
54
|
|
|
55
55
|
In the starter/site project, map these aliases to `src/config/*` and `src/i18n/*` in both Vite and TS config.
|
|
56
56
|
|
|
57
|
-
Giscus comments are configured from site-side `theme.comments` (core IDs + behavior fields like `mapping`, `inputPosition`, `theme`, and `lang`). If required core fields are not set, comments are not rendered.
|
|
57
|
+
Giscus comments are configured from site-side `theme.comments` (core IDs + behavior fields like `mapping`, `inputPosition`, `theme`, and `lang`). If required core fields are not set, comments are not rendered. When `mapping="specific"` set `term`; when `mapping="number"` set `number`.
|
|
58
58
|
|
|
59
59
|
## CLI
|
|
60
60
|
|
package/package.json
CHANGED
|
@@ -6,242 +6,248 @@ import HeaderLink from '../HeaderLink.astro';
|
|
|
6
6
|
import LangSwitcher from './LangSwitcher.astro';
|
|
7
7
|
import SocialMenu from './SocialMenu.astro';
|
|
8
8
|
import {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
9
|
+
DEFAULT_LOCALE,
|
|
10
|
+
LOCALE_LABELS,
|
|
11
|
+
type Locale,
|
|
12
|
+
SUPPORTED_LOCALES,
|
|
13
|
+
alternatePathForLocale,
|
|
14
|
+
isLocale,
|
|
15
|
+
stripLocaleFromPath,
|
|
16
16
|
} from '@anglefeint/site-i18n/config';
|
|
17
17
|
|
|
18
18
|
interface Props {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
19
|
+
locale?: Locale;
|
|
20
|
+
homeHref?: string;
|
|
21
|
+
/** Optional per-locale overrides (used for existence-aware language fallback on some routes). */
|
|
22
|
+
localeHrefs?: Partial<Record<Locale, string>>;
|
|
23
|
+
/** Show Blade Runner scanlines overlay on ai-page (header/footer only) */
|
|
24
|
+
scanlines?: boolean;
|
|
25
|
+
labels?: {
|
|
26
|
+
home: string;
|
|
27
|
+
blog: string;
|
|
28
|
+
about: string;
|
|
29
|
+
status: string;
|
|
30
|
+
statusAria: string;
|
|
31
|
+
language: string;
|
|
32
|
+
};
|
|
32
33
|
}
|
|
33
34
|
|
|
34
35
|
const props = Astro.props as Props;
|
|
35
36
|
const locale: Locale = props.locale && isLocale(props.locale) ? props.locale : DEFAULT_LOCALE;
|
|
36
37
|
const labels = {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
38
|
+
home: props.labels?.home ?? 'Home',
|
|
39
|
+
blog: props.labels?.blog ?? 'Blog',
|
|
40
|
+
about: props.labels?.about ?? 'About',
|
|
41
|
+
status: props.labels?.status ?? 'system: online',
|
|
42
|
+
statusAria: props.labels?.statusAria ?? 'System status',
|
|
43
|
+
language: props.labels?.language ?? 'Language',
|
|
42
44
|
};
|
|
43
45
|
const showAbout = THEME.ENABLE_ABOUT_PAGE;
|
|
44
46
|
const currentSubpath = stripLocaleFromPath(Astro.url.pathname, locale);
|
|
45
47
|
const homeHref = props.homeHref ?? alternatePathForLocale(locale, '/');
|
|
46
48
|
const buildLocaleHref = (targetLocale: Locale) => {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
49
|
+
// Language switcher: preserve current route when switching locales.
|
|
50
|
+
// Only fall back for About when the feature is disabled.
|
|
51
|
+
const sectionSubpath =
|
|
52
|
+
currentSubpath.startsWith('/about') && !showAbout ? '/' : currentSubpath || '/';
|
|
53
|
+
return alternatePathForLocale(targetLocale, sectionSubpath);
|
|
52
54
|
};
|
|
53
55
|
const localeOptions = SUPPORTED_LOCALES.map((targetLocale) => ({
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
56
|
+
locale: targetLocale,
|
|
57
|
+
label: LOCALE_LABELS[targetLocale],
|
|
58
|
+
href: props.localeHrefs?.[targetLocale] ?? buildLocaleHref(targetLocale),
|
|
57
59
|
}));
|
|
58
60
|
---
|
|
59
61
|
|
|
60
62
|
<header>
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
63
|
+
<nav>
|
|
64
|
+
<div class="nav-left">
|
|
65
|
+
<h2><a href={homeHref}>{SITE_TITLE}</a></h2>
|
|
66
|
+
</div>
|
|
67
|
+
<div class="internal-links">
|
|
68
|
+
<HeaderLink href={homeHref}>{labels.home}</HeaderLink>
|
|
69
|
+
<HeaderLink href={alternatePathForLocale(locale, '/blog/')}>{labels.blog}</HeaderLink>
|
|
70
|
+
{
|
|
71
|
+
showAbout && (
|
|
72
|
+
<HeaderLink href={alternatePathForLocale(locale, '/about/')}>{labels.about}</HeaderLink>
|
|
73
|
+
)
|
|
74
|
+
}
|
|
75
|
+
</div>
|
|
76
|
+
<div class="nav-right">
|
|
77
|
+
<div class="social-links">
|
|
78
|
+
<LangSwitcher label={labels.language} currentLocale={locale} options={localeOptions} />
|
|
79
|
+
<div class="nav-status" aria-label={labels.statusAria}>
|
|
80
|
+
<span class="nav-status-dot" aria-hidden="true"></span>
|
|
81
|
+
<span class="nav-status-text">{labels.status}</span>
|
|
82
|
+
</div>
|
|
83
|
+
<SocialMenu links={SOCIAL_LINKS} />
|
|
84
|
+
</div>
|
|
85
|
+
</div>
|
|
86
|
+
</nav>
|
|
87
|
+
{props.scanlines && <div class="ai-scanlines" aria-hidden="true" />}
|
|
82
88
|
</header>
|
|
83
89
|
<style>
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
90
|
+
header {
|
|
91
|
+
margin: 0;
|
|
92
|
+
padding: 0 1em;
|
|
93
|
+
background: var(--chrome-bg, var(--bg));
|
|
94
|
+
border-bottom: 1px solid var(--chrome-border, rgb(var(--border)));
|
|
95
|
+
}
|
|
96
|
+
h2 {
|
|
97
|
+
margin: 0;
|
|
98
|
+
font-size: 1em;
|
|
99
|
+
}
|
|
94
100
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
101
|
+
h2 a,
|
|
102
|
+
h2 a.active {
|
|
103
|
+
text-decoration: none;
|
|
104
|
+
color: var(--chrome-link, rgb(var(--text)));
|
|
105
|
+
border-bottom: none;
|
|
106
|
+
}
|
|
107
|
+
nav {
|
|
108
|
+
display: grid;
|
|
109
|
+
grid-template-columns: 1fr auto 1fr;
|
|
110
|
+
align-items: center;
|
|
111
|
+
gap: 0.6rem;
|
|
112
|
+
min-height: 56px;
|
|
113
|
+
width: 100%;
|
|
114
|
+
}
|
|
115
|
+
.nav-left {
|
|
116
|
+
justify-self: start;
|
|
117
|
+
}
|
|
118
|
+
.internal-links {
|
|
119
|
+
justify-self: center;
|
|
120
|
+
display: flex;
|
|
121
|
+
align-items: center;
|
|
122
|
+
gap: 0.1rem;
|
|
123
|
+
}
|
|
124
|
+
.nav-right {
|
|
125
|
+
justify-self: end;
|
|
126
|
+
display: flex;
|
|
127
|
+
align-items: center;
|
|
128
|
+
gap: 0.35rem;
|
|
129
|
+
min-width: max-content;
|
|
130
|
+
}
|
|
131
|
+
.internal-links :global(a) {
|
|
132
|
+
padding: 1em 0.5em;
|
|
133
|
+
color: var(--chrome-link, rgb(var(--text)));
|
|
134
|
+
border-bottom: 4px solid transparent;
|
|
135
|
+
text-decoration: none;
|
|
136
|
+
}
|
|
137
|
+
.internal-links :global(a.active) {
|
|
138
|
+
text-decoration: none;
|
|
139
|
+
border-bottom-color: var(--chrome-active, var(--accent));
|
|
140
|
+
}
|
|
141
|
+
.social-links a {
|
|
142
|
+
color: var(--chrome-link, rgb(var(--text)));
|
|
143
|
+
align-items: center;
|
|
144
|
+
justify-content: center;
|
|
145
|
+
padding: 0.6rem 0.35rem;
|
|
146
|
+
border-bottom: none;
|
|
147
|
+
}
|
|
148
|
+
.social-links a:hover {
|
|
149
|
+
color: var(--chrome-link-hover, var(--chrome-link, rgb(var(--text))));
|
|
150
|
+
}
|
|
151
|
+
.social-links,
|
|
152
|
+
.social-links a {
|
|
153
|
+
display: flex;
|
|
154
|
+
}
|
|
155
|
+
.social-links {
|
|
156
|
+
align-items: center;
|
|
157
|
+
gap: 0.4rem;
|
|
158
|
+
position: relative;
|
|
159
|
+
flex-wrap: nowrap;
|
|
160
|
+
--lang-switcher-border: rgba(132, 214, 255, 0.2);
|
|
161
|
+
--lang-switcher-bg: rgba(6, 16, 30, 0.35);
|
|
162
|
+
--lang-label-color: rgba(190, 226, 248, 0.78);
|
|
163
|
+
--lang-select-arrow: rgba(204, 236, 252, 0.82);
|
|
164
|
+
--lang-select-bg: rgba(9, 22, 40, 0.68);
|
|
165
|
+
--lang-select-border: rgba(132, 214, 255, 0.2);
|
|
166
|
+
--lang-select-text: rgba(204, 236, 252, 0.86);
|
|
167
|
+
--lang-select-focus-border: rgba(132, 214, 255, 0.5);
|
|
168
|
+
--lang-select-focus-ring: rgba(98, 180, 228, 0.18);
|
|
169
|
+
--lang-select-option-text: #ccecfb;
|
|
170
|
+
--lang-select-option-bg: #0b1c32;
|
|
171
|
+
}
|
|
172
|
+
.nav-status {
|
|
173
|
+
display: none;
|
|
174
|
+
align-items: center;
|
|
175
|
+
gap: 0.45rem;
|
|
176
|
+
padding: 0.24rem 0.5rem;
|
|
177
|
+
border-radius: 999px;
|
|
178
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
179
|
+
font-size: 0.62rem;
|
|
180
|
+
letter-spacing: 0.12em;
|
|
181
|
+
text-transform: uppercase;
|
|
182
|
+
color: rgba(186, 232, 252, 0.86);
|
|
183
|
+
background: rgba(6, 16, 30, 0.52);
|
|
184
|
+
border: 1px solid rgba(132, 214, 255, 0.22);
|
|
185
|
+
box-shadow:
|
|
186
|
+
0 0 0 1px rgba(132, 214, 255, 0.08),
|
|
187
|
+
0 0 16px rgba(90, 180, 255, 0.16);
|
|
188
|
+
white-space: nowrap;
|
|
189
|
+
position: absolute;
|
|
190
|
+
right: calc(100% + 0.5rem);
|
|
191
|
+
top: 50%;
|
|
192
|
+
transform: translateY(-50%);
|
|
193
|
+
pointer-events: none;
|
|
194
|
+
}
|
|
195
|
+
.nav-status-dot {
|
|
196
|
+
width: 0.44rem;
|
|
197
|
+
height: 0.44rem;
|
|
198
|
+
border-radius: 50%;
|
|
199
|
+
background: rgba(150, 226, 255, 0.96);
|
|
200
|
+
box-shadow: 0 0 10px rgba(122, 210, 255, 0.72);
|
|
201
|
+
animation: nav-status-pulse 1.8s steps(1, end) infinite;
|
|
202
|
+
}
|
|
203
|
+
@keyframes nav-status-pulse {
|
|
204
|
+
0%,
|
|
205
|
+
78%,
|
|
206
|
+
100% {
|
|
207
|
+
opacity: 1;
|
|
208
|
+
transform: scale(1);
|
|
209
|
+
}
|
|
210
|
+
82% {
|
|
211
|
+
opacity: 0.2;
|
|
212
|
+
transform: scale(0.7);
|
|
213
|
+
}
|
|
214
|
+
86% {
|
|
215
|
+
opacity: 1;
|
|
216
|
+
transform: scale(1.05);
|
|
217
|
+
}
|
|
218
|
+
90% {
|
|
219
|
+
opacity: 0.28;
|
|
220
|
+
transform: scale(0.78);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
body.ai-page .nav-status {
|
|
224
|
+
display: inline-flex;
|
|
225
|
+
}
|
|
226
|
+
body.about-page .nav-status {
|
|
227
|
+
display: none;
|
|
228
|
+
}
|
|
229
|
+
@media (max-width: 720px) {
|
|
230
|
+
nav {
|
|
231
|
+
grid-template-columns: 1fr;
|
|
232
|
+
gap: 0;
|
|
233
|
+
}
|
|
234
|
+
.nav-left {
|
|
235
|
+
display: none;
|
|
236
|
+
}
|
|
237
|
+
.internal-links {
|
|
238
|
+
justify-self: start;
|
|
239
|
+
}
|
|
240
|
+
.nav-right {
|
|
241
|
+
justify-self: end;
|
|
242
|
+
}
|
|
243
|
+
.nav-status {
|
|
244
|
+
display: none !important;
|
|
245
|
+
}
|
|
246
|
+
.social-links {
|
|
247
|
+
display: none;
|
|
248
|
+
}
|
|
249
|
+
.lang-switcher {
|
|
250
|
+
margin-right: 0;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
247
253
|
</style>
|
|
@@ -3,46 +3,46 @@ import { SOCIAL_LINKS, type SocialLink } from '@anglefeint/site-config/social';
|
|
|
3
3
|
import Icon from './Icon.astro';
|
|
4
4
|
|
|
5
5
|
interface Props {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
links?: SocialLink[];
|
|
7
|
+
iconSize?: number;
|
|
8
|
+
showPlaceholdersWhenEmpty?: boolean;
|
|
9
9
|
}
|
|
10
10
|
|
|
11
11
|
const {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
links = SOCIAL_LINKS,
|
|
13
|
+
iconSize = 32,
|
|
14
|
+
showPlaceholdersWhenEmpty = true,
|
|
15
15
|
} = Astro.props as Props;
|
|
16
16
|
const placeholderLinks: SocialLink[] = [
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
{ href: '', label: 'Mastodon', icon: 'mastodon' },
|
|
18
|
+
{ href: '', label: 'Twitter', icon: 'twitter' },
|
|
19
|
+
{ href: '', label: 'GitHub', icon: 'github' },
|
|
20
20
|
];
|
|
21
21
|
const resolvedLinks = links.length > 0 || !showPlaceholdersWhenEmpty ? links : placeholderLinks;
|
|
22
22
|
---
|
|
23
23
|
|
|
24
24
|
{
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
25
|
+
resolvedLinks.map((link) =>
|
|
26
|
+
links.length > 0 ? (
|
|
27
|
+
<a href={link.href} target="_blank" rel="noopener noreferrer">
|
|
28
|
+
<span class="sr-only">{link.label}</span>
|
|
29
|
+
{link.icon ? <Icon name={link.icon} size={iconSize} /> : <span>{link.label}</span>}
|
|
30
|
+
</a>
|
|
31
|
+
) : (
|
|
32
|
+
<span class="social-placeholder" aria-hidden="true">
|
|
33
|
+
{link.icon ? <Icon name={link.icon} size={iconSize} /> : <span>{link.label}</span>}
|
|
34
|
+
</span>
|
|
35
|
+
)
|
|
36
|
+
)
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
<style>
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
40
|
+
.social-placeholder {
|
|
41
|
+
display: inline-flex;
|
|
42
|
+
align-items: center;
|
|
43
|
+
justify-content: center;
|
|
44
|
+
opacity: 0.45;
|
|
45
|
+
filter: saturate(0.7);
|
|
46
|
+
cursor: default;
|
|
47
|
+
}
|
|
48
48
|
</style>
|
|
@@ -7,79 +7,80 @@ import { getMessages } from '@anglefeint/site-i18n/messages';
|
|
|
7
7
|
import type { ImageMetadata } from 'astro';
|
|
8
8
|
|
|
9
9
|
interface Props {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
10
|
+
locale?: Locale;
|
|
11
|
+
title: string;
|
|
12
|
+
description: string;
|
|
13
|
+
image?: ImageMetadata;
|
|
14
|
+
pageType?: 'website' | 'article';
|
|
15
|
+
publishedTime?: Date;
|
|
16
|
+
modifiedTime?: Date;
|
|
17
|
+
author?: string;
|
|
18
|
+
tags?: string[];
|
|
19
|
+
schema?: Record<string, unknown> | Record<string, unknown>[];
|
|
20
|
+
noindex?: boolean;
|
|
21
|
+
bodyClass: string;
|
|
22
|
+
mainClass?: string;
|
|
23
|
+
scanlines?: boolean;
|
|
24
|
+
localeHrefs?: Partial<Record<Locale, string>>;
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
const rawLocale = Astro.props.locale ?? DEFAULT_LOCALE;
|
|
28
28
|
const locale: Locale = isLocale(rawLocale) ? rawLocale : DEFAULT_LOCALE;
|
|
29
29
|
const {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
30
|
+
title,
|
|
31
|
+
description,
|
|
32
|
+
image,
|
|
33
|
+
pageType,
|
|
34
|
+
publishedTime,
|
|
35
|
+
modifiedTime,
|
|
36
|
+
author,
|
|
37
|
+
tags,
|
|
38
|
+
schema,
|
|
39
|
+
noindex,
|
|
40
|
+
bodyClass,
|
|
41
|
+
mainClass = 'page-main',
|
|
42
|
+
scanlines = false,
|
|
43
|
+
localeHrefs,
|
|
44
44
|
} = Astro.props as Props;
|
|
45
45
|
const messages = getMessages(locale);
|
|
46
46
|
---
|
|
47
47
|
|
|
48
48
|
<!doctype html>
|
|
49
49
|
<html lang={locale}>
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
50
|
+
<head>
|
|
51
|
+
<BaseHead
|
|
52
|
+
title={title}
|
|
53
|
+
description={description}
|
|
54
|
+
image={image}
|
|
55
|
+
pageType={pageType}
|
|
56
|
+
publishedTime={publishedTime}
|
|
57
|
+
modifiedTime={modifiedTime}
|
|
58
|
+
author={author}
|
|
59
|
+
tags={tags}
|
|
60
|
+
schema={schema}
|
|
61
|
+
noindex={noindex}
|
|
62
|
+
/>
|
|
63
|
+
<slot name="head" />
|
|
64
|
+
</head>
|
|
65
|
+
<body class={bodyClass}>
|
|
66
|
+
<slot name="body-start" />
|
|
67
|
+
<CommonHeader
|
|
68
|
+
locale={locale}
|
|
69
|
+
localeHrefs={localeHrefs}
|
|
70
|
+
scanlines={scanlines}
|
|
71
|
+
labels={{
|
|
72
|
+
home: messages.nav.home,
|
|
73
|
+
blog: messages.nav.blog,
|
|
74
|
+
about: messages.nav.about,
|
|
75
|
+
status: messages.nav.status,
|
|
76
|
+
statusAria: messages.nav.statusAria,
|
|
77
|
+
language: messages.langLabel,
|
|
78
|
+
}}
|
|
79
|
+
/>
|
|
80
|
+
<main class={mainClass}>
|
|
81
|
+
<slot />
|
|
82
|
+
</main>
|
|
83
|
+
<CommonFooter scanlines={scanlines} />
|
|
84
|
+
<slot name="body-end" />
|
|
85
|
+
</body>
|
|
85
86
|
</html>
|
package/src/config/theme.ts
CHANGED
package/src/i18n/messages.ts
CHANGED
|
@@ -9,6 +9,7 @@ export type Messages = {
|
|
|
9
9
|
blog: string;
|
|
10
10
|
about: string;
|
|
11
11
|
status: string;
|
|
12
|
+
statusAria: string;
|
|
12
13
|
};
|
|
13
14
|
home: {
|
|
14
15
|
hero: string;
|
|
@@ -41,10 +42,27 @@ export type Messages = {
|
|
|
41
42
|
related: string;
|
|
42
43
|
comments: string;
|
|
43
44
|
responseOutput: string;
|
|
45
|
+
rqBadge: string;
|
|
46
|
+
rqReplayAria: string;
|
|
47
|
+
metaPublished: string;
|
|
48
|
+
metaUpdated: string;
|
|
49
|
+
metaReadMinutes: string;
|
|
50
|
+
systemStatusAria: string;
|
|
51
|
+
systemModelLabel: string;
|
|
52
|
+
systemModeLabel: string;
|
|
53
|
+
systemStateLabel: string;
|
|
54
|
+
promptContextLabel: string;
|
|
55
|
+
latencyLabel: string;
|
|
56
|
+
confidenceLabel: string;
|
|
57
|
+
statsWords: string;
|
|
58
|
+
statsTokens: string;
|
|
44
59
|
heroMonitor: string;
|
|
45
60
|
heroSignalSync: string;
|
|
46
61
|
heroModelOnline: string;
|
|
47
62
|
regenerate: string;
|
|
63
|
+
relatedAria: string;
|
|
64
|
+
backToBlogAria: string;
|
|
65
|
+
paginationAria: string;
|
|
48
66
|
toastP10: string;
|
|
49
67
|
toastP30: string;
|
|
50
68
|
toastP60: string;
|
|
@@ -57,7 +75,13 @@ export const DEFAULT_MESSAGES: Record<Locale, Messages> = {
|
|
|
57
75
|
siteTitle: 'Angle Feint',
|
|
58
76
|
siteDescription: 'Cinematic web interfaces and AI-era engineering essays.',
|
|
59
77
|
langLabel: 'Language',
|
|
60
|
-
nav: {
|
|
78
|
+
nav: {
|
|
79
|
+
home: 'Home',
|
|
80
|
+
blog: 'Blog',
|
|
81
|
+
about: 'About',
|
|
82
|
+
status: 'system: online',
|
|
83
|
+
statusAria: 'System status',
|
|
84
|
+
},
|
|
61
85
|
home: {
|
|
62
86
|
hero: 'Write a short introduction for your site and what readers can expect from your posts.',
|
|
63
87
|
latest: 'Latest Posts',
|
|
@@ -89,10 +113,27 @@ export const DEFAULT_MESSAGES: Record<Locale, Messages> = {
|
|
|
89
113
|
related: 'Related',
|
|
90
114
|
comments: 'Comments',
|
|
91
115
|
responseOutput: 'Output',
|
|
116
|
+
rqBadge: 'monitor feed',
|
|
117
|
+
rqReplayAria: 'Replay monitor feed',
|
|
118
|
+
metaPublished: 'published',
|
|
119
|
+
metaUpdated: 'updated',
|
|
120
|
+
metaReadMinutes: 'min read',
|
|
121
|
+
systemStatusAria: 'Model status',
|
|
122
|
+
systemModelLabel: 'model',
|
|
123
|
+
systemModeLabel: 'mode',
|
|
124
|
+
systemStateLabel: 'state',
|
|
125
|
+
promptContextLabel: 'Context',
|
|
126
|
+
latencyLabel: 'latency est',
|
|
127
|
+
confidenceLabel: 'confidence',
|
|
128
|
+
statsWords: 'words',
|
|
129
|
+
statsTokens: 'tokens',
|
|
92
130
|
heroMonitor: 'neural monitor',
|
|
93
131
|
heroSignalSync: 'signal sync active',
|
|
94
132
|
heroModelOnline: 'model online',
|
|
95
133
|
regenerate: 'Regenerate',
|
|
134
|
+
relatedAria: 'Related posts',
|
|
135
|
+
backToBlogAria: 'Back to blog',
|
|
136
|
+
paginationAria: 'Pagination',
|
|
96
137
|
toastP10: 'context parsed 10%',
|
|
97
138
|
toastP30: 'context parsed 30%',
|
|
98
139
|
toastP60: 'inference stable 60%',
|
|
@@ -103,7 +144,13 @@ export const DEFAULT_MESSAGES: Record<Locale, Messages> = {
|
|
|
103
144
|
siteTitle: 'Angle Feint',
|
|
104
145
|
siteDescription: '映画的なWebインターフェースとAI時代のエンジニアリング考察。',
|
|
105
146
|
langLabel: '言語',
|
|
106
|
-
nav: {
|
|
147
|
+
nav: {
|
|
148
|
+
home: 'ホーム',
|
|
149
|
+
blog: 'ブログ',
|
|
150
|
+
about: 'プロフィール',
|
|
151
|
+
status: 'system: online',
|
|
152
|
+
statusAria: 'システム状態',
|
|
153
|
+
},
|
|
107
154
|
home: {
|
|
108
155
|
hero: 'このサイトの紹介文と、読者がどんな記事を期待できるかを書いてください。',
|
|
109
156
|
latest: '最新記事',
|
|
@@ -135,10 +182,27 @@ export const DEFAULT_MESSAGES: Record<Locale, Messages> = {
|
|
|
135
182
|
related: '関連記事',
|
|
136
183
|
comments: 'コメント',
|
|
137
184
|
responseOutput: '出力',
|
|
185
|
+
rqBadge: 'モニターフィード',
|
|
186
|
+
rqReplayAria: 'モニターフィードを再生',
|
|
187
|
+
metaPublished: '公開',
|
|
188
|
+
metaUpdated: '更新',
|
|
189
|
+
metaReadMinutes: '分で読了',
|
|
190
|
+
systemStatusAria: 'モデル状態',
|
|
191
|
+
systemModelLabel: 'モデル',
|
|
192
|
+
systemModeLabel: 'モード',
|
|
193
|
+
systemStateLabel: '状態',
|
|
194
|
+
promptContextLabel: 'コンテキスト',
|
|
195
|
+
latencyLabel: '推定レイテンシ',
|
|
196
|
+
confidenceLabel: '信頼度',
|
|
197
|
+
statsWords: '語',
|
|
198
|
+
statsTokens: 'トークン',
|
|
138
199
|
heroMonitor: 'ニューラルモニター',
|
|
139
200
|
heroSignalSync: 'シグナル同期中',
|
|
140
201
|
heroModelOnline: 'モデルオンライン',
|
|
141
202
|
regenerate: '再生成',
|
|
203
|
+
relatedAria: '関連記事',
|
|
204
|
+
backToBlogAria: 'ブログへ戻る',
|
|
205
|
+
paginationAria: 'ページネーション',
|
|
142
206
|
toastP10: '文脈解析 10%',
|
|
143
207
|
toastP30: '文脈解析 30%',
|
|
144
208
|
toastP60: '推論安定 60%',
|
|
@@ -149,7 +213,13 @@ export const DEFAULT_MESSAGES: Record<Locale, Messages> = {
|
|
|
149
213
|
siteTitle: 'Angle Feint',
|
|
150
214
|
siteDescription: '시네마틱 웹 인터페이스와 AI 시대 엔지니어링 에세이.',
|
|
151
215
|
langLabel: '언어',
|
|
152
|
-
nav: {
|
|
216
|
+
nav: {
|
|
217
|
+
home: '홈',
|
|
218
|
+
blog: '블로그',
|
|
219
|
+
about: '소개',
|
|
220
|
+
status: 'system: online',
|
|
221
|
+
statusAria: '시스템 상태',
|
|
222
|
+
},
|
|
153
223
|
home: {
|
|
154
224
|
hero: '사이트 소개와 방문자가 어떤 글을 기대할 수 있는지 간단히 작성하세요.',
|
|
155
225
|
latest: '최신 글',
|
|
@@ -181,10 +251,27 @@ export const DEFAULT_MESSAGES: Record<Locale, Messages> = {
|
|
|
181
251
|
related: '관련 글',
|
|
182
252
|
comments: '댓글',
|
|
183
253
|
responseOutput: '출력',
|
|
254
|
+
rqBadge: '모니터 피드',
|
|
255
|
+
rqReplayAria: '모니터 피드 다시 재생',
|
|
256
|
+
metaPublished: '게시',
|
|
257
|
+
metaUpdated: '수정',
|
|
258
|
+
metaReadMinutes: '분 읽기',
|
|
259
|
+
systemStatusAria: '모델 상태',
|
|
260
|
+
systemModelLabel: '모델',
|
|
261
|
+
systemModeLabel: '모드',
|
|
262
|
+
systemStateLabel: '상태',
|
|
263
|
+
promptContextLabel: '컨텍스트',
|
|
264
|
+
latencyLabel: '지연 추정',
|
|
265
|
+
confidenceLabel: '신뢰도',
|
|
266
|
+
statsWords: '단어',
|
|
267
|
+
statsTokens: '토큰',
|
|
184
268
|
heroMonitor: '뉴럴 모니터',
|
|
185
269
|
heroSignalSync: '신호 동기화 활성',
|
|
186
270
|
heroModelOnline: '모델 온라인',
|
|
187
271
|
regenerate: '재생성',
|
|
272
|
+
relatedAria: '관련 글',
|
|
273
|
+
backToBlogAria: '블로그로 돌아가기',
|
|
274
|
+
paginationAria: '페이지네이션',
|
|
188
275
|
toastP10: '컨텍스트 파싱 10%',
|
|
189
276
|
toastP30: '컨텍스트 파싱 30%',
|
|
190
277
|
toastP60: '추론 안정화 60%',
|
|
@@ -195,7 +282,13 @@ export const DEFAULT_MESSAGES: Record<Locale, Messages> = {
|
|
|
195
282
|
siteTitle: 'Angle Feint',
|
|
196
283
|
siteDescription: 'Interfaces web cinematográficas y ensayos de ingeniería en la era de IA.',
|
|
197
284
|
langLabel: 'Idioma',
|
|
198
|
-
nav: {
|
|
285
|
+
nav: {
|
|
286
|
+
home: 'Inicio',
|
|
287
|
+
blog: 'Blog',
|
|
288
|
+
about: 'Sobre mí',
|
|
289
|
+
status: 'system: online',
|
|
290
|
+
statusAria: 'Estado del sistema',
|
|
291
|
+
},
|
|
199
292
|
home: {
|
|
200
293
|
hero: 'Escribe una breve presentación del sitio y qué tipo de contenido encontrarán tus lectores.',
|
|
201
294
|
latest: 'Últimas publicaciones',
|
|
@@ -228,10 +321,27 @@ export const DEFAULT_MESSAGES: Record<Locale, Messages> = {
|
|
|
228
321
|
related: 'Relacionados',
|
|
229
322
|
comments: 'Comentarios',
|
|
230
323
|
responseOutput: 'Salida',
|
|
324
|
+
rqBadge: 'monitor de señal',
|
|
325
|
+
rqReplayAria: 'Reproducir monitor de señal',
|
|
326
|
+
metaPublished: 'publicado',
|
|
327
|
+
metaUpdated: 'actualizado',
|
|
328
|
+
metaReadMinutes: 'min de lectura',
|
|
329
|
+
systemStatusAria: 'Estado del modelo',
|
|
330
|
+
systemModelLabel: 'modelo',
|
|
331
|
+
systemModeLabel: 'modo',
|
|
332
|
+
systemStateLabel: 'estado',
|
|
333
|
+
promptContextLabel: 'Contexto',
|
|
334
|
+
latencyLabel: 'latencia est',
|
|
335
|
+
confidenceLabel: 'confianza',
|
|
336
|
+
statsWords: 'palabras',
|
|
337
|
+
statsTokens: 'tokens',
|
|
231
338
|
heroMonitor: 'monitor neural',
|
|
232
339
|
heroSignalSync: 'sincronización de señal activa',
|
|
233
340
|
heroModelOnline: 'modelo en línea',
|
|
234
341
|
regenerate: 'Regenerar',
|
|
342
|
+
relatedAria: 'Publicaciones relacionadas',
|
|
343
|
+
backToBlogAria: 'Volver al blog',
|
|
344
|
+
paginationAria: 'Paginación',
|
|
235
345
|
toastP10: 'contexto analizado 10%',
|
|
236
346
|
toastP30: 'contexto analizado 30%',
|
|
237
347
|
toastP60: 'inferencia estable 60%',
|
|
@@ -242,7 +352,13 @@ export const DEFAULT_MESSAGES: Record<Locale, Messages> = {
|
|
|
242
352
|
siteTitle: 'Angle Feint',
|
|
243
353
|
siteDescription: '电影感网页界面与 AI 时代工程实践文章。',
|
|
244
354
|
langLabel: '语言',
|
|
245
|
-
nav: {
|
|
355
|
+
nav: {
|
|
356
|
+
home: '首页',
|
|
357
|
+
blog: '博客',
|
|
358
|
+
about: '关于',
|
|
359
|
+
status: 'system: online',
|
|
360
|
+
statusAria: '系统状态',
|
|
361
|
+
},
|
|
246
362
|
home: {
|
|
247
363
|
hero: '在这里写一段站点简介,并告诉读者你将发布什么类型的内容。',
|
|
248
364
|
latest: '最新文章',
|
|
@@ -274,10 +390,27 @@ export const DEFAULT_MESSAGES: Record<Locale, Messages> = {
|
|
|
274
390
|
related: '相关文章',
|
|
275
391
|
comments: '评论',
|
|
276
392
|
responseOutput: '输出',
|
|
393
|
+
rqBadge: '监视器信号',
|
|
394
|
+
rqReplayAria: '重放监视器信号',
|
|
395
|
+
metaPublished: '发布',
|
|
396
|
+
metaUpdated: '更新',
|
|
397
|
+
metaReadMinutes: '分钟阅读',
|
|
398
|
+
systemStatusAria: '模型状态',
|
|
399
|
+
systemModelLabel: '模型',
|
|
400
|
+
systemModeLabel: '模式',
|
|
401
|
+
systemStateLabel: '状态',
|
|
402
|
+
promptContextLabel: '语境',
|
|
403
|
+
latencyLabel: '延迟估计',
|
|
404
|
+
confidenceLabel: '置信度',
|
|
405
|
+
statsWords: '词',
|
|
406
|
+
statsTokens: '令牌',
|
|
277
407
|
heroMonitor: '神经监视器',
|
|
278
408
|
heroSignalSync: '信号同步中',
|
|
279
409
|
heroModelOnline: '模型在线',
|
|
280
410
|
regenerate: '重新生成',
|
|
411
|
+
relatedAria: '相关文章',
|
|
412
|
+
backToBlogAria: '返回博客',
|
|
413
|
+
paginationAria: '分页导航',
|
|
281
414
|
toastP10: '语境解析 10%',
|
|
282
415
|
toastP30: '语境解析 30%',
|
|
283
416
|
toastP60: '推理稳定 60%',
|
|
@@ -58,8 +58,24 @@ const hasStats = aiModel || wordCount !== undefined || tokenCount !== undefined;
|
|
|
58
58
|
const confidenceText = aiConfidence !== undefined ? aiConfidence.toFixed(2) : undefined;
|
|
59
59
|
const enableRedQueen = THEME.EFFECTS.ENABLE_RED_QUEEN;
|
|
60
60
|
const comments = THEME.COMMENTS;
|
|
61
|
+
const normalizedCommentTerm = comments.TERM.trim();
|
|
62
|
+
const normalizedCommentNumber = comments.NUMBER.trim();
|
|
63
|
+
const hasValidCommentNumber =
|
|
64
|
+
/^[1-9]\d*$/.test(normalizedCommentNumber) &&
|
|
65
|
+
Number.isSafeInteger(Number(normalizedCommentNumber));
|
|
66
|
+
const hasMappingParam =
|
|
67
|
+
comments.MAPPING === 'specific'
|
|
68
|
+
? Boolean(normalizedCommentTerm)
|
|
69
|
+
: comments.MAPPING === 'number'
|
|
70
|
+
? hasValidCommentNumber
|
|
71
|
+
: true;
|
|
61
72
|
const hasCommentsConfig = Boolean(
|
|
62
|
-
comments.ENABLED &&
|
|
73
|
+
comments.ENABLED &&
|
|
74
|
+
comments.REPO &&
|
|
75
|
+
comments.REPO_ID &&
|
|
76
|
+
comments.CATEGORY &&
|
|
77
|
+
comments.CATEGORY_ID &&
|
|
78
|
+
hasMappingParam
|
|
63
79
|
);
|
|
64
80
|
---
|
|
65
81
|
|
|
@@ -105,13 +121,13 @@ const hasCommentsConfig = Boolean(
|
|
|
105
121
|
data-rq-src2={themeRedqueen2.src}
|
|
106
122
|
/>
|
|
107
123
|
<div class="rq-tv-badge">
|
|
108
|
-
|
|
124
|
+
{messages.blog.rqBadge}
|
|
109
125
|
<span class="rq-tv-dot" />
|
|
110
126
|
</div>
|
|
111
127
|
<button
|
|
112
128
|
type="button"
|
|
113
129
|
class="rq-tv-toggle"
|
|
114
|
-
aria-label=
|
|
130
|
+
aria-label={messages.blog.rqReplayAria}
|
|
115
131
|
aria-expanded="false"
|
|
116
132
|
>
|
|
117
133
|
▶
|
|
@@ -178,23 +194,51 @@ const hasCommentsConfig = Boolean(
|
|
|
178
194
|
<div class="prose">
|
|
179
195
|
<div class="title ai-title">
|
|
180
196
|
<div class="ai-meta-terminal">
|
|
181
|
-
$
|
|
182
|
-
{
|
|
183
|
-
{
|
|
197
|
+
$ {messages.blog.metaPublished}
|
|
198
|
+
{fmt(pubDate)}
|
|
199
|
+
{
|
|
200
|
+
updatedDate && (
|
|
201
|
+
<>
|
|
202
|
+
{' '}
|
|
203
|
+
| {messages.blog.metaUpdated} {fmt(updatedDate)}
|
|
204
|
+
</>
|
|
205
|
+
)
|
|
206
|
+
}
|
|
207
|
+
{
|
|
208
|
+
readMinutes !== undefined && (
|
|
209
|
+
<>
|
|
210
|
+
{' '}
|
|
211
|
+
| ~{readMinutes} {messages.blog.metaReadMinutes}
|
|
212
|
+
</>
|
|
213
|
+
)
|
|
214
|
+
}
|
|
184
215
|
</div>
|
|
185
216
|
{
|
|
186
217
|
hasSystemMeta && (
|
|
187
|
-
<div class="ai-system-row" aria-label=
|
|
188
|
-
{aiModel &&
|
|
189
|
-
|
|
190
|
-
|
|
218
|
+
<div class="ai-system-row" aria-label={messages.blog.systemStatusAria}>
|
|
219
|
+
{aiModel && (
|
|
220
|
+
<span class="ai-system-chip">
|
|
221
|
+
{messages.blog.systemModelLabel}: {aiModel}
|
|
222
|
+
</span>
|
|
223
|
+
)}
|
|
224
|
+
{aiMode && (
|
|
225
|
+
<span class="ai-system-chip">
|
|
226
|
+
{messages.blog.systemModeLabel}: {aiMode}
|
|
227
|
+
</span>
|
|
228
|
+
)}
|
|
229
|
+
{aiState && (
|
|
230
|
+
<span class="ai-system-chip">
|
|
231
|
+
{messages.blog.systemStateLabel}: {aiState}
|
|
232
|
+
</span>
|
|
233
|
+
)}
|
|
191
234
|
</div>
|
|
192
235
|
)
|
|
193
236
|
}
|
|
194
237
|
{
|
|
195
238
|
contextText && (
|
|
196
239
|
<div class="ai-prompt-line">
|
|
197
|
-
|
|
240
|
+
{messages.blog.promptContextLabel}: <span class="ai-prompt-topic">{contextText}</span>{' '}
|
|
241
|
+
→
|
|
198
242
|
</div>
|
|
199
243
|
)
|
|
200
244
|
}
|
|
@@ -212,12 +256,12 @@ const hasCommentsConfig = Boolean(
|
|
|
212
256
|
<div class="ai-response-meta">
|
|
213
257
|
{aiLatencyMs !== undefined && (
|
|
214
258
|
<span>
|
|
215
|
-
|
|
259
|
+
{messages.blog.latencyLabel} <strong>{aiLatencyMs}</strong> ms
|
|
216
260
|
</span>
|
|
217
261
|
)}
|
|
218
262
|
{confidenceText !== undefined && (
|
|
219
263
|
<span>
|
|
220
|
-
|
|
264
|
+
{messages.blog.confidenceLabel} <strong>{confidenceText}</strong>
|
|
221
265
|
</span>
|
|
222
266
|
)}
|
|
223
267
|
</div>
|
|
@@ -234,9 +278,17 @@ const hasCommentsConfig = Boolean(
|
|
|
234
278
|
<div class="ai-stats-corner">
|
|
235
279
|
{aiModel && <span class="ai-model-id">{aiModel}</span>}
|
|
236
280
|
{aiModel && (wordCount !== undefined || tokenCount !== undefined) && ' · '}
|
|
237
|
-
{wordCount !== undefined &&
|
|
281
|
+
{wordCount !== undefined && (
|
|
282
|
+
<span>
|
|
283
|
+
{compact(wordCount)} {messages.blog.statsWords}
|
|
284
|
+
</span>
|
|
285
|
+
)}
|
|
238
286
|
{wordCount !== undefined && tokenCount !== undefined && ' · '}
|
|
239
|
-
{tokenCount !== undefined &&
|
|
287
|
+
{tokenCount !== undefined && (
|
|
288
|
+
<span>
|
|
289
|
+
{compact(tokenCount)} {messages.blog.statsTokens}
|
|
290
|
+
</span>
|
|
291
|
+
)}
|
|
240
292
|
</div>
|
|
241
293
|
)
|
|
242
294
|
}
|
|
@@ -247,7 +299,7 @@ const hasCommentsConfig = Boolean(
|
|
|
247
299
|
</article>
|
|
248
300
|
{
|
|
249
301
|
related.length > 0 && (
|
|
250
|
-
<section class="ai-related" aria-label=
|
|
302
|
+
<section class="ai-related" aria-label={messages.blog.relatedAria}>
|
|
251
303
|
<h2 class="ai-related-title">{messages.blog.related}</h2>
|
|
252
304
|
<div class="ai-related-grid">
|
|
253
305
|
{related.map((p) => (
|
|
@@ -274,7 +326,7 @@ const hasCommentsConfig = Boolean(
|
|
|
274
326
|
</section>
|
|
275
327
|
)
|
|
276
328
|
}
|
|
277
|
-
<nav class="ai-back-to-blog" aria-label=
|
|
329
|
+
<nav class="ai-back-to-blog" aria-label={messages.blog.backToBlogAria}>
|
|
278
330
|
<a href={localePath(resolvedLocale, '/blog/')}>
|
|
279
331
|
<span class="ai-back-prompt">$</span>
|
|
280
332
|
<span class="ai-back-text">← {messages.blog.backToBlog}</span>
|
|
@@ -290,6 +342,8 @@ const hasCommentsConfig = Boolean(
|
|
|
290
342
|
data-category={comments.CATEGORY}
|
|
291
343
|
data-category-id={comments.CATEGORY_ID}
|
|
292
344
|
data-mapping={comments.MAPPING}
|
|
345
|
+
data-term={comments.MAPPING === 'specific' ? normalizedCommentTerm : undefined}
|
|
346
|
+
data-number={comments.MAPPING === 'number' ? normalizedCommentNumber : undefined}
|
|
293
347
|
data-strict={comments.STRICT}
|
|
294
348
|
data-reactions-enabled={comments.REACTIONS_ENABLED}
|
|
295
349
|
data-emit-metadata={comments.EMIT_METADATA}
|
|
@@ -25,7 +25,7 @@ export function initAboutModals(runtimeConfig, prefersReducedMotion) {
|
|
|
25
25
|
},
|
|
26
26
|
ai: {
|
|
27
27
|
title: 'AI',
|
|
28
|
-
body: '<pre>~ $ ai --status --verbose\n\nmodel:
|
|
28
|
+
body: '<pre>~ $ ai --status --verbose\n\nmodel: runtime-default\nmode: standard\ncontext window: 32k\nlatency: 100-250ms\nsafety: enabled\n\n>> system online\n>> ready</pre>',
|
|
29
29
|
type: 'plain',
|
|
30
30
|
},
|
|
31
31
|
decryptor: {
|