Files
web_template/web/dist/app3/index.html
Geliebte 10979351f8 feat: 默认语言改为英语 + 导航页添加i18n三语支持
- 导航页(index.html)添加完整i18n引擎,支持zh-CN/zh-TW/en
- 导航页添加语言切换按钮(EN/中/繁)
- 所有20个前端模板默认语言从zh-CN改为en
- 导航页SEO meta标签改为英文
2026-09-09 19:48:45 +08:00

1980 lines
89 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!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>
/* ==================== CSS Variables ==================== */
:root {
--coral: #ff6b6b;
--mint: #4ecdc4;
--sky: #45b7d8;
--amber: #ffa94d;
--purple: #9775fa;
--pink: #f783ac;
--green: #51cf66;
--bg-start: #f0f4ff;
--bg-end: #e8f5e9;
--card-bg: #ffffff;
--text-dark: #2d3748;
--text-mid: #4a5568;
--text-light: #a0aec0;
--shadow-soft: 0 4px 24px rgba(0,0,0,0.06);
--shadow-hover: 0 16px 48px rgba(0,0,0,0.12);
--radius-sm: 12px;
--radius-md: 16px;
--radius-lg: 20px;
--radius-xl: 24px;
--radius-pill: 999px;
--bounce: cubic-bezier(0.34, 1.56, 0.64, 1);
--ease-out: cubic-bezier(0.25, 0.46, 0.45, 0.94);
--font: -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "Helvetica Neue", Helvetica, Arial, sans-serif;
}
/* ==================== Reset & Base ==================== */
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
html { scroll-behavior: smooth; }
body {
font-family: var(--font);
color: var(--text-dark);
background: linear-gradient(160deg, var(--bg-start) 0%, var(--bg-end) 100%);
min-height: 100vh;
overflow-x: hidden;
line-height: 1.6;
}
a { color: inherit; text-decoration: none; }
button { font-family: inherit; cursor: pointer; border: none; outline: none; }
input { font-family: inherit; outline: none; }
img { max-width: 100%; display: block; }
::selection { background: rgba(78,205,196,0.3); }
/* ==================== Background Decorations ==================== */
.bg-decor { position: fixed; inset: 0; z-index: 0; pointer-events: none; overflow: hidden; }
.blob {
position: absolute;
border-radius: 50%;
filter: blur(80px);
opacity: 0.25;
animation: floatBlob 20s ease-in-out infinite;
}
.blob-1 { width: 400px; height: 400px; background: var(--coral); top: -120px; left: -100px; }
.blob-2 { width: 500px; height: 500px; background: var(--mint); bottom: -180px; right: -120px; animation-delay: -5s; }
.blob-3 { width: 300px; height: 300px; background: var(--sky); top: 35%; right: 5%; animation-delay: -10s; }
.blob-4 { width: 350px; height: 350px; background: var(--purple); top: 60%; left: 10%; animation-delay: -15s; }
.dots-pattern {
position: absolute; inset: 0;
background-image: radial-gradient(circle, rgba(69,183,216,0.08) 1.5px, transparent 1.5px);
background-size: 28px 28px;
}
@keyframes floatBlob {
0%, 100% { transform: translate(0,0) scale(1); }
33% { transform: translate(40px,-30px) scale(1.08); }
66% { transform: translate(-30px,20px) scale(0.95); }
}
/* ==================== Container ==================== */
.container { max-width: 1400px; margin: 0 auto; padding: 0 24px; position: relative; z-index: 1; }
/* ==================== Header ==================== */
.header {
position: sticky; top: 0; z-index: 100;
background: rgba(255,255,255,0.75);
backdrop-filter: blur(20px) saturate(180%);
-webkit-backdrop-filter: blur(20px) saturate(180%);
border-bottom: 1px solid rgba(255,255,255,0.6);
transition: box-shadow 0.3s var(--ease-out);
}
.header.scrolled { box-shadow: 0 2px 20px rgba(0,0,0,0.06); }
.header-inner {
max-width: 1400px; margin: 0 auto; padding: 14px 24px;
display: flex; align-items: center; gap: 32px;
}
.logo {
font-size: 26px; font-weight: 800; white-space: nowrap;
background: linear-gradient(135deg, var(--coral), var(--mint), var(--sky));
-webkit-background-clip: text; -webkit-text-fill-color: transparent;
background-clip: text;
letter-spacing: -0.5px;
}
.logo::before { content: "✦ "; -webkit-text-fill-color: initial; }
.nav { display: flex; gap: 6px; }
.nav-link {
padding: 8px 18px; border-radius: var(--radius-pill);
font-size: 15px; font-weight: 500; color: var(--text-mid);
transition: all 0.3s var(--bounce);
position: relative;
}
.nav-link:hover { background: rgba(78,205,196,0.12); color: var(--mint); transform: translateY(-2px); }
.nav-link.active {
background: linear-gradient(135deg, var(--coral), var(--pink));
color: #fff; box-shadow: 0 4px 12px rgba(255,107,107,0.35);
}
.header-search { margin-left: auto; display: flex; align-items: center; gap: 8px; }
.search-pill-wrap {
display: flex; align-items: center; gap: 8px;
background: rgba(255,255,255,0.9);
border: 2px solid rgba(78,205,196,0.2);
border-radius: var(--radius-pill);
padding: 6px 6px 6px 18px;
transition: all 0.3s var(--bounce);
}
.search-pill-wrap:focus-within {
border-color: var(--mint);
box-shadow: 0 4px 16px rgba(78,205,196,0.2);
transform: scale(1.02);
}
.search-pill-wrap svg { width: 18px; height: 18px; color: var(--text-light); flex-shrink: 0; }
.search-pill {
border: none; background: transparent; font-size: 14px; width: 200px;
color: var(--text-dark);
}
.search-pill::placeholder { color: var(--text-light); }
.search-go {
padding: 8px 20px; border-radius: var(--radius-pill); font-size: 14px; font-weight: 600;
background: linear-gradient(135deg, var(--mint), var(--sky));
color: #fff; transition: all 0.3s var(--bounce);
}
.search-go:hover { transform: scale(1.08); box-shadow: 0 6px 16px rgba(78,205,196,0.4); }
.search-go:active { transform: scale(0.95); }
/* ==================== 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-mid);
border: 1px solid rgba(78,205,196,0.2); background: rgba(255,255,255,0.8);
transition: all .25s ease;
}
.lang-btn:hover { background: rgba(78,205,196,0.12); color: var(--mint); border-color: var(--mint); }
.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 rgba(78,205,196,0.2); border-radius: var(--radius-md);
box-shadow: var(--shadow-hover); 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-mid);
cursor: pointer; transition: all .15s;
}
.lang-option:hover { background: rgba(78,205,196,0.1); color: var(--text-dark); }
.lang-option.active { color: var(--mint); font-weight: 600; }
.lang-option .check { width: 16px; height: 16px; opacity: 0; transition: opacity .2s; }
.lang-option.active .check { opacity: 1; }
/* ==================== Hero ==================== */
.hero {
position: relative; max-width: 1400px; margin: 32px auto; padding: 0 24px; z-index: 1;
}
.hero-banner {
position: relative; border-radius: var(--radius-xl); overflow: hidden;
padding: 72px 48px; text-align: center;
background: linear-gradient(135deg, #667eea 0%, #764ba2 30%, #f093fb 60%, #f5576c 100%);
}
.hero-banner::before {
content: ""; position: absolute; inset: 0;
background: radial-gradient(circle at 20% 30%, rgba(255,255,255,0.15) 0%, transparent 40%),
radial-gradient(circle at 80% 70%, rgba(255,255,255,0.1) 0%, transparent 40%);
}
.hero-blob {
position: absolute; border-radius: 50%; filter: blur(40px); opacity: 0.35;
animation: floatBlob 12s ease-in-out infinite;
}
.hero-blob-1 { width: 200px; height: 200px; background: #fff; top: -60px; right: 10%; }
.hero-blob-2 { width: 150px; height: 150px; background: #4ecdc4; bottom: -40px; left: 15%; animation-delay: -4s; }
.hero-shape {
position: absolute; opacity: 0.15; animation: floatBlob 10s ease-in-out infinite;
}
.hero-shape-1 { width: 60px; height: 60px; border-radius: 30%; background: #fff; top: 20%; left: 8%; transform: rotate(20deg); }
.hero-shape-2 { width: 40px; height: 40px; border-radius: 50%; background: #ffd700; top: 60%; right: 12%; animation-delay: -3s; }
.hero-shape-3 { width: 80px; height: 12px; border-radius: 6px; background: #fff; bottom: 15%; right: 25%; transform: rotate(-15deg); animation-delay: -6s; }
.hero-content { position: relative; z-index: 2; }
.hero-badge {
display: inline-block; padding: 6px 20px; border-radius: var(--radius-pill);
background: rgba(255,255,255,0.2); backdrop-filter: blur(10px);
color: #fff; font-size: 14px; font-weight: 600; margin-bottom: 20px;
border: 1px solid rgba(255,255,255,0.3);
}
.hero-title {
font-size: 48px; font-weight: 800; color: #fff; line-height: 1.3; margin-bottom: 16px;
text-shadow: 0 2px 20px rgba(0,0,0,0.15); letter-spacing: -1px;
}
.hero-subtitle { font-size: 18px; color: rgba(255,255,255,0.9); margin-bottom: 36px; max-width: 560px; margin-left: auto; margin-right: auto; }
.hero-btn {
display: inline-flex; align-items: center; gap: 10px;
padding: 16px 40px; border-radius: var(--radius-pill);
background: #fff; color: var(--coral); font-size: 18px; font-weight: 700;
transition: all 0.4s var(--bounce);
box-shadow: 0 8px 28px rgba(0,0,0,0.2);
}
.hero-btn:hover { transform: translateY(-4px) scale(1.05); box-shadow: 0 14px 40px rgba(0,0,0,0.25); }
.hero-btn:active { transform: translateY(-2px) scale(1.02); }
.hero-btn svg { width: 22px; height: 22px; }
.hero-stats { display: flex; justify-content: center; gap: 48px; margin-top: 40px; }
.hero-stat { text-align: center; }
.hero-stat-num { font-size: 32px; font-weight: 800; color: #fff; }
.hero-stat-label { font-size: 13px; color: rgba(255,255,255,0.8); margin-top: 4px; }
/* ==================== Section Title ==================== */
.section { padding: 48px 0; position: relative; z-index: 1; }
.section-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 32px; }
.section-title-wrap { display: flex; align-items: center; gap: 12px; }
.section-icon {
width: 40px; height: 40px; border-radius: var(--radius-sm);
display: flex; align-items: center; justify-content: center; font-size: 20px;
color: #fff; flex-shrink: 0;
}
.section-title { font-size: 28px; font-weight: 800; color: var(--text-dark); letter-spacing: -0.5px; }
.section-subtitle { font-size: 14px; color: var(--text-light); margin-top: 4px; }
.section-more {
font-size: 14px; font-weight: 600; color: var(--mint);
display: flex; align-items: center; gap: 4px; transition: all 0.3s var(--bounce);
}
.section-more:hover { gap: 8px; color: var(--sky); }
/* ==================== Category Pills ==================== */
.categories-section { padding: 32px 0 16px; position: relative; z-index: 1; }
.categories-wrap {
display: flex; flex-wrap: wrap; gap: 12px; justify-content: center;
}
.cat-pill {
padding: 10px 24px; border-radius: var(--radius-pill);
font-size: 15px; font-weight: 600; cursor: pointer;
background: var(--card-bg); color: var(--text-mid);
border: 2px solid transparent;
transition: all 0.35s var(--bounce);
box-shadow: var(--shadow-soft);
display: inline-flex; align-items: center; gap: 8px;
}
.cat-pill:hover { transform: translateY(-4px) scale(1.05); box-shadow: var(--shadow-hover); }
.cat-pill.active { color: #fff; border-color: rgba(255,255,255,0.3); transform: translateY(-2px); }
.cat-pill .cat-emoji { font-size: 18px; }
/* ==================== App Card (shared) ==================== */
.app-card {
background: var(--card-bg); border-radius: var(--radius-lg);
padding: 20px; box-shadow: var(--shadow-soft);
transition: all 0.4s var(--bounce); cursor: pointer;
position: relative; overflow: hidden;
display: flex; flex-direction: column;
}
.app-card::after {
content: ""; position: absolute; top: 0; right: 0;
width: 80px; height: 80px; border-radius: 50%;
background: var(--card-accent, var(--mint)); opacity: 0;
filter: blur(30px); transition: opacity 0.4s; pointer-events: none;
transform: translate(30px,-30px);
}
.app-card:hover {
transform: translateY(-10px) rotate(-1deg);
box-shadow: var(--shadow-hover);
}
.app-card:hover::after { opacity: 0.15; }
.app-card:active { transform: translateY(-4px) rotate(-0.5deg); }
.app-icon-wrap {
width: 64px; height: 64px; border-radius: 18px;
display: flex; align-items: center; justify-content: center;
font-size: 28px; font-weight: 700; color: #fff;
flex-shrink: 0; position: relative; overflow: hidden;
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
}
.app-icon-wrap::after {
content: ""; position: absolute; inset: 0;
background: linear-gradient(135deg, rgba(255,255,255,0.3) 0%, transparent 50%);
}
.app-icon-img { width: 100%; height: 100%; object-fit: cover; border-radius: 18px; }
.app-card-top { display: flex; gap: 14px; align-items: flex-start; margin-bottom: 12px; }
.app-card-info { flex: 1; min-width: 0; }
.app-card-name {
font-size: 16px; font-weight: 700; color: var(--text-dark);
white-space: nowrap; overflow: hidden; text-overflow: ellipsis; margin-bottom: 2px;
}
.app-card-dev {
font-size: 12px; color: var(--text-light);
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.app-card-desc {
font-size: 13px; color: var(--text-mid); line-height: 1.5;
display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
overflow: hidden; margin-bottom: 12px; flex: 1;
}
.app-card-bottom {
display: flex; align-items: center; justify-content: space-between; margin-top: auto;
}
.app-card-meta { display: flex; flex-direction: column; gap: 2px; }
.stars { display: flex; gap: 1px; font-size: 13px; line-height: 1; }
.star-full { color: #ffc94d; }
.star-half {
color: #ffc94d;
background: linear-gradient(90deg, #ffc94d 50%, #e2e8f0 50%);
-webkit-background-clip: text; -webkit-text-fill-color: transparent;
background-clip: text;
}
.star-empty { color: #e2e8f0; }
.app-card-rating-text { font-size: 12px; color: var(--text-light); margin-top: 2px; }
.app-card-downloads { font-size: 11px; color: var(--text-light); }
.btn-download {
padding: 8px 18px; border-radius: var(--radius-pill);
font-size: 13px; font-weight: 600; color: #fff;
background: linear-gradient(135deg, var(--mint), var(--sky));
transition: all 0.3s var(--bounce); white-space: nowrap;
box-shadow: 0 3px 10px rgba(78,205,196,0.3);
}
.btn-download:hover { transform: scale(1.1); box-shadow: 0 6px 16px rgba(78,205,196,0.4); }
.btn-download:active { transform: scale(0.95); }
.btn-download.coral { background: linear-gradient(135deg, var(--coral), var(--pink)); box-shadow: 0 3px 10px rgba(255,107,107,0.3); }
.btn-download.coral:hover { box-shadow: 0 6px 16px rgba(255,107,107,0.4); }
/* ==================== Featured Section ==================== */
.featured-grid {
display: grid; grid-template-columns: repeat(3, 1fr); gap: 24px;
}
.featured-card {
background: var(--card-bg); border-radius: var(--radius-xl);
overflow: hidden; box-shadow: var(--shadow-soft);
transition: all 0.4s var(--bounce); cursor: pointer; position: relative;
}
.featured-card:hover { transform: translateY(-12px) rotate(-1.5deg); box-shadow: var(--shadow-hover); }
.featured-card-top {
padding: 28px 24px 20px; position: relative; overflow: hidden;
}
.featured-card-top::before {
content: ""; position: absolute; inset: 0; opacity: 0.08;
background: var(--card-grad, linear-gradient(135deg, var(--coral), var(--mint)));
}
.featured-card-top-inner { position: relative; z-index: 1; display: flex; gap: 16px; align-items: center; }
.featured-card .app-icon-wrap { width: 72px; height: 72px; border-radius: 20px; font-size: 32px; }
.featured-card-name { font-size: 20px; font-weight: 800; color: var(--text-dark); margin-bottom: 4px; }
.featured-card-dev { font-size: 13px; color: var(--text-light); }
.featured-card-body { padding: 0 24px 20px; }
.featured-card-desc {
font-size: 14px; color: var(--text-mid); line-height: 1.6;
display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
margin-bottom: 16px;
}
.featured-card-tags { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 16px; }
.tag-pill {
padding: 4px 12px; border-radius: var(--radius-pill);
font-size: 12px; font-weight: 600;
}
.featured-card-footer {
display: flex; align-items: center; justify-content: space-between;
padding: 0 24px 24px;
}
.featured-card-stats { display: flex; gap: 20px; }
.featured-stat { display: flex; flex-direction: column; }
.featured-stat-val { font-size: 18px; font-weight: 700; color: var(--text-dark); }
.featured-stat-label { font-size: 11px; color: var(--text-light); }
.featured-card .btn-download { padding: 10px 24px; font-size: 14px; }
/* ==================== New Section ==================== */
.new-scroll-wrap {
display: flex; gap: 20px; overflow-x: auto; padding: 8px 4px 20px;
scroll-snap-type: x mandatory;
-webkit-overflow-scrolling: touch;
}
.new-scroll-wrap::-webkit-scrollbar { height: 8px; }
.new-scroll-wrap::-webkit-scrollbar-track { background: rgba(0,0,0,0.04); border-radius: 4px; }
.new-scroll-wrap::-webkit-scrollbar-thumb { background: var(--mint); border-radius: 4px; }
.new-card {
flex: 0 0 280px; scroll-snap-align: start;
background: var(--card-bg); border-radius: var(--radius-lg);
padding: 20px; box-shadow: var(--shadow-soft);
transition: all 0.4s var(--bounce); cursor: pointer; position: relative;
}
.new-card:hover { transform: translateY(-8px) rotate(1deg); box-shadow: var(--shadow-hover); }
.new-badge {
position: absolute; top: 14px; right: 14px;
padding: 4px 12px; border-radius: var(--radius-pill);
background: linear-gradient(135deg, var(--coral), #ff8787);
color: #fff; font-size: 11px; font-weight: 700; letter-spacing: 0.5px;
box-shadow: 0 3px 10px rgba(255,107,107,0.3);
animation: pulse 2s ease-in-out infinite;
}
@keyframes pulse {
0%, 100% { transform: scale(1); }
50% { transform: scale(1.08); }
}
/* ==================== Library Section ==================== */
.library-toolbar {
display: flex; flex-wrap: wrap; gap: 16px; align-items: center; margin-bottom: 28px;
background: var(--card-bg); padding: 16px 20px; border-radius: var(--radius-lg);
box-shadow: var(--shadow-soft);
}
.library-search {
display: flex; align-items: center; gap: 8px;
background: rgba(240,244,255,0.8); border-radius: var(--radius-pill);
padding: 8px 18px; flex: 1; min-width: 200px;
border: 2px solid transparent; transition: all 0.3s var(--bounce);
}
.library-search:focus-within { border-color: var(--mint); background: #fff; }
.library-search svg { width: 18px; height: 18px; color: var(--text-light); flex-shrink: 0; }
.library-search input { border: none; background: transparent; font-size: 14px; width: 100%; color: var(--text-dark); }
.library-search input::placeholder { color: var(--text-light); }
.sort-select {
padding: 10px 20px; border-radius: var(--radius-pill); font-size: 14px; font-weight: 600;
background: linear-gradient(135deg, rgba(151,117,250,0.1), rgba(78,205,196,0.1));
color: var(--text-dark); border: 2px solid rgba(151,117,250,0.15);
cursor: pointer; transition: all 0.3s var(--bounce);
appearance: none; -webkit-appearance: none;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%239775fa' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E");
background-repeat: no-repeat; background-position: right 16px center;
padding-right: 40px;
}
.sort-select:hover { border-color: var(--purple); transform: translateY(-2px); }
.sort-select option { background: #fff; color: var(--text-dark); }
.library-count { font-size: 14px; color: var(--text-light); }
.library-count strong { color: var(--text-dark); }
.app-grid {
display: grid; grid-template-columns: repeat(5, 1fr); gap: 20px;
}
/* ==================== Skeleton ==================== */
.skeleton-card {
background: var(--card-bg); border-radius: var(--radius-lg); padding: 20px;
box-shadow: var(--shadow-soft);
}
.skel-shimmer {
background: linear-gradient(90deg, #f0f0f5 0%, #e8e8f0 30%, #f5f5fa 50%, #e8e8f0 70%, #f0f0f5 100%);
background-size: 200% 100%; animation: shimmer 1.5s infinite linear;
border-radius: 8px;
}
@keyframes shimmer { 0% { background-position: 200% 0; } 100% { background-position: -200% 0; } }
.skel-icon { width: 64px; height: 64px; border-radius: 18px; }
.skel-line { height: 12px; margin-bottom: 8px; }
.skel-line.short { width: 60%; }
.skel-line.mid { width: 80%; }
/* ==================== Empty State ==================== */
.empty-state {
grid-column: 1 / -1; text-align: center; padding: 60px 20px;
color: var(--text-light);
}
.empty-state-icon { font-size: 56px; margin-bottom: 16px; opacity: 0.5; }
.empty-state-text { font-size: 16px; }
/* ==================== Pagination ==================== */
.pagination {
display: flex; justify-content: center; align-items: center; gap: 8px; margin-top: 36px;
}
.page-btn {
min-width: 40px; height: 40px; border-radius: var(--radius-sm);
background: var(--card-bg); color: var(--text-mid); font-size: 14px; font-weight: 600;
box-shadow: var(--shadow-soft); transition: all 0.3s var(--bounce);
display: flex; align-items: center; justify-content: center; padding: 0 12px;
}
.page-btn:hover { transform: translateY(-3px); box-shadow: var(--shadow-hover); color: var(--mint); }
.page-btn.active {
background: linear-gradient(135deg, var(--coral), var(--pink));
color: #fff; box-shadow: 0 4px 12px rgba(255,107,107,0.35);
}
.page-btn:disabled { opacity: 0.4; cursor: not-allowed; transform: none; }
.page-ellipsis { color: var(--text-light); padding: 0 4px; }
/* ==================== Modal ==================== */
.modal-overlay {
position: fixed; inset: 0; z-index: 1000;
background: rgba(45,55,72,0.4);
backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px);
display: flex; align-items: center; justify-content: center;
padding: 24px; opacity: 0; pointer-events: none;
transition: opacity 0.3s var(--ease-out);
}
.modal-overlay.active { opacity: 1; pointer-events: auto; }
.modal {
background: #fff; border-radius: var(--radius-xl);
max-width: 720px; width: 100%; max-height: 88vh; overflow-y: auto;
box-shadow: 0 24px 80px rgba(0,0,0,0.2);
transform: scale(0.8) translateY(40px); opacity: 0;
transition: all 0.5s var(--bounce);
}
.modal-overlay.active .modal { transform: scale(1) translateY(0); opacity: 1; }
.modal::-webkit-scrollbar { width: 8px; }
.modal::-webkit-scrollbar-track { background: transparent; }
.modal::-webkit-scrollbar-thumb { background: rgba(0,0,0,0.1); border-radius: 4px; }
.modal-header {
position: relative; padding: 32px 32px 24px; overflow: hidden;
}
.modal-header::before {
content: ""; position: absolute; inset: 0; opacity: 0.06;
background: var(--modal-grad, linear-gradient(135deg, var(--coral), var(--sky)));
}
.modal-header-inner { position: relative; z-index: 1; display: flex; gap: 20px; align-items: flex-start; }
.modal .app-icon-wrap { width: 88px; height: 88px; border-radius: 22px; font-size: 38px; }
.modal-app-name { font-size: 26px; font-weight: 800; color: var(--text-dark); margin-bottom: 6px; }
.modal-app-dev { font-size: 14px; color: var(--text-light); margin-bottom: 10px; }
.modal-rating-row { display: flex; align-items: center; gap: 8px; }
.modal-rating-num { font-size: 18px; font-weight: 700; color: #ffc94d; }
.modal-rating-text { font-size: 13px; color: var(--text-light); }
.modal-close {
position: absolute; top: 20px; right: 20px; z-index: 2;
width: 40px; height: 40px; border-radius: 50%;
background: rgba(255,255,255,0.9); color: var(--text-mid);
font-size: 22px; display: flex; align-items: center; justify-content: center;
transition: all 0.3s var(--bounce); box-shadow: var(--shadow-soft);
}
.modal-close:hover { transform: rotate(90deg) scale(1.1); background: var(--coral); color: #fff; }
.modal-body { padding: 0 32px 32px; }
.modal-section { margin-bottom: 24px; }
.modal-section-title {
font-size: 15px; font-weight: 700; color: var(--text-dark); margin-bottom: 12px;
display: flex; align-items: center; gap: 6px;
}
.modal-desc { font-size: 14px; color: var(--text-mid); line-height: 1.7; }
.modal-tags { display: flex; flex-wrap: wrap; gap: 8px; }
.modal-features { display: flex; flex-direction: column; gap: 10px; }
.modal-feature {
display: flex; align-items: center; gap: 10px; font-size: 14px; color: var(--text-mid);
}
.modal-feature-check {
width: 24px; height: 24px; border-radius: 50%; flex-shrink: 0;
display: flex; align-items: center; justify-content: center;
background: linear-gradient(135deg, var(--green), var(--mint)); color: #fff; font-size: 12px;
}
.modal-stats-grid {
display: grid; grid-template-columns: repeat(4, 1fr); gap: 12px;
}
.modal-stat-item {
background: rgba(240,244,255,0.6); border-radius: var(--radius-md);
padding: 14px; text-align: center;
}
.modal-stat-val { font-size: 16px; font-weight: 700; color: var(--text-dark); margin-bottom: 2px; }
.modal-stat-label { font-size: 12px; color: var(--text-light); }
.modal-screenshots { display: flex; gap: 12px; overflow-x: auto; padding-bottom: 8px; }
.modal-screenshots::-webkit-scrollbar { height: 6px; }
.modal-screenshots::-webkit-scrollbar-thumb { background: var(--mint); border-radius: 3px; }
.screenshot-item {
flex: 0 0 200px; height: 360px; border-radius: var(--radius-md);
overflow: hidden; box-shadow: var(--shadow-soft);
}
.screenshot-item img { width: 100%; height: 100%; object-fit: cover; }
.screenshot-placeholder {
width: 100%; height: 100%; display: flex; align-items: center; justify-content: center;
font-size: 14px; color: rgba(255,255,255,0.7); font-weight: 600;
}
.modal-download-btn {
width: 100%; padding: 16px; border-radius: var(--radius-md);
font-size: 17px; font-weight: 700; color: #fff;
background: linear-gradient(135deg, var(--coral), var(--pink), var(--purple));
background-size: 200% 100%; transition: all 0.4s var(--bounce);
box-shadow: 0 6px 20px rgba(255,107,107,0.3);
display: flex; align-items: center; justify-content: center; gap: 10px;
}
.modal-download-btn:hover {
transform: scale(1.03); background-position: 100% 0;
box-shadow: 0 10px 28px rgba(255,107,107,0.4);
}
.modal-download-btn:active { transform: scale(0.98); }
.modal-download-btn svg { width: 22px; height: 22px; }
.modal-loading { padding: 60px; text-align: center; color: var(--text-light); }
.modal-error { padding: 60px; text-align: center; color: var(--coral); }
/* ==================== Footer ==================== */
.footer {
margin-top: 60px; position: relative; z-index: 1;
background: linear-gradient(135deg, #2d3748 0%, #1a202c 100%);
color: rgba(255,255,255,0.7); overflow: hidden;
}
.footer::before {
content: ""; position: absolute; top: 0; left: 0; right: 0; height: 4px;
background: linear-gradient(90deg, var(--coral), var(--amber), var(--green), var(--mint), var(--sky), var(--purple));
}
.footer-inner {
max-width: 1400px; margin: 0 auto; padding: 48px 24px 24px;
display: grid; grid-template-columns: 2fr 1fr 1fr 1fr; gap: 40px;
}
.footer-brand .footer-logo {
font-size: 26px; font-weight: 800; margin-bottom: 12px;
background: linear-gradient(135deg, var(--coral), var(--mint), var(--sky));
-webkit-background-clip: text; -webkit-text-fill-color: transparent;
background-clip: text;
}
.footer-brand p { font-size: 14px; line-height: 1.7; max-width: 300px; }
.footer-col h4 { font-size: 16px; font-weight: 700; color: #fff; margin-bottom: 16px; }
.footer-col a { display: block; font-size: 14px; padding: 6px 0; transition: all 0.3s var(--ease-out); }
.footer-col a:hover { color: var(--mint); transform: translateX(4px); }
.footer-bottom {
max-width: 1400px; margin: 0 auto; padding: 20px 24px;
border-top: 1px solid rgba(255,255,255,0.1);
text-align: center; font-size: 13px; color: rgba(255,255,255,0.5);
}
/* ==================== Scroll to Top ==================== */
.scroll-top {
position: fixed; bottom: 32px; right: 32px; z-index: 200;
width: 52px; height: 52px; border-radius: 50%;
background: linear-gradient(135deg, var(--mint), var(--sky));
color: #fff; font-size: 24px;
display: flex; align-items: center; justify-content: center;
box-shadow: 0 8px 24px rgba(78,205,196,0.4);
opacity: 0; pointer-events: none; transform: scale(0.5);
transition: all 0.4s var(--bounce);
}
.scroll-top.show { opacity: 1; pointer-events: auto; transform: scale(1); }
.scroll-top:hover { transform: scale(1.15) rotate(-5deg); }
/* ==================== Toast ==================== */
.toast-container { position: fixed; top: 80px; right: 32px; z-index: 2000; display: flex; flex-direction: column; gap: 12px; }
.toast {
padding: 14px 24px; border-radius: var(--radius-md); font-size: 14px; font-weight: 600;
color: #fff; box-shadow: 0 8px 28px rgba(0,0,0,0.15);
transform: translateX(120%); opacity: 0; transition: all 0.4s var(--bounce);
display: flex; align-items: center; gap: 8px;
}
.toast.show { transform: translateX(0); opacity: 1; }
.toast-success { background: linear-gradient(135deg, var(--green), var(--mint)); }
.toast-error { background: linear-gradient(135deg, var(--coral), #ff8787); }
.toast-info { background: linear-gradient(135deg, var(--sky), var(--purple)); }
/* ==================== Reveal Animation ==================== */
.reveal {
opacity: 0; transform: translateY(40px) scale(0.96);
transition: all 0.7s var(--bounce);
}
.reveal.visible { opacity: 1; transform: translateY(0) scale(1); }
.reveal:nth-child(2) { transition-delay: 0.08s; }
.reveal:nth-child(3) { transition-delay: 0.16s; }
.reveal:nth-child(4) { transition-delay: 0.24s; }
.reveal:nth-child(5) { transition-delay: 0.32s; }
/* ==================== Responsive ==================== */
@media (max-width: 1200px) {
.app-grid { grid-template-columns: repeat(3, 1fr); }
.featured-grid { grid-template-columns: repeat(2, 1fr); }
.footer-inner { grid-template-columns: 2fr 1fr 1fr; }
.footer-col:last-child { display: none; }
}
@media (max-width: 900px) {
.hero-title { font-size: 36px; }
.hero-stats { gap: 28px; }
.section-title { font-size: 24px; }
.nav { display: none; }
.search-pill { width: 140px; }
}
@media (max-width: 768px) {
.app-grid { grid-template-columns: repeat(2, 1fr); }
.featured-grid { grid-template-columns: 1fr; }
.hero-banner { padding: 48px 24px; }
.hero-title { font-size: 28px; }
.hero-subtitle { font-size: 15px; }
.hero-stats { gap: 20px; }
.hero-stat-num { font-size: 24px; }
.header-inner { gap: 16px; padding: 12px 16px; }
.logo { font-size: 22px; }
.header-search { flex: 1; }
.search-pill { width: 100%; }
.container { padding: 0 16px; }
.section { padding: 32px 0; }
.library-toolbar { flex-direction: column; align-items: stretch; }
.modal-stats-grid { grid-template-columns: repeat(2, 1fr); }
.footer-inner { grid-template-columns: 1fr 1fr; gap: 24px; }
.footer-brand { grid-column: 1 / -1; }
.scroll-top { bottom: 20px; right: 20px; width: 44px; height: 44px; font-size: 20px; }
.toast-container { right: 16px; left: 16px; top: 70px; }
.toast { transform: translateY(-120%); }
.toast.show { transform: translateY(0); }
}
@media (max-width: 480px) {
.app-grid { grid-template-columns: 1fr; }
.hero-title { font-size: 24px; }
.hero-btn { padding: 14px 28px; font-size: 16px; }
.modal { border-radius: var(--radius-lg); }
.modal-header { padding: 24px 20px 16px; }
.modal-body { padding: 0 20px 20px; }
.modal .app-icon-wrap { width: 64px; height: 64px; border-radius: 18px; font-size: 28px; }
.modal-app-name { font-size: 20px; }
}
</style>
</head>
<body>
<!-- ==================== Background Decorations ==================== -->
<div class="bg-decor">
<div class="blob blob-1"></div>
<div class="blob blob-2"></div>
<div class="blob blob-3"></div>
<div class="blob blob-4"></div>
<div class="dots-pattern"></div>
</div>
<!-- ==================== Header ==================== -->
<header class="header" id="header">
<div class="header-inner">
<a href="#hero" class="logo" data-i18n="logo_name">应用集市</a>
<nav class="nav">
<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_discover">发现</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="header-search">
<div class="search-pill-wrap">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>
<input type="text" id="header-search" class="search-pill" data-i18n-placeholder="search_placeholder" placeholder="搜索应用...">
</div>
<button class="search-go" id="header-search-btn" data-i18n="search_btn">搜索</button>
</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" id="hero">
<div class="hero-banner">
<div class="hero-blob hero-blob-1"></div>
<div class="hero-blob hero-blob-2"></div>
<div class="hero-shape hero-shape-1"></div>
<div class="hero-shape hero-shape-2"></div>
<div class="hero-shape hero-shape-3"></div>
<div class="hero-content">
<span class="hero-badge" data-i18n="hero_badge">✨ 全新体验 · 海量应用</span>
<h1 class="hero-title" data-i18n="hero_title">发现好应用,点亮新生活</h1>
<p class="hero-subtitle" data-i18n="hero_subtitle">精选优质应用,覆盖生活方方面面,让你的每一天都更加高效、有趣、美好</p>
<button class="hero-btn" id="hero-explore-btn">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14M12 5l7 7-7 7"/></svg>
<span data-i18n="hero_explore">开始探索</span>
</button>
<div class="hero-stats">
<div class="hero-stat">
<div class="hero-stat-num" id="stat-apps">10万+</div>
<div class="hero-stat-label" data-i18n="stat_quality_apps">优质应用</div>
</div>
<div class="hero-stat">
<div class="hero-stat-num" id="stat-downloads">5亿+</div>
<div class="hero-stat-label" data-i18n="stat_total_downloads">累计下载</div>
</div>
<div class="hero-stat">
<div class="hero-stat-num" id="stat-users">800万+</div>
<div class="hero-stat-label" data-i18n="stat_active_users">活跃用户</div>
</div>
</div>
</div>
</div>
</section>
<!-- ==================== Categories ==================== -->
<section class="categories-section">
<div class="container">
<div class="section-header">
<div class="section-title-wrap">
<div class="section-icon" style="background:linear-gradient(135deg,var(--amber),var(--coral))">🏷</div>
<div>
<h2 class="section-title" data-i18n="section_categories">应用分类</h2>
<p class="section-subtitle" data-i18n="section_categories_sub">找到你感兴趣的领域</p>
</div>
</div>
</div>
<div class="categories-wrap" id="categories-wrap">
<div class="skel-shimmer" style="width:120px;height:42px;border-radius:999px"></div>
<div class="skel-shimmer" style="width:100px;height:42px;border-radius:999px"></div>
<div class="skel-shimmer" style="width:110px;height:42px;border-radius:999px"></div>
<div class="skel-shimmer" style="width:90px;height:42px;border-radius:999px"></div>
<div class="skel-shimmer" style="width:130px;height:42px;border-radius:999px"></div>
</div>
</div>
</section>
<!-- ==================== Featured ==================== -->
<section class="section" id="featured">
<div class="container">
<div class="section-header">
<div class="section-title-wrap">
<div class="section-icon" style="background:linear-gradient(135deg,var(--coral),var(--pink))">★</div>
<div>
<h2 class="section-title" data-i18n="section_featured">精选好应用</h2>
<p class="section-subtitle" data-i18n="section_featured_sub">编辑团队精心挑选</p>
</div>
</div>
<a href="#library" class="section-more" data-target="library" data-i18n="see_all">查看全部 →</a>
</div>
<div class="featured-grid" id="featured-grid">
<div class="skeleton-card"><div style="display:flex;gap:16px;margin-bottom:16px"><div class="skel-shimmer skel-icon" style="width:72px;height:72px;border-radius:20px"></div><div style="flex:1"><div class="skel-shimmer skel-line" style="width:60%;height:16px"></div><div class="skel-shimmer skel-line short" style="margin-top:8px"></div></div></div><div class="skel-shimmer skel-line mid"></div><div class="skel-shimmer skel-line mid"></div></div>
<div class="skeleton-card"><div style="display:flex;gap:16px;margin-bottom:16px"><div class="skel-shimmer skel-icon" style="width:72px;height:72px;border-radius:20px"></div><div style="flex:1"><div class="skel-shimmer skel-line" style="width:60%;height:16px"></div><div class="skel-shimmer skel-line short" style="margin-top:8px"></div></div></div><div class="skel-shimmer skel-line mid"></div><div class="skel-shimmer skel-line mid"></div></div>
<div class="skeleton-card"><div style="display:flex;gap:16px;margin-bottom:16px"><div class="skel-shimmer skel-icon" style="width:72px;height:72px;border-radius:20px"></div><div style="flex:1"><div class="skel-shimmer skel-line" style="width:60%;height:16px"></div><div class="skel-shimmer skel-line short" style="margin-top:8px"></div></div></div><div class="skel-shimmer skel-line mid"></div><div class="skel-shimmer skel-line mid"></div></div>
</div>
</div>
</section>
<!-- ==================== New Apps ==================== -->
<section class="section" id="new">
<div class="container">
<div class="section-header">
<div class="section-title-wrap">
<div class="section-icon" style="background:linear-gradient(135deg,var(--green),var(--mint))">✦</div>
<div>
<h2 class="section-title" data-i18n="section_new">新鲜上架</h2>
<p class="section-subtitle" data-i18n="section_new_sub">最新上架的优质应用</p>
</div>
</div>
<a href="#library" class="section-more" data-target="library" data-i18n="see_all">查看全部 →</a>
</div>
<div class="new-scroll-wrap" id="new-scroll-wrap">
<div class="skeleton-card" style="flex:0 0 280px"><div class="skel-shimmer skel-icon"></div><div class="skel-shimmer skel-line" style="width:60%;height:16px;margin-top:12px"></div><div class="skel-shimmer skel-line short" style="margin-top:8px"></div><div class="skel-shimmer skel-line mid" style="margin-top:12px"></div></div>
<div class="skeleton-card" style="flex:0 0 280px"><div class="skel-shimmer skel-icon"></div><div class="skel-shimmer skel-line" style="width:60%;height:16px;margin-top:12px"></div><div class="skel-shimmer skel-line short" style="margin-top:8px"></div><div class="skel-shimmer skel-line mid" style="margin-top:12px"></div></div>
<div class="skeleton-card" style="flex:0 0 280px"><div class="skel-shimmer skel-icon"></div><div class="skel-shimmer skel-line" style="width:60%;height:16px;margin-top:12px"></div><div class="skel-shimmer skel-line short" style="margin-top:8px"></div><div class="skel-shimmer skel-line mid" style="margin-top:12px"></div></div>
<div class="skeleton-card" style="flex:0 0 280px"><div class="skel-shimmer skel-icon"></div><div class="skel-shimmer skel-line" style="width:60%;height:16px;margin-top:12px"></div><div class="skel-shimmer skel-line short" style="margin-top:8px"></div><div class="skel-shimmer skel-line mid" style="margin-top:12px"></div></div>
</div>
</div>
</section>
<!-- ==================== Library ==================== -->
<section class="section" id="library">
<div class="container">
<div class="section-header">
<div class="section-title-wrap">
<div class="section-icon" style="background:linear-gradient(135deg,var(--sky),var(--purple))">⊞</div>
<div>
<h2 class="section-title" data-i18n="section_library">应用大全</h2>
<p class="section-subtitle" data-i18n="section_library_sub">探索所有应用</p>
</div>
</div>
</div>
<div class="library-toolbar">
<div class="library-search">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>
<input type="text" id="library-search" data-i18n-placeholder="search_lib" placeholder="搜索应用名称...">
</div>
<select class="sort-select" id="sort-select">
<option value="" data-i18n="sort_default">默认排序</option>
<option value="rating" data-i18n="sort_rating">评分最高</option>
<option value="downloads" data-i18n="sort_downloads">下载最多</option>
<option value="newest" data-i18n="sort_newest">最新上架</option>
</select>
<div class="library-count" id="library-count">加载中...</div>
</div>
<div class="app-grid" id="app-grid">
<div class="skeleton-card"><div style="display:flex;gap:14px;margin-bottom:12px"><div class="skel-shimmer skel-icon"></div><div style="flex:1"><div class="skel-shimmer skel-line" style="width:70%"></div><div class="skel-shimmer skel-line short" style="margin-top:6px"></div></div></div><div class="skel-shimmer skel-line mid"></div><div class="skel-shimmer skel-line mid"></div></div>
<div class="skeleton-card"><div style="display:flex;gap:14px;margin-bottom:12px"><div class="skel-shimmer skel-icon"></div><div style="flex:1"><div class="skel-shimmer skel-line" style="width:70%"></div><div class="skel-shimmer skel-line short" style="margin-top:6px"></div></div></div><div class="skel-shimmer skel-line mid"></div><div class="skel-shimmer skel-line mid"></div></div>
<div class="skeleton-card"><div style="display:flex;gap:14px;margin-bottom:12px"><div class="skel-shimmer skel-icon"></div><div style="flex:1"><div class="skel-shimmer skel-line" style="width:70%"></div><div class="skel-shimmer skel-line short" style="margin-top:6px"></div></div></div><div class="skel-shimmer skel-line mid"></div><div class="skel-shimmer skel-line mid"></div></div>
<div class="skeleton-card"><div style="display:flex;gap:14px;margin-bottom:12px"><div class="skel-shimmer skel-icon"></div><div style="flex:1"><div class="skel-shimmer skel-line" style="width:70%"></div><div class="skel-shimmer skel-line short" style="margin-top:6px"></div></div></div><div class="skel-shimmer skel-line mid"></div><div class="skel-shimmer skel-line mid"></div></div>
<div class="skeleton-card"><div style="display:flex;gap:14px;margin-bottom:12px"><div class="skel-shimmer skel-icon"></div><div style="flex:1"><div class="skel-shimmer skel-line" style="width:70%"></div><div class="skel-shimmer skel-line short" style="margin-top:6px"></div></div></div><div class="skel-shimmer skel-line mid"></div><div class="skel-shimmer skel-line mid"></div></div>
</div>
<div class="pagination" id="pagination"></div>
</div>
</section>
<!-- ==================== Modal ==================== -->
<div class="modal-overlay" id="modal-overlay">
<div class="modal" id="modal">
<div class="modal-loading" id="modal-loading" data-i18n="modal_loading">加载中...</div>
</div>
</div>
<!-- ==================== Footer ==================== -->
<footer class="footer">
<div class="footer-inner">
<div class="footer-brand">
<div class="footer-logo" data-i18n="footer_logo">应用集市</div>
<p data-i18n="footer_desc">发现好应用,点亮新生活。致力于为用户提供优质、安全、便捷的应用下载服务。</p>
</div>
<div class="footer-col">
<h4 data-i18n="footer_about">关于我们</h4>
<a href="#" data-i18n="footer_about_intro">公司介绍</a>
<a href="#" data-i18n="footer_join_us">加入我们</a>
<a href="#" data-i18n="footer_contact">联系方式</a>
<a href="#" data-i18n="footer_privacy">隐私政策</a>
</div>
<div class="footer-col">
<h4 data-i18n="footer_help">帮助中心</h4>
<a href="#" data-i18n="footer_faq">常见问题</a>
<a href="#" data-i18n="footer_guide">使用指南</a>
<a href="#" data-i18n="footer_feedback">意见反馈</a>
<a href="#" data-i18n="footer_complaint">投诉建议</a>
</div>
<div class="footer-col">
<h4 data-i18n="footer_partner">合作伙伴</h4>
<a href="#" data-i18n="footer_dev_join">开发者入驻</a>
<a href="#" data-i18n="footer_ad">广告合作</a>
<a href="#" data-i18n="footer_biz">商务洽谈</a>
<a href="#" data-i18n="footer_platform">开放平台</a>
</div>
</div>
<div class="footer-bottom">
<p data-i18n="footer_copyright">© 2024 应用集市 · All rights reserved · 仅供学习交流使用</p>
</div>
</footer>
<!-- ==================== Scroll to Top ==================== -->
<button class="scroll-top" id="scroll-top" aria-label="回到顶部">↑</button>
<!-- ==================== Toast Container ==================== -->
<div class="toast-container" id="toast-container"></div>
<script>
(function() {
'use strict';
/* ==================== i18n ==================== */
var I18N_LOCALES = {
'zh-CN': {
logo_name: '应用集市',
nav_home: '首页',
nav_discover: '发现',
nav_featured: '精选',
nav_new: '最新',
search_placeholder: '搜索应用...',
search_btn: '搜索',
search_lib: '搜索应用名称...',
hero_badge: '✨ 全新体验 · 海量应用',
hero_title: '发现好应用,点亮新生活',
hero_subtitle: '精选优质应用,覆盖生活方方面面,让你的每一天都更加高效、有趣、美好',
hero_explore: '开始探索',
stat_quality_apps: '优质应用',
stat_total_downloads: '累计下载',
stat_active_users: '活跃用户',
section_categories: '应用分类',
section_categories_sub: '找到你感兴趣的领域',
section_featured: '精选好应用',
section_featured_sub: '编辑团队精心挑选',
section_new: '新鲜上架',
section_new_sub: '最新上架的优质应用',
section_library: '应用大全',
section_library_sub: '探索所有应用',
see_all: '查看全部 →',
cat_all: '全部',
sort_default: '默认排序',
sort_rating: '评分最高',
sort_downloads: '下载最多',
sort_newest: '最新上架',
empty_categories: '暂无分类数据',
empty_featured: '暂无精选应用',
empty_new: '暂无新上架应用',
empty_library: '没有找到相关应用,试试其他关键词吧',
loading: '加载中...',
modal_loading: '加载中...',
modal_desc: '📝 应用介绍',
modal_tags: '🏷 标签',
modal_features: '✨ 特色功能',
modal_screenshots: '🖼 应用截图',
modal_info: '📊 应用信息',
modal_downloads: '下载量',
modal_size: '大小',
modal_version: '版本',
modal_update: '更新日期',
modal_download: '立即下载',
modal_unknown_dev: '未知开发者',
modal_unknown_cat: '未分类',
modal_no_desc: '暂无描述',
modal_platform: '多平台',
modal_free: '免费',
modal_reviews: '评价',
download: '下载',
downloads_suffix: ' 下载',
footer_logo: '应用集市',
footer_desc: '发现好应用,点亮新生活。致力于为用户提供优质、安全、便捷的应用下载服务。',
footer_about: '关于我们',
footer_about_intro: '公司介绍',
footer_join_us: '加入我们',
footer_contact: '联系方式',
footer_privacy: '隐私政策',
footer_help: '帮助中心',
footer_faq: '常见问题',
footer_guide: '使用指南',
footer_feedback: '意见反馈',
footer_complaint: '投诉建议',
footer_partner: '合作伙伴',
footer_dev_join: '开发者入驻',
footer_ad: '广告合作',
footer_biz: '商务洽谈',
footer_platform: '开放平台',
footer_copyright: '\u00a9 2024 应用集市 \u00b7 All rights reserved \u00b7 仅供学习交流使用',
total_apps: '共 <strong>{n}</strong> 款应用'
},
'zh-TW': {
logo_name: '應用集市',
nav_home: '首頁',
nav_discover: '發現',
nav_featured: '精選',
nav_new: '最新',
search_placeholder: '搜尋應用...',
search_btn: '搜尋',
search_lib: '搜尋應用名稱...',
hero_badge: '✨ 全新體驗 · 海量應用',
hero_title: '發現好應用,點亮新生活',
hero_subtitle: '精選優質應用,覆蓋生活方方面面,讓你的每一天都更加高效、有趣、美好',
hero_explore: '開始探索',
stat_quality_apps: '優質應用',
stat_total_downloads: '累計下載',
stat_active_users: '活躍使用者',
section_categories: '應用分類',
section_categories_sub: '找到你感興趣的領域',
section_featured: '精選好應用',
section_featured_sub: '編輯團隊精心挑選',
section_new: '新鮮上架',
section_new_sub: '最新上架的優質應用',
section_library: '應用大全',
section_library_sub: '探索所有應用',
see_all: '查看全部 →',
cat_all: '全部',
sort_default: '預設排序',
sort_rating: '評分最高',
sort_downloads: '下載最多',
sort_newest: '最新上架',
empty_categories: '暫無分類資料',
empty_featured: '暫無精選應用',
empty_new: '暫無新上架應用',
empty_library: '沒有找到相關應用,試試其他關鍵詞吧',
loading: '載入中...',
modal_loading: '載入中...',
modal_desc: '📝 應用介紹',
modal_tags: '🏷 標籤',
modal_features: '✨ 特色功能',
modal_screenshots: '🖼 應用截圖',
modal_info: '📊 應用資訊',
modal_downloads: '下載量',
modal_size: '大小',
modal_version: '版本',
modal_update: '更新日期',
modal_download: '立即下載',
modal_unknown_dev: '未知開發者',
modal_unknown_cat: '未分類',
modal_no_desc: '暫無描述',
modal_platform: '多平台',
modal_free: '免費',
modal_reviews: '評價',
download: '下載',
downloads_suffix: ' 下載',
footer_logo: '應用集市',
footer_desc: '發現好應用,點亮新生活。致力於為使用者提供優質、安全、便捷的應用下載服務。',
footer_about: '關於我們',
footer_about_intro: '公司介紹',
footer_join_us: '加入我們',
footer_contact: '聯絡方式',
footer_privacy: '隱私政策',
footer_help: '幫助中心',
footer_faq: '常見問題',
footer_guide: '使用指南',
footer_feedback: '意見回饋',
footer_complaint: '投訴建議',
footer_partner: '合作夥伴',
footer_dev_join: '開發者入駐',
footer_ad: '廣告合作',
footer_biz: '商務洽談',
footer_platform: '開放平台',
footer_copyright: '\u00a9 2024 應用集市 \u00b7 All rights reserved \u00b7 僅供學習交流使用',
total_apps: '共 <strong>{n}</strong> 款應用'
},
'en': {
logo_name: 'App Bazaar',
nav_home: 'Home',
nav_discover: 'Discover',
nav_featured: 'Featured',
nav_new: 'New',
search_placeholder: 'Search apps...',
search_btn: 'Search',
search_lib: 'Search app name...',
hero_badge: '✨ All-New Experience · Tons of Apps',
hero_title: 'Discover Great Apps, Light Up Your Life',
hero_subtitle: 'Curated quality apps covering every aspect of life, making your days more efficient, fun, and beautiful',
hero_explore: 'Start Exploring',
stat_quality_apps: 'Quality Apps',
stat_total_downloads: 'Total Downloads',
stat_active_users: 'Active Users',
section_categories: 'Categories',
section_categories_sub: 'Find what interests you',
section_featured: 'Featured Apps',
section_featured_sub: 'Handpicked by our editorial team',
section_new: 'Fresh Picks',
section_new_sub: 'Latest quality apps just added',
section_library: 'All Apps',
section_library_sub: 'Explore all apps',
see_all: 'See All →',
cat_all: 'All',
sort_default: 'Default',
sort_rating: 'Top Rated',
sort_downloads: 'Most Downloaded',
sort_newest: 'Newest',
empty_categories: 'No categories available',
empty_featured: 'No featured apps',
empty_new: 'No new apps',
empty_library: 'No apps found, try different keywords',
loading: 'Loading...',
modal_loading: 'Loading...',
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_download: 'Download Now',
modal_unknown_dev: 'Unknown Developer',
modal_unknown_cat: 'Uncategorized',
modal_no_desc: 'No description available',
modal_platform: 'Multi-platform',
modal_free: 'Free',
modal_reviews: 'Reviews',
download: 'Get',
downloads_suffix: ' downloads',
footer_logo: 'App Bazaar',
footer_desc: 'Discover great apps, light up your life. Dedicated to providing quality, safe, and convenient app download services.',
footer_about: 'About Us',
footer_about_intro: 'Company Intro',
footer_join_us: 'Join Us',
footer_contact: 'Contact',
footer_privacy: 'Privacy Policy',
footer_help: 'Help Center',
footer_faq: 'FAQ',
footer_guide: 'User Guide',
footer_feedback: 'Feedback',
footer_complaint: 'Complaints',
footer_partner: 'Partners',
footer_dev_join: 'Developer Program',
footer_ad: 'Advertise',
footer_biz: 'Business',
footer_platform: 'Open Platform',
footer_copyright: '\u00a9 2024 App Bazaar \u00b7 All rights reserved \u00b7 For demonstration purposes only',
total_apps: '<strong>{n}</strong> apps total'
}
};
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('appbazaar_lang'); if (s && I18N_LOCALES[s]) return s; } catch(e) {}
var b = navigator.language || navigator.userLanguage || '';
if (b.startsWith('zh-TW') || b.startsWith('zh-Hant')) return 'zh-TW';
if (b.startsWith('zh')) return 'en';
return 'en';
})();
function t(key) {
return (I18N_LOCALES[currentLang] && I18N_LOCALES[currentLang][key]) || (I18N_LOCALES['zh-CN'][key]) || key;
}
function applyI18n() {
document.querySelectorAll('[data-i18n]').forEach(function(el) {
var key = el.getAttribute('data-i18n');
el.textContent = t(key);
});
document.querySelectorAll('[data-i18n-placeholder]').forEach(function(el) {
var key = el.getAttribute('data-i18n-placeholder');
el.placeholder = t(key);
});
document.documentElement.lang = currentLang === 'zh-TW' ? 'zh-TW' : currentLang === 'zh-CN' ? 'zh-CN' : 'en';
document.getElementById('langLabel').textContent = LANG_LABELS[currentLang] || currentLang;
document.querySelectorAll('.lang-option').forEach(function(opt) {
opt.classList.toggle('active', opt.getAttribute('data-lang') === currentLang);
});
}
window.switchLang = function(lang) {
if (!I18N_LOCALES[lang]) return;
currentLang = lang;
try { localStorage.setItem('appbazaar_lang', lang); } catch(e) {}
applyI18n();
if (typeof window.onLangChange === 'function') window.onLangChange(lang);
document.getElementById('langDropdown').classList.remove('open');
};
window.toggleLangDropdown = function() {
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');
});
// ==================== Config ====================
var API_BASE = '';
// ==================== Category Colors ====================
var CAT_COLORS = [
{ bg: 'linear-gradient(135deg,#ffe8e8,#ffd0d0)', text: '#ff6b6b', solid: '#ff6b6b', emoji: '📱' },
{ bg: 'linear-gradient(135deg,#e8faf5,#d0f5ed)', text: '#4ecdc4', solid: '#4ecdc4', emoji: '🎮' },
{ bg: 'linear-gradient(135deg,#e8f4fc,#d0ecfa)', text: '#45b7d8', solid: '#45b7d8', emoji: '🛒' },
{ bg: 'linear-gradient(135deg,#fff5e8,#ffe8d0)', text: '#ffa94d', solid: '#ffa94d', emoji: '📚' },
{ bg: 'linear-gradient(135deg,#f3e8ff,#e8d0ff)', text: '#9775fa', solid: '#9775fa', emoji: '🎨' },
{ bg: 'linear-gradient(135deg,#e8fff0,#d0fae0)', text: '#51cf66', solid: '#51cf66', emoji: '🏃' },
{ bg: 'linear-gradient(135deg,#ffe8f5,#ffd0e8)', text: '#f783ac', solid: '#f783ac', emoji: '🎵' },
{ bg: 'linear-gradient(135deg,#e8f8ff,#d0f0ff)', text: '#3bc9db', solid: '#3bc9db', emoji: '💼' },
{ bg: 'linear-gradient(135deg,#fff8e8,#ffedc0)', text: '#fcc419', solid: '#fcc419', emoji: '🍳' },
{ bg: 'linear-gradient(135deg,#f0e8ff,#e0d0ff)', text: '#845ef7', solid: '#845ef7', emoji: '🔬' },
{ bg: 'linear-gradient(135deg,#e8fffa,#d0fff5)', text: '#20c997', solid: '#20c997', emoji: '✈️' },
{ bg: 'linear-gradient(135deg,#ffe8e8,#ffe0d0)', text: '#fa5252', solid: '#fa5252', emoji: '📷' },
];
var TAG_COLORS = [
{ bg: '#ffe8e8', text: '#ff6b6b' },
{ bg: '#e8faf5', text: '#4ecdc4' },
{ bg: '#e8f4fc', text: '#45b7d8' },
{ bg: '#fff5e8', text: '#ffa94d' },
{ bg: '#f3e8ff', text: '#9775fa' },
{ bg: '#e8fff0', text: '#51cf66' },
{ bg: '#ffe8f5', text: '#f783ac' },
{ bg: '#e8f8ff', text: '#3bc9db' },
];
// ==================== State ====================
var state = {
categories: [],
featuredApps: [],
newApps: [],
library: {
list: [], total: 0, page: 1, pageSize: 20,
categoryId: null, keyword: '', sort: '', loading: false
}
};
// ==================== Utility Functions ====================
function formatNumber(num) {
if (num === undefined || num === null || isNaN(num)) return '0';
num = Number(num);
if (currentLang === 'en') {
if (num >= 1000000000) return (num / 1000000000).toFixed(1).replace(/\.0$/, '') + 'B';
if (num >= 1000000) return (num / 1000000).toFixed(1).replace(/\.0$/, '') + 'M';
if (num >= 1000) return (num / 1000).toFixed(1).replace(/\.0$/, '') + 'K';
return num.toString();
}
if (num >= 100000000) return (num / 100000000).toFixed(1).replace(/\.0$/, '') + '\u4ebf';
if (num >= 10000) return (num / 10000).toFixed(1).replace(/\.0$/, '') + '\u4e07';
return num.toString();
}
function generateGradient(name) {
var hash = 0;
if (!name) name = 'A';
for (var i = 0; i < name.length; i++) {
hash = ((hash << 5) - hash) + name.charCodeAt(i);
hash |= 0;
}
var h = Math.abs(hash);
var hue1 = h % 360;
var hue2 = (hue1 + 40 + (Math.floor(h / 360) % 80)) % 360;
var hue3 = (hue2 + 50 + (Math.floor(h / 129600) % 60)) % 360;
return 'linear-gradient(135deg, hsl(' + hue1 + ',75%,68%), hsl(' + hue2 + ',72%,62%), hsl(' + hue3 + ',70%,58%))';
}
function parseJSON(str, fallback) {
if (!str) return fallback || [];
if (Array.isArray(str)) return str;
if (typeof str === 'object') return str;
try { return JSON.parse(str); } catch(e) { return fallback || []; }
}
function renderStars(rating) {
rating = parseFloat(rating) || 0;
var full = Math.floor(rating);
var hasHalf = (rating % 1) >= 0.25 && (rating % 1) < 0.75;
var extraFull = (rating % 1) >= 0.75 ? 1 : 0;
if (extraFull) { full++; hasHalf = false; }
var html = '';
for (var i = 0; i < 5; i++) {
if (i < full) {
html += '<span class="star star-full">★</span>';
} else if (i === full && hasHalf) {
html += '<span class="star star-half">★</span>';
} else {
html += '<span class="star star-empty">★</span>';
}
}
return html;
}
function debounce(fn, delay) {
var timer = null;
return function() {
var ctx = this, args = arguments;
clearTimeout(timer);
timer = setTimeout(function() { fn.apply(ctx, args); }, delay);
};
}
function escapeHtml(str) {
if (!str) return '';
return String(str).replace(/[&<>"']/g, function(c) {
return { '&':'&amp;', '<':'&lt;', '>':'&gt;', '"':'&quot;', "'":'&#39;' }[c];
});
}
function showToast(msg, type) {
type = type || 'info';
var container = document.getElementById('toast-container');
var toast = document.createElement('div');
toast.className = 'toast toast-' + type;
var icon = type === 'success' ? '✓' : (type === 'error' ? '✕' : 'ℹ');
toast.innerHTML = '<span>' + icon + '</span><span>' + escapeHtml(msg) + '</span>';
container.appendChild(toast);
setTimeout(function() { toast.classList.add('show'); }, 10);
setTimeout(function() {
toast.classList.remove('show');
setTimeout(function() { if (toast.parentNode) toast.parentNode.removeChild(toast); }, 400);
}, 3000);
}
function getCatColor(index) {
return CAT_COLORS[index % CAT_COLORS.length];
}
function getTagColor(index) {
return TAG_COLORS[index % TAG_COLORS.length];
}
function renderAppIcon(app, size) {
size = size || 64;
var radius = Math.round(size * 0.28);
var iconURL = '';
if (app.icon) {
var v = String(app.icon).trim();
if (v.indexOf('/uploads/') === 0 || v.indexOf('http://') === 0 || v.indexOf('https://') === 0) {
iconURL = v;
}
}
var grad = generateGradient(app.name);
var letter = (app.name || '?').charAt(0).toUpperCase();
var wrapStyle = 'width:' + size + 'px;height:' + size + 'px;border-radius:' + radius + 'px';
if (iconURL) {
return '<div class="app-icon-wrap" style="background:' + grad + ';' + wrapStyle + '">' +
'<img src="' + escapeHtml(iconURL) + '" class="app-icon-img" alt="' + escapeHtml(app.name) + '" loading="lazy" style="position:absolute;inset:0" onerror="this.style.display=\'none\';this.nextElementSibling.style.display=\'block\'">' +
'<span style="display:none">' + escapeHtml(letter) + '</span>' +
'</div>';
}
return '<div class="app-icon-wrap" style="background:' + grad + ';' + wrapStyle + '">' + escapeHtml(letter) + '</div>';
}
// ==================== API ====================
function fetchData(path) {
return fetch(API_BASE + path)
.then(function(res) {
if (!res.ok) throw new Error('HTTP ' + res.status);
return res.json();
})
.then(function(json) {
if (json.code !== 0) throw new Error(json.message || 'API error');
return json.data;
})
.catch(function(err) {
console.error('Fetch error [' + path + ']:', err.message);
return null;
});
}
// ==================== Render: Categories ====================
function renderCategories() {
var wrap = document.getElementById('categories-wrap');
if (!state.categories.length) {
wrap.innerHTML = '<div class="empty-state" style="grid-column:1/-1"><div class="empty-state-icon">📭</div><div class="empty-state-text">' + t('empty_categories') + '</div></div>';
return;
}
var html = '';
var allActive = state.library.categoryId === null ? ' active' : '';
var allColor = state.library.categoryId === null
? 'background:linear-gradient(135deg,var(--coral),var(--pink));color:#fff'
: '';
html += '<button class="cat-pill' + allActive + '" data-cat-id="" style="' + allColor + '"><span class="cat-emoji">🌟</span>' + escapeHtml(t('cat_all')) + '</button>';
state.categories.forEach(function(cat, i) {
var c = getCatColor(i);
var isActive = state.library.categoryId == cat.id;
var style = isActive
? 'background:' + c.solid + ';color:#fff'
: 'color:' + c.text;
var emoji = cat.icon || c.emoji;
if (cat.icon && cat.icon.indexOf('http') !== 0 && cat.icon.length <= 4) {
emoji = cat.icon;
} else if (cat.icon && cat.icon.indexOf('http') === 0) {
emoji = c.emoji;
}
html += '<button class="cat-pill' + (isActive ? ' active' : '') + '" data-cat-id="' + cat.id + '" style="' + style + '"><span class="cat-emoji">' + emoji + '</span>' + escapeHtml(cat.name) + '</button>';
});
wrap.innerHTML = html;
wrap.querySelectorAll('.cat-pill').forEach(function(btn) {
btn.addEventListener('click', function() {
var catId = btn.getAttribute('data-cat-id');
handleCategoryClick(catId ? parseInt(catId) : null);
});
});
}
// ==================== Render: Featured ====================
function renderFeatured() {
var grid = document.getElementById('featured-grid');
if (!state.featuredApps.length) {
grid.innerHTML = '<div class="empty-state" style="grid-column:1/-1"><div class="empty-state-icon">✨</div><div class="empty-state-text">' + t('empty_featured') + '</div></div>';
return;
}
var html = '';
state.featuredApps.forEach(function(app, i) {
var grad = generateGradient(app.name);
var tags = parseJSON(app.tags, []);
var tagsHtml = '';
tags.slice(0, 3).forEach(function(tag, j) {
var tc = getTagColor(j);
tagsHtml += '<span class="tag-pill" style="background:' + tc.bg + ';color:' + tc.text + '">' + escapeHtml(tag) + '</span>';
});
html += '<div class="featured-card reveal" data-app-id="' + app.id + '" style="--card-grad:' + grad + '">'
+ '<div class="featured-card-top">'
+ '<div class="featured-card-top-inner">'
+ renderAppIcon(app, 72)
+ '<div style="flex:1;min-width:0">'
+ '<div class="featured-card-name">' + escapeHtml(app.name) + '</div>'
+ '<div class="featured-card-dev">' + escapeHtml(app.developer || t('modal_unknown_dev')) + '</div>'
+ '</div>'
+ '</div>'
+ '</div>'
+ '<div class="featured-card-body">'
+ '<p class="featured-card-desc">' + escapeHtml(app.description || t('modal_no_desc')) + '</p>'
+ '<div class="featured-card-tags">' + tagsHtml + '</div>'
+ '</div>'
+ '<div class="featured-card-footer">'
+ '<div class="featured-card-stats">'
+ '<div class="featured-stat"><div class="featured-stat-val">' + (app.rating ? app.rating.toFixed(1) : '-') + '</div><div class="featured-stat-label">' + t('sort_rating') + '</div></div>'
+ '<div class="featured-stat"><div class="featured-stat-val">' + formatNumber(app.downloads) + '</div><div class="featured-stat-label">' + t('modal_downloads') + '</div></div>'
+ '</div>'
+ '<button class="btn-download coral" data-download-id="' + app.id + '">' + t('download') + '</button>'
+ '</div>'
+ '</div>';
});
grid.innerHTML = html;
grid.querySelectorAll('.featured-card').forEach(function(card) {
card.addEventListener('click', function(e) {
if (e.target.classList.contains('btn-download')) return;
openAppDetail(card.getAttribute('data-app-id'));
});
});
grid.querySelectorAll('[data-download-id]').forEach(function(btn) {
btn.addEventListener('click', function(e) {
e.stopPropagation();
var app = state.featuredApps.find(function(a) { return a.id == btn.getAttribute('data-download-id'); });
if (app) handleDownload(app);
});
});
observeReveal();
}
// ==================== Render: New ====================
function renderNew() {
var wrap = document.getElementById('new-scroll-wrap');
if (!state.newApps.length) {
wrap.innerHTML = '<div class="empty-state" style="width:100%"><div class="empty-state-icon">🆕</div><div class="empty-state-text">' + t('empty_new') + '</div></div>';
return;
}
var html = '';
state.newApps.forEach(function(app) {
html += '<div class="new-card reveal" data-app-id="' + app.id + '">'
+ '<div class="new-badge">NEW</div>'
+ '<div class="app-card-top">' + renderAppIcon(app, 56) + ''
+ '<div class="app-card-info">'
+ '<div class="app-card-name">' + escapeHtml(app.name) + '</div>'
+ '<div class="app-card-dev">' + escapeHtml(app.developer || t('modal_unknown_dev')) + '</div>'
+ '</div>'
+ '</div>'
+ '<p class="app-card-desc">' + escapeHtml(app.description || t('modal_no_desc')) + '</p>'
+ '<div class="app-card-bottom">'
+ '<div class="app-card-meta">'
+ '<div class="stars">' + renderStars(app.rating) + '</div>'
+ '<div class="app-card-downloads">' + formatNumber(app.downloads) + t('downloads_suffix') + '</div>'
+ '</div>'
+ '<button class="btn-download" data-download-id="' + app.id + '">' + t('download') + '</button>'
+ '</div>'
+ '</div>';
});
wrap.innerHTML = html;
wrap.querySelectorAll('.new-card').forEach(function(card) {
card.addEventListener('click', function(e) {
if (e.target.classList.contains('btn-download')) return;
openAppDetail(card.getAttribute('data-app-id'));
});
});
wrap.querySelectorAll('[data-download-id]').forEach(function(btn) {
btn.addEventListener('click', function(e) {
e.stopPropagation();
var app = state.newApps.find(function(a) { return a.id == btn.getAttribute('data-download-id'); });
if (app) handleDownload(app);
});
});
observeReveal();
}
// ==================== Render: Library ====================
function renderLibrary() {
var grid = document.getElementById('app-grid');
var count = document.getElementById('library-count');
count.innerHTML = t('total_apps').replace('{n}', state.library.total);
if (state.library.loading) {
var skelHtml = '';
for (var s = 0; s < 10; s++) {
skelHtml += '<div class="skeleton-card"><div style="display:flex;gap:14px;margin-bottom:12px"><div class="skel-shimmer skel-icon"></div><div style="flex:1"><div class="skel-shimmer skel-line" style="width:70%"></div><div class="skel-shimmer skel-line short" style="margin-top:6px"></div></div></div><div class="skel-shimmer skel-line mid"></div><div class="skel-shimmer skel-line mid"></div></div>';
}
grid.innerHTML = skelHtml;
document.getElementById('pagination').innerHTML = '';
return;
}
if (!state.library.list.length) {
grid.innerHTML = '<div class="empty-state"><div class="empty-state-icon">🔍</div><div class="empty-state-text">' + t('empty_library') + '</div></div>';
document.getElementById('pagination').innerHTML = '';
return;
}
var html = '';
state.library.list.forEach(function(app) {
var dlClass = app.is_new ? ' coral' : '';
html += '<div class="app-card reveal" data-app-id="' + app.id + '">'
+ '<div class="app-card-top">' + renderAppIcon(app, 56) + ''
+ '<div class="app-card-info">'
+ '<div class="app-card-name">' + escapeHtml(app.name) + '</div>'
+ '<div class="app-card-dev">' + escapeHtml(app.developer || t('modal_unknown_dev')) + '</div>'
+ '</div>'
+ '</div>'
+ '<p class="app-card-desc">' + escapeHtml(app.description || t('modal_no_desc')) + '</p>'
+ '<div class="app-card-bottom">'
+ '<div class="app-card-meta">'
+ '<div class="stars">' + renderStars(app.rating) + '</div>'
+ '<div class="app-card-rating-text">' + (app.rating ? app.rating.toFixed(1) : '-') + ' \u00b7 ' + formatNumber(app.downloads) + t('downloads_suffix') + '</div>'
+ '</div>'
+ '<button class="btn-download' + dlClass + '" data-download-id="' + app.id + '">' + t('download') + '</button>'
+ '</div>'
+ '</div>';
});
grid.innerHTML = html;
grid.querySelectorAll('.app-card').forEach(function(card) {
card.addEventListener('click', function(e) {
if (e.target.classList.contains('btn-download')) return;
openAppDetail(card.getAttribute('data-app-id'));
});
});
grid.querySelectorAll('[data-download-id]').forEach(function(btn) {
btn.addEventListener('click', function(e) {
e.stopPropagation();
var app = state.library.list.find(function(a) { return a.id == btn.getAttribute('data-download-id'); });
if (app) handleDownload(app);
});
});
renderPagination();
observeReveal();
}
// ==================== Render: Pagination ====================
function renderPagination() {
var wrap = document.getElementById('pagination');
var total = state.library.total;
var page = state.library.page;
var pageSize = state.library.pageSize;
var totalPages = Math.ceil(total / pageSize);
if (totalPages <= 1) { wrap.innerHTML = ''; return; }
var html = '';
if (page > 1) {
html += '<button class="page-btn" data-page="' + (page - 1) + '">&#8249;</button>';
}
var start = Math.max(1, page - 2);
var end = Math.min(totalPages, page + 2);
if (start > 1) {
html += '<button class="page-btn" data-page="1">1</button>';
if (start > 2) html += '<span class="page-ellipsis">...</span>';
}
for (var i = start; i <= end; i++) {
html += '<button class="page-btn' + (i === page ? ' active' : '') + '" data-page="' + i + '">' + i + '</button>';
}
if (end < totalPages) {
if (end < totalPages - 1) html += '<span class="page-ellipsis">...</span>';
html += '<button class="page-btn" data-page="' + totalPages + '">' + totalPages + '</button>';
}
if (page < totalPages) {
html += '<button class="page-btn" data-page="' + (page + 1) + '">&#8250;</button>';
}
wrap.innerHTML = html;
wrap.querySelectorAll('.page-btn').forEach(function(btn) {
btn.addEventListener('click', function() {
var p = parseInt(btn.getAttribute('data-page'));
if (p && p !== state.library.page) {
handlePageChange(p);
}
});
});
}
// ==================== Render: Modal ====================
function renderModalContent(app) {
var grad = generateGradient(app.name);
var tags = parseJSON(app.tags, []);
var features = parseJSON(app.features, []);
var screenshots = parseJSON(app.screenshots, []);
var tagsHtml = '';
tags.forEach(function(tag, i) {
var tc = getTagColor(i);
tagsHtml += '<span class="tag-pill" style="background:' + tc.bg + ';color:' + tc.text + '">' + escapeHtml(tag) + '</span>';
});
var featuresHtml = '';
features.forEach(function(feat) {
featuresHtml += '<div class="modal-feature"><div class="modal-feature-check">✓</div><span>' + escapeHtml(feat) + '</span></div>';
});
var screenshotsHtml = '';
screenshots.forEach(function(ss, i) {
if (typeof ss === 'string' && ss.indexOf('http') === 0) {
screenshotsHtml += '<div class="screenshot-item"><img src="' + escapeHtml(ss) + '" alt="Screenshot ' + (i+1) + '" loading="lazy"></div>';
} else {
var ssGrad = generateGradient(app.name + '_ss_' + i);
screenshotsHtml += '<div class="screenshot-item"><div class="screenshot-placeholder" style="background:' + ssGrad + '">' + escapeHtml(app.name) + '</div></div>';
}
});
var platformText = app.platform || t('modal_platform');
var priceText = app.price || t('modal_free');
return '<div class="modal-header" style="--modal-grad:' + grad + '">'
+ '<button class="modal-close" id="modal-close">✕</button>'
+ '<div class="modal-header-inner">'
+ renderAppIcon(app, 88)
+ '<div style="flex:1;min-width:0">'
+ '<h3 class="modal-app-name">' + escapeHtml(app.name) + '</h3>'
+ '<div class="modal-app-dev">' + escapeHtml(app.developer || t('modal_unknown_dev')) + '</div>'
+ '<div class="modal-rating-row">'
+ '<span class="stars" style="font-size:16px">' + renderStars(app.rating) + '</span>'
+ '<span class="modal-rating-num">' + (app.rating ? app.rating.toFixed(1) : '-') + '</span>'
+ '<span class="modal-rating-text">' + formatNumber(app.reviews) + ' ' + t('modal_reviews') + '</span>'
+ '</div>'
+ '</div>'
+ '</div>'
+ '</div>'
+ '<div class="modal-body">'
+ '<div class="modal-section">'
+ '<div class="modal-section-title">' + t('modal_desc') + '</div>'
+ '<p class="modal-desc">' + escapeHtml(app.description || t('modal_no_desc')) + '</p>'
+ '</div>'
+ (tags.length ? '<div class="modal-section"><div class="modal-section-title">' + t('modal_tags') + '</div><div class="modal-tags">' + tagsHtml + '</div></div>' : '')
+ (features.length ? '<div class="modal-section"><div class="modal-section-title">' + t('modal_features') + '</div><div class="modal-features">' + featuresHtml + '</div></div>' : '')
+ '<div class="modal-section">'
+ '<div class="modal-section-title">' + t('modal_info') + '</div>'
+ '<div class="modal-stats-grid">'
+ '<div class="modal-stat-item"><div class="modal-stat-val">' + formatNumber(app.downloads) + '</div><div class="modal-stat-label">' + t('modal_downloads') + '</div></div>'
+ '<div class="modal-stat-item"><div class="modal-stat-val">' + escapeHtml(app.size || '-') + '</div><div class="modal-stat-label">' + t('modal_size') + '</div></div>'
+ '<div class="modal-stat-item"><div class="modal-stat-val">' + escapeHtml(app.version || '-') + '</div><div class="modal-stat-label">' + t('modal_version') + '</div></div>'
+ '<div class="modal-stat-item"><div class="modal-stat-val">' + escapeHtml(app.update_date || '-') + '</div><div class="modal-stat-label">' + t('modal_update') + '</div></div>'
+ '</div>'
+ '</div>'
+ (screenshots.length ? '<div class="modal-section"><div class="modal-section-title">' + t('modal_screenshots') + '</div><div class="modal-screenshots">' + screenshotsHtml + '</div></div>' : '')
+ '<div style="display:flex;gap:12px;margin-top:8px">'
+ '<button class="modal-download-btn" id="modal-download-btn">'
+ '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><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>'
+ '</div>'
+ '<div style="text-align:center;margin-top:12px;font-size:13px;color:var(--text-light)">' + escapeHtml(platformText) + ' \u00b7 ' + escapeHtml(priceText) + '</div>'
+ '</div>';
}
function renderModalSkeleton() {
return '<div class="modal-loading">'
+ '<div class="skel-shimmer" style="width:80px;height:80px;border-radius:22px;margin:0 auto 20px"></div>'
+ '<div class="skel-shimmer skel-line" style="width:50%;height:20px;margin:0 auto 12px"></div>'
+ '<div class="skel-shimmer skel-line short" style="margin:0 auto"></div>'
+ '<div class="skel-shimmer skel-line mid" style="margin-top:20px"></div>'
+ '<div class="skel-shimmer skel-line mid"></div>'
+ '</div>';
}
// ==================== Actions ====================
function loadHome() {
fetchData('/api/v1/app/home').then(function(data) {
if (data) {
if (data.categories) state.categories = data.categories;
if (data.featured) state.featuredApps = data.featured;
if (data.new) state.newApps = data.new;
} else {
return Promise.all([
fetchData('/api/v1/app/categories'),
fetchData('/api/v1/app/featured?limit=8'),
fetchData('/api/v1/app/new?limit=4')
]).then(function(results) {
if (results[0]) state.categories = results[0];
if (results[1]) state.featuredApps = results[1];
if (results[2]) state.newApps = results[2];
});
}
}).then(function() {
renderCategories();
renderFeatured();
renderNew();
});
}
function loadLibrary() {
state.library.loading = true;
renderLibrary();
var params = [];
if (state.library.categoryId) params.push('category_id=' + state.library.categoryId);
if (state.library.keyword) params.push('keyword=' + encodeURIComponent(state.library.keyword));
if (state.library.sort) params.push('sort=' + state.library.sort);
params.push('page=' + state.library.page);
params.push('page_size=' + state.library.pageSize);
fetchData('/api/v1/app/apps?' + params.join('&')).then(function(data) {
state.library.loading = false;
if (data) {
state.library.list = data.list || [];
state.library.total = data.total || 0;
state.library.page = data.page || state.library.page;
} else {
state.library.list = [];
state.library.total = 0;
}
renderLibrary();
});
}
function openAppDetail(id) {
var overlay = document.getElementById('modal-overlay');
var modal = document.getElementById('modal');
overlay.classList.add('active');
document.body.style.overflow = 'hidden';
modal.innerHTML = renderModalSkeleton();
fetchData('/api/v1/app/apps/' + id).then(function(app) {
if (app) {
modal.innerHTML = renderModalContent(app);
var closeBtn = document.getElementById('modal-close');
if (closeBtn) closeBtn.addEventListener('click', closeAppDetail);
var dlBtn = document.getElementById('modal-download-btn');
if (dlBtn) dlBtn.addEventListener('click', function() { handleDownload(app); });
} else {
modal.innerHTML = '<div class="modal-error"><div style="font-size:48px;margin-bottom:16px">😵</div>' + t('empty_library') + '<br><button class="btn-download" style="margin-top:20px" onclick="closeAppDetail()">' + t('modal_close') + '</button></div>';
}
});
}
function closeAppDetail() {
var overlay = document.getElementById('modal-overlay');
overlay.classList.remove('active');
document.body.style.overflow = '';
}
function handleDownload(app) {
showToast(t('download') + ' ' + (app.name || '') + '...', 'success');
}
function handleCategoryClick(catId) {
state.library.categoryId = catId;
state.library.page = 1;
renderCategories();
loadLibrary();
scrollToSection('library');
}
function handleSortChange(sort) {
state.library.sort = sort;
state.library.page = 1;
loadLibrary();
}
function handlePageChange(page) {
state.library.page = page;
loadLibrary();
scrollToSection('library');
}
function handleSearch(keyword) {
state.library.keyword = keyword;
state.library.page = 1;
loadLibrary();
}
function scrollToSection(id) {
var el = document.getElementById(id);
if (el) {
var offset = 80;
var top = el.getBoundingClientRect().top + window.pageYOffset - offset;
window.scrollTo({ top: top, behavior: 'smooth' });
}
}
// ==================== IntersectionObserver ====================
var revealObserver = null;
function observeReveal() {
if (!revealObserver) {
revealObserver = new IntersectionObserver(function(entries) {
entries.forEach(function(entry) {
if (entry.isIntersecting) {
entry.target.classList.add('visible');
revealObserver.unobserve(entry.target);
}
});
}, { threshold: 0.08, rootMargin: '0px 0px -40px 0px' });
}
document.querySelectorAll('.reveal:not(.visible)').forEach(function(el) {
revealObserver.observe(el);
});
}
// ==================== Event Listeners ====================
function setupEventListeners() {
var headerSearch = document.getElementById('header-search');
var headerSearchBtn = document.getElementById('header-search-btn');
var debouncedSearch = debounce(function(val) {
document.getElementById('library-search').value = val;
handleSearch(val);
}, 300);
headerSearch.addEventListener('input', function() {
debouncedSearch(this.value);
});
headerSearch.addEventListener('keypress', function(e) {
if (e.key === 'Enter') {
document.getElementById('library-search').value = this.value;
handleSearch(this.value);
scrollToSection('library');
}
});
headerSearchBtn.addEventListener('click', function() {
var val = headerSearch.value;
document.getElementById('library-search').value = val;
handleSearch(val);
scrollToSection('library');
});
var librarySearch = document.getElementById('library-search');
var debouncedLibrarySearch = debounce(function(val) {
handleSearch(val);
}, 300);
librarySearch.addEventListener('input', function() {
debouncedLibrarySearch(this.value);
});
document.getElementById('sort-select').addEventListener('change', function() {
handleSortChange(this.value);
});
document.querySelectorAll('.nav-link').forEach(function(link) {
link.addEventListener('click', function(e) {
e.preventDefault();
var target = link.getAttribute('data-target');
if (target) {
scrollToSection(target);
document.querySelectorAll('.nav-link').forEach(function(l) { l.classList.remove('active'); });
link.classList.add('active');
}
});
});
document.querySelectorAll('.section-more').forEach(function(link) {
link.addEventListener('click', function(e) {
e.preventDefault();
var target = link.getAttribute('data-target');
if (target) scrollToSection(target);
});
});
document.getElementById('hero-explore-btn').addEventListener('click', function() {
scrollToSection('library');
});
document.getElementById('modal-overlay').addEventListener('click', function(e) {
if (e.target === this) closeAppDetail();
});
document.addEventListener('keydown', function(e) {
if (e.key === 'Escape') closeAppDetail();
});
var scrollTop = document.getElementById('scroll-top');
var header = document.getElementById('header');
var sections = ['hero', 'featured', 'new', 'library'];
window.addEventListener('scroll', function() {
var sy = window.pageYOffset;
header.classList.toggle('scrolled', sy > 10);
scrollTop.classList.toggle('show', sy > 400);
var current = 'hero';
for (var i = 0; i < sections.length; i++) {
var el = document.getElementById(sections[i]);
if (el && el.getBoundingClientRect().top <= 120) {
current = sections[i];
}
}
document.querySelectorAll('.nav-link').forEach(function(l) {
l.classList.toggle('active', l.getAttribute('data-target') === current);
});
});
scrollTop.addEventListener('click', function() {
window.scrollTo({ top: 0, behavior: 'smooth' });
});
}
// ==================== onLangChange ====================
window.onLangChange = function() {
applyI18n();
renderCategories();
renderFeatured();
renderNew();
loadLibrary();
};
// ==================== Init ====================
function init() {
applyI18n();
setupEventListeners();
loadHome();
loadLibrary();
observeReveal();
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
window.closeAppDetail = closeAppDetail;
})();
</script>
</body>
</html>