From 23116a847abeac6a0406d79e2b07d54d9369a074 Mon Sep 17 00:00:00 2001 From: Ronny Date: Sun, 12 Apr 2026 11:56:29 +0200 Subject: [PATCH] style: premium Dashboard redesign inspired by Stitch mockups MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- frontend/src/components/BottomNav.tsx | 9 +- frontend/src/pages/DashboardPage.module.css | 252 ++++++++++++++++---- frontend/src/pages/DashboardPage.tsx | 88 ++++--- 3 files changed, 258 insertions(+), 91 deletions(-) diff --git a/frontend/src/components/BottomNav.tsx b/frontend/src/components/BottomNav.tsx index 4b83c02..d0e1d84 100644 --- a/frontend/src/components/BottomNav.tsx +++ b/frontend/src/components/BottomNav.tsx @@ -1,5 +1,4 @@ import { NavLink } from 'react-router-dom'; -import { Home, Trophy, User, Swords } from 'lucide-react'; import styles from './BottomNav.module.css'; export default function BottomNav() { @@ -9,19 +8,19 @@ export default function BottomNav() { return ( diff --git a/frontend/src/pages/DashboardPage.module.css b/frontend/src/pages/DashboardPage.module.css index 8a3d5d5..f495416 100644 --- a/frontend/src/pages/DashboardPage.module.css +++ b/frontend/src/pages/DashboardPage.module.css @@ -2,14 +2,26 @@ padding: 16px; max-width: 600px; margin: 0 auto; + display: flex; + flex-direction: column; + gap: 14px; } +/* ═══════════════════════════════════════ + HERO CARD — Stadium atmosphere + ═══════════════════════════════════════ */ .hero { - background: var(--surface-mid); + position: relative; border-radius: var(--radius-lg); - padding: 20px; + padding: 24px 20px 20px; cursor: pointer; - border: 1px solid rgba(75, 183, 248, 0.1); + overflow: hidden; + background: linear-gradient(165deg, #141e38 0%, #0d1526 40%, #091020 100%); + border: 1px solid rgba(75, 183, 248, 0.12); + box-shadow: + 0 8px 32px rgba(0, 0, 0, 0.4), + 0 2px 8px rgba(0, 0, 0, 0.2), + inset 0 1px 0 rgba(255, 255, 255, 0.06); transition: transform 0.2s; } @@ -17,122 +29,264 @@ transform: scale(1.01); } -.heroLabel { - font-size: 0.75rem; - text-transform: uppercase; - letter-spacing: 1px; - color: var(--text-secondary); +/* Radial glow behind the teams */ +.heroGlow { + position: absolute; + top: 40%; + left: 50%; + width: 280px; + height: 200px; + transform: translate(-50%, -50%); + background: radial-gradient(ellipse, rgba(75, 183, 248, 0.12) 0%, rgba(254, 174, 50, 0.06) 40%, transparent 70%); + pointer-events: none; + z-index: 0; +} + +.heroHeader { display: flex; justify-content: space-between; align-items: center; + position: relative; + z-index: 1; + margin-bottom: 12px; +} + +.heroLabel { + font-size: 0.7rem; + text-transform: uppercase; + letter-spacing: 1.5px; + color: var(--text-muted); + font-weight: 600; } .heroCountdown { + display: flex; + align-items: center; + gap: 6px; color: var(--gold); font-weight: 700; - font-size: 0.85rem; + font-size: 0.75rem; + letter-spacing: 0.5px; + background: rgba(254, 174, 50, 0.1); + padding: 4px 10px; + border-radius: 20px; + border: 1px solid rgba(254, 174, 50, 0.2); } +.countdownDot { + width: 6px; + height: 6px; + border-radius: 50%; + background: var(--gold); + animation: pulse 2s ease-in-out infinite; +} + +@keyframes pulse { + 0%, 100% { opacity: 1; } + 50% { opacity: 0.3; } +} + +/* LED Kickoff Time */ +.heroKickoff { + text-align: center; + position: relative; + z-index: 1; + margin-bottom: 8px; +} + +.heroLED { + font-family: 'DSEG7', 'Courier New', monospace; + font-size: 22px; + color: #FECC4C; + letter-spacing: 0.04em; + text-shadow: + 0 0 4px rgba(254, 174, 50, 0.9), + 0 0 12px rgba(254, 174, 50, 0.5), + 0 0 24px rgba(254, 174, 50, 0.25); +} + +/* Teams */ .heroTeams { display: flex; justify-content: center; - align-items: center; - gap: 20px; - margin: 16px 0; + align-items: flex-start; + gap: 24px; + margin: 12px 0 20px; + position: relative; + z-index: 1; } .heroTeam { display: flex; flex-direction: column; align-items: center; - gap: 6px; + gap: 8px; + flex: 1; +} + +.heroCrestBox { + width: 72px; + height: 72px; + border-radius: 18px; + background: var(--surface-high); + box-shadow: + 0 4px 16px rgba(0, 0, 0, 0.3), + inset 0 1px 0 rgba(255, 255, 255, 0.15); + border: 1px solid rgba(255, 255, 255, 0.08); + display: flex; + align-items: center; + justify-content: center; + overflow: hidden; } .heroCrest { - width: 48px; - height: 48px; + width: 52px; + height: 52px; object-fit: contain; } -.heroVs { - font-size: 1.2rem; +.heroCrestFallback { + font-size: 32px; +} + +.heroTeamName { + font-size: 0.85rem; font-weight: 700; - color: var(--gold); -} - -.heroTip { - background: var(--surface-high); - border-radius: var(--radius-sm); - padding: 8px 16px; + color: var(--text-primary); + text-transform: uppercase; + letter-spacing: 0.5px; text-align: center; - color: var(--gold); - font-weight: 600; } +.heroVs { + font-size: 1.1rem; + font-weight: 800; + color: var(--gold); + margin-top: 24px; + text-shadow: 0 0 10px rgba(254, 174, 50, 0.3); +} + +/* CTA / Tip */ .heroTipBtn { display: block; width: 100%; - padding: 10px; + padding: 14px; border: none; - border-radius: var(--radius-sm); - background: var(--primary); + border-radius: var(--radius-md); + background: linear-gradient(135deg, var(--primary) 0%, #2196f3 100%); color: white; - font-weight: 600; + font-weight: 700; + font-size: 1rem; cursor: pointer; - font-size: 0.95rem; + position: relative; + z-index: 1; + box-shadow: 0 4px 20px rgba(75, 183, 248, 0.3); + transition: transform 0.15s, box-shadow 0.15s; + letter-spacing: 0.3px; } +.heroTipBtn:hover { + transform: translateY(-1px); + box-shadow: 0 6px 24px rgba(75, 183, 248, 0.4); +} + +.heroTipBtn:active { + transform: scale(0.98); +} + +.heroTip { + border-radius: var(--radius-sm); + padding: 10px 16px; + text-align: center; + color: var(--gold); + font-weight: 600; + font-size: 0.9rem; + background: rgba(254, 174, 50, 0.08); + border: 1px solid rgba(254, 174, 50, 0.15); + position: relative; + z-index: 1; +} + +.heroEmpty { + text-align: center; + color: var(--text-muted); + margin: 24px 0; + position: relative; + z-index: 1; +} + +/* ═══════════════════════════════════════ + STATS ROW + ═══════════════════════════════════════ */ .statsRow { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 8px; - margin: 16px 0; } .statTile { - background: var(--surface-mid); - border-radius: var(--radius-md); - padding: 14px 8px; + padding: 16px 8px; text-align: center; - border: 1px solid rgba(75, 183, 248, 0.08); } .statValue { display: block; - font-size: 1.5rem; - font-weight: 700; + font-size: 1.6rem; + font-weight: 800; + color: var(--text-primary); + line-height: 1; +} + +.statGold { color: var(--gold); } .statLabel { display: block; - font-size: 0.7rem; + font-size: 0.65rem; color: var(--text-muted); text-transform: uppercase; - letter-spacing: 0.5px; - margin-top: 4px; + letter-spacing: 0.8px; + margin-top: 6px; + font-weight: 600; } +/* ═══════════════════════════════════════ + NUDGES + ═══════════════════════════════════════ */ .nudges { display: flex; flex-direction: column; - gap: 6px; + gap: 8px; } .nudge { - background: var(--surface-low); - border-radius: var(--radius-sm); - padding: 12px 16px; - color: var(--text-secondary); - font-size: 0.9rem; + display: flex; + align-items: center; + gap: 12px; + padding: 14px 16px; cursor: pointer; - transition: background 0.2s; + transition: background 0.2s, transform 0.15s; } .nudge:hover { - background: var(--surface-mid); + transform: translateX(2px); } +.nudgeIcon { + font-size: 1.2rem; + flex-shrink: 0; +} + +.nudgeText { + font-size: 0.85rem; + color: var(--text-secondary); + line-height: 1.4; +} + +/* ═══════════════════════════════════════ + STATES + ═══════════════════════════════════════ */ .loading, .error { text-align: center; diff --git a/frontend/src/pages/DashboardPage.tsx b/frontend/src/pages/DashboardPage.tsx index 09e6745..26b603b 100644 --- a/frontend/src/pages/DashboardPage.tsx +++ b/frontend/src/pages/DashboardPage.tsx @@ -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 (
{/* Hero Card */} -
navigate('/spiele')}> -
- Nächstes Spiel +
navigate('/spiele')}> + {/* Radial glow background effect */} +
+ +
+ Nächstes Spiel {hero && ( - {formatCountdown(hero.match.minutesUntilKickoff)} + + {formatCountdown(hero.match.minutesUntilKickoff).toUpperCase()} )}
{hero ? ( <> + {/* LED Kickoff Time */} +
+ {formatKickoff(hero.match.utcDate)} +
+ + {/* Teams */}
- {hero.match.homeTeam.crest ? ( - {hero.match.homeTeam.name} - ) : ( -
- )} - {hero.match.homeTeam.shortName} +
+ {hero.match.homeTeam.crest ? ( + {hero.match.homeTeam.name} + ) : ( + 🏳️ + )} +
+ {hero.match.homeTeam.shortName}
- vs + + VS +
- {hero.match.awayTeam.crest ? ( - {hero.match.awayTeam.name} - ) : ( -
- )} - {hero.match.awayTeam.shortName} +
+ {hero.match.awayTeam.crest ? ( + {hero.match.awayTeam.name} + ) : ( + 🏳️ + )} +
+ {hero.match.awayTeam.shortName}
+ {/* CTA or Tip Display */} {hero.userTip ? (
Dein Tipp: {hero.userTip.home}:{hero.userTip.away} ✓ @@ -110,27 +126,23 @@ export default function DashboardPage(_props: Props) { }); }} > - Jetzt tippen + Jetzt tippen ⚽ ) : null} ) : ( -

- Keine anstehenden Spiele -

+

Keine anstehenden Spiele

)}
{/* Stats Row */}
- - {stats.rank !== null ? stats.rank : '—'} - + {stats.rank !== null ? stats.rank : '—'} Dein Rang
- {stats.totalPoints} + {stats.totalPoints} Punkte
@@ -151,7 +163,10 @@ export default function DashboardPage(_props: Props) { else if (nudge.type === 'leader') navigate('/rangliste'); }} > - {nudge.text} + + {nudge.type === 'untipped' ? '📅' : nudge.type === 'leader' ? '🏆' : '🎯'} + + {nudge.text}
))}
@@ -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,