Files

462 lines
23 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta http-equiv="Content-Security-Policy" content="default-src 'self' https: data:; script-src 'self' 'unsafe-inline'; connect-src https:; style-src 'self' 'unsafe-inline' https:; img-src 'self' data: https: *;">
<link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 64 64'%3E%3Crect width='64' height='64' rx='12' fill='%23050d08'/%3E%3Ctext x='9' y='43' font-size='28' font-family='monospace' fill='%233dff9c'%3E%3E_%3C/text%3E%3C/svg%3E">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="color-scheme" content="dark">
<title>hyonglin.top</title>
<style>
:root {
--bg: #020604;
--win: #07130c;
--border: #1d3a2b;
--border-soft: #14291e;
--text: #d7f5e4;
--muted: #6d8f7d;
--accent: #3dff9c;
--accent-dim: #2f8f63;
--cyan: #4dd8ff;
--amber: #ffc857;
--mono: ui-monospace, "SF Mono", "Cascadia Code", "JetBrains Mono", Menlo, Consolas, "Liberation Mono", monospace;
}
* { box-sizing: border-box; margin: 0; padding: 0; }
html, body { height: 100%; }
body {
font-family: var(--mono);
color: var(--text);
background:
radial-gradient(60% 42% at 50% -4%, rgba(61,255,156,.10), transparent 70%),
radial-gradient(90% 60% at 50% 110%, rgba(77,216,255,.05), transparent 70%),
var(--bg);
min-height: 100vh;
-webkit-font-smoothing: antialiased;
overflow-x: hidden;
}
::selection { background: var(--accent); color: #04120b; }
/* ===== CRT 扫描线 + 暗角 ===== */
.crt::before {
content: "";
position: fixed; inset: 0; z-index: 5; pointer-events: none;
background: repeating-linear-gradient(0deg, rgba(0,0,0,.20) 0px, rgba(0,0,0,.20) 1px, transparent 1px, transparent 3px);
opacity: .45;
}
.crt::after {
content: "";
position: fixed; inset: 0; z-index: 6; pointer-events: none;
background: radial-gradient(120% 90% at 50% 50%, transparent 60%, rgba(0,0,0,.42) 100%);
}
/* ===== 黑客帝国数据流背景 ===== */
#matrix { position: fixed; inset: 0; width: 100%; height: 100%; z-index: 0; pointer-events: none; opacity: .38; }
.stage { position: relative; z-index: 1; padding: 34px 18px 40px; display: flex; justify-content: center; }
/* ===== 终端窗口 ===== */
.window {
width: 100%; max-width: 960px;
background: linear-gradient(180deg, rgba(10,26,17,.96), rgba(6,15,10,.97));
border: 1px solid var(--border);
border-radius: 10px;
box-shadow:
0 0 0 1px rgba(0,0,0,.6),
0 24px 80px rgba(0,0,0,.55),
0 0 42px rgba(61,255,156,.07);
overflow: hidden;
}
.titlebar {
display: flex; align-items: center; gap: 8px;
padding: 9px 14px;
border-bottom: 1px solid var(--border-soft);
background: rgba(4,12,8,.6);
}
.dot { width: 10px; height: 10px; border-radius: 50%; flex-shrink: 0; }
.dot.r { background: #ff5f56; } .dot.y { background: #ffbd2e; } .dot.g { background: #27c93f; }
.titlebar .t { font-size: .74rem; color: var(--muted); letter-spacing: .04em; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.titlebar .mode { margin-left: auto; font-size: .72rem; color: var(--accent-dim); flex-shrink: 0; }
.inner { padding: 26px 26px 30px; }
/* ===== 开机日志 ===== */
#bootlog { font-size: .78rem; line-height: 1.75; color: var(--accent-dim); margin-bottom: 4px; min-height: 3em; }
#bootlog .bootline::before { content: " "; }
/* ===== 头部 ===== */
header { text-align: left; margin-top: 10px; }
.prompt-line { font-size: .8rem; color: var(--muted); margin-bottom: 10px; }
.prompt-line b { color: var(--accent); font-weight: 400; }
h1 {
font-size: clamp(2rem, 5.4vw, 3.1rem);
font-weight: 700; letter-spacing: -.02em; line-height: 1.05;
color: #eafff3;
text-shadow: 0 0 18px rgba(61,255,156,.4), 0 0 46px rgba(61,255,156,.16);
animation: glowpulse 7s ease-in-out infinite;
}
@keyframes glowpulse { 0%,100% { text-shadow: 0 0 18px rgba(61,255,156,.4), 0 0 46px rgba(61,255,156,.16); } 50% { text-shadow: 0 0 24px rgba(61,255,156,.55), 0 0 60px rgba(61,255,156,.22); } }
.cursor { display: inline-block; width: .55em; height: 1em; background: var(--accent); vertical-align: -0.12em; margin-left: .18em; animation: blink 1.1s steps(1) infinite; }
@keyframes blink { 50% { opacity: 0; } }
p.sub { color: var(--muted); margin-top: 10px; font-size: .9rem; letter-spacing: .02em; }
/* ===== 状态卡片:天气 / 日期 / 时间 ===== */
.info-row { display: grid; grid-template-columns: repeat(3, 1fr); gap: 14px; margin-top: 26px; }
.card {
border: 1px solid var(--border-soft);
background: rgba(9,24,16,.55);
padding: 12px 16px 14px;
position: relative;
}
.card::before, .card::after { content: ""; position: absolute; width: 8px; height: 8px; pointer-events: none; }
.card::before { top: -1px; left: -1px; border-top: 1px solid var(--accent-dim); border-left: 1px solid var(--accent-dim); }
.card::after { bottom: -1px; right: -1px; border-bottom: 1px solid var(--accent-dim); border-right: 1px solid var(--accent-dim); }
.card .label { font-size: .68rem; color: var(--muted); letter-spacing: .14em; text-transform: uppercase; margin-bottom: 7px; }
.card .value { font-size: 1.55rem; font-weight: 600; font-variant-numeric: tabular-nums; line-height: 1.15; color: #eafff3; }
.card#weather .value { font-size: 1.2rem; display: flex; align-items: center; gap: 8px; }
.card#weather .value .emoji { font-size: 1.45rem; filter: saturate(.9); }
.wcity { font-size: .72rem; color: var(--muted); margin-top: 5px; min-height: 1em; }
/* ===== Bing 搜索(提示符样式) ===== */
form.search {
display: flex; align-items: center; gap: 10px;
margin-top: 24px; padding: 6px 8px 6px 16px;
border: 1px solid var(--border);
background: rgba(5,14,9,.7);
transition: border-color .16s ease, box-shadow .16s ease;
}
form.search:focus-within { border-color: var(--accent-dim); box-shadow: 0 0 0 1px rgba(61,255,156,.18), 0 0 24px rgba(61,255,156,.07); }
form.search .ps { font-size: .9rem; color: var(--accent); white-space: nowrap; flex-shrink: 0; }
form.search select {
flex-shrink: 0; cursor: pointer;
font-family: var(--mono); font-size: .82rem;
color: var(--accent); background: rgba(4,12,8,.75);
border: 1px solid var(--border); border-radius: 6px;
padding: 6px 8px; outline: none;
}
form.search select:focus-visible { border-color: var(--accent-dim); }
form.search input {
flex: 1; min-width: 0; background: none; border: none; outline: none;
color: var(--text); font-family: var(--mono); font-size: .92rem; padding: 9px 0; caret-color: var(--accent);
}
form.search input::placeholder { color: #48645a; }
form.search button {
border: 1px solid var(--border); cursor: pointer; flex-shrink: 0;
padding: 7px 16px; font-family: var(--mono); font-size: .82rem; font-weight: 600;
color: var(--accent); background: transparent; letter-spacing: .05em;
transition: background .14s ease, color .14s ease;
}
form.search button:hover { background: var(--accent); color: #04120b; border-color: var(--accent); }
/* ===== 服务分组 ===== */
.grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 16px; margin-top: 28px; }
.panel { border: 1px solid var(--border-soft); background: rgba(7,18,12,.5); padding: 4px 0 6px; }
.panel > h2 {
display: flex; align-items: baseline; gap: 10px;
font-size: .8rem; font-weight: 600; letter-spacing: .08em;
color: #bfe9d3; padding: 12px 16px 10px;
border-bottom: 1px dashed var(--border-soft);
}
.panel > h2 .bar { color: var(--accent); }
.panel > h2 .count { margin-left: auto; font-size: .7rem; color: var(--muted); font-weight: 400; }
ul.links { list-style: none; padding: 6px 8px; display: flex; flex-direction: column; gap: 2px; }
ul.links a {
display: flex; align-items: center; gap: 10px;
padding: 9px 10px; text-decoration: none; color: var(--text);
border-left: 2px solid transparent;
transition: background .13s ease, border-color .13s ease;
}
ul.links a:hover, ul.links a:focus-visible {
background: rgba(61,255,156,.07);
border-left-color: var(--accent);
outline: none;
}
ul.links .arrow { color: var(--accent-dim); font-size: .85rem; flex-shrink: 0; transition: transform .13s ease, color .13s ease; }
ul.links a:hover .arrow { color: var(--accent); transform: translateX(3px); }
ul.links .icon {
font-size: .95rem; width: 26px; height: 26px; flex-shrink: 0;
display: flex; align-items: center; justify-content: center;
border: 1px solid var(--border-soft); background: rgba(4,12,8,.7);
}
ul.links .name { font-weight: 600; font-size: .9rem; white-space: nowrap; }
ul.links .desc { margin-left: auto; color: var(--muted); font-size: .74rem; text-align: right; overflow: hidden; text-overflow: ellipsis; max-width: 52%; }
/* ===== 页脚 ===== */
footer { margin-top: 30px; padding-top: 16px; border-top: 1px dashed var(--border-soft); display: flex; align-items: baseline; gap: 10px; font-size: .74rem; color: var(--muted); }
footer .code { color: var(--accent-dim); }
footer a { color: var(--accent-dim); text-decoration: none; border-bottom: 1px dotted var(--accent-dim); transition: color .14s ease, border-color .14s ease; }
footer a:hover { color: var(--accent); border-color: var(--accent); }
footer .brand { margin-left: auto; color: #bfe9d3; letter-spacing: .06em; }
/* ===== 入场动画 ===== */
.reveal { opacity: 0; transform: translateY(8px); transition: opacity .2s ease var(--rd, 0s), transform .2s ease var(--rd, 0s); }
body.ready .reveal { opacity: 1; transform: none; }
/* ===== 响应式 ===== */
@media (max-width: 760px) {
.grid { grid-template-columns: 1fr; }
.inner { padding: 20px 18px 24px; }
}
@media (max-width: 560px) {
.info-row { grid-template-columns: 1fr 1fr; }
.card#clock { grid-column: span 2; }
ul.links .desc { max-width: 46%; }
}
@media (max-width: 400px) {
ul.links .desc { display: none; }
}
/* ===== 减少动态效果 ===== */
@media (prefers-reduced-motion: reduce) {
h1, .cursor { animation: none; }
.reveal { transition: none; opacity: 1; transform: none; }
.crt::before { display: none; }
#matrix { display: none; }
}
</style>
<noscript><style>.reveal{opacity:1!important;transform:none!important}#bootlog{display:none}</style></noscript>
</head>
<body class="crt">
<canvas id="matrix" aria-hidden="true"></canvas>
<div class="stage">
<main class="window">
<div class="titlebar">
<span class="dot r"></span><span class="dot y"></span><span class="dot g"></span>
<span class="t">guest@hyonglin.top: ~</span>
<span class="mode">[ bash ]</span>
</div>
<div class="inner">
<div id="bootlog" aria-hidden="true"></div>
<header class="reveal" style="--rd:.02s">
<div class="prompt-line"><b>guest@hyonglin.top</b>:~$ whoami</div>
<h1>hyonglin.top<span class="cursor"></span></h1>
<p class="sub">个人服务导航 · 随时可达</p>
</header>
<div class="info-row reveal" style="--rd:.07s">
<div class="card" id="weather">
<div class="label">天气</div>
<div class="value"><span class="emoji">☀️</span><span class="wtxt">获取中…</span></div>
<div class="wcity"></div>
</div>
<div class="card" id="date">
<div class="label">日期</div>
<div class="value" id="dateval">----</div>
</div>
<div class="card" id="clock">
<div class="label">时间</div>
<div class="value" id="clockval">--:--:--</div>
</div>
</div>
<form class="search reveal" style="--rd:.11s" action="https://www.bing.com/search" method="get" target="_blank" rel="noopener">
<span class="ps">$</span>
<select name="engine" id="engine" aria-label="搜索引擎">
<option value="bing" selected>bing</option>
<option value="google">google</option>
<option value="baidu">baidu</option>
<option value="duckduckgo">duckduckgo</option>
</select>
<input type="text" name="q" placeholder="输入关键词,回车搜索…" autocomplete="off" aria-label="搜索">
<button type="submit">ENTER ↵</button>
</form>
<div class="grid">
<section class="panel reveal" style="--rd:.15s">
<h2><span class="bar">▍</span>📡 自建服务<span class="count">(4)</span></h2>
<ul class="links">
<li><a href="https://note.hyonglin.top"><span class="arrow">❯</span><span class="icon">📝</span><span class="name">笔记</span><span class="desc">note.hyonglin.top · SilverBullet</span></a></li>
<li><a href="https://git.hyonglin.top"><span class="arrow">❯</span><span class="icon">📦</span><span class="name">Git 服务</span><span class="desc">git.hyonglin.top · Gitea</span></a></li>
<li><a href="https://ssh.hyonglin.top"><span class="arrow">❯</span><span class="icon">🖥️</span><span class="name">网页 SSH 终端</span><span class="desc">ssh.hyonglin.top</span></a></li>
<li><a href="https://frp.hyonglin.top"><span class="arrow">❯</span><span class="icon">🔗</span><span class="name">frp 管理面板</span><span class="desc">frp.hyonglin.top</span></a></li>
</ul>
</section>
<section class="panel reveal" style="--rd:.18s">
<h2><span class="bar">▍</span>🤖 AI 助手<span class="count">(2)</span></h2>
<ul class="links">
<li><a href="https://chat.deepseek.com/" target="_blank" rel="noopener"><span class="arrow">❯</span><span class="icon">🤖</span><span class="name">DeepSeek</span><span class="desc">chat.deepseek.com</span></a></li>
<li><a href="https://www.doubao.com/chat/" target="_blank" rel="noopener"><span class="arrow">❯</span><span class="icon">💬</span><span class="name">豆包</span><span class="desc">doubao.com</span></a></li>
</ul>
</section>
<section class="panel reveal" style="--rd:.21s">
<h2><span class="bar">▍</span>☁️ 云服务<span class="count">(3)</span></h2>
<ul class="links">
<li><a href="https://dash.cloudflare.com/" target="_blank" rel="noopener"><span class="arrow">❯</span><span class="icon">🌐</span><span class="name">Cloudflare</span><span class="desc">dash.cloudflare.com</span></a></li>
<li><a href="https://auth.huaweicloud.com/" target="_blank" rel="noopener"><span class="arrow">❯</span><span class="icon">☁️</span><span class="name">华为云</span><span class="desc">auth.huaweicloud.com</span></a></li>
<li><a href="https://console.cloud.tencent.com/" target="_blank" rel="noopener"><span class="arrow">❯</span><span class="icon">🐧</span><span class="name">腾讯云</span><span class="desc">console.cloud.tencent.com</span></a></li>
</ul>
</section>
<section class="panel reveal" style="--rd:.24s">
<h2><span class="bar">▍</span>🔧 常用<span class="count">(3)</span></h2>
<ul class="links">
<li><a href="https://wx.mail.qq.com/" target="_blank" rel="noopener"><span class="arrow">❯</span><span class="icon">📧</span><span class="name">QQ 邮箱</span><span class="desc">wx.mail.qq.com</span></a></li>
<li><a href="https://www.bing.com/" target="_blank" rel="noopener"><span class="arrow">❯</span><span class="icon">🔍</span><span class="name">Bing 搜索</span><span class="desc">bing.com</span></a></li>
<li><a href="https://www.bilibili.com/" target="_blank" rel="noopener"><span class="arrow">❯</span><span class="icon">📺</span><span class="name">哔哩哔哩</span><span class="desc">bilibili.com</span></a></li>
</ul>
</section>
</div>
<footer class="reveal" style="--rd:.28s">
<span class="code">$ exit 0</span>
<span>— end of transmission —</span>
<span><a href="/terminal/" aria-current="page">终端版</a> · <a href="/">Bento</a> · <a href="/brutal/">Brutal</a> · <a href="/swiss/">Swiss</a> · <a href="/hud/">HUD</a></span>
<span class="brand">hyonglin.top</span>
</footer>
</div>
</main>
</div>
<script>
/* ===== 黑客帝国数据流:绿色字符雨,低透明度衬在窗口后面 ===== */
(function(){
var cv = document.getElementById('matrix');
if (!cv || !cv.getContext) return;
if (window.matchMedia && matchMedia('(prefers-reduced-motion: reduce)').matches) return;
var ctx = cv.getContext('2d');
var FONT = 14, W, H, cols, drops = [];
var CHARS = 'アカサタナハマヤラワ0123456789ABCDEF+-*/<>=|';
function resize() {
W = cv.width = window.innerWidth;
H = cv.height = window.innerHeight;
cols = Math.ceil(W / FONT);
drops = [];
for (var i = 0; i < cols; i++) drops.push(Math.random() * -H / FONT);
}
window.addEventListener('resize', resize);
resize();
function tick() {
ctx.fillStyle = 'rgba(2,6,4,.16)'; // 半透明黑做拖尾淡出
ctx.fillRect(0, 0, W, H);
ctx.font = FONT + 'px monospace';
for (var i = 0; i < cols; i++) {
var y = drops[i] * FONT;
ctx.fillStyle = Math.random() < .12 ? '#c8ffe3' : 'rgba(61,255,156,.7)';
ctx.fillText(CHARS[(Math.random() * CHARS.length) | 0], i * FONT, y);
if (y > H && Math.random() > .972) drops[i] = -Math.random() * 40;
else drops[i] += 1;
}
requestAnimationFrame(tick);
}
tick();
})();
/* ===== 开机自举动画(可点击/按键跳过,reduced-motion 直接显示) ===== */
var booted = false;
var bootEl = document.getElementById('bootlog');
var BOOT_LINES = [
'[ ok ] mount /services .......... 12 items',
'[ ok ] weather daemon ........... running',
'$ whoami → hyonglin.top — 个人服务导航'
];
function markReady() { document.body.classList.add('ready'); }
if (bootEl && window.matchMedia && matchMedia('(prefers-reduced-motion: reduce)').matches) {
BOOT_LINES.forEach(function(l){ var d = document.createElement('div'); d.className = 'bootline'; d.textContent = l; bootEl.appendChild(d); });
booted = true; markReady();
} else if (bootEl) {
var li = 0, ci = 0, cur = null;
function finishBoot() { if (!booted) { booted = true; markReady(); } }
function typeLine() {
if (booted) return;
if (li >= BOOT_LINES.length) { setTimeout(finishBoot, 60); return; }
var line = BOOT_LINES[li];
if (!cur) { cur = document.createElement('div'); cur.className = 'bootline'; bootEl.appendChild(cur); ci = 0; }
if (ci <= line.length) { cur.textContent = line.slice(0, ci++); setTimeout(typeLine, 3); }
else { li++; cur = null; setTimeout(typeLine, 45); }
}
function skipBoot() {
if (booted) return;
booted = true;
bootEl.innerHTML = '';
BOOT_LINES.forEach(function(l){ var d = document.createElement('div'); d.className = 'bootline'; d.textContent = l; bootEl.appendChild(d); });
markReady();
}
typeLine();
window.addEventListener('keydown', skipBoot, { once: true });
window.addEventListener('pointerdown', skipBoot, { once: true });
setTimeout(skipBoot, 1250); // 兜底:任何情况下 1.25 秒内一定就绪
}
/* ===== 搜索引擎选择 ===== */
(function(){
var sel = document.getElementById('engine');
if (!sel) return;
var ENGINES = {
bing: 'https://www.bing.com/search?q=',
google: 'https://www.google.com/search?q=',
baidu: 'https://www.baidu.com/s?wd=',
duckduckgo: 'https://duckduckgo.com/?q='
};
var form = sel.closest('form');
function apply() { form.action = ENGINES[sel.value] || ENGINES.bing; }
sel.addEventListener('change', apply);
apply();
})();
/* ===== 日期 + 时钟 ===== */
var week = ['日','一','二','三','四','五','六'];
function pad2(n) { return (n < 10 ? '0' : '') + n; }
function updateClock() {
var now = new Date();
var c = document.getElementById('clockval');
if (c) c.textContent = pad2(now.getHours()) + ':' + pad2(now.getMinutes()) + ':' + pad2(now.getSeconds());
var d = document.getElementById('dateval');
if (d) d.textContent = now.getFullYear() + '-' + pad2(now.getMonth()+1) + '-' + pad2(now.getDate()) + ' 周' + week[now.getDay()];
}
setInterval(updateClock, 1000);
updateClock();
/* ===== 天气:GPS 定位 + Open-Meteo(不依赖 IP) ===== */
var emojiBox = function(){ return document.querySelector('#weather .emoji'); };
var tempBox = function(){ return document.querySelector('#weather .wtxt'); };
var cityBox = function(){ return document.querySelector('.wcity'); };
var WMO = {
0:'☀️',1:'🌤',2:'⛅',3:'☁️',45:'🌫',48:'🌫',
51:'🌦',53:'🌦',55:'🌦',56:'🌨',57:'🌨',
61:'🌧',63:'🌧',65:'🌧',66:'🌨',67:'🌨',
71:'🌨',73:'🌨',75:'🌨',77:'🌨',80:'🌦',81:'🌦',82:'🌧',
85:'🌨',86:'🌨',95:'⛈',96:'⛈',99:'⛈'
};
function wmo(w) { return WMO[w] || '🌡️'; }
function setWeather(temp, wcode, city) {
if (tempBox()) tempBox().textContent = temp + '°C';
if (emojiBox()) emojiBox().textContent = wmo(wcode);
if (city && cityBox()) cityBox().textContent = city;
}
function getWeather(lat, lon, city) {
var url = 'https://api.open-meteo.com/v1/forecast?latitude=' + lat + '&longitude=' + lon +
'&current_weather=true&timezone=auto';
fetch(url).then(function(r){ return r.json(); }).then(function(d){
var cw = d.current_weather || {};
setWeather(Math.round(cw.temperature), cw.weathercode, city);
}).catch(function(){ tempBox() && (tempBox().textContent = '天气获取失败'); });
}
// 优先浏览器 GPS 定位;失败则 ipwho.is 按 IP 定位;再失败显示默认城市(北京)
var DEFAULT_LOC = { lat: 39.9042, lon: 116.4074, city: '北京' };
function byIP() {
fetch('https://ipwho.is/').then(function(r){ return r.json(); }).then(function(d){
if (d && d.latitude && d.longitude) getWeather(d.latitude, d.longitude, d.city || '');
else { getWeather(DEFAULT_LOC.lat, DEFAULT_LOC.lon, DEFAULT_LOC.city); }
}).catch(function(){ getWeather(DEFAULT_LOC.lat, DEFAULT_LOC.lon, DEFAULT_LOC.city); });
}
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(function(pos){
getWeather(pos.coords.latitude, pos.coords.longitude, '');
}, byIP, { timeout: 8000 });
} else {
byIP();
}
</script>
</body>
</html>