Files

349 lines
18 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 x='3' y='3' width='58' height='58' rx='12' fill='%23f5d337' stroke='%231a1a1a' stroke-width='6'/%3E%3Cpath d='M14 40 L28 26 L36 34 L50 20' fill='none' stroke='%231a1a1a' stroke-width='7'/%3E%3Ccircle cx='46' cy='46' r='6' fill='%23ff4d8d' stroke='%231a1a1a' stroke-width='4'/%3E%3C/svg%3E">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>hyonglin.top</title>
<style>
:root {
--bg: #f5d337;
--ink: #1a1a1a;
--paper: #ffffff;
--pink: #ff4d8d;
--blue: #29c5ff;
--green: #0fd67b;
--orange: #ff8a3d;
}
* { 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;
color: var(--ink);
background-color: var(--bg);
/* 网格纸纹理 + SVG 噪点 */
background-image:
url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.9' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='140' height='140' filter='url(%23n)' opacity='.55'/%3E%3C/svg%3E"),
linear-gradient(rgba(26,26,26,.07) 1px, transparent 1px),
linear-gradient(90deg, rgba(26,26,26,.07) 1px, transparent 1px);
background-size: 140px 140px, 36px 36px, 36px 36px;
min-height: 100vh;
-webkit-font-smoothing: antialiased;
overflow-x: hidden;
}
::selection { background: var(--pink); color: #fff; }
.wrap { max-width: 1080px; margin: 0 auto; padding: 34px 20px 40px; }
/* ===== 粗野网格:6 列,与 bento 版同构 ===== */
.grid { display: grid; grid-template-columns: repeat(6, 1fr); gap: 18px; }
.card {
background: var(--paper);
border: 3px solid var(--ink);
border-radius: 4px;
box-shadow: 6px 6px 0 var(--ink);
padding: 20px 22px;
position: relative;
animation: slam .5s cubic-bezier(.2,.9,.3,1.15) both;
animation-delay: var(--d, 0s);
}
@keyframes slam { from { opacity: 0; transform: translateY(16px) scale(.97); } to { opacity: 1; transform: none; } }
/* ===== Hero:巨型标题 + 贴纸 + 搜索 ===== */
.hero { grid-column: span 4; grid-row: span 2; display: flex; flex-direction: column; justify-content: center; background: var(--paper); }
.hero h1 {
font-size: clamp(2.6rem, 7vw, 5rem);
font-weight: 900; letter-spacing: -.04em; line-height: .98;
text-transform: lowercase;
text-shadow: 3px 3px 0 var(--pink), 6px 6px 0 var(--blue);
}
.hero p.sub { margin-top: 14px; font-size: 1.05rem; font-weight: 800; letter-spacing: .02em; }
.sticker {
position: absolute; top: -16px; right: 18px; z-index: 2;
background: var(--orange); color: var(--ink);
border: 3px solid var(--ink); border-radius: 999px;
padding: 7px 14px; font-size: .78rem; font-weight: 900; letter-spacing: .06em;
box-shadow: 3px 3px 0 var(--ink);
transform: rotate(6deg);
animation: wiggle 2.6s ease-in-out infinite alternate;
}
@keyframes wiggle { from { transform: rotate(4deg); } to { transform: rotate(9deg); } }
form.search {
display: flex; align-items: center; gap: 10px;
margin-top: 26px; padding: 8px 8px 8px 14px;
background: var(--paper);
border: 3px solid var(--ink); border-radius: 4px;
box-shadow: 5px 5px 0 var(--ink);
}
form.search:focus-within { box-shadow: 7px 7px 0 var(--pink); }
form.search .sicon { font-size: 1.05rem; flex-shrink: 0; }
form.search select {
flex-shrink: 0; cursor: pointer;
font-family: inherit; font-size: .84rem; font-weight: 800; color: var(--ink);
background: #fffbe8; border: 2px solid var(--ink); border-radius: 3px;
padding: 7px 6px; outline: none; max-width: 122px;
}
form.search input {
flex: 1; min-width: 0; background: none; border: none; outline: none;
color: var(--ink); font-family: inherit; font-size: .95rem; padding: 8px 0;
}
form.search input::placeholder { color: rgba(26,26,26,.45); font-weight: 600; }
form.search button {
border: 3px solid var(--ink); cursor: pointer; flex-shrink: 0;
padding: 9px 22px; border-radius: 3px;
font-family: inherit; font-size: .9rem; font-weight: 900; letter-spacing: .04em; color: var(--ink);
background: var(--pink); box-shadow: 3px 3px 0 var(--ink);
transition: transform .12s ease, box-shadow .12s ease;
}
form.search button:hover { transform: translate(-2px,-2px); box-shadow: 5px 5px 0 var(--ink); }
/* ===== 信息块:天气 / 时间 / 日期(保留三个独立元素) ===== */
.sky { grid-column: span 2; grid-row: span 2; display: flex; flex-direction: column; justify-content: center; gap: 6px; background: var(--blue); }
.card .label { font-size: .74rem; color: rgba(26,26,26,.65); letter-spacing: .1em; text-transform: uppercase; margin-bottom: 8px; font-weight: 900; }
.sky .emoji { font-size: 3.2rem; line-height: 1; }
.sky .temp { font-size: clamp(2.2rem, 4vw, 2.8rem); font-weight: 900; font-variant-numeric: tabular-nums; letter-spacing: -.02em; }
.wcity { font-size: .85rem; font-weight: 700; color: rgba(26,26,26,.7); min-height: 1.2em; }
.clockcard, .datecard { grid-column: span 3; display: flex; flex-direction: column; justify-content: center; }
.clockcard { background: var(--paper); }
.datecard { background: var(--green); }
.clockcard .value { font-size: clamp(1.9rem, 4vw, 2.6rem); font-weight: 900; 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: 900; font-variant-numeric: tabular-nums; letter-spacing: .02em; line-height: 1.15; }
/* ===== 服务分组面板:四色撞色块面 ===== */
.panel { grid-column: span 3; border-radius: 4px; }
.s-self { background: var(--green); }
.s-ai { background: var(--pink); }
.s-cloud { background: var(--blue); }
.s-util { background: var(--orange); }
.panel > h2 { display: flex; align-items: center; gap: 10px; font-size: 1.3rem; font-weight: 900; letter-spacing: -.01em; margin-bottom: 16px; }
.panel > h2 .count {
margin-left: auto; font-size: .78rem; font-weight: 900; color: var(--ink);
background: #fffbe8; border: 2px solid var(--ink); border-radius: 999px;
padding: 3px 12px; box-shadow: 2px 2px 0 var(--ink);
}
ul.tiles { list-style: none; display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 10px; }
ul.tiles a {
display: flex; align-items: center; gap: 10px;
padding: 12px 13px;
background: #fffbe8;
border: 3px solid var(--ink); border-radius: 4px;
box-shadow: 4px 4px 0 var(--ink);
text-decoration: none; color: var(--ink);
transition: transform .12s ease, box-shadow .12s ease;
}
ul.tiles a:hover { transform: translate(-2px,-2px); box-shadow: 7px 7px 0 var(--ink); }
ul.tiles a:focus-visible { outline: 3px dashed var(--ink); outline-offset: 2px; }
ul.tiles .icon {
font-size: 1.15rem; width: 40px; height: 40px; flex-shrink: 0;
display: flex; align-items: center; justify-content: center;
border: 2px solid var(--ink); border-radius: 3px; background: #fff;
}
ul.tiles .meta { display: flex; flex-direction: column; min-width: 0; flex: 1; }
ul.tiles .name { font-weight: 800; font-size: .92rem; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
ul.tiles .desc { color: rgba(26,26,26,.55); font-size: .74rem; font-weight: 600; margin-top: 2px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
ul.tiles .go { font-weight: 900; flex-shrink: 0; transform: translate(1px, -1px); }
/* ===== 页脚 ===== */
footer.card { background: none; border: none; box-shadow: none; padding: 8px 0; text-align: center; }
footer .brand { font-weight: 900; letter-spacing: .02em; margin-right: 10px; }
footer nav { display: inline-flex; flex-wrap: wrap; justify-content: center; gap: 6px 4px; font-size: .8rem; font-weight: 700; color: rgba(26,26,26,.75); }
footer a { color: var(--ink); text-decoration: none; border-bottom: 2px solid transparent; padding: 1px 2px; transition: background .1s ease; }
footer a:hover { background: #fffbe8; border-bottom-color: var(--ink); }
/* ===== 响应式:窄屏单列 ===== */
@media (max-width: 860px) {
.grid { grid-template-columns: 1fr; gap: 14px; }
.hero, .sky, .clockcard, .datecard, .panel { grid-column: auto; grid-row: auto; }
.sky { flex-direction: row; align-items: center; gap: 18px; }
.sky .emoji { font-size: 2.6rem; }
}
@media (max-width: 540px) {
.wrap { padding: 22px 14px 30px; }
ul.tiles { grid-template-columns: 1fr; }
}
@media (prefers-reduced-motion: reduce) {
.card { animation: none; }
.sticker { animation: none; }
ul.tiles a, form.search button { transition: none; }
}
</style>
</head>
<body>
<main class="wrap">
<div class="grid">
<!-- Hero:标题 + 搜索 -->
<section class="card hero" style="--d:.05s">
<span class="sticker">随时可达 ★</span>
<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">×4</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><span class="go">↗</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><span class="go">↗</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><span class="go">↗</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><span class="go">↗</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><span class="go">↗</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><span class="go">↗</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><span class="go">↗</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><span class="go">↗</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><span class="go">↗</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><span class="go">↗</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><span class="go">↗</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><span class="go">↗</span></a></li>
</ul>
</section>
<footer style="--d:.58s" class="card">
<span class="brand">hyonglin.top</span>
<nav>
<a href="/terminal/">终端版</a> /
<a href="/">Bento 版</a> /
<span aria-current="page">Brutal 版(当前)</span> /
<a href="/swiss/">Swiss 版</a> /
<a href="/hud/">HUD 版</a>
</nav>
</footer>
</div>
</main>
<script>
/* ===== 搜索引擎选择 ===== */
(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>