Skip to content

Commit 82fbaca

Browse files
patrickrbclaude
andcommitted
feat(ui): make new site design mobile-responsive
The redesign was built desktop-first with no media queries below 800px. This adds a three-tier responsive treatment (mobile <=640 / tablet 641-1024 / desktop >1024) across both the static design mockup and the live app. Live app (src/): - Navbar.tsx gets a useState hamburger that exposes all primary + more links in a single flat panel on <md viewports. - Landing page: hero CTAs stack with w-full sm:w-auto, preview grid collapses to single column below lg, section padding tightened. - Dashboard: page padding scaled with viewport; hero (map + Quick log) and log + DXpeditions grids drop to single column at lg; band-activity strip wraps to a 4-col grid on mobile. The 7-column QSO table renders as stacked QSO cards on <md via a parallel md:hidden tree over the same filteredContacts (no <Table> changes). - New-contact: page padding, outer layout single-col below xl, callsign mega-input scales 32->56px, lookup card grid restructures, form footer reverses with Save QSO on top on mobile. Static mockup (new-design/): - Global mobile/tablet rules at end of styles.css including the hamburger CSS-only toggle and a .tbl card-view with data-label ::before fallbacks. - Hamburger HTML + per-page media queries in all three pages. - data-label attributes on every <td> in the dashboard table for the card-view labels. Verified: lint, typecheck, and build all clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 60fa5c7 commit 82fbaca

8 files changed

Lines changed: 2379 additions & 138 deletions

File tree

new-design/dashboard.html

Lines changed: 662 additions & 0 deletions
Large diffs are not rendered by default.

new-design/landing.html

Lines changed: 509 additions & 0 deletions
Large diffs are not rendered by default.

new-design/new-contact.html

Lines changed: 525 additions & 0 deletions
Large diffs are not rendered by default.

new-design/styles.css

Lines changed: 445 additions & 0 deletions
Large diffs are not rendered by default.

src/app/dashboard/page.tsx

Lines changed: 169 additions & 101 deletions
Large diffs are not rendered by default.

src/app/new-contact/page.tsx

