style: premium Dashboard redesign inspired by Stitch mockups
Build & Deploy Tippspiel / build (push) Successful in 51s

Hero Card:
- Dramatic gradient background (navy → deep blue)
- Radial glow effect behind team flags (stadium atmosphere)
- LED kickoff time with golden glow
- Larger flag icons (72px) as app-icon style boxes
- Countdown as golden badge with pulsing dot
- Bigger CTA button with gradient and shadow

Bottom Nav:
- Filled/solid SVG icons (home, soccer ball, trophy, person)
  instead of Lucide outline icons — more premium feel

Nudges:
- Icon + text layout with hover animation
- Better spacing and visual hierarchy

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Ronny
2026-04-12 11:56:29 +02:00
parent 3f757e712f
commit 24eefef975
3 changed files with 258 additions and 91 deletions
+51 -37
View File
@@ -16,10 +16,17 @@ function formatStreak(streak: number): string {
return '0';
}
function formatKickoff(utcDate: string): string {
return new Date(utcDate).toLocaleString('de-DE', {
hour: '2-digit', minute: '2-digit', timeZone: 'Europe/Berlin',
});
}
function formatCountdown(minutes: number): string {
if (minutes < 60) return `in ${minutes} Min`;
if (minutes < 60) return `in ${Math.round(minutes)} Min`;
if (minutes < 60 * 24) return `in ${Math.floor(minutes / 60)}h`;
return `in ${Math.floor(minutes / (60 * 24))} Tagen`;
const days = Math.floor(minutes / (60 * 24));
return `in ${days} ${days === 1 ? 'Tag' : 'Tagen'}`;
}
export default function DashboardPage(_props: Props) {
@@ -45,46 +52,55 @@ export default function DashboardPage(_props: Props) {
return (
<div className={styles.dashboard}>
{/* Hero Card */}
<div className={`card ${styles.hero}`} onClick={() => navigate('/spiele')}>
<div className={styles.heroLabel}>
<span>Nächstes Spiel</span>
<div className={styles.hero} onClick={() => navigate('/spiele')}>
{/* Radial glow background effect */}
<div className={styles.heroGlow} />
<div className={styles.heroHeader}>
<span className={styles.heroLabel}>Nächstes Spiel</span>
{hero && (
<span className={styles.heroCountdown}>
{formatCountdown(hero.match.minutesUntilKickoff)}
<span className={styles.countdownDot} />
{formatCountdown(hero.match.minutesUntilKickoff).toUpperCase()}
</span>
)}
</div>
{hero ? (
<>
{/* LED Kickoff Time */}
<div className={styles.heroKickoff}>
<span className={styles.heroLED}>{formatKickoff(hero.match.utcDate)}</span>
</div>
{/* Teams */}
<div className={styles.heroTeams}>
<div className={styles.heroTeam}>
{hero.match.homeTeam.crest ? (
<img
src={hero.match.homeTeam.crest}
alt={hero.match.homeTeam.name}
className={styles.heroCrest}
/>
) : (
<div className={styles.heroCrest} />
)}
<span>{hero.match.homeTeam.shortName}</span>
<div className={styles.heroCrestBox}>
{hero.match.homeTeam.crest ? (
<img src={hero.match.homeTeam.crest} alt={hero.match.homeTeam.name} className={styles.heroCrest} />
) : (
<span className={styles.heroCrestFallback}>🏳</span>
)}
</div>
<span className={styles.heroTeamName}>{hero.match.homeTeam.shortName}</span>
</div>
<span className={styles.heroVs}>vs</span>
<span className={styles.heroVs}>VS</span>
<div className={styles.heroTeam}>
{hero.match.awayTeam.crest ? (
<img
src={hero.match.awayTeam.crest}
alt={hero.match.awayTeam.name}
className={styles.heroCrest}
/>
) : (
<div className={styles.heroCrest} />
)}
<span>{hero.match.awayTeam.shortName}</span>
<div className={styles.heroCrestBox}>
{hero.match.awayTeam.crest ? (
<img src={hero.match.awayTeam.crest} alt={hero.match.awayTeam.name} className={styles.heroCrest} />
) : (
<span className={styles.heroCrestFallback}>🏳</span>
)}
</div>
<span className={styles.heroTeamName}>{hero.match.awayTeam.shortName}</span>
</div>
</div>
{/* CTA or Tip Display */}
{hero.userTip ? (
<div className={styles.heroTip}>
Dein Tipp: {hero.userTip.home}:{hero.userTip.away}
@@ -110,27 +126,23 @@ export default function DashboardPage(_props: Props) {
});
}}
>
Jetzt tippen
Jetzt tippen
</button>
) : null}
</>
) : (
<p style={{ textAlign: 'center', color: 'var(--text-muted)', margin: '16px 0' }}>
Keine anstehenden Spiele
</p>
<p className={styles.heroEmpty}>Keine anstehenden Spiele</p>
)}
</div>
{/* Stats Row */}
<div className={styles.statsRow}>
<div className={`card ${styles.statTile}`}>
<span className={styles.statValue}>
{stats.rank !== null ? stats.rank : '—'}
</span>
<span className={styles.statValue}>{stats.rank !== null ? stats.rank : '—'}</span>
<span className={styles.statLabel}>Dein Rang</span>
</div>
<div className={`card ${styles.statTile}`}>
<span className={styles.statValue}>{stats.totalPoints}</span>
<span className={`${styles.statValue} ${styles.statGold}`}>{stats.totalPoints}</span>
<span className={styles.statLabel}>Punkte</span>
</div>
<div className={`card ${styles.statTile}`}>
@@ -151,7 +163,10 @@ export default function DashboardPage(_props: Props) {
else if (nudge.type === 'leader') navigate('/rangliste');
}}
>
{nudge.text}
<span className={styles.nudgeIcon}>
{nudge.type === 'untipped' ? '📅' : nudge.type === 'leader' ? '🏆' : '🎯'}
</span>
<span className={styles.nudgeText}>{nudge.text}</span>
</div>
))}
</div>
@@ -163,7 +178,6 @@ export default function DashboardPage(_props: Props) {
onClose={() => setTipMatch(null)}
onSaved={(_matchId, tipHome, tipAway) => {
setTipMatch(null);
// Update dashboard data to reflect the new tip
if (data && data.hero) {
setData({
...data,