Files
whoami/homepage/index.html
T

407 lines
21 KiB
HTML
Raw 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%3Cdefs%3E%3ClinearGradient id='g' x1='0' y1='0' x2='1' y2='1'%3E%3Cstop offset='0' stop-color='%236366f1'/%3E%3Cstop offset='1' stop-color='%2322d3ee'/%3E%3C/linearGradient%3E%3C/defs%3E%3Crect width='64' height='64' rx='14' fill='url(%23g)'/%3E%3C/svg%3E">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>hyonglin.top</title>
<style>
/* ===== 主题变量(默认浅色,深色自适应) ===== */
:root {
--bg: #f2f5fb;
--text: #141b2e;
--muted: #5c6a84;
--card: rgba(255,255,255,.66);
--border: rgba(15,30,70,.08);
--inset: rgba(255,255,255,.9);
--shadow: 0 10px 30px rgba(30,50,100,.08);
--shadow-hover: 0 16px 40px rgba(30,50,100,.14);
--grad: linear-gradient(120deg, #6366f1, #0ea5e9 55%, #22d3ee);
--sky-a: #dbeafe; --sky-b: #cffafe;
--orb-op: .5;
--ga: rgba(99,102,241,.17);
--gb: rgba(34,211,238,.15);
}
@media (prefers-color-scheme: dark) {
:root {
--bg: #0a0f1e;
--text: #eef2ff;
--muted: #8b97b3;
--card: rgba(255,255,255,.055);
--border: rgba(255,255,255,.09);
--inset: rgba(10,15,30,.45);
--shadow: 0 10px 30px rgba(0,0,0,.35);
--shadow-hover: 0 16px 42px rgba(0,0,0,.5);
--sky-a: rgba(59,130,246,.28); --sky-b: rgba(34,211,238,.16);
--orb-op: .38;
--ga: rgba(129,140,248,.22);
--gb: rgba(34,211,238,.20);
}
}
* { box-sizing: border-box; margin: 0; padding: 0; }
html, body { height: 100%; }
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Microsoft YaHei", sans-serif;
background: var(--bg);
color: var(--text);
min-height: 100vh;
-webkit-font-smoothing: antialiased;
overflow-x: hidden;
}
::selection { background: #6366f1; color: #fff; }
/* ===== 背景光斑(JS 驱动:缓慢漂移 + 鼠标视差)与跟随光晕 ===== */
.orb { position: fixed; border-radius: 50%; filter: blur(90px); z-index: 0; pointer-events: none; opacity: var(--orb-op); will-change: transform; }
.orb1 { width: 480px; height: 480px; top: -160px; left: -120px; background: #a5b4fc; }
.orb2 { width: 420px; height: 420px; bottom: -140px; right: -100px; background: #67e8f9; }
.orb3 { width: 360px; height: 360px; top: 32%; right: -150px; background: #f5d0fe; opacity: calc(var(--orb-op) * .8); }
.glow { position: fixed; left: 0; top: 0; z-index: 0; pointer-events: none; border-radius: 50%; will-change: transform; opacity: 0; }
.glow-a { width: 560px; height: 560px; background: radial-gradient(circle, var(--ga), transparent 65%); }
.glow-b { width: 420px; height: 420px; background: radial-gradient(circle, var(--gb), transparent 65%); }
.wrap { position: relative; z-index: 1; max-width: 1080px; margin: 0 auto; padding: 36px 20px 44px; }
/* ===== Bento 网格 ===== */
.grid { display: grid; grid-template-columns: repeat(6, 1fr); gap: 14px; }
.card {
background: var(--card);
border: 1px solid var(--border);
border-radius: 22px;
padding: 20px 22px;
backdrop-filter: blur(16px) saturate(1.3);
-webkit-backdrop-filter: blur(16px) saturate(1.3);
box-shadow: var(--shadow);
position: relative;
overflow: hidden;
animation: rise .6s cubic-bezier(.2,.7,.3,1) both;
animation-delay: var(--d, 0s);
}
@keyframes rise { from { opacity: 0; transform: translateY(14px) scale(.985); } to { opacity: 1; transform: none; } }
/* ===== Hero:标题 + 搜索 ===== */
.hero { grid-column: span 4; grid-row: span 2; display: flex; flex-direction: column; justify-content: center; }
.hero h1 {
font-size: clamp(2.2rem, 5vw, 3.3rem);
font-weight: 800; letter-spacing: -.03em; line-height: 1.08;
background: var(--grad);
-webkit-background-clip: text; background-clip: text; color: transparent;
}
.hero p.sub { color: var(--muted); margin-top: 10px; font-size: .98rem; }
form.search {
display: flex; align-items: center; gap: 8px;
margin-top: 26px; padding: 6px 6px 6px 16px;
background: var(--inset);
border: 1px solid var(--border);
border-radius: 999px;
transition: border-color .16s ease, box-shadow .16s ease;
}
form.search:focus-within { border-color: #818cf8; box-shadow: 0 0 0 3px rgba(99,102,241,.15); }
form.search .sicon { font-size: 1rem; opacity: .8; flex-shrink: 0; }
form.search select {
flex-shrink: 0; cursor: pointer;
font-size: .82rem; color: var(--muted);
background: transparent; border: 1px solid var(--border); border-radius: 999px;
padding: 7px 10px; outline: none; max-width: 118px;
}
form.search select:focus-visible { border-color: #818cf8; color: var(--text); }
form.search input {
flex: 1; min-width: 0; background: none; border: none; outline: none;
color: var(--text); font-size: .95rem; padding: 9px 0;
}
form.search input::placeholder { color: var(--muted); }
form.search button {
border: none; cursor: pointer; flex-shrink: 0;
padding: 10px 24px; border-radius: 999px;
font-size: .9rem; font-weight: 600; color: #fff;
background: var(--grad);
transition: transform .14s ease, filter .14s ease;
}
form.search button:hover { transform: scale(1.03); filter: brightness(1.08); }
/* ===== 天气卡 ===== */
.sky { grid-column: span 2; grid-row: span 2; display: flex; flex-direction: column; justify-content: center; gap: 6px; background: linear-gradient(165deg, var(--sky-a), var(--sky-b)); }
.card .label { font-size: .72rem; color: var(--muted); letter-spacing: .1em; text-transform: uppercase; margin-bottom: 8px; }
.sky .emoji { font-size: 3.2rem; line-height: 1; }
.sky .temp { font-size: clamp(2.2rem, 4vw, 2.8rem); font-weight: 700; font-variant-numeric: tabular-nums; letter-spacing: -.02em; }
.wcity { font-size: .8rem; color: var(--muted); min-height: 1.2em; }
/* ===== 日期 / 时间卡 ===== */
.clockcard, .datecard { grid-column: span 3; display: flex; flex-direction: column; justify-content: center; }
.clockcard .value { font-size: clamp(1.9rem, 4vw, 2.6rem); font-weight: 700; font-variant-numeric: tabular-nums; letter-spacing: .01em; line-height: 1.15; }
.datecard .value { font-size: clamp(1.3rem, 2.4vw, 1.7rem); font-weight: 650; font-variant-numeric: tabular-nums; letter-spacing: .02em; line-height: 1.15; }
/* ===== 服务分组面板 ===== */
.panel { grid-column: span 3; }
.panel > h2 { display: flex; align-items: center; gap: 8px; font-size: .95rem; font-weight: 700; margin-bottom: 14px; }
.panel > h2 .count { margin-left: auto; font-size: .72rem; font-weight: 600; color: var(--muted); background: var(--inset); border: 1px solid var(--border); padding: 3px 10px; border-radius: 999px; }
ul.tiles { list-style: none; display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 9px; }
ul.tiles a {
display: flex; align-items: center; gap: 10px;
padding: 11px 12px;
background: var(--inset);
border: 1px solid var(--border);
border-radius: 14px;
text-decoration: none; color: var(--text);
transition: transform .16s ease, border-color .16s ease, box-shadow .16s ease;
}
ul.tiles a:hover {
transform: translateY(-3px);
border-color: color-mix(in srgb, var(--ac) 55%, transparent);
box-shadow: 0 8px 24px color-mix(in srgb, var(--ac) 22%, transparent);
}
ul.tiles .icon {
font-size: 1.1rem; width: 36px; height: 36px; flex-shrink: 0;
display: flex; align-items: center; justify-content: center;
border-radius: 11px;
background: linear-gradient(135deg, color-mix(in srgb, var(--ac) 32%, transparent), color-mix(in srgb, var(--ac) 10%, transparent));
}
ul.tiles .meta { display: flex; flex-direction: column; min-width: 0; }
ul.tiles .name { font-weight: 650; font-size: .9rem; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
ul.tiles .desc { color: var(--muted); font-size: .72rem; margin-top: 2px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* 分组配色 */
.s-self { --ac: #10b981; }
.s-ai { --ac: #8b5cf6; }
.s-cloud { --ac: #3b82f6; }
.s-util { --ac: #f59e0b; }
/* ===== 页脚 ===== */
footer.card { background: none; border: none; box-shadow: none; backdrop-filter: none; -webkit-backdrop-filter: none; padding: 8px 0; }
footer { grid-column: span 6; text-align: center; color: var(--muted); font-size: .8rem; }
footer a { color: var(--muted); text-decoration: none; border-bottom: 1px dotted var(--muted); margin-left: 6px; }
footer a:hover { color: #6366f1; border-color: #6366f1; }
/* ===== 响应式 ===== */
@media (max-width: 880px) {
.grid { grid-template-columns: repeat(2, 1fr); }
.hero { grid-column: span 2; grid-row: auto; }
.sky { grid-column: span 2; grid-row: auto; flex-direction: row; align-items: center; gap: 18px; }
.sky .emoji { font-size: 2.6rem; }
.panel { grid-column: span 2; }
}
@media (max-width: 540px) {
.grid { grid-template-columns: 1fr; gap: 12px; }
.clockcard, .datecard, footer { grid-column: auto; }
.wrap { padding: 22px 14px 32px; }
}
@media (prefers-reduced-motion: reduce) {
.card { animation: none; }
ul.tiles a, form.search button { transition: none; }
}
</style>
</head>
<body>
<div class="orb orb1" id="orb1"></div>
<div class="orb orb2" id="orb2"></div>
<div class="orb orb3" id="orb3"></div>
<div class="glow glow-a" id="glowa"></div>
<div class="glow glow-b" id="glowb"></div>
<main class="wrap">
<div class="grid">
<!-- Hero:标题 + Bing 搜索 -->
<section class="card hero" style="--d:.05s">
<h1>hyonglin.top</h1>
<p class="sub">个人服务导航 · 随时可达</p>
<form class="search" action="https://www.bing.com/search" method="get" target="_blank" rel="noopener">
<span class="sicon">🔍</span>
<select name="engine" id="engine" aria-label="搜索引擎">
<option value="bing" selected>Bing</option>
<option value="google">Google</option>
<option value="baidu">百度</option>
<option value="duckduckgo">DuckDuckGo</option>
</select>
<input type="text" name="q" placeholder="输入关键词,回车搜索…" autocomplete="off" aria-label="搜索">
<button type="submit">搜索</button>
</form>
</section>
<!-- 天气 -->
<section class="card sky" style="--d:.12s">
<div class="label">天气</div>
<div class="emoji">☀️</div>
<div class="temp wtxt">获取中…</div>
<div class="wcity"></div>
</section>
<!-- 时间 / 日期 -->
<section class="card clockcard" style="--d:.2s">
<div class="label">时间</div>
<div class="value" id="clockval">--:--:--</div>
</section>
<section class="card datecard" style="--d:.26s">
<div class="label">日期</div>
<div class="value" id="dateval">----</div>
</section>
<!-- 自建服务 -->
<section class="card panel s-self" style="--d:.34s">
<h2>📡 自建服务<span class="count">5</span></h2>
<ul class="tiles">
<li><a href="https://note.hyonglin.top"><span class="icon">📝</span><span class="meta"><span class="name">笔记</span><span class="desc">note.hyonglin.top · SilverBullet</span></span></a></li>
<li><a href="https://git.hyonglin.top"><span class="icon">📦</span><span class="meta"><span class="name">Git 服务</span><span class="desc">git.hyonglin.top · Gitea</span></span></a></li>
<li><a href="https://ssh.hyonglin.top"><span class="icon">🖥️</span><span class="meta"><span class="name">网页 SSH 终端</span><span class="desc">ssh.hyonglin.top</span></span></a></li>
<li><a href="https://frp.hyonglin.top"><span class="icon">🔗</span><span class="meta"><span class="name">frp 管理面板</span><span class="desc">frp.hyonglin.top</span></span></a></li>
<li><a href="/xinquandashboard"><span class="icon">🖼️</span><span class="meta"><span class="name">Xinquan Dashboard</span><span class="desc">hyonglin.top/xinquandashboard</span></span></a></li>
</ul>
</section>
<!-- AI 助手 -->
<section class="card panel s-ai" style="--d:.4s">
<h2>🤖 AI 助手<span class="count">2</span></h2>
<ul class="tiles">
<li><a href="https://chat.deepseek.com/" target="_blank" rel="noopener"><span class="icon">🤖</span><span class="meta"><span class="name">DeepSeek</span><span class="desc">chat.deepseek.com</span></span></a></li>
<li><a href="https://www.doubao.com/chat/" target="_blank" rel="noopener"><span class="icon">💬</span><span class="meta"><span class="name">豆包</span><span class="desc">doubao.com</span></span></a></li>
</ul>
</section>
<!-- 云服务 -->
<section class="card panel s-cloud" style="--d:.46s">
<h2>☁️ 云服务<span class="count">3</span></h2>
<ul class="tiles">
<li><a href="https://dash.cloudflare.com/" target="_blank" rel="noopener"><span class="icon">🌐</span><span class="meta"><span class="name">Cloudflare</span><span class="desc">dash.cloudflare.com</span></span></a></li>
<li><a href="https://auth.huaweicloud.com/" target="_blank" rel="noopener"><span class="icon">☁️</span><span class="meta"><span class="name">华为云</span><span class="desc">auth.huaweicloud.com</span></span></a></li>
<li><a href="https://console.cloud.tencent.com/" target="_blank" rel="noopener"><span class="icon">🐧</span><span class="meta"><span class="name">腾讯云</span><span class="desc">console.cloud.tencent.com</span></span></a></li>
</ul>
</section>
<!-- 常用 -->
<section class="card panel s-util" style="--d:.52s">
<h2>🔧 常用<span class="count">3</span></h2>
<ul class="tiles">
<li><a href="https://wx.mail.qq.com/" target="_blank" rel="noopener"><span class="icon">📧</span><span class="meta"><span class="name">QQ 邮箱</span><span class="desc">wx.mail.qq.com</span></span></a></li>
<li><a href="https://www.bing.com/" target="_blank" rel="noopener"><span class="icon">🔍</span><span class="meta"><span class="name">Bing 搜索</span><span class="desc">bing.com</span></span></a></li>
<li><a href="https://www.bilibili.com/" target="_blank" rel="noopener"><span class="icon">📺</span><span class="meta"><span class="name">哔哩哔哩</span><span class="desc">bilibili.com</span></span></a></li>
</ul>
</section>
<footer style="--d:.58s" class="card"><span><a href="/terminal/">终端版</a> · <a href="/" aria-current="page">Bento</a> · <a href="/brutal/">Brutal</a> · <a href="/swiss/">Swiss</a> · <a href="/hud/">HUD</a></span>hyonglin.top</footer>
</div>
</main>
<script>
/* ===== 动态光晕:三团背景光斑缓慢漂移 + 鼠标视差,两团柔光跟随光标(带延迟拖尾) ===== */
(function(){
if (!window.matchMedia || matchMedia('(prefers-reduced-motion: reduce)').matches) return; // 减少动态效果:保持静态
var o1 = document.getElementById('orb1'),
o2 = document.getElementById('orb2'),
o3 = document.getElementById('orb3'),
gA = document.getElementById('glowa'),
gB = document.getElementById('glowb');
if (!o1 || !gA) return;
gA.style.opacity = '1';
gB.style.opacity = '1';
var W = window.innerWidth, H = window.innerHeight;
var tx = W / 2, ty = H / 2; // 光标目标位置
var cx = tx, cy = ty; // 主光晕(快)
var bx = tx, by = ty; // 副光晕(慢,拖尾感)
window.addEventListener('mousemove', function(e){ tx = e.clientX; ty = e.clientY; }, { passive: true });
window.addEventListener('resize', function(){ W = window.innerWidth; H = window.innerHeight; });
var t0 = performance.now();
function loop(now) {
var t = (now - t0) / 1000;
cx += (tx - cx) * .09;
cy += (ty - cy) * .09;
bx += (tx - bx) * .045;
by += (ty - by) * .045;
var nx = cx / W - .5, ny = cy / H - .5; // 归一化鼠标位置(-0.5 ~ 0.5)
o1.style.transform = 'translate(' + (Math.sin(t * .32) * 34 + nx * 70).toFixed(1) + 'px,' + (Math.cos(t * .26) * 28 + ny * 70).toFixed(1) + 'px)';
o2.style.transform = 'translate(' + (Math.sin(t * .24 + 2) * 40 - nx * 90).toFixed(1) + 'px,' + (Math.cos(t * .30 + 1) * 34 - ny * 90).toFixed(1) + 'px)';
o3.style.transform = 'translate(' + (Math.sin(t * .28 + 4) * 36 + nx * 50).toFixed(1) + 'px,' + (Math.cos(t * .22 + 3) * 30 + ny * 50).toFixed(1) + 'px)';
gA.style.transform = 'translate(' + (cx - 280).toFixed(1) + 'px,' + (cy - 280).toFixed(1) + 'px)';
gB.style.transform = 'translate(' + (bx - 210).toFixed(1) + 'px,' + (by - 210).toFixed(1) + 'px)';
requestAnimationFrame(loop);
}
loop(performance.now()); // 先同步画一帧,避免光晕从屏幕外滑入
requestAnimationFrame(loop);
})();
/* ===== 搜索引擎选择 ===== */
(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('.sky .emoji'); };
var tempBox = function(){ return document.querySelector('.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>