Sale!

Viking Dragon Boat Design Norse Birth Chart & Traits / Full Natal Chart Reading – Download, Printed or Framed Print

Price range: $39.96 through $159.96

Natal Birth Chart in a Viking Dragon Boat Design. Learn where your Zodiac planets aligned along with the Norse Rulers and Runes and how they form the traits that make you , you !

Description

Discover the unique cosmic & Norse mythology story of yourself or your loved ones with our Custom Viking Dragon boat Design Birth Chart & Traits print. Download the printable file to print in any size you like, order a print to frame yourself or a wooden framed print ready to hang.

See Frame and Print details below.

Step into a world of nostalgia and sentimentality as you explore the intricate details of your birth. Our Custom Birth Charts are a beautifully designed keepsake that encapsulates your personal journey. Using the alignments of the stars and planets at the time of your birth. The chart connects you to the very moment you entered this world, as well as the traits associated with the Norse rulers and runes.

Celestial Alignment: Unveil the magical alignment of zodiac signs, stars, and planets at the exact time, date, and place of your birth.
Nostalgic Reflection: Revisit cherished memories and emotions as you delve into the profound impact of your birth on your life’s trajectory.
Birthstone and Birth Flowers: Embrace the symbolic significance of your birthstone and flowers, representing your unique qualities and characteristics.
Uncover your Traits: Gain deep insights into your personality traits, strengths, and potential as revealed by the cosmic forces at the moment of your arrival.

Indulge in the enchanting beauty of your cosmic heritage. Whether you’re a proud mum, a strong-willed woman, or an adventurous man, this personalised treasure will evoke a sense of wonder and connection to the universe & Norse Mythology.

A Gift to Treasure

Whether you are searching for a memorable baby gift or a unique birthday present, our Custom Birth Chart is the perfect choice. Delight the special people in your life with a sentimental and nostalgic keepsake. Celebrate their cosmic journey. Each chart is a heartfelt reminder of their place in the universe.

Timeless Design

Our Custom Birth Charts boast stunning designs that capture the essence of your individuality. Each chart is a work of art. Purchase Print only and frame it yourself in your favourite frame or purchase a framed print. You can even choose to have it printed on exquisite tile or slate. Framed and tile or slate prints are packed in a gift box for easy gifting.

Add a Full Natal Chart Reading – A Full Reading gives insight into a person’s personality, life path, and potential by analysing the positions of the Sun, Moon, planets, and other celestial points in relation to the 12 astrological houses.

The Full Readings are sent to your Etsy Messenger for you to download and print.

Available in a range of print types and sizes including

Download Only – Your birth chart will be sent to your email or Etsy messages within 48 hours of purchase. Download the file and print to a size that suits your needs.

Print only – Printed on Premium Semi-Gloss Paper in A4, A3, A2, A1
Framed in wooden White, Black, Light Wood, or Dark Wood ready-to-hang frames in A4, A3, A2, A1

Sizing Measurements – reverse for horizontal prints
A4 (21×29.7 cm)
A3 (29.7×42 cm)
A2 (42×59.4 cm)
A1 (59.4×84.1 cm)

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

Size

A1BlackFrameFullRead, A1LWoodFrameFullRead, A1DWoodFrameFullRead, A2 White Frame Chart, A2 Black Frame Chart, A2 LWood Frame Chart, A2 DWood Frame Chart, A1 White Frame Chart, A1 Black Frame Chart, A1 LWood Frame Chart, A1 DWood Frame Chart, A3Whiteframefullread, A3Blackframefullread, A3LWoodframefullread, A3DWoodframefullread, Download Chart Only, Download/chart/read, A4 Print Chart Only, A4 Print w FullRead, A3 Print Chart Only, A3 Print w FullRead, A2 Print Chart Only, A2 Print w FullRead, A1 Print Chart Only, A1 Print w FullRead, A4 White Frame Chart, A4 Black Frame Chart, A4 LWood Frame Chart, A4 DWood Frame Chart, A4WhiteFrameFullread, A4BlackFrameFullRead, A4LWoodFrameFullRead, A4DWoodFrameFullRead, A3 White Frame Chart, A3 Black Frame Chart, A3 LWood Frame Chart, A3 DWood Frame Chart, A2WhiteFrameFullRead, A2BlackFrameFullRead, A2LWoodFrameFullRead, A2DwoodFrameFullRead, A1WhiteFrameFullRead

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.