mirror of
https://github.com/dsec-hub/dsec-notebook.git
synced 2026-09-22 07:24:27 +00:00
added theme toggle
This commit is contained in:
parent
f807ae9500
commit
be5f9e7536
6 changed files with 150 additions and 14 deletions
|
|
@ -78,7 +78,7 @@ define(['./workbox-7e5eb42b'], (function (workbox) { 'use strict';
|
|||
*/
|
||||
workbox.precacheAndRoute([{
|
||||
"url": "/",
|
||||
"revision": "0.4n2fvpih6f8"
|
||||
"revision": "0.5d3f7lf01og"
|
||||
}], {});
|
||||
workbox.cleanupOutdatedCaches();
|
||||
workbox.registerRoute(new workbox.NavigationRoute(workbox.createHandlerBoundToURL("/"), {
|
||||
|
|
|
|||
13
src/app.html
13
src/app.html
|
|
@ -12,6 +12,19 @@
|
|||
<meta name="apple-mobile-web-app-title" content="Notebook" />
|
||||
<meta name="format-detection" content="telephone=no" />
|
||||
<meta name="theme-color" content="#ffffff" />
|
||||
<script>
|
||||
(function () {
|
||||
var stored = localStorage.getItem("dsec_theme");
|
||||
var dark =
|
||||
stored === "dark" ||
|
||||
(!stored && window.matchMedia("(prefers-color-scheme: dark)").matches);
|
||||
var root = document.documentElement;
|
||||
root.classList.toggle("dark", dark);
|
||||
root.style.colorScheme = dark ? "dark" : "light";
|
||||
var meta = document.querySelector('meta[name="theme-color"]');
|
||||
if (meta) meta.setAttribute("content", dark ? "#131014" : "#ffffff");
|
||||
})();
|
||||
</script>
|
||||
<link rel="manifest" href="/manifest.webmanifest" />
|
||||
<link rel="icon" href="/favicon.ico" sizes="any" />
|
||||
<link rel="apple-touch-icon" href="/apple-touch-icon-180x180.png" />
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
<script lang="ts">
|
||||
import { isAuthenticated, initAuth, logout, currentUser } from "$lib/stores/auth";
|
||||
import { theme, toggleTheme } from "$lib/stores/theme";
|
||||
import { onMount } from "svelte";
|
||||
import { goto } from "$app/navigation";
|
||||
import { page } from "$app/state";
|
||||
|
|
@ -8,6 +9,7 @@
|
|||
let auth = $state(false);
|
||||
let admin = $state(false);
|
||||
let searchQuery = $state("");
|
||||
let isDark = $state(false);
|
||||
|
||||
function submitSearch(e: SubmitEvent) {
|
||||
e.preventDefault();
|
||||
|
|
@ -22,9 +24,11 @@
|
|||
initAuth();
|
||||
const unsubAuth = isAuthenticated.subscribe((v) => (auth = v));
|
||||
const unsubUser = currentUser.subscribe((u) => (admin = u?.role === "admin"));
|
||||
const unsubTheme = theme.subscribe((t) => (isDark = t === "dark"));
|
||||
return () => {
|
||||
unsubAuth();
|
||||
unsubUser();
|
||||
unsubTheme();
|
||||
};
|
||||
});
|
||||
|
||||
|
|
@ -34,7 +38,7 @@
|
|||
const onAdmin = $derived(path === "/admin" || path.startsWith("/admin/"));
|
||||
</script>
|
||||
|
||||
<header class="border-rule border-b bg-white">
|
||||
<header class="border-rule bg-surface border-b">
|
||||
<div class="mx-auto flex h-16 max-w-4xl items-center justify-between px-4 sm:px-6">
|
||||
<a href="/" class="text-ink font-serif text-[1.65rem] leading-none">Notebook</a>
|
||||
|
||||
|
|
@ -60,13 +64,50 @@
|
|||
{:else}
|
||||
<a href="/auth/login" class="nav-link">Sign in</a>
|
||||
{/if}
|
||||
<button
|
||||
type="button"
|
||||
class="text-muted hover:text-ink p-1"
|
||||
aria-label={isDark ? "Switch to light mode" : "Switch to dark mode"}
|
||||
onclick={toggleTheme}
|
||||
>
|
||||
{#if isDark}
|
||||
<svg
|
||||
class="h-5 w-5"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.5"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<circle cx="12" cy="12" r="4" />
|
||||
<path
|
||||
stroke-linecap="square"
|
||||
d="M12 2v2M12 20v2M4.93 4.93l1.41 1.41M17.66 17.66l1.41 1.41M2 12h2M20 12h2M4.93 19.07l1.41-1.41M17.66 6.34l1.41-1.41"
|
||||
/>
|
||||
</svg>
|
||||
{:else}
|
||||
<svg
|
||||
class="h-5 w-5"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.5"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="square"
|
||||
d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"
|
||||
/>
|
||||
</svg>
|
||||
{/if}
|
||||
</button>
|
||||
<form onsubmit={submitSearch}>
|
||||
<input
|
||||
type="search"
|
||||
bind:value={searchQuery}
|
||||
placeholder="Search"
|
||||
aria-label="Search notes and questions"
|
||||
class="border-rule text-ink placeholder:text-faint focus:border-primary w-40 rounded-none border bg-white px-2.5 py-1.5 text-[11px] tracking-wide transition-colors outline-none"
|
||||
class="border-rule text-ink placeholder:text-faint focus:border-primary bg-surface w-40 rounded-none border px-2.5 py-1.5 text-[11px] tracking-wide transition-colors outline-none"
|
||||
/>
|
||||
</form>
|
||||
</nav>
|
||||
|
|
@ -96,7 +137,7 @@
|
|||
bind:value={searchQuery}
|
||||
placeholder="Search"
|
||||
aria-label="Search notes and questions"
|
||||
class="border-rule text-ink placeholder:text-faint focus:border-primary w-full rounded-none border bg-white px-3 py-2 text-sm tracking-wide transition-colors outline-none"
|
||||
class="border-rule text-ink placeholder:text-faint focus:border-primary bg-surface w-full rounded-none border px-3 py-2 text-sm tracking-wide transition-colors outline-none"
|
||||
/>
|
||||
</form>
|
||||
<a href="/notes" class="nav-link block" onclick={() => (mobileMenuOpen = false)}
|
||||
|
|
@ -129,6 +170,43 @@
|
|||
onclick={() => (mobileMenuOpen = false)}>Sign in</a
|
||||
>
|
||||
{/if}
|
||||
<button
|
||||
type="button"
|
||||
class="text-muted hover:text-ink flex items-center gap-2 p-1"
|
||||
aria-label={isDark ? "Switch to light mode" : "Switch to dark mode"}
|
||||
onclick={toggleTheme}
|
||||
>
|
||||
{#if isDark}
|
||||
<svg
|
||||
class="h-5 w-5"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.5"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<circle cx="12" cy="12" r="4" />
|
||||
<path
|
||||
stroke-linecap="square"
|
||||
d="M12 2v2M12 20v2M4.93 4.93l1.41 1.41M17.66 17.66l1.41 1.41M2 12h2M20 12h2M4.93 19.07l1.41-1.41M17.66 6.34l1.41-1.41"
|
||||
/>
|
||||
</svg>
|
||||
{:else}
|
||||
<svg
|
||||
class="h-5 w-5"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.5"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="square"
|
||||
d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"
|
||||
/>
|
||||
</svg>
|
||||
{/if}
|
||||
</button>
|
||||
</nav>
|
||||
{/if}
|
||||
</header>
|
||||
|
|
|
|||
29
src/lib/stores/theme.ts
Normal file
29
src/lib/stores/theme.ts
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
import { get, writable } from "svelte/store";
|
||||
|
||||
export type Theme = "light" | "dark";
|
||||
|
||||
const STORAGE_KEY = "dsec_theme";
|
||||
|
||||
function getInitialTheme(): Theme {
|
||||
if (typeof window === "undefined") return "light";
|
||||
const stored = localStorage.getItem(STORAGE_KEY);
|
||||
if (stored === "light" || stored === "dark") return stored;
|
||||
return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
|
||||
}
|
||||
|
||||
export const theme = writable<Theme>(getInitialTheme());
|
||||
|
||||
function applyTheme(value: Theme) {
|
||||
const root = document.documentElement;
|
||||
root.classList.toggle("dark", value === "dark");
|
||||
root.style.colorScheme = value;
|
||||
const meta = document.querySelector('meta[name="theme-color"]');
|
||||
if (meta) meta.setAttribute("content", value === "dark" ? "#131014" : "#ffffff");
|
||||
}
|
||||
|
||||
export function toggleTheme() {
|
||||
const next: Theme = get(theme) === "dark" ? "light" : "dark";
|
||||
localStorage.setItem(STORAGE_KEY, next);
|
||||
theme.set(next);
|
||||
applyTheme(next);
|
||||
}
|
||||
|
|
@ -26,14 +26,14 @@
|
|||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
</svelte:head>
|
||||
|
||||
<div class="flex min-h-screen flex-col bg-white">
|
||||
<div class="bg-surface flex min-h-screen flex-col">
|
||||
<Navbar />
|
||||
<main class="flex-1">
|
||||
{@render children()}
|
||||
</main>
|
||||
<footer class="border-rule mt-16 border-t">
|
||||
<div class="mx-auto max-w-4xl px-4 py-8 sm:px-6">
|
||||
<p class="kicker">Notebook — written by students, for students.</p>
|
||||
<p class="kicker">DSEC Notebook</p>
|
||||
<p class="text-faint mt-2 text-xs">
|
||||
DSEC Notebook is a community resource for Deakin University students. Not affiliated
|
||||
with Deakin University.
|
||||
|
|
|
|||
|
|
@ -11,17 +11,33 @@
|
|||
--color-muted: #737373;
|
||||
--color-faint: #a3a3a3;
|
||||
--color-rule: #e5e5e5;
|
||||
--color-surface: #ffffff;
|
||||
--color-on-primary: #ffffff;
|
||||
}
|
||||
|
||||
.dark {
|
||||
--color-primary: #ea4c65;
|
||||
--color-primary-dark: #ea4c65;
|
||||
--color-secondary: #7fdfea;
|
||||
--color-secondary-dark: #5fd4e2;
|
||||
--color-ink: #f5f5f5;
|
||||
--color-muted: #b3b3b3;
|
||||
--color-faint: #7d7d7d;
|
||||
--color-rule: #2a2a2a;
|
||||
--color-surface: #131014;
|
||||
--color-on-primary: #1a1a1a;
|
||||
}
|
||||
|
||||
html {
|
||||
font-family: var(--font-sans);
|
||||
color: var(--color-ink);
|
||||
background: #fff;
|
||||
background: var(--color-surface);
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
|
||||
a, button {
|
||||
cursor: pointer;
|
||||
a,
|
||||
button {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.page {
|
||||
|
|
@ -33,11 +49,11 @@ a, button {
|
|||
}
|
||||
|
||||
.btn-primary {
|
||||
@apply border-primary bg-primary hover:bg-primary-dark hover:border-primary-dark inline-flex items-center justify-center rounded-none border px-5 py-2.5 text-[11px] font-medium tracking-[0.14em] text-white uppercase transition-colors disabled:cursor-not-allowed disabled:opacity-50;
|
||||
@apply border-primary bg-primary hover:brightness-110 inline-flex items-center justify-center rounded-none border px-5 py-2.5 text-[11px] font-medium tracking-[0.14em] text-white uppercase transition-colors disabled:cursor-not-allowed disabled:opacity-50;
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
@apply border-secondary text-secondary hover:bg-secondary inline-flex items-center justify-center rounded-none border bg-transparent px-5 py-2.5 text-[11px] font-medium tracking-[0.14em] uppercase transition-colors hover:text-white disabled:cursor-not-allowed disabled:opacity-50;
|
||||
@apply border-secondary text-secondary hover:bg-secondary hover:text-on-primary inline-flex items-center justify-center rounded-none border bg-transparent px-5 py-2.5 text-[11px] font-medium tracking-[0.14em] uppercase transition-colors disabled:cursor-not-allowed disabled:opacity-50;
|
||||
}
|
||||
|
||||
.btn-ghost {
|
||||
|
|
@ -45,15 +61,15 @@ a, button {
|
|||
}
|
||||
|
||||
.field {
|
||||
@apply border-rule text-ink placeholder:text-faint focus:border-primary block w-full rounded-none border bg-white px-3 py-2.5 text-sm transition-colors outline-none;
|
||||
@apply border-rule text-ink placeholder:text-faint focus:border-primary bg-surface block w-full rounded-none border px-3 py-2.5 text-sm transition-colors outline-none;
|
||||
}
|
||||
|
||||
.chip {
|
||||
@apply border-rule text-ink hover:border-ink inline-flex items-center rounded-none border bg-white px-3 py-1.5 font-sans text-[11px] font-medium tracking-[0.12em] uppercase transition-colors;
|
||||
@apply border-rule text-ink hover:border-ink bg-surface inline-flex items-center rounded-none border px-3 py-1.5 font-sans text-[11px] font-medium tracking-[0.12em] uppercase transition-colors;
|
||||
}
|
||||
|
||||
.chip-active {
|
||||
@apply border-primary bg-primary hover:border-primary-dark hover:bg-primary-dark text-white;
|
||||
@apply border-primary bg-primary hover:border-primary hover:bg-primary text-white;
|
||||
}
|
||||
|
||||
.nav-link {
|
||||
|
|
|
|||
Loading…
Reference in a new issue