responsive mobile

This commit is contained in:
ramzdhani11 2026-05-12 01:10:29 +07:00
parent cc5936051e
commit cd3a6052d5
6 changed files with 543 additions and 296 deletions

View File

@ -100,21 +100,30 @@
@section('scripts') @section('scripts')
<script> <script>
/* ========================= // Tunggu sampai Chart.js library loaded
TREND LINE function initCharts() {
========================= */ if (typeof Chart === 'undefined') {
const trendLabels = @json( console.warn('Chart.js belum loaded, retry dalam 100ms');
setTimeout(initCharts, 100);
return;
}
/* =========================
TREND LINE
========================= */
const trendLabels = @json(
$trend->pluck('date')->map(fn($d) => $trend->pluck('date')->map(fn($d) =>
\Carbon\Carbon::parse($d)->translatedFormat('D') \Carbon\Carbon::parse($d)->translatedFormat('D')
) )
); );
const trendData = @json($trend->pluck('total')); const trendData = @json($trend->pluck('total'));
// Optimize for mobile // Optimize for mobile
const isMobile = window.innerWidth < 768; const isMobile = window.innerWidth < 768;
new Chart(document.getElementById('trendChart'), { try {
new Chart(document.getElementById('trendChart'), {
type: 'line', type: 'line',
data: { data: {
labels: trendLabels, labels: trendLabels,
@ -152,21 +161,23 @@
} }
} }
} }
}); });
} catch (error) {
console.error('Error initializing trend chart:', error);
}
/* =========================
/* ========================= PIE CHART
PIE CHART ========================= */
========================= */ const distLabels = @json(
const distLabels = @json(
$distribution->pluck('category')->map( $distribution->pluck('category')->map(
fn($c) => ucfirst(str_replace('_',' ',$c)) fn($c) => ucfirst(str_replace('_',' ',$c))
) )
); );
const distData = @json($distribution->pluck('total')); const distData = @json($distribution->pluck('total'));
const dynamicColors = distLabels.map(label => { const dynamicColors = distLabels.map(label => {
if (label.toLowerCase() === 'matang') { if (label.toLowerCase() === 'matang') {
return '#ef4444'; // merah return '#ef4444'; // merah
} }
@ -176,9 +187,10 @@
else { else {
return '#f59e0b'; // kuning/orange return '#f59e0b'; // kuning/orange
} }
}); });
new Chart(document.getElementById('distributionChart'), { try {
new Chart(document.getElementById('distributionChart'), {
type: 'pie', type: 'pie',
data: { data: {
labels: distLabels, labels: distLabels,
@ -207,7 +219,14 @@
} }
} }
} }
}); });
} catch (error) {
console.error('Error initializing distribution chart:', error);
}
}
// Initialize charts saat DOM ready
document.addEventListener('DOMContentLoaded', initCharts);
</script> </script>
@endsection @endsection

View File

