lil icon update

This commit is contained in:
RetasyaSalsabila 2026-04-01 13:29:49 +07:00
parent 88b3ad8dc6
commit 7b0cb0350f
83 changed files with 1596 additions and 650 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 847 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 695 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 134 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

View File

@ -0,0 +1,174 @@
@extends('siswa.layouts.app')
@section('title', 'Challenge')
@push('styles')
<style>
.page-title { font-size: 28px; font-weight: 800; margin-top: -20px; margin-bottom: 6px; }
.page-subtitle { font-size: 14px; color: #64748b; margin-bottom: 24px; }
.challenge-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 20px;
}
.challenge-card {
background: white;
border-radius: 20px;
border: 2px solid #e5e5e5;
padding: 22px;
transition: transform 0.2s, box-shadow 0.2s;
position: relative;
overflow: hidden;
}
.challenge-card:hover { transform: translateY(-3px); box-shadow: 0 8px 24px rgba(0,0,0,0.1); }
.challenge-card.sudah { border-color: #a5e6ba; background: linear-gradient(135deg, #f0fdf4, #fff); }
.challenge-card.lewat { border-color: #fecaca; background: #fffafa; opacity: 0.85; }
.card-badge {
display: inline-flex;
align-items: center;
gap: 5px;
font-size: 11px;
font-weight: 700;
padding: 3px 10px;
border-radius: 99px;
margin-bottom: 12px;
}
.badge-aktif { background: #dcfce7; color: #16a34a; }
.badge-sudah { background: #e0f2fe; color: #0369a1; }
.badge-lewat { background: #fee2e2; color: #dc2626; }
.card-title { font-size: 16px; font-weight: 700; color: #1e293b; margin-bottom: 8px; line-height: 1.4; }
.card-desc { font-size: 13px; color: #64748b; margin-bottom: 14px; line-height: 1.6; }
.card-meta { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 16px; }
.meta-item {
display: inline-flex;
align-items: center;
gap: 5px;
background: #f8fafc;
border: 1px solid #e2e8f0;
border-radius: 99px;
padding: 4px 10px;
font-size: 12px;
color: #475569;
font-weight: 500;
}
.meta-item.exp { background: #fef9c3; color: #b45309; border-color: #fde68a; }
.btn-kerjakan {
display: block;
text-align: center;
background: linear-gradient(135deg, #667eea, #764ba2);
color: white;
border-radius: 12px;
padding: 10px;
font-size: 14px;
font-weight: 600;
text-decoration: none;
transition: opacity 0.2s;
}
.btn-kerjakan:hover { opacity: 0.9; color: white; }
.btn-lihat-hasil {
display: block;
text-align: center;
background: #e0f2fe;
color: #0369a1;
border-radius: 12px;
padding: 10px;
font-size: 14px;
font-weight: 600;
text-decoration: none;
}
.btn-lihat-hasil:hover { background: #bae6fd; color: #0369a1; }
.btn-disabled {
display: block;
text-align: center;
background: #f1f5f9;
color: #94a3b8;
border-radius: 12px;
padding: 10px;
font-size: 14px;
font-weight: 600;
cursor: not-allowed;
}
.empty-state { text-align: center; padding: 60px 20px; color: #94a3b8; }
.alert-error { background: #fee2e2; color: #991b1b; border-radius: 12px; padding: 12px 16px; margin-bottom: 20px; font-weight: 500; font-size: 14px; }
.challenge-card::before {
content: '🏆';
position: absolute;
top: 16px; right: 16px;
font-size: 28px;
opacity: 0.12;
}
.challenge-card.sudah::before { content: '✅'; opacity: 0.2; }
.challenge-card.lewat::before { content: '⏰'; opacity: 0.15; }
</style>
@endpush
@section('content')
<h3 class="page-title">🏆 Challenge</h3>
<p class="page-subtitle">Kerjakan challenge untuk mendapatkan EXP dan naik peringkat di leaderboard!</p>
@if(session('error'))
<div class="alert-error"> {{ session('error') }}</div>
@endif
@if($challenges->isEmpty())
<div class="empty-state">
<div style="font-size:56px;margin-bottom:16px">🎯</div>
<p style="font-size:16px;font-weight:600;color:#475569">Belum ada challenge untuk kelasmu.</p>
<p style="font-size:13px">Tunggu admin membuat challenge baru!</p>
</div>
@else
<div class="challenge-grid">
@foreach($challenges as $ch)
@php
$isLewat = \Carbon\Carbon::parse($ch->tenggat_waktu)->isPast();
$isSudah = in_array($ch->id_challenge, $sudahDikerjakan);
$cardClass = $isSudah ? 'sudah' : ($isLewat ? 'lewat' : '');
@endphp
<div class="challenge-card {{ $cardClass }}">
@if($isSudah)
<span class="card-badge badge-sudah"> Sudah Dikerjakan</span>
@elseif($isLewat)
<span class="card-badge badge-lewat"> Tenggat Lewat</span>
@else
<span class="card-badge badge-aktif">🔥 Aktif</span>
@endif
<div class="card-title">{{ $ch->judul_challenge }}</div>
@if($ch->deskripsi)
<div class="card-desc">{{ Str::limit($ch->deskripsi, 80) }}</div>
@endif
<div class="card-meta">
<span class="meta-item">📝 {{ $ch->soal_count }} soal</span>
<span class="meta-item exp"> {{ $ch->exp }} EXP</span>
<span class="meta-item"> {{ \Carbon\Carbon::parse($ch->tenggat_waktu)->format('d M Y, H:i') }}</span>
</div>
@if($isSudah)
<a href="{{ route('siswa.challenge.hasil', $ch->id_challenge) }}" class="btn-lihat-hasil">📊 Lihat Hasil</a>
@elseif($isLewat)
<span class="btn-disabled">Tenggat sudah lewat</span>
@else
<a href="{{ route('siswa.challenge.kerjakan', $ch->id_challenge) }}" class="btn-kerjakan">🚀 Kerjakan Sekarang</a>
@endif
</div>
@endforeach
</div>
@endif
@endsection

Binary file not shown.

After

Width:  |  Height:  |  Size: 695 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 695 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 982 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 918 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 939 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

View File

@ -95,25 +95,93 @@
font-size: 14px;
}
/* MODAL */
.modal-header-challenge {
background: linear-gradient(135deg, #667eea, #764ba2);
color: white;
border-bottom: none;
border-radius: 16px 16px 0 0;
}
.modal-header-challenge .btn-close { filter: brightness(0) invert(1); }
/* ── MODAL SHARED ─────────────────────────────────── */
.modal-content {
border-radius: 16px;
border: none;
box-shadow: 0 10px 40px rgba(0,0,0,0.2);
}
/* Header kuning (Tambah) */
.modal-header-yellow {
background: #f9c946;
color: #1e293b;
border-bottom: none;
border-radius: 16px 16px 0 0;
padding: 18px 24px;
}
.modal-header-yellow .modal-title { font-weight: 800; font-size: 18px; }
.modal-header-yellow .btn-close { filter: none; }
/* Header ungu (Edit — tetap seperti semula) */
.modal-header-challenge {
background: linear-gradient(135deg, #667eea, #764ba2);
color: white;
border-bottom: none;
border-radius: 16px 16px 0 0;
}
.modal-header-challenge .btn-close { filter: brightness(0) invert(1); }
.modal-body label { font-weight: 600; font-size: 13px; }
/* SOAL CARD */
/* ── STEP INDICATOR ───────────────────────────────── */
.step-indicator {
display: flex;
align-items: center;
gap: 0;
margin-bottom: 20px;
background: #f8fafc;
border-radius: 12px;
padding: 12px 16px;
border: 1px solid #e2e8f0;
}
.step-dot {
width: 30px;
height: 30px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 13px;
font-weight: 800;
flex-shrink: 0;
transition: all 0.3s;
}
.step-dot.active { background: #f9c946; color: #1e293b; box-shadow: 0 0 0 3px rgba(249,201,70,.3); }
.step-dot.done { background: #16a34a; color: white; }
.step-dot.inactive { background: #e2e8f0; color: #94a3b8; }
.step-label {
font-size: 12px;
font-weight: 700;
margin-left: 8px;
transition: color 0.3s;
}
.step-label.active { color: #1e293b; }
.step-label.inactive { color: #94a3b8; }
.step-line {
flex: 1;
height: 2px;
background: #e2e8f0;
margin: 0 12px;
border-radius: 2px;
overflow: hidden;
position: relative;
}
.step-line-fill {
position: absolute;
inset: 0;
background: #16a34a;
transform: scaleX(0);
transform-origin: left;
transition: transform 0.4s ease;
}
.step-line-fill.filled { transform: scaleX(1); }
/* ── STEP PANELS ──────────────────────────────────── */
.step-panel { display: none; }
.step-panel.active { display: block; }
/* ── SOAL CARD ────────────────────────────────────── */
.soal-card {
background: #f8fafc;
border: 1px solid #e2e8f0;
@ -122,7 +190,6 @@
margin-bottom: 12px;
position: relative;
}
.soal-card .soal-number {
position: absolute;
top: -10px;
@ -134,7 +201,6 @@
padding: 2px 10px;
border-radius: 99px;
}
.soal-card .btn-hapus-soal {
position: absolute;
top: 10px;
@ -151,7 +217,6 @@
align-items: center;
justify-content: center;
}
.soal-card .btn-hapus-soal:hover { background: #fca5a5; }
.opsi-grid {
@ -160,7 +225,6 @@
gap: 8px;
margin-top: 8px;
}
.opsi-item { display: flex; flex-direction: column; gap: 4px; }
.opsi-item label { font-size: 12px; color: #64748b; font-weight: 600; }
.opsi-item input { border-radius: 8px; border: 1px solid #cbd5e1; padding: 6px 10px; font-size: 13px; }
@ -175,7 +239,6 @@
padding: 8px 12px;
border: 1px solid #e2e8f0;
}
.jawaban-row label { font-size: 12px; color: #64748b; font-weight: 600; min-width: 90px; }
.btn-tambah-soal {
@ -197,7 +260,6 @@
grid-template-columns: repeat(3, 1fr);
gap: 8px;
}
.kelas-check-item {
display: flex;
align-items: center;
@ -209,10 +271,78 @@
cursor: pointer;
transition: all 0.2s;
}
.kelas-check-item:hover { background: #e6f0ff; border-color: #2b8ef3; }
.kelas-check-item input[type="checkbox"] { cursor: pointer; }
.modal-dialog-fixed .modal-content {
max-height: 90vh;
overflow: hidden;
display: flex;
flex-direction: column;
}
/* form harus ikut flex agar modal-body bisa grow */
.modal-dialog-fixed form {
display: contents;
}
.modal-dialog-fixed .modal-header,
.modal-dialog-fixed .modal-footer {
flex-shrink: 0;
}
.modal-dialog-fixed .modal-body {
flex: 1 1 auto;
overflow: hidden;
display: flex;
flex-direction: column;
min-height: 0;
padding-bottom: 0;
}
/* step-indicator tidak ikut scroll */
.modal-dialog-fixed .step-indicator {
flex-shrink: 0;
margin-bottom: 16px;
}
.modal-dialog-fixed .step-panel {
display: none;
}
.modal-dialog-fixed .step-panel.active {
display: flex;
flex-direction: column;
flex: 1 1 auto;
min-height: 0;
}
/* Step 1: scroll jika form terlalu panjang */
#tambah-step-1,
#edit-step-1 {
overflow-y: auto;
padding-right: 4px;
}
/* Step 2: soal-scroll-area yang mengelola scroll */
#tambah-step-2,
#edit-step-2 {
overflow: hidden;
}
.soal-scroll-area {
flex: 1 1 auto;
min-height: 0;
overflow-y: auto;
padding-right: 6px;
scroll-behavior: smooth;
}
.soal-scroll-area::-webkit-scrollbar { width: 6px; }
.soal-scroll-area::-webkit-scrollbar-track { background: #f1f5f9; border-radius: 99px; }
.soal-scroll-area::-webkit-scrollbar-thumb { background: #cbd5e1; border-radius: 99px; }
.soal-scroll-area::-webkit-scrollbar-thumb:hover { background: #94a3b8; }
.section-divider {
font-size: 13px;
font-weight: 700;
@ -221,6 +351,24 @@
padding-bottom: 6px;
margin: 16px 0 12px;
}
/* ── FOOTER BUTTONS ───────────────────────────────── */
.btn-green {
background: #16a34a;
color: white;
border: none;
border-radius: 10px;
padding: 9px 22px;
font-size: 14px;
font-weight: 700;
cursor: pointer;
font-family: 'Poppins', sans-serif;
display: inline-flex;
align-items: center;
gap: 6px;
transition: background 0.2s;
}
.btn-green:hover { background: #15803d; }
</style>
<h3 class="page-title">DAFTAR CHALLENGE</h3>
@ -230,18 +378,16 @@
@endif
<div class="custom-card">
<div class="d-flex justify-content-between align-items-center mb-3">
<button class="btn-primary-custom" onclick="openTambahModal()">
<img src="{{ asset('images/icon/main/add.png') }}" width="18"> Tambah Challenge
<img src="{{ asset('images/icon/main/add.png') }}" width="18" alt="Tambah"> Tambah Challenge
</button>
<form method="GET">
<div class="search-box">
<input type="text" name="search" placeholder="Cari challenge..."
value="{{ request('search') }}">
<button style="border:none;background:none" type="submit">
<img src="{{ asset('images/icon/main/search.png') }}" width="18">
<img src="{{ asset('images/icon/main/search.png') }}" width="18" alt="Cari">
</button>
</div>
</form>
@ -288,18 +434,18 @@
<td>
<button onclick="openEditModal({{ $ch->id_challenge }})"
style="border:none;background:none">
<img src="{{ asset('images/icon/main/edit.png') }}" class="action-icon">
<img src="{{ asset('images/icon/main/edit.png') }}" class="action-icon" alt="Edit">
</button>
<a href="{{ route('admin.challenge.show', $ch->id_challenge) }}"
style="border:none;background:none;display:inline">
<img src="{{ asset('images/icon/main/search.png') }}" class="action-icon">
<img src="{{ asset('images/icon/main/search.png') }}" class="action-icon" alt="Detail">
</a>
<form action="{{ route('admin.challenge.destroy', $ch->id_challenge) }}"
method="POST" class="d-inline"
onsubmit="return confirm('Yakin hapus challenge ini?')">
@csrf @method('DELETE')
<button type="submit" style="border:none;background:none">
<img src="{{ asset('images/icon/main/del.png') }}" class="action-icon">
<img src="{{ asset('images/icon/main/del.png') }}" class="action-icon" alt="Hapus">
</button>
</form>
</td>
@ -317,12 +463,14 @@
{{-- ============================================================ --}}
{{-- MODAL TAMBAH --}}
{{-- MODAL TAMBAH (2-step) --}}
{{-- ============================================================ --}}
<div class="modal fade" id="modalTambah" tabindex="-1">
<div class="modal-dialog modal-xl modal-dialog-centered modal-dialog-scrollable">
<div class="modal-dialog modal-xl modal-dialog-centered modal-dialog-fixed">
<div class="modal-content">
<div class="modal-header modal-header-challenge">
{{-- Header kuning --}}
<div class="modal-header modal-header-yellow">
<h5 class="modal-title">🏆 Tambah Challenge Baru</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
@ -331,67 +479,100 @@
@csrf
<div class="modal-body">
{{-- INFO CHALLENGE --}}
<div class="section-divider">📋 Informasi Challenge</div>
<div class="row g-3 mb-2">
<div class="col-8">
<label>Judul Challenge <span class="text-danger">*</span></label>
<input type="text" name="judul_challenge" class="form-control"
placeholder="Contoh: Challenge Matematika Minggu Ini" required>
</div>
<div class="col-4">
<label>Total EXP <span class="text-danger">*</span></label>
<input type="number" name="exp" class="form-control" value="100" min="0" required>
{{-- Step Indicator --}}
<div class="step-indicator" style="flex-shrink:0">
<div class="step-dot active" id="tambah-dot-1">1</div>
<span class="step-label active" id="tambah-label-1">Informasi Challenge</span>
<div class="step-line">
<div class="step-line-fill" id="tambah-line-fill"></div>
</div>
<div class="step-dot inactive" id="tambah-dot-2">2</div>
<span class="step-label inactive" id="tambah-label-2">Daftar Soal</span>
</div>
<div class="mb-3">
<label>Deskripsi</label>
<textarea name="deskripsi" class="form-control" rows="2"
placeholder="Deskripsi singkat challenge (opsional)"></textarea>
</div>
{{-- ─── STEP 1: Info Challenge ─── --}}
<div class="step-panel active" id="tambah-step-1">
<div class="row g-3 mb-3">
<div class="col-6">
<label>Tenggat Waktu <span class="text-danger">*</span></label>
<input type="datetime-local" name="tenggat_waktu" class="form-control" required
min="{{ now()->format('Y-m-d\TH:i') }}">
<div class="section-divider">📋 Informasi Challenge</div>
<div class="row g-3 mb-2">
<div class="col-8">
<label>Judul Challenge <span class="text-danger">*</span></label>
<input type="text" name="judul_challenge" id="tambah-judul" class="form-control"
placeholder="Contoh: Challenge Matematika Minggu Ini">
</div>
<div class="col-4">
<label>Total EXP <span class="text-danger">*</span></label>
<input type="number" name="exp" id="tambah-exp" class="form-control" value="100" min="0">
</div>
</div>
<div class="col-6">
<label>Badge Reward</label>
<select name="id_badge" class="form-control">
<option value="">-- Tanpa Badge --</option>
@foreach($badges as $badge)
<option value="{{ $badge->id_badge }}">{{ $badge->nama_badge }}</option>
@endforeach
</select>
<div class="mb-3">
<label>Deskripsi</label>
<textarea name="deskripsi" class="form-control" rows="2"
placeholder="Deskripsi singkat challenge (opsional)"></textarea>
</div>
</div>
{{-- PILIH KELAS --}}
<div class="section-divider">🏫 Target Kelas</div>
<div class="kelas-checkbox-grid mb-3">
@foreach($kelas as $k)
<label class="kelas-check-item">
<input type="checkbox" name="id_kelas[]" value="{{ $k->id_kelas }}">
<span style="font-size:13px;font-weight:600">{{ $k->tingkat }} {{ $k->nama_kelas }}</span>
</label>
@endforeach
</div>
<div class="row g-3 mb-3">
<div class="col-6">
<label>Tenggat Waktu <span class="text-danger">*</span></label>
<input type="datetime-local" name="tenggat_waktu" id="tambah-tenggat" class="form-control"
min="{{ now()->format('Y-m-d\TH:i') }}">
</div>
<div class="col-6">
<label>Badge Reward</label>
<select name="id_badge" class="form-control">
<option value="">-- Tanpa Badge --</option>
@foreach($badges as $badge)
<option value="{{ $badge->id_badge }}">{{ $badge->nama_badge }}</option>
@endforeach
</select>
</div>
</div>
{{-- SOAL --}}
<div class="section-divider">📝 Daftar Soal</div>
<div id="soalContainer"></div>
<button type="button" class="btn-tambah-soal" onclick="tambahSoal('soalContainer')">
+ Tambah Soal
<div class="section-divider">🏫 Target Kelas</div>
<div class="kelas-checkbox-grid mb-1">
@foreach($kelas as $k)
<label class="kelas-check-item">
<input type="checkbox" name="id_kelas[]" value="{{ $k->id_kelas }}">
<span style="font-size:13px;font-weight:600">{{ $k->tingkat }} {{ $k->nama_kelas }}</span>
</label>
@endforeach
</div>
</div>{{-- /step-1 --}}
{{-- ─── STEP 2: Soal ─── --}}
<div class="step-panel" id="tambah-step-2">
<div class="section-divider" style="flex-shrink:0">📝 Daftar Soal</div>
<div id="soalContainer" class="soal-scroll-area"></div>
<div style="flex-shrink:0;padding-top:8px">
<button type="button" class="btn-tambah-soal" onclick="tambahSoal('soalContainer')">
+ Tambah Soal
</button>
</div>
</div>{{-- /step-2 --}}
</div>{{-- /modal-body --}}
<div class="modal-footer" style="gap:8px">
{{-- Batal (step 1) / Kembali (step 2) --}}
<button type="button" class="btn btn-secondary" id="tambah-btn-back"
data-bs-dismiss="modal">Batal</button>
{{-- Next (step 1) --}}
<button type="button" class="btn-green" id="tambah-btn-next"
onclick="tambahGoStep2()">
Berikutnya
</button>
{{-- Submit (step 2, hidden awalnya) --}}
<button type="submit" class="btn-green" id="tambah-btn-submit" style="display:none">
💾 Simpan Challenge
</button>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Batal</button>
<button type="submit" class="btn btn-success">💾 Simpan Challenge</button>
</div>
</form>
</div>
</div>
@ -402,9 +583,9 @@
{{-- MODAL EDIT --}}
{{-- ============================================================ --}}
<div class="modal fade" id="modalEdit" tabindex="-1">
<div class="modal-dialog modal-xl modal-dialog-centered modal-dialog-scrollable">
<div class="modal-dialog modal-xl modal-dialog-centered modal-dialog-fixed">
<div class="modal-content">
<div class="modal-header modal-header-challenge">
<div class="modal-header modal-header-yellow">
<h5 class="modal-title">✏️ Edit Challenge</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
@ -413,74 +594,236 @@
@csrf @method('PUT')
<div class="modal-body">
<div class="section-divider">📋 Informasi Challenge</div>
<div class="row g-3 mb-2">
<div class="col-8">
<label>Judul Challenge <span class="text-danger">*</span></label>
<input type="text" name="judul_challenge" id="editJudul" class="form-control" required>
</div>
<div class="col-4">
<label>Total EXP <span class="text-danger">*</span></label>
<input type="number" name="exp" id="editExp" class="form-control" min="0" required>
{{-- Step Indicator --}}
<div class="step-indicator" style="flex-shrink:0">
<div class="step-dot active" id="edit-dot-1">1</div>
<span class="step-label active" id="edit-label-1">Informasi Challenge</span>
<div class="step-line">
<div class="step-line-fill" id="edit-line-fill"></div>
</div>
<div class="step-dot inactive" id="edit-dot-2">2</div>
<span class="step-label inactive" id="edit-label-2">Daftar Soal</span>
</div>
<div class="mb-3">
<label>Deskripsi</label>
<textarea name="deskripsi" id="editDeskripsi" class="form-control" rows="2"></textarea>
</div>
{{-- ─── STEP 1: Info Challenge ─── --}}
<div class="step-panel active" id="edit-step-1">
<div class="row g-3 mb-3">
<div class="col-6">
<label>Tenggat Waktu <span class="text-danger">*</span></label>
<input type="datetime-local" name="tenggat_waktu" id="editTenggat" class="form-control" required>
<div class="section-divider">📋 Informasi Challenge</div>
<div class="row g-3 mb-2">
<div class="col-8">
<label>Judul Challenge <span class="text-danger">*</span></label>
<input type="text" name="judul_challenge" id="editJudul" class="form-control" required>
</div>
<div class="col-4">
<label>Total EXP <span class="text-danger">*</span></label>
<input type="number" name="exp" id="editExp" class="form-control" min="0" required>
</div>
</div>
<div class="col-6">
<label>Badge Reward</label>
<select name="id_badge" id="editBadge" class="form-control">
<option value="">-- Tanpa Badge --</option>
@foreach($badges as $badge)
<option value="{{ $badge->id_badge }}">{{ $badge->nama_badge }}</option>
@endforeach
</select>
<div class="mb-3">
<label>Deskripsi</label>
<textarea name="deskripsi" id="editDeskripsi" class="form-control" rows="2"></textarea>
</div>
</div>
<div class="section-divider">🏫 Target Kelas</div>
<div class="kelas-checkbox-grid mb-3" id="editKelasContainer">
@foreach($kelas as $k)
<label class="kelas-check-item">
<input type="checkbox" name="id_kelas[]"
value="{{ $k->id_kelas }}" class="edit-kelas-check">
<span style="font-size:13px;font-weight:600">{{ $k->tingkat }} {{ $k->nama_kelas }}</span>
</label>
@endforeach
</div>
<div class="row g-3 mb-3">
<div class="col-6">
<label>Tenggat Waktu <span class="text-danger">*</span></label>
<input type="datetime-local" name="tenggat_waktu" id="editTenggat" class="form-control" required>
</div>
<div class="col-6">
<label>Badge Reward</label>
<select name="id_badge" id="editBadge" class="form-control">
<option value="">-- Tanpa Badge --</option>
@foreach($badges as $badge)
<option value="{{ $badge->id_badge }}">{{ $badge->nama_badge }}</option>
@endforeach
</select>
</div>
</div>
<div class="section-divider">📝 Daftar Soal</div>
<div id="editSoalContainer"></div>
<button type="button" class="btn-tambah-soal" onclick="tambahSoal('editSoalContainer')">
+ Tambah Soal
<div class="section-divider">🏫 Target Kelas</div>
<div class="kelas-checkbox-grid mb-1" id="editKelasContainer">
@foreach($kelas as $k)
<label class="kelas-check-item">
<input type="checkbox" name="id_kelas[]"
value="{{ $k->id_kelas }}" class="edit-kelas-check">
<span style="font-size:13px;font-weight:600">{{ $k->tingkat }} {{ $k->nama_kelas }}</span>
</label>
@endforeach
</div>
</div>{{-- /edit-step-1 --}}
{{-- ─── STEP 2: Soal ─── --}}
<div class="step-panel" id="edit-step-2">
<div class="section-divider" style="flex-shrink:0">📝 Daftar Soal</div>
<div id="editSoalContainer" class="soal-scroll-area"></div>
<div style="flex-shrink:0;padding-top:8px">
<button type="button" class="btn-tambah-soal" onclick="tambahSoal('editSoalContainer')">
+ Tambah Soal
</button>
</div>
</div>{{-- /edit-step-2 --}}
</div>{{-- /modal-body --}}
<div class="modal-footer" style="gap:8px">
<button type="button" class="btn btn-secondary" id="edit-btn-back"
data-bs-dismiss="modal">Batal</button>
<button type="button" class="btn-green" id="edit-btn-next"
onclick="editGoStep2()">
Berikutnya
</button>
<button type="submit" class="btn-green" id="edit-btn-submit" style="display:none">
💾 Update Challenge
</button>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Batal</button>
<button type="submit" class="btn btn-warning">💾 Update Challenge</button>
</div>
</form>
</div>
</div>
</div>
<script>
let soalCountTambah = 0;
let soalCountEdit = 0;
// ===== TAMBAH SOAL BARU =====
/* ══════════════════════════════════════════════════
STEP NAVIGATION MODAL TAMBAH
══════════════════════════════════════════════════ */
function tambahGoStep2() {
// Validasi step 1
const judul = document.getElementById('tambah-judul').value.trim();
const tenggat = document.getElementById('tambah-tenggat').value;
if (!judul) { alert('Judul challenge wajib diisi.'); return; }
if (!tenggat) { alert('Tenggat waktu wajib diisi.'); return; }
// Transisi
document.getElementById('tambah-step-1').classList.remove('active');
document.getElementById('tambah-step-2').classList.add('active');
// Indikator
document.getElementById('tambah-dot-1').classList.replace('active', 'done');
document.getElementById('tambah-dot-1').textContent = '✓';
document.getElementById('tambah-label-1').classList.replace('active', 'inactive');
document.getElementById('tambah-dot-2').classList.replace('inactive', 'active');
document.getElementById('tambah-label-2').classList.replace('inactive', 'active');
document.getElementById('tambah-line-fill').classList.add('filled');
// Footer buttons
document.getElementById('tambah-btn-next').style.display = 'none';
document.getElementById('tambah-btn-submit').style.display = '';
const backBtn = document.getElementById('tambah-btn-back');
backBtn.removeAttribute('data-bs-dismiss');
backBtn.textContent = '← Kembali';
backBtn.onclick = tambahGoStep1;
}
function tambahGoStep1() {
document.getElementById('tambah-step-2').classList.remove('active');
document.getElementById('tambah-step-1').classList.add('active');
document.getElementById('tambah-dot-1').classList.replace('done', 'active');
document.getElementById('tambah-dot-1').textContent = '1';
document.getElementById('tambah-label-1').classList.replace('inactive', 'active');
document.getElementById('tambah-dot-2').classList.replace('active', 'inactive');
document.getElementById('tambah-label-2').classList.replace('active', 'inactive');
document.getElementById('tambah-line-fill').classList.remove('filled');
document.getElementById('tambah-btn-next').style.display = '';
document.getElementById('tambah-btn-submit').style.display = 'none';
const backBtn = document.getElementById('tambah-btn-back');
backBtn.setAttribute('data-bs-dismiss', 'modal');
backBtn.textContent = 'Batal';
backBtn.onclick = null;
}
/* ══════════════════════════════════════════════════
STEP NAVIGATION MODAL EDIT
══════════════════════════════════════════════════ */
function editGoStep2() {
const judul = document.getElementById('editJudul').value.trim();
const tenggat = document.getElementById('editTenggat').value;
if (!judul) { alert('Judul challenge wajib diisi.'); return; }
if (!tenggat) { alert('Tenggat waktu wajib diisi.'); return; }
document.getElementById('edit-step-1').classList.remove('active');
document.getElementById('edit-step-2').classList.add('active');
document.getElementById('edit-dot-1').classList.replace('active', 'done');
document.getElementById('edit-dot-1').textContent = '✓';
document.getElementById('edit-label-1').classList.replace('active', 'inactive');
document.getElementById('edit-dot-2').classList.replace('inactive', 'active');
document.getElementById('edit-label-2').classList.replace('inactive', 'active');
document.getElementById('edit-line-fill').classList.add('filled');
document.getElementById('edit-btn-next').style.display = 'none';
document.getElementById('edit-btn-submit').style.display = '';
const backBtn = document.getElementById('edit-btn-back');
backBtn.removeAttribute('data-bs-dismiss');
backBtn.textContent = '← Kembali';
backBtn.onclick = editGoStep1;
}
function editGoStep1() {
document.getElementById('edit-step-2').classList.remove('active');
document.getElementById('edit-step-1').classList.add('active');
document.getElementById('edit-dot-1').classList.replace('done', 'active');
document.getElementById('edit-dot-1').textContent = '1';
document.getElementById('edit-label-1').classList.replace('inactive', 'active');
document.getElementById('edit-dot-2').classList.replace('active', 'inactive');
document.getElementById('edit-label-2').classList.replace('active', 'inactive');
document.getElementById('edit-line-fill').classList.remove('filled');
document.getElementById('edit-btn-next').style.display = '';
document.getElementById('edit-btn-submit').style.display = 'none';
const backBtn = document.getElementById('edit-btn-back');
backBtn.setAttribute('data-bs-dismiss', 'modal');
backBtn.textContent = 'Batal';
backBtn.onclick = null;
}
/* ══════════════════════════════════════════════════
EXP HELPER hitung & sebar exp_per_soal otomatis
══════════════════════════════════════════════════ */
function getExpInput(containerId) {
// Tambah → pakai #tambah-exp | Edit → pakai #editExp
return containerId === 'editSoalContainer'
? document.getElementById('editExp')
: document.getElementById('tambah-exp');
}
function recalcExp(containerId) {
const expInput = getExpInput(containerId);
const totalExp = parseInt(expInput ? expInput.value : 0) || 0;
const cards = document.querySelectorAll(`#${containerId} .soal-card`);
const jumlah = cards.length;
if (jumlah === 0) return;
// Bagi rata, sisa EXP diberikan ke soal terakhir
const base = Math.floor(totalExp / jumlah);
const remainder = totalExp - base * jumlah;
cards.forEach((card, i) => {
const hiddenInput = card.querySelector('input[name="exp_per_soal[]"]');
const displaySpan = card.querySelector('.exp-per-soal-display');
const val = base + (i === jumlah - 1 ? remainder : 0);
if (hiddenInput) hiddenInput.value = val;
if (displaySpan) displaySpan.textContent = val + ' EXP';
});
}
/* ══════════════════════════════════════════════════
SOAL HELPERS
══════════════════════════════════════════════════ */
function tambahSoal(containerId, data = null) {
const isEdit = containerId === 'editSoalContainer';
const count = isEdit ? ++soalCountEdit : ++soalCountTambah;
const isEdit = containerId === 'editSoalContainer';
const count = isEdit ? ++soalCountEdit : ++soalCountTambah;
const container = document.getElementById(containerId);
const card = document.createElement('div');
@ -525,17 +868,24 @@ function tambahSoal(containerId, data = null) {
`<option value="${o}" ${data && data.jawaban_benar === o ? 'selected' : ''}>${o}</option>`
).join('')}
</select>
<label style="margin-left:16px">EXP per Soal</label>
<input type="number" name="exp_per_soal[]" class="form-control" style="width:90px"
min="0" value="${data ? data.exp_per_soal : 10}" required>
{{-- Hidden nilainya diisi recalcExp() --}}
<input type="hidden" name="exp_per_soal[]" value="0">
<span style="margin-left:auto;background:#f0fdf4;color:#16a34a;font-size:12px;font-weight:700;
padding:4px 12px;border-radius:99px;white-space:nowrap">
<span class="exp-per-soal-display">0 EXP</span>
</span>
</div>
`;
container.appendChild(card);
renumberSoal(containerId);
recalcExp(containerId); // update semua kartu setelah soal baru ditambah
// Scroll ke soal baru
const scrollArea = container.closest('.soal-scroll-area') || container;
scrollArea.scrollTop = scrollArea.scrollHeight;
}
// ===== HAPUS SOAL =====
function hapusSoal(btn, containerId) {
const container = document.getElementById(containerId);
if (container.querySelectorAll('.soal-card').length <= 1) {
@ -544,56 +894,75 @@ function hapusSoal(btn, containerId) {
}
btn.closest('.soal-card').remove();
renumberSoal(containerId);
recalcExp(containerId); // update ulang setelah soal dihapus
}
// ===== RENUMBER SOAL =====
function renumberSoal(containerId) {
const cards = document.querySelectorAll(`#${containerId} .soal-card`);
cards.forEach((card, i) => {
document.querySelectorAll(`#${containerId} .soal-card`).forEach((card, i) => {
const num = card.querySelector('.soal-number');
if (num) num.textContent = `Soal ${i + 1}`;
});
}
// ===== BUKA MODAL TAMBAH =====
/* ══════════════════════════════════════════════════
OPEN MODAL TAMBAH
══════════════════════════════════════════════════ */
function openTambahModal() {
// Reset state
soalCountTambah = 0;
document.getElementById('soalContainer').innerHTML = '';
document.getElementById('formTambah').reset();
tambahSoal('soalContainer'); // default 1 soal
// Paksa step 1
tambahGoStep1();
// Default 1 soal
tambahSoal('soalContainer');
// Recalc EXP otomatis setiap kali angka EXP di step-1 berubah
const expElTambah = document.getElementById('tambah-exp');
expElTambah.oninput = () => recalcExp('soalContainer');
new bootstrap.Modal(document.getElementById('modalTambah')).show();
}
// ===== BUKA MODAL EDIT =====
/* ══════════════════════════════════════════════════
OPEN MODAL EDIT
══════════════════════════════════════════════════ */
async function openEditModal(idChallenge) {
soalCountEdit = 0;
document.getElementById('editSoalContainer').innerHTML = '';
// Fetch data challenge via AJAX
// Reset ke step 1
editGoStep1();
const res = await fetch(`/admin/challenge/${idChallenge}/edit-data`);
const data = await res.json();
// Isi form
document.getElementById('formEdit').action = `/admin/challenge/${idChallenge}`;
document.getElementById('editJudul').value = data.judul_challenge;
document.getElementById('editExp').value = data.exp;
document.getElementById('editDeskripsi').value = data.deskripsi ?? '';
document.getElementById('editBadge').value = data.id_badge ?? '';
document.getElementById('formEdit').action = `/admin/challenge/${idChallenge}`;
document.getElementById('editJudul').value = data.judul_challenge;
document.getElementById('editExp').value = data.exp;
document.getElementById('editDeskripsi').value = data.deskripsi ?? '';
document.getElementById('editBadge').value = data.id_badge ?? '';
// Format datetime-local
const dt = new Date(data.tenggat_waktu);
const pad = n => String(n).padStart(2,'0');
const dt = new Date(data.tenggat_waktu);
const pad = n => String(n).padStart(2, '0');
document.getElementById('editTenggat').value =
`${dt.getFullYear()}-${pad(dt.getMonth()+1)}-${pad(dt.getDate())}T${pad(dt.getHours())}:${pad(dt.getMinutes())}`;
// Centang kelas
document.querySelectorAll('.edit-kelas-check').forEach(cb => {
cb.checked = data.kelas.includes(parseInt(cb.value));
});
// Isi soal
data.soal.forEach(s => tambahSoal('editSoalContainer', s));
// Recalc EXP otomatis setiap kali angka EXP di modal edit berubah
const expElEdit = document.getElementById('editExp');
expElEdit.oninput = () => recalcExp('editSoalContainer');
// Recalc sekali saat modal dibuka
recalcExp('editSoalContainer');
new bootstrap.Modal(document.getElementById('modalEdit')).show();
}
</script>

View File

@ -138,7 +138,6 @@
@endphp
<div class="dash-header">
<div class="dash-title">{{ $greeting }}, {{ Auth::guard('admin')->user()->username ?? 'Admin' }} 👋</div>
<div class="dash-sub">{{ Carbon::now()->isoFormat('dddd, D MMMM Y') }}</div>
</div>

View File

@ -13,9 +13,9 @@
--blue-dark: #1a7ae0;
--blue-light:#e8f4ff;
--blue-mid: #dbeeff;
--accent1: #f97316; /* orange */
--accent2: #22c55e; /* green */
--accent3: #a855f7; /* purple */
--accent1: #f97316;
--accent2: #22c55e;
--accent3: #a855f7;
--bg: #f0f6ff;
--dark: #0f1f3d;
}
@ -29,6 +29,14 @@
color: var(--dark);
}
/* ── ICON SIZES ── */
.icon-pill { width: 18px; height: 18px; object-fit: contain; vertical-align: middle; }
.icon-sm { width: 20px; height: 20px; object-fit: contain; vertical-align: middle; }
.icon-md { width: 28px; height: 28px; object-fit: contain; vertical-align: middle; }
.icon-feat { width: 32px; height: 32px; object-fit: contain; }
.icon-portal { width: 40px; height: 40px; object-fit: contain; }
.icon-footer { width: 16px; height: 16px; object-fit: contain; vertical-align: middle; margin-right: 6px; }
/* ── NAVBAR ── */
.navbar {
background: rgba(255,255,255,0.9);
@ -51,7 +59,6 @@
}
.navbar-brand img { width: 42px; height: 42px; object-fit: contain; }
.nav-link { font-weight: 600; font-size: 14px; color: #475569 !important; transition: color 0.2s; }
.nav-link:hover { color: var(--blue) !important; }
@ -78,7 +85,6 @@
padding: 70px 0;
}
/* Decorative grid */
.hero::before {
content: '';
position: absolute;
@ -90,15 +96,12 @@
opacity: 0.4;
}
/* Big circle bg */
.hero-bg-circle {
position: absolute;
width: 700px;
height: 700px;
width: 700px; height: 700px;
border-radius: 50%;
background: radial-gradient(circle, rgba(43,142,243,0.12) 0%, transparent 70%);
right: -200px;
top: 50%;
right: -200px; top: 50%;
transform: translateY(-50%);
}
@ -129,10 +132,7 @@
}
.hero-title .blue-text { color: var(--blue); }
.hero-title .underline-text {
position: relative;
display: inline-block;
}
.hero-title .underline-text { position: relative; display: inline-block; }
.hero-title .underline-text::after {
content: '';
position: absolute;
@ -189,12 +189,7 @@
}
.btn-hero-outline:hover { border-color: var(--blue); color: var(--blue); transform: translateY(-3px); }
/* Hero right side */
.hero-visual {
position: relative;
z-index: 2;
animation: fadeUp 0.5s ease 0.15s both;
}
.hero-visual { position: relative; z-index: 2; animation: fadeUp 0.5s ease 0.15s both; }
.hero-main-card {
background: white;
@ -205,48 +200,25 @@
position: relative;
}
.hmc-header {
display: flex;
align-items: center;
gap: 12px;
margin-bottom: 24px;
padding-bottom: 16px;
border-bottom: 2px solid var(--blue-light);
}
.hmc-header { display: flex; align-items: center; gap: 12px; margin-bottom: 24px; padding-bottom: 16px; border-bottom: 2px solid var(--blue-light); }
.hmc-logo { width: 48px; height: 48px; border-radius: 14px; background: var(--blue-light); display: flex; align-items: center; justify-content: center; }
.hmc-logo img { width: 36px; height: 36px; object-fit: contain; }
.hmc-name { font-family: 'Sora', sans-serif; font-weight: 800; font-size: 15px; }
.hmc-sub { font-size: 12px; color: #94a3b8; }
.hmc-stat-row { display: grid; grid-template-columns: repeat(3,1fr); gap: 12px; margin-bottom: 20px; }
.hmc-stat {
background: var(--blue-light);
border-radius: 14px;
padding: 14px 10px;
text-align: center;
}
.hmc-stat { background: var(--blue-light); border-radius: 14px; padding: 14px 10px; text-align: center; }
.hmc-stat-num { font-family: 'Sora', sans-serif; font-weight: 800; font-size: 20px; color: var(--blue); }
.hmc-stat-label { font-size: 10px; color: #64748b; font-weight: 600; margin-top: 2px; }
.hmc-lb { background: var(--blue-light); border-radius: 16px; padding: 16px; }
.hmc-lb-title { font-size: 12px; font-weight: 700; color: #64748b; margin-bottom: 12px; }
.hmc-lb-title { font-size: 12px; font-weight: 700; color: #64748b; margin-bottom: 12px; display: flex; align-items: center; gap: 6px; }
.hmc-lb-row { display: flex; align-items: center; gap: 10px; padding: 8px; border-radius: 10px; background: white; margin-bottom: 8px; }
.hmc-lb-rank { width: 24px; height: 24px; border-radius: 50%; background: var(--blue); color: white; font-size: 11px; font-weight: 800; display: flex; align-items: center; justify-content: center; flex-shrink: 0; overflow: hidden; }
.hmc-lb-rank img { width: 20px; height: 20px; object-fit: contain; }
.hmc-lb-name { flex: 1; font-size: 12px; font-weight: 700; display: flex; align-items: center; gap: 4px; }
.hmc-lb-exp { font-size: 12px; font-weight: 700; color: var(--blue); display: flex; align-items: center; gap: 3px; }
.hmc-lb-row {
display: flex;
align-items: center;
gap: 10px;
padding: 8px;
border-radius: 10px;
background: white;
margin-bottom: 8px;
}
.hmc-lb-rank { width: 24px; height: 24px; border-radius: 50%; background: var(--blue); color: white; font-size: 11px; font-weight: 800; display: flex; align-items: center; justify-content: center; flex-shrink: 0; }
.hmc-lb-name { flex: 1; font-size: 12px; font-weight: 700; }
.hmc-lb-exp { font-size: 12px; font-weight: 700; color: var(--blue); }
/* Floating badges */
.float-badge {
position: absolute;
background: white;
@ -272,22 +244,11 @@
}
/* ── STATS BAR ── */
.stats-bar {
background: var(--blue);
padding: 28px 0;
}
.stats-bar { background: var(--blue); padding: 28px 0; }
.stat-bar-item { text-align: center; color: white; }
.stat-bar-num { font-family: 'Sora', sans-serif; font-weight: 800; font-size: 32px; }
.stat-bar-label { font-size: 13px; opacity: 0.8; margin-top: 2px; }
.stat-divider {
width: 1px;
height: 50px;
background: rgba(255,255,255,0.2);
margin: auto;
}
/* ── FEATURES ── */
.features-section { padding: 90px 0; }
@ -317,7 +278,6 @@
position: relative;
overflow: hidden;
}
.feat-card:hover { transform: translateY(-6px); box-shadow: 0 20px 48px rgba(43,142,243,0.12); border-color: var(--blue); }
.feat-icon {
@ -325,10 +285,8 @@
border-radius: 14px;
background: var(--blue-light);
display: flex; align-items: center; justify-content: center;
font-size: 24px;
margin-bottom: 16px;
}
.feat-icon.orange { background: #fff4ee; }
.feat-icon.green { background: #eefaf3; }
.feat-icon.purple { background: #f5eeff; }
@ -337,132 +295,46 @@
.feat-title { font-family: 'Sora', sans-serif; font-weight: 700; font-size: 17px; color: var(--dark); margin-bottom: 10px; }
.feat-desc { font-size: 13px; color: #64748b; line-height: 1.75; }
.feat-tag {
display: inline-block;
background: var(--blue-light);
color: var(--blue);
font-size: 11px;
font-weight: 700;
padding: 3px 10px;
border-radius: 99px;
margin-top: 14px;
}
.feat-tag { display: inline-block; background: var(--blue-light); color: var(--blue); font-size: 11px; font-weight: 700; padding: 3px 10px; border-radius: 99px; margin-top: 14px; }
/* ── HOW ── */
.how-section { padding: 90px 0; background: linear-gradient(180deg, white, var(--blue-light)); }
.step-wrap {
display: flex;
flex-direction: column;
gap: 0;
}
.step-item {
display: flex;
gap: 24px;
align-items: flex-start;
position: relative;
}
.step-item:not(:last-child)::after {
content: '';
position: absolute;
left: 23px;
top: 52px;
bottom: -20px;
width: 2px;
background: linear-gradient(180deg, var(--blue), transparent);
}
.step-num-wrap {
width: 48px;
height: 48px;
border-radius: 50%;
background: var(--blue);
color: white;
font-family: 'Sora', sans-serif;
font-weight: 800;
font-size: 18px;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
box-shadow: 0 6px 20px rgba(43,142,243,0.35);
}
.step-wrap { display: flex; flex-direction: column; gap: 0; }
.step-item { display: flex; gap: 24px; align-items: flex-start; position: relative; }
.step-item:not(:last-child)::after { content: ''; position: absolute; left: 23px; top: 52px; bottom: -20px; width: 2px; background: linear-gradient(180deg, var(--blue), transparent); }
.step-num-wrap { width: 48px; height: 48px; border-radius: 50%; background: var(--blue); color: white; font-family: 'Sora', sans-serif; font-weight: 800; font-size: 18px; display: flex; align-items: center; justify-content: center; flex-shrink: 0; box-shadow: 0 6px 20px rgba(43,142,243,0.35); }
.step-body { padding-bottom: 36px; }
.step-title { font-family: 'Sora', sans-serif; font-weight: 700; font-size: 17px; margin-bottom: 6px; }
.step-desc { font-size: 13px; color: #64748b; line-height: 1.75; }
/* ── LOGIN ── */
.login-section { padding: 90px 0; }
.portal-card {
background: white;
border-radius: 24px;
padding: 40px 32px;
text-align: center;
border: 2px solid #e8f0fb;
transition: all 0.25s;
position: relative;
overflow: hidden;
}
.portal-card::before {
content: '';
position: absolute;
top: 0; left: 0; right: 0;
height: 5px;
}
.portal-card { background: white; border-radius: 24px; padding: 40px 32px; text-align: center; border: 2px solid #e8f0fb; transition: all 0.25s; position: relative; overflow: hidden; }
.portal-card::before { content: ''; position: absolute; top: 0; left: 0; right: 0; height: 5px; }
.pc-blue::before { background: var(--blue); }
.pc-green::before { background: var(--accent2); }
.pc-orange::before { background: var(--accent1); }
.portal-card:hover { transform: translateY(-6px); box-shadow: 0 20px 48px rgba(43,142,243,0.12); }
.portal-icon-wrap {
width: 72px; height: 72px;
border-radius: 20px;
display: flex; align-items: center; justify-content: center;
font-size: 32px;
margin: 0 auto 18px;
}
.portal-icon-wrap { width: 72px; height: 72px; border-radius: 20px; display: flex; align-items: center; justify-content: center; margin: 0 auto 18px; }
.piw-blue { background: var(--blue-light); }
.piw-green { background: #eefaf3; }
.piw-orange { background: #fff4ee; }
.portal-title { font-family: 'Sora', sans-serif; font-weight: 800; font-size: 20px; margin-bottom: 8px; }
.portal-desc { font-size: 13px; color: #64748b; line-height: 1.7; margin-bottom: 24px; }
.btn-portal {
display: inline-block;
border-radius: 12px;
padding: 12px 28px;
font-weight: 700;
font-size: 14px;
text-decoration: none;
transition: all 0.2s;
}
.btn-portal { display: inline-block; border-radius: 12px; padding: 12px 28px; font-weight: 700; font-size: 14px; text-decoration: none; transition: all 0.2s; }
.bp-blue { background: var(--blue); color: white; }
.bp-green { background: var(--accent2); color: white; }
.bp-orange { background: var(--accent1); color: white; }
.btn-portal:hover { transform: translateY(-2px); box-shadow: 0 8px 24px rgba(0,0,0,0.15); color: white; }
/* ── FOOTER ── */
footer { background: var(--dark); color: white; padding: 48px 0 24px; }
.ft-brand { font-family: 'Sora', sans-serif; font-weight: 800; font-size: 18px; margin-bottom: 10px; }
.ft-desc { font-size: 13px; color: #94a3b8; line-height: 1.7; max-width: 300px; }
.ft-link { color: #94a3b8; font-size: 13px; text-decoration: none; display: block; margin-bottom: 8px; transition: color 0.2s; }
.ft-link { color: #94a3b8; font-size: 13px; text-decoration: none; display: flex; align-items: center; margin-bottom: 8px; transition: color 0.2s; }
.ft-link:hover { color: #60b4ff; }
.ft-heading { font-weight: 700; font-size: 14px; margin-bottom: 16px; }
.ft-copy { font-size: 12px; color: #475569; text-align: center; margin-top: 36px; padding-top: 20px; border-top: 1px solid #1e3a5f; }
.blue-dot { display: inline-block; width: 8px; height: 8px; border-radius: 50%; background: var(--blue); margin-right: 6px; }
</style>
</head>
<body>
@ -471,7 +343,7 @@
<nav class="navbar navbar-expand-lg">
<div class="container">
<a class="navbar-brand" href="#">
<img src="/images/logo/logosmk.png" alt="Logo">
<img src="/images/logo/logosmk.png" alt="Logo SMK">
<div>
<div>E-Learning RPL</div>
<div style="font-size:10px;color:#94a3b8;font-weight:500;">SMKN 1 Tapen</div>
@ -500,16 +372,22 @@
<div class="row align-items-center g-5">
<div class="col-lg-6 hero-content">
<div class="hero-pill">💻 Platform E-Learning Jurusan RPL</div>
<div class="hero-pill">
<img src="{{ asset('images/icon/lp/pc.png') }}" alt="Platform" class="icon-pill">
E-Learning Jurusan RPL
</div>
<h1 class="hero-title">
Kuasai <span class="blue-text">Coding</span>,<br>
Kerjakan <span class="blue-text">Tugas</span>,<br>
Raih <span class="underline-text">Prestasi</span>
</h1>
<p class="hero-desc">
Platform e-learning khusus jurusan Rekayasa Perangkat Lunak akses materi coding, kerjakan tugas, ikuti challenge, dan buktikan siapa developer terbaik di kelasmu!
Platform e-learning khusus jurusan Rekayasa Perangkat Lunak akses materi, kerjakan tugas, ikuti challenge, dan dapatkan badge terbaik!
</p>
<div class="hero-actions">
<a href="#masuk" class="btn-hero-main">🚀 Mulai Sekarang</a>
<a href="#masuk" class="btn-hero-main">
<img src="{{ asset('images/icon/lp/rocket.png') }}" alt="Mulai" class="icon-sm">
Mulai Sekarang
</a>
<a href="#fitur" class="btn-hero-outline">Lihat Fitur</a>
</div>
</div>
@ -518,15 +396,17 @@
<div class="hero-main-card">
<div class="float-badge fb-1">
<span>+150 EXP didapat!</span>
<img src="{{ asset('images/icon/lp/star.png') }}" alt="EXP" class="icon-sm">
<span>+150 EXP didapat!</span>
</div>
<div class="float-badge fb-2">
🏆 <span>Naik ke #2!</span>
<img src="{{ asset('images/icon/lp/piala.png') }}" alt="Naik peringkat" class="icon-sm">
<span>Naik ke #2!</span>
</div>
<div class="hmc-header">
<div class="hmc-logo">
<img src="/images/logo/logosmk.png" alt="Logo">
<img src="/images/logo/logosmk.png" alt="Logo SMK">
</div>
<div>
<div class="hmc-name">Dashboard Siswa RPL</div>
@ -536,35 +416,52 @@
<div class="hmc-stat-row">
<div class="hmc-stat">
<div class="hmc-stat-num">24</div>
<div class="hmc-stat-num">7</div>
<div class="hmc-stat-label">Materi</div>
</div>
<div class="hmc-stat">
<div class="hmc-stat-num">8</div>
<div class="hmc-stat-num">4</div>
<div class="hmc-stat-label">Tugas</div>
</div>
<div class="hmc-stat">
<div class="hmc-stat-num">850</div>
<div class="hmc-stat-num">304</div>
<div class="hmc-stat-label">EXP</div>
</div>
</div>
<div class="hmc-lb">
<div class="hmc-lb-title">🏅 Leaderboard Kelas</div>
<div class="hmc-lb-title">
<img src="{{ asset('images/icon/lp/piala.png') }}" alt="Leaderboard" class="icon-sm">
Leaderboard Kelas
</div>
<div class="hmc-lb-row">
<div class="hmc-lb-rank" style="background:#f59e0b;">🥇</div>
<div class="hmc-lb-name">Andi Prasetyo</div>
<div class="hmc-lb-exp">1.200 </div>
<div class="hmc-lb-rank" style="background:#f59e0b;">
<img src="{{ asset('images/icon/lp/medal1.png') }}" alt="Juara 1">
</div>
<div class="hmc-lb-name">Retasya Salsabila</div>
<div class="hmc-lb-exp">
1.200
<img src="{{ asset('images/icon/lp/star.png') }}" alt="EXP" class="icon-pill">
</div>
</div>
<div class="hmc-lb-row" style="background:#e8f4ff;">
<div class="hmc-lb-rank">2</div>
<div class="hmc-lb-name">Kamu 👋</div>
<div class="hmc-lb-exp">850 </div>
<div class="hmc-lb-rank" style="background:var(--blue);">2</div>
<div class="hmc-lb-name">
Kamu
<img src="{{ asset('images/icon/lp/wavinghand.png') }}" alt="Hai" class="icon-pill">
</div>
<div class="hmc-lb-exp">
850
<img src="{{ asset('images/icon/lp/star.png') }}" alt="EXP" class="icon-pill">
</div>
</div>
<div class="hmc-lb-row">
<div class="hmc-lb-rank" style="background:#64748b;">3</div>
<div class="hmc-lb-name">Siti Rahayu</div>
<div class="hmc-lb-exp">720 </div>
<div class="hmc-lb-name">Keisya Nadia</div>
<div class="hmc-lb-exp">
720
<img src="{{ asset('images/icon/lp/star.png') }}" alt="EXP" class="icon-pill">
</div>
</div>
</div>
</div>
@ -579,25 +476,25 @@
<div class="row align-items-center g-3">
<div class="col-6 col-md-3">
<div class="stat-bar-item">
<div class="stat-bar-num">500+</div>
<div class="stat-bar-num">10+</div>
<div class="stat-bar-label">Materi Tersedia</div>
</div>
</div>
<div class="col-6 col-md-3">
<div class="stat-bar-item">
<div class="stat-bar-num">1.200+</div>
<div class="stat-bar-num">100+</div>
<div class="stat-bar-label">Siswa Aktif</div>
</div>
</div>
<div class="col-6 col-md-3">
<div class="stat-bar-item">
<div class="stat-bar-num">300+</div>
<div class="stat-bar-num">15+</div>
<div class="stat-bar-label">Challenge Selesai</div>
</div>
</div>
<div class="col-6 col-md-3">
<div class="stat-bar-item">
<div class="stat-bar-num">50+</div>
<div class="stat-bar-num">15+</div>
<div class="stat-bar-label">Guru Pengajar</div>
</div>
</div>
@ -611,41 +508,49 @@
<div class="row align-items-center g-5 mb-5">
<div class="col-lg-5">
<div class="pill-label"> Fitur Platform</div>
<h2 class="sec-title">Lengkap untuk<br>Siswa RPL yang<br>Ambisius</h2>
<h2 class="sec-title">Lengkap untuk<br>Siswa RPL di<br>SMKN 1 Tapen</h2>
</div>
<div class="col-lg-6 offset-lg-1">
<p class="sec-desc">Dari materi pemrograman hingga gamifikasi semua dirancang khusus untuk mendukung proses belajar di jurusan Rekayasa Perangkat Lunak.</p>
<p class="sec-desc">Dari materi pemrograman hingga gamifikasi semua dirancang untuk mendukung proses belajar mengajar di jurusan Rekayasa Perangkat Lunak.</p>
</div>
</div>
<div class="row g-4">
<div class="col-md-6 col-lg-4">
<div class="feat-card">
<div class="feat-icon">📖</div>
<div class="feat-icon">
<img src="{{ asset('images/icon/lp/buku1.png') }}" alt="Materi Digital" class="icon-feat">
</div>
<div class="feat-title">Materi Digital</div>
<p class="feat-desc">Guru RPL upload modul, siswa akses kapan saja. Materi terorganisir per mapel dan kelas dari dasar coding sampai advanced.</p>
<p class="feat-desc">Guru RPL upload materi, siswa akses kapan saja. Materi terorganisir per mapel dan kelas dari dasar coding hingga advanced.</p>
<span class="feat-tag">Untuk Siswa & Guru</span>
</div>
</div>
<div class="col-md-6 col-lg-4">
<div class="feat-card">
<div class="feat-icon orange">📝</div>
<div class="feat-icon orange">
<img src="{{ asset('images/icon/lp/buku2.png') }}" alt="Pengumpulan Tugas" class="icon-feat">
</div>
<div class="feat-title">Pengumpulan Tugas</div>
<p class="feat-desc">Submit tugas coding online langsung dari browser. Tracking status real-time: belum, dikumpulkan, atau terlambat.</p>
<p class="feat-desc">Submit tugas langsung dari browser. Tracking status real-time: belum, dikumpulkan, atau terlambat.</p>
<span class="feat-tag" style="background:#fff4ee;color:#f97316;">Manajemen Tugas</span>
</div>
</div>
<div class="col-md-6 col-lg-4">
<div class="feat-card">
<div class="feat-icon green">🏆</div>
<div class="feat-icon green">
<img src="{{ asset('images/icon/lp/piala.png') }}" alt="Challenge Interaktif" class="icon-feat">
</div>
<div class="feat-title">Challenge Interaktif</div>
<p class="feat-desc">Kuis pilihan ganda seputar materi RPL dengan navigasi soal dinamis. Kerjakan sebelum tenggat dan buktikan kemampuanmu!</p>
<p class="feat-desc">Kuis pilihan ganda seputar materi RPL. Kerjakan sebelum tenggat dan buktikan kemampuanmu!</p>
<span class="feat-tag" style="background:#eefaf3;color:#22c55e;">Gamifikasi</span>
</div>
</div>
<div class="col-md-6 col-lg-4">
<div class="feat-card">
<div class="feat-icon purple"></div>
<div class="feat-icon purple">
<img src="{{ asset('images/icon/lp/star.png') }}" alt="Sistem EXP" class="icon-feat">
</div>
<div class="feat-title">Sistem EXP</div>
<p class="feat-desc">Setiap jawaban benar menghasilkan EXP. Semakin banyak belajar, semakin tinggi skormu!</p>
<span class="feat-tag" style="background:#f5eeff;color:#a855f7;">Reward System</span>
@ -653,18 +558,22 @@
</div>
<div class="col-md-6 col-lg-4">
<div class="feat-card">
<div class="feat-icon yellow">📊</div>
<div class="feat-icon yellow">
<img src="{{ asset('images/icon/lp/lb.png') }}" alt="Leaderboard" class="icon-feat">
</div>
<div class="feat-title">Leaderboard Real-time</div>
<p class="feat-desc">Peringkat diperbarui otomatis setiap kali siswa menyelesaikan challenge. Siapa #1?</p>
<span class="feat-tag" style="background:#fffbea;color:#d97706;">Kompetisi Sehat</span>
<span class="feat-tag" style="background:#fffbea;color:#d97706;">Kompetisi Sehat Antar Siswa</span>
</div>
</div>
<div class="col-md-6 col-lg-4">
<div class="feat-card">
<div class="feat-icon pink">🛡️</div>
<div class="feat-icon pink">
<img src="{{ asset('images/icon/lp/shield.png') }}" alt="Multi-Role Panel" class="icon-feat">
</div>
<div class="feat-title">Multi-Role Panel</div>
<p class="feat-desc">Dashboard terpisah untuk Siswa, Guru, dan Admin dengan hak akses yang sesuai peran.</p>
<span class="feat-tag" style="background:#fff0f6;color:#ec4899;">Keamanan</span>
<span class="feat-tag" style="background:#fff0f6;color:#ec4899;">Keamanan User</span>
</div>
</div>
</div>
@ -678,9 +587,8 @@
<div class="col-lg-4">
<div class="pill-label"> Cara Kerja</div>
<h2 class="sec-title">Mulai dalam<br>3 Langkah<br>Mudah</h2>
<p class="sec-desc" style="margin-top:14px;">Tidak perlu instalasi apapun cukup buka browser dan login!</p>
<p class="sec-desc" style="margin-top:14px;">Tidak perlu instalasi apapun cukup buka browser dan login!</p>
</div>
<div class="col-lg-7 offset-lg-1">
<div class="step-wrap">
<div class="step-item">
@ -694,7 +602,7 @@
<div class="step-num-wrap">2</div>
<div class="step-body">
<div class="step-title">Akses Materi &amp; Selesaikan Tugas</div>
<p class="step-desc">Pelajari materi yang diupload guru dan kumpulkan tugas tepat waktu untuk performa terbaik.</p>
<p class="step-desc">Pelajari materi yang diupload guru dan kumpulkan tugas tepat waktu.</p>
</div>
</div>
<div class="step-item">
@ -718,27 +626,32 @@
<h2 class="sec-title">Masuk Sesuai Peranmu</h2>
<p style="font-size:15px;color:#64748b;">Pilih portal yang sesuai dengan peranmu di jurusan RPL SMKN 1 Tapen.</p>
</div>
<div class="row g-4 justify-content-center">
<div class="col-md-4">
<div class="portal-card pc-blue">
<div class="portal-icon-wrap piw-blue">👨‍💻</div>
<div class="portal-icon-wrap piw-blue">
<img src="{{ asset('images/icon/lp/siswa.png') }}" alt="Siswa" class="icon-portal">
</div>
<div class="portal-title">Siswa RPL</div>
<p class="portal-desc">Akses materi coding, kumpulkan tugas, kerjakan challenge, dan pantau posisimu di leaderboard kelas.</p>
<p class="portal-desc">Akses materi coding, kumpulkan tugas, kerjakan challenge, dan lihat posisimu di leaderboard kelas.</p>
<a href="/siswa/login" class="btn-portal bp-blue">Masuk sebagai Siswa </a>
</div>
</div>
<div class="col-md-4">
<div class="portal-card pc-green">
<div class="portal-icon-wrap piw-green">👩‍🏫</div>
<div class="portal-icon-wrap piw-green">
<img src="{{ asset('images/icon/lp/guru.png') }}" alt="Guru" class="icon-portal">
</div>
<div class="portal-title">Guru</div>
<p class="portal-desc">Upload modul RPL, buat tugas coding, nilai pengumpulan siswa, dan pantau perkembangan kelas.</p>
<p class="portal-desc">Upload materi, buat tugas, nilai pengumpulan siswa, dan pantau perkembangan kelas.</p>
<a href="/guru/login" class="btn-portal bp-green">Masuk sebagai Guru </a>
</div>
</div>
<div class="col-md-4">
<div class="portal-card pc-orange">
<div class="portal-icon-wrap piw-orange">🛡️</div>
<div class="portal-icon-wrap piw-orange">
<img src="{{ asset('images/icon/lp/admin.png') }}" alt="Admin" class="icon-portal">
</div>
<div class="portal-title">Admin</div>
<p class="portal-desc">Kelola data jurusan RPL, buat challenge, dan pantau seluruh aktivitas di platform.</p>
<a href="/admin/login" class="btn-portal bp-orange">Masuk sebagai Admin </a>
@ -754,7 +667,7 @@
<div class="row g-4">
<div class="col-md-5">
<div class="d-flex align-items-center gap-3 mb-3">
<img src="/images/logo/logosmk.png" alt="Logo" style="width:40px;height:40px;object-fit:contain;filter:brightness(10);">
<img src="/images/logo/logosmk.png" alt="Logo SMK" style="width:40px;height:40px;object-fit:contain;filter:brightness(10);">
<div class="ft-brand">E-Learning RPL</div>
</div>
<p class="ft-desc">Platform e-learning dengan gamifikasi khusus jurusan Rekayasa Perangkat Lunak SMKN 1 Tapen.</p>
@ -767,18 +680,23 @@
</div>
<div class="col-md-3">
<div class="ft-heading">Portal</div>
<a href="/siswa/login" class="ft-link">👩‍🎓 Siswa</a>
<a href="/guru/login" class="ft-link">👩‍🏫 Guru</a>
<a href="/admin/login" class="ft-link">🛡️ Admin</a>
<a href="/siswa/login" class="ft-link">
<img src="{{ asset('images/icon/lp/siswa.png') }}" alt="Siswa" class="icon-footer"> Siswa
</a>
<a href="/guru/login" class="ft-link">
<img src="{{ asset('images/icon/lp/guru.png') }}" alt="Guru" class="icon-footer"> Guru
</a>
<a href="/admin/login" class="ft-link">
<img src="{{ asset('images/icon/lp/admin.png') }}" alt="Admin" class="icon-footer"> Admin
</a>
</div>
</div>
<div class="ft-copy">© 2025 SMKN 1 Tapen · Jurusan Rekayasa Perangkat Lunak · Platform E-Learning Gamifikasi</div>
<div class="ft-copy">© Retasya Salsabila · 2026 SMKN 1 Tapen · Jurusan Rekayasa Perangkat Lunak · Platform E-Learning Gamifikasi</div>
</div>
</footer>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
<script>
// Smooth scroll
document.querySelectorAll('a[href^="#"]').forEach(a => {
a.addEventListener('click', e => {
const t = document.querySelector(a.getAttribute('href'));
@ -786,7 +704,6 @@
});
});
// Scroll reveal
const observer = new IntersectionObserver((entries) => {
entries.forEach(e => {
if (e.isIntersecting) {

View File

@ -17,25 +17,23 @@
overflow: hidden;
}
.hasil-hero::before {
content: '🏆';
position: absolute;
font-size: 120px;
opacity: 0.08;
top: -10px; right: -10px;
}
.hasil-emoji { margin-bottom: 10px; }
.hasil-emoji img { width: 64px; height: 64px; object-fit: contain; }
.hasil-emoji { font-size: 52px; margin-bottom: 10px; }
.hasil-title { font-size: 22px; font-weight: 800; margin-bottom: 4px; }
.hasil-subtitle { font-size: 14px; opacity: 0.85; margin-bottom: 20px; }
.hasil-exp {
display: inline-block;
display: inline-flex;
align-items: center;
gap: 8px;
background: rgba(255,255,255,0.2);
border-radius: 99px;
padding: 8px 24px;
font-size: 20px;
font-weight: 800;
}
.hasil-exp img { width: 22px; height: 22px; object-fit: contain; }
.stat-row { display: grid; grid-template-columns: repeat(3,1fr); gap: 14px; margin-bottom: 24px; }
.stat-box { background: white; border-radius: 16px; border: 2px solid #e5e5e5; padding: 18px 14px; text-align: center; }
@ -43,7 +41,17 @@
.stat-label { font-size: 12px; color: #94a3b8; margin: 4px 0 0; font-weight: 500; }
.custom-card { background: white; border-radius: 20px; border: 2px solid #e5e5e5; padding: 24px; margin-bottom: 16px; }
.section-title { font-size: 16px; font-weight: 700; color: #1e293b; margin-bottom: 16px; }
.section-title {
font-size: 16px;
font-weight: 700;
color: #1e293b;
margin-bottom: 16px;
display: flex;
align-items: center;
gap: 8px;
}
.section-title img { width: 20px; height: 20px; object-fit: contain; }
.soal-review { border: 2px solid #e2e8f0; border-radius: 14px; padding: 18px; margin-bottom: 12px; }
.soal-review.benar { border-color: #22c55e; background: #f0fdf4; }
@ -51,9 +59,20 @@
.review-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px; }
.review-number { font-size: 12px; font-weight: 700; color: #64748b; }
.review-status { font-size: 12px; font-weight: 700; padding: 2px 10px; border-radius: 99px; }
.review-status {
display: inline-flex;
align-items: center;
gap: 4px;
font-size: 12px;
font-weight: 700;
padding: 3px 10px;
border-radius: 99px;
}
.review-status img { width: 13px; height: 13px; object-fit: contain; }
.review-status.benar { background: #dcfce7; color: #16a34a; }
.review-status.salah { background: #fee2e2; color: #dc2626; }
.review-pertanyaan { font-size: 14px; font-weight: 600; color: #1e293b; margin-bottom: 12px; line-height: 1.6; }
.opsi-review {
@ -66,7 +85,6 @@
margin-bottom: 6px;
background: #f8fafc;
}
.opsi-review.jawaban-benar { background: #dcfce7; font-weight: 700; color: #15803d; }
.opsi-review.salah-dipilih { background: #fee2e2; color: #991b1b; font-weight: 700; }
@ -77,13 +95,25 @@
display: flex; align-items: center; justify-content: center;
font-size: 11px; font-weight: 700; flex-shrink: 0;
}
.opsi-review.jawaban-benar .opsi-circle { background: #22c55e; color: white; }
.opsi-review.salah-dipilih .opsi-circle { background: #ef4444; color: white; }
.opsi-tag {
margin-left: auto;
font-size: 12px;
display: inline-flex;
align-items: center;
gap: 4px;
flex-shrink: 0;
white-space: nowrap;
}
.opsi-tag img { width: 13px; height: 13px; object-fit: contain; }
.btn-back {
display: block;
text-align: center;
display: flex;
justify-content: center;
align-items: center;
gap: 8px;
background: linear-gradient(135deg, #667eea, #764ba2);
color: white;
border-radius: 12px;
@ -100,7 +130,17 @@
@section('content')
@php
$emoji = $persentase >= 80 ? '🎉' : ($persentase >= 60 ? '👍' : '💪');
if ($persentase >= 80) {
$emojiSrc = asset('images/icon/siswac/confetti.png');
$emojiAlt = 'Selamat, hasil luar biasa!';
} elseif ($persentase >= 60) {
$emojiSrc = asset('images/icon/siswac/sip.png');
$emojiAlt = 'Bagus, terus tingkatkan!';
} else {
$emojiSrc = asset('images/icon/siswac/muscle.png');
$emojiAlt = 'Semangat, jangan menyerah!';
}
$pesan = $persentase >= 80
? 'Luar biasa! Kamu menguasai materi ini!'
: ($persentase >= 60 ? 'Bagus! Terus tingkatkan kemampuanmu!' : 'Jangan menyerah! Terus semangat belajar!');
@ -109,10 +149,15 @@
<div class="hasil-wrapper">
<div class="hasil-hero">
<div class="hasil-emoji">{{ $emoji }}</div>
<div class="hasil-emoji">
<img src="{{ $emojiSrc }}" alt="{{ $emojiAlt }}">
</div>
<div class="hasil-title">{{ $challenge->judul_challenge }}</div>
<div class="hasil-subtitle">{{ $pesan }}</div>
<div class="hasil-exp"> +{{ $peserta->exp }} EXP didapat!</div>
<div class="hasil-exp">
<img src="{{ asset('images/icon/siswac/star.png') }}" alt="Ikon bintang EXP">
+{{ $peserta->exp }} EXP didapat!
</div>
</div>
<div class="stat-row">
@ -131,7 +176,10 @@
</div>
<div class="custom-card">
<p class="section-title">📋 Pembahasan Jawaban</p>
<p class="section-title">
<img src="{{ asset('images/icon/siswac/buku1.png') }}" alt="Ikon buku">
Pembahasan Jawaban
</p>
@foreach($challenge->soal as $i => $soal)
@php
@ -143,7 +191,11 @@
<div class="review-header">
<span class="review-number">Soal {{ $i + 1 }}</span>
<span class="review-status {{ $isBenar ? 'benar' : 'salah' }}">
{{ $isBenar ? '✅ Benar' : '❌ Salah' }}
@if($isBenar)
<img src="{{ asset('images/icon/siswac/v.png') }}" alt="Centang benar"> Benar
@else
<img src="{{ asset('images/icon/siswac/x.png') }}" alt="Tanda salah"> Salah
@endif
</span>
</div>
@ -160,9 +212,11 @@
<span class="opsi-circle">{{ $opsi }}</span>
<span>{{ $soal->$key }}</span>
@if($isJwbBenar)
<span style="margin-left:auto;font-size:12px"> Jawaban benar</span>
<span class="opsi-tag">
<img src="{{ asset('images/icon/siswac/v.png') }}" alt="Centang benar"> Jawaban benar
</span>
@elseif($isDipilih)
<span style="margin-left:auto;font-size:12px"> Jawabanmu</span>
<span class="opsi-tag"> Jawabanmu</span>
@endif
</div>
@endforeach

View File

@ -4,7 +4,16 @@
@push('styles')
<style>
.page-title { font-size: 28px; font-weight: 800; margin-top: -20px; margin-bottom: 6px; }
.page-title {
font-size: 22px;
font-weight: 700;
color: #1e293b;
margin-bottom: 24px;
display: flex;
align-items: center;
gap: 10px;
}
.page-title img { width: 32px; height: 32px; object-fit: contain; }
.page-subtitle { font-size: 14px; color: #64748b; margin-bottom: 24px; }
.challenge-grid {
@ -13,6 +22,7 @@
gap: 20px;
}
/* Hapus ::before emoji — diganti dengan badge gambar */
.challenge-card {
background: white;
border-radius: 20px;
@ -22,11 +32,20 @@
position: relative;
overflow: hidden;
}
.challenge-card:hover { transform: translateY(-3px); box-shadow: 0 8px 24px rgba(0,0,0,0.1); }
.challenge-card.sudah { border-color: #a5e6ba; background: linear-gradient(135deg, #f0fdf4, #fff); }
.challenge-card.lewat { border-color: #fecaca; background: #fffafa; opacity: 0.85; }
/* Watermark pojok kanan atas via img absolut */
.card-watermark {
position: absolute;
top: 14px; right: 14px;
width: 36px; height: 36px;
object-fit: contain;
opacity: 0.12;
pointer-events: none;
}
.card-badge {
display: inline-flex;
align-items: center;
@ -37,6 +56,7 @@
border-radius: 99px;
margin-bottom: 12px;
}
.card-badge img { width: 13px; height: 13px; object-fit: contain; }
.badge-aktif { background: #dcfce7; color: #16a34a; }
.badge-sudah { background: #e0f2fe; color: #0369a1; }
@ -59,12 +79,14 @@
color: #475569;
font-weight: 500;
}
.meta-item img { width: 14px; height: 14px; object-fit: contain; }
.meta-item.exp { background: #fef9c3; color: #b45309; border-color: #fde68a; }
.btn-kerjakan {
display: block;
text-align: center;
display: flex;
justify-content: center;
align-items: center;
gap: 7px;
background: linear-gradient(135deg, #667eea, #764ba2);
color: white;
border-radius: 12px;
@ -74,11 +96,14 @@
text-decoration: none;
transition: opacity 0.2s;
}
.btn-kerjakan img { width: 16px; height: 16px; object-fit: contain; }
.btn-kerjakan:hover { opacity: 0.9; color: white; }
.btn-lihat-hasil {
display: block;
text-align: center;
display: flex;
justify-content: center;
align-items: center;
gap: 7px;
background: #e0f2fe;
color: #0369a1;
border-radius: 12px;
@ -87,6 +112,7 @@
font-weight: 600;
text-decoration: none;
}
.btn-lihat-hasil img { width: 16px; height: 16px; object-fit: contain; }
.btn-lihat-hasil:hover { background: #bae6fd; color: #0369a1; }
.btn-disabled {
@ -102,32 +128,42 @@
}
.empty-state { text-align: center; padding: 60px 20px; color: #94a3b8; }
.alert-error { background: #fee2e2; color: #991b1b; border-radius: 12px; padding: 12px 16px; margin-bottom: 20px; font-weight: 500; font-size: 14px; }
.empty-state img { width: 56px; height: 56px; object-fit: contain; margin-bottom: 16px; }
.challenge-card::before {
content: '🏆';
position: absolute;
top: 16px; right: 16px;
font-size: 28px;
opacity: 0.12;
.alert-error {
display: flex;
align-items: center;
gap: 8px;
background: #fee2e2;
color: #991b1b;
border-radius: 12px;
padding: 12px 16px;
margin-bottom: 20px;
font-weight: 500;
font-size: 14px;
}
.challenge-card.sudah::before { content: '✅'; opacity: 0.2; }
.challenge-card.lewat::before { content: '⏰'; opacity: 0.15; }
.alert-error img { width: 16px; height: 16px; object-fit: contain; flex-shrink: 0; }
</style>
@endpush
@section('content')
<h3 class="page-title">🏆 Challenge</h3>
<h3 class="page-title">
<img src="{{ asset('images/icon/siswac/piala.png') }}" alt="Ikon piala">
Challenge
</h3>
<p class="page-subtitle">Kerjakan challenge untuk mendapatkan EXP dan naik peringkat di leaderboard!</p>
@if(session('error'))
<div class="alert-error"> {{ session('error') }}</div>
<div class="alert-error">
<img src="{{ asset('images/icon/siswac/x.png') }}" alt="Error">
{{ session('error') }}
</div>
@endif
@if($challenges->isEmpty())
<div class="empty-state">
<div style="font-size:56px;margin-bottom:16px">🎯</div>
<img src="{{ asset('images/icon/siswac/target.png') }}" alt="Belum ada challenge">
<p style="font-size:16px;font-weight:600;color:#475569">Belum ada challenge untuk kelasmu.</p>
<p style="font-size:13px">Tunggu admin membuat challenge baru!</p>
</div>
@ -138,14 +174,36 @@
$isLewat = \Carbon\Carbon::parse($ch->tenggat_waktu)->isPast();
$isSudah = in_array($ch->id_challenge, $sudahDikerjakan);
$cardClass = $isSudah ? 'sudah' : ($isLewat ? 'lewat' : '');
// Watermark pojok kanan atas sesuai status
if ($isSudah) {
$watermarkSrc = asset('images/icon/siswac/v.png');
$watermarkAlt = 'Sudah dikerjakan';
} elseif ($isLewat) {
$watermarkSrc = asset('images/icon/siswac/alarm.png');
$watermarkAlt = 'Tenggat lewat';
} else {
$watermarkSrc = asset('images/icon/siswac/piala.png');
$watermarkAlt = 'Challenge aktif';
}
@endphp
<div class="challenge-card {{ $cardClass }}">
{{-- Watermark pengganti ::before emoji --}}
<img class="card-watermark" src="{{ $watermarkSrc }}" alt="{{ $watermarkAlt }}">
@if($isSudah)
<span class="card-badge badge-sudah"> Sudah Dikerjakan</span>
<span class="card-badge badge-sudah">
<img src="{{ asset('images/icon/siswac/v.png') }}" alt="Centang"> Sudah Dikerjakan
</span>
@elseif($isLewat)
<span class="card-badge badge-lewat"> Tenggat Lewat</span>
<span class="card-badge badge-lewat">
<img src="{{ asset('images/icon/siswac/alarm.png') }}" alt="Alarm"> Tenggat Lewat
</span>
@else
<span class="card-badge badge-aktif">🔥 Aktif</span>
<span class="card-badge badge-aktif">
<img src="{{ asset('images/icon/siswac/api.png') }}" alt="Api aktif"> Aktif
</span>
@endif
<div class="card-title">{{ $ch->judul_challenge }}</div>
@ -154,18 +212,34 @@
@endif
<div class="card-meta">
<span class="meta-item">📝 {{ $ch->soal_count }} soal</span>
<span class="meta-item exp"> {{ $ch->exp }} EXP</span>
<span class="meta-item"> {{ \Carbon\Carbon::parse($ch->tenggat_waktu)->format('d M Y, H:i') }}</span>
<span class="meta-item">
<img src="{{ asset('images/icon/siswac/buku1.png') }}" alt="Jumlah soal">
{{ $ch->soal_count }} soal
</span>
<span class="meta-item exp">
<img src="{{ asset('images/icon/siswac/star.png') }}" alt="EXP">
{{ $ch->exp }} EXP
</span>
<span class="meta-item">
<img src="{{ asset('images/icon/siswac/alarm.png') }}" alt="Tenggat waktu">
{{ \Carbon\Carbon::parse($ch->tenggat_waktu)->format('d M Y, H:i') }}
</span>
</div>
@if($isSudah)
<a href="{{ route('siswa.challenge.hasil', $ch->id_challenge) }}" class="btn-lihat-hasil">📊 Lihat Hasil</a>
<a href="{{ route('siswa.challenge.hasil', $ch->id_challenge) }}" class="btn-lihat-hasil">
<img src="{{ asset('images/icon/siswac/lb.png') }}" alt="Lihat hasil">
Lihat Hasil
</a>
@elseif($isLewat)
<span class="btn-disabled">Tenggat sudah lewat</span>
@else
<a href="{{ route('siswa.challenge.kerjakan', $ch->id_challenge) }}" class="btn-kerjakan">🚀 Kerjakan Sekarang</a>
<a href="{{ route('siswa.challenge.kerjakan', $ch->id_challenge) }}" class="btn-kerjakan">
<img src="{{ asset('images/icon/siswac/rocket.png') }}" alt="Kerjakan">
Kerjakan Sekarang
</a>
@endif
</div>
@endforeach
</div>

View File

@ -24,7 +24,12 @@
font-weight: 800;
color: #1e293b;
margin-bottom: 6px;
display: flex;
justify-content: center;
align-items: center;
gap: 8px;
}
.quiz-title img { width: 24px; height: 24px; object-fit: contain; }
.quiz-meta {
font-size: 13px;
@ -34,12 +39,12 @@
gap: 16px;
flex-wrap: wrap;
}
.quiz-meta span {
display: inline-flex;
align-items: center;
gap: 4px;
gap: 5px;
}
.quiz-meta img { width: 14px; height: 14px; object-fit: contain; }
/* Progress bar */
.progress-bar-wrap {
@ -49,14 +54,12 @@
margin-bottom: 28px;
overflow: hidden;
}
.progress-bar-fill {
height: 100%;
background: linear-gradient(90deg, #667eea, #764ba2);
border-radius: 99px;
transition: width 0.4s ease;
}
.progress-label {
text-align: center;
font-size: 12px;
@ -72,9 +75,8 @@
border: 2px solid #e5e5e5;
padding: 28px;
margin-bottom: 20px;
display: none; /* sembunyikan semua dulu */
display: none;
}
.soal-card.active { display: block; }
.soal-number {
@ -113,19 +115,16 @@
color: #1e293b;
user-select: none;
}
.opsi-item:hover {
border-color: #667eea;
background: #f0eeff;
}
.opsi-item.selected {
border-color: #667eea;
background: linear-gradient(135deg, #ede9fe, #f5f3ff);
font-weight: 600;
color: #4c1d95;
}
.opsi-item input[type="radio"] { display: none; }
.opsi-label-circle {
@ -141,7 +140,6 @@
flex-shrink: 0;
transition: all 0.18s;
}
.opsi-item.selected .opsi-label-circle {
background: #667eea;
color: white;
@ -156,6 +154,9 @@
}
.btn-nav {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 11px 24px;
border-radius: 12px;
border: none;
@ -165,11 +166,9 @@
transition: all 0.2s;
font-family: 'Poppins', sans-serif;
}
.btn-nav img { width: 16px; height: 16px; object-fit: contain; }
.btn-prev {
background: #f1f5f9;
color: #475569;
}
.btn-prev { background: #f1f5f9; color: #475569; }
.btn-prev:hover { background: #e2e8f0; }
.btn-prev:disabled { opacity: 0.4; cursor: not-allowed; }
@ -196,7 +195,6 @@
margin-bottom: 20px;
flex-wrap: wrap;
}
.soal-dot {
width: 32px;
height: 32px;
@ -211,13 +209,15 @@
cursor: pointer;
transition: all 0.2s;
}
.soal-dot.active { background: #667eea; color: white; }
.soal-dot.answered { background: #dcfce7; color: #16a34a; border: 2px solid #22c55e; }
.soal-dot.active.answered { background: #22c55e; color: white; }
/* Warning belum semua dijawab */
.warning-box {
display: none;
align-items: center;
gap: 8px;
background: #fff7ed;
border: 1px solid #fed7aa;
border-radius: 12px;
@ -226,8 +226,8 @@
color: #c2410c;
font-weight: 500;
margin-bottom: 16px;
display: none;
}
.warning-box img { width: 16px; height: 16px; object-fit: contain; flex-shrink: 0; }
</style>
@endpush
@ -237,11 +237,23 @@
{{-- Header --}}
<div class="quiz-header">
<div class="quiz-title">🏆 {{ $challenge->judul_challenge }}</div>
<div class="quiz-title">
<img src="{{ asset('images/icon/siswac/piala.png') }}" alt="Ikon piala">
{{ $challenge->judul_challenge }}
</div>
<div class="quiz-meta">
<span>📝 {{ $challenge->soal->count() }} Soal</span>
<span> {{ $challenge->exp }} EXP</span>
<span> Tenggat: {{ \Carbon\Carbon::parse($challenge->tenggat_waktu)->format('d M Y, H:i') }}</span>
<span>
<img src="{{ asset('images/icon/siswac/buku1.png') }}" alt="Jumlah soal">
{{ $challenge->soal->count() }} Soal
</span>
<span>
<img src="{{ asset('images/icon/siswac/star.png') }}" alt="EXP">
{{ $challenge->exp }} EXP
</span>
<span>
<img src="{{ asset('images/icon/siswac/alarm.png') }}" alt="Tenggat waktu">
Tenggat: {{ \Carbon\Carbon::parse($challenge->tenggat_waktu)->format('d M Y, H:i') }}
</span>
</div>
</div>
@ -291,7 +303,8 @@
{{-- Warning --}}
<div class="warning-box" id="warningBox">
⚠️ Masih ada <span id="warningCount"></span> soal yang belum dijawab. Yakin ingin submit?
<img src="{{ asset('images/icon/siswac/alert.png') }}" alt="Peringatan">
Masih ada <span id="warningCount"></span> soal yang belum dijawab. Yakin ingin submit?
</div>
{{-- Navigasi --}}
@ -304,7 +317,8 @@
<button type="submit" class="btn-nav btn-submit" id="btnSubmit"
onclick="return konfirmasiSubmit()">
🎯 Selesai & Submit
<img src="{{ asset('images/icon/siswac/target.png') }}" alt="Submit">
Selesai &amp; Submit
</button>
</div>
</form>
@ -319,11 +333,9 @@
let jawaban = {}; // { index: 'A'/'B'/'C'/'D' }
function goToSoal(index) {
// Sembunyikan soal sekarang
document.getElementById(`soal-${currentSoal}`).classList.remove('active');
document.getElementById(`dot-${currentSoal}`).classList.remove('active');
// Tampilkan soal target
currentSoal = index;
document.getElementById(`soal-${currentSoal}`).classList.add('active');
@ -345,16 +357,13 @@ function prevSoal() {
function pilihJawaban(soalIndex, opsi, idSoal) {
jawaban[soalIndex] = opsi;
// Hapus selected dari semua opsi soal ini
['A','B','C','D'].forEach(o => {
document.getElementById(`label-${soalIndex}-${o}`)?.classList.remove('selected');
});
// Tandai yang dipilih
document.getElementById(`label-${soalIndex}-${opsi}`)?.classList.add('selected');
document.getElementById(`radio-${soalIndex}-${opsi}`).checked = true;
// Update dot
const dot = document.getElementById(`dot-${soalIndex}`);
dot.classList.add('answered');
@ -370,9 +379,9 @@ function updateNav() {
if (currentSoal === totalSoal - 1) {
btnNext.style.display = 'none';
btnSubmit.style.display = 'block';
btnSubmit.style.display = 'inline-flex';
} else {
btnNext.style.display = 'block';
btnNext.style.display = 'inline-flex';
btnSubmit.style.display = 'none';
}
}
@ -392,7 +401,7 @@ function konfirmasiSubmit() {
if (belum > 0) {
document.getElementById('warningCount').textContent = belum + ' soal';
warningBox.style.display = 'block';
warningBox.style.display = 'flex';
return confirm(`Masih ada ${belum} soal yang belum dijawab. Yakin ingin submit?`);
}

View File

@ -40,6 +40,36 @@
.card-link:hover { text-decoration: underline; }
/* ICONS */
.icon-sm {
width: 16px;
height: 16px;
object-fit: contain;
vertical-align: middle;
}
.icon-md {
width: 24px;
height: 24px;
object-fit: contain;
vertical-align: middle;
}
.icon-rank {
width: 28px;
height: 28px;
object-fit: contain;
vertical-align: middle;
}
.icon-mascot {
width: 300px;
max-width: 100%;
height: auto;
object-fit: contain;
margin-top: 28px;
}
/* TUGAS */
.tugas-date-label {
font-size: 13px;
@ -81,6 +111,10 @@
}
.tugas-empty {
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
text-align: center;
color: #94a3b8;
font-size: 13px;
@ -161,14 +195,13 @@
}
.challenge-bolt {
width: 40px;
height: 40px;
width: 48px;
height: 48px;
background: #fef9c3;
border-radius: 10px;
display: flex;
align-items: center;
justify-content: center;
font-size: 20px;
flex-shrink: 0;
}
@ -239,24 +272,6 @@
border-top-color: #fde68a;
}
.mascot-img {
width: 300px; /* sesuaikan */
height: auto;
object-fit: contain;
}
.mascot-placeholder {
width: 130px;
height: 130px;
margin-top: 28px;
background: linear-gradient(135deg, #dbeafe, #e0f2fe);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 60px;
}
/* LEADERBOARD */
.lb-item {
display: flex;
@ -269,9 +284,10 @@
.lb-item:last-child { border-bottom: none; }
.lb-rank-icon {
font-size: 22px;
width: 32px;
text-align: center;
display: flex;
align-items: center;
justify-content: center;
}
.lb-avatar {
@ -327,7 +343,7 @@
@foreach($items as $item)
<div class="tugas-item">
<span class="tugas-time">{{ $item['jam'] }}</span>
<span style="font-size:18px">📋</span>
<img src="{{ asset('images/icon/siswadb/buku.png') }}" alt="Ikon tugas" class="icon-md">
<div class="tugas-info">
<p class="tugas-nama">{{ $item['nama'] }}</p>
<p class="tugas-mapel">{{ $item['mapel'] }}</p>
@ -335,7 +351,10 @@
</div>
@endforeach
@empty
<div class="tugas-empty">🎉 Tidak ada tugas yang pending!</div>
<div class="tugas-empty">
<img src="{{ asset('images/icon/siswadb/confetti.png') }}" alt="Semua tugas selesai" class="icon-sm">
Tidak ada tugas yang pending!
</div>
@endforelse
</div>
@ -370,7 +389,9 @@
@endphp
<div class="challenge-item">
<div class="challenge-bolt"></div>
<div class="challenge-bolt">
<img src="{{ asset('images/icon/siswadb/starw.png') }}" alt="Ikon challenge mingguan" class="icon-md">
</div>
<div style="flex:1">
<p class="challenge-desc">Ayo kerjakan challenge dan dapatkan EXP tambahan!</p>
<div class="progress-bar-wrap">
@ -393,15 +414,12 @@
ini, yuk lanjutkan untuk mendapatkan badge yang lebih menarik!
@else
Belum ada tugas yang diselesaikan minggu ini.
Ayo mulai kerjakan tugasmu! 💪
Ayo mulai kerjakan tugasmu!
<img src="{{ asset('images/icon/siswadb/muscle.png') }}" alt="Semangat!" class="icon-sm">
@endif
</div>
@if(file_exists(public_path('images/mascot.png')))
<img src="{{ asset('images/mascot.png') }}" class="mascot-img" alt="Mascot">
@else
<img src="{{ asset('images/icon/mascot/main_mascott.png') }}" alt="Mascot" class="mascot-img">
@endif
<img src="{{ asset('images/icon/mascot/mascot-hi.png') }}" alt="Maskot Belajar" class="icon-mascot">
</div>
{{-- ===== LEADERBOARD ===== --}}
@ -414,9 +432,12 @@
@forelse($leaderboard as $lb)
<div class="lb-item">
<span class="lb-rank-icon">
@if($lb['rank'] === 1) 🥇
@elseif($lb['rank'] === 2) 🥈
@else 🥉
@if($lb['rank'] === 1)
<img src="{{ asset('images/icon/siswadb/1.png') }}" alt="Peringkat 1" class="icon-rank">
@elseif($lb['rank'] === 2)
<img src="{{ asset('images/icon/siswadb/2.png') }}" alt="Peringkat 2" class="icon-rank">
@else
<img src="{{ asset('images/icon/siswadb/3.png') }}" alt="Peringkat 3" class="icon-rank">
@endif
</span>
<div class="lb-avatar">{{ strtoupper(substr($lb['nama'], 0, 1)) }}</div>

View File

@ -4,7 +4,7 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="csrf-token" content="{{ csrf_token() }}">
<title>@yield('title', 'Siswa Panel')</title>
<title>@yield('title', 'Dashboard Siswa')</title>
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700&display=swap" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
<style>
@ -12,18 +12,49 @@
body { font-family: 'Poppins', sans-serif; background-color: #f5f9ff; margin: 0; height: 100vh; overflow: hidden; }
.siswa-wrapper { display: flex; height: 100vh; overflow: hidden; }
/* GLOBAL ICONS — berlaku di semua halaman */
.icon-sm { width: 16px; height: 16px; object-fit: contain; vertical-align: middle; }
.icon-md { width: 24px; height: 24px; object-fit: contain; vertical-align: middle; }
.icon-rank { width: 28px; height: 28px; object-fit: contain; vertical-align: middle; }
.icon-mascot { width: 300px; max-width: 100%; height: auto; object-fit: contain; margin-top: 28px; }
.sidebar { width: 260px; flex-shrink: 0; background: #ffffff; border-right: 2px solid #e6f0ff; padding: 30px 20px; display: flex; flex-direction: column; transition: width 0.3s ease, padding 0.3s ease; overflow: hidden; height: 100vh; position: sticky; top: 0; }
.sidebar.collapsed { width: 0; padding: 0; border-right: none; }
.sidebar-logo { text-align: center; margin-bottom: 36px; white-space: nowrap; flex-shrink: 0; }
.sidebar-logo img { width: 90px; }
.sidebar-menu { display: flex; flex-direction: column; white-space: nowrap; }
.sidebar-link { display: flex; align-items: center; gap: 12px; padding: 11px 18px; margin-bottom: 8px; border-radius: 12px; color: #64748b; text-decoration: none; font-weight: 500; transition: all 0.2s ease; }
.sidebar-link { display: flex; align-items: center; gap: 12px; padding: 11px 18px; margin-bottom: 8px; border-radius: 12px; color: #64748b; text-decoration: none; font-weight: 500; font-size: 14px; transition: all 0.2s ease; }
.sidebar-link:hover { background: #e6f0ff; color: #1d4ed8; }
.sidebar-link.active { background: #e6f0ff; color: #1d4ed8; }
.sidebar-icon { width: 20px; height: 20px; flex-shrink: 0; }
.sidebar-logout { margin-top: auto; flex-shrink: 0; }
.sidebar-logout button { width: 100%; border: none; background: transparent; color: #ef4444; font-weight: 600; padding: 10px; text-align: left; border-radius: 12px; cursor: pointer; }
.sidebar-icon { width: 20px; height: 20px; flex-shrink: 0; object-fit: contain; }
/* Logout — disamain dengan layout guru */
.sidebar-logout {
margin-top: auto;
padding-top: 16px;
border-top: 1px solid #f1f5f9;
flex-shrink: 0;
}
.sidebar-logout button {
width: 100%;
border: none;
background: transparent;
color: #ef4444;
font-weight: 600;
font-size: 14px;
padding: 10px 18px;
text-align: left;
border-radius: 12px;
cursor: pointer;
display: flex;
align-items: center;
gap: 12px;
transition: background 0.2s;
font-family: 'Poppins', sans-serif;
white-space: nowrap;
}
.sidebar-logout button:hover { background: #fef2f2; }
.sidebar-logout svg { width: 18px; height: 18px; flex-shrink: 0; }
.sidebar-toggle-btn { position: fixed; top: 50%; transform: translateY(-50%); left: 260px; z-index: 1000; width: 22px; height: 48px; background: #2b8ef3; border: none; border-radius: 0 10px 10px 0; cursor: pointer; display: flex; align-items: center; justify-content: center; transition: left 0.3s ease, background 0.2s; box-shadow: 2px 0 8px rgba(43,142,243,0.3); }
.sidebar-toggle-btn:hover { background: #1a7ae0; }
@ -32,10 +63,11 @@
.sidebar-toggle-btn.collapsed .toggle-arrow { transform: rotate(180deg); }
.main { flex: 1; display: flex; flex-direction: column; min-width: 0; height: 100vh; overflow: hidden; }
.topbar { background: #2b8ef3; margin: 20px 20px 0; padding: 16px 24px; border-radius: 16px; color: white; display: flex; justify-content: space-between; align-items: center; flex-shrink: 0; }
.topbar { background: #2b8ef3; margin: 20px 20px 0; padding: 16px 24px; border-radius: 16px; color: white; display: flex; justify-content: space-between; align-items: center; box-shadow: 0 8px 24px rgba(43,142,243,0.25); flex-shrink: 0; }
.topbar-left { font-weight: 600; font-size: 16px; display: flex; align-items: center; gap: 10px; }
.topbar-right { display: flex; align-items: center; gap: 14px; }
.content { padding: 20px 28px 28px; flex: 1; overflow-y: auto; }
.content { padding: 28px 28px 28px; flex: 1; overflow-y: auto; }
/* NOTIF */
.notif-wrap { position: relative; }
@ -52,7 +84,7 @@
.notif-item { display: flex; align-items: flex-start; gap: 12px; padding: 13px 18px; border-bottom: 1px solid #f8fafc; transition: background 0.15s; cursor: default; }
.notif-item:hover { background: #f8faff; }
.notif-item:last-child { border-bottom: none; }
.notif-icon-wrap { width: 36px; height: 36px; border-radius: 12px; display: flex; align-items: center; justify-content: center; flex-shrink: 0; font-size: 16px; }
.notif-icon-wrap { width: 36px; height: 36px; border-radius: 12px; display: flex; align-items: center; justify-content: center; flex-shrink: 0; }
.notif-icon-materi { background: #e8f4ff; }
.notif-icon-tugas { background: #fff7e6; }
.notif-body { flex: 1; min-width: 0; }
@ -60,7 +92,7 @@
.notif-message { font-size: 12px; color: #475569; margin-bottom: 2px; line-height: 1.4; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.notif-time { font-size: 10px; color: #94a3b8; white-space: nowrap; margin-top: 2px; }
.notif-empty { padding: 32px 18px; text-align: center; }
.notif-empty-icon { font-size: 36px; margin-bottom: 8px; }
.notif-empty-icon { margin-bottom: 8px; display: flex; justify-content: center; }
.notif-empty-text { font-size: 13px; color: #94a3b8; }
.topbar-avatar { width: 36px; height: 36px; border-radius: 50%; object-fit: cover; border: 2px solid rgba(255,255,255,0.5); cursor: pointer; transition: all 0.2s; display: block; }
@ -105,27 +137,41 @@
<body>
<div class="siswa-wrapper">
<aside class="sidebar collapsed" id="mainSidebar">
<div class="sidebar-logo"><img src="{{ asset('images/logo/logosmk.png') }}" alt="Logo SMK"></div>
<div class="sidebar-logo">
<img src="{{ asset('images/logo/logosmk.png') }}" alt="Logo SMK">
</div>
<nav class="sidebar-menu">
<a href="{{ route('siswa.dashboard') }}" class="sidebar-link {{ request()->routeIs('siswa.dashboard') ? 'active' : '' }}">
<img src="{{ asset('images/icon/sidebar/home.png') }}" class="sidebar-icon" alt=""><span>Dashboard</span>
<img src="{{ asset('images/icon/sidebar/home.png') }}" class="sidebar-icon" alt="Dashboard">
<span>Dashboard</span>
</a>
<a href="{{ route('siswa.materi.index') }}" class="sidebar-link {{ request()->routeIs('siswa.materi*') ? 'active' : '' }}">
<img src="{{ asset('images/icon/sidebar/mapel.png') }}" class="sidebar-icon" alt=""><span>Materi</span>
<img src="{{ asset('images/icon/sidebar/mapel.png') }}" class="sidebar-icon" alt="Materi">
<span>Materi</span>
</a>
<a href="{{ route('siswa.tugas.index') }}" class="sidebar-link {{ request()->routeIs('siswa.tugas*') ? 'active' : '' }}">
<img src="{{ asset('images/icon/sidebar/siswa.png') }}" class="sidebar-icon" alt=""><span>Tugas</span>
<img src="{{ asset('images/icon/sidebar/siswa.png') }}" class="sidebar-icon" alt="Tugas">
<span>Tugas</span>
</a>
<a href="{{ route('siswa.challenge.index') }}" class="sidebar-link {{ request()->routeIs('siswa.challenge*') ? 'active' : '' }}">
<img src="{{ asset('images/icon/sidebar/challenge.png') }}" class="sidebar-icon" alt=""><span>Challenge</span>
<img src="{{ asset('images/icon/sidebar/challenge.png') }}" class="sidebar-icon" alt="Challenge">
<span>Challenge</span>
</a>
<a href="{{ route('siswa.leaderboard.index') }}" class="sidebar-link {{ request()->routeIs('siswa.leaderboard*') ? 'active' : '' }}">
<img src="{{ asset('images/icon/sidebar/lb.png') }}" class="sidebar-icon" alt=""><span>Leaderboard</span>
<img src="{{ asset('images/icon/sidebar/lb.png') }}" class="sidebar-icon" alt="Leaderboard">
<span>Leaderboard</span>
</a>
</nav>
<form action="{{ route('siswa.logout') }}" method="POST" class="sidebar-logout">
@csrf
<button type="submit" class="btn btn-danger w-100">Logout</button>
<button type="submit">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"/>
<polyline points="16 17 21 12 16 7"/>
<line x1="21" y1="12" x2="9" y2="12"/>
</svg>
Logout
</button>
</form>
</aside>
@ -135,7 +181,10 @@
<div class="main">
<header class="topbar">
<div class="topbar-left">👋 Hai, {{ Auth::guard('siswa')->user()->nama ?? 'Siswa' }}</div>
<div class="topbar-left">
<img src="{{ asset('images/icon/siswadb/wavinghand.png') }}" alt="Hai" class="icon-sm">
Hai, {{ Auth::guard('siswa')->user()->nama ?? 'Siswa' }}
</div>
<div class="topbar-right">
<div class="notif-wrap" id="notifWrap">
<button class="notif-btn" id="notifBtn" onclick="toggleNotif(event)">
@ -148,14 +197,19 @@
<span class="notif-head-count" id="notifCount">7 hari terakhir</span>
</div>
<div class="notif-list" id="notifList">
<div class="notif-empty"><div class="notif-empty-icon">🔔</div><div class="notif-empty-text">Memuat notifikasi...</div></div>
<div class="notif-empty">
<div class="notif-empty-icon">
<img src="{{ asset('images/icon/siswadb/notif.png') }}" alt="Tidak ada notifikasi" class="icon-md">
</div>
<div class="notif-empty-text">Memuat notifikasi...</div>
</div>
</div>
</div>
</div>
@php $siswa = Auth::guard('siswa')->user(); @endphp
@if($siswa->foto_profil)
<img src="{{ Storage::url($siswa->foto_profil) }}" class="topbar-avatar" id="topbar-foto" onclick="openProfileModal()" alt="Profil">
<img src="{{ Storage::url($siswa->foto_profil) }}" class="topbar-avatar" id="topbar-foto" onclick="openProfileModal()" alt="Foto profil {{ $siswa->nama }}">
@else
<div class="topbar-avatar-icon" id="topbar-foto-icon" onclick="openProfileModal()">
<svg viewBox="0 0 24 24" fill="none" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="8" r="4"/><path d="M4 20c0-4 3.6-7 8-7s8 3 8 7"/></svg>
@ -170,7 +224,9 @@
{{-- PROFILE MODAL --}}
<div class="profile-modal-overlay" id="profileModalOverlay" onclick="closeOnOverlay(event)">
<div class="profile-modal">
<button class="modal-close" onclick="closeProfileModal()"><svg viewBox="0 0 24 24" fill="none" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg></button>
<button class="modal-close" onclick="closeProfileModal()">
<svg viewBox="0 0 24 24" fill="none" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
</button>
<div class="modal-title">Edit Profil</div>
<div class="modal-subtitle">Perbarui foto profil dan password</div>
<div id="modal-toast" class="modal-toast"></div>
@ -178,9 +234,12 @@
@csrf
<div class="foto-upload-area">
<div class="foto-circle">
@if($siswa->foto_profil) <img src="{{ Storage::url($siswa->foto_profil) }}" id="modal-foto-preview" alt="">
@else <svg id="modal-foto-icon" viewBox="0 0 24 24" fill="none" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="8" r="4"/><path d="M4 20c0-4 3.6-7 8-7s8 3 8 7"/></svg>
<img src="" id="modal-foto-preview" alt="" style="display:none"> @endif
@if($siswa->foto_profil)
<img src="{{ Storage::url($siswa->foto_profil) }}" id="modal-foto-preview" alt="Foto profil saat ini">
@else
<svg id="modal-foto-icon" viewBox="0 0 24 24" fill="none" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="8" r="4"/><path d="M4 20c0-4 3.6-7 8-7s8 3 8 7"/></svg>
<img src="" id="modal-foto-preview" alt="Preview foto profil" style="display:none">
@endif
</div>
<div class="foto-info">
<p>JPG, PNG, WEBP · Maks. 2MB</p>
@ -188,24 +247,44 @@
<input type="file" name="foto_profil" id="modal-foto-input" accept="image/*" style="display:none">
</div>
</div>
<div class="modal-field"><label class="modal-label">NISN <span>(tidak dapat diubah)</span></label><input type="text" class="modal-input" value="{{ $siswa->nisn }}" disabled></div>
<div class="modal-field"><label class="modal-label">Nama Lengkap <span>(tidak dapat diubah)</span></label><input type="text" class="modal-input" value="{{ $siswa->nama }}" disabled></div>
<div class="modal-field">
<label class="modal-label">NISN <span>(tidak dapat diubah)</span></label>
<input type="text" class="modal-input" value="{{ $siswa->nisn }}" disabled>
</div>
<div class="modal-field">
<label class="modal-label">Nama Lengkap <span>(tidak dapat diubah)</span></label>
<input type="text" class="modal-input" value="{{ $siswa->nama }}" disabled>
</div>
<hr class="modal-divider">
<div class="modal-field"><label class="modal-label">Password Baru <span>(kosongkan jika tidak ingin mengubah)</span></label><input type="password" name="password" class="modal-input" placeholder="Password baru" autocomplete="new-password"></div>
<div class="modal-field"><label class="modal-label">Konfirmasi Password</label><input type="password" name="password_confirmation" class="modal-input" placeholder="Ulangi password baru" autocomplete="new-password"></div>
<div class="modal-field">
<label class="modal-label">Password Baru <span>(kosongkan jika tidak ingin mengubah)</span></label>
<input type="password" name="password" class="modal-input" placeholder="Password baru" autocomplete="new-password">
</div>
<div class="modal-field">
<label class="modal-label">Konfirmasi Password</label>
<input type="password" name="password_confirmation" class="modal-input" placeholder="Ulangi password baru" autocomplete="new-password">
</div>
<button type="submit" class="btn-save-modal" id="btn-save">Simpan Perubahan</button>
</form>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
@php
$iconMateri = asset('images/icon/siswadb/buku.png');
$iconTugas = asset('images/icon/siswadb/buku.png');
$iconNotif = asset('images/icon/siswadb/notif.png');
@endphp
<script>
const sidebar=document.getElementById('mainSidebar'),toggleBtn=document.getElementById('sidebarToggleBtn'),SIDEBAR_W=260;
function updateTogglePosition(c){toggleBtn.style.left=c?'0px':SIDEBAR_W+'px';c?toggleBtn.classList.add('collapsed'):toggleBtn.classList.remove('collapsed');}
toggleBtn.addEventListener('click',function(){const c=sidebar.classList.contains('collapsed');sidebar.classList.toggle('collapsed');updateTogglePosition(!c);});
const NOTIF_URL='{{ route("siswa.notifikasi") }}';
const notifIcons={materi:'📖',tugas:'📝'};
const notifIcons = {
materi: '<img src="{{ $iconMateri }}" alt="Materi baru" class="icon-md">',
tugas: '<img src="{{ $iconTugas }}" alt="Tugas baru" class="icon-md">'
};
async function fetchNotif(){
try{
const res=await fetch(NOTIF_URL,{headers:{'X-CSRF-TOKEN':document.querySelector('meta[name="csrf-token"]').content}});
@ -215,8 +294,19 @@ function updateTogglePosition(c){toggleBtn.style.left=c?'0px':SIDEBAR_W+'px';c?t
if(data.count>0)badge.textContent=data.count>99?'99+':data.count;
count.textContent=`${data.count} notifikasi`;
list.innerHTML=data.notifications.length===0
?`<div class="notif-empty"><div class="notif-empty-icon">🔔</div><div class="notif-empty-text">Tidak ada notifikasi baru</div></div>`
:data.notifications.map(n=>`<div class="notif-item"><div class="notif-icon-wrap notif-icon-${n.type}">${notifIcons[n.type]??'🔔'}</div><div class="notif-body"><div class="notif-title">${n.title}</div><div class="notif-message">${n.message}</div><div class="notif-time">${n.time}</div></div></div>`).join('');
?`<div class="notif-empty">
<div class="notif-empty-icon"><img src="{{ $iconNotif }}" alt="Tidak ada notifikasi" class="icon-md"></div>
<div class="notif-empty-text">Tidak ada notifikasi baru</div>
</div>`
:data.notifications.map(n=>`
<div class="notif-item">
<div class="notif-icon-wrap notif-icon-${n.type}">${notifIcons[n.type]??'🔔'}</div>
<div class="notif-body">
<div class="notif-title">${n.title}</div>
<div class="notif-message">${n.message}</div>
<div class="notif-time">${n.time}</div>
</div>
</div>`).join('');
}catch(e){}
}
function toggleNotif(e){e.stopPropagation();document.getElementById('notifDropdown').classList.toggle('show');}
@ -227,7 +317,13 @@ function openProfileModal(){document.getElementById('profileModalOverlay').class
function closeProfileModal(){document.getElementById('profileModalOverlay').classList.remove('show');document.body.style.overflow='';const t=document.getElementById('modal-toast');t.className='modal-toast';t.textContent='';}
function closeOnOverlay(e){if(e.target===document.getElementById('profileModalOverlay'))closeProfileModal();}
document.addEventListener('keydown',e=>{if(e.key==='Escape')closeProfileModal();});
document.getElementById('modal-foto-input').addEventListener('change',function(){const f=this.files[0];if(!f)return;const u=URL.createObjectURL(f);const p=document.getElementById('modal-foto-preview');const i=document.getElementById('modal-foto-icon');p.src=u;p.style.display='block';if(i)i.style.display='none';});
document.getElementById('modal-foto-input').addEventListener('change',function(){
const f=this.files[0];if(!f)return;
const u=URL.createObjectURL(f);
const p=document.getElementById('modal-foto-preview');
const i=document.getElementById('modal-foto-icon');
p.src=u;p.style.display='block';if(i)i.style.display='none';
});
document.getElementById('profile-form').addEventListener('submit',async function(e){
e.preventDefault();const btn=document.getElementById('btn-save');const toast=document.getElementById('modal-toast');
btn.disabled=true;btn.textContent='Menyimpan...';const fd=new FormData(this);
@ -235,10 +331,31 @@ function closeOnOverlay(e){if(e.target===document.getElementById('profileModalOv
const res=await fetch('{{ route("siswa.profile.update") }}',{method:'POST',headers:{'X-CSRF-TOKEN':document.querySelector('meta[name="csrf-token"]').content},body:fd});
const data=await res.json();
if(data.success){
if(data.foto_url){const tf=document.getElementById('topbar-foto');const ti=document.getElementById('topbar-foto-icon');if(tf){tf.src=data.foto_url+'?t='+Date.now();}else if(ti){const img=document.createElement('img');img.src=data.foto_url+'?t='+Date.now();img.className='topbar-avatar';img.id='topbar-foto';img.onclick=openProfileModal;img.alt='Profil';ti.replaceWith(img);}const mp=document.getElementById('modal-foto-preview');const mi=document.getElementById('modal-foto-icon');if(mp){mp.src=data.foto_url;mp.style.display='block';}if(mi)mi.style.display='none';}
if(data.foto_url){
const tf=document.getElementById('topbar-foto');
const ti=document.getElementById('topbar-foto-icon');
if(tf){tf.src=data.foto_url+'?t='+Date.now();}
else if(ti){
const img=document.createElement('img');
img.src=data.foto_url+'?t='+Date.now();
img.className='topbar-avatar';
img.id='topbar-foto';
img.onclick=openProfileModal;
img.alt='Foto profil {{ $siswa->nama }}';
ti.replaceWith(img);
}
const mp=document.getElementById('modal-foto-preview');
const mi=document.getElementById('modal-foto-icon');
if(mp){mp.src=data.foto_url;mp.style.display='block';}
if(mi)mi.style.display='none';
}
toast.className='modal-toast success';toast.textContent='✓ '+data.message;
this.querySelector('[name="password"]').value='';this.querySelector('[name="password_confirmation"]').value='';
}else{const err=data.errors?Object.values(data.errors).flat().join(' · '):'Terjadi kesalahan.';toast.className='modal-toast error';toast.textContent=err;}
this.querySelector('[name="password"]').value='';
this.querySelector('[name="password_confirmation"]').value='';
}else{
const err=data.errors?Object.values(data.errors).flat().join(' · '):'Terjadi kesalahan.';
toast.className='modal-toast error';toast.textContent=err;
}
}catch(err){toast.className='modal-toast error';toast.textContent='Gagal terhubung ke server.';}
finally{btn.disabled=false;btn.textContent='Simpan Perubahan';}
});

View File

@ -4,61 +4,120 @@
@push('styles')
<style>
.page-title { font-size: 28px; font-weight: 800; margin-top: -20px; margin-bottom: 6px; }
/* ── Ikon utilitas ─────────────────────────────────────── */
.icon-medal { width: 28px; height: 28px; object-fit: contain; vertical-align: middle; }
.icon-star { width: 16px; height: 16px; object-fit: contain; vertical-align: middle; margin-right: 2px; }
.icon-crown { width: 24px; height: 24px; object-fit: contain; display: block; }
.icon-rank { width: 24px; height: 24px; object-fit: contain; vertical-align: middle; }
.icon-book { width: 20px; height: 20px; object-fit: contain; vertical-align: middle; margin-right: 6px; }
.icon-target { width: 36px; height: 36px; object-fit: contain; vertical-align: middle; }
.icon-empty { width: 72px; height: 72px; object-fit: contain; margin-bottom: 12px; }
.icon-loader { width: 40px; height: 40px; object-fit: contain; margin-bottom: 8px; }
/* ── Page header ───────────────────────────────────────── */
.page-title {
font-size: 22px;
font-weight: 700;
color: #1e293b;
margin-bottom: 24px;
display: flex;
align-items: center;
gap: 10px;
}
.page-subtitle { font-size: 14px; color: #64748b; margin-bottom: 24px; }
/* ── Podium ────────────────────────────────────────────── */
.podium-wrap { display: flex; align-items: flex-end; justify-content: center; gap: 12px; margin-bottom: 32px; }
.podium-item { display: flex; flex-direction: column; align-items: center; gap: 8px; }
.podium-avatar { border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 22px; font-weight: 800; color: white; position: relative; overflow: hidden; flex-shrink: 0; width: 56px; height: 56px; }
.podium-avatar img { width: 100%; height: 100%; object-fit: cover; border-radius: 50%; }
.podium-crown { position: absolute; top: -16px; font-size: 18px; z-index: 1; pointer-events: none; }
.podium-avatar {
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 22px;
font-weight: 800;
color: white;
position: relative;
overflow: visible; /* crown perlu keluar area */
flex-shrink: 0;
width: 56px;
height: 56px;
}
.podium-avatar-inner {
width: 100%;
height: 100%;
border-radius: 50%;
overflow: hidden;
display: flex;
align-items: center;
justify-content: center;
}
.podium-avatar-inner img {
width: 100%;
height: 100%;
object-fit: cover;
border-radius: 50%;
}
.podium-crown {
position: absolute;
top: -22px;
left: 50%;
transform: translateX(-50%);
z-index: 2;
pointer-events: none;
line-height: 1;
}
.rank-1 .podium-avatar { background: linear-gradient(135deg,#f59e0b,#d97706); width:68px; height:68px; font-size:26px; }
.rank-1 .podium-avatar { background: linear-gradient(135deg,#f59e0b,#d97706); width: 68px; height: 68px; font-size: 26px; }
.rank-2 .podium-avatar { background: linear-gradient(135deg,#94a3b8,#64748b); }
.rank-3 .podium-avatar { background: linear-gradient(135deg,#f97316,#ea580c); }
.podium-name { font-size:13px; font-weight:700; color:#1e293b; text-align:center; max-width:90px; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
.podium-exp { font-size:12px; color:#64748b; }
.podium-name { font-size: 13px; font-weight: 700; color: #1e293b; text-align: center; max-width: 90px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.podium-exp { font-size: 12px; color: #64748b; display: flex; align-items: center; gap: 3px; }
.podium-bar { border-radius:12px 12px 0 0; width:80px; display:flex; align-items:center; justify-content:center; font-size:20px; font-weight:800; color:white; }
.rank-1 .podium-bar { height:80px; background:linear-gradient(135deg,#f59e0b,#d97706); }
.rank-2 .podium-bar { height:60px; background:linear-gradient(135deg,#94a3b8,#64748b); }
.rank-3 .podium-bar { height:44px; background:linear-gradient(135deg,#f97316,#ea580c); }
.podium-bar { border-radius: 12px 12px 0 0; width: 80px; display: flex; align-items: center; justify-content: center; font-size: 20px; font-weight: 800; color: white; }
.rank-1 .podium-bar { height: 80px; background: linear-gradient(135deg,#f59e0b,#d97706); }
.rank-2 .podium-bar { height: 60px; background: linear-gradient(135deg,#94a3b8,#64748b); }
.rank-3 .podium-bar { height: 44px; background: linear-gradient(135deg,#f97316,#ea580c); }
.my-rank-banner { background:linear-gradient(135deg,#667eea,#764ba2); border-radius:16px; padding:16px 20px; color:white; display:flex; align-items:center; gap:16px; margin-bottom:20px; }
.my-rank-avatar { width:48px; height:48px; border-radius:50%; object-fit:cover; border:2px solid rgba(255,255,255,0.5); flex-shrink:0; }
.my-rank-avatar-placeholder { width:48px; height:48px; border-radius:50%; background:rgba(255,255,255,0.2); display:flex; align-items:center; justify-content:center; font-size:20px; font-weight:800; flex-shrink:0; }
.my-rank-num { font-size:36px; font-weight:800; line-height:1; }
.my-rank-info { flex:1; }
.my-rank-label { font-size:12px; opacity:0.8; margin-bottom:2px; }
.my-rank-nama { font-size:16px; font-weight:700; }
.my-rank-exp { font-size:13px; opacity:0.9; }
/* ── My rank banner ────────────────────────────────────── */
.my-rank-banner { background: linear-gradient(135deg,#667eea,#764ba2); border-radius: 16px; padding: 16px 20px; color: white; display: flex; align-items: center; gap: 16px; margin-bottom: 20px; }
.my-rank-avatar { width: 48px; height: 48px; border-radius: 50%; object-fit: cover; border: 2px solid rgba(255,255,255,0.5); flex-shrink: 0; }
.my-rank-avatar-placeholder { width: 48px; height: 48px; border-radius: 50%; background: rgba(255,255,255,0.2); display: flex; align-items: center; justify-content: center; font-size: 20px; font-weight: 800; flex-shrink: 0; }
.my-rank-num { font-size: 36px; font-weight: 800; line-height: 1; }
.my-rank-info { flex: 1; }
.my-rank-label { font-size: 12px; opacity: 0.8; margin-bottom: 2px; }
.my-rank-nama { font-size: 16px; font-weight: 700; }
.my-rank-exp { font-size: 13px; opacity: 0.9; display: flex; align-items: center; gap: 4px; }
.custom-card { background:white; border-radius:20px; border:2px solid #e5e5e5; padding:22px; }
.section-title { font-size:15px; font-weight:700; color:#1e293b; margin-bottom:16px; display:flex; align-items:center; justify-content:space-between; }
/* ── Card & list ───────────────────────────────────────── */
.custom-card { background: white; border-radius: 20px; border: 2px solid #e5e5e5; padding: 22px; }
.section-title { font-size: 15px; font-weight: 700; color: #1e293b; margin-bottom: 16px; display: flex; align-items: center; justify-content: space-between; }
.live-dot { width:8px; height:8px; border-radius:50%; background:#22c55e; box-shadow:0 0 6px #22c55e; animation: pulse 2s ease-in-out infinite; display:inline-block; margin-right:6px; }
.live-dot { width: 8px; height: 8px; border-radius: 50%; background: #22c55e; box-shadow: 0 0 6px #22c55e; animation: pulse 2s ease-in-out infinite; display: inline-block; margin-right: 6px; }
@keyframes pulse { 0%,100%{opacity:1;transform:scale(1)} 50%{opacity:0.5;transform:scale(0.8)} }
.lb-row { display:flex; align-items:center; gap:12px; padding:10px 14px; border-radius:12px; margin-bottom:8px; transition:background 0.15s; }
.lb-row:hover { background:#f8fafc; }
.lb-row.highlight { background:#f0eeff; border:2px solid #c4b5fd; }
.lb-row { display: flex; align-items: center; gap: 12px; padding: 10px 14px; border-radius: 12px; margin-bottom: 8px; transition: background 0.15s; }
.lb-row:hover { background: #f8fafc; }
.lb-row.highlight { background: #f0eeff; border: 2px solid #c4b5fd; }
.lb-rank { width:32px; height:32px; border-radius:50%; background:#e2e8f0; display:flex; align-items:center; justify-content:center; font-size:13px; font-weight:700; color:#64748b; flex-shrink:0; }
.lb-rank.gold { background:#fef3c7; color:#d97706; }
.lb-rank.silver { background:#f1f5f9; color:#64748b; }
.lb-rank.bronze { background:#ffedd5; color:#ea580c; }
.lb-rank { width: 32px; height: 32px; border-radius: 50%; background: #e2e8f0; display: flex; align-items: center; justify-content: center; font-size: 13px; font-weight: 700; color: #64748b; flex-shrink: 0; }
.lb-rank.gold { background: #fef3c7; color: #d97706; }
.lb-rank.silver { background: #f1f5f9; color: #64748b; }
.lb-rank.bronze { background: #ffedd5; color: #ea580c; }
/* rank badge gambar tidak perlu background bulat */
.lb-rank.has-img { background: transparent; }
.lb-avatar { width:36px; height:36px; border-radius:50%; object-fit:cover; flex-shrink:0; border:2px solid #e2e8f0; }
.lb-avatar-placeholder { width:36px; height:36px; border-radius:50%; background:#e6f0ff; display:flex; align-items:center; justify-content:center; font-size:14px; font-weight:700; color:#2b8ef3; flex-shrink:0; }
.lb-avatar { width: 36px; height: 36px; border-radius: 50%; object-fit: cover; flex-shrink: 0; border: 2px solid #e2e8f0; }
.lb-avatar-placeholder { width: 36px; height: 36px; border-radius: 50%; background: #e6f0ff; display: flex; align-items: center; justify-content: center; font-size: 14px; font-weight: 700; color: #2b8ef3; flex-shrink: 0; }
.lb-nama { flex:1; font-size:14px; font-weight:600; color:#1e293b; }
.lb-nisn { font-size:12px; color:#94a3b8; }
.lb-exp { font-size:14px; font-weight:700; color:#667eea; }
.lb-nama { flex: 1; font-size: 14px; font-weight: 600; color: #1e293b; }
.lb-nisn { font-size: 12px; color: #94a3b8; }
.lb-exp { font-size: 14px; font-weight: 700; color: #667eea; display: flex; align-items: center; gap: 3px; }
.semester-badge { display:inline-block; background:#f0eeff; color:#667eea; font-size:12px; font-weight:700; padding:4px 12px; border-radius:99px; margin-bottom:20px; }
.empty-state { text-align:center; padding:40px 20px; color:#94a3b8; }
.semester-badge { display: inline-block; background: #f0eeff; color: #667eea; font-size: 12px; font-weight: 700; padding: 4px 12px; border-radius: 99px; margin-bottom: 20px; }
.empty-state { text-align: center; padding: 40px 20px; color: #94a3b8; }
</style>
@endpush
@ -66,7 +125,10 @@
@php $siswaLogin = Auth::guard('siswa')->user(); @endphp
<h3 class="page-title">🏅 Leaderboard</h3>
<h3 class="page-title">
<img src="{{ asset('images/icon/siswal/medal-pita.png') }}" alt="Ikon medal leaderboard" class="icon-medal">
Leaderboard
</h3>
<p class="page-subtitle">Peringkat siswa berdasarkan total EXP yang dikumpulkan.</p>
<span class="semester-badge">Semester {{ $semester }} · {{ $tahunAjaran }}</span>
@ -74,7 +136,9 @@
{{-- Container di-render real-time via JavaScript --}}
<div id="leaderboard-container">
<div style="text-align:center;padding:40px;color:#94a3b8;">
<div style="font-size:32px;margin-bottom:8px"></div>
<div>
<img src="{{ asset('images/icon/siswal/jam-pasir.png') }}" alt="Memuat data" class="icon-loader">
</div>
<p style="font-size:13px">Memuat data...</p>
</div>
</div>
@ -86,51 +150,80 @@
const currentSiswaId = {{ $siswaLogin->id_siswa }};
const jsonUrl = '{{ route("siswa.leaderboard.json") }}';
/* URL aset — dioper dari Blade agar JS tidak perlu tahu base URL */
const ASSETS = {
star: '{{ asset('images/icon/siswal/star.png') }}',
crown: '{{ asset('images/icon/siswal/crown.png') }}',
rank1: '{{ asset('images/icon/siswal/1.png') }}',
rank2: '{{ asset('images/icon/siswal/2.png') }}',
rank3: '{{ asset('images/icon/siswal/3.png') }}',
book: '{{ asset('images/icon/siswal/buku1.png') }}',
target: '{{ asset('images/icon/siswal/target.png') }}',
empty: '{{ asset('images/icon/siswal/lb.png') }}',
};
/* ── Helper: avatar untuk banner "posisimu" ── */
function avatarHtml(item, size = 'normal') {
const isLarge = size === 'large';
const w = isLarge ? 48 : 36;
const fontSize = isLarge ? 20 : 14;
const isLarge = size === 'large';
if (item.foto_url) {
const cls = isLarge ? 'my-rank-avatar' : 'lb-avatar';
return `<img src="${item.foto_url}?t=${Date.now()}" class="${cls}" alt="">`;
return `<img src="${item.foto_url}?t=${Date.now()}" class="${cls}" alt="Foto profil ${escHtml(item.nama)}">`;
}
const initial = item.nama ? item.nama.charAt(0).toUpperCase() : '?';
if (isLarge) {
return `<div class="my-rank-avatar-placeholder">${initial}</div>`;
return `<div class="my-rank-avatar-placeholder" aria-label="Inisial ${escHtml(item.nama)}">${initial}</div>`;
}
return `<div class="lb-avatar-placeholder">${initial}</div>`;
return `<div class="lb-avatar-placeholder" aria-label="Inisial ${escHtml(item.nama)}">${initial}</div>`;
}
function podiumAvatarHtml(item, rankClass) {
/* ── Helper: avatar di dalam podium ── */
function podiumAvatarHtml(item) {
if (item.foto_url) {
return `<img src="${item.foto_url}?t=${Date.now()}" alt="" style="width:100%;height:100%;object-fit:cover;border-radius:50%;">`;
return `<img src="${item.foto_url}?t=${Date.now()}" alt="Foto profil ${escHtml(item.nama)}">`;
}
return item.nama ? item.nama.charAt(0).toUpperCase() : '?';
const initial = item.nama ? item.nama.charAt(0).toUpperCase() : '?';
return `<span aria-hidden="true">${initial}</span>`;
}
function rankBadge(ranking) {
if (ranking === 1) return '🥇';
if (ranking === 2) return '🥈';
if (ranking === 3) return '🥉';
/* ── Helper: badge angka / gambar di kolom rank ── */
function rankBadgeHtml(ranking) {
if (ranking === 1) return `<img src="${ASSETS.rank1}" alt="Peringkat 1" class="icon-rank">`;
if (ranking === 2) return `<img src="${ASSETS.rank2}" alt="Peringkat 2" class="icon-rank">`;
if (ranking === 3) return `<img src="${ASSETS.rank3}" alt="Peringkat 3" class="icon-rank">`;
return ranking;
}
function rankClass(ranking) {
if (ranking === 1) return 'gold';
if (ranking === 2) return 'silver';
if (ranking === 3) return 'bronze';
function rankCssClass(ranking) {
if (ranking === 1) return 'gold has-img';
if (ranking === 2) return 'silver has-img';
if (ranking === 3) return 'bronze has-img';
return '';
}
/* ── Escape HTML sederhana ── */
function escHtml(str) {
return String(str ?? '')
.replace(/&/g,'&amp;').replace(/</g,'&lt;')
.replace(/>/g,'&gt;').replace(/"/g,'&quot;');
}
/* ── Render bintang + angka EXP ── */
function expHtml(exp) {
return `<img src="${ASSETS.star}" alt="EXP" class="icon-star">${Number(exp).toLocaleString('id')}`;
}
/* ══════════════════════════════════════════════════════════
RENDER UTAMA
══════════════════════════════════════════════════════════ */
function renderLeaderboard(data) {
const { leaderboard, myRank } = data;
if (!leaderboard || leaderboard.length === 0) {
document.getElementById('leaderboard-container').innerHTML = `
<div class="empty-state">
<div style="font-size:52px;margin-bottom:12px">📊</div>
<img src="${ASSETS.empty}" alt="Leaderboard kosong" class="icon-empty">
<p style="font-size:15px;font-weight:600;color:#475569">Belum ada data leaderboard.</p>
<p style="font-size:13px">Kerjakan challenge untuk masuk leaderboard!</p>
</div>`;
@ -141,42 +234,48 @@ function renderLeaderboard(data) {
const second = leaderboard.find(x => x.ranking === 2);
const third = leaderboard.find(x => x.ranking === 3);
// Podium
/* ── Podium ── */
let podiumHtml = '';
if (first) {
podiumHtml = `<div class="podium-wrap">`;
if (second) podiumHtml += `
<div class="podium-item rank-2">
<div class="podium-avatar">${podiumAvatarHtml(second)}</div>
<div class="podium-name">${second.nama}</div>
<div class="podium-exp"> ${Number(second.exp).toLocaleString('id')}</div>
<div class="podium-avatar">
<div class="podium-avatar-inner">${podiumAvatarHtml(second)}</div>
</div>
<div class="podium-name">${escHtml(second.nama)}</div>
<div class="podium-exp">${expHtml(second.exp)}</div>
<div class="podium-bar">2</div>
</div>`;
podiumHtml += `
<div class="podium-item rank-1">
<div class="podium-avatar">
<span class="podium-crown">👑</span>
${podiumAvatarHtml(first)}
<span class="podium-crown">
<img src="${ASSETS.crown}" alt="Mahkota juara 1" class="icon-crown">
</span>
<div class="podium-avatar-inner">${podiumAvatarHtml(first)}</div>
</div>
<div class="podium-name">${first.nama}</div>
<div class="podium-exp"> ${Number(first.exp).toLocaleString('id')}</div>
<div class="podium-name">${escHtml(first.nama)}</div>
<div class="podium-exp">${expHtml(first.exp)}</div>
<div class="podium-bar">1</div>
</div>`;
if (third) podiumHtml += `
<div class="podium-item rank-3">
<div class="podium-avatar">${podiumAvatarHtml(third)}</div>
<div class="podium-name">${third.nama}</div>
<div class="podium-exp"> ${Number(third.exp).toLocaleString('id')}</div>
<div class="podium-avatar">
<div class="podium-avatar-inner">${podiumAvatarHtml(third)}</div>
</div>
<div class="podium-name">${escHtml(third.nama)}</div>
<div class="podium-exp">${expHtml(third.exp)}</div>
<div class="podium-bar">3</div>
</div>`;
podiumHtml += `</div>`;
}
// My rank banner
/* ── Banner "Posisimu" ── */
let bannerHtml = '';
if (myRank) {
bannerHtml = `
@ -185,28 +284,28 @@ function renderLeaderboard(data) {
<div class="my-rank-num">#${myRank.ranking}</div>
<div class="my-rank-info">
<div class="my-rank-label">Posisimu saat ini</div>
<div class="my-rank-nama">${myRank.nama}</div>
<div class="my-rank-exp"> ${Number(myRank.exp).toLocaleString('id')} EXP</div>
<div class="my-rank-nama">${escHtml(myRank.nama)}</div>
<div class="my-rank-exp">${expHtml(myRank.exp)} EXP</div>
</div>
<div style="font-size:32px">🎯</div>
<img src="${ASSETS.target}" alt="Target peringkat" class="icon-target">
</div>`;
}
// Table rows
let rowsHtml = leaderboard.map(item => {
/* ── Baris tabel ── */
const rowsHtml = leaderboard.map(item => {
const isMe = item.id_siswa === currentSiswaId;
return `
<div class="lb-row ${isMe ? 'highlight' : ''}">
<div class="lb-rank ${rankClass(item.ranking)}">${rankBadge(item.ranking)}</div>
<div class="lb-rank ${rankCssClass(item.ranking)}">${rankBadgeHtml(item.ranking)}</div>
${avatarHtml(item)}
<div style="flex:1">
<div class="lb-nama">
${item.nama}
${escHtml(item.nama)}
${isMe ? '<span style="background:#c4b5fd;color:#4c1d95;font-size:10px;padding:2px 7px;border-radius:99px;font-weight:700;margin-left:4px">Kamu</span>' : ''}
</div>
<div class="lb-nisn">${item.nisn}</div>
<div class="lb-nisn">${escHtml(item.nisn)}</div>
</div>
<div class="lb-exp"> ${Number(item.exp).toLocaleString('id')}</div>
<div class="lb-exp">${expHtml(item.exp)}</div>
</div>`;
}).join('');
@ -215,28 +314,32 @@ function renderLeaderboard(data) {
${bannerHtml}
<div class="custom-card">
<p class="section-title">
<span>📋 Semua Peringkat</span>
<span><span class="live-dot"></span><span style="font-size:11px;color:#22c55e;font-weight:600">Live</span></span>
<span>
<img src="${ASSETS.book}" alt="Ikon daftar peringkat" class="icon-book">
Semua Peringkat
</span>
<span>
<span class="live-dot"></span>
<span style="font-size:11px;color:#22c55e;font-weight:600">Live</span>
</span>
</p>
${rowsHtml}
</div>`;
}
// Polling setiap 10 detik
/* ══════════════════════════════════════════════════════════
POLLING
══════════════════════════════════════════════════════════ */
async function pollLeaderboard() {
try {
const res = await fetch(jsonUrl);
const data = await res.json();
renderLeaderboard(data);
} catch (e) {
// Diam-diam gagal, coba lagi nanti
}
}
// Mulai polling
setInterval(pollLeaderboard, 10000);
// Load langsung saat halaman buka
pollLeaderboard();
</script>
@endpush

View File

@ -9,6 +9,9 @@
font-weight: 700;
color: #1e293b;
margin-bottom: 24px;
display: flex;
align-items: center;
gap: 10px;
}
.mapel-grid {
@ -45,7 +48,6 @@
display: flex;
align-items: center;
justify-content: center;
font-size: 24px;
}
.mapel-nama {
@ -81,32 +83,41 @@
}
.empty-state .empty-icon {
font-size: 48px;
margin-bottom: 12px;
display: flex;
justify-content: center;
}
</style>
@endpush
@section('content')
<h1 class="page-title">📚 Mata Pelajaran</h1>
<h1 class="page-title">
<img src="{{ asset('images/icon/siswam/stacked.png') }}" alt="Ikon mata pelajaran" class="icon-md">
Mata Pelajaran
</h1>
@if($mapelList->isEmpty())
<div class="empty-state">
<div class="empty-icon">📭</div>
<div class="empty-icon">
<img src="{{ asset('images/icon/siswam/mailbox.png') }}" alt="Belum ada mata pelajaran" class="icon-mascot" style="width:80px; margin-top:0;">
</div>
<p>Belum ada mata pelajaran untuk kelasmu.</p>
</div>
@else
<div class="mapel-grid">
@foreach($mapelList as $mapel)
<a href="{{ route('siswa.materi.show', $mapel['id_mengajar']) }}" class="mapel-card">
<div class="mapel-icon">📖</div>
<div class="mapel-icon">
<img src="{{ asset('images/icon/siswam/mapel.png') }}" alt="Ikon mapel" class="icon-md">
</div>
<div>
<p class="mapel-nama">{{ $mapel['nama_mapel'] }}</p>
<p class="mapel-guru">{{ $mapel['nama_guru'] }}</p>
</div>
<div class="mapel-badge">
📄 {{ $mapel['jumlah_materi'] }} Materi
<img src="{{ asset('images/icon/siswam/jml-m.png') }}" alt="Jumlah materi" class="icon-sm">
{{ $mapel['jumlah_materi'] }} Materi
</div>
</a>
@endforeach

View File

@ -29,7 +29,6 @@
}
.mapel-header-icon {
font-size: 36px;
background: rgba(255,255,255,0.2);
width: 60px;
height: 60px;
@ -80,7 +79,6 @@
display: flex;
align-items: center;
justify-content: center;
font-size: 22px;
flex-shrink: 0;
}
@ -148,7 +146,11 @@
color: #94a3b8;
}
.empty-state .empty-icon { font-size: 48px; margin-bottom: 12px; }
.empty-state .empty-icon {
margin-bottom: 12px;
display: flex;
justify-content: center;
}
</style>
@endpush
@ -157,7 +159,9 @@
<a href="{{ route('siswa.materi.index') }}" class="back-link"> Kembali ke Mata Pelajaran</a>
<div class="mapel-header">
<div class="mapel-header-icon">📖</div>
<div class="mapel-header-icon">
<img src="{{ asset('images/icon/siswam/mapel.png') }}" alt="Ikon mata pelajaran" class="icon-md">
</div>
<div>
<p class="mapel-header-title">{{ optional($mengajar->mapel)->nama_mapel ?? '-' }}</p>
<p class="mapel-header-sub">Guru: {{ optional($mengajar->guru)->nama ?? '-' }} &bull; {{ $materiList->count() }} Materi</p>
@ -166,7 +170,9 @@
@if($materiList->isEmpty())
<div class="empty-state">
<div class="empty-icon">📭</div>
<div class="empty-icon">
<img src="{{ asset('images/icon/siswam/mailbox.png') }}" alt="Belum ada materi" class="icon-mascot" style="width:80px; margin-top:0;">
</div>
<p>Belum ada materi yang diupload untuk mata pelajaran ini.</p>
</div>
@else
@ -178,22 +184,31 @@
: null;
$iconClass = match(true) {
in_array($ext, ['pdf']) => 'pdf',
in_array($ext, ['doc','docx']) => 'doc',
in_array($ext, ['pdf']) => 'pdf',
in_array($ext, ['doc','docx']) => 'doc',
in_array($ext, ['jpg','jpeg','png']) => 'img',
default => 'other',
default => 'other',
};
$iconEmoji = match($iconClass) {
'pdf' => '📄',
'doc' => '📝',
'img' => '🖼️',
default => '📎',
$iconSrc = match($iconClass) {
'pdf' => asset('images/icon/siswam/pdf.png'),
'doc' => asset('images/icon/siswam/doc.png'),
'img' => asset('images/icon/siswam/image.png'),
default => asset('images/icon/siswam/link.png'),
};
$iconAlt = match($iconClass) {
'pdf' => 'File PDF',
'doc' => 'File Word',
'img' => 'File Gambar',
default => 'File lainnya',
};
@endphp
<div class="materi-card">
<div class="materi-file-icon {{ $iconClass }}">{{ $iconEmoji }}</div>
<div class="materi-file-icon {{ $iconClass }}">
<img src="{{ $iconSrc }}" alt="{{ $iconAlt }}" class="icon-md">
</div>
<div class="materi-info">
<p class="materi-judul">{{ $materi->judul_materi }}</p>
@ -209,7 +224,8 @@
<a href="{{ asset('storage/' . $materi->lampiran_materi) }}"
target="_blank"
class="btn-download">
⬇️ Unduh
<img src="{{ asset('images/icon/siswam/download.png') }}" alt="Unduh materi" class="icon-sm">
Unduh
</a>
@else
<span class="btn-no-file">Tidak ada file</span>

View File

@ -9,6 +9,9 @@
font-weight: 700;
color: #1e293b;
margin-bottom: 24px;
display: flex;
align-items: center;
gap: 10px;
}
/* TABS */
@ -89,9 +92,9 @@
color: inherit;
}
.tugas-card.status-belum { border-left-color: #2b8ef3; }
.tugas-card.status-belum { border-left-color: #2b8ef3; }
.tugas-card.status-terlambat { border-left-color: #ef4444; }
.tugas-card.status-selesai { border-left-color: #22c55e; }
.tugas-card.status-selesai { border-left-color: #22c55e; }
.tugas-icon {
width: 48px;
@ -100,7 +103,6 @@
display: flex;
align-items: center;
justify-content: center;
font-size: 22px;
flex-shrink: 0;
}
@ -173,7 +175,11 @@
color: #94a3b8;
}
.empty-icon { font-size: 48px; margin-bottom: 12px; }
.empty-state .empty-icon {
margin-bottom: 12px;
display: flex;
justify-content: center;
}
/* ALERT */
.alert-success {
@ -184,6 +190,9 @@
margin-bottom: 20px;
font-weight: 500;
font-size: 14px;
display: flex;
align-items: center;
gap: 8px;
}
.alert-error {
@ -194,20 +203,32 @@
margin-bottom: 20px;
font-weight: 500;
font-size: 14px;
display: flex;
align-items: center;
gap: 8px;
}
</style>
@endpush
@section('content')
<h1 class="page-title">📋 Tugas</h1>
<h1 class="page-title">
<img src="{{ asset('images/icon/siswat/buku1.png') }}" alt="Ikon tugas" class="icon-md">
Tugas
</h1>
@if(session('success'))
<div class="alert-success"> {{ session('success') }}</div>
<div class="alert-success">
<img src="{{ asset('images/icon/siswat/v.png') }}" alt="Berhasil" class="icon-sm">
{{ session('success') }}
</div>
@endif
@if(session('error'))
<div class="alert-error"> {{ session('error') }}</div>
<div class="alert-error">
<img src="{{ asset('images/icon/siswat/x.png') }}" alt="Gagal" class="icon-sm">
{{ session('error') }}
</div>
@endif
{{-- TABS --}}
@ -230,7 +251,9 @@
<div id="tab-belum" class="tab-panel active">
@if($tugasBelum->isEmpty())
<div class="empty-state">
<div class="empty-icon">🎉</div>
<div class="empty-icon">
<img src="{{ asset('images/icon/siswat/confetti.png') }}" alt="Semua tugas sudah dikerjakan" class="icon-mascot" style="width:80px; margin-top:0;">
</div>
<p>Semua tugas sudah dikerjakan!</p>
</div>
@else
@ -238,7 +261,9 @@
@foreach($tugasBelum as $tugas)
<a href="{{ route('siswa.tugas.show', $tugas['id_tugas']) }}"
class="tugas-card status-belum">
<div class="tugas-icon">📋</div>
<div class="tugas-icon">
<img src="{{ asset('images/icon/siswat/buku1.png') }}" alt="Tugas belum dikerjakan" class="icon-md">
</div>
<div class="tugas-info">
<p class="tugas-judul">{{ $tugas['judul'] }}</p>
<p class="tugas-meta">{{ $tugas['nama_mapel'] }} &bull; {{ $tugas['nama_guru'] }}</p>
@ -258,7 +283,9 @@ class="tugas-card status-belum">
<div id="tab-terlambat" class="tab-panel">
@if($tugasTerlambat->isEmpty())
<div class="empty-state">
<div class="empty-icon"></div>
<div class="empty-icon">
<img src="{{ asset('images/icon/siswat/v.png') }}" alt="Tidak ada tugas terlambat" class="icon-mascot" style="width:80px; margin-top:0;">
</div>
<p>Tidak ada tugas terlambat!</p>
</div>
@else
@ -266,7 +293,9 @@ class="tugas-card status-belum">
@foreach($tugasTerlambat as $tugas)
<a href="{{ route('siswa.tugas.show', $tugas['id_tugas']) }}"
class="tugas-card status-terlambat">
<div class="tugas-icon"></div>
<div class="tugas-icon">
<img src="{{ asset('images/icon/siswat/alarm.png') }}" alt="Tugas terlambat" class="icon-md">
</div>
<div class="tugas-info">
<p class="tugas-judul">{{ $tugas['judul'] }}</p>
<p class="tugas-meta">{{ $tugas['nama_mapel'] }} &bull; {{ $tugas['nama_guru'] }}</p>
@ -286,7 +315,9 @@ class="tugas-card status-terlambat">
<div id="tab-selesai" class="tab-panel">
@if($tugasSelesai->isEmpty())
<div class="empty-state">
<div class="empty-icon">📭</div>
<div class="empty-icon">
<img src="{{ asset('images/icon/siswat/mailbox.png') }}" alt="Belum ada tugas dikumpulkan" class="icon-mascot" style="width:80px; margin-top:0;">
</div>
<p>Belum ada tugas yang dikumpulkan.</p>
</div>
@else
@ -294,7 +325,9 @@ class="tugas-card status-terlambat">
@foreach($tugasSelesai as $tugas)
<a href="{{ route('siswa.tugas.show', $tugas['id_tugas']) }}"
class="tugas-card status-selesai">
<div class="tugas-icon"></div>
<div class="tugas-icon">
<img src="{{ asset('images/icon/siswat/v.png') }}" alt="Tugas selesai" class="icon-md">
</div>
<div class="tugas-info">
<p class="tugas-judul">{{ $tugas['judul'] }}</p>
<p class="tugas-meta">{{ $tugas['nama_mapel'] }} &bull; {{ $tugas['nama_guru'] }}</p>
@ -303,7 +336,10 @@ class="tugas-card status-selesai">
<span class="status-badge badge-selesai">Dikumpulkan</span>
@if($tugas['exp'] > 0)
<br>
<span class="exp-badge"> {{ $tugas['exp'] }} EXP</span>
<span class="exp-badge">
<img src="{{ asset('images/icon/siswat/star.png') }}" alt="EXP" class="icon-sm">
{{ $tugas['exp'] }} EXP
</span>
@endif
</div>
</a>

View File

@ -90,6 +90,9 @@
font-weight: 700;
color: #1e293b;
margin: 0 0 20px;
display: flex;
align-items: center;
gap: 8px;
}
.upload-area {
@ -116,7 +119,11 @@
height: 100%;
}
.upload-icon { font-size: 36px; margin-bottom: 10px; }
.upload-icon {
margin-bottom: 10px;
display: flex;
justify-content: center;
}
.upload-text {
font-size: 14px;
@ -183,7 +190,11 @@
margin-bottom: 20px;
}
.sudah-kumpul-icon { font-size: 40px; margin-bottom: 10px; }
.sudah-kumpul-icon {
margin-bottom: 10px;
display: flex;
justify-content: center;
}
.sudah-kumpul-title {
font-size: 17px;
@ -224,6 +235,12 @@
color: #9a3412;
}
.terlambat-warning-icon {
display: flex;
align-items: center;
flex-shrink: 0;
}
.alert-success {
background: #dcfce7;
color: #166534;
@ -232,6 +249,9 @@
margin-bottom: 20px;
font-weight: 500;
font-size: 14px;
display: flex;
align-items: center;
gap: 8px;
}
.alert-error {
@ -242,6 +262,9 @@
margin-bottom: 20px;
font-weight: 500;
font-size: 14px;
display: flex;
align-items: center;
gap: 8px;
}
</style>
@endpush
@ -251,15 +274,21 @@
<a href="{{ route('siswa.tugas.index') }}" class="back-link"> Kembali ke Daftar Tugas</a>
@if(session('success'))
<div class="alert-success"> {{ session('success') }}</div>
<div class="alert-success">
<img src="{{ asset('images/icon/siswat/v.png') }}" alt="Berhasil" class="icon-sm">
{{ session('success') }}
</div>
@endif
@if(session('error'))
<div class="alert-error"> {{ session('error') }}</div>
<div class="alert-error">
<img src="{{ asset('images/icon/siswat/x.png') }}" alt="Gagal" class="icon-sm">
{{ session('error') }}
</div>
@endif
{{-- DETAIL TUGAS --}}
@php
$headerClass = $sudahKumpul ? 'selesai' : ($terlambat ? 'terlambat' : '');
$headerClass = $sudahKumpul ? 'selesai' : ($terlambat ? 'terlambat' : '');
$deadlineClass = $terlambat ? '' : 'aman';
@endphp
@ -268,13 +297,16 @@
<div class="tugas-meta-row">
<span class="meta-chip">
📚 {{ optional(optional($tugas->mengajar)->mapel)->nama_mapel ?? '-' }}
<img src="{{ asset('images/icon/siswat/stacked.png') }}" alt="Mata pelajaran" class="icon-sm">
{{ optional(optional($tugas->mengajar)->mapel)->nama_mapel ?? '-' }}
</span>
<span class="meta-chip">
👨‍🏫 {{ optional(optional($tugas->mengajar)->guru)->nama ?? '-' }}
<img src="{{ asset('images/icon/siswat/guru.png') }}" alt="Guru" class="icon-sm">
{{ optional(optional($tugas->mengajar)->guru)->nama ?? '-' }}
</span>
<span class="meta-chip deadline-chip {{ $deadlineClass }}">
Deadline: {{ \Carbon\Carbon::parse($tugas->deadline)->format('d M Y, H:i') }}
<img src="{{ asset('images/icon/siswat/alarm.png') }}" alt="Deadline" class="icon-sm">
Deadline: {{ \Carbon\Carbon::parse($tugas->deadline)->format('d M Y, H:i') }}
</span>
</div>
@ -288,7 +320,9 @@
{{-- WARNING TERLAMBAT --}}
@if($terlambat && !$sudahKumpul)
<div class="terlambat-warning">
<span style="font-size:20px">⚠️</span>
<span class="terlambat-warning-icon">
<img src="{{ asset('images/icon/siswat/alert.png') }}" alt="Peringatan terlambat" class="icon-md">
</span>
<div>
<strong>Deadline sudah lewat!</strong><br>
Kamu masih bisa mengumpulkan tugas ini, tapi akan ditandai sebagai <strong>terlambat</strong>.
@ -299,14 +333,19 @@
{{-- SUDAH DIKUMPULKAN --}}
@if($sudahKumpul)
<div class="sudah-kumpul-card">
<div class="sudah-kumpul-icon">🎉</div>
<div class="sudah-kumpul-icon">
<img src="{{ asset('images/icon/siswat/confetti.png') }}" alt="Tugas sudah dikumpulkan" class="icon-mascot" style="width:80px; margin-top:0;">
</div>
<p class="sudah-kumpul-title">Tugas sudah dikumpulkan!</p>
<p class="sudah-kumpul-sub">
Dikumpulkan pada {{ \Carbon\Carbon::parse($pengumpulan->tanggal_submit)->format('d M Y, H:i') }}
&bull; Status: <strong>{{ ucfirst($pengumpulan->status) }}</strong>
</p>
@if($pengumpulan->exp > 0)
<div class="exp-display"> {{ $pengumpulan->exp }} EXP didapat</div>
<div class="exp-display">
<img src="{{ asset('images/icon/siswat/star.png') }}" alt="EXP" class="icon-sm">
{{ $pengumpulan->exp }} EXP didapat
</div>
@else
<p style="font-size:13px;color:#16a34a;margin-top:8px">
EXP akan diberikan setelah guru menilai tugasmu.
@ -327,7 +366,10 @@
{{-- FORM SUBMIT --}}
@else
<div class="submit-card">
<p class="submit-title">📤 Upload Jawaban</p>
<p class="submit-title">
<img src="{{ asset('images/icon/siswat/upload.png') }}" alt="Upload jawaban" class="icon-md">
Upload Jawaban
</p>
<form action="{{ route('siswa.tugas.submit', $tugas->id_tugas) }}"
method="POST"
@ -336,7 +378,10 @@
@csrf
@error('lampiran_tugas')
<div class="alert-error" style="margin-bottom:16px"> {{ $message }}</div>
<div class="alert-error" style="margin-bottom:16px">
<img src="{{ asset('images/icon/siswat/x.png') }}" alt="Error validasi" class="icon-sm">
{{ $message }}
</div>
@enderror
<div class="upload-area" id="uploadArea">
@ -345,13 +390,15 @@
id="fileInput"
accept=".pdf,.doc,.docx,.jpg,.jpeg,.png"
required>
<div class="upload-icon">☁️</div>
<div class="upload-icon">
<img src="{{ asset('images/icon/siswat/upload.png') }}" alt="Upload file" class="icon-mascot" style="width:48px; margin-top:0;">
</div>
<p class="upload-text"><strong>Klik untuk pilih file</strong> atau drag & drop di sini</p>
<p class="upload-hint">PDF, DOC, DOCX, JPG, PNG &bull; Maks. 5MB</p>
</div>
<div class="file-preview" id="filePreview">
<span style="font-size:22px">📎</span>
<img src="{{ asset('images/icon/siswat/buku1.png') }}" alt="File terpilih" class="icon-md">
<span class="file-preview-name" id="fileName">-</span>
<span class="file-preview-size" id="fileSize">-</span>
</div>
@ -386,7 +433,6 @@
}
});
// Drag & drop highlight
uploadArea.addEventListener('dragover', (e) => {
e.preventDefault();
uploadArea.classList.add('drag-over');