fix(web): refine theme contrast and color system

This commit is contained in:
tianma-if committed 2026-08-24 11:48:54 +08:00
1 parent adcee97f78
commit 0bb405aeb7
17 files changed
+326 -194

No files matched your search

+10 -11
View File
@@ -46,19 +46,18 @@ export const LoginScreen = ({ error, instanceUrl: initialInstanceUrl, isSubmitti
};
return (
<main className="flex h-[100dvh] items-center justify-center bg-gradient-to-tr from-emerald-50/70 via-emerald-50 to-emerald-100 px-4 py-8 text-slate-950">
<div className="absolute inset-0 bg-[radial-gradient(circle_at_top_right,rgb(var(--brand-green-rgb)/0.06),transparent_45%)] pointer-events-none" />
<div className="absolute inset-0 bg-[radial-gradient(circle_at_bottom_left,rgb(var(--brand-green-rgb)/0.04),transparent_55%)] pointer-events-none" />
<GitHubRepositoryLink className="absolute right-4 top-[max(1rem,env(safe-area-inset-top))] z-10 h-10 w-10 justify-center rounded-full border border-slate-200 bg-white/85 text-slate-600 shadow-sm backdrop-blur transition hover:bg-white hover:text-slate-950 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-emerald-500/70" iconClassName="h-5 w-5" />
<main className="flex h-[100dvh] items-center justify-center bg-[var(--workspace-canvas)] px-4 py-8 text-slate-950">
<div className="pointer-events-none absolute inset-0 bg-[radial-gradient(circle_at_50%_15%,rgb(var(--brand-green-rgb)/0.045),transparent_42%)]" />
<GitHubRepositoryLink className="absolute right-4 top-[max(1rem,env(safe-area-inset-top))] z-10 h-10 w-10 justify-center rounded-full border border-slate-200 bg-white/85 text-slate-600 shadow-[0_4px_16px_rgb(var(--slate-900-rgb)/0.05)] backdrop-blur transition hover:border-slate-300 hover:bg-white hover:text-slate-950 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-emerald-500/60" iconClassName="h-5 w-5" />
<section className="relative w-full max-w-[400px] rounded-2xl border border-emerald-500/15 bg-white/95 p-8 shadow-[0_20px_50px_rgb(var(--brand-green-rgb)/0.08)] backdrop-blur-md transition-all duration-300 hover:shadow-[0_24px_60px_rgb(var(--brand-green-rgb)/0.12)]">
<section className="relative w-full max-w-[400px] rounded-2xl border border-slate-200 bg-white/95 p-8 shadow-[0_20px_50px_rgb(var(--slate-900-rgb)/0.08)] backdrop-blur-md">
<div className="mb-8 flex items-center gap-4">
<div className="flex h-12 w-12 items-center justify-center rounded-xl bg-gradient-to-br from-emerald-500 to-emerald-600 text-white shadow-[0_8px_16px_-4px_rgb(var(--brand-green-rgb)/0.35)]">
<div className="flex h-12 w-12 items-center justify-center rounded-xl bg-emerald-500 text-white shadow-[0_8px_18px_-8px_rgb(var(--brand-green-rgb)/0.45)]">
<LockKeyhole className="h-5.5 w-5.5" />
</div>
<div className="min-w-0">
<h1 className="text-xl font-bold leading-tight tracking-tight text-slate-900">{t("login.title")}</h1>
<p className="mt-1 text-xs font-medium uppercase tracking-wider text-slate-400">{t("login.subtitle")}</p>
<p className="mt-1 text-xs font-medium tracking-wide text-slate-500">{t("login.subtitle")}</p>
</div>
</div>
@@ -85,7 +84,7 @@ export const LoginScreen = ({ error, instanceUrl: initialInstanceUrl, isSubmitti
<span className="mb-2 block text-sm font-semibold text-slate-700">{t("login.desktopInstanceUrl")}</span>
<Input
autoComplete="url"
className="h-11 rounded-lg bg-slate-50/50 px-3.5 focus-visible:bg-white focus-visible:ring-4 focus-visible:ring-emerald-500/10"
className="h-11 rounded-lg bg-slate-50/50 px-3.5 focus-visible:bg-white focus-visible:ring-2 focus-visible:ring-emerald-500/20"
placeholder={t("login.instanceUrlPlaceholder")}
required
type="url"
@@ -99,7 +98,7 @@ export const LoginScreen = ({ error, instanceUrl: initialInstanceUrl, isSubmitti
<span className="mb-2 block text-sm font-semibold text-slate-700">{t("login.username")}</span>
<Input
autoComplete="username"
className="h-11 rounded-lg bg-slate-50/50 px-3.5 focus-visible:bg-white focus-visible:ring-4 focus-visible:ring-emerald-500/10"
className="h-11 rounded-lg bg-slate-50/50 px-3.5 focus-visible:bg-white focus-visible:ring-2 focus-visible:ring-emerald-500/20"
required
value={username}
onChange={(event) => setUsername(event.target.value)}
@@ -110,7 +109,7 @@ export const LoginScreen = ({ error, instanceUrl: initialInstanceUrl, isSubmitti
<span className="mb-2 block text-sm font-semibold text-slate-700">{t("login.password")}</span>
<Input
autoComplete="current-password"
className="h-11 rounded-lg bg-slate-50/50 px-3.5 focus-visible:bg-white focus-visible:ring-4 focus-visible:ring-emerald-500/10"
className="h-11 rounded-lg bg-slate-50/50 px-3.5 focus-visible:bg-white focus-visible:ring-2 focus-visible:ring-emerald-500/20"
required
type="password"
value={password}
@@ -119,7 +118,7 @@ export const LoginScreen = ({ error, instanceUrl: initialInstanceUrl, isSubmitti
</label>
<Button
className="w-full h-11 justify-center rounded-lg bg-emerald-500 hover:bg-emerald-600 text-white font-semibold transition-all duration-200 shadow-[0_8px_20px_-4px_rgb(var(--brand-green-rgb)/0.25)] hover:shadow-[0_12px_24px_-4px_rgb(var(--brand-green-rgb)/0.35)]"
className="h-11 w-full justify-center rounded-lg bg-emerald-500 font-semibold text-white shadow-[0_8px_20px_-8px_rgb(var(--brand-green-rgb)/0.35)] transition-colors duration-200 hover:bg-emerald-600"
size="md"
type="submit"
variant="solid"
+2 -1
View File
@@ -66,16 +66,17 @@ export const MermaidCodeBlock = ({ editor, node }: NodeViewProps) => {
let cancelled = false;
const timer = window.setTimeout(() => {
setRenderState("loading");
const palette = MERMAID_THEME_PALETTES[mermaidTheme];
const renderBeautiful = () => loadBeautifulMermaid()
.then(({ renderMermaidSVG, THEMES }) => renderMermaidSVG(source, {
...THEMES[mermaidTheme],
...palette,
transparent: true,
font: "Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, sans-serif",
padding: 24,
}));
const renderOfficial = () => loadMermaid().then(async (mermaid) => {
const palette = MERMAID_THEME_PALETTES[mermaidTheme];
mermaid.initialize({
startOnLoad: false,
securityLevel: "strict",
+15 -15
View File
@@ -34,21 +34,21 @@ export interface MermaidThemePalette {
}
export const MERMAID_THEME_PALETTES: Record<MermaidThemeName, MermaidThemePalette> = {
"zinc-light": { bg: "#FFFFFF", fg: "#27272A" },
"zinc-dark": { bg: "#18181B", fg: "#FAFAFA" },
"tokyo-night": { bg: "#1a1b26", fg: "#a9b1d6", line: "#3d59a1", accent: "#7aa2f7", muted: "#565f89" },
"tokyo-night-storm": { bg: "#24283b", fg: "#a9b1d6", line: "#3d59a1", accent: "#7aa2f7", muted: "#565f89" },
"tokyo-night-light": { bg: "#d5d6db", fg: "#343b58", line: "#34548a", accent: "#34548a", muted: "#9699a3" },
"catppuccin-mocha": { bg: "#1e1e2e", fg: "#cdd6f4", line: "#585b70", accent: "#cba6f7", muted: "#6c7086" },
"catppuccin-latte": { bg: "#eff1f5", fg: "#4c4f69", line: "#9ca0b0", accent: "#8839ef", muted: "#9ca0b0" },
nord: { bg: "#2e3440", fg: "#d8dee9", line: "#4c566a", accent: "#88c0d0", muted: "#616e88" },
"nord-light": { bg: "#eceff4", fg: "#2e3440", line: "#aab1c0", accent: "#5e81ac", muted: "#7b88a1" },
dracula: { bg: "#282a36", fg: "#f8f8f2", line: "#6272a4", accent: "#bd93f9", muted: "#6272a4" },
"zinc-light": { bg: "#FFFFFF", fg: "#27272A", line: "#a1a1aa", accent: "#52525b", muted: "#71717a" },
"zinc-dark": { bg: "#18181B", fg: "#FAFAFA", line: "#52525b", accent: "#a1a1aa", muted: "#a1a1aa" },
"tokyo-night": { bg: "#1a1b26", fg: "#a9b1d6", line: "#3d59a1", accent: "#7aa2f7", muted: "#7c85ac" },
"tokyo-night-storm": { bg: "#24283b", fg: "#a9b1d6", line: "#3d59a1", accent: "#7aa2f7", muted: "#8991b8" },
"tokyo-night-light": { bg: "#d5d6db", fg: "#343b58", line: "#34548a", accent: "#34548a", muted: "#545a71" },
"catppuccin-mocha": { bg: "#1e1e2e", fg: "#cdd6f4", line: "#585b70", accent: "#cba6f7", muted: "#9399b2" },
"catppuccin-latte": { bg: "#eff1f5", fg: "#4c4f69", line: "#9ca0b0", accent: "#8839ef", muted: "#666a80" },
nord: { bg: "#2e3440", fg: "#d8dee9", line: "#4c566a", accent: "#88c0d0", muted: "#97a0b4" },
"nord-light": { bg: "#eceff4", fg: "#2e3440", line: "#aab1c0", accent: "#5e81ac", muted: "#5f6a7e" },
dracula: { bg: "#282a36", fg: "#f8f8f2", line: "#6272a4", accent: "#bd93f9", muted: "#8894b8" },
"github-light": { bg: "#ffffff", fg: "#1f2328", line: "#d1d9e0", accent: "#0969da", muted: "#59636e" },
"github-dark": { bg: "#0d1117", fg: "#e6edf3", line: "#3d444d", accent: "#4493f8", muted: "#9198a1" },
"solarized-light": { bg: "#fdf6e3", fg: "#657b83", line: "#93a1a1", accent: "#268bd2", muted: "#93a1a1" },
"solarized-dark": { bg: "#002b36", fg: "#839496", line: "#586e75", accent: "#268bd2", muted: "#586e75" },
"one-dark": { bg: "#282c34", fg: "#abb2bf", line: "#4b5263", accent: "#c678dd", muted: "#5c6370" },
"solarized-light": { bg: "#fdf6e3", fg: "#586e75", line: "#93a1a1", accent: "#268bd2", muted: "#5f747b" },
"solarized-dark": { bg: "#002b36", fg: "#839496", line: "#586e75", accent: "#268bd2", muted: "#93a1a1" },
"one-dark": { bg: "#282c34", fg: "#abb2bf", line: "#4b5263", accent: "#c678dd", muted: "#8f96a3" },
};
export const EDITOR_THEME_NAMES = [
@@ -138,8 +138,8 @@ const MERMAID_THEME_STORAGE_KEY = "edgeever.mermaid-theme";
const EDITOR_THEME_STORAGE_KEY = "edgeever.editor-theme";
const CUSTOM_EDITOR_THEME_STORAGE_KEY = "edgeever.custom-editor-theme";
const CUSTOM_EDITOR_THEMES_STORAGE_KEY = "edgeever.custom-editor-themes";
const LIGHT_THEME_COLOR = "#f8fafc";
const DARK_THEME_COLOR = "#0f172a";
const LIGHT_THEME_COLOR = "#f3f5f7";
const DARK_THEME_COLOR = "#101311";
const AppearanceThemeContext = createContext<AppearanceThemeContextValue | null>(null);
const MermaidThemeContext = createContext<MermaidThemeContextValue | null>(null);
const EditorThemeContext = createContext<EditorThemeContextValue | null>(null);
+50 -3
View File
@@ -7,6 +7,27 @@ import {
resolveMermaidTheme,
} from "../components/ThemeProvider";
const BUILT_IN_EDITOR_THEMES = [
"minimal-emerald",
"outline-emerald",
"wechat-green",
"modern-mint",
"marxico",
];
const readDarkThemeTokens = (theme) => {
const css = readFileSync(new URL(`../styles/editor-themes/${theme}.css`, import.meta.url), "utf8");
const selector = `:root.dark .edgeever-editor[data-editor-theme="${theme}"]:not([data-editor-theme="default"])`;
const blockStart = css.indexOf(`${selector} {`);
expect(blockStart).toBeGreaterThanOrEqual(0);
const blockEnd = css.indexOf("}", blockStart);
const block = css.slice(blockStart, blockEnd);
const tokens = Object.fromEntries(
[...block.matchAll(/--editor-theme-([\w-]+):\s*(#[\da-f]{6}|var\([^;]+\));/gi)].map((match) => [match[1], match[2]])
);
return { block, css, tokens };
};
describe("dark theme contracts", () => {
test("default custom editor themes meet their contrast thresholds", () => {
for (const colors of [DEFAULT_CUSTOM_LIGHT_COLORS, DEFAULT_CUSTOM_DARK_COLORS]) {
@@ -25,13 +46,39 @@ describe("dark theme contracts", () => {
expect(css).toContain('[class~="text-emerald-700"]');
});
test("workspace dark surfaces stay neutral and bundled editor themes blend into the canvas", () => {
const css = readFileSync(new URL("../styles/globals.css", import.meta.url), "utf8");
expect(css).toContain("--workspace-canvas: #101311;");
expect(css).toContain("--workspace-sidebar: #121612;");
expect(css).toContain("--workspace-memo-list: #151a17;");
expect(css).toContain("--workspace-editor: #191e1b;");
expect(css).toContain(':not([data-editor-theme="custom"])');
expect(css).toContain("--editor-theme-bg: var(--workspace-editor);");
expect(contrastRatio("#cad4ce", "#191e1b")).toBeGreaterThanOrEqual(4.5);
expect(contrastRatio("#9aa9a0", "#191e1b")).toBeGreaterThanOrEqual(4.5);
});
test("every bundled editor theme has a complete accessible dark palette", () => {
for (const theme of BUILT_IN_EDITOR_THEMES) {
const { tokens } = readDarkThemeTokens(theme);
expect(tokens.bg).toBe("var(--workspace-editor)");
expect(contrastRatio(tokens.text, "#191e1b")).toBeGreaterThanOrEqual(4.5);
expect(contrastRatio(tokens.heading, "#191e1b")).toBeGreaterThanOrEqual(4.5);
expect(contrastRatio(tokens.accent, "#191e1b")).toBeGreaterThanOrEqual(3);
expect(contrastRatio(tokens.muted, tokens.soft)).toBeGreaterThanOrEqual(4.5);
expect(contrastRatio(tokens["code-text"], tokens["code-bg"])).toBeGreaterThanOrEqual(4.5);
}
});
test("Marxico keeps note content legible in dark mode", () => {
const css = readFileSync(new URL("../styles/editor-themes/marxico.css", import.meta.url), "utf8");
const { css, tokens } = readDarkThemeTokens("marxico");
expect(css).toContain(':root.dark .edgeever-editor[data-editor-theme="marxico"]');
expect(css).toContain("color: var(--editor-theme-text);");
expect(contrastRatio("#dce3ea", "#19222b")).toBeGreaterThanOrEqual(4.5);
expect(contrastRatio("#e7edf2", "#19222b")).toBeGreaterThanOrEqual(4.5);
expect(contrastRatio(tokens.text, "#191e1b")).toBeGreaterThanOrEqual(4.5);
expect(contrastRatio(tokens.heading, "#191e1b")).toBeGreaterThanOrEqual(4.5);
});
test("automatic Mermaid themes follow the resolved appearance", () => {
+34 -3
View File
@@ -1,7 +1,15 @@
import { describe, expect, test } from "bun:test";
import { renderMermaidSVG, THEMES } from "beautiful-mermaid";
import { MERMAID_THEME_PALETTES } from "../components/ThemeProvider";
import { contrastRatio } from "./color-contrast";
import { getOfficialMermaidThemeVariables } from "./mermaid-theme";
const SEQUENCE_SOURCE = `sequenceDiagram
participant User as 用户
participant App as 客户端
User->>App: 保存笔记
App-->>User: 保存成功`;
describe("official Mermaid theme variables", () => {
test("maps every built-in theme across flowchart, sequence, and state diagrams", () => {
for (const palette of Object.values(MERMAID_THEME_PALETTES)) {
@@ -20,13 +28,36 @@ describe("official Mermaid theme variables", () => {
}
});
test("keeps minimal two-color themes coherent", () => {
test("keeps the zinc light theme coherent", () => {
const palette = MERMAID_THEME_PALETTES["zinc-light"];
const variables = getOfficialMermaidThemeVariables(palette);
expect(variables.background).toBe(palette.bg);
expect(variables.primaryColor).toBe(palette.bg);
expect(variables.primaryBorderColor).toBe(palette.fg);
expect(variables.signalColor).toBe(palette.fg);
expect(variables.primaryBorderColor).toBe(palette.muted);
expect(variables.signalColor).toBe(palette.accent);
});
test("keeps primary and secondary diagram text readable in every theme", () => {
for (const [theme, palette] of Object.entries(MERMAID_THEME_PALETTES)) {
expect(contrastRatio(palette.fg, palette.bg), `${theme} primary text`).toBeGreaterThanOrEqual(4.5);
expect(palette.muted, `${theme} secondary text color`).toBeDefined();
expect(contrastRatio(palette.muted, palette.bg), `${theme} secondary text`).toBeGreaterThanOrEqual(4.5);
}
});
test("applies the accessible text palette when every theme renders a sequence diagram", () => {
for (const [theme, palette] of Object.entries(MERMAID_THEME_PALETTES)) {
const svg = renderMermaidSVG(SEQUENCE_SOURCE, {
...THEMES[theme],
...palette,
transparent: true,
});
expect(svg, `${theme} rendered SVG`).toContain(`--bg:${palette.bg}`);
expect(svg, `${theme} rendered SVG`).toContain(`--fg:${palette.fg}`);
expect(svg, `${theme} rendered SVG`).toContain(`--muted:${palette.muted}`);
expect(svg, `${theme} message text`).toContain('fill="var(--_text-muted)"');
}
});
});
+2
View File
@@ -1,5 +1,6 @@
import hljs from "highlight.js/lib/common";
import { renderMermaidSVG, THEMES } from "beautiful-mermaid";
import { MERMAID_THEME_PALETTES } from "@/components/ThemeProvider";
const WINDOWS_RESERVED_NAME = /^(con|prn|aux|nul|com[1-9]|lpt[1-9])(?:\.|$)/i;
const EXPORT_FONT_FAMILY =
@@ -187,6 +188,7 @@ export const renderMermaidBlocksForHtmlExport = async (root: HTMLElement) => {
try {
const svg = renderMermaidSVG(source, {
...THEMES["zinc-light"],
...MERMAID_THEME_PALETTES["zinc-light"],
transparent: true,
font: EXPORT_FONT_FAMILY,
padding: 24,
+34
View File
@@ -0,0 +1,34 @@
import { describe, expect, test } from "bun:test";
import { readFileSync } from "node:fs";
import { contrastRatio } from "./color-contrast";
const globals = readFileSync(new URL("../styles/globals.css", import.meta.url), "utf8");
describe("application color system", () => {
test("uses the same restrained brand green throughout the application", () => {
const mobileEditor = readFileSync(new URL("../styles/mobile-markdown-editor.css", import.meta.url), "utf8");
expect(globals).toContain("--brand-green: #16a06e;");
expect(globals).not.toContain("--brand-green: #00a82d;");
expect(mobileEditor).toContain("color: #16a06e;");
expect(contrastRatio("#11694a", "#f0f8f4")).toBeGreaterThanOrEqual(4.5);
});
test("keeps the light workspace crisp and neutral while preserving text hierarchy", () => {
expect(globals).toContain("--workspace-canvas: #f3f5f7;");
expect(globals).toContain("--workspace-sidebar: #eff1f4;");
expect(globals).toContain("--workspace-memo-list: #f6f7f9;");
expect(globals).toContain("--workspace-editor: #ffffff;");
expect(contrastRatio("#0f172a", "#ffffff")).toBeGreaterThanOrEqual(4.5);
expect(contrastRatio("#64748b", "#ffffff")).toBeGreaterThanOrEqual(4.5);
});
test("keeps dark workspace surfaces distinct without blue-black color casts", () => {
expect(globals).toContain("--workspace-canvas: #101311;");
expect(globals).toContain("--workspace-sidebar: #121612;");
expect(globals).toContain("--workspace-memo-list: #151a17;");
expect(globals).toContain("--workspace-editor: #191e1b;");
expect(contrastRatio("#cad4ce", "#191e1b")).toBeGreaterThanOrEqual(4.5);
expect(contrastRatio("#9aa9a0", "#191e1b")).toBeGreaterThanOrEqual(4.5);
});
});
+2
View File
@@ -1,6 +1,7 @@
import type { Editor } from "@tiptap/react";
import { MEMO_CONTENT_STYLE } from "@edgeever/shared";
import { marked } from "marked";
import { MERMAID_THEME_PALETTES } from "@/components/ThemeProvider";
import { copyHtmlToClipboard } from "@/lib/clipboard";
import { parseCustomCssToStyles } from "@/lib/css-sandbox";
@@ -280,6 +281,7 @@ const renderMermaidSvg = async (source: string) => {
const { renderMermaidSVG, THEMES } = await import("beautiful-mermaid");
return renderMermaidSVG(source, {
...THEMES["zinc-light"],
...MERMAID_THEME_PALETTES["zinc-light"],
transparent: true,
font: "Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, sans-serif",
padding: 24,
+2
View File
@@ -1,6 +1,7 @@
import { Previewer } from "pagedjs";
import hljs from "highlight.js/lib/common";
import { renderMermaidSVG, THEMES } from "beautiful-mermaid";
import { MERMAID_THEME_PALETTES } from "@/components/ThemeProvider";
import {
NOTE_PRINT_MESSAGE,
NOTE_PRINT_READY_MESSAGE,
@@ -39,6 +40,7 @@ const renderMermaidBlocks = async (root: HTMLElement) => {
try {
const svg = renderMermaidSVG(source, {
...THEMES["zinc-light"],
...MERMAID_THEME_PALETTES["zinc-light"],
transparent: true,
font: PRINT_FONT_FAMILY,
padding: 24,
+19 -13
View File
@@ -298,17 +298,17 @@
* but remap every hard-coded reading color so new and existing notes remain legible.
*/
:root.dark .edgeever-editor[data-editor-theme="marxico"]:not([data-editor-theme="default"]) {
--editor-theme-bg: #19222b;
--editor-theme-text: #dce3ea;
--editor-theme-muted: #99a7b5;
--editor-theme-heading: #e7edf2;
--editor-theme-accent: #68b7ff;
--editor-theme-soft: #202d38;
--editor-theme-border: #344451;
--editor-theme-code-bg: #2b2027;
--editor-theme-code-text: #f0a6bd;
--editor-theme-table-stripe: #1d2832;
--editor-theme-table-hover: #24323e;
--editor-theme-bg: var(--workspace-editor);
--editor-theme-text: #d9e1dc;
--editor-theme-muted: #99a7a0;
--editor-theme-heading: #eef2ef;
--editor-theme-accent: #78b7e8;
--editor-theme-soft: #202a25;
--editor-theme-border: #39473f;
--editor-theme-code-bg: #2a2023;
--editor-theme-code-text: #f2b0c2;
--editor-theme-table-stripe: #1d2621;
--editor-theme-table-hover: #24312a;
color-scheme: dark;
background: var(--editor-theme-bg) !important;
}
@@ -336,7 +336,7 @@
}
:root.dark .edgeever-editor[data-editor-theme="marxico"]:not([data-editor-theme="default"]) .ProseMirror a:not(.edgeever-attachment-link):hover {
color: #9acfff;
color: #acd5f4;
}
:root.dark .edgeever-editor[data-editor-theme="marxico"]:not([data-editor-theme="default"]) .ProseMirror blockquote {
@@ -351,7 +351,13 @@
}
:root.dark .edgeever-editor[data-editor-theme="marxico"]:not([data-editor-theme="default"]) .ProseMirror pre {
background: #202830;
background: #151d19;
color: var(--editor-theme-text);
}
:root.dark .edgeever-editor[data-editor-theme="marxico"]:not([data-editor-theme="default"]) .ProseMirror .edgeever-code-copy-button {
border-color: var(--editor-theme-border);
background: #151d19;
color: var(--editor-theme-text);
}
@@ -15,15 +15,15 @@
}
:root.dark .edgeever-editor[data-editor-theme="minimal-emerald"]:not([data-editor-theme="default"]) {
--editor-theme-bg: #111827;
--editor-theme-text: #d1d5db;
--editor-theme-muted: #9ca3af;
--editor-theme-heading: #f9fafb;
--editor-theme-accent: #34d399;
--editor-theme-soft: #1f2937;
--editor-theme-border: #374151;
--editor-theme-code-bg: #1f2937;
--editor-theme-code-text: #6ee7b7;
--editor-theme-bg: var(--workspace-editor);
--editor-theme-text: #d8e0db;
--editor-theme-muted: #97a59c;
--editor-theme-heading: #f0f4f1;
--editor-theme-accent: #4fd1a1;
--editor-theme-soft: #202923;
--editor-theme-border: #314039;
--editor-theme-code-bg: #111713;
--editor-theme-code-text: #7ee0bc;
}
.edgeever-editor[data-editor-theme="minimal-emerald"]:not([data-editor-theme="default"]) .ProseMirror {
@@ -76,7 +76,7 @@
.edgeever-editor[data-editor-theme="minimal-emerald"]:not([data-editor-theme="default"]) .ProseMirror a {
color: var(--editor-theme-accent);
text-decoration: underline;
text-decoration-color: rgba(5, 150, 105, 0.4);
text-decoration-color: color-mix(in srgb, var(--editor-theme-accent) 42%, transparent);
text-underline-offset: 2px;
}
@@ -15,15 +15,15 @@
}
:root.dark .edgeever-editor[data-editor-theme="modern-mint"]:not([data-editor-theme="default"]) {
--editor-theme-bg: #111a18;
--editor-theme-text: #cbd5e1;
--editor-theme-muted: #8fa39f;
--editor-theme-heading: #f1f5f9;
--editor-theme-accent: #14b8a6;
--editor-theme-soft: #182b26;
--editor-theme-border: #24423a;
--editor-theme-code-bg: #1e293b;
--editor-theme-code-text: #2ee5b5;
--editor-theme-bg: var(--workspace-editor);
--editor-theme-text: #d2ded9;
--editor-theme-muted: #90a49b;
--editor-theme-heading: #eff6f3;
--editor-theme-accent: #2dc9ac;
--editor-theme-soft: #1b2b25;
--editor-theme-border: #2f473e;
--editor-theme-code-bg: #101916;
--editor-theme-code-text: #66e5c7;
}
.edgeever-editor[data-editor-theme="modern-mint"]:not([data-editor-theme="default"]) .ProseMirror {
@@ -77,14 +77,10 @@
.edgeever-editor[data-editor-theme="modern-mint"]:not([data-editor-theme="default"]) .ProseMirror a {
color: var(--editor-theme-accent);
text-decoration: underline;
text-decoration-color: rgba(0, 179, 138, 0.4);
text-decoration-color: color-mix(in srgb, var(--editor-theme-accent) 42%, transparent);
text-underline-offset: 2px;
}
:root.dark .edgeever-editor[data-editor-theme="modern-mint"]:not([data-editor-theme="default"]) .ProseMirror a {
text-decoration-color: rgba(20, 184, 166, 0.4);
}
.edgeever-editor[data-editor-theme="modern-mint"]:not([data-editor-theme="default"]) .ProseMirror blockquote {
margin: 1.5rem 0;
border-left: 4px solid var(--editor-theme-accent);
@@ -15,15 +15,15 @@
}
:root.dark .edgeever-editor[data-editor-theme="outline-emerald"]:not([data-editor-theme="default"]) {
--editor-theme-bg: #0f172a;
--editor-theme-text: #cbd5e1;
--editor-theme-muted: #94a3b8;
--editor-theme-heading: #f8fafc;
--editor-theme-accent: #34d399;
--editor-theme-soft: #1e293b;
--editor-theme-border: #334155;
--editor-theme-code-bg: #020617;
--editor-theme-code-text: #e2e8f0;
--editor-theme-bg: var(--workspace-editor);
--editor-theme-text: #d3dcd6;
--editor-theme-muted: #8f9e95;
--editor-theme-heading: #f3f6f4;
--editor-theme-accent: #66d6ae;
--editor-theme-soft: #1d2822;
--editor-theme-border: #36463d;
--editor-theme-code-bg: #0f1411;
--editor-theme-code-text: #dbe6df;
}
.edgeever-editor[data-editor-theme="outline-emerald"]:not([data-editor-theme="default"]) .ProseMirror {
@@ -78,7 +78,7 @@
.edgeever-editor[data-editor-theme="outline-emerald"]:not([data-editor-theme="default"]) .ProseMirror a {
color: var(--editor-theme-accent);
text-decoration: underline;
text-decoration-color: rgba(5, 150, 105, 0.4);
text-decoration-color: color-mix(in srgb, var(--editor-theme-accent) 42%, transparent);
text-underline-offset: 2px;
}
@@ -15,15 +15,15 @@
}
:root.dark .edgeever-editor[data-editor-theme="wechat-green"]:not([data-editor-theme="default"]) {
--editor-theme-bg: #121212;
--editor-theme-text: #cbd5e1;
--editor-theme-muted: #94a3b8;
--editor-theme-heading: #fafafa;
--editor-theme-accent: #34d399;
--editor-theme-soft: #1a241e;
--editor-theme-border: #2d3d33;
--editor-theme-code-bg: #1e293b;
--editor-theme-code-text: #3eb575;
--editor-theme-bg: var(--workspace-editor);
--editor-theme-text: #d7ded9;
--editor-theme-muted: #9aa69e;
--editor-theme-heading: #f5f7f5;
--editor-theme-accent: #3ecf78;
--editor-theme-soft: #1c2a21;
--editor-theme-border: #314439;
--editor-theme-code-bg: #111a15;
--editor-theme-code-text: #70dfa0;
}
.edgeever-editor[data-editor-theme="wechat-green"]:not([data-editor-theme="default"]) .ProseMirror {
@@ -79,20 +79,16 @@
:root.dark .edgeever-editor[data-editor-theme="wechat-green"]:not([data-editor-theme="default"]) .ProseMirror strong {
color: var(--editor-theme-accent);
background: rgba(52, 211, 153, 0.15);
background: rgb(62 207 120 / 13%);
}
.edgeever-editor[data-editor-theme="wechat-green"]:not([data-editor-theme="default"]) .ProseMirror a {
color: var(--editor-theme-accent);
text-decoration: underline;
text-decoration-color: rgba(7, 193, 96, 0.4);
text-decoration-color: color-mix(in srgb, var(--editor-theme-accent) 42%, transparent);
text-underline-offset: 2px;
}
:root.dark .edgeever-editor[data-editor-theme="wechat-green"]:not([data-editor-theme="default"]) .ProseMirror a {
text-decoration-color: rgba(52, 211, 153, 0.4);
}
.edgeever-editor[data-editor-theme="wechat-green"]:not([data-editor-theme="default"]) .ProseMirror blockquote {
margin: 1.5rem 0;
border-left: 4px solid var(--editor-theme-accent);
+112 -96
View File
@@ -90,25 +90,25 @@ html[data-edgeever-environment="demo"] body::after {
:root {
--background: 80 11% 96%;
--foreground: 224 71% 4%;
--brand-green: #00a82d;
--brand-green-hover: #008f26;
--brand-green-muted: #e6f6ea;
--brand-green-soft: #f0fbf3;
--brand-green-border: #99dcab;
--brand-green-text: #00751f;
--brand-green-rgb: 0 168 45;
--brand-green-hover-rgb: 0 143 38;
--brand-green-50-rgb: 230 246 234;
--brand-green-100-rgb: 204 237 213;
--brand-green-200-rgb: 153 220 171;
--brand-green-300-rgb: 102 202 128;
--brand-green-400-rgb: 51 185 86;
--brand-green-500-rgb: 0 168 45;
--brand-green-600-rgb: 0 143 38;
--brand-green-700-rgb: 0 117 31;
--brand-green-800-rgb: 0 92 25;
--brand-green-900-rgb: 0 67 18;
--brand-green-950-rgb: 0 43 11;
--brand-green: #16a06e;
--brand-green-hover: #12845b;
--brand-green-muted: #def1e9;
--brand-green-soft: #f0f8f4;
--brand-green-border: #b8dfd0;
--brand-green-text: #11694a;
--brand-green-rgb: 22 160 110;
--brand-green-hover-rgb: 18 132 91;
--brand-green-50-rgb: 240 248 244;
--brand-green-100-rgb: 222 241 233;
--brand-green-200-rgb: 184 223 208;
--brand-green-300-rgb: 125 196 170;
--brand-green-400-rgb: 62 174 134;
--brand-green-500-rgb: 22 160 110;
--brand-green-600-rgb: 18 132 91;
--brand-green-700-rgb: 17 105 74;
--brand-green-800-rgb: 17 83 60;
--brand-green-900-rgb: 15 68 50;
--brand-green-950-rgb: 7 37 28;
--workspace-canvas: #f3f5f7;
--workspace-sidebar: #eff1f4;
--workspace-memo-list: #f6f7f9;
@@ -140,24 +140,24 @@ html[data-edgeever-environment="demo"] body::after {
--popover: 0 0% 100%;
--popover-foreground: 224 71% 4%;
--primary: 136 100% 33%;
--primary: 158 76% 36%;
--primary-foreground: 80 11% 96%;
--secondary: 136 55% 94%;
--secondary-foreground: 136 100% 25%;
--secondary: 158 32% 94%;
--secondary-foreground: 158 70% 25%;
--muted: 210 40% 96.1%;
--muted-foreground: 215.4 16.3% 46.9%;
--accent: 136 55% 94%;
--accent-foreground: 136 100% 25%;
--accent: 158 32% 94%;
--accent-foreground: 158 70% 25%;
--destructive: 0 84.2% 60.2%;
--destructive-foreground: 210 40% 98%;
--border: 220 13% 91%;
--input: 220 13% 91%;
--ring: 136 100% 33%;
--ring: 158 76% 36%;
--radius: 0.5rem;
}
@@ -173,42 +173,42 @@ html[data-edgeever-environment="demo"] body::after {
:root.dark {
color-scheme: dark;
--background: 210 26% 9%;
--foreground: 210 20% 89%;
--card: 211 22% 14%;
--card-foreground: 210 20% 89%;
--popover: 211 23% 15%;
--popover-foreground: 210 20% 89%;
--background: 150 8% 8%;
--foreground: 144 9% 88%;
--card: 150 10% 13%;
--card-foreground: 144 9% 88%;
--popover: 150 10% 14%;
--popover-foreground: 144 9% 88%;
--primary: 160 76% 36%;
--primary-foreground: 150 33% 97%;
--secondary: 210 20% 18%;
--secondary: 150 10% 18%;
--secondary-foreground: 160 35% 72%;
--muted: 210 17% 18%;
--muted-foreground: 210 12% 62%;
--accent: 210 18% 20%;
--muted: 150 8% 18%;
--muted-foreground: 145 8% 62%;
--accent: 150 10% 20%;
--accent-foreground: 160 40% 75%;
--destructive: 0 72% 51%;
--destructive-foreground: 210 20% 96%;
--border: 210 16% 24%;
--input: 210 16% 24%;
--destructive-foreground: 144 12% 96%;
--border: 150 10% 23%;
--input: 150 10% 23%;
--ring: 160 76% 36%;
--workspace-canvas: #10161c;
--workspace-sidebar: #131a21;
--workspace-memo-list: #161e26;
--workspace-editor: #19222b;
--workspace-divider: #2a3540;
--workspace-selection: #23323c;
--slate-50-rgb: 16 22 28;
--slate-100-rgb: 23 31 39;
--slate-200-rgb: 39 50 61;
--slate-300-rgb: 56 69 82;
--slate-400-rgb: 80 95 110;
--slate-500-rgb: 136 151 165;
--slate-600-rgb: 174 187 199;
--slate-700-rgb: 200 210 219;
--slate-800-rgb: 220 227 234;
--slate-900-rgb: 235 240 244;
--dark-surface-rgb: 25 34 43;
--workspace-canvas: #101311;
--workspace-sidebar: #121612;
--workspace-memo-list: #151a17;
--workspace-editor: #191e1b;
--workspace-divider: #2a312d;
--workspace-selection: #1d3028;
--slate-50-rgb: 15 20 17;
--slate-100-rgb: 21 28 24;
--slate-200-rgb: 38 49 43;
--slate-300-rgb: 55 68 61;
--slate-400-rgb: 78 92 84;
--slate-500-rgb: 137 150 142;
--slate-600-rgb: 182 195 187;
--slate-700-rgb: 202 212 206;
--slate-800-rgb: 221 229 224;
--slate-900-rgb: 233 239 235;
--dark-surface-rgb: 25 30 27;
--brand-green: #16a06e;
--brand-green-hover: #24b986;
--brand-green-muted: #183a31;
@@ -255,10 +255,8 @@ html.edgeever-mobile-native-editing #root {
body {
margin: 0;
background:
linear-gradient(180deg, rgba(248, 250, 252, 0.98), rgba(241, 245, 249, 0.98)),
#f8fafc;
color: #0f172a;
background: var(--workspace-canvas);
color: hsl(var(--foreground));
overscroll-behavior-x: none;
overscroll-behavior-y: auto;
}
@@ -329,6 +327,12 @@ html[data-edgeever-extension-theme] kbd {
font-family: var(--edgeever-theme-font-mono, ui-monospace, monospace);
}
/* Keep the strongest text neutral instead of falling back to Tailwind's
* blue-black slate-950 literal. Background uses of slate-950 remain intact. */
:root [class~="text-slate-950"] {
color: hsl(var(--foreground));
}
::view-transition-old(root),
::view-transition-new(root) {
animation-duration: 180ms;
@@ -447,6 +451,18 @@ html[data-edgeever-extension-theme] .edgeever-workspace-selection {
background: var(--workspace-selection) !important;
}
/* Curated reading themes should feel like part of the note canvas in dark
* mode. Custom themes keep their explicit background as an intentional user
* choice, while bundled themes contribute typography and semantic colors. */
:root.dark .edgeever-editor[data-editor-theme]:not([data-editor-theme="default"]):not([data-editor-theme="custom"]) {
--editor-theme-bg: var(--workspace-editor);
}
/* Repeated memo rows need less separation than the three workspace panes. */
:root.dark .edgeever-workspace-memo-list article {
border-color: color-mix(in srgb, var(--workspace-divider) 58%, transparent) !important;
}
:root.dark .ProseMirror {
color: hsl(var(--foreground));
}
@@ -594,14 +610,14 @@ textarea {
gap: 0.2rem;
max-width: min(100%, 32rem);
padding: 0.03rem 0.32rem 0.05rem;
border: 1px solid rgb(110 231 183 / 0.9);
border: 1px solid var(--brand-green-border);
border-radius: 0.35rem;
background: linear-gradient(135deg, rgb(236 253 245), rgb(209 250 229));
color: rgb(4 120 87);
background: var(--brand-green-soft);
color: var(--brand-green-text);
font-size: 0.9em;
font-weight: 500;
text-decoration: underline;
text-decoration-color: rgb(52 211 153 / 0.7);
text-decoration-color: color-mix(in srgb, var(--brand-green) 55%, transparent);
text-underline-offset: 2px;
vertical-align: baseline;
cursor: pointer;
@@ -619,25 +635,25 @@ textarea {
.ProseMirror a.edgeever-note-link:hover,
.ProseMirror a[href^="#memo="]:hover {
color: rgb(6 95 70);
border-color: rgb(16 185 129);
background: rgb(167 243 208);
text-decoration-color: rgb(4 120 87);
box-shadow: 0 2px 8px rgb(16 185 129 / 0.2);
color: var(--brand-green-text);
border-color: var(--brand-green);
background: var(--brand-green-muted);
text-decoration-color: var(--brand-green-text);
box-shadow: 0 2px 8px rgb(var(--brand-green-rgb) / 0.1);
}
:root.dark .ProseMirror a.edgeever-note-link,
:root.dark .ProseMirror a[href^="#memo="] {
border-color: rgb(52 211 153 / 0.7);
background: linear-gradient(135deg, rgb(6 78 59), rgb(6 95 70));
color: rgb(110 231 183);
border-color: var(--brand-green-border);
background: var(--brand-green-soft);
color: var(--brand-green-text);
}
:root.dark .ProseMirror a.edgeever-note-link:hover,
:root.dark .ProseMirror a[href^="#memo="]:hover {
border-color: rgb(110 231 183);
background: rgb(6 95 70);
color: rgb(167 243 208);
border-color: var(--brand-green);
background: var(--brand-green-muted);
color: var(--brand-green-text);
}
.ProseMirror a.edgeever-attachment-link,
@@ -647,15 +663,15 @@ textarea {
gap: 0.3rem;
max-width: min(100%, 30rem);
padding: 0.3rem 0.55rem;
border: 1px solid rgb(203 213 225 / 0.9);
border: 1px solid rgb(var(--slate-300-rgb) / 0.9);
border-radius: 0.45rem;
background: linear-gradient(135deg, rgb(248 250 252), rgb(241 245 249));
color: rgb(30 41 59);
background: rgb(var(--slate-50-rgb));
color: rgb(var(--slate-800-rgb));
font-size: 0.75rem;
font-weight: 600;
line-height: 1.2;
text-decoration: none;
box-shadow: 0 1px 2px rgb(15 23 42 / 0.06);
box-shadow: 0 1px 2px rgb(var(--slate-900-rgb) / 0.04);
transition: border-color 160ms ease, background 160ms ease, box-shadow 160ms ease;
}
@@ -672,16 +688,16 @@ textarea {
.ProseMirror a.edgeever-attachment-link:hover,
.ProseMirror a[href*="/api/v1/resources/"]:hover {
border-color: rgb(16 185 129 / 0.55);
background: linear-gradient(135deg, rgb(236 253 245), rgb(240 253 250));
box-shadow: 0 3px 10px rgb(15 118 110 / 0.12);
border-color: rgb(var(--brand-green-rgb) / 0.5);
background: var(--brand-green-soft);
box-shadow: 0 3px 10px rgb(var(--brand-green-rgb) / 0.08);
}
:root.dark .ProseMirror a.edgeever-attachment-link,
:root.dark .ProseMirror a[href*="/api/v1/resources/"] {
border-color: rgb(71 85 105 / 0.8);
background: linear-gradient(135deg, rgb(30 41 59), rgb(15 23 42));
color: rgb(226 232 240);
border-color: rgb(var(--slate-300-rgb) / 0.8);
background: rgb(var(--slate-100-rgb));
color: rgb(var(--slate-800-rgb));
}
.ProseMirror ul,
@@ -1399,7 +1415,7 @@ textarea {
:root.dark .ProseMirror tbody tr:hover td,
:root.dark .markdown-content tbody tr:hover td {
background: #202b3d;
background: #202b25;
}
/* Theme-aware surfaces for legacy white utilities and fixed semantic colors. */
@@ -1460,19 +1476,19 @@ textarea {
:root.dark [class~="bg-slate-900"],
:root.dark [class*="bg-slate-900/"] { background-color: rgb(var(--slate-900-rgb)) !important; }
:root.dark [class~="text-slate-50"] { color: #ebf0f4 !important; }
:root.dark [class~="text-slate-100"] { color: #e2e8ed !important; }
:root.dark [class~="text-slate-200"] { color: #d4dce3 !important; }
:root.dark [class~="text-slate-300"] { color: #c4ced7 !important; }
:root.dark [class~="text-slate-350"] { color: #aab7c3 !important; }
:root.dark [class~="text-slate-50"] { color: #eef2ef !important; }
:root.dark [class~="text-slate-100"] { color: #e5ebe7 !important; }
:root.dark [class~="text-slate-200"] { color: #d7dfda !important; }
:root.dark [class~="text-slate-300"] { color: #c6d0ca !important; }
:root.dark [class~="text-slate-350"] { color: #aebbb3 !important; }
:root.dark [class~="text-slate-400"],
:root.dark [class*="text-slate-400/"] { color: rgb(130 145 161 / var(--dark-utility-alpha, 1)) !important; }
:root.dark [class~="text-slate-500"] { color: #99a7b5 !important; }
:root.dark [class*="text-slate-400/"] { color: rgb(132 148 138 / var(--dark-utility-alpha, 1)) !important; }
:root.dark [class~="text-slate-500"] { color: #9aa9a0 !important; }
:root.dark [class~="text-slate-600"],
:root.dark [class~="text-slate-650"] { color: #b7c2cd !important; }
:root.dark [class~="text-slate-700"] { color: #c8d2db !important; }
:root.dark [class~="text-slate-800"] { color: #dce3ea !important; }
:root.dark [class~="text-slate-900"] { color: #e7edf2 !important; }
:root.dark [class~="text-slate-650"] { color: #b6c3bb !important; }
:root.dark [class~="text-slate-700"] { color: #cad4ce !important; }
:root.dark [class~="text-slate-800"] { color: #dde5e0 !important; }
:root.dark [class~="text-slate-900"] { color: #e9efeb !important; }
:root.dark [class~="border-slate-50"],
:root.dark [class*="border-slate-50/"] { border-color: rgb(var(--slate-50-rgb)) !important; }
@@ -495,8 +495,8 @@ input {
.edgeever-mobile-tiptap-content a:active,
.edgeever-mobile-tiptap-content a:hover {
color: #00a82d;
text-decoration-color: #00a82d;
color: #16a06e;
text-decoration-color: #16a06e;
}
.edgeever-mobile-tiptap-content a.edgeever-attachment-link,
+1 -1
View File
@@ -25,7 +25,7 @@ export default {
],
},
boxShadow: {
panel: "0 1px 2px rgb(15 23 42 / 0.07), 0 14px 34px rgb(36 56 39 / 0.08)",
panel: "0 1px 2px rgb(var(--slate-900-rgb) / 0.05), 0 16px 40px rgb(var(--slate-900-rgb) / 0.07)",
},
borderRadius: {
lg: "var(--radius)",