feat: 全站20个模板添加国际化(i18n)支持 - 简体中文/繁体中文/英文三语切换
- 所有18套前端模板 + 管理后台共20个HTML文件添加i18n引擎 - 前端JavaScript方案:I18N_LOCALES翻译字典 + data-i18n属性 + t()函数调用 - 语言切换器UI:下拉菜单选择,匹配各模板主题风格 - 语言持久化:localStorage存储 + URL参数覆盖 + 浏览器语言自动检测 - onLangChange回调:语言切换时动态重新渲染所有内容 - 支持语言:zh-CN(简体中文) / zh-TW(繁体中文) / en(English)
This commit is contained in:
469
web/dist/app2/index.html
vendored
469
web/dist/app2/index.html
vendored
@@ -161,6 +161,34 @@ img{max-width:100%;display:block}
|
||||
}
|
||||
.mobile-toggle{display:none;font-size:1.5rem;color:var(--neon-green);padding:4px}
|
||||
|
||||
/* ==================== 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:0.8rem;font-weight:500;color:var(--text-secondary);
|
||||
border:1px solid rgba(0,255,136,0.2);background:rgba(0,255,136,0.05);
|
||||
transition:all .25s ease;
|
||||
}
|
||||
.lang-btn:hover{background:rgba(0,255,136,0.12);color:var(--neon-green);border-color:rgba(0,255,136,0.4)}
|
||||
.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:#141414;border:1px solid rgba(0,255,136,0.2);border-radius:12px;
|
||||
box-shadow:0 8px 32px rgba(0,0,0,0.6);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:8px;font-size:0.85rem;color:var(--text-secondary);
|
||||
cursor:pointer;transition:all .15s;
|
||||
}
|
||||
.lang-option:hover{background:rgba(0,255,136,0.06);color:var(--text-primary)}
|
||||
.lang-option.active{color:var(--neon-green);font-weight:600}
|
||||
.lang-option .check{width:16px;height:16px;opacity:0;transition:opacity .2s}
|
||||
.lang-option.active .check{opacity:1}
|
||||
|
||||
/* ==================== LAYOUT ==================== */
|
||||
.container{max-width:1400px;margin:0 auto;padding:0 32px}
|
||||
.section{padding:72px 0}
|
||||
@@ -843,14 +871,35 @@ img{max-width:100%;display:block}
|
||||
<div class="header-inner">
|
||||
<a href="#hero" class="logo"><span class="logo-dot"></span>APP HUB</a>
|
||||
<nav class="nav" id="nav">
|
||||
<a href="#hero" class="nav-link active" data-target="hero">首页</a>
|
||||
<a href="#library" class="nav-link" data-target="library">探索</a>
|
||||
<a href="#featured" class="nav-link" data-target="featured">精选</a>
|
||||
<a href="#new" class="nav-link" data-target="new">新上架</a>
|
||||
<a href="#hero" class="nav-link active" data-target="hero" data-i18n="nav_home">首页</a>
|
||||
<a href="#library" class="nav-link" data-target="library" data-i18n="nav_explore">探索</a>
|
||||
<a href="#featured" class="nav-link" data-target="featured" data-i18n="nav_featured">精选</a>
|
||||
<a href="#new" class="nav-link" data-target="new" data-i18n="nav_new">新上架</a>
|
||||
</nav>
|
||||
<div class="search-wrap">
|
||||
<svg class="search-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/></svg>
|
||||
<input type="text" class="search-input" id="headerSearch" placeholder="搜索应用...">
|
||||
<input type="text" class="search-input" id="headerSearch" data-i18n-placeholder="search_placeholder" placeholder="搜索应用...">
|
||||
</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>
|
||||
<button class="mobile-toggle" id="mobileToggle">☰</button>
|
||||
</div>
|
||||
@@ -861,36 +910,36 @@ img{max-width:100%;display:block}
|
||||
<div class="hero-inner">
|
||||
<div class="hero-badge"><span class="hero-badge-dot"></span>SYSTEM ONLINE · v2.0.26</div>
|
||||
<h1 class="hero-title">
|
||||
<span class="glow-green">下一代</span><br>
|
||||
<span class="glow-cyan">应用中心</span>
|
||||
<span class="glow-green" data-i18n="hero_line1">下一代</span><br>
|
||||
<span class="glow-cyan" data-i18n="hero_line2">应用中心</span>
|
||||
</h1>
|
||||
<div class="hero-terminal">
|
||||
<div class="terminal-line"><span class="terminal-prompt">$</span> <span class="terminal-text">init app_hub --mode=dark</span></div>
|
||||
<div class="terminal-line"><span class="terminal-prompt">></span> <span class="terminal-text">loading app database...</span> <span class="terminal-cursor"></span></div>
|
||||
<div class="terminal-line"><span class="terminal-prompt">></span> <span class="terminal-text" style="color:var(--neon-green)">ready · 18 apps indexed</span></div>
|
||||
<div class="terminal-line"><span class="terminal-prompt">></span> <span class="terminal-text" data-i18n="hero_loading">loading app database...</span> <span class="terminal-cursor"></span></div>
|
||||
<div class="terminal-line"><span class="terminal-prompt">></span> <span class="terminal-text" style="color:var(--neon-green)" data-i18n="hero_ready">ready · 18 apps indexed</span></div>
|
||||
</div>
|
||||
<div class="hero-actions">
|
||||
<button class="btn-primary" onclick="scrollToSection('library')">
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M5 12h14M12 5l7 7-7 7"/></svg>
|
||||
进入商店
|
||||
<span data-i18n="hero_enter">进入商店</span>
|
||||
</button>
|
||||
<button class="btn-secondary" onclick="scrollToSection('featured')">
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"/></svg>
|
||||
精选推荐
|
||||
<span data-i18n="hero_featured">精选推荐</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="hero-stats">
|
||||
<div class="hero-stat">
|
||||
<div class="hero-stat-num" id="statApps">--</div>
|
||||
<div class="hero-stat-label">应用总数</div>
|
||||
<div class="hero-stat-label" data-i18n="stat_total_apps">应用总数</div>
|
||||
</div>
|
||||
<div class="hero-stat">
|
||||
<div class="hero-stat-num" id="statCats">--</div>
|
||||
<div class="hero-stat-label">分类数量</div>
|
||||
<div class="hero-stat-label" data-i18n="stat_total_cats">分类数量</div>
|
||||
</div>
|
||||
<div class="hero-stat">
|
||||
<div class="hero-stat-num" id="statDownloads">--</div>
|
||||
<div class="hero-stat-label">总下载量</div>
|
||||
<div class="hero-stat-label" data-i18n="stat_total_downloads">总下载量</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -901,7 +950,7 @@ img{max-width:100%;display:block}
|
||||
<div class="container">
|
||||
<div class="section-header reveal">
|
||||
<div class="section-title-wrap">
|
||||
<h2 class="section-title"><span class="accent">//</span> 分类导航</h2>
|
||||
<h2 class="section-title"><span class="accent">//</span> <span data-i18n="section_categories">分类导航</span></h2>
|
||||
<span class="section-tag">CATEGORIES</span>
|
||||
</div>
|
||||
<div class="section-line"></div>
|
||||
@@ -919,7 +968,7 @@ img{max-width:100%;display:block}
|
||||
<div class="container">
|
||||
<div class="section-header reveal">
|
||||
<div class="section-title-wrap">
|
||||
<h2 class="section-title"><span class="accent">//</span> 精选应用</h2>
|
||||
<h2 class="section-title"><span class="accent">//</span> <span data-i18n="section_featured">精选应用</span></h2>
|
||||
<span class="section-tag">FEATURED</span>
|
||||
</div>
|
||||
<div class="section-line"></div>
|
||||
@@ -938,7 +987,7 @@ img{max-width:100%;display:block}
|
||||
<div class="container">
|
||||
<div class="section-header reveal">
|
||||
<div class="section-title-wrap">
|
||||
<h2 class="section-title"><span class="accent">//</span> 最新上架</h2>
|
||||
<h2 class="section-title"><span class="accent">//</span> <span data-i18n="section_new">最新上架</span></h2>
|
||||
<span class="section-tag">NEW</span>
|
||||
</div>
|
||||
<div class="section-line"></div>
|
||||
@@ -957,7 +1006,7 @@ img{max-width:100%;display:block}
|
||||
<div class="container">
|
||||
<div class="section-header reveal">
|
||||
<div class="section-title-wrap">
|
||||
<h2 class="section-title"><span class="accent">//</span> 应用库</h2>
|
||||
<h2 class="section-title"><span class="accent">//</span> <span data-i18n="section_library">应用库</span></h2>
|
||||
<span class="section-tag">LIBRARY</span>
|
||||
</div>
|
||||
<div class="section-line"></div>
|
||||
@@ -965,13 +1014,13 @@ img{max-width:100%;display:block}
|
||||
<div class="library-toolbar reveal">
|
||||
<div class="lib-search">
|
||||
<svg class="search-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/></svg>
|
||||
<input type="text" class="search-input" id="libSearch" placeholder="搜索应用名称或开发者...">
|
||||
<input type="text" class="search-input" id="libSearch" data-i18n-placeholder="search_lib" placeholder="搜索应用名称或开发者...">
|
||||
</div>
|
||||
<div class="lib-sort" id="libSort">
|
||||
<button class="sort-btn active" data-sort="">默认</button>
|
||||
<button class="sort-btn" data-sort="rating">评分</button>
|
||||
<button class="sort-btn" data-sort="downloads">下载量</button>
|
||||
<button class="sort-btn" data-sort="newest">最新</button>
|
||||
<button class="sort-btn active" data-sort="" data-i18n="sort_default">默认</button>
|
||||
<button class="sort-btn" data-sort="rating" data-i18n="sort_rating">评分</button>
|
||||
<button class="sort-btn" data-sort="downloads" data-i18n="sort_downloads">下载量</button>
|
||||
<button class="sort-btn" data-sort="newest" data-i18n="sort_newest">最新</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="lib-cat-filter reveal" id="libCatFilter" style="margin-bottom:24px"></div>
|
||||
@@ -991,33 +1040,33 @@ img{max-width:100%;display:block}
|
||||
<div class="footer-inner">
|
||||
<div class="footer-brand">
|
||||
<div class="footer-logo">// APP HUB</div>
|
||||
<p class="footer-desc">下一代应用下载中心,暗黑科技风格设计,为您提供最前沿的应用发现体验。安全、快速、极致。</p>
|
||||
<p class="footer-desc" data-i18n="footer_desc">下一代应用下载中心,暗黑科技风格设计,为您提供最前沿的应用发现体验。安全、快速、极致。</p>
|
||||
</div>
|
||||
<div class="footer-links">
|
||||
<div class="footer-col">
|
||||
<h4>产品</h4>
|
||||
<a href="#featured">精选应用</a>
|
||||
<a href="#new">最新上架</a>
|
||||
<a href="#library">应用库</a>
|
||||
<h4 data-i18n="footer_product">产品</h4>
|
||||
<a href="#featured" data-i18n="nav_featured">精选应用</a>
|
||||
<a href="#new" data-i18n="nav_new">最新上架</a>
|
||||
<a href="#library" data-i18n="section_library">应用库</a>
|
||||
</div>
|
||||
<div class="footer-col">
|
||||
<h4>分类</h4>
|
||||
<a href="#library" onclick="filterByCategory(1)">社交通讯</a>
|
||||
<a href="#library" onclick="filterByCategory(2)">效率办公</a>
|
||||
<a href="#library" onclick="filterByCategory(3)">影音娱乐</a>
|
||||
<a href="#library" onclick="filterByCategory(6)">工具开发</a>
|
||||
<h4 data-i18n="footer_categories">分类</h4>
|
||||
<a href="#library" onclick="filterByCategory(1)" data-i18n="cat_social">社交通讯</a>
|
||||
<a href="#library" onclick="filterByCategory(2)" data-i18n="cat_productivity">效率办公</a>
|
||||
<a href="#library" onclick="filterByCategory(3)" data-i18n="cat_media">影音娱乐</a>
|
||||
<a href="#library" onclick="filterByCategory(6)" data-i18n="cat_tools">工具开发</a>
|
||||
</div>
|
||||
<div class="footer-col">
|
||||
<h4>关于</h4>
|
||||
<a href="#">关于我们</a>
|
||||
<a href="#">隐私政策</a>
|
||||
<a href="#">服务条款</a>
|
||||
<h4 data-i18n="footer_about">关于</h4>
|
||||
<a href="#" data-i18n="footer_about_us">关于我们</a>
|
||||
<a href="#" data-i18n="footer_privacy">隐私政策</a>
|
||||
<a href="#" data-i18n="footer_terms">服务条款</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer-bottom">
|
||||
<div class="footer-copy">© 2026 APP HUB · All rights reserved</div>
|
||||
<div class="footer-status"><span class="footer-status-dot"></span>SYSTEM OPERATIONAL</div>
|
||||
<div class="footer-copy" data-i18n="footer_copyright">© 2026 APP HUB · All rights reserved</div>
|
||||
<div class="footer-status"><span class="footer-status-dot"></span><span data-i18n="footer_status">SYSTEM OPERATIONAL</span></div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
@@ -1048,6 +1097,250 @@ img{max-width:100%;display:block}
|
||||
<div class="toast" id="toast"></div>
|
||||
|
||||
<script>
|
||||
/* ==================== i18n ==================== */
|
||||
var I18N_LOCALES = {
|
||||
'zh-CN': {
|
||||
nav_home: '首页',
|
||||
nav_explore: '探索',
|
||||
nav_featured: '精选',
|
||||
nav_new: '新上架',
|
||||
search_placeholder: '搜索应用...',
|
||||
search_lib: '搜索应用名称或开发者...',
|
||||
hero_line1: '下一代',
|
||||
hero_line2: '应用中心',
|
||||
hero_loading: 'loading app database...',
|
||||
hero_ready: 'ready · 18 apps indexed',
|
||||
hero_enter: '进入商店',
|
||||
hero_featured: '精选推荐',
|
||||
stat_total_apps: '应用总数',
|
||||
stat_total_cats: '分类数量',
|
||||
stat_total_downloads: '总下载量',
|
||||
section_categories: '分类导航',
|
||||
section_featured: '精选应用',
|
||||
section_new: '最新上架',
|
||||
section_library: '应用库',
|
||||
cat_all: '全部',
|
||||
cat_social: '社交通讯',
|
||||
cat_productivity: '效率办公',
|
||||
cat_media: '影音娱乐',
|
||||
cat_tools: '工具开发',
|
||||
sort_default: '默认',
|
||||
sort_rating: '评分',
|
||||
sort_downloads: '下载量',
|
||||
sort_newest: '最新',
|
||||
empty_featured: '暂无精选应用',
|
||||
empty_new: '暂无新上架应用',
|
||||
empty_library: '加载失败,请稍后重试',
|
||||
empty_no_match: '未找到匹配的应用',
|
||||
empty_tags: '暂无标签',
|
||||
empty_features: '暂无特性信息',
|
||||
modal_desc: '应用介绍',
|
||||
modal_tags: '标签',
|
||||
modal_features: '核心特性',
|
||||
modal_screenshots: '应用截图',
|
||||
modal_info: '应用信息',
|
||||
modal_downloads: '下载量',
|
||||
modal_size: '大小',
|
||||
modal_version: '版本',
|
||||
modal_update: '更新日期',
|
||||
modal_platform: '平台',
|
||||
modal_price: '价格',
|
||||
modal_free: '免费',
|
||||
modal_reviews: '评价',
|
||||
modal_download: '下载应用',
|
||||
modal_unknown_dev: '未知开发者',
|
||||
modal_unknown_cat: '未分类',
|
||||
modal_no_desc: '暂无描述',
|
||||
footer_product: '产品',
|
||||
footer_categories: '分类',
|
||||
footer_about: '关于',
|
||||
footer_about_us: '关于我们',
|
||||
footer_privacy: '隐私政策',
|
||||
footer_terms: '服务条款',
|
||||
footer_desc: '下一代应用下载中心,暗黑科技风格设计,为您提供最前沿的应用发现体验。安全、快速、极致。',
|
||||
footer_copyright: '\u00a9 2026 APP HUB \u00b7 All rights reserved',
|
||||
footer_status: 'SYSTEM OPERATIONAL',
|
||||
total_apps: '共 {n} 个应用'
|
||||
},
|
||||
'zh-TW': {
|
||||
nav_home: '首頁',
|
||||
nav_explore: '探索',
|
||||
nav_featured: '精選',
|
||||
nav_new: '新上架',
|
||||
search_placeholder: '搜尋應用...',
|
||||
search_lib: '搜尋應用名稱或開發者...',
|
||||
hero_line1: '下一代',
|
||||
hero_line2: '應用中心',
|
||||
hero_loading: 'loading app database...',
|
||||
hero_ready: 'ready \u00b7 18 apps indexed',
|
||||
hero_enter: '進入商店',
|
||||
hero_featured: '精選推薦',
|
||||
stat_total_apps: '應用總數',
|
||||
stat_total_cats: '分類數量',
|
||||
stat_total_downloads: '總下載量',
|
||||
section_categories: '分類導航',
|
||||
section_featured: '精選應用',
|
||||
section_new: '最新上架',
|
||||
section_library: '應用庫',
|
||||
cat_all: '全部',
|
||||
cat_social: '社交通訊',
|
||||
cat_productivity: '效率辦公',
|
||||
cat_media: '影音娛樂',
|
||||
cat_tools: '工具開發',
|
||||
sort_default: '預設',
|
||||
sort_rating: '評分',
|
||||
sort_downloads: '下載量',
|
||||
sort_newest: '最新',
|
||||
empty_featured: '暫無精選應用',
|
||||
empty_new: '暫無新上架應用',
|
||||
empty_library: '載入失敗,請稍後重試',
|
||||
empty_no_match: '未找到匹配的應用',
|
||||
empty_tags: '暫無標籤',
|
||||
empty_features: '暫無特性資訊',
|
||||
modal_desc: '應用介紹',
|
||||
modal_tags: '標籤',
|
||||
modal_features: '核心特性',
|
||||
modal_screenshots: '應用截圖',
|
||||
modal_info: '應用資訊',
|
||||
modal_downloads: '下載量',
|
||||
modal_size: '大小',
|
||||
modal_version: '版本',
|
||||
modal_update: '更新日期',
|
||||
modal_platform: '平台',
|
||||
modal_price: '價格',
|
||||
modal_free: '免費',
|
||||
modal_reviews: '評價',
|
||||
modal_download: '下載應用',
|
||||
modal_unknown_dev: '未知開發者',
|
||||
modal_unknown_cat: '未分類',
|
||||
modal_no_desc: '暫無描述',
|
||||
footer_product: '產品',
|
||||
footer_categories: '分類',
|
||||
footer_about: '關於',
|
||||
footer_about_us: '關於我們',
|
||||
footer_privacy: '隱私政策',
|
||||
footer_terms: '服務條款',
|
||||
footer_desc: '下一代應用下載中心,暗黑科技風格設計,為您提供最前沿的應用發現體驗。安全、快速、極致。',
|
||||
footer_copyright: '\u00a9 2026 APP HUB \u00b7 All rights reserved',
|
||||
footer_status: 'SYSTEM OPERATIONAL',
|
||||
total_apps: '共 {n} 個應用'
|
||||
},
|
||||
'en': {
|
||||
nav_home: 'Home',
|
||||
nav_explore: 'Explore',
|
||||
nav_featured: 'Featured',
|
||||
nav_new: 'New',
|
||||
search_placeholder: 'Search apps...',
|
||||
search_lib: 'Search app name or developer...',
|
||||
hero_line1: 'Next-Gen',
|
||||
hero_line2: 'App Hub',
|
||||
hero_loading: 'loading app database...',
|
||||
hero_ready: 'ready \u00b7 18 apps indexed',
|
||||
hero_enter: 'Enter Store',
|
||||
hero_featured: 'Featured Picks',
|
||||
stat_total_apps: 'Total Apps',
|
||||
stat_total_cats: 'Categories',
|
||||
stat_total_downloads: 'Total Downloads',
|
||||
section_categories: 'Categories',
|
||||
section_featured: 'Featured Apps',
|
||||
section_new: 'New Arrivals',
|
||||
section_library: 'App Library',
|
||||
cat_all: 'All',
|
||||
cat_social: 'Social',
|
||||
cat_productivity: 'Productivity',
|
||||
cat_media: 'Media & Entertainment',
|
||||
cat_tools: 'Tools & Dev',
|
||||
sort_default: 'Default',
|
||||
sort_rating: 'Top Rated',
|
||||
sort_downloads: 'Most Downloaded',
|
||||
sort_newest: 'Newest',
|
||||
empty_featured: 'No featured apps',
|
||||
empty_new: 'No new apps',
|
||||
empty_library: 'Load failed, please try again later',
|
||||
empty_no_match: 'No matching apps found',
|
||||
empty_tags: 'No tags',
|
||||
empty_features: 'No features available',
|
||||
modal_desc: 'Description',
|
||||
modal_tags: 'Tags',
|
||||
modal_features: 'Key Features',
|
||||
modal_screenshots: 'Screenshots',
|
||||
modal_info: 'App Info',
|
||||
modal_downloads: 'Downloads',
|
||||
modal_size: 'Size',
|
||||
modal_version: 'Version',
|
||||
modal_update: 'Updated',
|
||||
modal_platform: 'Platform',
|
||||
modal_price: 'Price',
|
||||
modal_free: 'Free',
|
||||
modal_reviews: 'Reviews',
|
||||
modal_download: 'Download App',
|
||||
modal_unknown_dev: 'Unknown Developer',
|
||||
modal_unknown_cat: 'Uncategorized',
|
||||
modal_no_desc: 'No description available',
|
||||
footer_product: 'Product',
|
||||
footer_categories: 'Categories',
|
||||
footer_about: 'About',
|
||||
footer_about_us: 'About Us',
|
||||
footer_privacy: 'Privacy Policy',
|
||||
footer_terms: 'Terms of Service',
|
||||
footer_desc: 'Next-gen app download hub with dark tech-inspired design. Providing you with the most cutting-edge app discovery experience. Safe, fast, extreme.',
|
||||
footer_copyright: '\u00a9 2026 APP HUB \u00b7 All rights reserved',
|
||||
footer_status: 'SYSTEM OPERATIONAL',
|
||||
total_apps: '{n} apps'
|
||||
}
|
||||
};
|
||||
|
||||
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('apphub_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 'zh-CN';
|
||||
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';
|
||||
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('apphub_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');
|
||||
});
|
||||
|
||||
/* ==================== STATE ==================== */
|
||||
const state = {
|
||||
categories: [],
|
||||
@@ -1069,7 +1362,7 @@ async function apiGet(path) {
|
||||
if (!res.ok) throw new Error('HTTP ' + res.status);
|
||||
const json = await res.json();
|
||||
if (json.code === 0) return json.data;
|
||||
throw new Error(json.message || '请求失败');
|
||||
throw new Error(json.message || t('empty_library'));
|
||||
} catch (e) {
|
||||
console.error('[API Error]', path, e);
|
||||
return null;
|
||||
@@ -1079,8 +1372,14 @@ async function apiGet(path) {
|
||||
/* ==================== UTILS ==================== */
|
||||
function formatNumber(n) {
|
||||
if (n == null || isNaN(n)) return '0';
|
||||
if (n >= 100000000) return (n / 100000000).toFixed(1).replace(/\.0$/, '') + '亿';
|
||||
if (n >= 10000) return (n / 10000).toFixed(1).replace(/\.0$/, '') + '万';
|
||||
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 String(n);
|
||||
}
|
||||
if (n >= 100000000) return (n / 100000000).toFixed(1).replace(/\.0$/, '') + '\u4ebf';
|
||||
if (n >= 10000) return (n / 10000).toFixed(1).replace(/\.0$/, '') + '\u4e07';
|
||||
return String(n);
|
||||
}
|
||||
|
||||
@@ -1171,7 +1470,6 @@ function appCardHTML(app, opts) {
|
||||
const isFeatured = opts.featured;
|
||||
const isNew = opts.newCard;
|
||||
const cardClass = isFeatured ? 'featured-card' : (isNew ? 'new-card' : 'app-card');
|
||||
const iconSize = isFeatured ? '' : '';
|
||||
|
||||
let badge = '';
|
||||
if (app.is_new && !isNew) {
|
||||
@@ -1187,10 +1485,10 @@ function appCardHTML(app, opts) {
|
||||
'<div class="app-icon">' + generateIconHTML(app) + '</div>' +
|
||||
'<div class="card-name-row">' +
|
||||
'<div class="card-name">' + escapeHtml(app.name) + '</div>' +
|
||||
'<div class="card-dev">' + escapeHtml(app.developer || '未知开发者') + '</div>' +
|
||||
'<div class="card-dev">' + escapeHtml(app.developer || t('modal_unknown_dev')) + '</div>' +
|
||||
'</div>' +
|
||||
'</div>' +
|
||||
'<span class="card-cat">' + escapeHtml(app.category_name || '未分类') + '</span>' +
|
||||
'<span class="card-cat">' + escapeHtml(app.category_name || t('modal_unknown_cat')) + '</span>' +
|
||||
'<p class="card-desc">' + escapeHtml(app.description || '') + '</p>' +
|
||||
'<div class="card-bottom">' +
|
||||
'<div class="card-rating">' +
|
||||
@@ -1208,7 +1506,7 @@ function appCardHTML(app, opts) {
|
||||
/* ==================== CATEGORY BAR ==================== */
|
||||
function renderCategoryBar() {
|
||||
const bar = document.getElementById('categoryBar');
|
||||
let html = '<button class="cat-pill active" data-cid="0" onclick="scrollToSection(\'library\')">全部</button>';
|
||||
let html = '<button class="cat-pill active" data-cid="0" onclick="scrollToSection(\'library\')">' + escapeHtml(t('cat_all')) + '</button>';
|
||||
state.categories.forEach(function (cat) {
|
||||
html += '<button class="cat-pill" data-cid="' + cat.id + '" onclick="filterByCategory(' + cat.id + ')">' + escapeHtml(cat.name) + '</button>';
|
||||
});
|
||||
@@ -1217,7 +1515,7 @@ function renderCategoryBar() {
|
||||
|
||||
function renderLibCatFilter() {
|
||||
const wrap = document.getElementById('libCatFilter');
|
||||
let html = '<button class="cat-pill active" data-libcid="0">全部</button>';
|
||||
let html = '<button class="cat-pill active" data-libcid="0">' + escapeHtml(t('cat_all')) + '</button>';
|
||||
state.categories.forEach(function (cat) {
|
||||
html += '<button class="cat-pill" data-libcid="' + cat.id + '">' + escapeHtml(cat.name) + '</button>';
|
||||
});
|
||||
@@ -1250,7 +1548,7 @@ function filterByCategory(cid) {
|
||||
function renderFeatured() {
|
||||
const grid = document.getElementById('featuredGrid');
|
||||
if (!state.featured || state.featured.length === 0) {
|
||||
grid.innerHTML = '<div class="empty-state"><div class="empty-state-icon">🔍</div><div class="empty-state-text">暂无精选应用</div></div>';
|
||||
grid.innerHTML = '<div class="empty-state"><div class="empty-state-icon">🔍</div><div class="empty-state-text">' + esc(t('empty_featured')) + '</div></div>';
|
||||
return;
|
||||
}
|
||||
let html = '';
|
||||
@@ -1264,7 +1562,7 @@ function renderFeatured() {
|
||||
function renderNew() {
|
||||
const scroll = document.getElementById('newScroll');
|
||||
if (!state.newApps || state.newApps.length === 0) {
|
||||
scroll.innerHTML = '<div class="empty-state"><div class="empty-state-icon">🔍</div><div class="empty-state-text">暂无新上架应用</div></div>';
|
||||
scroll.innerHTML = '<div class="empty-state"><div class="empty-state-icon">🔍</div><div class="empty-state-text">' + esc(t('empty_new')) + '</div></div>';
|
||||
return;
|
||||
}
|
||||
let html = '';
|
||||
@@ -1279,7 +1577,6 @@ async function loadLibrary() {
|
||||
if (state.loading) return;
|
||||
state.loading = true;
|
||||
const grid = document.getElementById('libraryGrid');
|
||||
// Show skeletons
|
||||
let skel = '';
|
||||
for (let i = 0; i < 10; i++) {
|
||||
skel += '<div class="skeleton-card"><div class="skel skel-icon"></div><div class="skel skel-line mid"></div><div class="skel skel-line short"></div><div class="skel skel-line"></div><div class="skel skel-line short"></div></div>';
|
||||
@@ -1297,7 +1594,7 @@ async function loadLibrary() {
|
||||
state.loading = false;
|
||||
|
||||
if (!data || !data.list) {
|
||||
grid.innerHTML = '<div class="empty-state"><div class="empty-state-icon">⚠</div><div class="empty-state-text">加载失败,请稍后重试</div></div>';
|
||||
grid.innerHTML = '<div class="empty-state"><div class="empty-state-icon">⚠</div><div class="empty-state-text">' + esc(t('empty_library')) + '</div></div>';
|
||||
document.getElementById('pagination').innerHTML = '';
|
||||
return;
|
||||
}
|
||||
@@ -1308,7 +1605,7 @@ async function loadLibrary() {
|
||||
state.library.pageSize = data.page_size || 10;
|
||||
|
||||
if (data.list.length === 0) {
|
||||
grid.innerHTML = '<div class="empty-state"><div class="empty-state-icon">🔍</div><div class="empty-state-text">未找到匹配的应用</div></div>';
|
||||
grid.innerHTML = '<div class="empty-state"><div class="empty-state-icon">🔍</div><div class="empty-state-text">' + esc(t('empty_no_match')) + '</div></div>';
|
||||
document.getElementById('pagination').innerHTML = '';
|
||||
return;
|
||||
}
|
||||
@@ -1329,7 +1626,7 @@ function renderPagination() {
|
||||
const totalPages = Math.ceil(total / pageSize);
|
||||
|
||||
if (totalPages <= 1) {
|
||||
wrap.innerHTML = '<span class="page-info">共 ' + total + ' 个应用</span>';
|
||||
wrap.innerHTML = '<span class="page-info">' + t('total_apps').replace('{n}', total) + '</span>';
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1373,7 +1670,6 @@ async function openModal(appId) {
|
||||
overlay.classList.add('open');
|
||||
document.body.style.overflow = 'hidden';
|
||||
|
||||
// Show skeleton
|
||||
content.innerHTML =
|
||||
'<div class="modal-skel">' +
|
||||
'<div style="display:flex;gap:20px;margin-bottom:24px">' +
|
||||
@@ -1389,7 +1685,7 @@ async function openModal(appId) {
|
||||
|
||||
const app = await apiGet('/apps/' + appId);
|
||||
if (!app) {
|
||||
content.innerHTML = '<div class="empty-state"><div class="empty-state-icon">⚠</div><div class="empty-state-text">加载应用详情失败</div></div>';
|
||||
content.innerHTML = '<div class="empty-state"><div class="empty-state-icon">⚠</div><div class="empty-state-text">' + esc(t('empty_library')) + '</div></div>';
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1399,9 +1695,9 @@ async function openModal(appId) {
|
||||
|
||||
let tagsHTML = '';
|
||||
if (tags.length) {
|
||||
tagsHTML = tags.map(function (t) { return '<span class="tag-pill">' + escapeHtml(t) + '</span>'; }).join('');
|
||||
tagsHTML = tags.map(function (tag) { return '<span class="tag-pill">' + escapeHtml(tag) + '</span>'; }).join('');
|
||||
} else {
|
||||
tagsHTML = '<span class="tag-pill" style="opacity:0.5">暂无标签</span>';
|
||||
tagsHTML = '<span class="tag-pill" style="opacity:0.5">' + t('empty_tags') + '</span>';
|
||||
}
|
||||
|
||||
let featuresHTML = '';
|
||||
@@ -1410,12 +1706,12 @@ async function openModal(appId) {
|
||||
return '<li class="feature-item"><span class="feature-check">▸</span>' + escapeHtml(f) + '</li>';
|
||||
}).join('');
|
||||
} else {
|
||||
featuresHTML = '<li class="feature-item" style="opacity:0.5"><span class="feature-check">▸</span>暂无特性信息</li>';
|
||||
featuresHTML = '<li class="feature-item" style="opacity:0.5"><span class="feature-check">▸</span>' + t('empty_features') + '</li>';
|
||||
}
|
||||
|
||||
let screenshotsHTML = '';
|
||||
if (screenshots.length) {
|
||||
screenshotsHTML = '<div class="modal-section"><div class="modal-section-title">应用截图</div><div style="display:flex;gap:10px;overflow-x:auto;padding-bottom:8px">' +
|
||||
screenshotsHTML = '<div class="modal-section"><div class="modal-section-title">' + t('modal_screenshots') + '</div><div style="display:flex;gap:10px;overflow-x:auto;padding-bottom:8px">' +
|
||||
screenshots.map(function (s) {
|
||||
return '<div style="flex:0 0 auto;min-width:200px;height:120px;background:rgba(255,255,255,0.03);border:1px solid var(--border-glass);border-radius:10px;display:flex;align-items:center;justify-content:center;font-size:0.75rem;color:var(--text-muted);padding:12px;text-align:center">' + escapeHtml(s) + '</div>';
|
||||
}).join('') +
|
||||
@@ -1427,46 +1723,46 @@ async function openModal(appId) {
|
||||
'<div class="app-icon modal-icon">' + generateIconHTML(app) + '</div>' +
|
||||
'<div class="modal-title-area">' +
|
||||
'<h2 class="modal-name">' + escapeHtml(app.name) + '</h2>' +
|
||||
'<div class="modal-dev">' + escapeHtml(app.developer || '未知开发者') + '</div>' +
|
||||
'<div class="modal-dev">' + escapeHtml(app.developer || t('modal_unknown_dev')) + '</div>' +
|
||||
'<div class="modal-cat-row">' +
|
||||
'<span class="card-cat">' + escapeHtml(app.category_name || '未分类') + '</span>' +
|
||||
'<span class="card-cat">' + escapeHtml(app.category_name || t('modal_unknown_cat')) + '</span>' +
|
||||
(app.is_new ? '<span class="new-badge" style="position:static">NEW</span>' : '') +
|
||||
(app.is_featured ? '<span class="feat-badge" style="position:static">FEATURED</span>' : '') +
|
||||
'</div>' +
|
||||
'<div class="modal-rating-row" style="margin-top:8px">' +
|
||||
'<div class="stars">' + starRatingHTML(app.rating) + '</div>' +
|
||||
'<span class="modal-rating-num">' + (parseFloat(app.rating) || 0).toFixed(1) + '</span>' +
|
||||
'<span class="modal-reviews">(' + formatNumber(app.reviews) + ' 评价)</span>' +
|
||||
'<span class="modal-reviews">(' + formatNumber(app.reviews) + ' ' + t('modal_reviews') + ')</span>' +
|
||||
'</div>' +
|
||||
'</div>' +
|
||||
'</div>' +
|
||||
'<div class="modal-section">' +
|
||||
'<div class="modal-section-title">应用介绍</div>' +
|
||||
'<p class="modal-desc">' + escapeHtml(app.description || '暂无描述') + '</p>' +
|
||||
'<div class="modal-section-title">' + t('modal_desc') + '</div>' +
|
||||
'<p class="modal-desc">' + escapeHtml(app.description || t('modal_no_desc')) + '</p>' +
|
||||
'</div>' +
|
||||
'<div class="modal-section">' +
|
||||
'<div class="modal-section-title">标签</div>' +
|
||||
'<div class="modal-section-title">' + t('modal_tags') + '</div>' +
|
||||
'<div class="tag-row">' + tagsHTML + '</div>' +
|
||||
'</div>' +
|
||||
'<div class="modal-section">' +
|
||||
'<div class="modal-section-title">核心特性</div>' +
|
||||
'<div class="modal-section-title">' + t('modal_features') + '</div>' +
|
||||
'<ul class="feature-list">' + featuresHTML + '</ul>' +
|
||||
'</div>' +
|
||||
screenshotsHTML +
|
||||
'<div class="modal-section">' +
|
||||
'<div class="modal-section-title">应用信息</div>' +
|
||||
'<div class="modal-section-title">' + t('modal_info') + '</div>' +
|
||||
'<div class="modal-stats">' +
|
||||
'<div class="stat-box"><div class="stat-label">下载量</div><div class="stat-value">' + formatNumber(app.downloads) + '</div></div>' +
|
||||
'<div class="stat-box"><div class="stat-label">大小</div><div class="stat-value">' + escapeHtml(app.size || '-') + '</div></div>' +
|
||||
'<div class="stat-box"><div class="stat-label">版本</div><div class="stat-value">' + escapeHtml(app.version || '-') + '</div></div>' +
|
||||
'<div class="stat-box"><div class="stat-label">更新日期</div><div class="stat-value">' + escapeHtml(app.update_date || '-') + '</div></div>' +
|
||||
'<div class="stat-box"><div class="stat-label">平台</div><div class="stat-value" style="font-size:0.8rem">' + escapeHtml(app.platform || '-') + '</div></div>' +
|
||||
'<div class="stat-box"><div class="stat-label">价格</div><div class="stat-value">' + escapeHtml(app.price || '免费') + '</div></div>' +
|
||||
'<div class="stat-box"><div class="stat-label">' + t('modal_downloads') + '</div><div class="stat-value">' + formatNumber(app.downloads) + '</div></div>' +
|
||||
'<div class="stat-box"><div class="stat-label">' + t('modal_size') + '</div><div class="stat-value">' + escapeHtml(app.size || '-') + '</div></div>' +
|
||||
'<div class="stat-box"><div class="stat-label">' + t('modal_version') + '</div><div class="stat-value">' + escapeHtml(app.version || '-') + '</div></div>' +
|
||||
'<div class="stat-box"><div class="stat-label">' + t('modal_update') + '</div><div class="stat-value">' + escapeHtml(app.update_date || '-') + '</div></div>' +
|
||||
'<div class="stat-box"><div class="stat-label">' + t('modal_platform') + '</div><div class="stat-value" style="font-size:0.8rem">' + escapeHtml(app.platform || '-') + '</div></div>' +
|
||||
'<div class="stat-box"><div class="stat-label">' + t('modal_price') + '</div><div class="stat-value">' + escapeHtml(app.price || t('modal_free')) + '</div></div>' +
|
||||
'</div>' +
|
||||
'</div>' +
|
||||
'<button class="modal-download" onclick="showToast(\'开始下载 ' + escapeHtml(app.name) + ' ' + escapeHtml(app.version || '') + '\')">' +
|
||||
'<button class="modal-download" onclick="showToast(\'' + escapeHtml(t('modal_download')) + ' ' + escapeHtml(app.name) + ' ' + escapeHtml(app.version || '') + '\')">' +
|
||||
'<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg>' +
|
||||
'下载应用' +
|
||||
t('modal_download') +
|
||||
'</button>';
|
||||
}
|
||||
|
||||
@@ -1511,7 +1807,6 @@ function initObserver() {
|
||||
});
|
||||
}
|
||||
observeAll();
|
||||
// Re-observe after dynamic content loads
|
||||
window._observeAll = observeAll;
|
||||
}
|
||||
|
||||
@@ -1533,13 +1828,23 @@ function initNavScroll() {
|
||||
});
|
||||
}
|
||||
|
||||
/* ==================== onLangChange ==================== */
|
||||
function onLangChange() {
|
||||
applyI18n();
|
||||
renderCategoryBar();
|
||||
renderLibCatFilter();
|
||||
renderFeatured();
|
||||
renderNew();
|
||||
loadLibrary();
|
||||
}
|
||||
|
||||
/* ==================== INIT ==================== */
|
||||
async function init() {
|
||||
applyI18n();
|
||||
initParticles();
|
||||
initObserver();
|
||||
initNavScroll();
|
||||
|
||||
// Header search → library search
|
||||
const headerSearch = document.getElementById('headerSearch');
|
||||
const libSearch = document.getElementById('libSearch');
|
||||
headerSearch.addEventListener('input', debounce(function (e) {
|
||||
@@ -1552,14 +1857,12 @@ async function init() {
|
||||
scrollToSection('library');
|
||||
}, 300));
|
||||
|
||||
// Library search
|
||||
libSearch.addEventListener('input', debounce(function (e) {
|
||||
state.libKeyword = e.target.value.trim();
|
||||
state.library.page = 1;
|
||||
loadLibrary();
|
||||
}, 300));
|
||||
|
||||
// Sort buttons
|
||||
document.querySelectorAll('#libSort .sort-btn').forEach(function (btn) {
|
||||
btn.addEventListener('click', function () {
|
||||
document.querySelectorAll('#libSort .sort-btn').forEach(function (b) { b.classList.remove('active'); });
|
||||
@@ -1570,7 +1873,6 @@ async function init() {
|
||||
});
|
||||
});
|
||||
|
||||
// Modal close on overlay click & ESC
|
||||
document.getElementById('modalOverlay').addEventListener('click', function (e) {
|
||||
if (e.target === this) closeModal();
|
||||
});
|
||||
@@ -1578,7 +1880,6 @@ async function init() {
|
||||
if (e.key === 'Escape') closeModal();
|
||||
});
|
||||
|
||||
// Mobile toggle
|
||||
document.getElementById('mobileToggle').addEventListener('click', function () {
|
||||
const nav = document.getElementById('nav');
|
||||
if (nav.style.display === 'flex') {
|
||||
@@ -1597,7 +1898,6 @@ async function init() {
|
||||
}
|
||||
});
|
||||
|
||||
// Load home data
|
||||
const home = await apiGet('/home');
|
||||
if (home) {
|
||||
state.categories = home.categories || [];
|
||||
@@ -1609,18 +1909,15 @@ async function init() {
|
||||
renderFeatured();
|
||||
renderNew();
|
||||
|
||||
// Update hero stats
|
||||
document.getElementById('statCats').textContent = state.categories.length;
|
||||
const totalDownloads = (state.featured.concat(state.newApps)).reduce(function (sum, app) {
|
||||
return sum + (app.downloads || 0);
|
||||
}, 0);
|
||||
// Also fetch total from library for accurate count
|
||||
document.getElementById('statApps').textContent = '18+';
|
||||
document.getElementById('statDownloads').textContent = formatNumber(totalDownloads || 560000000);
|
||||
|
||||
if (window._observeAll) window._observeAll();
|
||||
} else {
|
||||
// Fallback: load categories separately
|
||||
const cats = await apiGet('/categories');
|
||||
if (cats) {
|
||||
state.categories = cats;
|
||||
@@ -1642,10 +1939,8 @@ async function init() {
|
||||
document.getElementById('statDownloads').textContent = '--';
|
||||
}
|
||||
|
||||
// Load library
|
||||
loadLibrary();
|
||||
|
||||
// Set hero stats after library loads
|
||||
setTimeout(async function () {
|
||||
const libData = await apiGet('/apps?page=1&page_size=1');
|
||||
if (libData) {
|
||||
|
||||
Reference in New Issue
Block a user