Lavender Essential Oil $ 20.00
Relax and create a calm environment with Lavender essential oil. The perfect oil for soothing nerves and drifting off to sleep.
Description
A nurturing floral for the mind & body.
Lavender essential oil has been used traditionally in aromatherapy for calming to assist with relief of sleeplessness, stress, mild anxiety and PMS symptoms.
Discover the Benefits
Sleep Aid
Calming
Stress Reducing
Muscle Recovery
Skincare Friendly
Antibacterial
Hormonal Support
Health & Wellbeing
Size: 10ml / 0.34fl.oz
How to use Lavender Pure Essential Oil
Diffuser: Add 6 drops to your diffuser and let the aroma fill your space.
Topically: Add 2 drops to 10ml carrier oil for massage. Use immediately and discard any unused portion.
Inhalation: Apply 3 drops to a pillow or tissue and inhale.
Ingredients
Source: Bulgaria
Botanical name: 100%v/v Lavandula angustifolia herb top flowering essential oil
AUST L: 426414
Like this: Like Loading...
Related
Only logged in customers who have purchased this product may leave a review.
GDPR Cookie Consent with Real Cookie Banner
/* =============================================================
KJ — Birth Chart Generator (Universal, Clean + Stable)
- OSM place search (portal + mini fallback)
- Timezone-by-place-and-date → UT (fixes DST / wrong ASC)
- Swiss Ephemeris bootstrap (WASM) with safe wrappers
- Bright wheel, colored planet glyphs with anti-overlap stacking
- Aspect lines on wheel + responsive aspects table
- Export composite PNG (wheel + table) and email sender
- Elementor-safe wiring; single consent mover (no duplicates)
============================================================= */
/* ---------- Paths (update if you moved files) ---------- */
const JS_DIRECT = "/wp-content/kjahli/swisseph-kjahli.js";
const WASM_DIRECT = "/wp-content/kjahli/swisseph-kjahli.wasm";
const DATA_DIRECT = "/wp-content/kjahli/swisseph-kjahli.data";
const OSM_PROXY = "/wp-content/kjahli/osm-proxy.php";
/* ---------- UI helpers ---------- */
const $ = sel => document.querySelector(sel);
const logBox = $("#kj-log");
const log = (m)=>{ if(logBox){ logBox.textContent += m+"\n"; logBox.scrollTop = logBox.scrollHeight; } console.log(m); };
/* ---------- Colors / glyphs ---------- */
const PLANET_COLORS = {
Sun:"#f6c200", Moon:"#cccccc", Mercury:"#00a2ff", Venus:"#e75480",
Mars:"#d62828", Jupiter:"#ff7f0e", Saturn:"#8d6e63",
Uranus:"#29b6f6", Neptune:"#4b70dd", Pluto:"#9c27b0", "True Node":"#2e7d32", Chiron:"#607d8b"
};
window.PLANET_GLYPH = {
Sun:"☉", Moon:"☾", Mercury:"☿", Venus:"♀", Mars:"♂",
Jupiter:"♃", Saturn:"♄", Uranus:"♅", Neptune:"♆", Pluto:"♇",
"True Node":"☊", Chiron:"⚷"
};
const SIGNS = ["\u2648\uFE0E","\u2649\uFE0E","\u264A\uFE0E","\u264B\uFE0E","\u264C\uFE0E","\u264D\uFE0E","\u264E\uFE0E","\u264F\uFE0E","\u2650\uFE0E","\u2651\uFE0E","\u2652\uFE0E","\u2653\uFE0E"];
/* ---------- Aspect matcher (shared by wheel + exporter) ---------- */
(function exposeAspectMatcher(){
const _norm = d => ((d%360)+360)%360;
const _sep = (a,b)=>{ let d=Math.abs(_norm(a)-_norm(b)); return d>180?360-d:d; };
const ASPECTS = [
{key:"conj", angle: 0, orb:8, color:"#999999", width:1.8, sym:"☌"},
{key:"opp", angle:180, orb:8, color:"#e53935", width:2.4, sym:"☍"},
{key:"tri", angle:120, orb:6, color:"#1e88e5", width:2.0, sym:"△"},
{key:"sqr", angle: 90, orb:6, color:"#e53935", width:2.0, sym:"□"},
{key:"sex", angle: 60, orb:4, color:"#1e88e5", width:1.8, sym:"✶"},
];
window.matchAspect = (a1,a2)=>{
const s=_sep(a1,a2);
for(const a of ASPECTS){ if(Math.abs(s-a.angle)<=a.orb) return a; }
return null;
};
})();
/* =====================================================
TIMEZONE: Convert local birth time (place+date) to UTC
===================================================== */
async function getOffsetMinutesByLatLonAtLocalTime(lat, lon, localISO){
// TimeAPI.io (free, no key)
try{
const u = `https://timeapi.io/api/TimeZone/coordinate?latitude=${lat}&longitude=${lon}`;
const r = await fetch(u); if(r.ok){
const j = await r.json();
if(j?.timeZone){
const tz = j.timeZone;
const off = offsetMinutesForZoneAtLocal(tz, localISO);
if(Number.isFinite(off)) return off;
}
}
}catch(_){/* ignore */}
// worldtimeapi fallback — pick a plausible zone for AU/region
try{
const zones = await (await fetch("https://worldtimeapi.org/api/timezone")).json();
const guess = zones.find(z=>/Australia\//.test(z)) || zones.find(z=>/Etc\/GMT/.test(z));
if(guess){
const off = offsetMinutesForZoneAtLocal(guess, localISO);
if(Number.isFinite(off)) return off;
}
}catch(_){/* ignore */}
// Browser zone as last meaningful fallback
try{
const z = Intl.DateTimeFormat().resolvedOptions().timeZone;
const off = offsetMinutesForZoneAtLocal(z, localISO);
if(Number.isFinite(off)) return off;
}catch(_){/* ignore */}
log("⚠️ Timezone lookup failed — using UTC offset 0");
return 0;
}
function offsetMinutesForZoneAtLocal(timeZone, localISO){
try{
const dtf = new Intl.DateTimeFormat('en-US', {
timeZone, hour12:false,
year:'numeric', month:'2-digit', day:'2-digit',
hour:'2-digit', minute:'2-digit', second:'2-digit'
});
const [dPart,tPart] = localISO.split("T");
const [y,m,d] = dPart.split("-").map(Number);
const [H,Mi] = (tPart||"00:00").split(":").map(Number);
const utcGuess = Date.UTC(y, m-1, d, H, Mi, 0);
const parts = dtf.formatToParts(new Date(utcGuess));
const get = (type)=>+(parts.find(p=>p.type===type)?.value||"0");
const y2=get('year'), M2=get('month'), d2=get('day'), H2=get('hour'), m2=get('minute'), s2=get('second');
const backUTC = Date.UTC(y2, M2-1, d2, H2, m2, s2);
return (utcGuess - backUTC)/60000; // minutes
}catch(e){ log("TZ calc error: "+(e.message||e)); return NaN; }
}
function localToUTC_JD(y,m,d,H,M, lat, lon){
const iso = `${String(y).padStart(4,"0")}-${String(m).padStart(2,"0")}-${String(d).padStart(2,"0")}T${String(H).padStart(2,"0")}:${String(M).padStart(2,"0")}`;
return getOffsetMinutesByLatLonAtLocalTime(lat, lon, iso).then(offMin=> (H + M/60) - (offMin/60));
}
/* =====================================================
Swiss Ephemeris bootstrap
===================================================== */
let SwissephFactory=null, __se=null;
(async()=>{ try{ const mod=await import(JS_DIRECT); SwissephFactory=mod?.default||mod; log("🟣 Module attached"); }catch(e){ log("❌ ESM import failed: "+(e.message||e)); } })();
async function ensureSwiss(){
if(__se) return __se;
if(typeof SwissephFactory!=="function") throw new Error("Swiss factory not found");
const wasmURL=WASM_DIRECT+"?v="+Date.now();
const dataURL=DATA_DIRECT+"?v="+Date.now();
async function fetchInstantiateWasm(imports){
const r=await fetch(wasmURL,{cache:"no-store"});
if(!r.ok) throw new Error("WASM HTTP "+r.status);
const buf=await r.arrayBuffer();
const {instance}=await WebAssembly.instantiate(buf,imports);
return instance;
}
const modOpts={
locateFile:(p)=> p.endsWith(".wasm")?wasmURL : p.endsWith(".data")?dataURL : p,
instantiateWasm:(imports,cb)=>{fetchInstantiateWasm(imports).then(i=>cb(i));return{};},
print:(m)=>log(String(m)),
printErr:(m)=>log("ERR:"+String(m)),
monitorRunDependencies:(n)=>log("⏳ runDeps: "+n)
};
let mod = SwissephFactory(modOpts);
if(mod && typeof mod.then==="function") mod = await mod;
// Wrap low-level exports if needed (Emscripten variations)
if(typeof mod.swe_julday!=="function" && typeof mod._swe_julday==="function"){
const readCString=(ptr,max=512)=>{const heap=mod.HEAPU8;let end=ptr,stop=ptr+max;while(end
mod._swe_julday(y|0,m|0,d|0,+h,g|0);
mod.swe_calc_ut=(jd,ipl,iflag)=>{const px=mod._malloc(24),serr=mod._malloc(256);const rc=mod._swe_calc_ut(+jd,ipl|0,iflag|0,px,serr);const out={rc,x:[NaN,NaN,NaN],serr:""};if(rc>=0){out.x=[mod.HEAPF64[px/8],mod.HEAPF64[px/8+1],mod.HEAPF64[px/8+2]];}else{out.serr=readCString(serr,256);}mod._free(px);mod._free(serr);return out;};
const _housesFn = mod._swe_houses_ex || mod._swe_houses_ex2;
if(typeof _housesFn==="function"){
mod.swe_houses_ex=(jd,lat,lon,hsys)=>{const cusp=mod._malloc(13*8),ascm=mod._malloc(10*8);_housesFn(+jd,0,+lat,+lon,hsys.charCodeAt(0),cusp,ascm);const out={cusp:[],ascmc:[]};for(let i=0;i<13;i++) out.cusp[i]=mod.HEAPF64[cusp/8+i];for(let i=0;i<10;i++) out.ascmc[i]=mod.HEAPF64[ascm/8+i];mod._free(cusp);mod._free(ascm);return out;};
}
if(typeof mod.SE_GREG_CAL==="undefined") mod.SE_GREG_CAL=1;
log("ℹ️ Using wrapped C exports");
}
if(typeof mod.swe_julday!=="function") throw new Error("Swiss Ephemeris did not initialise.");
__se=mod; log("✅ Swiss Ephemeris ready");
return mod;
}
/* =====================================================
Wheel drawing (bright) + aspects on top
===================================================== */
function drawWheel(container,{asc,houses,planets}){
container.innerHTML="";
const size=640,cx=size/2,cy=size/2;
const rOuter=270, rBandO=260, rBandI=228, rInner=180;
const rChord=rInner-14, rGlyph=(rBandI+rInner)/2, rSignGlyph=(rBandO+rBandI)/2;
const SVGNS="http://www.w3.org/2000/svg";
const mk=(n)=>document.createElementNS(SVGNS,n);
const polar=(r,deg)=>{const a=(deg-90)*Math.PI/180; return {x:cx+r*Math.cos(a), y:cy+r*Math.sin(a)};};
const text=(x,y,val,fs,fill)=>{const t=mk("text");t.setAttribute("x",x);t.setAttribute("y",y);t.setAttribute("fill",fill);
t.setAttribute("font-size",fs);t.setAttribute("font-family","Noto Sans Symbols 2, DejaVu Sans, Arial Unicode MS, sans-serif");
t.setAttribute("font-weight","600");t.setAttribute("text-anchor","middle");t.setAttribute("dominant-baseline","middle");t.textContent=val;return t;};
const line=(x1,y1,x2,y2,stroke,sw,op=1)=>{const l=mk("line");l.setAttribute("x1",x1);l.setAttribute("y1",y1);l.setAttribute("x2",x2);l.setAttribute("y2",y2);
l.setAttribute("stroke",stroke);l.setAttribute("stroke-width",sw);l.setAttribute("stroke-linecap","round");l.setAttribute("opacity",op);return l;};
const svg=mk("svg"); svg.classList.add("kj-wheel"); svg.setAttribute("viewBox",`0 0 ${size} ${size}`);
// Outer rim + sign band slices
const rimOuter=mk("circle"); rimOuter.setAttribute("cx",cx);rimOuter.setAttribute("cy",cy);rimOuter.setAttribute("r",rOuter);
rimOuter.setAttribute("fill","#fff"); rimOuter.setAttribute("stroke","#fff"); rimOuter.setAttribute("stroke-width","26"); svg.appendChild(rimOuter);
const bandRing=mk("circle"); bandRing.setAttribute("cx",cx);bandRing.setAttribute("cy",cy);bandRing.setAttribute("r",rBandO+1);
bandRing.setAttribute("fill","none"); bandRing.setAttribute("stroke","#222"); bandRing.setAttribute("stroke-width","2"); svg.appendChild(bandRing);
const signBandFill="#4a4a4f", signSep="#2c2c30";
const toXY=(r,deg)=>{const a=(deg-90)*Math.PI/180;return[cx+r*Math.cos(a),cy+r*Math.sin(a)];};
for(let i=0;i<12;i++){
const start=i*30, end=start+30;
const [x1,y1]=toXY(rBandO,start), [x2,y2]=toXY(rBandO,end), [x3,y3]=toXY(rBandI,end), [x4,y4]=toXY(rBandI,start);
const p=mk("path");
p.setAttribute("d",`M ${x1} ${y1} A ${rBandO} ${rBandO} 0 0 1 ${x2} ${y2} L ${x3} ${y3} A ${rBandI} ${rBandI} 0 0 0 ${x4} ${y4} Z`);
p.setAttribute("fill",signBandFill); svg.appendChild(p);
const s1=polar(rBandO,start), s2=polar(rBandI,start);
svg.appendChild(line(s1.x,s1.y,s2.x,s2.y,signSep,2,0.9));
}
const bandInner=mk("circle"); bandInner.setAttribute("cx",cx);bandInner.setAttribute("cy",cy);bandInner.setAttribute("r",rBandI);
bandInner.setAttribute("fill","none"); bandInner.setAttribute("stroke","#1f1f22"); bandInner.setAttribute("stroke-width","2"); svg.appendChild(bandInner);
// Inner white wheel
const inner=mk("circle"); inner.setAttribute("cx",cx);inner.setAttribute("cy",cy);inner.setAttribute("r",rInner);
inner.setAttribute("fill","#ffffff"); inner.setAttribute("stroke","#d9dbe2"); inner.setAttribute("stroke-width","1.5"); svg.appendChild(inner);
// House cusps (Asc highlighted)
const cusps=(houses?.cusp?.length>=13)?houses.cusp:Array.from({length:13},(_,i)=>i*30);
for(let i=1;i<=12;i++){
const a=cusps[i] ?? (i*30);
const p1=polar(rInner,a), p2=polar(70,a);
const isAsc=(i===1);
svg.appendChild(line(p1.x,p1.y,p2.x,p2.y, isAsc? "#7c3aed":"#c8cbd5", isAsc?2.2:1.2, isAsc?1:.95));
}
// House numbers
for(let i=0;i<12;i++){ const mid=i*30+15; const pos=polar(100,mid); svg.appendChild(text(pos.x,pos.y,String(i+1),12,"#8a8f99")); }
// Sign glyphs (not purple)
for(let i=0;i<12;i++){ const pos=polar(rSignGlyph,i*30+15); svg.appendChild(text(pos.x,pos.y,SIGNS[i],19,"#f2f3f6")); }
// Aspect lines
if(Array.isArray(planets) && planets.length>1 && typeof window.matchAspect==="function"){
for(let i=0;ia.lon-b.lon);
let cur = [sorted[0]];
for(let i=1;i{
const k = group.length;
const start = -(k-1)/2;
for(let i=0;i{
const th=document.createElement("th");
th.innerHTML=(window.PLANET_GLYPH[p.name]||"•")+" "+p.name;
th.style.padding="8px"; th.style.borderBottom="1px solid #e6e6e6";
trh.appendChild(th);
});
thead.appendChild(trh);
const tbody=document.createElement("tbody");
for(let r=0;r=r){ td.textContent="—"; td.style.color="#bbb"; }
else{
const asp=window.matchAspect(planets[r].lon, planets[c].lon);
if(asp){
td.textContent=asp.sym;
td.style.color = (asp.key==="conj") ? "#666" : ((asp.key==="opp"||asp.key==="sqr") ? "#e53935" : "#1e88e5");
td.style.fontWeight="700";
}else{ td.textContent="·"; td.style.color="#bbb"; }
}
tr.appendChild(td);
}
tbody.appendChild(tr);
}
tbl.appendChild(thead); tbl.appendChild(tbody);
}
/* =====================================================
Generate & Clear
===================================================== */
const _norm = d => ((d%360)+360)%360;
async function generate(){
log("=== Generating chart ===");
try{
const se = await ensureSwiss();
const date = $("#kj-date")?.value;
const time = $("#kj-time")?.value;
const c = $("#kj-coords")?.value;
const hsys = $("#kj-house")?.value || "P";
if(!date||!time||!c){ log("❌ Fill date, time, and place"); alert("Please fill date, time, and pick a place."); return; }
const [y,m,d] = date.split("-").map(n=>parseInt(n,10));
const [H,Mi] = (time||"0:0").split(":").map(n=>parseInt(n,10));
const [lat,lon]= c.split(",").map(v=>parseFloat(v.trim()));
// Convert local time (at place) to UT hour
const utHour = await localToUTC_JD(y,m,d,H||0,Mi||0, lat, lon);
const jd = se.swe_julday(y,m,d, utHour, se.SE_GREG_CAL);
log("JD (UT): "+jd.toFixed(6));
// Planets
const PID={Sun:0,Moon:1,Mercury:2,Venus:3,Mars:4,Jupiter:5,Saturn:6,Uranus:7,Neptune:8,Pluto:9,"True Node":11,Chiron:15};
const planets=[];
for(const [name,id] of Object.entries(PID)){
const r=se.swe_calc_ut(jd,id,0);
if(r?.x){ planets.push({name,lon:_norm(r.x[0])}); }
}
// Houses & ASC (Swiss expects geo lon East+)
const hs = se.swe_houses_ex(jd, lat, lon, hsys);
const asc = hs.ascmc?.[0];
// Publish
window.LAST_PLANETS = planets;
window.LAST_ASC = asc;
window.LAST_HOUSES = hs;
drawWheel($("#kj-chart"), {asc, houses:hs, planets});
renderAspectsTable(planets);
}catch(e){ log("❌ Chart error: "+(e.message||e)); alert("Chart error: "+(e.message||e)); }
}
function clearAll(){
["#kj-date","#kj-time","#kj-place","#kj-coords"].forEach(sel=>{ const el=$(sel); if(el) el.value=""; });
if(logBox) logBox.textContent="";
const chart=$("#kj-chart"); if(chart) chart.innerHTML="The wheel will render here.";
const tbl=$("#kj-aspects-table"); if(tbl) tbl.innerHTML="";
}
/* Wire buttons (survive Elementor re-render) */
function wireButtons(){
$("#kj-go")?.addEventListener("click", generate);
$("#kj-clear")?.addEventListener("click", clearAll);
}
document.addEventListener("DOMContentLoaded", wireButtons);
new MutationObserver(wireButtons).observe(document.documentElement,{childList:true,subtree:true});
/* =====================================================
Exporter (wheel + full aspects table) & Email sender
===================================================== */
window.ensureCanvg = window.ensureCanvg || (async function ensureCanvgSafe(){
if (window.Canvg && typeof window.Canvg.from === "function") return window.Canvg;
const sources = [
"/wp-content/kjahli/canvg.min.js",
"https://cdn.jsdelivr.net/npm/canvg@3/dist/browser/canvg.min.js",
"https://unpkg.com/canvg@3/dist/browser/canvg.min.js",
"https://cdnjs.cloudflare.com/ajax/libs/canvg/3.0.10/umd/canvg.min.js"
];
for(const src of sources){
try{
await new Promise((res,rej)=>{ const s=document.createElement("script"); s.src=src; s.async=true; s.onload=res; s.onerror=()=>rej(); document.head.appendChild(s); });
if(window.Canvg && typeof window.Canvg.from==="function") return window.Canvg;
}catch(_){/* try next */}
}
return null; // no throw
});
window.exportCompositePngDataUrl = async function exportCompositePngDataUrl(){
const wheelSVG = document.querySelector('#kj-chart svg.kj-wheel');
if(!wheelSVG) throw new Error("No chart found. Generate a chart first.");
const svgString = new XMLSerializer().serializeToString(wheelSVG);
const wheelSize = 1100;
// Rasterize wheel
const wheelCanvas = document.createElement('canvas');
wheelCanvas.width = wheelSize; wheelCanvas.height = wheelSize;
const wCtx = wheelCanvas.getContext('2d');
wCtx.fillStyle = '#ffffff'; wCtx.fillRect(0,0,wheelSize,wheelSize);
try{
const CanvgRef = await window.ensureCanvg();
if(CanvgRef){ const v = await CanvgRef.from(wCtx, svgString); await v.render(); }
else{ const svg64 = btoa(unescape(encodeURIComponent(svgString))); const img = new Image(); await new Promise((res,rej)=>{ img.onload=res; img.onerror=()=>rej(new Error("SVG rasterize failed")); img.src='data:image/svg+xml;base64,'+svg64; }); wCtx.drawImage(img, 0, 0, wheelSize, wheelSize); }
}catch(_){ const svg64 = btoa(unescape(encodeURIComponent(svgString))); const img = new Image(); await new Promise((res,rej)=>{ img.onload=res; img.onerror=()=>rej(new Error("SVG rasterize failed")); img.src='data:image/svg+xml;base64,'+svg64; }); wCtx.drawImage(img, 0, 0, wheelSize, wheelSize); }
// Table data
const P = Array.isArray(window.LAST_PLANETS) ? window.LAST_PLANETS : [];
const W = 1200, topPad=40, leftPad=40, rightPad=40, gap=28, bottomPad=36;
const headerH=36, rowH=28;
const cols = P.length ? P.length+1 : 0;
const colW = cols ? Math.floor((W-leftPad-rightPad)/cols) : 0;
const tableH = P.length ? (headerH + rowH*P.length + 16) : 0;
const H = topPad + wheelSize + (P.length ? gap + tableH : 0) + bottomPad;
const canvas = document.createElement('canvas');
canvas.width = W; canvas.height = H;
const ctx = canvas.getContext('2d');
// BG
ctx.fillStyle="#ffffff"; ctx.fillRect(0,0,W,H);
// Wheel centered
const wx = Math.round((W-wheelSize)/2), wy = topPad;
ctx.drawImage(wheelCanvas, wx, wy, wheelSize, wheelSize);
if(P.length){
const x0=leftPad, y0=wy+wheelSize+gap;
// header
ctx.fillStyle="#f6f7fb"; ctx.fillRect(x0,y0, colW*cols, headerH);
ctx.strokeStyle="#e6e6e6"; ctx.lineWidth=1;
ctx.fillStyle="#222"; ctx.font="600 14px system-ui, -apple-system, Segoe UI, Arial";
// corner
ctx.strokeRect(x0,y0,colW,headerH);
for(let c=0;c=r){ ctx.fillStyle="#bbb"; ctx.fillText('—', x+colW/2-4, y+19); }
else{
const asp = window.matchAspect(P[r].lon, P[c].lon);
if(asp){ ctx.fillStyle=(asp.key==="conj")?"#666":((asp.key==="opp"||asp.key==="sqr")?"#e53935":"#1e88e5"); ctx.fillText(asp.sym, x+colW/2-6, y+19); }
else { ctx.fillStyle="#bbb"; ctx.fillText('·', x+colW/2-2, y+19); }
}
}
}
}
return canvas.toDataURL('image/png');
};
(function wireEmailButton(){
document.addEventListener("click", async (e)=>{
const btn = e.target.closest && e.target.closest("#kj-send");
if(!btn) return;
e.preventDefault();
const email = ($("#kj-email")?.value||"").trim();
if(!email){ alert("Please enter your email."); return; }
if(!window.kjAjax?.url || !window.kjAjax?.nonce){ alert("Email service not booted. Add the [kj_ajax_boot] shortcode on this page."); return; }
if(!document.querySelector('#kj-chart svg.kj-wheel')){ alert("Please generate a chart first."); return; }
try{
const pngDataUrl = await window.exportCompositePngDataUrl();
const date = $("#kj-date")?.value || '';
const time = $("#kj-time")?.value || '';
const place= $("#kj-place")?.value || '';
const consent = !!$("#kj-consent")?.checked;
const meta = `DOB: ${date} ${time} Place: ${place}`;
const fd = new FormData();
fd.append('action','kj_send_chart');
fd.append('nonce', window.kjAjax.nonce);
fd.append('email', email);
fd.append('consent', consent ? '1' : '0');
fd.append('meta', meta);
fd.append('png', pngDataUrl);
const res = await fetch(window.kjAjax.url, { method:'POST', body: fd, credentials:'same-origin' });
const j = await res.json();
if(!j?.success) throw new Error((j?.data?.message||j?.message)||('HTTP '+res.status));
alert("Chart sent! Check your inbox.");
}catch(err){ console.error(err); alert("Sorry — could not send image: "+(err.message||err)); }
});
})();
/* =====================================================
OSM place search (portal, bright, self-healing)
===================================================== */
(function OSMv3(){
const PROXY = OSM_PROXY || "/wp-content/kjahli/osm-proxy.php";
const LIMIT = 8;
// Badge (status)
let badge = document.getElementById("kj-badge");
if(!badge){
badge = document.createElement("div");
badge.id = "kj-badge";
Object.assign(badge.style,{
position:"fixed",right:"10px",bottom:"10px",zIndex:2147483647,
background:"#111",color:"#fff",font:"12px/1.3 system-ui",
padding:"8px 10px",borderRadius:"8px",boxShadow:"0 6px 16px rgba(0,0,0,.35)",
pointerEvents:'none' // never blocks other UI
});
badge.textContent = "OSM: booting…";
(document.readyState==="loading"
? document.addEventListener("DOMContentLoaded",()=>document.body.appendChild(badge))
: document.body.appendChild(badge));
}
const setBadge = t => { try{ badge.style.display="block"; badge.textContent=t; }catch(_){} };
// Styles for portal
if(!document.getElementById("kj-osm-portal-style")){
const st=document.createElement("style"); st.id="kj-osm-portal-style";
st.textContent = `
#kj-osm-portal{position:fixed;display:none;background:#fff;color:#111;border:1px solid #ccc;border-radius:10px;
max-height:260px;overflow:auto;padding:4px;box-shadow:0 10px 30px rgba(0,0,0,.2);z-index:2147483600}
#kj-osm-portal .itm{padding:8px 10px;border-radius:6px;cursor:pointer;color:#111;font-size:14px}
#kj-osm-portal .itm:hover,#kj-osm-portal .itm.active{background:#e8f0fe;color:#000}
`;
document.head.appendChild(st);
}
// Portal
let portal = document.getElementById("kj-osm-portal");
if(!portal){ portal=document.createElement("div"); portal.id="kj-osm-portal"; document.body.appendChild(portal); }
function findInput(){
return document.getElementById("kj-place")
|| document.querySelector('input#kj-place')
|| document.querySelector('input[placeholder*="Type 3"][type="text"]')
|| document.querySelector('input[placeholder*="letters"][type="text"]')
|| document.querySelector('input[placeholder*="Place"][type="text"]')
|| null;
}
function findCoords(){
return document.getElementById("kj-coords")
|| document.querySelector('input#kj-coords')
|| document.querySelector('input[readonly]')
|| null;
}
let state={input:null,coords:null,items:[],sel:-1,lastId:0,debounce:null};
function ensurePos(){ if(!state.input) return; const r=state.input.getBoundingClientRect(); portal.style.left=r.left+"px"; portal.style.top=(r.bottom+6)+"px"; portal.style.width=r.width+"px"; }
function show(){ ensurePos(); portal.style.display="block"; }
function hide(){ portal.style.display="none"; portal.innerHTML=""; state.sel=-1; state.items=[]; }
function unbind(prev){ if(!prev) return; prev.onfocus=prev.oninput=prev.onkeydown=null; }
function bind(input, coords){
if(!input) return; if(state.input===input) return;
unbind(state.input);
state={...state,input,coords,items:[],sel:-1,lastId:0};
document.addEventListener("pointerdown",(e)=>{ if(e.target===state.input || portal.contains(e.target)) return; hide(); });
window.addEventListener("scroll",()=>{ if(portal.style.display==="block") ensurePos(); }, true);
window.addEventListener("resize",()=>{ if(portal.style.display==="block") ensurePos(); });
state.input.onfocus=()=>{ if(state.input.value.trim().length>=3 && state.items.length) show(); };
state.input.oninput=()=>{
const q=state.input.value.trim();
if(q.length<3){ hide(); return; }
clearTimeout(state.debounce);
const id=++state.lastId;
state.debounce=setTimeout(()=>search(q,id),240);
};
state.input.onkeydown=(e)=>{
if(portal.style.display!=="block"||!state.items.length) return;
if(e.key==="ArrowDown"){ e.preventDefault(); state.sel=(state.sel+1)%state.items.length; updateSel(); }
else if(e.key==="ArrowUp"){ e.preventDefault(); state.sel=(state.sel-1+state.items.length)%state.items.length; updateSel(); }
else if(e.key==="Enter"){ e.preventDefault(); pick(state.items[state.sel>=0?state.sel:0]); }
else if(e.key==="Escape"){ hide(); }
};
setBadge("OSM: ready — type a place");
}
function updateSel(){ [...portal.children].forEach((el,i)=>el.classList.toggle("active", i===state.sel)); }
function pick(p){
const name=p.display_name||p.name||"[no name]";
state.input.value=name;
if(state.coords) state.coords.value=`${p.lat},${p.lon}`;
setBadge("OSM: picked "+(state.coords?state.coords.value:"(no coords field)"));
hide(); state.input.blur();
}
async function search(q,id){
try{
setBadge('OSM: searching… "'+q+'"');
let data=null;
try{
const r=await fetch(`${PROXY}?q=${encodeURIComponent(q)}&limit=${LIMIT}&v=${Date.now()}`,{cache:"no-store"});
if(r.ok) data=await r.json(); else setBadge("OSM: proxy "+r.status+" (fallback)");
}catch(_){ setBadge("OSM: proxy error (fallback)"); }
if(!Array.isArray(data)){
const rr=await fetch(`https://nominatim.openstreetmap.org/search?format=jsonv2&q=${encodeURIComponent(q)}&limit=${LIMIT}`);
if(rr.ok) data=await rr.json(); else setBadge("OSM: public "+rr.status);
}
if(id!==state.lastId) return;
render(Array.isArray(data)?data:[]);
}catch(e){ setBadge("OSM: error "+(e.message||e)); hide(); }
}
function render(results){
portal.innerHTML=""; state.items=results;
if(!state.items.length){ hide(); setBadge("OSM: 0 results"); return; }
for(const p of state.items){
const row=document.createElement("div");
row.className="itm"; row.textContent=p.display_name||p.name||"[no name]";
row.addEventListener("pointerdown",(e)=>{ e.preventDefault(); pick(p); });
portal.appendChild(row);
}
state.sel=-1; updateSel(); ensurePos(); show();
setBadge(`OSM: rendered ${state.items.length} items`);
}
// Poll + observe + watchdog
function poll(ms=250,max=160){
let tries=0; const t=setInterval(()=>{
const inp=findInput(), crd=findCoords(); tries++;
if(inp){ clearInterval(t); bind(inp, crd); }
else if(tries%10===0){ setBadge("OSM: waiting… ("+tries+")"); }
if(tries>max){ clearInterval(t); setBadge("OSM: gave up waiting"); }
},ms);
}
const mo=new MutationObserver(()=>{ const inp=findInput(), crd=findCoords(); if(inp && state.input!==inp) bind(inp, crd); });
mo.observe(document.documentElement,{childList:true,subtree:true});
setInterval(()=>{ const inp=findInput(), crd=findCoords(); if(inp && state.input!==inp) bind(inp, crd); }, 2000);
poll();
})();
/* =====================================================
Consent checkbox: move directly under email (single source of truth)
===================================================== */
(function placeConsentUnderEmail(){
const MSG_TEXT = 'Send me this chart and add me to your newsletter (optional).';
function moveOnce(){
const email = document.getElementById('kj-email');
const cb = document.getElementById('kj-consent');
if(!email || !cb) return false;
// Prefer a wrapping label; else build one so tap target stays big
let label = cb.closest('label');
if(!label){
label = document.createElement('label');
label.style.display = 'flex';
label.style.alignItems = 'center';
label.style.gap = '8px';
label.appendChild(cb);
const span = document.createElement('span');
span.id = 'kj-consent-msg';
span.textContent = MSG_TEXT;
label.appendChild(span);
}else{
if(!label.querySelector('#kj-consent-msg')){
const span = document.createElement('span');
span.id = 'kj-consent-msg';
span.textContent = MSG_TEXT;
label.appendChild(span);
}
}
const emailBlock = email.closest('.elementor-field-group') || email.parentElement;
if(!emailBlock) return false;
let wrap = document.getElementById('kj-consent-wrap');
if(!wrap){ wrap = document.createElement('div'); wrap.id = 'kj-consent-wrap'; }
Object.assign(wrap.style,{ marginTop:'8px', display:'flex', alignItems:'center', gap:'10px', font:'14px/1.4 system-ui,-apple-system,Segoe UI,Arial', color:'#e9e9ee' });
cb.style.width = cb.style.height = '18px';
cb.style.flex = '0 0 auto';
wrap.innerHTML = '';
wrap.appendChild(label);
emailBlock.insertAdjacentElement('afterend', wrap);
return true;
}
const tryMove = ()=>{ try{ moveOnce(); }catch(_){ /* noop */ } };
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', tryMove); else tryMove();
new MutationObserver(tryMove).observe(document.body,{childList:true,subtree:true});
setTimeout(tryMove, 400); setTimeout(tryMove, 900); setTimeout(tryMove, 1600);
})();
/* =====================================================
Mini OSM fallback — binds only if portal is not active
===================================================== */
(function miniOSMKeepAlive(){
const PROXY = "/wp-content/kjahli/osm-proxy.php";
let bound = false, tId = null;
function ensureStyles(){
if(document.getElementById('kj-mini-osm-style')) return;
const s=document.createElement('style'); s.id='kj-mini-osm-style';
s.textContent = `
#kj-mini-osm{position:absolute;left:0;right:0;top:100%;margin-top:6px;background:#fff;color:#111;
border:1px solid #ccc;border-radius:10px;max-height:260px;overflow:auto;padding:4px;box-shadow:0 10px 30px rgba(0,0,0,.2);z-index:2147483600;display:none}
#kj-mini-osm .itm{padding:8px 10px;border-radius:6px;cursor:pointer}
#kj-mini-osm .itm:hover{background:#e8f0fe}
.kj-mini-wrap{position:relative}
`;
document.head.appendChild(s);
}
function bind(){
if(bound) return;
const input = document.getElementById('kj-place');
const coords = document.getElementById('kj-coords');
if(!input) return;
// If the big portal is present and visible, do nothing
const portal = document.getElementById('kj-osm-portal');
if(portal && portal.style.display !== 'none') return;
ensureStyles();
if(!input.parentElement.classList.contains('kj-mini-wrap')){
input.parentElement.classList.add('kj-mini-wrap');
const dd = document.createElement('div'); dd.id='kj-mini-osm';
input.parentElement.appendChild(dd);
}
const list = document.getElementById('kj-mini-osm');
function show(){ list.style.display='block'; }
function hide(){ list.style.display='none'; list.innerHTML=''; }
function render(items){
list.innerHTML='';
if(!items.length){ hide(); return; }
items.forEach(p=>{
const row=document.createElement('div'); row.className='itm';
row.textContent = p.display_name || p.name || '[no name]';
row.onclick = ()=>{ input.value = row.textContent; if(coords) coords.value = `${p.lat},${p.lon}`; hide(); input.blur(); };
list.appendChild(row);
});
show();
}
let lastId=0, deb=null;
input.addEventListener('input', ()=>{
const q=input.value.trim();
if(q.length<3){ hide(); return; }
const id=++lastId;
clearTimeout(deb);
deb=setTimeout(async ()=>{
try{
const u = `${PROXY}?q=${encodeURIComponent(q)}&limit=8&v=${Date.now()}`;
let data=null; try{ const r=await fetch(u,{cache:'no-store'}); if(r.ok) data=await r.json(); }catch(_){/* ignore */}
if(!Array.isArray(data)){ const rr=await fetch(`https://nominatim.openstreetmap.org/search?format=jsonv2&q=${encodeURIComponent(q)}&limit=8`); if(rr.ok) data=await rr.json(); }
if(id!==lastId) return;
render(Array.isArray(data)?data:[]);
}catch(_){ hide(); }
}, 250);
});
input.addEventListener('keydown', (e)=>{ if(e.key==='Escape') hide(); });
document.addEventListener('pointerdown', (e)=>{ if(e.target!==input && !list.contains(e.target)) hide(); }, {capture:true});
bound = true;
}
function tick(){ try{ bind(); }catch(_){/* noop */} tId = setTimeout(tick, 1000); }
tick();
})();
/* =====================================================
Mobile/UI tweaks
===================================================== */
(function injectMobileCSS(){
if(document.getElementById('kj-mobile-fixes')) return;
const st = document.createElement('style'); st.id='kj-mobile-fixes';
st.textContent = `
.kj-bc{ max-width:680px; margin:0 auto; }
#kj-chart{ min-height: 40px; }
#kj-aspects-table{ overflow:auto; max-width:100%; }
/* Ensure overlays never block menu/toggles unless visible */
#kj-osm-portal,[id='kj-mini-osm']{ pointer-events:auto; }
#kj-badge{ pointer-events:none; }
/* Form label contrast on dark */
.kj-bc label, .kj-bc small, #kj-consent-wrap{ color:#e9e9ee; }
`;
document.head.appendChild(st);
})();
Reviews
There are no reviews yet.