diff --git a/src/color.c b/src/color.c
index ad46d67f..194c1229 100644
--- a/src/color.c
+++ b/src/color.c
@@ -59,7 +59,8 @@ void set_interface_colorpairs(void)
 			else if (index == GUIDE_STRIPE)
 				interface_color_pair[index] = A_REVERSE;
 			else if (index == SPOTLIGHTED) {
-				init_pair(index + 1, COLOR_BLACK, COLOR_YELLOW + (COLORS > 15 ? 8 : 0));
+				init_pair(index + 1, COLOR_BLACK,
+					COLOR_YELLOW + ((COLORS >= 16 && COLORS <= 256) ? 8 : 0));
 				interface_color_pair[index] = COLOR_PAIR(index + 1);
 			} else if (index == MINI_INFOBAR || index == PROMPT_BAR)
 				interface_color_pair[index] = interface_color_pair[TITLE_BAR];
diff --git a/src/rcfile.c b/src/rcfile.c
index 174716bf..e1756797 100644
--- a/src/rcfile.c
+++ b/src/rcfile.c
@@ -28,6 +28,7 @@
 #include <ctype.h>
 #include <errno.h>
 #include <glob.h>
+#include <stdint.h>
 #include <string.h>
 #include <unistd.h>
 
@@ -990,50 +991,109 @@ void parse_includes(char *ptr)
 	free(expanded);
 }
 
