Skip to content

Commit 02e9af8

Browse files
authored
Merge pull request #13 from junctor/sync/ui-ux-alignment
UI/UX alignment and polish across core views
2 parents 427bdee + 6337f08 commit 02e9af8

30 files changed

Lines changed: 2101 additions & 856 deletions

package-lock.json

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
"react-dom": "^19.1.1",
2323
"react-markdown": "^10.1.0",
2424
"react-router": "^7.8.2",
25+
"react-virtuoso": "^4.18.6",
2526
"remark-gfm": "^4.0.1",
2627
"tailwindcss": "^4.1.12"
2728
},

src/components/ConferenceHeader.tsx

Lines changed: 51 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import {
44
XMarkIcon,
55
HomeIcon,
66
ArrowTopRightOnSquareIcon,
7-
CodeBracketSquareIcon,
87
BookmarkSquareIcon,
98
CalendarDaysIcon,
109
UserGroupIcon,
@@ -73,17 +72,10 @@ export function ConferenceHeader({ conference }: { conference: HTConference }) {
7372
},
7473
{
7574
key: "home",
76-
label: "HackerTracker",
75+
label: "Home",
7776
to: "/",
7877
icon: HomeIcon,
7978
},
80-
{
81-
key: "github",
82-
label: "GitHub",
83-
to: "https://github.com/junctor/hackertracker-web",
84-
external: true,
85-
icon: CodeBracketSquareIcon,
86-
},
8779
];
8880

8981
if (conference.link) {
@@ -99,76 +91,82 @@ export function ConferenceHeader({ conference }: { conference: HTConference }) {
9991
}, [scheduleHref, isSchedule, bookmarksHref, isBookmarks, peopleHref, isPeople, conference.link]);
10092

10193
const baseHeader =
102-
"sticky top-0 h-14 z-50 border-b border-neutral-800 transition-colors backdrop-blur supports-[backdrop-filter]:backdrop-blur";
103-
const bg = scrolled ? "bg-neutral-950/90" : "bg-neutral-950/70";
94+
"sticky top-0 z-50 min-h-16 border-b border-white/10 text-white backdrop-blur-md transition-[background-color,border-color,box-shadow] duration-200 supports-[backdrop-filter]:backdrop-blur-md";
95+
const bg = scrolled ? "bg-slate-950/92 shadow-[0_12px_32px_rgba(2,6,23,0.3)]" : "bg-slate-950/82";
10496

