Files
CashuMints.space/web/src/components/Topbar.astro
T
michilis 6f17b572b1 Expand ecash explorer capabilities
Add Fedimint discovery, dual SQLite/Postgres storage, richer review handling, and generated social imagery.
2026-08-21 02:10:48 +02:00

94 lines
3.4 KiB
Plaintext

---
import Moai from './Moai.astro';
import LanguageSwitcher from './LanguageSwitcher.astro';
import { localePath, useI18n } from '../i18n';
import { pageLocale } from '../i18n/paths';
interface Props {
current?: 'mints' | 'fedimints' | 'reviews' | 'wallets' | 'about';
/** Shows the compact search affordance next to the brand (mint pages, /mints). */
mintCount?: number;
}
const { current, mintCount } = Astro.props;
const { locale } = pageLocale(Astro);
const t = useI18n(locale);
/*
* Route slugs stay English in every language, so the href is the English path run
* through `localePath`. See src/i18n/routing.ts for why that trade was made.
*/
/*
* "Cashu mints", not "Mints".
*
* Two ecosystems are listed now, so a bare "Mints" beside "Fedimints" reads as though
* federations were something other than mints — or as though the first link covered
* both. The extra word costs ~45px in the widest nav row and buys a label that is
* unambiguous at a glance. It is the same wording on /mints' own heading, in the
* breadcrumb and in the footer, so nothing calls the same page two things.
*/
const links = [
{ path: '/mints', label: t('nav.mints'), key: 'mints' },
{ path: '/fedimints', label: t('nav.fedimints'), key: 'fedimints' },
{ path: '/reviews', label: t('nav.reviews'), key: 'reviews' },
{ path: '/wallets', label: t('nav.wallets'), key: 'wallets' },
{ path: '/about', label: t('nav.about'), key: 'about' },
] as const;
---
<header class="topbar">
<div class="topbar-inner">
<a class="brand" href={localePath('/', locale)}>
<Moai />
{/* Below 420px the wordmark is what pushes the nav off the right edge. The
moai stays, and the name stays in the accessibility tree. */}
<span class="brand-text">Cashumints.space</span>
<span class="sr-only">Cashumints.space</span>
</a>
{
mintCount !== undefined && (
<a class="topbar-search" href={`${localePath('/mints', locale)}#mint-search`}>
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true">
<circle cx="11" cy="11" r="7" /><path d="m20 20-3.5-3.5" />
</svg>
{t('nav.searchMints', { n: mintCount })}
<kbd>/</kbd>
</a>
)
}
<nav class="nav" aria-label={t('nav.main')}>
{
links.map((link) => (
<a
href={localePath(link.path, locale)}
aria-current={current === link.key ? 'page' : undefined}
>
{link.label}
</a>
))
}
</nav>
{/*
The language control, next to the account one and in every layout.
In the header rather than only in the footer because of who needs it: a reader who
landed on a page in a language they do not read should not have to scroll the whole
of it to get out. The footer keeps its own copy, where someone deliberately looking
for site settings expects to find one.
*/}
<LanguageSwitcher variant="menu" />
{/*
The account control. Prerendered logged out, because a static build cannot know
who is reading; the island swaps in the avatar and name from the stored session
and the cached profile on first paint, before any relay is asked anything.
*/}
<div class="auth" data-auth>
<button class="auth-login" type="button" data-login-open>{t('login.open')}</button>
</div>
</div>
</header>