Widget:TwinSuns: Difference between revisions

From WyrdeWiki
No edit summary
No edit summary
Line 84: Line 84:
   var ID  = "<!--{$id|default:'ts'|escape:'js'}-->";
   var ID  = "<!--{$id|default:'ts'|escape:'js'}-->";
   var FPS = parseFloat("<!--{$fps|default:5|escape:'js'}-->") || 5;
   var FPS = parseFloat("<!--{$fps|default:5|escape:'js'}-->") || 5;
   var WOBBLE = parseFloat("<!--{$wobble|default:2.5|escape:'js'}-->") || 2.5;     // Δ amplitude (deg)
   var WOBBLE = parseFloat("<!--{$wobble|default:2.5|escape:'js'}-->") || 2.5;
   var EQUATOR = parseFloat("<!--{$equator|default:8|escape:'js'}-->") || 8;       // seasonal max (deg)
   var EQUATOR = parseFloat("<!--{$equator|default:8|escape:'js'}-->") || 8;
   var SEP = parseFloat("<!--{$sep|default:2.5|escape:'js'}-->") || 2.5;           // horizontal sep (deg)
   var SEP     = parseFloat("<!--{$sep|default:2.5|escape:'js'}-->")   || 2.5;
   var DEGPX = parseFloat("<!--{$degpx|default:10|escape:'js'}-->") || 10;         // px per degree
   var DEGPX   = parseFloat("<!--{$degpx|default:10|escape:'js'}-->")   || 10;


   function ge(s){ return document.getElementById(ID + s); }
   function ge(s){return document.getElementById(ID + s);}
  var el = {
    day: ge("-daylabel"),
    rng: ge("-x"),
    play: ge("-play"),
    date: ge("-date"),
    stage: ge("-stage"),
    sky: ge("-sky"),
    s: ge("-solara"),
    v: ge("-vaelus"),
    rel: ge("-relinc"),
    eq: ge("-eqinc")
  };


   // slightly larger stars (still proportional).  **Only small change.**
   // Safe init (works even if the widget is placed inside tabs/collapsibles)
   function setStarSizes(){
   function ready(fn){
     var w = el.stage.clientWidth;
     if(document.readyState === "complete" || document.readyState === "interactive"){
    var base = Math.round(w * 0.075);             // was 0.05
      setTimeout(fn,0);
     el.s.width = base; el.s.height = base;
     } else {
    el.v.width = Math.round(base * 0.72);
      document.addEventListener("DOMContentLoaded", fn, {once:true});
     el.v.height = Math.round(base * 0.72);
     }
   }
   }
  setStarSizes();
  window.addEventListener("resize", setStarSizes);


   // helpers
   ready(function init(){
  var TAU = Math.PI * 2;
    var el = {
  function phase(d){ return (d-1)/479; }                          // 0..~1
      day: ge("-daylabel"),
  function cosPhase(d){ return Math.cos(TAU * phase(d)); }        // day 1 -> +1
      rng: ge("-x"),
  function sinPhase(d){ return Math.sin(TAU * phase(d)); }
      play: ge("-play"),
  function fmtDeg(x){ var s = (x>=0?"+":""); return s + x.toFixed(2) + "°"; }
      date: ge("-date"),
      stage: ge("-stage"),
      sky: ge("-sky"),
      s: ge("-solara"),
      v: ge("-vaelus"),
      rel: ge("-relinc"),
      eq: ge("-eqinc")
    };


  // Opening/Closing per your definition:
    // If something failed to render, don't throw—just stop quietly
  // Opening  : 0 -> ±Δ  (moving away from 0) => delta * delta' > 0
    for (var k in el){ if(!el[k]) return; }
  // Closing  : ±Δ -> 0  (moving toward 0)    => delta * delta' <= 0
 
  function openingClosing(d){
     var TAU = Math.PI * 2;
    var delta  = WOBBLE * cosPhase(d);
    function cosPhase(d){ return Math.cos(TAU * ((d-1)/479)); }
     var d_delta = -WOBBLE * TAU * sinPhase(d); // derivative of Δ(t)
     function fmtDeg(x){ var s = (x>=0?"+":""); return s + x.toFixed(2) + "°"; }
     return (delta * d_delta > 0) ? "Opening" : "Closing";
     function openingClosing(delta, phase){
  }
      var s = Math.sin(TAU * phase);
      return (-s > 0) ? "Closing" : "Opening";
    }


  // calendar text
    var months = ["Primaris","Vernalis","Tharion","Lunarae","Cindrel","Solvane","Auren","Veyrath","Rhendol","Tirien","Gairos","Fenmir","Wyrdrun","Cernis","Eirathen"];
  var months = ["Primaris","Vernalis","Tharion","Lunarae","Cindrel","Solvane","Auren","Veyrath","Rhendol","Tirien","Gairos","Fenmir","Wyrdrun","Cernis","Eirathen"];
    function dateText(d){
  function dateText(d){
      if(d <= 7)      return "Fortnight of Renewal (Dawning), day " + (d+7) + " (FR " + (d+7) + ")";
    if(d <= 7){
      if(d >= 473){var k=d-472; return "Fortnight of Renewal (Fading), day " + k + " (FR " + k + ")";}
       return "Fortnight of Renewal (Dawning), day " + (d+7) + " (FR " + (d+7) + ")";
       var n = d - 7, m = Math.ceil(n/31), day = ((n-1)%31)+1;
    } else if (d >= 473){
      var k = d - 472; // 1..7
      return "Fortnight of Renewal (Fading), day " + k + " (FR " + k + ")";
    } else {
       var n = d - 7;                // 1..465
      var m = Math.ceil(n / 31);    // 1..15
      var day = ((n-1) % 31) + 1;   // 1..31
       return day + " " + months[m-1];
       return day + " " + months[m-1];
     }
     }
  }


  // split wobble shares (kept as before)
    var SOL_SHARE = 1.0, VAE_SHARE = 1.5, SUM = SOL_SHARE + VAE_SHARE;
  var SOL_SHARE = 1.0, VAE_SHARE = 1.5;
  var SUM = SOL_SHARE + VAE_SHARE;


  function update(){
    function setStarSizes(){
    var d = parseInt(el.rng.value,10);
      var w = el.stage.clientWidth || el.stage.getBoundingClientRect().width || 0;
      if(!w){ return; } // wait for layout
      var base = Math.round(w * 0.05);
      el.s.width = el.s.height = base;
      el.v.width = el.v.height = Math.round(base * 0.72);
    }


     // angles (deg)
     function update(){
    var delta = WOBBLE * cosPhase(d);       // +Δ at day 1
      try{
    var eq    = -EQUATOR * cosPhase(d);     // lowest at day 1, highest midsummer
        var d = parseInt(el.rng.value,10) || 1;
        var phase = (d-1)/479;


    el.day.textContent = d + " / 479";
        var delta = WOBBLE * cosPhase(d);
    el.date.textContent = dateText(d);
        var eq    = -EQUATOR * cosPhase(d);


    el.rel.textContent = fmtDeg(delta) + " (" + openingClosing(d) + ")";
        el.day.textContent  = d + " / 479";
    el.eq.textContent = fmtDeg(eq);
        el.date.textContent = dateText(d);
        el.rel.textContent = fmtDeg(delta) + " (" + openingClosing(delta, phase) + ")";
        el.eq.textContent   = fmtDeg(eq);


    // convert degrees to pixels for stage
        var w = el.stage.clientWidth, h = el.stage.clientHeight;
    var w = el.stage.clientWidth;
        if(!w || !h){ return; }
    var h = el.stage.clientHeight;
    var cx = w * 0.5;
    var cy = h * 0.33; // upper third


    // horizontal visual cue: center pair around cx with SEP/2 offset
        var cx = w * 0.5, cy = h * 0.33;
    var dx = (SEP/2) * DEGPX; // pixels
        var dx = (SEP/2) * DEGPX;


    // vertical: barycenter seasonal tilt (eq) + opposite wobble offsets
        var solOff =  (delta * (SOL_SHARE/SUM)) * DEGPX;
    var solOff =  (delta * (SOL_SHARE/SUM)) * DEGPX;
        var vaeOff = -(delta * (VAE_SHARE/SUM)) * DEGPX;
    var vaeOff = -(delta * (VAE_SHARE/SUM)) * DEGPX;
        var yBase  = cy - (eq * DEGPX);
    var yBase  = cy - (eq * DEGPX);


    // place
        el.s.style.left = (cx - dx) + "px";
    el.s.style.left = (cx - dx) + "px";
        el.v.style.left = (cx + dx) + "px";
    el.v.style.left = (cx + dx) + "px";
        el.s.style.top  = (yBase - solOff) + "px";
    el.s.style.top  = (yBase - solOff) + "px";
        el.v.style.top  = (yBase - vaeOff) + "px";
    el.v.style.top  = (yBase - vaeOff) + "px";
      }catch(e){ /* swallow to avoid killing other widgets */ }
  }
    }


  // slider & play
    // If the stage hasn't sized yet (e.g., inside tabs), retry until it has
  var timer=null, playing=false, interval = 1000/Math.max(1,FPS);
    function ensureSizedThen(fn){
  function step(){
      setStarSizes();
    var v = parseInt(el.rng.value,10);
      if(el.stage.clientWidth){ fn(); }
    v = (v>=479)?1:(v+1);
      else { setTimeout(function(){ ensureSizedThen(fn); }, 50); }
    el.rng.value = v;
    update();
  }
  el.rng.addEventListener("input", update);
  el.play.addEventListener("click", function(){
    if(!playing){
      playing = true; el.play.textContent = "■";
      timer = setInterval(step, interval);
    } else {
      playing = false; el.play.textContent = "▶";
      clearInterval(timer); timer=null;
     }
     }
    var timer=null, playing=false, interval = 1000/Math.max(1,FPS);
    function step(){ var v = parseInt(el.rng.value,10)||1; el.rng.value = (v>=479)?1:(v+1); update(); }
    el.rng.addEventListener("input", update);
    el.play.addEventListener("click", function(){
      if(!playing){ playing=true; el.play.textContent="■"; timer=setInterval(step, interval); }
      else { playing=false; el.play.textContent="▶"; clearInterval(timer); timer=null; }
    });
    window.addEventListener("resize", function(){ setStarSizes(); update(); });
    ensureSizedThen(function(){ update(); });
   });
   });
  update();
})();
})();
</script>
</script>
</includeonly>
</includeonly>

Revision as of 21:02, 11 November 2025

This widget shows WyrdeHaven’s twin suns with seasonal wobble & calendar.

Usage

1 / 479
Date: Fortnight of Renewal (Dawning), day 8 (FR 8)
Sky Solara Vaelus
Relative Solar Inclination (Δ): +2.50° (Closing)
Seasonal Equatorial Inclination (barycenter): -8.00°

param meaning default
size max stage width (px) 768
fps play speed (frames/s) 5
wobble solar relative wobble amplitude (Δ, degrees) 2.5
equator seasonal/barycenter max tilt (degrees) 8
sep visible horizontal separation between suns (degrees) 2.5
degpx vertical degree→pixel scale (px per degree) 10