From f6096d08156425fab7a54958ed1a2b8421d1e953 Mon Sep 17 00:00:00 2001 From: "kettu-builder[bot]" <347639528+kettu-builder[bot]@users.noreply.github.com> Date: Fri, 30 Oct 2026 23:33:48 -0700 Subject: [PATCH] feat: add Paper with its fonts Kettu's built-in theme is now neutral, so its former warm look moves here. Theme files may name a bundled face per kind of text, which the index script now checks, and it knows the agent-pi token. --- README.md | 4 +- index.json | 35 ++++++++ scripts/build-index.mjs | 29 ++++++- themes/paper.json | 183 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 249 insertions(+), 2 deletions(-) create mode 100644 themes/paper.json diff --git a/README.md b/README.md index 9c26e3e..617723e 100644 --- a/README.md +++ b/README.md @@ -7,11 +7,12 @@ Color themes for [Kettu](https://github.com/KettuAI/Kettu). Kettu lists them in | [Catppuccin](themes/catppuccin.json) | Latte and Mocha | [Catppuccin](https://github.com/catppuccin/catppuccin) | MIT | | [Gruvbox](themes/gruvbox.json) | Light and dark | [Pavel Pertsev](https://github.com/morhetz/gruvbox) | MIT | | [Nord](themes/nord.json) | Light and dark | [Arctic Ice Studio](https://github.com/nordtheme/nord) | MIT | +| [Paper](themes/paper.json) | Light and dark | [Kettu](https://github.com/KettuAI/Kettu) | MIT | | [Rosé Pine](themes/rose-pine.json) | Dawn and Main | [Rosé Pine](https://github.com/rose-pine/rose-pine-theme) | MIT | | [Solarized](themes/solarized.json) | Light and dark | [Ethan Schoonover](https://github.com/altercation/solarized) | MIT | | [Tokyo Night](themes/tokyo-night.json) | Light and Night | [enkia](https://github.com/tokyo-night/tokyo-night-vscode-theme) | MIT | -Each keeps its palette's published colors. Where one is too faint for text on its own surfaces, under 4.5:1, the theme uses the same hue a shade lighter or darker for text. Solarized keeps its body text tone, so Kettu marks its tile with a contrast warning. +Paper is Kettu's former built-in look: warm paper colors with Source Serif 4 for prose and headings and Inter for the interface. The others each keep their palette's published colors. Where one is too faint for text on its own surfaces, under 4.5:1, the theme uses the same hue a shade lighter or darker for text. Solarized keeps its body text tone, so Kettu marks its tile with a contrast warning. ## Adding a theme @@ -42,6 +43,7 @@ A theme here needs a license that lets anyone use and share it, and names the pa ``` - A color a variant leaves out keeps Kettu's own. +- `fonts`, which both variants share, may name a face for `ui`, `prose` (the agent's replies and headings) and `code`: `system`, `inter`, `source-serif-4` or `jetbrains-mono`, and for `code` only `system` or `jetbrains-mono`. Kettu ships each of them; a role left out keeps Kettu's own. - Colors are hex, `rgb()`, `hsl()`, `oklch()`, `oklab()` or `transparent`; nothing else, such as `var()` or color names. - A file is at most 256 KB. diff --git a/index.json b/index.json index dcebc26..7829f69 100644 --- a/index.json +++ b/index.json @@ -106,6 +106,41 @@ } } }, + { + "id": "paper", + "name": "Paper", + "author": "Kettu", + "version": "1.0.0", + "license": "MIT", + "file": "themes/paper.json", + "sha256": "4b0680014dc9a51acb67353dfca6e61ee3e6038d22caf6bb10c2181c19480bb1", + "preview": { + "light": { + "bg-app": "#fdfcf9", + "bg-sidebar": "#efede6", + "bg-surface": "#ffffff", + "bg-subtle": "#f2f0e9", + "bg-selected": "rgb(28 27 24 / 7.5%)", + "border-default": "rgb(28 27 24 / 12%)", + "border-subtle": "rgb(28 27 24 / 7.5%)", + "border-strong": "rgb(28 27 24 / 20%)", + "primary-from": "#2e2d29", + "primary-to": "#1c1b18" + }, + "dark": { + "bg-app": "#1c1b18", + "bg-sidebar": "#121110", + "bg-surface": "#262521", + "bg-subtle": "#2a2925", + "bg-selected": "rgb(244 243 238 / 9%)", + "border-default": "rgb(244 243 238 / 10%)", + "border-subtle": "rgb(244 243 238 / 6.5%)", + "border-strong": "rgb(244 243 238 / 18%)", + "primary-from": "#f4f3ee", + "primary-to": "#e3e1d9" + } + } + }, { "id": "rose-pine", "name": "Rosé Pine", diff --git a/scripts/build-index.mjs b/scripts/build-index.mjs index 07239eb..7fad8db 100644 --- a/scripts/build-index.mjs +++ b/scripts/build-index.mjs @@ -66,6 +66,7 @@ const TOKENS = new Set([ 'mark', 'checker', 'agent-opencode', + 'agent-pi', 'agent-other', 'heat-hue', 'heat-0', @@ -112,7 +113,23 @@ const PREVIEW_TOKENS = [ 'primary-to', ] -const FIELDS = ['kettuTheme', 'name', 'author', 'version', 'license', 'source', 'light', 'dark'] +const FIELDS = [ + 'kettuTheme', + 'name', + 'author', + 'version', + 'license', + 'source', + 'fonts', + 'light', + 'dark', +] +// The faces a theme may choose for each kind of text, all of them shipped with Kettu. +const FONTS = { + ui: ['system', 'inter', 'source-serif-4', 'jetbrains-mono'], + prose: ['system', 'inter', 'source-serif-4', 'jetbrains-mono'], + code: ['system', 'jetbrains-mono'], +} const VERSION = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/ const CODE_THEME = /^[a-z0-9-]{1,64}$/ const CONTROL = /[\u0000-\u001f\u007f]/ @@ -203,6 +220,16 @@ function checkTheme(fileName, raw) { throw new Error('has a source that is not an https URL') } + if (raw.fonts !== undefined) { + if (!isRecord(raw.fonts)) throw new Error('fonts is not an object') + for (const [role, face] of Object.entries(raw.fonts)) { + if (!Object.hasOwn(FONTS, role)) throw new Error(`fonts.${role} is not text Kettu sets`) + if (!FONTS[role].includes(face)) { + throw new Error(`fonts.${role} is not one of ${FONTS[role].join(', ')}`) + } + } + } + const preview = {} for (const mode of MODES) { const variant = raw[mode] diff --git a/themes/paper.json b/themes/paper.json new file mode 100644 index 0000000..56946d7 --- /dev/null +++ b/themes/paper.json @@ -0,0 +1,183 @@ +{ + "kettuTheme": 1, + "name": "Paper", + "author": "Kettu", + "version": "1.0.0", + "license": "MIT", + "source": "https://github.com/KettuAI/Kettu", + "fonts": { + "ui": "inter", + "prose": "source-serif-4", + "code": "jetbrains-mono" + }, + "light": { + "colors": { + "bg-app": "#fdfcf9", + "bg-sidebar": "#efede6", + "bg-surface": "#ffffff", + "bg-subtle": "#f2f0e9", + "bg-code-inline": "rgb(28 27 24 / 6.5%)", + "bg-code-block": "#f7f5ef", + "bg-hover": "rgb(28 27 24 / 5%)", + "bg-selected": "rgb(28 27 24 / 7.5%)", + "queue-back": "#f2f0ea", + "border-default": "rgb(28 27 24 / 12%)", + "border-subtle": "rgb(28 27 24 / 7.5%)", + "border-strong": "rgb(28 27 24 / 20%)", + "text-primary": "#1c1b18", + "text-secondary": "#55534c", + "text-tertiary": "#68655d", + "text-placeholder": "#6e6b63", + "text-disabled": "#a9a69d", + "icon-default": "#68655d", + "attention": "#a3441c", + "attention-dot": "#d2622f", + "danger": "#ac3b2d", + "success": "#2e7550", + "merged": "#6f4c8e", + "info": "#2d5e91", + "warning": "#8a6212", + "link": "#2d5e91", + "link-hover": "#234a73", + "danger-tint": "#fbf6f6", + "danger-border": "#eed8d5", + "success-tint": "#f2f7f5", + "info-tint": "#f2f5f8", + "warning-tint": "#f8f6f1", + "info-tint-hover": "#eaeff4", + "focus": "rgb(45 94 145 / 55%)", + "selection": "rgb(45 94 145 / 18%)", + "edit-ring": "#2a79d7", + "edit-selection": "#b3d6fe", + "primary-from": "#2e2d29", + "primary-to": "#1c1b18", + "primary-hover-from": "#3b3934", + "primary-hover-to": "#292824", + "primary-ring": "#1c1b18", + "primary-highlight": "rgb(255 255 255 / 12%)", + "primary-foreground": "#ffffff", + "scrollbar-thumb": "rgb(28 27 24 / 20%)", + "scrim": "rgb(28 27 24 / 14%)", + "viewer-scrim": "oklch(0.14 0.004 85 / 92%)", + "mark": "oklch(0.88 0.14 92 / 55%)", + "checker": "rgb(28 27 24 / 5%)", + "agent-opencode": "oklch(0.6 0.008 85)", + "agent-pi": "oklch(0.852 0.114 83.4)", + "agent-other": "oklch(0.8 0.008 85)", + "heat-hue": "#55534c", + "heat-0": "rgb(28 27 24 / 5.5%)", + "context-1": "oklch(0.52 0.09 250)", + "context-2": "oklch(0.56 0.07 190)", + "context-3": "oklch(0.52 0.09 310)", + "context-4": "oklch(0.68 0.11 75)", + "context-5": "oklch(0.56 0.09 155)", + "context-6": "oklch(0.56 0.1 25)", + "context-7": "oklch(0.6 0.02 85)", + "terminal-background": "#fdfcf9", + "terminal-foreground": "#1c1b18", + "terminal-cursor": "#1c1b18", + "terminal-selection": "rgb(45 94 145 / 18%)", + "ansi-black": "#1c1b18", + "ansi-red": "#ac3b2d", + "ansi-green": "#2e7550", + "ansi-yellow": "#8a6212", + "ansi-blue": "#2d5e91", + "ansi-magenta": "#6f4c8e", + "ansi-cyan": "#23707a", + "ansi-white": "#6d6a62", + "ansi-bright-black": "#75726a", + "ansi-bright-red": "#c24e3b", + "ansi-bright-green": "#3c8a5e", + "ansi-bright-yellow": "#a0761a", + "ansi-bright-blue": "#3b74ae", + "ansi-bright-magenta": "#8660a6", + "ansi-bright-cyan": "#2f8590", + "ansi-bright-white": "#a9a69d" + } + }, + "dark": { + "colors": { + "bg-app": "#1c1b18", + "bg-sidebar": "#121110", + "bg-surface": "#262521", + "bg-subtle": "#2a2925", + "bg-code-inline": "rgb(244 243 238 / 8.5%)", + "bg-code-block": "#181715", + "bg-hover": "rgb(244 243 238 / 5.5%)", + "bg-selected": "rgb(244 243 238 / 9%)", + "queue-back": "#201f1c", + "border-default": "rgb(244 243 238 / 10%)", + "border-subtle": "rgb(244 243 238 / 6.5%)", + "border-strong": "rgb(244 243 238 / 18%)", + "text-primary": "#eceae3", + "text-secondary": "#bab7ad", + "text-tertiary": "#9a978f", + "text-placeholder": "#95928a", + "text-disabled": "#5e5c56", + "icon-default": "#9a978f", + "attention": "#e8916a", + "attention-dot": "#e8916a", + "danger": "#ea8a7d", + "success": "#7dc196", + "merged": "#b798d4", + "info": "#80aada", + "warning": "#d9b463", + "link": "#80aada", + "link-hover": "#a6c4e8", + "danger-tint": "#3a2f2a", + "danger-border": "#593f39", + "success-tint": "#2f352d", + "info-tint": "#2f3234", + "warning-tint": "#383328", + "info-tint-hover": "#34393d", + "focus": "rgb(128 170 218 / 60%)", + "selection": "rgb(128 170 218 / 28%)", + "edit-ring": "#3a8ee6", + "edit-selection": "#3f638b", + "primary-from": "#f4f3ee", + "primary-to": "#e3e1d9", + "primary-hover-from": "#ffffff", + "primary-hover-to": "#eceae3", + "primary-ring": "#d4d1c8", + "primary-highlight": "rgb(255 255 255 / 60%)", + "primary-foreground": "#262521", + "scrollbar-thumb": "rgb(244 243 238 / 20%)", + "scrim": "rgb(0 0 0 / 32%)", + "viewer-scrim": "oklch(0.14 0.004 85 / 92%)", + "mark": "oklch(0.8 0.13 88 / 32%)", + "checker": "rgb(244 243 238 / 7%)", + "agent-opencode": "oklch(0.74 0.008 85)", + "agent-pi": "oklch(0.749 0.115 84.2)", + "agent-other": "oklch(0.46 0.008 85)", + "heat-hue": "#bab7ad", + "heat-0": "rgb(244 243 238 / 6.5%)", + "context-1": "oklch(0.72 0.08 250)", + "context-2": "oklch(0.74 0.06 190)", + "context-3": "oklch(0.72 0.08 310)", + "context-4": "oklch(0.78 0.1 75)", + "context-5": "oklch(0.74 0.08 155)", + "context-6": "oklch(0.74 0.09 25)", + "context-7": "oklch(0.7 0.02 85)", + "terminal-background": "#1c1b18", + "terminal-foreground": "#eceae3", + "terminal-cursor": "#eceae3", + "terminal-selection": "rgb(128 170 218 / 28%)", + "ansi-black": "#3a3833", + "ansi-red": "#ea8a7d", + "ansi-green": "#7dc196", + "ansi-yellow": "#d9b463", + "ansi-blue": "#80aada", + "ansi-magenta": "#b798d4", + "ansi-cyan": "#79c2c2", + "ansi-white": "#d9d6cd", + "ansi-bright-black": "#6e6b63", + "ansi-bright-red": "#f2a497", + "ansi-bright-green": "#97d3ac", + "ansi-bright-yellow": "#e8c77e", + "ansi-bright-blue": "#9dc0e8", + "ansi-bright-magenta": "#cbb0e2", + "ansi-bright-cyan": "#97d6d6", + "ansi-bright-white": "#f4f3ee" + } + } +}