Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/rw3b-offline-template-theme-aware.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@app/ratewise': patch
---

離線頁改為主題感知:依使用者主題顯示對應底色(深色主題不再閃淺紫),並保留斷網自我修復;同步狀態列顏色與 iOS 安全區。
3 changes: 3 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
node_modules/
.pnpm-store/

# 由 generate-offline-html.mjs 從模板生成(prebuild 禁 prettier,避免格式漂移)
apps/ratewise/public/offline.html

# Build output
dist/
build/
Expand Down
224 changes: 199 additions & 25 deletions apps/ratewise/public/offline.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,176 @@
<html lang="zh-TW">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="theme-color" content="#8B5CF6" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
<meta name="theme-color" content="#6366F1" />
<meta name="mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
<title>離線模式 - HaoRate</title>
<!-- [fix:2026-01-08] 使用相對路徑,確保在任何 base path 下都能正確載入 -->
<link rel="icon" type="image/svg+xml" href="favicon.svg" />
<link rel="apple-touch-icon" href="apple-touch-icon.png" />
<script>
(function () {
var themeColors = {"zen":"#6366F1","nitro":"#0096E6","kawaii":"#FF69B4","classic":"#8B4513","ocean":"#06B6D4","forest":"#22C55E"};
var supportedStyles = Object.keys(themeColors);
var selectedStyle = 'zen';

try {
var storedTheme = localStorage.getItem('ratewise-theme');
if (storedTheme) {
var parsedTheme = JSON.parse(storedTheme);
if (supportedStyles.indexOf(parsedTheme.style) >= 0) {
selectedStyle = parsedTheme.style;
}
}
} catch (e) {
selectedStyle = 'zen';
}

document.documentElement.dataset.style = selectedStyle;

var themeColor = document.querySelector('meta[name="theme-color"]');
if (themeColor) {
themeColor.setAttribute('content', themeColors[selectedStyle] || themeColors.zen);
}
})();
</script>
<style>
html[data-style='zen'] {
--offline-theme-color: #6366F1;
--offline-background: #F8FAFC;
--offline-surface: #FFFFFF;
--offline-border: #E2E8F0;
--offline-text: #0F172A;
--offline-text-muted: #64748B;
--offline-primary: #6366F1;
--offline-primary-foreground: #020617;
--offline-secondary: #475569;
--offline-accent: #3B82F6;
--offline-primary-tint: rgba(99, 102, 241, 0.08);
--offline-primary-border-tint: rgba(99, 102, 241, 0.18);
--offline-primary-shadow-soft: rgba(99, 102, 241, 0.18);
--offline-primary-shadow-strong: rgba(99, 102, 241, 0.26);
--offline-primary-shadow-hover: rgba(99, 102, 241, 0.34);
--offline-warning-tint: rgba(245, 158, 11, 0.16);
--offline-warning: #F59E0B;
--offline-success-tint: rgba(34, 197, 94, 0.12);
--offline-success: #22C55E;
}

html[data-style='nitro'] {
--offline-theme-color: #0096E6;
--offline-background: #020617;
--offline-surface: #0F172A;
--offline-border: #1E293B;
--offline-text: #FFFFFF;
--offline-text-muted: #CBD5E1;
--offline-primary: #0096E6;
--offline-primary-foreground: #020617;
--offline-secondary: #818CF8;
--offline-accent: #00FF88;
--offline-primary-tint: rgba(0, 150, 230, 0.08);
--offline-primary-border-tint: rgba(0, 150, 230, 0.18);
--offline-primary-shadow-soft: rgba(0, 150, 230, 0.18);
--offline-primary-shadow-strong: rgba(0, 150, 230, 0.26);
--offline-primary-shadow-hover: rgba(0, 150, 230, 0.34);
--offline-warning-tint: rgba(251, 191, 36, 0.16);
--offline-warning: #FBBF24;
--offline-success-tint: rgba(52, 211, 153, 0.12);
--offline-success: #34D399;
}

