Files
portal.tootaio.com/index.html
xiaomai eb23a772d0 feat(ui): overhaul landing page with new design and search functionality
This commit completely rewrites the main landing page (index.html) to introduce a modern, data-driven design and new features.

The previous implementation had hardcoded data and styles directly within the HTML file. This refactor separates concerns and improves maintainability.

Key changes:
- Complete visual redesign with a modern "glassmorphism" aesthetic and a new color palette.
- Externalized all service and link data into a new `siteConfig.json` file, which is now fetched asynchronously.
- Added a search bar to dynamically filter the list of services.
- Implemented a keyboard shortcut ('/') to focus the search input.
- Rewrote the JavaScript logic for rendering content, making it more modular and easier to manage.
2025-09-11 15:44:22 +08:00

275 lines
14 KiB
HTML

<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>Tootaio — All In One</title>
<meta name="description" content="Tootaio — All In One. Self-hosted tools collection." />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;600;800&family=Playfair+Display:wght@600;700;900&display=swap" rel="stylesheet">
<!-- Optional: feather icons (small) -->
<script src="https://unpkg.com/feather-icons"></script>
<style>
:root{
--bg-1:#070406;
--bg-2:#0f0b0f;
--card:#0f0d10;
--muted: rgba(255,255,255,0.72);
--gold1: #FFD166;
--gold2: #F4A261;
--accent: linear-gradient(135deg,var(--gold1),var(--gold2));
}
*{box-sizing:border-box}
html,body{height:100%}
body{
margin:0;
font-family:Inter,system-ui,Segoe UI,Roboto,"Helvetica Neue",Arial;
background: radial-gradient(1200px 600px at 10% 10%, rgba(244,162,97,0.06), transparent),
radial-gradient(800px 400px at 90% 90%, rgba(255,209,102,0.04), transparent),
linear-gradient(180deg,var(--bg-1),var(--bg-2));
color: #fff; -webkit-font-smoothing:antialiased; -moz-osx-font-smoothing:grayscale;
-webkit-text-size-adjust:100%;
padding:32px;
}
/* Container */
.container{max-width:1200px;margin:0 auto}
/* Header */
.header{display:flex;align-items:center;justify-content:space-between;gap:16px}
.brand{display:flex;align-items:center;gap:14px}
.logo-wrap{width:72px;height:72px;border-radius:50%;background:var(--accent);display:flex;align-items:center;justify-content:center;box-shadow:0 10px 30px rgba(0,0,0,0.6);}
.logo-text{font-family:'Playfair Display',serif;font-weight:700;color:#1b1200;letter-spacing:1px}
.brand-meta{line-height:1}
.brand-title{font-weight:800;font-size:20px}
.brand-sub{font-size:12px;color:var(--muted)}
/* Hero */
.hero{display:flex;flex-wrap:wrap;gap:28px;margin-top:28px}
.hero-left{flex:1;min-width:300px}
.hero-right{width:480px;min-width:280px}
h1{font-family:'Playfair Display',serif;margin:0;font-weight:900;font-size:44px;line-height:1}
p.lead{color:var(--muted);margin-top:14px;max-width:700px}
.cta-row{margin-top:20px;display:flex;gap:12px}
.btn{padding:12px 18px;border-radius:8px;border:1px solid rgba(255,255,255,0.06);cursor:pointer;text-decoration:none;display:inline-flex;align-items:center;gap:8px}
.btn-primary{background:var(--accent);color:#1b1200;font-weight:700;box-shadow:0 8px 30px rgba(248,176,47,0.12)}
.btn-ghost{background:transparent;border:1px solid rgba(255,255,255,0.08);color:var(--muted)}
/* glass card */
.glass{background:linear-gradient(180deg, rgba(255,255,255,0.03), rgba(255,255,255,0.02)); border-radius:14px; padding:18px; border:1px solid rgba(255,255,255,0.04); box-shadow:0 10px 30px rgba(6,6,6,0.6)}
/* Services grid */
.services-grid{display:grid;grid-template-columns:repeat(2,1fr);gap:12px;margin-top:16px}
.service{padding:14px;border-radius:12px;background:linear-gradient(180deg, rgba(255,255,255,0.01), transparent);border:1px solid rgba(255,255,255,0.03);display:flex;align-items:center;justify-content:space-between;gap:12px;text-decoration:none;color:inherit}
.service:hover{transform:translateY(-6px);transition:transform 220ms cubic-bezier(.2,.9,.3,1)}
.service-title{font-weight:700}
.service-desc{font-size:12px;color:var(--muted);margin-top:6px}
/* Secondary links */
.links-row{display:flex;gap:12px;flex-wrap:wrap;margin-top:16px}
.link-pill{display:inline-flex;gap:8px;align-items:center;padding:8px 12px;border-radius:999px;border:1px solid rgba(255,255,255,0.04);background:rgba(255,255,255,0.02);text-decoration:none;color:var(--muted)}
/* Footer */
footer{margin-top:36px;padding-top:24px;border-top:1px solid rgba(255,255,255,0.03);display:flex;justify-content:space-between;align-items:center;flex-wrap:wrap;gap:12px}
/* search */
.search{display:flex;align-items:center;background:rgba(255,255,255,0.02);padding:8px 12px;border-radius:12px;border:1px solid rgba(255,255,255,0.03)}
.search input{background:transparent;border:0;color:inherit;outline:none;width:260px}
/* responsiveness */
@media (max-width:900px){
.hero{flex-direction:column}
.hero-right{width:auto}
.services-grid{grid-template-columns:1fr}
h1{font-size:36px}
}
/* subtle marble SVG overlay */
.marble{position:absolute;right:-120px;top:-120px;opacity:0.06;pointer-events:none}
/* small helper */
.muted{color:var(--muted)}
</style>
</head>
<body>
<div class="container">
<header class="header">
<div class="brand">
<div class="logo-wrap"><div class="logo-text">T AIO</div></div>
<div class="brand-meta">
<div class="brand-title">Tootaio</div>
<div class="brand-sub">All In One · 私有托管 · 高端体验</div>
</div>
</div>
<div style="display:flex;align-items:center;gap:10px">
<div class="search glass">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="7"></circle><line x1="21" y1="21" x2="16.65" y2="16.65"></line></svg>
<input id="q" placeholder="Search services... (按回车)" />
</div>
<button id="langBtn" class="btn btn-ghost">中文</button>
</div>
</header>
<main class="hero">
<div class="hero-left">
<h1>奢华·私有·一站式 —— <span style="background:linear-gradient(90deg,var(--gold1),var(--gold2));-webkit-background-clip:text;background-clip:text;color:transparent">Tootaio</span></h1>
<p class="lead">将最常用的自托管服务与精品工具收纳于一处。我们为个人与团队提供稳定、可控且有温度的基础设施体验。</p>
<div class="cta-row">
<a class="btn btn-primary" href="#services">查看服务</a>
<a class="btn btn-ghost" href="#contact">联系我们</a>
</div>
<div class="links-row" id="otherLinksRegion"></div>
</div>
<div class="hero-right">
<div class="glass" id="services">
<div style="display:flex;align-items:center;justify-content:space-between">
<div>
<div style="font-weight:800">精品服务速览</div>
<div class="muted" style="font-size:12px;margin-top:6px">点击卡片以在新标签页打开服务</div>
</div>
<div id="stats" class="muted" style="font-size:12px">加载中…</div>
</div>
<div class="services-grid" id="servicesGrid" style="margin-top:12px"></div>
</div>
</div>
</main>
<footer id="contact">
<div>
<div style="font-weight:700">Tootaio</div>
<div class="muted" style="font-size:13px;margin-top:6px">欲了解更多,请通过下面链接联系。© <span id="year"></span></div>
</div>
<div id="footerLinks" style="display:flex;gap:12px;align-items:center"></div>
</footer>
</div>
<!-- decorative SVG marble -->
<svg class="marble" width="360" height="360" viewBox="0 0 200 200" fill="none" xmlns="http://www.w3.org/2000/svg">
<defs>
<linearGradient id="g1" x1="0%" x2="100%"><stop offset="0%" stop-color="#FFD166" stop-opacity="0.16"/><stop offset="100%" stop-color="#F4A261" stop-opacity="0.08"/></linearGradient>
</defs>
<circle cx="100" cy="100" r="80" fill="url(#g1)"/>
</svg>
<script>
// Default fallback config (your siteConfig.json can override this by being placed next to index.html)
const defaultSiteConfig = {};
// Try to fetch siteConfig.json; fall back to defaultSiteConfig
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(err){
console.warn('Using embedded default siteConfig (could not fetch ./siteConfig.json) —', err.message);
return defaultSiteConfig;
}
}
// render helpers
function el(tag, props={}, children=null){
const e = document.createElement(tag);
for(const k in props){
if(k === 'class') e.className = props[k];
else if(k === 'html') e.innerHTML = props[k];
else e.setAttribute(k, props[k]);
}
if(children){
if(Array.isArray(children)) children.forEach(c => e.appendChild(typeof c === 'string' ? document.createTextNode(c) : c));
else e.appendChild(typeof children === 'string' ? document.createTextNode(children) : children);
}
return e;
}
// icon map
const ICONS = {
'github': '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M9 19c-5 1.5-5-2.5-7-3"/><path d="M20 8c0-3.6-2.1-6-4.5-6S11 4.4 11 8c0 3.2 2.2 5.2 5 6 2.8.8 5 2.8 5 6 0 3-2.5 4-4.5 4-1 0-2.5-.3-3.5-1.2"/></svg>',
'blog': '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="4" width="18" height="16" rx="2"/><path d="M8 2v4"/><path d="M16 2v4"/></svg>',
'discord': '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M8 14s1.5 1 4 1 4-1 4-1"/><path d="M7 7s1.5-.5 5-1c3.5.5 5 1 5 1v8s-1.5-.8-5-1c-3.2.2-5 1-5 1V7z"/></svg>',
'video': '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="7" width="15" height="10" rx="2"/><polygon points="23 7 16 12 23 17 23 7"/></svg>'
};
function renderServices(cfg, lang='zh'){
const grid = document.getElementById('servicesGrid'); grid.innerHTML = '';
(cfg.services || []).forEach(s => {
const a = el('a',{class:'service',href:s.url,target:'_blank',rel:'noreferrer'});
const left = el('div');
const title = el('div',{class:'service-title'}, s.name);
const desc = el('div',{class:'service-desc'}, lang === 'zh' ? (s.description_zh || s.description_en || '') : (s.description_en || s.description_zh || ''));
left.appendChild(title); left.appendChild(desc);
const arrow = el('div',{html:'<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14"/><path d="M12 5l7 7-7 7"/></svg>'});
a.appendChild(left); a.appendChild(arrow);
grid.appendChild(a);
});
document.getElementById('stats').textContent = (cfg.services||[]).length + ' services';
}
function renderOtherLinks(cfg, lang='zh'){
const region = document.getElementById('otherLinksRegion'); region.innerHTML = '';
const footer = document.getElementById('footerLinks'); footer.innerHTML = '';
(cfg.otherLinks || []).forEach(l =>{
const pill = el('a',{class:'link-pill',href:l.url,target:'_blank',rel:'noreferrer',html:(ICONS[l.icon]||ICONS['blog']) + ' ' + (lang==='zh'?l.name_zh:l.name_en)});
region.appendChild(pill);
const f = el('a',{href:l.url,target:'_blank',rel:'noreferrer',class:'link-pill',html:(ICONS[l.icon]||'') + ' ' + (lang==='zh'?l.name_zh:l.name_en)});
footer.appendChild(f);
});
}
(async function init(){
const cfg = await loadConfig();
let lang = 'zh';
renderServices(cfg, lang);
renderOtherLinks(cfg, lang);
// year
document.getElementById('year').textContent = new Date().getFullYear();
// search
const q = document.getElementById('q');
q.addEventListener('keydown', (e)=>{
if(e.key === 'Enter'){
const term = q.value.trim().toLowerCase();
if(!term){ renderServices(cfg, lang); return; }
const filtered = (cfg.services||[]).filter(s => (s.name + ' ' + (s.description_en||'') + ' ' + (s.description_zh||'')).toLowerCase().includes(term));
renderServices({services:filtered}, lang);
}
});
// language toggle
const langBtn = document.getElementById('langBtn');
langBtn.addEventListener('click', ()=>{
lang = (lang === 'zh') ? 'en' : 'zh';
langBtn.textContent = (lang === 'zh') ? '中文' : 'EN';
renderServices(cfg, lang);
renderOtherLinks(cfg, lang);
// heading translations (minimal)
document.querySelector('.lead').textContent = (lang === 'zh') ? '将最常用的自托管服务与精品工具收纳于一处。我们为个人与团队提供稳定、可控且有温度的基础设施体验。' : 'We gather the most useful self-hosted tools — from source hosting and cloud storage to game asset mirrors. Tootaio delivers luxurious, stable solutions for individuals and teams.';
});
// quick keyboard: '/' focus search
window.addEventListener('keydown', (e)=>{ if(e.key === '/' && document.activeElement !== q){ e.preventDefault(); q.focus(); }});
})();
</script>
<!-- Usage notes (as comments):
- Drop your siteConfig.json next to this index.html (same folder). The page will try to fetch it and use it.
- You can freely edit styles in the <style> block to tune the "luxury" palette and spacing.
- If you want small enhancements: add pagination, category tags, icons per service, or a small admin UI to edit siteConfig in-browser.
-->
</body>
</html>