perbaiki halaamn tentang
This commit is contained in:
parent
a55559a202
commit
21065b0ab3
Binary file not shown.
|
After Width: | Height: | Size: 576 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 176 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 122 KiB |
|
|
@ -4,6 +4,8 @@ import { ThemeProvider } from './contexts/ThemeContext';
|
||||||
import { AuthProvider, useAuth } from './contexts/AuthContext';
|
import { AuthProvider, useAuth } from './contexts/AuthContext';
|
||||||
import ProtectedRoute from './components/ProtectedRoute';
|
import ProtectedRoute from './components/ProtectedRoute';
|
||||||
|
|
||||||
|
import { ScrollParticles } from './components/ui/scroll-particles';
|
||||||
|
|
||||||
// Pages
|
// Pages
|
||||||
import LandingPage from './pages/LandingPage';
|
import LandingPage from './pages/LandingPage';
|
||||||
import UserDashboard from './pages/UserDashboard';
|
import UserDashboard from './pages/UserDashboard';
|
||||||
|
|
@ -13,6 +15,7 @@ import OperatorDashboard from './pages/OperatorDashboard';
|
||||||
function App() {
|
function App() {
|
||||||
return (
|
return (
|
||||||
<ThemeProvider>
|
<ThemeProvider>
|
||||||
|
<ScrollParticles />
|
||||||
<AuthProvider>
|
<AuthProvider>
|
||||||
<Router>
|
<Router>
|
||||||
<Routes>
|
<Routes>
|
||||||
|
|
|
||||||
|
|
@ -1,200 +1,5 @@
|
||||||
import React from 'react';
|
import AboutSection from "@/components/ui/about-section";
|
||||||
import { motion } from 'framer-motion';
|
|
||||||
import { fadeIn, slideUp, slideLeft, staggerChildren } from '../utils/motionVariants';
|
|
||||||
|
|
||||||
const About = () => {
|
export default function About() {
|
||||||
const features = [
|
return <AboutSection />;
|
||||||
{
|
|
||||||
icon: (
|
|
||||||
<svg className="w-8 h-8" fill="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path d="M12 1L3 5v6c0 5.55 3.84 10.74 9 12 5.16-1.26 9-6.45 9-12V5l-9-4z" />
|
|
||||||
</svg>
|
|
||||||
),
|
|
||||||
title: 'Perlindungan',
|
|
||||||
description: 'Melindungi korban dan saksi dari segala bentuk ancaman atau intimidasi'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: (
|
|
||||||
<svg className="w-8 h-8" fill="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z" />
|
|
||||||
</svg>
|
|
||||||
),
|
|
||||||
title: 'Pendampingan',
|
|
||||||
description: 'Memberikan dukungan psikologis dan hukum yang dibutuhkan'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: (
|
|
||||||
<svg className="w-8 h-8" fill="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path d="M18 8h-1V6c0-2.76-2.24-5-5-5S7 3.24 7 6v2H6c-1.1 0-2 .9-2 2v10c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V10c0-1.1-.9-2-2-2zm-6 9c-1.1 0-2-.9-2-2s.9-2 2-2 2 .9 2 2-.9 2-2 2zm3.1-9H8.9V6c0-1.71 1.39-3.1 3.1-3.1 1.71 0 3.1 1.39 3.1 3.1v2z" />
|
|
||||||
</svg>
|
|
||||||
),
|
|
||||||
title: 'Kerahasiaan',
|
|
||||||
description: 'Menjaga identitas dan informasi pelapor dengan ketat'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: (
|
|
||||||
<svg className="w-8 h-8" fill="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z" />
|
|
||||||
</svg>
|
|
||||||
),
|
|
||||||
title: 'Keadilan',
|
|
||||||
description: 'Memastikan proses yang adil dan transparan untuk semua pihak'
|
|
||||||
}
|
}
|
||||||
];
|
|
||||||
|
|
||||||
return (
|
|
||||||
<section id="about" className="py-20 bg-gradient-to-br from-slate-50 via-indigo-50/60 to-purple-50 relative overflow-hidden">
|
|
||||||
{/* Background Pattern */}
|
|
||||||
<div className="absolute inset-0 opacity-5">
|
|
||||||
<div className="absolute top-10 right-10 w-64 h-64 bg-primary rounded-full blur-3xl"></div>
|
|
||||||
<div className="absolute bottom-10 left-10 w-96 h-96 bg-accent rounded-full blur-3xl"></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 relative z-10">
|
|
||||||
{/* Section Header */}
|
|
||||||
<motion.div
|
|
||||||
className="text-center mb-20"
|
|
||||||
variants={fadeIn}
|
|
||||||
initial="hidden"
|
|
||||||
whileInView="visible"
|
|
||||||
viewport={{ once: true }}
|
|
||||||
>
|
|
||||||
<motion.h2
|
|
||||||
className="text-4xl md:text-5xl font-bold text-[#191970] mb-6"
|
|
||||||
variants={slideUp}
|
|
||||||
initial="hidden"
|
|
||||||
whileInView="visible"
|
|
||||||
viewport={{ once: true }}
|
|
||||||
>
|
|
||||||
Tentang <span className="text-indigo-500">Satgas PPKPT</span>
|
|
||||||
</motion.h2>
|
|
||||||
<motion.p
|
|
||||||
className="text-xl text-slate-500 max-w-3xl mx-auto leading-relaxed"
|
|
||||||
variants={fadeIn}
|
|
||||||
initial="hidden"
|
|
||||||
whileInView="visible"
|
|
||||||
viewport={{ once: true }}
|
|
||||||
transition={{ delay: 0.2 }}
|
|
||||||
>
|
|
||||||
Satuan Tugas Pencegahan dan Penanganan Kekerasan Seksual Politeknik Negeri Jember
|
|
||||||
</motion.p>
|
|
||||||
</motion.div>
|
|
||||||
|
|
||||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-16 items-start">
|
|
||||||
{/* Left Content - Logo and Description */}
|
|
||||||
<motion.div
|
|
||||||
className="space-y-8"
|
|
||||||
variants={slideLeft}
|
|
||||||
initial="hidden"
|
|
||||||
whileInView="visible"
|
|
||||||
viewport={{ once: true }}
|
|
||||||
>
|
|
||||||
|
|
||||||
|
|
||||||
<motion.div
|
|
||||||
className="space-y-6"
|
|
||||||
variants={fadeIn}
|
|
||||||
initial="hidden"
|
|
||||||
whileInView="visible"
|
|
||||||
viewport={{ once: true }}
|
|
||||||
transition={{ delay: 0.3 }}
|
|
||||||
>
|
|
||||||
<h3 className="text-2xl font-bold text-[#191970]">
|
|
||||||
Layanan Pengaduan dan Pendampingan Terpercaya
|
|
||||||
</h3>
|
|
||||||
<p className="text-lg text-slate-500 leading-relaxed">
|
|
||||||
PolijeCare merupakan kanal resmi pengaduan Satgas PPKPT Politeknik Negeri Jember yang menangani laporan kekerasan seksual secara empati, profesional, dan menjaga kerahasiaan.
|
|
||||||
</p>
|
|
||||||
<p className="text-lg text-slate-500 leading-relaxed">
|
|
||||||
Kami berkomitmen untuk menciptakan lingkungan kampus yang <span className="text-[#191970] font-semibold">aman</span>, <span className="text-[#191970] font-semibold">mendukung</span>, dan <span className="text-[#191970] font-semibold">bebas dari kekerasan seksual</span> bagi seluruh sivitas akademika.
|
|
||||||
</p>
|
|
||||||
</motion.div>
|
|
||||||
|
|
||||||
{/* Stats */}
|
|
||||||
<motion.div
|
|
||||||
className="grid grid-cols-2 gap-6 pt-6"
|
|
||||||
variants={fadeIn}
|
|
||||||
initial="hidden"
|
|
||||||
whileInView="visible"
|
|
||||||
viewport={{ once: true }}
|
|
||||||
transition={{ delay: 0.4 }}
|
|
||||||
>
|
|
||||||
<div className="text-center p-4 bg-white/70 backdrop-blur-sm rounded-2xl shadow-sm border border-white/50">
|
|
||||||
<div className="text-3xl font-bold text-[#191970] mb-2">24/7</div>
|
|
||||||
<div className="text-sm text-slate-500 font-medium">Layanan Darurat</div>
|
|
||||||
</div>
|
|
||||||
<div className="text-center p-4 bg-white/70 backdrop-blur-sm rounded-2xl shadow-sm border border-white/50">
|
|
||||||
<div className="text-3xl font-bold text-indigo-500 mb-2">100%</div>
|
|
||||||
<div className="text-sm text-slate-500 font-medium">Rahasia Terjamin</div>
|
|
||||||
</div>
|
|
||||||
</motion.div>
|
|
||||||
</motion.div>
|
|
||||||
|
|
||||||
{/* Right Content - Features Grid */}
|
|
||||||
<motion.div
|
|
||||||
className="space-y-6"
|
|
||||||
variants={fadeIn}
|
|
||||||
initial="hidden"
|
|
||||||
whileInView="visible"
|
|
||||||
viewport={{ once: true }}
|
|
||||||
transition={{ delay: 0.2 }}
|
|
||||||
>
|
|
||||||
<motion.div
|
|
||||||
className="grid grid-cols-1 sm:grid-cols-2 gap-6"
|
|
||||||
variants={staggerChildren}
|
|
||||||
initial="hidden"
|
|
||||||
whileInView="visible"
|
|
||||||
viewport={{ once: true }}
|
|
||||||
>
|
|
||||||
{features.map((feature, index) => (
|
|
||||||
<motion.div
|
|
||||||
key={feature.title}
|
|
||||||
className="bg-white/70 backdrop-blur-sm rounded-2xl p-6 shadow-sm border border-white/50 hover:shadow-md hover:bg-white/90 transition-all duration-300 hover:-translate-y-1 group"
|
|
||||||
variants={fadeIn}
|
|
||||||
initial="hidden"
|
|
||||||
whileInView="visible"
|
|
||||||
viewport={{ once: true }}
|
|
||||||
transition={{ delay: 0.1 * index }}
|
|
||||||
whileHover={{ scale: 1.02 }}
|
|
||||||
>
|
|
||||||
<div className="flex items-start space-x-4">
|
|
||||||
<div className="flex-shrink-0 w-12 h-12 bg-gradient-to-br from-indigo-100 to-purple-100 rounded-xl flex items-center justify-center text-[#191970] group-hover:from-indigo-200 group-hover:to-purple-200 transition-all duration-300">
|
|
||||||
{feature.icon}
|
|
||||||
</div>
|
|
||||||
<div className="flex-1">
|
|
||||||
<h4 className="text-lg font-semibold text-[#191970] mb-2 group-hover:text-indigo-600 transition-colors">
|
|
||||||
{feature.title}
|
|
||||||
</h4>
|
|
||||||
<p className="text-slate-500 text-sm leading-relaxed">
|
|
||||||
{feature.description}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</motion.div>
|
|
||||||
))}
|
|
||||||
</motion.div>
|
|
||||||
|
|
||||||
{/* Quote */}
|
|
||||||
<motion.blockquote
|
|
||||||
className="bg-gradient-to-r from-indigo-50/80 to-purple-50/80 backdrop-blur-sm rounded-2xl p-6 border-l-4 border-[#191970]"
|
|
||||||
variants={fadeIn}
|
|
||||||
initial="hidden"
|
|
||||||
whileInView="visible"
|
|
||||||
viewport={{ once: true }}
|
|
||||||
transition={{ delay: 0.6 }}
|
|
||||||
>
|
|
||||||
<p className="text-lg text-slate-600 italic font-medium leading-relaxed">
|
|
||||||
"Setiap individu berhak mendapatkan perlindungan dan rasa aman dalam menempuh pendidikan. Mari bersama-sama menjaga kampus kita sebagai tempat yang aman dan mendukung bagi semua."
|
|
||||||
</p>
|
|
||||||
<footer className="mt-4 text-sm text-[#191970] font-semibold">
|
|
||||||
— Satgas PPKPT Polije
|
|
||||||
</footer>
|
|
||||||
</motion.blockquote>
|
|
||||||
</motion.div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default About;
|
|
||||||
|
|
|
||||||
|
|
@ -168,7 +168,7 @@ const Navbar = () => {
|
||||||
transition={{ duration: 0.25, ease: [0.25, 0.46, 0.45, 0.94] }}
|
transition={{ duration: 0.25, ease: [0.25, 0.46, 0.45, 0.94] }}
|
||||||
className="fixed top-0 left-0 right-0 z-50 transition-colors duration-300 bg-white/50 backdrop-blur-md border-b border-white/20 shadow-sm will-change-transform"
|
className="fixed top-0 left-0 right-0 z-50 transition-colors duration-300 bg-white/50 backdrop-blur-md border-b border-white/20 shadow-sm will-change-transform"
|
||||||
>
|
>
|
||||||
<div className="w-full px-4">
|
<div className="w-full px-8 lg:px-12">
|
||||||
<div className="flex items-center justify-between h-20">
|
<div className="flex items-center justify-between h-20">
|
||||||
{/* Logo Section - Left */}
|
{/* Logo Section - Left */}
|
||||||
<Link to="/" className="flex items-center space-x-2 cursor-default">
|
<Link to="/" className="flex items-center space-x-2 cursor-default">
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,369 @@
|
||||||
|
"use client";
|
||||||
|
|
||||||
|
import { TimelineContent } from "@/components/ui/timeline-animation";
|
||||||
|
import { VerticalCutReveal } from "@/components/ui/vertical-cut-reveal";
|
||||||
|
import { ArrowRight, Shield, Heart, Lock, Scale, Phone } from "lucide-react";
|
||||||
|
import { useRef, useEffect, useState } from "react";
|
||||||
|
import { useInView, motion, AnimatePresence } from "framer-motion";
|
||||||
|
|
||||||
|
export default function AboutSection() {
|
||||||
|
const heroRef = useRef(null);
|
||||||
|
const counterRef = useRef(null);
|
||||||
|
const isCounterInView = useInView(counterRef, { once: true });
|
||||||
|
const [count, setCount] = useState(0);
|
||||||
|
|
||||||
|
// Carousel state
|
||||||
|
const heroImages = ["/Gambar1.jpg", "/Gambar2.jpeg", "/Gambar3.jpeg"];
|
||||||
|
const [slideIndex, setSlideIndex] = useState(0);
|
||||||
|
const [progress, setProgress] = useState(0);
|
||||||
|
const SLIDE_DURATION = 5000; // 5 seconds
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const progressInterval = setInterval(() => {
|
||||||
|
setProgress((prev) => {
|
||||||
|
if (prev >= 100) return 0;
|
||||||
|
return prev + (100 / (SLIDE_DURATION / 50));
|
||||||
|
});
|
||||||
|
}, 50);
|
||||||
|
const slideTimer = setInterval(() => {
|
||||||
|
setSlideIndex((prev) => (prev + 1) % heroImages.length);
|
||||||
|
setProgress(0);
|
||||||
|
}, SLIDE_DURATION);
|
||||||
|
return () => {
|
||||||
|
clearInterval(progressInterval);
|
||||||
|
clearInterval(slideTimer);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isCounterInView) return;
|
||||||
|
let start = 0;
|
||||||
|
const end = 100;
|
||||||
|
const duration = 2000;
|
||||||
|
const stepTime = duration / end;
|
||||||
|
const timer = setInterval(() => {
|
||||||
|
start += 1;
|
||||||
|
setCount(start);
|
||||||
|
if (start >= end) clearInterval(timer);
|
||||||
|
}, stepTime);
|
||||||
|
return () => clearInterval(timer);
|
||||||
|
}, [isCounterInView]);
|
||||||
|
|
||||||
|
const revealVariants = {
|
||||||
|
visible: (i) => ({
|
||||||
|
y: 0,
|
||||||
|
opacity: 1,
|
||||||
|
filter: "blur(0px)",
|
||||||
|
transition: {
|
||||||
|
delay: i * 0.15,
|
||||||
|
duration: 0.3,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
hidden: {
|
||||||
|
filter: "blur(10px)",
|
||||||
|
y: -20,
|
||||||
|
opacity: 0,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const scaleVariants = {
|
||||||
|
visible: (i) => ({
|
||||||
|
opacity: 1,
|
||||||
|
filter: "blur(0px)",
|
||||||
|
transition: {
|
||||||
|
delay: i * 0.15,
|
||||||
|
duration: 0.3,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
hidden: {
|
||||||
|
filter: "blur(10px)",
|
||||||
|
opacity: 0,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section className="min-h-screen py-16 bg-[#f9f9f9] flex items-center" ref={heroRef} id="about">
|
||||||
|
<div className="max-w-[90rem] mx-auto w-full px-8 lg:px-12">
|
||||||
|
<div className="relative">
|
||||||
|
{/* Header */}
|
||||||
|
<div className="flex justify-between items-center mb-8 w-[85%] absolute lg:top-4 md:top-0 sm:-top-2 -top-3 z-10">
|
||||||
|
<div className="flex items-center gap-2 text-xl">
|
||||||
|
<span className="text-[#191970] animate-spin">✱</span>
|
||||||
|
<TimelineContent
|
||||||
|
as="span"
|
||||||
|
animationNum={0}
|
||||||
|
timelineRef={heroRef}
|
||||||
|
customVariants={revealVariants}
|
||||||
|
className="text-sm font-medium text-gray-600"
|
||||||
|
>
|
||||||
|
TENTANG KAMI
|
||||||
|
</TimelineContent>
|
||||||
|
</div>
|
||||||
|
<div className="flex gap-3">
|
||||||
|
<TimelineContent
|
||||||
|
as="div"
|
||||||
|
animationNum={0}
|
||||||
|
timelineRef={heroRef}
|
||||||
|
customVariants={revealVariants}
|
||||||
|
className="md:w-8 md:h-8 sm:w-6 w-5 sm:h-6 h-5 border border-indigo-200 bg-indigo-50 rounded-lg flex items-center justify-center"
|
||||||
|
>
|
||||||
|
<Shield className="w-4 h-4 text-[#191970]" />
|
||||||
|
</TimelineContent>
|
||||||
|
<TimelineContent
|
||||||
|
as="div"
|
||||||
|
animationNum={1}
|
||||||
|
timelineRef={heroRef}
|
||||||
|
customVariants={revealVariants}
|
||||||
|
className="md:w-8 md:h-8 sm:w-6 w-5 sm:h-6 h-5 border border-indigo-200 bg-indigo-50 rounded-lg flex items-center justify-center"
|
||||||
|
>
|
||||||
|
<Heart className="w-4 h-4 text-[#191970]" />
|
||||||
|
</TimelineContent>
|
||||||
|
<TimelineContent
|
||||||
|
as="div"
|
||||||
|
animationNum={2}
|
||||||
|
timelineRef={heroRef}
|
||||||
|
customVariants={revealVariants}
|
||||||
|
className="md:w-8 md:h-8 sm:w-6 w-5 sm:h-6 h-5 border border-indigo-200 bg-indigo-50 rounded-lg flex items-center justify-center"
|
||||||
|
>
|
||||||
|
<Lock className="w-4 h-4 text-[#191970]" />
|
||||||
|
</TimelineContent>
|
||||||
|
<TimelineContent
|
||||||
|
as="div"
|
||||||
|
animationNum={3}
|
||||||
|
timelineRef={heroRef}
|
||||||
|
customVariants={revealVariants}
|
||||||
|
className="md:w-8 md:h-8 sm:w-6 w-5 sm:h-6 h-5 border border-indigo-200 bg-indigo-50 rounded-lg flex items-center justify-center"
|
||||||
|
>
|
||||||
|
<Scale className="w-4 h-4 text-[#191970]" />
|
||||||
|
</TimelineContent>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Hero Image with clip path */}
|
||||||
|
<TimelineContent
|
||||||
|
as="figure"
|
||||||
|
animationNum={4}
|
||||||
|
timelineRef={heroRef}
|
||||||
|
customVariants={scaleVariants}
|
||||||
|
className="relative group"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
className="w-full"
|
||||||
|
width={"100%"}
|
||||||
|
height={"100%"}
|
||||||
|
viewBox="0 0 100 40"
|
||||||
|
>
|
||||||
|
<defs>
|
||||||
|
<clipPath
|
||||||
|
id="clip-inverted"
|
||||||
|
clipPathUnits={"userSpaceOnUse"}
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
transform="scale(100, 40)"
|
||||||
|
d="M0.0998072 1H0.422076H0.749756C0.767072 1 0.774207 0.961783 0.77561 0.942675V0.807325C0.777053 0.743631 0.791844 0.731953 0.799059 0.734076H0.969813C0.996268 0.730255 1.00088 0.693206 0.999875 0.675159V0.0700637C0.999875 0.0254777 0.985045 0.00477707 0.977629 0H0.902473C0.854975 0 0.890448 0.138535 0.850165 0.138535H0.0204424C0.00408849 0.142357 0 0.180467 0 0.199045V0.410828C0 0.449045 0.0136283 0.46603 0.0204424 0.469745H0.0523086C0.0696245 0.471019 0.0735527 0.497877 0.0733523 0.511146V0.915605C0.0723903 0.983121 0.090588 1 0.0998072 1Z"
|
||||||
|
fill="#D9D9D9"
|
||||||
|
/>
|
||||||
|
</clipPath>
|
||||||
|
</defs>
|
||||||
|
<g clipPath="url(#clip-inverted)">
|
||||||
|
<AnimatePresence>
|
||||||
|
<motion.image
|
||||||
|
key={slideIndex}
|
||||||
|
preserveAspectRatio="xMidYMid slice"
|
||||||
|
width={"100%"}
|
||||||
|
height={"100%"}
|
||||||
|
xlinkHref={heroImages[slideIndex]}
|
||||||
|
initial={{ x: 100 }}
|
||||||
|
animate={{ x: 0 }}
|
||||||
|
exit={{ x: -100 }}
|
||||||
|
transition={{ duration: 0.6, ease: "easeInOut" }}
|
||||||
|
/>
|
||||||
|
</AnimatePresence>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
{/* Slide progress indicators */}
|
||||||
|
<div className="absolute bottom-[3%] left-[10%] flex gap-3 items-center z-10">
|
||||||
|
{heroImages.map((_, i) => (
|
||||||
|
<div
|
||||||
|
key={i}
|
||||||
|
className="h-[6px] rounded-full overflow-hidden cursor-pointer shadow-sm transition-all duration-300"
|
||||||
|
style={{
|
||||||
|
width: i === slideIndex ? '3rem' : '1.5rem',
|
||||||
|
backgroundColor: 'rgba(255,255,255,0.4)'
|
||||||
|
}}
|
||||||
|
onClick={() => { setSlideIndex(i); setProgress(0); }}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="h-full rounded-full transition-all duration-100 ease-linear"
|
||||||
|
style={{
|
||||||
|
width: i === slideIndex ? `${progress}%` : i < slideIndex ? '100%' : '0%',
|
||||||
|
backgroundColor: '#fff',
|
||||||
|
boxShadow: '0 0 4px rgba(0,0,0,0.2)'
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
{/* 100% stat in the white cutout */}
|
||||||
|
<TimelineContent
|
||||||
|
as="div"
|
||||||
|
animationNum={5}
|
||||||
|
timelineRef={heroRef}
|
||||||
|
customVariants={revealVariants}
|
||||||
|
className="absolute bottom-[10%] right-[0.5%] text-right"
|
||||||
|
>
|
||||||
|
<div className="flex items-baseline gap-2 justify-end" ref={counterRef}>
|
||||||
|
<span className="text-[#191970] font-extrabold text-2xl sm:text-3xl lg:text-4xl leading-none" style={{ fontFamily: "'Inter', sans-serif" }}>{count}%</span>
|
||||||
|
<span className="text-gray-500 text-2xl sm:text-3xl lg:text-4xl uppercase tracking-wider font-normal leading-none">Rahasia</span>
|
||||||
|
</div>
|
||||||
|
<span className="text-gray-400 text-xs sm:text-sm mt-0.5 block text-left">kerahasiaan terjamin</span>
|
||||||
|
</TimelineContent>
|
||||||
|
</TimelineContent>
|
||||||
|
|
||||||
|
{/* Stats */}
|
||||||
|
<div className="flex flex-wrap items-center justify-start gap-6 py-4 text-sm">
|
||||||
|
<TimelineContent
|
||||||
|
as="div"
|
||||||
|
animationNum={5}
|
||||||
|
timelineRef={heroRef}
|
||||||
|
customVariants={revealVariants}
|
||||||
|
className="flex items-center gap-2 sm:text-base text-xs"
|
||||||
|
>
|
||||||
|
<span className="text-[#191970] font-bold">24/7</span>
|
||||||
|
<span className="text-gray-600">layanan aktif</span>
|
||||||
|
</TimelineContent>
|
||||||
|
<span className="text-gray-300">|</span>
|
||||||
|
<TimelineContent
|
||||||
|
as="div"
|
||||||
|
animationNum={7}
|
||||||
|
timelineRef={heroRef}
|
||||||
|
customVariants={revealVariants}
|
||||||
|
className="flex items-center gap-2 sm:text-base text-xs"
|
||||||
|
>
|
||||||
|
<span className="text-[#191970] font-bold">Gratis</span>
|
||||||
|
<span className="text-gray-600">tanpa biaya</span>
|
||||||
|
</TimelineContent>
|
||||||
|
<span className="text-gray-300">|</span>
|
||||||
|
<TimelineContent
|
||||||
|
as="div"
|
||||||
|
animationNum={8}
|
||||||
|
timelineRef={heroRef}
|
||||||
|
customVariants={revealVariants}
|
||||||
|
className="flex items-center gap-2 sm:text-base text-xs"
|
||||||
|
>
|
||||||
|
<span className="text-[#191970] font-bold">Cepat</span>
|
||||||
|
<span className="text-gray-600">respon tanggap</span>
|
||||||
|
</TimelineContent>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Main Content */}
|
||||||
|
<div className="grid md:grid-cols-3 gap-8">
|
||||||
|
<div className="md:col-span-2">
|
||||||
|
<h2 className="sm:text-4xl md:text-5xl text-2xl !leading-[110%] font-semibold text-gray-900 mb-8">
|
||||||
|
<VerticalCutReveal
|
||||||
|
splitBy="words"
|
||||||
|
staggerDuration={0.1}
|
||||||
|
staggerFrom="first"
|
||||||
|
reverse={true}
|
||||||
|
transition={{
|
||||||
|
type: "spring",
|
||||||
|
stiffness: 250,
|
||||||
|
damping: 30,
|
||||||
|
delay: 1,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Menciptakan Kampus Aman & Bermartabat.
|
||||||
|
</VerticalCutReveal>
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<TimelineContent
|
||||||
|
as="div"
|
||||||
|
animationNum={9}
|
||||||
|
timelineRef={heroRef}
|
||||||
|
customVariants={revealVariants}
|
||||||
|
className="grid md:grid-cols-2 gap-8 text-gray-600"
|
||||||
|
>
|
||||||
|
<TimelineContent
|
||||||
|
as="div"
|
||||||
|
animationNum={10}
|
||||||
|
timelineRef={heroRef}
|
||||||
|
customVariants={revealVariants}
|
||||||
|
className="sm:text-base text-xs"
|
||||||
|
>
|
||||||
|
<p className="leading-relaxed text-justify">
|
||||||
|
PolijeCare merupakan kanal resmi pengaduan Satgas Pencegahan
|
||||||
|
dan Penanganan Kekerasan Seksual (PPKPT) Politeknik Negeri
|
||||||
|
Jember. Kami hadir sebagai garda terdepan dalam melindungi
|
||||||
|
korban dan memberikan pendampingan profesional.
|
||||||
|
</p>
|
||||||
|
</TimelineContent>
|
||||||
|
<TimelineContent
|
||||||
|
as="div"
|
||||||
|
animationNum={11}
|
||||||
|
timelineRef={heroRef}
|
||||||
|
customVariants={revealVariants}
|
||||||
|
className="sm:text-base text-xs"
|
||||||
|
>
|
||||||
|
<p className="leading-relaxed text-justify">
|
||||||
|
Setiap laporan ditangani secara empatik, profesional, dan
|
||||||
|
menjaga kerahasiaan penuh. Kami percaya bahwa setiap individu
|
||||||
|
berhak mendapat rasa aman dalam menempuh pendidikan tanpa
|
||||||
|
ancaman kekerasan seksual.
|
||||||
|
</p>
|
||||||
|
</TimelineContent>
|
||||||
|
</TimelineContent>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="md:col-span-1">
|
||||||
|
<div className="text-right">
|
||||||
|
<TimelineContent
|
||||||
|
as="div"
|
||||||
|
animationNum={12}
|
||||||
|
timelineRef={heroRef}
|
||||||
|
customVariants={revealVariants}
|
||||||
|
className="text-[#191970] text-2xl font-bold mb-2"
|
||||||
|
>
|
||||||
|
SATGAS PPKPT
|
||||||
|
</TimelineContent>
|
||||||
|
<TimelineContent
|
||||||
|
as="div"
|
||||||
|
animationNum={13}
|
||||||
|
timelineRef={heroRef}
|
||||||
|
customVariants={revealVariants}
|
||||||
|
className="text-gray-600 text-sm mb-8"
|
||||||
|
>
|
||||||
|
Politeknik Negeri Jember
|
||||||
|
</TimelineContent>
|
||||||
|
|
||||||
|
<TimelineContent
|
||||||
|
as="div"
|
||||||
|
animationNum={14}
|
||||||
|
timelineRef={heroRef}
|
||||||
|
customVariants={revealVariants}
|
||||||
|
className="mb-6"
|
||||||
|
>
|
||||||
|
<p className="text-gray-900 font-medium mb-4">
|
||||||
|
Siap untuk melaporkan atau butuh bantuan? Kami siap mendengarkan Anda.
|
||||||
|
</p>
|
||||||
|
</TimelineContent>
|
||||||
|
|
||||||
|
<TimelineContent
|
||||||
|
as="a"
|
||||||
|
animationNum={15}
|
||||||
|
timelineRef={heroRef}
|
||||||
|
customVariants={revealVariants}
|
||||||
|
href="#contact"
|
||||||
|
className="group relative inline-flex w-fit ml-auto items-center gap-2 px-7 py-3 bg-[#191970] text-white rounded-full font-semibold text-sm cursor-pointer transition-all duration-300 hover:bg-[#1a237e] hover:text-white hover:shadow-[0_4px_20px_rgba(26,35,126,0.5)] hover:gap-3"
|
||||||
|
>
|
||||||
|
<Phone className="w-4 h-4 opacity-60" />
|
||||||
|
HUBUNGI KAMI
|
||||||
|
<ArrowRight className="w-4 h-4 transition-transform duration-300 group-hover:translate-x-1" />
|
||||||
|
</TimelineContent>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -44,8 +44,7 @@ export function ExpandableTabs({
|
||||||
}, [activeTab]);
|
}, [activeTab]);
|
||||||
|
|
||||||
useOnClickOutside(outsideClickRef, () => {
|
useOnClickOutside(outsideClickRef, () => {
|
||||||
setSelected(null);
|
// Keep the active tab visible — don't clear selection on outside click
|
||||||
onChange?.(null);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleSelect = (index) => {
|
const handleSelect = (index) => {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,92 @@
|
||||||
|
import { useState, useRef, useEffect } from "react";
|
||||||
|
import { motion, AnimatePresence, useScroll, useMotionValueEvent } from "framer-motion";
|
||||||
|
import { Plus, Heart, Star } from "lucide-react";
|
||||||
|
|
||||||
|
export function ScrollParticles() {
|
||||||
|
const { scrollY } = useScroll();
|
||||||
|
const [particles, setParticles] = useState([]);
|
||||||
|
const [windowHeight, setWindowHeight] = useState(0);
|
||||||
|
const lastY = useRef(0);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (typeof window !== "undefined") {
|
||||||
|
setWindowHeight(window.innerHeight);
|
||||||
|
const handleResize = () => setWindowHeight(window.innerHeight);
|
||||||
|
window.addEventListener("resize", handleResize);
|
||||||
|
return () => window.removeEventListener("resize", handleResize);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useMotionValueEvent(scrollY, "change", (latest) => {
|
||||||
|
const diff = Math.abs(latest - lastY.current);
|
||||||
|
const direction = latest > lastY.current ? 1 : -1;
|
||||||
|
|
||||||
|
// Spawn particles if scrolling fast enough
|
||||||
|
if (diff > 5 && windowHeight > 0) {
|
||||||
|
const scrollHeight = document.documentElement.scrollHeight - windowHeight;
|
||||||
|
const progress = latest / scrollHeight;
|
||||||
|
|
||||||
|
// Calculate approximate scrollbar thumb position (center of thumb)
|
||||||
|
const thumbY = progress * (windowHeight - 40);
|
||||||
|
|
||||||
|
const shapes = ["plus", "heart", "star"];
|
||||||
|
const randomShape = shapes[Math.floor(Math.random() * shapes.length)];
|
||||||
|
|
||||||
|
const newParticle = {
|
||||||
|
id: Date.now() + Math.random(),
|
||||||
|
top: thumbY + (Math.random() * 40 - 20), // Wider spread
|
||||||
|
left: -Math.random() * 20 - 10,
|
||||||
|
size: Math.random() * 14 + 10, // Larger size: 10px - 24px
|
||||||
|
color: Math.random() > 0.5 ? "#191970" : "#4C6EF5",
|
||||||
|
shape: randomShape,
|
||||||
|
rotation: Math.random() * 360,
|
||||||
|
velocity: {
|
||||||
|
x: -Math.random() * 30 - 20, // Faster drift left
|
||||||
|
y: (Math.random() * 30 - 15) + (direction * 8)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
setParticles(prev => [...prev.slice(-20), newParticle]);
|
||||||
|
}
|
||||||
|
|
||||||
|
lastY.current = latest;
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="fixed top-0 right-0 w-4 h-full pointer-events-none z-[9999]">
|
||||||
|
<AnimatePresence>
|
||||||
|
{particles.map((particle) => (
|
||||||
|
<motion.div
|
||||||
|
key={particle.id}
|
||||||
|
initial={{
|
||||||
|
opacity: 1,
|
||||||
|
x: 0,
|
||||||
|
y: particle.top,
|
||||||
|
scale: 0,
|
||||||
|
rotate: particle.rotation
|
||||||
|
}}
|
||||||
|
animate={{
|
||||||
|
opacity: 0,
|
||||||
|
x: particle.velocity.x,
|
||||||
|
y: particle.top + particle.velocity.y,
|
||||||
|
scale: 1,
|
||||||
|
rotate: particle.rotation + 180
|
||||||
|
}}
|
||||||
|
exit={{ opacity: 0 }}
|
||||||
|
transition={{ duration: 0.8, ease: "easeOut" }}
|
||||||
|
className="absolute right-1 flex items-center justify-center"
|
||||||
|
style={{
|
||||||
|
width: particle.size,
|
||||||
|
height: particle.size,
|
||||||
|
color: particle.color,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{particle.shape === "plus" && <Plus size={particle.size} strokeWidth={3} />}
|
||||||
|
{particle.shape === "heart" && <Heart size={particle.size} fill={particle.color} strokeWidth={0} />}
|
||||||
|
{particle.shape === "star" && <Star size={particle.size} fill={particle.color} strokeWidth={0} />}
|
||||||
|
</motion.div>
|
||||||
|
))}
|
||||||
|
</AnimatePresence>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,65 @@
|
||||||
|
"use client";
|
||||||
|
|
||||||
|
import React, { useRef, createElement, forwardRef } from "react";
|
||||||
|
import {
|
||||||
|
motion,
|
||||||
|
useInView,
|
||||||
|
useScroll,
|
||||||
|
useTransform,
|
||||||
|
} from "framer-motion";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
|
const TimelineContent = forwardRef(
|
||||||
|
(
|
||||||
|
{
|
||||||
|
as = "div",
|
||||||
|
children,
|
||||||
|
className,
|
||||||
|
animationNum = 0,
|
||||||
|
timelineRef,
|
||||||
|
customVariants,
|
||||||
|
...props
|
||||||
|
},
|
||||||
|
ref
|
||||||
|
) => {
|
||||||
|
const defaultVariants = {
|
||||||
|
hidden: {
|
||||||
|
opacity: 0,
|
||||||
|
y: 20,
|
||||||
|
filter: "blur(8px)",
|
||||||
|
},
|
||||||
|
visible: (i) => ({
|
||||||
|
opacity: 1,
|
||||||
|
y: 0,
|
||||||
|
filter: "blur(0px)",
|
||||||
|
transition: {
|
||||||
|
delay: i * 0.3,
|
||||||
|
duration: 0.5,
|
||||||
|
ease: "easeOut",
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
const variants = customVariants || defaultVariants;
|
||||||
|
const isInView = useInView(timelineRef, { once: true, amount: 0.1 });
|
||||||
|
const MotionComponent = motion[as] || motion.div;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<MotionComponent
|
||||||
|
ref={ref}
|
||||||
|
className={cn(className)}
|
||||||
|
initial="hidden"
|
||||||
|
animate={isInView ? "visible" : "hidden"}
|
||||||
|
variants={variants}
|
||||||
|
custom={animationNum}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</MotionComponent>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
TimelineContent.displayName = "TimelineContent";
|
||||||
|
|
||||||
|
export { TimelineContent };
|
||||||
|
|
@ -0,0 +1,189 @@
|
||||||
|
"use client";
|
||||||
|
|
||||||
|
import {
|
||||||
|
forwardRef,
|
||||||
|
useCallback,
|
||||||
|
useEffect,
|
||||||
|
useImperativeHandle,
|
||||||
|
useMemo,
|
||||||
|
useRef,
|
||||||
|
useState,
|
||||||
|
} from "react";
|
||||||
|
import { motion } from "framer-motion";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
|
const VerticalCutReveal = forwardRef(
|
||||||
|
(
|
||||||
|
{
|
||||||
|
children,
|
||||||
|
reverse = false,
|
||||||
|
transition = {
|
||||||
|
type: "spring",
|
||||||
|
stiffness: 190,
|
||||||
|
damping: 22,
|
||||||
|
},
|
||||||
|
splitBy = "words",
|
||||||
|
staggerDuration = 0.2,
|
||||||
|
staggerFrom = "first",
|
||||||
|
containerClassName,
|
||||||
|
wordLevelClassName,
|
||||||
|
elementLevelClassName,
|
||||||
|
onClick,
|
||||||
|
onStart,
|
||||||
|
onComplete,
|
||||||
|
autoStart = true,
|
||||||
|
...props
|
||||||
|
},
|
||||||
|
ref
|
||||||
|
) => {
|
||||||
|
const containerRef = useRef(null);
|
||||||
|
const text =
|
||||||
|
typeof children === "string" ? children : children?.toString() || "";
|
||||||
|
const [isAnimating, setIsAnimating] = useState(false);
|
||||||
|
|
||||||
|
const splitIntoCharacters = (text) => {
|
||||||
|
if (typeof Intl !== "undefined" && "Segmenter" in Intl) {
|
||||||
|
const segmenter = new Intl.Segmenter("en", { granularity: "grapheme" });
|
||||||
|
return Array.from(segmenter.segment(text), ({ segment }) => segment);
|
||||||
|
}
|
||||||
|
return Array.from(text);
|
||||||
|
};
|
||||||
|
|
||||||
|
const elements = useMemo(() => {
|
||||||
|
const words = text.split(" ");
|
||||||
|
if (splitBy === "characters") {
|
||||||
|
return words.map((word, i) => ({
|
||||||
|
characters: splitIntoCharacters(word),
|
||||||
|
needsSpace: i !== words.length - 1,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
return splitBy === "words"
|
||||||
|
? text.split(" ")
|
||||||
|
: splitBy === "lines"
|
||||||
|
? text.split("\n")
|
||||||
|
: text.split(splitBy);
|
||||||
|
}, [text, splitBy]);
|
||||||
|
|
||||||
|
const getStaggerDelay = useCallback(
|
||||||
|
(index) => {
|
||||||
|
const total =
|
||||||
|
splitBy === "characters"
|
||||||
|
? elements.reduce(
|
||||||
|
(acc, word) =>
|
||||||
|
acc +
|
||||||
|
(typeof word === "string"
|
||||||
|
? 1
|
||||||
|
: word.characters.length + (word.needsSpace ? 1 : 0)),
|
||||||
|
0
|
||||||
|
)
|
||||||
|
: elements.length;
|
||||||
|
if (staggerFrom === "first") return index * staggerDuration;
|
||||||
|
if (staggerFrom === "last")
|
||||||
|
return (total - 1 - index) * staggerDuration;
|
||||||
|
if (staggerFrom === "center") {
|
||||||
|
const center = Math.floor(total / 2);
|
||||||
|
return Math.abs(center - index) * staggerDuration;
|
||||||
|
}
|
||||||
|
if (staggerFrom === "random") {
|
||||||
|
const randomIndex = Math.floor(Math.random() * total);
|
||||||
|
return Math.abs(randomIndex - index) * staggerDuration;
|
||||||
|
}
|
||||||
|
return Math.abs(staggerFrom - index) * staggerDuration;
|
||||||
|
},
|
||||||
|
[elements.length, staggerFrom, staggerDuration]
|
||||||
|
);
|
||||||
|
|
||||||
|
const startAnimation = useCallback(() => {
|
||||||
|
setIsAnimating(true);
|
||||||
|
onStart?.();
|
||||||
|
}, [onStart]);
|
||||||
|
|
||||||
|
useImperativeHandle(ref, () => ({
|
||||||
|
startAnimation,
|
||||||
|
reset: () => setIsAnimating(false),
|
||||||
|
}));
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (autoStart) {
|
||||||
|
startAnimation();
|
||||||
|
}
|
||||||
|
}, [autoStart]);
|
||||||
|
|
||||||
|
const variants = {
|
||||||
|
hidden: { y: reverse ? "-100%" : "100%" },
|
||||||
|
visible: (i) => ({
|
||||||
|
y: 0,
|
||||||
|
transition: {
|
||||||
|
...transition,
|
||||||
|
delay: (transition?.delay || 0) + getStaggerDelay(i),
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<span
|
||||||
|
className={cn(
|
||||||
|
containerClassName,
|
||||||
|
"flex flex-wrap whitespace-pre-wrap",
|
||||||
|
splitBy === "lines" && "flex-col"
|
||||||
|
)}
|
||||||
|
onClick={onClick}
|
||||||
|
ref={containerRef}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<span className="sr-only">{text}</span>
|
||||||
|
|
||||||
|
{(splitBy === "characters"
|
||||||
|
? elements
|
||||||
|
: elements.map((el, i) => ({
|
||||||
|
characters: [el],
|
||||||
|
needsSpace: i !== elements.length - 1,
|
||||||
|
}))
|
||||||
|
).map((wordObj, wordIndex, array) => {
|
||||||
|
const previousCharsCount = array
|
||||||
|
.slice(0, wordIndex)
|
||||||
|
.reduce((sum, word) => sum + word.characters.length, 0);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<span
|
||||||
|
key={wordIndex}
|
||||||
|
aria-hidden="true"
|
||||||
|
className={cn("inline-flex overflow-hidden", wordLevelClassName)}
|
||||||
|
>
|
||||||
|
{wordObj.characters.map((char, charIndex) => (
|
||||||
|
<span
|
||||||
|
className={cn(
|
||||||
|
elementLevelClassName,
|
||||||
|
"whitespace-pre-wrap relative"
|
||||||
|
)}
|
||||||
|
key={charIndex}
|
||||||
|
>
|
||||||
|
<motion.span
|
||||||
|
custom={previousCharsCount + charIndex}
|
||||||
|
initial="hidden"
|
||||||
|
animate={isAnimating ? "visible" : "hidden"}
|
||||||
|
variants={variants}
|
||||||
|
onAnimationComplete={
|
||||||
|
wordIndex === elements.length - 1 &&
|
||||||
|
charIndex === wordObj.characters.length - 1
|
||||||
|
? onComplete
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
className="inline-block"
|
||||||
|
>
|
||||||
|
{char}
|
||||||
|
</motion.span>
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
|
{wordObj.needsSpace && <span> </span>}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
VerticalCutReveal.displayName = "VerticalCutReveal";
|
||||||
|
|
||||||
|
export { VerticalCutReveal };
|
||||||
|
|
@ -641,3 +641,37 @@ body {
|
||||||
.dark .highlight-marker::before {
|
.dark .highlight-marker::before {
|
||||||
background: rgba(80, 100, 220, 0.85);
|
background: rgba(80, 100, 220, 0.85);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Custom Scrollbar */
|
||||||
|
::-webkit-scrollbar {
|
||||||
|
width: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-track {
|
||||||
|
background: #f1f1f1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark ::-webkit-scrollbar-track {
|
||||||
|
background: #1f2937;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-thumb {
|
||||||
|
background: #191970;
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: #101050;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Firefox Scrollbar */
|
||||||
|
@supports (scrollbar-color: auto) {
|
||||||
|
html {
|
||||||
|
scrollbar-width: thin;
|
||||||
|
scrollbar-color: #191970 #f1f1f1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark html {
|
||||||
|
scrollbar-color: #191970 #1f2937;
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue