new ux start
This commit is contained in:
160
components/ui/hero-section-2.tsx
Normal file
160
components/ui/hero-section-2.tsx
Normal file
@@ -0,0 +1,160 @@
|
||||
import React from 'react';
|
||||
import { cn } from "@/lib/utils";
|
||||
import { motion } from 'framer-motion';
|
||||
|
||||
// Icon component for contact details
|
||||
const InfoIcon = ({ type }: { type: 'website' | 'phone' | 'address' }) => {
|
||||
const icons = {
|
||||
website: (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="h-5 w-5 text-primary">
|
||||
<circle cx="12" cy="12" r="10"></circle>
|
||||
<line x1="2" x2="22" y1="12" y2="12"></line>
|
||||
<path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"></path>
|
||||
</svg>
|
||||
),
|
||||
phone: (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="h-5 w-5 text-primary">
|
||||
<path d="M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72 12.84 12.84 0 0 0 .7 2.81 2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45 12.84 12.84 0 0 0 2.81.7A2 2 0 0 1 22 16.92z"></path>
|
||||
</svg>
|
||||
),
|
||||
address: (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="h-5 w-5 text-primary">
|
||||
<path d="M20 10c0 6-8 12-8 12s-8-6-8-12a8 8 0 0 1 16 0Z"></path>
|
||||
<circle cx="12" cy="10" r="3"></circle>
|
||||
</svg>
|
||||
),
|
||||
};
|
||||
return <div className="mr-2 flex-shrink-0">{icons[type]}</div>;
|
||||
};
|
||||
|
||||
|
||||
// Prop types for the HeroSection component
|
||||
interface HeroSectionProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
logo?: {
|
||||
url: string;
|
||||
alt: string;
|
||||
text?: string;
|
||||
};
|
||||
slogan?: string;
|
||||
title: React.ReactNode;
|
||||
subtitle: string;
|
||||
callToAction: {
|
||||
text: string;
|
||||
href: string;
|
||||
};
|
||||
backgroundImage: string;
|
||||
contactInfo: {
|
||||
website: string;
|
||||
phone: string;
|
||||
address: string;
|
||||
};
|
||||
}
|
||||
|
||||
const HeroSection = React.forwardRef<HTMLDivElement, HeroSectionProps>(
|
||||
({ className, logo, slogan, title, subtitle, callToAction, backgroundImage, contactInfo, ...props }, ref) => {
|
||||
|
||||
// Animation variants for the container to orchestrate children animations
|
||||
const containerVariants = {
|
||||
hidden: { opacity: 0 },
|
||||
visible: {
|
||||
opacity: 1,
|
||||
transition: {
|
||||
staggerChildren: 0.15,
|
||||
delayChildren: 0.2,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
// Animation variants for individual text/UI elements
|
||||
const itemVariants = {
|
||||
hidden: { y: 20, opacity: 0 },
|
||||
visible: {
|
||||
y: 0,
|
||||
opacity: 1,
|
||||
transition: {
|
||||
duration: 0.5,
|
||||
ease: "easeOut",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
return (
|
||||
<motion.section
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"relative flex w-full flex-col overflow-hidden bg-background text-foreground md:flex-row",
|
||||
className
|
||||
)}
|
||||
initial="hidden"
|
||||
animate="visible"
|
||||
variants={containerVariants}
|
||||
{...props}
|
||||
>
|
||||
{/* Left Side: Content */}
|
||||
<div className="flex w-full flex-col justify-between p-8 md:w-1/2 md:p-12 lg:w-3/5 lg:p-16">
|
||||
{/* Top Section: Logo & Main Content */}
|
||||
<div>
|
||||
<motion.header className="mb-12" variants={itemVariants}>
|
||||
{logo && (
|
||||
<div className="flex items-center">
|
||||
<img src={logo.url} alt={logo.alt} className="mr-3 h-8" />
|
||||
<div>
|
||||
{logo.text && <p className="text-lg font-bold text-foreground">{logo.text}</p>}
|
||||
{slogan && <p className="text-xs tracking-wider text-muted-foreground">{slogan}</p>}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</motion.header>
|
||||
|
||||
<motion.main variants={containerVariants}>
|
||||
<motion.h1 className="text-4xl font-bold leading-tight text-foreground md:text-5xl" variants={itemVariants}>
|
||||
{title}
|
||||
</motion.h1>
|
||||
<motion.div className="my-6 h-1 w-20 bg-primary" variants={itemVariants}></motion.div>
|
||||
<motion.p className="mb-8 max-w-md text-base text-muted-foreground" variants={itemVariants}>
|
||||
{subtitle}
|
||||
</motion.p>
|
||||
<motion.a href={callToAction.href} className="text-lg font-bold tracking-widest text-primary transition-colors hover:text-primary/80" variants={itemVariants}>
|
||||
{callToAction.text}
|
||||
</motion.a>
|
||||
</motion.main>
|
||||
</div>
|
||||
|
||||
{/* Bottom Section: Footer Info */}
|
||||
<motion.footer className="mt-12 w-full" variants={itemVariants}>
|
||||
<div className="grid grid-cols-1 gap-6 text-xs text-muted-foreground sm:grid-cols-3">
|
||||
<div className="flex items-center">
|
||||
<InfoIcon type="website" />
|
||||
<span>{contactInfo.website}</span>
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
<InfoIcon type="phone" />
|
||||
<span>{contactInfo.phone}</span>
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
<InfoIcon type="address" />
|
||||
<span>{contactInfo.address}</span>
|
||||
</div>
|
||||
</div>
|
||||
</motion.footer>
|
||||
</div>
|
||||
|
||||
{/* Right Side: Image with Clip Path Animation */}
|
||||
<motion.div
|
||||
className="w-full min-h-[300px] bg-cover bg-center md:w-1/2 md:min-h-full lg:w-2/5"
|
||||
style={{
|
||||
backgroundImage: `url(${backgroundImage})`,
|
||||
}}
|
||||
initial={{ clipPath: 'polygon(100% 0, 100% 0, 100% 100%, 100% 100%)' }}
|
||||
animate={{ clipPath: 'polygon(25% 0, 100% 0, 100% 100%, 0% 100%)' }}
|
||||
transition={{ duration: 1.2, ease: "circOut" }}
|
||||
>
|
||||
</motion.div>
|
||||
</motion.section>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
HeroSection.displayName = "HeroSection";
|
||||
|
||||
export { HeroSection };
|
||||
300
components/ui/modern-side-bar.tsx
Normal file
300
components/ui/modern-side-bar.tsx
Normal file
@@ -0,0 +1,300 @@
|
||||
"use client";
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import {
|
||||
Home,
|
||||
User,
|
||||
Settings,
|
||||
LogOut,
|
||||
Menu,
|
||||
X,
|
||||
ChevronLeft,
|
||||
ChevronRight,
|
||||
BarChart3,
|
||||
FileText,
|
||||
Bell,
|
||||
Search,
|
||||
HelpCircle
|
||||
} from 'lucide-react';
|
||||
|
||||
interface NavigationItem {
|
||||
id: string;
|
||||
name: string;
|
||||
icon: React.ComponentType<{ className?: string }>;
|
||||
href: string;
|
||||
badge?: string;
|
||||
}
|
||||
|
||||
interface SidebarProps {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
// Updated navigation items - remove logout from here
|
||||
const navigationItems: NavigationItem[] = [
|
||||
{ id: "dashboard", name: "Dashboard", icon: Home, href: "/dashboard" },
|
||||
{ id: "analytics", name: "Analytics", icon: BarChart3, href: "/analytics" },
|
||||
{ id: "documents", name: "Documents", icon: FileText, href: "/documents", badge: "3" },
|
||||
{ id: "notifications", name: "Notifications", icon: Bell, href: "/notifications", badge: "12" },
|
||||
{ id: "profile", name: "Profile", icon: User, href: "/profile" },
|
||||
{ id: "settings", name: "Settings", icon: Settings, href: "/settings" },
|
||||
{ id: "help", name: "Help & Support", icon: HelpCircle, href: "/help" },
|
||||
];
|
||||
|
||||
export function Sidebar({ className = "" }: SidebarProps) {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [isCollapsed, setIsCollapsed] = useState(false);
|
||||
const [activeItem, setActiveItem] = useState("dashboard");
|
||||
|
||||
// Auto-open sidebar on desktop
|
||||
useEffect(() => {
|
||||
const handleResize = () => {
|
||||
if (window.innerWidth >= 768) {
|
||||
setIsOpen(true);
|
||||
} else {
|
||||
setIsOpen(false);
|
||||
}
|
||||
};
|
||||
|
||||
handleResize();
|
||||
window.addEventListener('resize', handleResize);
|
||||
return () => window.removeEventListener('resize', handleResize);
|
||||
}, []);
|
||||
|
||||
const toggleSidebar = () => setIsOpen(!isOpen);
|
||||
const toggleCollapse = () => setIsCollapsed(!isCollapsed);
|
||||
|
||||
const handleItemClick = (itemId: string) => {
|
||||
setActiveItem(itemId);
|
||||
if (window.innerWidth < 768) {
|
||||
setIsOpen(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* Mobile hamburger button */}
|
||||
<button
|
||||
onClick={toggleSidebar}
|
||||
className="fixed top-6 left-6 z-50 p-3 rounded-lg bg-white shadow-md border border-slate-100 md:hidden hover:bg-slate-50 transition-all duration-200"
|
||||
aria-label="Toggle sidebar"
|
||||
>
|
||||
{isOpen ?
|
||||
<X className="h-5 w-5 text-slate-600" /> :
|
||||
<Menu className="h-5 w-5 text-slate-600" />
|
||||
}
|
||||
</button>
|
||||
|
||||
{/* Mobile overlay */}
|
||||
{isOpen && (
|
||||
<div
|
||||
className="fixed inset-0 bg-black/40 backdrop-blur-sm z-30 md:hidden transition-opacity duration-300"
|
||||
onClick={toggleSidebar}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Sidebar */}
|
||||
<div
|
||||
className={`
|
||||
fixed top-0 left-0 h-full bg-white border-r border-slate-200 z-40 transition-all duration-300 ease-in-out flex flex-col
|
||||
${isOpen ? "translate-x-0" : "-translate-x-full"}
|
||||
${isCollapsed ? "w-28" : "w-78"}
|
||||
md:translate-x-0 md:static md:z-auto
|
||||
${className}
|
||||
`}
|
||||
>
|
||||
{/* Header with logo and collapse button */}
|
||||
<div className="flex items-center justify-between p-5 border-b border-slate-200 bg-slate-50/60">
|
||||
{!isCollapsed && (
|
||||
<div className="flex items-center space-x-2.5">
|
||||
<div className="w-9 h-9 bg-blue-600 rounded-lg flex items-center justify-center shadow-sm">
|
||||
<span className="text-white font-bold text-base">A</span>
|
||||
</div>
|
||||
<div className="flex flex-col">
|
||||
<span className="font-semibold text-slate-800 text-base">Acme Corp</span>
|
||||
<span className="text-xs text-slate-500">Enterprise Dashboard</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{isCollapsed && (
|
||||
<div className="w-9 h-9 bg-blue-600 rounded-lg flex items-center justify-center mx-auto shadow-sm">
|
||||
<span className="text-white font-bold text-base">A</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Desktop collapse button */}
|
||||
<button
|
||||
onClick={toggleCollapse}
|
||||
className="hidden md:flex p-1.5 rounded-md hover:bg-slate-100 transition-all duration-200"
|
||||
aria-label={isCollapsed ? "Expand sidebar" : "Collapse sidebar"}
|
||||
>
|
||||
{isCollapsed ? (
|
||||
<ChevronRight className="h-4 w-4 text-slate-500" />
|
||||
) : (
|
||||
<ChevronLeft className="h-4 w-4 text-slate-500" />
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Search Bar */}
|
||||
{!isCollapsed && (
|
||||
<div className="px-4 py-3">
|
||||
<div className="relative">
|
||||
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 h-3.5 w-3.5 text-slate-400" />
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Search..."
|
||||
className="w-full pl-9 pr-4 py-2 bg-slate-50 border border-slate-200 rounded-md text-sm placeholder-slate-400 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all duration-200"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Navigation */}
|
||||
<nav className="flex-1 px-3 py-2 overflow-y-auto">
|
||||
<ul className="space-y-0.5">
|
||||
{navigationItems.map((item) => {
|
||||
const Icon = item.icon;
|
||||
const isActive = activeItem === item.id;
|
||||
|
||||
return (
|
||||
<li key={item.id}>
|
||||
<button
|
||||
onClick={() => handleItemClick(item.id)}
|
||||
className={`
|
||||
w-full flex items-center space-x-2.5 px-3 py-2.5 rounded-md text-left transition-all duration-200 group
|
||||
${isActive
|
||||
? "bg-blue-50 text-blue-700"
|
||||
: "text-slate-600 hover:bg-slate-50 hover:text-slate-900"
|
||||
}
|
||||
${isCollapsed ? "justify-center px-2" : ""}
|
||||
`}
|
||||
title={isCollapsed ? item.name : undefined}
|
||||
>
|
||||
<div className="flex items-center justify-center min-w-[24px]">
|
||||
<Icon
|
||||
className={`
|
||||
h-4.5 w-4.5 flex-shrink-0
|
||||
${isActive
|
||||
? "text-blue-600"
|
||||
: "text-slate-500 group-hover:text-slate-700"
|
||||
}
|
||||
`}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{!isCollapsed && (
|
||||
<div className="flex items-center justify-between w-full">
|
||||
<span className={`text-sm ${isActive ? "font-medium" : "font-normal"}`}>{item.name}</span>
|
||||
{item.badge && (
|
||||
<span className={`
|
||||
px-1.5 py-0.5 text-xs font-medium rounded-full
|
||||
${isActive
|
||||
? "bg-blue-100 text-blue-700"
|
||||
: "bg-slate-100 text-slate-600"
|
||||
}
|
||||
`}>
|
||||
{item.badge}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Badge for collapsed state */}
|
||||
{isCollapsed && item.badge && (
|
||||
<div className="absolute top-1 right-1 w-4 h-4 flex items-center justify-center rounded-full bg-blue-100 border border-white">
|
||||
<span className="text-[10px] font-medium text-blue-700">
|
||||
{parseInt(item.badge) > 9 ? '9+' : item.badge}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Tooltip for collapsed state */}
|
||||
{isCollapsed && (
|
||||
<div className="absolute left-full ml-2 px-2 py-1 bg-slate-800 text-white text-xs rounded opacity-0 invisible group-hover:opacity-100 group-hover:visible transition-all duration-200 whitespace-nowrap z-50">
|
||||
{item.name}
|
||||
{item.badge && (
|
||||
<span className="ml-1.5 px-1 py-0.5 bg-slate-700 rounded-full text-[10px]">
|
||||
{item.badge}
|
||||
</span>
|
||||
)}
|
||||
<div className="absolute left-0 top-1/2 transform -translate-y-1/2 -translate-x-1 w-1.5 h-1.5 bg-slate-800 rotate-45" />
|
||||
</div>
|
||||
)}
|
||||
</button>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
{/* Bottom section with profile and logout */}
|
||||
<div className="mt-auto border-t border-slate-200">
|
||||
{/* Profile Section */}
|
||||
<div className={`border-b border-slate-200 bg-slate-50/30 ${isCollapsed ? 'py-3 px-2' : 'p-3'}`}>
|
||||
{!isCollapsed ? (
|
||||
<div className="flex items-center px-3 py-2 rounded-md bg-white hover:bg-slate-50 transition-colors duration-200">
|
||||
<div className="w-8 h-8 bg-slate-200 rounded-full flex items-center justify-center">
|
||||
<span className="text-slate-700 font-medium text-sm">JD</span>
|
||||
</div>
|
||||
<div className="flex-1 min-w-0 ml-2.5">
|
||||
<p className="text-sm font-medium text-slate-800 truncate">John Doe</p>
|
||||
<p className="text-xs text-slate-500 truncate">Senior Administrator</p>
|
||||
</div>
|
||||
<div className="w-2 h-2 bg-green-500 rounded-full ml-2" title="Online" />
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex justify-center">
|
||||
<div className="relative">
|
||||
<div className="w-9 h-9 bg-slate-200 rounded-full flex items-center justify-center">
|
||||
<span className="text-slate-700 font-medium text-sm">JD</span>
|
||||
</div>
|
||||
<div className="absolute -bottom-1 -right-1 w-3 h-3 bg-green-500 rounded-full border-2 border-white" />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Logout Button */}
|
||||
<div className="p-3">
|
||||
<button
|
||||
onClick={() => handleItemClick("logout")}
|
||||
className={`
|
||||
w-full flex items-center rounded-md text-left transition-all duration-200 group
|
||||
text-red-600 hover:bg-red-50 hover:text-red-700
|
||||
${isCollapsed ? "justify-center p-2.5" : "space-x-2.5 px-3 py-2.5"}
|
||||
`}
|
||||
title={isCollapsed ? "Logout" : undefined}
|
||||
>
|
||||
<div className="flex items-center justify-center min-w-[24px]">
|
||||
<LogOut className="h-4.5 w-4.5 flex-shrink-0 text-red-500 group-hover:text-red-600" />
|
||||
</div>
|
||||
|
||||
{!isCollapsed && (
|
||||
<span className="text-sm">Logout</span>
|
||||
)}
|
||||
|
||||
{/* Tooltip for collapsed state */}
|
||||
{isCollapsed && (
|
||||
<div className="absolute left-full ml-2 px-2 py-1 bg-slate-800 text-white text-xs rounded opacity-0 invisible group-hover:opacity-100 group-hover:visible transition-all duration-200 whitespace-nowrap z-50">
|
||||
Logout
|
||||
<div className="absolute left-0 top-1/2 transform -translate-y-1/2 -translate-x-1 w-1.5 h-1.5 bg-slate-800 rotate-45" />
|
||||
</div>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Main Content Area */}
|
||||
<div
|
||||
className={`
|
||||
transition-all duration-300 ease-in-out w-full
|
||||
${isCollapsed ? "md:ml-20" : "md:ml-72"}
|
||||
`}
|
||||
>
|
||||
{/* Your content remains the same */}
|
||||
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
90
components/ui/timeline.tsx
Normal file
90
components/ui/timeline.tsx
Normal file
@@ -0,0 +1,90 @@
|
||||
"use client";
|
||||
import {
|
||||
useMotionValueEvent,
|
||||
useScroll,
|
||||
useTransform,
|
||||
motion,
|
||||
} from "framer-motion";
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
|
||||
interface TimelineEntry {
|
||||
title: string;
|
||||
content: React.ReactNode;
|
||||
}
|
||||
|
||||
export const Timeline = ({ data }: { data: TimelineEntry[] }) => {
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const [height, setHeight] = useState(0);
|
||||
|
||||
useEffect(() => {
|
||||
if (ref.current) {
|
||||
const rect = ref.current.getBoundingClientRect();
|
||||
setHeight(rect.height);
|
||||
}
|
||||
}, [ref]);
|
||||
|
||||
const { scrollYProgress } = useScroll({
|
||||
target: containerRef,
|
||||
offset: ["start 10%", "end 50%"],
|
||||
});
|
||||
|
||||
const heightTransform = useTransform(scrollYProgress, [0, 1], [0, height]);
|
||||
const opacityTransform = useTransform(scrollYProgress, [0, 0.1], [0, 1]);
|
||||
|
||||
return (
|
||||
<div
|
||||
className="w-full bg-white dark:bg-neutral-950 font-sans md:px-10"
|
||||
ref={containerRef}
|
||||
>
|
||||
<div className="max-w-7xl mx-auto py-20 px-4 md:px-8 lg:px-10">
|
||||
<h2 className="text-lg md:text-4xl mb-4 text-black dark:text-white max-w-4xl">
|
||||
Changelog from my journey
|
||||
</h2>
|
||||
<p className="text-neutral-700 dark:text-neutral-300 text-sm md:text-base max-w-sm">
|
||||
I've been working on Aceternity for the past 2 years. Here's
|
||||
a timeline of my journey.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div ref={ref} className="relative max-w-7xl mx-auto pb-20">
|
||||
{data.map((item, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className="flex justify-start pt-10 md:pt-40 md:gap-10"
|
||||
>
|
||||
<div className="sticky flex flex-col md:flex-row z-40 items-center top-40 self-start max-w-xs lg:max-w-sm md:w-full">
|
||||
<div className="h-10 absolute left-3 md:left-3 w-10 rounded-full bg-white dark:bg-black flex items-center justify-center">
|
||||
<div className="h-4 w-4 rounded-full bg-neutral-200 dark:bg-neutral-800 border border-neutral-300 dark:border-neutral-700 p-2" />
|
||||
</div>
|
||||
<h3 className="hidden md:block text-xl md:pl-20 md:text-5xl font-bold text-neutral-500 dark:text-neutral-500 ">
|
||||
{item.title}
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
<div className="relative pl-20 pr-4 md:pl-4 w-full">
|
||||
<h3 className="md:hidden block text-2xl mb-4 text-left font-bold text-neutral-500 dark:text-neutral-500">
|
||||
{item.title}
|
||||
</h3>
|
||||
{item.content}{" "}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
<div
|
||||
style={{
|
||||
height: height + "px",
|
||||
}}
|
||||
className="absolute md:left-8 left-8 top-0 overflow-hidden w-[2px] bg-[linear-gradient(to_bottom,var(--tw-gradient-stops))] from-transparent from-[0%] via-neutral-200 dark:via-neutral-700 to-transparent to-[99%] [mask-image:linear-gradient(to_bottom,transparent_0%,black_10%,black_90%,transparent_100%)] "
|
||||
>
|
||||
<motion.div
|
||||
style={{
|
||||
height: heightTransform,
|
||||
opacity: opacityTransform,
|
||||
}}
|
||||
className="absolute inset-x-0 top-0 w-[2px] bg-gradient-to-t from-purple-500 via-blue-500 to-transparent from-[0%] via-[10%] rounded-full"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user