Declutter feed header; add theme + accent options
- Feed header: the game pill is gone (the nav Games button opens the sheet); the active game is now a quiet text label under the title - Settings > Theme: add Black (OLED) and Sepia alongside System/Light/Dark - Settings > Accent colour: six swatches (red default, blue, green, amber, violet, rose) layered over any theme via [data-accent]; persisted - applyTheme() takes the settings object; system theme now keys off the absence of [data-theme] so explicit light/sepia don't fight the media query Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
parent
fa6df9d60b
commit
61ceb6abf3
@ -13,23 +13,36 @@ import { createStore } from './createStore.js';
|
||||
export const settings = createStore('pdx.settings', {
|
||||
versionGroup: 'scarlet-violet',
|
||||
pokedex: null,
|
||||
theme: 'system',
|
||||
theme: 'system', // system | light | dark | black | sepia
|
||||
accent: 'red', // red | blue | green | amber | violet | rose
|
||||
spriteStyle: 'official',
|
||||
showShiny: false,
|
||||
locale: 'en',
|
||||
});
|
||||
|
||||
export function applyTheme(theme = settings.get().theme) {
|
||||
const DARKISH = new Set(['dark', 'black']);
|
||||
|
||||
export function applyTheme(state = settings.get()) {
|
||||
const root = document.documentElement;
|
||||
if (theme === 'system') root.removeAttribute('data-theme');
|
||||
const { theme, accent } = state;
|
||||
|
||||
if (!theme || theme === 'system') root.removeAttribute('data-theme');
|
||||
else root.setAttribute('data-theme', theme);
|
||||
|
||||
if (!accent || accent === 'red') root.removeAttribute('data-accent');
|
||||
else root.setAttribute('data-accent', accent);
|
||||
|
||||
const meta = document.querySelector('meta[name="theme-color"]');
|
||||
if (meta) {
|
||||
const dark =
|
||||
theme === 'dark' ||
|
||||
(theme === 'system' &&
|
||||
DARKISH.has(theme) ||
|
||||
((!theme || theme === 'system') &&
|
||||
window.matchMedia('(prefers-color-scheme: dark)').matches);
|
||||
meta.setAttribute('content', dark ? '#0b0b0c' : '#b3161a');
|
||||
meta.setAttribute(
|
||||
'content',
|
||||
dark
|
||||
? '#0b0b0c'
|
||||
: getComputedStyle(root).getPropertyValue('--accent').trim() || '#b3161a',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -219,29 +219,13 @@
|
||||
line-height: 1.1;
|
||||
text-transform: capitalize;
|
||||
}
|
||||
.game-pill {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
margin-top: 8px;
|
||||
padding: 5px 10px 5px 12px;
|
||||
border: none;
|
||||
border-radius: 999px;
|
||||
background: var(--surface-2);
|
||||
color: var(--text-dim);
|
||||
font: inherit;
|
||||
.feed-head__game {
|
||||
display: block;
|
||||
margin-top: 4px;
|
||||
font-size: 0.82rem;
|
||||
font-weight: 600;
|
||||
text-decoration: none;
|
||||
color: var(--text-dim);
|
||||
text-transform: capitalize;
|
||||
cursor: pointer;
|
||||
}
|
||||
.game-pill:hover {
|
||||
color: var(--text);
|
||||
}
|
||||
.game-pill__chev {
|
||||
font-size: 0.9rem;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.ring {
|
||||
@ -1379,6 +1363,28 @@
|
||||
color: var(--text);
|
||||
font: inherit;
|
||||
}
|
||||
.swatches {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.swatch {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
border-radius: 50%;
|
||||
border: 2px solid var(--border);
|
||||
background: var(--sw);
|
||||
cursor: pointer;
|
||||
padding: 0;
|
||||
transition: transform 0.12s var(--ease-spring);
|
||||
}
|
||||
.swatch:hover {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
.swatch.is-on {
|
||||
border-color: var(--text);
|
||||
box-shadow: 0 0 0 2px var(--bg), 0 0 0 4px var(--sw);
|
||||
}
|
||||
|
||||
/* ---------- Toast ------------------------------------------- */
|
||||
.toast {
|
||||
|
||||
@ -47,8 +47,9 @@
|
||||
--type-fairy: #ec8fe6;
|
||||
}
|
||||
|
||||
/* System default: follow the OS only when no explicit theme is chosen. */
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root:not([data-theme="light"]) {
|
||||
:root:not([data-theme]) {
|
||||
--bg: #0b0b0c;
|
||||
--surface: #17171a;
|
||||
--surface-2: #202024;
|
||||
@ -73,6 +74,39 @@
|
||||
--shadow: 0 1px 2px rgba(0, 0, 0, 0.4), 0 8px 24px rgba(0, 0, 0, 0.35);
|
||||
}
|
||||
|
||||
/* Pure black — for OLED screens. */
|
||||
:root[data-theme="black"] {
|
||||
--bg: #000000;
|
||||
--surface: #0b0b0c;
|
||||
--surface-2: #17171a;
|
||||
--text: #f4f4f6;
|
||||
--text-dim: #9a9aa4;
|
||||
--border: #222226;
|
||||
--accent: #ff5a5f;
|
||||
--accent-text: #16161a;
|
||||
--shadow: 0 1px 2px rgba(0, 0, 0, 0.6), 0 10px 30px rgba(0, 0, 0, 0.55);
|
||||
--shadow-lg: 0 20px 50px -12px rgba(0, 0, 0, 0.65);
|
||||
}
|
||||
|
||||
/* Sepia — a warm, low-glare light theme. */
|
||||
:root[data-theme="sepia"] {
|
||||
--bg: #f2e8d5;
|
||||
--surface: #fbf4e4;
|
||||
--surface-2: #e9dbbe;
|
||||
--text: #3b3022;
|
||||
--text-dim: #7c6c52;
|
||||
--border: #dccbaa;
|
||||
--accent: #a8410f;
|
||||
--accent-text: #ffffff;
|
||||
}
|
||||
|
||||
/* Accent overrides — layered on top of any theme. Red is the default. */
|
||||
:root[data-accent="blue"] { --accent: #2f6fed; --accent-text: #ffffff; }
|
||||
:root[data-accent="green"] { --accent: #1f9d57; --accent-text: #ffffff; }
|
||||
:root[data-accent="amber"] { --accent: #d98a00; --accent-text: #1a1200; }
|
||||
:root[data-accent="violet"] { --accent: #7b52e0; --accent-text: #ffffff; }
|
||||
:root[data-accent="rose"] { --accent: #e0417a; --accent-text: #ffffff; }
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
@ -6,7 +6,6 @@ import { ui } from '../store/ui.js';
|
||||
import { selection, stats } from '../store/selection.js';
|
||||
import { Card } from '../components/Card.js';
|
||||
import { ProgressRing } from '../components/ProgressRing.js';
|
||||
import { openGameSheet } from '../components/GameSheet.js';
|
||||
|
||||
const FILTERS = [
|
||||
{ key: 'all', label: 'All', test: () => true },
|
||||
@ -82,12 +81,9 @@ export async function DexGrid() {
|
||||
const ring = ProgressRing();
|
||||
|
||||
const title = el('h1', {});
|
||||
const gamePill = el(
|
||||
'button',
|
||||
{ class: 'game-pill', type: 'button', onclick: () => openGameSheet() },
|
||||
el('span', {}),
|
||||
el('span', { class: 'game-pill__chev', 'aria-hidden': 'true' }, '⌄'),
|
||||
);
|
||||
// Which game is active — a quiet label, not a control. Change it from the
|
||||
// Games button in the nav.
|
||||
const gameLabel = el('span', { class: 'feed-head__game' });
|
||||
|
||||
const search = el('input', {
|
||||
class: 'field-search__input',
|
||||
@ -159,7 +155,7 @@ export async function DexGrid() {
|
||||
el(
|
||||
'div',
|
||||
{ class: 'feed-head__top' },
|
||||
el('div', { class: 'feed-head__id' }, title, gamePill),
|
||||
el('div', { class: 'feed-head__id' }, title, gameLabel),
|
||||
ring.node,
|
||||
),
|
||||
el(
|
||||
@ -260,7 +256,7 @@ export async function DexGrid() {
|
||||
ids = rows.map((r) => r.species.id);
|
||||
|
||||
title.textContent = prettify(dex.name || dex.key);
|
||||
gamePill.firstChild.textContent =
|
||||
gameLabel.textContent =
|
||||
st.versionGroup === 'all' ? 'All games' : prettify(st.versionGroup);
|
||||
refreshMeta();
|
||||
paintGrid();
|
||||
|
||||
@ -10,11 +10,43 @@ export async function SettingsView() {
|
||||
['system', 'System'],
|
||||
['light', 'Light'],
|
||||
['dark', 'Dark'],
|
||||
['black', 'Black (OLED)'],
|
||||
['sepia', 'Sepia'],
|
||||
], (v) => {
|
||||
settings.set({ theme: v });
|
||||
applyTheme(v);
|
||||
applyTheme();
|
||||
});
|
||||
|
||||
const ACCENTS = [
|
||||
['red', '#b3161a'],
|
||||
['blue', '#2f6fed'],
|
||||
['green', '#1f9d57'],
|
||||
['amber', '#d98a00'],
|
||||
['violet', '#7b52e0'],
|
||||
['rose', '#e0417a'],
|
||||
];
|
||||
const accentRow = el(
|
||||
'div',
|
||||
{ class: 'swatches' },
|
||||
...ACCENTS.map(([key, hex]) =>
|
||||
el('button', {
|
||||
class: `swatch${st.accent === key ? ' is-on' : ''}`,
|
||||
type: 'button',
|
||||
style: `--sw:${hex}`,
|
||||
title: key,
|
||||
'aria-label': `${key} accent`,
|
||||
onclick: () => {
|
||||
settings.set({ accent: key });
|
||||
applyTheme();
|
||||
[...accentRow.children].forEach((b, i) =>
|
||||
b.classList.toggle('is-on', ACCENTS[i][0] === key),
|
||||
);
|
||||
},
|
||||
}),
|
||||
),
|
||||
);
|
||||
const accentField = el('div', { class: 'field' }, el('span', {}, 'Accent colour'), accentRow);
|
||||
|
||||
const spriteField = selectField('Sprite style', st.spriteStyle, [
|
||||
['default', 'Pixel (modern)'],
|
||||
['game', 'Game era (pixel art from the selected game)'],
|
||||
@ -61,7 +93,7 @@ export async function SettingsView() {
|
||||
|
||||
view.append(
|
||||
el('header', { class: 'view__header' }, el('h1', {}, 'Settings')),
|
||||
el('div', { class: 'settings__group' }, themeField, spriteField, shinyField),
|
||||
el('div', { class: 'settings__group' }, themeField, accentField, spriteField, shinyField),
|
||||
|
||||
el('h2', {}, 'Your data'),
|
||||
storageNote,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user