Ship LNURL mint pages, indexing UI, and reviews rewrite.

Add lnurl list/detail routes, OG fixtures, i18n strings, and the write/
index client flows so the site surfaces the new mint type end to end.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
michilis
2026-08-22 03:44:43 +02:00
co-authored by Cursor
parent 2a9444942b
commit c74c7fc187
66 changed files with 7603 additions and 1241 deletions
+155 -20
View File
@@ -194,6 +194,20 @@
--violet: #A78BFA;
--mint: #5FD68F;
--red: #E06450;
/*
* The LNURL ecosystem accent, and the only cool blue on this site.
*
* The palette is otherwise warm, which is exactly what makes this readable as "a
* different ecosystem" at thumbnail size on a social card. Every other candidate was
* already taken by a meaning the colour discipline reserves: amber is the brand,
* mint is online/success, red is offline/critical, and violet is Nostr-protocol
* (which the Fedimint badge borrows). A blue is the only hue left that a reader
* cannot mistake for a status.
*
* Used for the LNURL card's badge and glow, and nowhere else. It is an ecosystem
* label, not a state, and must never decorate one.
*/
--lnurl: #4EA8DE;
--radius: 14px;
--body: 'Inter', 'Inter Fallback', system-ui, -apple-system, 'Segoe UI', sans-serif;
@@ -523,18 +537,85 @@ main { flex: 1; }
.kv-row .copy-btn { margin-left: auto; }
/* ---------- footer ---------- */
footer { border-top: 1px solid var(--line-soft); margin-top: 70px; }
.foot-inner {
max-width: var(--page-max); margin: 0 auto; padding: 28px;
display: flex; align-items: center; gap: 20px; flex-wrap: wrap;
font-size: 13px; color: var(--faint);
/*
* Three zones: the columns, then a thin bar. `main { flex: 1 }` and the column layout
* on `body` are what put this at the bottom of the viewport on a short page (the LNURL
* index with one mint) rather than halfway up it, so this needs no min-height of its
* own. `margin-top: 70px` is the gap after a long page; on a short one the flex growth
* of `main` swallows it.
*/
.site-footer { border-top: 1px solid var(--line-soft); margin-top: 70px; }
.foot-cols {
max-width: var(--page-max); margin: 0 auto;
padding: 44px var(--page-pad) 36px;
display: grid;
/*
* Four visible columns from three grid tracks: the middle one is the nav landmark,
* which holds Explore and Site. One landmark over both, rather than a track each,
* is what keeps the landmark list honest — see Footer.astro.
*/
grid-template-columns: minmax(0, 2.1fr) minmax(0, 2fr) minmax(0, 1.2fr);
gap: 40px 44px;
}
.foot-nav { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 40px 32px; }
/* The brand column. Narrower than its track so the sentences break at a readable
measure instead of running the full width of a 1240px grid cell. */
.foot-brand { max-width: 34ch; }
.foot-mark {
display: inline-flex; align-items: center; gap: 10px;
font-family: var(--display); font-weight: 700; font-size: 15.5px; letter-spacing: -.01em;
}
.foot-desc { color: var(--muted); font-size: 13.5px; line-height: 1.55; margin-top: 12px; }
/*
* The custodial warning. `--muted`, not `--faint`: it is the one sentence in this
* footer that a reader is worse off for having missed, and it used to be an orphan in
* the bottom right corner of the last row.
*/
.foot-warn { color: var(--muted); font-size: 13px; line-height: 1.5; margin-top: 10px; }
/* The small-caps label, the same one `.side-title` and `.fb-stat .k` use. An h2 in the
markup, so the margin is stated in full and the UA's own h2 margins cannot show. */
.foot-head {
font-size: 11px; font-weight: 600; letter-spacing: .09em; text-transform: uppercase;
color: var(--faint); margin: 0 0 14px;
}
.foot-col ul { list-style: none; display: flex; flex-direction: column; gap: 8px; }
.foot-col a {
font-size: 13.5px; color: var(--muted);
transition: color var(--dur-quick);
}
.foot-col a:hover { color: var(--text); }
.foot-bar {
max-width: var(--page-max); margin: 0 auto;
padding: 16px var(--page-pad);
border-top: 1px solid var(--line-soft);
display: flex; align-items: center; justify-content: space-between;
gap: 8px 24px; flex-wrap: wrap;
font-size: 12.5px; color: var(--faint);
}
.foot-inner .links { display: flex; gap: 18px; flex-wrap: wrap; }
.foot-inner a:hover { color: var(--muted); }
.foot-right { margin-left: auto; }
/* Dotted underline like every other in-sentence link: colour alone is not enough
to mark a link inside running text (WCAG 1.4.1). */
.foot-right a { color: var(--amber); border-bottom: 1px dotted rgba(240, 169, 59, .4); }
.foot-credit a { color: var(--amber); border-bottom: 1px dotted rgba(240, 169, 59, .4); }
/*
* The stack.
*
* At 900px the three tracks become one, which puts the columns in source order —
* Brand, then the nav's two columns side by side, then Language. Explore and Site stay
* paired down to 460px because two short link lists next to each other read as one
* block; below that they are two lists that would each be one word wide.
*/
@media (max-width: 900px) {
.foot-cols { grid-template-columns: minmax(0, 1fr); gap: 34px; }
.foot-brand { max-width: 46ch; }
}
@media (max-width: 460px) {
.foot-nav { grid-template-columns: minmax(0, 1fr); gap: 30px; }
}
/* ---------- prose (about, wallets) ---------- */
.prose { max-width: 720px; }
@@ -559,24 +640,29 @@ footer { border-top: 1px solid var(--line-soft); margin-top: 70px; }
/*
* Below this the header stops being one row.
*
* Five nav items, the brand, the language control and the account control do not fit
* Six nav items, the brand, the language control and the account control do not fit
* across anything narrower than a laptop, and a header that does not fit is a page that
* scrolls sideways: the body inherits the overflow and every section under it drifts. So
* the nav takes a row of its own and the two controls stay up top with the brand, which
* is also the order they are wanted in: what page am I on, then who am I and what
* language is this.
*
* 1040px, not the 620px this used to be. The old figure was already two pixels short of
* fitting four English items at 768, and five need 400px against the 303px the first row
* has left there — no amount of tightening the padding closes a gap that size.
* The number is set by the widest locale, never by English. Picking the English figure
* is how this shipped broken once already: every locale renders the same stylesheet, so
* the breakpoint has to clear the widest of them, with room for the next language to be
* longer still.
*
* The number is set by Spanish, which is the longest of the three ("Mints Cashu /
* Fedimints / Reseñas / Monederos / Acerca de") and needs 1008px to fit on one row
* against English's 906. Picking the English figure is how this shipped broken once
* already: every locale renders the same stylesheet, so the breakpoint has to clear the
* widest of them, with room for the next language to be longer still.
* History, because the direction of travel matters here: 620px fitted four items,
* 1040px fitted five (set by Spanish at 1008px against English's 906), and adding the
* LNURL index made six. Measured across all shipped locales at the time it was added,
* the widest first row needs 1123px, so this sits at 1180 — the same kind of headroom
* the 1040 figure carried over its own 1008.
*
* At 1280px, the widest desktop this site targets, all six fit on one row with room to
* spare in every locale. A seventh would not, and would need a real menu rather than
* another bump: this row is full.
*/
@media (max-width: 1040px) {
@media (max-width: 1180px) {
.topbar-inner { flex-wrap: wrap; row-gap: 10px; }
/* Pushes the pair to the right of the first row, where they were before it wrapped. */
.topbar-inner > .lang-menu { margin-left: auto; }
@@ -601,7 +687,10 @@ footer { border-top: 1px solid var(--line-soft); margin-top: 70px; }
@media (max-width: 640px) {
:root { --page-pad: 18px; }
.mint-grid { grid-template-columns: minmax(0, 1fr); }
.foot-right { margin-left: 0; }
/* Two centred lines rather than one row pushed to both edges: at this width the
copyright and the credit would otherwise sit in opposite corners with a gulf
between them. */
.foot-bar { justify-content: center; text-align: center; gap: 6px; }
}
@media (max-width: 420px) {
.brand { gap: 0; }
@@ -884,6 +973,52 @@ footer { border-top: 1px solid var(--line-soft); margin-top: 70px; }
.fb-stats { display: flex; gap: 26px; flex-wrap: wrap; margin-top: 8px; }
.fb-stat .k { font-size: 11px; letter-spacing: .09em; text-transform: uppercase; color: var(--faint); display: block; }
.fb-stat .v { font-family: var(--display); font-size: 24px; font-weight: 700; }
/* The one action a resolved-on-arrival mint offers: review it, right now. */
.fb-actions { display: flex; gap: 12px; flex-wrap: wrap; margin-top: 18px; }
/* The reviews panel the resolver starts once it has a subject to give it. */
.nf-reviews { margin-top: 26px; }
/* ---------- review by URL (the two-step dialog) ---------- */
/*
* Step one asks for an address. It is the same input the search boxes use, so it reads
* as one control family across the site rather than as a form that turned up in a modal.
*/
.rbu-input {
background: var(--bg); border: 1px solid var(--line); border-radius: 10px;
color: var(--text); font-family: var(--mono); font-size: 14px;
padding: 12px 12px; width: 100%; display: block;
}
.rbu-input:focus { outline: 2px solid var(--amber); outline-offset: 1px; }
.rbu-input:disabled { color: var(--muted); background: var(--card2); }
.rbu-input::placeholder { color: var(--faint); font-family: var(--mono); }
/* "That address answered as a Cashu mint" plus the button that acts on it. */
.rbu-switch { font-size: 13px; color: var(--muted); line-height: 1.6; }
/* Step two's way back, above the mint it is about to review. */
.rbu-back {
align-self: flex-start; display: inline-flex; align-items: center; gap: 5px;
border: none; background: none; cursor: pointer; padding: 2px 0;
font-family: var(--body); font-size: 12.5px; color: var(--muted);
}
.rbu-back:hover { color: var(--amber); }
/* The mint that was found: enough to recognise it by, and no more. */
.rbu-preview {
display: flex; align-items: center; gap: 11px; flex-wrap: wrap;
background: var(--card2); border: 1px solid var(--line-soft);
border-radius: 11px; padding: 10px 12px;
}
.rbu-icon {
width: 36px; height: 36px; border-radius: 9px; flex-shrink: 0;
display: inline-flex; align-items: center; justify-content: center;
font-family: var(--display); font-size: 16px; font-weight: 700; color: #141109;
}
.rbu-who { display: flex; flex-direction: column; gap: 1px; min-width: 120px; flex: 1; }
.rbu-name { font-size: 14px; font-weight: 600; overflow-wrap: anywhere; }
.rbu-domain { font-family: var(--mono); font-size: 11.5px; color: var(--faint); overflow-wrap: anywhere; }
.rbu-chips { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.rbu-note { font-size: 11.5px; color: var(--faint); }
/* ---------- skeletons ---------- */
/*