1/** 2 * Birthday art preload + tinted balloon texture cache. 3 */ 4(function (global) { 5 'use strict'; 6 7 var cache = { 8 baseBalloon: null, 9 goldBalloon: null, 10 cakeSheet: null, 11 tinted: {} 12 }; 13 14 function loadImage(src) { 15 return new Promise(function (resolve, reject) { 16 var img = new Image(); 17 img.crossOrigin = 'anonymous'; 18 img.onload = function () { resolve(img); }; 19 img.onerror = function () { reject(new Error('Failed to load ' + src)); }; 20 img.src = src; 21 }); 22 } 23 24 function hexToRgb(hex) { 25 var h = String(hex || '').replace('#', ''); 26 if (h.length === 3) { 27 h = h[0] + h[0] + h[1] + h[1] + h[2] + h[2]; 28 } 29 return { 30 r: parseInt(h.slice(0, 2), 16) || 0, 31 g: parseInt(h.slice(2, 4), 16) || 0, 32 b: parseInt(h.slice(4, 6), 16) || 0 33 }; 34 } 35 36 function bakeTintedBalloon(baseImg, targetHex) { 37 var key = String(targetHex).toLowerCase(); 38 if (cache.tinted[key]) return cache.tinted[key]; 39 40 var w = baseImg.naturalWidth || baseImg.width; 41 var h = baseImg.naturalHeight || baseImg.height; 42 var canvas = document.createElement('canvas'); 43 canvas.width = w; 44 canvas.height = h; 45 var ctx = canvas.getContext('2d'); 46 if (!ctx) return baseImg.src || null; 47 48 ctx.drawImage(baseImg, 0, 0, w, h); 49 var imageData = ctx.getImageData(0, 0, w, h); 50 var target = hexToRgb(targetHex); 51 var data = imageData.data; 52 53 for (var i = 0; i < data.length; i += 4) { 54 var a = data[i + 3]; 55 if (a < 8) continue; 56 var r = data[i]; 57 var g = data[i + 1]; 58 var b = data[i + 2]; 59 var lum = (0.299 * r + 0.587 * g + 0.114 * b) / 255; 60 var shade = 0.35 + lum * 0.75; 61 data[i] = Math.min(255, target.r * shade); 62 data[i + 1] = Math.min(255, target.g * shade); 63 data[i + 2] = Math.min(255, target.b * shade); 64 } 65 66 ctx.putImageData(imageData, 0, 0); 67 cache.tinted[key] = canvas.toDataURL('image/png'); 68 return cache.tinted[key]; 69 } 70 71 function preload(entry) { 72 if (!entry || !entry.assets) return Promise.reject(new Error('No birthday assets')); 73 return Promise.all([ 74 loadImage(entry.assets.balloon).then(function (img) { cache.baseBalloon = img; }), 75 loadImage(entry.assets.balloonGold).then(function (img) { cache.goldBalloon = img; }), 76 loadImage(entry.assets.cakeSheet).then(function (img) { cache.cakeSheet = img; }) 77 ]); 78 } 79 80 function getTintedSpriteSource(colorHex) { 81 if (!cache.baseBalloon) return null; 82 return bakeTintedBalloon(cache.baseBalloon, colorHex); 83 } 84 85 function getGoldSpriteSource() { 86 if (cache.goldBalloon && cache.goldBalloon.src) return cache.goldBalloon.src; 87 return '/images/birthday/balloon_gold.png'; 88 } 89 90 function pickRandomColor(entry) { 91 var colors = entry && entry.balloonColors ? entry.balloonColors : ['#F46607']; 92 return colors[Math.floor(Math.random() * colors.length)]; 93 } 94 95 function pickDisplaySize(entry) { 96 var min = (entry && entry.balloonDisplayPx && entry.balloonDisplayPx.min) || 80; 97 var max = (entry && entry.balloonDisplayPx && entry.balloonDisplayPx.max) || 100; 98 return min + Math.random() * (max - min); 99 } 100 101 global.CitrusBirthdayAssets = { 102 preload: preload, 103 getTintedSpriteSource: getTintedSpriteSource, 104 getGoldSpriteSource: getGoldSpriteSource, 105 pickRandomColor: pickRandomColor, 106 pickDisplaySize: pickDisplaySize, 107 getCakeSheet: function () { return cache.cakeSheet; }, 108 getBaseBalloon: function () { return cache.baseBalloon; } 109 }; 110})(typeof window !== 'undefined' ? window : this);
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.