fix: newsletter mobile layout — full phone support

- Page container: py-8 on mobile + pb-24 so content clears fixed bottom nav
- Masthead headline: single line (no br), text-4xl mobile / text-6xl sm+
- Issue header: vertical stack on all screens — date/badge row, large number,
  vibe text, then publish button full-width on mobile / auto on sm+
- Stats grid: always 3 cols (clean 2-row layout on phone), larger text-2xl
  numbers, text-[10px] labels readable on all Androids, shorter relay label
- Floating pill: max-w-[calc(100vw-1.5rem)] prevents overflow on narrow screens,
  right-3 tighter margin, whitespace-nowrap on text, touch-manipulation + 
  active:scale-95 for native tap feel, correct bottom offset for iOS safe area
- Monday admin banner: unchanged, already stacks correctly

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
This commit is contained in:
shakespeare.diy 2026-06-18 14:56:30 +07:00
parent 248f5b5ff2
commit 4f7b741d77
2 changed files with 35 additions and 32 deletions

View file

@ -69,22 +69,22 @@ function LatestDropPill() {
} }
return ( return (
<div className="fixed bottom-[calc(4.5rem+env(safe-area-inset-bottom,0px))] md:bottom-6 right-4 z-40 flex items-center gap-2 animate-in slide-in-from-bottom-4 duration-300"> <div className="fixed bottom-[calc(4.75rem+env(safe-area-inset-bottom,0px))] md:bottom-6 right-3 z-40 flex items-center gap-1.5 animate-in slide-in-from-bottom-4 duration-300 max-w-[calc(100vw-1.5rem)]">
<Link <Link
to="/newsletter" to="/newsletter"
className="flex items-center gap-2 pl-3 pr-2 py-2 rounded-full bg-black/95 border border-[#ccff00]/50 shadow-lg shadow-[#ccff00]/10 hover:border-[#ccff00] hover:shadow-[#ccff00]/20 transition-all group" className="flex items-center gap-2 pl-3 pr-3 py-2 rounded-full bg-black/98 border border-[#ccff00]/50 shadow-lg shadow-[#ccff00]/10 hover:border-[#ccff00] transition-all active:scale-95 touch-manipulation"
> >
<Radio className="h-3.5 w-3.5 text-[#ccff00] animate-pulse" /> <Radio className="h-3.5 w-3.5 text-[#ccff00] animate-pulse flex-shrink-0" />
<span className="text-xs font-black text-[#ccff00] uppercase tracking-wide"> <span className="text-xs font-black text-[#ccff00] uppercase tracking-wide whitespace-nowrap">
{formatIssueNumber(latest.number)} is out {formatIssueNumber(latest.number)} is out
</span> </span>
<span className="text-[10px] text-gray-500 hidden sm:inline pr-1"> <span className="text-[10px] text-gray-500 hidden sm:inline">
{age === 0 ? 'Today' : age === 1 ? 'Yesterday' : `${age}d ago`} · {age === 0 ? 'Today' : age === 1 ? 'Yesterday' : `${age}d ago`}
</span> </span>
</Link> </Link>
<button <button
onClick={() => setDismissed(latest.number)} onClick={() => setDismissed(latest.number)}
className="flex items-center justify-center w-7 h-7 rounded-full bg-black/95 border border-white/10 text-gray-500 hover:text-white hover:border-white/30 transition-colors" className="flex items-center justify-center w-7 h-7 flex-shrink-0 rounded-full bg-black/98 border border-white/10 text-gray-500 hover:text-white hover:border-white/30 transition-colors touch-manipulation active:scale-95"
aria-label="Dismiss" aria-label="Dismiss"
> >
<X className="h-3 w-3" /> <X className="h-3 w-3" />

View file

@ -192,7 +192,7 @@ function PublishToNostrButton({ issue }: { issue: NewsletterIssue }) {
<Button <Button
onClick={handlePublish} onClick={handlePublish}
disabled={isPending} disabled={isPending}
className="gap-2 bg-[#ccff00] text-black hover:bg-[#ccff00]/90 font-black uppercase text-xs h-9" className="gap-2 bg-[#ccff00] text-black hover:bg-[#ccff00]/90 font-black uppercase text-xs h-10 w-full sm:w-auto"
> >
<Share2 className="h-3.5 w-3.5" /> <Share2 className="h-3.5 w-3.5" />
{isPending ? 'Publishing...' : 'Post to Nostr'} {isPending ? 'Publishing...' : 'Post to Nostr'}
@ -215,21 +215,21 @@ function LiveStatsBlock() {
]; ];
return ( return (
<div className="mt-4 p-5 rounded-2xl border-2 border-white/8 bg-white/3"> <div className="mt-4 p-4 rounded-2xl border-2 border-white/8 bg-white/3">
<div className="grid grid-cols-3 sm:grid-cols-6 gap-3"> <div className="grid grid-cols-3 gap-4">
{items.map((item) => ( {items.map((item) => (
<div key={item.label} className="text-center"> <div key={item.label} className="text-center py-2">
<div className="flex justify-center mb-1">{item.icon}</div> <div className="flex justify-center mb-1.5">{item.icon}</div>
{isLoading ? ( {isLoading ? (
<Skeleton className="h-6 w-10 mx-auto mb-1" /> <Skeleton className="h-7 w-12 mx-auto mb-1" />
) : ( ) : (
<div className="font-black text-xl leading-none" style={{ color: item.color }}> <div className="font-black text-2xl leading-none" style={{ color: item.color }}>
{(item.value ?? 0).toLocaleString()} {(item.value ?? 0).toLocaleString()}
</div> </div>
)} )}
<div className="text-[9px] uppercase tracking-widest text-gray-600 mt-0.5">{item.label}</div> <div className="text-[10px] uppercase tracking-widest text-gray-500 mt-1">{item.label}</div>
{item.trusted !== undefined && !isLoading && ( {item.trusted !== undefined && !isLoading && (
<div className="text-[9px] text-green-500/70 mt-0.5"> <div className="text-[10px] text-green-500/60 mt-0.5">
🛡 {item.trusted} 🛡 {item.trusted}
</div> </div>
)} )}
@ -237,7 +237,7 @@ function LiveStatsBlock() {
))} ))}
</div> </div>
<p className="text-[10px] text-gray-600 text-center mt-3 uppercase tracking-widest"> <p className="text-[10px] text-gray-600 text-center mt-3 uppercase tracking-widest">
Live · pulled from wss://goskatespots.nostr1.com Live · goskatespots.nostr1.com
</p> </p>
</div> </div>
); );
@ -357,23 +357,27 @@ function IssueArticle({ issue }: { issue: NewsletterIssue }) {
return ( return (
<article> <article>
{/* Issue header */} {/* Issue header */}
<div className="flex items-start justify-between gap-4 mb-8 pb-6 border-b border-white/8"> <div className="mb-8 pb-6 border-b border-white/8">
<div> {/* Top row: issue number badge + date */}
<div className="text-[10px] font-black uppercase tracking-widest text-gray-500 mb-1"> <div className="flex items-center justify-between gap-3 mb-3">
<div className="text-[10px] font-black uppercase tracking-widest text-gray-500">
{formatIssueDate(issue.date)} {formatIssueDate(issue.date)}
</div> </div>
<h2 className="text-3xl font-black uppercase leading-none text-white" <div className="w-10 h-10 rounded-xl bg-gradient-to-br from-[#ff0040] to-[#ff6600] flex items-center justify-center shadow-lg shadow-[#ff0040]/30 flex-shrink-0">
style={{ fontFamily: 'Impact, "Arial Black", sans-serif' }}> <span className="text-white font-black text-[10px]" style={{ fontFamily: 'Impact, "Arial Black", sans-serif' }}>
#{String(issue.number).padStart(3, '0')}
</h2>
<p className="text-gray-400 text-sm mt-2 max-w-md">{issue.vibe}</p>
</div>
<div className="flex flex-col items-end gap-3">
<div className="w-14 h-14 rounded-2xl bg-gradient-to-br from-[#ff0040] to-[#ff6600] flex items-center justify-center shadow-lg shadow-[#ff0040]/30 flex-shrink-0">
<span className="text-white font-black text-sm" style={{ fontFamily: 'Impact, "Arial Black", sans-serif' }}>
#{String(issue.number).padStart(3, '0')} #{String(issue.number).padStart(3, '0')}
</span> </span>
</div> </div>
</div>
{/* Issue number large */}
<h2 className="text-4xl sm:text-5xl font-black uppercase leading-none text-white mb-2"
style={{ fontFamily: 'Impact, "Arial Black", sans-serif' }}>
#{String(issue.number).padStart(3, '0')}
</h2>
{/* Vibe line */}
<p className="text-gray-400 text-sm leading-relaxed mb-4">{issue.vibe}</p>
{/* Publish button — full width on mobile, auto on larger */}
<div className="flex">
<PublishToNostrButton issue={issue} /> <PublishToNostrButton issue={issue} />
</div> </div>
</div> </div>
@ -481,7 +485,7 @@ export default function NewsletterPage() {
return ( return (
<Layout> <Layout>
<div className="container max-w-2xl px-4 py-10 md:py-14"> <div className="container max-w-2xl px-4 py-8 md:py-14 pb-24 md:pb-14">
{/* ── Masthead ── */} {/* ── Masthead ── */}
<div className="mb-12 text-center"> <div className="mb-12 text-center">
@ -490,13 +494,12 @@ export default function NewsletterPage() {
Weekly Drop Weekly Drop
</div> </div>
<h1 <h1
className="text-5xl sm:text-6xl font-black uppercase leading-none mb-3" className="text-4xl sm:text-6xl font-black uppercase leading-none mb-3"
style={{ fontFamily: 'Impact, "Arial Black", sans-serif' }} style={{ fontFamily: 'Impact, "Arial Black", sans-serif' }}
> >
<span className="bg-gradient-to-r from-[#ff0040] via-[#ff6600] to-[#ccff00] bg-clip-text text-transparent"> <span className="bg-gradient-to-r from-[#ff0040] via-[#ff6600] to-[#ccff00] bg-clip-text text-transparent">
SKATE SKATE
</span> </span>
<br />
<span className="text-white">SPOTS</span> <span className="text-white">SPOTS</span>
</h1> </h1>
<p className="text-gray-500 text-xs uppercase tracking-widest"> <p className="text-gray-500 text-xs uppercase tracking-widest">