hamacs/ha-theme.org
2024-11-22 15:19:13 -08:00

478 lines
20 KiB
Org Mode
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#+title: Hamacs Color Theme
#+author: Howard Abrams
#+date: 2024-06-18
#+filetags: emacs hamacs
#+lastmod: [2024-11-22 Fri]
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 <http://gitlab.com/howardabrams>
;; 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 are designed for use with Emacs' built-in
;; theme support in Emacs 24. However, they also work with older Emacs
;; versions, in which case color-theme.el is required.
;;
;; 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
Ive 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 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 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")
("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 Orgs =noweb= feature):
#+BEGIN_SRC emacs-lisp :noweb yes
(defvar hamacs-theme-colors-alist
'(
<<specials>>
<<region>>
<<links>>
<<region>>
<<grays>>
<<colors>>
<<header-sizes>>
)
"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
Lets 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-70))))
`(font-lock-comment-delimiter-face ((t (:foreground ,gray-50))))
`(font-lock-string-face ((t (:foreground ,gray-90))))
`(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))))
))
#+END_SRC
Lets see how Org-formatted faces look:
- *bold*
- /italics/
- _underlined_
- =verbatim=
- ~code, er keys~
Functions and variables would look like:
#+begin_src emacs-lisp :tangle no
(defun function-name (arg1 arg2)
"The doc string is part string and part comment.
(interactive "P")
(list "strings not crazy" 2 :keywords-moreso))
#+end_src
And YAML specifically:
#+BEGIN_SRC yaml-ts :tangle no
foo:
number: 42
string: Nothing much
quoted_string: "Nothing much"
boolean: true
multiline: >
Here we go with {{ substitutions }} from Jinja/Ansible
#+END_SRC
#+BEGIN_SRC emacs-lisp :noweb yes :tangle no
(setq hamacs-theme-colors-alist
'(
<<specials>>
<<region>>
<<links>>
<<region>>
<<grays>>
<<colors>>
<<header-sizes>>
))
#+END_SRC
* 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
-