feat(pwa): add offline tip & optimize toast style
diff --git a/.scripts/pwa/i18n.json b/.scripts/pwa/i18n.json
index 3938ce6..a184811 100644
--- a/.scripts/pwa/i18n.json
+++ b/.scripts/pwa/i18n.json
@@ -2,11 +2,13 @@
     "zh": {
         "Reload": "刷新",
         "Close": "关闭",
-        "NewContent": "有更新可用,点击”刷新“按钮获取最新内容"
+        "NewContent": "有新内容更新,点击”刷新“按钮以获取最新内容",
+        "Offline": "📴 您当前处于离线模式"
     },
     "en": {
         "Reload": "Reload",
         "Close": "Close",
-        "NewContent": "New content available, click on reload button to update"
+        "NewContent": "New content available, click on reload button to update.",
+        "Offline": "📴 You are in OFFLINE mode."
     }
 }
\ No newline at end of file
diff --git a/.scripts/pwa/main.js b/.scripts/pwa/main.js
index 634cff5..d9e2596 100644
--- a/.scripts/pwa/main.js
+++ b/.scripts/pwa/main.js
@@ -1,7 +1,6 @@
 import { registerSW } from 'virtual:pwa-register'
 import i18n from './i18n.json'
-// TODO minify
-import styleCSS from './style.css?raw'
+import styleCSS from './style.css?inline'
 
 window.addEventListener('load', () => {
     const lang = i18n[window.EC_WWW_LANG || 'en']
@@ -13,27 +12,40 @@
     const pwaToast = document.createElement('div')
     pwaToast.className = 'pwa-toast'
     pwaToast.setAttribute('role', 'alert')
-    pwaToast.innerHTML = `<div class="pwa-msg">${lang['NewContent']}</div><button id="pwa-close">${lang['Close']}</button><button id="pwa-refresh">${lang['Reload']}</button>`
+    pwaToast.innerHTML = `<div class="pwa-msg"></div><button class="pwa-close">${lang['Close']}</button><button class="pwa-refresh">${lang['Reload']}</button>`
     document.body.appendChild(pwaToast)
 
-    const pwaCloseBtn = pwaToast.querySelector('#pwa-close')
-    const pwaRefreshBtn = pwaToast.querySelector('#pwa-refresh')
+    const pwaMsg = pwaToast.querySelector('.pwa-msg')
+    const pwaCloseBtn = pwaToast.querySelector('.pwa-close')
+    const pwaRefreshBtn = pwaToast.querySelector('.pwa-refresh')
 
     let refreshSW
+    let needRefresh
 
-    const refreshCallback = () => refreshSW && refreshSW(true)
+    const refreshCallback = () => {
+        refreshSW && refreshSW(true)
+        needRefresh = false
+    }
 
     const hideToast = () => {
         pwaToast.classList.remove('show')
     }
 
-    const showToast = () => {
+    const showToast = (msg, isRefresh) => {
+        pwaMsg.innerText = msg
+        pwaToast.classList[isRefresh ? 'add' : 'remove']('is-refresh')
         pwaToast.classList.add('show')
     }
 
     pwaRefreshBtn.addEventListener('click', refreshCallback)
     pwaCloseBtn.addEventListener('click', hideToast)
 
+    const onOffline = () => needRefresh || showToast(lang['Offline'])
+    navigator.onLine || onOffline()
+
+    window.addEventListener('online', () => needRefresh || hideToast())
+    window.addEventListener('offline', onOffline)
+
     refreshSW = registerSW({
         immediate: true,
         onOfflineReady() {
@@ -41,7 +53,8 @@
         },
         onNeedRefresh() {
             console.log('New content available')
-            showToast()
+            needRefresh = true
+            showToast(lang['NewContent'], true)
         },
         onRegisterError(e) {
             console.error('failed to register service worker', e)
diff --git a/.scripts/pwa/style.css b/.scripts/pwa/style.css
index f919388..7ca53c0 100644
--- a/.scripts/pwa/style.css
+++ b/.scripts/pwa/style.css
@@ -2,29 +2,59 @@
     visibility: hidden;
     opacity: 0;
     position: fixed;
-    right: 0;
-    bottom: 0;
-    margin: 16px;
+    right: 16px;
+    bottom: 16px;
     padding: 12px;
-    border: 1px solid #8885;
+    max-width: 240px;
+    color: #fff;
+    background-color: rgba(0, 0, 0, .6);
+    border: 1px solid #dcdfe6;
     border-radius: 4px;
-    z-index: 99999;
+    -webkit-box-shadow: 0 5px 10px 0 rgba(0, 0, 0, .2);
+    box-shadow: 0 5px 10px 0 rgba(0, 0, 0, .2);
+    font-size: 14px;
+    box-sizing: border-box;
+    z-index: 9999;
     text-align: left;
-    box-shadow: 3px 4px 5px 0 #8885;
-    background-color: #fff;
+    -webkit-transition: opacity ease .5s, visibility ease .5s;
     transition: opacity ease .5s, visibility ease .5s;
 }
-.pwa-toast .pwa-msg {
-    margin-bottom: 8px;
-}
 .pwa-toast button {
     float: right;
-    border: 1px solid #8885;
-    outline: none;
-    margin-right: 5px;
+    border: 1px solid #F72C5B;
+    margin-left: 5px;
+    margin-top: 8px;
     border-radius: 2px;
-    padding: 3px 10px;
-    background-color: #F5F7FA;
+    padding: 2px 8px;
+    background-color: #F72C5B;
+    font-weight: 500;
+    font-size: 86%;
+    vertical-align: middle;
+    -webkit-appearance: none;
+    -moz-user-select: none;
+    -webkit-user-select: none;
+    user-select: none;
+    cursor: pointer;
+}
+.pwa-toast button,
+.pwa-toast button:active,
+.pwa-toast button:hover {
+    color: #fff;
+    outline: none;
+}
+.pwa-toast button:hover {
+    border-color: #f96b8c;
+    background-color: #f96b8c;
+}
+.pwa-toast button:active {
+    border-color: #ca274d;
+    background-color: #ca274d;
+}
+.pwa-toast .pwa-refresh {
+    display: none;
+}
+.pwa-toast.is-refresh .pwa-refresh {
+    display: block;
 }
 .pwa-toast.show {
     visibility: visible;