feat: UI polish, a11y, performance optimizations

- Payment summary card with prominent amount display
- "Bezahlt" stamp over dimmed QR code with TX details below
- Hide wallet/address buttons when paid, show only PDF
- URI box removed (was technical noise)
- Smart countdown: "29 Tage, 23 Std." instead of ticking seconds
- Dynamic page title for shared invoices
- Font fallbacks with size-adjust to prevent layout shifts
- Async Google Fonts loading, proper preconnect hints
- Deferred script loading (defer attribute)
- Minified JS (app.min.js, i18n.min.js)
- WCAG contrast fixes for badges and disabled button
- Footer link always underlined for a11y
- Translated aria-labels via data-i18n-aria
- i18n onChange callback for dynamic content updates
- Result card fade-in animation, responsive QR on mobile
This commit is contained in:
Alexander Schmidt
2026-03-25 16:50:55 +01:00
parent b8f2e24a42
commit 8bcdb33fa3
6 changed files with 133 additions and 46 deletions

17
i18n.js
View File

@@ -32,6 +32,7 @@ var I18n = (function () {
pdf_footer: 'Erstellt mit xmrpay.link — Keine Registrierung, kein KYC',
qr_hint: 'Klick auf QR zum Speichern',
footer: 'Open Source &middot; Kein Backend &middot; Kein KYC &middot; <a href="https://gitea.schmidt.eco/schmidt1024/xmrpay.link" target="_blank">Source</a>',
aria_currency: 'Währung',
label_uri_details: 'Monero-URI anzeigen',
label_share_link: 'Teilbarer Link',
btn_new_request: 'Neue Zahlungsanforderung',
@@ -78,6 +79,7 @@ var I18n = (function () {
pdf_footer: 'Created with xmrpay.link — No registration, no KYC',
qr_hint: 'Click QR to save',
footer: 'Open Source &middot; No Backend &middot; No KYC &middot; <a href="https://gitea.schmidt.eco/schmidt1024/xmrpay.link" target="_blank">Source</a>',
aria_currency: 'Currency',
label_uri_details: 'Show Monero URI',
label_share_link: 'Shareable link',
btn_new_request: 'New payment request',
@@ -126,6 +128,9 @@ var I18n = (function () {
document.querySelectorAll('[data-i18n-html]').forEach(function (el) {
el.innerHTML = t[el.getAttribute('data-i18n-html')] || '';
});
document.querySelectorAll('[data-i18n-aria]').forEach(function (el) {
el.setAttribute('aria-label', t[el.getAttribute('data-i18n-aria')] || '');
});
}
function apply(lang) {
@@ -144,6 +149,16 @@ var I18n = (function () {
document.querySelectorAll('.lang-option').forEach(function (btn) {
btn.classList.toggle('active', btn.getAttribute('data-lang') === lang);
});
// Notify listeners
for (var i = 0; i < onChangeCallbacks.length; i++) {
onChangeCallbacks[i](lang);
}
}
var onChangeCallbacks = [];
function onChange(fn) {
onChangeCallbacks.push(fn);
}
function buildDropdown() {
@@ -205,5 +220,5 @@ var I18n = (function () {
apply(currentLang);
});
return { t: t, apply: apply, getLang: getLang };
return { t: t, apply: apply, getLang: getLang, onChange: onChange };
})();