This commit is contained in:
kediritechnopark
2025-08-22 18:27:47 +07:00
parent e356aabc31
commit c03cf59f86
7 changed files with 806 additions and 1360 deletions

54
package-lock.json generated
View File

@@ -14,6 +14,7 @@
"@testing-library/user-event": "^13.5.0",
"react": "^19.1.1",
"react-dom": "^19.1.1",
"react-router-dom": "^7.6.2",
"react-scripts": "5.0.1",
"web-vitals": "^2.1.4"
},
@@ -12866,6 +12867,53 @@
"node": ">=0.10.0"
}
},
"node_modules/react-router": {
"version": "7.8.1",
"resolved": "https://registry.npmjs.org/react-router/-/react-router-7.8.1.tgz",
"integrity": "sha512-5cy/M8DHcG51/KUIka1nfZ2QeylS4PJRs6TT8I4PF5axVsI5JUxp0hC0NZ/AEEj8Vw7xsEoD7L/6FY+zoYaOGA==",
"license": "MIT",
"dependencies": {
"cookie": "^1.0.1",
"set-cookie-parser": "^2.6.0"
},
"engines": {
"node": ">=20.0.0"
},
"peerDependencies": {
"react": ">=18",
"react-dom": ">=18"
},
"peerDependenciesMeta": {
"react-dom": {
"optional": true
}
}
},
"node_modules/react-router-dom": {
"version": "7.8.1",
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.8.1.tgz",
"integrity": "sha512-NkgBCF3sVgCiAWIlSt89GR2PLaksMpoo3HDCorpRfnCEfdtRPLiuTf+CNXvqZMI5SJLZCLpVCvcZrTdtGW64xQ==",
"license": "MIT",
"dependencies": {
"react-router": "7.8.1"
},
"engines": {
"node": ">=20.0.0"
},
"peerDependencies": {
"react": ">=18",
"react-dom": ">=18"
}
},
"node_modules/react-router/node_modules/cookie": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz",
"integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==",
"license": "MIT",
"engines": {
"node": ">=18"
}
},
"node_modules/react-scripts": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-5.0.1.tgz",
@@ -13703,6 +13751,12 @@
"node": ">= 0.8.0"
}
},
"node_modules/set-cookie-parser": {
"version": "2.7.1",
"resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz",
"integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==",
"license": "MIT"
},
"node_modules/set-function-length": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz",

View File

@@ -9,6 +9,7 @@
"@testing-library/user-event": "^13.5.0",
"react": "^19.1.1",
"react-dom": "^19.1.1",
"react-router-dom": "^7.6.2",
"react-scripts": "5.0.1",
"web-vitals": "^2.1.4"
},

File diff suppressed because it is too large Load Diff

409
src/LandingPage.js Normal file
View File