@ -17,9 +17,6 @@
<!-- DNS Prefetch untuk domain eksternal --> <!-- DNS Prefetch untuk domain eksternal -->
<link rel="dns-prefetch" href="https://cdn.tailwindcss.com"> <link rel="dns-prefetch" href="https://cdn.tailwindcss.com">
<!-- Tailwind CSS dengan preload -->
<script src="https://cdn.tailwindcss.com" defer></script>
<!-- Google Fonts - Inter dengan font-display: swap (non-blocking) --> <!-- Google Fonts - Inter dengan font-display: swap (non-blocking) -->
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet" media="print" onload="this.media='all'"> <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet" media="print" onload="this.media='all'">
<noscript><link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet"></noscript> <noscript><link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet"></noscript>
@ -30,11 +27,18 @@
<!-- CSRF Token --> <!-- CSRF Token -->
<meta name="csrf-token" content="{{ csrf_token() }}"> <meta name="csrf-token" content="{{ csrf_token() }}">
<!-- Chart.js (defer loading untuk non-blocking) --> <!-- Chart.js (tanpa defer agar library siap sebelum scripts lain) -->
@if(request()->routeIs('admin.dashboard') || request()->routeIs('admin.system-statistics')) @if(request()->routeIs('admin.dashboard') || request()->routeIs('admin.system-statistics'))
<script src="https://cdn.jsdelivr.net/npm/chart.js" defer></script> <script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js"></script>
@endif @endif
<!-- Anti-flash dark mode script -->
<script>
if (localStorage.getItem('theme') === 'dark') {
document.documentElement.classList.add('dark');
}
</script>
<style> <style>
/* Performance optimization: Use system fonts for faster render */ /* Performance optimization: Use system fonts for faster render */
body { body {
@ -300,27 +304,88 @@
transform: translateY(-2px); transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
} }
/* ===== SIDEBAR RESPONSIVE ===== */
#sidebar-wrapper {
position: fixed !important;
top: 0;
left: 0;
width: 260px;
height: 100% !important;
z-index: 50;
transform: translateX(-100%) !important;
transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1) !important;
overflow-y: auto;
will-change: transform;
background: #fff;
}
#sidebar-wrapper.is-open {
transform: translateX(0) !important;
box-shadow: 4px 0 24px rgba(0,0,0,0.18);
}
@media (min-width: 768px) {
#sidebar-wrapper {
position: relative !important;
width: 256px !important;
height: auto !important;
transform: translateX(0) !important;
box-shadow: none !important;
transition: none !important;
z-index: auto !important;
flex-shrink: 0;
}
}
#sidebar-overlay {
display: none;
position: fixed;
inset: 0;
background: rgba(0,0,0,0.5);
z-index: 45;
opacity: 0;
transition: opacity 0.3s ease;
}
#sidebar-overlay.is-active {
display: block;
opacity: 1;
}
@media (min-width: 768px) {
#sidebar-overlay { display: none !important; }
}
.main-content-area {
min-width: 0;
flex: 1;
overflow: auto;
}
</style> </style>
</head> </head>
<body class="bg-gray-50"> <body class="bg-gray-50">
<div class="flex h-screen overflow-hidden"> <div class="flex h-screen overflow-hidden">
<!-- Sidebar - Fixed on Desktop, Toggleable on Mobile --> <!-- Sidebar - Fixed on Desktop, Toggleable on Mobile -->
<div id="sidebar-wrapper" class="fixed md:relative md:w-64 w-64 h-screen bg-white border-r border-gray-200 transform -translate-x-full md:translate-x-0 transition-transform duration-300 z-30"> <div id="sidebar-wrapper" class="bg-white border-r border-gray-200">
@include('Admin.partials.sidebar') @include('Admin.partials.sidebar')
</div> </div>
<!-- Mobile Overlay (Hidden by default) --> <!-- Mobile Overlay (Hidden by default) -->
<div id="sidebar-overlay" class="fixed inset-0 bg-black bg-opacity-50 hidden z-20"></div> <div id="sidebar-overlay"></div>
<!-- Main Content --> <!-- Main Content -->
<div class="flex-1 overflow-auto"> <div class="main-content-area">
<!-- Top Navigation --> <!-- Top Navigation -->
<header class="bg-white shadow-sm border-b border-gray-200 sticky top-0 z-10"> <header class="bg-white shadow-sm border-b border-gray-200 sticky top-0 z-10">
<div class="flex items-center justify-between px-4 md:px-6 py-3 md:py-4"> <div class="flex items-center justify-between px-4 md:px-6 py-3 md:py-4">
<div class="flex items-center flex-1"> <div class="flex items-center flex-1">
<!-- Mobile Menu Toggle --> <!-- Mobile Menu Toggle -->
<button id="menuToggle" class="md:hidden p-2 hover:bg-gray-100 rounded-lg transition-colors mr-2"> <button id="menuToggle"
<i class="fas fa-bars text-xl text-gray-600"></i> class="md:hidden flex items-center justify-center"
style="min-width:44px; min-height:44px; background:transparent; border:none; cursor:pointer; margin-right:0.5rem;"
aria-label="Buka menu"
aria-expanded="false">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none"
xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
<rect y="4" width="24" height="2.5" rx="1.25" fill="#374151"/>
<rect y="11" width="24" height="2.5" rx="1.25" fill="#374151"/>
<rect y="18" width="24" height="2.5" rx="1.25" fill="#374151"/>
</svg>
</button> </button>
<!-- Page Title (Mobile) --> <!-- Page Title (Mobile) -->
<h1 class="text-base md:text-lg font-semibold text-gray-900">@yield('page-title')</h1> <h1 class="text-base md:text-lg font-semibold text-gray-900">@yield('page-title')</h1>
@ -407,61 +472,51 @@
const sidebarWrapper = document.getElementById('sidebar-wrapper'); const sidebarWrapper = document.getElementById('sidebar-wrapper');
const sidebarOverlay = document.getElementById('sidebar-overlay'); const sidebarOverlay = document.getElementById('sidebar-overlay');
// Toggle sidebar saat hamburger diklik function openSidebar() {
sidebarWrapper.classList.add('is-open');
sidebarOverlay.classList.add('is-active');
document.body.style.overflow = 'hidden';
if (menuToggle) menuToggle.setAttribute('aria-expanded', 'true');
}
function closeSidebar() {
sidebarWrapper.classList.remove('is-open');
sidebarOverlay.classList.remove('is-active');
document.body.style.overflow = '';
if (menuToggle) menuToggle.setAttribute('aria-expanded', 'false');
}
if (menuToggle) { if (menuToggle) {
menuToggle.addEventListener('click', function(e) { menuToggle.addEventListener('click', function(e) {
e.stopPropagation(); e.stopPropagation();
const isHidden = sidebarWrapper.classList.contains('-translate-x-full'); sidebarWrapper.classList.contains('is-open') ? closeSidebar() : openSidebar();
if (isHidden) {
// Buka sidebar
sidebarWrapper.classList.remove('-translate-x-full');
sidebarOverlay.classList.remove('hidden');
} else {
// Tutup sidebar
sidebarWrapper.classList.add('-translate-x-full');
sidebarOverlay.classList.add('hidden');
}
}); });
} }
// Tutup sidebar saat overlay diklik
if (sidebarOverlay) { if (sidebarOverlay) {
sidebarOverlay.addEventListener('click', function() { sidebarOverlay.addEventListener('click', closeSidebar);
sidebarWrapper.classList.add('-translate-x-full');
sidebarOverlay.classList.add('hidden');
});
} }
// Tutup sidebar saat menu link diklik (mobile only) document.querySelectorAll('#sidebar-wrapper .sidebar-link, #sidebar-wrapper a').forEach(function(link) {
const navLinks = document.querySelectorAll('.sidebar-link');
navLinks.forEach(link => {
link.addEventListener('click', function() { link.addEventListener('click', function() {
// Hanya tutup di mobile (< 768px) if (window.innerWidth < 768) closeSidebar();
if (window.innerWidth < 768) {
sidebarWrapper.classList.add('-translate-x-full');
sidebarOverlay.classList.add('hidden');
}
}); });
}); });
// Handle resize untuk responsive behavior document.addEventListener('keydown', function(e) {
if (e.key === 'Escape') closeSidebar();
});
let resizeTimer; let resizeTimer;
window.addEventListener('resize', function() { window.addEventListener('resize', function() {
clearTimeout(resizeTimer); clearTimeout(resizeTimer);
resizeTimer = setTimeout(function() { resizeTimer = setTimeout(function() {
// Jika window > md (768px), reset sidebar position
if (window.innerWidth >= 768) { if (window.innerWidth >= 768) {
sidebarWrapper.classList.remove('-translate-x-full'); sidebarWrapper.classList.remove('is-open');
sidebarOverlay.classList.add('hidden'); sidebarOverlay.classList.remove('is-active');
} else { document.body.style.overflow = '';
// Jika window < md (768px), hide sidebar dan overlay
if (!sidebarWrapper.classList.contains('-translate-x-full')) {
sidebarWrapper.classList.add('-translate-x-full');
} }
sidebarOverlay.classList.add('hidden'); }, 200);
}
}, 250);
}); });
}); });

