Neon theme: pure-black Matrix palette (CAJAFUERTE-inspired) alongside light/dark/system
This commit is contained in:
parent
ef6ea171f8
commit
74072b7d6d
7 changed files with 58 additions and 5 deletions
|
|
@ -1,4 +1,4 @@
|
||||||
export type Theme = 'light' | 'dark' | 'system';
|
export type Theme = 'light' | 'dark' | 'system' | 'neon';
|
||||||
|
|
||||||
/** Lifecycle of the NIP-46 remote signer. */
|
/** Lifecycle of the NIP-46 remote signer. */
|
||||||
export type SignerPhase = 'stopped' | 'connecting' | 'connected';
|
export type SignerPhase = 'stopped' | 'connecting' | 'connected';
|
||||||
|
|
|
||||||
|
|
@ -105,8 +105,12 @@ export function SettingsScreen() {
|
||||||
<option value="light">Light</option>
|
<option value="light">Light</option>
|
||||||
<option value="dark">Dark</option>
|
<option value="dark">Dark</option>
|
||||||
<option value="system">System</option>
|
<option value="system">System</option>
|
||||||
|
<option value="neon">Neon</option>
|
||||||
</select>
|
</select>
|
||||||
<p className="hint">“System” follows your desktop's light or dark preference.</p>
|
<p className="hint">
|
||||||
|
“System” follows your desktop's light or dark preference. “Neon” is a pure-black
|
||||||
|
theme with pink and purple accents.
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
|
||||||
|
|
@ -337,6 +337,10 @@ export function useApp(): AppContextValue {
|
||||||
|
|
||||||
/** Apply the requested theme (respecting system preference for `system`). */
|
/** Apply the requested theme (respecting system preference for `system`). */
|
||||||
export function applyTheme(theme: Theme): void {
|
export function applyTheme(theme: Theme): void {
|
||||||
|
if (theme === 'neon') {
|
||||||
|
document.documentElement.dataset.theme = 'neon';
|
||||||
|
return;
|
||||||
|
}
|
||||||
const prefersDark =
|
const prefersDark =
|
||||||
theme === 'dark' ||
|
theme === 'dark' ||
|
||||||
(theme === 'system' && window.matchMedia('(prefers-color-scheme: dark)').matches);
|
(theme === 'system' && window.matchMedia('(prefers-color-scheme: dark)').matches);
|
||||||
|
|
|
||||||
|
|
@ -58,6 +58,35 @@
|
||||||
--shadow-modal: 0 12px 40px rgba(0, 0, 0, 0.6);
|
--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;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
@ -312,7 +341,8 @@ a {
|
||||||
|
|
||||||
/* The artwork is black-on-white; flip it in dark themes so it stays black
|
/* 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. */
|
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);
|
filter: invert(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,19 @@ describe('SettingsScreen', () => {
|
||||||
expect(document.documentElement.dataset.theme).toBe('dark');
|
expect(document.documentElement.dataset.theme).toBe('dark');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('applies the neon theme to the document', async () => {
|
||||||
|
const backend = createFakeBackend();
|
||||||
|
installFakeBackend(backend);
|
||||||
|
renderWithApp(<SettingsScreen />);
|
||||||
|
|
||||||
|
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 () => {
|
it('toggles publish confirmation and npub shortening', async () => {
|
||||||
const backend = createFakeBackend();
|
const backend = createFakeBackend();
|
||||||
installFakeBackend(backend);
|
installFakeBackend(backend);
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,7 @@ Commands:
|
||||||
relays disable <url> Disable a relay
|
relays disable <url> Disable a relay
|
||||||
relays test <url> Test a relay connection
|
relays test <url> Test a relay connection
|
||||||
settings get Show application settings
|
settings get Show application settings
|
||||||
settings set theme <light|dark|system>
|
settings set theme <light|dark|system|neon>
|
||||||
settings set confirm <true|false>
|
settings set confirm <true|false>
|
||||||
settings set shorten <true|false>
|
settings set shorten <true|false>
|
||||||
delete-profile <npub> Delete a profile (moves it to undo stack)
|
delete-profile <npub> Delete a profile (moves it to undo stack)
|
||||||
|
|
@ -442,7 +442,7 @@ fn cli_settings(args: &[String]) -> Result<String, AppError> {
|
||||||
match key.as_str() {
|
match key.as_str() {
|
||||||
"theme" => {
|
"theme" => {
|
||||||
let theme = Theme::parse(value).ok_or_else(|| {
|
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;
|
app.settings.theme = theme;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@ pub enum Theme {
|
||||||
Light,
|
Light,
|
||||||
Dark,
|
Dark,
|
||||||
System,
|
System,
|
||||||
|
Neon,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Theme {
|
impl Theme {
|
||||||
|
|
@ -32,6 +33,7 @@ impl Theme {
|
||||||
"light" => Some(Self::Light),
|
"light" => Some(Self::Light),
|
||||||
"dark" => Some(Self::Dark),
|
"dark" => Some(Self::Dark),
|
||||||
"system" => Some(Self::System),
|
"system" => Some(Self::System),
|
||||||
|
"neon" => Some(Self::Neon),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue