微件:GGLScratchGame:修订间差异
来自Limbo Wiki Mirror
无编辑摘要 标签:(旧)WikiEditor |
无编辑摘要 标签:(旧)WikiEditor |
||
| 第1行: | 第1行: | ||
<noinclude> | <noinclude> | ||
== | == 刮刮乐重构版 == | ||
调用方式: | |||
<pre> | <pre> | ||
{{#widget:GGLScratchGame | {{#widget:GGLScratchGame | ||
|bg=图片1.png | |bg=图片1.png | ||
|cover= | |cover=刮开前.png | ||
|revealed=刮开后.png | |revealed=刮开后.png | ||
}} | }} | ||
</pre> | </pre> | ||
</noinclude> | </noinclude> | ||
<includeonly> | <includeonly> | ||
<style> | <style> | ||
/* | /* === 核心容器 === */ | ||
.ggl | .ggl-game-root { | ||
max-width: 400px; /* 限制最大显示宽度,防止在大屏太巨大 */ | |||
max-width: | |||
margin: 20px auto; | margin: 20px auto; | ||
font-family: 'Courier New', monospace; | |||
user-select: none; | user-select: none; | ||
} | } | ||
.game-header { | .game-header { | ||
text-align: center; | |||
color: #555; | |||
margin-bottom: 10px; | margin-bottom: 10px; | ||
font-weight: bold; | font-weight: bold; | ||
} | } | ||
/* | /* === 彩票视觉部分 === */ | ||
.ticket- | .ticket-wrapper { | ||
position: relative; | |||
width: 100%; | |||
/* 核心:保持 1075:1911 的宽高比 */ | |||
padding-bottom: 177.76%; | |||
background-size: 100% 100%; | background-size: 100% 100%; | ||
background-repeat: no-repeat; | background-repeat: no-repeat; | ||
box-shadow: 0 10px 20px rgba(0,0,0,0.3); | |||
border-radius: 8px; | |||
box-shadow: 0 | |||
border-radius: | |||
} | } | ||
. | /* === 刮奖区定位 (关键) === */ | ||
.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; | display: grid; | ||
grid-template-columns: repeat(5, 1fr); | grid-template-columns: repeat(5, 1fr); | ||
grid-template-rows: repeat(6, 1fr); | grid-template-rows: repeat(6, 1fr); | ||
/* 消除格子间隙,因为你的图是连续的 */ | |||
gap: 0; | |||
} | } | ||
/* | /* === 格子样式 === */ | ||
.grid-cell { | .grid-cell { | ||
width: 100%; | width: 100%; | ||
height: 100%; | |||
background- | background-size: 100% 100%; /* 强制图片拉伸填满格子 */ | ||
background-repeat: no-repeat; | |||
background- | |||
display: flex; | display: flex; | ||
align-items: center; | align-items: center; | ||
justify-content: center; | justify-content: center; | ||
cursor: pointer; | cursor: pointer; | ||
font- | |||
/* 初始文字透明 (刮开前看不到字) */ | |||
color: transparent; | |||
font-weight: bold; | |||
font-size: 14px; | |||
/* 文字描边,防止在复杂背景看不清 */ | |||
text-shadow: 1px 1px 0 #fff, -1px -1px 0 #fff; | |||
} | } | ||
/* 刮开后的状态 */ | |||
/* | |||
.grid-cell.revealed { | .grid-cell.revealed { | ||
cursor: default; | cursor: default; | ||
color: #d32f2f; /* 刮开后文字颜色 */ | |||
} | } | ||
/* 底部控制区 */ | /* === 底部控制区 (重写布局) === */ | ||
. | .controls-area { | ||
margin-top: 15px; | |||
display: flex; | display: flex; | ||
justify-content: space-between; | justify-content: space-between; /* 左右分布 */ | ||
align-items: flex-end | align-items: flex-end; | ||
} | } | ||
.mascot- | /* 左侧:气泡+颜文字 */ | ||
.mascot-box { | |||
flex: 1; | |||
text-align: left; | |||
margin-right: 10px; | margin-right: 10px; | ||
} | } | ||
.speech-bubble { | .speech-bubble { | ||
background: #fff; | background: #fff; | ||
| 第154行: | 第103行: | ||
font-size: 12px; | font-size: 12px; | ||
margin-bottom: 5px; | margin-bottom: 5px; | ||
box-shadow: 2px 2px | box-shadow: 2px 2px 0 rgba(0,0,0,0.1); | ||
min-height: 20px; | min-height: 20px; | ||
line-height: 1.3; | line-height: 1.3; | ||
} | } | ||
.mascot-face { | .mascot-face { | ||
| 第167行: | 第114行: | ||
} | } | ||
. | /* 右侧:按钮 */ | ||
padding: | .action-btn { | ||
padding: 0 20px; | |||
height: 40px; | |||
font-size: 14px; | font-size: 14px; | ||
background: #2196F3; | background: #2196F3; | ||
| 第175行: | 第124行: | ||
border-radius: 5px; | border-radius: 5px; | ||
cursor: pointer; | cursor: pointer; | ||
box-shadow: 2px 2px 0 #000; | box-shadow: 2px 2px 0 #000; | ||
white-space: nowrap; | |||
} | } | ||
. | .action-btn:active { | ||
transform: translate(2px, 2px); | transform: translate(2px, 2px); | ||
box-shadow: none; | box-shadow: none; | ||
} | } | ||
. | .action-btn:disabled { | ||
background: #ccc; | background: #ccc; | ||
box-shadow: none; | box-shadow: none; | ||
} | } | ||
.action-btn.next { background: #4CAF50; } | |||
.action-btn.danger { background: #000; color: red; border: 1px solid red; } | |||
/* | /* 结局特效 */ | ||
.invert-filter { | .invert-filter { filter: invert(100%); background: black !important; } | ||
} | |||
.crash-screen { | .crash-screen { | ||
position: fixed; | position: fixed; top: 0; left: 0; width: 100%; height: 100%; | ||
background: black; color: red; z-index: 99999; | |||
background: black; | display: flex; align-items: center; justify-content: center; | ||
display: flex; | |||
font-size: 30px; | font-size: 30px; | ||
} | } | ||
</style> | </style> | ||
<div class="ggl | <!-- HTML 结构 --> | ||
<div class="ggl-game-root" id="ggl-root"> | |||
<div class="game-header"> | <div class="game-header"> | ||
GGL-LOTTERY | 剩余: <span id="count-val">3</span> | |||
</div> | </div> | ||
<div class="ticket- | <!-- 1. 彩票底图层 --> | ||
< | <!-- 直接用 style 写入背景图,确保路径正确解析 --> | ||
<div class="ticket-wrapper" style="background-image: url('{{filepath:<!--{$bg|default:'图片1.png'}-->}}');"> | |||
<!-- 2. 刮奖区覆盖层 (绝对定位) --> | |||
<div class="scratch-grid-overlay" id="scratch-grid"> | |||
<!-- JS 生成格子 --> | |||
</div> | </div> | ||
<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> | </div> | ||
<button id="main-btn" class="action-btn">扫描结果</button> | |||
</div> | </div> | ||
</div> | </div> | ||
<div id="fake-wiki-entry" style="display:none; padding:20px; background:#fff; border:1px solid #ccc; max-width:800px; margin: | <!-- 伪词条 --> | ||
<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> | <h1 style="border-bottom: 1px solid #aaa;">六世恶言</h1> | ||
<div style="margin-top:10px;"> | |||
<p><b>描述:</b>“六世恶言”是一种具有高度认知危害的网络异常...</p> | |||
<p><b>描述:</b> | |||
<hr> | <hr> | ||
<p><b>收藏品3:</b> [回复] <b>“它是我藏在赛博刮刮乐内的,是被惩罚的人。”</b></p> | <p><b>收藏品3:</b> [回复] <b>“它是我藏在赛博刮刮乐内的,是被惩罚的人。”</b></p> | ||
</div> | </div> | ||
| 第262行: | 第191行: | ||
<script> | <script> | ||
(function() { | (function() { | ||
var root = document.getElementById('ggl | var root = document.getElementById('ggl-root'); | ||
if (!root) return; | if (!root) return; | ||
// 获取图片路径,用于JS赋值 | |||
var imgCover = '{{filepath:<!--{$cover|default:'图片2.png'}-->}}'; | |||
var imgRevealed = '{{filepath:<!--{$revealed|default:'图片3.png'}-->}}'; | |||
// 游戏状态 | |||
var TOTAL_CARDS = 11; | var TOTAL_CARDS = 11; | ||
var | var currentIdx = 0; | ||
var | var remain = 3; | ||
var isScanned = false; | var isScanned = false; | ||
var revealedCount = 0; | var revealedCount = 0; | ||
// DOM | |||
var elCount = root.querySelector('#count-val'); | var elCount = root.querySelector('#count-val'); | ||
var elGrid = root.querySelector('#scratch-grid'); | var elGrid = root.querySelector('#scratch-grid'); | ||
var elBtn = root.querySelector('# | var elBtn = root.querySelector('#main-btn'); | ||
var | var elBubble = root.querySelector('#mascot-text'); | ||
var | var elFace = root.querySelector('#mascot-face'); | ||
var | var elId = root.querySelector('#ticket-id'); | ||
var elFakeEntry = document.getElementById('fake-wiki-entry'); | var elFakeEntry = document.getElementById('fake-wiki-entry'); | ||
var | // 数据 | ||
{ type: ' | var cards = [ | ||
{ type: ' | { type: 'n', face: '', text: '结果:谢谢惠顾。' }, | ||
{ type: ' | { type: 'n', face: '(・ω・)', text: '检测到异常字符。' }, | ||
{ type: ' | { type: 'b', face: '(^▽^)', text: '恭喜!触发连击,奖励+2张!' }, | ||
{ type: ' | { type: 'n', face: 'o(*≧▽≦)ツ', text: '结果:1 Data 2。好耶!' }, | ||
{ type: ' | { type: 'c', face: '( ・_・)', text: '结果:中奖...?数据好像变了。' }, | ||
{ type: ' | { type: 'x', face: '', text: '对象已消失。', val: 'Data' }, | ||
{ type: ' | { type: 'g', face: '...', text: 'ERR_#0x00 数据损坏', val: '???' }, | ||
{ type: ' | { type: 'p', face: '', text: '声音检测:"好痛!"', val: 'Pain' }, | ||
{ type: ' | { type: 't', face: '', text: '收到文本:"你是谁?"', val: 'Info' }, | ||
{ type: ' | { type: 's', face: '', text: '警告:立即停止操作。', val: '停下' }, | ||
{ type: 'f', face: 'ERROR', text: '你知道的太多了。', val: '平安喜乐' } | |||
]; | ]; | ||
function | function setMascot(txt, face) { | ||
if( | if(txt) elBubble.innerText = txt; | ||
if(face !== undefined) | if(face !== undefined) elFace.innerText = face; | ||
} | } | ||
function | function randVal() { | ||
var | var arr = ['10mb', '50Gb', '5Kb', '1Tb', '福', '寿', '乐', '奖', '空']; | ||
return | return arr[Math.floor(Math.random() * arr.length)]; | ||
} | } | ||
function | function initCard(idx) { | ||
isScanned = false; | isScanned = false; | ||
revealedCount = 0; | revealedCount = 0; | ||
elBtn.innerText = "扫描结果"; | elBtn.innerText = "扫描结果"; | ||
elBtn.className = " | elBtn.className = "action-btn"; | ||
elBtn.disabled = false; | elBtn.disabled = false; | ||
elBtn.style = ""; | elBtn.style = ""; | ||
var | elId.innerText = (idx+1).toString().padStart(3,'0'); | ||
elCount.innerText = remain; | |||
var d = cards[idx]; | |||
elGrid.innerHTML = ''; | elGrid.innerHTML = ''; | ||
setMascot("请刮开所有区域...", d.face); | |||
if ( | if(d.type === 's') { // Stop | ||
elBtn.style.border = "2px solid red"; | |||
elBtn.style.color = "red"; | |||
} | } | ||
for (var i = 0; i < 30; i++) { | for(var i=0; i<30; i++) { | ||
var cell = document.createElement('div'); | var cell = document.createElement('div'); | ||
cell.className = 'grid-cell'; | cell.className = 'grid-cell'; | ||
var | // 1. 设置刮开前的图 (覆盖层) | ||
cell.style.backgroundImage = "url('" + imgCover + "')"; | |||
if( | |||
if( | // 决定内容 | ||
var val = d.val ? d.val : randVal(); | |||
if(idx === 2 && (i===10 || i===11)) val = "大奖"; | |||
if(idx === 8) val = "你是谁"; | |||
cell.dataset. | cell.dataset.val = val; | ||
(function(c, cIdx, | // 点击事件 | ||
(function(c, cIdx, cVal, idxI){ | |||
c.addEventListener('click', function() { | c.addEventListener('click', function() { | ||
if (c.classList.contains('revealed') || isScanned) return; | if(c.classList.contains('revealed') || isScanned) return; | ||
// 2. 切换为刮开后的图 | |||
c.classList.add('revealed'); | c.classList.add('revealed'); | ||
c.style.backgroundImage = "url('" + imgRevealed + "')"; | |||
c.innerText = cVal; // 显示文字 | |||
revealedCount++; | revealedCount++; | ||
if(cIdx === 5) { | // 剧情触发 | ||
if(cIdx === 5) { // 消失 | |||
setMascot(["别点了","我在看你"][Math.floor(Math.random()*2)], ""); | |||
c.innerText = ""; | c.innerText = ""; | ||
} | } | ||
if(cIdx === 7) { | if(cIdx === 7) { // 痛 | ||
c.innerText = ["痛!","别!"][Math.floor(Math.random()*2)]; | |||
c.style.color = "red"; | |||
setMascot("别挠我!", ">_<"); | |||
} | } | ||
if(cIdx === 4 && revealedCount === 15) { | if(cIdx === 4 && revealedCount === 15) { // 改字 | ||
setMascot("诶...?", "(;゚д゚)"); | |||
if(elGrid.children[29]) { | |||
elGrid.children[29].dataset.val = "中奖"; | |||
} | |||
} | } | ||
}); | }); | ||
})(cell, | })(cell, idx, val, i); | ||
elGrid.appendChild(cell); | elGrid.appendChild(cell); | ||
| 第367行: | 第309行: | ||
elBtn.addEventListener('click', function() { | elBtn.addEventListener('click', function() { | ||
if (isScanned) { | if(isScanned) { | ||
// Next | |||
if ( | currentIdx++; | ||
if(currentIdx < TOTAL_CARDS) initCard(currentIdx); | |||
} else { | } else { | ||
if (revealedCount < 30 && | // Scan | ||
if(revealedCount < 30 && currentIdx < 9) { | |||
if(!confirm("还没刮完,确定扫描吗?")) return; | if(!confirm("还没刮完,确定扫描吗?")) return; | ||
} | } | ||
var | var d = cards[currentIdx]; | ||
isScanned = true; | isScanned = true; | ||
elBtn.innerText = "扫描中..."; | elBtn.innerText = "扫描中..."; | ||
setTimeout(function(){ | setTimeout(function(){ | ||
setMascot(d.text); | |||
if ( | |||
if(d.type === 'b') { remain += 2; elCount.innerText = remain; } | |||
if(d.type === 'f') { doEnd(); return; } | |||
} | if(d.type === 's') { | ||
if ( | |||
setTimeout(function(){ | setTimeout(function(){ | ||
setMascot("严重错误。排出异物..."); | |||
elBtn.innerText = "取出未知票据"; | |||
elBtn.className = "action-btn danger"; | |||
}, 800); | }, 800); | ||
return; | return; | ||
} | } | ||
remain--; | |||
elCount.innerText = remain; | |||
if(remain < 0) { | |||
setMascot("票据耗尽。"); | |||
elCount.innerText = | |||
if ( | |||
elBtn.disabled = true; | elBtn.disabled = true; | ||
return; | return; | ||
} | } | ||
elBtn.innerText = "再来一张"; | elBtn.innerText = "再来一张"; | ||
elBtn.classList.add('next | elBtn.classList.add('next'); | ||
}, | }, 500); | ||
} | } | ||
}); | }); | ||
function | function doEnd() { | ||
document.body.classList.add('invert-filter'); | document.body.classList.add('invert-filter'); | ||
setTimeout(function(){ | setTimeout(function(){ | ||
var | var div = document.createElement('div'); | ||
div.className = 'crash-screen'; | |||
div.innerText = "FATAL ERROR"; | |||
document.body.appendChild( | document.body.appendChild(div); | ||
setTimeout(function(){ | setTimeout(function(){ | ||
div.remove(); | |||
document.body.classList.remove('invert-filter'); | document.body.classList.remove('invert-filter'); | ||
root.style.display = 'none'; | root.style.display = 'none'; | ||
if(elFakeEntry) elFakeEntry.style.display = 'block'; | if(elFakeEntry) elFakeEntry.style.display = 'block'; | ||
window.scrollTo(0,0); | window.scrollTo(0,0); | ||
alert(" | alert("【系统提示】收到新短信。"); | ||
}, 3000); | }, 3000); | ||
}, 1500); | }, 1500); | ||
} | } | ||
initCard(0); | |||
})(); | })(); | ||
</script> | </script> | ||
</includeonly> | </includeonly> | ||
2026年2月1日 (日) 13:02的版本
刮刮乐重构版
调用方式:
{{#widget:GGLScratchGame
|bg=图片1.png
|cover=刮开前.png
|revealed=刮开后.png
}}
