No edit summary |
No edit summary |
||
| Line 6: | Line 6: | ||
| id=ts1 | | id=ts1 | ||
| size=768 | | size=768 | ||
| fps= | | fps=8 | ||
| | | seasonAmpDeg=2.5 | ||
| | | wobbleSepDeg=2.5 | ||
| | | solaraUrl=https://www.wyrdehavenchronicl.es/wiki/images/f/fe/Solara.png | ||
| | | vaelusUrl=https://www.wyrdehavenchronicl.es/wiki/images/7/7a/Vaelus.png | ||
| | | skyUrl=http://wyrdehavenchronicl.es/images/WyrdeHaven_Sky.png | ||
| | | cosUrl=http://wyrdehavenchronicl.es/images/Cosinus.png | ||
}} | }} | ||
</noinclude><includeonly> | </noinclude><includeonly> | ||
<div class="wh-twinsuns" style="max-width:<!--{$size|default:768|escape:'html'}-->px;margin:1rem 0;"> | <div class="wh-twinsuns" style="max-width:<!--{$size|default:768|escape:'html'}-->px;margin:1rem 0;"> | ||
<!-- | <!-- centered day number above the slider --> | ||
<div style=" | <div style="text-align:center;margin-bottom:.25rem;"> | ||
<span id="<!--{$id|default:'ts'|escape:'html'}-->-daynum">1</span> / 479 | |||
< | |||
</div> | </div> | ||
<!-- | <!-- slider + cosine (cosine sits behind, same width) --> | ||
<div style="position:relative;display:flex;align-items:center;gap: | <div style="position:relative;display:flex;align-items:center;gap:.5rem;"> | ||
< | <img id="<!--{$id|default:'ts'|escape:'html'}-->-cos" | ||
src="<!--{$cosUrl|default:'http://wyrdehavenchronicl.es/images/Cosinus.png'|escape:'html'}-->" | |||
alt="Cosine" | |||
style="position:absolute;left:0;right:2.2rem;height:22px;object-fit:fill;opacity:.6;pointer-events:none;"/> | |||
<input id="<!--{$id|default:'ts'|escape:'html'}-->-x" type="range" min="1" max="479" value="1" | |||
style="flex:1;position:relative;z-index:1;"> | |||
<button id="<!--{$id|default:'ts'|escape:'html'}-->-play" | |||
<button id="<!--{$id|default:' | style="font-size:1rem;padding:.2rem .5rem;cursor:pointer;min-width:2rem;">▶</button> | ||
style="font-size:1rem;padding:.2rem . | |||
</div> | </div> | ||
<!-- | <!-- add breathing room below the slider so text never crosses the cosine --> | ||
<div style="margin:.25rem 0 .5rem;"> | <div style="height:.5rem"></div> | ||
<div style="margin:.25rem 0 .5rem 0;"> | |||
<strong>Date:</strong> | <strong>Date:</strong> | ||
<span id="<!--{$id|default:' | <span id="<!--{$id|default:'ts'|escape:'html'}-->-datestr">Fortnight of Renewal (Dawning), day 8</span> | ||
</div> | </div> | ||
<!-- | <!-- stage --> | ||
<div id="<!--{$id|default:' | <div id="<!--{$id|default:'ts'|escape:'html'}-->-stage" | ||
style="position:relative;width:100%;aspect-ratio:3/2;isolation:isolate;border:1px solid #333;background:#000;overflow:hidden;"> | style="position:relative;width:100%;aspect-ratio:3/2;isolation:isolate;border:1px solid #333;background:#000;overflow:hidden;"> | ||
<img id="<!--{$id|default:' | <img id="<!--{$id|default:'ts'|escape:'html'}-->-sky" | ||
src="<!--{$ | src="<!--{$skyUrl|default:'http://wyrdehavenchronicl.es/images/WyrdeHaven_Sky.png'|escape:'html'}-->" | ||
decoding="async" draggable="false" | alt="Sky backdrop" | ||
decoding="async" fetchpriority="high" draggable="false" | |||
style="position:absolute;inset:0;width:100%;height:100%;object-fit:cover;object-position:center;z-index:1;"/> | style="position:absolute;inset:0;width:100%;height:100%;object-fit:cover;object-position:center;z-index:1;"/> | ||
<img id="<!--{$id|default:' | <!-- suns --> | ||
src="<!--{$ | <img id="<!--{$id|default:'ts'|escape:'html'}-->-sol" | ||
src="<!--{$solaraUrl|default:'https://www.wyrdehavenchronicl.es/wiki/images/f/fe/Solara.png'|escape:'html'}-->" | |||
style="position:absolute | alt="Solara" | ||
<img id="<!--{$id|default:' | style="position:absolute;transform:translate(-50%,-50%);z-index:3;pointer-events:none;"/> | ||
src="<!--{$ | <img id="<!--{$id|default:'ts'|escape:'html'}-->-vae" | ||
src="<!--{$vaelusUrl|default:'https://www.wyrdehavenchronicl.es/wiki/images/7/7a/Vaelus.png'|escape:'html'}-->" | |||
style="position:absolute | alt="Vaelus" | ||
style="position:absolute;transform:translate(-50%,-50%);z-index:2;pointer-events:none;"/> | |||
</div> | </div> | ||
<div style="margin:.5rem 0 0 0;"> | |||
<div style="margin:.5rem 0 | <div><strong>Relative Solar Inclination (Δ):</strong> <span id="<!--{$id|default:'ts'|escape:'html'}-->-delta">+2.50°</span> <span id="<!--{$id|default:'ts'|escape:'html'}-->-phase">(Closing)</span></div> | ||
<strong>Relative Solar Inclination (Δ):</strong> | <div><strong>Seasonal Equatorial Inclination (barycenter):</strong> <span id="<!--{$id|default:'ts'|escape:'html'}-->-season">+0.00°</span></div> | ||
</div> | </div> | ||
</div> | </div> | ||
| Line 73: | Line 68: | ||
<script type="text/javascript"> | <script type="text/javascript"> | ||
(function(){ | (function(){ | ||
var ID | var ID = "<!--{$id|default:'ts'|escape:'js'}-->"; | ||
var FPS | var FPS = parseFloat("<!--{$fps|default:5|escape:'js'}-->") || 5; | ||
var SEASON_AMP = parseFloat("<!--{$ | // Configurable amplitudes (degrees) | ||
var | var SEASON_AMP = parseFloat("<!--{$seasonAmpDeg|default:2.5|escape:'js'}-->") || 2.5; // barycenter (seasonal) amplitude | ||
// | var WOBBLE_SEP = parseFloat("<!--{$wobbleSepDeg|default:2.5|escape:'js'}-->") || 2.5; // total Solara–Vaelus separation cue | ||
function ge(s){ return document.getElementById(ID + s); } | function ge(s){ return document.getElementById(ID + s); } | ||
var el = { | var el = { | ||
x: | daynum: ge("-daynum"), | ||
play: ge("-play"), | x: ge("-x"), | ||
play: ge("-play"), | |||
cos: ge("-cos"), | |||
sol: | stage: ge("-stage"), | ||
vae: ge("- | sky: ge("-sky"), | ||
delta: ge("-delta"), | sol: ge("-sol"), | ||
vae: ge("-vae"), | |||
dates: ge("-datestr"), | |||
delta: ge("-delta"), | |||
phase: ge("-phase"), | |||
season: ge("-season") | |||
}; | }; | ||
// | // keep cosine exactly behind the slider track | ||
var months = [ | function syncCosWidth(){ | ||
var r = el.x.getBoundingClientRect(); | |||
var C = el.cos; | |||
C.style.left = "0"; | |||
C.style.right = (r.height + 6) + "px"; // reserve space for the play button | |||
C.style.top = ((r.height - 22)/2) + "px"; | |||
} | |||
window.addEventListener("resize", syncCosWidth); | |||
// calendar helpers | |||
var months = ["Primaris","Vernalis","Tharion","Lunarae","Cindrel","Solvane","Auren","Veyrath","Rhendol","Tirien","Gairos","Fenmir","Wyrdrun","Cernis","Eirathen"]; | |||
function | function dateLabel(d){ | ||
// d: 1..479 | // d: 1..479 (year starts at midwinter) | ||
if (d <= 7) { | if(d <= 7){ | ||
return "Fortnight of Renewal (Dawning), day " + | return "Fortnight of Renewal (Dawning), day " + (d+7) + " (FR " + (d+7) + ")"; | ||
} | |||
if(d >= 473){ | |||
} | var k = d - 472; // 1..7 | ||
var | return "Fortnight of Renewal (Fading), day " + k + " (FR " + k + ")"; | ||
} | } | ||
// months: 15 × 31 days = 465 | |||
var n = d - 7; // 1..465 | |||
var mIndex = Math.floor((n-1)/31); // 0..14 | |||
var dayInMonth = ((n-1)%31) + 1; | |||
return dayInMonth + " " + months[mIndex]; | |||
} | } | ||
function | // trig helpers | ||
var TWO_PI = Math.PI*2; | |||
var | function cosCycle(day){ return Math.cos(TWO_PI * (day-1) / 479); } // day 1 -> +1 (midwinter max) | ||
return | |||
// mapping degrees->pixels; assume ~90° sky spans full stage height -> tweakable if needed | |||
function pxPerDeg(){ | |||
var h = el.stage.clientHeight; | |||
return h / 90; // 1° ≈ stageHeight/90 | |||
} | } | ||
function | // place suns | ||
function layout(day){ | |||
var | // seasonal declination (barycenter) | ||
// | var seasonDeg = SEASON_AMP * cosCycle(day); | ||
var | |||
// | // relative split stays constant at WOBBLE_SEP; Solara above when positive season phase starts (day 1) | ||
var | var halfSep = WOBBLE_SEP / 2; | ||
var | // pixel mapping | ||
var | var k = pxPerDeg(); | ||
var | |||
// | var stageRect = el.stage.getBoundingClientRect(); | ||
var W = el.stage.clientWidth, H = el.stage.clientHeight; | |||
var | |||
// center X for both suns | |||
var cx = W * 0.50; | |||
// base vertical position near upper third of sky | |||
var baseY = H * 0.28; | |||
var ySeason = baseY - (seasonDeg * k); | |||
var ySol = ySeason - (halfSep * k); | |||
var yVae = ySeason + (halfSep * k); | |||
// | // set sizes (Solara bigger than Vaelus, keep proportion within 256 canvas scale) | ||
var solSize = Math.round(Math.min(W, H) * 0.14); | |||
var | var vaeSize = Math.round(solSize * 0.65); | ||
Object.assign(el.sol.style, { left: cx+"px", top: ySol+"px", width: solSize+"px", height: "auto" }); | |||
Object.assign(el.vae.style, { left: cx+"px", top: yVae+"px", width: vaeSize+"px", height: "auto" }); | |||
el. | // texts | ||
el.delta.textContent = (WOBBLE_SEP >= 0 ? "+" : "") + WOBBLE_SEP.toFixed(2) + "°"; | |||
el.season.textContent = (seasonDeg>=0?"+":"") + seasonDeg.toFixed(2) + "°"; | |||
el. | |||
// | // Opening/Closing: |season| increasing -> Opening; decreasing -> Closing | ||
// derivative sign of |A cos| at day ~ compare neighboring days | |||
var eps = 1; | |||
el. | var prev = Math.abs(SEASON_AMP * cosCycle(Math.max(1, day-eps))); | ||
var next = Math.abs(SEASON_AMP * cosCycle(Math.min(479, day+eps))); | |||
el.phase.textContent = (next > prev) ? "(Opening)" : "(Closing)"; | |||
} | |||
function update(){ | |||
var d = parseInt(el.x.value,10); | |||
el.daynum.textContent = d; | |||
el.dates.textContent = dateLabel(d); | |||
layout(d); | |||
} | } | ||
// | // scrub throttle | ||
var raf = null; | var raf = null; | ||
el.x.addEventListener("input", function(){ | el.x.addEventListener("input", function(){ | ||
| Line 170: | Line 187: | ||
}); | }); | ||
// | // play/pause | ||
var playing=false | var timer=null, playing=false, interval=1000/Math.max(1,FPS); | ||
function step(){ | function step(){ | ||
var v = parseInt(el.x.value,10); | var v = parseInt(el.x.value,10); | ||
v = (v>= | v = (v>=479)?1:(v+1); | ||
el.x.value = v; | el.x.value = v; | ||
update(); | update(); | ||
| Line 180: | Line 197: | ||
el.play.addEventListener("click", function(){ | el.play.addEventListener("click", function(){ | ||
if(!playing){ | if(!playing){ | ||
playing=true; | playing=true; el.play.textContent="■"; | ||
timer = setInterval(step, interval); | |||
timer=setInterval(step, interval); | }else{ | ||
} else { | playing=false; el.play.textContent="▶"; | ||
playing=false; | |||
clearInterval(timer); timer=null; | clearInterval(timer); timer=null; | ||
} | } | ||
}); | }); | ||
// | // initial sync | ||
syncCosWidth(); | |||
update(); | update(); | ||
})(); | })(); | ||
</script> | </script> | ||
</includeonly> | </includeonly> | ||
Revision as of 20:18, 11 November 2025
This widget displays WyrdeHaven’s Twin Suns with seasonal wobble and calendar.
Usage
1 / 479
Date:
Fortnight of Renewal (Dawning), day 8 (FR 8)
Relative Solar Inclination (Δ):
+2.50° (Closing)
Seasonal Equatorial Inclination (barycenter):
-8.00°