Toggle menu
Toggle preferences menu
Toggle personal menu
Not logged in
Your IP address will be publicly visible if you make any edits.

Join the Playtest on Steam Now: SpiritVale

MediaWiki:Common.js: Difference between revisions

MediaWiki interface page
No edit summary
Tags: Mobile edit Mobile web edit
No edit summary
Tags: Mobile edit Mobile web edit
Line 17: Line 17:


   // Versioned init guard (allows safe updates without stale blocks)
   // Versioned init guard (allows safe updates without stale blocks)
   var LEVELS_VERSION = 2;
   var LEVELS_VERSION = 3; // bumped: min-level correctness + value label + rAF drag
   if (typeof COMMON.levelsInit === "number" && COMMON.levelsInit >= LEVELS_VERSION) return;
   if (typeof COMMON.levelsInit === "number" && COMMON.levelsInit >= LEVELS_VERSION) return;
   COMMON.levelsInit = LEVELS_VERSION;
   COMMON.levelsInit = LEVELS_VERSION;
Line 115: Line 115:
   }
   }


   function getCardLevel(card, maxLevel) {
  function getCardMinLevel(card, slider, maxLevel) {
    // Prefer explicit attributes if ever present (future-proof), then slider attrs.
    var raw =
      card.getAttribute("data-min-level") ||
      card.getAttribute("data-minLevel") ||
      card.getAttribute("data-level-min");
 
    if (raw == null || raw === "") {
      if (slider) {
        if (isRangeInput(slider)) raw = slider.getAttribute("min");
        else if (isCustomSlider(slider)) raw = slider.getAttribute("aria-valuemin") || slider.getAttribute("data-min");
      }
    }
 
    var min = clampInt(raw, 1, maxLevel, 1);
    if (min > maxLevel) min = maxLevel;
    return min;
  }
 
   function getCardLevel(card, minLevel, maxLevel) {
     var raw =
     var raw =
       card.getAttribute("data-level") ||
       card.getAttribute("data-level") ||
Line 123: Line 142:
         : null);
         : null);


     return clampInt(raw, 1, maxLevel, 1);
     return clampInt(raw, minLevel, maxLevel, minLevel);
  }
 
  function getStep(slider) {
    // Default = 1. Support future: data-step, or <input step="">
    var raw = null;
    if (slider) {
      raw = slider.getAttribute("data-step") || slider.getAttribute("step");
    }
    var s = parseInt(raw, 10);
    if (!s || isNaN(s) || s < 1) s = 1;
    return s;
   }
   }


Line 144: Line 174:
     var span = card.querySelector(".sv-level-num");
     var span = card.querySelector(".sv-level-num");
     if (span) span.textContent = String(level);
     if (span) span.textContent = String(level);
  }
  function setLevelMax(card, maxLevel) {
    var span = card.querySelector(".sv-level-max");
    if (span) span.textContent = String(maxLevel);
   }
   }


Line 149: Line 184:
     if (!scope) return;
     if (!scope) return;


    // JS arrays are 0-based; Lua uses 1-based "level".
     var index = level - 1;
     var index = level - 1;
     var nodes = scope.querySelectorAll(SERIES_SEL);
     var nodes = scope.querySelectorAll(SERIES_SEL);
Line 170: Line 206:
   }
   }


   function getCustomMin(slider) {
   function getCustomMin(slider, fallback) {
     return clampInt(
     return clampInt(
       slider.getAttribute("aria-valuemin") || slider.getAttribute("data-min"),
       slider.getAttribute("aria-valuemin") || slider.getAttribute("data-min"),
       1,
       1,
       999,
       999,
       1
       fallback != null ? fallback : 1
     );
     );
   }
   }


   function getCustomMax(slider) {
   function getCustomMax(slider, fallback) {
     return clampInt(
     return clampInt(
       slider.getAttribute("aria-valuemax") || slider.getAttribute("data-max"),
       slider.getAttribute("aria-valuemax") || slider.getAttribute("data-max"),
       1,
       1,
       999,
       999,
       1
       fallback != null ? fallback : 1
     );
     );
   }
   }


   function getCustomValue(slider, fallback) {
   function getCustomValue(slider, min, max, fallback) {
     return clampInt(
     return clampInt(
       slider.getAttribute("aria-valuenow") || slider.getAttribute("data-value"),
       slider.getAttribute("aria-valuenow") || slider.getAttribute("data-value"),
       1,
       min,
       999,
       max,
       fallback != null ? fallback : 1
       fallback != null ? fallback : min
     );
     );
   }
   }
