User:Surjection/swatch2
Jump to navigation
Jump to search
Now outdated: see Wiktionary:Palette/numbered
An alternative swatch: User:Surjection/swatch3 (both WCAG and APCA checks)
The base colors for each set are:
red | #e63949 |
scarlet | #e0562d |
orange | #d9760f |
amber | #cf9211 |
yellow | #baa72f |
lime | #97b53c |
green | #60b334 |
teal | #32b378 |
cyan | #28a0b5 |
blue | #3271f0 |
indigo | #584ee6 |
purple | #7f38e8 |
magenta | #9d2cdb |
rose | #a6448c |
grey | #666666 |
brown | #856142 |
The white foreground color is specified as #eaecf0 and the black foreground color as #202122.
We aim for the following contrast ratios, whichever is stricter (in light mode against the black foreground color and in night mode against the white foreground color):
Number | WCAG | APCA |
---|---|---|
0 | 15.00 | – |
1 | 13.25 | – |
2 | 11.50 | – |
3 | 9.25 | – |
4 | 7.50 | – |
5 | 6.00 | – |
6 | 4.50 | – |
7 | 3.00 | – |
8 | 2.10 | – |
9 | 1.40 | – |
Swatches
[edit]Light mode
[edit]red-0
|
red-1
|
red-2
|
red-3
|
red-4
|
red-5
|
red-6
|
red-7
|
red-8
|
red-9
|
scarlet-0
|
scarlet-1
|
scarlet-2
|
scarlet-3
|
scarlet-4
|
scarlet-5
|
scarlet-6
|
scarlet-7
|
scarlet-8
|
scarlet-9
|
orange-0
|
orange-1
|
orange-2
|
orange-3
|
orange-4
|
orange-5
|
orange-6
|
orange-7
|
orange-8
|
orange-9
|
amber-0
|
amber-1
|
amber-2
|
amber-3
|
amber-4
|
amber-5
|
amber-6
|
amber-7
|
amber-8
|
amber-9
|
yellow-0
|
yellow-1
|
yellow-2
|
yellow-3
|
yellow-4
|
yellow-5
|
yellow-6
|
yellow-7
|
yellow-8
|
yellow-9
|
lime-0
|
lime-1
|
lime-2
|
lime-3
|
lime-4
|
lime-5
|
lime-6
|
lime-7
|
lime-8
|
lime-9
|
green-0
|
green-1
|
green-2
|
green-3
|
green-4
|
green-5
|
green-6
|
green-7
|
green-8
|
green-9
|
teal-0
|
teal-1
|
teal-2
|
teal-3
|
teal-4
|
teal-5
|
teal-6
|
teal-7
|
teal-8
|
teal-9
|
cyan-0
|
cyan-1
|
cyan-2
|
cyan-3
|
cyan-4
|
cyan-5
|
cyan-6
|
cyan-7
|
cyan-8
|
cyan-9
|
blue-0
|
blue-1
|
blue-2
|
blue-3
|
blue-4
|
blue-5
|
blue-6
|
blue-7
|
blue-8
|
blue-9
|
indigo-0
|
indigo-1
|
indigo-2
|
indigo-3
|
indigo-4
|
indigo-5
|
indigo-6
|
indigo-7
|
indigo-8
|
indigo-9
|
purple-0
|
purple-1
|
purple-2
|
purple-3
|
purple-4
|
purple-5
|
purple-6
|
purple-7
|
purple-8
|
purple-9
|
magenta-0
|
magenta-1
|
magenta-2
|
magenta-3
|
magenta-4
|
magenta-5
|
magenta-6
|
magenta-7
|
magenta-8
|
magenta-9
|
rose-0
|
rose-1
|
rose-2
|
rose-3
|
rose-4
|
rose-5
|
rose-6
|
rose-7
|
rose-8
|
rose-9
|
grey-0
|
grey-1
|
grey-2
|
grey-3
|
grey-4
|
grey-5
|
grey-6
|
grey-7
|
grey-8
|
grey-9
|
brown-0
|
brown-1
|
brown-2
|
brown-3
|
brown-4
|
brown-5
|
brown-6
|
brown-7
|
brown-8
|
brown-9
|
red-0
|
red-1
|
red-2
|
red-3
|
red-4
|
red-5
|
red-6
|
red-7
|
red-8
|
red-9
|
scarlet-0
|
scarlet-1
|
scarlet-2
|
scarlet-3
|
scarlet-4
|
scarlet-5
|
scarlet-6
|
scarlet-7
|
scarlet-8
|
scarlet-9
|
orange-0
|
orange-1
|
orange-2
|
orange-3
|
orange-4
|
orange-5
|
orange-6
|
orange-7
|
orange-8
|
orange-9
|
amber-0
|
amber-1
|
amber-2
|
amber-3
|
amber-4
|
amber-5
|
amber-6
|
amber-7
|
amber-8
|
amber-9
|
yellow-0
|
yellow-1
|
yellow-2
|
yellow-3
|
yellow-4
|
yellow-5
|
yellow-6
|
yellow-7
|
yellow-8
|
yellow-9
|
lime-0
|
lime-1
|
lime-2
|
lime-3
|
lime-4
|
lime-5
|
lime-6
|
lime-7
|
lime-8
|
lime-9
|
green-0
|
green-1
|
green-2
|
green-3
|
green-4
|
green-5
|
green-6
|
green-7
|
green-8
|
green-9
|
teal-0
|
teal-1
|
teal-2
|
teal-3
|
teal-4
|
teal-5
|
teal-6
|
teal-7
|
teal-8
|
teal-9
|
cyan-0
|
cyan-1
|
cyan-2
|
cyan-3
|
cyan-4
|
cyan-5
|
cyan-6
|
cyan-7
|
cyan-8
|
cyan-9
|
blue-0
|
blue-1
|
blue-2
|
blue-3
|
blue-4
|
blue-5
|
blue-6
|
blue-7
|
blue-8
|
blue-9
|
indigo-0
|
indigo-1
|
indigo-2
|
indigo-3
|
indigo-4
|
indigo-5
|
indigo-6
|
indigo-7
|
indigo-8
|
indigo-9
|
purple-0
|
purple-1
|
purple-2
|
purple-3
|
purple-4
|
purple-5
|
purple-6
|
purple-7
|
purple-8
|
purple-9
|
magenta-0
|
magenta-1
|
magenta-2
|
magenta-3
|
magenta-4
|
magenta-5
|
magenta-6
|
magenta-7
|
magenta-8
|
magenta-9
|
rose-0
|
rose-1
|
rose-2
|
rose-3
|
rose-4
|
rose-5
|
rose-6
|
rose-7
|
rose-8
|
rose-9
|
grey-0
|
grey-1
|
grey-2
|
grey-3
|
grey-4
|
grey-5
|
grey-6
|
grey-7
|
grey-8
|
grey-9
|
brown-0
|
brown-1
|
brown-2
|
brown-3
|
brown-4
|
brown-5
|
brown-6
|
brown-7
|
brown-8
|
brown-9
|
Night mode
[edit]red-0
|
red-1
|
red-2
|
red-3
|
red-4
|
red-5
|
red-6
|
red-7
|
red-8
|
red-9
|
scarlet-0
|
scarlet-1
|
scarlet-2
|
scarlet-3
|
scarlet-4
|
scarlet-5
|
scarlet-6
|
scarlet-7
|
scarlet-8
|
scarlet-9
|
orange-0
|
orange-1
|
orange-2
|
orange-3
|
orange-4
|
orange-5
|
orange-6
|
orange-7
|
orange-8
|
orange-9
|
amber-0
|
amber-1
|
amber-2
|
amber-3
|
amber-4
|
amber-5
|
amber-6
|
amber-7
|
amber-8
|
amber-9
|
yellow-0
|
yellow-1
|
yellow-2
|
yellow-3
|
yellow-4
|
yellow-5
|
yellow-6
|
yellow-7
|
yellow-8
|
yellow-9
|
lime-0
|
lime-1
|
lime-2
|
lime-3
|
lime-4
|
lime-5
|
lime-6
|
lime-7
|
lime-8
|
lime-9
|
green-0
|
green-1
|
green-2
|
green-3
|
green-4
|
green-5
|
green-6
|
green-7
|
green-8
|
green-9
|
teal-0
|
teal-1
|
teal-2
|
teal-3
|
teal-4
|
teal-5
|
teal-6
|
teal-7
|
teal-8
|
teal-9
|
cyan-0
|
cyan-1
|
cyan-2
|
cyan-3
|
cyan-4
|
cyan-5
|
cyan-6
|
cyan-7
|
cyan-8
|
cyan-9
|
blue-0
|
blue-1
|
blue-2
|
blue-3
|
blue-4
|
blue-5
|
blue-6
|
blue-7
|
blue-8
|
blue-9
|
indigo-0
|
indigo-1
|
indigo-2
|
indigo-3
|
indigo-4
|
indigo-5
|
indigo-6
|
indigo-7
|
indigo-8
|
indigo-9
|
purple-0
|
purple-1
|
purple-2
|
purple-3
|
purple-4
|
purple-5
|
purple-6
|
purple-7
|
purple-8
|
purple-9
|
magenta-0
|
magenta-1
|
magenta-2
|
magenta-3
|
magenta-4
|
magenta-5
|
magenta-6
|
magenta-7
|
magenta-8
|
magenta-9
|
rose-0
|
rose-1
|
rose-2
|
rose-3
|
rose-4
|
rose-5
|
rose-6
|
rose-7
|
rose-8
|
rose-9
|
grey-0
|
grey-1
|
grey-2
|
grey-3
|
grey-4
|
grey-5
|
grey-6
|
grey-7
|
grey-8
|
grey-9
|
brown-0
|
brown-1
|
brown-2
|
brown-3
|
brown-4
|
brown-5
|
brown-6
|
brown-7
|
brown-8
|
brown-9
|
red-0
|
red-1
|
red-2
|
red-3
|
red-4
|
red-5
|
red-6
|
red-7
|
red-8
|
red-9
|
scarlet-0
|
scarlet-1
|
scarlet-2
|
scarlet-3
|
scarlet-4
|
scarlet-5
|
scarlet-6
|
scarlet-7
|
scarlet-8
|
scarlet-9
|
orange-0
|
orange-1
|
orange-2
|
orange-3
|
orange-4
|
orange-5
|
orange-6
|
orange-7
|
orange-8
|
orange-9
|
amber-0
|
amber-1
|
amber-2
|
amber-3
|
amber-4
|
amber-5
|
amber-6
|
amber-7
|
amber-8
|
amber-9
|
yellow-0
|
yellow-1
|
yellow-2
|
yellow-3
|
yellow-4
|
yellow-5
|
yellow-6
|
yellow-7
|
yellow-8
|
yellow-9
|
lime-0
|
lime-1
|
lime-2
|
lime-3
|
lime-4
|
lime-5
|
lime-6
|
lime-7
|
lime-8
|
lime-9
|
green-0
|
green-1
|
green-2
|
green-3
|
green-4
|
green-5
|
green-6
|
green-7
|
green-8
|
green-9
|
teal-0
|
teal-1
|
teal-2
|
teal-3
|
teal-4
|
teal-5
|
teal-6
|
teal-7
|
teal-8
|
teal-9
|
cyan-0
|
cyan-1
|
cyan-2
|
cyan-3
|
cyan-4
|
cyan-5
|
cyan-6
|
cyan-7
|
cyan-8
|
cyan-9
|
blue-0
|
blue-1
|
blue-2
|
blue-3
|
blue-4
|
blue-5
|
blue-6
|
blue-7
|
blue-8
|
blue-9
|
indigo-0
|
indigo-1
|
indigo-2
|
indigo-3
|
indigo-4
|
indigo-5
|
indigo-6
|
indigo-7
|
indigo-8
|
indigo-9
|
purple-0
|
purple-1
|
purple-2
|
purple-3
|
purple-4
|
purple-5
|
purple-6
|
purple-7
|
purple-8
|
purple-9
|
magenta-0
|
magenta-1
|
magenta-2
|
magenta-3
|
magenta-4
|
magenta-5
|
magenta-6
|
magenta-7
|
magenta-8
|
magenta-9
|
rose-0
|
rose-1
|
rose-2
|
rose-3
|
rose-4
|
rose-5
|
rose-6
|
rose-7
|
rose-8
|
rose-9
|
grey-0
|
grey-1
|
grey-2
|
grey-3
|
grey-4
|
grey-5
|
grey-6
|
grey-7
|
grey-8
|
grey-9
|
brown-0
|
brown-1
|
brown-2
|
brown-3
|
brown-4
|
brown-5
|
brown-6
|
brown-7
|
brown-8
|
brown-9
|
Script
[edit]import colorsys
import re
IDENTIFICATION = "[[User:Surjection/swatch2]]"
HEX_COLOR_RGB = re.compile(r"^#[0-9A-Fa-f]{6}$")
def _hex_to_rgb_comp(x):
return int(x, 16) / 255.0
def hex_to_rgb(hex_color):
if not HEX_COLOR_RGB.match(hex_color):
raise ValueError("hex color format not supported")
r, g, b = hex_color[1:3], hex_color[3:5], hex_color[5:7]
return (_hex_to_rgb_comp(r), _hex_to_rgb_comp(g), _hex_to_rgb_comp(b))
def rgb_to_hex(c):
r, g, b = c
r = int(round(r * 255.0))
g = int(round(g * 255.0))
b = int(round(b * 255.0))
return "#{:02x}{:02x}{:02x}".format(r, g, b)
def wcag_to_srgb(x):
if x <= 0.03928:
return x / 12.2
else:
return ((x + 0.055) / 1.055) ** 2.4
def quantize(color):
r, g, b = color
r = int(round(r * 255)) / 255
g = int(round(g * 255)) / 255
b = int(round(b * 255)) / 255
return r, g, b
def wcag_luminance(color):
rs, gs, bs = color
r = wcag_to_srgb(rs)
g = wcag_to_srgb(gs)
b = wcag_to_srgb(bs)
return 0.2126 * r + 0.7152 * g + 0.0722 * b
def wcag_contrast(fgcolor, bgcolor):
fgluma = wcag_luminance(fgcolor)
bgluma = wcag_luminance(bgcolor)
ratio = (fgluma + 0.05) / (bgluma + 0.05)
if ratio < 1:
ratio = 1.0 / ratio
return ratio
def _simple_srgb_luma(color):
# <https://github.com/Myndex/SAPC-APCA/blob/master/documentation/APCA-W3-LaTeX.md>
# <https://en.wiktionary.org/w/index.php?title=Module:palette&oldid=82494940#L-57--L-94>
r, g, b = color
s_rco, s_gco, s_bco = 0.2126729, 0.7151522, 0.0721750
main_trc = 2.4
return clamp(0, s_rco * (r ** main_trc) + s_gco * (g ** main_trc) + s_bco * (b ** main_trc), 1)
def _apca_f_sc(y_c):
# <https://github.com/Myndex/SAPC-APCA/blob/master/documentation/APCA-W3-LaTeX.md>
# <https://en.wiktionary.org/w/index.php?title=Module:palette&oldid=82494940#L-57--L-94>
if y_c <= 0:
return 0
elif y_c <= 0.022:
return y_c + (0.022 - y_c) ** 1.414
else:
return y_c
def apca_contrast(fgcolor, bgcolor):
# <https://github.com/Myndex/SAPC-APCA/blob/master/documentation/APCA-W3-LaTeX.md>
# <https://en.wiktionary.org/w/index.php?title=Module:palette&oldid=82494940#L-57--L-94>
ys_fg = _simple_srgb_luma(fgcolor)
ys_bg = _simple_srgb_luma(bgcolor)
y_fg = _apca_f_sc(ys_fg)
y_bg = _apca_f_sc(ys_bg)
if y_bg > y_fg:
s_apc = (y_bg ** 0.56 - y_fg ** 0.57) * 1.14
else:
s_apc = (y_bg ** 0.65 - y_fg ** 0.62) * 1.14
if abs(s_apc) < 0.1:
l_c = 0
elif s_apc > 0:
l_c = (s_apc - 0.027) * 100
else:
l_c = (s_apc + 0.027) * 100
return l_c
def clamp(a, x, b):
return max(a, min(x, b))
def _lighten_comp(component, power):
if not 0 <= power <= 1:
raise ValueError("power must be within [0, 1]")
return power + component * (1 - power)
def lighten(color, power):
if not 0 <= power <= 1:
raise ValueError("power must be within [0, 1]")
r, g, b = color
return (_lighten_comp(r, power), _lighten_comp(g, power), _lighten_comp(b, power))
def deepen(color, power):
if not 0 <= power <= 1:
raise ValueError("power must be within [0, 1]")
r, g, b = color
h, s, v = colorsys.rgb_to_hsv(r, g, b)
s_new = (1 - (1 - s) * (1 - power)) * (s if s < 0.5 else 1)
v_new = v * (1 - power)
return colorsys.hsv_to_rgb(h, s_new, v_new)
def blend(color, offset):
if offset > 0:
return lighten(color, offset)
elif offset < 0:
return deepen(color, -offset)
return color
class ContrastedColorMaker:
def __init__(self, base_color, reference, night_mode):
self._night_mode = night_mode
# generate all possible colors
div = 512
options = {}
self._colors = {}
self._wcag = []
self._apca = []
for i in range(-div, div + 1):
offset = clamp(-1, i / div, 1)
color = quantize(blend(base_color, offset))
if color not in options:
options[color] = offset
self._colors[offset] = color
self._wcag.append((wcag_contrast(color, reference), offset))
self._apca.append((abs(apca_contrast(color, reference)), offset))
self._wcag.sort()
self._apca.sort()
def for_contrast(self, contrast_wcag, contrast_apca):
wcag_meets = set(offset for contrast, offset in self._wcag if contrast >= contrast_wcag)
if contrast_apca is None:
meets = wcag_meets
else:
apca_meets = set(offset for contrast, offset in self._apca if contrast >= contrast_apca)
meets = wcag_meets & apca_meets
if meets:
return self._colors[(max if self._night_mode else min)(meets)]
return max(self._wcag)[-1]
BASE = {
"red": hex_to_rgb("#e63949"),
"scarlet": hex_to_rgb("#e0562d"),
"orange": hex_to_rgb("#d9760f"),
"amber": hex_to_rgb("#cf9211"),
"yellow": hex_to_rgb("#baa72f"),
"lime": hex_to_rgb("#97b53c"),
"green": hex_to_rgb("#60b334"),
"teal": hex_to_rgb("#32b378"),
"cyan": hex_to_rgb("#28a0b5"),
"blue": hex_to_rgb("#3271f0"),
"indigo": hex_to_rgb("#584ee6"),
"purple": hex_to_rgb("#7f38e8"),
"magenta": hex_to_rgb("#9d2cdb"),
"rose": hex_to_rgb("#a6448c"),
"grey": hex_to_rgb("#666666"),
"brown": hex_to_rgb("#856142"),
}
WHITE_BG = "#ffffff"
BLACK_FG = "#202122"
WHITE_FG = "#eaecf0"
BLACK_BG = "#101418"
white = hex_to_rgb(WHITE_FG)
black = hex_to_rgb(BLACK_FG)
CONTRASTS = {
0: (15.00, None),
1: (13.25, None),
2: (11.50, None),
3: ( 9.25, None),
4: ( 7.50, None),
5: ( 6.00, None),
6: ( 4.50, None),
7: ( 3.00, None),
8: ( 2.10, None),
9: ( 1.40, None),
}
colors_light = {}
colors_night = {}
for base_color_name, base_color in BASE.items():
tints_light = colors_light[base_color_name] = {}
tints_night = colors_night[base_color_name] = {}
contrasted_color_maker_light = ContrastedColorMaker(base_color, black, False)
contrasted_color_maker_night = ContrastedColorMaker(base_color, white, True)
for tint_number, (contrast_wcag, contrast_apca) in CONTRASTS.items():
tints_light[tint_number] = contrasted_color_maker_light.for_contrast(contrast_wcag, contrast_apca)
tints_night[tint_number] = contrasted_color_maker_night.for_contrast(contrast_wcag, contrast_apca)
def make_swatch_bg(colors, fg_color):
lines = []
for base_color_name, tints in colors.items():
if lines:
lines.append('|-')
else:
lines.append('{|')
for tint_number, color in tints.items():
name = f"{base_color_name}-{tint_number}"
lines.append(f'| style="background:{rgb_to_hex(color)}" | <div style="color:{fg_color};padding:15px 5px">{name}</div>')
lines.append("|}")
return "\n".join(lines)
def make_swatch_fg(colors, bg_color):
lines = []
for base_color_name, tints in colors.items():
if lines:
lines.append('|-')
else:
lines.append("{| " + f'style="background:{bg_color}"')
for tint_number, color in tints.items():
name = f"{base_color_name}-{tint_number}"
lines.append(f'| <div style="color:{rgb_to_hex(color)};padding:5px">{name}</div>')
lines.append("|}")
return "\n".join(lines)
def make_palette(colors_light, colors_night):
def wrap_frame(prefix, indent, suffix):
def _wrap(lines):
return prefix + "\n" + "\n".join(indent + line for line in lines) + "\n" + suffix
return _wrap
def gather_colors(colors):
rules = ["/* Autogenerated with " + IDENTIFICATION + " */"]
for base_color_name, tints in colors.items():
for tint_number, color in tints.items():
name = f"{base_color_name}-{tint_number}"
rules.append(f"--wikt-palette-{name}: {rgb_to_hex(color)};")
return rules
light_block = wrap_frame(":root, .skin-invert, .notheme {", "\t", "}")
night_block_1 = wrap_frame("@media screen {\n\thtml.skin-theme-clientpref-night {", "\t\t", "\t}\n}")
night_block_2 = wrap_frame("@media screen and (prefers-color-scheme: dark) {\n\thtml.skin-theme-clientpref-os {".strip(), "\t\t", "\t}\n}")
rules_light = gather_colors(colors_light)
rules_night = gather_colors(colors_night)
return light_block(rules_light) + "\n\n/* Styles need to be duplicated exactly between these two selectors. */\n" + night_block_1(rules_night) + "\n" + night_block_2(rules_night)
def print_swatches():
print("==Swatches==")
print("")
print("===Light mode===")
print("<!-- Autogenerated with " + IDENTIFICATION + " */ -->")
print(make_swatch_bg(colors_light, BLACK_FG))
print("")
print(make_swatch_fg(colors_light, WHITE_BG))
print("")
print("===Night mode===")
print("<!-- Autogenerated with " + IDENTIFICATION + " */ -->")
print(make_swatch_bg(colors_night, WHITE_FG))
print("")
print(make_swatch_fg(colors_night, BLACK_BG))
def print_palette():
print(make_palette(colors_light, colors_night))
print_swatches()