klasifikasi OK

This commit is contained in:
kizadl 2026-04-25 18:30:27 +07:00
parent 9eb801a7d5
commit e66523256c
10 changed files with 297 additions and 230 deletions

74
backend-ai/app.py Normal file
View File

@ -0,0 +1,74 @@
import os
import io
import numpy as np
import tensorflow as tf
from flask import Flask, request, jsonify
from flask_cors import CORS
from PIL import Image
app = Flask(__name__)
CORS(app)
# 1. SETUP PATH MODEL
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
MODEL_PATH = os.path.join(BASE_DIR, 'model_efficientNet.keras')
# 2. DUMMY PREPROCESS (Agar tidak error saat load Lambda layer)
def preprocess_input(x):
return x
print("⏳ Sedang memuat 'Otak AI'...")
try:
# Menggunakan parameter Keras 3 untuk memuat model lama
model = tf.keras.models.load_model(
MODEL_PATH,
custom_objects={'preprocess_input': preprocess_input},
compile=False,
safe_mode=False # Kunci agar Keras 3 mau menerima config Keras lama
)
print("✅ BERHASIL: Model AI siap digunakan!")
except Exception as e:
print(f"❌ GAGAL: {str(e)}")
print("--- SOLUSI ---")
print("Jika error 'Unrecognized keyword arguments', silakan info ke saya.")
# Label klasifikasi kopi kamu
labels = ['Honey', 'Natural', 'Washed']
@app.route('/predict', methods=['POST'])
def predict():
try:
file = request.files['image']
# 1. Load Gambar & Resize ke 224x224
img = Image.open(file.stream).convert('RGB')
img = img.resize((224, 224))
# 2. Konversi ke Array
img_array = np.array(img).astype('float32')
# 3. JURUS SAKTI: Gunakan preprocessing asli EfficientNet
# Ini akan menangani scaling warna agar sama persis dengan saat training
img_array = tf.keras.applications.efficientnet.preprocess_input(img_array)
img_array = np.expand_dims(img_array, axis=0)
# 4. Prediksi
preds = model.predict(img_array, verbose=0)
class_idx = np.argmax(preds[0])
confidence = float(np.max(preds[0]))
print(f"📥 Prediksi: {labels[class_idx]} ({confidence*100:.2f}%)")
return jsonify({
'label': labels[class_idx],
'confidence': f"{confidence * 100:.2f}%",
'status': 'success'
})
except Exception as e:
print(f"❌ ERROR PREDIKSI: {str(e)}")
return jsonify({'error': str(e)}), 500
if __name__ == '__main__':
# Jalankan di port 5001 agar tidak diblokir Windows AirPlay
app.run(host='127.0.0.1', port=5001, debug=True)

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,5 @@
flask==3.0.2
flask-cors==4.0.0
tensorflow==2.15.0
numpy==1.26.4
Pillow==10.2.0

View File

