git / VotyMehjong.html
KEXEL's picture
1.1
50a7d53 verified
<!DOCTYPE html>
<html lang="pt-br">
<head>
<meta charset="UTF-8">
<title>VOTYMEHJONG</title>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="description" content="VOTYMEHJONG GAME ADAPTÁVEL..." />
<meta name="mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="application-name" content="VOTYMEHJONG" />
<meta name="apple-mobile-web-app-title" content="VOTYMEHJONG" />
<meta name="msapplication-starturl" content="https://kexel-git.static.hf.space/VotyMehjong.html?Installe=votymehjong" />
<meta name="theme-color" content="#222" />
<meta name="msapplication-navbutton-color" content="#222" />
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
<meta name="apple-touch-fullscreen" content="yes" />
<meta name="robots" content="index, follow" />
<meta name="googlebot" content="index, follow" />
<meta property="og:image" content="https://placehold.co/512/3b2d50/bdbac1.png?font=roboto&text=VOTY.MEHJONG" />
<link rel="icon" type="image/png" sizes="512x512" href="https://placehold.co/512/3b2d50/bdbac1.png?font=roboto&text=VOTY.MEHJONG" />
<link rel="apple-touch-icon" type="image/png" sizes="512x512" href="https://placehold.co/512/3b2d50/bdbac1.png?font=roboto&text=VOTY.MEHJONG" />
<link rel="icon" type="image/png" sizes="192x192" href="https://placehold.co/192/3b2d50/bdbac1.png?font=roboto&text=VOTY.MEHJONG" />
<link rel="apple-touch-icon" type="image/png" sizes="192x192" href="https://placehold.co/192/3b2d50/bdbac1.png?font=roboto&text=VOTY.MEHJONG" />
<link rel="icon" type="image/png" sizes="144x144" href="https://placehold.co/144/3b2d50/bdbac1.png?font=roboto&text=VOTY.MEHJONG" />
<link rel="apple-touch-icon" type="image/png" sizes="144x144" href="https://placehold.co/144/3b2d50/bdbac1.png?font=roboto&text=VOTY.MEHJONG" />
<link rel="icon" type="image/png" sizes="96x96" href="https://placehold.co/96/3b2d50/bdbac1.png?font=roboto&text=VOTY.MEHJONG" />
<link rel="apple-touch-icon" type="image/png" sizes="96x96" href="https://placehold.co/96/3b2d50/bdbac1.png?font=roboto&text=VOTY.MEHJONG" />
<link rel="icon" type="image/png" sizes="72x72" href="https://placehold.co/72/3b2d50/bdbac1.png?font=roboto&text=VOTY.MEHJONG" />
<link rel="apple-touch-icon" type="image/png" sizes="72x72" href="https://placehold.co/72/3b2d50/bdbac1.png?font=roboto&text=VOTY.MEHJONG" />
<link rel="icon" type="image/png" sizes="48x48" href="https://placehold.co/48/3b2d50/bdbac1.png?font=roboto&text=VOTY.MEHJONG" />
<link rel="apple-touch-icon" type="image/png" sizes="48x48" href="https://placehold.co/48/3b2d50/bdbac1.png?font=roboto&text=VOTY.MEHJONG" />
<link rel="icon" type="image/png" sizes="36x36" href="https://placehold.co/36/3b2d50/bdbac1.png?font=roboto&text=VOTY.MEHJONG" />
<link rel="apple-touch-icon" type="image/png" sizes="36x36" href="https://placehold.co/36/3b2d50/bdbac1.png?font=roboto&text=VOTY.MEHJONG" />
<script src="data:application/javascript;charset=utf-8;base64,dmFyIGNvbG9iZD0iIzJiMzAzNWM3Iixjb2xvbGlzPSIjNzI2ZDNiIixjb2xvdHh0PSIjZmZmOSI7ZG9jdW1lbnQud3JpdGUoJzxzdHlsZT4ucGFjZXtwb2ludGVyLWV2ZW50czpub25lO3VzZXItc2VsZWN0Om5vbmU7ei1pbmRleDo5OTk5OTk5O3Bvc2l0aW9uOmZpeGVkO3RvcDowO2xlZnQ6MDtyaWdodDowO2JvdHRvbTowO2hlaWdodDoxMDAlO3dpZHRoOjEwMCU7YmFja2dyb3VuZDonK2NvbG9iZCsnO292ZXJmbG93OmhpZGRlbn0ucGFjZTo6YmVmb3Jle2NvbnRlbnQ6IkNBUlJFR0FORE8uLi4iO2NvbG9yOicrY29sb3R4dCsnO3Bvc2l0aW9uOmFic29sdXRlO3RvcDo0NyU7bGVmdDo1MCU7dHJhbnNmb3JtOnRyYW5zbGF0ZSgtNTAlLC01MCUpfS5wYWNlIC5wYWNlLXByb2dyZXNze2JveC1zaXppbmc6Ym9yZGVyLWJveDt0cmFuc2Zvcm06dHJhbnNsYXRlM2QoMCwwLDApO21heC13aWR0aDoxMDAlO3Bvc2l0aW9uOmZpeGVkO3otaW5kZXg6MjAwMDtkaXNwbGF5OmJsb2NrO3Bvc2l0aW9uOmFic29sdXRlO2JvdHRvbTo1MCU7cmlnaHQ6MTAwJTtoZWlnaHQ6NXB4O3dpZHRoOjEwMCU7YmFja2dyb3VuZDonK2NvbG9saXMrJ30ucGFjZS5wYWNlLWluYWN0aXZle2Rpc3BsYXk6bm9uZX08L3N0eWxlPicpOyhmdW5jdGlvbigpe2Z1bmN0aW9uIG8odCxlKXtyZXR1cm4gZnVuY3Rpb24oKXtyZXR1cm4gdC5hcHBseShlLGFyZ3VtZW50cyl9fXZhciB1LGMsaSxuLHksdCxsLHYscixzLGEsZSxwLHcsYixoLGYsZCxnLG0sayxTLHEseCxMLFAsVCxSLGosTyxFLE0sQSxDLE4sXyxGLFUsVyxYLEQsSCxJLHosRyxCPVtdLnNsaWNlLEo9e30uaGFzT3duUHJvcGVydHksSz1mdW5jdGlvbih0LGUpe2Zvcih2YXIgbiBpbiBlKUouY2FsbChlLG4pJiYodFtuXT1lW25dKTtmdW5jdGlvbiByKCl7dGhpcy5jb25zdHJ1Y3Rvcj10fXJldHVybiByLnByb3RvdHlwZT1lLnByb3RvdHlwZSx0LnByb3RvdHlwZT1uZXcgcix0Ll9fc3VwZXJfXz1lLnByb3RvdHlwZSx0fSxRPVtdLmluZGV4T2Z8fGZ1bmN0aW9uKHQpe2Zvcih2YXIgZT0wLG49dGhpcy5sZW5ndGg7ZTxuO2UrKylpZihlIGluIHRoaXMmJnRoaXNbZV09PT10KXJldHVybiBlO3JldHVybi0xfTtmdW5jdGlvbiBWKCl7fWZvcihmPXtjbGFzc05hbWU6IiIsY2F0Y2h1cFRpbWU6MTAwLGluaXRpYWxSYXRlOi4wMyxtaW5UaW1lOjI1MCxnaG9zdFRpbWU6MTAwLG1heFByb2dyZXNzUGVyRnJhbWU6MjAsZWFzZUZhY3RvcjoxLjI1LHN0YXJ0T25QYWdlTG9hZDohMCxyZXN0YXJ0T25QdXNoU3RhdGU6ITAscmVzdGFydE9uUmVxdWVzdEFmdGVyOjUwMCx0YXJnZXQ6ImJvZHkiLGVsZW1lbnRzOntjaGVja0ludGVydmFsOjEwMCxzZWxlY3RvcnM6WyJib2R5Il19LGV2ZW50TGFnOnttaW5TYW1wbGVzOjEwLHNhbXBsZUNvdW50OjMsbGFnVGhyZXNob2xkOjN9LGFqYXg6e3RyYWNrTWV0aG9kczpbIkdFVCJdLHRyYWNrV2ViU29ja2V0czohMCxpZ25vcmVVUkxzOltdfX0sTD1mdW5jdGlvbigpe3ZhciB0O3JldHVybiBudWxsIT0odD0idW5kZWZpbmVkIiE9dHlwZW9mIHBlcmZvcm1hbmNlJiZudWxsIT09cGVyZm9ybWFuY2UmJiJmdW5jdGlvbiI9PXR5cGVvZiBwZXJmb3JtYW5jZS5ub3c/cGVyZm9ybWFuY2Uubm93KCk6dm9pZCAwKT90OituZXcgRGF0ZX0sVD13aW5kb3cucmVxdWVzdEFuaW1hdGlvbkZyYW1lfHx3aW5kb3cubW96UmVxdWVzdEFuaW1hdGlvbkZyYW1lfHx3aW5kb3cud2Via2l0UmVxdWVzdEFuaW1hdGlvbkZyYW1lfHx3aW5kb3cubXNSZXF1ZXN0QW5pbWF0aW9uRnJhbWUsaD13aW5kb3cuY2FuY2VsQW5pbWF0aW9uRnJhbWV8fHdpbmRvdy5tb3pDYW5jZWxBbmltYXRpb25GcmFtZSxhPWZ1bmN0aW9uKHQsZSxuKXtyZXR1cm4oImZ1bmN0aW9uIj09dHlwZW9mIHQuYWRkRXZlbnRMaXN0ZW5lcj90LmFkZEV2ZW50TGlzdGVuZXIoZSxuLCExKTp2b2lkIDApfHwodFsib24iK2VdPW4pfSxudWxsPT1UJiYoVD1mdW5jdGlvbih0KXtyZXR1cm4gc2V0VGltZW91dCh0LDUwKX0saD1mdW5jdGlvbih0KXtyZXR1cm4gY2xlYXJUaW1lb3V0KHQpfSksaj1mdW5jdGlvbihlKXt2YXIgbj1MKCkscj1mdW5jdGlvbigpe3ZhciB0PUwoKS1uO3JldHVybiAzMzw9dD8obj1MKCksZSh0LGZ1bmN0aW9uKCl7cmV0dXJuIFQocil9KSk6c2V0VGltZW91dChyLDMzLXQpfTtyZXR1cm4gcigpfSxSPWZ1bmN0aW9uKCl7dmFyIHQ9YXJndW1lbnRzWzBdLGU9YXJndW1lbnRzWzFdLG49Mzw9YXJndW1lbnRzLmxlbmd0aD9CLmNhbGwoYXJndW1lbnRzLDIpOltdO3JldHVybiJmdW5jdGlvbiI9PXR5cGVvZiB0W2VdP3RbZV0uYXBwbHkodCxuKTp0W2VdfSxkPWZ1bmN0aW9uKCl7Zm9yKHZhciB0LGUsbixyPWFyZ3VtZW50c1swXSxzPTI8PWFyZ3VtZW50cy5sZW5ndGg/Qi5jYWxsKGFyZ3VtZW50cywxKTpbXSxvPTAsaT1zLmxlbmd0aDtvPGk7bysrKWlmKGU9c1tvXSlmb3IodCBpbiBlKUouY2FsbChlLHQpJiYobj1lW3RdLG51bGwhPXJbdF0mJiJvYmplY3QiPT10eXBlb2Ygclt0XSYmbnVsbCE9biYmIm9iamVjdCI9PXR5cGVvZiBuP2Qoclt0XSxuKTpyW3RdPW4pO3JldHVybiByfSxwPWZ1bmN0aW9uKHQpe2Zvcih2YXIgZSxuLHI9ZT0wLHM9MCxvPXQubGVuZ3RoO3M8bztzKyspbj10W3NdLHIrPU1hdGguYWJzKG4pLGUrKztyZXR1cm4gci9lfSxtPWZ1bmN0aW9uKHQsZSl7dmFyIG4scjtpZihudWxsPT10JiYodD0ib3B0aW9ucyIpLG51bGw9PWUmJihlPSEwKSxyPWRvY3VtZW50LnF1ZXJ5U2VsZWN0b3IoIltkYXRhLXBhY2UtIit0KyJdIikpe2lmKG49ci5nZXRBdHRyaWJ1dGUoImRhdGEtcGFjZS0iK3QpLCFlKXJldHVybiBuO3RyeXtyZXR1cm4gSlNPTi5wYXJzZShuKX1jYXRjaCh0KXtyZXR1cm4idW5kZWZpbmVkIiE9dHlwZW9mIGNvbnNvbGUmJm51bGwhPT1jb25zb2xlP2NvbnNvbGUuZXJyb3IoIkVycm9yIHBhcnNpbmcgaW5saW5lIHBhY2Ugb3B0aW9ucyIsdCk6dm9pZCAwfX19LFYucHJvdG90eXBlLm9uPWZ1bmN0aW9uKHQsZSxuLHIpe3ZhciBzO3JldHVybiBudWxsPT1yJiYocj0hMSksbnVsbD09dGhpcy5iaW5kaW5ncyYmKHRoaXMuYmluZGluZ3M9e30pLG51bGw9PShzPXRoaXMuYmluZGluZ3MpW3RdJiYoc1t0XT1bXSksdGhpcy5iaW5kaW5nc1t0XS5wdXNoKHtoYW5kbGVyOmUsY3R4Om4sb25jZTpyfSl9LFYucHJvdG90eXBlLm9uY2U9ZnVuY3Rpb24odCxlLG4pe3JldHVybiB0aGlzLm9uKHQsZSxuLCEwKX0sVi5wcm90b3R5cGUub2ZmPWZ1bmN0aW9uKHQsZSl7dmFyIG4scixzO2lmKG51bGwhPShudWxsIT0ocj10aGlzLmJpbmRpbmdzKT9yW3RdOnZvaWQgMCkpe2lmKG51bGw9PWUpcmV0dXJuIGRlbGV0ZSB0aGlzLmJpbmRpbmdzW3RdO2ZvcihuPTAscz1bXTtuPHRoaXMuYmluZGluZ3NbdF0ubGVuZ3RoOyl0aGlzLmJpbmRpbmdzW3RdW25dLmhhbmRsZXI9PT1lP3MucHVzaCh0aGlzLmJpbmRpbmdzW3RdLnNwbGljZShuLDEpKTpzLnB1c2gobisrKTtyZXR1cm4gc319LFYucHJvdG90eXBlLnRyaWdnZXI9ZnVuY3Rpb24oKXt2YXIgdCxlLG4scixzLG8saT1hcmd1bWVudHNbMF0sYT0yPD1hcmd1bWVudHMubGVuZ3RoP0IuY2FsbChhcmd1bWVudHMsMSk6W107aWYobnVsbCE9KHI9dGhpcy5iaW5kaW5ncykmJnJbaV0pe2ZvcihuPTAsbz1bXTtuPHRoaXMuYmluZGluZ3NbaV0ubGVuZ3RoOyllPShzPXRoaXMuYmluZGluZ3NbaV1bbl0pLmhhbmRsZXIsdD1zLmN0eCxzPXMub25jZSxlLmFwcGx5KG51bGwhPXQ/dDp0aGlzLGEpLHM/by5wdXNoKHRoaXMuYmluZGluZ3NbaV0uc3BsaWNlKG4sMSkpOm8ucHVzaChuKyspO3JldHVybiBvfX0sRz1WLHk9d2luZG93LlBhY2V8fHt9LHdpbmRvdy5QYWNlPXksZCh5LEcucHJvdG90eXBlKSxQPXkub3B0aW9ucz1kKHt9LGYsd2luZG93LnBhY2VPcHRpb25zLG0oKSksVz0wLEQ9KEk9WyJhamF4IiwiZG9jdW1lbnQiLCJldmVudExhZyIsImVsZW1lbnRzIl0pLmxlbmd0aDtXPEQ7VysrKSEwPT09UFtBPUlbV11dJiYoUFtBXT1mW0FdKTtmdW5jdGlvbiBZKCl7cmV0dXJuIFkuX19zdXBlcl9fLmNvbnN0cnVjdG9yLmFwcGx5KHRoaXMsYXJndW1lbnRzKX1mdW5jdGlvbiBaKCl7dGhpcy5wcm9ncmVzcz0wfWZ1bmN0aW9uICQoKXt0aGlzLmJpbmRpbmdzPXt9fWZ1bmN0aW9uIHR0KCl7dmFyIGUsbz10aGlzO3R0Ll9fc3VwZXJfXy5jb25zdHJ1Y3Rvci5hcHBseSh0aGlzLGFyZ3VtZW50cyksZT1mdW5jdGlvbihyKXt2YXIgcz1yLm9wZW47cmV0dXJuIHIub3Blbj1mdW5jdGlvbih0LGUsbil7cmV0dXJuIE0odCkmJm8udHJpZ2dlcigicmVxdWVzdCIse3R5cGU6dCx1cmw6ZSxyZXF1ZXN0OnJ9KSxzLmFwcGx5KHIsYXJndW1lbnRzKX19LHdpbmRvdy5YTUxIdHRwUmVxdWVzdD1mdW5jdGlvbih0KXt0PW5ldyBVKHQpO3JldHVybiBlKHQpLHR9O3RyeXtnKHdpbmRvdy5YTUxIdHRwUmVxdWVzdCxVKX1jYXRjaCh0KXt9aWYobnVsbCE9Ril7d2luZG93LlhEb21haW5SZXF1ZXN0PWZ1bmN0aW9uKCl7dmFyIHQ9bmV3IEY7cmV0dXJuIGUodCksdH07dHJ5e2cod2luZG93LlhEb21haW5SZXF1ZXN0LEYpfWNhdGNoKHQpe319aWYobnVsbCE9XyYmUC5hamF4LnRyYWNrV2ViU29ja2V0cyl7d2luZG93LldlYlNvY2tldD1mdW5jdGlvbih0LGUpe3ZhciBuPW51bGwhPWU/bmV3IF8odCxlKTpuZXcgXyh0KTtyZXR1cm4gTSgic29ja2V0IikmJm8udHJpZ2dlcigicmVxdWVzdCIse3R5cGU6InNvY2tldCIsdXJsOnQscHJvdG9jb2xzOmUscmVxdWVzdDpufSksbn07dHJ5e2cod2luZG93LldlYlNvY2tldCxfKX1jYXRjaCh0KXt9fX1mdW5jdGlvbiBldCgpe3RoaXMuY29tcGxldGU9byh0aGlzLmNvbXBsZXRlLHRoaXMpO3ZhciB0PXRoaXM7dGhpcy5lbGVtZW50cz1bXSxrKCkub24oInJlcXVlc3QiLGZ1bmN0aW9uKCl7cmV0dXJuIHQud2F0Y2guYXBwbHkodCxhcmd1bWVudHMpfSl9ZnVuY3Rpb24gbnQodCl7dmFyIGUsbixyLHM7Zm9yKG51bGw9PXQmJih0PXt9KSx0aGlzLmNvbXBsZXRlPW8odGhpcy5jb21wbGV0ZSx0aGlzKSx0aGlzLmVsZW1lbnRzPVtdLG51bGw9PXQuc2VsZWN0b3JzJiYodC5zZWxlY3RvcnM9W10pLG49MCxyPShzPXQuc2VsZWN0b3JzKS5sZW5ndGg7bjxyO24rKyllPXNbbl0sdGhpcy5lbGVtZW50cy5wdXNoKG5ldyBpKGUsdGhpcy5jb21wbGV0ZSkpfWZ1bmN0aW9uIHJ0KHQsZSl7dGhpcy5zZWxlY3Rvcj10LHRoaXMuY29tcGxldGVDYWxsYmFjaz1lLHRoaXMucHJvZ3Jlc3M9MCx0aGlzLmNoZWNrKCl9ZnVuY3Rpb24gc3QoKXt2YXIgdCxlLG49dGhpczt0aGlzLnByb2dyZXNzPW51bGwhPShlPXRoaXMuc3RhdGVzW2RvY3VtZW50LnJlYWR5U3RhdGVdKT9lOjEwMCx0PWRvY3VtZW50Lm9ucmVhZHlzdGF0ZWNoYW5nZSxkb2N1bWVudC5vbnJlYWR5c3RhdGVjaGFuZ2U9ZnVuY3Rpb24oKXtyZXR1cm4gbnVsbCE9bi5zdGF0ZXNbZG9jdW1lbnQucmVhZHlTdGF0ZV0mJihuLnByb2dyZXNzPW4uc3RhdGVzW2RvY3VtZW50LnJlYWR5U3RhdGVdKSwiZnVuY3Rpb24iPT10eXBlb2YgdD90LmFwcGx5KG51bGwsYXJndW1lbnRzKTp2b2lkIDB9fWZ1bmN0aW9uIG90KHQpe3RoaXMuc291cmNlPXQsdGhpcy5sYXN0PXRoaXMuc2luY2VMYXN0VXBkYXRlPTAsdGhpcy5yYXRlPVAuaW5pdGlhbFJhdGUsdGhpcy5jYXRjaHVwPTAsdGhpcy5wcm9ncmVzcz10aGlzLmxhc3RQcm9ncmVzcz0wLG51bGwhPXRoaXMuc291cmNlJiYodGhpcy5wcm9ncmVzcz1SKHRoaXMuc291cmNlLCJwcm9ncmVzcyIpKX1HPUVycm9yLEsoWSxHKSxuPVksWi5wcm90b3R5cGUuZ2V0RWxlbWVudD1mdW5jdGlvbigpe3ZhciB0O2lmKG51bGw9PXRoaXMuZWwpe2lmKCEodD1kb2N1bWVudC5xdWVyeVNlbGVjdG9yKFAudGFyZ2V0KSkpdGhyb3cgbmV3IG47dGhpcy5lbD1kb2N1bWVudC5jcmVhdGVFbGVtZW50KCJkaXYiKSx0aGlzLmVsLmNsYXNzTmFtZT0icGFjZSBwYWNlLWFjdGl2ZSIsZG9jdW1lbnQuYm9keS5jbGFzc05hbWU9ZG9jdW1lbnQuYm9keS5jbGFzc05hbWUucmVwbGFjZSgvKHBhY2UtZG9uZSApfC8sInBhY2UtcnVubmluZyAiKTt2YXIgZT0iIiE9PVAuY2xhc3NOYW1lPyIgIitQLmNsYXNzTmFtZToiIjt0aGlzLmVsLmlubmVySFRNTD0nPGRpdiBjbGFzcz0icGFjZS1wcm9ncmVzcycrZSsnIj48ZGl2IGNsYXNzPSJwYWNlLXByb2dyZXNzLWlubmVyIj48L2Rpdj48L2Rpdj48ZGl2IGNsYXNzPSJwYWNlLWFjdGl2aXR5Ij48L2Rpdj4nLG51bGwhPXQuZmlyc3RDaGlsZD90Lmluc2VydEJlZm9yZSh0aGlzLmVsLHQuZmlyc3RDaGlsZCk6dC5hcHBlbmRDaGlsZCh0aGlzLmVsKX1yZXR1cm4gdGhpcy5lbH0sWi5wcm90b3R5cGUuZmluaXNoPWZ1bmN0aW9uKCl7dmFyIHQ9dGhpcy5nZXRFbGVtZW50KCk7cmV0dXJuIHQuY2xhc3NOYW1lPXQuY2xhc3NOYW1lLnJlcGxhY2UoInBhY2UtYWN0aXZlIiwicGFjZS1pbmFjdGl2ZSIpLGRvY3VtZW50LmJvZHkuY2xhc3NOYW1lPWRvY3VtZW50LmJvZHkuY2xhc3NOYW1lLnJlcGxhY2UoInBhY2UtcnVubmluZyAiLCJwYWNlLWRvbmUgIil9LFoucHJvdG90eXBlLnVwZGF0ZT1mdW5jdGlvbih0KXtyZXR1cm4gdGhpcy5wcm9ncmVzcz10LHkudHJpZ2dlcigicHJvZ3Jlc3MiLHQpLHRoaXMucmVuZGVyKCl9LFoucHJvdG90eXBlLmRlc3Ryb3k9ZnVuY3Rpb24oKXt0cnl7dGhpcy5nZXRFbGVtZW50KCkucGFyZW50Tm9kZS5yZW1vdmVDaGlsZCh0aGlzLmdldEVsZW1lbnQoKSl9Y2F0Y2godCl7bj10fXJldHVybiB0aGlzLmVsPXZvaWQgMH0sWi5wcm90b3R5cGUucmVuZGVyPWZ1bmN0aW9uKCl7dmFyIHQsZSxuLHIscyxvLGk7aWYobnVsbD09ZG9jdW1lbnQucXVlcnlTZWxlY3RvcihQLnRhcmdldCkpcmV0dXJuITE7Zm9yKHQ9dGhpcy5nZXRFbGVtZW50KCkscj0idHJhbnNsYXRlM2QoIit0aGlzLnByb2dyZXNzKyIlLCAwLCAwKSIscz0wLG89KGk9WyJ3ZWJraXRUcmFuc2Zvcm0iLCJtc1RyYW5zZm9ybSIsInRyYW5zZm9ybSJdKS5sZW5ndGg7czxvO3MrKyllPWlbc10sdC5jaGlsZHJlblswXS5zdHlsZVtlXT1yO3JldHVybighdGhpcy5sYXN0UmVuZGVyZWRQcm9ncmVzc3x8dGhpcy5sYXN0UmVuZGVyZWRQcm9ncmVzc3wwIT09dGhpcy5wcm9ncmVzc3wwKSYmKHQuY2hpbGRyZW5bMF0uc2V0QXR0cmlidXRlKCJkYXRhLXByb2dyZXNzLXRleHQiLCgwfHRoaXMucHJvZ3Jlc3MpKyIlIiksMTAwPD10aGlzLnByb2dyZXNzP249Ijk5Ijoobj10aGlzLnByb2dyZXNzPDEwPyIwIjoiIixuKz0wfHRoaXMucHJvZ3Jlc3MpLHQuY2hpbGRyZW5bMF0uc2V0QXR0cmlidXRlKCJkYXRhLXByb2dyZXNzIiwiIituKSkseS50cmlnZ2VyKCJjaGFuZ2UiLHRoaXMucHJvZ3Jlc3MpLHRoaXMubGFzdFJlbmRlcmVkUHJvZ3Jlc3M9dGhpcy5wcm9ncmVzc30sWi5wcm90b3R5cGUuZG9uZT1mdW5jdGlvbigpe3JldHVybiAxMDA8PXRoaXMucHJvZ3Jlc3N9LGM9WiwkLnByb3RvdHlwZS50cmlnZ2VyPWZ1bmN0aW9uKHQsZSl7dmFyIG4scixzLG8saTtpZihudWxsIT10aGlzLmJpbmRpbmdzW3RdKXtmb3IoaT1bXSxyPTAscz0obz10aGlzLmJpbmRpbmdzW3RdKS5sZW5ndGg7cjxzO3IrKyluPW9bcl0saS5wdXNoKG4uY2FsbCh0aGlzLGUpKTtyZXR1cm4gaX19LCQucHJvdG90eXBlLm9uPWZ1bmN0aW9uKHQsZSl7dmFyIG47cmV0dXJuIG51bGw9PShuPXRoaXMuYmluZGluZ3MpW3RdJiYoblt0XT1bXSksdGhpcy5iaW5kaW5nc1t0XS5wdXNoKGUpfSxtPSQsVT13aW5kb3cuWE1MSHR0cFJlcXVlc3QsRj13aW5kb3cuWERvbWFpblJlcXVlc3QsXz13aW5kb3cuV2ViU29ja2V0LGc9ZnVuY3Rpb24odCxlKXt2YXIgbixyPVtdO2ZvcihuIGluIGUucHJvdG90eXBlKXRyeXtudWxsPT10W25dJiYiZnVuY3Rpb24iIT10eXBlb2YgZVtuXT8iZnVuY3Rpb24iPT10eXBlb2YgT2JqZWN0LmRlZmluZVByb3BlcnR5P3IucHVzaChPYmplY3QuZGVmaW5lUHJvcGVydHkodCxuLHtnZXQ6ZnVuY3Rpb24odCl7cmV0dXJuIGZ1bmN0aW9uKCl7cmV0dXJuIGUucHJvdG90eXBlW3RdfX0obiksY29uZmlndXJhYmxlOiEwLGVudW1lcmFibGU6ITB9KSk6ci5wdXNoKHRbbl09ZS5wcm90b3R5cGVbbl0pOnIucHVzaCh2b2lkIDApfWNhdGNoKHQpezB9cmV0dXJuIHJ9LHE9W10seS5pZ25vcmU9ZnVuY3Rpb24oKXt2YXIgdD1hcmd1bWVudHNbMF0sZT0yPD1hcmd1bWVudHMubGVuZ3RoP0IuY2FsbChhcmd1bWVudHMsMSk6W107cmV0dXJuIHEudW5zaGlmdCgiaWdub3JlIiksZT10LmFwcGx5KG51bGwsZSkscS5zaGlmdCgpLGV9LHkudHJhY2s9ZnVuY3Rpb24oKXt2YXIgdD1hcmd1bWVudHNbMF0sZT0yPD1hcmd1bWVudHMubGVuZ3RoP0IuY2FsbChhcmd1bWVudHMsMSk6W107cmV0dXJuIHEudW5zaGlmdCgidHJhY2siKSxlPXQuYXBwbHkobnVsbCxlKSxxLnNoaWZ0KCksZX0sTT1mdW5jdGlvbih0KXtpZihudWxsPT10JiYodD0iR0VUIiksInRyYWNrIj09PXFbMF0pcmV0dXJuImZvcmNlIjtpZighcS5sZW5ndGgmJlAuYWpheCl7aWYoInNvY2tldCI9PT10JiZQLmFqYXgudHJhY2tXZWJTb2NrZXRzKXJldHVybiEwO2lmKHQ9dC50b1VwcGVyQ2FzZSgpLDA8PVEuY2FsbChQLmFqYXgudHJhY2tNZXRob2RzLHQpKXJldHVybiEwfXJldHVybiExfSxLKHR0LG0pLHQ9dHQsWD1udWxsLEU9ZnVuY3Rpb24odCl7Zm9yKHZhciBlLG49UC5hamF4Lmlnbm9yZVVSTHMscj0wLHM9bi5sZW5ndGg7cjxzO3IrKylpZigic3RyaW5nIj09dHlwZW9mKGU9bltyXSkpe2lmKC0xIT09dC5pbmRleE9mKGUpKXJldHVybiEwfWVsc2UgaWYoZS50ZXN0KHQpKXJldHVybiEwO3JldHVybiExfSwoaz1mdW5jdGlvbigpe3JldHVybiBudWxsPT1YJiYoWD1uZXcgdCksWH0pKCkub24oInJlcXVlc3QiLGZ1bmN0aW9uKHQpe3ZhciBvLGk9dC50eXBlLGE9dC5yZXF1ZXN0LGU9dC51cmw7aWYoIUUoZSkpcmV0dXJuIHkucnVubmluZ3x8ITE9PT1QLnJlc3RhcnRPblJlcXVlc3RBZnRlciYmImZvcmNlIiE9PU0oaSk/dm9pZCAwOihvPWFyZ3VtZW50cywiYm9vbGVhbiI9PXR5cGVvZihlPVAucmVzdGFydE9uUmVxdWVzdEFmdGVyfHwwKSYmKGU9MCksc2V0VGltZW91dChmdW5jdGlvbigpe3ZhciB0LGUsbixyLHM9InNvY2tldCI9PT1pP2EucmVhZHlTdGF0ZTwxOjA8KHM9YS5yZWFkeVN0YXRlKSYmczw0O2lmKHMpe2Zvcih5LnJlc3RhcnQoKSxyPVtdLHQ9MCxlPShuPXkuc291cmNlcykubGVuZ3RoO3Q8ZTt0Kyspe2lmKChBPW5bdF0paW5zdGFuY2VvZiB1KXtBLndhdGNoLmFwcGx5KEEsbyk7YnJlYWt9ci5wdXNoKHZvaWQgMCl9cmV0dXJuIHJ9fSxlKSl9KSxldC5wcm90b3R5cGUud2F0Y2g9ZnVuY3Rpb24odCl7dmFyIGU9dC50eXBlLG49dC5yZXF1ZXN0LHQ9dC51cmw7aWYoIUUodCkpcmV0dXJuIG49bmV3KCJzb2NrZXQiPT09ZT9yOnMpKG4sdGhpcy5jb21wbGV0ZSksdGhpcy5lbGVtZW50cy5wdXNoKG4pfSxldC5wcm90b3R5cGUuY29tcGxldGU9ZnVuY3Rpb24oZSl7cmV0dXJuIHRoaXMuZWxlbWVudHM9dGhpcy5lbGVtZW50cy5maWx0ZXIoZnVuY3Rpb24odCl7cmV0dXJuIHQhPT1lfSl9LHU9ZXQscz1mdW5jdGlvbihlLG4pe3ZhciB0LHIscyxvLGk9dGhpcztpZih0aGlzLnByb2dyZXNzPTAsbnVsbCE9d2luZG93LlByb2dyZXNzRXZlbnQpZm9yKGEoZSwicHJvZ3Jlc3MiLGZ1bmN0aW9uKHQpe3JldHVybiB0Lmxlbmd0aENvbXB1dGFibGU/aS5wcm9ncmVzcz0xMDAqdC5sb2FkZWQvdC50b3RhbDppLnByb2dyZXNzPWkucHJvZ3Jlc3MrKDEwMC1pLnByb2dyZXNzKS8yfSksdD0wLHI9KG89WyJsb2FkIiwiYWJvcnQiLCJ0aW1lb3V0IiwiZXJyb3IiXSkubGVuZ3RoO3Q8cjt0KyspYShlLG9bdF0sZnVuY3Rpb24oKXtyZXR1cm4gbihpKSxpLnByb2dyZXNzPTEwMH0pO2Vsc2Ugcz1lLm9ucmVhZHlzdGF0ZWNoYW5nZSxlLm9ucmVhZHlzdGF0ZWNoYW5nZT1mdW5jdGlvbigpe3ZhciB0O3JldHVybiAwPT09KHQ9ZS5yZWFkeVN0YXRlKXx8ND09PXQ/KG4oaSksaS5wcm9ncmVzcz0xMDApOjM9PT1lLnJlYWR5U3RhdGUmJihpLnByb2dyZXNzPTUwKSwiZnVuY3Rpb24iPT10eXBlb2Ygcz9zLmFwcGx5KG51bGwsYXJndW1lbnRzKTp2b2lkIDB9fSxyPWZ1bmN0aW9uKHQsZSl7Zm9yKHZhciBuLHI9dGhpcyxzPXRoaXMucHJvZ3Jlc3M9MCxvPShuPVsiZXJyb3IiLCJvcGVuIl0pLmxlbmd0aDtzPG87cysrKWEodCxuW3NdLGZ1bmN0aW9uKCl7cmV0dXJuIGUociksci5wcm9ncmVzcz0xMDB9KX0sbnQucHJvdG90eXBlLmNvbXBsZXRlPWZ1bmN0aW9uKGUpe3JldHVybiB0aGlzLmVsZW1lbnRzPXRoaXMuZWxlbWVudHMuZmlsdGVyKGZ1bmN0aW9uKHQpe3JldHVybiB0IT09ZX0pfSxHPW50LHJ0LnByb3RvdHlwZS5jaGVjaz1mdW5jdGlvbigpe3ZhciB0PXRoaXM7cmV0dXJuIGRvY3VtZW50LnF1ZXJ5U2VsZWN0b3IodGhpcy5zZWxlY3Rvcik/dGhpcy5kb25lKCk6c2V0VGltZW91dChmdW5jdGlvbigpe3JldHVybiB0LmNoZWNrKCl9LFAuZWxlbWVudHMuY2hlY2tJbnRlcnZhbCl9LHJ0LnByb3RvdHlwZS5kb25lPWZ1bmN0aW9uKCl7cmV0dXJuIHRoaXMuY29tcGxldGVDYWxsYmFjayh0aGlzKSx0aGlzLmNvbXBsZXRlQ2FsbGJhY2s9bnVsbCx0aGlzLnByb2dyZXNzPTEwMH0saT1ydCxzdC5wcm90b3R5cGUuc3RhdGVzPXtsb2FkaW5nOjAsaW50ZXJhY3RpdmU6NTAsY29tcGxldGU6MTAwfSxLPXN0LG09ZnVuY3Rpb24oKXt2YXIgZSxuLHIscyxvLGk9dGhpczt0aGlzLnByb2dyZXNzPTAsbz1bXSxzPTAscj1MKCksbj1zZXRJbnRlcnZhbChmdW5jdGlvbigpe3ZhciB0PUwoKS1yLTUwO3JldHVybiByPUwoKSxvLnB1c2godCksby5sZW5ndGg+UC5ldmVudExhZy5zYW1wbGVDb3VudCYmby5zaGlmdCgpLGU9cChvKSwrK3M+PVAuZXZlbnRMYWcubWluU2FtcGxlcyYmZTxQLmV2ZW50TGFnLmxhZ1RocmVzaG9sZD8oaS5wcm9ncmVzcz0xMDAsY2xlYXJJbnRlcnZhbChuKSk6aS5wcm9ncmVzcz0zLyhlKzMpKjEwMH0sNTApfSxvdC5wcm90b3R5cGUudGljaz1mdW5jdGlvbih0LGUpe3JldHVybiBudWxsPT1lJiYoZT1SKHRoaXMuc291cmNlLCJwcm9ncmVzcyIpKSwxMDA8PWUmJih0aGlzLmRvbmU9ITApLGU9PT10aGlzLmxhc3Q/dGhpcy5zaW5jZUxhc3RVcGRhdGUrPXQ6KHRoaXMuc2luY2VMYXN0VXBkYXRlJiYodGhpcy5yYXRlPShlLXRoaXMubGFzdCkvdGhpcy5zaW5jZUxhc3RVcGRhdGUpLHRoaXMuY2F0Y2h1cD0oZS10aGlzLnByb2dyZXNzKS9QLmNhdGNodXBUaW1lLHRoaXMuc2luY2VMYXN0VXBkYXRlPTAsdGhpcy5sYXN0PWUpLGU+dGhpcy5wcm9ncmVzcyYmKHRoaXMucHJvZ3Jlc3MrPXRoaXMuY2F0Y2h1cCp0KSxlPTEtTWF0aC5wb3codGhpcy5wcm9ncmVzcy8xMDAsUC5lYXNlRmFjdG9yKSx0aGlzLnByb2dyZXNzKz1lKnRoaXMucmF0ZSp0LHRoaXMucHJvZ3Jlc3M9TWF0aC5taW4odGhpcy5sYXN0UHJvZ3Jlc3MrUC5tYXhQcm9ncmVzc1BlckZyYW1lLHRoaXMucHJvZ3Jlc3MpLHRoaXMucHJvZ3Jlc3M9TWF0aC5tYXgoMCx0aGlzLnByb2dyZXNzKSx0aGlzLnByb2dyZXNzPU1hdGgubWluKDEwMCx0aGlzLnByb2dyZXNzKSx0aGlzLmxhc3RQcm9ncmVzcz10aGlzLnByb2dyZXNzLHRoaXMucHJvZ3Jlc3N9LHY9b3QsYj1lPU49dz1PPUM9bnVsbCx5LnJ1bm5pbmc9ITEsUz1mdW5jdGlvbigpe2lmKFAucmVzdGFydE9uUHVzaFN0YXRlKXJldHVybiB5LnJlc3RhcnQoKX0sbnVsbCE9d2luZG93Lmhpc3RvcnkucHVzaFN0YXRlJiYoSD13aW5kb3cuaGlzdG9yeS5wdXNoU3RhdGUsd2luZG93Lmhpc3RvcnkucHVzaFN0YXRlPWZ1bmN0aW9uKCl7cmV0dXJuIFMoKSxILmFwcGx5KHdpbmRvdy5oaXN0b3J5LGFyZ3VtZW50cyl9KSxudWxsIT13aW5kb3cuaGlzdG9yeS5yZXBsYWNlU3RhdGUmJih6PXdpbmRvdy5oaXN0b3J5LnJlcGxhY2VTdGF0ZSx3aW5kb3cuaGlzdG9yeS5yZXBsYWNlU3RhdGU9ZnVuY3Rpb24oKXtyZXR1cm4gUygpLHouYXBwbHkod2luZG93Lmhpc3RvcnksYXJndW1lbnRzKX0pLGw9e2FqYXg6dSxlbGVtZW50czpHLGRvY3VtZW50OkssZXZlbnRMYWc6bX0sKHg9ZnVuY3Rpb24oKXt2YXIgdCxlLG4scixzLG8saSxhO2Zvcih5LnNvdXJjZXM9Qz1bXSxlPTAscj0obz1bImFqYXgiLCJlbGVtZW50cyIsImRvY3VtZW50IiwiZXZlbnRMYWciXSkubGVuZ3RoO2U8cjtlKyspITEhPT1QW3Q9b1tlXV0mJkMucHVzaChuZXcgbFt0XShQW3RdKSk7Zm9yKG49MCxzPShhPW51bGwhPShpPVAuZXh0cmFTb3VyY2VzKT9pOltdKS5sZW5ndGg7bjxzO24rKylBPWFbbl0sQy5wdXNoKG5ldyBBKFApKTtyZXR1cm4geS5iYXI9dz1uZXcgYyxPPVtdLE49bmV3IHZ9KSgpLHkuc3RvcD1mdW5jdGlvbigpe3JldHVybiB5LnRyaWdnZXIoInN0b3AiKSx5LnJ1bm5pbmc9ITEsdy5kZXN0cm95KCksYj0hMCxudWxsIT1lJiYoImZ1bmN0aW9uIj09dHlwZW9mIGgmJmgoZSksZT1udWxsKSx4KCl9LHkucmVzdGFydD1mdW5jdGlvbigpe3JldHVybiB5LnRyaWdnZXIoInJlc3RhcnQiKSx5LnN0b3AoKSx5LnN0YXJ0KCl9LHkuZ289ZnVuY3Rpb24oKXt2YXIgbTtyZXR1cm4geS5ydW5uaW5nPSEwLHcucmVuZGVyKCksbT1MKCksYj0hMSxlPWooZnVuY3Rpb24odCxlKXt3LnByb2dyZXNzO2Zvcih2YXIgbixyLHMsbyxpLGEsdSxjLGwscCxoPWE9MCxmPSEwLGQ9dT0wLGc9Qy5sZW5ndGg7dTxnO2Q9Kyt1KWZvcihBPUNbZF0saT1udWxsIT1PW2RdP09bZF06T1tkXT1bXSxzPWM9MCxsPShyPW51bGwhPShwPUEuZWxlbWVudHMpP3A6W0FdKS5sZW5ndGg7YzxsO3M9KytjKW89cltzXSxmJj0obz1udWxsIT1pW3NdP2lbc106aVtzXT1uZXcgdihvKSkuZG9uZSxvLmRvbmV8fChoKyssYSs9by50aWNrKHQpKTtyZXR1cm4gbj1hL2gsdy51cGRhdGUoTi50aWNrKHQsbikpLHcuZG9uZSgpfHxmfHxiPyh3LnVwZGF0ZSgxMDApLHkudHJpZ2dlcigiZG9uZSIpLHNldFRpbWVvdXQoZnVuY3Rpb24oKXtyZXR1cm4gdy5maW5pc2goKSx5LnJ1bm5pbmc9ITEseS50cmlnZ2VyKCJoaWRlIil9LE1hdGgubWF4KFAuZ2hvc3RUaW1lLE1hdGgubWF4KFAubWluVGltZS0oTCgpLW0pLDApKSkpOmUoKX0pfSx5LnN0YXJ0PWZ1bmN0aW9uKHQpe2QoUCx0KSx5LnJ1bm5pbmc9ITA7dHJ5e3cucmVuZGVyKCl9Y2F0Y2godCl7bj10fXJldHVybiBkb2N1bWVudC5xdWVyeVNlbGVjdG9yKCIucGFjZSIpPyh5LnRyaWdnZXIoInN0YXJ0IikseS5nbygpKTpzZXRUaW1lb3V0KHkuc3RhcnQsNTApfSwiZnVuY3Rpb24iPT10eXBlb2YgZGVmaW5lJiZkZWZpbmUuYW1kP2RlZmluZShmdW5jdGlvbigpe3JldHVybiB5fSk6Im9iamVjdCI9PXR5cGVvZiBleHBvcnRzP21vZHVsZS5leHBvcnRzPXk6UC5zdGFydE9uUGFnZUxvYWQmJnkuc3RhcnQoKX0pLmNhbGwodGhpcyk7"></script>
<link href='data:application/manifest+json;charset=utf-8,%7B%0A%20%20%20%20%22name%22%3A%20%22VOTYMEHJONG%22%2C%0A%20%20%20%20%22short_name%22%3A%20%22VOTYMEHJONG%22%2C%0A%20%20%20%20%22lang%22%3A%20%22pt-BR%22%2C%0A%20%20%20%20%22description%22%3A%20%22VOTYMEHJONG%20ADAPT%C3%81VEL.%20APLICATIVO%20BY%20DEVELOPER%20DAVIDSONBPE.%22%2C%0A%20%20%20%20%22theme_color%22%3A%20%22%23222%22%2C%0A%20%20%20%20%22background_color%22%3A%20%22%23222%22%2C%0A%20%20%20%20%22display%22%3A%20%22standalone%22%2C%0A%20%20%20%20%22orientation%22%3A%20%22any%22%2C%0A%20%20%20%20%22start_url%22%3A%20%22https%3A%2F%2Fkexel-git.static.hf.space%2FVotyMehjong.html%3FInstalle%3Dvotymehjong%22%2C%0A%20%20%20%20%22id%22%3A%20%22votymehjong%22%2C%0A%20%20%20%20%22iarc_rating_id%22%3A%20%221455-UI88-NH10-NM36-KK89-JD12-PP23-GT20-2025%22%2C%0A%20%20%20%20%22dir%22%3A%20%22auto%22%2C%0A%20%20%20%20%22display_override%22%3A%20%5B%0A%20%20%20%20%20%20%20%20%22standalone%22%0A%20%20%20%20%5D%2C%0A%20%20%20%20%22categories%22%3A%20%5B%0A%20%20%20%20%20%20%20%20%22books%22%0A%20%20%20%20%5D%2C%0A%20%20%20%20%22icons%22%3A%20%5B%0A%20%20%20%20%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%22src%22%3A%20%22https%3A%2F%2Fplacehold.co%2F144%2F3b2d50%2Fbdbac1.png%3Ffont%3Droboto%26text%3DVOTY.MEHJONG%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22sizes%22%3A%20%22144x144%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22type%22%3A%20%22image%2Fpng%22%0A%20%20%20%20%20%20%20%20%7D%2C%0A%20%20%20%20%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%22src%22%3A%20%22https%3A%2F%2Fplacehold.co%2F36%2F3b2d50%2Fbdbac1.png%3Ffont%3Droboto%26text%3DVOTY.MEHJONG%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22sizes%22%3A%20%2236x36%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22type%22%3A%20%22image%2Fpng%22%0A%20%20%20%20%20%20%20%20%7D%2C%0A%20%20%20%20%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%22src%22%3A%20%22https%3A%2F%2Fplacehold.co%2F48%2F3b2d50%2Fbdbac1.png%3Ffont%3Droboto%26text%3DVOTY.MEHJONG%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22sizes%22%3A%20%2248x48%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22type%22%3A%20%22image%2Fpng%22%0A%20%20%20%20%20%20%20%20%7D%2C%0A%20%20%20%20%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%22src%22%3A%20%22https%3A%2F%2Fplacehold.co%2F72%2F3b2d50%2Fbdbac1.png%3Ffont%3Droboto%26text%3DVOTY.MEHJONG%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22sizes%22%3A%20%2272x72%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22type%22%3A%20%22image%2Fpng%22%0A%20%20%20%20%20%20%20%20%7D%2C%0A%20%20%20%20%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%22src%22%3A%20%22https%3A%2F%2Fplacehold.co%2F96%2F3b2d50%2Fbdbac1.png%3Ffont%3Droboto%26text%3DVOTY.MEHJONG%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22sizes%22%3A%20%2296x96%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22type%22%3A%20%22image%2Fpng%22%0A%20%20%20%20%20%20%20%20%7D%2C%0A%20%20%20%20%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%22src%22%3A%20%22https%3A%2F%2Fplacehold.co%2F192%2F3b2d50%2Fbdbac1.png%3Ffont%3Droboto%26text%3DVOTY.MEHJONG%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22sizes%22%3A%20%22192x192%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22type%22%3A%20%22image%2Fpng%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22purpose%22%3A%20%22any%20maskable%20monochrome%22%0A%20%20%20%20%20%20%20%20%7D%2C%0A%20%20%20%20%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%22src%22%3A%20%22https%3A%2F%2Fplacehold.co%2F512%2F3b2d50%2Fbdbac1.png%3Ffont%3Droboto%26text%3DVOTY.MEHJONG%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22sizes%22%3A%20%22512x512%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22type%22%3A%20%22image%2Fpng%22%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%5D%2C%0A%20%20%20%20%22screenshots%22%3A%20%5B%0A%20%20%20%20%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%22src%22%3A%20%22https%3A%2F%2Fplacehold.co%2F512%2F3b2d50%2Fbdbac1.png%3Ffont%3Droboto%26text%3DVOTY.MEHJONG%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22sizes%22%3A%20%22512x512%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22type%22%3A%20%22image%2Fpng%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22platform%22%3A%20%22wide%22%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%5D%0A%7D' rel='manifest'/>
<script> if (navigator.serviceWorker) { navigator.serviceWorker.register } </script>
<script src="https://cdn.jsdelivr.net/gh/davserv/d-framework@refs/heads/cdn.tailwindcss/tailwindcss.js"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@6/css/all.min.css"/>
<style>
@keyframes tileHover {
0% { transform: translateY(0); }
50% { transform: translateY(-5px); }
100% { transform: translateY(0); }
}
.tile-hover:hover {
animation: tileHover 0.3s ease;
transform: translateY(-5px);
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.2);
}
.tile-selected {
transform: translateY(-15px);
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.2);
z-index: 10;
}
.tile-matched {
animation: fadeOut 0.5s ease forwards;
}
@keyframes fadeOut {
to {
opacity: 0;
transform: scale(0.8);
}
}
.board-container {
perspective: 1000px;
}
.tile {
transition: all 0.3s ease;
transform-style: preserve-3d;
}
.tile-inner {
position: relative;
width: 100%;
height: 100%;
transform-style: preserve-3d;
}
.tile-front, .tile-back {
position: absolute;
width: 100%;
height: 100%;
backface-visibility: hidden;
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
}
.tile-front {
background: linear-gradient(145deg, #f0f0f0, #ffffff);
transform: rotateY(180deg);
}
.tile-back {
background: linear-gradient(145deg, #4f46e5, #7c3aed);
color: white;
}
.flipped {
transform: rotateY(180deg);
}
.level-complete {
animation: pulse 2s infinite;
}
@keyframes pulse {
0% { transform: scale(1); }
50% { transform: scale(1.05); }
100% { transform: scale(1); }
}
/* Mobile-specific styles */
@media (max-width: 640px) {
.tile-front span, .tile-back i {
font-size: 50px !important;
}
.header-container {
flex-direction: column;
align-items: center;
gap: 1rem;
}
.stats-container {
width: 100%;
justify-content: space-between;
}
.controls-container {
flex-direction: column;
gap: 0.5rem;
}
.controls-container > div {
width: 100%;
}
.controls-container button {
width: 100%;
padding: 0.5rem;
}
.modal-content {
width: 90%;
padding: 1.5rem;
}
}
.text-mtg{
font-size: 50px !important;
}
</style>
</head>
<body class="bg-gray-100 min-h-screen font-sans">
<div class="container mx-auto px-2 sm:px-4 py-4 sm:py-8">
<!-- Header - Made responsive with flex column on mobile -->
<header class="header-container flex flex-col sm:flex-row justify-between items-center mb-4 sm:mb-8">
<div class="flex items-center mb-2 sm:mb-0">
<i class="fa-brands fa-fort-awesome text-3xl sm:text-4xl text-purple-600 mr-2 sm:mr-3"></i>
<h1 class="text-2xl sm:text-3xl font-bold text-gray-800 uppercase">Voty Mehjong</h1>
</div>
<div class="stats-container flex items-center space-x-2 sm:space-x-4 w-full sm:w-auto justify-center sm:justify-start">
<div class="bg-white rounded-lg shadow p-2 sm:p-3 flex items-center text-sm sm:text-base">
<i class="fas fa-clock text-purple-600 mr-1 sm:mr-2 text-sm sm:text-base"></i>
<span id="timer" class="font-bold">00:00</span>
</div>
<div class="bg-white rounded-lg shadow p-2 sm:p-3 flex items-center text-sm sm:text-base">
<i class="fas fa-layer-group text-purple-600 mr-1 sm:mr-2 text-sm sm:text-base"></i>
<span id="level" class="font-bold">Level 1</span>
</div>
<div class="bg-white rounded-lg shadow p-2 sm:p-3 flex items-center text-sm sm:text-base">
<i class="fas fa-star text-yellow-500 mr-1 sm:mr-2 text-sm sm:text-base"></i>
<span id="score" class="font-bold">0</span>
</div>
</div>
</header>
<!-- Game Controls - Stacked vertically on mobile -->
<div class="controls-container flex flex-col sm:flex-row justify-between mb-4 sm:mb-6">
<div class="flex space-x-2 sm:space-x-3 mb-2 sm:mb-0">
<button id="new-game" class="bg-purple-600 hover:bg-purple-700 text-white px-3 sm:px-4 py-1 sm:py-2 rounded-lg shadow flex items-center text-sm sm:text-base">
<i class="fas fa-plus-circle mr-1 sm:mr-2"></i> New Game
</button>
<button id="hint" class="bg-amber-500 hover:bg-amber-600 text-white px-3 sm:px-4 py-1 sm:py-2 rounded-lg shadow flex items-center text-sm sm:text-base">
<i class="fas fa-lightbulb mr-1 sm:mr-2"></i> Hint
</button>
</div>
<div>
<button id="sound-toggle" class="bg-gray-200 hover:bg-gray-300 text-gray-800 px-3 sm:px-4 py-1 sm:py-2 rounded-lg shadow flex items-center text-sm sm:text-base">
<i class="fas fa-volume-up mr-1 sm:mr-2"></i> Sound On
</button>
</div>
</div>
<!-- Game Board - Responsive grid with smaller tiles on mobile -->
<div class="board-container bg-white rounded-xl shadow-xl p-3 sm:p-6 mb-4 sm:mb-6 overflow-auto">
<div id="board" class="grid grid-cols-4 sm:grid-cols-6 md:grid-cols-8 gap-2 sm:gap-3 mx-auto"></div>
</div>
<!-- Game Status -->
<div id="game-status" class="text-center mb-4 sm:mb-6 hidden">
<div class="inline-block bg-green-100 border-l-4 border-green-500 text-green-700 p-3 sm:p-4 rounded-lg text-sm sm:text-base">
<p class="font-bold">Level Complete!</p>
</div>
</div>
<!-- Level Complete Modal - Responsive sizing -->
<div id="level-complete-modal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden z-50 p-2 sm:p-4">
<div class="modal-content bg-white rounded-xl shadow-2xl p-4 sm:p-8 w-full max-w-sm sm:max-w-md level-complete">
<div class="text-center">
<div class="text-4xl sm:text-6xl text-yellow-500 mb-2 sm:mb-4">
<i class="fas fa-trophy"></i>
</div>
<h2 class="text-xl sm:text-3xl font-bold text-gray-800 mb-1 sm:mb-2">Level Complete!</h2>
<p class="text-sm sm:text-base text-gray-600 mb-4 sm:mb-6">Great job! Ready for the next challenge?</p>
<div class="grid grid-cols-2 gap-2 sm:gap-4 mb-4 sm:mb-6">
<div class="bg-purple-50 rounded-lg p-2 sm:p-3">
<p class="text-xs sm:text-sm text-purple-600">Time</p>
<p id="level-time" class="font-bold text-lg sm:text-xl">00:45</p>
</div>
<div class="bg-purple-50 rounded-lg p-2 sm:p-3">
<p class="text-xs sm:text-sm text-purple-600">Score</p>
<p id="level-score" class="font-bold text-lg sm:text-xl">+250</p>
</div>
</div>
<button id="next-level" class="w-full bg-purple-600 hover:bg-purple-700 text-white py-2 sm:py-3 rounded-lg shadow-lg font-bold text-sm sm:text-base">
Next Level <i class="fas fa-arrow-right ml-1 sm:ml-2"></i>
</button>
</div>
</div>
</div>
<!-- Game Over Modal - Responsive sizing -->
<div id="game-over-modal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden z-50 p-2 sm:p-4">
<div class="modal-content bg-white rounded-xl shadow-2xl p-4 sm:p-8 w-full max-w-sm sm:max-w-md">
<div class="text-center">
<div class="text-4xl sm:text-6xl text-red-500 mb-2 sm:mb-4">
<i class="fas fa-gamepad"></i>
</div>
<h2 class="text-xl sm:text-3xl font-bold text-gray-800 mb-1 sm:mb-2">Game Over</h2>
<p class="text-sm sm:text-base text-gray-600 mb-4 sm:mb-6">Better luck next time!</p>
<div class="grid grid-cols-2 gap-2 sm:gap-4 mb-4 sm:mb-6">
<div class="bg-purple-50 rounded-lg p-2 sm:p-3">
<p class="text-xs sm:text-sm text-purple-600">Level Reached</p>
<p id="final-level" class="font-bold text-lg sm:text-xl">3</p>
</div>
<div class="bg-purple-50 rounded-lg p-2 sm:p-3">
<p class="text-xs sm:text-sm text-purple-600">Total Score</p>
<p id="final-score" class="font-bold text-lg sm:text-xl">750</p>
</div>
</div>
<button id="play-again" class="w-full bg-purple-600 hover:bg-purple-700 text-white py-2 sm:py-3 rounded-lg shadow-lg font-bold text-sm sm:text-base">
Play Again <i class="fas fa-redo ml-1 sm:ml-2"></i>
</button>
</div>
</div>
</div>
</div>
<!-- Audio elements for sound effects -->
<audio id="flip-sound" src="https://kexel-git.static.hf.space/gsom/zapsplat_multimedia_button_click.mp3" preload="auto"></audio>
<audio id="match-sound" src="https://kexel-git.static.hf.space/gsom/positive-beeps.mp3" preload="auto"></audio>
<audio id="mismatch-sound" src="https://kexel-git.static.hf.space/gsom/mixkit-quick-jump-arcade-game-239.mp3" preload="auto"></audio>
<audio id="hint-sound" src="https://kexel-git.static.hf.space/gsom/unlock-game-notification.mp3" preload="auto"></audio>
<audio id="level-complete-sound" src="https://kexel-git.static.hf.space/gsom/2015-preview.mp3" preload="auto"></audio>
<audio id="game-over-sound" src="https://kexel-git.static.hf.space/gsom/2027-preview.mp3" preload="auto"></audio>
<audio id="button-click-sound" src="https://kexel-git.static.hf.space/gsom/mixkit-mouse-click-close-1113.mp3" preload="auto"></audio>
<script>
document.addEventListener('DOMContentLoaded', () => {
// Game state
const state = {
board: [],
level: 1,
score: 0,
time: 0,
timerInterval: null,
selectedTiles: [],
matchedPairs: 0,
totalPairs: 0,
soundEnabled: true,
gameActive: false,
isMobile: window.innerWidth < 640 // Check if mobile device
};
// Audio elements
const flipSound = document.getElementById('flip-sound');
const matchSound = document.getElementById('match-sound');
const mismatchSound = document.getElementById('mismatch-sound');
const hintSound = document.getElementById('hint-sound');
const levelCompleteSound = document.getElementById('level-complete-sound');
const gameOverSound = document.getElementById('game-over-sound');
const buttonClickSound = document.getElementById('button-click-sound');
// DOM elements
const boardElement = document.getElementById('board');
const timerElement = document.getElementById('timer');
const levelElement = document.getElementById('level');
const scoreElement = document.getElementById('score');
const newGameButton = document.getElementById('new-game');
const hintButton = document.getElementById('hint');
const soundToggleButton = document.getElementById('sound-toggle');
const gameStatusElement = document.getElementById('game-status');
const levelCompleteModal = document.getElementById('level-complete-modal');
const gameOverModal = document.getElementById('game-over-modal');
const nextLevelButton = document.getElementById('next-level');
const playAgainButton = document.getElementById('play-again');
const levelTimeElement = document.getElementById('level-time');
const levelScoreElement = document.getElementById('level-score');
const finalLevelElement = document.getElementById('final-level');
const finalScoreElement = document.getElementById('final-score');
// Tile types (Vita Mahjong style)
const tileTypes = [
'1m', '2m', '3m', '4m', '5m', '6m', '7m', '8m', '9m', // Characters
'1s', '2s', '3s', '4s', '5s', '6s', '7s', '8s', '9s', // Bamboo
'1p', '2p', '3p', '4p', '5p', '6p', '7p', '8p', '9p', // Circles
'ew', 'sw', 'ww', 'nw', // Winds
'wd', 'gd', 'rd' // Dragons
];
// Initialize game
function initGame() {
state.level = 1;
state.score = 0;
state.time = 0;
state.matchedPairs = 0;
state.gameActive = true;
clearInterval(state.timerInterval);
startTimer();
updateUI();
createBoard();
// Play button click sound
playSound('button-click');
}
// Create game board based on current level
function createBoard() {
boardElement.innerHTML = '';
state.board = [];
state.selectedTiles = [];
state.matchedPairs = 0;
// Determine number of pairs based on level and screen size
const basePairs = state.isMobile ? 2 : 4;
const pairs = Math.min(basePairs + state.level, state.isMobile ? 12 : 32);
state.totalPairs = pairs;
// Create array of tile pairs
let tiles = [];
const availableTypes = [...tileTypes].sort(() => 0.5 - Math.random()).slice(0, pairs);
availableTypes.forEach(type => {
tiles.push(type, type);
});
// Shuffle tiles
tiles = shuffleArray(tiles);
// Determine grid columns based on screen size
const cols = state.isMobile ? 4 : (window.innerWidth < 768 ? 6 : 8);
const rows = Math.ceil(tiles.length / cols);
for (let i = 0; i < rows; i++) {
const row = [];
for (let j = 0; j < cols; j++) {
const index = i * cols + j;
if (index < tiles.length) {
row.push({
type: tiles[index],
flipped: false,
matched: false,
row: i,
col: j
});
} else {
row.push(null); // Empty slot for uneven boards
}
}
state.board.push(row);
}
// Render tiles
renderBoard();
}
// Render the game board
function renderBoard() {
boardElement.innerHTML = '';
// Calculate grid columns based on screen size
const cols = state.isMobile ? 4 : (window.innerWidth < 768 ? 6 : 8);
boardElement.className = `grid gap-2 sm:gap-3 mx-auto`;
boardElement.style.gridTemplateColumns = `repeat(${cols}, minmax(0, 1fr))`;
state.board.forEach((row, rowIndex) => {
row.forEach((tile, colIndex) => {
if (!tile) return;
const tileElement = document.createElement('div');
tileElement.className = `tile aspect-square cursor-pointer transition-all duration-300 ${tile.matched ? 'opacity-0' : ''}`;
tileElement.innerHTML = `
<div class="border-t-2 border-r-2 border-indigo-500/50 border-l-8 border-b-8 tile-inner ${
tile.flipped ? "flipped" : ""
}">
<div class="rounded-none tile-back flex items-center justify-center">
<i class="fa-brands fa-fort-awesome text-sm sm:text-5xl"></i>
</div>
<div class="tile-front">
<span class="text-xl sm:text-4xl font-bold">${getTileSymbol(
tile.type
)}</span>
</div>
</div>
`;
tileElement.addEventListener('click', () => handleTileClick(tile));
if (!tile.matched) {
tileElement.classList.add('tile-hover');
}
boardElement.appendChild(tileElement);
});
});
}
// Handle tile click
function handleTileClick(tile) {
if (!state.gameActive || tile.matched || tile.flipped || state.selectedTiles.length >= 2) {
return;
}
// Flip the tile
tile.flipped = true;
state.selectedTiles.push(tile);
// Play sound
playSound('flip');
renderBoard();
// Check for match if two tiles are selected
if (state.selectedTiles.length === 2) {
const [tile1, tile2] = state.selectedTiles;
if (tile1.type === tile2.type) {
// Match found
tile1.matched = true;
tile2.matched = true;
state.matchedPairs++;
state.score += 50 * state.level;
// Play success sound
playSound('match');
// Check if level is complete
if (state.matchedPairs === state.totalPairs) {
levelComplete();
}
// Clear selection after delay
setTimeout(() => {
state.selectedTiles = [];
renderBoard();
}, 500);
} else {
// No match
setTimeout(() => {
tile1.flipped = false;
tile2.flipped = false;
state.selectedTiles = [];
renderBoard();
// Play mismatch sound
playSound('mismatch');
}, 1000);
}
}
updateUI();
}
// Level complete
function levelComplete() {
state.gameActive = false;
clearInterval(state.timerInterval);
// Calculate bonus points based on time
const timeBonus = Math.max(0, 300 - state.time);
state.score += timeBonus;
// Show level complete modal
levelTimeElement.textContent = formatTime(state.time);
levelScoreElement.textContent = `+${timeBonus}`;
levelCompleteModal.classList.remove('hidden');
// Play level complete sound
playSound('level-complete');
updateUI();
}
// Next level
function nextLevel() {
playSound('button-click');
state.level++;
state.time = 0;
state.gameActive = true;
levelCompleteModal.classList.add('hidden');
startTimer();
createBoard();
updateUI();
// Show level up message
gameStatusElement.classList.remove('hidden');
gameStatusElement.querySelector('p').textContent = `Level ${state.level}!`;
setTimeout(() => {
gameStatusElement.classList.add('hidden');
}, 2000);
}
// Game over
function gameOver() {
state.gameActive = false;
clearInterval(state.timerInterval);
// Update final stats
finalLevelElement.textContent = state.level;
finalScoreElement.textContent = state.score;
// Show game over modal
gameOverModal.classList.remove('hidden');
// Play game over sound
playSound('game-over');
}
// Start timer
function startTimer() {
state.time = 0;
updateTimerDisplay();
state.timerInterval = setInterval(() => {
state.time++;
updateTimerDisplay();
// Game over if time exceeds limit (5 minutes)
if (state.time >= 300) {
gameOver();
}
}, 1000);
}
// Update timer display
function updateTimerDisplay() {
timerElement.textContent = formatTime(state.time);
}
// Format time as MM:SS
function formatTime(seconds) {
const mins = Math.floor(seconds / 60).toString().padStart(2, '0');
const secs = (seconds % 60).toString().padStart(2, '0');
return `${mins}:${secs}`;
}
// Update UI elements
function updateUI() {
levelElement.textContent = `Level ${state.level}`;
scoreElement.textContent = state.score;
}
// Get tile symbol for display
function getTileSymbol(type) {
const symbols = {
'1m': '一', '2m': '二', '3m': '三', '4m': '四', '5m': '五',
'6m': '六', '7m': '七', '8m': '八', '9m': '九',
'1s': '1', '2s': '2', '3s': '3', '4s': '4', '5s': '5',
'6s': '6', '7s': '7', '8s': '8', '9s': '9',
'1p': '❶', '2p': '❷', '3p': '❸', '4p': '❹', '5p': '❺',
'6p': '❻', '7p': '❼', '8p': '❽', '9p': '❾',
'ew': '東', 'sw': '南', 'ww': '西', 'nw': '北',
'wd': '白', 'gd': '發', 'rd': '中'
};
return symbols[type] || type;
}
// Shuffle array
function shuffleArray(array) {
const newArray = [...array];
for (let i = newArray.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[newArray[i], newArray[j]] = [newArray[j], newArray[i]];
}
return newArray;
}
// Play sound
function playSound(type) {
if (!state.soundEnabled) return;
try {
switch (type) {
case 'flip':
flipSound.currentTime = 0;
flipSound.play();
break;
case 'match':
matchSound.currentTime = 0;
matchSound.play();
break;
case 'mismatch':
mismatchSound.currentTime = 0;
mismatchSound.play();
break;
case 'hint':
hintSound.currentTime = 0;
hintSound.play();
break;
case 'level-complete':
levelCompleteSound.currentTime = 0;
levelCompleteSound.play();
break;
case 'game-over':
gameOverSound.currentTime = 0;
gameOverSound.play();
break;
case 'button-click':
buttonClickSound.currentTime = 0;
buttonClickSound.play();
break;
}
} catch (e) {
console.error('Error playing sound:', e);
}
}
// Provide hint
function provideHint() {
if (!state.gameActive || state.matchedPairs === state.totalPairs) {
return;
}
// Play button click sound
playSound('button-click');
// Find all unflipped, unmatched tiles
const unflippedTiles = [];
state.board.forEach(row => {
row.forEach(tile => {
if (tile && !tile.flipped && !tile.matched) {
unflippedTiles.push(tile);
}
});
});
if (unflippedTiles.length < 2) return;
// Find a matching pair
const tileCount = {};
let hintTile1 = null;
let hintTile2 = null;
for (const tile of unflippedTiles) {
if (tileCount[tile.type]) {
hintTile1 = tileCount[tile.type];
hintTile2 = tile;
break;
}
tileCount[tile.type] = tile;
}
if (hintTile1 && hintTile2) {
// Highlight the hint tiles
const tileElements = boardElement.querySelectorAll('.tile');
tileElements.forEach((element, index) => {
const cols = state.isMobile ? 4 : (window.innerWidth < 768 ? 6 : 8);
const row = Math.floor(index / cols);
const col = index % cols;
const tile = state.board[row]?.[col];
if (tile === hintTile1 || tile === hintTile2) {
element.classList.add('ring-4', 'ring-yellow-400', 'ring-opacity-75');
// Remove highlight after delay
setTimeout(() => {
element.classList.remove('ring-4', 'ring-yellow-400', 'ring-opacity-75');
}, 2000);
}
});
// Deduct points for using hint
state.score = Math.max(0, state.score - 25);
updateUI();
// Play hint sound
playSound('hint');
}
}
// Event listeners
newGameButton.addEventListener('click', initGame);
hintButton.addEventListener('click', provideHint);
soundToggleButton.addEventListener('click', () => {
state.soundEnabled = !state.soundEnabled;
soundToggleButton.innerHTML = state.soundEnabled
? '<i class="fas fa-volume-up mr-1 sm:mr-2"></i> Sound On'
: '<i class="fas fa-volume-mute mr-1 sm:mr-2"></i> Sound Off';
playSound('button-click');
});
nextLevelButton.addEventListener('click', nextLevel);
playAgainButton.addEventListener('click', () => {
playSound('button-click');
gameOverModal.classList.add('hidden');
initGame();
});
// Handle window resize
window.addEventListener('resize', () => {
state.isMobile = window.innerWidth < 640;
if (state.gameActive) {
createBoard();
}
});
// Initialize the game
initGame();
});
</script>
</body>
</html>
<!--<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Vita Mahjong</title>
<script src="https://cdn.tailwindcss.com"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
@keyframes tileHover {
0% { transform: translateY(0); }
50% { transform: translateY(-5px); }
100% { transform: translateY(0); }
}
.tile-hover:hover {
animation: tileHover 0.3s ease;
transform: translateY(-5px);
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.2);
}
.tile-selected {
transform: translateY(-15px);
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.2);
z-index: 10;
}
.tile-matched {
animation: fadeOut 0.5s ease forwards;
}
@keyframes fadeOut {
to {
opacity: 0;
transform: scale(0.8);
}
}
.board-container {
perspective: 1000px;
}
.tile {
transition: all 0.3s ease;
transform-style: preserve-3d;
}
.tile-inner {
position: relative;
width: 100%;
height: 100%;
transform-style: preserve-3d;
}
.tile-front, .tile-back {
position: absolute;
width: 100%;
height: 100%;
backface-visibility: hidden;
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
}
.tile-front {
background: linear-gradient(145deg, #f0f0f0, #ffffff);
transform: rotateY(180deg);
}
.tile-back {
background: linear-gradient(145deg, #4f46e5, #7c3aed);
color: white;
}
.flipped {
transform: rotateY(180deg);
}
.level-complete {
animation: pulse 2s infinite;
}
@keyframes pulse {
0% { transform: scale(1); }
50% { transform: scale(1.05); }
100% { transform: scale(1); }
}
</style>
</head>
<body class="bg-gray-100 min-h-screen font-sans">
<div class="container mx-auto px-4 py-8">
<header class="flex justify-between items-center mb-8">
<div class="flex items-center">
<i class="fas fa-dragon text-4xl text-purple-600 mr-3"></i>
<h1 class="text-3xl font-bold text-gray-800">Vita Mahjong</h1>
</div>
<div class="flex items-center space-x-4">
<div class="bg-white rounded-lg shadow p-3 flex items-center">
<i class="fas fa-clock text-purple-600 mr-2"></i>
<span id="timer" class="font-bold">00:00</span>
</div>
<div class="bg-white rounded-lg shadow p-3 flex items-center">
<i class="fas fa-layer-group text-purple-600 mr-2"></i>
<span id="level" class="font-bold">Level 1</span>
</div>
<div class="bg-white rounded-lg shadow p-3 flex items-center">
<i class="fas fa-star text-yellow-500 mr-2"></i>
<span id="score" class="font-bold">0</span>
</div>
</div>
</header>
<div class="flex justify-between mb-6">
<div class="flex space-x-3">
<button id="new-game" class="bg-purple-600 hover:bg-purple-700 text-white px-4 py-2 rounded-lg shadow flex items-center">
<i class="fas fa-plus-circle mr-2"></i> New Game
</button>
<button id="hint" class="bg-amber-500 hover:bg-amber-600 text-white px-4 py-2 rounded-lg shadow flex items-center">
<i class="fas fa-lightbulb mr-2"></i> Hint
</button>
</div>
<div>
<button id="sound-toggle" class="bg-gray-200 hover:bg-gray-300 text-gray-800 px-4 py-2 rounded-lg shadow flex items-center">
<i class="fas fa-volume-up mr-2"></i> Sound On
</button>
</div>
</div>
<div class="board-container bg-white rounded-xl shadow-xl p-6 mb-6">
<div id="board" class="grid grid-cols-8 gap-3 mx-auto"></div>
</div>
<div id="game-status" class="text-center mb-6 hidden">
<div class="inline-block bg-green-100 border-l-4 border-green-500 text-green-700 p-4 rounded-lg">
<p class="font-bold">Level Complete!</p>
</div>
</div>
<div id="level-complete-modal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden z-50">
<div class="bg-white rounded-xl shadow-2xl p-8 max-w-md w-full level-complete">
<div class="text-center">
<div class="text-6xl text-yellow-500 mb-4">
<i class="fas fa-trophy"></i>
</div>
<h2 class="text-3xl font-bold text-gray-800 mb-2">Level Complete!</h2>
<p class="text-gray-600 mb-6">Great job! Ready for the next challenge?</p>
<div class="grid grid-cols-2 gap-4 mb-6">
<div class="bg-purple-50 rounded-lg p-3">
<p class="text-sm text-purple-600">Time</p>
<p id="level-time" class="font-bold text-xl">00:45</p>
</div>
<div class="bg-purple-50 rounded-lg p-3">
<p class="text-sm text-purple-600">Score</p>
<p id="level-score" class="font-bold text-xl">+250</p>
</div>
</div>
<button id="next-level" class="w-full bg-purple-600 hover:bg-purple-700 text-white py-3 rounded-lg shadow-lg font-bold">
Next Level <i class="fas fa-arrow-right ml-2"></i>
</button>
</div>
</div>
</div>
<div id="game-over-modal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden z-50">
<div class="bg-white rounded-xl shadow-2xl p-8 max-w-md w-full">
<div class="text-center">
<div class="text-6xl text-red-500 mb-4">
<i class="fas fa-gamepad"></i>
</div>
<h2 class="text-3xl font-bold text-gray-800 mb-2">Game Over</h2>
<p class="text-gray-600 mb-6">Better luck next time!</p>
<div class="grid grid-cols-2 gap-4 mb-6">
<div class="bg-purple-50 rounded-lg p-3">
<p class="text-sm text-purple-600">Level Reached</p>
<p id="final-level" class="font-bold text-xl">3</p>
</div>
<div class="bg-purple-50 rounded-lg p-3">
<p class="text-sm text-purple-600">Total Score</p>
<p id="final-score" class="font-bold text-xl">750</p>
</div>
</div>
<button id="play-again" class="w-full bg-purple-600 hover:bg-purple-700 text-white py-3 rounded-lg shadow-lg font-bold">
Play Again <i class="fas fa-redo ml-2"></i>
</button>
</div>
</div>
</div>
</div>
<audio id="flip-sound" src="https://kexel-git.static.hf.space/gsom/zapsplat_multimedia_button_click.mp3" preload="auto"></audio>
<audio id="match-sound" src="https://kexel-git.static.hf.space/gsom/positive-beeps.mp3" preload="auto"></audio>
<audio id="mismatch-sound" src="https://kexel-git.static.hf.space/gsom/mixkit-quick-jump-arcade-game-239.mp3" preload="auto"></audio>
<audio id="hint-sound" src="https://kexel-git.static.hf.space/gsom/unlock-game-notification.mp3" preload="auto"></audio>
<audio id="level-complete-sound" src="https://kexel-git.static.hf.space/gsom/2015-preview.mp3" preload="auto"></audio>
<audio id="game-over-sound" src="https://kexel-git.static.hf.space/gsom/2027-preview.mp3" preload="auto"></audio>
<audio id="button-click-sound" src="https://kexel-git.static.hf.space/gsom/mixkit-mouse-click-close-1113.mp3" preload="auto"></audio>
<script>
document.addEventListener('DOMContentLoaded', () => {
// Game state
const state = {
board: [],
level: 1,
score: 0,
time: 0,
timerInterval: null,
selectedTiles: [],
matchedPairs: 0,
totalPairs: 0,
soundEnabled: true,
gameActive: false
};
// Audio elements
const flipSound = document.getElementById('flip-sound');
const matchSound = document.getElementById('match-sound');
const mismatchSound = document.getElementById('mismatch-sound');
const hintSound = document.getElementById('hint-sound');
const levelCompleteSound = document.getElementById('level-complete-sound');
const gameOverSound = document.getElementById('game-over-sound');
const buttonClickSound = document.getElementById('button-click-sound');
// DOM elements
const boardElement = document.getElementById('board');
const timerElement = document.getElementById('timer');
const levelElement = document.getElementById('level');
const scoreElement = document.getElementById('score');
const newGameButton = document.getElementById('new-game');
const hintButton = document.getElementById('hint');
const soundToggleButton = document.getElementById('sound-toggle');
const gameStatusElement = document.getElementById('game-status');
const levelCompleteModal = document.getElementById('level-complete-modal');
const gameOverModal = document.getElementById('game-over-modal');
const nextLevelButton = document.getElementById('next-level');
const playAgainButton = document.getElementById('play-again');
const levelTimeElement = document.getElementById('level-time');
const levelScoreElement = document.getElementById('level-score');
const finalLevelElement = document.getElementById('final-level');
const finalScoreElement = document.getElementById('final-score');
// Tile types (Vita Mahjong style)
const tileTypes = [
'1m', '2m', '3m', '4m', '5m', '6m', '7m', '8m', '9m', // Characters
'1s', '2s', '3s', '4s', '5s', '6s', '7s', '8s', '9s', // Bamboo
'1p', '2p', '3p', '4p', '5p', '6p', '7p', '8p', '9p', // Circles
'ew', 'sw', 'ww', 'nw', // Winds
'wd', 'gd', 'rd' // Dragons
];
// Initialize game
function initGame() {
state.level = 1;
state.score = 0;
state.time = 0;
state.matchedPairs = 0;
state.gameActive = true;
clearInterval(state.timerInterval);
startTimer();
updateUI();
createBoard();
// Play button click sound
playSound('button-click');
}
// Create game board based on current level
function createBoard() {
boardElement.innerHTML = '';
state.board = [];
state.selectedTiles = [];
state.matchedPairs = 0;
// Determine number of pairs based on level
const pairs = Math.min(4 + state.level, 32); // Max 32 pairs (64 tiles)
state.totalPairs = pairs;
// Create array of tile pairs
let tiles = [];
const availableTypes = [...tileTypes].sort(() => 0.5 - Math.random()).slice(0, pairs);
availableTypes.forEach(type => {
tiles.push(type, type);
});
// Shuffle tiles
tiles = shuffleArray(tiles);
// Create board matrix (8 columns, rows as needed)
const cols = 8;
const rows = Math.ceil(tiles.length / cols);
for (let i = 0; i < rows; i++) {
const row = [];
for (let j = 0; j < cols; j++) {
const index = i * cols + j;
if (index < tiles.length) {
row.push({
type: tiles[index],
flipped: false,
matched: false,
row: i,
col: j
});
} else {
row.push(null); // Empty slot for uneven boards
}
}
state.board.push(row);
}
// Render tiles
renderBoard();
}
// Render the game board
function renderBoard() {
boardElement.innerHTML = '';
// Calculate grid rows based on board rows
boardElement.className = `grid gap-3 mx-auto`;
boardElement.style.gridTemplateColumns = `repeat(8, minmax(0, 1fr))`;
state.board.forEach((row, rowIndex) => {
row.forEach((tile, colIndex) => {
if (!tile) return;
const tileElement = document.createElement('div');
tileElement.className = `tile aspect-square cursor-pointer transition-all duration-300 ${tile.matched ? 'opacity-0' : ''}`;
tileElement.innerHTML = `
<div class="tile-inner ${tile.flipped ? 'flipped' : ''}">
<div class="tile-back flex items-center justify-center">
<i class="fas fa-dragon text-2xl"></i>
</div>
<div class="tile-front">
<span class="text-3xl font-bold">${getTileSymbol(tile.type)}</span>
</div>
</div>
`;
tileElement.addEventListener('click', () => handleTileClick(tile));
if (!tile.matched) {
tileElement.classList.add('tile-hover');
}
boardElement.appendChild(tileElement);
});
});
}
// Handle tile click
function handleTileClick(tile) {
if (!state.gameActive || tile.matched || tile.flipped || state.selectedTiles.length >= 2) {
return;
}
// Flip the tile
tile.flipped = true;
state.selectedTiles.push(tile);
// Play sound
playSound('flip');
renderBoard();
// Check for match if two tiles are selected
if (state.selectedTiles.length === 2) {
const [tile1, tile2] = state.selectedTiles;
if (tile1.type === tile2.type) {
// Match found
tile1.matched = true;
tile2.matched = true;
state.matchedPairs++;
state.score += 50 * state.level;
// Play success sound
playSound('match');
// Check if level is complete
if (state.matchedPairs === state.totalPairs) {
levelComplete();
}
// Clear selection after delay
setTimeout(() => {
state.selectedTiles = [];
renderBoard();
}, 500);
} else {
// No match
setTimeout(() => {
tile1.flipped = false;
tile2.flipped = false;
state.selectedTiles = [];
renderBoard();
// Play mismatch sound
playSound('mismatch');
}, 1000);
}
}
updateUI();
}
// Level complete
function levelComplete() {
state.gameActive = false;
clearInterval(state.timerInterval);
// Calculate bonus points based on time
const timeBonus = Math.max(0, 300 - state.time);
state.score += timeBonus;
// Show level complete modal
levelTimeElement.textContent = formatTime(state.time);
levelScoreElement.textContent = `+${timeBonus}`;
levelCompleteModal.classList.remove('hidden');
// Play level complete sound
playSound('level-complete');
updateUI();
}
// Next level
function nextLevel() {
playSound('button-click');
state.level++;
state.time = 0;
state.gameActive = true;
levelCompleteModal.classList.add('hidden');
startTimer();
createBoard();
updateUI();
// Show level up message
gameStatusElement.classList.remove('hidden');
gameStatusElement.querySelector('p').textContent = `Level ${state.level}!`;
setTimeout(() => {
gameStatusElement.classList.add('hidden');
}, 2000);
}
// Game over
function gameOver() {
state.gameActive = false;
clearInterval(state.timerInterval);
// Update final stats
finalLevelElement.textContent = state.level;
finalScoreElement.textContent = state.score;
// Show game over modal
gameOverModal.classList.remove('hidden');
// Play game over sound
playSound('game-over');
}
// Start timer
function startTimer() {
state.time = 0;
updateTimerDisplay();
state.timerInterval = setInterval(() => {
state.time++;
updateTimerDisplay();
// Game over if time exceeds limit (5 minutes)
if (state.time >= 300) {
gameOver();
}
}, 1000);
}
// Update timer display
function updateTimerDisplay() {
timerElement.textContent = formatTime(state.time);
}
// Format time as MM:SS
function formatTime(seconds) {
const mins = Math.floor(seconds / 60).toString().padStart(2, '0');
const secs = (seconds % 60).toString().padStart(2, '0');
return `${mins}:${secs}`;
}
// Update UI elements
function updateUI() {
levelElement.textContent = `Level ${state.level}`;
scoreElement.textContent = state.score;
}
// Get tile symbol for display
function getTileSymbol(type) {
const symbols = {
'1m': '一', '2m': '二', '3m': '三', '4m': '四', '5m': '五',
'6m': '六', '7m': '七', '8m': '八', '9m': '九',
'1s': '1', '2s': '2', '3s': '3', '4s': '4', '5s': '5',
'6s': '6', '7s': '7', '8s': '8', '9s': '9',
'1p': '❶', '2p': '❷', '3p': '❸', '4p': '❹', '5p': '❺',
'6p': '❻', '7p': '❼', '8p': '❽', '9p': '❾',
'ew': '東', 'sw': '南', 'ww': '西', 'nw': '北',
'wd': '白', 'gd': '發', 'rd': '中'
};
return symbols[type] || type;
}
// Shuffle array
function shuffleArray(array) {
const newArray = [...array];
for (let i = newArray.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[newArray[i], newArray[j]] = [newArray[j], newArray[i]];
}
return newArray;
}
// Play sound
function playSound(type) {
if (!state.soundEnabled) return;
try {
switch (type) {
case 'flip':
flipSound.currentTime = 0;
flipSound.play();
break;
case 'match':
matchSound.currentTime = 0;
matchSound.play();
break;
case 'mismatch':
mismatchSound.currentTime = 0;
mismatchSound.play();
break;
case 'hint':
hintSound.currentTime = 0;
hintSound.play();
break;
case 'level-complete':
levelCompleteSound.currentTime = 0;
levelCompleteSound.play();
break;
case 'game-over':
gameOverSound.currentTime = 0;
gameOverSound.play();
break;
case 'button-click':
buttonClickSound.currentTime = 0;
buttonClickSound.play();
break;
}
} catch (e) {
console.error('Error playing sound:', e);
}
}
// Provide hint
function provideHint() {
if (!state.gameActive || state.matchedPairs === state.totalPairs) {
return;
}
// Play button click sound
playSound('button-click');
// Find all unflipped, unmatched tiles
const unflippedTiles = [];
state.board.forEach(row => {
row.forEach(tile => {
if (tile && !tile.flipped && !tile.matched) {
unflippedTiles.push(tile);
}
});
});
if (unflippedTiles.length < 2) return;
// Find a matching pair
const tileCount = {};
let hintTile1 = null;
let hintTile2 = null;
for (const tile of unflippedTiles) {
if (tileCount[tile.type]) {
hintTile1 = tileCount[tile.type];
hintTile2 = tile;
break;
}
tileCount[tile.type] = tile;
}
if (hintTile1 && hintTile2) {
// Highlight the hint tiles
const tileElements = boardElement.querySelectorAll('.tile');
tileElements.forEach((element, index) => {
const row = Math.floor(index / 8);
const col = index % 8;
const tile = state.board[row]?.[col];
if (tile === hintTile1 || tile === hintTile2) {
element.classList.add('ring-4', 'ring-yellow-400', 'ring-opacity-75');
// Remove highlight after delay
setTimeout(() => {
element.classList.remove('ring-4', 'ring-yellow-400', 'ring-opacity-75');
}, 2000);
}
});
// Deduct points for using hint
state.score = Math.max(0, state.score - 25);
updateUI();
// Play hint sound
playSound('hint');
}
}
// Event listeners
newGameButton.addEventListener('click', initGame);
hintButton.addEventListener('click', provideHint);
soundToggleButton.addEventListener('click', () => {
state.soundEnabled = !state.soundEnabled;
soundToggleButton.innerHTML = state.soundEnabled
? '<i class="fas fa-volume-up mr-2"></i> Sound On'
: '<i class="fas fa-volume-mute mr-2"></i> Sound Off';
playSound('button-click');
});
nextLevelButton.addEventListener('click', nextLevel);
playAgainButton.addEventListener('click', () => {
playSound('button-click');
gameOverModal.classList.add('hidden');
initGame();
});
// Initialize the game
initGame();
});
</script>
</body>
</html>
-->
<!--<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Vita Mahjong</title>
<script src="https://cdn.tailwindcss.com"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
@keyframes tileHover {
0% { transform: translateY(0); }
50% { transform: translateY(-5px); }
100% { transform: translateY(0); }
}
.tile-hover:hover {
animation: tileHover 0.3s ease;
transform: translateY(-5px);
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.2);
}
.tile-selected {
transform: translateY(-15px);
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.2);
z-index: 10;
}
.tile-matched {
animation: fadeOut 0.5s ease forwards;
}
@keyframes fadeOut {
to {
opacity: 0;
transform: scale(0.8);
}
}
.board-container {
perspective: 1000px;
}
.tile {
transition: all 0.3s ease;
transform-style: preserve-3d;
}
.tile-inner {
position: relative;
width: 100%;
height: 100%;
transform-style: preserve-3d;
}
.tile-front, .tile-back {
position: absolute;
width: 100%;
height: 100%;
backface-visibility: hidden;
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
}
.tile-front {
background: linear-gradient(145deg, #f0f0f0, #ffffff);
transform: rotateY(180deg);
}
.tile-back {
background: linear-gradient(145deg, #4f46e5, #7c3aed);
color: white;
}
.flipped {
transform: rotateY(180deg);
}
.level-complete {
animation: pulse 2s infinite;
}
@keyframes pulse {
0% { transform: scale(1); }
50% { transform: scale(1.05); }
100% { transform: scale(1); }
}
</style>
</head>
<body class="bg-gray-100 min-h-screen font-sans">
<div class="container mx-auto px-4 py-8">
<header class="flex justify-between items-center mb-8">
<div class="flex items-center">
<i class="fas fa-dragon text-4xl text-purple-600 mr-3"></i>
<h1 class="text-3xl font-bold text-gray-800">Vita Mahjong</h1>
</div>
<div class="flex items-center space-x-4">
<div class="bg-white rounded-lg shadow p-3 flex items-center">
<i class="fas fa-clock text-purple-600 mr-2"></i>
<span id="timer" class="font-bold">00:00</span>
</div>
<div class="bg-white rounded-lg shadow p-3 flex items-center">
<i class="fas fa-layer-group text-purple-600 mr-2"></i>
<span id="level" class="font-bold">Level 1</span>
</div>
<div class="bg-white rounded-lg shadow p-3 flex items-center">
<i class="fas fa-star text-yellow-500 mr-2"></i>
<span id="score" class="font-bold">0</span>
</div>
</div>
</header>
<div class="flex justify-between mb-6">
<div class="flex space-x-3">
<button id="new-game" class="bg-purple-600 hover:bg-purple-700 text-white px-4 py-2 rounded-lg shadow flex items-center">
<i class="fas fa-plus-circle mr-2"></i> New Game
</button>
<button id="hint" class="bg-amber-500 hover:bg-amber-600 text-white px-4 py-2 rounded-lg shadow flex items-center">
<i class="fas fa-lightbulb mr-2"></i> Hint
</button>
</div>
<div>
<button id="sound-toggle" class="bg-gray-200 hover:bg-gray-300 text-gray-800 px-4 py-2 rounded-lg shadow flex items-center">
<i class="fas fa-volume-up mr-2"></i> Sound On
</button>
</div>
</div>
<div class="board-container bg-white rounded-xl shadow-xl p-6 mb-6">
<div id="board" class="grid grid-cols-8 gap-3 mx-auto"></div>
</div>
<div id="game-status" class="text-center mb-6 hidden">
<div class="inline-block bg-green-100 border-l-4 border-green-500 text-green-700 p-4 rounded-lg">
<p class="font-bold">Level Complete!</p>
</div>
</div>
<div id="level-complete-modal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden z-50">
<div class="bg-white rounded-xl shadow-2xl p-8 max-w-md w-full level-complete">
<div class="text-center">
<div class="text-6xl text-yellow-500 mb-4">
<i class="fas fa-trophy"></i>
</div>
<h2 class="text-3xl font-bold text-gray-800 mb-2">Level Complete!</h2>
<p class="text-gray-600 mb-6">Great job! Ready for the next challenge?</p>
<div class="grid grid-cols-2 gap-4 mb-6">
<div class="bg-purple-50 rounded-lg p-3">
<p class="text-sm text-purple-600">Time</p>
<p id="level-time" class="font-bold text-xl">00:45</p>
</div>
<div class="bg-purple-50 rounded-lg p-3">
<p class="text-sm text-purple-600">Score</p>
<p id="level-score" class="font-bold text-xl">+250</p>
</div>
</div>
<button id="next-level" class="w-full bg-purple-600 hover:bg-purple-700 text-white py-3 rounded-lg shadow-lg font-bold">
Next Level <i class="fas fa-arrow-right ml-2"></i>
</button>
</div>
</div>
</div>
<div id="game-over-modal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden z-50">
<div class="bg-white rounded-xl shadow-2xl p-8 max-w-md w-full">
<div class="text-center">
<div class="text-6xl text-red-500 mb-4">
<i class="fas fa-gamepad"></i>
</div>
<h2 class="text-3xl font-bold text-gray-800 mb-2">Game Over</h2>
<p class="text-gray-600 mb-6">Better luck next time!</p>
<div class="grid grid-cols-2 gap-4 mb-6">
<div class="bg-purple-50 rounded-lg p-3">
<p class="text-sm text-purple-600">Level Reached</p>
<p id="final-level" class="font-bold text-xl">3</p>
</div>
<div class="bg-purple-50 rounded-lg p-3">
<p class="text-sm text-purple-600">Total Score</p>
<p id="final-score" class="font-bold text-xl">750</p>
</div>
</div>
<button id="play-again" class="w-full bg-purple-600 hover:bg-purple-700 text-white py-3 rounded-lg shadow-lg font-bold">
Play Again <i class="fas fa-redo ml-2"></i>
</button>
</div>
</div>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', () => {
// Game state
const state = {
board: [],
level: 1,
score: 0,
time: 0,
timerInterval: null,
selectedTiles: [],
matchedPairs: 0,
totalPairs: 0,
soundEnabled: true,
gameActive: false
};
// DOM elements
const boardElement = document.getElementById('board');
const timerElement = document.getElementById('timer');
const levelElement = document.getElementById('level');
const scoreElement = document.getElementById('score');
const newGameButton = document.getElementById('new-game');
const hintButton = document.getElementById('hint');
const soundToggleButton = document.getElementById('sound-toggle');
const gameStatusElement = document.getElementById('game-status');
const levelCompleteModal = document.getElementById('level-complete-modal');
const gameOverModal = document.getElementById('game-over-modal');
const nextLevelButton = document.getElementById('next-level');
const playAgainButton = document.getElementById('play-again');
const levelTimeElement = document.getElementById('level-time');
const levelScoreElement = document.getElementById('level-score');
const finalLevelElement = document.getElementById('final-level');
const finalScoreElement = document.getElementById('final-score');
// Tile types (Vita Mahjong style)
const tileTypes = [
'1m', '2m', '3m', '4m', '5m', '6m', '7m', '8m', '9m', // Characters
'1s', '2s', '3s', '4s', '5s', '6s', '7s', '8s', '9s', // Bamboo
'1p', '2p', '3p', '4p', '5p', '6p', '7p', '8p', '9p', // Circles
'ew', 'sw', 'ww', 'nw', // Winds
'wd', 'gd', 'rd' // Dragons
];
// Initialize game
function initGame() {
state.level = 1;
state.score = 0;
state.time = 0;
state.matchedPairs = 0;
state.gameActive = true;
clearInterval(state.timerInterval);
startTimer();
updateUI();
createBoard();
}
// Create game board based on current level
function createBoard() {
boardElement.innerHTML = '';
state.board = [];
state.selectedTiles = [];
state.matchedPairs = 0;
// Determine number of pairs based on level
const pairs = Math.min(4 + state.level, 32); // Max 32 pairs (64 tiles)
state.totalPairs = pairs;
// Create array of tile pairs
let tiles = [];
const availableTypes = [...tileTypes].sort(() => 0.5 - Math.random()).slice(0, pairs);
availableTypes.forEach(type => {
tiles.push(type, type);
});
// Shuffle tiles
tiles = shuffleArray(tiles);
// Create board matrix (8 columns, rows as needed)
const cols = 8;
const rows = Math.ceil(tiles.length / cols);
for (let i = 0; i < rows; i++) {
const row = [];
for (let j = 0; j < cols; j++) {
const index = i * cols + j;
if (index < tiles.length) {
row.push({
type: tiles[index],
flipped: false,
matched: false,
row: i,
col: j
});
} else {
row.push(null); // Empty slot for uneven boards
}
}
state.board.push(row);
}
// Render tiles
renderBoard();
}
// Render the game board
function renderBoard() {
boardElement.innerHTML = '';
// Calculate grid rows based on board rows
boardElement.className = `grid gap-3 mx-auto`;
boardElement.style.gridTemplateColumns = `repeat(8, minmax(0, 1fr))`;
state.board.forEach((row, rowIndex) => {
row.forEach((tile, colIndex) => {
if (!tile) return;
const tileElement = document.createElement('div');
tileElement.className = `tile aspect-square cursor-pointer transition-all duration-300 ${tile.matched ? 'opacity-0' : ''}`;
tileElement.innerHTML = `
<div class="tile-inner ${tile.flipped ? 'flipped' : ''}">
<div class="tile-back flex items-center justify-center">
<i class="fas fa-dragon text-2xl"></i>
</div>
<div class="tile-front">
<span class="text-3xl font-bold">${getTileSymbol(tile.type)}</span>
</div>
</div>
`;
tileElement.addEventListener('click', () => handleTileClick(tile));
if (!tile.matched) {
tileElement.classList.add('tile-hover');
}
boardElement.appendChild(tileElement);
});
});
}
// Handle tile click
function handleTileClick(tile) {
if (!state.gameActive || tile.matched || tile.flipped || state.selectedTiles.length >= 2) {
return;
}
// Flip the tile
tile.flipped = true;
state.selectedTiles.push(tile);
// Play sound
if (state.soundEnabled) {
playSound('flip');
}
renderBoard();
// Check for match if two tiles are selected
if (state.selectedTiles.length === 2) {
const [tile1, tile2] = state.selectedTiles;
if (tile1.type === tile2.type) {
// Match found
tile1.matched = true;
tile2.matched = true;
state.matchedPairs++;
state.score += 50 * state.level;
// Play success sound
if (state.soundEnabled) {
playSound('match');
}
// Check if level is complete
if (state.matchedPairs === state.totalPairs) {
levelComplete();
}
// Clear selection after delay
setTimeout(() => {
state.selectedTiles = [];
renderBoard();
}, 500);
} else {
// No match
setTimeout(() => {
tile1.flipped = false;
tile2.flipped = false;
state.selectedTiles = [];
renderBoard();
// Play mismatch sound
if (state.soundEnabled) {
playSound('mismatch');
}
}, 1000);
}
}
updateUI();
}
// Level complete
function levelComplete() {
state.gameActive = false;
clearInterval(state.timerInterval);
// Calculate bonus points based on time
const timeBonus = Math.max(0, 300 - state.time);
state.score += timeBonus;
// Show level complete modal
levelTimeElement.textContent = formatTime(state.time);
levelScoreElement.textContent = `+${timeBonus}`;
levelCompleteModal.classList.remove('hidden');
updateUI();
}
// Next level
function nextLevel() {
state.level++;
state.time = 0;
state.gameActive = true;
levelCompleteModal.classList.add('hidden');
startTimer();
createBoard();
updateUI();
// Show level up message
gameStatusElement.classList.remove('hidden');
gameStatusElement.querySelector('p').textContent = `Level ${state.level}!`;
setTimeout(() => {
gameStatusElement.classList.add('hidden');
}, 2000);
}
// Game over
function gameOver() {
state.gameActive = false;
clearInterval(state.timerInterval);
// Update final stats
finalLevelElement.textContent = state.level;
finalScoreElement.textContent = state.score;
// Show game over modal
gameOverModal.classList.remove('hidden');
}
// Start timer
function startTimer() {
state.time = 0;
updateTimerDisplay();
state.timerInterval = setInterval(() => {
state.time++;
updateTimerDisplay();
// Game over if time exceeds limit (5 minutes)
if (state.time >= 300) {
gameOver();
}
}, 1000);
}
// Update timer display
function updateTimerDisplay() {
timerElement.textContent = formatTime(state.time);
}
// Format time as MM:SS
function formatTime(seconds) {
const mins = Math.floor(seconds / 60).toString().padStart(2, '0');
const secs = (seconds % 60).toString().padStart(2, '0');
return `${mins}:${secs}`;
}
// Update UI elements
function updateUI() {
levelElement.textContent = `Level ${state.level}`;
scoreElement.textContent = state.score;
}
// Get tile symbol for display
function getTileSymbol(type) {
const symbols = {
'1m': '一', '2m': '二', '3m': '三', '4m': '四', '5m': '五',
'6m': '六', '7m': '七', '8m': '八', '9m': '九',
'1s': '1', '2s': '2', '3s': '3', '4s': '4', '5s': '5',
'6s': '6', '7s': '7', '8s': '8', '9s': '9',
'1p': '❶', '2p': '❷', '3p': '❸', '4p': '❹', '5p': '❺',
'6p': '❻', '7p': '❼', '8p': '❽', '9p': '❾',
'ew': '東', 'sw': '南', 'ww': '西', 'nw': '北',
'wd': '白', 'gd': '發', 'rd': '中'
};
return symbols[type] || type;
}
// Shuffle array
function shuffleArray(array) {
const newArray = [...array];
for (let i = newArray.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[newArray[i], newArray[j]] = [newArray[j], newArray[i]];
}
return newArray;
}
// Play sound
function playSound(type) {
// In a real implementation, you would play actual sound files
console.log(`Playing ${type} sound`);
}
// Provide hint
function provideHint() {
if (!state.gameActive || state.matchedPairs === state.totalPairs) {
return;
}
// Find all unflipped, unmatched tiles
const unflippedTiles = [];
state.board.forEach(row => {
row.forEach(tile => {
if (tile && !tile.flipped && !tile.matched) {
unflippedTiles.push(tile);
}
});
});
if (unflippedTiles.length < 2) return;
// Find a matching pair
const tileCount = {};
let hintTile1 = null;
let hintTile2 = null;
for (const tile of unflippedTiles) {
if (tileCount[tile.type]) {
hintTile1 = tileCount[tile.type];
hintTile2 = tile;
break;
}
tileCount[tile.type] = tile;
}
if (hintTile1 && hintTile2) {
// Highlight the hint tiles
const tileElements = boardElement.querySelectorAll('.tile');
tileElements.forEach((element, index) => {
const row = Math.floor(index / 8);
const col = index % 8;
const tile = state.board[row]?.[col];
if (tile === hintTile1 || tile === hintTile2) {
element.classList.add('ring-4', 'ring-yellow-400', 'ring-opacity-75');
// Remove highlight after delay
setTimeout(() => {
element.classList.remove('ring-4', 'ring-yellow-400', 'ring-opacity-75');
}, 2000);
}
});
// Deduct points for using hint
state.score = Math.max(0, state.score - 25);
updateUI();
// Play hint sound
if (state.soundEnabled) {
playSound('hint');
}
}
}
// Event listeners
newGameButton.addEventListener('click', initGame);
hintButton.addEventListener('click', provideHint);
soundToggleButton.addEventListener('click', () => {
state.soundEnabled = !state.soundEnabled;
soundToggleButton.innerHTML = state.soundEnabled
? '<i class="fas fa-volume-up mr-2"></i> Sound On'
: '<i class="fas fa-volume-mute mr-2"></i> Sound Off';
});
nextLevelButton.addEventListener('click', nextLevel);
playAgainButton.addEventListener('click', () => {
gameOverModal.classList.add('hidden');
initGame();
});
// Initialize the game
initGame();
});
</script>
</body>
</html>
-->