This commit is contained in:
Vassshhh
2025-07-30 20:31:37 +07:00
parent ee28551344
commit 731e7d90cc
11 changed files with 1347 additions and 100 deletions

View File

@@ -1,10 +1,10 @@
import React, { useEffect, useState } from 'react';
import 'bootstrap/dist/css/bootstrap.min.css';
// import './assets/css/templatemo-chain-app-dev.css'; // Assuming you copy your original CSS here
// import './assets/css/animated.css';
// import './assets/css/owl.css';
import React, { useEffect, useState, useRef } from 'react';
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
import styles from './components/Styles.module.css';
// Import components
import Login from './components/Login';
// Import your converted React components
import Header from './components/Header';
import HeroSection from './components/HeroSection';
import ServicesSection from './components/ServicesSection';
@@ -15,14 +15,70 @@ import KnowledgeBaseSection from './components/KnowledgeBaseSection';
import ClientsSection from './components/ClientsSection';
import Footer from './components/Footer';
import ProductDetailPage from './components/ProductDetailPage';
function HomePage({
hoveredCard,
setHoveredCard,
selectedProduct,
setSelectedProduct,
showedModal,
setShowedModal,
productSectionRef,
courseSectionRef
}) {
return (
<>
<HeroSection />
<ServicesSection />
<ProductSection
productSectionRef={productSectionRef}
hoveredCard={hoveredCard}
setHoveredCard={setHoveredCard}
setSelectedProduct={setSelectedProduct}
setShowedModal={setShowedModal}
/>
<AcademySection
courseSectionRef={courseSectionRef}
hoveredCard={hoveredCard}
setHoveredCard={setHoveredCard}
setSelectedProduct={setSelectedProduct}
setShowedModal={setShowedModal} />
<AboutUsSection />
<KnowledgeBaseSection />
<ClientsSection />
</>
);
}
function App() {
const [loading, setLoading] = useState(true);
// State yang diperlukan untuk HomePage
const [hoveredCard, setHoveredCard] = useState(null);
const [selectedProduct, setSelectedProduct] = useState({});
const [showedModal, setShowedModal] = useState(null); // 'product' | 'login' | null
const [postLoginAction, setPostLoginAction] = useState(null);
const [username, setUsername] = useState(null);
const productSectionRef = useRef(null);
const courseSectionRef = useRef(null);
const scrollToProduct = () => {
productSectionRef.current?.scrollIntoView({ behavior: "smooth" });
};
const scrollToCourse = () => {
courseSectionRef.current?.scrollIntoView({ behavior: "smooth" });
};
useEffect(() => {
// Simulate preloader and remove it after some time
const timer = setTimeout(() => {
setLoading(false);
}, 1000); // Adjust time as needed
}, 1000);
return () => clearTimeout(timer);
}, []);
@@ -42,29 +98,60 @@ function App() {
}
return (
<div className="App">
<Header />
<HeroSection />
{/* FULL WIDTH IMAGE SECTION */}
{/* This can be a separate component or integrated into HeroSection */}
<div className="custom-image-section wow fadeInRight">
<a href="https://registration.kediritechnopark.com/" target="_blank" className="custom-image-link" rel="noopener noreferrer">
<div className="custom-image-wrapper">
<img src="/assets/images/FREE!.png" className="custom-image" />
<div className="light-glare"></div>
</div>
</a>
<Router>
<div className="App">
<Header username={username} scrollToProduct={scrollToProduct} scrollToCourse={scrollToCourse} setShowedModal={setShowedModal} />
<Routes>
<Route
path="/"
element={
<HomePage
hoveredCard={hoveredCard}
setHoveredCard={setHoveredCard}
selectedProduct={selectedProduct}
setSelectedProduct={setSelectedProduct}
showedModal={showedModal}
setShowedModal={setShowedModal}
productSectionRef={productSectionRef}
courseSectionRef={courseSectionRef}
/>
}
/>
</Routes>
<Footer />
{/* Unified Modal */}
{showedModal && (
<div
className={styles.modal}
onClick={() => {
setShowedModal(null);
setSelectedProduct({});
}}
>
<div
className={styles.modalBody}
onClick={(e) => e.stopPropagation()}
>
{showedModal === 'product' && (
<ProductDetailPage
setPostLoginAction={setPostLoginAction}
setShowedModal={setShowedModal}
product={selectedProduct}
onClose={() => {
setShowedModal(null);
setSelectedProduct({});
}}
/>
)}
{showedModal === 'login' && (
<Login postLoginAction={postLoginAction} setPostLoginAction={setPostLoginAction} onClose={() => setShowedModal(null)} />
)}
</div>
</div>
)}
</div>
<ServicesSection />
<ProductSection />
<AcademySection />
<AboutUsSection />
<KnowledgeBaseSection />
<ClientsSection />
<Footer />
</div>
</Router>
);
}
export default App;
export default App;