-#define COLORCOUNT  20
-
-const char hues[COLORCOUNT][8] = { "red", "green", "blue",
-								   "yellow", "cyan", "magenta",
-								   "white", "black", "normal",
-								   "pink", "purple", "mauve",
-								   "lagoon", "mint", "lime",
-								   "peach", "orange", "latte",
-								   "grey", "gray" };
-
-short indices[COLORCOUNT] = { COLOR_RED, COLOR_GREEN, COLOR_BLUE,
-							  COLOR_YELLOW, COLOR_CYAN, COLOR_MAGENTA,
-							  COLOR_WHITE, COLOR_BLACK, THE_DEFAULT,
-							  204, 163, 134, 38, 48, 148, 215, 208, 137,
-							  COLOR_BLACK + 8, COLOR_BLACK + 8 };
-
-/* Return the short value corresponding to the given color name, and set
- * vivid to TRUE for a lighter color, and thick for a heavier typeface. */
-short color_to_short(const char *colorname, bool *vivid, bool *thick)
+static const struct {
+	const char *name;
+	short curses_color;
+} colors[] = {
+	{ "normal", THE_DEFAULT }, { "white", COLOR_WHITE },
+	{ "black", COLOR_BLACK }, { "red", COLOR_RED }, { "blue", COLOR_BLUE },
+	{ "green", COLOR_GREEN }, { "yellow", COLOR_YELLOW },
+	{ "magenta", COLOR_MAGENTA }, { "cyan", COLOR_CYAN },
+	{ "grey", COLOR_BLACK + 8 }, { "gray", COLOR_BLACK + 8 },
+	{ "pink", 204 }, { "purple", 163 }, { "mauve", 134 }, { "lagoon", 38 },
+	{ "mint", 48 }, { "lime", 148 }, { "peach", 215 }, { "orange", 208 },
+	{ "latte", 137 },
+};
+
+/* Return a side offset for the 6x6x6 color cube for 256-color terminals. */
+static int rgblevel_to_256color_cube_offset(uint8_t level)
+{
+	static const uint8_t offsets[] = {
+		0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 2,
+		2, 2, 2, 2, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 5, 5
+	};
+	return offsets[level >> 3];
+}
+
+/* Return the curses color for the gray ramp for 256-color terminals. */
+static short rgblevel_to_256color_gray_curses_color(uint8_t level)
 {
+	static const uint8_t curses_colors[] = {
+		16, 232, 233, 233, 234, 235, 236, 237, 237, 238, 239, 240,
+		241, 241, 242, 243, 244, 245, 245, 246, 247, 248, 249, 249,
+		250, 251, 252, 253, 253, 254, 255, 231
+	};
+	return curses_colors[level >> 3];
+}
+
+/* Return the curses color closest to the given color levels. */
+static short get_closest_curses_color(uint8_t r, uint8_t g, uint8_t b)
+{
+	if (COLORS != 256) return THE_DEFAULT;
+
+	/* Assume it uses the `xterm-256color` 6x6x6 color cube. */
+	int ro = rgblevel_to_256color_cube_offset(r);
+	int go = rgblevel_to_256color_cube_offset(g);
+	int bo = rgblevel_to_256color_cube_offset(b);
+	if (ro == go && ro == bo) {
+		/* Get a better gray, which is especially needed near black, using
+		 * a rough estimate of luma. */
+		return rgblevel_to_256color_gray_curses_color(
+			((int)r + 2 * (int)g + (int)b) / 4);
+	}
+	return (36 * ro) + (6 * go) + bo + 16;
+}
+
+/* Return the short value corresponding to the given color name, and set thick
+ * to TRUE for a heavier typeface. */
+short color_to_curses_color(const char *colorname, bool *thick)
+{
+	bool vivid = FALSE;
 	if (strncmp(colorname, "bright", 6) == 0 && colorname[6] != '\0') {
 		/* Prefix "bright" is deprecated; remove in 2024. */
-		*vivid = TRUE;
+		vivid = TRUE;
 		*thick = TRUE;
 		colorname += 6;
 	} else if (strncmp(colorname, "light", 5) == 0 && colorname[5] != '\0') {
-		*vivid = TRUE;
+		vivid = TRUE;
 		*thick = FALSE;
 		colorname += 5;
 	} else {
-		*vivid = FALSE;
 		*thick = FALSE;
 	}
 
-	for (int index = 0; index < COLORCOUNT; index++)
-		if (strcmp(colorname, hues[index]) == 0) {
-			if (index > 7 && *vivid) {
+	if (colorname[0] == '#' && colorname[1] != '\0') {
+		unsigned int r, g, b;
+		if (vivid) {
+			jot_error(N_("Color '%s' takes no prefix"), colorname);
+			return BAD_COLOR;
+		}
+		if (strlen(colorname) == 7 &&
+			sscanf(colorname, "#%2X%2X%2X", &r, &g, &b) == 3) {
+			return get_closest_curses_color(r, g, b);
+		} else if (strlen(colorname) == 4 &&
+				   sscanf(colorname, "#%1X%1X%1X", &r, &g, &b) == 3){
+			return get_closest_curses_color(r * 0x11, g * 0x11, b * 0x11);
+		}
+	} else {
+		for (int i = 0; i < sizeof(colors) / sizeof(colors[0]); ++i) {
+			if (strcmp(colorname, colors[i].name) != 0) continue;
+			short curses_color = colors[i].curses_color;
+			if (vivid && (curses_color < 0 || curses_color > 7)) {
 				jot_error(N_("Color '%s' takes no prefix"), colorname);
 				return BAD_COLOR;
-			} else if (index > 8 && COLORS < 255)
+			}
+
+			if ((curses_color >= COLORS) ||
+				(curses_color >= 8 && COLORS == 0x1000000) ||
+				(curses_color >= 16 && COLORS != 256)) {
 				return THE_DEFAULT;
-			else
-				return indices[index];
+			}
+
+			return curses_color +
+				((vivid && COLORS >= 16 && COLORS <= 256) ? 8 : 0);
 		}
+	}
 
 	jot_error(N_("Color \"%s\" not understood"), colorname);
 	return BAD_COLOR;
@@ -1043,7 +1103,7 @@ short color_to_short(const char *colorname, bool *vivid, bool *thick)
  * Return FALSE when any color name is invalid; otherwise return TRUE. */
 bool parse_combination(char *combostr, short *fg, short *bg, int *attributes)
 {
-	bool vivid, thick;
+	bool thick;
 	char *comma;
 
 	*attributes = A_NORMAL;
@@ -1074,22 +1134,18 @@ bool parse_combination(char *combostr, short *fg, short *bg, int *attributes)
 		*comma = '\0';
 
 	if (!comma || comma > combostr) {
-		*fg = color_to_short(combostr, &vivid, &thick);
+		*fg = color_to_curses_color(combostr, &thick);
 		if (*fg == BAD_COLOR)
 			return FALSE;
-		if (vivid && !thick && COLORS > 8)
-			*fg += 8;
-		else if (vivid)
+		if (thick)
 			*attributes |= A_BOLD;
 	} else
 		*fg = THE_DEFAULT;
 
 	if (comma) {
-		*bg = color_to_short(comma + 1, &vivid, &thick);
+		*bg = color_to_curses_color(comma + 1, &thick);
 		if (*bg == BAD_COLOR)
 			return FALSE;
-		if (vivid && COLORS > 8)
-			*bg += 8;
 	} else
 		*bg = THE_DEFAULT;
 