html[data-style='kawaii'] {
--offline-theme-color: #FF69B4;
--offline-background: #FFFAF4;
--offline-surface: #FFFFFF;
--offline-border: #FFE4E1;
--offline-text: #8E7C80;
--offline-text-muted: #B4A0A5;
--offline-primary: #FF69B4;
--offline-primary-foreground: #020617;
--offline-secondary: #EC4899;
--offline-accent: #FFB6C1;
--offline-primary-tint: rgba(255, 105, 180, 0.08);
--offline-primary-border-tint: rgba(255, 105, 180, 0.18);
--offline-primary-shadow-soft: rgba(255, 105, 180, 0.18);
--offline-primary-shadow-strong: rgba(255, 105, 180, 0.26);
--offline-primary-shadow-hover: rgba(255, 105, 180, 0.34);
--offline-warning-tint: rgba(253, 224, 71, 0.16);
--offline-warning: #FDE047;
--offline-success-tint: rgba(134, 239, 172, 0.12);
--offline-success: #86EFAC;
}

html[data-style='classic'] {
--offline-theme-color: #8B4513;
--offline-background: #FFFAFB;
--offline-surface: #FFFFFF;
--offline-border: #F5E6DC;
--offline-text: #431407;
--offline-text-muted: #78503C;
--offline-primary: #8B4513;
--offline-primary-foreground: #FFFFFF;
--offline-secondary: #A16207;
--offline-accent: #B47850;
--offline-primary-tint: rgba(139, 69, 19, 0.08);
--offline-primary-border-tint: rgba(139, 69, 19, 0.18);
--offline-primary-shadow-soft: rgba(139, 69, 19, 0.18);
--offline-primary-shadow-strong: rgba(139, 69, 19, 0.26);
--offline-primary-shadow-hover: rgba(139, 69, 19, 0.34);
--offline-warning-tint: rgba(180, 83, 9, 0.16);
--offline-warning: #B45309;
--offline-success-tint: rgba(22, 163, 74, 0.12);
--offline-success: #16A34A;
}

html[data-style='ocean'] {
--offline-theme-color: #06B6D4;
--offline-background: #F0F9FF;
--offline-surface: #FFFFFF;
--offline-border: #BAE6FD;
--offline-text: #075985;
--offline-text-muted: #164E63;
--offline-primary: #06B6D4;
--offline-primary-foreground: #020617;
--offline-secondary: #14B8A6;
--offline-accent: #0284C7;
--offline-primary-tint: rgba(6, 182, 212, 0.08);
--offline-primary-border-tint: rgba(6, 182, 212, 0.18);
--offline-primary-shadow-soft: rgba(6, 182, 212, 0.18);
--offline-primary-shadow-strong: rgba(6, 182, 212, 0.26);
--offline-primary-shadow-hover: rgba(6, 182, 212, 0.34);
--offline-warning-tint: rgba(245, 158, 11, 0.16);
--offline-warning: #F59E0B;
--offline-success-tint: rgba(20, 184, 166, 0.12);
--offline-success: #14B8A6;
}

html[data-style='forest'] {
--offline-theme-color: #22C55E;
--offline-background: #F0FDF4;
--offline-surface: #FFFFFF;
--offline-border: #BBF7D0;
--offline-text: #14532D;
--offline-text-muted: #166534;
--offline-primary: #22C55E;
--offline-primary-foreground: #020617;
--offline-secondary: #84CC16;
--offline-accent: #16A34A;
--offline-primary-tint: rgba(34, 197, 94, 0.08);
--offline-primary-border-tint: rgba(34, 197, 94, 0.18);
--offline-primary-shadow-soft: rgba(34, 197, 94, 0.18);
--offline-primary-shadow-strong: rgba(34, 197, 94, 0.26);
--offline-primary-shadow-hover: rgba(34, 197, 94, 0.34);
--offline-warning-tint: rgba(234, 179, 8, 0.16);
--offline-warning: #EAB308;
--offline-success-tint: rgba(34, 197, 94, 0.12);
--offline-success: #22C55E;
} html[data-style='nitro'] {
color-scheme: dark;
}

