Sale!

Red Dragon Birth Chart, Astrology Natal Chart, Custom & Personalised

(1 customer review)

Price range: $23.96 through $87.96

Red Dragon Birth Chart. Personalised birth chart and astronomy traits. A beautiful gift for newborn baby or special birthday for him or her. Choose downloadable file, Print only or a framed print.

Description

Custom & Personalised Birth Chart. Each chart is created using the individual’s birth details, giving an insight into the traits of that person according to their astrology. The chart shows where each planet sat at the time of birth. It also includes birthstones, birth flowers, ruling planets, and Chinese zodiac signs. Featuring a beautiful red dragon on an antique paper or green-blue background, this birth chart makes a stunning gift. Whether it’s for a newborn baby, or a special birthday for him or her, this gift is sure to be treasured for years to come. Order a downloadable file, a print only or get it framed and ready to hang. Frames come in white, black or blonde wood. If purchasing download only you will still be charged shipping fees. Please be advised that these fees have been removed from the cost price.

When placing your order please include the following in the order notes

Name of the person the chart is for

date of birth

time of birth including am or pm

place of birth

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

Chart Background Colour

Antique Paper, Green Blue dark

Frame Colour & Size

A4 Print Only, 40x60cm Print Only, Download Only, A4 Black Frame, A4 White Frame, A4 Wood Frame, 40x60cm White Frame, 40x60cm Black Frame, 40x60cm Wood Frame

1 review for Red Dragon Birth Chart, Astrology Natal Chart, Custom & Personalised

  1. Cath Chmielewski

    Very happy with purchase…items just as described…highly recommended…

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.