View File

@ -32,25 +32,25 @@
<!-- Navigation Menu --> <!-- Navigation Menu -->
<nav class="flex-1 p-2 md:p-4 space-y-1 md:space-y-2 overflow-y-auto"> <nav class="flex-1 p-2 md:p-4 space-y-1 md:space-y-2 overflow-y-auto">
<a href="{{ route('admin.dashboard') }}" <a href="{{ route('admin.dashboard') }}"
class="sidebar-link flex items-center space-x-3 px-3 md:px-4 py-2 md:py-3 rounded-lg text-sm md:text-base text-gray-700 hover:bg-gray-50 transition-all {{ request()->routeIs('admin.dashboard') ? 'active' : '' }}" onclick="document.getElementById('sidebar-wrapper').classList.add('-translate-x-full'); document.getElementById('sidebar-overlay').classList.add('hidden');"> class="sidebar-link flex items-center space-x-3 px-3 md:px-4 py-2 md:py-3 rounded-lg text-sm md:text-base text-gray-700 hover:bg-gray-50 transition-all {{ request()->routeIs('admin.dashboard') ? 'active' : '' }}">
<i class="fas fa-home w-5 flex-shrink-0"></i> <i class="fas fa-home w-5 flex-shrink-0"></i>
<span class="truncate">Dashboard</span> <span class="truncate">Dashboard</span>
</a> </a>
<a href="{{ route('admin.manage-admin') }}" <a href="{{ route('admin.manage-admin') }}"
class="sidebar-link flex items-center space-x-3 px-3 md:px-4 py-2 md:py-3 rounded-lg text-sm md:text-base text-gray-700 hover:bg-gray-50 transition-all {{ request()->routeIs('admin.manage-admin') ? 'active' : '' }}" onclick="document.getElementById('sidebar-wrapper').classList.add('-translate-x-full'); document.getElementById('sidebar-overlay').classList.add('hidden');"> class="sidebar-link flex items-center space-x-3 px-3 md:px-4 py-2 md:py-3 rounded-lg text-sm md:text-base text-gray-700 hover:bg-gray-50 transition-all {{ request()->routeIs('admin.manage-admin') ? 'active' : '' }}">
<i class="fas fa-users w-5 flex-shrink-0"></i> <i class="fas fa-users w-5 flex-shrink-0"></i>
<span class="truncate">Kelola Akun Admin</span> <span class="truncate">Kelola Akun Admin</span>
</a> </a>
<a href="{{ route('admin.classification-history') }}" <a href="{{ route('admin.classification-history') }}"
class="sidebar-link flex items-center space-x-3 px-3 md:px-4 py-2 md:py-3 rounded-lg text-sm md:text-base text-gray-700 hover:bg-gray-50 transition-all {{ request()->routeIs('admin.classification-history') ? 'active' : '' }}" onclick="document.getElementById('sidebar-wrapper').classList.add('-translate-x-full'); document.getElementById('sidebar-overlay').classList.add('hidden');"> class="sidebar-link flex items-center space-x-3 px-3 md:px-4 py-2 md:py-3 rounded-lg text-sm md:text-base text-gray-700 hover:bg-gray-50 transition-all {{ request()->routeIs('admin.classification-history') ? 'active' : '' }}">
<i class="fas fa-history w-5 flex-shrink-0"></i> <i class="fas fa-history w-5 flex-shrink-0"></i>
<span class="truncate">Riwayat Klasifikasi</span> <span class="truncate">Riwayat Klasifikasi</span>
</a> </a>
<a href="{{ route('admin.system-statistics') }}" <a href="{{ route('admin.system-statistics') }}"
class="sidebar-link flex items-center space-x-3 px-3 md:px-4 py-2 md:py-3 rounded-lg text-sm md:text-base text-gray-700 hover:bg-gray-50 transition-all {{ request()->routeIs('admin.system-statistics') ? 'active' : '' }}" onclick="document.getElementById('sidebar-wrapper').classList.add('-translate-x-full'); document.getElementById('sidebar-overlay').classList.add('hidden');"> class="sidebar-link flex items-center space-x-3 px-3 md:px-4 py-2 md:py-3 rounded-lg text-sm md:text-base text-gray-700 hover:bg-gray-50 transition-all {{ request()->routeIs('admin.system-statistics') ? 'active' : '' }}">
<i class="fas fa-chart-bar w-5 flex-shrink-0"></i> <i class="fas fa-chart-bar w-5 flex-shrink-0"></i>
<span class="truncate">Statistik Sistem</span> <span class="truncate">Statistik Sistem</span>
</a> </a>

