location | light mode | dark mode | WCAG contrast (with text colour) | WCAG contrast (with link colour) |
---|---|---|---|---|
Babel-0 left | #FFB3B3 | #4A0000 | Light mode: 9.485 (AAA) Dark mode: 13.75 (AAA) |
Light mode: 9.485 (AAA) Dark mode: 6.545 (AA) |
Babel-1 left | #FEA6DB | #570035 | Light mode: 8.979 (AAA) Dark mode: 12.18 (AAA) |
Light mode: 8.979 (AAA) Dark mode: 5.801 (AA) |
Babel-2 left | #EA9BFD | #4F0162 | Light mode: 8.145 (AAA) Dark mode: 11.73 (AAA) |
Light mode: 8.145 (AAA) Dark mode: 5.584 (AA) |
Babel-3 left | #A68FFC | #19026D | Light mode: 6.110 (AA) Dark mode: 13.97 (AAA) |
Light mode: 6.110 (AA) Dark mode: 6.649 (AA) |
Babel-4 left | #84B2FA | #043178 | Light mode: 7.472 (AAA) Dark mode: 10.34 (AAA) |
Light mode: 7.472 (AAA) Dark mode: 4.925 (AA) |
Babel-5 left | #78F7F8 | #068384 | Light mode: 12.64 (AAA) Dark mode: 3.870 (FAIL) |
Light mode: 12.64 (AAA) Dark mode: 1.842 (FAIL) |
Babel-N left | #6DF7A6 | #078E3F | Light mode: 11.90 (AAA) Dark mode: 3.586 (FAIL) |
Light mode: 11.90 (AAA) Dark mode: 1.707 (FAIL) |
Babel-0 right | #FFE0E8 | #410010 | Light mode: 13.11 (AAA) Dark mode: 14.40 (AAA) |
Light mode: 13.11 (AAA) Dark mode: 6.858 (AA) |
Babel-1 right | #FEDBF8 | #390130 | Light mode: 12.82 (AAA) Dark mode: 14.54 (AAA) |
Light mode: 12.82 (AAA) Dark mode: 6.921 (AA) |
Babel-2 right | #EFD6FE | #210135 | Light mode: 12.07 (AAA) Dark mode: 15.84 (AAA) |
Light mode: 12.07 (AAA) Dark mode: 7.542 (AAA) |
Babel-3 right | #D5D2FD | #05022F | Light mode: 11.11 (AAA) Dark mode: 16.81 (AAA) |
Light mode: 11.11 (AAA) Dark mode: 8.001 (AAA) |
Babel-4 right | #CDE4FD | #011831 | Light mode: 12.37 (AAA) Dark mode: 15.09 (AAA) |
Light mode: 12.37 (AAA) Dark mode: 7.184 (AAA) |
Babel-5 right | #C9FCFB | #023534 | Light mode: 14.43 (AAA) Dark mode: 11.37 (AAA) |
Light mode: 14.43 (AAA) Dark mode: 5.412 (AA) |
Babel-N right | #C4FCDB | #023A19 | Light mode: 14.05 (AAA) Dark mode: 10.94 (AAA) |
Light mode: 14.05 (AAA) Dark mode: 5.210 (AA) |
.babel-box {
float: left;
box-sizing: border-box;
border-style: solid;
border-width: 1px;
margin: 1px;
}
.babel-box-wrapper .babel-box {
width: calc(100% - 2px); /* subtract horizontal margin */
}
.babel-box table {
border-spacing: 0; /* replacement for cellspacing="0" */
}
.babel-box .babel-content {
width: 100%;
min-width: 238px;
height: 100%;
margin: 0;
}
.babel-box .babel-code {
width: 45px;
height: 45px;
text-align: center;
}
.babel-box .babel-text {
font-size: 8pt;
padding: 4pt;
line-height: 1.25em;
}
.babel-box.babel-0 { border-color: #FFB3B3; }
.babel-box.babel-0 .babel-content { background-color: #FFE0E8; }
.babel-box.babel-0 .babel-code { background-color: #FFB3B3; }
.babel-box.babel-1 { border-color: #FEA6DB; }
.babel-box.babel-1 .babel-content { background-color: #FEDBF8; }
.babel-box.babel-1 .babel-code { background-color: #FEA6DB; }
.babel-box.babel-2 { border-color: #EA9BFD; }
.babel-box.babel-2 .babel-content { background-color: #EFD6FE; }
.babel-box.babel-2 .babel-code { background-color: #EA9BFD; }
.babel-box.babel-3 { border-color: #A68FFC; }
.babel-box.babel-3 .babel-content { background-color: #D5D2FD; }
.babel-box.babel-3 .babel-code { background-color: #A68FFC; }
.babel-box.babel-4 { border-color: #84B2FA; }
.babel-box.babel-4 .babel-content { background-color: #CDE4FD; }
.babel-box.babel-4 .babel-code { background-color: #84B2FA; }
.babel-box.babel-5 { border-color: #78F7F8; }
.babel-box.babel-5 .babel-content { background-color: #C9FCFB; }
.babel-box.babel-5 .babel-code { background-color: #78F7F8; }
.babel-box.babel-N { border-color: #6DF7A6; }
.babel-box.babel-N .babel-content { background-color: #C4FCDB; }
.babel-box.babel-N .babel-code { background-color: #6DF7A6; }
/* dark mode colors; styles need to be duplicated exactly between these two media blocks */
@media screen {
html.skin-theme-clientpref-night .babel-box .babel-code a { color: #3366EE; }
html.skin-theme-clientpref-night .babel-box.babel-0 { border-color: #4A0000; }
html.skin-theme-clientpref-night .babel-box.babel-0 .babel-content { background-color: #410010; }
html.skin-theme-clientpref-night .babel-box.babel-0 .babel-code { background-color: #4A0000; }
html.skin-theme-clientpref-night .babel-box.babel-1 { border-color: #570035; }
html.skin-theme-clientpref-night .babel-box.babel-1 .babel-content { background-color: #390130; }
html.skin-theme-clientpref-night .babel-box.babel-1 .babel-code { background-color: #570035; }
html.skin-theme-clientpref-night .babel-box.babel-2 { border-color: #4F0162; }
html.skin-theme-clientpref-night .babel-box.babel-2 .babel-content { background-color: #210135; }
html.skin-theme-clientpref-night .babel-box.babel-2 .babel-code { background-color: #4F0162; }
html.skin-theme-clientpref-night .babel-box.babel-3 { border-color: #19026D; }
html.skin-theme-clientpref-night .babel-box.babel-3 .babel-content { background-color: #05022F; }
html.skin-theme-clientpref-night .babel-box.babel-3 .babel-code { background-color: #19026D; }
html.skin-theme-clientpref-night .babel-box.babel-4 { border-color: #043178; }
html.skin-theme-clientpref-night .babel-box.babel-4 .babel-content { background-color: #011831; }
html.skin-theme-clientpref-night .babel-box.babel-4 .babel-code { background-color: #043178; }
html.skin-theme-clientpref-night .babel-box.babel-5 { border-color: #068384; }
html.skin-theme-clientpref-night .babel-box.babel-5 .babel-content { background-color: #023534; }
html.skin-theme-clientpref-night .babel-box.babel-5 .babel-code { background-color: #068384; }
html.skin-theme-clientpref-night .babel-box.babel-N { border-color: #078E3F; }
html.skin-theme-clientpref-night .babel-box.babel-N .babel-content { background-color: #023A19; }
html.skin-theme-clientpref-night .babel-box.babel-N .babel-code { background-color: #078E3F; }
}
@media screen and (prefers-color-scheme: dark) {
html.skin-theme-clientpref-os .babel-box .babel-code a { color: #3366EE; }
html.skin-theme-clientpref-os .babel-box.babel-0 { border-color: #FFB3B3; }
html.skin-theme-clientpref-os .babel-box.babel-0 .babel-content { background-color: #FFE0E8; }
html.skin-theme-clientpref-os .babel-box.babel-0 .babel-code { color: #000000; }
html.skin-theme-clientpref-os .babel-box.babel-1 { border-color: #FEA6DB; }
html.skin-theme-clientpref-os .babel-box.babel-1 .babel-content { background-color: #FEDBF8; }
html.skin-theme-clientpref-os .babel-box.babel-1 .babel-code { color: #000000; }
html.skin-theme-clientpref-os .babel-box.babel-2 { border-color: #EA9BFD; }
html.skin-theme-clientpref-os .babel-box.babel-2 .babel-content { background-color: #EFD6FE; }
html.skin-theme-clientpref-os .babel-box.babel-2 .babel-code { color: #000000; }
html.skin-theme-clientpref-os .babel-box.babel-3 { border-color: #A68FFC; }
html.skin-theme-clientpref-os .babel-box.babel-3 .babel-content { background-color: #D5D2FD; }
html.skin-theme-clientpref-os .babel-box.babel-3 .babel-code { color: #000000; }
html.skin-theme-clientpref-os .babel-box.babel-4 { border-color: #84B2FA; }
html.skin-theme-clientpref-os .babel-box.babel-4 .babel-content { background-color: #CDE4FD; }
html.skin-theme-clientpref-os .babel-box.babel-4 .babel-code { color: #000000; }
html.skin-theme-clientpref-os .babel-box.babel-5 { border-color: #78F7F8; }
html.skin-theme-clientpref-os .babel-box.babel-5 .babel-content { background-color: #C9FCFB; }
html.skin-theme-clientpref-os .babel-box.babel-5 .babel-code { color: #000000; }
html.skin-theme-clientpref-os .babel-box.babel-N { border-color: #6DF7A6; }
html.skin-theme-clientpref-os .babel-box.babel-N .babel-content { background-color: #C4FCDB; }
html.skin-theme-clientpref-os .babel-box.babel-N .babel-code { color: #000000; }
}
local export = {}
local function hex_to_RGB(colour)
colour = colour:gsub("#", "")
return tonumber(colour:sub(1, 2), 16), tonumber(colour:sub(3, 4), 16), tonumber(colour:sub(5, 6), 16)
end
local function RGB_to_hex(r, g, b)
return ("#%02X%02X%02X"):format(r, g, b)
end
local function RGB_to_HSL(r, g, b)
r, g, b = r/255, g/255, b/255
local max = math.max(r, g, b)
local min = math.min(r, g, b)
local h, s, l = 0, 0, (max + min) / 2
if max ~= min then
local d = max - min
s = l > 0.5 and d / (2 - max - min) or d / (max + min)
if max == r then
h = ((g - b) / d) % 6
elseif max == g then
h = ((b - r) / d) + 2
else
h = ((r - g) / d) + 4
end
h = h * 60
end
return h, s, l
end
function HSL_to_RGB(h, s, l)
local function hue_to_RGB(p, q, t)
if t < 0 then t = t + 1 end
if t > 1 then t = t - 1 end
if t < 1/6 then return p + (q - p) * 6 * t end
if t < 1/2 then return q end
if t < 2/3 then return p + (q - p) * (2/3 - t) * 6 end
return p
end
h = h / 360
local r, g, b
if s == 0 then
r, g, b = l, l, l
else
local q = l < 0.5 and l * (1 + s) or l + s - l * s
local p = 2 * l - q
r = hue_to_RGB(p, q, h + 1/3)
g = hue_to_RGB(p, q, h)
b = hue_to_RGB(p, q, h - 1/3)
end
return math.floor(r * 255), math.floor(g * 255), math.floor(b * 255)
end
local function invert(colours, blend)
assert(blend <= 1.3, "blend cannot be greater than 1.3, but " .. blend .. " was provided")
blend = blend or 0
results = {}
n = 1
for _, hex in ipairs(colours) do
local h, s, l = RGB_to_HSL(hex_to_RGB(hex))
-- invert lightness and blend with lightness of dark mode background colour, 101418
l = (1 - blend) * math.max(1 - l, l - 0.81) + blend * 0.08
results = RGB_to_hex(HSL_to_RGB(h, s, l))
n = n + 1
end
return results
end
local function interpolate(first, second, steps, blend)
local results = {}
local h1, s1, l1 = RGB_to_HSL(hex_to_RGB(first))
local h2, s2, l2 = RGB_to_HSL(hex_to_RGB(second))
if math.abs(h2 - h1) < 180 then
if h2 > h1 then
h1 = h1 + 360
else
h2 = h2 + 360
end
end
n = 1
for i = 0, steps - 1 do
local t = i / (steps - 1)
local h = ((1 - t) * h1 + t * h2) % 360
local s = (1 - t) * s1 + t * s2
local l = (1 - t) * l1 + t * l2
results = RGB_to_hex(HSL_to_RGB(h, s, l))
n = n + 1
end
return results, invert(results, blend)
end
-- https://www.w3.org/WAI/GLhttps://dictious.com/en/Relative_luminance
local function get_WCAG_21_luminance(rgb)
local r, g, b = rgb, rgb, rgb
local function linearize(c)
if c <= 0.03928 then
return c / 12.92
else
return ((c + 0.055) / 1.055) ^ 2.4
end
end
r = linearize(r / 255)
g = linearize(g / 255)
b = linearize(b / 255)
return 0.2126 * r + 0.7152 * g + 0.0722 * b
end
-- Copied from ]:
-- Calculate the WCAG2.1 contrast ratio given two RGB tuples.
-- https://www.w3.org/WAI/WCAG21/Understanding/contrast-minimum.html#dfn-contrast-ratio
local function get_WCAG_21_contrast(rgb1, rgb2)
local luminance1 = get_WCAG_21_luminance(rgb1)
local luminance2 = get_WCAG_21_luminance(rgb2)
-- The lighter colour is used in the numerator.
local contrast_ratio
if luminance1 >= luminance2 then
contrast_ratio = (luminance1 + 0.05) / (luminance2 + 0.05)
else
contrast_ratio = (luminance2 + 0.05) / (luminance1 + 0.05)
end
return contrast_ratio
end
-- Get the WCAG2.1 rating as HTML, given a contrast value.
local function get_WCAG_21_rating(contrast_value)
if contrast_value >= 7 then
return "<span style=\"color:var(--wikt-palette-deepblue)\">(AAA)</span>"
elseif contrast_value >= 4.5 then
return "<span style=\"color:var(--wikt-palette-forestgreen)\">(AA)</span>"
else
return "<span style=\"color:var(--wikt-palette-red)\">(FAIL)</span>"
end
end
function export.show(frame)
local args = frame.args
--- COLOUR TABLE ---
local ret = [[{| class = "wikitable"
! location
! light mode
! dark mode
! WCAG contrast (with text colour)
! WCAG contrast (with <span style="color:var(--color-progressive--hover,#3056a9);">link</span> colour)
|- ]]
local blend = 0.03
local light_colours_left, dark_colours_left = interpolate(frame.args, frame.args, 7, blend)
local light_colours_right, dark_colours_right = interpolate(frame.args, frame.args, 7, blend)
local light_mode_text = { 32, 33, 34 }
local dark_mode_text = { 234, 236, 240 }
local light_mode_link = { 8, 50, 186 }
local dark_mode_link = { 136, 163, 232 }
local proficiencies = { "0", "1", "2", "3","4", "5", "N" }
local style_content = mw.title.new("Template:Babel/style.css"):getContent()
for n, i in ipairs(light_colours_left) do
style_content = style_content
:gsub("(%.babel%-box%.babel%-" .. proficiencies .. " { border%-color: )#+; }", "%1" .. i .. "; }")
:gsub("(%.babel%-box%.babel%-" .. proficiencies .. " %.babel%-code { background%-color: )#+; }", "%1" .. i .. "; }")
local contrast = get_WCAG_21_contrast({ hex_to_RGB(i) }, light_mode_text)
local link_contrast = get_WCAG_21_contrast({ hex_to_RGB(i) }, light_mode_link)
ret = ret .. '\n! Babel-' .. proficiencies .. ' left'
.. '\n| style="background-color:' .. i .. ';padding:1.7em 0.3em;" |<span style="position:relative;z-index:0;padding:2px 5px;background:var(--wikt-palette-lavender);opacity:0.8;border-radius:3px;">' .. i .. '</span>'
.. '\n| DARK_PLACEHOLDER_LEFT_' .. n
.. '\n| <b>Light mode:</b> ' .. tostring(contrast):sub(1, 5) .. ' ' .. get_WCAG_21_rating(contrast) .. '<br><b>Dark mode:</b> DARK_CONTRAST_LEFT_' .. n
.. '\n| <b>Light mode:</b> ' .. tostring(contrast):sub(1, 5) .. ' ' .. get_WCAG_21_rating(contrast) .. '<br><b>Dark mode:</b> DARK_LINK_CONTRAST_LEFT_' .. n
.. '\n|-'
end
for n, i in ipairs(light_colours_right) do
style_content = style_content
:gsub("(%.babel%-box%.babel%-" .. proficiencies .. " %.babel%-content { background%-color: )#+; }", "%1" .. i .. "; }")
local contrast = get_WCAG_21_contrast({ hex_to_RGB(i) }, light_mode_text)
ret = ret .. '\n! Babel-' .. proficiencies .. ' right'
.. '\n| style="background-color:' .. i .. ';padding:1.7em 0.3em;" |<span style="position:relative;z-index:0;padding:2px 5px;background:var(--wikt-palette-lavender);opacity:0.8;border-radius:3px;">' .. i .. '</span>'
.. '\n| DARK_PLACEHOLDER_RIGHT_' .. n
.. '\n| <b>Light mode:</b> ' .. tostring(contrast):sub(1, 5) .. ' ' .. get_WCAG_21_rating(contrast) .. '<br><b>Dark mode:</b> DARK_CONTRAST_RIGHT_' .. n
.. '\n| <b>Light mode:</b> ' .. tostring(contrast):sub(1, 5) .. ' ' .. get_WCAG_21_rating(contrast) .. '<br><b>Dark mode:</b> DARK_LINK_CONTRAST_RIGHT_' .. n
.. '\n|-'
end
for n, i in ipairs(dark_colours_left) do
style_content = style_content
:gsub("(html%.skin%-theme%-clientpref%-night %.babel%-box%.babel%-" .. proficiencies .. " { border%-color: )#+; }", "%1" .. i .. "; }")
:gsub("(html%.skin%-theme%-clientpref%-night %.babel%-box%.babel%-" .. proficiencies .. " %.babel%-code { )color: #000000; }", "%1 background-color: " .. i .. "; }")
:gsub("(html%.skin%-theme%-clientpref%-os %.babel%-box%.babel%-" .. proficiencies .. " { border%-color: )#+; }", "%1" .. i .. "; }")
:gsub("(html%.skin%-theme%-clientpref%-os %.babel%-box%.babel%-" .. proficiencies .. " %.babel%-code { )color: #000000; }", "%1 background-color: " .. i .. "; }")
local contrast = get_WCAG_21_contrast({ hex_to_RGB(i) }, dark_mode_text)
local link_contrast = get_WCAG_21_contrast({ hex_to_RGB(i) }, dark_mode_link)
ret = ret:gsub("DARK_PLACEHOLDER_LEFT_" .. n, 'style="background-color:' .. i .. ';padding:1.7em 0.3em;" |<span style="position:relative;z-index:0;padding:2px 5px;background:var(--wikt-palette-lavender);opacity:0.8;border-radius:3px;">' .. i .. '</span>')
:gsub("DARK_CONTRAST_LEFT_" .. n, tostring(contrast):sub(1, 5) .. ' ' .. get_WCAG_21_rating(contrast))
:gsub("DARK_LINK_CONTRAST_LEFT_" .. n, tostring(link_contrast):sub(1, 5) .. ' ' .. get_WCAG_21_rating(link_contrast))
end
for n, i in ipairs(dark_colours_right) do
style_content = style_content
:gsub("(html%.skin%-theme%-clientpref%-night %.babel%-box%.babel%-" .. proficiencies .. " %.babel%-content { background%-color: )#+; }", "%1" .. i .. "; }")
:gsub("(html%.skin%-theme%-clientpref%-os %.babel%-box%.babel%-" .. proficiencies .. " %.babel%-content { background%-color: )#+; }", "%1" .. i .. "; }")
local contrast = get_WCAG_21_contrast({ hex_to_RGB(i) }, dark_mode_text)
local link_contrast = get_WCAG_21_contrast({ hex_to_RGB(i) }, dark_mode_link)
ret = ret:gsub("DARK_PLACEHOLDER_RIGHT_" .. n, 'style="background-color:' .. i .. ';padding:1.7em 0.3em;" |<span style="position:relative;z-index:0;padding:2px 5px;background:var(--wikt-palette-lavender);opacity:0.8;border-radius:3px;">' .. i .. '</span>')
:gsub("DARK_CONTRAST_RIGHT_" .. n, tostring(contrast):sub(1, 5) .. ' ' .. get_WCAG_21_rating(contrast))
:gsub("DARK_LINK_CONTRAST_RIGHT_" .. n, tostring(link_contrast):sub(1, 5) .. ' ' .. get_WCAG_21_rating(link_contrast))
end
return ret .. "\n|}\n"
-- Stylesheet preview
.. frame:preprocess('<syntaxhighlight lang="CSS">' .. style_content .. '</syntaxhighlight>')
end
return export