Line 202: Line 238:
     slider.setAttribute("data-min", String(min));
     slider.setAttribute("data-min", String(min));
     slider.setAttribute("data-max", String(max));
     slider.setAttribute("data-max", String(max));
  }
  function ensureValueLabel(slider) {
    if (!slider || !slider.querySelector || !isCustomSlider(slider)) return null;
    var bubble = slider.querySelector(".sv-level-bubble");
    if (bubble) return bubble;
    bubble = document.createElement("span");
    bubble.className = "sv-level-bubble sv-hidden";
    bubble.setAttribute("hidden", "hidden");
    bubble.setAttribute("aria-hidden", "true");
    // Minimal layout-only styling (no color system changes yet).
    // We'll fully theme this in CSS next.
    bubble.style.position = "absolute";
    bubble.style.top = "-28px";
    bubble.style.left = "0%";
    bubble.style.transform = "translateX(-50%)";
    bubble.style.whiteSpace = "nowrap";
    bubble.style.fontWeight = "900";
    bubble.style.fontSize = "12px";
    bubble.style.lineHeight = "1";
    bubble.style.pointerEvents = "none";
    slider.appendChild(bubble);
    return bubble;
  }
  function unhide(el) {
    if (!el) return;
    if (el.classList) el.classList.remove("sv-hidden");
    if (el.removeAttribute) el.removeAttribute("hidden");
    if (el.style && el.style.display === "none") el.style.display = "";
  }
  function hide(el) {
    if (!el) return;
    if (el.classList) el.classList.add("sv-hidden");
    if (el.setAttribute) el.setAttribute("hidden", "hidden");
  }
  function setValueLabel(slider, value) {
    var bubble = ensureValueLabel(slider);
    if (!bubble) return;
    var min = getCustomMin(slider, 1);
    var max = getCustomMax(slider, min);
    if (max < min) max = min;
    var pct = max === min ? 0 : (value - min) / (max - min);
    if (pct < 0) pct = 0;
    if (pct > 1) pct = 1;
    bubble.textContent = String(value);
    bubble.style.left = pct * 100 + "%";
  }
  function showValueLabel(slider) {
    if (!slider) return;
    var bubble = ensureValueLabel(slider);
    if (!bubble) return;
    // Cancel any pending hide timers.
    if (slider._svBubbleTimer) {
      clearTimeout(slider._svBubbleTimer);
      slider._svBubbleTimer = null;
    }
    unhide(bubble);
  }
  function hideValueLabelSoon(slider, delayMs) {
    if (!slider) return;
    var bubble = slider.querySelector ? slider.querySelector(".sv-level-bubble") : null;
    if (!bubble) return;
    if (slider._svBubbleTimer) clearTimeout(slider._svBubbleTimer);
    slider._svBubbleTimer = setTimeout(function () {
      hide(bubble);
      slider._svBubbleTimer = null;
    }, delayMs != null ? delayMs : 450);
   }
   }


   function updateCustomVisual(slider, value) {
   function updateCustomVisual(slider, value) {
     var min = getCustomMin(slider);
     var min = getCustomMin(slider, 1);
     var max = getCustomMax(slider);
     var max = getCustomMax(slider, min);
     if (max < min) max = min;
     if (max < min) max = min;


Line 220: Line 338:
     if (thumb && thumb.style) thumb.style.left = left + "%";
     if (thumb && thumb.style) thumb.style.left = left + "%";
     if (fill && fill.style) fill.style.width = left + "%";
     if (fill && fill.style) fill.style.width = left + "%";
    // Keep the value label aligned, if present/visible.
    setValueLabel(slider, value);
   }
   }


   function setCustomValue(slider, value) {
   function setCustomValue(slider, value, min, max) {
     slider.setAttribute("aria-valuenow", String(value));
     slider.setAttribute("aria-valuenow", String(value));
     slider.setAttribute("data-value", String(value));
     slider.setAttribute("data-value", String(value));
    // Helpful SR text (doesn't affect visuals)
    slider.setAttribute("aria-valuetext", "Level " + String(value) + " of " + String(max));
     updateCustomVisual(slider, value);
     updateCustomVisual(slider, value);
  }
  function snapToStep(raw, min, max, step) {
    if (step < 1) step = 1;
    var x = raw;
    if (x < min) x = min;
    if (x > max) x = max;
    var snapped = min + Math.round((x - min) / step) * step;
    if (snapped < min) snapped = min;
    if (snapped > max) snapped = max;
    return snapped;
   }
   }


   function valueFromClientX(slider, clientX) {
   function valueFromClientX(slider, clientX) {
     var rect = slider.getBoundingClientRect();
     var rect = slider.getBoundingClientRect();
     var min = getCustomMin(slider);
     var min = getCustomMin(slider, 1);
     var max = getCustomMax(slider);
     var max = getCustomMax(slider, min);
     if (max < min) max = min;
     if (max < min) max = min;


Line 240: Line 377:


     var raw = min + x * (max - min);
     var raw = min + x * (max - min);
     var rounded = Math.round(raw);
     var step = getStep(slider);
     return clampInt(rounded, min, max, min);
     return snapToStep(raw, min, max, step);
   }
   }


   function unhide(el) {
   function setTickLabels(boundary, min, max) {
     if (!el) return;
     if (!boundary || !boundary.querySelector) return;
     if (el.classList) el.classList.remove("sv-hidden");
    var ticks = boundary.querySelector(".sv-level-ticklabels");
     if (el.removeAttribute) el.removeAttribute("hidden");
     if (!ticks) return;
     if (el.style && el.style.display === "none") el.style.display = "";
 
    // Always show min/max (lightweight + useful). We can enhance into real ticks later.
    // Only rebuild if needed to avoid layout churn.
    var curKey = ticks.getAttribute("data-sv-ticks") || "";
    var nextKey = String(min) + "/" + String(max);
     if (curKey === nextKey) return;
    ticks.setAttribute("data-sv-ticks", nextKey);
 
     while (ticks.firstChild) ticks.removeChild(ticks.firstChild);
 
    var a = document.createElement("span");
    a.className = "sv-level-tick sv-level-tick--min";
    a.textContent = String(min);
 
    var b = document.createElement("span");
    b.className = "sv-level-tick sv-level-tick--max";
    b.textContent = String(max);
 
    ticks.appendChild(a);
    ticks.appendChild(b);
  }
 
  function scheduleApply(card, rawLevel) {
    if (!card) return;
 
    card._svLevelNext = rawLevel;
    if (card._svLevelRaf) return;
 
    card._svLevelRaf = window.requestAnimationFrame
      ? requestAnimationFrame(function () {
          card._svLevelRaf = null;
          var v = card._svLevelNext;
          card._svLevelNext = null;
          applyLevelToCard(card, v);
        })
      : (function () {
          card._svLevelRaf = null;
          var v = card._svLevelNext;
          card._svLevelNext = null;
          applyLevelToCard(card, v);
        })();
   }
   }