View File

@ -217,13 +217,22 @@
@section('scripts') @section('scripts')
<script> <script>
// Detect mobile // Tunggu sampai Chart.js library loaded
const isMobileStats = window.innerWidth < 768; function initStatisticsCharts() {
if (typeof Chart === 'undefined') {
console.warn('Chart.js belum loaded, retry dalam 100ms');
setTimeout(initStatisticsCharts, 100);
return;
}
/* ================================================== // Detect mobile
BAR CHART const isMobileStats = window.innerWidth < 768;
================================================== */
new Chart(document.getElementById('barChart'), { /* ==================================================
BAR CHART
================================================== */
try {
new Chart(document.getElementById('barChart'), {
type: 'bar', type: 'bar',
data: { data: {
labels: @json($hariLabels), labels: @json($hariLabels),
@ -258,13 +267,16 @@
} }
} }
} }
}); });
} catch (error) {
console.error('Error initializing bar chart:', error);
}
/* ==================================================
/* ================================================== LINE CHART
LINE CHART ================================================== */
================================================== */ try {
new Chart(document.getElementById('lineChart'), { new Chart(document.getElementById('lineChart'), {
type: 'line', type: 'line',
data: { data: {
labels: @json($bulanLabels), labels: @json($bulanLabels),
@ -306,13 +318,16 @@
} }
} }
} }
}); });
} catch (error) {
console.error('Error initializing line chart:', error);
}
/* ==================================================
/* ================================================== PIE CHART
PIE CHART ================================================== */
================================================== */ try {
new Chart(document.getElementById('pieChart'), { new Chart(document.getElementById('pieChart'), {
type: 'pie', type: 'pie',
data: { data: {
labels: @json(array_column($distribusiData, 'label')), labels: @json(array_column($distribusiData, 'label')),
@ -345,7 +360,14 @@
} }
} }
} }
}); });
} catch (error) {
console.error('Error initializing pie chart:', error);
}
}
// Initialize charts saat DOM ready
document.addEventListener('DOMContentLoaded', initStatisticsCharts);
</script> </script>
@endsection @endsection

