✨ 点击图片换一张(老婆/猫/狗/二次元) ✨
<!DOCTYPE html>
<html>
<head>
<title>PCROCK BLOG</title>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="shortcut icon" href="favicon.ico" />
<style>
/* 全局样式 */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
background: linear-gradient(135deg, #1a2a3a 0%, #2a3a4a 50%, #1a3a3a 100%);
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;
min-height: 100vh;
position: relative;
}
/* 粒子容器 */
#particle-canvas {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: -1;
pointer-events: none;
}
/* 全局容器 */
.page-container {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
position: relative;
z-index: 1;
}
/* ========== 顶部区域:Logo 左,时间+播放器右(时间在上) ========== */
.top-bar {
display: flex;
justify-content: space-between;
align-items: flex-end;
margin-bottom: 30px;
gap: 20px;
flex-wrap: nowrap;
}
.logo-area {
flex-shrink: 0;
}
.ascii-logo {
overflow-x: auto;
text-align: left;
}
.ascii-logo pre {
font-family: 'DejaVu Sans Mono', 'Courier New', monospace;
font-size: 18px;
color: #FFFFFF;
margin: 0;
display: inline-block;
line-height: 1.2;
text-shadow: 0 0 10px rgba(0,0,0,0.5);
}
/* 右侧:时间在上,播放器在下 */
.right-group {
display: flex;
flex-direction: column;
align-items: flex-end;
gap: 8px;
flex-shrink: 0;
}
.time-display {
color: #FFFFFF;
font-size: 18px;
white-space: nowrap;
text-shadow: 0 0 5px rgba(0,0,0,0.5);
}
.music-player {
display: flex;
align-items: center;
gap: 8px;
}
.music-player button {
background: rgba(64, 64, 64, 0.8);
color: white;
border: none;
font-size: 18px;
width: 34px;
height: 34px;
border-radius: 50%;
cursor: pointer;
transition: all 0.2s;
display: inline-flex;
align-items: center;
justify-content: center;
backdrop-filter: blur(4px);
}
.music-player button:hover {
background: #FFCC33;
color: #202020;
transform: scale(1.05);
}
.music-player .song-name {
font-size: 15px;
color: #FFFFFF;
white-space: nowrap;
text-shadow: 0 0 5px rgba(0,0,0,0.5);
}
/* ========== 双栏布局:左侧导航 + 右侧内容 ========== */
.two-columns {
display: flex;
gap: 30px;
flex-wrap: wrap;
}
/* 左侧导航栏 - 固定宽度约5个字 */
.sidebar {
flex: 0 0 80px;
min-width: 80px;
max-width: 80px;
}
.nav-menu a {
color: #FFFFFF;
text-decoration: none;
font-size: 15px;
padding: 8px 12px;
transition: all 0.2s;
display: block;
white-space: nowrap;
background: rgba(255, 255, 255, 0.15);
backdrop-filter: blur(8px);
border-radius: 20px;
margin-bottom: 8px;
text-align: center;
border: 1px solid rgba(255, 255, 255, 0.1);
}
.nav-menu a:hover {
background: rgba(255, 204, 51, 0.85);
color: #202020;
transform: translateX(4px);
border-color: #FFCC33;
}
/* 右侧内容区 - 自动占满剩余宽度 */
.content-area {
flex: 1;
min-width: 260px;
background: rgba(245, 245, 245, 0.25);
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
border-radius: 24px;
padding: 28px 32px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
border: 1px solid rgba(255, 255, 255, 0.2);
}
/* 内容区文字颜色 */
.content-area .blog-header h2,
.content-area .blog-header h4 {
color: #1a1a2e;
}
.content-area .blog-title {
color: #2c3e50;
}
.content-area .blog-date {
color: #555;
}
.content-area .quote-section {
color: #2c3e50;
}
/* 嵌入 bg 页面 */
.bg-embed {
margin-bottom: 28px;
width: 100%;
border-radius: 12px;
overflow: hidden;
background: rgba(255,255,255,0.3);
box-shadow: 0 1px 4px rgba(0,0,0,0.1);
}
.bg-embed iframe {
width: 100%;
height: 380px;
border: none;
display: block;
}
/* 博客列表 - 类似文件列表 */
.blog-header {
margin: 20px 0 16px 0;
padding-bottom: 8px;
border-bottom: 2px solid rgba(0,0,0,0.1);
}
.blog-header h4 {
font-size: 20px;
font-weight: 600;
}
.blog-list {
list-style: none;
}
.blog-item {
display: flex;
justify-content: space-between;
align-items: baseline;
padding: 10px 0;
flex-wrap: wrap;
gap: 8px;
border-bottom: 1px solid rgba(0,0,0,0.08);
}
.blog-item:last-child {
border-bottom: none;
}
.blog-title {
font-size: 16px;
text-decoration: none;
font-weight: 500;
transition: color 0.2s;
}
.blog-title:hover {
color: #FFCC33;
text-decoration: underline;
}
.blog-date {
font-size: 13px;
white-space: nowrap;
}
.no-posts {
text-align: center;
padding: 40px;
color: #999;
}
/* 引用/名言区域 - 一言样式 */
.quote-section {
margin-top: 28px;
padding-top: 20px;
border-top: 1px solid rgba(0,0,0,0.1);
font-style: italic;
font-size: 14px;
text-align: center;
cursor: pointer;
transition: opacity 0.3s;
}
.quote-section:hover {
opacity: 0.8;
}
.quote-text {
display: block;
margin-bottom: 6px;
}
.quote-from {
font-size: 12px;
font-style: normal;
opacity: 0.7;
}
.quote-refresh {
font-size: 11px;
opacity: 0.6;
margin-top: 8px;
display: inline-block;
}
/* 计数器样式 */
.counter-container {
text-align: center;
margin-top: 30px;
padding: 15px 0 5px;
}
.counter-container img {
display: inline-block;
max-width: 100%;
height: auto;
}
/* ========== 响应式 ========== */
@media (max-width: 800px) {
.two-columns {
flex-direction: column;
}
.sidebar {
flex: 0 0 auto;
min-width: auto;
max-width: none;
}
.nav-menu {
flex-direction: row;
flex-wrap: wrap;
gap: 16px;
}
.nav-menu a {
white-space: nowrap;
}
.nav-menu a:hover {
transform: translateY(-2px);
}
.content-area {
padding: 20px;
}
}
@media (max-width: 700px) {
.page-container {
padding: 12px;
}
.ascii-logo pre {
font-size: 12px;
}
.music-player .song-name {
white-space: normal;
max-width: 120px;
font-size: 12px;
}
.time-display {
font-size: 14px;
}
.music-player button {
width: 28px;
height: 28px;
font-size: 14px;
}
.blog-title {
font-size: 14px;
}
.blog-date {
font-size: 11px;
}
}
</style>
</head>
<body>
<!-- 粒子画布 -->
<canvas id="particle-canvas"></canvas>
<div class="page-container">
<!-- 顶部:Logo 左,时间+播放器右 -->
<div class="top-bar">
<div class="logo-area">
<a target="_blank" href="http://pcrock.hkfree.work/">
<div class="ascii-logo">
<pre>
_____ _____ _____ ____ _____ _ __
| _ \ / ____| | __ \ / __ \ / ____| | |/ /
| |_) | | | | |__) | | | | | | | | ' /
| _ / | | | _ / | | | | | | | <
| | | |____ | | \ \ | |__| | | |____ | . \
|_| \_____| |_| \_\ \____/ \_____| |_|\_\
</pre>
</div>
</a>
</div>
<div class="right-group">
<div id="time" class="time-display"></div>
<div class="music-player">
<button id="playBtn">▶</button>
<button id="nextBtn">⏭</button>
<span class="song-name" id="songName">未播放</span>
</div>
</div>
</div>
<!-- 双栏布局 -->
<div class="two-columns">
<!-- 左侧导航 -->
<div class="sidebar">
<div id="navBar" class="nav-menu">
<span style="color:#FFFFFF">加载中...</span>
</div>
</div>
<!-- 右侧内容 -->
<div class="content-area">
<!-- 嵌入 bg 页面 -->
<div class="bg-embed">
<iframe src="https://pcrock99.github.io/bg" title="Bg页面"></iframe>
</div>
<!-- 博客列表 -->
<div class="blog-header">
<h4>📄 文章列表</h4>
</div>
<div id="blogContainer" class="blog-list">
<div class="no-posts">加载文章中...</div>
</div>
<!-- 一言区域 -->
<div class="quote-section" id="quoteBox">
<span class="quote-text" id="quoteText">加载一言中...</span>
<span class="quote-from" id="quoteFrom"></span>
<div class="quote-refresh">⏎ 点击刷新 / 自动更新</div>
</div>
</div>
</div>
<!-- 计数器区域 -->
<div class="counter-container">
<img src="https://count.getloli.com/@pcrock-blog?theme=asoul" alt="访问计数器">
</div>
</div>
<!-- 粒子特效脚本 -->
<script>
(function() {
const canvas = document.getElementById('particle-canvas');
const ctx = canvas.getContext('2d');
let particles = [];
let particleCount = 120;
let mouseX = null, mouseY = null;
function resizeCanvas() {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
}
class Particle {
constructor() {
this.x = Math.random() * canvas.width;
this.y = Math.random() * canvas.height;
this.size = Math.random() * 3 + 1;
this.speedX = (Math.random() - 0.5) * 0.5;
this.speedY = (Math.random() - 0.5) * 0.3;
this.opacity = Math.random() * 0.5 + 0.2;
this.color = `rgba(255, 255, 255, ${this.opacity})`;
}
update() {
this.x += this.speedX;
this.y += this.speedY;
// 边界重置
if (this.x < -50) this.x = canvas.width + 50;
if (this.x > canvas.width + 50) this.x = -50;
if (this.y < -50) this.y = canvas.height + 50;
if (this.y > canvas.height + 50) this.y = -50;
// 鼠标排斥效果
if (mouseX !== null && mouseY !== null) {
const dx = this.x - mouseX;
const dy = this.y - mouseY;
const dist = Math.sqrt(dx * dx + dy * dy);
if (dist < 100) {
const angle = Math.atan2(dy, dx);
const force = (100 - dist) / 100 * 2;
this.x += Math.cos(angle) * force;
this.y += Math.sin(angle) * force;
}
}
}
draw() {
ctx.beginPath();
ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2);
ctx.fillStyle = this.color;
ctx.fill();
}
}
function initParticles() {
particles = [];
for (let i = 0; i < particleCount; i++) {
particles.push(new Particle());
}
}
function animate() {
if (!ctx) return;
ctx.clearRect(0, 0, canvas.width, canvas.height);
// 绘制连接线
for (let i = 0; i < particles.length; i++) {
particles[i].update();
particles[i].draw();
for (let j = i + 1; j < particles.length; j++) {
const dx = particles[i].x - particles[j].x;
const dy = particles[i].y - particles[j].y;
const dist = Math.sqrt(dx * dx + dy * dy);
if (dist < 100) {
ctx.beginPath();
ctx.moveTo(particles[i].x, particles[i].y);
ctx.lineTo(particles[j].x, particles[j].y);
ctx.strokeStyle = `rgba(255, 255, 255, ${0.15 * (1 - dist / 100)})`;
ctx.stroke();
}
}
}
requestAnimationFrame(animate);
}
// 鼠标追踪
window.addEventListener('mousemove', (e) => {
mouseX = e.clientX;
mouseY = e.clientY;
});
window.addEventListener('mouseleave', () => {
mouseX = null;
mouseY = null;
});
window.addEventListener('resize', () => {
resizeCanvas();
initParticles();
});
resizeCanvas();
initParticles();
animate();
})();
</script>
<script>
// ========== 一言(Hitokoto)功能 ==========
const localQuotes = [
{ text: "「今天你要是不做,你明天还是得做。」", from: "MengCheng1015" },
{ text: "「代码就像黑暗森林,你永远不知道下一个 bug 在哪里。」", from: "程序员箴言" },
{ text: "「保持好奇,保持愚蠢。」", from: "Stay Hungry, Stay Foolish" },
{ text: "「简单是终极的复杂。」", from: "达·芬奇" },
{ text: "「Talk is cheap. Show me the code.」", from: "Linus Torvalds" },
{ text: "「求知若饥,虚心若愚。」", from: "Steve Jobs" },
{ text: "「不要等待机会,而要创造机会。」", from: "佚名" }
];
let quoteTimer = null;
async function fetchHitokoto() {
try {
const response = await fetch('https://v1.hitokoto.cn/?c=i&c=k&c=h&c=l&c=d&encode=json');
if (!response.ok) throw new Error('API 请求失败');
const data = await response.json();
return {
text: data.hitokoto || data.text,
from: data.from || data.source || "佚名"
};
} catch (error) {
console.warn('一言 API 获取失败,使用本地备选:', error);
const randomIndex = Math.floor(Math.random() * localQuotes.length);
return localQuotes[randomIndex];
}
}
async function updateQuote() {
const quoteTextEl = document.getElementById('quoteText');
const quoteFromEl = document.getElementById('quoteFrom');
quoteTextEl.textContent = '加载中...';
quoteFromEl.textContent = '';
const quote = await fetchHitokoto();
quoteTextEl.textContent = `「${quote.text}」`;
if (quote.from && quote.from !== '佚名') {
quoteFromEl.textContent = `—— ${quote.from}`;
} else if (quote.from === '佚名') {
quoteFromEl.textContent = '';
} else {
quoteFromEl.textContent = `—— ${quote.from}`;
}
}
function startQuoteTimer() {
if (quoteTimer) clearInterval(quoteTimer);
quoteTimer = setInterval(() => {
updateQuote();
}, 2 * 60 * 1000);
}
async function initQuote() {
await updateQuote();
startQuoteTimer();
const quoteBox = document.getElementById('quoteBox');
quoteBox.addEventListener('click', async (e) => {
e.stopPropagation();
await updateQuote();
startQuoteTimer();
});
}
// ========== 导航栏加载 ==========
async function loadNav() {
try {
const response = await fetch('/nav.json');
if (!response.ok) throw new Error('导航加载失败');
const navItems = await response.json();
const navBar = document.getElementById('navBar');
if (navItems.length === 0) {
navBar.innerHTML = '<span style="color:#AAAAAA">暂无导航</span>';
return;
}
navBar.innerHTML = navItems.map(item =>
`<a href="${item.url}" target="${item.target}">${item.name}</a>`
).join('');
} catch (error) {
console.error('导航加载失败', error);
document.getElementById('navBar').innerHTML = '<span style="color:#AAAAAA">导航加载失败</span>';
}
}
// ========== 博客文章加载 ==========
async function loadPosts() {
try {
const response = await fetch('/posts.json');
if (!response.ok) throw new Error('文章加载失败');
const posts = await response.json();
renderPosts(posts);
} catch (error) {
document.getElementById('blogContainer').innerHTML = '<div class="no-posts">暂无文章,请在 posts.json 中添加</div>';
console.error(error);
}
}
function renderPosts(posts) {
const container = document.getElementById('blogContainer');
if (!posts.length) {
container.innerHTML = '<div class="no-posts">暂无文章</div>';
return;
}
posts.sort((a, b) => new Date(b.date) - new Date(a.date));
container.innerHTML = posts.map(post => `
<div class="blog-item">
<a class="blog-title" href="/post.html?id=${post.id}">${post.title}</a>
<span class="blog-date">${post.date}</span>
</div>
`).join('');
}
// ========== 音乐播放器 ==========
let playlist = [];
let currentIndex = 0;
let audio = new Audio();
let isPlaying = false;
let isSkipping = false;
async function loadPlaylist() {
try {
const response = await fetch('https://pcrock99.github.io/playlist.m3u');
if (!response.ok) throw new Error('M3U8列表加载失败');
const text = await response.text();
const lines = text.split(/\r?\n/);
playlist = [];
let currentSong = null;
for (let line of lines) {
line = line.trim();
if (line === '' || line.startsWith('#')) {
if (line.startsWith('#EXTINF:')) {
const match = line.match(/#EXTINF:.*?,(.+)$/);
if (match && match[1]) {
currentSong = { name: match[1].trim(), url: '' };
}
}
continue;
}
if (currentSong && line) {
currentSong.url = line;
playlist.push(currentSong);
currentSong = null;
}
}
if (playlist.length === 0) {
document.getElementById('songName').innerText = '无歌曲';
return;
}
currentIndex = Math.floor(Math.random() * playlist.length);
tryLoadSong(currentIndex);
} catch (error) {
console.error('播放列表加载失败', error);
document.getElementById('songName').innerText = '列表加载失败';
}
}
function isLinkReachable(url, timeout = 4000) {
return new Promise((resolve) => {
const controller = new AbortController();
const timer = setTimeout(() => {
controller.abort();
resolve(false);
}, timeout);
fetch(url, {
method: 'HEAD',
signal: controller.signal,
mode: 'no-cors'
})
.then(() => {
clearTimeout(timer);
resolve(true);
})
.catch(() => {
clearTimeout(timer);
resolve(false);
});
});
}
async function tryLoadSong(index) {
if (isSkipping) return;
if (playlist.length === 0) return;
isSkipping = true;
if (index >= playlist.length) index = 0;
const song = playlist[index];
document.getElementById('songName').innerText = '检测中...';
const isReachable = await isLinkReachable(song.url, 4000);
if (!isReachable) {
console.log(`失效或超时,自动跳过: ${song.name}`);
let newIndex = Math.floor(Math.random() * playlist.length);
currentIndex = newIndex;
isSkipping = false;
tryLoadSong(currentIndex);
return;
}
document.getElementById('songName').innerText = song.name;
audio.src = song.url;
isSkipping = false;
if (isPlaying) {
const playPromise = audio.play();
if (playPromise !== undefined) {
playPromise.catch(() => nextSong(true));
}
}
}
function playPause() {
if (playlist.length === 0) return;
if (isPlaying) {
audio.pause();
document.getElementById('playBtn').innerHTML = '▶';
} else {
audio.play().catch(() => nextSong(true));
document.getElementById('playBtn').innerHTML = '⏸';
}
isPlaying = !isPlaying;
}
function nextSong(autoSkip = false) {
if (isSkipping && !autoSkip) return;
if (playlist.length === 0) return;
let newIndex = Math.floor(Math.random() * playlist.length);
currentIndex = newIndex;
tryLoadSong(currentIndex);
}
audio.addEventListener('ended', () => nextSong(true));
audio.addEventListener('error', () => nextSong(true));
document.getElementById('playBtn').onclick = playPause;
document.getElementById('nextBtn').onclick = () => nextSong(false);
// 初始化所有
loadNav();
loadPosts();
loadPlaylist();
initQuote();
</script>
<script>
// 时间显示
function updateTime() {
const timeEl = document.getElementById('time');
if (timeEl) {
timeEl.innerHTML = new Date().toLocaleString('chinese', {hour12: false});
}
}
updateTime();
setInterval(updateTime, 1000);
// 禁用右键和选择
document.oncontextmenu = function() { return false; };
document.onselectstart = function() { return false; };
</script>
<!-- 摸鱼倒计时 -->
<style>
.moyu-clock {
position: fixed;
bottom: 20px;
right: 20px;
background: rgba(0, 0, 0, 0.7);
backdrop-filter: blur(8px);
color: #FFCC33;
font-family: 'Courier New', monospace;
font-size: 14px;
padding: 10px 16px;
border-radius: 30px;
box-shadow: 0 2px 10px rgba(0,0,0,0.3);
z-index: 9999;
text-align: center;
cursor: pointer;
transition: all 0.3s ease;
border: 1px solid rgba(255,204,51,0.3);
pointer-events: auto;
}
.moyu-clock:hover {
background: rgba(0, 0, 0, 0.85);
transform: scale(1.02);
border-color: #FFCC33;
}
.moyu-clock span {
display: block;
}
.moyu-time {
font-size: 18px;
font-weight: bold;
letter-spacing: 1px;
}
.moyu-label {
font-size: 11px;
color: #aaa;
margin-top: 4px;
}
@media (max-width: 600px) {
.moyu-clock {
font-size: 10px;
padding: 6px 12px;
bottom: 10px;
right: 10px;
}
.moyu-time {
font-size: 13px;
}
}
</style>
<div class="moyu-clock" id="moyuClock" title="点击刷新">
<span class="moyu-time" id="moyuTime">--:--:--</span>
<span class="moyu-label" id="moyuLabel">摸鱼倒计时</span>
</div>
<script>
(function() {
const WORK_END_HOUR = 17;
const WORK_END_MINUTE = 30;
function formatTime(seconds) {
if (seconds <= 0) return null;
const hours = Math.floor(seconds / 3600);
const minutes = Math.floor((seconds % 3600) / 60);
const secs = seconds % 60;
return `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
}
function getWeekendSeconds(now) {
const day = now.getDay();
const currentSec = now.getHours() * 3600 + now.getMinutes() * 60 + now.getSeconds();
const endOfWorkSec = WORK_END_HOUR * 3600 + WORK_END_MINUTE * 60;
if (day === 5 && currentSec >= endOfWorkSec) {
const sundayEnd = new Date(now);
sundayEnd.setDate(now.getDate() + (7 - day));
sundayEnd.setHours(23, 59, 59, 999);
return Math.floor((sundayEnd - now) / 1000);
}
if (day === 6) {
const sundayEnd = new Date(now);
sundayEnd.setDate(now.getDate() + (7 - day));
sundayEnd.setHours(23, 59, 59, 999);
return Math.floor((sundayEnd - now) / 1000);
}
if (day === 0) {
const dayEnd = new Date(now);
dayEnd.setHours(23, 59, 59, 999);
return Math.floor((dayEnd - now) / 1000);
}
return 0;
}
function updateCountdown() {
const now = new Date();
const currentSec = now.getHours() * 3600 + now.getMinutes() * 60 + now.getSeconds();
const endSec = WORK_END_HOUR * 3600 + WORK_END_MINUTE * 60;
const day = now.getDay();
const timeEl = document.getElementById('moyuTime');
const labelEl = document.getElementById('moyuLabel');
if (day === 6 || day === 0) {
const weekendSec = getWeekendSeconds(now);
if (weekendSec > 0) {
timeEl.innerText = formatTime(weekendSec);
labelEl.innerText = '🌊 周末余额';
} else {
timeEl.innerText = '🎉 摸鱼中';
labelEl.innerText = '享受周末!';
}
return;
}
if (day === 5 && currentSec >= endSec) {
const weekendSec = getWeekendSeconds(now);
if (weekendSec > 0) {
timeEl.innerText = formatTime(weekendSec);
labelEl.innerText = '🎉 周末倒计时';
} else {
timeEl.innerText = '🎉 下班啦';
labelEl.innerText = '周末愉快!';
}
return;
}
if (currentSec < endSec) {
const remain = endSec - currentSec;
timeEl.innerText = formatTime(remain);
labelEl.innerText = day === 5 ? '🐟 周五下班' : '💼 下班倒计时';
} else {
timeEl.innerText = '🎉 已下班';
labelEl.innerText = '摸鱼愉快!';
}
}
const clockDiv = document.getElementById('moyuClock');
clockDiv.addEventListener('click', () => {
updateCountdown();
});
updateCountdown();
setInterval(updateCountdown, 1000);
})();
</script>
</body>
</html>