feat(site): nav, footer, theme toggle, language picker

This commit is contained in:
Christian Visintin
2026-06-07 21:44:19 +02:00
parent 6fa3c042b1
commit d670aed079
4 changed files with 102 additions and 0 deletions

View File

@@ -0,0 +1,17 @@
---
import { useTranslations, type Locale } from "../i18n/ui";
interface Props { locale: Locale; }
const { locale } = Astro.props;
const t = useTranslations(locale);
const year = new Date().getFullYear();
---
<footer class="mt-auto border-t border-line bg-mantle">
<div class="mx-auto flex max-w-5xl flex-col items-center gap-2 px-4 py-8 text-sm text-overlay">
<div class="flex gap-5">
<a href="https://github.com/veeso/termscp" class="hover:text-text">GitHub</a>
<a href="https://crates.io/crates/termscp" class="hover:text-text">crates.io</a>
<a href="https://ko-fi.com/veeso" class="hover:text-text">{t("footer.support")}</a>
</div>
<p>© {year} Christian Visintin · {t("footer.rights")}</p>
</div>
</footer>

View File

@@ -0,0 +1,25 @@
---
import { locales, defaultLocale, type Locale } from "../i18n/ui";
interface Props {
locale: Locale;
path: string; // unprefixed path, e.g. "/install" or "/"
}
const { locale, path } = Astro.props;
const labels: Record<Locale, string> = {
en: "EN",
"zh-CN": "中文",
it: "IT",
fr: "FR",
es: "ES",
};
const href = (l: Locale) => (l === defaultLocale ? path : `/${l}${path}`);
---
<div class="flex items-center gap-2 text-sm">
{locales.map((l) => (
<a
href={href(l)}
class={l === locale ? "text-green" : "text-overlay hover:text-text"}
>{labels[l]}</a>
))}
</div>

View File

@@ -0,0 +1,28 @@
---
import { defaultLocale, useTranslations, type Locale } from "../i18n/ui";
import ThemeToggle from "./ThemeToggle.astro";
import LangPicker from "./LangPicker.astro";
interface Props {
locale: Locale;
path: string;
}
const { locale, path } = Astro.props;
const t = useTranslations(locale);
const p = (sub: string) => (locale === defaultLocale ? sub : `/${locale}${sub}`);
---
<header class="sticky top-0 z-50 border-b border-line bg-mantle/90 backdrop-blur">
<nav class="mx-auto flex max-w-5xl items-center justify-between px-4 py-3">
<a href={p("/")} class="flex items-center gap-2 text-text">
<img src="/assets/images/termscp.webp" alt="termscp" class="h-7 w-7" />
<span class="font-bold">termscp</span>
</a>
<div class="flex items-center gap-5 text-sm">
<a href={p("/install")} class="text-overlay hover:text-text">{t("nav.install")}</a>
<a href={p("/user-manual")} class="text-overlay hover:text-text">{t("nav.manual")}</a>
<a href="https://github.com/veeso/termscp" class="text-overlay hover:text-text">{t("nav.github")}</a>
<LangPicker locale={locale} path={path} />
<ThemeToggle />
</div>
</nav>
</header>

View File

@@ -0,0 +1,32 @@
---
// Tiny client island: flips data-theme + persists. Contract: writes "frappe"|"latte".
---
<button
id="theme-toggle"
type="button"
aria-label="Toggle theme"
class="rounded p-2 text-text hover:bg-mantle"
>
<span data-icon="frappe">☾</span>
<span data-icon="latte" hidden>☀</span>
</button>
<script is:inline>
(() => {
const btn = document.getElementById("theme-toggle");
const sync = () => {
const t = document.documentElement.getAttribute("data-theme");
btn.querySelector('[data-icon="frappe"]').hidden = t !== "frappe";
btn.querySelector('[data-icon="latte"]').hidden = t !== "latte";
};
btn.addEventListener("click", () => {
const next =
document.documentElement.getAttribute("data-theme") === "frappe"
? "latte"
: "frappe";
document.documentElement.setAttribute("data-theme", next);
localStorage.setItem("theme", next);
sync();
});
sync();
})();
</script>