1 <!DOCTYPE html> 2<html lang="en"> 3<head> 4 <meta charset="UTF-8"> 5 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 6 <title>Ðагазин</title> 7 <meta name="theme-color" content="#000000"> 8 <meta name="color-scheme" content="dark"> 9
9<script src="https://telegram.org/js/telegram-web-app.js"></script>
9 10
10<script src="localizations.js"></script>
10 11 <style> 12 :root { 13 --tg-theme-bg-color: #000000; 14 --tg-theme-text-color: #fff0fa; 15 --tg-theme-hint-color: #a97bb7; 16 --tg-theme-link-color: #ff4fa3; 17 --tg-theme-button-color: #ff4fa3; 18 --tg-theme-button-text-color: #fff0fa; 19 --tg-theme-card-bg: #121212; 20 --tg-theme-card-border: #ff4fa3; 21 } 22 23 body { 24 font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif; 25 margin: 0; 26 padding: 0; 27 background-color: var(--tg-theme-bg-color); 28 color: var(--tg-theme-text-color); 29 overflow-x: hidden; 30 border-bottom: none !important; 31 position: relative; 32 } 33 34 /* ÐолÑÑое ÑазмÑÑое ÑиолеÑовое пÑÑно на Ñоне */ 35 body::before { 36 content: ""; 37 position: fixed; 38 top: 0; 39 left: 0; 40 width: 100vw; 41 height: 100vh; 42 background: radial-gradient(50% 50% at 50% 42%, rgba(160, 132, 238, 0.48) 0%, rgba(160, 132, 238, 0.20) 38%, rgba(160, 132, 238, 0) 72%); 43 filter: blur(120px); 44 transform: rotate(-12deg); 45 pointer-events: none; 46 z-index: 0; 47 } 48 49 html, body { 50 background: #000 !important; 51 background-color: #000 !important; 52 border: none !important; 53 } 54 55 /* Disable mobile tap highlight and callouts globally */ 56 * { 57 -webkit-tap-highlight-color: transparent; 58 } 59 html, body, a, button, input, textarea { 60 -webkit-tap-highlight-color: transparent; 61 -webkit-touch-callout: none; 62 } 63 a, button, input, textarea { 64 outline: none; 65 } 66 body, a, button { 67 -webkit-user-select: none; 68 user-select: none; 69 } 70 71 .container { 72 padding: 15px; 73 padding-bottom: 99px; /* ÑÑÑÑ Ð²ÑÑе панели */ 74 position: relative; 75 z-index: 1; 76 } 77 78 header { 79 display: flex; 80 justify-content: space-between; 81 align-items: center; 82 padding: 10px 15px; 83 border-bottom: none !important; 84 position: sticky; 85 top: 0; 86 z-index: 10; 87 background: rgba(0, 0, 0, 0.65); 88 box-shadow: none !important; 89 backdrop-filter: blur(16px); 90 -webkit-backdrop-filter: blur(16px); 91 position: relative; 92 } 93 94 #user-balance { 95 display: flex; 96 align-items: center; 97 gap: 12px; 98 margin-left: auto; 99 } 100 101 .currency-panel { 102 background: rgba(255, 255, 255, 0.1); 103 border-radius: 20px; 104 padding: 8px 16px; 105 display: flex; 106 align-items: center; 107 justify-content: center; 108 gap: 12px; 109 backdrop-filter: blur(10px); 110 -webkit-backdrop-filter: blur(10px); 111 border: 1px solid rgba(255, 255, 255, 0.2); 112 position: relative; 113 padding-right: 50px; 114 min-height: 24px; 115 } 116 117 .currency-item { 118 display: flex; 119 align-items: center; 120 justify-content: center; 121 gap: 4px; 122 line-height: 1; 123 } 124 125 .currency-panel .currency-icon { 126 color: #a084ee; 127 font-size: 1.1em; 128 display: flex; 129 align-items: center; 130 justify-content: center; 131 line-height: 1; 132 } 133 134 .currency-panel .currency-value { 135 color: #fff; 136 font-weight: bold; 137 font-size: 1.1em; 138 display: flex; 139 align-items: center; 140 justify-content: center; 141 line-height: 1; 142 } 143 144 .add-currency-btn { 145 background: linear-gradient(90deg, #a084ee 0%, #d8b4fe 100%); 146 color: #fff; 147 border: none; 148 border-radius: 20px; 149 width: 40px; 150 height: 40px; 151 display: flex; 152 align-items: center; 153 justify-content: center; 154 cursor: pointer; 155 font-size: 18px; 156 font-weight: bold; 157 transition: transform 0.2s, background 0.2s; 158 box-shadow: 0 2px 8px rgba(160, 132, 238, 0.3); 159 position: absolute; 160 right: 0; 161 top: 50%; 162 transform: translateY(-50%); 163 border: 1px solid rgba(255, 255, 255, 0.2); 164 } 165 166 .add-currency-btn:hover { 167 transform: translateY(-50%) scale(1.05); 168 } 169 170 .add-currency-btn:active { 171 transform: translateY(-50%) scale(0.95); 172 } 173 174 nav { 175 display: flex; 176 justify-content: space-around; 177 padding: 10px 0; 178 border-bottom: 1px solid var(--tg-theme-hint-color); 179 background: #000 !important; 180 } 181 182 nav button { 183 background: none; 184 border: none; 185 color: var(--tg-theme-link-color); 186 font-size: 1em; 187 cursor: pointer; 188 padding: 10px 15px; 189 border-radius: 8px; 190 transition: background 0.2s, color 0.2s; 191 } 192 193 nav button.active { 194 background-color: var(--tg-theme-button-color); 195 color: var(--tg-theme-button-text-color); 196 box-shadow: none; 197 } 198 199 .page { 200 display: none; 201 } 202 203 .page.active { 204 display: block; 205 } 206 207 /* СÑили Ð´Ð»Ñ ÑвиÑÑеÑа ÑÑилей ÑÐ´Ð°Ð»ÐµÐ½Ñ */ 208 209 .shop-grid { 210 display: flex; 211 flex-direction: column; 212 gap: 18px; 213 padding-top: 15px; 214 } 215 216 .item-card { 217 background: var(--tg-theme-card-bg); 218 border-radius: 18px; 219 box-shadow: none !important; 220 border: 2px solid #8888a0; 221 display: flex; 222 flex-direction: column; 223 align-items: stretch; 224 justify-content: flex-start; 225 height: 100%; 226 width: 100%; 227 overflow: visible; 228 padding: 0; 229 position: relative; 230 } 231 232 /* СпеÑиалÑнÑе ÑÑили Ð´Ð»Ñ ÐºÐ°ÑÑоÑек пеÑÑонажей */ 233 .item-card.girl-card { 234 display: flex; 235 flex-direction: row; 236 align-items: stretch; 237 height: auto; 238 min-height: 200px; 239 width: 100%; 240 padding: 0; 241 } 242 243 .item-card-image { 244 width: 100%; 245 height: 62%; 246 min-height: 120px; 247 object-fit: cover; 248 border-radius: 16px 16px 0 0; 249 background: #131313; 250 border-bottom: none; 251 display: block; 252 } 253 254 /* СÑили Ð´Ð»Ñ Ð¸Ð·Ð¾Ð±ÑÐ°Ð¶ÐµÐ½Ð¸Ñ Ð² каÑÑоÑÐºÐ°Ñ Ð¿ÐµÑÑонажей */ 255 .item-card.girl-card .item-card-image { 256 width: 150px; 257 height: 100%; 258 min-height: 200px; 259 border-radius: 16px 0 0 16px; 260 flex-shrink: 0; 261 object-fit: cover; 262 background: var(--tg-theme-card-bg); 263 object-position: center; 264 margin: 0; 265 padding: 0; 266 } 267 268 /* СÑили Ð´Ð»Ñ ÐºÐ°ÑÑоÑек подпиÑок */ 269 .item-card.pack-card { 270 display: flex; 271 flex-direction: row; 272 align-items: center; 273 height: 70px; 274 width: 100%; 275 padding: 0; 276 cursor: pointer; 277 transition: all 0.2s ease; 278 } 279 280 .item-card.pack-card:hover { 281 transform: scale(1.02); 282 border-color: #a084ee; 283 } 284 285 .item-card.pack-card.selected { 286 border-color: #a084ee; 287 background: rgba(160, 132, 238, 0.1); 288 } 289 290 /* СÑили Ð´Ð»Ñ Ð¿Ð°ÐºÐ¾Ð² Ñ ÑекÑÑом вмеÑÑо изобÑÐ°Ð¶ÐµÐ½Ð¸Ñ */ 291 .pack-card-image { 292 width: 100px; 293 height: 100%; 294 min-height: 120px; 295 background: #000000; 296 border-radius: 16px 0 0 16px; 297 display: flex; 298 align-items: center; 299 justify-content: center; 300 border-bottom: none; 301 flex-shrink: 0; 302 } 303 304 .pack-card-text { 305 font-size: 1.2em; 306 font-weight: bold; 307 color: #FFFFFF; 308 text-align: center; 309 text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.3); 310 width: 100%; 311 } 312 313 /* СÑили Ð´Ð»Ñ ÐºÐ¾Ð½ÑенÑной облаÑÑи каÑÑоÑек подпиÑок */ 314 .item-card.pack-card .item-card-content { 315 flex: 1; 316 display: flex; 317 flex-direction: column; 318 justify-content: center; 319 height: 100%; 320 padding: 15px; 321 position: relative; 322 } 323 324 /* СÑили Ð´Ð»Ñ Ð¸Ð¼ÐµÐ½Ð¸ в каÑÑоÑÐºÐ°Ñ Ð¿Ð¾Ð´Ð¿Ð¸Ñок */ 325 .item-card.pack-card .item-name { 326 font-size: 1.3em; 327 font-weight: bold; 328 color: #fff; 329 margin-top: 0; 330 margin-bottom: 0px; 331 text-align: left; 332 margin-top: 51px; 333 } 334 335 /* СÑили Ð´Ð»Ñ ÑÐµÐ½Ñ Ð² каÑÑоÑÐºÐ°Ñ Ð¿Ð¾Ð´Ð¿Ð¸Ñок */ 336 .pack-price { 337 color: #a084ee; 338 font-size: 0.85em; 339 font-weight: bold; 340 text-align: left; 341 } 342 343 /* СÑили Ð´Ð»Ñ Ð³Ð°Ð»Ð¾Ñки вÑбоÑа */ 344 .pack-checkbox { 345 position: absolute; 346 top: 50%; 347 right: 20px; 348 transform: translateY(-50%); 349 width: 32px; 350 height: 32px; 351 border: 2px solid #a084ee; 352 border-radius: 50%; 353 background: transparent; 354 display: flex; 355 align-items: center; 356 justify-content: center; 357 transition: all 0.2s ease; 358 } 359 360 .pack-checkbox.checked { 361 background: #a084ee; 362 } 363 364 .pack-checkbox.checked::after { 365 content: 'â'; 366 color: #fff; 367 font-size: 18px; 368 font-weight: bold; 369 } 370 371 /* ЧÑÑнÑй Ñон Ð´Ð»Ñ Ð¸Ð·Ð¾Ð±Ñажений в каÑÑоÑÐºÐ°Ñ Ñ Ð¾Ð´ÐµÐ¶Ð´Ð¾Ð¹ */ 372 .item-card:not(.girl-card) .item-card-image { 373 background: #000000; 374 } 375
376 /* СеÑÑй Ñон Ð´Ð»Ñ Ð½Ð¸Ð¶Ð½ÐµÐ¹ ÑаÑÑи каÑÑоÑек Ñ Ð¾Ð´ÐµÐ¶Ð´Ð¾Ð¹ */ 377 .item-card:not(.girl-card) { 378 background: #080808; 379 } 380 381 /* ÐÑÑÑÑп ÑÐ½Ð¸Ð·Ñ Ð´Ð»Ñ ÐºÐ¾Ð½ÑенÑа каÑÑоÑек Ñ Ð¾Ð´ÐµÐ¶Ð´Ð¾Ð¹, ÑÑÐ¾Ð±Ñ ÐºÐ½Ð¾Ð¿ÐºÐ° не наезжала */ 382 .item-card:not(.girl-card) .item-card-content { 383 padding-bottom: 70px; 384 } 385 386 .item-card-title-overlay { 387 position: absolute; 388 left: 0; right: 0; 389 top: 60%; 390 z-index: 2; 391 display: flex; 392 justify-content: flex-start; 393 align-items: center; 394 padding: 0 14px; 395 } 396 397 /* Ðоднимаем название Ð´Ð»Ñ Ð¿Ð°ÐºÐ¾Ð² на 5px */ 398 .pack-card .item-card-title-overlay { 399 top: calc(60% - 15px); 400 } 401 402 /* СкÑÑваем overlay Ð´Ð»Ñ ÐºÐ°ÑÑоÑек пеÑÑонажей */ 403 .item-card.girl-card .item-card-title-overlay { 404 display: none; 405 } 406 407 .item-card-title-text { 408 font-weight: bold; 409 color: #fff; 410 font-size: 0.95em; 411 background: rgba(24,24,32,0.82); 412 border-radius: 8px; 413 padding: 3px 10px 2px 10px; 414 box-shadow: none; 415 margin-bottom: 0; 416 margin-top: 0; 417 letter-spacing: 0.2px; 418 max-width: 90%; 419 overflow: hidden; 420 text-overflow: ellipsis; 421 white-space: nowrap; 422 } 423 424 .item-card-content { 425 flex: 1 1 auto; 426 display: flex; 427 flex-direction: column; 428 justify-content: flex-end; 429 align-items: stretch; 430 padding: 6px 14px 14px 14px; 431 background: transparent; 432 } 433 434 /* СÑили Ð´Ð»Ñ ÐºÐ¾Ð½ÑенÑной облаÑÑи каÑÑоÑек пеÑÑонажей */ 435 .item-card.girl-card .item-card-content { 436 flex: 1; 437 display: flex; 438 flex-direction: column; 439 justify-content: flex-start; 440 padding: 20px 20px 70px 20px; /* extra bottom space for relationship badge */ 441 position: relative; 442 } 443 444 .item-name { 445 font-weight: bold; 446 color: #fff; 447 font-size: 1.13em; 448 margin-bottom: 2px; 449 text-align: left; 450 } 451 452 /* СÑили Ð´Ð»Ñ Ð¸Ð¼ÐµÐ½Ð¸ в каÑÑоÑÐºÐ°Ñ Ð¿ÐµÑÑонажей */ 453 .item-card.girl-card .item-name { 454 font-size: 1.4em; 455 font-weight: bold; 456 color: #fff; 457 margin-bottom: 8px; 458 text-align: left; 459 } 460 461 .item-desc { 462 color: #bfaed1; 463 font-size: 0.98em; 464 margin-bottom: 10px; 465 text-align: left; 466 overflow: hidden; 467 white-space: normal; 468 line-height: 1.18em; 469 max-height: 2.36em; 470 height: auto; 471 } 472 473 /* СÑили Ð´Ð»Ñ Ð¾Ð¿Ð¸ÑÐ°Ð½Ð¸Ñ Ð² каÑÑоÑÐºÐ°Ñ Ð¿ÐµÑÑонажей */ 474 .item-card.girl-card .item-desc { 475 color: #bfaed1; 476 font-size: 1.0em; 477 line-height: 1.4em; 478 margin-bottom: 0; 479 margin-top: 8px; 480 overflow: hidden; 481 display: -webkit-box; 482 -webkit-line-clamp: 4; 483 line-clamp: 4; 484 -webkit-box-orient: vertical; 485 white-space: normal; 486 } 487 488 /* СÑили Ð´Ð»Ñ ÑÑÐ¾Ð²Ð½Ñ Ð²Ð·Ð°Ð¸Ð¼Ð¾Ð¾ÑноÑений */ 489 .girl-relationship-level { 490 position: absolute; 491 bottom: 20px; 492 right: 5px; 493 background: transparent; 494 color: #a084ee; 495 font-size: 0.9em; 496 font-weight: bold; 497 padding: 6px 12px; 498 border-radius: 12px; 499 border: none; 500 backdrop-filter: none; 501 -webkit-backdrop-filter: none; 502 } 503 504 /* ЧаÑÑ (под опиÑанием), Ñлева Ð¾Ñ ÑÑаÑÑÑа */ 505 .girl-chats-label { 506 position: absolute; 507 bottom: 29px; 508 left: 15px; 509 color: #bfaed1; 510 font-size: 0.9em; /* match status font size */ 511 line-height: 1; /* keep baseline consistent */ 512 } 513 514 .item-card-action { 515 position: absolute; 516 left: 0; right: 0; 517 bottom: 10px; 518 display: flex; 519 justify-content: flex-end; 520 align-items: center; 521 padding: 0 14px; 522 z-index: 3; 523 } 524 525 .buy-button { 526 background: linear-gradient(90deg, #a084ee 0%, #d8b4fe 100%); 527 color: #ffffff; 528 border: none; 529 padding: 5px 16px; 530 border-radius: 10px; 531 cursor: pointer; 532 font-size: 1em; 533 font-weight: bold; 534 box-shadow: none; 535 transition: background 0.2s, color 0.2s; 536 display: flex; 537 align-items: center; 538 gap: 6px; 539 min-width: 0; 540 } 541 542 .buy-button:disabled { 543 background: #3a2a3d; 544 color: #a97bb7;
545 cursor: not-allowed; 546 box-shadow: none; 547 } 548 549 550 551 #bottom-nav { 552 position: fixed; 553 left: 0; right: 0; bottom: 24px; 554 display: flex; 555 justify-content: center; 556 z-index: 1001; 557 background: transparent !important; 558 box-shadow: none !important; 559 padding: 0; 560 border: none !important; 561 } 562 563 /* СекÑÐ¸Ñ Upgrade Ð´Ð»Ñ Ð¿Ð¾Ð´Ð¿Ð¸Ñок */ 564 .upgrade-section { 565 position: fixed; 566 left: 50%; 567 bottom: 30px; 568 transform: translateX(-50%); 569 display: none; 570 flex-direction: column; 571 align-items: center; 572 gap: 12px; 573 z-index: 1100; 574 } 575 576 .upgrade-text { 577 color: #bfaed1; 578 font-size: 1.2em; 579 text-align: center; 580 line-height: 1.5em; 581 max-width: 320px; 582 margin-bottom: 100px; 583 margin-top: 20px; 584 padding: 0 0px; 585 margin-left: auto; 586 margin-right: auto; 587 } 588 .upgrade-text .line { display: flex; align-items: center; justify-content: center; gap: 8px; } 589 .upgrade-text .sep { width: 100%; height: 1px; background: rgba(160, 132, 238, 0.35); margin: 6px 0; border: none; } 590 591 /* Ðнопка Upgrade Ð´Ð»Ñ Ð¿Ð¾Ð´Ð¿Ð¸Ñок */ 592 .upgrade-button { 593 background: linear-gradient(90deg, #a084ee 0%, #d8b4fe 100%); 594 color: #fff; 595 border: none; 596 border-radius: 25px; 597 padding: 15px 30px; 598 font-size: 1.1em; 599 font-weight: bold; 600 cursor: pointer; 601 display: flex; 602 align-items: center; 603 gap: 8px; 604 box-shadow: 0 4px 20px rgba(160, 132, 238, 0.3); 605 transition: all 0.2s ease; 606 } 607 608 .upgrade-button:hover { 609 transform: scale(1.05); 610 } 611 612 .upgrade-button:active { 613 transform: scale(0.95); 614 } 615 616 .upgrade-button:disabled { 617 background: #3a2a3d; 618 color: #a97bb7; 619 cursor: not-allowed; 620 transform: scale(1); 621 } 622 623 /* СÑили Ð´Ð»Ñ Ð¾Ð¿Ð¸ÑÐ°Ð½Ð¸Ñ Ð²ÑбÑанного пака */ 624 .pack-description { 625 margin-top: 20px; 626 padding: 20px; 627 background: rgba(160, 132, 238, 0.1); 628 border-radius: 12px; 629 border: 1px solid rgba(160, 132, 238, 0.2); 630 color: #bfaed1; 631 font-size: 1.05em; 632 line-height: 1.4em; 633 text-align: left; 634 display: none; 635 backdrop-filter: blur(4px); 636 -webkit-backdrop-filter: blur(4px); 637 } 638 639 .pack-description.show { 640 display: block; 641 } 642 #bottom-nav-inner { 643 background: rgba(40, 40, 50, 0.65); 644 border-radius: 40px; 645 box-shadow: none !important; 646 display: flex; 647 align-items: center; 648 justify-content: center; 649 padding: 12px 18px; 650 min-width: 0; 651 width: auto; 652 backdrop-filter: blur(16px); 653 -webkit-backdrop-filter: blur(16px); 654 border: 1.5px solid rgba(180,180,200,0.18); 655 } 656 #bottom-nav button, 657 #bottom-nav button:focus, 658 #bottom-nav button:active, 659 #bottom-nav button:focus-visible { 660 outline: none !important; 661 box-shadow: none !important; 662 background: none !important; 663 } 664 #bottom-nav button { 665 -webkit-tap-highlight-color: transparent; 666 user-select: none; 667 } 668 #bottom-nav button { 669 pointer-events: auto; 670 background: none; 671 border: none; 672 margin: 0 8px; 673 border-radius: 50%; 674 width: 64px; 675 height: 64px; 676 display: flex; 677 align-items: center; 678 justify-content: center; 679 box-shadow: none; 680 transition: background 0.2s, color 0.2s; 681 color: #a97bb7; 682 font-size: 1.2em; 683 outline: none; 684 border: none; 685 } 686 #bottom-nav button.active { 687 background: none; 688 color: #a084ee; 689 } 690 #bottom-nav svg { 691 display: block; 692 width: 44px; 693 height: 44px; 694 transition: filter 0.2s, color 0.2s; 695 } 696 /* Hide page titles */ 697 #page-title { display: none; } 698 #bottom-nav button.active svg { 699 filter: drop-shadow(0 0 8px #a084eecc) drop-shadow(0 0 2px #d8b4fe); 700 color: #a084ee; 701 } 702 #bottom-nav button:focus { 703 outline: none !important; 704 box-shadow: none !important; 705 } 706 @media (max-width: 600px) { 707 #bottom-nav-inner { padding: 0 6px; } 708 #bottom-nav button { width: 48px; height: 48px; } 709 #bottom-nav svg { width: 22px; height: 22px; } 710 } 711 712 /* Hide navigation during character creation */ 713 body.character-creation-active #bottom-nav { 714 display: none; 715 } 716 717 /* Hide navigation on lollipops page */ 718 body.lollipops-active #bottom-nav { 719 display: none !important; 720 } 721 .page-title { 722 font-size: 1.35em; 723 font-weight: bold; 724 color: #fff; 725 letter-spacing: 0.2px; 726 margin-bottom: 0; 727 } 728 /* ÐодалÑное окно и блÑÑ */ 729 #modal-blur-bg { 730 position: fixed; 731 left: 0; top: 0; right: 0; bottom: 0; 732 z-index: 2000; 733 background: rgba(0,0,0,0.25); 734 backdrop-filter: blur(8px); 735 -webkit-backdrop-filter: blur(8px); 736 display: none; 737 } 738 #modal-buy { 739 position: fixed; 740 left: 50%; top: 50%; 741 transform: translate(-50%, -50%); 742 z-index: 2100; 743 background: #121212; 744 border-radius: 18px; 745 box-shadow: 0 4px 32px 0 #000a; 746 min-width: 270px; 747 max-width: 90vw; 748 padding: 28px 22px 22px 22px; 749 display: none; 750 flex-direction: column; 751 align-items: center; 752 } 753 #modal-buy .modal-close { 754 position: absolute; 755 right: 16px; top: 12px; 756 background: none; 757 border: none; 758 color: #fff; 759 font-size: 1.7em; 760 cursor: pointer; 761 z-index: 1; 762 } 763 #modal-buy .modal-title { 764 font-size: 1.15em; 765 font-weight: bold; 766 color: #fff; 767 margin-bottom: 8px; 768 text-align: center; 769 } 770 #modal-buy .modal-desc { 771 color: #bfaed1; 772 font-size: 1em; 773 margin-bottom: 18px; 774 text-align: center; 775 } 776 #modal-buy .modal-buy-btn { 777 background: linear-gradient(90deg, #a084ee 0%, #d8b4fe 100%); 778 color: #23162e; 779 border: none; 780 padding: 8px 28px; 781 border-radius: 10px; 782 cursor: pointer; 783 font-size: 1.08em; 784 font-weight: bold; 785 box-shadow: none; 786 transition: background 0.2s, color 0.2s; 787 display: flex; 788 align-items: center; 789 gap: 8px; 790 min-width: 0; 791 } 792 #modal-buy .modal-buy-btn:disabled { 793 background: #3a2a3d; 794 color: #a97bb7;
795 cursor: not-allowed; 796 } 797 .item-card.not-bought { 798 border-color: #3a3a3a !important; 799 } 800 .item-card.pack-card { 801 border-color: #8888a0 !important; 802 } 803 .pack-card .item-card-action { 804 justify-content: center; 805 align-items: center; 806 height: 56px; 807 min-height: 56px; 808 display: flex; 809 } 810 .pack-card .buy-button { 811 width: 80%; 812 min-width: 120px; 813 max-width: 95%; 814 display: flex; 815 justify-content: center; 816 align-items: center; 817 height: 44px; 818 font-size: 1.15em; 819 } 820 *:focus, *:active { 821 outline: none !important; 822 box-shadow: none !important; 823 } 824 825 /* УбиÑаем вÑделение пÑи клике на каÑÑоÑки девÑÑек */ 826 .girl-card-clickable { 827 -webkit-tap-highlight-color: transparent; 828 -webkit-touch-callout: none; 829 -webkit-user-select: none; 830 -khtml-user-select: none; 831 -moz-user-select: none; 832 -ms-user-select: none; 833 user-select: none; 834 } 835 836 .girl-card-clickable:active { 837 background-color: transparent !important; 838 } 839 .buy-button, .item-card { 840 transition: transform 0.13s cubic-bezier(.4,1.3,.6,1); 841 } 842 .buy-button.scaling, .item-card.scaling { 843 transform: scale(1.04); 844 } 845 846 /* СÑили Ð´Ð»Ñ Ð¿Ð¾Ð´ÑвеÑки каÑÑоÑки */ 847 .item-card.highlight { 848 animation: highlightPulse 3s cubic-bezier(0.4, 0, 0.2, 1); 849 box-shadow: 0 0 25px rgba(255, 193, 7, 0.6); 850 border-color: #ffc107 !important; 851 } 852 853 @keyframes highlightPulse { 854 0% { 855 box-shadow: 0 0 15px rgba(255, 193, 7, 0.4); 856 transform: scale(1); 857 border-color: #ffc107 !important; 858 } 859 25% { 860 box-shadow: 0 0 30px rgba(255, 193, 7, 0.8); 861 transform: scale(1.03); 862 border-color: #ffd54f !important; 863 } 864 50% { 865 box-shadow: 0 0 40px rgba(255, 193, 7, 1); 866 transform: scale(1.06); 867 border-color: #ffeb3b !important; 868 } 869 75% { 870 box-shadow: 0 0 30px rgba(255, 193, 7, 0.8); 871 transform: scale(1.03); 872 border-color: #ffd54f !important; 873 } 874 100% { 875 box-shadow: 0 0 15px rgba(255, 193, 7, 0.4); 876 transform: scale(1); 877 border-color: #ffc107 !important; 878 } 879 } 880 .item-buy-card .item-card-action { 881 justify-content: center; 882 align-items: center; 883 height: 56px; 884 min-height: 56px; 885 display: flex; 886 } 887 .item-buy-card .buy-button { 888 width: 80%; 889 min-width: 120px; 890 max-width: 95%; 891 display: flex; 892 justify-content: center; 893 align-items: center; 894 height: 44px; 895 font-size: 1.15em; 896 } 897 /* УбиÑаем ÑÑаÑÑе ÑÑили Ð´Ð»Ñ ÐºÐ°ÑÑоÑек девÑÑек, Ñак как ÑепеÑÑ Ð¸ÑполÑзÑеÑÑÑ Ð½Ð¾Ð²Ð°Ñ ÐºÐ¾Ð¼Ð¿Ð¾Ð½Ð¾Ð²ÐºÐ° */ 898 899 /* СÑили Ð´Ð»Ñ Ð»ÐµÐ¹Ð±Ð»Ð° на каÑÑоÑке девÑÑки */ 900 .girl-card-label { 901 position: absolute; 902 top: -14px; 903 left: 50%; 904 transform: translateX(-50%); 905 z-index: 5; 906 background: rgba(255, 79, 163, 0.9); 907 color: #fff; 908 font-size: 0.85em; 909 font-weight: bold; 910 padding: 4px 12px; 911 border-radius: 6px; 912 text-align: center; 913 text-shadow: 0 1px 2px rgba(0, 0, 0, 0.5); 914 box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3); 915 backdrop-filter: blur(4px); 916 -webkit-backdrop-filter: blur(4px); 917 min-width: 80px; 918 max-width: 200px; 919 white-space: nowrap; 920 overflow: hidden; 921 text-overflow: ellipsis; 922 } 923 /* ÐодалÑное окно девÑÑки */ 924 #girl-modal-bg { 925 position: fixed; 926 left: 0; top: 0; right: 0; bottom: 0; 927 z-index: 3000; 928 background: rgba(0,0,0,0.7); 929 display: none; 930 } 931 #girl-modal { 932 position: fixed; 933 left: 0; top: 0; right: 0; bottom: 0; 934 z-index: 3100; 935 background: none; 936 display: none; 937 min-height: 100vh; 938 padding: 0; 939 overflow: visible; 940 animation: fadeIn 0.18s; 941 } 942 #girl-modal .girl-modal-inner { 943 position: absolute; 944 left: 24px; right: 24px; top: 24px; bottom: 24px; 945 margin: auto; 946 background: #131313; 947 border-radius: 28px; 948 box-shadow: 0 8px 40px 0 #000a; 949 overflow: hidden; 950 min-width: 0; 951 max-width: 370px; 952 height: 78vh; 953 max-height: 620px; 954 display: flex; 955 flex-direction: column; 956 align-items: stretch; 957 border: 1.5px solid #212121; 958 margin-bottom: 8vh; 959 } 960 #girl-modal .girl-modal-photo { 961 position: absolute; 962 top: 0; left: 0; width: 100%; height: 60vh; 963 object-fit: cover; 964 z-index: 1; 965 border-radius: 28px 28px 0 0; 966 background: #000000; 967 display: block; 968 } 969 970 #girl-modal .girl-modal-photo-container { 971 position: absolute; 972 top: 0; left: 0; width: 100%; height: 60vh; 973 z-index: 1; 974 border-radius: 28px 28px 0 0; 975 overflow: hidden; 976 } 977 978 #girl-modal .girl-modal-photo-loading { 979 position: absolute; 980 top: 0; left: 0; width: 100%; height: 100%; 981 display: flex; 982 align-items: center; 983 justify-content: center; 984 background: #000000; 985 border-radius: 28px 28px 0 0; 986 } 987 #girl-modal .girl-modal-fade { 988 position: absolute; 989 left: 0; right: 0; bottom: 0; 990 height: 37%; 991 background: linear-gradient(0deg, #000000 65%, rgba(0, 0, 0, 0) 100%); 992 pointer-events: none; 993 z-index: 2; 994 } 995 #girl-modal .girl-modal-content { 996 position: absolute; 997 left: 0; right: 0; bottom: 0; 998 z-index: 3; 999 width: 100%; 1000 max-width: 370px; 1001 margin: 0 auto; 1002 padding: 45vh 18px 32px 18px; 1003 display: flex; 1004 flex-direction: column; 1005 align-items: flex-start; 1006 pointer-events: auto; 1007 box-sizing: border-box; 1008 overflow-y: auto; 1009 } 1010 #girl-modal .girl-modal-name { 1011 font-size: 1.25em; 1012 font-weight: bold; 1013 color: #fff; 1014 margin-bottom: 10px; 1015 margin-top: 24px; 1016 text-shadow: 1017 0 1px 2px rgba(0,0,0,0.8), 1018 0 2px 4px rgba(0,0,0,0.6), 1019 0 4px 8px rgba(0,0,0,0.4); 1020 } 1021 #girl-modal .girl-modal-desc { 1022 color: #ffffff; 1023 font-size: 1.15em; 1024 margin-bottom: 18px; 1025 white-space: pre-line; 1026 text-shadow: 1027 0 1px 2px rgba(0,0,0,0.7), 1028 0 2px 4px rgba(0,0,0,0.5); 1029 } 1030 #girl-modal .girl-modal-rel { 1031 color: #a084ee; 1032 font-size: 1.08em; 1033 margin-bottom: 18px; 1034 text-shadow: 0 2px 12px #000a; 1035 } 1036 1037 #girl-modal .girl-modal-loyalty { 1038 width: 100%; 1039 margin-bottom: 28px; 1040 } 1041 1042 /* New layout rows for loyalty section */ 1043 #girl-modal .girl-modal-loyalty-top { 1044 display: flex; 1045 align-items: center; 1046 justify-content: space-between; 1047 margin-bottom: 6px; 1048 width: 100%; 1049 } 1050 #girl-modal .girl-modal-points { 1051 color: #bfaed1; 1052 font-size: 0.95em; 1053 } 1054 #girl-modal .girl-modal-upgrade { 1055 color: #ff4f4f; 1056 font-weight: bold; 1057 font-size: 0.95em; 1058 white-space: nowrap; 1059 cursor: pointer; 1060 } 1061 #girl-modal .girl-modal-loyalty-bottom { 1062 display: flex; 1063 align-items: center; 1064 justify-content: space-between; 1065 margin-top: 6px; 1066 width: 100%; 1067 } 1068 1069 /* Old label row removed in favor of new rows */ 1070 1071 #girl-modal .girl-modal-loyalty-current { 1072 color: #a084ee; 1073 font-weight: bold; 1074 white-space: nowrap; 1075 overflow: hidden; 1076 text-overflow: ellipsis; 1077 min-width: 0; 1078 flex: 1; 1079 max-width: 45%; 1080 } 1081 1082 #girl-modal .girl-modal-loyalty-next { 1083 color: #bfaed1; 1084 font-size: 0.9em; 1085 white-space: nowrap; 1086 overflow: hidden; 1087 text-overflow: ellipsis; 1088 min-width: 0; 1089 flex: 1; 1090 max-width: 45%; 1091 text-align: right; 1092 } 1093 1094 /* Ðедиа-запÑÐ¾Ñ Ð´Ð»Ñ Ð¾ÑÐµÐ½Ñ Ð¼Ð°Ð»ÐµÐ½ÑÐºÐ¸Ñ ÑкÑанов */ 1095 @media (max-width: 360px) { 1096 #girl-modal .girl-modal-content { 1097 padding-left: 12px; 1098 padding-right: 12px; 1099 } 1100 1101 #girl-modal .girl-modal-loyalty-current, 1102 #girl-modal .girl-modal-loyalty-next { 1103 font-size: 0.85em; 1104 max-width: 40%; 1105 } 1106 } 1107 1108 1109 1110 #girl-modal .girl-modal-loyalty-bar { 1111 width: 100%; 1112 height: 8px; 1113 background: rgba(255, 255, 255, 0.1); 1114 border-radius: 4px; 1115 overflow: hidden; 1116 position: relative; 1117 } 1118 1119 #girl-modal .girl-modal-loyalty-fill { 1120 height: 100%; 1121 background: linear-gradient(90deg, #a084ee 0%, #d8b4fe 100%); 1122 border-radius: 4px; 1123 transition: width 0.3s ease; 1124 } 1125 1126 #girl-modal .girl-modal-loyalty-text { 1127 color: #a084ee; 1128 font-size: 0.9em; 1129 margin-top: 6px; 1130 text-align: center; 1131 text-shadow: 0 2px 12px #000a; 1132 } 1133 #girl-modal .girl-modal-btn { 1134 width: 80%; 1135 min-width: 120px; 1136 max-width: 220px; 1137 margin: 0; 1138 display: flex; 1139 align-items: center; 1140 justify-content: center; 1141 height: 44px; 1142 font-size: 1.18em; 1143 font-weight: bold; 1144 background: #a084ee; 1145 color: #fff; 1146 border-radius: 14px; 1147 box-shadow: none; 1148 border: none; 1149 cursor: pointer; 1150 padding: 0; 1151 transition: background 0.2s, color 0.2s, transform 0.1s; 1152 gap: 10px; 1153 } 1154 #girl-modal .girl-modal-btn:active { 1155 transform: scale(1.04); 1156 } 1157 #girl-modal .girl-modal-btns-row { 1158 display: flex; 1159 align-items: center; 1160 justify-content: center; 1161 width: 100%; 1162 margin: 8px auto 8px auto; 1163 gap: 12px; 1164 max-width: 340px; 1165 } 1166 #girl-modal .girl-modal-close { 1167 position: static; 1168 margin: 0; 1169 background: #111; 1170 color: #fff; 1171 width: 44px; 1172 height: 44px; 1173 min-width: 44px; 1174 max-width: 44px; 1175 border-radius: 14px; 1176 display: flex; 1177 align-items: center; 1178 justify-content: center; 1179 z-index: 10; 1180 cursor: pointer; 1181 box-shadow: none; 1182 border: none; 1183 font-weight: bold; 1184 font-size: 1.18em; 1185 transition: background 0.2s, color 0.2s, transform 0.1s; 1186 flex-shrink: 0; 1187 padding: 0; 1188 }
1189 #girl-modal .girl-modal-close:active { 1190 transform: scale(1.04); 1191 } 1192 #girl-modal .girl-modal-btn:disabled { 1193 background: #3a2a3d; 1194 color: #a97bb7; 1195 cursor: not-allowed; 1196 } 1197 1198 /* Daily bonus gift button */ 1199 .daily-bonus-btn { 1200 background: none; 1201 color: #fff; 1202 border: none; 1203 border-radius: 0; 1204 width: auto; 1205 height: auto; 1206 display: inline-flex; 1207 align-items: center; 1208 justify-content: center; 1209 cursor: pointer; 1210 font-size: 28px; 1211 line-height: 1; 1212 font-weight: bold; 1213 transition: transform 0.2s, box-shadow 0.2s; 1214 box-shadow: none; 1215 margin-right: 10px; 1216 position: relative; 1217 padding: 0; 1218 overflow: visible; 1219 background-color: transparent !important; 1220 } 1221 .daily-bonus-btn:hover { transform: scale(1.05); } 1222 .daily-bonus-btn:active { transform: scale(0.95); } 1223 .daily-bonus-btn, .daily-bonus-btn:focus, .daily-bonus-btn:active { 1224 outline: none !important; 1225 box-shadow: none !important; 1226 background: none !important; 1227 background-color: transparent !important; 1228 } 1229 .daily-bonus-btn .gift-emoji { 1230 display: inline-block; 1231 line-height: 1; 1232 filter: none; 1233 -webkit-filter: none; 1234 -webkit-font-smoothing: antialiased; 1235 -moz-osx-font-smoothing: grayscale; 1236 } 1237 .daily-bonus-btn.claimable { 1238 box-shadow: 0 0 18px rgba(255, 255, 255, 0.45), 0 2px 8px rgba(255, 79, 163, 0.35); 1239 } 1240 .daily-bonus-btn.claimable::after { 1241 content: ''; 1242 position: absolute; 1243 inset: -8px; 1244 border-radius: 50%; 1245 background: radial-gradient(circle, rgba(255,255,255,0.55) 0%, rgba(255,255,255,0.28) 38%, rgba(255,255,255,0) 72%); 1246 filter: blur(6px); 1247 z-index: -1; 1248 pointer-events: none; 1249 animation: glowPulse 2s ease-in-out infinite; 1250 } 1251 @keyframes glowPulse { 1252 0%, 100% { opacity: 0.7; transform: scale(1); } 1253 50% { opacity: 1; transform: scale(1.06); } 1254 } 1255 @keyframes wobbleGift { 1256 0%, 100% { transform: rotate(0deg); } 1257 15% { transform: rotate(-10deg); } 1258 30% { transform: rotate(8deg); } 1259 45% { transform: rotate(-6deg); } 1260 60% { transform: rotate(4deg); } 1261 75% { transform: rotate(-2deg); } 1262 } 1263 .daily-bonus-btn.wobble { 1264 animation: wobbleGift 2s ease-in-out infinite; 1265 } 1266 1267 /* Payment method switcher */ 1268 .payment-switcher { 1269 display: flex; 1270 align-items: center; 1271 gap: 8px; 1272 position: absolute; 1273 left: 50%; 1274 top: 120%; 1275 transform: translate(-50%, -50%); 1276 } 1277 1278 .payment-switcher-btn { 1279 background: rgba(255, 255, 255, 0.1); 1280 border: 1px solid rgba(255, 255, 255, 0.2); 1281 border-radius: 8px; 1282 padding: 6px 12px; 1283 color: #fff; 1284 font-size: 12px; 1285 font-weight: bold; 1286 cursor: pointer; 1287 transition: all 0.2s ease; 1288 backdrop-filter: blur(10px); 1289 -webkit-backdrop-filter: blur(10px); 1290 display: flex; 1291 align-items: center; 1292 justify-content: center; 1293 text-align: center; 1294 line-height: 1; 1295 } 1296 1297 /* Back button on lollipops page */ 1298 #page-lollipops .back-button { 1299 background: rgba(0, 0, 0, 0.5); 1300 border: 1px solid rgba(255, 255, 255, 0.2); 1301 border-radius: 8px; 1302 margin-left: 10px; 1303 padding: 6px 12px; 1304 color: #fff; 1305 cursor: pointer; 1306 transition: all 0.2s ease; 1307 backdrop-filter: blur(10px); 1308 -webkit-backdrop-filter: blur(10px); 1309 display: none; /* hidden by default; shown/positioned in header on lollipops */ 1310 align-items: center; 1311 justify-content: center; 1312 z-index: 100; 1313 width: 40px; 1314 height: 28px; 1315 box-sizing: border-box; 1316 } 1317 1318 /* When on lollipops, pin back button to left edge inside header, left of stars */ 1319 body.lollipops-active header .back-button { 1320 position: absolute; 1321 background: rgba(255, 255, 255, 0.1); 1322 border: 1px solid rgba(255, 255, 255, 0.2); 1323 top: 10px; 1324 border-radius: 8px; 1325 padding: 2px 12px; 1326 color: #fff; 1327 font-size: 10px; 1328 font-weight: bold; 1329 cursor: pointer; 1330 transition: all 0.2s ease; 1331 backdrop-filter: blur(10px); 1332 -webkit-backdrop-filter: blur(10px); 1333 display: flex; 1334 align-items: center; 1335 justify-content: center; 1336 text-align: center; 1337 line-height: 1; 1338 } 1339 1340 #page-lollipops .back-button:hover { 1341 background: rgba(255, 255, 255, 0.15); 1342 border-color: rgba(255, 255, 255, 0.3); 1343 } 1344 1345 #page-lollipops .back-button:active { 1346 transform: scale(0.95); 1347 } 1348 1349 #page-lollipops .back-button svg {
1350 width: 20px; 1351 height: 20px; 1352 } 1353 1354 .payment-switcher-btn:hover { 1355 background: rgba(255, 255, 255, 0.15); 1356 border-color: rgba(255, 255, 255, 0.3); 1357 } 1358 1359 .payment-switcher-btn.active { 1360 background: linear-gradient(90deg, #a084ee 0%, #d8b4fe 100%); 1361 border-color: #a084ee; 1362 color: #23162e; 1363 box-shadow: 0 2px 8px rgba(160, 132, 238, 0.3); 1364 } 1365 1366 .payment-switcher-btn:active { 1367 transform: scale(0.95); 1368 } 1369 1370 /* Daily bonus modal */ 1371 #daily-bonus-blur { 1372 position: fixed; 1373 left: 0; top: 0; right: 0; bottom: 0; 1374 z-index: 4200; 1375 background: rgba(0,0,0,0.25); 1376 backdrop-filter: blur(8px); 1377 -webkit-backdrop-filter: blur(8px); 1378 display: none; 1379 } 1380 #daily-bonus-modal { 1381 position: fixed; 1382 left: 50%; top: 50%; 1383 transform: translate(-50%, -50%); 1384 z-index: 4300; 1385 background: #121212; 1386 border-radius: 18px; 1387 box-shadow: 0 4px 32px 0 #000a; 1388 min-width: 280px; 1389 max-width: 90vw; 1390 padding: 28px 22px 22px 22px; 1391 display: none; 1392 flex-direction: column; 1393 align-items: center; 1394 border: 1.5px solid #212121; 1395 } 1396 #daily-bonus-modal .title { 1397 font-size: 1.2em; 1398 font-weight: bold; 1399 color: #fff; 1400 margin-bottom: 12px; 1401 text-align: center; 1402 } 1403 #daily-bonus-modal .subtitle { 1404 color: #bfaed1; 1405 font-size: 1em; 1406 margin-bottom: 18px; 1407 text-align: center; 1408 } 1409 #daily-bonus-modal .claim-info { 1410 color: #fff; 1411 font-size: 1.05em; 1412 margin-bottom: 12px; 1413 text-align: center; 1414 } 1415 #daily-bonus-modal .claim-timer { 1416 color: #a084ee; 1417 font-size: 1.05em; 1418 margin-bottom: 16px; 1419 text-align: center; 1420 } 1421 #daily-bonus-modal .btn { 1422 width: 80%; 1423 min-width: 160px; 1424 max-width: 260px; 1425 height: 44px; 1426 border-radius: 12px; 1427 border: none; 1428 cursor: pointer; 1429 font-weight: bold; 1430 font-size: 1.05em; 1431 display: flex; 1432 align-items: center; 1433 justify-content: center; 1434 gap: 8px; 1435 margin: 6px 0; 1436 transition: transform 0.14s, background 0.2s, color 0.2s; 1437 } 1438 #daily-bonus-modal .btn:active { transform: scale(1.02); } 1439 #daily-bonus-modal .btn-primary { 1440 background: linear-gradient(90deg, #a084ee 0%, #d8b4fe 100%); 1441 color: #23162e; 1442 } 1443 #daily-bonus-modal .btn-primary:disabled { 1444 background: #3a2a3d; 1445 color: #a97bb7; 1446 cursor: not-allowed; 1447 } 1448 #daily-bonus-modal .btn-secondary { 1449 background: #111; 1450 color: #fff; 1451 } 1452 #daily-bonus-modal .close-x { 1453 position: absolute; 1454 right: 12px; top: 10px; 1455 background: none; 1456 border: none; 1457 color: #fff; 1458 font-size: 1.6em; 1459 cursor: pointer; 1460 } 1461 .bonus-claimed-anim { 1462 position: fixed; 1463 left: 50%; top: 50%; 1464 transform: translate(-50%, -50%); 1465 z-index: 4400; 1466 color: #fff; 1467 font-size: 1.3em; 1468 padding: 10px 16px; 1469 background: rgba(0,0,0,0.5); 1470 border-radius: 12px; 1471 animation: bonusPop 0.9s ease-out forwards; 1472 } 1473 @keyframes bonusPop { 1474 0% { opacity: 0; transform: translate(-50%, -40%) scale(0.95); } 1475 20% { opacity: 1; transform: translate(-50%, -50%) scale(1.06); } 1476 60% { opacity: 1; transform: translate(-50%, -50%) scale(1.0); } 1477 100% { opacity: 0; transform: translate(-50%, -60%) scale(0.98); } 1478 } 1479 /* Create-character card */ 1480 .create-card { 1481 background: var(--tg-theme-card-bg); 1482 border: 2px dashed #a084ee; 1483 border-radius: 18px; 1484 display: flex; 1485 align-items: center; 1486 justify-content: center; 1487 gap: 12px; 1488 width: 100%; 1489 height: 120px; 1490 cursor: pointer; 1491 } 1492 .create-card .plus-btn { 1493 width: 64px; 1494 height: 64px; 1495 border-radius: 16px; 1496 background: linear-gradient(90deg, #a084ee 0%, #d8b4fe 100%); 1497 color: #fff; 1498 border: none; 1499 display: flex; 1500 align-items: center; 1501 justify-content: center; 1502 font-size: 1.8em; 1503 font-weight: bold; 1504 box-shadow: 0 4px 18px rgba(160, 132, 238, 0.35); 1505 } 1506 .create-card .plus-btn.loading { 1507 position: relative; 1508 color: transparent; 1509 pointer-events: none; 1510 opacity: 0.9; 1511 } 1512 .create-card .plus-btn.loading::after { 1513 content: ''; 1514 position: absolute; 1515 top: 50%; 1516 left: 50%; 1517 width: 22px; 1518 height: 22px; 1519 border: 3px solid rgba(255,255,255,0.35); 1520 border-top-color: #ffffff; 1521 border-radius: 50%; 1522 transform: translate(-50%, -50%); 1523 animation: spin 0.8s linear infinite; 1524 } 1525 .create-card .label { 1526 color: #fff; 1527 font-weight: bold; 1528 font-size: 1.1em; 1529 } 1530 1531 /* Character Creation Style Selection */ 1532 .character-creation-container { 1533 padding: 20px; 1534 max-width: 800px; 1535 margin: 0 auto; 1536 } 1537 1538 .style-selection-title { 1539 text-align: center; 1540 font-size: 1.4em; 1541 font-weight: bold; 1542 color: #fff; 1543 margin-bottom: 30px; 1544 } 1545 1546 .style-cards-container { 1547 display: flex; 1548 gap: 20px; 1549 justify-content: center; 1550 width: 90%; 1551 margin: 0 auto; 1552 } 1553 1554 .style-card { 1555 position: relative; 1556 flex: 1; 1557 aspect-ratio: 3/4;
1558 max-width: calc(50% - 10px); 1559 border-radius: 20px; 1560 overflow: hidden; 1561 cursor: pointer; 1562 transition: transform 0.3s ease, box-shadow 0.3s ease; 1563 border: 3px solid transparent; 1564 } 1565 1566 .style-card:hover { 1567 transform: scale(1.05); 1568 box-shadow: 0 10px 30px rgba(160, 132, 238, 0.4); 1569 border-color: #a084ee; 1570 } 1571 1572 .style-card.selected { 1573 border-color: #a084ee; 1574 box-shadow: 0 0 20px rgba(160, 132, 238, 0.6); 1575 transform: scale(1.02); 1576 } 1577 1578 .style-video { 1579 width: 100%; 1580 height: 100%; 1581 object-fit: cover; 1582 display: block; 1583 } 1584 1585 .style-label { 1586 position: absolute; 1587 bottom: 0; 1588 left: 0; 1589 right: 0; 1590 background: linear-gradient(transparent, rgba(0,0,0,0.8)); 1591 color: #fff; 1592 font-size: 1.5em; 1593 font-weight: bold; 1594 text-align: center; 1595 padding: 30px 20px 20px; 1596 } 1597 1598 /* Responsive design for mobile devices */ 1599 @media (max-width: 768px) { 1600 .style-cards-container { 1601 width: 95%; 1602 gap: 15px; 1603 } 1604 1605 .style-card { 1606 max-width: calc(50% - 7.5px); 1607 } 1608 1609 .style-label { 1610 font-size: 1.2em; 1611 padding: 20px 15px 15px; 1612 } 1613 } 1614 1615 @media (max-width: 480px) { 1616 .style-cards-container { 1617 width: 98%; 1618 gap: 10px; 1619 } 1620 1621 .style-card { 1622 max-width: calc(50% - 5px); 1623 } 1624 1625 .style-label { 1626 font-size: 1em; 1627 padding: 15px 10px 10px; 1628 } 1629 } 1630 1631 /* Character Creation Navigation Buttons */ 1632 .character-creation-navigation { 1633 display: flex; 1634 justify-content: center; 1635 align-items: center; 1636 gap: 20px; 1637 margin-top: 40px; 1638 width: 100%; 1639 } 1640 1641 .nav-button { 1642 border: none; 1643 border-radius: 12px; 1644 font-size: 1.1em; 1645 font-weight: bold; 1646 cursor: pointer; 1647 transition: all 0.3s ease; 1648 outline: none; 1649 } 1650 1651 .nav-back { 1652 width: 40px; 1653 height: 40px; 1654 background: rgba(0, 0, 0, 0.7); 1655 color: #fff; 1656 border-radius: 8px; 1657 display: flex; 1658 align-items: center; 1659 justify-content: center; 1660 position: relative; 1661 } 1662 1663 .nav-back::before { 1664 content: ''; 1665 width: 0; 1666 height: 0; 1667 border-top: 6px solid transparent; 1668 border-bottom: 6px solid transparent; 1669 border-right: 10px solid #fff; 1670 margin-left: -2px; 1671 } 1672 1673 .nav-back:hover { 1674 background: rgba(0, 0, 0, 0.8); 1675 transform: translateY(-2px); 1676 } 1677 1678 .nav-next { 1679 width: 120px; 1680 height: 40px; 1681 background: linear-gradient(90deg, #a084ee 0%, #d8b4fe 100%); 1682 color: #000; 1683 border-radius: 12px; 1684 box-shadow: 0 4px 15px rgba(160, 132, 238, 0.3); 1685 font-weight: bold; 1686 transition: all 0.3s ease; 1687 } 1688 1689 .nav-next:hover { 1690 transform: translateY(-2px); 1691 box-shadow: 0 6px 20px rgba(160, 132, 238, 0.4); 1692 } 1693 1694 .nav-next:disabled { 1695 background: #666; 1696 color: #999; 1697 box-shadow: none; 1698 cursor: not-allowed; 1699 transform: none; 1700 } 1701 1702 .nav-next:disabled:hover { 1703 transform: none; 1704 box-shadow: none; 1705 } 1706 1707 .nav-next:active { 1708 transform: translateY(0); 1709 } 1710 1711 .nav-back:active { 1712 transform: translateY(0); 1713 } 1714 1715 /* Step 2 Selection Styles */ 1716 .selection-section { 1717 margin-bottom: 30px; 1718 } 1719 1720 .section-title { 1721 font-size: 1.2em; 1722 font-weight: bold; 1723 color: #fff; 1724 margin-bottom: 15px; 1725 text-align: center; 1726 } 1727 1728 /* Ethnicity Cards */ 1729 .ethnicity-cards-container { 1730 display: flex; 1731 gap: 10px; 1732 justify-content: center; 1733 flex-wrap: wrap; 1734 margin-bottom: 20px; 1735 } 1736 1737 .ethnicity-card { 1738 width: 60px; 1739 height: 80px; 1740 border-radius: 8px; 1741 overflow: hidden; 1742 cursor: pointer; 1743 transition: all 0.3s ease; 1744 border: 2px solid transparent; 1745 position: relative; 1746 } 1747 1748 .ethnicity-card:hover { 1749 transform: scale(1.05); 1750 border-color: #a084ee; 1751 } 1752 1753 .ethnicity-card.selected { 1754 border-color: #a084ee; 1755 box-shadow: 0 0 15px rgba(160, 132, 238, 0.6); 1756 } 1757 1758 .ethnicity-image { 1759 width: 100%; 1760 height: 100%; 1761 object-fit: cover; 1762 } 1763 1764 .ethnicity-label { 1765 position: absolute; 1766 bottom: 0; 1767 left: 0; 1768 right: 0; 1769 background: linear-gradient(transparent, rgba(0,0,0,0.8)); 1770 color: #fff; 1771 font-size: 0.7em; 1772 font-weight: bold; 1773 text-align: center; 1774 padding: 8px 2px 4px; 1775 } 1776 1777 /* Age Cards */ 1778 .age-cards-container { 1779 display: flex; 1780 gap: 10px; 1781 justify-content: center; 1782 flex-wrap: wrap; 1783 margin-bottom: 20px; 1784 } 1785 1786 .age-card { 1787 width: 70px; 1788 height: 30px; 1789 background: rgba(160, 132, 238, 0.2); 1790 border: 2px solid transparent; 1791 border-radius: 8px; 1792 cursor: pointer; 1793 transition: all 0.3s ease; 1794 display: flex; 1795 align-items: center; 1796 justify-content: center; 1797 } 1798 1799 .age-card:hover { 1800 background: rgba(160, 132, 238, 0.3); 1801 border-color: #a084ee; 1802 } 1803 1804 .age-card.selected { 1805 background: rgba(160, 132, 238, 0.4); 1806 border-color: #a084ee; 1807 box-shadow: 0 0 10px rgba(160, 132, 238, 0.5); 1808 } 1809 1810 .age-label { 1811 color: #fff; 1812 font-size: 0.8em; 1813 font-weight: bold; 1814 text-align: center; 1815 } 1816 1817 /* Eye Color Cards */ 1818 .eye-color-cards-container { 1819 display: flex; 1820 gap: 15px; 1821 justify-content: center; 1822 flex-wrap: wrap; 1823 margin-bottom: 20px; 1824 } 1825 1826 .eye-color-card { 1827 width: 60px; 1828 height: 50px; 1829 border-radius: 8px; 1830 overflow: hidden; 1831 cursor: pointer; 1832 transition: all 0.3s ease; 1833 border: 3px solid transparent; 1834 position: relative; 1835 } 1836 1837 .eye-color-card:hover { 1838 transform: scale(1.1); 1839 border-color: #a084ee; 1840 } 1841 1842 .eye-color-card.selected { 1843 border-color: #a084ee; 1844 box-shadow: 0 0 15px rgba(160, 132, 238, 0.6); 1845 } 1846 1847 .eye-color-image { 1848 width: 100%; 1849 height: 100%; 1850 object-fit: cover; 1851 } 1852 1853 .eye-color-label { 1854 position: absolute; 1855 bottom: -25px; 1856 left: 50%; 1857 transform: translateX(-50%); 1858 color: #fff; 1859 font-size: 0.8em; 1860 font-weight: bold; 1861 text-align: center; 1862 white-space: nowrap; 1863 } 1864 1865 /* Step 3: Hair styles */ 1866 .hair-style-cards-container { 1867 display: flex; 1868 gap: 10px; 1869 justify-content: center; 1870 flex-wrap: wrap; 1871 margin-bottom: 20px; 1872 } 1873 .hair-style-card { 1874 width: 80px; 1875 height: 100px; 1876 border-radius: 10px; 1877 overflow: hidden; 1878 cursor: pointer; 1879 transition: all 0.3s ease; 1880 border: 2px solid transparent; 1881 position: relative; 1882 } 1883 .hair-style-card:hover { transform: scale(1.05); border-color: #a084ee; } 1884 .hair-style-card.selected { border-color: #a084ee; box-shadow: 0 0 15px rgba(160,132,238,0.6); } 1885 .hair-style-image { width: 100%; height: 100%; object-fit: cover; display: block; } 1886 .hair-style-label { 1887 position: absolute; bottom: 0; left: 0; right: 0; 1888 background: linear-gradient(transparent, rgba(0,0,0,0.85)); 1889 color: #fff; font-size: 0.75em; font-weight: bold; text-align: center; padding: 8px 2px 4px; 1890 } 1891 1892 /* Step 3: Hair colors */ 1893 .hair-color-cards-container { display: flex; gap: 10px; justify-content: center; flex-wrap: wrap; } 1894 .hair-color-card { 1895 width: 70px; height: 36px; border-radius: 8px; border: 2px solid transparent; 1896 display: flex; align-items: center; justify-content: center; cursor: pointer; 1897 transition: all 0.3s ease; color: #000; font-weight: 700; font-size: 0.8em; 1898 } 1899 .hair-color-card:hover { transform: translateY(-2px); border-color: #a084ee; } 1900 .hair-color-card.selected { outline: 2px solid #a084ee; outline-offset: 0; } 1901 .hair-color-card .hair-color-label { text-shadow: 0 1px 2px rgba(0,0,0,0.25); } 1902 1903 /* Color backgrounds */ 1904 .hair-color-card[data-hair-color="blonde"] { background: #f7e07b; } 1905 .hair-color-card[data-hair-color="brunette"] { background: #6b4f3a; color:#fff; } 1906 .hair-color-card[data-hair-color="black"] { background: #111; color:#fff; } 1907 .hair-color-card[data-hair-color="red"] { background: #d14b4b; color:#fff; } 1908 .hair-color-card[data-hair-color="pink"] { background: #ff6fb7; } 1909 .hair-color-card[data-hair-color="white"] { background: #f0f0f0; } 1910 .hair-color-card[data-hair-color="blue"] { background: #4a7bd1; color:#fff; } 1911 .hair-color-card[data-hair-color="green"] { background: #3aa55d; color:#fff; } 1912 .hair-color-card[data-hair-color="purple"] { background: #7c4dff; color:#fff; } 1913 .hair-color-card.multicolor { background: linear-gradient(90deg, #ff2a2a 0%, #7a2aff 50%, #7fff2a 100%); color:#fff; } 1914 1915 /* Step 4: Body / Breast / Butt cards */ 1916 .body-type-cards-container, .breast-size-cards-container, .butt-size-cards-container { 1917 display: flex; gap: 10px; justify-content: center; flex-wrap: wrap; margin-bottom: 20px; 1918 } 1919 .body-type-card, .breast-size-card, .butt-size-card { 1920 width: 90px; height: 110px; border-radius: 10px; overflow: hidden; cursor: pointer; transition: all 0.3s ease; border: 2px solid transparent; position: relative; 1921 } 1922 .body-type-card:hover, .breast-size-card:hover, .butt-size-card:hover { transform: scale(1.05); border-color: #a084ee; } 1923 .body-type-card.selected, .breast-size-card.selected, .butt-size-card.selected { border-color: #a084ee; box-shadow: 0 0 15px rgba(160,132,238,0.6); } 1924 .body-type-image, .breast-size-image, .butt-size-image { width: 100%; height: 100%; object-fit: cover; display: block; } 1925 .body-type-label, .breast-size-label, .butt-size-label { position: absolute; bottom: 0; left: 0; right: 0; background: linear-gradient(transparent, rgba(0,0,0,0.85)); color: #fff; font-size: 0.75em; font-weight: bold; text-align: center; padding: 8px 2px 4px; } 1926 1927 /* Step 5: Personality */ 1928 .personality-cards-container { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px; } 1929 @media (min-width: 520px) { .personality-cards-container { grid-template-columns: repeat(3, minmax(0, 1fr)); } } 1930 .personality-card { 1931 background: #121212; border: 1.5px solid #212121; border-radius: 12px; padding: 8px; color: #fff; 1932 display: flex; flex-direction: column; align-items: center; justify-content: flex-start; gap: 4px; 1933 text-align: center; cursor: pointer; transition: transform .2s ease, box-shadow .2s ease, border-color .2s ease; 1934 } 1935 .personality-card:hover { transform: translateY(-2px); border-color: #a084ee; box-shadow: 0 6px 18px rgba(160,132,238,0.25); } 1936 .personality-card.selected { outline: 2px solid #a084ee; outline-offset: 0; box-shadow: 0 0 0 3px rgba(160,132,238,0.25) inset; } 1937 .personality-emoji { font-size: 1.4em; line-height: 1; } 1938 .personality-title { font-weight: 800; margin: 0; font-size: 0.95em; } 1939 .personality-desc { font-size: 0.85em; color: #bfaed1; line-height: 1.2; width: 100%; } 1940 1941 /* Step 6: Relationship */ 1942 .relationship-cards-container { 1943 display: grid; 1944 grid-template-columns: repeat(3, minmax(0, 1fr)); 1945 gap: 10px; 1946 } 1947 .relationship-card { 1948 position: relative; 1949 background: #121212; 1950 border: 1.5px solid #212121; 1951 border-radius: 10px; 1952 width: 100%; 1953 padding-top: 100%; /* Square */ 1954 cursor: pointer; 1955 transition: transform .2s ease, box-shadow .2s ease, border-color .2s ease; 1956 --rel-card-size: 100px; 1957 } 1958 .relationship-card:hover { transform: translateY(-2px); border-color: #a084ee; box-shadow: 0 6px 18px rgba(160,132,238,0.25); } 1959 .relationship-card.selected { outline: 2px solid #a084ee; outline-offset: 0; box-shadow: 0 0 0 3px rgba(160,132,238,0.25) inset; } 1960 .relationship-emoji, .relationship-title { 1961 position: absolute; left: 50%; transform: translateX(-50%); 1962 text-align: center; color: #fff; 1963 } 1964 .relationship-emoji { top: 35%; line-height: 1; font-size: clamp(14px, 0.18 * var(--rel-card-size, 100px), 26px); } 1965 .relationship-title { top: 65%; font-weight: 800; font-size: clamp(11px, 0.12 * var(--rel-card-size, 100px), 18px); } 1966 1967 /* Summary page */ 1968 .summary-cards-container { 1969 display: grid; 1970 grid-template-columns: repeat(3, minmax(0, 1fr)); 1971 gap: 12px; 1972 } 1973 1974 .summary-category { 1975 display: flex; 1976 flex-direction: column; 1977 gap: 8px; 1978 } 1979 1980 .summary-category-label { 1981 font-size: 0.8em; 1982 font-weight: normal; 1983 color: #fff; 1984 text-align: center; 1985 } 1986 1987 .summary-card { 1988 position: relative; 1989 background: #121212; 1990 border: 1.5px solid #212121; 1991 border-radius: 10px; 1992 width: 100%; 1993 padding-top: 100%; /* square */ 1994 overflow: hidden; 1995 } 1996 .summary-card img { 1997 position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; display: block; 1998 } 1999 .summary-card .summary-overlay { 2000 position: absolute; left: 0; right: 0; bottom: 0; 2001 background: linear-gradient(transparent, rgba(0,0,0,0.85)); 2002 color: #fff; text-align: center; padding: 10px 6px 8px; 2003 font-weight: 700; font-size: 0.9em; 2004 } 2005 .summary-card .summary-text { 2006 position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; color: #fff; font-weight: 700; 2007 padding: 6px; text-align: center; 2008 } 2009 2010 .nav-bring-to-life { 2011 width: 120px; 2012 height: 40px; 2013 background: linear-gradient(90deg, #a084ee 0%, #d8b4fe 100%); 2014 color: #000; 2015 border-radius: 12px; 2016 box-shadow: 0 4px 15px rgba(160, 132, 238, 0.3); 2017 font-weight: bold; 2018 transition: all 0.3s ease; 2019 } 2020 2021 .nav-bring
2021-to-life:hover { 2022 transform: translateY(-2px); 2023 box-shadow: 0 6px 20px rgba(160, 132, 238, 0.4); 2024 } 2025 2026 .nav-bring-to-life:active { 2027 transform: translateY(0); 2028 } 2029 2030 /* Character Creation Status Modal */ 2031 #creation-status-modal { 2032 position: fixed; 2033 top: 0; 2034 left: 0; 2035 width: 100%; 2036 height: 100%; 2037 background: rgba(0, 0, 0, 0.8); 2038 display: none; 2039 justify-content: center; 2040 align-items: center; 2041 z-index: 5000; 2042 } 2043 2044 .creation-status-content { 2045 background: #121212; 2046 border: 2px solid #a084ee; 2047 border-radius: 20px; 2048 padding: 30px; 2049 text-align: center; 2050 min-width: 320px; 2051 max-width: 90vw; 2052 } 2053 2054 .creation-status-title { 2055 color: #fff; 2056 font-size: 1.5em; 2057 font-weight: bold; 2058 margin-bottom: 20px; 2059 } 2060 2061 .creation-status-message { 2062 color: #bfaed1; 2063 font-size: 1.1em; 2064 margin-bottom: 20px; 2065 } 2066 2067 .progress-container { 2068 width: 100%; 2069 background: #2a2a2a; 2070 border-radius: 10px; 2071 height: 20px; 2072 margin-bottom: 20px; 2073 overflow: hidden; 2074 } 2075 2076 .progress-bar { 2077 height: 100%; 2078 background: linear-gradient(90deg, #a084ee 0%, #d8b4fe 100%); 2079 border-radius: 10px; 2080 width: 0%; 2081 transition: width 0.1s ease; 2082 position: relative; 2083 } 2084 2085 .progress-text { 2086 position: absolute; 2087 top: 50%; 2088 left: 50%; 2089 transform: translate(-50%, -50%); 2090 color: #000; 2091 font-weight: bold; 2092 font-size: 0.8em; 2093 } 2094 2095 .creation-status-btn { 2096 background: linear-gradient(90deg, #a084ee 0%, #d8b4fe 100%); 2097 color: #000; 2098 border: none; 2099 border-radius: 12px; 2100 padding: 12px 24px; 2101 font-weight: bold; 2102 font-size: 1em; 2103 cursor: pointer; 2104 transition: all 0.3s ease; 2105 margin: 0 auto; 2106 display: block; 2107 } 2108 2109 .creation-status-btn:hover { 2110 transform: translateY(-2px); 2111 box-shadow: 0 6px 20px rgba(160, 132, 238, 0.4); 2112 } 2113 2114 .creation-status-btn:active { 2115 transform: translateY(0); 2116 } 2117 2118 /* Create-character modal */ 2119 #create-modal-blur { 2120 position: fixed; inset: 0; display: none; z-index: 3200; 2121 background: rgba(0,0,0,0.4); 2122 backdrop-filter: blur(8px); 2123 -webkit-backdrop-filter: blur(8px); 2124 } 2125 #create-modal { 2126 position: fixed; left: 50%; top: 50%; transform: translate(-50%, -50%); 2127 display: none; z-index: 3300; 2128 background: #121212; border-radius: 18px; padding: 22px; min-width: 260px; max-width: 90vw; 2129 border: 1.5px solid #212121; box-shadow: 0 8px 40px 0 #000a; 2130 } 2131 #create-modal .title { color: #fff; font-weight: bold; font-size: 1.15em; margin-bottom: 10px; text-align: center; } 2132 #create-modal .desc { color: #bfaed1; font-size: 1.02em; text-align: center; margin-bottom: 16px; } 2133 #create-modal .btns { display: flex; align-items: center; justify-content: center; gap: 12px; } 2134 #create-modal .btn { 2135 background: linear-gradient(90deg, #a084ee 0%, #d8b4fe 100%); 2136 color: #fff; border: none; border-radius: 12px; padding: 10px 20px; font-weight: bold; cursor: pointer; 2137 } 2138 #create-modal .close { background: #111; } 2139 2140 /* Loading Screen Styles */ 2141 .loading-screen { 2142 position: fixed; 2143 top: 0; 2144 left: 0; 2145 width: 100%; 2146 height: 100%; 2147 background: #0a0a0a; 2148 display: flex; 2149 align-items: center; 2150 justify-content: center; 2151 z-index: 9999; 2152 opacity: 1; 2153 transition: opacity 0.5s ease-out; 2154 } 2155 2156 .loading-screen.hidden { 2157 opacity: 0; 2158 pointer-events: none; 2159 } 2160 2161 .loading-content { 2162 text-align: center; 2163 color: #fff; 2164 } 2165 2166 .loading-spinner { 2167 width: 60px; 2168 height: 60px; 2169 border: 4px solid rgba(160, 132, 238, 0.3);
2170 border-top: 4px solid #a084ee; 2171 border-radius: 50%; 2172 animation: spin 1s linear infinite; 2173 margin: 0 auto 20px; 2174 } 2175 2176 @keyframes spin { 2177 0% { transform: rotate(0deg); } 2178 100% { transform: rotate(360deg); } 2179 } 2180 2181 .loading-title { 2182 font-size: 1.5em; 2183 font-weight: bold; 2184 margin-bottom: 10px; 2185 color: #fff; 2186 } 2187 2188 .loading-subtitle { 2189 font-size: 1em; 2190 color: #bfaed1; 2191 margin: 0; 2192 } 2193 2194 /* Photo loading indicator */ 2195 .photo-loading-spinner { 2196 width: 40px; 2197 height: 40px; 2198 border: 3px solid rgba(160, 132, 238, 0.3); 2199 border-top: 3px solid #a084ee; 2200 border-radius: 50%; 2201 animation: spin 1s linear infinite; 2202 margin: 0 auto; 2203 } 2204 2205 .photo-loading-container { 2206 display: flex; 2207 align-items: center; 2208 justify-content: center; 2209 width: 150px; 2210 height: 100%; 2211 min-height: 200px; 2212 background: #121212; 2213 border-radius: 16px 0 0 16px; 2214 position: relative; 2215 overflow: hidden; 2216 flex-shrink: 0; 2217 } 2218 </style> 2219</head> 2220<body> 2221 2222 <header> 2223 <div id="page-title" class="page-title"></div> 2224 <div id="user-balance"> 2225 <button id="daily-bonus-btn" class="daily-bonus-btn" title="Daily Bonus">ð</button> 2226 <div class="currency-panel"> 2227 <div class="currency-item"> 2228 <span class="currency-icon">ð¬</span> 2229 <span class="currency-value">0</span> 2230 </div> 2231 <div class="currency-item"> 2232 <span class="currency-icon">ð</span> 2233 <span class="currency-value">0</span> 2234 </div> 2235 <div class="currency-item"> 2236 <span class="currency-icon">ð</span> 2237 <span class="currency-value">0</span> 2238 </div> 2239 <button class="add-currency-btn">+</button> 2240 </div> 2241 </div> 2242 </header> 2243 2244 <nav id="bottom-nav"> 2245 <div id="bottom-nav-inner"> 2246 <button id="nav-girls" class="active" aria-label="ÐевÑÑки"> 2247 <svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M17 21v-4a2 2 0 0 0-2-2H9a2 2 0 0 0-2 2v4"/><path d="M12 7a4 4 0 1 0 0-8 4 4 0 0 0 0 8z"/></svg> 2248 </button> 2249 2250 <button id="nav-lollipops" aria-label="ÐеденÑÑ"> 2251 <svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> 2252 <line x1="12" y1="1" x2="12" y2="23"/> 2253 <path d="M17 5H9.5a3.5 3.5 0 0 0 0 7h5a3.5 3.5 0 0 1 0 7H6"/> 2254 </svg> 2255 </button> 2256 </div> 2257 </nav> 2258 2259 <div class="container"> 2260 <!-- Loading Screen --> 2261 <div id="loading-screen" class="loading-screen"> 2262 <div class="loading-content"> 2263 <div class="loading-spinner"></div> 2264 <h2 class="loading-title">Loading Characters...</h2> 2265 <p class="loading-subtitle">Please wait while we fetch your characters</p> 2266 </div> 2267 </div> 2268 2269 <div id="page-girls" class="page active"> 2270 <div id="girls-shop" class="shop-grid"></div> 2271 </div> 2272 2273 2274 2275 <div id="page-lollipops" class="page"> 2276 <button id="back-button" class="back-button" aria-label="Ðазад"> 2277 <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> 2278 <path d="M19 12H5"/> 2279 <path d="M12 19l-7-7 7-7"/> 2280 </svg> 2281 </button> 2282 <div id="lollipops-shop" class="shop-grid"></div> 2283 <div id="pack-description" class="pack-description"></div> 2284 <div id="upgrade-text" class="upgrade-text"></div> 2285 </div> 2286 2287 <div id="page-character-creation" class="page"> 2288 <div class="character-creation-container"> 2289 <div class="style-selection-title">Chose Style</div> 2290 <div class="style-cards-container"> 2291 <div class="style-card" data-style="realistic"> 2292 <video class="style-video" autoplay muted loop> 2293 <source src="Videos/Selection.mp4" type="video/mp4"> 2294 </video> 2295 <div class="style-label">Realistic</div> 2296 </div> 2297 <div class="style-card" data-style="anime"> 2298 <video class="style-video" autoplay muted loop> 2299 <source src="Videos/Selection2.mp4" type="video/mp4"> 2300 </video> 2301 <div class="style-label">Anime</div> 2302 </div> 2303 </div> 2304 <div class="character-creation-navigation"> 2305 <button class="nav-button nav-back"></button> 2306 <button class="nav-button nav-next" disabled>Next</button> 2307 </div> 2308 </div> 2309 </div> 2310 2311 <div id="page-character-creation-step2" class="page"> 2312 <div class="character-creation-container"> 2313 2314 <!-- Ethnicity Selection --> 2315 <div class="selection-section"> 2316 <div class="section-title">Choose Ethnicity</div> 2317 <div class="ethnicity-cards-container"> 2318 <div class="ethnicity-card" data-ethnicity="caucasian"> 2319 <img class="ethnicity-image" src="" alt="Caucasian" loading="lazy"> 2320 <div class="ethnicity-label">Caucasian</div> 2321 </div> 2322 <div class="ethnicity-card" data-ethnicity="latina"> 2323 <img class="ethnicity-image" src="" alt="Latina" loading="lazy"> 2324 <div class="ethnicity-label">Latina</div> 2325 </div> 2326 <div class="ethnicity-card" data-ethnicity="asian"> 2327 <img class="ethnicity-image" src="" alt="Asian" loading="lazy"> 2328 <div class="ethnicity-label">Asian</div> 2329 </div> 2330 <div class="ethnicity-card" data-ethnicity="arab"> 2331 <img class="ethnicity-image" src="" alt="Arab" loading="lazy"> 2332 <div class="ethnicity-label">Arab</div> 2333 </div> 2334 <div class="ethnicity-card" data-ethnicity="black"> 2335 <img class="ethnicity-image" src="" alt="Black" loading="lazy"> 2336 <div class="ethnicity-label">Black</div> 2337 </div> 2338 </div> 2339 </div> 2340 2341 <!-- Age Selection --> 2342 <div class="selection-section"> 2343 <div class="section-title">Choose Age</div> 2344 <div class="age-cards-container"> 2345 <div class="age-card" data-age="teen"> 2346 <div class="age-label">Teen (18+)</div> 2347 </div> 2348 <div class="age-card" data-age="20s"> 2349 <div class="age-label">20s</div> 2350 </div> 2351 <div class="age-card" data-age="30s"> 2352 <div class="age-label">30s</div> 2353 </div> 2354 <div class="age-card" data-age="40-55"> 2355 <div class="age-label">40-55</div> 2356 </div> 2357 </div> 2358 </div> 2359 2360 <!-- Eye Color Selection --> 2361 <div class="selection-section"> 2362 <div class="section-title">Choose Eye Color</div> 2363 <div class="eye-color-cards-container"> 2364 <div class="eye-color-card" data-eye-color="brown"> 2365 <img class="eye-color-image" src="Photos/Creation/eyeBrown.jpg" alt="Brown Eyes" loading="lazy"> 2366 <div class="eye-color-label">Brown</div> 2367 </div> 2368 <div class="eye-color-card" data-eye-color="blue"> 2369 <img class="eye-color-image" src="Photos/Creation/eyeBlue.jpg" alt="Blue Eyes" loading="lazy"> 2370 <div class="eye-color-label">Blue</div> 2371 </div> 2372 <div class="eye-color-card" data-eye-color="green"> 2373 <img class="eye-color-image" src="Photos/Creation/eyeGreen.jpg" alt="Green Eyes" loading="lazy"> 2374 <div class="eye-color-label">Green</div> 2375 </div> 2376 </div> 2377 </div> 2378 2379 <div class="character-creation-navigation"> 2380 <button class="nav-button nav-back"></button> 2381 <button class="nav-button nav-next" disabled>Next</button> 2382 </div> 2383 </div> 2384 </div> 2385 2386 <div id="page-character-creation-step3" class="page"> 2387 <div class="character-creation-container"> 2388 <div class="selection-section"> 2389 <div class="section-title">Choose Hair Style</div> 2390 <div class="hair-style-cards-container"> 2391 <div class="hair-style-card" data-hair="straight"> 2392 <img class="hair-style-image" src="" alt="Straight" loading="lazy"> 2393 <div class="hair-style-label">Straight</div> 2394 </div> 2395 <div class="hair-style-card" data-hair="bangs"> 2396 <img class="hair-style-image" src="" alt="Bangs" loading="lazy"> 2397 <div class="hair-style-label">Bangs</div> 2398 </div> 2399 <div class="hair-style-card" data-hair="curly"> 2400 <img class="hair-style-image" src="" alt="Curly" loading="lazy"> 2401 <div class="hair-style-label">Curly</div> 2402 </div> 2403 <div class="hair-style-card" data-hair="bun"> 2404 <img class="hair-style-image" src="" alt="Bun" loading="lazy"> 2405 <div class="hair-style-label">Bun</div> 2406 </div> 2407 <div class="hair-style-card" data-hair="short"> 2408 <img class="hair-style-image" src="" alt="Short" loading="lazy"> 2409 <div class="hair-style-label">Short</div> 2410 </div> 2411 <div class="hair-style-card" data-hair="long"> 2412 <img class="hair-style-image" src="" alt="Long" loading="lazy"> 2413 <div class="hair-style-label">Long</div> 2414 </div> 2415 <div class="hair-style-card" data-hair="ponytail"> 2416 <img class="hair-style-image" src="" alt="Ponytail" loading="lazy"> 2417 <div class="hair-style-label">Ponytail</div> 2418 </div> 2419 </div> 2420 </div> 2421 2422 <div class="selection-section"> 2423 <div class="section-title">Choose Hair Color</div> 2424 <div class="hair-color-cards-container"> 2425 <div class="hair-color-card" data-hair-color="blonde"><span class="hair-color-label">Blonde</span></div> 2426 <div class="hair-color-card" data-hair-color="brunette"><span class="hair-color-label">Brunette</span></div> 2427 <div class="hair-color-card" data-hair-color="black"><span class="hair-color-label">Black</span></div> 2428 <div class="hair-color-card" data-hair-color="red"><span class="hair-color-label">Red</span></div> 2429 <div class="hair-color-card" data-hair-color="pink"><span class="hair-color-label">Pink</span></div> 2430 <div class="hair-color-card" data-hair-color="white"><span class="hair-color-label">White</span></div> 2431 <div class="hair-color-card" data-hair-color="blue"><span class="hair-color-label">Blue</span></div> 2432 <div class="hair-color-card" data-hair-color="green"><span class="hair-color-label">Green</span></div> 2433 <div class="hair-color-card" data-hair-color="purple"><span class="hair-color-label">Purple</span></div> 2434 <div class="hair-color-card multicolor" data-hair-color="multicolor"><span class="hair-color-label">Multicolor</span></div> 2435 </div> 2436 </div> 2437 2438 <div class="character-creation-navigation"> 2439 <button class="nav-button nav-back"></button> 2440 <button class="nav-button nav-next" disabled>Next</button> 2441 </div> 2442 </div> 2443 </div> 2444 2445 <div id="page-character-creation-step4" class="page"> 2446 <div class="character-creation-container"> 2447 <!-- Body Type --> 2448 <div class="selection-section"> 2449 <div class="section-title">Choose Body Type</div> 2450 <div class="body-type-cards-container"> 2451 <div class="body-type-card" data-body="slim"> 2452 <img class="body-type-image" src="" alt="Slim" loading="lazy"> 2453 <div class="body-type-label">Slim</div> 2454 </div> 2455 <div class="body-type-card" data-body="athletic"> 2456 <img class="body-type-image" src="" alt="Athletic" loading="lazy"> 2457 <div class="body-type-label">Athletic</div> 2458 </div> 2459 <div class="body-type-card" data-body="voluptuous"> 2460 <img class="body-type-image" src="" alt="Voluptuous" loading="lazy"> 2461 <div class="body-type-label">Voluptuous</div> 2462 </div> 2463 <div class="body-type-card" data-body="curvy"> 2464 <img class="body-type-image" src="" alt="Curvy" loading="lazy"> 2465 <div class="body-type-label">Curvy</div> 2466 </div> 2467 </div> 2468 </div> 2469 2470 <!-- Breast Size --> 2471 <div class="selection-section"> 2472 <div class="section-title">Choose Breast Size</div> 2473 <div class="breast-size-cards-container"> 2474 <div class="breast-size-card" data-breast="small"> 2475 <img class="breast-size-image" src="" alt="Small" loading="lazy"> 2476 <div class="breast-size-label">Small</div> 2477 </div> 2478 <div class="breast-size-card" data-breast="medium"> 2479 <img class="breast-size-image" src="" alt="Medium" loading="lazy"> 2480 <div class="breast-size-label">Medium</div> 2481 </div> 2482 <div class="breast-size-card" data-breast="large"> 2483 <img class="breast-size-image" src="" alt="Large" loading="lazy"> 2484 <div class="breast-size-label">Large</div> 2485 </div> 2486 <div class="breast-size-card" data-breast="huge"> 2487 <img class="breast-size-image" src="" alt="Huge" loading="lazy"> 2488 <div class="breast-size-label">Huge<
2488/div> 2489 </div> 2490 </div> 2491 </div> 2492 2493 <!-- Butt Size --> 2494 <div class="selection-section"> 2495 <div class="section-title">Choose Butt Size</div> 2496 <div class="butt-size-cards-container"> 2497 <div class="butt-size-card" data-butt="small"> 2498 <img class="butt-size-image" src="" alt="Small" loading="lazy"> 2499 <div class="butt-size-label">Small</div> 2500 </div> 2501 <div class="butt-size-card" data-butt="medium"> 2502 <img class="butt-size-image" src="" alt="Medium" loading="lazy"> 2503 <div class="butt-size-label">Medium</div> 2504 </div> 2505 <div class="butt-size-card" data-butt="large"> 2506 <img class="butt-size-image" src="" alt="Large" loading="lazy"> 2507 <div class="butt-size-label">Large</div> 2508 </div> 2509 <div class="butt-size-card" data-butt="athletic"> 2510 <img class="butt-size-image" src="" alt="Athletic" loading="lazy"> 2511 <div class="butt-size-label">Athletic</div> 2512 </div> 2513 </div> 2514 </div> 2515 2516 <div class="character-creation-navigation"> 2517 <button class="nav-button nav-back"></button> 2518 <button class="nav-button nav-next" disabled>Next</button> 2519 </div> 2520 </div> 2521 </div> 2522 2523 <div id="page-character-creation-step5" class="page"> 2524 <div class="character-creation-container"> 2525 <div class="selection-section"> 2526 <div class="section-title">Choose Personality</div> 2527 <div class="personality-cards-container"> 2528 <div class="personality-card" data-personality="caregiver"> 2529 <div class="personality-emoji">ð¤±</div> 2530 <div class="personality-title">Caregiver</div> 2531 <div class="personality-desc">Nurturing, protective, and always there to offer comfort.</div> 2532 </div> 2533 <div class="personality-card" data-personality="sage"> 2534 <div class="personality-emoji">ð§</div> 2535 <div class="personality-title">Sage</div> 2536 <div class="personality-desc">Wise, reflective, and a source of guidance.</div> 2537 </div> 2538 <div class="personality-card" data-personality="innocent"> 2539 <div class="personality-emoji">ð¼</div> 2540 <div class="personality-title">Innocent</div> 2541 <div class="personality-desc">Optimistic, naive, and sees world with wonder.</div> 2542 </div> 2543 <div class="personality-card" data-personality="jester"> 2544 <div class="personality-emoji">ð</div> 2545 <div class="personality-title">Jester</div> 2546 <div class="personality-desc">Playful, humorous, and always there to make you laugh.</div> 2547 </div> 2548 <div class="personality-card" data-personality="temptress"> 2549 <div class="personality-emoji">ð</div> 2550 <div class="personality-title">Temptress</div> 2551 <div class="personality-desc">Flirtatious, playful, and always leaving you wanting more.</div> 2552 </div> 2553 <div class="personality-card" data-personality="dominant"> 2554 <div class="personality-emoji">ð¦</div> 2555 <div class="personality-title">Dominant</div> 2556 <div class="personality-desc">Assertive, controlling, and commanding.</div> 2557 </div> 2558 <div class="personality-card" data-personality="submissive"> 2559 <div class="personality-emoji">ð</div> 2560 <div class="personality-title">Submissive</div> 2561 <div class="personality-desc">Obedient, yielding, and happy to follow.</div> 2562 </div> 2563 <div class="personality-card" data-personality="lover"> 2564 <div class="personality-emoji">ð</div> 2565 <div class="personality-title">Lover</div> 2566 <div class="personality-desc">Romantic, affectionate, and cherishes deep emotional</div> 2567 </div> 2568 <div class="personality-card" data-personality="nympho"> 2569 <div class="personality-emoji">ð¥</div> 2570 <div class="personality-title">Nympho</div> 2571 <div class="personality-desc">Insatiable, passionate, and constantly craving intimacy.</div> 2572 </div> 2573 <div class="personality-card" data-personality="mean"> 2574 <div class="personality-emoji">ð§</div> 2575 <div class="personality-title">Mean</div> 2576 <div class="personality-desc">Cold, dismissive, and often sarcastic.</div> 2577 </div> 2578 <div class="personality-card" data-personality="confidant"> 2579 <div class="personality-emoji">ð¤</div> 2580 <div class="personality-title">Confidant</div> 2581 <div class="personality-desc">Trustworthy, a good listener, and always can offer advice.</div> 2582 </div> 2583 <div class="personality-card" data-personality="experimenter"> 2584 <div class="personality-emoji">ð§ª</div> 2585 <div class="personality-title">Experimenter</div> 2586 <div class="personality-desc">
2586Curious, willing, and always eager to try new things.</div> 2587 </div> 2588 </div> 2589 </div> 2590 2591 <div class="character-creation-navigation"> 2592 <button class="nav-button nav-back"></button> 2593 <button class="nav-button nav-next" disabled>Next</button> 2594 </div> 2595 </div> 2596 </div> 2597 2598 <div id="page-character-creation-step6" class="page"> 2599 <div class="character-creation-container"> 2600 <div class="selection-section"> 2601 <div class="section-title">Choose Relationship</div> 2602 <div class="relationship-cards-container"> 2603 <div class="relationship-card" data-relationship="stranger"> 2604 <div class="relationship-emoji">ðµï¸ââï¸</div> 2605 <div class="relationship-title">Stranger</div> 2606 </div> 2607 <div class="relationship-card" data-relationship="school_mate"> 2608 <div class="relationship-emoji">ð</div> 2609 <div class="relationship-title">School Mate</div> 2610 </div> 2611 <div class="relationship-card" data-relationship="colleague"> 2612 <div class="relationship-emoji">ð¼</div> 2613 <div class="relationship-title">Colleague</div> 2614 </div> 2615 <div class="relationship-card" data-relationship="mentor"> 2616 <div class="relationship-emoji">ð§âð«</div> 2617 <div class="relationship-title">Mentor</div> 2618 </div> 2619 <div class="relationship-card" data-relationship="girlfriend"> 2620 <div class="relationship-emoji">ð</div> 2621 <div class="relationship-title">Girlfriend</div> 2622 </div> 2623 <div class="relationship-card" data-relationship="sex_friend"> 2624 <div class="relationship-emoji">ð¥</div> 2625 <div class="relationship-title">Sex Friend</div> 2626 </div> 2627 <div class="relationship-card" data-relationship="wife"> 2628 <div class="relationship-emoji">ð</div> 2629 <div class="relationship-title">Wife</div> 2630 </div> 2631 <div class="relationship-card" data-relationship="mistress"> 2632 <div class="relationship-emoji">ð </div> 2633 <div class="relationship-title">Mistress</div> 2634 </div> 2635 <div class="relationship-card" data-relationship="friend"> 2636 <div class="relationship-emoji">ð¤</div> 2637 <div class="relationship-title">Friend</div> 2638 </div> 2639 <div class="relationship-card" data-relationship="best_friend"> 2640 <div class="relationship-emoji">ð</div> 2641 <div class="relationship-title">Best Friend</div> 2642 </div> 2643 <div class="relationship-card" data-relationship="step_sister"> 2644 <div class="relationship-emoji">ð§</div> 2645 <div class="relationship-title">Step Sister</div> 2646 </div> 2647 <div class="relationship-card" data-relationship="step_mom"> 2648 <div class="relationship-emoji">ð©âð§</div> 2649 <div class="relationship-title">Step Mom</div> 2650 </div> 2651 </div> 2652 </div> 2653 2654 <div class="character-creation-navigation"> 2655 <button class="nav-button nav-back"></button> 2656 <button class="nav-button nav-next" disabled>Next</button> 2657 </div> 2658 </div> 2659 </div> 2660 2661 <div id="page-character-creation-summary" class="page"> 2662 <div class="character-creation-container"> 2663 <div class="selection-section"> 2664 <div class="section-title">Summary</div> 2665 <div class="summary-cards-container"> 2666 <!-- Style --> 2667 <div class="summary-category"> 2668 <div class="summary-category-label">Style</div> 2669 <div class="summary-card" id="summary-style"> 2670 <div class="summary-card-content"> 2671 <div class="summary-card-title">Loading...</div> 2672 </div> 2673 </div> 2674 </div> 2675 2676 <!-- Ethnicity --> 2677 <div class="summary-category"> 2678 <div class="summary-category-label">Ethnicity</div> 2679 <div class="summary-card" id="summary-ethnicity"> 2680 <div class="summary-card-content"> 2681 <div class="summary-card-title">Loading...</div> 2682 </div> 2683 </div> 2684 </div> 2685 2686 <!-- Age --> 2687 <div class="summary-category"> 2688 <div class="summary-category-label">Age</div> 2689 <div class="summary-card" id="summary-age"> 2690 <div class="summary-card-content"> 2691 <div class="summary-card-title">Loading...</div> 2692 </div> 2693 </div> 2694 </div> 2695 2696 <!-- Eye Color --> 2697 <div class="summary-category"> 2698 <div class="summary-category-label">Eye Color</div> 2699 <div class="summary-card" id="summary-eye-color"> 2700 <div class="summary-card-content"> 2701 <div class="summary-card-title">Loading...</div> 2702 </div> 2703 </div> 2704 </div> 2705 2706 <!-- Hair Style --> 2707 <div class="summary-category"> 2708 <div class="summary-category-label">Hair Style</div> 2709 <div class="summary-card" id="summary-hair-style"> 2710 <div class="summary-card-content"> 2711 <div class="summary-card-title">Loading...</div> 2712 </div> 2713 </div> 2714 </div> 2715 2716 <!-- Hair Color --> 2717 <div class="summary-category"> 2718 <div class="summary-category-label">Hair Color</div> 2719 <div class="summary-card" id="summary-hair-color"> 2720 <div class="summary-card-content"> 2721 <div class="summary-card-title">Loading...</div> 2722 </div> 2723 </div> 2724 </div> 2725 2726 <!-- Body Type --> 2727 <div class="summary-category"> 2728 <div class="summary-category-label">Body Type</div> 2729 <div class="summary-card" id="summary-body-type"> 2730 <div class="summary-card-content"> 2731 <div class="summary-card-title">Loading...</div> 2732 </div> 2733 </div> 2734 </div> 2735 2736 <!-- Breast Size --> 2737 <div class="summary-category"> 2738 <div class="summary-category-label">Breast Size</div> 2739 <div class="summary-card" id="summary-breast-size"> 2740 <div class="summary-card-content"> 2741 <div class="summary-card-title">Loading...</div> 2742 </div> 2743 </div> 2744 </div> 2745 2746 <!-- Butt Size --> 2747 <div class="summary-category"> 2748 <div class="summary-category-label">Butt Size</div> 2749 <div class="summary-card" id="summary-butt-size"> 2750 <div class="summary-card-content"> 2751 <div class="summary-card-title">Loading...</div> 2752 </div> 2753 </div> 2754 </div> 2755 2756 <!-- Personality --> 2757 <div class="summary-category"> 2758 <div class="summary-category-label">Personality</div> 2759 <div class="summary-card" id="summary-personality"> 2760 <div class="summary-card-content"> 2761 <div class="summary-card-title">Loading...</div> 2762 </div> 2763 </div> 2764 </div> 2765 2766 <!-- Relationship --> 2767 <div class="summary-category"> 2768 <div class="summary-category-label">Relationship</div> 2769 <div class="summary-card" id="summary-relationship"> 2770 <div class="summary-card-content"> 2771 <div class="summary-card-title">Loading...</div> 2772 </div> 2773 </div> 2774 </div> 2775 </div> 2776 </div> 2777 2778 <div class="character-creation-navigation"> 2779 <button class="nav-button nav-back"></button> 2780 <button class="nav-button nav-bring-to-life">Bring To Life</button> 2781 </div> 2782 </div> 2783 </div> 2784 </div> 2785 2786 <!-- Ðнопка Upgrade Ð´Ð»Ñ Ð¿Ð¾Ð´Ð¿Ð¸Ñок --> 2787 <div id="upgrade-section" class="upgrade-section"> 2788 <button id="upgrade-button" class="upgrade-button">
2789 <span class="upgrade-button-text"></span> 2790 </button> 2791 </div> 2792 2793 <!-- Character Creation Status Modal --> 2794 <div id="creation-status-modal"> 2795 <div class="creation-status-content"> 2796 <div class="creation-status-title"></div> 2797 <div class="creation-status-message" id="creation-status-message"></div> 2798 <div class="progress-container" id="progress-container"> 2799 <div class="progress-bar" id="progress-bar"> 2800 <div class="progress-text" id="progress-text">0%</div> 2801 </div> 2802 </div> 2803 <button class="creation-status-btn" id="creation-status-btn" style="display: none;">OK</button> 2804 </div> 2805 </div> 2806 2807 2808 2809 2810
2810<script> 2811 // ============================================ 2812 // LOCALIZATION SERVICE (with local cache) 2813 // ============================================ 2814 2815 // Note: BUILT_IN_LOCALIZATIONS is now loaded from external file localizations.js 2816 // This keeps index.html clean and makes localization updates easier 2817 2818 class LocalizationService { 2819 constructor(config = {}) { 2820 this.defaultLanguage = 'en'; 2821 2822 this.localizations = {}; 2823 this.currentLanguage = 'en'; 2824 this.userId = null; 2825 this.isInitialized = false; 2826 2827 // Storage key for user language preference 2828 this.storageKey = 'user_language'; 2829 } 2830 2831 // Initialize the service 2832 async initialize(userId = null) { 2833 this.userId = userId; 2834 2835 // Step 1: Use built-in localizations immediately (instant load!) 2836 this.localizations = BUILT_IN_LOCALIZATIONS; 2837 this.isInitialized = true; 2838 console.log('[LocalizationService] â¡ Instant load from built-in cache'); 2839 2840 // Step 2: Always use 'en' language 2841 this.currentLanguage = 'en'; 2842 2843 console.log('[LocalizationService] â Initialized with language:', this.currentLanguage); 2844 2845 // Note: Localizations are now updated only at build time via update-localizations.js 2846 // No runtime fetching from Google Sheets for better performance and offline support 2847 } 2848 2849 // Get translation by key 2850 get(key, userId = null) { 2851 if (!key) return key; 2852 2853 const normalizedKey = key.toLowerCase(); 2854 const lang = this.currentLanguage; 2855 2856 // Try current language 2857 if (this.localizations[lang] && this.localizations[lang][normalizedKey] !== undefined) { 2858 return this.localizations[lang][normalizedKey].replace(/\\n
2858/g, '\n'); 2859 } 2860 2861 // Fallback to default language 2862 if (this.localizations[this.defaultLanguage] && 2863 this.localizations[this.defaultLanguage][normalizedKey] !== undefined) { 2864 console.warn(`[LocalizationService] Key "${key}" not found for "${lang}", using default language`); 2865 return this.localizations[this.defaultLanguage][normalizedKey].replace(/\\n/g, '\n'); 2866 } 2867 2868 // Return key in brackets if not found 2869 console.warn(`[LocalizationService] Translation not found for key: ${key}`); 2870 return `[${key}]`; 2871 } 2872 2873 // Set language for current user 2874 setLanguage(languageCode) { 2875 const lang = languageCode.toLowerCase(); 2876 2877 if (!this.localizations[lang]) { 2878 console.warn(`[LocalizationService] Language "${lang}" not available`); 2879 return false; 2880 } 2881 2882 this.currentLanguage = lang; 2883 this.saveLanguageToStorage(lang); 2884 2885 console.log('[LocalizationService] Language changed to:', lang); 2886 2887 return true; 2888 } 2889 2890 // Get current language 2891 getLanguage() { 2892 return this.currentLanguage; 2893 } 2894 2895 // Get available languages 2896 getAvailableLanguages() { 2897 return Object.keys(this.localizations); 2898 } 2899 2900 // Get language from localStorage 2901 getStoredLanguage() { 2902 try { 2903 return localStorage.getItem(this.storageKey); 2904 } catch (e) { 2905 return null; 2906 } 2907 } 2908 2909 // Save language to localStorage 2910 saveLanguageToStorage(lang) { 2911 try { 2912 localStorage.setItem(this.storageKey, lang); 2913 } catch (e) { 2914 console.warn('[LocalizationService] Failed to save language to storage:', e); 2915 } 2916 } 2917 } 2918 2919 // Global localization instance 2920 let localizationService = null; 2921 2922 // Helper function for getting translations 2923 function t(key) { 2924 if (!localizationService || !localizationService.isInitialized) { 2925 return `[${key}]`; 2926 } 2927 return localizationService.get(key); 2928 } 2929 2930 // ============================================ 2931 // MAIN APPLICATION 2932 // ============================================ 2933 2934 document.addEventListener('DOMContentLoaded', function() { 2935 const tg = window.Telegram.WebApp; 2936 tg.ready(); 2937 tg.expand(); 2938 2939 // ФоÑÑиÑÑем ÑÑмнÑÑ ÑÐµÐ¼Ñ ÑеÑез theme_params, еÑли Telegram не пеÑедал Ð¸Ñ Ñвно 2940 const darkThemeParams = { 2941 bg_color: '#000000', 2942 secondary_bg_color: '#000000', 2943 header_bg_color: '#000000', 2944 text_color: '#000000', 2945 hint_color: '#000000', 2946 link_color: '#000000', 2947 button_color: '#000000', 2948 button_text_color: '#000000', 2949 }; 2950 const themeParams = tg.themeParams || {}; 2951 // ÐÑли Telegram не пеÑедал bg_color или он не ÑÑмнÑй, пÑименÑем Ñвои знаÑÐµÐ½Ð¸Ñ 2952 function applyThemeVars(params) { 2953 const root = document.documentElement;
2954 root.style.setProperty('--tg-theme-bg-color', params.bg_color || '#000'); 2955 root.style.setProperty('--tg-theme-card-bg', params.secondary_bg_color || '#121212'); 2956 root.style.setProperty('--tg-theme-text-color', params.text_color || '#000000'); 2957 root.style.setProperty('--tg-theme-hint-color', params.hint_color || '#000000'); 2958 root.style.setProperty('--tg-theme-link-color', params.link_color || '#000000'); 2959 root.style.setProperty('--tg-theme-button-color', params.button_color || '#000000'); 2960 root.style.setProperty('--tg-theme-button-text-color', params.button_text_color || '#000000'); 2961 } 2962 // ÐÑовеÑÑем, еÑÑÑ Ð»Ð¸ bg_color и он ли ÑÑмнÑй, инаÑе ÑоÑÑиÑÑем 2963 if (!themeParams.bg_color || themeParams.bg_color.toLowerCase() !== '#000000') { 2964 applyThemeVars(darkThemeParams); 2965 } else { 2966 applyThemeVars(themeParams); 2967 } 2968 2969 // --- MOCK DATA (заглÑÑки, Ñ.к. Ð½ÐµÑ Ð±Ñкенда) --- 2970 const user = { 2971 id: tg.initDataUnsafe?.user?.id || '000000', 2972 name: tg.initDataUnsafe?.user?.first_name || '', // Will be set after localization init 2973 lollipops: 0, // HardCurrency 2974 energy: 0, // SoftCurrency 2975 purchasedItems: [], 2976 gifts: 0 2977 }; 2978 2979 // ============================================ 2980 // INITIALIZE LOCALIZATION SERVICE 2981 // ============================================ 2982 2983 // Determine user's language from Telegram (fallback) 2984 const telegramLanguage = tg.initDataUnsafe?.user?.language_code || 'en'; 2985 2986 // Initialize localization service 2987 // Localizations are loaded from localizations.js (updated at build time) 2988 localizationService = new LocalizationService({ 2989 defaultLanguage: telegramLanguage === 'en' ? 'en' : 'ru' 2990 }); 2991 2992 // Function to fetch user language from API 2993 async function fetchUserLanguage() { 2994 try { 2995 const response = await fetch(`${API_BASE}/getlang?user=${user.id}`); 2996 if (!response.ok) { 2997 console.warn('[Language] Failed to fetch language from API, using fallback'); 2998 return null; 2999 } 3000 // API returns JSON-serialized string 3001 const languageCode = await response.json(); 3002 // Handle null or empty values 3003 if (!languageCode || typeof languageCode !== 'string' || languageCode.trim() === '') { 3004 console.log('[Language] No language found in API response'); 3005 return null; 3006 } 3007 console.log('[Language] Fetched language from API:', languageCode); 3008 return languageCode.trim().toLowerCase(); 3009 } catch (error) { 3010 console.warn('[Language] Error fetching language from API:', error); 3011 return null; 3012 } 3013 } 3014 3015 // Function to update modal window texts after language change 3016 function updateModalTexts() { 3017 // Update Daily Bonus modal 3018 const dailyBonusModal = document.getElementById('daily-bonus-modal'); 3019 if (dailyBonusModal) { 3020 const title = dailyBonusModal.querySelector('.title'); 3021 const claimInfo = dailyBonusModal.querySelector('.claim-info'); 3022 const claimBtn = dailyBonusModal.querySelector('#daily-claim-btn'); 3023 const upgradeBtn = dailyBonusModal.querySelector('#daily-upgrade-btn'); 3024 const closeX = dailyBonusModal.querySelector('.close-x'); 3025 3026 if (title) title.textContent = t('modal_dailyBonus_title'); 3027 if (claimInfo) claimInfo.textContent = t('modal_dailyBonus_info'); 3028 if (claimBtn) claimBtn.innerHTML = `<span>ð</span> ${t('button_claim')}`; 3029 if (upgradeBtn) upgradeBtn.innerHTML = `<span>â¡</span> ${t('button_upgrade')}`; 3030 if (closeX) closeX.setAttribute('aria-label', t('button_close')); 3031 } 3032 3033 // Update Character Creation modal 3034 const createModal = document.getElementById('create-modal'); 3035 if (createModal) { 3036 const title = createModal.querySelector('.title'); 3037 const benefits = createModal.querySelector('.benefits'); 3038 const closeBtn = createModal.querySelector('.close'); 3039 3040 if (title) title.textContent = t('modal_create_title'); 3041 if (benefits) benefits.textContent = t('girl_creation_modal_window'); 3042 if (closeBtn) closeBtn.textContent = t('button_close'); 3043 } 3044 3045 // Update Creation Status modal 3046 const creationStatusTitle = document.querySelector('.creation-status-title'); 3047 const creationStatusBtn = document.getElementById('creation-status-btn'); 3048 3049 if (creationStatusTitle) { 3050 creationStatusTitle.textContent = t('modal_create_title'); 3051 } 3052 if (creationStatusBtn) { 3053 creationStatusBtn.textContent = t('button_ok'); 3054 } 3055 } 3056 3057 // Initialize localization (async) 3058 (async function() { 3059 try { 3060 await localizationService.initialize(user.id); 3061 3062 // Fetch language from API first (at every app start) 3063 const apiLanguage = await fetchUserLanguage(); 3064 3065 // Determine which language to use 3066 let languageToUse = null; 3067 3068 if (apiLanguage) { 3069 // Use language from API if available 3070 const availableLangs = localizationService.getAvailableLanguages(); 3071 if (availableLangs.includes(apiLanguage)) { 3072 languageToUse = apiLanguage; 3073 } else { 3074 console.warn(`[Language] Language "${apiLanguage}" from API is not available, using fallback`); 3075 } 3076 } 3077 3078 // Fallback to stored language or Telegram language 3079 if (!languageToUse) { 3080 const storedLang = localizationService.getStoredLanguage(); 3081 if (storedLang) { 3082 languageToUse = storedLang; 3083 } else { 3084 // First time user - use Telegram language 3085 const availableLangs = localizationService.getAvailableLanguages(); 3086 if (availableLangs.includes(telegramLanguage)) { 3087 languageToUse = telegramLanguage; 3088 } 3089 } 3090 } 3091 3092 // Set the determined language 3093 if (languageToUse) { 3094 localizationService.setLanguage(languageToUse); 3095 } 3096 3097 // Set default user name if not set 3098 if (!user.name) { 3099 user.name = t('user_default_name'); 3100 } 3101 3102 // Initialize UI elements with localized text 3103 const upgradeButtonText = document.querySelector('.upgrade-button-text'); 3104 if (upgradeButtonText) { 3105 upgradeButtonText.textContent = t('button_upgrade_main'); 3106 } 3107 3108 const creationStatusBtn = document.getElementById('creation-status-btn'); 3109 if (creationStatusBtn) { 3110 creationStatusBtn.textContent = t('button_ok'); 3111 } 3112 3113 // Initialize creation summary cards with localized "Loading..." text 3114 const summaryCardTitles = document.querySelectorAll('.summary-card-title');
3115 summaryCardTitles.forEach(title => { 3116 if (title.textContent === 'Loading...') { 3117 title.textContent = t('creation_loading'); 3118 } 3119 }); 3120 3121 console.log('[App] Localization initialized successfully'); 3122 3123 // Update modal texts after language is set (with delays to ensure modals are created) 3124 setTimeout(() => { 3125 if (typeof updateModalTexts === 'function') { 3126 updateModalTexts(); 3127 } 3128 }, 100); 3129 // Also update after a longer delay to catch any modals created later 3130 setTimeout(() => { 3131 if (typeof updateModalTexts === 'function') { 3132 updateModalTexts(); 3133 } 3134 }, 500); 3135 } catch (error) { 3136 console.error('[App] Failed to initialize localization:', error); 3137 // Continue with fallback keys 3138 } 3139 3140 // Initialize store and render UI (always, regardless of localization success) 3141 await initializeStore(); 3142 switchPage(currentPage); 3143 })(); 3144 3145 // ÐевÑÑки ÑепеÑÑ Ð·Ð°Ð³ÑÑжаÑÑÑÑ Ð¸Ð· API 3146 let girlsData = []; 3147 let girlsPhotos = new Map(); // ÐÑÑ Ð´Ð»Ñ ÑоÑогÑаÑий девÑÑек 3148 let photosLoaded = false; // Флаг Ð´Ð»Ñ Ð¾ÑÑÐ»ÐµÐ¶Ð¸Ð²Ð°Ð½Ð¸Ñ Ð·Ð°Ð³ÑÑзки ÑоÑогÑаÑий 3149 let girlsDataLoaded = false; // Флаг Ð´Ð»Ñ Ð¾ÑÑÐ»ÐµÐ¶Ð¸Ð²Ð°Ð½Ð¸Ñ Ð·Ð°Ð³ÑÑзки даннÑÑ Ð´ÐµÐ²ÑÑек 3150 let girlsPhotosLoaded = false; // Флаг Ð´Ð»Ñ Ð¾ÑÑÐ»ÐµÐ¶Ð¸Ð²Ð°Ð½Ð¸Ñ Ð·Ð°Ð³ÑÑзки ÑоÑогÑаÑий девÑÑек 3151 let girlsShopRendered = false; // Флаг Ð´Ð»Ñ Ð¾ÑÑÐ»ÐµÐ¶Ð¸Ð²Ð°Ð½Ð¸Ñ ÑендеÑинга магазина девÑÑек 3152 3153 3154 3155 // ÐаннÑе Ð´Ð»Ñ Ð¿Ð°ÐºÐ¾Ð² ÑепеÑÑ Ð±ÑдÑÑ Ð·Ð°Ð³ÑÑжаÑÑÑÑ 3156 let lollipopPacks = []; 3157 3158 // --- DOM Elements --- 3159 const userInfoEl = document.getElementById('page-title'); 3160 const userBalanceEl = document.getElementById('user-balance'); 3161 const navButtons = { 3162 girls: document.getElementById('nav-girls'), 3163 lollipops: document.getElementById('nav-lollipops') 3164 }; 3165 const pages = { 3166 girls: document.getElementById('page-girls'), 3167 lollipops: document.getElementById('page-lollipops'), 3168 'character-creation': document.getElementById('page-character-creation'), 3169 'character-creation-step2': document.getElementById('page-character-creation-step2'), 3170 'character-creation-step3': document.getElementById('page-character-creation-step3'), 3171 'character-creation-step4': document.getElementById('page-character-creation-step4'), 3172 'character-creation-step5': document.getElementById('page-character-creation-step5'), 3173 'character-creation-step6': document.getElementById('page-character-creation-step6'), 3174 'character-creation-summary': document.getElementById('page-character-creation-summary') 3175 }; 3176 const shops = { 3177 girls: document.getElementById('girls-shop'), 3178 lollipops: document.getElementById('lollipops-shop') 3179 }; 3180 3181 // --- State --- 3182 let currentStyle = 'real'; // ÐÑегда иÑполÑзÑем ÑÑÐ¸Ð»Ñ 'real' 3183 let selectedPackId = null; // ID вÑбÑанного пака 3184 let lastSelectedPackId = null; // ID поÑледней вÑбÑанной каÑÑоÑки Ð´Ð»Ñ Ð²Ð¾ÑÑÑÐ°Ð½Ð¾Ð²Ð»ÐµÐ½Ð¸Ñ 3185 let isPaymentInProgress = false; // ÐаÑиÑа Ð¾Ñ Ð´Ð²Ð¾Ð¹Ð½Ð¾Ð³Ð¾ оÑкÑÑÑÐ¸Ñ Ð¸Ð½Ð²Ð¾Ð¹Ñа 3186 let selectedPaymentMethod = 'card'; // ÐÑбÑаннÑй меÑод оплаÑÑ: 'stars' или 'card' 3187 3188 // СÑÑлки на Tribute Ð´Ð»Ñ Ð¾Ð¿Ð»Ð°ÑÑ ÐºÐ°ÑÑой по подпиÑкам (по ÑзÑкам инÑеÑÑейÑа) 3189 const CARD_PAYMENT_LINKS_CONFIG = { 3190 default: { 3191 bronze: 'https://t.me/tribute/app?startapp=sCus', 3192 silver: 'https://t.me/tribute/app?startapp=sCuq', 3193 gold: 'https://t.me/tribute/app?startapp=sCeP', 3194 platinum: 'https://t.me/tribute/app?startapp=sEGR' 3195 }, 3196 ru: { 3197 bronze: 'https://t.me/tribute/app?startapp=sFvs', 3198 silver: 'https://t.me/tribute/app?startapp=sFvr', 3199 gold: 'https://t.me/tribute/app?startapp=sFvq', 3200 platinum: 'https://t.me/tribute/app?startapp=sFvx' 3201 } 3202 }; 3203 3204 function getActiveLanguageCode() { 3205 // Always return 'en' 3206 return 'en'; 3207 } 3208 3209 function getCardPaymentLinksForLanguage() { 3210 const langCode = getActiveLanguageCode(); 3211 if (CARD_PAYMENT_LINKS_CONFIG[langCode]) { 3212 return CARD_PAYMENT_LINKS_CONFIG[langCode]; 3213 } 3214 return CARD_PAYMENT_LINKS_CONFIG.default; 3215 } 3216 3217 // СÑÐ¸Ð»Ñ Ð²Ñегда 'real', ÑÑнкÑÐ¸Ñ Ð¿Ð¾Ð»ÑÑÐµÐ½Ð¸Ñ ÑÑÐ¸Ð»Ñ Ñ ÑеÑвеÑа Ñдалена 3218 3219 function getInitialPage() {
3220 const urlParams = new URLSearchParams(window.location.search); 3221 const page = urlParams.get('page'); 3222 if (page && ['girls', 'lollipops'].includes(page)) { 3223 return page; 3224 } 3225 return 'girls'; 3226 } 3227 3228 function getHighlightId() { 3229 const urlParams = new URLSearchParams(window.location.search); 3230 return urlParams.get('highlight'); 3231 } 3232 3233 function highlightCard(id, type) { 3234 console.log('highlightCard called:', { id, type, highlightId, highlightShown }); 3235 if (!highlightId || highlightId !== id || highlightShown) { 3236 console.log('highlightCard: early return - no highlightId, id mismatch, or already shown'); 3237 return; 3238 } 3239 3240 // ÐÐ°Ñ Ð¾Ð´Ð¸Ð¼ каÑÑоÑÐºÑ Ð¿Ð¾ id 3241 let card = null; 3242 if (type === 'pack') { 3243 // ÐÐ»Ñ Ð¿Ð°ÐºÐ¾Ð² иÑем по имени пака 3244 const pack = lollipopPacks.find(p => p.name === id); 3245 console.log('Looking for pack:', id, 'Found pack:', pack); 3246 if (pack) { 3247 card = document.querySelector(`[data-id="${pack.id}"][data-type="pack"]`); 3248 console.log('Looking for pack card:', `[data-id="${pack.id}"][data-type="pack"]`, card); 3249 } 3250 } 3251 3252 if (card) { 3253 console.log('Card found, adding highlight'); 3254 // УÑÑанавливаем Ñлаг, ÑÑо подÑвеÑка бÑла показана 3255 highlightShown = true; 3256 3257 // ÐобавлÑем клаÑÑ Ð¿Ð¾Ð´ÑвеÑки 3258 card.classList.add('highlight'); 3259 3260 // ÐÑокÑÑÑиваем к каÑÑоÑке 3261 card.scrollIntoView({ 3262 behavior: 'smooth', 3263 block: 'center' 3264 }); 3265 3266 // УбиÑаем подÑвеÑÐºÑ ÑеÑез 3 ÑекÑÐ½Ð´Ñ 3267 setTimeout(() => { 3268 card.classList.remove('highlight'); 3269 }, 3000); 3270 } else { 3271 console.log('Card not found'); 3272 } 3273 } 3274 3275 let currentPage = getInitialPage(); 3276 let highlightId = getHighlightId(); 3277 let highlightShown = false; // Флаг Ð´Ð»Ñ Ð¾ÑÑÐ»ÐµÐ¶Ð¸Ð²Ð°Ð½Ð¸Ñ Ð¿Ð¾ÐºÐ°Ð·Ð° подÑвеÑки 3278 3279 // --- Functions --- 3280 3281 // ÐбÑÑÑка Ð´Ð»Ñ fetch 3282 async function debugFetch(url, options) { 3283 try { 3284 const resp = await fetch(url, options); 3285 const text = await resp.clone().text(); 3286 // ÐÑÑаемÑÑ Ð²ÐµÑнÑÑÑ JSON, еÑли возможно 3287 try { return { resp, json: JSON.parse(text) }; } catch { return { resp, text }; } 3288 } catch (e) { 3289 throw e; 3290 } 3291 } 3292 3293 // API base URL 3294 const API_BASE = 'https://api.candyaitunnel.xyz'; 3295 3296 // ÐолÑÑение баланÑа валÑÑÑ 3297 async function fetchCurrency() { 3298 try { 3299 const { resp, json: data } = await debugFetch(`${API_BASE}/currency?user=${user.id}`); 3300 if (!resp.ok) throw new Error(); 3301 user.lollipops = data.HardCurrency || 0; 3302 user.energy = data.SoftCurrency || 0; 3303 user.gifts = data.Gifts || 0; 3304 } catch { 3305 user.lollipops = 0; 3306 user.energy = 0; 3307 user.gifts = 0; 3308 } 3309 } 3310 3311 // ФÑнкÑÐ¸Ñ Ð´Ð»Ñ Ð¿Ð¾ÐºÐ°Ð·Ð° загÑÑзоÑного ÑкÑана 3312 function showLoadingScreen() { 3313 const loadingScreen = document.getElementById('loading-screen'); 3314 if (loadingScreen) { 3315 loadingScreen.classList.remove('hidden'); 3316 } 3317 } 3318 3319 // ФÑнкÑÐ¸Ñ Ð´Ð»Ñ ÑкÑÑÑÐ¸Ñ Ð·Ð°Ð³ÑÑзоÑного ÑкÑана 3320 function hideLoadingScreen() { 3321 const loadingScreen = document.getElementById('loading-screen'); 3322 if (loadingScreen) { 3323 loadingScreen.classList.add('hidden'); 3324 setTimeout(() => { 3325 loadingScreen.remove(); 3326 }, 500); 3327 } 3328 } 3329 3330 // ФÑнкÑÐ¸Ñ Ð´Ð»Ñ Ð¿ÑинÑдиÑелÑного Ð¾Ð±Ð½Ð¾Ð²Ð»ÐµÐ½Ð¸Ñ Ð´Ð°Ð½Ð½ÑÑ Ð´ÐµÐ²ÑÑек (напÑимеÑ, поÑле покÑпки) 3331 async function refreshGirlsData() { 3332 try { 3333 const { resp, json } = await debugFetch(`${API_BASE}/girls?user=${user.id}`); 3334 if (resp.ok) { 3335 girlsData = json; 3336 girlsDataLoaded = true; // УÑÑанавливаем Ñлаг 3337 girlsShopRendered = false; // СбÑаÑÑваем Ñлаг ÑендеÑинга ÑÑÐ¾Ð±Ñ Ð¿ÐµÑеÑоздаÑÑ ÐºÐ°ÑÑоÑки 3338 console.log('Refreshed girls data:', girlsData); 3339 return true; 3340 } 3341 } catch (error) { 3342 console.error('Failed to refresh girls data:', error); 3343 } 3344 return false;
3345 } 3346 3347 // ФÑнкÑÐ¸Ñ Ð´Ð»Ñ Ð·Ð°Ð³ÑÑзки ÑоÑогÑаÑий девÑÑек 3348 async function loadGirlsPhotos() { 3349 // Ðе загÑÑжаем ÑоÑогÑаÑии повÑоÑно еÑли они Ñже загÑÑÐ¶ÐµÐ½Ñ 3350 if (girlsPhotosLoaded) { 3351 console.log('Girls photos already loaded, skipping...'); 3352 return; 3353 } 3354 3355 try { 3356 const { resp, json } = await debugFetch(`${API_BASE}/photos?user=${user.id}`); 3357 if (resp.ok && Array.isArray(json)) { 3358 // ÐÑиÑаем кÑÑ Ð¸ загÑÑжаем новÑе ÑоÑогÑаÑии 3359 girlsPhotos.clear(); 3360 json.forEach(photoData => { 3361 if (photoData.GirlId !== undefined && photoData.Photo) { 3362 // ÐÑполÑзÑем GirlId как клÑÑ Ð´Ð»Ñ ÐºÑÑа 3363 girlsPhotos.set(photoData.GirlId, photoData.Photo); 3364 } 3365 }); 3366 girlsPhotosLoaded = true; // УÑÑанавливаем Ñлаг поÑле ÑÑпеÑной загÑÑзки 3367 console.log('Loaded photos for girls:', girlsPhotos.size); 3368 3369 // ÐбновлÑем оÑобÑажение каÑÑоÑек Ñ ÑоÑогÑаÑиÑми 3370 updateGirlCardsWithPhotos(); 3371 } 3372 } catch (error) { 3373 console.error('Failed to load girls photos:', error); 3374 } 3375 } 3376 3377 // ФÑнкÑÐ¸Ñ Ð´Ð»Ñ Ð¿ÑинÑдиÑелÑного Ð¾Ð±Ð½Ð¾Ð²Ð»ÐµÐ½Ð¸Ñ ÑоÑогÑаÑий девÑÑек 3378 async function refreshGirlsPhotos() { 3379 girlsPhotosLoaded = false; // СбÑаÑÑваем Ñлаг 3380 girlsPhotos.clear(); // ÐÑиÑаем кÑÑ 3381 await loadGirlsPhotos(); // ÐагÑÑжаем заново 3382 } 3383 3384 // ФÑнкÑÐ¸Ñ Ð´Ð»Ñ Ð¾Ð±Ð½Ð¾Ð²Ð»ÐµÐ½Ð¸Ñ ÐºÐ°ÑÑоÑек девÑÑек Ñ ÑоÑогÑаÑиÑми 3385 function updateGirlCardsWithPhotos() { 3386 const girlCards = document.querySelectorAll('.girl-card'); 3387 girlCards.forEach(card => { 3388 const girlName = card.dataset.id; // dataset.id ÑодеÑÐ¶Ð¸Ñ GirlName 3389 const loadingContainer = card.querySelector('.photo-loading-container'); 3390 3391 // ÐÐ°Ñ Ð¾Ð´Ð¸Ð¼ девÑÑÐºÑ Ð² даннÑÑ Ð¿Ð¾ имени ÑÑÐ¾Ð±Ñ Ð¿Ð¾Ð»ÑÑиÑÑ GirlId 3392 const girl = girlsData.find(g => g.GirlName === girlName); 3393 if (!girl) return; 3394 3395 if (loadingContainer && girlsPhotos.has(girl.GirlId)) { 3396 const photo = girlsPhotos.get(girl.GirlId); 3397 3398 // Создаем ÑÐ»ÐµÐ¼ÐµÐ½Ñ Ð¸Ð·Ð¾Ð±ÑÐ°Ð¶ÐµÐ½Ð¸Ñ 3399 const img = document.createElement('img'); 3400 img.className = 'item-card-image'; 3401 img.src = `data:image/jpeg;base64,${photo}`; 3402 img.alt = girlName; 3403 img.onerror = function() { 3404 this.onerror = null; 3405 this.src = `https://placehold.co/300x220/9C27B0/FFFFFF?text=${encodeURIComponent(girlName)}`; 3406 }; 3407 3408 // ÐаменÑем конÑÐµÐ¹Ð½ÐµÑ Ð·Ð°Ð³ÑÑзки на изобÑажение 3409 loadingContainer.parentNode.replaceChild(img, loadingContainer); 3410 } else if (loadingContainer && !girlsPhotos.has(girl.GirlId)) { 3411 // ÐÑли ÑоÑогÑаÑÐ¸Ñ Ð½Ðµ найдена, показÑваем placeholder 3412 const img = document.createElement('img'); 3413 img.className = 'item-card-image'; 3414 img.src = `https://placehold.co/300x220/9C27B0/FFFFFF?text=${encodeURIComponent(girlName)}`; 3415 img.alt = girlName; 3416 3417 // ÐаменÑем конÑÐµÐ¹Ð½ÐµÑ Ð·Ð°Ð³ÑÑзки на placeholder 3418 loadingContainer.parentNode.replaceChild(img, loadingContainer); 3419 } 3420 }); 3421 3422 // ÐбновлÑем модалÑное окно еÑли оно оÑкÑÑÑо 3423 updateModalPhotoIfOpen(); 3424 } 3425 3426 // ФÑнкÑÐ¸Ñ Ð´Ð»Ñ Ð¾Ð±Ð½Ð¾Ð²Ð»ÐµÐ½Ð¸Ñ ÑоÑогÑаÑии в модалÑном окне еÑли оно оÑкÑÑÑо 3427 function updateModalPhotoIfOpen() { 3428 const girlModal = document.getElementById('girl-modal'); 3429 if (!girlModal || girlModal.style.display === 'none') return; 3430 3431 const girlModalPhotoLoading = girlModal.querySelector('.girl-modal-photo-loading'); 3432 const girlModalPhoto = girlModal.querySelector('.girl-modal-photo'); 3433 3434 if (!girlModalPhotoLoading || !girlModalPhoto) return; 3435 3436 // ÐолÑÑаем Ð¸Ð¼Ñ Ð´ÐµÐ²ÑÑки из модалÑного окна 3437 const girlModalName = girlModal.querySelector('.girl-modal-name'); 3438 if (!girlModalName) return; 3439 3440 const girlName = girlModalName.textContent; 3441 const girl = girlsData.find(g => g.GirlName === girlName); 3442 if (!girl) return; 3443 3444 // ÐÐ»Ñ Ð»Ð¾ÐºÐ°Ð»ÑнÑÑ Ð´ÐµÐ²ÑÑек иÑполÑзÑем локалÑнÑй Ñайл 3445 if (Number(girl.GirlId) < 1000) { 3446 girlModalPhoto.src = `Photos/${encodeURIComponent(girl.GirlName)}.jpg`; 3447 girlModalPhoto.style.display = 'block'; 3448 girlModalPhotoLoading.style.display = 'none'; 3449 }
3449 else if (girlsPhotos.has(girl.GirlId)) { 3450 // ÐÑли ÑоÑогÑаÑÐ¸Ñ Ð·Ð°Ð³ÑÑзилаÑÑ, показÑваем ÐµÑ 3451 const photo = girlsPhotos.get(girl.GirlId); 3452 girlModalPhoto.src = `data:image/jpeg;base64,${photo}`; 3453 girlModalPhoto.style.display = 'block'; 3454 girlModalPhotoLoading.style.display = 'none'; 3455 } 3456 } 3457 3458 // ÐниÑиализаÑÐ¸Ñ Ð»ÐµÐ½Ð¸Ð²Ð¾Ð¹ загÑÑзки локалÑнÑÑ ÑоÑо девÑÑек в ÑпиÑке 3459 function initLocalGirlLazyLoading() { 3460 try { 3461 const images = document.querySelectorAll('img.lazy-local[data-src]'); 3462 if (!images || images.length === 0) return; 3463 // ФолбÑк: еÑли IntersectionObserver недоÑÑÑпен â ÑÑÐ°Ð·Ñ Ð²ÑÑÑавлÑем src 3464 if (!('IntersectionObserver' in window)) { 3465 images.forEach(img => { 3466 if (!img.getAttribute('src')) { 3467 img.setAttribute('src', img.getAttribute('data-src')); 3468 img.removeAttribute('data-src'); 3469 } 3470 }); 3471 return; 3472 } 3473 const observer = new IntersectionObserver((entries, obs) => { 3474 entries.forEach(entry => { 3475 if (entry.isIntersecting) { 3476 const img = entry.target; 3477 const ds = img.getAttribute('data-src'); 3478 if (ds && !img.getAttribute('src')) { 3479 img.setAttribute('src', ds); 3480 img.removeAttribute('data-src'); 3481 } 3482 obs.unobserve(img); 3483 } 3484 }); 3485 }, { rootMargin: '200px 0px', threshold: 0.01 }); 3486 images.forEach(img => observer.observe(img)); 3487 } catch {} 3488 } 3489 3490 // ÐÐ»Ð°Ð²Ð½Ð°Ñ Ð°ÑÐ¸Ð½Ñ ÑÐ¾Ð½Ð½Ð°Ñ ÑÑнкÑÐ¸Ñ Ð´Ð»Ñ Ð¸Ð½Ð¸ÑиализаÑии магазина 3491 async function initializeStore() { 3492 // ÐоказÑваем загÑÑзоÑнÑй ÑкÑан 3493 showLoadingScreen(); 3494 3495 await fetchCurrency(); 3496 // ÐагÑÑжаем девÑÑек ÑолÑко еÑли они еÑе не загÑÑÐ¶ÐµÐ½Ñ 3497 if (!girlsDataLoaded) { 3498 console.log('Loading girls data from API...'); 3499 try { 3500 const { resp, json } = await debugFetch(`${API_BASE}/girls?user=${user.id}`); 3501 if (!resp.ok) throw new Error(`HTTP error! status: ${resp.status}`); 3502 girlsData = json; 3503 girlsDataLoaded = true; // УÑÑанавливаем Ñлаг поÑле ÑÑпеÑной загÑÑзки 3504 3505 // Debug logging to check girls data 3506 console.log('Loaded girls data:', girlsData); 3507 girlsData.forEach((girl, index) => { 3508 console.log(`Girl ${index}:`, girl.GirlName, 'Photo:', girl.Photo ? girl.Photo.substring(0, 50) + '...' : 'No photo'); 3509 }); 3510 } catch (error) { 3511 girlsData = []; 3512 girlsDataLoaded = false; // СбÑаÑÑваем Ñлаг пÑи оÑибке 3513 tg.showAlert(t('error_loadCharacters')); 3514 } 3515 } else { 3516 console.log('Using cached girls data, skipping API call'); 3517 } 3518 3519 // СкÑÑваем загÑÑзоÑнÑй ÑкÑан поÑле загÑÑзки девÑÑек 3520 hideLoadingScreen(); 3521 3522 // ÐагÑÑжаем ÑоÑогÑаÑии девÑÑек в Ñоновом Ñежиме ÑолÑко еÑли даннÑе девÑÑек бÑли загÑÑÐ¶ÐµÐ½Ñ Ð¸ ÑоÑогÑаÑии еÑе не загÑÑÐ¶ÐµÐ½Ñ 3523 if (girlsDataLoaded && !girlsPhotosLoaded) { 3524 loadGirlsPhotos(); 3525 } 3526 3527 // ÐагÑÑжаем пÑодÑкÑÑ (паки валÑÑ) 3528 try { 3529 const { resp, json: apiProducts } = await debugFetch(`${API_BASE}/products?user=${user.id}`); 3530 if (!resp.ok) { 3531 throw new Error(`HTTP error! status: ${resp.status}`); 3532 } 3533 lollipopPacks = apiProducts 3534 .filter(product => { 3535 // Accept bronze/silver/gold packs (platinum ÑкÑÑваем) 3536 const name = String(product.Name || '').toLowerCase(); 3537 return name.includes('bronze') || name.includes('silver') || name.includes('gold'); 3538 }) 3539 .map((product, index) => { 3540 const name = String(product.Name || ''); 3541 return { 3542 id: 201 + index, 3543 name: name, 3544 stars: product.Price, 3545 lollipops: 0, 3546 icon: 'â', 3547 image: `https://placehold.co/150x120/FFC107/FFFFFF?text=Unlimited%20Messages&font-size=16&font-weight=bold` 3548 }; 3549 }); 3550 } catch (error) { 3551 lollipopPacks = []; 3552 tg.showAlert(t('error_loadProducts')); 3553 } 3554 updateUI(); 3555 } 3556 3557 function updateUI() { 3558 // Ðазвание вкладки 3559 let title = ''; 3560 if (currentPage === 'girls') title = t('page_title_characters'); 3561 else if (currentPage === 'lollipops') title = t('page_title_subscriptions'); 3562 userInfoEl.textContent = title; 3563 3564 // ÐоказÑваем пеÑеклÑÑаÑÐµÐ»Ñ Ð¾Ð¿Ð»Ð°ÑÑ ÑолÑко на ÑÑÑаниÑе подпиÑок 3565 const paymentSwitcher = document.getElementById('payment-switcher'); 3566 if (paymentSwitcher) { 3567 paymentSwitcher.style.display = currentPage === 'lollipops' ? 'flex' : 'none'; 3568 } 3569 3570 // Ðа ÑÑÑаниÑе подпиÑок ÑкÑÑваем подаÑок и Ð¿Ð°Ð½ÐµÐ»Ñ Ð²Ð°Ð»ÑÑ 3571 if (currentPage === 'lollipops') { 3572 userBalanceEl.innerHTML = ''; 3573 } else { 3574 userBalanceEl.innerHTML = ` 3575 <button id="daily-bonus-btn" class="daily-bonus-btn" title="Daily Bonus"><span class="gift-emoji">ð</span></button> 3576 <div class="currency-panel"> 3577 <div class="currency-item">
3578 <span class="currency-icon">ð¬</span> 3579 <span class="currency-value">${user.energy}</span> 3580 </div> 3581 <div class="currency-item"> 3582 <span class="currency-icon">ð</span> 3583 <span class="currency-value">${user.gifts}</span> 3584 </div> 3585 <div class="currency-item"> 3586 <span class="currency-icon">ð</span> 3587 <span class="currency-value">${user.lollipops}</span> 3588 </div> 3589 <button class="add-currency-btn">+</button> 3590 </div> 3591 `; 3592 } 3593 3594 // ÐобавлÑем пеÑеклÑÑаÑÐµÐ»Ñ Ð¾Ð¿Ð»Ð°ÑÑ Ð¾ÑделÑно в header 3595 const header = document.querySelector('header'); 3596 let existingSwitcher = document.getElementById('payment-switcher'); 3597 if (existingSwitcher) { 3598 existingSwitcher.remove(); 3599 } 3600 3601 if (currentPage === 'lollipops') { 3602 const paymentSwitcher = document.createElement('div'); 3603 paymentSwitcher.className = 'payment-switcher'; 3604 paymentSwitcher.id = 'payment-switcher'; 3605 paymentSwitcher.innerHTML = ` 3606 <button class="payment-switcher-btn ${selectedPaymentMethod === 'stars' ? 'active' : ''}" data-method="stars">${t('payment_stars')}</button> 3607 <button class="payment-switcher-btn ${selectedPaymentMethod === 'card' ? 'active' : ''}" data-method="card">${t('payment_card')}</button> 3608 `; 3609 header.insertBefore(paymentSwitcher, userBalanceEl); 3610 3611 // ÐобавлÑем обÑабоÑÑики Ð´Ð»Ñ Ð¿ÐµÑеклÑÑаÑÐµÐ»Ñ Ð¾Ð¿Ð»Ð°ÑÑ 3612 const paymentSwitcherBtns = paymentSwitcher.querySelectorAll('.payment-switcher-btn'); 3613 paymentSwitcherBtns.forEach(btn => { 3614 btn.addEventListener('click', () => { 3615 const method = btn.dataset.method; 3616 selectedPaymentMethod = method; 3617 3618 // ÐбновлÑем акÑивное ÑоÑÑоÑние кнопок 3619 paymentSwitcherBtns.forEach(b => b.classList.remove('active')); 3620 btn.classList.add('active'); 3621 3622 console.log('[Payment] Method switched to:', method); 3623 3624 // ÐеÑеÑиÑовÑваем ÑÐµÐ½Ñ Ð² каÑÑоÑÐºÐ°Ñ Ð¿Ð¾Ð´Ð¿Ð¸Ñок 3625 if (currentPage === 'lollipops') { 3626 renderLollipopsShop(); 3627 } 3628 }); 3629 }); 3630 } 3631 3632 // ÐобавлÑем обÑабоÑÑик ÑобÑÑÐ¸Ñ Ð´Ð»Ñ ÐºÐ½Ð¾Ð¿ÐºÐ¸ Ñ Ð¿Ð»ÑÑиком (ÑолÑко еÑли не на ÑÑÑаниÑе подпиÑок) 3633 if (currentPage !== 'lollipops') { 3634 const addCurrencyBtn = userBalanceEl.querySelector('.add-currency-btn'); 3635 if (addCurrencyBtn) { 3636 addCurrencyBtn.addEventListener('click', () => switchPage('lollipops')); 3637 } 3638 } 3639 3640 3641 // Re-bind daily bonus button and refresh state so wobble applies even before opening modal 3642 bindDailyBonusBtn(); 3643 refreshDailyBonusState(); 3644 renderGirlsShop(); 3645 renderLollipopsShop(); 3646 // ÐоÑле лÑбого ÑендеÑа пеÑеÑÑиÑÑваем вÑÑоÑÑ Ð¾Ð¿Ð¸ÑÐ°Ð½Ð¸Ñ Ð´ÐµÐ²ÑÑек 3647 if (currentPage === 'girls') { 3648 setTimeout(() => adjustGirlDescriptions && adjustGirlDescriptions(), 0); 3649 setTimeout(() => adjustGirlDescriptions && adjustGirlDescriptions(), 120); 3650 } 3651 } 3652 3653 function switchPage(pageName) { 3654 currentPage = pageName;
3655 Object.keys(pages).forEach(key => { 3656 pages[key].classList.toggle('active', key === pageName); 3657 if (navButtons[key]) { 3658 navButtons[key].classList.toggle('active', key === pageName); 3659 } 3660 }); 3661 3662 // СкÑÑваем/показÑваем навигаÑÐ¸Ñ Ð´Ð»Ñ Ð²ÑÐµÑ ÑÑÑÐ°Ð½Ð¸Ñ ÑÐ¾Ð·Ð´Ð°Ð½Ð¸Ñ Ð¿ÐµÑÑонажа 3663 if (pageName.startsWith('character-creation')) { 3664 document.body.classList.add('character-creation-active'); 3665 } else { 3666 document.body.classList.remove('character-creation-active'); 3667 } 3668 3669 // Hide/show navigation on lollipops page 3670 if (pageName === 'lollipops') { 3671 document.body.classList.add('lollipops-active'); 3672 } else { 3673 document.body.classList.remove('lollipops-active'); 3674 } 3675 3676 // Move back button into header on lollipops page; return it back otherwise 3677 (function relocateBackButton() { 3678 const headerEl = document.querySelector('header'); 3679 const lollipopsPageEl = pages.lollipops; 3680 const backBtnEl = document.getElementById('back-button'); 3681 if (!backBtnEl || !headerEl || !lollipopsPageEl) return; 3682 if (pageName === 'lollipops') { 3683 // Place before payment switcher (first in header) 3684 headerEl.insertBefore(backBtnEl, headerEl.firstChild); 3685 backBtnEl.style.display = 'flex'; 3686 } else { 3687 lollipopsPageEl.insertBefore(backBtnEl, lollipopsPageEl.firstChild); 3688 backBtnEl.style.display = 'none'; 3689 } 3690 })(); 3691 3692 // СкÑÑваем ÐºÐ½Ð¾Ð¿ÐºÑ Upgrade и опиÑание еÑли пеÑеклÑÑаемÑÑ Ð½Ðµ на ÑÑÑаниÑÑ Ð¿Ð¾Ð´Ð¿Ð¸Ñок 3693 if (pageName !== 'lollipops') { 3694 hideUpgradeButton(); 3695 hidePackDescription(); 3696 selectedPackId = null; // СбÑаÑÑваем ÑолÑко ÑекÑÑий вÑбоÑ, но ÑÐ¾Ñ ÑанÑем lastSelectedPackId 3697 const ut = document.getElementById('upgrade-text'); 3698 if (ut) ut.innerHTML = ''; 3699 } 3700 3701 updateUI(); 3702 } 3703 3704 // ФÑнкÑÐ¸Ñ Ð¿ÐµÑеклÑÑÐµÐ½Ð¸Ñ ÑÑÐ¸Ð»Ñ Ñдалена, вÑегда иÑполÑзÑеÑÑÑ 'real' 3705 3706 // ФÑнкÑии Ð´Ð»Ñ ÑабоÑÑ Ñ Ð²ÑбоÑом паков 3707 function togglePackSelection(card, packId) { 3708 console.log('togglePackSelection called:', { card, packId }); 3709 3710 // УбиÑаем вÑделение Ñ Ð´ÑÑÐ³Ð¸Ñ ÐºÐ°ÑÑоÑек 3711 document.querySelectorAll('.pack-card').forEach(c => { 3712 c.classList.remove('selected'); 3713 c.querySelector('.pack-checkbox').classList.remove('checked'); 3714 }); 3715 3716 // ÐÑделÑем ÑекÑÑÑÑ ÐºÐ°ÑÑоÑÐºÑ 3717 card.classList.add('selected'); 3718 card.querySelector('.pack-checkbox').classList.add('checked'); 3719 selectedPackId = packId; 3720 lastSelectedPackId = packId; // Ð¡Ð¾Ñ ÑанÑем вÑбÑаннÑй ID 3721 3722 // ÐоказÑваем опиÑание вÑбÑанного пака 3723 showPackDescription(packId); 3724 3725 // ÐбновлÑем ÑекÑÑ Ð½Ð°Ð´ кнопкой Upgrade 3726 updateUpgradeText(packId); 3727 3728 // ÐоказÑваем ÐºÐ½Ð¾Ð¿ÐºÑ Upgrade 3729 showUpgradeButton(); 3730 } 3731 3732 function showUpgradeButton() { 3733 const upgradeSection = document.getElementById('upgrade-section'); 3734 if (upgradeSection) { 3735 upgradeSection.style.display = 'flex'; 3736 if (selectedPackId) { 3737 updateUpgradeText(selectedPackId); 3738 } 3739 } 3740 } 3741 3742 function hideUpgradeButton() { 3743 const upgradeSection = document.getElementById('upgrade-section'); 3744 if (upgradeSection) { 3745 upgradeSection.style.display = 'none'; 3746 } 3747 } 3748 3749 function updateUpgradeText(packId) { 3750 const pack = lollipopPacks.find(p => String(p.id) === String(packId)); 3751 const upgradeTextEl = document.getElementById('upgrade-text'); 3752 3753 if (pack && upgradeTextEl) { 3754 let html = ''; 3755 const lname = pack.name.toLowerCase(); 3756 if (lname.includes('bronze')) { 3757 html = `<div class="line">ð¬ ${t('subscription_feature_unlimitedMessages')}</div>`; 3758 }
3758 else if (lname.includes('silver')) { 3759 html = `<div class="line">ð¬ ${t('subscription_feature_unlimitedMessages')}</div>` + 3760 '<div class="sep"></div>' + 3761 `<div class="line">ð· ${t('subscription_feature_unlimitedPhotos')}</div>` + 3762 '<div class="sep"></div>' + 3763 `<div class="line">ð§ ${t('subscription_feature_customCharacters')}</div>`; 3764 } else if (lname.includes('gold')) { 3765 html = `<div class="line">ð¬ ${t('subscription_feature_unlimitedMessages')}</div>` + 3766 '<div class="sep"></div>' + 3767 `<div class="line">ð· ${t('subscription_feature_unlimitedPhotos')}</div>` + 3768 '<div class="sep"></div>' + 3769 `<div class="line">ð ${t('subscription_feature_createPhotos')} ð</div>` + 3770 '<div class="sep"></div>' + 3771 `<div class="line">ð§ ${t('subscription_feature_customCharacters')}</div>` + 3772 '<div class="sep"></div>' + 3773 `<div class="line">â¡ ${t('subscription_feature_fasterResponse')}</div>` + 3774 '<div class="sep"></div>' + 3775 `<div class="line">ð¬ ${t('subscription_feature_customScenarios')}</div>` 3776 }
3776 else if (lname.includes('platinum')) { 3777 html = `<div class="line">ð¬ ${t('subscription_feature_unlimitedMessages')}</div>` + 3778 '<div class="sep"></div>' + 3779 `<div class="line">ð· ${t('subscription_feature_unlimitedPhotos')}</div>` + 3780 '<div class="sep"></div>' + 3781 `<div class="line">ð ${t('subscription_feature_createPhotos')} ð</div>` + 3782 '<div class="sep"></div>' + 3783 `<div class="line">ð§ ${t('subscription_feature_customCharacters')}</div>` + 3784 '<div class="sep"></div>' + 3785 `<div class="line">â¡ ${t('subscription_feature_fasterResponse')}</div>` + 3786 '<div class="sep"></div>' + 3787 `<div class="line">ð¬ ${t('subscription_feature_customScenarios')}</div>` 3788 } else { 3789 html = `<div class="line">ð¬ ${t('subscription_feature_unlimitedMessages')}</div>`; 3790 } 3791 upgradeTextEl.innerHTML = html; 3792 } 3793 } 3794 3795 function showPackDescription(packId) { 3796 const pack = lollipopPacks.find(p => String(p.id) === packId); 3797 const descriptionEl = document.getElementById('pack-description'); 3798 3799 console.log('showPackDescription called:', { packId, pack, descriptionEl }); 3800 3801 if (pack && descriptionEl) { 3802 // ÐпÑеделÑем опиÑание в завиÑимоÑÑи Ð¾Ñ Ð²ÑбÑанного пака 3803 let description = ''; 3804 if (pack.name.toLowerCase().includes('small')) { 3805 description = 'ð¬ Unlimited messaging'; 3806 } else if (pack.name.toLowerCase().includes('medium')) { 3807 description = 'ð¬ Unlimited messaging<br>𤳠Unlimited photos<br>ð Create your scenarios'; 3808 } else { 3809 description = 'ð¬ Unlimited messaging<br>𤳠Unlimited photos<br>ð Create your scenarios<br>ð§âð¤âð§ Create your characters'; 3810 } 3811 3812 descriptionEl.innerHTML = description; 3813 descriptionEl.classList.add('show'); 3814 console.log('Description set:', description); 3815 } else { 3816 console.log('Pack or descriptionEl not found:', { pack, descriptionEl }); 3817 } 3818 } 3819 3820 function hidePackDescription() { 3821 const descriptionEl = document.getElementById('pack-description'); 3822 if (descriptionEl) { 3823 descriptionEl.classList.remove('show'); 3824 } 3825 } 3826 3827 // ФÑнкÑÐ¸Ñ Ð´Ð»Ñ Ð¿Ð¾ÐºÑпки вÑбÑанного пака 3828 async function handlePackPurchase(pack) { 3829 if (isPaymentInProgress) { console.log('[Upgrade] payment already in progress'); return; } 3830 isPaymentInProgress = true; 3831 3832 try { 3833 if (selectedPaymentMethod === 'card') { 3834 // ÐплаÑа каÑÑой - пеÑенапÑавлÑем на ÑооÑвеÑÑÑвÑÑÑÑÑ ÑÑÑÐ»ÐºÑ 3835 const paymentLink = getCardPaymentLink(pack.name); 3836 console.log('[Upgrade] Redirecting to card payment:', paymentLink); 3837 window.open(paymentLink, '_blank'); 3838 isPaymentInProgress = false; 3839 return; 3840 } 3841 3842 // ÐплаÑа звÑздами - иÑполÑзÑем ÑÑÑеÑÑвÑÑÑÑÑ Ð»Ð¾Ð³Ð¸ÐºÑ 3843 const invoiceUrl = `${API_BASE}/getInvoice?user=${user.id}&packName=${encodeURIComponent(pack.name)}`; 3844 console.log('[Upgrade] requesting invoice:', invoiceUrl); 3845 const { resp, json, text } = await debugFetch(invoiceUrl); 3846 if (!resp.ok) { 3847 tg.showAlert(t('error_paymentLink')); 3848 isPaymentInProgress = false;
3849 return; 3850 } 3851 // Try to extract link from various possible fields or raw string 3852 let invoiceLink = null; 3853 if (typeof json === 'string' && json) invoiceLink = json; 3854 if (!invoiceLink && typeof text === 'string' && text) invoiceLink = text; 3855 if (!invoiceLink && json && typeof json === 'object') { 3856 invoiceLink = json.invoice_link || json.link || json.url || json.invoiceUrl || json.result || null; 3857 } 3858 3859 if (!invoiceLink) { 3860 tg.showAlert('Payment link is missing.'); 3861 isPaymentInProgress = false; 3862 return; 3863 } 3864 3865 // Open invoice natively. Ðе делаем Ñоллбек, ÑÑÐ¾Ð±Ñ Ð¸Ð·Ð±ÐµÐ¶Ð°ÑÑ Ð´Ñблей 3866 try { 3867 await tg.openInvoice(invoiceLink); 3868 } catch (e) { 3869 console.log('[Upgrade] openInvoice error', e); 3870 isPaymentInProgress = false; 3871 } 3872 } catch (error) { 3873 console.error('Error buying pack:', error); 3874 tg.showAlert('Purchase error. Please try again later.'); 3875 isPaymentInProgress = false; 3876 } 3877 } 3878 3879 // ФÑнкÑÐ¸Ñ Ð´Ð»Ñ ÑоÑмаÑиÑÐ¾Ð²Ð°Ð½Ð¸Ñ Ð¸Ð¼ÐµÐ½ веÑей и паков 3880 function formatDisplayName(name, type) { 3881 if (!name) return ''; 3882 3883 let formattedName = name; 3884 3885 if (type === 'pack') { 3886 // ФоÑмаÑиÑование имен паков 3887 const packNameMap = { 3888 'starter_pack': 'Starter Pack', 3889 'premium_pack': 'Premium Pack', 3890 'vip_pack': 'VIP Pack', 3891 'special_offer': 'Special Offer', 3892 'limited_edition': 'Limited Edition', 3893 'bundle_pack': 'Bundle Pack' 3894 }; 3895 3896 // ÐÑовеÑÑем ÑоÑное Ñовпадение 3897 if (packNameMap[name]) { 3898 formattedName = packNameMap[name]; 3899 } else { 3900 // ÐÑли ÑоÑного ÑÐ¾Ð²Ð¿Ð°Ð´ÐµÐ½Ð¸Ñ Ð½ÐµÑ, ÑоÑмаÑиÑÑем по пÑавилам 3901 formattedName = name 3902 .replace(/_/g, ' ') // ÐаменÑем подÑеÑÐºÐ¸Ð²Ð°Ð½Ð¸Ñ Ð½Ð° пÑÐ¾Ð±ÐµÐ»Ñ 3903 .split(' ') 3904 .map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()) 3905 .join(' '); 3906 } 3907 } 3908 3909 return formattedName; 3910 } 3911 3912 // ÐÑÑиÑление ÑÐµÐ½Ñ Ð·Ð° Ð½ÐµÐ´ÐµÐ»Ñ Ð² Stars: floor(stars/4) Ð´Ð»Ñ Ð¼ÐµÑÑÑнÑÑ Ð¿Ð¾Ð´Ð¿Ð¸Ñок, floor(stars/52) Ð´Ð»Ñ Ð³Ð¾Ð´Ð¾Ð²Ð¾Ð¹ 3913 function computeWeeklyStars(stars, packName) { 3914 const total = Number(stars) || 0; 3915 const name = String(packName || '').toLowerCase(); 3916 let divisor = 4; // по ÑмолÑÐ°Ð½Ð¸Ñ Ð´Ð»Ñ Ð¼ÐµÑÑÑнÑÑ Ð¿Ð¾Ð´Ð¿Ð¸Ñок 3917 3918 if (name.includes('platinum')) { 3919 divisor = 52; // Ð´Ð»Ñ Ð³Ð¾Ð´Ð¾Ð²Ð¾Ð¹ подпиÑки 3920 } 3921 3922 const weekly = Math.floor(total / divisor); 3923 return Math.max(1, weekly); 3924 } 3925 3926 // ÐолÑÑение ÑÑÑлки Ð´Ð»Ñ Ð¾Ð¿Ð»Ð°ÑÑ ÐºÐ°ÑÑой по ÑÐ¸Ð¿Ñ Ð¿Ð¾Ð´Ð¿Ð¸Ñки 3927 function getCardPaymentLink(packName) { 3928 const name = String(packName || '').toLowerCase(); 3929 const links = getCardPaymentLinksForLanguage(); 3930 if (name.includes('bronze')) { 3931 return links.bronze; 3932 } else if (name.includes('silver')) { 3933 return links.silver; 3934 } else if (name.includes('gold')) { 3935 return links.gold; 3936 } else if (name.includes('platinum')) { 3937 return links.platinum; 3938 } else { 3939 return links.bronze; // fallback Ð´Ð»Ñ Ð½ÐµÐ¸Ð·Ð²ÐµÑÑнÑÑ Ð¿Ð¾Ð´Ð¿Ð¸Ñок 3940 } 3941 } 3942 3943 // ФоÑмаÑиÑование ÑÐµÐ½Ñ Ð² завиÑимоÑÑи Ð¾Ñ ÑпоÑоба оплаÑÑ 3944 function formatPriceDisplay(stars, paymentMethod, packName) { 3945 const name = String(packName || '').toLowerCase(); 3946 3947 if (paymentMethod === 'card') { 3948 // ÐÐ»Ñ ÐºÐ°ÑÑÑ Ð¿Ð¾ÐºÐ°Ð·Ñваем ÑикÑиÑованнÑе ÑÐµÐ½Ñ Ð² доллаÑÐ°Ñ 3949 let weeklyEuros; 3950 if (name.includes('bronze')) { 3951 weeklyEuros = 1.99; 3952 } else if (name.includes('silver')) { 3953 weeklyEuros = 3.99; 3954 } else if (name.includes('gold')) { 3955 weeklyEuros = 5.99; 3956 } else if (name.includes('platinum')) { 3957 weeklyEuros = 1.79; 3958 } else { 3959 weeklyEuros = 2.99; // fallback 3960 } 3961 const weeklyText = t('subscription_price_week').replace('{0}', `$${weeklyEuros}`); 3962 return weeklyText; 3963 } else { 3964 // ÐÐ»Ñ Ð·Ð²Ñзд показÑваем ÑолÑко звÑÐ·Ð´Ñ Ð·Ð° Ð½ÐµÐ´ÐµÐ»Ñ 3965 const weeklyStars = computeWeeklyStars(stars, packName); 3966 const weeklyText = t('subscription_price_stars').replace('{0}', weeklyStars); 3967 return weeklyText; 3968 } 3969 } 3970
3971 // Persisted chats counter per girl (50..800), stored in localStorage 3972 function getGirlChatsCount(girlName) { 3973 try { 3974 const key = `girlChats:${girlName}`; 3975 const saved = localStorage.getItem(key); 3976 const val = Number(saved); 3977 if (Number.isFinite(val) && val >= 50 && val <= 800) return val; 3978 const generated = Math.floor(Math.random() * (800 - 50 + 1)) + 50; 3979 localStorage.setItem(key, String(generated)); 3980 return generated; 3981 } catch { 3982 // Fallback if storage is unavailable 3983 return Math.floor(Math.random() * (800 - 50 + 1)) + 50; 3984 } 3985 } 3986 3987 function createCard(item, type) { 3988 let isPurchased = false; 3989 let canAfford = true; 3990 let price = '', itemName = '', buttonText = '', description = '', id = '', icon = ''; 3991 let imageUrl = ''; 3992 3993 if (type === 'girl') { 3994 isPurchased = item.Bought; 3995 canAfford = user.lollipops >= item.Price && !isPurchased; 3996 price = item.Price; 3997 itemName = item.GirlName; 3998 buttonText = isPurchased ? 'Bought' : 'Buy'; 3999 description = item.DetailedDescription || item.Description || ''; 4000 id = item.GirlName; 4001 icon = 'ð¬'; 4002 // ÐÐ»Ñ Ð´ÐµÐ²ÑÑек болÑÑе не иÑполÑзÑем поле Photo, Ñак как ÑоÑогÑаÑии загÑÑжаÑÑÑÑ Ð¾ÑделÑно 4003 imageUrl = ''; // ÐÑÐ´ÐµÑ Ð·Ð°Ð¼ÐµÐ½ÐµÐ½Ð¾ на индикаÑÐ¾Ñ Ð·Ð°Ð³ÑÑзки 4004 4005 // Debug logging for card creation 4006 console.log('Creating card for girl:', itemName); 4007 } else if (type === 'pack') { 4008 // ÐÑе паки ÑепеÑÑ ÑолÑко за Telegram Stars 4009 price = item.stars; // Цена в Telegram Stars 4010 canAfford = true; // ÐÐ»Ñ Hard валÑÑÑ Ð²Ñегда доÑÑÑпно (оплаÑа ÑеÑез Telegram) 4011 4012 // ÐÑоÑÑое пеÑеименование паков 4013 if (item.name.toLowerCase().includes('bronze')) { 4014 itemName = t('subscription_bronze_app'); 4015 } else if (item.name.toLowerCase().includes('silver')) { 4016 itemName = t('subscription_silver_app'); 4017 } else if (item.name.toLowerCase().includes('gold')) { 4018 itemName = t('subscription_gold_app'); 4019 } else { 4020 itemName = item.name; 4021 } 4022 4023 buttonText = 'Buy'; 4024 description = ''; 4025 id = item.id; 4026 icon = 'â'; // ÐÑегда звÑÐ·Ð´Ñ Ð´Ð»Ñ Ð¿Ð°ÐºÐ¾Ð² 4027 imageUrl = item.image || `https://placehold.co/300x220/FFC107/000000?text=${encodeURIComponent(itemName)}`; 4028 } 4029 4030 const card = document.createElement('div'); 4031 card.className = 'item-card' + (type === 'girl' ? ' girl-card' : ''); 4032 card.setAttribute('data-id', id); 4033 card.setAttribute('data-type', type); 4034 4035 // ÐобавлÑем лейбл Ð´Ð»Ñ Ð´ÐµÐ²ÑÑек, еÑли он еÑÑÑ 4036 const labelHtml = (type === 'girl' && item.Label && item.Label.trim() !== '') 4037 ? `<div class="girl-card-label">${item.Label}</div>` 4038 : ''; 4039 4040 if (type === 'pack') { 4041 // ÐÐ»Ñ Ð¿Ð°ÐºÐ¾Ð² Ñоздаем новÑÑ ÑÑÑÑкÑÑÑÑ Ñ Ð²ÑбоÑом 4042 const priceText = formatPriceDisplay(item.stars, selectedPaymentMethod, item.name); 4043 const icon = selectedPaymentMethod === 'card' ? 'ð³' : 'â'; 4044 card.innerHTML = ` 4045 <div class="item-card-content"> 4046 <div class="item-name">${itemName}</div>
4047 <div class="pack-price">${icon} ${priceText}</div> 4048 <div class="pack-checkbox"></div> 4049 </div> 4050 `; 4051 } else { 4052 // ÐÐ»Ñ ÐºÐ°ÑÑоÑек пеÑÑонажей Ñоздаем новÑÑ ÑÑÑÑкÑÑÑÑ 4053 if (type === 'girl') { 4054 const chatsCount = getGirlChatsCount(itemName); 4055 4056 // ÐÑовеÑÑем, загÑÑжена ли ÑоÑогÑаÑÐ¸Ñ 4057 let photoHtml = ''; 4058 if (Number(item.GirlId) < 1000) { 4059 // ÐÐ»Ñ Ð»Ð¾ÐºÐ°Ð»ÑнÑÑ Ð´ÐµÐ²ÑÑек иÑполÑзÑем локалÑнÑй Ñайл и ленивÑÑ Ð·Ð°Ð³ÑÑÐ·ÐºÑ 4060 const localPath = `Photos/${encodeURIComponent(itemName)}.jpg`; 4061 photoHtml = `<img class="item-card-image lazy-local" data-src="${localPath}" alt="${itemName}" loading="lazy" onerror="this.onerror=null;this.src='https://placehold.co/300x220/9C27B0/FFFFFF?text=${encodeURIComponent(itemName)}'">`; 4062 } else if (girlsPhotosLoaded && girlsPhotos.has(item.GirlId)) { 4063 // ÐÑли ÑоÑо Ñже загÑÑжено из API, показÑваем его 4064 const photo = girlsPhotos.get(item.GirlId); 4065 photoHtml = `<img class="item-card-image" src="data:image/jpeg;base64,${photo}" alt="${itemName}" onerror="this.onerror=null;this.src='https://placehold.co/300x220/9C27B0/FFFFFF?text=${encodeURIComponent(itemName)}'">`; 4066 } else { 4067 // ÐÑли ÑоÑо еÑе не загÑÑжено, показÑваем индикаÑÐ¾Ñ Ð·Ð°Ð³ÑÑзки 4068 photoHtml = ` 4069 <div class="photo-loading-container"> 4070 <div class="photo-loading-spinner"></div> 4071 </div> 4072 `; 4073 } 4074 4075 card.innerHTML = ` 4076 ${labelHtml} 4077 ${photoHtml} 4078 <div class="item-card-content"> 4079 <div class="item-name">${itemName}</div> 4080 <div class="item-desc">${description}</div> 4081 <div class="girl-chats-label">${chatsCount}k ${t('character_chats')}</div> 4082 <div class="girl-relationship-level">${item.CurrentLoyaltyLevel || t('character_unknown')}</div> 4083 </div> 4084 `; 4085 } else { 4086 // ÐÐ»Ñ Ð¾ÑÑалÑнÑÑ ÐºÐ°ÑÑоÑек оÑÑавлÑем ÑÑаÑÑÑ ÑÑÑÑкÑÑÑÑ 4087 card.innerHTML = ` 4088 ${labelHtml} 4089 <img class="item-card-image" src="${imageUrl}" alt="${itemName}" onerror="this.onerror=null;this.src='https://placehold.co/300x220/9C27B0/FFFFFF?text=${encodeURIComponent(itemName)}'"> 4090 <div class="item-card-title-overlay"> 4091 <span class="item-card-title-text">${itemName}</span> 4092 </div> 4093 <div class="item-card-content"> 4094 <div class="item-desc">${description}</div> 4095 </div> 4096 `; 4097 } 4098 } 4099 if (type === 'pack') { 4100 card.classList.add('pack-card'); 4101 // ÐобавлÑем обÑабоÑÑик клика Ð´Ð»Ñ Ð²ÑбоÑа каÑÑоÑки 4102 card.addEventListener('click', () => { 4103 togglePackSelection(card, id); 4104 }); 4105 } else if (!isPurchased) { 4106 card.classList.add('not-bought'); 4107 card.style.cursor = 'default'; 4108 } 4109 4110 // ÐобавлÑем обÑабоÑÑик клика Ð´Ð»Ñ ÐºÐ°ÑÑоÑек девÑÑек 4111 if (type === 'girl') { 4112 card.classList.add('girl-card-clickable'); 4113 card.style.cursor = 'pointer'; 4114 card.onclick = function() { 4115 // ÐеÑедаем обÑÐµÐºÑ Ð´ÐµÐ²ÑÑки напÑÑмÑÑ, а не иÑем по имени 4116 openGirlModal(item); 4117 }; 4118 } 4119 4120 return card; 4121 } 4122 4123 function renderGirlsShop() { 4124 // ÐÑли магазин Ñже оÑÑендеÑен, не пеÑеÑоздаем каÑÑоÑки 4125 if (girlsShopRendered) { 4126 console.log('Girls shop already rendered, skipping...'); 4127 return; 4128 } 4129 4130 shops.girls.innerHTML = ''; 4131 // Add create-character card on top 4132 const createCardEl = document.createElement('div'); 4133 createCardEl.className = 'create-card'; 4134 createCardEl.innerHTML = `<button class="plus-btn">+</button><div class="label">${t('character_create')}</div>`; 4135 const plusBtn = createCardEl.querySelector('.plus-btn'); 4136 createCardEl.addEventListener('click', async () => { 4137 // Show loading on the button while checking subscription 4138 if (plusBtn) { 4139 plusBtn.classList.add('loading'); 4140 plusBtn.setAttribute('disabled', 'true'); 4141 } 4142 try { 4143 const subscriptionResponse = await fetch(`${API_BASE}/subscription?user=${user.id}`); 4144 const subscriptionStatusRaw = await subscriptionResponse.text(); 4145 const subscriptionStatus = subscriptionStatusRaw 4146 .trim() 4147 .replace(/^\"|\"$/g, '') 4148 .toLowerCase(); 4149 if (subscriptionStatus === 'none' || subscriptionStatus === 'bronze') { 4150 // Update texts in case language changed 4151 if (typeof updateModalTexts === 'function') { 4152 updateModalTexts(); 4153 } 4154 const createModalBlurEl = document.getElementById('create-modal-blur'); 4155 const createModalEl = document.getElementById('create-modal'); 4156 if (createModalBlurEl && createModalEl) { 4157 createModalBlurEl.style.display = 'block'; 4158 createModalEl.style.display = 'block'; 4159 } 4160 return; 4161 } 4162 // Allow Silver, Gold, Platinum to proceed 4163 if (subscriptionStatus === 'silver' || subscriptionStatus === 'gold' || subscriptionStatus === 'platinum') { 4164 openCreateModal(); 4165 return; 4166 } 4167 // Fallback: if unexpected status, show upgrade modal 4168 // Update texts in case language changed 4169 if (typeof updateModalTexts === 'function') { 4170 updateModalTexts(); 4171 } 4172 const createModalBlurEl = document.getElementById('create-modal-blur'); 4173 const createModalEl = document.getElementById('create-modal'); 4174 if (createModalBlurEl && createModalEl) { 4175 createModalBlurEl.style.display = 'block'; 4176 createModalEl.style.display = 'block'; 4177 } 4178 } catch (e) { 4179 console.error('Error checking subscription for create card:', e); 4180 // Update texts in case language changed 4181 if (typeof updateModalTexts === 'function') { 4182 updateModalTexts(); 4183 } 4184 const createModalBlurEl = document.getElementById('create-modal-blur'); 4185 const createModalEl = document.getElementById('create-modal'); 4186 if (createModalBlurEl && createModalEl) { 4187 createModalBlurEl.style.display = 'block'; 4188 createModalEl.style.display = 'block'; 4189 } 4190 } finally { 4191 if (plusBtn) { 4192 plusBtn.classList.remove('loading'); 4193 plusBtn.removeAttribute('disabled'); 4194 } 4195 } 4196 }); 4197 shops.girls.appendChild(createCardEl); 4198 4199 // Safety check for girlsData 4200 if (!girlsData || !Array.isArray(girlsData) || girlsData.length === 0) { 4201 shops.girls.innerHTML = '<p>Loading characters...</p>'; 4202 return; 4203 } 4204 girlsData.forEach(girl => shops.girls.appendChild(createCard(girl, 'girl'))); 4205 4206 // ÐÑмеÑаем ÑÑо магазин оÑÑендеÑен 4207 girlsShopRendered = true; 4208 } 4209 4210 4211 4212 function renderLollipopsShop() { 4213 shops.lollipops.innerHTML = ''; 4214 if(lollipopPacks.length === 0) { 4215 shops.lollipops.innerHTML = '<p>Loading products...</p>' 4216 return; 4217 } 4218 lollipopPacks.forEach(pack => shops.lollipops.appendChild(createCard(pack, 'pack'))); 4219 4220 // ÐоÑÑÑанавливаем вÑÐ±Ð¾Ñ Ð¸Ð»Ð¸ вÑбиÑаем поÑледнÑÑ ÐºÐ°ÑÑоÑÐºÑ Ð¿Ð¾ ÑмолÑÐ°Ð½Ð¸Ñ 4221 if (lollipopPacks.length > 0 && currentPage === 'lollipops') { 4222 let packToSelect = null; 4223 4224 // ÐÑли еÑÑÑ ÑÐ¾Ñ ÑаненнÑй вÑбоÑ, пÑÑаемÑÑ ÐµÐ³Ð¾ воÑÑÑановиÑÑ 4225 if (lastSelectedPackId) { 4226 packToSelect = lollipopPacks.find(p => String(p.id) === String(lastSelectedPackId)); 4227 console.log('Trying to restore last selection:', lastSelectedPackId, 'Found pack:', packToSelect); 4228 } 4229 4230 // ÐÑли не ÑдалоÑÑ Ð²Ð¾ÑÑÑановиÑÑ, вÑбиÑаем поÑледнÑÑ ÐºÐ°ÑÑоÑÐºÑ 4231 if (!packToSelect) { 4232 packToSelect = lollipopPacks[lollipopPacks.length - 1]; 4233 console.log('Auto-selecting last pack as fallback:', packToSelect); 4234 } 4235 4236 const cardToSelect = document.querySelector(`[data-id="${packToSelect.id}"][data-type="pack"]`); 4237 console.log('Found card to select:', cardToSelect); 4238 if (cardToSelect) { 4239 console.log('Calling togglePackSelection for selection'); 4240 togglePackSelection(cardToSelect, packToSelect.id); 4241 } 4242 } 4243 4244 // ÐодÑвеÑиваем каÑÑоÑÐºÑ ÐµÑли еÑÑÑ Ð¿Ð°ÑамеÑÑ highlight 4245 console.log('renderLollipopsShop: highlightId =', highlightId); 4246 if (highlightId) { 4247 console.log('renderLollipopsShop: calling highlightCard with delay'); 4248 setTimeout(() => { 4249 highlightCard(highlightId, 'pack'); 4250 }, 100); 4251 } 4252 } 4253 4254 async function handleBuy(e) { 4255 const target = e.target; 4256 if (!target.classList.contains('buy-button')) return; 4257 4258 const id = target.dataset.id; 4259 const type = target.dataset.type; 4260 4261 if (type === 'girl') { 4262 if (!girlsData || !Array.isArray(girlsData)) return; 4263 const girl = girlsData.find(g => g.GirlName === id); 4264 if (girl && !girl.Bought) { 4265 // ÐокÑпка ÑеÑез API 4266 try { 4267 const { resp, json: buyResult } = await debugFetch(`${API_BASE}/buy?user=${user.id}&girlName=${encodeURIComponent(girl.GirlName)}`); 4268 if (buyResult.Success) { 4269 tg.HapticFeedback.notificationOccurred('success'); 4270 tg.showAlert(t('alert_alreadyBought')); 4271 await fetchCurrency(); 4272 // ÐбновлÑем ÑпиÑок девÑÑек 4273 await refreshGirlsData(); 4274 4275 // ÐбновлÑем ÐºÐ½Ð¾Ð¿ÐºÑ Ð² модалÑном окне, еÑли оно оÑкÑÑÑо 4276 const girlModalBtn = document.querySelector('.girl-modal-btn'); 4277 if (girlModalBtn && girlModalBtn.dataset.girlId === String(girl.GirlId)) { 4278 girlModalBtn.textContent = t('button_chat'); 4279 girlModalBtn.disabled = false;
4280 girlModalBtn.onclick = () => { 4281 // ÐÑкÑÑваем модалÑное окно вÑбоÑа иÑÑоÑии 4282 storyModal.dataset.girlId = String(girl.GirlId); 4283 openStoryModal(girl.GirlName); 4284 }; 4285 } 4286 4287 updateUI(); 4288 } else { 4289 tg.HapticFeedback.notificationOccurred('error'); 4290 tg.showAlert('ÐедоÑÑаÑоÑно ÑÑедÑÑв!'); 4291 await fetchCurrency(); 4292 updateUI(); 4293 } 4294 } catch { 4295 tg.showAlert('ÐÑибка пÑи покÑпке. ÐопÑобÑйÑе позже.'); 4296 } 4297 } else if (girl && girl.Bought) { 4298 tg.showAlert('Already bought!'); 4299 } 4300 } else if (type === 'pack') { 4301 // Ðогика покÑпки паков ÑепеÑÑ Ð¾Ð±ÑабаÑÑваеÑÑÑ ÑеÑез ÐºÐ½Ð¾Ð¿ÐºÑ Upgrade 4302 // ÐÑа веÑка болÑÑе не иÑполÑзÑеÑÑÑ 4303 } 4304 4305 updateUI(); 4306 } 4307 4308 // --- Init & Event Listeners --- 4309 navButtons.girls.addEventListener('click', () => switchPage('girls')); 4310 4311 navButtons.lollipops.addEventListener('click', () => switchPage('lollipops')); 4312 4313 // Back button on lollipops page 4314 const backButton = document.getElementById('back-button'); 4315 if (backButton) { 4316 backButton.addEventListener('click', () => switchPage('girls')); 4317 } 4318 4319 // ÐбÑабоÑÑики Ð´Ð»Ñ ÑвиÑÑеÑа ÑÑилей ÑÐ´Ð°Ð»ÐµÐ½Ñ 4320 4321 // ÐобавлÑем обÑабоÑÑик Ð´Ð»Ñ ÐºÐ½Ð¾Ð¿ÐºÐ¸ Ñ Ð¿Ð»ÑÑиком в панели валÑÑ 4322 const addCurrencyBtn = document.querySelector('.add-currency-btn'); 4323 if (addCurrencyBtn) { 4324 addCurrencyBtn.addEventListener('click', () => switchPage('lollipops')); 4325 } 4326 4327 document.querySelector('.container').addEventListener('click', handleBuy); 4328 4329 async function handleUpgradeAction() { 4330 try { 4331 console.log('[Upgrade] action'); 4332 if (isPaymentInProgress) { console.log('[Upgrade] blocked: payment in progress'); return; } 4333 // ÐÑли ниÑего не вÑбÑано â вÑбеÑем поÑледнÑÑ ÐºÐ°ÑÑоÑÐºÑ Ð¿Ð¾ ÑмолÑÐ°Ð½Ð¸Ñ 4334 if (!selectedPackId && Array.isArray(lollipopPacks) && lollipopPacks.length > 0) { 4335 const fallbackPack = lollipopPacks[lollipopPacks.length - 1]; 4336 const cardToSelect = document.querySelector(`[data-id="${fallbackPack.id}"][data-type="pack"]`); 4337 if (cardToSelect) togglePackSelection(cardToSelect, fallbackPack.id); 4338 } 4339 if (!selectedPackId) { 4340 tg.showAlert('Please select a subscription first.'); 4341 return; 4342 } 4343 const pack = lollipopPacks.find(p => String(p.id) === String(selectedPackId)); 4344 console.log('[Upgrade] selected pack:', pack); 4345 if (pack) { 4346 await handlePackPurchase(pack); 4347 } else { 4348 tg.showAlert('Subscription not found.'); 4349 } 4350 } catch (e) { 4351 console.error('[Upgrade] handler error', e); 4352 } 4353 } 4354 4355 // ÐбÑабоÑÑик Ð´Ð»Ñ ÐºÐ½Ð¾Ð¿ÐºÐ¸ Upgrade 4356 const upgradeButtonEl = document.getElementById('upgrade-button'); 4357 if (upgradeButtonEl) { 4358 upgradeButtonEl.addEventListener('click', handleUpgradeAction); 4359 } 4360 4361 // Ðбновление баланÑа поÑле ÑÑпеÑной оплаÑÑ ÑеÑез Telegram Stars 4362 tg.onEvent('invoicePaid', async () => { 4363 await fetchCurrency(); 4364 updateUI(); 4365 isPaymentInProgress = false;
4366 }); 4367 // ÐбÑабоÑка закÑÑÑÐ¸Ñ Ð¾ÐºÐ½Ð° оплаÑÑ (Ð´Ð»Ñ Ð¾Ð±ÑаÑной ÑвÑзи) 4368 tg.onEvent('invoiceClosed', async (data) => { 4369 try { 4370 if (data && data.status === 'paid') { 4371 await fetchCurrency(); 4372 updateUI(); 4373 } else if (data && data.status === 'failed') { 4374 tg.showAlert('Payment failed. Please try again.'); 4375 } 4376 } catch {} 4377 isPaymentInProgress = false; 4378 }); 4379 4380 // --- ÐодалÑное окно девÑÑки --- 4381 const girlModalBg = document.createElement('div'); 4382 girlModalBg.id = 'girl-modal-bg'; 4383 document.body.appendChild(girlModalBg); 4384 const girlModal = document.createElement('div'); 4385 girlModal.id = 'girl-modal'; 4386 girlModal.innerHTML = ` 4387 <div class="girl-modal-inner"> 4388 <div class="girl-modal-photo-container"> 4389 <div class="girl-modal-photo-loading"> 4390 <div class="photo-loading-spinner"></div> 4391 </div> 4392 <img class="girl-modal-photo" src="" alt="" style="display: none;"> 4393 </div> 4394 <div class="girl-modal-fade"></div> 4395 <div class="girl-modal-content"> 4396 <div class="girl-modal-name"></div> 4397 <div class="girl-modal-desc"></div> 4398 <div class="girl-modal-loyalty"> 4399 <div class="girl-modal-loyalty-top"> 4400 <span class="girl-modal-points"></span> 4401 <span class="girl-modal-upgrade"></span> 4402 </div> 4403 <div class="girl-modal-loyalty-bar"> 4404 <div class="girl-modal-loyalty-fill"></div> 4405 </div> 4406 <div class="girl-modal-loyalty-bottom"> 4407 <span class="girl-modal-loyalty-current"></span> 4408 <span class="girl-modal-loyalty-next"></span> 4409 </div> 4410 <div class="girl-modal-loyalty-text"></div> 4411 </div> 4412 <div class="girl-modal-btns-row"> 4413 <button class="girl-modal-close" title="Close"> 4414 <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="6" y1="6" x2="18" y2="18"/><line x1="18" y1="6" x2="6" y2="18"/></svg> 4415 </button> 4416 <button class="girl-modal-btn"></button> 4417 </div> 4418 </div> 4419 </div> 4420 `; 4421 document.body.appendChild(girlModal); 4422 const girlModalCloseBtn = girlModal.querySelector('.girl-modal-close'); 4423 const girlModalPhoto = girlModal.querySelector('.girl-modal-photo'); 4424 const girlModalName = girlModal.querySelector('.girl-modal-name'); 4425 const girlModalDesc = girlModal.querySelector('.girl-modal-desc'); 4426 const girlModalLoyaltyCurrent = girlModal.querySelector('.girl-modal-loyalty-current'); 4427 const girlModalLoyaltyNext = girlModal.querySelector('.girl-modal-loyalty-next'); 4428 const girlModalLoyaltyFill = girlModal.querySelector('.girl-modal-loyalty-fill'); 4429 const girlModalLoyaltyText = girlModal.querySelector('.girl-modal-loyalty-text'); 4430 const girlModalBtn = girlModal.querySelector('.girl-modal-btn'); 4431 const girlModalPoints = girlModal.querySelector('.girl-modal-points'); 4432 const girlModalUpgrade = girlModal.querySelector('.girl-modal-upgrade'); 4433 4434 // --- ÐодалÑное окно вÑбоÑа иÑÑоÑии --- 4435 const storyModalBg = document.createElement('div'); 4436 storyModalBg.id = 'story-modal-bg'; 4437 storyModalBg.style.cssText = `
4438 position: fixed; 4439 left: 0; top: 0; right: 0; bottom: 0; 4440 z-index: 4000; 4441 background: rgba(0,0,0,0.7); 4442 display: none; 4443 `; 4444 document.body.appendChild(storyModalBg); 4445 4446 const storyModal = document.createElement('div'); 4447 storyModal.id = 'story-modal'; 4448 storyModal.style.cssText = ` 4449 position: fixed; 4450 left: 50%; top: 50%; 4451 transform: translate(-50%, -50%); 4452 z-index: 4100; 4453 background: #131313; 4454 border-radius: 28px; 4455 box-shadow: 0 8px 40px 0 #000a; 4456 min-width: 300px; 4457 max-width: 90vw; 4458 padding: 32px 24px; 4459 display: none; 4460 flex-direction: column; 4461 align-items: stretch; 4462 border: 1.5px solid #212121; 4463 `; 4464 storyModal.innerHTML = ` 4465 <div style="text-align: center; margin-bottom: 24px;"> 4466 <div style="font-size: 1.25em; font-weight: bold; color: #fff; margin-bottom: 8px;">ÐÑбеÑиÑе иÑÑоÑиÑ</div> 4467 <div style="color: #bfaed1; font-size: 1em;">ÐÑбеÑиÑе иÑÑоÑÐ¸Ñ Ð´Ð»Ñ Ð½Ð°Ñала ÑаÑа</div> 4468 </div> 4469 <div id="story-options" style="display: flex; flex-direction: column; gap: 16px;"> 4470 <button class="story-option" data-story="massage" style=" 4471 background: linear-gradient(90deg, #a084ee 0%, #d8b4fe 100%); 4472 color: #fff; 4473 border: none; 4474 border-radius: 16px; 4475 padding: 20px; 4476 font-size: 1.1em; 4477 font-weight: bold; 4478 cursor: pointer; 4479 transition: transform 0.2s, box-shadow 0.2s; 4480 text-align: left; 4481 display: flex; 4482 align-items: center; 4483 gap: 16px; 4484 "> 4485 <div style=" 4486 width: 48px; 4487 height: 48px; 4488 background: rgba(255, 255, 255, 0.2); 4489 border-radius: 12px; 4490 display: flex; 4491 align-items: center; 4492 justify-content: center; 4493 font-size: 1.5em; 4494 ">ðââï¸</div> 4495 <div> 4496 <div style="font-weight: bold; margin-bottom: 4px;">ÐÑÑоÑÐ¸Ñ Ð² маÑÑажке</div> 4497 <div style="font-size: 0.9em; opacity: 0.8;">ÐаÑниÑе ÑÐ°Ñ Ñ Ð¼Ð°ÑÑажем</div> 4498 </div> 4499 </button> 4500 </div> 4501 <button class="story-modal-close" style=" 4502 position: absolute; 4503 right: 16px; top: 16px; 4504 background: #111; 4505 color: #fff; 4506 width: 40px; 4507 height: 40px; 4508 border-radius: 12px; 4509 display: flex; 4510 align-items: center; 4511 justify-content: center; 4512 cursor: pointer; 4513 border: none; 4514 font-weight: bold; 4515 font-size: 1.2em; 4516 transition: background 0.2s; 4517 ">â</button> 4518 `; 4519 document.body.appendChild(storyModal); 4520 4521 const storyModalCloseBtn = storyModal.querySelector('.story-modal-close'); 4522 const storyOptions = storyModal.querySelectorAll('.story-option'); 4523 4524 // ФÑнкÑии Ð´Ð»Ñ ÑабоÑÑ Ñ Ð¼Ð¾Ð´Ð°Ð»ÑнÑм окном вÑбоÑа иÑÑоÑии 4525 function openStoryModal(girlName) { 4526 storyModalBg.style.display = 'block'; 4527 storyModal.style.display = 'flex'; 4528 } 4529 4530 function closeStoryModal() { 4531 storyModalBg.style.display = 'none'; 4532 storyModal.style.display = 'none'; 4533 } 4534 4535 // ÐбÑабоÑÑик вÑбоÑа иÑÑоÑии 4536 function handleStorySelection(storyType, girlId) { 4537 closeStoryModal(); 4538 closeGirlModal(); 4539 4540 // ÐÑполнÑем Ð»Ð¾Ð³Ð¸ÐºÑ Ð¾ÑкÑÑÑÐ¸Ñ ÑаÑа (пÑоÑлÑй Ñ
4540ÑнкÑионал кнопки "Chat") 4541 console.log('Sending request to /openchat for girlId:', girlId, 'with story:', storyType); 4542 4543 if (!user.id || !girlId) { 4544 console.error('Error: user.id or girlId is empty'); 4545 tg.showAlert('Error: insufficient data to open chat'); 4546 return; 4547 } 4548 4549 // ФÑнкÑÐ¸Ñ Ð´Ð»Ñ Ð·Ð°ÐºÑÑÑÐ¸Ñ Ð¼Ð¸Ð½Ð¸-аппа 4550 const closeMiniApp = () => { 4551 console.log('ÐÑÑаемÑÑ Ð·Ð°ÐºÑÑÑÑ Ð¼Ð¸Ð½Ð¸-апп...'); 4552 4553 // ÐеÑод 1: ÐÑновной меÑод Telegram WebApp 4554 try { 4555 if (window.Telegram && window.Telegram.WebApp) { 4556 window.Telegram.WebApp.close(); 4557 console.log('Ðини-апп закÑÑÑ ÑеÑез window.Telegram.WebApp.close()'); 4558 return true; 4559 } 4560 } catch (e) { 4561 console.log('window.Telegram.WebApp.close() не ÑÑабоÑал:', e); 4562 } 4563 4564 // ÐеÑод 2: ЧеÑез пеÑеменнÑÑ tg 4565 try { 4566 if (tg && typeof tg.close === 'function') { 4567 tg.close(); 4568 console.log('Ðини-апп закÑÑÑ ÑеÑез tg.close()'); 4569 return true; 4570 } 4571 } catch (e) { 4572 console.log('tg.close() не ÑÑабоÑал:', e); 4573 } 4574 4575 // ÐеÑод 3: ÐопÑÑка закÑÑÑÑ Ð¾ÐºÐ½Ð¾ бÑаÑзеÑа 4576 try { 4577 window.close(); 4578 console.log('ÐопÑÑка закÑÑÑÑ ÑеÑез window.close()'); 4579 return true; 4580 } catch (e) { 4581 console.log('window.close() не ÑÑабоÑал:', e); 4582 } 4583 4584 // ÐеÑод 4: ÐопÑÑка ÑеÑез Telegram Bot API 4585 try { 4586 if (window.Telegram && window.Telegram.WebApp && window.Telegram.WebApp.MainButton) { 4587 window.Telegram.WebApp.MainButton.hide(); 4588 window.Telegram.WebApp.close(); 4589 console.log('Ðини-апп закÑÑÑ ÑеÑез MainButton.hide() + close()'); 4590 return true; 4591 } 4592 } catch (e) { 4593 console.log('MainButton меÑод не ÑÑабоÑал:', e); 4594 } 4595 4596 console.log('ÐÑе меÑÐ¾Ð´Ñ Ð·Ð°ÐºÑÑÑÐ¸Ñ Ð½Ðµ ÑÑабоÑали'); 4597 return false; 4598 }; 4599 4600 // ÐÑпÑавлÑем запÑÐ¾Ñ Ð½Ð° ÑеÑÐ²ÐµÑ 4601 try { 4602 console.log('Sending request to /openchat...'); 4603 4604 // ÐÑпÑавлÑем запÑÐ¾Ñ Ð±ÐµÐ· Ð¾Ð¶Ð¸Ð´Ð°Ð½Ð¸Ñ Ð¾ÑвеÑа (fire and forget) 4605 fetch(`${API_BASE}/openchat?user=${user.id}&girlName=${encodeURIComponent(girlId)}`) 4606 .then(response => { 4607 console.log('Server response received:', response.status); 4608 if (response.ok) { 4609 tg.HapticFeedback.notificationOccurred('success'); 4610 } else { 4611 tg.HapticFeedback.notificationOccurred('error'); 4612 } 4613 }) 4614 .catch(error => { 4615 console.error('ÐÑибка пÑи оÑпÑавке запÑоÑа:', error); 4616 tg.HapticFeedback.notificationOccurred('error'); 4617 }); 4618 4619 // СÑÐ°Ð·Ñ Ð¾ÑкÑÑваем ÑÑÑÐ»ÐºÑ Ð½Ð° боÑа 4620 console.log('ÐÑкÑÑваем ÑÑÑÐ»ÐºÑ Ð½Ð° боÑа...'); 4621 try { 4622 tg.openTelegramLink('https://t.me/CandyAIGirlsBot'); 4623 } catch (e) { 4624 console.log('tg.openTelegramLink() не ÑÑабоÑал:', e); 4625 } 4626 4627 // ÐакÑÑваем мини-апп Ñ Ð½ÐµÐ±Ð¾Ð»ÑÑой задеÑжкой 4628 setTimeout(() => { 4629 closeMiniApp(); 4630 }, 500); 4631 4632 } catch (error) { 4633 console.error('ÐÑибка пÑи обÑабоÑке:', error); 4634 tg.HapticFeedback.notificationOccurred('error'); 4635 4636 // Ð ÑлÑÑае оÑибки вÑе Ñавно пÑÑаемÑÑ Ð¾ÑкÑÑÑÑ ÑÑÑÐ»ÐºÑ Ð¸ закÑÑÑÑ 4637 try { 4638 tg.openTelegramLink('https://t.me/CandyAIGirlsBot'); 4639 } catch (e) { 4640 console.log('Ðе ÑдалоÑÑ Ð¾ÑкÑÑÑÑ ÑÑÑÐ»ÐºÑ Ð½Ð° боÑа:', e); 4641 } 4642 4643 // ÐакÑÑваем мини-апп даже пÑи оÑибке 4644 setTimeout(() => { 4645 closeMiniApp(); 4646 }, 500); 4647 } 4648 } 4649 4650 // ÐобавлÑем обÑабоÑÑики ÑобÑÑий Ð´Ð»Ñ Ð¼Ð¾Ð´Ð°Ð»Ñного окна вÑбоÑа иÑÑоÑии 4651 storyModalCloseBtn.addEventListener('click', closeStoryModal); 4652 storyModalBg.addEventListener('click', closeStoryModal); 4653 4654 storyOptions.forEach(option => { 4655 option.addEventListener('click', () => { 4656 const storyType = option.dataset.story; 4657 const girlId = storyModal.dataset.girlId; 4658 handleStorySelection(storyType, girlId); 4659 }); 4660 4661 // ÐобавлÑем hover ÑÑÑекÑÑ 4662 option.addEventListener('mouseenter', () => { 4663 option.style.transform = 'scale(1.02)'; 4664 option.style.boxShadow = '0 4px 20px rgba(160, 132, 238, 0.3)'; 4665 }); 4666 4667 option.addEventListener('mouseleave', () => { 4668 option.style.transform = 'scale(1)';
4669 option.style.boxShadow = 'none'; 4670 }); 4671 }); 4672 4673 function openGirlModal(girl) { 4674 document.body.style.overflow = 'hidden'; 4675 4676 // Debug logging to check photo data 4677 console.log('Opening modal for girl:', girl.GirlName); 4678 4679 // ÐолÑÑаем ÑлеменÑÑ Ð¼Ð¾Ð´Ð°Ð»Ñного окна 4680 const girlModalPhotoContainer = girlModal.querySelector('.girl-modal-photo-container'); 4681 const girlModalPhotoLoading = girlModal.querySelector('.girl-modal-photo-loading'); 4682 const girlModalPhoto = girlModal.querySelector('.girl-modal-photo'); 4683 4684 // ÐÑполÑзÑем локалÑнÑÑ ÑоÑогÑаÑÐ¸Ñ Ð´Ð»Ñ GirlId < 1000, инаÑе из кÑÑа/индикаÑÐ¾Ñ 4685 if (Number(girl.GirlId) < 1000) { 4686 girlModalPhoto.src = `Photos/${encodeURIComponent(girl.GirlName)}.jpg`; 4687 girlModalPhoto.style.display = 'block'; 4688 girlModalPhotoLoading.style.display = 'none'; 4689 } else if (girlsPhotos.has(girl.GirlId)) { 4690 const photo = girlsPhotos.get(girl.GirlId); 4691 girlModalPhoto.src = `data:image/jpeg;base64,${photo}`; 4692 girlModalPhoto.style.display = 'block'; 4693 girlModalPhotoLoading.style.display = 'none'; 4694 } else { 4695 // ÐоказÑваем индикаÑÐ¾Ñ Ð·Ð°Ð³ÑÑзки 4696 girlModalPhotoLoading.style.display = 'flex'; 4697 girlModalPhoto.style.display = 'none'; 4698 girlModalPhoto.src = ''; // ÐÑиÑаем src 4699 } 4700 4701 girlModalPhoto.onerror = function() { 4702 this.onerror=null; 4703 this.src='https://placehold.co/300x220/9C27B0/FFFFFF?text='+encodeURIComponent(girl.GirlName); 4704 }; 4705 girlModalName.textContent = girl.GirlName; 4706 girlModalDesc.textContent = girl.DetailedDescription || ''; 4707 4708 // УÑÑанавливаем аÑÑибÑÑÑ Ð´Ð»Ñ Ð¸Ð´ÐµÐ½ÑиÑикаÑии девÑÑки 4709 girlModalBtn.dataset.girlId = String(girl.GirlId); 4710 storyModal.dataset.girlId = String(girl.GirlId); 4711 4712 // ÐбновлÑем ÑÑÑÐ¾ÐºÑ Ð»Ð¾ÑлÑноÑÑи 4713 const currentLoyalty = girl.CurrentLoyaltyPoints || 0; 4714 const maxLoyalty = girl.MaxLoyaltyPoints || 10; 4715 const loyaltyPercentage = maxLoyalty > 0 ? (currentLoyalty / maxLoyalty) * 100 : 0; 4716 4717 // Top row: points (left) and upgrade (right) 4718 if (girlModalPoints) { 4719 girlModalPoints.textContent = `${currentLoyalty} / ${maxLoyalty}`; 4720 } 4721 if (girlModalUpgrade) { 4722 const giftsLeft = Number(user.gifts) || 0; 4723 girlModalUpgrade.textContent = `ð Upgrade (${giftsLeft}) â¬ï¸`; 4724 girlModalUpgrade.onclick = async () => { 4725 // Optimistic UI update if user has gifts 4726 const revert = { did: false, prevGifts: Number(user.gifts) || 0, prevPoints: null }; 4727 const giftsValueEl = document.querySelector('#user-balance .currency-panel .currency-item:nth-child(2) .currency-value'); 4728 const parsePoints = () => { 4729 const txt = girlModalPoints && girlModalPoints.textContent ? girlModalPoints.textContent : '0 / 0'; 4730 const m = txt.match(/\s*(\d+)\s*\/\s*(\d+)\s*/); 4731 if (!m) return { cur: 0, max: 0 }; 4732 return { cur: parseInt(m[1], 10) || 0, max: parseInt(m[2], 10) || 0 }; 4733 }; 4734 const applyPoints = (cur, max) => { 4735 if (girlModalPoints) girlModalPoints.textContent = `${cur} / ${max}`; 4736 const percent = max > 0 ? Math.min(100, Math.max(0, (cur / max) * 100)) : 0; 4737 if (girlModalLoyaltyFill) girlModalLoyaltyFill.style.width = percent + '%'; 4738 }; 4739 const doOptimistic = () => { 4740 if ((Number(user.gifts) || 0) <= 0) return false;
4741 // save current 4742 const { cur, max } = parsePoints(); 4743 revert.prevPoints = { cur, max }; 4744 // gifts-- 4745 user.gifts = Math.max(0, (Number(user.gifts) || 0) - 1); 4746 if (giftsValueEl) giftsValueEl.textContent = String(user.gifts); 4747 if (girlModalUpgrade) girlModalUpgrade.textContent = `ð Upgrade (${Number(user.gifts) || 0}) â¬ï¸`; 4748 // points++ (cap at max) 4749 const newCur = Math.min(max, cur + 1); 4750 applyPoints(newCur, max); 4751 // update girlsData cache 4752 const idx = Array.isArray(girlsData) ? girlsData.findIndex(g => g.GirlName === girl.GirlName) : -1; 4753 if (idx >= 0) { 4754 const prev = girlsData[idx].CurrentLoyaltyPoints || 0; 4755 girlsData[idx].CurrentLoyaltyPoints = Math.min(girlsData[idx].MaxLoyaltyPoints || 10, prev + 1); 4756 } 4757 revert.did = true; 4758 return true; 4759 }; 4760 const undoOptimistic = () => { 4761 if (!revert.did) return; 4762 // revert gifts 4763 user.gifts = revert.prevGifts; 4764 if (giftsValueEl) giftsValueEl.textContent = String(user.gifts); 4765 if (girlModalUpgrade) girlModalUpgrade.textContent = `ð Upgrade (${Number(user.gifts) || 0}) â¬ï¸`; 4766 // revert points 4767 if (revert.prevPoints) applyPoints(revert.prevPoints.cur, revert.prevPoints.max); 4768 }; 4769 // do optimistic update 4770 doOptimistic(); 4771 try { 4772 const url = `${API_BASE}/sendgift?user=${user.id}&girl=${encodeURIComponent(girl.GirlId)}`; 4773 const { resp, json, text } = await debugFetch(url); 4774 const norm = (v) => { 4775 if (typeof v !== 'string') return ''; 4776 let s = v.trim().toLowerCase(); 4777 // strip single or double surrounding quotes if present 4778 s = s.replace(/^['"]|['"]$/g, ''); 4779 return s; 4780 }; 4781 let ok = false; 4782 if (resp.ok) { 4783 if (json) { 4784 if (typeof json === 'string') { 4785 const s = norm(json); 4786 ok = (s === 'success' || s === 'true'); 4787 } else { 4788 ok = !!(json.success === true || json.Success === true || json.success === 'success' || json.Success === 'success'); 4789 } 4790 } else if (typeof text === 'string') { 4791 const s = norm(text); 4792 ok = (s === 'success' || s === 'true'); 4793 } 4794 } 4795 if (ok) { 4796 // Refresh gifts and girl data 4797 await fetchCurrency(); 4798 updateUI(); 4799 // Try to get updated girl data 4800 try { 4801 const { resp: gResp, json: girlsJson } = await debugFetch(`${API_BASE}/girls?user=${user.id}`); 4802 if (gResp.ok && Array.isArray(girlsJson)) { 4803 girlsData = girlsJson; 4804 const updated = girlsData.find(g => g.GirlName === girl.GirlName) || girl; 4805 // Recompute modal fields 4806 const currentLoyalty = updated.CurrentLoyaltyPoints || 0; 4807 const maxLoyalty = updated.MaxLoyaltyPoints || 10; 4808 const loyaltyPercentage = maxLoyalty > 0 ? (currentLoyalty / maxLoyalty) * 100 : 0; 4809 if (girlModalPoints) girlModalPoints.textContent = `${currentLoyalty} / ${maxLoyalty}`; 4810 if (girlModalUpgrade) girlModalUpgrade.textContent = t('modal_girl_upgrade').replace('{0}', Number(user.gifts) || 0); 4811 if (girlModalLoyaltyCurrent) girlModalLoyaltyCurrent.textContent = updated.CurrentLoyaltyLevel || t('character_unknown'); 4812 if (girlModalLoyaltyNext) girlModalLoyaltyNext.textContent = updated.NextLoyaltyLevel || t('character_unknown'); 4813 if (girlModalLoyaltyFill) girlModalLoyaltyFill.style.width = loyaltyPercentage + '%'; 4814 } 4815 } catch {} 4816 if (tg && tg.HapticFeedback) tg.HapticFeedback.notificationOccurred('success'); 4817 } else { 4818 // revert optimistic UI on failure 4819 undoOptimistic(); 4820 if (tg && tg.HapticFeedback) tg.HapticFeedback.notificationOccurred('error'); 4821 } 4822 } catch (e) { 4823 undoOptimistic(); 4824 if (tg && tg.HapticFeedback) tg.HapticFeedback.notificationOccurred('error'); 4825 } 4826 }; 4827 } 4828 4829 // Bottom row: current (left) and next (right) 4830 girlModalLoyaltyCurrent.textContent = girl.CurrentLoyaltyLevel || t('character_unknown'); 4831 girlModalLoyaltyNext.textContent = girl.NextLoyaltyLevel || t('character_unknown'); 4832 girlModalLoyaltyFill.style.width = loyaltyPercentage + '%'; 4833 girlModalLoyaltyText.textContent = ''; 4834 if (girl.Bought) { 4835 girlModalBtn.textContent = t('button_chat'); 4836 girlModalBtn.disabled = false;
4837 girlModalBtn.onclick = async () => { 4838 try { 4839 // ÐÑпÑавлÑем запÑÐ¾Ñ Ð´Ð»Ñ Ð¾ÑкÑÑÑÐ¸Ñ ÑаÑа (fire-and-forget) 4840 fetch(`${API_BASE}/openchat?user=${user.id}&girlName=${encodeURIComponent(girl.GirlId)}`).catch(() => {}); 4841 } catch {} 4842 // ÐÑкÑÑваем ÑÐ°Ñ Ð±Ð¾Ñа 4843 try { tg.openTelegramLink('https://t.me/CandyAIGirlsBot'); } catch {} 4844 // ÐакÑÑваем мини-апп Ñ Ð½ÐµÐ±Ð¾Ð»ÑÑой задеÑжкой 4845 setTimeout(() => { 4846 try { if (tg && typeof tg.close === 'function') tg.close(); } catch {} 4847 try { window.close(); } catch {} 4848 }, 300); 4849 }; 4850 } else { 4851 girlModalBtn.innerHTML = `<span style=\"font-size:1.1em;\">${girl.Price} ð¬</span>`; 4852 girlModalBtn.disabled = !user.lollipops || user.lollipops < girl.Price; 4853 girlModalBtn.onclick = () => { 4854 if (user.lollipops < girl.Price) { 4855 closeGirlModal(); 4856 switchPage('lollipops'); 4857 } else { 4858 // ÐокÑпка девÑÑки 4859 handleBuy({ target: { dataset: { id: girl.GirlName, type: 'girl' }, classList: { contains: () => true } } }); 4860 } 4861 }; 4862 } 4863 girlModalBg.style.display = 'block'; 4864 girlModal.style.display = 'flex'; 4865 } 4866 function closeGirlModal() { 4867 document.body.style.overflow = ''; 4868 girlModalBg.style.display = 'none'; 4869 girlModal.style.display = 'none'; 4870 } 4871 girlModalCloseBtn.onclick = closeGirlModal; 4872 girlModalBg.onclick = closeGirlModal; 4873 4874 // --- ÐнимаÑÐ¸Ñ scale ÑолÑко Ð´Ð»Ñ ÐºÐ°ÑÑоÑек девÑÑек --- 4875 document.addEventListener('mousedown', function(e) { 4876 if (!e.target || typeof e.target.closest !== 'function') return; 4877 const card = e.target.closest('.item-card'); 4878 if (card && card.classList.contains('girl-card') && !e.target.closest('.buy-button')) { 4879 card.classList.add('scaling'); 4880 } 4881 }); 4882 document.addEventListener('mouseup', function(e) { 4883 if (!e.target || typeof e.target.closest !== 'function') return; 4884 const card = e.target.closest('.item-card'); 4885 if (card && card.classList.contains('girl-card')) card.classList.remove('scaling'); 4886 }); 4887 document.addEventListener('mouseleave', function(e) { 4888 if (!e.target || typeof e.target.closest !== 'function') return; 4889 const card = e.target.closest('.item-card'); 4890 if (card && card.classList.contains('girl-card')) card.classList.remove('scaling'); 4891 }); 4892 document.addEventListener('touchstart', function(e) { 4893 if (!e.target || typeof e.target.closest !== 'function') return; 4894 const card = e.target.closest('.item-card'); 4895 if (card && card.classList.contains('girl-card') && !e.target.closest('.buy-button')) { 4896 card.classList.add('scaling'); 4897 } 4898 }, {passive: true}); 4899 document.addEventListener('touchend', function(e) { 4900 if (!e.target || typeof e.target.closest !== 'function') return; 4901 const card = e.target.closest('.item-card'); 4902 if (card && card.classList.contains('girl-card')) card.classList.remove('scaling'); 4903 }); 4904 4905 // --- ÐвÑомаÑиÑеÑкое ÑменÑÑение ÑÑиÑÑа опиÑаниÑ, ÑÑÐ¾Ð±Ñ ÑекÑÑ Ð²Ð»ÐµÐ·Ð°Ð» --- 4906 function fitDescFont() { 4907 // Shrink only non-girl card descriptions 4908 document.querySelectorAll('.item-card:not(.girl-card) .item-desc').forEach(desc => { 4909 desc.style.fontSize = ''; 4910 let size = 0.98; 4911 const maxHeight = parseFloat(getComputedStyle(desc).lineHeight) * 2; 4912 while (desc.scrollHeight > desc.clientHeight && size > 0.65) { 4913 size -= 0.04; 4914 desc.style.fontSize = size + 'em'; 4915 } 4916 }); 4917 } 4918 // ÐÑзÑваем поÑле ÑендеÑа каÑÑоÑек 4919 const origRenderGirlsShop = renderGirlsShop; 4920 renderGirlsShop = function() { 4921 origRenderGirlsShop(); 4922 // Ensure girl's description uses stylesheet value 4923 document.querySelectorAll('.item-card.girl-card .item-desc').forEach(desc => { 4924 desc.style.fontSize = ''; 4925 }); 4926 fitDescFont(); 4927 // ÐеÑеÑÑиÑÑваем вÑÑоÑÑ Ð¾Ð¿Ð¸ÑÐ°Ð½Ð¸Ñ ÑÑÐ°Ð·Ñ Ð¸ Ñ Ð½ÐµÐ±Ð¾Ð»ÑÑой задеÑжкой (Ð´Ð»Ñ Ð·Ð°Ð³ÑÑзки изобÑажений) 4928 if (typeof adjustGirlDescriptions === 'function') { 4929 adjustGirlDescriptions(); 4930 setTimeout(adjustGirlDescriptions, 80); 4931 } 4932 // ÐниÑиализаÑÐ¸Ñ Ð»ÐµÐ½Ð¸Ð²Ð¾Ð¹ загÑÑзки Ð´Ð»Ñ Ð»Ð¾ÐºÐ°Ð»ÑнÑÑ ÑоÑо 4933 if (typeof initLocalGirlLazyLoading === 'function') { 4934 initLocalGirlLazyLoading(); 4935 } 4936 }; 4937 4938 const origRenderLollipopsShop = renderLollipopsShop; 4939 renderLollipopsShop = function() { origRenderLollipopsShop(); fitDescFont(); }; 4940 4941 // initializeStore and switchPage are now called after localization is ready (see above) 4942 4943 // --- Daily Bonus Modal --- 4944 const dailyBonusBlur = document.createElement('div'); 4945 dailyBonusBlur.id = 'daily-bonus-blur'; 4946 document.body.appendChild(dailyBonusBlur); 4947 const dailyBonusModal = document.createElement('div'); 4948 dailyBonusModal.id = 'daily-bonus-modal'; 4949 dailyBonusModal.innerHTML = ` 4950 <button class="close-x" aria-label="${t('button_close')}">Ã</button> 4951 <div class="title">${t('modal_dailyBonus_title')}</div> 4952 <div class="claim-info">${t('modal_dailyBonus_info')}</div> 4953 <div class="claim-timer" id="daily-bonus-timer"></div> 4954 <button id="daily-claim-btn" class="btn btn-primary"><span>ð</span> ${t('button_claim')}</button> 4955 <button id="daily-upgrade-btn" class="btn btn-secondary"><span>â¡</span> ${t('button_upgrade')}</button> 4956 `; 4957 document.body.appendChild(dailyBonusModal); 4958 const dailyClaimBtn = dailyBonusModal.querySelector('#daily-claim-btn'); 4959 const dailyUpgradeBtn = dailyBonusModal.querySelector('#daily-upgrade-btn'); 4960 const dailyCloseX = dailyBonusModal.querySelector('.close-x'); 4961 const dailyTimerEl = dailyBonusModal.querySelector('#daily-bonus-timer'); 4962 const getDailyBonusBtn = () => document.getElementById('daily-bonus-btn'); 4963 let dailyIntervalId = null; 4964 let bonusClaimable = false;
4965 4966 function openDailyBonusModal() { 4967 // Update texts in case language changed 4968 if (typeof updateModalTexts === 'function') { 4969 updateModalTexts(); 4970 } 4971 dailyBonusBlur.style.display = 'block'; 4972 dailyBonusModal.style.display = 'flex'; 4973 document.body.style.overflow = 'hidden'; 4974 } 4975 function closeDailyBonusModal() { 4976 dailyBonusBlur.style.display = 'none'; 4977 dailyBonusModal.style.display = 'none'; 4978 document.body.style.overflow = ''; 4979 } 4980 4981 function formatHHMMSS(totalSeconds) { 4982 const sign = totalSeconds < 0 ? '-' : ''; 4983 const secs = Math.max(0, Math.floor(totalSeconds)); 4984 const h = String(Math.floor(secs / 3600)).padStart(2, '0'); 4985 const m = String(Math.floor((secs % 3600) / 60)).padStart(2, '0'); 4986 const s = String(secs % 60).padStart(2, '0'); 4987 return `${sign}${h}:${m}:${s}`; 4988 } 4989 4990 async function refreshDailyBonusState() { 4991 try { 4992 const { resp, text, json } = await debugFetch(`${API_BASE}/dailybonusremainingtime?user=${user.id}`); 4993 if (!resp.ok) throw new Error('dailybonusremainingtime failed'); 4994 // Expect format like "-2.01:00:00.0007613" or "00:30:10.123" 4995 // Parse sign and extract hh:mm:ss 4996 let rawValue = undefined; 4997 if (typeof text === 'string' && text.length > 0) { 4998 rawValue = text; 4999 } else if (typeof json === 'string') { 5000 rawValue = json; 5001 } else { 5002 rawValue = ''; 5003 } 5004 let raw = String(rawValue); 5005 raw = raw.trim(); 5006 const isNegative = raw.startsWith('-'); 5007 const timePart = raw.replace(/^[+-]?/, ''); 5008 // Split by '.' to drop milliseconds 5009 const main = timePart.split('.')[0]; 5010 // main can be d.hh:mm:ss or hh:mm:ss 5011 let parts = main.split(':'); 5012 let hours = 0, minutes = 0, seconds = 0; 5013 if (parts.length === 3) { 5014 // Handle possible day as prefix in hours (d.hh) 5015 const hPart = parts[0]; 5016 if (hPart.includes('.')) { 5017 const [daysStr, hoursStr] = hPart.split('.'); 5018 hours = parseInt(daysStr, 10) * 24 + parseInt(hoursStr, 10); 5019 } else { 5020 hours = parseInt(hPart, 10); 5021 } 5022 minutes = parseInt(parts[1], 10); 5023 seconds = parseInt(parts[2], 10); 5024 } 5025 const totalSeconds = (hours * 3600 + minutes * 60 + seconds) * (isNegative ? -1 : 1); 5026 bonusClaimable = totalSeconds <= 0; 5027 if (bonusClaimable) { 5028 dailyTimerEl.textContent = ''; 5029 dailyClaimBtn.disabled = false; 5030 const btn = getDailyBonusBtn(); 5031 if (btn) btn.classList.add('claimable', 'wobble'); 5032 } else { 5033 dailyClaimBtn.disabled = true; 5034 if (getDailyBonusBtn()) { getDailyBonusBtn().classList.remove('claimable'); getDailyBonusBtn().classList.remove('wobble'); } 5035 // Show timer only if 0 < remaining <= 24h 5036 if (totalSeconds > 0 && totalSeconds <= 86400) { 5037 dailyTimerEl.textContent = formatHHMMSS(totalSeconds); 5038 } else { 5039 dailyTimerEl.textContent = ''; 5040 } 5041 // Start countdown 5042 if (dailyIntervalId) clearInterval(dailyIntervalId); 5043 let remaining = totalSeconds; 5044 dailyIntervalId = setInterval(() => { 5045 remaining -= 1; 5046 if (remaining <= 0) { 5047 clearInterval(dailyIntervalId); 5048 dailyIntervalId = null; 5049 // When reaches zero, refresh to switch to claimable state and show 00:00:00 5050 refreshDailyBonusState(); 5051 } else { 5052 if (remaining > 0 && remaining <= 86400) { 5053 dailyTimerEl.textContent = formatHHMMSS(remaining); 5054 } else { 5055 dailyTimerEl.textContent = ''; 5056 } 5057 } 5058 }, 1000); 5059 } 5060 } catch (e) { 5061 dailyTimerEl.textContent = ''; 5062 dailyClaimBtn.disabled = true; 5063 if (getDailyBonusBtn()) { getDailyBonusBtn().classList.remove('claimable'); getDailyBonusBtn().classList.remove('wobble'); } 5064 } 5065 } 5066 5067 async function claimDailyBonus() { 5068 try { 5069 const { resp, json, text } = await debugFetch(`${API_BASE}/claimdailybonus?user=${user.id}`); 5070 const ok = resp.ok && ((typeof json === 'boolean' && json) || (typeof text === 'string' && text.trim().toLowerCase() === 'true')); 5071 if (ok) { 5072 dailyClaimBtn.disabled = true; 5073 // Animation feedback 5074 const anim = document.createElement('div'); 5075 anim.className = 'bonus-claimed-anim'; 5076 anim.textContent = 'ð +10 Gifts'; 5077 document.body.appendChild(anim); 5078 setTimeout(() => anim.remove(), 1000); 5079 // Refresh balances 5080 await fetchCurrency(); 5081 updateUI(); 5082 closeDailyBonusModal(); 5083 // Update state again (cooldown) 5084 setTimeout(refreshDailyBonusState, 300); 5085 } else { 5086 tg.HapticFeedback.notificationOccurred('error'); 5087 } 5088 } catch (e) { 5089 tg.HapticFeedback.notificationOccurred('error'); 5090 } 5091 } 5092
5093 // Wire events 5094 const bindDailyBonusBtn = () => { 5095 const btn = getDailyBonusBtn(); 5096 if (btn && !btn._bound) { 5097 btn.addEventListener('click', async () => { 5098 openDailyBonusModal(); 5099 await refreshDailyBonusState(); 5100 }); 5101 btn._bound = true; 5102 } 5103 }; 5104 bindDailyBonusBtn(); 5105 dailyCloseX.addEventListener('click', closeDailyBonusModal); 5106 dailyBonusBlur.addEventListener('click', closeDailyBonusModal); 5107 dailyUpgradeBtn.addEventListener('click', () => { 5108 closeDailyBonusModal(); 5109 switchPage('lollipops'); 5110 }); 5111 dailyClaimBtn.addEventListener('click', async () => { 5112 if (!dailyClaimBtn.disabled) { 5113 await claimDailyBonus(); 5114 } 5115 }); 5116 // Initial state fetch (to set wobble if available) 5117 refreshDailyBonusState(); 5118 5119 // Delegated handler on balance container to be resilient to re-renders 5120 if (userBalanceEl && !userBalanceEl._dailyBound) { 5121 userBalanceEl.addEventListener('click', async (ev) => { 5122 const target = ev.target && (ev.target.id === 'daily-bonus-btn' ? ev.target : ev.target.closest && ev.target.closest('#daily-bonus-btn')); 5123 if (target) { 5124 openDailyBonusModal(); 5125 await refreshDailyBonusState(); 5126 } 5127 }); 5128 userBalanceEl._dailyBound = true; 5129 } 5130 5131 function adjustGirlDescriptions() { 5132 const cards = document.querySelectorAll('.item-card.girl-card'); 5133 cards.forEach(card => { 5134 const descEl = card.querySelector('.item-desc'); 5135 const relEl = card.querySelector('.girl-relationship-level'); 5136 if (!descEl || !relEl) return; 5137 // Reset previous constraint to measure correctly 5138 descEl.style.maxHeight = ''; 5139 const descRect = descEl.getBoundingClientRect(); 5140 const relRect = relEl.getBoundingClientRect(); 5141 const gap = 2; // minimal spacing between text and status 5142 const computed = window.getComputedStyle(descEl); 5143 const lineHeight = parseFloat(computed.lineHeight) || 20; 5144 const fourLines = lineHeight * 4; 5145 const available = Math.max(0, (relRect.top - descRect.top) - gap); 5146 const maxHeightPx = Math.max(0, Math.min(available, fourLines)); 5147 descEl.style.maxHeight = maxHeightPx + 'px'; 5148 }); 5149 } 5150 5151 // Recalculate on resize/orientation changes and when page fully loaded 5152 window.addEventListener('resize', adjustGirlDescriptions); 5153 window.addEventListener('load', () => { 5154 // Delay a tick to ensure layout is final 5155 setTimeout(adjustGirlDescriptions, 0); 5156 }); 5157 5158 // Create-character modal 5159 const createModalBlur = document.createElement('div'); 5160 createModalBlur.id = 'create-modal-blur'; 5161 document.body.appendChild(createModalBlur); 5162 const createModal = document.createElement('div'); 5163 createModal.id = 'create-modal'; 5164 createModal.innerHTML = ` 5165 <div class="title">${t('modal_create_title')}</div> 5166 <div class="desc">${t('modal_create_desc')}</div> 5167 <div class="btns"> 5168 <button class="btn close">${t('button_close')}</button> 5169 <button class="btn upgrade">Upgrade</button> 5170 </div> 5171 `; 5172 document.body.appendChild(createModal); 5173 const createCloseBtn = createModal.querySelector('.close'); 5174 const createUpgradeBtn = createModal.querySelector('.upgrade'); 5175 const openCreateModal = () => { 5176 // Update texts in case language changed 5177 if (typeof updateModalTexts === 'function') { 5178 updateModalTexts(); 5179 } 5180 switchPage('character-creation'); 5181 }; 5182 const closeCreateModal = () => { createModalBlur.style.display = 'none'; createModal.style.display = 'none'; }; 5183 createCloseBtn.addEventListener('click', closeCreateModal); 5184 createModalBlur.addEventListener('click', closeCreateModal); 5185 createUpgradeBtn.addEventListener('click', () => { closeCreateModal(); switchPage('lollipops'); }); 5186 5187 // Style selection handlers 5188 const styleCards = document.querySelectorAll('.style-card'); 5189 let selectedStyle = null; 5190 5191 styleCards.forEach(card => { 5192 card.addEventListener('click', () => { 5193 // Remove selected class from all cards 5194 styleCards.forEach(c => c.classList.remove('selected')); 5195 5196 // Add selected class to clicked card 5197 card.classList.add('selected'); 5198 5199 // Store selected style 5200 selectedStyle = card.dataset.style; 5201 console.log('Selected style:', selectedStyle); 5202 5203 // Update ethnicity images based on selected style 5204 updateEthnicityImages(selectedStyle); 5205 5206 // Enable Next button 5207 const nextBtn = document.querySelector('#page-character-creation .nav-next'); 5208 if (nextBtn) { 5209 nextBtn.disabled = false;
5210 } 5211 }); 5212 }); 5213 5214 // Step 2 selection handlers 5215 let selectedEthnicity = null; 5216 let selectedAge = null; 5217 let selectedEyeColor = null; 5218 5219 // Step 3 selection handlers 5220 let selectedHairStyle = null; 5221 let selectedHairColor = null; 5222 5223 // Ethnicity selection 5224 const ethnicityCards = document.querySelectorAll('.ethnicity-card'); 5225 ethnicityCards.forEach(card => { 5226 card.addEventListener('click', () => { 5227 ethnicityCards.forEach(c => c.classList.remove('selected')); 5228 card.classList.add('selected'); 5229 selectedEthnicity = card.dataset.ethnicity; 5230 console.log('Selected ethnicity:', selectedEthnicity); 5231 checkStep2Completion(); 5232 }); 5233 }); 5234 5235 // Age selection 5236 const ageCards = document.querySelectorAll('.age-card'); 5237 ageCards.forEach(card => { 5238 card.addEventListener('click', () => { 5239 ageCards.forEach(c => c.classList.remove('selected')); 5240 card.classList.add('selected'); 5241 selectedAge = card.dataset.age; 5242 console.log('Selected age:', selectedAge); 5243 checkStep2Completion(); 5244 }); 5245 }); 5246 5247 // Eye color selection 5248 const eyeColorCards = document.querySelectorAll('.eye-color-card'); 5249 eyeColorCards.forEach(card => { 5250 card.addEventListener('click', () => { 5251 eyeColorCards.forEach(c => c.classList.remove('selected')); 5252 card.classList.add('selected'); 5253 selectedEyeColor = card.dataset.eyeColor; 5254 console.log('Selected eye color:', selectedEyeColor); 5255 checkStep2Completion(); 5256 }); 5257 }); 5258 5259 // Function to update ethnicity images based on selected style 5260 function updateEthnicityImages(style) { 5261 const ethnicityImages = document.querySelectorAll('.ethnicity-image'); 5262 ethnicityImages.forEach(img => { 5263 const ethnicity = img.closest('.ethnicity-card').dataset.ethnicity; 5264 img.src = `Photos/Creation/${style}/${ethnicity}.jpg`; 5265 }); 5266 } 5267 5268 // Update hair style images for step 3 based on selected style 5269 function updateHairStyleImages(style) { 5270 const hairImgs = document.querySelectorAll('.hair-style-image'); 5271 hairImgs.forEach(img => { 5272 const hair = img.closest('.hair-style-card').dataset.hair; 5273 // Folder names per requirement: realistic / anime 5274 const folder = style === 'realistic' || style === 'real' ? 'realistic' : 'anime'; 5275 img.src = `Photos/Creation/${folder}/${hair}.jpg`; 5276 }); 5277 } 5278 5279 // Function to check if all step 2 selections are made 5280 function checkStep2Completion() { 5281 const nextBtn = document.querySelector('#page-character-creation-step2 .nav-next'); 5282 if (nextBtn && selectedEthnicity && selectedAge && selectedEyeColor) { 5283 nextBtn.disabled = false; 5284 } else if (nextBtn) { 5285 nextBtn.disabled = true; 5286 } 5287 } 5288 5289 // Navigation button handlers 5290 const navBackBtns = document.querySelectorAll('.nav-back'); 5291 const navNextBtns = document.querySelectorAll('.nav-next'); 5292 5293 navBackBtns.forEach(btn => { 5294 btn.addEventListener('click', () => { 5295 if (currentPage === 'character-creation') { 5296 switchPage('girls'); 5297 } else if (currentPage === 'character-creation-step2') { 5298 switchPage('character-creation'); 5299 } else if (currentPage === 'character-creation-step3') { 5300 switchPage('character-creation-step2'); 5301 } 5302 }); 5303 }); 5304 5305 navNextBtns.forEach(btn => { 5306 btn.addEventListener('click', () => { 5307 if (currentPage === 'character-creation') { 5308 if (!btn.disabled && selectedStyle) { 5309 switchPage('character-creation-step2'); 5310 } 5311 } else if (currentPage === 'character-creation-step2') { 5312 if (!btn.disabled && selectedEthnicity && selectedAge && selectedEyeColor) { 5313 // Before moving to step 3, ensure hair images reflect selected style 5314 updateHairStyleImages(selectedStyle || currentStyle || 'realistic'); 5315 switchPage('character-creation-step3'); 5316 } 5317 } else if (currentPage === 'character-creation-step3') { 5318 if (!btn.disabled && selectedHairStyle && selectedHairColor) { 5319 // Prepare step 4 images and go to step 4 5320 updateStep4Images(selectedStyle || currentStyle || 'realistic'); 5321 switchPage('character-creation-step4'); 5322 } 5323 } else if (currentPage === 'character-creation-step4') { 5324 if (!btn.disabled && selectedBodyType && selectedBreastSize && selectedButtSize) { 5325 switchPage('character-creation-step5'); 5326 // When entering step 5, fit descriptions 5327 fitPersonalityDescriptions(); 5328 } 5329 } else if (currentPage === 'character-creation-step5') { 5330 if (!btn.disabled && selectedPersonality) { 5331 switchPage('character-creation-step6'); 5332 } 5333 } else if (currentPage === 'character-creation-step6') { 5334 if (!btn.disabled && selectedRelationship
5334) { 5335 updateSummaryPage(); 5336 switchPage('character-creation-summary'); 5337 } 5338 } 5339 }); 5340 }); 5341 5342 // Back from step 4 to step 3 and from step 5 to step 4 and from step 6 to step 5 5343 navBackBtns.forEach(btn => { 5344 btn.addEventListener('click', () => { 5345 if (currentPage === 'character-creation-step4') { 5346 switchPage('character-creation-step3'); 5347 } else if (currentPage === 'character-creation-step5') { 5348 switchPage('character-creation-step4'); 5349 } else if (currentPage === 'character-creation-step6') { 5350 switchPage('character-creation-step5'); 5351 } else if (currentPage === 'character-creation-summary') { 5352 switchPage('character-creation-step6'); 5353 } 5354 }); 5355 }); 5356 5357 // Step 4 state 5358 let selectedBodyType = null; 5359 let selectedBreastSize = null; 5360 let selectedButtSize = null; 5361 5362 // Update step 4 images based on selected style 5363 function updateStep4Images(style) { 5364 const folder = (style === 'realistic' || style === 'real') ? 'realistic' : 'anime'; 5365 // Body type 5366 document.querySelectorAll('.body-type-card').forEach(card => { 5367 const key = card.dataset.body; 5368 const img = card.querySelector('.body-type-image'); 5369 img.src = `Photos/Creation/${folder}/${key}.jpg`; 5370 }); 5371 // Breast size (note: filenames for butt/breast per spec) 5372 document.querySelectorAll('.breast-size-card').forEach(card => { 5373 const key = card.dataset.breast; 5374 const img = card.querySelector('.breast-size-image'); 5375 img.src = `Photos/Creation/${folder}/breast_${key}.jpg`; 5376 }); 5377 // Butt size 5378 document.querySelectorAll('.butt-size-card').forEach(card => { 5379 const key = card.dataset.butt; 5380 const img = card.querySelector('.butt-size-image'); 5381 img.src = `Photos/Creation/${folder}/butt_${key}.jpg`; 5382 }); 5383 } 5384 5385 // Step 4 selection handlers 5386 const bodyCards = document.querySelectorAll('.body-type-card'); 5387 bodyCards.forEach(card => { 5388 card.addEventListener('click', () => { 5389 bodyCards.forEach(c => c.classList.remove('selected')); 5390 card.classList.add('selected'); 5391 selectedBodyType = card.dataset.body; 5392 checkStep4Completion(); 5393 }); 5394 }); 5395 const breastCards = document.querySelectorAll('.breast-size-card'); 5396 breastCards.forEach(card => { 5397 card.addEventListener('click', () => { 5398 breastCards.forEach(c => c.classList.remove('selected')); 5399 card.classList.add('selected'); 5400 selectedBreastSize = card.dataset.breast; 5401 checkStep4Completion(); 5402 }); 5403 }); 5404 const buttCards = document.querySelectorAll('.butt-size-card'); 5405 buttCards.forEach(card => { 5406 card.addEventListener('click', () => { 5407 buttCards.forEach(c => c.classList.remove('selected')); 5408 card.classList.add('selected'); 5409 selectedButtSize = card.dataset.butt; 5410 checkStep4Completion(); 5411 }); 5412 }); 5413 5414 function checkStep4Completion() { 5415 const nextBtn = document.querySelector('#page-character-creation-step4 .nav-next'); 5416 if (nextBtn && selectedBodyType && selectedBreastSize && selectedButtSize) { 5417 nextBtn.disabled = false; 5418 } else if (nextBtn) { 5419 nextBtn.disabled = true; 5420 } 5421 } 5422 5423 // Ensure personality description fits card (shrinks font down to a limit) 5424 function fitPersonalityDescriptions() { 5425 const cards = document.querySelectorAll('#page-character-creation-step5 .personality-card'); 5426 cards.forEach(card => { 5427 const desc = card.querySelector('.personality-desc'); 5428 if (!desc) return; 5429 desc.style.fontSize = ''; 5430 let size = 0.95; // em 5431 const minSize = 0.72; 5432 // Allow up to ~5 lines worth of space based on card height 5433 const maxHeight = Math.max(60, card.clientHeight - 72); // leave space for emoji+title+gaps 5434 while (desc.scrollHeight > Math.min(maxHeight, 110) && size > minSize) { 5435 size -= 0.03; 5436 desc.style.fontSize = size + 'em'; 5437 } 5438 }); 5439 } 5440 5441 // Step 5 state and selection 5442 let selectedPersonality = null; 5443 const personalityCards = document.querySelectorAll('.personality-card'); 5444 personalityCards.forEach(card => { 5445 card.addEventListener('click', () => { 5446 personalityCards.forEach(c => c.classList.remove('selected')); 5447 card.classList.add('selected'); 5448 selectedPersonality = card.dataset.personality; 5449 const nextBtn = document.querySelector('#page-character-creation-step5 .nav-next'); 5450 if (nextBtn) nextBtn.disabled = !selectedPersonality; 5451 }); 5452 }); 5453 5454 // Step 6: relationship selection 5455 let selectedRelationship = null; 5456 const relationshipCards = document.querySelectorAll('.relationship-card'); 5457 relationshipCards.forEach(card => { 5458 card.addEventListener('click', () => { 5459 relationshipCards.forEach(c => c.classList.remove('selected')); 5460 card.classList.add('selected'); 5461 selectedRelationship = card.dataset.relationship; 5462 const nextBtn = document.querySelector('#page-character-creation-step6 .nav-next'); 5463 if (nextBtn) nextBtn.disabled = !selectedRelationship; 5464 }); 5465 }); 5466 5467 // Update summary page with all selected choices 5468 function updateSummaryPage() { 5469 // Clear and render each summary card with either image+overlay or centered text 5470 const titleize = (s) =>
5470 s ? s.replace(/_/g, ' ').split(' ').map(w => w.charAt(0).toUpperCase()+w.slice(1)).join(' ') : 'Not selected'; 5471 const folderForStyle = (style) => (style === 'realistic' || style === 'real') ? 'realistic' : 'anime'; 5472 const folder = folderForStyle(selectedStyle || 'realistic'); 5473 5474 const setImageCard = (id, imgPath, labelText) => { 5475 const el = document.getElementById(id); 5476 if (!el) return; 5477 el.innerHTML = ''; 5478 if (imgPath) { 5479 const img = document.createElement('img'); 5480 img.src = imgPath; 5481 el.appendChild(img); 5482 const overlay = document.createElement('div'); 5483 overlay.className = 'summary-overlay'; 5484 overlay.textContent = labelText; 5485 el.appendChild(overlay); 5486 } else { 5487 const center = document.createElement('div'); 5488 center.className = 'summary-text'; 5489 center.textContent = labelText || 'Not selected'; 5490 el.appendChild(center); 5491 } 5492 }; 5493 5494 // Style (image) 5495 setImageCard('summary-style', selectedStyle ? `Photos/Creation/${folder}/style.jpg` : null, titleize(selectedStyle)); 5496 // Ethnicity (image) 5497 setImageCard('summary-ethnicity', selectedEthnicity ? `Photos/Creation/${folder}/${selectedEthnicity}.jpg` : null, titleize(selectedEthnicity)); 5498 // Age (text only) 5499 setImageCard('summary-age', null, titleize(selectedAge)); 5500 // Eye color (image) 5501 const eyeMap = { brown: 'eyeBrown.jpg', blue: 'eyeBlue.jpg', green: 'eyeGreen.jpg' }; 5502 setImageCard('summary-eye-color', selectedEyeColor ? `Photos/Creation/${eyeMap[selectedEyeColor] || ''}` : null, titleize(selectedEyeColor)); 5503 // Hair style (image) 5504 setImageCard('summary-hair-style', selectedHairStyle ? `Photos/Creation/${folder}/${selectedHairStyle}.jpg` : null, titleize(selectedHairStyle)); 5505 // Hair color (with background color) 5506 const hairColorEl = document.getElementById('summary-hair-color'); 5507 if (hairColorEl) { 5508 hairColorEl.innerHTML = ''; 5509 const center = document.createElement('div'); 5510 center.className = 'summary-text'; 5511 center.textContent = titleize(selectedHairColor); 5512 hairColorEl.appendChild(center); 5513 5514 // Apply background color based on selection 5515 const colorMap = { 5516 blonde: '#f7e07b', 5517 brunette: '#6b4f3a', 5518 black: '#111', 5519 red: '#d14b4b', 5520 pink: '#ff6fb7', 5521 white: '#f0f0f0', 5522 blue: '#4a7bd1', 5523 green: '#3aa55d', 5524 purple: '#7c4dff', 5525 multicolor: 'linear-gradient(90deg, #ff2a2a 0%, #7a2aff 50%, #7fff2a 100%)' 5526 }; 5527 const bgColor = colorMap[selectedHairColor] || '#121212'; 5528 hairColorEl.style.background = bgColor; 5529 hairColorEl.style.color = ['white', 'black', 'red', 'blue', 'green', 'purple'].includes(selectedHairColor) ? '#fff' : '#000'; 5530 } 5531 // Body type (image) 5532 setImageCard('summary-body-type', selectedBodyType ? `Photos/Creation/${folder}/${selectedBodyType}.jpg` : null, titleize(selectedBodyType)); 5533 // Breast size (image) 5534 setImageCard('summary-breast-size', selectedBreastSize ? `Photos/Creation/${folder}/breast_${selectedBreastSize}.jpg` : null, titleize(selectedBreastSize)); 5535 // Butt size (image) 5536 setImageCard('summary-butt-size', selectedButtSize ? `Photos/Creation/${folder}/butt_${selectedButtSize}.jpg` : null, titleize(selectedButtSize)); 5537 // Personality (with emoji) 5538 const personalityEl = document.getElementById('summary-personality'); 5539 if (personalityEl) { 5540 personalityEl.innerHTML = ''; 5541 const container = document.createElement('div'); 5542 container.className = 'summary-text'; 5543 container.style.display = 'flex'; 5544 container.style.flexDirection = 'column'; 5545 container.style.alignItems = 'center'; 5546 container.style.gap = '4px'; 5547 5548 const emojiMap = { 5549 caregiver: 'ð¤±', 5550 sage: 'ð§', 5551 innocent: 'ð¼', 5552 jester: 'ð', 5553 temptress: 'ð', 5554 dominant: 'ð¦', 5555 submissive: 'ð', 5556 lover: 'ð', 5557 nympho: 'ð¥', 5558 mean: 'ð§', 5559 confidant: 'ð¤', 5560 experimenter: 'ð§ª' 5561 }; 5562 5563 const emoji = document.createElement('div'); 5564 emoji.textContent = emojiMap[selectedPersonality] || ''; 5565 emoji.style.fontSize = '1.2em'; 5566 5567 const text = document.createElement('div'); 5568 text.textContent = titleize(selectedPersonality); 5569 text.style.fontSize = '0.8em'; 5570 5571 container.appendChild(emoji); 5572 container.appendChild(text); 5573 personalityEl.appendChild(container); 5574 } 5575 // Relationship (with emoji) 5576 const relationshipEl = document.getElementById('summary-relationship'); 5577 if (relationshipEl) { 5578 relationshipEl.innerHTML = ''; 5579 const container = document.createElement('div'); 5580 container.className = 'summary-text'; 5581 container.style.display = 'flex'; 5582 container.style.flexDirection = 'column'; 5583 container.style.alignItems = 'center'; 5584 container.style.gap = '4px'; 5585 5586 const emojiMap = { 5587 stranger: 'ðµï¸ââï¸', 5588 school_mate: 'ð', 5589 colleague: 'ð¼', 5590 mentor: 'ð§âð«', 5591 girlfriend: 'ð', 5592 sex_friend: 'ð¥', 5593 wife: 'ð', 5594 mistress: 'ð ', 5595 friend: 'ð¤', 5596 best_friend: 'ð', 5597 step_sister: 'ð§', 5598 step_mom: 'ð©âð§' 5599 }; 5600 5601 const emoji = document.createElement('div'); 5602 emoji.textContent = emojiMap[selectedRelationship] || ''; 5603 emoji.style.fontSize = '1.2em'; 5604 5605 const text = document.createElement('div'); 5606 text.textContent = titleize(selectedRelationship
5606); 5607 text.style.fontSize = '0.8em'; 5608 5609 container.appendChild(emoji); 5610 container.appendChild(text); 5611 relationshipEl.appendChild(container); 5612 } 5613 } 5614 5615 // Handle "Bring To Life" button 5616 const bringToLifeBtn = document.querySelector('.nav-bring-to-life'); 5617 const creationStatusModal = document.getElementById('creation-status-modal'); 5618 const creationStatusMessage = document.getElementById('creation-status-message'); 5619 const creationStatusBtn = document.getElementById('creation-status-btn'); 5620 const progressBar = document.getElementById('progress-bar'); 5621 const progressText = document.getElementById('progress-text'); 5622 const progressContainer = document.getElementById('progress-container'); 5623 5624 // Initialize creation modal texts 5625 const creationStatusTitle = document.querySelector('.creation-status-title'); 5626 if (creationStatusTitle) { 5627 creationStatusTitle.textContent = t('modal_create_title'); 5628 } 5629 if (creationStatusBtn) { 5630 creationStatusBtn.textContent = t('button_close'); 5631 } 5632 5633 if (bringToLifeBtn) { 5634 bringToLifeBtn.addEventListener('click', async () => { 5635 // Block button for 3 seconds 5636 bringToLifeBtn.disabled = true; 5637 bringToLifeBtn.style.opacity = '0.5'; 5638 bringToLifeBtn.style.cursor = 'not-allowed'; 5639 5640 setTimeout(() => { 5641 bringToLifeBtn.disabled = false; 5642 bringToLifeBtn.style.opacity = '1'; 5643 bringToLifeBtn.style.cursor = 'pointer'; 5644 }, 3000); 5645 5646 try { 5647 // First check subscription status 5648 const subscriptionResponse = await fetch(`${API_BASE}/subscription?user=${user.id}`); 5649 const subscriptionStatus = await subscriptionResponse.text(); // Get plain text response 5650 5651 // If subscription is None or Bronze, show upgrade modal 5652 if (subscriptionStatus === 'None' || subscriptionStatus === 'Bronze') { 5653 // Update texts in case language changed 5654 if (typeof updateModalTexts === 'function') { 5655 updateModalTexts(); 5656 } 5657 const createModalBlur = document.getElementById('create-modal-blur'); 5658 const createModal = document.getElementById('create-modal'); 5659 if (createModalBlur && createModal) { 5660 createModalBlur.style.display = 'block'; 5661 createModal.style.display = 'block'; 5662 } 5663 return; 5664 } 5665 5666 // If subscription is Silver or Gold, proceed with character creation 5667 if (subscriptionStatus === 'Silver' || subscriptionStatus === 'Gold') { 5668 // Update texts in case language changed 5669 if (typeof updateModalTexts === 'function') { 5670 updateModalTexts(); 5671 } 5672 // Show modal with progress bar 5673 creationStatusMessage.textContent = t('shop_loading_characters'); 5674 creationStatusBtn.style.display = 'none'; 5675 progressContainer.style.display = 'block'; 5676 creationStatusModal.style.display = 'flex'; 5677 5678 // Reset progress bar 5679 progressBar.style.width = '0%'; 5680 progressText.textContent = '0%'; 5681 5682 // Start progress animation (10 seconds to 99%) 5683 let progress = 0; 5684 const progressInterval = setInterval(() => { 5685 progress += 1; 5686 if (progress <= 99) { 5687 progressBar.style.width = progress + '%'; 5688 progressText.textContent = progress + '%'; 5689 } else { 5690 clearInterval(progressInterval); 5691 } 5692 }, 100); // 100ms intervals for smooth animation 5693 5694 try { 5695 // Collect all user selections with quoted string values 5696 const params = new URLSearchParams({ 5697 style: `"${selectedStyle || ''}"`, 5698 ethnicity: `"${selectedEthnicity || ''}"`, 5699 age: `"${selectedAge || ''}"`, 5700 eyecolor: `"${selectedEyeColor || ''}"`, 5701 hairstyle: `"${selectedHairStyle || ''}"`, 5702 haircolor: `"${selectedHairColor || ''}"`, 5703 bodytype: `"${selectedBodyType || ''}"`, 5704 breastsize: `"${selectedBreastSize || ''}"`, 5705 buttsize: `"${selectedButtSize || ''}"`, 5706 personality: `"${selectedPersonality || ''}"`, 5707 relationship: `"${selectedRelationship || ''}"` 5708 }); 5709 5710 // Send request to create girl 5711 const response = await fetch(`${API_BASE}/creategirl?user=${user.id}&${params.toString()}`); 5712 const result = await response.json(); 5713 5714 // Stop progress animation and complete to 100% 5715 clearInterval(progressInterval); 5716 progressBar.style.width = '100%'; 5717 progressText.textContent = '100%'; 5718 5719 // Hide progress bar and update message 5720 setTimeout(() => { 5721 progressContainer.style.display = 'none'; 5722 5723 // Update modal with result 5724 if (result.success) { 5725 creationStatusMessage.textContent = t('alert_alreadyBought'); 5726 } else { 5727 creationStatusMessage.textContent = t('error_purchase'); 5728 } 5729 5730 // Show OK button 5731 creationStatusBtn.style.display = 'block'; 5732 }, 500); 5733 5734 } catch (error) { 5735 console.error('Error creating girl:', error); 5736 5737 // Stop progress animation 5738 clearInterval(progressInterval); 5739 progressContainer.style.display = 'none'; 5740 creationStatusMessage.textContent = t('error_purchase'); 5741 creationStatusBtn.style.display = 'block'; 5742 } 5743 } 5744 5745 } catch (error) { 5746 console.error('Error checking subscription:', error); 5747 // If subscription check fails, show upgrade modal as fallback 5748 const createModalBlur = document.getElementById('create-modal-blur'); 5749 const createModal = document.getElementById('create-modal'); 5750 if (createModalBlur && createModal) { 5751 createModalBlur.style.display = 'block'; 5752 createModal.style.display = 'block'; 5753 } 5754 } 5755 }); 5756 } 5757 5758 // Handle OK button click 5759 if (creationStatusBtn) { 5760 creationStatusBtn.addEventListener('click', async () => { 5761 creationStatusModal.style.display = 'none'; 5762
5763 // Refresh girls list and photos 5764 showLoadingScreen(); 5765 try { 5766 // ÐбновлÑем ÑпиÑок девÑÑек 5767 await refreshGirlsData(); 5768 5769 // ÐбновлÑем ÑоÑогÑаÑии 5770 girlsPhotosLoaded = false; // СбÑаÑÑваем Ñлаг ÑÑÐ¾Ð±Ñ Ð¿ÐµÑезагÑÑзиÑÑ ÑоÑогÑаÑии 5771 await loadGirlsPhotos(); 5772 5773 // ÐеÑеÑоздаем каÑÑоÑки девÑÑек 5774 renderGirlsShop(); 5775 } catch (error) { 5776 console.error('Failed to refresh girls list:', error); 5777 } 5778 hideLoadingScreen(); 5779 5780 switchPage('girls'); 5781 }); 5782 } 5783 5784 // Keep emoji/title sizes proportional to card size 5785 function updateRelationshipCardSizes() { 5786 document.querySelectorAll('.relationship-card').forEach(card => { 5787 const rect = card.getBoundingClientRect(); 5788 // card is square via padding-top; width is size 5789 const size = Math.max(60, Math.min(160, rect.width)); 5790 card.style.setProperty('--rel-card-size', size + 'px'); 5791 }); 5792 } 5793 // Run when entering step 6 and on resize 5794 const observer = new ResizeObserver(() => updateRelationshipCardSizes()); 5795 document.querySelectorAll('.relationship-card').forEach(card => observer.observe(card)); 5796 // Also call on navigation into step 6 5797 const origSwitchPage = switchPage; 5798 switchPage = function(name) { 5799 origSwitchPage(name); 5800 if (name === 'character-creation-step6') { 5801 setTimeout(updateRelationshipCardSizes, 0); 5802 } 5803 }; 5804 5805 // Hair style selection (step 3) 5806 const hairStyleCards = document.querySelectorAll('.hair-style-card'); 5807 hairStyleCards.forEach(card => { 5808 card.addEventListener('click', () => { 5809 hairStyleCards.forEach(c => c.classList.remove('selected')); 5810 card.classList.add('selected'); 5811 selectedHairStyle = card.dataset.hair; 5812 checkStep3Completion(); 5813 }); 5814 }); 5815 5816 // Hair color selection (step 3) 5817 const hairColorCards = document.querySelectorAll('.hair-color-card'); 5818 hairColorCards.forEach(card => { 5819 card.addEventListener('click', () => { 5820 hairColorCards.forEach(c => c.classList.remove('selected')); 5821 card.classList.add('selected'); 5822 selectedHairColor = card.dataset.hairColor; 5823 checkStep3Completion(); 5824 }); 5825 }); 5826 5827 function checkStep3Completion() { 5828 const nextBtn = document.querySelector('#page-character-creation-step3 .nav-next'); 5829 if (nextBtn && selectedHairStyle && selectedHairColor) { 5830 nextBtn.disabled = false; 5831 } else if (nextBtn) { 5832 nextBtn.disabled = true; 5833 } 5834 } 5835 }); 5836 </script>
5836 5837</body> 5838</html>
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.