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 (
<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
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" />
<span className="text-xs font-black text-[#ccff00] uppercase tracking-wide">
<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 whitespace-nowrap">
{formatIssueNumber(latest.number)} is out
</span>
<span className="text-[10px] text-gray-500 hidden sm:inline pr-1">
{age === 0 ? 'Today' : age === 1 ? 'Yesterday' : `${age}d ago`}
<span className="text-[10px] text-gray-500 hidden sm:inline">
· {age === 0 ? 'Today' : age === 1 ? 'Yesterday' : `${age}d ago`}
</span>
</Link>
<button
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"
>
<X className="h-3 w-3" />

View file

@ -192,7 +192,7 @@ function PublishToNostrButton({ issue }: { issue: NewsletterIssue }) {
<Button
onClick={handlePublish}
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" />
{isPending ? 'Publishing...' : 'Post to Nostr'}
@ -215,21 +215,21 @@ function LiveStatsBlock() {
];
return (
<div className="mt-4 p-5 rounded-2xl border-2 border-white/8 bg-white/3">
<div className="grid grid-cols-3 sm:grid-cols-6 gap-3">
<div className="mt-4 p-4 rounded-2xl border-2 border-white/8 bg-white/3">
<div className="grid grid-cols-3 gap-4">
{items.map((item) => (
<div key={item.label} className="text-center">
<div className="flex justify-center mb-1">{item.icon}</div>
<div key={item.label} className="text-center py-2">
<div className="flex justify-center mb-1.5">{item.icon}</div>
{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()}
</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 && (
<div className="text-[9px] text-green-500/70 mt-0.5">
<div className="text-[10px] text-green-500/60 mt-0.5">
🛡 {item.trusted}
</div>
)}
@ -237,7 +237,7 @@ function LiveStatsBlock() {
))}
</div>
<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>
</div>
);
@ -357,23 +357,27 @@ function IssueArticle({ issue }: { issue: NewsletterIssue }) {
return (
<article>
{/* Issue header */}
<div className="flex items-start justify-between gap-4 mb-8 pb-6 border-b border-white/8">
<div>
<div className="text-[10px] font-black uppercase tracking-widest text-gray-500 mb-1">
<div className="mb-8 pb-6 border-b border-white/8">
{/* Top row: issue number badge + date */}
<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)}
</div>
<h2 className="text-3xl font-black uppercase leading-none text-white"
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' }}>
<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">
<span className="text-white font-black text-[10px]" style={{ fontFamily: 'Impact, "Arial Black", sans-serif' }}>
#{String(issue.number).padStart(3, '0')}
</span>
</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} />
</div>
</div>
@ -481,7 +485,7 @@ export default function NewsletterPage() {
return (
<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 ── */}
<div className="mb-12 text-center">
@ -490,13 +494,12 @@ export default function NewsletterPage() {
Weekly Drop
</div>
<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' }}
>
<span className="bg-gradient-to-r from-[#ff0040] via-[#ff6600] to-[#ccff00] bg-clip-text text-transparent">
SKATE
</span>
<br />
<span className="text-white">SPOTS</span>
</h1>
<p className="text-gray-500 text-xs uppercase tracking-widest">