diff --git a/adapters/bootstrap/racing/RacingTeamFactory.ts b/adapters/bootstrap/racing/RacingTeamFactory.ts index 4af38aaf8..3c4a32cd3 100644 --- a/adapters/bootstrap/racing/RacingTeamFactory.ts +++ b/adapters/bootstrap/racing/RacingTeamFactory.ts @@ -39,10 +39,28 @@ export class RacingTeamFactory { const teamId = seedId(`team-${i}`, this.persistence); + const racingNames = [ + 'Apex Performance', 'Velocity Racing', 'Zenith Motorsport', 'Quantum Racing', + 'Ignition Racing', 'Precision Dynamics', 'Overdrive Motorsport', 'Apex Predators', + 'Gridline Racing', 'Shift Point Motorsport', 'Redline Performance', 'Apex Legends', + 'Circuit Breakers', 'Full Throttle Racing', 'Gearhead Motorsport', 'Piston Cup Racing', + 'Turbo Titans', 'Nitro Knights', 'Velocity Vanguards', 'Mach One Racing', + 'Apex Alliance', 'Elite Endurance', 'Sprint Specialists', 'Grand Prix Group', + 'Podium Pursuit', 'Victory Vibe', 'Championship Chase', 'Racing Renegades', + 'Track Titans', 'Asphalt Assassins', 'Speed Syndicate', 'Fast Lane Force', + 'Apex Architects', 'Velocity Visionaries', 'Zenith Zephyrs', 'Quantum Quicksilver', + 'Ignition Iron', 'Precision Pilots', 'Overdrive Outlaws', 'Apex Aces', + 'Gridline Guardians', 'Shift Point Sentinels', 'Redline Rebels', 'Apex Avengers', + 'Circuit Crusaders', 'Full Throttle Falcons', 'Gearhead Giants', 'Piston Cup Pros', + 'Turbo Tigers', 'Nitro Ninjas' + ]; + + const name = racingNames[(i - 1) % racingNames.length]!; + return Team.create({ id: teamId, - name: faker.company.name() + ' Racing', - tag: faker.string.alpha({ length: 4, casing: 'upper' }), + name: name, + tag: name.split(' ').map(w => w[0]).join('').toUpperCase().substring(0, 4), description: faker.lorem.sentences(2), ownerId: owner.id, leagues: teamLeagues, @@ -203,8 +221,8 @@ export class RacingTeamFactory { generateTeamStats(teams: Team[]): Map { const statsMap = new Map(); - // Available regions - const regions = ['Europe', 'North America', 'South America', 'Asia', 'Oceania', 'Africa']; + // Available regions (using country codes for flags) + const regions = ['DE', 'GB', 'US', 'FR', 'IT', 'ES', 'BR', 'JP', 'AU', 'NL', 'BE', 'AT', 'CH', 'SE', 'NO', 'FI', 'DK', 'PL', 'CZ', 'HU']; // Available languages const allLanguages = ['English', 'German', 'French', 'Spanish', 'Italian', 'Portuguese', 'Japanese', 'Korean', 'Russian', 'Chinese']; diff --git a/apps/api/src/domain/media/MediaController.ts b/apps/api/src/domain/media/MediaController.ts index dc966f758..fee37e5ee 100644 --- a/apps/api/src/domain/media/MediaController.ts +++ b/apps/api/src/domain/media/MediaController.ts @@ -86,14 +86,18 @@ export class MediaController { @Res() res: Response, ): Promise { this.logger.debug('[MediaController] Generating team logo', { teamId }); - const svg = this.mediaGenerationService.generateTeamLogo(teamId); - const svgLength = svg.length; + const url = this.mediaGenerationService.generateTeamLogo(teamId); + + if (url.startsWith('http')) { + res.redirect(HttpStatus.FOUND, url); + return; + } res.setHeader('Content-Type', 'image/svg+xml; charset=utf-8'); res.setHeader('Cache-Control', 'public, max-age=86400'); - res.status(HttpStatus.OK).send(svg); + res.status(HttpStatus.OK).send(url); - this.logger.info('[MediaController] Team logo generated', { teamId, svgLength }); + this.logger.info('[MediaController] Team logo generated', { teamId }); } @Public() @@ -105,14 +109,18 @@ export class MediaController { @Res() res: Response, ): Promise { this.logger.debug('[MediaController] Generating league logo', { leagueId }); - const svg = this.mediaGenerationService.generateLeagueLogo(leagueId); - const svgLength = svg.length; + const url = this.mediaGenerationService.generateLeagueLogo(leagueId); + + if (url.startsWith('http')) { + res.redirect(HttpStatus.FOUND, url); + return; + } res.setHeader('Content-Type', 'image/svg+xml; charset=utf-8'); res.setHeader('Cache-Control', 'public, max-age=86400'); - res.status(HttpStatus.OK).send(svg); + res.status(HttpStatus.OK).send(url); - this.logger.info('[MediaController] League logo generated', { leagueId, svgLength }); + this.logger.info('[MediaController] League logo generated', { leagueId }); } @Public() @@ -124,14 +132,18 @@ export class MediaController { @Res() res: Response, ): Promise { this.logger.debug('[MediaController] Generating league cover', { leagueId }); - const svg = this.mediaGenerationService.generateLeagueCover(leagueId); - const svgLength = svg.length; + const url = this.mediaGenerationService.generateLeagueCover(leagueId); + + if (url.startsWith('http')) { + res.redirect(HttpStatus.FOUND, url); + return; + } res.setHeader('Content-Type', 'image/svg+xml; charset=utf-8'); res.setHeader('Cache-Control', 'public, max-age=86400'); - res.status(HttpStatus.OK).send(svg); + res.status(HttpStatus.OK).send(url); - this.logger.info('[MediaController] League cover generated', { leagueId, svgLength }); + this.logger.info('[MediaController] League cover generated', { leagueId }); } @Public() @@ -143,14 +155,18 @@ export class MediaController { @Res() res: Response, ): Promise { this.logger.debug('[MediaController] Generating driver avatar', { driverId }); - const svg = this.mediaGenerationService.generateDriverAvatar(driverId); - const svgLength = svg.length; + const url = this.mediaGenerationService.generateDriverAvatar(driverId); + + if (url.startsWith('http')) { + res.redirect(HttpStatus.FOUND, url); + return; + } res.setHeader('Content-Type', 'image/svg+xml; charset=utf-8'); res.setHeader('Cache-Control', 'public, max-age=86400'); - res.status(HttpStatus.OK).send(svg); + res.status(HttpStatus.OK).send(url); - this.logger.info('[MediaController] Driver avatar generated', { driverId, svgLength }); + this.logger.info('[MediaController] Driver avatar generated', { driverId }); } @Public() @@ -223,27 +239,30 @@ export class MediaController { @Res() res: Response, ): Promise { this.logger.debug('[MediaController] Generating media', { type, id }); - let svg: string; + let url: string; // Route to appropriate generator based on type if (type === 'team') { - svg = this.mediaGenerationService.generateTeamLogo(id); + url = this.mediaGenerationService.generateTeamLogo(id); } else if (type === 'league') { - svg = this.mediaGenerationService.generateLeagueLogo(id); + url = this.mediaGenerationService.generateLeagueLogo(id); } else if (type === 'driver') { - svg = this.mediaGenerationService.generateDriverAvatar(id); + url = this.mediaGenerationService.generateDriverAvatar(id); } else { // Fallback: generate a generic logo - svg = this.mediaGenerationService.generateLeagueLogo(`${type}-${id}`); + url = this.mediaGenerationService.generateLeagueLogo(`${type}-${id}`); } - const svgLength = svg.length; + if (url.startsWith('http')) { + res.redirect(HttpStatus.FOUND, url); + return; + } res.setHeader('Content-Type', 'image/svg+xml; charset=utf-8'); res.setHeader('Cache-Control', 'public, max-age=31536000, immutable'); - res.status(HttpStatus.OK).send(svg); + res.status(HttpStatus.OK).send(url); - this.logger.info('[MediaController] Generated media served', { type, id, svgLength }); + this.logger.info('[MediaController] Generated media served', { type, id }); } @Public() diff --git a/apps/website/app/globals.css b/apps/website/app/globals.css index a754b0253..16152c5ef 100644 --- a/apps/website/app/globals.css +++ b/apps/website/app/globals.css @@ -190,6 +190,15 @@ animation: fade-in-up 0.4s cubic-bezier(0.16, 1, 0.3, 1) forwards; } + .scrollbar-hide::-webkit-scrollbar { + display: none; + } + + .scrollbar-hide { + -ms-overflow-style: none; + scrollbar-width: none; + } + @media (prefers-reduced-motion: reduce) { .animate-fade-in-up { animation: none; diff --git a/apps/website/components/shared/Carousel.tsx b/apps/website/components/shared/Carousel.tsx new file mode 100644 index 000000000..69c217598 --- /dev/null +++ b/apps/website/components/shared/Carousel.tsx @@ -0,0 +1,144 @@ +'use client'; + +import React, { useRef, useState, useEffect } from 'react'; +import { ChevronLeft, ChevronRight } from 'lucide-react'; +import { IconButton } from '@/ui/IconButton'; +import { Box } from '@/ui/Box'; +import { Heading } from '@/ui/Heading'; +import { Text } from '@/ui/Text'; + +interface CarouselProps { + children: React.ReactNode; + title?: string; + count?: number; +} + +export function Carousel({ children, title, count }: CarouselProps) { + const scrollRef = useRef(null); + const [showLeft, setShowLeft] = useState(false); + const [showRight, setShowRight] = useState(true); + + const checkScroll = () => { + if (scrollRef.current) { + const { scrollLeft, scrollWidth, clientWidth } = scrollRef.current; + setShowLeft(scrollLeft > 10); + setShowRight(scrollLeft < scrollWidth - clientWidth - 10); + } + }; + + useEffect(() => { + checkScroll(); + window.addEventListener('resize', checkScroll); + return () => window.removeEventListener('resize', checkScroll); + }, []); + + const scroll = (direction: 'left' | 'right') => { + if (scrollRef.current) { + const { clientWidth } = scrollRef.current; + const scrollAmount = clientWidth * 0.8; + scrollRef.current.scrollBy({ + left: direction === 'left' ? -scrollAmount : scrollAmount, + behavior: 'smooth' + }); + } + }; + + return ( + + {/* Header with Title and Controls */} + + + {title && ( + + {title} + + )} + {count !== undefined && ( + + {count} + + )} + + + + scroll('left')} + variant="secondary" + size="sm" + disabled={!showLeft} + className={`transition-opacity duration-300 ${showLeft ? 'opacity-100' : 'opacity-30'}`} + /> + scroll('right')} + variant="secondary" + size="sm" + disabled={!showRight} + className={`transition-opacity duration-300 ${showRight ? 'opacity-100' : 'opacity-30'}`} + /> + + + + {/* Scroll Area with Fades */} + + {/* Left Fade */} + + + {/* Scroll Container */} + + {React.Children.map(children, (child) => ( + + {child} + + ))} + + + {/* Right Fade */} + + + + ); +} diff --git a/apps/website/components/teams/TeamCard.tsx b/apps/website/components/teams/TeamCard.tsx index fb93b82b7..d0494fbfe 100644 --- a/apps/website/components/teams/TeamCard.tsx +++ b/apps/website/components/teams/TeamCard.tsx @@ -1,15 +1,23 @@ 'use client'; import React from 'react'; -import { TeamCard as UiTeamCard } from '@/ui/TeamCard'; +import { ChevronRight, Users, Zap } from 'lucide-react'; +import { Card } from '@/ui/Card'; +import { CountryFlag } from '@/ui/CountryFlag'; +import { Heading } from '@/ui/Heading'; +import { Icon } from '@/ui/Icon'; +import { Logo } from '@/ui/Logo'; +import { Text } from '@/ui/Text'; +import { Badge } from '@/ui/Badge'; +import { Stack } from '@/ui/Stack'; +import { Grid } from '@/ui/Grid'; +import { Box } from '@/ui/Box'; import { TeamSummaryData } from '@/lib/view-data/TeamsViewData'; -import { Image } from '@/ui/Image'; interface TeamCardProps { team?: TeamSummaryData; // Compatibility props name?: string; - leagueName?: string; logo?: string; memberCount?: number; ratingLabel?: string; @@ -25,7 +33,6 @@ interface TeamCardProps { export function TeamCard({ team, name, - leagueName, logo, memberCount, ratingLabel, @@ -40,7 +47,6 @@ export function TeamCard({ const data = team || { teamId: '', teamName: name || '', - leagueName: leagueName || '', memberCount: memberCount || 0, logoUrl: logo, ratingLabel: ratingLabel || '-', @@ -50,22 +56,98 @@ export function TeamCard({ isRecruiting: isRecruiting || false, performanceLevel: performanceLevel, description: description, + countryCode: region, }; return ( - : undefined} - memberCount={data.memberCount} - rating={data.ratingLabel} - wins={data.winsLabel} - races={data.racesLabel} - region={data.region} - isRecruiting={data.isRecruiting} - performanceLevel={data.performanceLevel} - description={data.description} + onClick?.(data.teamId)} - /> + transition + fullHeight + position="relative" + > + {data.isRecruiting && ( + + + RECRUITING + + + )} + + + {/* Header: Logo and Identity */} + + + + + {data.teamName} + + + {data.performanceLevel && ( + + + {data.performanceLevel} + + )} + + + + + {/* Technical Stats Grid - Engineered Look */} + + + Rating + {data.ratingLabel} + + + Wins + {data.winsLabel} + + + Races + {data.racesLabel} + + + + {data.description && ( + + {data.description} + + )} + + {/* Spacer to push footer down */} + + + {/* Footer: Metadata */} + + + + + {data.memberCount} + + {data.countryCode && ( + + + {data.countryCode} + + )} + + + + Details + + + + + ); } diff --git a/apps/website/components/teams/TeamHero.tsx b/apps/website/components/teams/TeamHero.tsx index 4a3fe2550..eb1163cc7 100644 --- a/apps/website/components/teams/TeamHero.tsx +++ b/apps/website/components/teams/TeamHero.tsx @@ -1,74 +1,60 @@ 'use client'; -import { JoinTeamButton } from '@/components/teams/JoinTeamButton'; -import { TeamLogo } from '@/components/teams/TeamLogo'; -import { TeamTag } from '@/components/teams/TeamTag'; -import { Card } from '@/ui/Card'; -import { Group } from '@/ui/Group'; +import React, { ReactNode } from 'react'; +import { Glow } from '@/ui/Glow'; import { Heading } from '@/ui/Heading'; -import { StatGrid } from '@/ui/StatGrid'; import { Text } from '@/ui/Text'; +import { Stack } from '@/ui/Stack'; +import { Box } from '@/ui/Box'; -interface TeamHeroProps { - team: { - id: string; - name: string; - tag: string | null; - description?: string; - category?: string | null; - createdAt?: string; - foundedDateLabel?: string; - leagues: { id: string }[]; - }; - memberCount: number; - memberCountLabel?: string; - leagueCountLabel?: string; - onUpdate: () => void; +export interface TeamHeroProps { + title: ReactNode; + description: string; + stats?: ReactNode; + actions?: ReactNode; + sideContent?: ReactNode; } -export function TeamHero({ team, memberCount, memberCountLabel, leagueCountLabel, onUpdate }: TeamHeroProps) { +export function TeamHero({ + title, + description, + stats, + actions, + sideContent +}: TeamHeroProps) { return ( - - - - + + + + + + + {title} + + + {description} + - - - {team.name} - {team.tag && } - - - {team.description} - - 0 ? [{ - label: 'Activity', - value: leagueCountLabel || 'Unknown', - }] : []), - ]} - /> - - - - - - + {stats && {stats}} + + {actions && ( + + {actions} + + )} + + + {sideContent && ( + + {sideContent} + + )} + + ); } diff --git a/apps/website/components/teams/TeamHeroSectionWrapper.tsx b/apps/website/components/teams/TeamHeroSectionWrapper.tsx index bb6c7a655..45db39b98 100644 --- a/apps/website/components/teams/TeamHeroSectionWrapper.tsx +++ b/apps/website/components/teams/TeamHeroSectionWrapper.tsx @@ -7,7 +7,7 @@ import { TeamHeroStats } from '@/components/teams/TeamHeroStats'; import type { TeamSummaryViewModel } from '@/lib/view-models/TeamSummaryViewModel'; import { Button } from '@/ui/Button'; import { Icon } from '@/ui/Icon'; -import { TeamHero } from '@/ui/TeamHero'; +import { TeamHero } from '@/components/teams/TeamHero'; import { Text } from '@/ui/Text'; import { Stack } from '@/ui/Stack'; import { diff --git a/apps/website/components/teams/TeamsDirectoryHeader.tsx b/apps/website/components/teams/TeamsDirectoryHeader.tsx index 1e349b144..68e9c8241 100644 --- a/apps/website/components/teams/TeamsDirectoryHeader.tsx +++ b/apps/website/components/teams/TeamsDirectoryHeader.tsx @@ -3,7 +3,7 @@ import React from 'react'; import { Plus } from 'lucide-react'; import { Button } from '@/ui/Button'; -import { TeamsHeader } from '@/ui/TeamsHeader'; +import { TeamsHeader } from './TeamsHeader'; interface TeamsDirectoryHeaderProps { onCreateTeam: () => void; @@ -12,7 +12,7 @@ interface TeamsDirectoryHeaderProps { export function TeamsDirectoryHeader({ onCreateTeam }: TeamsDirectoryHeaderProps) { return ( ({ teamId: team.id, teamName: team.name, - leagueName: team.leagues[0] || '', memberCount: team.memberCount, logoUrl: team.logoUrl, ratingLabel: RatingDisplay.format(team.rating), + ratingValue: team.rating || 0, winsLabel: NumberDisplay.format(team.totalWins || 0), racesLabel: NumberDisplay.format(team.totalRaces || 0), region: team.region, @@ -24,6 +24,7 @@ export class TeamsViewDataBuilder { category: team.category, performanceLevel: team.performanceLevel, description: team.description, + countryCode: team.region, // Assuming region contains country code for now })); return { teams }; diff --git a/apps/website/lib/view-data/TeamsViewData.ts b/apps/website/lib/view-data/TeamsViewData.ts index 15ec733b9..20cc2a92c 100644 --- a/apps/website/lib/view-data/TeamsViewData.ts +++ b/apps/website/lib/view-data/TeamsViewData.ts @@ -8,10 +8,10 @@ import { ViewData } from '../contracts/view-data/ViewData'; export interface TeamSummaryData { teamId: string; teamName: string; - leagueName: string; memberCount: number; logoUrl?: string; ratingLabel: string; + ratingValue: number; winsLabel: string; racesLabel: string; region?: string; @@ -19,6 +19,7 @@ export interface TeamSummaryData { category?: string; performanceLevel?: string; description?: string; + countryCode?: string; } export interface TeamsViewData extends ViewData { diff --git a/apps/website/templates/TeamsTemplate.tsx b/apps/website/templates/TeamsTemplate.tsx index 511124b63..615b184a7 100644 --- a/apps/website/templates/TeamsTemplate.tsx +++ b/apps/website/templates/TeamsTemplate.tsx @@ -12,6 +12,8 @@ import { EmptyState } from '@/ui/EmptyState'; import { Container } from '@/ui/Container'; import { Heading } from '@/ui/Heading'; import { Text } from '@/ui/Text'; +import { Box } from '@/ui/Box'; +import { Carousel } from '@/components/shared/Carousel'; interface TeamsTemplateProps extends TemplateProps { searchQuery: string; @@ -34,7 +36,6 @@ export function TeamsTemplate({ const filteredTeams = useMemo(() => { return teams.filter(team => team.teamName.toLowerCase().includes(searchQuery.toLowerCase()) || - (team.leagueName && team.leagueName.toLowerCase().includes(searchQuery.toLowerCase())) || (team.region && team.region.toLowerCase().includes(searchQuery.toLowerCase())) ); }, [teams, searchQuery]); @@ -45,7 +46,7 @@ export function TeamsTemplate({ } const topTeams = [...teams] - .sort((a, b) => parseFloat(b.ratingLabel) - parseFloat(a.ratingLabel)) + .sort((a, b) => b.ratingValue - a.ratingValue) .slice(0, 3); const recruitingTeams = teams.filter(t => t.isRecruiting && !topTeams.find(top => top.teamId === t.teamId)); @@ -68,35 +69,29 @@ export function TeamsTemplate({ - + + + {clusters.length > 0 ? ( -
+
{clusters.map((cluster) => ( -
-
- - {cluster.title} - -
-
- {cluster.teams.length} -
-
- - - {cluster.teams.map((team) => ( - onTeamClick?.(id)} - /> - ))} - -
+ + {cluster.teams.map((team) => ( + onTeamClick?.(id)} + /> + ))} + ))}
) : ( diff --git a/apps/website/ui/Card.tsx b/apps/website/ui/Card.tsx index 2f084f83b..87eeb8108 100644 --- a/apps/website/ui/Card.tsx +++ b/apps/website/ui/Card.tsx @@ -32,6 +32,7 @@ export interface CardProps { group?: boolean | any; w?: string | any; justifyContent?: string | any; + fullHeight?: boolean | any; } /** @@ -65,6 +66,7 @@ export const Card = forwardRef(({ gap, py, backgroundColor, + fullHeight, }, ref) => { const variantClasses = { default: 'bg-[var(--ui-color-bg-surface)] border-[var(--ui-color-border-default)] shadow-sm', @@ -112,6 +114,7 @@ export const Card = forwardRef(({ ...(alignItems ? { alignItems } : {}), ...(gap !== undefined ? { gap: `${gap * 0.25}rem` } : {}), ...(border === false ? { border: 'none' } : {}), + ...(fullHeight ? { height: '100%' } : {}), }; return ( @@ -129,7 +132,7 @@ export const Card = forwardRef(({
)} -
+
{children}
diff --git a/apps/website/ui/Logo.tsx b/apps/website/ui/Logo.tsx index 50b3c3728..6413ce546 100644 --- a/apps/website/ui/Logo.tsx +++ b/apps/website/ui/Logo.tsx @@ -24,29 +24,39 @@ export const Logo = ({ variant = 'muted', border = true, }: LogoProps) => { + const finalSize = typeof size === 'number' ? `${size}px` : size; + return ( {src ? ( {alt} ) : icon ? ( - 32 ? 5 : 4) : 5} intent="low" /> + 32 ? 6 : 4) : 6} intent="low" /> ) : null} ); diff --git a/apps/website/ui/Surface.tsx b/apps/website/ui/Surface.tsx index 612df9819..66a7fff90 100644 --- a/apps/website/ui/Surface.tsx +++ b/apps/website/ui/Surface.tsx @@ -87,6 +87,7 @@ export const Surface = forwardRef(( }; const style: React.CSSProperties = { + ...(props.style || {}), ...variantStyles[variant], borderRadius: rounded !== 'none' ? `var(--ui-radius-${String(rounded)})` : undefined, boxShadow: shadow !== 'none' ? `var(--ui-shadow-${String(shadow)})` : undefined, diff --git a/apps/website/ui/TeamCard.tsx b/apps/website/ui/TeamCard.tsx deleted file mode 100644 index 3a95007eb..000000000 --- a/apps/website/ui/TeamCard.tsx +++ /dev/null @@ -1,115 +0,0 @@ -import { ChevronRight, Globe, Users, Zap } from 'lucide-react'; -import { ReactNode } from 'react'; -import { Card } from './Card'; -import { Heading } from './Heading'; -import { Icon } from './Icon'; -import { Text } from './Text'; -import { Badge } from './Badge'; - -export interface TeamCardProps { - name: string; - leagueName?: string; - logo?: ReactNode; - memberCount: number; - rating?: string; - wins?: string; - races?: string; - region?: string; - isRecruiting?: boolean; - performanceLevel?: string; - onClick?: () => void; - description?: string; -} - -export const TeamCard = ({ - name, - leagueName, - logo, - memberCount, - rating, - wins, - races, - region = 'EU', - isRecruiting, - performanceLevel, - description, - onClick -}: TeamCardProps) => { - return ( - -
- {/* Header: Logo and Identity */} -
-
- {logo || } -
-
-
- {name} - {isRecruiting && ( - RECRUITING - )} -
-
- {leagueName && {leagueName}} - {performanceLevel && ( -
- - {performanceLevel} -
- )} -
-
-
- - {/* Technical Stats Grid - Engineered Look */} - {(rating || wins || races) && ( -
-
- Rating - {rating || '-'} -
-
- Wins - {wins || '-'} -
-
- Races - {races || '-'} -
-
- )} - - {description && ( - - {description} - - )} - - {/* Footer: Metadata */} -
-
-
- - {memberCount} -
-
- - {region} -
-
- -
- Details - -
-
-
-
- ); -}; diff --git a/apps/website/ui/TeamHero.tsx b/apps/website/ui/TeamHero.tsx deleted file mode 100644 index 0ec078fff..000000000 --- a/apps/website/ui/TeamHero.tsx +++ /dev/null @@ -1,57 +0,0 @@ -import { ReactNode } from 'react'; -import { Box } from './Box'; -import { Glow } from './Glow'; -import { Heading } from './Heading'; -import { Text } from './Text'; - -export interface TeamHeroProps { - title: ReactNode; - description: string; - stats?: ReactNode; - actions?: ReactNode; - sideContent?: ReactNode; -} - -export const TeamHero = ({ - title, - description, - stats, - actions, - sideContent -}: TeamHeroProps) => { - return ( - - - - - - - {title} - - - {description} - - - {stats && {stats}} - - {actions && ( - - {actions} - - )} - - - {sideContent && ( - - {sideContent} - - )} - - - ); -}; diff --git a/core/media/domain/services/MediaGenerationService.ts b/core/media/domain/services/MediaGenerationService.ts index 17f0e1d91..d8f549420 100644 --- a/core/media/domain/services/MediaGenerationService.ts +++ b/core/media/domain/services/MediaGenerationService.ts @@ -8,190 +8,39 @@ import { faker } from '@faker-js/faker'; */ export class MediaGenerationService { /** - * Generates a deterministic SVG avatar for a driver - */ - generateDriverAvatar(driverId: string): string { - faker.seed(this.hashCode(driverId)); - - const firstName = faker.person.firstName(); - const lastName = faker.person.lastName(); - const initials = ((firstName?.[0] || 'D') + (lastName?.[0] || 'R')).toUpperCase(); - - const primaryColor = faker.color.rgb({ format: 'hex' }); - const secondaryColor = faker.color.rgb({ format: 'hex' }); - - const patterns = ['gradient', 'stripes', 'circles', 'diamond']; - const pattern = faker.helpers.arrayElement(patterns); - - let patternSvg = ''; - switch (pattern) { - case 'gradient': - patternSvg = ` - - - - - - - - `; - break; - case 'stripes': - patternSvg = ` - - - `; - break; - case 'circles': - patternSvg = ` - - - - `; - break; - case 'diamond': - patternSvg = ` - - - `; - break; - } - - return ` - - ${patternSvg} - ${initials} - - `; - } - - /** - * Generates a deterministic SVG logo for a team - * Now includes team name initials for better branding + * Generates a deterministic logo URL for a team + * Uses a real placeholder image service for high-quality racing logos. */ generateTeamLogo(teamId: string): string { faker.seed(this.hashCode(teamId)); - - const primaryColor = faker.color.rgb({ format: 'hex' }); - const secondaryColor = faker.color.rgb({ format: 'hex' }); - - // Generate deterministic initials from seeded faker data - // This creates consistent initials for the same teamId - const adjective = faker.company.buzzAdjective(); - const noun = faker.company.catchPhraseNoun(); - const initials = ((adjective?.[0] || 'T') + (noun?.[0] || 'M')).toUpperCase(); - - const shapes = ['circle', 'square', 'triangle', 'hexagon']; - const shape = faker.helpers.arrayElement(shapes); - - let shapeSvg = ''; - switch (shape) { - case 'circle': - shapeSvg = ``; - break; - case 'square': - shapeSvg = ``; - break; - case 'triangle': - shapeSvg = ``; - break; - case 'hexagon': - shapeSvg = ``; - break; - } - - return ` - - - - - - - - - ${shapeSvg} - - - - ${initials} - - `; + return `https://picsum.photos/seed/${teamId}/200/200`; } /** - * Generates a deterministic SVG logo for a league - * Updated to use the same faker style as team logos for consistency + * Generates a deterministic logo URL for a league + * Uses a real placeholder image service for high-quality league logos. */ generateLeagueLogo(leagueId: string): string { faker.seed(this.hashCode(leagueId)); - - const primaryColor = faker.color.rgb({ format: 'hex' }); - const secondaryColor = faker.color.rgb({ format: 'hex' }); - - // Generate deterministic initials from seeded faker data - // This creates consistent initials for the same leagueId - const adjective = faker.company.buzzAdjective(); - const noun = faker.company.catchPhraseNoun(); - const initials = ((adjective?.[0] || 'L') + (noun?.[0] || 'G')).toUpperCase(); - - const shapes = ['circle', 'square', 'triangle', 'hexagon']; - const shape = faker.helpers.arrayElement(shapes); - - let shapeSvg = ''; - switch (shape) { - case 'circle': - shapeSvg = ``; - break; - case 'square': - shapeSvg = ``; - break; - case 'triangle': - shapeSvg = ``; - break; - case 'hexagon': - shapeSvg = ``; - break; - } - - return ` - - - - - - - - - ${shapeSvg} - - - - ${initials} - - `; + return `https://picsum.photos/seed/l-${leagueId}/200/200`; } /** - * Generates a deterministic SVG cover for a league + * Generates a deterministic avatar URL for a driver + * Uses a real placeholder image service for high-quality driver avatars. + */ + generateDriverAvatar(driverId: string): string { + faker.seed(this.hashCode(driverId)); + return `https://i.pravatar.cc/150?u=${driverId}`; + } + + /** + * Generates a deterministic cover URL for a league + * Uses a real placeholder image service for high-quality league covers. */ generateLeagueCover(leagueId: string): string { faker.seed(this.hashCode(leagueId)); - - const primaryColor = faker.color.rgb({ format: 'hex' }); - const secondaryColor = faker.color.rgb({ format: 'hex' }); - - return ` - - - - - - - - - - - `; + return `https://picsum.photos/seed/c-${leagueId}/800/200`; } /**