View File

@@ -1,7 +1,8 @@
import React, { useEffect, useState } from 'react';
import { Container, Row, Col, Card, Button } from 'react-bootstrap';
import { Container } from 'react-bootstrap';
import styles from './Styles.module.css';
const AcademySection = () => {
const AcademySection = ({hoveredCard, setHoveredCard, setSelectedProduct, setShowedModal, courseSectionRef}) => {
const [products, setProducts] = useState([]);
useEffect(() => {
@@ -18,43 +19,53 @@ const AcademySection = () => {
}, []);
return (
<section id="academy" className="academy-tables py-5">
<section id="services" className="services py-5">
<Container>
<div className="section-heading text-center mb-4">
<h4>OUR <em>ACADEMY PROGRAM</em></h4>
<img src="/assets/images/heading-line-dec.png" alt="" />
<p>Academy Program adalah wadah belajar digital untuk anak-anak dan remaja. Didesain interaktif, kreatif, dan gratis setiap modul membekali peserta dengan keterampilan masa depan, dari teknologi dasar hingga coding dan proyek nyata.</p>
</div>
<Row>
{products.map((product, idx) => (
<Col lg={3} md={4} sm={6} xs={12} key={idx} className="mb-4">
<Card className="academy-item-regular h-100">
<Card.Body>
<Card.Title>{product.name}</Card.Title>
<div className="icon mb-3">
<img src={product.image || '/assets/images/pricing-table-01.png'} alt={product.name} className="img-fluid" />
<div className={styles.coursesGrid}>
{products &&
products[0]?.name &&
products.map(product => (
<div
key={product.id}
className={`${styles.courseCard} ${hoveredCard === product.id ? styles.courseCardHover : ''}`}
onClick={() => {
setSelectedProduct(product);
setShowedModal('product');
}}
onMouseEnter={() => setHoveredCard(product.id)}
onMouseLeave={() => setHoveredCard(null)}
>
<div className={styles.courseImage} style={{ backgroundImage: `url(${product.image})` }}>
{product.price === 0 && (
<span className={styles.courseLabel}>Free</span>
)}
</div>
<div className={styles.courseContent}>
<h3 className={styles.courseTitle}>{product.name}</h3>
<p className={styles.courseDesc}>{product.description}</p>
<div className={styles.coursePrice}>
<span
className={
product.price === 0
? styles.freePrice
: styles.currentPrice
}
>
{product.price == null
? 'Pay-As-You-Go'
: `Rp ${product.price.toLocaleString('id-ID')}`}
</span>
</div>
<ul>
{product.duration && (
<li>
Durasi:{" "}
{product.duration.hours
? `${product.duration.hours} jam`
: product.duration.days
? `${product.duration.days} hari`
: "-"}
</li>
)}
<li>Harga: {product.currency} {product.price.toLocaleString()}</li>
</ul>
<div className="border-button mt-3">
<Button variant="outline-primary" href="#program">Lihat Detail</Button>
</div>
</Card.Body>
</Card>
</Col>
))}
</Row>
</div>
</div>
))}
</div>
</Container>
</section>
);

View File

@@ -1,24 +1,72 @@
import React from 'react';
import { Navbar, Nav, Container } from 'react-bootstrap';
import React, { useState, useEffect, useRef } from 'react';
import { useNavigate } from 'react-router-dom';
import { Navbar, Nav, Container } from 'react-bootstrap';
import styles from './Styles.module.css';
const Header = ({ username, scrollToProduct, scrollToCourse, setShowedModal }) => {
const navigate = useNavigate();
const [hoveredNav, setHoveredNav] = useState(null);
const Header = () => {
return (
<Navbar bg="light" expand="lg" sticky="top">
<Container>
<Navbar.Brand href="#">Kediri Technopark</Navbar.Brand>
<Navbar.Toggle aria-controls="basic-navbar-nav" />
<Navbar.Collapse id="basic-navbar-nav">
<Nav className="me-auto">
<Nav.Link href="#top">Home</Nav.Link>
<Nav.Link href="#services">Services</Nav.Link>
<Nav.Link href="#produk">Product</Nav.Link>
<Nav.Link href="#academy">Academy</Nav.Link>
<Nav.Link href="#about">About</Nav.Link>
<Nav.Link href="#knowledge">Knowledge</Nav.Link>
</Nav>
</Navbar.Collapse>
</Container>
</Navbar>
<header className={styles.header}>
<img src="./kediri-technopark-logo.png" className={styles.logo} alt="Logo" />
<nav className={styles.nav}>
<a
className={`${styles.navLink} ${hoveredNav === 2 ? styles.navLinkHover : ''}`}
onMouseEnter={() => setHoveredNav(2)}
onMouseLeave={() => setHoveredNav(null)}
onClick={() => {
if (username == null) {
scrollToProduct();
}
else {
navigate('/products');
}
}}
>
PRODUCTS
</a>
<a
className={`${styles.navLink} ${hoveredNav === 3 ? styles.navLinkHover : ''}`}
onMouseEnter={() => setHoveredNav(3)}
onMouseLeave={() => setHoveredNav(null)}
onClick={() => {
if (username == null) {
scrollToCourse();
}
else {
navigate('/courses');
}
}}
>
COURSES
</a>
<a
className={`${styles.navLink} ${hoveredNav === 4 ? styles.navLinkHover : ''}`}
onMouseEnter={() => setHoveredNav(4)}
onMouseLeave={() => setHoveredNav(null)}
>
USER
</a>
</nav>
<div className={styles.authButtons}>
{username ? (
<span style={{ color: '#2563eb', fontWeight: '600' }}>
Halo, {username}
</span>
) : (
<button className={styles.loginButton} onClick={() => setShowedModal('login')}>
LOGIN
</button>
)}
</div>
</header>
);
};

240
src/components/Login.js Normal file
View File

@@ -0,0 +1,240 @@
import React, { useState } from 'react';
const LoginRegister = ({postLoginAction, setPostLoginAction}) => {
const [tab, setTab] = useState('login'); // 'login' or 'register'
const [email, setEmail] = useState('');
const [username, setUsername] = useState('');
const [password, setPassword] = useState('');
const styles = {
container: {
backgroundColor: 'white',
borderRadius: '1rem',
padding: '2rem',
maxWidth: '400px',
margin: '0 auto',
boxShadow: '0 8px 25px rgba(0, 0, 0, 0.15)',
fontFamily: 'Inter, system-ui, sans-serif',
},
title: {
fontSize: '1.3rem',
fontWeight: 'bold',
color: '#1e293b',
marginBottom: '1.5rem',
textAlign: 'center',
},
tabContainer: {
display: 'flex',
justifyContent: 'center',
marginBottom: '1.5rem',
},
tabButton: (active) => ({
cursor: 'pointer',
padding: '0.5rem 1rem',
borderBottom: active ? '2px solid #2563eb' : '2px solid transparent',
fontWeight: active ? 'bold' : 'normal',
color: active ? '#2563eb' : '#64748b',
background: 'none',
border: 'none',
outline: 'none',
fontSize: '1rem',
margin: '0 0.5rem',
}),
input: {
display: 'block',
padding: '0.75rem 1rem',
marginBottom: '1rem',
borderRadius: '0.5rem',
border: '1px solid #cbd5e1',
fontSize: '0.9rem',
outline: 'none',
boxSizing: 'border-box',
width: '100%',
},
inputWrapper: {
width: '100%',
},
button: {
display: 'block',
width: '100%',
padding: '0.75rem 1.5rem',
borderRadius: '0.5rem',
border: 'none',
fontSize: '0.75rem',
fontWeight: '600',
cursor: 'pointer',
transition: 'all 0.3s ease',
textTransform: 'uppercase',
backgroundColor: '#2563eb',
color: 'white',
},
};
const handleLogin = async (e) => {
e.preventDefault();
if (!username || !password) {
alert('Username dan password wajib diisi');
return;
}
try {
const res = await fetch('https://bot.kediritechnopark.com/webhook/user-dev/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username, password }),
});
if (!res.ok) {
const err = await res.text();
alert(`Login gagal: ${err}`);
return;
}
const data = await res.json();
const token = data[0].token;
if (token) {
document.cookie = `token=${token}; path=/; max-age=${7 * 24 * 60 * 60}`;
if (postLoginAction) {
postLoginAction(); // resume action (e.g., checkout)
setPostLoginAction(null);
}
} else {
alert('Token tidak ditemukan pada respons login');
}
} catch (error) {
alert('Terjadi kesalahan saat login');
console.error(error);
}
};
const handleRegister = async (e) => {
e.preventDefault();
if (!email || !username || !password) {
alert('Email, username, dan password wajib diisi');
return;
}
try {
const res = await fetch('https://bot.kediritechnopark.com/webhook/user-dev/register', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email, username, password }),
});
if (!res.ok) {
const err = await res.text();
alert(`Registrasi gagal: ${err}`);
return;
}
alert('Registrasi berhasil! Silakan login.');
setTab('login');
setEmail('');
setUsername('');
setPassword('');
} catch (error) {
alert('Terjadi kesalahan saat registrasi');
console.error(error);
}
};
return (
<div style={styles.container}>
<h2 style={styles.title}>{tab === 'login' ? 'Login' : 'Register'}</h2>
<div style={styles.tabContainer}>
<button
style={styles.tabButton(tab === 'login')}
onClick={() => setTab('login')}
type="button"
>
Login
</button>
<button
style={styles.tabButton(tab === 'register')}
onClick={() => setTab('register')}
type="button"
>
Register
</button>
</div>
{tab === 'login' ? (
<form onSubmit={handleLogin}>
<div style={styles.inputWrapper}>
<input
style={styles.input}
type="text"
placeholder="Username"
value={username}
onChange={(e) => setUsername(e.target.value)}
autoComplete="username"
/>
</div>
<div style={styles.inputWrapper}>
<input
style={styles.input}
type="password"
placeholder="Password"
value={password}
onChange={(e) => setPassword(e.target.value)}
autoComplete="current-password"
/>
</div>
<button
type="submit"
style={styles.button}
onMouseOver={(e) => (e.target.style.backgroundColor = '#1d4ed8')}
onMouseOut={(e) => (e.target.style.backgroundColor = '#2563eb')}
>
Masuk
</button>
</form>
) : (
<form onSubmit={handleRegister}>
<div style={styles.inputWrapper}>
<input
style={styles.input}
type="email"
placeholder="Email"
value={email}
onChange={(e) => setEmail(e.target.value)}
autoComplete="email"
/>
</div>
<div style={styles.inputWrapper}>
<input
style={styles.input}
type="text"
placeholder="Username"
value={username}
onChange={(e) => setUsername(e.target.value)}
autoComplete="username"
/>
</div>
<div style={styles.inputWrapper}>
<input
style={styles.input}
type="password"
placeholder="Password"
value={password}
onChange={(e) => setPassword(e.target.value)}
autoComplete="new-password"
/>
</div>
<button
type="submit"
style={styles.button}
onMouseOver={(e) => (e.target.style.backgroundColor = '#1d4ed8')}
onMouseOut={(e) => (e.target.style.backgroundColor = '#2563eb')}
>
Daftar
</button>
</form>
)}
</div>
);
};
export default LoginRegister;

