A minimal colorscheme for neovim and more
0

Configure Feed

Select the types of activity you want to include in your feed.

palette: replace hsluv with hardcoded hex colors

Signed-off-by: Ron Shavit <me@ronshavit.com>

Ron Shavit (Jun 29, 2026, 2:09 PM +0300) 7d4873c0 3f5b8b6b

+66 -445
-377
lua/nor/hsluv.lua
··· 1 - -- Taken and slightly modified without shame from https://github.com/hsluv/hsluv-lua/blob/master/hsluv.lua 2 - 3 - --[[ 4 - Lua implementation of HSLuv and HPLuv color spaces 5 - Homepage: http://www.hsluv.org/ 6 - 7 - Copyright (C) 2019 Alexei Boronine 8 - 9 - Permission is hereby granted, free of charge, to any person obtaining a copy of this software and 10 - associated documentation files (the "Software"), to deal in the Software without restriction, including 11 - without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 - copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the 13 - following conditions: 14 - 15 - The above copyright notice and this permission notice shall be included in all copies or substantial 16 - portions of the Software. 17 - 18 - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT 19 - LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN 20 - NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 21 - WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 22 - SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 - ]] 24 - 25 - ---@class hsluv.Line 26 - ---@field slope number 27 - ---@field intercept number 28 - 29 - local M = {} 30 - 31 - local hexChars = "0123456789abcdef" 32 - 33 - ---@param line hsluv.Line 34 - ---@return number 35 - local function distance_line_from_origin(line) 36 - return math.abs(line.intercept) / math.sqrt((line.slope ^ 2) + 1) 37 - end 38 - 39 - ---@param theta number 40 - ---@param line hsluv.Line 41 - ---@return number 42 - local function length_of_ray_until_intersect(theta, line) 43 - return line.intercept / (math.sin(theta) - line.slope * math.cos(theta)) 44 - end 45 - 46 - ---@param l number 47 - ---@return hsluv.Line[] 48 - function M.get_bounds(l) 49 - local result = {} 50 - local sub2 51 - local sub1 = ((l + 16) ^ 3) / 1560896 52 - if sub1 > M.epsilon then 53 - sub2 = sub1 54 - else 55 - sub2 = l / M.kappa 56 - end 57 - 58 - for i = 1, 3 do 59 - local m1 = M.m[i][1] 60 - local m2 = M.m[i][2] 61 - local m3 = M.m[i][3] 62 - 63 - for t = 0, 1 do 64 - local top1 = (284517 * m1 - 94839 * m3) * sub2 65 - local top2 = (838422 * m3 + 769860 * m2 + 731718 * m1) * l * sub2 - 769860 * t * l 66 - local bottom = (632260 * m3 - 126452 * m2) * sub2 + 126452 * t 67 - table.insert(result, { 68 - slope = top1 / bottom, 69 - intercept = top2 / bottom, 70 - }) 71 - end 72 - end 73 - return result 74 - end 75 - 76 - ---@param l number 77 - ---@return number 78 - function M.max_safe_chroma_for_l(l) 79 - local bounds = M.get_bounds(l) 80 - local min = 1.7976931348623157e+308 81 - 82 - for i = 1, 6 do 83 - local length = distance_line_from_origin(bounds[i]) 84 - if length >= 0 then min = math.min(min, length) end 85 - end 86 - return min 87 - end 88 - 89 - ---@param l number 90 - ---@param h number 91 - ---@return number 92 - function M.max_safe_chroma_for_lh(l, h) 93 - local hrad = h / 360 * math.pi * 2 94 - local bounds = M.get_bounds(l) 95 - local min = 1.7976931348623157e+308 96 - 97 - for i = 1, 6 do 98 - local bound = bounds[i] 99 - local length = length_of_ray_until_intersect(hrad, bound) 100 - if length >= 0 then min = math.min(min, length) end 101 - end 102 - return min 103 - end 104 - 105 - ---@param a number[] 106 - ---@param b number[] 107 - ---@return number 108 - function M.dot_product(a, b) 109 - local sum = 0 110 - for i = 1, 3 do 111 - sum = sum + a[i] * b[i] 112 - end 113 - return sum 114 - end 115 - 116 - ---@param c number 117 - ---@return number 118 - function M.from_linear(c) 119 - if c <= 0.0031308 then 120 - return 12.92 * c 121 - else 122 - return 1.055 * (c ^ 0.416666666666666685) - 0.055 123 - end 124 - end 125 - 126 - ---@param c number 127 - ---@return number 128 - function M.to_linear(c) 129 - if c > 0.04045 then 130 - return ((c + 0.055) / 1.055) ^ 2.4 131 - else 132 - return c / 12.92 133 - end 134 - end 135 - 136 - ---@param tuple number[] 137 - ---@return number[] 138 - function M.xyz_to_rgb(tuple) 139 - return { 140 - M.from_linear(M.dot_product(M.m[1], tuple)), 141 - M.from_linear(M.dot_product(M.m[2], tuple)), 142 - M.from_linear(M.dot_product(M.m[3], tuple)), 143 - } 144 - end 145 - 146 - ---@param tuple number[] 147 - ---@return number[] 148 - function M.rgb_to_xyz(tuple) 149 - local rgbl = { 150 - M.to_linear(tuple[1]), 151 - M.to_linear(tuple[2]), 152 - M.to_linear(tuple[3]), 153 - } 154 - return { 155 - M.dot_product(M.minv[1], rgbl), 156 - M.dot_product(M.minv[2], rgbl), 157 - M.dot_product(M.minv[3], rgbl), 158 - } 159 - end 160 - 161 - ---@param Y number 162 - ---@return number 163 - function M.y_to_l(Y) 164 - if Y <= M.epsilon then 165 - return Y / M.refY * M.kappa 166 - else 167 - return 116 * ((Y / M.refY) ^ 0.333333333333333315) - 16 168 - end 169 - end 170 - 171 - ---@param L number 172 - ---@return number 173 - function M.l_to_y(L) 174 - if L <= 8 then 175 - return M.refY * L / M.kappa 176 - else 177 - return M.refY * (((L + 16) / 116) ^ 3) 178 - end 179 - end 180 - 181 - ---@param tuple number[] 182 - ---@return number[] 183 - function M.xyz_to_luv(tuple) 184 - local X = tuple[1] 185 - local Y = tuple[2] 186 - local divider = X + 15 * Y + 3 * tuple[3] 187 - local varU = 4 * X 188 - local varV = 9 * Y 189 - if divider ~= 0 then 190 - varU = varU / divider 191 - varV = varV / divider 192 - else 193 - varU = 0 194 - varV = 0 195 - end 196 - local L = M.y_to_l(Y) 197 - if L == 0 then return { 0, 0, 0 } end 198 - return { L, 13 * L * (varU - M.refU), 13 * L * (varV - M.refV) } 199 - end 200 - 201 - ---@param tuple number[] 202 - ---@return number[] 203 - function M.luv_to_xyz(tuple) 204 - local L = tuple[1] 205 - local U = tuple[2] 206 - local V = tuple[3] 207 - if L == 0 then return { 0, 0, 0 } end 208 - local varU = U / (13 * L) + M.refU 209 - local varV = V / (13 * L) + M.refV 210 - local Y = M.l_to_y(L) 211 - local X = 0 - (9 * Y * varU) / (((varU - 4) * varV) - varU * varV) 212 - return { X, Y, (9 * Y - 15 * varV * Y - varV * X) / (3 * varV) } 213 - end 214 - 215 - ---@param tuple number[] 216 - ---@return number[] 217 - function M.luv_to_lch(tuple) 218 - local L = tuple[1] 219 - local U = tuple[2] 220 - local V = tuple[3] 221 - local C = math.sqrt(U * U + V * V) 222 - local H 223 - if C < 0.00000001 then 224 - H = 0 225 - else 226 - H = math.atan2(V, U) * 180.0 / 3.1415926535897932 227 - if H < 0 then H = 360 + H end 228 - end 229 - return { L, C, H } 230 - end 231 - 232 - ---@param tuple number[] 233 - ---@return number[] 234 - function M.lch_to_luv(tuple) 235 - local L = tuple[1] 236 - local C = tuple[2] 237 - local Hrad = tuple[3] / 360.0 * 2 * math.pi 238 - return { L, math.cos(Hrad) * C, math.sin(Hrad) * C } 239 - end 240 - 241 - ---@param tuple number[] 242 - ---@return number[] 243 - function M.hsluv_to_lch(tuple) 244 - local H = tuple[1] 245 - local S = tuple[2] 246 - local L = tuple[3] 247 - if L > 99.9999999 then return { 100, 0, H } end 248 - if L < 0.00000001 then return { 0, 0, H } end 249 - return { L, M.max_safe_chroma_for_lh(L, H) / 100 * S, H } 250 - end 251 - 252 - ---@param tuple number[] 253 - ---@return number[] 254 - function M.lch_to_hsluv(tuple) 255 - local L = tuple[1] 256 - local C = tuple[2] 257 - local H = tuple[3] 258 - local max_chroma = M.max_safe_chroma_for_lh(L, H) 259 - if L > 99.9999999 then return { H, 0, 100 } end 260 - if L < 0.00000001 then return { H, 0, 0 } end 261 - 262 - return { H, C / max_chroma * 100, L } 263 - end 264 - 265 - ---@param tuple number[] 266 - ---@return number[] 267 - function M.hpluv_to_lch(tuple) 268 - local H = tuple[1] 269 - local S = tuple[2] 270 - local L = tuple[3] 271 - if L > 99.9999999 then return { 100, 0, H } end 272 - if L < 0.00000001 then return { 0, 0, H } end 273 - return { L, M.max_safe_chroma_for_l(L) / 100 * S, H } 274 - end 275 - 276 - ---@param tuple number[] 277 - ---@return number[] 278 - function M.lch_to_hpluv(tuple) 279 - local L = tuple[1] 280 - local C = tuple[2] 281 - local H = tuple[3] 282 - if L > 99.9999999 then return { H, 0, 100 } end 283 - if L < 0.00000001 then return { H, 0, 0 } end 284 - return { H, C / M.max_safe_chroma_for_l(L) * 100, L } 285 - end 286 - 287 - ---@param tuple number[] 288 - ---@return string 289 - function M.rgb_to_hex(tuple) 290 - local h = "#" 291 - for i = 1, 3 do 292 - local c = math.floor(tuple[i] * 255 + 0.5) 293 - local digit2 = math.fmod(c, 16) 294 - local x = (c - digit2) / 16 295 - local digit1 = math.floor(x) 296 - h = h .. string.sub(hexChars, digit1 + 1, digit1 + 1) 297 - h = h .. string.sub(hexChars, digit2 + 1, digit2 + 1) 298 - end 299 - return h 300 - end 301 - 302 - ---@param hex string 303 - ---@return number[] 304 - function M.hex_to_rgb(hex) 305 - hex = string.lower(hex) 306 - local ret = {} 307 - for i = 0, 2 do 308 - local char1 = string.sub(hex, i * 2 + 2, i * 2 + 2) 309 - local char2 = string.sub(hex, i * 2 + 3, i * 2 + 3) 310 - local digit1 = string.find(hexChars, char1) - 1 311 - local digit2 = string.find(hexChars, char2) - 1 312 - ret[i + 1] = (digit1 * 16 + digit2) / 255.0 313 - end 314 - return ret 315 - end 316 - 317 - ---@param tuple number[] 318 - ---@return number[] 319 - function M.lch_to_rgb(tuple) return M.xyz_to_rgb(M.luv_to_xyz(M.lch_to_luv(tuple))) end 320 - 321 - ---@param tuple number[] 322 - ---@return number[] 323 - function M.rgb_to_lch(tuple) return M.luv_to_lch(M.xyz_to_luv(M.rgb_to_xyz(tuple))) end 324 - 325 - ---@param tuple number[] 326 - ---@return number[] 327 - function M.hsluv_to_rgb(tuple) return M.lch_to_rgb(M.hsluv_to_lch(tuple)) end 328 - 329 - ---@param tuple number[] 330 - ---@return number[] 331 - function M.rgb_to_hsluv(tuple) return M.lch_to_hsluv(M.rgb_to_lch(tuple)) end 332 - 333 - ---@param tuple number[] 334 - ---@return number[] 335 - function M.hpluv_to_rgb(tuple) return M.lch_to_rgb(M.hpluv_to_lch(tuple)) end 336 - 337 - ---@param tuple number[] 338 - ---@return number[] 339 - function M.rgb_to_hpluv(tuple) return M.lch_to_hpluv(M.rgb_to_lch(tuple)) end 340 - 341 - ---@param tuple number[] 342 - ---@return string 343 - function M.hsluv_to_hex(tuple) return M.rgb_to_hex(M.hsluv_to_rgb(tuple)) end 344 - 345 - ---@param tuple number[] 346 - ---@return string 347 - function M.hpluv_to_hex(tuple) return M.rgb_to_hex(M.hpluv_to_rgb(tuple)) end 348 - 349 - ---@param s string 350 - ---@return number[] 351 - function M.hex_to_hsluv(s) return M.rgb_to_hsluv(M.hex_to_rgb(s)) end 352 - 353 - ---@param s string 354 - ---@return number[] 355 - function M.hex_to_hpluv(s) return M.rgb_to_hpluv(M.hex_to_rgb(s)) end 356 - 357 - ---@type table<number, table<number, number>> 358 - M.m = { 359 - { 3.240969941904521, -1.537383177570093, -0.498610760293 }, 360 - { -0.96924363628087, 1.87596750150772, 0.041555057407175 }, 361 - { 0.055630079696993, -0.20397695888897, 1.056971514242878 }, 362 - } 363 - 364 - ---@type table<number, table<number, number>> 365 - M.minv = { 366 - { 0.41239079926595, 0.35758433938387, 0.18048078840183 }, 367 - { 0.21263900587151, 0.71516867876775, 0.072192315360733 }, 368 - { 0.019330818715591, 0.11919477979462, 0.95053215224966 }, 369 - } 370 - 371 - M.refY = 1.0 372 - M.refU = 0.19783000664283 373 - M.refV = 0.46831999493879 374 - M.kappa = 903.2962962 375 - M.epsilon = 0.0088564516 376 - 377 - return M
+66 -68
lua/nor/palettes.lua
··· 1 - local hsluv = require("nor.hsluv") 2 - 3 1 ---@class nor.Color 4 2 ---@field bg string 5 3 ---@field mid string ··· 28 26 ---@type nor.Palette 29 27 local dark = { 30 28 bg = "#000000", 31 - bg_dim = hsluv.hsluv_to_hex({ 0, 0, 15 }), 32 - bg_alt = hsluv.hsluv_to_hex({ 0, 0, 30 }), 33 - fg_hint = hsluv.hsluv_to_hex({ 0, 0, 45 }), 34 - fg_dim = hsluv.hsluv_to_hex({ 0, 0, 65 }), 35 - fg_alt = hsluv.hsluv_to_hex({ 0, 0, 80 }), 29 + bg_dim = "#262626", 30 + bg_alt = "#474747", 31 + fg_hint = "#6a6a6a", 32 + fg_dim = "#9e9e9e", 33 + fg_alt = "#c6c6c6", 36 34 fg = "#ffffff", 37 35 red = { 38 - bg = hsluv.hsluv_to_hex({ 15, 50, 20 }), 39 - mid = hsluv.hsluv_to_hex({ 12, 60, 45 }), 40 - fg = hsluv.hsluv_to_hex({ 12, 65, 60 }), 41 - fg_intense = hsluv.hsluv_to_hex({ 12, 80, 70 }), 36 + bg = "#50231f", 37 + mid = "#b84243", 38 + fg = "#e26d6d", 39 + fg_intense = "#f29090", 42 40 }, 43 41 orange = { 44 - bg = hsluv.hsluv_to_hex({ 50, 65, 20 }), 45 - mid = hsluv.hsluv_to_hex({ 40, 70, 50 }), 46 - fg = hsluv.hsluv_to_hex({ 25, 75, 60 }), 47 - fg_intense = hsluv.hsluv_to_hex({ 25, 85, 70 }), 42 + bg = "#3e2d19", 43 + mid = "#a06b3f", 44 + fg = "#de7247", 45 + fg_intense = "#f69070", 48 46 }, 49 47 green = { 50 - bg = hsluv.hsluv_to_hex({ 160, 65, 20 }), 51 - mid = hsluv.hsluv_to_hex({ 150, 70, 50 }), 52 - fg = hsluv.hsluv_to_hex({ 140, 65, 70 }), 53 - fg_intense = hsluv.hsluv_to_hex({ 140, 85, 70 }), 48 + bg = "#1a352c", 49 + mid = "#418365", 50 + fg = "#66bd86", 51 + fg_intense = "#43c178", 54 52 }, 55 53 blue = { 56 - bg = hsluv.hsluv_to_hex({ 215, 65, 25 }), 57 - mid = hsluv.hsluv_to_hex({ 205, 70, 50 }), 58 - fg = hsluv.hsluv_to_hex({ 190, 70, 65 }), 59 - fg_intense = hsluv.hsluv_to_hex({ 190, 80, 70 }), 54 + bg = "#224047", 55 + mid = "#448088", 56 + fg = "#5baba9", 57 + fg_intense = "#51bbb9", 60 58 }, 61 59 cyan = { 62 - bg = hsluv.hsluv_to_hex({ 185, 65, 25 }), 63 - mid = hsluv.hsluv_to_hex({ 175, 70, 50 }), 64 - fg = hsluv.hsluv_to_hex({ 170, 70, 70 }), 65 - fg_intense = hsluv.hsluv_to_hex({ 170, 80, 80 }), 60 + bg = "#21413e", 61 + mid = "#428277", 62 + fg = "#61bba8", 63 + fg_intense = "#5edbc2", 66 64 }, 67 65 magenta = { 68 - bg = hsluv.hsluv_to_hex({ 320, 50, 20 }), 69 - mid = hsluv.hsluv_to_hex({ 325, 65, 50 }), 70 - fg = hsluv.hsluv_to_hex({ 335, 70, 65 }), 71 - fg_intense = hsluv.hsluv_to_hex({ 335, 80, 75 }), 66 + bg = "#492341", 67 + mid = "#bb4d9e", 68 + fg = "#e57ab8", 69 + fg_intense = "#f29fcd", 72 70 }, 73 - success = hsluv.hsluv_to_hex({ 150, 85, 60 }), 74 - error = hsluv.hsluv_to_hex({ 12, 80, 50 }), 75 - warning = hsluv.hsluv_to_hex({ 55, 95, 70 }), 76 - info = hsluv.hsluv_to_hex({ 205, 85, 60 }), 71 + success = "#38a277", 72 + error = "#df3435", 73 + warning = "#dba124", 74 + info = "#3b9ea9", 77 75 } 78 76 79 77 ---@type nor.Palette 80 78 local light = { 81 79 bg = "#ffffff", 82 - bg_dim = hsluv.hsluv_to_hex({ 0, 0, 96 }), 83 - bg_alt = hsluv.hsluv_to_hex({ 0, 0, 85 }), 84 - fg_hint = hsluv.hsluv_to_hex({ 0, 0, 75 }), 85 - fg_dim = hsluv.hsluv_to_hex({ 0, 0, 45 }), 86 - fg_alt = hsluv.hsluv_to_hex({ 0, 0, 25 }), 80 + bg_dim = "#f3f3f3", 81 + bg_alt = "#d4d4d4", 82 + fg_hint = "#b9b9b9", 83 + fg_dim = "#6a6a6a", 84 + fg_alt = "#3b3b3b", 87 85 fg = "#000000", 88 86 red = { 89 - bg = hsluv.hsluv_to_hex({ 15, 50, 90 }), 90 - mid = hsluv.hsluv_to_hex({ 12, 60, 55 }), 91 - fg = hsluv.hsluv_to_hex({ 12, 70, 45 }), 92 - fg_intense = hsluv.hsluv_to_hex({ 12, 85, 35 }), 87 + bg = "#f1dedd", 88 + mid = "#db5959", 89 + fg = "#c13939", 90 + fg_intense = "#a11c1d", 93 91 }, 94 92 orange = { 95 - bg = hsluv.hsluv_to_hex({ 50, 65, 90 }), 96 - mid = hsluv.hsluv_to_hex({ 40, 75, 55 }), 97 - fg = hsluv.hsluv_to_hex({ 25, 80, 45 }), 98 - fg_intense = hsluv.hsluv_to_hex({ 25, 90, 35 }), 93 + bg = "#f6deca", 94 + mid = "#b37640", 95 + fg = "#a8522d", 96 + fg_intense = "#883c15", 99 97 }, 100 98 green = { 101 - bg = hsluv.hsluv_to_hex({ 160, 50, 90 }), 102 - mid = hsluv.hsluv_to_hex({ 150, 60, 50 }), 103 - fg = hsluv.hsluv_to_hex({ 140, 65, 35 }), 104 - fg_intense = hsluv.hsluv_to_hex({ 140, 80, 25 }), 99 + bg = "#a6f2d7", 100 + mid = "#4b8268", 101 + fg = "#2e5b3f", 102 + fg_intense = "#164429", 105 103 }, 106 104 blue = { 107 - bg = hsluv.hsluv_to_hex({ 215, 50, 90 }), 108 - mid = hsluv.hsluv_to_hex({ 205, 60, 50 }), 109 - fg = hsluv.hsluv_to_hex({ 210, 70, 40 }), 110 - fg_intense = hsluv.hsluv_to_hex({ 210, 85, 30 }), 105 + bg = "#cae7f1", 106 + mid = "#4e7f85", 107 + fg = "#35656e", 108 + fg_intense = "#194d56", 111 109 }, 112 110 cyan = { 113 - bg = hsluv.hsluv_to_hex({ 185, 50, 90 }), 114 - mid = hsluv.hsluv_to_hex({ 175, 60, 50 }), 115 - fg = hsluv.hsluv_to_hex({ 170, 65, 35 }), 116 - fg_intense = hsluv.hsluv_to_hex({ 170, 80, 25 }), 111 + bg = "#a6f0ea", 112 + mid = "#4c8077", 113 + fg = "#305a51", 114 + fg_intense = "#17423a", 117 115 }, 118 116 magenta = { 119 - bg = hsluv.hsluv_to_hex({ 310, 50, 90 }), 120 - mid = hsluv.hsluv_to_hex({ 300, 60, 50 }), 121 - fg = hsluv.hsluv_to_hex({ 295, 70, 40 }), 122 - fg_intense = hsluv.hsluv_to_hex({ 295, 85, 30 }), 117 + bg = "#f1dcf0", 118 + mid = "#a956b7", 119 + fg = "#8a3ca1", 120 + fg_intense = "#711f86", 123 121 }, 124 - success = hsluv.hsluv_to_hex({ 140, 70, 80 }), 125 - error = hsluv.hsluv_to_hex({ 12, 80, 45 }), 126 - warning = hsluv.hsluv_to_hex({ 55, 90, 45 }), 127 - info = hsluv.hsluv_to_hex({ 205, 70, 45 }), 122 + success = "#6edc98", 123 + error = "#c92d2f", 124 + warning = "#88641e", 125 + info = "#3c737a", 128 126 } 129 127 130 128 return { dark = dark, light = light }