Merge pull request 'Codex/design 启用新的页面风格' (#10) from codex/design into master
Reviewed-on: #10 -- [CST 2026-07-19 Sunday 16:12:13]
This commit is contained in:
commit
4b015839af
112 files changed
+63309
No files matched your search
+626
@@ -0,0 +1,626 @@
|
||||
<!DOCTYPE html><html lang="zh-CN"> <head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><meta name="description" content="Personal blog by biss."><meta name="author" content="biss"><link rel="canonical" href="https://blog.biss.click/"><link rel="alternate" type="application/rss+xml" title="Bi's Blog RSS" href="https://blog.biss.click/rss.xml"><meta property="og:site_name" content="Bi's Blog"><meta property="og:locale" content="zh_CN"><meta property="og:type" content="website"><meta property="og:title" content="Bi's Blog"><meta property="og:description" content="Personal blog by biss."><meta property="og:url" content="https://blog.biss.click/"><meta property="og:image" content="https://free.picui.cn/free/2025/08/10/689845496a283.png"><meta property="og:image:alt" content="Bi's Blog"><meta name="twitter:card" content="summary_large_image"><meta name="twitter:title" content="Bi's Blog"><meta name="twitter:description" content="Personal blog by biss."><meta name="twitter:image" content="https://free.picui.cn/free/2025/08/10/689845496a283.png"><link rel="icon" href="/images/Bi.ico"><link rel="manifest" href="/manifest.webmanifest"><link rel="apple-touch-icon" href="/images/apple-touch-icon.png"><meta name="theme-color" content="#f7f5ef" media="(prefers-color-scheme: light)"><meta name="theme-color" content="#0b1014" media="(prefers-color-scheme: dark)"><script defer src="https://umami.biss.click/script.js" data-website-id="3d97b310-b241-4fc6-bc9c-68e317fc7d42"></script><script>window.SITE_CONFIG = {"site":{"url":"https://blog.biss.click","name":"Bi's Blog","description":"Personal blog by biss.","language":"zh-CN","domain":"blog.biss.click","slogan":"好奇心转化为代码的空间。","startTime":"2025-01-01T00:00:00+08:00","copyrightStartYear":2025,"license":"内容采用 CC BY-NC-SA 4.0 协议"},"author":{"name":"biss","email":"bishsh2006@gmail.com","github":"https://github.com/bishshi","avatar":"https://free.picui.cn/free/2025/08/10/689845496a283.png"},"comments":{"envId":"https://comment.biss.click","region":"","script":"https://cdn.jsdelivr.net/npm/twikoo@1.7.7/dist/twikoo.min.js"},"analytics":{"umami":{"script":"https://umami.biss.click/script.js","websiteId":"3d97b310-b241-4fc6-bc9c-68e317fc7d42","statsEndpoint":"https://blogumami.biss.click"}},"talks":{"apiUrl":"https://mm.biss.click/api/echo/page","pageSize":30,"homeLimit":6,"cacheKey":"talksCache","cacheTimeKey":"talksCacheTime","cacheDurationMs":1800000,"fallbackAvatar":"https://free.picui.cn/free/2025/08/10/689845496a283.png"},"search":{"apiKey":"2uxQzk7eD2GBCljdSmIiuU3ause7UK9n","server":{"host":"typesense.biss.click","port":"443","protocol":"https"},"indexName":"blogs","searchParams":{"query_by":"title,content","highlight_full_fields":"title,content","per_page":8,"num_typos":1,"typo_tokens_threshold":1,"prefix":true},"ui":{"maxRetries":10,"retryDelay":100,"animationDuration":300,"maxInitRetries":30,"placeholder":"输入关键词寻找故事..."}},"externalLinks":{"waitSeconds":10,"whitelist":["blog.biss.click","biss.click","github.com","localhost","127.0.0.1"]}};</script><script>
|
||||
(() => {
|
||||
const storageKey = 'site-theme';
|
||||
const media = window.matchMedia('(prefers-color-scheme: dark)');
|
||||
const getSystemTheme = () => (media.matches ? 'dark' : 'light');
|
||||
const getSavedTheme = () => {
|
||||
try {
|
||||
const theme = localStorage.getItem(storageKey);
|
||||
return theme === 'dark' || theme === 'light' ? theme : null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
document.documentElement.dataset.theme = getSavedTheme() || getSystemTheme();
|
||||
})();
|
||||
</script><link rel="stylesheet" href="https://cdn.jsdmirror.com/npm/instantsearch.css/themes/reset-min.css"><link rel="stylesheet" href="https://cdn.jsdmirror.com/npm/@fancyapps/ui@5.0/dist/fancybox/fancybox.css"><link rel="stylesheet" href="https://cdn.jsdmirror.com/npm/@fortawesome/fontawesome-free@6.7.2/css/all.min.css"><title>Bi's Blog</title><link rel="stylesheet" href="/_astro/BaseLayout.DvPRLM9I.css"></head> <body> <header class="site-header"> <nav class="nav"> <a class="brand" href="/" aria-label="Bi's Blog 首页"><span>biss</span><i>.log</i></a> <button class="nav-menu-toggle" type="button" aria-label="打开菜单" aria-expanded="false" data-nav-toggle> <i class="fa-solid fa-bars nav-menu-open" aria-hidden="true"></i> <i class="fa-solid fa-xmark nav-menu-close" aria-hidden="true"></i> </button> <div class="nav-drawer-backdrop" data-nav-backdrop></div> <div class="nav-menu" data-nav-menu> <div class="nav-links nav-center"> <a href="/archives/"><i class="fa-solid fa-box-archive nav-icon" aria-hidden="true"></i><span>归档</span></a><a href="/categories/"><i class="fa-solid fa-folder-open nav-icon" aria-hidden="true"></i><span>分类</span></a><a href="/tags/"><i class="fa-solid fa-tags nav-icon" aria-hidden="true"></i><span>标签</span></a><a href="/shuoshuo/"><i class="fa-regular fa-comments nav-icon" aria-hidden="true"></i><span>说说</span></a><a href="/about/"><i class="fa-solid fa-heart nav-icon" aria-hidden="true"></i><span>关于</span></a><a href="/link/"><i class="fa-solid fa-link nav-icon" aria-hidden="true"></i><span>友链</span></a> </div> <div class="nav-links nav-actions"> <button class="nav-random" type="button" onclick="randomPost()" aria-label="前往随机文章" title="前往随机文章"> <i class="fa-solid fa-shuffle nav-icon" aria-hidden="true"></i> <span>随机文章</span> </button> <button class="nav-search search-typesense-trigger" type="button" aria-label="搜索" title="搜索"> <i class="fa-solid fa-magnifying-glass nav-icon" aria-hidden="true"></i> <span>搜索</span> </button> <button class="nav-console" type="button" aria-label="打开中控台" title="中控台" data-console-open> <i class="fa-solid fa-gauge-high nav-icon" aria-hidden="true"></i> <span>中控台</span> </button> <button class="theme-toggle" type="button" aria-label="切换夜间模式" title="切换夜间模式" data-theme-toggle> <i class="fa-solid fa-sun theme-toggle-sun nav-icon" aria-hidden="true"></i> <i class="fa-solid fa-moon theme-toggle-moon nav-icon" aria-hidden="true"></i> </button> </div> </div> </nav> </header> <div class="control-console" data-console-modal aria-hidden="true"> <div class="control-console-backdrop" data-console-close></div> <section class="control-console-panel" role="dialog" aria-modal="true" aria-labelledby="control-console-title" tabindex="-1"> <header class="control-console-head"> <div> <p>CONTROL CENTER</p> <h2 id="control-console-title">中控台</h2> </div> <button class="control-console-close" type="button" aria-label="关闭中控台" title="关闭" data-console-close> <i class="fa-solid fa-xmark" aria-hidden="true"></i> </button> </header> <div class="control-console-grid"> <a class="control-site-card" href="https://biss.click" target="_blank" rel="noreferrer"> <i class="fa-solid fa-house-chimney" aria-hidden="true"></i> <span> <strong>主站入口</strong> <em>个人主页与常用入口集合</em> </span> <small>biss.click</small> </a><a class="control-site-card" href="https://mm.biss.click" target="_blank" rel="noreferrer"> <i class="fa-regular fa-comments" aria-hidden="true"></i> <span> <strong>说说动态</strong> <em>短句、碎片记录和近况更新</em> </span> <small>mm.biss.click</small> </a><a class="control-site-card" href="https://comment.biss.click" target="_blank" rel="noreferrer"> <i class="fa-solid fa-message" aria-hidden="true"></i> <span> <strong>评论系统</strong> <em>Twikoo 评论服务与访客互动</em> </span> <small>comment.biss.click</small> </a><a class="control-site-card" href="https://umami.biss.click" target="_blank" rel="noreferrer"> <i class="fa-solid fa-chart-line" aria-hidden="true"></i> <span> <strong>站点统计</strong> <em>Umami 访问统计与站点数据</em> </span> <small>umami.biss.click</small> </a> </div> <div class="control-console-info" aria-label="站点信息"> <section> <span>当前站点</span> <strong>blog.biss.click</strong> </section> <section> <span>站点作者</span> <strong>biss</strong> </section> <section> <span>运行起点</span> <strong>2025-01-01</strong> </section> </div> </section> </div> <main> <section class="hero home-hero"> <div class="hero-copy"> <p class="hero-kicker"><span>FIELD NOTES</span><span>2025—NOW</span></p> <h1><span>biss</span><em>/log</em></h1> <p class="hero-intro">记录代码跑通之前,也记录它为什么没跑通。</p> <p class="hero-route">OpenCV · OpenGL · 自托管服务 · 没打算归类的日常</p> </div> <aside id="bber-talk" class=Line truncated
|
||||
上一页
|
||||
</span><div class="pagination-pages" aria-label="第 1 页,共 3 页"><span class="pagination-number is-current" aria-current="page">1</span><a class="pagination-number" href="/page/2/">2</a><a class="pagination-number" href="/page/3/">3</a></div><a class="pagination-link pagination-next" href="/page/2/" aria-label="下一页">
|
||||
下一页
|
||||
</a></nav> </div> <aside class="home-sidebar" aria-label="首页侧边栏"> <section class="sidebar-card profile-card"> <div class="profile-identity"> <img class="profile-avatar" src="https://free.picui.cn/free/2025/08/10/689845496a283.png" alt="biss" loading="lazy"> <div> <span>MAINTAINER</span> <strong class="profile-name">biss</strong> </div> </div> <p class="profile-bio">在代码、图形和自托管服务之间做长期记录。</p> <div class="profile-stats"> <a href="/archives/"> <span>文章</span> <strong>44</strong> </a> <a href="/tags/"> <span>标签</span> <strong>20</strong> </a> <a href="/categories/"> <span>分类</span> <strong>5</strong> </a> </div> <a class="follow-button" href="https://github.com/bishshi" target="_blank" rel="noreferrer"> <span>查看 GitHub</span> <span aria-hidden="true">↗</span> </a> <div class="profile-links" aria-label="联系方式"> <a href="https://github.com/bishshi" target="_blank" rel="noreferrer" aria-label="GitHub">GitHub</a> <a href="mailto:bishsh2006@gmail.com" aria-label="Email">Email</a> </div> </section> <div class="sticky-sidebar"> <section class="sidebar-card notice-card"> <h2><span aria-hidden="true">●</span> 站点广播</h2> <div id="welcome-ip-location-info">
|
||||
欢迎来自 <strong>远方</strong> 的小友。<br>
|
||||
烧烤三件套,灵魂蘸料。<br>
|
||||
当前位置距离博主约 <strong>473</strong> 公里!
|
||||
</div> <p id="aside-weather">当前天气加载中...</p> </section> <section class="sidebar-card calendar-card"> <h2>日历</h2> <div class="calendar-widget"> <div class="calendar-summary"> <strong id="calendar-week">第--周 周--</strong> <span id="calendar-date">--</span> <small id="calendar-solar">----年--月 第--天</small> <small id="calendar-lunar">今天也很好</small> </div> <div class="calendar-grid" id="calendar-grid" aria-label="本月日历"></div> </div> </section> <section class="sidebar-card schedule-card"> <h2>剩余时间</h2> <div class="schedule-widget"> <div class="schedule-left"> <strong>距离除夕</strong> <span id="new-year-days">--</span> <small id="new-year-date">---- -- --</small> </div> <div class="schedule-bars"> <div class="schedule-row"> <span>本年</span> <progress id="year-progress" max="100" value="0"></progress> <em id="year-left">--天</em> </div> <div class="schedule-row"> <span>本月</span> <progress id="month-progress" max="100" value="0"></progress> <em id="month-left">--天</em> </div> <div class="schedule-row"> <span>本周</span> <progress id="week-progress" max="100" value="0"></progress> <em id="week-left">--天</em> </div> </div> </div> </section> <section class="sidebar-card site-info-card" data-site-info-card data-stats-endpoint="https://blogumami.biss.click"> <h2><i class="fa-solid fa-chart-line" aria-hidden="true"></i> 网站信息</h2> <dl class="site-info-list"> <div> <dt>文章数目</dt> <dd>44</dd> </div><div> <dt>本站总字数</dt> <dd>36,128</dd> </div><div> <dt>本站访客数</dt> <dd data-stat-key="visitors">--</dd> </div><div> <dt>本站访问量</dt> <dd data-stat-key="pageviews">--</dd> </div><div> <dt>最后更新时间</dt> <dd>2026/06/20</dd> </div> </dl> </section> <script>
|
||||
(() => {
|
||||
const cacheDuration = 10 * 60 * 1000;
|
||||
const formatter = new Intl.NumberFormat('en-US');
|
||||
const cards = [...document.querySelectorAll('[data-site-info-card]')];
|
||||
const endpoint = cards.find((card) => card.dataset.statsEndpoint)?.dataset.statsEndpoint;
|
||||
|
||||
if (!endpoint) return;
|
||||
|
||||
const cacheKey = `site-info-stats:${endpoint}`;
|
||||
|
||||
const updateCards = (stats) => {
|
||||
const visitors = Number(stats?.visitors);
|
||||
const pageviews = Number(stats?.pageviews);
|
||||
|
||||
cards.forEach((card) => {
|
||||
const visitorsEl = card.querySelector('[data-stat-key="visitors"]');
|
||||
const pageviewsEl = card.querySelector('[data-stat-key="pageviews"]');
|
||||
if (visitorsEl && Number.isFinite(visitors)) visitorsEl.textContent = formatter.format(visitors);
|
||||
if (pageviewsEl && Number.isFinite(pageviews)) pageviewsEl.textContent = formatter.format(pageviews);
|
||||
});
|
||||
};
|
||||
|
||||
try {
|
||||
const cached = JSON.parse(localStorage.getItem(cacheKey) || 'null');
|
||||
if (cached?.time && Date.now() - cached.time < cacheDuration) {
|
||||
updateCards(cached.data);
|
||||
return;
|
||||
}
|
||||
} catch {
|
||||
localStorage.removeItem(cacheKey);
|
||||
}
|
||||
|
||||
fetch(endpoint, { headers: { Accept: 'application/json' } })
|
||||
.then((response) => {
|
||||
if (!response.ok) throw new Error(`Stats request failed: ${response.status}`);
|
||||
return response.json();
|
||||
})
|
||||
.then((data) => {
|
||||
localStorage.setItem(cacheKey, JSON.stringify({ time: Date.now(), data }));
|
||||
updateCards(data);
|
||||
})
|
||||
.catch(() => {
|
||||
});
|
||||
})();
|
||||
</script> </div> </aside> <script>
|
||||
(() => {
|
||||
const dayNames = ['周日', '周一', '周二', '周三', '周四', '周五', '周六'];
|
||||
const pad = (value) => String(value).padStart(2, '0');
|
||||
const dayMs = 86400000;
|
||||
const percent = (used, total) => Math.min(100, Math.max(0, (used / total) * 100));
|
||||
const lunarFormatter = new Intl.DateTimeFormat('zh-CN-u-ca-chinese', {
|
||||
year: 'numeric',
|
||||
month: 'long',
|
||||
day: 'numeric',
|
||||
});
|
||||
const solarFestivals = {
|
||||
'01-01': '元旦',
|
||||
'03-08': '妇女节',
|
||||
'03-12': '植树节',
|
||||
'05-01': '劳动节',
|
||||
'05-04': '青年节',
|
||||
'06-01': '儿童节',
|
||||
'07-01': '建党节',
|
||||
'08-01': '建军节',
|
||||
'09-10': '教师节',
|
||||
'10-01': '国庆节',
|
||||
};
|
||||
const lunarFestivals = {
|
||||
'正月-1': '春节',
|
||||
'正月-15': '元宵',
|
||||
'五月-5': '端午',
|
||||
'七月-7': '七夕',
|
||||
'七月-15': '中元',
|
||||
'八月-15': '中秋',
|
||||
'九月-9': '重阳',
|
||||
'腊月-8': '腊八',
|
||||
'腊月-23': '北方小年',
|
||||
'腊月-24': '南方小年',
|
||||
};
|
||||
const springFestivalDates = [
|
||||
'2024-02-10',
|
||||
'2025-01-29',
|
||||
'2026-02-17',
|
||||
'2027-02-06',
|
||||
'2028-01-26',
|
||||
'2029-02-13',
|
||||
'2030-02-03',
|
||||
'2031-01-23',
|
||||
'2032-02-11',
|
||||
];
|
||||
const setText = (id, value) => {
|
||||
const el = document.getElementById(id);
|
||||
if (el) el.textContent = value;
|
||||
};
|
||||
const toDateKey = (date) => `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}`;
|
||||
const toMonthDayKey = (date) => `${pad(date.getMonth() + 1)}-${pad(date.getDate())}`;
|
||||
const escapeHtml = (value) =>
|
||||
String(value).replace(/[&<>"']/g, (char) => ({
|
||||
'&': '&',
|
||||
'<': '<',
|
||||
'>': '>',
|
||||
'"': '"',
|
||||
"'": ''',
|
||||
})[char]);
|
||||
|
||||
function dayOfYear(date) {
|
||||
const start = new Date(date.getFullYear(), 0, 1);
|
||||
return Math.floor((date - start) / 86400000) + 1;
|
||||
}
|
||||
|
||||
function weekOfYear(date) {
|
||||
const firstDay = new Date(date.getFullYear(), 0, 1);
|
||||
const pastDays = Math.floor((date - firstDay) / dayMs);
|
||||
return Math.ceil((pastDays + firstDay.getDay() + 1) / 7);
|
||||
}
|
||||
|
||||
function getLunarInfo(date) {
|
||||
const parts = lunarFormatter.formatToParts(date);
|
||||
const month = parts.find((part) => part.type === 'month')?.value || '';
|
||||
const day = parts.find((part) => part.type === 'day')?.value || '';
|
||||
const yearName = parts.find((part) => part.type === 'yearName')?.value || '';
|
||||
return {
|
||||
yearName,
|
||||
month,
|
||||
day,
|
||||
dayName: day === '1' ? month : `${day}日`,
|
||||
festival: lunarFestivals[`${month}-${day}`] || '',
|
||||
};
|
||||
}
|
||||
|
||||
function getFestival(date) {
|
||||
const lunar = getLunarInfo(date);
|
||||
const solar = solarFestivals[toMonthDayKey(date)] || '';
|
||||
return solar || lunar.festival;
|
||||
}
|
||||
|
||||
function getNextNewYearEve(now) {
|
||||
const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
|
||||
const nextSpring = springFestivalDates
|
||||
.map((date) => new Date(`${date}T00:00:00`))
|
||||
.find((date) => date > today);
|
||||
|
||||
if (!nextSpring) {
|
||||
return new Date(now.getFullYear() + 1, 1, 16);
|
||||
}
|
||||
|
||||
return new Date(nextSpring.getFullYear(), nextSpring.getMonth(), nextSpring.getDate() - 1);
|
||||
}
|
||||
|
||||
function renderCalendar(now) {
|
||||
const todayLunar = getLunarInfo(now);
|
||||
const todayFestival = getFestival(now);
|
||||
setText('calendar-week', `第${weekOfYear(now)}周 ${dayNames[now.getDay()]}`);
|
||||
setText('calendar-date', pad(now.getDate()));
|
||||
setText('calendar-solar', `${now.getFullYear()}年${now.getMonth() + 1}月 第${dayOfYear(now)}天`);
|
||||
setText(
|
||||
'calendar-lunar',
|
||||
todayFestival
|
||||
? `${todayLunar.yearName}年 ${todayFestival}`
|
||||
: `${todayLunar.yearName}年 农历${todayLunar.month}${todayLunar.day}日`
|
||||
);
|
||||
|
||||
const grid = document.getElementById('calendar-grid');
|
||||
if (!grid) return;
|
||||
|
||||
const year = now.getFullYear();
|
||||
const month = now.getMonth();
|
||||
const first = new Date(year, month, 1).getDay();
|
||||
const days = new Date(year, month + 1, 0).getDate();
|
||||
const previousDays = new Date(year, month, 0).getDate();
|
||||
const cells = [];
|
||||
|
||||
for (let i = 0; i < 42; i += 1) {
|
||||
const dayNumber = i - first + 1;
|
||||
const isCurrent = dayNumber >= 1 && dayNumber <= days;
|
||||
const displayDay = isCurrent
|
||||
? dayNumber
|
||||
: dayNumber < 1
|
||||
? previousDays + dayNumber
|
||||
: dayNumber - days;
|
||||
const cellDate = new Date(year, month, dayNumber);
|
||||
const lunar = getLunarInfo(cellDate);
|
||||
const isToday = isCurrent && displayDay === now.getDate();
|
||||
const title = `${toDateKey(cellDate)} 农历${lunar.month}${lunar.day}日`;
|
||||
cells.push(`
|
||||
<span
|
||||
class="${isCurrent ? '' : 'muted'} ${isToday ? 'today' : ''}"
|
||||
title="${escapeHtml(title)}"
|
||||
aria-label="${escapeHtml(title)}"
|
||||
>
|
||||
<b>${displayDay}</b>
|
||||
</span>
|
||||
`);
|
||||
}
|
||||
|
||||
grid.innerHTML = cells.join('');
|
||||
}
|
||||
|
||||
function renderSchedule(now) {
|
||||
const year = now.getFullYear();
|
||||
const nextEve = getNextNewYearEve(now);
|
||||
const daysToEve = Math.max(0, Math.ceil((nextEve - now) / dayMs));
|
||||
const yearStart = new Date(year, 0, 1);
|
||||
const yearEnd = new Date(year + 1, 0, 1);
|
||||
const monthStart = new Date(year, now.getMonth(), 1);
|
||||
const monthEnd = new Date(year, now.getMonth() + 1, 1);
|
||||
const weekStart = new Date(year, now.getMonth(), now.getDate() - ((now.getDay() + 6) % 7));
|
||||
const weekEnd = new Date(weekStart.getFullYear(), weekStart.getMonth(), weekStart.getDate() + 7);
|
||||
|
||||
setText('new-year-days', String(daysToEve));
|
||||
setText('new-year-date', `${nextEve.getFullYear()}-${pad(nextEve.getMonth() + 1)}-${pad(nextEve.getDate())}`);
|
||||
|
||||
const rows = [
|
||||
['year', yearStart, yearEnd],
|
||||
['month', monthStart, monthEnd],
|
||||
['week', weekStart, weekEnd],
|
||||
];
|
||||
|
||||
rows.forEach(([name, start, end]) => {
|
||||
const total = end - start;
|
||||
const used = now - start;
|
||||
const progressValue = percent(used, total);
|
||||
const left = Math.max(0, Math.ceil((end - now) / dayMs));
|
||||
const bar = document.getElementById(`${name}-progress`);
|
||||
if (bar) {
|
||||
bar.value = progressValue;
|
||||
bar.title = `已过 ${progressValue.toFixed(1)}%`;
|
||||
bar.setAttribute('aria-valuetext', `已过 ${progressValue.toFixed(1)}%`);
|
||||
}
|
||||
setText(`${name}-left`, `${progressValue.toFixed(0)}% / ${left}天`);
|
||||
});
|
||||
}
|
||||
|
||||
function initSidebar() {
|
||||
const now = new Date();
|
||||
renderCalendar(now);
|
||||
renderSchedule(now);
|
||||
}
|
||||
|
||||
initSidebar();
|
||||
})();
|
||||
</script> <script src="https://code.jquery.com/jquery-4.0.0.min.js"></script> <script src="https://cdn.jsdmirror.com/gh/bishshi/welcomemessage/txmap.js"></script> <script src="/js/weather.js"></script> </section> <script src="/js/shuoshuoshouye.js"></script> </main> <footer class="site-footer" aria-label="站点页脚"> <div class="footer-inner"> <section class="footer-top" aria-label="站点信息"> <a class="footer-brand" href="/" aria-label="Bi's Blog 首页"> <img src="/images/Bi.ico" alt="" loading="lazy"> <span>Bi's Blog</span> </a> <p class="footer-slogan">好奇心转化为代码的空间。</p> </section> <section class="footer-contact" aria-label="联系方式"> <p>联系方式</p> <div class="footer-socials"> <a href="mailto:bishsh2006@gmail.com" aria-label="发送邮件" title="发送邮件"> <i class="fa-solid fa-envelope" aria-hidden="true"></i> </a><a href="/rss.xml" aria-label="订阅 RSS" title="订阅 RSS"> <i class="fa-solid fa-rss" aria-hidden="true"></i> </a><a href="https://github.com/bishshi" target="_blank" rel="noreferrer" aria-label="GitHub" title="GitHub"> <i class="fa-brands fa-github" aria-hidden="true"></i> </a> </div> <nav class="footer-contact-links" aria-label="页脚链接"> <a href="/archives/">发展历程</a> <span aria-hidden="true">|</span><a href="/privacy/">隐私政策</a> </nav> </section> <section class="footer-bottom" aria-label="版权信息"> <div> <p>© 2025 - 2026 Bi's Blog</p> <p>blog.biss.click | 内容采用 CC BY-NC-SA 4.0 协议</p> </div> <div class="footer-meta"> <p>本站已运行:<span id="site-runtime">计算中...</span></p> <p> <a href="https://fastly.com/" target="_blank" rel="noreferrer">Fastly</a> <span aria-hidden="true">|</span><a href="https://astro.build/" target="_blank" rel="noreferrer">ASTRO</a> </p> </div> </section> </div> </footer> <aside class="hitokoto-card is-collapsed" aria-label="一言" data-hitokoto-card data-astro-cid-4cgfuthx> <div class="hitokoto-card__head" data-astro-cid-4cgfuthx> <button class="hitokoto-card__toggle" type="button" aria-label="展开一言" aria-expanded="false" title="一言" data-hitokoto-toggle data-astro-cid-4cgfuthx> <i class="fa-solid fa-quote-left" aria-hidden="true" data-astro-cid-4cgfuthx></i> <span data-astro-cid-4cgfuthx>一言</span> </button> <div class="hitokoto-card__actions" data-astro-cid-4cgfuthx> <button class="hitokoto-card__refresh" type="button" aria-label="换一句" title="换一句" data-hitokoto-refresh data-astro-cid-4cgfuthx> <i class="fa-solid fa-rotate" aria-hidden="true" data-astro-cid-4cgfuthx></i> </button> <button class="hitokoto-card__collapse" type="button" aria-label="收起一言" title="收起" data-hitokoto-collapse data-astro-cid-4cgfuthx> <i class="fa-solid fa-chevron-down" aria-hidden="true" data-astro-cid-4cgfuthx></i> </button> </div> </div> <a class="hitokoto-card__text" href="https://hitokoto.cn/" target="_blank" rel="noreferrer" data-hitokoto-text data-astro-cid-4cgfuthx>
|
||||
获取中...
|
||||
</a> <p class="hitokoto-card__from" data-hitokoto-from data-astro-cid-4cgfuthx>来自 Hitokoto</p> </aside> <script type="module">(()=>{const o=document.querySelector("[data-hitokoto-card]"),c=o?.querySelector("[data-hitokoto-toggle]"),s=o?.querySelector("[data-hitokoto-text]"),l=o?.querySelector("[data-hitokoto-from]"),g=o?.querySelector("[data-hitokoto-refresh]"),k=o?.querySelector("[data-hitokoto-collapse]");if(!(o instanceof HTMLElement)||!(c instanceof HTMLButtonElement)||!(s instanceof HTMLAnchorElement)||!(l instanceof HTMLElement))return;const y="https://yiyan.biss.click/api?encode=json",u="hitokoto-card-cache",d="hitokoto-card-expanded",x=360*60*1e3,f={sentence:"生活明朗,万物可爱。",source:"本地缓存",url:"https://hitokoto.cn/"},S=["/","/index.html"].includes(window.location.pathname),w=window.matchMedia("(max-width: 720px)");function E(){try{const t=localStorage.getItem(d);if(t==="true")return!0;if(t==="false")return!1}catch{}return null}function L(t){try{localStorage.setItem(d,String(t))}catch{}}function r(t,{persist:e=!1}={}){o.classList.toggle("is-collapsed",!t),c.setAttribute("aria-expanded",String(t)),c.setAttribute("aria-label",t?"收起一言":"展开一言"),e&&L(t)}function h(){try{const t=localStorage.getItem(u);if(!t)return null;const e=JSON.parse(t);return!e||typeof e.expiresAt!="number"||e.expiresAt<=Date.now()?null:e.data}catch{return null}}function H(t){try{localStorage.setItem(u,JSON.stringify({data:t,expiresAt:Date.now()+x}))}catch{}}function i(t){s.textContent=t.sentence,s.href=t.url||"https://hitokoto.cn/",l.textContent=t.source?`出自 ${t.source}`:"来自 Hitokoto"}async function p({force:t=!1}={}){if(!t){const e=h();if(e){i(e);return}}o.classList.add("is-loading"),s.textContent="获取中...";try{const e=await fetch(y);if(!e.ok)throw new Error(`Hitokoto request failed: ${e.status}`);const n=await e.json(),a=n.hitokoto||n.content||n.sentence||n.text,q=typeof a=="string"&&a.trim()?a.trim():f.sentence,A=typeof n.from=="string"&&n.from.trim()?n.from.trim():"Hitokoto",M=n.uuid?`https://hitokoto.cn/?uuid=${encodeURIComponent(n.uuid)}`:"https://hitokoto.cn/",m={sentence:q,source:A,url:M};H(m),i(m)}catch(e){console.error(e),i(h()||f)}finally{o.classList.remove("is-loading")}}const b=E();r(b??(S&&!w.matches)),c.addEventListener("click",()=>{r(o.classList.contains("is-collapsed"),{persist:!0})}),g?.addEventListener("click",t=>{t.stopPropagation(),r(!0,{persist:!0}),p({force:!0})}),k?.addEventListener("click",t=>{t.stopPropagation(),r(!1,{persist:!0})}),o.addEventListener("click",t=>{o.classList.contains("is-collapsed")&&(t.target instanceof Element&&t.target.closest("a, button")||r(!0,{persist:!0}))}),p()})();</script> <div class="custom-context-menu" data-context-menu role="menu" aria-hidden="true"> <div class="context-menu-nav" aria-label="页面导航"> <button type="button" data-context-action="back" aria-label="后退上一页" title="后退上一页"> <i class="fa-solid fa-arrow-left" aria-hidden="true"></i> </button> <button type="button" data-context-action="forward" aria-label="前进下一页" title="前进下一页"> <i class="fa-solid fa-arrow-right" aria-hidden="true"></i> </button> <button type="button" data-context-action="reload" aria-label="刷新页面" title="刷新页面"> <i class="fa-solid fa-rotate-right" aria-hidden="true"></i> </button> <a href="/" aria-label="返回首页" title="返回首页"> <i class="fa-solid fa-house" aria-hidden="true"></i> </a> </div> <div class="context-menu-group" data-selection-group hidden> <button type="button" data-context-action="copy-selection" data-selection-item> <i class="fa-regular fa-copy" aria-hidden="true"></i> <span>复制选中文字</span> </button> <button type="button" data-context-action="search-selection" data-selection-item> <i class="fa-brands fa-microsoft" aria-hidden="true"></i> <span>用 Bing 搜索</span> </button> <button type="button" data-context-action="quote-selection" data-selection-item data-comments-item> <i class="fa-regular fa-comment-dots" aria-hidden="true"></i> <span>引用到评论区</span> </button> </div> <div class="context-menu-group" data-image-group hidden> <button type="button" data-context-action="copy-image"> <i class="fa-regular fa-image" aria-hidden="true"></i> <span>复制图片</span> </button> <button type="button" data-context-action="open-image"> <i class="fa-solid fa-up-right-from-square" aria-hidden="true"></i> <span>新页面打开图片</span> </button> <button type="button" data-context-action="copy-image-url"> <i class="fa-solid fa-link" aria-hidden="true"></i> <span>复制图片地址</span> </button> </div> <div class="context-menu-group"> <a href="/archives/"> <i class="fa-solid fa-box-archive" aria-hidden="true"></i> <span>时间线</span> </a> <a href="/categories/"> <i class="fa-solid fa-folder" aria-hidden="true"></i> <span>分类</span> </a> <a href="/tags/"> <i class="fa-solid fa-hashtag" aria-hidden="true"></i> <span>标签</span> </a> <button typeLine truncated
|
||||
</div> <script>
|
||||
(() => {
|
||||
const menu = document.querySelector('[data-context-menu]');
|
||||
if (!menu) return;
|
||||
|
||||
const nativeHint = document.querySelector('[data-context-native-hint]');
|
||||
const selectionItems = [...menu.querySelectorAll('[data-selection-item]')];
|
||||
const selectionGroup = menu.querySelector('[data-selection-group]');
|
||||
const commentsItems = [...menu.querySelectorAll('[data-comments-item]')];
|
||||
const imageGroup = menu.querySelector('[data-image-group]');
|
||||
const focusableSelector = 'a[href], button:not([disabled])';
|
||||
let lastSelectionText = '';
|
||||
let lastImage = null;
|
||||
let nativeHintTimer = 0;
|
||||
|
||||
const isNativeContextTarget = (target) =>
|
||||
target instanceof Element &&
|
||||
Boolean(target.closest('input, textarea, select, [contenteditable="true"], [contenteditable=""]'));
|
||||
|
||||
const getCommentsTarget = () =>
|
||||
document.querySelector('.twikoo-comments, #twikoo-wrap, #twikoo, .tk-comments-container');
|
||||
|
||||
const getSelectionText = () => String(window.getSelection?.() || '').trim();
|
||||
|
||||
const getImageTarget = (target) => {
|
||||
if (!(target instanceof Element)) return null;
|
||||
const image = target.closest('img');
|
||||
return image instanceof HTMLImageElement ? image : null;
|
||||
};
|
||||
|
||||
const getImageUrl = () => {
|
||||
if (!lastImage) return '';
|
||||
return lastImage.currentSrc || lastImage.src || lastImage.getAttribute('src') || '';
|
||||
};
|
||||
|
||||
const copyText = async (text) => {
|
||||
if (navigator.clipboard?.writeText) {
|
||||
await navigator.clipboard.writeText(text);
|
||||
return;
|
||||
}
|
||||
|
||||
const textarea = document.createElement('textarea');
|
||||
textarea.value = text;
|
||||
textarea.setAttribute('readonly', '');
|
||||
textarea.style.position = 'fixed';
|
||||
textarea.style.inset = '0 auto auto -9999px';
|
||||
document.body.appendChild(textarea);
|
||||
textarea.select();
|
||||
document.execCommand('copy');
|
||||
textarea.remove();
|
||||
};
|
||||
|
||||
const setMenuState = () => {
|
||||
const hasSelection = lastSelectionText.length > 0;
|
||||
const hasComments = Boolean(getCommentsTarget());
|
||||
const hasImage = Boolean(getImageUrl());
|
||||
|
||||
selectionItems.forEach((item) => {
|
||||
item.hidden = !hasSelection;
|
||||
});
|
||||
if (selectionGroup) selectionGroup.hidden = !hasSelection;
|
||||
|
||||
commentsItems.forEach((item) => {
|
||||
item.hidden = !hasComments || (item.hasAttribute('data-selection-item') && !hasSelection);
|
||||
});
|
||||
|
||||
if (imageGroup) imageGroup.hidden = !hasImage;
|
||||
};
|
||||
|
||||
const closeMenu = () => {
|
||||
menu.classList.remove('is-open');
|
||||
menu.setAttribute('aria-hidden', 'true');
|
||||
};
|
||||
|
||||
const openMenu = (x, y) => {
|
||||
setMenuState();
|
||||
menu.classList.add('is-open');
|
||||
menu.setAttribute('aria-hidden', 'false');
|
||||
|
||||
const rect = menu.getBoundingClientRect();
|
||||
const gap = 10;
|
||||
const left = Math.min(Math.max(gap, x), window.innerWidth - rect.width - gap);
|
||||
const top = Math.min(Math.max(gap, y), window.innerHeight - rect.height - gap);
|
||||
|
||||
menu.style.left = `${left}px`;
|
||||
menu.style.top = `${top}px`;
|
||||
};
|
||||
|
||||
const showNativeHint = () => {
|
||||
if (!nativeHint) return;
|
||||
window.clearTimeout(nativeHintTimer);
|
||||
nativeHint.classList.add('is-visible');
|
||||
nativeHintTimer = window.setTimeout(() => {
|
||||
nativeHint.classList.remove('is-visible');
|
||||
}, 2600);
|
||||
};
|
||||
|
||||
const jumpToComments = () => {
|
||||
const target = getCommentsTarget();
|
||||
target?.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
||||
};
|
||||
|
||||
const quoteToComments = () => {
|
||||
jumpToComments();
|
||||
window.setTimeout(() => {
|
||||
const textarea = document.querySelector('#twikoo textarea, .twikoo-comments textarea, .tk-input textarea');
|
||||
if (!(textarea instanceof HTMLTextAreaElement) || !lastSelectionText) return;
|
||||
|
||||
const quote = lastSelectionText
|
||||
.split(/\r?\n/)
|
||||
.map((line) => `> ${line}`)
|
||||
.join('\n');
|
||||
const current = textarea.value.trim();
|
||||
textarea.value = current ? `${current}\n\n${quote}\n` : `${quote}\n`;
|
||||
textarea.dispatchEvent(new Event('input', { bubbles: true }));
|
||||
textarea.focus();
|
||||
}, 500);
|
||||
};
|
||||
|
||||
const searchSelection = () => {
|
||||
if (!lastSelectionText) return;
|
||||
const url = `https://www.bing.com/search?q=${encodeURIComponent(lastSelectionText)}`;
|
||||
window.open(url, '_blank', 'noopener,noreferrer');
|
||||
};
|
||||
|
||||
const copyImage = async () => {
|
||||
const imageUrl = getImageUrl();
|
||||
if (!imageUrl) return;
|
||||
|
||||
try {
|
||||
if (!navigator.clipboard?.write || typeof ClipboardItem === 'undefined') {
|
||||
throw new Error('Image clipboard is not available.');
|
||||
}
|
||||
|
||||
const response = await fetch(imageUrl, { mode: 'cors' });
|
||||
const blob = await response.blob();
|
||||
await navigator.clipboard.write([
|
||||
new ClipboardItem({ [blob.type || 'image/png']: blob }),
|
||||
]);
|
||||
} catch {
|
||||
await copyText(new URL(imageUrl, window.location.href).toString());
|
||||
}
|
||||
};
|
||||
|
||||
const openImage = () => {
|
||||
const imageUrl = getImageUrl();
|
||||
if (!imageUrl) return;
|
||||
window.open(new URL(imageUrl, window.location.href).toString(), '_blank', 'noopener,noreferrer');
|
||||
};
|
||||
|
||||
document.addEventListener('contextmenu', (event) => {
|
||||
if (event.ctrlKey) {
|
||||
closeMenu();
|
||||
nativeHint?.classList.remove('is-visible');
|
||||
return;
|
||||
}
|
||||
|
||||
if (isNativeContextTarget(event.target)) return;
|
||||
|
||||
lastSelectionText = getSelectionText();
|
||||
lastImage = getImageTarget(event.target);
|
||||
event.preventDefault();
|
||||
openMenu(event.clientX, event.clientY);
|
||||
showNativeHint();
|
||||
});
|
||||
|
||||
document.addEventListener('click', (event) => {
|
||||
const actionTarget = event.target instanceof Element ? event.target.closest('[data-context-action]') : null;
|
||||
if (!actionTarget || !menu.contains(actionTarget)) {
|
||||
closeMenu();
|
||||
return;
|
||||
}
|
||||
|
||||
const action = actionTarget.dataset.contextAction;
|
||||
closeMenu();
|
||||
|
||||
if (action === 'back') history.back();
|
||||
if (action === 'forward') history.forward();
|
||||
if (action === 'reload') location.reload();
|
||||
if (action === 'copy-selection' && lastSelectionText) copyText(lastSelectionText);
|
||||
if (action === 'search-selection') searchSelection();
|
||||
if (action === 'quote-selection') quoteToComments();
|
||||
if (action === 'copy-image') copyImage();
|
||||
if (action === 'open-image') openImage();
|
||||
if (action === 'copy-image-url') copyText(new URL(getImageUrl(), window.location.href).toString());
|
||||
if (action === 'random-post' && typeof window.randomPost === 'function') window.randomPost(actionTarget);
|
||||
if (action === 'comments') jumpToComments();
|
||||
if (action === 'print') window.print();
|
||||
});
|
||||
|
||||
document.addEventListener('keydown', (event) => {
|
||||
if (event.key === 'Escape') closeMenu();
|
||||
if (event.key === 'Tab' && menu.classList.contains('is-open')) {
|
||||
const items = [...menu.querySelectorAll(focusableSelector)].filter((item) => !item.closest('[hidden]'));
|
||||
if (items.length === 0) return;
|
||||
const first = items[0];
|
||||
const last = items[items.length - 1];
|
||||
|
||||
if (event.shiftKey && document.activeElement === first) {
|
||||
event.preventDefault();
|
||||
last.focus();
|
||||
} else if (!event.shiftKey && document.activeElement === last) {
|
||||
event.preventDefault();
|
||||
first.focus();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
window.addEventListener('scroll', closeMenu, { passive: true });
|
||||
window.addEventListener('resize', closeMenu);
|
||||
})();
|
||||
</script> <aside class="pwa-toast" data-pwa-toast hidden role="status" aria-live="polite"> <div class="pwa-toast-icon" aria-hidden="true"><i class="fa-solid fa-download"></i></div> <div class="pwa-toast-copy"> <strong data-pwa-title>PWA 提示</strong> <p data-pwa-message></p> </div> <div class="pwa-toast-actions"> <button class="pwa-toast-primary" type="button" data-pwa-primary>确定</button> <button class="pwa-toast-close" type="button" data-pwa-close aria-label="关闭提示">稍后</button> </div> </aside> <script src="https://cdn.jsdmirror.com/npm/instantsearch.js@4.56.0"></script> <script src="https://cdn.jsdmirror.com/npm/typesense-instantsearch-adapter@2.7.0/dist/typesense-instantsearch-adapter.min.js"></script> <script src="https://cdn.jsdmirror.com/npm/@fancyapps/ui@5.0/dist/fancybox/fancybox.umd.js"></script> <script src="/js/random.js"></script> <script src="/js/search/typesense-search.js"></script> <script src="/js/external-links.js"></script> <script src="/js/pwa.js"></script> <script>
|
||||
(() => {
|
||||
const nav = document.querySelector('.nav');
|
||||
const toggle = document.querySelector('[data-nav-toggle]');
|
||||
const menu = document.querySelector('[data-nav-menu]');
|
||||
const backdrop = document.querySelector('[data-nav-backdrop]');
|
||||
if (!nav || !toggle || !menu) return;
|
||||
|
||||
function setMenu(open) {
|
||||
nav.classList.toggle('is-open', open);
|
||||
toggle.setAttribute('aria-expanded', String(open));
|
||||
toggle.setAttribute('aria-label', open ? '关闭菜单' : '打开菜单');
|
||||
menu.setAttribute('aria-hidden', String(!open && window.innerWidth <= 720));
|
||||
document.body.classList.toggle('has-nav-drawer', open);
|
||||
}
|
||||
|
||||
toggle.addEventListener('click', () => {
|
||||
setMenu(!nav.classList.contains('is-open'));
|
||||
});
|
||||
|
||||
backdrop?.addEventListener('click', () => setMenu(false));
|
||||
|
||||
menu.addEventListener('click', (event) => {
|
||||
if (event.target instanceof Element && event.target.closest('a')) setMenu(false);
|
||||
});
|
||||
|
||||
document.addEventListener('click', (event) => {
|
||||
if (!nav.classList.contains('is-open')) return;
|
||||
if (event.target instanceof Node && nav.contains(event.target)) return;
|
||||
setMenu(false);
|
||||
});
|
||||
|
||||
document.addEventListener('keydown', (event) => {
|
||||
if (event.key === 'Escape' && nav.classList.contains('is-open')) setMenu(false);
|
||||
});
|
||||
|
||||
window.addEventListener('resize', () => {
|
||||
if (window.innerWidth > 720) setMenu(false);
|
||||
else menu.setAttribute('aria-hidden', String(!nav.classList.contains('is-open')));
|
||||
});
|
||||
|
||||
menu.setAttribute('aria-hidden', String(window.innerWidth <= 720));
|
||||
})();
|
||||
|
||||
(() => {
|
||||
const modal = document.querySelector('[data-console-modal]');
|
||||
const panel = modal?.querySelector('.control-console-panel');
|
||||
const openButtons = document.querySelectorAll('[data-console-open]');
|
||||
const closeButtons = modal?.querySelectorAll('[data-console-close]') || [];
|
||||
if (!modal || !panel || openButtons.length === 0) return;
|
||||
|
||||
function setConsole(open) {
|
||||
modal.classList.toggle('is-open', open);
|
||||
modal.setAttribute('aria-hidden', String(!open));
|
||||
document.body.classList.toggle('has-control-console', open);
|
||||
if (open) panel.focus();
|
||||
}
|
||||
|
||||
openButtons.forEach((button) => {
|
||||
button.addEventListener('click', () => setConsole(true));
|
||||
});
|
||||
|
||||
closeButtons.forEach((button) => {
|
||||
button.addEventListener('click', () => setConsole(false));
|
||||
});
|
||||
|
||||
document.addEventListener('keydown', (event) => {
|
||||
if (event.key === 'Escape' && modal.classList.contains('is-open')) setConsole(false);
|
||||
});
|
||||
})();
|
||||
|
||||
(() => {
|
||||
const storageKey = 'site-theme';
|
||||
const root = document.documentElement;
|
||||
const button = document.querySelector('[data-theme-toggle]');
|
||||
const media = window.matchMedia('(prefers-color-scheme: dark)');
|
||||
const getSystemTheme = () => (media.matches ? 'dark' : 'light');
|
||||
const getSavedTheme = () => {
|
||||
try {
|
||||
const theme = localStorage.getItem(storageKey);
|
||||
return theme === 'dark' || theme === 'light' ? theme : null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
const setSavedTheme = (theme) => {
|
||||
try {
|
||||
if (theme === getSystemTheme()) localStorage.removeItem(storageKey);
|
||||
else localStorage.setItem(storageKey, theme);
|
||||
} catch {
|
||||
// localStorage can be unavailable in strict privacy modes.
|
||||
}
|
||||
};
|
||||
const getTheme = () => getSavedTheme() || getSystemTheme();
|
||||
|
||||
function applyTheme(theme) {
|
||||
root.dataset.theme = theme;
|
||||
root.dataset.themeMode = getSavedTheme() ? 'manual' : 'auto';
|
||||
button?.setAttribute('aria-label', theme === 'dark' ? '切换到日间模式' : '切换到夜间模式');
|
||||
button?.setAttribute(
|
||||
'title',
|
||||
getSavedTheme()
|
||||
? (theme === 'dark' ? '切换到日间模式' : '切换到夜间模式')
|
||||
: `跟随系统:${theme === 'dark' ? '夜间' : '日间'}模式`
|
||||
);
|
||||
}
|
||||
|
||||
applyTheme(getTheme());
|
||||
|
||||
button?.addEventListener('click', () => {
|
||||
const nextTheme = root.dataset.theme === 'dark' ? 'light' : 'dark';
|
||||
setSavedTheme(nextTheme);
|
||||
applyTheme(nextTheme);
|
||||
});
|
||||
|
||||
const handleSystemThemeChange = () => {
|
||||
if (!getSavedTheme()) applyTheme(getSystemTheme());
|
||||
};
|
||||
|
||||
if (typeof media.addEventListener === 'function') {
|
||||
media.addEventListener('change', handleSystemThemeChange);
|
||||
} else if (typeof media.addListener === 'function') {
|
||||
media.addListener(handleSystemThemeChange);
|
||||
}
|
||||
})();
|
||||
|
||||
(() => {
|
||||
const runtime = document.getElementById('site-runtime');
|
||||
if (!runtime) return;
|
||||
|
||||
const start = new Date(window.SITE_CONFIG.site.startTime).getTime();
|
||||
const pad = (value) => String(value).padStart(2, '0');
|
||||
|
||||
function updateRuntime() {
|
||||
const diff = Math.max(0, Date.now() - start);
|
||||
const days = Math.floor(diff / 86400000);
|
||||
const hours = Math.floor((diff / 3600000) % 24);
|
||||
const minutes = Math.floor((diff / 60000) % 60);
|
||||
const seconds = Math.floor((diff / 1000) % 60);
|
||||
runtime.textContent = `${days} 天 ${pad(hours)} 时 ${pad(minutes)} 分 ${pad(seconds)} 秒`;
|
||||
}
|
||||
|
||||
updateRuntime();
|
||||
window.setInterval(updateRuntime, 1000);
|
||||
})();
|
||||
</script> </body> </html>
|
||||
Reference in new issue
Block a user