Created
May 3, 2026 06:58
-
-
Save carddass2018-svg/27841cccf19623c72ffe7e57a84d6509 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <!DOCTYPE html> | |
| <html lang="zh-Hant"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no"> | |
| <title>龍珠閃卡 - 強光位移版</title> | |
| <style> | |
| body { background: #000; margin: 0; height: 100vh; display: flex; align-items: center; justify-content: center; overflow: hidden; font-family: sans-serif; transition: transform 0.1s ease-out; transform-style: preserve-3d; } | |
| #auth-overlay { position: fixed; inset: 0; background: #000; z-index: 9999; display: flex; flex-direction: column; align-items: center; justify-content: center; } | |
| #auth-btn { background: #f1c40f; border: none; padding: 20px 50px; border-radius: 12px; font-size: 20px; font-weight: bold; cursor: pointer; } | |
| .panel { position: fixed; top: 0; left: 0; width: 100%; background: #111; padding: 20px; border-bottom: 2px solid #333; z-index: 999; color: #fff; transition: 0.3s; } | |
| .panel.hidden { transform: translateY(-110%); } | |
| #set-btn { position: fixed; top: 15px; left: 15px; z-index: 1000; background: rgba(0,0,0,0.8); color: #fff; border: 1px solid #f1c40f; border-radius: 50%; width: 44px; height: 44px; display: flex; align-items: center; justify-content: center; cursor: pointer; } | |
| #perspective-container { perspective: 1200px; padding: 50px; } | |
| #card { | |
| width: 85vw; max-width: 320px; aspect-ratio: 1/1.46; position: relative; | |
| border-radius: 20px; background: #000; | |
| box-shadow: 0 0 50px rgba(0,0,0,0.9); | |
| overflow: hidden; | |
| -webkit-mask-image: -webkit-radial-gradient(white, black); | |
| mask-image: radial-gradient(white, black); | |
| isolation: isolate; | |
| transform: translateZ(0); | |
| transform-style: preserve-3d; | |
| } | |
| .layer { position: absolute; inset: 0; width: 100%; height: 100%; pointer-events: none; object-fit: fill; border-radius: inherit; } | |
| #base-img { z-index: 1; } | |
| #foil-img { z-index: 5; mix-blend-mode: hard-light; opacity: 0.8; display: none; } | |
| #shine-layer { | |
| z-index: 10; position: absolute; | |
| /* 增大容器範圍以支撐超大範圍位移 */ | |
| inset: -200%; | |
| /* 🚀 讓光暈更凝聚,中心更亮 */ | |
| background: radial-gradient(circle at center, rgba(255,255,255,0.8) 0%, rgba(255,255,255,0.2) 20%, transparent 50%); | |
| background-size: 60% 60%; /* 🚀 縮小背景尺寸讓位移更劇烈 */ | |
| background-repeat: no-repeat; | |
| mix-blend-mode: overlay; | |
| filter: blur(15px); /* 稍微調低模糊度,讓光影邊界明確一點 */ | |
| pointer-events: none; | |
| opacity: 0; | |
| will-change: background-position; | |
| } | |
| canvas { display: none; } | |
| </style> | |
| </head> | |
| <body> | |
| <div id="auth-overlay"><button id="auth-btn" onclick="init()">啟動強光位移版</button></div> | |
| <div id="set-btn" onclick="document.getElementById('p').classList.toggle('hidden')">⚙️</div> | |
| <div class="panel" id="p"> | |
| <div class="row" style="font-size:12px; margin-bottom:10px;">1. 底圖: <input type="file" accept="image/*" onchange="ld(this,'b')"></div> | |
| <div class="row" style="font-size:12px; margin-bottom:10px;">2. 遮罩: <input type="file" accept="image/*" onchange="ld(this,'m')"></div> | |
| <div class="row" style="font-size:12px; margin-bottom:10px;">3. 貼圖: <input type="file" accept="image/*" onchange="ld(this,'f')"></div> | |
| <button id="gen-btn" onclick="render()" style="width:100%; padding:12px; background:#444; color:#888; border:none; border-radius:8px; font-weight:bold; cursor:not-allowed;">生成閃卡</button> | |
| </div> | |
| <div id="perspective-container"> | |
| <div id="card"> | |
| <img id="base-img" class="layer"> | |
| <img id="foil-img" class="layer"> | |
| <div id="shine-layer"></div> | |
| </div> | |
| </div> | |
| <canvas id="cvs"></canvas> | |
| <script> | |
| let cache = { b:null, m:null, f:null }; | |
| const foilE = document.getElementById('foil-img'), | |
| baseE = document.getElementById('base-img'), | |
| shineE = document.getElementById('shine-layer'), | |
| cardE = document.getElementById('card'), | |
| cvs = document.getElementById('cvs'); | |
| function ld(input, type) { | |
| if (!input.files[0]) return; | |
| const reader = new FileReader(); | |
| reader.onload = (e) => { | |
| const img = new Image(); | |
| img.onload = () => { | |
| const max = 900; | |
| let scale = Math.min(1, max / Math.max(img.width, img.height)); | |
| const tmpC = document.createElement('canvas'); | |
| tmpC.width = img.width * scale; tmpC.height = img.height * scale; | |
| tmpC.getContext('2d').drawImage(img, 0, 0, tmpC.width, tmpC.height); | |
| const smallImg = new Image(); | |
| smallImg.onload = () => { | |
| cache[type] = smallImg; | |
| if (type === 'b') baseE.src = smallImg.src; | |
| if (cache.b && cache.m && cache.f) { | |
| const btn = document.getElementById('gen-btn'); | |
| btn.style.background = "#f1c40f"; btn.style.color = "#000"; btn.style.cursor = "pointer"; | |
| } | |
| }; | |
| smallImg.src = tmpC.toDataURL('image/png'); | |
| }; | |
| img.src = e.target.result; | |
| }; | |
| reader.readAsDataURL(input.files[0]); | |
| } | |
| function render() { | |
| if (!cache.b || !cache.m || !cache.f) return; | |
| const w = cache.m.width, h = cache.m.height; | |
| cvs.width = w; cvs.height = h; | |
| const ctx = cvs.getContext('2d'); | |
| ctx.drawImage(cache.m, 0, 0, w, h); | |
| const mData = ctx.getImageData(0,0,w,h).data; | |
| ctx.clearRect(0,0,w,h); | |
| ctx.drawImage(cache.f, 0, 0, w, h); | |
| const fImgData = ctx.getImageData(0,0,w,h); | |
| const fData = fImgData.data; | |
| for (let i = 0; i < fData.length; i += 4) { | |
| const br = (mData[i] + mData[i+1] + mData[i+2]) / 3; | |
| if (br < 80) fData[i+3] = 0; | |
| else if (br < 120) fData[i+3] = ((br - 80) / 40) * 255; | |
| } | |
| ctx.putImageData(fImgData, 0, 0); | |
| foilE.src = cvs.toDataURL(); | |
| foilE.style.display = 'block'; | |
| document.getElementById('p').classList.add('hidden'); | |
| } | |
| function init() { | |
| if (typeof DeviceOrientationEvent !== 'undefined' && typeof DeviceOrientationEvent.requestPermission === 'function') { | |
| DeviceOrientationEvent.requestPermission().then(res => { if(res === 'granted') start(); }); | |
| } else { start(); } | |
| } | |
| function start() { | |
| document.getElementById('auth-overlay').style.display = 'none'; | |
| let lastG = 0, lastB = 0, hueCycle = 0; | |
| const LERP = 0.35; // 🚀 靈敏度再次拉滿 | |
| const OFFSET_B = 60; | |
| window.addEventListener('deviceorientation', e => { | |
| window.requestAnimationFrame(() => { | |
| let curG = e.gamma || 0; | |
| let curB = (e.beta || OFFSET_B) - OFFSET_B; | |
| lastG += (curG - lastG) * LERP; | |
| lastB += (curB - lastB) * LERP; | |
| // 3D 旋轉 | |
| cardE.style.transform = `rotateX(${-Math.max(-25, Math.min(25, lastB * 0.9))}deg) rotateY(${Math.max(-25, Math.min(25, lastG * 0.9))}deg) translateZ(0)`; | |
| // 🌈 顏色變幻加速 | |
| hueCycle += (Math.abs(lastG - curG) + Math.abs(lastB - curB)) * 3.5; | |
| const hue = ((lastG + lastB) * 6) + hueCycle; | |
| foilE.style.filter = `hue-rotate(${hue}deg) saturate(0.5) brightness(1.2)`; | |
| // 🌫️ 🚀 2D 光影極速位移:使用超大倍率 45 | |
| // 這樣即使手機傾斜 10 度,光影中心也會移動 450%,從而快速劃過卡面 | |
| const posX = 50 + lastG * 45; | |
| const posY = 50 + lastB * 45; | |
| shineE.style.backgroundPosition = `${posX}% ${posY}%`; | |
| // 強光感:傾斜時亮度顯著提升 | |
| const intensity = Math.min(1, (Math.abs(lastG) + Math.abs(lastB)) / 40); | |
| shineE.style.opacity = 0.4 + intensity * 0.5; | |
| }); | |
| }); | |
| } | |
| </script> | |
| </body> | |
| </html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment