FlopMaster

Poker odds assistant

当前为 2025-05-22 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name FlopMaster
  3. // @namespace https://greasyfork.org/en/users/1469540-davrone
  4. // @version 1.0
  5. // @description Poker odds assistant
  6. // @author Davrone
  7. // @match https://www.torn.com/page.php?sid=holdem
  8. // @run-at document-body
  9. // @license MIT
  10. // @grant none
  11. // ==/UserScript==
  12.  
  13. function debugBindErrors(methods) {
  14. for (let method of methods) {
  15. console.log(`Testing method: ${method}`);
  16. try {
  17. if (this[method] === undefined) {
  18. console.error(`Method ${method} is undefined!`);
  19. }
  20. } catch (e) {
  21. console.error(`Error checking method ${method}:`, e);
  22. }
  23. }
  24. }
  25.  
  26. let GM_addStyle = function(s) {
  27. let style = document.createElement("style");
  28. style.type = "text/css";
  29. style.textContent = s;
  30. document.head.appendChild(style);
  31. }
  32.  
  33. class FloatingSuits {
  34. constructor(container, options = {}) {
  35. this.container = container;
  36. this.suits = [];
  37. this.options = {
  38. suitCount: options.suitCount || 30,
  39. minSize: options.minSize || 16,
  40. maxSize: options.maxSize || 35,
  41. minSpeed: options.minSpeed || 20,
  42. maxSpeed: options.maxSpeed || 60,
  43. minSwayAmount: options.minSwayAmount || 10,
  44. maxSwayAmount: options.maxSwayAmount || 40,
  45. minSwayTime: options.minSwayTime || 2,
  46. maxSwayTime: options.maxSwayTime || 6
  47. };
  48.  
  49. this.suitSymbols = ['♠', '♥', '♦', '♣'];
  50. this.suitClasses = ['spades', 'hearts', 'diamonds', 'clubs'];
  51. this.running = false;
  52. this.suitElements = [];
  53.  
  54. this.init();
  55. }
  56.  
  57. init() {
  58.  
  59. this.container.innerHTML = '';
  60. this.suitElements = [];
  61.  
  62. const rect = this.container.getBoundingClientRect();
  63.  
  64. for (let i = 0; i < this.options.suitCount; i++) {
  65. this.createSuit(rect, true);
  66. }
  67. }
  68.  
  69. createSuit(rect, initial = false) {
  70. const suitIndex = Math.floor(Math.random() * this.suitSymbols.length);
  71. const size = Math.floor(Math.random() * (this.options.maxSize - this.options.minSize)) + this.options.minSize;
  72.  
  73. const suitElement = document.createElement('div');
  74. suitElement.className = `floating-suit ${this.suitClasses[suitIndex]}`;
  75. suitElement.textContent = this.suitSymbols[suitIndex];
  76. suitElement.style.fontSize = `${size}px`;
  77.  
  78. if (Math.random() < 0.2) {
  79. suitElement.classList.add('gold');
  80. }
  81.  
  82. const x = Math.random() * (rect.width - size);
  83. const y = initial ? Math.random() * rect.height : -size;
  84.  
  85. suitElement.style.left = `${x}px`;
  86. suitElement.style.top = `${y}px`;
  87.  
  88. this.container.appendChild(suitElement);
  89.  
  90. const speed = this.options.minSpeed + Math.random() * (this.options.maxSpeed - this.options.minSpeed);
  91. const sway = this.options.minSwayAmount + Math.random() * (this.options.maxSwayAmount - this.options.minSwayAmount);
  92. const swayTime = this.options.minSwayTime + Math.random() * (this.options.maxSwayTime - this.options.minSwayTime);
  93. const rotation = Math.random() * 360;
  94. const rotationSpeed = (Math.random() - 0.5) * 2;
  95.  
  96. suitElement.style.transform = `rotate(${rotation}deg)`;
  97.  
  98. this.suitElements.push({
  99. element: suitElement,
  100. x: x,
  101. y: y,
  102. speed: speed,
  103. sway: sway,
  104. swayTime: swayTime,
  105. swayOffset: Math.random() * Math.PI * 2, // Random starting point
  106. rotation: rotation,
  107. rotationSpeed: rotationSpeed,
  108. size: size
  109. });
  110.  
  111. return this.suitElements[this.suitElements.length - 1];
  112. }
  113.  
  114. start() {
  115. if (!this.running) {
  116. this.running = true;
  117. this.lastTime = performance.now();
  118. requestAnimationFrame(this.update.bind(this));
  119. }
  120. }
  121.  
  122. stop() {
  123. this.running = false;
  124. }
  125.  
  126. update(timestamp) {
  127. if (!this.running) return;
  128.  
  129. const deltaTime = (timestamp - this.lastTime) / 1000; // Convert to seconds
  130. this.lastTime = timestamp;
  131.  
  132. const rect = this.container.getBoundingClientRect();
  133.  
  134. for (let i = this.suitElements.length - 1; i >= 0; i--) {
  135. const suit = this.suitElements[i];
  136.  
  137. suit.y += suit.speed * deltaTime;
  138.  
  139. const swayX = Math.sin((timestamp / 1000 / suit.swayTime) + suit.swayOffset) * suit.sway;
  140. suit.x = Math.max(0, Math.min(rect.width - suit.size, suit.x + (swayX * deltaTime)));
  141.  
  142. suit.rotation += suit.rotationSpeed * deltaTime * 20;
  143.  
  144. suit.element.style.transform = `rotate(${suit.rotation}deg)`;
  145. suit.element.style.left = `${suit.x}px`;
  146. suit.element.style.top = `${suit.y}px`;
  147.  
  148. if (suit.y > rect.height) {
  149. suit.element.remove();
  150. this.suitElements.splice(i, 1);
  151.  
  152. this.createSuit(rect);
  153. }
  154. }
  155.  
  156. requestAnimationFrame(this.update.bind(this));
  157. }
  158.  
  159. setDensity(count) {
  160. const rect = this.container.getBoundingClientRect();
  161. const currentCount = this.suitElements.length;
  162.  
  163. if (count > currentCount) {
  164.  
  165. for (let i = 0; i < count - currentCount; i++) {
  166. this.createSuit(rect);
  167. }
  168. } else if (count < currentCount) {
  169.  
  170. const toRemove = currentCount - count;
  171. for (let i = 0; i < toRemove; i++) {
  172. if (this.suitElements.length > 0) {
  173. const index = Math.floor(Math.random() * this.suitElements.length);
  174. this.suitElements[index].element.remove();
  175. this.suitElements.splice(index, 1);
  176. }
  177. }
  178. }
  179.  
  180. this.options.suitCount = count;
  181. }
  182. }
  183.  
  184. class PokerCalculatorModule {
  185. constructor() {
  186. this.debugMode = false;
  187. this.lastProcessTime = 0;
  188. this.processingThrottle = 1500;
  189. this.lastUpdateTime = 0;
  190. this.renderDelayTime = 500;
  191.  
  192. this.upgradesToShow = 10;
  193. this.lastLength = 0;
  194. this.isHandActive = false;
  195. this.opponentStats = new Map();
  196.  
  197. this.preflopStats = {
  198. 'AA': { wins: 85.2, ties: 0.5, total: 85.2 },
  199. 'KK': { wins: 82.4, ties: 0.5, total: 82.4 },
  200. 'QQ': { wins: 80.0, ties: 0.5, total: 80.0 },
  201. 'JJ': { wins: 77.5, ties: 0.5, total: 77.5 },
  202. 'TT': { wins: 75.1, ties: 0.5, total: 75.1 },
  203. '99': { wins: 72.1, ties: 0.5, total: 72.1 },
  204. '88': { wins: 69.1, ties: 0.5, total: 69.1 },
  205. '77': { wins: 66.2, ties: 0.5, total: 66.2 },
  206. '66': { wins: 63.3, ties: 0.5, total: 63.3 },
  207. '55': { wins: 60.3, ties: 0.5, total: 60.3 },
  208. '44': { wins: 57.0, ties: 0.5, total: 57.0 },
  209. '33': { wins: 53.7, ties: 0.5, total: 53.7 },
  210. '22': { wins: 50.3, ties: 0.5, total: 50.3 },
  211.  
  212. 'AKs': { wins: 66.1, ties: 0.9, total: 67.0 },
  213. 'AQs': { wins: 65.3, ties: 0.8, total: 66.1 },
  214. 'AJs': { wins: 64.6, ties: 0.8, total: 65.4 },
  215. 'ATs': { wins: 63.9, ties: 0.8, total: 64.7 },
  216. 'A9s': { wins: 62.3, ties: 0.7, total: 63.0 },
  217. 'A8s': { wins: 61.4, ties: 0.7, total: 62.1 },
  218. 'A7s': { wins: 60.4, ties: 0.7, total: 61.1 },
  219. 'A6s': { wins: 59.3, ties: 0.7, total: 60.0 },
  220. 'A5s': { wins: 59.2, ties: 0.7, total: 59.9 },
  221. 'A4s': { wins: 58.5, ties: 0.7, total: 59.2 },
  222. 'A3s': { wins: 57.8, ties: 0.7, total: 58.5 },
  223. 'A2s': { wins: 57.1, ties: 0.7, total: 57.8 },
  224.  
  225. 'AK': { wins: 64.5, ties: 0.9, total: 65.4 },
  226. 'AQ': { wins: 63.6, ties: 0.9, total: 64.5 },
  227. 'AJ': { wins: 62.7, ties: 0.9, total: 63.6 },
  228. 'AT': { wins: 62.0, ties: 0.9, total: 62.9 },
  229. 'A9': { wins: 60.2, ties: 0.9, total: 61.1 },
  230. 'A8': { wins: 59.2, ties: 0.9, total: 60.1 },
  231. 'A7': { wins: 58.2, ties: 0.9, total: 59.1 },
  232. 'A6': { wins: 57.1, ties: 0.9, total: 58.0 },
  233. 'A5': { wins: 56.8, ties: 0.9, total: 57.7 },
  234. 'A4': { wins: 56.2, ties: 0.9, total: 57.1 },
  235. 'A3': { wins: 55.5, ties: 0.9, total: 56.4 },
  236. 'A2': { wins: 54.7, ties: 0.9, total: 55.6 },
  237.  
  238. 'KQs': { wins: 62.5, ties: 0.9, total: 63.4 },
  239. 'KJs': { wins: 61.7, ties: 0.9, total: 62.6 },
  240. 'KTs': { wins: 60.9, ties: 0.9, total: 61.8 },
  241. 'K9s': { wins: 59.1, ties: 0.9, total: 60.0 },
  242. 'K8s': { wins: 57.6, ties: 0.9, total: 58.5 },
  243. 'K7s': { wins: 56.9, ties: 0.9, total: 57.8 },
  244. 'K6s': { wins: 55.9, ties: 0.9, total: 56.8 },
  245. 'K5s': { wins: 55.1, ties: 0.9, total: 56.0 },
  246. 'K4s': { wins: 54.3, ties: 0.9, total: 55.2 },
  247. 'K3s': { wins: 53.6, ties: 0.9, total: 54.5 },
  248. 'K2s': { wins: 52.9, ties: 0.9, total: 53.8 },
  249.  
  250. 'KQ': { wins: 60.9, ties: 0.9, total: 61.8 },
  251. 'KJ': { wins: 60.0, ties: 0.9, total: 60.9 },
  252. 'KT': { wins: 59.1, ties: 0.9, total: 60.0 },
  253. 'K9': { wins: 57.3, ties: 0.9, total: 58.2 },
  254. 'K8': { wins: 55.9, ties: 0.9, total: 56.8 },
  255. 'K7': { wins: 55.1, ties: 0.9, total: 56.0 },
  256. 'K6': { wins: 54.1, ties: 0.9, total: 55.0 },
  257. 'K5': { wins: 53.3, ties: 0.9, total: 54.2 },
  258. 'K4': { wins: 52.5, ties: 0.9, total: 53.4 },
  259. 'K3': { wins: 51.7, ties: 0.9, total: 52.6 },
  260. 'K2': { wins: 51.0, ties: 0.9, total: 51.9 },
  261.  
  262. 'QJs': { wins: 59.4, ties: 0.9, total: 60.3 },
  263. 'QTs': { wins: 58.6, ties: 0.9, total: 59.5 },
  264. 'Q9s': { wins: 56.7, ties: 0.9, total: 57.6 },
  265. 'Q8s': { wins: 55.3, ties: 0.9, total: 56.2 },
  266. 'Q7s': { wins: 54.2, ties: 0.9, total: 55.1 },
  267. 'Q6s': { wins: 53.4, ties: 0.9, total: 54.3 },
  268. 'Q5s': { wins: 52.6, ties: 0.9, total: 53.5 },
  269. 'Q4s': { wins: 51.7, ties: 0.9, total: 52.6 },
  270. 'Q3s': { wins: 51.0, ties: 0.9, total: 51.9 },
  271. 'Q2s': { wins: 50.3, ties: 0.9, total: 51.2 },
  272.  
  273. 'QJ': { wins: 57.6, ties: 0.9, total: 58.5 },
  274. 'QT': { wins: 56.8, ties: 0.9, total: 57.7 },
  275. 'Q9': { wins: 55.0, ties: 0.9, total: 55.9 },
  276. 'Q8': { wins: 53.5, ties: 0.9, total: 54.4 },
  277. 'Q7': { wins: 52.3, ties: 0.9, total: 53.2 },
  278. 'Q6': { wins: 51.5, ties: 0.9, total: 52.4 },
  279. 'Q5': { wins: 50.7, ties: 0.9, total: 51.6 },
  280. 'Q4': { wins: 49.8, ties: 0.9, total: 50.7 },
  281. 'Q3': { wins: 49.1, ties: 0.9, total: 50.0 },
  282. 'Q2': { wins: 48.4, ties: 0.9, total: 49.3 },
  283.  
  284. 'JTs': { wins: 57.1, ties: 0.9, total: 58.0 },
  285. 'J9s': { wins: 55.1, ties: 0.9, total: 56.0 },
  286. 'J8s': { wins: 53.7, ties: 0.9, total: 54.6 },
  287. 'J7s': { wins: 52.1, ties: 0.9, total: 53.0 },
  288. 'J6s': { wins: 51.0, ties: 0.9, total: 51.9 },
  289. 'J5s': { wins: 50.1, ties: 0.9, total: 51.0 },
  290. 'J4s': { wins: 49.2, ties: 0.9, total: 50.1 },
  291. 'J3s': { wins: 48.5, ties: 0.9, total: 49.4 },
  292. 'J2s': { wins: 47.8, ties: 0.9, total: 48.7 },
  293.  
  294. 'JT': { wins: 55.3, ties: 0.9, total: 56.2 },
  295. 'J9': { wins: 53.2, ties: 0.9, total: 54.1 },
  296. 'J8': { wins: 51.7, ties: 0.9, total: 52.6 },
  297. 'J7': { wins: 50.5, ties: 0.9, total: 51.4 },
  298. 'J6': { wins: 49.2, ties: 0.9, total: 50.1 },
  299. 'J5': { wins: 48.3, ties: 0.9, total: 49.2 },
  300. 'J4': { wins: 47.4, ties: 0.9, total: 48.3 },
  301. 'J3': { wins: 46.7, ties: 0.9, total: 47.6 },
  302. 'J2': { wins: 46.0, ties: 0.9, total: 46.9 },
  303.  
  304. 'T9s': { wins: 53.4, ties: 0.9, total: 54.3 },
  305. 'T8s': { wins: 51.9, ties: 0.9, total: 52.8 },
  306. 'T7s': { wins: 50.5, ties: 0.9, total: 51.4 },
  307. 'T6s': { wins: 49.0, ties: 0.9, total: 49.9 },
  308. 'T5s': { wins: 48.0, ties: 0.9, total: 48.9 },
  309. 'T4s': { wins: 47.1, ties: 0.9, total: 48.0 },
  310. 'T3s': { wins: 46.4, ties: 0.9, total: 47.3 },
  311. 'T2s': { wins: 45.7, ties: 0.9, total: 46.6 },
  312.  
  313. 'T9': { wins: 51.4, ties: 0.9, total: 52.3 },
  314. 'T8': { wins: 49.9, ties: 0.9, total: 50.8 },
  315. 'T7': { wins: 48.6, ties: 0.9, total: 49.5 },
  316. 'T6': { wins: 47.3, ties: 0.9, total: 48.2 },
  317. 'T5': { wins: 46.1, ties: 0.9, total: 47.0 },
  318. 'T4': { wins: 45.2, ties: 0.9, total: 46.1 },
  319. 'T3': { wins: 44.5, ties: 0.9, total: 45.4 },
  320. 'T2': { wins: 43.8, ties: 0.9, total: 44.7 },
  321.  
  322. '98s': { wins: 51.4, ties: 0.9, total: 52.3 },
  323. '97s': { wins: 49.9, ties: 0.9, total: 50.8 },
  324. '96s': { wins: 48.4, ties: 0.9, total: 49.3 },
  325. '95s': { wins: 46.9, ties: 0.9, total: 47.8 },
  326. '94s': { wins: 46.0, ties: 0.9, total: 46.9 },
  327. '93s': { wins: 45.3, ties: 0.9, total: 46.2 },
  328. '92s': { wins: 44.6, ties: 0.9, total: 45.5 },
  329.  
  330. '98': { wins: 48.1, ties: 0.9, total: 49.0 },
  331. '97': { wins: 46.9, ties: 0.9, total: 47.8 },
  332. '96': { wins: 45.5, ties: 0.9, total: 46.4 },
  333. '95': { wins: 44.2, ties: 0.9, total: 45.1 },
  334. '94': { wins: 43.3, ties: 0.9, total: 44.2 },
  335. '93': { wins: 42.6, ties: 0.9, total: 43.5 },
  336. '92': { wins: 41.9, ties: 0.9, total: 42.8 },
  337.  
  338. '87s': { wins: 49.0, ties: 0.9, total: 49.9 },
  339. '86s': { wins: 47.5, ties: 0.9, total: 48.4 },
  340. '85s': { wins: 46.1, ties: 0.9, total: 47.0 },
  341. '84s': { wins: 45.0, ties: 0.9, total: 45.9 },
  342. '83s': { wins: 44.3, ties: 0.9, total: 45.2 },
  343. '82s': { wins: 43.6, ties: 0.9, total: 44.5 },
  344.  
  345. '87': { wins: 46.1, ties: 0.9, total: 47.0 },
  346. '86': { wins: 44.4, ties: 0.9, total: 45.3 },
  347. '85': { wins: 43.2, ties: 0.9, total: 44.1 },
  348. '84': { wins: 42.1, ties: 0.9, total: 43.0 },
  349. '83': { wins: 41.4, ties: 0.9, total: 42.3 },
  350. '82': { wins: 40.7, ties: 0.9, total: 41.6 },
  351.  
  352. '76s': { wins: 46.4, ties: 0.9, total: 47.3 },
  353. '75s': { wins: 44.9, ties: 0.9, total: 45.8 },
  354. '74s': { wins: 43.7, ties: 0.9, total: 44.6 },
  355. '73s': { wins: 43.0, ties: 0.9, total: 43.9 },
  356. '72s': { wins: 42.3, ties: 0.9, total: 43.2 },
  357.  
  358. '76': { wins: 43.6, ties: 0.9, total: 44.5 },
  359. '75': { wins: 41.8, ties: 0.9, total: 42.7 },
  360. '74': { wins: 40.7, ties: 0.9, total: 41.6 },
  361. '73': { wins: 40.0, ties: 0.9, total: 40.9 },
  362. '72': { wins: 39.3, ties: 0.9, total: 40.2 },
  363.  
  364. '65s': { wins: 44.1, ties: 0.9, total: 45.0 },
  365. '64s': { wins: 42.8, ties: 0.9, total: 43.7 },
  366. '63s': { wins: 42.2, ties: 0.9, total: 43.1 },
  367. '62s': { wins: 41.5, ties: 0.9, total: 42.4 },
  368.  
  369. '65': { wins: 41.0, ties: 0.9, total: 41.9 },
  370. '64': { wins: 39.9, ties: 0.9, total: 40.8 },
  371. '63': { wins: 39.2, ties: 0.9, total: 40.1 },
  372. '62': { wins: 38.5, ties: 0.9, total: 39.4 },
  373.  
  374. '54s': { wins: 42.3, ties: 0.9, total: 43.2 },
  375. '53s': { wins: 41.7, ties: 0.9, total: 42.6 },
  376. '52s': { wins: 41.0, ties: 0.9, total: 41.9 },
  377.  
  378. '54': { wins: 39.4, ties: 0.9, total: 40.3 },
  379. '53': { wins: 38.7, ties: 0.9, total: 39.6 },
  380. '52': { wins: 38.0, ties: 0.9, total: 38.9 },
  381.  
  382. '43s': { wins: 40.9, ties: 0.9, total: 41.8 },
  383. '42s': { wins: 40.2, ties: 0.9, total: 41.1 },
  384.  
  385. '43': { wins: 37.7, ties: 0.9, total: 38.6 },
  386. '42': { wins: 37.0, ties: 0.9, total: 37.9 },
  387.  
  388. '32s': { wins: 39.4, ties: 0.9, total: 40.3 },
  389. '32': { wins: 36.3, ties: 0.9, total: 37.2 }
  390. };
  391.  
  392. this.lastBetAmount = 0;
  393. this.lastRecommendation = "";
  394. this.lastPlayerDecision = null;
  395. this.lastCommunityCount = 0;
  396. this.messageBoxObserverStarted = false;
  397.  
  398. this.lastUpdateCall = 0;
  399.  
  400. this.history = {
  401. correctRecommendations: 0,
  402. totalHands: 0,
  403. adjustThresholds() {
  404. if (this.totalHands > 0) {
  405. const successRate = this.correctRecommendations / this.totalHands;
  406. if (successRate < 0.5) {
  407. this.raiseThreshold -= 5;
  408. this.foldThreshold += 5;
  409. } else if (successRate > 0.7) {
  410. this.raiseThreshold += 5;
  411. this.foldThreshold -= 5;
  412. }
  413. }
  414. }
  415. };
  416.  
  417. this.addStyle();
  418.  
  419. try {
  420. const savedOpponents = localStorage.getItem('pokerHelperOpponentStats');
  421. if (savedOpponents) {
  422. this.opponentStats = new Map(JSON.parse(savedOpponents));
  423. }
  424. } catch (e) {
  425. console.error("Failed to load saved data:", e);
  426. }
  427.  
  428. this.update = this.update.bind(this);
  429. this.detectHandEnd = this.detectHandEnd.bind(this);
  430. }
  431.  
  432. update() {
  433. const now = Date.now();
  434. if (this.lastUpdateTime && now - this.lastUpdateTime < 1000) {
  435. setTimeout(this.update.bind(this), 1000);
  436. return;
  437. }
  438. this.lastUpdateTime = now;
  439.  
  440. let allCards = this.getFullDeck();
  441. let knownCards = Array.from(document.querySelectorAll("[class*='flipper___'] > div[class*='front___'] > div")).map(e => {
  442. var card = (e.classList[1] || "null-0").split("_")[0]
  443. .replace("-A", "-14")
  444. .replace("-K", "-13")
  445. .replace("-Q", "-12")
  446. .replace("-J", "-11");
  447. if (card == "cardSize") card = "null-0";
  448. return card;
  449. });
  450.  
  451. let communityCards = knownCards.slice(0, 5);
  452. let communityCardsCount = communityCards.filter(e => !e.includes("null")).length;
  453. let isPreFlop = communityCardsCount === 0;
  454.  
  455. allCards = this.filterDeck(allCards, communityCards.filter(e => !e.includes("null")));
  456.  
  457. if (knownCards.filter(e => !e.includes("null")).length === 0 && communityCardsCount === 0) {
  458. if (this.isHandActive) {
  459. this.detectHandEnd();
  460. this.isHandActive = false;
  461. document.getElementById("pokerCalc-action").textContent = "Waiting for cards...";
  462. document.querySelector("#pokerCalc-myHand tbody").innerHTML = "";
  463. document.querySelector("#pokerCalc-upgrades tbody").innerHTML = "";
  464. document.querySelector("#pokerCalc-oppPossHands tbody").innerHTML = "";
  465. document.querySelector("#pokerCalc-preflop tbody").innerHTML = "";
  466.  
  467. document.getElementById("pokerCalc-preflop").style.display = "none";
  468. document.getElementById("pokerCalc-myHand").style.display = "table";
  469. document.getElementById("pokerCalc-upgrades").style.display = "table";
  470. document.getElementById("pokerCalc-oppPossHands").style.display = "table";
  471. }
  472. setTimeout(this.update.bind(this), 1000);
  473. return;
  474. } else {
  475. this.isHandActive = true;
  476. }
  477.  
  478. if (JSON.stringify(knownCards).length != this.lastLength || communityCardsCount !== this.lastCommunityCount) {
  479. this.lastCommunityCount = communityCardsCount;
  480.  
  481. document.querySelector("#pokerCalc-myHand tbody").innerHTML = "";
  482. document.querySelector("#pokerCalc-upgrades tbody").innerHTML = "";
  483. document.querySelector("#pokerCalc-oppPossHands tbody").innerHTML = "";
  484. document.querySelector("#pokerCalc-preflop tbody").innerHTML = "";
  485.  
  486. if (isPreFlop) {
  487. document.getElementById("pokerCalc-preflop").style.display = "table";
  488. document.getElementById("pokerCalc-myHand").style.display = "none";
  489. document.getElementById("pokerCalc-upgrades").style.display = "none";
  490. document.getElementById("pokerCalc-oppPossHands").style.display = "none";
  491.  
  492. this.processPreFlopStats();
  493. } else {
  494. document.getElementById("pokerCalc-preflop").style.display = "none";
  495. document.getElementById("pokerCalc-myHand").style.display = "table";
  496. document.getElementById("pokerCalc-upgrades").style.display = "table";
  497. document.getElementById("pokerCalc-oppPossHands").style.display = "table";
  498. this.processPostFlopStats(knownCards, communityCards, allCards);
  499. }
  500.  
  501. this.lastLength = JSON.stringify(knownCards).length;
  502. }
  503.  
  504. setTimeout(this.update.bind(this), 1000);
  505. }
  506.  
  507. detectHandEnd() {
  508. this.lastBetAmount = 0;
  509. this.lastRecommendation = "";
  510. }
  511.  
  512. calculatePreFlopPotential(holeCards, handNotation) {
  513. const card1Value = parseInt(holeCards[0].split("-")[1]);
  514. const card2Value = parseInt(holeCards[1].split("-")[1]);
  515. const hasPair = card1Value === card2Value;
  516. const isSuited = handNotation.endsWith('s');
  517.  
  518. let pairChance = hasPair ? 100 : 32;
  519. let twoPairChance = 0;
  520. let tripsChance = hasPair ? 12 : 0;
  521. let fullHouseChance = 0;
  522. let straightChance = 0;
  523. let flushChance = 0;
  524. let quadsChance = hasPair ? 3 : 0;
  525. let straightFlushChance = 0;
  526. let royalFlushChance = 0;
  527.  
  528. if (!hasPair) {
  529. const card1Rank = card1Value;
  530. const card2Rank = card2Value;
  531. const gap = Math.abs(card1Rank - card2Rank);
  532.  
  533. twoPairChance = 4;
  534.  
  535. if (gap <= 4) {
  536. straightChance = 12 - (gap * 2);
  537. }
  538.  
  539. if (isSuited) {
  540. flushChance = 6;
  541.  
  542. if (gap <= 4) {
  543. straightFlushChance = 0.5;
  544.  
  545. if (card1Rank >= 10 && card2Rank >= 10) {
  546. royalFlushChance = 0.2;
  547. }
  548. }
  549. }
  550. }
  551.  
  552. return {
  553. pairChance,
  554. twoPairChance,
  555. tripsChance,
  556. fullHouseChance,
  557. straightChance,
  558. flushChance,
  559. quadsChance,
  560. straightFlushChance,
  561. royalFlushChance
  562. };
  563. }
  564.  
  565. calculateDrawPotential(holeCards, communityCards) {
  566. const allCards = [...holeCards, ...communityCards].filter(c => !c.includes("null"));
  567.  
  568. let pairChance = 0;
  569. let twoPairChance = 0;
  570. let tripsChance = 0;
  571. let fullHouseChance = 0;
  572. let straightChance = 0;
  573. let flushChance = 0;
  574. let quadsChance = 0;
  575. let straightFlushChance = 0;
  576. let royalFlushChance = 0;
  577.  
  578. if (allCards.length < 2) return {
  579. pairChance, twoPairChance, tripsChance, fullHouseChance,
  580. straightChance, flushChance, quadsChance, straightFlushChance, royalFlushChance
  581. };
  582.  
  583. const ranks = allCards.map(card => parseInt(card.split("-")[1]));
  584. const suits = allCards.map(card => card.split("-")[0]);
  585.  
  586. const rankCounts = {};
  587. ranks.forEach(rank => {
  588. rankCounts[rank] = (rankCounts[rank] || 0) + 1;
  589. });
  590.  
  591. const suitCounts = {};
  592. suits.forEach(suit => {
  593. suitCounts[suit] = (suitCounts[suit] || 0) + 1;
  594. });
  595.  
  596. const handObject = this.makeHandObject(allCards);
  597. const hasPair = this.hasPair(allCards, handObject);
  598. const hasTwoPair = this.hasTwoPairs(allCards, handObject);
  599. const hasTrips = this.hasThreeOfAKind(allCards, handObject);
  600. const hasQuads = this.hasFourOfAKind(allCards, handObject);
  601. const hasFullHouse = this.hasFullHouse(allCards, handObject);
  602. const hasStraight = this.hasStraight(allCards, handObject);
  603. const hasFlush = this.hasFlush(allCards, handObject);
  604. const hasStraightFlush = this.hasStraightFlush(allCards, handObject);
  605. const hasRoyalFlush = this.hasRoyalFlush(allCards, handObject);
  606.  
  607. if (hasPair) pairChance = 100;
  608. if (hasTwoPair) twoPairChance = 100;
  609. if (hasTrips) tripsChance = 100;
  610. if (hasQuads) quadsChance = 100;
  611. if (hasFullHouse) fullHouseChance = 100;
  612. if (hasStraight) straightChance = 100;
  613. if (hasFlush) flushChance = 100;
  614. if (hasStraightFlush) straightFlushChance = 100;
  615. if (hasRoyalFlush) royalFlushChance = 100;
  616.  
  617. const communityCount = communityCards.filter(c => !c.includes("null")).length;
  618. const cardsToBeDealt = 5 - communityCount;
  619.  
  620. if (cardsToBeDealt > 0 && !hasRoyalFlush) {
  621. if (!hasPair && !hasTwoPair && !hasTrips) {
  622. const pairOptions = Object.values(rankCounts).filter(count => count === 1).length;
  623. pairChance = Math.min(100, (pairOptions * 3 * cardsToBeDealt / 47) * 100);
  624. }
  625.  
  626. if (hasPair && !hasTwoPair && !hasFullHouse) {
  627. const unpaired = ranks.filter(rank => rankCounts[rank] === 1);
  628. twoPairChance = Math.min(100, (unpaired.length * 3 * cardsToBeDealt / 47) * 100);
  629. }
  630.  
  631. if ((hasPair || hasTwoPair) && !hasTrips) {
  632. const pairRanks = Object.entries(rankCounts)
  633. .filter(([rank, count]) => count === 2)
  634. .map(([rank]) => parseInt(rank));
  635.  
  636. if (pairRanks.length > 0) {
  637. tripsChance = Math.min(100, (pairRanks.length * 2 * cardsToBeDealt / 47) * 100);
  638. }
  639. }
  640.  
  641. if (hasTrips && !hasQuads) {
  642. const tripRanks = Object.entries(rankCounts)
  643. .filter(([rank, count]) => count === 3)
  644. .map(([rank]) => parseInt(rank));
  645.  
  646. if (tripRanks.length > 0) {
  647. quadsChance = Math.min(100, (tripRanks.length * cardsToBeDealt / 47) * 100);
  648. }
  649. }
  650.  
  651. if (hasTrips && !hasFullHouse) {
  652. const singleCards = Object.entries(rankCounts)
  653. .filter(([rank, count]) => count === 1)
  654. .length;
  655.  
  656. fullHouseChance = Math.min(100, (singleCards * 3 * cardsToBeDealt / 47) * 100);
  657. } else if (hasTwoPair && !hasFullHouse) {
  658. fullHouseChance = Math.min(100, (4 * cardsToBeDealt / 47) * 100);
  659. }
  660.  
  661. if (!hasStraight) {
  662. const uniqueRanks = [...new Set(ranks)].sort((a, b) => a - b);
  663.  
  664. if (uniqueRanks.includes(14)) {
  665. uniqueRanks.push(1);
  666. }
  667.  
  668. let outCount = 0;
  669.  
  670. for (let i = 0; i <= uniqueRanks.length - 4; i++) {
  671. if (uniqueRanks[i+3] - uniqueRanks[i] <= 4) {
  672. const gap = uniqueRanks[i+3] - uniqueRanks[i] - 3;
  673. if (gap === 0) {
  674. outCount = Math.max(outCount, 8);
  675. } else if (gap === 1) {
  676. outCount = Math.max(outCount, 4);
  677. }
  678. }
  679. }
  680.  
  681. straightChance = Math.min(100, (outCount * cardsToBeDealt / 47) * 100);
  682. }
  683.  
  684. if (!hasFlush) {
  685. const maxSuitCount = Math.max(...Object.values(suitCounts).map(count => count || 0));
  686.  
  687. if (maxSuitCount >= 4) {
  688. flushChance = Math.min(100, (9 * cardsToBeDealt / 47) * 100);
  689. } else if (maxSuitCount === 3 && communityCount <= 3) {
  690. flushChance = Math.min(100, (cardsToBeDealt / 47) * 100);
  691. }
  692. }
  693.  
  694. if (!hasStraightFlush && flushChance > 0 && straightChance > 0) {
  695. const flushSuit = Object.entries(suitCounts)
  696. .filter(([suit, count]) => count >= 3)
  697. .map(([suit]) => suit);
  698.  
  699. if (flushSuit.length > 0) {
  700. const suitedCards = allCards.filter(card => card.startsWith(flushSuit[0]));
  701. const suitedRanks = suitedCards.map(card => parseInt(card.split("-")[1]));
  702.  
  703. const uniqueRanks = [...new Set(suitedRanks)].sort((a, b) => a - b);
  704.  
  705. for (let i = 0; i <= uniqueRanks.length - 3; i++) {
  706. if (uniqueRanks[i+2] - uniqueRanks[i] <= 4) {
  707. straightFlushChance = Math.min(100, (cardsToBeDealt / 47) * 25);
  708.  
  709. if (uniqueRanks.includes(10) && uniqueRanks.includes(11) &&
  710. uniqueRanks.includes(12) && uniqueRanks.includes(13) &&
  711. uniqueRanks.includes(14)) {
  712. royalFlushChance = 100;
  713. } else if (uniqueRanks.some(r => r >= 10) &&
  714. uniqueRanks.every(r => r <= 14)) {
  715. royalFlushChance = Math.min(100, (cardsToBeDealt / 47) * 5);
  716. }
  717.  
  718. break;
  719. }
  720. }
  721. }
  722. }
  723. }
  724.  
  725. return {
  726. pairChance: Math.min(100, Math.max(0, Math.round(pairChance))),
  727. twoPairChance: Math.min(100, Math.max(0, Math.round(twoPairChance))),
  728. tripsChance: Math.min(100, Math.max(0, Math.round(tripsChance))),
  729. fullHouseChance: Math.min(100, Math.max(0, Math.round(fullHouseChance))),
  730. straightChance: Math.min(100, Math.max(0, Math.round(straightChance))),
  731. flushChance: Math.min(100, Math.max(0, Math.round(flushChance))),
  732. quadsChance: Math.min(100, Math.max(0, Math.round(quadsChance))),
  733. straightFlushChance: Math.min(100, Math.max(0, Math.round(straightFlushChance))),
  734. royalFlushChance: Math.min(100, Math.max(0, Math.round(royalFlushChance)))
  735. };
  736. }
  737.  
  738. processPreFlopStats() {
  739. let playerNodes = document.querySelectorAll("[class*='playerMeGateway___']");
  740. if (!playerNodes || playerNodes.length === 0) return;
  741.  
  742. let holeCards = Array.from(playerNodes[0].querySelectorAll("div[class*='front___'] > div")).map(e => {
  743. var card = (e.classList[1] || "null-0").split("_")[0]
  744. .replace("-A", "-14")
  745. .replace("-K", "-13")
  746. .replace("-Q", "-12")
  747. .replace("-J", "-11");
  748. if (card == "cardSize") card = "null-0";
  749. return card;
  750. }).filter(c => !c.includes("null"));
  751.  
  752. if (holeCards.length !== 2) return;
  753.  
  754. const card1 = this.convertToNotation(holeCards[0]);
  755. const card2 = this.convertToNotation(holeCards[1]);
  756.  
  757. if (!card1 || !card2) return;
  758.  
  759. const card1Value = parseInt(holeCards[0].split("-")[1]);
  760. const card2Value = parseInt(holeCards[1].split("-")[1]);
  761. const hasPair = card1Value === card2Value;
  762.  
  763. const suited = holeCards[0].split("-")[0] === holeCards[1].split("-")[0];
  764.  
  765. let handNotation;
  766. if (card1 === card2) {
  767. handNotation = card1 + card1;
  768. } else {
  769. const sortedCards = [card1, card2].sort((a, b) => {
  770. const values = { 'A': 14, 'K': 13, 'Q': 12, 'J': 11, 'T': 10 };
  771. const valA = values[a] || parseInt(a);
  772. const valB = values[b] || parseInt(b);
  773. return valB - valA;
  774. });
  775.  
  776. handNotation = sortedCards[0] + sortedCards[1];
  777. if (suited) handNotation += 's';
  778. }
  779.  
  780. const winEquity = this.calculatePreflopEquity(handNotation);
  781.  
  782. const drawPotentials = this.calculatePreFlopPotential(holeCards, handNotation);
  783.  
  784. this.updateProbabilityMeter(winEquity, drawPotentials);
  785.  
  786. const handStats = this.preflopStats[handNotation];
  787.  
  788. if (handStats) {
  789. const recommendation = this.getPreFlopRecommendation(handStats.total);
  790. const actionButton = document.getElementById("pokerCalc-action");
  791. actionButton.textContent = recommendation;
  792.  
  793. actionButton.classList.remove("action-raise", "action-call", "action-fold");
  794.  
  795. if (recommendation.includes("Raise")) {
  796. actionButton.classList.add("action-raise");
  797. } else if (recommendation.includes("Call")) {
  798. actionButton.classList.add("action-call");
  799. } else if (recommendation.includes("Fold")) {
  800. actionButton.classList.add("action-fold");
  801. }
  802.  
  803. this.lastRecommendation = recommendation;
  804.  
  805. let statsHTML = `<tr>
  806. <td>${handNotation}</td>
  807. <td>${handStats.wins.toFixed(2)}%</td>
  808. <td>${handStats.ties.toFixed(2)}%</td>
  809. <td>${handStats.total.toFixed(2)}%</td>
  810. <td>${this.getPreflopHandTier(handStats.total)}</td>
  811. </tr>`;
  812.  
  813. document.querySelector("#pokerCalc-preflop tbody").innerHTML = statsHTML;
  814. } else {
  815. this.estimateHandStats(handNotation);
  816. }
  817. }
  818.  
  819. processPostFlopStats(knownCards, communityCards, allCards) {
  820. let playerNodes = document.querySelectorAll("[class*='playerMeGateway___']");
  821. playerNodes.forEach(player => {
  822. let myCards = Array.from(player.querySelectorAll("div[class*='front___'] > div")).map(e => {
  823. var card = (e.classList[1] || "null-0").split("_")[0]
  824. .replace("-A", "-14")
  825. .replace("-K", "-13")
  826. .replace("-Q", "-12")
  827. .replace("-J", "-11");
  828. if (card == "cardSize") card = "null-0";
  829. return card;
  830. });
  831.  
  832. let myHand = this.getHandScore(communityCards.concat(myCards));
  833.  
  834. if (myHand.score > 0) {
  835. const drawPotentials = this.calculateDrawPotential(myCards, communityCards);
  836.  
  837. let myRank = this.calculateHandRank(myHand, communityCards, allCards);
  838.  
  839. const potSize = parseInt(document.querySelector(".pot-display")?.textContent || 0);
  840. const betToCall = parseInt(document.querySelector(".bet-to-call")?.textContent || 0);
  841. const potOdds = betToCall / (potSize + betToCall);
  842. const recommendation = this.getRecommendation(myRank.topNumber, potOdds);
  843.  
  844. const actionButton = document.getElementById("pokerCalc-action");
  845. actionButton.textContent = recommendation;
  846.  
  847. actionButton.classList.remove("action-raise", "action-call", "action-fold");
  848.  
  849. if (recommendation.includes("Raise") || recommendation.includes("Bet")) {
  850. actionButton.classList.add("action-raise");
  851. } else if (recommendation.includes("Call") || recommendation.includes("Check")) {
  852. actionButton.classList.add("action-call");
  853. } else if (recommendation.includes("Fold")) {
  854. actionButton.classList.add("action-fold");
  855. }
  856.  
  857. this.lastRecommendation = recommendation;
  858.  
  859. document.querySelector("#pokerCalc-myHand tbody").innerHTML += `<tr><td>Me</td><td>${myHand.description}</td><td>${myRank.rank}</td><td>${myRank.top}</td></tr>`;
  860.  
  861. let myUpgrades = {};
  862. let bestOppHands = {};
  863. let additionalCards = [];
  864. let additionalOppCards = [];
  865.  
  866. if (communityCards.filter(e => !e.includes("null")).length == 3) {
  867. for (let a of allCards) {
  868. for (let b of allCards) {
  869. if (a > b) additionalCards.push([a, b]);
  870. }
  871. }
  872. } else if (communityCards.filter(e => !e.includes("null")).length == 4) {
  873. for (let a of allCards) additionalCards.push([a]);
  874. } else if (communityCards.filter(e => !e.includes("null")).length == 5) {
  875. for (let a of allCards) {
  876. for (let b of allCards) {
  877. if (a > b) additionalOppCards.push([a, b]);
  878. }
  879. }
  880. }
  881.  
  882. for (let cards of additionalCards) {
  883. let thisHand = this.getHandScore(communityCards.concat(cards).concat(myCards));
  884. if (thisHand.score > myHand.score) {
  885. let type = thisHand.description.split(":")[0];
  886. if (thisHand.description.includes("Four of a kind") || thisHand.description.includes("Three of a kind") || thisHand.description.includes("Pair")) {
  887. type += ": " + thisHand.description.split("</span>")[1].split("<span")[0].trim() + "s";
  888. } else if (thisHand.description.includes("Full house")) {
  889. type += ": " + thisHand.description.split("</span>")[1].split("<span")[0].trim() + "s full of " + thisHand.description.split("</span>").reverse()[0].split("</td>")[0] + "s";
  890. } else if (thisHand.description.includes("Straight")) {
  891. type += ": " + thisHand.description.split("</span>")[1].split("<span")[0].trim() + "-high";
  892. } else if (thisHand.description.includes("Two pairs")) {
  893. type += ": " + thisHand.description.split("</span>")[1].split("<span")[0].trim() + "s and " + thisHand.description.split("</span>")[3].split("<span")[0].trim() + "s";
  894. }
  895.  
  896. if (!myUpgrades.hasOwnProperty(type)) {
  897. myUpgrades[type] = { hand: thisHand, type: type, cards: cards, score: thisHand.score, duplicates: 0, chance: 0 };
  898. }
  899. myUpgrades[type].description = thisHand.description;
  900. myUpgrades[type].duplicates++;
  901. }
  902. }
  903.  
  904. let topUpgrades = Object.values(myUpgrades).map(e => {
  905. const chance = (e.duplicates / additionalCards.length) * 100;
  906.  
  907. const newCommunity = communityCards.concat(e.cards);
  908. const remainingDeck = this.filterDeck(allCards, [...e.cards, ...myCards]);
  909. const handRank = this.calculateHandRank(e.hand, newCommunity, remainingDeck);
  910.  
  911. return {
  912. ...e,
  913. chance: chance,
  914. rank: handRank.rank || "N/A",
  915. top: handRank.top || "N/A"
  916. };
  917. });
  918.  
  919. let aggregatedChances = {
  920. pairChance: 0,
  921. twoPairChance: 0,
  922. tripsChance: 0,
  923. fullHouseChance: 0,
  924. straightChance: 0,
  925. flushChance: 0,
  926. quadsChance: 0,
  927. straightFlushChance: 0,
  928. royalFlushChance: 0
  929. };
  930.  
  931. topUpgrades.forEach(upgrade => {
  932. const type = upgrade.type.split(':')[0].trim();
  933.  
  934. if (type.includes('Pair') && !type.includes('Two')) {
  935. aggregatedChances.pairChance += upgrade.chance;
  936. } else if (type.includes('Two pairs')) {
  937. aggregatedChances.twoPairChance += upgrade.chance;
  938. } else if (type.includes('Three of a kind')) {
  939. aggregatedChances.tripsChance += upgrade.chance;
  940. } else if (type.includes('Full house')) {
  941. aggregatedChances.fullHouseChance += upgrade.chance;
  942. } else if (type.includes('Four of a kind')) {
  943. aggregatedChances.quadsChance += upgrade.chance;
  944. } else if (type.includes('Straight') && !type.includes('flush')) {
  945. aggregatedChances.straightChance += upgrade.chance;
  946. } else if (type.includes('Flush') && !type.includes('Straight') && !type.includes('Royal')) {
  947. aggregatedChances.flushChance += upgrade.chance;
  948. } else if (type.includes('Straight flush') && !type.includes('Royal')) {
  949. aggregatedChances.straightFlushChance += upgrade.chance;
  950. } else if (type.includes('Royal flush')) {
  951. aggregatedChances.royalFlushChance += upgrade.chance;
  952. }
  953. });
  954.  
  955. if (myHand.description.includes('Pair') && !myHand.description.includes('Two')) {
  956. aggregatedChances.pairChance = 100;
  957. } else if (myHand.description.includes('Two pairs')) {
  958. aggregatedChances.pairChance = 100;
  959. aggregatedChances.twoPairChance = 100;
  960. } else if (myHand.description.includes('Three of a kind')) {
  961. aggregatedChances.pairChance = 100;
  962. aggregatedChances.tripsChance = 100;
  963. } else if (myHand.description.includes('Straight') && !myHand.description.includes('flush')) {
  964. aggregatedChances.straightChance = 100;
  965. } else if (myHand.description.includes('Flush') && !myHand.description.includes('Straight') && !myHand.description.includes('Royal')) {
  966. aggregatedChances.flushChance = 100;
  967. } else if (myHand.description.includes('Full house')) {
  968. aggregatedChances.pairChance = 100;
  969. aggregatedChances.twoPairChance = 100;
  970. aggregatedChances.tripsChance = 100;
  971. aggregatedChances.fullHouseChance = 100;
  972. } else if (myHand.description.includes('Four of a kind')) {
  973. aggregatedChances.pairChance = 100;
  974. aggregatedChances.tripsChance = 100;
  975. aggregatedChances.quadsChance = 100;
  976. } else if (myHand.description.includes('Straight flush') && !myHand.description.includes('Royal')) {
  977. aggregatedChances.straightChance = 100;
  978. aggregatedChances.flushChance = 100;
  979. aggregatedChances.straightFlushChance = 100;
  980. } else if (myHand.description.includes('Royal flush')) {
  981. aggregatedChances.straightChance = 100;
  982. aggregatedChances.flushChance = 100;
  983. aggregatedChances.straightFlushChance = 100;
  984. aggregatedChances.royalFlushChance = 100;
  985. }
  986.  
  987. Object.keys(aggregatedChances).forEach(key => {
  988. aggregatedChances[key] = Math.min(100, aggregatedChances[key]);
  989. });
  990.  
  991. this.updateProbabilityMeter(myRank.winProbability, aggregatedChances);
  992.  
  993. let bestUpgradeChance = -1;
  994. let bestUpgradeIndex = -1;
  995.  
  996. for (let i = 0; i < topUpgrades.length; i++) {
  997. if (topUpgrades[i].chance > bestUpgradeChance) {
  998. bestUpgradeChance = topUpgrades[i].chance;
  999. bestUpgradeIndex = i;
  1000. }
  1001. }
  1002.  
  1003. let upgradeString = "";
  1004. for (let i = 0; i < topUpgrades.length; i++) {
  1005. const upgrade = topUpgrades[i];
  1006. const isBestHand = i === bestUpgradeIndex;
  1007. upgradeString += `<tr class="${isBestHand ? 'best-hand' : ''}">`;
  1008. upgradeString += `<td>${upgrade.chance.toFixed(2)}%</td><td>${upgrade.type}</td><td>${upgrade.rank}</td><td>${upgrade.top}</td>`;
  1009. upgradeString += "</tr>";
  1010. }
  1011. document.querySelector("#pokerCalc-upgrades tbody").innerHTML = upgradeString;
  1012.  
  1013. for (let cards of additionalOppCards) {
  1014. let oppPossHand = this.getHandScore(communityCards.concat(cards));
  1015. let type = oppPossHand.description.split(":")[0];
  1016. if (oppPossHand.description.includes("Four of a kind") || oppPossHand.description.includes("Three of a kind") || oppPossHand.description.includes("Pair")) {
  1017. type += ": " + oppPossHand.description.split("</span>")[1].split("<span")[0].trim() + "s";
  1018. } else if (oppPossHand.description.includes("Full house")) {
  1019. type += ": " + oppPossHand.description.split("</span>")[1].split("<span")[0].trim() + "s full of " + oppPossHand.description.split("</span>").reverse()[0].split("</td>")[0] + "s";
  1020. } else if (oppPossHand.description.includes("Straight")) {
  1021. type += ": " + oppPossHand.description.split("</span>")[1].split("<span")[0].trim() + "-high";
  1022. } else if (oppPossHand.description.includes("Two pairs")) {
  1023. type += ": " + oppPossHand.description.split("</span>")[1].split("<span")[0].trim() + "s and " + oppPossHand.description.split("</span>")[3].split("<span")[0].trim() + "s";
  1024. }
  1025.  
  1026. if (!bestOppHands.hasOwnProperty(type)) {
  1027. bestOppHands[type] = { hand: oppPossHand, type: type, cards: cards, score: oppPossHand.score, duplicates: 0, chance: 0 };
  1028. }
  1029. bestOppHands[type].description = oppPossHand.description;
  1030. bestOppHands[type].duplicates++;
  1031. }
  1032.  
  1033. let topOppHands = Object.values(bestOppHands);
  1034. topOppHands.forEach(e => {
  1035. e.chance = (e.duplicates / additionalOppCards.length) * 100;
  1036. });
  1037. topOppHands = topOppHands
  1038. .sort((a, b) => b.score - a.score)
  1039. .slice(0, this.upgradesToShow);
  1040.  
  1041. topOppHands.forEach(e => {
  1042. const newCommunity = communityCards.concat(e.cards);
  1043. const remainingDeck = this.filterDeck(allCards, e.cards);
  1044. const thisRank = this.calculateHandRank(e.hand, newCommunity, remainingDeck);
  1045. e.rank = thisRank.rank;
  1046. e.top = thisRank.top;
  1047. });
  1048.  
  1049. let bestOppHandScore = -1;
  1050. let bestOppHandIndex = -1;
  1051.  
  1052. for (let i = 0; i < topOppHands.length; i++) {
  1053. if (topOppHands[i].score > bestOppHandScore) {
  1054. bestOppHandScore = topOppHands[i].score;
  1055. bestOppHandIndex = i;
  1056. }
  1057. }
  1058.  
  1059. let oppHandString = "";
  1060. for (let i = 0; i < topOppHands.length; i++) {
  1061. const upgrade = topOppHands[i];
  1062. const isBestHand = i === bestOppHandIndex;
  1063. oppHandString += `<tr class="${isBestHand ? 'best-opp-hand' : ''}">`;
  1064. oppHandString += `<td>${upgrade.chance.toFixed(2)}%</td><td>${upgrade.type}</td><td>${upgrade.rank}</td><td>${upgrade.top}</td>`;
  1065. oppHandString += "</tr>";
  1066. }
  1067. document.querySelector("#pokerCalc-oppPossHands tbody").innerHTML = oppHandString;
  1068. }
  1069. });
  1070. }
  1071.  
  1072. getRecommendation(topNumber, potOdds) {
  1073. const topPercent = topNumber * 100;
  1074. const thresholds = {
  1075. 'pre-flop': { raise: 25, fold: 60 },
  1076. 'flop': { raise: 20, fold: 50 },
  1077. 'turn': { raise: 15, fold: 40 },
  1078. 'river': { raise: 10, fold: 30 }
  1079. };
  1080. const betRound = this.getBettingRound();
  1081. const { raise, fold } = thresholds[betRound] || { raise: 20, fold: 50 };
  1082.  
  1083. if (topPercent <= raise) {
  1084. return "Raise/Bet - Strong Hand";
  1085. } else if (topPercent <= fold) {
  1086. if (potOdds > 0 && potOdds < topNumber) {
  1087. return "Call - Pot Odds Favorable";
  1088. }
  1089. return "Call/Check - Moderate";
  1090. } else {
  1091. return "Fold - Weak Hand";
  1092. }
  1093. }
  1094.  
  1095. getBettingRound() {
  1096. const communityCards = Array.from(document.querySelectorAll("[class*='flipper___'] > div[class*='front___'] > div")).slice(0, 5);
  1097. const numCards = communityCards.filter(e => !e.classList.contains("null-0")).length;
  1098. switch (numCards) {
  1099. case 0: return 'pre-flop';
  1100. case 3: return 'flop';
  1101. case 4: return 'turn';
  1102. case 5: return 'river';
  1103. default: return 'unknown';
  1104. }
  1105. }
  1106.  
  1107. addStatisticsTable() {
  1108. const observer = new MutationObserver((mutations, obs) => {
  1109. const reactRoot = document.querySelector("#react-root");
  1110. if (reactRoot) {
  1111. if (!document.getElementById("pokerCalc-div")) {
  1112. const div = document.createElement("div");
  1113. div.id = "pokerCalc-div";
  1114. div.style.position = "relative";
  1115. div.innerHTML = `
  1116. <div class="suits-container"></div>
  1117. <div class="flopmaster-header">
  1118. <div id="pokerCalc-recommendations">
  1119. <div class="action-chip">
  1120. <div class="chip-inner">
  1121. <span id="pokerCalc-action">Waiting for cards...</span>
  1122. </div>
  1123. <div class="chip-edge"></div>
  1124. </div>
  1125. </div>
  1126.  
  1127. <div id="flopmaster-logo">
  1128. <div class="logo-container">
  1129. <div class="logo-card">
  1130. <span class="logo-text">FlopMaster</span>
  1131. <div class="logo-suits">
  1132. <span class="suit hearts">♥</span>
  1133. <span class="suit spades">♠</span>
  1134. <span class="suit diamonds">♦</span>
  1135. <span class="suit clubs">♣</span>
  1136. </div>
  1137. </div>
  1138. </div>
  1139. </div>
  1140. </div>
  1141.  
  1142. <div class="win-probability-meter">
  1143. <div class="meter-label">Win Probability</div>
  1144. <div class="meter-container">
  1145. <div class="meter-groove"></div>
  1146. <div class="meter-bar" style="width: 0%"></div>
  1147. <div class="meter-value">0%</div>
  1148. </div>
  1149. </div>
  1150.  
  1151. <div class="mini-meters-container">
  1152. <div class="mini-meter pair-meter">
  1153. <div class="mini-meter-label">Pair</div>
  1154. <div class="mini-meter-container">
  1155. <div class="mini-meter-groove"></div>
  1156. <div class="mini-meter-bar" data-type="pair" style="width: 0%"></div>
  1157. <div class="mini-meter-value">0%</div>
  1158. </div>
  1159. </div>
  1160.  
  1161. <div class="mini-meter two-pair-meter">
  1162. <div class="mini-meter-label">Two Pair</div>
  1163. <div class="mini-meter-container">
  1164. <div class="mini-meter-groove"></div>
  1165. <div class="mini-meter-bar" data-type="twoPair" style="width: 0%"></div>
  1166. <div class="mini-meter-value">0%</div>
  1167. </div>
  1168. </div>
  1169.  
  1170. <div class="mini-meter trips-meter">
  1171. <div class="mini-meter-label">Three of a Kind</div>
  1172. <div class="mini-meter-container">
  1173. <div class="mini-meter-groove"></div>
  1174. <div class="mini-meter-bar" data-type="trips" style="width: 0%"></div>
  1175. <div class="mini-meter-value">0%</div>
  1176. </div>
  1177. </div>
  1178.  
  1179. <div class="mini-meter straight-meter">
  1180. <div class="mini-meter-label">Straight</div>
  1181. <div class="mini-meter-container">
  1182. <div class="mini-meter-groove"></div>
  1183. <div class="mini-meter-bar" data-type="straight" style="width: 0%"></div>
  1184. <div class="mini-meter-value">0%</div>
  1185. </div>
  1186. </div>
  1187.  
  1188. <div class="mini-meter flush-meter">
  1189. <div class="mini-meter-label">Flush</div>
  1190. <div class="mini-meter-container">
  1191. <div class="mini-meter-groove"></div>
  1192. <div class="mini-meter-bar" data-type="flush" style="width: 0%"></div>
  1193. <div class="mini-meter-value">0%</div>
  1194. </div>
  1195. </div>
  1196.  
  1197. <div class="mini-meter full-house-meter">
  1198. <div class="mini-meter-label">Full House</div>
  1199. <div class="mini-meter-container">
  1200. <div class="mini-meter-groove"></div>
  1201. <div class="mini-meter-bar" data-type="fullHouse" style="width: 0%"></div>
  1202. <div class="mini-meter-value">0%</div>
  1203. </div>
  1204. </div>
  1205.  
  1206. <div class="mini-meter quads-meter">
  1207. <div class="mini-meter-label">Four of a Kind</div>
  1208. <div class="mini-meter-container">
  1209. <div class="mini-meter-groove"></div>
  1210. <div class="mini-meter-bar" data-type="quads" style="width: 0%"></div>
  1211. <div class="mini-meter-value">0%</div>
  1212. </div>
  1213. </div>
  1214.  
  1215. <div class="mini-meter straight-flush-meter">
  1216. <div class="mini-meter-label">Straight Flush</div>
  1217. <div class="mini-meter-container">
  1218. <div class="mini-meter-groove"></div>
  1219. <div class="mini-meter-bar" data-type="straightFlush" style="width: 0%"></div>
  1220. <div class="mini-meter-value">0%</div>
  1221. </div>
  1222. </div>
  1223.  
  1224. <div class="mini-meter royal-flush-meter">
  1225. <div class="mini-meter-label">Royal Flush</div>
  1226. <div class="mini-meter-container">
  1227. <div class="mini-meter-groove"></div>
  1228. <div class="mini-meter-bar" data-type="royalFlush" style="width: 0%"></div>
  1229. <div class="mini-meter-value">0%</div>
  1230. </div>
  1231. </div>
  1232. </div>
  1233.  
  1234. <table id="pokerCalc-preflop" style="display: none;">
  1235. <caption>Pre-Flop Hand Statistics</caption>
  1236. <thead>
  1237. <tr>
  1238. <th>Hand</th>
  1239. <th>Win %</th>
  1240. <th>Tie %</th>
  1241. <th>Total %</th>
  1242. <th>Tier</th>
  1243. </tr>
  1244. </thead>
  1245. <tbody></tbody>
  1246. </table>
  1247. <table id="pokerCalc-myHand">
  1248. <caption>Your Hand</caption>
  1249. <thead>
  1250. <tr>
  1251. <th>Name</th>
  1252. <th>Hand</th>
  1253. <th>Rank</th>
  1254. <th>Top</th>
  1255. </tr>
  1256. </thead>
  1257. <tbody></tbody>
  1258. </table>
  1259. <table id="pokerCalc-upgrades">
  1260. <caption>Your Potential Hands</caption>
  1261. <thead>
  1262. <tr>
  1263. <th>Chance</th>
  1264. <th>Hand</th>
  1265. <th>Rank</th>
  1266. <th>Top</th>
  1267. </tr>
  1268. </thead>
  1269. <tbody></tbody>
  1270. </table>
  1271. <table id="pokerCalc-oppPossHands">
  1272. <caption>Opponent Potential Hands</caption>
  1273. <thead>
  1274. <tr>
  1275. <th>Chance</th>
  1276. <th>Hand</th>
  1277. <th>Rank</th>
  1278. <th>Top</th>
  1279. </tr>
  1280. </thead>
  1281. <tbody></tbody>
  1282. </table>
  1283. `;
  1284. reactRoot.after(div);
  1285.  
  1286. this.initProbabilityMeter();
  1287.  
  1288. setTimeout(() => {
  1289. const suitsContainer = document.querySelector('.suits-container');
  1290. if (suitsContainer) {
  1291. const floatingSuits = new FloatingSuits(suitsContainer, {
  1292. suitCount: 25,
  1293. minSize: 18,
  1294. maxSize: 42,
  1295. minSpeed: 10,
  1296. maxSpeed: 80
  1297. });
  1298.  
  1299. window.pokerFloatingSuits = floatingSuits;
  1300. floatingSuits.start();
  1301.  
  1302. setTimeout(() => {
  1303.  
  1304. const fps = window.performance?.now ? 60 : 30; // Simplified - assume 60 FPS for modern browsers
  1305. if (fps < 40) {
  1306. floatingSuits.setDensity(20); // Reduce number of suits on lower-end devices
  1307. }
  1308. }, 3000);
  1309. }
  1310. }, 500);
  1311. }
  1312. obs.disconnect();
  1313. this.update();
  1314. }
  1315. });
  1316. observer.observe(document, { childList: true, subtree: true });
  1317. }
  1318.  
  1319. initProbabilityMeter() {
  1320. const updateMeter = (percentage = 0, handStats = null) => {
  1321. const meterBar = document.querySelector('.meter-bar');
  1322. const meterValue = document.querySelector('.meter-value');
  1323.  
  1324. if (meterBar && meterValue) {
  1325. meterBar.style.transition = 'width 0.8s ease-in-out';
  1326. meterBar.style.width = `${percentage}%`;
  1327.  
  1328. meterValue.textContent = `${percentage.toFixed(1)}%`;
  1329.  
  1330. if (percentage >= 70) {
  1331. meterBar.style.backgroundColor = '#4CAF50';
  1332. } else if (percentage >= 40) {
  1333. meterBar.style.backgroundColor = '#FFC107';
  1334. } else {
  1335. meterBar.style.backgroundColor = '#F44336';
  1336. }
  1337.  
  1338. if (handStats) {
  1339. this.updateMiniMeters(handStats);
  1340. }
  1341. }
  1342. };
  1343.  
  1344. this.updateProbabilityMeter = updateMeter;
  1345.  
  1346. updateMeter(0);
  1347. }
  1348.  
  1349. updateMiniMeters(stats = {}) {
  1350. const pairChance = stats.pairChance || 0;
  1351. const twoPairChance = stats.twoPairChance || 0;
  1352. const tripsChance = stats.tripsChance || 0;
  1353. const fullHouseChance = stats.fullHouseChance || 0;
  1354. const straightChance = stats.straightChance || 0;
  1355. const flushChance = stats.flushChance || 0;
  1356. const quadsChance = stats.quadsChance || 0;
  1357. const straightFlushChance = stats.straightFlushChance || 0;
  1358. const royalFlushChance = stats.royalFlushChance || 0;
  1359.  
  1360. const pairBar = document.querySelector('.mini-meter-bar[data-type="pair"]');
  1361. const pairValue = pairBar?.parentNode.querySelector('.mini-meter-value');
  1362. if (pairBar && pairValue) {
  1363. pairBar.style.width = `${pairChance}%`;
  1364. pairValue.textContent = `${pairChance.toFixed(1)}%`;
  1365. }
  1366.  
  1367. const twoPairBar = document.querySelector('.mini-meter-bar[data-type="twoPair"]');
  1368. const twoPairValue = twoPairBar?.parentNode.querySelector('.mini-meter-value');
  1369. if (twoPairBar && twoPairValue) {
  1370. twoPairBar.style.width = `${twoPairChance}%`;
  1371. twoPairValue.textContent = `${twoPairChance.toFixed(1)}%`;
  1372. }
  1373.  
  1374. const tripsBar = document.querySelector('.mini-meter-bar[data-type="trips"]');
  1375. const tripsValue = tripsBar?.parentNode.querySelector('.mini-meter-value');
  1376. if (tripsBar && tripsValue) {
  1377. tripsBar.style.width = `${tripsChance}%`;
  1378. tripsValue.textContent = `${tripsChance.toFixed(1)}%`;
  1379. }
  1380.  
  1381. const quadsBar = document.querySelector('.mini-meter-bar[data-type="quads"]');
  1382. const quadsValue = quadsBar?.parentNode.querySelector('.mini-meter-value');
  1383. if (quadsBar && quadsValue) {
  1384. quadsBar.style.width = `${quadsChance}%`;
  1385. quadsValue.textContent = `${quadsChance.toFixed(1)}%`;
  1386. }
  1387.  
  1388. const fullHouseBar = document.querySelector('.mini-meter-bar[data-type="fullHouse"]');
  1389. const fullHouseValue = fullHouseBar?.parentNode.querySelector('.mini-meter-value');
  1390. if (fullHouseBar && fullHouseValue) {
  1391. fullHouseBar.style.width = `${fullHouseChance}%`;
  1392. fullHouseValue.textContent = `${fullHouseChance.toFixed(1)}%`;
  1393. }
  1394.  
  1395. const straightBar = document.querySelector('.mini-meter-bar[data-type="straight"]');
  1396. const straightValue = straightBar?.parentNode.querySelector('.mini-meter-value');
  1397. if (straightBar && straightValue) {
  1398. straightBar.style.width = `${straightChance}%`;
  1399. straightValue.textContent = `${straightChance.toFixed(1)}%`;
  1400. }
  1401.  
  1402. const flushBar = document.querySelector('.mini-meter-bar[data-type="flush"]');
  1403. const flushValue = flushBar?.parentNode.querySelector('.mini-meter-value');
  1404. if (flushBar && flushValue) {
  1405. flushBar.style.width = `${flushChance}%`;
  1406. flushValue.textContent = `${flushChance.toFixed(1)}%`;
  1407. }
  1408.  
  1409. const straightFlushBar = document.querySelector('.mini-meter-bar[data-type="straightFlush"]');
  1410. const straightFlushValue = straightFlushBar?.parentNode.querySelector('.mini-meter-value');
  1411. if (straightFlushBar && straightFlushValue) {
  1412. straightFlushBar.style.width = `${straightFlushChance}%`;
  1413. straightFlushValue.textContent = `${straightFlushChance.toFixed(1)}%`;
  1414. }
  1415.  
  1416. const royalFlushBar = document.querySelector('.mini-meter-bar[data-type="royalFlush"]');
  1417. const royalFlushValue = royalFlushBar?.parentNode.querySelector('.mini-meter-value');
  1418. if (royalFlushBar && royalFlushValue) {
  1419. royalFlushBar.style.width = `${royalFlushChance}%`;
  1420. royalFlushValue.textContent = `${royalFlushChance.toFixed(1)}%`;
  1421. }
  1422. }
  1423.  
  1424. calculatePreflopEquity(handNotation) {
  1425. if (this.preflopStats[handNotation]) {
  1426. return this.preflopStats[handNotation].total;
  1427. }
  1428.  
  1429. const isPair = handNotation.length === 2;
  1430. const isSuited = handNotation.endsWith('s');
  1431.  
  1432. let card1, card2;
  1433. if (isPair) {
  1434. card1 = card2 = this.getCardRank(handNotation[0]);
  1435. } else {
  1436. card1 = this.getCardRank(handNotation[0]);
  1437. card2 = this.getCardRank(handNotation[1].replace('s', ''));
  1438. }
  1439.  
  1440. let equity = 0;
  1441.  
  1442. if (isPair) {
  1443. equity = 50 + (card1 * 2.5);
  1444. } else {
  1445. let baseEquity = (card1 + card2) * 1.5;
  1446. const isConnected = Math.abs(card1 - card2) <= 2;
  1447.  
  1448. if (isSuited) baseEquity += 5;
  1449. if (isConnected) baseEquity += 3;
  1450.  
  1451. equity = baseEquity;
  1452. }
  1453.  
  1454. return Math.min(Math.max(equity, 30), 85);
  1455. }
  1456.  
  1457. getCardRank(cardValue) {
  1458. const values = { 'A': 14, 'K': 13, 'Q': 12, 'J': 11, 'T': 10 };
  1459. return values[cardValue] || parseInt(cardValue);
  1460. }
  1461.  
  1462. estimateHandStats(handNotation) {
  1463. const winEquity = this.calculatePreflopEquity(handNotation);
  1464.  
  1465. const isPocketPair = handNotation.length === 2 && handNotation[0] === handNotation[1];
  1466. const isSuited = handNotation.endsWith('s');
  1467.  
  1468. let pairChance = isPocketPair ? 100 : 40;
  1469. let twoPairChance = isPocketPair ? 20 : 5;
  1470. let tripsChance = isPocketPair ? 25 : 0;
  1471. let fullHouseChance = isPocketPair ? 5 : 0;
  1472. let straightChance = 0;
  1473. let flushChance = 0;
  1474. let quadsChance = isPocketPair ? 3 : 0;
  1475. let straightFlushChance = 0;
  1476. let royalFlushChance = 0;
  1477.  
  1478. if (!isPocketPair) {
  1479. const card1 = this.getCardRank(handNotation[0]);
  1480. const card2 = this.getCardRank(handNotation[1].replace('s', ''));
  1481. const isConnected = Math.abs(card1 - card2) <= 3;
  1482.  
  1483. if (isConnected) {
  1484. straightChance = 20 - (Math.abs(card1 - card2) * 5);
  1485. }
  1486.  
  1487. if (isSuited) {
  1488. flushChance = 15;
  1489.  
  1490. if (isConnected) {
  1491. straightFlushChance = 1;
  1492.  
  1493. if (card1 >= 10 && card2 >= 10) {
  1494. royalFlushChance = 0.5;
  1495. }
  1496. }
  1497. }
  1498. }
  1499.  
  1500. const recommendation = this.getPreFlopRecommendation(winEquity);
  1501. const actionButton = document.getElementById("pokerCalc-action");
  1502. actionButton.textContent = recommendation;
  1503.  
  1504. actionButton.classList.remove("action-raise", "action-call", "action-fold");
  1505.  
  1506. if (recommendation.includes("Raise")) {
  1507. actionButton.classList.add("action-raise");
  1508. } else if (recommendation.includes("Call")) {
  1509. actionButton.classList.add("action-call");
  1510. } else if (recommendation.includes("Fold")) {
  1511. actionButton.classList.add("action-fold");
  1512. }
  1513.  
  1514. this.lastRecommendation = recommendation;
  1515.  
  1516. let statsHTML = `<tr>
  1517. <td>${handNotation}</td>
  1518. <td>${(winEquity * 0.95).toFixed(2)}%</td>
  1519. <td>${(winEquity * 0.05).toFixed(2)}%</td>
  1520. <td>${winEquity.toFixed(2)}%</td>
  1521. <td>${this.getPreflopHandTier(winEquity)}</td>
  1522. </tr>`;
  1523.  
  1524. document.querySelector("#pokerCalc-preflop tbody").innerHTML = statsHTML;
  1525.  
  1526. this.updateProbabilityMeter(winEquity, {
  1527. pairChance,
  1528. twoPairChance,
  1529. tripsChance,
  1530. fullHouseChance,
  1531. straightChance,
  1532. flushChance,
  1533. quadsChance,
  1534. straightFlushChance,
  1535. royalFlushChance
  1536. });
  1537. }
  1538.  
  1539. convertToNotation(card) {
  1540. if (!card || card === "null-0") return null;
  1541.  
  1542. const value = card.split("-")[1];
  1543. switch (value) {
  1544. case "14": return "A";
  1545. case "13": return "K";
  1546. case "12": return "Q";
  1547. case "11": return "J";
  1548. case "10": return "T";
  1549. default: return value;
  1550. }
  1551. }
  1552.  
  1553. getPreflopHandTier(winPercentage) {
  1554. if (winPercentage >= 75) return "Premium";
  1555. if (winPercentage >= 65) return "Strong";
  1556. if (winPercentage >= 55) return "Playable";
  1557. if (winPercentage >= 45) return "Speculative";
  1558. return "Weak";
  1559. }
  1560.  
  1561. getPreFlopRecommendation(winPercentage) {
  1562. const position = this.getPlayerPosition();
  1563.  
  1564. let raiseThreshold, callThreshold;
  1565.  
  1566. switch (position) {
  1567. case 'early':
  1568. raiseThreshold = 70;
  1569. callThreshold = 60;
  1570. break;
  1571. case 'middle':
  1572. raiseThreshold = 65;
  1573. callThreshold = 55;
  1574. break;
  1575. case 'late':
  1576. raiseThreshold = 60;
  1577. callThreshold = 50;
  1578. break;
  1579. case 'button':
  1580. case 'smallBlind':
  1581. raiseThreshold = 55;
  1582. callThreshold = 45;
  1583. break;
  1584. case 'bigBlind':
  1585. raiseThreshold = 60;
  1586. callThreshold = 40;
  1587. break;
  1588. default:
  1589. raiseThreshold = 65;
  1590. callThreshold = 50;
  1591. }
  1592.  
  1593. if (winPercentage < 30) {
  1594. return "Fold - Weak Hand";
  1595. }
  1596.  
  1597. if (winPercentage >= raiseThreshold) {
  1598. return "Raise - Strong Hand";
  1599. } else if (winPercentage >= callThreshold) {
  1600. return "Call - Moderate Hand";
  1601. } else {
  1602. return "Fold - Weak Hand";
  1603. }
  1604. }
  1605.  
  1606. getPlayerPosition() {
  1607. return 'middle';
  1608. }
  1609.  
  1610. getFullDeck() {
  1611. let result = [];
  1612. for (let suit of ["hearts", "diamonds", "spades", "clubs"]) {
  1613. for (let value of [2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]) {
  1614. result.push(suit + "-" + value);
  1615. }
  1616. }
  1617. return result;
  1618. }
  1619.  
  1620. filterDeck(deck, cards) {
  1621. for (let card of cards) {
  1622. let index = deck.indexOf(card);
  1623. if (index != -1) {
  1624. delete deck[index];
  1625. }
  1626. }
  1627. return deck.filter(e => e != "empty");
  1628. }
  1629.  
  1630. calculateHandRank(myHand, communityCards, allCards) {
  1631. if (!myHand?.score || !Array.isArray(communityCards) || !Array.isArray(allCards) ||
  1632. communityCards.length === 0 || allCards.length === 0) {
  1633. return {
  1634. rank: "N/A",
  1635. top: "N/A",
  1636. topNumber: 0,
  1637. betterHands: 0,
  1638. equalHands: 0,
  1639. worseHands: 0,
  1640. totalHands: 0
  1641. };
  1642. }
  1643.  
  1644. let betterHands = 0;
  1645. let equalHands = 0;
  1646. let worseHands = 0;
  1647. let totalHands = 0;
  1648.  
  1649. const availableCards = allCards.filter(card =>
  1650. card &&
  1651. typeof card === 'string' &&
  1652. !communityCards.includes(card) &&
  1653. !myHand.result.includes(card)
  1654. );
  1655.  
  1656. if (availableCards.length < 2) {
  1657. return {
  1658. rank: "N/A",
  1659. top: "N/A",
  1660. topNumber: 0.5
  1661. };
  1662. }
  1663.  
  1664. for (let i = 0; i < availableCards.length - 1; i++) {
  1665. for (let j = i + 1; j < availableCards.length; j++) {
  1666. const combo = [availableCards[i], availableCards[j]];
  1667. let thisHand = this.getHandScore(communityCards.concat(combo));
  1668.  
  1669. if (thisHand.score > myHand.score) {
  1670. betterHands++;
  1671. } else if (thisHand.score === myHand.score) {
  1672. const tieBreaker = this.compareTiedHands(myHand, thisHand);
  1673. if (tieBreaker > 0) betterHands++;
  1674. else if (tieBreaker < 0) worseHands++;
  1675. else equalHands++;
  1676. } else {
  1677. worseHands++;
  1678. }
  1679. totalHands++;
  1680. }
  1681. }
  1682.  
  1683. if (totalHands === 0) {
  1684. return {
  1685. rank: "N/A",
  1686. top: "N/A",
  1687. topNumber: 0.5
  1688. };
  1689. }
  1690.  
  1691. const trueRank = betterHands + Math.ceil(equalHands / 2);
  1692. const percentile = ((betterHands + equalHands / 2) / totalHands) * 100;
  1693.  
  1694. const winProbability = 100 - percentile;
  1695.  
  1696. return {
  1697. rank: `${trueRank + 1} / ${totalHands}`,
  1698. top: `${percentile.toFixed(1)}%`,
  1699. topNumber: percentile / 100,
  1700. winProbability: winProbability
  1701. };
  1702. }
  1703.  
  1704. compareTiedHands(hand1, hand2) {
  1705. const kickers1 = hand1.result.map(card => parseInt(card.split('-')[1])).sort((a, b) => b - a);
  1706. const kickers2 = hand2.result.map(card => parseInt(card.split('-')[1])).sort((a, b) => b - a);
  1707.  
  1708. for (let i = 0; i < kickers1.length; i++) {
  1709. if (kickers1[i] !== kickers2[i]) {
  1710. return kickers2[i] - kickers1[i];
  1711. }
  1712. }
  1713. return 0;
  1714. }
  1715.  
  1716. prettifyHand(hand) {
  1717. let resultText = "";
  1718. for (let card of hand) {
  1719. if (card != "null-0") {
  1720. resultText += " " + card
  1721. .replace("diamonds", "<span class='diamonds'>♦</span>")
  1722. .replace("spades", "<span class='spades'>♠</span>")
  1723. .replace("hearts", "<span class='hearts'>♥</span>")
  1724. .replace("clubs", "<span class='clubs'>♣</span>")
  1725. .replace("-14", "-A")
  1726. .replace("-13", "K")
  1727. .replace("-12", "Q")
  1728. .replace("-11", "J")
  1729. .replace("-", "");
  1730. }
  1731. }
  1732. return resultText;
  1733. }
  1734.  
  1735. getHandScore(hand) {
  1736. hand = hand.filter(e => !e.includes("null"));
  1737.  
  1738. if (hand.length < 5) { return { description: "", score: 0 }; }
  1739.  
  1740. let resultString = "";
  1741. let resultText = "";
  1742. let handResult;
  1743. let handObject = this.makeHandObject(hand);
  1744.  
  1745. if (handResult = this.hasFourOfAKind(hand, handObject)) {
  1746. resultString += "7";
  1747. resultText += "Four of a kind:";
  1748. } else if (handResult = this.hasFullHouse(hand, handObject)) {
  1749. resultString += "6";
  1750. resultText += "Full house:";
  1751. } else if (handResult = this.hasFlush(hand, handObject)) {
  1752. let isRoyal = this.hasRoyalFlush(hand, handObject);
  1753.  
  1754. if (isRoyal) {
  1755. handResult = isRoyal;
  1756. resultString += "9";
  1757. resultText += "Royal flush:";
  1758. } else {
  1759. let isStraight = this.hasStraightFlush(hand, handObject);
  1760.  
  1761. if (isStraight) {
  1762. handResult = isStraight;
  1763. resultString += "8";
  1764. resultText += "Straight flush:";
  1765. } else {
  1766. resultString += "5";
  1767. resultText += "Flush:";
  1768. }
  1769. }
  1770. } else if (handResult = this.hasStraight(hand, handObject)) {
  1771. resultString += "4";
  1772. resultText += "Straight:";
  1773. } else if (handResult = this.hasThreeOfAKind(hand, handObject)) {
  1774. resultString += "3";
  1775. resultText += "Three of a kind:";
  1776. } else if (handResult = this.hasTwoPairs(hand, handObject)) {
  1777. resultString += "2";
  1778. resultText += "Two pairs:";
  1779. } else if (handResult = this.hasPair(hand, handObject)) {
  1780. resultString += "1";
  1781. resultText += "Pair:";
  1782. } else {
  1783. resultString += "0";
  1784. resultText += "High card:";
  1785. handResult = hand.slice(0, 5);
  1786. }
  1787.  
  1788. for (let card of handResult) {
  1789. resultString += parseInt(card.split("-")[1]).toString(16);
  1790. }
  1791.  
  1792. resultText += this.prettifyHand(handResult);
  1793.  
  1794. return { description: resultText, result: handResult, score: parseInt(resultString, 16) };
  1795. }
  1796.  
  1797. makeHandObject(hand) {
  1798. let resultMap = { cards: hand, suits: {}, values: {} };
  1799.  
  1800. hand.sort((a, b) => parseInt(b.split("-")[1]) - parseInt(a.split("-")[1])).filter(e => e != "null-0").forEach(e => {
  1801. let suit = e.split("-")[0];
  1802. let value = e.split("-")[1];
  1803.  
  1804. if (!resultMap.suits.hasOwnProperty(suit)) {
  1805. resultMap.suits[suit] = [];
  1806. }
  1807.  
  1808. if (!resultMap.values.hasOwnProperty(value)) {
  1809. resultMap.values[value] = [];
  1810. }
  1811.  
  1812. resultMap.suits[suit].push(e);
  1813. resultMap.values[value].push(e);
  1814. });
  1815.  
  1816. return resultMap;
  1817. }
  1818.  
  1819. hasRoyalFlush(hand, handObject) {
  1820. for (let suit in handObject.suits) {
  1821. const suitCards = handObject.suits[suit];
  1822. if (suitCards.length >= 5) {
  1823. const values = new Set(suitCards.map(card => parseInt(card.split("-")[1])));
  1824. if ([10, 11, 12, 13, 14].every(value => values.has(value))) {
  1825. return suitCards
  1826. .filter(card => parseInt(card.split("-")[1]) >= 10)
  1827. .sort((a, b) => parseInt(b.split("-")[1]) - parseInt(a.split("-")[1]))
  1828. .slice(0, 5);
  1829. }
  1830. }
  1831. }
  1832. return null;
  1833. }
  1834.  
  1835. hasStraightFlush(hand, handObject) {
  1836. for (let suit in handObject.suits) {
  1837. const suitCards = handObject.suits[suit];
  1838. if (suitCards.length >= 5) {
  1839. const straightFlush = this.hasStraight(suitCards, this.makeHandObject(suitCards));
  1840. if (straightFlush) {
  1841. return straightFlush;
  1842. }
  1843. }
  1844. }
  1845. return null;
  1846. }
  1847.  
  1848. hasFourOfAKind(hand, handObject) {
  1849. let quadruplets = Object.values(handObject.values).filter(e => e.length == 4);
  1850.  
  1851. if (quadruplets.length > 0) {
  1852. delete hand[hand.indexOf(quadruplets[0][0])];
  1853. delete hand[hand.indexOf(quadruplets[0][1])];
  1854. delete hand[hand.indexOf(quadruplets[0][2])];
  1855. delete hand[hand.indexOf(quadruplets[0][3])];
  1856.  
  1857. hand = hand.filter(e => e != "empty");
  1858.  
  1859. return quadruplets[0].concat(hand).slice(0, 5);
  1860. }
  1861. return null;
  1862. }
  1863.  
  1864. hasFullHouse(hand, handObject) {
  1865. let triplets = Object.values(handObject.values)
  1866. .filter(e => e.length === 3)
  1867. .sort((a, b) => parseInt(b[0].split("-")[1]) - parseInt(a[0].split("-")[1]));
  1868.  
  1869. if (triplets.length === 0) {
  1870. return null;
  1871. }
  1872.  
  1873. for (let threeOfKind of triplets) {
  1874. const threeValue = parseInt(threeOfKind[0].split("-")[1]);
  1875.  
  1876. const pairs = Object.values(handObject.values)
  1877. .filter(e => e.length >= 2 && parseInt(e[0].split("-")[1]) !== threeValue)
  1878. .sort((a, b) => parseInt(b[0].split("-")[1]) - parseInt(a[0].split("-")[1]));
  1879.  
  1880. if (pairs.length > 0) {
  1881. return threeOfKind.slice(0, 3).concat(pairs[0].slice(0, 2));
  1882. }
  1883. }
  1884. return null;
  1885. }
  1886.  
  1887. hasFlush(hand, handObject) {
  1888. for (let suit in handObject.suits) {
  1889. if (handObject.suits[suit].length >= 5) {
  1890. return handObject.suits[suit]
  1891. .sort((a, b) => parseInt(b.split("-")[1]) - parseInt(a.split("-")[1]))
  1892. .slice(0, 5);
  1893. }
  1894. }
  1895. return null;
  1896. }
  1897.  
  1898. hasStraight(hand, handObject) {
  1899. const valueMap = new Map();
  1900. hand.forEach(card => {
  1901. const value = parseInt(card.split("-")[1]);
  1902. if (!valueMap.has(value) || parseInt(valueMap.get(value).split("-")[1]) < value) {
  1903. valueMap.set(value, card);
  1904. }
  1905. });
  1906.  
  1907. const uniqueValues = Array.from(valueMap.keys()).sort((a, b) => b - a);
  1908.  
  1909. for (let i = 0; i <= uniqueValues.length - 5; i++) {
  1910. const possibleStraight = uniqueValues.slice(i, i + 5);
  1911. if (possibleStraight[0] - possibleStraight[4] === 4) {
  1912. return possibleStraight.map(value => valueMap.get(value));
  1913. }
  1914. }
  1915.  
  1916. if (uniqueValues.includes(14) &&
  1917. uniqueValues.includes(2) &&
  1918. uniqueValues.includes(3) &&
  1919. uniqueValues.includes(4) &&
  1920. uniqueValues.includes(5)) {
  1921. return [
  1922. valueMap.get(5),
  1923. valueMap.get(4),
  1924. valueMap.get(3),
  1925. valueMap.get(2),
  1926. valueMap.get(14)
  1927. ];
  1928. }
  1929.  
  1930. return null;
  1931. }
  1932.  
  1933. hasThreeOfAKind(hand, handObject) {
  1934. let triplets = Object.values(handObject.values).filter(e => e.length == 3);
  1935.  
  1936. if (triplets.length > 0) {
  1937. delete hand[hand.indexOf(triplets[0][0])];
  1938. delete hand[hand.indexOf(triplets[0][1])];
  1939. delete hand[hand.indexOf(triplets[0][2])];
  1940.  
  1941. hand = hand.filter(e => e != "empty");
  1942.  
  1943. return triplets[0].concat(hand).slice(0, 5);
  1944. }
  1945. return null;
  1946. }
  1947.  
  1948. hasTwoPairs(hand, handObject) {
  1949. let pairs = Object.values(handObject.values).filter(e => e.length == 2);
  1950.  
  1951. if (pairs.length > 1) {
  1952. delete hand[hand.indexOf(pairs[0][0])];
  1953. delete hand[hand.indexOf(pairs[0][1])];
  1954. delete hand[hand.indexOf(pairs[1][0])];
  1955. delete hand[hand.indexOf(pairs[1][1])];
  1956.  
  1957. hand = hand.filter(e => e != "empty");
  1958.  
  1959. if (parseInt(pairs[0][0].split("-")[1]) > parseInt(pairs[1][0].split("-")[1])) {
  1960. return pairs[0].concat(pairs[1].concat(hand)).slice(0, 5);
  1961. } else {
  1962. return pairs[1].concat(pairs[0].concat(hand)).slice(0, 5);
  1963. }
  1964. }
  1965. return null;
  1966. }
  1967.  
  1968. hasPair(hand, handObject) {
  1969. let pairs = Object.values(handObject.values).filter(e => e.length == 2);
  1970.  
  1971. if (pairs.length > 0) {
  1972. delete hand[hand.indexOf(pairs[0][0])];
  1973. delete hand[hand.indexOf(pairs[0][1])];
  1974.  
  1975. hand = hand.filter(e => e != "empty");
  1976.  
  1977. return pairs[0].concat(hand).slice(0, 5);
  1978. }
  1979. return null;
  1980. }
  1981.  
  1982. addStyle() {
  1983. try {
  1984. const styleText = `
  1985. @import url('https://fonts.googleapis.com/css2?family=Playfair+Display:wght@600;700&family=Roboto:wght@400;500;700&display=swap');
  1986.  
  1987. #pokerCalc-div * {
  1988. font-family: 'Roboto', 'Arial', sans-serif;
  1989. box-sizing: border-box;
  1990. color: #fbf7d5;
  1991. }
  1992.  
  1993. .flopmaster-header {
  1994. display: flex;
  1995. flex-direction: row;
  1996. align-items: center;
  1997. justify-content: space-between;
  1998. margin-bottom: 20px;
  1999. gap: 20px;
  2000. }
  2001.  
  2002. #flopmaster-logo {
  2003. text-align: right;
  2004. padding: 5px 0;
  2005. position: relative;
  2006. flex: 0 0 auto;
  2007. }
  2008.  
  2009. .logo-container {
  2010. position: relative;
  2011. display: inline-block;
  2012. perspective: 1200px;
  2013. transform-style: preserve-3d;
  2014. }
  2015.  
  2016. .logo-card {
  2017. position: relative;
  2018. background: linear-gradient(145deg, #0e7a38 0%, #054122 90%);
  2019. border-radius: 12px;
  2020. padding: 15px 25px;
  2021. box-shadow:
  2022. 0 10px 25px rgba(0,0,0,0.6),
  2023. 0 0 20px rgba(255,255,255,0.15) inset;
  2024. border: 2px solid rgba(255,215,0,0.4);
  2025. overflow: hidden;
  2026. transform-style: preserve-3d;
  2027. transition: all 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275);
  2028. min-width: 240px;
  2029. animation: floatCard 5s ease-in-out infinite alternate;
  2030. }
  2031.  
  2032. .logo-card::before {
  2033. content: '';
  2034. position: absolute;
  2035. top: 0;
  2036. left: 0;
  2037. right: 0;
  2038. bottom: 0;
  2039. background: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNjAiIGhlaWdodD0iNjAiIHZpZXdCb3g9IjAgMCA2MCA2MCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48ZyBmaWxsPSJub25lIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiPjxwYXRoIGQ9Ik0zNiAzNGMwLTEuMTA1LS44OTUtMi0yLTJzLTIgLjg5NS0yIDJjMCAxLjEwNC44OTUgMiAyIDJzMi0uODk2IDItMnoiIGZpbGw9InJnYmEoMjU1LDI1NSwyNTUsMC4wNSkiLz48L2c+PC9zdmc+');
  2040. opacity: 0.2;
  2041. z-index: 0;
  2042. }
  2043.  
  2044. .logo-card:hover {
  2045. transform: translateY(-5px) rotateX(5deg) rotateY(5deg);
  2046. box-shadow:
  2047. 0 15px 25px rgba(0,0,0,0.6),
  2048. 0 0 30px rgba(212, 175, 55, 0.4);
  2049. cursor: pointer;
  2050. }
  2051.  
  2052. @keyframes floatCard {
  2053. 0% { transform: translateY(0) rotateZ(0deg); }
  2054. 50% { transform: translateY(-5px) rotateZ(0.5deg); }
  2055. 100% { transform: translateY(-3px) rotateZ(-0.5deg); }
  2056. }
  2057.  
  2058. @keyframes softGreenPulse {
  2059. 0% { filter: hue-rotate(-10deg) brightness(0.95); }
  2060. 33% { filter: hue-rotate(0deg) brightness(1.05); }
  2061. 66% { filter: hue-rotate(10deg) brightness(1.1); }
  2062. 100% { filter: hue-rotate(0deg) brightness(1); }
  2063. }
  2064.  
  2065. .logo-text {
  2066. font-size: 35px;
  2067. font-weight: 800;
  2068. font-family: 'Playfair Display', serif;
  2069. letter-spacing: 1px;
  2070. display: block;
  2071. width: 100%;
  2072. text-align: center;
  2073. position: relative;
  2074. z-index: 2;
  2075. color: #fff6c8;
  2076. margin: 5px 0;
  2077. text-shadow:
  2078. 0 0 5px #fff,
  2079. 0 0 10px #fff,
  2080. 0 0 20px #fff,
  2081. 0 0 40px #ffb700,
  2082. 0 0 80px #ffb700;
  2083. animation: neonGoldGlow 1.5s ease-in-out infinite alternate;
  2084. }
  2085.  
  2086. .logo-suits {
  2087. position: relative;
  2088. display: flex;
  2089. justify-content: space-around;
  2090. padding: 2px 10px;
  2091. margin-top: 5px;
  2092. }
  2093.  
  2094. .logo-suits .suit {
  2095. font-size: 14px;
  2096. transform-origin: center;
  2097. animation: pulsate 3s infinite;
  2098. filter: drop-shadow(0 3px 4px rgba(0,0,0,0.7));
  2099. transition: all 0.3s ease;
  2100. background-image: none !important;
  2101. -webkit-text-fill-color: initial !important;
  2102. -webkit-background-clip: initial !important;
  2103. background-clip: initial !important;
  2104. }
  2105.  
  2106. .logo-suits .suit:hover {
  2107. transform: scale(1.3) translateY(-3px);
  2108. filter: drop-shadow(0 5px 10px rgba(0,0,0,0.8));
  2109. }
  2110.  
  2111. .logo-suits .suit.hearts,
  2112. .logo-suits .suit.diamonds {
  2113. color: #e62222 !important;
  2114. text-shadow: 0 0 5px rgba(230, 34, 34, 0.7);
  2115. }
  2116.  
  2117. .logo-suits .suit.clubs,
  2118. .logo-suits .suit.spades {
  2119. color: #000000 !important;
  2120. text-shadow: 0 0 5px rgba(255, 255, 255, 0.5);
  2121. }
  2122.  
  2123. .suit.hearts { animation-delay: 0s; }
  2124. .suit.diamonds { animation-delay: 0.75s; }
  2125. .suit.clubs { animation-delay: 1.5s; }
  2126. .suit.spades { animation-delay: 2.25s; }
  2127.  
  2128. @keyframes pulsate {
  2129. 0%, 100% { transform: scale(1); opacity: 0.8; }
  2130. 50% { transform: scale(2); opacity: 1; }
  2131. }
  2132.  
  2133. /* Floating suits animation */
  2134. .suits-container {
  2135. position: absolute;
  2136. top: 0;
  2137. left: 0;
  2138. right: 0;
  2139. bottom: 0;
  2140. overflow: hidden;
  2141. pointer-events: none;
  2142. z-index: -1;
  2143. }
  2144.  
  2145. .floating-suit {
  2146. position: absolute;
  2147. display: inline-block;
  2148. opacity: 0.12;
  2149. user-select: none;
  2150. transition: transform 0.3s ease;
  2151. z-index: -1;
  2152. will-change: transform, top, left;
  2153. }
  2154.  
  2155. .floating-suit.hearts,
  2156. .floating-suit.diamonds {
  2157. color: #e62222;
  2158. text-shadow: 0 0 3px rgba(230, 34, 34, 0.3);
  2159. }
  2160.  
  2161. .floating-suit.clubs,
  2162. .floating-suit.spades {
  2163. color: #000;
  2164. text-shadow: 0 0 3px rgba(0, 0, 0, 0.2);
  2165. }
  2166.  
  2167. .floating-suit.gold {
  2168. color: #d4af37;
  2169. text-shadow: 0 0 4px rgba(212, 175, 55, 0.5);
  2170. }
  2171.  
  2172. #pokerCalc-div::after {
  2173. content: "";
  2174. position: absolute;
  2175. bottom: 0;
  2176. left: 50%;
  2177. transform: translateX(-50%);
  2178. width: 80%;
  2179. height: 1px;
  2180. background: linear-gradient(to right, transparent, rgba(212, 175, 55, 0.5), transparent);
  2181. }
  2182.  
  2183. #pokerCalc-div {
  2184. background: linear-gradient(160deg, #0a5f2a 0%, #052517 100%);
  2185. color: #fbf7d5;
  2186. padding: 25px;
  2187. margin: 15px;
  2188. border-radius: 16px;
  2189. box-shadow:
  2190. 0 10px 35px rgba(0,0,0,0.5),
  2191. 0 0 40px rgba(0,0,0,0.1) inset;
  2192. border: 3px solid;
  2193. border-image: linear-gradient(45deg, #d4af37, #f1c736, #d4af37) 1;
  2194. position: relative;
  2195. overflow: hidden;
  2196. z-index: 1;
  2197. }
  2198.  
  2199. #pokerCalc-div::before {
  2200. content: "";
  2201. position: absolute;
  2202. top: 0;
  2203. left: 0;
  2204. right: 0;
  2205. bottom: 0;
  2206. background-image:
  2207. url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 100 100"><rect width="100" height="100" fill="none" stroke="%23d4af37" stroke-width="0.5" stroke-dasharray="5,5"/></svg>'),
  2208. url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI1IiBoZWlnaHQ9IjUiPgo8cmVjdCB3aWR0aD0iNSIgaGVpZ2h0PSI1IiBmaWxsPSIjMGE1ZjJhIj48L3JlY3Q+CjxwYXRoIGQ9Ik0wIDVMNSAwWk02IDRMNCA2Wk0tMSAxTDEgLTFaIiBzdHJva2U9IiMwNzRhMjMiIHN0cm9rZS13aWR0aD0iMSI+PC9wYXRoPgo8L3N2Zz4='),
  2209. radial-gradient(circle at 100% 100%, rgba(7,74,35,0.6) 0%, transparent 50%),
  2210. radial-gradient(circle at 0% 0%, rgba(20,140,60,0.3) 0%, transparent 50%);
  2211. opacity: 0.5;
  2212. z-index: -1;
  2213. pointer-events: none;
  2214. animation: moveGrid 20s linear infinite;
  2215. }
  2216.  
  2217. @keyframes moveGrid {
  2218. 0% { background-position: 0 0, 0 0, 0 0, 0 0; }
  2219. 100% { background-position: 100px 100px, 0 0, 0 0, 0 0; }
  2220. }
  2221.  
  2222. /* 3D Enhanced Win Probability Meter */
  2223. .win-probability-meter {
  2224. margin: 25px auto 15px;
  2225. max-width: 90%;
  2226. position: relative;
  2227. }
  2228.  
  2229. .meter-label {
  2230. font-size: 14px;
  2231. font-weight: 500;
  2232. margin-bottom: 5px;
  2233. color: #fbf7d5;
  2234. text-align: center;
  2235. letter-spacing: 0.5px;
  2236. text-shadow: 0 2px 4px rgba(0,0,0,0.7);
  2237. position: relative;
  2238. }
  2239.  
  2240. .meter-container {
  2241. height: 25px;
  2242. position: relative;
  2243. border-radius: 12px;
  2244. background: linear-gradient(to bottom, #052517, #0a5f2a);
  2245. padding: 4px;
  2246. box-shadow:
  2247. 0 4px 10px rgba(0,0,0,0.7),
  2248. 0 10px 20px rgba(0,0,0,0.3);
  2249. position: relative;
  2250. overflow: hidden;
  2251. border: 1px solid rgba(212, 175, 55, 0.6);
  2252. transform-style: preserve-3d;
  2253. perspective: 500px;
  2254. }
  2255.  
  2256. .meter-groove {
  2257. position: absolute;
  2258. top: 4px;
  2259. left: 4px;
  2260. right: 4px;
  2261. bottom: 4px;
  2262. background: rgba(0,0,0,0.6);
  2263. border-radius: 9px;
  2264. box-shadow:
  2265. inset 0 2px 6px rgba(0,0,0,0.8),
  2266. inset 0 0 3px rgba(0,0,0,0.6);
  2267. background-image:
  2268. linear-gradient(rgba(10,10,10,0.6) 1px, transparent 1px),
  2269. linear-gradient(90deg, rgba(10,10,10,0.6) 1px, transparent 1px);
  2270. background-size: 5px 5px;
  2271. z-index: 1;
  2272. }
  2273.  
  2274. .meter-bar {
  2275. height: 17px;
  2276. margin-top: 0;
  2277. width: 0%;
  2278. background: linear-gradient(to bottom,
  2279. rgba(255,255,255,0.15) 0%,
  2280. rgba(255,255,255,0) 40%,
  2281. rgba(0,0,0,0.3) 100%),
  2282. linear-gradient(to right, #F44336, #FFC107, #4CAF50);
  2283. border-radius: 8px;
  2284. transition: width 0.8s cubic-bezier(0.22, 1, 0.36, 1);
  2285. position: relative;
  2286. box-shadow:
  2287. 0 0 10px rgba(255,255,255,0.3),
  2288. 0 1px 1px rgba(255,255,255,0.5) inset,
  2289. 0 -1px 1px rgba(0,0,0,0.5) inset;
  2290. z-index: 2;
  2291. transform: translateZ(3px);
  2292. }
  2293.  
  2294. .meter-bar::after {
  2295. content: '';
  2296. position: absolute;
  2297. top: 0;
  2298. left: 0;
  2299. right: 0;
  2300. height: 8px;
  2301. background: linear-gradient(to bottom, rgba(255,255,255,0.3), transparent);
  2302. border-radius: 8px 8px 0 0;
  2303. z-index: 3;
  2304. }
  2305.  
  2306. .meter-container::before {
  2307. content: '';
  2308. position: absolute;
  2309. top: -5px;
  2310. left: 0;
  2311. right: 0;
  2312. height: 10px;
  2313. background: linear-gradient(to bottom, transparent, rgba(0,0,0,0.3));
  2314. z-index: 0;
  2315. transform: rotateX(45deg);
  2316. transform-origin: bottom;
  2317. }
  2318.  
  2319. .meter-container::after {
  2320. content: '';
  2321. position: absolute;
  2322. bottom: -5px;
  2323. left: 0;
  2324. right: 0;
  2325. height: 10px;
  2326. background: linear-gradient(to top, transparent, rgba(0,0,0,0.3));
  2327. z-index: 0;
  2328. transform: rotateX(-45deg);
  2329. transform-origin: top;
  2330. }
  2331.  
  2332. .meter-value {
  2333. position: absolute;
  2334. top: 0;
  2335. left: 0;
  2336. right: 0;
  2337. bottom: 0;
  2338. display: flex;
  2339. align-items: center;
  2340. justify-content: center;
  2341. color: white;
  2342. font-weight: bold;
  2343. font-size: 14px;
  2344. text-shadow: 0 1px 2px rgba(0,0,0,0.9), 0 0 10px rgba(0,0,0,0.5);
  2345. z-index: 4;
  2346. transform: translateZ(5px);
  2347. }
  2348.  
  2349. /* Mini Meters Styling */
  2350. .mini-meters-container {
  2351. display: flex;
  2352. flex-wrap: wrap;
  2353. justify-content: space-between;
  2354. gap: 10px;
  2355. margin: 5px auto 15px;
  2356. max-width: 94%;
  2357. }
  2358.  
  2359. .mini-meter {
  2360. flex: 1 1 30%;
  2361. min-width: 120px;
  2362. margin-bottom: 5px;
  2363. }
  2364.  
  2365. .mini-meter-label {
  2366. font-size: 11px;
  2367. font-weight: 500;
  2368. margin-bottom: 3px;
  2369. color: #fbf7d5;
  2370. text-align: center;
  2371. letter-spacing: 0.5px;
  2372. text-shadow: 0 1px 3px rgba(0,0,0,0.7);
  2373. }
  2374.  
  2375. .mini-meter-container {
  2376. height: 16px;
  2377. position: relative;
  2378. border-radius: 8px;
  2379. background: linear-gradient(to bottom, #052517, #0a5f2a);
  2380. padding: 3px;
  2381. box-shadow:
  2382. 0 2px 6px rgba(0,0,0,0.6),
  2383. 0 6px 10px rgba(0,0,0,0.2);
  2384. position: relative;
  2385. overflow: hidden;
  2386. border: 1px solid rgba(212, 175, 55, 0.4);
  2387. transform-style: preserve-3d;
  2388. }
  2389.  
  2390. .mini-meter-groove {
  2391. position: absolute;
  2392. top: 3px;
  2393. left: 3px;
  2394. right: 3px;
  2395. bottom: 3px;
  2396. background: rgba(0,0,0,0.6);
  2397. border-radius: 6px;
  2398. box-shadow: inset 0 1px 4px rgba(0,0,0,0.8);
  2399. background-image:
  2400. linear-gradient(rgba(10,10,10,0.6) 1px, transparent 1px),
  2401. linear-gradient(90deg, rgba(10,10,10,0.6) 1px, transparent 1px);
  2402. background-size: 4px 4px;
  2403. z-index: 1;
  2404. }
  2405.  
  2406. .mini-meter-bar {
  2407. height: 10px;
  2408. margin-top: 0;
  2409. width: 0%;
  2410. border-radius: 5px;
  2411. transition: width 0.6s cubic-bezier(0.22, 1, 0.36, 1);
  2412. position: relative;
  2413. box-shadow:
  2414. 0 0 6px rgba(255,255,255,0.2),
  2415. 0 1px 1px rgba(255,255,255,0.3) inset;
  2416. z-index: 2;
  2417. transform: translateZ(2px);
  2418. }
  2419.  
  2420. /* More vibrant color scheme */
  2421. .mini-meter-bar[data-type="pair"] {
  2422. background: #5C6BC0;
  2423. }
  2424.  
  2425. .mini-meter-bar[data-type="twoPair"] {
  2426. background: #42A5F5;
  2427. }
  2428.  
  2429. .mini-meter-bar[data-type="trips"] {
  2430. background: #AB47BC;
  2431. }
  2432.  
  2433. .mini-meter-bar[data-type="fullHouse"] {
  2434. background: #7E57C2;
  2435. }
  2436.  
  2437. .mini-meter-bar[data-type="straight"] {
  2438. background: #FFA726;
  2439. }
  2440.  
  2441. .mini-meter-bar[data-type="flush"] {
  2442. background: #66BB6A;
  2443. }
  2444.  
  2445. .mini-meter-bar[data-type="quads"] {
  2446. background: #EC407A;
  2447. }
  2448.  
  2449. .mini-meter-bar[data-type="straightFlush"] {
  2450. background: #26C6DA;
  2451. }
  2452.  
  2453. .mini-meter-bar[data-type="royalFlush"] {
  2454. background: linear-gradient(45deg, #FFEB3B, #FFC107, #FF9800);
  2455. box-shadow: 0 0 10px 2px rgba(255, 215, 0, 0.7);
  2456. animation: royal-glow 2s infinite alternate;
  2457. }
  2458.  
  2459. @keyframes royal-glow {
  2460. 0% { box-shadow: 0 0 5px 1px rgba(255, 215, 0, 0.5); }
  2461. 100% { box-shadow: 0 0 15px 3px rgba(255, 215, 0, 0.9); }
  2462. }
  2463.  
  2464. .mini-meter-bar {
  2465. transition:
  2466. width 0.8s cubic-bezier(0.22, 1, 0.36, 1),
  2467. background-color 0.5s ease;
  2468. }
  2469.  
  2470. /* Pulse animation for high percentages */
  2471. .mini-meter-bar[style*="90%"] {
  2472. animation: high-percent-pulse 1.5s infinite;
  2473. }
  2474.  
  2475. @keyframes high-percent-pulse {
  2476. 0%, 100% { opacity: 1; }
  2477. 50% { opacity: 0.8; }
  2478. }
  2479.  
  2480. .mini-meter-value {
  2481. position: absolute;
  2482. top: 0;
  2483. left: 0;
  2484. right: 0;
  2485. bottom: 0;
  2486. display: flex;
  2487. align-items: center;
  2488. justify-content: center;
  2489. color: white;
  2490. font-weight: bold;
  2491. font-size: 10px;
  2492. text-shadow: 0 1px 2px rgba(0,0,0,0.9);
  2493. z-index: 4;
  2494. transform: translateZ(3px);
  2495. }
  2496.  
  2497. #pokerCalc-recommendations {
  2498. flex: 1;
  2499. display: flex;
  2500. align-items: center;
  2501. justify-content: flex-start;
  2502. margin-right: 20px;
  2503. }
  2504.  
  2505. .action-chip {
  2506. position: relative;
  2507. width: 280px;
  2508. height: 80px;
  2509. cursor: pointer;
  2510. overflow: visible;
  2511. }
  2512.  
  2513. .chip-inner {
  2514. position: absolute;
  2515. width: 100%;
  2516. height: 100%;
  2517. border-radius: 40px;
  2518. background: linear-gradient(145deg, #2d2d2d, #151515);
  2519. box-shadow: 0 5px 15px rgba(0,0,0,0.6);
  2520. overflow: hidden;
  2521. display: flex;
  2522. align-items: center;
  2523. justify-content: center;
  2524. z-index: 2;
  2525. transition: all 0.3s ease;
  2526. border: 8px dashed rgba(255,255,255,0.15);
  2527. }
  2528.  
  2529. .action-chip:hover .chip-inner {
  2530. box-shadow: 0 8px 25px rgba(0,0,0,0.7);
  2531. }
  2532.  
  2533. .chip-inner::after {
  2534. content: "";
  2535. position: absolute;
  2536. top: 0;
  2537. left: 0;
  2538. right: 0;
  2539. height: 40%;
  2540. background: linear-gradient(to bottom, rgba(255,255,255,0.15), transparent);
  2541. pointer-events: none;
  2542. border-radius: 40px 40px 0 0;
  2543. }
  2544.  
  2545. .chip-inner::before {
  2546. content: "";
  2547. position: absolute;
  2548. inset: 8px;
  2549. border-radius: 32px;
  2550. background: radial-gradient(circle at center, #262626, #111111);
  2551. z-index: -1;
  2552. }
  2553.  
  2554. .chip-edge {
  2555. position: absolute;
  2556. width: calc(100% - 16px);
  2557. height: calc(100% - 16px);
  2558. top: 8px;
  2559. left: 8px;
  2560. border-radius: 32px;
  2561. z-index: 1;
  2562. transition: all 0.3s ease;
  2563. }
  2564.  
  2565. #pokerCalc-action {
  2566. color: #fff;
  2567. font-weight: bold;
  2568. font-size: 18px;
  2569. padding: 8px 20px;
  2570. display: inline-flex;
  2571. align-items: center;
  2572. justify-content: center;
  2573. letter-spacing: 0.5px;
  2574. transition: all 0.3s ease;
  2575. text-align: center;
  2576. font-family: 'Roboto', 'Arial Black', Arial, sans-serif;
  2577. position: relative;
  2578. line-height: 1.3;
  2579. }
  2580.  
  2581. #pokerCalc-action::before,
  2582. #pokerCalc-action::after {
  2583. content: "";
  2584. position: absolute;
  2585. width: 10px;
  2586. height: 10px;
  2587. border-radius: 50%;
  2588. background-color: currentColor;
  2589. opacity: 0.7;
  2590. transition: all 0.3s ease;
  2591. box-shadow: 0 0 10px currentColor;
  2592. }
  2593.  
  2594. #pokerCalc-action::before {
  2595. left: -25px;
  2596. }
  2597.  
  2598. #pokerCalc-action::after {
  2599. right: -25px;
  2600. }
  2601.  
  2602. #pokerCalc-action.action-raise {
  2603. color: #50e150;
  2604. animation: neonGreenGlow 1.5s ease-in-out infinite alternate;
  2605. text-shadow:
  2606. 0 0 5px #fff,
  2607. 0 0 10px #fff,
  2608. 0 0 20px #fff,
  2609. 0 0 40px #50e150,
  2610. 0 0 80px #50e150;
  2611. }
  2612.  
  2613. #pokerCalc-action.action-raise ~ .chip-edge {
  2614. box-shadow: 0 0 20px 5px rgba(80, 225, 80, 0.4);
  2615. animation: pulse-chip-green 2s infinite alternate;
  2616. }
  2617.  
  2618. #pokerCalc-action.action-call {
  2619. color: #f0ad4e;
  2620. animation: neonOrangeGlow 1.5s ease-in-out infinite alternate;
  2621. text-shadow:
  2622. 0 0 5px #fff,
  2623. 0 0 10px #fff,
  2624. 0 0 20px #fff,
  2625. 0 0 40px #f0ad4e,
  2626. 0 0 80px #f0ad4e;
  2627. }
  2628.  
  2629. #pokerCalc-action.action-call ~ .chip-edge {
  2630. box-shadow: 0 0 20px 5px rgba(240, 173, 78, 0.4);
  2631. animation: pulse-chip-orange 2s infinite alternate;
  2632. }
  2633.  
  2634. #pokerCalc-action.action-fold {
  2635. color: #f05050;
  2636. animation: neonRedGlow 1.5s ease-in-out infinite alternate;
  2637. text-shadow:
  2638. 0 0 5px #fff,
  2639. 0 0 10px #fff,
  2640. 0 0 20px #fff,
  2641. 0 0 40px #f05050,
  2642. 0 0 80px #f05050;
  2643. }
  2644.  
  2645. #pokerCalc-action.action-fold ~ .chip-edge {
  2646. box-shadow: 0 0 20px 5px rgba(240, 80, 80, 0.4);
  2647. animation: pulse-chip-red 2s infinite alternate;
  2648. }
  2649.  
  2650. @keyframes pulse-chip-green {
  2651. 0% { box-shadow: 0 0 10px 5px rgba(80, 225, 80, 0.4); }
  2652. 100% { box-shadow: 0 0 25px 8px rgba(80, 225, 80, 0.7); }
  2653. }
  2654.  
  2655. @keyframes pulse-chip-orange {
  2656. 0% { box-shadow: 0 0 10px 5px rgba(240, 173, 78, 0.4); }
  2657. 100% { box-shadow: 0 0 25px 8px rgba(240, 173, 78, 0.7); }
  2658. }
  2659.  
  2660. @keyframes pulse-chip-red {
  2661. 0% { box-shadow: 0 0 10px 5px rgba(240, 80, 80, 0.4); }
  2662. 100% { box-shadow: 0 0 25px 8px rgba(240, 80, 80, 0.7); }
  2663. }
  2664.  
  2665. /* Enhanced Holographic Table Effect */
  2666. #pokerCalc-div table {
  2667. border-collapse: separate;
  2668. border-spacing: 0;
  2669. margin: 25px 0;
  2670. width: 100%;
  2671. border-radius: 12px;
  2672. transition: all 0.3s ease;
  2673. background: linear-gradient(
  2674. 135deg,
  2675. rgba(10, 30, 20, 0.8) 0%,
  2676. rgba(20, 60, 40, 0.8) 50%,
  2677. rgba(10, 30, 20, 0.8) 100%
  2678. );
  2679. backdrop-filter: blur(5px);
  2680. border: 1px solid rgba(212, 175, 55, 0.5);
  2681. box-shadow:
  2682. 0 0 20px rgba(212, 175, 55, 0.3),
  2683. 0 0 40px rgba(212, 175, 55, 0.2);
  2684. position: relative;
  2685. overflow: hidden;
  2686. }
  2687.  
  2688. /* Primary shimmer layer - more visible but still seamless */
  2689. #pokerCalc-div table::before {
  2690. content: '';
  2691. position: absolute;
  2692. top: 0;
  2693. left: 0;
  2694. right: 0;
  2695. bottom: 0;
  2696. background: linear-gradient(
  2697. 70deg,
  2698. rgba(255,255,255,0) 0%,
  2699. rgba(255,255,255,0) 35%,
  2700. rgba(255,255,255,0.15) 45%,
  2701. rgba(255,255,255,0.2) 50%,
  2702. rgba(255,255,255,0.15) 55%,
  2703. rgba(255,255,255,0) 65%,
  2704. rgba(255,255,255,0) 100%
  2705. );
  2706. background-size: 200% 200%;
  2707. background-position: 0% 0;
  2708. animation: hologram-flow 12s linear infinite;
  2709. pointer-events: none;
  2710. z-index: 1;
  2711. }
  2712.  
  2713. /* Secondary subtle gold shimmer layer */
  2714. #pokerCalc-div table::after {
  2715. content: '';
  2716. position: absolute;
  2717. top: 0;
  2718. left: 0;
  2719. right: 0;
  2720. bottom: 0;
  2721. background: linear-gradient(
  2722. 110deg,
  2723. rgba(212, 175, 55, 0) 0%,
  2724. rgba(212, 175, 55, 0) 40%,
  2725. rgba(212, 175, 55, 0.07) 47%,
  2726. rgba(212, 175, 55, 0.1) 50%,
  2727. rgba(212, 175, 55, 0.07) 53%,
  2728. rgba(212, 175, 55, 0) 60%,
  2729. rgba(212, 175, 55, 0) 100%
  2730. );
  2731. background-size: 200% 200%;
  2732. background-position: 100% 0;
  2733. animation: hologram-flow 18s linear infinite;
  2734. pointer-events: none;
  2735. z-index: 1;
  2736. opacity: 0.9;
  2737. }
  2738.  
  2739. @keyframes hologram-flow {
  2740. 0% { background-position: 100% 0; }
  2741. 100% { background-position: -100% 0; }
  2742. }
  2743.  
  2744. #pokerCalc-div table:hover {
  2745. box-shadow:
  2746. 0 12px 20px rgba(0, 0, 0, 0.3),
  2747. 0 0 20px rgba(212, 175, 55, 0.4);
  2748. transform: translateY(-3px);
  2749. }
  2750.  
  2751. #pokerCalc-div th {
  2752. background: linear-gradient(145deg, #272727, #1c1c1c);
  2753. color: #fbf7d5;
  2754. padding: 15px 12px;
  2755. font-weight: 600;
  2756. font-size: 14px;
  2757. text-transform: uppercase;
  2758. letter-spacing: 1.5px;
  2759. border-bottom: 2px solid rgba(212, 175, 55, 0.3);
  2760. position: relative;
  2761. overflow: hidden;
  2762. text-shadow: 0 1px 2px rgba(0, 0, 0, 0.8);
  2763. z-index: 2;
  2764. }
  2765.  
  2766. #pokerCalc-div th::after {
  2767. content: '';
  2768. position: absolute;
  2769. bottom: 0;
  2770. left: 0;
  2771. right: 0;
  2772. height: 1px;
  2773. background: linear-gradient(to right, transparent, rgba(212, 175, 55, 0.5), transparent);
  2774. }
  2775.  
  2776. #pokerCalc-div td {
  2777. background: rgba(26, 26, 26, 0.8);
  2778. padding: 12px;
  2779. border-bottom: 1px solid rgba(255, 255, 255, 0.05);
  2780. position: relative;
  2781. transition: all 0.3s ease;
  2782. color: #fbf7d5;
  2783. font-size: 14px;
  2784. z-index: 2;
  2785. }
  2786.  
  2787. #pokerCalc-div tr:hover td {
  2788. background: rgba(40, 40, 40, 0.8);
  2789. transform: translateX(3px);
  2790. box-shadow: -3px 0 10px rgba(0, 0, 0, 0.2);
  2791. }
  2792.  
  2793. #pokerCalc-div tr:last-child td {
  2794. border-bottom: none;
  2795. }
  2796.  
  2797. #pokerCalc-div tbody tr {
  2798. position: relative;
  2799. transition: all 0.3s ease;
  2800. }
  2801.  
  2802. #pokerCalc-div tbody tr:hover {
  2803. background: rgba(212, 175, 55, 0.1);
  2804. transform: scale(1.01);
  2805. z-index: 2;
  2806. }
  2807.  
  2808. #pokerCalc-div caption {
  2809. color: #fbf7d5;
  2810. font-size: 1.2em;
  2811. font-weight: bold;
  2812. margin: 15px 0 10px;
  2813. text-align: left;
  2814. letter-spacing: 1px;
  2815. position: relative;
  2816. padding-left: 40px;
  2817. text-shadow: 0 1px 2px rgba(0, 0, 0, 0.5);
  2818. font-family: 'Playfair Display', serif;
  2819. animation: neonGoldGlow 3s ease-in-out infinite alternate;
  2820. }
  2821.  
  2822. #pokerCalc-div caption::before {
  2823. content: '♠♣♥♦';
  2824. position: absolute;
  2825. left: 3px;
  2826. top: 50%;
  2827. transform: translateY(-50%);
  2828. font-size: 0.9em;
  2829. letter-spacing: 2px;
  2830. }
  2831.  
  2832. #pokerCalc-div caption::before {
  2833. text-shadow: 0 0 5px rgba(0, 0, 0, 0.5);
  2834. }
  2835.  
  2836. /* Style the individual suit icons in captions */
  2837. #pokerCalc-myHand caption::before,
  2838. #pokerCalc-preflop caption::before {
  2839. content: '♠♣♥♦';
  2840. }
  2841.  
  2842. #pokerCalc-upgrades caption::before {
  2843. content: '♠♣♥♦';
  2844. }
  2845.  
  2846. #pokerCalc-oppPossHands caption::before {
  2847. content: '♠♣♥♦';
  2848. }
  2849.  
  2850. /* Make caption icons black and red */
  2851. #pokerCalc-div caption::before {
  2852. background: linear-gradient(to right,
  2853. black 0%, black 25%,
  2854. black 25%, black 50%,
  2855. #e62222 50%, #e62222 75%,
  2856. #e62222 75%, #e62222 100%);
  2857. -webkit-background-clip: text;
  2858. -webkit-text-fill-color: transparent;
  2859. background-clip: text;
  2860. }
  2861.  
  2862. #pokerCalc-div td:nth-child(1) {
  2863. font-weight: bold;
  2864. color: #fbf7d5;
  2865. position: relative;
  2866. background: rgba(30, 30, 30, 0.9);
  2867. }
  2868.  
  2869. #pokerCalc-div td:nth-child(3),
  2870. #pokerCalc-div td:nth-child(4) {
  2871. font-weight: bold;
  2872. color: #fbf7d5;
  2873. }
  2874.  
  2875. .rank-up {
  2876. color: #70ff70 !important;
  2877. text-shadow: 0 0 5px rgba(112, 255, 112, 0.5);
  2878. }
  2879.  
  2880. .rank-down {
  2881. color: #ff7070 !important;
  2882. text-shadow: 0 0 5px rgba(255, 112, 112, 0.5);
  2883. }
  2884.  
  2885. .similar-hand td {
  2886. opacity: 0.7;
  2887. }
  2888.  
  2889. .best-hand {
  2890. background: rgba(64, 195, 64, 0.2) !important;
  2891. border-left: 4px solid #40c340 !important;
  2892. animation: glowGreen 2s infinite alternate;
  2893. }
  2894.  
  2895. @keyframes glowGreen {
  2896. 0% { box-shadow: inset 0 0 5px rgba(64, 195, 64, 0.5); }
  2897. 100% { box-shadow: inset 0 0 15px rgba(64, 195, 64, 0.8); }
  2898. }
  2899.  
  2900. .best-hand td {
  2901. color: #fbf7d5 !important;
  2902. font-weight: bold;
  2903. }
  2904.  
  2905. .best-hand td:first-child {
  2906. position: relative;
  2907. }
  2908.  
  2909. .best-hand td:first-child::before {
  2910. content: '★';
  2911. position: absolute;
  2912. left: -20px;
  2913. top: 50%;
  2914. transform: translateY(-50%);
  2915. color: #40c340;
  2916. animation: starPulse 1.5s infinite alternate;
  2917. }
  2918.  
  2919. @keyframes starPulse {
  2920. 0% { transform: translateY(-50%) scale(1); opacity: 0.7; }
  2921. 100% { transform: translateY(-50%) scale(1.3); opacity: 1; }
  2922. }
  2923.  
  2924. .potential-best-hand {
  2925. background: rgba(64, 64, 195, 0.2) !important;
  2926. border-left: 4px solid #4040c3 !important;
  2927. animation: glowBlue 2s infinite alternate;
  2928. }
  2929.  
  2930. @keyframes glowBlue {
  2931. 0% { box-shadow: inset 0 0 5px rgba(64, 64, 195, 0.5); }
  2932. 100% { box-shadow: inset 0 0 15px rgba(64, 64, 195, 0.8); }
  2933. }
  2934.  
  2935. .potential-best-hand td {
  2936. color: #fbf7d5 !important;
  2937. font-weight: bold;
  2938. }
  2939.  
  2940. .best-opp-hand {
  2941. background: rgba(195, 64, 64, 0.2) !important;
  2942. border-left: 4px solid #c34040 !important;
  2943. animation: glowRed 2s infinite alternate;
  2944. }
  2945.  
  2946. @keyframes glowRed {
  2947. 0% { box-shadow: inset 0 0 5px rgba(195, 64, 64, 0.5); }
  2948. 100% { box-shadow: inset 0 0 15px rgba(195, 64, 64, 0.8); }
  2949. }
  2950.  
  2951. .best-opp-hand td {
  2952. color: #fbf7d5 !important;
  2953. font-weight: bold;
  2954. }
  2955.  
  2956. .ev-positive {
  2957. border-left: 4px solid #40c340 !important;
  2958. box-shadow: inset 3px 0 10px rgba(64, 195, 64, 0.3);
  2959. }
  2960.  
  2961. .ev-negative {
  2962. border-left: 4px solid #c34040 !important;
  2963. box-shadow: inset 3px 0 10px rgba(195, 64, 64, 0.3);
  2964. }
  2965.  
  2966. .bluff-alert {
  2967. animation: pulse-red 1.5s infinite;
  2968. position: relative;
  2969. }
  2970.  
  2971. .bluff-alert::before {
  2972. content: '⚠️';
  2973. position: absolute;
  2974. left: -25px;
  2975. animation: shakeWarning 0.8s infinite;
  2976. }
  2977.  
  2978. @keyframes shakeWarning {
  2979. 0%, 100% { transform: translateX(0); }
  2980. 25% { transform: translateX(-2px); }
  2981. 75% { transform: translateX(2px); }
  2982. }
  2983.  
  2984. @keyframes pulse-red {
  2985. 0%, 100% {
  2986. border-color: #c34040;
  2987. box-shadow: 0 0 5px rgba(195, 64, 64, 0.6);
  2988. }
  2989. 50% {
  2990. border-color: #ff4040;
  2991. box-shadow: 0 0 15px rgba(255, 64, 64, 0.9);
  2992. }
  2993. }
  2994.  
  2995. @keyframes float {
  2996. 0%, 100% {
  2997. transform: translateY(0);
  2998. }
  2999. 50% {
  3000. transform: translateY(-8px);
  3001. }
  3002. }
  3003.  
  3004. /* Card Icons in Statistics */
  3005. span[class*="spades"], span[class*="hearts"],
  3006. span[class*="clubs"], span[class*="diamonds"] {
  3007. display: inline-block;
  3008. font-size: 16px;
  3009. margin: 0 1px;
  3010. vertical-align: middle;
  3011. filter: drop-shadow(0 2px 2px rgba(0,0,0,0.5));
  3012. }
  3013.  
  3014. span[class*="hearts"], span[class*="diamonds"] {
  3015. color: #ff5555 !important;
  3016. }
  3017.  
  3018. span[class*="spades"], span[class*="clubs"] {
  3019. color: #ffffff !important;
  3020. }
  3021.  
  3022. /* Neon Glow Effects */
  3023. .neon-text {
  3024. text-shadow:
  3025. 0 0 5px #fff,
  3026. 0 0 10px #fff,
  3027. 0 0 20px #fff,
  3028. 0 0 40px #0ff,
  3029. 0 0 80px #0ff,
  3030. 0 0 90px #0ff,
  3031. 0 0 100px #0ff,
  3032. 0 0 150px #0ff;
  3033. animation: neonGlow 1.5s ease-in-out infinite alternate;
  3034. }
  3035.  
  3036. @keyframes neonGlow {
  3037. from {
  3038. text-shadow:
  3039. 0 0 5px #fff,
  3040. 0 0 10px #fff,
  3041. 0 0 20px #fff,
  3042. 0 0 40px #0ff,
  3043. 0 0 80px #0ff,
  3044. 0 0 90px #0ff,
  3045. 0 0 100px #0ff,
  3046. 0 0 150px #0ff;
  3047. }
  3048. to {
  3049. text-shadow:
  3050. 0 0 2px #fff,
  3051. 0 0 5px #fff,
  3052. 0 0 10px #fff,
  3053. 0 0 20px #0ff,
  3054. 0 0 40px #0ff,
  3055. 0 0 60px #0ff,
  3056. 0 0 70px #0ff,
  3057. 0 0 100px #0ff;
  3058. }
  3059. }
  3060.  
  3061. @keyframes neonGoldGlow {
  3062. from {
  3063. text-shadow:
  3064. 0 0 2px #fff,
  3065. 0 0 5px #fff,
  3066. 0 0 10px #ffb700,
  3067. 0 0 20px rgba(255, 183, 0, 0.5);
  3068. }
  3069. to {
  3070. text-shadow:
  3071. 0 0 1px #fff,
  3072. 0 0 3px #fff,
  3073. 0 0 5px #ffb700,
  3074. 0 0 10px rgba(255, 183, 0, 0.3);
  3075. }
  3076. }
  3077.  
  3078. @keyframes neonGreenGlow {
  3079. from {
  3080. text-shadow:
  3081. 0 0 2px #fff,
  3082. 0 0 5px #fff,
  3083. 0 0 10px rgba(80, 225, 80, 0.4);
  3084. }
  3085. to {
  3086. text-shadow:
  3087. 0 0 1px #fff,
  3088. 0 0 3px #fff,
  3089. 0 0 5px rgba(80, 225, 80, 0.2);
  3090. }
  3091. }
  3092.  
  3093. @keyframes neonOrangeGlow {
  3094. from {
  3095. text-shadow:
  3096. 0 0 2px #fff,
  3097. 0 0 5px #fff,
  3098. 0 0 10px rgba(240, 173, 78, 0.4);
  3099. }
  3100. to {
  3101. text-shadow:
  3102. 0 0 1px #fff,
  3103. 0 0 3px #fff,
  3104. 0 0 5px rgba(240, 173, 78, 0.2);
  3105. }
  3106. }
  3107.  
  3108. @keyframes neonRedGlow {
  3109. from {
  3110. text-shadow:
  3111. 0 0 2px #fff,
  3112. 0 0 5px #fff,
  3113. 0 0 10px rgba(240, 80, 80, 0.4);
  3114. }
  3115. to {
  3116. text-shadow:
  3117. 0 0 1px #fff,
  3118. 0 0 3px #fff,
  3119. 0 0 5px rgba(240, 80, 80, 0.2);
  3120. }
  3121. }
  3122. `;
  3123.  
  3124. const style = document.createElement("style");
  3125. style.type = "text/css";
  3126.  
  3127. if (style.styleSheet) {
  3128. style.styleSheet.cssText = styleText;
  3129. } else {
  3130. style.appendChild(document.createTextNode(styleText));
  3131. }
  3132.  
  3133. document.head.appendChild(style);
  3134. } catch (e) {
  3135. console.error("Error adding styles:", e);
  3136.  
  3137. const minimalStyle = document.createElement("style");
  3138. minimalStyle.textContent = "#pokerCalc-div { font-family: Arial; color: gold; background: #0a5f2a; padding: 20px; }";
  3139. document.head.appendChild(minimalStyle);
  3140. }
  3141. }
  3142. }
  3143.  
  3144. window.pokerCalculator = new PokerCalculatorModule();
  3145. window.pokerCalculator.addStatisticsTable();
  3146.  
  3147. window.addEventListener("hashchange", () => {
  3148. if (window.location.href.includes("sid=holdem")) {
  3149. if (!document.getElementById("pokerCalc-div")) {
  3150. window.pokerCalculator = new PokerCalculatorModule();
  3151. window.pokerCalculator.addStatisticsTable();
  3152. }
  3153. }
  3154. });
  3155.  
  3156. window.addEventListener("error", (e) => {
  3157. if (e.message && e.message.includes("pokerCalculator")) {
  3158. console.log("Poker Helper error detected, attempting to recover...");
  3159. try {
  3160. window.pokerCalculator = new PokerCalculatorModule();
  3161. window.pokerCalculator.addStatisticsTable();
  3162. } catch (err) {
  3163. console.error("Could not recover poker helper:", err);
  3164. }
  3165. }
  3166. });
  3167.  
  3168. (() => {
  3169. setTimeout(() => {
  3170. const div = document.getElementById("pokerCalc-div");
  3171. if (div) {
  3172. const versionInfo = document.createElement("div");
  3173. versionInfo.style.fontSize = "11px";
  3174. versionInfo.style.color = "#d4af37";
  3175. versionInfo.style.textAlign = "right";
  3176. versionInfo.style.marginTop = "10px";
  3177. versionInfo.style.letterSpacing = "0.5px";
  3178. versionInfo.style.fontFamily = "'Roboto', sans-serif";
  3179. versionInfo.style.textShadow = "0 1px 2px rgba(0,0,0,0.8)";
  3180. versionInfo.innerHTML = "FlopMaster v1.0 <span style='opacity:0.7;'></span> <span style='font-size:9px;opacity:0.8;'></span>";
  3181. div.appendChild(versionInfo);
  3182. }
  3183. }, 3000);
  3184. })();