@ -0,0 +1,141 @@
<template>
<section id="features" class="py-24 px-4">
<div class="max-w-6xl mx-auto">
<div class="text-center mb-16">
<h2 class="text-3xl sm:text-4xl font-bold text-white mb-4 font-display">Keunggulan Sistem Kami</h2>
<p class="text-zinc-400 max-w-2xl mx-auto">
Didukung oleh teknologi kecerdasan buatan terkini untuk memberikan klasifikasi biji kopi yang cepat, presisi, dan andal.
</p>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
<!-- Large card - AI Architecture -->
<div
class="md:col-span-2 group relative p-6 rounded-2xl bg-zinc-900 border border-zinc-800 hover:border-emerald-500/50 hover:scale-[1.02] transition-all duration-300 overflow-hidden"
>
<div class="flex items-start justify-between mb-8">
<div>
<div class="p-2 rounded-lg bg-emerald-500/10 w-fit mb-4">
<Activity class="w-5 h-5 text-emerald-500" :stroke-width="1.5" />
</div>
<h3 class="text-xl font-semibold text-white mb-2">Arsitektur EfficientNetB0</h3>
<p class="text-zinc-400 text-sm">
Model klasifikasi kami menggunakan EfficientNetB0, menghadirkan keseimbangan optimal antara akurasi tinggi dan efisiensi komputasi untuk mendeteksi proses pasca-panen (Natural, Honey, Wash).
</p>
</div>
<div class="flex items-center gap-2">
<div
v-for="(active, i) in dots"
:key="i"
:class="[
'w-2 h-2 rounded-full',
active ? 'bg-emerald-500 animate-pulse' : 'bg-zinc-700',
]"
/>
</div>
</div>
<div class="grid grid-cols-4 gap-4">
<div v-for="(metric, idx) in metrics" :key="metric" class="text-center">
<div class="text-2xl font-bold text-white mb-1">{{ metricValues[idx] }}%</div>
<div class="text-xs text-zinc-500">{{ metric }}</div>
</div>
</div>
</div>
<!-- Security -->
<div
class="group relative p-6 rounded-2xl bg-zinc-900 border border-zinc-800 hover:border-emerald-500/50 hover:scale-[1.02] transition-all duration-300">
<div class="p-2 rounded-lg bg-zinc-800 w-fit mb-4 group-hover:bg-emerald-500/10 transition-colors">
<Bean
class="w-5 h-5 text-zinc-400 group-hover:text-emerald-500 transition-colors -rotate-15 group-hover:rotate-0 transition-transform duration-500"
:stroke-width="1.5"
/>
</div>
<h3 class="text-lg font-semibold text-white mb-2">Target Klasifikasi</h3>
<p class="text-zinc-400 text-sm mb-4">
Model ini dilatih khusus untuk membedakan tiga metode pemrosesan pasca-panen utama:
</p>
<div class="flex flex-wrap gap-2">
<span class="px-2 py-1 text-[10px] font-black bg-zinc-800 rounded text-emerald-500 border border-emerald-500/20 tracking-widest uppercase">Natural</span>
<span class="px-2 py-1 text-[10px] font-black bg-zinc-800 rounded text-emerald-500 border border-emerald-500/20 tracking-widest uppercase">Honey</span>
<span class="px-2 py-1 text-[10px] font-black bg-zinc-800 rounded text-emerald-500 border border-emerald-500/20 tracking-widest uppercase">Wash</span>
</div>
</div>
<!-- Analytics -->
<div
class="group relative p-6 rounded-2xl bg-zinc-900 border border-zinc-800 hover:border-emerald-500/50 hover:scale-[1.02] transition-all duration-300"
>
<div class="p-2 rounded-lg bg-zinc-800 w-fit mb-4 group-hover:bg-emerald-500/10 transition-colors">
<BarChart3 class="w-5 h-5 text-zinc-400 group-hover:text-emerald-500 transition-colors" :stroke-width="1.5" />
</div>
<h3 class="text-lg font-semibold text-white mb-2">Performa Stabil</h3>
<p class="text-zinc-400 text-sm mb-4">Grafik akurasi klasifikasi berdasarkan dataset pelatihan yang komprehensif.</p>
<div class="flex items-end gap-1 h-12">
<div class="flex-1 bg-emerald-500/20 h-[80%] rounded-t-sm"></div>
<div class="flex-1 bg-emerald-500/40 h-[60%] rounded-t-sm"></div>
<div class="flex-1 bg-emerald-500 h-[95%] rounded-t-sm"></div>
<div class="flex-1 bg-emerald-500/60 h-[40%] rounded-t-sm"></div>
</div>
</div>
<!-- Performance -->
<div
class="group relative p-6 rounded-2xl bg-zinc-900 border border-zinc-800 hover:border-emerald-500/50 hover:scale-[1.02] transition-all duration-300"
>
<div class="p-2 rounded-lg bg-zinc-800 w-fit mb-4 group-hover:bg-emerald-500/10 transition-colors">
<Zap class="w-5 h-5 text-zinc-400 group-hover:text-emerald-500 transition-colors" :stroke-width="1.5" />
</div>
<h3 class="text-lg font-semibold text-white mb-2">Proses Instan</h3>
<p class="text-zinc-400 text-sm mb-4">
Infrastruktur yang dioptimalkan untuk memberikan hasil prediksi klasifikasi secara instan.
</p>
<div class="flex items-center gap-2 text-emerald-500 text-sm">
<span class="font-mono">~150ms</span>
<span class="text-zinc-500 italic">inference time</span>
</div>
</div>
<!-- Command Palette -->
<div
class="group relative p-6 rounded-2xl bg-zinc-900 border border-zinc-800 hover:border-emerald-500/50 hover:scale-[1.02] transition-all duration-300"
>
<div class="p-2 rounded-lg bg-zinc-800 w-fit mb-4 group-hover:bg-emerald-500/10 transition-colors">
<Command class="w-5 h-5 text-zinc-400 group-hover:text-emerald-500 transition-colors" :stroke-width="1.5" />
</div>
<h3 class="text-lg font-semibold text-white mb-2">Panduan Cepat</h3>
<p class="text-zinc-400 text-sm mb-6">Gunakan pintasan keyboard untuk berpindah antarmuka dan memulai analisis dengan cepat.</p>
<div class="flex items-center gap-1">
<kbd class="px-2 py-1 text-xs bg-zinc-800 border border-zinc-700 rounded text-zinc-300 font-mono italic">
Alt + S
</kbd>
<span class="text-xs text-zinc-600 ml-1">untuk Scan</span>
</div>
</div>
</div>
</div>
</section>
</template>
<script setup lang="ts">
import { ref, onMounted, onBeforeUnmount } from 'vue'
import { Activity, Command, BarChart3, Zap, Shield } from 'lucide-vue-next'
import { Bean, Upload, FolderOpen, X } from 'lucide-vue-next'
const dots = ref([true, true, true, false, true])
const metrics = ['Akurasi', 'Presisi', 'Recall', 'F1-Score']
const metricValues = [96, 95, 96, 95]
let interval: ReturnType<typeof setInterval>
onMounted(() => {
interval = setInterval(() => {
dots.value = dots.value.map(() => Math.random() > 0.2)
}, 2000)
})
onBeforeUnmount(() => {
clearInterval(interval)
})
</script>

