hamacs/ha-theme.org
2024-11-22 12:56:39 -08:00

20 KiB
Raw Blame History

Hamacs Color Theme

A literate programming file for defining a warm, autumn, but subtle color theme for Emacs.

Subtle Warm Colors

Ive 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 dont 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 came up with a pattern palette: For the darkest gray, assume 10 for the blue, and start with 15 shades more for the red, and 7 more shades for the green. Then, for each step towards white, decrease the red by one, and the green, by 1/2. See the following table:

#101010 1f1710
#202020 2e2720
#303030 3d3630
#404040 4c4640
#505050 5b5550
#606060 6a6560
#707070 797470
#808080 888480
#909090 979390
#a0a0a0 a6a3a0
#b0b0b0 b5b2b0
#c0c0c0 c4c2c0
#d0d0d0 d3d1d0
#e0e0e0 e2e1e0

Actually, using the HSV scale (hue, saturation and value, where value is brightness). 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:

/git/howard/hamacs/media/commit/6fc09faf66b72ea8fc8f9dc96627f512415661a9/ha-theme-grays.svg

I can encapsulate those values in an

  ("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")

In Emacs way of displaying the colors?

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.

/git/howard/hamacs/media/commit/6fc09faf66b72ea8fc8f9dc96627f512415661a9/ha-theme-colors.svg

And encapsulate them in a named list:

  ("red-lt"    . "#ffb7a5") ; make this lighter as it is the :symbol
  ("red"       . "#d8a2aa")
  ("orange-lt" . "#f2d7a9")
  ("orange-dk" . "#d67c00")
  ("brown-lt"  . "#d69333")
  ("yellow-lt" . "#e5e1ce")
  ("almond"    . "#ffe3bf")
  ("green-lt"  . "#a8b269")
  ("blue-lt"   . "#b1d2ee")
  ("blue"      . "#81a2be")
  ("slate"     . "#708C93") ; Slate? For function name? Change this
  ("purple-dk" . "#b172ff")
  ("purple"    . "#cfacf9")
  ("purple-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)

Color definition injects the named lists defined above (using Orgs noweb feature):

  (defvar hamacs-theme-colors-alist
    '(
      <<specials>>
      <<region>>
      <<links>>
      <<region>>
      <<grays>>
      <<colors>>
      <<header-sizes>>
      )
    "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

Lets 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-doc-face ((t (:foreground ,orange-lt))))
     `(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))))
     `(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-dk))))
     `(org-document-info ((t (:foreground ,brown-lt))))
     `(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))))
     ))

Lets 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.
  It references ARG1 and ARG2, but also `other-functions'."
    (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