All checks were successful
Deploy on Master Change / deploy (push) Successful in 44s
406 lines
13 KiB
HTML
406 lines
13 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="vi">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Simple Care - Tải Ứng Dụng & Hướng Dẫn Hỗ Trợ Sinh Viên</title>
|
|
<style>
|
|
:root {
|
|
--primary: #bb2126;
|
|
--primary-hover: #9c1b1f;
|
|
--bg: #f8fafc;
|
|
--card-bg: #ffffff;
|
|
--text-main: #1e293b;
|
|
--text-sub: #475569;
|
|
--border: #e2e8f0;
|
|
--radius: 8px;
|
|
--transition: all 0.2s ease;
|
|
--shadow: 0 1px 3px rgba(0,0,0,0.05), 0 1px 2px rgba(0,0,0,0.03);
|
|
--font: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
|
}
|
|
|
|
* {
|
|
box-sizing: border-box;
|
|
margin: 0;
|
|
padding: 0;
|
|
}
|
|
|
|
body {
|
|
font-family: var(--font);
|
|
background-color: var(--bg);
|
|
color: var(--text-main);
|
|
line-height: 1.5;
|
|
padding: 2rem 1rem;
|
|
}
|
|
|
|
.container {
|
|
max-width: 800px;
|
|
margin: 0 auto;
|
|
}
|
|
|
|
header {
|
|
text-align: center;
|
|
margin-bottom: 2.5rem;
|
|
padding-bottom: 1.5rem;
|
|
border-bottom: 1px solid var(--border);
|
|
}
|
|
|
|
h1 {
|
|
font-size: 2rem;
|
|
color: var(--primary);
|
|
margin-bottom: 0.5rem;
|
|
font-weight: 800;
|
|
}
|
|
|
|
.subtitle {
|
|
color: var(--text-sub);
|
|
font-size: 1rem;
|
|
}
|
|
|
|
section {
|
|
margin-bottom: 2.5rem;
|
|
}
|
|
|
|
h2 {
|
|
font-size: 1.25rem;
|
|
margin-bottom: 1.25rem;
|
|
color: var(--text-main);
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
}
|
|
|
|
.grid {
|
|
display: grid;
|
|
grid-template-columns: 1fr;
|
|
gap: 1rem;
|
|
}
|
|
|
|
@media (min-width: 600px) {
|
|
.grid {
|
|
grid-template-columns: 1fr 1fr;
|
|
}
|
|
}
|
|
|
|
.card {
|
|
background: var(--card-bg);
|
|
border: 1px solid var(--border);
|
|
border-radius: var(--radius);
|
|
padding: 1.25rem;
|
|
box-shadow: var(--shadow);
|
|
display: flex;
|
|
flex-direction: column;
|
|
justify-content: space-between;
|
|
transition: var(--transition);
|
|
}
|
|
|
|
.card:hover {
|
|
border-color: var(--primary);
|
|
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.05);
|
|
}
|
|
|
|
.card-title {
|
|
font-weight: 700;
|
|
font-size: 1.05rem;
|
|
margin-bottom: 0.25rem;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
}
|
|
|
|
.card-desc {
|
|
color: var(--text-sub);
|
|
font-size: 0.85rem;
|
|
margin-bottom: 1.25rem;
|
|
flex-grow: 1;
|
|
}
|
|
|
|
.platform-badge {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
padding: 0.2rem 0.5rem;
|
|
border-radius: 4px;
|
|
font-size: 0.75rem;
|
|
font-weight: 600;
|
|
background: #f1f5f9;
|
|
color: var(--text-sub);
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.5px;
|
|
}
|
|
|
|
.btn {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
gap: 8px;
|
|
background-color: var(--primary);
|
|
color: #fff;
|
|
text-decoration: none;
|
|
padding: 0.65rem 1rem;
|
|
border-radius: var(--radius);
|
|
font-weight: 600;
|
|
font-size: 0.9rem;
|
|
transition: var(--transition);
|
|
text-align: center;
|
|
border: none;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.btn:hover {
|
|
background-color: var(--primary-hover);
|
|
}
|
|
|
|
/* Guides section styles */
|
|
.guide-list {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 0.75rem;
|
|
}
|
|
|
|
.guide-item {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
padding: 0.85rem 1.25rem;
|
|
background: var(--card-bg);
|
|
border: 1px solid var(--border);
|
|
border-radius: var(--radius);
|
|
box-shadow: var(--shadow);
|
|
transition: var(--transition);
|
|
}
|
|
|
|
.guide-item:hover {
|
|
border-color: var(--primary);
|
|
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.05);
|
|
}
|
|
|
|
.guide-link {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 10px;
|
|
color: var(--text-main);
|
|
text-decoration: none;
|
|
font-weight: 600;
|
|
font-size: 0.95rem;
|
|
flex-grow: 1;
|
|
}
|
|
|
|
.guide-link:hover {
|
|
color: var(--primary);
|
|
}
|
|
|
|
.guide-link-btn {
|
|
font-size: 0.85rem;
|
|
color: var(--primary);
|
|
font-weight: 600;
|
|
text-decoration: none;
|
|
}
|
|
|
|
.guide-link-btn:hover {
|
|
text-decoration: underline;
|
|
}
|
|
|
|
.loading-container, .error-container {
|
|
text-align: center;
|
|
padding: 3rem 1.5rem;
|
|
background: var(--card-bg);
|
|
border: 1px solid var(--border);
|
|
border-radius: var(--radius);
|
|
color: var(--text-sub);
|
|
}
|
|
|
|
.spinner {
|
|
width: 32px;
|
|
height: 32px;
|
|
border: 3px solid #e2e8f0;
|
|
border-top-color: var(--primary);
|
|
border-radius: 50%;
|
|
animation: spin 0.8s linear infinite;
|
|
margin: 0 auto 1rem;
|
|
}
|
|
|
|
@keyframes spin {
|
|
to { transform: rotate(360deg); }
|
|
}
|
|
|
|
footer {
|
|
text-align: center;
|
|
margin-top: 4rem;
|
|
padding-top: 1.5rem;
|
|
border-top: 1px solid var(--border);
|
|
color: var(--text-sub);
|
|
font-size: 0.8rem;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="container">
|
|
<header>
|
|
<h1>Simple Care</h1>
|
|
<p class="subtitle">Cổng tải ứng dụng và tài liệu hướng dẫn học tập, thi cử dành cho sinh viên</p>
|
|
</header>
|
|
|
|
<main>
|
|
<!-- Applications section -->
|
|
<section>
|
|
<h2>
|
|
<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round" style="color: var(--primary)">
|
|
<rect x="4" y="4" width="16" height="16" rx="2" ry="2" />
|
|
<path d="M9 22V12h6v10M2 17l10 5 10-5M2 12l10 5 10-5" />
|
|
</svg>
|
|
Ứng dụng hỗ trợ
|
|
</h2>
|
|
<div id="apps-loading" class="loading-container">
|
|
<div class="spinner"></div>
|
|
<p>Đang tải danh sách ứng dụng...</p>
|
|
</div>
|
|
<div id="apps-error" class="error-container" style="display: none;"></div>
|
|
<div id="apps-list" class="grid" style="display: none;"></div>
|
|
</section>
|
|
|
|
<!-- Guides section -->
|
|
<section>
|
|
<h2>
|
|
<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round" style="color: var(--primary)">
|
|
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" />
|
|
<polyline points="14 2 14 8 20 8" />
|
|
<line x1="16" y1="13" x2="8" y2="13" />
|
|
<line x1="16" y1="17" x2="8" y2="17" />
|
|
</svg>
|
|
Hướng dẫn & Tài liệu
|
|
</h2>
|
|
<div id="guides-loading" class="loading-container">
|
|
<div class="spinner"></div>
|
|
<p>Đang tải danh sách tài liệu...</p>
|
|
</div>
|
|
<div id="guides-error" class="error-container" style="display: none;"></div>
|
|
<div id="guides-list" class="guide-list" style="display: none;"></div>
|
|
</section>
|
|
</main>
|
|
|
|
<footer>
|
|
<p>© 2026 Simple Care. Bản quyền thuộc Rikkei Academy.</p>
|
|
</footer>
|
|
</div>
|
|
|
|
<script>
|
|
// Platforms coloring/badges
|
|
function getPlatformIcon(platform) {
|
|
const p = platform.toLowerCase();
|
|
if (p.includes('win')) {
|
|
return `<svg viewBox="0 0 24 24" width="18" height="18" fill="currentColor" style="color: #0078d7"><path d="M0 3.449L9.75 2.1v9.45H0V3.449zM0 12.45h9.75v9.45L0 20.551v-8.1zM10.8 1.95L24 0v11.55H10.8V1.95zM10.8 12.45H24v11.55l-13.2-1.95v-9.6z"/></svg>`;
|
|
}
|
|
if (p.includes('mac') || p.includes('apple') || p.includes('ios')) {
|
|
return `<svg viewBox="0 0 24 24" width="18" height="18" fill="currentColor" style="color: #555"><path d="M18.71 19.5c-.83 1.24-1.71 2.45-3.05 2.47-1.34.03-1.77-.79-3.29-.79-1.53 0-2 .77-3.27.82-1.31.05-2.3-1.32-3.14-2.53C4.25 17 2.94 12.45 4.7 9.39c.87-1.52 2.43-2.48 4.12-2.51 1.28-.02 2.5.87 3.29.87.78 0 2.26-1.07 3.81-.91.65.03 2.47.26 3.64 1.98-.09.06-2.17 1.28-2.15 3.81.03 3.02 2.65 4.03 2.68 4.04-.03.07-.42 1.44-1.38 2.83M15.97 4.17c.66-.81 1.11-1.93.99-3.05-1 .04-2.22.67-2.94 1.51-.62.73-1.16 1.87-1.01 2.97 1.12.09 2.27-.58 2.96-1.43z"/></svg>`;
|
|
}
|
|
if (p.includes('linux')) {
|
|
return `<svg viewBox="0 0 24 24" width="18" height="18" fill="currentColor" style="color: #FCC624"><path d="M12 2c-3.3 0-6 2.7-6 6 0 2.2.8 4.2 2.2 5.6C6.5 15.6 5 18.5 5 22h14c0-3.5-1.5-6.4-3.2-8.4 1.4-1.4 2.2-3.4 2.2-5.6 0-3.3-2.7-6-6-6zm0 2c2.2 0 4 1.8 4 4 0 .9-.3 1.7-.8 2.3-.3.4-.7.7-1.2.9-.6.2-1.3.3-2 .3s-1.4-.1-2-.3c-.5-.2-.9-.5-1.2-.9-.5-.6-.8-1.4-.8-2.3 0-2.2 1.8-4 4-4z"/></svg>`;
|
|
}
|
|
return `<svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" style="color: var(--primary)"><circle cx="12" cy="12" r="10"/><line x1="2" y1="12" x2="22" y2="12"/><path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"/></svg>`;
|
|
}
|
|
|
|
const host = 'https://sv.rikkeiraia.org';
|
|
|
|
async function loadApps() {
|
|
const loader = document.getElementById('apps-loading');
|
|
const errorDiv = document.getElementById('apps-error');
|
|
const listDiv = document.getElementById('apps-list');
|
|
|
|
try {
|
|
const response = await fetch(`${host}/api/public/app-downloads`);
|
|
if (!response.ok) throw new Error('Không thể lấy danh sách ứng dụng');
|
|
|
|
const res = await response.json();
|
|
const data = res.data || [];
|
|
|
|
loader.style.display = 'none';
|
|
|
|
if (data.length === 0) {
|
|
errorDiv.innerText = 'Không có ứng dụng nào khả dụng để tải về.';
|
|
errorDiv.style.display = 'block';
|
|
return;
|
|
}
|
|
|
|
listDiv.innerHTML = data.map(app => `
|
|
<div class="card">
|
|
<div>
|
|
<div class="card-title">
|
|
${getPlatformIcon(app.platform)}
|
|
${app.name}
|
|
</div>
|
|
<div class="card-desc">${app.description || 'Hỗ trợ quá trình kiểm tra và học tập.'}</div>
|
|
</div>
|
|
<div style="display:flex; justify-content:space-between; align-items:center;">
|
|
<span class="platform-badge">${app.platform === 'macOS' ? 'MacOS' : app.platform}</span>
|
|
<a href="${app.downloadUrl}" target="_blank" rel="noopener noreferrer" class="btn">
|
|
<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="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ải về
|
|
</a>
|
|
</div>
|
|
</div>
|
|
`).join('');
|
|
|
|
listDiv.style.display = 'grid';
|
|
} catch (err) {
|
|
loader.style.display = 'none';
|
|
errorDiv.innerText = 'Lỗi kết nối máy chủ. Vui lòng thử lại sau.';
|
|
errorDiv.style.display = 'block';
|
|
}
|
|
}
|
|
|
|
async function loadGuides() {
|
|
const loader = document.getElementById('guides-loading');
|
|
const errorDiv = document.getElementById('guides-error');
|
|
const listDiv = document.getElementById('guides-list');
|
|
|
|
try {
|
|
const response = await fetch(`${host}/api/public/app-guides`);
|
|
if (!response.ok) throw new Error('Không thể lấy danh sách hướng dẫn');
|
|
|
|
const res = await response.json();
|
|
const data = res.data || [];
|
|
|
|
loader.style.display = 'none';
|
|
|
|
if (data.length === 0) {
|
|
errorDiv.innerText = 'Không có tài liệu hoặc video hướng dẫn nào.';
|
|
errorDiv.style.display = 'block';
|
|
return;
|
|
}
|
|
|
|
listDiv.innerHTML = data.map(guide => {
|
|
const isVideo = guide.url.toLowerCase().includes('youtube.com') || guide.url.toLowerCase().includes('youtu.be') || guide.url.toLowerCase().includes('drive.google.com/file');
|
|
const icon = isVideo
|
|
? `<svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" style="color: #ea4335"><polygon points="23 7 16 12 23 17 23 7"/><rect x="1" y="5" width="15" height="14" rx="2" ry="2"/></svg>`
|
|
: `<svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" style="color: #4285f4"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/></svg>`;
|
|
|
|
return `
|
|
<div class="guide-item">
|
|
<a href="${guide.url}" target="_blank" rel="noopener noreferrer" class="guide-link">
|
|
${icon}
|
|
${guide.title}
|
|
</a>
|
|
<a href="${guide.url}" target="_blank" rel="noopener noreferrer" class="guide-link-btn">Xem →</a>
|
|
</div>
|
|
`;
|
|
}).join('');
|
|
|
|
listDiv.style.display = 'flex';
|
|
} catch (err) {
|
|
loader.style.display = 'none';
|
|
errorDiv.innerText = 'Lỗi kết nối máy chủ. Vui lòng thử lại sau.';
|
|
errorDiv.style.display = 'block';
|
|
}
|
|
}
|
|
|
|
// Run
|
|
loadApps();
|
|
loadGuides();
|
|
</script>
|
|
</body>
|
|
</html>
|