* {
margin: 0;
padding: 0;
Expand All @@ -19,51 +182,58 @@
font-family:
-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
min-height: 100vh;
min-height: 100dvh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background: linear-gradient(135deg, #e8ecf4 0%, #f3e8ff 50%, #ede9fe 100%);
padding: 1rem;
background: linear-gradient(
135deg,
var(--offline-background) 0%,
var(--offline-primary-tint) 50%,
var(--offline-surface) 100%
);
padding: calc(1rem + env(safe-area-inset-top, 0px)) 1rem
calc(1rem + env(safe-area-inset-bottom, 0px));
text-align: center;
}

.container {
max-width: 400px;
padding: 2rem;
background: rgba(255, 255, 255, 0.9);
background: var(--offline-surface);
border-radius: 24px;
box-shadow: 0 8px 32px rgba(139, 92, 246, 0.15);
border: 1px solid rgba(139, 92, 246, 0.1);
box-shadow: 0 8px 32px var(--offline-primary-shadow-soft);
border: 1px solid var(--offline-primary-border-tint);
}

.icon {
width: 80px;
height: 80px;
margin: 0 auto 1.5rem;
background: linear-gradient(135deg, #8b5cf6 0%, #7c3aed 100%);
background: linear-gradient(135deg, var(--offline-primary) 0%, var(--offline-accent) 100%);
border-radius: 20px;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 4px 16px rgba(139, 92, 246, 0.3);
box-shadow: 0 4px 16px var(--offline-primary-shadow-strong);
}

.icon svg {
width: 48px;
height: 48px;
color: white;
color: var(--offline-primary-foreground);
}

h1 {
font-size: 1.5rem;
color: #4c1d95;
color: var(--offline-text);
margin-bottom: 0.75rem;
font-weight: 600;
}

p {
color: #6b7280;
color: var(--offline-text-muted);
line-height: 1.6;
margin-bottom: 1.5rem;
}
Expand All @@ -73,8 +243,8 @@
align-items: center;
gap: 0.5rem;
padding: 0.5rem 1rem;
background: #fef3c7;
color: #92400e;
background: var(--offline-warning-tint);
color: var(--offline-warning);
border-radius: 9999px;
font-size: 0.875rem;
font-weight: 500;
Expand All @@ -85,7 +255,7 @@
content: '';
width: 8px;
height: 8px;
background: #f59e0b;
background: var(--offline-warning);
border-radius: 50%;
animation: pulse 2s infinite;
}
Expand All @@ -105,8 +275,12 @@
align-items: center;
gap: 0.5rem;
padding: 0.75rem 1.5rem;
background: linear-gradient(135deg, #8b5cf6 0%, #7c3aed 100%);
color: white;
background: linear-gradient(
135deg,
var(--offline-primary) 0%,
var(--offline-secondary) 100%
);
color: var(--offline-primary-foreground);
border: none;
border-radius: 12px;
font-size: 1rem;
Expand All @@ -115,12 +289,12 @@
transition:
transform 0.2s,
box-shadow 0.2s;
box-shadow: 0 4px 12px rgba(139, 92, 246, 0.3);
box-shadow: 0 4px 12px var(--offline-primary-shadow-strong);
}

.retry-btn:hover {
transform: translateY(-2px);
box-shadow: 0 6px 16px rgba(139, 92, 246, 0.4);
box-shadow: 0 6px 16px var(--offline-primary-shadow-hover);
}

.retry-btn:active {
Expand All @@ -130,12 +304,12 @@
.tips {
margin-top: 2rem;
padding-top: 1.5rem;
border-top: 1px solid #e5e7eb;
border-top: 1px solid var(--offline-border);
}

.tips h2 {
font-size: 0.875rem;
color: #6b7280;
color: var(--offline-text-muted);
font-weight: 500;
margin-bottom: 0.75rem;
}
Expand All @@ -150,23 +324,23 @@
align-items: flex-start;
gap: 0.5rem;
font-size: 0.875rem;
color: #4b5563;
color: var(--offline-text);
margin-bottom: 0.5rem;
}

.tips li::before {
content: '•';
color: #8b5cf6;
color: var(--offline-primary);
font-weight: bold;
}

.cached-data {
margin-top: 1rem;
padding: 0.75rem;
background: #f0fdf4;
background: var(--offline-success-tint);
border-radius: 8px;
font-size: 0.875rem;
color: #166534;
color: var(--offline-success);
}
</style>
</head>
Expand Down
Loading
Loading