Make a bitcoin lightning lottery app called "blotto" where you can buy lottery tickets. Each time someone pays, the timer until the game ends increases. GAME STORAGE - The backend must store the game state whenever the state changes. - There should be a current game - There should be a list of previous games - Each game should have an ID, start date, end date, status(waiting, active, or ended), whether the prize pool has been claimed, the winning ticket ID, and the payout invoice - The countdown timer MUST be calculated from the end date minus the start date. - The game ID should be a simple sequential ID starting at 1. - Each game should have a list of tickets - Each ticket should have a public ID, hidden ID, invoice, and whether it was paid - The ticket public ID should be a simple sequential ID starting at 1. - The ticket hidden ID is the FIRST 10 characters of the payment preimage which will be provided by the user to redeem the winnings. - Each game should have a list of donations - Each donation should have an amount in sats, an invoice, and whether it was paid NEW GAME - The game will be in a waiting state until at least 2 tickets are purchased. GAME START - Once at least 2 tickets are purchased, the game will start. - By default the game should start with a countdown of 0 + the time calculation of the number of tickets purchased. - Special case: if exactly 2 tickets are purchased at the time of game start, add another 2 hours to the countdown time. TIME INCREASE - For each ticket purchased (not just on game start), the countdown time should increase by 60 seconds. PAYMENTS - The backend should generate an invoice using NWC and store it on the ticket(s)/donation. - The response of the request must ONLY contain the invoice. - While the user is waiting for the payment to be confirmed, a loading indicator should show. - Bitcoin Connect payment dialog must be used to make the payment. - the frontend must poll to the backend to verify the invoice was paid, by passing only the invoice in the request. - make sure to update the state of bitcoin connect payment dialog once the backend returns confirmation that the payment was made (the backend must return the preimage) - the backend MUST verify the payment was made by looking up up the invoice and MUST check that the preimage is non-empty, only then update the ticket(s)/donation. - if the backend receives a request to verify the payment after the ticket(s)/donation has already been marked as paid, do not process it again (e.g. DO NOT alter the prize pool, DO NOT post new nostr events), but still return a success response with the necessary information (for verifying tickets purchases, make sure to return the same receipt data as below). TICKET PURCHASING - On the homepage there should be a single "Buy tickets" button. When clicked it will open a purchase modal. - The purchase modal should show: - an input to choose number of tickets - the default value should be 10 tickets - the max property should be 10000 - make sure the user can clear the input completely and type (the onChange handler should set the exact target value, parse the value on submit instead) - the total price - the total amount of time the countdown timer will be increased (based on the time increase calculation above) - When the backend creates the invoice, the invoice description should be: BLOTTO Game # Tickets #-# - When the backend returns a success response that the ticket(s) were paid (from polling), the response should contain receipt data of the payment: - the game ID, - from public ID - to public ID, - hidden ID - Once the frontend has the receipt data it should show the following content in a modal: - The receipt data (formatted nicely and human readable). - A button to download the receipt, which should be a different color and should flash until the user clicks it. - The filename of the receipt should be "BLOTTO__-.txt" and the contents should be the hidden ID. - text telling them to screenshot the page (in bold). - Once the modal is closed make sure to reset any state so that the user can buy more tickets if they want to. PRIZE POOL - The prize pool / winning amount should be the rounded down value of the total number of paid tickets * the ticket price * 0.95 + sum of paid donations * 0.99. DONATING TO THE PRIZE POOL - If the game is not ended there should be a link to donate to the prize pool (styled similar to "View previous games" link), which will open a similar modal as buying tickets. - The user should be able to type the value in sats that they want to donate to the prize pool. FETCHING BACKEND GAME STATE - When someone is on the page, every 10 seconds it should poll to get the latest state of the game. - The game state MUST NOT include tickets or hidden IDs. It should return the summed prize pool, total number of tickets sold, countdown time remaining (0 if the game is not active), and status(waiting, active, or ended). MAIN PAGE DISPLAY - The title should show on the main page. - The app version should be shown on the page next to the title, but in a much smaller font and should be styled differently. The current version is V2. - The app description should be shown under the title: "Lightning Lottery - 21 sats per ticket" and "each ticket purchased increases countdown timer" (both on separate lines, second line smaller text. Include a relevant emoji in front of each sentence) - The countdown timer should show on the main page. IMPORTANT: if the game is STARTED, it must locally decrease by 1 every second to show a smooth countdown. This MUST be done without requiring to do another API call to get the state. - The amount of PAID tickets and the prize pool should show on the main page. - Whenever the latest polled state has a different (increased) countdown timer than the previous state, and the page has been open for more than 20 seconds, there should be a "timeout increased!" notification next to the countdown timer. This MUST be completely separate from the game state fetching code to avoid accidentally triggering another fetch. WAITING DISPLAY - If less than 2 tickets have been purchased, display "The game will start once 2 tickets are purchased". - Still show the countdown timer, but have it show "--:--:--" GAME END - The backend should choose a winning ticket randomly from the PAID tickets in the current game. - If the countdown hits 0, the game ends and there should be an animated text on top of all other elements: "GAME ENDED!", and below 'Click on "View previous games" to view winning ticket and claim prize' - The game should be reset so players can buy tickets again. VIEWING PREVIOUS GAMES - There should be a link (in a small font, on its own row) to view previous games which will open in a modal: (Game ID, Date started, Date ended, number tickets sold + Prize pool in sats + winning ticket public ID + whether the ticket was claimed or not, and a button to claim the prize for that game if not claimed + the time remaining to claim the prize) CLAIMING PRIZE - Allow users to either paste their game ID and hidden ID in text inputs, or attach their ticket file they previously downloaded (using an input field - which should also read the ticket filename and assign the Game ID field). - They also need to provide their lightning address to claim the prize. - If the Game ID and hidden ID matches the winner for that game: - if the game's payout_invoice is NULL, use the provided lightning address and lightning tools to fetch an invoice for the prize pool amount. - set the payout invoice for the game only if it is not set (e.g. `where id = gameId and payout_invoice is NULL`) - get the payout invoice from the DB to ensure we are using the original one (to avoid double payouts) - the backend must use Nostr Wallet Connect to pay the payout invoice. - Do not allow the user to claim the prize if it already has been claimed. - Only allow prizes to be claimed up to 24 hours after the game ended. FOLLOW ON NOSTR - There should be a link (in a small font, on its own row) which opens https://njump.me/npub1gcevkl0nz0t2wxc4pqlmudveqsu2l36f463zw6m9jr3j9d5n6esqur6c2m in a new tab GAME STYLE - Style the game with a fun, lightning lottery theme. - Make sure the content is vertically centered if it doesn't full the entire height of the screen. Make sure elements are HORIZONTALLY centered as well. - Countdown should have a white border around it. Make the box wider with bigger font than the tickets sold and prize pool and use a large letter spacing. - If the game is active and the countdown time is less than 30 seconds, make the text flash. - If the game is active and the countdown time is less than two minutes, use a red text color. - If the game is active and the countdown time is less than one hour, use an orange text color. - If the countdown time is bigger than one hour or the game is waiting, use a white text color. - Tickets sold, prize pool should be on the same row and have white borders around them. - The small links should be white with 0.8 alpha - The buy tickets button should be green - The background should be a gradient purple - Do not use dark backgrounds for modals - Do not use alert dialogs for success. Use positive toast / notification messages instead NOSTR EVENTS - The backend should post notes to Nostr when the following events happen. - All events should have a link at the bottom to "Join the game at https://lnfly.albylabs.com/api/apps/199/view" - Discard posting if the last nostr event was less than 5 seconds ago. NOSTR EVENT LIST - When a new game starts after at least two tickets purchased (include game ID) - When a game ends (include game ID, number of tickets sold, prize pool, winning ticket number) - When one or more tickets were purchased (include number of tickets purchased, total number of paid tickets, new prize pool amount, new total countdown timeout in HH:MM:SS). If the number of purchased tickets hits (10, 100, 1000, 10000, 100000) announce that as part of the message. If >= 10 (shrimp), 100 (dolphin), 1000 (shark), 10000 (whale) tickets are purchased note that this purchaser is this type of bitcoiner. e.g. if 5000 tickets were bought, then this purchaser is a shark. - When the prize was claimed (winning ticket number, total prize pool) - When a new donation is added to the prize pool (include the donation amount * 0.99). Specifically mention that it's a donation. NOSTR EVENT AI CONTENT - The message content should attempt to be modified using AI (if not, just fall back to the current message). - Include the same info but the content should have some uniqueness and personality. - Use the style of the enthusiastic announcer AI of dungeon crawler carl doing a global announcement - Keep the response to less than 400 characters - Do not actually reference crawlers since this is a lottery game called blotto. REACT - The frontend should use React rather than plain HTML.