该充电了

Published on
/
/趣玩前端
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>该充电了</title>
    <style>
      * {
        box-sizing: border-box;
        -webkit-font-smoothing: antialiased;
        text-rendering: optimizeLegibility;
        scroll-behavior: smooth;
      }
      html,
      body {
        height: 100%;
        overflow: hidden;
      }

      body {
        font-family: Departure Mono;
        background: #1e1e1e;
        background: linear-gradient(28deg, #111, #272727);
        color: #fff;
        position: relative;
        height: 100%;
      }

      .face {
        --xv: 0;
        --yv: 0;
        background: #141414;
        background: #fff1;
        background-image: radial-gradient(transparent 1px, #111 0);
        background-size: 3px 3px;
        font-weight: 1000;
        position: absolute;
        margin: auto;
        left: 0;
        right: 0;
        top: 0;
        bottom: 3em;
        width: 3em;
        height: 3em;
        border-radius: 10px;
        border: 1px solid #fff2;
        display: flex;
        justify-content: center;
        align-items: center;
        padding-top: 1em;
        overflow: hidden;
        user-select: none;
        box-shadow: 0px 0px 0px 2px #0005, 0px 0px 0px 1px #fff3,
          -2px 2px 2px -1px #000, 0px 0px 12px 0px #fff0,
          inset -2px 2px 3px -2px #fffa;
        cursor: pointer;
        transition: all 0.4s ease-out;

        backdrop-filter: blur(10px);
        perspective: 1000px;
      }
      .face #eyel {
        translate: 0 -1em;
      }
      .face #eyer {
        translate: 0 -1em;
      }

      body:has(.pwr-btn:not(.empty)) .face {
        box-shadow: 0px 0px 0px 2px #0005, 0px 0px 0px 1px #fff3,
          -2px 2px 2px -1px #000, 0px 0px 12px 0px #fff2,
          inset -2px 2px 3px -2px #fffa;
      }

      .face > * {
        text-shadow: -3px 3px 3px #000c, 0 1px 12px #fffa, 0 0 5px #fff;
      }
      body:has(.pwr-btn:not(.empty)) .face:hover > * {
        transform: translateX(calc(var(--yv) * 0.2px)) translateY(
            calc(var(--xv) * -0.2px)
          );
        transition: all 0.4s ease-out;
      }
      body:has(.pwr-btn.empty) .face > * {
        opacity: 0.2;
      }
      .face::before {
        content: '';
        display: block;
        width: 100%;
        height: 100%;
        position: absolute;
        top: 0;
        left: 0;
        box-shadow: inset -2px 2px 2px 0px #fff5, inset 2px -2px 2px 1px #1118;
        scale: 1.1;
        border-radius: 12px;
        transform: translateX(calc(var(--yv) * 0.2px)) translateY(
            calc(var(--xv) * -0.2px)
          );
        transition: all 0.4s ease-out;
      }

      .face::after {
        content: '';
        display: block;
        width: 200%;
        height: 0.6em;
        background: #fff2;
        position: absolute;
        rotate: 24deg;
        top: -50%;
        transition: all 0.4s ease-in-out;
      }
      .face:hover {
        scale: 2;
        box-shadow: -24px 33px 8px -10px #0005, 0px 0px 0px 1px #fff3,
          -2px 2px 2px -1px #000, 0px 0px 12px 0px #fff0,
          inset -2px 2px 3px -2px #fffc !important;
      }
      .face:hover::after {
        top: 150%;
        rotate: 42deg;
      }

      .pwr-btn {
        position: absolute;
        left: 0;
        right: 0;
        top: 2em;
        bottom: 0;
        margin: auto;
        translate: 0 min(10em, 30vh);

        background: #141414;
        background: #5478ad85;
        background-image: radial-gradient(transparent 1px, #111 0);
        background-size: 3px 3px;
        width: fit-content;
        height: fit-content;
        padding: 0.4em 1.6em;
        border: 1px solid #fff4;
        border-radius: 6px;
        cursor: pointer;

        box-shadow: 0px 0px 0px 2px #0005, 0px 0px 0px 1px #fff3,
          0px 0px 0px 1px #fff3, -2px 2px 2px -1px #000, 0px 0px 12px 0px #fff2,
          inset -2px 2px 3px -2px #fffa;

        text-shadow: 0 1px 12px #fffa, 0 0 5px #fff;
        user-select: none;

        transition: all 0.4s ease-in-out;
      }
      .pwr-btn::before {
        content: '';
        display: block;
        position: absolute;
        left: 0;
        bottom: 0;
        border-bottom-left-radius: 6px;
        border-bottom-right-radius: 6px;
        background: #fff;
        mix-blend-mode: difference;
        filter: blur(1px);

        height: calc(var(--power, 0) * 1%);
        width: 100%;
        transition: all 0.4s ease-in-out;
      }
      .pwr-btn.full::before {
        border-radius: 6px;
      }
      .pwr-btn.full {
        filter: brightness(1.2);
        box-shadow: 0px 0px 0px 2px #0005, 0px 0px 6px 1px #fff3,
          0px 0px 12px 1px #fff3, -2px 2px 2px -1px #000, 0px 0px 12px 0px #fff2,
          inset -2px 2px 3px -2px #fffa;
      }
      .pwr-btn.empty {
        filter: brightness(0.5);
      }

      .power-line {
        position: absolute;
        left: 0;
        right: 0;
        top: 0;
        bottom: 0;
        margin: auto;
        translate: 0 calc(min(10em, 30vh) / 2);
        width: 1.6px;
        height: min(10em, 30vh);
        transition: all 0.4s ease-in-out;
        background: repeating-linear-gradient(
          transparent,
          transparent 4px,
          #fff1 4px,
          #fff1 8px
        );
        filter: drop-shadow(0 0 2px #fff);
        overflow: hidden;
      }
      .power-line::before {
        content: '';
        display: block;
        position: absolute;
        bottom: 0;
        width: 100%;
        height: 40%;
        background: linear-gradient(transparent 0%, #fff 50%, transparent 100%);
        animation: generation 1.618s ease-out infinite;
        opacity: 0;
      }
      @keyframes generation {
        0% {
          translate: 0 100%;
        }
        100% {
          translate: 0 calc(-1 * min(10em, 30vh));
        }
      }
      .power-line::after {
        content: '';
        display: block;
        position: absolute;
        bottom: 0;
        left: -2.5em;
        width: 5em;
        height: 100%;
        box-shadow: inset 0 0 2em 1em #fff6;
        opacity: 0;
      }
      body:has(.pwr-btn:not(.empty)) .power-line::before,
      body:has(.pwr-btn:not(.empty)) .power-line::after {
        opacity: 0.6;
        transition: all 1.618s ease-in-out;
      }
      body:has(.pwr-btn:not(.empty)) .power-line {
        background: repeating-linear-gradient(
          transparent,
          transparent 4px,
          #fff4 4px,
          #fff4 8px
        );
      }

      .face .zzz {
        display: none;
      }
      .face.sleeping .zzz {
        display: block;
        color: #fff;
        position: absolute;
        left: 0;
        right: 0;
        top: 0;
        bottom: 0;
        width: 100%;
        height: 100%;
      }
      .face .zzz span {
        display: flex;
        justify-content: center;
        align-items: center;
        width: 100%;
        height: 100%;
        position: absolute;
        left: 20%;
        right: 0;
        top: 20%;
        bottom: 0;
        scale: 0;
        font-size: 1em;
        animation: zzzs 1.5s linear infinite;
      }
      .face .zzz span:nth-child(2) {
        animation-delay: 0.5s;
      }
      .face .zzz span:nth-child(3) {
        animation-delay: 1s;
      }
      @keyframes zzzs {
        0% {
          scale: 0;
          translate: 0;
          rotate: 0;
        }
        100% {
          scale: 1;
          translate: 1em -2em;
          rotate: -45deg;
        }
      }
    </style>
  </head>
  <body>
    <div class="power-line"></div>
    <div class="face">
      <div id="eyel">x</div>
      <div id="mouth">-</div>
      <div id="eyer">x</div>
      <div class="zzz"><span>Z</span><span>z</span><span>z</span></div>
    </div>
    <div class="pwr-btn empty">Generate</div>

    <script>
      let eyel, eyer, mouth;
      let standardInterval;
      let currentMood = 'dead';
      const faceEl = document.querySelector('.face');
      moods(currentMood);

      function cycleChar(eid, charPairs) {
        const element = document.getElementById(eid);
        if (!element) return;
        if (element.timeoutId) {
          clearTimeout(element.timeoutId);
        }
        if (!element.generation) {
          element.generation = 0;
        }
        element.generation++;
        const generation = element.generation;
        let index = 0;
        function updateChar() {
          if (element.generation !== generation) return;
          const [char, duration] = charPairs[index];
          element.textContent = char;
          index = (index + 1) % charPairs.length;
          if (duration != 0) {
            element.timeoutId = setTimeout(updateChar, duration * 1000);
          } else {
            element.timeoutId = null;
          }
        }
        updateChar();
      }

      function clearCharCycles() {
        ['eyel', 'eyer', 'mouth'].forEach((id) => {
          const element = document.getElementById(id);
          if (element && element.timeoutId) {
            clearTimeout(element.timeoutId);
            element.timeoutId = null;
          }
          if (element && element.generation) {
            element.generation++;
          }
        });
      }

      function moods(mood) {
        clearCharCycles();

        if (mood === 'dead') {
          eyel = [['x', 0]];
          eyer = [['x', 0]];
          mouth = [['-', 0]];
        }
        if (mood === 'awake') {
          eyel = [
            ['.', 4],
            ['', 0.3],
            ['\u{2027}', 7],
            ['', 0.3],
          ];
          eyer = [
            ['.', 4],
            ['', 0.3],
            ['\u{2027}', 7],
            ['', 0.3],
          ];
          mouth = [['.', 0]];
        }
        if (mood === 'happy') {
          eyel = [
            ['.', 4],
            ['', 0.3],
            ['\u{2027}', 7],
            ['', 0.3],
          ];
          eyer = [
            ['.', 4],
            ['', 0.3],
            ['\u{2027}', 7],
            ['', 0.3],
          ];
          mouth = [['\u{02d8}', 0]];
        }
        if (mood === 'excited') {
          eyel = [
            ['O', 3],
            ['\u{002a}', 0.4],
            ['O', 1.6],
            ['>', 2],
          ];
          eyer = [
            ['O', 5],
            ['<', 2],
          ];
          mouth = [
            ['\u{02d8}', 5],
            ['\u{25bd}', 2],
          ];
        }
        if (mood === 'concerned') {
          eyel = [
            ['.', 4],
            ['', 0.3],
            ['\u{2027}', 7],
            ['', 0.3],
          ];
          eyer = [
            ['.', 4],
            ['', 0.3],
            ['\u{2027}', 7],
            ['', 0.3],
          ];
          mouth = [['\u{00ba}', 0]];
        }
        if (mood === 'sleeping') {
          eyel = [['-', 4]];
          eyer = [['-', 4]];
          mouth = [
            ['\u{2027}', 4],
            ['\u{00ba}', 1],
          ];
          faceEl.classList.add('sleeping');
        } else if (faceEl.classList.contains('sleeping')) {
          faceEl.classList.remove('sleeping');
        }

        cycleChar('eyel', eyel);
        cycleChar('eyer', eyer);
        cycleChar('mouth', mouth);
      }

      let power = 0;
      const maxPower = 100;
      const decayRate = 1;
      const increment = 2;
      const holdDuration = 16180;
      let isHolding = false;
      let isDropping = false;
      let lastPower = power;
      let accumulatedDrop = 0;
      const powerBtn = document.querySelector('.pwr-btn');

      function updatePower() {
        if (!isHolding) {
          power = Math.max(0, power - decayRate);
          checkPowerDrop();
          updatePowerHeight();
        }
        let newMood = currentMood;

        if (power === 0) {
          powerBtn.classList.add('empty');
          powerBtn.classList.remove('full');
          powerBtn.innerHTML = 'Generate';
        } else if (power >= 100) {
          powerBtn.innerHTML = '~ FULL ~';
          powerBtn.classList.add('full');
          powerBtn.classList.remove('empty');
        } else {
          powerBtn.classList.remove('full');
          powerBtn.classList.remove('empty');
          powerBtn.innerHTML = 'Generate';
        }

        if (power === 0) {
          newMood = 'dead';
        } else if (power > 0 && power < 20) {
          newMood = 'concerned';
        } else if (isDropping && power > 30 && power < 70) {
          newMood = 'sleeping';
        } else if (power >= 20 && power < 50) {
          newMood = 'awake';
        } else if (power >= 50 && power < 90) {
          newMood = 'happy';
        } else if (power >= 90 && power <= 100) {
          newMood = 'excited';
        }

        const moodList = [
          'dead',
          'excited',
          'awake',
          'concerned',
          'happy',
          'sleeping',
        ];
        if (newMood !== currentMood) {
          currentMood = newMood;
          if (moodList.includes(newMood)) {
            moods(newMood);
          } else {
            clearCharCycles();
          }
        }
      }

      function checkPowerDrop() {
        if (power < lastPower) {
          accumulatedDrop += lastPower - power;
        } else if (power > lastPower) {
          accumulatedDrop = 0;
        }
        if (accumulatedDrop >= 20) {
          isDropping = true;
        } else {
          isDropping = false;
        }
        lastPower = power;
      }

      function increasePower() {
        accumulatedDrop = 0;
        if (!isHolding) {
          power = Math.min(maxPower, power + increment);
          if (power === maxPower && !isHolding) {
            startHold();
          }
          updatePowerHeight();
        }
      }

      function updatePowerHeight() {
        powerBtn.style.setProperty('--power', power);
      }

      function startHold() {
        isHolding = true;
        powerBtn.classList.add('full');
        powerBtn.innerHTML = '~ FULL ~';
        setTimeout(() => {
          isHolding = false;
        }, holdDuration);
      }

      powerBtn.addEventListener('click', increasePower);
      standardInterval = setInterval(updatePower, 1000);
      updatePowerHeight();

      const ct = document.querySelector('.face');
      ct.addEventListener('mousemove', function (e) {
        const rect = ct.getBoundingClientRect();
        const se = 42;
        const x = e.clientX - rect.left;
        const y = e.clientY - rect.top;
        const rotateX = (y / rect.height - 0.5) * -se;
        const rotateY = (x / rect.width - 0.5) * se;
        ct.style.transform = `rotateX(${rotateX}deg) rotateY(${rotateY}deg)`;
        ct.style.setProperty('--xv', rotateX);
        ct.style.setProperty('--yv', rotateY);
      });
      ct.addEventListener('mouseleave', function () {
        ct.style.transform = 'rotateX(0deg) rotateY(0deg)';
        ct.style.setProperty('--xv', 0);
        ct.style.setProperty('--yv', 0);
      });
    </script>
  </body>
</html>