From 787168b248770b4203e853d25d65b36b1d732e46 Mon Sep 17 00:00:00 2001 From: Alexander Schmidt Date: Wed, 25 Mar 2026 17:18:41 +0100 Subject: [PATCH] =?UTF-8?q?perf:=20100%=20Lighthouse=20score=20=E2=80=94?= =?UTF-8?q?=20contrast,=20CLS,=20caching=20fixes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Font preload eliminates layout shift (CLS 0) - Dual accent colors: --accent for filled buttons, --accent-text for text on dark bg - All WCAG AA contrast ratios met (buttons, links, badges, countdown) - Language picker: position absolute instead of fixed - CoinGecko rates proxied with 2min server-side cache (no CORS, no rate limit) - English as default inline text (no empty→text shift) --- api/rates.php | 43 +++++++++++++++++++++++++++++++++++++------ index.html | 1 + style.css | 19 ++++++++++--------- 3 files changed, 48 insertions(+), 15 deletions(-) diff --git a/api/rates.php b/api/rates.php index 6d60047..5a274b3 100644 --- a/api/rates.php +++ b/api/rates.php @@ -1,9 +1,21 @@ true, @@ -15,8 +27,27 @@ $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); if ($response !== false && $httpCode === 200) { - echo $response; -} else { - http_response_code(502); - echo json_encode(['error' => 'Failed to fetch rates']); + $data = json_decode($response, true); + if ($data) { + $data['_time'] = time(); + file_put_contents($cacheFile, json_encode($data)); + unset($data['_time']); + header('Cache-Control: public, max-age=60'); + echo json_encode($data); + exit; + } } + +// On error, serve stale cache if available +if (file_exists($cacheFile)) { + $cached = json_decode(file_get_contents($cacheFile), true); + if ($cached) { + unset($cached['_time']); + header('Cache-Control: public, max-age=30'); + echo json_encode($cached); + exit; + } +} + +http_response_code(502); +echo json_encode(['error' => 'Failed to fetch rates']); diff --git a/index.html b/index.html index 329fe09..6fb0b19 100644 --- a/index.html +++ b/index.html @@ -6,6 +6,7 @@ xmrpay.link — Monero Invoice Generator + diff --git a/style.css b/style.css index 3632fea..dc2e083 100644 --- a/style.css +++ b/style.css @@ -23,8 +23,9 @@ --border: #333; --text: #e0e0e0; --text-muted: #888; - --accent: #ff6600; - --accent-hover: #ff8533; + --accent: #c74a00; + --accent-hover: #a83f00; + --accent-text: #e87830; --success: #4caf50; --error: #f44336; --radius: 8px; @@ -54,7 +55,7 @@ header { } .lang-picker { - position: fixed; + position: absolute; top: 0.75rem; right: 0.75rem; z-index: 50; @@ -119,7 +120,7 @@ header { } .lang-option.active { - color: var(--accent); + color: var(--accent-text); } header h1 { @@ -138,7 +139,7 @@ header h1 a:hover { } header h1 span { - color: var(--accent); + color: var(--accent-text); } header p { @@ -590,8 +591,8 @@ textarea { .btn-new { margin-top: 0.8rem; background: transparent; - border: 1px solid var(--accent); - color: var(--accent); + border: 1px solid var(--accent-text); + color: var(--accent-text); } .btn-new:hover { @@ -628,7 +629,7 @@ footer { } footer a { - color: var(--accent); + color: var(--accent-text); text-decoration: underline; } @@ -644,7 +645,7 @@ footer a { } .countdown.active { - color: var(--accent); + color: var(--accent-text); } @media (max-width: 500px) {