let img1, img2, img3, img4, eyeOverlayImg, hundosSheet, logoSheet, accessoriesImg, pupsgrid; let externalTileSeed = (typeof window !== 'undefined' && Number.isInteger(window.TILE_SEED)) ? window.TILE_SEED : null; const tileSize = 200; const gridCount = 8; let chartstyle; const cryptoSlang = [ "HODL", "FOMO", "FUD", "WAGMI", "NGMI", "REKT", "BTFD", "ATH", "ATL", "BAGHOLDER", "EXIT LIQUIDITY", "DEAD CAT", "PUMP", "DUMP", "BOUNCE", "SHILL", "RUG", "FLIP", "CORRECTION", "PARABOLIC", "WHALE", "APE", "JEET", "CHAD", "TOKENOMICS", "CAPITULATION", "DIAMOND HANDS", "PAPERHANDS", "WEN", "LAMBO", "MOON", "DEX", "CEX", "NORMIE", "BRO", "DEGEN", "GAS", "FEES", "STAKED", "FARM", "YIELD", "LP", "SNIPER", "AIRDROP", "DAO", "PROTOCOL", "PFP", "MINT", "FLOOR", "SWEEP", "METADATA", "REVEAL", "DOXXED", "BURN", "UTILITY", "ART", "META", "DERIVATIVE", "BLUE CHIP", "DUST", "PHISHING", "HACKED", "WALLET", "DRAINED", "SOFT RUG", "FRONT RUN", "INSIDER", "CABAL", "SCAM", "DYOR", "BULLISH", "BEARISH", "PEPE", "BOBO", "SOLD", "BOUGHT", "ALTCOIN", "SEASON", "GM", "FIAT", "MAXI", "PUMPAMANTALS", "FUNDAMENTALS", "VAPORWARE", "SHITCOIN", "HALVING", "100X", "ALPHA", "HFSP", "WAGECUCK", "HOPIUM", "COPIUM", "NOCOINER", "DCA", "KYC", "TA", "CHART", "INDICATOR", "LEVERAGE", "MARGIN", "CALL", "MULTISIG", "SMART", "CONTRACT", "NODE", "ROI", "TVL", "MEV", "FORK", "TOKEN", "LONG", "SHORT", "PROFIT", "VOLUME", "BREAKOUT", "REVERSAL", "PATTERN", "CANDLE", "WICK", "GAP", "YOLO", "ROCKET", "AUTIST", "BOOMER", "MEME", "SQUEEZE", "TRAP", "FALLING KNIFE", "ENTRY", "WEEKEND", "VOLATILITY", "OPTION", "DIVERGENCE", "HYPE", "RETAIL", "INSTITUTIONAL", "SATS", "HASHRATE", "FEAR", "GREED", "DOVISH", "HAWKISH", "SUICIDE", "SWING", "BEAR", "BULL", "RISK", "REWARD", "RUNNER", "UNLOCK", "BETA", "VC", "NUKE", "TRIPLE", "WEDGE", "TOP", "BOTTOM", "DOJI", "PENNANT", "CUP & HANDLE", "ACCUMULATION", "DISTRIBUTION", "BOTS", "LIVESTREAM", "BONDED", "CURVE", "LOL", "LOCKED", "WHITELIST", "RETIRE", "BLOODLINE", "MCDONALDS", "REPRICING", "PIVOT", "ATOMIC SWAPS", "L1", "L2", "LIQUIDATION", "CONFERENCE", "ANNOUNCEMENT", "BACKTESTING", "POW", "POS", "BLACKSLISTED", "HEDGE", "ALLOCATION", "VESTED", "UNDERWATER", "BREAKOUT", "CONFIRMATION", "RETEST", "BREAKDOWN", "SUPPORT", "RESISTANCE", "SCREENSHOT", "PRIVACY", "MIXER", "ANON", "DEV", "DEFI", "PASSWORD", "BRIDGE", "WRAPPED", "VPN", "FDV", "4CHAN", "HEAD & SHOLDERS", "RSI", "MACD", "MOMENTUM", "TRENCH", "PREDICTION", "ARBITRAGE", "INCENTIVE", "INFLATION", "EMISSION SCHEDULE", "RATE CUT", "QUANTUM COMPUTING", "UTOPIAN", "CYPHERPUNK", "LIBERTARIAN", "ANARCHIST", "BITMAP", "RUNES", "ORDINALS", "TROLL", "SURVEILLANCE", "AI AGENTS", "ENCRYPTED", "PRIVACY", "MICROSTRATEGY", "ETF", "SOON", "FANBOI", "HATER", "ALGORITHMIC", "PERPS", "TECH", "AUTONOMOUS", "INVERSE", "ROADMAP", "RELAUNCH", "COMMUNITY", "PREMINE", "FCFS", "REBASE", "PERMISSIONLESS", "GATED", "CROSSOVER", "SIGNAL", "BOTS", "MICRO CAP", "MID CAP", "LARGE CAP", "COLLECTION", "KOL", "MANIPULATION", "REVENGE", "STOP LOSS", "TRANSPARENCY", "EXPOSURE", "CTO", "MARKETING", "AGGREGATION", "ASIAN", "TAX", "MECHANISM", "BUG", "MIGRATION", "UPGRADE", "IMPULSE", "TELEPORTED", "PEGGED", "DEPEGGED", "VALUATION", "LARP", "LORE", "PROGRAMMED", "ASYMMETRIC", "UPSIDE", "DOWNSIDE", "R/R", "RISK", "REWARD", "DISCORD", "TELEGRAM", "X", "CALLERS", "CROSS-CHAIN", "WYCKOFF", "ELLIOT WAVE", "BOLLINGER BANDS", "COMPRESSION", "MULTIPLIER", "COMPOUND", "STEALTH", "LAUNCH", "GAMBLING", "RWA", "CAPITAL", "DEPLOYED", "LITIGATION", "ENFORCEMENT", "REGULATIONS", "VIBE", "TRIGGER", "SECTOR", "COLLATERAL", "STRATEGY", "OUTPERFORMANCE", "UNDERPERFORMANCE", "LAGGING", "LEADING", "INFLOWS", "OUTFLOWS", "STRADDLE", "STOCHASTIC", "TARGET", "FIBONACCI", "EXTENSION", "ALL IN", "BONUS", "TESTNET", "POINTS", "ROLLBACK", "DEFAULT", "OG", "TRUSTED", "REPOST", "BLACK SWAN", "FUMBLE", "SETUP", "MEAN REVERSION", "TRENDLINE", "GENERATIVE", "LOCK IN", "CYCLE", "COOK", "STINK BID", "HARDWARE", "SOFTWARE", "OPEN SOURCE", "GITHUB", "ADVERSARIAL", "NEURAL", "ORGANIC", "LIQUID", "ILLIQUID", "REBRAND", "UNWIND", "COVERING", "HONEYPOT", "BAGS", "COLLECTOR", "UNDEREXPOSED", "OVEREXPOSED", "UNBANKED", "BANNED", "LISTED", "DELISTED", "COPYCAT", "BIAS", "BROKIE", "PRESALE", "MARKET DEPTH", "SLIPPAGE", "WASHED", "LIQUIDITY", "BAIT & SWITCH", "SYNTHETIC", "ROCKET FUEL", "BLACKROCK", "IOTB", "GENERATIONAL", "BITCOIN", "ETHEREUM", "SOLANA", "NFTS", "TOKEN", "TOP BLAST", "BOTTOM SELL", "CONFIDENTIAL", "EXPLOIT", "WTF", "CASINO", "FADE", "BEEPLE" ]; let exporting = false; let exportSeedStart = 0; let exportSeedEnd = 0; let exportSeedCurrent = 0; let exportZip; let progressDiv; const positiveEmojis = [ '๐Ÿฅฐ','๐Ÿคฉ','๐Ÿค‘','๐Ÿฅณ','๐Ÿซก','๐Ÿ’‹','๐Ÿ’ฏ','๐Ÿ’ฆ','๐Ÿ‘','๐Ÿ™','๐Ÿ’ช', '๐Ÿง ','๐Ÿ‘€','๐Ÿง‘โ€๐Ÿš€','๐Ÿ‹๏ธโ€โ™‚๏ธ','๐Ÿฆ','๐Ÿฆ„','๐Ÿธ','๐Ÿณ','๐Ÿ€','๐ŸŒ', '๐Ÿ’','๐Ÿ†','๐Ÿ•','๐Ÿฟ','๐Ÿพ','๐Ÿบ','๐Ÿ–','๐Ÿ›','๐Ÿช‚','๐ŸŒ™', '๐ŸŒˆ','โšก','๐Ÿ”ฅ','๐ŸŽ‰','๐ŸŽ','๐Ÿ†','๐ŸŽฏ','๐Ÿ‘‘','๐Ÿ’Ž','๐Ÿ“ธ', '๐Ÿ’ก','๐Ÿ’ฐ','๐Ÿ’ธ','๐Ÿงฒ','๐Ÿ’Š','โžก','โœ…','๐Ÿคฏ','๐Ÿ”‘', ]; const negativeEmojis = [ '๐Ÿค”','๐Ÿ˜ฌ','๐Ÿซจ','๐Ÿคฎ','๐Ÿ˜ญ','๐Ÿคฌ','๐Ÿ’€','๐Ÿ’ฉ','๐Ÿคก','๐Ÿ’”','โค๏ธโ€๐Ÿ”ฅ', '๐Ÿ–•','๐Ÿซต','๐Ÿคทโ€โ™‚๏ธ','๐Ÿ„','๐ŸŸ','โ˜•','๐Ÿฅค','๐Ÿš”','๐Ÿšจ', 'โณ','๐ŸŒš','๐Ÿงจ','๐Ÿ”ซ','๐ŸŽฒ','๐Ÿ”’','๐Ÿ’ฃ','๐Ÿฉธ','๐Ÿšฝ','๐Ÿงฏ', 'โš ','โ›”','๐Ÿšซ','โžก','โ†ฉ','๐Ÿ”„','๐Ÿ”œ','โ‰', 'โ™ป','โ˜ข','๐Ÿ†˜','๐Ÿšฉ','๐Ÿดโ€โ˜ ๏ธ','๐Ÿคฏ' ]; const eyePositions = [ [ { eye1: [102.00, 100.00], eye2: [161.00, 102.00] }, { eye1: [82.00, 88.00], eye2: [82.00, 88.00] }, { eye1: [106.00, 79.00], eye2: [148.00, 91.00] }, { eye1: [92.00, 105.00], eye2: [134.00, 108.00] }, { eye1: [107.00, 66.00], eye2: [149.00, 66.00] }, { eye1: [62.00, 80.00], eye2: [103.00, 80.00] }, { eye1: [72.00, 76.00], eye2: [127.00, 76.00] }, { eye1: [50.00, 83.00], eye2: [50.00, 83.00] }, { eye1: [88.00, 87.00], eye2: [128.00, 86.00] }, { eye1: [93.00, 99.00], eye2: [136.00, 104.00] }, { eye1: [77.00, 94.00], eye2: [124.00, 94.00] }, { eye1: [68.00, 103.00], eye2: [115.00, 96.00] }, { eye1: [82.00, 93.00], eye2: [126.00, 92.00] }, { eye1: [118.00, 100.00], eye2: [160.00, 91.00] }, { eye1: [76.00, 113.00], eye2: [120.00, 107.00] }, { eye1: [73.00, 106.00], eye2: [120.00, 105.00] }, ], [ { eye1: [74.00, 92.00], eye2: [117.00, 91.00] }, { eye1: [101.00, 92.00], eye2: [146.00, 96.00] }, { eye1: [80.00, 107.00], eye2: [133.00, 114.00] }, { eye1: [131.00, 61.00], eye2: [162.00, 68.00] }, { eye1: [53.00, 107.00], eye2: [125.00, 103.00] }, { eye1: [47.00, 112.00], eye2: [88.00, 114.00] }, { eye1: [147.00, 85.00], eye2: [179.00, 80.00] }, { eye1: [131.00, 89.00], eye2: [167.00, 88.00] }, { eye1: [80.00, 79.00], eye2: [121.00, 79.00] }, { eye1: [83.00, 109.00], eye2: [123.00, 108.00] }, { eye1: [87.00, 57.00], eye2: [142.00, 48.00] }, { eye1: [112.00, 112.00], eye2: [152.00, 111.00] }, { eye1: [81.00, 98.00], eye2: [127.00, 95.00] }, { eye1: [64.00, 90.00], eye2: [120.00, 92.00] }, { eye1: [88.00, 86.00], eye2: [141.00, 86.00] }, { eye1: [90.00, 85.00], eye2: [141.00, 78.00] }, ], [ { eye1: [93.00, 99.00], eye2: [144.00, 80.00] }, { eye1: [95.00, 110.00], eye2: [171.00, 108.00] }, { eye1: [53.00, 85.00], eye2: [93.00, 89.00] }, { eye1: [38.00, 80.00], eye2: [88.00, 89.00] }, { eye1: [86.00, 87.00], eye2: [132.00, 86.00] }, { eye1: [80.00, 85.00], eye2: [132.00, 87.00] }, { eye1: [110.00, 87.00], eye2: [160.00, 88.00] }, { eye1: [59.00, 94.00], eye2: [135.00, 93.00] }, { eye1: [55.00, 69.00], eye2: [104.00, 69.00] }, { eye1: [105.00, 99.00], eye2: [157.00, 100.00] }, { eye1: [108.00, 103.00], eye2: [158.00, 102.00] }, { eye1: [79.00, 89.00], eye2: [122.00, 84.00] }, { eye1: [66.00, 104.00], eye2: [106.00, 102.00] }, { eye1: [67.00, 103.00], eye2: [112.00, 102.00] }, { eye1: [98.00, 97.00], eye2: [139.00, 93.00] }, { eye1: [88.00, 90.00], eye2: [131.00, 90.00] }, ], [ { eye1: [93.00, 106.00], eye2: [132.00, 103.00] }, { eye1: [85.00, 104.00], eye2: [134.00, 109.00] }, { eye1: [105.00, 90.00], eye2: [153.00, 86.00] }, { eye1: [109.00, 88.00], eye2: [157.00, 90.00] }, { eye1: [55.00, 82.00], eye2: [97.00, 80.00] }, { eye1: [83.00, 106.00], eye2: [115.00, 98.00] }, { eye1: [117.00, 102.00], eye2: [159.00, 102.00] }, { eye1: [105.00, 38.00], eye2: [143.00, 36.00] }, { eye1: [85.00, 107.00], eye2: [126.00, 102.00] }, { eye1: [112.00, 78.00], eye2: [157.00, 78.00] }, { eye1: [100.00, 91.00], eye2: [151.00, 92.00] }, { eye1: [99.00, 98.00], eye2: [159.00, 96.00] }, { eye1: [100.00, 98.00], eye2: [148.00, 100.00] }, { eye1: [106.00, 106.00], eye2: [154.00, 98.00] }, { eye1: [54.00, 106.00], eye2: [99.00, 100.00] }, { eye1: [81.00, 86.00], eye2: [124.00, 86.00] }, ], [ { eye1: [118.00,103.00], eye2: [161.00,103.00] }, { eye1: [112.00,102.00], eye2: [151.00,103.00] }, { eye1: [78.00, 72.00], eye2: [144.00, 73.00] }, { eye1: [76.00, 62.00], eye2: [143.00, 61.00] }, { eye1: [90.00, 96.00], eye2: [138.00, 96.00] }, { eye1: [80.00, 95.00], eye2: [127.00, 95.00] }, { eye1: [78.00, 97.00], eye2: [124.00, 96.00] }, { eye1: [84.00, 89.00], eye2: [130.00, 90.00] }, { eye1: [82.00, 85.00], eye2: [130.00, 80.00] }, { eye1: [76.00, 77.00], eye2: [128.00, 74.00] }, { eye1: [92.00, 82.00], eye2: [132.00, 77.00] }, { eye1: [77.00, 72.00], eye2: [127.00, 69.00] }, { eye1: [73.00, 77.00], eye2: [121.00, 71.00] }, { eye1: [67.00, 74.00], eye2: [112.00, 71.00] }, { eye1: [66.00, 75.00], eye2: [114.00, 70.00] }, { eye1: [79.00, 59.00], eye2: [120.00, 58.00] } ] ]; const headpos = [ [ { head: [102.00, 53.00] }, { head: [110.00, 74.00] }, { head: [95.00, 57.00] }, { head: [106.00, 57.00] }, { head: [118.00, 42.00] }, { head: [118.00, 48.00] }, { head: [104.00, 57.00] }, { head: [102.00, 51.00] }, { head: [108.00, 49.00] }, { head: [106.00, 65.00] }, { head: [104.00, 64.00] }, { head: [111.00, 78.00] }, { head: [105.00, 62.88] }, { head: [99.00, 49.88] }, { head: [97.00, 79.88] }, { head: [101.00, 70.88] } ], [ { head: [104.00, 59.00] }, { head: [109.00, 62.00] }, { head: [109.00, 73.00] }, { head: [104.00, 47.00] }, { head: [102.00, 76.00] }, { head: [86.00, 79.00] }, { head: [153.00, 64.00] }, { head: [149.00, 60.00] }, { head: [106.00, 58.00] }, { head: [94.00, 78.00] }, { head: [106.00, 35.00] }, { head: [100.00, 79.00] }, { head: [103.00, 65.88] }, { head: [103.00, 62.00] }, { head: [109.00, 50.88] }, { head: [119.00, 45.00] } ], [ { head: [92.00, 61.00] }, { head: [122.00, 68.00] }, { head: [105.00, 59.00] }, { head: [98.00, 66.00] }, { head: [106.00, 62.00] }, { head: [91.00, 67.00] }, { head: [106.00, 52.00] }, { head: [80.00, 55.00] }, { head: [109.00, 31.00] }, { head: [105.00, 64.00] }, { head: [110.00, 61.00] }, { head: [114.00, 66.00] }, { head: [100.00, 74.00] }, { head: [96.00, 79.00] }, { head: [98.00, 68.00] }, { head: [107.00, 64.00] } ], [ { head: [99.00, 68.00] }, { head: [98.00, 68.00] }, { head: [105.00, 59.00] }, { head: [111.00, 70.00] }, { head: [118.00, 50.00] }, { head: [102.00, 75.00] }, { head: [103.00, 60.00] }, { head: [135.00, 31.00] }, { head: [111.00, 78.00] }, { head: [100.00, 57.00] }, { head: [101.00, 56.00] }, { head: [102.00, 62.00] }, { head: [100.00, 62.00] }, { head: [110.00, 56.00] }, { head: [87.00, 63.00] }, { head: [105.00, 65.00] } ], [ { head: [103.00, 77.00] }, { head: [103.00, 78.00] }, { head: [105.00, 55.00] }, { head: [104.00, 53.00] }, { head: [111.00, 70.00] }, { head: [107.00, 70.00] }, { head: [101.00, 70.00] }, { head: [111.00, 70.00] }, { head: [102.00, 62.00] }, { head: [101.00, 57.00] }, { head: [100.00, 66.00] }, { head: [101.00, 52.00] }, { head: [97.00, 51.00] }, { head: [96.00, 48.00] }, { head: [87.00, 53.00] }, { head: [102.00, 37.00] } ] ]; const SeedCategories = [ { name: "pnl100", emoji: null, emojiSet: ['๐Ÿš€','๐ŸŽฏ','๐Ÿค‘'], secondaryEmoji: "โœจ", bgGradient: { top: [0, 100, 255], bottom: [135, 206, 250] }, maxRotation: 45, textSize: 17, textColor: [68, 250, 86], textStroke: 3 }, { name: "pnlNeg100", emoji: null, emojiSet: ['โ˜ ๏ธ','๐Ÿ’ฉ','๐Ÿ’ฃ'], secondaryEmoji: "๐Ÿ’€", bgColor: [0, 0, 0], maxRotation: 45, textSize: 17, textColor: [255,0,0], textStroke: 3 }, { name: "pnlZero", emoji: " ", secondaryEmoji: " ", bgColor: [200,200,200], maxRotation: 0, textSize: 17, textColor: [200,200,200], textStroke: 3 }, { name: "ascending", emoji: " ", secondaryEmoji: " ", maxRotation: 45, textSize: 15, textColor: [245, 233, 103], textStroke: 3 }, { name: "descending", emoji: "๐Ÿ”บ", secondaryEmoji: " ", maxRotation: 45, textSize: 15, textColor: [245, 233, 103], textStroke: 3 }, { name: "pairrepeat", emoji: '๐Ÿ’', secondaryEmoji: " ", maxRotation: 45, textSize: 18, textColor: [186, 63, 65], textStroke: 3 }, { name: "solo", emoji: "๐Ÿฅ‡", secondaryEmoji: "โญ", bgGradient: { top: [255,215,0], bottom: [184,134,11] }, maxRotation: 0, textSize: 50, textColor: [255,255,255], textStroke: 5 }, { name: "nice", emoji: "๐Ÿ’‹", secondaryEmoji: "โ™ฅ", bgGradient: { top: [255,105,180], bottom: [200,50,140] }, maxRotation: 45, textSize: 20, textColor: [255,105,180], textStroke: 5 }, { name: "blaze", emoji: "๐Ÿ”ฅ", secondaryEmoji: "๐ŸŒฑ", bgGradient: { top: [16, 255, 15], bottom: [18, 168, 17] }, maxRotation: 0, textSize: 20, textColor: [0,255,0], textStroke: 5 }, { name: "beast", emoji: "๐Ÿ‘น", secondaryEmoji: "๐Ÿ”ฅ", bgGradient: { top: [255,0,0], bottom: [0,0,0] }, maxRotation: 0, textSize: 20, textColor: [255,0,0], textStroke: 5 }, { name: "emergency", emoji: "๐Ÿšจ", secondaryEmoji: " ", bgColor: [0,0,0], maxRotation: 45, textSize: 25, textColor: [255,0,0], textStroke: 6 }, { name: "max", emoji: "๐Ÿ…", secondaryEmoji: "๐Ÿ…", bgGradient: { top: [255,215,0], bottom: [184,134,11] }, maxRotation: 0, textSize: 20, textColor: [255,255,255], textStroke: 6 }, { name: "millennium", emoji: "0๏ธโƒฃ", secondaryEmoji: " ", bgColor: [63, 189, 255], maxRotation: 45, textSize: 18, textColor: [230,230,235], textStroke: 4 }, { name: "gold", emoji: "โญ", secondaryEmoji: "๐Ÿฅ‡", bgGradient: { top: [255,215,0], bottom: [184,134,11] }, maxRotation: 0, textSize: 24, textColor: [242, 206, 98], textStroke: 5 }, { name: "silver", emoji: "๐Ÿฅˆ", secondaryEmoji: "๐Ÿฅˆ", bgGradient: { top: [220,220,220], bottom: [100,100,100] }, maxRotation: 0, textSize: 22, textColor: [235,235,235], textStroke: 4 }, { name: "palindrome", emoji: "๐Ÿฆ‹", secondaryEmoji: "๐Ÿ’", bgGradient: { top: [210,160,255], bottom: [90,20,120] }, maxRotation: 45, textSize: 16, textColor: [80, 207, 247], textStroke: 3 }, { name: "pups", emoji: "โ‚ฟ", secondaryEmoji: "โ‚ฟ", bgColor: [255, 128, 0], maxRotation: 15, textSize: 16, textColor: [227, 148, 42], textStroke: 3 }, { name: "mcd", emoji : " ", secondaryEmoji: "๐Ÿ”", bgColor: [250, 0, 0], maxRotation: 45, textSize: 15, textColor: [240, 238, 87], textStroke: 3 }, { name: "hands", emoji : " ", secondaryEmoji: "๐Ÿ’ช", bgGradient: { top: [255,200,120], bottom: [255,240,200] }, maxRotation: 45, textSize: 18, textColor: [189, 227, 255], textStroke: 3 }, { name: "ledger", emoji : " ", secondaryEmoji: "๐Ÿ”’", bgGradient: { top: [ 70, 70, 70], bottom: [200,200,200] }, maxRotation: 45, textSize: 16, textColor: [117, 64, 242], textStroke: 3 }, { name: "ramen", emoji : " ", secondaryEmoji: "๐Ÿœ", bgColor: [112, 213, 250], maxRotation: 45, textSize: 16, textColor: [132, 233, 240], textStroke: 3 }, { name: "crypto_bitcoin", secondaryEmoji: " ", bgColor: [255,138, 0], maxRotation: 45, textSize: 18, textColor: [235,235, 235], textStroke: 4 }, { name: "crypto_ethereum", secondaryEmoji: " ", bgColor: [106,115,173], maxRotation: 45, textSize: 16, textColor: [202, 218, 247], textStroke: 4 }, { name: "crypto_solana", secondaryEmoji: " ", bgGradient: { top: [114,154,250], bottom: [102,214,230] }, maxRotation: 45, textSize: 18, textColor: [113, 242, 242], textStroke: 4 }, { name: "crypto_binance", secondaryEmoji: " ", bgColor: [0,0,0], maxRotation: 45, textSize: 18, textColor: [214, 190, 70], textStroke: 4 }, { name: "crypto_bitmap", secondaryEmoji: " ", bgColor: [0,0,0], maxRotation: 45, textSize: 18, textColor: [240, 155, 50], textStroke: 3 }, { name: "crypto_pump", secondaryEmoji: "๐ŸŽฒ", bgColor: [167,230,214], maxRotation: 45, textSize: 15, textColor: [132, 235, 188], textStroke: 4 }, { name: "normal", emoji: null, secondaryEmoji: " ", maxRotation: 45, textSize: 15, textColor: [0,0,0], textStroke: 0 } ]; const SHADOW_CFG = { enabled: true, color: 'rgba(0,0,0,0.40)', blur: 3, offsetX: 2, offsetY: 2 }; const SPECIAL_SEEDS = [ 457, 907, 1361, 1823, 2269, 2729, 3181, 3631, 4091, 4547, 5003, 5449, 5903, 6361, 6811, 7271, 7723, 8179, 8627, 9001, 9539 ]; const SPECIAL_TILES = { 457: { phrase: "THE WHALE", emoji: "๐Ÿณ" }, 907: { phrase: "THE DEV", emoji: "๐Ÿ’ป" }, 1361: { phrase: "THE MINER", emoji: "" }, 1823: { phrase: "THE APE", emoji: "๐Ÿฆ" }, 2269: { phrase: "THE BULL", emoji: "๐Ÿ‚" }, 2729: { phrase: "THE BEAR", emoji: "๐Ÿ‘Ž" }, 3181: { phrase: "THE MOON", emoji: "๐ŸŒ™" }, 3631: { phrase: "THE PUMP", emoji: "๐Ÿ“ˆ" }, 4091: { phrase: "THE DUMP", emoji: "๐Ÿ“‰" }, 4547: { phrase: "THE TOP", emoji: "โฌ†๏ธ" }, 5003: { phrase: "THE BOTTOM", emoji: "โฌ‡๏ธ" }, 5449: { phrase: "THE REVERSAL", emoji: "๐Ÿ”„" }, 5903: { phrase: "THE TARGET", emoji: "๐ŸŽฏ" }, 6361: { phrase: "THE BAGS", emoji: "๐Ÿ›๏ธ" }, 6811: { phrase: "THE AIRDROP", emoji: "๐Ÿช‚" }, 7271: { phrase: "THE CABAL", emoji: "๐Ÿ˜ˆ" }, 7723: { phrase: "THE BOT", emoji: "๐Ÿค–" }, 8179: { phrase: "THE KOL", emoji: "๐Ÿคก" }, 8627: { phrase: "THE ALPHA", emoji: "๐Ÿ’ก" }, 9001: { phrase: "THE FEAR", emoji: "๐Ÿ˜ฌ" }, 9539: { phrase: "THE GREED", emoji: "๐Ÿค‘" } }; function preload() { img1 = loadImage('image1.png'); img2 = loadImage('image2.png'); img3 = loadImage('image3.png'); img4 = loadImage('image4.png'); eyeOverlayImg = loadImage('eyes.png'); logoSheet = loadImage('logos.png'); hundosSheet = loadImage('hundos.png'); accessoriesImg = loadImage('accessories.png'); pupsgrid = loadImage('ordinals.png'); } function setup() { createCanvas(330, 330); noLoop(); progressDiv = createDiv('Idle') .style('font-family','monospace') .style('margin','8px 0'); createButton('Start Batch 1โ€“1000').mousePressed(() => { if (!exporting) startBatchExport(9001, 10000); }); createButton('Start Batch 1001โ€“2000').mousePressed(() => { if (!exporting) startBatchExport(8001, 9000); }); createButton('Abort Batch').mousePressed(() => { exporting = false; progressDiv.html('Aborted (will finalize zip if any images).'); if (exportZip) finalizeZip(true); }); } function deterministicPnL(seed) { const r = mulberry32(seed * 9176 + 12345)(); return Math.floor(r * 201) - 100; } function deterministicVol(seed) { const r = mulberry32(seed * 337 + 777)(); return r * 20; } function chooseChartStyleFromSeed(seed) { const r = mulberry32(seed * 4441 + 99)(); if (r < 0.50) return 'candlestick'; if (r < 0.80) return 'area'; return 'bar'; } function startBatchExport(startSeed, endSeed) { if (exporting) return; if (startSeed < 1 || endSeed > 10000 || endSeed < startSeed) { console.error('Invalid seed range'); return; } exporting = true; exportSeedStart = startSeed; exportSeedEnd = endSeed; exportSeedCurrent = startSeed; exportZip = new JSZip(); progressDiv.html(`Starting ${startSeed}โ€“${endSeed}`); processNextSeed(); } function processNextSeed() { if (!exporting) return; if (exportSeedCurrent > exportSeedEnd) { finalizeZip(false); return; } background(0); drawSingleTileExport(exportSeedCurrent); canvas.toBlob(blob => { const fname = `seed_${String(exportSeedCurrent).padStart(5,'0')}.png`; exportZip.file(fname, blob); const pct = ((exportSeedCurrent - exportSeedStart + 1) / (exportSeedEnd - exportSeedStart + 1) * 100).toFixed(1); progressDiv.html(`Seed ${exportSeedCurrent}/${exportSeedEnd} (${pct}%)`); exportSeedCurrent++; requestAnimationFrame(processNextSeed); }, 'image/png'); } function finalizeZip(aborted) { progressDiv.html(aborted ? 'Aborted โ€“ zippingโ€ฆ' : 'Batch complete โ€“ zippingโ€ฆ'); exportZip.generateAsync({type:'blob'}).then(blob => { const name = aborted ? `tiles_partial_${exportSeedStart}-${exportSeedCurrent - 1}.zip` : `tiles_${String(exportSeedStart).padStart(5,'0')}-${String(exportSeedEnd).padStart(5,'0')}.zip`; triggerDownload(blob, name); exporting = false; progressDiv.html(aborted ? 'Aborted zip downloaded.' : 'Done.'); }); } function triggerDownload(blob, filename) { const a = document.createElement('a'); a.href = URL.createObjectURL(blob); a.download = filename; document.body.appendChild(a); a.click(); setTimeout(() => { URL.revokeObjectURL(a.href); a.remove(); }, 2000); } function drawSingleTileExport(seed) { externalTileSeed = seed; drawSingleTile(width, height, 0, 0); } function draw() { background(0); const tileSizeCanvas = 330; push(); drawSingleTile(tileSizeCanvas, tileSizeCanvas, 0, 0); pop(); } function readExternalSeed() { if (externalTileSeed != null) return; const scriptTag = document.getElementById('tile-config'); if (scriptTag) { const attr = scriptTag.getAttribute('data-seed'); if (attr && /^\d+$/.test(attr)) { externalTileSeed = parseInt(attr, 10); } } } function drawSingleTile(w, h, row, col) { let tileSeed = (externalTileSeed != null) ? externalTileSeed : 1; if (SPECIAL_TILES[tileSeed]) { drawSpecialTile(tileSeed, width, height); return; } applyTileSeed(tileSeed); let pnl = floor(random(-100, 101)); let volatility = random(0, 20); let phrase = generatePhrase(3); let category = getTileSeedCategory(tileSeed, pnl); let baseLayer = createGraphics(w, h); let style = chooseChartStyle(); const { layer: bgLayer, largeW, largeH } = drawBackgroundFill(category, pnl, tileSeed, w, h); drawSecondaryPattern(bgLayer, category, pnl, w, largeW, largeH, 200); const emojiDrawn = drawEmojis(bgLayer, category, tileSeed, pnl, w, largeW, largeH, 200); if (category === 'normal' && !emojiDrawn) { drawSymbols(bgLayer, category, tileSeed, pnl, w, largeW, largeH); } drawHundosLayer(bgLayer, category, tileSeed, pnl, w, h); drawCryptoLogoLayer(bgLayer, category, tileSeed, pnl, w, largeW, largeH); let clippedBg = getClippedRotatedBg(bgLayer, w, h, tileSeed, pnl, category); imageMode(CORNER); image(clippedBg, 0, 0); if (style === 'candlestick') drawCandlestickChart(pnl, volatility, w, h, tileSeed, clippedBg); if (style === 'area') { drawAreaChart(pnl, volatility, w, h, tileSeed, clippedBg); } if (style === 'bar') { const path = generateBarPricePath(pnl, volatility, w, h); drawBarChartFromPath(path, w, h, clippedBg);} const { imageIndex, tileIndex, tileX, tileY } = drawBaseTile(tileSeed, pnl, category, w, h); drawGlasses(tileSeed, imageIndex, tileIndex, tileX, tileY); drawHats(tileSeed, imageIndex, tileIndex, tileX, tileY); drawEyeOverlay(tileSeed, imageIndex, tileIndex, tileX, tileY); drawSeedText(tileSeed, category, w); drawPnLText(pnl, w); drawPhrase(phrase, w, h, tileSeed, pnl); } function getCategoryDefinition(name) { return SeedCategories.find(c => c.name === name) || SeedCategories.find(c => c.name === 'normal'); } function getTileSeedCategory(seed, pnl) { const s = seed.toString(); if (pnl === 100) return "pnl100"; if (pnl === -100) return "pnlNeg100"; if (pnl === 0) return "pnlZero"; const excluded = new Set([1000,2000,3000,4000,5000,6000,7000,8000,9000,10000]); if (!excluded.has(seed) && /^\d{4}$/.test(s)) { const is_aBBB = /^([0-9])((?!\1)[0-9])\2\2$/.test(s); const is_AAAB = /^([0-9])\1\1(?!\1)[0-9]$/.test(s); const is_AAAA = /^([0-9])\1{3}$/.test(s); if ((is_aBBB || is_AAAB) && !is_AAAA) { return "pups"; } } const asc3 = ["012","123","234","345","456","567","678","789"].includes(s); const asc4 = ["0123","1234","2345","3456","4567","5678","6789"].includes(s); if (asc3 || asc4) return "ascending"; const d3 = ["210","321","432","543","654","765","876","987"].includes(s); const d4 = ["3210","4321","5432","6543","7654","8765","9876"].includes(s); if (d3 || d4) return "descending"; if (/^([0-9])\1([0-9])\2$/.test(s) && s[0] !== s[2]) { return "pairrepeat"; } if (seed === 1) return "solo"; if (seed === 69 || seed === 6969) return "nice"; if (seed === 420) return "blaze"; if (seed === 666) return "beast"; if (seed === 911) return "emergency"; if (seed === 10000) return "max"; if (seed % 100 === 0) return "millennium"; if (/^([0-9])\1{2,3}$/.test(s)) return "gold"; if (s.length === 4 && s.slice(0,2) === s.slice(2) && !/^([0-9])\1{3}$/.test(s)) { return "silver"; } if (s === s.split("").reverse().join("") && s.length >= 2 && s.length <= 4) { return "palindrome"; } if (pnl !== 0 && Math.abs(pnl) >= 75 && Math.abs(pnl) < 100) { const hundosRng = mulberry32(seed + 424400); const trigger = hundosRng(); if (trigger < 0.10) { if (pnl > 0) { const posHundos = ['ledger','hands']; return posHundos[ seed % posHundos.length ]; } else { const negHundos = ['mcd','ramen']; return negHundos[ seed % negHundos.length ]; } } } if (pnl !== 100 && pnl !== -100 && (seed % 100) < 5) { const logos = ['bitcoin','ethereum','solana','binance','bitmap','pump']; const i = floor(mulberry32(seed + 4245)() * logos.length); return `crypto_${logos[i]}`; } return "normal"; } function drawSpecialTile(seed, w, h) { const { emoji, phrase } = SPECIAL_TILES[seed]; const gc = drawingContext; const grd = gc.createLinearGradient(0, 0, 0, h); grd.addColorStop(0, '#000'); grd.addColorStop(1, '#000'); gc.fillStyle = grd; gc.fillRect(0, 0, w, h); push(); textAlign(CENTER, CENTER); textStyle(NORMAL); textSize(w * 0.5); noStroke(); fill(255); text(emoji, w/2, h/2 - w*0.1); pop(); push(); textAlign(CENTER, CENTER); textStyle(BOLD); textFont('Trebuchet MS'); textSize(w * 0.09); fill(225); noStroke(); text(phrase, w/2, h*0.875); pop(); } function drawBackgroundFill(category, pnl, seed, w, h) { const buffer = 150; const largeW = w + buffer; const largeH = h + buffer; const seedNorm = constrain((seed - 1) / 9999, 0, 1); const def = SeedCategories.find(c => c.name === category) || SeedCategories.find(c => c.name === 'normal'); const layer = createGraphics(largeW, largeH); layer.noStroke(); if ((category === 'pnl100' || category === 'pnlNeg100') && def.bgGradient) { const top = layer.color(...def.bgGradient.top), bot = layer.color(...def.bgGradient.bottom); for (let y = 0; y < largeH; y++) { layer.stroke(lerpColor(top, bot, map(y, 0, largeH, 0, 1))); layer.line(0, y, largeW, y); } } else if (def.bgColor) { layer.background(layer.color(...def.bgColor)); } else if (def.bgGradient) { const top = layer.color(...def.bgGradient.top), bot = layer.color(...def.bgGradient.bottom); for (let y = 0; y < largeH; y++) { layer.stroke(lerpColor(top, bot, map(y, 0, largeH, 0, 1))); layer.line(0, y, largeW, y); } } else { const deepRed = layer.color(165, 0, 0), deepGreen = layer.color(0, 135, 0), palePos1 = layer.color(251, 255, 75), paleNeg1 = layer.color(251, 255, 75), palePosMax = layer.color(18, 201, 209), paleNegMax = layer.color(205, 18, 209); const rawPnL = map(abs(pnl), 0, 100, 0, 1); const tPnl = constrain(rawPnL, 0.2, 0.8); const paleGreen = lerpColor(palePos1, palePosMax, seedNorm), paleRed = lerpColor(paleNeg1, paleNegMax, seedNorm); let bgCol; if (pnl > 0) bgCol = lerpColor(paleGreen, deepGreen, tPnl); else if (pnl < 0) bgCol = lerpColor(paleRed, deepRed, tPnl); else bgCol = layer.color(220); layer.background(bgCol); } if (category === 'ascending' || category === 'descending') { drawStairPattern(layer, category, pnl, w, h); } if (category === 'pairrepeat') { drawPairRepeatPattern(layer, pnl, w, h); } return { layer, largeW, largeH }; } function drawStairPattern(layer, direction, pnl, w, h) { const buffer = 150; const largeW = w + buffer; const largeH = h + buffer; const tPnl = map(abs(pnl), 0, 100, 0, 1); const stepSize = max(1, floor(lerp(5, 50, tPnl))); const centerPx = layer.get(largeW/2, largeH/2); const baseColRGB = layer.color(centerPx[0], centerPx[1], centerPx[2]); layer.colorMode(HSB, 360, 100, 100, 1); const Hcomp = (layer.hue(baseColRGB) + 180) % 360; const PATTERN_ALPHA = 0.5; const patternCol = layer.color(Hcomp, 100, 100, PATTERN_ALPHA); layer.colorMode(RGB, 255); layer.push(); layer.noStroke(); layer.fill(patternCol); const basePattern = [ [1,0,0,0], [1,0,1,1], [0,0,1,0], [1,1,1,0] ]; const pattern = direction === "ascending" ? basePattern : basePattern.map(row => row.slice().reverse()); for (let yy = 0; yy < ceil(largeH / stepSize); yy++) { for (let xx = 0; xx < ceil(largeW / stepSize); xx++) { if (pattern[yy % 4][xx % 4]) { layer.rect(xx * stepSize, yy * stepSize, stepSize, stepSize); } } } layer.pop(); } function drawPairRepeatPattern(layer, pnl, w, h) { const buffer = 150; const largeW = w + buffer; const largeH = h + buffer; const tPnl = map(abs(pnl), 0, 100, 0, 1); const rawSz = lerp(25, 85, tPnl); const cellSz = max(1, round(rawSz)); const cols = ceil(largeW / cellSz); const rows = ceil(largeH / cellSz); const centerPx = layer.get(largeW/2, largeH/2); const baseColRGB = layer.color(centerPx[0], centerPx[1], centerPx[2]); layer.colorMode(HSB, 360, 100, 100, 1); const Hcomp = (layer.hue(baseColRGB) + 180) % 360; const PATTERN_ALPHA = 0.15; const patternCol = layer.color(Hcomp, 100, 100, PATTERN_ALPHA); layer.colorMode(RGB, 255); layer.push(); layer.noStroke(); for (let ry = 0; ry < rows; ry++) { for (let cx = 0; cx < cols; cx++) { if ((ry + cx) % 2 === 0) { layer.fill(patternCol); } else { layer.fill(255, 255, 255, 0); } layer.rect(cx * cellSz, ry * cellSz, cellSz, cellSz); } } layer.pop(); } function drawSecondaryPattern(layer,category,pnl,w,largeW,largeH,emojiOpacity = 5) { const def = SeedCategories.find(c => c.name === category); if (!def || !def.secondaryEmoji || def.secondaryEmoji.trim() === '') return; const cfg = { baseScale: 0.10, scaleByIntensity: 0.60, minScale: 0.1, maxScale: 0.6, spacingFactor: 1.05, altRowOffset: 0.5, rowJitter: 0, colJitter: 0, opacity: emojiOpacity }; const intensity = map(abs(pnl), 0, 100, 0, 1); let symSize = cfg.baseScale * w * (1 + cfg.scaleByIntensity * intensity); symSize = constrain(symSize, cfg.minScale * w, cfg.maxScale * w); const spacing = symSize * cfg.spacingFactor; const jitterSeed = (category.charCodeAt(0) * 131 + pnl * 977 + w) | 0; const rng = mulberry32(jitterSeed); layer.push(); layer.textAlign(CENTER, CENTER); layer.textFont('Arial Black'); layer.textStyle(BOLD); layer.textSize(symSize); layer.noStroke(); layer.fill(255, 255, 255, cfg.opacity); const rows = ceil((largeH + spacing * 2) / spacing); const cols = ceil((largeW + spacing * 2) / spacing); for (let r = -1; r < rows; r++) { const y = r * spacing; const xOffset = (r % 2 === 0 ? 0 : cfg.altRowOffset * spacing); for (let c = -1; c < cols; c++) { let x = c * spacing + xOffset; if (cfg.rowJitter > 0 || cfg.colJitter > 0) { x += (rng() - 0.5) * spacing * cfg.colJitter; const yJittered = y + (rng() - 0.5) * spacing * cfg.rowJitter; layer.text(def.secondaryEmoji, x, yJittered); } else { layer.text(def.secondaryEmoji, x, y); } } } layer.pop(); } function drawSymbols(layer, category, seed, pnl, w, largeW, largeH) { if (category !== 'normal' ) return; const symbol = pnl < 0 ? '-' : '+'; const t = map(abs(pnl), 0, 100, 0, 1); const e = 1 - sqrt(1 - t*t); const minF = 0.09, maxF = 0.9; const minM = 0.75, maxM = 0.525; const symSize = lerp(w * minF, w * maxF, e); const spacing = lerp(symSize * minM, symSize * maxM, e); const cSeed = seed < 5000 ? seed + 2500 : seed - 2500; const rv = mulberry32(cSeed + 54321)(); const fP = pnl > 0 ? floor(rv * 100) + 1 : pnl < 0 ? -(floor(rv * 100) + 1) : 0; let baseCol; if (fP === 0) { baseCol = color(255); } else { const dr = color(245, 42, 42), dg = color( 0,207, 33), p1 = color(238,245,118), n1 = color(238,245,118), pM = color( 11,248,255), nM = color(241, 44,255), seedNorm = constrain((seed - 1) / 9999, 0, 1), pg = lerpColor(p1, pM, seedNorm), pr = lerpColor(n1, nM, seedNorm), tF = map(abs(fP), 0, 100, 0, 1); baseCol = fP > 0 ? lerpColor(pg, dg, tF) : lerpColor(pr, dr, tF); } const fillCol = color(red(baseCol), green(baseCol), blue(baseCol), 255); layer.push(); layer.textAlign(CENTER, CENTER); layer.textFont('Arial Black'); layer.textStyle(BOLD); layer.textSize(symSize); layer.noStroke(); layer.fill(fillCol); for (let y = -spacing; y < largeH + spacing; y += spacing) { const xOff = (floor(y/spacing) % 2) ? spacing/2 : 0; for (let x = -spacing; x < largeW + spacing; x += spacing) { const gx = x + xOff; const gy = y; withShadow(layer, () => { layer.text(symbol, gx, gy); }, { blur: 2, offsetX: 2, offsetY: 2, color: 'rgba(0,0,0,0.35)' }); } } layer.pop(); } function drawEmojis(layer, category, seed, pnl, w, largeW, largeH, emojiOpacity = 225) { if (category.startsWith('crypto_')) return false; const def = SeedCategories.find(c => c.name === category); if (!def) return false; const cfg = { normalEmojiChance: 0.65, baseScale: 0.2, scaleByIntensity: 1.25, minScale: 0.08, maxScale: 1.25, spacingFactor: 1.05, altRowOffset: 0.5, rowJitter: 0, colJitter: 0, shadowEnabled: true, shadowBaseBlur: 2, shadowBlurScale: 0.05, shadowOffsetX: 4, shadowOffsetY: 4, shadowAlpha: 0.40 }; const rng = mulberry32(seed + 2222); const isNormal = category === 'normal'; if (isNormal && rng() > cfg.normalEmojiChance) { return false; } let symbol = null; if (def.emojiSet?.length) { symbol = def.emojiSet[floor(rng() * def.emojiSet.length)]; } else if (def.emoji) { symbol = def.emoji; } else if (pnl === 0) { symbol = '='; } else { const pool = pnl < 0 ? negativeEmojis : positiveEmojis; symbol = pool[floor(rng() * pool.length)]; } if (!symbol) return false; const intensity = map(abs(pnl), 0, 100, 0, 1); let symSize = cfg.baseScale * w * (1 + cfg.scaleByIntensity * intensity); symSize = constrain(symSize, cfg.minScale * w, cfg.maxScale * w); const spacing = symSize * cfg.spacingFactor; const layoutSeed = seed * 103 + category.length * 97 + (pnl + 5000); const jitterRng = mulberry32(layoutSeed); const rows = ceil((largeH + spacing * 2) / spacing); const cols = ceil((largeW + spacing * 2) / spacing); layer.push(); layer.textAlign(CENTER, CENTER); layer.textFont('Arial Black'); layer.textStyle(BOLD); layer.textSize(symSize); layer.noStroke(); layer.colorMode(RGB, 255); if (cfg.shadowEnabled) { const ctx = layer.drawingContext; ctx.save(); ctx.shadowColor = `rgba(0,0,0,${cfg.shadowAlpha})`; ctx.shadowBlur = cfg.shadowBaseBlur + symSize * cfg.shadowBlurScale; ctx.shadowOffsetX = cfg.shadowOffsetX; ctx.shadowOffsetY = cfg.shadowOffsetY; layer.fill(255, 255, 255, emojiOpacity); for (let r = -1; r < rows; r++) { const y = r * spacing; const xOff = (r % 2 === 0 ? 0 : cfg.altRowOffset * spacing); for (let c = -1; c < cols; c++) { let x = c * spacing + xOff; let yy = y; if (cfg.colJitter) yy += (jitterRng() - 0.5) * spacing * cfg.rowJitter; if (cfg.rowJitter) x += (jitterRng() - 0.5) * spacing * cfg.colJitter; layer.text(symbol, x, yy); } } ctx.restore(); } else { layer.fill(255, 255, 255, emojiOpacity); for (let r = -1; r < rows; r++) { const y = r * spacing; const xOff = (r % 2 === 0 ? 0 : cfg.altRowOffset * spacing); for (let c = -1; c < cols; c++) { let x = c * spacing + xOff; let yy = y; if (cfg.colJitter) yy += (jitterRng() - 0.5) * spacing * cfg.rowJitter; if (cfg.rowJitter) x += (jitterRng() - 0.5) * spacing * cfg.colJitter; layer.text(symbol, x, yy); } } } layer.pop(); return true; } function drawHundosLayer(layer, category, seed, pnl, w, h) { const hundosCats = ['ledger','hands','mcd','ramen']; if (!hundosCats.includes(category)) return; const spriteIndexMap = { ledger:0, hands:1, mcd:2, ramen:3 }; const pickIndex = spriteIndexMap[category]; const buffer = 150; const largeW = w + buffer, largeH = h + buffer; const cols = 2; const tileW = hundosSheet.width / cols; const tileH = hundosSheet.height / cols; const sx = (pickIndex % cols) * tileW; const sy = floor(pickIndex / cols) * tileH; const sprite= hundosSheet.get(sx, sy, tileW, tileH); const intensity = map(abs(pnl), 0, 100, 0, 1); const minSize = w * 0.18; const maxSize = w * 0.75; const primSize = lerp(minSize, maxSize, intensity); const primSpacing = primSize * 0.8; layer.push(); layer.imageMode(CENTER); layer.tint(255, 220); for (let y = -primSpacing; y < largeH + primSpacing; y += primSpacing) { const xOff = (floor(y/primSpacing) % 2) ? primSpacing/2 : 0; for (let x = -primSpacing; x < largeW + primSpacing; x += primSpacing) { layer.image(sprite, x + xOff, y, primSize, primSize); } } layer.noTint(); layer.pop(); } function drawCryptoLogoLayer(layer, category, seed, pnl, w, h) { if (!category.startsWith('crypto_')) return; const logoKey = category.slice('crypto_'.length); const logos = ['bitcoin','ethereum','solana','binance','bitmap','pump']; const idx = logos.indexOf(logoKey); if (idx < 0) return; const buffer = 150; const largeW = w + buffer, largeH = h + buffer; const intensity = map(Math.abs(pnl), 0, 100, 0, 1); const tileW = logoSheet.width / 3; const tileH = logoSheet.height / 2; const sx = (idx % 3) * tileW; const sy = floor(idx / 3) * tileH; const logo = logoSheet.get(sx, sy, tileW, tileH); const sizeMin = w * 0.05, sizeMax = w * 0.6; const baseSize = lerp(sizeMin, sizeMax, intensity); const spacing = lerp(baseSize * 1.35, baseSize / 1.25, intensity); layer.push(); layer.imageMode(CENTER); for (let y = -spacing; y < largeH + spacing; y += spacing) { const xOff = (floor(y / spacing) % 2) ? spacing/2 : 0; for (let x = -spacing; x < largeW + spacing; x += spacing) { layer.image(logo, x + xOff, y, baseSize, baseSize); } } layer.pop(); } function getClippedRotatedBg(bgLayer, w, h, tileSeed, pnl, category, lowClamp = 20, highClamp = 235) { const def = getCategoryDefinition(category); const maxRad = radians(def.maxRotation || 0); const frac = constrain((tileSeed - 1) / 9999, 0, 1); const angle = frac * maxRad * (pnl > 0 ? -1 : 1); const srcW = bgLayer.width; const srcH = bgLayer.height; const D = ceil(sqrt(srcW*srcW + srcH*srcH)); const buf = createGraphics(D, D); buf.push(); buf.translate(D/2, D/2); buf.rotate(angle); buf.imageMode(CENTER); buf.image(bgLayer, 0, 0); buf.pop(); applyClampAndDesaturate(buf, 35, 220, 0.875); const x0 = (D - w) / 2; const y0 = (D - h) / 2; return buf.get(x0, y0, w, h); } function applyClampAndDesaturate(buf, lowClamp, highClamp, satFactor) { buf.loadPixels(); for (let i = 0; i < buf.pixels.length; i += 4) { let r = constrain(buf.pixels[i], lowClamp, highClamp); let g = constrain(buf.pixels[i + 1], lowClamp, highClamp); let b = constrain(buf.pixels[i + 2], lowClamp, highClamp); const lum = 0.2126 * r + 0.7152 * g + 0.0722 * b; r = lum + (r - lum) * satFactor; g = lum + (g - lum) * satFactor; b = lum + (b - lum) * satFactor; buf.pixels[i] = r; buf.pixels[i + 1] = g; buf.pixels[i + 2] = b; } buf.updatePixels(); } function chooseChartStyle() { let r = random(1); if (r < 0.50) return 'candlestick'; else if (r < 0.75) return 'area'; else return 'bar'; } function generatePricePath(pnl, volatility, w, h, points = 20) { const margin = 10; const startY = h * 0.5; let endY; if (pnl >= 1) { const clamped = constrain(pnl, 1, 100); const logPnL = log(clamped) / log(100); endY = lerp(startY, margin, logPnL); } else if (pnl > 0) { endY = lerp(startY, startY - 20, pnl); } else if (pnl === 0) { endY = startY; } else { endY = map(pnl, -100, 0, h - margin, startY); } let pricePath = []; const baseNoiseOffset = random(10000); const noiseAmp = volatility * h * 0.05; for (let i = 0; i < points; i++) { const t = i / (points - 1); const linearY = lerp(startY, endY, t); let y; if (i === 0 || i === points - 1) { y = linearY; } else { const nv = noise(baseNoiseOffset + t * 2); const noiseMapped = map(nv, 0, 1, -1, 1); y = linearY + noiseMapped * noiseAmp; } pricePath.push(constrain(y, margin, h - margin)); } return pricePath; } function generateBarPricePath(pnl, volatility, w, h, points=12, margin=10) { const mid = h * 0.5; const endY = pnlToY(pnl, h, margin); const startY = mid; const noiseSeedBase = pnl * 1337 + points * 17; const rng = mulberry32(noiseSeedBase); noiseSeed(noiseSeedBase); const path = []; for (let i=0; i= topMargin) return y; const d = topMargin - y; const adjusted = topMargin - (softness * Math.log1p(d / softness)); return max(0, adjusted); } function generateCandles(pnl, volatility, w, h) { let candles = []; let candleCount = 12; let candleWidth = w / candleCount; let startY = h / 2; let endY = map(pnl, -100, 100, h, 0); noiseSeed(random(1000)); let pricePath = []; for (let i = 0; i < candleCount; i++) { let t = i / (candleCount - 1); let noiseVal = (noise(t * 0.2) * 2 - 1) + (noise(t * 0.5 + 100) * 2 - 1) * 0.5; let trendY = lerp(startY, endY, t); let offset = noiseVal * volatility * 5; let y = constrain(trendY + offset, 10, h - 10); pricePath.push(y); } let prevClose; for (let i = 0; i < candleCount; i++) { let x = i * candleWidth + candleWidth / 2; let open, close; if (i === 0) { let step = random(-volatility, volatility) * 2; let direction = step >= 0 ? 1 : -1; let bodySize = abs(step) + 5; if (direction > 0) { close = startY; open = close - bodySize; } else { close = startY; open = close + bodySize; } } else if (i === candleCount - 1) { open = prevClose; close = endY; } else { open = prevClose; let targetY = pricePath[i]; let step = random(-volatility, volatility) * 2; close = open + step + (targetY - open) * 0.2; } let high = max(open, close) + random(0, volatility * 2); let low = min(open, close) - random(0, volatility * 2); candles.push({ x, open: constrain(open, 0, h), close: constrain(close, 0, h), high: constrain(high, 0, h), low: constrain(low, 0, h) }); prevClose = close; } return candles; } function drawCandlestickChart(pnl, volatility, w, h) { noSmooth(); push(); try { const candles = generateCandles(pnl, volatility, w, h); const slotW = w / candles.length; const bodyW = slotW * 0.9; for (let c of candles) { push(); const cx = round(c.x); const highY = round(c.high); const lowY = round(c.low); const openY = round(c.open); const closeY = round(c.close); const yTop = min(openY, closeY); const yBot = max(openY, closeY); const bodyH = max(1, yBot - yTop); const bodyX = cx - bodyW / 2; const isBull = closeY <= openY ? true : false; const bodyCol = isBull ? color(0,128,0) : color(255,0,0); const shadowOffsetX = 4; const shadowOffsetY = 4; const shadowAlpha = 110; const shadowBlur = 8; push(); drawingContext.save(); drawingContext.shadowColor = 'rgba(0,0,0,0.45)'; drawingContext.shadowBlur = shadowBlur; drawingContext.shadowOffsetX = 0; drawingContext.shadowOffsetY = 0; noStroke(); fill(0, shadowAlpha); rect(bodyX + shadowOffsetX, yTop + shadowOffsetY, bodyW, bodyH); drawingContext.restore(); pop(); drawingContext.save(); drawingContext.lineCap = 'butt'; drawingContext.shadowColor = 'rgba(0,0,0,0.4)'; drawingContext.shadowBlur = 4; drawingContext.shadowOffsetX = 2; drawingContext.shadowOffsetY = 2; stroke(0); strokeWeight(2); line(cx, highY, cx, yBot); line(cx, lowY, cx, yTop); drawingContext.restore(); blendMode(BLEND); noStroke(); fill(red(bodyCol), green(bodyCol), blue(bodyCol), 150); rect(bodyX, yTop, bodyW, bodyH); blendMode(ADD); fill(bodyCol, 64); rect(bodyX, yTop, bodyW, bodyH); blendMode(BLEND); pop(); } } catch (err) { console.error('Candlestick drawing error:', err); } pop(); smooth(); } function drawAreaChart(pnl, volatility, w, h, seed, bgBuffer) { const src = bgBuffer || { get: (x,y) => get(x,y) }; const yTop = max(0, floor(h * 0.25)); const yBot = min(h - 1, floor(h * 0.75)); const pxTop = src.get(floor(w/2), yTop); const pxBot = src.get(floor(w/2), yBot); let colTop = color(pxTop[0], pxTop[1], pxTop[2]); let colBot = color(pxBot[0], pxBot[1], pxBot[2]); colorMode(HSB, 360, 100, 100, 1); let h1 = hue(colTop), s1 = saturation(colTop), b1 = brightness(colTop); let h2 = hue(colBot), s2 = saturation(colBot), b2 = brightness(colBot); function avgHue(a, b) { let d = ((b - a + 540) % 360) - 180; return (a + d / 2 + 360) % 360; } let baseH = avgHue(h1, h2); let baseS = (s1 + s2) * 0.5; let baseB = (b1 + b2) * 0.5; if (baseS < 5) { const rng = mulberry32(seed + 424242); baseH = (rng() * 360) % 360; baseS = 18 + rng() * 14; } const jitter = (mulberry32(seed + 424243)() - 0.5) * 12; const Hcomp = (baseH + 180 + jitter + 360) % 360; const Scomp = constrain(baseS, 15, 70); const Bcomp = constrain(baseB, 25, 85); const fillColorStrong = color(Hcomp, Scomp, Bcomp, 0.5); const fillColorSoft = color(Hcomp, Scomp, Bcomp, 0.3); const pricePath = generatePricePath(pnl, volatility, w, h, 12); const topMargin = 6; const pathClamped = pricePath.map(y => softTopClamp(y, topMargin)); blendMode(BLEND); noStroke(); fill(fillColorSoft); beginShape(); vertex(0, h); for (let i = 0; i < pricePath.length; i++) { const x = map(i, 0, pricePath.length - 1, 0, w); vertex(x, pricePath[i]); } vertex(w, h); endShape(CLOSE); blendMode(ADD); fill(fillColorStrong); beginShape(); vertex(0, h); for (let i = 0; i < pricePath.length; i++) { const x = map(i, 0, pricePath.length - 1, 0, w); vertex(x, pricePath[i]); } vertex(w, h); endShape(CLOSE); blendMode(ADD); stroke(color(Hcomp, Scomp, Bcomp, 0.25)); strokeWeight(3); noFill(); beginShape(); for (let i = 0; i < pricePath.length; i++) { const x = map(i, 0, pricePath.length - 1, 0, w); vertex(x, pricePath[i]); } endShape(); blendMode(BLEND); colorMode(RGB, 255); } function drawBarChartFromPath(rawPath, w, h, bgBuffer, barCount = 12, debugBars = false) { barCount = min(barCount, rawPath.length); const topMargin = 6; const path = []; for (let i = 0; i < barCount; i++) { const y = softTopClamp(constrain(rawPath[i], 0, h - 10), topMargin); path.push(y); } const samplePixel = (x, y) => { const px = (bgBuffer && bgBuffer.get) ? bgBuffer.get(x, y) : get(x, y); return color(px[0], px[1], px[2]); }; const xMid = floor(w / 2); const colTop = samplePixel(xMid, floor(h * 0.25)); const colBot = samplePixel(xMid, floor(h * 0.75)); colorMode(HSB, 360, 100, 100, 1); function avgHue(a, b) { const d = ((b - a + 540) % 360) - 180; return (a + d / 2 + 360) % 360; } let baseH = avgHue(hue(colTop), hue(colBot)); let baseS = (saturation(colTop) + saturation(colBot)) * 0.5; let baseB = (brightness(colTop) + brightness(colBot)) * 0.5; if (baseS < 6) { const rng = mulberry32(xMid * 101 + h * 313 + 70007); baseH = (rng() * 360) % 360; baseS = 24 + rng() * 18; } const Hcomp = (baseH + 180) % 360; const Scomp = constrain(baseS < 40 ? baseS * 1.4 : baseS * 1.1, 30, 65); const Braw = baseB * 0.65 + 10; const Bcomp = constrain(Braw, 50, 55); const coreFill = color(Hcomp, Scomp * 0.55, max(10, Bcomp - 15), 0.18); const baseFill = color(Hcomp, Scomp, Bcomp, 0.50); const glowFill = color(Hcomp, Scomp, min(100, Bcomp + 25), 0.50); if (debugBars) { push(); colorMode(RGB, 255); noStroke(); fill(coreFill); rect(4, 4, 14, 14); fill(baseFill); rect(22, 4, 14, 14); fill(glowFill); rect(40, 4, 14, 14); pop(); } colorMode(RGB, 255); const cellW = w / barCount; const rectW = cellW * 0.85; function drawPass(fillCol) { fill(fillCol); noStroke(); for (let i = 0; i < barCount; i++) { const yTop = path[i]; const barH = max(0, h - yTop); const x = i * cellW + (cellW - rectW) / 2; rect(x, yTop, rectW, barH); } } blendMode(MULTIPLY); drawPass(coreFill); blendMode(BLEND); drawPass(baseFill); blendMode(ADD); drawPass(glowFill); blendMode(BLEND); colorMode(RGB, 255); } function pnlToY(pnl, h, margin=10) { const mid = h * 0.5; if (pnl > 0) { const clamped = constrain(pnl, 1, 100); const logT = Math.log(clamped) / Math.log(100); return lerp(mid, margin, logT); } else if (pnl < 0) { const t = constrain((pnl - (-100)) / (-1 - (-100)), 0, 1); const eased = t * t; return lerp(h - margin, mid, eased); } else { return mid; } } function drawBaseTile(tileSeed, pnl, category, w, h) { const TINT_FACTOR_MAX = 0.50; const SAT_BOOST_MAX = 0.2; const ADD_ALPHA_MAX = 20; const POS_TINT_COLOR = [0, 255, 0]; const NEG_TINT_COLOR = [255, 0, 0]; const POS_ADD_COLOR = [80, 255, 80]; const NEG_ADD_COLOR = [255, 80, 80]; const rng = mulberry32(tileSeed + 5678); let imageIndex, tileIndex; let baseTile; if (category === 'pups') { const cols = 4, rows = 4; imageIndex = 4; const colIndex = floor(rng() * cols); const rowIndex = floor(rng() * rows); tileIndex = rowIndex * cols + colIndex; baseTile = createImage(tileSize, tileSize); baseTile.copy( pupsgrid, colIndex * tileSize, rowIndex * tileSize, tileSize, tileSize, 0, 0, tileSize, tileSize ); } else { imageIndex = floor(rng() * 4); const img = [img1, img2, img3, img4][imageIndex]; const colIndex = floor(rng() * 4); const rowRange = pnl < 0 ? [2,3] : pnl > 0 ? [0,1] : [0,3]; const rowIndex = floor(rng() * (rowRange[1] - rowRange[0] + 1)) + rowRange[0]; tileIndex = rowIndex * 4 + colIndex; baseTile = createImage(tileSize, tileSize); baseTile.copy( img, colIndex * tileSize, rowIndex * tileSize, tileSize, tileSize, 0, 0, tileSize, tileSize ); } if (pnl === 100 || pnl === -100) { const isPos = (pnl === 100); const target = isPos ? POS_TINT_COLOR : NEG_TINT_COLOR; const tintF = TINT_FACTOR_MAX; const satBoost = SAT_BOOST_MAX; baseTile.loadPixels(); for (let i = 0; i < baseTile.pixels.length; i += 4) { let r = baseTile.pixels[i]; let g = baseTile.pixels[i + 1]; let b = baseTile.pixels[i + 2]; const a = baseTile.pixels[i + 3]; r = lerp(r, target[0], tintF); g = lerp(g, target[1], tintF); b = lerp(b, target[2], tintF); r = constrain(128 + (r - 128) * (1 + satBoost), 0, 255); g = constrain(128 + (g - 128) * (1 + satBoost), 0, 255); b = constrain(128 + (b - 128) * (1 + satBoost), 0, 255); baseTile.pixels[i] = r; baseTile.pixels[i + 1] = g; baseTile.pixels[i + 2] = b; baseTile.pixels[i + 3] = a; } baseTile.updatePixels(); } const tileX = (w - tileSize) / 2; const tileY = (h - tileSize) / 2; imageMode(CORNER); withShadow(this, () => { image(baseTile, tileX, tileY, tileSize, tileSize); }, { blur: 6, offsetX: 3, offsetY: 6, color: 'rgba(0,0,0,0.45)' }); if (pnl === 100 || pnl === -100) { const isPos = (pnl === 100); const addCol = isPos ? POS_ADD_COLOR : NEG_ADD_COLOR; push(); blendMode(ADD); tint(addCol[0], addCol[1], addCol[2], ADD_ALPHA_MAX); image(baseTile, tileX, tileY, tileSize, tileSize); tint(255); blendMode(BLEND); pop(); } return { imageIndex, tileIndex, tileX, tileY }; } function drawSeedText(seed, category, w) { const def = getCategoryDefinition(category); const size = (def.textSize !== undefined ? def.textSize : 15); const strokeW = (def.textStroke !== undefined ? def.textStroke : 0); let fillCol; if (Array.isArray(def.textColor) && def.textColor.length === 3) { fillCol = color(def.textColor[0], def.textColor[1], def.textColor[2]); } else { fillCol = color(0); } push(); textAlign(LEFT, TOP); textFont('Arial Black'); textStyle(BOLD); textSize(size); if (strokeW > 0) { stroke(0); strokeWeight(strokeW); } else { noStroke(); } fill(fillCol); text(seed, 15, 26); pop(); } function drawPnLText(pnl, w) { push(); textFont('Arial Black'); textAlign(CENTER, TOP); textStyle(BOLD); let baseSize = map(abs(pnl), 0, 100, 28, 56); textSize(baseSize); let sw = 5; let display; let fillColor; let strokeColor = color(0); if (pnl > 0) { display = `${pnl}x`; fillColor = color(20, 255, 20); } else if (pnl < 0) { display = `${pnl}%`; fillColor = color(255, 0, 0); } else { display = 'BREAKEVEN'; fillColor = color(200,200,200); } stroke(strokeColor); strokeWeight(sw); fill(0); text(display, w / 2, 11); fill(fillColor); text(display, w / 2, 8); pop(); } function generatePhrase(n) { let available = [...cryptoSlang]; shuffle(available, true); return available.slice(0, n); } function drawPhrase(words, w, h, seed, pnl) { const cat = getTileSeedCategory(seed, pnl); let wordColorFn = () => { const neon = ['#39FF14', '#FF073A', '#0FF0FC', '#FF6EC7', '#FFD700']; return random(neon); }; if (pnl === 100) wordColorFn = () => '#39FF14'; else if (pnl === -100) wordColorFn = () => '#FF073A'; else if (pnl === 0) wordColorFn = () => '#CCCCCC'; else if (cat === 'gold') wordColorFn = () => '#FFD700'; else if (cat === 'silver') wordColorFn = () => '#FFFFFF'; push(); textSize(27); textStyle(BOLD); textFont('Trebuchet MS'); textAlign(LEFT, CENTER); const margin = 20; const maxWidth = w - 2 * margin; const lineHeight = 23; const spacing = lineHeight * 1.15; const lines = [[]]; let currentWidth = 0; for (let word of words) { const tw = textWidth(word + ' '); if (currentWidth + tw > maxWidth && lines[lines.length-1].length) { lines.push([]); currentWidth = 0; } lines[lines.length-1].push({ word, width: tw }); currentWidth += tw; } const regionTop = h - 115; const regionBottom = h; const regionCenter = (regionTop + regionBottom) / 2; const blockHeight = (lines.length - 1) * spacing; let baseY = regionCenter - blockHeight / 2; const threeLineShift = 12; if (lines.length === 3) { baseY -= threeLineShift; } for (let i = 0; i < lines.length; i++) { const line = lines[i]; const lineW = line.reduce((sum,wObj) => sum + wObj.width, 0); let x = (w - lineW) / 2; let y = baseY + i * spacing; for (let { word, width } of line) { stroke(0); strokeWeight(4); fill(0); text(word, x, y + 29); fill(wordColorFn()); text(word, x, y+27); x += width; } } pop(); } function applyTileSeed(seed) { const saltedSeed = seed * 1151 + 7919; randomSeed(saltedSeed); noiseSeed(saltedSeed); } function mulberry32(a) { return function() { var t = a += 0x6D2B79F5; t = Math.imul(t ^ (t >>> 15), t | 1); t ^= t + Math.imul(t ^ (t >>> 7), t | 61); return ((t ^ (t >>> 14)) >>> 0) / 4294967296; }; } function drawGlasses(tileSeed, imageIndex, tileIndex, tileX, tileY) { const eye = eyePositions[imageIndex]?.[tileIndex]; if (!eye) return; const [x1, y1] = eye.eye1.map(Number); const [x2, y2] = eye.eye2.map(Number); if (![x1,y1,x2,y2].every(Number.isFinite)) return; const SINGLE_GLASS_OFFSETS = { x: -15, y: -10 }; const singleEye = (x1 === x2 && y1 === y2); const offX = singleEye ? SINGLE_GLASS_OFFSETS.x : 0; const offY = singleEye ? SINGLE_GLASS_OFFSETS.y : 0; const angle = singleEye ? 0 : atan2(y2 - y1, x2 - x1); const relX1 = map(x1, 0, tileSize, 0, tileSize); const relY1 = map(y1, 0, tileSize, 0, tileSize); const relX2 = map(x2, 0, tileSize, 0, tileSize); const relY2 = map(y2, 0, tileSize, 0, tileSize); const rng = mulberry32(tileSeed + 31415), p = rng(); let row; if (p < 0.07) row = 0; else if (p < 0.38) row = 1; else if (p < 0.69) row = 2; else row = 3; const col = 1; const accImg = accessoriesImg.get(col*tileSize, row*tileSize, tileSize, tileSize); const eyeDist = dist(relX1, relY1, relX2, relY2); const norm = constrain((eyeDist - 20) / dist(0,0,tileSize,tileSize), 0,1); const renderSize = lerp(120, 450, norm); if (singleEye) { push(); imageMode(CENTER); translate(tileX + relX1 + offX, tileY + relY1 + offY); rotate(angle); withShadow(this, () => { image(accImg, 0, 0, renderSize, renderSize); }, { blur: 4, offsetX: 2, offsetY: 4, color: 'rgba(0,0,0,0.40)' }); pop(); } else { const midX = tileX + (relX1 + relX2) * 0.5 + offX; const midY = tileY + (relY1 + relY2) * 0.5 + offY + 50; push(); imageMode(CENTER); translate(midX, midY); rotate(angle); withShadow(this, () => { image(accImg, 0, -renderSize/2, renderSize, renderSize); }, { blur: 4, offsetX: 2, offsetY: 2, color: 'rgba(0,0,0,0.40)' }); pop(); } } function drawHats(tileSeed, imageIndex, tileIndex, tileX, tileY) { const t = tileSize; if (!headpos[imageIndex] || !headpos[imageIndex][tileIndex]) return; if (!eyePositions[imageIndex] || !eyePositions[imageIndex][tileIndex]) return; const drawRng = mulberry32(tileSeed + 10007); if (drawRng() > 0.08) return; const eye = eyePositions[imageIndex][tileIndex]; const [x1, y1] = eye.eye1; const [x2, y2] = eye.eye2; const relX1 = map(x1, 0, t, 0, t); const relY1 = map(y1, 0, t, 0, t); const relX2 = map(x2, 0, t, 0, t); const relY2 = map(y2, 0, t, 0, t); const eyeDist = dist(relX1, relY1, relX2, relY2); const minEyeDist = 20; const maxEyeDist = dist(0, 0, t, t); const sizeMin = 200; const sizeMax = 600; const norm = constrain((eyeDist - minEyeDist) / (maxEyeDist - minEyeDist), 0, 1); const renderSize = (eyeDist < minEyeDist) ? sizeMin : lerp(sizeMin, sizeMax, norm); const angle = (eyeDist < minEyeDist) ? 0 : atan2(relY2 - relY1, relX2 - relX1); const row = floor(drawRng() * 4); const col = 0; const sx = col * t; const sy = row * t; const accImg = accessoriesImg.get(sx, sy, t, t); const head = headpos[imageIndex][tileIndex].head; const midX = tileX + head[0]; const midY = tileY + head[1]; const yOffset = 30; push(); translate(midX, midY + yOffset); rotate(angle); withShadow(this, () => { image(accImg, -renderSize / 2, -renderSize, renderSize, renderSize); }, { blur: 4, offsetX: 2, offsetY: 2, color: 'rgba(0,0,0,0.42)' }); pop(); } function drawEyeOverlay(tileSeed, imageIndex, tileIndex, tileX, tileY, chance = 0.08) { const roll = mulberry32(tileSeed + 4242)(); if (roll > chance) return; const imageSet = eyePositions[imageIndex]; if (!imageSet) return; const coord = imageSet[tileIndex]; if (!coord) return; const [ex1, ey1] = [Number(coord.eye1[0]), Number(coord.eye1[1])]; const [ex2, ey2] = [Number(coord.eye2[0]), Number(coord.eye2[1])]; if (![ex1, ey1, ex2, ey2].every(v => Number.isFinite(v))) return; const t = tileSize; const relX1 = map(ex1, 0, t, 0, t); const relY1 = map(ey1, 0, t, 0, t); const relX2 = map(ex2, 0, t, 0, t); const relY2 = map(ey2, 0, t, 0, t); const cat = getTileSeedCategory(tileSeed); let sx, sy; if (cat === 'normal') { const choice = tileSeed % 3; sx = (choice % 2) * t; sy = (choice < 2 ? 0 : t); } else { sx = t; sy = t; } const eyeCrop = eyeOverlayImg.get(sx, sy, t, t); const eyeRng = mulberry32(tileSeed + 4243); const minDeg = -15, maxDeg = 15; for (let [rx, ry] of [[relX1, relY1], [relX2, relY2]]) { const angle = radians(lerp(minDeg, maxDeg, eyeRng())); push(); imageMode(CENTER); translate(tileX + rx, tileY + ry); rotate(angle); withShadow(this, () => { image(eyeCrop, 0, 0, 60, 60); }, { blur: 3, offsetX: 2, offsetY: 4, color: 'rgba(0,0,0,0.45)' }); pop(); } } function withShadow(p5layer, drawFn, { color = SHADOW_CFG.color, blur = SHADOW_CFG.blur, offsetX = SHADOW_CFG.offsetX, offsetY = SHADOW_CFG.offsetY } = {}) { if (!SHADOW_CFG.enabled) { drawFn(); return; } const ctx = p5layer.drawingContext; ctx.save(); ctx.shadowColor = color; ctx.shadowBlur = blur; ctx.shadowOffsetX = offsetX; ctx.shadowOffsetY = offsetY; drawFn(); ctx.restore(); }