<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<title>读心术纸牌魔术</title>
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css"
/>
<link
rel="stylesheet"
href="https://fonts.googleapis.com/css2?family=Unica+One&display=swap"
/>
<style>
body {
font-family: "Unica One", sans-serif;
background-color: #000d1a;
color: #fff;
text-align: center;
padding: 2em;
margin: 0;
overflow-x: hidden;
}
#smoke-bg {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
object-fit: cover;
z-index: -3;
pointer-events: none;
opacity: 0.25;
animation: videoFadeInOut 10s ease-in-out infinite;
}
@keyframes videoFadeInOut {
0%,
100% {
opacity: 0.25;
}
40% {
opacity: 0.05;
}
60% {
opacity: 0.05;
}
}
#video-fade {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: black;
opacity: 0;
pointer-events: none;
z-index: -2;
animation: videoFadeLoop 25s linear infinite;
}
@keyframes videoFadeLoop {
0%,
95% {
opacity: 0;
}
98% {
opacity: 0.25;
}
100% {
opacity: 0;
}
}
h2 {
font-size: 2.3rem;
font-weight: 600;
margin-bottom: 1rem;
text-shadow: 0 0 15px #00f2ff, 0 0 25px #005eff;
}
.card-container {
position: relative;
width: 100%;
max-width: 1000px;
margin: 2rem auto 3rem auto;
height: 180px;
display: flex;
justify-content: center;
align-items: flex-end;
pointer-events: none;
}
.card-container img {
position: absolute;
bottom: 0;
width: 90px;
transform-origin: bottom center;
border-radius: 8px;
transform: scale(1.15) rotate(0deg);
box-shadow: 0 0 25px rgba(0, 200, 255, 0.4);
transition: transform 0.3s ease, box-shadow 0.3s ease;
pointer-events: auto;
left: 50%;
z-index: 1;
}
.card-container img:hover {
transform: scale(1.15) rotate(0deg);
box-shadow: none;
z-index: 2;
}
#card-pick {
margin-top: 5px;
}
#card-group {
margin-top: -15px;
}
.final-reveal {
justify-content: center;
align-items: center;
display: flex;
flex-wrap: wrap;
position: relative;
height: 300px;
width: 100%;
}
.final-reveal img {
position: absolute;
width: 150px;
animation: smoothReveal 0.8s ease-out forwards,
floatGlow 1.5s ease-in-out infinite alternate,
floatUp 5s ease-in-out infinite;
opacity: 0;
transform: scale(0.6) translateY(60px);
left: calc(50% - 75px);
transform-origin: center center;
margin: 0;
box-shadow: 0 0 25px rgba(0, 195, 255, 0.4);
border-radius: 8px;
}
@keyframes smoothReveal {
to {
opacity: 1;
transform: scale(1) translateY(0);
}
}
@keyframes floatGlow {
0% {
transform: scale(1) translateY(0);
box-shadow: 0 0 15px rgba(0, 195, 255, 0.4);
}
100% {
transform: scale(1.05) translateY(-10px);
box-shadow: 0 0 35px rgba(0, 255, 255, 0.6);
}
}
@keyframes floatUp {
0% {
transform: translateY(0) rotate(0deg);
opacity: 0;
}
50% {
transform: translateY(-100px) rotate(5deg);
opacity: 0.7;
}
100% {
transform: translateY(-200px) rotate(-5deg);
opacity: 0;
}
}
button {
font-family: "Unica One", sans-serif;
background: #111;
color: white;
border: 2px solid white;
font-weight: 600;
font-size: 1rem;
padding: 10px 20px;
margin-top: 1rem;
border-radius: 50px;
cursor: pointer;
transition: all 0.25s ease;
display: inline-flex;
align-items: center;
gap: 8px;
}
button:hover {
background: #fff;
color: #111;
box-shadow: 0 0 15px #00c3ff;
}
.hidden {
display: none;
}
@media (max-width: 768px) {
h2 {
font-size: 1.6rem;
}
.card-container {
height: 130px;
}
.card-container img {
width: 70px;
}
.final-reveal img {
width: 110px;
}
}
</style>
</head>
<body>
<div id="stage">
<h2 id="main-title">选择一张牌,并记住它</h2>
<div id="step-1">
<div id="card-pick" class="card-container"></div>
<button onclick="startTrick()">
<i class="fa-solid fa-wand-magic-sparkles"></i> 我选好了
</button>
</div>
<div id="step-2" class="hidden">
<h2>你看到你的牌了吗?</h2>
<div id="group-controls">
<button onclick="answer(true)">
<i class="fa-solid fa-check"></i> 是
</button>
<button onclick="answer(false)">
<i class="fa-solid fa-xmark"></i> 否
</button>
</div>
<div id="card-group" class="card-container"></div>
</div>
<div id="step-3" class="hidden">
<h2>你心里想的牌是</h2>
<div id="revealed-card" class="card-container final-reveal"></div>
<button onclick="shuffleDeck()">
<i class="fa-solid fa-shuffle"></i> 洗牌
</button>
</div>
</div>
<script>
let selectedCards = [];
let cardMap = {};
let currentBit = 0;
let answerBits = 0;
const maxBits = 5;
let fullDeck = [];
const cardPickContainer = document.getElementById("card-pick");
const groupContainer = document.getElementById("card-group");
const revealContainer = document.getElementById("revealed-card");
async function fetchCards() {
try {
const res = await fetch(
"https://deckofcardsapi.com/api/deck/new/draw/?count=52"
);
const data = await res.json();
fullDeck = data.cards;
selectedCards = fullDeck.slice(0, 20);
selectedCards.forEach((card, i) => {
card.binaryValue = i + 1;
cardMap[card.binaryValue] = card;
});
selectedCards.forEach((card) => {
const img = document.createElement("img");
img.src = card.image;
img.alt = `${card.value} of ${card.suit}`;
cardPickContainer.appendChild(img);
});
fanCards(cardPickContainer);
} catch (error) {
console.error("Error fetching cards:", error);
}
}
function fanCards(container) {
const cards = container.querySelectorAll("img");
const total = cards.length;
const spacing = 5;
const startAngle = -Math.floor(total / 2) * spacing;
cards.forEach((card, i) => {
const angle = startAngle + i * spacing;
const offset = (i - total / 2) * 30 - 20;
card.style.transform = `rotate(${angle}deg)`;
card.style.left = `calc(50% + ${offset}px)`;
card.style.bottom = `0`;
card.style.zIndex = i;
});
}
function startTrick() {
document.getElementById("step-1").classList.add("hidden");
document.getElementById("main-title").classList.add("hidden");
document.getElementById("step-2").classList.remove("hidden");
showNextGroup();
}
function showNextGroup() {
groupContainer.innerHTML = "";
const bit = 1 << currentBit;
const group = selectedCards.filter(
(card) => (card.binaryValue & bit) !== 0
);
const availableExtras = fullDeck.filter(
(c) => !selectedCards.includes(c)
);
const shuffledExtras = availableExtras.sort(() => Math.random() - 0.5);
const extras = shuffledExtras.slice(0, 14 - group.length);
const combined = [...group, ...extras].sort(() => Math.random() - 0.5);
combined.forEach((card) => {
const img = document.createElement("img");
img.src = card.image;
img.alt = `${card.value} of ${card.suit}`;
groupContainer.appendChild(img);
});
fanCards(groupContainer);
}
function answer(isYes) {
if (isYes) answerBits += 1 << currentBit;
currentBit++;
if (currentBit >= maxBits) {
revealCard();
} else {
showNextGroup();
}
}
function revealCard() {
document.getElementById("step-2").classList.add("hidden");
document.getElementById("step-3").classList.remove("hidden");
const card = cardMap[answerBits];
if (card) {
const mainCard = document.createElement("img");
mainCard.src = card.image;
mainCard.alt = `${card.value} of ${card.suit}`;
mainCard.className = "main-card";
revealContainer.appendChild(mainCard);
setTimeout(() => {
for (let i = 0; i < 12; i++) {
const img = document.createElement("img");
img.src = card.image;
img.alt = `${card.value} of ${card.suit}`;
const randomLeft = Math.random() * 100;
const randomDelay = Math.random() * 5;
const randomDuration = 8 + Math.random() * 7;
img.style.left = `${randomLeft}%`;
img.style.animationDelay = `${randomDelay}s`;
img.style.animationDuration = `${randomDuration}s`;
revealContainer.appendChild(img);
}
}, 800);
} else {
revealContainer.innerHTML = `<div class="error-message">牌没有找到. 请重试!</div>`;
}
}
function shuffleDeck() {
document.getElementById("step-3").classList.add("hidden");
document.getElementById("main-title").classList.remove("hidden");
document.getElementById("step-1").classList.remove("hidden");
cardPickContainer.innerHTML = "";
groupContainer.innerHTML = "";
revealContainer.innerHTML = "";
currentBit = 0;
answerBits = 0;
selectedCards = [];
cardMap = {};
fetchCards();
}
const isThumbnail =
window.location.href.includes("pen") &&
!window.location.href.includes("edit");
if (isThumbnail) {
window.addEventListener("load", () => {
startTrick();
for (let i = 0; i < maxBits; i++) {
setTimeout(() => {
answer(Math.random() < 0.5);
}, i * 800);
}
});
} else {
fetchCards();
}
</script>
</body>
</html>