diff --git a/.github/actions/build-release-assets/package.sh b/.github/actions/build-release-assets/package.sh index 795b36da..69fe24c8 100644 --- a/.github/actions/build-release-assets/package.sh +++ b/.github/actions/build-release-assets/package.sh @@ -19,7 +19,7 @@ bun release ( cd dist tar -zcf theme-github-extras.tar.gz \ - theme-github-pink-*.css theme-github-gitea-*.css theme-github-catppuccin-*.css + theme-github-pink-*.css theme-github-gitea-*.css theme-github-gruvbox-*.css theme-github-catppuccin-*.css ) # Template assets must extract beside templates under the documented custom root. diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index d0cdcc1e..83d29456 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -4,8 +4,8 @@ Gitea GitHub Theme is a build-time CSS generation framework that produces static `.css` theme files for the [Gitea](https://github.com/go-gitea/gitea) self-hosted code platform. The project takes design tokens and color -definitions as input, transforms them through a layered pipeline, and outputs 30+ minified CSS files covering 7 theme -series (default, colorblind, tritanopia, pink, gitea, catppuccin, high-contrast). +definitions as input, transforms them through a layered pipeline, and outputs 30+ minified CSS files covering 8 theme +series (default, colorblind, tritanopia, pink, gitea, gruvbox, catppuccin, high-contrast). **Core principle:** Full type safety and CSS-in-JS developer experience at build time, pure static CSS delivered to the browser with zero runtime overhead. diff --git a/README.md b/README.md index 46e14b13..bcc1dcd6 100644 --- a/README.md +++ b/README.md @@ -51,8 +51,8 @@ Install the resulting files using your Forgejo data directory (commonly `data/gi 1. Extract `theme-github-base.tar.gz`, then place the contained `*.css` files in `/public/assets/css` (create the directory if necessary). This archive contains the standard light, dark, auto, and soft-dark themes. 2. Optionally extract `theme-github-accessibility.tar.gz` for colorblind and high-contrast themes, or - `theme-github-extras.tar.gz` for Pink, Gitea-compatible, and Catppuccin themes. Place their CSS files in the same - `/public/assets/css` directory. + `theme-github-extras.tar.gz` for Pink, Gitea-compatible, Gruvbox, and Catppuccin themes. Place their CSS files in the + same `/public/assets/css` directory. 3. Extract `theme-github-fonts.tar.gz` into `/public`. This supplies the Mona Sans variable font; its SIL Open Font License is included beside the font. 4. Extract the optional `theme-github-templates.tar.gz` archive into ``. It includes the templates and @@ -188,6 +188,14 @@ THEMES = github-gitea-auto, github-gitea-light, github-gitea-dark +### Gruvbox Themes + +```ini +THEMES = github-gruvbox-auto, github-gruvbox-light, github-gruvbox-dark +``` + +The auto theme follows the system color-scheme preference and selects the matching Gruvbox light or dark palette. + ### Catppuccin Themes ```ini diff --git a/dev/docker-compose.yml b/dev/docker-compose.yml index 4eb70c4a..ea5a8c41 100644 --- a/dev/docker-compose.yml +++ b/dev/docker-compose.yml @@ -25,7 +25,7 @@ services: FORGEJO__server__LANDING_PAGE: login FORGEJO__security__INSTALL_LOCK: "true" FORGEJO__ui__DEFAULT_THEME: github-auto - FORGEJO__ui__THEMES: "${FORGEJO_PREVIEW_THEMES:-github-auto,github-catppuccin-auto,github-catppuccin-frappe,github-catppuccin-latte,github-catppuccin-macchiato,github-catppuccin-mocha,github-colorblind-auto,github-colorblind-dark,github-colorblind-light,github-dark,github-gitea-auto,github-gitea-dark,github-gitea-light,github-high-contrast-auto,github-high-contrast-colorblind-auto,github-high-contrast-colorblind-dark,github-high-contrast-colorblind-light,github-high-contrast-dark,github-high-contrast-light,github-high-contrast-soft-dark,github-high-contrast-tritanopia-auto,github-high-contrast-tritanopia-dark,github-high-contrast-tritanopia-light,github-light,github-pink-auto,github-pink-dark,github-pink-light,github-pink-soft-dark,github-soft-dark,github-tritanopia-auto,github-tritanopia-dark,github-tritanopia-light}" + FORGEJO__ui__THEMES: "${FORGEJO_PREVIEW_THEMES:-github-auto,github-catppuccin-auto,github-catppuccin-frappe,github-catppuccin-latte,github-catppuccin-macchiato,github-catppuccin-mocha,github-colorblind-auto,github-colorblind-dark,github-colorblind-light,github-dark,github-gitea-auto,github-gitea-dark,github-gitea-light,github-gruvbox-auto,github-gruvbox-dark,github-gruvbox-light,github-high-contrast-auto,github-high-contrast-colorblind-auto,github-high-contrast-colorblind-dark,github-high-contrast-colorblind-light,github-high-contrast-dark,github-high-contrast-light,github-high-contrast-soft-dark,github-high-contrast-tritanopia-auto,github-high-contrast-tritanopia-dark,github-high-contrast-tritanopia-light,github-light,github-pink-auto,github-pink-dark,github-pink-light,github-pink-soft-dark,github-soft-dark,github-tritanopia-auto,github-tritanopia-dark,github-tritanopia-light}" ports: - "3000:3000" - "2222:22" diff --git a/src/build-output.test.ts b/src/build-output.test.ts index adcb2a08..d1a032d2 100644 --- a/src/build-output.test.ts +++ b/src/build-output.test.ts @@ -56,6 +56,10 @@ const EXPECTED_THEME_FILES = [ `${PREFIX}gitea-dark.css`, `${PREFIX}gitea-light.css`, `${PREFIX}gitea-auto.css`, + // gruvbox + `${PREFIX}gruvbox-dark.css`, + `${PREFIX}gruvbox-light.css`, + `${PREFIX}gruvbox-auto.css`, // catppuccin `${PREFIX}catppuccin-mocha.css`, `${PREFIX}catppuccin-latte.css`, @@ -116,6 +120,66 @@ describe("构建产物基本验证", () => { }); }); +describe("Gruvbox 语法高亮 CSS", () => { + for (const mode of ["light", "dark"]) { + it(`${mode} includes Chroma and CodeMirror rules`, () => { + const css = fs.readFileSync(path.join(DIST_DIR, `${PREFIX}gruvbox-${mode}.css`), "utf-8"); + + expect(css).toContain(".chroma .k{"); + expect(css).toContain(".codemirror-container .tok-keyword{"); + }); + } + + it("dark code blocks use a black overlay", () => { + const css = fs.readFileSync(path.join(DIST_DIR, `${PREFIX}gruvbox-dark.css`), "utf-8"); + + expect(css).toContain("--color-markup-code-block:#00000020"); + }); + + it("light code blocks use the Gruvbox light surface", () => { + const css = fs.readFileSync(path.join(DIST_DIR, `${PREFIX}gruvbox-light.css`), "utf-8"); + + expect(css).toContain("--color-markup-code-block:#ebdbb2"); + }); + + it("uses each mode's base background for the menu", () => { + const lightCss = fs.readFileSync(path.join(DIST_DIR, `${PREFIX}gruvbox-light.css`), "utf-8"); + const darkCss = fs.readFileSync(path.join(DIST_DIR, `${PREFIX}gruvbox-dark.css`), "utf-8"); + + expect(lightCss).toContain("--color-menu:#fbf1c7"); + expect(darkCss).toContain("--color-menu:#282828"); + }); + + for (const [mode, background] of [ + ["light", "#fbf1c7"], + ["dark", "#282828"], + ]) { + it(`${mode} color-light matches color-body`, () => { + const css = fs.readFileSync(path.join(DIST_DIR, `${PREFIX}gruvbox-${mode}.css`), "utf-8"); + + expect(css).toContain(`--color-light:${background}`); + expect(css).toContain(`--color-body:${background}`); + }); + } + + it("uses bg2 for the RSS tooltip background in both modes", () => { + const lightCss = fs.readFileSync(path.join(DIST_DIR, `${PREFIX}gruvbox-light.css`), "utf-8"); + const darkCss = fs.readFileSync(path.join(DIST_DIR, `${PREFIX}gruvbox-dark.css`), "utf-8"); + + expect(lightCss).toContain("--color-tooltip-bg:#d5c4a1"); + expect(darkCss).toContain("--color-tooltip-bg:#504945"); + }); + + it("uses the requested shadow color in light mode only", () => { + const lightCss = fs.readFileSync(path.join(DIST_DIR, `${PREFIX}gruvbox-light.css`), "utf-8"); + const darkCss = fs.readFileSync(path.join(DIST_DIR, `${PREFIX}gruvbox-dark.css`), "utf-8"); + + expect(lightCss).toContain("--color-shadow:#25292e0a"); + expect(darkCss).toContain("--color-shadow:#1d202158"); + }); +}); + +it("ships page stylesheets unchanged with valid CSS and a template reference", async () => { it("ships component stylesheets unchanged with valid CSS and a template reference", async () => { const { transform } = await import("lightningcss"); const templates = fs @@ -710,8 +774,8 @@ const SIZE_LIMITS = { AUTO_MAX: 1 * 1024, // 1 KB /** 非 auto 主题包含完整主题变量 + 公共样式,当前约 207-216 KB */ SOLID_MAX: 220 * 1024, // 220 KB (给予主题页面样式增长空间) - /** 所有 CSS 文件总大小上限,当前约 2.6 MB */ - TOTAL_MAX: 5 * 1024 * 1024, // 5 MB + /** 所有 CSS 文件总大小上限,当前约 5.2 MB */ + TOTAL_MAX: 6 * 1024 * 1024, // 6 MB } as const; describe("CSS 文件大小限制", () => { @@ -743,7 +807,7 @@ describe("CSS 文件大小限制", () => { ).toEqual([]); }); - it("所有 CSS 文件总大小 < 5MB", () => { + it("所有 CSS 文件总大小 < 6MB", () => { let totalSize = 0; for (const fileName of EXPECTED_THEME_FILES) { totalSize += fs.statSync(path.join(DIST_DIR, fileName)).size; diff --git a/src/core/theme.ts b/src/core/theme.ts index b0a71843..6a7e668f 100644 --- a/src/core/theme.ts +++ b/src/core/theme.ts @@ -22,7 +22,7 @@ import pkg from "../../package.json"; import type { GiteaColor, GitHubColor, ThemeColor } from "../palette"; import { gitea2ThemeVars, github2ThemeColor, primer2Chroma, primer2CodeMirror, theme2ThemeVars } from "../palette"; import selectors from "../selectors"; -import { otherThemeVars, syntaxVars, themeVars, type Syntax } from "../types"; +import { otherThemeVars, syntaxVars, themeVars, type Chroma, type CodeMirror, type Syntax } from "../types"; import { createChroma } from "./chroma"; import { createCodeMirror } from "./codemirror"; import type { MapLeafNodes } from "./types"; @@ -39,7 +39,13 @@ function getThemeVersion(): string { export type ThemeVars = { isDarkTheme: boolean } & MapLeafNodes; export type ThemeColors = | { colorType: "github"; themeColor: GitHubColor } - | { colorType: "gitea"; themeColor: GiteaColor; syntaxColor: Syntax } + | { + colorType: "gitea"; + themeColor: GiteaColor; + syntaxColor: Syntax; + chromaColor?: Chroma; + codeMirrorColor?: CodeMirror; + } | { colorType: "theme"; themeColor: ThemeColor; syntaxColor: Syntax }; export type Theme = { isDarkTheme: boolean; @@ -134,5 +140,7 @@ export function createTheme(theme: Theme): void { createCodeMirror(primer2CodeMirror(theme.themeColor)); } else { createGlobalTheme(selectors.root, syntaxVars, theme.syntaxColor); + if (theme.colorType === "gitea" && theme.chromaColor) createChroma(theme.chromaColor); + if (theme.colorType === "gitea" && theme.codeMirrorColor) createCodeMirror(theme.codeMirrorColor); } } diff --git a/src/index.ts b/src/index.ts index 32da3c75..a8575792 100644 --- a/src/index.ts +++ b/src/index.ts @@ -27,12 +27,15 @@ export { display2GitHubColor, github2ThemeColor, primer2GitHubColor, + primer2Chroma, + primer2CodeMirror, } from "./palette"; export type { DisplayColor, GitHubColor, GiteaColor, ThemeColor } from "./palette"; export { chromaVars, customThemeVars, otherThemeVars, syntaxVars, themeVars } from "./types"; export type { Ansi, Chroma, + CodeMirror, Console, Diff, GitHub, diff --git a/theme.config.ts b/theme.config.ts index e4ab3470..ed3bd49b 100644 --- a/theme.config.ts +++ b/theme.config.ts @@ -79,6 +79,13 @@ export default defineThemeConfig({ light: { theme: themes.gitea.light }, }, }, + { + themeSeriesName: "gruvbox", + themes: { + dark: { theme: themes.gruvbox.dark }, + light: { theme: themes.gruvbox.light }, + }, + }, { themeSeriesName: "catppuccin", themes: { diff --git a/themes/gruvbox.ts b/themes/gruvbox.ts new file mode 100644 index 00000000..ab1bb7c9 --- /dev/null +++ b/themes/gruvbox.ts @@ -0,0 +1,484 @@ +import type { Chroma, CodeMirror, GiteaColor, Syntax } from "@lutinglt/gitea-github-theme/core"; +import { deepOverride, defineTheme, primer2Chroma, primer2CodeMirror } from "@lutinglt/gitea-github-theme/core"; +import { default as primer } from "@lutinglt/gitea-github-theme/primer"; +import { giteaDarkColor, giteaDarkSyntax } from "./giteaDark"; +import { giteaLightColor, giteaLightSyntax } from "./giteaLight"; + +type GruvboxPalette = { + bg0: string; + bg1: string; + bg2: string; + bg3: string; + bg4: string; + fg0: string; + fg1: string; + fg2: string; + fg3: string; + red: string; + redBright: string; + green: string; + greenBright: string; + yellow: string; + yellowBright: string; + blue: string; + blueBright: string; + purple: string; + purpleBright: string; + aqua: string; + aquaBright: string; + orange: string; + orangeBright: string; +}; + +function alphaScale(color: string) { + return { + num10: `${color}19`, + num20: `${color}33`, + num30: `${color}4b`, + num40: `${color}66`, + num50: `${color}80`, + num60: `${color}99`, + num70: `${color}b3`, + num80: `${color}cc`, + num90: `${color}e1`, + }; +} + +function createGruvboxColor(base: GiteaColor, palette: GruvboxPalette): GiteaColor { + const { + bg0, + bg1, + bg2, + bg3, + bg4, + fg0, + fg1, + fg2, + fg3, + red, + redBright, + green, + greenBright, + yellow, + yellowBright, + blue, + blueBright, + purple, + purpleBright, + aqua, + aquaBright, + orange, + orangeBright, + } = palette; + + return deepOverride(base, { + primary: { + self: blue, + contrast: fg0, + dark: { num1: blue, num2: blueBright, num3: aqua, num4: aquaBright, num5: fg3, num6: fg2, num7: fg1 }, + light: { num1: blueBright, num2: blue, num3: aquaBright, num4: aqua, num5: fg3, num6: fg2, num7: fg1 }, + alpha: alphaScale(blue), + hover: blueBright, + active: aqua, + }, + secondary: { + self: bg2, + dark: { + num1: bg3, + num2: bg4, + num3: fg3, + num4: fg2, + num5: fg1, + num6: fg0, + num7: bg0, + num8: bg0, + num9: bg1, + num10: bg2, + num11: bg3, + num12: bg4, + num13: fg3, + }, + light: { num1: bg1, num2: bg0, num3: fg0, num4: fg1 }, + alpha: alphaScale(bg2), + button: bg1, + hover: bg2, + active: bg3, + }, + named: { + red: { self: red, light: redBright, dark: { num1: red, num2: redBright } }, + orange: { self: orange, light: orangeBright, dark: { num1: orange, num2: orangeBright } }, + yellow: { self: yellow, light: yellowBright, dark: { num1: yellow, num2: yellowBright } }, + olive: { self: yellow, light: yellowBright, dark: { num1: yellow, num2: yellowBright } }, + green: { self: green, light: greenBright, dark: { num1: green, num2: greenBright } }, + teal: { self: aqua, light: aquaBright, dark: { num1: aqua, num2: aquaBright } }, + blue: { self: blue, light: blueBright, dark: { num1: blue, num2: blueBright } }, + violet: { self: purple, light: purpleBright, dark: { num1: purple, num2: purpleBright } }, + purple: { self: purple, light: purpleBright, dark: { num1: purple, num2: purpleBright } }, + pink: { self: purple, light: purpleBright, dark: { num1: purple, num2: purpleBright } }, + brown: { self: orange, light: orangeBright, dark: { num1: orange, num2: orangeBright } }, + black: { self: bg0, light: bg2, dark: { num1: bg1, num2: bg2 } }, + grey: { self: fg2, light: fg3 }, + gold: yellow, + white: fg0, + }, + message: { + error: { bg: { self: `${red}20`, active: `${red}40`, hover: `${red}30` }, border: red, text: redBright }, + success: { bg: `${green}20`, border: green, text: greenBright }, + warning: { bg: `${yellow}20`, border: yellow, text: yellowBright }, + info: { bg: `${blue}20`, border: blue, text: blueBright }, + priority: { bg: `${purple}20`, border: purple, text: purpleBright }, + }, + ansi: { + black: bg0, + red, + green, + yellow, + blue, + magenta: purple, + cyan: aqua, + white: fg2, + bright: { + black: bg4, + red: redBright, + green: greenBright, + yellow: yellowBright, + blue: blueBright, + magenta: purpleBright, + cyan: aquaBright, + white: fg0, + }, + }, + console: { + fg: { self: fg1, subtle: fg3 }, + bg: bg0, + border: bg2, + activeBg: bg2, + hoverBg: bg1, + menu: { bg: bg1, border: bg2 }, + link: blue, + }, + diff: { + added: { + fg: greenBright, + linenum: { bg: `${green}30` }, + row: { bg: `${green}20`, border: `${green}40` }, + word: { bg: `${green}50` }, + }, + moved: { row: { bg: `${blue}20`, border: `${blue}50` } }, + removed: { + fg: redBright, + linenum: { bg: `${red}30` }, + row: { bg: `${red}20`, border: `${red}40` }, + word: { bg: `${red}50` }, + }, + inactive: bg1, + }, + other: { + logo: green, + body: bg0, + footer: bg1, + timeline: bg2, + box: { header: bg1, body: { self: bg0, highlight: bg1 } }, + text: { self: fg1, light: { self: fg2, num1: fg2, num2: fg3, num3: fg3 }, dark: fg0 }, + input: { text: fg1, background: bg0, toggleBackground: bg3, border: bg3 }, + light: { self: bg0, border: `${fg0}28` }, + hover: { self: `${fg0}19`, opaque: bg1 }, + active: `${fg0}24`, + menu: bg0, + card: bg1, + markup: { tableRow: `${fg0}0f`, code: { block: base.isDarkTheme ? "#00000020" : bg1, inline: bg1 } }, + button: bg1, + codeBg: bg0, + shadow: { self: base.isDarkTheme ? "#1d202158" : "#25292e0a", opaque: "#1d2021" }, + secondaryBg: bg1, + expandButton: bg2, + placeholderText: fg3, + editor: { lineHighlight: `${bg2}66`, selection: `${blue}50` }, + projectColumnBg: bg1, + caret: fg1, + reaction: { bg: `${fg0}12`, hoverBg: `${blue}28`, activeBg: `${blue}40` }, + tooltip: { text: fg0, bg: bg2 }, + nav: { bg: bg0, hoverBg: bg1, text: fg1 }, + secondaryNavBg: bg1, + label: { text: fg1, bg: `${fg3}4b`, hoverBg: `${fg3}a0`, activeBg: `${fg3}ff` }, + accent: blue, + smallAccent: aqua, + highlight: { fg: yellowBright, bg: `${yellow}30` }, + overlayBackdrop: "#1d2021c0", + danger: red, + transparency: { grid: { light: bg1, dark: bg0 } }, + workflowEdgeHover: blue, + }, + series16: { + num0: red, + num1: green, + num2: yellow, + num3: blue, + num4: purple, + num5: aqua, + num6: orange, + num7: fg2, + num8: redBright, + num9: greenBright, + num10: yellowBright, + num11: blueBright, + num12: purpleBright, + num13: aquaBright, + num14: orangeBright, + num15: fg0, + }, + }); +} + +function createGruvboxSyntax(base: Syntax, palette: GruvboxPalette): Syntax { + return deepOverride(base, { + keyword: palette.redBright, + bool: palette.purpleBright, + control: palette.red, + name: palette.blueBright, + type: palette.yellowBright, + number: palette.purpleBright, + operator: palette.orangeBright, + regexp: palette.redBright, + string: palette.greenBright, + comment: palette.fg3, + invalid: palette.redBright, + link: palette.blueBright, + tag: palette.redBright, + attribute: palette.yellowBright, + property: palette.aquaBright, + variable: palette.blueBright, + stringSpecial: palette.aquaBright, + escape: palette.purpleBright, + entity: palette.orangeBright, + preproc: palette.aquaBright, + preprocFile: palette.greenBright, + decorator: palette.purpleBright, + namespace: palette.fg1, + namePseudo: palette.yellowBright, + commentSpecial: palette.orangeBright, + text: palette.fg1, + textAlt: palette.fg2, + punctuation: palette.fg2, + whitespace: palette.fg3, + diffFg: palette.fg0, + deletedBg: `${palette.red}50`, + insertedBg: `${palette.green}50`, + emph: palette.orangeBright, + strong: palette.yellowBright, + heading: palette.redBright, + subheading: palette.greenBright, + output: palette.fg3, + prompt: palette.blueBright, + traceback: palette.redBright, + matchingBracketBg: `${palette.aqua}48`, + nonmatchingBracketBg: `${palette.red}48`, + }); +} + +function createGruvboxChroma(base: Chroma, palette: GruvboxPalette): Chroma { + return deepOverride(base, { + textWhiteSpace: palette.fg2, + err: palette.redBright, + keyword: { + self: palette.redBright, + constant: palette.purpleBright, + declaration: palette.redBright, + namespace: palette.aquaBright, + pseudo: palette.purpleBright, + reserved: palette.redBright, + type: palette.yellowBright, + }, + name: { + self: palette.fg1, + attribute: palette.blueBright, + builtin: palette.purpleBright, + builtinPseudo: palette.fg1, + class: palette.yellowBright, + constant: palette.purpleBright, + decorator: palette.aquaBright, + entity: palette.orangeBright, + exception: palette.redBright, + function: palette.greenBright, + functionMagic: palette.aquaBright, + label: palette.yellowBright, + namespace: palette.aquaBright, + other: palette.fg1, + property: palette.aquaBright, + tag: palette.redBright, + variable: palette.blueBright, + variableClass: palette.blueBright, + variableGlobal: palette.blueBright, + variableInstance: palette.blueBright, + variableMagic: palette.purpleBright, + }, + literal: { self: palette.purpleBright, date: palette.purpleBright }, + string: { + self: palette.greenBright, + affix: palette.redBright, + backtick: palette.aquaBright, + char: palette.greenBright, + delimiter: palette.greenBright, + doc: palette.fg3, + double: palette.greenBright, + escape: palette.aquaBright, + heredoc: palette.greenBright, + interpol: palette.aquaBright, + other: palette.greenBright, + regex: palette.redBright, + single: palette.greenBright, + symbol: palette.purpleBright, + }, + number: { + self: palette.purpleBright, + bin: palette.purpleBright, + float: palette.purpleBright, + hex: palette.purpleBright, + integer: palette.purpleBright, + integerLong: palette.purpleBright, + oct: palette.purpleBright, + }, + operator: { self: palette.orangeBright, word: palette.redBright, reserved: palette.redBright }, + punctuation: palette.fg2, + comment: { + self: palette.fg3, + hashbang: palette.fg3, + multiline: palette.fg3, + preproc: palette.aquaBright, + preprocFile: palette.greenBright, + single: palette.fg3, + special: palette.orangeBright, + }, + generic: { + self: palette.fg1, + deleted: palette.redBright, + emph: palette.orangeBright, + error: palette.redBright, + heading: palette.yellowBright, + inserted: palette.greenBright, + output: palette.fg2, + prompt: palette.blueBright, + strong: palette.fg1, + subheading: palette.greenBright, + traceback: palette.redBright, + underline: palette.fg1, + }, + }); +} + +function createGruvboxCodeMirror(base: CodeMirror, palette: GruvboxPalette): CodeMirror { + return deepOverride(base, { + token: { + keyword: palette.redBright, + atom: palette.purpleBright, + bool: palette.purpleBright, + variableName: palette.blueBright, + variableName2: palette.fg1, + propertyName: palette.aquaBright, + typeName: palette.yellowBright, + className: palette.yellowBright, + namespace: palette.aquaBright, + macroName: palette.aquaBright, + labelName: palette.yellowBright, + number: palette.purpleBright, + string: palette.greenBright, + string2: palette.aquaBright, + operator: palette.orangeBright, + punctuation: palette.fg2, + comment: palette.fg3, + meta: palette.aquaBright, + invalid: palette.redBright, + link: palette.blueBright, + heading: palette.yellowBright, + emphasis: palette.orangeBright, + strong: palette.fg1, + inserted: palette.greenBright, + deleted: palette.redBright, + }, + language: { + json: palette.aquaBright, + json5: palette.aquaBright, + yaml: palette.aquaBright, + css: palette.aquaBright, + html: palette.blueBright, + xml: palette.blueBright, + }, + }); +} + +const darkPalette: GruvboxPalette = { + bg0: "#282828", + bg1: "#3c3836", + bg2: "#504945", + bg3: "#665c54", + bg4: "#7c6f64", + fg0: "#fbf1c7", + fg1: "#ebdbb2", + fg2: "#d5c4a1", + fg3: "#bdae93", + red: "#cc241d", + redBright: "#fb4934", + green: "#98971a", + greenBright: "#b8bb26", + yellow: "#d79921", + yellowBright: "#fabd2f", + blue: "#458588", + blueBright: "#83a598", + purple: "#b16286", + purpleBright: "#d3869b", + aqua: "#689d6a", + aquaBright: "#8ec07c", + orange: "#d65d0e", + orangeBright: "#fe8019", +}; + +const lightPalette: GruvboxPalette = { + bg0: "#fbf1c7", + bg1: "#ebdbb2", + bg2: "#d5c4a1", + bg3: "#bdae93", + bg4: "#a89984", + fg0: "#282828", + fg1: "#3c3836", + fg2: "#504945", + fg3: "#665c54", + red: "#cc241d", + redBright: "#9d0006", + green: "#98971a", + greenBright: "#79740e", + yellow: "#d79921", + yellowBright: "#b57614", + blue: "#458588", + blueBright: "#076678", + purple: "#b16286", + purpleBright: "#8f3f71", + aqua: "#689d6a", + aquaBright: "#427b58", + orange: "#d65d0e", + orangeBright: "#af3a03", +}; + +export const gruvboxDarkColor = createGruvboxColor(giteaDarkColor, darkPalette); +export const gruvboxLightColor = createGruvboxColor(giteaLightColor, lightPalette); +export const gruvboxDarkSyntax = createGruvboxSyntax(giteaDarkSyntax, darkPalette); +export const gruvboxLightSyntax = createGruvboxSyntax(giteaLightSyntax, lightPalette); +export const gruvboxDarkChroma = createGruvboxChroma(primer2Chroma(primer.dark), darkPalette); +export const gruvboxLightChroma = createGruvboxChroma(primer2Chroma(primer.light), lightPalette); +export const gruvboxDarkCodeMirror = createGruvboxCodeMirror(primer2CodeMirror(primer.dark), darkPalette); +export const gruvboxLightCodeMirror = createGruvboxCodeMirror(primer2CodeMirror(primer.light), lightPalette); + +export const gruvboxDarkTheme = defineTheme({ + colorType: "gitea", + themeColor: gruvboxDarkColor, + syntaxColor: gruvboxDarkSyntax, + chromaColor: gruvboxDarkChroma, + codeMirrorColor: gruvboxDarkCodeMirror, +}); + +export const gruvboxLightTheme = defineTheme({ + colorType: "gitea", + themeColor: gruvboxLightColor, + syntaxColor: gruvboxLightSyntax, + chromaColor: gruvboxLightChroma, + codeMirrorColor: gruvboxLightCodeMirror, +}); diff --git a/themes/index.ts b/themes/index.ts index 4c5f88c3..9b3f130b 100644 --- a/themes/index.ts +++ b/themes/index.ts @@ -25,6 +25,7 @@ import { } from "./catppuccin"; import { giteaDarkColor, giteaDarkTheme } from "./giteaDark"; import { giteaLightColor, giteaLightTheme } from "./giteaLight"; +import { gruvboxDarkTheme, gruvboxLightTheme } from "./gruvbox"; import { githubColorblindDarkTheme, githubColorblindLightTheme, @@ -86,6 +87,10 @@ export default { dark: giteaDarkTheme, light: giteaLightTheme, }, + gruvbox: { + dark: gruvboxDarkTheme, + light: gruvboxLightTheme, + }, catppuccin: { latte: catppuccinLatteTheme, frappe: catppuccinFrappeTheme,