BuyTicket.cshtml 95 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656
  1. @model LotteryWebApp.Models.HomeIndex_ViewModel
  2. @{
  3. ViewData["Title"] = "Millions - Buy Ticket";
  4. Layout = "~/Areas/Millions/Views/Shared/_Layout.cshtml";
  5. var currentTerm = Model.listTerm?.FirstOrDefault();
  6. }
  7. @using LotteryWebApp.Languages;
  8. @using LotteryWebApp.Common;
  9. @using Microsoft.AspNetCore.Http;
  10. @{
  11. var isFreeTicketMode = Context.Request.Query["freeTicketMode"] == "1"
  12. && Model.termType == Constants.Millions_CODE;
  13. var freeTicketPromotionCode = isFreeTicketMode
  14. ? Context.Session.GetString("Millions.FreeTicket.PromotionCode")
  15. : null;
  16. }
  17. @section Styles {
  18. <link rel="stylesheet" href="/Millions/css/buy-ticket.css" asp-append-version="true" />
  19. <style>
  20. /* BuyTicket has its own bottom bar; avoid global navbar padding gap */
  21. body.millions-bg { padding-bottom: 0 !important; }
  22. body.millions-bg .main-container { padding-bottom: 0 !important; }
  23. </style>
  24. }
  25. @if (Model.termType == Constants.PIC10_BIGSMALL_CODE || Model.termType == Constants.PIC10_ODDEVEN_CODE)
  26. {
  27. var isBigSmall = Model.termType == Constants.PIC10_BIGSMALL_CODE;
  28. var themeColor = isBigSmall ? "#0A9800" : "#AA3DC8";
  29. var gameTitle = isBigSmall ? Lang.millions_big_small : Lang.millions_odd_even;
  30. var prizeAmount = isBigSmall ? "100.000" : "200.000";
  31. <!-- ==================== CARD SELECTION GAME UI (BIG/SMALL or ODD/EVEN) ==================== -->
  32. <div class="millions-buy-ticket-page main-container animate__animated animate__fadeIn overflow-hidden">
  33. <!-- Header -->
  34. <div class="header">
  35. <button onclick="location.href='@Url.Action("GameHome", "Home")'" class="back-btn" type="button">
  36. <i class="fa-solid fa-arrow-left"></i>
  37. </button>
  38. <div class="header-title">@gameTitle</div>
  39. </div>
  40. <div class="content-card">
  41. <!-- Sticky Term Info sub-header -->
  42. <div class="ticket-subheader">
  43. <div class="flex items-center justify-between">
  44. <div class="flex items-center gap-2 border border-[#E5E7EB] rounded-lg px-3 py-1.5 bg-gray-50">
  45. <i class="fa-regular fa-calendar text-[[@themeColor]] text-sm" style="color: @themeColor"></i>
  46. <span class="text-[12px] font-bold text-gray-700">@DateTime.Now.ToString("dddd, MMM dd, yyyy", new System.Globalization.CultureInfo("en-US"))</span>
  47. </div>
  48. <div class="flex items-center gap-1 ml-4 flex-1 justify-end">
  49. <span class="text-[12px] font-bold text-gray-400">@Lang.millions_round</span>
  50. <span class="text-[18px] font-black text-brown-main">#@(currentTerm?.id ?? "123")</span>
  51. </div>
  52. </div>
  53. </div>
  54. <!-- Scrollable Content Area -->
  55. <div class="ticket-scroll-area bg-white pb-40" id="bsContent">
  56. <!-- Results for the last 5 days -->
  57. <div class="px-5 pt-4 pb-2">
  58. <h3 class="text-[17px] font-bold text-black text-center mb-3">Results for the last 5 days</h3>
  59. <div id="last5Results" class="flex justify-between gap-1.5 px-1">
  60. @{
  61. var pastTerms = ViewBag.PastTerms as List<LotteryWebApp.Service.Term> ?? new List<LotteryWebApp.Service.Term>();
  62. foreach (var term in pastTerms) {
  63. var rawResult = (term.result ?? "").Trim().ToUpper();
  64. var dateStr = "--";
  65. DateTime dt;
  66. if (DateTime.TryParse(term.date_random, out dt)) {
  67. dateStr = dt.ToString("MMM dd", new System.Globalization.CultureInfo("en-US"));
  68. }
  69. var resKey = rawResult;
  70. var resTitle = rawResult;
  71. if (isBigSmall) {
  72. if (rawResult == "S") { resKey = "Small"; resTitle = Lang.Small; }
  73. else if (rawResult == "B") { resKey = "Big"; resTitle = Lang.Big; }
  74. } else {
  75. if (rawResult == "O") { resKey = "Odd"; resTitle = Lang.Odd; }
  76. else if (rawResult == "E") { resKey = "Even"; resTitle = Lang.Even; }
  77. }
  78. var resColor = "";
  79. var bgStyle = "background: linear-gradient(136deg, #FFFAE6 0%, #FFE588 100%);";
  80. var textStyle = "-webkit-text-stroke: 0.5px #000;";
  81. if (resKey == "NA" || string.IsNullOrEmpty(rawResult)) {
  82. bgStyle = "background: #EAEAEA;";
  83. resTitle = "";
  84. textStyle = "";
  85. } else {
  86. if (isBigSmall) {
  87. resColor = resKey == "Big" ? "#A2FF00" : "#FF4157";
  88. } else {
  89. resColor = resKey == "Odd" ? "#FFC700" : "#B33BD0";
  90. }
  91. textStyle += $" color: {resColor};";
  92. }
  93. <div class="flex flex-col items-center justify-center min-h-[64px] flex-1 rounded-lg py-1.5 border border-gray-200" style="@bgStyle">
  94. <span class="text-[11px] font-bold text-black whitespace-nowrap opacity-70">@dateStr</span>
  95. <span class="text-[22px] font-black leading-tight" style="@textStyle">@resTitle</span>
  96. </div>
  97. }
  98. for(int r = pastTerms.Count; r < 5; r++) {
  99. <div class="flex flex-col items-center justify-center min-h-[64px] flex-1 rounded-lg py-1.5 border border-gray-200" style="background: #EAEAEA; opacity: 0.7;">
  100. <span class="text-[11px] font-bold text-black whitespace-nowrap opacity-70">--</span>
  101. <span class="text-[22px] font-black leading-tight" style="color: transparent;"></span>
  102. </div>
  103. }
  104. }
  105. </div>
  106. </div>
  107. <!-- Selection Cards -->
  108. <!-- Selection Cards -->
  109. <div class="px-5 pt-4 flex gap-3 h-[245px]">
  110. <!-- Card 1 (Big or Odd) -->
  111. <div id="cardBig" onclick="selectBigSmall('big')" class="bigsmall-card flex-1 rounded-2xl relative cursor-pointer transition-all duration-300 active:scale-[0.97] border-2 border-transparent overflow-hidden flex flex-col" style="background: linear-gradient(180deg, #FFF7DB 0%, #FFEEB3 100%);">
  112. <img src="/Millions/img/bigsmall/light_effect_1.png" class="absolute top-0 left-0 w-full opacity-60 pointer-events-none" alt="" />
  113. <img src="/Millions/img/bigsmall/light_effect_2.png" class="absolute bottom-12 left-0 w-full opacity-60 pointer-events-none" alt="" />
  114. <div class="flex flex-col items-center pt-4 pb-3 px-3 relative z-10 h-full justify-between">
  115. <div class="flex flex-col items-center w-full">
  116. <div class="h-[34px] flex items-center justify-center">
  117. <div class="text-[40px] font-[800] leading-none" style="-webkit-text-stroke: 1px #000; text-shadow: 1px 2px 2px rgba(0,0,0,0.15); color: @(isBigSmall ? "#A2FF00" : "#FFC700");">@(isBigSmall ? "Big" : "Odd")</div>
  118. </div>
  119. <div class="text-[11px] font-medium text-gray-500 mt-1 h-[28px] text-center leading-tight">
  120. @(isBigSmall ? "13 number from" : "Odd numbers") <br/> <b>@(isBigSmall ? "41-80" : "1, 3, 5, 7, 9")</b>
  121. </div>
  122. </div>
  123. <div class="flex items-center justify-center -space-x-2 my-2">
  124. @if (isBigSmall) {
  125. <img src="/Millions/img/bigsmall/ball_42_dark.png" class="w-12 h-12 relative z-[1]" alt="42" />
  126. <img src="/Millions/img/bigsmall/ball_88_big.png" class="w-16 h-16 relative z-[2]" alt="88" />
  127. <img src="/Millions/img/bigsmall/ball_54_dark.png" class="w-12 h-12 relative z-[1]" alt="54" />
  128. } else {
  129. <img src="/Millions/img/oddeven/ball_3.png" class="w-12 h-12 relative z-[1]" alt="3" />
  130. <img src="/Millions/img/oddeven/ball_1.png" class="w-16 h-16 relative z-[2]" alt="1" />
  131. <img src="/Millions/img/oddeven/ball_19.png" class="w-12 h-12 relative z-[1]" alt="19" />
  132. }
  133. </div>
  134. <button class="bigsmall-btn w-full py-2.5 rounded-xl font-black text-[15px] transition-all shadow-md" style="background: @themeColor; color: white;">@Lang.millions_select</button>
  135. </div>
  136. </div>
  137. <!-- Card 2 (Small or Even) -->
  138. <div id="cardSmall" onclick="selectBigSmall('small')" class="bigsmall-card flex-1 rounded-2xl relative cursor-pointer transition-all duration-300 active:scale-[0.97] border-2 border-transparent overflow-hidden flex flex-col" style="background: linear-gradient(180deg, #FFF7DB 0%, #FFEEB3 100%);">
  139. <img src="/Millions/img/bigsmall/light_effect_1.png" class="absolute top-0 left-0 w-full opacity-60 pointer-events-none" alt="" />
  140. <img src="/Millions/img/bigsmall/light_effect_2.png" class="absolute bottom-12 left-0 w-full opacity-60 pointer-events-none" alt="" />
  141. <div class="flex flex-col items-center pt-4 pb-3 px-3 relative z-10 h-full justify-between">
  142. <div class="flex flex-col items-center w-full">
  143. <div class="h-[34px] flex items-center justify-center">
  144. <div class="text-[40px] font-[800] leading-none italic" style="-webkit-text-stroke: 1px #000; text-shadow: 1px 2px 2px rgba(0,0,0,0.15); color: @(isBigSmall ? "#FF4157" : "#B33BD0");">@(isBigSmall ? "Small" : "Even")</div>
  145. </div>
  146. <div class="text-[11px] font-medium text-gray-500 mt-1 h-[28px] text-center leading-tight">
  147. @(isBigSmall ? "13 number from" : "Even numbers") <br/> <b>@(isBigSmall ? "01-40" : "0, 2, 4, 6, 8")</b>
  148. </div>
  149. </div>
  150. <div class="flex items-center justify-center -space-x-2 my-2">
  151. @if (isBigSmall) {
  152. <img src="/Millions/img/bigsmall/ball_5_blue.png" class="w-12 h-12 relative z-[1]" alt="5" />
  153. <img src="/Millions/img/bigsmall/ball_1_big.png" class="w-16 h-16 relative z-[2]" alt="1" />
  154. <img src="/Millions/img/bigsmall/ball_8_pink.png" class="w-12 h-12 relative z-[1]" alt="8" />
  155. } else {
  156. <img src="/Millions/img/oddeven/ball_66.png" class="w-12 h-12 relative z-[1]" alt="66" />
  157. <img src="/Millions/img/oddeven/ball_68.png" class="w-16 h-16 relative z-[2]" alt="68" />
  158. <img src="/Millions/img/oddeven/ball_88.png" class="w-12 h-12 relative z-[1]" alt="88" />
  159. }
  160. </div>
  161. <button class="bigsmall-btn w-full py-2.5 rounded-xl font-black text-[15px] transition-all shadow-md"
  162. style="background: @themeColor; color: white;">@Lang.millions_select</button>
  163. </div>
  164. </div>
  165. </div>
  166. <!-- You Choose + Prize Section -->
  167. <div class="px-5 pt-6 flex flex-col items-center gap-4">
  168. <div class="flex flex-col items-center">
  169. <span class="text-[15px] font-medium text-black">@Lang.millions_you_choose</span>
  170. <span id="choiceDisplay" class="text-[42px] font-[800] leading-tight" style="-webkit-text-stroke: 1px #000;">—</span>
  171. </div>
  172. <div class="flex flex-col items-center">
  173. <span class="text-[18px] font-medium text-black">@Lang.millions_prize_if_win</span>
  174. <div class="flex items-baseline gap-1">
  175. <span class="text-[28px] font-black text-black">@prizeAmount</span>
  176. <span class="text-[14px] font-black text-[#E56B23]">HTG</span>
  177. </div>
  178. </div>
  179. </div>
  180. </div>
  181. <!-- Bottom Action Bar -->
  182. <div class="p-4 bg-white border-t border-gray-200 z-[70] shadow-[0px_-4px_10px_rgba(0,0,0,0.03)] absolute bottom-0 w-full left-0">
  183. <div class="flex items-center justify-between">
  184. <div class="flex flex-col">
  185. <div class="flex items-center gap-1.5 mb-0.5">
  186. <span class="text-[11px] text-black font-black uppercase">@Lang.total_ticket:</span>
  187. <span id="bsTotalTicketCount" class="text-[12px] font-black text-black">0</span>
  188. </div>
  189. <span class="text-[11px] text-black font-black uppercase">@Lang.millions_estimated_ticket_price</span>
  190. <div class="flex items-baseline gap-1">
  191. <span id="bsTotalPrice" class="text-[28px] font-black text-black">0</span>
  192. <span class="text-[13px] font-black" style="color: @themeColor">HTG</span>
  193. </div>
  194. </div>
  195. <button id="bsPaymentBtn" onclick="bsShowPayment()" class="text-white text-[20px] font-black px-10 py-3.5 rounded-xl shadow-lg active:scale-95 transition-all" style="background: @themeColor;">
  196. @Lang.millions_payment
  197. </button>
  198. </div>
  199. </div>
  200. </div>
  201. </div>
  202. }
  203. else
  204. {
  205. <!-- ==================== Basic Pick 10 / Odd-Even GAME UI ==================== -->
  206. <div class="millions-buy-ticket-page main-container animate__animated animate__fadeIn overflow-hidden">
  207. <!-- Header -->
  208. <div class="header">
  209. <button onclick="location.href='@Url.Action("GameHome", "Home")'" class="back-btn" type="button">
  210. <i class="fa-solid fa-arrow-left"></i>
  211. </button>
  212. <div class="header-title">
  213. @if (Model.termType == Constants.PIC10_BASIC_CODE) { <text>Basic Pick 10</text> }
  214. else if (Model.termType == Constants.Millions_CODE) { <text>Millions</text> }
  215. else { @Lang.millions_odd_even }
  216. </div>
  217. </div>
  218. <div class="content-card">
  219. <!-- Sticky Term Info sub-header -->
  220. <div class="ticket-subheader">
  221. <div class="flex items-center justify-between">
  222. <div class="flex items-center gap-2 border border-[#E5E7EB] rounded-lg px-3 py-1.5 bg-gray-50">
  223. <i class="fa-regular fa-calendar text-[#0062FF] text-sm"></i>
  224. <span class="text-[12px] font-bold text-gray-700">@DateTime.Now.ToString("dddd, MMM dd, yyyy", new System.Globalization.CultureInfo("en-US"))</span>
  225. </div>
  226. <div class="flex items-center gap-1 ml-4 flex-1 justify-end">
  227. <span class="text-[12px] font-bold text-gray-400">@Lang.millions_round</span>
  228. <span class="text-[18px] font-black text-brown-main">#@(currentTerm?.id ?? "12459")</span>
  229. </div>
  230. </div>
  231. </div>
  232. <!-- Ticket Scrollable List Area -->
  233. <div id="ticketContainer" class="ticket-scroll-area p-4 flex flex-col gap-6 bg-[#F5F5F5] pb-6">
  234. @for (int i = 1; i <= (isFreeTicketMode ? 1 : 2); i++)
  235. {
  236. <div class="ticket-card relative group active:bg-gray-50 transition-colors cursor-pointer animate__animated animate__fadeIn">
  237. @if (!isFreeTicketMode)
  238. {
  239. <button onclick="removeEntry(this)" class="absolute -right-2 top-2 w-7 h-7 bg-[#FF0000] text-white rounded-full flex items-center justify-center text-[11px] shadow-sm z-[10] hover:bg-red-700 transition-all border-2 border-red-600">
  240. <i class="fa-solid fa-xmark"></i>
  241. </button>
  242. }
  243. <div class="absolute left-2 top-4 w-9 h-9 rounded-full border-[3px] border-white flex items-center justify-center text-white font-black text-sm shadow-md ticket-index">@i</div>
  244. <div class="flex flex-col gap-1 pl-8">
  245. <span class="text-[16px] font-extrabold text-[#333]">@Lang.millions_select_10_lucky_numbers</span>
  246. <div class="flex items-center gap-4">
  247. <div class="grid grid-cols-6 gap-y-2 gap-x-1 flex-1">
  248. @for (int j = 0; j < (Model.termType == Constants.PIC10_BASIC_CODE ? 10 : (Model.termType == Constants.Millions_CODE ? 6 : 15)); j++)
  249. {
  250. var isMbBall = Model.termType == Constants.Millions_CODE && j == 5;
  251. <div class="relative w-fit">
  252. <div class="ball-circle ball-empty@(isMbBall ? " ball-mb-circle" : "")"></div>
  253. @if (isMbBall)
  254. {
  255. <div class="absolute -bottom-[6px] -right-[4px] bg-[#FFD700] text-black text-[8.5px] font-black px-[2.5px] py-[1.5px] rounded-[3px] shadow-sm leading-none z-10 border border-[#EAA800]">MB</div>
  256. }
  257. </div>
  258. }
  259. </div>
  260. <div class="w-[90px] action-area flex flex-col gap-2">
  261. <button onclick="randomizeTicket(this)" class="rand-btn w-full bg-[#2B83F2] text-white flex items-center justify-center gap-1.5 py-1.5 rounded-xl font-extrabold text-[11px] shadow-sm active:scale-95 transition-all">
  262. <i class="fa-solid fa-arrows-rotate text-[10px]"></i> @Lang.Random
  263. </button>
  264. <div class="edit-delete-group hidden flex flex-col gap-2">
  265. <button onclick="editFullTicket(this)" class="w-full bg-[#16A34A] text-white flex items-center justify-center gap-1.5 py-1.5 rounded-xl font-black text-[12px] shadow-sm active:scale-95 transition-all">
  266. <img src="/Millions/img/icons/icon_edit.png" onerror="this.style.display='none'" class="w-[16px] h-[16px] object-contain"> @Lang.millions_edit
  267. </button>
  268. <button onclick="clearTicket(this)" class="w-full bg-[#A855F7] text-white flex items-center justify-center gap-1.5 py-1.5 rounded-xl font-black text-[12px] shadow-sm active:scale-95 transition-all">
  269. <img src="/Millions/img/icons/icon_delete.png" onerror="this.style.display='none'" class="w-[16px] h-[16px] object-contain"> @Lang.millions_delete
  270. </button>
  271. </div>
  272. </div>
  273. </div>
  274. </div>
  275. </div>
  276. }
  277. @if (!isFreeTicketMode)
  278. {
  279. <div class="w-full pt-2" id="addButtonWrapper">
  280. <button onclick="addNewTicket()" class="w-fit min-w-[220px] mx-auto bg-[#2B83F2] text-white font-black text-md py-2.5 px-6 rounded-xl shadow-md border-b-[4px] border-[#1F66D6] active:translate-y-[1px] active:border-b-[1px] transition-all flex items-center justify-center gap-2">
  281. @Lang.millions_add_more_ticket
  282. </button>
  283. </div>
  284. }
  285. <div class="h-4"></div>
  286. </div>
  287. <!-- Bottom Action Bar -->
  288. <div class="ticket-bottom-bar p-4 bg-white border-t border-gray-200 z-[70] shadow-[0px_-4px_10px_rgba(0,0,0,0.03)]">
  289. <div class="flex items-center justify-between">
  290. <div class="flex flex-col">
  291. <div class="flex items-center gap-1.5 mb-0.5">
  292. <span class="text-[11px] text-black font-black uppercase">@Lang.total_ticket:</span>
  293. <span id="totalTicketCount" class="text-[12px] font-black text-black">0</span>
  294. </div>
  295. <span class="text-[11px] text-black font-black uppercase">@Lang.millions_estimated_ticket_price</span>
  296. <div class="flex items-baseline gap-1">
  297. <span id="totalPrice" class="text-[28px] font-black text-black">0</span>
  298. <span class="text-[13px] font-black text-[#0062FF]">HTG</span>
  299. </div>
  300. </div>
  301. <button onclick="preparePayment()" class="bg-[#0062FF] text-white text-[20px] font-black px-10 py-3.5 rounded-xl shadow-[0px_6px_15px_rgba(238,0,51,0.25)] active:scale-95 transition-all">
  302. @Lang.millions_payment
  303. </button>
  304. </div>
  305. </div>
  306. </div>
  307. </div>
  308. }
  309. <!-- Payment channel selection -->
  310. <div id="paymentChannelModal" class="hidden fixed inset-0 z-[140] bg-black/70 backdrop-blur-[3px] items-center justify-center p-4 font-bricolage">
  311. <div class="relative w-full max-w-[370px] overflow-hidden rounded-[32px] bg-[#F8F9FC] shadow-[0_28px_70px_rgba(0,0,0,0.35)] animate__animated animate__zoomIn animate__faster">
  312. <div class="relative overflow-hidden bg-gradient-to-br from-[#0062FF] to-[#003FA8] px-6 pb-7 pt-6 text-white">
  313. <div class="absolute -right-8 -top-10 h-32 w-32 rounded-full bg-white/10"></div><div class="absolute -bottom-12 -left-8 h-28 w-28 rounded-full bg-black/10"></div>
  314. <button type="button" onclick="hidePaymentChannelModal()" class="absolute right-4 top-4 z-10 flex h-9 w-9 items-center justify-center rounded-full bg-white/15 text-white transition hover:bg-white/25 active:scale-90" aria-label="Close"><i class="fa-solid fa-xmark"></i></button>
  315. <div class="relative z-[1] flex items-center gap-4"><div class="flex h-14 w-14 shrink-0 items-center justify-center rounded-2xl bg-white text-[25px] text-[#0062FF] shadow-lg"><i class="fa-solid fa-wallet"></i></div><div><h2 class="text-[21px] font-black leading-tight">@Lang.payment_method_title</h2><p class="mt-1 text-[13px] font-semibold text-white/80">@Lang.payment_method_subtitle</p></div></div>
  316. </div>
  317. <div class="space-y-3 p-5">
  318. <button type="button" onclick="selectPaymentChannel('@Constants.BASIC_WALLET_TICKET')" class="group flex w-full items-center gap-4 rounded-[20px] border-2 border-transparent bg-white p-4 text-left shadow-[0_5px_18px_rgba(31,41,55,0.07)] transition hover:-translate-y-0.5 hover:border-[#0062FF] active:scale-[0.98]">
  319. <span class="flex h-12 w-12 shrink-0 items-center justify-center rounded-2xl bg-[#E7F0FF] text-[21px] text-[#0062FF]"><i class="fa-solid fa-mobile-screen-button"></i></span><span class="min-w-0 flex-1"><span class="block text-[16px] font-black text-[#171A24]">@Lang.payment_main_account</span><span class="mt-0.5 block text-[12px] font-semibold text-gray-500">@Lang.payment_main_account_desc</span></span><span class="flex h-8 w-8 items-center justify-center rounded-full bg-gray-100 text-gray-400 transition group-hover:bg-[#0062FF] group-hover:text-white"><i class="fa-solid fa-chevron-right text-[12px]"></i></span>
  320. </button>
  321. <button type="button" onclick="selectPaymentChannel('@Constants.NATCASH_WALLET_TICKET')" class="group flex w-full items-center gap-4 rounded-[20px] border-2 border-transparent bg-white p-4 text-left shadow-[0_5px_18px_rgba(31,41,55,0.07)] transition hover:-translate-y-0.5 hover:border-[#0A9800] active:scale-[0.98]">
  322. <span class="flex h-12 w-12 shrink-0 items-center justify-center rounded-2xl bg-[#E7F8E6] text-[21px] text-[#0A9800]"><i class="fa-solid fa-wallet"></i></span><span class="min-w-0 flex-1"><span class="flex items-center gap-2 text-[16px] font-black text-[#171A24]">@Lang.payment_wallet <span class="rounded-full bg-[#E7F8E6] px-2 py-0.5 text-[9px] font-black text-[#0A9800]">PIN</span></span><span class="mt-0.5 block text-[12px] font-semibold text-gray-500">@Lang.payment_wallet_desc</span></span><span class="flex h-8 w-8 items-center justify-center rounded-full bg-gray-100 text-gray-400 transition group-hover:bg-[#0A9800] group-hover:text-white"><i class="fa-solid fa-chevron-right text-[12px]"></i></span>
  323. </button>
  324. <p class="pt-1 text-center text-[11px] font-semibold text-gray-400"><i class="fa-solid fa-shield-halved mr-1 text-[#0A9800]"></i>@Lang.payment_security_message</p>
  325. </div>
  326. </div>
  327. </div>
  328. <!-- Order Summary Screen (from Figma) -->
  329. <div id="orderSummaryModal" class="hidden fixed inset-0 z-[150] bg-black/60 flex items-center justify-center p-0 font-bricolage backdrop-blur-[2px]">
  330. <div class="w-full h-full bg-white flex flex-col animate__animated animate__fadeInRight animate__faster shadow-2xl relative lg:max-w-[414px]">
  331. <!-- Header đồng bộ tuyệt đối -->
  332. <div class="buy-ticket-modal-header sticky top-0 z-[60]">
  333. <button onclick="hideOrderSummary()" class="back-btn" type="button">
  334. <i class="fa-solid fa-arrow-left"></i>
  335. </button>
  336. <div class="header-title">
  337. @if (Model.termType == Constants.PIC10_BASIC_CODE) { <text>Basic Pick 10</text> }
  338. else if (Model.termType == Constants.Millions_CODE) { <text>Millions</text> }
  339. else if (Model.termType == Constants.PIC10_BIGSMALL_CODE) { @Lang.millions_big_small }
  340. else { @Lang.millions_odd_even }
  341. </div>
  342. </div>
  343. <!-- Content Area: Split into scrollable list and static summary -->
  344. <div class="flex-1 flex flex-col min-h-0 bg-gray-50">
  345. <!-- Scrollable Ticket List -->
  346. <div class="flex-1 overflow-y-auto p-5 pb-2">
  347. <div class="flex justify-between items-center px-1 mb-3">
  348. <span class="text-[14px] font-bold text-gray-800">Selected Tickets</span>
  349. <span class="text-[14px] font-bold text-gray-800">@Lang.price</span>
  350. </div>
  351. <!-- Ticket List -->
  352. <div id="summaryTicketList" class="flex flex-col gap-4">
  353. <!-- Populated dynamically via JS -->
  354. </div>
  355. </div>
  356. <!-- Static Summary Info (Dates & Totals) -->
  357. <div class="shrink-0 bg-white p-5 space-y-4 border-t border-gray-100 shadow-[0px_-10px_20px_rgba(0,0,0,0.03)]">
  358. <!-- Dates Section -->
  359. <div class="grid grid-cols-2 gap-4">
  360. <div class="space-y-1">
  361. <span class="text-[11px] font-black text-[#0062FF] block text-center uppercase tracking-tight">@Lang.date_purchase</span>
  362. <div class="bg-white border border-gray-100 rounded-xl py-2.5 px-2 text-center font-black text-[11px] text-gray-700 shadow-sm">
  363. @DateTime.Now.ToString("dddd MMM dd, yyyy", new System.Globalization.CultureInfo("en-US"))
  364. </div>
  365. </div>
  366. <div class="space-y-1">
  367. <span class="text-[11px] font-black text-[#0062FF] block text-center uppercase tracking-tight">@Lang.draw_date</span>
  368. <div class="bg-white border border-gray-100 rounded-xl py-2.5 px-2 text-center font-black text-[11px] text-gray-700 shadow-sm" id="summaryResultDate">
  369. @{
  370. DateTime drawDate;
  371. bool isValidDate = DateTime.TryParse(currentTerm?.date_end, out drawDate);
  372. if (isValidDate) {
  373. @drawDate.ToString("dddd MMM dd, yyyy", new System.Globalization.CultureInfo("en-US"))
  374. } else {
  375. @:N/A
  376. }
  377. }
  378. </div>
  379. </div>
  380. </div>
  381. <!-- Totals Section -->
  382. <div class="space-y-1 px-1">
  383. <div class="flex justify-between items-center">
  384. <span class="text-[14px] font-bold text-black">@Lang.total_ticket</span>
  385. <span id="summaryTotalCount" class="text-[16px] font-black text-black">0</span>
  386. </div>
  387. <div class="flex justify-between items-center">
  388. <span class="text-[14px] font-bold text-black">@Lang.total_money</span>
  389. <div class="flex items-baseline gap-1">
  390. <span id="summaryTotalAmount" class="text-[32px] font-black text-black">0</span>
  391. <span class="text-[16px] font-black text-[#0062FF]">HTG</span>
  392. </div>
  393. </div>
  394. </div>
  395. </div>
  396. </div>
  397. <div class="shrink-0 p-4 bg-white border-t border-gray-100 grid grid-cols-2 gap-4 pb-8 shadow-[0px_-4px_20px_rgba(0,0,0,0.05)]">
  398. <button onclick="hideOrderSummary()" class="bg-[#FFB000] text-white py-2.5 rounded-2xl font-black text-[18px] shadow-lg shadow-orange-200 active:scale-95 transition-all uppercase tracking-wide">
  399. @Lang.reorder
  400. </button>
  401. <button id="finalPaymentBtn" onclick="confirmCheckout(this)" class="bg-[#0062FF] text-white py-2.5 rounded-2xl font-black text-[18px] shadow-lg shadow-red-200 active:scale-95 transition-all uppercase tracking-wide">
  402. @Lang.millions_payment
  403. </button>
  404. </div>
  405. </div>
  406. </div>
  407. <!-- OTP Verification Modal (V2 Design Overhaul - Matching Figma) -->
  408. <div id="otpModal" class="fixed inset-0 bg-black/80 backdrop-blur-sm z-[200] hidden items-center justify-center p-4">
  409. <!-- Outer container to hold the modal + external overflowing elements (like stars/glitter) -->
  410. <div class="relative w-full max-w-[360px] mt-6 animate__animated animate__zoomIn animate__faster">
  411. <!-- Glitter effect (external to the white box) -->
  412. <div class="absolute -top-[100px] -left-[10px] w-[380px] h-[160px] mix-blend-screen pointer-events-none z-0 overflow-visible opacity-90" style="background: url('/Millions/img/modal/otp_glitter.png') no-repeat center center; background-size: contain;"></div>
  413. <!-- The White Box -->
  414. <div class="bg-white rounded-[32px] w-full relative z-10 shadow-[0px_0px_30px_rgba(255,255,255,0.15)] pb-5 border border-yellow-500/20">
  415. <!-- Floating Coins from Figma -->
  416. <div class="absolute -top-[40px] -right-[10px] z-[80] w-[120px] pointer-events-none drop-shadow-2xl">
  417. <img src="/Millions/img/modal/otp_coins.png" class="w-full object-contain" />
  418. </div>
  419. <!-- Red Header Section -->
  420. <div class="relative w-full pt-4 pb-4 bg-[#0062FF] rounded-t-[32px] flex flex-col items-center justify-center z-20" style="border-bottom-left-radius: 50% 20px; border-bottom-right-radius: 50% 20px;">
  421. <h2 class="text-white text-[17px] sm:text-[20px] font-[900] uppercase tracking-wide relative z-10 font-bricolage drop-shadow-md text-center px-4 leading-tight w-[80%] mr-auto">@Lang.millions_confirm_purchase</h2>
  422. </div>
  423. <!-- Modal Body Content -->
  424. <div class="relative px-6 pt-3 flex flex-col items-center gap-3 z-10">
  425. <!-- Shield Icon: mix-blend-mode multiply to remove the white square background -->
  426. <div class="relative w-full flex justify-center mb-0 mt-1">
  427. <img src="/Millions/img/modal/otp_shield.png" alt="Success" class="w-[90px] object-contain">
  428. </div>
  429. <!-- Info Rows (Aligned exactly like screenshot) -->
  430. <div class="w-full flex flex-col gap-2 items-center mt-0">
  431. <!-- Ticket Info -->
  432. <div class="grid grid-cols-[90px_auto] items-center gap-3 w-full justify-center">
  433. <span class="text-[15px] font-bold text-[#555] text-right font-bricolage">@Lang.millions_ticket</span>
  434. <div id="otpTicketTypeContainer" class="flex justify-start">
  435. <img id="otpTicketTypeImg" src="/Millions/img/even_text.png" class="h-6 object-contain drop-shadow-sm" />
  436. </div>
  437. </div>
  438. <!-- Amount Info -->
  439. <div class="grid grid-cols-[90px_auto] items-center gap-3 w-full justify-center">
  440. <span class="text-[15px] font-bold text-[#555] text-right font-bricolage">@Lang.millions_amount</span>
  441. <div class="flex items-baseline gap-1.5 justify-start">
  442. <span id="otpTotalAmount" class="text-[22px] font-[900] text-black leading-none tracking-tight">1.000</span>
  443. <span class="text-[15px] font-[900] text-[#F37021] font-bricolage">HTG</span>
  444. </div>
  445. </div>
  446. </div>
  447. <p id="paymentCodeInstruction" class="text-[14px] font-bold text-[#555] text-center px-4 leading-snug mt-0 font-bricolage">@Lang.millions_otp_instruction</p>
  448. <!-- 6-Slots OTP Grid (Large Gray Blocks) -->
  449. <div class="flex items-center justify-center gap-2 w-full mt-1 px-2" id="otpInputs">
  450. @for (int i = 1; i <= 6; i++)
  451. {
  452. <input type="tel" maxlength="1" id="otp@(i)"
  453. oninput="moveToNext(this, '@(i < 6 ? "otp" + (i + 1) : "")')"
  454. class="w-[42px] h-[48px] bg-[#757575] rounded-[10px] text-white text-[26px] font-bold text-center appearance-none focus:bg-[#555] focus:scale-[1.03] focus:ring-2 focus:ring-[#0062FF]/50 transition-all outline-none shadow-inner otp-slot"
  455. placeholder="-">
  456. }
  457. </div>
  458. <div id="walletPinContainer" class="hidden w-full mt-1 px-2">
  459. <div class="relative">
  460. <i class="fa-solid fa-lock absolute left-4 top-1/2 -translate-y-1/2 text-[#0A9800]"></i>
  461. <input id="walletPinInput" type="password" inputmode="numeric" autocomplete="off"
  462. oninput="this.value = this.value.replace(/[^0-9]/g, ''); updateWalletPinState()"
  463. class="h-[52px] w-full rounded-[14px] border-2 border-gray-200 bg-gray-50 pl-11 pr-12 text-center text-[22px] font-black tracking-[0.3em] text-gray-800 outline-none transition focus:border-[#0A9800] focus:bg-white focus:ring-4 focus:ring-green-100"
  464. placeholder="••••">
  465. <button type="button" onclick="toggleWalletPinVisibility()" class="absolute right-4 top-1/2 -translate-y-1/2 text-gray-400 active:scale-90" aria-label="Show PIN"><i id="walletPinEye" class="fa-solid fa-eye"></i></button>
  466. </div>
  467. </div>
  468. <!-- OTP Error Message -->
  469. <p id="otpError" class="text-[#0062FF] font-bold text-[14px] text-center mb-1 hidden animate__animated animate__shakeX"></p>
  470. <!-- Timer & Resend -->
  471. <div class="flex flex-col items-center gap-1 mt-0">
  472. <span id="otpTimer" class="text-[15px] font-black text-[#0062FF] font-bricolage">60s</span>
  473. <button id="resendOtpBtn" onclick="sendOtpRequest()" class="hidden text-[14px] font-black text-[#0A9800] underline decoration-solid underline-offset-[3px] transition-all hover:text-[#087a00] disabled:opacity-40 disabled:no-underline font-bricolage">@Lang.millions_request_new_otp</button>
  474. </div>
  475. <!-- Action Buttons: Cancel / Confirm -->
  476. <div class="grid grid-cols-2 gap-3 w-full mt-2">
  477. <button onclick="hideOtpModal()" class="bg-[#FF0000] text-white py-[12px] rounded-[16px] font-[900] text-[17px] shadow-[0_8px_15px_rgba(255,0,0,0.2)] active:scale-95 transition-all tracking-wide">@Lang.millions_cancel</button>
  478. <button id="otpConfirmBtn" disabled onclick="finalizePurchase(this)" class="bg-[#0A9800] text-white py-[12px] rounded-[16px] font-[900] text-[17px] shadow-[0_8px_15px_rgba(10,152,0,0.2)] active:scale-95 transition-all tracking-wide disabled:opacity-50 disabled:grayscale disabled:cursor-not-allowed">@Lang.millions_confirm</button>
  479. </div>
  480. </div>
  481. </div>
  482. </div>
  483. </div>
  484. @section Scripts {
  485. <script>
  486. const maxBalls = @(Model.termType == Constants.PIC10_BASIC_CODE ? 10 : (Model.termType == Constants.Millions_CODE ? 6 : (Model.termType == Constants.PIC10_BIGSMALL_CODE ? 13 : 15)));
  487. const isFreeTicketMode = @(isFreeTicketMode ? "true" : "false");
  488. const freeTicketPromotionCode = @Html.Raw(System.Text.Json.JsonSerializer.Serialize(freeTicketPromotionCode));
  489. function reindexTickets() {
  490. $(".ticket-card").each(function(index) {
  491. $(this).find(".ticket-index").text(index + 1);
  492. });
  493. }
  494. function removeEntry(btn) {
  495. var card = $(btn).closest('.ticket-card');
  496. card.addClass('animate__animated animate__fadeOutRight animate__faster');
  497. setTimeout(function() {
  498. card.remove();
  499. reindexTickets();
  500. updateTotalPrice();
  501. }, 300);
  502. }
  503. function randomizeTicket(btn) {
  504. var icon = $(btn).find('i');
  505. icon.addClass('fa-spin');
  506. var card = $(btn).closest('.ticket-card');
  507. var balls = card.find('.ball-circle');
  508. // Generate unique random numbers
  509. var numbers = [];
  510. if ("@Model.termType" === "@Constants.Millions_CODE") {
  511. // First 5 standard balls (1-70)
  512. while(numbers.length < 5){
  513. var r = Math.floor(Math.random() * 70) + 1;
  514. if(numbers.indexOf(r) === -1) numbers.push(r);
  515. }
  516. numbers.sort((a, b) => a - b);
  517. // 6th Mega Ball (1-24)
  518. var mb = Math.floor(Math.random() * 24) + 1;
  519. numbers.push(mb);
  520. } else {
  521. while(numbers.length < maxBalls){
  522. var r = Math.floor(Math.random() * 80) + 1;
  523. if(numbers.indexOf(r) === -1) numbers.push(r);
  524. }
  525. numbers.sort((a, b) => a - b);
  526. }
  527. // Update balls with staggered animation
  528. balls.each(function(index) {
  529. var ball = $(this);
  530. setTimeout(function() {
  531. var formattedNum = numbers[index].toString().padStart(2, '0');
  532. ball.removeClass('ball-empty').addClass('ball-filled').text(formattedNum);
  533. ball.addClass('animate__animated animate__flipInY');
  534. if(index === maxBalls - 1) {
  535. setTimeout(() => {
  536. icon.removeClass('fa-spin');
  537. // Replace button with Edit/Delete group
  538. $(btn).hide();
  539. card.find('.edit-delete-group').removeClass('hidden').addClass('animate__animated animate__fadeInRight animate__faster');
  540. updateTotalPrice();
  541. }, 300);
  542. }
  543. }, index * 40);
  544. });
  545. }
  546. function clearTicket(btn) {
  547. var card = $(btn).closest('.ticket-card');
  548. var balls = card.find('.ball-circle');
  549. balls.removeClass('ball-filled animate__animated animate__flipInY shadow-inner').addClass('ball-empty').text('');
  550. // Hide Edit/Delete group and show Rand button
  551. card.find('.edit-delete-group').addClass('hidden');
  552. card.find('.rand-btn').show().addClass('animate__animated animate__fadeInLeft animate__faster');
  553. updateTotalPrice();
  554. }
  555. // Open Number Picker Modal
  556. var activeBall = null;
  557. var activeTicketCard = null;
  558. var selectedNumbers = []; // For Edit mode, this will be an array of fixed size with nulls
  559. var isMultiMode = false;
  560. function editFullTicket(btn) {
  561. isMultiMode = true;
  562. activeTicketCard = $(btn).closest('.ticket-card');
  563. activeBall = null;
  564. // Initialize fixed slots for slot-based editing
  565. selectedNumbers = new Array(maxBalls).fill(null);
  566. let existing = [];
  567. activeTicketCard.find('.ball-filled').each(function() {
  568. var num = $(this).text().trim();
  569. if(num) existing.push(num);
  570. });
  571. // Initial fill from left to right
  572. existing.forEach((num, idx) => {
  573. if(idx < maxBalls) selectedNumbers[idx] = num;
  574. });
  575. openNumberPicker(true);
  576. }
  577. function openNumberPicker(isMulti) {
  578. var modal = $("#numberPickerModal");
  579. var grid = $("#numberGrid");
  580. var title = $("#modalTitle");
  581. var countArea = $(".selection-count-area");
  582. var confirmBtn = $("#confirmBtn");
  583. grid.empty();
  584. if (isMulti) {
  585. title.text("Edit Ticket");
  586. countArea.removeClass('hidden');
  587. confirmBtn.removeClass('hidden').text("@Lang.confirm");
  588. } else {
  589. title.text("Pick Number");
  590. countArea.addClass('hidden');
  591. confirmBtn.removeClass('hidden').text("@Lang.confirm");
  592. }
  593. updateSelectionCount();
  594. liveUpdateTicket();
  595. // Focus card: append a tall spacer so even the last ticket can scroll up
  596. // above the number-picker sheet (a flex column container does not honor
  597. // padding-bottom as scrollable space), then bring the card to the top.
  598. if (activeTicketCard && activeTicketCard.length) {
  599. var container = $("#ticketContainer");
  600. $("#editScrollSpacer").remove();
  601. container.append('<div id="editScrollSpacer" aria-hidden="true" style="height:100vh;flex-shrink:0;"></div>');
  602. var scrollPos = activeTicketCard.offset().top - container.offset().top + container.scrollTop();
  603. container.stop().animate({ scrollTop: scrollPos - 15 }, 500);
  604. }
  605. var isMillions = "@Model.termType" === "@Constants.Millions_CODE";
  606. if (isMillions) {
  607. // Millions Split UI: 1-70 (cols-7) | Divider | 1-24 (cols-3)
  608. var splitHtml = `
  609. <div class="flex gap-3 h-full">
  610. <!-- Left: Standard (1-70) -->
  611. <div class="flex-1 flex flex-col h-full min-w-0">
  612. <span class="text-[10px] font-black text-blue-600 mb-1 uppercase tracking-wider shrink-0">Standard (1-70)</span>
  613. <div class="flex-1 overflow-y-auto grid grid-cols-7 gap-1 pr-1 pb-2" id="standardGrid" style="scrollbar-width: thin; scrollbar-color: #0062FF transparent;"></div>
  614. </div>
  615. <!-- Divider -->
  616. <div class="w-[1px] bg-gray-300 self-stretch my-1 shrink-0"></div>
  617. <!-- Right: Mega Ball (1-24) -->
  618. <div class="w-[105px] flex flex-col h-full shrink-0">
  619. <span class="text-[10px] font-black text-red-600 mb-1 uppercase tracking-wider shrink-0 text-center">Mega (1-24)</span>
  620. <div class="flex-1 overflow-y-auto grid grid-cols-3 gap-1 pl-1 pb-2" id="megaGrid" style="scrollbar-width: thin; scrollbar-color: #E3132D transparent;"></div>
  621. </div>
  622. </div>
  623. `;
  624. grid.append(splitHtml);
  625. var standardContainer = $("#standardGrid");
  626. var megaContainer = $("#megaGrid");
  627. // Populate Standard (1-70)
  628. for (var i = 1; i <= 70; i++) {
  629. var formatted = i.toString().padStart(2, '0');
  630. var isSelected = selectedNumbers.slice(0, 5).includes(formatted);
  631. var btnClass = isSelected
  632. ? 'bg-[#0062FF] text-white shadow-sm'
  633. : 'bg-white text-gray-700 hover:bg-gray-100 border border-gray-100';
  634. standardContainer.append(`<button type="button" class="std-num-btn w-[26px] h-[26px] rounded-full font-black text-[9.5px] transition-all flex items-center justify-center ${btnClass}">${formatted}</button>`);
  635. }
  636. // Populate Mega (1-24)
  637. for (var i = 1; i <= 24; i++) {
  638. var formatted = i.toString().padStart(2, '0');
  639. var isSelected = selectedNumbers[5] === formatted;
  640. var btnClass = isSelected
  641. ? 'bg-[#E3132D] text-white shadow-sm'
  642. : 'bg-white text-gray-700 hover:bg-gray-100 border border-gray-100';
  643. megaContainer.append(`<button type="button" class="mega-num-btn w-[26px] h-[26px] rounded-full font-black text-[9.5px] transition-all flex items-center justify-center ${btnClass}">${formatted}</button>`);
  644. }
  645. // Standard Grid Click Handler
  646. standardContainer.on('click', '.std-num-btn', function(e) {
  647. e.preventDefault();
  648. var $btn = $(this);
  649. var picked = $btn.text().trim();
  650. if (isMultiMode) {
  651. var stdSelected = selectedNumbers.slice(0, 5);
  652. if (stdSelected.includes(picked)) {
  653. // Deselect
  654. let idx = selectedNumbers.indexOf(picked);
  655. if (idx !== -1 && idx < 5) selectedNumbers[idx] = null;
  656. $btn.removeClass('bg-[#0062FF] text-white shadow-sm').addClass('bg-white text-gray-700 border border-gray-100');
  657. } else {
  658. // Select standard ball (first empty slot in 0-4)
  659. let emptyIdx = -1;
  660. for (var k = 0; k < 5; k++) {
  661. if (selectedNumbers[k] === null) {
  662. emptyIdx = k;
  663. break;
  664. }
  665. }
  666. if (emptyIdx !== -1) {
  667. selectedNumbers[emptyIdx] = picked;
  668. $btn.addClass('bg-[#0062FF] text-white shadow-sm').removeClass('bg-white text-gray-700 border border-gray-100');
  669. }
  670. }
  671. liveUpdateTicket();
  672. }
  673. updateSelectionCount();
  674. });
  675. // Mega Grid Click Handler
  676. megaContainer.on('click', '.mega-num-btn', function(e) {
  677. e.preventDefault();
  678. var $btn = $(this);
  679. var picked = $btn.text().trim();
  680. if (isMultiMode) {
  681. if (selectedNumbers[5] === picked) {
  682. // Deselect Mega Ball
  683. selectedNumbers[5] = null;
  684. $btn.removeClass('bg-[#E3132D] text-white shadow-sm').addClass('bg-white text-gray-700 border border-gray-100');
  685. } else {
  686. // Overwrite Mega Ball
  687. selectedNumbers[5] = picked;
  688. megaContainer.find('.mega-num-btn').removeClass('bg-[#E3132D] text-white shadow-sm').addClass('bg-white text-gray-700 border border-gray-100');
  689. $btn.addClass('bg-[#E3132D] text-white shadow-sm').removeClass('bg-white text-gray-700 border border-gray-100');
  690. }
  691. liveUpdateTicket();
  692. }
  693. updateSelectionCount();
  694. });
  695. } else {
  696. // Default Pick 10 UI: 1-80 Grid
  697. var normalHtml = `<div class="grid grid-cols-10 gap-1.5 w-full pb-2 h-full overflow-y-auto" id="normalGrid"></div>`;
  698. grid.append(normalHtml);
  699. var normalContainer = $("#normalGrid");
  700. for (var i = 1; i <= 80; i++) {
  701. var formatted = i.toString().padStart(2, '0');
  702. var isSelected = selectedNumbers.includes(formatted);
  703. var btnClass = isSelected
  704. ? 'bg-[#0062FF] text-white shadow-md scale-105 active-num'
  705. : 'bg-white text-gray-700 hover:bg-gray-100 border border-gray-100';
  706. normalContainer.append(`<button type="button" class="num-btn w-8 h-8 rounded-full font-black text-[11px] transition-all focus:outline-none flex items-center justify-center ${btnClass}">${formatted}</button>`);
  707. }
  708. normalContainer.on('click', '.num-btn', function(e) {
  709. e.preventDefault();
  710. var $btn = $(this);
  711. var picked = $btn.text().trim();
  712. if (isMultiMode) {
  713. if (selectedNumbers.includes(picked)) {
  714. let idx = selectedNumbers.indexOf(picked);
  715. if (idx !== -1) selectedNumbers[idx] = null;
  716. $btn.removeClass('bg-[#0062FF] text-white shadow-md scale-105 active-num').addClass('bg-white text-gray-700 border border-gray-100');
  717. } else {
  718. let firstEmpty = selectedNumbers.indexOf(null);
  719. if (firstEmpty !== -1) {
  720. selectedNumbers[firstEmpty] = picked;
  721. $btn.addClass('bg-[#0062FF] text-white shadow-md scale-105 active-num').removeClass('bg-white text-gray-700 border border-gray-100');
  722. }
  723. }
  724. liveUpdateTicket();
  725. }
  726. updateSelectionCount();
  727. });
  728. }
  729. modal.removeClass('hidden').addClass('flex');
  730. modal.find('.modal-content').removeClass('animate__zoomOut').addClass('animate__zoomIn');
  731. }
  732. function updateSelectionCount() {
  733. var count = selectedNumbers.filter(n => n !== null && n !== "").length;
  734. $("#selectionCount").text(count);
  735. $("#maxSelectionCount").text(maxBalls);
  736. var isValid = isMultiMode ? (count === maxBalls) : (count === 1);
  737. if (isValid) {
  738. $("#confirmBtn").prop('disabled', false).removeClass('opacity-50 cursor-not-allowed').addClass('cursor-pointer');
  739. } else {
  740. $("#confirmBtn").prop('disabled', true).addClass('opacity-50 cursor-not-allowed').removeClass('cursor-pointer');
  741. }
  742. }
  743. function liveUpdateTicket() {
  744. if (!activeTicketCard) return;
  745. var balls = activeTicketCard.find('.ball-circle');
  746. if (isMultiMode) {
  747. // Update Ticket Card balls
  748. for(var i=0; i < maxBalls; i++){
  749. var val = selectedNumbers[i];
  750. if (i < balls.length) {
  751. var tBall = $(balls[i]);
  752. if (val) {
  753. tBall.removeClass('ball-empty').addClass('ball-filled').text(val);
  754. } else {
  755. tBall.removeClass('ball-filled').addClass('ball-empty').text('');
  756. }
  757. }
  758. }
  759. // Toggle Rand/Edit button visibility
  760. if (selectedNumbers.filter(n => n !== null && n !== "").length > 0) {
  761. activeTicketCard.find('.rand-btn').hide();
  762. activeTicketCard.find('.edit-delete-group').removeClass('hidden').show();
  763. } else {
  764. activeTicketCard.find('.rand-btn').show();
  765. activeTicketCard.find('.edit-delete-group').addClass('hidden');
  766. }
  767. updateTotalPrice();
  768. }
  769. }
  770. // All immediate jQuery event bindings must wait for DOM + jQuery to be ready
  771. document.addEventListener("DOMContentLoaded", function() {
  772. // Click on a ball (filled or empty) to edit the full ticket
  773. $(document).on('click', '.ball-filled, .ball-empty', function() {
  774. editFullTicket(this);
  775. });
  776. updateTotalPrice();
  777. // Confirm button handler
  778. $(document).off('click', '#confirmBtn').on('click', '#confirmBtn', function(e) {
  779. e.preventDefault();
  780. // Check non-null count
  781. var count = selectedNumbers.filter(n => n !== null && n !== "").length;
  782. if (count === 0) return;
  783. if (isMultiMode && activeTicketCard && activeTicketCard.length > 0) {
  784. // Double check validation for Mega Ball before confirming
  785. if ("@Model.termType" === "@Constants.Millions_CODE") {
  786. var mbVal = selectedNumbers[5];
  787. if (mbVal) {
  788. var mbValInt = parseInt(mbVal);
  789. if (mbValInt < 1 || mbValInt > 24) {
  790. showNotification("🎱 @Lang.millions_megaball_invalid_title\n@Lang.millions_megaball_invalid_desc", "warning");
  791. return;
  792. }
  793. }
  794. }
  795. // Keep exact selected order, do not sort!
  796. var finalSorted = [];
  797. if ("@Model.termType" === "@Constants.Millions_CODE") {
  798. var first5 = selectedNumbers.slice(0, 5).filter(n => n !== null && n !== "");
  799. var mb = selectedNumbers[5];
  800. finalSorted = first5;
  801. if (mb) finalSorted.push(mb);
  802. } else {
  803. finalSorted = selectedNumbers.filter(n => n !== null && n !== "");
  804. }
  805. var balls = activeTicketCard.find('.ball-circle');
  806. balls.removeClass('ball-filled').addClass('ball-empty').text('');
  807. finalSorted.forEach((num, index) => {
  808. if (index < balls.length) {
  809. $(balls[index]).removeClass('ball-empty').addClass('ball-filled').text(num);
  810. $(balls[index]).addClass('animate__animated animate__pulse');
  811. setTimeout(() => $(balls[index]).removeClass('animate__animated animate__pulse'), 500);
  812. }
  813. });
  814. activeTicketCard.find('.rand-btn').hide();
  815. activeTicketCard.find('.edit-delete-group').removeClass('hidden').show();
  816. updateTotalPrice();
  817. } else if (!isMultiMode && activeBall && activeBall.length > 0) {
  818. // Single mode: Update only the active ball
  819. activeBall.text(selectedNumbers[0]).removeClass('ball-empty').addClass('ball-filled');
  820. activeBall.addClass('animate__animated animate__pulse');
  821. setTimeout(() => activeBall.removeClass('animate__animated animate__pulse'), 500);
  822. var card = activeBall.closest('.ticket-card');
  823. var filledBalls = card.find('.ball-filled').length;
  824. if (filledBalls > 0) {
  825. card.find('.rand-btn').hide();
  826. card.find('.edit-delete-group').removeClass('hidden').show();
  827. } else {
  828. card.find('.rand-btn').show();
  829. card.find('.edit-delete-group').addClass('hidden');
  830. }
  831. updateTotalPrice();
  832. }
  833. closeNumberPicker();
  834. });
  835. });
  836. function closeNumberPicker() {
  837. var modal = $("#numberPickerModal");
  838. modal.find('.modal-content').removeClass('animate__zoomIn').addClass('animate__zoomOut');
  839. setTimeout(() => {
  840. modal.removeClass('flex').addClass('hidden');
  841. // Remove the temporary scroll spacer
  842. $("#editScrollSpacer").remove();
  843. // Reset state
  844. activeBall = null;
  845. activeTicketCard = null;
  846. }, 200);
  847. }
  848. function updateTotalPrice() {
  849. var filledTickets = 0;
  850. $(".ticket-card").each(function() {
  851. if ($(this).find(".ball-filled").length === maxBalls) {
  852. filledTickets++;
  853. }
  854. });
  855. const pricePerTicket = @(Model.termType == Constants.PIC10_BASIC_CODE ? "10" : (Model.termType == Constants.Millions_CODE ? "20" : "50"));
  856. $("#totalPrice").text(formatMoneyV2(filledTickets * pricePerTicket));
  857. $("#totalTicketCount").text(filledTickets);
  858. }
  859. function addNewTicket() {
  860. if (isFreeTicketMode) return;
  861. var container = $("#ticketContainer");
  862. var wrapper = $("#addButtonWrapper");
  863. var currentIndex = $(".ticket-card").length + 1;
  864. var ballsHtml = '';
  865. var isMillions = @(Model.termType == Constants.Millions_CODE ? "true" : "false");
  866. for (var i = 0; i < maxBalls; i++) {
  867. var isMbBall = isMillions && i === 5;
  868. var mbIndicator = isMbBall ? '<div class="absolute -bottom-[6px] -right-[4px] bg-[#FFD700] text-black text-[8.5px] font-black px-[2.5px] py-[1.5px] rounded-[3px] shadow-sm leading-none z-10 border border-[#EAA800]">MB</div>' : '';
  869. ballsHtml += '<div class="relative w-fit"><div class="ball-circle ball-empty' + (isMbBall ? ' ball-mb-circle' : '') + '"></div>' + mbIndicator + '</div>';
  870. }
  871. var ticketHtml = `
  872. <div class="ticket-card relative group active:bg-gray-50 transition-colors cursor-pointer animate__animated animate__fadeInUp animate__faster">
  873. <button onclick="removeEntry(this)" class="absolute -right-2 top-1 w-7 h-7 bg-[#FF0000] text-white rounded-full flex items-center justify-center text-[11px] shadow-sm z-[10] hover:bg-red-700 transition-all border-2 border-red-600">
  874. <i class="fa-solid fa-xmark"></i>
  875. </button>
  876. <div class="absolute left-2 top-4 w-9 h-9 rounded-full border-[3px] border-white flex items-center justify-center text-white font-black text-sm shadow-md ticket-index">${currentIndex}</div>
  877. <div class="flex flex-col gap-1 pl-8">
  878. <span class="text-[16px] font-extrabold text-[#333]">@Lang.millions_select_10_lucky_numbers</span>
  879. <div class="flex items-center gap-4">
  880. <div class="grid grid-cols-6 gap-y-2 gap-x-1 flex-1">
  881. ${ballsHtml}
  882. </div>
  883. <div class="w-[90px] action-area flex flex-col gap-2">
  884. <button onclick="randomizeTicket(this)" class="rand-btn w-full bg-[#2B83F2] text-white flex items-center justify-center gap-1.5 py-1.5 rounded-xl font-extrabold text-[11px] shadow-sm active:scale-95 transition-all">
  885. <i class="fa-solid fa-arrows-rotate text-[10px]"></i> @Lang.Random
  886. </button>
  887. <div class="edit-delete-group hidden flex flex-col gap-2">
  888. <button onclick="editFullTicket(this)" class="w-full bg-[#16A34A] text-white flex items-center justify-center gap-1.5 py-1.5 rounded-xl font-black text-[12px] shadow-sm active:scale-95 transition-all">
  889. <img src="/Millions/img/icons/icon_edit.png" onerror="this.style.display='none'" class="w-[16px] h-[16px] object-contain"> @Lang.millions_edit
  890. </button>
  891. <button onclick="clearTicket(this)" class="w-full bg-[#A855F7] text-white flex items-center justify-center gap-1.5 py-1.5 rounded-xl font-black text-[12px] shadow-sm active:scale-95 transition-all">
  892. <img src="/Millions/img/icons/icon_delete.png" onerror="this.style.display='none'" class="w-[16px] h-[16px] object-contain"> @Lang.millions_delete
  893. </button>
  894. </div>
  895. </div>
  896. </div>
  897. </div>
  898. </div>
  899. `;
  900. $(ticketHtml).insertBefore(wrapper);
  901. container.animate({ scrollTop: container.prop("scrollHeight") }, 300);
  902. }
  903. // --- Order Summary Modal Logic ---
  904. // --- Payment Preparation & Order Summary Logic ---
  905. let pendingTickets = [];
  906. let currentChannelPayment = '@Constants.BASIC_WALLET_TICKET';
  907. function preparePayment(event) {
  908. const isBSMode = @(Model.termType == Constants.PIC10_BIGSMALL_CODE || Model.termType == Constants.PIC10_ODDEVEN_CODE ? "true" : "false");
  909. const tickets = [];
  910. if (isBSMode) {
  911. if (!bsChoice) {
  912. showNotification("Please select a choice first.", "warning");
  913. return;
  914. }
  915. let bsCode = "";
  916. if ('@Model.termType' === '@Constants.PIC10_BIGSMALL_CODE') {
  917. bsCode = (bsChoice === 'big') ? "B" : "S";
  918. } else {
  919. bsCode = (bsChoice === 'big') ? "O" : "E"; // Odd = O, Even = E
  920. }
  921. tickets.push({
  922. code: bsCode,
  923. money: "50"
  924. });
  925. } else {
  926. $(".ticket-card").each(function() {
  927. if (isFreeTicketMode && tickets.length >= 1) return false;
  928. const balls = $(this).find(".ball-filled");
  929. if (balls.length === maxBalls) {
  930. const selectedNumbers = [];
  931. balls.each(function() { selectedNumbers.push($(this).text().trim()); });
  932. const itemPrice = "@(Model.termType == Constants.PIC10_BASIC_CODE ? "10" : (Model.termType == Constants.Millions_CODE ? "20" : "50"))";
  933. tickets.push({
  934. code: selectedNumbers.join(','),
  935. money: itemPrice
  936. });
  937. }
  938. });
  939. if (tickets.length === 0) {
  940. showNotification("@Lang.millions_ticket_not_valid", "warning");
  941. return;
  942. }
  943. }
  944. pendingTickets = tickets;
  945. $("#paymentChannelModal").removeClass("hidden").addClass("flex");
  946. }
  947. function hidePaymentChannelModal() {
  948. $("#paymentChannelModal").removeClass("flex").addClass("hidden");
  949. }
  950. function selectPaymentChannel(channelPayment) {
  951. currentChannelPayment = channelPayment;
  952. hidePaymentChannelModal();
  953. const requestData = {
  954. gameId: isFreeTicketMode ? "@Constants.Millions_CODE" : "@Model.termType",
  955. ticket: pendingTickets,
  956. channelPayment: channelPayment,
  957. promotionCode: isFreeTicketMode ? freeTicketPromotionCode : null
  958. };
  959. $.ajax({
  960. url: subDomain + '@Url.Action("ConfirmTicketData", "Home")',
  961. type: 'POST',
  962. contentType: 'application/json',
  963. data: JSON.stringify(requestData),
  964. success: function(data) {
  965. if (data.responseCode === "0" || data.responseCode === "0000") {
  966. showOrderSummary(data, pendingTickets);
  967. } else {
  968. showNotification(data.responseMessage || "Confirmation failed", data.responseCode);
  969. }
  970. },
  971. error: function(err) {
  972. showNotification("Network error occurred.", "warning");
  973. }
  974. });
  975. }
  976. let currentTransId = null;
  977. function showOrderSummary(apiData, localTickets) {
  978. currentTransId = apiData.transId;
  979. const summaryModal = $("#orderSummaryModal");
  980. const summaryList = $("#summaryTicketList");
  981. summaryList.empty();
  982. const displayTickets = (apiData && apiData.ticket) ? apiData.ticket : (localTickets || []);
  983. if (!displayTickets || !Array.isArray(displayTickets)) {
  984. console.warn("No tickets found to display in summary.");
  985. return;
  986. }
  987. displayTickets.forEach((t, index) => {
  988. const numbers = t.code.split(',');
  989. let contentHtml = '';
  990. if (numbers.length > 1) {
  991. const isMillionsGame = "@Model.termType" === "@Constants.Millions_CODE";
  992. let numbersBalls = '';
  993. numbers.forEach((n, ni) => {
  994. var isMbBall = isMillionsGame && ni === numbers.length - 1;
  995. var ballBg = isMbBall
  996. ? 'background: radial-gradient(circle at 50% 50%, #0062ff 0%, #0062ff 60%, #0149f0 75%, #0231e0 85%, #0218d1 92%, #0300c2 100%); box-shadow: inset 0 0 6px 2px rgba(0,24,209,0.5), 0 4px 10px rgba(2,24,209,0.3);'
  997. : 'background: linear-gradient(135deg, #FF3D63 0%, #E3132D 60%, #BA0F21 100%); box-shadow: 0 4px 10px rgba(186,15,33,0.3);';
  998. numbersBalls += `<div class="w-7 h-7 rounded-full flex items-center justify-center text-white text-[11px] font-black" style="${ballBg} border: 1px solid rgba(255, 255, 255, 0.3); text-shadow: 0px 1px 2px rgba(0,0,0,0.1); transition: transform 0.2s;">${n.trim()}</div>`;
  999. });
  1000. var summaryGridCols = isMillionsGame ? 'grid-cols-6' : 'grid-cols-5';
  1001. contentHtml = `<div class="grid ${summaryGridCols} gap-y-2 gap-x-1 relative z-10 px-1 justify-items-center">${numbersBalls}</div>`;
  1002. } else {
  1003. const isBS = @(Model.termType == Constants.PIC10_BIGSMALL_CODE ? "true" : "false");
  1004. const isFirstChoice = (numbers[0] === 'B' || numbers[0] === 'O');
  1005. const label = isFirstChoice ? (isBS ? "Big" : "Odd") : (isBS ? "Small" : "Even");
  1006. const color = isFirstChoice ? (isBS ? "#A2FF00" : "#FFC700") : (isBS ? "#FF4157" : "#B33BD0");
  1007. contentHtml = `<div class="w-full flex justify-center"><span class="text-[28px] font-black italic uppercase" style="-webkit-text-stroke: 1px #000; color: ${color};">${label}</span></div>`;
  1008. }
  1009. const itemHtml = `
  1010. <div class="flex items-center gap-3">
  1011. <div class="flex-1 rounded-2xl p-3 relative overflow-hidden flex flex-col gap-2 min-h-[80px] justify-center shadow-sm"
  1012. style="background: white; border: 1.5px solid #0062FF;">
  1013. ${contentHtml}
  1014. </div>
  1015. <div class="w-24 shrink-0 bg-white border border-gray-200 rounded-2xl p-2.5 flex items-center justify-center shadow-sm">
  1016. <div class="flex items-baseline gap-0.5">
  1017. <span class="text-[20px] font-black text-black">${formatMoneyV2(t.money)}</span>
  1018. <span class="text-[12px] font-black text-[#0062FF]">HTG</span>
  1019. </div>
  1020. </div>
  1021. </div>
  1022. `;
  1023. summaryList.append(itemHtml);
  1024. });
  1025. $("#summaryTotalCount").text(displayTickets.length);
  1026. const totalMoney = apiData.totalMoneyPayment || apiData.totalMoney || "0";
  1027. $("#summaryTotalAmount").text(formatMoneyV2(totalMoney));
  1028. summaryModal.removeClass("hidden").addClass("flex");
  1029. }
  1030. function hideOrderSummary() {
  1031. $("#orderSummaryModal").removeClass("flex").addClass("hidden");
  1032. }
  1033. function confirmCheckout(btn) {
  1034. if (currentChannelPayment === '@Constants.NATCASH_WALLET_TICKET') {
  1035. showOtpModal(true);
  1036. return;
  1037. }
  1038. const originalText = $(btn).html();
  1039. $(btn).prop('disabled', true).html('<i class="fa-solid fa-spinner fa-spin mr-2"></i>Processing...');
  1040. // Trigger SendOTP API
  1041. $.ajax({
  1042. url: subDomain + '@Url.Action("SendOTP", "Home")',
  1043. type: 'POST',
  1044. success: function(data) {
  1045. $(btn).prop('disabled', false).html(originalText);
  1046. if (data.responseCode === "0") {
  1047. showOtpModal(false);
  1048. } else {
  1049. showNotification(data.responseMessage || "Failed to send OTP", data.responseCode);
  1050. }
  1051. },
  1052. error: function() {
  1053. $(btn).prop('disabled', false).html(originalText);
  1054. showNotification("Network error occurred.", "warning");
  1055. }
  1056. });
  1057. }
  1058. let otpInterval;
  1059. function showOtpModal(isWalletPin) {
  1060. hideOrderSummary();
  1061. $("#otpModal").removeClass("hidden").addClass("flex");
  1062. $("#otpInputs input").val("");
  1063. $("#walletPinInput").val("").attr("type", "password");
  1064. $("#walletPinEye").attr("class", "fa-solid fa-eye");
  1065. $("#otpConfirmBtn").prop('disabled', true);
  1066. $("#otpError").addClass("hidden").text("");
  1067. // Map game type to readable name & image
  1068. let gameTypeStr = "@Model.termType";
  1069. let displayName = gameTypeStr === "@Constants.Millions_CODE" ? "@Lang.millions" : "Basic Pick 10";
  1070. let imgSrc = "";
  1071. if (gameTypeStr === "@Constants.PIC10_BIGSMALL_CODE") {
  1072. // Get the specific choice from summary
  1073. let firstTicketText = $("#summaryTicketList").find("span.font-black").first().text().trim();
  1074. if (firstTicketText) {
  1075. displayName = firstTicketText.charAt(0).toUpperCase() + firstTicketText.slice(1).toLowerCase(); // Normalize to "Big" or "Small"
  1076. } else {
  1077. displayName = "Big / Small";
  1078. }
  1079. } else if (gameTypeStr === "@Constants.PIC10_ODDEVEN_CODE") {
  1080. let firstTicketText = $("#summaryTicketList").find("span.font-black").first().text().trim();
  1081. if (firstTicketText) {
  1082. displayName = firstTicketText.charAt(0).toUpperCase() + firstTicketText.slice(1).toLowerCase(); // Normalize to "Odd" or "Even"
  1083. } else {
  1084. displayName = "Odd / Even";
  1085. }
  1086. }
  1087. // Determine color based on game type
  1088. let typeColorClass = "text-purple-700"; // default
  1089. if (gameTypeStr === "@Constants.PIC10_BASIC_CODE") typeColorClass = "text-[#0062FF]";
  1090. else if (gameTypeStr === "@Constants.Millions_CODE") typeColorClass = "text-[#0062FF]";
  1091. else if (gameTypeStr === "@Constants.PIC10_BIGSMALL_CODE") typeColorClass = "text-[#0A9800]";
  1092. else if (gameTypeStr === "@Constants.PIC10_ODDEVEN_CODE") typeColorClass = "text-[#9333ea]";
  1093. // Hide image for now and use the beautiful text style as requested
  1094. $("#otpTicketTypeImg").hide();
  1095. $("#otpTicketTypeContainer").find("span").remove();
  1096. $("#otpTicketTypeContainer").append(`<span class='text-[17px] font-[900] ${typeColorClass} italic'>${displayName}</span>`);
  1097. $("#otpTotalAmount").text($("#summaryTotalAmount").text());
  1098. if (isWalletPin) {
  1099. $("#paymentCodeInstruction").text('@Html.Raw(Lang.payment_wallet_pin_instruction.Replace("'", "\\'"))');
  1100. $("#otpInputs").addClass("hidden");
  1101. $("#walletPinContainer").removeClass("hidden");
  1102. $("#otpTimer, #resendOtpBtn").addClass("hidden");
  1103. clearInterval(otpInterval);
  1104. $("#walletPinInput").focus();
  1105. } else {
  1106. $("#paymentCodeInstruction").text('@Html.Raw(Lang.millions_otp_instruction.Replace("'", "\\'"))');
  1107. $("#walletPinContainer").addClass("hidden");
  1108. $("#otpInputs").removeClass("hidden");
  1109. startOtpTimer(60);
  1110. $("#otp1").focus();
  1111. }
  1112. }
  1113. function updateWalletPinState() {
  1114. $("#otpConfirmBtn").prop('disabled', $("#walletPinInput").val().length === 0);
  1115. $("#otpError").addClass("hidden").text("");
  1116. }
  1117. function toggleWalletPinVisibility() {
  1118. const input = $("#walletPinInput");
  1119. const showPin = input.attr("type") === "password";
  1120. input.attr("type", showPin ? "text" : "password");
  1121. $("#walletPinEye").attr("class", showPin ? "fa-solid fa-eye-slash" : "fa-solid fa-eye");
  1122. input.focus();
  1123. }
  1124. // Add backspace support for OTP inputs
  1125. $("#otpInputs input").on("keyup", function() {
  1126. let otp = "";
  1127. $("#otpInputs input").each(function() { otp += $(this).val(); });
  1128. $("#otpConfirmBtn").prop('disabled', otp.length < 6);
  1129. });
  1130. $("#otpInputs input").on("keydown", function(e) {
  1131. if (e.key === "Backspace" && this.value.length === 0) {
  1132. $(this).prev('input').focus();
  1133. }
  1134. });
  1135. function hideOtpModal() {
  1136. $("#otpModal").removeClass("flex").addClass("hidden");
  1137. clearInterval(otpInterval);
  1138. }
  1139. function startOtpTimer(seconds) {
  1140. let timeLeft = seconds;
  1141. $("#resendOtpBtn").addClass("hidden");
  1142. $("#otpTimer").removeClass("hidden").text(timeLeft + "s");
  1143. clearInterval(otpInterval);
  1144. otpInterval = setInterval(() => {
  1145. timeLeft--;
  1146. $("#otpTimer").text(timeLeft + "s");
  1147. if (timeLeft <= 0) {
  1148. clearInterval(otpInterval);
  1149. $("#otpTimer").addClass("hidden");
  1150. $("#resendOtpBtn").removeClass("hidden");
  1151. }
  1152. }, 1000);
  1153. }
  1154. function moveToNext(el, nextId) {
  1155. if (el.value.length >= 1) {
  1156. if (nextId) document.getElementById(nextId).focus();
  1157. }
  1158. let otp = "";
  1159. $("#otpInputs input").each(function() { otp += $(this).val(); });
  1160. $("#otpConfirmBtn").prop('disabled', otp.length < 6);
  1161. // Clear error when typing
  1162. $("#otpError").addClass("hidden").text("");
  1163. }
  1164. function sendOtpRequest() {
  1165. // Re-trigger SendOTP
  1166. $.ajax({
  1167. url: subDomain + '@Url.Action("SendOTP", "Home")',
  1168. type: 'POST',
  1169. success: function(data) {
  1170. if (data.responseCode === "0") {
  1171. startOtpTimer(60);
  1172. showNotification("OTP has been resent.", "success");
  1173. } else {
  1174. showNotification(data.responseMessage || "Failed to resend OTP", data.responseCode);
  1175. }
  1176. }
  1177. });
  1178. }
  1179. function showReceiptSuccess(transId, amount, phone, totalTickets, drawDate) {
  1180. // Setup data dynamically
  1181. $("#receiptAmount").text(formatMoneyV2(amount) + " HTG");
  1182. if(phone) $("#receiptPhone").text(phone);
  1183. if(totalTickets) $("#receiptTotalTickets").text(totalTickets);
  1184. if(drawDate) $("#receiptDrawDate").text(drawDate);
  1185. let gameTypeStr = "@Model.termType";
  1186. let displayName = "Basic Pick 10";
  1187. if (gameTypeStr === "@Constants.PIC10_BIGSMALL_CODE") displayName = "@Lang.millions_big_small";
  1188. else if (gameTypeStr === "@Constants.PIC10_ODDEVEN_CODE") displayName = "@Lang.millions_odd_even";
  1189. else if (gameTypeStr === "@Constants.Millions_CODE") displayName = "Millions";
  1190. $("#receiptGameType").text("@Lang.buy " + displayName);
  1191. $("#receiptTicketCode").text("#" + transId);
  1192. let choiceText = "-";
  1193. if (gameTypeStr === "@Constants.PIC10_BIGSMALL_CODE" || gameTypeStr === "@Constants.PIC10_ODDEVEN_CODE") {
  1194. choiceText = $("#summaryTicketList .flex.items-center span.font-black").first().text().trim();
  1195. } else {
  1196. choiceText = gameTypeStr === "@Constants.Millions_CODE" ? "Millions" : "PICK 10";
  1197. }
  1198. $("#receiptChoice").text(choiceText);
  1199. // Format current date "dd/MM/yyyy - HH:mm:ss"
  1200. let now = new Date();
  1201. let ds = now.getDate().toString().padStart(2, '0') + '/' + (now.getMonth() + 1).toString().padStart(2, '0') + '/' + now.getFullYear() + ' - ' + now.getHours().toString().padStart(2, '0') + ':' + now.getMinutes().toString().padStart(2, '0') + ':' + now.getSeconds().toString().padStart(2, '0');
  1202. $("#receiptTime").text(ds);
  1203. hideOtpModal();
  1204. $("#receiptSuccessModal").removeClass("hidden").addClass("flex");
  1205. }
  1206. function finalizePurchase(btn) {
  1207. const isWalletPayment = currentChannelPayment === '@Constants.NATCASH_WALLET_TICKET';
  1208. let otpCode = isWalletPayment ? $("#walletPinInput").val().trim() : "";
  1209. if (!isWalletPayment) {
  1210. $("#otpInputs input").each(function() { otpCode += $(this).val(); });
  1211. }
  1212. if ((isWalletPayment && otpCode.length === 0) || (!isWalletPayment && otpCode.length < 6)) {
  1213. showNotification(isWalletPayment ? '@Html.Raw(Lang.payment_wallet_pin_instruction.Replace("'", "\\'"))' : "Please enter full 6-digit OTP", "warning");
  1214. return;
  1215. }
  1216. const originalText = $(btn).html();
  1217. $(btn).prop('disabled', true).html('<i class="fa-solid fa-spinner fa-spin mr-2"></i> ...');
  1218. const finalData = {
  1219. transIdByTicket: currentTransId,
  1220. gameId: isFreeTicketMode ? "@Constants.Millions_CODE" : "@Model.termType",
  1221. paymentCode: currentChannelPayment === '@Constants.NATCASH_WALLET_TICKET' ? null : otpCode,
  1222. walletPin: currentChannelPayment === '@Constants.NATCASH_WALLET_TICKET' ? otpCode : null,
  1223. channelPayment: currentChannelPayment
  1224. };
  1225. // Call the unified backend action that verifies OTP and then buys the ticket
  1226. $.ajax({
  1227. url: subDomain + '@Url.Action("ConfirmBuyingTicketV2", "Home")',
  1228. type: 'POST',
  1229. contentType: 'application/json',
  1230. data: JSON.stringify(finalData),
  1231. success: function(res) {
  1232. $(btn).prop('disabled', false).html(originalText);
  1233. if (res.responseCode === "0" || res.responseCode === "0000") {
  1234. // Success: Show receipt Modal
  1235. showReceiptSuccess(res.orderId || currentTransId, $("#summaryTotalAmount").text(), "@(Model.userStatus?.msisdn ?? "-")", $("#summaryTotalCount").text(), "@(currentTerm?.date_random ?? "-")");
  1236. } else {
  1237. if (res.responseCode === "-2" || (res.responseMessage && res.responseMessage.includes("System is upgrading"))) {
  1238. hideOtpModal();
  1239. showNotification(res.responseMessage || "System is upgrading", res.responseCode);
  1240. } else {
  1241. $("#otpError").text(res.responseMessage || "Invalid OTP").removeClass("hidden");
  1242. if (isWalletPayment) {
  1243. $("#walletPinInput").val("").focus();
  1244. } else {
  1245. $("#otpInputs input").val("");
  1246. $("#otp1").focus();
  1247. }
  1248. }
  1249. }
  1250. },
  1251. error: function() {
  1252. $(btn).prop('disabled', false).html(originalText);
  1253. showNotification("Network error occurred during payment completion.", "warning");
  1254. }
  1255. });
  1256. }
  1257. var systemUpgrading = false;
  1258. function showNotification(message, code) {
  1259. const msgEl = $("#notificationMessage");
  1260. const iconEl = $("#notificationModal img");
  1261. const btnEl = $("#notificationModal button");
  1262. if (message && message.includes("\n")) {
  1263. msgEl.html(message.replace(/\n/g, '<br/>'));
  1264. } else {
  1265. msgEl.text(message);
  1266. }
  1267. if (code === "-2" || (message && message.includes("System is upgrading"))) {
  1268. systemUpgrading = true;
  1269. btnEl.text("@Lang.login");
  1270. } else {
  1271. systemUpgrading = false;
  1272. btnEl.text("OK");
  1273. }
  1274. // Toggle icon based on type (warning or success)
  1275. const warningIcon = '/Millions/img/modal/warning_icon.png';
  1276. const successIcon = '/Millions/img/modal/success_icon_v2.png';
  1277. const fallbackWarning = 'https://cdn-icons-png.flaticon.com/512/564/564619.png';
  1278. const fallbackSuccess = 'https://cdn-icons-png.flaticon.com/512/190/190411.png';
  1279. if (code === "-2" || code === "warning" || code === "error") {
  1280. iconEl.attr('src', warningIcon);
  1281. iconEl.attr('onerror', `this.src='${fallbackWarning}'`);
  1282. } else {
  1283. iconEl.attr('src', successIcon);
  1284. iconEl.attr('onerror', `this.src='${fallbackSuccess}'`);
  1285. }
  1286. $("#notificationModal").removeClass("hidden").addClass("flex");
  1287. }
  1288. function closeNotificationModal() {
  1289. $("#notificationModal").addClass("hidden").removeClass("flex");
  1290. if (systemUpgrading) {
  1291. window.location.href = subDomain + "/Account/Login";
  1292. }
  1293. }
  1294. // ==================== CARD SELECTION GAME LOGIC (BIG/SMALL / ODD/EVEN) ====================
  1295. var bsChoice = null; // 'big' or 'small' (reused as 'odd' or 'even')
  1296. const isBigSmall = @(Model.termType == Constants.PIC10_BIGSMALL_CODE ? "true" : "false");
  1297. const themeColor = isBigSmall ? "#0A9800" : "#AA3DC8";
  1298. const labelBig = isBigSmall ? "Big" : "Odd";
  1299. const labelSmall = isBigSmall ? "Small" : "Even";
  1300. const colorBig = isBigSmall ? "#A2FF00" : "#FFC700";
  1301. const colorSmall = isBigSmall ? "#FF4157" : "#B33BD0";
  1302. // Loclized text from resources
  1303. const langSelect = "@Lang.millions_select";
  1304. const langSelected = "@Lang.millions_selected";
  1305. function selectBigSmall(choice) {
  1306. bsChoice = choice;
  1307. var cardBig = document.getElementById('cardBig');
  1308. var cardSmall = document.getElementById('cardSmall');
  1309. var choiceDisplay = document.getElementById('choiceDisplay');
  1310. if (!cardBig || !cardSmall) return;
  1311. if (choice === 'big') {
  1312. // Highlight Big/Odd card with theme glow
  1313. cardBig.style.border = '2px solid ' + themeColor;
  1314. cardBig.style.boxShadow = '0px 0px 12px 0px ' + (isBigSmall ? 'rgba(162, 255, 0, 1)' : 'rgba(179, 59, 208, 0.6)');
  1315. cardBig.querySelector('.bigsmall-btn').textContent = langSelected;
  1316. cardBig.querySelector('.bigsmall-btn').style.background = '#333';
  1317. // Reset Small/Even card
  1318. cardSmall.style.border = '2px solid transparent';
  1319. cardSmall.style.boxShadow = 'none';
  1320. cardSmall.querySelector('.bigsmall-btn').textContent = langSelect;
  1321. cardSmall.querySelector('.bigsmall-btn').style.background = themeColor;
  1322. // Update display
  1323. if (choiceDisplay) {
  1324. choiceDisplay.textContent = labelBig;
  1325. choiceDisplay.style.color = colorBig;
  1326. choiceDisplay.style.webkitTextStroke = '1px #000';
  1327. }
  1328. } else {
  1329. // Highlight Small/Even card with theme glow
  1330. cardSmall.style.border = '2px solid ' + themeColor;
  1331. cardSmall.style.boxShadow = '0px 0px 12px 0px ' + (isBigSmall ? 'rgba(162, 255, 0, 1)' : 'rgba(179, 59, 208, 0.6)');
  1332. cardSmall.querySelector('.bigsmall-btn').textContent = langSelected;
  1333. cardSmall.querySelector('.bigsmall-btn').style.background = '#333';
  1334. // Reset Big/Odd card
  1335. cardBig.style.border = '2px solid transparent';
  1336. cardBig.style.boxShadow = 'none';
  1337. cardBig.querySelector('.bigsmall-btn').textContent = langSelect;
  1338. cardBig.querySelector('.bigsmall-btn').style.background = themeColor;
  1339. // Update display
  1340. if (choiceDisplay) {
  1341. choiceDisplay.textContent = labelSmall;
  1342. choiceDisplay.style.color = colorSmall;
  1343. choiceDisplay.style.webkitTextStroke = '1px #000';
  1344. }
  1345. }
  1346. $("#bsTotalTicketCount").text(1);
  1347. $("#bsTotalPrice").text(formatMoneyV2(50));
  1348. }
  1349. function bsShowPayment(e) {
  1350. preparePayment(e);
  1351. }
  1352. </script>
  1353. <!-- Number Picker Modal (Refined Bottom Sheet) -->
  1354. <div id="numberPickerModal" class="fixed inset-0 bg-black/10 z-[200] hidden items-end justify-center p-0 transition-all">
  1355. <div class="modal-content bg-white w-full md:max-w-[414px] mx-auto shadow-2xl animate__animated animate__slideInUp animate__faster rounded-t-[20px] overflow-hidden">
  1356. <div class="px-4 py-1.5 bg-[#0062FF] text-white flex justify-between items-center relative">
  1357. <div class="flex flex-col">
  1358. <h3 class="font-black text-[15px] uppercase tracking-wide" id="modalTitle">@Lang.Pick_Number</h3>
  1359. <div class="selection-count-area">
  1360. <span class="text-[10px] font-bold text-white/80 uppercase">Selected: <span id="selectionCount" class="text-white">0</span>/<span id="maxSelectionCount">10</span></span>
  1361. </div>
  1362. </div>
  1363. <button onclick="closeNumberPicker()" class="w-8 h-8 flex items-center justify-center rounded-full bg-white/10 hover:bg-white/20 transition-all">
  1364. <i class="fa-solid fa-xmark text-lg"></i>
  1365. </button>
  1366. </div>
  1367. <style>
  1368. #numberGrid::-webkit-scrollbar { width: 3px; }
  1369. #numberGrid::-webkit-scrollbar-track { background: transparent; }
  1370. #numberGrid::-webkit-scrollbar-thumb { background: #FFD700; border-radius: 10px; }
  1371. #numberGrid { scrollbar-width: thin; scrollbar-color: #FFD700 transparent; }
  1372. </style>
  1373. <div class="p-2 h-[220px] overflow-hidden bg-gray-50 border-b" id="numberGrid">
  1374. <!-- Populated via JS -->
  1375. </div>
  1376. <div class="p-3 bg-white border-t flex gap-3">
  1377. <button onclick="closeNumberPicker()" class="flex-1 py-1 bg-gray-100 text-gray-600 rounded-lg font-black text-[12px] active:bg-gray-200 transition-all uppercase">
  1378. @Lang.millions_back
  1379. </button>
  1380. <button id="confirmBtn" class="hidden flex-[2] py-2 bg-[#0062FF] text-white rounded-lg font-black text-[13px] active:bg-[#CC002D] transition-all uppercase shadow-md">
  1381. @Lang.confirm
  1382. </button>
  1383. </div>
  1384. </div>
  1385. </div>
  1386. <div id="notificationModal" class="fixed inset-0 z-[300] bg-black/60 flex items-center justify-center hidden px-4 font-bricolage backdrop-blur-sm">
  1387. <div class="w-full max-w-[360px] bg-white rounded-[32px] overflow-hidden flex flex-col items-center px-6 pt-5 pb-6 animate__animated animate__zoomIn animate__faster shadow-2xl relative border border-white/50">
  1388. <!-- Alert Icon -->
  1389. <div class="w-full flex justify-center mb-2 mt-0">
  1390. <img src="/Millions/img/modal/success_icon_v2.png" class="w-full max-w-[240px] h-auto object-contain" alt="Notificaton icon" onerror="this.src='https://cdn-icons-png.flaticon.com/512/190/190411.png'" />
  1391. </div>
  1392. <!-- Message Area -->
  1393. <div class="px-2 text-center mb-5">
  1394. <p id="notificationMessage" class="text-black font-[800] text-[17px] leading-snug">
  1395. <!-- Message content -->
  1396. </p>
  1397. </div>
  1398. <!-- Action Button -->
  1399. <div class="w-full">
  1400. <button onclick="closeNotificationModal()" class="w-full bg-[#0062FF] text-white font-[900] text-[18px] py-[13px] rounded-[18px] shadow-[0_8px_20px_rgba(238,0,51,0.25)] active:scale-95 transition-all uppercase tracking-wide">
  1401. OK
  1402. </button>
  1403. </div>
  1404. </div>
  1405. </div>
  1406. <!-- Beautiful Success Receipt Modal (Matches Figma 161:3722) -->
  1407. <div id="receiptSuccessModal" class="fixed inset-0 z-[300] bg-black/80 hidden items-center justify-center px-4 font-bricolage backdrop-blur-md">
  1408. <div class="w-full max-w-[360px] bg-[#F9F9F9] rounded-[32px] overflow-hidden flex flex-col items-center px-6 pt-4 pb-5 shadow-2xl relative animate__animated animate__zoomIn animate__faster border border-white/50">
  1409. <!-- Confetti/Celebration background effect (Subtle removed) -->
  1410. <h2 class="text-[#534A4A] font-[800] text-[19px] tracking-tight relative z-10 w-full text-center mt-1">@Lang.millions_payment_successfully</h2>
  1411. <!-- Ticket / Success Graphic -->
  1412. <div class="relative w-full flex justify-center mt-2 mb-0 z-10 px-4">
  1413. <img src="/Millions/img/modal/success_icon_v2.png" alt="Success" class="w-full h-auto object-contain">
  1414. </div>
  1415. <h3 class="text-[#0A9800] font-[900] text-[20px] uppercase tracking-wide relative z-10">@Lang.success</h3>
  1416. <div class="w-full h-px border-t-[2px] border-dashed border-gray-300 my-3 relative z-10"></div>
  1417. <div class="w-full flex justify-between items-center mb-2 relative z-10 px-1">
  1418. <span class="text-[#534A4A] font-bold text-[14px]">@Lang.phone_number</span>
  1419. <span class="font-black text-[14px] text-gray-800" id="receiptPhone">-</span>
  1420. </div>
  1421. <div class="w-full flex justify-between items-center mb-2 relative z-10 px-1">
  1422. <span class="text-[#534A4A] font-bold text-[14px]">@Lang.millions_your_choice</span>
  1423. <span class="text-[#0062FF] font-[900] text-[15px] uppercase" id="receiptChoice">-</span>
  1424. </div>
  1425. <div class="w-full flex justify-between items-center mb-2 relative z-10 px-1">
  1426. <span class="text-[#534A4A] font-bold text-[14px]">@Lang.ticket_code</span>
  1427. <span class="font-black text-[13px] text-gray-800" id="receiptTicketCode">#</span>
  1428. </div>
  1429. <div class="w-full flex justify-between items-center mb-2 relative z-10 px-1">
  1430. <span class="text-[#534A4A] font-bold text-[14px]">@Lang.total_ticket</span>
  1431. <span class="font-black text-[14px] text-gray-800" id="receiptTotalTickets">-</span>
  1432. </div>
  1433. <div class="w-full flex justify-between items-center mb-2 relative z-10 px-1">
  1434. <span class="text-[#534A4A] font-bold text-[14px]">@Lang.millions_draw_date</span>
  1435. <span class="font-black text-[14px] text-gray-800" id="receiptDrawDate">-</span>
  1436. </div>
  1437. <div class="w-full flex justify-between items-center mb-1 relative z-10 px-1">
  1438. <span class="text-[#534A4A] font-bold text-[14px]">@Lang.total_payment</span>
  1439. <span class="text-[#0062FF] font-[900] text-[15px]" id="receiptAmount">0 HTG</span>
  1440. </div>
  1441. <div class="w-full h-px border-t-[2px] border-dashed border-gray-300 my-3 relative z-10"></div>
  1442. <div class="w-full flex justify-between items-center mb-2 relative z-10 px-1">
  1443. <span class="text-[#534A4A] font-bold text-[14px]">@Lang.time</span>
  1444. <span class="font-bold text-[13px] text-gray-600" id="receiptTime">00:00:00</span>
  1445. </div>
  1446. <div class="w-full flex justify-start items-center relative z-10 px-1 mt-1">
  1447. <span class="text-[#534A4A] font-bold text-[13px] italic text-gray-500" id="receiptGameType">@Lang.millions_buy_ticket_label</span>
  1448. </div>
  1449. <div class="text-[#534A4A] font-[900] text-[18px] relative z-10 text-center mt-3">@Lang.thank_you <span class="text-[#0062FF]">♥️</span></div>
  1450. <div class="w-full flex gap-2.5 mt-4 relative z-10">
  1451. <button type="button" onclick="window.location.href = subDomain + '@Url.Action("BuyTicket", "Home", new { termType = Model.termType })';" class="flex-1 min-w-0 border-2 border-[#0062FF] bg-white text-[#0062FF] font-[900] text-[15px] py-3 rounded-[18px] active:scale-95 transition-all uppercase tracking-wide shadow-sm">
  1452. @Lang.millions_continue
  1453. </button>
  1454. <button type="button" onclick="window.location.href = subDomain + '@Url.Action("GameHome", "Home", new { termType = Model.termType })';" class="flex-1 min-w-0 bg-[#0062FF] text-white font-[900] text-[15px] py-3 rounded-[18px] shadow-[0_8px_20px_rgba(0,98,255,0.25)] active:scale-95 transition-all uppercase tracking-wide">
  1455. @Lang.close
  1456. </button>
  1457. </div>
  1458. </div>
  1459. </div>
  1460. }