Hamacs Color Theme
A literate programming file for defining a warm, autumn, but subtle color theme for Emacs.
Subtle Warm Colors
I’ve decided that I need to make my own theme. I realize that I like the warm tones of monokai or flexoki, but both are a wee bit garish and visually jarring, and I prefer the subtle color variations of 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:
I encapsulate those values in a list:
("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")
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.
And encapsulate them in a named list:
("red" . "#860116") ("red-lt" . "#d8a2aa") ("orange" . "#ce5f15") ("orange-lt" . "#d89e77") ("yellow" . "#f9b419") ("yellow-lt" . "#d8bb77") ("green" . "#899d03") ("green-lt" . "#b3bf8e") ("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")
Special Settings
The following lists tweaks to specific settings.
The default is a high contrast between a 95% and 5% brightest values:
("default-fg" . "#f2e9e1") ("default-bg" . "#0c0906") ("active" . "#801000") ("inactive" . "#462200") ("cursor" . "orange")
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:
("region" . "#945703")
Org and Markdown inherit from some Emacs faces, so we define a consistent color for hyperlinks:
("link-color" . "#87a9b2") ("visited-color" . "#c3dee5")
Header Sizes
For Org, Markdown, and other document formatting, I want a list of header size increases:
("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)
Colors as Code
Color definition injects the named lists defined above (using Org’s noweb
feature):
(defvar hamacs-theme-colors-alist '( ("default-fg" . "#f2e9e1") ("default-bg" . "#0c0906") ("active" . "#801000") ("inactive" . "#462200") ("cursor" . "orange") ("region" . "#945703") ("link-color" . "#87a9b2") ("visited-color" . "#c3dee5") ("region" . "#945703") ("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") ("red" . "#860116") ("red-lt" . "#d8a2aa") ("orange" . "#ce5f15") ("orange-lt" . "#d89e77") ("yellow" . "#f9b419") ("yellow-lt" . "#d8bb77") ("green" . "#899d03") ("green-lt" . "#b3bf8e") ("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") ("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) ) "A list of named colors available in theme.")
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:
(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))
Can we see our colors?
(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)))
Dark Theme
Let’s make a theme:
(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-70)))) `(font-lock-comment-delimiter-face ((t (:foreground ,gray-50)))) `(font-lock-string-face ((t (:foreground ,yellow-lt)))) `(font-lock-type-face ((t (:foreground ,green-lt)))) `(font-lock-doc-face ((t (:foreground ,almond)))) `(font-lock-constant-face ((t (:foreground ,orange-lt)))) `(font-lock-builtin-face ((t (:foreground ,red-lt)))) `(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 ,purple :height ,smaller)))) `(org-property-value ((t (:foreground ,purple-lt :height ,smaller)))) `(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)))) ))
Let’s see how Org-formatted faces look:
- bold
- italics
- underlined
verbatim
code, er keys
Functions and variables would look like:
(defun function-name (arg1 arg2) "The doc string is part string and part comment. (interactive "P") (list "strings not crazy" 2 :keywords-moreso))
And YAML specifically:
foo: number: 42 string: Nothing much quoted_string: "Nothing much" boolean: true multiline: > Here we go with {{ substitutions }} from Jinja/Ansible
(setq hamacs-theme-colors-alist '( ("default-fg" . "#f2e9e1") ("default-bg" . "#0c0906") ("active" . "#801000") ("inactive" . "#462200") ("cursor" . "orange") ("region" . "#945703") ("link-color" . "#87a9b2") ("visited-color" . "#c3dee5") ("region" . "#945703") ("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") ("red" . "#860116") ("red-lt" . "#d8a2aa") ("orange" . "#ce5f15") ("orange-lt" . "#d89e77") ("yellow" . "#f9b419") ("yellow-lt" . "#d8bb77") ("green" . "#899d03") ("green-lt" . "#b3bf8e") ("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") ("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) ))