Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 23 additions & 1 deletion app/page.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export default function Home() {
const [user, setUser] = useState(null);
const [claimStatus, setClaimStatus] = useState('unclaimed'); // 'unclaimed' | 'claimed' | 'no_match'
const [claimedLogins, setClaimedLogins] = useState(new Set());
const [sidebarOpen, setSidebarOpen] = useState(false);
const globeRef = useRef(null);

useEffect(() => {
Expand Down Expand Up @@ -149,11 +150,16 @@ export default function Home() {

const handleSelectDev = useCallback((dev) => {
setSelectedDev(dev);
setSidebarOpen(false);
if (dev?.lat != null && dev?.lng != null) {
setFlyTarget({ lat: dev.lat, lng: dev.lng });
}
}, []);

const handleToggleSidebar = useCallback(() => {
setSidebarOpen(prev => !prev);
}, []);

const handleSelectCountry = useCallback((country, view) => {
setSelectedCountry(country);
if (view) {
Expand All @@ -174,6 +180,7 @@ export default function Home() {
setFiltered(developers);
setFlyTarget(null);
setSelectedCountry('');
setSidebarOpen(false);
}, [developers]);

if (loading || error) {
Expand All @@ -182,7 +189,17 @@ export default function Home() {

return (
<div id="app">
<Header onHome={handleHome} theme={theme} onToggleTheme={handleToggleTheme} user={user} onLogout={handleLogout} onClaim={handleClaim} claimStatus={claimStatus} />
<Header
onHome={handleHome}
theme={theme}
onToggleTheme={handleToggleTheme}
user={user}
onLogout={handleLogout}
onClaim={handleClaim}
claimStatus={claimStatus}
sidebarOpen={sidebarOpen}
onToggleSidebar={handleToggleSidebar}
/>
<SearchBar
developers={developers}
onResults={handleSearch}
Expand All @@ -206,7 +223,12 @@ export default function Home() {
countryFilter={selectedCountry}
onCountryFilterChange={setSelectedCountry}
claimedLogins={claimedLogins}
open={sidebarOpen}
onClose={() => setSidebarOpen(false)}
/>
{sidebarOpen && (
<div className="sidebar-backdrop" onClick={() => setSidebarOpen(false)} />
)}
{selectedDev && (
<DetailPanel dev={selectedDev} onClose={handleCloseDetail} claimedLogins={claimedLogins} />
)}
Expand Down
23 changes: 20 additions & 3 deletions components/Header.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import React from 'react';
import UserMenu from './UserMenu.jsx';

export default function Header({ onHome, theme, onToggleTheme, user, onLogout, onClaim, claimStatus }) {
export default function Header({ onHome, theme, onToggleTheme, user, onLogout, onClaim, claimStatus, sidebarOpen, onToggleSidebar }) {
return (
<header className="header">
<div className="header__brand" onClick={onHome} style={{ cursor: 'pointer' }}>
Expand All @@ -12,6 +12,23 @@ export default function Header({ onHome, theme, onToggleTheme, user, onLogout, o
<span className="header__subtitle">Visualizing the World's Top Open-Source Contributors</span>
</div>
<div className="header__actions">
<button
type="button"
className="btn btn--sidebar-toggle"
onClick={onToggleSidebar}
aria-label={sidebarOpen ? 'Close leaderboard' : 'Open leaderboard'}
aria-expanded={sidebarOpen}
title={sidebarOpen ? 'Close leaderboard' : 'Open leaderboard'}
>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<line x1="8" y1="6" x2="21" y2="6" />
<line x1="8" y1="12" x2="21" y2="12" />
<line x1="8" y1="18" x2="21" y2="18" />
<line x1="3" y1="6" x2="3.01" y2="6" />
<line x1="3" y1="12" x2="3.01" y2="12" />
<line x1="3" y1="18" x2="3.01" y2="18" />
</svg>
</button>
<button
type="button"
className="btn btn--theme"
Expand Down Expand Up @@ -41,13 +58,13 @@ export default function Header({ onHome, theme, onToggleTheme, user, onLogout, o
<svg viewBox="0 0 16 16" width="16" height="16" fill="currentColor">
<path d="M8 .25a.75.75 0 0 1 .673.418l1.882 3.815 4.21.612a.75.75 0 0 1 .416 1.279l-3.046 2.97.719 4.192a.75.75 0 0 1-1.088.791L8 12.347l-3.766 1.98a.75.75 0 0 1-1.088-.79l.72-4.194L.818 6.374a.75.75 0 0 1 .416-1.28l4.21-.611L7.327.668A.75.75 0 0 1 8 .25z" />
</svg>
Star on GitHub
<span className="btn__label">Star on GitHub</span>
</a>
<a href="https://github.com/sponsors/sajeetharan" target="_blank" rel="noreferrer" className="btn btn--sponsor">
<svg viewBox="0 0 16 16" width="16" height="16" fill="currentColor">
<path d="m8 14.25.345.666a.75.75 0 0 1-.69 0l-.008-.004-.018-.01a7.152 7.152 0 0 1-.31-.17 22.055 22.055 0 0 1-3.434-2.414C2.045 10.731 0 8.35 0 5.5 0 2.836 2.086 1 4.25 1 5.797 1 7.153 1.802 8 3.02 8.847 1.802 10.203 1 11.75 1 13.914 1 16 2.836 16 5.5c0 2.85-2.045 5.231-3.885 6.818a22.066 22.066 0 0 1-3.744 2.584l-.018.01-.006.003h-.002z" />
</svg>
Sponsor
<span className="btn__label">Sponsor</span>
</a>
<UserMenu user={user} onLogout={onLogout} onClaim={onClaim} claimStatus={claimStatus} />
</div>
Expand Down
10 changes: 8 additions & 2 deletions components/Leaderboard.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ export default function Leaderboard({
countryFilter = '',
onCountryFilterChange,
claimedLogins,
open = false,
onClose,
}) {
const listRef = useRef(null);
const [scrollTop, setScrollTop] = useState(0);
Expand Down Expand Up @@ -115,7 +117,8 @@ export default function Leaderboard({
};

return (
<aside className="sidebar" id="sidebar">
<aside className={`sidebar${open ? ' open' : ''}`} id="sidebar">
<div className="sidebar__drag-handle" onClick={onClose} aria-hidden="true" />
<div className="sidebar__header">
<div className="sidebar__header-row">
<h2>Leaderboard</h2>
Expand All @@ -124,6 +127,9 @@ export default function Leaderboard({
✕ Clear filters
</button>
)}
<button className="sidebar__close-btn" onClick={onClose} aria-label="Close leaderboard" title="Close leaderboard">
&times;
</button>
</div>
<div className="sidebar__count">{filtered.length} developer{filtered.length !== 1 ? 's' : ''}</div>
<div className="sidebar__filters">
Expand Down Expand Up @@ -195,4 +201,4 @@ export default function Leaderboard({
</ul>
</aside>
);
}
}
111 changes: 110 additions & 1 deletion styles/main.css
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,31 @@ body {
height: 16px;
}

.btn--sidebar-toggle {
display: none;
align-items: center;
justify-content: center;
width: 32px;
height: 32px;
padding: 0;
border-radius: 50%;
background: var(--overlay-subtle);
border: 1px solid var(--border);
color: var(--text-secondary);
cursor: pointer;
transition: background 0.2s, color 0.2s;
}

.btn--sidebar-toggle:hover {
background: var(--overlay-subtle-strong);
color: var(--text-primary);
}

.btn--sidebar-toggle svg {
width: 18px;
height: 18px;
}

/* Centered search bar */
.search-bar {
position: fixed;
Expand Down Expand Up @@ -363,6 +388,7 @@ body {
#globe-container {
width: 100%;
height: 100%;
touch-action: none;
}

/* Globe Legend */
Expand Down Expand Up @@ -452,6 +478,32 @@ body {
border-color: #f87171;
}

.sidebar__close-btn {
display: none;
align-items: center;
justify-content: center;
width: 28px;
height: 28px;
padding: 0;
border-radius: 50%;
background: var(--overlay-subtle);
border: 1px solid var(--border);
color: var(--text-secondary);
font-size: 18px;
line-height: 1;
cursor: pointer;
flex-shrink: 0;
}

.sidebar__close-btn:hover {
background: var(--overlay-subtle-strong);
color: var(--text-primary);
}

.sidebar__drag-handle {
display: none;
}

.sidebar__count {
font-size: 11px;
color: var(--text-muted);
Expand Down Expand Up @@ -1150,6 +1202,10 @@ body {
color: var(--text-secondary);
}

.sidebar-backdrop {
display: none;
}

/* Responsive */
@media (max-width: 1024px) {
.sidebar {
Expand All @@ -1167,6 +1223,10 @@ body {
}

@media (max-width: 768px) {
.btn--sidebar-toggle {
display: flex;
}

.sidebar {
width: 100%;
right: 0;
Expand All @@ -1178,19 +1238,41 @@ body {
border-top: 1px solid var(--border);
border-radius: 16px 16px 0 0;
transform: translateY(100%);
z-index: 110;
padding-top: 8px;
}
.sidebar.open {
transform: translateY(0);
}
.sidebar__drag-handle {
display: block;
width: 40px;
height: 4px;
margin: 0 auto 8px;
border-radius: 2px;
background: var(--border);
cursor: pointer;
}
.sidebar__close-btn {
display: flex;
}
.sidebar__header {
padding: 12px 16px;
padding: 0 16px 12px;
}
.sidebar__filters {
grid-template-columns: 1fr;
gap: 6px;
}
.sidebar-backdrop {
display: block;
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.4);
z-index: 109;
}
.detail-panel {
width: 100%;
z-index: 105;
}
.search-bar {
width: calc(100% - 24px);
Expand Down Expand Up @@ -1219,6 +1301,33 @@ body {
}
}

@media (max-width: 480px) {
.btn__label {
display: none;
}
.btn--star,
.btn--sponsor {
padding: 6px 8px;
}
.header__title {
font-size: 16px;
}
.header__logo {
width: 22px;
height: 22px;
}
.header__actions {
gap: 6px;
}
.sidebar {
height: 65vh;
}
.globe-legend {
font-size: 10px;
padding: 6px 10px;
}
}

/* =============== User Menu / Auth =============== */

.btn--signin {
Expand Down
Loading