diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx
index fe713ac..5dfc6fb 100644
--- a/frontend/src/App.tsx
+++ b/frontend/src/App.tsx
@@ -8,7 +8,6 @@ import {
SwipeableDrawer,
CircularProgress,
Badge,
- alpha,
useMediaQuery,
useTheme as useMuiTheme,
} from '@mui/material';
@@ -22,8 +21,10 @@ import { GlobalSearch } from './app/GlobalSearch';
import { useAuth } from './hooks/useAuth';
import { useTheme } from './hooks/useTheme';
import { useDrawerAnchor } from './hooks/useDrawerAnchor';
-import { NavSidebar, NAV_SIDEBAR_WIDTH } from './components/layout/NavSidebar';
+import { NavSidebar } from './components/layout/NavSidebar';
import { BrandMark } from './components/layout/BrandMark';
+import { DesktopTopBar } from './components/layout/DesktopTopBar';
+import { DesktopCategoryBar } from './components/layout/DesktopCategoryBar';
import { useCategories } from './api/queries/useCategories';
import { NotificationMenu } from './components/layout/NotificationMenu';
import { RequireSuperuser } from './components/auth/RequireSuperuser';
@@ -144,6 +145,8 @@ const App: React.FC = () => {
)}
+ {!isMobile && }
+
{isMobile && (
{
)}
-
- {!isMobile && (
- theme.tokens.glass.border,
- bgcolor: (theme) =>
- alpha(
- theme.palette.background.default,
- theme.palette.mode === 'light' ? 0.65 : 0.45,
- ),
- backdropFilter: (theme) => theme.tokens.glass.blurStrong,
- WebkitBackdropFilter: (theme) => theme.tokens.glass.blurStrong,
- zIndex: 1,
- }}
- >
-
-
- )}
+ {!isMobile && }
+
{
+ const { t } = useTranslation();
+ if (categories.length === 0) return null;
+
+ return (
+
+
+
+ {categories.map((cat) => (
+ theme.tokens.elevation.md,
+ },
+ '&::after': {
+ content: '""',
+ position: 'absolute',
+ left: 0,
+ top: 0,
+ bottom: 0,
+ width: 4,
+ background: (theme) => theme.tokens.accent.gradient,
+ },
+ }}
+ >
+
+
+ {t(`categories.${cat.slug}`, { defaultValue: cat.name })}
+
+
+
+
+ ))}
+
+
+ );
+};
+
+export interface QuickStatsStripProps {
+ topName?: string;
+ topScore?: number;
+ newestName?: string;
+ categoryCount: number;
+}
+
+/** Desktop-only featured strip — fills the wider canvas with at-a-glance picks. */
+export const QuickStatsStrip = ({
+ topName,
+ topScore,
+ newestName,
+ categoryCount,
+}: QuickStatsStripProps) => {
+ const { t } = useTranslation();
+
+ const items = [
+ {
+ icon: ,
+ label: t('home.statTopRated', { defaultValue: 'Top Rated' }),
+ value: topName,
+ sub: topScore != null ? `${topScore.toFixed(1)} / 10` : undefined,
+ },
+ {
+ icon: ,
+ label: t('home.statNewest', { defaultValue: 'Newest Arrival' }),
+ value: newestName,
+ },
+ {
+ icon: ,
+ label: t('home.statCategories', { defaultValue: 'Categories' }),
+ value: String(categoryCount),
+ },
+ ].filter((i) => i.value);
+
+ if (items.length === 0) return null;
+
+ return (
+
+ {items.map((item) => (
+
+
+ {item.icon}
+
+
+
+ {item.label}
+
+
+ {item.value}
+
+ {item.sub && (
+
+ {item.sub}
+
+ )}
+
+
+ ))}
+
+ );
+};
diff --git a/frontend/src/components/layout/DesktopCategoryBar.tsx b/frontend/src/components/layout/DesktopCategoryBar.tsx
new file mode 100644
index 0000000..c5b955d
--- /dev/null
+++ b/frontend/src/components/layout/DesktopCategoryBar.tsx
@@ -0,0 +1,108 @@
+import { Link, useLocation } from 'react-router-dom';
+import { Box, Button, alpha } from '@mui/material';
+import type { Theme } from '@mui/material';
+import HomeOutlinedIcon from '@mui/icons-material/HomeOutlined';
+import GroupsOutlinedIcon from '@mui/icons-material/GroupsOutlined';
+import { useTranslation } from 'react-i18next';
+import { useAuth } from '../../hooks/useAuth';
+
+export interface DesktopCategoryBarProps {
+ categories: { name: string; slug: string }[];
+}
+
+/**
+ * Desktop horizontal nav (md+), pinned just under the top bar. Categories are
+ * the dominant browse axis here — laid out as a scrollable row of tabs — with
+ * Home / Community as primary links on the left. Replaces the old left rail.
+ */
+export const DesktopCategoryBar = ({ categories }: DesktopCategoryBarProps) => {
+ const { t } = useTranslation();
+ const location = useLocation();
+ const { user } = useAuth();
+
+ const isActive = (path: string) =>
+ path === '/' ? location.pathname === '/' : location.pathname.startsWith(path);
+
+ const tabSx = (active: boolean) => ({
+ flexShrink: 0,
+ borderRadius: 2,
+ px: 1.75,
+ py: 0.75,
+ fontWeight: active ? 700 : 500,
+ color: active ? 'primary.main' : 'text.secondary',
+ bgcolor: active ? (theme: Theme) => alpha(theme.palette.primary.main, 0.12) : 'transparent',
+ '&:hover': {
+ color: 'text.primary',
+ bgcolor: 'action.hover',
+ },
+ });
+
+ return (
+ theme.zIndex.appBar - 1,
+ borderBottom: '1px solid',
+ borderColor: (theme) => theme.tokens.glass.border,
+ backgroundColor: (theme) =>
+ alpha(theme.palette.background.default, theme.palette.mode === 'light' ? 0.8 : 0.6),
+ backdropFilter: (theme) => theme.tokens.glass.blurStrong,
+ WebkitBackdropFilter: (theme) => theme.tokens.glass.blurStrong,
+ }}
+ >
+
+
+
+ {user && (
+
+ )}
+
+
+
+ {categories.map((cat) => {
+ const path = `/category/${cat.slug}`;
+ return (
+
+ );
+ })}
+
+
+ );
+};
diff --git a/frontend/src/components/layout/DesktopTopBar.tsx b/frontend/src/components/layout/DesktopTopBar.tsx
new file mode 100644
index 0000000..959c9f8
--- /dev/null
+++ b/frontend/src/components/layout/DesktopTopBar.tsx
@@ -0,0 +1,193 @@
+import { useState } from 'react';
+import { Link } from 'react-router-dom';
+import {
+ Avatar,
+ Badge,
+ Box,
+ Button,
+ Divider,
+ IconButton,
+ ListItemIcon,
+ ListItemText,
+ Menu,
+ MenuItem,
+ Typography,
+} from '@mui/material';
+import NotificationsIcon from '@mui/icons-material/Notifications';
+import PersonOutlineOutlinedIcon from '@mui/icons-material/PersonOutlineOutlined';
+import CollectionsBookmarkOutlinedIcon from '@mui/icons-material/CollectionsBookmarkOutlined';
+import SettingsOutlinedIcon from '@mui/icons-material/SettingsOutlined';
+import HelpOutlineOutlinedIcon from '@mui/icons-material/HelpOutlineOutlined';
+import AdminPanelSettingsOutlinedIcon from '@mui/icons-material/AdminPanelSettingsOutlined';
+import LogoutOutlinedIcon from '@mui/icons-material/LogoutOutlined';
+import { useTranslation } from 'react-i18next';
+import { GlassAppBar } from '../ui';
+import { GlobalSearch } from '../../app/GlobalSearch';
+import { useAuth } from '../../hooks/useAuth';
+import { BrandMark } from './BrandMark';
+import { NotificationMenu } from './NotificationMenu';
+
+/**
+ * Desktop top bar (md+). Promotes global search to the primary, centered slot;
+ * notifications + an account menu sit on the right. Everything that used to be
+ * a flat sidebar row (profile, dashboard, settings, support, admin, following,
+ * logout) now lives behind the avatar menu so the chrome stays light.
+ */
+export const DesktopTopBar = () => {
+ const { t } = useTranslation();
+ const { user, following, logout } = useAuth();
+
+ const [notifAnchorEl, setNotifAnchorEl] = useState(null);
+ const [acctAnchorEl, setAcctAnchorEl] = useState(null);
+ const acctOpen = Boolean(acctAnchorEl);
+ const closeAcct = () => setAcctAnchorEl(null);
+
+ const unreadCount = user?.unread_notifications_count || 0;
+
+ return (
+
+
+
+
+
+
+ {user ? (
+ <>
+ setNotifAnchorEl(e.currentTarget)}>
+
+
+
+
+ setNotifAnchorEl(null)}
+ menuSx={{ mt: 1 }}
+ />
+
+ setAcctAnchorEl(e.currentTarget)}
+ aria-label={t('nav.profile')}
+ aria-haspopup="true"
+ aria-expanded={acctOpen}
+ sx={{ ml: 0.5 }}
+ >
+
+ {user.username.charAt(0).toUpperCase()}
+
+
+
+
+ >
+ ) : (
+
+ )}
+
+
+ );
+};
diff --git a/frontend/src/i18n.ts b/frontend/src/i18n.ts
index 056131a..ce3c280 100644
--- a/frontend/src/i18n.ts
+++ b/frontend/src/i18n.ts
@@ -114,6 +114,11 @@ const resources = {
exploreFlavors: 'Explore Flavors',
hallOfFame: 'Hall of Fame',
hallOfFameSubtitle: 'Highest-rated flavors in the archive',
+ browseCategories: 'Browse Categories',
+ browseCategoriesSubtitle: 'Jump straight into a flavor line',
+ statTopRated: 'Top Rated',
+ statNewest: 'Newest Arrival',
+ statCategories: 'Categories',
recForYou: 'Recommended for You',
recForYouSubtitle: 'Picked by tasters who share your palate',
popularPicks: 'Popular Picks',
@@ -645,6 +650,11 @@ const resources = {
exploreFlavors: 'Sorten entdecken',
hallOfFame: 'Hall of Fame',
hallOfFameSubtitle: 'Die bestbewerteten Sorten im Archiv',
+ browseCategories: 'Kategorien durchstöbern',
+ browseCategoriesSubtitle: 'Direkt zu einer Sorten-Linie springen',
+ statTopRated: 'Bestbewertet',
+ statNewest: 'Neuheit',
+ statCategories: 'Kategorien',
recForYou: 'Für dich empfohlen',
recForYouSubtitle: 'Ausgewählt von Testern mit ähnlichem Geschmack',
popularPicks: 'Beliebte Sorten',
diff --git a/frontend/src/pages/MainPage.tsx b/frontend/src/pages/MainPage.tsx
index b44c55e..64ad0f3 100644
--- a/frontend/src/pages/MainPage.tsx
+++ b/frontend/src/pages/MainPage.tsx
@@ -9,6 +9,7 @@ import {
IconButton,
Skeleton,
useMediaQuery,
+ useTheme,
} from '@mui/material';
import { Link, useLocation } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
@@ -34,6 +35,8 @@ import {
EmptyState,
} from '../components/ui';
import MainPageRecommendations from './MainPageRecommendations';
+import { CategoryBrowseGrid, QuickStatsStrip } from '../components/home/DesktopHomeSections';
+import { useCategories } from '../api/queries/useCategories';
const TOP_LIMIT = 5;
const AUTO_ADVANCE_MS = 6000;
@@ -91,6 +94,8 @@ const MainPage: React.FC = () => {
const [autoPlay, setAutoPlay] = useState(true);
const [hovered, setHovered] = useState(false);
const prefersReducedMotion = useMediaQuery('(prefers-reduced-motion: reduce)');
+ const muiTheme = useTheme();
+ const isDesktop = useMediaQuery(muiTheme.breakpoints.up('md'));
const touchStartX = useRef(null);
const query = new URLSearchParams(location.search).get('q') || '';
@@ -100,6 +105,7 @@ const MainPage: React.FC = () => {
const { data: searchResults = [], isLoading: searchLoading } = useFlavorSearch(query);
const { data: topFlavorsAll = [], isLoading: topLoading } = useTopFlavors(null);
const { data: newestFlavors = [], isLoading: newestLoading } = useNewestFlavors();
+ const { data: categories = [] } = useCategories();
const topFlavors = topFlavorsAll
.filter((f) => f.category_slug !== 'packs-and-other')
@@ -268,6 +274,16 @@ const MainPage: React.FC = () => {
)}
+ {/* Desktop-only featured strip — fills the wider canvas */}
+ {isDesktop && (
+
+ )}
+
{/* Hall of Fame */}
{
)}
+ {/* Desktop-only category browser */}
+ {isDesktop && }
+
{/* Algorithmic discovery: personalized (members) or popular picks (visitors) */}