@anglefeint/astro-theme 0.1.11 → 0.1.13

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/package.json CHANGED
@@ -1,8 +1,17 @@
1
1
  {
2
2
  "name": "@anglefeint/astro-theme",
3
- "version": "0.1.11",
3
+ "version": "0.1.13",
4
4
  "type": "module",
5
5
  "description": "Anglefeint core theme package for Astro",
6
+ "keywords": [
7
+ "astro",
8
+ "astro-theme",
9
+ "blog",
10
+ "portfolio",
11
+ "cyberpunk",
12
+ "hacker",
13
+ "matrix"
14
+ ],
6
15
  "license": "MIT",
7
16
  "files": [
8
17
  "src/index.ts",
@@ -216,12 +216,13 @@ body.ai-page::-webkit-scrollbar-thumb {
216
216
  opacity: 0.7;
217
217
  }
218
218
  }
219
- .ai-network {
219
+ .ai-network-canvas {
220
220
  position: absolute;
221
221
  inset: -8%;
222
222
  width: 116%;
223
223
  height: 116%;
224
224
  opacity: 1;
225
+ pointer-events: none;
225
226
  filter: brightness(1.28) saturate(1.08);
226
227
  animation: ai-drift 25s ease-in-out infinite;
227
228
  }
@@ -240,57 +241,6 @@ body.ai-page::-webkit-scrollbar-thumb {
240
241
  transform: translate(0.5%, -0.8%) scale(1.005);
241
242
  }
242
243
  }
243
- .ai-network .ai-dots circle {
244
- transform-origin: center;
245
- animation:
246
- ai-dot-float 8s ease-in-out infinite,
247
- ai-dot-breathe 5s ease-in-out infinite;
248
- }
249
- .ai-network .ai-dots circle:nth-child(odd) {
250
- animation-delay: -3s;
251
- }
252
- .ai-network .ai-dots circle:nth-child(3n) {
253
- animation-delay: -5s;
254
- }
255
- .ai-network .ai-dots circle:nth-child(5n) {
256
- animation-duration: 10s;
257
- }
258
- .ai-network .ai-dots circle:nth-child(4n) {
259
- animation-delay: -1s;
260
- }
261
- .ai-network .ai-lines line {
262
- stroke-dasharray: 6 12;
263
- animation: ai-line-pulse 4s linear infinite;
264
- }
265
- .ai-network .ai-lines line:nth-child(odd) {
266
- animation-delay: -2s;
267
- }
268
- @keyframes ai-line-pulse {
269
- to {
270
- stroke-dashoffset: -18;
271
- }
272
- }
273
- @keyframes ai-dot-float {
274
- 0%,
275
- 100% {
276
- transform: translate(0, 0);
277
- }
278
- 33% {
279
- transform: translate(1.5px, -2px);
280
- }
281
- 66% {
282
- transform: translate(-1px, 1.5px);
283
- }
284
- }
285
- @keyframes ai-dot-breathe {
286
- 0%,
287
- 100% {
288
- opacity: 1;
289
- }
290
- 50% {
291
- opacity: 0.75;
292
- }
293
- }
294
244
  body.ai-page .ai-content,
295
245
  body.ai-page footer {
296
246
  position: relative;
@@ -15,10 +15,11 @@ const { tagline = SITE_TAGLINE, scanlines = false } = Astro.props as Props;
15
15
 
16
16
  <footer>
17
17
  &copy; {today.getFullYear()} {SITE_TITLE}. {tagline}
18
- {SOCIAL_LINKS.length > 0 && (
19
- <div class="social-links">
20
- <SocialMenu links={SOCIAL_LINKS} />
21
- </div>
18
+ <div class="social-links">
19
+ <SocialMenu links={SOCIAL_LINKS} />
20
+ </div>
21
+ {import.meta.env.DEV && SOCIAL_LINKS.length === 0 && (
22
+ <p class="social-hint">Configure social links in <code>src/site.config.ts</code></p>
22
23
  )}
23
24
  {scanlines && <div class="ai-scanlines" aria-hidden="true"></div>}
24
25
  </footer>
@@ -36,6 +37,16 @@ const { tagline = SITE_TAGLINE, scanlines = false } = Astro.props as Props;
36
37
  gap: 1em;
37
38
  margin-top: 1em;
38
39
  }
40
+ .social-hint {
41
+ margin: 0.6rem 0 0;
42
+ font-size: 0.78rem;
43
+ color: var(--chrome-text-muted, rgb(var(--text-muted)));
44
+ opacity: 0.82;
45
+ }
46
+ .social-hint code {
47
+ font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
48
+ font-size: 0.75rem;
49
+ }
39
50
  .social-links a {
40
51
  text-decoration: none;
41
52
  color: var(--chrome-text-muted, rgb(var(--text-muted)));
@@ -151,53 +151,17 @@ const localeOptions = SUPPORTED_LOCALES.map((targetLocale) => ({
151
151
  gap: 0.4rem;
152
152
  position: relative;
153
153
  flex-wrap: nowrap;
154
- }
155
- .lang-switcher {
156
- display: inline-flex;
157
- align-items: center;
158
- gap: 0.4rem;
159
- padding: 0.2rem 0.4rem;
160
- margin-right: 0.1rem;
161
- border: 1px solid rgba(132, 214, 255, 0.2);
162
- border-radius: 999px;
163
- background: rgba(6, 16, 30, 0.35);
164
- flex-shrink: 0;
165
- }
166
- .lang-label {
167
- display: inline-block;
168
- font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
169
- font-size: 0.6rem;
170
- text-transform: uppercase;
171
- letter-spacing: 0.12em;
172
- color: rgba(190, 226, 248, 0.78);
173
- white-space: nowrap;
174
- }
175
- .lang-select {
176
- appearance: none;
177
- -webkit-appearance: none;
178
- -moz-appearance: none;
179
- background:
180
- linear-gradient(45deg, transparent 50%, rgba(204, 236, 252, 0.82) 50%) calc(100% - 0.9rem) 50% / 0.35rem 0.35rem no-repeat,
181
- linear-gradient(135deg, rgba(204, 236, 252, 0.82) 50%, transparent 50%) calc(100% - 0.7rem) 50% / 0.35rem 0.35rem no-repeat,
182
- rgba(9, 22, 40, 0.68);
183
- border: 1px solid rgba(132, 214, 255, 0.2);
184
- border-radius: 999px;
185
- padding: 0.2rem 1.45rem 0.2rem 0.55rem;
186
- min-width: 6rem;
187
- font-size: 0.62rem;
188
- line-height: 1;
189
- font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
190
- color: rgba(204, 236, 252, 0.86);
191
- cursor: pointer;
192
- }
193
- .lang-select:focus {
194
- outline: none;
195
- border-color: rgba(132, 214, 255, 0.5);
196
- box-shadow: 0 0 0 2px rgba(98, 180, 228, 0.18);
197
- }
198
- .lang-select option {
199
- color: #ccecfb;
200
- background: #0b1c32;
154
+ --lang-switcher-border: rgba(132, 214, 255, 0.2);
155
+ --lang-switcher-bg: rgba(6, 16, 30, 0.35);
156
+ --lang-label-color: rgba(190, 226, 248, 0.78);
157
+ --lang-select-arrow: rgba(204, 236, 252, 0.82);
158
+ --lang-select-bg: rgba(9, 22, 40, 0.68);
159
+ --lang-select-border: rgba(132, 214, 255, 0.2);
160
+ --lang-select-text: rgba(204, 236, 252, 0.86);
161
+ --lang-select-focus-border: rgba(132, 214, 255, 0.5);
162
+ --lang-select-focus-ring: rgba(98, 180, 228, 0.18);
163
+ --lang-select-option-text: #ccecfb;
164
+ --lang-select-option-bg: #0b1c32;
201
165
  }
202
166
  .nav-status {
203
167
  display: none;
@@ -33,3 +33,60 @@ const { label, currentLocale, options } = Astro.props as Props;
33
33
  }
34
34
  </select>
35
35
  </div>
36
+
37
+ <style>
38
+ .lang-switcher {
39
+ display: inline-flex;
40
+ align-items: center;
41
+ gap: 0.4rem;
42
+ padding: 0.2rem 0.4rem;
43
+ margin-right: 0.1rem;
44
+ border: 1px solid var(--lang-switcher-border, rgba(132, 214, 255, 0.2));
45
+ border-radius: 999px;
46
+ background: var(--lang-switcher-bg, rgba(6, 16, 30, 0.35));
47
+ flex-shrink: 0;
48
+ }
49
+ .lang-label {
50
+ display: inline-block;
51
+ font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
52
+ font-size: 0.6rem;
53
+ text-transform: uppercase;
54
+ letter-spacing: 0.12em;
55
+ color: var(--lang-label-color, rgba(190, 226, 248, 0.78));
56
+ white-space: nowrap;
57
+ }
58
+ .lang-select {
59
+ appearance: none;
60
+ -webkit-appearance: none;
61
+ -moz-appearance: none;
62
+ background:
63
+ linear-gradient(45deg, transparent 50%, var(--lang-select-arrow, rgba(204, 236, 252, 0.82)) 50%) calc(100% - 0.9rem) 50% /
64
+ 0.35rem 0.35rem no-repeat,
65
+ linear-gradient(135deg, var(--lang-select-arrow, rgba(204, 236, 252, 0.82)) 50%, transparent 50%) calc(100% - 0.7rem) 50% /
66
+ 0.35rem 0.35rem no-repeat,
67
+ var(--lang-select-bg, rgba(9, 22, 40, 0.68));
68
+ border: 1px solid var(--lang-select-border, rgba(132, 214, 255, 0.2));
69
+ border-radius: 999px;
70
+ padding: 0.2rem 1.45rem 0.2rem 0.55rem;
71
+ min-width: 6rem;
72
+ font-size: 0.62rem;
73
+ line-height: 1;
74
+ font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
75
+ color: var(--lang-select-text, rgba(204, 236, 252, 0.86));
76
+ cursor: pointer;
77
+ }
78
+ .lang-select:focus {
79
+ outline: none;
80
+ border-color: var(--lang-select-focus-border, rgba(132, 214, 255, 0.5));
81
+ box-shadow: 0 0 0 2px var(--lang-select-focus-ring, rgba(98, 180, 228, 0.18));
82
+ }
83
+ .lang-select option {
84
+ color: var(--lang-select-option-text, #ccecfb);
85
+ background: var(--lang-select-option-bg, #0b1c32);
86
+ }
87
+ @media (max-width: 720px) {
88
+ .lang-switcher {
89
+ margin-right: 0;
90
+ }
91
+ }
92
+ </style>
@@ -5,16 +5,44 @@ import Icon from './Icon.astro';
5
5
  interface Props {
6
6
  links?: SocialLink[];
7
7
  iconSize?: number;
8
+ showPlaceholdersWhenEmpty?: boolean;
8
9
  }
9
10
 
10
- const { links = SOCIAL_LINKS, iconSize = 32 } = Astro.props as Props;
11
+ const {
12
+ links = SOCIAL_LINKS,
13
+ iconSize = 32,
14
+ showPlaceholdersWhenEmpty = true,
15
+ } = Astro.props as Props;
16
+ const placeholderLinks: SocialLink[] = [
17
+ { href: '', label: 'Mastodon', icon: 'mastodon' },
18
+ { href: '', label: 'Twitter', icon: 'twitter' },
19
+ { href: '', label: 'GitHub', icon: 'github' },
20
+ ];
21
+ const resolvedLinks = links.length > 0 || !showPlaceholdersWhenEmpty ? links : placeholderLinks;
11
22
  ---
12
23
 
13
24
  {
14
- links.map((link) => (
15
- <a href={link.href} target="_blank" rel="noopener noreferrer">
16
- <span class="sr-only">{link.label}</span>
17
- {link.icon ? <Icon name={link.icon} size={iconSize} /> : <span>{link.label}</span>}
18
- </a>
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" title="Configure social links in src/site.config.ts">
33
+ {link.icon ? <Icon name={link.icon} size={iconSize} /> : <span>{link.label}</span>}
34
+ </span>
35
+ )
19
36
  ))
20
37
  }
38
+
39
+ <style>
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
+ </style>
@@ -48,29 +48,6 @@ const hasSystemMeta = Boolean(aiModel || aiMode || aiState);
48
48
  const hasResponseMeta = aiLatencyMs !== undefined || aiConfidence !== undefined;
49
49
  const hasStats = aiModel || wordCount !== undefined || tokenCount !== undefined;
50
50
  const confidenceText = aiConfidence !== undefined ? aiConfidence.toFixed(2) : undefined;
51
-
52
- // 点云:全屏分散但不过于稀疏
53
- const w = 1200; const h = 800;
54
- const pts: { x: number; y: number; r: number; depth: number }[] = [];
55
- const pad = 50;
56
- for (let i = 0; i < 58; i++) {
57
- const x = pad + Math.random() * (w - pad * 2);
58
- const y = pad + Math.random() * (h - pad * 2);
59
- const depth = Math.random();
60
- pts.push({
61
- x, y,
62
- r: 1.1 + depth * 1.3,
63
- depth,
64
- });
65
- }
66
- const connectDist = 138;
67
- const edges: [number, number][] = [];
68
- for (let i = 0; i < pts.length; i++) {
69
- for (let j = i + 1; j < pts.length; j++) {
70
- const d = Math.hypot(pts[i].x - pts[j].x, pts[i].y - pts[j].y);
71
- if (d < connectDist) edges.push([i, j]);
72
- }
73
- }
74
51
  ---
75
52
 
76
53
  <AiShell
@@ -99,45 +76,7 @@ for (let i = 0; i < pts.length; i++) {
99
76
  <span class="ai-particle" style={`--x: ${20 + (i * 7) % 60}%; --y: ${10 + (i * 11) % 70}%; --d: ${3 + (i % 4)}s`}></span>
100
77
  ))}
101
78
  </div>
102
- <svg class="ai-network" viewBox="0 0 1200 800" preserveAspectRatio="xMidYMid slice">
103
- <defs>
104
- <linearGradient id="ai-line-grad" x1="0%" y1="0%" x2="100%" y2="0%">
105
- <stop offset="0%" stop-color="rgba(180,235,255,0.26)" />
106
- <stop offset="50%" stop-color="rgba(236,252,255,0.74)" />
107
- <stop offset="100%" stop-color="rgba(180,235,255,0.26)" />
108
- </linearGradient>
109
- <filter id="ai-dot-glow">
110
- <feGaussianBlur stdDeviation="2.2" result="blur" />
111
- <feMerge>
112
- <feMergeNode in="blur" />
113
- <feMergeNode in="SourceGraphic" />
114
- </feMerge>
115
- </filter>
116
- </defs>
117
- <g class="ai-lines">
118
- {edges.map(([i, j]) => (
119
- <line
120
- x1={pts[i].x}
121
- y1={pts[i].y}
122
- x2={pts[j].x}
123
- y2={pts[j].y}
124
- stroke="url(#ai-line-grad)"
125
- stroke-width="0.4"
126
- />
127
- ))}
128
- </g>
129
- <g class="ai-dots">
130
- {pts.map((p) => (
131
- <circle
132
- cx={p.x}
133
- cy={p.y}
134
- r={p.r}
135
- fill={`rgba(232,255,255,${0.72 + p.depth * 0.48})`}
136
- filter="url(#ai-dot-glow)"
137
- />
138
- ))}
139
- </g>
140
- </svg>
79
+ <canvas class="ai-network-canvas" aria-hidden="true"></canvas>
141
80
  </div>
142
81
  <aside class="rq-tv rq-tv-collapsed">
143
82
  <div class="rq-tv-stage" data-rq-src={themeRedqueen1.src} data-rq-src2={themeRedqueen2.src}></div>
@@ -53,6 +53,118 @@ export function initBlogpostEffects() {
53
53
  window.scrollTo({ top: 0, behavior: prefersReducedMotion ? 'auto' : 'smooth' });
54
54
  });
55
55
  }
56
+ function initNetworkCanvas() {
57
+ var canvas = document.querySelector('.ai-network-canvas');
58
+ if (!canvas) return;
59
+ var ctx = canvas.getContext('2d');
60
+ if (!ctx) return;
61
+ var MAX_DPR = 2;
62
+ var rafId = 0;
63
+ var start = 0;
64
+ var last = 0;
65
+ var fps = prefersReducedMotion ? 1 : 30;
66
+ var frameMs = 1000 / fps;
67
+ var points = [];
68
+ var edges = [];
69
+
70
+ function seededRandom(seed) {
71
+ var s = seed >>> 0;
72
+ return function() {
73
+ s = (1664525 * s + 1013904223) >>> 0;
74
+ return s / 4294967296;
75
+ };
76
+ }
77
+
78
+ function resize() {
79
+ var rect = canvas.getBoundingClientRect();
80
+ var dpr = Math.min(MAX_DPR, window.devicePixelRatio || 1);
81
+ canvas.width = Math.max(2, Math.round(rect.width * dpr));
82
+ canvas.height = Math.max(2, Math.round(rect.height * dpr));
83
+ ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
84
+
85
+ var w = rect.width;
86
+ var h = rect.height;
87
+ var rand = seededRandom(0xA13F09);
88
+ var count = Math.max(20, Math.min(36, Math.round((w * h) / 32000)));
89
+ var connectDist = Math.min(160, Math.max(90, Math.min(w, h) * 0.18));
90
+ var maxEdges = 120;
91
+ points = [];
92
+ for (var i = 0; i < count; i++) {
93
+ points.push({
94
+ x: 20 + rand() * Math.max(20, w - 40),
95
+ y: 20 + rand() * Math.max(20, h - 40),
96
+ r: 1 + rand() * 1.6,
97
+ p: rand() * Math.PI * 2,
98
+ a: 0.5 + rand() * 0.5,
99
+ });
100
+ }
101
+ edges = [];
102
+ for (var a = 0; a < points.length; a++) {
103
+ for (var b = a + 1; b < points.length; b++) {
104
+ if (edges.length >= maxEdges) break;
105
+ var dx = points[a].x - points[b].x;
106
+ var dy = points[a].y - points[b].y;
107
+ var d = Math.sqrt(dx * dx + dy * dy);
108
+ if (d < connectDist) edges.push([a, b, d / connectDist]);
109
+ }
110
+ if (edges.length >= maxEdges) break;
111
+ }
112
+ }
113
+
114
+ function render(ts) {
115
+ if (!start) start = ts;
116
+ if (!prefersReducedMotion && ts - last < frameMs) {
117
+ rafId = requestAnimationFrame(render);
118
+ return;
119
+ }
120
+ last = ts;
121
+ var t = (ts - start) * 0.001;
122
+ var w = canvas.clientWidth;
123
+ var h = canvas.clientHeight;
124
+ ctx.clearRect(0, 0, w, h);
125
+
126
+ for (var i = 0; i < edges.length; i++) {
127
+ var e = edges[i];
128
+ var p1 = points[e[0]];
129
+ var p2 = points[e[1]];
130
+ var alpha = (1 - e[2]) * (prefersReducedMotion ? 0.2 : (0.18 + 0.06 * Math.sin(t * 0.9 + i)));
131
+ ctx.strokeStyle = 'rgba(190, 236, 255,' + Math.max(0.06, alpha).toFixed(3) + ')';
132
+ ctx.lineWidth = 0.6;
133
+ ctx.beginPath();
134
+ ctx.moveTo(p1.x, p1.y);
135
+ ctx.lineTo(p2.x, p2.y);
136
+ ctx.stroke();
137
+ }
138
+
139
+ for (var j = 0; j < points.length; j++) {
140
+ var p = points[j];
141
+ var pulse = prefersReducedMotion ? 1 : (1 + 0.18 * Math.sin(t * 1.5 + p.p));
142
+ ctx.fillStyle = 'rgba(228, 251, 255,' + (0.58 * p.a).toFixed(3) + ')';
143
+ ctx.beginPath();
144
+ ctx.arc(p.x, p.y, p.r * pulse, 0, Math.PI * 2);
145
+ ctx.fill();
146
+ }
147
+
148
+ if (!prefersReducedMotion) rafId = requestAnimationFrame(render);
149
+ }
150
+
151
+ function stop() {
152
+ if (!rafId) return;
153
+ cancelAnimationFrame(rafId);
154
+ rafId = 0;
155
+ }
156
+
157
+ resize();
158
+ render(performance.now());
159
+ window.addEventListener('resize', resize, { passive: true });
160
+ document.addEventListener('visibilitychange', function() {
161
+ if (prefersReducedMotion) return;
162
+ if (document.hidden) stop();
163
+ else if (!rafId) rafId = requestAnimationFrame(render);
164
+ });
165
+ window.addEventListener('beforeunload', stop, { once: true });
166
+ }
167
+ initNetworkCanvas();
56
168
  function initHeroCanvas() {
57
169
  var shell = document.querySelector('.hero-shell');
58
170
  if (!shell) return;