diff --git a/frontend/src/lib/types.ts b/frontend/src/lib/types.ts index d0b3576..2581bfe 100644 --- a/frontend/src/lib/types.ts +++ b/frontend/src/lib/types.ts @@ -1,4 +1,4 @@ -export type Theme = 'light' | 'dark' | 'system'; +export type Theme = 'light' | 'dark' | 'system' | 'neon'; /** Lifecycle of the NIP-46 remote signer. */ export type SignerPhase = 'stopped' | 'connecting' | 'connected'; diff --git a/frontend/src/screens/SettingsScreen.tsx b/frontend/src/screens/SettingsScreen.tsx index 95c4578..2ea58e1 100644 --- a/frontend/src/screens/SettingsScreen.tsx +++ b/frontend/src/screens/SettingsScreen.tsx @@ -105,8 +105,12 @@ export function SettingsScreen() { + -

“System” follows your desktop's light or dark preference.

+

+ “System” follows your desktop's light or dark preference. “Neon” is a pure-black + theme with pink and purple accents. +

diff --git a/frontend/src/state/AppProvider.tsx b/frontend/src/state/AppProvider.tsx index 0b5541d..6167031 100644 --- a/frontend/src/state/AppProvider.tsx +++ b/frontend/src/state/AppProvider.tsx @@ -337,6 +337,10 @@ export function useApp(): AppContextValue { /** Apply the requested theme (respecting system preference for `system`). */ export function applyTheme(theme: Theme): void { + if (theme === 'neon') { + document.documentElement.dataset.theme = 'neon'; + return; + } const prefersDark = theme === 'dark' || (theme === 'system' && window.matchMedia('(prefers-color-scheme: dark)').matches); diff --git a/frontend/src/styles.css b/frontend/src/styles.css index 06e610c..61bdf7d 100644 --- a/frontend/src/styles.css +++ b/frontend/src/styles.css @@ -58,6 +58,35 @@ --shadow-modal: 0 12px 40px rgba(0, 0, 0, 0.6); } +/* "Neon" — pure-black Matrix/Cyberpunk palette inspired by the CAJAFUERTE + Obsidian theme (MIT): black panels, deep-pink primary (#ff1493), purple + secondary (#8b00ff family), yellow highlights (#ffd700). */ +:root[data-theme='neon'] { + --bg: #000000; + --surface: #0b0b0e; + --surface-2: #131318; + --surface-hover: #191921; + --border: #2b1224; + --border-strong: #4d1c3d; + --text: #f5f2f7; + --text-muted: #a89bb0; + --primary: #ff1493; + --primary-hover: #ff4fb0; + --primary-soft: #2b0a1e; + --on-primary: #ffffff; + --danger: #ff3355; + --danger-soft: #330810; + --warning: #ffd700; + --warning-soft: #332b03; + --success: #00e68a; + --success-soft: #04301f; + --info: #b388ff; + --info-soft: #1c1035; + --focus: #ff1493; + --shadow: 0 1px 2px rgba(0, 0, 0, 0.6), 0 8px 24px rgba(255, 20, 147, 0.07); + --shadow-modal: 0 12px 40px rgba(0, 0, 0, 0.85), 0 0 32px rgba(255, 20, 147, 0.12); +} + * { box-sizing: border-box; } @@ -312,7 +341,8 @@ a { /* The artwork is black-on-white; flip it in dark themes so it stays black bird on dark tile instead of a glaring white square. */ -html[data-theme='dark'] .sidebar-logo img { +html[data-theme='dark'] .sidebar-logo img, +html[data-theme='neon'] .sidebar-logo img { filter: invert(1); } diff --git a/frontend/src/test/SettingsScreen.test.tsx b/frontend/src/test/SettingsScreen.test.tsx index e88e83d..68ecbf2 100644 --- a/frontend/src/test/SettingsScreen.test.tsx +++ b/frontend/src/test/SettingsScreen.test.tsx @@ -29,6 +29,19 @@ describe('SettingsScreen', () => { expect(document.documentElement.dataset.theme).toBe('dark'); }); + it('applies the neon theme to the document', async () => { + const backend = createFakeBackend(); + installFakeBackend(backend); + renderWithApp(); + + const themeSelect = await screen.findByLabelText('Theme'); + fireEvent.change(themeSelect, { target: { value: 'neon' } }); + await waitFor(() => { + expect(backend.state.settings.theme).toBe('neon'); + }); + expect(document.documentElement.dataset.theme).toBe('neon'); + }); + it('toggles publish confirmation and npub shortening', async () => { const backend = createFakeBackend(); installFakeBackend(backend); diff --git a/src/main.rs b/src/main.rs index 8c74756..6f630a7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -35,7 +35,7 @@ Commands: relays disable Disable a relay relays test Test a relay connection settings get Show application settings - settings set theme + settings set theme settings set confirm settings set shorten delete-profile Delete a profile (moves it to undo stack) @@ -442,7 +442,7 @@ fn cli_settings(args: &[String]) -> Result { match key.as_str() { "theme" => { let theme = Theme::parse(value).ok_or_else(|| { - AppError::config("Theme must be one of: light, dark, system") + AppError::config("Theme must be one of: light, dark, system, neon") })?; app.settings.theme = theme; } diff --git a/src/settings.rs b/src/settings.rs index 8cddc71..55ee740 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -23,6 +23,7 @@ pub enum Theme { Light, Dark, System, + Neon, } impl Theme { @@ -32,6 +33,7 @@ impl Theme { "light" => Some(Self::Light), "dark" => Some(Self::Dark), "system" => Some(Self::System), + "neon" => Some(Self::Neon), _ => None, } }