first initial commit

This commit is contained in:
2026-02-07 22:27:31 +08:00
commit 07786f8eb0
18 changed files with 6035 additions and 0 deletions

79
assets/js/carousel.js Normal file
View File

@@ -0,0 +1,79 @@
document.addEventListener('DOMContentLoaded', function() {
const carousel = document.querySelector('.js-carousel');
if (!carousel) return;
const track = carousel.querySelector('.js-carousel-track');
const slides = track.children;
const dots = carousel.querySelectorAll('.js-dot');
const prevBtn = carousel.querySelector('.js-carousel-prev');
const nextBtn = carousel.querySelector('.js-carousel-next');
let current = 0;
const total = slides.length;
let touchStartX = 0;
let touchEndX = 0;
function updateDisplay() {
// 移动轨道
track.style.transform = `translateX(-${current * 100}%)`;
// 更新指示器
dots.forEach((dot, i) => {
if (i === current) {
dot.classList.add('bg-white', 'w-6');
dot.classList.remove('bg-white/50');
} else {
dot.classList.remove('bg-white', 'w-6');
dot.classList.add('bg-white/50');
}
});
}
function next() {
current = (current === total - 1) ? 0 : current + 1;
updateDisplay();
}
function prev() {
current = (current === 0) ? total - 1 : current - 1;
updateDisplay();
}
// 自动轮播
let autoPlay = setInterval(next, 5000);
function resetTimer() {
clearInterval(autoPlay);
autoPlay = setInterval(next, 5000);
}
// 事件监听
if (nextBtn) nextBtn.addEventListener('click', () => { next(); resetTimer(); });
if (prevBtn) prevBtn.addEventListener('click', () => { prev(); resetTimer(); });
dots.forEach(dot => {
dot.addEventListener('click', () => {
current = parseInt(dot.getAttribute('data-index'));
updateDisplay();
resetTimer();
});
});
// --- 触摸滑动逻辑 ---
carousel.addEventListener('touchstart', (e) => {
touchStartX = e.changedTouches[0].screenX;
}, { passive: true });
carousel.addEventListener('touchend', (e) => {
touchEndX = e.changedTouches[0].screenX;
const distance = touchStartX - touchEndX;
const minSwipeDistance = 50;
if (Math.abs(distance) > minSwipeDistance) {
if (distance > 0) next(); else prev();
resetTimer();
}
}, { passive: true });
// 初始化
updateDisplay();
});

150
assets/js/mobile-menu.js Normal file
View File

@@ -0,0 +1,150 @@
(function() {
'use strict';
let drawerElement = null;
let isDrawerOpen = false;
// 创建抽屉 (从隐藏的 div 克隆)
function createDrawer() {
const template = document.getElementById('mobile-drawer-template');
if (!template) {
console.error('Drawer template not found');
return null;
}
const overlay = template.querySelector('#mobile-sidebar-overlay');
if (!overlay) {
console.error('Overlay element not found in template');
return null;
}
const clone = overlay.cloneNode(true);
clone.id = 'mobile-sidebar-overlay-active';
// --- [新增逻辑] 自动搬运侧边栏挂件 ---
const sidebarWidgets = document.getElementById('sidebar-widgets');
const drawerContent = clone.querySelector('.p-5.space-y-6'); // 抽屉的内容容器
if (sidebarWidgets && drawerContent) {
// 创建一个专门存放侧边栏内容的容器
const sidebarMobileContainer = document.createElement('section');
sidebarMobileContainer.className = 'mobile-sidebar-content space-y-6 pt-4 border-t border-gray-100';
sidebarMobileContainer.innerHTML = '<h3 class="text-xs font-bold text-gray-400 uppercase tracking-wider mb-4">实时资讯</h3>';
// 克隆侧边栏所有的子元素(欢迎卡片、计时器等)
const widgetsClone = sidebarWidgets.cloneNode(true);
// 移除原本的 ID 避免冲突
widgetsClone.removeAttribute('id');
// 将侧边栏内容追加到抽屉中
sidebarMobileContainer.appendChild(widgetsClone);
drawerContent.appendChild(sidebarMobileContainer);
}
// ------------------------------------
document.body.appendChild(clone);
return clone;
}
function openDrawer() {
if (isDrawerOpen) return;
if (!drawerElement) {
drawerElement = createDrawer();
if (!drawerElement) return;
bindDrawerEvents();
}
// 锁定背景滚动
const scrollY = window.scrollY;
document.body.style.overflow = 'hidden';
document.body.style.position = 'fixed';
document.body.style.top = `-${scrollY}px`;
document.body.style.width = '100%';
drawerElement.style.display = 'block';
isDrawerOpen = true;
requestAnimationFrame(() => {
requestAnimationFrame(() => {
const backdrop = drawerElement.querySelector('.drawer-backdrop');
const panel = drawerElement.querySelector('.drawer-panel');
if (backdrop) backdrop.style.opacity = '1';
if (panel) panel.style.transform = 'translateX(0)';
});
});
// 如果克隆的内容里包含计时器,需要重新初始化计时器逻辑
if (window.initTimers) window.initTimers(drawerElement);
}
function closeDrawer() {
if (!isDrawerOpen || !drawerElement) return;
const backdrop = drawerElement.querySelector('.drawer-backdrop');
const panel = drawerElement.querySelector('.drawer-panel');
if (backdrop) backdrop.style.opacity = '0';
if (panel) panel.style.transform = 'translateX(100%)';
setTimeout(() => {
if (drawerElement) {
drawerElement.style.display = 'none';
}
// 恢复滚动
const scrollY = document.body.style.top;
document.body.style.overflow = '';
document.body.style.position = '';
document.body.style.top = '';
document.body.style.width = '';
window.scrollTo(0, parseInt(scrollY || '0') * -1);
isDrawerOpen = false;
}, 300);
}
function bindDrawerEvents() {
if (!drawerElement) return;
const closeBtn = drawerElement.querySelector('#drawer-close-btn');
if (closeBtn) {
closeBtn.addEventListener('click', (e) => {
e.preventDefault();
closeDrawer();
});
}
const backdrop = drawerElement.querySelector('.drawer-backdrop');
if (backdrop) {
backdrop.addEventListener('click', closeDrawer);
}
const links = drawerElement.querySelectorAll('.drawer-link, .mobile-nav-grid a, a');
links.forEach(link => {
link.addEventListener('click', () => {
setTimeout(closeDrawer, 100);
});
});
}
document.addEventListener('DOMContentLoaded', () => {
const trigger = document.getElementById('mobile-menu-trigger');
if (trigger) {
trigger.addEventListener('click', (e) => {
e.preventDefault();
e.stopPropagation();
openDrawer();
});
}
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape' && isDrawerOpen) closeDrawer();
});
});
window.mobileMenu = {
open: openDrawer,
close: closeDrawer,
isOpen: () => isDrawerOpen
};
})();

87
assets/js/share.js Normal file
View File

