feat(frontend): show the RD mark beside the storefront wordmark #75

Merged
bermudalamb merged 1 commits from feature/header-brand-mark into main 2026-08-20 11:00:18 -05:00
3 changed files with 88 additions and 3 deletions
+10 -3
View File
@@ -4,6 +4,7 @@ import { ShoppingCartOutlined, FilterOutlined } from '@ant-design/icons';
import { Link, useLocation, useSearchParams } from 'react-router-dom'; import { Link, useLocation, useSearchParams } from 'react-router-dom';
import { Item, FilterOptions, fetchItems, fetchFilterOptions } from './api'; import { Item, FilterOptions, fetchItems, fetchFilterOptions } from './api';
import ItemCard from './components/ItemCard'; import ItemCard from './components/ItemCard';
import BrandMark from './components/BrandMark';
import FilterDrawer from './components/FilterDrawer'; import FilterDrawer from './components/FilterDrawer';
import ActiveFilterChips from './components/ActiveFilterChips'; import ActiveFilterChips from './components/ActiveFilterChips';
import { import {
@@ -118,9 +119,15 @@ export default function App() {
borderBottom: `1px solid ${token.colorBorderSecondary}` borderBottom: `1px solid ${token.colorBorderSecondary}`
}} }}
> >
<Title level={3} className="site-header-title" style={{ color: token.colorText }}> {/* Wrapped so the mark and the wordmark travel together — the title
Redefined Designs keeps its own ellipsis behaviour when the header gets narrow, and the
</Title> mark is never what gets truncated. */}
<div className="site-header-brand" style={{ color: token.colorText }}>
<BrandMark size={28} />
<Title level={3} className="site-header-title" style={{ color: token.colorText }}>
Redefined Designs
</Title>
</div>
<div className="site-header-actions"> <div className="site-header-actions">
<Switch checked={mode === 'dark'} onChange={toggle} checkedChildren="Dark" unCheckedChildren="Light" /> <Switch checked={mode === 'dark'} onChange={toggle} checkedChildren="Dark" unCheckedChildren="Light" />
<Link to="/cart"> <Link to="/cart">
+65
View File
@@ -0,0 +1,65 @@
import { useId } from 'react';
interface Props {
size?: number;
className?: string;
}
/**
* The RD monogram, as an inline SVG rather than an `<img src="/favicon.svg">`.
*
* 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 (
<svg
className={className}
width={size}
height={size}
viewBox="0 0 64 64"
aria-hidden="true"
focusable="false"
>
<mask id={maskId}>
{/* White shows the tile, black punches the letters through it. */}
<rect x="2" y="2" width="60" height="60" rx="15" fill="#fff" />
<g
fill="none"
stroke="#000"
strokeWidth="6.5"
strokeLinecap="butt"
strokeLinejoin="miter"
>
<path d="M15 18 V46" />
<path d="M15 18 H20 A7 7 0 0 1 20 32 H15" />
<path d="M20 32 L27 46" />
<path d="M37 18 V46" />
<path d="M37 18 H38 A14 14 0 0 1 38 46 H37" />
</g>
</mask>
<rect
x="2"
y="2"
width="60"
height="60"
rx="15"
fill="currentColor"
mask={`url(#${maskId})`}
/>
</svg>
);
}
+13
View File
@@ -45,6 +45,19 @@ body { margin: 0; }
line-height: normal; 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 { .site-header-title {
margin: 0 !important; margin: 0 !important;
font-size: clamp(16px, 5vw, 24px); font-size: clamp(16px, 5vw, 24px);