- 导航页(index.html)添加完整i18n引擎,支持zh-CN/zh-TW/en - 导航页添加语言切换按钮(EN/中/繁) - 所有20个前端模板默认语言从zh-CN改为en - 导航页SEO meta标签改为英文
1619 lines
87 KiB
HTML
1619 lines
87 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="zh-CN">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>CLOUD PLAY — 云端游戏 即开即玩</title>
|
||
<style>
|
||
/* ============================ Reset & Variables ============================ */
|
||
:root{
|
||
--bg-0:#0d1117; --bg-1:#161b22; --bg-2:#1c2433;
|
||
--blue:#3b82f6; --blue-l:#60a5fa; --blue-d:#2563eb;
|
||
--green:#10b981; --green-d:#059669;
|
||
--purple:#8b5cf6; --pink:#ec4899; --amber:#f59e0b;
|
||
--text:#e6edf3; --text-dim:#9ba6b3; --text-faint:#6b7684;
|
||
--glass:rgba(255,255,255,.04); --glass-2:rgba(255,255,255,.07);
|
||
--border:rgba(255,255,255,.08); --border-2:rgba(255,255,255,.16);
|
||
--radius:16px; --radius-sm:12px; --radius-lg:22px;
|
||
--shadow:0 8px 32px rgba(0,0,0,.45);
|
||
--shadow-soft:0 4px 20px rgba(0,0,0,.3);
|
||
--maxw:1320px;
|
||
--ease:cubic-bezier(.22,.61,.36,1);
|
||
}
|
||
*{margin:0;padding:0;box-sizing:border-box}
|
||
html{scroll-behavior:smooth}
|
||
body{
|
||
font-family:-apple-system,BlinkMacSystemFont,"Segoe UI","PingFang SC","Hiragino Sans GB","Microsoft YaHei",sans-serif;
|
||
background:var(--bg-0); color:var(--text);
|
||
line-height:1.6; -webkit-font-smoothing:antialiased; overflow-x:hidden;
|
||
min-height:100vh;
|
||
}
|
||
body::before{
|
||
content:""; position:fixed; inset:0; z-index:-2;
|
||
background:
|
||
radial-gradient(900px 600px at 12% -5%, rgba(59,130,246,.16), transparent 60%),
|
||
radial-gradient(800px 500px at 95% 8%, rgba(139,92,246,.13), transparent 55%),
|
||
radial-gradient(700px 600px at 50% 120%, rgba(16,185,129,.10), transparent 55%),
|
||
linear-gradient(180deg,#0d1117 0%,#11161f 40%,#161b22 100%);
|
||
}
|
||
a{color:inherit;text-decoration:none}
|
||
button{font-family:inherit;cursor:pointer;border:none;background:none;color:inherit}
|
||
img{display:block;max-width:100%}
|
||
ul{list-style:none}
|
||
::-webkit-scrollbar{width:10px;height:10px}
|
||
::-webkit-scrollbar-track{background:transparent}
|
||
::-webkit-scrollbar-thumb{background:rgba(255,255,255,.12);border-radius:10px}
|
||
::-webkit-scrollbar-thumb:hover{background:rgba(255,255,255,.22)}
|
||
::selection{background:rgba(59,130,246,.35)}
|
||
|
||
/* ============================ Layout helpers ============================ */
|
||
.wrap{max-width:var(--maxw);margin:0 auto;padding:0 28px}
|
||
.section{padding:64px 0 24px}
|
||
.section-head{display:flex;align-items:flex-end;justify-content:space-between;gap:16px;margin-bottom:28px;flex-wrap:wrap}
|
||
.section-title{font-size:26px;font-weight:800;letter-spacing:-.02em;display:flex;align-items:center;gap:12px}
|
||
.section-title .bar{width:5px;height:26px;border-radius:4px;background:linear-gradient(180deg,var(--blue),var(--purple))}
|
||
.section-sub{color:var(--text-dim);font-size:14px;margin-top:6px;font-weight:400}
|
||
.link-more{color:var(--blue-l);font-size:14px;font-weight:600;display:inline-flex;align-items:center;gap:6px;transition:gap .25s var(--ease)}
|
||
.link-more:hover{gap:10px}
|
||
|
||
/* ============================ Header ============================ */
|
||
header{
|
||
position:fixed;top:0;left:0;right:0;z-index:100;
|
||
backdrop-filter:blur(18px) saturate(140%); -webkit-backdrop-filter:blur(18px) saturate(140%);
|
||
background:rgba(13,17,23,.72); border-bottom:1px solid var(--border);
|
||
transition:background .3s var(--ease),border-color .3s var(--ease);
|
||
}
|
||
header.scrolled{background:rgba(13,17,23,.9);border-color:var(--border-2)}
|
||
.nav{max-width:var(--maxw);margin:0 auto;padding:0 28px;height:68px;display:flex;align-items:center;gap:34px}
|
||
.logo{font-size:21px;font-weight:900;letter-spacing:.02em;white-space:nowrap;
|
||
background:linear-gradient(100deg,var(--blue-l),var(--purple) 60%,var(--pink));
|
||
-webkit-background-clip:text;background-clip:text;color:transparent;display:flex;align-items:center;gap:10px}
|
||
.logo .dot{width:11px;height:11px;border-radius:50%;background:linear-gradient(135deg,var(--green),var(--blue));
|
||
box-shadow:0 0 14px rgba(16,185,129,.7);animation:pulse 2.4s ease-in-out infinite}
|
||
@keyframes pulse{0%,100%{transform:scale(1);opacity:1}50%{transform:scale(1.25);opacity:.75}}
|
||
.nav-links{display:flex;gap:6px}
|
||
.nav-links a{padding:8px 14px;border-radius:10px;font-size:14.5px;font-weight:500;color:var(--text-dim);
|
||
transition:all .25s var(--ease);position:relative}
|
||
.nav-links a:hover{color:var(--text);background:var(--glass-2)}
|
||
.nav-links a.active{color:var(--text);background:var(--glass-2)}
|
||
.nav-links a.active::after{content:"";position:absolute;left:14px;right:14px;bottom:2px;height:2px;border-radius:2px;
|
||
background:linear-gradient(90deg,var(--blue),var(--purple))}
|
||
.nav-right{margin-left:auto;display:flex;align-items:center;gap:14px}
|
||
.search-box{display:flex;align-items:center;gap:8px;background:var(--glass-2);border:1px solid var(--border);
|
||
border-radius:12px;padding:9px 14px;width:240px;transition:all .3s var(--ease)}
|
||
.search-box:focus-within{border-color:rgba(59,130,246,.55);box-shadow:0 0 0 3px rgba(59,130,246,.15);width:300px}
|
||
.search-box svg{width:17px;height:17px;color:var(--text-faint);flex-shrink:0}
|
||
.search-box input{background:none;border:none;outline:none;color:var(--text);font-size:14px;width:100%;font-family:inherit}
|
||
.search-box input::placeholder{color:var(--text-faint)}
|
||
.btn-signin{padding:9px 18px;border-radius:11px;font-size:14px;font-weight:600;color:#fff;white-space:nowrap;
|
||
background:linear-gradient(135deg,var(--blue),var(--blue-d));box-shadow:0 4px 16px rgba(59,130,246,.4);
|
||
transition:transform .25s var(--ease),box-shadow .25s var(--ease)}
|
||
.btn-signin:hover{transform:translateY(-2px);box-shadow:0 8px 24px rgba(59,130,246,.55)}
|
||
.menu-toggle{display:none;width:40px;height:40px;border-radius:10px;align-items:center;justify-content:center;background:var(--glass-2);border:1px solid var(--border)}
|
||
.menu-toggle span{display:block;width:18px;height:2px;background:var(--text);position:relative;transition:.3s}
|
||
.menu-toggle span::before,.menu-toggle span::after{content:"";position:absolute;left:0;width:18px;height:2px;background:var(--text);transition:.3s}
|
||
.menu-toggle span::before{top:-6px}.menu-toggle span::after{top:6px}
|
||
|
||
/* ============================ Language Switcher ============================ */
|
||
.lang-switcher{position:relative}
|
||
.lang-btn{display:flex;align-items:center;gap:6px;padding:8px 14px;border-radius:var(--radius-sm);
|
||
font-size:13px;font-weight:600;color:var(--text-dim);cursor:pointer;
|
||
background:var(--glass-2);border:1px solid var(--border);transition:all .25s var(--ease)}
|
||
.lang-btn:hover{color:var(--blue-l);background:var(--glass);border-color:var(--border-2)}
|
||
.lang-btn svg{width:16px;height:16px}
|
||
.lang-dropdown{
|
||
position:absolute;top:calc(100% + 8px);right:0;min-width:160px;z-index:200;
|
||
background:rgba(22,27,34,.96);backdrop-filter:blur(20px);-webkit-backdrop-filter:blur(20px);
|
||
border:1px solid var(--border-2);border-radius:var(--radius-sm);
|
||
box-shadow:0 12px 40px rgba(0,0,0,.6);
|
||
opacity:0;transform:translateY(-8px);pointer-events:none;transition:all .25s var(--ease);
|
||
}
|
||
.lang-dropdown.open{opacity:1;transform:none;pointer-events:auto}
|
||
.lang-option{
|
||
display:flex;align-items:center;gap:10px;padding:12px 16px;
|
||
font-size:14px;color:var(--text-dim);cursor:pointer;transition:all .2s var(--ease);
|
||
}
|
||
.lang-option:hover{background:var(--glass-2);color:var(--text)}
|
||
.lang-option.active{color:var(--blue-l);font-weight:600}
|
||
.lang-option .check{width:16px;height:16px;opacity:0;transition:opacity .2s var(--ease)}
|
||
.lang-option.active .check{opacity:1}
|
||
|
||
/* ============================ Hero ============================ */
|
||
.hero{position:relative;padding:130px 0 70px;overflow:hidden}
|
||
.hero-mesh{position:absolute;inset:0;z-index:-1;overflow:hidden}
|
||
.hero-mesh .blob{position:absolute;border-radius:50%;filter:blur(70px);opacity:.55;animation:float 14s ease-in-out infinite}
|
||
.hero-mesh .b1{width:480px;height:480px;background:radial-gradient(circle,var(--blue),transparent 70%);top:-120px;left:-60px}
|
||
.hero-mesh .b2{width:420px;height:420px;background:radial-gradient(circle,var(--purple),transparent 70%);top:40px;right:8%;animation-delay:-4s}
|
||
.hero-mesh .b3{width:380px;height:380px;background:radial-gradient(circle,var(--green),transparent 70%);bottom:-140px;left:40%;animation-delay:-8s}
|
||
@keyframes float{0%,100%{transform:translate(0,0) scale(1)}33%{transform:translate(40px,-30px) scale(1.08)}66%{transform:translate(-30px,25px) scale(.95)}}
|
||
.hero-grid-bg{position:absolute;inset:0;z-index:-1;opacity:.4;
|
||
background-image:linear-gradient(rgba(255,255,255,.035) 1px,transparent 1px),linear-gradient(90deg,rgba(255,255,255,.035) 1px,transparent 1px);
|
||
background-size:48px 48px;mask-image:radial-gradient(ellipse 80% 60% at 50% 30%,#000,transparent 75%);-webkit-mask-image:radial-gradient(ellipse 80% 60% at 50% 30%,#000,transparent 75%)}
|
||
.hero-inner{display:grid;grid-template-columns:1.15fr .85fr;gap:50px;align-items:center}
|
||
.hero-badge{display:inline-flex;align-items:center;gap:8px;padding:7px 14px;border-radius:999px;font-size:12.5px;font-weight:600;
|
||
background:rgba(59,130,246,.12);border:1px solid rgba(59,130,246,.3);color:var(--blue-l);margin-bottom:22px}
|
||
.hero-badge .live{width:7px;height:7px;border-radius:50%;background:var(--green);box-shadow:0 0 10px var(--green);animation:pulse 1.8s infinite}
|
||
.hero h1{font-size:60px;line-height:1.05;font-weight:900;letter-spacing:-.03em;margin-bottom:22px}
|
||
.hero h1 .grad{background:linear-gradient(100deg,var(--blue-l),var(--purple) 55%,var(--pink));-webkit-background-clip:text;background-clip:text;color:transparent}
|
||
.hero p.lead{font-size:17px;color:var(--text-dim);max-width:520px;margin-bottom:34px}
|
||
.hero-cta{display:flex;gap:16px;align-items:center;flex-wrap:wrap}
|
||
.btn-cta{display:inline-flex;align-items:center;gap:10px;padding:15px 30px;border-radius:14px;font-size:16px;font-weight:700;color:#fff;
|
||
background:linear-gradient(135deg,var(--blue),var(--purple));box-shadow:0 8px 30px rgba(59,130,246,.45),0 0 0 0 rgba(59,130,246,.4);
|
||
transition:transform .3s var(--ease),box-shadow .3s var(--ease);animation:glow 2.6s ease-in-out infinite}
|
||
@keyframes glow{0%,100%{box-shadow:0 8px 30px rgba(59,130,246,.45),0 0 0 0 rgba(59,130,246,.5)}50%{box-shadow:0 8px 36px rgba(139,92,246,.55),0 0 0 14px rgba(59,130,246,0)}}
|
||
.btn-cta:hover{transform:translateY(-3px)}
|
||
.btn-ghost{display:inline-flex;align-items:center;gap:10px;padding:15px 26px;border-radius:14px;font-size:15px;font-weight:600;
|
||
background:var(--glass-2);border:1px solid var(--border-2);color:var(--text);transition:all .3s var(--ease)}
|
||
.btn-ghost:hover{background:var(--glass);border-color:rgba(255,255,255,.28);transform:translateY(-3px)}
|
||
.hero-stats{display:flex;gap:30px;margin-top:40px}
|
||
.hero-stats .st{display:flex;flex-direction:column}
|
||
.hero-stats .num{font-size:26px;font-weight:800;background:linear-gradient(100deg,var(--blue-l),var(--green));-webkit-background-clip:text;background-clip:text;color:transparent}
|
||
.hero-stats .lbl{font-size:13px;color:var(--text-faint);margin-top:2px}
|
||
/* Hero preview card */
|
||
.hero-preview{position:relative;perspective:1200px}
|
||
.hero-card{position:relative;border-radius:var(--radius-lg);overflow:hidden;border:1px solid var(--border-2);
|
||
box-shadow:0 30px 80px rgba(0,0,0,.5);transform:rotateY(-9deg) rotateX(4deg);transition:transform .6s var(--ease);background:var(--bg-1)}
|
||
.hero-card:hover{transform:rotateY(-4deg) rotateX(2deg)}
|
||
.hero-card .hc-cover{height:300px;position:relative;background:linear-gradient(135deg,#1e3a5f,#3b1d6e)}
|
||
.hero-card .hc-cover::after{content:"";position:absolute;inset:0;background:linear-gradient(180deg,transparent 40%,rgba(13,17,23,.9))}
|
||
.hero-card .hc-body{padding:22px;position:relative;margin-top:-70px;z-index:2}
|
||
.hero-card .hc-tag{display:inline-block;font-size:11px;font-weight:700;padding:4px 10px;border-radius:6px;
|
||
background:rgba(16,185,129,.18);color:var(--green);border:1px solid rgba(16,185,129,.3);margin-bottom:12px}
|
||
.hero-card .hc-title{font-size:22px;font-weight:800;margin-bottom:8px}
|
||
.hero-card .hc-meta{display:flex;gap:16px;color:var(--text-dim);font-size:13px;margin-bottom:16px}
|
||
.hero-card .play-bar{display:flex;align-items:center;justify-content:space-between;padding:14px 16px;border-radius:12px;
|
||
background:var(--glass);border:1px solid var(--border)}
|
||
.hero-card .pb-left{display:flex;align-items:center;gap:10px;font-size:13px;color:var(--text-dim)}
|
||
.hero-card .pb-btn{width:42px;height:42px;border-radius:50%;display:flex;align-items:center;justify-content:center;
|
||
background:linear-gradient(135deg,var(--green),var(--green-d));box-shadow:0 4px 16px rgba(16,185,129,.5);transition:transform .3s}
|
||
.hero-card .pb-btn:hover{transform:scale(1.1)}
|
||
.hero-card .pb-btn svg{width:16px;height:16px;margin-left:2px;fill:#fff}
|
||
|
||
/* ============================ Category pills ============================ */
|
||
.cat-row{display:flex;gap:10px;flex-wrap:wrap;margin-bottom:8px}
|
||
.cat-pill{padding:10px 20px;border-radius:999px;font-size:14px;font-weight:600;color:var(--text-dim);
|
||
background:var(--glass);border:1px solid var(--border);transition:all .3s var(--ease);white-space:nowrap}
|
||
.cat-pill:hover{color:var(--text);border-color:var(--border-2);background:var(--glass-2)}
|
||
.cat-pill.active{color:#fff;background:linear-gradient(135deg,var(--blue),var(--purple));border-color:transparent;
|
||
box-shadow:0 6px 20px rgba(59,130,246,.4)}
|
||
|
||
/* ============================ Cards common ============================ */
|
||
.card{background:var(--glass);border:1px solid var(--border);border-radius:var(--radius);overflow:hidden;
|
||
transition:transform .35s var(--ease),border-color .35s var(--ease),box-shadow .35s var(--ease);position:relative}
|
||
.card:hover{transform:translateY(-6px);border-color:var(--border-2);box-shadow:var(--shadow)}
|
||
.cover{position:relative;overflow:hidden;background:var(--bg-2)}
|
||
.cover .cv-art{position:absolute;inset:0;transition:transform .5s var(--ease)}
|
||
.cover:hover .cv-art{transform:scale(1.08)}
|
||
.cover .cv-glow{position:absolute;inset:0;background:linear-gradient(180deg,transparent 45%,rgba(0,0,0,.55));opacity:0;transition:opacity .35s}
|
||
.cover:hover .cv-glow{opacity:1}
|
||
.play-overlay{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;opacity:0;transition:opacity .35s var(--ease);z-index:3}
|
||
.cover:hover .play-overlay{opacity:1}
|
||
.play-overlay .po-btn{width:56px;height:56px;border-radius:50%;display:flex;align-items:center;justify-content:center;
|
||
background:linear-gradient(135deg,var(--green),var(--green-d));box-shadow:0 8px 28px rgba(16,185,129,.6);
|
||
transform:scale(.7);transition:transform .35s var(--ease)}
|
||
.cover:hover .play-overlay .po-btn{transform:scale(1)}
|
||
.play-overlay .po-btn svg{width:22px;height:22px;margin-left:3px;fill:#fff}
|
||
.cover .cv-badge{position:absolute;top:12px;left:12px;z-index:4;font-size:11px;font-weight:700;padding:4px 9px;border-radius:6px;letter-spacing:.02em}
|
||
.badge-hot{background:linear-gradient(135deg,var(--pink),#f43f5e);color:#fff;box-shadow:0 3px 12px rgba(244,63,94,.5)}
|
||
.badge-new{background:linear-gradient(135deg,var(--green),var(--green-d));color:#fff;box-shadow:0 3px 12px rgba(16,185,129,.5)}
|
||
.cover .cv-rating{position:absolute;top:12px;right:12px;z-index:4;font-size:12px;font-weight:700;padding:4px 10px;border-radius:7px;
|
||
background:rgba(0,0,0,.55);backdrop-filter:blur(6px);color:var(--amber);display:flex;align-items:center;gap:4px}
|
||
|
||
/* Generated cover art */
|
||
.gcover{display:flex;align-items:flex-end;padding:16px;color:rgba(255,255,255,.95)}
|
||
.gcover .gc-watermark{position:absolute;font-size:96px;font-weight:900;opacity:.16;top:-10px;right:-6px;line-height:1;letter-spacing:-.04em}
|
||
.gcover .gc-orb1{position:absolute;width:120px;height:120px;border-radius:50%;background:rgba(255,255,255,.14);filter:blur(8px);top:-30px;left:-20px}
|
||
.gcover .gc-orb2{position:absolute;width:80px;height:80px;border-radius:50%;background:rgba(0,0,0,.2);filter:blur(6px);bottom:-20px;right:30px}
|
||
.gcover .gc-cat{position:relative;font-size:11px;font-weight:600;letter-spacing:.04em;text-transform:uppercase;opacity:.85;padding:3px 9px;border-radius:6px;background:rgba(0,0,0,.28);backdrop-filter:blur(4px)}
|
||
|
||
/* ============================ Hot section ============================ */
|
||
.hot-grid{display:grid;grid-template-columns:1.7fr 1fr 1fr;grid-template-rows:auto auto;gap:18px}
|
||
.hot-grid .card.big{grid-row:span 2;display:flex;flex-direction:column}
|
||
.hot-grid .card.big .cover{flex:1;min-height:380px}
|
||
.hot-grid .card:not(.big) .cover{height:150px}
|
||
.card-body{padding:16px 18px}
|
||
.card-title{font-size:16px;font-weight:700;margin-bottom:6px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
||
.card.big .card-title{font-size:22px;white-space:normal}
|
||
.card-meta{display:flex;align-items:center;gap:12px;color:var(--text-dim);font-size:12.5px;flex-wrap:wrap}
|
||
.card-meta .dot{width:3px;height:3px;border-radius:50%;background:var(--text-faint)}
|
||
.card-desc{color:var(--text-dim);font-size:13px;margin-top:8px;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}
|
||
.card.big .card-desc{font-size:14px;-webkit-line-clamp:3;margin-top:10px}
|
||
.card-tags{display:flex;gap:6px;flex-wrap:wrap;margin-top:10px}
|
||
.tag-mini{font-size:11px;color:var(--text-dim);padding:3px 8px;border-radius:6px;background:var(--glass-2);border:1px solid var(--border)}
|
||
|
||
/* ============================ New section (horizontal scroll) ============================ */
|
||
.scroll-x{display:flex;gap:16px;overflow-x:auto;padding:6px 4px 16px;scroll-snap-type:x mandatory;scroll-padding-left:4px}
|
||
.scroll-x::-webkit-scrollbar{height:6px}
|
||
.scroll-x .card{min-width:240px;max-width:240px;scroll-snap-align:start}
|
||
.scroll-x .card .cover{height:140px}
|
||
|
||
/* ============================ Library ============================ */
|
||
.lib-bar{display:flex;gap:14px;flex-wrap:wrap;align-items:center;margin-bottom:26px}
|
||
.lib-search{display:flex;align-items:center;gap:9px;background:var(--glass-2);border:1px solid var(--border);border-radius:12px;padding:11px 16px;flex:1;min-width:220px;transition:all .3s var(--ease)}
|
||
.lib-search:focus-within{border-color:rgba(59,130,246,.5);box-shadow:0 0 0 3px rgba(59,130,246,.12)}
|
||
.lib-search svg{width:17px;height:17px;color:var(--text-faint);flex-shrink:0}
|
||
.lib-search input{background:none;border:none;outline:none;color:var(--text);font-size:14px;width:100%;font-family:inherit}
|
||
.lib-search input::placeholder{color:var(--text-faint)}
|
||
.lib-cats{display:flex;gap:8px;flex-wrap:wrap}
|
||
.lib-sort{position:relative}
|
||
.lib-sort select{appearance:none;-webkit-appearance:none;padding:11px 38px 11px 16px;border-radius:12px;font-size:14px;font-weight:600;
|
||
background:var(--glass-2);border:1px solid var(--border);color:var(--text);cursor:pointer;font-family:inherit;transition:all .3s var(--ease)}
|
||
.lib-sort select:hover{border-color:var(--border-2)}
|
||
.lib-sort::after{content:"";position:absolute;right:15px;top:50%;width:8px;height:8px;border-right:2px solid var(--text-dim);border-bottom:2px solid var(--text-dim);transform:translateY(-65%) rotate(45deg);pointer-events:none}
|
||
.game-grid{display:grid;grid-template-columns:repeat(5,1fr);gap:18px}
|
||
.game-grid .card .cover{height:165px}
|
||
.game-grid .card-body{padding:14px 15px}
|
||
|
||
/* Pagination */
|
||
.pager{display:flex;justify-content:center;align-items:center;gap:8px;margin-top:38px;flex-wrap:wrap}
|
||
.pager button{min-width:40px;height:40px;padding:0 12px;border-radius:11px;font-size:14px;font-weight:600;color:var(--text-dim);
|
||
background:var(--glass);border:1px solid var(--border);transition:all .25s var(--ease)}
|
||
.pager button:hover:not(:disabled){color:var(--text);border-color:var(--border-2);background:var(--glass-2)}
|
||
.pager button.active{color:#fff;background:linear-gradient(135deg,var(--blue),var(--purple));border-color:transparent;box-shadow:0 4px 16px rgba(59,130,246,.4)}
|
||
.pager button:disabled{opacity:.35;cursor:not-allowed}
|
||
.pager .dots{color:var(--text-faint);padding:0 4px}
|
||
.lib-info{text-align:center;color:var(--text-faint);font-size:13px;margin-top:14px}
|
||
|
||
/* ============================ Skeleton ============================ */
|
||
.skeleton{background:var(--glass);border:1px solid var(--border);border-radius:var(--radius);overflow:hidden}
|
||
.sk-shimmer{position:relative;background:var(--bg-2);overflow:hidden}
|
||
.sk-shimmer::after{content:"";position:absolute;inset:0;transform:translateX(-100%);
|
||
background:linear-gradient(90deg,transparent,rgba(255,255,255,.07),transparent);animation:shimmer 1.5s infinite}
|
||
@keyframes shimmer{100%{transform:translateX(100%)}}
|
||
.sk-cover{height:165px}
|
||
.sk-line{height:13px;border-radius:6px;margin:10px 14px}
|
||
|
||
/* ============================ Stars ============================ */
|
||
.stars{position:relative;display:inline-block;font-size:14px;line-height:1;letter-spacing:2px;font-family:Arial,sans-serif}
|
||
.stars .bg{color:rgba(255,255,255,.18)}
|
||
.stars .fg{position:absolute;left:0;top:0;overflow:hidden;white-space:nowrap;
|
||
background:linear-gradient(90deg,var(--amber),#fbbf24);-webkit-background-clip:text;background-clip:text;color:transparent}
|
||
|
||
/* ============================ Modal ============================ */
|
||
.modal-mask{position:fixed;inset:0;z-index:200;display:flex;align-items:center;justify-content:center;padding:24px;
|
||
background:rgba(7,10,16,.7);backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px);
|
||
opacity:0;visibility:hidden;transition:opacity .35s var(--ease),visibility .35s}
|
||
.modal-mask.open{opacity:1;visibility:visible}
|
||
.modal{width:100%;max-width:860px;max-height:90vh;overflow:hidden;border-radius:var(--radius-lg);
|
||
background:var(--bg-1);border:1px solid var(--border-2);box-shadow:0 40px 100px rgba(0,0,0,.6);
|
||
transform:translateY(30px) scale(.97);opacity:0;transition:transform .4s var(--ease),opacity .4s;display:flex;flex-direction:column}
|
||
.modal-mask.open .modal{transform:translateY(0) scale(1);opacity:1}
|
||
.modal-header{position:relative;height:230px;flex-shrink:0;overflow:hidden}
|
||
.modal-header .mh-art{position:absolute;inset:0}
|
||
.modal-header .mh-art::after{content:"";position:absolute;inset:0;background:linear-gradient(180deg,rgba(22,27,34,.2) 0%,rgba(22,27,34,.6) 60%,var(--bg-1) 100%)}
|
||
.modal-close{position:absolute;top:16px;right:16px;z-index:5;width:38px;height:38px;border-radius:11px;display:flex;align-items:center;justify-content:center;
|
||
background:rgba(0,0,0,.5);backdrop-filter:blur(8px);border:1px solid var(--border);transition:all .25s var(--ease)}
|
||
.modal-close:hover{background:rgba(0,0,0,.7);transform:rotate(90deg)}
|
||
.modal-close svg{width:18px;height:18px;stroke:var(--text)}
|
||
.modal-header-content{position:absolute;left:0;right:0;bottom:0;padding:26px 30px;z-index:3}
|
||
.modal-badges{display:flex;gap:8px;margin-bottom:12px}
|
||
.modal-title{font-size:32px;font-weight:900;letter-spacing:-.02em;margin-bottom:10px}
|
||
.modal-sub{display:flex;align-items:center;gap:14px;flex-wrap:wrap;color:var(--text-dim);font-size:13.5px}
|
||
.modal-body{overflow-y:auto;padding:26px 30px 30px}
|
||
.modal-row{display:grid;grid-template-columns:1.5fr 1fr;gap:30px;margin-bottom:26px}
|
||
.modal-desc h4{font-size:15px;font-weight:700;margin-bottom:10px;color:var(--text)}
|
||
.modal-desc p{color:var(--text-dim);font-size:14px;line-height:1.75}
|
||
.modal-features{margin-top:18px}
|
||
.modal-features h4{font-size:15px;font-weight:700;margin-bottom:12px}
|
||
.feat-list{display:grid;grid-template-columns:1fr 1fr;gap:10px}
|
||
.feat-item{display:flex;align-items:center;gap:9px;font-size:13.5px;color:var(--text-dim)}
|
||
.feat-item .ck{width:20px;height:20px;border-radius:6px;flex-shrink:0;display:flex;align-items:center;justify-content:center;
|
||
background:rgba(16,185,129,.16);color:var(--green)}
|
||
.feat-item .ck svg{width:12px;height:12px;stroke:var(--green);stroke-width:3}
|
||
.modal-info{background:var(--glass);border:1px solid var(--border);border-radius:var(--radius);padding:20px}
|
||
.info-row{display:flex;justify-content:space-between;gap:12px;padding:9px 0;border-bottom:1px solid var(--border);font-size:13.5px}
|
||
.info-row:last-child{border-bottom:none}
|
||
.info-row .k{color:var(--text-faint);white-space:nowrap}
|
||
.info-row .v{color:var(--text);font-weight:600;text-align:right}
|
||
.modal-tags{display:flex;flex-wrap:wrap;gap:8px;margin-bottom:24px}
|
||
.tag-pill{font-size:12.5px;font-weight:600;padding:6px 13px;border-radius:999px;
|
||
background:rgba(139,92,246,.12);border:1px solid rgba(139,92,246,.3);color:#c4b5fd}
|
||
.modal-cta{display:flex;gap:14px;align-items:center;flex-wrap:wrap;padding-top:6px}
|
||
.btn-play-big{display:inline-flex;align-items:center;gap:10px;padding:15px 36px;border-radius:14px;font-size:16px;font-weight:700;color:#fff;
|
||
background:linear-gradient(135deg,var(--green),var(--green-d));box-shadow:0 8px 30px rgba(16,185,129,.5);
|
||
transition:transform .3s var(--ease),box-shadow .3s var(--ease)}
|
||
.btn-play-big:hover{transform:translateY(-3px);box-shadow:0 12px 40px rgba(16,185,129,.65)}
|
||
.btn-play-big svg{width:18px;height:18px;fill:#fff}
|
||
.btn-fav{width:54px;height:54px;border-radius:14px;display:flex;align-items:center;justify-content:center;
|
||
background:var(--glass-2);border:1px solid var(--border-2);transition:all .3s var(--ease)}
|
||
.btn-fav:hover{background:var(--glass);border-color:rgba(255,255,255,.3);transform:translateY(-3px)}
|
||
.btn-fav svg{width:22px;height:22px;stroke:var(--text-dim);fill:none;stroke-width:2}
|
||
.modal-sk{height:420px;display:flex;flex-direction:column}
|
||
.modal-sk .sk-shimmer{height:230px;border-radius:0}
|
||
.modal-sk .sk-line.w60{width:60%;margin:24px 30px 0}
|
||
.modal-sk .sk-line.w90{width:90%}
|
||
.modal-sk .sk-line.w40{width:40%}
|
||
|
||
/* ============================ Footer ============================ */
|
||
footer{margin-top:80px;border-top:1px solid var(--border);background:rgba(13,17,23,.6)}
|
||
.foot{max-width:var(--maxw);margin:0 auto;padding:54px 28px 30px;display:grid;grid-template-columns:1.6fr 1fr 1fr 1fr;gap:40px}
|
||
.foot-brand .logo{margin-bottom:16px}
|
||
.foot-brand p{color:var(--text-dim);font-size:14px;max-width:320px;line-height:1.7}
|
||
.foot-soc{display:flex;gap:10px;margin-top:20px}
|
||
.foot-soc a{width:38px;height:38px;border-radius:10px;display:flex;align-items:center;justify-content:center;background:var(--glass-2);border:1px solid var(--border);transition:all .3s var(--ease)}
|
||
.foot-soc a:hover{background:var(--glass);border-color:var(--border-2);transform:translateY(-3px)}
|
||
.foot-soc svg{width:18px;height:18px;stroke:var(--text-dim);fill:none;stroke-width:1.8}
|
||
.foot-col h5{font-size:14px;font-weight:700;margin-bottom:16px;color:var(--text)}
|
||
.foot-col a{display:block;color:var(--text-dim);font-size:13.5px;padding:6px 0;transition:color .25s}
|
||
.foot-col a:hover{color:var(--blue-l)}
|
||
.foot-bottom{max-width:var(--maxw);margin:0 auto;padding:20px 28px;border-top:1px solid var(--border);display:flex;justify-content:space-between;align-items:center;flex-wrap:wrap;gap:12px}
|
||
.foot-bottom p{color:var(--text-faint);font-size:13px}
|
||
|
||
/* ============================ Toast ============================ */
|
||
.toast-wrap{position:fixed;top:88px;right:24px;z-index:300;display:flex;flex-direction:column;gap:10px}
|
||
.toast{padding:14px 20px;border-radius:12px;font-size:14px;font-weight:600;color:#fff;background:rgba(28,36,51,.95);
|
||
border:1px solid var(--border-2);backdrop-filter:blur(12px);box-shadow:var(--shadow);display:flex;align-items:center;gap:10px;
|
||
transform:translateX(120%);transition:transform .4s var(--ease)}
|
||
.toast.show{transform:translateX(0)}
|
||
.toast.success{border-left:3px solid var(--green)}
|
||
.toast .ti{width:20px;height:20px;flex-shrink:0;color:var(--green)}
|
||
|
||
/* ============================ Reveal animation ============================ */
|
||
.reveal{opacity:0;transform:translateY(28px);transition:opacity .7s var(--ease),transform .7s var(--ease)}
|
||
.reveal.in{opacity:1;transform:none}
|
||
|
||
/* ============================ Empty state ============================ */
|
||
.empty{text-align:center;padding:70px 20px;color:var(--text-faint)}
|
||
.empty svg{width:64px;height:64px;margin:0 auto 18px;opacity:.4}
|
||
.empty p{font-size:15px}
|
||
|
||
/* ============================ Responsive ============================ */
|
||
@media(max-width:1100px){
|
||
.hero-inner{grid-template-columns:1fr;gap:40px}
|
||
.hero-preview{max-width:480px}
|
||
.game-grid{grid-template-columns:repeat(3,1fr)}
|
||
.hot-grid{grid-template-columns:1fr 1fr}
|
||
.hot-grid .card.big{grid-row:span 1;grid-column:span 2}
|
||
.hot-grid .card.big .cover{min-height:260px}
|
||
.foot{grid-template-columns:1fr 1fr;gap:30px}
|
||
}
|
||
@media(max-width:760px){
|
||
.wrap{padding:0 18px}
|
||
.nav{padding:0 18px;gap:16px}
|
||
.nav-links,.search-box{display:none}
|
||
.menu-toggle{display:flex}
|
||
.hero{padding:110px 0 50px}
|
||
.hero h1{font-size:40px}
|
||
.hero p.lead{font-size:15px}
|
||
.hero-stats{gap:20px}
|
||
.section{padding:46px 0 16px}
|
||
.section-title{font-size:22px}
|
||
.game-grid{grid-template-columns:repeat(2,1fr);gap:14px}
|
||
.hot-grid{grid-template-columns:1fr}
|
||
.hot-grid .card.big{grid-column:span 1}
|
||
.modal-row{grid-template-columns:1fr;gap:20px}
|
||
.feat-list{grid-template-columns:1fr}
|
||
.modal-title{font-size:24px}
|
||
.modal-header{height:190px}
|
||
.foot{grid-template-columns:1fr;gap:28px}
|
||
.lib-bar{flex-direction:column;align-items:stretch}
|
||
.lib-search{width:100%}
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
|
||
<!-- ============================ HEADER ============================ -->
|
||
<header id="header">
|
||
<nav class="nav">
|
||
<a href="#top" class="logo"><span class="dot"></span>CLOUD PLAY</a>
|
||
<ul class="nav-links">
|
||
<li><a href="#top" class="active" data-nav="home" data-i18n="nav_home"></a></li>
|
||
<li><a href="#library" data-nav="browse" data-i18n="nav_browse"></a></li>
|
||
<li><a href="#hot" data-nav="hot" data-i18n="nav_hot"></a></li>
|
||
<li><a href="#new" data-nav="new" data-i18n="nav_new"></a></li>
|
||
</ul>
|
||
<div class="nav-right">
|
||
<div class="search-box">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="7"/><path d="m21 21-4.3-4.3"/></svg>
|
||
<input type="text" id="navSearch" data-i18n-placeholder="nav_search_placeholder" autocomplete="off">
|
||
</div>
|
||
<button class="btn-signin" data-i18n="btn_signin"></button>
|
||
<div class="lang-switcher" id="langSwitcher">
|
||
<button class="lang-btn" id="langBtn" onclick="toggleLangDropdown()">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><path d="M2 12h20M12 2a15 15 0 0 1 0 20 15 15 0 0 1 0-20z"/></svg>
|
||
<span id="langLabel">中文</span>
|
||
</button>
|
||
<div class="lang-dropdown" id="langDropdown">
|
||
<div class="lang-option active" data-lang="zh-CN" onclick="switchLang('zh-CN')">
|
||
<svg class="check" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg>
|
||
中文
|
||
</div>
|
||
<div class="lang-option" data-lang="zh-TW" onclick="switchLang('zh-TW')">
|
||
<svg class="check" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg>
|
||
繁體中文
|
||
</div>
|
||
<div class="lang-option" data-lang="en" onclick="switchLang('en')">
|
||
<svg class="check" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg>
|
||
English
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<button class="menu-toggle" aria-label="菜单"><span></span></button>
|
||
</div>
|
||
</nav>
|
||
</header>
|
||
|
||
<!-- ============================ HERO ============================ -->
|
||
<section class="hero" id="top">
|
||
<div class="hero-grid-bg"></div>
|
||
<div class="hero-mesh"><span class="blob b1"></span><span class="blob b2"></span><span class="blob b3"></span></div>
|
||
<div class="wrap hero-inner">
|
||
<div class="hero-text">
|
||
<span class="hero-badge"><span class="live"></span><span data-i18n="hero_badge"></span></span>
|
||
<h1><span data-i18n="hero_title_line1"></span><br><span class="grad" data-i18n="hero_title_line2"></span></h1>
|
||
<p class="lead" data-i18n="hero_desc"></p>
|
||
<div class="hero-cta">
|
||
<button class="btn-cta" onclick="document.getElementById('hot').scrollIntoView({behavior:'smooth'})">
|
||
<svg width="18" height="18" viewBox="0 0 24 24" fill="#fff"><path d="M8 5v14l11-7z"/></svg>
|
||
<span data-i18n="hero_cta_play"></span>
|
||
</button>
|
||
<a href="#library" class="btn-ghost" data-i18n="hero_cta_browse"></a>
|
||
</div>
|
||
<div class="hero-stats">
|
||
<div class="st"><span class="num" id="statGames">0</span><span class="lbl" data-i18n="hero_stat_games"></span></div>
|
||
<div class="st"><span class="num">99.9%</span><span class="lbl" data-i18n="hero_stat_uptime"></span></div>
|
||
<div class="st"><span class="num"><20ms</span><span class="lbl" data-i18n="hero_stat_latency"></span></div>
|
||
</div>
|
||
</div>
|
||
<div class="hero-preview">
|
||
<div class="hero-card">
|
||
<div class="hc-cover" id="heroCover"></div>
|
||
<div class="hc-body">
|
||
<span class="hc-tag" data-i18n="hero_card_hot"></span>
|
||
<h3 class="hc-title" id="heroTitle"></h3>
|
||
<div class="hc-meta">
|
||
<span id="heroCat">—</span>
|
||
<span>·</span>
|
||
<span id="heroDev">—</span>
|
||
</div>
|
||
<div class="play-bar">
|
||
<div class="pb-left">
|
||
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 2v20M2 12h20"/></svg>
|
||
<span data-i18n="hero_card_cloud_note"></span>
|
||
</div>
|
||
<button class="pb-btn" id="heroPlay"><svg viewBox="0 0 24 24"><path d="M8 5v14l11-7z"/></svg></button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- ============================ CATEGORY FILTER ============================ -->
|
||
<section class="section" style="padding-top:24px">
|
||
<div class="wrap">
|
||
<div class="cat-row" id="catRow">
|
||
<button class="cat-pill active" data-cat="0" data-i18n="cat_all"></button>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- ============================ HOT GAMES ============================ -->
|
||
<section class="section" id="hot">
|
||
<div class="wrap">
|
||
<div class="section-head reveal">
|
||
<div>
|
||
<h2 class="section-title"><span class="bar"></span><span data-i18n="section_hot"></span></h2>
|
||
<p class="section-sub" data-i18n="section_hot_sub"></p>
|
||
</div>
|
||
<a href="#library" class="link-more"><span data-i18n="section_view_all"></span> <svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><path d="m9 18 6-6-6-6"/></svg></a>
|
||
</div>
|
||
<div class="hot-grid" id="hotGrid"></div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- ============================ NEW GAMES ============================ -->
|
||
<section class="section" id="new">
|
||
<div class="wrap">
|
||
<div class="section-head reveal">
|
||
<div>
|
||
<h2 class="section-title"><span class="bar"></span><span data-i18n="section_new"></span></h2>
|
||
<p class="section-sub" data-i18n="section_new_sub"></p>
|
||
</div>
|
||
</div>
|
||
<div class="scroll-x" id="newScroll"></div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- ============================ GAME LIBRARY ============================ -->
|
||
<section class="section" id="library">
|
||
<div class="wrap">
|
||
<div class="section-head reveal">
|
||
<div>
|
||
<h2 class="section-title"><span class="bar"></span><span data-i18n="section_library"></span></h2>
|
||
<p class="section-sub" data-i18n="section_library_sub"></p>
|
||
</div>
|
||
</div>
|
||
<div class="lib-bar reveal">
|
||
<div class="lib-search">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="7"/><path d="m21 21-4.3-4.3"/></svg>
|
||
<input type="text" id="libSearch" data-i18n-placeholder="lib_search_placeholder" autocomplete="off">
|
||
</div>
|
||
<div class="lib-cats" id="libCats"></div>
|
||
<div class="lib-sort">
|
||
<select id="libSort">
|
||
<option value="" data-i18n="sort_default"></option>
|
||
<option value="rating" data-i18n="sort_rating"></option>
|
||
<option value="plays" data-i18n="sort_plays"></option>
|
||
<option value="newest" data-i18n="sort_newest"></option>
|
||
</select>
|
||
</div>
|
||
</div>
|
||
<div class="game-grid" id="gameGrid"></div>
|
||
<div class="lib-info" id="libInfo"></div>
|
||
<div class="pager" id="pager"></div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- ============================ FOOTER ============================ -->
|
||
<footer>
|
||
<div class="foot">
|
||
<div class="foot-brand">
|
||
<a href="#top" class="logo"><span class="dot"></span>CLOUD PLAY</a>
|
||
<p data-i18n="footer_brand_desc"></p>
|
||
<div class="foot-soc">
|
||
<a href="javascript:void(0)" aria-label="微博"><svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="10"/></svg></a>
|
||
<a href="javascript:void(0)" aria-label="微信"><svg viewBox="0 0 24 24"><path d="M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z"/></svg></a>
|
||
<a href="javascript:void(0)" aria-label="B站"><svg viewBox="0 0 24 24"><rect x="3" y="6" width="18" height="14" rx="3"/><path d="M8 3l4 3 4-3"/></svg></a>
|
||
</div>
|
||
</div>
|
||
<div class="foot-col">
|
||
<h5 data-i18n="footer_platform"></h5>
|
||
<a href="#top" data-i18n="nav_home"></a>
|
||
<a href="#library" data-i18n="nav_browse"></a>
|
||
<a href="#hot" data-i18n="footer_hot_ranking"></a>
|
||
<a href="#new" data-i18n="footer_new_release"></a>
|
||
</div>
|
||
<div class="foot-col">
|
||
<h5 data-i18n="footer_support"></h5>
|
||
<a href="javascript:void(0)" data-i18n="footer_help_center"></a>
|
||
<a href="javascript:void(0)" data-i18n="footer_device_req"></a>
|
||
<a href="javascript:void(0)" data-i18n="footer_network_diag"></a>
|
||
<a href="javascript:void(0)" data-i18n="footer_contact"></a>
|
||
</div>
|
||
<div class="foot-col">
|
||
<h5 data-i18n="footer_about"></h5>
|
||
<a href="javascript:void(0)" data-i18n="footer_about_us"></a>
|
||
<a href="javascript:void(0)" data-i18n="footer_terms"></a>
|
||
<a href="javascript:void(0)" data-i18n="footer_privacy"></a>
|
||
<a href="javascript:void(0)" data-i18n="footer_partners"></a>
|
||
</div>
|
||
</div>
|
||
<div class="foot-bottom">
|
||
<p>© 2026 CLOUD PLAY. <span data-i18n="footer_copyright"></span></p>
|
||
<p data-i18n="footer_powered"></p>
|
||
</div>
|
||
</footer>
|
||
|
||
<!-- ============================ MODAL ============================ -->
|
||
<div class="modal-mask" id="modalMask">
|
||
<div class="modal" id="modal"></div>
|
||
</div>
|
||
|
||
<!-- ============================ TOAST ============================ -->
|
||
<div class="toast-wrap" id="toastWrap"></div>
|
||
|
||
<script>
|
||
"use strict";
|
||
/* ===========================================================================
|
||
CLOUD PLAY — 现代云游戏平台前端逻辑(含 i18n 国际化)
|
||
所有 API 调用使用 fetch;当后端不可用时回退到内置 Mock 数据以保证模板可独立预览
|
||
=========================================================================== */
|
||
|
||
/* ===========================================================================
|
||
I18N — 国际化
|
||
=========================================================================== */
|
||
const I18N_LOCALES = {
|
||
'zh-CN': {
|
||
nav_home: '首页',
|
||
nav_browse: '浏览',
|
||
nav_hot: '热门',
|
||
nav_new: '新游',
|
||
nav_search_placeholder: '搜索游戏...',
|
||
btn_signin: '登录',
|
||
hero_badge: '云端串流 · 零延迟 · 4K 画质',
|
||
hero_title_line1: '云端游戏',
|
||
hero_title_line2: '即开即玩',
|
||
hero_desc: '无需下载,无需高端硬件。在任意设备上畅玩 3A 大作,从手机到电视,进度无缝衔接。强大的云端算力,让每一帧都丝滑流畅。',
|
||
hero_cta_play: '立即体验',
|
||
hero_cta_browse: '浏览全部游戏',
|
||
hero_stat_games: '款游戏',
|
||
hero_stat_uptime: '服务可用率',
|
||
hero_stat_latency: '平均延迟',
|
||
hero_card_hot: '正在热玩',
|
||
hero_card_cloud_note: '云端启动 · 无需下载',
|
||
cat_all: '全部',
|
||
section_hot: '热门游戏',
|
||
section_hot_sub: '当下最受玩家追捧的云端大作',
|
||
section_view_all: '查看全部',
|
||
section_new: '最新上线',
|
||
section_new_sub: '新鲜上架,第一时间云端畅玩',
|
||
section_library: '全部游戏',
|
||
section_library_sub: '探索完整的云端游戏库',
|
||
lib_search_placeholder: '搜索游戏名称...',
|
||
sort_default: '默认排序',
|
||
sort_rating: '评分最高',
|
||
sort_plays: '最受欢迎',
|
||
sort_newest: '最新上线',
|
||
empty_hot: '暂无热门游戏',
|
||
empty_new: '暂无新游',
|
||
empty_no_match: '没有找到匹配的游戏',
|
||
empty_game_not_found: '游戏不存在',
|
||
modal_badge_hot: '热门',
|
||
modal_badge_new: '新游',
|
||
modal_play_plays: '游玩',
|
||
modal_game_desc: '游戏简介',
|
||
modal_no_desc: '暂无简介',
|
||
modal_features: '核心特性',
|
||
modal_dev: '开发商',
|
||
modal_pub: '发行商',
|
||
modal_size: '游戏大小',
|
||
modal_version: '当前版本',
|
||
modal_release: '发行日期',
|
||
modal_platform: '支持平台',
|
||
modal_downloads: '下载量',
|
||
modal_play_btn: '开始游戏',
|
||
modal_cloud_note: '云端启动 · 无需下载安装',
|
||
toast_cloud_launch: '正在云端启动',
|
||
toast_please_wait: ',请稍候…',
|
||
toast_fav_removed: '已取消收藏',
|
||
toast_fav_added: '已加入收藏',
|
||
footer_brand_desc: '新一代云端游戏平台,让任何人都能在任何设备上畅玩 3A 大作。告别硬件限制,拥抱无限可能。',
|
||
footer_platform: '平台',
|
||
footer_hot_ranking: '热门排行',
|
||
footer_new_release: '最新上线',
|
||
footer_support: '支持',
|
||
footer_help_center: '帮助中心',
|
||
footer_device_req: '设备要求',
|
||
footer_network_diag: '网络诊断',
|
||
footer_contact: '联系我们',
|
||
footer_about: '关于',
|
||
footer_about_us: '关于我们',
|
||
footer_terms: '服务条款',
|
||
footer_privacy: '隐私政策',
|
||
footer_partners: '合作伙伴',
|
||
footer_copyright: '保留所有权利。',
|
||
footer_powered: '由云端算力驱动 · 即开即玩',
|
||
mock_cat_action: '动作冒险',
|
||
mock_cat_rpg: '角色扮演',
|
||
mock_cat_strategy: '策略模拟',
|
||
mock_cat_casual: '休闲益智',
|
||
mock_cat_sports: '体育竞速',
|
||
mock_cat_shooter: '射击对战'
|
||
},
|
||
'en': {
|
||
nav_home: 'Home',
|
||
nav_browse: 'Browse',
|
||
nav_hot: 'Trending',
|
||
nav_new: 'New',
|
||
nav_search_placeholder: 'Search games...',
|
||
btn_signin: 'Sign In',
|
||
hero_badge: 'Cloud Streaming · Zero Latency · 4K Quality',
|
||
hero_title_line1: 'Cloud Gaming',
|
||
hero_title_line2: 'Play Instantly',
|
||
hero_desc: 'No downloads, no high-end hardware needed. Play AAA titles on any device — from phones to TVs, with seamless progress sync. Powerful cloud computing makes every frame silky smooth.',
|
||
hero_cta_play: 'Play Now',
|
||
hero_cta_browse: 'Browse All Games',
|
||
hero_stat_games: 'games',
|
||
hero_stat_uptime: 'Service Uptime',
|
||
hero_stat_latency: 'Avg Latency',
|
||
hero_card_hot: 'Trending Now',
|
||
hero_card_cloud_note: 'Cloud Launch · No Download',
|
||
cat_all: 'All',
|
||
section_hot: 'Trending Games',
|
||
section_hot_sub: 'The most popular cloud titles right now',
|
||
section_view_all: 'View All',
|
||
section_new: 'Latest Releases',
|
||
section_new_sub: 'Fresh arrivals, play instantly in the cloud',
|
||
section_library: 'All Games',
|
||
section_library_sub: 'Explore the complete cloud game library',
|
||
lib_search_placeholder: 'Search game title...',
|
||
sort_default: 'Default',
|
||
sort_rating: 'Top Rated',
|
||
sort_plays: 'Most Popular',
|
||
sort_newest: 'Newest',
|
||
empty_hot: 'No trending games yet',
|
||
empty_new: 'No new games yet',
|
||
empty_no_match: 'No matching games found',
|
||
empty_game_not_found: 'Game not found',
|
||
modal_badge_hot: 'Hot',
|
||
modal_badge_new: 'New',
|
||
modal_play_plays: 'plays',
|
||
modal_game_desc: 'Game Description',
|
||
modal_no_desc: 'No description available',
|
||
modal_features: 'Key Features',
|
||
modal_dev: 'Developer',
|
||
modal_pub: 'Publisher',
|
||
modal_size: 'Game Size',
|
||
modal_version: 'Version',
|
||
modal_release: 'Release Date',
|
||
modal_platform: 'Platform',
|
||
modal_downloads: 'Downloads',
|
||
modal_play_btn: 'Start Game',
|
||
modal_cloud_note: 'Cloud Launch · No Download Required',
|
||
toast_cloud_launch: 'Launching',
|
||
toast_please_wait: ' in the cloud, please wait…',
|
||
toast_fav_removed: 'Removed from favorites',
|
||
toast_fav_added: 'Added to favorites',
|
||
footer_brand_desc: 'A new generation cloud gaming platform that lets anyone play AAA titles on any device. Say goodbye to hardware limitations and embrace infinite possibilities.',
|
||
footer_platform: 'Platform',
|
||
footer_hot_ranking: 'Top Charts',
|
||
footer_new_release: 'New Releases',
|
||
footer_support: 'Support',
|
||
footer_help_center: 'Help Center',
|
||
footer_device_req: 'Device Requirements',
|
||
footer_network_diag: 'Network Diagnostics',
|
||
footer_contact: 'Contact Us',
|
||
footer_about: 'About',
|
||
footer_about_us: 'About Us',
|
||
footer_terms: 'Terms of Service',
|
||
footer_privacy: 'Privacy Policy',
|
||
footer_partners: 'Partners',
|
||
footer_copyright: 'All rights reserved.',
|
||
footer_powered: 'Powered by Cloud Computing · Play Instantly',
|
||
mock_cat_action: 'Action Adventure',
|
||
mock_cat_rpg: 'RPG',
|
||
mock_cat_strategy: 'Strategy',
|
||
mock_cat_casual: 'Casual',
|
||
mock_cat_sports: 'Sports & Racing',
|
||
mock_cat_shooter: 'Shooter'
|
||
},
|
||
'zh-TW': {
|
||
nav_home: '首頁',
|
||
nav_browse: '瀏覽',
|
||
nav_hot: '熱門',
|
||
nav_new: '新遊',
|
||
nav_search_placeholder: '搜尋遊戲...',
|
||
btn_signin: '登入',
|
||
hero_badge: '雲端串流 · 零延遲 · 4K 畫質',
|
||
hero_title_line1: '雲端遊戲',
|
||
hero_title_line2: '即開即玩',
|
||
hero_desc: '無需下載,無需高端硬體。在任意裝置上暢玩 3A 大作,從手機到電視,進度無縫銜接。強大的雲端算力,讓每一幀都絲滑流暢。',
|
||
hero_cta_play: '立即體驗',
|
||
hero_cta_browse: '瀏覽全部遊戲',
|
||
hero_stat_games: '款遊戲',
|
||
hero_stat_uptime: '服務可用率',
|
||
hero_stat_latency: '平均延遲',
|
||
hero_card_hot: '正在熱玩',
|
||
hero_card_cloud_note: '雲端啟動 · 無需下載',
|
||
cat_all: '全部',
|
||
section_hot: '熱門遊戲',
|
||
section_hot_sub: '當下最受玩家追捧的雲端大作',
|
||
section_view_all: '查看全部',
|
||
section_new: '最新上線',
|
||
section_new_sub: '新鮮上架,第一時間雲端暢玩',
|
||
section_library: '全部遊戲',
|
||
section_library_sub: '探索完整的雲端遊戲庫',
|
||
lib_search_placeholder: '搜尋遊戲名稱...',
|
||
sort_default: '預設排序',
|
||
sort_rating: '評分最高',
|
||
sort_plays: '最受歡迎',
|
||
sort_newest: '最新上線',
|
||
empty_hot: '暫無熱門遊戲',
|
||
empty_new: '暫無新遊',
|
||
empty_no_match: '沒有找到匹配的遊戲',
|
||
empty_game_not_found: '遊戲不存在',
|
||
modal_badge_hot: '熱門',
|
||
modal_badge_new: '新遊',
|
||
modal_play_plays: '遊玩',
|
||
modal_game_desc: '遊戲簡介',
|
||
modal_no_desc: '暫無簡介',
|
||
modal_features: '核心特性',
|
||
modal_dev: '開發商',
|
||
modal_pub: '發行商',
|
||
modal_size: '遊戲大小',
|
||
modal_version: '目前版本',
|
||
modal_release: '發行日期',
|
||
modal_platform: '支援平台',
|
||
modal_downloads: '下載量',
|
||
modal_play_btn: '開始遊戲',
|
||
modal_cloud_note: '雲端啟動 · 無需下載安裝',
|
||
toast_cloud_launch: '正在雲端啟動',
|
||
toast_please_wait: ',請稍候…',
|
||
toast_fav_removed: '已取消收藏',
|
||
toast_fav_added: '已加入收藏',
|
||
footer_brand_desc: '新世代雲端遊戲平台,讓任何人都能在任意裝置上暢玩 3A 大作。告別硬體限制,攜抱無限可能。',
|
||
footer_platform: '平台',
|
||
footer_hot_ranking: '熱門排行',
|
||
footer_new_release: '最新上線',
|
||
footer_support: '支援',
|
||
footer_help_center: '幫助中心',
|
||
footer_device_req: '裝置要求',
|
||
footer_network_diag: '網路診斷',
|
||
footer_contact: '聯絡我們',
|
||
footer_about: '關於',
|
||
footer_about_us: '關於我們',
|
||
footer_terms: '服務條款',
|
||
footer_privacy: '隱私政策',
|
||
footer_partners: '合作夥伴',
|
||
footer_copyright: '保留所有權利。',
|
||
footer_powered: '由雲端算力驅動 · 即開即玩',
|
||
mock_cat_action: '動作冒險',
|
||
mock_cat_rpg: '角色扮演',
|
||
mock_cat_strategy: '策略模擬',
|
||
mock_cat_casual: '休閒益智',
|
||
mock_cat_sports: '體育競速',
|
||
mock_cat_shooter: '射擊對戰'
|
||
}
|
||
};
|
||
|
||
const LANG_LABELS = {'zh-CN':'中文','en':'EN','zh-TW':'繁中'};
|
||
|
||
let currentLang = (function(){
|
||
try{
|
||
var u=new URLSearchParams(location.search).get('lang');
|
||
if(u&&I18N_LOCALES[u]) return u;
|
||
}catch(e){}
|
||
try{var s=localStorage.getItem('lang');if(s&&I18N_LOCALES[s]) return s;}catch(e){}
|
||
var b=navigator.language||navigator.userLanguage||'';
|
||
if(b.startsWith('zh-TW')||b.startsWith('zh-Hant')) return 'zh-TW';
|
||
if(b.startsWith('zh')) return 'en';
|
||
return 'en';
|
||
})();
|
||
|
||
function t(key){return (I18N_LOCALES[currentLang]&&I18N_LOCALES[currentLang][key])||(I18N_LOCALES['zh-CN'][key])||key;}
|
||
|
||
function applyI18n(){
|
||
document.querySelectorAll('[data-i18n]').forEach(function(el){
|
||
var key=el.getAttribute('data-i18n');
|
||
el.textContent=t(key);
|
||
});
|
||
document.querySelectorAll('[data-i18n-placeholder]').forEach(function(el){
|
||
var key=el.getAttribute('data-i18n-placeholder');
|
||
el.placeholder=t(key);
|
||
});
|
||
document.documentElement.lang=currentLang==='zh-TW'?'zh-TW':currentLang==='zh-CN'?'zh-CN':'en';
|
||
var lbl=document.getElementById('langLabel');
|
||
if(lbl) lbl.textContent=LANG_LABELS[currentLang]||currentLang;
|
||
document.querySelectorAll('.lang-option').forEach(function(opt){
|
||
opt.classList.toggle('active',opt.getAttribute('data-lang')===currentLang);
|
||
});
|
||
document.title='CLOUD PLAY · '+t('hero_title_line2');
|
||
}
|
||
|
||
function switchLang(lang){
|
||
if(!I18N_LOCALES[lang]) return;
|
||
currentLang=lang;
|
||
try{localStorage.setItem('lang',lang);}catch(e){}
|
||
applyI18n();
|
||
if(typeof onLangChange==='function') onLangChange(lang);
|
||
document.getElementById('langDropdown').classList.remove('open');
|
||
}
|
||
window.switchLang = switchLang;
|
||
|
||
function toggleLangDropdown(){
|
||
document.getElementById('langDropdown').classList.toggle('open');
|
||
}
|
||
window.toggleLangDropdown = toggleLangDropdown;
|
||
document.addEventListener('click',function(e){
|
||
var sw=document.getElementById('langSwitcher');
|
||
if(sw&&!sw.contains(e.target)) document.getElementById('langDropdown').classList.remove('open');
|
||
});
|
||
|
||
let onLangChange = null;
|
||
|
||
/* ===========================================================================
|
||
API / Mock
|
||
=========================================================================== */
|
||
const API = "/api/v1/game";
|
||
|
||
const MOCK = {
|
||
categories: [],
|
||
games: []
|
||
};
|
||
|
||
function buildMockData(){
|
||
const catData = [
|
||
{id:1,nameKey:'mock_cat_action',icon:"action",sort:1},
|
||
{id:2,nameKey:'mock_cat_rpg',icon:"rpg",sort:2},
|
||
{id:3,nameKey:'mock_cat_strategy',icon:"strategy",sort:3},
|
||
{id:4,nameKey:'mock_cat_casual',icon:"casual",sort:4},
|
||
{id:5,nameKey:'mock_cat_sports',icon:"sports",sort:5},
|
||
{id:6,nameKey:'mock_cat_shooter',icon:"shooter",sort:6}
|
||
];
|
||
MOCK.categories = catData;
|
||
|
||
const namePool = {
|
||
'zh-CN': ['暗影纪元:觉醒','忍者之刃:影遁','深海迷踪','星域编年史','幻灵大陆:重生','修仙问道录','帝国黎明:文明崛起','星际拓荒者','农场物语:四季','方块消消乐:无尽版','脑洞大师','音符跳跃','极速狂飙:霓虹之城','绿茵传奇 2026','战术先锋:零点行动','星际战甲:远征','荒野求生:大逃杀'],
|
||
'en': ['Shadow Epoch: Awakening','Ninja Blade: Shadow Step','Deep Sea Mystery','Star Chronicles','Phantom Realm: Rebirth','Immortal Cultivation','Empire Dawn: Civilization Rising','Star Pioneer','Farm Story: Four Seasons','Block Puzzle: Endless','Brain Master','Neon Jump','Speed Rush: Neon City','Green Pitch 2026','Tactical Vanguard: Zero Hour','Star Armor: Expedition','Wild Survival: Battle Royale'],
|
||
'zh-TW': ['暗影紀元:覺醒','忍者之刃:影遁','深海迷蹤','星域編年史','幻靈大陸:重生','修仙問道錄','帝國黎明:文明崛起','星際拓荒者','農場物語:四季','方塊消消樂:無盡版','腦洞大師','音符跳躍','極速狂飆:霓虹之城','綠茵傳奇 2026','戰術先鋒:零點行動','星際戰甲:遠征','荒野求生:大逃殺']
|
||
};
|
||
const descPool = {
|
||
'zh-CN': ['开放世界动作冒险巨作。探索广袤的奇幻大陆,与远古巨龙激战,揭开失落文明的秘密。流畅的战斗系统,震撼的视觉效果,超过 100 小时的主线剧情等你体验。','硬核动作游戏,融合忍术与暗杀。在战国时代的日本,扮演最后的影忍,用速度和技巧消灭敌人。像素级精准操作,爽快连招系统。','水下探险生存游戏。在神秘的深海世界中采集资源、建造基地、发现远古遗迹。当心隐藏在黑暗中的深海巨兽。','科幻 MMORPG 巨作。跨越银河系的史诗冒险,选择你的种族和职业,与千万玩家共同书写星际历史。','经典日式 RPG 回归。手绘风格画面,感人至深的剧情,回合制战斗系统全新升级。收集 200+ 幻灵伙伴,组建你的最强队伍。','国风仙侠开放世界 RPG。从凡人修炼至飞升仙界,体验完整的修仙体系。御剑飞行、炼丹炼器、渡劫飞升,感受东方仙侠之美。','4X 策略巨作。从石器时代到星际文明,带领你的民族走过万年历史。外交、战争、科技、文化,多种胜利方式。','太空殖民模拟经营。建立你的星际帝国,开发星球资源,研究科技,与外星文明交流或对抗。沙盒式自由玩法。','治愈系农场模拟。种田、养殖、钓鱼、采矿、交友。四季变换的美景,温馨的村庄生活。支持 4 人联机合作。','经典三消玩法全新升级。数百个精心设计的关卡,丰富的道具和障碍,每周更新限时活动。轻松上手,难以放下。','创意脑筋急转弯合集。1000+ 脑洞题目,考验你的逆向思维和创意。每关都有意想不到的解法。','音乐节奏游戏。跟随节拍跳跃,感受音乐与操作的完美融合。100+ 授权热门曲目,多种难度可选。','赛博朋克风格竞速游戏。在霓虹闪烁的未来都市飙车,改装你的超跑,挑战极限赛道。支持 16 人在线竞速。','次世代足球模拟。真实球员授权,精细到每个动作的动作捕捉。全新战术系统,经理模式深度运营。','5v5 战术 FPS 对战。精准枪械手感,丰富的战术道具,公平竞技无付费增强。排位赛、休闲赛、僵尸模式应有尽有。','PvE 合作射击 RPG。扮演星际战士,穿着可定制战甲,使用超能力和先进武器,与队友一起完成史诗任务。','100 人大逃杀。跳伞、搜装备、缩圈、活到最后。多种地图和模式,真实弹道物理。战术竞技的极致体验。'],
|
||
'en': ['Open-world action adventure epic. Explore a vast fantasy continent, battle ancient dragons, and uncover the secrets of a lost civilization. Fluid combat, stunning visuals, and 100+ hours of main story.','Hardcore action game blending ninjutsu and assassination. Play as the last shadow ninja in Sengoku-era Japan, eliminating enemies with speed and precision. Pixel-perfect controls and satisfying combo system.','Underwater exploration survival game. Gather resources, build bases, and discover ancient ruins in a mysterious deep-sea world. Beware of the leviathans lurking in the darkness.','Sci-fi MMORPG epic. An intergalactic saga where you choose your race and class, writing stellar history alongside millions of players.','Classic JRPG returns. Hand-drawn art style, deeply emotional story, and a revamped turn-based combat system. Collect 200+ Phantom companions to build your ultimate team.','Chinese xianxia open-world RPG. Cultivate from mortal to immortal, experiencing a complete cultivation system. Sword flying, alchemy, tribulation ascension.','4X strategy epic. Guide your civilization from the Stone Age to interstellar文明 through diplomacy, war, technology, and culture. Multiple victory paths. Supports 12-player multiplayer.','Space colonization sim. Build your interstellar empire, develop planetary resources, research tech, and engage with alien civilizations. Sandbox freedom.','Healing farm sim. Grow crops, raise animals, fish, mine, and befriend villagers. Four seasons of scenic beauty and cozy village life. Supports 4-player co-op.','Classic match-3 gameplay reimagined. Hundreds of精心设计 levels with rich power-ups and obstacles. Weekly limited-time events. Easy to pick up, hard to put down.','Creative brain teaser collection. 1000+ mind-bending puzzles testing your lateral thinking and creativity. Every level has an unexpected solution.','Music rhythm game. Jump to the beat and feel the perfect fusion of music and gameplay. 100+ licensed popular tracks with multiple difficulty levels.','Cyberpunk-style racing game. Race through neon-lit future cityscapes, customize your supercar, and tackle extreme tracks. Supports 16-player online racing.','Next-gen football simulation. Real player授权, motion-capture-level animation detail. New tactical system and deep manager mode. Official esports title.','5v5 tactical FPS. Precise gunplay, rich tactical tools, fair competition with no pay-to-win. Ranked, casual, and zombie modes available.','PvE co-op shooter RPG. Play as a star warrior in customizable power armor, using superpowers and advanced weapons to complete epic missions with teammates.','100-player battle royale. Drop in, gear up, survive the shrinking zone, and be the last one standing. Multiple maps and modes with realistic ballistics.'],
|
||
'zh-TW': ['開放世界動作冒險巨作。探索廣袤的奇幻大陸,與遠古巨龍激戰,揭開失落文明的秘密。流暢的戰鬥系統,震撼的視覺效果,超過 100 小時的主線劇情等你體驗。','硬核動作遊戲,融合忍術與暗殺。在戰國時代的日本,扮演最後的影忍,用速度和技巧消滅敵人。像素級精準操作,爽快連招系統。','水下探險生存遊戲。在神秘的深海世界中採集資源、建造基地、發現遠古遺跡。當心隱藏在黑暗中的深海巨獸。','科幻 MMORPG 巨作。跨越銀河系的史詩冒險,選擇你的種族和職業,與千萬玩家共同書寫星際歷史。','經典日式 RPG 回歸。手繪風格畫面,感人至深的劇情,回合制戰鬥系統全新升級。收集 200+ 幻靈夥伴,組建你的最強隊伍。','國風仙俠開放世界 RPG。從凡人修煉至飛升仙界,體驗完整的修仙體系。御劍飛行、煉丹煉器、渡劫飛升,感受東方仙俠之美。','4X 策略巨作。從石器時代到星際文明,帶領你的民族走過萬年歷史。外交、戰爭、科技、文化,多種勝利方式。','太空殖民模擬經營。建立你的星際帝國,開發星球資源,研究科技,與外星文明交流或對抗。沙盒式自由玩法。','治癒系農場模擬。種田、養殖、釣魚、採礦、交友。四季變換的美景,溫馨的村莊生活。支援 4 人聯機合作。','經典三消玩法全新升級。數百個精心設計的關卡,豐富的道具和障礙,每週更新限時活動。輕鬆上手,難以放下。','創意腦筋急轉彎合集。1000+ 腦洞題目,考驗你的逆向思維和創意。每關都有意想不到的解法。','音樂節奏遊戲。跟隨節拍跳躍,感受音樂與操作的完美融合。100+ 授權熱門曲目,多種難度可選。','賽博朋克風格競速遊戲。在霓虹閃爍的未來都市飆車,改裝你的超跑,挑戰極限賽道。支援 16 人線上競速。','次世代足球模擬。真實球員授權,精細到每個動作的動作捕捉。全新戰術系統,經理模式深度運營。','5v5 戰術 FPS 對戰。精準槍械手感,豐富的戰術道具,公平競技無付費增強。排位賽、休閒賽、殭屍模式應有盡有。','PvE 合作射擊 RPG。扮演星際戰士,穿著可定制戰甲,使用超能力和先進武器,與隊友一起完成史詩任務。','100 人大逃殺。跳傘、搜裝備、縮圈、活到最後。多種地圖和模式,真實彈道物理。戰術競技的極致體驗。']
|
||
};
|
||
|
||
const tagPool = {
|
||
'zh-CN': ['开放世界','多人在线','单人剧情','PVP','PVE','沙盒','硬核','多人合作','次世代','免费游玩'],
|
||
'en': ['Open World','Multiplayer','Story Mode','PVP','PVE','Sandbox','Hardcore','Co-op','Next-Gen','Free to Play'],
|
||
'zh-TW': ['開放世界','多人線上','單人劇情','PVP','PVE','沙盒','硬核','多人合作','次世代','免費遊玩']
|
||
};
|
||
const featPool = {
|
||
'zh-CN': ['无缝开放世界','实时战斗系统','动态天气系统','多人在线合作','分支剧情选择','跨平台云存档'],
|
||
'en': ['Seamless Open World','Real-Time Combat','Dynamic Weather','Online Co-op','Branching Story','Cross-Platform Cloud Save'],
|
||
'zh-TW': ['無縫開放世界','即時戰鬥系統','動態天氣系統','多人線上合作','分支劇情選擇','跨平台雲端存檔']
|
||
};
|
||
const devs = ['Phoenix Studio','Shadow Forge','Abyss Team','Galaxy Works','Dreamlight','云海工作室','Strategy Labs','Cosmic Dev','Green Valley','Fun Factory','Brain Box','Rhythm Works','Turbo Studio','Sports Pro','Tactical Ops','Digital Forge','Battle Royal'];
|
||
const plats = {'zh-CN':['PC / PS5 / Xbox','PC / PS5','PC / Switch','全平台','PC / Mobile'],'en':['PC / PS5 / Xbox','PC / PS5','PC / Switch','All Platforms','PC / Mobile'],'zh-TW':['PC / PS5 / Xbox','PC / PS5','PC / Switch','全平台','PC / Mobile']};
|
||
const catIds = [1,1,1,2,2,2,3,3,3,4,4,4,5,5,6,6,6];
|
||
const hotIds = [1,2,4,6,7,9,10,13,15,16,17];
|
||
const newIds = [3,11,17];
|
||
|
||
function rand(arr){return arr[Math.floor(Math.random()*arr.length)];}
|
||
function randInt(a,b){return Math.floor(Math.random()*(b-a+1))+a;}
|
||
|
||
const games = [];
|
||
for(let i=0;i<namePool['zh-CN'].length;i++){
|
||
const id=i+1;
|
||
const lang=currentLang;
|
||
const names=namePool[lang]||namePool['zh-CN'];
|
||
const descs=descPool[lang]||descPool['zh-CN'];
|
||
const tags=tagPool[lang]||tagPool['zh-CN'];
|
||
const feats=featPool[lang]||featPool['zh-CN'];
|
||
const st=plats[lang]||plats['zh-CN'];
|
||
const shuffledTags=[...tags].sort(()=>Math.random()-.5).slice(0,randInt(2,4));
|
||
const shuffledFeats=[...feats].sort(()=>Math.random()-.5).slice(0,randInt(3,5));
|
||
games.push({
|
||
id, name:names[i]||'Game '+id,
|
||
description:descs[i%descs.length],
|
||
category_id:catIds[i]||1,
|
||
category_name:t(MOCK.categories[(catIds[i]||1)-1].nameKey),
|
||
cover:"",
|
||
developer:rand(devs), publisher:rand(devs),
|
||
rating:(4+Math.random()).toFixed(1),
|
||
plays:randInt(100000,3500000),
|
||
downloads:randInt(50000,2500000),
|
||
size:randInt(3,80)+'.'+randInt(0,9)+' GB',
|
||
version:'v'+randInt(1,8)+'.'+randInt(0,5)+'.'+randInt(0,9),
|
||
release_date:'2026-'+String(randInt(1,12)).padStart(2,'0')+'-'+String(randInt(1,28)).padStart(2,'0'),
|
||
platform:rand(st),
|
||
tags:JSON.stringify(shuffledTags),
|
||
screenshots:'[]',
|
||
features:JSON.stringify(shuffledFeats),
|
||
is_hot:hotIds.includes(id),
|
||
is_new:newIds.includes(id),
|
||
sort:id, status:1
|
||
});
|
||
}
|
||
MOCK.games=games;
|
||
}
|
||
buildMockData();
|
||
|
||
/* ===========================================================================
|
||
State
|
||
=========================================================================== */
|
||
const state = {
|
||
categories: [],
|
||
libCategory: 0,
|
||
libKeyword: "",
|
||
libSort: "",
|
||
libPage: 1,
|
||
libPageSize: 10,
|
||
libTotal: 0,
|
||
};
|
||
|
||
/* ===========================================================================
|
||
工具函数
|
||
=========================================================================== */
|
||
function $(sel,root){return(root||document).querySelector(sel);}
|
||
function $$(sel,root){return Array.from((root||document).querySelectorAll(sel));}
|
||
|
||
function formatNumber(n){
|
||
n=Number(n)||0;
|
||
if(n>=10000){return(n/10000).toFixed(n%10000===0?0:1).replace(/\.0$/,'')+"万";}
|
||
return String(n);
|
||
}
|
||
|
||
function parseJSON(str,fallback){
|
||
if(Array.isArray(str))return str;
|
||
if(typeof str!=="string"||!str)return fallback||[];
|
||
try{const v=JSON.parse(str);return Array.isArray(v)?v:(fallback||[]);}
|
||
catch(e){return fallback||[];}
|
||
}
|
||
|
||
function hashStr(s){
|
||
let h=0;
|
||
for(let i=0;i<s.length;i++){h=(h<<5)-h+s.charCodeAt(i);h|=0;}
|
||
return Math.abs(h);
|
||
}
|
||
|
||
const PALETTES=[
|
||
["#3b82f6","#8b5cf6"],["#10b981","#3b82f6"],["#8b5cf6","#ec4899"],
|
||
["#f59e0b","#ef4444"],["#06b6d4","#3b82f6"],["#ec4899","#8b5cf6"],
|
||
["#14b8a6","#06b6d4"],["#f43f5e","#f59e0b"],["#6366f1","#a855f7"],
|
||
["#0ea5e9","#22c55e"],["#d946ef","#6366f1"],["#f97316","#eab308"]
|
||
];
|
||
|
||
function coverGradient(name){
|
||
const p=PALETTES[hashStr(name||"x")%PALETTES.length];
|
||
return `linear-gradient(135deg,${p[0]} 0%,${p[1]} 100%)`;
|
||
}
|
||
|
||
function coverArt(game,hidden){
|
||
const grad=coverGradient(game.name);
|
||
const ch=(game.name||"?").replace(/[^\u4e00-\u9fa5a-zA-Z0-9]/g,"").charAt(0)||"?";
|
||
const disp=hidden?'display:none;':'';
|
||
return `<div class="cv-art gcover" style="${disp}background:${grad}">
|
||
<span class="gc-orb1"></span><span class="gc-orb2"></span>
|
||
<span class="gc-watermark">${ch}</span>
|
||
<span class="gc-cat">${game.category_name||""}</span>
|
||
</div>`;
|
||
}
|
||
|
||
function coverHTML(game,opts){
|
||
opts=opts||{};
|
||
let inner;
|
||
if(game.cover){
|
||
inner=`<img class="cv-art" src="${game.cover}" alt="${game.name}" style="width:100%;height:100%;object-fit:cover" onerror="this.style.display='none';this.nextElementSibling.style.display=''">`+coverArt(game,true);
|
||
}else{
|
||
inner=coverArt(game);
|
||
}
|
||
let badges="";
|
||
if(game.is_hot)badges+=`<span class="cv-badge badge-hot">${t('modal_badge_hot')}</span>`;
|
||
else if(game.is_new)badges+=`<span class="cv-badge badge-new">${t('modal_badge_new')}</span>`;
|
||
if(game.rating){badges+=`<span class="cv-rating">★ ${Number(game.rating).toFixed(1)}</span>`;}
|
||
const play=opts.noPlay?"":`<div class="play-overlay"><div class="po-btn"><svg viewBox="0 0 24 24"><path d="M8 5v14l11-7z"/></svg></div></div>`;
|
||
return `${inner}<div class="cv-glow"></div>${badges}${play}`;
|
||
}
|
||
|
||
function starsHTML(rating){
|
||
rating=Number(rating)||0;
|
||
const pct=Math.max(0,Math.min(100,(rating/5)*100));
|
||
return `<span class="stars" style="--pct:${pct}%">
|
||
<span class="bg">★★★★★</span>
|
||
<span class="fg" style="width:${pct}%">★★★★★</span>
|
||
</span>`;
|
||
}
|
||
|
||
function debounce(fn,wait){
|
||
let t;
|
||
return function(){
|
||
const ctx=this,args=arguments;
|
||
clearTimeout(t);
|
||
t=setTimeout(()=>fn.apply(ctx,args),wait||300);
|
||
};
|
||
}
|
||
|
||
function toast(msg,type){
|
||
const wrap=$("#toastWrap");
|
||
const el=document.createElement("div");
|
||
el.className="toast "+(type||"success");
|
||
el.innerHTML=`<svg class="ti" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><path d="M20 6 9 17l-5-5"/></svg><span>${msg}</span>`;
|
||
wrap.appendChild(el);
|
||
requestAnimationFrame(()=>el.classList.add("show"));
|
||
setTimeout(()=>{el.classList.remove("show");setTimeout(()=>el.remove(),400);},2600);
|
||
}
|
||
|
||
/* ===========================================================================
|
||
API 层(fetch 优先,失败回退 Mock)
|
||
=========================================================================== */
|
||
async function apiGet(path,params){
|
||
const url=new URL(API+path,location.origin);
|
||
if(params){Object.keys(params).forEach(k=>{if(params[k]!==undefined&¶ms[k]!==""&¶ms[k]!==null)url.searchParams.set(k,params[k]);});}
|
||
try{
|
||
const res=await fetch(url.toString(),{headers:{"Accept":"application/json"}});
|
||
if(!res.ok)throw new Error("HTTP "+res.status);
|
||
const json=await res.json();
|
||
if(json&&json.code===0)return json.data;
|
||
throw new Error(json&&json.message||"接口异常");
|
||
}catch(e){
|
||
return mockRoute(path,params);
|
||
}
|
||
}
|
||
|
||
function mockRoute(path,params){
|
||
params=params||{};
|
||
if(path==="/home"){
|
||
return{
|
||
categories:MOCK.categories,
|
||
hot:MOCK.games.filter(g=>g.is_hot).sort((a,b)=>b.plays-a.plays).slice(0,8),
|
||
new:MOCK.games.filter(g=>g.is_new).slice(0,4)
|
||
};
|
||
}
|
||
if(path==="/categories"){return MOCK.categories;}
|
||
if(path==="/hot"){
|
||
let limit=parseInt(params.limit)||8;
|
||
return MOCK.games.filter(g=>g.is_hot).sort((a,b)=>b.plays-a.plays).slice(0,limit);
|
||
}
|
||
if(path==="/new"){
|
||
let limit=parseInt(params.limit)||4;
|
||
return MOCK.games.filter(g=>g.is_new).slice(0,limit);
|
||
}
|
||
if(path==="/games"){
|
||
let list=MOCK.games.slice();
|
||
if(params.category_id){const cid=parseInt(params.category_id);list=list.filter(g=>g.category_id===cid);}
|
||
if(params.keyword){const kw=params.keyword.toLowerCase();list=list.filter(g=>(g.name||"").toLowerCase().includes(kw)||(g.description||"").toLowerCase().includes(kw));}
|
||
const sort=params.sort;
|
||
if(sort==="rating")list.sort((a,b)=>b.rating-a.rating);
|
||
else if(sort==="plays")list.sort((a,b)=>b.plays-a.plays);
|
||
else if(sort==="newest")list.sort((a,b)=>new Date(b.release_date)-new Date(a.release_date));
|
||
else list.sort((a,b)=>a.sort-b.sort);
|
||
const total=list.length;
|
||
const page=parseInt(params.page)||1;
|
||
const pageSize=parseInt(params.page_size)||12;
|
||
const start=(page-1)*pageSize;
|
||
return{list:list.slice(start,start+pageSize),total,page,page_size:pageSize};
|
||
}
|
||
const m=path.match(/^\/games\/(\d+)$/);
|
||
if(m){const id=parseInt(m[1]);return MOCK.games.find(g=>g.id===id)||null;}
|
||
return null;
|
||
}
|
||
|
||
/* ===========================================================================
|
||
渲染:分类
|
||
=========================================================================== */
|
||
function renderCategoryPills(cats){
|
||
const row=$("#catRow");
|
||
const all=`<button class="cat-pill active" data-cat="0">${t('cat_all')}</button>`;
|
||
row.innerHTML=all+cats.map(c=>`<button class="cat-pill" data-cat="${c.id}">${t(c.nameKey)}</button>`).join("");
|
||
$$(".cat-pill",row).forEach(btn=>{
|
||
btn.addEventListener("click",()=>{
|
||
$$(".cat-pill",row).forEach(b=>b.classList.remove("active"));
|
||
btn.classList.add("active");
|
||
state.libCategory=parseInt(btn.dataset.cat);
|
||
state.libPage=1;
|
||
$$("#libCats .cat-pill").forEach(b=>{
|
||
b.classList.toggle("active",b.dataset.cat===btn.dataset.cat);
|
||
});
|
||
loadLibrary();
|
||
document.getElementById("library").scrollIntoView({behavior:"smooth"});
|
||
});
|
||
});
|
||
const libCats=$("#libCats");
|
||
libCats.innerHTML=`<button class="cat-pill active" data-cat="0">${t('cat_all')}</button>`+cats.map(c=>`<button class="cat-pill" data-cat="${c.id}">${t(c.nameKey)}</button>`).join("");
|
||
$$(".cat-pill",libCats).forEach(btn=>{
|
||
btn.addEventListener("click",()=>{
|
||
$$(".cat-pill",libCats).forEach(b=>b.classList.remove("active"));
|
||
btn.classList.add("active");
|
||
state.libCategory=parseInt(btn.dataset.cat);
|
||
state.libPage=1;
|
||
$$("#catRow .cat-pill").forEach(b=>{
|
||
b.classList.toggle("active",b.dataset.cat===btn.dataset.cat);
|
||
});
|
||
loadLibrary();
|
||
});
|
||
});
|
||
}
|
||
|
||
/* ===========================================================================
|
||
渲染:热门游戏
|
||
=========================================================================== */
|
||
function renderHot(games){
|
||
const grid=$("#hotGrid");
|
||
if(!games||!games.length){grid.innerHTML=emptyHTML(t('empty_hot'));return;}
|
||
const big=games[0];
|
||
const rest=games.slice(1,5);
|
||
let html=`<div class="card big" data-id="${big.id}">
|
||
<div class="cover">${coverHTML(big)}</div>
|
||
<div class="card-body">
|
||
<h3 class="card-title">${big.name}</h3>
|
||
<div class="card-meta">
|
||
<span>${big.category_name||""}</span><span class="dot"></span>
|
||
<span>${big.developer||""}</span><span class="dot"></span>
|
||
<span>${formatNumber(big.plays)} ${t('modal_play_plays')}</span>
|
||
</div>
|
||
<p class="card-desc">${big.description||""}</p>
|
||
<div class="card-tags">${parseJSON(big.tags).slice(0,4).map(t=>`<span class="tag-mini">${t}</span>`).join("")}</div>
|
||
</div>
|
||
</div>`;
|
||
rest.forEach(g=>{
|
||
html+=`<div class="card" data-id="${g.id}">
|
||
<div class="cover">${coverHTML(g)}</div>
|
||
<div class="card-body">
|
||
<h3 class="card-title">${g.name}</h3>
|
||
<div class="card-meta">
|
||
<span>${g.category_name||""}</span><span class="dot"></span>
|
||
<span>${formatNumber(g.plays)} ${t('modal_play_plays')}</span>
|
||
</div>
|
||
</div>
|
||
</div>`;
|
||
});
|
||
grid.innerHTML=html;
|
||
bindCardClicks(grid);
|
||
}
|
||
|
||
/* ===========================================================================
|
||
渲染:新游
|
||
=========================================================================== */
|
||
function renderNew(games){
|
||
const scroll=$("#newScroll");
|
||
if(!games||!games.length){scroll.innerHTML=emptyHTML(t('empty_new'));return;}
|
||
scroll.innerHTML=games.map(g=>`
|
||
<div class="card" data-id="${g.id}">
|
||
<div class="cover">${coverHTML(g)}</div>
|
||
<div class="card-body">
|
||
<h3 class="card-title">${g.name}</h3>
|
||
<div class="card-meta">
|
||
<span>${g.category_name||""}</span><span class="dot"></span>
|
||
<span>${g.release_date||""}</span>
|
||
</div>
|
||
</div>
|
||
</div>`).join("");
|
||
bindCardClicks(scroll);
|
||
}
|
||
|
||
/* ===========================================================================
|
||
渲染:游戏库
|
||
=========================================================================== */
|
||
function renderLibrary(data){
|
||
const grid=$("#gameGrid");
|
||
const info=$("#libInfo");
|
||
if(!data||!data.list||!data.list.length){
|
||
grid.innerHTML="";
|
||
info.textContent=t('empty_no_match');
|
||
renderPager(0,1);
|
||
return;
|
||
}
|
||
grid.innerHTML=data.list.map(g=>`
|
||
<div class="card" data-id="${g.id}">
|
||
<div class="cover">${coverHTML(g)}</div>
|
||
<div class="card-body">
|
||
<h3 class="card-title">${g.name}</h3>
|
||
<div class="card-meta">
|
||
<span>${g.category_name||""}</span><span class="dot"></span>
|
||
<span>${formatNumber(g.plays)} ${t('modal_play_plays')}</span>
|
||
</div>
|
||
</div>
|
||
</div>`).join("");
|
||
bindCardClicks(grid);
|
||
state.libTotal=data.total||0;
|
||
info.textContent=`${data.total} ${t('hero_stat_games')} · ${data.page} / ${Math.max(1,Math.ceil(data.total/(data.page_size||state.libPageSize)))}`;
|
||
renderPager(data.total,data.page,data.page_size||state.libPageSize);
|
||
}
|
||
|
||
function bindCardClicks(root){
|
||
$$(".card[data-id]",root).forEach(card=>{
|
||
card.addEventListener("click",()=>openModal(parseInt(card.dataset.id)));
|
||
});
|
||
}
|
||
|
||
function emptyHTML(text){
|
||
return `<div class="empty" style="grid-column:1/-1">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M21 15V6a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h9"/><path d="m16 19 2 2 4-4"/></svg>
|
||
<p>${text}</p></div>`;
|
||
}
|
||
|
||
/* ===========================================================================
|
||
分页
|
||
=========================================================================== */
|
||
function renderPager(total,page,pageSize){
|
||
const pager=$("#pager");
|
||
pageSize=pageSize||state.libPageSize;
|
||
const pages=Math.max(1,Math.ceil(total/pageSize));
|
||
if(pages<=1){pager.innerHTML="";return;}
|
||
let html=`<button data-page="prev" ${page<=1?"disabled":""}>‹</button>`;
|
||
const showPages=[];
|
||
if(pages<=7){for(let i=1;i<=pages;i++)showPages.push(i);}
|
||
else{
|
||
showPages.push(1);
|
||
if(page>3)showPages.push("...");
|
||
const start=Math.max(2,page-1),end=Math.min(pages-1,page+1);
|
||
for(let i=start;i<=end;i++)showPages.push(i);
|
||
if(page<pages-2)showPages.push("...");
|
||
showPages.push(pages);
|
||
}
|
||
showPages.forEach(p=>{
|
||
if(p==="...")html+=`<span class="dots">···</span>`;
|
||
else html+=`<button class="${p===page?'active':''}" data-page="${p}">${p}</button>`;
|
||
});
|
||
html+=`<button data-page="next" ${page>=pages?"disabled":""}>›</button>`;
|
||
pager.innerHTML=html;
|
||
$$("button[data-page]",pager).forEach(btn=>{
|
||
btn.addEventListener("click",()=>{
|
||
const p=btn.dataset.page;
|
||
let next=page;
|
||
if(p==="prev")next=page-1;
|
||
else if(p==="next")next=page+1;
|
||
else next=parseInt(p);
|
||
if(next<1||next>pages||next===page)return;
|
||
state.libPage=next;
|
||
loadLibrary(true);
|
||
});
|
||
});
|
||
}
|
||
|
||
/* ===========================================================================
|
||
骨架屏
|
||
=========================================================================== */
|
||
function skeletonGrid(n){
|
||
n=n||10;
|
||
let html="";
|
||
for(let i=0;i<n;i++){
|
||
html+=`<div class="skeleton"><div class="sk-shimmer sk-cover"></div>
|
||
<div class="sk-shimmer sk-line" style="width:70%"></div>
|
||
<div class="sk-shimmer sk-line" style="width:45%"></div></div>`;
|
||
}
|
||
return html;
|
||
}
|
||
|
||
function skeletonHot(){
|
||
return `<div class="skeleton" style="grid-row:span 2"><div class="sk-shimmer" style="height:380px"></div>
|
||
<div class="sk-shimmer sk-line" style="width:60%"></div><div class="sk-shimmer sk-line" style="width:80%"></div></div>`+
|
||
[1,2,3,4].map(()=>`<div class="skeleton"><div class="sk-shimmer" style="height:150px"></div><div class="sk-shimmer sk-line" style="width:70%"></div></div>`).join("");
|
||
}
|
||
|
||
/* ===========================================================================
|
||
数据加载
|
||
=========================================================================== */
|
||
async function loadHome(){
|
||
$("#hotGrid").innerHTML=skeletonHot();
|
||
$("#newScroll").innerHTML=[1,2,3,4].map(()=>`<div class="skeleton"><div class="sk-shimmer" style="height:140px"></div><div class="sk-shimmer sk-line" style="width:70%"></div></div>`).join("");
|
||
const data=await apiGet("/home");
|
||
if(data){
|
||
state.categories=data.categories||[];
|
||
renderCategoryPills(state.categories);
|
||
$("#statGames").textContent=MOCK.games.length;
|
||
animateCounter("#statGames",MOCK.games.length);
|
||
renderHot(data.hot||[]);
|
||
renderNew(data.new||[]);
|
||
renderHeroCard(data.hot&&data.hot[0]);
|
||
observeReveal();
|
||
}
|
||
}
|
||
|
||
function renderHeroCard(game){
|
||
if(!game)return;
|
||
const cover=$("#heroCover");
|
||
if(game.cover&&/^https?:\/\//.test(game.cover)){
|
||
cover.innerHTML=`<img src="${game.cover}" alt="${game.name}" style="width:100%;height:100%;object-fit:cover">`;
|
||
}else{
|
||
cover.style.background=coverGradient(game.name);
|
||
cover.innerHTML=`<span style="position:absolute;font-size:140px;font-weight:900;opacity:.18;top:-10px;right:-6px;color:#fff">${(game.name||"?").charAt(0)}</span>`;
|
||
}
|
||
$("#heroTitle").textContent=game.name;
|
||
$("#heroCat").textContent=game.category_name||"";
|
||
$("#heroDev").textContent=game.developer||"";
|
||
$("#heroPlay").onclick=()=>openModal(game.id);
|
||
}
|
||
|
||
async function loadLibrary(keepScroll){
|
||
const grid=$("#gameGrid");
|
||
if(!keepScroll)grid.innerHTML=skeletonGrid(10);
|
||
else grid.style.opacity=".4";
|
||
const data=await apiGet("/games",{
|
||
category_id:state.libCategory||undefined,
|
||
keyword:state.libKeyword||undefined,
|
||
sort:state.libSort||undefined,
|
||
page:state.libPage,
|
||
page_size:state.libPageSize
|
||
});
|
||
grid.style.opacity="1";
|
||
renderLibrary(data);
|
||
}
|
||
|
||
function animateCounter(sel,target){
|
||
const el=$(sel);
|
||
if(!el)return;
|
||
let cur=0;
|
||
const dur=1200,t0=performance.now();
|
||
function step(t){
|
||
const p=Math.min(1,(t-t0)/dur);
|
||
const eased=1-Math.pow(1-p,3);
|
||
el.textContent=Math.round(target*eased)+"+";
|
||
if(p<1)requestAnimationFrame(step);
|
||
}
|
||
requestAnimationFrame(step);
|
||
}
|
||
|
||
/* ===========================================================================
|
||
详情 Modal
|
||
=========================================================================== */
|
||
async function openModal(id){
|
||
const mask=$("#modalMask");
|
||
const modal=$("#modal");
|
||
modal.innerHTML=`<div class="modal-sk">
|
||
<div class="sk-shimmer"></div>
|
||
<div class="sk-shimmer sk-line w60" style="height:24px"></div>
|
||
<div class="sk-shimmer sk-line w40"></div>
|
||
<div class="sk-shimmer sk-line w90"></div>
|
||
<div class="sk-shimmer sk-line w90"></div>
|
||
<div class="sk-shimmer sk-line w60"></div>
|
||
</div>`;
|
||
mask.classList.add("open");
|
||
document.body.style.overflow="hidden";
|
||
const game=await apiGet("/games/"+id);
|
||
if(!game){modal.innerHTML=`<div class="empty" style="padding:80px"><p>${t('empty_game_not_found')}</p></div>`;return;}
|
||
renderModal(game);
|
||
}
|
||
|
||
function renderModal(g){
|
||
const modal=$("#modal");
|
||
const tags=parseJSON(g.tags);
|
||
const features=parseJSON(g.features);
|
||
const headerArt=g.cover
|
||
?`<img src="${g.cover}" alt="${g.name}" style="width:100%;height:100%;object-fit:cover" onerror="this.style.display='none';this.parentNode.querySelectorAll('[data-fb]').forEach(function(e){e.style.display=''})"><div data-fb style="position:absolute;inset:0;background:${coverGradient(g.name)};display:none"></div><span data-fb style="position:absolute;font-size:200px;font-weight:900;opacity:.14;top:-30px;right:10px;color:#fff;display:none">${(g.name||"?").charAt(0)}</span>`
|
||
:`<div style="position:absolute;inset:0;background:${coverGradient(g.name)}"></div>
|
||
<span style="position:absolute;font-size:200px;font-weight:900;opacity:.14;top:-30px;right:10px;color:#fff">${(g.name||"?").charAt(0)}</span>`;
|
||
|
||
const badges=[];
|
||
if(g.is_hot)badges.push(`<span class="cv-badge badge-hot">${t('modal_badge_hot')}</span>`);
|
||
if(g.is_new)badges.push(`<span class="cv-badge badge-new">${t('modal_badge_new')}</span>`);
|
||
|
||
modal.innerHTML=`
|
||
<div class="modal-header">
|
||
<button class="modal-close" id="modalClose" aria-label="Close">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke-width="2"><path d="M18 6 6 18M6 6l12 12"/></svg>
|
||
</button>
|
||
<div class="mh-art">${headerArt}</div>
|
||
<div class="modal-header-content">
|
||
<div class="modal-badges">${badges.join("")}</div>
|
||
<h2 class="modal-title">${g.name}</h2>
|
||
<div class="modal-sub">
|
||
<span>${g.category_name||""}</span>
|
||
<span>·</span>
|
||
<span>${starsHTML(g.rating)} ${Number(g.rating).toFixed(1)}</span>
|
||
<span>·</span>
|
||
<span>${formatNumber(g.plays)} ${t('modal_play_plays')}</span>
|
||
<span>·</span>
|
||
<span>${g.developer||""}</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="modal-body">
|
||
<div class="modal-tags">${tags.map(tg=>`<span class="tag-pill"># ${tg}</span>`).join("")}</div>
|
||
<div class="modal-row">
|
||
<div class="modal-desc">
|
||
<h4>${t('modal_game_desc')}</h4>
|
||
<p>${g.description||t('modal_no_desc')}</p>
|
||
<div class="modal-features">
|
||
<h4>${t('modal_features')}</h4>
|
||
<div class="feat-list">
|
||
${features.map(f=>`<div class="feat-item"><span class="ck"><svg viewBox="0 0 24 24" fill="none"><path d="M20 6 9 17l-5-5"/></svg></span>${f}</div>`).join("")}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="modal-info">
|
||
<div class="info-row"><span class="k">${t('modal_dev')}</span><span class="v">${g.developer||"—"}</span></div>
|
||
<div class="info-row"><span class="k">${t('modal_pub')}</span><span class="v">${g.publisher||"—"}</span></div>
|
||
<div class="info-row"><span class="k">${t('modal_size')}</span><span class="v">${g.size||"—"}</span></div>
|
||
<div class="info-row"><span class="k">${t('modal_version')}</span><span class="v">${g.version||"—"}</span></div>
|
||
<div class="info-row"><span class="k">${t('modal_release')}</span><span class="v">${g.release_date||"—"}</span></div>
|
||
<div class="info-row"><span class="k">${t('modal_platform')}</span><span class="v">${g.platform||"—"}</span></div>
|
||
<div class="info-row"><span class="k">${t('modal_downloads')}</span><span class="v">${formatNumber(g.downloads)}</span></div>
|
||
</div>
|
||
</div>
|
||
<div class="modal-cta">
|
||
<button class="btn-play-big" id="modalPlay">
|
||
<svg viewBox="0 0 24 24"><path d="M8 5v14l11-7z"/></svg>
|
||
${t('modal_play_btn')}
|
||
</button>
|
||
<button class="btn-fav" id="modalFav" aria-label="Favorite">
|
||
<svg viewBox="0 0 24 24"><path d="M19 14c1.49-1.46 3-3.21 3-5.5A5.5 5.5 0 0 0 16.5 3c-1.76 0-3 .5-4.5 2-1.5-1.5-2.74-2-4.5-2A5.5 5.5 0 0 0 2 8.5c0 2.29 1.51 4.04 3 5.5l7 7Z"/></svg>
|
||
</button>
|
||
<span style="color:var(--text-faint);font-size:13px">${t('modal_cloud_note')}</span>
|
||
</div>
|
||
</div>`;
|
||
|
||
$("#modalClose").addEventListener("click",closeModal);
|
||
$("#modalPlay").addEventListener("click",()=>toast(t('toast_cloud_launch')+" "+g.name+t('toast_please_wait'),"success"));
|
||
$("#modalFav").addEventListener("click",function(){
|
||
const svg=this.querySelector("svg");
|
||
const active=svg.getAttribute("data-on")==="1";
|
||
if(active){svg.setAttribute("data-on","0");svg.style.fill="none";svg.style.stroke="var(--text-dim)";toast(t('toast_fav_removed'));}
|
||
else{svg.setAttribute("data-on","1");svg.style.fill="var(--pink)";svg.style.stroke="var(--pink)";toast(t('toast_fav_added'));}
|
||
});
|
||
}
|
||
|
||
function closeModal(){
|
||
const mask=$("#modalMask");
|
||
mask.classList.remove("open");
|
||
document.body.style.overflow="";
|
||
}
|
||
|
||
/* ===========================================================================
|
||
滚动揭示动画
|
||
=========================================================================== */
|
||
let io;
|
||
function observeReveal(){
|
||
if(!io){
|
||
io=new IntersectionObserver((entries)=>{
|
||
entries.forEach(e=>{
|
||
if(e.isIntersecting){e.target.classList.add("in");io.unobserve(e.target);}
|
||
});
|
||
},{threshold:0.12,rootMargin:"0px 0px -40px 0px"});
|
||
}
|
||
$$(".reveal:not(.in)").forEach(el=>io.observe(el));
|
||
$("#hotGrid").classList.add("reveal");
|
||
$("#newScroll").classList.add("reveal");
|
||
$("#gameGrid").classList.add("reveal");
|
||
$$(".reveal:not(.in)").forEach(el=>io.observe(el));
|
||
}
|
||
|
||
/* ===========================================================================
|
||
事件绑定 & 初始化
|
||
=========================================================================== */
|
||
function bindEvents(){
|
||
const navSearch=$("#navSearch");
|
||
const libSearch=$("#libSearch");
|
||
navSearch.addEventListener("input",debounce(()=>{
|
||
state.libKeyword=navSearch.value.trim();
|
||
libSearch.value=state.libKeyword;
|
||
state.libPage=1;
|
||
loadLibrary();
|
||
if(state.libKeyword)document.getElementById("library").scrollIntoView({behavior:"smooth"});
|
||
},300));
|
||
|
||
libSearch.addEventListener("input",debounce(()=>{
|
||
state.libKeyword=libSearch.value.trim();
|
||
navSearch.value=state.libKeyword;
|
||
state.libPage=1;
|
||
loadLibrary();
|
||
},300));
|
||
|
||
$("#libSort").addEventListener("change",function(){
|
||
state.libSort=this.value;
|
||
state.libPage=1;
|
||
loadLibrary();
|
||
});
|
||
|
||
const header=$("#header");
|
||
window.addEventListener("scroll",()=>{
|
||
if(window.scrollY>20)header.classList.add("scrolled");
|
||
else header.classList.remove("scrolled");
|
||
},{passive:true});
|
||
|
||
const navMap={top:"home",library:"browse",hot:"hot",new:"new"};
|
||
window.addEventListener("scroll",()=>{
|
||
let current="home";
|
||
const sections=["top","hot","new","library"];
|
||
for(let i=sections.length-1;i>=0;i--){
|
||
const el=document.getElementById(sections[i]);
|
||
if(el&&el.getBoundingClientRect().top<=120){current=navMap[sections[i]];break;}
|
||
}
|
||
$$(".nav-links a").forEach(a=>a.classList.toggle("active",a.dataset.nav===current));
|
||
},{passive:true});
|
||
|
||
$("#modalMask").addEventListener("click",e=>{
|
||
if(e.target===e.currentTarget)closeModal();
|
||
});
|
||
document.addEventListener("keydown",e=>{
|
||
if(e.key==="Escape"&&$("#modalMask").classList.contains("open"))closeModal();
|
||
});
|
||
|
||
$(".menu-toggle").addEventListener("click",()=>{
|
||
document.getElementById("library").scrollIntoView({behavior:"smooth"});
|
||
});
|
||
|
||
$$('.nav-links a[href^="#"],a.btn-ghost[href^="#"],.foot-col a[href^="#"]').forEach(a=>{
|
||
a.addEventListener("click",e=>{
|
||
const href=a.getAttribute("href");
|
||
if(href&&href.length>1&&document.querySelector(href)){
|
||
e.preventDefault();
|
||
document.querySelector(href).scrollIntoView({behavior:"smooth"});
|
||
}
|
||
});
|
||
});
|
||
}
|
||
|
||
/* ===========================================================================
|
||
语言切换回调
|
||
=========================================================================== */
|
||
onLangChange = function(lang){
|
||
buildMockData();
|
||
state.libCategory=0;
|
||
state.libKeyword="";
|
||
state.libSort="";
|
||
state.libPage=1;
|
||
$("#navSearch").value="";
|
||
$("#libSearch").value="";
|
||
$("#libSort").value="";
|
||
loadHome();
|
||
loadLibrary();
|
||
};
|
||
|
||
/* ===========================================================================
|
||
初始化
|
||
=========================================================================== */
|
||
document.addEventListener("DOMContentLoaded",()=>{
|
||
applyI18n();
|
||
bindEvents();
|
||
loadHome();
|
||
loadLibrary();
|
||
observeReveal();
|
||
});
|
||
</script>
|
||
</body>
|
||
</html>
|