diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..47d2ec7 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,14 @@ +{ + "recommendations": [ + "ritwickdey.LiveServer", + "esbenp.prettier-vscode", + "dbaeumer.vscode-eslint", + "formulahendry.auto-rename-tag", + "christian-kohler.path-intellisense", + "usernamehw.errorlens", + "streetsidesoftware.code-spell-checker", + "bradlc.vscode-tailwindcss", + "yoavbls.pretty-ts-errors", + "ms-vscode.vscode-typescript-next" + ] +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..c25e780 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,29 @@ +{ + "editor.tabSize": 2, + "editor.insertSpaces": true, + "editor.formatOnSave": true, + "editor.defaultFormatter": "esbenp.prettier-vscode", + + "files.autoSave": "afterDelay", + "files.autoSaveDelay": 800, + + "emmet.includeLanguages": { + "javascript": "javascriptreact" + }, + + "editor.linkedEditing": true, + + "liveServer.settings.donotShowInfoMsg": true, + + "prettier.singleQuote": true, + "prettier.semi": true, + + "eslint.validate": ["javascript", "javascriptreact"], + + "editor.suggestOnTriggerCharacters": true, + "editor.quickSuggestions": { + "strings": true + }, + + "workbench.startupEditor": "none" +} \ No newline at end of file diff --git a/404.html b/404.html new file mode 100644 index 0000000..0e63ae9 --- /dev/null +++ b/404.html @@ -0,0 +1,100 @@ + + + + + + 404 + + + +
+
+

404

+ +

this page doesn’t exist… or it got eaten by your cousin's cat.

+

you might’ve clicked a broken link, or you had a little typo moment when typing the URL.

+ +

don’t worry. it happens more than you think!

+ +

+ error: 404_not_found
+ status: resource_unavailable
+ message: the requested page could not be located on this server
+ path: invalid or removed +

+ +

+ try going back home, or pretend this never happened... let's keep this between us okay? +

+ + ← back to safety!!! +
+
+ + \ No newline at end of file diff --git a/FAQ.html b/FAQ.html new file mode 100644 index 0000000..a8952ef --- /dev/null +++ b/FAQ.html @@ -0,0 +1,306 @@ + + + + + + FAQ - auth's RNG + + + + +
+ +

frequently asked questions

+

this also serves as a tutorial for auth's RNG :)

+
+ + + search what you need help with (beta) + +

basics

+ +
+
how do i roll?
+
press the roll button above the "total rolls: X" counter, or press auto roll if you're lazy.
+
+ +
+
why is there a big counter on the screen?
+
every 100 rolls, you get x4 luck for a minute. its nothing to worry about, its only good, no bad.
+
+ +
+
what are anomalies?
+
anomalies are little rare things where you can "consume" them for a 0.5x permanent luck boost that can build up overtime. this is great for getting rarities that are usually impossible with just raw luck. you get them every time you roll a rarity above 1/10,000.
+
+ +

shop system

+ +
+
how do i get points?
+
+ double-click any rarity in your inventory to "sell it out" for points. you keep the rarity, but it gets marked as sold. the rarer it is, the more points you get! +
+ formula: rarity number ÷ 3 (rounded up)
+ example: a 1/90 rarity gives you 30 points
+ if you have duplicates, you sell all unsold copies at once for more points +
+
+
+ +
+
what happens when i sell out?
+
the rarity gets a "sold" marker, meaning you can't sell it again until you get another duplicate. once you roll another copy, the sold marker disappears and you can sell the new copies.
+
+ +
+
what are the shop upgrades?
+
+ luck boost: increases your luck multiplier by 1.1x per level (stacks multiplicatively). max level 100. costs 50 points + (level × 25). +

+ roll speed: decreases roll animation time by 0.2 seconds per level. max level 3 (minimum 0.25s roll time). costs 100 points + (level × 50). +

+ point multiplier: decreases the divisor used to calculate points by 0.2 per level. starts at ÷3, eventually becomes ÷1 (meaning you get full rarity value as points). max level 10. costs 150 points + (level × 75). +
+ legendary 1/90 with no upgrades: 90 ÷ 3 = 30 points
+ with 5 point multiplier upgrades: 90 ÷ 2 = 45 points
+ at max level: 90 ÷ 1 = 90 points! +
+

+ rarity magnet: each level you get a 1.1x luck boost to get rarities you haven't got before. max level 5. +

+ point printer: its basically the kinda upgrade you see in most incremental games. every second you get +1 points and depending on how much levels you have on it you can either get 30/sec on level 30, or 300/sec on level 300. there is no max level. +

+ duplicate chance: every level you get a 1% chance that stacks. every roll yoou're essentially getting a chance to clone that rarity you got into 2. so lets say you rolled common and you get a popup saying that you got a duplicate, you now got 2 commons from that roll. max level 10. +

+ the potions are simple. you dont need help on potions trust me. +
+
+ +

gauntlets system

+
+
what are gauntlets?
+
they are a niche feature with auth's RNG! gauntlets are split into tiers, and each require you to get certain rarities. when you acquire all of those rarities you can choose a reward for yourself! sometimes you need multiple of those rarities, sometimes you need this rarity, but it's mainly to reward you of grinding and idling.
+
+ +
+
what is the global gauntlet?
+
that's a gauntlet! (obviously) but it is special because it is GLOBAL. every 2 days the rarities needed to get rewards get rotated, so one day it could be really easy-to-get rarities, but in the next 2 days, it could get to very hard-to-gey rarities. the rarities needed are usually in the medium - hard tiers to let anyone claim a reward from the global gauntlet easily, but not easily at the same time.
+
+ +

other features

+ +
+
do the rarities do anything?
+
not right now. they're mostly so you can collect them, kinda like pokemon cards.
+
+ +
+
do achievements do anything?
+
no. its mostly there to give a sense of progression at the start. they dont do anything.
+
+ +
+
what are run cards?
+
run cards can be generated with the button under "reset data...". when you press the button it generates a summary of your current stats. your rolls, your playtime, your run id, and your rarities. these numbers may not be accurate as the run card logic is held together by duct tape.
+
+ +
+
what are the daily and weekly features?
+
its supposed to keep you on the site HAHAHAHA but the streaks don't do anything, it's just to keep you awfully addicted to the game
+
+ +
+
what does the wishing well do?
+
the wishing well is a bonus feature in auth's RNG. you can trust your luck and get a chance to double the amount of points you put in. you can only put in the amount of points you have. when you throw points into the wishing well, theres a 40% chance that the amount of points you put in will double, and theres a 60% chance that the amount of points you put in will not be doubled. if the points get doubled, you get the final amount. if the points don't get doubled, you dont get anything and you lose the amount of points you put in.
+
+ +
+
what are the keybinds?
+
A and Left Arrow to move pages to left. D and Right Arrow to move pages to right. W to click (just hover your mouse onto something and press W)
+
+ +

ranked mode

+ +
+
what is ranked mode?
+
ranked mode is a way to challenge your friends with pure raw luck! you type out your friend's user id and your friend types out your user id, then you guys battle.
+
+ +
+
do i get anything if i win
+
you get 1 win and get placed on the leaderboard. the leaderboard is for people with the most wins
+
+ +
+
do i lose anything if i lose
+
...no
+
+ +
+
i thought neocities was a static web host? how did you even manage to add servers?
+
i made a service called "haze" as the advanced backend for auth's RNG. i then just made the link a constant to fetch from.
+
+ +
+
why the ranked system?
+
fun
+
+ +

txt

+ +
+
what is txt?
+
txt is a social media platform service purely for auth's RNG players and normal people alike! it's a great way to interact with the community if you dont have discord. you can also link your auth's RNG client TO your txt account to show off that you gamble for fun
+
+ +
+
what does linking do
+
it just puts a badge on your profile in the words of "auth's RNG linked!". it doesnt do anything other than that
+
+ +
+
why txt?
+
because i said so. now go make an account please please please
+
+ +

troubleshooting

+ +
+
i took a daily/weekly and it reset my streak to 1. is it bugged?
+
no, you just missed a day or a week and you lost your streak because of it.
+
+ +
+
i left the tab on auto roll. why didnt i get anything?
+
its almost definitely not auth's RNG itself and its definitely your browser. if you have a browser like Microsoft Edge or Opera GX, those browsers really like to sleep tabs when inactive or not opened for a bit. either stay on the tab where you're playing auth's RNG or change a setting in your browser. look for something like "battery saver" or "sleep tabs" or "resource saving" or "CPU/RAM limiter" and turn them/it off.
+
+ +
+
my file size is under 10 megabytes, why cant i upload it? (custom music)
+
sometimes localStorage gets a bit too aggressive. try compressing it down to under 3 MB so it can upload safely.
+
+ +
+
why isnt music playing?
+
you probably clicked the "mute music" option in settings. uncheck it and music will most definitely work. if music doesnt play still, its your browser. try moving to a different browser like chrome or firefox.
+
+ +
+
why isnt ranked mode working?
+
it may be a CSP or CORS issue, check the console and open an issue in the github repository or post in the nerimity or discord server!
+
+ +
+
why isnt some things working?
+
you found a bug. join the nerimity server linked in the "links and more" page and report in "bugs". or join the discord server and report in #bugs or email me at auth24d@proton.me --- however i may not be active on my email. you can also open a github issue.
+
+ + ← back to game + +
+ + \ No newline at end of file diff --git a/README.md b/README.md index 29cf66e..8394219 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,43 @@ # auth's RNG the repository for auth's RNG! -all the magic happens in the nightly branch. this branch is for the stable releases for auth's RNG. +![Version](https://img.shields.io/github/v/release/auth1ery/auths-RNG) +![Stars](https://img.shields.io/github/stars/auth1ery/auths-RNG) +![Forks](https://img.shields.io/github/forks/auth1ery/auths-RNG) +![Watchers](https://img.shields.io/github/watchers/auth1ery/auths-RNG) -https://authsrng.xyz -- stable site -https://nightly.authsrng.xyz -- bleeding edge, unstable nightly site +main branch: https://authsrng.xyz (main stable site) + +nightly branch: https://nightly.authsrng.xyz (unstable testing branch, but contains the latest pre-versions) + +--- + +# about auth's RNG + +auth's RNG can be described as "a web-based, incremental RNG game!" + +it has many features, many depths of gameplay, and is overall decently addicting! + +roll for rarities, sell-out those rarities, buy upgrades, wish upon the wishing well, and collect the most rarities possible! + +auth's RNG is inspired by RNG games on the platform Roblox, such as Sol's RNG and Juke's RNG + +--- + +# versioning notes + +this repository starts at version `9.1`. however, the game has been updated since 1.0 beta. + +- site archives can be found at https://archive.org with the wayback machine. search https://authsrng.neocities.org - plus possible later archives can be found at https://authsrng.w3spaces.com + +- and personal, found archives are at https://archive.org/details/@skunko_lee + +the earliest archive can be found on version `5.5`, which is when auth's RNG became open source. + +--- + +# contributing notes + +the `.github/workflows` part should NOT be changed or else they change how the development process work. if you do, i will hunt you down with a shovel + +refer to `documentation/CONTRIBUTING.md` for further instructions! diff --git a/_headers b/_headers new file mode 100644 index 0000000..1564feb --- /dev/null +++ b/_headers @@ -0,0 +1 @@ +Cache-Control: public, max-age=31536000, immutable \ No newline at end of file diff --git a/_security b/_security new file mode 100644 index 0000000..1e22d96 --- /dev/null +++ b/_security @@ -0,0 +1 @@ +Contact: mailto:skunkolee@gmail.com \ No newline at end of file diff --git a/assets/audio/dayjays.wav b/assets/audio/dayjays.wav new file mode 100644 index 0000000..61aba8a Binary files /dev/null and b/assets/audio/dayjays.wav differ diff --git a/assets/audio/moonlight.mp3 b/assets/audio/moonlight.mp3 new file mode 100644 index 0000000..5efcd11 Binary files /dev/null and b/assets/audio/moonlight.mp3 differ diff --git a/assets/audio/nocturne.mp3 b/assets/audio/nocturne.mp3 new file mode 100644 index 0000000..7e63d3f Binary files /dev/null and b/assets/audio/nocturne.mp3 differ diff --git a/assets/audio/wavelocity.mp3 b/assets/audio/wavelocity.mp3 new file mode 100644 index 0000000..91f8ba3 Binary files /dev/null and b/assets/audio/wavelocity.mp3 differ diff --git a/assets/audio/welcomecity.mp3 b/assets/audio/welcomecity.mp3 new file mode 100644 index 0000000..984d98e Binary files /dev/null and b/assets/audio/welcomecity.mp3 differ diff --git a/assets/images/authsRNG.png b/assets/images/authsRNG.png new file mode 100644 index 0000000..3b313d7 Binary files /dev/null and b/assets/images/authsRNG.png differ diff --git a/assets/scripts/main.js b/assets/scripts/main.js new file mode 100644 index 0000000..6c7c9f4 --- /dev/null +++ b/assets/scripts/main.js @@ -0,0 +1,2759 @@ +(function () { + var s = document.createElement('script'); + s.src = 'legacy-polyfills.js'; + s.async = false; + document.head.appendChild(s); +})(); + +const rollBtn = document.getElementById('rollBtn'), + spinner = document.getElementById('spinner'), + inventoryList = document.getElementById('inventoryList'), + resetBtn = document.getElementById('resetBtn'), + totalRollsEl = document.getElementById('totalRolls'), + achievementsContainer = document.getElementById('achievementsContainer'); + +const POINTS_KEY = 'shopPoints'; +const SHOP_UPGRADES_KEY = 'shopUpgrades'; +const SOLD_OUT_KEY = 'soldOutRarities'; + +let points = 0; +let shopUpgrades = { + luck: 0, + speed: 0, + pointMult: 0, + magnet: 0, + printer: 0, + duplicate: 0, +}; +let soldOutRarities = new Map(); +let rollSpeed = 1.0; +let shopLuckMultiplier = 1.0; +let pointDivisor = 3.0; + +const STORAGE_KEY = 'rarityInventory', + TOTAL_ROLLS_KEY = 'totalRolls', + ACHIEVEMENTS_KEY = 'achievementsUnlocked'; + +const ANOMALIES_KEY = 'anomalies'; +const ANOMALIES_USED_KEY = 'anomaliesUsed'; +let anomalies = 0; +let anomaliesUsed = 0; + +const POTIONS_KEY = 'playerPotions'; +const ACTIVE_POTIONS_KEY = 'activePotions'; + +let playerPotions = { + luck2x: 0, + luck4x: 0, + luck10x: 0, + luck50x: 0, + luck100x: 0, + luck150x: 0, + luck250x: 0, + luck300x: 0, + duplicate: 0, +}; + +let activePotions = []; +let potionLuckMultiplier = 1; +let duplicateRollsLeft = 0; + +const potionData = { + luck2x: { + name: '2x luck', + mult: 2, + duration: 30000, + cost: 2000, + emoji: '✨', + }, + luck4x: { + name: '4x luck', + mult: 4, + duration: 30000, + cost: 5000, + emoji: '💫', + }, + luck10x: { + name: '10x luck', + mult: 10, + duration: 30000, + cost: 15000, + emoji: '🌟', + }, + luck50x: { + name: '50x luck', + mult: 50, + duration: 30000, + cost: 30000, + emoji: '⭐', + }, + luck100x: { + name: '100x luck', + mult: 100, + duration: 30000, + cost: 45000, + emoji: '🔥', + }, + luck150x: { + name: '150x luck', + mult: 150, + duration: 30000, + cost: 60000, + emoji: '💥', + }, + luck250x: { + name: '250x luck', + mult: 250, + duration: 30000, + cost: 80000, + emoji: '⚡', + }, + luck300x: { + name: 'OMEGA LUCK', + mult: 300, + duration: 30000, + cost: 150000, + emoji: '💎', + }, + duplicate: { name: 'duplicate', rolls: 10, cost: 5000, emoji: '🎭' }, +}; + +function calculateRarityPoints(rarity) { + const denom = Math.round(1 / rarity.chance); + return Math.ceil(denom / pointDivisor); +} + +function updatePointsDisplay() { + document.getElementById('pointsValue').textContent = points; +} + +function updateShopUI() { + document.getElementById('luckLevel').textContent = shopUpgrades.luck; + document.getElementById('speedLevel').textContent = shopUpgrades.speed; + document.getElementById('pointLevel').textContent = shopUpgrades.pointMult; + + const luckCost = Math.floor(25 + shopUpgrades.luck * shopUpgrades.luck * 15); + const speedCost = Math.floor( + 50 + shopUpgrades.speed * shopUpgrades.speed * 55, + ); + const pointCost = Math.floor( + 100 + shopUpgrades.pointMult * shopUpgrades.pointMult * 35, + ); + + // Update button text to show costs + const luckBtn = document.getElementById('buyLuckBtn'); + const speedBtn = document.getElementById('buySpeedBtn'); + const pointBtn = document.getElementById('buyPointBtn'); + + if (luckBtn) luckBtn.textContent = `buy luck upgrade (${luckCost} pts)`; + if (speedBtn) speedBtn.textContent = `buy speed upgrade (${speedCost} pts)`; + if (pointBtn) pointBtn.textContent = `buy points upgrade (${pointCost} pts)`; + + // Disable buttons if can't afford or maxed out + if (luckBtn) luckBtn.disabled = points < luckCost || shopUpgrades.luck >= 100; + if (speedBtn) + speedBtn.disabled = points < speedCost || shopUpgrades.speed >= 3; + if (pointBtn) + pointBtn.disabled = points < pointCost || shopUpgrades.pointMult >= 10; + + const magnetLevelEl = document.getElementById('magnetLevel'); + const printerLevelEl = document.getElementById('printerLevel'); + const dupeLevelEl = document.getElementById('dupeLevel'); + + if (magnetLevelEl) magnetLevelEl.textContent = shopUpgrades.magnet || 0; + if (printerLevelEl) printerLevelEl.textContent = shopUpgrades.printer || 0; + if (dupeLevelEl) dupeLevelEl.textContent = shopUpgrades.duplicate || 0; + + const magnetCost = 500 + (shopUpgrades.magnet || 0) * 1000; + const printerCost = + 1000 + (shopUpgrades.printer || 0) * (shopUpgrades.printer || 0) * 500; + const dupeCost = + 800 + (shopUpgrades.duplicate || 0) * (shopUpgrades.duplicate || 0) * 400; + + const magnetBtn = document.getElementById('buyMagnetBtn'); + const printerBtn = document.getElementById('buyPrinterBtn'); + const dupeBtn = document.getElementById('buyDupeBtn'); + + if (magnetBtn) { + magnetBtn.textContent = `upgrade (${magnetCost} pts)`; + magnetBtn.disabled = points < magnetCost || (shopUpgrades.magnet || 0) >= 5; + } + if (printerBtn) { + printerBtn.textContent = `upgrade (${printerCost} pts)`; + printerBtn.disabled = points < printerCost; + } + if (dupeBtn) { + dupeBtn.textContent = `upgrade (${dupeCost} pts)`; + dupeBtn.disabled = points < dupeCost || (shopUpgrades.duplicate || 0) >= 10; + } +} + +function updatePotionUI() { + for (const [key, count] of Object.entries(playerPotions)) { + const countEl = document.getElementById(`potion-${key}-count`); + if (countEl) countEl.textContent = count; + } +} + +function buyPotion(potionType) { + const data = potionData[potionType]; + if (!data) return; + + if (points >= data.cost) { + points -= data.cost; + playerPotions[potionType]++; + updatePointsDisplay(); + updatePotionUI(); + saveAllData(); + showAnomalyPopup(`bought ${data.emoji} ${data.name}!`); + } else { + alert(`need ${data.cost} points!`); + } +} + +function usePotion(potionType) { + if (playerPotions[potionType] <= 0) { + alert(`you don't have any ${potionData[potionType].name} potions!`); + return; + } + + const data = potionData[potionType]; + + if (potionType === 'duplicate') { + duplicateRollsLeft = data.rolls; + playerPotions[potionType]--; + updatePotionUI(); + saveAllData(); + showAnomalyPopup(`${data.emoji} next ${data.rolls} rolls will be x2!`); + return; + } + + // Luck potions + const endTime = Date.now() + data.duration; + activePotions.push({ + type: potionType, + endTime: endTime, + multiplier: data.mult, + }); + + playerPotions[potionType]--; + recalcPotionLuck(); + updatePotionUI(); + updateActivePotionsDisplay(); + saveAllData(); + showAnomalyPopup(`${data.emoji} ${data.name} activated!`); +} + +function recalcPotionLuck() { + potionLuckMultiplier = 1; + activePotions = activePotions.filter((p) => p.endTime > Date.now()); + + activePotions.forEach((p) => { + potionLuckMultiplier *= p.multiplier; + }); + + updateLuckDisplay(); +} + +function updateActivePotionsDisplay() { + const display = document.getElementById('activePotionsDisplay'); + const list = document.getElementById('activePotionsList'); + + if (!display || !list) return; + + if (activePotions.length === 0 && duplicateRollsLeft === 0) { + display.style.display = 'none'; + return; + } + + display.style.display = 'block'; + list.innerHTML = ''; + + activePotions.forEach((p) => { + const data = potionData[p.type]; + const timeLeft = Math.ceil((p.endTime - Date.now()) / 1000); + + const div = document.createElement('div'); + div.className = 'active-potion'; + div.innerHTML = ` +
${data.emoji} ${data.mult}x luck
+
${timeLeft}s remaining
+ `; + list.appendChild(div); + }); + + if (duplicateRollsLeft > 0) { + const div = document.createElement('div'); + div.className = 'active-potion'; + div.innerHTML = ` +
🎭 duplicate
+
${duplicateRollsLeft} rolls left
+ `; + list.appendChild(div); + } +} + +// Update potion timers +setInterval(() => { + if (activePotions.length > 0) { + recalcPotionLuck(); + updateActivePotionsDisplay(); + } +}, 1000); + +// Make functions global +window.buyPotion = buyPotion; +window.usePotion = usePotion; + +function showConfirmModal(title, text, onConfirm) { + const modal = document.getElementById('confirmModal'); + const modalTitle = document.getElementById('modalTitle'); + const modalText = document.getElementById('modalText'); + const confirmBtn = document.getElementById('modalConfirm'); + const cancelBtn = document.getElementById('modalCancel'); + + if (!modal || !modalTitle || !modalText || !confirmBtn || !cancelBtn) return; + + modalTitle.textContent = title; + modalText.textContent = text; + modal.style.display = 'flex'; + + // Remove old listeners by cloning buttons + const newConfirmBtn = confirmBtn.cloneNode(true); + const newCancelBtn = cancelBtn.cloneNode(true); + confirmBtn.parentNode.replaceChild(newConfirmBtn, confirmBtn); + cancelBtn.parentNode.replaceChild(newCancelBtn, cancelBtn); + + // Add new listeners + newConfirmBtn.addEventListener('click', () => { + onConfirm(); + modal.style.display = 'none'; + }); + + newCancelBtn.addEventListener('click', () => { + modal.style.display = 'none'; + }); + + // Close on background click + const bgClickHandler = (e) => { + if (e.target === modal) { + modal.style.display = 'none'; + modal.removeEventListener('click', bgClickHandler); + } + }; + modal.addEventListener('click', bgClickHandler); +} + +let globalLuckMultiplier = 1; +function recalcLuckMultiplier() { + globalLuckMultiplier = 1 + shopUpgrades.luck * 0.1 + anomaliesUsed * 0.5; + + globalLuckMultiplier *= potionLuckMultiplier; + + updateLuckDisplay(); +} + +function updateLuckDisplay() { + const luckEl = document.getElementById('luckMultiplier'); + const breakdownEl = document.getElementById('luckBreakdown'); + + if (!luckEl || !breakdownEl) return; + + luckEl.textContent = `luck multiplier: ${globalLuckMultiplier.toFixed(1)}x`; + + const parts = []; + + if (anomaliesUsed > 0) { + const anomalyMult = 1 + anomaliesUsed * 0.5; + parts.push( + `anomalies: ${anomalyMult.toFixed(1)}x (${anomaliesUsed} consumed)`, + ); + } + + if (shopUpgrades.luck > 0) { + parts.push( + `shop upgrade: ${shopLuckMultiplier.toFixed(1)}x (level ${shopUpgrades.luck})`, + ); + } + + if (luckBoostActive) { + parts.push(`temporary boost: 4.0x (active)`); + } + + if (potionLuckMultiplier > 1) { + parts.push(`potions: ${potionLuckMultiplier.toFixed(1)}x`); + } + + if (duplicateRollsLeft > 0) { + parts.push(`duplicate: ${duplicateRollsLeft} rolls left`); + } + + if (parts.length === 0) { + breakdownEl.textContent = 'base luck (no modifiers active)'; + } else { + breakdownEl.textContent = parts.join(' • '); + } +} + +let luckBoostActive = false; +let luckBoostEndTime = 0; +let luckInterval = null; + +const LUCK_KEY = 'luckBoostState'; + +let totalRolls = 0; +const inventoryData = new Map(); +const achievementsUnlocked = new Set(); + +const backgroundMusic = new Audio('assets/audio/y2mn5w.mp3'); +backgroundMusic.loop = true; +backgroundMusic.volume = 0.3; + +const lunarMusic = new Audio(' '); +lunarMusic.volume = 0.6; + +const runId = Math.floor(Math.random() * 1e10); + +const playtimeKey = 'totalPlaytime'; +let totalSeconds = parseInt(localStorage.getItem(playtimeKey)) || 0; + +function formatTimeDisplay(seconds) { + const h = Math.floor(seconds / 3600); + const m = Math.floor((seconds % 3600) / 60); + const s = seconds % 60; + return [h > 0 ? `${h}h` : '', m > 0 ? `${m}m` : '', `${s}s`] + .filter(Boolean) + .join(' '); +} + +function updatePlaytimeDisplay() { + const display = document.getElementById('playtimeDisplay'); + if (display) { + display.textContent = 'total playtime: ' + formatTimeDisplay(totalSeconds); + } +} + +updatePlaytimeDisplay(); + +setInterval(() => { + totalSeconds++; + localStorage.setItem(playtimeKey, totalSeconds); + updatePlaytimeDisplay(); +}, 1000); + +const rarities = [ + { name: 'SUMMER', chance: 1 / 1000000000000000 }, + { name: 'finished.', chance: 1 / 100000000000000 }, + { name: 'pseudopseudohypoparathyroidism', chance: 1 / 10000000000000 }, + { name: '...', chance: 1 / 10000000000 }, + { name: 'the world', chance: 1 / 8200000000 }, + { name: 'Antimatter', chance: 1 / 5000000000 }, + { name: 'Dissociation', chance: 1 / 2000000000 }, + { name: 'Void', chance: 1 / 1000000000 }, + { name: 'brother what', chance: 1 / 400000000 }, + { name: 'Schizophrenia', chance: 1 / 300000000 }, + { name: 'Multiverse', chance: 1 / 200000000 }, + { name: 'CHARGED', chance: 1 / 100000000 }, + { name: 'Psychosis', chance: 1 / 50000000 }, + { name: 'Extinction', chance: 1 / 30000000 }, + { name: 'STOP PLAYING', chance: 1 / 10000000 }, + { name: 'Paranoia', chance: 1 / 8000000 }, + { name: 'Supermassive', chance: 1 / 5000000 }, + { name: 'Delusion', chance: 1 / 3000000 }, + { name: 'Kyawthuite', chance: 1 / 1500000 }, + { name: 'Globular but better', chance: 1 / 1300000 }, + { name: 'Obsession', chance: 1 / 1200000 }, + { name: 'you shouldnt have', chance: 1 / 1156852 }, + { name: 'francium', chance: 1 / 1100000 }, + { name: 'Impossible...', chance: 1 / 1000000 }, + { name: 'Trauma', chance: 1 / 900000 }, + { name: 'Interstellar', chance: 1 / 800000 }, + { name: 'Dissociative', chance: 1 / 700000 }, + { name: 'Superluminal', chance: 1 / 600000 }, + { name: 'Mania', chance: 1 / 500000 }, + { name: 'Gravitational', chance: 1 / 400000 }, + { name: 'Anxiety', chance: 1 / 300000 }, + { name: 'Cosmic', chance: 1 / 250000 }, + { name: 'Neurosis', chance: 1 / 200000 }, + { name: 'Event Horizon', chance: 1 / 101234 }, + { name: 'kill me', chance: 1 / 100000 }, + { name: 'Breakdown', chance: 1 / 95000 }, + { name: 'you suck bro', chance: 1 / 90000 }, + { name: 'Supergalaxy', chance: 1 / 88000 }, + { name: 'trust your luck', chance: 1 / 85000 }, + { name: 'astatine', chance: 1 / 80000 }, + { name: 'Depression', chance: 1 / 75000 }, + { name: 'Obfuscate', chance: 1 / 70000 }, + { name: 'Pulsar', chance: 1 / 65000 }, + { name: 'enuresis', chance: 1 / 60000 }, + { name: 'Panic', chance: 1 / 55000 }, + { name: 'Intel', chance: 1 / 50000 }, + { name: 'Nebulous', chance: 1 / 45000 }, + { name: 'anorexia', chance: 1 / 40000 }, + { name: 'Starborn', chance: 1 / 38000 }, + { name: '...whaat', chance: 1 / 35000 }, + { name: 'Disorder', chance: 1 / 33000 }, + { name: 'uh...', chance: 1 / 30000 }, + { name: 'rare rarity :3', chance: 1 / 30000 }, + { name: 'Galactic', chance: 1 / 28000 }, + { name: 'Eventide', chance: 1 / 25000 }, + { name: 'Phobia', chance: 1 / 23000 }, + { name: 'Catatonia', chance: 1 / 20000 }, + { name: 'Astroid', chance: 1 / 19000 }, + { name: 'Quantic', chance: 1 / 18000 }, + { name: 'Bipolar', chance: 1 / 17000 }, + { name: 'Photon', chance: 1 / 16000 }, + { name: 'Redshifted', chance: 1 / 15000 }, + { name: 'cyclothymic', chance: 1 / 14000 }, + { name: 'oh my-', chance: 1 / 13500 }, + { name: 'depressive', chance: 1 / 12900 }, + { name: 'im gonna cry', chance: 1 / 12800 }, + { name: 'dude why', chance: 1 / 12700 }, + { name: 'Kuiper', chance: 1 / 12600 }, + { name: 'Globular', chance: 1 / 12500 }, + { name: 'Elliptical', chance: 1 / 12400 }, + { name: 'Irregular', chance: 1 / 12300 }, + { name: 'Supercluster', chance: 1 / 12200 }, + { name: 'Hyperstar', chance: 1 / 12100 }, + { name: 'Hypernova', chance: 1 / 12000 }, + { name: 'youre him', chance: 1 / 11950 }, + { name: 'Supergiant', chance: 1 / 11900 }, + { name: 'Supervoid', chance: 1 / 11800 }, + { name: 'Superflare but better', chance: 1 / 11700 }, + { name: 'Supercloud but better', chance: 1 / 11600 }, + { name: 'Singularity', chance: 1 / 11500 }, + { name: 'Wormhole', chance: 1 / 11400 }, + { name: 'Blackhole', chance: 1 / 11300 }, + { name: 'Quasar', chance: 1 / 11200 }, + { name: 'Neutron', chance: 1 / 11100 }, + { name: 'Vortex', chance: 1 / 11050 }, + { name: 'Protogalaxy', chance: 1 / 11000 }, + { name: 'Hypervelocity', chance: 1 / 10900 }, + { name: 'Exoplanet', chance: 1 / 10800 }, + { name: 'Planetary', chance: 1 / 10700 }, + { name: 'Perplex', chance: 1 / 10600 }, + { name: 'Protostar', chance: 1 / 10500 }, + { name: 'Circumstellar', chance: 1 / 10400 }, + { name: 'microscopic', chance: 1 / 10350 }, + { name: 'Protoplanetary', chance: 1 / 10300 }, + { name: 'Magnetar', chance: 1 / 10200 }, + { name: 'Stellar', chance: 1 / 10100 }, + { name: 'cat', chance: 1 / 10000 }, + { name: 'Chromosphere', chance: 1 / 9975 }, + { name: 'Rogue', chance: 1 / 9950 }, + { name: 'Lagrange', chance: 1 / 9850 }, + { name: 'erm what', chance: 1 / 9825 }, + { name: 'Perigee', chance: 1 / 9800 }, + { name: 'Apogee', chance: 1 / 9775 }, + { name: 'Ecliptic', chance: 1 / 9750 }, + { name: 'Parsec', chance: 1 / 9725 }, + { name: 'Lightyear', chance: 1 / 9700 }, + { name: 'Astronomical', chance: 1 / 9675 }, + { name: 'Coronal', chance: 1 / 9650 }, + { name: 'Cepheid', chance: 1 / 9625 }, + { name: 'stop', chance: 1 / 9590 }, + { name: 'Luminosity', chance: 1 / 9575 }, + { name: 'Accretion', chance: 1 / 9550 }, + { name: '...?!!', chance: 1 / 9540 }, + { name: 'Bolometric', chance: 1 / 9525 }, + { name: 'Innovation', chance: 1 / 9520 }, + { name: 'Spectroscopy', chance: 1 / 9500 }, + { name: 'Parallax', chance: 1 / 9475 }, + { name: 'Supernova', chance: 1 / 9450 }, + { name: 'Precession', chance: 1 / 9425 }, + { name: 'Nutation', chance: 1 / 9400 }, + { name: 'Libration', chance: 1 / 9375 }, + { name: 'uhmmmm how', chance: 1 / 9350 }, + { name: 'Occultation', chance: 1 / 9325 }, + { name: 'Coronagraph', chance: 1 / 9300 }, + { name: 'Spectrograph', chance: 1 / 9275 }, + { name: 'Neutrino', chance: 1 / 9250 }, + { name: 'Interferometer', chance: 1 / 9225 }, + { name: 'Astrometry', chance: 1 / 9200 }, + { name: 'Photometry', chance: 1 / 9175 }, + { name: 'Meteorite', chance: 1 / 9150 }, + { name: 'Radiometry', chance: 1 / 9125 }, + { name: 'rarity names suck', chance: 1 / 9100 }, + { name: 'Thermodynamic', chance: 1 / 9075 }, + { name: 'Supercloud', chance: 1 / 9050 }, + { name: 'Entropy', chance: 1 / 9025 }, + { name: '// !strict', chance: 1 / 9000 }, + { name: 'Isentropic', chance: 1 / 8975 }, + { name: 'Superflare', chance: 1 / 8950 }, + { name: 'Adiabatic', chance: 1 / 8925 }, + { name: 'Isothermal', chance: 1 / 8900 }, + { name: 'Barotropic', chance: 1 / 8875 }, + { name: 'Planetesimal', chance: 1 / 8850 }, + { name: 'Polytropic', chance: 1 / 8825 }, + { name: 'Euphoria', chance: 1 / 8800 }, + { name: 'Relativistic', chance: 1 / 8775 }, + { name: 'Gamma', chance: 1 / 8750 }, + { name: 'Lorentz', chance: 1 / 8725 }, + { name: 'Minkowski', chance: 1 / 8700 }, + { name: 'Schwarzschild', chance: 1 / 8675 }, + { name: 'Kerr', chance: 1 / 8650 }, + { name: 'Ergosphere', chance: 1 / 8625 }, + { name: 'Dwarf', chance: 1 / 8600 }, + { name: 'Penrose', chance: 1 / 8575 }, + { name: 'Hawking', chance: 1 / 8550 }, + { name: 'Arcane', chance: 1 / 8500 }, + { name: 'Cascade', chance: 1 / 8400 }, + { name: 'Hubble', chance: 1 / 8375 }, + { name: 'Cosmological', chance: 1 / 8350 }, + { name: 'Redshift', chance: 1 / 8325 }, + { name: 'rah?', chance: 1 / 8300 }, + { name: 'Blueshift', chance: 1 / 8275 }, + { name: 'Aberration', chance: 1 / 8225 }, + { name: 'Lucent', chance: 1 / 8200 }, + { name: 'Refraction', chance: 1 / 8175 }, + { name: 'central', chance: 1 / 8170 }, + { name: 'STOP GAMBLING', chance: 1 / 8150 }, + { name: 'Diffraction', chance: 1 / 8125 }, + { name: 'Celestia', chance: 1 / 8100 }, + { name: 'Polarization', chance: 1 / 8075 }, + { name: 'Synchrotron', chance: 1 / 8050 }, + { name: 'Bremsstrahlung', chance: 1 / 8025 }, + { name: 'Seraphic', chance: 1 / 8000 }, + { name: 'Compton', chance: 1 / 7975 }, + { name: 'Photoelectric', chance: 1 / 7950 }, + { name: 'Nucleosynthesis', chance: 1 / 7925 }, + { name: 'Aetherial', chance: 1 / 7900 }, + { name: 'Fusion', chance: 1 / 7875 }, + { name: 'Fission', chance: 1 / 7850 }, + { name: 'Isotope', chance: 1 / 7825 }, + { name: 'Aether', chance: 1 / 7800 }, + { name: 'Deuterium', chance: 1 / 7775 }, + { name: 'Tritium', chance: 1 / 7750 }, + { name: 'Iridial', chance: 1 / 7700 }, + { name: 'Lithium', chance: 1 / 7675 }, + { name: 'Beryllium', chance: 1 / 7650 }, + { name: 'Boron', chance: 1 / 7625 }, + { name: 'Halcyon', chance: 1 / 7600 }, + { name: 'Nitrogen', chance: 1 / 7550 }, + { name: 'Stardrift', chance: 1 / 7500 }, + { name: 'hell', chance: 1 / 7450 }, + { name: 'Sodium', chance: 1 / 7425 }, + { name: 'Moonlit', chance: 1 / 7400 }, + { name: 'Magnesium', chance: 1 / 7375 }, + { name: 'cotton', chance: 1 / 7350 }, + { name: 'Aluminum', chance: 1 / 7325 }, + { name: 'Frame', chance: 1 / 7300 }, + { name: 'Silicon', chance: 1 / 7275 }, + { name: 'Phosphorus', chance: 1 / 7250 }, + { name: 'Sulfur', chance: 1 / 7225 }, + { name: 'Chemical', chance: 1 / 7200 }, + { name: 'Chlorine', chance: 1 / 7175 }, + { name: 'Argon', chance: 1 / 7150 }, + { name: 'Potassium', chance: 1 / 7125 }, + { name: 'Script', chance: 1 / 7100 }, + { name: 'Calcium', chance: 1 / 7075 }, + { name: 'Scandium', chance: 1 / 7050 }, + { name: 'Titanium', chance: 1 / 7025 }, + { name: 'go outside lil bro', chance: 1 / 7000 }, + { name: 'Vanadium', chance: 1 / 6975 }, + { name: 'Chromium', chance: 1 / 6950 }, + { name: 'Ruby', chance: 1 / 6935 }, + { name: 'Manganese', chance: 1 / 6925 }, + { name: 'Entertain', chance: 1 / 6900 }, + { name: 'Iron-56', chance: 1 / 6875 }, + { name: 'Nickel', chance: 1 / 6825 }, + { name: 'Prospect', chance: 1 / 6800 }, + { name: 'Copper', chance: 1 / 6775 }, + { name: 'Zinc', chance: 1 / 6750 }, + { name: 'Gallium', chance: 1 / 6725 }, + { name: 'Infamy', chance: 1 / 6700 }, + { name: 'Germanium', chance: 1 / 6675 }, + { name: 'Arsenic', chance: 1 / 6650 }, + { name: 'Selenium', chance: 1 / 6625 }, + { name: 'Rust', chance: 1 / 6600 }, + { name: 'Bromine', chance: 1 / 6575 }, + { name: 'Krypton', chance: 1 / 6550 }, + { name: 'Rubidium', chance: 1 / 6525 }, + { name: 'Low', chance: 1 / 6500 }, + { name: 'Strontium', chance: 1 / 6475 }, + { name: 'Yttrium', chance: 1 / 6450 }, + { name: 'Zirconium', chance: 1 / 6425 }, + { name: 'Toggle', chance: 1 / 6400 }, + { name: 'Niobium', chance: 1 / 6375 }, + { name: 'Molybdenum', chance: 1 / 6350 }, + { name: 'Technetium', chance: 1 / 6325 }, + { name: 'Doppler', chance: 1 / 6300 }, + { name: 'Ruthenium', chance: 1 / 6275 }, + { name: 'Rhodium', chance: 1 / 6250 }, + { name: 'Palladium', chance: 1 / 6225 }, + { name: 'Carpal', chance: 1 / 6200 }, + { name: 'Silver', chance: 1 / 6175 }, + { name: 'Cadmium', chance: 1 / 6150 }, + { name: 'Indium', chance: 1 / 6125 }, + { name: 'Interfere', chance: 1 / 6100 }, + { name: 'Tin', chance: 1 / 6075 }, + { name: 'cloudy', chance: 1 / 6050 }, + { name: 'Antimony', chance: 1 / 6025 }, + { name: 'Intermission', chance: 1 / 6000 }, + { name: 'Tellurium', chance: 1 / 5975 }, + { name: 'Iodine', chance: 1 / 5950 }, + { name: 'Xenon', chance: 1 / 5925 }, + { name: 'Alternate', chance: 1 / 5900 }, + { name: 'Cesium', chance: 1 / 5875 }, + { name: 'SyntaxError', chance: 1 / 5850 }, + { name: 'Barium', chance: 1 / 5825 }, + { name: 'Subzero', chance: 1 / 5800 }, + { name: 'Lanthanum', chance: 1 / 5775 }, + { name: 'Cerium', chance: 1 / 5750 }, + { name: 'Praseodymium', chance: 1 / 5725 }, + { name: 'Automatic', chance: 1 / 5700 }, + { name: 'Neodymium', chance: 1 / 5675 }, + { name: 'Promethium', chance: 1 / 5650 }, + { name: 'Samarium', chance: 1 / 5625 }, + { name: 'Encount', chance: 1 / 5600 }, + { name: 'Europium', chance: 1 / 5575 }, + { name: '🦶', chance: 1 / 5550 }, + { name: 'Gadolinium', chance: 1 / 5525 }, + { name: 'Telepath', chance: 1 / 5500 }, + { name: 'Terbium', chance: 1 / 5475 }, + { name: 'Dysprosium', chance: 1 / 5450 }, + { name: 'Holmium', chance: 1 / 5425 }, + { name: 'Airborne', chance: 1 / 5400 }, + { name: 'Erbium', chance: 1 / 5375 }, + { name: 'Thulium', chance: 1 / 5350 }, + { name: 'Ytterbium', chance: 1 / 5325 }, + { name: 'Viking', chance: 1 / 5300 }, + { name: 'Lutetium', chance: 1 / 5275 }, + { name: 'Hafnium', chance: 1 / 5250 }, + { name: 'Tantalum', chance: 1 / 5225 }, + { name: 'Wraith', chance: 1 / 5200 }, + { name: 'Tungsten', chance: 1 / 5175 }, + { name: 'Rhenium', chance: 1 / 5150 }, + { name: 'Osmium', chance: 1 / 5125 }, + { name: 'Spectral', chance: 1 / 5100 }, + { name: 'Iridium', chance: 1 / 5075 }, + { name: 'Platinum', chance: 1 / 5050 }, + { name: 'Polonium', chance: 1 / 5025 }, + { name: 'Nebula', chance: 1 / 5000 }, + { name: 'Radon', chance: 1 / 4975 }, + { name: 'Radium', chance: 1 / 4950 }, + { name: 'Actinium', chance: 1 / 4925 }, + { name: 'Vesper', chance: 1 / 4900 }, + { name: 'Thorium', chance: 1 / 4875 }, + { name: 'Protactinium', chance: 1 / 4850 }, + { name: 'Uranium', chance: 1 / 4825 }, + { name: 'Command', chance: 1 / 4800 }, + { name: 'Neptunium', chance: 1 / 4775 }, + { name: 'Plutonium', chance: 1 / 4750 }, + { name: 'Americium', chance: 1 / 4725 }, + { name: 'Quell', chance: 1 / 4700 }, + { name: 'Curium', chance: 1 / 4675 }, + { name: 'Berkelium', chance: 1 / 4650 }, + { name: 'Californium', chance: 1 / 4625 }, + { name: 'Unravel', chance: 1 / 4600 }, + { name: 'Einsteinium', chance: 1 / 4575 }, + { name: 'Fermium', chance: 1 / 4550 }, + { name: 'Mendelevium', chance: 1 / 4525 }, + { name: 'Decimate', chance: 1 / 4500 }, + { name: 'Nobelium', chance: 1 / 4475 }, + { name: 'Lawrencium', chance: 1 / 4450 }, + { name: 'Rutherfordium', chance: 1 / 4425 }, + { name: 'Comet', chance: 1 / 4400 }, + { name: 'Dubnium', chance: 1 / 4375 }, + { name: 'Seaborgium', chance: 1 / 4350 }, + { name: 'Bohrium', chance: 1 / 4325 }, + { name: 'Melancholy', chance: 1 / 4300 }, + { name: 'Hassium', chance: 1 / 4275 }, + { name: 'Meitnerium', chance: 1 / 4250 }, + { name: 'Darmstadtium', chance: 1 / 4225 }, + { name: 'Asteroid', chance: 1 / 4200 }, + { name: 'Roentgenium', chance: 1 / 4175 }, + { name: 'Copernicium', chance: 1 / 4150 }, + { name: 'Nihonium', chance: 1 / 4125 }, + { name: 'Radiation', chance: 1 / 4100 }, + { name: 'Flerovium', chance: 1 / 4075 }, + { name: 'Moscovium', chance: 1 / 4050 }, + { name: 'Livermorium', chance: 1 / 4025 }, + { name: 'Escensia', chance: 1 / 4000 }, + { name: 'Tennessine', chance: 1 / 3975 }, + { name: 'Oganesson', chance: 1 / 3950 }, + { name: 'Ionosphere', chance: 1 / 3925 }, + { name: 'Supermoon', chance: 1 / 3900 }, + { name: 'Mesosphere', chance: 1 / 3875 }, + { name: 'Stratosphere', chance: 1 / 3850 }, + { name: 'Troposphere', chance: 1 / 3825 }, + { name: 'Anxious', chance: 1 / 3800 }, + { name: 'Exosphere', chance: 1 / 3775 }, + { name: 'Thermosphere', chance: 1 / 3750 }, + { name: 'Magnetosphere', chance: 1 / 3725 }, + { name: 'Dreamless', chance: 1 / 3700 }, + { name: 'Heliosphere', chance: 1 / 3675 }, + { name: 'Plasmasphere', chance: 1 / 3650 }, + { name: 'Photosphere', chance: 1 / 3625 }, + { name: 'Wanderer', chance: 1 / 3600 }, + { name: 'Radiative', chance: 1 / 3575 }, + { name: 'Convective', chance: 1 / 3550 }, + { name: 'Tachocline', chance: 1 / 3525 }, + { name: 'Corpulence', chance: 1 / 3500 }, + { name: 'Nucleon', chance: 1 / 3475 }, + { name: 'Solutions', chance: 1 / 3450 }, + { name: 'Proton', chance: 1 / 3425 }, + { name: 'ok bro', chance: 1 / 3410 }, + { name: 'Points', chance: 1 / 3400 }, + { name: 'Electron', chance: 1 / 3375 }, + { name: 'just stop', chance: 1 / 3350 }, + { name: 'Positron', chance: 1 / 3325 }, + { name: 'Abandoned', chance: 1 / 3300 }, + { name: 'Antiproton', chance: 1 / 3275 }, + { name: 'Vertical', chance: 1 / 3250 }, + { name: 'Antineutron', chance: 1 / 3225 }, + { name: 'touch grass bro', chance: 1 / 3200 }, + { name: 'Muon', chance: 1 / 3175 }, + { name: 'Null', chance: 1 / 3170 }, + { name: 'Spectrum', chance: 1 / 3150 }, + { name: 'Tau', chance: 1 / 3145 }, + { name: 'deja vu', chance: 1 / 3128 }, + { name: 'Pioneer', chance: 1 / 3115 }, + { name: 'Perplexed', chance: 1 / 3100 }, + { name: 'Kaon', chance: 1 / 3085 }, + { name: 'Meson', chance: 1 / 3070 }, + { name: 'Zenith', chance: 1 / 3050 }, + { name: 'The End?', chance: 1 / 3000 }, + { name: 'Fermion', chance: 1 / 2990 }, + { name: 'Spectra', chance: 1 / 2978 }, + { name: 'Boson', chance: 1 / 2965 }, + { name: 'Poltergeist', chance: 1 / 2950 }, + { name: 'Gluon', chance: 1 / 2940 }, + { name: 'Graviton', chance: 1 / 2925 }, + { name: 'MURDER', chance: 1 / 2900 }, + { name: 'Quark', chance: 1 / 2875 }, + { name: 'Pixelated', chance: 1 / 2850 }, + { name: 'Charm', chance: 1 / 2840 }, + { name: 'Strange', chance: 1 / 2825 }, + { name: 'Nightmare', chance: 1 / 2800 }, + { name: 'Bottom', chance: 1 / 2790 }, + { name: 'Top', chance: 1 / 2775 }, + { name: 'Prophetic', chance: 1 / 2750 }, + { name: 'Upsilon', chance: 1 / 2740 }, + { name: 'Omega', chance: 1 / 2725 }, + { name: 'Lambda', chance: 1 / 2710 }, + { name: 'Delta', chance: 1 / 2665 }, + { name: 'Desire', chance: 1 / 2650 }, + { name: 'Experience', chance: 1 / 2637 }, + { name: 'Daydream', chance: 1 / 2600 }, + { name: 'Alpha', chance: 1 / 2560 }, + { name: 'bridged', chance: 1 / 2550 }, + { name: 'Peripherals', chance: 1 / 2500 }, + { name: 'kappa', chance: 1 / 2490 }, + { name: 'Micro', chance: 1 / 2450 }, + { name: 'Terminal', chance: 1 / 2400 }, + { name: 'pale', chance: 1 / 2390 }, + { name: 'Overload', chance: 1 / 2360 }, + { name: 'Equinox', chance: 1 / 2300 }, + { name: 'Thoughts', chance: 1 / 2250 }, + { name: 'Coherence', chance: 1 / 2200 }, + { name: 'Verbose', chance: 1 / 2150 }, + { name: 'Pillars', chance: 1 / 2140 }, + { name: 'horsehead hahahaha', chance: 1 / 2125 }, + { name: 'Solstice', chance: 1 / 2100 }, + { name: 'Orion', chance: 1 / 2090 }, + { name: 'Crab', chance: 1 / 2075 }, + { name: 'Paralysis', chance: 1 / 2050 }, + { name: 'Veil', chance: 1 / 2040 }, + { name: 'Ring', chance: 1 / 2025 }, + { name: 'Paradox', chance: 1 / 2000 }, + { name: 'Helix', chance: 1 / 1990 }, + { name: 'Dumbbell', chance: 1 / 1975 }, + { name: 'Duration', chance: 1 / 1950 }, + { name: 'Owl', chance: 1 / 1940 }, + { name: 'Butterfly', chance: 1 / 1925 }, + { name: 'Despair', chance: 1 / 1900 }, + { name: 'Eskimo', chance: 1 / 1890 }, + { name: 'Lagoon', chance: 1 / 1875 }, + { name: 'funny haha', chance: 1 / 1870 }, + { name: 'Wildfire', chance: 1 / 1854 }, + { name: 'Trifid', chance: 1 / 1840 }, + { name: 'Eagle', chance: 1 / 1825 }, + { name: 'Insanity', chance: 1 / 1800 }, + { name: 'Rosette', chance: 1 / 1790 }, + { name: 'Purpose', chance: 1 / 1750 }, + { name: 'Pelican', chance: 1 / 1725 }, + { name: 'Lunarity', chance: 1 / 1700 }, + { name: 'Swan', chance: 1 / 1690 }, + { name: 'California', chance: 1 / 1675 }, + { name: 'Twilight', chance: 1 / 1650 }, + { name: 'Cone', chance: 1 / 1640 }, + { name: 'Iris', chance: 1 / 1625 }, + { name: 'Constellation', chance: 1 / 1600 }, + { name: 'still playing?', chance: 1 / 1590 }, + { name: 'Jellyfish', chance: 1 / 1575 }, + { name: 'Gold', chance: 1 / 1570 }, + { name: 'wowie', chance: 1 / 1570 }, + { name: 'Heart', chance: 1 / 1560 }, + { name: 'Soul', chance: 1 / 1555 }, + { name: 'Aperture', chance: 1 / 1550 }, + { name: 'Eclipse', chance: 1 / 1500 }, + { name: 'Flame', chance: 1 / 1490 }, + { name: 'Tarantula', chance: 1 / 1475 }, + { name: '<>', chance: 1 / 1466 }, + { name: 'Keyhole', chance: 1 / 1455 }, + { name: 'Inferno', chance: 1 / 1444 }, + { name: 'Carina', chance: 1 / 1430 }, + { name: 'Tempered', chance: 1 / 1425 }, + { name: 'Documented', chance: 1 / 1410 }, + { name: 'Matrix', chance: 1 / 1405 }, + { name: 'Grayscale', chance: 1 / 1400 }, + { name: 'Homunculus', chance: 1 / 1390 }, + { name: 'Garden', chance: 1 / 1380 }, + { name: 'Constant', chance: 1 / 1350 }, + { name: 'Trapezium', chance: 1 / 1335 }, + { name: 'Access', chance: 1 / 1320 }, + { name: 'Betelgeuse', chance: 1 / 1310 }, + { name: 'Gladiator', chance: 1 / 1300 }, + { name: 'Rigel', chance: 1 / 1285 }, + { name: 'Sirius', chance: 1 / 1270 }, + { name: 'Amethyst', chance: 1 / 1260 }, + { name: 'Blink', chance: 1 / 1245 }, + { name: 'Cobalt', chance: 1 / 1230 }, + { name: 'Procyon', chance: 1 / 1215 }, + { name: 'Terrifying', chance: 1 / 1200 }, + { name: 'Aldebaran', chance: 1 / 1190 }, + { name: 'Heliocentric', chance: 1 / 1175 }, + { name: 'Antares', chance: 1 / 1160 }, + { name: 'Comet', chance: 1 / 1150 }, + { name: 'Arcturus', chance: 1 / 1140 }, + { name: 'Vega', chance: 1 / 1125 }, + { name: 'anyone there?', chance: 1 / 1100 }, + { name: 'Capella', chance: 1 / 1090 }, + { name: 'Stressed', chance: 1 / 1075 }, + { name: 'Pollux', chance: 1 / 1060 }, + { name: 'Divine', chance: 1 / 1050 }, + { name: 'Fomalhaut', chance: 1 / 1035 }, + { name: 'Meteor', chance: 1 / 1025 }, + { name: 'Deneb', chance: 1 / 1010 }, + { name: 'Lunar', chance: 1 / 1000 }, + { name: 'Regulus', chance: 1 / 990 }, + { name: 'Hopeless', chance: 1 / 975 }, + { name: 'Altair', chance: 1 / 960 }, + { name: 'Appalled', chance: 1 / 950 }, + { name: 'Spica', chance: 1 / 935 }, + { name: 'Dreamy', chance: 1 / 930 }, + { name: 'Index', chance: 1 / 915 }, + { name: 'Catastropic', chance: 1 / 900 }, + { name: 'Achernar', chance: 1 / 885 }, + { name: 'Gravity', chance: 1 / 865 }, + { name: 'Hadar', chance: 1 / 850 }, + { name: 'Equations', chance: 1 / 830 }, + { name: 'Canopus', chance: 1 / 815 }, + { name: 'Tidal', chance: 1 / 800 }, + { name: 'Lucky', chance: 1 / 777 }, + { name: 'Starlight', chance: 1 / 750 }, + { name: 'Proxima', chance: 1 / 735 }, + { name: 'IO', chance: 1 / 720 }, + { name: 'Merciful', chance: 1 / 700 }, + { name: 'Worried', chance: 1 / 675 }, + { name: 'the spooky', chance: 1 / 666 }, + { name: 'Process', chance: 1 / 650 }, + { name: 'Celestial', chance: 1 / 625 }, + { name: 'Divinity', chance: 1 / 600 }, + { name: 'Lonely', chance: 1 / 575 }, + { name: 'Storm', chance: 1 / 550 }, + { name: 'Cosmos', chance: 1 / 535 }, + { name: 'Glass', chance: 1 / 520 }, + { name: 'Lazer', chance: 1 / 500 }, + { name: 'Jetdroid', chance: 1 / 475 }, + { name: 'Prism', chance: 1 / 450 }, + { name: 'Ultra', chance: 1 / 430 }, + { name: 'Astral', chance: 1 / 400 }, + { name: 'Fearful', chance: 1 / 375 }, + { name: 'Orbit', chance: 1 / 350 }, + { name: 'Solar', chance: 1 / 325 }, + { name: 'Chroma', chance: 1 / 300 }, + { name: 'Guilty', chance: 1 / 275 }, + { name: 'Theory', chance: 1 / 250 }, + { name: 'heartstruck', chance: 1 / 230 }, + { name: 'Crazy', chance: 1 / 200 }, + { name: 'Saturn', chance: 1 / 185 }, + { name: 'Lapis', chance: 1 / 170 }, + { name: 'Fabled', chance: 1 / 150 }, + { name: 'Troubled', chance: 1 / 135 }, + { name: 'Superior', chance: 1 / 120 }, + { name: 'Jupiter', chance: 1 / 110 }, + { name: 'Rainbow', chance: 1 / 100 }, + { name: 'meh', chance: 1 / 95 }, + { name: 'Distorted', chance: 1 / 90 }, + { name: 'windy', chance: 1 / 86 }, + { name: 'sandy', chance: 1 / 82 }, + { name: 'Hardcore', chance: 1 / 80 }, + { name: 'Berry', chance: 1 / 75 }, + { name: 'Lucid', chance: 1 / 72 }, + { name: 'Legendary', chance: 1 / 70 }, + { name: 'Mars', chance: 1 / 65 }, + { name: 'Neon', chance: 1 / 60 }, + { name: 'Comfort', chance: 1 / 55 }, + { name: 'Amazing', chance: 1 / 50 }, + { name: 'Voltage', chance: 1 / 47 }, + { name: 'skill issue', chance: 1 / 46 }, + { name: 'Epic', chance: 1 / 45 }, + { name: 'Venus', chance: 1 / 40 }, + { name: 'Formula', chance: 1 / 37 }, + { name: 'Rare', chance: 1 / 35 }, + { name: 'Apple', chance: 1 / 33 }, + { name: 'Good', chance: 1 / 30 }, + { name: 'Mercury', chance: 1 / 26 }, + { name: 'Cherry', chance: 1 / 23 }, + { name: 'Decent', chance: 1 / 20 }, + { name: 'Tired', chance: 1 / 15 }, + { name: 'Cool', chance: 1 / 10 }, + { name: 'roll more CMON', chance: 1 / 9 }, + { name: 'Blown', chance: 1 / 7 }, + { name: 'Garbage', chance: 1 / 5 }, + { name: 'Uncommon', chance: 1 / 4 }, + { name: 'Common', chance: 1 / 2 }, +]; + +// Cutscene mapping +const cutsceneMap = { + cat: 'assets/videos/cat.mp4', + SUMMER: 'assets/videos/SUMMER.mp4', + Points: 'assets/videos/averagecutscene1.mp4', + Electron: 'assets/videos/averagecutscene1.mp4', + Positron: 'assets/videos/averagecutscene1.mp4', + Promethium: 'assets/videos/averagecutscene1.mp4', + Neodymium: 'assets/videos/averagecutscene1.mp4', + Automatic: 'assets/videos/averagecutscene1.mp4', + Praseodymium: 'assets/videos/averagecutscene1.mp4', + Cerium: 'assets/videos/averagecutscene1.mp4', + Lanthanum: 'assets/videos/averagecutscene1.mp4', + Subzero: 'assets/videos/averagecutscene1.mp4', + Barium: 'assets/videos/averagecutscene1.mp4', + SyntaxError: 'assets/videos/averagecutscene1.mp4', + Cesium: 'assets/videos/averagecutscene1.mp4', + Alternate: 'assets/videos/averagecutscene1.mp4', + Titanium: 'assets/videos/averagecutscene1.mp4', + Scandium: 'assets/videos/averagecutscene1.mp4', + Calcium: 'assets/videos/averagecutscene1.mp4', + Script: 'assets/videos/averagecutscene1.mp4', +}; +let isCutscenePlaying = false; + +function playCutscene(rarityName, callback) { + const videoUrl = cutsceneMap[rarityName]; + if (!videoUrl) { + callback(); + return; + } + + isCutscenePlaying = true; + rollBtn.disabled = true; + + // STOP ALL MUSIC BEFORE CUTSCENE BECAUSE YES OF COURSE + const wasBackgroundMusicPlaying = !backgroundMusic.paused; + const wasLunarMusicPlaying = !lunarMusic.paused; + backgroundMusic.pause(); + lunarMusic.pause(); + + const overlay = document.getElementById('cutsceneOverlay'); + const video = document.getElementById('cutsceneVideo'); + + video.src = videoUrl; + overlay.classList.add('active'); + + // fade in + setTimeout(() => { + video.play().catch((err) => { + console.error('Video playback failed:', err); + endCutscene( + overlay, + callback, + wasBackgroundMusicPlaying, + wasLunarMusicPlaying, + ); + }); + }, 100); + + // when video ends + video.onended = () => { + endCutscene( + overlay, + callback, + wasBackgroundMusicPlaying, + wasLunarMusicPlaying, + ); + }; + + // Error handling + video.onerror = () => { + console.error('video failed to load'); + endCutscene( + overlay, + callback, + wasBackgroundMusicPlaying, + wasLunarMusicPlaying, + ); + }; +} + +function endCutscene( + overlay, + callback, + wasBackgroundMusicPlaying, + wasLunarMusicPlaying, +) { + // fade out + overlay.classList.add('fadeout'); + + setTimeout(() => { + overlay.classList.remove('active', 'fadeout'); + const video = document.getElementById('cutsceneVideo'); + video.pause(); + video.src = ''; + + callback(); + + // RESUME MUSIC AFTER CUTSCENE (if it was playing before) + const isMuted = checkMuteSettings(); + if (!isMuted) { + if (wasBackgroundMusicPlaying) { + backgroundMusic.play().catch(() => {}); + } + if (wasLunarMusicPlaying) { + lunarMusic.play().catch(() => {}); + } + } + + // Re-enable rolling after 5 seconds + setTimeout(() => { + isCutscenePlaying = false; + rollBtn.disabled = false; + }, 5000); + }, 500); +} + +const achievementsList = [ + { + id: 'roller', + name: 'Roller', + subtitle: 'Get 100 Rolls', + check: () => totalRolls >= 100, + }, + { + id: 'gambler', + name: 'Gambler', + subtitle: 'Get 200 Rolls', + check: () => totalRolls >= 200, + }, + { + id: 'discordMod', + name: 'Discord Mod', + subtitle: 'Get 300 Rolls', + check: () => totalRolls >= 300, + }, + { + id: 'touchGrass', + name: 'Touch Grass Please', + subtitle: 'Get 400 Rolls', + check: () => totalRolls >= 400, + }, + { + id: 'addicted', + name: 'Addicted', + subtitle: 'Get 500 Rolls', + check: () => totalRolls >= 500, + }, + { + id: 'insane', + name: 'Insane', + subtitle: 'Get 600 Rolls', + check: () => totalRolls >= 600, + }, + { + id: 'joel', + name: 'Joel', + subtitle: 'Get 700 Rolls', + check: () => totalRolls >= 700, + }, + { + id: 'crazyAddicted', + name: 'Crazy Addicted', + subtitle: 'Get 800 Rolls', + check: () => totalRolls >= 800, + }, + { + id: 'funkyTown', + name: 'Funky Town', + subtitle: 'Get 900 Rolls', + check: () => totalRolls >= 900, + }, + { + id: 'outsideTime', + name: "It's Outside Time Now", + subtitle: 'Get 1000 Rolls', + check: () => totalRolls >= 1000, + }, + { + id: 'actually-addicted', + name: 'Actually Addicted', + subtitle: 'Get 5000 Rolls', + check: () => totalRolls >= 5000, + }, + { + id: 'what', + name: '..what', + subtitle: 'Get 7000 Rolls', + check: () => totalRolls >= 7000, + }, + { + id: 'devoted', + name: 'Devoted', + subtitle: 'Get 10000 Rolls', + check: () => totalRolls >= 10000, + }, + { + id: 'get-a-job', + name: 'get a job', + subtitle: 'Get 15000 Rolls', + check: () => totalRolls >= 15000, + }, + { + id: 'genuinely-insane', + name: 'Genuinely Insane', + subtitle: 'Get 25000 Rolls', + check: () => totalRolls >= 25000, + }, + { + id: 'roll-factory', + name: 'Roll Factory', + subtitle: 'Get 30000 Rolls', + check: () => totalRolls >= 30000, + }, + { + id: 'just-one-more-roll', + name: 'Just One More Roll', + subtitle: 'Get 50000 Rolls', + check: () => totalRolls >= 50000, + }, + { + id: 'got-no-life', + name: 'Got No LIfe', + subtitle: 'Get 70000 Rolls', + check: () => totalRolls >= 70000, + }, + { + id: 'introverted', + name: 'Introverted', + subtitle: 'Get 150000 Rolls', + check: () => totalRolls >= 150000, + }, + { + id: 'holy-hell', + name: 'HOLY HELL', + subtitle: 'Get 500000 Rolls', + check: () => totalRolls >= 500000, + }, + { + id: 'dude', + name: 'dude', + subtitle: 'Get 1000000 Rolls', + check: () => totalRolls >= 1000000, + }, + { + id: 'please-just-stop', + name: 'PLEASE JUST STOP', + subtitle: 'Get 5000000 Rolls', + check: () => totalRolls >= 5000000, + }, + { + id: 'you-will-pay', + name: 'you will pay', + subtitle: 'Get 10000000 Rolls', + check: () => totalRolls >= 10000000, + }, + { + id: 'startingOut', + name: 'Starting Out', + subtitle: 'Get A Rarity Under 1/70', + check: (rarity) => rarity && 1 / rarity.chance < 70, + }, + { + id: 'lucky', + name: 'Lucky', + subtitle: 'Get A Rarity Above 1/70', + check: (rarity) => rarity && 1 / rarity.chance > 70, + }, + { + id: 'spammin', + name: 'Spammin', + subtitle: 'Get A Rarity Above 1/300', + check: (rarity) => rarity && 1 / rarity.chance > 300, + }, + { + id: 'leftHanded', + name: 'Left Handed', + subtitle: 'Get A Rarity Above 1/600', + check: (rarity) => rarity && 1 / rarity.chance > 600, + }, + { + id: 'insanelyLucky', + name: 'Insanely Lucky', + subtitle: 'Get A Rarity Above 1/800', + check: (rarity) => rarity && 1 / rarity.chance > 800, + }, + { + id: 'lunar', + name: 'Lunar', + subtitle: 'Get Lunar', + check: (rarity) => rarity && rarity.name === 'Lunar', + }, + { + id: 'jackpot', + name: 'Jackpot', + subtitle: 'Get A Rarity Above 5000', + check: (rarity) => rarity && 1 / rarity.chance > 5000, + }, + { + id: 'antimatter', + name: 'Antimatter', + subtitle: 'Get A Rarity Above 15000', + check: (rarity) => rarity && 1 / rarity.chance > 15000, + }, + { + id: 'oh-my-god', + name: 'oh my god', + subtitle: 'Get A Rarity Above 50000', + check: (rarity) => rarity && 1 / rarity.chance > 50000, + }, + { + id: 'market-crash', + name: 'Market Crash', + subtitle: 'Get A Rarity Above 100000', + check: (rarity) => rarity && 1 / rarity.chance > 100000, + }, + { + id: 'phenomenon', + name: 'Phenomenon', + subtitle: 'Get A Rarity Above 10000000', + check: (rarity) => rarity && 1 / rarity.chance > 10000000, + }, + { + id: 'ok-bro', + name: 'ok bro', + subtitle: 'Get A Rarity Above 1000000000', + check: (rarity) => rarity && 1 / rarity.chance > 1000000000, + }, + { + id: 'summer', + name: 'SUMMER', + subtitle: 'Get SUMMER', + check: (rarity) => rarity && rarity.name === 'SUMMER', + }, +]; + +function updateAchievementsUI() { + achievementsContainer.innerHTML = ''; + achievementsList.forEach((ach) => { + const unlocked = achievementsUnlocked.has(ach.id); + const div = document.createElement('div'); + div.className = 'achievement' + (unlocked ? ' unlocked' : ''); + const nameEl = document.createElement('div'); + nameEl.className = 'achievement-name'; + nameEl.textContent = ach.name; + const subEl = document.createElement('div'); + subEl.className = 'achievement-subtitle'; + subEl.textContent = ach.subtitle; + div.appendChild(nameEl); + div.appendChild(subEl); + achievementsContainer.appendChild(div); + }); +} + +function saveAllData() { + const arr = Array.from(inventoryData.values()).map( + ({ rarityObj, count }) => ({ + name: rarityObj.name, + chance: rarityObj.chance, + count, + }), + ); + localStorage.setItem(STORAGE_KEY, JSON.stringify(arr)); + localStorage.setItem(TOTAL_ROLLS_KEY, totalRolls); + localStorage.setItem( + ACHIEVEMENTS_KEY, + JSON.stringify(Array.from(achievementsUnlocked)), + ); + localStorage.setItem(ANOMALIES_KEY, String(anomalies)); + localStorage.setItem(ANOMALIES_USED_KEY, String(anomaliesUsed)); + localStorage.setItem(POINTS_KEY, points); + localStorage.setItem(SHOP_UPGRADES_KEY, JSON.stringify(shopUpgrades)); + localStorage.setItem( + SOLD_OUT_KEY, + JSON.stringify(Array.from(soldOutRarities.entries())), + ); + localStorage.setItem(POTIONS_KEY, JSON.stringify(playerPotions)); + localStorage.setItem( + ACTIVE_POTIONS_KEY, + JSON.stringify({ + active: activePotions, + duplicateLeft: duplicateRollsLeft, + }), + ); +} + +function loadAllData() { + const sr = localStorage.getItem(TOTAL_ROLLS_KEY); + if (sr !== null) { + totalRolls = parseInt(sr, 10); + updateTotalRolls(); + } + + const sv = localStorage.getItem(STORAGE_KEY); + if (sv) { + try { + JSON.parse(sv).forEach((item) => { + const o = rarities.find((r) => r.name === item.name); + if (o) { + const li = document.createElement('li'); + inventoryData.set(o.name, { + rarityObj: o, + count: item.count, + liElement: li, + }); + updateItem(inventoryData.get(o.name)); + inventoryList.appendChild(li); + } + }); + } catch {} + } + + const sa = localStorage.getItem(ACHIEVEMENTS_KEY); + if (sa) { + try { + const arr = JSON.parse(sa); + arr.forEach((id) => achievementsUnlocked.add(id)); + } catch {} + } + updateAchievementsUI(); + + const saAnom = localStorage.getItem(ANOMALIES_KEY); + if (saAnom !== null) anomalies = parseInt(saAnom, 10) || 0; + const saAnomUsed = localStorage.getItem(ANOMALIES_USED_KEY); + if (saAnomUsed !== null) anomaliesUsed = parseInt(saAnomUsed, 10) || 0; + recalcLuckMultiplier(); +} + +function updateTotalRolls() { + totalRollsEl.textContent = `total rolls: ${totalRolls}`; +} + +function addToInventory(o) { + if (inventoryData.has(o.name)) { + const d = inventoryData.get(o.name); + d.count++; + updateItem(d); + } else { + const li = document.createElement('li'); + inventoryData.set(o.name, { rarityObj: o, count: 1, liElement: li }); + updateItem(inventoryData.get(o.name)); + inventoryList.appendChild(li); + } + inventoryList.scrollTop = inventoryList.scrollHeight; + updateCollectedCounter(); + + if (shopUpgrades.duplicate > 0) { + const dupeChance = shopUpgrades.duplicate / 100; + if (Math.random() < dupeChance) { + // Add another copy! + if (inventoryData.has(o.name)) { + const d = inventoryData.get(o.name); + d.count++; + updateItem(d); + } + showAnomalyPopup('duplicate proc!'); + } + } + + // Handle duplicate potion + if (duplicateRollsLeft > 0) { + if (inventoryData.has(o.name)) { + const d = inventoryData.get(o.name); + d.count++; + updateItem(d); + } + duplicateRollsLeft--; + updateActivePotionsDisplay(); + saveAllData(); + } +} + +document.getElementById('buyMagnetBtn').addEventListener('click', () => { + const cost = 500 + shopUpgrades.magnet * 1000; + if (points >= cost && shopUpgrades.magnet < 5) { + points -= cost; + shopUpgrades.magnet++; + updatePointsDisplay(); + updateShopUI(); + saveAllData(); + } +}); + +document.getElementById('buyPrinterBtn').addEventListener('click', () => { + const cost = 1000 + shopUpgrades.printer * shopUpgrades.printer * 500; + if (points >= cost) { + points -= cost; + shopUpgrades.printer++; + updatePointsDisplay(); + updateShopUI(); + saveAllData(); + } +}); + +document.getElementById('buyDupeBtn').addEventListener('click', () => { + const cost = 800 + shopUpgrades.duplicate * shopUpgrades.duplicate * 400; + if (points >= cost && shopUpgrades.duplicate < 10) { + points -= cost; + shopUpgrades.duplicate++; + updatePointsDisplay(); + updateShopUI(); + saveAllData(); + } +}); + +// Point printer passive generation +setInterval(() => { + if (shopUpgrades.printer > 0) { + points += shopUpgrades.printer; + updatePointsDisplay(); + } +}, 1000); + +function updateItem(d) { + const { rarityObj, count, liElement } = d; + const denom = Math.round(1 / rarityObj.chance); + + liElement.textContent = + count > 1 + ? `${rarityObj.name} (1/${denom}) x${count}` + : `${rarityObj.name} (1/${denom})`; + + liElement.classList.add('new-roll'); + setTimeout(() => liElement.classList.remove('new-roll'), 2000); + + const key = rarityObj.name; + const soldData = soldOutRarities.get(key); + if (soldData && soldData.count >= count) { + liElement.classList.add('sold-out'); + } else { + liElement.classList.remove('sold-out'); + } + + // Remove old handler by replacing element (prevents memory leaks) + liElement.ondblclick = null; + + // Add new handler + liElement.addEventListener( + 'dblclick', + function sellHandler() { + const currentData = inventoryData.get(rarityObj.name); + if (!currentData) return; + + const soldData = soldOutRarities.get(key); + const alreadySold = soldData ? soldData.count : 0; + const availableToSell = currentData.count - alreadySold; + + if (availableToSell <= 0) { + alert('all copies already sold out!'); + return; + } + + const pointsEarned = calculateRarityPoints(rarityObj) * availableToSell; + + showConfirmModal( + 'sell rarity?', + `sell ${availableToSell}x ${rarityObj.name} for ${pointsEarned} points? (you keep the rarity)`, + () => { + points += pointsEarned; + soldOutRarities.set(key, { count: currentData.count }); + updatePointsDisplay(); + updateShopUI(); + saveAllData(); + updateItem(currentData); + recalcLuckMultiplier(); + updateLuckDisplay(); + }, + ); + }, + { once: false }, + ); +} + +function getRandomRarity() { + let totalWeight = 0; + rarities.forEach((r) => { + const denom = Math.round(1 / r.chance); + const isNoticeable = denom >= 100; + + let mult = 1; + if (luckBoostActive && isNoticeable) mult *= 4; + if (isNoticeable) mult *= globalLuckMultiplier; + + // Rarity magnet - boost uncollected rarities + if (shopUpgrades.magnet > 0 && !inventoryData.has(r.name) && isNoticeable) { + mult *= 1 + shopUpgrades.magnet * 0.1; + } + + totalWeight += r.chance * mult; + }); + + let rand = Math.random() * totalWeight; + + for (const o of rarities) { + const denom = Math.round(1 / o.chance); + const isNoticeable = denom >= 100; + let mult = 1; + if (luckBoostActive && isNoticeable) mult *= 4; + if (isNoticeable) mult *= globalLuckMultiplier; + + // Rarity magnet + if (shopUpgrades.magnet > 0 && !inventoryData.has(o.name) && isNoticeable) { + mult *= 1 + shopUpgrades.magnet * 0.1; + } + + const effectiveChance = o.chance * mult; + rand -= effectiveChance; + if (rand <= 0) return o; + } + + return rarities[rarities.length - 1]; +} + +function checkAchievements(currentRarity) { + let newlyUnlocked = false; + achievementsList.forEach((ach) => { + if (!achievementsUnlocked.has(ach.id)) { + if (ach.check(currentRarity)) { + achievementsUnlocked.add(ach.id); + newlyUnlocked = true; + } + } + }); + if (newlyUnlocked) { + updateAchievementsUI(); + saveAllData(); + } +} + +function updateAnomalyUI() { + const el = document.getElementById('anomalyCount'); + if (!el) return; + el.textContent = `Anomalies: ${anomalies}`; + + const btn = document.getElementById('consumeAnomalyBtn'); + if (btn) btn.disabled = anomalies <= 0; + + const allBtn = document.getElementById('consumeAllAnomaliesBtn'); + if (allBtn) allBtn.disabled = anomalies <= 0; +} + +function awardAnomalyIfEligible(rarityObj) { + if (!rarityObj) return false; + const denom = Math.round(1 / rarityObj.chance); + if (denom > 10000) { + anomalies++; + try { + localStorage.setItem(ANOMALIES_KEY, String(anomalies)); + } catch {} + showAnomalyPopup('+1 anomaly'); + updateAnomalyUI(); + saveAllData(); + return true; + } + return false; +} + +function showAnomalyPopup(text) { + let p = document.getElementById('anomalyPopup'); + if (!p) { + p = document.createElement('div'); + p.id = 'anomalyPopup'; + document.body.appendChild(p); + } + p.textContent = text; + p.classList.add('show'); + setTimeout(() => p.classList.remove('show'), 1500); +} + +function consumeAnomaly() { + if (anomalies <= 0) { + alert('no anomalies to consume :('); + return; + } + anomalies--; + anomaliesUsed++; + recalcLuckMultiplier(); + updateAnomalyUI(); + updateLuckDisplay(); + saveAllData(); + showAnomalyPopup('ANOMALY CONSUMED! permanent boost'); +} + +function consumeAllAnomalies() { + if (anomalies <= 0) { + alert('no anomalies to consume :('); + return; + } + + const count = anomalies; + anomaliesUsed += count; + anomalies = 0; + + recalcLuckMultiplier(); + updateAnomalyUI(); + updateLuckDisplay(); + saveAllData(); + showAnomalyPopup( + `CONSUMED ${count} ANOMALIES! +${(count * 0.5).toFixed(1)}x permanent luck!`, + ); +} + +function renderSortedInventory(mode) { + inventoryList.innerHTML = ''; + + let items = Array.from(inventoryData.values()); + + if (mode === 'rare') { + items.sort((a, b) => a.rarityObj.chance - b.rarityObj.chance); + } + + if (mode === 'common') { + items.sort((a, b) => b.rarityObj.chance - a.rarityObj.chance); + } + + if (mode === 'alpha') { + items.sort((a, b) => a.rarityObj.name.localeCompare(b.rarityObj.name)); + } + + items.forEach((d) => inventoryList.appendChild(d.liElement)); +} + +const savedPoints = localStorage.getItem(POINTS_KEY); +if (savedPoints !== null) points = parseInt(savedPoints, 10) || 0; + +const savedUpgrades = localStorage.getItem(SHOP_UPGRADES_KEY); +if (savedUpgrades) { + try { + shopUpgrades = JSON.parse(savedUpgrades); + } catch {} +} + +const savedSoldOut = localStorage.getItem(SOLD_OUT_KEY); +if (savedSoldOut) { + try { + soldOutRarities = new Map(JSON.parse(savedSoldOut)); + } catch {} +} + +shopLuckMultiplier = 1 + shopUpgrades.luck * 0.1; +rollSpeed = Math.max(0.25, 1.0 - shopUpgrades.speed * 0.2); +pointDivisor = Math.max(1.0, 3.0 - shopUpgrades.pointMult * 0.2); + +const savedPotions = localStorage.getItem(POTIONS_KEY); +if (savedPotions) { + try { + playerPotions = JSON.parse(savedPotions); + } catch {} +} + +const savedActive = localStorage.getItem(ACTIVE_POTIONS_KEY); +if (savedActive) { + try { + const data = JSON.parse(savedActive); + activePotions = data.active || []; + duplicateRollsLeft = data.duplicateLeft || 0; + recalcPotionLuck(); + updateActivePotionsDisplay(); + } catch {} +} + +updatePotionUI(); +recalcLuckMultiplier(); +updatePointsDisplay(); +updateShopUI(); + +function resetInventory() { + if ( + confirm( + 'are you comfortably sure that you will delete your sweet sweet data???', + ) + ) { + localStorage.removeItem(STORAGE_KEY); + localStorage.removeItem(TOTAL_ROLLS_KEY); + localStorage.removeItem(ACHIEVEMENTS_KEY); + localStorage.removeItem(ANOMALIES_KEY); + localStorage.removeItem(ANOMALIES_USED_KEY); + localStorage.removeItem(POINTS_KEY); + localStorage.removeItem(SHOP_UPGRADES_KEY); + localStorage.removeItem(SOLD_OUT_KEY); + localStorage.removeItem(LUCK_KEY); + localStorage.removeItem('daily_lastClaim'); + localStorage.removeItem('daily_streak'); + localStorage.removeItem('weekly_lastClaim'); + localStorage.removeItem('weekly_streak'); + localStorage.removeItem(playtimeKey); + + inventoryData.clear(); + inventoryList.innerHTML = ''; + achievementsUnlocked.clear(); + updateAchievementsUI(); + totalRolls = 0; + updateTotalRolls(); + points = 0; + anomalies = 0; + anomaliesUsed = 0; + shopUpgrades = { luck: 0, speed: 0, pointMult: 0 }; + soldOutRarities.clear(); + shopLuckMultiplier = 1.0; + rollSpeed = 1.0; + pointDivisor = 3.0; + totalSeconds = 0; + + recalcLuckMultiplier(); + updatePointsDisplay(); + updateShopUI(); + updateAnomalyUI(); + updatePlaytimeDisplay(); + updateLuckDisplay(); + + alert('all data reset! it was your choice btw'); + location.reload(); + } +} + +function startLuckBoost() { + luckBoostActive = true; + luckBoostEndTime = Date.now() + 60000; + updateLuckDisplay(); + + document.getElementById('luckBoostOverlay').style.display = 'flex'; + + localStorage.setItem( + LUCK_KEY, + JSON.stringify({ + active: luckBoostActive, + endTime: luckBoostEndTime, + }), + ); + + if (luckInterval) clearInterval(luckInterval); + luckInterval = setInterval(updateLuckTimer, 200); +} + +function updateLuckTimer() { + const timerEl = document.getElementById('luckTimer'); + + const msLeft = luckBoostEndTime - Date.now(); + + if (msLeft <= 0) { + endLuckBoost(); + return; + } + + timerEl.textContent = Math.ceil(msLeft / 1000); +} + +function endLuckBoost() { + luckBoostActive = false; + luckBoostEndTime = 0; + updateLuckDisplay(); + + document.getElementById('luckBoostOverlay').style.display = 'none'; + + if (luckInterval) clearInterval(luckInterval); + + localStorage.removeItem(LUCK_KEY); +} + +function checkMuteSettings() { + try { + const settingsStr = localStorage.getItem('userSettings'); + if (settingsStr) { + const settings = JSON.parse(settingsStr); + if (settings.muted) { + backgroundMusic.pause(); + backgroundMusic.volume = 0; + lunarMusic.pause(); + lunarMusic.volume = 0; + return true; + } + } + } catch (e) {} + return false; +} + +function spinAndReveal(res) { + spinner.innerHTML = ''; + const items = []; + for (let i = 0; i < 50; i++) { + items.push(rarities[Math.floor(Math.random() * rarities.length)]); + } + items.push(res); + items.forEach((o) => { + const d = document.createElement('div'); + d.className = 'spin-item'; + d.textContent = o.name; + spinner.appendChild(d); + }); + + if (totalRolls % 100 === 0) { + startLuckBoost(); + } + + const h = 48, + total = items.length, + scroll = h * (total - 1); + const duration = rollSpeed; + spinner.style.transition = `transform ${duration}s ease-out`; + spinner.style.transform = `translateY(-${scroll}px)`; + + setTimeout( + () => { + totalRolls++; + updateTotalRolls(); + addToInventory(res); + awardAnomalyIfEligible(res); + checkAchievements(res); + + // Check if this rarity has a cutscene edeedded + if (cutsceneMap[res.name]) { + playCutscene(res.name, () => { + // after cutscene ends, handle music + const isMuted = checkMuteSettings(); + + if (res.name === 'Lunar') { + if (!isMuted) { + lunarMusic.currentTime = 0; + lunarMusic.play(); + } + backgroundMusic.pause(); + } else { + lunarMusic.pause(); + if (!isMuted) { + backgroundMusic.play(); + } + } + + saveAllData(); + }); + } else { + // no cutscene, proceed normally + const isMuted = checkMuteSettings(); + + if (res.name === 'Lunar') { + if (!isMuted) { + lunarMusic.currentTime = 0; + lunarMusic.play(); + } + backgroundMusic.pause(); + } else { + lunarMusic.pause(); + if (!isMuted) { + backgroundMusic.play(); + } + } + + rollBtn.disabled = false; + saveAllData(); + } + }, + duration * 1000 + 1000, + ); +} + +const sortSelect = document.getElementById('sortSelect'); + +rollBtn.addEventListener('click', () => { + if (isCutscenePlaying) return; + rollBtn.disabled = true; + spinner.style.transition = 'none'; + spinner.style.transform = 'translateY(0)'; + const res = getRandomRarity(); + + const isMuted = checkMuteSettings(); + if (!isMuted && backgroundMusic.paused && res.name !== 'Lunar') { + backgroundMusic.play().catch(() => {}); + } + + setTimeout(() => spinAndReveal(res), 100); +}); + +if (sortSelect) { + sortSelect.addEventListener('change', () => { + renderSortedInventory(sortSelect.value); + }); +} + +resetBtn.addEventListener('click', resetInventory); + +loadAllData(); +updateTotalRolls(); +const ls = localStorage.getItem(LUCK_KEY); +if (ls) { + try { + const obj = JSON.parse(ls); + if (obj.active && obj.endTime > Date.now()) { + luckBoostActive = true; + luckBoostEndTime = obj.endTime; + document.getElementById('luckBoostOverlay').style.display = 'flex'; + luckInterval = setInterval(updateLuckTimer, 200); + } else { + localStorage.removeItem(LUCK_KEY); + } + } catch {} +} + +function updateCollectedCounter() { + const collected = inventoryData.size; + const total = rarities.length; + document.getElementById('collectedCounter').textContent = + `${collected}/${total} collected`; +} + +updateAchievementsUI(); +updateCollectedCounter(); + +const consumeBtn = document.getElementById('consumeAnomalyBtn'); +if (consumeBtn) { + consumeBtn.addEventListener('click', () => { + consumeAnomaly(); + }); +} + +const consumeAllBtn = document.getElementById('consumeAllAnomaliesBtn'); +if (consumeAllBtn) { + consumeAllBtn.addEventListener('click', () => { + consumeAllAnomalies(); + }); +} + +updateAnomalyUI(); +renderSortedInventory(sortSelect.value); + +setInterval(saveAllData, 10000); + +document.addEventListener('DOMContentLoaded', () => { + const btn = document.getElementById('rollBtn'); + if (btn) { + btn.addEventListener('click', () => { + btn.classList.remove('scale-up'); + void btn.offsetWidth; + btn.classList.add('scale-up'); + }); + } else { + console.warn('rollBtn not found'); + } +}); + +function formatPlaytime(seconds) { + const h = Math.floor(seconds / 3600); + const m = Math.floor((seconds % 3600) / 60); + const s = seconds % 60; + if (h > 0) return `${h}h ${m}m`; + if (m > 0) return `${m}m ${s}s`; + return `${s}s`; +} + +const weeklyBtn = document.getElementById('weeklyBtn'); +const weeklyStatus = document.getElementById('weeklyStatus'); + +function loadWeeklyData() { + return { + lastClaim: localStorage.getItem('weekly_lastClaim'), + streak: Number(localStorage.getItem('weekly_streak') || 0), + }; +} + +function saveWeeklyData(lastClaim, streak) { + localStorage.setItem('weekly_lastClaim', lastClaim); + localStorage.setItem('weekly_streak', streak); +} + +function updateWeeklyUI() { + const { lastClaim, streak } = loadWeeklyData(); + const now = Date.now(); + const oneWeek = 7 * 24 * 60 * 60 * 1000; + + if (!lastClaim || now - Number(lastClaim) >= oneWeek) { + weeklyBtn.disabled = false; + weeklyStatus.textContent = `weekly reward available · streak: ${streak}`; + } else { + weeklyBtn.disabled = true; + weeklyStatus.textContent = `weekly claimed · streak: ${streak}`; + } +} + +weeklyBtn.addEventListener('click', () => { + const { lastClaim, streak } = loadWeeklyData(); + const now = Date.now(); + let newStreak = streak; + + if (!lastClaim) { + newStreak = 1; + } else { + const diffWeeks = Math.floor( + (now - Number(lastClaim)) / (7 * 24 * 60 * 60 * 1000), + ); + newStreak = diffWeeks === 1 ? streak + 1 : 1; + } + + saveWeeklyData(now.toString(), newStreak); + updateWeeklyUI(); + alert(`weekly claimed!\nstreak: ${newStreak}`); +}); + +updateWeeklyUI(); + +const dailyBtn = document.getElementById('dailyBtn'); +const dailyStatus = document.getElementById('dailyStatus'); + +function getToday() { + const d = new Date(); + const y = d.getFullYear(); + const m = String(d.getMonth() + 1).padStart(2, '0'); + const day = String(d.getDate()).padStart(2, '0'); + return `${y}-${m}-${day}`; +} + +function loadDailyData() { + return { + lastClaim: localStorage.getItem('daily_lastClaim'), + streak: Number(localStorage.getItem('daily_streak') || 0), + }; +} + +function saveDailyData(lastClaim, streak) { + localStorage.setItem('daily_lastClaim', lastClaim); + localStorage.setItem('daily_streak', streak); +} + +function updateDailyUI() { + const { lastClaim, streak } = loadDailyData(); + const today = getToday(); + + if (lastClaim === today) { + dailyBtn.disabled = true; + dailyStatus.textContent = `daily claimed · streak: ${streak}`; + } else { + dailyBtn.disabled = false; + dailyStatus.textContent = `daily available · current streak: ${streak}`; + } +} + +dailyBtn.addEventListener('click', () => { + const today = getToday(); + const { lastClaim, streak } = loadDailyData(); + + let newStreak = streak; + + if (!lastClaim) { + newStreak = 1; + } else { + const last = new Date(lastClaim); + const now = new Date(today); + + const diffDays = Math.round((now - last) / (1000 * 60 * 60 * 24)); // fuh theres an error here that doesnt do ANYTHING... nastyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy + + if (diffDays === 1) { + newStreak += 1; + } else if (diffDays > 1) { + newStreak = 1; + } + } + + saveDailyData(today, newStreak); + updateDailyUI(); + + alert(`daily claimed!\nstreak: ${newStreak}`); +}); + +updateDailyUI(); + +const genBtn = document.getElementById('generateRunCard'); +if (genBtn) genBtn.addEventListener('click', generateRunCard); +else + console.warn( + 'generateRunCard button not found in DOM, maybe consider... adding it in the DOM????', + ); + +function generateRunCard() { + const rarityCounts = {}; + for (const [name, { rarityObj, count }] of inventoryData.entries()) { + rarityCounts[rarityObj.name] = (rarityCounts[rarityObj.name] || 0) + count; + } + + const canvas = document.createElement('canvas'); + canvas.width = 800; + canvas.height = 420; + const ctx = canvas.getContext('2d'); + + ctx.fillStyle = '#0e0e0e'; + ctx.fillRect(0, 0, canvas.width, canvas.height); + + ctx.fillStyle = '#dcdcdc'; + ctx.font = '18px monospace'; + ctx.fillText("auth's RNG ::: run summary", 40, 38); + ctx.font = '12px monospace'; + ctx.fillText('────────────────────────', 40, 58); + + ctx.font = '14px monospace'; + let y = 90; + const line = (t, indent = 0) => { + ctx.fillText(t, 40 + indent, y); + y += 22; + }; + + line(`total rolls ${totalRolls}`); + line(`playtime ${formatPlaytime(totalSeconds)}`); + line(`run id ${runId}`); + line(''); + line('rarities collected:'); + + if (Object.keys(rarityCounts).length === 0) { + line(' (none yet)'); + } else { + const entries = Object.entries(rarityCounts).sort((a, b) => b[1] - a[1]); + const MAX_LINES = 18; + let i = 0; + for (const [name, cnt] of entries) { + if (i >= MAX_LINES) break; + const short = name.length > 24 ? name.slice(0, 21) + '...' : name; + line(`${short.padEnd(24)} x${cnt}`, 12); + i++; + } + if (entries.length > MAX_LINES) { + line(`...and ${entries.length - MAX_LINES} more`, 9); + } + } + + const dataUrl = canvas.toDataURL('image/png'); + const a = document.createElement('a'); + a.href = dataUrl; + a.download = 'authsrng_run.png'; + + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + + console.log('loaded!'); + console.log('all assets are loaded'); +} + +window.backgroundMusic = backgroundMusic; +window.lunarMusic = lunarMusic; + +document.getElementById('buyLuckBtn').addEventListener('click', () => { + const cost = 50 + shopUpgrades.luck * 25; + if (points >= cost && shopUpgrades.luck < 100) { + points -= cost; + shopUpgrades.luck++; + shopLuckMultiplier = 1 + shopUpgrades.luck * 0.1; + recalcLuckMultiplier(); + updatePointsDisplay(); + updateShopUI(); + saveAllData(); + } +}); + +document.getElementById('buySpeedBtn').addEventListener('click', () => { + const cost = 100 + shopUpgrades.speed * 50; + if (points >= cost && shopUpgrades.speed < 3) { + points -= cost; + shopUpgrades.speed++; + rollSpeed = Math.max(0.25, 1.0 - shopUpgrades.speed * 0.2); + updatePointsDisplay(); + updateShopUI(); + saveAllData(); + } +}); + +document.getElementById('buyPointBtn').addEventListener('click', () => { + const cost = 150 + shopUpgrades.pointMult * 75; + if (points >= cost && shopUpgrades.pointMult < 10) { + points -= cost; + shopUpgrades.pointMult++; + pointDivisor = Math.max(1.0, 3.0 - shopUpgrades.pointMult * 0.2); + updatePointsDisplay(); + updateShopUI(); + saveAllData(); + } +}); + +const buyMagnetBtn = document.getElementById('buyMagnetBtn'); +if (buyMagnetBtn) { + buyMagnetBtn.addEventListener('click', () => { + const cost = 500 + (shopUpgrades.magnet || 0) * 1000; + if (points >= cost && (shopUpgrades.magnet || 0) < 5) { + points -= cost; + shopUpgrades.magnet = (shopUpgrades.magnet || 0) + 1; + updatePointsDisplay(); + updateShopUI(); + saveAllData(); + } + }); +} + +const buyPrinterBtn = document.getElementById('buyPrinterBtn'); +if (buyPrinterBtn) { + buyPrinterBtn.addEventListener('click', () => { + const level = shopUpgrades.printer || 0; + const cost = 1000 + level * level * 500; + if (points >= cost) { + points -= cost; + shopUpgrades.printer = level + 1; + updatePointsDisplay(); + updateShopUI(); + saveAllData(); + } + }); +} + +const buyDupeBtn = document.getElementById('buyDupeBtn'); +if (buyDupeBtn) { + buyDupeBtn.addEventListener('click', () => { + const level = shopUpgrades.duplicate || 0; + const cost = 800 + level * level * 400; + if (points >= cost && level < 10) { + points -= cost; + shopUpgrades.duplicate = level + 1; + updatePointsDisplay(); + updateShopUI(); + saveAllData(); + } + }); +} + +// Point printer passive generation +setInterval(() => { + if (shopUpgrades.printer && shopUpgrades.printer > 0) { + points += shopUpgrades.printer; + updatePointsDisplay(); + } +}, 1000); +updatePointsDisplay(); +updateShopUI(); + +document.addEventListener('DOMContentLoaded', function () { + const indexBtn = document.getElementById('indexBtn'); + const indexModal = document.getElementById('indexModal'); + const indexClose = document.getElementById('indexClose'); + const indexList = document.getElementById('indexList'); + const indexStats = document.getElementById('indexStats'); + const indexSearch = document.getElementById('indexSearch'); + + // Safety check + if (!indexBtn || !indexModal || !indexClose || !indexList || !indexStats) { + console.warn( + 'Index elements not found. Make sure modal HTML is in the page.', + ); + return; + } + + function openIndex() { + updateIndexDisplay(); + indexModal.classList.add('show'); + if (indexSearch) { + indexSearch.value = ''; + indexSearch.focus(); + } + } + + function closeIndex() { + indexModal.classList.remove('show'); + } + + function updateIndexDisplay(searchTerm = '') { + // Update stats + const collected = inventoryData.size; + const total = rarities.length; + indexStats.textContent = `${collected}/${total} collected`; + + // Clear and rebuild list + indexList.innerHTML = ''; + + // Sort rarities by chance (RAREST FIRST - smallest chance value = rarest) + const sortedRarities = [...rarities].sort((a, b) => a.chance - b.chance); + + // Filter by search term + const filteredRarities = searchTerm + ? sortedRarities.filter((rarity) => { + const isUnlocked = inventoryData.has(rarity.name); + // Only search unlocked rarities by name + return ( + isUnlocked && + rarity.name.toLowerCase().includes(searchTerm.toLowerCase()) + ); + }) + : sortedRarities; + + // Show message if no results + if (filteredRarities.length === 0 && searchTerm) { + const noResults = document.createElement('div'); + noResults.style.textAlign = 'center'; + noResults.style.opacity = '0.5'; + noResults.style.padding = '20px'; + noResults.textContent = 'no rarities found'; + indexList.appendChild(noResults); + return; + } + + filteredRarities.forEach((rarity) => { + const isUnlocked = inventoryData.has(rarity.name); + const count = isUnlocked ? inventoryData.get(rarity.name).count : 0; + + const item = document.createElement('div'); + item.className = `index-item ${isUnlocked ? 'unlocked' : 'locked'}`; + + const leftSide = document.createElement('div'); + leftSide.style.display = 'flex'; + leftSide.style.alignItems = 'center'; + + const name = document.createElement('div'); + name.className = 'index-item-name'; + name.textContent = isUnlocked ? rarity.name : '???'; + + const chance = document.createElement('div'); + chance.className = 'index-item-chance'; + const denom = Math.round(1 / rarity.chance); + chance.textContent = isUnlocked ? `1/${denom}` : '1/???'; + chance.style.marginLeft = '12px'; + + leftSide.appendChild(name); + leftSide.appendChild(chance); + + const rightSide = document.createElement('div'); + if (isUnlocked && count > 0) { + const countEl = document.createElement('div'); + countEl.className = 'index-item-count'; + countEl.textContent = `x${count}`; + rightSide.appendChild(countEl); + } + + item.appendChild(leftSide); + item.appendChild(rightSide); + indexList.appendChild(item); + }); + } + + // Event listeners + indexBtn.addEventListener('click', openIndex); + indexClose.addEventListener('click', closeIndex); + + // Search functionality + if (indexSearch) { + indexSearch.addEventListener('input', (e) => { + updateIndexDisplay(e.target.value); + }); + } + + // Close on background click + indexModal.addEventListener('click', (e) => { + if (e.target === indexModal) { + closeIndex(); + } + }); + + // Close on Escape key + document.addEventListener('keydown', (e) => { + if (e.key === 'Escape' && indexModal.classList.contains('show')) { + closeIndex(); + } + }); +}); + +document.addEventListener('keydown', (e) => { + // Ignore if typing in input fields + if ( + e.target.tagName === 'INPUT' || + e.target.tagName === 'TEXTAREA' || + e.target.tagName === 'SELECT' + ) { + return; + } + + const key = e.key.toLowerCase(); + + // Space = Roll (+ for start anim) + if (key === ' ' || key === '+') { + e.preventDefault(); + const rollBtn = document.getElementById('rollBtn'); + if (rollBtn && !rollBtn.disabled) { + rollBtn.click(); + } + } + + // A = Previous page, D = Next page + if (key === 'a') { + e.preventDefault(); + const prevBtn = document.getElementById('prevPage'); + if (prevBtn && !prevBtn.disabled) { + prevBtn.click(); + } + } + + if (key === 'd') { + e.preventDefault(); + const nextBtn = document.getElementById('nextPage'); + if (nextBtn && !nextBtn.disabled) { + nextBtn.click(); + } + } + + // W = Click (simulates mouse click at cursor position) + if (key === 'w') { + e.preventDefault(); + const elementUnderCursor = document.elementFromPoint( + window.lastMouseX || window.innerWidth / 2, + window.lastMouseY || window.innerHeight / 2, + ); + if (elementUnderCursor) { + elementUnderCursor.click(); + } + } +}); + +// Track mouse position for W key +window.lastMouseX = window.innerWidth / 2; +window.lastMouseY = window.innerHeight / 2; + +document.addEventListener('mousemove', (e) => { + window.lastMouseX = e.clientX; + window.lastMouseY = e.clientY; +}); + +const WELL_KEY = 'wishingWell'; +const WELL_COOLDOWN = 2 * 60 * 60 * 1000; + +let wellData = { + lastThrow: 0, + totalThrown: 0, + totalReceived: 0, + timesThrown: 0, + successes: 0, +}; + +// Load well data +function loadWellData() { + const saved = localStorage.getItem(WELL_KEY); + if (saved) { + try { + wellData = JSON.parse(saved); + } catch (e) { + console.error('Failed to load well data:', e); + } + } + updateWellUI(); +} + +// Save well data +function saveWellData() { + localStorage.setItem(WELL_KEY, JSON.stringify(wellData)); +} + +// Set well amount from quick buttons +function setWellAmount(amount) { + const input = document.getElementById('wellInput'); + if (input) input.value = amount; +} + +// Check if well is on cooldown +function isWellOnCooldown() { + const now = Date.now(); + const timeSinceLastThrow = now - wellData.lastThrow; + return timeSinceLastThrow < WELL_COOLDOWN; +} + +// Get remaining cooldown time +function getRemainingCooldown() { + const now = Date.now(); + const elapsed = now - wellData.lastThrow; + const remaining = WELL_COOLDOWN - elapsed; + return Math.max(0, remaining); +} + +// Format time for display +function formatWellTime(ms) { + const hours = Math.floor(ms / (60 * 60 * 1000)); + const minutes = Math.floor((ms % (60 * 60 * 1000)) / (60 * 1000)); + const seconds = Math.floor((ms % (60 * 1000)) / 1000); + + if (hours > 0) return `${hours}h ${minutes}m`; + if (minutes > 0) return `${minutes}m ${seconds}s`; + return `${seconds}s`; +} + +// Update well UI +function updateWellUI() { + const status = document.getElementById('wellStatus'); + const timer = document.getElementById('wellTimer'); + const throwBtn = document.getElementById('throwWellBtn'); + const totalThrown = document.getElementById('wellTotalThrown'); + const totalReceived = document.getElementById('wellTotalReceived'); + const timesThrown = document.getElementById('wellTimesThrown'); + const successRate = document.getElementById('wellSuccessRate'); + + if (!status || !timer || !throwBtn) return; + + if (isWellOnCooldown()) { + const remaining = getRemainingCooldown(); + throwBtn.disabled = true; + status.textContent = 'the well is recovering its magic...'; + timer.textContent = `available in: ${formatWellTime(remaining)}`; + } else { + throwBtn.disabled = false; + status.textContent = 'ready to accept your offering'; + timer.textContent = ''; + } + + // Update stats + if (totalThrown) totalThrown.textContent = wellData.totalThrown; + if (totalReceived) totalReceived.textContent = wellData.totalReceived; + if (timesThrown) timesThrown.textContent = wellData.timesThrown; + if (successRate) { + const rate = + wellData.timesThrown > 0 + ? Math.round((wellData.successes / wellData.timesThrown) * 100) + : 0; + successRate.textContent = `${rate}%`; + } +} + +// Throw points into well +function throwIntoWell() { + const input = document.getElementById('wellInput'); + const amount = parseInt(input.value) || 0; + + // Validation + if (amount <= 0) { + alert('you must throw at least 1 point!'); + return; + } + + if (amount > points) { + alert(`you only have ${points} points!`); + return; + } + + if (isWellOnCooldown()) { + alert('the well is still recovering its magic!'); + return; + } + + // Deduct points + points -= amount; + updatePointsDisplay(); + + // Ripple animation + createWellRipple(); + + // 40% chance to win + const won = Math.random() < 0.4; + + // Update stats + wellData.lastThrow = Date.now(); + wellData.totalThrown += amount; + wellData.timesThrown++; + + let reward = 0; + + if (won) { + reward = amount * 2; + points += reward; + wellData.totalReceived += reward; + wellData.successes++; + updatePointsDisplay(); + showWellResult(true, reward); + } else { + showWellResult(false, amount); + } + + // Save everything + saveWellData(); + saveAllData(); + updateWellUI(); + + // Clear input + input.value = ''; + + // Start cooldown timer + startWellCooldownTimer(); +} + +// Create ripple animation +function createWellRipple() { + const visual = document.getElementById('wellVisual'); + if (!visual) return; + + const ripple = document.createElement('div'); + ripple.className = 'well-ripple'; + visual.appendChild(ripple); + + setTimeout(() => { + ripple.remove(); + }, 1500); +} + +// Show result modal +function showWellResult(won, amount) { + const modal = document.getElementById('wellResultModal'); + const icon = document.getElementById('wellResultIcon'); + const text = document.getElementById('wellResultText'); + const amountEl = document.getElementById('wellResultAmount'); + + if (!modal || !icon || !text || !amountEl) return; + + if (won) { + icon.textContent = '✨'; + text.textContent = 'the well grants your wish!'; + amountEl.textContent = `+${amount} points`; + amountEl.style.color = '#4a4'; + } else { + icon.textContent = '🌊'; + text.textContent = 'the well accepts your offering...'; + amountEl.textContent = 'but nothing happens'; + amountEl.style.color = '#888'; + } + + modal.classList.add('show'); +} + +// Close result modal +function closeWellResult() { + const modal = document.getElementById('wellResultModal'); + if (modal) modal.classList.remove('show'); +} + +// Start cooldown timer that updates every second +let wellTimerInterval = null; + +function startWellCooldownTimer() { + if (wellTimerInterval) clearInterval(wellTimerInterval); + + wellTimerInterval = setInterval(() => { + if (!isWellOnCooldown()) { + clearInterval(wellTimerInterval); + wellTimerInterval = null; + } + updateWellUI(); + }, 1000); +} + +// Initialize welling well +loadWellData(); + +// add da tee event listener to throw button +const throwWellBtn = document.getElementById('throwWellBtn'); +if (throwWellBtn) { + throwWellBtn.addEventListener('click', throwIntoWell); +} + +// CFGVHHSUGDCSVHBDJOKVHBHFDSJDOJFBH VSBJNSUHNKXJBHVGCTFDFGHIJNKJBHVGCFXRDTFYGUHINKMTDFGKN ,MNDWBGVFYGHEK;F,NKRG +if (isWellOnCooldown()) { + startWellCooldownTimer(); +} + +// FINISH THIS SCRIPT A;READY +window.setWellAmount = setWellAmount; +window.closeWellResult = closeWellResult; diff --git a/assets/scripts/screensaver.js b/assets/scripts/screensaver.js new file mode 100644 index 0000000..3533e96 --- /dev/null +++ b/assets/scripts/screensaver.js @@ -0,0 +1,260 @@ +(function(){var s=document.createElement('script');s.src='legacy-polyfills.js';s.async=false;document.head.appendChild(s);})(); + +(function() { + const messages = [ + "wake up", + "theres more to come", + "its not over yet", + "come back", + "gahhhhhhhhhhhhhhhhhhh", + "fun fact about auth: hes canadian", + "fun fact about auth: hes left handed", + "fun fact about auth: hes chinese", + "it's NOT okay bro", + "something called outside", + "hey loser wake up", + "you're idle bro", + "sfvszfhjksdhkshjsd", + "heyo", + "player please just wake up", + "do you like rolling", + "yo gambler how is this game going for you", + "ahahahahahahhahaha epic", + "im scared of math is it just me", + "bro is auto rolling right now HAHAHAHAHAHAH", + "did you just eat a chip", + "oh yeah okay", + "wake... up...", + "hey its 2080 the world is ending", + "*sigh*", + "alone intelligence", + "plugorino?? oh, no plugorino...", + "AAAAAHHHHH AUTH IS A FEMBOY OH NOOOOOOO", + "hm?", + "make the sun explode already", + "https://discord.gg/mTDw8jJYqX", + "i love freemium apps so much that they absolutely suck", + "ah ok", + "running on a rope here help me", + "music", + "when auths rng update? no", + "winner of the fastest update award", + "does anyone read this?", + "im sentient", + "gwmdgj hkmmmmmmmmmm", + "this is a call for help", + "rah???", + "join the discord server!", + "go outside and smell the flowers and hear the birds", + "discord mod lmao", + "GO TO SLEEP", + "fastest update award is given to ME", + "mommy says im special", + "let screensaver, textElem, idleTimer, messageTimer;", + "please speed i need this", + "my mom is kinda homeless", + "WOAAAAHHHHHHHHHHHH did you do a backflip???", + "also play terraria!", + "slurs", + "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee", + "this game was made with neocities", + "https://authsblog.blogspot.com/", + "stop clipfarming", + "imagine clipfarming", + "mommy said its your brothers turn", + "mommy said its your sisters turn", + "DIE", + "your hp is now 0", + "i wuv my mommy wahhhhhhhhhhhhhhhhhhhhhh", + "GDEHVHUWGFSW", + "123456789101112131415", + "rizz", + "im alpha sigma beta", + "help me make this make sense", + "shhh you're gonna wake the big one!!", + "snarpy", + "i started the Estrogen", + "auth is a femboy..", + "IM NOT A FEMBOY", + "swear words", + "bad words", + "slurs", + "Thinking...", + "auth's RNG is actually coded by mark zuckerberg", + "also go play... uhm... oh", + "https://icedcubed.neocities.org", + "i will chronically decline", + "WHO CHRONICALLY DECLINED", + "im watching you", + "im behind you", + "shhhh mommy's here", + "cant let the boys know i listen to mommy ASMR", + "the boys knew i listen to mommy ASMR...", + "geometry dash ou ou ou", + "ABORT. ABORT. ABORT.", + "AUGHHHHHHHH THE PAINNNNNNNNNNNN", + "stop gambling", + "how many family members have you lost while playing this", + "burger", + "VITAMIN C???", + "VITAMIN A???", + "VITAMIN B12???", + "this game is enough to kill a victorian child", + "yikes............", + "roblos", + "american freedom baby", + "SPIDER-", + "club penguin my beloved", + "i will say bad word", + "me adding the doctype html of whatever doctype html does", + "auths... the RNG?????", + "its spelled auth's RNG not auths rng cmon guys", + "LMAOOOOOOOOOO", + "DIE IN A FIRE SCUMBAG", + "robber games", + "SHABANG", + "wut the dawg doin", + "RAHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH", + "mincraft", + "i used to always say REEEE when im mad... i used to be so damn cringe", + "guys i am NOT a femboy, i am TRANS", + "please stop calling me gay or harrass me because im trans.. please...", + "subway", + "imagine swearing lmao" + ]; + + let screensaver, textElem, idleTimer, messageTimer, typeInterval; + let screensaverActive = false; + let usedMessages = []; + let hue = 0; + let hueInterval; + + function shuffleArray(array) { + const shuffled = [...array]; + for (let i = shuffled.length - 1; i > 0; i--) { + const j = Math.floor(Math.random() * (i + 1)); + [shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]]; + } + return shuffled; + } + + function createScreensaver() { + screensaver = document.createElement("div"); + screensaver.style.cssText = ` + position: fixed; + inset: 0; + display: flex; + align-items: center; + justify-content: center; + background: #0e0e0e; + z-index: 9999; + opacity: 0; + transition: opacity 1.5s ease; + pointer-events: none; + `; + + textElem = document.createElement("div"); + textElem.style.cssText = ` + font-family: monospace; + font-size: 1.4rem; + color: #dcdcdc; + text-align: center; + padding: 2rem; + max-width: 80vw; + opacity: 0.85; + letter-spacing: 0.05em; + line-height: 1.6; + `; + + screensaver.appendChild(textElem); + document.body.appendChild(screensaver); + } + + function typeWriter(text) { + textElem.textContent = ""; + let i = 0; + + clearInterval(typeInterval); + + typeInterval = setInterval(() => { + if (i < text.length) { + textElem.textContent += text.charAt(i); + i++; + } else { + clearInterval(typeInterval); + } + }, 60); + } + + function getNextMessage() { + if (usedMessages.length === 0) { + usedMessages = shuffleArray(messages); + } + return usedMessages.pop(); + } + + function randomMessage() { + const msg = getNextMessage(); + typeWriter(msg); + } + + function startBackgroundAnimation() { + hue = Math.floor(Math.random() * 360); + hueInterval = setInterval(() => { + hue = (hue + 0.5) % 360; + screensaver.style.background = `radial-gradient(circle at center, hsla(${hue}, 50%, 8%, 0.9), #0a0a0a)`; + }, 80); + } + + function stopBackgroundAnimation() { + clearInterval(hueInterval); + screensaver.style.background = "#0e0e0e"; + } + + function showScreensaver() { + if (screensaverActive) return; + screensaverActive = true; + + clearInterval(messageTimer); + clearInterval(typeInterval); + + screensaver.style.pointerEvents = "auto"; + screensaver.style.opacity = 1; + + startBackgroundAnimation(); + randomMessage(); + + messageTimer = setInterval(randomMessage, 6000); + } + + function hideScreensaver() { + if (!screensaverActive) return; + screensaverActive = false; + + screensaver.style.opacity = 0; + screensaver.style.pointerEvents = "none"; + + clearInterval(messageTimer); + clearInterval(typeInterval); + stopBackgroundAnimation(); + + textElem.textContent = ""; + } + + function resetIdleTimer() { + clearTimeout(idleTimer); + idleTimer = setTimeout(showScreensaver, 120000); + + if (screensaverActive) { + hideScreensaver(); + } + } + + createScreensaver(); + + ["mousemove", "keydown", "mousedown", "touchstart", "wheel"].forEach(evt => + document.addEventListener(evt, resetIdleTimer, { passive: true }) + ); + + resetIdleTimer(); +})(); \ No newline at end of file diff --git a/assets/scripts/startanim.js b/assets/scripts/startanim.js new file mode 100644 index 0000000..7b2de1f --- /dev/null +++ b/assets/scripts/startanim.js @@ -0,0 +1,112 @@ +(function () { + var s = document.createElement('script'); + s.src = 'legacy-polyfills.js'; + s.async = false; + document.head.appendChild(s); +})(); + +window.addEventListener('DOMContentLoaded', () => { + const style = document.createElement('style'); + style.textContent = ` + .entry-container { + position: fixed; + inset: 0; + z-index: 9999; + background: #0e0e0e; + display: flex; + justify-content: center; + align-items: center; + overflow: hidden; + opacity: 1; + transition: opacity 1.2s ease; + } + + .tap-text { + position: absolute; + bottom: 8%; + color: #dcdcdc; + font-size: 0.9em; + font-family: monospace; + letter-spacing: 0.08em; + opacity: 0; + animation: fadein 1.5s ease forwards 0.8s, pulse 2s ease-in-out infinite 2.5s; + user-select: none; + text-transform: lowercase; + } + + @keyframes fadein { + from { opacity: 0; transform: translateY(10px); } + to { opacity: 0.6; transform: translateY(0); } + } + + @keyframes pulse { + 0%, 100% { opacity: 0.6; } + 50% { opacity: 0.25; } + } + + .center-line { + position: absolute; + top: 50%; + left: 50%; + width: 1px; + height: 1px; + background: #dcdcdc; + transform: translate(-50%, -50%); + opacity: 0.8; + transition: all 0.8s cubic-bezier(0.4, 0, 0.2, 1); + } + + .expand-horizontal { + width: 100%; + height: 1px; + } + + .expand-vertical { + width: 100%; + height: 100%; + } + + .fade-line { + animation: fade-line 0.6s ease forwards; + } + + @keyframes fade-line { + from { opacity: 0.8; } + to { opacity: 0; } + } + `; + document.head.appendChild(style); + + const container = document.createElement('div'); + container.className = 'entry-container'; + container.innerHTML = ` +
+
click/tap to wake up
+ `; + document.body.appendChild(container); + + const line = container.querySelector('.center-line'); + const text = container.querySelector('.tap-text'); + + function startSequence() { + text.style.display = 'none'; + + line.classList.add('expand-horizontal'); + + setTimeout(() => { + line.classList.add('expand-vertical'); + + setTimeout(() => { + line.classList.add('fade-line'); + + setTimeout(() => { + container.style.opacity = '0'; + setTimeout(() => container.remove(), 1200); + }, 600); + }, 800); + }, 800); + } + + container.addEventListener('click', startSequence, { once: true }); + container.addEventListener('touchstart', startSequence, { once: true }); +}); diff --git a/assets/scripts/utils.ts b/assets/scripts/utils.ts new file mode 100644 index 0000000..ff14e22 --- /dev/null +++ b/assets/scripts/utils.ts @@ -0,0 +1,42 @@ +export function clamp(value: number, min: number, max: number): number { + return Math.min(Math.max(value, min), max); +} + +export function formatNumber(value: number, decimals: number = 2): string { + return value.toFixed(decimals); +} + +export function randomRange(min: number, max: number): number { + return Math.random() * (max - min) + min; +} + +export function randomInt(min: number, max: number): number { + return Math.floor(Math.random() * (max - min + 1)) + min; +} + +export function debounce void>( + fn: T, + delay: number +): (...args: Parameters) => void { + let timer: ReturnType; + + return (...args: Parameters) => { + clearTimeout(timer); + timer = setTimeout(() => fn(...args), delay); + }; +} + +export function throttle void>( + fn: T, + limit: number +): (...args: Parameters) => void { + let inThrottle = false; + + return (...args: Parameters) => { + if (!inThrottle) { + fn(...args); + inThrottle = true; + setTimeout(() => (inThrottle = false), limit); + } + }; +} \ No newline at end of file diff --git a/assets/videos/SUMMER.mp4 b/assets/videos/SUMMER.mp4 new file mode 100644 index 0000000..cb321fe Binary files /dev/null and b/assets/videos/SUMMER.mp4 differ diff --git a/assets/videos/averagecutscene1.mp4 b/assets/videos/averagecutscene1.mp4 new file mode 100644 index 0000000..31186a0 Binary files /dev/null and b/assets/videos/averagecutscene1.mp4 differ diff --git a/assets/videos/cat.mp4 b/assets/videos/cat.mp4 new file mode 100644 index 0000000..bd70609 Binary files /dev/null and b/assets/videos/cat.mp4 differ diff --git a/countdown.html b/countdown.html new file mode 100644 index 0000000..f30fbfd --- /dev/null +++ b/countdown.html @@ -0,0 +1,100 @@ + + + + + + countdown + + + +
+

update 9.1 countdown!!

+

update 9.1 is so far the biggest update of the game! right now we're in beta, but at April 24, 2026, 5:00 PM (MST) (UTC-7), it will be released!

+ +

if you have JUST started playing auth's RNG, i doubt you'll understand all this so uhh ignore it

+ +

delay counter: 3

+ +

counting down to release:

+
+ + ← back to game +
+ + + \ No newline at end of file diff --git a/credits.html b/credits.html index 036b595..76fdb8a 100644 --- a/credits.html +++ b/credits.html @@ -1,49 +1,182 @@ - - - - - - credits - - - -
- Neocities Logo -

credits

-

commentbox.io - forums

-

hosted by neocities

-

songs: "The Second Sanctuary" by Toby Fox

-

w3spaces for giving me backups of the game files, since i went over the bandwidth limit and i didn't want to wait a month, lifesaver

-

catbox - storing music files

-

itch.io for easy advertising whole still reaching out to a large community

-

special thanks to sols RNG for inspiration

-

MIT licensed from version 5.8

-

made by auth and thanks for playing <3

-

issues? skunkolee@gmail.com

- - - -
- - + + + + + + credits + + + +
+

credits

+

hosted by neocities!

+

songs:

+
    +
  • Toby Fox - WELCOME TO THE CITY
  • +
  • leon chang - wavelocity
  • +
  • Squee - Summer
  • +
  • and Beethoven and Chopin for the fire songs made in 1830 🔥🔥🔥
  • +
+

all music is property of their respective owners. no copyright infringement intended. slow down lawyers

+

+ catbox and file garden for storing files (music/video) +

+

+ fow (octo) for the blog system +

+

+ special thanks to sols RNG for inspiration. horrible game but, its the + inspiration that counts. right? heh. heh. heh... right? man im so bad at pulling off jokes +

+

+ open source imports: +

+

+

contributors:

+
+ + + + +
+

+ also special thanks to the community! i love you all mwah mwah +

+

MIT licensed from version 5.8

+

FULLY open source on github!

+

made by auth and thanks for playing <3

+

issues? email: auth24d(at)proton(dot)me - OR JOIN THE nerimity server AS AN ALTERNATIVE OPTION!

+ + ← back to game +
+ + \ No newline at end of file diff --git a/development/data.html b/development/data.html new file mode 100644 index 0000000..d024e6c --- /dev/null +++ b/development/data.html @@ -0,0 +1,113 @@ + + + + + + + +Index of auths-RNG + + + + + + +

Index of auth1ery/auths-RNG

+ + + + + + + + + + +
NameTypePath
+ + + + + \ No newline at end of file diff --git a/development/rssgenerate.html b/development/rssgenerate.html new file mode 100644 index 0000000..89c1529 --- /dev/null +++ b/development/rssgenerate.html @@ -0,0 +1,198 @@ + + + + + +RSS Generator + + + +
+

RSS Feed Generator

+

FOR BLOG USE ONLY!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

+ + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + \ No newline at end of file diff --git a/documentation/CLAUDE.md b/documentation/CLAUDE.md new file mode 100644 index 0000000..00c67a9 --- /dev/null +++ b/documentation/CLAUDE.md @@ -0,0 +1,26 @@ +# Claude and How To Use It + +This file covers how to use tools like: + +- Claude +- Claude Code +and more Claude related AI coding tools. + +We are anti-AI but if you have to use Claude Code and other AI programming tools, you may as well do so under these strict rules, under the following: + +1. Comment the start and the end of the generated code. +It is vital to seperate and show that you have used Claude when making your changes. + +2. Make sure you follow the styling guidelines. +Under STYLES.md is where you can find guidelines and tips on how to make styling consistent, while also having important elements be the star of the show. + +3. Do not lie about using AI. +Lying or some sort of deception to have people think you aren't using AI when you are not will be found out in some way, shape, or form. Please be rightfully honest if you have used Claude or not, otherwise you could get into some trouble later on. + +4. Don't justify it. +AI sucks, we all hate AI, and we're fine if you use AI only if you follow these guidelines in this file. If you try and manipulate people that "AI will be the future of coding" and "You should use AI for coding and contributing" then not only you are wrong, but also we also care about human-made code, and personality. + +5. Only use AI for new things. +We don't want an existing system in place to be replaced with a "refined" AI-generated version. Only add small to medium sized, non-gamechanging features and elements to the game. Never make a big or substantial, new system or feature, no exceptions. + +This project was coded with AI before, but it is becoming more human and more man-made. We want to stay human, but if you want to use AI nonetheless, you can, but you're limited. diff --git a/documentation/CODE_OF_CONDUCT.md b/documentation/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..d2e6147 --- /dev/null +++ b/documentation/CODE_OF_CONDUCT.md @@ -0,0 +1,73 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to making participation in our project and +our community a harassment-free experience for everyone, regardless of age, body +size, disability, ethnicity, gender identity and expression, level of experience, +education, socio-economic status, nationality, personal appearance, race, +religion, or sexual identity and orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment +include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or + advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic + address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or +reject comments, commits, code, wiki edits, issues, and other contributions +that are not aligned to this Code of Conduct, or to ban temporarily or +permanently any contributor for other behaviors that they deem inappropriate, +threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces +when an individual is representing the project or its community. Examples of +representing a project or community include using an official project e-mail +address, posting via an official social media account, or acting as an appointed +representative at an online or offline event. Representation of a project may be +further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by contacting the project team at skunkolee@gmail.com - All +complaints will be reviewed and investigated and will result in a response that +is deemed necessary and appropriate to the circumstances. The project team is +obligated to maintain confidentiality with regard to the reporter of an incident. +Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good +faith may face temporary or permanent repercussions as determined by other +members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, +available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html + +[homepage]: https://www.contributor-covenant.org diff --git a/documentation/CONTRIBUTING.md b/documentation/CONTRIBUTING.md new file mode 100644 index 0000000..cd91a78 --- /dev/null +++ b/documentation/CONTRIBUTING.md @@ -0,0 +1,60 @@ +# contributing to auth's RNG + +thank you for your interest in contributing to **auth's RNG**! please follow these guidelines to ensure smooth collaboration!!!!!!!!!!!!!!! + +## how to contribute + +1. **fork the repository** + click the "fork" button on the top-right of the repo page to create your own copy. + +2. **clone your fork** + once you've forked the repo, clone it to your local machine: + `git clone https://github.com/your-username/auths-RNG.git` + +3. **create a branch** + create a new branch for your work: + `git checkout -b feature/your-feature-name` + +4. **make changes** + work on the feature, fix, or improvement you want to contribute. + +5. **commit your changes** + commit your changes with a clear and concise message: + `git commit -m "add feature: description of the change"` + +6. **push to your fork** + push your changes to your forked repo: + `git push origin feature/your-feature-name` + +7. **create a pull request** + open a pull request (PR) from your fork's branch to the main repository. describe the changes you've made and why! + +## pull request guidelines + +- **describe your changes** clearly. provide context on what was changed and why. +- **test your changes** before submitting a PR. ensure that the game still works and that your code does not introduce bugs. +- **keep commits focused** on one task or feature. if necessary, break down large changes into smaller, manageable pieces. +- **maintain coding style**. follow existing coding conventions, such as indentation, variable naming, and comments. +- **avoid modifying unrelated files**. only modify files related to the feature or bug fix you're working on. +- **keep it simple**. don't overcomplicate your changes. aim for clean, understandable code. +- and the most important.. **NEVER TOUCH THE .github/workflows FOLDERS AND FILES!** please... im begging you... + +## issue tracker + +if you find a bug or have a feature request, feel free to open an issue. please be clear about the problem you're encountering or the feature you'd like to see added. + +## code of conduct + +refer to CODE_OF_CONDUCT.md + +## license + +by contributing to this project, you agree that your contributions will be licensed under the same license as the project. see the LICENSE file for more details. + +## AI + +refer to CLAUDE.md + +## styling + +refer to STYLING.md diff --git a/documentation/STYLES.md b/documentation/STYLES.md new file mode 100644 index 0000000..7e8d7a2 --- /dev/null +++ b/documentation/STYLES.md @@ -0,0 +1,30 @@ +# Styling Guidelines and Tips + +This file helps you to make your new contributions not only function good, but also importantly: look and feel good as well. + +--- + +Keep the styles of your buttons and stuff as-is: + +- Make it dark. +- Make it almost minimalist. +- Make it clean, but not too clean to keep it feeling niche. +- Use lowercase text. +- Use emojis sparely. +- Don't make it too cluttered. +- Don't make it too small or too big. +- Z-index the absolute hell out of it if needed. +- Make sure it looks good on all different types of user themes and more. + +You want to take inspiration from many other elements in auth's RNG. You can't make something for something without trying or using that something, right? + +Do not make it stand out too, too much. Examples of this are brightly white elements, and aggressively rainbow buttons and popups. + +Learn color theory when using multiple colors, or at least learn the basics of it. It will help increase your chances of getting your PR merged. + +To see a basic example of the styling, check out the FAQ page on the site, or credits page. + +--- + +Overall, keep it dark, minimalist, but just slightly messy enough to the point it doesn't feel like over-polished, corporation slop, and instead it's own thing. + diff --git a/favicon.ico b/favicon.ico new file mode 100644 index 0000000..8fd8af5 Binary files /dev/null and b/favicon.ico differ diff --git a/functions/api/verify.js b/functions/api/verify.js new file mode 100644 index 0000000..1d27f55 --- /dev/null +++ b/functions/api/verify.js @@ -0,0 +1,30 @@ +export async function onRequestPost({ request, env }) { + const { token } = await request.json(); + + if (!token) { + return Response.json({ success: false, error: "no token" }, { status: 400 }); + } + + const formData = new FormData(); + formData.append("secret", env.TURNSTILE_SECRET); + formData.append("response", token); + + const result = await fetch( + "https://challenges.cloudflare.com/turnstile/v0/siteverify", + { + method: "POST", + body: formData, + } + ); + + const data = await result.json(); + + if (!data.success) { + return Response.json( + { success: false, error: "invalid captcha" }, + { status: 403 } + ); + } + + return Response.json({ success: true }); +} diff --git a/index.html b/index.html index d7dfc29..4f05f8a 100644 --- a/index.html +++ b/index.html @@ -1,1663 +1,2618 @@ - - - - - - - - - - - - auth's RNG - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - auth's RNG - - - -

welcome to auth's RNG!

- - -
-

- forums - -

-

- guestbook -

-

- my biolink -

-
- -

version 9.0 • cosmetic changes

-update 9.1 countdown! - -

go collect em all!!!

- -

- -
-
-
-

total rolls: 0

-
    -
    - -
    - - - - - - - -

    - this game is for entertainment purposes only. no real money or prizes are involved. - it does not constitute gambling and is intended for users 13 years and older.
    - this project going from Version 5.8 is MIT Licensed. license: - - https://authsrng.neocities.org/LICENSE.txt - - -

    - - - - - - - - - - - - - - - - -
    Life Wasted In Total: 0s
    - - -

    - - credits

    - - - - - - - - - - - - -
    - - - - - - - -
    -
    -
    -
    -
    - - - - - - - - - -

    ----------------------------------------

    - - - - - - - - - - - - - - - - - - - + + + + + + + + + auth's RNG + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +UNSTABLE BUILD! this build is for testers and the developer of the game. do NOT trust your save data with this version. click me to dismiss. +
    + + + +
    + + + + +
    + + + + + + + + + + + + +
    +
    + + + + + +

    welcome to auth's RNG!

    + +

    psst user, yeah you, this is version 9.1 pre-release, don't expect really epic things right now its in + progress

    + +

    + + + +
    +

    ----

    +
    + + UPDATE 9.1 COUNTDOWN!!! + blog + +
    +
    + +
    + + +
    +
    +
    + +

    total rolls: 0

    + +
    +

    luck multiplier: 1.0x

    +

    +
    + +
    +
    checking daily…
    + +
    + +
    +
    checking weekly…
    + +
    + +
    + +
    +
    + ................. +
    + sort + +
    +
    + +
    +
    +
    0/0 collected
    + +
    +
      +
      + +
      + anomalies: 0 +
      + + +
      +
      +
      + +
      + +
      + +
      +

      shop

      + +
      + points: 0 +
      + +
      +
      +
      luck boost
      +
      level: 0/100
      +
      +
      get 1.1x more luck on chances (multiplicative)
      + +
      + +
      +
      +
      roll speed
      +
      level: 0/3
      +
      +
      decrease roll time by 0.2s (min 0.25s)
      + +
      + +
      +
      +
      point multiplier
      +
      level: 0/10
      +
      +
      decrease point divisor by 0.2 (more points per sell)
      + +
      + +
      +
      +
      rarity magnet
      +
      level: 0/5
      +
      +
      1.1x boost to uncollected rarities per level
      + +
      + +
      +
      +
      point printer
      +
      level: 0
      +
      +
      passive points generation (1pt/sec per level)
      + +
      + +
      +
      +
      duplicate chance
      +
      level: 0/10
      +
      +
      1% chance per level to get +1 extra rarity on roll
      + +
      + + +
      +
      🧪 potions
      + +
      + +
      +
      +
      2x luck
      +
      30 seconds
      +
      2,000 pts
      +
      owned: 0
      + + +
      + + +
      +
      💫
      +
      4x luck
      +
      30 seconds
      +
      5,000 pts
      +
      owned: 0
      + + +
      + + +
      +
      🌟
      +
      10x luck
      +
      30 seconds
      +
      15,000 pts
      +
      owned: 0
      + + +
      + + +
      +
      +
      50x luck
      +
      30 seconds
      +
      30,000 pts
      +
      owned: 0
      + + +
      + + +
      +
      🔥
      +
      100x luck
      +
      30 seconds
      +
      45,000 pts
      +
      owned: 0
      + + +
      + + +
      +
      💥
      +
      150x luck
      +
      30 seconds
      +
      60,000 pts
      +
      owned: 0
      + + +
      + + +
      +
      +
      250x luck
      +
      30 seconds
      +
      80,000 pts
      +
      owned: 0
      + + +
      + + +
      +
      💎
      +
      OMEGA LUCK
      +
      30 seconds
      +
      150,000 pts
      +
      owned: 0
      + + +
      + + +
      +
      🎭
      +
      duplicate
      +
      next 10 rolls x2
      +
      5,000 pts
      +
      owned: 0
      + + +
      +
      +
      + +
      + +
      + +
      + +
      +

      gauntlets

      +

      collect all rarities in a tier to claim a reward!

      +
      your rolls: 0
      +
      +
      + +
      +

      wishing well

      + +
      +
      ancient wishing well
      +
      throw points in and make a wish...
      + +
      + 🌊 +
      + +
      + + +
      + + + + +
      + + +
      + +
      ready to accept your offering!!
      +
      + +
      +
      total thrown: 0 points
      +
      total received: 0 points
      +
      times thrown: 0
      +
      success rate: 0%
      +
      +
      +
      + +
      +

      settings

      + +
      + + + + + + + + + + + +
      + + + + + + + +
      + + + + be careful, eruda gives you access to the whole client-side of auth's RNG! you could easily corrupt or delete your save file accidentally if you don't know what you're doing! + +
      + + + + +
      + + + + + + + max 10MB - will be saved locally + + + +
      + + + + + + + + + + +
      +
      settings transfer
      +
      +
      + + + + + +
      +
      + +
      +
      save data transfer
      +
      +
      + + + + + +
      +
      +
      +
      + +
      +

      links and more

      + + + + + +
      + +
      + + + +
      total playtime: 0s
      + +

      + + credits + + lite version +FAQ + + +

      + +

      join us on...

      + + +
      +
      + +

      even more links...

      + + +

      + + Flag Counter + + + +
      +
      +
      +
      + +

      feed my pet while you're at it:

      + It's tamaNOTchi! Click to feed! + +
      + +
      +
      + +
      +
      +
      +
      +
      +
      +
      + +
      + + + + +
      swipe to navigate!!!
      + + + +
      + +
      + +
      +
      +
      +
      +
      + +
      + +
      +
      60
      +
      4x luck
      +
      + +
      + + + +
      + +
      +
      +
      +

      rarity index

      + +
      +
      0/0 collected
      + +
      +
      +
      + +
      +
      wee
      +
      the well grants your wish!
      +
      +100 points
      + +
      + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/legacy-polyfills.js b/legacy-polyfills.js new file mode 100644 index 0000000..1680952 --- /dev/null +++ b/legacy-polyfills.js @@ -0,0 +1,2060 @@ +/** + * Advanced Polyfills Library + * Comprehensive ES5/ES6+ polyfills for legacy browser support + * @version 2.0.0 + * @license MIT + */ + +(function (global, document) { + 'use strict'; + + var supportsDefineProperty = (function () { + try { + Object.defineProperty({}, 'x', {}); + return true; + } catch (e) { + return false; + } + })(); + + var defineProperty = supportsDefineProperty + ? Object.defineProperty + : function (obj, prop, descriptor) { + if (descriptor.get || descriptor.set) { + throw new TypeError( + 'Getters & setters not supported on this browser', + ); + } + if (descriptor.hasOwnProperty('value')) { + obj[prop] = descriptor.value; + } + return obj; + }; + + // ==================== Array Polyfills ==================== + + if (!Array.prototype.forEach) { + defineProperty(Array.prototype, 'forEach', { + value: function (callback, thisArg) { + if (this == null) { + throw new TypeError( + 'Array.prototype.forEach called on null or undefined', + ); + } + if (typeof callback !== 'function') { + throw new TypeError(callback + ' is not a function'); + } + var O = Object(this); + var len = O.length >>> 0; + var k = 0; + + while (k < len) { + if (k in O) { + callback.call(thisArg, O[k], k, O); + } + k++; + } + }, + writable: true, + configurable: true, + }); + } + + if (!Array.prototype.map) { + defineProperty(Array.prototype, 'map', { + value: function (callback, thisArg) { + if (this == null) { + throw new TypeError( + 'Array.prototype.map called on null or undefined', + ); + } + if (typeof callback !== 'function') { + throw new TypeError(callback + ' is not a function'); + } + var O = Object(this); + var len = O.length >>> 0; + var A = new Array(len); + var k = 0; + + while (k < len) { + if (k in O) { + A[k] = callback.call(thisArg, O[k], k, O); + } + k++; + } + return A; + }, + writable: true, + configurable: true, + }); + } + + if (!Array.prototype.filter) { + defineProperty(Array.prototype, 'filter', { + value: function (callback, thisArg) { + if (this == null) { + throw new TypeError( + 'Array.prototype.filter called on null or undefined', + ); + } + if (typeof callback !== 'function') { + throw new TypeError(callback + ' is not a function'); + } + var O = Object(this); + var len = O.length >>> 0; + var res = []; + var k = 0; + + while (k < len) { + if (k in O) { + var kValue = O[k]; + if (callback.call(thisArg, kValue, k, O)) { + res.push(kValue); + } + } + k++; + } + return res; + }, + writable: true, + configurable: true, + }); + } + + if (!Array.prototype.reduce) { + defineProperty(Array.prototype, 'reduce', { + value: function (callback, initialValue) { + if (this == null) { + throw new TypeError( + 'Array.prototype.reduce called on null or undefined', + ); + } + if (typeof callback !== 'function') { + throw new TypeError(callback + ' is not a function'); + } + var O = Object(this); + var len = O.length >>> 0; + var k = 0; + var accumulator; + + if (arguments.length >= 2) { + accumulator = initialValue; + } else { + var kPresent = false; + while (!kPresent && k < len) { + kPresent = k in O; + if (kPresent) { + accumulator = O[k]; + } + k++; + } + if (!kPresent) { + throw new TypeError('Reduce of empty array with no initial value'); + } + } + + while (k < len) { + if (k in O) { + accumulator = callback(accumulator, O[k], k, O); + } + k++; + } + return accumulator; + }, + writable: true, + configurable: true, + }); + } + + if (!Array.prototype.reduceRight) { + defineProperty(Array.prototype, 'reduceRight', { + value: function (callback, initialValue) { + if (this == null) { + throw new TypeError( + 'Array.prototype.reduceRight called on null or undefined', + ); + } + if (typeof callback !== 'function') { + throw new TypeError(callback + ' is not a function'); + } + var O = Object(this); + var len = O.length >>> 0; + var k = len - 1; + var accumulator; + + if (arguments.length >= 2) { + accumulator = initialValue; + } else { + var kPresent = false; + while (!kPresent && k >= 0) { + kPresent = k in O; + if (kPresent) { + accumulator = O[k]; + } + k--; + } + if (!kPresent) { + throw new TypeError('Reduce of empty array with no initial value'); + } + } + + while (k >= 0) { + if (k in O) { + accumulator = callback(accumulator, O[k], k, O); + } + k--; + } + return accumulator; + }, + writable: true, + configurable: true, + }); + } + + if (!Array.prototype.indexOf) { + defineProperty(Array.prototype, 'indexOf', { + value: function (searchElement, fromIndex) { + if (this == null) { + throw new TypeError( + 'Array.prototype.indexOf called on null or undefined', + ); + } + var O = Object(this); + var len = O.length >>> 0; + if (len === 0) return -1; + + var n = fromIndex | 0; + if (n >= len) return -1; + + var k = Math.max(n >= 0 ? n : len - Math.abs(n), 0); + while (k < len) { + if (k in O && O[k] === searchElement) { + return k; + } + k++; + } + return -1; + }, + writable: true, + configurable: true, + }); + } + + if (!Array.prototype.lastIndexOf) { + defineProperty(Array.prototype, 'lastIndexOf', { + value: function (searchElement, fromIndex) { + if (this == null) { + throw new TypeError( + 'Array.prototype.lastIndexOf called on null or undefined', + ); + } + var O = Object(this); + var len = O.length >>> 0; + if (len === 0) return -1; + + var n = len - 1; + if (arguments.length > 1) { + n = fromIndex | 0; + n = n >= 0 ? Math.min(n, len - 1) : len - Math.abs(n); + } + + for (var k = n; k >= 0; k--) { + if (k in O && O[k] === searchElement) { + return k; + } + } + return -1; + }, + writable: true, + configurable: true, + }); + } + + if (!Array.prototype.every) { + defineProperty(Array.prototype, 'every', { + value: function (callback, thisArg) { + if (this == null) { + throw new TypeError( + 'Array.prototype.every called on null or undefined', + ); + } + if (typeof callback !== 'function') { + throw new TypeError(callback + ' is not a function'); + } + var O = Object(this); + var len = O.length >>> 0; + var k = 0; + + while (k < len) { + if (k in O && !callback.call(thisArg, O[k], k, O)) { + return false; + } + k++; + } + return true; + }, + writable: true, + configurable: true, + }); + } + + if (!Array.prototype.some) { + defineProperty(Array.prototype, 'some', { + value: function (callback, thisArg) { + if (this == null) { + throw new TypeError( + 'Array.prototype.some called on null or undefined', + ); + } + if (typeof callback !== 'function') { + throw new TypeError(callback + ' is not a function'); + } + var O = Object(this); + var len = O.length >>> 0; + var k = 0; + + while (k < len) { + if (k in O && callback.call(thisArg, O[k], k, O)) { + return true; + } + k++; + } + return false; + }, + writable: true, + configurable: true, + }); + } + + if (!Array.prototype.find) { + defineProperty(Array.prototype, 'find', { + value: function (predicate, thisArg) { + if (this == null) { + throw new TypeError( + 'Array.prototype.find called on null or undefined', + ); + } + if (typeof predicate !== 'function') { + throw new TypeError(predicate + ' is not a function'); + } + var O = Object(this); + var len = O.length >>> 0; + + for (var i = 0; i < len; i++) { + var value = O[i]; + if (predicate.call(thisArg, value, i, O)) { + return value; + } + } + return undefined; + }, + writable: true, + configurable: true, + }); + } + + if (!Array.prototype.findIndex) { + defineProperty(Array.prototype, 'findIndex', { + value: function (predicate, thisArg) { + if (this == null) { + throw new TypeError( + 'Array.prototype.findIndex called on null or undefined', + ); + } + if (typeof predicate !== 'function') { + throw new TypeError(predicate + ' is not a function'); + } + var O = Object(this); + var len = O.length >>> 0; + + for (var i = 0; i < len; i++) { + if (predicate.call(thisArg, O[i], i, O)) { + return i; + } + } + return -1; + }, + writable: true, + configurable: true, + }); + } + + if (!Array.prototype.fill) { + defineProperty(Array.prototype, 'fill', { + value: function (value, start, end) { + if (this == null) { + throw new TypeError( + 'Array.prototype.fill called on null or undefined', + ); + } + var O = Object(this); + var len = O.length >>> 0; + var relativeStart = start >> 0; + var k = + relativeStart < 0 + ? Math.max(len + relativeStart, 0) + : Math.min(relativeStart, len); + var relativeEnd = end === undefined ? len : end >> 0; + var final = + relativeEnd < 0 + ? Math.max(len + relativeEnd, 0) + : Math.min(relativeEnd, len); + + while (k < final) { + O[k] = value; + k++; + } + return O; + }, + writable: true, + configurable: true, + }); + } + + if (!Array.prototype.includes) { + defineProperty(Array.prototype, 'includes', { + value: function (searchElement, fromIndex) { + if (this == null) { + throw new TypeError( + 'Array.prototype.includes called on null or undefined', + ); + } + var O = Object(this); + var len = O.length >>> 0; + if (len === 0) return false; + + var n = fromIndex | 0; + var k = Math.max(n >= 0 ? n : len - Math.abs(n), 0); + + function sameValueZero(x, y) { + return ( + x === y || + (typeof x === 'number' && + typeof y === 'number' && + isNaN(x) && + isNaN(y)) + ); + } + + while (k < len) { + if (sameValueZero(O[k], searchElement)) { + return true; + } + k++; + } + return false; + }, + writable: true, + configurable: true, + }); + } + + if (!Array.prototype.flat) { + defineProperty(Array.prototype, 'flat', { + value: function (depth) { + var d = depth === undefined ? 1 : Math.floor(depth); + if (d < 1) return Array.prototype.slice.call(this); + + var flattenDeep = function (arr, d) { + return d > 0 + ? arr.reduce(function (acc, val) { + return acc.concat( + Array.isArray(val) ? flattenDeep(val, d - 1) : val, + ); + }, []) + : arr.slice(); + }; + + return flattenDeep(this, d); + }, + writable: true, + configurable: true, + }); + } + + if (!Array.prototype.flatMap) { + defineProperty(Array.prototype, 'flatMap', { + value: function (callback, thisArg) { + return this.map(callback, thisArg).flat(1); + }, + writable: true, + configurable: true, + }); + } + + if (!Array.isArray) { + Array.isArray = function (arg) { + return Object.prototype.toString.call(arg) === '[object Array]'; + }; + } + + if (!Array.from) { + Array.from = function (arrayLike, mapFn, thisArg) { + if (arrayLike == null) { + throw new TypeError('Array.from requires an array-like object'); + } + + var items = Object(arrayLike); + var len = items.length >>> 0; + var A = typeof this === 'function' ? new this(len) : new Array(len); + var k = 0; + + while (k < len) { + var kValue = items[k]; + if (mapFn) { + A[k] = + typeof thisArg !== 'undefined' + ? mapFn.call(thisArg, kValue, k) + : mapFn(kValue, k); + } else { + A[k] = kValue; + } + k++; + } + A.length = len; + return A; + }; + } + + if (!Array.of) { + Array.of = function () { + return Array.prototype.slice.call(arguments); + }; + } + + // ==================== Object Polyfills ==================== + + if (!Object.keys) { + Object.keys = function (obj) { + if (obj !== Object(obj)) { + throw new TypeError('Object.keys called on non-object'); + } + var keys = []; + for (var prop in obj) { + if (Object.prototype.hasOwnProperty.call(obj, prop)) { + keys.push(prop); + } + } + return keys; + }; + } + + if (!Object.values) { + Object.values = function (obj) { + if (obj !== Object(obj)) { + throw new TypeError('Object.values called on non-object'); + } + var values = []; + for (var prop in obj) { + if (Object.prototype.hasOwnProperty.call(obj, prop)) { + values.push(obj[prop]); + } + } + return values; + }; + } + + if (!Object.entries) { + Object.entries = function (obj) { + if (obj !== Object(obj)) { + throw new TypeError('Object.entries called on non-object'); + } + var entries = []; + for (var prop in obj) { + if (Object.prototype.hasOwnProperty.call(obj, prop)) { + entries.push([prop, obj[prop]]); + } + } + return entries; + }; + } + + if (!Object.create) { + Object.create = function (proto, propertiesObject) { + if (typeof proto !== 'object' && typeof proto !== 'function') { + throw new TypeError( + 'Object prototype may only be an Object or null: ' + proto, + ); + } + if (proto === null) { + throw new Error('null [[Prototype]] not supported'); + } + + function F() {} + F.prototype = proto; + var obj = new F(); + + if (propertiesObject !== undefined) { + Object.defineProperties(obj, propertiesObject); + } + + return obj; + }; + } + + if (!Object.assign) { + Object.assign = function (target) { + if (target == null) { + throw new TypeError('Cannot convert undefined or null to object'); + } + + var to = Object(target); + for (var index = 1; index < arguments.length; index++) { + var nextSource = arguments[index]; + if (nextSource != null) { + for (var nextKey in nextSource) { + if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) { + to[nextKey] = nextSource[nextKey]; + } + } + } + } + return to; + }; + } + + if (!Object.freeze) { + Object.freeze = function (obj) { + return obj; + }; + } + + if (!Object.seal) { + Object.seal = function (obj) { + return obj; + }; + } + + if (!Object.preventExtensions) { + Object.preventExtensions = function (obj) { + return obj; + }; + } + + if (!Object.isFrozen) { + Object.isFrozen = function () { + return false; + }; + } + + if (!Object.isSealed) { + Object.isSealed = function () { + return false; + }; + } + + if (!Object.isExtensible) { + Object.isExtensible = function () { + return true; + }; + } + + if (!Object.getOwnPropertyNames) { + Object.getOwnPropertyNames = function (obj) { + return Object.keys(obj); + }; + } + + if (!Object.setPrototypeOf) { + Object.setPrototypeOf = function (obj, proto) { + obj.__proto__ = proto; + return obj; + }; + } + + if (!Object.getPrototypeOf) { + Object.getPrototypeOf = function (obj) { + if (obj !== Object(obj)) { + throw new TypeError('Object.getPrototypeOf called on non-object'); + } + return obj.__proto__ || obj.constructor.prototype; + }; + } + + // ==================== String Polyfills ==================== + + if (!String.prototype.trim) { + defineProperty(String.prototype, 'trim', { + value: function () { + return this.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, ''); + }, + writable: true, + configurable: true, + }); + } + + if (!String.prototype.trimStart && !String.prototype.trimLeft) { + var trimStart = function () { + return this.replace(/^[\s\uFEFF\xA0]+/, ''); + }; + defineProperty(String.prototype, 'trimStart', { + value: trimStart, + writable: true, + configurable: true, + }); + defineProperty(String.prototype, 'trimLeft', { + value: trimStart, + writable: true, + configurable: true, + }); + } + + if (!String.prototype.trimEnd && !String.prototype.trimRight) { + var trimEnd = function () { + return this.replace(/[\s\uFEFF\xA0]+$/, ''); + }; + defineProperty(String.prototype, 'trimEnd', { + value: trimEnd, + writable: true, + configurable: true, + }); + defineProperty(String.prototype, 'trimRight', { + value: trimEnd, + writable: true, + configurable: true, + }); + } + + if (!String.prototype.repeat) { + defineProperty(String.prototype, 'repeat', { + value: function (count) { + if (this == null) { + throw new TypeError( + 'String.prototype.repeat called on null or undefined', + ); + } + var str = String(this); + count = Number(count) || 0; + + if (count < 0 || count === Infinity) { + throw new RangeError('Invalid count value'); + } + + count = Math.floor(count); + if (str.length === 0 || count === 0) { + return ''; + } + + var result = ''; + while (count > 0) { + if (count & 1) { + result += str; + } + count >>>= 1; + if (count > 0) { + str += str; + } + } + return result; + }, + writable: true, + configurable: true, + }); + } + + if (!String.prototype.startsWith) { + defineProperty(String.prototype, 'startsWith', { + value: function (searchString, position) { + position = position || 0; + return this.substr(position, searchString.length) === searchString; + }, + writable: true, + configurable: true, + }); + } + + if (!String.prototype.endsWith) { + defineProperty(String.prototype, 'endsWith', { + value: function (searchString, length) { + if (length === undefined || length > this.length) { + length = this.length; + } + return ( + this.substring(length - searchString.length, length) === searchString + ); + }, + writable: true, + configurable: true, + }); + } + + if (!String.prototype.includes) { + defineProperty(String.prototype, 'includes', { + value: function (search, start) { + if (typeof start !== 'number') { + start = 0; + } + if (start + search.length > this.length) { + return false; + } + return this.indexOf(search, start) !== -1; + }, + writable: true, + configurable: true, + }); + } + + if (!String.prototype.padStart) { + defineProperty(String.prototype, 'padStart', { + value: function (targetLength, padString) { + targetLength = targetLength >> 0; + padString = String(typeof padString !== 'undefined' ? padString : ' '); + if (this.length >= targetLength) { + return String(this); + } + targetLength = targetLength - this.length; + if (targetLength > padString.length) { + padString += padString.repeat(targetLength / padString.length); + } + return padString.slice(0, targetLength) + String(this); + }, + writable: true, + configurable: true, + }); + } + + if (!String.prototype.padEnd) { + defineProperty(String.prototype, 'padEnd', { + value: function (targetLength, padString) { + targetLength = targetLength >> 0; + padString = String(typeof padString !== 'undefined' ? padString : ' '); + if (this.length >= targetLength) { + return String(this); + } + targetLength = targetLength - this.length; + if (targetLength > padString.length) { + padString += padString.repeat(targetLength / padString.length); + } + return String(this) + padString.slice(0, targetLength); + }, + writable: true, + configurable: true, + }); + } + + // ==================== Function Polyfills ==================== + + if (!Function.prototype.bind) { + defineProperty(Function.prototype, 'bind', { + value: function (oThis) { + if (typeof this !== 'function') { + throw new TypeError( + 'Function.prototype.bind - what is trying to be bound is not callable', + ); + } + + var aArgs = Array.prototype.slice.call(arguments, 1); + var fToBind = this; + var fNOP = function () {}; + var fBound = function () { + return fToBind.apply( + this instanceof fNOP ? this : oThis, + aArgs.concat(Array.prototype.slice.call(arguments)), + ); + }; + + if (this.prototype) { + fNOP.prototype = this.prototype; + } + fBound.prototype = new fNOP(); + + return fBound; + }, + writable: true, + configurable: true, + }); + } + + // ==================== Number Polyfills ==================== + + if (!Number.isNaN) { + Number.isNaN = function (value) { + return typeof value === 'number' && isNaN(value); + }; + } + + if (!Number.isFinite) { + Number.isFinite = function (value) { + return typeof value === 'number' && isFinite(value); + }; + } + + if (!Number.isInteger) { + Number.isInteger = function (value) { + return ( + typeof value === 'number' && + isFinite(value) && + Math.floor(value) === value + ); + }; + } + + if (!Number.isSafeInteger) { + Number.isSafeInteger = function (value) { + return ( + Number.isInteger(value) && Math.abs(value) <= Number.MAX_SAFE_INTEGER + ); + }; + } + + if (!Number.parseFloat) { + Number.parseFloat = parseFloat; + } + + if (!Number.parseInt) { + Number.parseInt = parseInt; + } + + if (typeof Number.EPSILON === 'undefined') { + Number.EPSILON = Math.pow(2, -52); + } + + if (typeof Number.MAX_SAFE_INTEGER === 'undefined') { + Number.MAX_SAFE_INTEGER = Math.pow(2, 53) - 1; + } + + if (typeof Number.MIN_SAFE_INTEGER === 'undefined') { + Number.MIN_SAFE_INTEGER = -(Math.pow(2, 53) - 1); + } + + // ==================== Math Polyfills ==================== + + if (!Math.trunc) { + Math.trunc = function (v) { + v = +v; + return v - (v % 1) || (!isFinite(v) || v === 0 ? v : v < 0 ? -0 : 0); + }; + } + + if (!Math.sign) { + Math.sign = function (x) { + x = +x; + if (x === 0 || isNaN(x)) { + return x; + } + return x > 0 ? 1 : -1; + }; + } + + if (!Math.cbrt) { + Math.cbrt = function (x) { + var y = Math.pow(Math.abs(x), 1 / 3); + return x < 0 ? -y : y; + }; + } + + if (!Math.log10) { + Math.log10 = function (x) { + return Math.log(x) * Math.LOG10E; + }; + } + + if (!Math.log2) { + Math.log2 = function (x) { + return Math.log(x) * Math.LOG2E; + }; + } + + if (!Math.log1p) { + Math.log1p = function (x) { + x = Number(x); + if (x < -1 || x !== x) return NaN; + if (x === 0 || x === Infinity) return x; + var nearX = x + 1 - 1; + return nearX === 0 ? x : x * (Math.log(x + 1) / nearX); + }; + } + + if (!Math.expm1) { + Math.expm1 = function (x) { + x = Number(x); + if (x === 0 || x !== x || x === Infinity) return x; + return Math.exp(x) - 1; + }; + } + + if (!Math.sinh) { + Math.sinh = function (x) { + var y = Math.exp(x); + return (y - 1 / y) / 2; + }; + } + + if (!Math.cosh) { + Math.cosh = function (x) { + var y = Math.exp(x); + return (y + 1 / y) / 2; + }; + } + + if (!Math.tanh) { + Math.tanh = function (x) { + if (x === Infinity) return 1; + if (x === -Infinity) return -1; + var y = Math.exp(2 * x); + return (y - 1) / (y + 1); + }; + } + + if (!Math.asinh) { + Math.asinh = function (x) { + if (x === -Infinity) return x; + return Math.log(x + Math.sqrt(x * x + 1)); + }; + } + + if (!Math.acosh) { + Math.acosh = function (x) { + return Math.log(x + Math.sqrt(x * x - 1)); + }; + } + + if (!Math.atanh) { + Math.atanh = function (x) { + return Math.log((1 + x) / (1 - x)) / 2; + }; + } + + if (!Math.hypot) { + Math.hypot = function () { + var y = 0; + var length = arguments.length; + for (var i = 0; i < length; i++) { + if (arguments[i] === Infinity || arguments[i] === -Infinity) { + return Infinity; + } + y += arguments[i] * arguments[i]; + } + return Math.sqrt(y); + }; + } + + if (!Math.clz32) { + Math.clz32 = function (x) { + x = Number(x) >>> 0; + return x ? 32 - x.toString(2).length : 32; + }; + } + + if (!Math.imul) { + Math.imul = function (a, b) { + var ah = (a >>> 16) & 0xffff; + var al = a & 0xffff; + var bh = (b >>> 16) & 0xffff; + var bl = b & 0xffff; + return (al * bl + (((ah * bl + al * bh) << 16) >>> 0)) | 0; + }; + } + + if (!Math.fround) { + Math.fround = function (x) { + return new Float32Array([x])[0]; + }; + } + + // ==================== Date Polyfills ==================== + + if (!Date.now) { + Date.now = function () { + return new Date().getTime(); + }; + } + + if (!Date.prototype.toISOString) { + (function () { + function pad(number) { + if (number < 10) { + return '0' + number; + } + return number; + } + + Date.prototype.toISOString = function () { + return ( + this.getUTCFullYear() + + '-' + + pad(this.getUTCMonth() + 1) + + '-' + + pad(this.getUTCDate()) + + 'T' + + pad(this.getUTCHours()) + + ':' + + pad(this.getUTCMinutes()) + + ':' + + pad(this.getUTCSeconds()) + + '.' + + (this.getUTCMilliseconds() / 1000).toFixed(3).slice(2, 5) + + 'Z' + ); + }; + })(); + } + + // ==================== Promise Polyfill ==================== + + if (typeof Promise === 'undefined') { + (function () { + var PENDING = 0; + var FULFILLED = 1; + var REJECTED = 2; + + function Promise(executor) { + var self = this; + self.state = PENDING; + self.value = undefined; + self.handlers = []; + + function fulfill(result) { + if (self.state !== PENDING) return; + self.state = FULFILLED; + self.value = result; + self.handlers.forEach(handle); + self.handlers = null; + } + + function reject(error) { + if (self.state !== PENDING) return; + self.state = REJECTED; + self.value = error; + self.handlers.forEach(handle); + self.handlers = null; + } + + function resolve(result) { + try { + var then = getThen(result); + if (then) { + doResolve(then.bind(result), resolve, reject); + return; + } + fulfill(result); + } catch (e) { + reject(e); + } + } + + function handle(handler) { + if (self.state === PENDING) { + self.handlers.push(handler); + } else { + if ( + self.state === FULFILLED && + typeof handler.onFulfilled === 'function' + ) { + handler.onFulfilled(self.value); + } + if ( + self.state === REJECTED && + typeof handler.onRejected === 'function' + ) { + handler.onRejected(self.value); + } + } + } + + self.done = function (onFulfilled, onRejected) { + setTimeout(function () { + handle({ + onFulfilled: onFulfilled, + onRejected: onRejected, + }); + }, 0); + }; + + self.then = function (onFulfilled, onRejected) { + return new Promise(function (resolve, reject) { + self.done( + function (result) { + if (typeof onFulfilled === 'function') { + try { + return resolve(onFulfilled(result)); + } catch (ex) { + return reject(ex); + } + } else { + return resolve(result); + } + }, + function (error) { + if (typeof onRejected === 'function') { + try { + return resolve(onRejected(error)); + } catch (ex) { + return reject(ex); + } + } else { + return reject(error); + } + }, + ); + }); + }; + + self.catch = function (onRejected) { + return self.then(null, onRejected); + }; + + doResolve(executor, resolve, reject); + } + + Promise.resolve = function (value) { + if ( + value && + typeof value === 'object' && + value.constructor === Promise + ) { + return value; + } + return new Promise(function (resolve) { + resolve(value); + }); + }; + + Promise.reject = function (reason) { + return new Promise(function (resolve, reject) { + reject(reason); + }); + }; + + Promise.all = function (promises) { + return new Promise(function (resolve, reject) { + if (!Array.isArray(promises)) { + return reject(new TypeError('Promise.all accepts an array')); + } + + var results = new Array(promises.length); + var remaining = promises.length; + + if (remaining === 0) { + return resolve(results); + } + + function resolver(index) { + return function (value) { + results[index] = value; + if (--remaining === 0) { + resolve(results); + } + }; + } + + for (var i = 0; i < promises.length; i++) { + Promise.resolve(promises[i]).then(resolver(i), reject); + } + }); + }; + + Promise.race = function (promises) { + return new Promise(function (resolve, reject) { + if (!Array.isArray(promises)) { + return reject(new TypeError('Promise.race accepts an array')); + } + + for (var i = 0; i < promises.length; i++) { + Promise.resolve(promises[i]).then(resolve, reject); + } + }); + }; + + function getThen(value) { + var t = typeof value; + if (value && (t === 'object' || t === 'function')) { + var then = value.then; + if (typeof then === 'function') { + return then; + } + } + return null; + } + + function doResolve(fn, onFulfilled, onRejected) { + var done = false; + try { + fn( + function (value) { + if (done) return; + done = true; + onFulfilled(value); + }, + function (reason) { + if (done) return; + done = true; + onRejected(reason); + }, + ); + } catch (ex) { + if (done) return; + done = true; + onRejected(ex); + } + } + + global.Promise = Promise; + })(); + } + + if (Promise && !Promise.finally) { + Promise.prototype.finally = function (callback) { + var P = this.constructor; + return this.then( + function (value) { + return P.resolve(callback()).then(function () { + return value; + }); + }, + function (reason) { + return P.resolve(callback()).then(function () { + throw reason; + }); + }, + ); + }; + } + + if (Promise && !Promise.allSettled) { + Promise.allSettled = function (promises) { + return Promise.all( + promises.map(function (p) { + return Promise.resolve(p).then( + function (value) { + return { status: 'fulfilled', value: value }; + }, + function (reason) { + return { status: 'rejected', reason: reason }; + }, + ); + }), + ); + }; + } + + // ==================== DOM Polyfills ==================== + + if (!document) return; + + // Element.matches + if (global.Element && !Element.prototype.matches) { + Element.prototype.matches = + Element.prototype.matchesSelector || + Element.prototype.mozMatchesSelector || + Element.prototype.msMatchesSelector || + Element.prototype.oMatchesSelector || + Element.prototype.webkitMatchesSelector || + function (s) { + var matches = (this.document || this.ownerDocument).querySelectorAll(s); + var i = matches.length; + while (--i >= 0 && matches.item(i) !== this) {} + return i > -1; + }; + } + + // Element.closest + if (global.Element && !Element.prototype.closest) { + Element.prototype.closest = function (s) { + var el = this; + do { + if (el.matches(s)) return el; + el = el.parentElement || el.parentNode; + } while (el !== null && el.nodeType === 1); + return null; + }; + } + + // Element.remove + if (global.Element && !Element.prototype.remove) { + Element.prototype.remove = function () { + if (this.parentNode) { + this.parentNode.removeChild(this); + } + }; + } + + // ChildNode.before + if (global.Element && !Element.prototype.before) { + Element.prototype.before = function () { + var parent = this.parentNode; + if (!parent) return; + var args = Array.prototype.slice.call(arguments); + var i = args.length; + while (i--) { + var node = args[i]; + if (typeof node === 'string') { + node = document.createTextNode(node); + } + parent.insertBefore(node, this); + } + }; + } + + // ChildNode.after + if (global.Element && !Element.prototype.after) { + Element.prototype.after = function () { + var parent = this.parentNode; + if (!parent) return; + var args = Array.prototype.slice.call(arguments); + var ref = this.nextSibling; + var i = 0; + while (i < args.length) { + var node = args[i]; + if (typeof node === 'string') { + node = document.createTextNode(node); + } + if (ref) { + parent.insertBefore(node, ref); + } else { + parent.appendChild(node); + } + i++; + } + }; + } + + // ChildNode.replaceWith + if (global.Element && !Element.prototype.replaceWith) { + Element.prototype.replaceWith = function () { + var parent = this.parentNode; + if (!parent) return; + var args = Array.prototype.slice.call(arguments); + var i = 0; + while (i < args.length) { + var node = args[i]; + if (typeof node === 'string') { + node = document.createTextNode(node); + } + parent.insertBefore(node, this); + i++; + } + parent.removeChild(this); + }; + } + + // ParentNode.append + if (global.Element && !Element.prototype.append) { + Element.prototype.append = function () { + var args = Array.prototype.slice.call(arguments); + var i = 0; + while (i < args.length) { + var node = args[i]; + if (typeof node === 'string') { + node = document.createTextNode(node); + } + this.appendChild(node); + i++; + } + }; + } + + // ParentNode.prepend + if (global.Element && !Element.prototype.prepend) { + Element.prototype.prepend = function () { + var args = Array.prototype.slice.call(arguments); + var ref = this.firstChild; + var i = 0; + while (i < args.length) { + var node = args[i]; + if (typeof node === 'string') { + node = document.createTextNode(node); + } + if (ref) { + this.insertBefore(node, ref); + } else { + this.appendChild(node); + } + i++; + } + }; + } + + // CustomEvent + if (typeof global.CustomEvent !== 'function') { + function CustomEvent(event, params) { + params = params || { bubbles: false, cancelable: false, detail: null }; + var evt = document.createEvent('CustomEvent'); + evt.initCustomEvent( + event, + params.bubbles, + params.cancelable, + params.detail, + ); + return evt; + } + CustomEvent.prototype = global.Event.prototype; + global.CustomEvent = CustomEvent; + } + + // classList polyfill + if (!('classList' in document.createElement('_')) && global.Element) { + (function (view) { + if (!('Element' in view)) return; + + var classListProp = 'classList'; + var protoProp = 'prototype'; + var elemCtrProto = view.Element[protoProp]; + var objCtr = Object; + var strTrim = + String[protoProp].trim || + function () { + return this.replace(/^\s+|\s+$/g, ''); + }; + var arrIndexOf = + Array[protoProp].indexOf || + function (item) { + var i = 0, + len = this.length; + for (; i < len; i++) { + if (i in this && this[i] === item) { + return i; + } + } + return -1; + }; + + var DOMTokenList = function (elem) { + var classes = strTrim.call(elem.getAttribute('class') || ''); + var tokens = classes ? classes.split(/\s+/) : []; + var i = 0; + this.length = tokens.length; + this._elem = elem; + for (; i < this.length; i++) { + this[i] = tokens[i]; + } + }; + + var listProto = (DOMTokenList[protoProp] = []); + + listProto.item = function (i) { + return this[i] || null; + }; + + listProto.contains = function (token) { + token += ''; + return arrIndexOf.call(this, token) !== -1; + }; + + listProto.add = function () { + var tokens = arguments; + var i = 0; + var l = tokens.length; + var token; + var updated = false; + + do { + token = tokens[i] + ''; + if (arrIndexOf.call(this, token) === -1) { + this[this.length] = token; + this.length++; + updated = true; + } + } while (++i < l); + + if (updated) { + this._elem.className = this.toString(); + } + }; + + listProto.remove = function () { + var tokens = arguments; + var i = 0; + var l = tokens.length; + var token; + var updated = false; + var index; + + do { + token = tokens[i] + ''; + index = arrIndexOf.call(this, token); + while (index !== -1) { + this.splice(index, 1); + this.length--; + updated = true; + index = arrIndexOf.call(this, token); + } + } while (++i < l); + + if (updated) { + this._elem.className = this.toString(); + } + }; + + listProto.toggle = function (token, force) { + token += ''; + var result = this.contains(token); + var method = result + ? force !== true && 'remove' + : force !== false && 'add'; + + if (method) { + this[method](token); + } + + if (force === true || force === false) { + return force; + } + return !result; + }; + + listProto.replace = function (token, replacement_token) { + var index = arrIndexOf.call(this, token + ''); + if (index !== -1) { + this.splice(index, 1); + this.add(replacement_token); + this._elem.className = this.toString(); + return true; + } + return false; + }; + + listProto.toString = function () { + return Array.prototype.slice.call(this).join(' '); + }; + + listProto.splice = Array.prototype.splice; + + if (objCtr.defineProperty) { + var classListPropDesc = { + get: function () { + return new DOMTokenList(this); + }, + enumerable: true, + configurable: true, + }; + try { + objCtr.defineProperty(elemCtrProto, classListProp, classListPropDesc); + } catch (ex) { + if (ex.number === undefined || ex.number === -0x7ff5ec54) { + classListPropDesc.enumerable = false; + objCtr.defineProperty( + elemCtrProto, + classListProp, + classListPropDesc, + ); + } + } + } + })(global); + } + + // requestAnimationFrame / cancelAnimationFrame + (function () { + var lastTime = 0; + var vendors = ['webkit', 'moz', 'ms', 'o']; + + for (var x = 0; x < vendors.length && !global.requestAnimationFrame; ++x) { + global.requestAnimationFrame = + global[vendors[x] + 'RequestAnimationFrame']; + global.cancelAnimationFrame = + global[vendors[x] + 'CancelAnimationFrame'] || + global[vendors[x] + 'CancelRequestAnimationFrame']; + } + + if (!global.requestAnimationFrame) { + global.requestAnimationFrame = function (callback) { + var currTime = Date.now(); + var timeToCall = Math.max(0, 16 - (currTime - lastTime)); + var id = global.setTimeout(function () { + callback(currTime + timeToCall); + }, timeToCall); + lastTime = currTime + timeToCall; + return id; + }; + } + + if (!global.cancelAnimationFrame) { + global.cancelAnimationFrame = function (id) { + clearTimeout(id); + }; + } + })(); + + // performance.now + if (!global.performance) { + global.performance = {}; + } + + if (!global.performance.now) { + var nowOffset = Date.now(); + + if ( + global.performance.timing && + global.performance.timing.navigationStart + ) { + nowOffset = global.performance.timing.navigationStart; + } + + global.performance.now = function () { + return Date.now() - nowOffset; + }; + } + + // Event listeners + (function () { + if (!global.addEventListener) { + global.addEventListener = function (type, listener, useCapture) { + global.attachEvent('on' + type, listener); + }; + } + + if (!global.removeEventListener) { + global.removeEventListener = function (type, listener, useCapture) { + global.detachEvent('on' + type, listener); + }; + } + + if (document && !document.addEventListener) { + document.addEventListener = function (type, listener, useCapture) { + document.attachEvent('on' + type, listener); + }; + } + + if (document && !document.removeEventListener) { + document.removeEventListener = function (type, listener, useCapture) { + document.detachEvent('on' + type, listener); + }; + } + })(); + + // console + if (!global.console) { + global.console = { + log: function () {}, + warn: function () {}, + error: function () {}, + info: function () {}, + debug: function () {}, + trace: function () {}, + dir: function () {}, + group: function () {}, + groupCollapsed: function () {}, + groupEnd: function () {}, + time: function () {}, + timeEnd: function () {}, + assert: function () {}, + clear: function () {}, + count: function () {}, + table: function () {}, + }; + } + + // Node constants + if (typeof Node === 'undefined') { + global.Node = { + ELEMENT_NODE: 1, + ATTRIBUTE_NODE: 2, + TEXT_NODE: 3, + CDATA_SECTION_NODE: 4, + ENTITY_REFERENCE_NODE: 5, + ENTITY_NODE: 6, + PROCESSING_INSTRUCTION_NODE: 7, + COMMENT_NODE: 8, + DOCUMENT_NODE: 9, + DOCUMENT_TYPE_NODE: 10, + DOCUMENT_FRAGMENT_NODE: 11, + NOTATION_NODE: 12, + }; + } + + // querySelectorAll and querySelector + if (document && !document.querySelectorAll) { + document.querySelectorAll = function (selector) { + var doc = document; + var head = doc.documentElement.firstChild; + var styleTag = doc.createElement('style'); + head.appendChild(styleTag); + doc._qsa = []; + + styleTag.styleSheet.cssText = + selector + + '{x-qsa:expression(document._qsa && document._qsa.push(this))}'; + global.scrollBy(0, 0); + head.removeChild(styleTag); + + var elements = []; + while (doc._qsa.length) { + var element = doc._qsa.shift(); + element.style.removeAttribute('x-qsa'); + elements.push(element); + } + doc._qsa = null; + return elements; + }; + } + + if (document && !document.querySelector) { + document.querySelector = function (selector) { + var elements = document.querySelectorAll(selector); + return elements.length ? elements[0] : null; + }; + } + + // ==================== Utility Functions ==================== + + // DOMContentLoaded helper + global.$ready = function (callback) { + if (typeof callback !== 'function') return; + + if ( + document.readyState === 'complete' || + document.readyState === 'interactive' + ) { + setTimeout(callback, 1); + } else if (document.addEventListener) { + document.addEventListener('DOMContentLoaded', callback); + } else if (document.attachEvent) { + document.attachEvent('onreadystatechange', function () { + if (document.readyState !== 'loading') { + callback(); + } + }); + } + }; + + // Event handler helpers + global.$on = function (target, event, handler, capture) { + if (!target || !event || !handler) return; + + if (target.addEventListener) { + target.addEventListener(event, handler, capture || false); + } else if (target.attachEvent) { + target.attachEvent('on' + event, handler); + } + }; + + global.$off = function (target, event, handler, capture) { + if (!target || !event || !handler) return; + + if (target.removeEventListener) { + target.removeEventListener(event, handler, capture || false); + } else if (target.detachEvent) { + target.detachEvent('on' + event, handler); + } + }; + + // Query selector helpers + global.$qs = function (selector, context) { + return (context || document).querySelector(selector); + }; + + global.$qsa = function (selector, context) { + var results = (context || document).querySelectorAll(selector); + return Array.prototype.slice.call(results); + }; + + // Class manipulation helpers + global.$hasClass = function (el, className) { + if (!el || !className) return false; + if (el.classList) return el.classList.contains(className); + return new RegExp('(^|\\s)' + className + '(\\s|$)').test(el.className); + }; + + global.$addClass = function (el, className) { + if (!el || !className) return; + if (el.classList) { + el.classList.add(className); + } else if (!global.$hasClass(el, className)) { + el.className = (el.className + ' ' + className).trim(); + } + }; + + global.$removeClass = function (el, className) { + if (!el || !className) return; + if (el.classList) { + el.classList.remove(className); + } else { + el.className = el.className + .replace(new RegExp('(^|\\s)' + className + '(\\s|$)', 'g'), ' ') + .trim(); + } + }; + + global.$toggleClass = function (el, className) { + if (!el || !className) return; + if (el.classList) { + el.classList.toggle(className); + } else if (global.$hasClass(el, className)) { + global.$removeClass(el, className); + } else { + global.$addClass(el, className); + } + }; + + // CSS helper + global.$css = function (el, styles) { + if (!el) return; + + if (typeof styles === 'string') { + return el.style[styles]; + } + + for (var prop in styles) { + if (styles.hasOwnProperty(prop)) { + el.style[prop] = styles[prop]; + } + } + }; + + // Attribute helper + global.$attr = function (el, name, value) { + if (!el || !name) return; + + if (value === undefined) { + return el.getAttribute(name); + } + + if (value === null) { + el.removeAttribute(name); + } else { + el.setAttribute(name, value); + } + }; + + // JSON helpers (enhanced) + if (typeof JSON !== 'object' || !JSON.parse || !JSON.stringify) { + global.JSON = { + parse: function (text) { + if (typeof text !== 'string') { + throw new TypeError('JSON.parse expects a string'); + } + try { + return new Function('return ' + text)(); + } catch (e) { + throw new SyntaxError('Invalid JSON: ' + e.message); + } + }, + stringify: function (value, replacer, space) { + var indent = ''; + var gap = ''; + + if (typeof space === 'number') { + for (var i = 0; i < space; i++) { + gap += ' '; + } + } else if (typeof space === 'string') { + gap = space; + } + + function str(key, holder) { + var value = holder[key]; + + if (value && typeof value.toJSON === 'function') { + value = value.toJSON(key); + } + + if (typeof replacer === 'function') { + value = replacer.call(holder, key, value); + } + + switch (typeof value) { + case 'string': + return quote(value); + case 'number': + return isFinite(value) ? String(value) : 'null'; + case 'boolean': + case 'null': + return String(value); + case 'object': + if (!value) return 'null'; + indent += gap; + var partial = []; + + if (Array.isArray(value)) { + var len = value.length; + for (var i = 0; i < len; i++) { + partial[i] = str(i, value) || 'null'; + } + var v = + partial.length === 0 + ? '[]' + : gap + ? '[\n' + + indent + + partial.join(',\n' + indent) + + '\n' + + indent.slice(0, -gap.length) + + ']' + : '[' + partial.join(',') + ']'; + indent = indent.slice(0, -gap.length); + return v; + } + + for (var k in value) { + if (Object.prototype.hasOwnProperty.call(value, k)) { + var v = str(k, value); + if (v) { + partial.push(quote(k) + (gap ? ': ' : ':') + v); + } + } + } + + v = + partial.length === 0 + ? '{}' + : gap + ? '{\n' + + indent + + partial.join(',\n' + indent) + + '\n' + + indent.slice(0, -gap.length) + + '}' + : '{' + partial.join(',') + '}'; + indent = indent.slice(0, -gap.length); + return v; + } + } + + function quote(string) { + var escapable = + /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g; + var meta = { + '\b': '\\b', + '\t': '\\t', + '\n': '\\n', + '\f': '\\f', + '\r': '\\r', + '"': '\\"', + '\\': '\\\\', + }; + + escapable.lastIndex = 0; + return escapable.test(string) + ? '"' + + string.replace(escapable, function (a) { + var c = meta[a]; + return typeof c === 'string' + ? c + : '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); + }) + + '"' + : '"' + string + '"'; + } + + return str('', { '': value }); + }, + }; + } + + global.$parseJSON = function (str) { + try { + return JSON.parse(str); + } catch (e) { + console.error('JSON parse error:', e); + return null; + } + }; + + global.$toJSON = function (obj) { + try { + return JSON.stringify(obj); + } catch (e) { + console.error('JSON stringify error:', e); + return '{}'; + } + }; + + // Polyfill initialization complete + if (global.console && global.console.log) { + console.log('polyfills v2.0.0 loaded successfully'); + } +})( + typeof window !== 'undefined' ? window : this, + typeof document !== 'undefined' ? document : {}, +); diff --git a/manifest.json b/manifest.json index 0d9051d..0d31905 100644 --- a/manifest.json +++ b/manifest.json @@ -16,5 +16,9 @@ "sizes": "512x512", "type": "image/png" } - ] + ], + "dir": "auto", + "lang": "en", + "orientation": "portrait", + "categories": ["entertainment", "games"] } diff --git a/robots.txt b/robots.txt index 2edfb13..53f6d88 100644 --- a/robots.txt +++ b/robots.txt @@ -1,134 +1,43 @@ -# Allow only beneficial bots - -User-agent: Googlebot -Allow: / - -User-agent: bingbot -Allow: / - -User-agent: DuckDuckBot -Allow: / - -User-agent: Slurp -Allow: / - -User-agent: BraveBot -Allow: / - -User-agent: Applebot -Allow: / - -User-agent: ia_archiver -Allow: / - -User-agent: OpenAI-GPT-Web -Allow: / - -# Block known AI and data crawlers - -User-agent: GPTBot -Disallow: / - -User-agent: ChatGPT-User -Disallow: / - -User-agent: Claude-Web -Disallow: / - -User-agent: ClaudeBot -Disallow: / - -User-agent: anthropic-ai -Disallow: / - -User-agent: PerplexityBot -Disallow: / - -User-agent: cohere-ai -Disallow: / - -User-agent: Amazonbot -Disallow: / - -User-agent: Bytespider -Disallow: / - -User-agent: Diffbot -Disallow: / - -User-agent: CCBot -Disallow: / - -User-agent: OAI-SearchBot -Disallow: / - -User-agent: AI2Bot -Disallow: / - -User-agent: Ai2Bot-Dolma -Disallow: / - -User-agent: FacebookBot -Disallow: / - -User-agent: Meta-ExternalAgent -Disallow: / - -User-agent: Meta-ExternalFetcher -Disallow: / - -User-agent: YouBot -Disallow: / - -User-agent: DuckAssistBot -Disallow: / - -User-agent: Webzio-Extended -Disallow: / - -User-agent: Timpibot -Disallow: / - -User-agent: img2dataset -Disallow: / - -User-agent: ImagesiftBot -Disallow: / - -User-agent: PetalBot -Disallow: / - -User-agent: ICC-Crawler -Disallow: / - -User-agent: iaskspider/2.0 -Disallow: / - -User-agent: Scrapy -Disallow: / - -User-agent: omgili -Disallow: / - -User-agent: omgilibot -Disallow: / - -User-agent: Kangaroo Bot -Disallow: / - -User-agent: VelenPublicWebCrawler -Disallow: / - -User-agent: Sidetrade indexer bot -Disallow: / - -User-agent: ISSCyberRiskCrawler -Disallow: / - -User-agent: PanguBot -Disallow: / - -# Block all other unidentified bots - -User-agent: * -Disallow: / +User-agent: Googlebot +Allow: / +Crawl-delay: 15 + +User-agent: Applebot +Allow: / +Crawl-delay: 15 + +User-agent: DuckAssistBot +Allow: / +Crawl-delay: 15 + +User-agent: Yandex +Allow: / +Crawl-delay: 15 + +User-agent: Baiduspider +Allow: / +Crawl-delay: 15 + +User-agent: Sogou web spider +Allow: / +Crawl-delay: 15 + +User-agent: Shenma +Allow: / +Crawl-delay: 15 + +User-agent: ia_archiver +Allow: / + +User-agent: archive.org_bot +Allow: / + +User-agent: CCBot +Allow: / + +User-agent: AlexandriaOrgBot +Allow: / + +User-agent: * +Disallow: / +Crawl-delay: 15 \ No newline at end of file diff --git a/rss.xml b/rss.xml new file mode 100644 index 0000000..9644505 --- /dev/null +++ b/rss.xml @@ -0,0 +1,98 @@ + + + + auth's RNG blog + https://authsrng.neocities.org/blog/blog.html + news, updates, and a whole lotta things + en-us + Mon, 16 Mar 2026 20:18:11 GMT + + + just so you guys don't think i'm doing nothing + https://authsrng.neocities.org/blog/post-s4f5.html?id=011 + just so you guys don't think i'm doing nothing + Mon, 16 Mar 2026 00:00:00 GMT + https://authsrng.neocities.org/blog/post-s4f5.html?id=011 + + + + possibly moving(?) + https://authsrng.neocities.org/blog/post-s4f5.html?id=010 + possibly moving(?) + Tue, 24 Feb 2026 00:00:00 GMT + https://authsrng.neocities.org/blog/post-s4f5.html?id=010 + + + + looking forward + https://authsrng.neocities.org/blog/post-s4f5.html?id=009 + looking forward + Sun, 22 Feb 2026 00:00:00 GMT + https://authsrng.neocities.org/blog/post-s4f5.html?id=009 + + + + going niche + https://authsrng.neocities.org/blog/post-s4f5.html?id=008 + going niche + Thu, 12 Feb 2026 00:00:00 GMT + https://authsrng.neocities.org/blog/post-s4f5.html?id=008 + + + + update announcement + https://authsrng.neocities.org/blog/post-s4f5.html?id=007 + update announcement + Sun, 08 Feb 2026 00:00:00 GMT + https://authsrng.neocities.org/blog/post-s4f5.html?id=007 + + + + a world without bots + https://authsrng.neocities.org/blog/post-s4f5.html?id=006 + a world without bots + Wed, 28 Jan 2026 00:00:00 GMT + https://authsrng.neocities.org/blog/post-s4f5.html?id=006 + + + + bing block + https://authsrng.neocities.org/blog/post-s4f5.html?id=005 + bing block + Wed, 28 Jan 2026 00:00:00 GMT + https://authsrng.neocities.org/blog/post-s4f5.html?id=005 + + + + potential hiatus? + https://authsrng.neocities.org/blog/post-s4f5.html?id=004 + potential hiatus? + Mon, 26 Jan 2026 00:00:00 GMT + https://authsrng.neocities.org/blog/post-s4f5.html?id=004 + + + + a note about the page system + https://authsrng.neocities.org/blog/post-s4f5.html?id=003 + a note about the page system + Tue, 20 Jan 2026 00:00:00 GMT + https://authsrng.neocities.org/blog/post-s4f5.html?id=003 + + + + 2026 roadmap + https://authsrng.neocities.org/blog/post-s4f5.html?id=002 + 2026 roadmap + Mon, 19 Jan 2026 00:00:00 GMT + https://authsrng.neocities.org/blog/post-s4f5.html?id=002 + + + + hello world + https://authsrng.neocities.org/blog/post-s4f5.html?id=001 + hello world + Mon, 19 Jan 2026 00:00:00 GMT + https://authsrng.neocities.org/blog/post-s4f5.html?id=001 + + + \ No newline at end of file diff --git a/screensaver.js b/screensaver.js deleted file mode 100644 index 8e1241e..0000000 --- a/screensaver.js +++ /dev/null @@ -1,95 +0,0 @@ -// screensaver.js - -(function() { - const messages = [ - "wake up", - "theres more to come", - "its not over yet", - "come back", - "it's okay", - "you can rest for now", - "hey loser wake up", - "you're idle bro", - "sfvszfhjksdhkshjsd", - "such an asshole", - "player please just wake up" - ]; - - let screensaver, textElem, idleTimer, messageTimer; - - function createScreensaver() { - screensaver = document.createElement("div"); - screensaver.id = "screensaver"; - screensaver.style.position = "fixed"; - screensaver.style.top = 0; - screensaver.style.left = 0; - screensaver.style.width = "100%"; - screensaver.style.height = "100%"; - screensaver.style.display = "flex"; - screensaver.style.alignItems = "center"; - screensaver.style.justifyContent = "center"; - screensaver.style.background = "radial-gradient(circle at center, rgba(0,0,0,0.8), rgba(0,0,0,0.95))"; - screensaver.style.zIndex = 9999; - screensaver.style.opacity = 0; - screensaver.style.transition = "opacity 1.5s ease"; - screensaver.style.pointerEvents = "none"; - - textElem = document.createElement("div"); - textElem.style.fontFamily = "monospace"; - textElem.style.fontSize = "2rem"; - textElem.style.color = "white"; - textElem.style.textShadow = "0 0 15px rgba(255,255,255,0.6)"; - textElem.style.whiteSpace = "pre"; - screensaver.appendChild(textElem); - - document.body.appendChild(screensaver); - - // psychedelic background animation - let hue = 0; - setInterval(() => { - hue = (hue + 1) % 360; - screensaver.style.background = `radial-gradient(circle at center, hsla(${hue},70%,40%,0.5), rgba(0,0,0,0.95))`; - }, 50); - } - - function typeWriter(text) { - textElem.textContent = ""; - let i = 0; - const interval = setInterval(() => { - textElem.textContent += text.charAt(i); - i++; - if (i >= text.length) clearInterval(interval); - }, 80); - } - - function randomMessage() { - const msg = messages[Math.floor(Math.random() * messages.length)]; - typeWriter(msg); - } - - function showScreensaver() { - screensaver.style.pointerEvents = "auto"; - screensaver.style.opacity = 1; - randomMessage(); - messageTimer = setInterval(randomMessage, 5000); - } - - function hideScreensaver() { - screensaver.style.opacity = 0; - screensaver.style.pointerEvents = "none"; - clearInterval(messageTimer); - } - - function resetIdleTimer() { - clearTimeout(idleTimer); - idleTimer = setTimeout(showScreensaver, 120000); // 2 minutes - if (screensaver && screensaver.style.opacity === "1") hideScreensaver(); - } - - // init - createScreensaver(); - ["mousemove", "keydown", "mousedown", "touchstart"].forEach(evt => - document.addEventListener(evt, resetIdleTimer) - ); - resetIdleTimer(); -})(); diff --git a/service-worker.js b/service-worker.js index 6c9f3f3..f46712b 100644 --- a/service-worker.js +++ b/service-worker.js @@ -1,20 +1,20 @@ -self.addEventListener('install', event => { +self.addEventListener('install', (event) => { event.waitUntil( - caches.open('app-cache').then(cache => { + caches.open('app-cache').then((cache) => { return cache.addAll([ '/', '/index.html', '/manifest.json', - 'https://i.imgur.com/RZVHfEq.png' + 'https://i.imgur.com/RZVHfEq.png', ]); - }) + }), ); }); -self.addEventListener('fetch', event => { +self.addEventListener('fetch', (event) => { event.respondWith( - caches.match(event.request).then(response => { + caches.match(event.request).then((response) => { return response || fetch(event.request); - }) + }), ); }); diff --git a/startanim.js b/startanim.js deleted file mode 100644 index b1ce020..0000000 --- a/startanim.js +++ /dev/null @@ -1,95 +0,0 @@ -// startanim.js -window.addEventListener("DOMContentLoaded", () => { - const style = document.createElement("style"); - style.textContent = ` - .entry-container { - position: fixed; - inset: 0; - z-index: 9999; - background: black; - display: flex; - justify-content: center; - align-items: center; - overflow: hidden; - opacity: 1; - transition: opacity 1s ease; - } - - .tap-text { - position: absolute; - bottom: 5%; - color: white; - font-size: 1.2em; - font-family: sans-serif; - opacity: 0; - animation: fadein 2s forwards, blink 1.2s infinite alternate 2s; - white-space: nowrap; - user-select: none; - } - - @keyframes fadein { - from { opacity: 0; } - to { opacity: 1; } - } - - @keyframes blink { - from { opacity: 1; } - to { opacity: 0.4; } - } - - .white-line { - position: absolute; - top: 50%; - left: 0; - width: 100%; - height: 3px; - background: white; - filter: blur(2px); - transform: translateY(-50%); - transition: all 1s ease-in-out; - } - - .wipe { - height: 100%; - filter: blur(0); - } - - .fadeout-line { - animation: fadeout-line 1s forwards; - } - - @keyframes fadeout-line { - from { opacity: 1; } - to { opacity: 0; } - } - `; - document.head.appendChild(style); - - const container = document.createElement("div"); - container.className = "entry-container"; - container.innerHTML = ` -
      -
      tap/click to start...
      - `; - document.body.appendChild(container); - - const line = container.querySelector(".white-line"); - const text = container.querySelector(".tap-text"); - - function startWipe() { - text.style.display = "none"; - line.classList.add("wipe"); - - setTimeout(() => { - line.classList.add("fadeout-line"); - // after the line fades, fade out the whole container - setTimeout(() => { - container.style.opacity = "0"; - setTimeout(() => container.remove(), 1000); - }, 1000); - }, 1000); - } - - container.addEventListener("click", startWipe, { once: true }); - container.addEventListener("touchstart", startWipe, { once: true }); -}); diff --git a/styles.css b/styles.css new file mode 100644 index 0000000..1fd26a3 --- /dev/null +++ b/styles.css @@ -0,0 +1,2030 @@ +:root { + --bg-color: #0e0e0e; + --text-color: #dcdcdc; + --border-color: #2a2a2a; + --button-bg: #1a1a1a; + --button-hover: #222; + --button-text: #dcdcdc; + --input-bg: #1a1a1a; + --panel-bg: #111; + --overlay-bg: #0a0a0a; + --link-border: #444; + --achievement-bg: #1a2a1a; + --achievement-border: #2a4a2a; +} + +[data-theme='white'] { + --bg-color: #ffffff; + --text-color: #0e0e0e; + --border-color: #d0d0d0; + --button-bg: #f0f0f0; + --button-hover: #e0e0e0; + --button-text: #0e0e0e; + --input-bg: #f5f5f5; + --panel-bg: #fafafa; + --overlay-bg: #f8f8f8; + --link-border: #ccc; + --achievement-bg: #e8f5e8; + --achievement-border: #90c090; +} + +@keyframes scale-up-center { + 0% { + transform: scale(0.5); + } + + 100% { + transform: scale(1); + } +} + +@keyframes shaderPulse { + 0% { + background: radial-gradient( + circle at center, + rgba(255, 255, 255, 0.04), + rgba(255, 0, 255, 0.03), + rgba(0, 180, 255, 0.03), + rgba(0, 255, 150, 0.02), + rgba(255, 0, 0, 0.02) + ); + } + + 100% { + background: radial-gradient( + circle at center, + rgba(255, 255, 255, 0.02), + rgba(255, 0, 255, 0.02), + rgba(0, 180, 255, 0.02), + rgba(0, 255, 150, 0.01), + rgba(255, 0, 0, 0.01) + ); + } +} + +@keyframes rgb { + 0% { + color: #ff6666; + } + + 33% { + color: #66ff66; + } + + 66% { + color: #6666ff; + } + + 100% { + color: #ff6666; + } +} + +@keyframes flash { + 0%, + 50%, + 100% { + background-color: var(--text-color); + color: var(--bg-color); + } + + 25%, + 75% { + background-color: var(--bg-color); + color: var(--text-color); + } +} + +@keyframes bounce { + from { + transform: translateY(0); + } + + to { + transform: translateY(-5px); + } +} + +* { + box-sizing: border-box; +} + +body { + background-color: var(--bg-color); + color: var(--text-color); + font-family: monospace; + margin: 0; + padding: 28px; + line-height: 1.7; + font-size: 15px; + min-height: 100vh; + display: flex; + flex-direction: column; + align-items: center; + user-select: none; + transition: + background 0.3s, + color 0.3s; + overflow-x: hidden; + overflow-y: auto; + max-width: 100vw; +} + +body, +button { + cursor: none !important; +} + +h1, +h2 { + font-weight: normal; + letter-spacing: 0.06em; + text-transform: lowercase; + opacity: 0.95; + color: var(--text-color); +} + +h1 { + font-size: 1.6rem; + margin: 0 0 0.4em 0; +} + +h2 { + font-size: 1.4rem; + margin: 0 0 1.2em 0; +} + +p { + color: var(--text-color); + opacity: 0.75; + font-size: 0.95em; + margin: 0 0 1em 0; + max-width: 540px; + line-height: 1.6; + text-align: center; +} + +a { + color: var(--text-color); + text-decoration: none; + border-bottom: 1px dotted var(--link-border); + opacity: 0.8; + transition: opacity 0.2s; +} + +a:hover { + opacity: 1; +} + +button { + background: var(--button-bg); + color: var(--button-text); + border: 1px solid var(--border-color); + padding: 10px 18px; + font-family: monospace; + font-size: 0.95em; + cursor: pointer; + border-radius: 2px; + transition: all 0.2s; + font-weight: normal; +} + +button:hover:not(:disabled) { + background: var(--button-hover); + border-color: var(--border-color); +} + +button:disabled { + opacity: 0.4; + cursor: not-allowed; +} + +button.small { + padding: 4px 8px; + font-size: 0.85em; +} + +select { + background: var(--input-bg); + color: var(--text-color); + border: 1px solid var(--border-color); + padding: 4px 8px; + font-family: monospace; + font-size: 0.85em; + border-radius: 2px; +} + +.lazy { + opacity: 0; + transition: opacity 0.6s ease; + will-change: opacity; + pointer-events: none; +} + +.lazy.visible { + opacity: 1; + pointer-events: auto; +} + +.scale-up { + animation: scale-up-center 0.5s ease forwards; +} + +#rollBtn { + padding: 12px 24px; + font-size: 1.1em; + width: 100%; + max-width: 540px; + margin-bottom: 1em; + animation: scale-up-center 0.5s ease forwards; +} + +#rollBtn:disabled { + background: var(--panel-bg); +} + +#spinnerContainer { + height: 48px; + width: 100%; + max-width: 540px; + overflow: hidden; + position: relative; + border: 1px solid var(--border-color); + background: var(--overlay-bg); + margin-bottom: 1em; + border-radius: 2px; +} + +#spinner { + position: absolute; + top: 0; + width: 100%; + display: flex; + flex-direction: column; +} + +.spin-item { + height: 48px; + display: flex; + align-items: center; + justify-content: center; + font-size: 1em; + font-weight: normal; + opacity: 0.9; + color: var(--text-color); +} + +#totalRolls { + opacity: 0.7; + font-size: 0.9em; + margin-bottom: 0.5em; + font-weight: normal; + color: var(--text-color); +} + +#dailyContainer, +#weeklyContainer { + margin-top: 15px; + padding: 12px; + background: var(--panel-bg); + border: 1px solid var(--border-color); + border-radius: 3px; + max-width: 540px; + width: 100%; + text-align: center; +} + +#dailyStatus, +#weeklyStatus { + margin-bottom: 0.8em; + font-size: 0.9em; + opacity: 0.7; + color: var(--text-color); +} + +#dailyBtn, +#weeklyBtn { + padding: 6px 14px; + font-size: 0.85em; + margin-top: 8px; +} + +#luckBoostOverlay { + position: fixed; + inset: 0; + pointer-events: none; + display: none; + justify-content: center; + align-items: center; + flex-direction: column; + background: radial-gradient( + circle at center, + rgba(255, 255, 255, 0.04) 0%, + rgba(255, 0, 255, 0.03) 25%, + rgba(0, 180, 255, 0.03) 50%, + rgba(0, 255, 150, 0.02) 75%, + rgba(255, 0, 0, 0.02) 100% + ); + animation: shaderPulse 4s infinite alternate; + z-index: 9999; +} + +#luckTimer { + font-size: 80px; + font-weight: 900; + color: var(--text-color); + opacity: 0.25; + text-shadow: 0 0 30px currentColor; +} + +#luckFooter { + position: fixed; + bottom: 20px; + width: 100%; + text-align: center; + font-size: 16px; + font-weight: normal; + color: var(--text-color); + opacity: 0.15; + text-shadow: 0 0 10px currentColor; + pointer-events: none; +} + +#inventorySection { + width: 100%; + max-width: 540px; + margin-top: 20px; +} + +.inventory-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 0.8em; + font-size: 0.85em; +} + +.inventory-title { + letter-spacing: 0.08em; + text-transform: lowercase; + opacity: 0.5; + color: var(--text-color); +} + +.inventory-controls { + display: flex; + align-items: center; + gap: 8px; +} + +.sort-label { + opacity: 0.6; + color: var(--text-color); +} + +#collectedCounter { + opacity: 0.5; + font-size: 0.85em; + margin-bottom: 0.5em; + font-weight: normal; + color: var(--text-color); +} + +#inventoryContainer { + position: relative; + width: 100%; +} + +#inventoryList { + list-style: none; + margin: 0; + padding: 12px; + background: var(--overlay-bg); + border: 1px solid var(--border-color); + border-radius: 2px; + max-height: 280px; + overflow-y: auto; + font-size: 0.9em; + font-weight: normal; + text-align: center; +} + +#inventoryList li { + padding: 6px 8px; + margin-bottom: 4px; + opacity: 0.75; + border-radius: 2px; + transition: + opacity 0.2s, + background 0.2s; + color: var(--text-color); +} + +#inventoryList li:hover { + opacity: 1; + background: var(--panel-bg); +} + +#inventoryList li.new-roll { + background: var(--button-bg); + opacity: 1; +} + +#anomalyPanel { + display: flex; + justify-content: space-between; + align-items: center; + margin-top: 0.8em; + font-size: 0.85em; + opacity: 0.7; +} + +#anomalyCount { + font-weight: normal; + color: var(--text-color); +} + +#consumeAnomalyBtn { + padding: 4px 10px; + font-size: 0.85em; +} + +#consumeAnomalyBtn:disabled { + opacity: 0.3; +} + +#anomalyPopup { + position: fixed; + left: 50%; + top: 14%; + transform: translateX(-50%); + background: var(--panel-bg); + border: 1px solid var(--border-color); + padding: 10px 14px; + border-radius: 2px; + color: var(--text-color); + font-weight: normal; + font-size: 0.9em; + z-index: 10001; + pointer-events: none; + opacity: 0; + transition: opacity 0.2s ease; +} + +#anomalyPopup.show { + opacity: 1; +} + +#achievementsContainer { + margin-top: 2rem; + max-width: 540px; + width: 100%; + background: var(--overlay-bg); + border: 1px solid var(--border-color); + border-radius: 3px; + padding: 12px; + max-height: 300px; + overflow-y: auto; +} + +.achievement { + padding: 8px 10px; + margin-bottom: 8px; + background: var(--panel-bg); + border-radius: 2px; + opacity: 0.6; + transition: all 0.3s; + display: flex; + flex-direction: column; +} + +.achievement.unlocked { + opacity: 1; + background: var(--achievement-bg); + border-left: 2px solid var(--achievement-border); +} + +.achievement-name { + font-size: 0.95em; + margin-bottom: 0.2em; + font-weight: normal; + color: var(--text-color); +} + +.achievement-subtitle { + font-size: 0.8em; + opacity: 0.6; + font-style: italic; + font-weight: normal; + color: var(--text-color); +} + +#resetContainer { + margin-top: 1.5rem; + text-align: center; +} + +#resetBtn { + color: #ff6666; + padding: 6px 12px; + font-size: 0.85em; +} + +#generateRunCard { + padding: 6px 12px; + font-size: 0.85em; + margin-top: 0.5em; +} + +#autoRollBtn { + position: fixed; + bottom: 16px; + right: 16px; + padding: 6px 10px; + font-size: 0.85em; + opacity: 0.5; + z-index: 9999; + background: var(--bg-color); + border: 1px solid var(--border-color); +} + +#autoRollBtn:hover { + opacity: 0.8; +} + +#autoRollBtn.active { + opacity: 1; + border-color: var(--border-color); +} + +small.helper { + color: var(--text-color); + opacity: 0.6; + display: block; + margin-top: 0.2rem; + font-size: 0.85em; +} + +hr.sep { + border: none; + border-top: 1px solid var(--border-color); + margin: 0.8rem 0; +} + +#playtimeDisplay { + margin-top: 1.5rem; + font-size: 0.9em; + color: var(--text-color); + opacity: 0.5; + font-style: italic; +} + +#devOverlayPanel { + position: fixed; + top: 10px; + right: 10px; + background: var(--panel-bg); + color: var(--text-color); + padding: 12px; + border-radius: 3px; + border: 1px solid var(--border-color); + font-size: 0.75em; + z-index: 99999; + display: none; + font-family: 'Segoe UI', monospace; + max-width: 450px; + max-height: 85vh; + overflow-y: auto; + line-height: 1.4; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); +} + +#devOverlayPanel::-webkit-scrollbar { + width: 6px; +} + +#devOverlayPanel::-webkit-scrollbar-track { + background: var(--overlay-bg); +} + +#devOverlayPanel::-webkit-scrollbar-thumb { + background: var(--border-color); + border-radius: 3px; +} + +.dev-section { + margin-bottom: 10px; + padding-bottom: 10px; + border-bottom: 1px solid var(--border-color); +} + +.dev-section:last-child { + border-bottom: none; + margin-bottom: 0; + padding-bottom: 0; +} + +.dev-header { + color: var(--text-color); + font-weight: bold; + font-size: 1em; + margin-bottom: 6px; + opacity: 0.9; +} + +.dev-label { + color: var(--text-color); + opacity: 0.7; + font-weight: normal; +} + +.dev-value { + color: var(--text-color); + opacity: 0.9; +} + +.dev-warning { + color: #ffaa66; +} + +.dev-error { + color: #ff6666; +} + +.dev-success { + color: #66ff66; +} + +.dev-key { + color: var(--text-color); + opacity: 0.6; +} + +.dev-dom-tag { + color: var(--text-color); + opacity: 0.8; +} + +#devOverlayToggle { + position: fixed; + top: 50px; + right: 10px; + background: var(--button-bg); + color: var(--button-text); + border: 1px solid var(--border-color); + padding: 6px 12px; + font-family: monospace; + font-size: 0.85em; + cursor: pointer; + border-radius: 2px; + z-index: 100000; + display: none; +} + +#devOverlayPanel.collapsed { + display: none !important; +} + +#seasonCanvas { + position: fixed; + top: 0; + left: 0; + width: 100vw; + height: 100vh; + pointer-events: none; + z-index: 1; +} + +#fullscreenPopup { + display: none; + position: fixed; + top: 0; + left: 0; + width: 100vw; + height: 100vh; + background: url('https://files.ozel.im/td32j.png') no-repeat center center; + background-size: cover; + z-index: 9999; +} + +#custom-cursor { + position: fixed; + top: 0; + left: 0; + width: 24px; + height: 24px; + pointer-events: none; + transform: translate(-50%, -50%); + z-index: 999999; + mix-blend-mode: difference; + transition: + transform 0.148s cubic-bezier(0.68, -0.55, 0.265, 1.55), + box-shadow 0.3s ease-out; + border-radius: 50%; + display: block !important; +} + +#custom-cursor.glow { + box-shadow: 0 0 8px 5px rgba(220, 220, 220, 0.25); +} + +#custom-cursor .circle { + position: absolute; + top: 50%; + left: 50%; + width: 24px; + height: 24px; + border: 1.5px solid rgba(220, 220, 220, 0.3); + border-radius: 50%; + transform: translate(-50%, -50%); +} + +#custom-cursor .dot { + position: absolute; + top: 50%; + left: 50%; + width: 6px; + height: 6px; + background: #dcdcdc; + border-radius: 50%; + transform: translate(-50%, -50%); +} + +#custom-cursor .plus { + position: absolute; + top: 50%; + left: 50%; + width: 14px; + height: 14px; + transform: translate(-50%, -50%); + transition: transform 0.148s ease; +} + +#custom-cursor .plus::before, +#custom-cursor .plus::after { + content: ''; + position: absolute; + background: rgba(220, 220, 220, 0.5); +} + +#custom-cursor .plus::before { + left: 50%; + top: 0; + width: 2px; + height: 100%; + transform: translateX(-50%); + border-radius: 1px; +} + +#custom-cursor .plus::after { + top: 50%; + left: 0; + width: 100%; + height: 2px; + transform: translateY(-50%); + border-radius: 1px; +} + +.rgb-link { + font-weight: normal; + animation: rgb 2s linear infinite; +} + +#interaction-lightup { + position: fixed; + inset: 0; + pointer-events: none; + background: rgba(220, 220, 220, 0.02); + opacity: 0; + transition: opacity 0.21s ease; + z-index: 0; +} + +@media (pointer: coarse) { + #custom-cursor { + display: none; + } + + body, + button { + cursor: auto !important; + } +} + +@media (max-width: 600px) { + body { + padding: 20px; + font-size: 14px; + } + + #rollBtn { + padding: 10px 18px; + font-size: 1em; + } + + #settingsPanel { + width: calc(100vw - 20px); + } + + #settingsTooltip { + width: calc(100vw - 40px); + right: 20px; + } +} + +noscript div { + background: var(--panel-bg); + color: #ff8888; + padding: 20px; + text-align: center; + border: 1px solid var(--border-color); + border-radius: 3px; + margin: 20px; +} + +.image-column { + display: flex; + flex-direction: row; + flex-wrap: wrap; + gap: 8px; + align-items: center; + justify-content: center; + max-width: 540px; + margin: 0 auto; +} + +.image-link { + position: relative; + display: inline-block; + transition: all 0.2s ease; + text-decoration: none; + flex-shrink: 0; +} + +.link-text { + color: var(--text-color); + font-size: 13px; + padding: 6px 12px; + border-radius: 3px; + background: var(--button-bg); + border: 1px solid var(--border-color); + transition: all 0.2s ease; + display: block; + white-space: nowrap; + font-family: monospace; + font-weight: normal; + letter-spacing: 0.02em; + text-transform: lowercase; +} + +.image-link:nth-child(2) .link-text { + animation: rgb 2s linear infinite; + border-color: transparent; + background: var(--panel-bg); +} + +@keyframes rgb { + 0% { + color: #ff6666; + } + 16% { + color: #ffaa66; + } + 33% { + color: #ffff66; + } + 50% { + color: #66ff66; + } + 66% { + color: #66ffff; + } + 83% { + color: #ff66ff; + } + 100% { + color: #ff6666; + } +} + +.image-link:hover { + transform: translateY(-2px); +} + +.image-link:hover .link-text { + background: var(--button-hover); + border-color: var(--text-color); + opacity: 1; +} + +.image-link:nth-child(2):hover .link-text { + box-shadow: 0 0 12px rgba(255, 255, 255, 0.2); +} + +.tooltip { + display: none; +} + +@media (max-width: 600px) { + .image-column { + gap: 6px; + padding: 0 10px; + } + + .link-text { + font-size: 12px; + padding: 5px 10px; + } +} + +@media (max-width: 400px) { + .image-column { + gap: 5px; + } + + .link-text { + font-size: 11px; + padding: 4px 8px; + } +} + +@media (hover: none) and (pointer: coarse) { + .image-link { + min-width: 44px; + min-height: 44px; + display: flex; + align-items: center; + justify-content: center; + } + + .link-text { + padding: 8px 12px; + } + + .image-link:hover { + transform: none; + } + + .image-link:active .link-text { + background: var(--button-hover); + transform: scale(0.97); + } +} + +[data-theme='white'] .image-link:nth-child(2) .link-text { + background: rgba(0, 0, 0, 0.03); +} + +[data-theme='white'] .image-link:hover .link-text { + background: rgba(0, 0, 0, 0.05); +} + +.image-column + br, +br + .image-column { + display: block; + margin: 8px 0; +} + +#pointsDisplay { + background: var(--panel-bg); + border: 1px solid var(--border-color); + padding: 12px; + border-radius: 3px; + margin-top: 20px; + font-size: 1.1em; + text-align: center; + color: var(--text-color); +} + +#shopBtn { + position: fixed; + top: 10px; + left: 10px; + background: var(--button-bg); + padding: 6px 12px; + border-radius: 2px; + z-index: 9999; + font-size: 0.85em; +} + +#shopPanel { + position: fixed; + top: 60px; + left: 10px; + background: var(--panel-bg); + padding: 16px; + border: 1px solid var(--border-color); + border-radius: 3px; + width: 320px; + max-height: 560px; + overflow: auto; + transform: translateY(-20px); + opacity: 0; + pointer-events: none; + transition: 0.25s ease; + z-index: 9998; + font-size: 0.9em; +} + +#shopPanel.open { + transform: translateY(0); + opacity: 1; + pointer-events: auto; +} + +.shop-item { + background: var(--overlay-bg); + border: 1px solid var(--border-color); + padding: 16px; + margin-bottom: 16px; + border-radius: 2px; + width: 100%; +} + +.shop-item-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 8px; +} + +.shop-item-name { + font-size: 1em; + font-weight: bold; + color: var(--text-color); +} + +.shop-item-level { + font-size: 0.85em; + opacity: 0.7; + color: var(--text-color); +} + +.shop-item-desc { + font-size: 0.85em; + opacity: 0.7; + margin-bottom: 8px; + color: var(--text-color); +} + +.shop-item-footer { + display: flex; + justify-content: space-between; + align-items: center; +} + +.shop-item-cost { + font-size: 0.9em; + color: #ffb86b; +} + +#inventoryList li.sold-out { + opacity: 0.6; + position: relative; +} + +#inventoryList li.sold-out::after { + content: 'sold'; + position: absolute; + right: 8px; + top: 50%; + transform: translateY(-50%); + font-size: 0.75em; + color: #4a4; + font-weight: bold; +} + +#confirmModal { + display: none; + position: fixed; + inset: 0; + background: rgba(0, 0, 0, 0.85); + z-index: 10000; + align-items: center; + justify-content: center; +} + +.modal-content { + background: var(--panel-bg); + border: 2px solid var(--border-color); + padding: 24px; + border-radius: 4px; + max-width: 400px; + text-align: center; + color: var(--text-color); +} + +.modal-buttons { + display: flex; + gap: 12px; + margin-top: 20px; + justify-content: center; +} + +#luckDisplay { + max-width: 540px; + width: 100%; + margin-top: 10px; + padding: 10px 12px; + background: var(--panel-bg); + border: 1px solid var(--border-color); + border-radius: 2px; + text-align: center; +} + +#luckMultiplier { + margin: 0 0 0.3em 0; + font-size: 0.95em; + opacity: 0.8; + color: var(--text-color); +} + +#luckBreakdown { + margin: 0; + font-size: 0.8em; + opacity: 0.6; + color: var(--text-color); + line-height: 1.5; +} + +#cutsceneOverlay { + display: none; + position: fixed; + inset: 0; + background: #000; + z-index: 99999; + opacity: 0; + transition: opacity 0.5s ease; +} + +#cutsceneOverlay.active { + display: block; + opacity: 1; +} + +#cutsceneOverlay.fadeout { + opacity: 0; +} + +#cutsceneVideo { + width: 100%; + height: 100%; + object-fit: contain; +} + +.page-container { + display: flex; + width: 100%; + transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1); + will-change: transform; + align-items: flex-start; + overflow: visible; +} + +.page { + min-width: 100%; + width: 100%; + flex-shrink: 0; + display: flex; + flex-direction: column; + align-items: center; + padding: 40px 20px 120px 20px; + min-height: 100vh; +} + +.page-wrapper { + width: 100%; + max-width: 540px; + overflow: hidden; + position: relative; + margin: 0 auto; + min-height: 100vh; +} + +.page-dots { + display: flex; + gap: 8px; + justify-content: center; + margin: 20px 0; + position: fixed; + bottom: 20px; + left: 50%; + transform: translateX(-50%); + z-index: 9997; + padding: 8px 16px; + background: var(--panel-bg); + border: 1px solid var(--border-color); + border-radius: 20px; +} + +.page-dot { + width: 8px; + height: 8px; + border-radius: 50%; + background: var(--border-color); + transition: all 0.3s ease; + cursor: pointer; +} + +.page-dot.active { + background: var(--text-color); + width: 24px; + border-radius: 4px; +} + +.page-dot.flash-red { + animation: flash-red 0.3s ease; +} + +@keyframes flash-red { + 0%, + 100% { + background: var(--text-color); + } + 50% { + background: #ff4444; + box-shadow: 0 0 8px #ff4444; + } +} + +.page-arrow { + position: fixed; + top: 50%; + transform: translateY(-50%); + background: none; + border: none; + color: var(--text-color); + font-size: 2rem; + opacity: 0.6; + transition: opacity 0.2s; + padding: 20px; + z-index: 9996; + cursor: pointer; +} + +.page-arrow:hover { + opacity: 1; +} + +.page-arrow.left { + left: 10px; +} + +.page-arrow.right { + right: 10px; +} + +.page-arrow:disabled { + opacity: 0.1; + pointer-events: none; +} + +.swipe-indicator { + position: fixed; + top: 20px; + left: 50%; + transform: translateX(-50%); + background: var(--panel-bg); + border: 1px solid var(--border-color); + padding: 6px 16px; + border-radius: 20px; + font-size: 0.8em; + opacity: 0; + pointer-events: none; + transition: opacity 0.3s; + z-index: 9995; +} + +.swipe-indicator.show { + opacity: 0.6; +} + +@media (min-width: 768px) { + .swipe-indicator { + display: none; + } +} + +@media (max-width: 767px) { + .page-arrow { + display: none; + } + + #autoRollBtn { + bottom: 90px; + } +} + +#indexBtn { + padding: 4px 10px; + font-size: 0.85em; + margin-left: 8px; +} + +#indexModal { + display: none; + position: fixed; + inset: 0; + background: rgba(0, 0, 0, 0.9); + z-index: 10000; + align-items: center; + justify-content: center; +} + +#indexModal.show { + display: flex; +} + +.index-content { + background: var(--panel-bg); + border: 2px solid var(--border-color); + padding: 24px; + border-radius: 4px; + max-width: 600px; + width: 90%; + max-height: 80vh; + overflow-y: auto; + color: var(--text-color); +} + +.index-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 20px; + padding-bottom: 12px; + border-bottom: 1px solid var(--border-color); +} + +.index-close { + background: none; + border: none; + color: var(--text-color); + font-size: 24px; + cursor: pointer; + padding: 0; + width: 30px; + height: 30px; + opacity: 0.7; + transition: opacity 0.2s; +} + +.index-close:hover { + opacity: 1; +} + +.index-stats { + font-size: 0.9em; + opacity: 0.8; + margin-bottom: 20px; + text-align: center; +} + +.index-list { + display: flex; + flex-direction: column; + gap: 8px; +} + +.index-item { + padding: 12px 16px; + background: var(--overlay-bg); + border: 1px solid var(--border-color); + border-radius: 3px; + display: flex; + justify-content: space-between; + align-items: center; + transition: all 0.2s; +} + +.index-item.locked { + opacity: 0.3; + background: var(--bg-color); +} + +.index-item.unlocked { + opacity: 1; + border-left: 3px solid var(--achievement-border); +} + +.index-item.unlocked:hover { + background: var(--button-hover); + transform: translateX(4px); +} + +.index-item-name { + font-size: 0.95em; + font-weight: normal; +} + +.index-item-chance { + font-size: 0.85em; + opacity: 0.7; + font-family: monospace; +} + +.index-item.locked .index-item-name { + color: transparent; + text-shadow: 0 0 8px rgba(220, 220, 220, 0.5); + user-select: none; +} + +.index-item-count { + font-size: 0.85em; + color: var(--achievement-border); + margin-left: 12px; +} + +.index-search { + width: 100%; + padding: 10px 12px; + margin-bottom: 16px; + background: var(--input-bg); + border: 1px solid var(--border-color); + border-radius: 3px; + color: var(--text-color); + font-family: monospace; + font-size: 0.9em; +} + +.index-search::placeholder { + opacity: 0.5; +} + +.index-search:focus { + outline: none; + border-color: var(--text-color); +} + +.well-container { + max-width: 500px; + width: 100%; + margin: 20px auto; + padding: 24px; + background: var(--panel-bg); + border: 1px solid var(--border-color); + border-radius: 3px; + text-align: center; +} + +.well-title { + font-size: 1.2em; + margin-bottom: 8px; + color: var(--text-color); +} + +.well-subtitle { + font-size: 0.85em; + opacity: 0.6; + margin-bottom: 20px; + color: var(--text-color); +} + +.well-visual { + width: 120px; + height: 120px; + margin: 0 auto 20px; + background: var(--overlay-bg); + border: 2px solid var(--border-color); + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + font-size: 3em; + position: relative; + overflow: hidden; +} + +.well-ripple { + position: absolute; + border-radius: 50%; + border: 2px solid var(--text-color); + opacity: 0; + animation: ripple 1.5s ease-out; +} + +@keyframes ripple { + 0% { + width: 0; + height: 0; + opacity: 0.6; + } + 100% { + width: 200px; + height: 200px; + opacity: 0; + } +} + +.well-input-container { + margin: 20px 0; +} + +.well-input { + width: 100%; + padding: 12px; + font-size: 1.1em; + text-align: center; + background: var(--input-bg); + border: 1px solid var(--border-color); + border-radius: 3px; + color: var(--text-color); + font-family: monospace; + margin-bottom: 12px; +} + +.well-input:focus { + outline: none; + border-color: var(--text-color); +} + +.well-quick-btns { + display: flex; + gap: 8px; + margin-bottom: 16px; + flex-wrap: wrap; +} + +.well-quick-btn { + flex: 1; + min-width: 80px; + padding: 6px 12px; + font-size: 0.85em; +} + +.well-throw-btn { + width: 100%; + padding: 12px 24px; + font-size: 1em; + margin-bottom: 12px; +} + +.well-throw-btn:disabled { + opacity: 0.4; + cursor: not-allowed; +} + +.well-status { + font-size: 0.9em; + opacity: 0.7; + margin-top: 12px; + min-height: 20px; + color: var(--text-color); +} + +.well-timer { + font-size: 0.9em; + color: #ffaa66; + margin-top: 8px; +} + +.well-stats { + margin-top: 20px; + padding-top: 20px; + border-top: 1px solid var(--border-color); + font-size: 0.85em; + opacity: 0.7; +} + +.well-result { + position: fixed; + top: 50%; + left: 50%; + transform: translate(-50%, -50%) scale(0); + background: var(--panel-bg); + border: 2px solid var(--border-color); + padding: 32px; + border-radius: 8px; + z-index: 10002; + text-align: center; + min-width: 300px; + opacity: 0; + transition: all 0.3s ease; +} + +.well-result.show { + transform: translate(-50%, -50%) scale(1); + opacity: 1; +} + +.well-result-icon { + font-size: 4em; + margin-bottom: 16px; +} + +.well-result-text { + font-size: 1.2em; + margin-bottom: 8px; + color: var(--text-color); +} + +.well-result-amount { + font-size: 1.5em; + color: #4a4; + font-weight: bold; + margin-bottom: 16px; +} + +.well-result-close { + padding: 8px 24px; +} + +/* Legacy Mode Styles */ +body.legacy-mode .page-wrapper { + overflow: visible !important; + height: auto !important; +} + +body.legacy-mode .page-container { + transform: none !important; + display: block !important; + flex-direction: column !important; +} + +body.legacy-mode .page { + min-width: 100% !important; + width: 100% !important; + padding: 20px !important; + min-height: auto !important; +} + +body.legacy-mode .page > h2:first-child { + display: none !important; +} + +body.legacy-mode .page-dots, +body.legacy-mode .page-arrow, +body.legacy-mode .swipe-indicator { + display: none !important; +} + +/* Hide page 2 and 4 in legacy mode since we're using popups */ +body.legacy-mode #page-2, +body.legacy-mode #page-4 { + display: none !important; +} + +/* Legacy mode buttons */ +#legacyShopBtn, +#legacySettingsBtn { + display: none; + position: fixed; + top: 10px; + background: var(--button-bg); + color: var(--button-text); + border: 1px solid var(--border-color); + padding: 6px 12px; + border-radius: 2px; + cursor: pointer; + z-index: 9999; + font-size: 0.85em; +} + +#legacyShopBtn { + left: 10px; +} + +#legacySettingsBtn { + right: 10px; +} + +body.legacy-mode #legacyShopBtn, +body.legacy-mode #legacySettingsBtn { + display: block !important; +} + +/* Legacy mode popups */ +#legacyShopPopup, +#legacySettingsPopup { + position: fixed; + top: 60px; + background: var(--panel-bg); + padding: 16px; + border: 1px solid var(--border-color); + border-radius: 3px; + width: 320px; + max-height: 560px; + overflow: auto; + transform: translateY(-20px); + opacity: 0; + pointer-events: none; + transition: 0.25s ease; + z-index: 9998; + font-size: 0.9em; +} + +#legacyShopPopup { + left: 10px; +} + +#legacySettingsPopup { + right: 10px; +} + +#legacyShopPopup.open, +#legacySettingsPopup.open { + transform: translateY(0); + opacity: 1; + pointer-events: auto; +} + +/* Potion System Styles */ +.potion-section { + margin-top: 20px; + padding-top: 20px; + border-top: 2px solid var(--border-color); +} + +.potion-section-title { + font-size: 1.1em; + font-weight: bold; + margin-bottom: 16px; + text-align: center; + color: var(--text-color); + opacity: 0.9; +} + +.potion-grid { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 12px; + margin-bottom: 16px; +} + +.potion-item { + background: var(--overlay-bg); + border: 1px solid var(--border-color); + padding: 12px; + border-radius: 2px; + text-align: center; + transition: all 0.2s; +} + +.potion-item:hover { + background: var(--button-hover); + border-color: var(--text-color); +} + +.potion-emoji { + font-size: 2em; + margin-bottom: 8px; +} + +.potion-name { + font-size: 0.9em; + font-weight: bold; + margin-bottom: 4px; + color: var(--text-color); +} + +.potion-effect { + font-size: 0.75em; + opacity: 0.7; + margin-bottom: 8px; + color: var(--text-color); +} + +.potion-cost { + font-size: 0.85em; + color: #ffb86b; + margin-bottom: 8px; +} + +.potion-owned { + font-size: 0.8em; + color: #4a4; + margin-bottom: 8px; +} + +.potion-item button { + width: 100%; + padding: 6px 12px; + font-size: 0.8em; +} + +.active-potions-display { + position: fixed; + top: 100px; + right: 10px; + background: var(--panel-bg); + border: 1px solid var(--border-color); + padding: 12px; + border-radius: 3px; + min-width: 200px; + max-width: 250px; + z-index: 9997; + font-size: 0.85em; +} + +.active-potions-title { + font-weight: bold; + margin-bottom: 8px; + opacity: 0.9; + color: var(--text-color); +} + +.active-potion { + padding: 8px; + margin-bottom: 8px; + background: var(--overlay-bg); + border-left: 3px solid #4a4; + border-radius: 2px; +} + +.active-potion-name { + font-weight: bold; + margin-bottom: 4px; + color: var(--text-color); +} + +.active-potion-timer { + font-size: 0.9em; + opacity: 0.7; + color: #ffaa66; +} + +.gauntlet-tier { + background: rgba(255, 255, 255, 0.02); + border: 1px solid #1f1f1f; + border-radius: 4px; + padding: 12px 14px; + margin-bottom: 10px; + transition: opacity 0.2s; +} +.gauntlet-tier[data-tier='global'] { + border-color: #1e2d50; +} +.gauntlet-tier[data-tier='easy'] { + border-color: #1a2e1a; +} +.gauntlet-tier[data-tier='medium'] { + border-color: #1a1a38; +} +.gauntlet-tier[data-tier='hard'] { + border-color: #38181a; +} +.gauntlet-tier[data-tier='insane'] { + border-color: #38183a; +} +.gauntlet-tier[data-tier='godlike'] { + border-color: #38361a; +} +.gauntlet-tier[data-tier='global'] .gauntlet-tier-name { + color: #88aaff; +} +.gauntlet-tier[data-tier='easy'] .gauntlet-tier-name { + color: #88dd88; +} +.gauntlet-tier[data-tier='medium'] .gauntlet-tier-name { + color: #7788ff; +} +.gauntlet-tier[data-tier='hard'] .gauntlet-tier-name { + color: #ff7777; +} +.gauntlet-tier[data-tier='insane'] .gauntlet-tier-name { + color: #dd44ff; +} +.gauntlet-tier[data-tier='godlike'] .gauntlet-tier-name { + color: #ffd700; +} + +.gauntlet-locked { + opacity: 0.42; + filter: saturate(0.4); +} + +.gauntlet-tier-header { + display: flex; + justify-content: space-between; + align-items: flex-start; + margin-bottom: 5px; +} +.gauntlet-tier-name { + font-size: 1em; + font-weight: bold; +} +.gauntlet-global-badge { + display: inline-block; + font-size: 0.65em; + padding: 1px 5px; + background: rgba(60, 100, 210, 0.14); + border: 1px solid rgba(100, 150, 255, 0.3); + color: #88aaff; + border-radius: 2px; + margin-left: 5px; + vertical-align: middle; +} +.gauntlet-tier-meta { + font-size: 0.72em; + opacity: 0.45; + text-align: right; + flex-shrink: 0; + margin-left: 8px; +} +.gauntlet-unlocked-meta { + color: #88cc88; + opacity: 0.85; +} +.gauntlet-rotation-text { + font-size: 0.7em; + opacity: 0.4; + margin-bottom: 6px; +} +.gauntlet-progress-text { + font-size: 0.72em; + opacity: 0.5; + margin: 4px 0 3px; +} +.gauntlet-bar-wrap { + background: rgba(255, 255, 255, 0.05); + border: 1px solid #222; + border-radius: 2px; + height: 4px; + margin-bottom: 8px; + overflow: hidden; +} +.gauntlet-bar { + height: 100%; + background: #668; + transition: width 0.3s ease; + border-radius: 2px; +} +.gauntlet-chip-grid { + display: flex; + flex-wrap: wrap; + gap: 4px; + margin-bottom: 4px; +} +.gauntlet-chip { + font-size: 0.7em; + padding: 3px 6px; + border: 1px solid #2a2a2a; + border-radius: 2px; +} +.chip-has { + border-color: #3a5a3a; + color: #88cc88; + background: rgba(50, 110, 50, 0.1); +} +.chip-missing { + opacity: 0.35; +} +.gauntlet-hr { + border: none; + border-top: 1px solid #1e1e1e; + margin: 9px 0 7px; +} +.gauntlet-reward-label { + font-size: 0.68em; + opacity: 0.38; + text-transform: uppercase; + letter-spacing: 0.05em; + margin-bottom: 5px; +} +.gauntlet-reward-row { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 5px; +} +.gauntlet-rew-btn { + font-family: inherit; + font-size: 0.72em; + padding: 6px 4px; + background: transparent; + border: 1px solid #2a2a2a; + color: inherit; + border-radius: 2px; + cursor: pointer; + text-align: center; + line-height: 1.4; + transition: all 0.12s; +} +.gauntlet-rew-btn:disabled { + opacity: 0.28; + cursor: not-allowed; +} +.gauntlet-rew-btn:not(:disabled):hover { + border-color: #555; + background: rgba(255, 255, 255, 0.04); +} +.gauntlet-rew-btn.rew-ready { + border-color: #3a5a3a; + color: #9ddd9d; + background: rgba(40, 90, 40, 0.1); +} +.gauntlet-rew-btn.rew-ready:not(:disabled):hover { + border-color: #5a8a5a; + background: rgba(50, 110, 50, 0.15); +} +.gauntlet-cd-text { + font-size: 0.68em; + opacity: 0.38; + margin-top: 6px; + font-style: italic; +} +.gauntlet-locked-preview { + font-size: 0.7em; + opacity: 0.22; + margin-top: 6px; + font-style: italic; +}