|
|
| 第1行: |
第1行: |
| <noinclude> | | <widget name="GGLScratchGame" /> |
| == 刮刮乐重构版 ==
| |
| 使用方式:修改第 222-224 行的图片URL,然后保存此页面
| |
| 当前配置的图片:
| |
| * 底图(bg):https://wm.gaoice.run/images/thumb/b/b6/%E5%9B%BE%E7%89%871.png/180px-%E5%9B%BE%E7%89%871.png
| |
| * 覆盖图(cover):https://wm.gaoice.run/images/thumb/5/5a/%E5%88%AE%E5%BC%80%E5%89%8D.png/180px-%E5%88%AE%E5%BC%80%E5%89%8D.png
| |
| * 底层图(revealed):https://wm.gaoice.run/images/thumb/4/4a/%E5%88%AE%E5%BC%80%E5%90%8E.jpg/180px-%E5%88%AE%E5%BC%80%E5%90%8E.jpg
| |
| </noinclude>
| |
| | |
| <includeonly>
| |
| <style>
| |
| /* === 核心容器 === */
| |
| .ggl-game-root {
| |
| max-width: 400px; /* 限制最大显示宽度,防止在大屏太巨大 */
| |
| margin: 20px auto;
| |
| font-family: 'Courier New', monospace;
| |
| user-select: none;
| |
| }
| |
| | |
| .game-header {
| |
| text-align: center;
| |
| color: #555;
| |
| margin-bottom: 10px;
| |
| font-weight: bold;
| |
| }
| |
| | |
| /* === 彩票视觉部分 === */
| |
| .ticket-wrapper {
| |
| position: relative;
| |
| width: 100%;
| |
| /* 核心:保持 1075:1911 的宽高比 */
| |
| padding-bottom: 177.76%;
| |
| background-size: 100% 100%;
| |
| background-repeat: no-repeat;
| |
| box-shadow: 0 10px 20px rgba(0,0,0,0.3);
| |
| border-radius: 8px;
| |
| }
| |
| | |
| /* === 刮奖区定位 (关键) === */
| |
| .scratch-grid-overlay {
| |
| position: absolute;
| |
| /* 以下百分比是根据 1075x1911 图和 846x1020 区域计算得出 */
| |
| width: 78.7%; /* 846 / 1075 */
| |
| height: 53.4%; /* 1020 / 1911 */
| |
| left: 10.65%; /* (100% - 78.7%) / 2 */
| |
| top: 34%; /* 估算黄色区域距离顶部的距离,可根据实际效果微调 */
| |
|
| |
| display: grid;
| |
| grid-template-columns: repeat(5, 1fr);
| |
| grid-template-rows: repeat(6, 1fr);
| |
| /* 消除格子间隙,因为你的图是连续的 */
| |
| gap: 0;
| |
| }
| |
| | |
| /* === 格子样式 === */
| |
| .grid-cell {
| |
| width: 100%;
| |
| height: 100%;
| |
| background-size: 100% 100%; /* 强制图片拉伸填满格子 */
| |
| background-repeat: no-repeat;
| |
|
| |
| display: flex;
| |
| align-items: center;
| |
| justify-content: center;
| |
| cursor: pointer;
| |
|
| |
| /* 初始文字透明 (刮开前看不到字) */
| |
| color: transparent;
| |
| font-weight: bold;
| |
| font-size: 14px;
| |
| /* 文字描边,防止在复杂背景看不清 */
| |
| text-shadow: 1px 1px 0 #fff, -1px -1px 0 #fff;
| |
| }
| |
| | |
| /* 刮开后的状态 */
| |
| .grid-cell.revealed {
| |
| cursor: default;
| |
| color: #d32f2f; /* 刮开后文字颜色 */
| |
| }
| |
| | |
| /* === 底部控制区 (重写布局) === */
| |
| .controls-area {
| |
| margin-top: 15px;
| |
| display: flex;
| |
| justify-content: space-between; /* 左右分布 */
| |
| align-items: flex-end;
| |
| }
| |
| | |
| /* 左侧:气泡+颜文字 */
| |
| .mascot-box {
| |
| flex: 1;
| |
| text-align: left;
| |
| margin-right: 10px;
| |
| }
| |
| .speech-bubble {
| |
| background: #fff;
| |
| border: 1px solid #333;
| |
| border-radius: 8px 8px 8px 0;
| |
| padding: 8px;
| |
| font-size: 12px;
| |
| margin-bottom: 5px;
| |
| box-shadow: 2px 2px 0 rgba(0,0,0,0.1);
| |
| min-height: 20px;
| |
| line-height: 1.3;
| |
| }
| |
| .mascot-face {
| |
| font-size: 18px;
| |
| font-weight: bold;
| |
| color: #333;
| |
| margin-left: 5px;
| |
| }
| |
| | |
| /* 右侧:按钮 */
| |
| .action-btn {
| |
| padding: 0 20px;
| |
| height: 40px;
| |
| font-size: 14px;
| |
| background: #2196F3;
| |
| color: white;
| |
| border: none;
| |
| border-radius: 5px;
| |
| cursor: pointer;
| |
| box-shadow: 2px 2px 0 #000;
| |
| white-space: nowrap;
| |
| }
| |
| .action-btn:active {
| |
| transform: translate(2px, 2px);
| |
| box-shadow: none;
| |
| }
| |
| .action-btn:disabled {
| |
| background: #ccc;
| |
| box-shadow: none;
| |
| }
| |
| .action-btn.next { background: #4CAF50; }
| |
| .action-btn.danger { background: #000; color: red; border: 1px solid red; }
| |
| | |
| /* 结局特效 */
| |
| .invert-filter { filter: invert(100%); background: black !important; }
| |
| .crash-screen {
| |
| position: fixed; top: 0; left: 0; width: 100%; height: 100%;
| |
| background: black; color: red; z-index: 99999;
| |
| display: flex; align-items: center; justify-content: center;
| |
| font-size: 30px;
| |
| }
| |
| </style>
| |
| | |
| <!-- HTML 结构 -->
| |
| <!-- WIDGET_PARAMS: bg={{{bg}}}, cover={{{cover}}}, revealed={{{revealed}}} -->
| |
| <div class="ggl-game-root" id="ggl-root">
| |
| <div class="game-header">
| |
| GGL-LOTTERY | 剩余: <span id="count-val">3</span>
| |
| </div>
| |
| | |
| <!-- 1. 彩票底图层 -->
| |
| <!-- 用 data-* 属性存放参数,避免style属性中的模板解析失败 -->
| |
| <div class="ticket-wrapper" id="ticket-bg" data-bg="{{{bg|图片1.png}}}" data-cover="{{{cover|刮开前.png}}}" data-revealed="{{{revealed|刮开后.jpg}}}">
| |
|
| |
| <!-- 2. 刮奖区覆盖层 (绝对定位) -->
| |
| <div class="scratch-grid-overlay" id="scratch-grid">
| |
| <!-- JS 生成格子 -->
| |
| </div>
| |
| | |
| <!-- ID 显示 (可选,定位在右上角或哪里,这里为了简洁先隐藏或微调) -->
| |
| <div style="position: absolute; top: 2%; right: 5%; color: white; font-weight: bold; font-size: 12px;">
| |
| NO.<span id="ticket-id">001</span>
| |
| </div>
| |
| </div>
| |
| | |
| <!-- 3. 底部控制 -->
| |
| <div class="controls-area">
| |
| <div class="mascot-box">
| |
| <div class="speech-bubble" id="mascot-text">请刮开所有区域...</div>
| |
| <div class="mascot-face" id="mascot-face">(・ω・)</div>
| |
| </div>
| |
| <button id="main-btn" class="action-btn">扫描结果</button>
| |
| </div>
| |
| </div>
| |
| | |
| <!-- 伪词条 -->
| |
| <div id="fake-wiki-entry" style="display:none; padding:20px; background:#fff; border:1px solid #ccc; max-width:800px; margin:20px auto;">
| |
| <h1 style="border-bottom: 1px solid #aaa;">六世恶言</h1>
| |
| <div style="margin-top:10px;">
| |
| <p><b>描述:</b>“六世恶言”是一种具有高度认知危害的网络异常...</p>
| |
| <hr>
| |
| <p><b>收藏品3:</b> [回复] <b>“它是我藏在赛博刮刮乐内的,是被惩罚的人。”</b></p>
| |
| </div>
| |
| </div>
| |
| | |
| <script>
| |
| (function() {
| |
| var root = document.getElementById('ggl-root');
| |
| if (!root) return;
| |
| | |
| // ===== 参数配置 =====
| |
| // 直接在这里修改图片URL
| |
| var imgBg = 'https://wm.gaoice.run/images/thumb/b/b6/%E5%9B%BE%E7%89%871.png/180px-%E5%9B%BE%E7%89%871.png';
| |
| var imgCover = 'https://wm.gaoice.run/images/thumb/5/5a/%E5%88%AE%E5%BC%80%E5%89%8D.png/180px-%E5%88%AE%E5%BC%80%E5%89%8D.png';
| |
| var imgRevealed = 'https://wm.gaoice.run/images/thumb/4/4a/%E5%88%AE%E5%BC%80%E5%90%8E.jpg/180px-%E5%88%AE%E5%BC%80%E5%90%8E.jpg';
| |
|
| |
| // 调试:打印最终获取的参数
| |
| console.log('Widget参数:', { imgBg, imgCover, imgRevealed });
| |
|
| |
| // 应用背景图
| |
| var elTicketBg = document.getElementById('ticket-bg');
| |
| if (elTicketBg && imgBg) {
| |
| elTicketBg.style.backgroundImage = "url('" + imgBg + "')";
| |
| }
| |
| var TOTAL_CARDS = 11;
| |
| var currentIdx = 0;
| |
| var remain = 3;
| |
| var isScanned = false;
| |
| var revealedCount = 0;
| |
| | |
| // DOM
| |
| var elCount = root.querySelector('#count-val');
| |
| var elGrid = root.querySelector('#scratch-grid');
| |
| var elBtn = root.querySelector('#main-btn');
| |
| var elBubble = root.querySelector('#mascot-text');
| |
| var elFace = root.querySelector('#mascot-face');
| |
| var elId = root.querySelector('#ticket-id');
| |
| var elFakeEntry = document.getElementById('fake-wiki-entry');
| |
| | |
| // 数据
| |
| var cards = [
| |
| { type: 'n', face: '', text: '结果:谢谢惠顾。' },
| |
| { type: 'n', face: '(・ω・)', text: '检测到异常字符。' },
| |
| { type: 'b', face: '(^▽^)', text: '恭喜!触发连击,奖励+2张!' },
| |
| { type: 'n', face: 'o(*≧▽≦)ツ', text: '结果:1 Data 2。好耶!' },
| |
| { type: 'c', face: '( ・_・)', text: '结果:中奖...?数据好像变了。' },
| |
| { type: 'x', face: '', text: '对象已消失。', val: 'Data' },
| |
| { type: 'g', face: '...', text: 'ERR_#0x00 数据损坏', val: '???' },
| |
| { type: 'p', face: '', text: '声音检测:"好痛!"', val: 'Pain' },
| |
| { type: 't', face: '', text: '收到文本:"你是谁?"', val: 'Info' },
| |
| { type: 's', face: '', text: '警告:立即停止操作。', val: '停下' },
| |
| { type: 'f', face: 'ERROR', text: '你知道的太多了。', val: '平安喜乐' }
| |
| ];
| |
| | |
| function setMascot(txt, face) {
| |
| if(txt) elBubble.innerText = txt;
| |
| if(face !== undefined) elFace.innerText = face;
| |
| }
| |
| | |
| function randVal() {
| |
| var arr = ['10mb', '50Gb', '5Kb', '1Tb', '福', '寿', '乐', '奖', '空'];
| |
| return arr[Math.floor(Math.random() * arr.length)];
| |
| }
| |
| | |
| function initCard(idx) {
| |
| isScanned = false;
| |
| revealedCount = 0;
| |
| elBtn.innerText = "扫描结果";
| |
| elBtn.className = "action-btn";
| |
| elBtn.disabled = false;
| |
| elBtn.style = "";
| |
| | |
| elId.innerText = (idx+1).toString().padStart(3,'0');
| |
| elCount.innerText = remain;
| |
| | |
| var d = cards[idx];
| |
| elGrid.innerHTML = '';
| |
| setMascot("请刮开所有区域...", d.face);
| |
| | |
| if(d.type === 's') { // Stop
| |
| elBtn.style.border = "2px solid red";
| |
| elBtn.style.color = "red";
| |
| }
| |
| | |
| for(var i=0; i<30; i++) {
| |
| var cell = document.createElement('div');
| |
| cell.className = 'grid-cell';
| |
|
| |
| // 1. 设置刮开前的图 (覆盖层)
| |
| cell.style.backgroundImage = "url('" + imgCover + "')";
| |
| | |
| // 决定内容
| |
| var val = d.val ? d.val : randVal();
| |
| if(idx === 2 && (i===10 || i===11)) val = "大奖";
| |
| if(idx === 8) val = "你是谁";
| |
| | |
| cell.dataset.val = val;
| |
| | |
| // 点击事件
| |
| (function(c, cIdx, cVal, idxI){
| |
| c.addEventListener('click', function() {
| |
| if(c.classList.contains('revealed') || isScanned) return;
| |
|
| |
| // 2. 切换为刮开后的图
| |
| c.classList.add('revealed');
| |
| c.style.backgroundImage = "url('" + imgRevealed + "')";
| |
| c.innerText = cVal; // 显示文字
| |
|
| |
| revealedCount++;
| |
| | |
| // 剧情触发
| |
| if(cIdx === 5) { // 消失
| |
| setMascot(["别点了","我在看你"][Math.floor(Math.random()*2)], "");
| |
| c.innerText = "";
| |
| }
| |
| if(cIdx === 7) { // 痛
| |
| c.innerText = ["痛!","别!"][Math.floor(Math.random()*2)];
| |
| c.style.color = "red";
| |
| setMascot("别挠我!", ">_<");
| |
| }
| |
| if(cIdx === 4 && revealedCount === 15) { // 改字
| |
| setMascot("诶...?", "(;゚д゚)");
| |
| if(elGrid.children[29]) {
| |
| elGrid.children[29].dataset.val = "中奖";
| |
| }
| |
| }
| |
| });
| |
| })(cell, idx, val, i);
| |
| | |
| elGrid.appendChild(cell);
| |
| }
| |
| }
| |
| | |
| elBtn.addEventListener('click', function() {
| |
| if(isScanned) {
| |
| // Next
| |
| currentIdx++;
| |
| if(currentIdx < TOTAL_CARDS) initCard(currentIdx);
| |
| } else {
| |
| // Scan
| |
| if(revealedCount < 30 && currentIdx < 9) {
| |
| if(!confirm("还没刮完,确定扫描吗?")) return;
| |
| }
| |
|
| |
| var d = cards[currentIdx];
| |
| isScanned = true;
| |
| elBtn.innerText = "扫描中...";
| |
|
| |
| setTimeout(function(){
| |
| setMascot(d.text);
| |
|
| |
| if(d.type === 'b') { remain += 2; elCount.innerText = remain; }
| |
| if(d.type === 'f') { doEnd(); return; }
| |
| if(d.type === 's') {
| |
| setTimeout(function(){
| |
| setMascot("严重错误。排出异物...");
| |
| elBtn.innerText = "取出未知票据";
| |
| elBtn.className = "action-btn danger";
| |
| }, 800);
| |
| return;
| |
| }
| |
| | |
| remain--;
| |
| elCount.innerText = remain;
| |
| if(remain < 0) {
| |
| setMascot("票据耗尽。");
| |
| elBtn.disabled = true;
| |
| return;
| |
| }
| |
| elBtn.innerText = "再来一张";
| |
| elBtn.classList.add('next');
| |
| }, 500);
| |
| }
| |
| });
| |
| | |
| function doEnd() {
| |
| document.body.classList.add('invert-filter');
| |
| setTimeout(function(){
| |
| var div = document.createElement('div');
| |
| div.className = 'crash-screen';
| |
| div.innerText = "FATAL ERROR";
| |
| document.body.appendChild(div);
| |
| setTimeout(function(){
| |
| div.remove();
| |
| document.body.classList.remove('invert-filter');
| |
| root.style.display = 'none';
| |
| if(elFakeEntry) elFakeEntry.style.display = 'block';
| |
| window.scrollTo(0,0);
| |
| alert("【系统提示】收到新短信。");
| |
| }, 3000);
| |
| }, 1500);
| |
| }
| |
| | |
| initCard(0);
| |
| })();
| |
| </script>
| |
| </includeonly>
| |