@@ -0,0 +1,87 @@
(function() {
const initShare = () => {
const shareBtn = document.getElementById('share-btn');
const sharePanel = document.getElementById('share-panel');
const copyBtn = document.getElementById('copy-link');
const shareContainer = document.getElementById('share-component');
const closeBtn = document.getElementById('close-share');
if (!shareBtn || !sharePanel) return;
// 1. 处理分享主按钮
shareBtn.addEventListener('click', async (e) => {
e.preventDefault();
e.stopPropagation();
const shareData = {
title: document.title,
url: window.location.href
};
// 原生分享触发条件:浏览器支持 + 必须是 HTTPS 环境
if (navigator.share && window.isSecureContext) {
try {
await navigator.share(shareData);
return; // 成功唤起系统分享,不再向下执行
} catch (err) {
// 如果不是用户主动取消(AbortError),则打印错误并在下方降级处理
if (err.name !== 'AbortError') console.error('Share failed:', err);
}
}
// 降级方案:显示自定义 HTML 分享面板
sharePanel.classList.toggle('hidden');
});
// 2. 处理链接复制
copyBtn.addEventListener('click', (e) => {
e.preventDefault();
const currentUrl = window.location.href;
navigator.clipboard.writeText(currentUrl).then(() => {
const iconBg = document.getElementById('copy-icon-bg');
const copySvg = document.getElementById('copy-svg');
const checkSvg = document.getElementById('check-svg');
const copyText = document.getElementById('copy-text');
// 成功反馈状态
iconBg.classList.replace('bg-slate-100', 'bg-green-500');
iconBg.classList.replace('text-slate-600', 'text-white');
copySvg.classList.add('hidden');
checkSvg.classList.remove('hidden');
copyText.innerText = '已复制';
setTimeout(() => {
iconBg.classList.replace('bg-green-500', 'bg-slate-100');
iconBg.classList.replace('text-white', 'text-slate-600');
copySvg.classList.remove('hidden');
checkSvg.classList.add('hidden');
copyText.innerText = '复制';
}, 2000);
}).catch(() => {
alert('复制失败,请手动复制地址栏链接');
});
});
// 3. 面板交互控制
const closePanel = () => sharePanel.classList.add('hidden');
// 点击关闭按钮隐藏
if (closeBtn) closeBtn.addEventListener('click', closePanel);
// 点击页面其他地方隐藏
document.addEventListener('click', (e) => {
if (!shareContainer.contains(e.target)) closePanel();
});
// 阻止面板内部点击触发隐藏
sharePanel.addEventListener('click', (e) => e.stopPropagation());
};
// 确保 DOM 加载后运行
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initShare);
} else {
initShare();
}
})();

57
assets/js/timer.js Normal file
View File

@@ -0,0 +1,57 @@
function initClassTimers() {
const timers = document.querySelectorAll('.js-event-timer');
timers.forEach(timer => {
const targetTimeStr = timer.getAttribute('data-target');
const labelEl = timer.querySelector('.js-timer-label');
const daysEl = timer.querySelector('.js-days');
const hoursEl = timer.querySelector('.js-hours');
const minutesEl = timer.querySelector('.js-minutes');
const secondsEl = timer.querySelector('.js-seconds');
// 清除可能存在的旧定时器防止PJAX导致的叠加
if (timer.timerId) clearInterval(timer.timerId);
function updateTimer() {
const targetDate = new Date(targetTimeStr).getTime();
// 简单格式校验,防止 Excerpt 填错导致报错
if (isNaN(targetDate)) {
if (labelEl) labelEl.innerText = "日期错误";
return;
}
const now = new Date().getTime();
const difference = targetDate - now;
const isPast = difference < 0;
const absDiff = Math.abs(difference);
// 更新状态标签
if (labelEl) {
labelEl.innerText = isPast ? "已经过去" : "倒计时中";
}
// 计算时间
const d = Math.floor(absDiff / (1000 * 60 * 60 * 24));
const h = Math.floor((absDiff / (1000 * 60 * 60)) % 24);
const m = Math.floor((absDiff / 1000 / 60) % 60);
const s = Math.floor((absDiff / 1000) % 60);
// 更新数字显示 (加了动画平滑过渡感)
if (daysEl) daysEl.innerText = d.toString().padStart(2, '0');
if (hoursEl) hoursEl.innerText = h.toString().padStart(2, '0');
if (minutesEl) minutesEl.innerText = m.toString().padStart(2, '0');
if (secondsEl) secondsEl.innerText = s.toString().padStart(2, '0');
}
updateTimer();
timer.timerId = setInterval(updateTimer, 1000);
});
}
// 首次加载执行
document.addEventListener('DOMContentLoaded', initClassTimers);
// PJAX 兼容执行 (适配 Ghost 常用的主题加载方式)
if (typeof pjax !== 'undefined' || document.querySelector('[data-pjax]')) {
document.addEventListener('pjax:complete', initClassTimers);
}