PageSourceSearch

https://kemeow0815.github.io/

html kemeow0815.github.io collected 2026-10-03 09:27:09 UTC 55,379 bytes, 1,478 lines download raw bytes

1<!DOCTYPE html>
2<html lang="zh-CN">
3<head>
4  <meta charset="UTF-8">
5<meta name="viewport" content="width=device-width, initial-scale=1.0">
6<meta http-equiv="X-UA-Compatible" content="ie=edge">
7<link rel="stylesheet" href="/styles/main.min.css">
8
9
10<link rel="icon" href="/favicon.ico" type="image/x-icon">
11
12
13<script>
14(function() {
15  // Check for saved theme preference
16  var savedTheme = localStorage.getItem('theme');
17  
18  if (savedTheme) {
19    // Use saved preference
20    document.documentElement.setAttribute('data-theme', savedTheme);
21  } else {
22    // Check system preference
23    var prefersDark = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
24    var theme = prefersDark ? 'dark' : 'light';
25    document.documentElement.setAttribute('data-theme', theme);
26  }
27})();
28</script>
28
29
30
31<meta name="description" content="每一段旅行,都有终点。">
32<meta property="og:site_name" content="喵洛阁">
33<meta property="og:title" content="喵洛阁">
34<meta property="og:description" content="每一段旅行,都有终点。">
35
36<meta property="og:image" content="/images/avatar.png">
37
38<meta property="og:url" content="https://kemeow0815.github.io">
39<meta property="og:type" content="website">
40<meta name="twitter:card" content="summary_large_image">
41<meta name="twitter:title" content="喵洛阁">
42<meta name="twitter:description" content="每一段旅行,都有终点。">
43
44<meta name="twitter:image" content="/images/avatar.png">
45
46
47  <meta name="generator" content="Gridea Pro 1.1.1">
48  <title>喵洛阁</title>
49  
50<meta name="description" content="每一段旅行,都有终点。">
51<link rel="canonical" href="https://kemeow0815.github.io/">
52<script type="application/ld+json">{"@context":"https://schema.org","@type":"WebSite","name":"喵洛阁","url":"https://kemeow0815.github.io","description":"每一段旅行,都有终点。","potentialAction":{"@type":"SearchAction","query-input":"required name=search_term_string","target":"https://kemeow0815.github.io/search?q={search_term_string}"}}</script>
52
53<meta property="og:title" content="喵洛阁">
54<meta property="og:description" content="每一段旅行,都有终点。">
55<meta property="og:url" content="https://kemeow0815.github.io/">
56<meta property="og:type" content="website">
57<meta property="og:site_name" content="喵洛阁">
58<meta property="og:image" content="/images/avatar.png">
59<meta name="twitter:card" content="summary_large_image">
60<meta name="twitter:url" content="https://kemeow0815.github.io/">
61<meta name="twitter:title" content="喵洛阁">
62<meta name="twitter:description" content="每一段旅行,都有终点。">
63<meta name="twitter:image" content="/images/avatar.png">
64</head>
65<body class="flavor-theme">
66
67  <!-- 加载页面 -->
68  
69  <div id="loading-overlay" class="loading-overlay">
70    <div class="loading-content">
71      
72      <div class="loading-spinner"></div>
73      
74      <div class="loading-title">加载中...</div>
75    </div>
76  </div>
77  
78
79  <!-- 流星背景 -->
80  <div class="bg-container">
81    <div class="bg-stars" id="bg-stars"></div>
82  </div>
83
84  <header class="site-header" id="siteHeader">
85  <div class="header-inner">
86    <div class="top-bar">
87
88      
89      <div class="top-bar__left">
90        
91        <a href="https://kemeow0815.github.io" class="site-avatar-link" aria-label="返回首页">
92          <img class="site-avatar" src="https://jsd.268682.xyz/gh/Kemeow0815/img@main/img/kemeow0815.webp" alt="喵洛阁" />
93        </a>
94        
95        <a href="https://kemeow0815.github.io" class="site-name-link">喵洛阁</a>
96      </div>
97
98      
99      <nav class="top-bar__nav" id="sectionBar">
100        
101        
102          
103          
104          <a class="nav-item" href="https://kemeow0815.github.io/">🏠首页</a>
105          
106        
107          
108          
109          <div class="nav-item-wrapper">
110            <div class="nav-item nav-item--has-children" data-menu-id="menu-2">
111              <a class="nav-item__link" href="javascript:void(0);">📖文章</a>
112              <svg class="nav-item__arrow" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
113                <polyline points="6 9 12 15 18 9"></polyline>
114              </svg>
115            </div>
116            <div class="nav-submenu" id="submenu-menu-2">
117              
118              <a class="nav-submenu__item" href="/post">📚全部文章</a>
119              
120              <a class="nav-submenu__item" href="/archives">📦归档</a>
121              
122              <a class="nav-submenu__item" href="/tags">🔖标签</a>
123              
124            </div>
125          </div>
126          
127        
128          
129          
130          <a class="nav-item" href="/memos">📝闪念</a>
131          
132        
133          
134          
135          <div class="nav-item-wrapper">
136            <div class="nav-item nav-item--has-children" data-menu-id="menu-4">
137              <a class="nav-item__link" href="javascript:void(0);">🔗链接</a>
138              <svg class="nav-item__arrow" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
139                <polyline points="6 9 12 15 18 9"></polyline>
140              </svg>
141            </div>
142            <div class="nav-submenu" id="submenu-menu-4">
143              
144              <a class="nav-submenu__item" href="/links">🧑‍🤝‍🧑友链</a>
145              
146              <a class="nav-submenu__item" href="/post/fcircle">💬朋友圈</a>
147              
148            </div>
149          </div>
150          
151        
152          
153          
154          <div class="nav-item-wrapper">
155            <div class="nav-item nav-item--has-children" data-menu-id="menu-5">
156              <a class="nav-item__link" href="javascript:void(0);">📔页面</a>
157              <svg class="nav-item__arrow" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
158                <polyline points="6 9 12 15 18 9"></polyline>
159              </svg>
160            </div>
161            <div class="nav-submenu" id="submenu-menu-5">
162              
163              <a class="nav-submenu__item" href="/post/sponsor">💗赞助</a>
164              
165              <a class="nav-submenu__item" href="/post/timetable">🗓️课程表</a>
166              
167            </div>
168          </div>
169          
170        
171          
172          
173          <a class="nav-item" href="/post/about">🫠关于</a>
174          
175        
176        
177      </nav>
178
179      
180      <div class="top-bar__right">
181        <button class="btn-icon btn-theme" id="btnTheme" type="button" aria-label="切换主题">
182          
183          <svg class="theme-icon-moon" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
184            <path d="M20.742 13.045a8 8 0 0 1-2.077.271c-2.135 0-4.14-.83-5.646-2.336a8.03 8.03 0 0 1-2.064-7.723A1 1 0 0 0 9.73 2.034a10 10 0 0 0-4.489 2.582c-3.898 3.898-3.898 10.243 0 14.143a9.94 9.94 0 0 0 7.072 2.93a9.93 9.93 0 0 0 7.07-2.929a10 10 0 0 0 2.583-4.491a1 1 0 0 0-1.224-1.224m-2.772 4.301a7.95 7.95 0 0 1-5.656 2.343a7.95 7.95 0 0 1-5.658-2.344c-3.118-3.119-3.118-8.195 0-11.314a8 8 0 0 1 2.06-1.483a10.03 10.03 0 0 0 2.89 7.848a9.97 9.97 0 0 0 7.848 2.891a8 8 0 0 1-1.484 2.059"/>
185          </svg>
186          
187          <svg class="theme-icon-sun" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
188            <circle cx="12" cy="12" r="4"/>
189            <path d="M12 2v2"/>
190            <path d="M12 20v2"/>
191            <path d="m4.93 4.93 1.41 1.41"/>
192            <path d="m17.66 17.66 1.41 1.41"/>
193            <path d="M2 12h2"/>
194            <path d="M20 12h2"/>
195            <path d="m6.34 17.66-1.41 1.41"/>
196            <path d="m19.07 4.93-1.41 1.41"/>
197          </svg>
198        </button>
199        <button class="btn-icon btn-search search-btn" id="btnSearch" type="button" aria-label="搜索">
200          <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
201            stroke-linecap="round" stroke-linejoin="round">
202            <circle cx="11" cy="11" r="8"></circle>
203            <line x1="21" y1="21" x2="16.65" y2="16.65"></line>
204          </svg>
205        </button>
206        <button class="btn-icon btn-nav-toggle mobile-only" id="btnNavToggle" type="button" aria-label="切换导航">
207          <div class="hamburger" id="hamburger">
208            <span class="hamburger__line hamburger__line--top"></span>
209            <span class="hamburger__line hamburger__line--bottom"></span>
210          </div>
211        </button>
212      </div>
213
214    </div>
215  </div>
216</header>
217
218
219<div class="header-spacer"></div>
220
221<script>
222(function(){
223  // Mobile nav toggle
224  var btn = document.getElementById('btnNavToggle');
225  var nav = document.getElementById('sectionBar');
226  var hamburger = document.getElementById('hamburger');
227  if (!btn || !nav) return;
228  btn.addEventListener('click', function(){
229    nav.classList.toggle('top-bar__nav--open');
230    if (hamburger) hamburger.classList.toggle('is-active');
231  });
232
233  // Submenu toggle for mobile
234  var navItemsWithChildren = document.querySelectorAll('.nav-item--has-children');
235  navItemsWithChildren.forEach(function(item) {
236    item.addEventListener('click', function(e) {
237      // Only handle click on mobile
238      if (window.innerWidth > 768) return;
239      
240      e.preventDefault();
241      e.stopPropagation();
242      
243      var menuId = this.getAttribute('data-menu-id');
244      var submenu = document.getElementById('submenu-' + menuId);
245      
246      // Close other open submenus
247      navItemsWithChildren.forEach(function(otherItem) {
248        if (otherItem !== item) {
249          var otherMenuId = otherItem.getAttribute('data-menu-id');
250          var otherSubmenu = document.getElementById('submenu-' + otherMenuId);
251          if (otherSubmenu && otherSubmenu.classList.contains('nav-submenu--open')) {
252            otherSubmenu.classList.remove('nav-submenu--open');
253            otherItem.classList.remove('nav-item--expanded');
254          }
255        }
256      });
257      
258      // Toggle current submenu
259      if (submenu) {
260        submenu.classList.toggle('nav-submenu--open');
261        this.classList.toggle('nav-item--expanded');
262      }
263    });
264  });
265
266  // Close mobile nav when clicking on simple nav items
267  nav.querySelectorAll('.nav-item:not(.nav-item--has-children)').forEach(function(item){
268    item.addEventListener('click', function(){
269      nav.classList.remove('top-bar__nav--open');
270      if (hamburger) hamburger.classList.remove('is-active');
271    });
272  });
273
274  // Theme toggle with performance optimization
275  var themeBtn = document.getElementById('btnTheme');
276  if (themeBtn) {
277    themeBtn.addEventListener('click', function() {
278      var currentTheme = document.documentElement.getAttribute('data-theme') || 'dark';
279      var newTheme = currentTheme === 'dark' ? 'light' : 'dark';
280      
281      // Add class to disable transitions during theme change
282      document.body.classList.add('theme-changing');
283      
284      // Use requestAnimationFrame for smooth theme switch
285      requestAnimationFrame(function() {
286        document.documentElement.setAttribute('data-theme', newTheme);
287        localStorage.setItem('theme', newTheme);
288        
289        // Remove the class after theme change is complete
290        requestAnimationFrame(function() {
291          document.body.classList.remove('theme-changing');
292        });
293      });
294    });
295  }
296})();
297</script>
297
298
299
300  <main class="site-main" role="main">
301    
302
303
304
305<section class="hero-section" style="--hero-wave-color: #ffffff">
306  <img class="hero-image" src="https://jsd.268682.xyz/gh/Kemeow0815/img@main/img/bg.webp" alt="" />
307  <div class="hero-overlay"></div>
308  <div class="hero-content">
309    
310    <h2 class="hero-title clamp-3">每一段旅行,都有终点。</h2>
311    
312    
313    <p class="hero-subtitle">我在这个博客分享我的生活日常、踩坑记录和资源分享。</p>
314    
315    
316  </div>
317  
318  <div class="hero-wave">
319    <svg class="wave wave-1" viewBox="0 0 1200 120" preserveAspectRatio="none">
320      <path d="M0,60 C200,90 400,30 600,60 C800,90 1000,30 1200,60 L1200,120 L0,120 Z" class="wave-path wave-path-1"/>
321    </svg>
322    <svg class="wave wave-2" viewBox="0 0 1200 120" preserveAspectRatio="none">
323      <path d="M0,40 C150,70 350,10 600,40 C850,70 1050,10 1200,40 L1200,120 L0,120 Z" class="wave-path wave-path-2"/>
324    </svg>
325    <svg class="wave wave-3" viewBox="0 0 1200 120" preserveAspectRatio="none">
326      <path d="M0,20 C300,50 500,0 600,20 C700,40 900,0 1200,20 L1200,120 L0,120 Z" class="wave-path wave-path-3"/>
327    </svg>
328  </div>
329  
330</section>
331
332
333<div class="container">
334  
335  
336
337<div class="bb-time-list" id="bbTimeList">
338  <i class="bb-time-list__icon bb-time-list__icon--newspaper" onclick="window.location.href='https://memos.268682.xyz'">
339    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
340      <path d="M19 20H5a2 2 0 01-2-2V6a2 2 0 012-2h10a2 2 0 012 2v1m2 13a2 2 0 01-2-2V7m2 13a2 2 0 002-2V9a2 2 0 00-2-2h-2m-4-3H9M7 16h6M7 8h6v4H7V8z"/>
341    </svg>
342  </i>
343  <div class="bb-time-list__container" id="bbTalk" onclick="window.location.href='https://memos.268682.xyz'">
344    <div class="bb-time-list__wrapper" id="bberTalk">
345      <div class="bb-time-list__item">加载中...</div>
346    </div>
347  </div>
348  <i class="bb-time-list__icon bb-time-list__icon--arrow" title="查看更多" onclick="window.location.href='https://memos.268682.xyz'">
349    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
350      <path d="M9 5l7 7-7 7"/>
351    </svg>
352  </i>
353</div>
354
355<script>
356// BBTime Config Debug - Raw values from theme_config
357console.log('[BBTime Debug] Raw theme_config.bbTimeDataSource:', "memos");
358console.log('[BBTime Debug] Raw theme_config.bbTimeMemosUrl:', "https://m.314926.xyz");
359
360(function() {
361  // 配置 - 使用更明确的默认值处理
362  var rawDataSource = "memos";
363  var rawMemosUrl = "https://m.314926.xyz";
364  var rawGithubUrl = "https://jsd.268682.xyz/gh/Kemeow0815/ghapi-json-generator@output/v2/repos/Kemeow0815/blog-timeline/issues%3F%3Fstate%3Dall%26labels%3D%E6%97%A5%E5%B8%B8%26per_page%3D20/data.json";
365  var rawMemosToken = "";
366  var rawMemosFilter = "";
367  var rawCustomUrl = "";
368  var rawInterval = "3";
369  var rawMaxItems = "10";
370  
371  // 处理空值(Pongo2 可能输出空字符串而不是使用默认值)
372  var CONFIG = {
373    dataSource: rawDataSource || 'github',
374    githubUrl: rawGithubUrl || 'https://jsd.268682.xyz/gh/Kemeow0815/ghapi-json-generator@output/v2/repos/Kemeow0815/blog-timeline/issues%3F%3Fstate%3Dall%26labels%3D%E6%97%A5%E5%B8%B8%26per_page%3D20/data.json',
375    memosUrl: rawMemosUrl || '',
376    memosToken: rawMemosToken || '',
377    memosFilter: rawMemosFilter || '',
378    customUrl: rawCustomUrl || '',
379    interval: (parseInt(rawInterval) || 3) * 1000,
380    maxItems: parseInt(rawMaxItems) || 10
381  };
382  
383  // 调试信息
384  console.log('[BBTime] Final CONFIG:', CONFIG);
385  console.log('[BBTime] dataSource:', CONFIG.dataSource);
386  console.log('[BBTime] memosUrl:', CONFIG.memosUrl);
387  
388  var currentIndex = 0;
389  var items = [];
390  var timer = null;
391  
392  // 获取 API URL
393  function getApiUrl() {
394    switch (CONFIG.dataSource) {
395      case 'memos':
396        if (!CONFIG.memosUrl) {
397          console.error('Memos URL is not configured');
398          return null;
399        }
400        var url = CONFIG.memosUrl.replace(/\/$/, '') + '/api/v1/memos?pageSize=' + CONFIG.maxItems;
401        if (CONFIG.memosFilter) {
402          url += '&filter=' + encodeURIComponent(CONFIG.memosFilter);
403        }
404        return url;
405      case 'custom':
406        return CONFIG.customUrl || null;
407      case 'github':
408      default:
409        return CONFIG.githubUrl;
410    }
411  }
412  
413  // 获取请求头
414  function getHeaders() {
415    var headers = {};
416    if (CONFIG.dataSource === 'memos' && CONFIG.memosToken) {
417      headers['Authorization'] = 'Bearer ' + CONFIG.memosToken;
418    }
419    return headers;
420  }
421  
422  // 获取数据
423  async function fetchData() {
424    var apiUrl = getApiUrl();
425    if (!apiUrl) {
426      console.error('No API URL configured');
427      return [];
428    }
429    
430    try {
431      var response = await fetch(apiUrl, { headers: getHeaders() });
432      if (!response.ok) throw new Error('Network response was not ok');
433      var data = await response.json();
434      return data;
435    } catch (error) {
436      console.error('Failed to fetch bbTime data:', error);
437      return [];
438    }
439  }
440  
441  // 处理 GitHub Issues 数据
442  function processGithubData(data) {
443    if (!Array.isArray(data)) return [];
444    return data.slice(0, CONFIG.maxItems).map(function(item) {
445      var content = item.body || item.title || '';
446      content = content.replace(/<[^>]*>/g, '');
447      if (content.length > 50) {
448        content = content.substring(0, 50) + '...';
449      }
450      return {
451        content: content,
452        title: item.title || '',
453        hasImage: item.body && item.body.includes('<img'),
454        hasLink: item.body && item.body.includes('http'),
455        createdAt: item.created_at
456      };
457    }).filter(function(item) {
458      return item.content.trim().length > 0;
459    });
460  }
461  
462  // 处理 Memos 数据
463  function processMemosData(data) {
464    // Memos API 返回格式: { memos: [...] }
465    var memos = data.memos || data;
466    if (!Array.isArray(memos)) return [];
467    
468    return memos.slice(0, CONFIG.maxItems).map(function(item) {
469      var content = item.content || '';
470      // 去除 Markdown 图片语法 ![alt](url)
471      content = content.replace(/!\[([^\]]*)\]\(([^)]+)\)/g, '');
472      // 去除 HTML 标签
473      content = content.replace(/<[^>]*>/g, '');
474      // 去除多余空白
475      content = content.replace(/\s+/g, ' ').trim();
476      
477      if (content.length > 50) {
478        content = content.substring(0, 50) + '...';
479      }
480      
481      // 检查是否有资源(图片)
482      var hasImage = item.resources && item.resources.length > 0 && 
483                     item.resources.some(function(r) { return r.type && r.type.startsWith('image/'); });
484      
485      // 检查是否有链接
486      var hasLink = /https?:\/\/[^\s]+/.test(item.content || '');
487      
488      return {
489        content: content,
490        title: '',
491        hasImage: hasImage,
492        hasLink: hasLink,
493        createdAt: item.createTime || item.create_time
494      };
495    }).filter(function(item) {
496      return item.content.trim().length > 0;
497    });
498  }
499  
500  // 处理自定义 JSON 数据
501  function processCustomData(data) {
502    if (!Array.isArray(data)) return [];
503    return data.slice(0, CONFIG.maxItems).map(function(item) {
504      var content = item.content || '';
505      content = content.replace(/<[^>]*>/g, '');
506      if (content.length > 50) {
507        content = content.substring(0, 50) + '...';
508      }
509      return {
510        content: content,
511        title: item.title || '',
512        hasImage: item.hasImage || false,
513        hasLink: item.hasLink || false,
514        createdAt: item.date || item.createdAt || ''
515      };
516    }).filter(function(item) {
517      return item.content.trim().length > 0;
518    });
519  }
520  
521  // 处理数据
522  function processData(data) {
523    switch (CONFIG.dataSource) {
524      case 'memos':
525        return processMemosData(data);
526      case 'custom':
527        return processCustomData(data);
528      case 'github':
529      default:
530        return processGithubData(data);
531    }
532  }
533  
534  // 渲染单项
535  function renderItem(item) {
536    var html = '<div class="bb-time-list__slide">';
537    html += '<span class="bb-time-list__content">' + escapeHtml(item.content) + '</span>';
538    
539    if (item.hasImage) {
540      html += '<i class="bb-time-list__badge bb-time-list__badge--image"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="18" height="18" rx="2" ry="2"/><circle cx="8.5" cy="8.5" r="1.5"/><polyline points="21 15 16 10 5 21"/></svg></i>';
541    } else if (item.hasLink) {
542      html += '<i class="bb-time-list__badge bb-time-list__badge--link"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M10 13a5 5 0 007.54.54l3-3a5 5 0 00-7.07-7.07l-1.72 1.71"/><path d="M14 11a5 5 0 00-7.54-.54l-3 3a5 5 0 007.07 7.07l1.71-1.71"/></svg></i>';
543    }
544    
545    html += '</div>';
546    return html;
547  }
548  
549  // HTML 转义
550  function escapeHtml(text) {
551    var div = document.createElement('div');
552    div.textContent = text;
553    return div.innerHTML;
554  }
555  
556  // 渲染轮播
557  function renderCarousel() {
558    var container = document.getElementById('bberTalk');
559    if (!container || items.length === 0) return;
560    
561    if (items.length === 1) {
562      container.innerHTML = renderItem(items[0]);
563      return;
564    }
565    
566    var displayItems = items.concat([items[0]]);
567    var html = displayItems.map(renderItem).join('');
568    container.innerHTML = html;
569  }
570  
571  // 切换到指定索引
572  function goToIndex(index) {
573    if (items.length <= 1) return;
574    
575    var container = document.getElementById('bberTalk');
576    if (!container) return;
577    
578    var slides = container.querySelectorAll('.bb-time-list__slide');
579    if (slides.length === 0) return;
580    
581    if (index >= items.length) {
582      container.style.transition = 'transform 0.5s ease';
583      container.style.transform = 'translateY(-' + (index * 100) + '%)';
584      
585      setTimeout(function() {
586        container.style.transition = 'none';
587        container.style.transform = 'translateY(0)';
588        currentIndex = 0;
589      }, 500);
590    } else {
591      container.style.transition = 'transform 0.5s ease';
592      container.style.transform = 'translateY(-' + (index * 100) + '%)';
593      currentIndex = index;
594    }
595  }
596  
597  // 下一条
598  function next() {
599    goToIndex(currentIndex + 1);
600  }
601  
602  // 开始轮播
603  function start() {
604    if (timer) clearInterval(timer);
605    if (items.length > 1) {
606      timer = setInterval(next, CONFIG.interval);
607    }
608  }
609  
610  // 停止轮播
611  function stop() {
612    if (timer) {
613      clearInterval(timer);
614      timer = null;
615    }
616  }
617  
618  // 初始化
619  async function init() {
620    var data = await fetchData();
621    items = processData(data);
622    
623    if (items.length === 0) {
624      document.getElementById('bbTimeList').style.display = 'none';
625      return;
626    }
627    
628    renderCarousel();
629    start();
630    
631    var container = document.getElementById('bbTimeList');
632    if (container && items.length > 1) {
633      container.addEventListener('mouseenter', stop);
634      container.addEventListener('mouseleave', start);
635    }
636  }
637  
638  if (document.readyState === 'loading') {
639    document.addEventListener('DOMContentLoaded', init);
640  } else {
641    init();
642  }
643})();
644</script>
644
645
646
647
648  
649  
650  <section class="section-popular">
651    <div class="section-header">
652      <h2 class="section-title">热门文章</h2>
653      <a href="/post" class="section-more meta-text">查看全部 &rarr;</a>
654    </div>
655    <div class="compact-grid">
656      
657       <article class="post-card post-card--compact">
658  <a href="/post/timetable-guide/" class="post-card__inner">
659    <div class="post-card__body">
660      <h3 class="post-card__title clamp-2">Astro 博客课程表页面实现教程</h3>
661      <div class="post-card__meta meta-text">2026-04-13</div>
662    </div>
663    
664    <img class="post-card__thumb-square" src="https://imgbed.050815.xyz/file/cover/timetable/timetable__1_.webp" alt="Astro 博客课程表页面实现教程" loading="lazy" />
665    
666  </a>
667</article>
668  
669       <article class="post-card post-card--compact">
670  <a href="/post/use-neon-database-with-waline-on-vercel/" class="post-card__inner">
671    <div class="post-card__body">
672      <h3 class="post-card__title clamp-2">waline使用neon数据库和在vercel部署</h3>
673      <div class="post-card__meta meta-text">2026-04-11</div>
674    </div>
675    
676    <img class="post-card__thumb-square" src="https://jsd.268682.xyz/gh/zsxcoder/github-img@main/img/waline-neon-deploy.avif" alt="waline使用neon数据库和在vercel部署" loading="lazy" />
677    
678  </a>
679</article>
680  
681       <article class="post-card post-card--compact">
682  <a href="/post/post-diff-guide/" class="post-card__inner">
683    <div class="post-card__body">
684      <h3 class="post-card__title clamp-2">新文章通知记录</h3>
685      <div class="post-card__meta meta-text">2026-04-11</div>
686    </div>
687    
688    <img class="post-card__thumb-square" src="https://jsd.268682.xyz/gh/zsxcoder/github-img@main/img/post-diff-update.avif" alt="新文章通知记录" loading="lazy" />
689    
690  </a>
691</article>
692   </div>
693  </section>
694  
695
696  <div class="content-grid">
697    <div class="main-content">
698      
699      
700      <section class="section-latest">
701        <div class="section-header">
702          <h2 class="section-title">最新文章</h2>
703        </div>
704        <div class="post-list">
705          
706          <article class="post-card post-card--list">
707  <div class="post-card__body">
708    <a href="/post/timetable-guide/" class="post-card__title-link">
709      <h3 class="post-card__title clamp-3">Astro 博客课程表页面实现教程</h3>
710    </a>
711    <a href="/post/timetable-guide/" class="post-card__excerpt clamp-2">
712      Astro 博客课程表页面实现教程
713前言
714本文详细讲解如何使用 Astro + TypeScript 构建一个功能完整的课程表页面,包括数据结构设计、数据解析、组件架构等核心知识点。
715整体架构
716课程表页面采用分层设计:
717
718数据层 - ...
719    </a>
720    <div class="post-card__meta meta-text">
721      2026-04-13
722      
723    </div>
724  </div>
725  
726  <a href="/post/timetable-guide/" class="post-card__thumb-link">
727    <img class="post-card__thumb" src="https://imgbed.050815.xyz/file/cover/timetable/timetable__1_.webp" alt="Astro 博客课程表页面实现教程" loading="lazy" />
728  </a>
729  
730</article>
731
732          
733          <article class="post-card post-card--list">
734  <div class="post-card__body">
735    <a href="/post/use-neon-database-with-waline-on-vercel/" class="post-card__title-link">
736      <h3 class="post-card__title clamp-3">waline使用neon数据库和在vercel部署</h3>
737    </a>
738    <a href="/post/use-neon-database-with-waline-on-vercel/" class="post-card__excerpt clamp-2">
739      文章介绍了使用Waline和neon数据库在Vercel上部署博客的过程。首先,需要创建数据库并配置存储服务,然后定义数据库名称并执行创建表操作。接下来,通过点击顶部按钮重新部署,使数据库服务生效。最后,添加环境变量并进入项目页面进行...
740    </a>
741    <div class="post-card__meta meta-text">
742      2026-04-11
743      
744    </div>
745  </div>
746  
747  <a href="/post/use-neon-database-with-waline-on-vercel/" class="post-card__thumb-link">
748    <img class="post-card__thumb" src="https://jsd.268682.xyz/gh/zsxcoder/github-img@main/img/waline-neon-deploy.avif" alt="waline使用neon数据库和在vercel部署" loading="lazy" />
749  </a>
750  
751</article>
752
753          
754          <article class="post-card post-card--list">
755  <div class="post-card__body">
756    <a href="/post/post-diff-guide/" class="post-card__title-link">
757      <h3 class="post-card__title clamp-3">新文章通知记录</h3>
758    </a>
759    <a href="/post/post-diff-guide/" class="post-card__excerpt clamp-2">
760      文章介绍了博客新文章通知功能的实现原理,包括 RSS 数据获取、IndexedDB 存储、文章变更检测、差异对比算法等核心逻辑。通过对比新旧文章数据,自动检测新增和更新的文章,并以动画弹窗形式通知用户。
761
762新文章通知记录
763
764有时候,...
765    </a>
766    <div class="post-card__meta meta-text">
767      2026-04-11
768      
769    </div>
770  </div>
771  
772  <a href="/post/post-diff-guide/" class="post-card__thumb-link">
773    <img class="post-card__thumb" src="https://jsd.268682.xyz/gh/zsxcoder/github-img@main/img/
773post-diff-update.avif" alt="新文章通知记录" loading="lazy" />
774  </a>
775  
776</article>
777
778          
779        </div>
780      </section>
781      
782
783      
784      
785    </div>
786
787    
788    
789    <aside class="sidebar">
790      <div class="sidebar__section sidebar__about">
791        
792        <img class="sidebar__avatar" src="https://cdn.268682.xyz/images/avatar.png" alt="喵洛阁" />
793        
794        <h3 class="sidebar__title">喵洛阁</h3>
795        
796        <p class="sidebar__desc">每一段旅行,都有终点。</p>
797        
798        
799        <p class="sidebar__bio">欢迎来到克喵的博客!</p>
800        
801      </div>
802
803      
804      <div class="sidebar__section">
805        <h4 class="sidebar__section-title meta-text">课程表</h4>
806        <link rel="stylesheet" href="/styles/timetable.css">
807        <a href="/post/timetable" class="timetable-card" id="timetable-card" style="display: none;">
808  <div class="timetable-card__inner">
809    <div class="timetable-card__loading" id="timetable-card-loading">
810      <div class="timetable-card__skeleton"></div>
811      <div class="timetable-card__skeleton timetable-card__skeleton--medium"></div>
812      <div class="timetable-card__skeleton timetable-card__skeleton--short"></div>
813    </div>
814    <div class="timetable-card__content" id="timetable-card-content" style="display: none;">
815      <div class="timetable-card__status" id="timetable-card-status">加载中...</div>
816      <div class="timetable-card__lines" id="timetable-card-lines"></div>
817    </div>
818  </div>
819</a>
820
821<script>
822(function() {
823  var timetableData = null;
824
825  // 加载课表数据
826  async function loadTimetableData() {
827    try {
828      const paths = ['/timetable.json', '/assets/timetable.json'];
829      
830      for (const path of paths) {
831        try {
832          const response = await fetch(path);
833          if (response.ok) {
834            timetableData = await response.json();
835            return true;
836          }
837        } catch (e) {
838          console.log('Failed to load from', path);
839        }
840      }
841      
842      return false;
843    } catch (error) {
844      console.error('Error loading timetable:', error);
845      return false;
846    }
847  }
848
849  // 构建映射
850  function buildMaps() {
851    var courseMap = {};
852    if (timetableData && timetableData.courses) {
853      timetableData.courses.forEach(function(c) {
854        courseMap[c.id] = c;
855      });
856    }
857
858    var timeMap = {};
859    if (timetableData && timetableData.timeTable) {
860      timetableData.timeTable.forEach(function(t) {
861        timeMap[t.node] = t;
862      });
863    }
864    
865    return { courseMap: courseMap, timeMap: timeMap };
866  }
867
868  // 处理颜色
869  function parseColor(raw) {
870    if (!raw) return '#54675e';
871    var str = String(raw);
872    if (str.length === 9 && str[0] === '#') {
873      return '#' + str.slice(3);
874    }
875    return str;
876  }
877
878  // 计算当前周次
879  function resolveCurrentWeek() {
880    if (!timetableData || !timetableData.settings || !timetableData.settings.startDate) return 1;
881    var startDateStr = timetableData.settings.startDate;
882    var parts = startDateStr.split('-');
883    if (parts.length !== 3) return 1;
884    var startDate = new Date(parseInt(parts[0]), parseInt(parts[1]) - 1, parseInt(parts[2]));
885    var now = new Date();
886    var diffTime = now.getTime() - startDate.getTime();
887    var diffDays = Math.floor(diffTime / (1000 * 60 * 60 * 24));
888    var week = Math.floor(diffDays / 7) + 1;
889    var maxWeek = timetableData.settings.maxWeek || 20;
890    return Math.max(1, Math.min(week, maxWeek));
891  }
892
893  // 解析时间
894  function parseTimeToMinute(text) {
895    var parts = String(text || '').split(':');
896    if (parts.length !== 2) return null;
897    var hour = parseInt(parts[0]);
898    var minute = parseInt(parts[1]);
899    if (isNaN(hour) || isNaN(minute)) return null;
900    return hour * 60 + minute;
901  }
902
903  // 提取时间范围
904  function extractRangeMinutes(timeText) {
905    var match = String(timeText || '').match(/(\d{1,2}:\d{2})\s*-\s*(\d{1,2}:\d{2})/);
906    if (!match) return null;
907    var startMinute = parseTimeToMinute(match[1]);
908    var endMinute = parseTimeToMinute(match[2]);
909    if (startMinute === null || endMinute === null) return null;
910    return { startMinute: startMinute, endMinute: endMinute };
911  }
912
913  // 格式化时长
914  function formatDuration(totalSeconds) {
915    var safeSecs = Math.max(0, Math.floor(totalSeconds));
916    var days = Math.floor(safeSecs / 86400);
917    var hours = Math.floor((safeSecs % 86400) / 3600);
918    var minutes = Math.floor((safeSecs % 3600) / 60);
919    var seconds = safeSecs % 60;
920    var parts = [];
921    if (days > 0) parts.push(days + '天');
922    if (hours > 0) parts.push(hours + '时');
923    if (minutes > 0) parts.push(minutes + '分钟');
924    if (seconds > 0 || parts.length === 0) parts.push(seconds + '秒');
925    return parts.join(',');
926  }
927
928  // 获取今日课程
929  function getTodayCourses(currentWeek, maps) {
930    var now = new Date();
931    var day = now.getDay();
932    if (day === 0) day = 7;
933    var todayCourses = [];
934    
935    if (!timetableData || !timetableData.schedules) {
936      return todayCourses;
937    }
938    
939    timetableData.schedules.forEach(function(schedule) {
940      if (schedule.day !== day) return;
941      if (schedule.startWeek > currentWeek || schedule.endWeek < currentWeek) return;
942      var courseDef = maps.courseMap[schedule.id];
943      if (!courseDef) return;
944      var startNode = schedule.startNode;
945      var step = schedule.step || 1;
946      var endNode = startNode + step - 1;
947      var startNodeTime = maps.timeMap[startNode];
948      var endNodeTime = maps.timeMap[endNode];
949      if (!startNodeTime) return;
950      var startTime = startNodeTime.startTime;
951      var endTime = endNodeTime ? endNodeTime.endTime : startNodeTime.endTime;
952      var timeText = startTime + ' - ' + endTime;
953      var range = extractRangeMinutes(timeText);
954      if (!range) return;
955      todayCourses.push({
956        courseId: schedule.id,
957        courseName: courseDef.courseName,
958        color: parseColor(courseDef.color),
959        teacher: schedule.teacher || '未填写',
960        room: schedule.room || '未填写',
961        day: day,
962        startNode: startNode,
963        endNode: endNode,
964        startWeek: schedule.startWeek,
965        endWeek: schedule.endWeek,
966        timeText: timeText,
967        startMinute: range.startMinute,
968        endMinute: range.endMinute
969      });
970    });
971    
972    return todayCourses.sort(function(a, b) {
973      return a.startMinute - b.startMinute;
974    });
975  }
976
977  // 获取本周所有课程
978  function getAllCoursesThisWeek(currentWeek, maps) {
979    var allCourses = [];
980    if (!timetableData || !timetableData.schedules) {
981      return allCourses;
982    }
983    for (var d = 1; d <= 7; d++) {
984      timetableData.schedules.forEach(function(schedule) {
985        if (schedule.day !== d) return;
986        if (schedule.startWeek > currentWeek || schedule.endWeek < currentWeek) return;
987        var courseDef = maps.courseMap[schedule.id];
988        if (!courseDef) return;
989        var startNode = schedule.startNode;
990        var step = schedule.step || 1;
991        var endNode = startNode + step - 1;
992        var startNodeTime = maps.timeMap[startNode];
993        var endNodeTime = maps.timeMap[endNode];
994        if (!startNodeTime) return;
995        var startTime = startNodeTime.startTime;
996        var endTime = endNodeTime ? endNodeTime.endTime : startNodeTime.endTime;
997        var timeText = startTime + ' - ' + endTime;
998        var range = extractRangeMinutes(timeText);
999        if (!range) return;
1000        allCourses.push({
1001          courseId: schedule.id,
1002          courseName: courseDef.courseName,
1003          color: parseColor(courseDef.color),
1004          teacher: schedule.teacher || '未填写',
1005          room: schedule.room || '未填写',
1006          day: d,
1007          startNode: startNode,
1008          endNode: endNode,
1009          timeText: timeText,
1010          startMinute: range.startMinute,
1011          endMinute: range.endMinute
1012        });
1013      });
1014    }
1015    return allCourses.sort(function(a, b) {
1016      if (a.day !== b.day) return a.day - b.day;
1017      return a.startMinute - b.startMinute;
1018    });
1019  }
1020
1021  // 解析实时状态
1022  function resolveLiveState(currentWeek, maps) {
1023    var now = new Date();
1024    var currentSecond = now.getHours() * 3600 + now.getMinutes() * 60 + now.getSeconds();
1025    var currentMinute = Math.floor(currentSecond / 60);
1026    var day = now.getDay();
1027    if (day === 0) day = 7;
1028    
1029    if (day >= 6) {
1030      var allCourses = getAllCoursesThisWeek(currentWeek, maps);
1031      var nextWeekFirstCourse = null;
1032      for (var i = 0; i < allCourses.length; i++) {
1033        if (allCourses[i].day === 1) {
1034          nextWeekFirstCourse = allCourses[i];
1035          break;
1036        }
1037      }
1038      if (nextWeekFirstCourse) {
1039        var daysUntilMonday = day === 6 ? 2 : 1;
1040        var secondsUntilCourse = daysUntilMonday * 86400 + nextWeekFirstCourse.startMinute * 60 - currentSecond;
1041        return {
1042          status: '本周课毕',
1043          lines: [
1044            { label: '下周首节:', value: nextWeekFirstCourse.courseName + ' - ' + (nextWeekFirstCourse.room || '未知'), bold: true, color: nextWeekFirstCourse.color },
1045            { label: '距上课还有:', value: formatDuration(secondsUntilCourse), bold: true, countdown: true }
1046          ]
1047        };
1048      }
1049      return { status: '周末', lines: [] };
1050    }
1051    
1052    var courses = getTodayCourses(currentWeek, maps);
1053    
1054    if (courses.length === 0) {
1055      var allCourses = getAllCoursesThisWeek(currentWeek, maps);
1056      var nextDayCourse = null;
1057      for (var i = 0; i < allCourses.length; i++) {
1058        if (allCourses[i].day > day) {
1059          nextDayCourse = allCourses[i];
1060          break;
1061        }
1062      }
1063      if (nextDayCourse) {
1064        var daysUntil = nextDayCourse.day - day;
1065        var secondsUntilCourse = daysUntil * 86400 + nextDayCourse.startMinute * 60 - currentSecond;
1066        return {
1067          status: '今日课毕',
1068          lines: [
1069            { label: '翌日首节:', value: nextDayCourse.courseName + ' - ' + (nextDayCourse.room || '未知'), bold: true, color: nextDayCourse.color },
1070            { label: '距上课还有:', value: formatDuration(secondsUntilCourse), bold: true, countdown: true }
1071          ]
1072        };
1073      }
1074      return { status: '今日课毕', lines: [] };
1075    }
1076    
1077    for (var i = 0; i < courses.length; i++) {
1078      var current = courses[i];
1079      var prev = i > 0 ? courses[i - 1] : null;
1080      var next = courses[i + 1] || null;
1081      
1082      if (currentMinute >= current.startMinute && currentMinute < current.endMinute) {
1083        var remainSeconds = current.endMinute * 60 - currentSecond;
1084        var lines = [
1085          { label: '本节:', value: current.courseName + ' - ' + (current.room || '未知'), bold: true, color: current.color },
1086          { label: '距下课还有:', value: formatDuration(remainSeconds), bold: true, countdown: true }
1087        ];
1088        if (prev) {
1089          lines.unshift({ label: '上节:', value: prev.courseName + ' - ' + (prev.room || '未知'), strikethrough: true, color: prev.color });
1090        }
1091        if (next) {
1092          lines.push({ label: '下节:', value: next.courseName + ' - ' + (next.room || '未知'), color: next.color });
1093        }
1094        return { status: '上课', lines: lines };
1095      }
1096      
1097      if (next && currentMinute >= current.endMinute && currentMinute < next.startMinute) {
1098        var remainSeconds = next.startMinute * 60 - currentSecond;
1099        return {
1100          status: '课间',
1101          lines: [
1102            { label: '上节:', value: current.courseName + ' - ' + (current.room || '未知'), strikethrough: true, color: current.color },
1103            { label: '下节:', value: next.courseName + ' - ' + (next.room || '未知'), bold: true, color: next.color },
1104            { label: '距上课还有:', value: formatDuration(remainSeconds), bold: true, countdown: true }
1105          ]
1106        };
1107      }
1108    }
1109    
1110    var lastCourse = courses[courses.length - 1];
1111    if (currentMinute >= lastCourse.endMinute) {
1112      var allCourses = getAllCoursesThisWeek(currentWeek, maps);
1113      var nextDayCourse = null;
1114      for (var i = 0; i < allCourses.length; i++) {
1115        if (allCourses[i].day > day) {
1116          nextDayCourse = allCourses[i];
1117          break;
1118        }
1119      }
1120      if (nextDayCourse) {
1121        var daysUntil = nextDayCourse.day - day;
1122        var secondsUntilCourse = daysUntil * 86400 + nextDayCourse.startMinute * 60 - currentSecond;
1123        return {
1124          status: '今日课毕',
1125          lines: [
1126            { label: '翌日首节:', value: nextDayCourse.courseName + ' - ' + (nextDayCourse.room || '未知'), bold: true, color: nextDayCourse.color },
1127            { label: '距上课还有:', value: formatDuration(secondsUntilCourse), bold: true, countdown: true }
1128          ]
1129        };
1130      }
1131      return { status: '今日课毕', lines: [] };
1132    }
1133    
1134    var firstCourse = courses[0];
1135    var remainSeconds = firstCourse.startMinute * 60 - currentSecond;
1136    return {
1137      status: '课前',
1138      lines: [
1139        { label: '首节:', value: firstCourse.courseName + ' - ' + (firstCourse.room || '未知'), bold: true, color: firstCourse.color },
1140        { label: '距上课还有:', value: formatDuration(remainSeconds), bold: true, countdown: true }
1141      ]
1142    };
1143  }
1144
1145  // 更新卡片显示
1146  function updateCard(maps) {
1147    var currentWeek = resolveCurrentWeek();
1148    var state = resolveLiveState(currentWeek, maps);
1149    
1150    var loadingEl = document.getElementById('timetable-card-loading');
1151    var contentEl = document.getElementById('timetable-card-content');
1152    var statusEl = document.getElementById('timetable-card-status');
1153    var linesEl = document.getElementById('timetable-card-lines');
1154    
1155    if (loadingEl) loadingEl.style.display = 'none';
1156    if (contentEl) contentEl.style.display = 'block';
1157    
1158    if (statusEl) {
1159      statusEl.textContent = state.status;
1160    }
1161    
1162    if (linesEl) {
1163      linesEl.innerHTML = '';
1164      state.lines.forEach(function(line) {
1165        var lineDiv = document.createElement('div');
1166        lineDiv.className = 'timetable-card__line';
1167        
1168        var labelSpan = document.createElement('span');
1169        labelSpan.className = 'timetable-card__label';
1170        labelSpan.textContent = line.label;
1171        lineDiv.appendChild(labelSpan);
1172        
1173        var valueSpan = document.createElement('span');
1174        valueSpan.className = 'timetable-card__value';
1175        if (line.bold) valueSpan.classList.add('timetable-card__value--bold');
1176        if (line.strikethrough) valueSpan.classList.add('timetable-card__value--strikethrough');
1177        if (line.countdown) valueSpan.classList.add('timetable-card__countdown');
1178        if (line.color) valueSpan.style.color = line.color;
1179        valueSpan.textContent = line.value;
1180        lineDiv.appendChild(valueSpan);
1181        
1182        linesEl.appendChild(lineDiv);
1183      });
1184    }
1185  }
1186
1187  // 初始化
1188  async function init() {
1189    var loaded = await loadTimetableData();
1190    
1191    if (!loaded) {
1192      // 隐藏卡片
1193      var card = document.getElementById('timetable-card');
1194      if (card) card.style.display = 'none';
1195      return;
1196    }
1197    
1198    // 显示卡片
1199    var card = document.getElementById('timetable-card');
1200    if (card) card.style.display = 'block';
1201    
1202    var maps = buildMaps();
1203    updateCard(maps);
1204    setInterval(function() { updateCard(maps); }, 1000);
1205  }
1206
1207  if (document.readyState === 'loading') {
1208    document.addEventListener('DOMContentLoaded', init);
1209  } else {
1210    init();
1211  }
1212})();
1213</script>
1213
1214
1215      </div>
1216
1217      
1218      <div class="sidebar__section">
1219        <h4 class="sidebar__section-title meta-text">标签</h4>
1220        <div class="sidebar__tags">
1221          
1222          <a href="/tag/bo-ke/" class="tag-pill">博客</a>
1223          
1224          <a href="/tag/gong-neng/" class="tag-pill">功能</a>
1225          
1226          <a href="/tag/w-a-l-i-n-e-ping-lun/" class="tag-pill">waline评论</a>
1227          
1228          <a href="/tag/ye-mian/" class="tag-pill">页面</a>
1229          
1230        </div>
1231      </div>
1232      
1233
1234    </aside>
1235    
1236  </div>
1237</div>
1238
1239  </main>
1240
1241  <footer class="site-footer">
1242  <div class="footer-inner">
1243
1244    
1245    
1246    
1247    <nav class="footer-nav" aria-label="页脚导航" id="footer-nav-custom">
1248    </nav>
1249    
1249<script>
1250      (function() {
1251        var navData = [
1252  {"name": "主页", "link": "https://www.kemiaosw.top"},
1253  {"name": "BLog", "link": "https://kemeow0815.github.io/blog"},
1254  {"name": "友链屋", "link": "https://www.268682.xyz"}, {"name": "Gwitter", "link": "https://gwitter.268682.xyz"}
1255];
1256        if (Array.isArray(navData)) {
1257          var navHtml = '';
1258          navData.forEach(function(item) {
1259            navHtml += '<a class="footer-nav__link" href="' + item.link + '">' + item.name + '</a>';
1260          });
1261          document.getElementById('footer-nav-custom').innerHTML = navHtml;
1262        }
1263      })();
1264    </script>
1264
1265    
1266
1267    
1268    <div class="footer-social">
1269      
1270      <a class="footer-social__link" href="https://github.com/Kemeow0815" target="_blank" rel="noopener noreferrer"
1271        aria-label="GitHub">
1272        <svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor">
1273          <path
1274            d="M12 0C5.37 0 0 5.37 0 12c0 5.31 3.435 9.795 8.205 11.385.6.105.825-.255.825-.57 0-.285-.015-1.23-.015-2.235-3.015.555-3.795-.735-4.035-1.41-.135-.345-.72-1.41-1.23-1.695-.42-.225-1.02-.78-.015-.795.945-.015 1.62.87 1.845 1.23 1.08 1.815 2.805 1.305 3.495.99.105-.78.42-1.305.765-1.605-2.67-.3-5.46-1.335-5.46-5.925 0-1.305.465-2.385 1.23-3.225-.12-.3-.54-1.53.12-3.18 0 0 1.005-.315 3.3 1.23.96-.27 1.98-.405 3-.405s2.04.135 3 .405c2.295-1.56 3.3-1.23 3.3-1.23.66 1.65.24 2.88.12 3.18.765.84 1.23 1.905 1.23 3.225 0 4.605-2.805 5.625-5.475 5.925.435.375.81 1.095.81 2.22 0 1.605-.015 2.895-.015 3.3 0 .315.225.69.825.57A12.02 12.02 0 0024 12c0-6.63-5.37-12-12-12z" />
1275        </svg>
1276      </a>
1277      
1278      
1279      
1280      
1281      <a class="footer-social__link" href="https://t.me/Kemeow0815" target="_blank" rel="noopener noreferrer"
1282        aria-label="Telegram">
1283        <svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor">
1284          <path d="M12 0C5.37 0 0 5.37 0 12s5.37 12 12 12 12-5.37 12-12S18.63 0 12 0zm5.95 8.24l-2.17 10.24c-.16.73-.6.9-1.22.56l-3.37-2.48-1.63 1.56c-.18.18-.33.33-.68.33l.24-3.44 6.25-5.65c.27-.24-.06-.37-.42-.13l-7.72 4.86-3.33-1.04c-.72-.23-.74-.72.15-1.06l13.02-5.02c.6-.22 1.12.14.93 1.03z"/>
1285        </svg>
1286      </a>
1287      
1288      
1289      <a class="footer-social__link" href="https://qm.qq.com/q/kz5EuEimk0" target="_blank" rel="noopener noreferrer"
1290        aria-label="QQ">
1291        <svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor">
1292          <path d="M12.003 2c-2.265 0-6.29 1.364-6.29 7.325v1.195S3.55 14.96 3.55 17.474c0 .665.17 1.025.281 1.025.114 0 .902-.484 1.748-2.072 0 0-.18 2.197 1.904 3.967 0 0-1.77.495-1.77 1.182 0 .686 4.078.43 6.29.43 2.21 0 6.288.256 6.288-.43 0-.687-1.768-1.182-1.768-1.182 2.085-1.77 1.905-3.967 1.905-3.967.845 1.588 1.634 2.072 1.746 2.072.111 0 .283-.36.283-1.025 0-2.514-2.166-6.954-2.166-6.954V9.325C18.29 3.364 14.268 2 12.003 2z"/>
1293        </svg>
1294      </a>
1295      
1296      
1297      <a class="footer-social__link" href="https://space.bilibili.com/3546643173477234" target="_blank" rel="noopener noreferrer"
1298        aria-label="Bilibili">
1299        <svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor">
1300          <path d="M17.813 4.653h.854c1.51.054 2.769.578 3.773 1.574 1.004.995 1.524 2.249 1.56 3.76v7.36c-.036 1.51-.556 2.769-1.56 3.773s-2.262 1.524-3.773 1.56H5.333c-1.51-.036-2.769-.556-3.773-1.56S.036 18.858 0 17.347v-7.36c.036-1.511.556-2.765 1.56-3.76 1.004-.996 2.262-1.52 3.773-1.574h.774l-1.174-1.12a1.234 1.234 0 0 1-.373-.906c0-.356.124-.658.373-.907l.027-.027c.267-.249.573-.373.92-.373.347 0 .653.124.92.373L9.653 4.44c.071.071.134.142.187.213h4.267a.836.836 0 0 1 .16-.213l2.853-2.747c.267-.249.573-.373.92-.373.347 0 .662.151.929.4.267.249.391.551.391.907 0 .355-.124.657-.373.906zM5.333 7.24c-.746.018-1.373.276-1.88.773-.506.498-.769 1.13-.786 1.894v7.52c.017.764.28 1.395.786 1.893.507.498 1.134.756 1.88.773h13.334c.746-.017 1.373-.275 1.88-.773.506-.498.769-1.129.786-1.893v-7.52c-.017-.765-.28-1.396-.786-1.894-.507-.497-1.134-.755-1.88-.773zM8 11.107c.373 0 .684.124.933.373.25.249.383.569.4.96v1.173c-.017.391-.15.711-.4.96-.249.25-.56.374-.933.374s-.684-.125-.933-.374c-.25-.249-.383-.569-.4-.96V12.44c0-.373.129-.689.386-.947.258-.257.574-.386.947-.386zm8 0c.373 0 .684.124.933.373.25.249.383.569.4.96v1.173c-.017.391-.15.711-.4.96-.249.25-.56.374-.933.374s-.684-.125-.933-.374c-.25-.249-.383-.569-.4-.96V12.44c.017-.391.15-.711.4-.96.249-.249.56-.373.933-.373z"/>
1301        </svg>
1302      </a>
1303      
1304      
1305      <a class="footer-social__link" href="https://web.okjike.com/u/07152f0c-0f65-4501-855b-031f3e20e4a5" target="_blank" rel="noopener noreferrer"
1306        aria-label="即刻">
1307        <svg width="20" height="20" viewBox="0 0 1024 1024" fill="currentColor">
1308          <path d="M660.39 123.63v463.59S645.04 726.4 558.98 771.43c-53.12 31.21-128.85 67.54-228.63 70.61l58.33 58.33s175-15.35 254.82-87.5 82.89-159.9 82.89-194.31V172.75l-66-49.12z" fill="#62B2E4"/>
1309          <path d="M660.39 123.63v463.58s-15.36 139.17-101.41 184.21c-53.12 31.22-128.85 67.54-228.62 70.62L297.6 702.85s70.62-13.31 117.71-28.64c47.07-15.36 72-95.19 72-95.19V123.63h173.08z" fill="#F3DB2B"/>
1310        </svg>
1311      </a>
1312      
1313      
1314      <a class="footer-social__link" href="mailto:[email protected]" aria-label="邮箱">
1315        <svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor">
1316          <path d="M20 4H4c-1.1 0-1.99.9-1.99 2L2 18c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2zm0 4l-8 5-8-5V6l8 5 8-5v2z"/>
1317        </svg>
1318      </a>
1319      
1320      <a class="footer-social__link" href="/feed.xml" aria-label="RSS">
1321        <svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor">
1322          <path d="M6.18 15.64a2.18 2.18 0 110 4.36 2.18 2.18 0 010-4.36zM4 4.44A15.56 15.56 0 0119.56 20h-2.83A12.73 12.73 0 004 7.27V4.44zm0 5.66a9.9 9.9 0 019.9 9.9h-2.83A7.07 7.07 0 004 12.93V10.1z"/>
1323        </svg>
1324      </a>
1325    </div>
1326
1327    
1328    <div class="footer-copyright">
1329      <p>&copy; 2026 喵洛阁. All rights reserved.</p>
1330    </div>
1331
1332    
1333    
1334    <div class="footer-custom">
1335      <span id="runtime_span" style="text-align: center;"></span>
1336<br>
1337<script defer src="https://busuanzi.9420.ltd/js"></script>
1337
1338<div style="text-align: center;">
1339    本站总访问量 <span id="busuanzi_site_pv"></span> 次
1340    本站总访客数 <span id="busuanzi_site_uv"></span> 人
1341</div>
1342<!-- 引入 CSS (HEAD标签内) -->
1343<link rel="stylesheet" href="https://cdn.268682.xyz/music/netease-mini-player-v2.css">
1344
1345<!-- 引入 JS (BODY标签底部) -->
1346<script src="https://cdn.268682.xyz/music/netease-mini-player-v2.js"></script>
1346
1347<div class="netease-mini-player"
1348     data-playlist-id="13681647281"
1349     data-embed="false"
1350     data-position="bottom-left"
1351     data-lyric="true"
1352     data-theme="auto"
1353     data-default-minimized="true"
1354></div>
1355    </div>
1356    
1357
1358    
1359    <div class="footer-powered">
1360      <span>Powered by <a href="https://gridea.pro" target="_blank" rel="noopener noreferrer">Gridea Pro</a></span>
1361    </div>
1362
1363  </div>
1364</footer>
1365
1366  
1367  <div class="search-overlay" id="searchOverlay" style="display:none;">
1368  <div class="search-modal">
1369    <div class="search-header">
1370      <svg class="search-icon" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor"
1371        stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
1372        <circle cx="11" cy="11" r="8"></circle>
1373        <line x1="21" y1="21" x2="16.65" y2="16.65"></line>
1374      </svg>
1375      <input type="text" class="search-input" id="searchInput" placeholder="搜索文章..." autocomplete="off" />
1376      <span class="search-kbd">&#8984;K</span>
1377      <button class="search-close" id="searchClose" type="button" aria-label="关闭搜索">
1378        <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
1379          stroke-linecap="round" stroke-linejoin="round">
1380          <line x1="18" y1="6" x2="6" y2="18"></line>
1381          <line x1="6" y1="6" x2="18" y2="18"></line>
1382        </svg>
1383      </button>
1384    </div>
1385    <div class="search-results" id="searchResults"></div>
1386  </div>
1387</div>
1388
1389<script src="/scripts/search.js"></script>
1389
1390  
1391
1392  
1393
1394  
1395
1396  
1397  
1397<script>function show_runtime() {
1398  var X = new Date("2024/11/1 12:00:00");
1399  var Y = new Date();
1400  var T = Y.getTime() - X.getTime();
1401  var M = 24 * 60 * 60 * 1000;
1402  var a = T / M;
1403  var A = Math.floor(a);
1404  var b = (a - A) * 24;
1405  var B = Math.floor(b);
1406  var c = (b - B) * 60;
1407  var C = Math.floor((b - B) * 60);
1408  var D = Math.floor((c - C) * 60);
1409  document.getElementById("runtime_span").innerHTML = "⏱️本站已运行 " + A + "天" + B + "小时" + C + "分" + D + "秒";
1410  setTimeout(show_runtime, 1000);
1411}
1412show_runtime();</script>
1412
1413  
1414
1415  <!-- 加载页面脚本 -->
1416  
1417  
1417<script>
1418  (function() {
1419    // 页面加载完成后隐藏加载动画
1420    function hideLoadingOverlay() {
1421      const overlay = document.getElementById('loading-overlay');
1422      if (overlay) {
1423        overlay.classList.add('fade-out');
1424        setTimeout(function() {
1425          overlay.style.display = 'none';
1426        }, 600);
1427      }
1428    }
1429    
1430    // 等待页面完全加载
1431    if (document.readyState === 'complete') {
1432      hideLoadingOverlay();
1433    } else {
1434      window.addEventListener('load', hideLoadingOverlay);
1435    }
1436  })();
1437  </script>
1437
1438  
1439
1440  <!-- 流星背景脚本 - 性能优化 -->
1441  
1441<script>
1442  (function() {
1443    const starsContainer = document.getElementById('bg-stars');
1444    if (!starsContainer) return;
1445    
1446    // 检测移动端和性能模式
1447    const isMobile = window.matchMedia('(max-width: 750px)').matches;
1448    const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
1449    
1450    // 根据设备调整流星数量
1451    const starCount = isMobile ? 15 : (prefersReducedMotion ? 0 : 25);
1452    
1453    // 使用 DocumentFragment 批量添加
1454    const fragment = document.createDocumentFragment();
1455    
1456    for (let i = 0; i < starCount; i++) {
1457      const star = document.createElement('div');
1458      star.className = 'bg-star';
1459      
1460      // 随机参数 - 使用整数避免过多重绘
1461      const topOffset = Math.floor(Math.random() * 130) + 'vh';
1462      const fallDuration = (Math.floor(Math.random() * 8) + 6) + 's';
1463      const fallDelay = (Math.floor(Math.random() * 15)) + 's';
1464      
1465      star.style.setProperty('--top-offset', topOffset);
1466      star.style.setProperty('--fall-duration', fallDuration);
1467      star.style.setProperty('--fall-delay', fallDelay);
1468      
1469      fragment.appendChild(star);
1470    }
1471    
1472    starsContainer.appendChild(fragment);
1473  })();
1474  </script>
1474
1475
1476<script>console.log("\n%c ✦ Gridea Pro v1.1.1 %c · Theme: flavor-theme v1.0.0 · https://gridea.pro %c\n","color:#fadfa3;background:#111111;padding:9px 14px;border-radius:4px 0 0 4px;font-weight:500;font-size:12px","color:#e05c00;background:#fff3ec;padding:9px 14px;border-radius:0 4px 4px 0;font-size:11px","")</script>
1476
1477</body>
1478</html>

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.