{"__browser":{"country":"US","device":"unknown_device","mobile":false,"name":"unknown browser","platform":"unknown_platform","version":"0"},"__constants":{},"__CPDATA":{"domain_iframe":"https://cdpn.io","environment":"production","host":"codepen.io","iframe_allow":"accelerometer *; ambient-light-sensor *; camera *; display-capture *; encrypted-media *; geolocation *; gyroscope *; microphone *; midi *; payment *; vr *; web-share *; serial *; xr-spatial-tracking *","iframe_sandbox":"allow-downloads allow-forms allow-modals allow-pointer-lock allow-popups-to-escape-sandbox allow-popups allow-presentation allow-same-origin allow-scripts allow-top-navigation-by-user-activation"},"__graphql":{"data":{"data":{"sessionUser":{"id":"VoDkNZ","name":"Captain Anonymous","title":"Captain Anonymous","avatar":"https://assets.codepen.io/t-1/user-default-avatar.jpg?format=auto&version=0","currentContext":{"id":"VoDkNZ","title":"Captain Anonymous","name":"Captain Anonymous","avatar":"https://assets.codepen.io/t-1/user-default-avatar.jpg?format=auto&version=0","username":"anon","__typename":"User"},"currentTeamId":null,"username":"anon","admin":false,"anon":true,"pro":false,"verified":false,"featureFlags":[],"teams":[],"__typename":"User"}}},"url":"https://codepen.io/graphql","api":"cprails"},"__user":{"anon":true,"base_url":"/anon/","cohorts":[],"current_team_hashid":null,"current_team_id":0,"hashid":"VoDkNZ","id":1,"itemType":"user","name":"Captain Anonymous","paid":false,"tier":0,"username":"anon","created_at":null,"email_verified":null,"collections_count":0,"collections_private_count":0,"followers_count":0,"followings_count":0,"pens_count":0,"pens_private_count":0,"projects_count":0,"projects_private_count":0},"__firebase":{"config":{"apiKey":"AIzaSyBgLAe7N_MdFpuVofMkcQLGwwhUu5tuxls","authDomain":"codepen-store-production.firebaseapp.com","databaseURL":"https://codepen-store-production.firebaseio.com","disabled":false,"projectId":"codepen-store-production"},"token":"eyJhbGciOiJSUzI1NiJ9.eyJhdWQiOiJodHRwczovL2lkZW50aXR5dG9vbGtpdC5nb29nbGVhcGlzLmNvbS9nb29nbGUuaWRlbnRpdHkuaWRlbnRpdHl0b29sa2l0LnYxLklkZW50aXR5VG9vbGtpdCIsImNsYWltcyI6eyJvd25lcklkIjoiVm9Ea05aIiwiYWRtaW4iOmZhbHNlfSwiZXhwIjoxNzI1ODA4OTYxLCJpYXQiOjE3MjU4MDUzNjEsImlzcyI6ImZpcmViYXNlLWFkbWluc2RrLThva3lsQGNvZGVwZW4tc3RvcmUtcHJvZHVjdGlvbi5pYW0uZ3NlcnZpY2VhY2NvdW50LmNvbSIsInN1YiI6ImZpcmViYXNlLWFkbWluc2RrLThva3lsQGNvZGVwZW4tc3RvcmUtcHJvZHVjdGlvbi5pYW0uZ3NlcnZpY2VhY2NvdW50LmNvbSIsInVpZCI6IlZvRGtOWiJ9.tx7r4zX8h2IB-UNdFtg4cCdhZUoDGgjZ1wx975Ya0UpBAjUPLBBxiXrsFnXv3kRP5x_0TUXfBYYa_GGgsgJ5LPwE2l2p8RUcdAllGof5w3gmaLSxbvvytfYlQPCCE4fT7QFhaxEioKHm6wVMObGqyd8wMf7xPkoBb-RDbaPPU_-jBvBETNAcIooCVsrED_GRHB14Xyp_abh0BpR1Q9DgV0j1P_yq0-7buZngq41vYpOb2IPzbVNR4wuVqEsF_qfylynqR87XXJO5VK-4xsxhMcw7bkzRiv5PG48cf_yL5OQ0kix2q84keYWsOfgNY3svcV9jAiJyfJIkjNCyGOuAYw"},"__pay_stripe_public_key":"pk_live_2GndomDfiklqpSNQn8FrGuwZSMIMzha7DkLJqlYe7IR0ihKAlKdiHg68JJc5eVPt68rzAjzAAVXcUwjySHRCsgjQQ00gtRBUFNH","__pay_braintree_env":"production","__item":"{\"id\":45733078,\"user_id\":246719,\"html\":\"<link href=\\\"https:\\/\\/fonts.googleapis.com\\/css?family=Montserrat:400,600&display=swap\\\" rel=\\\"stylesheet\\\">\\n\\n<div class=\\\"main_titles center\\\">\\n <h1>Optimal Overlay Finder<\\/h1>\\n <h2>For Readable Text on a Background Image<\\/h2>\\n<\\/div>\\n\\n<div class=\\\"content_width content_grid\\\">\\n\\n <div class=\\\"choose_image\\\">\\n <h3>Choose an image:<\\/h3>\\n <div class=\\\"img_btns\\\">\\n <button class=\\\"standard_btn sample_img_btn\\\" data-url=\\\"https:\\/\\/assets.codepen.io\\/246719\\/blue-rose-pexels-photo-1482972.jpeg?width=1034&height=689&format=auto\\\">Flower<\\/button>\\n <button class=\\\"standard_btn sample_img_btn\\\" data-url=\\\"https:\\/\\/assets.codepen.io\\/246719\\/snow-pexels-photo-691668.jpeg?width=1012&height=675&fit=scale-down&format=auto\\\">Snow<\\/button>\\n <button id=\\\"custom_img_btn\\\" class=\\\"standard_btn sample_img_btn hide\\\" data-url=\\\"\\\">Custom<\\/button>\\n <label class=\\\"upload_label standard_btn\\\">\\n Upload\\n <input type=\\\"file\\\" id=\\\"uploader\\\">\\n <\\/label>\\n <\\/div>\\n <\\/div>\\n\\n <div class=\\\"enter_text\\\">\\n <h3>Enter your text:<\\/h3>\\n <input id=\\\"foreground_text_input\\\" type=\\\"text\\\" name=\\\"\\\" placeholder=\\\"Example text goes here\\\">\\n <\\/div>\\n\\n<\\/div>\\n\\n<div class=\\\"content_width color_choice_title\\\">\\n <h3>Choose your colors:<\\/h3>\\n<\\/div>\\n\\n<div class=\\\"content_width content_grid\\\">\\n <label class=\\\"color_picker\\\">\\n Overlay color\\n <span class=\\\"color_preview_holder\\\">\\n <span class=\\\"color_preview\\\" id=\\\"overlay_color_preview\\\"><\\/span>\\n <\\/span>\\n <input id=\\\"overlay_color_input\\\" type=\\\"color\\\" value=\\\"#000000\\\">\\n <\\/label>\\n <label class=\\\"color_picker\\\">\\n Text color\\n <span class=\\\"color_preview_holder\\\">\\n <span class=\\\"color_preview\\\" id=\\\"text_color_preview\\\"><\\/span>\\n <\\/span>\\n <input id=\\\"text_color_input\\\" type=\\\"color\\\" value=\\\"#ffffff\\\">\\n <\\/label>\\n\\n<\\/div>\\n\\n<div class=\\\"image_showcase_section content_width content_grid\\\">\\n\\n <div class=\\\"image_showcase before\\\">\\n <h3>Before:<\\/h3>\\n <div class=\\\"image_holder\\\">\\n <div class=\\\"bg_image_container\\\">\\n <img src=\\\"\\\" alt=\\\"\\\" class=\\\"bg_image js_bg_image\\\">\\n <\\/div>\\n <div class=\\\"foreground_text_container\\\">\\n <div class=\\\"foreground_text\\\"><\\/div>\\n <\\/div>\\n <\\/div>\\n <\\/div>\\n <div class=\\\"image_showcase after\\\">\\n <h3>After:<\\/h3>\\n <div class=\\\"image_holder\\\">\\n <div class=\\\"bg_image_container\\\">\\n <img src=\\\"\\\" alt=\\\"\\\" class=\\\"bg_image js_bg_image\\\">\\n <\\/div>\\n <div id=\\\"overlay\\\"><\\/div>\\n <div class=\\\"foreground_text_container\\\">\\n <div class=\\\"foreground_text\\\"><\\/div>\\n <\\/div>\\n <\\/div>\\n <\\/div>\\n\\n<\\/div>\\n\\n<div class=\\\"output_area center\\\">\\n <div class=\\\"optimal_opacity_headline\\\">Optimal overlay opacity:<\\/div>\\n <div class=\\\"optimal_opacity_value\\\" id=\\\"optimal_opacity_output_container\\\"><\\/div>\\n <div id=\\\"no_solution\\\" class=\\\"hide\\\">\\n <div class=\\\"no_solution_heading\\\">\\n No Solution\\n <\\/div>\\n <div class=\\\"no_solution_explanation\\\">\\n If your text doesn't have enough contrast with your image or your overlay color, there won't be an optimal solution. For example, there's no opacity that will make blue text show up on an overlay that's the same shade of blue.\\n <\\/div>\\n <\\/div>\\n<\\/div>\\n\\n<div style=\\\"display:none;\\\">\\n <img id=\\\"original_image\\\" class=\\\"js_bg_image\\\" src=\\\"\\\" alt=\\\"\\\" crossorigin=\\\"*\\\">\\n<\\/div>\\n\\n<div class=\\\"debug\\\">\\n <h3>Original image placed in a canvas:<\\/h3>\\n <canvas id=\\\"image_canvas\\\"><\\/canvas>\\n<\\/div>\",\"css\":\"body {\\n margin: 0px;\\n}\\nbody, button, input {\\n font-family: 'Montserrat', sans-serif;\\n color: #556;\\n}\\nh1, h2, h3, h4, h5, h6 {\\n font-weight: normal;\\n}\\n\\nh1 {\\n font-size: 60px;\\n margin: 0px;\\n}\\nh2 {\\n font-size: 30px;\\n margin: 0px;\\n color: #889;\\n}\\nh3 {\\n font-size: 15px;\\n font-weight: 600;\\n text-transform: uppercase;\\n margin: 0px 0px 10px;\\n}\\n\\n.center {\\n text-align: center;\\n}\\n\\n.color_output {\\n display: inline-block;\\n border: 3px dashed #333;\\n margin-right: 50px;\\n width: 300px;\\n height: 300px;\\n color: #fff;\\n}\\n\\n#canvas {\\n width: 500px;\\n}\\n\\n.debug {\\n display: none;\\n}\\n\\n#color_mixing_canvas {\\n width: 500px;\\n height: 100px;\\n border: 3px dashed #333;\\n}\\n\\n#overlay {\\n background-color: #000;\\n opacity: 0;\\n width: 100%;\\n height: 100%;\\n position: absolute;\\n left: 0;\\n top: 0;\\n}\\n\\n.image_showcase {\\n min-height: 100px;\\n text-align: left;\\n}\\n\\n.image_holder {\\n position: relative;\\n background-color: #333;\\n color: #fff;\\n border: 1px solid #ddd;\\n}\\n\\n.bg_image {\\n width: 100%;\\n display: block;\\n}\\n\\n.foreground_text_container {\\n position: absolute;\\n left: 0;\\n top: 0;\\n width: 100%;\\n height: 100%;\\n display: flex;\\n align-items: center;\\n justify-content: center;\\n}\\n.foreground_text {\\n font-size: 30px;\\n padding: 40px;\\n}\\n\\ninput[type=\\\"text\\\"] {\\n box-sizing: border-box;\\n width: 100%;\\n padding: 20px;\\n font-size: 20px;\\n border: 1px solid #ddd;\\n}\\n\\n.content_width {\\n max-width: 1000px;\\n margin: auto;\\n padding: 0px 40px;\\n}\\n.content_grid {\\n display: grid;\\n grid-template-columns: 1fr 1fr;\\n grid-gap: 40px;\\n}\\n\\n.main_titles {\\n margin: 100px 40px 60px;\\n}\\n\\n.image_showcase_section {\\n margin-top: 42px;\\n margin-bottom: 42px;\\n}\\n\\n@media (max-width: 700px) {\\n .content_grid {\\n grid-template-columns: 1fr;\\n }\\n}\\n\\ninput[type=\\\"file\\\"],\\ninput[type=\\\"color\\\"] {\\n display: none;\\n}\\n\\n.img_btns {\\n display: flex;\\n flex-wrap: wrap;\\n margin-bottom: -10px;\\n margin-right: -10px;\\n}\\n\\n.standard_btn {\\n border: none;\\n background-color: #dde4ea;\\n color: #334;\\n padding: 20px;\\n font-size: 20px;\\n display: block;\\n text-align: center;\\n cursor: pointer;\\n margin-right: 10px;\\n margin-bottom: 10px;\\n transition: all 0.2s;\\n flex: 1;\\n}\\n.standard_btn:hover {\\n background-color: #334;\\n color: #dde4ea;\\n}\\n.standard_btn:focus {\\n outline: none;\\n}\\n\\n.hide {\\n display: none;\\n}\\n\\n.color_picker {\\n position: relative;\\n background-color: #dde4ea;\\n color: #334;\\n padding: 20px;\\n font-size: 20px;\\n display: inline-block;\\n text-align: center;\\n cursor: pointer;\\n transition: all 0.2s;\\n}\\n.color_picker:hover {\\n background-color: #334;\\n color: #dde4ea;\\n}\\n\\n.color_choice_title {\\n margin-top: 40px;\\n}\\n\\n.color_preview_holder {\\n display: inline-block;\\n padding: 4px;\\n background-color: #eee;\\n border: 1px solid #aaa;\\n height: calc(100% - 20px);\\n width: 45px;\\n box-sizing: border-box;\\n margin-left: 20px;\\n position: absolute;\\n right: 10px;\\n top: 10px;\\n}\\n\\n.color_preview {\\n box-sizing: border-box;\\n display: block;\\n background-color: white;\\n border: 1px solid #aaa;\\n width: 100%;\\n height: 100%;\\n}\\n\\n.output_area {\\n margin-bottom: 80px;\\n}\\n\\n.optimal_opacity_headline {\\n font-size: 30px;\\n}\\n\\n.optimal_opacity_value {\\n font-size: 60px;\\n}\\n\\n.no_solution_heading {\\n font-size: 60px;\\n}\\n.no_solution_explanation {\\n max-width: 680px;\\n margin: 10px auto 0;\\n padding: 0px 40px;\\n text-align: left;\\n}\\n\\n\\n\\n\\n@media (max-width: 900px) {\\n .main_titles {\\n margin: 24px 40px;\\n }\\n h1 {\\n font-size: 36px;\\n }\\n h2 {\\n font-size: 20px;\\n }\\n .standard_btn {\\n padding: 10px;\\n font-size: 18px;\\n }\\n input[type=\\\"text\\\"] {\\n padding: 10px;\\n font-size: 18px;\\n }\\n .color_choice_title {\\n margin-top: 20px;\\n }\\n .color_picker {\\n padding: 10px;\\n font-size: 18px;\\n }\\n .color_preview_holder {\\n padding: 2px;\\n height: calc(100% - 10px);\\n width: 34px;\\n box-sizing: border-box;\\n right: 5px;\\n top: 5px;\\n }\\n .foreground_text {\\n font-size: 20px;\\n padding: 20px;\\n }\\n .image_showcase_section {\\n margin: 20px auto;\\n }\\n .output_area {\\n margin-bottom: 20px;\\n }\\n}\",\"js\":\"class App {\\n\\n desiredContrast = 4.5\\n \\/\\/ Contrast level AA = 4.5, Level AAA = 7\\n \\/\\/ Reference: https:\\/\\/www.w3.org\\/WAI\\/WCAG21\\/quickref\\/?versions=2.0&showtechniques=143#qr-visual-audio-contrast-contrast\\n\\n state = {\\n textColor: {r:255, g:255, b:255},\\n overlayColor: {r:0, g:0, b:0},\\n }\\n\\n elements = {\\n uploader: document.getElementById('uploader'),\\n optimal_opacity_output_container: document.getElementById('optimal_opacity_output_container'),\\n image_canvas: document.getElementById('image_canvas'),\\n overlay: document.getElementById('overlay'),\\n custom_img_btn: document.getElementById('custom_img_btn'),\\n foreground_text_input: document.getElementById('foreground_text_input'),\\n foreground_text: document.querySelectorAll('.foreground_text'),\\n sample_image_buttons: document.querySelectorAll('.sample_img_btn'),\\n background_images: document.querySelectorAll('.js_bg_image'),\\n\\n text_color_input: document.getElementById('text_color_input'),\\n overlay_color_input: document.getElementById('overlay_color_input'),\\n\\n text_color_preview: document.getElementById('text_color_preview'),\\n overlay_color_preview: document.getElementById('overlay_color_preview'),\\n\\n no_solution: document.getElementById('no_solution'),\\n\\n original_image: document.getElementById('original_image'),\\n }\\n\\n start() {\\n this.attachUploader();\\n this.attachTextUpdaters();\\n this.storeColorsFromInputs();\\n this.updateTextColor(this.state.textColor);\\n this.attachOverlayUpdater();\\n this.prepareSampleImages();\\n this.attachColorChangeListeners();\\n }\\n\\n attachTextUpdaters() {\\n this.elements.foreground_text_input.addEventListener('keyup', () => {\\n this.updateText(this.elements.foreground_text_input);\\n });\\n this.updateText(this.elements.foreground_text_input);\\n }\\n\\n attachOverlayUpdater() {\\n this.elements.original_image.addEventListener('load', () => {this.updateOverlay()});\\n }\\n\\n prepareSampleImages() {\\n const { sample_image_buttons } = this.elements;\\n const firstImageUrl = sample_image_buttons[0].getAttribute('data-url');\\n this.loadImage(firstImageUrl);\\n sample_image_buttons.forEach((btn) => {\\n btn.addEventListener('click', () => {\\n const url = btn.getAttribute('data-url');\\n this.loadImage(url);\\n });\\n });\\n }\\n\\n attachColorChangeListeners() {\\n document.querySelectorAll('input[type=color]').forEach((colorInput) => {\\n colorInput.addEventListener('input', () => {\\n this.storeColorsFromInputs();\\n this.updateTextColor(this.state.textColor);\\n this.updateOverlay();\\n });\\n });\\n }\\n\\n attachUploader() {\\n const { uploader } = this.elements;\\n uploader.addEventListener('change', () => {\\n const file = uploader.files[0];\\n\\n const reader = new FileReader();\\n reader.onload = (e) => {\\n const url = e.target.result;\\n this.loadImage(url);\\n this.updateCustomImgBtn(url);\\n };\\n reader.readAsDataURL(file);\\n });\\n }\\n\\n updateCustomImgBtn(url) {\\n this.elements.custom_img_btn.classList.remove('hide');\\n this.elements.custom_img_btn.setAttribute('data-url', url);\\n }\\n\\n updateText(textInput) {\\n this.elements.foreground_text.forEach((textBox) => {\\n textBox.innerText = textInput.value || textInput.placeholder;\\n });\\n }\\n\\n updateTextColor(color) {\\n this.elements.foreground_text.forEach((textBox) => {\\n textBox.style.color = `rgb(\\n ${color.r},\\n ${color.g},\\n ${color.b}\\n )`;\\n });\\n }\\n\\n loadImage(url) {\\n this.elements.background_images.forEach( (image) => {\\n image.src = url;\\n });\\n }\\n\\n updateOverlay() {\\n const { image_canvas, original_image } = this.elements;\\n const { textColor, overlayColor } = this.state;\\n\\n const imagePixelColors = this.getImagePixelColorsUsingCanvas(original_image, image_canvas);\\n\\n const worstContrastColorInImage = this.getWorstContrastColorInImage(textColor, imagePixelColors);\\n\\n const optimalOpacity = this.findOptimalOverlayOpacity(textColor, overlayColor, worstContrastColorInImage, this.desiredContrast);\\n\\n this.showOptimalOpacity(optimalOpacity);\\n }\\n\\n getImagePixelColorsUsingCanvas(image, canvas) {\\n const ctx = canvas.getContext('2d');\\n\\n canvas.height = this.getCanvasHeightToMatchImageProportions(canvas, image);\\n\\n const sourceImageCoordinates = [0, 0, image.width, image.height];\\n const destinationCanvasCoordinates = [0, 0, canvas.width, canvas.height];\\n\\n ctx.drawImage(\\n image,\\n ...sourceImageCoordinates,\\n ...destinationCanvasCoordinates\\n );\\n\\n \\/\\/ Remember getImageData only works for same-origin or cross-origin-enabled images.\\n \\/\\/ See https:\\/\\/developer.mozilla.org\\/en-US\\/docs\\/Web\\/HTML\\/CORS_enabled_image for more info.\\n const imagePixelColors = ctx.getImageData(...destinationCanvasCoordinates);\\n\\n return imagePixelColors;\\n }\\n\\n getCanvasHeightToMatchImageProportions(canvas, image) {\\n return (image.height \\/ image.width) * canvas.width;\\n }\\n\\n showOptimalOpacity(optimalOpacity) {\\n const { overlay, optimal_opacity_output_container } = this.elements;\\n\\n optimal_opacity_output_container.innerHTML = optimalOpacity.toFixed(3);\\n overlay.style.backgroundColor = `rgb(\\n ${this.state.overlayColor.r},\\n ${this.state.overlayColor.g},\\n ${this.state.overlayColor.b}\\n )`;\\n this.elements.overlay.style.opacity = optimalOpacity;\\n }\\n\\n getWorstContrastColorInImage(textColor, imagePixelColors) {\\n\\n let worstContrastColorInImage;\\n let worstContrast = Infinity;\\n\\n for (let i = 0; i < imagePixelColors.data.length; i += 4) {\\n let pixelColor = {\\n r: imagePixelColors.data[i],\\n g: imagePixelColors.data[i + 1],\\n b: imagePixelColors.data[i + 2],\\n };\\n\\n let contrast = this.getContrast(textColor, pixelColor);\\n\\n if(contrast < worstContrast) {\\n worstContrast = contrast;\\n worstContrastColorInImage = pixelColor;\\n }\\n }\\n\\n return worstContrastColorInImage;\\n }\\n\\n getContrast(color1, color2) {\\n const color1_luminance = this.getLuminance(color1);\\n const color2_luminance = this.getLuminance(color2);\\n\\n const lighterColorLuminance = Math.max(color1_luminance, color2_luminance);\\n const darkerColorLuminance = Math.min(color1_luminance, color2_luminance);\\n\\n const contrast = (lighterColorLuminance + 0.05) \\/ (darkerColorLuminance + 0.05);\\n return contrast;\\n }\\n\\n getLuminance({r,g,b}) {\\n return (0.2126 * this.getLinearRGB(r) + 0.7152 * this.getLinearRGB(g) + 0.0722 * this.getLinearRGB(b));\\n }\\n\\n getLinearRGB(primaryColor_8bit) {\\n \\/\\/ First convert from 8-bit rbg (0-255) to standard RGB (0-1)\\n const primaryColor_sRGB = this.convert_8bit_RGB_to_standard_RGB(primaryColor_8bit);\\n\\n \\/\\/ Then convert from sRGB to linear RGB so we can use it to calculate luminance\\n const primaryColor_RGB_linear = this.convert_standard_RGB_to_linear_RGB(primaryColor_sRGB);\\n\\n return primaryColor_RGB_linear;\\n }\\n\\n convert_8bit_RGB_to_standard_RGB(primaryColor_8bit) {\\n return primaryColor_8bit \\/ 255;\\n }\\n\\n convert_standard_RGB_to_linear_RGB(primaryColor_sRGB) {\\n const primaryColor_linear = primaryColor_sRGB < 0.03928 ?\\n primaryColor_sRGB\\/12.92 :\\n Math.pow((primaryColor_sRGB + 0.055) \\/ 1.055, 2.4);\\n return primaryColor_linear;\\n }\\n\\n getTextContrastWithImagePlusOverlay({textColor, overlayColor, imagePixelColor, overlayOpacity}) {\\n const colorOfImagePixelPlusOverlay = this.mixColors(imagePixelColor, overlayColor, overlayOpacity);\\n const contrast = this.getContrast(this.state.textColor, colorOfImagePixelPlusOverlay);\\n return contrast;\\n }\\n\\n mixColors(baseColor, overlayColor, overlayOpacity) {\\n const mixedColor = {\\n r: baseColor.r + (overlayColor.r - baseColor.r) * overlayOpacity,\\n g: baseColor.g + (overlayColor.g - baseColor.g) * overlayOpacity,\\n b: baseColor.b + (overlayColor.b - baseColor.b) * overlayOpacity,\\n }\\n return mixedColor;\\n }\\n\\n findOptimalOverlayOpacity(textColor, overlayColor, worstContrastColorInImage, desiredContrast) {\\n const isOverlayNecessary = this.isOverlayNecessary(textColor, worstContrastColorInImage, desiredContrast);\\n if (!isOverlayNecessary) {\\n return 0;\\n }\\n\\n const opacityGuessRange = {\\n lowerBound: 0,\\n midpoint: 0.5,\\n upperBound: 1,\\n };\\n\\n let numberOfGuesses = 0;\\n const maxGuesses = 8;\\n const opacityLimit = 0.99;\\n\\n while (numberOfGuesses < maxGuesses) {\\n numberOfGuesses++;\\n const currentGuess = opacityGuessRange.midpoint;\\n\\n const contrastOfGuess = this.getTextContrastWithImagePlusOverlay({\\n textColor,\\n overlayColor,\\n imagePixelColor: worstContrastColorInImage,\\n overlayOpacity: currentGuess,\\n });\\n\\n const isGuessTooLow = contrastOfGuess < desiredContrast;\\n const isGuessTooHigh = contrastOfGuess > desiredContrast;\\n\\n if (isGuessTooLow) {\\n opacityGuessRange.lowerBound = currentGuess;\\n }\\n else if (isGuessTooHigh) {\\n opacityGuessRange.upperBound = currentGuess;\\n }\\n\\n const newMidpoint = ((opacityGuessRange.upperBound - opacityGuessRange.lowerBound) \\/ 2) + opacityGuessRange.lowerBound;\\n opacityGuessRange.midpoint = newMidpoint;\\n }\\n\\n const optimalOpacity = opacityGuessRange.midpoint;\\n\\n if (optimalOpacity > opacityLimit) {\\n this.elements.optimal_opacity_output_container.classList.add('hide');\\n this.elements.no_solution.classList.remove('hide');\\n return opacityLimit;\\n }\\n\\n this.elements.optimal_opacity_output_container.classList.remove('hide');\\n this.elements.no_solution.classList.add('hide');\\n return optimalOpacity;\\n }\\n\\n isOverlayNecessary(textColor, worstContrastColorInImage, desiredContrast) {\\n const contrastWithoutOverlay = this.getContrast(textColor, worstContrastColorInImage);\\n return contrastWithoutOverlay < desiredContrast;\\n }\\n\\n convertHexToRGB(hex) {\\n const raw_hex = hex.replace(\\/#\\/g, '');\\n const r = parseInt(raw_hex.substring(0,2), 16);\\n const g = parseInt(raw_hex.substring(2,4), 16);\\n const b = parseInt(raw_hex.substring(4,6), 16);\\n return {r, g, b};\\n }\\n\\n storeColorsFromInputs() {\\n this.state.textColor = this.convertHexToRGB(this.elements.text_color_input.value);\\n this.state.overlayColor = this.convertHexToRGB(this.elements.overlay_color_input.value);\\n this.elements.text_color_preview.style.backgroundColor = this.elements.text_color_input.value;\\n this.elements.overlay_color_preview.style.backgroundColor = this.elements.overlay_color_input.value;\\n }\\n\\n}\\n\\nconst app = new App();\\napp.start();\\n\\n\",\"html_pre_processor\":\"none\",\"css_pre_processor\":\"none\",\"js_pre_processor\":\"none\",\"html_classes\":\"\",\"css_starter\":\"neither\",\"js_library\":null,\"created_at\":\"2020-07-05T19:05:06.000Z\",\"updated_at\":\"2020-07-17T00:46:11.000Z\",\"title\":\"Optimal Overlay Finder - Readable Text on a Background Image\",\"description\":\"This is a tool to find the optimal overlay opacity that makes your text readable on a background image without hiding the image too much.\",\"slug_hash\":\"oNbEqGV\",\"head\":\"\",\"private\":false,\"slug_hash_private\":\"a7ed58cf97964b8b42566fbffe219aec\",\"has_animation\":false,\"team_id\":0,\"css_prefix\":\"neither\",\"template\":false,\"parent_id\":40110392,\"comments_count\":0,\"custom_screenshot_filename\":null,\"loves_count\":0,\"pick\":false,\"popularity_score\":0,\"views_count\":0,\"pick_visible_at\":null,\"cpid\":\"0173205e-1ed0-7e44-bccb-9cf19d06ddb4\",\"is_new_editor_pen\":false,\"protected\":false,\"access\":\"Public\",\"pen_hash\":null,\"hashid\":\"oNbEqGV\"}"}