diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx
index 26eadac..55282f6 100755
--- a/frontend/src/App.tsx
+++ b/frontend/src/App.tsx
@@ -4,6 +4,7 @@ import { ShoppingCartOutlined, FilterOutlined } from '@ant-design/icons';
import { Link, useLocation, useSearchParams } from 'react-router-dom';
import { Item, FilterOptions, fetchItems, fetchFilterOptions } from './api';
import ItemCard from './components/ItemCard';
+import BrandMark from './components/BrandMark';
import FilterDrawer from './components/FilterDrawer';
import ActiveFilterChips from './components/ActiveFilterChips';
import {
@@ -118,9 +119,15 @@ export default function App() {
borderBottom: `1px solid ${token.colorBorderSecondary}`
}}
>
-
- Redefined Designs
-
+ {/* Wrapped so the mark and the wordmark travel together — the title
+ keeps its own ellipsis behaviour when the header gets narrow, and the
+ mark is never what gets truncated. */}
+
+
+
+ Redefined Designs
+
+
diff --git a/frontend/src/components/BrandMark.tsx b/frontend/src/components/BrandMark.tsx
new file mode 100644
index 0000000..963dc11
--- /dev/null
+++ b/frontend/src/components/BrandMark.tsx
@@ -0,0 +1,65 @@
+import { useId } from 'react';
+
+interface Props {
+ size?: number;
+ className?: string;
+}
+
+/**
+ * The RD monogram, as an inline SVG rather than an ``.
+ *
+ * Two reasons it has to be inline. The tile is drawn in `currentColor`, so it
+ * follows the antd text token and inverts with the app's own theme switch — the
+ * favicon file inverts on `prefers-color-scheme`, which tracks the operating
+ * system and would disagree with the app whenever someone toggles the theme
+ * themselves. And the letters are knocked out with a mask rather than painted in
+ * the background colour, so the mark sits correctly on any surface instead of
+ * carrying a hardcoded backdrop that only matches the header.
+ *
+ * Decorative: the header already says "Redefined Designs" beside it, so it is
+ * hidden from assistive technology rather than read out twice.
+ */
+export default function BrandMark({ size = 28, className }: Props) {
+ // Unique per instance — two marks on one page would otherwise share a mask id
+ // and the second would reference the first. The id is stripped to alphanumerics
+ // because React's generated ids contain colons.
+ const maskId = `rd-knockout-${useId().replace(/[^a-zA-Z0-9]/g, '')}`;
+
+ return (
+
+ );
+}
diff --git a/frontend/src/styles.css b/frontend/src/styles.css
index 057cfa6..a222c88 100755
--- a/frontend/src/styles.css
+++ b/frontend/src/styles.css
@@ -45,6 +45,19 @@ body { margin: 0; }
line-height: normal;
}
+.site-header-brand {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ flex: 1 1 auto;
+ min-width: 0;
+}
+
+/* The mark holds its size while the wordmark absorbs the squeeze. */
+.site-header-brand svg {
+ flex: none;
+}
+
.site-header-title {
margin: 0 !important;
font-size: clamp(16px, 5vw, 24px);