Line 256: Line 433:
     var maxLevel = getCardMaxLevel(card);
     var maxLevel = getCardMaxLevel(card);
     var slider = getLevelSlider(card);
     var slider = getLevelSlider(card);
     var level = clampInt(rawLevel, 1, maxLevel, getCardLevel(card, maxLevel));
    var minLevel = getCardMinLevel(card, slider, maxLevel);
 
     var level = clampInt(rawLevel, minLevel, maxLevel, getCardLevel(card, minLevel, maxLevel));


     card.setAttribute("data-level", String(level));
     card.setAttribute("data-level", String(level));
     setLevelNumber(card, level);
     setLevelNumber(card, level);
    setLevelMax(card, maxLevel);


     if (slider) {
     if (slider) {
Line 267: Line 447:
     }
     }


     if (slider && maxLevel > 1) {
     if (slider) {
       if (isRangeInput(slider)) {
       if (isRangeInput(slider)) {
         slider.setAttribute("min", "1");
         slider.setAttribute("min", String(minLevel));
         slider.setAttribute("max", String(maxLevel));
         slider.setAttribute("max", String(maxLevel));
        slider.setAttribute("step", String(getStep(slider)));
        slider.setAttribute("aria-valuetext", "Level " + String(level) + " of " + String(maxLevel));
         if (String(slider.value) !== String(level)) slider.value = String(level);
         if (String(slider.value) !== String(level)) slider.value = String(level);
       } else if (isCustomSlider(slider)) {
       } else if (isCustomSlider(slider)) {
         setCustomBounds(slider, 1, maxLevel);
         setCustomBounds(slider, minLevel, maxLevel);
         setCustomValue(slider, level);
         setCustomValue(slider, level, minLevel, maxLevel);
       }
       }
    } else if (slider && isCustomSlider(slider)) {
      setCustomBounds(slider, 1, maxLevel);
      setCustomValue(slider, level);
     }
     }


     var boundary = getLevelBoundary(slider);
     var boundary = getLevelBoundary(slider);
    setTickLabels(boundary, minLevel, maxLevel);
     var scope = getLevelScopeContainer(card, boundary);
     var scope = getLevelScopeContainer(card, boundary);
     applySeriesToScope(scope, boundary, level);
     applySeriesToScope(scope, boundary, level);