View File

@ -137,6 +137,13 @@ class="h-4 w-4 text-red-600 focus:ring-red-500 border-gray-300 rounded"
</a> </a>
</div> </div>
<!-- Lupa Kata Sandi (Disabled) -->
<div>
<span class="text-sm text-gray-400 cursor-not-allowed">
Lupa kata sandi?
</span>
</div>
<!-- Login Button --> <!-- Login Button -->
<div> <div>
<button <button
@ -154,16 +161,6 @@ class="btn-login w-full bg-red-500 hover:bg-red-600 text-white font-semibold py-
</button> </button>
</div> </div>
</form> </form>
<!-- Additional Links -->
<div class="mt-6 text-center">
<p class="text-sm text-gray-600">
Belum punya akun admin?
<a href="{{ route('admin.manage-admin') }}" class="text-red-600 hover:text-red-700 font-medium transition-colors">
Hubungi administrator
</a>
</p>
</div>
</div> </div>
<!-- Security Notice --> <!-- Security Notice -->
@ -176,7 +173,7 @@ class="btn-login w-full bg-red-500 hover:bg-red-600 text-white font-semibold py-
<!-- Footer --> <!-- Footer -->
<footer class="fixed bottom-4 left-4 text-xs text-gray-300 opacity-60"> <footer class="fixed bottom-4 left-4 text-xs text-gray-300 opacity-60">
Made with by MaturityScan Made with by TomatScan
</footer> </footer>
<script> <script>
@ -209,16 +206,16 @@ class="btn-login w-full bg-red-500 hover:bg-red-600 text-white font-semibold py-
btnLoading.classList.remove('hidden'); btnLoading.classList.remove('hidden');
}); });
// Auto-focus username field on load // Auto-focus email field on load
window.addEventListener('load', function() { window.addEventListener('load', function() {
document.getElementById('username').focus(); document.getElementById('email').focus();
}); });
// Add keyboard navigation // Add keyboard navigation
document.addEventListener('keydown', function(e) { document.addEventListener('keydown', function(e) {
if (e.key === 'Enter') { if (e.key === 'Enter') {
const activeElement = document.activeElement; const activeElement = document.activeElement;
if (activeElement.id === 'username') { if (activeElement.id === 'email') {
e.preventDefault(); e.preventDefault();
document.getElementById('password').focus(); document.getElementById('password').focus();
} }

View File

@ -14,6 +14,47 @@
<!-- Font Awesome --> <!-- Font Awesome -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
/* Cegah layout shift saat gambar loading */
img {
display: block;
max-width: 100%;
height: auto;
}
/* Placeholder abu-abu saat gambar belum load */
img:not([src]), img[src=""] {
background-color: #f3f4f6;
min-height: 100px;
}
/* Animasi skeleton untuk gambar loading */
@keyframes skeleton-pulse {
0% { background-color: #e5e7eb; }
100% { background-color: #f9fafb; }
}
/* Smooth scroll */
html {
scroll-behavior: smooth;
}
/* Reduce motion untuk device hemat baterai */
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
animation-duration: 0.01ms !important;
transition-duration: 0.01ms !important;
}
}
/* GPU acceleration untuk animasi hover */
.hover\:scale-105,
.hover\:-translate-y-2,
.hover\:rotate-0 {
will-change: transform;
}
</style>
</head> </head>
<body class="bg-gray-50"> <body class="bg-gray-50">
<!-- Navbar --> <!-- Navbar -->
@ -50,14 +91,38 @@
</div> </div>
</div> </div>
<div class="md:hidden"> <div class="md:hidden">
<button class="text-gray-500 hover:text-gray-700 focus:outline-none"> <button id="mobileMenuBtn"
<svg class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor"> class="text-gray-500 hover:text-gray-700 focus:outline-none p-2 rounded-lg hover:bg-gray-100 transition-colors"
aria-label="Buka menu"
aria-expanded="false">
<svg id="iconHamburger" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16" /> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16" />
</svg> </svg>
<svg id="iconClose" class="h-6 w-6 hidden" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
</svg>
</button> </button>
</div> </div>
</div> </div>
</div> </div>
<!-- Mobile Menu Dropdown -->
<div id="mobileMenu"
class="hidden md:hidden border-t border-gray-100 bg-white">
<div class="px-4 py-3 space-y-1">
<a href="#"
class="block px-3 py-2 rounded-lg text-gray-900 hover:bg-red-50 hover:text-red-600 font-medium transition-colors">
Beranda
</a>
<a href="{{ route('about') }}"
class="block px-3 py-2 rounded-lg text-gray-600 hover:bg-red-50 hover:text-red-600 font-medium transition-colors">
Tentang Kami
</a>
<a href="{{ route('login') }}"
class="block w-full text-center bg-red-600 hover:bg-red-700 text-white font-semibold py-2 px-4 rounded-lg transition-colors mt-2">
Login
</a>
</div>
</div>
</nav> </nav>
<!-- Hero Section --> <!-- Hero Section -->
@ -84,6 +149,8 @@
<div class="bg-white rounded-2xl shadow-2xl p-8 transform rotate-3 hover:rotate-0 transition-transform duration-300"> <div class="bg-white rounded-2xl shadow-2xl p-8 transform rotate-3 hover:rotate-0 transition-transform duration-300">
<img src="{{ asset('assets/images/tomatt.png') }}" <img src="{{ asset('assets/images/tomatt.png') }}"
alt="Fresh Tomatoes" alt="Fresh Tomatoes"
loading="lazy"
decoding="async"
class="rounded-lg shadow-md w-full h-auto"> class="rounded-lg shadow-md w-full h-auto">
</div> </div>
</div> </div>
@ -102,7 +169,11 @@ class="rounded-lg shadow-md w-full h-auto">
<!-- Mentah Card --> <!-- Mentah Card -->
<div class="bg-green-50 border-2 border-green-200 rounded-xl p-8 text-center hover:shadow-xl transition-all duration-300 transform hover:-translate-y-2"> <div class="bg-green-50 border-2 border-green-200 rounded-xl p-8 text-center hover:shadow-xl transition-all duration-300 transform hover:-translate-y-2">
<div class="w-32 h-32 bg-green-500 rounded-full mx-auto mb-6 flex items-center justify-center p-2"> <div class="w-32 h-32 bg-green-500 rounded-full mx-auto mb-6 flex items-center justify-center p-2">
<img src="{{ asset('assets/images/mentah.jpg') }}" alt="Tomat Mentah" class="w-full h-full object-contain rounded-full"> <img src="{{ asset('assets/images/mentah.jpg') }}"
alt="Tomat Mentah"
loading="lazy"
decoding="async"
class="w-full h-full object-contain rounded-full">
</div> </div>
<h3 class="text-2xl font-bold text-green-800 mb-4">Mentah</h3> <h3 class="text-2xl font-bold text-green-800 mb-4">Mentah</h3>
<p class="text-gray-600 mb-4">Tomat yang belum matang sempurna, berwarna hijau dan tekstur masih keras.</p> <p class="text-gray-600 mb-4">Tomat yang belum matang sempurna, berwarna hijau dan tekstur masih keras.</p>
@ -114,7 +185,11 @@ class="rounded-lg shadow-md w-full h-auto">
<!-- Setengah Matang Card --> <!-- Setengah Matang Card -->
<div class="bg-yellow-50 border-2 border-yellow-200 rounded-xl p-8 text-center hover:shadow-xl transition-all duration-300 transform hover:-translate-y-2"> <div class="bg-yellow-50 border-2 border-yellow-200 rounded-xl p-8 text-center hover:shadow-xl transition-all duration-300 transform hover:-translate-y-2">
<div class="w-32 h-32 bg-yellow-500 rounded-full mx-auto mb-6 flex items-center justify-center p-2"> <div class="w-32 h-32 bg-yellow-500 rounded-full mx-auto mb-6 flex items-center justify-center p-2">
<img src="{{ asset('assets/images/setengahmateng.jpg') }}" alt="Tomat Setengah Matang" class="w-full h-full object-contain rounded-full"> <img src="{{ asset('assets/images/setengahmateng.jpg') }}"
alt="Tomat Setengah Matang"
loading="lazy"
decoding="async"
class="w-full h-full object-contain rounded-full">
</div> </div>
<h3 class="text-2xl font-bold text-yellow-800 mb-4">Setengah Matang</h3> <h3 class="text-2xl font-bold text-yellow-800 mb-4">Setengah Matang</h3>
<p class="text-gray-600 mb-4">Tomat dalam proses pematangan, perpaduan warna hijau dan merah.</p> <p class="text-gray-600 mb-4">Tomat dalam proses pematangan, perpaduan warna hijau dan merah.</p>
@ -126,7 +201,11 @@ class="rounded-lg shadow-md w-full h-auto">
<!-- Matang Card --> <!-- Matang Card -->
<div class="bg-red-50 border-2 border-red-200 rounded-xl p-8 text-center hover:shadow-xl transition-all duration-300 transform hover:-translate-y-2"> <div class="bg-red-50 border-2 border-red-200 rounded-xl p-8 text-center hover:shadow-xl transition-all duration-300 transform hover:-translate-y-2">
<div class="w-32 h-32 bg-red-500 rounded-full mx-auto mb-6 flex items-center justify-center p-2"> <div class="w-32 h-32 bg-red-500 rounded-full mx-auto mb-6 flex items-center justify-center p-2">
<img src="{{ asset('assets/images/matang.jpg') }}" alt="Tomat Matang" class="w-full h-full object-contain rounded-full"> <img src="{{ asset('assets/images/matang.jpg') }}"
alt="Tomat Matang"
loading="lazy"
decoding="async"
class="w-full h-full object-contain rounded-full">
</div> </div>
<h3 class="text-2xl font-bold text-red-800 mb-4">Matang</h3> <h3 class="text-2xl font-bold text-red-800 mb-4">Matang</h3>
<p class="text-gray-600 mb-4">Tomat matang sempurna, berwarna merah cerah dan tekstur lembut.</p> <p class="text-gray-600 mb-4">Tomat matang sempurna, berwarna merah cerah dan tekstur lembut.</p>
@ -239,5 +318,80 @@ class="rounded-lg shadow-md w-full h-auto">
</div> </div>
</div> </div>
</footer> </footer>
<script>
// =====================
// MOBILE MENU TOGGLE
// =====================
(function() {
var btn = document.getElementById('mobileMenuBtn');
var menu = document.getElementById('mobileMenu');
var iconOpen = document.getElementById('iconHamburger');
var iconClose = document.getElementById('iconClose');
if (!btn || !menu) return;
btn.addEventListener('click', function() {
var isOpen = !menu.classList.contains('hidden');
if (isOpen) {
menu.classList.add('hidden');
iconOpen.classList.remove('hidden');
iconClose.classList.add('hidden');
btn.setAttribute('aria-expanded', 'false');
} else {
menu.classList.remove('hidden');
iconOpen.classList.add('hidden');
iconClose.classList.remove('hidden');
btn.setAttribute('aria-expanded', 'true');
}
});
// Tutup menu saat link diklik
menu.querySelectorAll('a').forEach(function(link) {
link.addEventListener('click', function() {
menu.classList.add('hidden');
iconOpen.classList.remove('hidden');
iconClose.classList.add('hidden');
btn.setAttribute('aria-expanded', 'false');
});
});
// Tutup menu saat resize ke desktop
window.addEventListener('resize', function() {
if (window.innerWidth >= 768) {
menu.classList.add('hidden');
iconOpen.classList.remove('hidden');
iconClose.classList.add('hidden');
btn.setAttribute('aria-expanded', 'false');
}
});
})();
// =====================
// LAZY LOAD GAMBAR FALLBACK
// untuk browser lama yang tidak support loading="lazy"
// =====================
(function() {
if ('loading' in HTMLImageElement.prototype) return;
if (!('IntersectionObserver' in window)) return;
var obs = new IntersectionObserver(function(entries, observer) {
entries.forEach(function(entry) {
if (!entry.isIntersecting) return;
var img = entry.target;
if (img.dataset.src) {
img.src = img.dataset.src;
img.removeAttribute('data-src');
}
observer.unobserve(img);
});
}, { rootMargin: '200px' });
document.querySelectorAll('img[loading="lazy"]').forEach(function(img) {
obs.observe(img);
});
})();
</script>
</body> </body>
</html> </html>