From 3a2027e8257f69c41052b983fffc77056dfede2e Mon Sep 17 00:00:00 2001 From: Michael Czechowski Date: Fri, 16 Jan 2026 02:08:16 +0100 Subject: [PATCH] feat: add custom CodeMirror theme with purple accent colors MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace oneDark theme with custom crispyEditorTheme featuring: - Purple accent (#c9a6eb) for keywords and HTML tags - Matching dark background (#262630) - Applied consistently across editor and reference code blocks 🤖 Generated with [Claude Code](https://claude.com/claude-code) --- src/app.js | 7 ++-- src/i18n.js | 2 +- src/impl/CodeEditor.js | 81 ++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 82 insertions(+), 8 deletions(-) diff --git a/src/app.js b/src/app.js index 3865fa0..91cc3ad 100644 --- a/src/app.js +++ b/src/app.js @@ -1,5 +1,5 @@ import { LessonEngine } from "./impl/LessonEngine.js"; -import { CodeEditor } from "./impl/CodeEditor.js"; +import { CodeEditor, crispyEditorTheme } from "./impl/CodeEditor.js"; import { renderLesson, renderModuleList, renderLevelIndicator, updateActiveLessonInSidebar } from "./helpers/renderer.js"; import { loadModules } from "./config/lessons.js"; import { initI18n, t, getLanguage, setLanguage, applyTranslations } from "./i18n.js"; @@ -10,7 +10,6 @@ import { getRandomTemplate } from "./config/playground-templates.js"; // CodeMirror imports for syntax highlighting import { EditorState } from "@codemirror/state"; import { EditorView } from "@codemirror/view"; -import { oneDark } from "@codemirror/theme-one-dark"; import { html } from "@codemirror/lang-html"; import { css } from "@codemirror/lang-css"; @@ -79,7 +78,7 @@ function highlightSectionCodeBlocks() { // Create read-only CodeMirror view const state = EditorState.create({ doc: content, - extensions: [langExtension, oneDark, readOnlyTheme, EditorState.readOnly.of(true), EditorView.lineWrapping] + extensions: [langExtension, crispyEditorTheme, readOnlyTheme, EditorState.readOnly.of(true), EditorView.lineWrapping] }); const view = new EditorView({ @@ -2153,7 +2152,7 @@ function highlightReferenceCodeBlocks() { const view = new EditorView({ state: EditorState.create({ doc: code, - extensions: [isHtml ? html() : css(), oneDark, readOnlyTheme, EditorState.readOnly.of(true), EditorView.editable.of(false)] + extensions: [isHtml ? html() : css(), crispyEditorTheme, readOnlyTheme, EditorState.readOnly.of(true), EditorView.editable.of(false)] }), parent }); diff --git a/src/i18n.js b/src/i18n.js index cea5c8b..a8164cb 100644 --- a/src/i18n.js +++ b/src/i18n.js @@ -253,7 +253,7 @@ const translations = { // Landing page landingHeroTitle: "Web Programmierung", - landingHeroHighlight: "Selbstständig Coden lernen", + landingHeroHighlight: "Selbstständig lernen", landingHeroSubtitle: "Meistere HTML, CSS und Tailwind durch praktische Übungen mit sofortigem Feedback. Kostenlos und Open Source.", landingCtaStart: "Jetzt starten", landingWhyTitle: "Warum Code Crispies funktioniert", diff --git a/src/impl/CodeEditor.js b/src/impl/CodeEditor.js index 3a8636f..c39f089 100644 --- a/src/impl/CodeEditor.js +++ b/src/impl/CodeEditor.js @@ -5,13 +5,88 @@ import { EditorState, Prec } from "@codemirror/state"; import { EditorView, keymap, placeholder } from "@codemirror/view"; import { defaultKeymap, historyKeymap, indentMore, indentLess, undo, redo } from "@codemirror/commands"; import { history } from "@codemirror/commands"; -import { oneDark } from "@codemirror/theme-one-dark"; import { html } from "@codemirror/lang-html"; import { css } from "@codemirror/lang-css"; import { autocompletion } from "@codemirror/autocomplete"; import { abbreviationTracker, expandAbbreviation } from "@emmetio/codemirror6-plugin"; +import { HighlightStyle, syntaxHighlighting } from "@codemirror/language"; +import { tags } from "@lezer/highlight"; -// Custom overrides for One Dark theme +// Custom theme with purple accent colors (matching app completed state) +const crispyTheme = EditorView.theme( + { + "&": { + backgroundColor: "#262630", + color: "#c8c8d0" + }, + ".cm-content": { + caretColor: "#9b6dd4" + }, + ".cm-cursor, .cm-dropCursor": { + borderLeftColor: "#9b6dd4" + }, + "&.cm-focused > .cm-scroller > .cm-selectionLayer .cm-selectionBackground, .cm-selectionBackground, .cm-content ::selection": { + backgroundColor: "#3e3e4a" + }, + ".cm-panels": { + backgroundColor: "#262630", + color: "#c8c8d0" + }, + ".cm-searchMatch": { + backgroundColor: "#3e3e4a", + outline: "1px solid #9b6dd4" + }, + ".cm-searchMatch.cm-searchMatch-selected": { + backgroundColor: "rgba(155, 109, 212, 0.3)" + }, + ".cm-activeLine": { + backgroundColor: "#2e2e3a" + }, + ".cm-selectionMatch": { + backgroundColor: "#3e3e4a" + }, + ".cm-gutters": { + backgroundColor: "#262630", + color: "#808090", + border: "none" + }, + ".cm-activeLineGutter": { + backgroundColor: "#2e2e3a" + }, + ".cm-lineNumbers .cm-gutterElement": { + color: "#808090" + } + }, + { dark: true } +); + +// Syntax highlighting with purple accent +const crispyHighlight = HighlightStyle.define([ + { tag: tags.keyword, color: "#c9a6eb" }, + { tag: tags.operator, color: "#cdd6f4" }, + { tag: tags.variableName, color: "#89b4fa" }, + { tag: tags.propertyName, color: "#89b4fa" }, + { tag: tags.attributeName, color: "#89b4fa" }, + { tag: tags.className, color: "#89b4fa" }, + { tag: tags.tagName, color: "#c9a6eb" }, + { tag: tags.string, color: "#a6e3a1" }, + { tag: tags.number, color: "#fab387" }, + { tag: tags.bool, color: "#fab387" }, + { tag: tags.null, color: "#fab387" }, + { tag: tags.comment, color: "#6c7086", fontStyle: "italic" }, + { tag: tags.bracket, color: "#cdd6f4" }, + { tag: tags.punctuation, color: "#cdd6f4" }, + { tag: tags.definition(tags.variableName), color: "#89b4fa" }, + { tag: tags.function(tags.variableName), color: "#89b4fa" }, + { tag: tags.atom, color: "#c9a6eb" }, + { tag: tags.unit, color: "#a6e3a1" }, + { tag: tags.color, color: "#f9e2af" } +]); + +// Combined theme export +export const crispyEditorTheme = [crispyTheme, syntaxHighlighting(crispyHighlight)]; + +// Custom overrides for editor styling const editorTheme = EditorView.theme( { "&": { @@ -51,7 +126,7 @@ export class CodeEditor { // Build extensions array const extensions = [ langExtension, - oneDark, + crispyEditorTheme, editorTheme, // History for undo/redo history(),