計算機
* { margin:0; padding:0; box-sizing:border-box; -webkit-tap-highlight-color:transparent; }
:root {
--bg:#000000; --btn-num:#333333; --btn-func:#a5a5a5;
--btn-op:#ff9f0a; --btn-equal:#ff9f0a;
--text-main:#ffffff; --text-op:#ffffff; --text-func:#000000;
--text-display:#ffffff; --text-expr:#888888;
--cursor:#ff9f0a; --divider:#222222; --shadow:rgba(0,0,0,0.4);
}
html,body { height:100%; background:var(--bg); font-family:-apple-system,'SF Pro Display','Helvetica Neue',sans-serif; overflow:hidden; touch-action:manipulation; }
#app { display:flex; flex-direction:column; height:100dvh; max-width:480px; margin:0 auto; background:var(--bg); position:relative; transition:filter 0.3s; }
/* ── 鎖定遮罩 ── */
#lock-overlay {
display:none; position:absolute; inset:0; z-index:50;
background:rgba(0,0,0,0.0);
/* 攔截所有觸控 */
}
body.locked #lock-overlay { display:block; }
body.locked #app { filter:brightness(0.75); }
/* ── 顯示區 ── */
#display-area { flex:1; display:flex; flex-direction:column; justify-content:flex-end; align-items:flex-end; padding:0 24px 12px; min-height:0; }
#top-expr { font-size:18px; color:var(--text-expr); margin-bottom:2px; min-height:24px; text-align:right; width:100%; letter-spacing:0.2px; }
#display-row { display:flex; align-items:flex-end; justify-content:flex-end; width:100%; }
#display-num { font-size:80px; font-weight:200; color:var(--text-display); letter-spacing:-3px; line-height:1; transition:font-size 0.1s; text-align:right; word-break:break-all; }
#cursor { display:none; }
@keyframes blink { 0%,100%{opacity:1} 50%{opacity:0} }
/* ── 工具列 ── */
#toolbar { display:flex; justify-content:space-between; align-items:center; padding:6px 18px; }
.tool-btn { width:44px; height:44px; display:flex; align-items:center; justify-content:center; cursor:pointer; border:none; background:transparent; }
.tool-btn svg { width:26px; height:26px; }
#backspace-btn { background:transparent; border:none; width:44px; height:44px; display:flex; align-items:center; justify-content:center; cursor:pointer; padding:0; }
#backspace-btn svg { width:30px; height:24px; }
#backspace-btn:active { opacity:0.5; }
#divider { display:none; }
#divider { height:1px; background:var(--divider); }
/* ── 按鈕區 ── */
#btn-area { padding:8px 12px 24px; display:flex; flex-direction:column; gap:12px; }
.btn-row { display:flex; justify-content:space-between; gap:12px; }
.btn { flex:1; aspect-ratio:1; border-radius:50%; border:none; cursor:pointer; font-size:32px; font-weight:400; display:flex; align-items:center; justify-content:center; user-select:none; -webkit-user-select:none; box-shadow:none; transition:transform 0.08s, opacity 0.08s, box-shadow 0.2s; color:var(--text-main); background:var(--btn-num); }
.btn:active { transform:scale(0.93); opacity:0.7; }
.btn.func { background:var(--btn-func); color:var(--text-func); font-size:26px; font-weight:500; }
.btn.op { background:var(--btn-op); color:var(--text-op); font-size:32px; font-weight:400; }
.btn.equal { background:var(--btn-equal); color:var(--text-op); font-size:32px; }
.btn.small-text { font-size:22px; }
#btn-add.holding { box-shadow:0 0 0 3px #ffffff; }
#btn-0.holding { box-shadow:0 0 0 3px #e91e8c; }
/* ── 設定 Modal ── */
#settings-overlay { display:none; position:fixed; inset:0; background:rgba(0,0,0,0.5); z-index:100; align-items:flex-end; justify-content:center; }
#settings-overlay.open { display:flex; }
#settings-panel { background:#f5f5f5; width:100%; max-width:480px; border-radius:20px 20px 0 0; padding:20px 20px 36px; animation:slideUp 0.3s ease; max-height:92dvh; overflow-y:auto; }
@keyframes slideUp { from{transform:translateY(100%)} to{transform:translateY(0)} }
#settings-panel h2 { font-size:18px; font-weight:700; color:#1a1a1a; margin-bottom:14px; text-align:center; }
.tab-row { display:flex; margin-bottom:16px; border-radius:10px; overflow:hidden; border:2px solid var(--btn-equal); }
.tab-btn { flex:1; padding:10px 4px; border:none; cursor:pointer; font-size:12px; font-weight:600; background:#fff; color:#888; transition:background 0.15s,color 0.15s; }
.tab-btn.active { background:var(--btn-equal); color:#fff; }
.tab-content { display:none; }
.tab-content.active { display:block; }
.setting-label { font-size:13px; color:#888; margin-bottom:8px; }
.setting-row { display:flex; align-items:center; gap:10px; margin-bottom:14px; }
.setting-input { flex:1; font-size:34px; font-weight:300; color:#1a1a1a; border:none; border-bottom:2.5px solid var(--btn-equal); background:transparent; text-align:center; padding-bottom:4px; outline:none; }
#custom-input { border-bottom-color:#e91e8c; letter-spacing:3px; }
.setting-unit { font-size:18px; color:#888; }
.preview-box { border-radius:12px; padding:14px; text-align:center; margin-bottom:14px; }
.preview-box.dark { background:#1a1a1a; }
.preview-box.pink-bg { background:#1a0812; }
.preview-label { font-size:12px; color:#aaa; margin-bottom:6px; }
.preview-value { font-size:20px; font-weight:600; letter-spacing:2px; font-family:monospace; }
.preview-value.green { color:var(--btn-equal); }
.preview-value.pink { color:#e91e8c; }
.quick-row { display:flex; gap:8px; margin-bottom:16px; }
.quick-btn { flex:1; padding:10px 4px; border-radius:8px; border:2px solid transparent; background:#fff; font-size:13px; font-weight:500; color:#555; cursor:pointer; text-align:center; }
.quick-btn.active { border-color:var(--btn-equal); background:#e8fdf0; color:var(--btn-equal); font-weight:700; }
.save-btn { width:100%; padding:15px; color:white; border:none; border-radius:12px; font-size:17px; font-weight:700; cursor:pointer; letter-spacing:0.5px; margin-bottom:10px; }
.save-btn.green { background:var(--btn-equal); }
.save-btn.green:active { background:#17a045; }
.save-btn.pink { background:#e91e8c; }
.save-btn.pink:active { background:#c2185b; }
#close-btn { width:100%; padding:10px; background:transparent; color:#888; border:none; font-size:15px; cursor:pointer; }
.hint-box { background:#fff8e1; border-left:4px solid #ffc107; border-radius:8px; padding:12px; margin-bottom:14px; font-size:12px; color:#795548; line-height:1.8; }
.badge { display:inline-block; padding:1px 7px; border-radius:20px; font-size:11px; font-weight:700; }
.badge.green { background:#e8fdf0; color:var(--btn-equal); }
.badge.pink { background:#fce4ec; color:#e91e8c; }
.note { font-size:11px; color:#bbb; margin-top:6px; }
hr.sep { border:none; border-top:1px solid #e0e0e0; margin:14px 0; }
🎩 後台設定
模式一 長按「+」3秒 → 時間預言,自動鎖定 X 秒後解鎖
模式二 長按「0」3秒 → 自訂數字預言,同樣自動解鎖
連按「=」5次(3秒內)→ 開啟此設定頁
觸發後幾分鐘顯示「此時此刻」
分鐘
鎖定後幾秒自動解鎖
秒
輸入你想要觀眾最後看到的數字
*輸入什麼數字,觀眾按 = 後就會看到什麼
// ══ 狀態 ══
let display='0', topExpr='', accumulator=0;
let pendingOp=null, waitNext=false, justEqual=false;
let offsetMinutes=parseInt(localStorage.getItem('magic_offset')||'30');
let customTarget=localStorage.getItem('magic_custom')||'';
let lockSeconds=parseInt(localStorage.getItem('magic_lock_seconds')||'15');
let isLocked=false;
let lockTimer=null;
let eqCount=0, eqTimer=null;
const EQ_SECRET=5, EQ_TIMEOUT=3000, HOLD_MS=3000;
const elNum=document.getElementById('display-num');
const elExpr=document.getElementById('top-expr');
const elAdd=document.getElementById('btn-add');
const elZero=document.getElementById('btn-0');
const overlay=document.getElementById('settings-overlay');
function render() {
elNum.textContent=display; elExpr.textContent=topExpr;
const l=display.length;
elNum.style.fontSize=l>11?'34px':l>8?'48px':l>5?'60px':'68px';
}
// ══ 鎖定 / 解鎖 ══
function lock(showMagicFn) {
isLocked=true;
document.body.classList.add('locked');
if(lockTimer) clearTimeout(lockTimer);
// 等 X 秒後顯示魔術數字,並立刻解鎖
lockTimer=setTimeout(()=>{
if(!isLocked) return;
showMagicFn();
if(navigator.vibrate) navigator.vibrate(40);
unlock(); // 魔術數字出現後立刻解鎖,讓觀眾自己按
}, lockSeconds*1000);
}
function unlock() {
isLocked=false;
document.body.classList.remove('locked');
if(lockTimer){ clearTimeout(lockTimer); lockTimer=null; }
}
// ══ 計算機邏輯 ══
function applyOp(a,op,b){ return op==='+'?a+b:op==='−'?a-b:op==='×'?a*b:op==='÷'?(b?a/b:0):b; }
function fmt(n){ if(isNaN(n)||!isFinite(n))return'Error'; if(Math.abs(n)>=1e12)return n.toExponential(4); return String(parseFloat(n.toPrecision(12))); }
function handleDigit(d) {
if(isLocked)return;
if(justEqual){ display=d; topExpr=''; accumulator=0; pendingOp=null; waitNext=false; justEqual=false; render(); return; }
if(waitNext){ display=d; waitNext=false; }
else{ display=display==='0'?d:display.length>=13?display:display+d; }
justEqual=false; render();
}
function handleDot() {
if(isLocked)return;
if(waitNext){ display='0.'; waitNext=false; render(); return; }
if(!display.includes('.')){ display+='.'; render(); }
}
function handleOp(op) {
if(isLocked)return;
const cur=parseFloat(display);
if(pendingOp&&!waitNext){ const r=applyOp(accumulator,pendingOp,cur); display=fmt(r); accumulator=r; topExpr=display+' '+op; }
else{ accumulator=cur; topExpr=fmt(cur)+' '+op; }
pendingOp=op; waitNext=true; justEqual=false; render();
}
function handleEqual() {
if(isLocked)return;
eqCount++;
if(eqTimer)clearTimeout(eqTimer);
eqTimer=setTimeout(()=>{eqCount=0;},EQ_TIMEOUT);
if(eqCount>=EQ_SECRET){ eqCount=0; openSettings(); return; }
if(!pendingOp)return;
const cur=parseFloat(display), result=applyOp(accumulator,pendingOp,cur);
topExpr=fmt(accumulator)+' '+pendingOp+' '+fmt(cur)+' =';
display=fmt(result); accumulator=result;
pendingOp=null; waitNext=true; justEqual=true; render();
}
function handleClear() {
if(isLocked)return;
display='0'; topExpr=''; accumulator=0; pendingOp=null; waitNext=false; justEqual=false; render();
}
function handleBackspace() {
if(isLocked)return;
if(waitNext||justEqual)return;
display=display.length {
const now=new Date(), target=new Date(now.getTime()+offsetMinutes*60*1000);
const m=target.getMonth()+1, d=String(target.getDate()).padStart(2,'0');
const h=String(target.getHours()).padStart(2,'0'), mi=String(target.getMinutes()).padStart(2,'0');
const magic=parseInt(String(m)+d+h+mi) - snapAcc;
topExpr=fmt(snapAcc)+' +'; display=String(Math.abs(magic));
pendingOp='+'; waitNext=false; justEqual=false; render();
});
}
function triggerMagicCustom() {
if(!customTarget||isNaN(parseInt(customTarget))){ alert('請先在後台設定「模式二」的目標數字!'); return; }
const snapAcc = accumulator;
if(navigator.vibrate) navigator.vibrate([80, 60, 80]);
lock(() => {
const magic=parseInt(customTarget) - snapAcc;
topExpr=fmt(snapAcc)+' +'; display=String(Math.abs(magic));
pendingOp='+'; waitNext=false; justEqual=false; render();
});
}
// ══ 長按設定(dot=模式一,zero=模式二,兩者再次長按解鎖)══
function setupHold(el, onHold, onClick) {
let held=false, timer=null;
el.addEventListener('pointerdown', ()=>{
held=false; el.classList.add('holding');
timer=setTimeout(()=>{
held=true; el.classList.remove('holding');
if(!isLocked){ onHold(); }
}, HOLD_MS);
});
el.addEventListener('pointerup', ()=>{ clearTimeout(timer); el.classList.remove('holding'); if(!held)onClick(); });
el.addEventListener('pointerleave', ()=>{ clearTimeout(timer); el.classList.remove('holding'); });
el.addEventListener('contextmenu', e=>e.preventDefault());
}
setupHold(elAdd, triggerMagicTime, ()=>handleOp('+'));
setupHold(elZero, triggerMagicCustom, ()=>handleDigit('0'));
// ══ 其他按鈕 ══
const bindings={
'btn-1':()=>handleDigit('1'),'btn-2':()=>handleDigit('2'),'btn-3':()=>handleDigit('3'),
'btn-4':()=>handleDigit('4'),'btn-5':()=>handleDigit('5'),'btn-6':()=>handleDigit('6'),
'btn-7':()=>handleDigit('7'),'btn-8':()=>handleDigit('8'),'btn-9':()=>handleDigit('9'),
'btn-add':()=>handleOp('+'),'btn-sub':()=>handleOp('−'),'btn-mul':()=>handleOp('×'),'btn-div':()=>handleOp('÷'),
'btn-eq':handleEqual,'btn-c':handleClear,'btn-pm':handlePlusMinus,
'btn-pct':handlePercent,'btn-paren':()=>{},'backspace-btn':handleBackspace,
};
Object.entries(bindings).forEach(([id,fn])=>{ const el=document.getElementById(id); if(el)el.addEventListener('click',fn); });
// ══ 設定 Modal ══
document.querySelectorAll('.tab-btn').forEach(btn=>{
btn.addEventListener('click',()=>{
document.querySelectorAll('.tab-btn').forEach(b=>b.classList.remove('active'));
document.querySelectorAll('.tab-content').forEach(c=>c.classList.remove('active'));
btn.classList.add('active');
document.getElementById(btn.dataset.tab).classList.add('active');
});
});
function updateTimePreview() {
const m=parseInt(document.getElementById('offset-input').value)||0;
const now=new Date(), target=new Date(now.getTime()+m*60*1000);
const mo=target.getMonth()+1, d=String(target.getDate()).padStart(2,'0');
const h=String(target.getHours()).padStart(2,'0'), mi=String(target.getMinutes()).padStart(2,'0');
document.getElementById('preview-time').textContent=`${mo}${d}${h}${mi} → ${mo}/${d} ${h}:${mi}`;
document.querySelectorAll('.quick-btn').forEach(b=>b.classList.toggle('active',parseInt(b.dataset.m)===m));
}
function updateCustomPreview() {
const v=document.getElementById('custom-input').value.trim();
document.getElementById('preview-custom').textContent=v||'—';
}
document.getElementById('offset-input').addEventListener('input',updateTimePreview);
document.getElementById('custom-input').addEventListener('input',updateCustomPreview);
document.querySelectorAll('.quick-btn').forEach(b=>{
b.addEventListener('click',()=>{ document.getElementById('offset-input').value=b.dataset.m; updateTimePreview(); });
});
document.getElementById('save-time-btn').addEventListener('click',()=>{
const n=parseInt(document.getElementById('offset-input').value);
const s=parseInt(document.getElementById('lock-input').value);
if(isNaN(n)||n1440){alert('請輸入 0~1440 之間的數字');return;}
if(isNaN(s)||s120){alert('鎖定秒數請輸入 5~120');return;}
offsetMinutes=n; localStorage.setItem('magic_offset',n);
lockSeconds=s; localStorage.setItem('magic_lock_seconds',s);
const btn=document.getElementById('save-time-btn'); btn.textContent='✓ 已儲存!';
setTimeout(()=>{ btn.textContent='儲存模式一設定'; overlay.classList.remove('open'); },900);
});
document.getElementById('save-custom-btn').addEventListener('click',()=>{
const v=document.getElementById('custom-input').value.trim();
if(!v||isNaN(parseInt(v))){alert('請輸入純數字');return;}
customTarget=v; localStorage.setItem('magic_custom',v);
const btn=document.getElementById('save-custom-btn'); btn.textContent='✓ 已儲存!';
setTimeout(()=>{ btn.textContent='儲存模式二設定'; overlay.classList.remove('open'); },900);
});
document.getElementById('close-btn').addEventListener('click',()=>overlay.classList.remove('open'));
overlay.addEventListener('click',e=>{ if(e.target===overlay)overlay.classList.remove('open'); });
function openSettings() {
document.getElementById('offset-input').value=offsetMinutes;
document.getElementById('lock-input').value=lockSeconds;
document.getElementById('custom-input').value=customTarget;
updateTimePreview(); updateCustomPreview();
overlay.classList.add('open');
}
// ══ 加入主畫面圖示(canvas 畫計算機圖示)══
function generateIcon() {
const c=document.createElement('canvas'); c.width=192; c.height=192;
const ctx=c.getContext('2d');
// 背景
ctx.fillStyle='#f0f0f0'; ctx.roundRect(0,0,192,192,32); ctx.fill();
// 顯示列
ctx.fillStyle='#e0e0e0'; ctx.roundRect(16,16,160,48,8); ctx.fill();
ctx.fillStyle='#1a1a1a'; ctx.font='bold 28px sans-serif'; ctx.textAlign='right';
ctx.fillText('0',164,52);
// 按鈕格
const cols=4, rows=5, bw=32, bh=22, gap=6, ox=16, oy=76;
const colors=['#e0e0e0','#e0e0e0','#e0e0e0','#616161',
'#fff','#fff','#fff','#616161',
'#fff','#fff','#fff','#616161',
'#fff','#fff','#fff','#616161',
'#fff','#fff','#fff','#1db954'];
for(let r=0;r<rows;r++) for(let col=0;col<cols;col++) {
const i=r*cols+col;
ctx.fillStyle=colors[i];
const x=ox+col*(bw+gap), y=oy+r*(bh+gap);
ctx.beginPath(); ctx.roundRect(x,y,bw,bh,bw/2); ctx.fill();
}
return c.toDataURL('image/png');
}
// 加入 apple-touch-icon
const iconLink=document.createElement('link');
iconLink.rel='apple-touch-icon';
iconLink.href=generateIcon();
document.head.appendChild(iconLink);
render();