diff --git a/frontend/index.html b/frontend/index.html index 65cb860..fef8afa 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -3,6 +3,7 @@ + Siftlode diff --git a/frontend/public/favicon.svg b/frontend/public/favicon.svg new file mode 100644 index 0000000..001d9a0 --- /dev/null +++ b/frontend/public/favicon.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index b33ee87..2a86416 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -18,6 +18,7 @@ import { type ThemePrefs, } from "./lib/theme"; import { hasFilterParams, isPage, paramsToFilters, readPage, stripUrlParams, type Page } from "./lib/urlState"; +import { pageTitleKey } from "./lib/pageMeta"; import { loadLayout, normalizeLayout, @@ -284,6 +285,18 @@ export default function App() { // Expose the current setPage to decoupled callers (download toast "View", etc.). useEffect(() => setNavigator(setPage)); + // Reflect the current module in the browser tab title so open tabs are distinguishable and the + // history reads sensibly. Feed = brand only; an open channel page shows the channel name. + useEffect(() => { + const brand = "Siftlode"; + const label = channelView + ? channelView.name || t("header.channelManager") + : page === "feed" + ? null + : t(pageTitleKey(page)); + document.title = label ? `${label} · ${brand}` : brand; + }, [page, channelView, i18n.language, t]); + function setSidebarLayout(next: SidebarLayout) { setSidebarLayoutState(next); saveLayoutLocal(next); diff --git a/frontend/src/components/Header.tsx b/frontend/src/components/Header.tsx index 69fa319..2967d18 100644 --- a/frontend/src/components/Header.tsx +++ b/frontend/src/components/Header.tsx @@ -2,6 +2,8 @@ import { useTranslation } from "react-i18next"; import { Search, X, Youtube } from "lucide-react"; import type { FeedFilters, Me } from "../lib/api"; import type { Page } from "../lib/urlState"; +import { pageTitleKey } from "../lib/pageMeta"; +import PageTitle from "./PageTitle"; // Contextual top bar over the content column. Primary navigation, account and the per-user sync // status live in the left NavSidebar; the feed's scope toggle now lives in the filter sidebar. @@ -72,26 +74,8 @@ export default function Header({ )} ) : ( -
- {page === "stats" - ? t("header.usageStats") - : page === "playlists" - ? t("header.account.playlists") - : page === "settings" - ? t("settings.title") - : page === "scheduler" - ? t("header.scheduler") - : page === "config" - ? t("header.configuration") - : page === "users" - ? t("header.users") - : page === "notifications" - ? t("inbox.navLabel") - : page === "messages" - ? t("messages.navLabel") - : page === "downloads" - ? t("downloads.navLabel") - : t("header.channelManager")} +
+
)} diff --git a/frontend/src/components/NavSidebar.tsx b/frontend/src/components/NavSidebar.tsx index 56a635b..965d6be 100644 --- a/frontend/src/components/NavSidebar.tsx +++ b/frontend/src/components/NavSidebar.tsx @@ -256,8 +256,9 @@ export default function NavSidebar({ {!collapsed && ( diff --git a/frontend/src/components/PageTitle.tsx b/frontend/src/components/PageTitle.tsx new file mode 100644 index 0000000..1ea61ae --- /dev/null +++ b/frontend/src/components/PageTitle.tsx @@ -0,0 +1,11 @@ +// The centered module title in the top bar. Every non-feed module renders through this one +// component, so its styling lives in a single place. Design element (user-picked): a tracked +// small-caps "eyebrow" with a leading accent dot. +export default function PageTitle({ label }: { label: string }) { + return ( + + + {label} + + ); +} diff --git a/frontend/src/lib/pageMeta.ts b/frontend/src/lib/pageMeta.ts new file mode 100644 index 0000000..19dd9d9 --- /dev/null +++ b/frontend/src/lib/pageMeta.ts @@ -0,0 +1,32 @@ +import type { Page } from "./urlState"; + +// The i18n key for a page's human title — shared by the top-bar header and the browser tab title +// so the two never drift. Keep in sync with the nav modules in NavSidebar / App's page switch. +export function pageTitleKey(page: Page): string { + switch (page) { + case "feed": + return "header.account.feed"; + case "channels": + return "header.channelManager"; + case "playlists": + return "header.account.playlists"; + case "notifications": + return "inbox.navLabel"; + case "messages": + return "messages.navLabel"; + case "downloads": + return "downloads.navLabel"; + case "stats": + return "header.usageStats"; + case "scheduler": + return "header.scheduler"; + case "config": + return "header.configuration"; + case "users": + return "header.users"; + case "settings": + return "settings.title"; + default: + return "header.account.feed"; + } +}