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 @@ - - + \ No newline at end of file diff --git a/views/header.php b/views/header.php index 1033dbc..409a113 100644 --- a/views/header.php +++ b/views/header.php @@ -1,5 +1,11 @@ +query("SELECT * FROM categories ORDER BY name ASC")->fetchAll(); +$currentLang = \App\Services\LanguageService::getLang(); +?> - + @@ -9,7 +15,7 @@ - + @@ -18,9 +24,19 @@ + + - - + +
@@ -31,15 +47,37 @@ 100% { transform: translate(15%, 15%) scale(1.2); } } @keyframes color-cycle { - 0% { background-color: #10B981; } /* Success Green */ - 25% { background-color: #3B82F6; } /* Primary Blue */ - 50% { background-color: #F59E0B; } /* Warning Amber */ - 75% { background-color: #EC4899; } /* Pink */ + 0% { background-color: #10B981; } + 25% { background-color: #3B82F6; } + 50% { background-color: #F59E0B; } + 75% { background-color: #EC4899; } 100% { background-color: #10B981; } } + + @media (max-width: 991.98px) { + .mobile-theme-row { + display: flex; + align-items: center; + justify-content: space-between; + background: var(--subtle-bg); + padding: 0.65rem 1rem; + border-radius: 12px; + border: 1px solid var(--border-color); + margin-bottom: 0.75rem; + } + .mobile-lang-row { + display: flex; + align-items: center; + justify-content: space-between; + background: var(--subtle-bg); + padding: 0.65rem 1rem; + border-radius: 12px; + border: 1px solid var(--border-color); + } + } -