Lines changed: 13 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -424,7 +424,7 @@ export default function NewContactPage() {
424424
<div className="min-h-screen">
425425
<Navbar />
426426

427-
<main className="max-w-[1400px] mx-auto px-6 lg:px-8 py-8">
427+
<main className="max-w-[1400px] mx-auto px-4 sm:px-6 lg:px-8 py-6 sm:py-8">
428428
<PageHeader
429429
title="Log a contact"
430430
sub={
@@ -449,15 +449,12 @@ export default function NewContactPage() {
449449
/>
450450

451451
<form onSubmit={handleSubmit}>
452-
<div
453-
className="grid gap-6"
454-
style={{ gridTemplateColumns: 'minmax(0, 1.4fr) minmax(0, 1fr)' }}
455-
>
452+
<div className="grid gap-6 grid-cols-1 xl:[grid-template-columns:minmax(0,1.4fr)_minmax(0,1fr)]">
456453
{/* LEFT — form */}
457454
<div className="flex flex-col gap-5 min-w-0">
458455
{/* Callsign hero */}
459456
<Card
460-
className="p-8 flex flex-col gap-5"
457+
className="p-5 sm:p-8 flex flex-col gap-5"
461458
style={{
462459
background:
463460
'linear-gradient(180deg, rgba(77,208,255,0.04), transparent 60%), var(--card)',
@@ -483,7 +480,7 @@ export default function NewContactPage() {
483480
placeholder="W1AW"
484481
autoComplete="off"
485482
className={cn(
486-
'w-full bg-transparent border-0 border-b-2 border-line-hi text-fg font-mono text-[56px] font-semibold tracking-[0.04em] py-4 outline-none transition-colors uppercase placeholder:text-fg-3',
483+
'w-full bg-transparent border-0 border-b-2 border-line-hi text-fg font-mono text-[32px] sm:text-[56px] font-semibold tracking-[0.04em] py-3 sm:py-4 outline-none transition-colors uppercase placeholder:text-fg-3',
487484
'focus:border-accent',
488485
validationErrors.callsign ? 'border-bad' : ''
489486
)}
@@ -501,16 +498,15 @@ export default function NewContactPage() {
501498
{lookupResult ? (
502499
lookupResult.found ? (
503500
<div
504-
className="grid items-center gap-4 p-4 rounded-[14px] border border-accent-glow"
501+
className="grid items-center gap-4 p-4 rounded-[14px] border border-accent-glow [grid-template-columns:48px_1fr] sm:[grid-template-columns:56px_1fr_auto]"
505502
style={{
506-
gridTemplateColumns: '56px 1fr auto',
507503
background:
508504
'linear-gradient(180deg, var(--accent-soft), transparent), var(--bg-1)',
509505
}}
510506
>
511507
<div
512508
aria-hidden="true"
513-
className="w-14 h-14 rounded-[12px] grid place-items-center text-[#051018] font-mono font-bold text-lg"
509+
className="w-12 h-12 sm:w-14 sm:h-14 rounded-[12px] grid place-items-center text-[#051018] font-mono font-bold text-lg"
514510
style={{
515511
background:
516512
'linear-gradient(135deg, var(--accent), #7a9bff)',
@@ -535,7 +531,7 @@ export default function NewContactPage() {
535531
</div>
536532
) : null}
537533
</div>
538-
<Chip variant="ok">
534+
<Chip variant="ok" className="col-span-2 sm:col-span-1 justify-self-start sm:justify-self-auto">
539535
<Check className="h-3.5 w-3.5" />
540536
Verified
541537
</Chip>
@@ -550,7 +546,7 @@ export default function NewContactPage() {
550546
</Card>
551547

552548
{/* Band & Mode */}
553-
<Card className="p-6">
549+
<Card className="p-4 sm:p-6">
554550
<div className="flex items-center justify-between mb-4">
555551
<h3 className="text-[17px] font-semibold">Band &amp; Mode</h3>
556552
{formData.frequency ? (
@@ -643,7 +639,7 @@ export default function NewContactPage() {
643639
</Card>
644640

645641
{/* Signal report & details */}
646-
<Card className="p-6">
642+
<Card className="p-4 sm:p-6">
647643
<h3 className="text-[17px] font-semibold mb-4">
648644
Signal report &amp; details
649645
</h3>
@@ -792,12 +788,12 @@ export default function NewContactPage() {
792788
</div>
793789
) : null}
794790

795-
<div className="flex items-center gap-3 pt-1">
796-
<Button asChild variant="secondary">
791+
<div className="flex flex-col-reverse sm:flex-row sm:items-center gap-3 pt-1">
792+
<Button asChild variant="secondary" className="w-full sm:w-auto">
797793
<Link href="/dashboard">Cancel</Link>
798794
</Button>
799-
<div className="flex-1" />
800-
<Button type="submit" size="lg" disabled={isLoading}>
795+
<div className="hidden sm:block sm:flex-1" />
796+
<Button type="submit" size="lg" disabled={isLoading} className="w-full sm:w-auto">
801797
{isLoading ? (
802798
<>
803799
<Loader2 className="animate-spin" />

src/app/page.tsx

Lines changed: 15 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ export default function Home() {
129129
return (
130130
<div className="flex-1">
131131
{/* Transparent landing topbar */}
132-
<header className="flex items-center gap-7 px-10 py-6">
132+
<header className="flex items-center gap-4 sm:gap-7 px-4 sm:px-6 lg:px-10 py-4 sm:py-6">
133133
<BrandLockup href="/" />
134134
<nav className="hidden md:flex items-center gap-1 ml-2">
135135
<a
@@ -145,8 +145,8 @@ export default function Home() {
145145
Dashboard
146146
</Link>
147147
</nav>
148-
<div className="ml-auto flex items-center gap-3">
149-
<Button asChild variant="ghost">
148+
<div className="ml-auto flex items-center gap-2 sm:gap-3">
149+
<Button asChild variant="ghost" className="hidden sm:inline-flex">
150150
<Link href="/login">Sign in</Link>
151151
</Button>
152152
<Button asChild>
@@ -156,7 +156,7 @@ export default function Home() {
156156
</header>
157157

158158
{/* Hero */}
159-
<section className="px-10 pt-20 pb-16 max-w-[1280px] mx-auto text-center">
159+
<section className="px-4 sm:px-6 lg:px-10 pt-12 sm:pt-20 pb-12 sm:pb-16 max-w-[1280px] mx-auto text-center">
160160
<Chip variant="accent" className="mb-7">
161161
<Dot tone="ok" live />
162162
Open source · self-host or cloud
@@ -197,18 +197,18 @@ export default function Home() {
197197
Log a QSO in three keystrokes, sync to LoTW &amp; QRZ automatically,
198198
and watch your contacts light up the world.
199199
</p>
200-
<div className="flex flex-col sm:flex-row gap-3 justify-center items-center">
201-
<Button asChild size="lg">
200+
<div className="flex flex-col sm:flex-row gap-3 justify-center sm:items-center">
201+
<Button asChild size="lg" className="w-full sm:w-auto">
202202
<Link href="/register">Try it free</Link>
203203
</Button>
204-
<Button asChild variant="secondary" size="lg">
204+
<Button asChild variant="secondary" size="lg" className="w-full sm:w-auto">
205205
<Link href="/dashboard">See live logging</Link>
206206
</Button>
207207
</div>
208208
</section>
209209

210210
{/* Preview mockup */}
211-
<div className="relative max-w-[1280px] mx-auto px-10 pb-24">
211+
<div className="relative max-w-[1280px] mx-auto px-4 sm:px-6 lg:px-10 pb-16 sm:pb-24">
212212
<div
213213
className="absolute pointer-events-none"
214214
style={{
@@ -241,11 +241,11 @@ export default function Home() {
241241
</Chip>
242242
</div>
243243
</div>
244-
<div className="grid min-h-[460px]" style={{ gridTemplateColumns: '1.6fr 1fr' }}>
244+
<div className="grid min-h-[460px] grid-cols-1 lg:[grid-template-columns:1.6fr_1fr]">
245245
<WorldBackdrop
246246
pins={PREVIEW_PINS}
247247
arcs={PREVIEW_ARCS}
248-
className="border-r border-line"
248+
className="border-b lg:border-b-0 lg:border-r border-line min-h-[260px]"
249249
>
250250
<div className="absolute top-4 left-4 z-10">
251251
<Chip>
@@ -300,7 +300,7 @@ export default function Home() {
300300
{/* Features */}
301301
<section
302302
id="features"
303-
className="max-w-[1280px] mx-auto px-10 py-20"
303+
className="max-w-[1280px] mx-auto px-4 sm:px-6 lg:px-10 py-12 sm:py-20"
304304
>
305305
<div className="text-[13px] font-mono text-accent uppercase tracking-[0.12em] mb-3.5">
306306
Built for operators
@@ -340,9 +340,8 @@ export default function Home() {
340340

341341
{/* CTA */}
342342
<section
343-
className="relative mx-10 mb-20 rounded-[28px] overflow-hidden border border-line-hi text-center"
343+
className="relative mx-4 sm:mx-6 lg:mx-10 mb-12 sm:mb-20 px-5 sm:px-10 py-12 sm:py-20 rounded-[28px] overflow-hidden border border-line-hi text-center"
344344
style={{
345-
padding: '80px 40px',
346345
background:
347346
'radial-gradient(800px 400px at 50% 100%, rgba(77,208,255,0.18), transparent 60%), radial-gradient(600px 300px at 50% 0%, rgba(167,139,250,0.12), transparent 60%), linear-gradient(180deg, #131923, #0e131c)',
348347
}}
@@ -370,11 +369,11 @@ export default function Home() {
370369
<p className="text-lg text-fg-1 mb-8">
371370
Free to self-host. Open source. Ready when you are.
372371
</p>
373-
<div className="flex flex-col sm:flex-row gap-3 justify-center items-center">
374-
<Button asChild size="lg">
372+
<div className="flex flex-col sm:flex-row gap-3 justify-center sm:items-center">
373+
<Button asChild size="lg" className="w-full sm:w-auto">
375374
<Link href="/register">Start logging</Link>
376375
</Button>
377-
<Button asChild variant="secondary" size="lg">
376+
<Button asChild variant="secondary" size="lg" className="w-full sm:w-auto">
378377
<a
379378
href="https://github.com/patrickrb/nextlog"
380379
target="_blank"

src/components/Navbar.tsx

Lines changed: 41 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
'use client';
22

3-
import React from 'react';
3+
import React, { useState, useEffect } from 'react';
44
import Link from 'next/link';
55
import { usePathname } from 'next/navigation';
6-
import { Plus, Search, ChevronDown } from 'lucide-react';
6+
import { Plus, Search, ChevronDown, Menu, X } from 'lucide-react';
77

88
import { Button } from '@/components/ui/button';
99
import { BrandLockup } from '@/components/ui/brand-mark';
@@ -51,13 +51,18 @@ export default function Navbar({ actions }: NavbarProps) {
5151
// but no longer rendered — pages now use <PageHeader> for that.
5252
const { user } = useUser();
5353
const pathname = usePathname();
54+
const [mobileOpen, setMobileOpen] = useState(false);
55+
56+
useEffect(() => {
57+
setMobileOpen(false);
58+
}, [pathname]);
5459

5560
const isActive = (href: string) =>
5661
href === '/' ? pathname === '/' : pathname?.startsWith(href);
5762

5863
return (
5964
<header
60-
className="sticky top-0 z-50 flex items-center gap-7 px-6 lg:px-8 py-4 border-b border-line"
65+
className="sticky top-0 z-50 flex items-center gap-4 md:gap-7 px-4 sm:px-6 lg:px-8 py-4 border-b border-line"
6166
style={{
6267
background: 'rgb(from var(--bg) r g b / 0.72)',
6368
backdropFilter: 'saturate(140%) blur(14px)',
@@ -66,6 +71,16 @@ export default function Navbar({ actions }: NavbarProps) {
6671
>
6772
<BrandLockup href="/dashboard" />
6873

74+
<button
75+
type="button"
76+
aria-label={mobileOpen ? 'Close menu' : 'Open menu'}
77+
aria-expanded={mobileOpen}
78+
onClick={() => setMobileOpen((v) => !v)}
79+
className="md:hidden ml-auto inline-grid place-items-center h-9 w-9 rounded-[8px] border border-line-hi bg-bg-2 text-fg cursor-pointer hover:bg-bg-3"
80+
>
81+
{mobileOpen ? <X className="h-4 w-4" /> : <Menu className="h-4 w-4" />}
82+
</button>
83+
6984
<nav className="hidden md:flex items-center gap-1 ml-2">
7085
{PRIMARY_LINKS.map((link) => {
7186
const active = isActive(link.href);
@@ -102,7 +117,29 @@ export default function Navbar({ actions }: NavbarProps) {
102117
</DropdownMenu>
103118
</nav>
104119

105-
<div className="ml-auto flex items-center gap-3">
120+
{mobileOpen && (
121+
<nav className="md:hidden absolute top-full left-0 right-0 bg-bg-1 border-b border-line p-3 flex flex-col gap-1 shadow-lg">
122+
{[...PRIMARY_LINKS, ...MORE_LINKS].map((link) => {
123+
const active = isActive(link.href);
124+
return (
125+
<Link
126+
key={link.href}
127+
href={link.href}
128+
className={cn(
129+
'px-3.5 py-3 rounded-[8px] text-[15px] transition-colors',
130+
active
131+
? 'bg-accent/10 text-accent'
132+
: 'text-fg-1 hover:bg-white/5 hover:text-fg'
133+
)}
134+
>
135+
{link.label}
136+
</Link>
137+
);
138+
})}
139+
</nav>
140+
)}
141+
142+
<div className="flex items-center gap-2 sm:gap-3 md:ml-auto">
106143
{user?.callsign ? (
107144
<Chip variant="default" className="hidden sm:inline-flex">
108145
<Dot tone="ok" live />

0 commit comments

Comments
 (0)