@anglefeint/astro-theme 0.1.20 → 0.1.22

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,6 +1,6 @@
1
1
  {
2
2
  "name": "@anglefeint/astro-theme",
3
- "version": "0.1.20",
3
+ "version": "0.1.22",
4
4
  "type": "module",
5
5
  "description": "Anglefeint core theme package for Astro",
6
6
  "keywords": [
@@ -36,6 +36,10 @@ export type Messages = {
36
36
  backToBlog: string;
37
37
  related: string;
38
38
  regenerate: string;
39
+ toastP10: string;
40
+ toastP30: string;
41
+ toastP60: string;
42
+ toastDone: string;
39
43
  };
40
44
  };
41
45
 
@@ -71,6 +75,10 @@ export const DEFAULT_MESSAGES: Record<Locale, Messages> = {
71
75
  backToBlog: 'Back to blog',
72
76
  related: 'Related',
73
77
  regenerate: 'Regenerate',
78
+ toastP10: 'context parsed 10%',
79
+ toastP30: 'context parsed 30%',
80
+ toastP60: 'inference stable 60%',
81
+ toastDone: 'output finalized',
74
82
  },
75
83
  },
76
84
  ja: {
@@ -104,6 +112,10 @@ export const DEFAULT_MESSAGES: Record<Locale, Messages> = {
104
112
  backToBlog: 'ブログへ戻る',
105
113
  related: '関連記事',
106
114
  regenerate: '再生成',
115
+ toastP10: '文脈解析 10%',
116
+ toastP30: '文脈解析 30%',
117
+ toastP60: '推論安定 60%',
118
+ toastDone: '出力確定',
107
119
  },
108
120
  },
109
121
  ko: {
@@ -137,6 +149,10 @@ export const DEFAULT_MESSAGES: Record<Locale, Messages> = {
137
149
  backToBlog: '블로그로 돌아가기',
138
150
  related: '관련 글',
139
151
  regenerate: '재생성',
152
+ toastP10: '컨텍스트 파싱 10%',
153
+ toastP30: '컨텍스트 파싱 30%',
154
+ toastP60: '추론 안정화 60%',
155
+ toastDone: '출력 완료',
140
156
  },
141
157
  },
142
158
  es: {
@@ -170,6 +186,10 @@ export const DEFAULT_MESSAGES: Record<Locale, Messages> = {
170
186
  backToBlog: 'Volver al blog',
171
187
  related: 'Relacionados',
172
188
  regenerate: 'Regenerar',
189
+ toastP10: 'contexto analizado 10%',
190
+ toastP30: 'contexto analizado 30%',
191
+ toastP60: 'inferencia estable 60%',
192
+ toastDone: 'salida finalizada',
173
193
  },
174
194
  },
175
195
  zh: {
@@ -203,6 +223,10 @@ export const DEFAULT_MESSAGES: Record<Locale, Messages> = {
203
223
  backToBlog: '返回博客',
204
224
  related: '相关文章',
205
225
  regenerate: '重新生成',
226
+ toastP10: '语境解析 10%',
227
+ toastP30: '语境解析 30%',
228
+ toastP60: '推理稳定 60%',
229
+ toastDone: '输出完成',
206
230
  },
207
231
  },
208
232
  };
@@ -90,7 +90,15 @@ const enableRedQueen = THEME.EFFECTS.ENABLE_RED_QUEEN;
90
90
  )}
91
91
  <div class="ai-read-progress" aria-hidden="true"></div>
92
92
  <button type="button" class="ai-back-to-top" aria-label="Back to top" title="Back to top">↑</button>
93
- <div class="ai-stage-toast" aria-live="polite" aria-atomic="true"></div>
93
+ <div
94
+ class="ai-stage-toast"
95
+ aria-live="polite"
96
+ aria-atomic="true"
97
+ data-toast-p10={messages.blog.toastP10}
98
+ data-toast-p30={messages.blog.toastP30}
99
+ data-toast-p60={messages.blog.toastP60}
100
+ data-toast-done={messages.blog.toastDone}
101
+ ></div>
94
102
  <div class="ai-mouse-glow" aria-hidden="true"></div>
95
103
  <div class="ai-depth-blur" aria-hidden="true"></div>