@@ -0,0 +1,409 @@
import React, { useState, useEffect } from "react";
import { useParams } from "react-router-dom";
import formatRupiah from './helpers/formatRupiah';
export default function App() {
const { organizationId } = useParams();
const [organization, setOrganization] = useState([]);
const [tickets, setTickets] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const [isLoggedIn, setIsLoggedIn] = useState(false);
const [showModal, setShowModal] = useState(false);
const [modalTab, setModalTab] = useState("login"); // "login" or "register"
const [selectedTicket, setSelectedTicket] = useState(null);
const [formData, setFormData] = useState({});
useEffect(() => {
if (!organizationId) {
setError("Parameter organization tidak ditemukan di URL.");
setLoading(false);
return;
}
fetch(
`https://auto.apps.kediritechnopark.com/webhook/mayagen/organization?organization=${organizationId}`
)
.then((res) => {
if (!res.ok) throw new Error("Gagal mengambil data tiket");
return res.json();
})
.then((data) => {
setTickets(data[0].tickets);
setOrganization(data[0]);
setLoading(false);
})
.catch((err) => {
setError(err.message);
setLoading(false);
});
}, [organizationId]);
// Dummy login handler
function handleLogin(e) {
e.preventDefault();
// Kamu bisa validasi di sini
setIsLoggedIn(true);
}
// Dummy register handler
function handleRegister(e) {
e.preventDefault();
// Kamu bisa validasi dan buat user di sini
setIsLoggedIn(true);
}
// Form tiket submit
function handleSubmitTicket(e) {
e.preventDefault();
alert(`Form tiket untuk ${selectedTicket.nama_tiket} submitted:\n` + JSON.stringify(formData, null, 2));
setShowModal(false);
setSelectedTicket(null);
setFormData({});
}
// Handle klik beli tiket
function handleBuyClick(ticket) {
if (!isLoggedIn) {
setModalTab("login");
setShowModal(true);
setSelectedTicket(ticket);
} else {
setSelectedTicket(ticket);
setShowModal(true);
}
}
// Handle form input change untuk tiket form
function handleFormChange(e) {
const { name, value } = e.target;
setFormData((prev) => ({ ...prev, [name]: value }));
}
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error}</div>;
return (
<div className="bg-brand-dark text-gray-300 font-sans antialiased min-h-screen relative">
<main>
{/* Hero Section */}
<section className="relative h-[70vh] flex items-center justify-center text-center text-white overflow-hidden">
<div
className="absolute inset-0 bg-cover bg-center z-0"
style={{
backgroundImage:
"url('https://images.unsplash.com/photo-1524368535928-5b5e00ddc76b?q=80&w=2070&auto=format&fit=crop')",
}}
></div>
<div className="absolute inset-0 bg-black/70 z-10"></div>
<div className="relative z-20 container mx-auto px-6">
<h1 className="text-4xl md:text-6xl font-extrabold leading-tight">
{organization.nama_organization}
</h1>
<p className="mt-4 max-w-2xl mx-auto text-lg text-gray-200">
{organization.deskripsi}
</p>
</div>
</section>
{/* Acara Section */}
<section id="acara" className="py-20">
<div className="container mx-auto px-6">
<div className="text-center mb-12">
<h2 className="text-3xl md:text-4xl font-bold text-white">
Acara Mendatang
</h2>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
{tickets.map((ticket, i) => {
const isSoldOut = ticket.sold_out || false;
return (
<div
key={ticket.tiket_id || i}
className={`bg-brand-card rounded-lg overflow-hidden border border-brand-subtle flex flex-col ${isSoldOut ? "relative" : ""
}`}
>
{isSoldOut && (
<div className="absolute inset-0 bg-black/60 flex items-center justify-center z-10">
<span className="text-white text-2xl font-bold border-2 border-white px-6 py-2 rotate-[-15deg]">
TIKET HABIS
</span>
</div>
)}
<img
src={
ticket.poster ||
`https://placehold.co/600x400/161B22/FFFFFF?text=${encodeURIComponent(
ticket.nama_tiket.replace(/\s/g, "+")
)}`
}
alt="Poster Acara"
className={`w-full h-48 object-cover ${isSoldOut ? "filter grayscale" : ""
}`}
/>
<div className="p-6 flex flex-col flex-grow">
<h3 className="text-xl font-bold text-white">
{ticket.nama_tiket}
</h3>
<div className="flex items-center text-sm text-gray-400 mt-2 space-x-4">
<span>📅 {ticket.tanggal || "Tanggal belum tersedia"}</span>
<span>📍 {ticket.lokasi || "Lokasi belum tersedia"}</span>
</div>
<p
className={`mt-4 text-lg font-semibold ${isSoldOut ? "text-gray-500" : "text-white"
}`}
>
{ticket.price === "0.00" ? "Gratis" : formatRupiah(ticket.price)}
</p>
<div className="mt-auto pt-4">
{isSoldOut ? (
<button
disabled
className="block w-full text-center bg-brand-subtle text-gray-500 font-semibold py-2.5 px-5 rounded-lg cursor-not-allowed"
>
Tiket Habis
</button>
) : (
<button
className="block w-full text-center bg-brand-accent hover:bg-brand-accent-hover text-white font-semibold py-2.5 px-5 rounded-lg transition-colors"
onClick={() => handleBuyClick(ticket)}
>
{ticket.price == 0 ? "Ambil Tiket" : `Beli Tiket`}
</button>
)}
</div>
</div>
</div>
);
})}
</div>
</div>
</section>
{organization.about &&
<section id="tentang" className="py-20 bg-brand-card">
<div className="container mx-auto px-6 grid lg:grid-cols-2 gap-12 items-center">
<div>
<img
src="https://images.unsplash.com/photo-1523580494863-6f3031224c94?q=80&w=2070&auto=format&fit=crop"
className="rounded-lg"
alt="Tim SoundVibes Production"
/>
</div>
<div>
<h2 className="text-3xl font-bold text-white">
Tentang {organization.nama_organization}
</h2>
<p className="mt-4 text-gray-400">
Didirikan pada tahun 2020, SoundVibes Production adalah promotor musik
yang berdedikasi untuk menciptakan acara live yang tak terlupakan.
Misi kami adalah menghubungkan penggemar dengan artis favorit mereka
melalui produksi acara yang berkualitas tinggi, aman, dan nyaman.
</p>
<p className="mt-4 text-gray-400">
Kami percaya bahwa musik memiliki kekuatan untuk menyatukan, dan
kami bangga menjadi jembatan antara panggung dan penonton.
</p>
</div>
</div>
</section>
}
</main>
{/* Footer */}
<footer
id="kontak"
className="bg-brand-dark border-t border-brand-subtle"
>
<div className="container mx-auto px-6 py-8 text-center text-gray-400">
<h3 className="text-2xl font-bold text-white">
{organization.nama_organization}
</h3>
<p className="mt-2">Ikuti kami untuk update acara terbaru!</p>
<div className="flex justify-center space-x-6 mt-4">
<a href="#" className="hover:text-white">
Instagram
</a>
<a href="#" className="hover:text-white">
Twitter
</a>
<a href="#" className="hover:text-white">
Facebook
</a>
</div>
<p className="mt-8 text-sm text-gray-500">
Data dalam situs ini mungkin dilindungi hak cipta oleh {organization.nama_organization}.
</p>
</div>
</footer>
{/* Modal */}
{showModal && (
<div className="fixed inset-0 bg-black bg-opacity-70 flex justify-center items-center z-50">
<div className="bg-brand-card rounded-lg max-w-md w-full p-6 relative">
<button
className="absolute top-3 right-3 text-gray-400 hover:text-white"
onClick={() => {
setShowModal(false);
setSelectedTicket(null);
setFormData({});
}}
>
</button>
{!isLoggedIn && (
<>
{/* Tabs */}
<div className="flex mb-4 border-b border-gray-600">
<button
className={`flex-1 py-2 ${modalTab === "login"
? "border-b-2 border-brand-accent font-bold"
: "text-gray-400"
}`}
onClick={() => setModalTab("login")}
>
Login
</button>
<button
className={`flex-1 py-2 ${modalTab === "register"
? "border-b-2 border-brand-accent font-bold"
: "text-gray-400"
}`}
onClick={() => setModalTab("register")}
>
Register
</button>
</div>
{/* Login Form */}
{modalTab === "login" && (
<form onSubmit={handleLogin} className="space-y-4">
<input
type="email"
placeholder="Email"
required
className="w-full p-2 rounded bg-gray-800 text-white"
/>
<input
type="password"
placeholder="Password"
required
className="w-full p-2 rounded bg-gray-800 text-white"
/>
<button
type="submit"
className="w-full bg-brand-accent py-2 rounded hover:bg-brand-accent-hover transition-colors"
>
Login
</button>
</form>
)}
{/* Register Form */}
{modalTab === "register" && (
<form onSubmit={handleRegister} className="space-y-4">
<input
type="text"
placeholder="Nama Lengkap"
required
className="w-full p-2 rounded bg-gray-800 text-white"
/>
<input
type="email"
placeholder="Email"
required
className="w-full p-2 rounded bg-gray-800 text-white"
/>
<input
type="password"
placeholder="Password"
required
className="w-full p-2 rounded bg-gray-800 text-white"
/>
<button
type="submit"
className="w-full bg-brand-accent py-2 rounded hover:bg-brand-accent-hover transition-colors"
>
Register
</button>
</form>
)}
</>
)}
{/* Jika sudah login, tampilkan form tiket */}
{isLoggedIn && selectedTicket && (
<form onSubmit={handleSubmitTicket} className="space-y-4">
<h2 className="text-xl font-bold mb-4 text-white">
Formulir {selectedTicket.nama_tiket}
</h2>
{Object.entries(selectedTicket.form).map(([key, type]) => {
if (type === "text") {
return (
<div key={key}>
<label className="block mb-1 text-gray-300 capitalize" htmlFor={key}>
{key.replace(/_/g, " ")}
</label>
<input
type="text"
id={key}
name={key}
required
value={formData[key] || ""}
onChange={handleFormChange}
className="w-full p-2 rounded bg-gray-800 text-white"
/>
</div>
);
}
if (type === "selection") {
// Contoh opsi metode pembayaran (bisa disesuaikan)
const options = [
"Transfer Bank",
"Kartu Kredit",
"Cash",
"E-Wallet",
];
return (
<div key={key}>
<label className="block mb-1 text-gray-300 capitalize" htmlFor={key}>
{key.replace(/_/g, " ")}
</label>
<select
id={key}
name={key}
required
value={formData[key] || ""}
onChange={handleFormChange}
className="w-full p-2 rounded bg-gray-800 text-white"
>
<option value="">Pilih {key.replace(/_/g, " ")}</option>
{options.map((opt) => (
<option key={opt} value={opt}>
{opt}
</option>
))}
</select>
</div>
);
}
return null;
})}
<button
type="submit"
className="w-full bg-brand-accent py-2 rounded hover:bg-brand-accent-hover transition-colors"
>
{selectedTicket.price == 0 ? "Dapatkan Tiket" : `Bayar Rp ${selectedTicket.price}`}
</button>
</form>
)}
</div>
</div>
)}
</div>
);
}

View File

@@ -0,0 +1,8 @@
export default function formatRupiah(number) {
return new Intl.NumberFormat('id-ID', {
style: 'currency',
currency: 'IDR',
minimumFractionDigits: 0,
maximumFractionDigits: 0
}).format(number);
}

View File

@@ -1,614 +1,37 @@
/* Direktif Tailwind */
@tailwind base;
@tailwind components;
@tailwind utilities;
/* Tambahan untuk font */
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap');
/* Custom Theme Overrides */
:root {
--color-brand-dark: #0D1117;
--color-brand-card: #161B22;
--color-brand-accent: #10B981;
--color-brand-accent-hover: #059669;
--color-brand-subtle: #30363D;
}
body {
font-family: 'Manrope', sans-serif;
background-color: #f8fafc; /* Background utama, mirip dengan brand guide */
color: #282930; /* Teks gelap utama */
margin: 0;
overflow-x: hidden; /* Mencegah scroll horizontal */
@apply bg-[var(--color-brand-dark)] text-gray-300 font-sans antialiased;
}
/* Custom styles for sections and elements */
.header-bg {
background-color: #ffffff;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
/* Font override for Inter */
* {
font-family: 'Inter', sans-serif;
}
.hero-section {
background-color: #ffffff;
/* padding: 4rem 2rem; */
text-align: center;
@keyframes marquee {
0% {
transform: translateX(0%);
}
.stats-grid {
display: grid;
grid-template-columns: repeat(1, minmax(0, 1fr)); /* 1 kolom di layar kecil */
gap: 1.5rem;
margin-top: 3rem;
}
@media (min-width: 640px) { /* sm breakpoint */
.stats-grid {
grid-template-columns: repeat(2, minmax(0, 1fr)); /* 2 kolom di layar sedang */
}
}
@media (min-width: 1024px) { /* lg breakpoint */
.stats-grid {
grid-template-columns: repeat(4, minmax(0, 1fr)); /* 4 kolom di layar besar */
}
}
.stat-card {
background-color: #ffffff;
border-radius: 1rem;
text-align: left;
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
transition: transform 0.2s ease-in-out;
}
.stat-card.dark-bg {
background-color: #282930; /* Menggunakan warna gelap dari brand guide */
color: #e2e8f0;
}
.stat-card.light-green-bg {
background-color: #E5FFCC; /* Menggunakan warna hijau terang dari brand guide */
color: #142F32; /* Teks gelap di atas hijau terang */
}
.stat-card:hover {
transform: translateY(-5px);
}
.bottom-section {
background-color: #282930; /* Menggunakan warna gelap dari brand guide */
padding: 4rem 2rem;
color: #e2e8f0;
text-align: center;
}
.bottom-grid {
display: grid;
grid-template-columns: repeat(1, minmax(0, 1fr)); /* 1 kolom di layar kecil */
gap: 1.5rem;
margin-top: 3rem;
}
@media (min-width: 768px) { /* md breakpoint */
.bottom-grid {
grid-template-columns: repeat(3, minmax(0, 1fr)); /* 3 kolom di layar sedang */
}
}
.bottom-card {
background-color: #142F32; /* Menggunakan warna hijau gelap dari brand guide */
border-radius: 1rem;
padding: 2rem;
text-align: left;
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.2), 0 4px 6px -2px rgba(0, 0, 0, 0.1);
transition: transform 0.2s ease-in-out;
}
.bottom-card:hover {
transform: translateY(-5px);
}
.button-primary {
background-color: #142F32; /* Menggunakan warna hijau gelap dari brand guide */
color: white;
transition: all 0.3s ease;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}
.button-primary:hover {
background-color: #0d1e20; /* Hijau gelap lebih gelap */
transform: translateY(-2px);
box-shadow: 0 6px 8px rgba(0, 0, 0, 0.15);
}
.button-secondary {
background-color: transparent;
border: 1px solid #9ca3af; /* Gray border */
color: #4b5563; /* Dark gray text */
transition: all 0.3s ease;
}
.button-secondary:hover {
background-color: #f3f4f6; /* Light gray background */
transform: translateY(-2px);
}
.button-sign-up {
background-color: #142F32; /* Menggunakan warna hijau gelap dari brand guide */
color: white;
transition: all 0.3s ease;
}
.button-sign-up:hover {
background-color: #0d1e20; /* Hijau gelap lebih gelap */
}
/* Responsive image for the first card */
.card-image {
width: 100%;
height: auto;
border-radius: 0.75rem; /* Sudut membulat untuk gambar */
object-fit: cover; /* Memastikan gambar menutupi area */
}
/* Styles for Key Benefits Section */
.key-benefits-section {
background-color: #f8fafc;
padding: 4rem 2rem;
text-align: center;
display: flex;
flex-direction: column;
align-items: center;
}
@media (min-width: 768px) {
.key-benefits-section {
flex-direction: row;
text-align: left;
justify-content: center;
gap: 4rem;
}
}
.benefits-content {
flex: 1;
max-width: 600px;
}
.benefits-image-card {
flex: 1;
background-color: #ffffff;
border-radius: 1rem;
padding: 1.5rem;
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
margin-top: 2rem;
max-width: 400px;
}
@media (min-width: 768px) {
.benefits-image-card {
margin-top: 0;
}
}
.benefit-item {
display: flex;
align-items: flex-start;
margin-bottom: 1rem;
}
.benefit-item i {
color: #22c55e; /* Hijau centang */
margin-right: 0.75rem;
font-size: 1.25rem;
margin-top: 0.25rem;
}
/* Styles for Tailored Plans Section */
.tailored-plans-section {
background-color: #282930; /* Menggunakan warna gelap dari brand guide */
padding: 4rem 2rem;
color: #e2e8f0;
text-align: center;
}
.pricing-grid {
display: grid;
grid-template-columns: repeat(1, minmax(0, 1fr));
gap: 2rem;
margin-top: 3rem;
max-width: 900px;
margin-left: auto;
margin-right: auto;
}
@media (min-width: 768px) {
.pricing-grid {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
}
.pricing-card {
background-color: #142F32; /* Menggunakan warna hijau gelap dari brand guide */
border-radius: 1rem;
padding: 2.5rem;
text-align: left;
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.2), 0 4px 6px -2px rgba(0, 0, 0, 0.1);
}
.pricing-card .price {
font-size: 3rem;
font-weight: 800;
color: #ffffff;
margin-bottom: 0.5rem;
}
.pricing-card .price-per-month {
color: #9ca3af;
font-size: 1rem;
}
.pricing-card .feature-list {
margin-top: 1.5rem;
margin-bottom: 2rem;
}
.pricing-card .feature-item {
display: flex;
align-items: center;
margin-bottom: 0.75rem;
color: #cbd5e0;
}
.pricing-card .feature-item i {
color: #22c55e; /* Hijau centang */
margin-right: 0.5rem;
}
/* New styles for Seamless Integrations Section */
.integrations-section {
background-color: #f8fafc;
padding: 4rem 2rem;
text-align: center;
display: flex;
flex-direction: column;
align-items: center;
}
@media (min-width: 768px) {
.integrations-section {
flex-direction: row;
text-align: left;
justify-content: center;
gap: 4rem;
}
}
.integrations-content {
flex: 1;
max-width: 600px;
}
.integrations-visual {
flex: 1;
background-color: #E5FFCC; /* Menggunakan warna hijau terang dari brand guide */
border-radius: 1rem;
padding: 2rem;
margin-top: 2rem;
max-width: 500px;
position: relative;
overflow: hidden;
display: flex;
justify-content: center;
align-items: center;
min-height: 300px;
}
@media (min-width: 768px) {
.integrations-visual {
margin-top: 0;
100% {
transform: translateX(-50%);
}
}
.integration-icon-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 1.5rem;
width: 100%;
height: 100%;
position: relative;
justify-items: center;
align-items: center;
}
.integration-icon {
background-color: #ffffff;
border-radius: 50%;
width: 60px;
height: 60px;
display: flex;
justify-content: center;
align-items: center;
box-shadow: 0 5px 10px rgba(0, 0, 0, 0.1);
font-size: 1.5rem;
color: #4a5568;
}
/* From Idea to Production Section */
.idea-to-production-section {
background-color: #282930; /* Menggunakan warna gelap dari brand guide */
padding: 4rem 2rem;
color: #e2e8f0;
text-align: center;
}
/* Footer Section */
.footer-section {
background-color: #282930; /* Menggunakan warna gelap dari brand guide */
padding: 4rem 2rem;
color: #a0aec0; /* Teks abu-abu lebih terang */
font-size: 0.9rem;
}
.footer-grid {
display: grid;
grid-template-columns: repeat(1, minmax(0, 1fr));
gap: 2rem;
}
@media (min-width: 640px) {
.footer-grid {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
}
@media (min-width: 1024px) {
.footer-grid {
grid-template-columns: repeat(5, minmax(0, 1fr));
}
}
.footer-col h3 {
font-weight: 600;
color: #ffffff;
margin-bottom: 1rem;
}
.footer-col ul li {
margin-bottom: 0.5rem;
}
.footer-col ul li a {
color: #a0aec0;
transition: color 0.2s ease;
}
.footer-col ul li a:hover {
color: #ffffff;
}
.social-icons a {
color: #a0aec0;
font-size: 1.25rem;
margin-right: 1rem;
transition: color 0.2s ease;
}
.social-icons a:hover {
color: #ffffff;
}
/* Modal Styles */
.modal {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.7);
display: flex;
justify-content: center;
align-items: center;
z-index: 1000;
opacity: 0;
visibility: hidden;
transition: opacity 0.3s ease, visibility 0.3s ease;
}
.modal.show {
opacity: 1;
visibility: visible;
}
.modal-content {
background-color: #ffffff;
padding: 2.5rem;
border-radius: 1rem;
box-shadow: 0 10px 20px rgba(0, 0, 0, 0.2);
max-width: 450px;
width: 90%;
text-align: center;
position: relative;
transform: translateY(-20px);
transition: transform 0.3s ease;
}
.modal.show .modal-content {
transform: translateY(0);
}
.close-button {
position: absolute;
top: 1rem;
right: 1rem;
font-size: 1.5rem;
cursor: pointer;
color: #4b5563;
}
/* Dashboard Styles */
.dashboard-container {
display: none; /* Hidden by default */
grid-template-columns: 250px 1fr; /* Sidebar fixed width, content fills */
min-height: 100vh;
background-color: #f8fafc;
color: #282930;
}
@media (max-width: 768px) {
.dashboard-container {
grid-template-columns: 1fr; /* Stack on small screens */
}
.dashboard-sidebar {
width: 100%;
position: relative;
box-shadow: none;
}
}
.dashboard-sidebar {
background-color: #142F32; /* Hijau gelap */
color: #E5FFCC;
padding: 2rem 1rem;
display: flex;
flex-direction: column;
align-items: center;
box-shadow: 2px 0 5px rgba(0, 0, 0, 0.1);
}
.dashboard-sidebar-item {
width: 100%;
padding: 0.75rem 1rem;
margin-bottom: 0.5rem;
border-radius: 0.5rem;
display: flex;
align-items: center;
cursor: pointer;
transition: background-color 0.2s ease, color 0.2s ease;
}
.dashboard-sidebar-item:hover {
background-color: rgba(229, 255, 204, 0.1); /* Hijau terang transparan */
color: #E5FFCC;
}
.dashboard-sidebar-item.active {
background-color: #E5FFCC;
color: #142F32;
font-weight: 600;
}
.dashboard-main-content {
padding: 2rem;
background-color: #f8fafc;
overflow-y: auto;
}
.chat-container {
display: flex;
flex-direction: column;
height: calc(100vh - 4rem); /* Adjust for padding */
background-color: #ffffff;
border-radius: 1rem;
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.05);
overflow: hidden;
}
.chat-messages {
flex-grow: 1;
padding: 1.5rem;
overflow-y: auto;
display: flex;
flex-direction: column;
gap: 1rem;
}
.chat-message {
max-width: 80%;
padding: 0.75rem 1rem;
border-radius: 0.75rem;
word-wrap: break-word;
}
.chat-message.user {
background-color: #E5FFCC;
align-self: flex-end;
color: #142F32;
}
.chat-message.bot {
background-color: #f1f5f9;
align-self: flex-start;
color: #282930;
}
.chat-input-area {
display: flex;
padding: 1rem;
border-top: 1px solid #e2e8f0;
background-color: #ffffff;
}
.chat-input-area input {
flex-grow: 1;
padding: 0.75rem 1rem;
border: 1px solid #cbd5e0;
border-radius: 0.5rem;
margin-right: 0.75rem;
font-size: 1rem;
}
.chat-input-area button {
background-color: #142F32;
color: white;
padding: 0.75rem 1.25rem;
border-radius: 0.5rem;
transition: background-color 0.2s ease;
}
.chat-input-area button:hover {
background-color: #0d1e20;
}
.loading-indicator {
display: none;
text-align: center;
padding: 1rem;
color: #777C90;
}
.loading-indicator.show {
display: block;
}
.image-gen-preview {
max-width: 100%;
height: auto;
border-radius: 0.75rem;
margin-top: 1.5rem;
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
}
.dashboard-section-content {
background-color: #ffffff;
padding: 2rem;
border-radius: 1rem;
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.05);
min-height: calc(100vh - 8rem); /* Adjust for header/footer/padding */
}
.dashboard-section-content h2 {
font-size: 2.25rem;
font-weight: 800;
margin-bottom: 1.5rem;
color: #282930;
}
.dashboard-section-content p {
color: #4b5563;
margin-bottom: 1rem;
}
.dashboard-section-content ul {
list-style: none;
padding: 0;
}
.dashboard-section-content ul li {
background-color: #f1f5f9;
padding: 1rem;
border-radius: 0.75rem;
margin-bottom: 0.75rem;
display: flex;
justify-content: space-between;
align-items: center;
.animate-marquee {
animation: marquee 40s linear infinite;
}

View File

@@ -1,10 +1,21 @@
/** @type {import('tailwindcss').Config} */
// tailwind.config.js
module.exports = {
content: [
"./src/**/*.{js,jsx,ts,tsx}",
"./src/**/*.{js,jsx,ts,tsx}", // pastikan path sesuai struktur project kamu
],
theme: {
extend: {},
extend: {
fontFamily: {
sans: ['Inter', 'sans-serif'],
},
colors: {
'brand-dark': '#0D1117',
'brand-card': '#161B22',
'brand-accent': '#10B981',
'brand-accent-hover': '#059669',
'brand-subtle': '#30363D',
},
},
},
plugins: [],
}