#+title: Hamacs Color Theme #+author: Howard Abrams #+date: 2024-06-18 #+filetags: emacs hamacs #+lastmod: [2024-12-10 Tue] A literate programming file for defining a warm, autumn, but subtle color theme for Emacs. #+begin_src emacs-lisp :exports none ;;; hamacs-theme --- A warm, subtle Emacs theme -*- lexical-binding: t; -*- ;; ;; © 2024 Howard Abrams ;; Licensed under a Creative Commons Attribution 4.0 International License. ;; See http://creativecommons.org/licenses/by/4.0/ ;; ;; Author: Howard Abrams ;; Maintainer: Howard Abrams ;; Created: June 18, 2024 ;; ;; While obvious, GNU Emacs does not include this file or project. ;; ;; *NB:* Do not edit this file. Instead, edit the original literate file at: ;; /home/howard/other/hamacs/ha-theme.org ;; And tangle the file to recreate this one. ;; ;;; Commentary: ;; ;; These color themes, designed for use with Emacs' built-in theme ;; support in Emacs 24, also work with older Emacs versions, in which ;; case, require color-theme.el. ;; ;; Usage: ;; ;; If your Emacs has the `load-theme' command, you can use it to ;; activate one of these themes programatically, or use ;; `customize-themes' to select a theme interactively. ;; ;;; Code: (require 'color) (eval-when-compile (require 'ansi-color)) (declare-function color-theme-install "color-theme") #+end_src * Subtle Warm Colors I’ve decided that I need to make my own theme. I realize that I like the warm tones of [[https://monokai.com/][monokai]] or [[https://github.com/crmsnbleyd/flexoki-emacs-theme][flexoki]], but both are a wee bit garish and visually jarring, and I prefer the subtle color variations of [[https://github.com/ianyepan/wilmersdorf-emacs-theme][wilmersdorf]], but would like to warm that cool color palette. My goals are: - Warm tones from an Autumn palette, so… - White as cream, and Black as a dark brown - Subtle colors, as I don’t want to look at a garish screen - Contrast should be high between foreground and background Consider this theme /drab/? That is the beauty of themes. Everyone can make their own. ** Warm Grays The codes for grays are hex numbers with equal values of red, green and blue, for instance, =#888888=. To /warm/ a gray, we need to add a bit more green, and a bit more than that of red. I notice, the closer to white you go, the less of that “more” you need. Otherwise, I end up with dark brown and beige. I started with a /pattern/ of manipulating the RPG values, but found working with the HSV scale (hue, saturation and value, where /value/ is brightness) better. The /hue/ would be a nice red-orange color, around =30=, and the saturation would /start/ as =1=, but increase as we get darker, because we can increase the color saturation the closer to black we get. We can create 20 shades of warm gray: #+begin_src pikchr :results file :file ha-theme-grays.svg :exports results R1: box "H:30 S:1 V:95%" "#f2f1ef" fit color 0x0c0906 thin fill 0xf2f1ef box "H:30 S:1 V:90%" "#e5e4e3" same color 0x0c0906 thin fill 0xe5e4e3 box "H:30 S:1 V:85%" "#d8d7d6" same color 0x0c0906 thin fill 0xd8d7d6 R2: box "H:30 S:2 V:80%" "#ccc9c7" same color 0x0c0906 thin fill 0xccc9c7 with .n at 0 below R1.s box "H:30 S:2 V:75%" "#bfbdbb" same color 0x0c0906 thin fill 0xbfbdbb box "H:30 S:3 V:70%" "#b2afad" same color 0x0c0906 thin fill 0xb2afad R3: box "H:30 S:4 V:65%" "#a6a3a0" same color 0x0c0906 thin fill 0xa6a3a0 with .n at 0 below R2.s box "H:30 S:5 V:60%" "#999491" same color 0x0c0906 thin fill 0x999491 box "H:30 S:6 V:55%" "#8c8883" same color 0x0c0906 thin fill 0x8c8883 R4: box "H:30 S:7 V:50%" "#7f7a76" same color 0xf2f1ef thin fill 0x7f7a76 with .n at 0 below R3.s box "H:30 S:9 V:45%" "#726d68" same color 0xf2f1ef thin fill 0x726d68 box "H:30 S:10 V:40%" "#66615c" same color 0xf2f1ef thin fill 0x66615c R5: box "H:30 S:12 V:35%" "#59534e" same color 0xf2f1ef thin fill 0x59534e with .n at 0 below R4.s box "H:30 S:16 V:30%" "#4c4640" same color 0xf2f1ef thin fill 0x4c4640 box "H:30 S:21 V:25%" "#3f3932" same color 0xf2f1ef thin fill 0x3f3932 R6: box "H:30 S:e5 V:20%" "#332c26" same color 0xf2f1ef thin fill 0x332c26 with .n at 0 below R5.s box "H:30 S:30 V:15%" "#26201a" same color 0xf2f1ef thin fill 0x26201a box "H:30 S:48 V:10%" "#19130d" same color 0xf2f1ef thin fill 0x19130d # box "H:30 S:75 V:5%" "#0c0703" fit color 0xf2f1ef thin fill 0x0c0703 #+end_src [[file:ha-theme-grays.svg]] I encapsulate those values in a list: #+NAME: grays #+BEGIN_SRC emacs-lisp :tangle no ("gray-95" . "#f9f8f7") ("gray-90" . "#f2f1ef") ("gray-85" . "#e5e4e3") ("gray-80" . "#d8d7d6") ("gray-75" . "#ccc9c7") ("gray-70" . "#bfbdbb") ("gray-65" . "#b2afad") ("gray-60" . "#a6a3a0") ("gray-55" . "#999491") ("gray-50" . "#8c8883") ("gray-45" . "#7f7a76") ("gray-40" . "#726d68") ("gray-35" . "#66615c") ("gray-30" . "#59534e") ("gray-25" . "#4c4640") ("gray-20" . "#3f3932") ("gray-15" . "#332c26") ("gray-10" . "#26201a") ("gray-05" . "#19130d") ("gray-00" . "#0c0703") #+END_SRC ** Subtle Colors With this knowledge of /saturation/ vs. darkness /value/, I can collect /warm/ colors I like (think autumn), and then /desaturate/ them while still keeping a high contrast, e.g. closer to white and black. #+begin_src pikchr :results file :file ha-theme-colors.svg :exports results Red: box fit fill 0x0c0906 "Red" "#860116" color 0x860116 box same fill 0x860116 "H: 351" "25/85" color 0x0c0906 with .w at .1 right of previous.e box same fill 0x0c0906 "Red-lt" "#d8a2aa" color 0xd8a2aa with .w at .1 right of previous.e Org: box fit fill 0x0c0906 "Orange" "#ce5f15" color 0xce5f15 with .n at .1 below Red.s box same fill 0xce5f15 "H: 24" "25/85" color 0x0c0906 with .w at .1 right of previous.e box same fill 0x0c0906 "Orange-lt" "#d89e77" color 0xd89e77 with .w at .1 right of previous.e Ylw: box fit fill 0x0c0906 "Yellow" "#f9b419" color 0xf9b419 with .n at .1 below Org.s box same fill 0xf9b419 "H: 42" "45/85" color 0x0c0906 with .w at .1 right of previous.e box same fill 0x0c0906 "Yellow-lt" "#d8bb77" color 0xd8b8a2 with .w at .1 right of previous.e Gr1: box fit fill 0x0c0906 "Green" "#899d03" color 0x899d03 with .n at .1 below Ylw.s box same fill 0x899d03 "H: 68" "45/85" color 0x0c0906 with .w at .1 right of previous.e box same fill 0x0c0906 "Green-lt" "#B3BF8E" color 0xB3BF8E with .w at .1 right of previous.e Gr2: box fit fill 0x0c0906 "Dk Green" "#55702c" color 0x55702c with .n at .1 below Gr1.s box same fill 0x55702c "H: 68" "45/85" color 0x0c0906 with .w at .1 right of previous.e box same fill 0x0c0906 "Dk Green-lt" "#979e6e" color 0x979e6e with .w at .1 right of previous.e Bl1: box fit fill 0x0c0906 "Blue" "#6f8b93" color 0x6f8b93 with .n at .1 below Gr2.s box same fill 0x6f8b93 "H: 193" "45/85" color 0x0c0906 with .w at .1 right of previous.e box same fill 0x0c0906 "Blue-lt" "#91b5bf" color 0x91b5bf with .w at .1 right of previous.e Prp: box fit fill 0x0c0906 "Purple" "#893161" color 0x893161 with .n at .1 below Bl1.s box same fill 0x893161 "H: 68" "45/85" color 0x0c0906 with .w at .1 right of previous.e box same fill 0x0c0906 "Purple-lt" "#997a8b" color 0x997a8b with .w at .1 right of previous.e /* or 625E7B */ #+end_src [[file:ha-theme-colors.svg]] And encapsulate them in a named list: #+NAME: colors #+BEGIN_SRC emacs-lisp :tangle no ("red" . "#860116") ("red-lt" . "#d8a2aa") ("orange" . "#ce5f15") ("orange-lt" . "#d89e77") ("yellow" . "#f9b419") ("yellow-lt" . "#d8bb77") ("green" . "#899d03") ("green-lt" . "#b3bf8e") ("dk-green" . "#55702c") ("dk-green-lt" . "#979e6e") ("blue" . "#6f8b93") ("blue-lt" . "#91b5bf") ("purple" . "#893161") ("purple-lt" . "#997a8b") ("red2-lt" . "#ffb7a5") ("red2" . "#d8a2aa") ("orange2-lt" . "#f2d7a9") ("orange-dk" . "#d67c00") ("brown-lt" . "#d69333") ("yellow2-lt" . "#e5e1ce") ("almond" . "#ffe3bf") ("green2-lt" . "#a8b269") ("blue2-lt" . "#b1d2ee") ("blue2" . "#81a2be") ("slate" . "#708C93") ; Slate? For function name? Change this ("purple-dk" . "#b172ff") ("purple2" . "#cfacf9") ("purple2-lt" . "#e5d4f9") #+END_SRC ** Special Settings The following lists tweaks to specific settings. The default is a high contrast between a 95% and 5% brightest values: #+NAME: specials #+BEGIN_SRC emacs-lisp :tangle no ("default-fg" . "#f2e9e1") ("default-bg" . "#0c0906") ("active" . "#801000") ("inactive" . "#462200") ("cursor" . "orange") #+END_SRC Our /active/ and /inactive/ is mostly used for the mode-line, and is designed to be noticeable, but not too much? The region should be noticeable, but not obnoxious: #+NAME: region #+BEGIN_SRC emacs-lisp :tangle no ("region" . "#945703") #+END_SRC Org and Markdown inherit from some Emacs faces, so we define a consistent color for [[http://duckduckgo.com][hyperlinks]]: #+NAME: links #+BEGIN_SRC emacs-lisp :tangle no ("link-color" . "#87a9b2") ("visited-color" . "#c3dee5") #+END_SRC ** Header Sizes For Org, Markdown, and other document formatting, I want a list of header size increases: #+NAME: header-sizes #+BEGIN_SRC emacs-lisp :tangle no ("header-1" . 2.2) ("header-2" . 1.8) ("header-3" . 1.4) ("header-4" . 1.2) ("header-5" . 1.16) ("header-6" . 1.14) ("header-7" . 1.12) ("header-8" . 1.1) ("normal" . 1.0) ("small" . 0.9) ("smaller" . 0.85) ("smallest" . 0.8) #+END_SRC ** Colors as Code Color definition injects the /named/ lists defined above (using Org’s =noweb= feature): #+BEGIN_SRC emacs-lisp :noweb yes (defvar hamacs-theme-colors-alist '( <> <> <> <> <> <> <> ) "A list of named colors available in theme.") #+END_SRC * Theme Support Stole the following macro from Zenburn, which converts color references defined above, but only within the body of the macro. Sweet way to trim down a lot of boilerplate: #+BEGIN_SRC emacs-lisp (defmacro hamacs-with-color-variables (&rest body) "`let' bind all colors defined in `hamacs-theme-colors-alist' around BODY. Also bind `class' to ((class color) (min-colors 89))." (declare (indent 0)) `(let ((class '((class color) (min-colors 89))) ,@(mapcar (lambda (cons) (list (intern (car cons)) (cdr cons))) hamacs-theme-colors-alist)) ,@body)) #+END_SRC Can we *see* our colors? #+BEGIN_SRC emacs-lisp :noweb yes (defun hamacs-theme-color-show () "Create a buffer and show off the color choices." (interactive) (switch-to-buffer "*hamacs-theme-colors*") (delete-region (point-min) (point-max)) (let ((default-fg "#f2e9e1") (default-bg "#0c0906")) (dolist (color-tuple hamacs-theme-colors-alist) (let ((name (car color-tuple)) (rgb (cdr color-tuple))) (when (and (stringp rgb) (string-match (rx bos "#") rgb)) (insert ;; `(default ((t (:foreground ,default-fg :background ,default-bg)))) ;; Color against default dark: (propertize (format " %-15s " name) 'face `(:foreground ,rgb :background ,default-bg ;; :box (:line-width (2 . 2) :color ,default-fg) )) " " ;; Color against default fg: (propertize (format " %-15s " name) 'face `(:foreground ,rgb :background ,default-fg ;; :box (:line-width (2 . 2) :color ,default-bg) )) " " ;; Color with dark on background (propertize (format " %-15s " name) 'face `(:foreground ,default-bg :background ,rgb)) " " ;; Color with light on background (propertize (format " %-15s " name) 'face `(:foreground ,default-fg :background ,rgb)) )) (newline)))) (goto-char (point-min))) #+END_SRC * Dark Theme Let’s make a /theme/: #+BEGIN_SRC emacs-lisp (deftheme hamacs () "A warm, low-contrast theme for GNU Emacs") (hamacs-with-color-variables (custom-theme-set-faces 'hamacs `(default ((t (:foreground ,default-fg :background ,default-bg)))) `(fringe ((t :background ,default-bg))) `(tab-bar ((t :foreground ,default-fg :background ,default-bg))) `(tab-line ((t :foreground ,default-fg :background ,default-bg))) `(window-divider ((t :foreground "black"))) `(cursor ((t (:foreground ,gray-10 :background ,cursor)))) `(region ((t (:background ,region)))) `(mode-line ((t (:background ,active :foreground "white")))) `(mode-line-active ((t (:background ,active)))) `(mode-line-inactive ((t (:background ,inactive)))) `(doom-modeline-buffer-path ((t (:foreground ,almond)))) `(doom-modeline-buffer-file ((t (:foreground "white" :weight bold)))) `(doom-modeline-buffer-major-mode ((t (:foreground ,almond)))) `(doom-modeline-info ((t (:foreground ,green-lt)))) `(doom-modeline-time ((t (:foreground ,default-fg)))) `(line-number ((t (:foreground ,gray-50 :background ,gray-10)))) `(line-number-current-line ((t (:foreground ,gray-95 :background ,gray-20 :weight ultra-bold)))) `(header-line ((t (:foreground ,gray-80)))) `(help-key-binding ((t (:foreground ,gray-80 :weight ultra-bold)))) `(bold ((t (:foreground ,gray-90 :weight ultra-bold)))) `(italics ((t (:foreground ,gray-95)))) `(bold-italic ((t (:foreground "white")))) `(link ((t (:foreground ,link-color)))) `(link-visited ((t (:foreground ,visited-color)))) `(font-lock-comment-face ((t (:foreground ,gray-60)))) `(font-lock-comment-delimiter-face ((t (:foreground ,gray-50)))) `(font-lock-string-face ((t (:foreground ,gray-75)))) `(font-lock-type-face ((t (:foreground ,green-lt)))) `(font-lock-doc-face ((t (:foreground ,almond)))) ;; References like `reference' in doc strings: `(font-lock-constant-face ((t (:foreground ,link-color)))) ;; Keywords like :this `(font-lock-builtin-face ((t (:foreground ,red-lt)))) ;; Like defun or `interactive' `(font-lock-keyword-face ((t (:foreground ,green-lt)))) `(font-lock-function-name-face ((t (:foreground ,blue :weight ultra-bold)))) `(font-lock-property-use-face ((t (:foreground ,blue-lt)))) `(parenthesis ((t (:foreground ,gray-50)))) `(show-paren-match ((t (:foreground ,cursor :weight ultra-bold)))) `(org-link ((t (:foreground ,link-color)))) `(org-code ((t (:foreground ,almond)))) `(org-verbatim ((t (:foreground ,gray-95)))) `(org-block ((t (:background ,gray-10)))) `(org-block-begin-line ((t (:foreground ,gray-50 :background ,gray-20 :height ,smaller :extend t)))) `(org-block-end-line ((t (:inherit org-block-begin-line :height ,smallest)))) `(org-document-title ((t (:foreground ,orange-lt :weight ultra-bold)))) `(org-document-info ((t (:foreground ,brown-lt)))) `(org-document-info-keyword ((t (:foreground ,gray-70)))) `(org-meta-line ((t (:foreground ,gray-55)))) `(org-drawer ((t (:foreground ,purple-dk :height ,smallest)))) `(org-special-keyword ((t (:foreground ,purple2 :height ,small)))) `(org-property-value ((t (:foreground ,purple2-lt :height ,small)))) `(org-table ((t (:foreground ,purple-lt)))) `(org-quote ((t (:inherit variable-pitch :slant italic :height 0.9)))) `(org-level-1 ((t (:inherit variable-pitch :foreground ,default-fg :weight bold :height ,header-1)))) `(org-level-2 ((t (:inherit variable-pitch :foreground ,default-fg :weight bold :height ,header-2)))) `(org-level-3 ((t (:inherit variable-pitch :foreground ,default-fg :weight bold :height ,header-3)))) `(org-level-4 ((t (:inherit variable-pitch :foreground ,default-fg :weight bold :height ,header-4)))) `(org-level-5 ((t (:inherit variable-pitch :foreground ,default-fg :weight bold :height ,header-5)))) `(org-level-6 ((t (:inherit variable-pitch :foreground ,default-fg :weight bold :height ,header-6)))) `(org-level-7 ((t (:inherit variable-pitch :foreground ,default-fg :weight bold :height ,header-7)))) `(org-level-8 ((t (:inherit variable-pitch :foreground ,default-fg :weight bold :height ,header-8)))) `(markdown-italic-face ((t (:foreground unspecified)))) `(markdown-bold-face ((t (:foreground unspecified)))) `(markdown-pre-face ((t (:foreground ,(face-attribute 'org-code :foreground) :family ,(face-attribute 'default :family))))) `(markdown-code-face ((t (:background ,(face-attribute 'org-block :background))))) `(markdown-language-keyword-face ((t (:foreground ,(face-attribute 'org-block-begin-line :foreground))))) ;; Change this: `(markdown-url-face ((t (:foreground ,(face-attribute 'org-link :foreground))))) `(markdown-header-face ((t (:font ,ha-variable-header-font :foreground ,default-fg)))) `(markdown-header-face-1 ((t (:inherit org-header-1)))) `(markdown-header-face-2 ((t (:inherit org-header-2)))) `(markdown-header-face-3 ((t (:inherit org-header-3)))) `(markdown-header-face-4 ((t (:inherit org-header-4)))) `(markdown-header-face-5 ((t (:inherit org-header-5)))) `(markdown-header-face-6 ((t (:inherit org-header-6)))) `(magit-filename ((t (:foreground ,yellow-lt)))) `(minibuffer-prompt ((t (:foreground ,orange-lt)))) `(vertico-group-separator ((t (:foreground ,gray-45)))) `(vertico-group-multiline ((t (:foreground ,gray-45)))) `(vertico-group-title ((t (:foreground ,gray-45)))) `(sh-heredoc ((t (:foreground ,almond)))) `(sh-quoted-exec ((t (:foreground ,orange2-lt)))) `(ahs-face ((t (:foreground ,orange-lt :background unspecified)))) `(ahs-plugin-default-face ((t (:foreground unspecified :background unspecified)))) `(mastodon-display-name-face ((t (:foreground ,orange-lt)))) `(mastodon-boosted-face ((t (:foreground ,green-lt)))) )) #+END_SRC [[file:ha-theme-results.png]] * Technical Artifacts :noexport: Let's =provide= a name so we can =require= this file: #+begin_src emacs-lisp :exports none (provide 'hamacs-theme) ;;; hamacs-theme.el ends here #+end_src #+DESCRIPTION: defining a warm, autumn, but subtle color theme for Emacs. #+PROPERTY: header-args:sh :tangle no #+PROPERTY: header-args:emacs-lisp :tangle hamacs-theme.el #+PROPERTY: header-args :results none :eval no-export :comments no mkdirp yes #+OPTIONS: num:nil toc:nil todo:nil tasks:nil tags:nil date:nil #+OPTIONS: skip:nil author:nil email:nil creator:nil timestamp:nil #+INFOJS_OPT: view:nil toc:nil ltoc:t mouse:underline buttons:0 path:http://orgmode.org/org-info.js -