微件:GGLScratchGame

来自Limbo Wiki Mirror
Gaoice留言 | 贡献2026年2月1日 (日) 16:44的版本
     
     
   <button class="sl-open-btn">来一张彩票</button>
     Error: src attribute required.
   <button class="sl-mascot">(◕‿◕) …</button>
   <button class="sl-scan">扫描结果</button>

<style> .scratch-lottery-root {

 max-width: 520px;
 margin: 2em auto;
 font-family: system-ui, sans-serif;
 user-select: none;

} .sl-header {

 display: flex;
 justify-content: space-between;
 align-items: center;
 margin-bottom: .5em;

} .sl-open-btn { padding: .4em .8em; } .sl-stage { position: relative; } .sl-canvas { position: relative; width: 100%; } .sl-bg { width: 100%; display: block; } .sl-area { position: absolute; } .sl-tile {

 position: absolute;
 width: 169px;
 height: 169px;
 cursor: pointer;
 outline: none;

} .sl-cover img, .sl-reveal img {

 width: 100%;
 height: 100%;
 object-fit: cover;

} .sl-bubble {

 position: absolute;
 background: #fff;
 border: 1px solid #000;
 padding: .4em .6em;
 font-size: 13px;
 z-index: 50;

} .sl-controls {

 display: flex;
 margin-top: .5em;

} .sl-controls button {

 flex: 1;
 padding: .6em;

} .sl-scan.highlight {

 background: #ffe600;
 animation: pulse 1s infinite;

} @keyframes pulse {

 from { box-shadow: 0 0 0 rgba(255,230,0,.8); }
 to { box-shadow: 0 0 12px rgba(255,230,0,0); }

} .sl-invert {

 filter: invert(1) hue-rotate(180deg);

} .sl-debug {

 font-size: 11px;
 color: #666;
 margin-bottom: .5em;

} </style>

<script> (function () {

 const root = document.currentScript.parentElement;
 const cfg = JSON.parse(root.dataset.config || '{}');
 const bg = root.querySelector('.sl-bg');
 const area = root.querySelector('.sl-area');
 const mascot = root.querySelector('.sl-mascot');
 const scanBtn = root.querySelector('.sl-scan');
 const openBtn = root.querySelector('.sl-open-btn');
 const debugBox = root.querySelector('.sl-debug');
 const tickets = cfg.tickets || [];
 const states = cfg.states || {};
 let ticketIndex = 0;
 let playerTickets = cfg.initialTickets || 3;
 let currentTicket = null;
 let revealed = new Set();
 bg.src = cfg.config.bg;
 function layout() {
   const scale = bg.clientWidth / 1075;
   area.style.left = (cfg.config.area.x * scale) + 'px';
   area.style.top = (cfg.config.area.y * scale) + 'px';
   area.style.width = (cfg.config.area.width * scale) + 'px';
   area.style.height = (cfg.config.area.height * scale) + 'px';
 }
 bg.onload = layout;
 window.addEventListener('resize', layout);
 function updateInfo() {
   root.querySelector('.sl-ticket-count').textContent = '🎟️ 票数:' + playerTickets;
   root.querySelector('.sl-ticket-index').textContent =
     currentTicket ? '当前票:' + currentTicket.id : '当前票:-';
   if (cfg.debug) {
     debugBox.textContent =
       'ticketIndex=' + ticketIndex +
       ' revealed=' + revealed.size +
       ' playerTickets=' + playerTickets;
   }
 }
 function openTicket() {
   if (playerTickets <= 0 || ticketIndex >= tickets.length) return;
   currentTicket = tickets[ticketIndex++];
   playerTickets--;
   revealed.clear();
   scanBtn.classList.remove('highlight');
   mascot.style.visibility = 'visible';
   renderTiles();
   updateInfo();
 }
 function renderTiles() {
   area.innerHTML = ;
   currentTicket.outcomes.forEach((stateId, i) => {
     const tile = document.createElement('div');
     tile.className = 'sl-tile';
     tile.tabIndex = 0;
     tile.setAttribute('aria-label', '刮奖格子 ' + (i + 1));
     const col = i % cfg.config.cols;
     const row = Math.floor(i / cfg.config.cols);
     tile.style.left = (col * cfg.config.tileSize) + 'px';
     tile.style.top = (row * cfg.config.tileSize) + 'px';
     const cover = document.createElement('div');
     cover.className = 'sl-cover';
     const coverImg = document.createElement('img');
     coverImg.src = cfg.config.cover;
     cover.appendChild(coverImg);
     tile.appendChild(cover);
     tile.onclick = () => revealTile(i, tile);
     tile.onkeydown = e => {
       if (e.key === 'Enter' || e.key === ' ') revealTile(i, tile);
     };
     area.appendChild(tile);
   });
 }
 function revealTile(i, tile) {
   if (revealed.has(i)) return;
   revealed.add(i);
   tile.innerHTML = ;
   const reveal = document.createElement('div');
   reveal.className = 'sl-reveal';
   const img = document.createElement('img');
   img.src = cfg.config.reveal;
   reveal.appendChild(img);
   tile.appendChild(reveal);
   const state = states[currentTicket.outcomes[i]];
   showBubble(tile, state?.text || );
   handleState(currentTicket.outcomes[i]);
   checkCompletion();
 }
 function showBubble(tile, text) {
   if (!text) return;
   const bubble = document.createElement('div');
   bubble.className = 'sl-bubble';
   bubble.textContent = text;
   tile.appendChild(bubble);
   const rect = tile.getBoundingClientRect();
   bubble.style.top = rect.bottom > window.innerHeight - 80 ? '-2.4em' : '100%';
   bubble.style.left = '0';
   setTimeout(() => bubble.remove(), 3000);
 }
 function handleState(id) {
   const s = states[id];
   if (!s) return;
   if (s.type === 'emoji') mascot.textContent = s.text;
   if (id === 6) mascot.style.visibility = 'hidden';
   if (id === 10) scanBtn.classList.add('highlight');
   if (id === 11) endGame();
 }
 function checkCompletion() {
   if (revealed.size === 30 && currentTicket.id === 10) {
     scanBtn.classList.add('highlight');
   }
 }
 function endGame() {
   root.classList.add('sl-invert');
   setTimeout(() => {
     root.style.display = 'none';
     location.hash = '#词条正文';
   }, 1500);
 }
 openBtn.onclick = openTicket;
 scanBtn.onclick = () => {
   if (currentTicket && currentTicket.id === 10) openTicket();
 };
 updateInfo();

})(); </script>