feat: mark the 4O3A panels in the settings sidebar

Antenna Genius and Tuner Genius now carry a small 4O3A wordmark, so the two
panels that drive that hardware are recognisable without reading the labels.

Drawn inline rather than shipped as an image file, following the language
picker's flags: it scales with the row, follows the theme through currentColor,
and adds no binary asset to the repository.

It is a plain wordmark, not the manufacturer's logo artwork — I did not pull an
image off the web, since I cannot verify the provenance or licence of what I
would find, and redistributing a company's mark inside the product is the
author's call. The component is one function and takes the official SVG in its
place if that is preferred.

Not applied to the Amplifier panel: it covers both the 4O3A PowerGenius XL and
the SPE Expert range, so a single vendor mark there would be wrong.
This commit is contained in:
2026-07-29 23:28:27 +02:00
parent d98bb2e929
commit 337392bb6d
2 changed files with 27 additions and 5 deletions
+23 -3
View File
@@ -198,7 +198,26 @@ type SectionId =
type TreeNode =
| { kind: 'group'; label: string; icon?: any; defaultOpen?: boolean; children: TreeNode[] }
| { kind: 'item'; label: string; id: SectionId; disabled?: boolean };
| { kind: 'item'; label: string; id: SectionId; disabled?: boolean; vendor?: 'o3a' };
// A compact 4O3A wordmark for the sidebar, drawn here rather than shipped as an
// image: an inline SVG scales with the row, follows the theme (currentColor), and
// adds no binary asset. It marks which panels drive 4O3A hardware — the same
// reason the language picker draws its flags inline.
//
// This is a plain wordmark, NOT the manufacturer's logo artwork. If you would
// rather show the official mark, drop the SVG in and swap this component out.
function VendorMark({ vendor }: { vendor: 'o3a' }) {
if (vendor !== 'o3a') return null;
return (
<span
className="ml-auto shrink-0 rounded-[3px] border border-current/30 px-1 text-[9px] font-bold leading-[14px] tracking-[0.06em] opacity-70"
title="4O3A Signature"
>
4O3A
</span>
);
}
// buildTree returns the settings sidebar. The FlexRadio item only appears when
// the active CAT backend is a Flex (per-band antenna config is Flex-specific).
@@ -208,8 +227,8 @@ function buildTree(flexAvailable: boolean, t: (k: string) => string): TreeNode[]
{ kind: 'item', label: t('sec.rotator'), id: 'rotator' },
{ kind: 'item', label: t('sec.winkeyer'), id: 'winkeyer' },
{ kind: 'item', label: t('sec.antenna'), id: 'antenna' },
{ kind: 'item', label: t('sec.antgenius'), id: 'antgenius' },
{ kind: 'item', label: t('sec.tunergenius'), id: 'tunergenius' },
{ kind: 'item', label: t('sec.antgenius'), id: 'antgenius', vendor: 'o3a' },
{ kind: 'item', label: t('sec.tunergenius'), id: 'tunergenius', vendor: 'o3a' },
{ kind: 'item', label: t('sec.pgxl'), id: 'pgxl' },
...(flexAvailable ? [{ kind: 'item', label: t('sec.flex'), id: 'flex' } as TreeNode] : []),
{ kind: 'item', label: t('sec.relayauto'), id: 'relayauto' },
@@ -325,6 +344,7 @@ function TreeNodeView({
style={{ paddingLeft: 8 + depth * 14 }}
>
<span className="truncate">{node.label}</span>
{node.vendor && !node.disabled && <VendorMark vendor={node.vendor} />}
{node.disabled && (
<Construction className="ml-auto size-3 shrink-0 opacity-60" />
)}