Line 298: Line 479:
       var maxLevel = getCardMaxLevel(card);
       var maxLevel = getCardMaxLevel(card);
       var slider = getLevelSlider(card);
       var slider = getLevelSlider(card);
      var minLevel = getCardMinLevel(card, slider, maxLevel);
      var start = getCardLevel(card, minLevel, maxLevel);


      var start = getCardLevel(card, maxLevel);
       if (slider && maxLevel > minLevel) {
       if (slider && maxLevel > 1) {
         if (isRangeInput(slider)) start = clampInt(slider.value, minLevel, maxLevel, start);
         if (isRangeInput(slider)) start = slider.value;
         else if (isCustomSlider(slider)) start = getCustomValue(slider, minLevel, maxLevel, start);
         else if (isCustomSlider(slider)) start = getCustomValue(slider, start);
       }
       }


Line 310: Line 493:
   }
   }


  // Native range inputs
   document.addEventListener(
   document.addEventListener(
     "input",
     "input",
Line 320: Line 504:
       if (!card) return;
       if (!card) return;


       applyLevelToCard(card, t.value);
       scheduleApply(card, t.value);
     },
     },
     true
     true
   );
   );


  // Custom slider pointer interaction (click anywhere + drag)
   document.addEventListener(
   document.addEventListener(
     "pointerdown",
     "pointerdown",
     function (e) {
     function (e) {
      if (e.button != null && e.button !== 0) return; // primary only
       var slider =
       var slider =
         e.target && e.target.closest
         e.target && e.target.closest
Line 348: Line 535:
       }
       }


       applyLevelToCard(card, valueFromClientX(slider, e.clientX));
       // Show value label while interacting
      showValueLabel(slider);
 
      scheduleApply(card, valueFromClientX(slider, e.clientX));
     },
     },
     true
     true
