feat(ui): introduce games-first landing page design
This commit completely redesigns the landing page to prioritize games over tools, reflecting a strategic shift in focus. The previous generic list has been replaced with a modern, two-column layout. - A new 'Games' section displays projects as large, visual cards with thumbnails. - A secondary 'Tools' section lists other services in a compact format. - The CSS has been entirely rewritten for a new visual identity, improved responsiveness, and better animations. - JavaScript logic is updated to categorize services into 'games' or 'tools' and render them accordingly.
This commit is contained in:
734
index.html
734
index.html
@@ -3,272 +3,616 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||||
<title>Tootaio — All In One</title>
|
<title>Tootaio — Games First</title>
|
||||||
<meta name="description" content="Tootaio — All In One. Self-hosted tools collection." />
|
<meta name="description" content="Tootaio — Game studio first, tools second." />
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;600;700;800&family=Playfair+Display:wght@600;700;800&display=swap" rel="stylesheet">
|
||||||
<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">
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
||||||
<!-- Optional: feather icons (small) -->
|
|
||||||
<script src="https://unpkg.com/feather-icons"></script>
|
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
:root{
|
:root {
|
||||||
--bg-1:#070406;
|
--bg: #07060a;
|
||||||
--bg-2:#0f0b0f;
|
--card: #131118;
|
||||||
--card:#0f0d10;
|
--card-hover: #1a1721;
|
||||||
--muted: rgba(255,255,255,0.72);
|
--muted: #bdb8c4;
|
||||||
--gold1: #FFD166;
|
--text: #f5f3f7;
|
||||||
--gold2: #F4A261;
|
--accent: #ffbf58;
|
||||||
--accent: linear-gradient(135deg,var(--gold1),var(--gold2));
|
--accent-2: #ff7b7b;
|
||||||
}
|
--accent-gradient: linear-gradient(135deg, var(--accent), var(--accent-2));
|
||||||
*{box-sizing:border-box}
|
--gap: 24px;
|
||||||
html,body{height:100%}
|
--radius: 16px;
|
||||||
body{
|
--shadow: 0 10px 30px rgba(0, 0, 0, 0.4);
|
||||||
margin:0;
|
--transition: all 0.3s ease;
|
||||||
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}
|
box-sizing: border-box;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
/* Header */
|
body {
|
||||||
.header{display:flex;align-items:center;justify-content:space-between;gap:16px}
|
margin: 0;
|
||||||
.brand{display:flex;align-items:center;gap:14px}
|
min-height: 100vh;
|
||||||
.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);}
|
background: linear-gradient(180deg, #050406 0%, #0b0710 100%);
|
||||||
.logo-text{font-family:'Playfair Display',serif;font-weight:700;color:#1b1200;letter-spacing:1px}
|
color: var(--text);
|
||||||
.brand-meta{line-height:1}
|
font-family: Inter, system-ui, Segoe UI, Roboto, sans-serif;
|
||||||
.brand-title{font-weight:800;font-size:20px}
|
padding: 32px;
|
||||||
.brand-sub{font-size:12px;color:var(--muted)}
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
/* Hero */
|
.wrap {
|
||||||
.hero{display:flex;flex-wrap:wrap;gap:28px;margin-top:28px}
|
max-width: 1280px;
|
||||||
.hero-left{flex:1;min-width:300px}
|
margin: 0 auto;
|
||||||
.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 */
|
/* Header Styles */
|
||||||
.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)}
|
header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 20px;
|
||||||
|
margin-bottom: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
/* Services grid */
|
.logo {
|
||||||
.services-grid{display:grid;grid-template-columns:repeat(2,1fr);gap:12px;margin-top:16px}
|
display: flex;
|
||||||
.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}
|
gap: 16px;
|
||||||
.service:hover{transform:translateY(-6px);transition:transform 220ms cubic-bezier(.2,.9,.3,1)}
|
align-items: center;
|
||||||
.service-title{font-weight:700}
|
transition: var(--transition);
|
||||||
.service-desc{font-size:12px;color:var(--muted);margin-top:6px}
|
}
|
||||||
|
|
||||||
/* Secondary links */
|
.logo:hover {
|
||||||
.links-row{display:flex;gap:12px;flex-wrap:wrap;margin-top:16px}
|
transform: translateY(-2px);
|
||||||
.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)}
|
}
|
||||||
|
|
||||||
|
.logo-mark {
|
||||||
|
width: 64px;
|
||||||
|
height: 64px;
|
||||||
|
border-radius: 16px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-weight: 800;
|
||||||
|
font-family: Playfair Display, serif;
|
||||||
|
background: var(--accent-gradient);
|
||||||
|
color: #251200;
|
||||||
|
box-shadow: 0 8px 30px rgba(255, 150, 0, 0.2);
|
||||||
|
font-size: 22px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-family: Playfair Display, serif;
|
||||||
|
margin: 0;
|
||||||
|
font-size: 36px;
|
||||||
|
font-weight: 800;
|
||||||
|
letter-spacing: -0.5px;
|
||||||
|
background: linear-gradient(to right, #ffbf58, #ff7b7b);
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
-webkit-text-fill-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.subtitle {
|
||||||
|
color: var(--muted);
|
||||||
|
font-size: 14px;
|
||||||
|
margin-top: 8px;
|
||||||
|
font-weight: 400;
|
||||||
|
}
|
||||||
|
|
||||||
|
.top-controls {
|
||||||
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search {
|
||||||
|
background: rgba(255, 255, 255, 0.03);
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.06);
|
||||||
|
padding: 12px 16px;
|
||||||
|
border-radius: 12px;
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
align-items: center;
|
||||||
|
transition: var(--transition);
|
||||||
|
}
|
||||||
|
|
||||||
|
.search:focus-within {
|
||||||
|
border-color: rgba(255, 191, 88, 0.3);
|
||||||
|
box-shadow: 0 0 0 3px rgba(255, 191, 88, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.search input {
|
||||||
|
background: transparent;
|
||||||
|
border: 0;
|
||||||
|
outline: none;
|
||||||
|
color: inherit;
|
||||||
|
width: 240px;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search input::placeholder {
|
||||||
|
color: var(--muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
padding: 12px 18px;
|
||||||
|
border-radius: 12px;
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||||
|
background: rgba(255, 255, 255, 0.03);
|
||||||
|
color: var(--muted);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: var(--transition);
|
||||||
|
font-weight: 500;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn:hover {
|
||||||
|
background: rgba(255, 255, 255, 0.06);
|
||||||
|
border-color: rgba(255, 255, 255, 0.12);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Main Content */
|
||||||
|
.main {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 2fr 1fr;
|
||||||
|
gap: var(--gap);
|
||||||
|
margin-top: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-title {
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: 700;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-title i {
|
||||||
|
color: var(--accent);
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.games, .tools {
|
||||||
|
background: linear-gradient(180deg, rgba(255, 255, 255, 0.03), transparent);
|
||||||
|
padding: 24px;
|
||||||
|
border-radius: var(--radius);
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.05);
|
||||||
|
box-shadow: var(--shadow);
|
||||||
|
transition: var(--transition);
|
||||||
|
}
|
||||||
|
|
||||||
|
.games:hover, .tools:hover {
|
||||||
|
border-color: rgba(255, 255, 255, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
.games-header, .tools-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.count {
|
||||||
|
font-size: 13px;
|
||||||
|
color: var(--muted);
|
||||||
|
background: rgba(255, 255, 255, 0.03);
|
||||||
|
padding: 4px 10px;
|
||||||
|
border-radius: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Games Grid */
|
||||||
|
.games-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(2, 1fr);
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.game-card {
|
||||||
|
position: relative;
|
||||||
|
border-radius: 14px;
|
||||||
|
overflow: hidden;
|
||||||
|
cursor: pointer;
|
||||||
|
min-height: 180px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: flex-end;
|
||||||
|
padding: 16px;
|
||||||
|
background-size: cover;
|
||||||
|
background-position: center;
|
||||||
|
border: 1px solid rgba(0, 0, 0, 0.3);
|
||||||
|
transition: var(--transition);
|
||||||
|
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.game-card::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background: linear-gradient(to top, rgba(0, 0, 0, 0.8) 0%, transparent 70%);
|
||||||
|
opacity: 0.8;
|
||||||
|
transition: var(--transition);
|
||||||
|
}
|
||||||
|
|
||||||
|
.game-card:hover {
|
||||||
|
transform: translateY(-5px);
|
||||||
|
box-shadow: 0 12px 24px rgba(0, 0, 0, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.game-card:hover::before {
|
||||||
|
opacity: 0.9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.game-card .meta {
|
||||||
|
position: relative;
|
||||||
|
z-index: 2;
|
||||||
|
backdrop-filter: blur(6px);
|
||||||
|
background: linear-gradient(180deg, rgba(0, 0, 0, 0.3), rgba(0, 0, 0, 0.6));
|
||||||
|
padding: 12px;
|
||||||
|
border-radius: 10px;
|
||||||
|
transition: var(--transition);
|
||||||
|
}
|
||||||
|
|
||||||
|
.game-card:hover .meta {
|
||||||
|
background: linear-gradient(180deg, rgba(0, 0, 0, 0.4), rgba(0, 0, 0, 0.7));
|
||||||
|
}
|
||||||
|
|
||||||
|
.game-title {
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 16px;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.game-sub {
|
||||||
|
font-size: 13px;
|
||||||
|
color: var(--muted);
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Tools List */
|
||||||
|
.tools-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tool-item {
|
||||||
|
display: flex;
|
||||||
|
gap: 16px;
|
||||||
|
align-items: center;
|
||||||
|
padding: 16px;
|
||||||
|
border-radius: 14px;
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.05);
|
||||||
|
background: rgba(255, 255, 255, 0.02);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: var(--transition);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tool-item:hover {
|
||||||
|
transform: translateX(5px);
|
||||||
|
background: rgba(255, 255, 255, 0.05);
|
||||||
|
border-color: rgba(255, 255, 255, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tool-icon {
|
||||||
|
width: 52px;
|
||||||
|
height: 52px;
|
||||||
|
border-radius: 12px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-weight: 700;
|
||||||
|
background: var(--accent-gradient);
|
||||||
|
color: #251200;
|
||||||
|
font-size: 18px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tool-content {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tool-title {
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 16px;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tool-desc {
|
||||||
|
font-size: 13px;
|
||||||
|
color: var(--muted);
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
|
||||||
/* Footer */
|
/* 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}
|
footer {
|
||||||
|
margin-top: 40px;
|
||||||
/* search */
|
display: flex;
|
||||||
.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)}
|
justify-content: space-between;
|
||||||
.search input{background:transparent;border:0;color:inherit;outline:none;width:260px}
|
align-items: center;
|
||||||
|
color: var(--muted);
|
||||||
/* responsiveness */
|
font-size: 14px;
|
||||||
@media (max-width:900px){
|
padding-top: 20px;
|
||||||
.hero{flex-direction:column}
|
border-top: 1px solid rgba(255, 255, 255, 0.05);
|
||||||
.hero-right{width:auto}
|
|
||||||
.services-grid{grid-template-columns:1fr}
|
|
||||||
h1{font-size:36px}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* subtle marble SVG overlay */
|
.footer-brand {
|
||||||
.marble{position:absolute;right:-120px;top:-120px;opacity:0.06;pointer-events:none}
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
/* small helper */
|
.footer-tagline {
|
||||||
.muted{color:var(--muted)}
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-tagline i {
|
||||||
|
color: var(--accent);
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Responsive */
|
||||||
|
@media (max-width: 1024px) {
|
||||||
|
.main {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.games-grid {
|
||||||
|
grid-template-columns: repeat(2, 1fr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
body {
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
header {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.top-controls {
|
||||||
|
width: 100%;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search input {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.games-grid {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer {
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 12px;
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 480px) {
|
||||||
|
.logo-mark {
|
||||||
|
width: 52px;
|
||||||
|
height: 52px;
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: 28px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
padding: 10px 14px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Animation */
|
||||||
|
@keyframes fadeIn {
|
||||||
|
from { opacity: 0; transform: translateY(10px); }
|
||||||
|
to { opacity: 1; transform: translateY(0); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.game-card, .tool-item {
|
||||||
|
animation: fadeIn 0.5s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Scrollbar */
|
||||||
|
::-webkit-scrollbar {
|
||||||
|
width: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-track {
|
||||||
|
background: rgba(255, 255, 255, 0.03);
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-thumb {
|
||||||
|
background: rgba(255, 255, 255, 0.1);
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: rgba(255, 255, 255, 0.2);
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="container">
|
<div class="wrap">
|
||||||
<header class="header">
|
<header>
|
||||||
<div class="brand">
|
<div class="logo">
|
||||||
<div class="logo-wrap"><div class="logo-text">T AIO</div></div>
|
<div class="logo-mark">T</div>
|
||||||
<div class="brand-meta">
|
<div>
|
||||||
<div class="brand-title">Tootaio</div>
|
<h1>Tootaio</h1>
|
||||||
<div class="brand-sub">All In One · 私有托管 · 高端体验</div>
|
<div class="subtitle">Game Studio · Tools as a service</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style="display:flex;align-items:center;gap:10px">
|
<div class="top-controls">
|
||||||
<div class="search glass">
|
<div class="search">
|
||||||
<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>
|
<i class="fas fa-search"></i>
|
||||||
<input id="q" placeholder="Search services... (按回车)" />
|
<input id="q" placeholder="查找游戏或工具(回车)" />
|
||||||
</div>
|
</div>
|
||||||
<button id="langBtn" class="btn btn-ghost">中文</button>
|
<button id="langBtn" class="btn">
|
||||||
|
<i class="fas fa-globe"></i>
|
||||||
|
<span>中文</span>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<main class="hero">
|
<main class="main">
|
||||||
<div class="hero-left">
|
<section class="games" aria-label="Games">
|
||||||
<h1>奢华·私有·一站式 —— <span style="background:linear-gradient(90deg,var(--gold1),var(--gold2));-webkit-background-clip:text;background-clip:text;color:transparent">Tootaio</span></h1>
|
<div class="games-header">
|
||||||
<p class="lead">将最常用的自托管服务与精品工具收纳于一处。我们为个人与团队提供稳定、可控且有温度的基础设施体验。</p>
|
<div class="section-title">
|
||||||
|
<i class="fas fa-gamepad"></i>
|
||||||
<div class="cta-row">
|
<span>Games</span>
|
||||||
<a class="btn btn-primary" href="#services">查看服务</a>
|
|
||||||
<a class="btn btn-ghost" href="#contact">联系我们</a>
|
|
||||||
</div>
|
</div>
|
||||||
|
<div class="count" id="gamesCount">—</div>
|
||||||
<div class="links-row" id="otherLinksRegion"></div>
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
<div class="games-grid" id="gamesGrid"></div>
|
||||||
|
</section>
|
||||||
|
|
||||||
<div class="hero-right">
|
<aside class="tools" aria-label="Tools">
|
||||||
<div class="glass" id="services">
|
<div class="tools-header">
|
||||||
<div style="display:flex;align-items:center;justify-content:space-between">
|
<div class="section-title">
|
||||||
<div>
|
<i class="fas fa-tools"></i>
|
||||||
<div style="font-weight:800">精品服务速览</div>
|
<span>Tools</span>
|
||||||
<div class="muted" style="font-size:12px;margin-top:6px">点击卡片以在新标签页打开服务</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div id="stats" class="muted" style="font-size:12px">加载中…</div>
|
<div class="count" id="toolsCount">—</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="tools-list" id="toolsList"></div>
|
||||||
<div class="services-grid" id="servicesGrid" style="margin-top:12px"></div>
|
</aside>
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<footer id="contact">
|
<footer>
|
||||||
<div>
|
<div class="footer-brand">Tootaio — <span id="year"></span></div>
|
||||||
<div style="font-weight:700">Tootaio</div>
|
<div class="footer-tagline">
|
||||||
<div class="muted" style="font-size:13px;margin-top:6px">欲了解更多,请通过下面链接联系。© <span id="year"></span></div>
|
<i class="fas fa-heart"></i>
|
||||||
|
<span>Focused on Games • Tools for ops & creators</span>
|
||||||
</div>
|
</div>
|
||||||
<div id="footerLinks" style="display:flex;gap:12px;align-items:center"></div>
|
|
||||||
</footer>
|
</footer>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- decorative SVG marble -->
|
<script>
|
||||||
<svg class="marble" width="360" height="360" viewBox="0 0 200 200" fill="none" xmlns="http://www.w3.org/2000/svg">
|
// Minimal default config (falls back if siteConfig.json missing)
|
||||||
<defs>
|
const defaultSiteConfig = {
|
||||||
<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>
|
services: [
|
||||||
</defs>
|
{ name: "supergame.tootaio.com", url: "https://supergame.tootaio.com", description_en: "Our flagship game demo.", description_zh: "我们的旗舰游戏演示。", category: "game", thumbnail: "" },
|
||||||
<circle cx="100" cy="100" r="80" fill="url(#g1)"/>
|
{ name: "git.tootaio.com", url: "https://git.tootaio.com", description_en: "Gitea self-host.", description_zh: "代码托管服务。", category: "tool" },
|
||||||
</svg>
|
{ name: "memos.tootaio.com", url: "https://memos.tootaio.com", description_en: "Notes", description_zh: "笔记空间", category: "tool" },
|
||||||
|
{ name: "life-restart.tootaio.com", url: "https://life-restart.tootaio.com", description_en: "Mirrored game.", description_zh: "游戏镜像", category: "game" }
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
<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(){
|
async function loadConfig(){
|
||||||
try{
|
try{
|
||||||
const res = await fetch('./siteConfig.json', {cache:'no-store'});
|
const res = await fetch('./siteConfig.json', {cache:'no-store'});
|
||||||
if(!res.ok) throw new Error('no siteConfig.json');
|
if(!res.ok) throw new Error('no siteConfig.json');
|
||||||
const cfg = await res.json();
|
const cfg = await res.json();
|
||||||
return cfg;
|
return cfg;
|
||||||
}catch(err){
|
}catch(e){
|
||||||
console.warn('Using embedded default siteConfig (could not fetch ./siteConfig.json) —', err.message);
|
console.warn('siteConfig.json not found, using default config.');
|
||||||
return defaultSiteConfig;
|
return defaultSiteConfig;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// render helpers
|
function guessCategory(s){
|
||||||
function el(tag, props={}, children=null){
|
// if not present, try to guess from name/description
|
||||||
const e = document.createElement(tag);
|
if(s.category) return s.category;
|
||||||
for(const k in props){
|
const name = (s.name || '').toLowerCase();
|
||||||
if(k === 'class') e.className = props[k];
|
const desc = (s.description_en||'' + s.description_zh||'').toLowerCase();
|
||||||
else if(k === 'html') e.innerHTML = props[k];
|
if(/game|play|demo|unity|godot|itch|gamepad|pixel/.test(name+desc)) return 'game';
|
||||||
else e.setAttribute(k, props[k]);
|
return 'tool';
|
||||||
}
|
|
||||||
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
|
function makeThumbPlaceholder(name, size=320){
|
||||||
const ICONS = {
|
// generate simple SVG data URI with initials
|
||||||
'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>',
|
const initials = (name || '').split(/[.\\-\\s]/).slice(0,2).map(x=>x[0]?.toUpperCase()||'').join('');
|
||||||
'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>',
|
const bg = ['#d97706','#ef4444','#7c3aed','#06b6d4','#f59e0b'][Math.abs(name.length)%5];
|
||||||
'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>',
|
const fg = '#fff';
|
||||||
'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>'
|
const svg = `<svg xmlns='http://www.w3.org/2000/svg' width='${size}' height='${Math.round(size*0.6)}'><rect width='100%' height='100%' fill='${bg}' rx='8'/><text x='50%' y='55%' font-size='36' dominant-baseline='middle' text-anchor='middle' fill='${fg}' font-family='Inter,system-ui'>${initials}</text></svg>`;
|
||||||
};
|
return 'data:image/svg+xml;charset=utf-8,' + encodeURIComponent(svg);
|
||||||
|
}
|
||||||
|
|
||||||
function renderServices(cfg, lang='zh'){
|
function render(cfg, lang='zh'){
|
||||||
const grid = document.getElementById('servicesGrid'); grid.innerHTML = '';
|
const services = (cfg.services || []).map(s => ({...s, category: guessCategory(s)}));
|
||||||
(cfg.services || []).forEach(s => {
|
const games = services.filter(s => s.category === 'game');
|
||||||
const a = el('a',{class:'service',href:s.url,target:'_blank',rel:'noreferrer'});
|
const tools = services.filter(s => s.category !== 'game');
|
||||||
const left = el('div');
|
|
||||||
const title = el('div',{class:'service-title'}, s.name);
|
const gamesGrid = document.getElementById('gamesGrid');
|
||||||
const desc = el('div',{class:'service-desc'}, lang === 'zh' ? (s.description_zh || s.description_en || '') : (s.description_en || s.description_zh || ''));
|
const toolsList = document.getElementById('toolsList');
|
||||||
left.appendChild(title); left.appendChild(desc);
|
gamesGrid.innerHTML = '';
|
||||||
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>'});
|
toolsList.innerHTML = '';
|
||||||
a.appendChild(left); a.appendChild(arrow);
|
|
||||||
grid.appendChild(a);
|
games.forEach(s => {
|
||||||
|
const thumb = s.thumbnail || makeThumbPlaceholder(s.name);
|
||||||
|
const a = document.createElement('a');
|
||||||
|
a.className = 'game-card';
|
||||||
|
a.href = s.url;
|
||||||
|
a.target = '_blank';
|
||||||
|
a.rel = 'noreferrer';
|
||||||
|
a.style.backgroundImage = `url('${thumb}')`;
|
||||||
|
const meta = document.createElement('div'); meta.className = 'meta';
|
||||||
|
const title = document.createElement('div'); title.className = 'game-title'; title.textContent = s.name;
|
||||||
|
const sub = document.createElement('div'); sub.className = 'game-sub'; sub.textContent = lang === 'zh' ? (s.description_zh || s.description_en || '') : (s.description_en || s.description_zh || '');
|
||||||
|
meta.appendChild(title); meta.appendChild(sub);
|
||||||
|
a.appendChild(meta);
|
||||||
|
gamesGrid.appendChild(a);
|
||||||
});
|
});
|
||||||
document.getElementById('stats').textContent = (cfg.services||[]).length + ' services';
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderOtherLinks(cfg, lang='zh'){
|
tools.forEach(s => {
|
||||||
const region = document.getElementById('otherLinksRegion'); region.innerHTML = '';
|
const icon = document.createElement('div'); icon.className = 'tool-icon';
|
||||||
const footer = document.getElementById('footerLinks'); footer.innerHTML = '';
|
icon.textContent = (s.name || '').slice(0,2).toUpperCase();
|
||||||
(cfg.otherLinks || []).forEach(l =>{
|
const toolContent = document.createElement('div'); toolContent.className = 'tool-content';
|
||||||
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)});
|
const tTitle = document.createElement('div'); tTitle.className = 'tool-title'; tTitle.textContent = s.name;
|
||||||
region.appendChild(pill);
|
const tSub = document.createElement('div'); tSub.className = 'tool-desc'; tSub.textContent = lang === 'zh' ? (s.description_zh || '') : (s.description_en || '');
|
||||||
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)});
|
toolContent.appendChild(tTitle); toolContent.appendChild(tSub);
|
||||||
footer.appendChild(f);
|
const a = document.createElement('a'); a.className = 'tool-item'; a.href = s.url; a.target='_blank'; a.rel='noreferrer';
|
||||||
|
a.appendChild(icon); a.appendChild(toolContent);
|
||||||
|
toolsList.appendChild(a);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
document.getElementById('gamesCount').textContent = games.length + ' 项目';
|
||||||
|
document.getElementById('toolsCount').textContent = tools.length + ' 项目';
|
||||||
}
|
}
|
||||||
|
|
||||||
(async function init(){
|
(async function init(){
|
||||||
const cfg = await loadConfig();
|
const cfg = await loadConfig();
|
||||||
let lang = 'zh';
|
let lang = 'zh';
|
||||||
renderServices(cfg, lang);
|
render(cfg, lang);
|
||||||
renderOtherLinks(cfg, lang);
|
|
||||||
|
|
||||||
// year
|
|
||||||
document.getElementById('year').textContent = new Date().getFullYear();
|
document.getElementById('year').textContent = new Date().getFullYear();
|
||||||
|
|
||||||
// search
|
// search
|
||||||
const q = document.getElementById('q');
|
const q = document.getElementById('q');
|
||||||
q.addEventListener('keydown', (e)=>{
|
q.addEventListener('keydown',(e)=>{
|
||||||
if(e.key === 'Enter'){
|
if(e.key === 'Enter'){
|
||||||
const term = q.value.trim().toLowerCase();
|
const term = q.value.trim().toLowerCase();
|
||||||
if(!term){ renderServices(cfg, lang); return; }
|
if(!term){ render(cfg, lang); return; }
|
||||||
const filtered = (cfg.services||[]).filter(s => (s.name + ' ' + (s.description_en||'') + ' ' + (s.description_zh||'')).toLowerCase().includes(term));
|
const filtered = (cfg.services||[]).filter(s => {
|
||||||
renderServices({services:filtered}, lang);
|
const txt = (s.name + ' ' + (s.description_en||'') + ' ' + (s.description_zh||'')).toLowerCase();
|
||||||
|
return txt.includes(term);
|
||||||
|
});
|
||||||
|
render({services:filtered}, lang);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// keyboard shortcut to focus search
|
||||||
|
window.addEventListener('keydown',(e)=>{ if(e.key === '/' && document.activeElement !== q){ e.preventDefault(); q.focus(); }});
|
||||||
|
|
||||||
// language toggle
|
// language toggle
|
||||||
const langBtn = document.getElementById('langBtn');
|
document.getElementById('langBtn').addEventListener('click', ()=>{
|
||||||
langBtn.addEventListener('click', ()=>{
|
|
||||||
lang = (lang === 'zh') ? 'en' : 'zh';
|
lang = (lang === 'zh') ? 'en' : 'zh';
|
||||||
langBtn.textContent = (lang === 'zh') ? '中文' : 'EN';
|
document.getElementById('langBtn').querySelector('span').textContent = (lang === 'zh') ? '中文' : 'EN';
|
||||||
renderServices(cfg, lang);
|
render(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>
|
||||||
</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>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
Reference in New Issue
Block a user