redesign looking good

This commit is contained in:
dimitar committed 2026-02-16 18:50:33 +01:00
1 parent 6abae13dbd
commit 4c4d741b1f
12 files changed
+767 -562

No files matched your search

@@ -1,7 +1,6 @@
import React, { useState, useEffect, useRef } from 'react';
import { Link } from '@tanstack/react-router';
import { useActiveLiveBlogs } from '@/queries/live-blogs';
import { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button';
import { Play, Pause, ChevronRight, Radio } from 'lucide-react';
@@ -9,7 +8,7 @@ interface LiveBlogTickerProps {
className?: string;
maxItems?: number;
autoScroll?: boolean;
scrollSpeed?: number; // pixels per second
scrollSpeed?: number;
}
export function LiveBlogTicker({
@@ -25,7 +24,6 @@ export function LiveBlogTicker({
const contentRef = useRef<HTMLDivElement>(null);
const animationRef = useRef<number | undefined>(undefined);
// Calculate total width needed for scrolling
const [totalWidth, setTotalWidth] = useState(0);
useEffect(() => {
@@ -35,7 +33,6 @@ export function LiveBlogTicker({
}
}, [activeBlogs]);
// Auto-scroll animation
useEffect(() => {
if (!autoScroll || isPaused || !activeBlogs || activeBlogs.length === 0) {
if (animationRef.current) {
@@ -53,7 +50,6 @@ export function LiveBlogTicker({
setScrollPosition(prev => {
let newPos = prev + (scrollSpeed * delta) / 1000;
// Reset when scrolled past content
if (newPos > totalWidth) {
newPos = -(tickerRef.current?.offsetWidth || 0);
}
@@ -75,7 +71,7 @@ export function LiveBlogTicker({
if (isLoading) {
return (
<div className={`bg-muted/50 border rounded-lg p-3 ${className}`}>
<div className={`border-brutal-sm bg-card p-3 ${className}`}>
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<div className="animate-pulse h-4 w-24 bg-muted rounded"></div>
@@ -87,7 +83,7 @@ export function LiveBlogTicker({
}
if (!activeBlogs || activeBlogs.length === 0) {
return null; // Don't show ticker if no active blogs
return null;
}
const displayBlogs = activeBlogs.slice(0, maxItems);
@@ -97,56 +93,48 @@ export function LiveBlogTicker({
};
const handleBlogClick = () => {
// Reset scroll position when user interacts
setScrollPosition(0);
};
return (
<div className={`bg-background border rounded-lg overflow-hidden ${className}`}>
{/* Ticker header */}
<div className="flex items-center justify-between px-4 py-2 border-b bg-muted/30">
<div className="flex items-center gap-2">
<Radio className="w-4 h-4 text-primary" />
<span className="font-medium text-sm">Live Now</span>
<Badge variant="secondary" className="text-xs">
{activeBlogs.length} active
</Badge>
<div className={`border-brutal-sm bg-card overflow-hidden ${className}`}>
<div className="flex items-center justify-between px-4 py-2 border-b-2 border-foreground/10 bg-secondary">
<div className="flex items-center gap-3">
<Radio className="w-4 h-4 text-accent animate-pulse" />
<span className="font-body text-sm font-bold uppercase tracking-wider">LIVE</span>
<span className="px-2 py-0.5 bg-accent text-foreground text-xs font-body font-bold uppercase">
{activeBlogs.length}
</span>
</div>
<div className="flex items-center gap-2">
{autoScroll && (
<Button
variant="ghost"
variant="brutal"
size="sm"
onClick={handlePauseToggle}
className="h-7 px-2"
className="h-7 px-2 text-xs"
>
{isPaused ? (
<Play className="w-3 h-3" />
) : (
<Pause className="w-3 h-3" />
)}
<span className="ml-1 text-xs">{isPaused ? 'Play' : 'Pause'}</span>
</Button>
)}
<Link to="/live-blogs">
<Button variant="ghost" size="sm" className="h-7 px-2">
<span className="text-xs">View All</span>
<Button variant="brutal" size="sm" className="h-7 px-2 text-xs">
Сите
<ChevronRight className="w-3 h-3 ml-1" />
</Button>
</Link>
</div>
</div>
{/* Ticker content */}
<div
ref={tickerRef}
className="relative h-12 overflow-hidden"
style={{
maskImage: 'linear-gradient(to right, transparent, black 20px, black calc(100% - 20px), transparent)',
WebkitMaskImage: 'linear-gradient(to right, transparent, black 20px, black calc(100% - 20px), transparent)'
}}
>
<div
ref={contentRef}
@@ -156,76 +144,63 @@ export function LiveBlogTicker({
transition: isPaused ? 'transform 0.3s ease' : 'none'
}}
>
{displayBlogs.map((blog, index) => (
{displayBlogs.map((blog) => (
<React.Fragment key={blog.id}>
<Link
to="/live-blogs/$slug"
params={{ slug: blog.slug }}
onClick={handleBlogClick}
className="inline-flex items-center gap-2 px-4 py-1 rounded-full hover:bg-accent transition-colors group"
className="inline-flex items-center gap-2 px-4 py-1 border-r-2 border-foreground/10 hover:bg-accent transition-colors group"
>
<div className="flex items-center gap-2">
<div className="relative">
<div className="w-2 h-2 bg-green-500 rounded-full animate-pulse"></div>
<div className="absolute inset-0 w-2 h-2 bg-green-500 rounded-full animate-ping"></div>
<div className="w-2 h-2 bg-accent rounded-full animate-pulse"></div>
</div>
<span className="font-medium text-sm group-hover:text-primary transition-colors">
<span className="font-body text-sm font-medium group-hover:text-foreground transition-colors">
{blog.title}
</span>
{blog.updates && blog.updates.length > 0 && (
<Badge variant="outline" className="text-xs">
{blog.updates.length} updates
</Badge>
<span className="px-2 py-0.5 border border-foreground bg-background text-xs font-body">
{blog.updates.length}
</span>
)}
</div>
</Link>
{/* Separator (except after last item) */}
{index < displayBlogs.length - 1 && (
<div className="mx-2 text-muted-foreground">•</div>
)}
</React.Fragment>
))}
{/* Duplicate content for seamless looping */}
{displayBlogs.map((blog, index) => (
{displayBlogs.map((blog) => (
<React.Fragment key={`${blog.id}-dup`}>
<Link
to="/live-blogs/$slug"
params={{ slug: blog.slug }}
onClick={handleBlogClick}
className="inline-flex items-center gap-2 px-4 py-1 rounded-full hover:bg-accent transition-colors group"
className="inline-flex items-center gap-2 px-4 py-1 border-r-2 border-foreground/10 hover:bg-accent transition-colors group"
>
<div className="flex items-center gap-2">
<div className="relative">
<div className="w-2 h-2 bg-green-500 rounded-full animate-pulse"></div>
<div className="absolute inset-0 w-2 h-2 bg-green-500 rounded-full animate-ping"></div>
<div className="w-2 h-2 bg-accent rounded-full animate-pulse"></div>
</div>
<span className="font-medium text-sm group-hover:text-primary transition-colors">
<span className="font-body text-sm font-medium group-hover:text-foreground transition-colors">
{blog.title}
</span>
{blog.updates && blog.updates.length > 0 && (
<Badge variant="outline" className="text-xs">
{blog.updates.length} updates
</Badge>
<span className="px-2 py-0.5 border border-foreground bg-background text-xs font-body">
{blog.updates.length}
</span>
)}
</div>
</Link>
{index < displayBlogs.length - 1 && (
<div className="mx-2 text-muted-foreground">•</div>
)}
</React.Fragment>
))}
</div>
</div>
{/* Progress indicator */}
{autoScroll && totalWidth > 0 && (
<div className="px-4 pb-2">
<div className="h-1 bg-muted rounded-full overflow-hidden">
<div className="h-1 bg-foreground/10">
<div
className="h-full bg-primary transition-all duration-300"
className="h-full bg-accent transition-all duration-300"
style={{
width: `${Math.min(100, (scrollPosition / totalWidth) * 100 * 2)}%`
}}
@@ -235,4 +210,4 @@ export function LiveBlogTicker({
)}
</div>
);
}
}