Line 359: Line 549:
       if (e.pointerId !== _drag.pointerId) return;
       if (e.pointerId !== _drag.pointerId) return;
       e.preventDefault();
       e.preventDefault();
       applyLevelToCard(_drag.card, valueFromClientX(_drag.slider, e.clientX));
 
       showValueLabel(_drag.slider);
      scheduleApply(_drag.card, valueFromClientX(_drag.slider, e.clientX));
     },
     },
     true
     true
Line 373: Line 565:
       } catch (err) {}
       } catch (err) {}
     }
     }
    // Let the value label linger briefly, then hide.
    hideValueLabelSoon(_drag.slider, 650);
     _drag = null;
     _drag = null;
   }
   }
Line 379: Line 575:
   document.addEventListener("pointercancel", endDrag, true);
   document.addEventListener("pointercancel", endDrag, true);


  // Keyboard support for custom slider
   document.addEventListener(
   document.addEventListener(
     "keydown",
     "keydown",
Line 391: Line 588:
       if (!card) return;
       if (!card) return;


       var min = getCustomMin(slider);
       var maxLevel = getCardMaxLevel(card);
       var max = getCustomMax(slider);
       var minLevel = getCardMinLevel(card, slider, maxLevel);
       if (max < min) max = min;
       var step = getStep(slider);


       var cur = getCustomValue(slider, getCardLevel(card, max));
       var cur = getCustomValue(slider, minLevel, maxLevel, getCardLevel(card, minLevel, maxLevel));
       var next = cur;
       var next = cur;
      // Shift+Arrow: faster adjustments
      var arrowStep = step;
      if (e.shiftKey) arrowStep = step * 5;


       if (e.key === "ArrowLeft" || e.key === "Left" || e.key === "ArrowDown") {
       if (e.key === "ArrowLeft" || e.key === "Left" || e.key === "ArrowDown") {
         next = cur - 1;
         next = cur - arrowStep;
       } else if (e.key === "ArrowRight" || e.key === "Right" || e.key === "ArrowUp") {
       } else if (e.key === "ArrowRight" || e.key === "Right" || e.key === "ArrowUp") {
         next = cur + 1;
         next = cur + arrowStep;
       } else if (e.key === "Home") {
       } else if (e.key === "Home") {
         next = min;
         next = minLevel;
       } else if (e.key === "End") {
       } else if (e.key === "End") {
         next = max;
         next = maxLevel;
       } else if (e.key === "PageDown") {
       } else if (e.key === "PageDown") {
         next = cur - 5;
         next = cur - step * 5;
       } else if (e.key === "PageUp") {
       } else if (e.key === "PageUp") {
         next = cur + 5;
         next = cur + step * 5;
       } else {
       } else {
         return;
         return;
Line 415: Line 616:


       e.preventDefault();
       e.preventDefault();
       next = clampInt(next, min, max, cur);
       next = clampInt(next, minLevel, maxLevel, cur);
       applyLevelToCard(card, next);
 
       showValueLabel(slider);
      scheduleApply(card, next);
      hideValueLabelSoon(slider, 900);
    },
    true
  );
 
  // Show label on focus (keyboard discoverability), hide on blur
  document.addEventListener(
    "focusin",
    function () {
      var el = document.activeElement;
      if (!el || !el.closest) return;
      var slider = el.closest(".sv-level-range--custom, .sv-level-range[data-sv-slider='1']");
      if (!slider) return;
 
      var max = getCustomMax(slider, 1);
      var min = getCustomMin(slider, 1);
      var v = getCustomValue(slider, min, max, min);
 
      setValueLabel(slider, v);
      showValueLabel(slider);
    },
    true
  );
 
  document.addEventListener(
    "focusout",
    function (e) {
      var t = e.target;
      if (!t || !t.closest) return;
      var slider = t.closest(".sv-level-range--custom, .sv-level-range[data-sv-slider='1']");
      if (!slider) return;
 
      hideValueLabelSoon(slider, 300);
     },
     },
     true
     true