Improve mobile/Android UI responsiveness
- Add xs (375px) breakpoint to Tailwind config for small phone targeting - Fix main content bottom padding to account for safe-area-inset-bottom - Redesign mobile bottom nav: active dot indicator, proper safe-area padding, flex-1 tabs that scale to any width, tighter text on xs screens - Fix hero section: smaller padding/text on tiny phones (320-375px), horizontal badge wrapping, full-width buttons stack below xs - Add CSS improvements: overscroll-behavior, -webkit-text-size-adjust, input font-size fix for iOS zoom prevention, scroll-x helper class, touch-friendly min tap target sizes Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
This commit is contained in:
parent
1441855eb1
commit
aac2b7b9a2
4 changed files with 140 additions and 94 deletions
|
|
@ -236,88 +236,97 @@ function MobileNav({
|
|||
userName?: string;
|
||||
t: (key: string) => string;
|
||||
}) {
|
||||
// Decide which tabs to show — max 5 on small screens
|
||||
// Logged in: Home, Map, Lab, Events, Profile (wallet in profile dropdown)
|
||||
// Logged out: Home, Map, Lab, Events, Login
|
||||
const tabs = [
|
||||
...navItemKeys,
|
||||
...(isLoggedIn
|
||||
? [{ href: '/wallet', labelKey: 'nav.wallet', icon: Wallet }]
|
||||
: []),
|
||||
];
|
||||
|
||||
return (
|
||||
<nav className="fixed bottom-0 left-0 right-0 z-50 md:hidden">
|
||||
{/* Nav bar */}
|
||||
<div
|
||||
className="glass border-t border-white/10"
|
||||
style={{ paddingBottom: 'env(safe-area-inset-bottom, 0px)', paddingLeft: 'env(safe-area-inset-left, 0px)', paddingRight: 'env(safe-area-inset-right, 0px)' }}
|
||||
>
|
||||
<div className="flex items-stretch justify-around px-1 pt-1 pb-1">
|
||||
{tabs.map((item) => {
|
||||
const active = isActive(item.href);
|
||||
const isWallet = item.href === '/wallet';
|
||||
return (
|
||||
<nav className="fixed bottom-0 left-0 right-0 z-50 md:hidden safe-area-bottom">
|
||||
<div className="glass border-t border-border safe-area-left safe-area-right">
|
||||
<div className="flex items-center justify-around px-1 py-2">
|
||||
{navItemKeys.map((item) => (
|
||||
<Link
|
||||
key={item.href}
|
||||
to={item.href}
|
||||
className={cn(
|
||||
'flex flex-col items-center justify-center gap-0.5 py-2 px-2 rounded-xl transition-all duration-200 min-w-[48px] min-h-[48px] touch-manipulation active:scale-95',
|
||||
isActive(item.href)
|
||||
? 'text-primary'
|
||||
: 'text-muted-foreground'
|
||||
'relative flex flex-col items-center justify-center gap-0.5 py-1.5 px-1 rounded-xl transition-all duration-150 flex-1 touch-manipulation active:scale-90',
|
||||
active
|
||||
? isWallet ? 'text-orange-400' : 'text-[#ff0040]'
|
||||
: 'text-gray-500'
|
||||
)}
|
||||
>
|
||||
<item.icon className={cn(
|
||||
'h-5 w-5 transition-transform',
|
||||
isActive(item.href) && 'scale-110'
|
||||
{/* Active indicator dot */}
|
||||
{active && (
|
||||
<span className={cn(
|
||||
'absolute top-0 left-1/2 -translate-x-1/2 w-1 h-1 rounded-full',
|
||||
isWallet ? 'bg-orange-400' : 'bg-[#ff0040]'
|
||||
)} />
|
||||
<span className="text-[10px] font-medium leading-tight">{t(item.labelKey)}</span>
|
||||
</Link>
|
||||
))}
|
||||
{/* Wallet in mobile nav */}
|
||||
{isLoggedIn && (
|
||||
<Link
|
||||
to="/wallet"
|
||||
className={cn(
|
||||
'flex flex-col items-center justify-center gap-0.5 py-2 px-2 rounded-xl transition-all duration-200 min-w-[48px] min-h-[48px] relative touch-manipulation active:scale-95',
|
||||
isActive('/wallet')
|
||||
? 'text-orange-500'
|
||||
: 'text-muted-foreground'
|
||||
)}
|
||||
>
|
||||
{isWallet ? (
|
||||
<div className="relative">
|
||||
<Wallet className={cn(
|
||||
'h-5 w-5 transition-transform',
|
||||
isActive('/wallet') && 'scale-110'
|
||||
)} />
|
||||
<Wallet className={cn('h-[22px] w-[22px] transition-transform', active && 'scale-110')} />
|
||||
{cashuBalance > 0 && (
|
||||
<span className="absolute -top-1 -right-2 min-w-[16px] h-4 px-1 text-[8px] font-bold bg-orange-500 text-white rounded-full flex items-center justify-center">
|
||||
<span className="absolute -top-1.5 -right-2.5 min-w-[16px] h-4 px-1 text-[8px] font-bold bg-orange-500 text-white rounded-full flex items-center justify-center leading-none">
|
||||
{formatSatsCompact(cashuBalance)}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<span className="text-[10px] font-medium leading-tight">{t('nav.wallet')}</span>
|
||||
</Link>
|
||||
) : (
|
||||
<item.icon className={cn('h-[22px] w-[22px] transition-transform', active && 'scale-110')} />
|
||||
)}
|
||||
{/* Profile/Login in mobile nav */}
|
||||
<span className="text-[9px] xs:text-[10px] font-semibold leading-tight tracking-wide uppercase">
|
||||
{t(item.labelKey)}
|
||||
</span>
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
|
||||
{/* Profile / Login tab */}
|
||||
<Link
|
||||
to={isLoggedIn ? "/settings" : "/settings"}
|
||||
to="/settings"
|
||||
className={cn(
|
||||
'flex flex-col items-center justify-center gap-0.5 py-2 px-2 rounded-xl transition-all duration-200 min-w-[48px] min-h-[48px] relative touch-manipulation active:scale-95',
|
||||
isActive('/settings')
|
||||
? 'text-primary'
|
||||
: 'text-muted-foreground'
|
||||
'relative flex flex-col items-center justify-center gap-0.5 py-1.5 px-1 rounded-xl transition-all duration-150 flex-1 touch-manipulation active:scale-90',
|
||||
isActive('/settings') ? 'text-[#ff0040]' : 'text-gray-500'
|
||||
)}
|
||||
>
|
||||
{isActive('/settings') && (
|
||||
<span className="absolute top-0 left-1/2 -translate-x-1/2 w-1 h-1 rounded-full bg-[#ff0040]" />
|
||||
)}
|
||||
{isLoggedIn && userPubkey ? (
|
||||
<div className="relative">
|
||||
<Avatar className={cn(
|
||||
'h-6 w-6 border-2 transition-transform',
|
||||
isActive('/settings') ? 'border-primary scale-110' : 'border-gray-600'
|
||||
'h-[22px] w-[22px] border-2 transition-transform',
|
||||
isActive('/settings') ? 'border-[#ff0040] scale-110' : 'border-gray-600'
|
||||
)}>
|
||||
<AvatarImage src={userAvatar} alt={userName} />
|
||||
<AvatarFallback className="bg-[#ff0040]/20 text-[#ff0040] text-[8px] font-bold">
|
||||
{(userName || '??').slice(0, 2).toUpperCase()}
|
||||
</AvatarFallback>
|
||||
</Avatar>
|
||||
{/* Green online indicator dot */}
|
||||
<span className="absolute -bottom-0.5 -right-0.5 w-2.5 h-2.5 bg-green-500 border-2 border-black rounded-full" />
|
||||
<span className="absolute -bottom-0.5 -right-0.5 w-2 h-2 bg-green-500 border border-black rounded-full" />
|
||||
</div>
|
||||
) : (
|
||||
<User className="h-5 w-5" />
|
||||
<User className="h-[22px] w-[22px]" />
|
||||
)}
|
||||
<span className="text-[10px] font-medium leading-tight">
|
||||
{isLoggedIn ? 'Profile' : 'Log In'}
|
||||
<span className="text-[9px] xs:text-[10px] font-semibold leading-tight tracking-wide uppercase">
|
||||
{isLoggedIn ? 'Me' : 'Log In'}
|
||||
</span>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
{/* Safe area spacer for notched devices */}
|
||||
<div className="h-safe-area-bottom bg-background" />
|
||||
</nav>
|
||||
);
|
||||
}
|
||||
|
|
@ -741,8 +750,8 @@ export function Layout({ children }: LayoutProps) {
|
|||
{/* Backup Reminder */}
|
||||
<BackupReminder />
|
||||
|
||||
{/* Main Content */}
|
||||
<main className="pb-20 md:pb-6">
|
||||
{/* Main Content — extra bottom padding on mobile so the fixed bottom nav never overlaps */}
|
||||
<main className="pb-[calc(5rem+env(safe-area-inset-bottom,0px))] md:pb-6">
|
||||
{children}
|
||||
</main>
|
||||
|
||||
|
|
|
|||
|
|
@ -347,25 +347,54 @@
|
|||
html, body {
|
||||
overflow-x: hidden;
|
||||
max-width: 100vw;
|
||||
/* Prevent elastic overscroll on the root — pages handle their own scroll */
|
||||
overscroll-behavior: none;
|
||||
}
|
||||
|
||||
/* Improve touch targets for mobile */
|
||||
@media (max-width: 768px) {
|
||||
button, a, input, select, textarea {
|
||||
min-height: 44px;
|
||||
/* Smooth momentum scrolling on iOS */
|
||||
* {
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
|
||||
/* Better tap highlight on mobile */
|
||||
/* Better tap highlight on mobile — subtle red glow */
|
||||
a, button {
|
||||
-webkit-tap-highlight-color: rgba(255, 0, 64, 0.1);
|
||||
-webkit-tap-highlight-color: rgba(255, 0, 64, 0.08);
|
||||
}
|
||||
|
||||
/* Improve touch targets for mobile — WCAG minimum 44×44px */
|
||||
@media (max-width: 768px) {
|
||||
button, [role="button"] {
|
||||
min-height: 44px;
|
||||
min-width: 44px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Fix input zoom on iOS */
|
||||
@media screen and (max-width: 768px) {
|
||||
/* Fix input zoom on iOS — font-size must be ≥16px to prevent auto-zoom */
|
||||
input, select, textarea {
|
||||
font-size: 16px !important;
|
||||
font-size: max(16px, 1em);
|
||||
}
|
||||
|
||||
/* Prevent text size adjustments after orientation changes on iOS */
|
||||
html {
|
||||
-webkit-text-size-adjust: 100%;
|
||||
text-size-adjust: 100%;
|
||||
}
|
||||
|
||||
/* Better scrolling containers — prevent pull-to-refresh on inner scrollable areas */
|
||||
.scroll-container {
|
||||
overscroll-behavior-y: contain;
|
||||
}
|
||||
|
||||
/* Touch-friendly horizontal scroll rows (e.g. filter chips) */
|
||||
.scroll-x {
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
overscroll-behavior-x: contain;
|
||||
scroll-snap-type: x proximity;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
.scroll-x > * {
|
||||
scroll-snap-align: start;
|
||||
}
|
||||
|
||||
/* Hide scrollbar utility - useful for horizontal scrolling filters */
|
||||
|
|
|
|||
|
|
@ -40,62 +40,62 @@ function HeroSection() {
|
|||
<div className="absolute bottom-0 left-0 w-[500px] h-[500px] bg-[#ccff00] opacity-15 rounded-full blur-[120px]" />
|
||||
<div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-[400px] h-[400px] bg-[#ff6600] opacity-10 rounded-full blur-[100px]" />
|
||||
|
||||
<div className="relative container px-4 sm:px-6 md:px-8 py-16 sm:py-20 md:py-28">
|
||||
<div className="relative container px-3 xs:px-4 sm:px-6 md:px-8 py-10 xs:py-14 sm:py-20 md:py-28">
|
||||
<div className="max-w-4xl mx-auto text-center">
|
||||
<div className="mb-8">
|
||||
<div className="inline-block px-6 py-2 rounded-full border-2 border-[#ff0040]/50 bg-black/50 backdrop-blur-sm mb-6">
|
||||
<span className="text-[#ff0040] font-bold uppercase tracking-wider text-sm">
|
||||
<Sparkles className="inline h-4 w-4 mr-2" />
|
||||
<div className="mb-6 xs:mb-8">
|
||||
<div className="inline-flex items-center px-3 xs:px-6 py-1.5 xs:py-2 rounded-full border-2 border-[#ff0040]/50 bg-black/50 backdrop-blur-sm mb-4 xs:mb-6">
|
||||
<span className="text-[#ff0040] font-bold uppercase tracking-wider text-xs xs:text-sm">
|
||||
<Sparkles className="inline h-3 w-3 xs:h-4 xs:w-4 mr-1 xs:mr-2" />
|
||||
{t('home.tagline')}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<h1 className="text-4xl sm:text-5xl md:text-7xl lg:text-8xl font-black uppercase mb-6 leading-[0.9]" style={{
|
||||
<h1 className="text-3xl xs:text-4xl sm:text-5xl md:text-7xl lg:text-8xl font-black uppercase mb-4 xs:mb-6 leading-[0.9]" style={{
|
||||
fontFamily: 'Impact, "Arial Black", sans-serif',
|
||||
letterSpacing: '0.05em'
|
||||
}}>
|
||||
<span className="block gradient-text">{t('home.title')}</span>
|
||||
</h1>
|
||||
|
||||
<p className="text-base sm:text-lg md:text-xl lg:text-2xl text-gray-200 mb-3 md:mb-4 max-w-2xl mx-auto leading-relaxed font-bold uppercase tracking-wide px-4">
|
||||
<p className="text-sm xs:text-base sm:text-lg md:text-xl lg:text-2xl text-gray-200 mb-2 md:mb-4 max-w-2xl mx-auto leading-relaxed font-bold uppercase tracking-wide px-2 xs:px-4">
|
||||
{t('home.subtitle')}
|
||||
</p>
|
||||
<p className="text-sm md:text-base text-gray-400 max-w-xl mx-auto px-4 mb-8">
|
||||
<p className="text-xs xs:text-sm md:text-base text-gray-400 max-w-xl mx-auto px-2 xs:px-4 mb-6 xs:mb-8">
|
||||
{t('home.description')}
|
||||
</p>
|
||||
|
||||
<div className="space-y-3 mb-8">
|
||||
<div className="inline-flex items-center gap-2 px-4 py-2 rounded-full bg-yellow-500/20 border border-yellow-500/50">
|
||||
<span className="text-yellow-400 font-bold text-sm uppercase">
|
||||
<div className="flex flex-wrap items-center justify-center gap-2 mb-6 xs:mb-8 px-2">
|
||||
<div className="inline-flex items-center gap-1.5 px-3 py-1.5 rounded-full bg-yellow-500/20 border border-yellow-500/50">
|
||||
<span className="text-yellow-400 font-bold text-xs uppercase">
|
||||
⚠️ {t('home.betaWarning')}
|
||||
</span>
|
||||
</div>
|
||||
<div className="inline-flex items-center gap-2 px-4 py-2 rounded-full bg-blue-500/20 border border-blue-500/50 ml-2">
|
||||
<span className="text-blue-400 font-bold text-sm uppercase">
|
||||
<div className="inline-flex items-center gap-1.5 px-3 py-1.5 rounded-full bg-blue-500/20 border border-blue-500/50">
|
||||
<span className="text-blue-400 font-bold text-xs uppercase">
|
||||
📱 {t('home.pwaHint')}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col sm:flex-row items-center justify-center gap-3 sm:gap-4 mb-6 px-4">
|
||||
<Link to="/map" className="w-full sm:w-auto">
|
||||
<div className="flex flex-col xs:flex-row items-stretch xs:items-center justify-center gap-2 xs:gap-3 sm:gap-4 mb-6 px-2 xs:px-4">
|
||||
<Link to="/map" className="w-full xs:w-auto">
|
||||
<Button
|
||||
size="lg"
|
||||
className="w-full sm:w-auto gap-2 bg-[#ff0040] hover:bg-[#ff0040]/90 text-white border-2 border-[#ff0040] shadow-[0_0_20px_rgba(255,0,64,0.5)] font-bold uppercase px-6 sm:px-8 py-4 sm:py-6 text-sm sm:text-base"
|
||||
className="w-full xs:w-auto gap-2 bg-[#ff0040] hover:bg-[#ff0040]/90 text-white border-2 border-[#ff0040] shadow-[0_0_20px_rgba(255,0,64,0.5)] font-bold uppercase px-5 sm:px-8 py-3 xs:py-4 sm:py-6 text-sm sm:text-base h-auto"
|
||||
>
|
||||
<MapPin className="h-4 w-4 sm:h-5 sm:w-5" />
|
||||
<MapPin className="h-4 w-4 sm:h-5 sm:w-5 flex-shrink-0" />
|
||||
{t('home.exploreMap')}
|
||||
</Button>
|
||||
</Link>
|
||||
{user && (
|
||||
<Link to="/create-spot" className="w-full sm:w-auto">
|
||||
<Link to="/create-spot" className="w-full xs:w-auto">
|
||||
<Button
|
||||
size="lg"
|
||||
variant="outline"
|
||||
className="w-full sm:w-auto gap-2 border-2 border-[#ccff00] bg-transparent hover:bg-[#ccff00]/10 text-[#ccff00] shadow-[0_0_15px_rgba(204,255,0,0.3)] font-bold uppercase px-6 sm:px-8 py-4 sm:py-6 text-sm sm:text-base"
|
||||
className="w-full xs:w-auto gap-2 border-2 border-[#ccff00] bg-transparent hover:bg-[#ccff00]/10 text-[#ccff00] shadow-[0_0_15px_rgba(204,255,0,0.3)] font-bold uppercase px-5 sm:px-8 py-3 xs:py-4 sm:py-6 text-sm sm:text-base h-auto"
|
||||
>
|
||||
<Plus className="h-4 w-4 sm:h-5 sm:w-5" />
|
||||
<Plus className="h-4 w-4 sm:h-5 sm:w-5 flex-shrink-0" />
|
||||
{t('home.addSpot')}
|
||||
</Button>
|
||||
</Link>
|
||||
|
|
|
|||
|
|
@ -18,6 +18,14 @@ export default {
|
|||
'2xl': '1400px'
|
||||
}
|
||||
},
|
||||
screens: {
|
||||
'xs': '375px',
|
||||
'sm': '640px',
|
||||
'md': '768px',
|
||||
'lg': '1024px',
|
||||
'xl': '1280px',
|
||||
'2xl': '1400px',
|
||||
},
|
||||
extend: {
|
||||
fontFamily: {
|
||||
sans: ['Outfit Variable', 'Outfit', 'system-ui', 'sans-serif'],
|
||||
|
|
|
|||
Loading…
Reference in a new issue