- 导航页(index.html)添加完整i18n引擎,支持zh-CN/zh-TW/en - 导航页添加语言切换按钮(EN/中/繁) - 所有20个前端模板默认语言从zh-CN改为en - 导航页SEO meta标签改为英文
1580 lines
81 KiB
HTML
1580 lines
81 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>应用商店 - 发现精彩应用</title>
|
||
<style>
|
||
/* ============ Reset & Base ============ */
|
||
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||
:root {
|
||
--blue: #0071e3;
|
||
--blue-light: #2997ff;
|
||
--blue-dark: #0058b0;
|
||
--bg: #ffffff;
|
||
--bg-soft: #f5f5f7;
|
||
--bg-softer: #fafafa;
|
||
--text: #1d1d1f;
|
||
--text-secondary: #6e6e73;
|
||
--text-tertiary: #86868b;
|
||
--border: #d2d2d7;
|
||
--border-soft: #e8e8ed;
|
||
--radius-sm: 10px;
|
||
--radius-md: 14px;
|
||
--radius-lg: 18px;
|
||
--radius-xl: 24px;
|
||
--shadow-sm: 0 1px 3px rgba(0,0,0,0.04), 0 1px 2px rgba(0,0,0,0.03);
|
||
--shadow-md: 0 4px 16px rgba(0,0,0,0.06), 0 2px 6px rgba(0,0,0,0.04);
|
||
--shadow-lg: 0 12px 40px rgba(0,0,0,0.10), 0 4px 12px rgba(0,0,0,0.05);
|
||
--header-h: 60px;
|
||
--max-w: 1200px;
|
||
}
|
||
html { scroll-behavior: smooth; }
|
||
body {
|
||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Helvetica Neue', 'PingFang SC', 'Microsoft YaHei', sans-serif;
|
||
background: var(--bg);
|
||
color: var(--text);
|
||
line-height: 1.5;
|
||
-webkit-font-smoothing: antialiased;
|
||
-moz-osx-font-smoothing: grayscale;
|
||
}
|
||
a { color: inherit; text-decoration: none; }
|
||
button { font-family: inherit; cursor: pointer; border: none; background: none; }
|
||
img { max-width: 100%; display: block; }
|
||
::-webkit-scrollbar { width: 8px; height: 8px; }
|
||
::-webkit-scrollbar-track { background: transparent; }
|
||
::-webkit-scrollbar-thumb { background: #c7c7cc; border-radius: 8px; }
|
||
::-webkit-scrollbar-thumb:hover { background: #a1a1a6; }
|
||
.container { max-width: var(--max-w); margin: 0 auto; padding: 0 24px; }
|
||
|
||
/* ============ Header ============ */
|
||
.site-header {
|
||
position: sticky; top: 0; z-index: 100;
|
||
height: var(--header-h);
|
||
background: rgba(255,255,255,0.82);
|
||
backdrop-filter: saturate(180%) blur(20px);
|
||
-webkit-backdrop-filter: saturate(180%) blur(20px);
|
||
border-bottom: 1px solid var(--border-soft);
|
||
}
|
||
.header-inner {
|
||
height: 100%; max-width: var(--max-w); margin: 0 auto; padding: 0 24px;
|
||
display: flex; align-items: center; gap: 32px;
|
||
}
|
||
.logo {
|
||
font-size: 19px; font-weight: 700; letter-spacing: -0.3px;
|
||
display: flex; align-items: center; gap: 8px; white-space: nowrap;
|
||
}
|
||
.logo-mark {
|
||
width: 30px; height: 30px; border-radius: 8px;
|
||
background: linear-gradient(135deg, var(--blue-light), var(--blue));
|
||
display: grid; place-items: center; color: #fff; font-size: 16px;
|
||
box-shadow: 0 2px 8px rgba(0,113,227,0.35);
|
||
}
|
||
.main-nav { display: flex; gap: 4px; }
|
||
.nav-link {
|
||
padding: 8px 14px; border-radius: 980px; font-size: 14px; font-weight: 500;
|
||
color: var(--text-secondary); transition: all .25s ease; white-space: nowrap;
|
||
}
|
||
.nav-link:hover { background: var(--bg-soft); color: var(--text); }
|
||
.nav-link.active { color: var(--blue); background: rgba(0,113,227,0.08); }
|
||
.header-search {
|
||
margin-left: auto; position: relative; flex: 0 1 280px;
|
||
}
|
||
.header-search input {
|
||
width: 100%; height: 38px; border-radius: 980px;
|
||
border: 1px solid var(--border-soft); background: var(--bg-soft);
|
||
padding: 0 16px 0 38px; font-size: 14px; color: var(--text);
|
||
transition: all .25s ease; outline: none;
|
||
}
|
||
.header-search input::placeholder { color: var(--text-tertiary); }
|
||
.header-search input:focus { background: #fff; border-color: var(--blue-light); box-shadow: 0 0 0 3px rgba(0,113,227,0.12); }
|
||
.header-search svg { position: absolute; left: 13px; top: 50%; transform: translateY(-50%); color: var(--text-tertiary); }
|
||
@media (max-width: 720px) {
|
||
.main-nav { display: none; }
|
||
.header-search { flex: 1; }
|
||
}
|
||
|
||
/* ============ Language Switcher ============ */
|
||
.lang-switcher { position: relative; flex-shrink: 0; }
|
||
.lang-btn {
|
||
display: flex; align-items: center; gap: 6px; padding: 7px 12px; border-radius: 980px;
|
||
font-size: 13px; font-weight: 500; color: var(--text-secondary);
|
||
border: 1px solid var(--border-soft); background: var(--bg-soft);
|
||
transition: all .25s ease;
|
||
}
|
||
.lang-btn:hover { background: #ededf0; color: var(--text); border-color: var(--border); }
|
||
.lang-btn svg { width: 16px; height: 16px; }
|
||
.lang-btn .chevron { width: 12px; height: 12px; }
|
||
.lang-dropdown {
|
||
position: absolute; top: calc(100% + 6px); right: 0; min-width: 150px;
|
||
background: #fff; border: 1px solid var(--border-soft); border-radius: var(--radius-md);
|
||
box-shadow: var(--shadow-lg); padding: 6px; z-index: 200;
|
||
opacity: 0; visibility: hidden; transform: translateY(-6px); transition: all .2s ease;
|
||
}
|
||
.lang-dropdown.open { opacity: 1; visibility: visible; transform: translateY(0); }
|
||
.lang-option {
|
||
display: flex; align-items: center; gap: 8px; padding: 9px 12px;
|
||
border-radius: var(--radius-sm); font-size: 14px; color: var(--text-secondary);
|
||
cursor: pointer; transition: all .15s;
|
||
}
|
||
.lang-option:hover { background: var(--bg-soft); color: var(--text); }
|
||
.lang-option.active { color: var(--blue); font-weight: 600; }
|
||
.lang-option .check { width: 16px; height: 16px; opacity: 0; transition: opacity .2s; }
|
||
.lang-option.active .check { opacity: 1; }
|
||
|
||
/* ============ Hero ============ */
|
||
.hero {
|
||
padding: 72px 0 56px; text-align: center; position: relative; overflow: hidden;
|
||
background:
|
||
radial-gradient(120% 120% at 50% -20%, rgba(41,151,255,0.10), transparent 60%),
|
||
linear-gradient(180deg, #fbfbfd 0%, #ffffff 100%);
|
||
}
|
||
.hero::after {
|
||
content: ''; position: absolute; left: 50%; bottom: -60px; transform: translateX(-50%);
|
||
width: 600px; height: 200px; border-radius: 50%;
|
||
background: radial-gradient(closest-side, rgba(0,113,227,0.07), transparent);
|
||
filter: blur(10px);
|
||
}
|
||
.hero-eyebrow {
|
||
display: inline-block; font-size: 13px; font-weight: 600; color: var(--blue);
|
||
background: rgba(0,113,227,0.08); padding: 6px 14px; border-radius: 980px; margin-bottom: 20px;
|
||
}
|
||
.hero h1 {
|
||
font-size: clamp(34px, 5vw, 52px); font-weight: 700; letter-spacing: -1px; line-height: 1.1;
|
||
background: linear-gradient(180deg, #1d1d1f 0%, #43434a 100%);
|
||
-webkit-background-clip: text; background-clip: text; -webkit-text-fill-color: transparent;
|
||
}
|
||
.hero p {
|
||
font-size: clamp(16px, 2vw, 19px); color: var(--text-secondary); margin-top: 16px; max-width: 560px; margin-left: auto; margin-right: auto;
|
||
}
|
||
.hero-cta {
|
||
margin-top: 30px; display: inline-flex; align-items: center; gap: 8px;
|
||
height: 46px; padding: 0 26px; border-radius: 980px; font-size: 15px; font-weight: 600;
|
||
background: linear-gradient(135deg, var(--blue-light), var(--blue)); color: #fff;
|
||
box-shadow: 0 8px 20px rgba(0,113,227,0.30); transition: transform .2s ease, box-shadow .2s ease;
|
||
}
|
||
.hero-cta:hover { transform: translateY(-2px); box-shadow: 0 12px 28px rgba(0,113,227,0.38); }
|
||
.hero-cta:active { transform: translateY(0); }
|
||
|
||
/* ============ Section common ============ */
|
||
.section { padding: 56px 0; }
|
||
.section-head { display: flex; align-items: baseline; justify-content: space-between; margin-bottom: 28px; gap: 16px; }
|
||
.section-title { font-size: 26px; font-weight: 700; letter-spacing: -0.5px; }
|
||
.section-sub { font-size: 14px; color: var(--text-tertiary); margin-top: 4px; }
|
||
.section-more { font-size: 14px; color: var(--blue); font-weight: 500; transition: opacity .2s; white-space: nowrap; }
|
||
.section-more:hover { opacity: 0.7; }
|
||
|
||
/* ============ Category pills ============ */
|
||
.cat-bar { display: flex; gap: 10px; overflow-x: auto; padding-bottom: 6px; margin: 0 -24px; padding-left: 24px; padding-right: 24px; -webkit-overflow-scrolling: touch; }
|
||
.cat-bar::-webkit-scrollbar { height: 0; }
|
||
.cat-pill {
|
||
flex-shrink: 0; height: 36px; padding: 0 16px; border-radius: 980px;
|
||
background: var(--bg-soft); color: var(--text-secondary); font-size: 14px; font-weight: 500;
|
||
display: inline-flex; align-items: center; gap: 6px; transition: all .25s ease; white-space: nowrap;
|
||
border: 1px solid transparent;
|
||
}
|
||
.cat-pill:hover { background: #ededf0; }
|
||
.cat-pill.active { background: var(--text); color: #fff; }
|
||
.cat-pill .cat-emoji { font-size: 15px; }
|
||
|
||
/* ============ Featured ============ */
|
||
.featured-grid { display: grid; grid-template-columns: 2fr 1fr; grid-template-rows: auto auto; gap: 20px; }
|
||
.feature-big { grid-row: span 2; min-height: 360px; border-radius: var(--radius-xl); overflow: hidden; position: relative; cursor: pointer; }
|
||
.feature-card { border-radius: var(--radius-lg); overflow: hidden; position: relative; cursor: pointer; background: var(--bg-soft); }
|
||
.feature-big, .feature-card { box-shadow: var(--shadow-sm); transition: transform .3s ease, box-shadow .3s ease; }
|
||
.feature-big:hover, .feature-card:hover { transform: translateY(-4px); box-shadow: var(--shadow-lg); }
|
||
.feature-bg { position: absolute; inset: 0; }
|
||
.feature-content { position: relative; padding: 28px; height: 100%; display: flex; flex-direction: column; justify-content: flex-end; color: #fff; }
|
||
.feature-card .feature-content { padding: 20px; }
|
||
.feature-content .ftag {
|
||
align-self: flex-start; font-size: 11px; font-weight: 700; letter-spacing: 0.5px; text-transform: uppercase;
|
||
background: rgba(255,255,255,0.25); backdrop-filter: blur(8px); padding: 4px 10px; border-radius: 980px; margin-bottom: 12px;
|
||
}
|
||
.feature-big h3 { font-size: 28px; font-weight: 700; letter-spacing: -0.5px; }
|
||
.feature-card h3 { font-size: 18px; font-weight: 700; }
|
||
.feature-content .fdesc { font-size: 14px; opacity: 0.9; margin-top: 6px; }
|
||
.feature-btn {
|
||
margin-top: 16px; align-self: flex-start; height: 34px; padding: 0 18px; border-radius: 980px;
|
||
background: rgba(255,255,255,0.95); color: var(--text); font-size: 13px; font-weight: 600;
|
||
transition: all .2s ease;
|
||
}
|
||
.feature-btn:hover { background: #fff; transform: scale(1.04); }
|
||
.feature-rating { display: flex; align-items: center; gap: 4px; font-size: 13px; margin-top: 8px; opacity: 0.95; }
|
||
.feature-rating .stars { color: #ffd33d; letter-spacing: 1px; }
|
||
@media (max-width: 820px) {
|
||
.featured-grid { grid-template-columns: 1fr; }
|
||
.feature-big { grid-row: span 1; min-height: 260px; }
|
||
}
|
||
|
||
/* ============ New apps horizontal ============ */
|
||
.h-scroll-wrap { position: relative; }
|
||
.h-scroll { display: flex; gap: 16px; overflow-x: auto; padding-bottom: 10px; scroll-snap-type: x mandatory; -webkit-overflow-scrolling: touch; }
|
||
.h-scroll::-webkit-scrollbar { height: 0; }
|
||
.new-card {
|
||
flex: 0 0 220px; scroll-snap-align: start; background: var(--bg-soft); border-radius: var(--radius-lg);
|
||
padding: 18px; cursor: pointer; transition: transform .25s ease, box-shadow .25s ease; position: relative;
|
||
}
|
||
.new-card:hover { transform: translateY(-3px); box-shadow: var(--shadow-md); }
|
||
.new-badge {
|
||
position: absolute; top: 12px; right: 12px; font-size: 10px; font-weight: 700; letter-spacing: 0.5px;
|
||
background: var(--blue); color: #fff; padding: 3px 8px; border-radius: 6px;
|
||
}
|
||
.new-card .nc-head { display: flex; gap: 12px; align-items: center; }
|
||
.new-card .nc-name { font-size: 15px; font-weight: 600; }
|
||
.new-card .nc-dev { font-size: 12px; color: var(--text-tertiary); margin-top: 2px; }
|
||
.new-card .nc-desc { font-size: 13px; color: var(--text-secondary); margin-top: 12px; line-height: 1.45; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
|
||
.new-card .nc-foot { display: flex; align-items: center; justify-content: space-between; margin-top: 14px; }
|
||
.new-card .nc-rate { font-size: 13px; color: var(--text-secondary); display: flex; align-items: center; gap: 3px; }
|
||
.new-card .nc-rate .stars { color: #ffd33d; }
|
||
|
||
/* ============ App Library ============ */
|
||
.library-controls {
|
||
display: flex; gap: 12px; align-items: center; flex-wrap: wrap; margin-bottom: 24px;
|
||
}
|
||
.lib-search { position: relative; flex: 1 1 220px; min-width: 200px; }
|
||
.lib-search input {
|
||
width: 100%; height: 42px; border-radius: var(--radius-md); border: 1px solid var(--border-soft);
|
||
background: var(--bg-soft); padding: 0 16px 0 40px; font-size: 14px; outline: none; transition: all .25s;
|
||
}
|
||
.lib-search input:focus { background: #fff; border-color: var(--blue-light); box-shadow: 0 0 0 3px rgba(0,113,227,0.12); }
|
||
.lib-search svg { position: absolute; left: 13px; top: 50%; transform: translateY(-50%); color: var(--text-tertiary); }
|
||
.sort-dropdown { position: relative; }
|
||
.sort-btn {
|
||
height: 42px; padding: 0 16px; border-radius: var(--radius-md); border: 1px solid var(--border-soft);
|
||
background: var(--bg-soft); font-size: 14px; font-weight: 500; color: var(--text); display: flex; align-items: center; gap: 6px; transition: all .25s;
|
||
}
|
||
.sort-btn:hover { background: #ededf0; }
|
||
.sort-menu {
|
||
position: absolute; top: calc(100% + 6px); right: 0; min-width: 160px; background: #fff;
|
||
border: 1px solid var(--border-soft); border-radius: var(--radius-md); box-shadow: var(--shadow-lg);
|
||
padding: 6px; opacity: 0; visibility: hidden; transform: translateY(-6px); transition: all .2s ease; z-index: 20;
|
||
}
|
||
.sort-menu.open { opacity: 1; visibility: visible; transform: translateY(0); }
|
||
.sort-item { padding: 9px 12px; border-radius: var(--radius-sm); font-size: 14px; color: var(--text-secondary); transition: all .15s; display: flex; align-items: center; justify-content: space-between; }
|
||
.sort-item:hover { background: var(--bg-soft); color: var(--text); }
|
||
.sort-item.active { color: var(--blue); font-weight: 600; }
|
||
.sort-item .check { opacity: 0; }
|
||
.sort-item.active .check { opacity: 1; }
|
||
|
||
.app-grid { display: grid; grid-template-columns: repeat(6, 1fr); gap: 16px; }
|
||
@media (max-width: 1000px) { .app-grid { grid-template-columns: repeat(4, 1fr); } }
|
||
@media (max-width: 640px) { .app-grid { grid-template-columns: repeat(2, 1fr); } }
|
||
|
||
.app-card {
|
||
background: var(--bg-soft); border-radius: var(--radius-lg); padding: 16px; cursor: pointer;
|
||
transition: transform .25s ease, box-shadow .25s ease; display: flex; flex-direction: column;
|
||
}
|
||
.app-card:hover { transform: translateY(-3px); box-shadow: var(--shadow-md); }
|
||
.app-card .ac-head { display: flex; gap: 12px; align-items: center; }
|
||
.app-card .ac-info { flex: 1; min-width: 0; }
|
||
.app-card .ac-name { font-size: 14px; font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
||
.app-card .ac-cat { font-size: 12px; color: var(--text-tertiary); margin-top: 2px; }
|
||
.app-card .ac-desc { font-size: 12px; color: var(--text-secondary); margin-top: 12px; line-height: 1.45; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; flex: 1; }
|
||
.app-card .ac-foot { display: flex; align-items: center; justify-content: space-between; margin-top: 14px; }
|
||
.app-card .ac-rate { font-size: 12px; color: var(--text-secondary); display: flex; align-items: center; gap: 3px; }
|
||
.app-card .ac-rate .stars { color: #ffd33d; font-size: 11px; }
|
||
.app-card .ac-dl {
|
||
height: 28px; padding: 0 14px; border-radius: 980px; background: #fff; color: var(--blue);
|
||
font-size: 12px; font-weight: 600; border: 1px solid rgba(0,113,227,0.2); transition: all .2s ease;
|
||
}
|
||
.app-card .ac-dl:hover { background: var(--blue); color: #fff; border-color: var(--blue); }
|
||
|
||
/* App icon (CSS gradient) */
|
||
.app-icon {
|
||
width: 56px; height: 56px; border-radius: 14px; flex-shrink: 0; position: relative; overflow: hidden;
|
||
display: grid; place-items: center; color: #fff; font-size: 22px; font-weight: 700;
|
||
box-shadow: 0 2px 6px rgba(0,0,0,0.12);
|
||
}
|
||
.app-icon::after { content: ''; position: absolute; inset: 0; background: linear-gradient(160deg, rgba(255,255,255,0.25), transparent 50%); }
|
||
.app-icon.sm { width: 44px; height: 44px; border-radius: 11px; font-size: 17px; }
|
||
.app-icon.xs { width: 36px; height: 36px; border-radius: 9px; font-size: 14px; }
|
||
.app-icon.lg { width: 88px; height: 88px; border-radius: 20px; font-size: 34px; }
|
||
|
||
/* ============ Pagination ============ */
|
||
.pagination { display: flex; justify-content: center; align-items: center; gap: 6px; margin-top: 36px; flex-wrap: wrap; }
|
||
.page-btn {
|
||
min-width: 38px; height: 38px; padding: 0 10px; border-radius: var(--radius-sm); font-size: 14px;
|
||
color: var(--text-secondary); background: var(--bg-soft); transition: all .2s; display: grid; place-items: center;
|
||
}
|
||
.page-btn:hover:not(:disabled) { background: #ededf0; color: var(--text); }
|
||
.page-btn.active { background: var(--blue); color: #fff; font-weight: 600; }
|
||
.page-btn:disabled { opacity: 0.4; cursor: not-allowed; }
|
||
.page-ellipsis { padding: 0 4px; color: var(--text-tertiary); }
|
||
|
||
/* ============ Modal ============ */
|
||
.modal-backdrop {
|
||
position: fixed; inset: 0; z-index: 1000; background: rgba(0,0,0,0.4);
|
||
backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px);
|
||
opacity: 0; visibility: hidden; transition: opacity .3s ease, visibility .3s ease;
|
||
display: grid; place-items: center; padding: 24px; overflow-y: auto;
|
||
}
|
||
.modal-backdrop.open { opacity: 1; visibility: visible; }
|
||
.modal {
|
||
background: #fff; border-radius: var(--radius-xl); max-width: 680px; width: 100%; margin: auto;
|
||
max-height: 90vh; overflow-y: auto; box-shadow: var(--shadow-lg);
|
||
transform: scale(0.95) translateY(10px); opacity: 0; transition: transform .35s cubic-bezier(0.22,1,0.36,1), opacity .35s ease;
|
||
}
|
||
.modal-backdrop.open .modal { transform: scale(1) translateY(0); opacity: 1; }
|
||
.modal-close {
|
||
position: sticky; top: 0; float: right; margin: 14px 14px 0 0; z-index: 5;
|
||
width: 32px; height: 32px; border-radius: 50%; background: rgba(0,0,0,0.05); color: var(--text-secondary);
|
||
display: grid; place-items: center; font-size: 18px; transition: all .2s;
|
||
}
|
||
.modal-close:hover { background: rgba(0,0,0,0.1); transform: rotate(90deg); }
|
||
.modal-body { padding: 0 32px 36px; }
|
||
.modal-hero { display: flex; gap: 20px; align-items: center; padding-top: 24px; }
|
||
.modal-hero .mh-info { flex: 1; min-width: 0; }
|
||
.modal-hero h2 { font-size: 24px; font-weight: 700; letter-spacing: -0.3px; }
|
||
.modal-hero .mh-dev { font-size: 14px; color: var(--text-secondary); margin-top: 4px; }
|
||
.modal-hero .mh-meta { display: flex; align-items: center; gap: 14px; margin-top: 10px; flex-wrap: wrap; }
|
||
.modal-hero .mh-rating { display: flex; align-items: center; gap: 4px; font-size: 14px; }
|
||
.modal-hero .mh-rating .stars { color: #ffd33d; letter-spacing: 1px; }
|
||
.modal-hero .mh-rating .rnum { font-weight: 600; }
|
||
.modal-hero .mh-rating .rcount { color: var(--text-tertiary); font-size: 13px; }
|
||
.modal-hero .mh-price { font-size: 13px; color: var(--text-secondary); }
|
||
.modal-dl-btn {
|
||
flex-shrink: 0; height: 40px; padding: 0 24px; border-radius: 980px; font-size: 15px; font-weight: 600;
|
||
background: linear-gradient(135deg, var(--blue-light), var(--blue)); color: #fff; transition: transform .2s;
|
||
}
|
||
.modal-dl-btn:hover { transform: scale(1.04); }
|
||
.modal-stats { display: grid; grid-template-columns: repeat(3,1fr); gap: 12px; margin-top: 24px; padding: 18px 0; border-top: 1px solid var(--border-soft); border-bottom: 1px solid var(--border-soft); }
|
||
.modal-stat { text-align: center; }
|
||
.modal-stat .ms-val { font-size: 18px; font-weight: 700; }
|
||
.modal-stat .ms-label { font-size: 12px; color: var(--text-tertiary); margin-top: 2px; }
|
||
.modal-section { margin-top: 26px; }
|
||
.modal-section h4 { font-size: 16px; font-weight: 700; margin-bottom: 10px; }
|
||
.modal-section p { font-size: 14px; color: var(--text-secondary); line-height: 1.6; }
|
||
.tag-row { display: flex; gap: 8px; flex-wrap: wrap; }
|
||
.tag-pill { font-size: 12px; font-weight: 500; color: var(--blue); background: rgba(0,113,227,0.08); padding: 5px 12px; border-radius: 980px; }
|
||
.feature-list { list-style: none; display: flex; flex-direction: column; gap: 10px; }
|
||
.feature-list li { font-size: 14px; color: var(--text-secondary); display: flex; align-items: flex-start; gap: 8px; }
|
||
.feature-list li::before { content: '\2713'; color: var(--blue); font-weight: 700; flex-shrink: 0; }
|
||
.shots-row { display: flex; gap: 12px; overflow-x: auto; padding-bottom: 6px; }
|
||
.shots-row::-webkit-scrollbar { height: 0; }
|
||
.shot-item { flex: 0 0 200px; height: 360px; border-radius: var(--radius-md); overflow: hidden; position: relative; }
|
||
.shot-item .shot-label { position: absolute; bottom: 12px; left: 12px; color: #fff; font-size: 12px; font-weight: 600; text-shadow: 0 1px 4px rgba(0,0,0,0.4); }
|
||
.modal-version { font-size: 13px; color: var(--text-tertiary); margin-top: 22px; padding-top: 18px; border-top: 1px solid var(--border-soft); display: flex; gap: 24px; flex-wrap: wrap; }
|
||
.modal-version span b { color: var(--text-secondary); font-weight: 500; }
|
||
@media (max-width: 560px) {
|
||
.modal-body { padding: 0 20px 28px; }
|
||
.modal-hero { flex-direction: column; text-align: center; }
|
||
.modal-stats { grid-template-columns: repeat(3,1fr); }
|
||
}
|
||
|
||
/* ============ Skeleton ============ */
|
||
.skeleton { background: #ececee; border-radius: var(--radius-sm); position: relative; overflow: hidden; }
|
||
.skeleton::after {
|
||
content: ''; position: absolute; inset: 0;
|
||
background: linear-gradient(90deg, transparent, rgba(255,255,255,0.65), transparent);
|
||
transform: translateX(-100%); animation: shimmer 1.4s infinite;
|
||
}
|
||
@keyframes shimmer { 100% { transform: translateX(100%); } }
|
||
.sk-card { background: var(--bg-soft); border-radius: var(--radius-lg); padding: 16px; }
|
||
.sk-line { height: 12px; border-radius: 6px; }
|
||
.sk-line.w70 { width: 70%; } .sk-line.w50 { width: 50%; } .sk-line.w90 { width: 90%; }
|
||
|
||
/* ============ Fade-in animation ============ */
|
||
.fade-in { opacity: 0; transform: translateY(24px); transition: opacity .6s ease, transform .6s ease; }
|
||
.fade-in.visible { opacity: 1; transform: translateY(0); }
|
||
|
||
/* ============ Footer ============ */
|
||
.site-footer { background: var(--bg-soft); border-top: 1px solid var(--border-soft); padding: 48px 0 32px; margin-top: 40px; }
|
||
.footer-inner { max-width: var(--max-w); margin: 0 auto; padding: 0 24px; }
|
||
.footer-top { display: flex; justify-content: space-between; gap: 32px; flex-wrap: wrap; }
|
||
.footer-brand { max-width: 280px; }
|
||
.footer-brand .logo { margin-bottom: 12px; }
|
||
.footer-brand p { font-size: 13px; color: var(--text-tertiary); line-height: 1.6; }
|
||
.footer-cols { display: flex; gap: 56px; flex-wrap: wrap; }
|
||
.footer-col h5 { font-size: 13px; font-weight: 600; margin-bottom: 12px; }
|
||
.footer-col a { display: block; font-size: 13px; color: var(--text-tertiary); padding: 4px 0; transition: color .2s; }
|
||
.footer-col a:hover { color: var(--blue); }
|
||
.footer-bottom { margin-top: 36px; padding-top: 20px; border-top: 1px solid var(--border-soft); display: flex; justify-content: space-between; gap: 12px; flex-wrap: wrap; font-size: 12px; color: var(--text-tertiary); }
|
||
@media (max-width: 640px) { .footer-cols { gap: 32px; } }
|
||
|
||
/* ============ Toast ============ */
|
||
.toast { position: fixed; bottom: 32px; left: 50%; transform: translateX(-50%) translateY(20px); z-index: 2000; background: rgba(29,29,31,0.92); color: #fff; padding: 12px 22px; border-radius: var(--radius-md); font-size: 14px; opacity: 0; visibility: hidden; transition: all .3s ease; box-shadow: var(--shadow-lg); }
|
||
.toast.show { opacity: 1; visibility: visible; transform: translateX(-50%) translateY(0); }
|
||
|
||
/* ============ Empty ============ */
|
||
.empty-state { text-align: center; padding: 60px 20px; color: var(--text-tertiary); }
|
||
.empty-state svg { margin-bottom: 16px; opacity: 0.5; }
|
||
.empty-state p { font-size: 15px; }
|
||
|
||
/* ============ Misc ============ */
|
||
.hidden { display: none !important; }
|
||
</style>
|
||
</head>
|
||
<body>
|
||
|
||
<!-- ======================= HEADER ======================= -->
|
||
<header class="site-header">
|
||
<div class="header-inner">
|
||
<a href="#" class="logo" onclick="goHome(event)">
|
||
<span class="logo-mark">✚</span>
|
||
<span data-i18n="logo_name">应用商店</span>
|
||
</a>
|
||
<nav class="main-nav" id="mainNav">
|
||
<a href="#" class="nav-link active" data-nav="home" onclick="navTo(event,'home')" data-i18n="nav_home">首页</a>
|
||
<a href="#" class="nav-link" data-nav="discover" onclick="navTo(event,'discover')" data-i18n="nav_discover">发现</a>
|
||
<a href="#" class="nav-link" data-nav="ranking" onclick="navTo(event,'ranking')" data-i18n="nav_ranking">排行榜</a>
|
||
<a href="#" class="nav-link" data-nav="new" onclick="navTo(event,'new')" data-i18n="nav_new">新上架</a>
|
||
</nav>
|
||
<div class="header-search">
|
||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><circle cx="11" cy="11" r="7"/><path d="M21 21l-4.3-4.3"/></svg>
|
||
<input type="text" id="headerSearch" data-i18n-placeholder="search_all" placeholder="搜索应用、游戏..." autocomplete="off">
|
||
</div>
|
||
<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>
|
||
<svg class="chevron" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round"><path d="M6 9l6 6 6-6"/></svg>
|
||
</button>
|
||
<div class="lang-dropdown" id="langDropdown">
|
||
<div class="lang-option" data-lang="zh-CN" onclick="switchLang('zh-CN')">
|
||
<svg class="check" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg>
|
||
<span>简体中文</span>
|
||
</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="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg>
|
||
<span>繁體中文</span>
|
||
</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="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg>
|
||
<span>English</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</header>
|
||
|
||
<!-- ======================= HERO ======================= -->
|
||
<section class="hero">
|
||
<div class="container">
|
||
<span class="hero-eyebrow" data-i18n="hero_eyebrow">全新体验 · 精选推荐</span>
|
||
<h1 data-i18n="hero_title">发现精彩应用</h1>
|
||
<p data-i18n="hero_desc">探索海量优质应用与游戏,从效率工具到娱乐大作,总有一款适合你。</p>
|
||
<a href="#library" class="hero-cta" onclick="exploreNow(event)">
|
||
<span data-i18n="hero_cta">立即探索</span>
|
||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round"><path d="M5 12h14M13 6l6 6-6 6"/></svg>
|
||
</a>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- ======================= CATEGORY FILTER ======================= -->
|
||
<section class="section" style="padding-top:24px; padding-bottom:0;">
|
||
<div class="container">
|
||
<div class="cat-bar" id="catBar">
|
||
<button class="cat-pill skeleton" style="width:80px;height:36px;"></button>
|
||
<button class="cat-pill skeleton" style="width:90px;height:36px;"></button>
|
||
<button class="cat-pill skeleton" style="width:70px;height:36px;"></button>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- ======================= FEATURED ======================= -->
|
||
<section class="section" id="featuredSection">
|
||
<div class="container">
|
||
<div class="section-head">
|
||
<div>
|
||
<h2 class="section-title" data-i18n="featured_title">精选推荐</h2>
|
||
<p class="section-sub" data-i18n="featured_sub">编辑团队为你精心挑选</p>
|
||
</div>
|
||
<a href="#" class="section-more" onclick="seeAllFeatured(event)" data-i18n="see_all">查看全部 ›</a>
|
||
</div>
|
||
<div class="featured-grid" id="featuredGrid">
|
||
<div class="feature-big skeleton"></div>
|
||
<div class="feature-card skeleton" style="min-height:170px;"></div>
|
||
<div class="feature-card skeleton" style="min-height:170px;"></div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- ======================= NEW APPS ======================= -->
|
||
<section class="section" id="newSection">
|
||
<div class="container">
|
||
<div class="section-head">
|
||
<div>
|
||
<h2 class="section-title" data-i18n="new_title">新上架</h2>
|
||
<p class="section-sub" data-i18n="new_sub">最新加入的应用</p>
|
||
</div>
|
||
<a href="#" class="section-more" onclick="seeAllNew(event)" data-i18n="see_all">查看全部 ›</a>
|
||
</div>
|
||
<div class="h-scroll-wrap">
|
||
<div class="h-scroll" id="newScroll">
|
||
<div class="new-card skeleton" style="height:200px;"></div>
|
||
<div class="new-card skeleton" style="height:200px;"></div>
|
||
<div class="new-card skeleton" style="height:200px;"></div>
|
||
<div class="new-card skeleton" style="height:200px;"></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- ======================= APP LIBRARY ======================= -->
|
||
<section class="section" id="library">
|
||
<div class="container">
|
||
<div class="section-head">
|
||
<div>
|
||
<h2 class="section-title" data-i18n="library_title">全部应用</h2>
|
||
<p class="section-sub" data-i18n="library_sub">浏览所有应用</p>
|
||
</div>
|
||
</div>
|
||
<div class="library-controls">
|
||
<div class="lib-search">
|
||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><circle cx="11" cy="11" r="7"/><path d="M21 21l-4.3-4.3"/></svg>
|
||
<input type="text" id="libSearch" data-i18n-placeholder="search_name" placeholder="搜索应用名称..." autocomplete="off">
|
||
</div>
|
||
<div class="sort-dropdown">
|
||
<button class="sort-btn" id="sortBtn" onclick="toggleSort(event)">
|
||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M3 6h18M6 12h12M10 18h4"/></svg>
|
||
<span id="sortLabel" data-i18n="sort_default">默认排序</span>
|
||
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round"><path d="M6 9l6 6 6-6"/></svg>
|
||
</button>
|
||
<div class="sort-menu" id="sortMenu">
|
||
<div class="sort-item active" data-sort="" onclick="setSort('')"><span data-i18n="sort_default">默认排序</span> <span class="check">✓</span></div>
|
||
<div class="sort-item" data-sort="rating" onclick="setSort('rating')"><span data-i18n="sort_rating">评分最高</span> <span class="check">✓</span></div>
|
||
<div class="sort-item" data-sort="downloads" onclick="setSort('downloads')"><span data-i18n="sort_downloads">下载最多</span> <span class="check">✓</span></div>
|
||
<div class="sort-item" data-sort="newest" onclick="setSort('newest')"><span data-i18n="sort_newest">最新发布</span> <span class="check">✓</span></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="app-grid" id="appGrid"></div>
|
||
<div class="pagination" id="pagination"></div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- ======================= FOOTER ======================= -->
|
||
<footer class="site-footer">
|
||
<div class="footer-inner">
|
||
<div class="footer-top">
|
||
<div class="footer-brand">
|
||
<div class="logo"><span class="logo-mark">✚</span><span data-i18n="logo_name">应用商店</span></div>
|
||
<p data-i18n="footer_desc">发现、下载并体验海量优质应用与游戏。我们致力于为用户提供安全、丰富的应用生态。</p>
|
||
</div>
|
||
<div class="footer-cols">
|
||
<div class="footer-col">
|
||
<h5 data-i18n="footer_explore">探索</h5>
|
||
<a href="#" onclick="navTo(event,'home')" data-i18n="nav_home">首页</a>
|
||
<a href="#" onclick="navTo(event,'discover')" data-i18n="nav_discover">发现</a>
|
||
<a href="#" onclick="navTo(event,'ranking')" data-i18n="nav_ranking">排行榜</a>
|
||
<a href="#" onclick="navTo(event,'new')" data-i18n="nav_new">新上架</a>
|
||
</div>
|
||
<div class="footer-col">
|
||
<h5 data-i18n="footer_about">关于</h5>
|
||
<a href="#" data-i18n="footer_about_us">关于我们</a>
|
||
<a href="#" data-i18n="footer_privacy">隐私政策</a>
|
||
<a href="#" data-i18n="footer_terms">服务条款</a>
|
||
<a href="#" data-i18n="footer_contact">联系方式</a>
|
||
</div>
|
||
<div class="footer-col">
|
||
<h5 data-i18n="footer_dev">开发者</h5>
|
||
<a href="#" data-i18n="footer_dev_center">开发者中心</a>
|
||
<a href="#" data-i18n="footer_listing_guide">上架指南</a>
|
||
<a href="#" data-i18n="footer_api_docs">API 文档</a>
|
||
<a href="#" data-i18n="footer_cooperation">合作申请</a>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="footer-bottom">
|
||
<span data-i18n="footer_copyright">© 2026 应用商店. 保留所有权利.</span>
|
||
<span data-i18n="footer_disclaimer">仅供学习展示用途</span>
|
||
</div>
|
||
</div>
|
||
</footer>
|
||
|
||
<!-- ======================= MODAL ======================= -->
|
||
<div class="modal-backdrop" id="modalBackdrop" onclick="closeModalOnBackdrop(event)">
|
||
<div class="modal" id="appModal">
|
||
<button class="modal-close" onclick="closeModal()" aria-label="关闭" data-i18n-aria="modal_close">×</button>
|
||
<div class="modal-body" id="modalBody"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ======================= TOAST ======================= -->
|
||
<div class="toast" id="toast"></div>
|
||
|
||
<script>
|
||
"use strict";
|
||
/* =========================================================================
|
||
i18n 国际化引擎
|
||
========================================================================= */
|
||
var I18N_LOCALES = {
|
||
'zh-CN': {
|
||
logo_name: '应用商店',
|
||
nav_home: '首页',
|
||
nav_discover: '发现',
|
||
nav_ranking: '排行榜',
|
||
nav_new: '新上架',
|
||
search_all: '搜索应用、游戏...',
|
||
search_name: '搜索应用名称...',
|
||
hero_eyebrow: '全新体验 · 精选推荐',
|
||
hero_title: '发现精彩应用',
|
||
hero_desc: '探索海量优质应用与游戏,从效率工具到娱乐大作,总有一款适合你。',
|
||
hero_cta: '立即探索',
|
||
cat_all: '全部',
|
||
featured_title: '精选推荐',
|
||
featured_sub: '编辑团队为你精心挑选',
|
||
featured_tag: '编辑精选',
|
||
featured_recommended: '推荐',
|
||
see_all: '查看全部',
|
||
view_detail: '查看详情',
|
||
new_title: '新上架',
|
||
new_sub: '最新加入的应用',
|
||
library_title: '全部应用',
|
||
library_sub: '浏览所有应用',
|
||
sort_default: '默认排序',
|
||
sort_rating: '评分最高',
|
||
sort_downloads: '下载最多',
|
||
sort_newest: '最新发布',
|
||
download: '下载',
|
||
downloads_label: '下载量',
|
||
reviews_label: '评分数',
|
||
size_label: '大小',
|
||
rating_count: '评分',
|
||
app_intro: '应用介绍',
|
||
app_tags: '标签',
|
||
app_features: '功能特性',
|
||
app_screenshots: '应用截图',
|
||
version_label: '版本:',
|
||
update_label: '更新日期:',
|
||
platform_label: '平台:',
|
||
category_label: '分类:',
|
||
empty_featured: '暂无精选应用',
|
||
empty_new: '暂无新应用',
|
||
empty_apps: '没有找到匹配的应用',
|
||
empty_app_detail: '应用信息不存在',
|
||
modal_close: '关闭',
|
||
toast_downloading: '开始下载「',
|
||
toast_downloading_end: '」',
|
||
footer_desc: '发现、下载并体验海量优质应用与游戏。我们致力于为用户提供安全、丰富的应用生态。',
|
||
footer_explore: '探索',
|
||
footer_about: '关于',
|
||
footer_about_us: '关于我们',
|
||
footer_privacy: '隐私政策',
|
||
footer_terms: '服务条款',
|
||
footer_contact: '联系方式',
|
||
footer_dev: '开发者',
|
||
footer_dev_center: '开发者中心',
|
||
footer_listing_guide: '上架指南',
|
||
footer_api_docs: 'API 文档',
|
||
footer_cooperation: '合作申请',
|
||
footer_copyright: '\u00a9 2026 应用商店. 保留所有权利.',
|
||
footer_disclaimer: '仅供学习展示用途'
|
||
},
|
||
'zh-TW': {
|
||
logo_name: '應用商店',
|
||
nav_home: '首頁',
|
||
nav_discover: '發現',
|
||
nav_ranking: '排行榜',
|
||
nav_new: '新上架',
|
||
search_all: '搜尋應用、遊戲...',
|
||
search_name: '搜尋應用名稱...',
|
||
hero_eyebrow: '全新體驗 · 精選推薦',
|
||
hero_title: '發現精彩應用',
|
||
hero_desc: '探索海量優質應用與遊戲,從效率工具到娛樂大作,總有一款適合你。',
|
||
hero_cta: '立即探索',
|
||
cat_all: '全部',
|
||
featured_title: '精選推薦',
|
||
featured_sub: '編輯團隊為你精心挑選',
|
||
featured_tag: '編輯精選',
|
||
featured_recommended: '推薦',
|
||
see_all: '查看全部',
|
||
view_detail: '查看详情',
|
||
new_title: '新上架',
|
||
new_sub: '最新加入的應用',
|
||
library_title: '全部應用',
|
||
library_sub: '瀏覽所有應用',
|
||
sort_default: '預設排序',
|
||
sort_rating: '評分最高',
|
||
sort_downloads: '下載最多',
|
||
sort_newest: '最新發佈',
|
||
download: '下載',
|
||
downloads_label: '下載量',
|
||
reviews_label: '評分數',
|
||
size_label: '大小',
|
||
rating_count: '評分',
|
||
app_intro: '應用介紹',
|
||
app_tags: '標籤',
|
||
app_features: '功能特性',
|
||
app_screenshots: '應用截圖',
|
||
version_label: '版本:',
|
||
update_label: '更新日期:',
|
||
platform_label: '平台:',
|
||
category_label: '分類:',
|
||
empty_featured: '暫無精選應用',
|
||
empty_new: '暫無新應用',
|
||
empty_apps: '沒有找到匹配的應用',
|
||
empty_app_detail: '應用資訊不存在',
|
||
modal_close: '關閉',
|
||
toast_downloading: '開始下載「',
|
||
toast_downloading_end: '」',
|
||
footer_desc: '發現、下載並體驗海量優質應用與遊戲。我們致力於為使用者提供安全、豐富的應用生態。',
|
||
footer_explore: '探索',
|
||
footer_about: '關於',
|
||
footer_about_us: '關於我們',
|
||
footer_privacy: '隱私政策',
|
||
footer_terms: '服務條款',
|
||
footer聯絡: '聯絡方式',
|
||
footer_dev: '開發者',
|
||
footer_dev_center: '開發者中心',
|
||
footer_listing_guide: '上架指南',
|
||
footer_api_docs: 'API 文件',
|
||
footer_cooperation: '合作申請',
|
||
footer_copyright: '\u00a9 2026 應用商店. 保留所有權利.',
|
||
footer_disclaimer: '僅供學習展示用途'
|
||
},
|
||
'en': {
|
||
logo_name: 'App Store',
|
||
nav_home: 'Home',
|
||
nav_discover: 'Discover',
|
||
nav_ranking: 'Rankings',
|
||
nav_new: 'New',
|
||
search_all: 'Search apps, games...',
|
||
search_name: 'Search app name...',
|
||
hero_eyebrow: 'All-New Experience · Curated Picks',
|
||
hero_title: 'Discover Amazing Apps',
|
||
hero_desc: 'Explore a vast collection of quality apps and games, from productivity tools to entertainment hits — there\'s something for everyone.',
|
||
hero_cta: 'Explore Now',
|
||
cat_all: 'All',
|
||
featured_title: 'Featured',
|
||
featured_sub: 'Handpicked by our editorial team',
|
||
featured_tag: 'Editor\'s Choice',
|
||
featured_recommended: 'Recommended',
|
||
see_all: 'See All',
|
||
view_detail: 'View Details',
|
||
new_title: 'New Arrivals',
|
||
new_sub: 'Recently added apps',
|
||
library_title: 'All Apps',
|
||
library_sub: 'Browse all apps',
|
||
sort_default: 'Default',
|
||
sort_rating: 'Top Rated',
|
||
sort_downloads: 'Most Downloaded',
|
||
sort_newest: 'Newest',
|
||
download: 'Get',
|
||
downloads_label: 'Downloads',
|
||
reviews_label: 'Ratings',
|
||
size_label: 'Size',
|
||
rating_count: 'ratings',
|
||
app_intro: 'App Description',
|
||
app_tags: 'Tags',
|
||
app_features: 'Features',
|
||
app_screenshots: 'Screenshots',
|
||
version_label: 'Version: ',
|
||
update_label: 'Updated: ',
|
||
platform_label: 'Platform: ',
|
||
category_label: 'Category: ',
|
||
empty_featured: 'No featured apps',
|
||
empty_new: 'No new apps',
|
||
empty_apps: 'No matching apps found',
|
||
empty_app_detail: 'App information not available',
|
||
modal_close: 'Close',
|
||
toast_downloading: 'Downloading "',
|
||
toast_downloading_end: '"',
|
||
footer_desc: 'Discover, download, and enjoy a vast collection of quality apps and games. We are dedicated to providing a safe and rich app ecosystem.',
|
||
footer_explore: 'Explore',
|
||
footer_about: 'About',
|
||
footer_about_us: 'About Us',
|
||
footer_privacy: 'Privacy Policy',
|
||
footer_terms: 'Terms of Service',
|
||
footer_contact: 'Contact',
|
||
footer_dev: 'Developers',
|
||
footer_dev_center: 'Developer Center',
|
||
footer_listing_guide: 'Listing Guide',
|
||
footer_api_docs: 'API Docs',
|
||
footer_cooperation: 'Partnerships',
|
||
footer_copyright: '\u00a9 2026 App Store. All rights reserved.',
|
||
footer_disclaimer: 'For demonstration purposes only'
|
||
}
|
||
};
|
||
|
||
var LANG_LABELS = {'zh-CN': '中文', 'zh-TW': '繁中', 'en': 'EN'};
|
||
|
||
var 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('appstore_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.querySelectorAll('[data-i18n-aria]').forEach(function(el) {
|
||
var key = el.getAttribute('data-i18n-aria');
|
||
el.setAttribute('aria-label', t(key));
|
||
});
|
||
document.documentElement.lang = currentLang === 'zh-TW' ? 'zh-TW' : currentLang === 'zh-CN' ? 'zh-CN' : 'en';
|
||
document.getElementById('langLabel').textContent = LANG_LABELS[currentLang] || currentLang;
|
||
document.querySelectorAll('.lang-option').forEach(function(opt) {
|
||
opt.classList.toggle('active', opt.getAttribute('data-lang') === currentLang);
|
||
});
|
||
}
|
||
|
||
function switchLang(lang) {
|
||
if (!I18N_LOCALES[lang]) return;
|
||
currentLang = lang;
|
||
try { localStorage.setItem('appstore_lang', lang); } catch(e) {}
|
||
applyI18n();
|
||
if (typeof onLangChange === 'function') onLangChange(lang);
|
||
document.getElementById('langDropdown').classList.remove('open');
|
||
}
|
||
|
||
function toggleLangDropdown() {
|
||
document.getElementById('langDropdown').classList.toggle('open');
|
||
}
|
||
document.addEventListener('click', function(e) {
|
||
var sw = document.getElementById('langSwitcher');
|
||
if (sw && !sw.contains(e.target)) document.getElementById('langDropdown').classList.remove('open');
|
||
});
|
||
|
||
/* =========================================================================
|
||
配置 & 工具函数
|
||
========================================================================= */
|
||
var API_BASE = '/api/v1/app';
|
||
var PAGE_SIZE = 12;
|
||
|
||
/* 状态 */
|
||
var state = {
|
||
categories: [],
|
||
currentCat: '',
|
||
sort: '',
|
||
keyword: '',
|
||
page: 1,
|
||
total: 0,
|
||
cache: {}
|
||
};
|
||
|
||
/* ---- 工具:格式化大数字 ---- */
|
||
function formatNum(n) {
|
||
if (n == null || isNaN(n)) return '0';
|
||
n = Number(n);
|
||
if (n >= 100000000) return (n / 100000000).toFixed(1).replace(/\.0$/, '') + '亿';
|
||
if (n >= 10000) return (n / 10000).toFixed(1).replace(/\.0$/, '') + '万';
|
||
return n.toString();
|
||
}
|
||
|
||
function formatNumI18n(n) {
|
||
if (n == null || isNaN(n)) return '0';
|
||
n = Number(n);
|
||
if (currentLang === 'en') {
|
||
if (n >= 1000000000) return (n / 1000000000).toFixed(1).replace(/\.0$/, '') + 'B';
|
||
if (n >= 1000000) return (n / 1000000).toFixed(1).replace(/\.0$/, '') + 'M';
|
||
if (n >= 1000) return (n / 1000).toFixed(1).replace(/\.0$/, '') + 'K';
|
||
return n.toString();
|
||
}
|
||
if (n >= 100000000) return (n / 100000000).toFixed(1).replace(/\.0$/, '') + '亿';
|
||
if (n >= 10000) return (n / 10000).toFixed(1).replace(/\.0$/, '') + '万';
|
||
return n.toString();
|
||
}
|
||
|
||
/* ---- 工具:星级 HTML ---- */
|
||
function starsHtml(rating) {
|
||
rating = parseFloat(rating) || 0;
|
||
var html = '';
|
||
for (var k = 0; k < 5; k++) {
|
||
var fill = Math.max(0, Math.min(1, rating - k));
|
||
html += '<span style="display:inline-block;position:relative;font-size:inherit;">'
|
||
+ '<span style="color:#d8d8df;">\u2605</span>'
|
||
+ '<span style="position:absolute;left:0;top:0;overflow:hidden;width:' + (fill * 100) + '%;color:#ffd33d;">\u2605</span>'
|
||
+ '</span>';
|
||
}
|
||
return html;
|
||
}
|
||
|
||
/* ---- 工具:安全解析 JSON ---- */
|
||
function parseJSON(str, fallback) {
|
||
if (Array.isArray(str)) return str;
|
||
if (typeof str !== 'string') return fallback || [];
|
||
try { var v = JSON.parse(str); return v; }
|
||
catch (e) { return fallback || []; }
|
||
}
|
||
|
||
/* ---- 工具:用应用名 hash 生成稳定的渐变色 ---- */
|
||
function hashStr(s) {
|
||
s = s || 'A';
|
||
var h = 0;
|
||
for (var i = 0; i < s.length; i++) { h = ((h << 5) - h) + s.charCodeAt(i); h |= 0; }
|
||
return Math.abs(h);
|
||
}
|
||
function gradFromName(name) {
|
||
var h = hashStr(name);
|
||
var hue1 = h % 360;
|
||
var hue2 = (hue1 + 40 + (h % 80)) % 360;
|
||
var sat = 70 + (h % 20);
|
||
return 'linear-gradient(135deg, hsl(' + hue1 + ',' + sat + '%,60%), hsl(' + hue2 + ',' + (sat + 5) + '%,50%))';
|
||
}
|
||
function initials(name) {
|
||
name = name || '?';
|
||
return name.charAt(0);
|
||
}
|
||
|
||
/* ---- 工具:escape HTML ---- */
|
||
function esc(s) {
|
||
if (s == null) return '';
|
||
return String(s).replace(/[&<>"']/g, function (c) {
|
||
return { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[c];
|
||
});
|
||
}
|
||
|
||
/* ---- 工具:判断 icon 字段是否为图片 URL ---- */
|
||
function isIconURL(icon) {
|
||
if (!icon) return false;
|
||
var v = String(icon).trim();
|
||
return v.indexOf('/uploads/') === 0 || v.indexOf('http://') === 0 || v.indexOf('https://') === 0;
|
||
}
|
||
|
||
/* ---- 工具:生成应用图标 HTML ---- */
|
||
function appIconHTML(app, cls) {
|
||
var grad = gradFromName(app.name);
|
||
var clsAttr = 'app-icon' + (cls ? ' ' + cls : '');
|
||
var iconURL = isIconURL(app.icon) ? String(app.icon).trim() : '';
|
||
if (iconURL) {
|
||
return '<div class="' + clsAttr + '" style="background:' + grad + ';">'
|
||
+ '<img src="' + esc(iconURL) + '" alt="' + esc(app.name) + '" style="position:absolute;inset:0;width:100%;height:100%;object-fit:cover" onerror="this.style.display=\'none\';this.nextElementSibling.style.display=\'block\'">'
|
||
+ '<span style="display:none">' + esc(initials(app.name)) + '</span>'
|
||
+ '</div>';
|
||
}
|
||
return '<div class="' + clsAttr + '" style="background:' + grad + ';">' + esc(initials(app.name)) + '</div>';
|
||
}
|
||
|
||
/* ---- 工具:debounce ---- */
|
||
function debounce(fn, wait) {
|
||
var t;
|
||
return function () {
|
||
var ctx = this, args = arguments;
|
||
clearTimeout(t);
|
||
t = setTimeout(function () { fn.apply(ctx, args); }, wait);
|
||
};
|
||
}
|
||
|
||
/* ---- 工具:toast ---- */
|
||
var toastTimer;
|
||
function showToast(msg) {
|
||
var el = document.getElementById('toast');
|
||
el.textContent = msg;
|
||
el.classList.add('show');
|
||
clearTimeout(toastTimer);
|
||
toastTimer = setTimeout(function () { el.classList.remove('show'); }, 2200);
|
||
}
|
||
|
||
/* =========================================================================
|
||
Mock 数据
|
||
========================================================================= */
|
||
var MOCK = (function () {
|
||
var catDefs = [
|
||
{ id: 1, name: '推荐', icon: '\u2b50', sort: 1 },
|
||
{ id: 2, name: '游戏', icon: '\ud83c\udfae', sort: 2 },
|
||
{ id: 3, name: '效率', icon: '\u26a1', sort: 3 },
|
||
{ id: 4, name: '社交', icon: '\ud83d\udc65', sort: 4 },
|
||
{ id: 5, name: '工具', icon: '\ud83d\udee0', sort: 5 },
|
||
{ id: 6, name: '影音', icon: '\ud83c\udfa7', sort: 6 },
|
||
{ id: 7, name: '教育', icon: '\ud83d\udcda', sort: 7 },
|
||
{ id: 8, name: '生活', icon: '\ud83c\udfe1', sort: 8 },
|
||
{ id: 9, name: '财务', icon: '\ud83d\udcb0', sort: 9 }
|
||
];
|
||
|
||
var catDefsEN = [
|
||
{ id: 1, name: 'Recommended', icon: '\u2b50', sort: 1 },
|
||
{ id: 2, name: 'Games', icon: '\ud83c\udfae', sort: 2 },
|
||
{ id: 3, name: 'Productivity', icon: '\u26a1', sort: 3 },
|
||
{ id: 4, name: 'Social', icon: '\ud83d\udc65', sort: 4 },
|
||
{ id: 5, name: 'Tools', icon: '\ud83d\udee0', sort: 5 },
|
||
{ id: 6, name: 'Media', icon: '\ud83c\udfa7', sort: 6 },
|
||
{ id: 7, name: 'Education', icon: '\ud83d\udcda', sort: 7 },
|
||
{ id: 8, name: 'Lifestyle', icon: '\ud83c\udfe1', sort: 8 },
|
||
{ id: 9, name: 'Finance', icon: '\ud83d\udcb0', sort: 9 }
|
||
];
|
||
|
||
var catDefsTW = [
|
||
{ id: 1, name: '推薦', icon: '\u2b50', sort: 1 },
|
||
{ id: 2, name: '遊戲', icon: '\ud83c\udfae', sort: 2 },
|
||
{ id: 3, name: '效率', icon: '\u26a1', sort: 3 },
|
||
{ id: 4, name: '社交', icon: '\ud83d\udc65', sort: 4 },
|
||
{ id: 5, name: '工具', icon: '\ud83d\udee0', sort: 5 },
|
||
{ id: 6, name: '影音', icon: '\ud83c\udfa7', sort: 6 },
|
||
{ id: 7, name: '教育', icon: '\ud83d\udcda', sort: 7 },
|
||
{ id: 8, name: '生活', icon: '\ud83c\udfe1', sort: 8 },
|
||
{ id: 9, name: '財務', icon: '\ud83d\udcb0', sort: 9 }
|
||
];
|
||
|
||
function getLocalizedCats() {
|
||
if (currentLang === 'en') return catDefsEN;
|
||
if (currentLang === 'zh-TW') return catDefsTW;
|
||
return catDefs;
|
||
}
|
||
|
||
var appDefs = [
|
||
{ name: '极光笔记', dev: 'Aurora Studio', cat: 3, desc: '一款支持 Markdown 与思维导图的智能笔记应用,让灵感随手记录。', tags: ['笔记','Markdown','同步'], features: ['实时云端同步','支持思维导图','富文本与双链','端到端加密'], rating: 4.9, reviews: 128340, downloads: 56000000, size: '85 MB', ver: '3.2.1', date: '2026-06-20', plat: 'iOS / Android', price: '免费' },
|
||
{ name: '星海航海家', dev: 'Cosmos Games', cat: 2, desc: '在浩瀚星海中探索未知星球,建立你的星际舰队,征服宇宙。', tags: ['冒险','策略','科幻'], features: ['开放宇宙地图','多人实时对战','超过 200 种舰船','精美 3D 画面'], rating: 4.7, reviews: 89021, downloads: 23000000, size: '1.2 GB', ver: '2.0.5', date: '2026-07-01', plat: 'iOS / Android', price: '免费' },
|
||
{ name: '云听音乐', dev: 'CloudTune', cat: 6, desc: '高品质无损音乐播放器,千万曲库,智能推荐你的专属歌单。', tags: ['音乐','无损','推荐'], features: ['无损音质播放','AI 智能歌单','歌词逐字滚动','离线缓存'], rating: 4.8, reviews: 567200, downloads: 320000000, size: '120 MB', ver: '8.1.0', date: '2026-06-28', plat: 'iOS / Android', price: '免费' },
|
||
{ name: '时光日历', dev: 'TimeFlow', cat: 8, desc: '集日历、待办、习惯追踪于一体的时间管理工具,优雅而强大。', tags: ['日历','待办','效率'], features: ['自然语言输入','习惯打卡','多端同步','小组件支持'], rating: 4.6, reviews: 45120, downloads: 12000000, size: '45 MB', ver: '4.3.2', date: '2026-06-15', plat: 'iOS / Android', price: '免费' },
|
||
{ name: '像素工坊', dev: 'PixelArt Inc.', cat: 2, desc: '复古像素风格的沙盒建造游戏,发挥创意打造你的像素世界。', tags: ['沙盒','像素','创造'], features: ['无限创造模式','多人联机','丰富素材库','支持 MOD'], rating: 4.5, reviews: 234560, downloads: 89000000, size: '380 MB', ver: '1.9.0', date: '2026-06-22', plat: 'iOS / Android', price: '\u00a618' },
|
||
{ name: '速聊 Messenger', dev: 'QuickChat', cat: 4, desc: '极简安全的即时通讯应用,端到端加密,消息阅后即焚。', tags: ['聊天','加密','通讯'], features: ['端到端加密','阅后即焚','高清视频通话','多设备登录'], rating: 4.4, reviews: 312000, downloads: 450000000, size: '78 MB', ver: '6.0.3', date: '2026-07-05', plat: 'iOS / Android', price: '免费' },
|
||
{ name: '极简记账', dev: 'LedgerLab', cat: 9, desc: '一键记账,多维度图表分析,让每一笔收支清清楚楚。', tags: ['记账','理财','图表'], features: ['智能分类','多账本管理','预算提醒','数据导出'], rating: 4.7, reviews: 89300, downloads: 34000000, size: '52 MB', ver: '5.2.1', date: '2026-06-18', plat: 'iOS / Android', price: '免费' },
|
||
{ name: '番茄专注', dev: 'FocusLab', cat: 3, desc: '番茄工作法效率工具,白噪音伴你专注,告别拖延。', tags: ['番茄钟','专注','白噪音'], features: ['番茄计时','白噪音库','专注统计','森林养成'], rating: 4.8, reviews: 67800, downloads: 21000000, size: '38 MB', ver: '3.1.4', date: '2026-07-03', plat: 'iOS / Android', price: '免费' },
|
||
{ name: '星空摄影', dev: 'AstroShot', cat: 8, desc: '专业级手机摄影应用,AI 增强、长曝光、星空模式一应俱全。', tags: ['摄影','修图','AI'], features: ['AI 智能增强','星空拍摄模式','RAW 格式输出','专业参数调节'], rating: 4.6, reviews: 156000, downloads: 67000000, size: '95 MB', ver: '7.0.2', date: '2026-06-25', plat: 'iOS / Android', price: '免费' },
|
||
{ name: '编程学院', dev: 'CodeEdu', cat: 7, desc: '互动式编程学习平台,从零基础到全栈工程师,边学边练。', tags: ['编程','学习','在线'], features: ['互动编程练习','多语言课程','项目实战','在线编译器'], rating: 4.9, reviews: 98000, downloads: 15000000, size: '110 MB', ver: '4.0.0', date: '2026-06-30', plat: 'iOS / Android', price: '免费' },
|
||
{ name: '萌宠乐园', dev: 'PetWorld', cat: 2, desc: '养育你的虚拟萌宠,互动养成小游戏合集,治愈每一天。', tags: ['养成','休闲','治愈'], features: ['多种萌宠选择','互动小游戏','每日任务','社交访友'], rating: 4.5, reviews: 432000, downloads: 120000000, size: '260 MB', ver: '2.4.1', date: '2026-06-12', plat: 'iOS / Android', price: '免费' },
|
||
{ name: '文件管家', dev: 'FileMaster', cat: 5, desc: '全能文件管理工具,支持压缩解压、PDF 转换、无线传输。', tags: ['文件','管理','工具'], features: ['无线传输','压缩解压','PDF 工具','云盘挂载'], rating: 4.6, reviews: 76500, downloads: 43000000, size: '64 MB', ver: '6.3.0', date: '2026-06-20', plat: 'iOS / Android', price: '免费' },
|
||
{ name: '冥想空间', dev: 'MindPeace', cat: 8, desc: '引导式冥想与呼吸练习,缓解压力,改善睡眠质量。', tags: ['冥想','健康','助眠'], features: ['引导冥想课程','呼吸训练','睡眠白噪音','心情日记'], rating: 4.8, reviews: 54000, downloads: 9800000, size: '72 MB', ver: '3.0.5', date: '2026-07-02', plat: 'iOS / Android', price: '免费' },
|
||
{ name: '赛车竞速', dev: 'TurboGames', cat: 2, desc: '超真实 3D 赛车竞速,百辆豪车任你选,驰骋世界赛道。', tags: ['赛车','竞速','3D'], features: ['超百辆授权跑车','真实物理引擎','多人实时竞速','自定义改装'], rating: 4.4, reviews: 287000, downloads: 76000000, size: '1.5 GB', ver: '5.1.0', date: '2026-06-10', plat: 'iOS / Android', price: '免费' },
|
||
{ name: '翻译助手', dev: 'LingoPro', cat: 5, desc: '支持 100+ 语言的实时翻译,拍照翻译、语音对话即说即译。', tags: ['翻译','语言','工具'], features: ['100+ 语言互译','拍照翻译','语音实时对话','离线词库'], rating: 4.7, reviews: 198000, downloads: 99000000, size: '88 MB', ver: '9.0.1', date: '2026-06-27', plat: 'iOS / Android', price: '免费' },
|
||
{ name: '健身教练', dev: 'FitCoach', cat: 8, desc: 'AI 私人健身教练,定制训练计划,动作纠正,科学塑形。', tags: ['健身','运动','AI'], features: ['AI 动作识别','定制训练计划','卡路里追踪','社区挑战'], rating: 4.6, reviews: 123000, downloads: 55000000, size: '130 MB', ver: '4.2.0', date: '2026-06-19', plat: 'iOS / Android', price: '免费' },
|
||
{ name: '绘画大师', dev: 'ArtCanvas', cat: 5, desc: '专业数字绘画工具,支持图层、笔刷、矢量,创意无限。', tags: ['绘画','设计','创意'], features: ['200+ 专业笔刷','图层管理系统','矢量绘图','压感支持'], rating: 4.8, reviews: 67000, downloads: 18000000, size: '210 MB', ver: '2.7.3', date: '2026-07-04', plat: 'iOS / Android', price: '\u00a538' },
|
||
{ name: '天气通', dev: 'SkyCast', cat: 8, desc: '精准天气预报与空气质量监测,分钟级降水提醒。', tags: ['天气','预报','生活'], features: ['分钟级降水预报','15 天趋势','空气质量监测','灾害预警'], rating: 4.5, reviews: 345000, downloads: 210000000, size: '58 MB', ver: '7.5.2', date: '2026-06-23', plat: 'iOS / Android', price: '免费' },
|
||
{ name: '象棋大师', dev: 'BoardKing', cat: 2, desc: '中国象棋对弈平台,AI 难度可调,在线对战棋友遍布全球。', tags: ['象棋','棋牌','对战'], features: ['AI 多级难度','在线实时对战','棋谱复盘','残局挑战'], rating: 4.7, reviews: 89000, downloads: 33000000, size: '48 MB', ver: '3.4.1', date: '2026-06-14', plat: 'iOS / Android', price: '免费' },
|
||
{ name: '英语流利说', dev: 'EnglishPro', cat: 7, desc: 'AI 口语教练,情景对话练习,让你的英语脱口而出。', tags: ['英语','口语','学习'], features: ['AI 口语评分','情景对话','发音纠正','定制课程'], rating: 4.6, reviews: 256000, downloads: 88000000, size: '145 MB', ver: '8.2.0', date: '2026-06-26', plat: 'iOS / Android', price: '免费' },
|
||
{ name: '区块链钱包', dev: 'CryptoSafe', cat: 9, desc: '安全便捷的数字资产管理钱包,支持多链,DeFi 一站式。', tags: ['钱包','区块链','DeFi'], features: ['多链资产管理','硬件钱包支持','DeFi 聚合','安全审计'], rating: 4.3, reviews: 45000, downloads: 7600000, size: '92 MB', ver: '5.0.0', date: '2026-06-21', plat: 'iOS / Android', price: '免费' },
|
||
{ name: '飞行模拟器', dev: 'SkyPilot', cat: 2, desc: '高保真飞行模拟,全球机场与航线,体验机长的驾驶乐趣。', tags: ['模拟','飞行','3D'], features: ['全球真实机场','多种机型','实时天气','多视角驾驶'], rating: 4.5, reviews: 78000, downloads: 22000000, size: '2.1 GB', ver: '1.5.2', date: '2026-06-08', plat: 'iOS / Android', price: '\u00a068' },
|
||
{ name: '食谱大全', dev: 'CookMate', cat: 8, desc: '百万道菜谱,视频教学,根据冰箱食材智能推荐菜谱。', tags: ['美食','菜谱','生活'], features: ['百万菜谱库','视频教学','智能推荐','购物清单'], rating: 4.7, reviews: 167000, downloads: 61000000, size: '76 MB', ver: '6.1.0', date: '2026-06-24', plat: 'iOS / Android', price: '免费' },
|
||
{ name: '云盘同步', dev: 'CloudSync', cat: 5, desc: '安全可靠的云存储服务,自动备份照片文件,跨设备访问。', tags: ['云盘','备份','存储'], features: ['自动照片备份','大文件传输','端到端加密','多端同步'], rating: 4.4, reviews: 234000, downloads: 180000000, size: '68 MB', ver: '10.0.0', date: '2026-06-17', plat: 'iOS / Android', price: '免费' }
|
||
];
|
||
|
||
var apps = appDefs.map(function (a, i) {
|
||
var localizedCats = getLocalizedCats();
|
||
var catName = (localizedCats.find(function (c) { return c.id === a.cat; }) || {}).name || (getLocalizedCats()[0] || {}).name || '应用';
|
||
return {
|
||
id: i + 1,
|
||
name: a.name,
|
||
description: a.desc,
|
||
category_id: a.cat,
|
||
category_name: catName,
|
||
icon: '',
|
||
developer: a.dev,
|
||
rating: a.rating,
|
||
reviews: a.reviews,
|
||
downloads: a.downloads,
|
||
size: a.size,
|
||
version: a.ver,
|
||
update_date: a.date,
|
||
platform: a.plat,
|
||
price: a.price,
|
||
tags: JSON.stringify(a.tags),
|
||
screenshots: JSON.stringify([a.name + ' ' + (currentLang === 'en' ? 'Screenshot' : '截图') + ' 1', a.name + ' ' + (currentLang === 'en' ? 'Screenshot' : '截图') + ' 2', a.name + ' ' + (currentLang === 'en' ? 'Screenshot' : '截图') + ' 3']),
|
||
features: JSON.stringify(a.features),
|
||
is_featured: i < 5,
|
||
is_new: i >= (appDefs.length - 6),
|
||
sort: i + 1,
|
||
status: 1
|
||
};
|
||
});
|
||
|
||
function ok(data) { return { code: 0, message: 'ok', data: data }; }
|
||
|
||
return {
|
||
categories: function () { return ok(getLocalizedCats().slice()); },
|
||
featured: function (limit) {
|
||
limit = limit || 8;
|
||
return ok(apps.filter(function (a) { return a.is_featured; }).slice(0, limit));
|
||
},
|
||
newApps: function (limit) {
|
||
limit = limit || 4;
|
||
return ok(apps.filter(function (a) { return a.is_new; }).sort(function (x, y) {
|
||
return new Date(y.update_date) - new Date(x.update_date);
|
||
}).slice(0, limit));
|
||
},
|
||
apps: function (params) {
|
||
var cat = params.category_id, kw = (params.keyword || '').toLowerCase().trim(), sort = params.sort || '';
|
||
var page = parseInt(params.page) || 1, ps = parseInt(params.page_size) || PAGE_SIZE;
|
||
var list = apps.slice();
|
||
if (cat) list = list.filter(function (a) { return a.category_id == cat; });
|
||
if (kw) list = list.filter(function (a) {
|
||
return a.name.toLowerCase().indexOf(kw) > -1 || a.developer.toLowerCase().indexOf(kw) > -1;
|
||
});
|
||
if (sort === 'rating') list.sort(function (a, b) { return b.rating - a.rating; });
|
||
else if (sort === 'downloads') list.sort(function (a, b) { return b.downloads - a.downloads; });
|
||
else if (sort === 'newest') list.sort(function (a, b) { return new Date(b.update_date) - new Date(a.update_date); });
|
||
var total = list.length;
|
||
var start = (page - 1) * ps;
|
||
var paged = list.slice(start, start + ps);
|
||
return ok({ list: paged, total: total, page: page, page_size: ps });
|
||
},
|
||
detail: function (id) {
|
||
var a = apps.find(function (x) { return x.id == id; });
|
||
return a ? ok(a) : ok(null);
|
||
},
|
||
home: function () {
|
||
return ok({
|
||
categories: getLocalizedCats().slice(),
|
||
featured: apps.filter(function (a) { return a.is_featured; }).slice(0, 5),
|
||
new: apps.filter(function (a) { return a.is_new; }).sort(function (x, y) {
|
||
return new Date(y.update_date) - new Date(x.update_date);
|
||
}).slice(0, 4)
|
||
});
|
||
}
|
||
};
|
||
})();
|
||
|
||
/* =========================================================================
|
||
API 调用层
|
||
========================================================================= */
|
||
function apiGet(path, useMockFn) {
|
||
var url = API_BASE + path;
|
||
return fetch(url, { headers: { 'Accept': 'application/json' } })
|
||
.then(function (r) {
|
||
if (!r.ok) throw new Error('HTTP ' + r.status);
|
||
return r.json();
|
||
})
|
||
.then(function (res) {
|
||
if (res && typeof res.code !== 'undefined' && res.code !== 0) throw new Error(res.message || 'API error');
|
||
return res;
|
||
})
|
||
.catch(function (err) {
|
||
console.warn('[API] Request failed, using local data:', url, err.message);
|
||
return useMockFn();
|
||
});
|
||
}
|
||
|
||
function fetchCategories() {
|
||
return apiGet('/categories', function () { return MOCK.categories(); }).then(function (r) { return r.data; });
|
||
}
|
||
function fetchFeatured(limit) {
|
||
return apiGet('/featured?limit=' + (limit || 8), function () { return MOCK.featured(limit); }).then(function (r) { return r.data; });
|
||
}
|
||
function fetchNew(limit) {
|
||
return apiGet('/new?limit=' + (limit || 4), function () { return MOCK.newApps(limit); }).then(function (r) { return r.data; });
|
||
}
|
||
function fetchApps(params) {
|
||
var qs = [];
|
||
if (params.category_id) qs.push('category_id=' + params.category_id);
|
||
if (params.keyword) qs.push('keyword=' + encodeURIComponent(params.keyword));
|
||
if (params.sort) qs.push('sort=' + params.sort);
|
||
qs.push('page=' + (params.page || 1));
|
||
qs.push('page_size=' + (params.page_size || PAGE_SIZE));
|
||
var path = '/apps?' + qs.join('&');
|
||
return apiGet(path, function () { return MOCK.apps(params); }).then(function (r) { return r.data; });
|
||
}
|
||
function fetchDetail(id) {
|
||
return apiGet('/apps/' + id, function () { return MOCK.detail(id); }).then(function (r) { return r.data; });
|
||
}
|
||
|
||
/* =========================================================================
|
||
渲染:分类
|
||
========================================================================= */
|
||
function renderCategories(cats) {
|
||
state.categories = cats;
|
||
var bar = document.getElementById('catBar');
|
||
var html = '<button class="cat-pill active" data-cat="" onclick="selectCat(\'\')"><span class="cat-emoji">\u2728</span>' + esc(t('cat_all')) + '</button>';
|
||
cats.forEach(function (c) {
|
||
html += '<button class="cat-pill" data-cat="' + c.id + '" onclick="selectCat(' + c.id + ')">'
|
||
+ '<span class="cat-emoji">' + esc(c.icon || '\u25cf') + '</span>' + esc(c.name) + '</button>';
|
||
});
|
||
bar.innerHTML = html;
|
||
}
|
||
|
||
function selectCat(catId) {
|
||
state.currentCat = catId;
|
||
state.page = 1;
|
||
var pills = document.querySelectorAll('#catBar .cat-pill');
|
||
pills.forEach(function (p) {
|
||
p.classList.toggle('active', String(p.getAttribute('data-cat')) === String(catId));
|
||
});
|
||
loadLibrary();
|
||
var lib = document.getElementById('library');
|
||
if (lib) lib.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
||
}
|
||
|
||
/* =========================================================================
|
||
渲染:精选
|
||
========================================================================= */
|
||
function renderFeatured(list) {
|
||
var grid = document.getElementById('featuredGrid');
|
||
if (!list || !list.length) { grid.innerHTML = '<div class="empty-state" style="grid-column:1/-1;"><p>' + esc(t('empty_featured')) + '</p></div>'; return; }
|
||
var html = '';
|
||
list.forEach(function (app, idx) {
|
||
var grad = gradFromName(app.name);
|
||
if (idx === 0) {
|
||
html += '<div class="feature-big fade-in" onclick="openApp(' + app.id + ')">'
|
||
+ '<div class="feature-bg" style="background:' + grad + ';"></div>'
|
||
+ '<div class="feature-content">'
|
||
+ '<span class="ftag">' + esc(t('featured_tag')) + '</span>'
|
||
+ '<h3>' + esc(app.name) + '</h3>'
|
||
+ '<p class="fdesc">' + esc(app.description) + '</p>'
|
||
+ '<div class="feature-rating"><span class="stars">' + '\u2605'.repeat(5) + '</span> ' + app.rating + ' · ' + formatNumI18n(app.downloads) + ' ' + esc(t('downloads_label')) + '</div>'
|
||
+ '<button class="feature-btn" onclick="event.stopPropagation();openApp(' + app.id + ')">' + esc(t('view_detail')) + '</button>'
|
||
+ '</div></div>';
|
||
} else {
|
||
html += '<div class="feature-card fade-in" onclick="openApp(' + app.id + ')">'
|
||
+ '<div class="feature-bg" style="background:' + grad + ';"></div>'
|
||
+ '<div class="feature-content">'
|
||
+ '<span class="ftag">' + esc(t('featured_recommended')) + '</span>'
|
||
+ '<h3>' + esc(app.name) + '</h3>'
|
||
+ '<p class="fdesc">' + esc(app.category_name) + '</p>'
|
||
+ '<div class="feature-rating"><span class="stars">' + '\u2605'.repeat(5) + '</span> ' + app.rating + '</div>'
|
||
+ '</div></div>';
|
||
}
|
||
});
|
||
grid.innerHTML = html;
|
||
observeFadeIn();
|
||
}
|
||
|
||
/* =========================================================================
|
||
渲染:新上架
|
||
========================================================================= */
|
||
function renderNew(list) {
|
||
var wrap = document.getElementById('newScroll');
|
||
if (!list || !list.length) { wrap.innerHTML = '<div class="empty-state"><p>' + esc(t('empty_new')) + '</p></div>'; return; }
|
||
var html = '';
|
||
list.forEach(function (app) {
|
||
var grad = gradFromName(app.name);
|
||
html += '<div class="new-card fade-in" onclick="openApp(' + app.id + ')">'
|
||
+ '<span class="new-badge">NEW</span>'
|
||
+ '<div class="nc-head">'
|
||
+ appIconHTML(app, 'sm')
|
||
+ '<div><div class="nc-name">' + esc(app.name) + '</div><div class="nc-dev">' + esc(app.developer) + '</div></div>'
|
||
+ '</div>'
|
||
+ '<p class="nc-desc">' + esc(app.description) + '</p>'
|
||
+ '<div class="nc-foot">'
|
||
+ '<span class="nc-rate"><span class="stars">' + '\u2605' + '</span>' + app.rating + '</span>'
|
||
+ '<span style="font-size:11px;color:var(--text-tertiary);">' + esc(app.size) + '</span>'
|
||
+ '</div></div>';
|
||
});
|
||
wrap.innerHTML = html;
|
||
observeFadeIn();
|
||
}
|
||
|
||
/* =========================================================================
|
||
渲染:应用库网格
|
||
========================================================================= */
|
||
function renderAppGrid(list) {
|
||
var grid = document.getElementById('appGrid');
|
||
if (!list || !list.length) {
|
||
grid.innerHTML = '<div class="empty-state" style="grid-column:1/-1;">'
|
||
+ '<svg width="56" height="56" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.2"><circle cx="11" cy="11" r="7"/><path d="M21 21l-4.3-4.3"/></svg>'
|
||
+ '<p>' + esc(t('empty_apps')) + '</p></div>';
|
||
return;
|
||
}
|
||
var html = '';
|
||
list.forEach(function (app) {
|
||
var grad = gradFromName(app.name);
|
||
html += '<div class="app-card fade-in" onclick="openApp(' + app.id + ')">'
|
||
+ '<div class="ac-head">'
|
||
+ appIconHTML(app, '')
|
||
+ '<div class="ac-info"><div class="ac-name">' + esc(app.name) + '</div><div class="ac-cat">' + esc(app.category_name) + '</div></div>'
|
||
+ '</div>'
|
||
+ '<p class="ac-desc">' + esc(app.description) + '</p>'
|
||
+ '<div class="ac-foot">'
|
||
+ '<span class="ac-rate"><span class="stars">' + '\u2605' + '</span>' + app.rating + '</span>'
|
||
+ '<button class="ac-dl" onclick="event.stopPropagation();openApp(' + app.id + ')">' + esc(t('download')) + '</button>'
|
||
+ '</div></div>';
|
||
});
|
||
grid.innerHTML = html;
|
||
observeFadeIn();
|
||
}
|
||
|
||
/* 骨架屏 */
|
||
function renderGridSkeleton() {
|
||
var grid = document.getElementById('appGrid');
|
||
var html = '';
|
||
for (var i = 0; i < 12; i++) {
|
||
html += '<div class="app-card sk-card">'
|
||
+ '<div class="ac-head"><div class="skeleton" style="width:56px;height:56px;border-radius:14px;"></div>'
|
||
+ '<div style="flex:1;"><div class="skeleton sk-line w70" style="height:14px;"></div><div class="skeleton sk-line w50" style="margin-top:8px;height:12px;"></div></div></div>'
|
||
+ '<div class="skeleton sk-line w90" style="margin-top:14px;height:12px;"></div>'
|
||
+ '<div class="skeleton sk-line w50" style="margin-top:6px;height:12px;"></div>'
|
||
+ '<div style="display:flex;justify-content:space-between;margin-top:14px;"><div class="skeleton sk-line" style="width:50px;height:12px;"></div><div class="skeleton" style="width:50px;height:28px;border-radius:980px;"></div></div>'
|
||
+ '</div>';
|
||
}
|
||
grid.innerHTML = html;
|
||
}
|
||
|
||
/* =========================================================================
|
||
分页
|
||
========================================================================= */
|
||
function renderPagination() {
|
||
var el = document.getElementById('pagination');
|
||
var total = state.total, ps = PAGE_SIZE, page = state.page;
|
||
var pages = Math.ceil(total / ps);
|
||
if (pages <= 1) { el.innerHTML = ''; return; }
|
||
|
||
var html = '';
|
||
html += '<button class="page-btn" onclick="goPage(' + (page - 1) + ')" ' + (page <= 1 ? 'disabled' : '') + '>‹</button>';
|
||
|
||
var maxVisible = 5;
|
||
var startP = Math.max(1, page - 2);
|
||
var endP = Math.min(pages, startP + maxVisible - 1);
|
||
if (endP - startP < maxVisible - 1) startP = Math.max(1, endP - maxVisible + 1);
|
||
|
||
if (startP > 1) {
|
||
html += '<button class="page-btn" onclick="goPage(1)">1</button>';
|
||
if (startP > 2) html += '<span class="page-ellipsis">...</span>';
|
||
}
|
||
for (var p = startP; p <= endP; p++) {
|
||
html += '<button class="page-btn ' + (p === page ? 'active' : '') + '" onclick="goPage(' + p + ')">' + p + '</button>';
|
||
}
|
||
if (endP < pages) {
|
||
if (endP < pages - 1) html += '<span class="page-ellipsis">...</span>';
|
||
html += '<button class="page-btn" onclick="goPage(' + pages + ')">' + pages + '</button>';
|
||
}
|
||
|
||
html += '<button class="page-btn" onclick="goPage(' + (page + 1) + ')" ' + (page >= pages ? 'disabled' : '') + '>›</button>';
|
||
el.innerHTML = html;
|
||
}
|
||
|
||
function goPage(p) {
|
||
if (p < 1) return;
|
||
var pages = Math.ceil(state.total / PAGE_SIZE);
|
||
if (pages && p > pages) return;
|
||
state.page = p;
|
||
loadLibrary();
|
||
document.getElementById('library').scrollIntoView({ behavior: 'smooth', block: 'start' });
|
||
}
|
||
|
||
/* =========================================================================
|
||
排序
|
||
========================================================================= */
|
||
function toggleSort(e) {
|
||
if (e) e.stopPropagation();
|
||
var menu = document.getElementById('sortMenu');
|
||
menu.classList.toggle('open');
|
||
}
|
||
function setSort(sort) {
|
||
state.sort = sort;
|
||
state.page = 1;
|
||
var labelKey = { '': 'sort_default', 'rating': 'sort_rating', 'downloads': 'sort_downloads', 'newest': 'sort_newest' };
|
||
document.getElementById('sortLabel').textContent = t(labelKey[sort] || 'sort_default');
|
||
document.querySelectorAll('#sortMenu .sort-item').forEach(function (it) {
|
||
it.classList.toggle('active', it.getAttribute('data-sort') === sort);
|
||
});
|
||
document.getElementById('sortMenu').classList.remove('open');
|
||
loadLibrary();
|
||
}
|
||
document.addEventListener('click', function (e) {
|
||
var menu = document.getElementById('sortMenu');
|
||
var btn = document.getElementById('sortBtn');
|
||
if (menu.classList.contains('open') && !menu.contains(e.target) && !btn.contains(e.target)) {
|
||
menu.classList.remove('open');
|
||
}
|
||
});
|
||
|
||
/* =========================================================================
|
||
加载应用库
|
||
========================================================================= */
|
||
function loadLibrary() {
|
||
renderGridSkeleton();
|
||
fetchApps({
|
||
category_id: state.currentCat,
|
||
keyword: state.keyword,
|
||
sort: state.sort,
|
||
page: state.page,
|
||
page_size: PAGE_SIZE
|
||
}).then(function (data) {
|
||
state.total = data.total;
|
||
state.page = data.page;
|
||
renderAppGrid(data.list);
|
||
renderPagination();
|
||
});
|
||
}
|
||
|
||
/* =========================================================================
|
||
搜索(debounce 300ms)
|
||
========================================================================= */
|
||
var debouncedSearch = debounce(function (kw) {
|
||
state.keyword = kw;
|
||
state.page = 1;
|
||
loadLibrary();
|
||
}, 300);
|
||
|
||
/* =========================================================================
|
||
模态框:应用详情
|
||
========================================================================= */
|
||
function openApp(id) {
|
||
var body = document.getElementById('modalBody');
|
||
body.innerHTML = '<div style="padding-top:24px;">'
|
||
+ '<div style="display:flex;gap:20px;align-items:center;">'
|
||
+ '<div class="skeleton" style="width:88px;height:88px;border-radius:20px;"></div>'
|
||
+ '<div style="flex:1;"><div class="skeleton sk-line w50" style="height:24px;"></div><div class="skeleton sk-line w70" style="margin-top:10px;height:14px;"></div><div class="skeleton sk-line w40" style="margin-top:8px;height:14px;"></div></div>'
|
||
+ '</div></div>';
|
||
document.getElementById('modalBackdrop').classList.add('open');
|
||
document.body.style.overflow = 'hidden';
|
||
|
||
if (state.cache[id]) { renderModal(state.cache[id]); return; }
|
||
|
||
fetchDetail(id).then(function (app) {
|
||
if (!app) { body.innerHTML = '<div class="empty-state"><p>' + esc(t('empty_app_detail')) + '</p></div>'; return; }
|
||
state.cache[id] = app;
|
||
renderModal(app);
|
||
});
|
||
}
|
||
|
||
function renderModal(app) {
|
||
var body = document.getElementById('modalBody');
|
||
var grad = gradFromName(app.name);
|
||
var tags = parseJSON(app.tags, []);
|
||
var features = parseJSON(app.features, []);
|
||
var screenshots = parseJSON(app.screenshots, []);
|
||
|
||
var tagsHtml = tags.map(function (tag) { return '<span class="tag-pill">' + esc(tag) + '</span>'; }).join('');
|
||
var featHtml = features.map(function (f) { return '<li>' + esc(f) + '</li>'; }).join('');
|
||
var shotsHtml = screenshots.map(function (s, i) {
|
||
var sg = gradFromName(s + i);
|
||
return '<div class="shot-item" style="background:' + sg + ';"><span class="shot-label">' + esc(s) + '</span></div>';
|
||
}).join('');
|
||
|
||
body.innerHTML =
|
||
'<div class="modal-hero">'
|
||
+ appIconHTML(app, 'lg')
|
||
+ '<div class="mh-info">'
|
||
+ '<h2>' + esc(app.name) + '</h2>'
|
||
+ '<div class="mh-dev">' + esc(app.developer) + '</div>'
|
||
+ '<div class="mh-meta">'
|
||
+ '<div class="mh-rating"><span class="stars">' + starsHtml(app.rating) + '</span><span class="rnum">' + app.rating + '</span><span class="rcount">(' + formatNumI18n(app.reviews) + ' ' + esc(t('rating_count')) + ')</span></div>'
|
||
+ '<span class="mh-price">' + esc(app.price || t('download')) + '</span>'
|
||
+ '</div>'
|
||
+ '</div>'
|
||
+ '<button class="modal-dl-btn" onclick="downloadApp(' + app.id + ',\'' + esc(app.name).replace(/'/g, "\\'") + '\')">' + esc(t('download')) + '</button>'
|
||
+ '</div>'
|
||
+ '<div class="modal-stats">'
|
||
+ '<div class="modal-stat"><div class="ms-val">' + formatNumI18n(app.downloads) + '</div><div class="ms-label">' + esc(t('downloads_label')) + '</div></div>'
|
||
+ '<div class="modal-stat"><div class="ms-val">' + formatNumI18n(app.reviews) + '</div><div class="ms-label">' + esc(t('reviews_label')) + '</div></div>'
|
||
+ '<div class="modal-stat"><div class="ms-val">' + esc(app.size || '-') + '</div><div class="ms-label">' + esc(t('size_label')) + '</div></div>'
|
||
+ '</div>'
|
||
+ '<div class="modal-section"><h4>' + esc(t('app_intro')) + '</h4><p>' + esc(app.description) + '</p></div>'
|
||
+ (tags.length ? '<div class="modal-section"><h4>' + esc(t('app_tags')) + '</h4><div class="tag-row">' + tagsHtml + '</div></div>' : '')
|
||
+ (features.length ? '<div class="modal-section"><h4>' + esc(t('app_features')) + '</h4><ul class="feature-list">' + featHtml + '</ul></div>' : '')
|
||
+ (screenshots.length ? '<div class="modal-section"><h4>' + esc(t('app_screenshots')) + '</h4><div class="shots-row">' + shotsHtml + '</div></div>' : '')
|
||
+ '<div class="modal-version">'
|
||
+ '<span><b>' + esc(t('version_label')) + '</b>' + esc(app.version || '-') + '</span>'
|
||
+ '<span><b>' + esc(t('update_label')) + '</b>' + esc(app.update_date || '-') + '</span>'
|
||
+ '<span><b>' + esc(t('platform_label')) + '</b>' + esc(app.platform || '-') + '</span>'
|
||
+ '<span><b>' + esc(t('category_label')) + '</b>' + esc(app.category_name || '-') + '</span>'
|
||
+ '</div>';
|
||
}
|
||
|
||
function downloadApp(id, name) {
|
||
showToast(t('toast_downloading') + name + t('toast_downloading_end'));
|
||
}
|
||
|
||
function closeModal() {
|
||
document.getElementById('modalBackdrop').classList.remove('open');
|
||
document.body.style.overflow = '';
|
||
}
|
||
function closeModalOnBackdrop(e) {
|
||
if (e.target === document.getElementById('modalBackdrop')) closeModal();
|
||
}
|
||
document.addEventListener('keydown', function (e) {
|
||
if (e.key === 'Escape') closeModal();
|
||
});
|
||
|
||
/* =========================================================================
|
||
IntersectionObserver 淡入动画
|
||
========================================================================= */
|
||
var fadeObserver = null;
|
||
function observeFadeIn() {
|
||
if (!('IntersectionObserver' in window)) {
|
||
document.querySelectorAll('.fade-in').forEach(function (el) { el.classList.add('visible'); });
|
||
return;
|
||
}
|
||
if (!fadeObserver) {
|
||
fadeObserver = new IntersectionObserver(function (entries) {
|
||
entries.forEach(function (entry) {
|
||
if (entry.isIntersecting) {
|
||
entry.target.classList.add('visible');
|
||
fadeObserver.unobserve(entry.target);
|
||
}
|
||
});
|
||
}, { threshold: 0.1, rootMargin: '0px 0px -40px 0px' });
|
||
}
|
||
document.querySelectorAll('.fade-in:not(.visible)').forEach(function (el) { fadeObserver.observe(el); });
|
||
}
|
||
|
||
/* =========================================================================
|
||
导航 & 交互
|
||
========================================================================= */
|
||
function navTo(e, key) {
|
||
if (e) e.preventDefault();
|
||
document.querySelectorAll('#mainNav .nav-link').forEach(function (l) { l.classList.remove('active'); });
|
||
var link = document.querySelector('#mainNav .nav-link[data-nav="' + key + '"]');
|
||
if (link) link.classList.add('active');
|
||
if (key === 'home') {
|
||
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||
} else if (key === 'discover') {
|
||
document.getElementById('featuredSection').scrollIntoView({ behavior: 'smooth' });
|
||
} else if (key === 'ranking') {
|
||
state.sort = 'downloads';
|
||
state.page = 1;
|
||
document.getElementById('sortLabel').textContent = t('sort_downloads');
|
||
document.querySelectorAll('#sortMenu .sort-item').forEach(function (it) {
|
||
it.classList.toggle('active', it.getAttribute('data-sort') === 'downloads');
|
||
});
|
||
loadLibrary();
|
||
document.getElementById('library').scrollIntoView({ behavior: 'smooth' });
|
||
} else if (key === 'new') {
|
||
document.getElementById('newSection').scrollIntoView({ behavior: 'smooth' });
|
||
}
|
||
}
|
||
function goHome(e) { if (e) e.preventDefault(); navTo(e, 'home'); }
|
||
function exploreNow(e) { if (e) e.preventDefault(); document.getElementById('library').scrollIntoView({ behavior: 'smooth' }); }
|
||
function seeAllFeatured(e) { if (e) e.preventDefault(); document.getElementById('featuredSection').scrollIntoView({ behavior: 'smooth' }); }
|
||
function seeAllNew(e) { if (e) e.preventDefault(); document.getElementById('newSection').scrollIntoView({ behavior: 'smooth' }); }
|
||
|
||
/* =========================================================================
|
||
语言切换回调:重新渲染动态内容
|
||
========================================================================= */
|
||
var lastHomeData = null;
|
||
function onLangChange() {
|
||
applyI18n();
|
||
loadLibrary();
|
||
if (lastHomeData) {
|
||
renderCategories(lastHomeData.categories || []);
|
||
renderFeatured(lastHomeData.featured || []);
|
||
renderNew(lastHomeData.new || []);
|
||
}
|
||
}
|
||
|
||
/* =========================================================================
|
||
初始化
|
||
========================================================================= */
|
||
function init() {
|
||
applyI18n();
|
||
|
||
document.getElementById('headerSearch').addEventListener('input', function (e) {
|
||
var kw = e.target.value;
|
||
document.getElementById('libSearch').value = kw;
|
||
debouncedSearch(kw);
|
||
});
|
||
document.getElementById('libSearch').addEventListener('input', function (e) {
|
||
var kw = e.target.value;
|
||
document.getElementById('headerSearch').value = kw;
|
||
debouncedSearch(kw);
|
||
});
|
||
|
||
loadLibrary();
|
||
|
||
apiGet('/home', function () { return MOCK.home(); }).then(function (res) {
|
||
var d = res.data;
|
||
lastHomeData = d;
|
||
renderCategories(d.categories || []);
|
||
renderFeatured(d.featured || []);
|
||
renderNew(d.new || []);
|
||
}).catch(function () {
|
||
fetchCategories().then(function (cats) { lastHomeData = lastHomeData || {}; lastHomeData.categories = cats; renderCategories(cats); });
|
||
fetchFeatured(5).then(function (feat) { lastHomeData = lastHomeData || {}; lastHomeData.featured = feat; renderFeatured(feat); });
|
||
fetchNew(4).then(function (nw) { lastHomeData = lastHomeData || {}; lastHomeData.new = nw; renderNew(nw); });
|
||
});
|
||
}
|
||
|
||
if (document.readyState === 'loading') {
|
||
document.addEventListener('DOMContentLoaded', init);
|
||
} else {
|
||
init();
|
||
}
|
||
</script>
|
||
</body>
|
||
</html>
|