Catppuccin Mocha theme and a legible cursor

Mocha with the palette's red as the accent is now the default; the old
green scheme moves to `:set theme=green` alongside amber and ice.

Two readability fixes in the editor:

- The vim block cursor paints the character inside itself in that
  character's own syntax colour, so an accent-coloured glyph — a
  heading's `#`, a link — vanished against the accent-coloured block.
  The glyph is now forced to the background colour.
- CodeMirror injects its own `.cm-activeLine` rule after this stylesheet,
  so the equally specific override never applied and the current line
  rendered as a bright blue box. Qualified the selector and toned the
  band down to a faint tint of the foreground.

Insert mode also gets a 2px bar instead of the browser hairline caret.
This commit is contained in:
nvrl committed 2026-09-05 06:36:53 +02:00
1 parent e9f44b8809
commit e0565ea1b4
4 files changed
+66 -18

No files matched your search

+1 -1
View File
@@ -59,7 +59,7 @@ The desktop UI is modal. In the browse pane:
Inside the editor the full vim keymap is live — `w`/`b`, `dd`, `ciw`, visual
mode, macros, `:w` to write. `:help` lists every ex command; the useful ones are
`:w`, `:wq`, `:q!`, `:new [yyyy-mm-dd]`, `:date`, `:title`, `:share`, `:link`,
`:upload`, `:media`, `:search`, `:set theme=green|amber|ice` and `:set novim`.
`:upload`, `:media`, `:search`, `:set theme=mocha|green|amber|ice` and `:set novim`.
On a touch device modal editing is turned off and the same actions are buttons.
+58 -9
View File
@@ -1,5 +1,25 @@
/* ── palette ─────────────────────────────────────────────────────────────── */
:root {
/* Catppuccin Mocha, accented with the palette's red. */
:root, [data-theme='mocha'] {
--bg: #1e1e2e;
--bg-alt: #181825;
--bg-lift: #313244;
--line: #45475a;
--fg: #cdd6f4;
--fg-dim: #a6adc8;
--fg-faint: #6c7086;
--accent: #f38ba8;
--accent-dim: #a35b70;
--warn: #f9e2af;
--error: #eba0ac;
--select: #4a3543;
--font: ui-monospace, "JetBrains Mono", "Fira Code", "SF Mono", Menlo, Consolas, monospace;
--radius: 2px;
color-scheme: dark;
}
[data-theme='green'] {
--bg: #0b0e0d;
--bg-alt: #101513;
--bg-lift: #161d1a;
@@ -12,10 +32,6 @@
--warn: #f2c14e;
--error: #ff6b6b;
--select: #1c3a2c;
--font: ui-monospace, "JetBrains Mono", "Fira Code", "SF Mono", Menlo, Consolas, monospace;
--radius: 2px;
color-scheme: dark;
}
[data-theme='amber'] {
@@ -28,6 +44,8 @@
--fg-faint: #4d3f28;
--accent: #ffb454;
--accent-dim: #b57a25;
--warn: #f2c14e;
--error: #ff6b6b;
--select: #3a2a10;
}
@@ -41,6 +59,8 @@
--fg-faint: #3d4f60;
--accent: #7cc7ff;
--accent-dim: #3d84b8;
--warn: #f2c14e;
--error: #ff6b6b;
--select: #17324a;
}
@@ -169,11 +189,40 @@ input::placeholder { color: var(--fg-faint); }
}
.cm-content { caret-color: var(--accent); }
.cm-gutters { background: transparent; border: 0; color: var(--fg-faint); }
.cm-activeLineGutter { background: transparent; color: var(--fg-dim); }
.cm-activeLine { background: rgb(255 255 255 / 2.5%); }
.cm-editor .cm-activeLineGutter { background: transparent; color: var(--fg-dim); }
/*
* CodeMirror injects its own `.cm-activeLine` rule (a light blue #cceeff44)
* into the document after this stylesheet, so an equally specific selector
* loses and the current line renders as a bright box. Qualify it to win.
*/
.cm-editor .cm-activeLine { background: color-mix(in srgb, var(--fg) 4%, transparent); }
.cm-selectionBackground, .cm-content ::selection { background: var(--select) !important; }
.cm-fat-cursor { background: var(--accent) !important; opacity: 1 !important; }
.cm-animate-fat-cursor { background: var(--accent) !important; }
/* Insert mode: a 2px bar reads far better than the browser's hairline caret. */
.cm-cursor, .cm-cursor-primary {
border-left: 2px solid var(--accent);
border-right: 0;
}
/*
* Normal mode: the vim plugin paints the character *inside* the block cursor,
* inheriting its syntax colour, so an accent-coloured glyph — a heading's `#`,
* a link — disappears against the accent-coloured block. Force the glyph to the
* background colour instead, which is legible whatever the token was.
*/
.cm-fat-cursor,
.cm-animate-fat-cursor {
background: var(--accent) !important;
color: var(--bg) !important;
opacity: 1 !important;
}
.cm-fat-cursor.cm-cursor-secondary { background: var(--accent-dim) !important; }
/* Unfocused, the plugin hardcodes a pink outline; keep it on-theme. */
.cm-editor:not(.cm-focused) .cm-fat-cursor {
background: none !important;
outline: 1px solid var(--accent-dim) !important;
color: transparent !important;
}
.cm-panels { background: var(--bg-alt); color: var(--fg); border-color: var(--line); }
.cm-vim-panel { padding: 2px 8px; background: var(--bg-alt); }
.cm-vim-panel input { background: transparent; border: 0; color: var(--accent); }
+4 -4
View File
@@ -1,4 +1,4 @@
import { diary } from './store.svelte'
import { diary, type Theme } from './store.svelte'
import { parseDay } from './util'
/** Filled in by App.svelte so ex-commands can reach the DOM-bound bits. */
@@ -194,13 +194,13 @@ export const commands: CommandSpec[] = [
{
name: 'set',
args: '<option>',
help: 'theme=green|amber|ice, vim, novim',
help: 'theme=mocha|green|amber|ice, vim, novim',
run: (arg) => {
const option = arg.trim()
if (option === 'vim') return diary.setVim(true), diary.say('vim keys on')
if (option === 'novim') return diary.setVim(false), diary.say('vim keys off')
const theme = /^theme=(green|amber|ice)$/.exec(option)?.[1]
if (theme) return diary.setTheme(theme as 'green' | 'amber' | 'ice'), diary.say(`theme ${theme}`)
const theme = /^theme=(mocha|green|amber|ice)$/.exec(option)?.[1]
if (theme) return diary.setTheme(theme as Theme), diary.say(`theme ${theme}`)
diary.say(`unknown option: ${option}`, 'error')
},
},
+3 -4
View File
@@ -2,6 +2,7 @@ import { api, ApiError, type Entry, type EntrySummary, type MediaFile } from './
export type Pane = 'list' | 'entry'
export type Flash = { text: string; kind: 'info' | 'error' } | null
export type Theme = 'mocha' | 'green' | 'amber' | 'ice'
/**
* The whole application state. It is deliberately one object: the keyboard
@@ -29,9 +30,7 @@ class Diary {
overlay = $state<'none' | 'help' | 'media'>('none')
media = $state<MediaFile[]>([])
theme = $state<'green' | 'amber' | 'ice'>(
(localStorage.getItem('diary:theme') as 'green' | 'amber' | 'ice') ?? 'green',
)
theme = $state<Theme>((localStorage.getItem('diary:theme') as Theme) ?? 'mocha')
vimEnabled = $state(localStorage.getItem('diary:vim') !== 'off')
@@ -177,7 +176,7 @@ class Diary {
this.media = await api.listMedia()
}
setTheme(theme: 'green' | 'amber' | 'ice') {
setTheme(theme: Theme) {
this.theme = theme
localStorage.setItem('diary:theme', theme)
}