---
import Base from '../../layouts/Base.astro';
import ColorBends from '../../components/ColorBends.astro';
import MintCard from '../../components/MintCard.astro';
import PulseTicker from '../../components/PulseTicker.astro';
import {
fetchFedimint, fetchFedimints, fetchHealth, fetchLnurlMint, fetchLnurlMints, fetchMint,
fetchMints, fetchStats,
} from '../../lib/api';
import { fetchLatestReviews } from '../../lib/nostr-build';
import { feedTargets, targetPath } from '../../lib/feed-resolve';
import { displayDomain, iconGradient, sentiment, shortNpub } from '../../lib/format';
import { profileName, readCapabilities } from '@cashumints/shared';
import { localePath, useI18n, type Locale } from '../../i18n';
import { formatters } from '../../i18n/format';
import { localePaths } from '../../i18n/paths';
/** One page per locale: `/`, `/es`, `/nl`. */
export const getStaticPaths = localePaths;
interface Props {
locale: Locale;
}
const { locale } = Astro.props;
const t = useI18n(locale);
const f = formatters(t);
const [mints, federations, lnurlMints, stats, health] = await Promise.all([
fetchMints(),
fetchFedimints(),
fetchLnurlMints(),
fetchStats(),
fetchHealth(),
]);
const top = mints.slice(0, 6);
const topFederations = federations.slice(0, 6);
const topLnurl = lnurlMints.slice(0, 6);
/*
* The reviews strip resolves against every ecosystem, because its heading is "latest
* reviews" and not "latest mint reviews". Federations rarely reach it in practice — the
* junk filter wants a written body and nearly every 38173 review is a bare `[5/5]` —
* but when one does, it is genuinely among the latest and it links to its own page. The
* same is true of an LNURL mint, which today has the further handicap that 38174 is a
* proposed kind with barely any events on the network at all.
*/
const reviewTargets = feedTargets(mints, federations, lnurlMints);
/*
* The mint details and the relay read have nothing to say to each other — the reviews
* only need the target list above — so they wait together rather than one behind the
* other.
*
* The carousel holds a screenful and a bit: enough that scrolling it is worth doing,
* few enough that they are all real, recent, written reviews. Anything the relays
* cannot fill is simply fewer cards, and the carousel's controls disappear with them.
*/
const [details, fediDetails, lnurlDetails, latest] = await Promise.all([
// Distributions give the card sentiment bars real data instead of a rating proxy.
Promise.all(top.map((m) => fetchMint(m.host).catch(() => null))),
Promise.all(topFederations.map((federation) => fetchFedimint(federation.host).catch(() => null))),
Promise.all(topLnurl.map((mint) => fetchLnurlMint(mint.host).catch(() => null))),
fetchLatestReviews(reviewTargets, 10),
]);
const sentiments = details.map((d) => (d ? sentiment(d.rating_distribution) : undefined));
// Same details, read a second way: the card's "melt only" or "frozen" chip.
const capabilities = details.map((d) => (d ? readCapabilities(d.info?.nuts) : null));
// No capability read for federations: they publish no NUT switches to draw a chip from.
const fediSentiments = fediDetails.map((d) => (d ? sentiment(d.rating_distribution) : undefined));
const lnurlSentiments = lnurlDetails.map((d) => (d ? sentiment(d.rating_distribution) : undefined));
/*
* An LNURL card's chip comes from two probed facts rather than from NUT switches: an
* advertised withdraw ceiling of zero, and a Lightning node the mint cannot reach.
*/
const lnurlChips = lnurlDetails.map((d) =>
d
? {
max_withdrawable_msat: d.max_withdrawable_msat ?? null,
funding_available: d.funding_available ?? null,
}
: null,
);
const description = t('home.description', {
mints: stats.mints_total,
reviews: stats.reviews_total,
});
/*
* The one sentence on the page with a link inside it. Interpolated as markup rather
* than split into three strings, so a translator can put "NIP-87" wherever the
* sentence needs it instead of wherever English left a gap.
*/
const signedBody = t('home.why.signed.body', {
link: `${t('home.why.signed.link')}`,
});
---
{t('home.hero.lede')}
{t('home.latest.empty')}
) : ({t('home.why.verify.body')}
{t('home.why.checked.body')}