Files
portal.tootaio.com/index.html
xiaomai 210ad4b97b refactor(ui): implement compact and modern UI redesign
This commit introduces a significant UI overhaul for a more compact, modern, and information-dense layout.

Key visual changes:
- Adjusted color palette, spacing, font sizes, and border radii for a cleaner look.
- Replaced centered section titles with a left-aligned design featuring icons.
- Improved card header layout to better handle long titles and URLs.
- Grouped search bar and language toggle into a unified control area.

Additionally, a fallback mechanism has been added to the script. The page will now use a default configuration if `siteConfig.json` is unavailable, improving robustness.
2025-09-11 17:03:48 +08:00

682 lines
22 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 name="viewport" content="width=device-width, initial-scale=1.0">
<title>Tootaio Studio - 游戏开发与工具服务</title>
<meta name="description" content="Tootaio工作室专注于游戏开发与工具服务提供多种在线工具和资源">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
<style>
:root {
--bg-primary: #0f0f1a;
--bg-secondary: #1a1a2a;
--bg-card: #222236;
--bg-card-hover: #2a2a40;
--accent-primary: #ff9e44;
--accent-secondary: #ff6b6b;
--text-primary: #ffffff;
--text-secondary: #b8b8d0;
--text-muted: #7a7a9d;
--border-color: rgba(255, 255, 255, 0.08);
--gradient: linear-gradient(135deg, var(--accent-primary), var(--accent-secondary));
--shadow: 0 5px 15px rgba(0, 0, 0, 0.2);
--radius: 12px;
--transition: all 0.2s ease;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Inter', sans-serif;
background: var(--bg-primary);
color: var(--text-primary);
line-height: 1.5;
padding: 0;
overflow-x: hidden;
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: 0 20px;
}
/* Header Styles */
header {
padding: 30px 0 20px;
text-align: center;
background: var(--bg-primary);
border-bottom: 1px solid var(--border-color);
}
.logo {
display: inline-flex;
align-items: center;
justify-content: center;
width: 60px;
height: 60px;
border-radius: 50%;
background: var(--gradient);
margin-bottom: 15px;
box-shadow: var(--shadow);
transition: var(--transition);
}
.logo:hover {
transform: scale(1.05);
}
.logo-text {
font-weight: 700;
font-size: 20px;
color: #fff;
}
h1 {
font-size: 32px;
margin-bottom: 8px;
background: linear-gradient(to right, var(--accent-primary), var(--accent-secondary));
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.tagline {
font-size: 16px;
color: var(--text-secondary);
max-width: 500px;
margin: 0 auto 20px;
}
.header-controls {
display: flex;
justify-content: center;
align-items: center;
gap: 15px;
margin-top: 15px;
}
.search-container {
position: relative;
width: 300px;
}
.search-box {
width: 100%;
padding: 12px 15px;
background: rgba(255, 255, 255, 0.05);
border: 1px solid var(--border-color);
border-radius: 30px;
color: var(--text-primary);
font-size: 14px;
padding-left: 40px;
transition: var(--transition);
}
.search-box:focus {
outline: none;
border-color: var(--accent-primary);
box-shadow: 0 0 0 2px rgba(255, 158, 68, 0.2);
}
.search-icon {
position: absolute;
left: 15px;
top: 50%;
transform: translateY(-50%);
color: var(--text-muted);
font-size: 14px;
}
.lang-btn {
background: rgba(255, 255, 255, 0.05);
border: 1px solid var(--border-color);
color: var(--text-secondary);
padding: 10px 15px;
border-radius: 30px;
cursor: pointer;
transition: var(--transition);
font-size: 14px;
}
.lang-btn:hover {
background: rgba(255, 255, 255, 0.1);
}
/* Main Content */
main {
padding: 40px 0;
}
.section-title {
font-size: 22px;
margin-bottom: 25px;
position: relative;
padding-left: 15px;
display: flex;
align-items: center;
gap: 10px;
}
.section-title::before {
content: '';
width: 5px;
height: 22px;
background: var(--gradient);
border-radius: 10px;
}
.section-title i {
color: var(--accent-primary);
font-size: 18px;
}
.grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: 20px;
margin-bottom: 40px;
}
.card {
background: var(--bg-card);
border-radius: var(--radius);
overflow: hidden;
transition: var(--transition);
border: 1px solid var(--border-color);
box-shadow: var(--shadow);
display: flex;
flex-direction: column;
height: 100%;
}
.card:hover {
transform: translateY(-3px);
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.3);
border-color: rgba(255, 158, 68, 0.2);
}
.card-header {
padding: 18px;
position: relative;
display: flex;
align-items: center;
gap: 12px;
border-bottom: 1px solid var(--border-color);
}
.card-icon {
width: 40px;
height: 40px;
border-radius: 10px;
background: var(--gradient);
display: flex;
align-items: center;
justify-content: center;
font-size: 16px;
flex-shrink: 0;
}
.card-title-container {
flex: 1;
min-width: 0;
}
.card-title {
font-size: 16px;
font-weight: 600;
margin-bottom: 4px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.card-url {
color: var(--accent-primary);
font-size: 12px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.card-body {
padding: 18px;
flex-grow: 1;
}
.card-description {
color: var(--text-secondary);
font-size: 14px;
line-height: 1.5;
}
.card-footer {
padding: 0 18px 18px;
}
.card-link {
display: inline-flex;
align-items: center;
padding: 8px 16px;
background: rgba(255, 158, 68, 0.1);
color: var(--accent-primary);
border-radius: 20px;
text-decoration: none;
font-weight: 500;
font-size: 14px;
transition: var(--transition);
}
.card-link:hover {
background: rgba(255, 158, 68, 0.2);
transform: translateX(3px);
}
.card-link i {
margin-left: 6px;
font-size: 12px;
}
/* Footer */
footer {
background: var(--bg-secondary);
padding: 30px 0;
border-top: 1px solid var(--border-color);
}
.footer-content {
display: flex;
justify-content: space-between;
align-items: center;
flex-wrap: wrap;
gap: 15px;
}
.footer-logo {
font-weight: 700;
font-size: 18px;
}
.footer-links {
display: flex;
gap: 15px;
}
.footer-link {
color: var(--text-secondary);
text-decoration: none;
transition: var(--transition);
font-size: 14px;
}
.footer-link:hover {
color: var(--accent-primary);
}
.copyright {
color: var(--text-muted);
font-size: 13px;
margin-top: 20px;
width: 100%;
text-align: center;
}
/* Responsive */
@media (max-width: 768px) {
h1 {
font-size: 28px;
}
.grid {
grid-template-columns: 1fr;
}
.header-controls {
flex-direction: column;
}
.search-container {
width: 100%;
max-width: 300px;
}
.footer-content {
flex-direction: column;
text-align: center;
}
.footer-links {
justify-content: center;
flex-wrap: wrap;
}
}
/* Animation */
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.card {
animation: fadeIn 0.4s ease-out;
}
/* 紧凑型调整 */
.compact-view .card-header {
padding: 15px;
}
.compact-view .card-body {
padding: 15px;
}
.compact-view .card-footer {
padding: 0 15px 15px;
}
.compact-view .grid {
gap: 15px;
}
</style>
</head>
<body>
<header>
<div class="container">
<div class="logo">
<span class="logo-text">T</span>
</div>
<h1>Tootaio Studio</h1>
<p class="tagline">专注于游戏开发与创新工具服务的创意工作室</p>
<div class="header-controls">
<div class="search-container">
<i class="fas fa-search search-icon"></i>
<input type="text" class="search-box" placeholder="搜索服务或工具..." id="searchInput">
</div>
<button class="lang-btn" id="langToggle">EN</button>
</div>
</div>
</header>
<main class="container compact-view">
<h2 class="section-title">
<i class="fas fa-server"></i>
<span>我们的服务</span>
</h2>
<div class="grid" id="servicesGrid">
<!-- 服务卡片将通过JavaScript动态生成 -->
</div>
<h2 class="section-title">
<i class="fas fa-link"></i>
<span>其他链接</span>
</h2>
<div class="grid" id="linksGrid">
<!-- 链接卡片将通过JavaScript动态生成 -->
</div>
</main>
<footer>
<div class="container">
<div class="footer-content">
<div class="footer-logo">Tootaio</div>
<div class="footer-links">
<a href="#" class="footer-link">关于我们</a>
<a href="#" class="footer-link">联系方式</a>
<a href="#" class="footer-link">使用条款</a>
</div>
</div>
<div class="copyright">
&copy; <span id="currentYear"></span> Tootaio Studio. 保留所有权利。
</div>
</div>
</footer>
<script>
// 默认配置
const defaultSiteConfig = {
services: [
{
"name": "git.tootaio.com",
"url": "https://git.tootaio.com",
"description_en": "Self-hosted Gitea open source community platform.",
"description_zh": "自托管的 Gitea 开源社区平台。",
"icon": "code-branch"
},
{
"name": "nas.tootaio.com",
"url": "https://nas.tootaio.com",
"description_en": "Cloud storage and file sharing service.",
"description_zh": "云端存储和文件共享服务。",
"icon": "cloud"
},
{
"name": "torrent.tootaio.com",
"url": "https://torrent.tootaio.com",
"description_en": "Download torrent files to the server with the help of the server.",
"description_zh": "借助服务器,将 Torrent 文件下载到服务器中。",
"icon": "download"
},
{
"name": "wiki.tootaio.com",
"url": "https://wiki.tootaio.com",
"description_en": "Our studio's various game/project planning wiki.",
"description_zh": "我们工作室的各个游戏/项目策划维基百科。",
"icon": "book"
},
{
"name": "kenney-assets.tootaio.com",
"url": "https://kenney-assets.tootaio.com",
"description_en": "Unofficial mirror site for Kenney's assets.",
"description_zh": "Kenney资源非官网镜像站。",
"icon": "palette"
},
{
"name": "itch-gd-dl.tootaio.com",
"url": "https://itch-gd-dl.tootaio.com",
"description_en": "Itch.io Godot game crawler and downloader.",
"description_zh": "Itch.io Godot 游戏爬取器。",
"icon": "gamepad"
},
{
"name": "pdf.tootaio.com",
"url": "https://pdf.tootaio.com",
"description_en": "Online PDF tools and utilities.",
"description_zh": "在线PDF工具集。",
"icon": "file-pdf"
},
{
"name": "life-restart.tootaio.com",
"url": "https://life-restart.tootaio.com",
"description_en": "Life Restart Simulator mirror site.",
"description_zh": "人生重开模拟器镜像站。",
"icon": "redo"
},
{
"name": "memos.tootaio.com",
"url": "https://memos.tootaio.com",
"description_en": "Note-taking space and knowledge management.",
"description_zh": "笔记空间和知识管理。",
"icon": "sticky-note"
},
{
"name": "json.tootaio.com",
"url": "https://json.tootaio.com",
"description_en": "JSON Hero viewer for visualizing JSON data.",
"description_zh": "JSON Hero 查看器可视化JSON数据。",
"icon": "code"
}
],
otherLinks: [
{
"name_en": "Kingsmai's Personal Blog",
"name_zh": "Kingsmai 的个人博客",
"url": "https://kingsmai.github.io",
"icon": "blog"
},
{
"name_en": "GitHub Profile",
"name_zh": "GitHub 主页",
"url": "https://github.com/kingsmai",
"icon": "github"
},
{
"name_en": "Discord Community",
"name_zh": "Discord 社区",
"url": "https://discord.com/invite/sJcv7ZM",
"icon": "discord"
},
{
"name_en": "BiliBili Space",
"name_zh": "BiliBili 空间",
"url": "https://space.bilibili.com/670118055",
"icon": "video"
}
]
};
// 配置数据
async function loadConfig() {
try {
const res = await fetch('./siteConfig.json', { cache: 'no-store' });
if (!res.ok) throw new Error('no siteConfig.json');
const cfg = await res.json();
return cfg;
} catch (e) {
console.warn('siteConfig.json not found, using default config.');
return defaultSiteConfig;
}
}
// 当前语言设置
let currentLang = 'zh';
// 初始化页面
document.addEventListener('DOMContentLoaded', function () {
// 设置当前年份
document.getElementById('currentYear').textContent = new Date().getFullYear();
// 渲染内容
renderContent();
// 添加搜索功能
document.getElementById('searchInput').addEventListener('input', function (e) {
filterContent(e.target.value);
});
// 添加语言切换功能
document.getElementById('langToggle').addEventListener('click', function () {
currentLang = currentLang === 'zh' ? 'en' : 'zh';
this.textContent = currentLang === 'zh' ? 'EN' : '中文';
renderContent();
});
});
// 渲染内容
async function renderContent() {
const siteConfig = await loadConfig();
renderServices(siteConfig);
renderLinks(siteConfig);
}
// 渲染服务卡片
function renderServices(siteConfig) {
const grid = document.getElementById('servicesGrid');
grid.innerHTML = '';
siteConfig.services.forEach(service => {
const card = document.createElement('div');
card.className = 'card';
card.innerHTML = `
<div class="card-header">
<div class="card-icon">
<i class="fas fa-${service.icon || 'link'}"></i>
</div>
<div class="card-title-container">
<h3 class="card-title">${service.name}</h3>
<span class="card-url">${service.url.replace('https://', '')}</span>
</div>
</div>
<div class="card-body">
<p class="card-description">${currentLang === 'zh' ? service.description_zh : service.description_en}</p>
</div>
<div class="card-footer">
<a href="${service.url}" target="_blank" class="card-link">
${currentLang === 'zh' ? '访问服务' : 'Visit Service'}
<i class="fas fa-arrow-right"></i>
</a>
</div>
`;
grid.appendChild(card);
});
}
// 渲染链接卡片
function renderLinks(siteConfig) {
const grid = document.getElementById('linksGrid');
grid.innerHTML = '';
siteConfig.otherLinks.forEach(link => {
const card = document.createElement('div');
card.className = 'card';
card.innerHTML = `
<div class="card-header">
<div class="card-icon">
<i class="fab fa-${link.icon || 'link'}"></i>
</div>
<div class="card-title-container">
<h3 class="card-title">${currentLang === 'zh' ? link.name_zh : link.name_en}</h3>
<span class="card-url">${link.url.replace('https://', '')}</span>
</div>
</div>
<div class="card-footer">
<a href="${link.url}" target="_blank" class="card-link">
${currentLang === 'zh' ? '访问链接' : 'Visit Link'}
<i class="fas fa-arrow-right"></i>
</a>
</div>
`;
grid.appendChild(card);
});
}
// 过滤内容
function filterContent(searchTerm) {
const services = document.querySelectorAll('#servicesGrid .card');
const links = document.querySelectorAll('#linksGrid .card');
const allItems = [...services, ...links];
const term = searchTerm.toLowerCase();
if (term === '') {
allItems.forEach(item => item.style.display = 'flex');
return;
}
allItems.forEach(item => {
const title = item.querySelector('.card-title').textContent.toLowerCase();
const description = item.querySelector('.card-description')?.textContent.toLowerCase() || '';
const url = item.querySelector('.card-url').textContent.toLowerCase();
if (title.includes(term) || description.includes(term) || url.includes(term)) {
item.style.display = 'flex';
} else {
item.style.display = 'none';
}
});
}
</script>
</body>
</html>