Luckyfox fox logo
LUCKYFOX Code walkthrough

Code walkthrough

How the standalone timer file is put together.

The point of this walkthrough is not to make the file look clever. It is to make it understandable. Everything lives in one HTML file, and each section exists for a practical reason.

Lines 8-50

1. Visual states make the timer easier to read at a glance

The page uses simple CSS variables and state-based background colours so “idle”, “running”, “paused”, “countdown”, and “done” all feel different without adding complexity.

      :root {
        --bg-idle: #111;
        --bg-run: #0f7a2a;
        --bg-pause: #b38f00;
        --bg-countdown: #4a4a00;
        --bg-done: #8b0000;
        --ui-bg: rgba(0, 0, 0, 0.35);
        --ui-bg-hover: rgba(0, 0, 0, 0.5);
      }
      html,
      body {
        height: 100%;
        margin: 0;
      }
      body {
        display: flex;
        flex-direction: column;
        align-items: center;
        justify-content: center;
        background: var(--bg-idle);
        color: #fff;
        font-family: system-ui, -apple-system, Segoe UI, Roboto, Helvetica,
          Arial, sans-serif;
        transition: background 200ms ease-in-out;
        text-align: center;
        user-select: none;
        padding: 2vmin;
      }
      body[data-state="idle"] {
        background: var(--bg-idle);
      }
      body[data-state="running"] {
        background: var(--bg-run);
      }
      body[data-state="paused"] {
        background: var(--bg-pause);
      }
      body[data-state="countdown"] {
        background: var(--bg-countdown);
      }
      body[data-state="done"] {
        background: var(--bg-done);
      }

Lines 202-238

2. The routine is defined in one easy-to-edit array

The exercises live in a plain JavaScript array. That makes the file approachable: swap the titles and descriptions, and the timer can support a completely different routine without changing the core logic.

      const EXERCISES = [
        {
          n: 1,
          title: "Arms Up & Down",
          desc: "Pumps circulation through the upper body, recharging your nervous system and firing up metabolism.",
        },
        {
          n: 2,
          title: "Dead Arms",
          desc: "Swing your arms loose like ropes. This shakes out tension, opens fascia, and stimulates lymph drainage, your body’s detox system.",
        },
        {
          n: 3,
          title: "Body Waves",
          desc: "Opens spine and fascia chains, moving stuck energy while resetting your posture and digestion.",
        },
        {
          n: 4,
          title: "Toe Lifts Side to Side",
          desc: "Engages lower body fascia and pumps lymph through the legs, essential for fat loss and anti-aging.",
        },
        {
          n: 5,
          title: "Horse Stand Pulse",
          desc: "Ancient vitality builder. Strengthens legs, core, and circulation while grounding your nervous system.",
        },
        {
          n: 6,
          title: "Bounces",
          desc: "One of the best ways to stimulate lymph flow, burn fat, and energize cells. It’s youth in motion.",
        },
        {
          n: 7,
          title: "Arm Swings",
          desc: "Expels stagnation, resets breath rhythm and increases calorie burn without strain.",
        },
      ];

Lines 251-366

3. The timing logic handles both the main routine and the transition countdowns