View File

@@ -0,0 +1,95 @@
.container {
background-color: white;
border-radius: 1rem;
padding: 2rem;
max-width: 700px;
margin: 0 auto;
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15);
font-family: 'Inter', system-ui, sans-serif;
text-align: center;
}
.image {
width: 100%;
height: 260px;
background-color: #e2e8f0;
border-radius: 0.75rem;
display: flex;
align-items: center;
justify-content: center;
color: #64748b;
font-size: 1rem;
margin-bottom: 1.5rem;
}
.headerRow {
display: flex;
justify-content: center;
align-items: center;
gap: 1rem;
margin-bottom: 1rem;
text-align: center;
}
.title {
font-size: 1.1rem;
font-weight: bold;
color: #1e293b;
margin: 0;
}
.price {
font-size: 1.1rem;
font-weight: bold;
color: #2563eb; /* default color, bisa override di inline style */
}
.description {
font-size: 1rem;
color: #64748b;
margin-bottom: 1.5rem;
line-height: 1.6;
}
.buttonGroup {
display: flex;
justify-content: space-between;
gap: 1rem;
}
.button {
padding: 0.75rem 0.4rem;
border-radius: 0.5rem;
border: none;
font-size: 0.55rem;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
text-transform: uppercase;
}
.addToCartButton {
background-color: #fbbf24;
color: #1e293b;
margin-right: 0.5rem;
flex: 1;
}
.checkoutButton {
background-color: #2563eb;
color: white;
flex: 1;
}
/* Responsive untuk mobile */
@media (max-width: 600px) {
.container {
text-align: left;
}
.headerRow {
justify-content: flex-start;
}
.buttonGroup {
gap: 0.5rem;
}
}