View File

@ -1,20 +1,17 @@
<template> <template>
<section id="klasifikasi" class="py-24 px-4 bg-black"> <section id="klasifikasi" class="py-24 px-4 bg-black">
<div class="max-w-6xl mx-auto"> <div class="max-w-6xl mx-auto">
<div class="text-center mb-16"> <div class="text-center mb-16">
<h2 class="text-3xl sm:text-4xl font-bold text-white mb-4 font-display">COFFEE SCAN</h2> <h2 class="text-3xl sm:text-4xl font-bold text-white mb-4 font-display ">COFFEE <span class="text-emerald-500">SCANNER</span></h2>
<p class="text-zinc-400 max-w-2xl mx-auto"> <p class="text-zinc-400 max-w-2xl mx-auto">
Identifikasi proses pasca-panen biji kopi Anda (Natural, Honey, Wash) menggunakan teknologi Artificial Intelligence. Identifikasi proses pasca-panen biji kopi Anda (Natural, Honey, Wash) menggunakan teknologi Artificial Intelligence.
</p> </p>
</div> </div>
<div class="max-w-5xl mx-auto"> <div class="max-w-5xl mx-auto">
<div <div class="group relative p-8 rounded-3xl bg-zinc-900 border border-zinc-800 hover:border-emerald-500/50 transition-all duration-500 overflow-hidden">
class="group relative p-8 rounded-3xl bg-zinc-900 border border-zinc-800 hover:border-emerald-500/50 transition-all duration-500 overflow-hidden"
>
<div class="flex flex-col md:flex-row items-stretch justify-between gap-8"> <div class="flex flex-col md:flex-row items-stretch justify-between gap-8">
<div class="flex-1 flex flex-col justify-center"> <div class="flex-1 flex flex-col justify-center">
<div class="p-3 rounded-xl bg-emerald-500/10 w-fit mb-6"> <div class="p-3 rounded-xl bg-emerald-500/10 w-fit mb-6">
<Upload class="w-6 h-6 text-emerald-500" :stroke-width="2" /> <Upload class="w-6 h-6 text-emerald-500" :stroke-width="2" />
@ -26,8 +23,10 @@
<div class="flex items-center gap-3"> <div class="flex items-center gap-3">
<div class="flex items-center gap-1.5 px-3 py-1.5 rounded-full bg-emerald-500/10 border border-emerald-500/20"> <div class="flex items-center gap-1.5 px-3 py-1.5 rounded-full bg-emerald-500/10 border border-emerald-500/20">
<div class="w-2 h-2 rounded-full bg-emerald-500 animate-pulse"></div> <div :class="['w-2 h-2 rounded-full bg-emerald-500', isLoading ? 'animate-ping' : 'animate-pulse']"></div>
<span class="text-[10px] font-bold text-emerald-500 uppercase tracking-wider">AI Model Ready</span> <span class="text-[10px] font-bold text-emerald-500 uppercase tracking-wider">
{{ isLoading ? 'Processing...' : 'AI Model Ready' }}
</span>
</div> </div>
</div> </div>
</div> </div>
@ -68,74 +67,30 @@
</label> </label>
<button <button
:disabled="!imagePreview" @click="startClassification"
class="w-full py-4 rounded-xl font-bold text-base transition-all border-2 border-emerald-600 text-emerald-500 hover:bg-emerald-600/10 active:scale-95 disabled:opacity-50 disabled:border-zinc-700 disabled:text-zinc-500 disabled:hover:bg-transparent disabled:active:scale-100 disabled:cursor-not-allowed flex-none" :disabled="!imagePreview || isLoading"
class="w-full py-4 rounded-xl font-bold text-base transition-all border-2 border-emerald-600 text-emerald-500 hover:bg-emerald-600 hover:text-white active:scale-95 disabled:opacity-50 disabled:border-zinc-700 disabled:text-zinc-500 disabled:cursor-not-allowed flex items-center justify-center gap-2"
> >
Analisis Sekarang <Loader2 v-if="isLoading" class="w-5 h-5 animate-spin" />
{{ isLoading ? 'Sedang Menganalisis...' : 'Analisis Sekarang' }}
</button> </button>
</div> </div>
</div> </div>
</div> </div>
<!-- <div <transition enter-active-class="transition duration-500 ease-out" enter-from-class="transform scale-95 opacity-0" enter-to-class="transform scale-100 opacity-100">
class="group relative p-6 rounded-2xl bg-zinc-900 border border-zinc-800 hover:border-zinc-600 hover:scale-[1.02] transition-all duration-300" <div v-if="predictionResult" class="mt-8 p-8 rounded-3xl bg-emerald-600 shadow-[0_0_50px_-12px_rgba(16,185,129,0.5)] text-center relative overflow-hidden">
> <div class="relative z-10">
<div class="p-2 rounded-lg bg-zinc-800 w-fit mb-4"> <p class="text-xs font-black text-emerald-100 uppercase tracking-[0.3em] mb-2">Hasil Klasifikasi AI</p>
<Command class="w-5 h-5 text-zinc-400" :stroke-width="1.5" /> <h4 class="text-5xl font-black text-white italic mb-2 tracking-tighter">{{ predictionResult.label }}</h4>
<div class="inline-block px-4 py-1.5 bg-black/20 rounded-full backdrop-blur-md">
<span class="text-emerald-500 font-bold text-sm uppercase tracking-widest">Confidence: {{ predictionResult.confidence }}</span>
</div> </div>
<h3 class="text-lg font-semibold text-white mb-2">Panduan Cepat</h3>
<p class="text-zinc-400 text-sm mb-6">Gunakan shortcut untuk berpindah antar mode klasifikasi.</p>
<div class="flex items-center gap-1">
<kbd class="px-2 py-1 text-xs bg-zinc-800 border border-zinc-700 rounded text-zinc-300 font-mono italic">Alt + S</kbd>
<span class="text-xs text-zinc-600 ml-1">untuk Scan</span>
</div> </div>
</div> --> <Bean class="absolute -right-10 -bottom-10 w-40 h-40 text-white/10 rotate-12" />
</div>
</transition>
<!-- <div
class="group relative p-6 rounded-2xl bg-zinc-900 border border-zinc-800 hover:border-zinc-600 hover:scale-[1.02] transition-all duration-300"
>
<div class="p-2 rounded-lg bg-zinc-800 w-fit mb-4">
<BarChart3 class="w-5 h-5 text-zinc-400" :stroke-width="1.5" />
</div>
<h3 class="text-lg font-semibold text-white mb-2">Akurasi Model</h3>
<p class="text-zinc-400 text-sm mb-4">Performa klasifikasi berdasarkan dataset saat ini.</p>
<div class="flex items-end gap-1 h-12">
<div class="flex-1 bg-orange-500/20 h-[80%] rounded-t-sm"></div>
<div class="flex-1 bg-orange-500/40 h-[60%] rounded-t-sm"></div>
<div class="flex-1 bg-orange-500 h-[95%] rounded-t-sm"></div>
<div class="flex-1 bg-orange-500/60 h-[40%] rounded-t-sm"></div>
</div>
</div> -->
<!-- <div
class="group relative p-6 rounded-2xl bg-zinc-900 border border-zinc-800 hover:border-zinc-600 hover:scale-[1.02] transition-all duration-300"
>
<div class="p-2 rounded-lg bg-zinc-800 w-fit mb-4">
<Zap class="w-5 h-5 text-zinc-400" :stroke-width="1.5" />
</div>
<h3 class="text-lg font-semibold text-white mb-2">Proses Cepat</h3>
<p class="text-zinc-400 text-sm mb-4">
Prediksi instan menggunakan akselerasi GPU via TensorFlow.js.
</p>
<div class="flex items-center gap-2 text-orange-500 text-sm">
<span class="font-mono">~150ms</span>
<span class="text-zinc-500 italic">inference time</span>
</div>
</div> -->
<!-- <div
class="group relative p-6 rounded-2xl bg-zinc-900 border border-zinc-800 hover:border-zinc-600 hover:scale-[1.02] transition-all duration-300"
>
<div class="p-2 rounded-lg bg-zinc-800 w-fit mb-4">
<Shield class="w-5 h-5 text-zinc-400" :stroke-width="1.5" />
</div>
<h3 class="text-lg font-semibold text-white mb-2">Privasi Terjamin</h3>
<p class="text-zinc-400 text-sm mb-4">Foto Anda diproses langsung di browser tanpa disimpan di server kami.</p>
<div class="flex items-center gap-2">
<span class="px-2 py-1 text-[10px] font-bold bg-zinc-800 rounded text-zinc-400">CLIENT-SIDE</span>
<span class="px-2 py-1 text-[10px] font-bold bg-zinc-800 rounded text-zinc-400">ENCRYPTED</span>
</div>
</div> -->
</div> </div>
</div> </div>
</section> </section>
@ -143,23 +98,20 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref } from 'vue' import { ref } from 'vue'
import { import { Upload, Plus, X, Loader2, Bean } from 'lucide-vue-next'
Upload,
Plus,
X,
Command,
BarChart3,
Zap,
Shield
} from 'lucide-vue-next'
const imagePreview = ref<string | null>(null) const imagePreview = ref<string | null>(null)
const selectedFile = ref<File | null>(null)
const predictionResult = ref<{ label: string, confidence: string } | null>(null)
const isLoading = ref(false)
const handleFileChange = (event: Event) => { const handleFileChange = (event: Event) => {
const target = event.target as HTMLInputElement const target = event.target as HTMLInputElement
const file = target.files?.[0] const file = target.files?.[0]
if (file) { if (file) {
selectedFile.value = file
predictionResult.value = null // Reset hasil jika ganti gambar
const reader = new FileReader() const reader = new FileReader()
reader.onload = (e) => { reader.onload = (e) => {
imagePreview.value = e.target?.result as string imagePreview.value = e.target?.result as string
@ -168,7 +120,38 @@ const handleFileChange = (event: Event) => {
} }
} }
const startClassification = async () => {
if (!selectedFile.value) return
isLoading.value = true
const formData = new FormData()
formData.append('image', selectedFile.value)
try {
const response = await fetch('http://127.0.0.1:5001/predict', {
method: 'POST',
body: formData
})
if (!response.ok) throw new Error("Gagal terhubung ke AI server")
const data = await response.json()
// Pastikan data yang dikirim Flask sesuai (label & confidence)
predictionResult.value = {
label: data.label,
confidence: data.confidence
}
} catch (error) {
alert("Error: Pastikan backend-ai sudah dijalankan (python app.py)")
console.error(error)
} finally {
isLoading.value = false
}
}
const clearImage = () => { const clearImage = () => {
imagePreview.value = null imagePreview.value = null
selectedFile.value = null
predictionResult.value = null
} }
</script> </script>

View File

@ -54,7 +54,7 @@ import { Check } from 'lucide-vue-next'
const technologies = [ const technologies = [
{ {
name: 'Convolutional Neural Network', name: 'Convolutional Neural Network',
badge: 'Algoritma AI', badge: 'Algoritma',
description: 'Metode Deep Learning mutakhir yang secara khusus dirancang untuk memproses dan menganalisis data visual dengan meniru cara kerja korteks visual manusia.', description: 'Metode Deep Learning mutakhir yang secara khusus dirancang untuk memproses dan menganalisis data visual dengan meniru cara kerja korteks visual manusia.',
features: [ features: [
'Mampu mengenali pola spasial pada tekstur dan warna biji kopi', 'Mampu mengenali pola spasial pada tekstur dan warna biji kopi',
@ -77,14 +77,14 @@ const technologies = [
highlighted: true, highlighted: true,
}, },
{ {
name: 'Klasifikasi Pasca-Panen', name: 'TensorFlow.js & WebGL',
badge: 'AI Recognition', badge: 'Deployment',
description: 'Implementasi model CNN yang dilatih secara khusus untuk membedakan karakteristik morfologi dan visual pada tiga metode pemrosesan kopi utama.', description: 'Infrastruktur runtime yang memungkinkan model AI dijalankan secara native di sisi klien menggunakan akselerasi perangkat keras tanpa bergantung pada server backend.',
features: [ features: [
'Identifikasi akurat untuk proses Natural, Honey, dan Wash', 'Komputasi paralel menggunakan WebGL (GPU Acceleration)',
'Analisis tekstur dan gradasi warna biji kopi secara mendalam', 'Zero-latency inference (Prediksi instan tanpa jeda jaringan)',
'Inference real-time dengan latensi rendah di browser', 'Data Privacy: Pemrosesan foto dilakukan 100% lokal di browser',
'Optimasi akurasi berdasarkan varietas Arabika Bondowoso' 'Konversi model format .keras ke format JSON teroptimasi'
], ],
highlighted: false, highlighted: false,
}, },

View File

@ -1,136 +0,0 @@
<template>
<section id="features" class="py-24 px-4">
<div class="max-w-6xl mx-auto">
<div class="text-center mb-16">
<h2 class="text-3xl sm:text-4xl font-bold text-white mb-4 font-display">Keunggulan Sistem Kami</h2>
<p class="text-zinc-400 max-w-2xl mx-auto">
Didukung oleh teknologi kecerdasan buatan terkini untuk memberikan klasifikasi biji kopi yang cepat, presisi, dan andal.
</p>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
<!-- Large card - AI Architecture -->
<div
class="md:col-span-2 group relative p-6 rounded-2xl bg-zinc-900 border border-zinc-800 hover:border-emerald-500/50 hover:scale-[1.02] transition-all duration-300 overflow-hidden"
>
<div class="flex items-start justify-between mb-8">
<div>
<div class="p-2 rounded-lg bg-emerald-500/10 w-fit mb-4">
<Activity class="w-5 h-5 text-emerald-500" :stroke-width="1.5" />
</div>
<h3 class="text-xl font-semibold text-white mb-2">Arsitektur EfficientNetB0</h3>
<p class="text-zinc-400 text-sm">
Model klasifikasi kami menggunakan EfficientNetB0, menghadirkan keseimbangan optimal antara akurasi tinggi dan efisiensi komputasi untuk mendeteksi proses pasca-panen (Natural, Honey, Wash).
</p>
</div>
<div class="flex items-center gap-2">
<div
v-for="(active, i) in dots"
:key="i"
:class="[
'w-2 h-2 rounded-full',
active ? 'bg-emerald-500 animate-pulse' : 'bg-zinc-700',
]"
/>
</div>
</div>
<div class="grid grid-cols-4 gap-4">
<div v-for="(metric, idx) in metrics" :key="metric" class="text-center">
<div class="text-2xl font-bold text-white mb-1">{{ metricValues[idx] }}%</div>
<div class="text-xs text-zinc-500">{{ metric }}</div>
</div>
</div>
</div>
<!-- Command Palette -->
<div
class="group relative p-6 rounded-2xl bg-zinc-900 border border-zinc-800 hover:border-emerald-500/50 hover:scale-[1.02] transition-all duration-300"
>
<div class="p-2 rounded-lg bg-zinc-800 w-fit mb-4 group-hover:bg-emerald-500/10 transition-colors">
<Command class="w-5 h-5 text-zinc-400 group-hover:text-emerald-500 transition-colors" :stroke-width="1.5" />
</div>
<h3 class="text-lg font-semibold text-white mb-2">Panduan Cepat</h3>
<p class="text-zinc-400 text-sm mb-6">Gunakan pintasan keyboard untuk berpindah antarmuka dan memulai analisis dengan cepat.</p>
<div class="flex items-center gap-1">
<kbd class="px-2 py-1 text-xs bg-zinc-800 border border-zinc-700 rounded text-zinc-300 font-mono italic">
Alt + S
</kbd>
<span class="text-xs text-zinc-600 ml-1">untuk Scan</span>
</div>
</div>
<!-- Analytics -->
<div
class="group relative p-6 rounded-2xl bg-zinc-900 border border-zinc-800 hover:border-emerald-500/50 hover:scale-[1.02] transition-all duration-300"
>
<div class="p-2 rounded-lg bg-zinc-800 w-fit mb-4 group-hover:bg-emerald-500/10 transition-colors">
<BarChart3 class="w-5 h-5 text-zinc-400 group-hover:text-emerald-500 transition-colors" :stroke-width="1.5" />
</div>
<h3 class="text-lg font-semibold text-white mb-2">Performa Stabil</h3>
<p class="text-zinc-400 text-sm mb-4">Grafik akurasi klasifikasi berdasarkan dataset pelatihan yang komprehensif.</p>
<div class="flex items-end gap-1 h-12">
<div class="flex-1 bg-emerald-500/20 h-[80%] rounded-t-sm"></div>
<div class="flex-1 bg-emerald-500/40 h-[60%] rounded-t-sm"></div>
<div class="flex-1 bg-emerald-500 h-[95%] rounded-t-sm"></div>
<div class="flex-1 bg-emerald-500/60 h-[40%] rounded-t-sm"></div>
</div>
</div>
<!-- Performance -->
<div
class="group relative p-6 rounded-2xl bg-zinc-900 border border-zinc-800 hover:border-emerald-500/50 hover:scale-[1.02] transition-all duration-300"
>
<div class="p-2 rounded-lg bg-zinc-800 w-fit mb-4 group-hover:bg-emerald-500/10 transition-colors">
<Zap class="w-5 h-5 text-zinc-400 group-hover:text-emerald-500 transition-colors" :stroke-width="1.5" />
</div>
<h3 class="text-lg font-semibold text-white mb-2">Proses Instan</h3>
<p class="text-zinc-400 text-sm mb-4">
Infrastruktur yang dioptimalkan untuk memberikan hasil prediksi klasifikasi secara instan.
</p>
<div class="flex items-center gap-2 text-emerald-500 text-sm">
<span class="font-mono">~150ms</span>
<span class="text-zinc-500 italic">inference time</span>
</div>
</div>
<!-- Security -->
<div
class="group relative p-6 rounded-2xl bg-zinc-900 border border-zinc-800 hover:border-emerald-500/50 hover:scale-[1.02] transition-all duration-300"
>
<div class="p-2 rounded-lg bg-zinc-800 w-fit mb-4 group-hover:bg-emerald-500/10 transition-colors">
<Shield class="w-5 h-5 text-zinc-400 group-hover:text-emerald-500 transition-colors" :stroke-width="1.5" />
</div>
<h3 class="text-lg font-semibold text-white mb-2">Privasi Terjamin</h3>
<p class="text-zinc-400 text-sm mb-4">Foto Anda dapat diproses dengan aman tanpa harus khawatir disalahgunakan.</p>
<div class="flex items-center gap-2">
<span class="px-2 py-1 text-[10px] font-bold bg-zinc-800 rounded text-zinc-400 tracking-wider">CLIENT-SIDE</span>
<span class="px-2 py-1 text-[10px] font-bold bg-zinc-800 rounded text-zinc-400 tracking-wider">ENCRYPTED</span>
</div>
</div>
</div>
</div>
</section>
</template>
<script setup lang="ts">
import { ref, onMounted, onBeforeUnmount } from 'vue'
import { Activity, Command, BarChart3, Zap, Shield } from 'lucide-vue-next'
const dots = ref([true, true, true, false, true])
const metrics = ['Akurasi', 'Presisi', 'Recall', 'F1-Score']
const metricValues = [96, 95, 96, 95]
let interval: ReturnType<typeof setInterval>
onMounted(() => {
interval = setInterval(() => {
dots.value = dots.value.map(() => Math.random() > 0.2)
}, 2000)
})
onBeforeUnmount(() => {
clearInterval(interval)
})
</script>

View File

@ -2,10 +2,10 @@
<div class="bg-background text-foreground min-h-screen"> <div class="bg-background text-foreground min-h-screen">
<Navbar /> <Navbar />
<Hero /> <Hero />
<LogoMarquee /> <Logo />
<BentoGrid /> <Scanner />
<Kelebihan /> <Keunggulan />
<Pricing /> <Teknologi />
<FAQ /> <FAQ />
<FinalCta /> <FinalCta />
<Footer/> <Footer/>
@ -15,10 +15,10 @@
<script setup lang="ts"> <script setup lang="ts">
import Navbar from '@/components/public/Navbar.vue' import Navbar from '@/components/public/Navbar.vue'
import Hero from '@/components/public/Hero.vue' import Hero from '@/components/public/Hero.vue'
import LogoMarquee from '@/components/public/LogoMarquee.vue' import Logo from '@/components/public/Logo.vue'
import Kelebihan from '@/components/public/kelebihan.vue' import Keunggulan from '@/components/public/Keunggulan.vue'
import BentoGrid from '@/components/public/BentoGrid.vue' import Scanner from '@/components/public/Scanner.vue'
import Pricing from '@/components/public/Pricing.vue' import Teknologi from '@/components/public/Teknologi.vue'
import FAQ from '@/components/public/FAQ.vue' import FAQ from '@/components/public/FAQ.vue'
import FinalCta from '@/components/public/FinalCta.vue' import FinalCta from '@/components/public/FinalCta.vue'
</script> </script>