96
104
  <div class="ai-thinking-dots" aria-hidden="true"><span></span><span></span><span></span></div>
@@ -15,6 +15,8 @@ export function initHeroCanvas(prefersReducedMotion) {
15
15
  var heroStart = 0;
16
16
  var heroRaf = 0;
17
17
  var resizeTimer = 0;
18
+ var isInViewport = true;
19
+ var heroObserver = null;
18
20
  var baseCanvas = document.createElement('canvas');
19
21
  var baseCtx = baseCanvas.getContext('2d');
20
22
  var pixelCanvas = document.createElement('canvas');
@@ -117,6 +119,10 @@ export function initHeroCanvas(prefersReducedMotion) {
117
119
  }
118
120
 
119
121
  function heroRender(t) {
122
+ if (prefersReducedMotion || document.hidden || !isInViewport) {
123
+ heroRaf = 0;
124
+ return;
125
+ }
120
126
  if (!heroStart) heroStart = t;
121
127
  var elapsed = (t - heroStart) * 0.001;
122
128
  frameCount++;
@@ -237,9 +243,24 @@ export function initHeroCanvas(prefersReducedMotion) {
237
243
  }
238
244
  }
239
245
 
246
+ if (!prefersReducedMotion && !document.hidden && isInViewport) {
247
+ heroRaf = requestAnimationFrame(heroRender);
248
+ } else {
249
+ heroRaf = 0;
250
+ }
251
+ }
252
+
253
+ function startHeroLoop() {
254
+ if (prefersReducedMotion || document.hidden || !isInViewport || !canvas.img || heroRaf) return;
240
255
  heroRaf = requestAnimationFrame(heroRender);
241
256
  }
242
257
 
258
+ function stopHeroLoop() {
259
+ if (!heroRaf) return;
260
+ cancelAnimationFrame(heroRaf);
261
+ heroRaf = 0;
262
+ }
263
+
243
264
  var img = new Image();
244
265
  img.onload = function() {
245
266
  canvas.img = img;
@@ -250,7 +271,7 @@ export function initHeroCanvas(prefersReducedMotion) {
250
271
  ctx.drawImage(baseCanvas, 0, 0);
251
272
  return;
252
273
  }
253
- heroRaf = requestAnimationFrame(heroRender);
274
+ startHeroLoop();
254
275
  };
255
276
  img.src = new URL(src, window.location.href).href;
256
277
 
@@ -267,16 +288,27 @@ export function initHeroCanvas(prefersReducedMotion) {
267
288
  function onHeroVisibilityChange() {
268
289
  if (prefersReducedMotion) return;
269
290
  if (document.hidden) {
270
- if (heroRaf) cancelAnimationFrame(heroRaf);
271
- heroRaf = 0;
272
- } else if (canvas.img && !heroRaf) {
273
- heroRaf = requestAnimationFrame(heroRender);
291
+ stopHeroLoop();
292
+ } else {
293
+ startHeroLoop();
274
294
  }
275
295
  }
276
296
 
297
+ if (!prefersReducedMotion && typeof IntersectionObserver !== 'undefined') {
298
+ heroObserver = new IntersectionObserver(function(entries) {
299
+ var entry = entries[0];
300
+ if (!entry) return;
301
+ isInViewport = entry.isIntersecting;
302
+ if (isInViewport) startHeroLoop();
303
+ else stopHeroLoop();
304
+ }, { threshold: 0.02 });
305
+ heroObserver.observe(shell);
306
+ }
307
+
277
308
  document.addEventListener('visibilitychange', onHeroVisibilityChange);
278
309
  window.addEventListener('beforeunload', function() {
279
310
  if (resizeTimer) clearTimeout(resizeTimer);
280
- cancelAnimationFrame(heroRaf);
311
+ stopHeroLoop();
312
+ if (heroObserver) heroObserver.disconnect();
281
313
  }, { once: true });
282
314
  }
