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:
shakespeare.diy 2026-06-17 22:04:29 +07:00
parent 1441855eb1
commit aac2b7b9a2
4 changed files with 140 additions and 94 deletions

View file

@ -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>

View file

@ -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 */

View file

@ -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>

View file

@ -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'],