From 7ab76ccafb41cdfa37fb34c6c1eb237348662f9e Mon Sep 17 00:00:00 2001 From: npeter83 Date: Sat, 4 Jul 2026 06:17:40 +0200 Subject: [PATCH] feat(ui): favicon, dynamic tab title, clickable logo, styled module headers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Session-close cosmetics: 1. Favicon — an 'S' monogram (indigo→violet rounded square, path-drawn so it's font-independent) at public/favicon.svg, linked in index.html (tab had none before). 2. Dynamic document.title — reflects the current module ('Downloads · Siftlode'); Feed = brand only; an open channel page shows the channel name. Shared pageTitleKey() in lib/pageMeta.ts keeps the tab title and the top-bar header in lockstep. 3. The 'Siftlode' logo already navigated to Feed but read as static text — added a hover background + cursor affordance so it's clearly clickable. 4. Module header — new shared PageTitle component (used by every non-feed module via Header) with a tracked small-caps 'eyebrow' + leading accent dot (user-picked style B), styled in one place. Verified in a real browser: favicon served (image/svg+xml), title updates per module, logo→feed, header restyled; no console errors. --- frontend/index.html | 1 + frontend/public/favicon.svg | 11 +++++++++ frontend/src/App.tsx | 13 +++++++++++ frontend/src/components/Header.tsx | 24 ++++--------------- frontend/src/components/NavSidebar.tsx | 3 ++- frontend/src/components/PageTitle.tsx | 11 +++++++++ frontend/src/lib/pageMeta.ts | 32 ++++++++++++++++++++++++++ 7 files changed, 74 insertions(+), 21 deletions(-) create mode 100644 frontend/public/favicon.svg create mode 100644 frontend/src/components/PageTitle.tsx create mode 100644 frontend/src/lib/pageMeta.ts 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"; + } +}