Autosave: 20260225-222606

This commit is contained in:
Flatlogic Bot 2026-02-25 22:26:07 +00:00
parent 3d02f25bbd
commit b67ab46d1e
11 changed files with 879 additions and 181 deletions

View File

@ -200,15 +200,26 @@ class AuthController extends Controller {
public function requestWithdrawal() {
if (!isset($_SESSION['user_id'])) {
if (is_ajax()) {
header('Content-Type: application/json');
echo json_encode(['error' => 'Unauthorized']);
exit;
}
$this->redirect('/login');
}
$amount = (float)$_POST['amount'];
$method = $_POST['method'];
$details = $_POST['details'];
$amount = (float)($_POST['amount'] ?? 0);
$method = $_POST['method'] ?? '';
$details = $_POST['details'] ?? '';
if ($amount < 10000) { // Minimum WD
$_SESSION['error'] = __('error_min_withdraw');
$error = __('error_min_withdraw');
if (is_ajax()) {
header('Content-Type: application/json');
echo json_encode(['error' => $error]);
exit;
}
$_SESSION['error'] = $error;
$this->redirect('/profile');
}
@ -218,7 +229,13 @@ class AuthController extends Controller {
$balance = $stmt->fetchColumn();
if ($balance < $amount) {
$_SESSION['error'] = __('error_insufficient_balance');
$error = __('error_insufficient_balance');
if (is_ajax()) {
header('Content-Type: application/json');
echo json_encode(['error' => $error]);
exit;
}
$_SESSION['error'] = $error;
$this->redirect('/profile');
}
@ -230,7 +247,14 @@ class AuthController extends Controller {
$stmt = $db->prepare("INSERT INTO withdrawals (user_id, amount, method, account_details, status) VALUES (?, ?, ?, ?, 'pending')");
$stmt->execute([$_SESSION['user_id'], $amount, $method, $details]);
$_SESSION['success'] = __('success_withdraw_submitted');
$success = __('success_withdraw_submitted');
if (is_ajax()) {
header('Content-Type: application/json');
echo json_encode(['success' => $success, 'new_balance' => $balance - $amount]);
exit;
}
$_SESSION['success'] = $success;
$this->redirect('/profile');
}
}
}

View File

@ -37,9 +37,43 @@ class ContactController extends Controller {
$_SESSION['success'] = 'Your message has been sent successfully!';
} else {
$_SESSION['error'] = 'Failed to send message. Please try again later.';
// Log error if needed: error_log($res['error']);
}
$this->redirect('/contact');
}
}
public function ajaxReport() {
if (!is_ajax()) {
$this->redirect('/');
}
header('Content-Type: application/json');
$email = $_POST['email'] ?? '';
$subject = $_POST['subject'] ?? 'App Report';
$message = $_POST['message'] ?? '';
$apk_name = $_POST['apk_name'] ?? 'Unknown App';
if (empty($email) || empty($message)) {
echo json_encode(['error' => 'Email and message are required.']);
exit;
}
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
echo json_encode(['error' => 'Invalid email address.']);
exit;
}
require_once __DIR__ . '/../../mail/MailService.php';
$full_message = "Report for App: $apk_name\n\nUser Email: $email\n\nMessage:\n$message";
$res = \MailService::sendContactMessage('System Report', $email, $full_message, null, $subject);
if (!empty($res['success'])) {
echo json_encode(['success' => 'Report submitted successfully!']);
} else {
echo json_encode(['error' => 'Failed to submit report.']);
}
exit;
}
}

View File

@ -75,3 +75,7 @@ function get_client_ip() {
return $ipaddress;
}
function is_ajax() {
return !empty($_SERVER['HTTP_X_REQUESTED_WITH']) && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) == 'xmlhttprequest';
}

View File

@ -362,8 +362,234 @@ body.animated-bg {
padding: 0.75rem 1.5rem;
font-size: 1rem;
}
.card-title {
font-size: 0.85rem;
}
/* Mobile Bottom Navigation */
.mobile-bottom-nav {
position: fixed;
bottom: 0;
left: 0;
right: 0;
height: 65px;
background: var(--card-bg);
display: flex;
align-items: center;
justify-content: space-around;
box-shadow: 0 -5px 20px rgba(0,0,0,0.1);
z-index: 1040;
border-top: 1px solid var(--border-color);
padding-bottom: env(safe-area-inset-bottom);
}
.mobile-nav-item {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
text-decoration: none;
color: var(--muted-text);
font-size: 0.7rem;
font-weight: 500;
transition: all 0.2s ease;
flex: 1;
}
.mobile-nav-item i {
font-size: 1.25rem;
margin-bottom: 4px;
}
.mobile-nav-item.active {
color: var(--accent-color);
}
.mobile-nav-item.active i {
transform: translateY(-2px);
}
/* Sticky Download Button for Mobile */
.sticky-download-bar {
position: fixed;
bottom: 65px; /* Above mobile-bottom-nav */
left: 0;
right: 0;
background: var(--card-bg);
padding: 0.75rem 1rem;
box-shadow: 0 -5px 15px rgba(0,0,0,0.05);
z-index: 1030;
border-top: 1px solid var(--border-color);
transform: translateY(100%);
transition: transform 0.3s ease;
}
.sticky-download-bar.show {
transform: translateY(0);
}
/* Search Overlay */
.search-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: var(--bg-color);
z-index: 1100;
display: none;
padding: 2rem 1.5rem;
}
.search-overlay.active {
display: block;
animation: fadeIn 0.3s ease;
}
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
.search-overlay .btn-close-search {
position: absolute;
top: 1.5rem;
right: 1.5rem;
font-size: 1.5rem;
color: var(--text-color);
background: none;
border: none;
}
/* Share FAB */
.share-fab {
position: fixed;
bottom: 80px;
right: 20px;
width: 50px;
height: 50px;
background: var(--accent-color);
color: white;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 4px 15px rgba(16, 185, 129, 0.4);
z-index: 1020;
text-decoration: none;
transition: all 0.3s ease;
}
.share-fab:hover {
color: white;
transform: scale(1.1);
}
/* WhatsApp FAB */
.whatsapp-fab {
position: fixed;
bottom: 80px;
left: 20px;
width: 50px;
height: 50px;
background: #25D366;
color: white;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 4px 15px rgba(37, 211, 102, 0.4);
z-index: 1020;
text-decoration: none;
transition: all 0.3s ease;
}
.whatsapp-fab:hover {
color: white;
transform: scale(1.1);
}
/* Back to Top */
.back-to-top {
position: fixed;
bottom: 140px;
right: 20px;
width: 40px;
height: 40px;
background: var(--card-bg);
color: var(--text-color);
border: 1px solid var(--border-color);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
z-index: 1010;
text-decoration: none;
transition: all 0.3s ease;
opacity: 0;
visibility: hidden;
}
.back-to-top.show {
opacity: 1;
visibility: visible;
}
/* Category Chips Scroll */
.category-scroll .btn {
border: none;
transition: all 0.2s ease;
}
.category-scroll .btn-light {
background: var(--subtle-bg);
color: var(--muted-text);
}
.category-scroll .btn-light:hover {
background: var(--border-color);
color: var(--text-color);
}
/* Featured Scroll Hover */
.featured-scroll .card:hover {
background: var(--subtle-bg);
}
/* Hide desktop elements on mobile and vice-versa */
@media (min-width: 992px) {
.mobile-bottom-nav, .sticky-download-bar, .search-overlay, .share-fab, .whatsapp-fab {
display: none !important;
}
}
@media (max-width: 991.98px) {
body {
padding-bottom: 70px; /* Space for bottom nav */
}
.navbar-brand {
font-size: 1.1rem;
}
.navbar-toggler {
padding: 0.25rem 0.5rem;
}
/* Improve card layout on mobile for 3 columns */
.card-body {
padding: 0.5rem !important;
}
.card-title {
font-size: 0.75rem !important;
margin-bottom: 0.25rem !important;
}
.badge {
padding: 0.2rem 0.4rem !important;
}
#ai-chat-wrapper {
bottom: 75px !important;
}
}

