(function () { /* ========= Constants ========= */ var ORDER = [0, 1.5, 3, 9, 15]; var PRACTICE_PROBS = { 1.5: 17, 3: 19, 9: 23, 15: 11, 0: 30 }; /* ========= CSS (精簡版:用 .prize-* 一次吃全站色) ========= */ var css = ` :root{ --prize-1-5:#8dd3c7;--prize-3:#ffffb3;--prize-9:#bebada;--prize-15:#fb8072;--prize-0:#d0d0d0; --text:#222; } .ticket-grid label{color:transparent!important;font-size:0!important;display:flex;flex-direction:column} .prize-1-5{background:var(--prize-1-5)!important} .prize-3{background:var(--prize-3)!important} .prize-9{background:var(--prize-9)!important} .prize-15{background:var(--prize-15)!important} .prize-0{background:var(--prize-0)!important} .lottery-vis{margin-top:6px;font-size:12px!important;color:var(--text)!important;width:100%} .legend{display:flex;flex-wrap:wrap;gap:4px 8px;margin-top:8px;} .legend-item{display:flex;align-items:center;gap:4px;white-space:nowrap} .legend-color{width:10px;height:10px;border-radius:3px;border:1px solid #ccc} .legend{ font-size:12px !important; line-height:1.2 !important; max-width: 240px; margin-inline: 0 auto; width:100%; font-family: system-ui, -apple-system, "Segoe UI", "Microsoft JhengHei", "Noto Sans TC", sans-serif !important; } .legend-item{font-size:12px !important; line-height:1.2 !important; font-weight:700 !important; width: 45%} .legend-item span:last-child{font-size:12px !important; line-height:1.2 !important; font-weight:700 !important; font-variant-numeric: tabular-nums; } /* ====== Number 10×10 (原始大小) ====== */ .lottery-vis--number { width: 100%; overflow: hidden; } .num-card { max-width: 240px; margin: 0 auto; padding: 12px; background: #fff; border: 1px solid #eee; border-radius: 12px; box-shadow: 0 2px 8px rgba(0,0,0,0.05); } .num-grid-wrap { display: flex; justify-content: center; padding: 5px 0; } .num-grid { --cell: 16px; display: grid; grid-template-columns: repeat(10, var(--cell)); grid-template-rows: repeat(10, var(--cell)); border: 1.5px solid rgba(0,0,0,0.4); background: #fff; } .num-cell { width: var(--cell); height: var(--cell); border-right: 0.5px solid rgba(0,0,0,0.2); border-bottom: 0.5px solid rgba(0,0,0,0.2); box-sizing: border-box; } .num-cell:nth-child(10n) { border-right: none; } .num-cell:nth-last-child(-n+10) { border-bottom: none; } .num-ranges { margin-top: 12px; display: flex; flex-direction: column; gap: 4px; } .num-row { display: flex; align-items: center; gap: 2px; font-size: 12px; color: #444; } .num-money { width: 40%; text-align: right; font-weight: 900; color: #000; } .num-text { monospace; font-weight: 900; flex-grow: 1; text-align: right; } .num-count { display: none; } /* ====== Wheel (✅ 終極防撞邊界:絕對不超格) ====== */ .lottery-vis--wheel{text-align:center} /* 寬度嚴格收縮至 210px,保證完全躲在外層卡片的 Padding 內 */ .wheel-wrap{width:210px;height:136px;margin:8px auto 4px;position:relative;display:flex;justify-content:center;align-items:center} /* 圓盤大小 114px (保留放大效果,但留出左右空間) */ .wheel-circle{width:114px;height:114px;border-radius:50%;border:2px solid #ccc;box-sizing:border-box;position:relative;z-index:2} .wheel-label{ position:absolute;left:50%;top:50%;transform:translate(-50%,-50%); font-size:12px;font-weight:800;padding:2px 6px;border-radius:999px;background:rgba(255,255,255,.78); border:1px solid rgba(0,0,0,.12);pointer-events:none;white-space:nowrap;z-index:4 } .wheel-svg{position:absolute;inset:0;width:100%;height:100%;z-index:1;pointer-events:none} .wheel-svg polyline{stroke:rgba(0,0,0,.55);stroke-width:1.6;fill:none} .wheel-svg circle{fill:rgba(0,0,0,.55)} /* 外拉標籤字體微調、間距縮緊,防超線 */ .wheel-callout{ position:absolute;z-index:3;font-size:10.5px;font-weight:800;color:var(--text); padding:1px 4px;border-radius:4px;background:rgba(255,255,255,.90);border:1px solid rgba(0,0,0,.12); white-space:nowrap;pointer-events:none;letter-spacing:-0.2px; } /* ====== Slot (✅ 嚴格限制最大寬度防超格) ====== */ .lottery-vis--slot .slot-machine{ margin: 8px auto; max-width: 240px; /* 原 260px,縮緊確保不超過外層卡片 */ border-radius:14px;border:1px solid rgba(0,0,0,.10); background:linear-gradient(180deg,#fff,#f6f6f6); padding:10px 8px 12px;display:grid;grid-template-columns:1fr 70px;gap:8px;align-items:center; box-shadow:0 8px 18px rgba(0,0,0,.06) } .lottery-vis--slot .slot-header{grid-column:1/-1;display:flex;font-weight:900;font-size:12px;opacity:.95} .lottery-vis--slot .slot-reels{display:grid;grid-template-columns:1fr;gap:8px;position:relative} .lottery-vis--slot .slot-window{ height:100px; border-radius:12px;border:2px solid rgba(0,0,0,.12); background:radial-gradient(circle at 30% 30%,#4a4a4a,#1a1a1a); box-shadow:inset 0 0 0 2px rgba(0,0,0,.35);position:relative; overflow: visible; } .lottery-vis--slot .slot-strip{height:100%;display:flex;flex-direction:column;border-radius:10px;overflow:hidden} .lottery-vis--slot .slot-seg{ width: 100%; flex: none; overflow: hidden; display: block; } .lottery-vis--slot .slot-side{display:flex;flex-direction:column;align-items:center;gap:8px} .lottery-vis--slot .slot-lever{width:44px;height:44px;border-radius:999px;border:2px solid rgba(0,0,0,.18); background:radial-gradient(circle at 30% 30%,#ff5b5b,#b80000);box-shadow:0 6px 0 #6a0000;opacity:.75} .lottery-vis--slot .slot-btn-deco{width:68px;padding:6px 0;border-radius:10px;border:1px solid rgba(0,0,0,.14);background:rgba(255,255,255,.7);font-size:12px;text-align:center} .lottery-vis--slot .slot-note{grid-column:1/-1;color:#333;font-size:12px;opacity:.92;margin-top:2px} .slot-line{display:none;} .slot-label { position: absolute; left: 100%; margin-left: 15px; transform: translateY(-50%); font-size: 12px; font-weight: 800; color: #222; white-space: nowrap; background: rgba(255,255,255,0.85); padding: 1px 4px; border-radius: 4px; z-index: 11; } /* ====== Practice Box (完全無更動) ====== */ .lottery-practice-box{ margin-top:10px;padding:12px;border-radius:14px;border:1px solid rgba(0,0,0,.10); background:linear-gradient(180deg,#fff,#f7f7f7);box-shadow:0 10px 22px rgba(0,0,0,.06) } .lottery-practice-title{font-size:12px;font-weight:950;color:#111;margin-bottom:6px} .lottery-practice-desc{font-size:12px;color:#333;opacity:.92;line-height:1.4} .lottery-practice-controls{display:flex;flex-direction:column;gap:10px;align-items:flex-start;margin-top:10px} .lottery-practice-btn{ appearance:none;border:0;border-radius:12px;padding:10px 14px;font-size:13px;font-weight:900;letter-spacing:.2px;color:#fff; background:linear-gradient(180deg,rgba(20,20,20,.98),rgba(0,0,0,.92)); box-shadow:0 10px 22px rgba(0,0,0,.12);cursor:pointer; transition:transform 80ms ease,box-shadow 160ms ease,opacity 160ms ease;user-select:none } .lottery-practice-btn:hover{box-shadow:0 12px 26px rgba(0,0,0,.16)} .lottery-practice-btn:active{transform:translateY(1px);box-shadow:0 8px 18px rgba(0,0,0,.14)} .lottery-practice-btn:disabled{opacity:.45;cursor:not-allowed;transform:none;box-shadow:none} .lottery-practice-result{ font-size:12px;color:#333;line-height:1.35;background:rgba(255,255,255,.7); border:1px solid rgba(0,0,0,.08);border-radius:10px;padding:8px 10px;width:100%;box-sizing:border-box } /* Practice Wheel */ .practice-wheel-area{display:flex;flex-direction: column; align-items: flex-start;justify-content:center;margin-top:6px;align-items: center; width: 240px; /* ← match the wheel width exactly */ margin-inline: auto; /* ← center the whole block within the practice box */ } .pwheel-wrap{width:170px;height:170px;position:relative} .pwheel-rot{width:170px;height:170px;transform:rotate(0deg);transition:transform 2400ms cubic-bezier(.12,.9,.18,1);will-change:transform} .pwheel-circle{width:170px;height:170px;border-radius:50%;border:2px solid #cfcfcf;box-sizing:border-box} .pwheel-center{position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);width:52px;height:52px;border-radius:50%;background:#fff;border:1px solid rgba(0,0,0,.18);z-index:6} .pwheel-pointer{position:absolute;left:50%;top:-2px;transform:translateX(-50%);width:0;height:0;border-left:10px solid transparent;border-right:10px solid transparent;border-top:18px solid rgba(0,0,0,.8);z-index:9;filter:drop-shadow(0 2px 2px rgba(0,0,0,.25))} .pwheel-label{position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);font-size:12px;font-weight:900;color:#222;padding:2px 7px;border-radius:999px;background:rgba(255,255,255,.78);border:1px solid rgba(0,0,0,.12);white-space:nowrap;pointer-events:none} .lottery-practice-box .pwheel-wrap { margin: 8px auto; /* center the 170px wheel within the box */ display: block; } /* Practice Slot */ .practice-slot { margin: 6px auto; max-width: 260px; border-radius: 14px; border: 1px solid rgba(0,0,0,.10); background: linear-gradient(180deg,#fff,#f2f2f2); padding: 10px; display: grid; grid-template-columns: 1fr 80px; gap: 10px; align-items: center; box-shadow: 0 10px 22px rgba(0,0,0,0.06); } .practice-slot .legend { grid-column: 1 / -1; /* span both columns */ max-width: 100%; margin-top: 4px; } .practice-slot-title{grid-column:1/-1;font-size:12px;font-weight:900;color:#111;opacity:.95} .practice-slot-window{height:96px;border-radius:12px;border:2px solid rgba(0,0,0,.12);background:radial-gradient(circle at 30% 30%,#3b3b3b,#161616);position:relative;overflow:hidden} .practice-slot-window::after{content:"";position:absolute;inset:0;background:linear-gradient(180deg,rgba(255,255,255,.20),rgba(255,255,255,0) 35%,rgba(0,0,0,.30));pointer-events:none} .practice-payline{position:absolute;left:10px;right:10px;top:50%;border-top:2px solid rgba(255,255,255,.55);transform:translateY(-50%);z-index:7;pointer-events:none} .practice-strip { position: absolute; left: 0; right: 0; top: 0; will-change: transform; } .practice-cell{height:32px;display:flex;align-items:center;justify-content:center;font-size:12px;font-weight:900;color:#222;border-top:1px solid rgba(255,255,255,.10)} .practice-slot-side{display:flex;flex-direction:column;align-items:center;gap:10px} .practice-slot-knob{width:46px;height:46px;border-radius:999px;border:2px solid rgba(0,0,0,.18);background:radial-gradient(circle at 30% 30%,#ff5b5b,#b80000);box-shadow:0 6px 0 #6a0000;opacity:.85} .practice-slot-tip{font-size:11px;color:#444;opacity:.9;text-align:center} .lottery-vis--wheel .legend, .lottery-vis--slot .legend { max-width: 240px; margin-inline: auto; } .lottery-vis--slot .slot-machine .legend, .slot-machine .legend { grid-column: 1 / -1; max-width: 240px; } /* Practice Number Draw */ .practice-draw{display:flex;flex-direction:column;gap:10px;margin-top:10px} .practice-box{width:140px;height:80px;border-radius:14px;border:1px solid rgba(0,0,0,.10);background:radial-gradient(circle at 30% 30%,#fff,#f1f1f1); display:flex;align-items:center;justify-content:center;box-shadow:inset 0 0 0 2px rgba(0,0,0,.04)} .practice-ball{width:52px;height:52px;border-radius:999px;display:flex;align-items:center;justify-content:center;font-weight:950;font-size:18px;color:#111;background:#fff; border:1px solid rgba(0,0,0,.14);box-shadow:0 8px 16px rgba(0,0,0,.10)} .practice-ball.drawing{animation:wobble .24s ease-in-out infinite alternate} @keyframes wobble{from{transform:rotate(-3deg) translateY(-1px)}to{transform:rotate(3deg) translateY(1px)}} .practice-draw-note{font-size:12px;color:#333;opacity:.9;line-height:1.35} `; var style = document.createElement("style"); style.type = "text/css"; style.appendChild(document.createTextNode(css)); document.head.appendChild(style); /* ========= Helpers ========= */ function fmtPct(x) { var s = (Math.round(x * 10) / 10).toString(); return s.replace(/\.0$/, ""); } function textAmt(a) { return a + " USD"; } function el(tag, cls, text) { var n = document.createElement(tag); if (cls) n.className = cls; if (text !== undefined && text !== null) n.textContent = text; return n; } function detectMode(playType) { if ((playType || "").indexOf("轉盤") !== -1 || (playType || "").indexOf("Spinner") !== -1) return "wheel"; if ((playType || "").indexOf("拉霸") !== -1 || (playType || "").indexOf("Slot") !== -1) return "slot"; return "number"; } function parseProbs(text) { if (!text) return null; var out = {}, found = false; var m; var re1 = /Win\s+(\d+(?:\.\d+)?)\s+USD\s+prob:\s*(\d+(?:\.\d+)?)\s*%/gi; while ((m = re1.exec(text)) !== null) { var k = parseFloat(m[1]), p = parseFloat(m[2]); if (isFinite(k) && isFinite(p)) { out[k] = p; found = true; } } if (!found) { var re2 = /(\d+(?:\.\d+)?)\s*(?:USD|usd|\$|元)?\s*:\s*(\d+(?:\.\d+)?)\s*%/g; while ((m = re2.exec(text)) !== null) { var k = parseFloat(m[1]), p = parseFloat(m[2]); if (isFinite(k) && isFinite(p)) { out[k] = p; found = true; } } } return found ? out : null; } function toEntries(probMap) { var arr = []; for (var i = 0; i < ORDER.length; i++) { var k = ORDER[i]; var p = (probMap && probMap[k]) ? probMap[k] : 0; if (p > 0) arr.push({ key: k, prob: p }); } return arr; } function getPrizeClass(key) { return "prize-" + String(key).replace(".", "-"); } function buildLegend(entries) { var legend = el("div", "legend"); for (var i = 0; i < entries.length; i++) { var e = entries[i]; var item = el("div", "legend-item"); item.appendChild(el("span", "legend-color " + getPrizeClass(e.key))); item.appendChild(el("span", "", e.key + " USD:" + fmtPct(e.prob) + "%")); legend.appendChild(item); } return legend; } function allocateCountsTo100(entries) { var map = {}; for (var i = 0; i < entries.length; i++) map[entries[i].key] = entries[i].prob; var ordered = []; for (var j = 0; j < ORDER.length; j++) ordered.push({ key: ORDER[j], prob: map[ORDER[j]] || 0 }); var floors = [], rem = [], sum = 0; for (var t = 0; t < ordered.length; t++) { var raw = ordered[t].prob; var f = Math.floor(raw); floors[t] = f; sum += f; rem.push({ idx: t, r: raw - f }); } if (sum < 100) { rem.sort(function (a, b) { return b.r - a.r; }); for (var add = 0; add < (100 - sum); add++) floors[rem[add % rem.length].idx] += 1; } var out = []; for (var z = 0; z < ordered.length; z++) out.push({ key: ordered[z].key, prob: ordered[z].prob, count: floors[z] }); return out; } function probsToRanges(entries) { var alloc = allocateCountsTo100(entries); var cur = 1; var ranges = []; for (var i = 0; i < alloc.length; i++) { var a = alloc[i]; if (a.count > 0) { var s = cur, e = cur + a.count - 1; ranges.push({ key: a.key, count: a.count, start: s, end: e }); cur = e + 1; } else { ranges.push({ key: a.key, count: 0, start: null, end: null }); } } return ranges; } function sampleFromRanges(ranges) { var n = Math.floor(Math.random() * 100) + 1; var prize = 1; for (var i = 0; i < ranges.length; i++) { var r = ranges[i]; if (r.count > 0 && n >= r.start && n <= r.end) { prize = r.key; break; } } return { number: n, prize: prize }; } /* ========= Visual: Number 10×10 (原始比例) ========= */ function buildNumberVis(entries) { var ranges = probsToRanges(entries); var container = el("div", "lottery-vis lottery-vis--number"); var card = el("div", "num-card"); var gridWrap = el("div", "num-grid-wrap"); var grid = el("div", "num-grid"); for (var i = 0; i < ranges.length; i++) { var seg = ranges[i]; for (var k = 0; k < seg.count; k++) { var num = seg.start + k; var cell = el("div", "num-cell " + getPrizeClass(seg.key)); cell.title = "number " + num + " → " + textAmt(seg.key); grid.appendChild(cell); } } while (grid.childNodes.length < 100) grid.appendChild(el("div", "num-cell prize-1", "")); gridWrap.appendChild(grid); card.appendChild(gridWrap); var list = el("div", "num-ranges"); for (var j = 0; j < ranges.length; j++) { var r = ranges[j]; var row = el("div", "num-row"); row.appendChild(el("span", "num-chip " + getPrizeClass(r.key))); row.appendChild(el("span", "num-money", textAmt(r.key))); row.appendChild(el("span", "num-text", r.count ? ((r.start === r.end) ? String(r.start) : (r.start + " — " + r.end)) : "—")); list.appendChild(row); } card.appendChild(list); card.appendChild(buildLegend(entries)); container.appendChild(card); return container; } /* ========= Visual: Wheel (✅ 嚴格防溢出版) ========= */ function buildWheelVis(entries) { var total = 0; for (var i = 0; i < entries.length; i++) total += entries[i].prob; if (total <= 0) return null; // ✅ 寬度收合至 210px,保證兩側即使加上標籤也完美藏在票卡 Padding 內 var sizeW = 210, sizeH = 136, cx = sizeW / 2, cy = sizeH / 2; var startDeg = 0; var parts = []; var arcs = []; for (var j = 0; j < entries.length; j++) { var e = entries[j]; var sweep = (e.prob / total) * 360; var endDeg = startDeg + sweep; parts.push("var(--prize-" + String(e.key).replace(".", "-") + ") " + startDeg.toFixed(2) + "deg " + endDeg.toFixed(2) + "deg"); arcs.push({ key: e.key, start: startDeg, end: endDeg, sweep: sweep }); startDeg = endDeg; } var container = el("div", "lottery-vis lottery-vis--wheel"); var wrap = el("div", "wheel-wrap"); var circle = el("div", "wheel-circle"); circle.style.backgroundImage = "conic-gradient(" + parts.join(",") + ")"; wrap.appendChild(circle); var svg = document.createElementNS("http://www.w3.org/2000/svg", "svg"); svg.setAttribute("class", "wheel-svg"); svg.setAttribute("width", sizeW); svg.setAttribute("height", sizeH); svg.setAttribute("viewBox", "0 0 " + sizeW + " " + sizeH); wrap.appendChild(svg); var right = [], left = []; // ✅ 配合 114px 圓盤半徑 (R_edge=57),向外拉伸到 62px (R_out=62) // 左側剩餘 43px 空間,完美符合微調後的字體寬度,不破圖! var R_edge = 57, R_out = 62, minGap = 16, IN_LABEL_MIN_SWEEP = 75; for (var a = 0; a < arcs.length; a++) { var arc = arcs[a]; var mid = (arc.start + arc.end) / 2; var rad = (mid - 90) * Math.PI / 180; var cos = Math.cos(rad), sin = Math.sin(rad); if (arc.sweep >= IN_LABEL_MIN_SWEEP) { var rIn = 37; // 內部標籤座標調整 var lab = el("div", "wheel-label", textAmt(arc.key)); lab.style.left = (cx + cos * rIn).toFixed(1) + "px"; lab.style.top = (cy + sin * rIn).toFixed(1) + "px"; wrap.appendChild(lab); } else { var side = (cos >= 0) ? "R" : "L"; var wantY = cy + sin * R_out; (side === "R" ? right : left).push({ key: arc.key, cos: cos, sin: sin, side: side, wantY: wantY }); } } function adjust(list) { list.sort(function (u, v) { return u.wantY - v.wantY; }); for (var i2 = 0; i2 < list.length; i2++) { var y = list[i2].wantY; if (y < 10) y = 10; if (y > sizeH - 10) y = sizeH - 10; if (i2 > 0) { var py = list[i2 - 1].y; if (y - py < minGap) y = py + minGap; } list[i2].y = y; } for (var i3 = list.length - 2; i3 >= 0; i3--) { var ny = list[i3 + 1].y; if (list[i3].y > ny - minGap) list[i3].y = ny - minGap; } } adjust(right); adjust(left); function addCallouts(list) { for (var c = 0; c < list.length; c++) { var it = list[c]; var x1 = cx + it.cos * R_edge, y1 = cy + it.sin * R_edge; var lx = (it.side === "R") ? (cx + R_out) : (cx - R_out); var ly = it.y; var poly = document.createElementNS("http://www.w3.org/2000/svg", "polyline"); poly.setAttribute("points", x1.toFixed(1) + "," + y1.toFixed(1) + " " + (cx + it.cos * (R_edge + 3)).toFixed(1) + "," + (cy + it.sin * (R_edge + 3)).toFixed(1) + " " + lx.toFixed(1) + "," + ly.toFixed(1)); svg.appendChild(poly); var dot = document.createElementNS("http://www.w3.org/2000/svg", "circle"); dot.setAttribute("cx", x1.toFixed(1)); dot.setAttribute("cy", y1.toFixed(1)); dot.setAttribute("r", "2.2"); svg.appendChild(dot); var call = el("div", "wheel-callout", textAmt(it.key)); call.style.top = ly.toFixed(1) + "px"; if (it.side === "R") { call.style.left = (lx + 2).toFixed(1) + "px"; call.style.transform = "translate(0,-50%)"; } else { call.style.left = (lx - 2).toFixed(1) + "px"; call.style.transform = "translate(-100%,-50%)"; } wrap.appendChild(call); } } addCallouts(right); addCallouts(left); container.appendChild(wrap); container.appendChild(buildLegend(entries)); return container; } /* ========= Visual: Slot (✅ 嚴格限制最大寬度防超格) ========= */ function buildSlotVis(entries) { var total = 0; for (var i = 0; i < entries.length; i++) total += entries[i].prob; if (total <= 0) return null; var container = el("div", "lottery-vis lottery-vis--slot"); var machine = el("div", "slot-machine"); machine.appendChild(el("div", "slot-header", "")); var reels = el("div", "slot-reels"); var win = el("div", "slot-window"); var strip = el("div", "slot-strip"); var acc = 0; var labelsToAdjust = []; var slotH = 100; // 配合原本小巧的視窗高度 for (var j = 0; j < entries.length; j++) { var e = entries[j]; if (e.prob <= 0) continue; var seg = el("div", "slot-seg " + getPrizeClass(e.key), ""); seg.style.flex = String(e.prob); strip.appendChild(seg); var percent = acc + (e.prob / 2); var wantY_px = (percent / 100) * slotH; labelsToAdjust.push({ key: e.key, wantY: wantY_px, prob: e.prob }); acc += e.prob; } win.appendChild(strip); var lineSvg = document.createElementNS("http://www.w3.org/2000/svg", "svg"); lineSvg.style.position = "absolute"; lineSvg.style.left = "100%"; lineSvg.style.top = "0"; lineSvg.style.width = "20px"; lineSvg.style.height = "100%"; lineSvg.style.overflow = "visible"; lineSvg.style.zIndex = "10"; lineSvg.style.pointerEvents = "none"; win.appendChild(lineSvg); var minGapSlot = 15; // 在 100px 內適度調整間距 labelsToAdjust.sort(function(a,b) { return a.wantY - b.wantY; }); for (var l = 0; l < labelsToAdjust.length; l++) { var item = labelsToAdjust[l]; var y = item.wantY; if (y < 6) y = 6; if (y > slotH - 6) y = slotH - 6; if (l > 0) { var py = labelsToAdjust[l-1].y; if (y - py < minGapSlot) y = py + minGapSlot; } item.y = y; } for (var l2 = labelsToAdjust.length - 2; l2 >= 0; l2--) { var ny = labelsToAdjust[l2+1].y; if (labelsToAdjust[l2].y > ny - minGapSlot) labelsToAdjust[l2].y = ny - minGapSlot; } for (var m = 0; m < labelsToAdjust.length; m++) { var it = labelsToAdjust[m]; var poly = document.createElementNS("http://www.w3.org/2000/svg", "polyline"); poly.setAttribute("points", "0," + it.wantY + " 8," + it.y + " 15," + it.y); poly.setAttribute("fill", "none"); poly.setAttribute("stroke", "rgba(0,0,0,0.5)"); poly.setAttribute("stroke-width", "1.5"); lineSvg.appendChild(poly); var lab = el("div", "slot-label", textAmt(it.key)); lab.style.top = it.y + "px"; win.appendChild(lab); } reels.appendChild(win); machine.appendChild(reels); machine.appendChild(el("div", "slot-note", "")); machine.appendChild(buildLegend(entries)); // ← inside machine, spans both grid columns container.appendChild(machine); return container; } /* ========= Practice: Wheel ========= */ function buildPracticeWheel(entries) { var normEntries = []; for (var ni = 0; ni < entries.length; ni++) { var nk = parseFloat(entries[ni].key), np = parseFloat(entries[ni].prob) || 0; if (isFinite(nk) && np > 0) normEntries.push({ key: nk, prob: np }); } if (normEntries.length === 0) normEntries = PRACTICE_ENTRIES; var total = 0; for (var i = 0; i < normEntries.length; i++) total += normEntries[i].prob; if (total <= 0) return null; var wrap = el("div", "pwheel-wrap"); wrap.appendChild(el("div", "pwheel-pointer")); var rot = el("div", "pwheel-rot"); wrap.appendChild(rot); var start = 0, parts = [], arcs = []; for (var j = 0; j < normEntries.length; j++) { var e = normEntries[j]; var sweep = (e.prob / total) * 360; var end = start + sweep; parts.push("var(--prize-" + String(e.key).replace(".", "-") + ") " + start.toFixed(2) + "deg " + end.toFixed(2) + "deg"); arcs.push({ key: e.key, start: start, end: end, sweep: sweep }); start = end; } var circle = el("div", "pwheel-circle"); circle.style.backgroundImage = "conic-gradient(" + parts.join(",") + ")"; rot.appendChild(circle); var size = 170, cx = size / 2, cy = size / 2; for (var a = 0; a < arcs.length; a++) { var arc = arcs[a]; var rad = ((arc.start + arc.end) / 2 - 90) * Math.PI / 180; var lab = el("div", "pwheel-label", textAmt(arc.key)); lab.style.left = (cx + Math.cos(rad) * 56).toFixed(1) + "px"; lab.style.top = (cy + Math.sin(rad) * 56).toFixed(1) + "px"; lab.style.position = "absolute"; lab.style.zIndex = "5"; rot.appendChild(lab); } wrap.appendChild(el("div", "pwheel-center")); var state = { rotation: 0 }; return { el: wrap, spinToPrize: function (prizeKey, done) { var pk = parseFloat(prizeKey); var arc = arcs.find(function(a){ return a.key === pk; }); if (!arc) return done && done(); var aDeg = arc.start + Math.random() * (arc.end - arc.start); var target = -aDeg; while (target <= state.rotation + 1080) target += 360; state.rotation = target; rot.style.transition = "transform 2400ms cubic-bezier(0.12, 0.9, 0.18, 1)"; rot.style.transform = "rotate(" + state.rotation + "deg)"; var h = function(ev){ if(ev.propertyName==="transform"){rot.removeEventListener("transitionend",h); done && done();}}; rot.addEventListener("transitionend", h); }}; } /* ========= Practice: Slot ========= */ function buildPracticeSlot(entriesOverride) { var probMap = {}; if (entriesOverride && entriesOverride.length > 0) { var hasData = false; for (var ei = 0; ei < entriesOverride.length; ei++) { var eKey = parseFloat(entriesOverride[ei].key); var eProb = parseFloat(entriesOverride[ei].prob) || 0; if (isFinite(eKey) && eProb > 0) { probMap[eKey] = eProb; hasData = true; } } if (!hasData) probMap = PRACTICE_PROBS; } else { probMap = PRACTICE_PROBS; } var machine = el("div", "practice-slot"); machine.appendChild(el("div", "practice-slot-title", "")); var win = el("div", "practice-slot-window"); machine.appendChild(win); win.appendChild(el("div", "practice-payline")); var strip = el("div", "practice-strip"); win.appendChild(strip); var UNIT_H = 0.9, singleLoopHeight = 0; ORDER.forEach(function(k) { singleLoopHeight += (probMap[k] || 0) * UNIT_H; }); if (singleLoopHeight <= 0) { probMap = PRACTICE_PROBS; singleLoopHeight = 0; ORDER.forEach(function(k) { singleLoopHeight += (probMap[k] || 0) * UNIT_H; }); } for (var loop = 0; loop < 12; loop++) { ORDER.forEach(function(prize) { var prob = probMap[prize] || 0; if (prob <= 0) return; var cell = el("div", "practice-cell " + getPrizeClass(prize), prob > 10 ? textAmt(prize) : ""); cell.style.height = (prob * UNIT_H) + "px"; strip.appendChild(cell); }); } return { el: machine, spinToPrize: function (prizeKey, done) { var pk = parseFloat(prizeKey); strip.style.transition = "none"; strip.style.transform = "translateY(0px)"; strip.offsetHeight; var offsetInLoop = 0; for (var i = 0; i < ORDER.indexOf(pk); i++) offsetInLoop += (probMap[ORDER[i]] || 0) * UNIT_H; var targetCellH = (probMap[pk] || 0) * UNIT_H; var finalPos = (5 * singleLoopHeight) + offsetInLoop + (targetCellH / 2) - 48; strip.style.transition = "transform 2200ms cubic-bezier(0.15, 0.9, 0.2, 1)"; strip.style.transform = "translateY(" + (-finalPos) + "px)"; var h = function(ev){ if(ev.propertyName==="transform"){strip.removeEventListener("transitionend",h); done && done();}}; strip.addEventListener("transitionend", h); }}; } /* ========= Practice: Number draw ========= */ function buildPracticeNumberDraw() { var wrap = el("div", "practice-draw"); var box = el("div", "practice-box"); var ball = el("div", "practice-ball", "?"); box.appendChild(ball); wrap.appendChild(box); wrap.appendChild(el("div", "practice-draw-note", "")); return { el: wrap, run: function(done) { ball.classList.add("drawing"); var out = sampleFromRanges(PRACTICE_RANGES), tick = 0; var timer = setInterval(function () { tick++; ball.textContent = String(Math.floor(Math.random() * 100) + 1); if (tick >= 16) { clearInterval(timer); ball.classList.remove("drawing"); ball.textContent = String(out.number); done && done(out); } }, 60); }}; } /* ========= Practice Box ========= */ function buildPracticeBox(mode) { var box = el("div", "lottery-practice-box"); box.appendChild(el("div", "lottery-practice-title", mode === "wheel" ? "Wheel (Practice Mode)" : mode === "slot" ? "Slot Machine (Practice Mode)" : "Draw a number (Practice Mode)")); box.appendChild(el("div", "lottery-practice-desc", "")); var btn = el("button", "lottery-practice-btn", "play"); btn.type = "button"; var result = el("div", "lottery-practice-result", "(press to play)"); var controls = el("div", "lottery-practice-controls"); controls.appendChild(btn); controls.appendChild(result); var running = false; if (mode === "wheel") { var area = el("div", "practice-wheel-area"), wheel = buildPracticeWheel(PRACTICE_ENTRIES); if (wheel && wheel.el) area.appendChild(wheel.el); area.appendChild(buildLegend(PRACTICE_ENTRIES)); // legend inside area, below wheel box.appendChild(area); box.appendChild(controls); btn.addEventListener("click", function () { if (running) return; running = true; btn.disabled = true; var out = sampleFromRanges(PRACTICE_RANGES); wheel.spinToPrize(out.prize, function () { result.textContent = "Practice draw result: " + textAmt(out.prize); btn.disabled = false; running = false; }); }); } else if (mode === "slot") { var slot = buildPracticeSlot(); slot.el.appendChild(buildLegend(PRACTICE_ENTRIES)); box.appendChild(slot.el); box.appendChild(controls); btn.addEventListener("click", function () { if (running) return; running = true; btn.disabled = true; var out = sampleFromRanges(PRACTICE_RANGES); slot.spinToPrize(out.prize, function () { result.textContent = "Practice draw result: " + textAmt(out.prize); btn.disabled = false; running = false; }); }); } else { box.appendChild(buildNumberVis(PRACTICE_ENTRIES)); var draw = buildPracticeNumberDraw(); box.appendChild(draw.el); box.appendChild(controls); btn.addEventListener("click", function () { if (running) return; running = true; btn.disabled = true; draw.run(function (out) { result.textContent = "Practice draw result: " + textAmt(out.prize); btn.disabled = false; running = false; }); }); } return box; } function boot() { var grids = document.querySelectorAll(".ticket-grid"); for (var g = 0; g < grids.length; g++) { var grid = grids[g], mode = detectMode(grid.getAttribute("data-play-type")), labels = grid.querySelectorAll("label"); for (var i = 0; i < labels.length; i++) { var label = labels[i], probs = parseProbs(label.innerText || label.textContent); if (probs) { label.innerHTML = ""; label.appendChild(mode === "wheel" ? buildWheelVis(toEntries(probs)) : mode === "slot" ? buildSlotVis(toEntries(probs)) : buildNumberVis(toEntries(probs))); } } } var slotRoot = document.getElementById("demo-slot-root"); if (slotRoot) slotRoot.appendChild(buildPracticeBox("slot")); var numRoot = document.getElementById("demo-number-root"); if (numRoot) numRoot.appendChild(buildPracticeBox("number")); var wheelRoot = document.getElementById("demo-wheel-root"); if (wheelRoot) wheelRoot.appendChild(buildPracticeBox("wheel")); initPayoffDraw(); } function initPayoffDraw() { var cfg = window.PAYOFF_DRAW_CONFIG; if (!cfg) return; var root = document.getElementById(cfg.rootId || "payoff-draw-root"); if (!root) return; var mode = cfg.mode || "number", already = (localStorage.getItem(cfg.storageKey || "lottery_payoff_played") === "1"); var unlock = function(){ var h = document.getElementById("id_payoff_clicked"), n = document.getElementById("next-wrap"); if(h) h.value="1"; if(n) n.style.display="block"; }; var resT = function(){ return mode === "number" ? "Your Number: " + cfg.finalNumber + ". The prize is " + textAmt(cfg.finalPrize) + "!" : "The prize is " + textAmt(cfg.finalPrize) + "!"; }; var box = el("div", "lottery-practice-box"); box.appendChild(el("div", "lottery-practice-title", "")); var btn = el("button", "lottery-practice-btn", already ? "Completed" : "Start Draw"); var result = el("div", "lottery-practice-result", already ? resT() : "(Press to start)"); var controls = el("div", "lottery-practice-controls"); controls.appendChild(btn); controls.appendChild(result); if (mode === "wheel") { var wheel = buildPracticeWheel(cfg.entries); if (!wheel || !wheel.el) { box.appendChild(controls); } else { var area = el("div", "practice-wheel-area"); area.appendChild(wheel.el); area.appendChild(buildLegend(cfg.entries)); box.appendChild(area); box.appendChild(controls); if(already){ btn.disabled=true; unlock(); } else { btn.onclick = function(){ btn.disabled=true; wheel.spinToPrize(parseFloat(cfg.finalPrize), function(){ result.textContent=resT(); localStorage.setItem(cfg.storageKey,"1"); unlock(); }); }; } } } else if (mode === "slot") { var slot = buildPracticeSlot(cfg.entries); slot.el.appendChild(buildLegend(cfg.entries)); box.appendChild(slot.el); box.appendChild(controls); if(already){ btn.disabled=true; unlock(); } else { btn.onclick = function(){ btn.disabled=true; slot.spinToPrize(parseFloat(cfg.finalPrize), function(){ result.textContent=resT(); localStorage.setItem(cfg.storageKey,"1"); unlock(); }); }; } } else { box.appendChild(buildNumberVis(cfg.entries)); var drawBox = el("div", "practice-draw"); var ballBox = el("div", "practice-box"); var ball = el("div", "practice-ball", already ? String(cfg.finalNumber) : "?"); ballBox.appendChild(ball); drawBox.appendChild(ballBox); box.appendChild(drawBox); box.appendChild(controls); if(already){ btn.disabled=true; unlock(); } else { btn.onclick = function(){ btn.disabled=true; ball.classList.add("drawing"); var t=0, timer=setInterval(function(){ t++; ball.textContent = String(Math.floor(Math.random()*100)+1); if(t>=16){ clearInterval(timer); ball.classList.remove("drawing"); ball.textContent = String(cfg.finalNumber); result.textContent = resT(); localStorage.setItem(cfg.storageKey,"1"); unlock(); } }, 70); }; } } root.innerHTML = ""; root.appendChild(box); } var PRACTICE_ENTRIES = toEntries(PRACTICE_PROBS); var PRACTICE_RANGES = probsToRanges(PRACTICE_ENTRIES); if (document.readyState === "loading") document.addEventListener("DOMContentLoaded", boot); else boot(); })();