View File

@@ -0,0 +1,133 @@
import React, { useEffect, useState } from 'react';
import styles from './ProductDetail.module.css';
const ProductDetail = ({ product, setPostLoginAction, setShowedModal }) => {
const [inCart, setInCart] = useState(false);
useEffect(() => {
const existingCookie = document.cookie
.split('; ')
.find(row => row.startsWith('itemsId='));
let items = [];
if (existingCookie) {
try {
const value = decodeURIComponent(existingCookie.split('=')[1]);
items = JSON.parse(value);
if (!Array.isArray(items)) items = [];
} catch (e) {
items = [];
}
}
setInCart(items.includes(product.id));
}, [product.id]);
const onSetCart = () => {
const existingCookie = document.cookie
.split('; ')
.find(row => row.startsWith('itemsId='));
let items = [];
if (existingCookie) {
try {
const value = decodeURIComponent(existingCookie.split('=')[1]);
items = JSON.parse(value);
if (!Array.isArray(items)) items = [];
} catch (e) {
items = [];
}
}
let updatedItems;
if (items.includes(product.id)) {
updatedItems = items.filter(id => id !== product.id); // remove
setInCart(false);
} else {
updatedItems = [...items, product.id]; // add
setInCart(true);
}
document.cookie = `itemsId=${JSON.stringify(updatedItems)}; path=/; max-age=${7 * 24 * 60 * 60
}`;
};
const onCheckout = () => {
// Ambil token dari cookie
const tokenCookie = document.cookie
.split('; ')
.find(row => row.startsWith('token='));
const token = tokenCookie ? tokenCookie.split('=')[1] : '';
// Ambil itemsId dari cookie
const itemsCookie = document.cookie
.split('; ')
.find(row => row.startsWith('itemsId='));
let items = [];
if (itemsCookie) {
try {
items = JSON.parse(itemsCookie.split('=')[1]);
if (!Array.isArray(items)) items = [];
} catch (e) {
items = [];
}
}
if (!items.includes(product.id)) {
items.push(product.id);
}
// Encode items ke string untuk query param
const itemsParam = JSON.stringify(items);
if (!tokenCookie) {
setPostLoginAction(() => () => onCheckout()); // remember intent
setShowedModal('login');
return;
}
// Redirect dengan token dan itemsId di query
window.location.href = `http://localhost:3001/?token=${token}&itemsId=${itemsParam}&redirect_uri=http://localhost:3000/courses&redirect_failed=http://localhost:3000`;
};
// Override harga warna jika free
const priceColor = product.price === 0 ? '#059669' : '#2563eb';
return (
<div className={styles.container}>
<div className={styles.image}>📦</div>
<div className={styles.headerRow}>
<h2 className={styles.title}>{product.name}</h2>
<div className={styles.price} style={{ color: priceColor }}>
{product.price === 0
? 'Free'
: `Rp ${parseInt(product.price).toLocaleString('id-ID')}`}
</div>
</div>
<p className={styles.description}>{product.description}</p>
<div className={styles.buttonGroup}>
<button
className={`${styles.button} ${styles.addToCartButton}`}
onClick={onSetCart}
onMouseOver={e => (e.target.style.backgroundColor = '#facc15')}
onMouseOut={e => (e.target.style.backgroundColor = '#fbbf24')}
>
{inCart ? 'Hapus dari Keranjang' : '+ Keranjang'}
</button>
<button
className={`${styles.button} ${styles.checkoutButton}`}
onClick={onCheckout}
onMouseOver={e => (e.target.style.backgroundColor = '#1d4ed8')}
onMouseOut={e => (e.target.style.backgroundColor = '#2563eb')}
>
Checkout
</button>
</div>
</div>
);
};
export default ProductDetail;

