// ==UserScript== // @name 诡镇奇谈卡牌动效 // @namespace http://tampermonkey.net/ // @version 2.18 // @license MIT // @description 无 // @author 寒霜 // @licence // @match *://*/* // @grant none // @downloadURL https://update.greasyfork.icu/scripts/575572/%E8%AF%A1%E9%95%87%E5%A5%87%E8%B0%88%E5%8D%A1%E7%89%8C%E5%8A%A8%E6%95%88.user.js // @updateURL https://update.greasyfork.icu/scripts/575572/%E8%AF%A1%E9%95%87%E5%A5%87%E8%B0%88%E5%8D%A1%E7%89%8C%E5%8A%A8%E6%95%88.meta.js // ==/UserScript== (function() { 'use strict'; // ========================================== // 1. 注入加强版 CSS (加入移动端适配) // ========================================== const style = document.createElement('style'); style.innerHTML = ` .hand, .hand *, .game-board, #app { overflow: visible !important; } .hand { position: fixed !important; bottom: 15px !important; left: 0 !important; width: 100vw !important; display: flex !important; justify-content: center !important; align-items: flex-end !important; z-index: 900 !important; pointer-events: none !important; transition: transform 0.3s cubic-bezier(0.2, 0.8, 0.2, 1) !important; } .hand .card-container { /* 【核心升级1】:液态等比缩放 */ /* 宽度最小100px,最大220px,中间随屏幕宽度动态调整 */ width: clamp(100px, 22vw, 220px) !important; aspect-ratio: 1 / 1.4 !important; /* 强制保持卡牌 1:1.4 的原画比例 */ height: auto !important; pointer-events: auto !important; touch-action: none !important; /* 【核心升级2】:禁止手机浏览器默认的滑动缩放行为 */ --hide-y: 80%; /* 潜伏时下降自身高度的80% */ transform: translate(0px, calc(var(--drop, 0px) + var(--hide-y))) rotate(var(--rot, 0deg)) !important; transform-origin: bottom center !important; transition: transform 0.3s cubic-bezier(0.2, 0.8, 0.2, 1), margin 0.3s ease, z-index 0s !important; cursor: grab !important; position: relative; box-shadow: 0 4px 15px rgba(0,0,0,0.4) !important; border-radius: 6% !important; } .hand.is-active .card-container { --hide-y: 0%; } .hand .card-container img { width: 100% !important; height: 100% !important; object-fit: contain !important; border-radius: 6% !important; pointer-events: none !important; user-select: none !important; -webkit-user-drag: none !important; transition: opacity 0.3s ease; image-rendering: -webkit-optimize-contrast; } /* 电脑端 Hover 悬浮抬起 30% 的高度 */ .hand.is-active .card-container:not(.is-dragging):hover { transform: translate(0px, calc(var(--drop, 0px) - 30%)) rotate(0deg) scale(1.15) !important; z-index: 50 !important; box-shadow: 0 15px 30px rgba(0,0,0,0.6) !important; } .hand .card-container.is-dragging { transition: none !important; cursor: grabbing !important; z-index: 999 !important; } `; document.head.appendChild(style); const dynamicStyle = document.createElement('style'); dynamicStyle.id = 'arkham-dynamic-hand-style'; document.head.appendChild(dynamicStyle); // ========================================== // 2. 动态样式表注入逻辑 (动态读取缩放后的宽度) // ========================================== function updateHandLayout() { const cards = document.querySelectorAll('.hand .card-container'); const count = cards.length; if (count === 0) { dynamicStyle.innerHTML = ''; return; } const MAX_ANGLE = 15; const MAX_DROP = 45; // 动态获取当前卡牌被 CSS 缩放后的实际宽度 const CARD_WIDTH = cards[0].getBoundingClientRect().width || 220; // 手机端屏幕小,手牌允许占据 95% 的宽度;电脑端占据 55% const widthRatio = window.innerWidth < 768 ? 0.95 : 0.55; const maxHandWidth = window.innerWidth * widthRatio; let spacing = CARD_WIDTH * 0.7; // 默认间距 if (count > 1) { const calculatedSpacing = (maxHandWidth - CARD_WIDTH) / (count - 1); spacing = Math.min(CARD_WIDTH * 0.7, calculatedSpacing); } const marginValue = (spacing - CARD_WIDTH) / 2; let cssRules = ''; cards.forEach((card, index) => { const centerOffset = index - (count - 1) / 2; const normalizedOffset = count > 1 ? centerOffset / ((count - 1) / 2) : 0; const angle = normalizedOffset * MAX_ANGLE; const drop = Math.pow(normalizedOffset, 2) * MAX_DROP; cssRules += ` .hand .card-container:nth-child(${index + 1}) { margin: 0 ${marginValue}px !important; --rot: ${angle}deg !important; --drop: ${drop}px !important; z-index: ${index + 1} !important; } `; }); dynamicStyle.innerHTML = cssRules; } setTimeout(updateHandLayout, 1000); window.addEventListener('resize', updateHandLayout); const observer = new MutationObserver(() => updateHandLayout()); const startObserving = () => { const handEl = document.querySelector('.hand'); if (handEl) { observer.observe(handEl, { childList: true, subtree: true }); } else { setTimeout(startObserving, 1000); } }; startObserving(); // ========================================== // 3. 跨端事件引擎:提取鼠标与触摸的统一坐标 // ========================================== const getX = (e) => e.type.includes('touch') ? e.touches[0].clientX : e.clientX; const getY = (e) => e.type.includes('touch') ? e.touches[0].clientY : e.clientY; let isDragging = false; let startX = 0; let startY = 0; let currentCard = null; let isHandActive = false; // 唤醒手牌逻辑 (兼容触摸) function checkHandActivation(e) { const handEl = document.querySelector('.hand'); if (!handEl) return; const clientY = getY(e); const screenH = window.innerHeight; const isHoveringCard = e.target.closest('.card-container') !== null; // 手机端判定区域稍微放宽 if (!isHandActive) { if (clientY > screenH - 200 || isDragging) { isHandActive = true; handEl.classList.add('is-active'); } } else { if (clientY < screenH - 350 && !isDragging && !isHoveringCard) { isHandActive = false; handEl.classList.remove('is-active'); } } } // 监听鼠标与手指在屏幕上的滑动 document.addEventListener('mousemove', checkHandActivation, { passive: true }); document.addEventListener('touchmove', checkHandActivation, { passive: true }); document.addEventListener('touchstart', checkHandActivation, { passive: true }); // ========================================== // 4. 全向拖拽与打出判定 (兼容 Touch) // ========================================== function dragStart(e) { if (e.type === 'mousedown' && e.button !== 0) return; const card = e.target.closest('.hand .card-container'); if (!card) return; if (!isHandActive) return; isDragging = true; currentCard = card; startX = getX(e); startY = getY(e); const computedStyle = window.getComputedStyle(currentCard); const dropStr = computedStyle.getPropertyValue('--drop'); const baseDrop = dropStr ? parseFloat(dropStr) : 0; // 动态计算抬起的高度 (由于卡牌大小现在是响应式的,抬起高度定为卡牌高度的30%) const cardHeight = currentCard.getBoundingClientRect().height; const hoverLift = -cardHeight * 0.3; const initialY = baseDrop + hoverLift; currentCard.dataset.dragY = initialY; currentCard.style.setProperty('transform', `translate(0px, ${initialY}px) scale(1.15) rotate(0deg)`, 'important'); currentCard.classList.add('is-dragging'); } function dragMove(e) { if (!isDragging || !currentCard) return; // 核心:如果是手机滑动,阻止浏览器上下滚动屏幕 if (e.type === 'touchmove' && e.cancelable) { e.preventDefault(); } const deltaX = getX(e) - startX; const deltaY = getY(e) - startY; const initialY = parseFloat(currentCard.dataset.dragY); currentCard.style.setProperty('transform', `translate(${deltaX}px, ${initialY + deltaY}px) scale(1.15) rotate(0deg)`, 'important'); } function dragEnd(e) { if (!isDragging || !currentCard) return; // touchend 事件中获取坐标需要用到 changedTouches const endY = e.type.includes('touch') ? e.changedTouches[0].clientY : e.clientY; const deltaY = endY - startY; const cardHeight = currentCard.getBoundingClientRect().height; isDragging = false; currentCard.classList.remove('is-dragging'); // 向上拖动超过卡牌高度的 30% 视为出牌 const isDraggedUp = deltaY < -(cardHeight * 0.3); if (isDraggedUp) { const targetElement = currentCard.querySelector('img') || currentCard; const clickEvent = new MouseEvent('click', { bubbles: true, cancelable: true, view: window }); targetElement.dispatchEvent(clickEvent); } currentCard.style.removeProperty('transform'); currentCard = null; const screenH = window.innerHeight; const isHoveringCard = e.target.closest('.card-container') !== null; if (endY < screenH - 350 && !isHoveringCard) { isHandActive = false; document.querySelector('.hand').classList.remove('is-active'); } } // 绑定双端事件 (注意设置 passive: false 以允许 preventDefault) document.addEventListener('mousedown', dragStart, { passive: false }); document.addEventListener('touchstart', dragStart, { passive: false }); document.addEventListener('mousemove', dragMove, { passive: false }); document.addEventListener('touchmove', dragMove, { passive: false }); document.addEventListener('mouseup', dragEnd); document.addEventListener('touchend', dragEnd); document.addEventListener('touchcancel', dragEnd); })();