Light mode update
This commit is contained in:
@@ -5,6 +5,12 @@
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Rats with Gats</title>
|
||||
<script>
|
||||
// Apply the saved theme before first paint to avoid a flash of the
|
||||
// wrong palette. lib/theme.js owns this key afterwards.
|
||||
document.documentElement.dataset.theme =
|
||||
localStorage.getItem('pirats-theme') === 'dark' ? 'dark' : 'light';
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
|
||||
@@ -76,7 +76,7 @@
|
||||
width: 75px;
|
||||
height: 112px;
|
||||
border-radius: var(--radius-sm);
|
||||
box-shadow: 0 3px 10px rgba(0, 0, 0, 0.4);
|
||||
box-shadow: var(--shadow-card);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
@@ -114,7 +114,7 @@
|
||||
height: 165px;
|
||||
border-radius: var(--radius-md);
|
||||
border-width: 2px;
|
||||
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.5);
|
||||
box-shadow: var(--shadow-card-raised);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
@@ -125,7 +125,7 @@
|
||||
|
||||
.card-large:hover {
|
||||
transform: translateY(-8px) scale(1.05);
|
||||
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.55), 0 0 14px color-mix(in srgb, var(--accent) 30%, transparent);
|
||||
box-shadow: var(--shadow-card-lifted), 0 0 14px color-mix(in srgb, var(--accent) 30%, transparent);
|
||||
}
|
||||
|
||||
.card-large .card-corner {
|
||||
|
||||
@@ -1,16 +1,99 @@
|
||||
/* ============================================================
|
||||
RATS WITH GATS — THEME TOKENS
|
||||
"Lantern & Brine": a lantern-lit pirate ship at night.
|
||||
|
||||
Two themes:
|
||||
- "Fair Winds" (default, light): blue skies, bright seas, and
|
||||
parchment treasure maps with big red Xs. Wind Waker energy.
|
||||
- "Lantern & Brine" (data-theme="dark"): a lantern-lit pirate
|
||||
ship at night.
|
||||
|
||||
This file is the ONLY place colors, fonts, radii, and shadows
|
||||
are defined. Every other stylesheet derives translucent
|
||||
variants with color-mix(), so re-theming the app means
|
||||
editing this file and nothing else.
|
||||
editing this file and nothing else. The active theme is the
|
||||
data-theme attribute on <html>, set from localStorage by an
|
||||
inline script in index.html and toggled via lib/theme.js.
|
||||
(Font files are bundled via @fontsource imports in main.js.)
|
||||
============================================================ */
|
||||
|
||||
:root {
|
||||
/* --- Raw palette ---------------------------------------- */
|
||||
/* --- "Fair Winds" raw palette (light, default) ----------- */
|
||||
--sky: #cde6f5; /* page background: midday sky */
|
||||
--sky-deep: #9fc9e2; /* page edges: deeper blue toward the horizon */
|
||||
--sail: #f8f1dd; /* panel surface: weathered sailcloth */
|
||||
--sail-bright: #fffaec; /* raised surface */
|
||||
|
||||
--map-ink: #33291a; /* primary text: sepia chart ink */
|
||||
--map-faded: #7d6e54; /* muted text */
|
||||
|
||||
--doubloon: #a8770f; /* primary accent: antique gold */
|
||||
--doubloon-bright: #c08a13;
|
||||
--doubloon-dark: #7c560a;
|
||||
|
||||
--lagoon: #0c8d96; /* the Deep: tropical shallows */
|
||||
--palm: #2f8f4e; /* Pi-Rats, success */
|
||||
--x-red: #c92a35; /* danger: the X that marks the spot */
|
||||
--cannonball: #2c3245; /* black suits: dark navy iron */
|
||||
--sunset: #c2640e; /* warnings, jokers */
|
||||
--sea-orchid: #7757c9; /* votes, objectives, misc arcana */
|
||||
--sea-mist: #69808f; /* dead & ghostly things */
|
||||
|
||||
/* Playing-card faces (crisp cards on the sailcloth table) */
|
||||
--card-face: #fffdf2;
|
||||
--card-face-shade: #e7d7ac;
|
||||
--card-ink-red: #b03546;
|
||||
--card-ink-black: #2a3040;
|
||||
|
||||
/* --- Semantic assignments -------------------------------- */
|
||||
--bg: var(--sky);
|
||||
--bg-deep: var(--sky-deep);
|
||||
--surface: var(--sail);
|
||||
--surface-raised: var(--sail-bright);
|
||||
--well: color-mix(in srgb, var(--map-ink) 8%, transparent);
|
||||
|
||||
--text: var(--map-ink);
|
||||
--text-muted: var(--map-faded);
|
||||
--text-inverse: #fff8e6;
|
||||
|
||||
--accent: var(--doubloon);
|
||||
--accent-bright: var(--doubloon-bright);
|
||||
--accent-dark: var(--doubloon-dark);
|
||||
|
||||
--deep: var(--lagoon);
|
||||
--pirat: var(--palm);
|
||||
--success: var(--palm);
|
||||
--danger: var(--x-red);
|
||||
--warning: var(--sunset);
|
||||
--mystic: var(--sea-orchid);
|
||||
--ghost: var(--sea-mist);
|
||||
|
||||
--suit-red: var(--x-red);
|
||||
--suit-black: var(--cannonball);
|
||||
|
||||
/* Borders */
|
||||
--edge: color-mix(in srgb, var(--map-ink) 22%, transparent);
|
||||
--edge-soft: color-mix(in srgb, var(--map-ink) 11%, transparent);
|
||||
--edge-accent: color-mix(in srgb, var(--accent) 40%, transparent);
|
||||
|
||||
/* --- Type ------------------------------------------------ */
|
||||
--font-display: 'Pirata One', 'Alegreya SC', serif;
|
||||
--font-heading: 'Alegreya SC', serif;
|
||||
--font-body: 'Alegreya Sans', sans-serif;
|
||||
|
||||
/* --- Shape & motion -------------------------------------- */
|
||||
--radius-sm: 6px;
|
||||
--radius-md: 10px;
|
||||
--radius-lg: 14px;
|
||||
--shadow-soft: 0 4px 14px rgba(36, 62, 87, 0.18);
|
||||
--shadow-deep: 0 10px 30px rgba(36, 62, 87, 0.28);
|
||||
--shadow-card: 0 3px 10px rgba(36, 62, 87, 0.22);
|
||||
--shadow-card-raised: 0 4px 15px rgba(36, 62, 87, 0.3);
|
||||
--shadow-card-lifted: 0 10px 25px rgba(36, 62, 87, 0.32);
|
||||
--transition-smooth: all 0.25s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
:root[data-theme="dark"] {
|
||||
/* --- "Lantern & Brine" raw palette ----------------------- */
|
||||
--ink-deep: #0a0d13; /* darkest: page edges, sunken wells */
|
||||
--ink: #131822; /* page background */
|
||||
--hull: #1b212e; /* panel surface */
|
||||
@@ -28,14 +111,10 @@
|
||||
--coral: #e8606e; /* danger, red suits */
|
||||
--bone: #dfe6ef; /* black suits */
|
||||
--ember: #f0954c; /* warnings, jokers */
|
||||
--mystic: #a08cd8; /* votes, objectives, misc arcana */
|
||||
--ghost: #8fa3b8; /* dead & ghostly things */
|
||||
|
||||
/* Playing-card faces (light parchment cards on a dark table) */
|
||||
--card-face: #f2e9d2;
|
||||
--card-face-shade: #ddcfa9;
|
||||
--card-ink-red: #b03546;
|
||||
--card-ink-black: #2a3040;
|
||||
|
||||
/* --- Semantic assignments -------------------------------- */
|
||||
--bg: var(--ink);
|
||||
@@ -57,6 +136,8 @@
|
||||
--success: var(--kelp);
|
||||
--danger: var(--coral);
|
||||
--warning: var(--ember);
|
||||
--mystic: #a08cd8;
|
||||
--ghost: #8fa3b8;
|
||||
|
||||
--suit-red: var(--coral);
|
||||
--suit-black: var(--bone);
|
||||
@@ -66,16 +147,10 @@
|
||||
--edge-soft: color-mix(in srgb, var(--parchment) 8%, transparent);
|
||||
--edge-accent: color-mix(in srgb, var(--accent) 35%, transparent);
|
||||
|
||||
/* --- Type ------------------------------------------------ */
|
||||
--font-display: 'Pirata One', 'Alegreya SC', serif;
|
||||
--font-heading: 'Alegreya SC', serif;
|
||||
--font-body: 'Alegreya Sans', sans-serif;
|
||||
|
||||
/* --- Shape & motion -------------------------------------- */
|
||||
--radius-sm: 6px;
|
||||
--radius-md: 10px;
|
||||
--radius-lg: 14px;
|
||||
/* --- Shadows --------------------------------------------- */
|
||||
--shadow-soft: 0 4px 14px rgba(0, 0, 0, 0.35);
|
||||
--shadow-deep: 0 10px 30px rgba(0, 0, 0, 0.5);
|
||||
--transition-smooth: all 0.25s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
--shadow-card: 0 3px 10px rgba(0, 0, 0, 0.4);
|
||||
--shadow-card-raised: 0 4px 15px rgba(0, 0, 0, 0.5);
|
||||
--shadow-card-lifted: 0 10px 25px rgba(0, 0, 0, 0.55);
|
||||
}
|
||||
|
||||
@@ -1,10 +1,16 @@
|
||||
<script>
|
||||
import { extraMenuLinks } from '../lib/menu';
|
||||
import { theme, toggleTheme } from '../lib/theme';
|
||||
|
||||
let open = false;
|
||||
|
||||
const baseLinks = [
|
||||
$: themeLink = $theme === 'dark'
|
||||
? { label: '☀️ Light Mode', action: toggleTheme, keepOpen: true, title: 'Switch to the light theme' }
|
||||
: { label: '🌙 Dark Mode', action: toggleTheme, keepOpen: true, title: 'Switch to the dark theme' };
|
||||
|
||||
$: baseLinks = [
|
||||
{ label: '📖 Rules', href: '/rules', external: true, title: 'Open the rulebook in a new tab' },
|
||||
themeLink,
|
||||
];
|
||||
|
||||
$: links = [...$extraMenuLinks, ...baseLinks];
|
||||
@@ -29,7 +35,7 @@
|
||||
<button
|
||||
class="menu-item"
|
||||
title={link.title}
|
||||
on:click={() => { open = false; link.action(); }}
|
||||
on:click={() => { if (!link.keepOpen) open = false; link.action(); }}
|
||||
>{link.label}</button>
|
||||
{:else}
|
||||
<a
|
||||
|
||||
23
frontend/src/lib/theme.js
Normal file
23
frontend/src/lib/theme.js
Normal file
@@ -0,0 +1,23 @@
|
||||
import { writable } from 'svelte/store';
|
||||
|
||||
// Light/dark theme preference, shared across games via localStorage (the
|
||||
// rulebook page reads the same key). index.html applies the saved theme
|
||||
// before first paint; this store keeps the <html> attribute and storage in
|
||||
// sync when the user toggles from the corner menu.
|
||||
const STORAGE_KEY = 'pirats-theme';
|
||||
|
||||
function savedTheme() {
|
||||
const value = localStorage.getItem(STORAGE_KEY);
|
||||
return value === 'dark' ? 'dark' : 'light';
|
||||
}
|
||||
|
||||
export const theme = writable(savedTheme());
|
||||
|
||||
theme.subscribe((value) => {
|
||||
document.documentElement.dataset.theme = value;
|
||||
localStorage.setItem(STORAGE_KEY, value);
|
||||
});
|
||||
|
||||
export function toggleTheme() {
|
||||
theme.update((value) => (value === 'dark' ? 'light' : 'dark'));
|
||||
}
|
||||
Reference in New Issue
Block a user