diff --git a/web/public/static/langs/zh_Hans.json b/web/public/static/langs/zh_Hans.json
index 098a72f6..60542465 100644
--- a/web/public/static/langs/zh_Hans.json
+++ b/web/public/static/langs/zh_Hans.json
@@ -352,5 +352,12 @@
     "account_upgrade_dialog_tier_price_billed_monthly": "{{price}} 每年。按月计费。",
     "account_upgrade_dialog_tier_price_billed_yearly": "{{价格}} 按年计费。节省 {{save}}。",
     "account_upgrade_dialog_billing_contact_email": "有关账单问题,请直接<Link>联系我们 </Link>。",
-    "account_upgrade_dialog_billing_contact_website": "有关账单问题,请参考我们的<Link>网站 </Link>。"
+    "account_upgrade_dialog_billing_contact_website": "有关账单问题,请参考我们的<Link>网站 </Link>。",
+    "publish_dialog_call_item": "拨打电话 {{number}}",
+    "publish_dialog_call_label": "拨号",
+    "publish_dialog_chip_call_label": "拨号",
+    "publish_dialog_chip_call_no_verified_numbers_tooltip": "未验证的手机号",
+    "account_basics_phone_numbers_title": "电话号码",
+    "account_basics_phone_numbers_description": "电话通知",
+    "account_basics_phone_numbers_dialog_description": "要使用来电通知功能,您需要添加并验证至少一个电话号码。可以通过短信或电话进行验证。"
 }
diff --git a/web/src/components/ActionBar.jsx b/web/src/components/ActionBar.jsx
index 6a36cdb6..1f41aac0 100644
--- a/web/src/components/ActionBar.jsx
+++ b/web/src/components/ActionBar.jsx
@@ -19,11 +19,14 @@ import Navigation from "./Navigation";
 import accountApi from "../app/AccountApi";
 import PopupMenu from "./PopupMenu";
 import { SubscriptionPopup } from "./SubscriptionPopup";
