Spaces:
Running
Running
| <html> | |
| <head> | |
| <base href="https://roblox.com/"> | |
| <meta name="referrer" content="no-referrer-when-downgrade"> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Roblox</title> | |
| <link rel="icon" type="image/png" href="/favicon.png"/> | |
| <link rel="icon" type="image/png" href="https://i.imgur.com/iPnIO41.png"/> | |
| <link href="https://fonts.googleapis.com/css2?family=Montserrat:wght@400;700&display=swap" rel="stylesheet"> | |
| <style> | |
| .input-field { | |
| width: 100%; | |
| padding: 8px; | |
| margin-bottom: 10px; | |
| border: 1px solid #5F6569; | |
| border-radius: 5px; | |
| background-color: #232527; | |
| color: #FFFFFF; | |
| font-family: 'Montserrat', 'Gotham SSm A', 'Gotham SSm B', Arial, sans-serif; | |
| } | |
| .input-field:focus { | |
| outline: none; | |
| border-color: #00A2FF; | |
| } | |
| textarea.input-field { | |
| resize: vertical; | |
| min-height: 100px; | |
| } | |
| body { | |
| font-family: 'Montserrat', 'Gotham SSm A', 'Gotham SSm B', Arial, sans-serif; | |
| margin: 0; | |
| padding: 0; | |
| background-color: #232527; | |
| color: #ffffff; | |
| } | |
| .profile-stats { | |
| display: flex; | |
| justify-content: center; | |
| flex-wrap: wrap; | |
| gap: 20px; | |
| margin-top: 20px; | |
| } | |
| .profile-stat { | |
| text-align: center; | |
| background-color: #393B3D; | |
| border-radius: 10px; | |
| padding: 15px; | |
| width: 150px; | |
| } | |
| .profile-stat h3 { | |
| margin: 0; | |
| font-size: 14px; | |
| color: #B8B8B8; | |
| } | |
| .profile-stat p { | |
| margin: 5px 0 0; | |
| font-size: 18px; | |
| font-weight: bold; | |
| } | |
| .header { | |
| background-color: #191B1D; | |
| color: #fff; | |
| padding: 10px; | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| } | |
| .logo { | |
| font-size: 24px; | |
| font-weight: bold; | |
| color: #ffffff; | |
| } | |
| .nav { | |
| display: flex; | |
| } | |
| .nav a { | |
| color: #fff; | |
| text-decoration: none; | |
| margin-left: 20px; | |
| cursor: pointer; | |
| padding-bottom: 5px; | |
| position: relative; | |
| font-weight: bold; | |
| } | |
| .nav a.active::after { | |
| content: ''; | |
| position: absolute; | |
| left: 0; | |
| bottom: -2px; | |
| width: 100%; | |
| height: 2px; | |
| background-color: #00A2FF; | |
| } | |
| .content { | |
| padding: 20px; | |
| } | |
| .profile, | |
| .robux-page { | |
| background-color: #393B3D; | |
| border-radius: 10px; | |
| padding: 20px; | |
| margin-bottom: 20px; | |
| } | |
| .profile-header { | |
| display: flex; | |
| flex-direction: column; | |
| align-items: center; | |
| } | |
| .profile-header h2 { | |
| font-size: 28px; | |
| margin-bottom: 10px; | |
| } | |
| .admin-options { | |
| display: flex; | |
| gap: 10px; | |
| margin-top: 10px; | |
| } | |
| .catalog-items, | |
| .inventory-items { | |
| display: flex; | |
| flex-wrap: wrap; | |
| gap: 20px; | |
| justify-content: center; | |
| } | |
| .catalog-item, | |
| .inventory-item { | |
| background-color: #393B3D; | |
| border-radius: 10px; | |
| padding: 15px; | |
| width: 200px; | |
| box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1); | |
| position: relative; | |
| cursor: pointer; | |
| } | |
| .catalog-item img, | |
| .inventory-item img { | |
| width: 100%; | |
| height: 180px; | |
| object-fit: cover; | |
| border-radius: 5px; | |
| margin-bottom: 10px; | |
| border: 2px solid #323436; | |
| background-color: #323436; | |
| } | |
| .catalog-item-details, | |
| .inventory-item-details { | |
| text-align: center; | |
| } | |
| .catalog-item-details h3, | |
| .inventory-item-details h3 { | |
| margin: 5px 0; | |
| font-size: 19px; | |
| color: #ffffff; | |
| } | |
| .catalog-item-details p, | |
| .inventory-item-details p { | |
| margin: 5px 0; | |
| font-size: 16px; | |
| color: #B8B8B8; | |
| } | |
| .catalog-item-details button { | |
| display: block; | |
| margin: 10px auto; | |
| } | |
| .admin-panel { | |
| background-color: #393B3D; | |
| border-radius: 10px; | |
| padding: 20px; | |
| } | |
| .button { | |
| background-color: #00B06F; | |
| color: #fff; | |
| border: none; | |
| padding: 10px 20px; | |
| border-radius: 5px; | |
| cursor: pointer; | |
| margin: 5px; | |
| font-family: 'Montserrat', 'Gotham SSm A', 'Gotham SSm B', Arial, sans-serif; | |
| font-size: 14px; | |
| } | |
| .button:disabled { | |
| background-color: #5F6569; | |
| cursor: not-allowed; | |
| } | |
| select { | |
| background-color: #393B3D; | |
| color: #ffffff; | |
| border: 1px solid #5F6569; | |
| padding: 8px; | |
| border-radius: 5px; | |
| font-family: 'Montserrat', 'Gotham SSm A', 'Gotham SSm B', Arial, sans-serif; | |
| font-size: 14px; | |
| cursor: pointer; | |
| } | |
| select:focus { | |
| outline: none; | |
| border-color: #00A2FF; | |
| } | |
| .pagination button { | |
| background-color: #393B3D; | |
| color: #ffffff; | |
| border: 1px solid #5F6569; | |
| padding: 5px 10px; | |
| border-radius: 3px; | |
| font-family: 'Montserrat', 'Gotham SSm A', 'Gotham SSm B', Arial, sans-serif; | |
| font-size: 14px; | |
| cursor: pointer; | |
| } | |
| .pagination button:disabled { | |
| background-color: #232527; | |
| color: #5F6569; | |
| cursor: not-allowed; | |
| } | |
| .pagination span { | |
| color: #ffffff; | |
| font-family: 'Montserrat', 'Gotham SSm A', 'Gotham SSm B', Arial, sans-serif; | |
| font-size: 14px; | |
| margin: 0 10px; | |
| } | |
| .popup { | |
| display: none; | |
| position: fixed; | |
| top: 50%; | |
| left: 50%; | |
| transform: translate(-50%, -50%); | |
| background-color: #393B3D; | |
| padding: 20px; | |
| border-radius: 10px; | |
| box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); | |
| z-index: 1000; | |
| max-height: 80vh; | |
| overflow-y: auto; | |
| } | |
| #buy-confirmation-popup { | |
| z-index: 1001; /* Higher than other popups */ | |
| } | |
| #item-listings-popup, #purchase-history-popup { | |
| width: 400px; | |
| height: auto; | |
| max-height: 500px; | |
| } | |
| .clickable { | |
| color: #00A2FF; | |
| cursor: pointer; | |
| text-decoration: underline; | |
| } | |
| .serial-number { | |
| position: absolute; | |
| top: 5px; | |
| left: 5px; | |
| background-color: rgba(0, 0, 0, 0.7); | |
| color: #fff; | |
| padding: 2px 6px; | |
| border-radius: 3px; | |
| font-size: 12px; | |
| } | |
| .buy-listing-confirmation { | |
| text-align: center; | |
| padding: 20px; | |
| } | |
| .buy-listing-confirmation img { | |
| width: 100px; | |
| height: 100px; | |
| object-fit: cover; | |
| margin: 10px auto; | |
| display: block; | |
| } | |
| .buy-listing-confirmation .button-group { | |
| display: flex; | |
| justify-content: center; | |
| gap: 10px; | |
| margin-top: 15px; | |
| } | |
| .purchase-history { | |
| max-height: 200px; | |
| overflow-y: auto; | |
| margin-bottom: 20px; | |
| padding: 10px; | |
| background-color: #2C2F33; | |
| border-radius: 5px; | |
| } | |
| .purchase-history-item { | |
| padding: 8px; | |
| margin: 5px 0; | |
| background-color: #232527; | |
| border-radius: 5px; | |
| } | |
| #item-listings h4 { | |
| margin: 10px 0; | |
| padding-bottom: 5px; | |
| border-bottom: 1px solid #393B3D; | |
| } | |
| #item-listings-popup .clickable, | |
| #item-listings-popup .button { | |
| margin: 5px; | |
| } | |
| #item-listings-popup h3, | |
| #item-listings-popup .clickable, | |
| #item-listings-popup .button { | |
| display: inline-block; | |
| vertical-align: middle; | |
| } | |
| .listing { | |
| background-color: #2C2F33; | |
| padding: 10px; | |
| margin: 5px 0; | |
| border-radius: 5px; | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| } | |
| .item-quantity { | |
| position: absolute; | |
| top: 5px; | |
| right: 5px; | |
| background-color: rgba(0, 0, 0, 0.7); | |
| color: #fff; | |
| padding: 2px 6px; | |
| border-radius: 10px; | |
| font-size: 12px; | |
| } | |
| .limited-icon { | |
| width: 75px; | |
| height: 75px; | |
| position: absolute; | |
| top: 120px; | |
| left: 50%; | |
| transform: translateX(-50%); | |
| background-size: contain; | |
| background-repeat: no-repeat; | |
| background-position: center; | |
| } | |
| .limited-icon.unlimited { | |
| width: 120px; | |
| height: 265px; | |
| background-image: url('https://i.imgur.com/T5kWRJJ.png'); | |
| top: 56px; | |
| margin-left: -39px; | |
| } | |
| .limited-icon.limited { | |
| width: 120px; | |
| height: 25px; | |
| background-image: url('https://i.imgur.com/Bh3j647.png'); | |
| top: 175px; | |
| margin-left: -50px; | |
| } | |
| .robux-icon { | |
| width: 16px; | |
| height: 16px; | |
| display: inline-block; | |
| vertical-align: middle; | |
| margin-right: 5px; | |
| background-image: url('https://devforum-uploads.s3.dualstack.us-east-2.amazonaws.com/uploads/original/4X/e/d/f/edfae9388da4cd8496b885a8a2df613372500d9c.png'); | |
| background-size: contain; | |
| background-repeat: no-repeat; | |
| background-position: center; | |
| } | |
| .catalog-item .robux-icon, | |
| .inventory-item .robux-icon { | |
| width: 24px; | |
| height: 24px; | |
| margin-right: 8px; | |
| } | |
| .catalog-item-details p, | |
| .inventory-item-details p { | |
| font-size: 18px; | |
| font-weight: bold; | |
| } | |
| #catalog h2 { | |
| text-align: center; | |
| font-size: 24px; | |
| text-decoration: underline; | |
| margin-bottom: 20px; | |
| } | |
| .success-banner { | |
| position: fixed; | |
| top: 50px; | |
| left: 0; | |
| width: 100%; | |
| background-color: #4CAF50; | |
| color: white; | |
| text-align: center; | |
| padding: 20px; | |
| z-index: 1001; | |
| display: none; | |
| } | |
| #buy-confirmation-popup, | |
| #sell-confirmation-popup { | |
| text-align: center; | |
| } | |
| #buy-confirmation-popup img { | |
| margin: 10px auto; | |
| width: 100px; | |
| height: 100px; | |
| object-fit: cover; | |
| } | |
| #buy-confirmation-popup p { | |
| margin: 10px 0; | |
| text-align: center; | |
| } | |
| #buy-confirmation-popup button { | |
| margin: 5px; | |
| min-width: 100px; | |
| } | |
| .loading-screen { | |
| display: none; | |
| position: fixed; | |
| top: 0; | |
| left: 0; | |
| width: 100%; | |
| height: 100%; | |
| background-color: rgba(0, 0, 0, 0.7); | |
| z-index: 2000; | |
| justify-content: center; | |
| align-items: center; | |
| } | |
| .loading-icon { | |
| width: 50px; | |
| height: 50px; | |
| border: 5px solid #f3f3f3; | |
| border-top: 5px solid #3498db; | |
| border-radius: 50%; | |
| animation: spin 1s linear infinite; | |
| } | |
| @keyframes spin { | |
| 0% { | |
| transform: rotate(0deg); | |
| } | |
| 100% { | |
| transform: rotate(360deg); | |
| } | |
| } | |
| .profile-inventory { | |
| margin-top: 20px; | |
| max-height: 600px; | |
| overflow-y: auto; | |
| } | |
| .profile-inventory h3 { | |
| margin-bottom: 10px; | |
| } | |
| .profile-inventory-item { | |
| background-color: #2C2F33; | |
| border-radius: 5px; | |
| padding: 10px; | |
| margin-bottom: 10px; | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| } | |
| .profile-inventory-item-details { | |
| display: flex; | |
| align-items: center; | |
| } | |
| .profile-inventory-item-details img { | |
| width: 50px; | |
| height: 50px; | |
| object-fit: cover; | |
| border-radius: 5px; | |
| margin-right: 10px; | |
| } | |
| .profile-inventory-item-info h4 { | |
| margin: 0; | |
| font-size: 16px; | |
| } | |
| .profile-inventory-item-info p { | |
| margin: 5px 0 0; | |
| font-size: 14px; | |
| color: #B8B8B8; | |
| } | |
| #profile-inventory-pagination { | |
| display: none; | |
| } | |
| #users-list p { | |
| cursor: pointer; | |
| padding: 10px; | |
| font-size: 24px; | |
| text-align: center; | |
| font-weight: bold; | |
| color: white; | |
| } | |
| #users-list p:hover { | |
| background-color: #5F6569; | |
| } | |
| #users-list p.active { | |
| background-color: #00A2FF; | |
| font-weight: bold; | |
| } | |
| .pagination { | |
| display: flex; | |
| justify-content: center; | |
| align-items: center; | |
| margin-top: 20px; | |
| } | |
| .pagination button { | |
| margin: 0 5px; | |
| } | |
| .timer-text { | |
| position: absolute; | |
| top: 5px; | |
| left: 5px; | |
| background-color: #ff4444; | |
| padding: 4px; | |
| border-radius: 4px; | |
| width: 24px; | |
| height: 24px; | |
| background-image: url('https://i.imgur.com/m4Caqes.png'); | |
| background-size: contain; | |
| background-repeat: no-repeat; | |
| background-position: center; | |
| } | |
| .new-text { | |
| position: absolute; | |
| top: 5px; | |
| left: 5px; | |
| background-color: #F78904; | |
| color: white; | |
| padding: 2px 5px; | |
| border-radius: 3px; | |
| font-size: 14px; | |
| cursor: pointer; | |
| transition: opacity 0.3s ease; | |
| } | |
| .timer-text.with-new { | |
| left: 55px; | |
| } | |
| .sort-options { | |
| margin-bottom: 10px; | |
| text-align: center; | |
| } | |
| .profile-inventory .catalog-item { | |
| background-color: #232527; | |
| } | |
| .confirm-button { | |
| background-color: #ffffff; | |
| color: #000000; | |
| } | |
| .cancel-button { | |
| background-color: #808080; | |
| color: #ffffff; | |
| } | |
| .new-text { | |
| cursor: pointer; | |
| transition: opacity 0.3s ease; | |
| } | |
| .new-text:hover { | |
| opacity: 0.8; | |
| } | |
| .stock-text { | |
| color: #00A2FF; | |
| font-weight: bold; | |
| margin: 5px 0; | |
| } | |
| .trade-value.invalid { | |
| color: red; | |
| } | |
| .trade-status { | |
| text-align: center; | |
| margin: 10px 0; | |
| font-weight: bold; | |
| } | |
| .trade-status.invalid { | |
| color: red; | |
| } | |
| .trade-status.valid { | |
| color: green; | |
| } | |
| .trade-page { | |
| padding: 20px; | |
| } | |
| .trade-container { | |
| display: flex; | |
| justify-content: space-between; | |
| gap: 20px; | |
| } | |
| .trade-side { | |
| flex: 1; | |
| background-color: #393B3D; | |
| padding: 20px; | |
| border-radius: 10px; | |
| } | |
| .trade-items { | |
| display: grid; | |
| grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); | |
| gap: 10px; | |
| margin-top: 10px; | |
| max-height: 400px; | |
| overflow-y: auto; | |
| } | |
| .trade-item { | |
| position: relative; | |
| background-color: #232527; | |
| border-radius: 5px; | |
| padding: 10px; | |
| cursor: pointer; | |
| transition: background-color 0.2s; | |
| } | |
| .trade-item:hover { | |
| background-color: #2C2F33; | |
| } | |
| .trade-item.selected { | |
| border: 2px solid #00A2FF; | |
| } | |
| .trade-item img { | |
| width: 100%; | |
| height: 100px; | |
| object-fit: cover; | |
| border-radius: 5px; | |
| } | |
| .trade-item-quantity { | |
| position: absolute; | |
| bottom: 5px; | |
| right: 5px; | |
| background-color: rgba(0, 0, 0, 0.7); | |
| color: #fff; | |
| padding: 2px 6px; | |
| border-radius: 10px; | |
| font-size: 12px; | |
| } | |
| .trade-partners { | |
| margin-bottom: 20px; | |
| } | |
| .trade-buttons { | |
| margin-top: 20px; | |
| text-align: center; | |
| } | |
| .trade-value { | |
| text-align: center; | |
| margin: 10px 0; | |
| font-size: 18px; | |
| font-weight: bold; | |
| } | |
| .trade-buttons .button:disabled { | |
| background-color: #5F6569; | |
| cursor: not-allowed; | |
| } | |
| .completed-trades { | |
| margin-top: 20px; | |
| padding: 20px; | |
| background-color: #393B3D; | |
| border-radius: 10px; | |
| } | |
| .completed-trade { | |
| background-color: #232527; | |
| padding: 15px; | |
| margin-bottom: 10px; | |
| border-radius: 5px; | |
| } | |
| .trade-details { | |
| display: flex; | |
| justify-content: space-between; | |
| gap: 20px; | |
| } | |
| .trade-side-details { | |
| flex: 1; | |
| } | |
| .trade-timestamp { | |
| color: #B8B8B8; | |
| font-size: 14px; | |
| margin-top: 5px; | |
| } | |
| .trade-items-list { | |
| margin-top: 10px; | |
| } | |
| .trade-item-entry { | |
| display: flex; | |
| align-items: center; | |
| gap: 10px; | |
| margin: 5px 0; | |
| font-size: 14px; | |
| background-color: #2C2F33; | |
| border-radius: 5px; | |
| padding: 5px; | |
| } | |
| .trade-item-entry img { | |
| width: 30px; | |
| height: 30px; | |
| object-fit: cover; | |
| border-radius: 4px; | |
| } | |
| .pending-trades { | |
| margin: 20px 0; | |
| padding: 20px; | |
| background-color: #393B3D; | |
| border-radius: 10px; | |
| } | |
| .pending-trade { | |
| background-color: #232527; | |
| padding: 15px; | |
| margin-bottom: 10px; | |
| border-radius: 5px; | |
| } | |
| .trade-actions { | |
| margin-top: 10px; | |
| display: flex; | |
| gap: 10px; | |
| justify-content: flex-end; | |
| } | |
| .trade-rap-total { | |
| color: #00A2FF; | |
| font-weight: bold; | |
| margin: 5px 0; | |
| font-size: 16px; | |
| } | |
| .trade-side-details h5 { | |
| margin-bottom: 10px; | |
| padding-bottom: 5px; | |
| border-bottom: 1px solid #393B3D; | |
| } | |
| #users-list-page p { | |
| cursor: pointer; | |
| padding: 10px; | |
| font-size: 24px; | |
| text-align: center; | |
| font-weight: bold; | |
| color: white; | |
| } | |
| #users-list-page p:hover { | |
| background-color: #5F6569; | |
| } | |
| .profile-username-display { | |
| font-size: 28px; | |
| font-weight: bold; | |
| color: white; | |
| text-align: center; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| gap: 10px; | |
| } | |
| .verified-badge { | |
| width: 20px; | |
| height: 20px; | |
| vertical-align: middle; | |
| } | |
| .declined-trades { | |
| margin-top: 20px; | |
| padding: 20px; | |
| background-color: #393B3D; | |
| border-radius: 10px; | |
| } | |
| .declined-trades .completed-trade { | |
| background-color: #232527; | |
| padding: 15px; | |
| margin-bottom: 10px; | |
| border-radius: 5px; | |
| opacity: 0.8; | |
| } | |
| .declined-trades h3 { | |
| color: #ff4444; | |
| margin-bottom: 15px; | |
| } | |
| #catalog-search { | |
| padding-right: 35px; | |
| } | |
| #catalog-search + div svg { | |
| color: #B8B8B8; | |
| transition: color 0.2s; | |
| } | |
| #catalog-search + div:hover svg { | |
| color: #ffffff; | |
| } | |
| .transaction-list { | |
| max-height: 400px; | |
| overflow-y: auto; | |
| margin: 20px 0; | |
| padding: 10px; | |
| background-color: #2C2F33; | |
| border-radius: 10px; | |
| } | |
| .transaction-item { | |
| background-color: #393B3D; | |
| padding: 15px; | |
| margin: 10px 0; | |
| border-radius: 5px; | |
| } | |
| .transaction-item p { | |
| margin: 5px 0; | |
| color: #FFFFFF; | |
| } | |
| .total-spent { | |
| font-size: 18px; | |
| font-weight: bold; | |
| margin: 15px 0; | |
| color: #00A2FF; | |
| } | |
| .robux-page h2 { | |
| font-size: 32px; | |
| margin-bottom: 20px; | |
| display: flex; | |
| align-items: center; | |
| gap: 10px; | |
| } | |
| .robux-page h3 { | |
| font-size: 24px; | |
| margin: 20px 0; | |
| color: #FFFFFF; | |
| } | |
| .robux-page .button { | |
| margin: 10px; | |
| min-width: 150px; | |
| } | |
| #detail-limited-icon.unlimited { | |
| width: 120px; | |
| height: 265px; | |
| background-image: url('https://i.imgur.com/T5kWRJJ.png'); | |
| position: absolute; | |
| bottom: 10px; | |
| left: 10px; | |
| background-size: contain; | |
| background-repeat: no-repeat; | |
| background-position: center; | |
| } | |
| #detail-limited-icon.limited { | |
| width: 120px; | |
| height: 25px; | |
| background-image: url('https://i.imgur.com/Bh3j647.png'); | |
| position: absolute; | |
| bottom: 10px; | |
| left: 10px; | |
| background-size: contain; | |
| background-repeat: no-repeat; | |
| background-position: center; | |
| } | |
| .detail-rap-text { | |
| color: #B8B8B8; | |
| font-size: 14px; | |
| margin-top: 5px; | |
| } | |
| .equipped-items-container { | |
| margin-top: 20px; | |
| padding: 15px; | |
| background-color: #393B3D; | |
| border-radius: 10px; | |
| text-align: center; | |
| } | |
| .equipped-items-title { | |
| font-size: 20px; | |
| font-weight: bold; | |
| margin-bottom: 15px; | |
| text-align: center; | |
| } | |
| .equipped-items-grid { | |
| display: grid; | |
| grid-template-columns: repeat(5, 1fr); | |
| gap: 10px; | |
| justify-content: center; | |
| margin: 0 auto; | |
| max-width: 90%; | |
| } | |
| #profile-equipped-items { | |
| display: flex; | |
| flex-wrap: wrap; | |
| justify-content: center; | |
| gap: 10px; | |
| } | |
| .equipped-item { | |
| background-color: #232527; | |
| border-radius: 5px; | |
| padding: 15px; | |
| text-align: center; | |
| width: 180px; | |
| } | |
| .equipped-item img { | |
| width: 150px; | |
| height: 150px; | |
| object-fit: cover; | |
| border-radius: 5px; | |
| margin-bottom: 8px; | |
| } | |
| .equipped-item-name { | |
| font-weight: bold; | |
| font-size: 16px; | |
| margin-bottom: 8px; | |
| white-space: normal; | |
| overflow: visible; | |
| text-overflow: clip; | |
| } | |
| .equipped-item-rap, .equipped-item-price { | |
| font-size: 14px; | |
| color: #B8B8B8; | |
| } | |
| .put-on-button, .take-off-button { | |
| display: block; | |
| margin: 5px auto; | |
| padding: 5px 10px; | |
| border: none; | |
| border-radius: 5px; | |
| cursor: pointer; | |
| font-size: 12px; | |
| } | |
| .put-on-button { | |
| background-color: #00B06F; | |
| color: white; | |
| } | |
| .take-off-button { | |
| background-color: #ff4444; | |
| color: white; | |
| } | |
| .user-bio { | |
| margin-top: 10px; | |
| padding: 15px; | |
| background-color: #232527; | |
| border-radius: 10px; | |
| font-style: italic; | |
| text-align: center; | |
| position: relative; | |
| } | |
| .edit-bio-button { | |
| position: absolute; | |
| top: 5px; | |
| right: 5px; | |
| background-color: #393B3D; | |
| color: white; | |
| border: none; | |
| border-radius: 5px; | |
| padding: 3px 8px; | |
| font-size: 12px; | |
| cursor: pointer; | |
| } | |
| .inventory-toggle { | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| margin-bottom: 15px; | |
| } | |
| .toggle-switch { | |
| position: relative; | |
| display: inline-block; | |
| width: 60px; | |
| height: 30px; | |
| margin: 0 10px; | |
| } | |
| .toggle-switch input { | |
| opacity: 0; | |
| width: 0; | |
| height: 0; | |
| } | |
| .toggle-slider { | |
| position: absolute; | |
| cursor: pointer; | |
| top: 0; | |
| left: 0; | |
| right: 0; | |
| bottom: 0; | |
| background-color: #ccc; | |
| transition: .4s; | |
| border-radius: 30px; | |
| } | |
| .toggle-slider:before { | |
| position: absolute; | |
| content: ""; | |
| height: 22px; | |
| width: 22px; | |
| left: 4px; | |
| bottom: 4px; | |
| background-color: white; | |
| transition: .4s; | |
| border-radius: 50%; | |
| } | |
| input:checked + .toggle-slider { | |
| background-color: #00A2FF; | |
| } | |
| input:checked + .toggle-slider:before { | |
| transform: translateX(30px); | |
| } | |
| .admin-badge { | |
| width: 20px; | |
| height: 20px; | |
| vertical-align: middle; | |
| margin-right: 5px; | |
| } | |
| .leaderboard-row-private { | |
| background-color: #232527 ; | |
| } | |
| .leaderboard-row-public { | |
| background-color: #2C2F33 ; | |
| } | |
| .leaderboard-row .robux-icon { | |
| width: 18px; | |
| height: 18px; | |
| margin-right: 5px; | |
| } | |
| .leaderboard-row { | |
| font-size: 18px; | |
| } | |
| #announcement-banner > div { | |
| outline: none ; | |
| border: none ; | |
| box-sizing: border-box; | |
| } | |
| .sortable-list { | |
| max-height: 400px; | |
| overflow-y: auto; | |
| margin-bottom: 15px; | |
| } | |
| .sortable-user-item { | |
| padding: 10px 15px; | |
| background-color: #2C2F33; | |
| border-radius: 5px; | |
| margin-bottom: 8px; | |
| cursor: pointer; | |
| user-select: none; | |
| transition: background-color 0.2s; | |
| } | |
| .sortable-user-item:hover { | |
| background-color: #393B3D; | |
| } | |
| .sortable-user-item.dragging { | |
| opacity: 0.7; | |
| background-color: #1F2022; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="loading-screen" id="loading-screen"> | |
| <div class="loading-icon"></div> | |
| </div> | |
| <div class="success-banner" id="success-banner"> | |
| Successful purchase! | |
| </div> | |
| <div class="header"> | |
| <div class="logo"><img src="https://i.imgur.com/Z39wYb8.png" alt="Roblox Logo" style="height: 25px;"></div> | |
| <div id="current-user-display"></div> | |
| <div class="nav"> | |
| <a onclick="showProfile(); return false;">Profile</a> | |
| <a onclick="showCatalog(); return false;">Catalog</a> | |
| <a onclick="showAdmin(); return false;" id="admin-link" style="display:none;">Admin</a> | |
| <a onclick="showInventory(); return false;">Inventory</a> | |
| <a onclick="showRobuxPage(); return false;"><span class="robux-icon"></span><span id="robux-amount" style="font-weight: bold;">0</span></a> | |
| <a onclick="showTrade(); return false;">Trade</a> | |
| <a onclick="showLeaderboard(); return false;">Leaderboard</a> | |
| <a onclick="showUsers(); return false;">Switch Account</a> | |
| </div> | |
| </div> | |
| <div id="announcement-banner" style="display:none; background-color: transparent; padding: 0; margin: 0;"></div> | |
| <div class="content"> | |
| <div id="profile" class="profile"> | |
| <div class="profile-header"> | |
| <h2 id="profile-username"></h2> | |
| </div> | |
| <div class="user-bio" style="margin: 10px 0 20px 0;"> | |
| <p id="user-bio-text">"I have no bio."</p> | |
| <button class="edit-bio-button" onclick="showEditBioPopup('')" style="display: none;">Edit Bio</button> | |
| </div> | |
| <div class="profile-stats"> | |
| <div class="profile-stat"> | |
| <h3>Join Date</h3> | |
| <p id="join-date"></p> | |
| </div> | |
| <div class="profile-stat"> | |
| <h3>Place Visits</h3> | |
| <p id="place-visits">0</p> | |
| </div> | |
| <div class="profile-stat"> | |
| <h3>Friends</h3> | |
| <p id="friends">0</p> | |
| </div> | |
| <div class="profile-stat"> | |
| <h3>Followers</h3> | |
| <p id="followers">0</p> | |
| </div> | |
| <div class="profile-stat"> | |
| <h3>Following</h3> | |
| <p id="following">0</p> | |
| </div> | |
| <div class="profile-stat"> | |
| <h3>RAP</h3> | |
| <p id="rap">0</p> | |
| </div> | |
| </div> | |
| <div class="equipped-items-container"> | |
| <div class="equipped-items-title">Equipped Items</div> | |
| <div id="profile-equipped-items" class="equipped-items-grid"></div> | |
| </div> | |
| <div class="profile-inventory"> | |
| <h3 style="text-align: center; font-weight: bold; font-size: 24px; text-decoration: underline;">Inventory</h3> | |
| <div id="profile-inventory-items" class="catalog-items"></div> | |
| <div class="pagination" id="profile-inventory-pagination"></div> | |
| </div> | |
| </div> | |
| <div id="catalog" style="display:none;"> | |
| <h2>Catalog</h2> | |
| <div class="sort-options"> | |
| <div style="display: flex; align-items: center; justify-content: center; gap: 10px; margin-bottom: 10px;"> | |
| <div style="position: relative; display: inline-block;"> | |
| <input type="text" id="catalog-search" class="input-field" placeholder="Search items..." style="width: 200px; padding-right: 35px;"> | |
| <div onclick="performSearch()" style="position: absolute; right: 10px; top: 50%; transform: translateY(-50%); cursor: pointer;"> | |
| <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16"> | |
| <path d="M11.742 10.344a6.5 6.5 0 1 0-1.397 1.398h-.001c.03.04.062.078.098.115l3.85 3.85a1 1 0 0 0 1.415-1.414l-3.85-3.85a1.007 1.007 0 0 0-.115-.1zM12 6.5a5.5 5.5 0 1 1-11 0 5.5 5.5 0 0 1 11 0z"/> | |
| </svg> | |
| </div> | |
| </div> | |
| <select id="catalog-sort"> | |
| <option value="newest">Newest Items</option> | |
| <option value="price-asc">Price: Low to High</option> | |
| <option value="price-desc">Price: High to Low</option> | |
| <option value="oldest">Oldest Items</option> | |
| </select> | |
| </div> | |
| </div> | |
| <div id="catalog-items" class="catalog-items"></div> | |
| <div class="pagination" id="catalog-pagination"></div> | |
| </div> | |
| <div id="admin" style="display:none;"> | |
| <h2>Admin Panel</h2> | |
| <button class="button" onclick="showUploadPopup()">Upload Catalog Item</button> | |
| <button class="button" onclick="showDatabase()">Database</button> | |
| <button class="button" onclick="showAddUserPopup()">Add User</button> | |
| <button class="button" onclick="showAnnouncementPopup()">Announcement</button> | |
| <button class="button" onclick="showUserSortingPopup()">Sort Users</button> | |
| <button class="button" onclick="showHiddenItems()">View Hidden Items</button> | |
| <div id="current-announcements" style="margin-top: 20px;"></div> | |
| </div> | |
| <div id="inventory" style="display:none;"> | |
| <h2>Inventory</h2> | |
| <div class="inventory-toggle"> | |
| <span>Private Inventory</span> | |
| <label class="toggle-switch"> | |
| <input type="checkbox" id="private-inventory-toggle" onchange="togglePrivateInventory()"> | |
| <span class="toggle-slider"></span> | |
| </label> | |
| </div> | |
| <div id="inventory-items" class="catalog-items"></div> | |
| <div class="pagination" id="inventory-pagination"></div> | |
| </div> | |
| <div id="robux-page" class="robux-page" style="display:none;"> | |
| <h2><span class="robux-icon"></span><span id="robux-amount-page"></span></h2> | |
| <h3>Transaction History</h3> | |
| <div class="total-spent">Total Spent: <span id="total-spent">0</span> Robux</div> | |
| <div id="transaction-list" class="transaction-list"></div> | |
| <button class="button" onclick="addRobux(400)">Add 400 Robux</button> | |
| <button class="button" onclick="addRobux(800)">Add 800 Robux</button> | |
| <button class="button" onclick="addRobux(1700)">Add 1700 Robux</button> | |
| <button class="button" onclick="addRobux(4500)">Add 4500 Robux</button> | |
| <button class="button" onclick="addRobux(10000)">Add 10000 Robux</button> | |
| <button class="button" onclick="addRobux(22500)">Add 22500 Robux</button> | |
| </div> | |
| <div id="trade" class="trade-page" style="display:none;"> | |
| <h2>Trade</h2> | |
| <div class="trade-partners"> | |
| <select id="trade-partner" class="input-field"> | |
| <option value>Select Trade Partner</option> | |
| </select> | |
| </div> | |
| <div class="trade-status" id="trade-status"></div> | |
| <div class="trade-container"> | |
| <div class="trade-side"> | |
| <h3>Your Offers</h3> | |
| <div class="trade-value">Total Value: <span id="your-trade-value">0</span></div> | |
| <div id="your-trade-items" class="trade-items"></div> | |
| </div> | |
| <div class="trade-side"> | |
| <h3>Their Offers</h3> | |
| <div class="trade-value">Total Value: <span id="their-trade-value">0</span></div> | |
| <div id="their-trade-items" class="trade-items"></div> | |
| </div> | |
| </div> | |
| <div class="trade-buttons"> | |
| <button class="button" onclick="sendTrade()" id="send-trade-btn">Send Trade</button> | |
| <button class="button" onclick="clearTrade()">Clear Trade</button> | |
| </div> | |
| <div class="completed-trades"> | |
| <div id="completed-trades-list"></div> | |
| </div> | |
| <div class="pending-trades"></div> | |
| <div class="declined-trades"></div> | |
| </div> | |
| <div id="leaderboard" class="users-page" style="display:none;"> | |
| <h2>Leaderboard</h2> | |
| <div id="leaderboard-list"></div> | |
| </div> | |
| </div> | |
| <div id="item-detail-page" style="display:none;" class="content"> | |
| <button class="button" onclick="backToCatalog()" style="margin-bottom: 20px;">← Back to Catalog</button> | |
| <div style="display: flex; padding: 20px; background-color: #393B3D; border-radius: 10px;"> | |
| <div style="flex: 1; position: relative;"> | |
| <img id="detail-item-image" src="" alt="" style="width: 100%; max-width: 500px; border-radius: 10px;"> | |
| <div id="detail-limited-icon" class="limited-icon" style="position: absolute; top: 379px; left: 139px; width: 200px; height: 211px;"></div> | |
| </div> | |
| <div style="flex: 1; padding-left: 30px;"> | |
| <h2 id="detail-item-name" style="font-size: 32px; font-weight: bold; margin-bottom: 10px;"></h2> | |
| <p style="margin-bottom: 20px;">By: Roblox <img src="https://en.help.roblox.com/hc/article_attachments/7997146649876" alt="Verified" style="width: 16px; height: 16px; vertical-align: middle;"></p> | |
| <p style="margin-bottom: 20px;"><strong>Description: </strong><span id="detail-item-description" style="color: #B8B8B8;"></span></p> | |
| <div id="detail-item-price" style="font-size: 24px; font-weight: bold;"></div> | |
| <div id="detail-item-buttons"></div> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="overlay" id="overlay"></div> | |
| <div class="popup" id="upload-popup"> | |
| <h3>Upload Catalog Item</h3> | |
| <input type="text" id="item-name" placeholder="Item Name" class="input-field"> | |
| <input type="text" id="item-image" placeholder="Image URL" class="input-field" oninput="previewUploadImage()" value="https://i.imgur.com/uXWWQIs.png"> | |
| <img id="upload-preview-image" style="max-width: 200px; max-height: 200px; display: none; margin: 10px auto;"> | |
| <input type="number" id="item-price" placeholder="Price" class="input-field"> | |
| <input type="number" id="item-rap" placeholder="RAP" class="input-field"> | |
| <input type="number" id="item-stock" placeholder="Stock (Optional)" class="input-field"> | |
| <input type="number" id="item-max-purchase" placeholder="Max Purchase Amount Per User" class="input-field"> | |
| <select id="item-limited-status"> | |
| <option value="none">Not Limited</option> | |
| <option value="limited">Limited</option> | |
| <option value="unlimited">Unlimited</option> | |
| </select> | |
| <select id="item-timer-status"> | |
| <option value="none">None</option> | |
| <option value="timer">Timer</option> | |
| </select> | |
| <div id="timer-fields" style="display: none; margin: 10px 0;"> | |
| <div style="display: flex; gap: 10px;"> | |
| <input type="number" id="item-timer-hours" placeholder="Hours" class="input-field" min="0" max="720"> | |
| <input type="number" id="item-timer-minutes" placeholder="Minutes" class="input-field" min="0" max="59"> | |
| <input type="number" id="item-timer-seconds" placeholder="Seconds" class="input-field" min="0" max="59"> | |
| </div> | |
| </div> | |
| <div style="margin: 10px 0;"> | |
| <label><input type="checkbox" id="item-offsale"> Off Sale</label> | |
| </div> | |
| <div style="margin: 10px 0;"> | |
| <label><input type="checkbox" id="item-hidden"> Hidden from Catalog</label> | |
| </div> | |
| <textarea id="item-description" placeholder="Item Description" class="input-field" style="min-height: 100px;"></textarea> | |
| <button class="button" onclick="confirmUpload()">Upload</button> | |
| <button class="button" onclick="closePopup('upload-popup')">Cancel</button> | |
| </div> | |
| <div class="popup" id="database-popup"> | |
| <h3>Database</h3> | |
| <input type="text" id="database-search" class="search-bar" placeholder="Search in database..."> | |
| <textarea id="database-content" rows="20" cols="50"></textarea> | |
| <button class="button" onclick="saveDatabase()">Save Changes</button> | |
| <button class="button" onclick="closePopup('database-popup')">Close</button> | |
| </div> | |
| <div class="popup" id="users-popup"> | |
| <h3>Users</h3> | |
| <div id="users-list"></div> | |
| <button class="button" onclick="closePopup('users-popup')">Close</button> | |
| </div> | |
| <div class="popup" id="add-user-popup"> | |
| <h3>Add User</h3> | |
| <input type="text" id="new-username" placeholder="Username" class="input-field"> | |
| <button class="button" onclick="addUser()">Add User</button> | |
| <button class="button" onclick="closePopup('add-user-popup')">Cancel</button> | |
| </div> | |
| <div class="popup" id="buy-confirmation-popup"> | |
| <h3>Confirm Purchase</h3> | |
| <img id="buy-confirmation-image" src alt="Item Image" style="max-width: 100px; max-height: 100px; display: block; margin: 10px auto;"> | |
| <p>Would you like to buy the item <span id="buy-item-name"></span> from Roblox for <span id="buy-item-price"></span>?</p> | |
| <button class="button confirm-button" onclick="confirmPurchase()">Confirm</button> | |
| <button class="button cancel-button" onclick="cancelPurchase()">Cancel</button> | |
| <p>Your balance after this transaction will be <span class="robux-icon"></span><span id="robux-after-purchase"></span>.</p> | |
| </div> | |
| <div class="popup" id="edit-item-popup"> | |
| <h3>Edit Catalog Item</h3> | |
| <input type="text" id="edit-item-name" readonly class="input-field"> | |
| <input type="text" id="edit-item-name-new" placeholder="New Item Name" class="input-field"> | |
| <input type="text" id="edit-item-image" placeholder="New Image URL" class="input-field" oninput="previewEditImage()"> | |
| <img id="edit-preview-image" style="max-width: 200px; max-height: 200px; display: none; margin: 10px auto;"> | |
| <input type="number" id="edit-item-price" placeholder="New Price" class="input-field"> | |
| <input type="number" id="edit-item-rap" placeholder="New RAP" class="input-field"> | |
| <input type="number" id="edit-item-max-purchase" placeholder="Max Purchase Amount Per User" class="input-field"> | |
| <select id="edit-item-limited-status"> | |
| <option value="none">Not Limited</option> | |
| <option value="limited">Limited</option> | |
| <option value="unlimited">Unlimited</option> | |
| </select> | |
| <select id="edit-item-timer-status"> | |
| <option value="none">None</option> | |
| <option value="timer">Timer</option> | |
| </select> | |
| <div id="edit-timer-fields" style="display: none; margin: 10px 0;"> | |
| <div style="display: flex; gap: 10px;"> | |
| <input type="number" id="edit-item-timer-hours" placeholder="Hours" class="input-field" min="0" max="720"> | |
| <input type="number" id="edit-item-timer-minutes" placeholder="Minutes" class="input-field" min="0" max="59"> | |
| <input type="number" id="edit-item-timer-seconds" placeholder="Seconds" class="input-field" min="0" max="59"> | |
| </div> | |
| </div> | |
| <div style="margin: 10px 0;"> | |
| <label><input type="checkbox" id="edit-item-offsale"> Off Sale</label> | |
| </div> | |
| <div style="margin: 10px 0;"> | |
| <label><input type="checkbox" id="edit-item-hidden"> Hidden from Catalog</label> | |
| </div> | |
| <textarea id="edit-item-description" placeholder="Item Description" class="input-field" style="min-height: 100px;"></textarea> | |
| <div style="margin: 10px 0;"> | |
| <label><input type="checkbox" id="edit-item-new-badge"> Give New Badge</label> | |
| </div> | |
| <button class="button" onclick="saveItemChanges()">Save Changes</button> | |
| <button class="button" onclick="closePopup('edit-item-popup')">Cancel</button> | |
| </div> | |
| <div class="popup" id="sell-confirmation-popup"> | |
| <h3>Confirm Sale</h3> | |
| <img id="sell-confirmation-image" src alt="Item Image" style="max-width: 100px; max-height: 100px; display: block; margin: 10px auto;"> | |
| <p>Are you sure you want to sell this item?</p> | |
| <p>Your Robux after this sale: <span id="robux-after-sale"></span></p> | |
| <button class="button confirm-button" onclick="confirmSale()">Confirm</button> | |
| <button class="button cancel-button" onclick="cancelSale()">Cancel</button> | |
| </div> | |
| <div class="popup" id="upload-confirmation-popup"> | |
| <h3>Confirm Upload</h3> | |
| <p>Are you sure you want to upload this item?</p> | |
| <button class="button" onclick="uploadItem()">Confirm</button> | |
| <button class="button" onclick="closePopup('upload-confirmation-popup')">Cancel</button> | |
| </div> | |
| <div class="popup" id="delete-confirmation-popup"> | |
| <h3>Confirm Delete</h3> | |
| <p>Are you sure you want to delete this item?</p> | |
| <p>This action cannot be undone and will refund all users who purchased this item.</p> | |
| <button class="button" onclick="confirmDeleteItem()">Yes, Delete</button> | |
| <button class="button" onclick="closePopup('delete-confirmation-popup')">Cancel</button> | |
| </div> | |
| <div class="popup" id="delete-user-confirmation-popup"> | |
| <h3>Confirm Delete User</h3> | |
| <p>Are you sure you want to delete this user?</p> | |
| <p>This action cannot be undone.</p> | |
| <button class="button" onclick="confirmDeleteUser()">Yes, Delete</button> | |
| <button class="button" onclick="closePopup('delete-user-confirmation-popup')">Cancel</button> | |
| </div> | |
| <div class="popup" id="item-listings-popup"> | |
| <h3>Item Listings</h3> | |
| <p class="clickable" onclick="showPurchaseHistory()">Purchase History</p> | |
| <button class="button" onclick="closePopup('item-listings-popup')">Close</button> | |
| <div id="item-listings"></div> | |
| </div> | |
| <div class="popup" id="purchase-history-popup"> | |
| <h3>Purchase History</h3> | |
| <div id="purchase-history"></div> | |
| <button class="button" onclick="closePopup('purchase-history-popup')">Close</button> | |
| </div> | |
| <div class="popup" id="sell-item-popup"> | |
| <h3>List Item for Sale</h3> | |
| <div> | |
| <h4>Select Quantity to Sell:</h4> | |
| <select id="quantity-select" class="input-field"></select> | |
| </div> | |
| <input type="number" id="listing-price" class="input-field" placeholder="Price" max="999999999" oninput="updateSellPrice(this.value)"> | |
| <p id="seller-earnings" style="color: #B8B8B8; margin: 5px 0;"></p> | |
| <button class="button" onclick="confirmCreateListing()">Create Listing</button> | |
| <button class="button" onclick="closePopup('sell-item-popup')">Cancel</button> | |
| </div> | |
| <div class="popup" id="listing-confirmation-popup"></div> | |
| <div id="owners-popup" class="popup"> | |
| <h3>Item Owners</h3> | |
| <div id="owners-list"></div> | |
| <button class="button" onclick="closePopup('owners-popup')">Close</button> | |
| </div> | |
| <div class="popup" id="give-item-popup"> | |
| <h3>Give Item</h3> | |
| <select id="recipient-select" class="input-field"> | |
| <option value="">Select User</option> | |
| </select> | |
| <input type="number" id="give-quantity" class="input-field" placeholder="Quantity" min="1" value="1"> | |
| <button class="button" onclick="confirmGiveItem()">Give Item</button> | |
| <button class="button" onclick="closePopup('give-item-popup')">Cancel</button> | |
| </div> | |
| <div class="popup" id="confirm-give-popup"> | |
| <h3>Confirm Give Item</h3> | |
| <p>Are you sure you want to give this item to <span id="recipient-name"></span>?</p> | |
| <button class="button" onclick="giveItem()">Confirm</button> | |
| <button class="button" onclick="closePopup('confirm-give-popup')">Cancel</button> | |
| </div> | |
| <div class="popup" id="edit-bio-popup"> | |
| <h3>Edit Bio</h3> | |
| <textarea id="bio-text" class="input-field" rows="5" style="width: 100%"></textarea> | |
| <button class="button" onclick="saveBio()">Save</button> | |
| <button class="button" onclick="closePopup('edit-bio-popup')">Cancel</button> | |
| </div> | |
| <div class="popup" id="remove-item-confirmation-popup"> | |
| <h3>Confirm Remove Item</h3> | |
| <p>Are you sure you want to remove <span class="remove-item-name"></span> from <span class="remove-item-username"></span>'s inventory?</p> | |
| <button class="button" onclick="confirmRemoveItem()">Yes, Remove</button> | |
| <button class="button" onclick="closePopup('remove-item-confirmation-popup')">Cancel</button> | |
| </div> | |
| <div class="popup" id="announcement-popup"> | |
| <h3>Create Announcement</h3> | |
| <textarea id="announcement-text" class="input-field" placeholder="Announcement text"></textarea> | |
| <select id="announcement-color" class="input-field"> | |
| <option value="#ff4444">Red</option> | |
| <option value="#00B06F">Green</option> | |
| <option value="#00A2FF">Blue</option> | |
| <option value="#F78904">Orange</option> | |
| <option value="#9b59b6">Purple</option> | |
| <option value="#e74c3c">Dark Red</option> | |
| <option value="#2ecc71">Dark Green</option> | |
| <option value="#3498db">Light Blue</option> | |
| <option value="#e67e22">Dark Orange</option> | |
| <option value="#16a085">Teal</option> | |
| </select> | |
| <button class="button" onclick="addAnnouncement()">Add Announcement</button> | |
| <button class="button" onclick="closePopup('announcement-popup')">Cancel</button> | |
| </div> | |
| <div class="popup" id="hidden-items-popup"> | |
| <h3>Hidden Items</h3> | |
| <div id="hidden-items-list" class="catalog-items" style="max-height: 500px; overflow-y: auto;"></div> | |
| <button class="button" onclick="closePopup('hidden-items-popup')">Close</button> | |
| </div> | |
| <div class="popup" id="edit-announcement-popup"> | |
| <h3>Edit Announcement</h3> | |
| <textarea id="edit-announcement-text" class="input-field" placeholder="Edit announcement text"></textarea> | |
| <select id="edit-announcement-color" class="input-field"> | |
| <option value="#ff4444">Red</option> | |
| <option value="#00B06F">Green</option> | |
| <option value="#00A2FF">Blue</option> | |
| <option value="#F78904">Orange</option> | |
| <option value="#9b59b6">Purple</option> | |
| <option value="#e74c3c">Dark Red</option> | |
| <option value="#2ecc71">Dark Green</option> | |
| <option value="#3498db">Light Blue</option> | |
| <option value="#e67e22">Dark Orange</option> | |
| <option value="#16a085">Teal</option> | |
| </select> | |
| <button class="button" onclick="saveEditedAnnouncement()">Save Changes</button> | |
| <button class="button" onclick="closePopup('edit-announcement-popup')">Cancel</button> | |
| </div> | |
| <div class="popup" id="delete-user-confirmation-popup"> | |
| <div class="popup-content"> | |
| <h2>Delete User</h2> | |
| <p>Are you sure you want to delete the user <span class="username"></span>?</p> | |
| <div class="popup-actions"> | |
| <button class="button" onclick="closePopup('delete-user-confirmation-popup')">Cancel</button> | |
| <button class="button" onclick="deleteUser()">Yes, Delete</button> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="popup" id="edit-user-popup"> | |
| <h3>Edit User</h3> | |
| <label for="edit-username">Username:</label> | |
| <input type="text" id="edit-username" class="input-field" placeholder="Username"> | |
| <label for="edit-join-date">Join Date:</label> | |
| <input type="text" id="edit-join-date" class="input-field" placeholder="Join Date"> | |
| <label for="edit-place-visits">Place Visits:</label> | |
| <input type="number" id="edit-place-visits" class="input-field" placeholder="Place Visits"> | |
| <label for="edit-friends">Friends:</label> | |
| <input type="number" id="edit-friends" class="input-field" placeholder="Friends"> | |
| <label for="edit-followers">Followers:</label> | |
| <input type="number" id="edit-followers" class="input-field" placeholder="Followers"> | |
| <label for="edit-following">Following:</label> | |
| <input type="number" id="edit-following" class="input-field" placeholder="Following"> | |
| <label for="edit-rap">RAP:</label> | |
| <input type="number" id="edit-rap" class="input-field" placeholder="RAP"> | |
| <label for="edit-robux">Robux:</label> | |
| <input type="number" id="edit-robux" class="input-field" placeholder="Robux"> | |
| <button class="button" onclick="saveUserChanges()">Save Changes</button> | |
| <button class="button" onclick="closePopup('edit-user-popup')">Cancel</button> | |
| </div> | |
| <div class="popup" id="user-sorting-popup"> | |
| <h3>Sort Users (Drag and Drop)</h3> | |
| <div id="sortable-users-list" class="sortable-list"></div> | |
| <button class="button" onclick="saveUserOrder()">Save Order</button> | |
| <button class="button" onclick="closePopup('user-sorting-popup')">Cancel</button> | |
| </div> | |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script> | |
| <script> | |
| let currentUser = null; | |
| let currentProfileInventoryPage = 1; | |
| let users = []; | |
| let catalogItems = []; | |
| let itemToBuy = null; | |
| let itemToSell = null; | |
| let itemToDelete = null; | |
| let currentCatalogPage = 1; | |
| let currentInventoryPage = 1; | |
| const itemsPerPage = 14; | |
| let pendingTrades = []; | |
| let completedTrades = []; | |
| let declinedTrades = []; | |
| let itemListings = []; | |
| let purchaseHistory = []; | |
| const MAX_EQUIPPED_ITEMS = 10; | |
| let itemToGive = null; | |
| let recipientUsername = null; | |
| let itemDetailSource = 'catalog'; // Possible values: 'catalog', 'profile' | |
| function toggleEquipItem(itemName) { | |
| if (!currentUser) return; | |
| if (!currentUser.equippedItems) { | |
| currentUser.equippedItems = []; | |
| } | |
| const isEquipped = currentUser.equippedItems.includes(itemName); | |
| if (isEquipped) { | |
| // Take off the item | |
| currentUser.equippedItems = currentUser.equippedItems.filter(name => name !== itemName); | |
| } else { | |
| // Put on the item | |
| if (currentUser.equippedItems.length >= MAX_EQUIPPED_ITEMS) { | |
| alert(`You can only have ${MAX_EQUIPPED_ITEMS} equipped items at a time.`); | |
| return; | |
| } | |
| currentUser.equippedItems.push(itemName); | |
| } | |
| saveToLocalStorage(); | |
| updateInventory(); | |
| updateEquippedItems(); | |
| // If on profile page, update the profile equipped items too | |
| if ($('#profile').is(':visible')) { | |
| updateProfileEquippedItems(currentUser); | |
| } | |
| } | |
| function updateEquippedItems() { | |
| if (!currentUser) return; | |
| if (!currentUser.equippedItems) { | |
| currentUser.equippedItems = []; | |
| } | |
| } | |
| function updateProfileEquippedItems(user) { | |
| if (!user) return; | |
| if (!user.equippedItems) { | |
| user.equippedItems = []; | |
| } | |
| let equippedHtml = ''; | |
| if (user.equippedItems.length > 0) { | |
| for (let itemName of user.equippedItems) { | |
| const item = user.inventory.find(i => i.name === itemName); | |
| if (item) { | |
| const catalogItem = catalogItems.find(i => i.name === itemName); | |
| const hasRap = catalogItem && catalogItem.rap > 0; | |
| const rapHtml = hasRap ? `<div class="equipped-item-rap">RAP: ${formatNumber(catalogItem.rap)}</div>` : ''; | |
| let priceHtml = ''; | |
| if (catalogItem) { | |
| if (catalogItem.offSale) { | |
| priceHtml = '<div class="equipped-item-price" style="font-weight: bold; color: #ff4444;">Offsale</div>'; | |
| } else if (catalogItem.limitedStatus !== 'none') { | |
| const listings = itemListings.filter(l => l.itemName === itemName) | |
| .sort((a, b) => a.price - b.price); | |
| const lowestListing = listings[0]; | |
| if (!lowestListing) { | |
| priceHtml = '<div class="equipped-item-price" style="font-weight: bold;">No Resellers</div>'; | |
| } else { | |
| priceHtml = `<div class="equipped-item-price" style="font-weight: bold;">Price: <span class="robux-icon"></span>${formatNumber(lowestListing.price)}</div>`; | |
| } | |
| } else if (catalogItem.price === 0) { | |
| priceHtml = '<div class="equipped-item-price" style="font-weight: bold;">Free</div>'; | |
| } else { | |
| priceHtml = `<div class="equipped-item-price" style="font-weight: bold;">Price: <span class="robux-icon"></span>${formatNumber(catalogItem.price)}</div>`; | |
| } | |
| } | |
| equippedHtml += ` | |
| <div class="equipped-item"> | |
| <img src="${item.image}" alt="${item.name}"> | |
| <div class="equipped-item-name">${item.name}</div> | |
| ${rapHtml} | |
| ${priceHtml} | |
| </div> | |
| `; | |
| } | |
| } | |
| } else { | |
| equippedHtml = '<p style="grid-column: span 5; text-align: center; color: #808080;">No equipped items</p>'; | |
| } | |
| $('#profile-equipped-items').html(equippedHtml); | |
| } | |
| function loadFromLocalStorage() { | |
| let data = localStorage.getItem('robloxSimData'); | |
| if (data) { | |
| let parsedData = JSON.parse(data); | |
| users = parsedData.users; | |
| catalogItems = parsedData.catalogItems; | |
| completedTrades = parsedData.completedTrades || []; | |
| pendingTrades = parsedData.pendingTrades || []; | |
| declinedTrades = parsedData.declinedTrades || []; | |
| itemListings = parsedData.itemListings || []; | |
| purchaseHistory = parsedData.purchaseHistory || []; | |
| // Initialize equipped items if not present | |
| users.forEach(user => { | |
| if (!user.equippedItems) { | |
| user.equippedItems = []; | |
| } | |
| if (!user.bio) { | |
| user.bio = "I have no bio."; | |
| } | |
| if (user.privateInventory === undefined) { | |
| user.privateInventory = false; | |
| } | |
| if (!user.isAdmin) { | |
| user.isAdmin = false; | |
| } | |
| }); | |
| // Remove apostrophes from all item names | |
| catalogItems.forEach(item => { | |
| if (item.name && item.name.includes("'")) { | |
| item.name = item.name.replace(/'/g, ""); | |
| } | |
| }); | |
| // Update user inventory items to match catalog items without apostrophes | |
| users.forEach(user => { | |
| if (user.inventory) { | |
| user.inventory.forEach(item => { | |
| if (item.name && item.name.includes("'")) { | |
| item.name = item.name.replace(/'/g, ""); | |
| } | |
| }); | |
| } | |
| }); | |
| } else { | |
| users = [{ | |
| username: 'Roblox', | |
| joinDate: '1/1/2023', | |
| placeVisits: 0, | |
| friends: 0, | |
| followers: 0, | |
| following: 0, | |
| rap: 0, | |
| robux: 0, | |
| inventory: [], | |
| transactions: [], | |
| equippedItems: [], | |
| bio: "I have no bio.", | |
| isAdmin: true | |
| }]; | |
| completedTrades = []; | |
| pendingTrades = []; | |
| declinedTrades = []; | |
| } | |
| } | |
| function verifyUser(username) { | |
| let user = users.find(u => u.username === username); | |
| if (user) { | |
| user.verified = !user.verified; | |
| saveToLocalStorage(); | |
| showUserProfile(username); | |
| showSuccessBanner(user.verified ? 'User verified!' : 'User unverified!'); | |
| } | |
| } | |
| function resetUsername(username) { | |
| let user = users.find(u => u.username === username); | |
| if (user && user.username !== 'Roblox') { | |
| let newUsername = prompt('Enter new username:'); | |
| if (newUsername && newUsername !== 'Roblox' && !users.some(u => u.username === newUsername)) { | |
| pendingTrades.forEach(trade => { | |
| if (trade.sender === user.username) trade.sender = newUsername; | |
| if (trade.receiver === user.username) trade.receiver = newUsername; | |
| }); | |
| completedTrades.forEach(trade => { | |
| if (trade.sender === user.username) trade.sender = newUsername; | |
| if (trade.receiver === user.username) trade.receiver = newUsername; | |
| }); | |
| catalogItems.forEach(item => { | |
| if (item.newViewedBy) { | |
| let index = item.newViewedBy.indexOf(user.username); | |
| if (index !== -1) { | |
| item.newViewedBy[index] = newUsername; | |
| } | |
| } | |
| }); | |
| user.username = newUsername; | |
| saveToLocalStorage(); | |
| showUserProfile(newUsername); | |
| showSuccessBanner('Username reset successfully!'); | |
| } else { | |
| alert('Invalid username or username already exists.'); | |
| } | |
| } | |
| } | |
| function banUser(username) { | |
| let user = users.find(u => u.username === username); | |
| if (user && user.username !== 'Roblox') { | |
| user.banned = !user.banned; | |
| if (user.banned) { | |
| pendingTrades = pendingTrades.filter(trade => trade.sender !== user.username && trade.receiver !== user.username); | |
| } | |
| saveToLocalStorage(); | |
| showUserProfile(username); | |
| showSuccessBanner(user.banned ? 'User banned!' : 'User unbanned!'); | |
| } | |
| } | |
| function makeAdmin(username) { | |
| let user = users.find(u => u.username === username); | |
| if (user) { | |
| user.isAdmin = !user.isAdmin; | |
| saveToLocalStorage(); | |
| showUserProfile(username); | |
| showSuccessBanner(user.isAdmin ? 'User is now an admin!' : 'User is no longer an admin!'); | |
| } | |
| } | |
| function showUserProfile(username) { | |
| $('.profile-inventory').scrollTop(0); | |
| let user = users.find(u => u.username === username); | |
| if (user) { | |
| let verifiedBadge = user.verified ? ' <img src="https://en.help.roblox.com/hc/article_attachments/7997146649876" alt="Verified" class="verified-badge">' : ''; | |
| let adminBadge = user.isAdmin ? ' <img src="https://i.imgur.com/WRPVUAh.png" alt="Admin" class="admin-badge">' : ''; | |
| $('#profile-username').html(` | |
| <div class="profile-username-display" style="color: ${user.banned ? 'red' : 'white'}"> | |
| ${adminBadge}${user.username}${verifiedBadge} | |
| </div> | |
| `); | |
| $('#profile-username').css('color', user.banned ? 'red' : ''); | |
| $('#join-date').text(user.joinDate); | |
| $('#place-visits').text(formatNumber(user.placeVisits)); | |
| $('#friends').text(formatNumber(user.friends)); | |
| $('#followers').text(formatNumber(user.followers)); | |
| $('#following').text(formatNumber(user.following)); | |
| let totalRap = 0; | |
| if (user.inventory) { | |
| totalRap = user.inventory.reduce((sum, item) => { | |
| let catalogItem = catalogItems.find(i => i.name === item.name); | |
| let itemRap = catalogItem ? catalogItem.rap : item.rap; | |
| return sum + itemRap * (item.quantity || 1); | |
| }, 0); | |
| } | |
| $('#rap').text(formatNumber(totalRap)); | |
| // Update bio text | |
| $('#user-bio-text').text(`"${user.bio || "I have no bio."}"`); | |
| $('.edit-bio-button').css('display', currentUser && (currentUser.username === user.username || currentUser.username === 'Roblox') ? 'block' : 'none'); | |
| $('.edit-bio-button').attr('onclick', `showEditBioPopup('${user.username}')`); | |
| updateProfileEquippedItems(user); | |
| updateProfileInventory(user); | |
| showProfile(); | |
| if (currentUser.username === 'Roblox') { | |
| $('.admin-options').remove(); | |
| let adminOptionsHtml = ` | |
| <div class="admin-options"> | |
| <button class="button" onclick="verifyUser('${user.username}')">${user.verified ? 'Unverify' : 'Verify'}</button> | |
| <button class="button" onclick="resetUsername('${user.username}')">Reset Username</button> | |
| <button class="button" onclick="banUser('${user.username}')">${user.banned ? 'Unban' : 'Ban'}</button> | |
| <button class="button" onclick="showDeleteUserConfirmation('${user.username}')">Delete</button> | |
| <button class="button" onclick="makeAdmin('${user.username}')">${user.isAdmin ? 'Remove Admin' : 'Make Admin'}</button> | |
| <button class="button" onclick="showEditUserPopup('${user.username}')">Edit</button> | |
| </div>`; | |
| $('.profile-header').append(adminOptionsHtml); | |
| } else { | |
| $('.admin-options').remove(); | |
| } | |
| } | |
| } | |
| function showProfile() { | |
| $('#profile').show(); | |
| $('#catalog').hide(); | |
| $('#admin').hide(); | |
| $('#inventory').hide(); | |
| $('#robux-page').hide(); | |
| $('#users-page').hide(); | |
| $('#trade').hide(); | |
| $('#leaderboard').hide(); | |
| setActiveNavItem('profile'); | |
| } | |
| function showCatalog() { | |
| $('#profile').hide(); | |
| $('#catalog').show(); | |
| $('#admin').hide(); | |
| $('#inventory').hide(); | |
| $('#robux-page').hide(); | |
| $('#users-page').hide(); | |
| $('#trade').hide(); | |
| $('#leaderboard').hide(); | |
| updateCatalog(); | |
| setActiveNavItem('catalog'); | |
| } | |
| function showAdmin() { | |
| $('#profile').hide(); | |
| $('#catalog').hide(); | |
| $('#admin').show(); | |
| $('#inventory').hide(); | |
| $('#robux-page').hide(); | |
| $('#users-page').hide(); | |
| $('#trade').hide(); | |
| $('#leaderboard').hide(); | |
| setActiveNavItem('admin'); | |
| } | |
| function showInventory() { | |
| $('#profile').hide(); | |
| $('#catalog').hide(); | |
| $('#admin').hide(); | |
| $('#inventory').show(); | |
| $('#robux-page').hide(); | |
| $('#users-page').hide(); | |
| $('#trade').hide(); | |
| $('#leaderboard').hide(); | |
| updateInventory(); | |
| setActiveNavItem('inventory'); | |
| } | |
| function showRobuxPage() { | |
| $('#profile').hide(); | |
| $('#catalog').hide(); | |
| $('#admin').hide(); | |
| $('#inventory').hide(); | |
| $('#robux-page').show(); | |
| $('#trade').hide(); | |
| $('#leaderboard').hide(); | |
| updateRobuxPage(); | |
| setActiveNavItem('robux'); | |
| } | |
| function showTrade() { | |
| $('#profile').hide(); | |
| $('#catalog').hide(); | |
| $('#admin').hide(); | |
| $('#inventory').hide(); | |
| $('#robux-page').hide(); | |
| $('#trade').show(); | |
| $('#leaderboard').hide(); | |
| updateTradePartners(); | |
| updateTradeItems(); | |
| updatePendingTrades(); | |
| updateCompletedTrades(); | |
| updateDeclinedTrades(); | |
| setActiveNavItem('trade'); | |
| } | |
| function showLeaderboard() { | |
| $('#profile').hide(); | |
| $('#catalog').hide(); | |
| $('#admin').hide(); | |
| $('#inventory').hide(); | |
| $('#robux-page').hide(); | |
| $('#trade').hide(); | |
| $('#leaderboard').show(); | |
| updateLeaderboard(); | |
| setActiveNavItem('leaderboard'); | |
| } | |
| function updateTradePartners() { | |
| let html = '<option value="">Select Trade Partner</option>'; | |
| users.forEach(user => { | |
| if (user.username !== currentUser.username && !user.banned && !user.privateInventory) { | |
| html += `<option value="${user.username}">${user.username}</option>`; | |
| } | |
| }); | |
| $('#trade-partner').html(html); | |
| } | |
| let selectedItems = { | |
| yours: [], | |
| theirs: [] | |
| }; | |
| function updateTradeItems() { | |
| if (!currentUser) return; | |
| let yourHtml = ''; | |
| let theirHtml = ''; | |
| let partner = users.find(u => u.username === $('#trade-partner').val()); | |
| if (!partner) return; | |
| let yourSortedItems = currentUser.inventory ? [...currentUser.inventory].sort((a, b) => b.price - a.price) : []; | |
| let theirSortedItems = partner && partner.inventory ? [...partner.inventory].sort((a, b) => b.price - a.price) : []; | |
| if (yourSortedItems.length) { | |
| yourSortedItems.forEach(item => { | |
| if (item.rap > 0) { | |
| let catalogItem = catalogItems.find(i => i.name === item.name); | |
| let currentRap = catalogItem ? catalogItem.rap : item.rap; | |
| let currentQuantity = selectedItems.yours.filter(i => i === item.name).length; | |
| let maxQuantity = item.quantity || 1; | |
| let selected = currentQuantity > 0 ? 'selected' : ''; | |
| yourHtml += ` | |
| <div class="trade-item ${selected}" onclick="toggleTradeItem('yours', "${escapeHtml(item.name)}")"> | |
| <img src="${item.image}" alt="${item.name}"> | |
| <p>${item.name}</p> | |
| <p>RAP per item: ${formatNumber(currentRap)}</p> | |
| <p>You own: ${maxQuantity}</p> | |
| <div class="trade-item-quantity"> | |
| <button onclick="changeQuantity('yours', "${escapeHtml(item.name)}", -1)" ${currentQuantity === 0 ? 'disabled' : ''}>-</button> | |
| <span>${currentQuantity}</span> | |
| <button onclick="changeQuantity('yours', "${escapeHtml(item.name)}", 1)" ${currentQuantity >= maxQuantity ? 'disabled' : ''}>+</button> | |
| </div> | |
| </div> | |
| `; | |
| } | |
| }); | |
| } | |
| if (partner && theirSortedItems.length) { | |
| theirSortedItems.forEach(item => { | |
| if (item.rap > 0) { | |
| let catalogItem = catalogItems.find(i => i.name === item.name); | |
| let currentRap = catalogItem ? catalogItem.rap : item.rap; | |
| let currentQuantity = selectedItems.theirs.filter(i => i === item.name).length; | |
| let maxQuantity = item.quantity || 1; | |
| let selected = currentQuantity > 0 ? 'selected' : ''; | |
| theirHtml += ` | |
| <div class="trade-item ${selected}" onclick="toggleTradeItem('theirs', "${escapeHtml(item.name)}")"> | |
| <img src="${item.image}" alt="${item.name}"> | |
| <p>${item.name}</p> | |
| <p>RAP per item: ${formatNumber(currentRap)}</p> | |
| <p>They own: ${maxQuantity}</p> | |
| <div class="trade-item-quantity"> | |
| <button onclick="changeQuantity('theirs', "${escapeHtml(item.name)}", -1)" ${currentQuantity === 0 ? 'disabled' : ''}>-</button> | |
| <span>${currentQuantity}</span> | |
| <button onclick="changeQuantity('theirs', "${escapeHtml(item.name)}", 1)" ${currentQuantity >= maxQuantity ? 'disabled' : ''}>+</button> | |
| </div> | |
| </div> | |
| `; | |
| } | |
| }); | |
| } | |
| $('#your-trade-items').html(yourHtml || '<p>No tradeable items found</p>'); | |
| $('#their-trade-items').html(theirHtml || '<p>No tradeable items found</p>'); | |
| $('#trade-status').text(''); | |
| $('#send-trade-btn').prop('disabled', currentUser.banned); | |
| $('.trade-buttons button').prop('disabled', currentUser.banned); | |
| updateTradeValues(); | |
| } | |
| function updateTradeValues() { | |
| let yourTotalValue = selectedItems.yours.reduce((total, itemName) => { | |
| let userItem = currentUser.inventory.find(i => i.name === itemName); | |
| let catalogItem = catalogItems.find(i => i.name === itemName); | |
| let itemRap = catalogItem ? catalogItem.rap : userItem.rap; | |
| return total + itemRap; | |
| }, 0); | |
| let partner = users.find(u => u.username === $('#trade-partner').val()); | |
| let theirTotalValue = selectedItems.theirs.reduce((total, itemName) => { | |
| let partnerItem = partner?.inventory.find(i => i.name === itemName); | |
| let catalogItem = catalogItems.find(i => i.name === itemName); | |
| let itemRap = catalogItem ? catalogItem.rap : (partnerItem ? partnerItem.rap : 0); | |
| return total + itemRap; | |
| }, 0); | |
| $('#your-trade-value').text(formatNumber(yourTotalValue)); | |
| $('#their-trade-value').text(formatNumber(theirTotalValue)); | |
| } | |
| function toggleTradeItem(side, itemName) { | |
| let partner = users.find(u => u.username === $('#trade-partner').val()); | |
| let inventory = side === 'yours' ? currentUser.inventory : partner.inventory; | |
| let item = inventory.find(i => i.name === itemName); | |
| if (!item || item.rap === 0) return; | |
| let currentQuantity = selectedItems[side].filter(i => i === itemName).length; | |
| let maxQuantity = item.quantity || 1; | |
| if (currentQuantity >= maxQuantity) { | |
| selectedItems[side] = selectedItems[side].filter(i => i !== itemName); | |
| } else { | |
| selectedItems[side].push(itemName); | |
| } | |
| updateTradeItems(); | |
| updateTradeValues(); | |
| } | |
| function changeQuantity(side, itemName, change) { | |
| event.stopPropagation(); | |
| let partner = users.find(u => u.username === $('#trade-partner').val()); | |
| let inventory = side === 'yours' ? currentUser.inventory : partner.inventory; | |
| let item = inventory.find(i => i.name === itemName); | |
| if (!item || item.rap === 0) return; | |
| let currentQuantity = selectedItems[side].filter(i => i === itemName).length; | |
| let maxQuantity = item.quantity || 1; | |
| if (change > 0 && currentQuantity < maxQuantity) { | |
| selectedItems[side].push(itemName); | |
| } else if (change < 0 && currentQuantity > 0) { | |
| let index = selectedItems[side].lastIndexOf(itemName); | |
| if (index !== -1) { | |
| selectedItems[side].splice(index, 1); | |
| } | |
| } | |
| updateTradeItems(); | |
| updateTradeValues(); | |
| } | |
| function clearTrade() { | |
| if (currentUser && currentUser.banned) return; | |
| selectedItems = { | |
| yours: [], | |
| theirs: [] | |
| }; | |
| updateTradeItems(); | |
| updateTradeValues(); | |
| } | |
| function sendTrade() { | |
| if (!currentUser || currentUser.banned) return; | |
| let partner = users.find(u => u.username === $('#trade-partner').val()); | |
| if (!partner) return; | |
| let existingTrade = pendingTrades.find(t => t.sender === currentUser.username && t.receiver === partner.username || t.sender === partner.username && t.receiver === currentUser.username); | |
| if (existingTrade) { | |
| pendingTrades = pendingTrades.filter(t => t.id !== existingTrade.id); | |
| } | |
| showLoadingScreen(); | |
| setTimeout(() => { | |
| let newTrade = { | |
| id: Date.now(), | |
| sender: currentUser.username, | |
| receiver: partner.username, | |
| senderItems: selectedItems.yours.map(itemName => { | |
| let item = currentUser.inventory.find(i => i.name === itemName); | |
| return { | |
| name: itemName, | |
| rap: item.rap | |
| }; | |
| }), | |
| receiverItems: selectedItems.theirs.map(itemName => { | |
| let item = partner.inventory.find(i => i.name === itemName); | |
| return { | |
| name: itemName, | |
| rap: item.rap | |
| }; | |
| }), | |
| timestamp: new Date().toISOString(), | |
| status: 'pending' | |
| }; | |
| pendingTrades.push(newTrade); | |
| saveToLocalStorage(); | |
| hideLoadingScreen(); | |
| showSuccessBanner('Trade request sent!'); | |
| clearTrade(); | |
| updateTradeItems(); | |
| updatePendingTrades(); | |
| }, 1200); | |
| } | |
| function editTrade(tradeId) { | |
| let trade = pendingTrades.find(t => t.id === tradeId); | |
| if (!trade) return; | |
| $('#trade-partner').val(trade.sender === currentUser.username ? trade.receiver : trade.sender); | |
| selectedItems = { | |
| yours: [], | |
| theirs: [] | |
| }; | |
| if (trade.receiver === currentUser.username) { | |
| trade.receiverItems.forEach(item => { | |
| selectedItems.yours.push(item.name); | |
| }); | |
| trade.senderItems.forEach(item => { | |
| selectedItems.theirs.push(item.name); | |
| }); | |
| } else { | |
| trade.senderItems.forEach(item => { | |
| selectedItems.yours.push(item.name); | |
| }); | |
| trade.receiverItems.forEach(item => { | |
| selectedItems.theirs.push(item.name); | |
| }); | |
| } | |
| pendingTrades = pendingTrades.filter(t => t.id !== tradeId); | |
| saveToLocalStorage(); | |
| updateTradeItems(); | |
| updatePendingTrades(); | |
| } | |
| function updateUserRAP(user) { | |
| if (typeof user.rap === 'number') return; | |
| if (!user.inventory) return; | |
| user.rap = user.inventory.reduce((sum, item) => { | |
| let catalogItem = catalogItems.find(i => i.name === item.name); | |
| let itemRap = catalogItem ? catalogItem.rap : item.rap; | |
| return sum + itemRap * (item.quantity || 1); | |
| }, 0); | |
| } | |
| function updateDeclinedTrades() { | |
| let relevantDeclinedTrades = declinedTrades.filter(trade => trade.sender === currentUser.username || trade.receiver === currentUser.username).sort((a, b) => new Date(b.declinedAt) - new Date(a.declinedAt)); | |
| let html = '<h3>Declined Trades</h3>'; | |
| relevantDeclinedTrades.forEach(trade => { | |
| let senderItems = []; | |
| let receiverItems = []; | |
| trade.senderItems.forEach(item => { | |
| if (!senderItems[item.name]) { | |
| let catalogItem = catalogItems.find(i => i.name === item.name); | |
| let currentRap = catalogItem ? catalogItem.rap : item.rap; | |
| senderItems[item.name] = { | |
| name: item.name, | |
| rap: currentRap, | |
| count: 1 | |
| }; | |
| } else { | |
| senderItems[item.name].count++; | |
| } | |
| }); | |
| trade.receiverItems.forEach(item => { | |
| if (!receiverItems[item.name]) { | |
| let catalogItem = catalogItems.find(i => i.name === item.name); | |
| let currentRap = catalogItem ? catalogItem.rap : item.rap; | |
| receiverItems[item.name] = { | |
| name: item.name, | |
| rap: currentRap, | |
| count: 1 | |
| }; | |
| } else { | |
| receiverItems[item.name].count++; | |
| } | |
| }); | |
| let senderItemsList = Object.values(senderItems); | |
| let receiverItemsList = Object.values(receiverItems); | |
| let senderTotalRap = senderItemsList.reduce((sum, item) => sum + item.rap * item.count, 0); | |
| let receiverTotalRap = receiverItemsList.reduce((sum, item) => sum + item.rap * item.count, 0); | |
| // Determine if the current user is the sender or receiver | |
| const isSender = trade.sender === currentUser.username; | |
| const senderLabel = isSender ? `${trade.sender} (GIVING)` : `${trade.sender} (GETTING)`; | |
| const receiverLabel = isSender ? `${trade.receiver} (GETTING)` : `${trade.receiver} (GIVING)`; | |
| html += ` | |
| <div class="completed-trade" style="border-left: 3px solid #ff4444;"> | |
| <div class="trade-details"> | |
| <div class="trade-side-details"> | |
| <h5>${senderLabel}'s Offer (Total RAP: ${formatNumber(senderTotalRap)})</h5> | |
| <div class="trade-items-list"> | |
| ${senderItemsList.map(item => ` | |
| <div class="trade-item-entry"> | |
| <img src="${catalogItems.find(i => i.name === item.name)?.image || ''}" alt="${item.name}"> | |
| <span>${item.name} x${item.count} (Each Item Value: ${formatNumber(item.rap)})</span> | |
| </div> | |
| `).join('')} | |
| </div> | |
| </div> | |
| <div class="trade-side-details"> | |
| <h5>${receiverLabel}'s Offer (Total RAP: ${formatNumber(receiverTotalRap)})</h5> | |
| <div class="trade-items-list"> | |
| ${receiverItemsList.map(item => ` | |
| <div class="trade-item-entry"> | |
| <img src="${catalogItems.find(i => i.name === item.name)?.image || ''}" alt="${item.name}"> | |
| <span>${item.name} x${item.count} (Each Item Value: ${formatNumber(item.rap)})</span> | |
| </div> | |
| `).join('')} | |
| </div> | |
| </div> | |
| </div> | |
| <div class="trade-timestamp">Declined by ${trade.declinedBy} on ${new Date(trade.declinedAt).toLocaleString()}</div> | |
| </div> | |
| `; | |
| }); | |
| let declinedTradesContainer = document.createElement('div'); | |
| declinedTradesContainer.className = 'declined-trades'; | |
| declinedTradesContainer.innerHTML = html; | |
| $('.declined-trades').remove(); | |
| $('.completed-trades').after(declinedTradesContainer); | |
| } | |
| function groupItems(items) { | |
| let grouped = {}; | |
| items.forEach(item => { | |
| if (!grouped[item.name]) { | |
| grouped[item.name] = { | |
| name: item.name, | |
| rap: item.rap, | |
| count: 1 | |
| }; | |
| } else { | |
| grouped[item.name].count++; | |
| } | |
| }); | |
| return Object.values(grouped); | |
| } | |
| function updatePendingTrades() { | |
| let relevantTrades = pendingTrades.filter(trade => trade.sender === currentUser.username || trade.receiver === currentUser.username).sort((a, b) => new Date(b.timestamp) - new Date(a.timestamp)); | |
| let html = '<div class="pending-trades"><h3>Pending Trades</h3>'; | |
| relevantTrades.forEach(trade => { | |
| let groupedSenderItems = {}; | |
| let groupedReceiverItems = {}; | |
| trade.senderItems.forEach(item => { | |
| let catalogItem = catalogItems.find(i => i.name === item.name); | |
| let currentRap = catalogItem ? catalogItem.rap : item.rap; | |
| if (!groupedSenderItems[item.name]) { | |
| groupedSenderItems[item.name] = { | |
| name: item.name, | |
| rap: currentRap, | |
| count: 1 | |
| }; | |
| } else { | |
| groupedSenderItems[item.name].count++; | |
| } | |
| }); | |
| trade.receiverItems.forEach(item => { | |
| let catalogItem = catalogItems.find(i => i.name === item.name); | |
| let currentRap = catalogItem ? catalogItem.rap : item.rap; | |
| if (!groupedReceiverItems[item.name]) { | |
| groupedReceiverItems[item.name] = { | |
| name: item.name, | |
| rap: currentRap, | |
| count: 1 | |
| }; | |
| } else { | |
| groupedReceiverItems[item.name].count++; | |
| } | |
| }); | |
| let senderItems = Object.values(groupedSenderItems); | |
| let receiverItems = Object.values(groupedReceiverItems); | |
| let senderTotalRap = senderItems.reduce((sum, item) => sum + item.rap * item.count, 0); | |
| let receiverTotalRap = receiverItems.reduce((sum, item) => sum + item.rap * item.count, 0); | |
| // Determine if the current user is the sender or receiver | |
| const isSender = trade.sender === currentUser.username; | |
| const senderLabel = isSender ? `${trade.sender} (GIVING)` : `${trade.sender} (GETTING)`; | |
| const receiverLabel = isSender ? `${trade.receiver} (GETTING)` : `${trade.receiver} (GIVING)`; | |
| html += ` | |
| <div class="pending-trade"> | |
| <div class="trade-details"> | |
| <div class="trade-side-details"> | |
| <h5>${senderLabel}'s Offer (Total RAP: ${formatNumber(senderTotalRap)})</h5> | |
| <div class="trade-items-list"> | |
| ${senderItems.map(item => ` | |
| <div class="trade-item-entry"> | |
| <img src="${catalogItems.find(i => i.name === item.name)?.image || ''}" alt="${item.name}"> | |
| <span>${item.name} x${item.count} (Each Item Value: ${formatNumber(item.rap)})</span> | |
| </div> | |
| `).join('')} | |
| </div> | |
| </div> | |
| <div class="trade-side-details"> | |
| <h5>${receiverLabel}'s Offer (Total RAP: ${formatNumber(receiverTotalRap)})</h5> | |
| <div class="trade-items-list"> | |
| ${receiverItems.map(item => ` | |
| <div class="trade-item-entry"> | |
| <img src="${catalogItems.find(i => i.name === item.name)?.image || ''}" alt="${item.name}"> | |
| <span>${item.name} x${item.count} (Each Item Value: ${formatNumber(item.rap)})</span> | |
| </div> | |
| `).join('')} | |
| </div> | |
| </div> | |
| </div> | |
| <div class="trade-timestamp">Sent: ${new Date(trade.timestamp).toLocaleString()}</div> | |
| <div class="trade-actions"> | |
| <button class="button" onclick="editTrade(${trade.id})">Edit</button> | |
| ${trade.receiver === currentUser.username ? ` | |
| <button class="button" onclick="acceptTrade(${trade.id})">Accept</button> | |
| ` : ''} | |
| <button class="button" onclick="declineTrade(${trade.id})">${trade.receiver === currentUser.username ? 'Decline' : 'Cancel'}</button> | |
| </div> | |
| </div> | |
| `; | |
| }); | |
| html += '</div>'; | |
| $('.pending-trades').remove(); | |
| $('.completed-trades').before(html); | |
| } | |
| function groupTradeItems(items) { | |
| let grouped = {}; | |
| items.forEach(item => { | |
| if (!grouped[item.name]) { | |
| grouped[item.name] = { | |
| name: item.name, | |
| rap: item.rap, | |
| count: 1 | |
| }; | |
| } else { | |
| grouped[item.name].count++; | |
| } | |
| }); | |
| return Object.values(grouped); | |
| } | |
| function updateCompletedTrades() { | |
| let relevantCompletedTrades = completedTrades.filter(trade => trade.sender === currentUser.username || trade.receiver === currentUser.username).sort((a, b) => new Date(b.completedAt) - new Date(a.completedAt)); | |
| let html = '<h3>Completed Trades</h3>'; | |
| relevantCompletedTrades.forEach(trade => { | |
| let senderItems = groupTradeItems(trade.senderItems.map(item => { | |
| let catalogItem = catalogItems.find(i => i.name === item.name); | |
| return { | |
| name: item.name, | |
| rap: catalogItem ? catalogItem.rap : item.rap | |
| }; | |
| })); | |
| let receiverItems = groupTradeItems(trade.receiverItems.map(item => { | |
| let catalogItem = catalogItems.find(i => i.name === item.name); | |
| return { | |
| name: item.name, | |
| rap: catalogItem ? catalogItem.rap : item.rap | |
| }; | |
| })); | |
| let senderTotalRap = senderItems.reduce((sum, item) => sum + item.rap * item.count, 0); | |
| let receiverTotalRap = receiverItems.reduce((sum, item) => sum + item.rap * item.count, 0); | |
| // Determine if the current user is the sender or receiver | |
| const isSender = trade.sender === currentUser.username; | |
| const senderLabel = isSender ? `${trade.sender} (GIVING)` : `${trade.sender} (GETTING)`; | |
| const receiverLabel = isSender ? `${trade.receiver} (GETTING)` : `${trade.receiver} (GIVING)`; | |
| html += ` | |
| <div class="completed-trade"> | |
| <div class="trade-details"> | |
| <div class="trade-side-details"> | |
| <h5>${senderLabel}'s Offer (Total RAP: ${formatNumber(senderTotalRap)})</h5> | |
| <div class="trade-items-list"> | |
| ${senderItems.map(item => ` | |
| <div class="trade-item-entry"> | |
| <img src="${catalogItems.find(i => i.name === item.name)?.image || ''}" alt="${item.name}"> | |
| <span>${item.name} x${item.count} (Each Item Value: ${formatNumber(item.rap)})</span> | |
| </div> | |
| `).join('')} | |
| </div> | |
| </div> | |
| <div class="trade-side-details"> | |
| <h5>${receiverLabel}'s Offer (Total RAP: ${formatNumber(receiverTotalRap)})</h5> | |
| <div class="trade-items-list"> | |
| ${receiverItems.map(item => ` | |
| <div class="trade-item-entry"> | |
| <img src="${catalogItems.find(i => i.name === item.name)?.image || ''}" alt="${item.name}"> | |
| <span>${item.name} x${item.count} (Each Item Value: ${formatNumber(item.rap)})</span> | |
| </div> | |
| `).join('')} | |
| </div> | |
| </div> | |
| </div> | |
| <div class="trade-timestamp">Completed: ${new Date(trade.completedAt).toLocaleString()}</div> | |
| </div> | |
| `; | |
| }); | |
| $('#completed-trades-list').html(html); | |
| } | |
| function updateUsersPage() { | |
| let usersHtml = ''; | |
| for (let user of users) { | |
| usersHtml += `<p class="${user.username === currentUser.username ? 'current-user' : ''}" onclick="showUserProfile('${user.username}')">${user.username}</p>`; | |
| } | |
| $('#users-list-page').html(usersHtml); | |
| } | |
| function updateProfileInventory(user) { | |
| let inventoryHtml = ''; | |
| // Check if inventory is private and viewer is not owner or Roblox | |
| const isPrivate = user.privateInventory && currentUser.username !== user.username && currentUser.username !== 'Roblox'; | |
| if (isPrivate) { | |
| inventoryHtml = '<p style="text-align: center; font-size: 20px; color: #B8B8B8; margin-top: 50px;">This user\'s inventory is private</p>'; | |
| $('#profile-inventory-items').html(inventoryHtml); | |
| $('#profile-inventory-pagination').hide(); | |
| // Set RAP display to "Private" | |
| $('#rap').text('Private'); | |
| return; | |
| } | |
| if (user.inventory) { | |
| let expandedInventory = []; | |
| user.inventory.forEach(item => { | |
| for (let i = 0; i < (item.quantity || 1); i++) { | |
| expandedInventory.push(item); | |
| } | |
| }); | |
| let sortedInventory = expandedInventory.sort((a, b) => { | |
| // Remove the equipped items sorting logic | |
| let catalogItemA = catalogItems.find(i => i.name === a.name); | |
| let catalogItemB = catalogItems.find(i => i.name === b.name); | |
| let isLimitedA = catalogItemA && (catalogItemA.limitedStatus === 'limited' || catalogItemA.limitedStatus === 'unlimited'); | |
| let isLimitedB = catalogItemB && (catalogItemB.limitedStatus === 'limited' || catalogItemB.limitedStatus === 'unlimited'); | |
| let isOffSaleA = catalogItemA && catalogItemA.offSale; | |
| let isOffSaleB = catalogItemB && catalogItemB.offSale; | |
| if (isLimitedA && !isLimitedB) return -1; | |
| if (!isLimitedA && isLimitedB) return 1; | |
| if (isLimitedA && isLimitedB) { | |
| return (catalogItemB.rap || 0) - (catalogItemA.rap || 0); | |
| } | |
| if (isOffSaleA && !isOffSaleB) return 1; | |
| if (!isOffSaleA && isOffSaleB) return -1; | |
| return (b.price || 0) - (a.price || 0); | |
| }); | |
| for (let item of sortedInventory) { | |
| let limitedIcon = ''; | |
| if (item.limitedStatus === 'unlimited') { | |
| limitedIcon = '<div class="limited-icon unlimited"></div>'; | |
| } else if (item.limitedStatus === 'limited') { | |
| limitedIcon = '<div class="limited-icon limited"></div>'; | |
| } | |
| let catalogItem = catalogItems.find(i => i.name === item.name); | |
| let isOffSale = catalogItem && catalogItem.offSale; | |
| const listings = itemListings.filter(l => l.itemName === item.name) | |
| .sort((a, b) => a.price - b.price); | |
| const lowestListing = listings[0]; | |
| const priceDisplay = (catalogItem.limitedStatus !== 'none' && !lowestListing) ? | |
| '<p style="color: #B8B8B8; font-weight: bold;">No Resellers</p>' : | |
| (lowestListing ? | |
| `<p><span class="robux-icon"></span>${formatNumber(lowestListing.price)}</p>` : | |
| (catalogItem.price === 0 ? | |
| '<p><span class="robux-icon"></span>Free</p>' : | |
| `<p><span class="robux-icon"></span>${formatNumber(catalogItem.price)}</p>`)); | |
| let removeButton = ''; | |
| if (currentUser.username === 'Roblox') { | |
| removeButton = `<button class="button" style="background-color: #ff4444; margin-top: 5px;" onclick="event.stopPropagation(); showRemoveItemConfirmation('${user.username}', '${escapeHtml(item.name)}')">Remove</button>`; | |
| } | |
| inventoryHtml += ` | |
| <div class="catalog-item" onclick="showItemDetail("${escapeHtml(item.name)}");" style="cursor: pointer;"> | |
| <img src="${item.image}" alt="${item.name}"> | |
| ${limitedIcon} | |
| <div class="catalog-item-details"> | |
| <h3>${item.name}</h3> | |
| ${isOffSale ? '<p style="color: #ff4444;">Offsale</p>' : ''} | |
| ${(item.limitedStatus !== 'none' && !item.offSale) ? `<p>RAP: ${formatNumber(catalogItem.rap)}</p>` : ''} | |
| <p>By: Roblox <img src="https://en.help.roblox.com/hc/article_attachments/7997146649876" alt="Verified" style="width: 16px; height: 16px; vertical-align: middle;"></p> | |
| ${removeButton} | |
| </div> | |
| </div> | |
| `; | |
| } | |
| $('#profile-inventory-items').html(inventoryHtml || '<p>No items in inventory</p>'); | |
| updateProfileInventoryPagination(Math.ceil(sortedInventory.length / itemsPerPage), currentProfileInventoryPage); | |
| } | |
| } | |
| function updateProfileInventoryPagination(totalPages, currentPage) { | |
| let paginationHtml = ` | |
| <button onclick="updateProfileInventoryPage(${Math.max(1, currentPage - 1)})" ${currentPage === 1 ? 'disabled' : ''}>←</button> | |
| <span>${currentPage} / ${totalPages}</span> | |
| <button onclick="updateProfileInventoryPage(${Math.min(totalPages, currentPage + 1)})" ${currentPage === totalPages ? 'disabled' : ''}>→</button> | |
| `; | |
| $('#profile-inventory-pagination').html(paginationHtml); | |
| } | |
| function updateProfileInventoryPage(page) { | |
| currentProfileInventoryPage = page; | |
| let user = users.find(u => u.username === currentUser.username); | |
| if (user) { | |
| let inventory = user.inventory; | |
| if (inventory) { | |
| let expandedInventory = []; | |
| inventory.forEach(item => { | |
| for (let i = 0; i < (item.quantity || 1); i++) { | |
| expandedInventory.push(item); | |
| } | |
| }); | |
| let sortedInventory = expandedInventory.sort((a, b) => { | |
| // Remove the equipped items sorting logic | |
| let catalogItemA = catalogItems.find(i => i.name === a.name); | |
| let catalogItemB = catalogItems.find(i => i.name === b.name); | |
| let isLimitedA = catalogItemA && (catalogItemA.limitedStatus === 'limited' || catalogItemA.limitedStatus === 'unlimited'); | |
| let isLimitedB = catalogItemB && (catalogItemB.limitedStatus === 'limited' || catalogItemB.limitedStatus === 'unlimited'); | |
| let isOffSaleA = catalogItemA && catalogItemA.offSale; | |
| let isOffSaleB = catalogItemB && catalogItemB.offSale; | |
| if (isLimitedA && !isLimitedB) return -1; | |
| if (!isLimitedA && isLimitedB) return 1; | |
| if (isLimitedA && isLimitedB) { | |
| return (catalogItemB.rap || 0) - (catalogItemA.rap || 0); | |
| } | |
| if (isOffSaleA && !isOffSaleB) return 1; | |
| if (!isOffSaleA && isOffSaleB) return -1; | |
| return (b.price || 0) - (a.price || 0); | |
| }); | |
| let startIndex = (currentProfileInventoryPage - 1) * itemsPerPage; | |
| let endIndex = startIndex + itemsPerPage; | |
| let pageItems = sortedInventory.slice(startIndex, endIndex); | |
| let inventoryHtml = ''; | |
| for (let item of pageItems) { | |
| if (!item) continue; | |
| let limitedIcon = ''; | |
| if (item.limitedStatus === 'unlimited') { | |
| limitedIcon = '<div class="limited-icon unlimited"></div>'; | |
| } else if (item.limitedStatus === 'limited') { | |
| limitedIcon = '<div class="limited-icon limited"></div>'; | |
| } | |
| let catalogItem = catalogItems.find(i => i.name === item.name) || {}; | |
| let isOffSale = catalogItem && catalogItem.offSale; | |
| const listings = itemListings.filter(l => l.itemName === item.name) | |
| .sort((a, b) => a.price - b.price); | |
| const lowestListing = listings[0]; | |
| const priceDisplay = (catalogItem.limitedStatus !== 'none' && !lowestListing) ? | |
| '<p style="color: #B8B8B8; font-weight: bold;">No Resellers</p>' : | |
| (lowestListing ? | |
| `<p><span class="robux-icon"></span>${formatNumber(lowestListing.price)}</p>` : | |
| (catalogItem.price === 0 ? | |
| '<p><span class="robux-icon"></span>Free</p>' : | |
| `<p><span class="robux-icon"></span>${formatNumber(catalogItem.price)}</p>`)); | |
| let userQuantity = currentUser.inventory?.find(i => i.name === item.name)?.quantity || 0; | |
| let userQuantityDisplay = userQuantity > 0 ? `<div class="item-quantity">x${userQuantity}</div>` : ''; | |
| let buyButtonDisabled = item.offSale || currentUser.banned; | |
| if (!item.limitedStatus && !item.stock && item.timerStatus !== 'timer' && item.quantity > 0) { | |
| buyButtonDisabled = true; | |
| } | |
| if (item.maxPurchase !== null && userQuantity >= item.maxPurchase) { | |
| buyButtonDisabled = true; | |
| } | |
| let canSell = currentUser && !currentUser.banned && | |
| currentUser.inventory && currentUser.inventory.some(i => i.name === item.name) && | |
| (item.limitedStatus === 'limited' || item.limitedStatus === 'unlimited'); | |
| let purchasesDisplay = (item.limitedStatus === 'limited' || item.limitedStatus === 'unlimited') ? | |
| `<p style="color: #B8B8B8;">Purchases: ${formatNumber(item.purchases || 0)}</p>` : ''; | |
| inventoryHtml += ` | |
| <div class="catalog-item" onclick="showItemDetail("${escapeHtml(item.name)}");" style="cursor: pointer;"> | |
| <img src="${item.image}" alt="${item.name}"> | |
| ${limitedIcon} | |
| ${userQuantityDisplay} | |
| <div class="catalog-item-details"> | |
| <h3>${item.name}</h3> | |
| ${isOffSale ? '<p style="color: #ff4444;">Offsale</p>' : ''} | |
| ${(item.limitedStatus !== 'none' && !item.offSale) ? `<p>RAP: ${formatNumber(catalogItem.rap)}</p>` : ''} | |
| ${purchasesDisplay} | |
| <p>By: Roblox <img src="https://en.help.roblox.com/hc/article_attachments/7997146649876" alt="Verified" style="width: 16px; height: 16px; vertical-align: middle;"></p> | |
| </div> | |
| </div> | |
| `; | |
| } | |
| $('#profile-inventory-items').html(inventoryHtml || '<p>No items in inventory</p>'); | |
| } | |
| } | |
| } | |
| function setActiveNavItem(page) { | |
| $('.nav a').removeClass('active'); | |
| if (page === 'trade') { | |
| $('.nav a[onclick*="showTrade"]').addClass('active'); | |
| } else if (page === 'leaderboard') { | |
| $('.nav a[onclick*="showLeaderboard"]').addClass('active'); | |
| } else { | |
| $(`.nav a[onclick*="show${page.charAt(0).toUpperCase() + page.slice(1)}"]`).addClass('active'); | |
| } | |
| } | |
| function updateProfile() { | |
| if (!currentUser) return; | |
| showUserProfile(currentUser.username); | |
| updateRobuxDisplay(); | |
| } | |
| function updateRobuxDisplay() { | |
| if (!currentUser) return; | |
| let robuxAmount = currentUser.robux; | |
| let displayAmount = robuxAmount < 10000 ? formatNumber(robuxAmount) : abbreviateNumber(robuxAmount); | |
| $('#robux-amount').html('<strong>' + displayAmount + '</strong>'); | |
| $('#robux-amount-page').text(formatNumber(robuxAmount)); | |
| } | |
| function abbreviateNumber(number) { | |
| if (number >= 1000000000000) { | |
| let trillions = number / 1000000000000; | |
| if (trillions >= 100) { | |
| return Math.floor(trillions) + 'T+'; | |
| } | |
| return (Math.floor(trillions * 10) / 10).toString().replace('.0', '') + 'T+'; | |
| } else if (number >= 1000000000) { | |
| let billions = number / 1000000000; | |
| if (billions >= 100) { | |
| return Math.floor(billions) + 'B+'; | |
| } else if (billions >= 10) { | |
| return (Math.floor(billions * 10) / 10).toString().replace('.0', '') + 'B+'; | |
| } else { | |
| return (Math.floor(billions * 10) / 10).toString().replace('.0', '') + 'B+'; | |
| } | |
| } else if (number >= 1000000) { | |
| return (Math.floor(number / 1000000 * 10) / 10).toString().replace('.0', '') + 'M+'; | |
| } else if (number >= 10000) { | |
| return (Math.floor(number / 1000 * 10) / 10).toString().replace('.0', '') + 'K+'; | |
| } | |
| return formatNumber(number); | |
| } | |
| function formatNumber(number) { | |
| return number.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ","); | |
| } | |
| function updateRobuxPage() { | |
| if (!currentUser) return; | |
| updateRobuxDisplay(); | |
| let transactionHtml = ''; | |
| let totalSpent = 0; | |
| if (currentUser.transactions) { | |
| for (let transaction of currentUser.transactions.slice().reverse()) { | |
| transactionHtml += ` | |
| <div class="transaction-item"> | |
| <p>${transaction.type}: ${transaction.item} - <span class="robux-icon"></span>${formatNumber(transaction.amount)}</p> | |
| <p>Date: ${transaction.date}</p> | |
| </div> | |
| `; | |
| if (transaction.type === 'Purchase') { | |
| totalSpent += transaction.amount; | |
| } | |
| } | |
| } | |
| $('#transaction-list').html(transactionHtml); | |
| $('#total-spent').text(formatNumber(totalSpent)); | |
| } | |
| function updateCatalog() { | |
| let searchTerm = $('#catalog-search').val().toLowerCase().trim(); | |
| let sortOption = $('#catalog-sort').val(); | |
| let sortedCatalogItems = [...catalogItems].filter(item => | |
| // Only show hidden items when searching for them | |
| ((!item.hidden || searchTerm) && | |
| (!searchTerm || item.name.toLowerCase().includes(searchTerm))) | |
| ); | |
| sortedCatalogItems.sort((a, b) => { | |
| if (a.timerStatus === 'timer' && b.timerStatus !== 'timer') return -1; | |
| if (a.timerStatus !== 'timer' && b.timerStatus === 'timer') return 1; | |
| if (a.isNew && (!a.newViewedBy || !a.newViewedBy.includes(currentUser.username))) return -1; | |
| if (b.isNew && (!b.newViewedBy || !b.newViewedBy.includes(currentUser.username))) return 1; | |
| // Sort by the selected option | |
| switch(sortOption) { | |
| case 'newest': | |
| return new Date(b.createdAt || 0) - new Date(a.createdAt || 0); | |
| case 'oldest': | |
| return new Date(a.createdAt || 0) - new Date(b.createdAt || 0); | |
| case 'price-asc': | |
| let aListingsAsc = itemListings.filter(l => l.itemName === a.name) | |
| .sort((a, b) => a.price - b.price); | |
| let bListingsAsc = itemListings.filter(l => l.itemName === b.name) | |
| .sort((a, b) => a.price - b.price); | |
| let aPriceAsc = !a.offSale && (a.limitedStatus === 'none' ? a.price : (aListingsAsc[0]?.price || null)); | |
| let bPriceAsc = !b.offSale && (b.limitedStatus === 'none' ? b.price : (bListingsAsc[0]?.price || null)); | |
| if (aPriceAsc !== null && bPriceAsc === null) return -1; | |
| if (aPriceAsc === null && bPriceAsc !== null) return 1; | |
| if (aPriceAsc !== null && bPriceAsc !== null) return aPriceAsc - bPriceAsc; | |
| break; | |
| case 'price-desc': | |
| let aListingsDesc = itemListings.filter(l => l.itemName === a.name) | |
| .sort((a, b) => a.price - b.price); | |
| let bListingsDesc = itemListings.filter(l => l.itemName === b.name) | |
| .sort((a, b) => a.price - b.price); | |
| let aPriceDesc = !a.offSale && (a.limitedStatus === 'none' ? a.price : (aListingsDesc[0]?.price || null)); | |
| let bPriceDesc = !b.offSale && (b.limitedStatus === 'none' ? b.price : (bListingsDesc[0]?.price || null)); | |
| if (aPriceDesc !== null && bPriceDesc === null) return -1; | |
| if (aPriceDesc === null && bPriceDesc !== null) return 1; | |
| if (aPriceDesc !== null && bPriceDesc !== null) return bPriceDesc - aPriceDesc; | |
| break; | |
| } | |
| let isOffSaleA = a.offSale; | |
| let isOffSaleB = b.offSale; | |
| if (isOffSaleA && !isOffSaleB) return 1; | |
| if (!isOffSaleA && isOffSaleB) return -1; | |
| // Default fallback sorting | |
| if (a.rap > b.rap) return -1; | |
| if (a.rap < b.rap) return 1; | |
| return 0; | |
| }); | |
| let startIndex = (currentCatalogPage - 1) * itemsPerPage; | |
| let endIndex = startIndex + itemsPerPage; | |
| let pageItems = sortedCatalogItems.slice(startIndex, endIndex); | |
| let catalogHtml = ''; | |
| for (let item of pageItems) { | |
| const listings = itemListings.filter(l => l.itemName === item.name) | |
| .sort((a, b) => a.price - b.price); | |
| let priceDisplay; | |
| if (item.offSale) { | |
| priceDisplay = '<p style="color: #ff4444;">Offsale</p>'; | |
| } else if (item.limitedStatus !== 'none') { | |
| const lowestListing = listings[0]; | |
| priceDisplay = !lowestListing ? | |
| '<p style="color: #B8B8B8; font-weight: bold;">No Resellers</p>' : | |
| `<p><span class="robux-icon"></span>${formatNumber(lowestListing.price)}</p>`; | |
| } else { | |
| priceDisplay = item.price === 0 ? | |
| '<p><span class="robux-icon"></span>Free</p>' : | |
| `<p><span class="robux-icon"></span>${formatNumber(item.price)}</p>`; | |
| } | |
| let limitedIcon = ''; | |
| if (item.limitedStatus === 'unlimited') { | |
| limitedIcon = '<div class="limited-icon unlimited"></div>'; | |
| } else if (item.limitedStatus === 'limited') { | |
| limitedIcon = '<div class="limited-icon limited"></div>'; | |
| } | |
| let isNewForCurrentUser = item.isNew && (!item.newViewedBy || !item.newViewedBy.includes(currentUser.username)); | |
| let hasTimer = item.timerStatus === 'timer'; | |
| let stockText = ''; | |
| if (item.stock !== undefined && !item.rap) { | |
| if (item.stock > 0) { | |
| stockText = `<p style="color: #808080;">Stock: ${formatNumber(item.stock)}</p>`; | |
| } | |
| } | |
| let userQuantity = currentUser.inventory?.find(i => i.name === item.name)?.quantity || 0; | |
| let userQuantityDisplay = userQuantity > 0 ? `<div class="item-quantity">x${userQuantity}</div>` : ''; | |
| let buyButtonDisabled = item.offSale || currentUser.banned; | |
| if (!item.limitedStatus && !item.stock && item.timerStatus !== 'timer' && item.quantity > 0) { | |
| buyButtonDisabled = true; | |
| } | |
| if (item.maxPurchase !== null && userQuantity >= item.maxPurchase) { | |
| buyButtonDisabled = true; | |
| } | |
| let canSell = currentUser && !currentUser.banned && | |
| currentUser.inventory && currentUser.inventory.some(i => i.name === item.name) && | |
| (item.limitedStatus === 'limited' || item.limitedStatus === 'unlimited'); | |
| let purchasesDisplay = (item.limitedStatus === 'limited' || item.limitedStatus === 'unlimited') ? | |
| `<p style="color: #B8B8B8;">Purchases: ${formatNumber(item.purchases || 0)}</p>` : ''; | |
| catalogHtml += ` | |
| <div class="catalog-item" onclick="showItemDetail("${escapeHtml(item.name)}");" style="cursor: pointer;"> | |
| <img src="${item.image}" alt="${item.name}"> | |
| ${limitedIcon} | |
| ${userQuantityDisplay} | |
| <div class="catalog-item-details"> | |
| <h3>${item.name}</h3> | |
| ${priceDisplay} | |
| ${(item.limitedStatus !== 'none' && !item.offSale) ? `<p>RAP: ${formatNumber(item.rap)}</p>` : ''} | |
| ${purchasesDisplay} | |
| <p><span class="clickable" style="text-decoration: underline; color: inherit;" onclick="event.stopPropagation(); showUserProfile('Roblox');">By: Roblox</span> <img src="https://en.help.roblox.com/hc/article_attachments/7997146649876" alt="Verified" style="width: 16px; height: 16px; vertical-align: middle;"></p> | |
| ${stockText} | |
| </div> | |
| ${isNewForCurrentUser ? `<div class="new-text" onclick="markItemAsViewed(event, "${escapeHtml(item.name)}")">New</div>` : ''} | |
| ${hasTimer && (!item.stock || item.stock > 0) ? '<div class="timer-text' + (isNewForCurrentUser ? ' with-new' : '') + '"></div>' : ''} | |
| </div> | |
| `; | |
| } | |
| $('#catalog-items').html(catalogHtml); | |
| updatePaginationWithArrows(Math.ceil(sortedCatalogItems.length / itemsPerPage), currentCatalogPage, 'catalog-pagination', changeCatalogPage); | |
| } | |
| function markItemAsViewed(event, itemName) { | |
| event.stopPropagation(); | |
| let item = catalogItems.find(i => i.name === itemName); | |
| if (item && (!item.newViewedBy || !item.newViewedBy.includes(currentUser.username))) { | |
| if (!item.newViewedBy) item.newViewedBy = []; | |
| item.newViewedBy.push(currentUser.username); | |
| saveToLocalStorage(); | |
| updateCatalog(); | |
| } | |
| } | |
| function updatePaginationWithArrows(totalPages, currentPage, paginationId, changePageFunction) { | |
| let paginationHtml = ` | |
| <button onclick="${changePageFunction.name}(${Math.max(1, currentPage - 1)})" ${currentPage === 1 ? 'disabled' : ''}>←</button> | |
| <span>${currentPage} / ${totalPages}</span> | |
| <button onclick="${changePageFunction.name}(${Math.min(totalPages, currentPage + 1)})" ${currentPage === totalPages ? 'disabled' : ''}>→</button> | |
| `; | |
| $(`#${paginationId}`).html(paginationHtml); | |
| } | |
| function changeCatalogPage(page) { | |
| currentCatalogPage = page; | |
| updateCatalog(); | |
| } | |
| function showUploadPopup() { | |
| $('#upload-popup').show(); | |
| $('#overlay').show(); | |
| } | |
| function confirmUpload() { | |
| $('#upload-popup').hide(); | |
| $('#upload-confirmation-popup').show(); | |
| $('#overlay').show(); | |
| } | |
| function uploadItem() { | |
| let itemName = $('#item-name').val().trim(); | |
| if (!itemName) { | |
| alert('Please enter an item name'); | |
| return; | |
| } | |
| let safeItemName = itemName.replace(/'/g, ""); | |
| let stock = parseInt($('#item-stock').val()); | |
| let maxPurchase = parseInt($('#item-max-purchase').val()) || null; | |
| let isOffsale = $('#item-offsale').is(':checked'); | |
| let isHidden = $('#item-hidden').is(':checked'); | |
| let timerStatus = $('#item-timer-status').val(); | |
| let newItem = { | |
| name: safeItemName, | |
| image: $('#upload-preview-image').attr('src'), // Ensure the image URL is set correctly | |
| price: parseInt($('#item-price').val()), | |
| rap: parseInt($('#item-rap').val()) || 0, | |
| offSale: isOffsale, | |
| hidden: isHidden, | |
| limitedStatus: $('#item-limited-status').val(), | |
| timerStatus: timerStatus, | |
| maxPurchase: maxPurchase, | |
| isNew: !isOffsale, | |
| createdAt: new Date().toISOString(), | |
| newViewedBy: isOffsale ? users.map(u => u.username) : [], | |
| description: $('#item-description').val().trim() || 'No description available.', | |
| purchases: 0 | |
| }; | |
| // Add timer information if timer is enabled | |
| if (timerStatus === 'timer') { | |
| const hours = parseInt($('#item-timer-hours').val()) || 0; | |
| const minutes = parseInt($('#item-timer-minutes').val()) || 0; | |
| const seconds = parseInt($('#item-timer-seconds').val()) || 0; | |
| // Only set timer if time is specified | |
| if (hours > 0 || minutes > 0 || seconds > 0) { | |
| const timerDuration = (hours * 60 * 60 + minutes * 60 + seconds) * 1000; | |
| newItem.timerEndTime = Date.now() + timerDuration; | |
| } | |
| } | |
| if (!isNaN(stock) && stock > 0) { | |
| newItem.stock = stock; | |
| newItem.originalStock = stock; | |
| } | |
| catalogItems.push(newItem); | |
| let robloxUser = users.find(u => u.username === 'Roblox'); | |
| if (robloxUser) { | |
| if (!robloxUser.inventory) robloxUser.inventory = []; | |
| robloxUser.inventory.push({ | |
| ...newItem, | |
| quantity: 1 | |
| }); | |
| robloxUser.rap = robloxUser.inventory.reduce((sum, item) => { | |
| let catalogItem = catalogItems.find(i => i.name === item.name); | |
| let itemRap = catalogItem ? catalogItem.rap : item.rap; | |
| return sum + itemRap * (item.quantity || 1); | |
| }, 0); | |
| } | |
| $('#item-name').val(''); | |
| $('#item-image').val(''); | |
| $('#item-price').val(''); | |
| $('#item-rap').val(''); | |
| $('#item-stock').val(''); | |
| $('#item-max-purchase').val(''); | |
| $('#item-limited-status').val('none'); | |
| $('#item-timer-status').val('none'); | |
| $('#item-offsale').prop('checked', false); | |
| $('#item-hidden').prop('checked', false); | |
| $('#item-description').val(''); | |
| // Reset timer fields | |
| $('#item-timer-hours').val(''); | |
| $('#item-timer-minutes').val(''); | |
| $('#item-timer-seconds').val(''); | |
| $('#timer-fields').hide(); | |
| closePopup('upload-confirmation-popup'); | |
| showCatalog(); | |
| updateProfile(); | |
| saveToLocalStorage(); | |
| } | |
| function showBuyConfirmation(itemName) { | |
| if (!currentUser) return; | |
| itemToBuy = catalogItems.find(i => i.name === itemName); | |
| if (!itemToBuy || itemToBuy.offSale || currentUser.banned) return; | |
| if (itemToBuy.limitedStatus === 'limited' || itemToBuy.limitedStatus === 'unlimited') { | |
| let listingsHtml = '<h4>Current Listings</h4>'; | |
| const listings = itemListings.filter(l => l.itemName === itemName) | |
| .sort((a, b) => a.price - b.price); | |
| if (listings.length > 0) { | |
| listingsHtml += ` | |
| ${listings.map(listing => ` | |
| <div class="listing"> | |
| <span> | |
| <span class="clickable" onclick="showUserProfile('${listing.seller}'); closePopup('item-listings-popup')"> ${listing.seller}</span> - | |
| <span class="robux-icon"></span>${formatNumber(listing.price)} | |
| </span> | |
| <div> | |
| ${listing.seller !== currentUser.username ? | |
| `<button class="button" onclick="showListingConfirmation('${listing.id}')">Buy</button>` : | |
| `<button class="button" onclick="deleteListing('${listing.id}')">Delete</button>`} | |
| </div> | |
| </div> | |
| `).join('')} | |
| `; | |
| } else { | |
| listingsHtml += '<p style="color: #B8B8B8; font-weight: bold;">No Resellers</p>'; | |
| } | |
| listingsHtml += '<h4>Purchase History</h4><div class="purchase-history">'; | |
| const itemHistory = purchaseHistory | |
| .filter(purchase => purchase.itemName === itemName) | |
| .slice() | |
| .reverse(); | |
| if (itemHistory.length > 0) { | |
| listingsHtml += ` | |
| ${itemHistory.map(purchase => ` | |
| <div class="purchase-history-item"> | |
| Sold for <span class="robux-icon"></span>${formatNumber(purchase.price)} at ${new Date(purchase.date).toLocaleDateString()} | |
| By <span class="clickable" onclick="showUserProfile('${purchase.seller}'); closePopup('item-listings-popup')"> ${purchase.seller}</span> to | |
| <span class="clickable" onclick="showUserProfile('${purchase.buyer}'); closePopup('item-listings-popup')"> ${purchase.buyer}</span> | |
| </div> | |
| `).join('')} | |
| `; | |
| } else { | |
| listingsHtml += '<p>No purchase history for this item</p>'; | |
| } | |
| listingsHtml += '</div>'; | |
| $('#item-listings').html(listingsHtml); | |
| $('#item-listings-popup').show(); | |
| $('#overlay').show(); | |
| // Scroll to top | |
| $('#item-listings').scrollTop(0); | |
| } else { | |
| // Rest of existing non-limited item purchase logic... | |
| if (itemToBuy.stock !== undefined && itemToBuy.stock <= 0) { | |
| alert('Item is out of stock!'); | |
| return; | |
| } | |
| let existingItem = currentUser.inventory?.find(i => i.name === itemToBuy.name); | |
| if (existingItem && !itemToBuy.limitedStatus && itemToBuy.timerStatus !== 'timer') { | |
| alert('You can only own one copy of this item.'); | |
| return; | |
| } | |
| if (itemToBuy.maxPurchase !== null && existingItem && existingItem.quantity >= itemToBuy.maxPurchase) { | |
| alert(`You can only own ${itemToBuy.maxPurchase} of this item.`); | |
| return; | |
| } | |
| $('#buy-confirmation-image').attr('src', itemToBuy.image); | |
| $('#buy-item-name').text(itemToBuy.name); | |
| $('#buy-item-price').html(`<span class="robux-icon"></span>${formatNumber(itemToBuy.price)}`); | |
| let balanceAfterPurchase = currentUser.robux - itemToBuy.price; | |
| $('#robux-after-purchase').text(formatNumber(balanceAfterPurchase)); | |
| const forceEnable = itemToBuy.stock === 1; | |
| $('.confirm-button').prop('disabled', balanceAfterPurchase < 0 && !forceEnable); | |
| if (balanceAfterPurchase < 0 && !forceEnable) { | |
| $('.confirm-button').css('background-color', '#808080'); | |
| } else { | |
| $('.confirm-button').css('background-color', '#ffffff'); | |
| } | |
| $('#buy-confirmation-popup').show(); | |
| $('#overlay').show(); | |
| } | |
| } | |
| function cancelPurchase() { | |
| closePopup('buy-confirmation-popup'); | |
| } | |
| function confirmPurchase() { | |
| if (!currentUser || !itemToBuy || currentUser.banned) return; | |
| showLoadingScreen(); | |
| setTimeout(() => { | |
| let catalogItem = catalogItems.find(i => i.name === itemToBuy.name); | |
| if (catalogItem.stock !== undefined) { | |
| if (catalogItem.stock <= 0) { | |
| hideLoadingScreen(); | |
| showSuccessBanner('Item is out of stock!'); | |
| closePopup('buy-confirmation-popup'); | |
| return; | |
| } | |
| catalogItem.stock--; | |
| // Update stock text on item detail page if it's visible | |
| if ($('#item-detail-page').is(':visible')) { | |
| let stockText = ''; | |
| if (catalogItem.stock !== undefined && !catalogItem.rap) { | |
| if (catalogItem.stock > 0) { | |
| stockText = `<p style="color: #808080; margin-top: 10px;">Stock: ${formatNumber(catalogItem.stock)}</p>`; | |
| } | |
| } | |
| // Find and update the stock text in the price display | |
| let priceHtml = $('#detail-item-price').html(); | |
| priceHtml = priceHtml.replace(/<p style="color: #808080; margin-top: 10px;">Stock: .*?<\/p>/, ''); | |
| priceHtml += stockText; | |
| $('#detail-item-price').html(priceHtml); | |
| } | |
| if (catalogItem.stock === 0) { | |
| catalogItem.limitedStatus = 'unlimited'; | |
| catalogItem.rap = 0; // Reset RAP when going back on sale | |
| // Clear any existing listings since it's a new limited item | |
| itemListings = itemListings.filter(listing => listing.itemName !== itemToBuy.name); | |
| } | |
| // Force enable confirm button if purchasing the last stock | |
| if (catalogItem.stock === 0 || catalogItem.originalStock === 1) { | |
| $('.confirm-button').prop('disabled', false); | |
| $('.confirm-button').css('background-color', '#ffffff'); | |
| } | |
| } | |
| if (currentUser.robux < itemToBuy.price) { | |
| hideLoadingScreen(); | |
| showSuccessBanner('Not enough Robux!'); | |
| closePopup('buy-confirmation-popup'); | |
| return; | |
| } | |
| let existingItem = currentUser.inventory?.find(i => i.name === itemToBuy.name); | |
| if (existingItem && !itemToBuy.limitedStatus && itemToBuy.timerStatus !== 'timer') { | |
| hideLoadingScreen(); | |
| showSuccessBanner('You can only own one copy of this item.'); | |
| closePopup('buy-confirmation-popup'); | |
| return; | |
| } | |
| if (itemToBuy.maxPurchase !== null && existingItem && existingItem.quantity >= itemToBuy.maxPurchase) { | |
| hideLoadingScreen(); | |
| showSuccessBanner(`You can only own ${itemToBuy.maxPurchase} of this item.`); | |
| closePopup('buy-confirmation-popup'); | |
| return; | |
| } | |
| // Deduct Robux from buyer | |
| currentUser.robux -= itemToBuy.price; | |
| // Give Roblox account the robux from the purchase | |
| let robloxUser = users.find(u => u.username === 'Roblox'); | |
| if (robloxUser && (!itemToBuy.limitedStatus || !itemToBuy.rap)) { | |
| if (!robloxUser.robux) robloxUser.robux = 0; | |
| robloxUser.robux += itemToBuy.price; | |
| // Update Roblox's transactions | |
| if (!robloxUser.transactions) robloxUser.transactions = []; | |
| robloxUser.transactions.push({ | |
| type: 'Sale', | |
| item: itemToBuy.name, | |
| amount: itemToBuy.price, | |
| date: new Date().toLocaleDateString('en-US') | |
| }); | |
| } | |
| if (!currentUser.inventory) currentUser.inventory = []; | |
| // Update inventory and quantity | |
| if (existingItem) { | |
| existingItem.quantity = (existingItem.quantity || 1) + 1; | |
| } else { | |
| currentUser.inventory.push({ | |
| ...itemToBuy, | |
| quantity: 1 | |
| }); | |
| } | |
| // Update "You own" text on item detail page immediately | |
| if ($('#item-detail-page').is(':visible')) { | |
| let userQuantity = currentUser.inventory.find(i => i.name === itemToBuy.name)?.quantity || 0; | |
| let priceHtml = $('#detail-item-price').html(); | |
| // Remove existing "You own" text if it exists | |
| priceHtml = priceHtml.replace(/<p class="detail-rap-text" style="color: #00A2FF;">You own: <strong>\d+<\/strong><\/p>/, ''); | |
| // Add updated "You own" text | |
| priceHtml += `<p class="detail-rap-text" style="color: #00A2FF;">You own: <strong>${userQuantity}</strong></p>`; | |
| $('#detail-item-price').html(priceHtml); | |
| // Update buy button state | |
| if (itemToBuy.maxPurchase !== null && userQuantity >= itemToBuy.maxPurchase) { | |
| $('#detail-buy-button').prop('disabled', true); | |
| } | |
| } | |
| if (!currentUser.transactions) currentUser.transactions = []; | |
| currentUser.transactions.push({ | |
| type: 'Purchase', | |
| item: itemToBuy.name, | |
| amount: itemToBuy.price, | |
| date: new Date().toLocaleDateString('en-US') | |
| }); | |
| itemToBuy.purchases = (itemToBuy.purchases || 0) + 1; | |
| saveToLocalStorage(); | |
| hideLoadingScreen(); | |
| closePopup('buy-confirmation-popup'); | |
| showSuccessBanner('Successful purchase!'); | |
| updateRobuxDisplay(); | |
| }, 1200); | |
| } | |
| function showSellConfirmation(itemName) { | |
| if (!currentUser) return; | |
| itemToSell = currentUser.inventory.find(i => i.name === itemName); | |
| let catalogItem = catalogItems.find(i => i.name === itemName); | |
| if (catalogItem && catalogItem.offSale) return; | |
| if (itemToSell.rap === 0) { | |
| alert("This item cannot be sold as it has 0 RAP."); | |
| return; | |
| } | |
| let robuxAfterSale = currentUser.robux + (catalogItem ? catalogItem.price : itemToSell.price); | |
| $('#robux-after-sale').text(formatNumber(robuxAfterSale)); | |
| $('#sell-confirmation-image').attr('src', itemToSell.image); | |
| $('#sell-confirmation-popup').show(); | |
| $('#overlay').show(); | |
| } | |
| function confirmSale() { | |
| if (!currentUser || !itemToSell || itemToSell.rap === 0) return; | |
| showLoadingScreen(); | |
| setTimeout(() => { | |
| let catalogItem = catalogItems.find(i => i.name === itemToSell.name); | |
| let salePrice = catalogItem ? catalogItem.price : itemToSell.price; | |
| currentUser.robux += salePrice; | |
| let inventoryItem = currentUser.inventory.find(i => i.name === itemToSell.name); | |
| if (inventoryItem.quantity && inventoryItem.quantity > 1) { | |
| inventoryItem.quantity--; | |
| } else { | |
| currentUser.inventory = currentUser.inventory.filter(i => i.name !== itemToSell.name); | |
| } | |
| currentUser.rap = currentUser.inventory.reduce((sum, item) => { | |
| let catalogItem = catalogItems.find(i => i.name === item.name); | |
| let itemRap = catalogItem ? catalogItem.rap : item.rap; | |
| return sum + itemRap * (item.quantity || 1); | |
| }, 0); | |
| if (!currentUser.transactions) currentUser.transactions = []; | |
| currentUser.transactions.push({ | |
| type: 'Sale', | |
| item: itemToSell.name, | |
| amount: salePrice, | |
| date: new Date().toLocaleDateString('en-US') | |
| }); | |
| updateInventory(); | |
| updateRobuxDisplay(); | |
| saveToLocalStorage(); | |
| hideLoadingScreen(); | |
| showSuccessBanner('Successful sale!'); | |
| closePopup('sell-confirmation-popup'); | |
| showInventory(); | |
| }, 1200); | |
| } | |
| function cancelSale() { | |
| closePopup('sell-confirmation-popup'); | |
| } | |
| function showDatabase() { | |
| $('#database-content').val(JSON.stringify({ | |
| users, | |
| catalogItems | |
| }, null, 2)); | |
| $('#database-popup').show(); | |
| $('#overlay').show(); | |
| } | |
| function saveDatabase() { | |
| try { | |
| let data = JSON.parse($('#database-content').val()); | |
| if (!data || typeof data !== 'object') { | |
| throw new Error('Invalid database format'); | |
| } | |
| // Ensure required properties exist and are arrays | |
| users = Array.isArray(data.users) ? data.users : []; | |
| catalogItems = Array.isArray(data.catalogItems) ? data.catalogItems : []; | |
| // Initialize missing properties | |
| users.forEach(user => { | |
| if (!user.inventory) user.inventory = []; | |
| if (!user.transactions) user.transactions = []; | |
| if (typeof user.robux !== 'number') user.robux = 0; | |
| }); | |
| saveToLocalStorage(); | |
| closePopup('database-popup'); | |
| showSuccessBanner('Database saved successfully!'); | |
| // Only update UI if we have a current user | |
| if (currentUser) { | |
| // Find the current user in the new users array | |
| currentUser = users.find(u => u.username === currentUser.username) || users[0]; | |
| updateCatalog(); | |
| updateProfile(); | |
| updateInventory(); | |
| } | |
| } catch (error) { | |
| console.error('Database save error:', error); | |
| alert('Error saving database: ' + error.message); | |
| } | |
| } | |
| function showUsers() { | |
| let usersHtml = ''; | |
| for (let user of users) { | |
| usersHtml += `<p class="${user.username === currentUser.username ? 'current-user' : ''}" onclick="switchUser('${user.username}')">${user.username}</p>`; | |
| } | |
| $('#users-list').html(usersHtml); | |
| $('#users-popup').show(); | |
| $('#overlay').show(); | |
| } | |
| function switchUser(username) { | |
| currentUser = users.find(u => u.username === username); | |
| updateProfile(); | |
| updateInventory(); | |
| updateRobuxDisplay(); | |
| $('#admin-link').hide(); | |
| if (username === 'Roblox' || (currentUser && currentUser.isAdmin)) { | |
| $('#admin-link').show(); | |
| } | |
| $('#current-user-display').text(`Current User: ${username}`); | |
| closePopup('users-popup'); | |
| showProfile(); | |
| $('.profile-inventory').scrollTop(0); | |
| currentCatalogPage = 1; | |
| currentInventoryPage = 1; | |
| updateCatalog(); | |
| $('#item-detail-page').hide(); | |
| $('#catalog').hide(); | |
| } | |
| function showAddUserPopup() { | |
| $('#add-user-popup').show(); | |
| $('#overlay').show(); | |
| } | |
| function addUser() { | |
| let newUsername = $('#new-username').val(); | |
| if (newUsername && !users.some(u => u.username === newUsername)) { | |
| let newUser = { | |
| username: newUsername, | |
| joinDate: new Date().toLocaleDateString('en-US'), | |
| placeVisits: 0, | |
| friends: 0, | |
| followers: 0, | |
| following: 0, | |
| rap: 0, | |
| robux: 0, | |
| inventory: [], | |
| transactions: [] | |
| }; | |
| users.push(newUser); | |
| catalogItems.forEach(item => { | |
| if (!item.newViewedBy) { | |
| item.newViewedBy = []; | |
| } | |
| item.newViewedBy.push(newUsername); | |
| }); | |
| saveToLocalStorage(); | |
| closePopup('add-user-popup'); | |
| showSuccessBanner('User added successfully!'); | |
| } else { | |
| alert('Invalid username or username already exists.'); | |
| } | |
| } | |
| function showEditItemPopup(itemName) { | |
| let item = catalogItems.find(i => i.name === itemName); | |
| if (item) { | |
| $('#edit-item-name').val(item.name); | |
| $('#edit-item-name-new').val(item.name); | |
| $('#edit-preview-image').attr('src', item.image).show(); | |
| $('#edit-preview-image').data('imageUrl', item.image); | |
| $('#edit-item-image').val(item.image); | |
| $('#edit-item-price').val(item.price); | |
| $('#edit-item-rap').val(item.rap); | |
| $('#edit-item-max-purchase').val(item.maxPurchase || ''); | |
| $('#edit-item-limited-status').val(item.limitedStatus || 'none'); | |
| $('#edit-item-timer-status').val(item.timerStatus || 'none'); | |
| $('#edit-item-hidden').prop('checked', item.hidden || false); | |
| $('#edit-item-offsale').prop('checked', item.offSale || false); | |
| $('#edit-item-description').val(item.description || ''); | |
| // Show/hide timer fields based on timer status | |
| if (item.timerStatus === 'timer') { | |
| $('#edit-timer-fields').show(); | |
| // If there's a timerEndTime, calculate and display the remaining time | |
| if (item.timerEndTime) { | |
| const timeLeft = Math.max(0, item.timerEndTime - Date.now()); | |
| const hours = Math.floor(timeLeft / (1000 * 60 * 60)); | |
| const minutes = Math.floor((timeLeft % (1000 * 60 * 60)) / (1000 * 60)); | |
| const seconds = Math.floor((timeLeft % (1000 * 60)) / 1000); | |
| $('#edit-item-timer-hours').val(hours); | |
| $('#edit-item-timer-minutes').val(minutes); | |
| $('#edit-item-timer-seconds').val(seconds); | |
| } else { | |
| // Default values if no timer end time is set but timer is enabled | |
| $('#edit-item-timer-hours').val(24); | |
| $('#edit-item-timer-minutes').val(0); | |
| $('#edit-item-timer-seconds').val(0); | |
| } | |
| } else { | |
| $('#edit-timer-fields').hide(); | |
| // Clear timer fields when not in use | |
| $('#edit-item-timer-hours').val(''); | |
| $('#edit-item-timer-minutes').val(''); | |
| $('#edit-item-timer-seconds').val(''); | |
| } | |
| // Force trigger the change event to ensure UI is updated | |
| $('#edit-item-timer-status').trigger('change'); | |
| $('#edit-item-popup').show(); | |
| $('#overlay').show(); | |
| } | |
| } | |
| async function saveItemChanges() { | |
| let oldItemName = $('#edit-item-name').val(); | |
| let newItemName = $('#edit-item-name-new').val().trim(); | |
| if (!newItemName) { | |
| alert('Please enter a valid item name'); | |
| return; | |
| } | |
| let safeNewItemName = newItemName.replace(/'/g, ""); | |
| let item = catalogItems.find(i => i.name === oldItemName); | |
| if (item) { | |
| const newImageUrl = $('#edit-item-image').val(); | |
| if (newImageUrl) { | |
| try { | |
| const response = await fetch(newImageUrl); | |
| const blob = await response.blob(); | |
| const permanentUrl = await window.websim.upload(blob); | |
| $('#edit-preview-image').data('imageUrl', permanentUrl); | |
| } catch (err) { | |
| console.error('Failed to upload image:', err); | |
| // If image upload fails, ensure we keep using the original image URL | |
| if (!$('#edit-preview-image').data('imageUrl')) { | |
| $('#edit-preview-image').data('imageUrl', item.image); | |
| } | |
| } | |
| } else { | |
| // If no new image URL provided, use the original image | |
| $('#edit-preview-image').data('imageUrl', item.image); | |
| } | |
| let oldRap = item.rap; | |
| item.name = safeNewItemName; | |
| item.image = $('#edit-preview-image').data('imageUrl'); | |
| item.price = parseInt($('#edit-item-price').val()); | |
| item.rap = parseInt($('#edit-item-rap').val()) || 0; | |
| item.limitedStatus = $('#edit-item-limited-status').val(); | |
| const timerStatus = $('#edit-item-timer-status').val(); | |
| item.timerStatus = timerStatus; | |
| // Handle timer settings | |
| if (timerStatus === 'timer') { | |
| const hours = parseInt($('#edit-item-timer-hours').val()) || 0; | |
| const minutes = parseInt($('#edit-item-timer-minutes').val()) || 0; | |
| const seconds = parseInt($('#edit-item-timer-seconds').val()) || 0; | |
| // Only show timer badge if time is specified | |
| if (hours > 0 || minutes > 0 || seconds > 0) { | |
| const timerDuration = (hours * 60 * 60 + minutes * 60 + seconds) * 1000; | |
| item.timerEndTime = Date.now() + timerDuration; | |
| } else { | |
| delete item.timerEndTime; | |
| } | |
| } else { | |
| // If timer is disabled, remove timer end time | |
| delete item.timerEndTime; | |
| } | |
| item.maxPurchase = parseInt($('#edit-item-max-purchase').val()) || null; | |
| item.description = $('#edit-item-description').val(); | |
| item.hidden = $('#edit-item-hidden').is(':checked'); | |
| item.offSale = $('#edit-item-offsale').is(':checked'); | |
| // If new badge is checked, update the item's createdAt timestamp to current time | |
| if ($('#edit-item-new-badge').is(':checked')) { | |
| item.isNew = true; | |
| item.newViewedBy = []; | |
| item.createdAt = new Date().toISOString(); // Update timestamp to current time | |
| } | |
| for (let user of users) { | |
| let userItem = user.inventory.find(i => i.name === oldItemName); | |
| if (userItem) { | |
| userItem.name = safeNewItemName; | |
| userItem.image = item.image; | |
| userItem.price = item.price; | |
| userItem.limitedStatus = item.limitedStatus; | |
| userItem.rap = item.rap; | |
| userItem.offSale = item.offSale; | |
| } | |
| } | |
| for (let user of users) { | |
| user.rap = user.inventory.reduce((sum, item) => { | |
| let catalogItem = catalogItems.find(i => i.name === item.name); | |
| let itemRap = catalogItem ? catalogItem.rap : item.rap; | |
| return sum + itemRap * (item.quantity || 1); | |
| }, 0); | |
| } | |
| let catalogIndex = catalogItems.findIndex(i => i.name === oldItemName); | |
| if (catalogIndex !== -1) { | |
| catalogItems[catalogIndex] = item; | |
| } | |
| saveToLocalStorage(); | |
| closePopup('edit-item-popup'); | |
| showSuccessBanner('Item updated successfully!'); | |
| updateCatalog(); | |
| if (currentUser.username !== 'Roblox') { | |
| updateInventory(); | |
| updateProfile(); | |
| } | |
| } | |
| if ($('#edit-item-new-badge').is(':checked')) { | |
| item.isNew = true; | |
| item.newViewedBy = []; | |
| } | |
| } | |
| function showDeleteConfirmation(itemName) { | |
| itemToDelete = catalogItems.find(i => i.name === itemName); | |
| $('#delete-confirmation-popup').show(); | |
| $('#overlay').show(); | |
| } | |
| function confirmDeleteItem() { | |
| if (!itemToDelete) return; | |
| catalogItems = catalogItems.filter(i => i.name !== itemToDelete.name); | |
| for (let user of users) { | |
| let userItem = user.inventory.find(i => i.name === itemToDelete.name); | |
| if (userItem) { | |
| user.robux += itemToDelete.price * (userItem.quantity || 1); | |
| if (typeof user.rap !== 'number') { | |
| user.rap -= itemToDelete.rap * (userItem.quantity || 1); | |
| } | |
| user.inventory = user.inventory.filter(i => i.name !== itemToDelete.name); | |
| if (!user.transactions) user.transactions = []; | |
| user.transactions.push({ | |
| type: 'Refund', | |
| item: itemToDelete.name, | |
| amount: itemToDelete.price * (userItem.quantity || 1), | |
| date: new Date().toLocaleDateString('en-US') | |
| }); | |
| } | |
| } | |
| saveToLocalStorage(); | |
| closePopup('delete-confirmation-popup'); | |
| showSuccessBanner('Item deleted and refunds processed!'); | |
| updateCatalog(); | |
| updateInventory(); | |
| updateProfile(); | |
| updateRobuxDisplay(); | |
| } | |
| function showDeleteUserConfirmation(username) { | |
| $('#delete-user-confirmation-popup .username').text(username); | |
| $('#delete-user-confirmation-popup').data('username', username); | |
| $('#delete-user-confirmation-popup').show(); | |
| $('#overlay').show(); | |
| } | |
| function deleteUser() { | |
| const username = $('#delete-user-confirmation-popup').data('username'); | |
| if (!username) return; | |
| showLoadingScreen(); | |
| setTimeout(() => { | |
| users = users.filter(u => u.username !== username); | |
| saveToLocalStorage(); | |
| closePopup('delete-user-confirmation-popup'); | |
| showSuccessBanner('User deleted successfully!'); | |
| showProfile(); | |
| updateCatalog(); | |
| updateInventory(); | |
| updateRobuxPage(); | |
| }, 1200); | |
| } | |
| function acceptTrade(tradeId) { | |
| let trade = pendingTrades.find(t => t.id === tradeId); | |
| if (!trade || currentUser.banned) return; | |
| showLoadingScreen(); | |
| setTimeout(() => { | |
| // Remove the trade from pending trades | |
| pendingTrades = pendingTrades.filter(t => t.id !== tradeId); | |
| // Save a copy for declinedTrades tracking | |
| const tradeCopy = JSON.parse(JSON.stringify(trade)); | |
| // Get the sender and receiver users | |
| let sender = users.find(u => u.username === trade.sender); | |
| let receiver = users.find(u => u.username === trade.receiver); | |
| if (!sender || !receiver) { | |
| hideLoadingScreen(); | |
| return; | |
| } | |
| const transferItems = (fromUser, toUser, items) => { | |
| for (let item of items) { | |
| let fromItem = fromUser.inventory.find(i => i.name === item.name); | |
| if (fromItem?.quantity > 1) { | |
| fromItem.quantity--; | |
| } else { | |
| fromUser.inventory = fromUser.inventory.filter(i => i.name !== item.name); | |
| } | |
| // Add to recipient | |
| let toItem = toUser.inventory.find(i => i.name === item.name); | |
| if (toItem) { | |
| toItem.quantity = (toItem.quantity || 1) + 1; | |
| } else { | |
| toUser.inventory.push({ | |
| name: item.name, | |
| image: catalogItems.find(ci => ci.name === item.name)?.image || '', | |
| quantity: 1, | |
| rap: item.rap, | |
| limitedStatus: 'limited' | |
| }); | |
| } | |
| // Remove from equipped items if needed | |
| if (fromUser.equippedItems && fromUser.equippedItems.includes(item.name)) { | |
| fromUser.equippedItems = fromUser.equippedItems.filter(i => i !== item.name); | |
| } | |
| } | |
| }; | |
| transferItems(sender, receiver, trade.senderItems); | |
| transferItems(receiver, sender, trade.receiverItems); | |
| // Set completedAt timestamp | |
| trade.completedAt = new Date().toISOString(); | |
| // Add to completed trades | |
| completedTrades.push(trade); | |
| saveToLocalStorage(); | |
| hideLoadingScreen(); | |
| showSuccessBanner('Trade completed!'); | |
| updateTradeItems(); | |
| updatePendingTrades(); | |
| updateCompletedTrades(); | |
| updateProfile(); | |
| updateInventory(); | |
| }, 1200); | |
| } | |
| function declineTrade(tradeId) { | |
| let trade = pendingTrades.find(t => t.id === tradeId); | |
| if (!trade) return; | |
| pendingTrades = pendingTrades.filter(t => t.id !== tradeId); | |
| trade.status = 'declined'; | |
| trade.declinedAt = new Date().toISOString(); | |
| trade.declinedBy = currentUser.username; | |
| declinedTrades.unshift(trade); | |
| saveToLocalStorage(); | |
| showSuccessBanner('Trade declined!'); | |
| updatePendingTrades(); | |
| updateDeclinedTrades(); | |
| } | |
| function toggleOffSale(itemName) { | |
| let item = catalogItems.find(i => i.name === itemName); | |
| if (item) { | |
| item.offSale = !item.offSale; | |
| if (item.offSale) { | |
| item.timerStatus = 'none'; | |
| } else { | |
| item.isNew = true; | |
| item.newViewedBy = []; | |
| if (item.limitedStatus === 'limited' || item.limitedStatus === 'unlimited') { | |
| item.rap = 0; // Reset RAP when going back on sale | |
| // Clear any existing listings since it's a new limited item | |
| itemListings = itemListings.filter(listing => listing.itemName !== item.name); | |
| } | |
| } | |
| saveToLocalStorage(); | |
| showItemDetail(itemName); // Update the detail page instead of going back to catalog | |
| } | |
| } | |
| function showLoadingScreen() { | |
| $('#loading-screen').css('display', 'flex'); | |
| } | |
| function hideLoadingScreen() { | |
| $('#loading-screen').css('display', 'none'); | |
| } | |
| let bannerTimeout; | |
| function showSuccessBanner(message) { | |
| clearTimeout(bannerTimeout); | |
| $('#success-banner').stop(true, true).hide(); | |
| $('#success-banner').text(message); | |
| $('#success-banner').fadeIn(); | |
| bannerTimeout = setTimeout(() => { | |
| $('#success-banner').fadeOut(); | |
| }, 2000); | |
| } | |
| function closePopup(popupId) { | |
| $(`#${popupId}`).hide(); | |
| $('#overlay').hide(); | |
| } | |
| function addRobux(amount) { | |
| if (currentUser && !currentUser.banned) { | |
| currentUser.robux += amount; | |
| updateRobuxDisplay(); | |
| saveToLocalStorage(); | |
| } | |
| } | |
| function saveToLocalStorage() { | |
| localStorage.setItem('robloxSimData', JSON.stringify({ | |
| users, | |
| catalogItems, | |
| completedTrades, | |
| pendingTrades, | |
| declinedTrades, | |
| itemListings, | |
| purchaseHistory | |
| })); | |
| } | |
| function recalculateRAP(item, salePrice) { | |
| const difference = salePrice - item.rap; | |
| const adjustment = Math.floor(difference / 10); | |
| return item.rap + adjustment; | |
| } | |
| function createListing(itemName) { | |
| const userItem = currentUser.inventory.find(i => i.name === itemName); | |
| if (!userItem) { | |
| alert('You do not own this item'); | |
| return; | |
| } | |
| const catalogItem = catalogItems.find(i => i.name === itemName); | |
| const currentRap = catalogItem ? catalogItem.rap : userItem.rap; | |
| // Get current listings for this item by this user | |
| const existingListings = itemListings.filter(l => | |
| l.itemName === itemName && l.seller === currentUser.username | |
| ); | |
| const listedQuantities = existingListings.map(l => l.quantity); | |
| const quantityHtml = ` | |
| <div> | |
| <h4>Select Item to Sell:</h4> | |
| <h5>Current RAP: ${formatNumber(currentRap)}</h5> | |
| <select id="quantity-select" class="input-field"> | |
| ${Array.from({length: userItem.quantity || 1}, (_, i) => i + 1) | |
| .map(num => ` | |
| <option value="${num}" | |
| ${listedQuantities.includes(num) ? 'style="color: #808080;" disabled' : ''}> | |
| Item #${num}${listedQuantities.includes(num) ? ' (Listed)' : ''} | |
| </option> | |
| `).join('')} | |
| </select> | |
| </div> | |
| `; | |
| $('#sell-item-popup').html(` | |
| ${quantityHtml} | |
| <input type="number" id="listing-price" class="input-field" placeholder="Price" max="999999999" | |
| oninput="updateSellPrice(this.value)"> | |
| <p id="seller-earnings" style="color: #B8B8B8; margin: 5px 0;"></p> | |
| <button class="button" onclick="confirmCreateListing()">Create Listing</button> | |
| <button class="button" onclick="closePopup('sell-item-popup')">Cancel</button> | |
| `); | |
| $('#sell-item-popup').show(); | |
| $('#overlay').show(); | |
| $('#sell-item-popup').data('itemName', itemName); | |
| } | |
| function updateSellPrice(price) { | |
| const earnings = Math.floor(price * 0.9); // 10% fee | |
| $('#seller-earnings').text(`You will receive: ${formatNumber(earnings)} Robux after 10% marketplace fee`); | |
| } | |
| function confirmCreateListing() { | |
| const itemName = $('#sell-item-popup').data('itemName'); | |
| const quantity = parseInt($('#quantity-select').val()); | |
| const price = parseInt($('#listing-price').val()); | |
| if (!quantity || quantity < 1) { | |
| alert('Please select a quantity'); | |
| return; | |
| } | |
| if (!price || price < 1) { | |
| alert('Please enter a valid price'); | |
| return; | |
| } | |
| if (price > 999999999) { | |
| alert('Maximum price is 999,999,999 Robux'); | |
| return; | |
| } | |
| showLoadingScreen(); | |
| setTimeout(() => { | |
| const listing = { | |
| id: Date.now().toString(), | |
| itemName, | |
| quantity, | |
| price, | |
| seller: currentUser.username, | |
| createdAt: new Date().toISOString() | |
| }; | |
| itemListings.push(listing); | |
| saveToLocalStorage(); | |
| hideLoadingScreen(); | |
| closePopup('sell-item-popup'); | |
| showSuccessBanner('Item listed successfully!'); | |
| updateCatalog(); | |
| updateInventory(); | |
| }, 1200); | |
| } | |
| function showPurchaseHistory() { | |
| const itemName = $('#sell-item-popup').data('itemName'); | |
| let historyHtml = ''; | |
| // Filter purchase history for just this item | |
| const itemHistory = purchaseHistory | |
| .filter(purchase => purchase.itemName === itemName) | |
| .slice() | |
| .reverse(); | |
| if (itemHistory.length > 0) { | |
| historyHtml += ` | |
| ${itemHistory.map(purchase => ` | |
| <div class="purchase-history-item"> | |
| Sold for <span class="robux-icon"></span>${formatNumber(purchase.price)} at ${new Date(purchase.date).toLocaleDateString()} | |
| By <span class="clickable" onclick="showUserProfile('${purchase.seller}'); closePopup('item-listings-popup')"> ${purchase.seller}</span> to | |
| <span class="clickable" onclick="showUserProfile('${purchase.buyer}'); closePopup('item-listings-popup')"> ${purchase.buyer}</span> | |
| </div> | |
| `).join('')} | |
| `; | |
| } else { | |
| historyHtml += '<p>No purchase history for this item</p>'; | |
| } | |
| $('#purchase-history').html(historyHtml); | |
| $('#purchase-history-popup').show(); | |
| $('#overlay').show(); | |
| // Scroll to top | |
| $('#purchase-history').scrollTop(0); | |
| } | |
| function showOwners(itemName) { | |
| let ownersList = ''; | |
| users.forEach(user => { | |
| // Skip users with private inventory unless currentUser is Roblox | |
| if (user.privateInventory && currentUser.username !== 'Roblox') return; | |
| const userItem = user.inventory?.find(i => i.name === itemName); | |
| if (userItem && userItem.quantity > 0) { | |
| const verified = user.verified ? ' <img src="https://en.help.roblox.com/hc/article_attachments/7997146649876" alt="Verified" class="verified-badge">' : ''; | |
| ownersList += `<p style="padding: 8px; margin: 5px 0; background-color: #232527; border-radius: 5px;"> | |
| <span class="clickable" onclick="showUserProfile('${user.username}'); closePopup('owners-popup')"> ${user.username}${verified}</span>: x${userItem.quantity} | |
| </p>`; | |
| } | |
| }); | |
| if (!ownersList) { | |
| ownersList = '<p>No users currently own this item</p>'; | |
| } | |
| $('#owners-list').html(ownersList); | |
| $('#owners-popup').show(); | |
| $('#overlay').show(); | |
| } | |
| function updateInventory() { | |
| if (!currentUser || !currentUser.inventory) return; | |
| // Update the toggle state | |
| $('#private-inventory-toggle').prop('checked', currentUser.privateInventory || false); | |
| if (!currentUser.equippedItems) { | |
| currentUser.equippedItems = []; | |
| } | |
| let sortedInventory = [...currentUser.inventory].sort((a, b) => { | |
| // If item is equipped, put it first | |
| const aEquipped = currentUser.equippedItems.includes(a.name); | |
| const bEquipped = currentUser.equippedItems.includes(b.name); | |
| if (aEquipped && !bEquipped) return -1; | |
| if (!aEquipped && bEquipped) return 1; | |
| let catalogItemA = catalogItems.find(i => i.name === a.name); | |
| let catalogItemB = catalogItems.find(i => i.name === b.name); | |
| let isLimitedA = catalogItemA && (catalogItemA.limitedStatus === 'limited' || catalogItemA.limitedStatus === 'unlimited'); | |
| let isLimitedB = catalogItemB && (catalogItemB.limitedStatus === 'limited' || catalogItemB.limitedStatus === 'unlimited'); | |
| let isOffSaleA = catalogItemA && catalogItemA.offSale; | |
| let isOffSaleB = catalogItemB && catalogItemB.offSale; | |
| if (isLimitedA && !isLimitedB) return -1; | |
| if (!isLimitedA && isLimitedB) return 1; | |
| if (isLimitedA && isLimitedB) { | |
| return (catalogItemB.rap || 0) - (catalogItemA.rap || 0); | |
| } | |
| if (isOffSaleA && !isOffSaleB) return 1; | |
| if (!isOffSaleA && isOffSaleB) return -1; | |
| return (b.price || 0) - (a.price || 0); | |
| }); | |
| let startIndex = (currentInventoryPage - 1) * itemsPerPage; | |
| let endIndex = startIndex + itemsPerPage; | |
| let pageItems = sortedInventory.slice(startIndex, endIndex); | |
| let inventoryHtml = ''; | |
| for (let item of pageItems) { | |
| if (!item) continue; | |
| let limitedIcon = ''; | |
| if (item.limitedStatus === 'unlimited') { | |
| limitedIcon = '<div class="limited-icon unlimited"></div>'; | |
| } else if (item.limitedStatus === 'limited') { | |
| limitedIcon = '<div class="limited-icon limited"></div>'; | |
| } | |
| let catalogItem = catalogItems.find(i => i.name === item.name) || {}; | |
| let isOffSale = catalogItem && catalogItem.offSale; | |
| const listings = itemListings.filter(l => l.itemName === item.name) | |
| .sort((a, b) => a.price - b.price); | |
| const lowestListing = listings[0]; | |
| const priceDisplay = (catalogItem.limitedStatus !== 'none' && !lowestListing) ? | |
| '<p style="color: #B8B8B8; font-weight: bold;">No Resellers</p>' : | |
| (lowestListing ? | |
| `<p><span class="robux-icon"></span>${formatNumber(lowestListing.price)}</p>` : | |
| (catalogItem.price === 0 ? | |
| '<p><span class="robux-icon"></span>Free</p>' : | |
| `<p><span class="robux-icon"></span>${formatNumber(catalogItem.price)}</p>`)); | |
| const isEquipped = currentUser.equippedItems.includes(item.name); | |
| const equipButton = isEquipped ? | |
| `<button class="take-off-button" onclick="event.stopPropagation(); toggleEquipItem('${escapeHtml(item.name)}')">Take Off</button>` : | |
| `<button class="put-on-button" onclick="event.stopPropagation(); toggleEquipItem('${escapeHtml(item.name)}')">Put On</button>`; | |
| inventoryHtml += ` | |
| <div class="catalog-item"> | |
| <img src="${item.image || ''}" alt="${item.name || 'Item'}"> | |
| ${limitedIcon} | |
| <div class="catalog-item-details"> | |
| <h3>${item.name || 'Unnamed Item'}</h3> | |
| ${isOffSale ? '<p style="color: #ff4444;">Offsale</p>' : ''} | |
| ${(item.limitedStatus !== 'none' && !item.offSale) ? `<p>RAP: ${formatNumber(catalogItem.rap || 0)}</p>` : ''} | |
| ${priceDisplay} | |
| <p><span class="clickable" style="text-decoration: underline;" onclick="event.stopPropagation(); showUserProfile('Roblox');">By: Roblox</span> <img src="https://en.help.roblox.com/hc/article_attachments/7997146649876" alt="Verified" style="width: 16px; height: 16px; vertical-align: middle;"></p> | |
| ${equipButton} | |
| </div> | |
| </div> | |
| `; | |
| } | |
| $('#inventory-items').html(inventoryHtml || '<p>No items in inventory</p>'); | |
| updatePaginationWithArrows(Math.ceil(sortedInventory.length / itemsPerPage), currentInventoryPage, 'inventory-pagination', changeInventoryPage); | |
| } | |
| function updatePaginationWithArrows(totalPages, currentPage, paginationId, changePageFunction) { | |
| let paginationHtml = ` | |
| <button onclick="${changePageFunction.name}(${Math.max(1, currentPage - 1)})" ${currentPage === 1 ? 'disabled' : ''}>←</button> | |
| <span>${currentPage} / ${totalPages}</span> | |
| <button onclick="${changePageFunction.name}(${Math.min(totalPages, currentPage + 1)})" ${currentPage === totalPages ? 'disabled' : ''}>→</button> | |
| `; | |
| $(`#${paginationId}`).html(paginationHtml); | |
| } | |
| function changeInventoryPage(page) { | |
| currentInventoryPage = page; | |
| updateInventory(); | |
| } | |
| function togglePrivateInventory() { | |
| if (!currentUser) return; | |
| currentUser.privateInventory = $('#private-inventory-toggle').is(':checked'); | |
| saveToLocalStorage(); | |
| updateInventory(); | |
| // If currently viewing the user's profile, refresh it | |
| if ($('#profile').is(':visible') && $('#profile-username').text().trim() === currentUser.username) { | |
| showUserProfile(currentUser.username); | |
| } | |
| } | |
| function showRemoveItemConfirmation(username, itemName) { | |
| $('#remove-item-confirmation-popup .remove-item-username').text(username); | |
| $('#remove-item-confirmation-popup .remove-item-name').text(itemName); | |
| $('#remove-item-confirmation-popup').data('username', username); | |
| $('#remove-item-confirmation-popup').data('itemName', itemName); | |
| $('#remove-item-confirmation-popup').show(); | |
| $('#overlay').show(); | |
| } | |
| function confirmRemoveItem() { | |
| const username = $('#remove-item-confirmation-popup').data('username'); | |
| const itemName = $('#remove-item-confirmation-popup').data('itemName'); | |
| const user = users.find(u => u.username === username); | |
| if (!user) return; | |
| showLoadingScreen(); | |
| setTimeout(() => { | |
| // Find the item in the user's inventory | |
| const inventoryItem = user.inventory.find(i => i.name === itemName); | |
| if (inventoryItem) { | |
| if (inventoryItem.quantity > 1) { | |
| inventoryItem.quantity--; | |
| } else { | |
| user.inventory = user.inventory.filter(i => i.name !== itemName); | |
| } | |
| // Remove from equipped items if it was equipped | |
| if (user.equippedItems && user.equippedItems.includes(itemName)) { | |
| user.equippedItems = user.equippedItems.filter(name => name !== itemName); | |
| } | |
| // Update RAP | |
| user.rap = user.inventory.reduce((sum, item) => { | |
| const catalogItem = catalogItems.find(i => i.name === item.name); | |
| const itemRap = catalogItem ? catalogItem.rap : item.rap; | |
| return sum + itemRap * (item.quantity || 1); | |
| }, 0); | |
| saveToLocalStorage(); | |
| hideLoadingScreen(); | |
| closePopup('remove-item-confirmation-popup'); | |
| showSuccessBanner(`Item removed from ${username}'s inventory!`); | |
| // Refresh the profile view | |
| showUserProfile(username); | |
| } | |
| }, 800); | |
| } | |
| function escapeHtml(str) { | |
| if (!str) return ''; | |
| const div = document.createElement('div'); | |
| div.textContent = str; | |
| return div.innerHTML; | |
| } | |
| function showGiveItemPopup(itemName) { | |
| if (currentUser.username !== 'Roblox') return; | |
| itemToGive = catalogItems.find(i => i.name === itemName); | |
| if (!itemToGive) return; | |
| let usersHtml = '<option value="">Select User</option>'; | |
| users.forEach(user => { | |
| if (user.username !== 'Roblox') { | |
| usersHtml += `<option value="${user.username}">${user.username}</option>`; | |
| } | |
| }); | |
| $('#recipient-select').html(usersHtml); | |
| $('#give-quantity').val(1); | |
| $('#give-item-popup').show(); | |
| $('#overlay').show(); | |
| } | |
| function confirmGiveItem() { | |
| recipientUsername = $('#recipient-select').val(); | |
| if (!recipientUsername) { | |
| alert('Please select a user'); | |
| return; | |
| } | |
| $('#recipient-name').text(recipientUsername); | |
| $('#give-item-popup').hide(); | |
| $('#confirm-give-popup').show(); | |
| } | |
| function giveItem() { | |
| if (!itemToGive || !recipientUsername) return; | |
| const quantity = parseInt($('#give-quantity').val()) || 1; | |
| showLoadingScreen(); | |
| setTimeout(() => { | |
| const recipient = users.find(u => u.username === recipientUsername); | |
| if (!recipient) return; | |
| if (!recipient.inventory) recipient.inventory = []; | |
| let existingItem = recipient.inventory.find(i => i.name === itemToGive.name); | |
| if (existingItem) { | |
| existingItem.quantity = (existingItem.quantity || 1) + quantity; | |
| } else { | |
| recipient.inventory.push({ | |
| ...itemToGive, | |
| quantity: quantity | |
| }); | |
| } | |
| saveToLocalStorage(); | |
| hideLoadingScreen(); | |
| closePopup('confirm-give-popup'); | |
| showSuccessBanner(`${quantity} item(s) given to ${recipientUsername}!`); | |
| }, 1200); | |
| } | |
| function saveBio() { | |
| if (!currentUser) return; | |
| let bioText = $('#bio-text').val().trim(); | |
| let username = $('#edit-bio-popup').data('username'); | |
| let user = users.find(u => u.username === username); | |
| if (user) { | |
| user.bio = bioText; | |
| saveToLocalStorage(); | |
| closePopup('edit-bio-popup'); | |
| showUserProfile(username); | |
| showSuccessBanner('Bio updated successfully!'); | |
| } | |
| } | |
| function showEditBioPopup(username) { | |
| let user = users.find(u => u.username === username); | |
| if (user) { | |
| $('#bio-text').val(user.bio || ''); | |
| $('#edit-bio-popup').data('username', username); | |
| $('#edit-bio-popup').show(); | |
| $('#overlay').show(); | |
| } | |
| } | |
| function updateLeaderboard() { | |
| let sortedUsers = [...users]; | |
| // First, calculate RAP for each user | |
| sortedUsers.forEach(user => { | |
| if (!user.privateInventory || currentUser.username === 'Roblox') { | |
| user.calculatedRap = 0; | |
| if (user.inventory) { | |
| user.calculatedRap = user.inventory.reduce((sum, item) => { | |
| let catalogItem = catalogItems.find(i => i.name === item.name); | |
| let itemRap = catalogItem ? catalogItem.rap : item.rap; | |
| return sum + itemRap * (item.quantity || 1); | |
| }, 0); | |
| } | |
| } | |
| }); | |
| // Sort users: non-private by RAP (descending), private users at the bottom | |
| sortedUsers.sort((a, b) => { | |
| if (a.privateInventory && !b.privateInventory && currentUser.username !== 'Roblox') return 1; | |
| if (!a.privateInventory && b.privateInventory && currentUser.username !== 'Roblox') return -1; | |
| if (a.privateInventory && b.privateInventory) return 0; | |
| return b.calculatedRap - a.calculatedRap; | |
| }); | |
| let leaderboardHtml = '<div style="padding: 20px; background-color: #393B3D; border-radius: 10px;">'; | |
| leaderboardHtml += ` | |
| <div style="display: grid; grid-template-columns: 1fr 2fr 1fr; gap: 10px; padding: 10px; margin-bottom: 10px; border-bottom: 1px solid #5F6569; font-weight: bold; font-size: 20px;"> | |
| <div>Rank</div> | |
| <div>Username</div> | |
| <div>RAP</div> | |
| </div> | |
| `; | |
| sortedUsers.forEach((user, index) => { | |
| const verifiedBadge = user.verified ? ' <img src="https://en.help.roblox.com/hc/article_attachments/7997146649876" alt="Verified" class="verified-badge">' : ''; | |
| const adminBadge = user.isAdmin ? ' <img src="https://i.imgur.com/WRPVUAh.png" alt="Admin" class="admin-badge">' : ''; | |
| const robuxIcon = '<span class="robux-icon" style="width:18px;height:18px;display:inline-block;background-image:url(https://devforum-uploads.s3.dualstack.us-east-2.amazonaws.com/uploads/original/4X/e/d/f/edfae9388da4cd8496b885a8a2df613372500d9c.png);background-size:contain;background-repeat:no-repeat;background-position:center;"></span>'; | |
| const rapDisplay = (user.privateInventory && currentUser.username !== 'Roblox') ? 'Private' : robuxIcon + formatNumber(user.calculatedRap); | |
| const rowClass = (user.privateInventory && currentUser.username !== 'Roblox') ? 'leaderboard-row-private' : 'leaderboard-row-public'; | |
| leaderboardHtml += ` | |
| <div class="${rowClass} leaderboard-row" style="display: grid; grid-template-columns: 1fr 2fr 1fr; gap: 10px; padding: 10px; border-radius: 5px; margin-bottom: 5px;"> | |
| <div>${index + 1}</div> | |
| <div class="clickable" onclick="showUserProfile('${user.username}')"> | |
| ${adminBadge}${user.username}${verifiedBadge} | |
| </div> | |
| <div>${rapDisplay}</div> | |
| </div> | |
| `; | |
| }); | |
| leaderboardHtml += '</div>'; | |
| $('#leaderboard-list').html(leaderboardHtml); | |
| } | |
| function setActiveNavItem(page) { | |
| $('.nav a').removeClass('active'); | |
| if (page === 'trade') { | |
| $('.nav a[onclick*="showTrade"]').addClass('active'); | |
| } else if (page === 'leaderboard') { | |
| $('.nav a[onclick*="showLeaderboard"]').addClass('active'); | |
| } else { | |
| $(`.nav a[onclick*="show${page.charAt(0).toUpperCase() + page.slice(1)}"]`).addClass('active'); | |
| } | |
| } | |
| function switchUser(username) { | |
| currentUser = users.find(u => u.username === username); | |
| updateProfile(); | |
| updateInventory(); | |
| updateRobuxDisplay(); | |
| $('#admin-link').hide(); | |
| if (username === 'Roblox' || (currentUser && currentUser.isAdmin)) { | |
| $('#admin-link').show(); | |
| } | |
| $('#current-user-display').text(`Current User: ${username}`); | |
| closePopup('users-popup'); | |
| showProfile(); | |
| $('.profile-inventory').scrollTop(0); | |
| currentCatalogPage = 1; | |
| currentInventoryPage = 1; | |
| updateCatalog(); | |
| $('#item-detail-page').hide(); | |
| $('#catalog').hide(); | |
| } | |
| function showAnnouncementPopup() { | |
| $('#announcement-popup').show(); | |
| $('#overlay').show(); | |
| } | |
| function showHiddenItems() { | |
| // Get all hidden items | |
| const hiddenItems = catalogItems.filter(item => item.hidden); | |
| if (hiddenItems.length === 0) { | |
| $('#hidden-items-list').html('<p style="text-align: center; padding: 20px;">No hidden items found</p>'); | |
| } else { | |
| let hiddenItemsHtml = ''; | |
| for (let item of hiddenItems) { | |
| let limitedIcon = ''; | |
| if (item.limitedStatus === 'unlimited') { | |
| limitedIcon = '<div class="limited-icon unlimited"></div>'; | |
| } else if (item.limitedStatus === 'limited') { | |
| limitedIcon = '<div class="limited-icon limited"></div>'; | |
| } | |
| const priceDisplay = item.offSale ? | |
| '<p style="color: #ff4444;">Offsale</p>' : | |
| `<p><span class="robux-icon"></span>${formatNumber(item.price)}</p>`; | |
| hiddenItemsHtml += ` | |
| <div class="catalog-item" onclick="showItemDetail("${escapeHtml(item.name)}"); closePopup('hidden-items-popup');" style="cursor: pointer;"> | |
| <img src="${item.image}" alt="${item.name}"> | |
| ${limitedIcon} | |
| <div class="catalog-item-details"> | |
| <h3>${item.name}</h3> | |
| ${priceDisplay} | |
| ${(item.limitedStatus !== 'none' && !item.offSale) ? `<p>RAP: ${formatNumber(item.rap)}</p>` : ''} | |
| <p>By: Roblox <img src="https://en.help.roblox.com/hc/article_attachments/7997146649876" alt="Verified" style="width: 16px; height: 16px; vertical-align: middle;"></p> | |
| </div> | |
| </div> | |
| `; | |
| } | |
| $('#hidden-items-list').html(hiddenItemsHtml); | |
| } | |
| $('#hidden-items-popup').show(); | |
| $('#overlay').show(); | |
| } | |
| function addAnnouncement() { | |
| const text = $('#announcement-text').val().trim(); | |
| const color = $('#announcement-color').val(); | |
| if (!text) return; | |
| const announcement = { | |
| id: Date.now(), | |
| text, | |
| color | |
| }; | |
| let saved = JSON.parse(localStorage.getItem("announcementData") || "[]"); | |
| saved.push(announcement); | |
| localStorage.setItem("announcementData", JSON.stringify(saved)); | |
| renderAnnouncements(); | |
| $('#announcement-popup').hide(); | |
| $('#overlay').hide(); | |
| } | |
| function deleteAnnouncement(id) { | |
| let saved = JSON.parse(localStorage.getItem("announcementData") || "[]"); | |
| saved = saved.filter(a => a.id !== id); | |
| localStorage.setItem("announcementData", JSON.stringify(saved)); | |
| renderAnnouncements(); | |
| } | |
| function renderAnnouncements() { | |
| let saved = JSON.parse(localStorage.getItem("announcementData") || "[]"); | |
| const current = saved.map(a => ` | |
| <div draggable="true" ondragstart="handleDragStart(event, ${a.id})" ondragover="handleDragOver(event)" ondrop="handleDrop(event, ${a.id})" style="background:${a.color};color:white;padding:10px;margin-bottom:5px;border-radius:5px;cursor: move;" data-id="${a.id}"> | |
| <span>${a.text}</span> | |
| <span style="float:right;cursor:pointer;" class="edit-announcement-icon" data-id="${a.id}">✎</span> | |
| <span style="float:right;cursor:pointer;margin-right:10px;" onclick="deleteAnnouncement(${a.id})">✖</span> | |
| <span style="float:right;cursor:pointer;margin-right:10px;" onclick="toggleAnnouncementVisibility(${a.id})">👁️</span> | |
| </div> | |
| `).join(''); | |
| document.getElementById('current-announcements').innerHTML = current; | |
| if (saved.length) { | |
| // Show announcement banner below header | |
| const topBanner = saved.filter(a => !a.hidden).map(a => `<div style="background:${a.color};color:white;text-align:center;padding:6px;font-weight:bold;font-size:14px;">${a.text}</div>`).join(''); | |
| document.getElementById('announcement-banner').innerHTML = topBanner; | |
| document.getElementById('announcement-banner').style.display = saved.filter(a => !a.hidden).length > 0 ? "block" : "none"; | |
| } else { | |
| document.getElementById('announcement-banner').style.display = "none"; | |
| } | |
| } | |
| $(document).ready(() => { | |
| renderAnnouncements(); | |
| $('#current-announcements').on('click', '.edit-announcement-icon', function(e) { | |
| e.stopPropagation(); | |
| const id = parseInt($(this).data('id')); | |
| showEditAnnouncementPopup(id); | |
| }); | |
| }); | |
| function renderAnnouncements() { | |
| let saved = JSON.parse(localStorage.getItem("announcementData") || "[]"); | |
| const current = saved.map(a => ` | |
| <div draggable="true" ondragstart="handleDragStart(event, ${a.id})" ondragover="handleDragOver(event)" ondrop="handleDrop(event, ${a.id})" style="background:${a.color};color:white;padding:10px;margin-bottom:5px;border-radius:5px;cursor: move;" data-id="${a.id}"> | |
| <span>${a.text}</span> | |
| <span style="float:right;cursor:pointer;" class="edit-announcement-icon" data-id="${a.id}">✎</span> | |
| <span style="float:right;cursor:pointer;margin-right:10px;" onclick="deleteAnnouncement(${a.id})">✖</span> | |
| <span style="float:right;cursor:pointer;margin-right:10px;" onclick="toggleAnnouncementVisibility(${a.id})">👁️</span> | |
| </div> | |
| `).join(''); | |
| document.getElementById('current-announcements').innerHTML = current; | |
| if (saved.length) { | |
| // Show announcement banner below header | |
| const topBanner = saved.filter(a => !a.hidden).map(a => `<div style="background:${a.color};color:white;text-align:center;padding:6px;font-weight:bold;font-size:14px;">${a.text}</div>`).join(''); | |
| document.getElementById('announcement-banner').innerHTML = topBanner; | |
| document.getElementById('announcement-banner').style.display = saved.filter(a => !a.hidden).length > 0 ? "block" : "none"; | |
| } else { | |
| document.getElementById('announcement-banner').style.display = "none"; | |
| } | |
| } | |
| function toggleAnnouncementVisibility(id) { | |
| let saved = JSON.parse(localStorage.getItem("announcementData") || "[]"); | |
| const announcementIndex = saved.findIndex(a => a.id === id); | |
| if (announcementIndex !== -1) { | |
| saved[announcementIndex].hidden = !saved[announcementIndex].hidden; | |
| localStorage.setItem("announcementData", JSON.stringify(saved)); | |
| renderAnnouncements(); | |
| } | |
| } | |
| $(document).ready(function () { | |
| loadFromLocalStorage(); | |
| currentUser = users.find(u => u.username === 'Roblox') || users[0]; | |
| // Make Roblox admin by default | |
| let robloxUser = users.find(u => u.username === 'Roblox'); | |
| if (robloxUser) { | |
| robloxUser.isAdmin = true; | |
| saveToLocalStorage(); | |
| } | |
| updateProfile(); | |
| showProfile(); | |
| $('#current-user-display').text(`Current User: ${currentUser.username}`); | |
| $('#admin-link').hide(); | |
| if (currentUser.username === 'Roblox') { | |
| $('#admin-link').show(); | |
| } | |
| // ... existing code ... | |
| // ... existing ready code ... | |
| // Add this section for search functionality | |
| $('#catalog-search').on('keypress', function(e) { | |
| if (e.which === 13) { | |
| performSearch(); | |
| } | |
| }); | |
| // Add listener for the sort dropdown | |
| $('#catalog-sort').change(function() { | |
| updateCatalog(); | |
| }); | |
| // Add listener for the trade partner dropdown | |
| $('#trade-partner').change(function() { | |
| selectedItems = { | |
| yours: [], | |
| theirs: [] | |
| }; | |
| updateTradeItems(); | |
| }); | |
| // ... rest of existing ready code ... | |
| }); | |
| function previewUploadImage() { | |
| const imageUrl = $('#item-image').val() || "https://i.imgur.com/uXWWQIs.png"; | |
| // If using the specific image URL, make sure it's set properly | |
| if (imageUrl === "https://i.imgur.com/uXWWQIs.png") { | |
| $('#upload-preview-image').attr('src', "https://i.imgur.com/uXWWQIs.png").show(); | |
| $('#upload-preview-image').data('imageUrl', "https://i.imgur.com/uXWWQIs.png"); | |
| } else { | |
| $('#upload-preview-image').attr('src', imageUrl).show(); | |
| $('#upload-preview-image').data('imageUrl', imageUrl); | |
| } | |
| } | |
| function performSearch() { | |
| currentCatalogPage = 1; | |
| updateCatalog(); | |
| } | |
| function showItemDetail(itemName) { | |
| const item = catalogItems.find(i => i.name === itemName); | |
| if (!item) return; | |
| // Check if profile is visible, if so, set the source to profile | |
| if ($('#profile').is(':visible')) { | |
| itemDetailSource = 'profile'; | |
| // Hide profile when showing item detail from profile page | |
| $('#profile').hide(); | |
| // Show catalog when coming from profile | |
| $('#catalog').show(); | |
| } else { | |
| itemDetailSource = 'catalog'; | |
| } | |
| // Update detail page elements | |
| $('#detail-item-image').attr('src', item.image); | |
| $('#detail-item-name').html(item.name + (item.timerStatus === 'timer' ? | |
| ' <img src="https://i.imgur.com/m4Caqes.png" style="width: 24px; height: 24px; vertical-align: middle; margin-left: 5px;">' : '')); | |
| $('#detail-item-description').text(item.description || 'No description available.'); | |
| // Create a unique timer ID for this item | |
| const safeItemName = itemName.replace(/[^a-zA-Z0-9]/g, "_"); | |
| const timerElementId = `detail-timer-countdown-${safeItemName}`; | |
| // Remove any existing timer display to prevent duplicates | |
| $('.detail-timer-countdown').remove(); | |
| // Add timer countdown display under the item name if timer is active | |
| if (item.timerStatus === 'timer' && item.timerEndTime) { | |
| // Create timer display | |
| $(`<div id="${timerElementId}" class="detail-timer-countdown" style="color: #ff4444; font-weight: bold; margin-top: 5px;"></div>`).insertAfter('#detail-item-name'); | |
| // Start the timer update with the unique element ID | |
| updateItemTimer(itemName, timerElementId); | |
| } | |
| // Set limited icon if needed | |
| if (item.limitedStatus === 'unlimited') { | |
| $('#detail-limited-icon').show().removeClass('limited').addClass('unlimited'); | |
| } else if (item.limitedStatus === 'limited') { | |
| $('#detail-limited-icon').show().removeClass('unlimited').addClass('limited'); | |
| } else { | |
| $('#detail-limited-icon').hide(); | |
| } | |
| // Update price display | |
| let priceHtml = ''; | |
| if (item.offSale) { | |
| priceHtml = '<p style="color: #ff4444; font-weight: bold; font-size: 24px;">Offsale</p>'; | |
| if (item.rap > 0) { | |
| priceHtml += `<p class="detail-rap-text">RAP: <strong>${formatNumber(item.rap)}</strong></p>`; | |
| } | |
| // Add original price if it exists for offsale items | |
| if (item.price) { | |
| priceHtml += `<p class="detail-rap-text">Original Price: <strong><span class="robux-icon"></span>${formatNumber(item.price)}</strong></p>`; | |
| } | |
| // Add purchases count for offsale items | |
| priceHtml += `<p class="detail-rap-text" style="cursor: pointer;" onclick="showOwners('${escapeHtml(item.name)}')">Sales: <strong>${formatNumber(item.purchases || 0)}</strong></p>`; | |
| } else if (item.limitedStatus !== 'none') { | |
| const listings = itemListings.filter(l => l.itemName === item.name) | |
| .sort((a, b) => a.price - b.price); | |
| priceHtml = !listings.length ? | |
| '<p style="color: #B8B8B8; font-weight: bold; font-size: 24px;">No Resellers</p>' : | |
| `<p style="font-size: 24px;"><span class="robux-icon"></span>${formatNumber(listings[0].price)}</p>`; | |
| if (item.rap > 0) { | |
| priceHtml += `<p class="detail-rap-text">RAP: <strong>${formatNumber(item.rap)}</strong></p>`; | |
| } | |
| // Add original price if it exists and item is limited | |
| if (item.price) { | |
| priceHtml += `<p class="detail-rap-text">Original Price: <strong><span class="robux-icon"></span>${formatNumber(item.price)}</strong></p>`; | |
| } | |
| // Add purchases count | |
| priceHtml += `<p class="detail-rap-text" style="cursor: pointer;" onclick="showOwners('${escapeHtml(item.name)}')">Sales: <strong>${formatNumber(item.purchases || 0)}</strong></p>`; | |
| } else { | |
| priceHtml = item.price === 0 ? | |
| '<p style="font-size: 24px;"><span class="robux-icon"></span>Free</p>' : | |
| `<p style="font-size: 24px;"><span class="robux-icon"></span>${formatNumber(item.price || 0)}</p>`; | |
| // Only show sales count for Roblox account | |
| if (currentUser.username === 'Roblox') { | |
| priceHtml += `<p class="detail-rap-text" style="cursor: pointer;" onclick="showOwners('${escapeHtml(item.name)}')">Sales: <strong>${formatNumber(item.purchases || 0)}</strong></p>`; | |
| } | |
| } | |
| // Add ownership count | |
| let userQuantity = currentUser.inventory?.find(i => i.name === item.name)?.quantity || 0; | |
| if (userQuantity > 0) { | |
| priceHtml += `<p class="detail-rap-text" style="color: #00A2FF;">You own: <strong>${userQuantity}</strong></p>`; | |
| } | |
| // Add stock text if not a limited item and has no RAP | |
| if (item.stock !== undefined && !item.rap) { | |
| if (item.stock > 0) { | |
| priceHtml += `<p style="color: #808080; margin-top: 10px;">Stock: ${formatNumber(item.stock)}</p>`; | |
| } | |
| } | |
| $('#detail-item-price').html(priceHtml); | |
| // Update buttons | |
| const buttonContainer = $('#detail-item-buttons'); | |
| let buttonHtml = ''; | |
| // Buy Button | |
| let buyButtonDisabled = item.offSale || currentUser.banned; | |
| if (!item.limitedStatus && !item.stock && item.timerStatus !== 'timer' && item.quantity > 0) { | |
| buyButtonDisabled = true; | |
| } | |
| if (item.maxPurchase !== null && userQuantity >= item.maxPurchase) { | |
| buyButtonDisabled = true; | |
| } | |
| buttonHtml += `<button id="detail-buy-button" class="button" onclick="showBuyConfirmation("${escapeHtml(item.name)}")" | |
| ${buyButtonDisabled ? 'disabled' : ''} style="font-size: 18px; padding: 15px 30px;">Buy</button>`; | |
| // Sell Button (for current user's owned items) | |
| let canSell = currentUser && !currentUser.banned && | |
| currentUser.inventory && currentUser.inventory.some(i => i.name === item.name) && | |
| (item.limitedStatus === 'limited' || item.limitedStatus === 'unlimited'); | |
| if (canSell) { | |
| buttonHtml += `<button id="detail-sell-button" class="button" onclick="createListing("${escapeHtml(item.name)}")" | |
| style="font-size: 18px; padding: 15px 30px; margin-left: 10px;">Sell</button>`; | |
| } | |
| // Admin Buttons (for Roblox account) | |
| if (currentUser && currentUser.username === 'Roblox') { | |
| buttonHtml += ` | |
| <button class="button" onclick="showEditItemPopup("${escapeHtml(item.name)}")" style="margin-left: 10px;">Edit</button> | |
| <button class="button" onclick="showDeleteConfirmation("${escapeHtml(item.name)}")" style="margin-left: 10px;">Delete</button> | |
| <button class="button" onclick="toggleOffSale("${escapeHtml(item.name)}"); backToCatalog();" style="margin-left: 10px;"> | |
| ${item.offSale ? 'On Sale' : 'Off Sale'} | |
| </button> | |
| <button class="button" onclick="showGiveItemPopup("${escapeHtml(item.name)}")" style="margin-left: 10px;">Give</button> | |
| `; | |
| } | |
| // Update button container | |
| $('#detail-item-buttons').html(buttonHtml); | |
| // Show detail page, hide catalog | |
| $('#catalog').hide(); | |
| $('#item-detail-page').show(); | |
| } | |
| function backToCatalog() { | |
| $('#item-detail-page').hide(); | |
| // If the user came from profile, return to profile | |
| if (itemDetailSource === 'profile') { | |
| $('#profile').show(); | |
| } else { | |
| // Otherwise return to catalog | |
| $('#catalog').show(); | |
| } | |
| } | |
| function showListingConfirmation(listingId) { | |
| const listing = itemListings.find(l => l.id === listingId); | |
| if (!listing) return; | |
| const item = catalogItems.find(i => i.name === listing.itemName); | |
| if (!item) return; | |
| let robuxAfterPurchase = currentUser.robux - listing.price; | |
| $('#buy-confirmation-image').attr('src', item.image); | |
| $('#buy-item-name').text(item.name); | |
| $('#buy-item-price').html(`<span class="robux-icon"></span>${formatNumber(listing.price)}`); | |
| $('#robux-after-purchase').text(formatNumber(robuxAfterPurchase)); | |
| // Update the popup content | |
| const buyConfirmationPopup = $('#buy-confirmation-popup'); | |
| buyConfirmationPopup.find('h3').text('Confirm Purchase'); | |
| buyConfirmationPopup.find('p:first-of-type').html( | |
| `Would you like to buy the item <span id="buy-item-name">${item.name}</span> from ${listing.seller} for <span id="buy-item-price"><span class="robux-icon"></span>${formatNumber(listing.price)}</span>?` | |
| ); | |
| buyConfirmationPopup.find('.confirm-button').attr('onclick', `confirmBuyListing('${listing.id}')`); | |
| buyConfirmationPopup.find('.cancel-button').attr('onclick', 'cancelPurchase()'); | |
| buyConfirmationPopup.find('p:last-of-type').html( | |
| `Your balance after this transaction will be <span class="robux-icon"></span><span id="robux-after-purchase">${formatNumber(robuxAfterPurchase)}</span>.` | |
| ); | |
| // Disable confirm button if balance would be negative | |
| $('.confirm-button').prop('disabled', robuxAfterPurchase < 0); | |
| if (robuxAfterPurchase < 0) { | |
| $('.confirm-button').css('background-color', '#808080'); | |
| } else { | |
| $('.confirm-button').css('background-color', '#ffffff'); | |
| } | |
| $('#buy-confirmation-popup').show(); | |
| $('#overlay').show(); | |
| } | |
| function confirmBuyListing(listingId) { | |
| const listing = itemListings.find(l => l.id === listingId); | |
| if (!listing) return; | |
| const item = catalogItems.find(i => i.name === listing.itemName); | |
| if (!item) return; | |
| if (currentUser.robux < listing.price) { | |
| alert('Not enough Robux!'); | |
| return; | |
| } | |
| const seller = users.find(u => u.username === listing.seller); | |
| if (!seller) { | |
| alert('Seller not found!'); | |
| return; | |
| } | |
| const sellerItem = seller.inventory.find(i => i.name === listing.itemName); | |
| if (!sellerItem) { | |
| alert('Item no longer available!'); | |
| return; | |
| } | |
| showLoadingScreen(); | |
| setTimeout(() => { | |
| // Deduct Robux from buyer | |
| currentUser.robux -= listing.price; | |
| // Add 90% of price to seller (10% marketplace fee) | |
| const sellerEarnings = Math.floor(listing.price * 0.9); | |
| seller.robux += sellerEarnings; | |
| // Give Roblox account the 10% marketplace fee | |
| const robloxUser = users.find(u => u.username === 'Roblox'); | |
| if (robloxUser) { | |
| if (!robloxUser.robux) robloxUser.robux = 0; | |
| const marketplaceFee = listing.price - sellerEarnings; // The 10% fee | |
| robloxUser.robux += marketplaceFee; | |
| // Add marketplace fee transaction to Roblox's history | |
| if (!robloxUser.transactions) robloxUser.transactions = []; | |
| robloxUser.transactions.push({ | |
| type: 'Sale', | |
| item: `${item.name} (10% Marketplace Fee)`, | |
| amount: marketplaceFee, | |
| date: new Date().toLocaleDateString('en-US') | |
| }); | |
| } | |
| // Add transaction record for buyer | |
| if (!currentUser.transactions) currentUser.transactions = []; | |
| currentUser.transactions.push({ | |
| type: 'Purchase', | |
| item: listing.itemName, | |
| amount: listing.price, | |
| date: new Date().toLocaleDateString('en-US') | |
| }); | |
| // Add transaction record for seller | |
| if (!seller.transactions) seller.transactions = []; | |
| seller.transactions.push({ | |
| type: 'Sale', | |
| item: listing.itemName, | |
| amount: sellerEarnings, | |
| date: new Date().toLocaleDateString('en-US') | |
| }); | |
| // Add item to buyer's inventory | |
| if (!currentUser.inventory) currentUser.inventory = []; | |
| let userItem = currentUser.inventory.find(i => i.name === listing.itemName); | |
| if (userItem) { | |
| userItem.quantity = (userItem.quantity || 1) + 1; | |
| } else { | |
| currentUser.inventory.push({ | |
| ...catalogItems.find(i => i.name === listing.itemName), | |
| quantity: 1 | |
| }); | |
| } | |
| // Remove item from seller's inventory | |
| if (sellerItem.quantity > 1) { | |
| sellerItem.quantity--; | |
| } else { | |
| seller.inventory = seller.inventory.filter(i => i.name !== listing.itemName); | |
| } | |
| // Remove the listing | |
| itemListings = itemListings.filter(l => l.id !== listingId); | |
| // Update RAP - Check if this is the first purchase | |
| const catalogItem = catalogItems.find(i => i.name === listing.itemName); | |
| if (catalogItem) { | |
| // If item has 0 RAP and is limited/limited u, this is the first purchase | |
| if (catalogItem.rap === 0 && (catalogItem.limitedStatus === 'limited' || catalogItem.limitedStatus === 'unlimited')) { | |
| catalogItem.rap = listing.price; // Set initial RAP to full price for first purchase | |
| } else { | |
| // For subsequent purchases, use the normal RAP calculation | |
| catalogItem.rap = recalculateRAP(catalogItem, listing.price); | |
| } | |
| } | |
| // Add to purchase history | |
| purchaseHistory.push({ | |
| itemName: listing.itemName, | |
| quantity: listing.quantity, | |
| price: listing.price, | |
| seller: listing.seller, | |
| buyer: currentUser.username, | |
| date: new Date().toISOString() | |
| }); | |
| // Update item detail page if visible | |
| if ($('#item-detail-page').is(':visible')) { | |
| showItemDetail(listing.itemName); | |
| } | |
| // Immediately update Robux display | |
| updateRobuxDisplay(); | |
| saveToLocalStorage(); | |
| hideLoadingScreen(); | |
| closePopup('buy-confirmation-popup'); | |
| closePopup('item-listings-popup'); | |
| showSuccessBanner('Purchase successful!'); | |
| }, 1200); | |
| } | |
| function deleteListing(listingId) { | |
| const listing = itemListings.find(l => l.id === listingId); | |
| if (!listing) return; | |
| // Only allow Roblox or the listing owner to delete | |
| if (currentUser.username !== 'Roblox' && currentUser.username !== listing.seller) return; | |
| itemListings = itemListings.filter(l => l.id !== listingId); | |
| saveToLocalStorage(); | |
| showSuccessBanner('Listing deleted successfully!'); | |
| // Refresh the listings popup | |
| const itemName = listing.itemName; | |
| closePopup('item-listings-popup'); | |
| showBuyConfirmation(itemName); | |
| } | |
| function showOwners(itemName) { | |
| let ownersList = ''; | |
| users.forEach(user => { | |
| // Skip users with private inventory unless currentUser is Roblox | |
| if (user.privateInventory && currentUser.username !== 'Roblox') return; | |
| const userItem = user.inventory?.find(i => i.name === itemName); | |
| if (userItem && userItem.quantity > 0) { | |
| const verified = user.verified ? ' <img src="https://en.help.roblox.com/hc/article_attachments/7997146649876" alt="Verified" class="verified-badge">' : ''; | |
| ownersList += `<p style="padding: 8px; margin: 5px 0; background-color: #232527; border-radius: 5px;"> | |
| <span class="clickable" onclick="showUserProfile('${user.username}'); closePopup('owners-popup')"> ${user.username}${verified}</span>: x${userItem.quantity} | |
| </p>`; | |
| } | |
| }); | |
| if (!ownersList) { | |
| ownersList = '<p>No users currently own this item</p>'; | |
| } | |
| $('#owners-list').html(ownersList); | |
| $('#owners-popup').show(); | |
| $('#overlay').show(); | |
| } | |
| function updateInventory() { | |
| if (!currentUser || !currentUser.inventory) return; | |
| // Update the toggle state | |
| $('#private-inventory-toggle').prop('checked', currentUser.privateInventory || false); | |
| if (!currentUser.equippedItems) { | |
| currentUser.equippedItems = []; | |
| } | |
| let sortedInventory = [...currentUser.inventory].sort((a, b) => { | |
| // If item is equipped, put it first | |
| const aEquipped = currentUser.equippedItems.includes(a.name); | |
| const bEquipped = currentUser.equippedItems.includes(b.name); | |
| if (aEquipped && !bEquipped) return -1; | |
| if (!aEquipped && bEquipped) return 1; | |
| let catalogItemA = catalogItems.find(i => i.name === a.name); | |
| let catalogItemB = catalogItems.find(i => i.name === b.name); | |
| let isLimitedA = catalogItemA && (catalogItemA.limitedStatus === 'limited' || catalogItemA.limitedStatus === 'unlimited'); | |
| let isLimitedB = catalogItemB && (catalogItemB.limitedStatus === 'limited' || catalogItemB.limitedStatus === 'unlimited'); | |
| let isOffSaleA = catalogItemA && catalogItemA.offSale; | |
| let isOffSaleB = catalogItemB && catalogItemB.offSale; | |
| if (isLimitedA && !isLimitedB) return -1; | |
| if (!isLimitedA && isLimitedB) return 1; | |
| if (isLimitedA && isLimitedB) { | |
| return (catalogItemB.rap || 0) - (catalogItemA.rap || 0); | |
| } | |
| if (isOffSaleA && !isOffSaleB) return 1; | |
| if (!isOffSaleA && isOffSaleB) return -1; | |
| return (b.price || 0) - (a.price || 0); | |
| }); | |
| let startIndex = (currentInventoryPage - 1) * itemsPerPage; | |
| let endIndex = startIndex + itemsPerPage; | |
| let pageItems = sortedInventory.slice(startIndex, endIndex); | |
| let inventoryHtml = ''; | |
| for (let item of pageItems) { | |
| if (!item) continue; | |
| let limitedIcon = ''; | |
| if (item.limitedStatus === 'unlimited') { | |
| limitedIcon = '<div class="limited-icon unlimited"></div>'; | |
| } else if (item.limitedStatus === 'limited') { | |
| limitedIcon = '<div class="limited-icon limited"></div>'; | |
| } | |
| let catalogItem = catalogItems.find(i => i.name === item.name) || {}; | |
| let isOffSale = catalogItem && catalogItem.offSale; | |
| const listings = itemListings.filter(l => l.itemName === item.name) | |
| .sort((a, b) => a.price - b.price); | |
| const lowestListing = listings[0]; | |
| const priceDisplay = (catalogItem.limitedStatus !== 'none' && !lowestListing) ? | |
| '<p style="color: #B8B8B8; font-weight: bold;">No Resellers</p>' : | |
| (lowestListing ? | |
| `<p><span class="robux-icon"></span>${formatNumber(lowestListing.price)}</p>` : | |
| (catalogItem.price === 0 ? | |
| '<p><span class="robux-icon"></span>Free</p>' : | |
| `<p><span class="robux-icon"></span>${formatNumber(catalogItem.price || 0)}</p>`)); | |
| const isEquipped = currentUser.equippedItems.includes(item.name); | |
| const equipButton = isEquipped ? | |
| `<button class="take-off-button" onclick="event.stopPropagation(); toggleEquipItem('${escapeHtml(item.name)}')">Take Off</button>` : | |
| `<button class="put-on-button" onclick="event.stopPropagation(); toggleEquipItem('${escapeHtml(item.name)}')">Put On</button>`; | |
| inventoryHtml += ` | |
| <div class="catalog-item"> | |
| <img src="${item.image || ''}" alt="${item.name || 'Item'}"> | |
| ${limitedIcon} | |
| <div class="catalog-item-details"> | |
| <h3>${item.name || 'Unnamed Item'}</h3> | |
| ${isOffSale ? '<p style="color: #ff4444;">Offsale</p>' : ''} | |
| ${(item.limitedStatus !== 'none' && !item.offSale) ? `<p>RAP: ${formatNumber(catalogItem.rap || 0)}</p>` : ''} | |
| ${priceDisplay} | |
| <p>By: Roblox <img src="https://en.help.roblox.com/hc/article_attachments/7997146649876" alt="Verified" style="width: 16px; height: 16px; vertical-align: middle;"></p> | |
| ${equipButton} | |
| </div> | |
| </div> | |
| `; | |
| } | |
| $('#inventory-items').html(inventoryHtml || '<p>No items in inventory</p>'); | |
| updatePaginationWithArrows(Math.ceil(sortedInventory.length / itemsPerPage), currentInventoryPage, 'inventory-pagination', changeInventoryPage); | |
| } | |
| function updatePaginationWithArrows(totalPages, currentPage, paginationId, changePageFunction) { | |
| let paginationHtml = ` | |
| <button onclick="${changePageFunction.name}(${Math.max(1, currentPage - 1)})" ${currentPage === 1 ? 'disabled' : ''}>←</button> | |
| <span>${currentPage} / ${totalPages}</span> | |
| <button onclick="${changePageFunction.name}(${Math.min(totalPages, currentPage + 1)})" ${currentPage === totalPages ? 'disabled' : ''}>→</button> | |
| `; | |
| $(`#${paginationId}`).html(paginationHtml); | |
| } | |
| function changeInventoryPage(page) { | |
| currentInventoryPage = page; | |
| updateInventory(); | |
| } | |
| function togglePrivateInventory() { | |
| if (!currentUser) return; | |
| currentUser.privateInventory = $('#private-inventory-toggle').is(':checked'); | |
| saveToLocalStorage(); | |
| updateInventory(); | |
| // If currently viewing the user's profile, refresh it | |
| if ($('#profile').is(':visible') && $('#profile-username').text().trim() === currentUser.username) { | |
| showUserProfile(currentUser.username); | |
| } | |
| } | |
| function showRemoveItemConfirmation(username, itemName) { | |
| $('#remove-item-confirmation-popup .remove-item-username').text(username); | |
| $('#remove-item-confirmation-popup .remove-item-name').text(itemName); | |
| $('#remove-item-confirmation-popup').data('username', username); | |
| $('#remove-item-confirmation-popup').data('itemName', itemName); | |
| $('#remove-item-confirmation-popup').show(); | |
| $('#overlay').show(); | |
| } | |
| function confirmRemoveItem() { | |
| const username = $('#remove-item-confirmation-popup').data('username'); | |
| const itemName = $('#remove-item-confirmation-popup').data('itemName'); | |
| const user = users.find(u => u.username === username); | |
| if (!user) return; | |
| showLoadingScreen(); | |
| setTimeout(() => { | |
| // Find the item in the user's inventory | |
| const inventoryItem = user.inventory.find(i => i.name === itemName); | |
| if (inventoryItem) { | |
| if (inventoryItem.quantity > 1) { | |
| inventoryItem.quantity--; | |
| } else { | |
| user.inventory = user.inventory.filter(i => i.name !== itemName); | |
| } | |
| // Remove from equipped items if it was equipped | |
| if (user.equippedItems && user.equippedItems.includes(itemName)) { | |
| user.equippedItems = user.equippedItems.filter(name => name !== itemName); | |
| } | |
| // Update RAP | |
| user.rap = user.inventory.reduce((sum, item) => { | |
| const catalogItem = catalogItems.find(i => i.name === item.name); | |
| const itemRap = catalogItem ? catalogItem.rap : item.rap; | |
| return sum + itemRap * (item.quantity || 1); | |
| }, 0); | |
| saveToLocalStorage(); | |
| hideLoadingScreen(); | |
| closePopup('remove-item-confirmation-popup'); | |
| showSuccessBanner(`Item removed from ${username}'s inventory!`); | |
| // Refresh the profile view | |
| showUserProfile(username); | |
| } | |
| }, 800); | |
| } | |
| function escapeHtml(str) { | |
| if (!str) return ''; | |
| const div = document.createElement('div'); | |
| div.textContent = str; | |
| return div.innerHTML; | |
| } | |
| function showGiveItemPopup(itemName) { | |
| if (currentUser.username !== 'Roblox') return; | |
| itemToGive = catalogItems.find(i => i.name === itemName); | |
| if (!itemToGive) return; | |
| let usersHtml = '<option value="">Select User</option>'; | |
| users.forEach(user => { | |
| if (user.username !== 'Roblox') { | |
| usersHtml += `<option value="${user.username}">${user.username}</option>`; | |
| } | |
| }); | |
| $('#recipient-select').html(usersHtml); | |
| $('#give-quantity').val(1); | |
| $('#give-item-popup').show(); | |
| $('#overlay').show(); | |
| } | |
| function confirmGiveItem() { | |
| recipientUsername = $('#recipient-select').val(); | |
| if (!recipientUsername) { | |
| alert('Please select a user'); | |
| return; | |
| } | |
| $('#recipient-name').text(recipientUsername); | |
| $('#give-item-popup').hide(); | |
| $('#confirm-give-popup').show(); | |
| } | |
| function giveItem() { | |
| if (!itemToGive || !recipientUsername) return; | |
| const quantity = parseInt($('#give-quantity').val()) || 1; | |
| showLoadingScreen(); | |
| setTimeout(() => { | |
| const recipient = users.find(u => u.username === recipientUsername); | |
| if (!recipient) return; | |
| if (!recipient.inventory) recipient.inventory = []; | |
| let existingItem = recipient.inventory.find(i => i.name === itemToGive.name); | |
| if (existingItem) { | |
| existingItem.quantity = (existingItem.quantity || 1) + quantity; | |
| } else { | |
| recipient.inventory.push({ | |
| ...itemToGive, | |
| quantity: quantity | |
| }); | |
| } | |
| saveToLocalStorage(); | |
| hideLoadingScreen(); | |
| closePopup('confirm-give-popup'); | |
| showSuccessBanner(`${quantity} item(s) given to ${recipientUsername}!`); | |
| }, 1200); | |
| } | |
| function saveBio() { | |
| if (!currentUser) return; | |
| let bioText = $('#bio-text').val().trim(); | |
| let username = $('#edit-bio-popup').data('username'); | |
| let user = users.find(u => u.username === username); | |
| if (user) { | |
| user.bio = bioText; | |
| saveToLocalStorage(); | |
| closePopup('edit-bio-popup'); | |
| showUserProfile(username); | |
| showSuccessBanner('Bio updated successfully!'); | |
| } | |
| } | |
| function showEditBioPopup(username) { | |
| let user = users.find(u => u.username === username); | |
| if (user) { | |
| $('#bio-text').val(user.bio || ''); | |
| $('#edit-bio-popup').data('username', username); | |
| $('#edit-bio-popup').show(); | |
| $('#overlay').show(); | |
| } | |
| } | |
| function updateLeaderboard() { | |
| let sortedUsers = [...users]; | |
| // First, calculate RAP for each user | |
| sortedUsers.forEach(user => { | |
| if (!user.privateInventory || currentUser.username === 'Roblox') { | |
| user.calculatedRap = 0; | |
| if (user.inventory) { | |
| user.calculatedRap = user.inventory.reduce((sum, item) => { | |
| let catalogItem = catalogItems.find(i => i.name === item.name); | |
| let itemRap = catalogItem ? catalogItem.rap : item.rap; | |
| return sum + itemRap * (item.quantity || 1); | |
| }, 0); | |
| } | |
| } | |
| }); | |
| // Sort users: non-private by RAP (descending), private users at the bottom | |
| sortedUsers.sort((a, b) => { | |
| if (a.privateInventory && !b.privateInventory && currentUser.username !== 'Roblox') return 1; | |
| if (!a.privateInventory && b.privateInventory && currentUser.username !== 'Roblox') return -1; | |
| if (a.privateInventory && b.privateInventory) return 0; | |
| return b.calculatedRap - a.calculatedRap; | |
| }); | |
| let leaderboardHtml = '<div style="padding: 20px; background-color: #393B3D; border-radius: 10px;">'; | |
| leaderboardHtml += ` | |
| <div style="display: grid; grid-template-columns: 1fr 2fr 1fr; gap: 10px; padding: 10px; margin-bottom: 10px; border-bottom: 1px solid #5F6569; font-weight: bold; font-size: 20px;"> | |
| <div>Rank</div> | |
| <div>Username</div> | |
| <div>RAP</div> | |
| </div> | |
| `; | |
| sortedUsers.forEach((user, index) => { | |
| const verifiedBadge = user.verified ? ' <img src="https://en.help.roblox.com/hc/article_attachments/7997146649876" alt="Verified" class="verified-badge">' : ''; | |
| const adminBadge = user.isAdmin ? ' <img src="https://i.imgur.com/WRPVUAh.png" alt="Admin" class="admin-badge">' : ''; | |
| const robuxIcon = '<span class="robux-icon" style="width:18px;height:18px;display:inline-block;background-image:url(https://devforum-uploads.s3.dualstack.us-east-2.amazonaws.com/uploads/original/4X/e/d/f/edfae9388da4cd8496b885a8a2df613372500d9c.png);background-size:contain;background-repeat:no-repeat;background-position:center;"></span>'; | |
| const rapDisplay = (user.privateInventory && currentUser.username !== 'Roblox') ? 'Private' : robuxIcon + formatNumber(user.calculatedRap); | |
| const rowClass = (user.privateInventory && currentUser.username !== 'Roblox') ? 'leaderboard-row-private' : 'leaderboard-row-public'; | |
| leaderboardHtml += ` | |
| <div class="${rowClass} leaderboard-row" style="display: grid; grid-template-columns: 1fr 2fr 1fr; gap: 10px; padding: 10px; border-radius: 5px; margin-bottom: 5px;"> | |
| <div>${index + 1}</div> | |
| <div class="clickable" onclick="showUserProfile('${user.username}')"> | |
| ${adminBadge}${user.username}${verifiedBadge} | |
| </div> | |
| <div>${rapDisplay}</div> | |
| </div> | |
| `; | |
| }); | |
| leaderboardHtml += '</div>'; | |
| $('#leaderboard-list').html(leaderboardHtml); | |
| } | |
| function setActiveNavItem(page) { | |
| $('.nav a').removeClass('active'); | |
| if (page === 'trade') { | |
| $('.nav a[onclick*="showTrade"]').addClass('active'); | |
| } else if (page === 'leaderboard') { | |
| $('.nav a[onclick*="showLeaderboard"]').addClass('active'); | |
| } else { | |
| $(`.nav a[onclick*="show${page.charAt(0).toUpperCase() + page.slice(1)}"]`).addClass('active'); | |
| } | |
| } | |
| function switchUser(username) { | |
| currentUser = users.find(u => u.username === username); | |
| updateProfile(); | |
| updateInventory(); | |
| updateRobuxDisplay(); | |
| $('#admin-link').hide(); | |
| if (username === 'Roblox' || (currentUser && currentUser.isAdmin)) { | |
| $('#admin-link').show(); | |
| } | |
| $('#current-user-display').text(`Current User: ${username}`); | |
| closePopup('users-popup'); | |
| showProfile(); | |
| $('.profile-inventory').scrollTop(0); | |
| currentCatalogPage = 1; | |
| currentInventoryPage = 1; | |
| updateCatalog(); | |
| $('#item-detail-page').hide(); | |
| $('#catalog').hide(); | |
| } | |
| function showAnnouncementPopup() { | |
| $('#announcement-popup').show(); | |
| $('#overlay').show(); | |
| } | |
| function showHiddenItems() { | |
| // Get all hidden items | |
| const hiddenItems = catalogItems.filter(item => item.hidden); | |
| if (hiddenItems.length === 0) { | |
| $('#hidden-items-list').html('<p style="text-align: center; padding: 20px;">No hidden items found</p>'); | |
| } else { | |
| let hiddenItemsHtml = ''; | |
| for (let item of hiddenItems) { | |
| let limitedIcon = ''; | |
| if (item.limitedStatus === 'unlimited') { | |
| limitedIcon = '<div class="limited-icon unlimited"></div>'; | |
| } else if (item.limitedStatus === 'limited') { | |
| limitedIcon = '<div class="limited-icon limited"></div>'; | |
| } | |
| const priceDisplay = item.offSale ? | |
| '<p style="color: #ff4444;">Offsale</p>' : | |
| `<p><span class="robux-icon"></span>${formatNumber(item.price)}</p>`; | |
| hiddenItemsHtml += ` | |
| <div class="catalog-item" onclick="showItemDetail("${escapeHtml(item.name)}"); closePopup('hidden-items-popup');" style="cursor: pointer;"> | |
| <img src="${item.image}" alt="${item.name}"> | |
| ${limitedIcon} | |
| <div class="catalog-item-details"> | |
| <h3>${item.name}</h3> | |
| ${priceDisplay} | |
| ${(item.limitedStatus !== 'none' && !item.offSale) ? `<p>RAP: ${formatNumber(item.rap)}</p>` : ''} | |
| <p>By: Roblox <img src="https://en.help.roblox.com/hc/article_attachments/7997146649876" alt="Verified" style="width: 16px; height: 16px; vertical-align: middle;"></p> | |
| </div> | |
| </div> | |
| `; | |
| } | |
| $('#hidden-items-list').html(hiddenItemsHtml); | |
| } | |
| $('#hidden-items-popup').show(); | |
| $('#overlay').show(); | |
| } | |
| function addAnnouncement() { | |
| const text = $('#announcement-text').val().trim(); | |
| const color = $('#announcement-color').val(); | |
| if (!text) return; | |
| const announcement = { | |
| id: Date.now(), | |
| text, | |
| color | |
| }; | |
| let saved = JSON.parse(localStorage.getItem("announcementData") || "[]"); | |
| saved.push(announcement); | |
| localStorage.setItem("announcementData", JSON.stringify(saved)); | |
| renderAnnouncements(); | |
| $('#announcement-popup').hide(); | |
| $('#overlay').hide(); | |
| } | |
| function deleteAnnouncement(id) { | |
| let saved = JSON.parse(localStorage.getItem("announcementData") || "[]"); | |
| saved = saved.filter(a => a.id !== id); | |
| localStorage.setItem("announcementData", JSON.stringify(saved)); | |
| renderAnnouncements(); | |
| } | |
| function renderAnnouncements() { | |
| let saved = JSON.parse(localStorage.getItem("announcementData") || "[]"); | |
| const current = saved.map(a => ` | |
| <div draggable="true" ondragstart="handleDragStart(event, ${a.id})" ondragover="handleDragOver(event)" ondrop="handleDrop(event, ${a.id})" style="background:${a.color};color:white;padding:10px;margin-bottom:5px;border-radius:5px;cursor: move;" data-id="${a.id}"> | |
| <span>${a.text}</span> | |
| <span style="float:right;cursor:pointer;" class="edit-announcement-icon" data-id="${a.id}">✎</span> | |
| <span style="float:right;cursor:pointer;margin-right:10px;" onclick="deleteAnnouncement(${a.id})">✖</span> | |
| <span style="float:right;cursor:pointer;margin-right:10px;" onclick="toggleAnnouncementVisibility(${a.id})">👁️</span> | |
| </div> | |
| `).join(''); | |
| document.getElementById('current-announcements').innerHTML = current; | |
| if (saved.length) { | |
| // Show announcement banner below header | |
| const topBanner = saved.filter(a => !a.hidden).map(a => `<div style="background:${a.color};color:white;text-align:center;padding:6px;font-weight:bold;font-size:14px;">${a.text}</div>`).join(''); | |
| document.getElementById('announcement-banner').innerHTML = topBanner; | |
| document.getElementById('announcement-banner').style.display = saved.filter(a => !a.hidden).length > 0 ? "block" : "none"; | |
| } else { | |
| document.getElementById('announcement-banner').style.display = "none"; | |
| } | |
| } | |
| $(document).ready(() => { | |
| renderAnnouncements(); | |
| }); | |
| function toggleAnnouncementVisibility(id) { | |
| let saved = JSON.parse(localStorage.getItem("announcementData") || "[]"); | |
| const announcementIndex = saved.findIndex(a => a.id === id); | |
| if (announcementIndex !== -1) { | |
| saved[announcementIndex].hidden = !saved[announcementIndex].hidden; | |
| localStorage.setItem("announcementData", JSON.stringify(saved)); | |
| renderAnnouncements(); | |
| } | |
| } | |
| function updateItemTimer(itemName, timerElementId) { | |
| const item = catalogItems.find(i => i.name === itemName); | |
| if (!item || !item.timerEndTime) return; | |
| // Calculate remaining time | |
| const now = Date.now(); | |
| const timeLeft = Math.max(0, item.timerEndTime - now); | |
| if (timeLeft <= 0) { | |
| // Timer expired, make item offsale | |
| console.log('Timer expired for item:', itemName); | |
| item.offSale = true; | |
| delete item.timerEndTime; | |
| // Also remove timer status so badge disappears | |
| item.timerStatus = 'none'; | |
| saveToLocalStorage(); | |
| // Update display | |
| $(`#${timerElementId}`).text('This item is now offsale').css('color', '#ff4444'); | |
| // Update item display to show offsale status | |
| $('#detail-item-price').html('<p style="color: #ff4444; font-weight: bold; font-size: 24px;">Offsale</p>'); | |
| // Disable buy button | |
| $('#detail-buy-button').prop('disabled', true); | |
| // Update catalog to reflect the change | |
| updateCatalog(); | |
| return; | |
| } | |
| // Calculate hours, minutes, seconds | |
| const hours = Math.floor(timeLeft / (1000 * 60 * 60)); | |
| const minutes = Math.floor((timeLeft % (1000 * 60 * 60)) / (1000 * 60)); | |
| const seconds = Math.floor((timeLeft % (1000 * 60)) / 1000); | |
| // Format the countdown display | |
| const formattedTime = `Will be offsale in ${hours}h ${minutes}m ${seconds}s`; | |
| $(`#${timerElementId}`).text(formattedTime); | |
| // Update every second, passing the unique timer element ID | |
| setTimeout(() => updateItemTimer(itemName, timerElementId), 1000); | |
| } | |
| function checkExpiredTimers() { | |
| const now = Date.now(); | |
| let needsSave = false; | |
| catalogItems.forEach(item => { | |
| if (item.timerStatus === 'timer' && item.timerEndTime) { | |
| if (now >= item.timerEndTime) { | |
| console.log('Expired timer found for item:', item.name); | |
| item.offSale = true; | |
| delete item.timerEndTime; | |
| item.timerStatus = 'none'; // Remove timer status so badge disappears | |
| needsSave = true; | |
| } | |
| } | |
| }); | |
| if (needsSave) { | |
| console.log('Saving changes after checking expired timers'); | |
| saveToLocalStorage(); | |
| // Update the catalog display to reflect the changes | |
| updateCatalog(); | |
| } | |
| } | |
| function showEditAnnouncementPopup(id) { | |
| const saved = JSON.parse(localStorage.getItem("announcementData") || "[]"); | |
| const announcement = saved.find(a => a.id === id); | |
| if (announcement) { | |
| $('#edit-announcement-text').val(announcement.text); | |
| $('#edit-announcement-color').val(announcement.color); | |
| $('#edit-announcement-popup').data('announcementId', id); | |
| $('#edit-announcement-popup').show(); | |
| $('#overlay').show(); | |
| } | |
| } | |
| function saveEditedAnnouncement() { | |
| const id = $('#edit-announcement-popup').data('announcementId'); | |
| const text = $('#edit-announcement-text').val().trim(); | |
| const color = $('#edit-announcement-color').val(); | |
| if (!text) return; | |
| let saved = JSON.parse(localStorage.getItem("announcementData") || "[]"); | |
| const announcement = saved.find(a => a.id === id); | |
| if (announcement) { | |
| announcement.text = text; | |
| announcement.color = color; | |
| localStorage.setItem("announcementData", JSON.stringify(saved)); | |
| renderAnnouncements(); | |
| $('#edit-announcement-popup').hide(); | |
| $('#overlay').hide(); | |
| } | |
| } | |
| $(document).ready(() => { | |
| renderAnnouncements(); | |
| $('#current-announcements').on('click', '.edit-announcement-icon', function(e) { | |
| e.stopPropagation(); | |
| const id = parseInt($(this).data('id')); | |
| showEditAnnouncementPopup(id); | |
| }); | |
| }); | |
| let dragSrcId = null; | |
| function handleDragStart(e, id) { | |
| dragSrcId = id; | |
| } | |
| function handleDragOver(e) { | |
| e.preventDefault(); | |
| } | |
| function handleDrop(e, targetId) { | |
| if (dragSrcId === null || dragSrcId === targetId) return; | |
| let saved = JSON.parse(localStorage.getItem("announcementData") || "[]"); | |
| const fromIndex = saved.findIndex(a => a.id === dragSrcId); | |
| const toIndex = saved.findIndex(a => a.id === targetId); | |
| if (fromIndex > -1 && toIndex > -1) { | |
| const [moved] = saved.splice(fromIndex, 1); | |
| saved.splice(toIndex, 0, moved); | |
| localStorage.setItem("announcementData", JSON.stringify(saved)); | |
| renderAnnouncements(); | |
| } | |
| } | |
| // Add JavaScript to show/hide timer fields based on timer status selection | |
| $(document).ready(function() { | |
| // Check if timer is selected on load for both forms | |
| if ($('#item-timer-status').val() === 'timer') { | |
| $('#timer-fields').show(); | |
| } else { | |
| $('#timer-fields').hide(); | |
| } | |
| if ($('#edit-item-timer-status').val() === 'timer') { | |
| $('#edit-timer-fields').show(); | |
| } else { | |
| $('#edit-timer-fields').hide(); | |
| } | |
| // For upload form | |
| $('#item-timer-status').change(function() { | |
| if ($(this).val() === 'timer') { | |
| $('#timer-fields').show(); | |
| } else { | |
| $('#timer-fields').hide(); | |
| } | |
| }); | |
| // For edit form | |
| $('#edit-item-timer-status').change(function() { | |
| if ($(this).val() === 'timer') { | |
| $('#edit-timer-fields').show(); | |
| } else { | |
| $('#edit-timer-fields').hide(); | |
| } | |
| }); | |
| // Check for expired timers every second | |
| setInterval(checkExpiredTimers, 1000); | |
| }); | |
| function showEditUserPopup(username) { | |
| let user = users.find(u => u.username === username); | |
| if (user) { | |
| $('#edit-username').val(user.username); | |
| $('#edit-join-date').val(user.joinDate); | |
| $('#edit-place-visits').val(user.placeVisits); | |
| $('#edit-friends').val(user.friends); | |
| $('#edit-followers').val(user.followers); | |
| $('#edit-following').val(user.following); | |
| $('#edit-rap').val(user.rap); | |
| $('#edit-robux').val(user.robux); | |
| $('#edit-user-popup').show(); | |
| $('#overlay').show(); | |
| } | |
| } | |
| function saveUserChanges() { | |
| let username = $('#edit-username').val(); | |
| let joinDate = $('#edit-join-date').val(); | |
| let placeVisits = parseInt($('#edit-place-visits').val()); | |
| let friends = parseInt($('#edit-friends').val()); | |
| let followers = parseInt($('#edit-followers').val()); | |
| let following = parseInt($('#edit-following').val()); | |
| let rap = parseInt($('#edit-rap').val()); | |
| let robux = parseInt($('#edit-robux').val()); | |
| let user = users.find(u => u.username === username); | |
| if (user) { | |
| user.joinDate = joinDate; | |
| user.placeVisits = placeVisits; | |
| user.friends = friends; | |
| user.followers = followers; | |
| user.following = following; | |
| user.rap = rap; | |
| user.robux = robux; | |
| saveToLocalStorage(); | |
| closePopup('edit-user-popup'); | |
| showSuccessBanner('User updated successfully!'); | |
| } | |
| } | |
| function previewUploadImage() { | |
| const imageUrl = $('#item-image').val() || "https://i.imgur.com/uXWWQIs.png"; | |
| // If using the specific image URL, make sure it's set properly | |
| if (imageUrl === "https://i.imgur.com/uXWWQIs.png") { | |
| $('#upload-preview-image').attr('src', "https://i.imgur.com/uXWWQIs.png").show(); | |
| $('#upload-preview-image').data('imageUrl', "https://i.imgur.com/uXWWQIs.png"); | |
| } else { | |
| $('#upload-preview-image').attr('src', imageUrl).show(); | |
| $('#upload-preview-image').data('imageUrl', imageUrl); | |
| } | |
| } | |
| function showUserSortingPopup() { | |
| // Generate the sortable user list | |
| let sortableUsersHtml = ''; | |
| for (let user of users) { | |
| sortableUsersHtml += ` | |
| <div class="sortable-user-item" draggable="true" data-username="${user.username}"> | |
| <span>${user.username}</span> | |
| <span style="cursor: move; float: right;">☰</span> | |
| </div> | |
| `; | |
| } | |
| $('#sortable-users-list').html(sortableUsersHtml); | |
| // Add drag and drop event listeners to the sortable list | |
| const sortableList = document.getElementById('sortable-users-list'); | |
| const sortableItems = sortableList.querySelectorAll('.sortable-user-item'); | |
| sortableItems.forEach(item => { | |
| item.addEventListener('dragstart', function() { | |
| // Add a class to indicate the item being dragged | |
| setTimeout(() => this.classList.add('dragging'), 0); | |
| }); | |
| item.addEventListener('dragend', function() { | |
| // Remove the dragging class | |
| this.classList.remove('dragging'); | |
| }); | |
| }); | |
| sortableList.addEventListener('dragover', function(e) { | |
| e.preventDefault(); | |
| const draggingItem = sortableList.querySelector('.dragging'); | |
| const siblings = [...sortableList.querySelectorAll('.sortable-user-item:not(.dragging)')]; | |
| // Find the sibling after which the dragging item should be placed | |
| let nextSibling = siblings.find(sibling => { | |
| return e.clientY <= sibling.offsetTop + sibling.offsetHeight / 2; | |
| }); | |
| // Insert dragging item before the found sibling or at the end | |
| sortableList.insertBefore(draggingItem, nextSibling); | |
| }); | |
| // Show the popup | |
| $('#user-sorting-popup').show(); | |
| $('#overlay').show(); | |
| } | |
| function saveUserOrder() { | |
| // Get the new order of users from the sortable list | |
| const sortableItems = document.querySelectorAll('.sortable-user-item'); | |
| let newUserOrder = []; | |
| // Create a new users array with the updated order | |
| sortableItems.forEach(item => { | |
| const username = item.dataset.username; | |
| const user = users.find(u => u.username === username); | |
| if (user) { | |
| newUserOrder.push(user); | |
| } | |
| }); | |
| // Update the users array with the new order | |
| if (newUserOrder.length === users.length) { | |
| users = newUserOrder; | |
| saveToLocalStorage(); | |
| showSuccessBanner('User order saved successfully!'); | |
| closePopup('user-sorting-popup'); | |
| } else { | |
| alert('Error: Some users were lost during sorting. Please try again.'); | |
| } | |
| } | |
| // Add this CSS to the existing styles | |
| function addSortableUserStyles() { | |
| const styleElement = document.createElement('style'); | |
| styleElement.innerHTML = ` | |
| .sortable-list { | |
| max-height: 400px; | |
| overflow-y: auto; | |
| margin-bottom: 15px; | |
| } | |
| .sortable-user-item { | |
| padding: 10px 15px; | |
| background-color: #2C2F33; | |
| border-radius: 5px; | |
| margin-bottom: 8px; | |
| cursor: pointer; | |
| user-select: none; | |
| transition: background-color 0.2s; | |
| } | |
| .sortable-user-item:hover { | |
| background-color: #393B3D; | |
| } | |
| .sortable-user-item.dragging { | |
| opacity: 0.7; | |
| background-color: #1F2022; | |
| } | |
| `; | |
| document.head.appendChild(styleElement); | |
| } | |
| // Call this function when the document is ready | |
| $(document).ready(function() { | |
| // ... existing ready code ... | |
| addSortableUserStyles(); | |
| }); | |
| // Add these functions to handle user sorting drag and drop | |
| function handleUserDragStart(event, username) { | |
| event.dataTransfer.setData('text/plain', username); | |
| setTimeout(() => event.target.classList.add('dragging'), 0); | |
| } | |
| function handleUserDragOver(event) { | |
| event.preventDefault(); | |
| } | |
| function handleUserDrop(event, targetUsername) { | |
| event.preventDefault(); | |
| const draggedUsername = event.dataTransfer.getData('text/plain'); | |
| if (draggedUsername === targetUsername) return; | |
| // Find the indices of the dragged and target items | |
| const draggedIndex = users.findIndex(u => u.username === draggedUsername); | |
| const targetIndex = users.findIndex(u => u.username === targetUsername); | |
| if (draggedIndex === -1 || targetIndex === -1) return; | |
| // Reorder the users array | |
| const userToMove = users.splice(draggedIndex, 1)[0]; | |
| users.splice(targetIndex, 0, userToMove); | |
| // Update the UI | |
| showUserSortingPopup(); | |
| } | |
| function showUserSortingPopup() { | |
| // Generate the sortable user list | |
| let sortableUsersHtml = ''; | |
| for (let user of users) { | |
| sortableUsersHtml += ` | |
| <div draggable="true" | |
| ondragstart="handleUserDragStart(event, '${user.username}')" | |
| ondragover="handleUserDragOver(event)" | |
| ondrop="handleUserDrop(event, '${user.username}')" | |
| class="sortable-user-item" | |
| data-username="${user.username}"> | |
| <span>${user.username}</span> | |
| <span style="float:right;cursor:pointer;margin-right:10px;">☰</span> | |
| </div> | |
| `; | |
| } | |
| $('#sortable-users-list').html(sortableUsersHtml); | |
| // Show the popup | |
| $('#user-sorting-popup').show(); | |
| $('#overlay').show(); | |
| } | |
| function saveUserOrder() { | |
| saveToLocalStorage(); | |
| showSuccessBanner('User order saved successfully!'); | |
| closePopup('user-sorting-popup'); | |
| } | |
| </script> | |
| <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=ewr23r2r/344dddd" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> | |
| </html> |