feat(ui): add theme-aware SVG favicon that follows light/dark mode
- Create light (orange-700) and dark (orange-600) coffee cup SVG favicons - Add explicit routes for both SVG variants - Set correct favicon on initial load before body renders - Swap favicon href on manual theme toggle - Listen for OS prefers-color-scheme changes when no manual override set
This commit is contained in:
parent
44174b4361
commit
3e0c2ae771
5 changed files with 46 additions and 1 deletions
|
|
@ -33,6 +33,8 @@ pub(super) fn router() -> axum::Router<AppState> {
|
||||||
get(searchable_select_js),
|
get(searchable_select_js),
|
||||||
)
|
)
|
||||||
.route("/favicon.ico", get(favicon))
|
.route("/favicon.ico", get(favicon))
|
||||||
|
.route("/favicon-light.svg", get(favicon_light))
|
||||||
|
.route("/favicon-dark.svg", get(favicon_dark))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn scan_redirect() -> Redirect {
|
async fn scan_redirect() -> Redirect {
|
||||||
|
|
@ -73,3 +75,17 @@ async fn favicon() -> impl IntoResponse {
|
||||||
include_bytes!("../../../../static/favicon.ico").as_ref(),
|
include_bytes!("../../../../static/favicon.ico").as_ref(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn favicon_light() -> impl IntoResponse {
|
||||||
|
(
|
||||||
|
[("content-type", "image/svg+xml")],
|
||||||
|
include_str!("../../../../static/favicon-light.svg"),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn favicon_dark() -> impl IntoResponse {
|
||||||
|
(
|
||||||
|
[("content-type", "image/svg+xml")],
|
||||||
|
include_str!("../../../../static/favicon-dark.svg"),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
|
||||||
5
static/favicon-dark.svg
Normal file
5
static/favicon-dark.svg
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
|
||||||
|
<path fill="#ea580c" d="M4 12h18v13a4 4 0 0 1-4 4H8a4 4 0 0 1-4-4V12Z"/>
|
||||||
|
<path fill="none" stroke="#ea580c" stroke-linecap="round" stroke-width="2.5" d="M22 16c3.5 0 5.5 2.5 5.5 4.5S25.5 25 22 25"/>
|
||||||
|
<path fill="none" stroke="#ea580c" stroke-linecap="round" stroke-width="2" d="M10 9V4m6 5V4"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 391 B |
5
static/favicon-light.svg
Normal file
5
static/favicon-light.svg
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
|
||||||
|
<path fill="#c2410c" d="M4 12h18v13a4 4 0 0 1-4 4H8a4 4 0 0 1-4-4V12Z"/>
|
||||||
|
<path fill="none" stroke="#c2410c" stroke-linecap="round" stroke-width="2.5" d="M22 16c3.5 0 5.5 2.5 5.5 4.5S25.5 25 22 25"/>
|
||||||
|
<path fill="none" stroke="#c2410c" stroke-linecap="round" stroke-width="2" d="M10 9V4m6 5V4"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 391 B |
|
|
@ -5,11 +5,14 @@
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
<title>{% block title %}Brewlog{% endblock %}</title>
|
<title>{% block title %}Brewlog{% endblock %}</title>
|
||||||
<link rel="stylesheet" href="/styles.css" />
|
<link rel="stylesheet" href="/styles.css" />
|
||||||
|
<link rel="icon" id="favicon" type="image/svg+xml" href="/favicon-light.svg" />
|
||||||
<script>
|
<script>
|
||||||
(() => {
|
(() => {
|
||||||
const stored = localStorage.getItem("theme");
|
const stored = localStorage.getItem("theme");
|
||||||
if (stored === "dark" || (!stored && matchMedia("(prefers-color-scheme: dark)").matches)) {
|
const isDark = stored === "dark" || (!stored && matchMedia("(prefers-color-scheme: dark)").matches);
|
||||||
|
if (isDark) {
|
||||||
document.documentElement.setAttribute("data-theme", "dark");
|
document.documentElement.setAttribute("data-theme", "dark");
|
||||||
|
document.getElementById("favicon").href = "/favicon-dark.svg";
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -66,8 +66,24 @@
|
||||||
document.querySelectorAll(".theme-icon-dark").forEach((el) => {
|
document.querySelectorAll(".theme-icon-dark").forEach((el) => {
|
||||||
el.classList.toggle("hidden", !isDark);
|
el.classList.toggle("hidden", !isDark);
|
||||||
});
|
});
|
||||||
|
const favicon = document.getElementById("favicon");
|
||||||
|
if (favicon) {
|
||||||
|
favicon.href = isDark ? "/favicon-dark.svg" : "/favicon-light.svg";
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Set correct icon state on load
|
// Set correct icon state on load
|
||||||
updateThemeIcons();
|
updateThemeIcons();
|
||||||
|
|
||||||
|
// Follow OS theme changes when the user hasn't manually chosen
|
||||||
|
matchMedia("(prefers-color-scheme: dark)").addEventListener("change", (e) => {
|
||||||
|
if (localStorage.getItem("theme")) return;
|
||||||
|
const html = document.documentElement;
|
||||||
|
if (e.matches) {
|
||||||
|
html.setAttribute("data-theme", "dark");
|
||||||
|
} else {
|
||||||
|
html.removeAttribute("data-theme");
|
||||||
|
}
|
||||||
|
updateThemeIcons();
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue