读心术纸牌魔术

Published on
/
/趣玩前端
<!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&amp;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>