View File

@@ -1,7 +1,9 @@
import React, { useEffect, useState } from 'react';
import { Container, Row, Col, Card } from 'react-bootstrap';
import { Container } from 'react-bootstrap';
import styles from './Styles.module.css';
const ProductSection = () => {
const ProductSection = ({ hoveredCard, setHoveredCard, setSelectedProduct, setShowedModal, productSectionRef }) => {
const [products, setProducts] = useState([]);
useEffect(() => {
@@ -10,7 +12,7 @@ const ProductSection = () => {
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ type: 'product', onlyParents:true }),
body: JSON.stringify({ type: 'product', onlyParents: true }),
})
.then(res => res.json())
.then(data => setProducts(data))
@@ -18,27 +20,53 @@ const ProductSection = () => {
}, []);
return (
<section id="produk" className="product-section py-5 bg-light">
<section id="services" className="services py-5">
<Container>
<div className="section-heading text-center mb-4">
<h4>OUR <em>PRODUCT</em></h4>
<h4>OUR <em>PRODUCTS</em></h4>
<img src="/assets/images/heading-line-dec.png" alt="" />
<p>Kami menyediakan berbagai solusi teknologi untuk mendukung transformasi digital bisnis dan masyarakat.</p>
</div>
<Row className="justify-content-center">
<Col lg={12}>
<div className="d-flex overflow-auto">
{products.map((product, idx) => (
<Card key={idx} className="text-center me-3" style={{ minWidth: '200px' }}>
<Card.Img variant="top" src={product.image || '/assets/images/placeholder.png'} alt={product.name} />
<Card.Body>
<Card.Title>{product.name}</Card.Title>
</Card.Body>
</Card>
))}
</div>
</Col>
</Row>
<div className={styles.coursesGrid}>
{products &&
products[0]?.name &&
products.map(product => (
<div
key={product.id}
className={`${styles.courseCard} ${hoveredCard === product.id ? styles.courseCardHover : ''}`}
onClick={() => {
setSelectedProduct(product);
setShowedModal('product');
}}
onMouseEnter={() => setHoveredCard(product.id)}
onMouseLeave={() => setHoveredCard(null)}
>
<div className={styles.courseImage} style={{ backgroundImage: `url(${product.image})` }}>
{product.price === 0 && (
<span className={styles.courseLabel}>Free</span>
)}
</div>
<div className={styles.courseContent}>
<h3 className={styles.courseTitle}>{product.name}</h3>
<p className={styles.courseDesc}>{product.description}</p>
<div className={styles.coursePrice}>
<span
className={
product.price === 0
? styles.freePrice
: styles.currentPrice
}
>
{product.price == null
? 'Pay-As-You-Go'
: `Rp ${product.price.toLocaleString('id-ID')}`}
</span>
</div>
</div>
</div>
))}
</div>
</Container>
</section>
);

View File

@@ -0,0 +1,550 @@
/* TechnoAcademyWebsite.module.css */
/* Header */
.modal {
position: fixed;
width: 100%;
height: 100%;
top: 0;
color: white;
background-color: rgba(0, 0, 0, 0.527);
text-align: center;
display: flex;
justify-content: center;
align-items: center;
z-index: 1001;
}
.modalBody {
color: white;
text-align: center;
display: flex;
align-items: center;
}
.header {
background-color: white;
padding: 14px 6rem;
display: flex;
justify-content: space-between;
align-items: center;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
position: sticky;
top: 0;
z-index: 1000;
}
.logo {
display: flex;
align-items: center;
font-size: 1.2rem;
font-weight: bold;
width: 130px;
}
.nav {
display: flex;
gap: 2rem;
align-items: center;
}
.navLink {
text-decoration: none;
color: #64748b;
font-weight: 500;
font-size: 0.95rem;
transition: color 0.3s ease;
cursor: pointer;
}
.navLinkHover {
color: #2563eb;
}
.authButtons {
display: flex;
gap: 1rem;
align-items: center;
}
.searchButton,
.userButton {
background-color: #2563eb;
color: white;
border: none;
border-radius: 50%;
width: 34px;
height: 34px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.3s ease;
}
.loginButton {
background-color: transparent;
color: #64748b;
border: none;
font-size: 0.9rem;
cursor: pointer;
padding: 0.5rem 1rem;
}
/* Hero Section */
.hero {
background-image: url('https://academy.kediritechnopark.com/wp-content/uploads/2025/07/pexels-fauxels-3184360-scaled.jpg');
background-size: cover;
background-position: center;
padding: 0 2rem;
color: white;
position: relative;
border-radius: 0 0 0 60px;
text-align: left;
height: calc(100vh - 61.61px);
display: flex;
}
.heroContainer {
max-width: 1200px;
margin: 0 auto;
display: grid;
grid-template-columns: 1fr 1fr;
gap: 4rem;
align-items: center;
}
.heroContent {
z-index: 2;
}
.heroTitle {
font-size: 3.5rem;
font-weight: bold;
margin-top: 0px;
margin-bottom: 1.5rem;
line-height: 1.1;
}
.heroDescription {
font-size: 1.1rem;
line-height: 1.6;
margin-bottom: 2rem;
opacity: 0.9;
}
.joinButton {
background: linear-gradient(135deg, rgb(59, 130, 246) 0%, rgb(30, 64, 175) 100%);
color: white;
border: none;
padding: 1rem 2rem;
border-radius: 0.5rem;
font-size: 1rem;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
text-transform: uppercase;
}
.heroImage {
display: flex;
justify-content: center;
align-items: center;
}
.heroImagePlaceholder {
width: 500px;
height: 350px;
background-color: rgba(255, 255, 255, 0.1);
border-radius: 1rem;
display: flex;
align-items: center;
justify-content: center;
font-size: 1.2rem;
color: white;
border: 2px dashed rgba(255, 255, 255, 0.3);
}
.Section {
padding: 2rem 6rem;
background-color: #f8fafc;
}
.coursesContainer {
max-width: 1200px;
margin: 0 auto;
}
.coursesTitle {
font-size: 2.5rem;
font-weight: bold;
color: #1e293b;
margin-bottom: 3rem;
}
.coursesGrid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 2rem;
}
.courseCard {
background-color: white;
border-radius: 1rem;
overflow: hidden;
box-shadow: 2px 4px 6px rgba(0, 0, 0, 0.3);
transition: all 0.3s ease;
cursor: pointer;
/* Tambahan untuk menghilangkan highlight biru */
-webkit-tap-highlight-color: transparent;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
outline: none;
}
.courseCard:focus {
outline: none;
}
.courseCardHover {
transform: translateY(-4px);
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15);
}
.courseImage {
width: 100%;
height: 200px;
background-color: #e2e8f0;
position: relative;
display: flex;
align-items: center;
justify-content: center;
color: #64748b;
background-repeat: no-repeat;
background-size: cover;
background-position: center;
}
.courseLabel {
position: absolute;
top: 1rem;
right: 1rem;
background-color: #ef4444;
color: white;
padding: 0.25rem 0.75rem;
border-radius: 1rem;
font-size: 0.8rem;
font-weight: 600;
}
.courseContent {
padding: 1.5rem;
text-align: left;
}
.courseCategory {
font-size: 0.8rem;
color: #64748b;
margin-bottom: 0.5rem;
text-transform: uppercase;
letter-spacing: 0.05em;
}
.courseTitle {
font-size: 1.2rem;
font-weight: bold;
color: #1e293b;
margin-bottom: 1rem;
margin-top: 0;
}
.courseStats {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 1rem;
font-size: 0.9rem;
color: #64748b;
}
.courseStat {
display: flex;
align-items: center;
gap: 0.25rem;
}
.coursePrice {
display: flex;
align-items: center;
justify-content: space-between;
}
.originalPrice {
text-decoration: line-through;
color: #94a3b8;
font-size: 0.9rem;
}
.currentPrice {
font-size: 1.2rem;
font-weight: bold;
color: #2563eb;
}
.freePrice {
font-size: 1.2rem;
font-weight: bold;
color: #059669;
}
.featuresContainer {
max-width: 1200px;
margin: 0 auto;
}
.featuresTitle {
font-size: 2.5rem;
font-weight: bold;
color: #1e293b;
margin-bottom: 2rem;
margin-top: 0;
text-align: left;
}
.featuresDescription {
font-size: 1.1rem;
color: #64748b;
line-height: 1.6;
margin-bottom: 3rem;
text-align: left;
max-width: 800px;
}
.featuresList {
display: grid;
gap: 2rem;
}
.featureItem {
display: flex;
gap: 1.5rem;
background-color: #f8fafc;
border-radius: 1rem;
align-items: flex-start;
}
.featureIcon {
width: 60px;
height: 60px;
border-radius: 50%;
background-color: #2563eb;
color: white;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
font-size: 1.5rem;
}
.featureContent {
flex: 1;
}
.featureTitle {
font-size: 1.3rem;
font-weight: bold;
color: #1e293b;
margin-top: 6px;
margin-bottom: 0.5rem;
text-align: left;
}
.featureDescription {
color: #64748b;
line-height: 1.6;
text-align: left;
}
.ctaContainer {
max-width: 1200px;
margin: 0 auto;
display: grid;
grid-template-columns: 1fr 1fr;
gap: 4rem;
}
.ctaCard {
background-color: white;
padding: 3rem;
border-radius: 1rem;
text-align: center;
box-shadow: 10px 10px 10px rgb(0 0 0 / 15%);
transition: all 0.3s ease;
display: flex;
flex-direction: column;
justify-content: space-between;
}
.ctaIcon {
width: 80px;
height: 80px;
border-radius: 50%;
background-color: #2563eb;
color: white;
display: flex;
align-items: center;
justify-content: center;
margin: 0 auto 2rem;
font-size: 2rem;
}
.ctaTitle {
font-size: 1.5rem;
font-weight: bold;
color: #1e293b;
margin-bottom: 1rem;
}
.ctaDescription {
color: #64748b;
line-height: 1.6;
margin-bottom: 2rem;
}
.ctaButton {
background-color: #2563eb;
color: white;
border: none;
padding: 1rem 2rem;
border-radius: 0.5rem;
font-size: 1rem;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
text-transform: uppercase;
}
/* Footer */
.footer {
background-color: #1e293b;
color: white;
padding: 2rem;
text-align: center;
}
.footerContent {
max-width: 1200px;
margin: 0 auto;
display: flex;
justify-content: space-between;
align-items: center;
}
.footerText {
font-size: 0.9rem;
color: #94a3b8;
}
.socialLinks {
display: flex;
gap: 1rem;
}
.socialLink {
width: 40px;
height: 40px;
background-color: #374151;
border-radius: 0.5rem;
display: flex;
align-items: center;
justify-content: center;
color: white;
text-decoration: none;
transition: all 0.3s ease;
cursor: pointer;
}
.socialLink:hover {
background-color: #4b5563;
}
/* Responsive Styles */
@media (max-width: 800px) {
.modalBody {
width: 80%;
}
.header {
padding: 14px 2rem;
}
.heroContainer {
grid-template-columns: 1fr;
text-align: center;
}
.ctaTitle,
.courseTitle {
font-size: 15px;
margin: 0;
}
.ctaDescription,
.courseContent p {
font-size: 13px;
margin: 6px 0px;
}
.ctaCard,
.Section {
padding: 2rem 0.8rem;
background-color: #f8fafc;
}
.courseContent {
text-align: left;
padding: 0.8rem;
}
.ctaContainer,
.coursesGrid {
grid-template-columns: repeat(auto-fit, minmax(138px, 1fr));
gap: 0.8rem;
}
.featureTitle {
margin-top: 6px;
margin-bottom: 0.5rem;
text-align: left;
}
.featureDescription {
text-align: left;
margin-bottom: 0;
}
.featuresList {
gap: 0rem;
}
.ctaButton {
font-size: 12px;
padding: 1rem 1rem;
margin-top: 10px;
}
}
@media (max-width: 600px) {
.nav {
display: none;
}
}