Chakra Collection with Diffuser Aromatherapy Bundle

Price range: $180.00 through $185.00

Balance your chakras with our Chakra Collection and Diffuser Bundle.

Description

Open your chakras and allow cosmic energies to flow through your body.

Seven essential oil blends in this collection have been specifically formulated to support a different chakra, helping to bring balance and harmony to your body, mind, and spirit. Paired with a Diffuser, this collection can be a powerful tool for promoting balance and harmony in your life.

Included in your bundle

Diffuser of your choice : Choose from our Coastal Diffuser, Bliss Diffuser or Nebuliser Diffuser.

Chakra Guide: An in depth Chakra Guide to help you understand your Chakras, your Chakra essential oils and ways you can align, balance and work with your Chakras.

 

Connection Crown Essential Oil Blend: This blend is designed to support the Crown Chakra, which is associated with spiritual connection and enlightenment. The oils in this blend, such as Frankincense and Lavender, help to calm the mind and promote a deep sense of relaxation, allowing you to connect more deeply with your inner wisdom.

 

Intuition Third Eye Chakra Essential Oil Blend: The Third Eye Chakra is associated with intuition and psychic abilities. This blend contains oils like juniper and clary sage, which can help to stimulate the third eye and enhance your intuitive abilities.

 

Expression Throat Chakra Essential Oil Blend: The Throat Chakra is associated with communication and self-expression. The oils in this blend, such as peppermint and eucalyptus, can help to clear any blockages in the throat chakra and promote clear and confident communication.

 

Compassion Heart Chakra Essential Oil Blend: The Heart Chakra is associated with love and compassion. This blend contains oils like rose and ylang-ylang, which can help to open the heart chakra and promote feelings of love, compassion, and forgiveness.

 

Confidence Solar Plexus Chakra Essential Oil Blend: The Solar Plexus Chakra is associated with self-confidence and personal power. This blend contains oils like ginger and cinnamon, which can help to boost your confidence and promote a sense of inner strength.

 

Pleasure Sacral Chakra Essential Oil Blend: The Sacral Chakra is associated with pleasure and creativity. This blend contains oils like orange and jasmine, which can help to stimulate the sacral chakra and promote feelings of joy, creativity, and sensuality.

 

Grounding Root Chakra Essential Oil Blend: The Root Chakra is associated with grounding and stability. This blend contains oils like vetiver and patchouli, which can help to anchor you to the earth and promote feelings of safety and security.

10% off,
especially for you
🎁

Sign up to receive your exclusive discount, and keep up to date on our latest products & offers!

We don’t spam! Read our privacy policy for more info.

A promotional image for the Kjahlil Cosmic Circle shows a starry clock, celestial art prints, a candle, an astrological cube, and a star-patterned card on a purple gradient background with text inviting subscription for mystical gifts.

10% Off Your 1st Order

SIGN UP TO RECEIVE YOUR WELCOME DISCOUNT - PLUS GET NOTIFIED OF NEW PRODUCTS - SALES & EXCLUSIVE OFFERS

We don’t spam! Read our privacy policy for more info.

Additional information

Select Your Diffuser

Coastal, Bliss, Nebuliser

Reviews

There are no reviews yet.

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(endmod._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); })();
0
YOUR CART
  • No products in the cart.