diff --git a/app/Controllers/HomeController.php b/app/Controllers/HomeController.php
index 0576d27..23ab6a0 100644
--- a/app/Controllers/HomeController.php
+++ b/app/Controllers/HomeController.php
@@ -19,6 +19,14 @@ class HomeController extends Controller {
$apkService = new ApkService();
$apks = $apkService->getAllApks($category, $search);
+ // Handle AJAX requests for filtering/searching
+ if (!empty($_SERVER['HTTP_X_REQUESTED_WITH']) && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) == 'xmlhttprequest') {
+ $this->view('partials/apk_list', [
+ 'apks' => $apks
+ ]);
+ return;
+ }
+
$this->view('home', [
'apks' => $apks,
'title' => get_setting('site_name', 'ApkNusa') . __('home_title_suffix')
diff --git a/assets/css/custom.css b/assets/css/custom.css
index 606ae33..f401f06 100644
--- a/assets/css/custom.css
+++ b/assets/css/custom.css
@@ -1,13 +1,51 @@
+:root {
+ --bg-color: #ffffff;
+ --text-color: #1E293B;
+ --card-bg: #FFFFFF;
+ --navbar-bg: rgba(255, 255, 255, 0.8);
+ --border-color: rgba(0, 0, 0, 0.05);
+ --subtle-bg: #f8fafc;
+ --muted-text: #64748b;
+ --footer-bg: #ffffff;
+ --accent-color: #10B981;
+}
+
+[data-theme="dark"] {
+ --bg-color: #0f172a;
+ --text-color: #f1f5f9;
+ --card-bg: #1e293b;
+ --navbar-bg: rgba(15, 23, 42, 0.8);
+ --border-color: rgba(255, 255, 255, 0.1);
+ --subtle-bg: #1e293b;
+ --muted-text: #94a3b8;
+ --footer-bg: #0f172a;
+ --accent-color: #34D399;
+}
+
body {
font-family: 'Inter', sans-serif;
- /* Animated gradient background for a subtle dynamic feel */
- background: linear-gradient(-45deg, #ffffff, #f8fafc, #f1f5f9, #f8fafc);
+ background: var(--bg-color);
background-size: 400% 400%;
- animation: gradientBG 15s ease infinite;
background-attachment: fixed;
- color: #1E293B;
+ color: var(--text-color);
position: relative;
min-height: 100vh;
+ transition: background-color 0.3s ease, color 0.3s ease;
+}
+
+h1, h2, h3, h4, h5, h6, .h1, .h2, .h3, .h4, .h5, .h6 {
+ color: var(--text-color) !important;
+}
+
+/* Ensure headings inside light-text containers are visible */
+.text-white h1, .text-white h2, .text-white h3, .text-white h4, .text-white h5, .text-white h6,
+.bg-dark h1, .bg-dark h2, .bg-dark h3, .bg-dark h4, .bg-dark h5, .bg-dark h6 {
+ color: #ffffff !important;
+}
+
+body.animated-bg {
+ background: linear-gradient(-45deg, var(--bg-color), var(--subtle-bg), var(--bg-color), var(--subtle-bg));
+ animation: gradientBG 15s ease infinite;
}
@keyframes gradientBG {
@@ -16,7 +54,7 @@ body {
100% { background-position: 0% 50%; }
}
-/* Background blobs are visible and animated */
+/* Background blobs */
.bg-blob {
display: block !important;
pointer-events: none;
@@ -80,15 +118,20 @@ body {
background-color: #ECFDF5 !important;
}
+[data-theme="dark"] .bg-success-subtle {
+ background-color: rgba(16, 185, 129, 0.1) !important;
+}
+
.rounded-4 { border-radius: 1rem !important; }
.rounded-5 { border-radius: 1.5rem !important; }
-/* Navbar styling with glassmorphism */
+/* Navbar styling */
.navbar {
- background: rgba(255, 255, 255, 0.8) !important;
+ background: var(--navbar-bg) !important;
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
- border-bottom: 1px solid rgba(255, 255, 255, 0.3);
+ border-bottom: 1px solid var(--border-color) !important;
+ transition: background-color 0.3s ease, border-color 0.3s ease;
}
.navbar-brand {
@@ -97,12 +140,215 @@ body {
/* Card styling */
.card {
- border: none;
- background-color: #FFFFFF;
+ border: 1px solid var(--border-color);
+ background-color: var(--card-bg);
+ color: var(--text-color);
+ transition: background-color 0.3s ease, border-color 0.3s ease, color 0.3s ease;
}
-/* Mobile adjustments to match the screenshot */
-@media (max-width: 767.98px) {
+.card-title {
+ color: var(--text-color);
+}
+
+.text-muted {
+ color: var(--muted-text) !important;
+}
+
+/* Theme toggle button styling */
+.theme-toggle-btn {
+ cursor: pointer;
+ padding: 8px;
+ border-radius: 50%;
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
+ width: 42px;
+ height: 42px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ border: 1px solid var(--border-color);
+ background: var(--subtle-bg);
+ color: var(--text-color);
+ position: relative;
+ overflow: hidden;
+}
+
+.theme-toggle-btn:hover {
+ background-color: var(--border-color);
+ transform: rotate(15deg) scale(1.05);
+}
+
+.theme-toggle-btn i {
+ font-size: 1.1rem;
+ transition: transform 0.5s cubic-bezier(0.4, 0, 0.2, 1);
+}
+
+[data-theme="dark"] .theme-toggle-btn {
+ box-shadow: 0 0 15px rgba(245, 158, 11, 0.2);
+ border-color: rgba(245, 158, 11, 0.3);
+}
+
+[data-theme="dark"] .theme-toggle-btn i {
+ color: #F59E0B;
+}
+
+/* Language selector polish */
+.lang-selector-btn {
+ background: var(--subtle-bg);
+ border: 1px solid var(--border-color);
+ border-radius: 12px;
+ padding: 0.5rem 1rem;
+ font-size: 0.9rem;
+ font-weight: 500;
+ color: var(--text-color);
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ transition: all 0.2s ease;
+}
+
+.lang-selector-btn:hover {
+ background-color: var(--border-color);
+ border-color: var(--muted-text);
+}
+
+.lang-selector-btn i {
+ color: var(--accent-color);
+}
+
+/* Override Bootstrap utilities for dark mode */
+[data-theme="dark"] .bg-white,
+[data-theme="dark"] .btn-white {
+ background-color: var(--card-bg) !important;
+ color: var(--text-color) !important;
+}
+
+[data-theme="dark"] .bg-light {
+ background-color: var(--subtle-bg) !important;
+}
+
+[data-theme="dark"] .border-top,
+[data-theme="dark"] .border-bottom,
+[data-theme="dark"] .border {
+ border-color: var(--border-color) !important;
+}
+
+[data-theme="dark"] footer.bg-white {
+ background-color: var(--footer-bg) !important;
+}
+
+[data-theme="dark"] .list-unstyled a.text-muted:hover {
+ color: var(--text-color) !important;
+}
+
+[data-theme="dark"] .form-control {
+ background-color: var(--subtle-bg);
+ border-color: var(--border-color);
+ color: var(--text-color);
+}
+
+[data-theme="dark"] .form-control::placeholder {
+ color: var(--muted-text);
+}
+
+[data-theme="dark"] .dropdown-menu {
+ background-color: var(--card-bg);
+ border-color: var(--border-color);
+ box-shadow: 0 1rem 3rem rgba(0, 0, 0, 0.4) !important;
+ padding: 0.5rem;
+ border-radius: 1rem;
+}
+
+[data-theme="dark"] .dropdown-item {
+ color: var(--text-color);
+ border-radius: 0.5rem;
+}
+
+[data-theme="dark"] .dropdown-item:hover {
+ background-color: var(--subtle-bg);
+ color: var(--text-color);
+}
+
+[data-theme="dark"] .nav-link {
+ color: var(--text-color);
+}
+
+[data-theme="dark"] .nav-link:hover {
+ color: #10B981;
+}
+
+[data-theme="dark"] .btn-outline-dark {
+ border-color: var(--text-color);
+ color: var(--text-color);
+}
+
+[data-theme="dark"] .btn-outline-dark:hover {
+ background-color: var(--text-color);
+ color: var(--bg-color);
+}
+
+[data-theme="dark"] .badge.bg-light {
+ background-color: rgba(255,255,255,0.1) !important;
+ color: var(--text-color) !important;
+}
+
+[data-theme="dark"] .breadcrumb-item.active {
+ color: var(--muted-text) !important;
+}
+
+[data-theme="dark"] .navbar-toggler-icon {
+ filter: invert(1) grayscale(1) brightness(2);
+}
+
+/* Mobile adjustments */
+@media (max-width: 991.98px) {
+ .navbar-collapse {
+ background-color: var(--card-bg);
+ margin: 0 -1rem;
+ padding: 1rem;
+ border-radius: 0 0 1.5rem 1.5rem;
+ border: 1px solid var(--border-color);
+ border-top: none;
+ box-shadow: 0 1.5rem 3rem rgba(0,0,0,0.1);
+ }
+
+ [data-theme="dark"] .navbar-collapse {
+ box-shadow: 0 1.5rem 3rem rgba(0,0,0,0.4);
+ }
+
+ .navbar-nav .nav-item {
+ width: 100%;
+ padding: 0.15rem 0;
+ }
+
+ .navbar-nav .nav-link {
+ padding: 0.85rem 1.25rem !important;
+ border-radius: 0.75rem;
+ font-weight: 500;
+ }
+
+ .navbar-nav .nav-link:hover {
+ background-color: var(--subtle-bg);
+ }
+
+ .navbar-nav .dropdown-menu {
+ background-color: var(--subtle-bg);
+ border: none;
+ box-shadow: none !important;
+ margin: 0.5rem 1rem;
+ padding: 0.5rem;
+ }
+
+ .mobile-controls-row {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 1rem 1.25rem;
+ background: var(--subtle-bg);
+ border-radius: 1rem;
+ margin-top: 0.75rem;
+ border: 1px solid var(--border-color);
+ }
+
.display-4 {
font-size: 2rem !important;
line-height: 1.2;
@@ -120,4 +366,4 @@ body {
.card-title {
font-size: 0.85rem;
}
-}
\ No newline at end of file
+}
diff --git a/assets/js/main.js b/assets/js/main.js
index 236a20d..d4a6888 100644
--- a/assets/js/main.js
+++ b/assets/js/main.js
@@ -1,11 +1,137 @@
document.addEventListener('DOMContentLoaded', () => {
// Basic interaction for toasts
- const toasts = document.querySelectorAll('.toast');
- toasts.forEach(toastEl => {
- const toast = new bootstrap.Toast(toastEl, { delay: 5000 });
- toast.show();
- });
+ try {
+ const toasts = document.querySelectorAll('.toast');
+ toasts.forEach(toastEl => {
+ if (window.bootstrap && bootstrap.Toast) {
+ const toast = new bootstrap.Toast(toastEl, { delay: 5000 });
+ toast.show();
+ }
+ });
+ } catch (e) {
+ console.error('Toast error:', e);
+ }
+
+ 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') {
+ icon.className = 'fa-solid fa-sun';
+ } else {
+ icon.className = 'fa-solid fa-moon';
+ }
+ });
+
+ // Update all theme status texts
+ const textLabels = document.querySelectorAll('.theme-status-text');
+ textLabels.forEach(label => {
+ label.textContent = theme === 'dark' ? 'Dark Mode' : 'Light Mode';
+ });
+ };
+
+ // Theme Toggle Logic
+ const initThemeToggle = (btnId) => {
+ const themeToggle = document.getElementById(btnId);
+ if (!themeToggle) return;
+
+ 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);
+ });
+ };
+
+ // 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');
+
+ if (!gridContainer || filters.length === 0) return;
+
+ filters.forEach(filter => {
+ filter.addEventListener('click', (e) => {
+ e.preventDefault();
+
+ const category = filter.getAttribute('data-category');
+ const url = filter.getAttribute('href');
+ const categoryName = filter.textContent;
+
+ // 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} `;
+ }
+
+ // Update title if not searching
+ if (latestTitle && !url.includes('search=')) {
+ // We could use translations here but for simplicity we'll just use the category name
+ // if it's All Categories, we'll reset to original (usually "Latest APKs")
+ // However, we'll just keep it simple for now.
+ }
+
+ // 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';
+ });
+ });
+ });
+
+ // Handle browser back/forward
+ window.addEventListener('popstate', (e) => {
+ window.location.reload(); // Simple solution for now
+ });
+ };
+
+ // 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();
- // Lazy load or pre-fetch images if needed
console.log('ApkNusa ready.');
-});
\ No newline at end of file
+});
diff --git a/assets/pasted-20260225-185350-6cccde68.jpg b/assets/pasted-20260225-185350-6cccde68.jpg
new file mode 100644
index 0000000..9d3ed86
Binary files /dev/null and b/assets/pasted-20260225-185350-6cccde68.jpg differ
diff --git a/assets/pasted-20260225-185708-99182cde.jpg b/assets/pasted-20260225-185708-99182cde.jpg
new file mode 100644
index 0000000..20ac142
Binary files /dev/null and b/assets/pasted-20260225-185708-99182cde.jpg differ
diff --git a/assets/pasted-20260225-190048-5c3f7756.jpg b/assets/pasted-20260225-190048-5c3f7756.jpg
new file mode 100644
index 0000000..31411c2
Binary files /dev/null and b/assets/pasted-20260225-190048-5c3f7756.jpg differ
diff --git a/assets/pasted-20260225-191611-248aed3b.jpg b/assets/pasted-20260225-191611-248aed3b.jpg
new file mode 100644
index 0000000..0ea6f98
Binary files /dev/null and b/assets/pasted-20260225-191611-248aed3b.jpg differ
diff --git a/views/admin/footer.php b/views/admin/footer.php
index 793508f..919a76f 100644
--- a/views/admin/footer.php
+++ b/views/admin/footer.php
@@ -1,12 +1,73 @@
-