View File

@ -15,7 +15,6 @@ document.addEventListener('DOMContentLoaded', () => {
const html = document.documentElement;
const updateIcons = (theme) => {
// Update all theme toggle icons
const icons = document.querySelectorAll('#theme-toggle i, #theme-toggle-mobile i');
icons.forEach(icon => {
if (theme === 'dark') {
@ -25,7 +24,6 @@ document.addEventListener('DOMContentLoaded', () => {
}
});
// Update all theme status texts
const textLabels = document.querySelectorAll('.theme-status-text');
textLabels.forEach(label => {
label.textContent = theme === 'dark' ? 'Dark Mode' : 'Light Mode';
@ -40,71 +38,140 @@ document.addEventListener('DOMContentLoaded', () => {
themeToggle.addEventListener('click', () => {
const currentTheme = html.getAttribute('data-theme') || 'light';
const newTheme = currentTheme === 'dark' ? 'light' : 'dark';
// Update UI
html.setAttribute('data-theme', newTheme);
// Update All Icons and Labels
updateIcons(newTheme);
// Save preference
document.cookie = `theme=${newTheme}; path=/; max-age=${365 * 24 * 60 * 60}`;
localStorage.setItem('theme', newTheme);
});
};
// Unified AJAX Content Loader
const loadContent = (url, updateUrl = true) => {
const gridContainer = document.getElementById('apk-grid-container');
if (!gridContainer) return;
gridContainer.style.opacity = '0.5';
gridContainer.style.pointerEvents = 'none';
fetch(url, {
headers: { 'X-Requested-With': 'XMLHttpRequest' }
})
.then(response => response.text())
.then(data => {
gridContainer.innerHTML = data;
gridContainer.style.opacity = '1';
gridContainer.style.pointerEvents = 'all';
if (updateUrl) {
window.history.pushState({}, '', url);
}
// Scroll to grid top on mobile if needed
if (window.innerWidth < 992) {
const latestSection = document.getElementById('latest');
if (latestSection) {
window.scrollTo({
top: latestSection.offsetTop - 100,
behavior: 'smooth'
});
}
}
})
.catch(err => {
console.error('Fetch error:', err);
gridContainer.style.opacity = '1';
gridContainer.style.pointerEvents = 'all';
});
};
// AJAX Category Filtering
const initCategoryAjax = () => {
const filters = document.querySelectorAll('.category-filter');
const gridContainer = document.getElementById('apk-grid-container');
const dropdownBtn = document.getElementById('category-dropdown-btn');
const latestTitle = document.getElementById('latest-title');
document.addEventListener('click', (e) => {
const filter = e.target.closest('.category-filter, .ajax-cat-link');
if (!filter) return;
if (!gridContainer || filters.length === 0) return;
e.preventDefault();
const url = filter.getAttribute('href');
const categoryName = filter.textContent.trim();
const dropdownBtn = document.getElementById('category-dropdown-btn');
filters.forEach(filter => {
filter.addEventListener('click', (e) => {
e.preventDefault();
const category = filter.getAttribute('data-category');
const url = filter.getAttribute('href');
const categoryName = filter.textContent;
if (dropdownBtn && filter.classList.contains('category-filter')) {
dropdownBtn.innerHTML = `${categoryName} <i class="bi bi-chevron-down ms-1 small"></i>`;
}
// Update UI state
gridContainer.style.opacity = '0.5';
gridContainer.style.pointerEvents = 'none';
// Fetch data
fetch(url, {
headers: {
'X-Requested-With': 'XMLHttpRequest'
}
})
.then(response => response.text())
.then(data => {
gridContainer.innerHTML = data;
gridContainer.style.opacity = '1';
gridContainer.style.pointerEvents = 'all';
// Update dropdown button text
if (dropdownBtn) {
dropdownBtn.innerHTML = `${categoryName} <i class="bi bi-chevron-down ms-1 small"></i>`;
}
// Update URL without refreshing
window.history.pushState({ category: category }, '', url);
})
.catch(err => {
console.error('Fetch error:', err);
gridContainer.style.opacity = '1';
gridContainer.style.pointerEvents = 'all';
// Update active state for chips if they are chips
if (filter.classList.contains('ajax-cat-link')) {
document.querySelectorAll('.ajax-cat-link').forEach(btn => {
btn.classList.remove('btn-success');
btn.classList.add('btn-light');
});
});
});
filter.classList.remove('btn-light');
filter.classList.add('btn-success');
}
// Handle browser back/forward
window.addEventListener('popstate', (e) => {
window.location.reload(); // Simple solution for now
loadContent(url);
// Close search overlay if open
const searchOverlay = document.getElementById('search-overlay');
if (searchOverlay) searchOverlay.classList.remove('active');
});
};
// AJAX Search
const initSearchAjax = () => {
const searchForm = document.getElementById('ajax-search-form');
if (!searchForm) return;
searchForm.addEventListener('submit', (e) => {
e.preventDefault();
const formData = new FormData(searchForm);
const query = formData.get('search');
const url = `/?search=${encodeURIComponent(query)}`;
loadContent(url);
const searchOverlay = document.getElementById('search-overlay');
if (searchOverlay) searchOverlay.classList.remove('active');
});
};
// Newsletter AJAX
const initNewsletterAjax = () => {
const btn = document.getElementById('newsletter-btn');
const emailInput = document.getElementById('newsletter-email');
const msg = document.getElementById('newsletter-msg');
if (!btn || !emailInput) return;
btn.addEventListener('click', () => {
const email = emailInput.value;
if (!email) return;
btn.disabled = true;
const originalText = btn.innerHTML;
btn.innerHTML = '<span class="spinner-border spinner-border-sm"></span>';
fetch('/api/newsletter/subscribe', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email: email })
})
.then(res => res.json())
.then(data => {
btn.disabled = false;
btn.innerHTML = originalText;
if (data.success) {
msg.innerHTML = `<span class="text-success">${data.success}</span>`;
emailInput.value = '';
} else {
msg.innerHTML = `<span class="text-danger">${data.error}</span>`;
}
})
.catch(err => {
btn.disabled = false;
btn.innerHTML = originalText;
msg.innerHTML = '<span class="text-danger">An error occurred.</span>';
});
});
};
@ -137,11 +204,8 @@ document.addEventListener('DOMContentLoaded', () => {
const content = document.createElement('div');
content.className = (isUser ? 'bg-success text-white' : 'bg-white') + ' p-3 rounded-4 shadow-sm small';
content.style.maxWidth = '85%';
if (isUser) {
content.style.borderBottomRightRadius = '0';
} else {
content.style.borderBottomLeftRadius = '0';
}
content.style.borderBottomRightRadius = isUser ? '0' : 'inherit';
content.style.borderBottomLeftRadius = isUser ? 'inherit' : '0';
content.textContent = message;
div.appendChild(content);
@ -156,7 +220,6 @@ document.addEventListener('DOMContentLoaded', () => {
appendMessage(message, true);
chatInput.value = '';
// Loading state
const loadingDiv = document.createElement('div');
loadingDiv.className = 'mb-3 d-flex';
loadingDiv.innerHTML = '<div class="bg-white p-3 rounded-4 shadow-sm small" style="border-bottom-left-radius: 0 !important;"><div class="spinner-border spinner-border-sm text-success" role="status"></div> Thinking...</div>';
@ -165,9 +228,7 @@ document.addEventListener('DOMContentLoaded', () => {
fetch('/api/ai/chat', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ message: message })
})
.then(response => response.json())
@ -180,7 +241,7 @@ document.addEventListener('DOMContentLoaded', () => {
}
})
.catch(err => {
messagesContainer.removeChild(loadingDiv);
if (messagesContainer.contains(loadingDiv)) messagesContainer.removeChild(loadingDiv);
appendMessage('Error connecting to AI assistant.');
console.error(err);
});
@ -192,24 +253,60 @@ document.addEventListener('DOMContentLoaded', () => {
});
};
// Mobile Overlays & Utils
const initMobileUtils = () => {
const searchTrigger = document.getElementById('mobile-search-trigger');
const searchOverlay = document.getElementById('search-overlay');
const closeSearch = document.getElementById('close-search-overlay');
if (searchTrigger && searchOverlay) {
searchTrigger.addEventListener('click', (e) => {
e.preventDefault();
searchOverlay.classList.add('active');
const input = searchOverlay.querySelector('input');
if (input) setTimeout(() => input.focus(), 300);
});
}
if (closeSearch && searchOverlay) {
closeSearch.addEventListener('click', () => {
searchOverlay.classList.remove('active');
});
}
// Back to Top
const backToTop = document.getElementById('back-to-top');
if (backToTop) {
window.addEventListener('scroll', () => {
if (window.pageYOffset > 300) {
backToTop.classList.add('show');
} else {
backToTop.classList.remove('show');
}
});
backToTop.addEventListener('click', (e) => {
e.preventDefault();
window.scrollTo({ top: 0, behavior: 'smooth' });
});
}
};
// Initial Sync
const currentTheme = html.getAttribute('data-theme') || 'light';
updateIcons(currentTheme);
// Sync theme from localStorage if cookie is missing but localStorage has it
const savedTheme = localStorage.getItem('theme');
const currentCookie = document.cookie.split('; ').find(row => row.startsWith('theme='))?.split('=')[1];
if (savedTheme && !currentCookie) {
html.setAttribute('data-theme', savedTheme);
updateIcons(savedTheme);
document.cookie = `theme=${savedTheme}; path=/; max-age=${365 * 24 * 60 * 60}`;
}
initThemeToggle('theme-toggle');
initThemeToggle('theme-toggle-mobile');
initCategoryAjax();
initSearchAjax();
initNewsletterAjax();
initAIChat();
initMobileUtils();
console.log('ApkNusa ready.');
// Handle browser back/forward
window.addEventListener('popstate', () => {
loadContent(window.location.href, false);
});
console.log('ApkNusa AJAX Engine active.');
});

View File

@ -40,6 +40,7 @@ if (get_setting('maintenance_mode') === '1') {
$router = new Router();
$router->post('/api/newsletter/subscribe', 'NewsletterController@subscribe');
$router->post('/api/report', 'ContactController@ajaxReport');
$router->post('/api/ai/chat', 'AIController@chat');
// Sitemap

View File

@ -2,14 +2,15 @@
<div class="container py-3">
<!-- Breadcrumb -->
<nav aria-label="breadcrumb" class="mb-4">
<nav aria-label="breadcrumb" class="mb-4 d-none d-md-block">
<ol class="breadcrumb mb-0 small">
<li class="breadcrumb-item"><a href="/" class="text-success text-decoration-none"><?php echo __('home'); ?></a></li>
<?php
$db = db_pdo();
$catName = $db->query("SELECT name FROM categories WHERE id = " . ($apk['category_id'] ?: 0))->fetchColumn();
$catSlug = $db->query("SELECT slug FROM categories WHERE id = " . ($apk['category_id'] ?: 0))->fetchColumn();
?>
<li class="breadcrumb-item"><a href="/?category=<?php echo strtolower($catName); ?>" class="text-success text-decoration-none"><?php echo $catName ?: 'Apps'; ?></a></li>
<li class="breadcrumb-item"><a href="/?category=<?php echo $catSlug; ?>" class="text-success text-decoration-none"><?php echo $catName ?: 'Apps'; ?></a></li>
<li class="breadcrumb-item active text-muted" aria-current="page"><?php echo $apk['title']; ?></li>
</ol>
</nav>
@ -17,15 +18,15 @@
<div class="row g-4">
<!-- Main Content -->
<div class="col-lg-8">
<div class="bg-white p-4 p-md-5 rounded-4 shadow-sm mb-4">
<div class="bg-white p-3 p-md-5 rounded-4 shadow-sm mb-4">
<!-- Header Section -->
<div class="d-flex align-items-start mb-4">
<?php
$icon = !empty($apk['icon_path']) ? '/'.$apk['icon_path'] : $apk['image_url'];
?>
<img src="<?php echo $icon; ?>" class="rounded-4 me-3 me-md-4 shadow-sm" width="100" height="100" alt="<?php echo $apk['title']; ?>" style="object-fit: cover; min-width: 100px;">
<img src="<?php echo $icon; ?>" class="rounded-4 me-3 me-md-4 shadow-sm" width="80" height="80" alt="<?php echo $apk['title']; ?>" style="object-fit: cover; min-width: 80px;">
<div>
<h1 class="h2 fw-bold mb-1 d-flex align-items-center flex-wrap">
<h1 class="h4 fw-bold mb-1 d-flex align-items-center flex-wrap">
<?php echo $apk['title']; ?>
<span class="badge bg-light text-muted fw-normal ms-2 fs-6">v<?php echo $apk['version']; ?></span>
</h1>
@ -33,20 +34,17 @@
<div class="d-flex flex-wrap gap-2 mb-0">
<span class="badge bg-success-subtle text-success border border-success-subtle px-2 py-1 fw-medium rounded">
<i class="bi bi-download me-1"></i> <?php echo number_format($apk['total_downloads']); ?> <?php echo __('downloads'); ?>
<i class="bi bi-download me-1"></i> <?php echo number_format($apk['total_downloads']); ?>
</span>
<span class="badge bg-info-subtle text-info border border-info-subtle px-2 py-1 fw-medium rounded">
<i class="bi bi-shield-check me-1"></i> <?php echo __('verified_safe'); ?>
<i class="bi bi-shield-check me-1"></i> <?php echo __('verified'); ?>
</span>
</div>
<div class="mt-2 text-muted small">
<i class="bi bi-calendar3 me-1"></i> <?php echo date('M d, Y', strtotime($apk['created_at'])); ?>
</div>
</div>
</div>
<!-- Action Button -->
<div class="mb-4">
<div class="mb-4" id="main-download-btn-area">
<a href="/download/<?php echo $apk['slug']; ?>" target="_blank" class="btn btn-success btn-lg w-100 py-3 rounded-pill fw-bold shadow-sm mb-2">
<i class="bi bi-download me-2"></i> <?php echo __('download_now'); ?>
</a>
@ -107,6 +105,30 @@
</p>
</div>
</div>
<!-- Related Apps Section -->
<?php
$relatedApks = $db->query("SELECT * FROM apks WHERE category_id = " . ($apk['category_id'] ?: 0) . " AND id != " . $apk['id'] . " LIMIT 6")->fetchAll();
if ($relatedApks):
?>
<div class="mb-5">
<h4 class="fw-bold h5 mb-3 d-flex align-items-center">
<i class="bi bi-grid-fill text-success me-2"></i> Similar Apps
</h4>
<div class="row g-2">
<?php foreach ($relatedApks as $rapk): ?>
<div class="col-4 col-md-4">
<a href="/apk/<?php echo $rapk['slug']; ?>" class="text-decoration-none">
<div class="card border-0 shadow-sm rounded-4 text-center p-2 h-100 hover-lift">
<img src="<?php echo !empty($rapk['icon_path']) ? '/'.$rapk['icon_path'] : $rapk['image_url']; ?>" class="rounded-3 mx-auto mb-2 shadow-sm" width="48" height="48" style="object-fit: cover;">
<h6 class="card-title fw-bold mb-0 text-truncate small" style="font-size: 0.7rem;"><?php echo $rapk['title']; ?></h6>
</div>
</a>
</div>
<?php endforeach; ?>
</div>
</div>
<?php endif; ?>
</div>
<!-- Sidebar / Additional Info -->
@ -128,7 +150,23 @@
<?php endif; ?>
</div>
<div class="bg-dark text-white p-4 rounded-4 shadow-sm text-center border-0">
<!-- Report / Request Section -->
<div class="bg-light p-4 rounded-4 mb-4 border-0">
<h6 class="fw-bold mb-3 d-flex align-items-center">
<i class="bi bi-flag-fill text-danger me-2"></i> Support & Issues
</h6>
<p class="text-muted small mb-3">Found a problem with this app or want to request a newer version?</p>
<div class="d-grid gap-2">
<button type="button" class="btn btn-outline-danger btn-sm rounded-pill" data-bs-toggle="modal" data-bs-target="#reportModal" onclick="setReportType('Report Issue')">
<i class="bi bi-exclamation-triangle me-1"></i> Report Issue
</button>
<button type="button" class="btn btn-outline-dark btn-sm rounded-pill" data-bs-toggle="modal" data-bs-target="#reportModal" onclick="setReportType('Request Update')">
<i class="bi bi-arrow-repeat me-1"></i> Request Update
</button>
</div>
</div>
<div class="bg-dark text-white p-4 rounded-4 shadow-sm text-center border-0 d-none d-lg-block">
<div class="bg-success bg-opacity-10 rounded-circle d-inline-flex p-3 mb-3">
<i class="bi bi-trophy text-success h4 mb-0"></i>
</div>
@ -141,6 +179,56 @@
</div>
</div>
<!-- Report/Request Modal -->
<div class="modal fade" id="reportModal" tabindex="-1">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content border-0 rounded-4 overflow-hidden">
<div class="modal-header bg-dark text-white py-4 border-0">
<h5 class="modal-title fw-bold" id="reportModalLabel">Report Issue</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<form id="report-form">
<input type="hidden" name="apk_name" value="<?php echo htmlspecialchars($apk['title']); ?>">
<input type="hidden" name="subject" id="report-subject" value="Report Issue">
<div class="modal-body p-4">
<div id="report-alert"></div>
<div class="mb-3">
<label class="form-label fw-bold">Your Email</label>
<input type="email" class="form-control" name="email" placeholder="email@example.com" required>
</div>
<div class="mb-0">
<label class="form-label fw-bold">Message / Details</label>
<textarea class="form-control" name="message" rows="4" placeholder="Please describe the issue or your request..." required></textarea>
</div>
</div>
<div class="modal-footer border-0 p-4 pt-0">
<button type="button" class="btn btn-light px-4 rounded-pill" data-bs-dismiss="modal">Cancel</button>
<button type="submit" class="btn btn-success px-4 rounded-pill" id="report-submit-btn">Send</button>
</div>
</form>
</div>
</div>
</div>
<!-- Sticky Download Bar (Mobile Only) -->
<div class="sticky-download-bar" id="sticky-download-bar">
<div class="d-flex align-items-center">
<img src="<?php echo $icon; ?>" class="rounded-2 me-3" width="40" height="40" alt="<?php echo $apk['title']; ?>">
<div class="flex-grow-1 overflow-hidden">
<h6 class="fw-bold mb-0 text-truncate" style="font-size: 0.9rem;"><?php echo $apk['title']; ?></h6>
<span class="x-small text-muted" style="font-size: 0.7rem;">v<?php echo $apk['version']; ?></span>
</div>
<a href="/download/<?php echo $apk['slug']; ?>" class="btn btn-success btn-sm rounded-pill px-3 fw-bold">
Download
</a>
</div>
</div>
<!-- Share FAB (Mobile Only) -->
<a href="#" class="share-fab" id="mobile-share-btn">
<i class="bi bi-share-fill"></i>
</a>
<script>
function copyShareLink() {
var copyText = document.getElementById("shareLink");
@ -148,16 +236,86 @@ function copyShareLink() {
copyText.setSelectionRange(0, 99999);
navigator.clipboard.writeText(copyText.value);
// Simple toast or alert replacement
const btn = event.target;
const originalText = btn.innerText;
btn.innerText = "<?php echo __('copy'); ?>ed!";
btn.innerText = "Copied!";
btn.classList.replace('btn-success', 'btn-dark');
setTimeout(() => {
btn.innerText = originalText;
btn.classList.replace('btn-dark', 'btn-success');
}, 2000);
}
function setReportType(type) {
document.getElementById('reportModalLabel').innerText = type;
document.getElementById('report-subject').value = type + ': <?php echo addslashes($apk['title']); ?>';
}
document.getElementById('report-form').addEventListener('submit', function(e) {
e.preventDefault();
const btn = document.getElementById('report-submit-btn');
const alertBox = document.getElementById('report-alert');
btn.disabled = true;
btn.innerHTML = '<span class="spinner-border spinner-border-sm"></span> Sending...';
const formData = new FormData(this);
fetch('/api/report', {
method: 'POST',
headers: { 'X-Requested-With': 'XMLHttpRequest' },
body: formData
})
.then(res => res.json())
.then(data => {
btn.disabled = false;
btn.innerHTML = 'Send';
if (data.success) {
alertBox.innerHTML = '<div class="alert alert-success border-0 small">' + data.success + '</div>';
setTimeout(() => {
const modal = bootstrap.Modal.getInstance(document.getElementById('reportModal'));
modal.hide();
alertBox.innerHTML = '';
this.reset();
}, 2000);
} else {
alertBox.innerHTML = '<div class="alert alert-danger border-0 small">' + data.error + '</div>';
}
})
.catch(err => {
btn.disabled = false;
btn.innerHTML = 'Send';
alertBox.innerHTML = '<div class="alert alert-danger border-0 small">An error occurred.</div>';
});
});
// Sticky Bar Logic
window.addEventListener('scroll', function() {
const mainBtn = document.getElementById('main-download-btn-area');
const stickyBar = document.getElementById('sticky-download-bar');
if (mainBtn && stickyBar) {
const rect = mainBtn.getBoundingClientRect();
if (rect.bottom < 0) {
stickyBar.classList.add('show');
} else {
stickyBar.classList.remove('show');
}
}
});
// Native Web Share API
document.getElementById('mobile-share-btn').addEventListener('click', function(e) {
e.preventDefault();
if (navigator.share) {
navigator.share({
title: '<?php echo $apk['title']; ?>',
text: 'Check out this app on ApkNusa!',
url: '<?php echo $shareLink; ?>',
}).catch((error) => console.log('Error sharing', error));
} else {
copyShareLink();
}
});
</script>
<?php include 'footer.php'; ?>
<?php include 'footer.php'; ?>

View File

@ -27,7 +27,7 @@
<div class="card shadow border-0 rounded-4 mb-4">
<div class="card-body p-4 text-center bg-light">
<h6 class="text-uppercase text-muted fw-bold mb-2"><?php echo __('balance'); ?></h6>
<h2 class="fw-bold text-success mb-3">Rp <?php echo number_format($user['balance'], 0, ',', '.'); ?></h2>
<h2 class="fw-bold text-success mb-3" id="user-balance">Rp <?php echo number_format($user['balance'], 0, ',', '.'); ?></h2>
<button class="btn btn-success btn-lg px-5 rounded-pill" data-bs-toggle="modal" data-bs-target="#withdrawModal">
<i class="fas fa-wallet me-2"></i> <?php echo __('withdraw'); ?>
</button>
@ -37,12 +37,14 @@
</div>
<div class="col-lg-8">
<?php if (isset($success)): ?>
<div class="alert alert-success border-0 rounded-4 mb-4"><?php echo $success; ?></div>
<?php endif; ?>
<?php if (isset($error)): ?>
<div class="alert alert-danger border-0 rounded-4 mb-4"><?php echo $error; ?></div>
<?php endif; ?>
<div id="alert-container">
<?php if (isset($success)): ?>
<div class="alert alert-success border-0 rounded-4 mb-4"><?php echo $success; ?></div>
<?php endif; ?>
<?php if (isset($error)): ?>
<div class="alert alert-danger border-0 rounded-4 mb-4"><?php echo $error; ?></div>
<?php endif; ?>
</div>
<div class="card shadow border-0 rounded-4 mb-4">
<div class="card-header bg-white py-3">
@ -68,7 +70,7 @@
</div>
<div class="card-body p-0">
<div class="table-responsive">
<table class="table table-hover align-middle mb-0">
<table class="table table-hover align-middle mb-0" id="withdrawal-table">
<thead class="bg-light">
<tr>
<th class="ps-4"><?php echo __('date'); ?></th>
@ -79,7 +81,7 @@
</thead>
<tbody>
<?php if (empty($withdrawals)): ?>
<tr>
<tr id="no-history-row">
<td colspan="4" class="text-center py-5 text-muted"><?php echo __('no_history'); ?></td>
</tr>
<?php endif; ?>
@ -118,8 +120,9 @@
<h5 class="modal-title fw-bold"><?php echo __('request_withdrawal'); ?></h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<form action="/withdraw" method="POST">
<form id="withdraw-form" action="/withdraw" method="POST">
<div class="modal-body p-4">
<div id="modal-alert"></div>
<div class="mb-3">
<label class="form-label fw-bold"><?php echo __('amount_to_withdraw'); ?></label>
<div class="input-group">
@ -145,7 +148,7 @@
</div>
<div class="modal-footer border-0 p-4 pt-0">
<button type="button" class="btn btn-light px-4 rounded-pill" data-bs-dismiss="modal"><?php echo __('cancel'); ?></button>
<button type="submit" class="btn btn-success px-4 rounded-pill"><?php echo __('submit_request'); ?></button>
<button type="submit" class="btn btn-success px-4 rounded-pill" id="withdraw-submit-btn"><?php echo __('submit_request'); ?></button>
</div>
</form>
</div>
@ -160,6 +163,74 @@ function copyText(id) {
navigator.clipboard.writeText(copyText.value);
alert("<?php echo __('ref_copy_success_js'); ?>");
}
document.getElementById('withdraw-form').addEventListener('submit', function(e) {
e.preventDefault();
const form = this;
const btn = document.getElementById('withdraw-submit-btn');
const modalAlert = document.getElementById('modal-alert');
const alertContainer = document.getElementById('alert-container');
const balanceEl = document.getElementById('user-balance');
const tableBody = document.querySelector('#withdrawal-table tbody');
const noHistoryRow = document.getElementById('no-history-row');
btn.disabled = true;
btn.innerHTML = '<span class="spinner-border spinner-border-sm"></span> Processing...';
const formData = new FormData(form);
fetch('/withdraw', {
method: 'POST',
headers: {
'X-Requested-With': 'XMLHttpRequest'
},
body: formData
})
.then(res => res.json())
.then(data => {
btn.disabled = false;
btn.innerHTML = '<?php echo __('submit_request'); ?>';
if (data.success) {
// Update balance
if (data.new_balance !== undefined) {
balanceEl.textContent = 'Rp ' + data.new_balance.toLocaleString('id-ID');
}
// Close modal
const modal = bootstrap.Modal.getInstance(document.getElementById('withdrawModal'));
modal.hide();
// Show success message on main page
alertContainer.innerHTML = '<div class="alert alert-success border-0 rounded-4 mb-4">' + data.success + '</div>';
// Add to table (simplified, just reload or prepend)
// For now, let's just prepend a row if we can
if (noHistoryRow) noHistoryRow.remove();
const now = new Date();
const dateStr = now.toLocaleDateString('id-ID', { day: '2-digit', month: 'short', year: 'numeric' }) + ', ' +
now.toLocaleTimeString('id-ID', { hour: '2-digit', minute: '2-digit' });
const newRow = `<tr>
<td class="ps-4">${dateStr}</td>
<td class="fw-bold text-success">Rp ${parseInt(formData.get('amount')).toLocaleString('id-ID')}</td>
<td>${formData.get('method')}</td>
<td><span class="badge bg-warning">Pending</span></td>
</tr>`;
tableBody.insertAdjacentHTML('afterbegin', newRow);
form.reset();
} else {
modalAlert.innerHTML = '<div class="alert alert-danger border-0 small">' + data.error + '</div>';
}
})
.catch(err => {
btn.disabled = false;
btn.innerHTML = '<?php echo __('submit_request'); ?>';
modalAlert.innerHTML = '<div class="alert alert-danger border-0 small">An error occurred. Please try again.</div>';
});
});
</script>
<?php include __DIR__ . '/../footer.php'; ?>
<?php include __DIR__ . '/../footer.php'; ?>

View File

@ -74,6 +74,70 @@
</div>
</footer>
<!-- Mobile Bottom Navigation -->
<nav class="mobile-bottom-nav">
<a href="/" class="mobile-nav-item <?php echo $_SERVER['REQUEST_URI'] == '/' ? 'active' : ''; ?>">
<i class="bi bi-house-door"></i>
<span><?php echo __('home'); ?></span>
</a>
<a href="#" class="mobile-nav-item" id="mobile-search-trigger">
<i class="bi bi-search"></i>
<span><?php echo __('search'); ?></span>
</a>
<a href="/blog" class="mobile-nav-item <?php echo strpos($_SERVER['REQUEST_URI'], '/blog') !== false ? 'active' : ''; ?>">
<i class="bi bi-newspaper"></i>
<span>Blog</span>
</a>
<a href="<?php echo isset($_SESSION['user_id']) ? '/profile' : '/login'; ?>" class="mobile-nav-item <?php echo (strpos($_SERVER['REQUEST_URI'], '/profile') !== false || strpos($_SERVER['REQUEST_URI'], '/login') !== false) ? 'active' : ''; ?>">
<i class="bi bi-person"></i>
<span><?php echo __('profile'); ?></span>
</a>
</nav>
<!-- WhatsApp FAB (Mobile) -->
<?php if ($wa = get_setting('whatsapp_url')): ?>
<a href="<?php echo $wa; ?>" target="_blank" class="whatsapp-fab">
<i class="bi bi-whatsapp fs-3"></i>
</a>
<?php endif; ?>
<!-- Back to Top -->
<a href="#" class="back-to-top" id="back-to-top">
<i class="bi bi-arrow-up"></i>
</a>
<!-- Search Overlay -->
<div class="search-overlay" id="search-overlay">
<button class="btn-close-search" id="close-search-overlay">
<i class="bi bi-x-lg"></i>
</button>
<div class="mt-5">
<h4 class="fw-bold mb-4">Search APKs</h4>
<form action="/" method="GET" id="ajax-search-form">
<div class="input-group input-group-lg border rounded-pill overflow-hidden shadow-sm">
<span class="input-group-text bg-white border-0 ps-3">
<i class="bi bi-search text-muted"></i>
</span>
<input type="text" name="search" class="form-control border-0 ps-1" placeholder="Search for apps or games..." autofocus>
<button class="btn btn-success px-4" type="submit">Go</button>
</div>
</form>
<div class="mt-5">
<h6 class="text-muted small fw-bold text-uppercase mb-3">Popular Categories</h6>
<div class="d-flex flex-wrap gap-2">
<?php
$db = db();
$popCats = $db->query("SELECT * FROM categories LIMIT 6")->fetchAll();
foreach ($popCats as $cat):
?>
<a href="/?category=<?php echo $cat['slug']; ?>" class="btn btn-light btn-sm rounded-pill px-3 ajax-cat-link" data-category="<?php echo $cat['slug']; ?>"><?php echo $cat['name']; ?></a>
<?php endforeach; ?>
</div>
</div>
</div>
</div>
<!-- AI Chat Assistant -->
<div id="ai-chat-wrapper" class="fixed-bottom p-3 d-flex flex-column align-items-end" style="z-index: 1050; pointer-events: none;">
<div id="ai-chat-window" class="card shadow-lg border-0 mb-3 d-none" style="width: 350px; max-width: 90vw; height: 450px; pointer-events: auto; border-radius: 20px;">
@ -119,39 +183,6 @@
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<script src="/assets/js/main.js?v=<?php echo time(); ?>"></script>
<script>
document.getElementById('newsletter-btn').addEventListener('click', function() {
const email = document.getElementById('newsletter-email').value;
const msg = document.getElementById('newsletter-msg');
if (!email) return;
this.disabled = true;
this.innerHTML = '<span class="spinner-border spinner-border-sm"></span>';
fetch('/api/newsletter/subscribe', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email: email })
})
.then(res => res.json())
.then(data => {
this.disabled = false;
this.innerHTML = '<?php echo __("subscribe"); ?>';
if (data.success) {
msg.innerHTML = '<span class="text-success">' + data.success + '</span>';
document.getElementById('newsletter-email').value = '';
} else {
msg.innerHTML = '<span class="text-danger">' + data.error + '</span>';
}
})
.catch(err => {
this.disabled = false;
this.innerHTML = '<?php echo __("subscribe"); ?>';
msg.innerHTML = '<span class="text-danger">An error occurred.</span>';
});
});
</script>
<?php echo get_setting('body_js'); ?>
</body>
</html>
</html>

View File

@ -1,42 +1,66 @@
<?php include 'header.php'; ?>
<div class="container">
<div class="row align-items-center mb-5 mt-5">
<div class="row align-items-center mb-5 mt-4 mt-lg-5 text-center text-lg-start">
<div class="col-lg-7">
<h1 class="display-4 fw-bold mb-3"><?php echo __('hero_title'); ?></h1>
<p class="lead text-muted mb-4"><?php echo __('hero_subtitle'); ?></p>
<p class="lead text-muted mb-4 px-3 px-lg-0"><?php echo __('hero_subtitle'); ?></p>
<div class="d-flex gap-2">
<div class="d-flex gap-2 justify-content-center justify-content-lg-start">
<a href="#latest" class="btn btn-success btn-lg px-4 rounded-pill"><?php echo __('explore_apps'); ?></a>
<a href="/register" class="btn btn-outline-dark btn-lg px-4 rounded-pill border-1"><?php echo __('join_referral'); ?></a>
</div>
</div>
<div class="col-lg-5 d-none d-lg-block text-center">
<div class="position-relative">
<!-- Dynamic Hero Circle -->
<div class="position-absolute rounded-circle" style="width: 400px; height: 400px; top: -50px; right: -50px; z-index: -1; filter: blur(60px); opacity: 0.15; animation: color-cycle 20s infinite, floating 10s infinite ease-in-out;"></div>
<img src="https://img.icons8.com/color/512/android-os.png" class="img-fluid floating-animation" alt="Android APKs" style="max-height: 350px;">
</div>
</div>
</div>
<!-- Featured Section (Horizontal Scroll on Mobile) -->
<?php
$featuredApks = $db->query("SELECT * FROM apks WHERE is_vip = 1 LIMIT 10")->fetchAll();
if ($featuredApks):
?>
<section class="mb-5">
<h4 class="fw-bold mb-3 d-flex align-items-center">
<i class="bi bi-fire text-danger me-2"></i> Featured Apps
</h4>
<div class="d-flex overflow-auto pb-3 featured-scroll" style="scrollbar-width: none; -ms-overflow-style: none;">
<?php foreach ($featuredApks as $f): ?>
<div class="flex-shrink-0 me-3" style="width: 130px;">
<a href="/apk/<?php echo $f['slug']; ?>" class="text-decoration-none">
<div class="card border-0 shadow-sm rounded-4 text-center p-2 h-100 hover-lift">
<img src="<?php echo !empty($f['icon_path']) ? '/'.$f['icon_path'] : $f['image_url']; ?>" class="rounded-3 mx-auto mb-2 shadow-sm" width="56" height="56" style="object-fit: cover;">
<h6 class="card-title fw-bold mb-0 text-truncate small" style="font-size: 0.75rem;"><?php echo $f['title']; ?></h6>
<span class="x-small text-muted" style="font-size: 0.65rem;"><?php echo $f['version']; ?></span>
</div>
</a>
</div>
<?php endforeach; ?>
</div>
</section>
<style>.featured-scroll::-webkit-scrollbar { display: none; }</style>
<?php endif; ?>
<section id="latest" class="mb-5">
<div class="d-flex justify-content-between align-items-center mb-4">
<h2 class="fw-bold mb-0" id="latest-title">
<div class="d-flex justify-content-between align-items-center mb-3">
<h2 class="fw-bold mb-0 h4" id="latest-title">
<?php if (!empty($_GET['search'])): ?>
<?php echo __('search_results_for', 'Search results for'); ?>: "<?php echo htmlspecialchars($_GET['search']); ?>"
<?php else: ?>
<?php echo __('latest_apks'); ?>
<?php endif; ?>
</h2>
<div class="dropdown">
<button class="btn btn-white shadow-sm border rounded-pill dropdown-toggle" type="button" data-bs-toggle="dropdown" id="category-dropdown-btn">
<?php echo __('categories'); ?> <i class="bi bi-chevron-down ms-1 small"></i>
<div class="dropdown d-none d-md-block">
<button class="btn btn-white shadow-sm border rounded-pill dropdown-toggle btn-sm" type="button" data-bs-toggle="dropdown" id="category-dropdown-btn">
<?php echo __('categories'); ?>
</button>
<ul class="dropdown-menu dropdown-menu-end shadow border-0" id="category-menu">
<li><a class="dropdown-item category-filter" href="/" data-category=""><?php echo __('all_categories'); ?></a></li>
<?php
$db = db();
$categories = $db->query("SELECT * FROM categories")->fetchAll();
foreach ($categories as $cat): ?>
<li><a class="dropdown-item category-filter" href="/?category=<?php echo $cat['slug']; ?>" data-category="<?php echo $cat['slug']; ?>"><?php echo $cat['name']; ?></a></li>
@ -44,26 +68,56 @@
</ul>
</div>
</div>
<!-- Category Chips for Mobile & Quick Filter -->
<div class="d-flex overflow-auto pb-3 mb-4 category-scroll" style="scrollbar-width: none; -ms-overflow-style: none;">
<a href="/" class="btn <?php echo !isset($_GET['category']) ? 'btn-success' : 'btn-light'; ?> rounded-pill px-4 me-2 flex-shrink-0 btn-sm shadow-sm ajax-cat-link" data-category=""><?php echo __('all'); ?></a>
<?php foreach ($categories as $cat): ?>
<?php $isActive = isset($_GET['category']) && $_GET['category'] == $cat['slug']; ?>
<a href="/?category=<?php echo $cat['slug']; ?>" class="btn <?php echo $isActive ? 'btn-success' : 'btn-light'; ?> rounded-pill px-4 me-2 flex-shrink-0 btn-sm shadow-sm ajax-cat-link" data-category="<?php echo $cat['slug']; ?>"><?php echo $cat['name']; ?></a>
<?php endforeach; ?>
</div>
<style>.category-scroll::-webkit-scrollbar { display: none; }</style>
<div id="apk-grid-container">
<?php include 'partials/apk_list.php'; ?>
</div>
</section>
<div class="bg-dark text-white p-5 rounded-5 mt-5 mb-5 shadow-lg position-relative overflow-hidden">
<!-- Subtle dark mode blob -->
<!-- Referral Banner -->
<div class="bg-dark text-white p-4 p-md-5 rounded-5 mt-5 mb-5 shadow-lg position-relative overflow-hidden">
<div class="position-absolute bg-success opacity-10 rounded-circle" style="width: 300px; height: 300px; bottom: -100px; left: -100px; filter: blur(50px);"></div>
<div class="row align-items-center text-center text-lg-start position-relative">
<div class="col-lg-8">
<h2 class="fw-bold mb-3"><?php echo __('referral_journey_title'); ?></h2>
<p class="mb-0 text-white-50"><?php echo __('referral_journey_text'); ?></p>
<h2 class="fw-bold mb-3 h3"><?php echo __('referral_journey_title'); ?></h2>
<p class="mb-0 text-white-50 small"><?php echo __('referral_journey_text'); ?></p>
</div>
<div class="col-lg-4 text-center text-lg-end mt-4 mt-lg-0">
<a href="/register" class="btn btn-success btn-lg px-5 rounded-pill"><?php echo __('get_started'); ?></a>
<a href="/register" class="btn btn-success btn-lg px-5 rounded-pill w-100 w-lg-auto"><?php echo __('get_started'); ?></a>
</div>
</div>
</div>
<!-- Newsletter Section -->
<section class="py-5 mb-5">
<div class="card border-0 shadow-lg rounded-5 overflow-hidden">
<div class="card-body p-4 p-md-5">
<div class="row align-items-center">
<div class="col-lg-6 mb-4 mb-lg-0">
<h3 class="fw-bold mb-2">Subscribe to our Newsletter</h3>
<p class="text-muted mb-0">Get notified about the latest APKs and updates directly in your inbox.</p>
</div>
<div class="col-lg-6">
<form action="/newsletter/subscribe" method="POST" class="d-flex gap-2 p-1 bg-light rounded-pill border shadow-sm">
<input type="email" name="email" class="form-control border-0 bg-transparent px-4 py-2" placeholder="Enter your email" required>
<button type="submit" class="btn btn-success rounded-pill px-4">Subscribe</button>
</form>
</div>
</div>
</div>
</div>
</section>
</div>
<?php include 'footer.php'; ?>

View File

@ -6,31 +6,29 @@
<a href="/" class="btn btn-outline-success rounded-pill px-4 mt-2"><?php echo __('view_all_apks', 'View All APKs'); ?></a>
</div>
<?php else: ?>
<div class="row g-3 g-md-4">
<div class="row g-2 g-md-4">
<?php foreach ($apks as $apk): ?>
<div class="col-4 col-md-4">
<div class="col-4 col-md-4 col-lg-3">
<div class="card h-100 border-0 shadow-sm rounded-4 hover-lift">
<div class="card-body p-2 p-md-4 text-center text-md-start">
<div class="d-md-flex align-items-center mb-2 mb-md-3">
<?php
$icon = !empty($apk['icon_path']) ? '/'.$apk['icon_path'] : $apk['image_url'];
?>
<div class="mx-auto mx-md-0 mb-2 mb-md-0" style="width: 50px; height: 50px;">
<img src="<?php echo $icon; ?>" class="rounded-3" width="50" height="50" alt="<?php echo $apk['title']; ?>" style="object-fit: cover; width: 50px; height: 50px;">
</div>
<div class="ms-md-3 flex-grow-1 overflow-hidden">
<h6 class="card-title fw-bold mb-0 text-truncate"><?php echo $apk['title']; ?></h6>
<span class="badge bg-light text-dark fw-normal d-none d-md-inline-block">v<?php echo $apk['version']; ?></span>
</div>
<div class="card-body p-2 p-md-3 text-center d-flex flex-column h-100">
<?php
$icon = !empty($apk['icon_path']) ? '/'.$apk['icon_path'] : $apk['image_url'];
?>
<div class="mx-auto mb-2" style="width: 50px; height: 50px; min-height: 50px;">
<img src="<?php echo $icon; ?>" class="rounded-3 shadow-sm" width="50" height="50" alt="<?php echo $apk['title']; ?>" style="object-fit: cover; width: 50px; height: 50px;">
</div>
<p class="card-text text-muted small mb-3 line-clamp-2 d-none d-md-block"><?php echo $apk['description']; ?></p>
<div class="d-flex flex-column flex-md-row justify-content-between align-items-center gap-2">
<span class="text-muted small d-none d-md-inline-block"><i class="fas fa-download me-1"></i> <?php echo number_format($apk['total_downloads']); ?></span>
<a href="/apk/<?php echo $apk['slug']; ?>" class="btn btn-success rounded-pill px-3 btn-sm fw-medium w-100 w-md-auto"><?php echo __('details'); ?></a>
<h6 class="card-title fw-bold mb-1 text-truncate" style="font-size: 0.75rem;"><?php echo $apk['title']; ?></h6>
<div class="mb-2">
<span class="badge bg-light text-muted fw-normal x-small" style="font-size: 0.65rem;">v<?php echo $apk['version']; ?></span>
</div>
<div class="mt-auto">
<a href="/apk/<?php echo $apk['slug']; ?>" class="btn btn-success rounded-pill py-1 btn-sm fw-medium w-100" style="font-size: 0.7rem;"><?php echo __('details'); ?></a>
</div>
</div>
</div>
</div>
<?php endforeach; ?>
</div>
<?php endif; ?>
<?php endif; ?>