1/***********************************************************/ 2/* */ 3/* Plataforma e-ducativa - Argentina */ 4/* */ 5/* Copyright (c) 2026 de e-ducativa Educación Virtual S.A. */ 6/* */ 7/***********************************************************/ 8/** 9 * A partir de una variable CSS con un color, calcula el contraste y lo establece en otra variable CSS 10 * 11 * @param {string} css_variable - El nombre de la variable CSS que contiene el color principal. 12 * @param {string} css_contrast_variable - El nombre de la variable CSS que se actualizara con el valor de contraste calculado. 13 * 14 */ 15export function updateCssContrastVariable(css_variable, css_contrast_variable, algorithm = "apca") { 16 const chatMainColor = getComputedStyle(document.documentElement).getPropertyValue(css_variable).trim(); 17 document.documentElement.style.setProperty( 18 css_contrast_variable, 19 contrastColor({ cssColor: chatMainColor, algorithm }) 20 ); 21} 22 23/** 24 * Calcula el color de contraste para el color CSS dado. 25 * 26 * Para hacer esto, se crea un elemento <div> temporal en el documento, se le establece el color de fondo 27 * con el valor pasado como argumento y se obtiene el color computado por el navegador. 28 * 29 * Se puede calcular el contraste optimo mediante dos algoritmos: 30 * WCAG (Web Content Accessibility Guidelines) 31 * YIQ (YIQ color space) 32 * 33 * @param {object} 34 * @prop {string} cssColor - El color CSS para el que se va a calcular el contraste. 35 * @prop {string} algorithm - El algoritmo a utilizar para calcular el contraste. Puede ser "luminance", "yiq" o "apca". 36 * 37 * @return {string} El color de contraste para el color CSS dado, que sera #000000 o #FFFFFF. 38 */ 39export function contrastColor({ cssColor, algorithm = "apca" }) { 40 const temp = document.createElement("div"); 41 temp.style.color = cssColor; 42 document.body.appendChild(temp); 43 44 const computedColor = getComputedStyle(temp).color; 45 document.body.removeChild(temp); 46 47 const [r, g, b] = computedColor.match(/\d+/g).map(Number).slice(0, 3); 48 49 if (algorithm === "apca") { 50 return apca(r, g, b); 51 } else if (algorithm === "yiq") { 52 return yiq(r, g, b); 53 } else if (algorithm === "luminance") { 54 return luminance(r, g, b); 55 } 56} 57 58// Luminance (WCAG 2.1) 59function luminance(r, g, b) { 60 function _luminance(rr, gg, bb) { 61 const a = [rr, gg, bb].map((v) => { 62 v /= 255; 63 return v <= 0.03928 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4); 64 }); 65 return 0.2126 * a[0] + 0.7152 * a[1] + 0.0722 * a[2]; 66 } 67 const bgLuminance = _luminance(r, g, b); 68 69 const contrastWithBlack = (bgLuminance + 0.01) / 0.01; 70 const contrastWithWhite = 1.01 / (bgLuminance + 0.01); 71 72 return contrastWithBlack > contrastWithWhite ? "#000000" : "#FFFFFF"; 73} 74 75function yiq(r, g, b) { 76 const yiq = (r * 299 + g * 587 + b * 114) / 1000; 77 return yiq >= 128 ? "#000000" : "#FFFFFF"; 78} 79 80// APCA-W3 (version 0.0.98G-4g) 81function apca(r, g, b) { 82 // APCA-W3 constants 83 const mainTRC = 2.4; 84 85 // sRGB coefficients 86 const sRco = 0.2126729; 87 const sGco = 0.7151522; 88 const sBco = 0.0721750; 89 90 // Normalization exponents (polarity aware) 91 const normBG = 0.56; 92 const normTXT = 0.57; 93 const revBG = 0.65; 94 const revTXT = 0.62; 95 96 // Soft clamp for very dark colors 97 const blkThrs = 0.022; 98 const blkClmp = 1.414; 99 100 // Scalers and offsets 101 const scaleBoW = 1.14; 102 const loBoWoffset = 0.027; 103 const scaleWoB = 1.14; 104 const loWoBoffset = 0.027; 105 const loClip = 0.1; 106 107 // Linearize sRGB (simple gamma, as per APCA-W3) 108 function toLinear(c) { 109 return Math.pow(c / 255, mainTRC); 110 } 111 112 const R = toLinear(r); 113 const G = toLinear(g); 114 const B = toLinear(b); 115 const bgY = sRco * R + sGco * G + sBco * B; 116 117 const txtY_white = 1.0; 118 const txtY_black = 0.0; 119 120 const contrastWhite = contrastAPCA_W3(txtY_white, bgY, normBG, normTXT, revBG, revTXT, blkThrs, blkClmp, scaleBoW, loBoWoffset, scaleWoB, loWoBoffset, loCl
120ip); 121 const contrastBlack = contrastAPCA_W3(txtY_black, bgY, normBG, normTXT, revBG, revTXT, blkThrs, blkClmp, scaleBoW, loBoWoffset, scaleWoB, loWoBoffset, loClip); 122 123 return Math.abs(contrastBlack) > Math.abs(contrastWhite) ? "#000000" : "#FFFFFF"; 124} 125 126// APCA-W3 contrast calculation 127function contrastAPCA_W3(txtY, bgY, normBG, normTXT, revBG, revTXT, blkThrs, blkClmp, scaleBoW, loBoWoffset, scaleWoB, loWoBoffset, loClip) { 128 // Soft clamp for very dark colors 129 txtY = txtY > blkThrs ? txtY : txtY + Math.pow(blkThrs - txtY, blkClmp); 130 bgY = bgY > blkThrs ? bgY : bgY + Math.pow(blkThrs - bgY, blkClmp); 131 132 let SAPC; 133 if (bgY > txtY) { 134 // Normal polarity: dark text on light background 135 SAPC = (Math.pow(bgY, normBG) - Math.pow(txtY, normTXT)) * scaleBoW; 136 return SAPC < loClip ? 0 : (SAPC - loBoWoffset) * 100; 137 } else { 138 // Reverse polarity: light text on dark background 139 SAPC = (Math.pow(bgY, revBG) - Math.pow(txtY, revTXT)) * scaleWoB; 140 return SAPC > -loClip ? 0 : (SAPC + loWoBoffset) * 100; 141 } 142}
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.