ommp.git

ommp

~ Oh My Music Player — a standalone terminal music player built with Rust and ratatui
git clone https://github.com/debuman/ommp.git

site/static/js/status.js (3433B)

      1 // The bar pinned to the bottom of the page is ommp's own status bar. It reads
      2 // the page the way the player reads a track: how far in you are, and what is
      3 // playing right now. Nothing here is required to read the page — if this file
      4 // never loads, the bar simply sits at 0:00.
      5 (function () {
      6   "use strict";
      7 
      8   var fill = document.querySelector(".sb-fill");
      9   var track = document.querySelector(".sb-track");
     10   var elapsed = document.querySelector(".sb-elapsed");
     11   if (!fill || !track || !elapsed) return;
     12 
     13   var TOTAL = 237; // 3:57, the length of the track in the screenshot
     14   var sections = Array.prototype.slice.call(document.querySelectorAll("[data-section]"));
     15 
     16   function clock(seconds) {
     17     var s = Math.round(seconds);
     18     return Math.floor(s / 60) + ":" + String(s % 60).padStart(2, "0");
     19   }
     20 
     21   function update() {
     22     var scrollable = document.documentElement.scrollHeight - window.innerHeight;
     23     var progress = scrollable > 0 ? Math.min(1, Math.max(0, window.scrollY / scrollable)) : 0;
     24 
     25     fill.style.width = (progress * 100).toFixed(2) + "%";
     26     elapsed.textContent = clock(progress * TOTAL);
     27 
     28     // Whichever labelled section has most recently crossed the middle of the
     29     // viewport is the one you are reading.
     30     var middle = window.innerHeight / 2;
     31     var current = "ommp";
     32     for (var i = 0; i < sections.length; i++) {
     33       if (sections[i].getBoundingClientRect().top <= middle) {
     34         current = sections[i].getAttribute("data-section");
     35       }
     36     }
     37     if (track.textContent !== current) track.textContent = current;
     38   }
     39 
     40   var queued = false;
     41   function onScroll() {
     42     if (queued) return;
     43     queued = true;
     44     requestAnimationFrame(function () { queued = false; update(); });
     45   }
     46 
     47   addEventListener("scroll", onScroll, { passive: true });
     48   addEventListener("resize", onScroll);
     49   update();
     50 
     51   // Copy a command. The button is its own element beside the box, so the box
     52   // never resizes — the icon swaps and that is the whole feedback.
     53   document.querySelectorAll(".cmd-copy").forEach(function (btn) {
     54     var cmd = btn.parentNode.querySelector(".cmd");
     55     if (!cmd) return;
     56     btn.addEventListener("click", function () {
     57       if (!navigator.clipboard) return;
     58       // textContent, so the "$ " prompt drawn by CSS is left behind.
     59       navigator.clipboard.writeText(cmd.textContent.trim()).then(function () {
     60         btn.classList.add("is-copied");
     61         setTimeout(function () { btn.classList.remove("is-copied"); }, 1600);
     62       });
     63     });
     64   });
     65 
     66   // The setup you skip crosses itself out as you reach it. The markup ships
     67   // already struck, so this first un-strikes it and then puts the lines back
     68   // one at a time; if the script never runs, the finished state is what shows.
     69   var setup = document.querySelector(".setup-skipped");
     70   if (setup && "IntersectionObserver" in window) {
     71     var lines = Array.prototype.slice.call(setup.children);
     72     setup.parentNode.classList.add("setup-armed");
     73 
     74     var still = matchMedia("(prefers-reduced-motion: reduce)").matches;
     75     var strike = new IntersectionObserver(function (entries) {
     76       entries.forEach(function (e) {
     77         if (!e.isIntersecting) return;
     78         strike.unobserve(e.target);
     79         lines.forEach(function (li, i) {
     80           setTimeout(function () { li.classList.add("is-struck"); }, still ? 0 : i * 260);
     81         });
     82       });
     83     }, { threshold: 0.6 });
     84 
     85     strike.observe(setup);
     86   }
     87 })();