+import { useIsLaunchedPWA } from "./hooks";
 
 const ActionBar = (props) => {
   const theme = useTheme();
   const { t } = useTranslation();
   const location = useLocation();
+  const isLaunchedPWA = useIsLaunchedPWA();
+
   let title = "ntfy";
   if (props.selected) {
     title = topicDisplayName(props.selected);
@@ -32,6 +35,22 @@ const ActionBar = (props) => {
   } else if (location.pathname === routes.account) {
     title = t("action_bar_account");
   }
+
+  const getActionBarBackground = () => {
+    if (isLaunchedPWA) {
+      return "#317f6f";
+    }
+
+    switch (theme.palette.mode) {
+      case "dark":
+        return "linear-gradient(150deg, #203631 0%, #2a6e60 100%)";
+
+      case "light":
+      default:
+        return "linear-gradient(150deg, #338574 0%, #56bda8 100%)";
+    }
+  };
+
   return (
     <AppBar
       position="fixed"
@@ -44,7 +63,7 @@ const ActionBar = (props) => {
       <Toolbar
         sx={{
           pr: "24px",
-          background: theme.palette.actionBarBackground,
+          background: getActionBarBackground(),
         }}
       >
         <IconButton
diff --git a/web/src/components/App.jsx b/web/src/components/App.jsx
index e174ccc4..2951a2bc 100644
--- a/web/src/components/App.jsx
+++ b/web/src/components/App.jsx
@@ -5,7 +5,7 @@ import { ThemeProvider, createTheme } from "@mui/material/styles";
 import { useLiveQuery } from "dexie-react-hooks";
 import { BrowserRouter, Outlet, Route, Routes, useParams } from "react-router-dom";
 import { AllSubscriptions, SingleSubscription } from "./Notifications";
-import themeOptions, { darkPalette, lightPalette } from "./theme";
+import { darkTheme, lightTheme } from "./theme";
 import Navigation from "./Navigation";
 import ActionBar from "./ActionBar";
 import Preferences from "./Preferences";
@@ -45,13 +45,7 @@ const App = () => {
   const prefersDarkMode = useMediaQuery("(prefers-color-scheme: dark)");
   const themePreference = useLiveQuery(() => prefs.theme());
   const theme = React.useMemo(
-    () =>
-      createTheme({
-        ...themeOptions,
-        palette: {
-          ...(darkModeEnabled(prefersDarkMode, themePreference) ? darkPalette : lightPalette),
-        },
-      }),
+    () => createTheme(darkModeEnabled(prefersDarkMode, themePreference) ? darkTheme : lightTheme),
     [prefersDarkMode, themePreference]
   );
 
diff --git a/web/src/components/Navigation.jsx b/web/src/components/Navigation.jsx
index ad671d99..9025222e 100644
--- a/web/src/components/Navigation.jsx
+++ b/web/src/components/Navigation.jsx
@@ -18,6 +18,7 @@ import {
   Box,
   IconButton,
   Button,
+  useTheme,
 } from "@mui/material";
 import * as React from "react";
 import { useContext, useState } from "react";
@@ -83,6 +84,7 @@ const Navigation = (props) => {
 Navigation.width = navWidth;
 
 const NavList = (props) => {
+  const theme = useTheme();
   const { t } = useTranslation();
   const navigate = useNavigate();
   const location = useLocation();
@@ -126,7 +128,7 @@ const NavList = (props) => {
   return (
     <>
       <Toolbar sx={{ display: { xs: "none", sm: "block" } }} />
-      <List component="nav" sx={{ paddingTop: alertVisible ? "0" : "" }}>
+      <List component="nav" sx={{ paddingTop: { xs: 0, sm: alertVisible ? 0 : "" } }}>
         {showNotificationPermissionRequired && <NotificationPermissionRequired />}
         {showNotificationPermissionDenied && <NotificationPermissionDeniedAlert />}
         {showNotificationBrowserNotSupportedBox && <NotificationBrowserNotSupportedAlert />}
@@ -186,7 +188,11 @@ const NavList = (props) => {
           </ListItemIcon>
           <ListItemText primary={t("nav_button_subscribe")} />
         </ListItemButton>
-        {showUpgradeBanner && <UpgradeBanner />}
+        {showUpgradeBanner && (
+          // The text background gradient didn't seem to do well with switching between light/dark mode,
+          // So adding a `key` forces React to replace the entire component when the theme changes
+          <UpgradeBanner key={`upgrade-banner-${theme.palette.mode}`} mode={theme.palette.mode} />
+        )}
       </List>
       <SubscribeDialog
         key={`subscribeDialog${subscribeDialogKey}`} // Resets dialog when canceled/closed
@@ -199,7 +205,7 @@ const NavList = (props) => {
   );
 };
 
-const UpgradeBanner = () => {
+const UpgradeBanner = ({ mode }) => {
   const { t } = useTranslation();
   const [dialogKey, setDialogKey] = useState(0);
   const [dialogOpen, setDialogOpen] = useState(false);
@@ -216,13 +222,16 @@ const UpgradeBanner = () => {
         width: `${Navigation.width - 1}px`,
         bottom: 0,
         mt: "auto",
-        background: "linear-gradient(150deg, rgba(196, 228, 221, 0.46) 0%, rgb(255, 255, 255) 100%)",
+        background:
+          mode === "light"
+            ? "linear-gradient(150deg, rgba(196, 228, 221, 0.46) 0%, rgb(255, 255, 255) 100%)"
+            : "linear-gradient(150deg, #203631 0%, #2a6e60 100%)",
       }}
     >
       <Divider />
       <ListItemButton onClick={handleClick} sx={{ pt: 2, pb: 2 }}>
         <ListItemIcon>
-          <CelebrationIcon sx={{ color: "#55b86e" }} fontSize="large" />
+          <CelebrationIcon sx={{ color: mode === "light" ? "#55b86e" : "#00ff95" }} fontSize="large" />
         </ListItemIcon>
         <ListItemText
           sx={{ ml: 1 }}
@@ -232,7 +241,10 @@ const UpgradeBanner = () => {
             style: {
               fontWeight: 500,
               fontSize: "1.1rem",
-              background: "-webkit-linear-gradient(45deg, #09009f, #00ff95 80%)",
+              background:
+                mode === "light"
+                  ? "-webkit-linear-gradient(45deg, #09009f, #00ff95 80%)"
+                  : "-webkit-linear-gradient(45deg,rgb(255, 255, 255), #00ff95 80%)",
               WebkitBackgroundClip: "text",
               WebkitTextFillColor: "transparent",
             },
diff --git a/web/src/components/theme.js b/web/src/components/theme.js
index f789a0c5..64217eee 100644
--- a/web/src/components/theme.js
+++ b/web/src/components/theme.js
@@ -1,5 +1,5 @@
 /** @type {import("@mui/material").ThemeOptions} */
-const themeOptions = {
+const baseThemeOptions = {
   components: {
     MuiListItemIcon: {
       styleOverrides: {
@@ -22,37 +22,53 @@ const themeOptions = {
 
 // https://github.com/binwiederhier/ntfy-android/blob/main/app/src/main/res/values/colors.xml
 
-/** @type {import("@mui/material").ThemeOptions['palette']} */
-export const lightPalette = {
-  mode: "light",
-  primary: {
-    main: "#338574",
+/** @type {import("@mui/material").ThemeOptions} */
+export const lightTheme = {
+  ...baseThemeOptions,
+  components: {
+    ...baseThemeOptions.components,
   },
-  secondary: {
-    main: "#6cead0",
+  palette: {
+    mode: "light",
+    primary: {
+      main: "#338574",
+    },
+    secondary: {
+      main: "#6cead0",
+    },
+    error: {
+      main: "#c30000",
+    },
   },
-  error: {
-    main: "#c30000",
-  },
-  actionBarBackground: "linear-gradient(150deg, #338574 0%, #56bda8 100%)",
 };
 
-/** @type {import("@mui/material").ThemeOptions['palette']} */
-export const darkPalette = {
-  mode: "dark",
-  background: {
-    paper: "#1b2124",
+/** @type {import("@mui/material").ThemeOptions} */
+export const darkTheme = {
+  ...baseThemeOptions,
+  components: {
+    ...baseThemeOptions.components,
+    MuiSnackbarContent: {
+      styleOverrides: {
+        root: {
+          color: "#000",
+          backgroundColor: "#aeaeae",
+        },
+      },
+    },
   },
-  primary: {
-    main: "#65b5a3",
+  palette: {
+    mode: "dark",
+    background: {
+      paper: "#1b2124",
+    },
+    primary: {
+      main: "#65b5a3",
+    },
+    secondary: {
+      main: "#6cead0",
+    },
+    error: {
+      main: "#fe4d2e",
+    },
   },
-  secondary: {
-    main: "#6cead0",
-  },
-  error: {
-    main: "#fe4d2e",
-  },
-  actionBarBackground: "linear-gradient(150deg, #203631 0%, #2a6e60 100%)",
 };
-
-export default themeOptions;