@@ -1,9 +1,22 @@
1
1
  export function initReadProgressAndBackToTop(prefersReducedMotion) {
2
2
  var progress = document.querySelector('.ai-read-progress');
3
3
  var toast = document.querySelector('.ai-stage-toast');
4
- var stageSeen = { p30: false, p60: false, p90: false };
4
+ var stageSeen = { p10: false, p30: false, p60: false, done: false };
5
5
  var toastTimer = 0;
6
6
  var hasScrolled = false;
7
+ var toastText = {
8
+ p10: 'context parsed 10%',
9
+ p30: 'context parsed 30%',
10
+ p60: 'inference stable 60%',
11
+ done: 'output finalized',
12
+ };
13
+
14
+ if (toast && toast.dataset) {
15
+ toastText.p10 = toast.dataset.toastP10 || toastText.p10;
16
+ toastText.p30 = toast.dataset.toastP30 || toastText.p30;
17
+ toastText.p60 = toast.dataset.toastP60 || toastText.p60;
18
+ toastText.done = toast.dataset.toastDone || toastText.done;
19
+ }
7
20
 
8
21
  function showStageToast(msg) {
9
22
  if (!toast) return;
@@ -25,17 +38,21 @@ export function initReadProgressAndBackToTop(prefersReducedMotion) {
25
38
  if (btn) btn.classList.toggle('visible', scrollTop > 400);
26
39
  if (!hasScrolled && scrollTop > 6) hasScrolled = true;
27
40
  if (!hasScrolled) return;
41
+ if (!stageSeen.p10 && p >= 0.1) {
42
+ stageSeen.p10 = true;
43
+ showStageToast(toastText.p10);
44
+ }
28
45
  if (!stageSeen.p30 && p >= 0.3) {
29
46
  stageSeen.p30 = true;
30
- showStageToast('context parsed');
47
+ showStageToast(toastText.p30);
31
48
  }
32
49
  if (!stageSeen.p60 && p >= 0.6) {
33
50
  stageSeen.p60 = true;
34
- showStageToast('inference stable');
51
+ showStageToast(toastText.p60);
35
52
  }
36
- if (!stageSeen.p90 && p >= 0.9) {
37
- stageSeen.p90 = true;
38
- showStageToast('output finalized');
53
+ if (!stageSeen.done && p >= 0.9) {
54
+ stageSeen.done = true;
55
+ showStageToast(toastText.done);
39
56
  }
40
57
  }
41
58
 
@@ -124,67 +124,40 @@ export function initRedQueenTv(prefersReducedMotion) {
124
124
  preloadRetryTimers.add(id);
125
125
  }
126
126
 
127
- function preloadGif(item, token, attempt, done) {
127
+ function preloadDecoderData(item, token, attempt, done) {
128
128
  if (token !== playToken || !isPlaying) return;
129
129
  var tryCount = typeof attempt === 'number' ? attempt : 0;
130
130
  var cachedData = mediaDataCache[item.url];
131
-
132
- if (cachedData instanceof ArrayBuffer) {
133
- if (typeof ImageDecoder === 'undefined') {
134
- done(true);
131
+ function retryOrFail() {
132
+ if (tryCount >= PRELOAD_RETRY_MAX) {
133
+ done(false);
135
134
  return;
136
135
  }
137
- var cachedDecoder = new ImageDecoder({ data: cachedData, type: item.type });
138
- cachedDecoder.tracks.ready.then(async function() {
139
- var result = await cachedDecoder.decode({ frameIndex: 0 });
136
+ var retryDelay = Math.min(1800, RETRY_BASE_MS * (tryCount + 1));
137
+ schedulePreloadRetry(function() {
138
+ preloadDecoderData(item, token, tryCount + 1, done);
139
+ }, retryDelay);
140
+ }
141
+
142
+ function verifyBuffer(buffer) {
143
+ mediaDataCache[item.url] = buffer;
144
+ var decoder = new ImageDecoder({ data: buffer, type: item.type });
145
+ decoder.tracks.ready.then(async function() {
146
+ var result = await decoder.decode({ frameIndex: 0 });
140
147
  if (result && result.image && result.image.close) result.image.close();
141
148
  done(true);
142
- }).catch(function() {
143
- if (tryCount >= PRELOAD_RETRY_MAX) {
144
- done(false);
145
- return;
146
- }
147
- var retryDelay = Math.min(1800, RETRY_BASE_MS * (tryCount + 1));
148
- schedulePreloadRetry(function() {
149
- preloadGif(item, token, tryCount + 1, done);
150
- }, retryDelay);
151
- });
149
+ }).catch(retryOrFail);
150
+ }
151
+
152
+ if (cachedData instanceof ArrayBuffer) {
153
+ verifyBuffer(cachedData);
152
154
  return;
153
155
  }
154
156
 
155
157
  fetch(resolveItemUrl(item))
156
158
  .then(function(response) { return response.arrayBuffer(); })
157
- .then(function(buffer) {
158
- mediaDataCache[item.url] = buffer;
159
- if (typeof ImageDecoder === 'undefined') {
160
- done(true);
161
- return;
162
- }
163
- var decoder = new ImageDecoder({ data: buffer, type: item.type });
164
- decoder.tracks.ready.then(async function() {
165
- var result = await decoder.decode({ frameIndex: 0 });
166
- if (result && result.image && result.image.close) result.image.close();
167
- done(true);
168
- }).catch(function() {
169
- if (tryCount >= PRELOAD_RETRY_MAX) {
170
- done(false);
171
- return;
172
- }
173
- var retryDelay = Math.min(1800, RETRY_BASE_MS * (tryCount + 1));
174
- schedulePreloadRetry(function() {
175
- preloadGif(item, token, tryCount + 1, done);
176
- }, retryDelay);
177
- });
178
- }).catch(function() {
179
- if (tryCount >= PRELOAD_RETRY_MAX) {
180
- done(false);
181
- return;
182
- }
183
- var retryDelay = Math.min(1800, RETRY_BASE_MS * (tryCount + 1));
184
- schedulePreloadRetry(function() {
185
- preloadGif(item, token, tryCount + 1, done);
186
- }, retryDelay);
187
- });
159
+ .then(verifyBuffer)
160
+ .catch(retryOrFail);
188
161
  }
189
162
 
190
163
  function preloadImage(item, token, attempt, done) {
@@ -235,8 +208,9 @@ export function initRedQueenTv(prefersReducedMotion) {
235
208
  if (left <= 0) finish(!failed);
236
209
  }
237
210
 
211
+ var hasImageDecoder = typeof ImageDecoder !== 'undefined';
238
212
  playlist.forEach(function(item) {
239
- if (item.type === 'image/gif') preloadGif(item, token, 0, markDone);
213
+ if (hasImageDecoder) preloadDecoderData(item, token, 0, markDone);
240
214
  else preloadImage(item, token, 0, markDone);
241
215
  });
242
216
  }
@@ -89,19 +89,31 @@ body.ai-page::-webkit-scrollbar-thumb {
89
89
  #13070f 100%
90
90
  );
91
91
  }
92
+ .ai-glow::after {
93
+ content: "";
94
+ position: absolute;
95
+ inset: 0;
96
+ pointer-events: none;
97
+ background:
98
+ radial-gradient(circle at 30% 28%, rgba(164, 220, 255, 0.1), rgba(164, 220, 255, 0) 46%),
99
+ radial-gradient(circle at 72% 64%, rgba(255, 112, 140, 0.08), rgba(255, 112, 140, 0) 52%);
100
+ opacity: 0.38;
101
+ transform: translateZ(0);
102
+ will-change: opacity;
103
+ }
92
104
  .ai-glow-shift {
93
105
  animation: ai-glow-shift 25s ease-in-out infinite;
94
106
  }
95
107
  @keyframes ai-glow-shift {
96
108
  0%,
97
109
  100% {
98
- filter: brightness(1) saturate(1);
110
+ opacity: 0.32;
99
111
  }
100
112
  33% {
101
- filter: brightness(1.02) saturate(1.1) hue-rotate(5deg);
113
+ opacity: 0.52;
102
114
  }
103
115
  66% {
104
- filter: brightness(0.99) saturate(1.05) hue-rotate(-3deg);
116
+ opacity: 0.4;
105
117
  }
106
118
  }
107
119
  /* 柔和光晕:中心径向青蓝光 */
@@ -154,9 +154,7 @@
154
154
  will-change: transform, opacity;
155
155
  transition:
156
156
  transform 320ms ease,
157
- opacity 220ms ease,
158
- box-shadow 320ms ease,
159
- background 320ms ease;
157
+ opacity 220ms ease;
160
158
  }
161
159
  .rq-tv::before {
162
160
  content: "";