feat: glitter border on team/class cards, mobile-friendly class selector

- Animated gold border glitter effect on active team card and class image
- Classes selector: 2-column grid on mobile (no scroll), list on desktop
- Selector appears above detail on mobile (flex-col-reverse)
- Compact selector items on mobile (icon + name only)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-10 19:55:09 +03:00
parent 303c52653c
commit f2b840416d
4 changed files with 58 additions and 11 deletions

View File

@@ -283,6 +283,47 @@
} }
} }
/* ===== Team Card Glitter ===== */
@keyframes glitter-move {
0% {
background-position: 0% 0%;
}
100% {
background-position: 200% 200%;
}
}
.team-card-glitter {
position: relative;
}
/* Animated gold border glow */
.team-card-glitter::before {
content: "";
position: absolute;
inset: -1px;
border-radius: inherit;
padding: 2px;
background: linear-gradient(
135deg,
transparent 20%,
rgba(201, 169, 110, 0.6) 30%,
rgba(212, 184, 122, 1) 35%,
transparent 45%,
transparent 55%,
rgba(201, 169, 110, 0.5) 65%,
rgba(212, 184, 122, 0.9) 70%,
transparent 80%
);
background-size: 200% 200%;
animation: glitter-move 3s linear infinite;
mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
mask-composite: exclude;
pointer-events: none;
z-index: 1;
}
/* ===== Section Divider ===== */ /* ===== Section Divider ===== */
.section-divider { .section-divider {
@@ -331,4 +372,8 @@
.glow-hover:hover { .glow-hover:hover {
transform: none; transform: none;
} }
.team-card-glitter::before {
animation: none !important;
}
} }

View File

@@ -44,7 +44,7 @@ export function Classes() {
<div> <div>
{/* Hero image */} {/* Hero image */}
{item.images && item.images[0] && ( {item.images && item.images[0] && (
<div className="relative aspect-[16/9] w-full overflow-hidden rounded-2xl"> <div className="team-card-glitter relative aspect-[16/9] w-full overflow-hidden rounded-2xl">
<Image <Image
src={item.images[0]} src={item.images[0]}
alt={item.name} alt={item.name}
@@ -75,10 +75,10 @@ export function Classes() {
</div> </div>
)} )}
renderSelectorItem={(item, _i, isActive) => ( renderSelectorItem={(item, _i, isActive) => (
<div className="flex items-center gap-3 p-3"> <div className="flex items-center gap-2 px-3 py-2 lg:gap-3 lg:p-3">
{/* Icon */} {/* Icon */}
<div <div
className={`flex h-9 w-9 shrink-0 items-center justify-center rounded-lg transition-colors ${ className={`flex h-7 w-7 lg:h-9 lg:w-9 shrink-0 items-center justify-center rounded-lg transition-colors ${
isActive isActive
? "bg-[#c9a96e]/20 text-[#d4b87a]" ? "bg-[#c9a96e]/20 text-[#d4b87a]"
: "bg-neutral-200/50 text-neutral-500 dark:bg-white/[0.06] dark:text-neutral-400" : "bg-neutral-200/50 text-neutral-500 dark:bg-white/[0.06] dark:text-neutral-400"
@@ -88,7 +88,7 @@ export function Classes() {
</div> </div>
<div className="min-w-0"> <div className="min-w-0">
<p <p
className={`text-sm font-semibold truncate transition-colors ${ className={`text-xs lg:text-sm font-semibold truncate transition-colors ${
isActive isActive
? "text-[#c9a96e]" ? "text-[#c9a96e]"
: "text-neutral-700 dark:text-neutral-300" : "text-neutral-700 dark:text-neutral-300"
@@ -96,7 +96,7 @@ export function Classes() {
> >
{item.name} {item.name}
</p> </p>
<p className="text-xs text-neutral-500 dark:text-neutral-500 truncate"> <p className="hidden lg:block text-xs text-neutral-500 dark:text-neutral-500 truncate">
{item.description} {item.description}
</p> </p>
</div> </div>

View File

@@ -213,7 +213,7 @@ export function Team() {
return ( return (
<div <div
key={m.name} key={m.name}
className="absolute bottom-0 overflow-hidden rounded-2xl border pointer-events-none" className={`absolute bottom-0 overflow-hidden rounded-2xl border pointer-events-none ${style.isCenter ? "team-card-glitter" : ""}`}
style={{ style={{
width: style.width, width: style.width,
height: style.height, height: style.height,
@@ -221,8 +221,10 @@ export function Team() {
zIndex: style.zIndex, zIndex: style.zIndex,
transform: style.transform, transform: style.transform,
filter: style.filter, filter: style.filter,
borderColor: style.borderColor, borderColor: style.isCenter ? "transparent" : style.borderColor,
boxShadow: style.boxShadow, boxShadow: style.isCenter
? "0 0 40px rgba(201,169,110,0.15), 0 0 80px rgba(201,169,110,0.08)"
: style.boxShadow,
transition: style.transition, transition: style.transition,
}} }}
> >

View File

@@ -97,7 +97,7 @@ export function ShowcaseLayout<T>({
return ( return (
<div <div
className="flex flex-col gap-6 lg:flex-row lg:gap-8" className="flex flex-col-reverse gap-6 lg:flex-row lg:gap-8"
onMouseEnter={handleMouseEnter} onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave} onMouseLeave={handleMouseLeave}
> >
@@ -131,14 +131,14 @@ export function ShowcaseLayout<T>({
<div className="lg:w-[40%]"> <div className="lg:w-[40%]">
<div <div
ref={selectorRef} ref={selectorRef}
className="styled-scrollbar flex gap-3 overflow-x-auto pb-2 lg:max-h-[600px] lg:flex-col lg:overflow-y-auto lg:overflow-x-visible lg:pb-0 lg:pr-1" className="grid grid-cols-2 gap-2 lg:grid-cols-1 lg:gap-3 lg:max-h-[600px] lg:overflow-y-auto lg:pr-1 styled-scrollbar"
> >
{items.map((item, i) => ( {items.map((item, i) => (
<button <button
key={i} key={i}
ref={i === activeIndex ? activeItemRef : null} ref={i === activeIndex ? activeItemRef : null}
onClick={() => onSelect(i)} onClick={() => onSelect(i)}
className={`flex-shrink-0 cursor-pointer rounded-xl border-2 text-left transition-all duration-300 ${ className={`cursor-pointer rounded-xl border-2 text-left transition-all duration-300 ${
i === activeIndex i === activeIndex
? "border-[#c9a96e]/60 bg-[#c9a96e]/10 dark:bg-[#c9a96e]/5" ? "border-[#c9a96e]/60 bg-[#c9a96e]/10 dark:bg-[#c9a96e]/5"
: "border-transparent bg-neutral-100 hover:bg-neutral-200 dark:bg-white/[0.03] dark:hover:bg-white/[0.06]" : "border-transparent bg-neutral-100 hover:bg-neutral-200 dark:bg-white/[0.03] dark:hover:bg-white/[0.06]"