Skip to content

Commit bc608ea

Browse files
mchestrclaude
andcommitted
style: improve mobile responsiveness and dashboard visual design
- Add mobile-friendly base styles with dvh viewport handling - Fix unauthenticated login page to use fixed positioning (no scroll) - Update user dashboard for proper mobile layout: - Sticky header on mobile with backdrop blur - Content starts from top on mobile, centered on desktop - Move Wrapped card above quick links as hero element - Add "Quick Links" section label for visual separation - Redesign Wrapped card as eye-catching hero callout: - Large gradient year display with animated border - Floating sparkle animations - Prominent CTA buttons - Special states for loading, generating, completed, and failed - Redesign Announcements card as alert-style notification: - Animated gradient border with amber/orange theme - Animated bell icon that rings periodically - Pulsing "New" badge - Glowing dot indicators for each announcement - Simplify quick link cards (Plex, Requests, Discord): - Remove description text that was getting cut off - Clean icon + title + arrow layout - More compact padding - Consistent hover animations 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <[email protected]>
1 parent 8244e03 commit bc608ea

File tree

8 files changed

+420
-321
lines changed

8 files changed

+420
-321
lines changed

app/(app)/page.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -64,9 +64,9 @@ export default async function Home() {
6464
}
6565

6666
return (
67-
<main className="flex min-h-screen flex-col items-center justify-center bg-gradient-to-b from-slate-900 via-slate-800 to-slate-900">
68-
<div className="flex flex-col items-center gap-8">
69-
<h1 className="text-4xl sm:text-5xl md:text-6xl font-bold bg-gradient-to-r from-cyan-400 via-purple-400 to-pink-400 bg-clip-text text-transparent">
67+
<main className="fixed inset-0 flex flex-col items-center justify-center bg-gradient-to-b from-slate-900 via-slate-800 to-slate-900 px-4">
68+
<div className="flex flex-col items-center gap-6 sm:gap-8">
69+
<h1 className="text-4xl sm:text-5xl md:text-6xl font-bold bg-gradient-to-r from-cyan-400 via-purple-400 to-pink-400 bg-clip-text text-transparent text-center">
7070
{serverName}
7171
</h1>
7272
<PlexSignInButton
@@ -76,7 +76,7 @@ export default async function Home() {
7676
showDisclaimer={false}
7777
buttonText="Sign in with Plex"
7878
loadingText="Signing in..."
79-
buttonClassName="px-8 py-4 flex justify-center items-center gap-3 text-white text-lg font-semibold rounded-xl bg-gradient-to-r from-cyan-600 via-purple-600 to-pink-600 hover:from-cyan-500 hover:via-purple-500 hover:to-pink-500 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-purple-500/50 disabled:opacity-50 disabled:cursor-not-allowed transition-all duration-200 shadow-lg"
79+
buttonClassName="px-6 sm:px-8 py-3 sm:py-4 flex justify-center items-center gap-3 text-white text-base sm:text-lg font-semibold rounded-xl bg-gradient-to-r from-cyan-600 via-purple-600 to-pink-600 hover:from-cyan-500 hover:via-purple-500 hover:to-pink-500 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-purple-500/50 disabled:opacity-50 disabled:cursor-not-allowed transition-all duration-200 shadow-lg"
8080
/>
8181
</div>
8282
</main>

app/globals.css

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,19 @@
11
@import "tailwindcss";
22

3+
/* Base mobile-friendly styles */
4+
html,
5+
body {
6+
height: 100%;
7+
overflow-x: hidden;
8+
}
9+
10+
/* Use dvh for mobile viewport handling (accounts for browser chrome) */
11+
@supports (height: 100dvh) {
12+
body {
13+
min-height: 100dvh;
14+
}
15+
}
16+
317
@theme {
418
--animate-float: float 2s ease-in-out infinite;
519
--animate-wiggle: wiggle 1.5s ease-in-out infinite;
@@ -60,3 +74,12 @@
6074
}
6175
}
6276

77+
@keyframes gradient-shift {
78+
0%, 100% {
79+
background-position: 0% 50%;
80+
}
81+
50% {
82+
background-position: 100% 50%;
83+
}
84+
}
85+

components/dashboard/announcements-card.tsx

Lines changed: 81 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -14,79 +14,98 @@ interface AnnouncementsCardProps {
1414

1515
export function AnnouncementsCard({ announcements }: AnnouncementsCardProps) {
1616
if (announcements.length === 0) {
17-
return (
18-
<motion.div
19-
className="relative overflow-hidden rounded-2xl border border-slate-700/50 bg-gradient-to-br from-slate-900 via-slate-800/80 to-slate-900 p-6 shadow-xl shadow-black/20"
20-
initial={{ opacity: 0, y: 20 }}
21-
animate={{ opacity: 1, y: 0 }}
22-
transition={{ duration: 0.4, ease: "easeOut", delay: 0.1 }}
23-
data-testid="announcements-card-empty"
24-
>
25-
<div className="flex items-center gap-3">
26-
<div className="flex h-10 w-10 items-center justify-center rounded-lg bg-slate-700/50 text-slate-500">
27-
<svg className="h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
28-
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M11 5.882V19.24a1.76 1.76 0 01-3.417.592l-2.147-6.15M18 13a3 3 0 100-6M5.436 13.683A4.001 4.001 0 017 6h1.832c4.1 0 7.625-1.234 9.168-3v14c-1.543-1.766-5.067-3-9.168-3H7a3.988 3.988 0 01-1.564-.317z" />
29-
</svg>
30-
</div>
31-
<div>
32-
<h3 className="text-lg font-semibold text-white">Announcements</h3>
33-
<p className="text-sm text-slate-500">No announcements at this time</p>
34-
</div>
35-
</div>
36-
</motion.div>
37-
)
17+
return null
3818
}
3919

4020
return (
4121
<motion.div
42-
className="relative overflow-hidden rounded-2xl border border-cyan-500/20 bg-gradient-to-br from-slate-900 via-cyan-950/20 to-slate-900 shadow-xl shadow-black/20"
43-
initial={{ opacity: 0, y: 20 }}
22+
className="relative overflow-hidden rounded-xl sm:rounded-2xl"
23+
initial={{ opacity: 0, y: -10 }}
4424
animate={{ opacity: 1, y: 0 }}
45-
transition={{ duration: 0.4, ease: "easeOut", delay: 0.1 }}
25+
transition={{ duration: 0.4, ease: "easeOut" }}
4626
data-testid="announcements-card"
4727
>
48-
{/* Header */}
49-
<div className="flex items-center gap-3 border-b border-white/5 px-6 py-4">
50-
<div className="flex h-10 w-10 items-center justify-center rounded-lg bg-gradient-to-br from-cyan-500/20 to-cyan-600/10 text-cyan-400">
51-
<svg className="h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
52-
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M11 5.882V19.24a1.76 1.76 0 01-3.417.592l-2.147-6.15M18 13a3 3 0 100-6M5.436 13.683A4.001 4.001 0 017 6h1.832c4.1 0 7.625-1.234 9.168-3v14c-1.543-1.766-5.067-3-9.168-3H7a3.988 3.988 0 01-1.564-.317z" />
53-
</svg>
28+
{/* Animated gradient border */}
29+
<div
30+
className="absolute inset-0 rounded-xl sm:rounded-2xl bg-gradient-to-r from-amber-500 via-orange-500 to-amber-500 opacity-80"
31+
style={{
32+
backgroundSize: '200% 200%',
33+
animation: 'gradient-shift 3s ease infinite',
34+
}}
35+
/>
36+
37+
{/* Inner content */}
38+
<div className="relative m-[1px] rounded-xl sm:rounded-2xl bg-gradient-to-br from-amber-950/90 via-slate-900 to-slate-900">
39+
{/* Ambient glow */}
40+
<div className="pointer-events-none absolute -right-10 -top-10 h-32 w-32 rounded-full bg-amber-500/20 blur-3xl" />
41+
<div className="pointer-events-none absolute -left-10 -bottom-10 h-24 w-24 rounded-full bg-orange-500/10 blur-2xl" />
42+
43+
{/* Header */}
44+
<div className="relative flex items-center gap-3 border-b border-amber-500/20 px-4 sm:px-5 py-3 sm:py-4">
45+
{/* Animated bell icon */}
46+
<motion.div
47+
className="flex h-9 w-9 sm:h-10 sm:w-10 items-center justify-center rounded-lg bg-gradient-to-br from-amber-500 to-orange-600 shadow-lg shadow-amber-500/30"
48+
animate={{ rotate: [0, -10, 10, -10, 0] }}
49+
transition={{ duration: 0.5, repeat: Infinity, repeatDelay: 3 }}
50+
>
51+
<svg className="h-5 w-5 sm:h-5 sm:w-5 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
52+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 17h5l-1.405-1.405A2.032 2.032 0 0118 14.158V11a6.002 6.002 0 00-4-5.659V5a2 2 0 10-4 0v.341C7.67 6.165 6 8.388 6 11v3.159c0 .538-.214 1.055-.595 1.436L4 17h5m6 0v1a3 3 0 11-6 0v-1m6 0H9" />
53+
</svg>
54+
</motion.div>
55+
56+
<div className="flex-1">
57+
<div className="flex items-center gap-2">
58+
<h3 className="text-base sm:text-lg font-bold text-white">Announcements</h3>
59+
{/* Pulsing new badge */}
60+
<span className="relative flex">
61+
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-amber-400 opacity-50" />
62+
<span className="relative inline-flex items-center rounded-full bg-gradient-to-r from-amber-500 to-orange-500 px-2 py-0.5 text-[10px] font-bold uppercase tracking-wide text-white shadow-sm">
63+
New
64+
</span>
65+
</span>
66+
</div>
67+
<p className="text-xs text-amber-200/70">Important updates from the server</p>
68+
</div>
69+
70+
{announcements.length > 1 && (
71+
<span className="rounded-full bg-amber-500/20 border border-amber-500/30 px-2.5 py-0.5 text-xs font-bold text-amber-300">
72+
{announcements.length}
73+
</span>
74+
)}
5475
</div>
55-
<h3 className="text-lg font-semibold text-white">Announcements</h3>
56-
{announcements.length > 1 && (
57-
<span className="ml-auto rounded-full bg-cyan-500/20 px-2.5 py-0.5 text-xs font-medium text-cyan-300">
58-
{announcements.length}
59-
</span>
60-
)}
61-
</div>
6276

63-
{/* Announcements list */}
64-
<div className="max-h-64 overflow-y-auto">
65-
<AnimatePresence>
66-
{announcements.map((announcement, index) => (
67-
<motion.article
68-
key={announcement.id}
69-
className="border-b border-white/5 px-6 py-4 last:border-b-0"
70-
initial={{ opacity: 0, x: -10 }}
71-
animate={{ opacity: 1, x: 0 }}
72-
transition={{ duration: 0.3, delay: index * 0.05 }}
73-
data-testid={`announcement-${announcement.id}`}
74-
>
75-
<div className="flex items-start gap-3">
76-
<div className="mt-0.5 h-2 w-2 shrink-0 rounded-full bg-cyan-400" />
77-
<div className="flex-1 min-w-0">
78-
<h4 className="font-medium text-white">{announcement.title}</h4>
79-
<div className="mt-1 text-sm text-slate-300 prose prose-sm prose-invert max-w-none prose-p:my-1 prose-ul:my-1 prose-li:my-0.5 prose-a:text-cyan-400 prose-a:no-underline hover:prose-a:underline">
80-
<ReactMarkdown>{announcement.content}</ReactMarkdown>
77+
{/* Announcements list */}
78+
<div className="relative max-h-48 sm:max-h-56 overflow-y-auto">
79+
<AnimatePresence>
80+
{announcements.map((announcement, index) => (
81+
<motion.article
82+
key={announcement.id}
83+
className="border-b border-white/5 px-4 sm:px-5 py-3 sm:py-4 last:border-b-0 hover:bg-white/[0.02] transition-colors"
84+
initial={{ opacity: 0, x: -10 }}
85+
animate={{ opacity: 1, x: 0 }}
86+
transition={{ duration: 0.3, delay: index * 0.05 }}
87+
data-testid={`announcement-${announcement.id}`}
88+
>
89+
<div className="flex items-start gap-3">
90+
{/* Glowing dot indicator */}
91+
<div className="relative mt-1.5 shrink-0">
92+
<span className="absolute inline-flex h-2 w-2 rounded-full bg-amber-400 opacity-50 animate-ping" />
93+
<span className="relative inline-flex h-2 w-2 rounded-full bg-amber-400" />
94+
</div>
95+
<div className="flex-1 min-w-0">
96+
<h4 className="text-sm sm:text-base font-semibold text-white">{announcement.title}</h4>
97+
<div className="mt-1.5 text-xs sm:text-sm text-slate-300 prose prose-sm prose-invert max-w-none prose-p:my-1 prose-ul:my-1 prose-li:my-0.5 prose-a:text-amber-400 prose-a:no-underline hover:prose-a:underline">
98+
<ReactMarkdown>{announcement.content}</ReactMarkdown>
99+
</div>
100+
<p className="mt-2 text-xs text-amber-300/60 font-medium">
101+
{formatRelativeDate(announcement.createdAt)}
102+
</p>
81103
</div>
82-
<p className="mt-2 text-xs text-slate-500">
83-
{formatRelativeDate(announcement.createdAt)}
84-
</p>
85104
</div>
86-
</div>
87-
</motion.article>
88-
))}
89-
</AnimatePresence>
105+
</motion.article>
106+
))}
107+
</AnimatePresence>
108+
</div>
90109
</div>
91110
</motion.div>
92111
)

0 commit comments

Comments
 (0)