mirror of
https://github.com/jcreek/LivingDexTracker.git
synced 2026-07-13 11:03:44 +00:00
Merge pull request #80 from jcreek/48-add-dark-mode-toggle
48 add dark mode toggle
This commit is contained in:
+14
-1
@@ -1,9 +1,22 @@
|
|||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html lang="en" data-theme="pokeball">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
|
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<script>
|
||||||
|
(function () {
|
||||||
|
try {
|
||||||
|
const storedTheme = localStorage.getItem('theme');
|
||||||
|
const allowed = ['pokeball', 'dark'];
|
||||||
|
const theme = storedTheme && allowed.includes(storedTheme) ? storedTheme : 'pokeball';
|
||||||
|
document.documentElement.setAttribute('data-theme', theme);
|
||||||
|
} catch (e) {
|
||||||
|
// Fallback to default theme if localStorage is not accessible
|
||||||
|
document.documentElement.setAttribute('data-theme', 'pokeball');
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
%sveltekit.head%
|
%sveltekit.head%
|
||||||
<link rel="stylesheet" href="%sveltekit.assets%/output.css" />
|
<link rel="stylesheet" href="%sveltekit.assets%/output.css" />
|
||||||
</head>
|
</head>
|
||||||
|
|||||||
@@ -0,0 +1,59 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { theme, applyTheme } from '$lib/stores/theme.js';
|
||||||
|
import { onMount } from 'svelte';
|
||||||
|
|
||||||
|
let currentTheme: 'pokeball' | 'dark' = 'pokeball';
|
||||||
|
|
||||||
|
// Subscribe to theme changes
|
||||||
|
$: (currentTheme = $theme), applyTheme($theme);
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
// Initialize theme from localStorage
|
||||||
|
theme.init();
|
||||||
|
});
|
||||||
|
|
||||||
|
function toggleTheme() {
|
||||||
|
theme.toggle();
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<button
|
||||||
|
class="btn btn-ghost btn-circle"
|
||||||
|
on:click={toggleTheme}
|
||||||
|
aria-label="Toggle dark mode"
|
||||||
|
title="Toggle dark mode"
|
||||||
|
>
|
||||||
|
{#if currentTheme === 'pokeball'}
|
||||||
|
<!-- Sun icon for light mode (clicking will switch to dark) -->
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
class="h-5 w-5"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
stroke="currentColor"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="2"
|
||||||
|
d="M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
{:else}
|
||||||
|
<!-- Moon icon for dark mode (clicking will switch to light) -->
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
class="h-5 w-5"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
stroke="currentColor"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="2"
|
||||||
|
d="M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
{/if}
|
||||||
|
</button>
|
||||||
@@ -59,7 +59,7 @@
|
|||||||
>
|
>
|
||||||
<div class="dex-column pokedex-entry-container">
|
<div class="dex-column pokedex-entry-container">
|
||||||
<div class="flex mb-2">
|
<div class="flex mb-2">
|
||||||
<div class="sprite-container flex justify-center items-center bg-white rounded-lg p-2">
|
<div class="sprite-container flex justify-center items-center bg-base-100 rounded-lg p-2">
|
||||||
<PokemonSprite
|
<PokemonSprite
|
||||||
pokemonName={pokedexEntry.pokemon}
|
pokemonName={pokedexEntry.pokemon}
|
||||||
pokedexNumber={pokedexEntry.pokedexNumber}
|
pokedexNumber={pokedexEntry.pokedexNumber}
|
||||||
@@ -68,18 +68,20 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="pl-2">
|
<div class="pl-2">
|
||||||
<h3 class="text-xl font-bold pt-1 text-secondary">{pokedexEntry.pokemon}</h3>
|
<h3 class="text-xl font-bold pt-1">{pokedexEntry.pokemon}</h3>
|
||||||
<sub class="text-gray-200">#{pokedexEntry.pokedexNumber.toString().padStart(3, '0')}</sub>
|
<sub class="text-primary-content/80"
|
||||||
|
>#{pokedexEntry.pokedexNumber.toString().padStart(3, '0')}</sub
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{#if showForms}
|
{#if showForms}
|
||||||
<div class="bg-white text-black rounded-lg p-4 mb-2">
|
<div class="bg-base-100 text-base-content rounded-lg p-4 mb-2">
|
||||||
<p><strong>Form:</strong> {pokedexEntry.form ? pokedexEntry.form : '-'}</p>
|
<p><strong>Form:</strong> {pokedexEntry.form ? pokedexEntry.form : '-'}</p>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<div class="bg-white text-black rounded-lg p-4">
|
<div class="bg-base-100 text-base-content rounded-lg p-4">
|
||||||
{#if pokedexEntry.evolutionInformation}
|
{#if pokedexEntry.evolutionInformation}
|
||||||
<p><strong>How to evolve: </strong>{pokedexEntry.evolutionInformation}</p>
|
<p><strong>How to evolve: </strong>{pokedexEntry.evolutionInformation}</p>
|
||||||
{:else}
|
{:else}
|
||||||
@@ -95,7 +97,9 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{#if catchRecord}
|
{#if catchRecord}
|
||||||
<div class="dex-column catch-record-container bg-white text-black rounded-lg p-4 mb-4 md:mb-0">
|
<div
|
||||||
|
class="dex-column catch-record-container bg-base-100 text-base-content rounded-lg p-4 mb-4 md:mb-0"
|
||||||
|
>
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<div class="form-control">
|
<div class="form-control">
|
||||||
<label class="cursor-pointer label">
|
<label class="cursor-pointer label">
|
||||||
@@ -103,7 +107,7 @@
|
|||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
bind:checked={catchRecord.caught}
|
bind:checked={catchRecord.caught}
|
||||||
class="checkbox checkbox-primary border-black"
|
class="checkbox checkbox-primary"
|
||||||
on:change={onCaughtChange}
|
on:change={onCaughtChange}
|
||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
@@ -116,7 +120,7 @@
|
|||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
bind:checked={catchRecord.haveToEvolve}
|
bind:checked={catchRecord.haveToEvolve}
|
||||||
class="checkbox checkbox-primary border-black"
|
class="checkbox checkbox-primary"
|
||||||
on:change={onNeedsToEvolveChange}
|
on:change={onNeedsToEvolveChange}
|
||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
@@ -126,30 +130,30 @@
|
|||||||
<div class="form-control">
|
<div class="form-control">
|
||||||
<label class="cursor-pointer label">
|
<label class="cursor-pointer label">
|
||||||
<span class="block font-bold mr-2">In Home:</span>
|
<span class="block font-bold mr-2">In Home:</span>
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
bind:checked={catchRecord.inHome}
|
|
||||||
class="checkbox checkbox-primary border-black"
|
|
||||||
on:change={() => updateCatchRecord('toggle')}
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{#if pokedexEntry.canGigantamax && showForms}
|
|
||||||
<div class="flex items-center">
|
|
||||||
<div class="form-control">
|
|
||||||
<label class="cursor-pointer label">
|
|
||||||
<span class="block font-bold mr-2">Has Gigantamaxed:</span>
|
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
bind:checked={catchRecord.hasGigantamaxed}
|
bind:checked={catchRecord.inHome}
|
||||||
class="checkbox checkbox-primary border-black"
|
class="checkbox checkbox-primary"
|
||||||
on:change={() => updateCatchRecord('toggle')}
|
on:change={() => updateCatchRecord('toggle')}
|
||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{#if pokedexEntry.canGigantamax && showForms}
|
||||||
|
<div class="flex items-center">
|
||||||
|
<div class="form-control">
|
||||||
|
<label class="cursor-pointer label">
|
||||||
|
<span class="block font-bold mr-2">Has Gigantamaxed:</span>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
bind:checked={catchRecord.hasGigantamaxed}
|
||||||
|
class="checkbox checkbox-primary"
|
||||||
|
on:change={() => updateCatchRecord('toggle')}
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
<p>
|
<p>
|
||||||
<label
|
<label
|
||||||
class="block font-bold mb-1"
|
class="block font-bold mb-1"
|
||||||
@@ -158,7 +162,7 @@
|
|||||||
<textarea
|
<textarea
|
||||||
bind:value={catchRecord.personalNotes}
|
bind:value={catchRecord.personalNotes}
|
||||||
id={`personalNotesInput-${catchRecord._id || pokedexEntry._id}`}
|
id={`personalNotesInput-${catchRecord._id || pokedexEntry._id}`}
|
||||||
class="form-textarea w-full p-2 border rounded"
|
class="textarea textarea-bordered w-full"
|
||||||
style="min-height: 120px;"
|
style="min-height: 120px;"
|
||||||
on:input={() => updateCatchRecord('notes')}
|
on:input={() => updateCatchRecord('notes')}
|
||||||
on:change={() => updateCatchRecord('notes-blur')}
|
on:change={() => updateCatchRecord('notes-blur')}
|
||||||
@@ -169,7 +173,7 @@
|
|||||||
|
|
||||||
<div class="dex-column additional-details-container">
|
<div class="dex-column additional-details-container">
|
||||||
{#if showOrigins}
|
{#if showOrigins}
|
||||||
<div class="bg-white text-black rounded-lg p-4 mb-2">
|
<div class="bg-base-100 text-base-content rounded-lg p-4 mb-2">
|
||||||
<h3 class="text-xl font-semibold mb-4">Origin Dex Requirements</h3>
|
<h3 class="text-xl font-semibold mb-4">Origin Dex Requirements</h3>
|
||||||
<p><strong>Region to Catch In:</strong> {pokedexEntry.regionToCatchIn}</p>
|
<p><strong>Region to Catch In:</strong> {pokedexEntry.regionToCatchIn}</p>
|
||||||
<p><strong>Games to catch in:</strong></p>
|
<p><strong>Games to catch in:</strong></p>
|
||||||
@@ -184,7 +188,7 @@
|
|||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
{#if pokedexEntry.catchInformation.length > 0}
|
{#if pokedexEntry.catchInformation.length > 0}
|
||||||
<div class="bg-white text-black rounded-lg p-4 mb-2">
|
<div class="bg-base-100 text-base-content rounded-lg p-4 mb-2">
|
||||||
<p><strong>Where to catch: </strong></p>
|
<p><strong>Where to catch: </strong></p>
|
||||||
<ul class="list-disc list-inside">
|
<ul class="list-disc list-inside">
|
||||||
{#each pokedexEntry.catchInformation as info}
|
{#each pokedexEntry.catchInformation as info}
|
||||||
@@ -206,7 +210,7 @@
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
<div class="bg-white text-black rounded-lg p-4 mb-2">
|
<div class="bg-base-100 text-base-content rounded-lg p-4 mb-2">
|
||||||
<p>
|
<p>
|
||||||
<strong>Where to catch: </strong>Currently missing - can you
|
<strong>Where to catch: </strong>Currently missing - can you
|
||||||
<a
|
<a
|
||||||
@@ -229,27 +233,4 @@
|
|||||||
width: 68px;
|
width: 68px;
|
||||||
height: 68px;
|
height: 68px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-checkbox {
|
|
||||||
appearance: none;
|
|
||||||
background-color: #fff;
|
|
||||||
border: 2px solid #000;
|
|
||||||
border-radius: 0.25rem;
|
|
||||||
width: 1.5rem;
|
|
||||||
height: 1.5rem;
|
|
||||||
display: inline-block;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-checkbox:checked {
|
|
||||||
background-color: #00bfff;
|
|
||||||
background-size: 100% 100%;
|
|
||||||
background-position: center center;
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-textarea {
|
|
||||||
min-height: 3rem;
|
|
||||||
background-color: #fff;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -25,7 +25,7 @@
|
|||||||
|
|
||||||
{#if isOpen}
|
{#if isOpen}
|
||||||
<div class="modal modal-open" role="dialog" aria-modal="true">
|
<div class="modal modal-open" role="dialog" aria-modal="true">
|
||||||
<div class="modal-box-custom">
|
<div class="modal-box-custom bg-primary text-primary-content">
|
||||||
<button class="close-button" on:click={onClose} aria-label="Close"> ✕ </button>
|
<button class="close-button" on:click={onClose} aria-label="Close"> ✕ </button>
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<slot />
|
<slot />
|
||||||
@@ -52,7 +52,6 @@
|
|||||||
max-width: 72rem;
|
max-width: 72rem;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-height: 90vh;
|
max-height: 90vh;
|
||||||
background-color: rgba(220, 38, 38, 0.95);
|
|
||||||
border: none;
|
border: none;
|
||||||
box-shadow: 0 25px 50px -12px rgb(0 0 0 / 0.5);
|
box-shadow: 0 25px 50px -12px rgb(0 0 0 / 0.5);
|
||||||
outline: none;
|
outline: none;
|
||||||
|
|||||||
@@ -175,9 +175,9 @@
|
|||||||
} else {
|
} else {
|
||||||
const placement = calculateBoxPlacement(index);
|
const placement = calculateBoxPlacement(index);
|
||||||
if (placement.column % 2 === 0) {
|
if (placement.column % 2 === 0) {
|
||||||
return 'background-color: #ffffff';
|
return 'background-color: var(--ld-box-bg-even);';
|
||||||
} else {
|
} else {
|
||||||
return 'background-color: #f9f9f9;';
|
return 'background-color: var(--ld-box-bg-odd);';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -533,7 +533,6 @@
|
|||||||
form={pokedexEntry.form}
|
form={pokedexEntry.form}
|
||||||
shiny={showShiny}
|
shiny={showShiny}
|
||||||
/>
|
/>
|
||||||
<span class="md:hidden">ⓘ</span>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div slot="tooltip">
|
<div slot="tooltip">
|
||||||
@@ -596,6 +595,21 @@
|
|||||||
</main>
|
</main>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
/*
|
||||||
|
Theme-aware backgrounds for non-caught box slots.
|
||||||
|
- Light mode (`pokeball`) keeps the original exact colors.
|
||||||
|
- Dark mode maps to DaisyUI theme base tokens so it stays consistent with the active theme.
|
||||||
|
*/
|
||||||
|
:global(:root) {
|
||||||
|
--ld-box-bg-even: var(--fallback-b1, oklch(var(--b1) / 1));
|
||||||
|
--ld-box-bg-odd: var(--fallback-b3, oklch(var(--b3) / 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
:global([data-theme='pokeball']) {
|
||||||
|
--ld-box-bg-even: #ffffff;
|
||||||
|
--ld-box-bg-odd: #f9f9f9;
|
||||||
|
}
|
||||||
|
|
||||||
.boxes-grid {
|
.boxes-grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(1, minmax(0, 1fr));
|
grid-template-columns: repeat(1, minmax(0, 1fr));
|
||||||
@@ -612,6 +626,13 @@
|
|||||||
border: 1px solid #ddd;
|
border: 1px solid #ddd;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
|
/*
|
||||||
|
On small screens the grid columns get narrow; without an explicit ratio,
|
||||||
|
the button height becomes content-driven (padding + sprite) and you end up
|
||||||
|
with rectangular cells. Force each slot to be square.
|
||||||
|
*/
|
||||||
|
aspect-ratio: 1 / 1;
|
||||||
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.pokemon-box--filtered-out {
|
.pokemon-box--filtered-out {
|
||||||
@@ -638,15 +659,53 @@
|
|||||||
.pokemon-box :global(img) {
|
.pokemon-box :global(img) {
|
||||||
width: var(--sprite-size, 64px);
|
width: var(--sprite-size, 64px);
|
||||||
height: var(--sprite-size, 64px);
|
height: var(--sprite-size, 64px);
|
||||||
|
max-width: 100%;
|
||||||
|
max-height: 100%;
|
||||||
|
display: block;
|
||||||
object-fit: contain;
|
object-fit: contain;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sprite-placeholder {
|
.sprite-placeholder {
|
||||||
width: var(--sprite-size, 64px);
|
width: var(--sprite-size, 64px);
|
||||||
height: var(--sprite-size, 64px);
|
height: var(--sprite-size, 64px);
|
||||||
|
max-width: 100%;
|
||||||
|
max-height: 100%;
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Mobile: keep 6 columns but prevent sprite/padding from forcing tall cells */
|
||||||
|
@media (max-width: 767px) {
|
||||||
|
.pokemon-box-inner {
|
||||||
|
/* cap padding so the sprite can fit inside small squares */
|
||||||
|
padding: min(var(--cell-padding, 1rem), 0.35rem);
|
||||||
|
}
|
||||||
|
|
||||||
|
.pokemon-box :global(img) {
|
||||||
|
/* allow the sprite to shrink with the square cell */
|
||||||
|
width: min(var(--sprite-size, 64px), 100%);
|
||||||
|
height: min(var(--sprite-size, 64px), 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sprite-placeholder {
|
||||||
|
width: min(var(--sprite-size, 64px), 100%);
|
||||||
|
height: min(var(--sprite-size, 64px), 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-badge {
|
||||||
|
width: 0.95rem;
|
||||||
|
height: 0.95rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-badge-top {
|
||||||
|
top: 0.35rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-icon {
|
||||||
|
width: 0.85rem;
|
||||||
|
height: 0.85rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.status-badge {
|
.status-badge {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|||||||
@@ -0,0 +1,76 @@
|
|||||||
|
import { writable } from 'svelte/store';
|
||||||
|
|
||||||
|
type Theme = 'pokeball' | 'dark';
|
||||||
|
|
||||||
|
const STORAGE_KEY = 'theme';
|
||||||
|
|
||||||
|
function isTheme(type: string): type is Theme {
|
||||||
|
return type === 'pokeball' || type === 'dark';
|
||||||
|
}
|
||||||
|
|
||||||
|
function createThemeStore() {
|
||||||
|
// Get stored theme from localStorage or default to 'pokeball'
|
||||||
|
let initialTheme: Theme = 'pokeball';
|
||||||
|
if (typeof window !== 'undefined') {
|
||||||
|
try {
|
||||||
|
const storedTheme = localStorage.getItem(STORAGE_KEY);
|
||||||
|
if (storedTheme && isTheme(storedTheme)) {
|
||||||
|
initialTheme = storedTheme;
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// ignore (privacy mode / disabled storage)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const { subscribe, set: writableSet, update } = writable<Theme>(initialTheme);
|
||||||
|
|
||||||
|
return {
|
||||||
|
subscribe,
|
||||||
|
set: (value: Theme) => {
|
||||||
|
if (isTheme(value)) {
|
||||||
|
writableSet(value);
|
||||||
|
if (typeof window !== 'undefined') {
|
||||||
|
try {
|
||||||
|
localStorage.setItem(STORAGE_KEY, value);
|
||||||
|
} catch {
|
||||||
|
// ignore (privacy mode / disabled storage)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
toggle: () => {
|
||||||
|
update((theme) => {
|
||||||
|
const newTheme = theme === 'pokeball' ? 'dark' : 'pokeball';
|
||||||
|
if (typeof window !== 'undefined') {
|
||||||
|
try {
|
||||||
|
localStorage.setItem(STORAGE_KEY, newTheme);
|
||||||
|
} catch {
|
||||||
|
// ignore (privacy mode / disabled storage)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return newTheme;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
init: () => {
|
||||||
|
if (typeof window !== 'undefined') {
|
||||||
|
try {
|
||||||
|
const stored = localStorage.getItem(STORAGE_KEY);
|
||||||
|
if (stored && isTheme(stored)) {
|
||||||
|
writableSet(stored);
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// ignore (privacy mode / disabled storage)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export const theme = createThemeStore();
|
||||||
|
|
||||||
|
// Helper function to apply theme to HTML element
|
||||||
|
export function applyTheme(themeValue: Theme) {
|
||||||
|
if (typeof document !== 'undefined') {
|
||||||
|
document.documentElement.setAttribute('data-theme', themeValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,6 +5,7 @@
|
|||||||
import { type User } from '@supabase/auth-js';
|
import { type User } from '@supabase/auth-js';
|
||||||
import SignIn from '$lib/components/SignIn.svelte';
|
import SignIn from '$lib/components/SignIn.svelte';
|
||||||
import SignOut from '$lib/components/SignOut.svelte';
|
import SignOut from '$lib/components/SignOut.svelte';
|
||||||
|
import ThemeToggle from '$lib/components/ThemeToggle.svelte';
|
||||||
|
|
||||||
import { pwaInfo } from 'virtual:pwa-info';
|
import { pwaInfo } from 'virtual:pwa-info';
|
||||||
import { pwaAssetsHead } from 'virtual:pwa-assets/head';
|
import { pwaAssetsHead } from 'virtual:pwa-assets/head';
|
||||||
@@ -28,7 +29,6 @@
|
|||||||
const {
|
const {
|
||||||
data: { subscription }
|
data: { subscription }
|
||||||
} = supabase.auth.onAuthStateChange((event, session) => {
|
} = supabase.auth.onAuthStateChange((event, session) => {
|
||||||
console.log('[Layout] Auth state changed:', event, session ? 'Session found' : 'No session');
|
|
||||||
if (session) {
|
if (session) {
|
||||||
localUser = session.user;
|
localUser = session.user;
|
||||||
} else {
|
} else {
|
||||||
@@ -137,6 +137,7 @@
|
|||||||
<a class="btn btn-ghost text-xl" href="/">Living Dex Tracker</a>
|
<a class="btn btn-ghost text-xl" href="/">Living Dex Tracker</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="navbar-end">
|
<div class="navbar-end">
|
||||||
|
<ThemeToggle />
|
||||||
<div class="dropdown dropdown-end">
|
<div class="dropdown dropdown-end">
|
||||||
{#if localUser}
|
{#if localUser}
|
||||||
<div tabindex="0" role="button" class="btn btn-ghost btn-circle avatar">
|
<div tabindex="0" role="button" class="btn btn-ghost btn-circle avatar">
|
||||||
|
|||||||
Reference in New Issue
Block a user