Enhance realm cards with visual effects and add a featured realm carousel

Add visual polish to realm cards including shimmer, corner accents, and particles. Implement a featured realm carousel with auto-rotation and manual navigation. Introduce an animated counter for statistics.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 9203795e-937a-4306-b81d-b4d5c78c240e
Replit-Commit-Checkpoint-Type: intermediate_checkpoint
Replit-Commit-Event-Id: e3c5eaaa-04d7-465e-8410-f39c62c5c621
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/7c94b7a0-29c7-4f2e-94ef-44b2153872b7/9203795e-937a-4306-b81d-b4d5c78c240e/GF6ep3l
Replit-Helium-Checkpoint-Created: true
This commit is contained in:
sirpiglr 2025-12-06 03:12:22 +00:00
parent 71f5bf5063
commit c1afedb201
3 changed files with 492 additions and 7 deletions

View file

@ -53,7 +53,7 @@ localPort = 8044
externalPort = 3003
[[ports]]
localPort = 38223
localPort = 34311
externalPort = 3002
[[ports]]

View file

@ -16,6 +16,7 @@ interface IsometricRealmCardProps {
index: number;
onClick: (realm: RealmData) => void;
isSelected: boolean;
isFeatured?: boolean;
}
export default function IsometricRealmCard({
@ -23,6 +24,7 @@ export default function IsometricRealmCard({
index,
onClick,
isSelected,
isFeatured = false,
}: IsometricRealmCardProps) {
const cardRef = useRef<HTMLDivElement>(null);
const [tilt, setTilt] = useState({ x: 0, y: 0 });
@ -88,7 +90,7 @@ export default function IsometricRealmCard({
{/* Main card surface */}
<div
className="card-surface"
className={`card-surface ${isFeatured ? 'is-featured' : ''}`}
style={{
borderColor: isHovered || isSelected ? realm.color : `${realm.color}40`,
boxShadow: isHovered
@ -97,6 +99,30 @@ export default function IsometricRealmCard({
'--card-color': realm.color,
} as CSSProperties}
>
{/* Shimmer effect */}
<div className="card-shimmer" />
{/* Corner accents */}
<div className="corner-accent tl" style={{ borderColor: realm.color }} />
<div className="corner-accent tr" style={{ borderColor: realm.color }} />
<div className="corner-accent bl" style={{ borderColor: realm.color }} />
<div className="corner-accent br" style={{ borderColor: realm.color }} />
{/* Ambient particles inside card */}
<div className="card-particles">
{[...Array(4)].map((_, i) => (
<span
key={i}
className="card-particle"
style={{
background: realm.color,
animationDelay: `${i * 0.8}s`,
left: `${20 + i * 20}%`,
}}
/>
))}
</div>
{/* Floating icon layer */}
<div
className="card-icon-layer"
@ -267,6 +293,87 @@ export default function IsometricRealmCard({
100% { --gradient-angle: 360deg; }
}
.card-shimmer {
position: absolute;
inset: 0;
background: linear-gradient(
105deg,
transparent 40%,
hsl(var(--foreground) / 0.03) 45%,
hsl(var(--foreground) / 0.06) 50%,
hsl(var(--foreground) / 0.03) 55%,
transparent 60%
);
transform: translateX(-100%);
pointer-events: none;
border-radius: 18px;
}
.realm-card:hover .card-shimmer {
animation: cardShimmer 2s ease-in-out;
}
@keyframes cardShimmer {
0% { transform: translateX(-100%); }
100% { transform: translateX(100%); }
}
.corner-accent {
position: absolute;
width: 12px;
height: 12px;
border: 2px solid;
opacity: 0;
transition: opacity 0.3s ease;
pointer-events: none;
}
.corner-accent.tl { top: 6px; left: 6px; border-right: none; border-bottom: none; }
.corner-accent.tr { top: 6px; right: 6px; border-left: none; border-bottom: none; }
.corner-accent.bl { bottom: 6px; left: 6px; border-right: none; border-top: none; }
.corner-accent.br { bottom: 6px; right: 6px; border-left: none; border-top: none; }
.realm-card:hover .corner-accent {
opacity: 0.6;
}
.card-particles {
position: absolute;
inset: 0;
overflow: hidden;
pointer-events: none;
border-radius: 18px;
}
.card-particle {
position: absolute;
bottom: -10px;
width: 4px;
height: 4px;
border-radius: 50%;
opacity: 0;
}
.realm-card:hover .card-particle {
animation: floatUp 3s ease-in-out infinite;
}
@keyframes floatUp {
0% { transform: translateY(0); opacity: 0; }
20% { opacity: 0.6; }
80% { opacity: 0.6; }
100% { transform: translateY(-200px); opacity: 0; }
}
.card-surface.is-featured {
animation: featuredPulse 2s ease-in-out infinite;
}
@keyframes featuredPulse {
0%, 100% { box-shadow: 0 0 20px var(--card-color, transparent); }
50% { box-shadow: 0 0 40px var(--card-color, transparent); }
}
.card-icon-layer {
transform-style: preserve-3d;
margin-bottom: 20px;

View file

@ -130,12 +130,43 @@ const realms: RealmData[] = [
},
];
// Animated counter component
function AnimatedCounter({ target, duration = 2000 }: { target: number; duration?: number }) {
const [count, setCount] = useState(0);
useEffect(() => {
const startTime = Date.now();
const animate = () => {
const elapsed = Date.now() - startTime;
const progress = Math.min(elapsed / duration, 1);
const eased = 1 - Math.pow(1 - progress, 3);
setCount(Math.floor(target * eased));
if (progress < 1) requestAnimationFrame(animate);
};
const timer = setTimeout(animate, 500);
return () => clearTimeout(timer);
}, [target, duration]);
return <>{count.toLocaleString()}</>;
}
export default function IsometricRealmSelector() {
const navigate = useNavigate();
const [selectedRealm, setSelectedRealm] = useState<string | null>(null);
const [mousePosition, setMousePosition] = useState({ x: 0.5, y: 0.5 });
const [featuredIndex, setFeaturedIndex] = useState(0);
const [isPaused, setIsPaused] = useState(false);
const particles = useMemo(() => generateParticles(20), []);
// Auto-rotate featured realm
useEffect(() => {
if (isPaused) return;
const interval = setInterval(() => {
setFeaturedIndex((prev) => (prev + 1) % realms.length);
}, 4000);
return () => clearInterval(interval);
}, [isPaused]);
useEffect(() => {
let rafId: number;
@ -248,17 +279,17 @@ export default function IsometricRealmSelector() {
transition={{ duration: 0.6, delay: 0.3 }}
>
<div className="stat-item">
<span className="stat-number">12,000+</span>
<span className="stat-number"><AnimatedCounter target={12000} />+</span>
<span className="stat-label">Builders</span>
</div>
<div className="stat-divider" />
<div className="stat-item">
<span className="stat-number">500+</span>
<span className="stat-number"><AnimatedCounter target={500} />+</span>
<span className="stat-label">Projects</span>
</div>
<div className="stat-divider" />
<div className="stat-item">
<span className="stat-number">7</span>
<span className="stat-number"><AnimatedCounter target={7} duration={1000} /></span>
<span className="stat-label">Realms</span>
</div>
<div className="stat-divider" />
@ -268,14 +299,89 @@ export default function IsometricRealmSelector() {
</div>
</motion.div>
{/* Featured Realm Spotlight Carousel */}
<motion.div
className="featured-realm-section"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6, delay: 0.35 }}
onMouseEnter={() => setIsPaused(true)}
onMouseLeave={() => setIsPaused(false)}
>
<div className="featured-header">
<h2>Featured Realm</h2>
<div className="carousel-indicators">
{realms.map((realm, index) => (
<button
key={realm.id}
className={`indicator ${index === featuredIndex ? 'active' : ''}`}
onClick={() => setFeaturedIndex(index)}
style={{ '--indicator-color': realm.color } as React.CSSProperties}
/>
))}
</div>
</div>
<div className="featured-carousel">
<AnimatePresence mode="wait">
<motion.div
key={realms[featuredIndex].id}
className="featured-card"
initial={{ opacity: 0, x: 50, scale: 0.95 }}
animate={{ opacity: 1, x: 0, scale: 1 }}
exit={{ opacity: 0, x: -50, scale: 0.95 }}
transition={{ duration: 0.4, ease: "easeOut" }}
style={{ '--featured-color': realms[featuredIndex].color } as React.CSSProperties}
>
<div className="featured-glow" />
<div className="featured-shimmer" />
<div className="featured-content">
<div className="featured-icon-wrapper">
<span className="featured-icon">{realms[featuredIndex].icon}</span>
<div className="featured-particles">
{[...Array(6)].map((_, i) => (
<span key={i} className="featured-particle" style={{ '--i': i } as React.CSSProperties} />
))}
</div>
</div>
<div className="featured-info">
<div className="featured-badge">
<span className="online-dot" />
<span>{Math.floor(Math.random() * 200 + 50)} online</span>
</div>
<h3 className="featured-title">{realms[featuredIndex].label}</h3>
<p className="featured-description">{realms[featuredIndex].description}</p>
<div className="featured-features">
{realms[featuredIndex].features.map((feature, i) => (
<span key={i} className="featured-feature">{feature}</span>
))}
</div>
<button
className="featured-cta"
onClick={() => handleRealmClick(realms[featuredIndex])}
>
Enter {realms[featuredIndex].label}
<span className="cta-arrow"></span>
</button>
</div>
</div>
<div className="featured-corner tl" />
<div className="featured-corner tr" />
<div className="featured-corner bl" />
<div className="featured-corner br" />
</motion.div>
</AnimatePresence>
</div>
</motion.div>
<motion.div
className="hero-text"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6, delay: 0.4 }}
>
<h2>Select Your Realm</h2>
<p>Each realm unlocks a unique experience tailored to your journey</p>
<h2>All Realms</h2>
<p>Explore every realm and find your place in the ecosystem</p>
</motion.div>
<div className="realms-grid">
@ -286,6 +392,7 @@ export default function IsometricRealmSelector() {
index={index}
onClick={handleRealmClick}
isSelected={selectedRealm === realm.id}
isFeatured={index === featuredIndex}
/>
))}
</div>
@ -509,6 +616,245 @@ export default function IsometricRealmSelector() {
50% { opacity: 0.4; }
}
/* Featured Realm Carousel */
.featured-realm-section {
margin-bottom: 60px;
}
.featured-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
}
.featured-header h2 {
font-size: 20px;
font-weight: 700;
color: hsl(var(--foreground));
text-transform: uppercase;
letter-spacing: 0.1em;
}
.carousel-indicators {
display: flex;
gap: 8px;
}
.indicator {
width: 32px;
height: 4px;
border-radius: 2px;
background: hsl(var(--muted));
border: none;
cursor: pointer;
transition: all 0.3s ease;
}
.indicator.active {
background: var(--indicator-color);
box-shadow: 0 0 10px var(--indicator-color);
}
.indicator:hover {
background: var(--indicator-color);
opacity: 0.7;
}
.featured-carousel {
position: relative;
min-height: 280px;
}
.featured-card {
position: relative;
padding: 32px;
border-radius: 20px;
background: linear-gradient(145deg, hsl(var(--muted) / 0.8) 0%, hsl(var(--card) / 0.6) 100%);
border: 2px solid var(--featured-color, hsl(var(--aethex-500)));
backdrop-filter: blur(12px);
overflow: hidden;
}
.featured-glow {
position: absolute;
inset: -100px;
background: radial-gradient(ellipse at 30% 30%, var(--featured-color) 0%, transparent 50%);
opacity: 0.15;
pointer-events: none;
}
.featured-shimmer {
position: absolute;
inset: 0;
background: linear-gradient(
105deg,
transparent 40%,
hsl(var(--foreground) / 0.05) 45%,
hsl(var(--foreground) / 0.1) 50%,
hsl(var(--foreground) / 0.05) 55%,
transparent 60%
);
transform: translateX(-100%);
animation: shimmer 3s infinite;
pointer-events: none;
}
@keyframes shimmer {
0% { transform: translateX(-100%); }
100% { transform: translateX(100%); }
}
.featured-content {
position: relative;
display: flex;
gap: 32px;
align-items: center;
z-index: 1;
}
.featured-icon-wrapper {
position: relative;
flex-shrink: 0;
}
.featured-icon {
font-size: 80px;
display: block;
filter: drop-shadow(0 0 20px var(--featured-color));
}
.featured-particles {
position: absolute;
inset: -20px;
pointer-events: none;
}
.featured-particle {
position: absolute;
width: 6px;
height: 6px;
border-radius: 50%;
background: var(--featured-color);
opacity: 0.6;
animation: float-particle 3s ease-in-out infinite;
animation-delay: calc(var(--i) * 0.5s);
}
.featured-particle:nth-child(1) { top: 0; left: 50%; }
.featured-particle:nth-child(2) { top: 25%; right: 0; }
.featured-particle:nth-child(3) { bottom: 25%; right: 0; }
.featured-particle:nth-child(4) { bottom: 0; left: 50%; }
.featured-particle:nth-child(5) { bottom: 25%; left: 0; }
.featured-particle:nth-child(6) { top: 25%; left: 0; }
@keyframes float-particle {
0%, 100% { transform: translate(0, 0) scale(1); opacity: 0.6; }
50% { transform: translate(5px, -10px) scale(1.2); opacity: 1; }
}
.featured-info {
flex: 1;
}
.featured-badge {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 4px 12px;
background: hsl(120, 100%, 50%, 0.15);
border: 1px solid hsl(120, 100%, 50%, 0.3);
border-radius: 20px;
font-size: 11px;
font-weight: 600;
color: hsl(120, 100%, 60%);
margin-bottom: 12px;
}
.online-dot {
width: 6px;
height: 6px;
border-radius: 50%;
background: hsl(120, 100%, 50%);
animation: pulse 2s ease-in-out infinite;
}
.featured-title {
font-size: 32px;
font-weight: 800;
color: var(--featured-color);
margin-bottom: 8px;
letter-spacing: 0.05em;
}
.featured-description {
font-size: 15px;
color: hsl(var(--muted-foreground));
margin-bottom: 16px;
line-height: 1.6;
}
.featured-features {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-bottom: 20px;
}
.featured-feature {
padding: 6px 12px;
background: var(--featured-color, hsl(var(--aethex-500)));
background: color-mix(in srgb, var(--featured-color) 15%, transparent);
border: 1px solid var(--featured-color);
border-radius: 6px;
font-size: 12px;
font-weight: 500;
color: hsl(var(--foreground));
}
.featured-cta {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 14px 28px;
background: var(--featured-color);
border: none;
border-radius: 10px;
font-size: 14px;
font-weight: 700;
color: hsl(var(--background));
cursor: pointer;
transition: all 0.2s ease;
text-transform: uppercase;
letter-spacing: 0.05em;
}
.featured-cta:hover {
transform: translateY(-2px);
box-shadow: 0 10px 30px color-mix(in srgb, var(--featured-color) 50%, transparent);
}
.featured-cta .cta-arrow {
transition: transform 0.2s ease;
}
.featured-cta:hover .cta-arrow {
transform: translateX(4px);
}
.featured-corner {
position: absolute;
width: 20px;
height: 20px;
border: 2px solid var(--featured-color);
opacity: 0.6;
}
.featured-corner.tl { top: 8px; left: 8px; border-right: none; border-bottom: none; }
.featured-corner.tr { top: 8px; right: 8px; border-left: none; border-bottom: none; }
.featured-corner.bl { bottom: 8px; left: 8px; border-right: none; border-top: none; }
.featured-corner.br { bottom: 8px; right: 8px; border-left: none; border-top: none; }
.hero-text {
text-align: center;
margin-bottom: 40px;
@ -601,6 +947,38 @@ export default function IsometricRealmSelector() {
height: 24px;
}
.featured-realm-section {
margin-bottom: 40px;
}
.featured-header {
flex-direction: column;
gap: 12px;
align-items: flex-start;
}
.featured-content {
flex-direction: column;
text-align: center;
}
.featured-icon {
font-size: 60px;
}
.featured-title {
font-size: 24px;
}
.featured-features {
justify-content: center;
}
.featured-cta {
width: 100%;
justify-content: center;
}
.hero-text {
margin-bottom: 32px;
}