The heart of the file is a small set of state variables and a `tick()` function. It tracks elapsed time, swaps between exercise mode and “get ready” countdown mode, and updates the UI as each minute changes.

      let totalSeconds = 7 * 60;
      let remaining = totalSeconds;
      let intervalId = null;
      let running = false;
      let elapsed = 0;
      let lastMinuteIndex = -1;

      // Countdown variables
      let isCountdown = false;
      let countdownRemaining = 0;
      const COUNTDOWN_DURATION = 5;

      function setState(s) {
        body.setAttribute("data-state", s);
      }
      function formatTime(secs) {
        const m = Math.floor(secs / 60)
          .toString()
          .padStart(2, "0");
        const s = (secs % 60).toString().padStart(2, "0");
        return `${m}:${s}`;
      }
      function showExercise(elapsedSec) {
        if (isCountdown) {
          // During countdown, show next exercise info
          const nextMinuteIndex = Math.floor(elapsedSec / 60);
          const nextStep = nextMinuteIndex % EXERCISES.length;
          const nextData = EXERCISES[nextStep];
          stepNumber.textContent = `Get Ready - Exercise ${nextData.n}`;
          titleEl.textContent = nextData.title;
          descEl.textContent = nextData.desc;
          return;
        }

        const minuteIndex = Math.floor(elapsedSec / 60);
        if (minuteIndex !== lastMinuteIndex) {
          lastMinuteIndex = minuteIndex;
          const step = minuteIndex % EXERCISES.length;
          const data = EXERCISES[step];
          stepNumber.textContent = `Exercise ${data.n}`;
          titleEl.textContent = data.title;
          descEl.textContent = data.desc;
        }
      }
      function updateUI() {
        if (isCountdown) {
          timerDisplay.innerHTML = `<div class="thumbs-icon">👍</div>${countdownRemaining}`;
          showExercise(elapsed);
        } else {
          timerDisplay.textContent = formatTime(remaining);
          showExercise(elapsed);
        }
      }
      function tick() {
        if (isCountdown) {
          // Handle countdown between exercises
          if (countdownRemaining > 0) {
            countdownRemaining--;
            updateUI();
            if (countdownRemaining === 0) {
              // Countdown finished, resume normal exercise
              isCountdown = false;
              lastMinuteIndex = -1; // Force refresh of exercise display
              setState("running");
              updateUI();
            }
          }
          return;
        }

        if (remaining > 0) {
          remaining--;
          elapsed++;
          updateUI();

          // Check if we're at a minute boundary and not the last minute
          if (remaining > 0 && remaining % 60 === 0 && elapsed > 0) {
            // Start countdown between exercises
            isCountdown = true;
            countdownRemaining = COUNTDOWN_DURATION;
            setState("countdown");
            updateUI();
            return;
          }

          if (remaining === 0) {
            stopTimer();
            setState("done");
          }
        }
      }
      function startTimer() {
        if (intervalId) return;
        if (remaining === 0) resetTimer();
        running = true;
        setState("running");
        intervalId = setInterval(tick, 1000);
      }
      function stopTimer() {
        clearInterval(intervalId);
        intervalId = null;
        running = false;
      }
      function resetTimer() {
        stopTimer();
        const mins = Math.max(1, parseInt(minutesInput.value, 10) || 7);
        totalSeconds = mins * 60;
        remaining = totalSeconds;
        elapsed = 0;
        lastMinuteIndex = -1;
        isCountdown = false;
        countdownRemaining = 0;
        timerDisplay.textContent = formatTime(remaining);
        showExercise(0);
        setState("idle");
      }

Lines 368-403

4. Keyboard and fullscreen controls keep the experience hands-off

The controls are designed around actual use: space to start or pause, `R` to reset, `F` for fullscreen, and on-screen buttons as a fallback. That keeps it practical whether you are at a desk or across the room.

      document.addEventListener("keydown", (e) => {
        if (document.activeElement === minutesInput) {
          return;
        }
        if (e.code === "Space") {
          e.preventDefault();
          running ? (stopTimer(), setState("paused")) : startTimer();
        }
        if (e.key.toLowerCase() === "r") {
          resetTimer();
        }
        if (e.key.toLowerCase() === "f") {
          toggleFullscreen();
        }
      });
      minutesInput.addEventListener("change", resetTimer);
      resetBtn.addEventListener("click", resetTimer);
      resetBtnTop.addEventListener("click", resetTimer);

      fsBtn.addEventListener("click", toggleFullscreen);
      function toggleFullscreen() {
        if (!document.fullscreenElement) {
          document.documentElement.requestFullscreen?.();
          fsBtn.textContent = "Exit Fullscreen";
        } else {
          document.exitFullscreen?.();
          fsBtn.textContent = "Fullscreen";
        }
      }
      document.addEventListener("fullscreenchange", () => {
        fsBtn.textContent = document.fullscreenElement
          ? "Exit Fullscreen"
          : "Fullscreen";
      });

      resetTimer();

How to adapt it

If you want to make the timer your own, the easiest place to start is the `EXERCISES` array. Change the titles and descriptions, keep the structure the same, and the rest of the file will keep working.