10597
return (
10698
<header className={`${baseHeader} ${bg}`}>
107-
<div className="flex h-14 w-full items-center justify-between px-4 sm:px-6 lg:px-10">
108-
{/* Left: Conference name / brand */}
99+
<div
100+
aria-hidden="true"
101+
className="pointer-events-none absolute inset-x-0 bottom-0 h-px bg-linear-to-r from-transparent via-[#017FA4]/35 to-transparent"
102+
/>
103+
104+
<div className="flex min-h-16 w-full items-center justify-between gap-3 px-4 py-2.5 sm:px-6 lg:px-10">
109105
<div className="min-w-0">
110106
<Link
111107
to={scheduleHref}
112-
className="block truncate text-lg font-extrabold text-white hover:opacity-90 sm:text-xl"
108+
className="ui-focus-ring block truncate rounded-xl px-2 py-1.5 text-lg font-bold tracking-tight text-slate-50 transition-colors hover:bg-white/4 hover:text-white focus-visible:outline-none sm:text-xl"
113109
aria-label={`${conference.name} — view schedule`}
114110
>
115111
{conference.name}
116112
</Link>
117113
</div>
118114

119-
{/* Desktop nav */}
120115
<nav className="hidden items-center gap-2 sm:flex">
121-
{items.map(({ key, label, to, external, icon: Icon, ariaCurrent }) =>
122-
external ? (
116+
{items.map(({ key, label, to, external, icon: Icon, ariaCurrent }) => {
117+
const common =
118+
"ui-btn-base ui-focus-ring group min-h-10 gap-2 rounded-xl border-white/10 bg-white/4 px-3 text-sm text-slate-300 shadow-[0_10px_28px_rgba(2,6,23,0.16)] hover:-translate-y-0.5 hover:shadow-[0_16px_34px_rgba(2,6,23,0.24)] focus-visible:-translate-y-0.5 focus-visible:outline-none focus-visible:shadow-[0_16px_34px_rgba(2,6,23,0.24)]";
119+
const classes = ariaCurrent
120+
? `${common} border-[#017FA4]/35 bg-[#017FA4]/12 text-white`
121+
: `${common} hover:border-[#017FA4]/28 hover:bg-[#017FA4]/8 hover:text-slate-50`;
122+
const iconClassName = ariaCurrent
123+
? "text-[#6CCDBB]"
124+
: "text-slate-400 transition-colors group-hover:text-[#6CCDBB] group-focus-visible:text-[#6CCDBB]";
125+
126+
return external ? (
123127
<a
124128
key={key}
125129
href={to}
126130
target="_blank"
127131
rel="noreferrer"
128-
className="inline-flex items-center gap-2 rounded-lg border border-neutral-800 bg-neutral-900/60 px-3 py-2 text-sm text-neutral-300 transition-colors hover:bg-neutral-900 hover:text-white"
132+
className={classes}
129133
aria-label={label}
130134
title={label}
131135
>
132-
<Icon className="h-5 w-5" />
136+
<Icon className={`h-5 w-5 ${iconClassName}`} />
133137
<span className="hidden md:inline">{label}</span>
134138
</a>
135139
) : (
136140
<Link
137141
key={key}
138142
to={to}
139-
className={[
140-
"inline-flex items-center gap-2 rounded-lg border border-neutral-800 px-3 py-2 text-sm transition-colors",
141-
ariaCurrent
142-
? "bg-neutral-900 text-white"
143-
: "bg-neutral-900/60 text-neutral-300 hover:bg-neutral-900 hover:text-white",
144-
].join(" ")}
145-
aria-label={label}
146-
title={label}
143+
className={classes}
144+
aria-label={key === "home" ? "Hacker Tracker home" : label}
145+
title={key === "home" ? "Hacker Tracker home" : label}
147146
aria-current={ariaCurrent ? "page" : undefined}
148147
>
149-
<Icon className="h-5 w-5" />
150-
<span className="hidden md:inline">{label}</span>
148+
<Icon className={`h-5 w-5 ${iconClassName}`} />
149+
{key === "home" ? null : <span className="hidden md:inline">{label}</span>}
151150
</Link>
152-
),
153-
)}
151+
);
152+
})}
154153
</nav>
155154

156-
{/* Mobile: Popover menu */}
157155
<div className="flex items-center gap-2 sm:hidden">
158156
<Popover className="relative">
159157
{({ open }) => (
160158
<>
161159
<PopoverButton
162160
aria-label={open ? "Close menu" : "Open menu"}
163161
className={[
164-
"inline-flex h-9 w-9 items-center justify-center rounded-md border border-neutral-700 bg-neutral-900 hover:bg-neutral-800 focus:outline-none focus-visible:ring-2 focus-visible:ring-cyan-400/50",
165-
open ? "ring-1 ring-cyan-400/40" : "",
162+
"ui-icon-btn ui-focus-ring h-10 min-h-10 w-10 min-w-10 rounded-xl border-white/10 bg-white/4 text-slate-300 shadow-[0_10px_24px_rgba(2,6,23,0.16)] focus-visible:outline-none",
163+
open ? "border-[#017FA4]/35 bg-[#017FA4]/12 text-[#6CCDBB]" : "",
166164
].join(" ")}
167165
>
168166
{open ? (
169-
<XMarkIcon className="h-5 w-5 text-neutral-200" aria-hidden="true" />
167+
<XMarkIcon className="h-5 w-5" aria-hidden="true" />
170168
) : (
171-
<Bars3Icon className="h-5 w-5 text-neutral-200" aria-hidden="true" />
169+
<Bars3Icon className="h-5 w-5" aria-hidden="true" />
172170
)}
173171
</PopoverButton>
174172

@@ -181,7 +179,11 @@ export function ConferenceHeader({ conference }: { conference: HTConference }) {
181179
leaveFrom="opacity-100 translate-y-0"
182180
leaveTo="opacity-0 -translate-y-1"
183181
>
184-
<PopoverPanel className="absolute right-0 mt-2 w-56 origin-top-right rounded-lg border border-neutral-800 bg-neutral-950/95 p-2 shadow-lg backdrop-blur">
182+
<PopoverPanel className="absolute right-0 mt-2 w-60 origin-top-right rounded-[1.25rem] border border-white/12 bg-slate-950/98 p-2 shadow-[0_20px_48px_rgba(0,0,0,0.42)] backdrop-blur-md">
183+
<span
184+
aria-hidden="true"
185+
className="pointer-events-none absolute inset-x-3 top-0 h-px bg-white/10"
186+
/>
185187
<div className="space-y-1 text-sm">
186188
{items.map(({ key, label, to, external, icon: Icon, ariaCurrent }) =>
187189
external ? (
@@ -191,9 +193,9 @@ export function ConferenceHeader({ conference }: { conference: HTConference }) {
191193
href={to}
192194
target="_blank"
193195
rel="noreferrer"
194-
className="flex w-full items-center gap-2 rounded-md px-3 py-2 text-left text-neutral-300 transition-colors hover:bg-neutral-900 hover:text-white"
196+
className="ui-focus-ring group flex w-full items-center gap-2 rounded-xl border border-transparent px-3 py-2.5 text-left text-slate-300 transition-colors hover:border-white/10 hover:bg-[#017FA4]/8 hover:text-white focus-visible:outline-none"
195197
>
196-
<Icon className="h-5 w-5" />
198+
<Icon className="h-5 w-5 text-slate-400 transition-colors group-hover:text-[#6CCDBB] group-focus-visible:text-[#6CCDBB]" />
197199
{label}
198200
</PopoverButton>
199201
) : (
@@ -202,14 +204,20 @@ export function ConferenceHeader({ conference }: { conference: HTConference }) {
202204
as={Link}
203205
to={to}
204206
className={[
205-
"flex w-full items-center gap-2 rounded-md px-3 py-2 text-left transition-colors",
207+
"ui-focus-ring group flex w-full items-center gap-2 rounded-xl border px-3 py-2.5 text-left transition-colors focus-visible:outline-none",
206208
ariaCurrent
207-
? "bg-neutral-900 text-white"
208-
: "text-neutral-300 hover:bg-neutral-900 hover:text-white",
209+
? "border-[#017FA4]/35 bg-[#017FA4]/12 text-white"
210+
: "border-transparent text-slate-300 hover:border-white/10 hover:bg-[#017FA4]/8 hover:text-white",
209211
].join(" ")}
210212
aria-current={ariaCurrent ? "page" : undefined}
211213
>
212-
<Icon className="h-5 w-5" />
214+
<Icon
215+
className={`h-5 w-5 ${
216+
ariaCurrent
217+
? "text-[#6CCDBB]"
218+
: "text-slate-400 transition-colors group-hover:text-[#6CCDBB] group-focus-visible:text-[#6CCDBB]"
219+
}`}
220+
/>
213221
{label}
214222
</PopoverButton>
215223
),

src/components/ErrorPage.tsx

Lines changed: 9 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -9,52 +9,34 @@ export default function ErrorPage({ msg }: { msg?: string }) {
99
<HTHeader />
1010

1111
<main id="main" className="flex flex-1 items-center justify-center px-6">
12-
<div className="relative text-center">
13-
{/* Glitchy title */}
14-
<div className="isolation-isolate relative inline-block select-none">
15-
<h1 className="relative text-4xl font-extrabold tracking-tight text-red-400 motion-safe:animate-[glitch_2s_steps(12,end)_infinite] md:text-6xl">
16-
ERROR
17-
<span
18-
aria-hidden
19-
className="absolute inset-0 text-cyan-300 opacity-95 mix-blend-screen motion-safe:animate-[rgb_2.4s_ease-in-out_infinite]"
20-
>
21-
ERROR
22-
</span>
23-
<span
24-
aria-hidden
25-
className="absolute inset-0 -translate-x-[2px] translate-y-[1px] text-fuchsia-400 opacity-95 mix-blend-screen motion-safe:animate-[rgb_2.4s_ease-in-out_infinite]"
26-
style={{ animationDelay: "0.15s" }}
27-
>
28-
ERROR
29-
</span>
30-
</h1>
31-
</div>
12+
<div className="ui-card relative w-full max-w-2xl px-6 py-7 text-center sm:px-8 sm:py-9">
13+
<h1 className="text-3xl font-semibold text-balance text-gray-100 sm:text-4xl">
14+
We couldn&apos;t load this page
15+
</h1>
3216

33-
{/* Message */}
3417
{msg ? (
3518
<pre
3619
role="alert"
37-
className="mx-auto mt-6 max-w-xl overflow-x-auto rounded-lg border border-red-700/70 bg-red-950/30 p-4 text-left font-mono text-xs text-red-200 md:text-sm"
20+
className="mx-auto mt-6 max-h-[40dvh] max-w-xl overflow-auto rounded-xl border border-red-400/20 bg-red-950/25 p-4 text-left font-mono text-xs leading-6 whitespace-pre-wrap text-red-100 md:text-sm"
3821
>
3922
{msg}
4023
</pre>
4124
) : (
42-
<p className="mt-6 text-base text-gray-300">
43-
Something went sideways. Try again or head back home.
25+
<p role="alert" className="mx-auto mt-3 max-w-xl text-sm leading-6 text-gray-300">
26+
Try again in a moment, or head back home.
4427
</p>
4528
)}
4629

47-
{/* Actions */}
4830
<div className="mt-8 flex flex-wrap justify-center gap-3">
4931
<Link
5032
to="/"
51-
className="rounded-md border border-gray-600/70 px-4 py-2 text-sm font-semibold text-gray-100 transition-colors hover:border-gray-500 hover:bg-gray-800/70 focus:ring-2 focus:ring-indigo-400 focus:outline-none"
33+
className="ui-btn-base ui-btn-secondary ui-focus-ring focus-visible:outline-none"
5234
>
5335
Return Home
5436
</Link>
5537
<Link
5638
to="/support"
57-
className="rounded-md border border-gray-600/70 px-4 py-2 text-sm font-semibold text-gray-100 transition-colors hover:border-gray-500 hover:bg-gray-800/70 focus:ring-2 focus:ring-indigo-400 focus:outline-none"
39+
className="ui-btn-base ui-btn-secondary ui-focus-ring focus-visible:outline-none"
5840
>
5941
Contact Support
6042
</Link>

src/components/HTFooter.tsx

Lines changed: 42 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,62 @@
1+
import { CodeBracketSquareIcon } from "@heroicons/react/16/solid";
2+
import { Link } from "react-router";
3+
14
export function HTFooter() {
25
return (
3-
<footer className="mt-auto border-t border-neutral-800 bg-neutral-950/95 backdrop-blur">
4-
{/* glow bar */}
5-
<div
6-
aria-hidden
7-
className="h-px w-full bg-gradient-to-r from-cyan-500/20 via-fuchsia-400/20 to-amber-400/20"
8-
/>
9-
<div className="mx-auto max-w-7xl px-4 py-8">
10-
<div className="grid items-start gap-6 text-sm sm:grid-cols-3">
11-
<div className="text-neutral-300">
12-
<p className="font-semibold">HackerTracker</p>
13-
<p className="mt-1 text-neutral-400">
14-
Community-built schedules for hackers, by hackers.
6+
<footer className="mt-auto border-t border-white/10 bg-[linear-gradient(180deg,rgba(2,6,23,0.92),rgba(2,6,23,0.98))] text-slate-300">
7+
<div className="mx-auto w-[min(72rem,calc(100%_-_2rem))] py-5 sm:w-[min(72rem,calc(100%_-_3rem))] sm:py-6">
8+
<div className="flex flex-col gap-4 lg:flex-row lg:items-center lg:justify-between">
9+
<div className="min-w-0 space-y-1.5">
10+
<p className="text-[11px] font-semibold tracking-[0.16em] text-slate-500 uppercase">
11+
Hacker Tracker
12+
</p>
13+
<p className="max-w-2xl text-sm leading-6 text-slate-400">
14+
Community-built schedules, people, and conference information for Hacker Tracker.
1515
</p>
1616
</div>
1717

18-
<nav className="flex flex-wrap gap-x-6 gap-y-2 text-neutral-300">
19-
<a href="/conferences" className="underline-offset-4 hover:text-white hover:underline">
18+
<nav
19+
aria-label="Footer"
20+
className="flex flex-wrap items-center gap-x-4 gap-y-2 text-sm text-slate-300"
21+
>
22+
<Link
23+
to="/conferences"
24+
className="ui-focus-ring rounded px-1 py-0.5 underline-offset-4 transition-colors hover:text-white hover:underline focus-visible:outline-none"
25+
>
2026
Conferences
21-
</a>
22-
<a href="/about" className="underline-offset-4 hover:text-white hover:underline">
27+
</Link>
28+
<Link
29+
to="/about"
30+
className="ui-focus-ring rounded px-1 py-0.5 underline-offset-4 transition-colors hover:text-white hover:underline focus-visible:outline-none"
31+
>
2332
About
24-
</a>
25-
<a href="/support" className="underline-offset-4 hover:text-white hover:underline">
33+
</Link>
34+
<Link
35+
to="/support"
36+
className="ui-focus-ring rounded px-1 py-0.5 underline-offset-4 transition-colors hover:text-white hover:underline focus-visible:outline-none"
37+
>
2638
Support
27-
</a>
39+
</Link>
2840
<a
2941
href="https://github.com/junctor/hackertracker-web"
3042
target="_blank"
3143
rel="noopener noreferrer"
32-
className="underline-offset-4 hover:text-white hover:underline"
44+
className="ui-focus-ring rounded px-1 py-0.5 underline-offset-4 transition-colors hover:text-white hover:underline focus-visible:outline-none"
3345
>
3446
GitHub
3547
</a>
3648
</nav>
3749

38-
<div className="text-neutral-400 sm:text-right">
39-
Built with <span className="text-red-500"></span> by the HackerTracker team
40-
</div>
50+
<a
51+
href="https://github.com/junctor/hackertracker-web"
52+
target="_blank"
53+
rel="noopener noreferrer"
54+
aria-label="View source on GitHub"
55+
className="ui-btn-base ui-btn-secondary ui-focus-ring w-fit gap-2 rounded-xl px-3.5 text-sm text-slate-200 shadow-[0_10px_24px_rgba(2,6,23,0.18)] focus-visible:outline-none"
56+
>
57+
<CodeBracketSquareIcon className="h-4 w-4 text-[#6CCDBB]" aria-hidden="true" />
58+
<span>View Source</span>
59+
</a>
4160
</div>
4261
</div>
4362
</footer>

0 commit comments

Comments
 (0)