diff --git a/viz/index.html b/viz/index.html index 86b5658..2375fbb 100644 --- a/viz/index.html +++ b/viz/index.html @@ -537,7 +537,7 @@

💰 Your holdings — live now. Right-click the sky → 💰 your holdings. Declare what you hold — coins, and metals through their tokens (PAXG is an ounce of gold) — and how much of each, and the amounts become weights: a bigger bag sings louder, each holding's influence auto-scaled by its share of the total. Prices poll straight from the public market to your browser — the server never learns your bag, and the ledger lives only on your machine. Each holding becomes me.btc, me.paxg…, the whole bag breathes as me.portfolio (drift) with me.portfolio.vel as its churn (flux) — and it routes where money lives in every tradition: Hod's quicksilver, the Wardens' wages in the West, the dwarves' gold in Nidavellir, the Metal phase, your hopes and fears in the spread. Stock tickers come later (they need a keyed API); your coins and your gold can sing today. Your finances were already an instrument; we only added the speakers.

-

✨ Your stars — live now. Right-click the sky → ✨ your stars: birth date, time to the ten-minute, the UTC offset where you were born — and the instrument casts your chart in the browser, with a real ephemeris (truncated Meeus for the lights, JPL orbital elements for the planets, precessed to the tropical zodiac; verified against eclipse timings and JPL positions to a fraction of a degree). Computed here, kept here: the server never sees a chart. Then the sky's living conversation with your sky becomes seven sources: natal.harmony (transiting trines & sextiles to your points — grace), natal.tension (squares & oppositions — friction), natal.hits (conjunctions — emphasis), natal.moon (the fast hand: the Moon crossing your chart, fresh every few hours), natal.saturn (the slow teacher, a months-long pressure), natal.birthday (the solar return, swelling as your day approaches) — and the deep one, natal.web: your chart's own tight aspects form a resonance network, and a transit striking one end of your natal opposition rings the whole figure, exactly as astrology reads it. Your natal planets also appear as gold ghosts under the godtime dial, so you can watch Saturn creep toward your natal Sun for weeks before you hear it arrive. Transit orbs are kept tight (aspects peak sharply at exactness, as transit practice demands) so these sources spike and breathe rather than drone. Load anyone's chart — yours, a lover's, a friend's; the instrument plays relationships too.

+

✨ Your stars — live now. Right-click the sky → ✨ your stars: birth date, time to the ten-minute, the UTC offset where you were born — and the instrument casts your chart in the browser, with a real ephemeris (truncated Meeus for the lights, JPL orbital elements for the planets, precessed to the tropical zodiac; verified against eclipse timings and JPL positions to a fraction of a degree). Computed here, kept here: the server never sees a chart. Then the sky's living conversation with your sky becomes seven sources: natal.harmony (transiting trines & sextiles to your points — grace), natal.tension (squares & oppositions — friction), natal.hits (conjunctions — emphasis), natal.moon (the fast hand: the Moon crossing your chart, fresh every few hours), natal.saturn (the slow teacher, a months-long pressure), natal.birthday (the solar return, swelling as your day approaches) — and the deep one, natal.web: your chart's own tight aspects form a resonance network, and a transit striking one end of your natal opposition rings the whole figure, exactly as astrology reads it. Your natal planets also appear as gold ghosts under the godtime dial, so you can watch Saturn creep toward your natal Sun for weeks before you hear it arrive. Transit orbs are kept tight (aspects peak sharply at exactness, as transit practice demands) so these sources spike and breathe rather than drone. Load anyone's chart — yours, a lover's, a friend's; the instrument plays relationships too. Give it your birthplace (lat/lon) and the chart gains its angles: the ascendant and midheaven, computed from the sidereal time of your birth (the formula is verified against the sunrise itself — the Sun stands on the ascendant as it rises). Your rising sign is announced when the chart is cast, transits striking the angles become natal.angles (hard contacts to the ASC and MC — among the heaviest events in transit work), and the wheel skin draws your whole-sign houses, the oldest house system there is, with the ASC and MC marked in gold. One honest caveat from the mathematics: the angles move a degree every four minutes, so they are only as true as your birth time.

One Source, many Spirits, one Matter — and the Source was never only "out there." The instrument's ambition is simple: everything that varies can sing, and the first citizen of that everything is you.

@@ -831,7 +831,7 @@
  • Right-click anything — the everything menu. Every node carries its full settings under the right button: mute, solo, a level fader (0–2×), a trim offset, every cable it carries with its amount slider and an unpatch ×, and a nested patch to → list for laying new cables without dragging. Right-click the empty sky for the view menu. And the modifiers, for the fast hands: click mutes · ⇧-click opens the node's tab · ⇧-drag rides the level up and down · ⌥-click solos a source · ⌘-click multi-selects for grouping · ⌘⇧-click resets a node to neutral. People play differently; the instrument shouldn't care which hand you reach with.
  • Click a wheel — go inside it (mind mode). In chakra view, click any chakra's core and the camera steps into the wheel: its mandala of petals turns around your whole field of view, and that chakra's feeds circle you — drag to look around the full 360°. A vector console appears under your hands: 12 keys of that wheel's own scale (C minor pentatonic at the root, rising key by key to B hirajoshi at the crown), played by clicking, by the Q] row, or by any connected MIDI keyboard — every note runs through the world's own reverb and grit, so you are jamming inside the instrument, in that chakra's mode, while the planet plays around you. travel the spine wheel to wheel; esc steps back out. And for powerful machines, right-click the sky and set graphics to ultra ✦: every orbiting feed grows a glowing comet trail, and the orbits breathe so the trails weave slow spirals.
  • Skins — the same spine, another cosmology. Right-click the sky and cycle skin. Beyond the seven chakras there is the planes: the Seven Planes & forty-nine sub-planes of the Cosmic Physical Plane from the Alice Bailey / D.K. teachings — I Divine (white light) down through Monadic (violet), Atmic, Buddhic, Mental (where the egoic lotus sits), Astral, to VII Physical (red). The spine becomes the 49-sub-plane spectrum ladder; the side-brackets mark the Monad, the Spiritual Triad and the Personality; and every feed climbs to the plane that matches its rarefaction — the sun's own wind at the Divine, orbits and the sky's geometry at the Monadic, the great cyclic wheels at the Atmic, omen and aspect at the Buddhic, abstract thought and money-as-idea at the Mental, desire and fire and felt sensation at the Astral, and the dense body of the world — weather, quakes, the mortal rates, your own hands — at the Physical. Go inside any plane (mind mode) and its keys play a scale pitched to its altitude: airy whole-tone up at the Divine, grounded minor-pentatonic down at the Physical.
  • -
  • One Source, many Spirits, one Matter. That is the whole architecture: the Source is the world's data, the same for everyone; the Spirit is the skin — the belief system through which you read it; the Matter is the sound. Choose your spirit, and the same planet plays through it. The shelf so far, each rendered from its own published lore, literally and without frills: the Tree of Life (Hermetic Qabalah, Golden Dawn attributions — ten sephirot on three pillars, all twenty-two canonical paths, Mercury's data at Hod, the Moon's at Yesod, the body of the world at Malkut); the Lodge (from published Masonic sources only — Worshipful Master in the East, Wardens West and South, the North dark, Boaz and Jachin at the porch where your own hands stand as the candidate, the Blazing Star, the Volume of the Sacred Law as humanity's open book, and music given to the South, for Beauty holds the arts); the Hermetic ladder of the seven planetary gates, lead to silver; Yggdrasil and its nine worlds, the dwarves keeping the markets in Nidavellir; the Sufi lata'if, where the wind data literally breathes at Ruh, spirit-as-breath; and the Wu Xing, five phases in their generating ring and controlling star, the metal birds of the air routed to Metal; and the Spread — the Celtic Cross of the tarot (Waite's published layout), dealt live and never finished: the heavens in what crowns you, the deep debts and quake-roots in what is beneath you, the retrogrades in what is behind you, births and seasons in what is before you, money and fire in your hopes and fears — and your own hands in the Querent's place, because you are the one asking. Right-click the sky → skin to turn the wheel of traditions. Each map stands as its true figure — the Tree as a tree, the Lodge as a lodge — and every station opens into mind mode with its own scale under your hands.
  • +
  • One Source, many Spirits, one Matter. That is the whole architecture: the Source is the world's data, the same for everyone; the Spirit is the skin — the belief system through which you read it; the Matter is the sound. Choose your spirit, and the same planet plays through it. The shelf so far, each rendered from its own published lore, literally and without frills: the Tree of Life (Hermetic Qabalah, Golden Dawn attributions — ten sephirot on three pillars, all twenty-two canonical paths, Mercury's data at Hod, the Moon's at Yesod, the body of the world at Malkut); the Lodge (from published Masonic sources only — Worshipful Master in the East, Wardens West and South, the North dark, Boaz and Jachin at the porch where your own hands stand as the candidate, the Blazing Star, the Volume of the Sacred Law as humanity's open book, and music given to the South, for Beauty holds the arts); the Hermetic ladder of the seven planetary gates, lead to silver; Yggdrasil and its nine worlds, the dwarves keeping the markets in Nidavellir; the Sufi lata'if, where the wind data literally breathes at Ruh, spirit-as-breath; and the Wu Xing, five phases in their generating ring and controlling star, the metal birds of the air routed to Metal; and the Spread — the Celtic Cross of the tarot (Waite's published layout), dealt live and never finished: the heavens in what crowns you, the deep debts and quake-roots in what is beneath you, the retrogrades in what is behind you, births and seasons in what is before you, money and fire in your hopes and fears — and your own hands in the Querent's place, because you are the one asking. Right-click the sky → skin to turn the wheel of traditions. Each map stands as its true figure — the Tree as a tree, the Lodge as a lodge — and every station opens into mind mode with its own scale under your hands. Newest on the shelf: the wheel — the astrologer's own view, twelve signs in a circle with 0° Aries at the ascendant's place, every feed routed to its sign by traditional rulership (wildfires to Aries, your holdings to Taurus, the word to Gemini, the far-travelling planes to Sagittarius…), the live planets riding the rim, your natal chart on the inner ring, and the aspects drawn as chords across the circle — red the squares and oppositions, green the trines and sextiles, gold the conjunctions, dashed where the living sky strikes your natal points. And everywhere else, those same aspects now draw as faint cables between the planets' own spheres in any skin — the sky's geometry made visible in whatever cosmology you're standing in. And every Spirit now speaks its own tongue: the sephirot in Hebrew (כתר, חכמה, בינה…), the chakras in Devanagari (सहस्रार, अनाहत…), the lata'if in Arabic (قلب, روح, سر…), the nine worlds in Old Norse (ásgarðr, miðgarðr, helheimr…), the planetary gates in their glyphs (☽ ☿ ♀ ☉ ♂ ♃ ♄), the five phases in their characters (木 火 土 金 水). The lore, in the letters it was written in.
  • The groove grids — sequence the voices. Open any voice's tab (shift-click it) and there's a 16-step grid on the godtime clock. The note voices (lead, bass) get a mini piano roll — eight rows of minor pentatonic, click to place a note, click again to clear — and while your groove is on, your melody replaces the data's: bitcoin lets go of the lead and you take it. Every other voice gets an 808-style gate row that rhythmically chops the world's signal — the reverb, the grit, the percussion breathing in your pattern while the planet still decides how hard. Grooves ride the godtime tempo (lock the dial, or let your heart drive it), and they save with your patch. And the sources get the grid too, with a different power: sample & hold — while a source's groove is on, the world's number is only sampled on your lit steps and held between them, so a smooth drifting feed becomes stepped, rhythmic, quantized in time. Bitcoin arpeggiating in your pattern. The wind as a sequence. If an out port is chosen in ⚙, godtime also sends MIDI clock (24 ppqn), so a DAW can slave its tempo to the planet — or to your pulse.
  • @@ -1691,6 +1691,7 @@ drawParticles(dt); if (chakraMode) drawChakraView(t); else for (const n of sources.values()) drawSourceNode(n, t); + drawAspectCables(t); // the sky's geometry, drawn between its spheres for (const n of dests.values()) drawDestNode(n, t); drawEditing(t); if (performMode) drawPerform(t, dt); @@ -1979,13 +1980,13 @@ const chakraSpin = { rot: 0, vel: 0, tilt: 0.35 }; const CHAKRAS = [ // top -> bottom, traditional petal counts - { key: "crown", label: "crown · sahasrara", petals: 20, col: [186, 122, 255] }, - { key: "eye", label: "third eye · ajna", petals: 2, col: [122, 110, 255] }, - { key: "throat", label: "throat · vishuddha", petals: 16, col: [86, 178, 255] }, - { key: "heart", label: "heart · anahata", petals: 12, col: [96, 230, 140] }, - { key: "solar", label: "solar plexus · manipura", petals: 10, col: [255, 214, 74] }, - { key: "sacral", label: "sacral · svadhisthana", petals: 6, col: [255, 150, 64] }, - { key: "root", label: "root · muladhara", petals: 4, col: [255, 88, 88] }, + { key: "crown", label: "crown · सहस्रार sahasrara", petals: 20, col: [186, 122, 255] }, + { key: "eye", label: "third eye · आज्ञा ajna", petals: 2, col: [122, 110, 255] }, + { key: "throat", label: "throat · विशुद्ध vishuddha", petals: 16, col: [86, 178, 255] }, + { key: "heart", label: "heart · अनाहत anahata", petals: 12, col: [96, 230, 140] }, + { key: "solar", label: "solar plexus · मणिपूर manipura", petals: 10, col: [255, 214, 74] }, + { key: "sacral", label: "sacral · स्वाधिष्ठान svadhisthana", petals: 6, col: [255, 150, 64] }, + { key: "root", label: "root · मूलाधार muladhara", petals: 4, col: [255, 88, 88] }, ]; const CHAKRA_BY_KEY = {}; for (const c of CHAKRAS) CHAKRA_BY_KEY[c.key] = c; @@ -2127,16 +2128,16 @@ // such). Three pillars: Mercy right, Severity left, Equilibrium the middle. // The 22 paths below are the canonical set, all of them, none invented. const TREE = [ - { key: "keter", label: "keter · crown", petals: 1, col: [255, 255, 255], pos: [0.50, 0.00] }, - { key: "chokmah", label: "chokmah · wisdom", petals: 2, col: [150, 154, 160], pos: [0.84, 0.12] }, - { key: "binah", label: "binah · understanding", petals: 3, col: [70, 66, 92], pos: [0.16, 0.12] }, - { key: "chesed", label: "chesed · mercy", petals: 4, col: [60, 110, 230], pos: [0.84, 0.38] }, - { key: "gevurah", label: "gevurah · severity", petals: 5, col: [210, 40, 40], pos: [0.16, 0.38] }, - { key: "tiferet", label: "tiferet · beauty", petals: 6, col: [255, 210, 60], pos: [0.50, 0.50] }, - { key: "netzach", label: "netzach · victory", petals: 7, col: [40, 190, 100], pos: [0.84, 0.64] }, - { key: "hod", label: "hod · splendour", petals: 8, col: [240, 140, 40], pos: [0.16, 0.64] }, - { key: "yesod", label: "yesod · foundation", petals: 9, col: [140, 70, 220], pos: [0.50, 0.78] }, - { key: "malkut", label: "malkut · kingdom", petals: 10, col: [140, 120, 60], pos: [0.50, 0.98] }, + { key: "keter", label: "כתר keter · crown", petals: 1, col: [255, 255, 255], pos: [0.50, 0.00] }, + { key: "chokmah", label: "חכמה chokmah · wisdom", petals: 2, col: [150, 154, 160], pos: [0.84, 0.12] }, + { key: "binah", label: "בינה binah · understanding", petals: 3, col: [70, 66, 92], pos: [0.16, 0.12] }, + { key: "chesed", label: "חסד chesed · mercy", petals: 4, col: [60, 110, 230], pos: [0.84, 0.38] }, + { key: "gevurah", label: "גבורה gevurah · severity", petals: 5, col: [210, 40, 40], pos: [0.16, 0.38] }, + { key: "tiferet", label: "תפארת tiferet · beauty", petals: 6, col: [255, 210, 60], pos: [0.50, 0.50] }, + { key: "netzach", label: "נצח netzach · victory", petals: 7, col: [40, 190, 100], pos: [0.84, 0.64] }, + { key: "hod", label: "הוד hod · splendour", petals: 8, col: [240, 140, 40], pos: [0.16, 0.64] }, + { key: "yesod", label: "יסוד yesod · foundation", petals: 9, col: [140, 70, 220], pos: [0.50, 0.78] }, + { key: "malkut", label: "מלכות malkut · kingdom", petals: 10, col: [140, 120, 60], pos: [0.50, 0.98] }, ]; const TREE_PATHS = [[0,1],[0,2],[0,5],[1,2],[1,5],[1,3],[2,5],[2,4],[3,4],[3,5],[3,6], [4,5],[4,7],[5,6],[5,7],[5,8],[6,7],[6,8],[6,9],[7,8],[7,9],[8,9]]; @@ -2205,13 +2206,13 @@ // THE LADDER OF THE SPHERES — Hermetic ascent, Moon lowest to Saturn highest, // each gate its classical metal. As above, so below. const LADDER = [ - { key: "saturn", label: "saturn · lead gate · the threshold", petals: 7, col: [95, 90, 110], }, - { key: "jupiter", label: "jupiter · tin gate", petals: 6, col: [70, 110, 200], }, - { key: "mars", label: "mars · iron gate", petals: 5, col: [190, 60, 48], }, - { key: "sun", label: "sol · gold gate · the heart", petals: 4, col: [246, 200, 60], }, - { key: "venus", label: "venus · copper gate", petals: 3, col: [100, 190, 130], }, - { key: "mercury", label: "mercury · quicksilver gate", petals: 2, col: [200, 176, 130], }, - { key: "moon", label: "luna · silver gate", petals: 1, col: [225, 230, 240], }, + { key: "saturn", label: "♄ saturn · lead gate · the threshold", petals: 7, col: [95, 90, 110], }, + { key: "jupiter", label: "♃ jupiter · tin gate", petals: 6, col: [70, 110, 200], }, + { key: "mars", label: "♂ mars · iron gate", petals: 5, col: [190, 60, 48], }, + { key: "sun", label: "☉ sol · gold gate · the heart", petals: 4, col: [246, 200, 60], }, + { key: "venus", label: "♀ venus · copper gate", petals: 3, col: [100, 190, 130], }, + { key: "mercury", label: "☿ mercury · quicksilver gate", petals: 2, col: [200, 176, 130], }, + { key: "moon", label: "☽ luna · silver gate", petals: 1, col: [225, 230, 240], }, ]; const LADDER_SCALES = { saturn: { root: 69, scale: "wholetone" }, jupiter: { root: 65, scale: "mixolydian" }, @@ -2235,15 +2236,15 @@ // YGGDRASIL — the nine worlds on the world-tree; the classic three-tier // arrangement (a scholarly reconstruction, as the Eddas never draw one map). const NINE = [ - { key: "asgard", label: "asgard · the aesir", petals: 8, col: [236, 223, 140] }, - { key: "vanaheim", label: "vanaheim · the vanir", petals: 6, col: [86, 196, 150] }, - { key: "alfheim", label: "alfheim · the light elves", petals: 6, col: [214, 236, 255] }, - { key: "midgard", label: "midgard · humankind", petals: 4, col: [110, 170, 110] }, - { key: "jotunheim", label: "jotunheim · the giants", petals: 5, col: [120, 130, 148] }, - { key: "nidavellir", label: "nidavellir · the dwarves", petals: 4, col: [150, 116, 82] }, - { key: "muspelheim", label: "muspelheim · fire", petals: 7, col: [255, 96, 52] }, - { key: "niflheim", label: "niflheim · ice & mist", petals: 7, col: [150, 196, 230] }, - { key: "helheim", label: "helheim · the dead", petals: 3, col: [110, 90, 125] }, + { key: "asgard", label: "ásgarðr · the aesir", petals: 8, col: [236, 223, 140] }, + { key: "vanaheim", label: "vanaheimr · the vanir", petals: 6, col: [86, 196, 150] }, + { key: "alfheim", label: "álfheimr · the light elves", petals: 6, col: [214, 236, 255] }, + { key: "midgard", label: "miðgarðr · humankind", petals: 4, col: [110, 170, 110] }, + { key: "jotunheim", label: "jötunheimr · the giants", petals: 5, col: [120, 130, 148] }, + { key: "nidavellir", label: "niðavellir · the dwarves", petals: 4, col: [150, 116, 82] }, + { key: "muspelheim", label: "múspellsheimr · fire", petals: 7, col: [255, 96, 52] }, + { key: "niflheim", label: "niflheimr · ice & mist", petals: 7, col: [150, 196, 230] }, + { key: "helheim", label: "helheimr · the dead", petals: 3, col: [110, 90, 125] }, ]; const NINE_SCALES = { asgard: { root: 62, scale: "lydian" }, vanaheim: { root: 57, scale: "mixolydian" }, @@ -2270,12 +2271,12 @@ // LATA'IF-E-SITTA — the six subtle centres of the Sufi heart (Naqshbandi // tabulation, colours per Sirhindi's school). Ruh is spirit-as-breath. const LATAIF = [ - { key: "akhfa", label: "akhfa · the most hidden", petals: 6, col: [70, 175, 90] }, - { key: "khafi", label: "khafi · the hidden", petals: 5, col: [90, 85, 100] }, - { key: "sirr", label: "sirr · the secret", petals: 4, col: [240, 240, 245] }, - { key: "ruh", label: "ruh · the spirit", petals: 3, col: [225, 60, 66] }, - { key: "qalb", label: "qalb · the heart", petals: 2, col: [235, 205, 60] }, - { key: "nafs", label: "nafs · the self", petals: 7, col: [70, 110, 220] }, + { key: "akhfa", label: "أخفى akhfa · the most hidden", petals: 6, col: [70, 175, 90] }, + { key: "khafi", label: "خفي khafi · the hidden", petals: 5, col: [90, 85, 100] }, + { key: "sirr", label: "سر sirr · the secret", petals: 4, col: [240, 240, 245] }, + { key: "ruh", label: "روح ruh · the spirit", petals: 3, col: [225, 60, 66] }, + { key: "qalb", label: "قلب qalb · the heart", petals: 2, col: [235, 205, 60] }, + { key: "nafs", label: "نفس nafs · the self", petals: 7, col: [70, 110, 220] }, ]; const LATAIF_SCALES = { akhfa: { root: 72, scale: "lydian" }, khafi: { root: 69, scale: "phrygian" }, @@ -2362,6 +2363,152 @@ return "present"; // what covers you right now } + // THE WHEEL — the astrologer's own view: twelve signs in a circle, 0° Aries + // at the ascendant's place (left), longitudes running counterclockwise. The + // live planets ride the rim; a loaded natal chart sits on the inner ring; + // aspects draw as chords — red the hard, green the soft, gold the meetings. + const SIGNS = [ + { key: "aries", label: "♈ aries", petals: 1, col: [255, 90, 70] }, + { key: "taurus", label: "♉ taurus", petals: 2, col: [110, 200, 110] }, + { key: "gemini", label: "♊ gemini", petals: 3, col: [255, 220, 120] }, + { key: "cancer", label: "♋ cancer", petals: 4, col: [180, 200, 230] }, + { key: "leo", label: "♌ leo", petals: 5, col: [255, 190, 60] }, + { key: "virgo", label: "♍ virgo", petals: 6, col: [180, 220, 160] }, + { key: "libra", label: "♎ libra", petals: 7, col: [230, 180, 220] }, + { key: "scorpio", label: "♏ scorpio", petals: 8, col: [190, 70, 100] }, + { key: "sagittarius", label: "♐ sagittarius", petals: 9, col: [220, 130, 255] }, + { key: "capricorn", label: "♑ capricorn", petals: 10, col: [160, 150, 130] }, + { key: "aquarius", label: "♒ aquarius", petals: 11, col: [110, 210, 240] }, + { key: "pisces", label: "♓ pisces", petals: 12, col: [140, 160, 255] }, + ]; + SIGNS.forEach((s2, i) => { + const a2 = (180 + i * 30 + 15) * Math.PI / 180; // sign midpoints, Aries left, CCW + s2.pos = [0.5 + 0.45 * Math.cos(a2), 0.5 - 0.45 * Math.sin(a2)]; + }); + const SIGN_SCALES = {}; + SIGNS.forEach((s2, i) => { // circle of fifths up the zodiac, + SIGN_SCALES[s2.key] = { // scales by element + root: 48 + (i * 7) % 12, + scale: ["mixolydian", "minor_pent", "lydian", "dorian"][i % 4], + }; + }); + function zodiacOf(key) { // by traditional rulership/affinity + const k = (key || "").toLowerCase(), p = k.split(".")[0]; + if (p === "fire") return "aries"; // Mars burns + if (p === "me" || k === "market.turnover" || k === "crypto.price") return "taurus"; // holdings + if (p === "wiki" || /mercury_retro/.test(k)) return "gemini"; // the word; Mercury's own condition + if (p === "weather" || k === "almanac.dec") return "cancer"; // the home sky + if (p === "sun" || /^sky\.|light|ambient/.test(k)) return "leo"; // the light itself + if (/aqi|pm25|lifeexp/.test(k)) return "virgo"; // health + if (/harmony|season/.test(k)) return "libra"; // balance, equinox + if (/deaths|debt|tension/.test(k)) return "scorpio"; // the deep and the owed + if (p === "planes" || p === "iss" || /inflation/.test(k)) return "sagittarius"; // far travel; Jupiterian expansion + if (/quake|hunger|poverty/.test(k)) return "capricorn"; // Saturn: structure, mountains, want + if (p === "crypto" || /popvel|births|zodiac/.test(k)) return "aquarius"; // the collective + if (p === "natal") return natalActive && natalActive.asc != null + ? SIGNS[Math.floor(natalActive.asc / 30)].key : "aries"; // your rising sign + if (["hand", "tof", "touch", "body"].includes(p)) + return natalActive ? SIGNS[Math.floor(natalActive.pos.sun / 30)].key : "leo"; // your sun sign + return "pisces"; // the dream-tide + } + function rezodiac() { // the planets walk the signs live + for (const gp of GT_PLANETS) { + const n = sources.get(gp[0]); + if (n && typeof n.raw === "number") n.station = SIGNS[Math.floor((((n.raw % 360) + 360) % 360) / 30)].key; + } + } + function drawWheelOverlay(t, g) { + const box = mapBox(g); + const cx = (box.x0 + box.x1) / 2, cy = (box.y0 + box.y1) / 2; + const R = Math.min(box.x1 - box.x0, box.y1 - box.y0) * 0.5 - 4; + const lonXY = (lon, r) => { + const a2 = (180 + lon) * Math.PI / 180; + return [cx + r * Math.cos(a2), cy - r * Math.sin(a2)]; + }; + ctx.save(); + ctx.textAlign = "center"; ctx.textBaseline = "middle"; + ctx.strokeStyle = "rgba(200,212,240,0.20)"; ctx.lineWidth = 1; + ctx.beginPath(); ctx.arc(cx, cy, R, 0, Math.PI * 2); ctx.stroke(); + ctx.beginPath(); ctx.arc(cx, cy, R * 0.55, 0, Math.PI * 2); ctx.stroke(); + + // whole-sign houses + the ascendant, when a chart with a birthplace is cast + if (natalActive && natalActive.asc != null) { + const ascSign = Math.floor(natalActive.asc / 30); + for (let i2 = 0; i2 < 12; i2++) { + const s1 = lonXY(i2 * 30, R * 0.55), s2 = lonXY(i2 * 30, R); + ctx.beginPath(); ctx.moveTo(s1[0], s1[1]); ctx.lineTo(s2[0], s2[1]); + ctx.strokeStyle = "rgba(200,212,240,0.10)"; ctx.lineWidth = 1; ctx.stroke(); + const hp = lonXY(i2 * 30 + 15, R * 0.47); + ctx.fillStyle = "rgba(190,205,235,0.4)"; + ctx.font = "500 9px 'Helvetica Neue', Arial, sans-serif"; + ctx.fillText(String(((i2 - ascSign + 12) % 12) + 1), hp[0], hp[1]); + } + const a1 = lonXY(natalActive.asc, R * 0.5), a2 = lonXY(natalActive.asc, R + 9); + ctx.beginPath(); ctx.moveTo(a1[0], a1[1]); ctx.lineTo(a2[0], a2[1]); + ctx.strokeStyle = "rgba(255,214,130,0.7)"; ctx.lineWidth = 1.6; ctx.stroke(); + const al = lonXY(natalActive.asc, R + 20); + ctx.fillStyle = "rgba(255,214,130,0.85)"; + ctx.font = "600 9px 'Helvetica Neue', Arial, sans-serif"; + ctx.fillText("ASC", al[0], al[1]); + if (natalActive.mc != null) { + const m1 = lonXY(natalActive.mc, R * 0.5), m2 = lonXY(natalActive.mc, R + 9); + ctx.beginPath(); ctx.moveTo(m1[0], m1[1]); ctx.lineTo(m2[0], m2[1]); + ctx.strokeStyle = "rgba(255,214,130,0.45)"; ctx.lineWidth = 1.2; ctx.stroke(); + const ml = lonXY(natalActive.mc, R + 20); + ctx.fillStyle = "rgba(255,214,130,0.6)"; + ctx.fillText("MC", ml[0], ml[1]); + } + } + + const KINDCOL = { hard: "rgba(255,95,95,", soft: "rgba(110,220,150,", conj: "rgba(255,214,110," }; + const live = []; + for (const gp of GT_PLANETS) { + const n = sources.get(gp[0]); + if (n && typeof n.raw === "number") + live.push({ k: gp[0], glyph: gp[1], lon: ((n.raw % 360) + 360) % 360 }); + } + // sky-to-sky chords on the rim + for (let i2 = 0; i2 < live.length; i2++) for (let j2 = i2 + 1; j2 < live.length; j2++) { + const h = aspectHit(live[i2].lon, live[j2].lon, /sun|moon/.test(live[i2].k + live[j2].k)); + if (!h || h.s < 0.05) continue; + const p1 = lonXY(live[i2].lon, R), p2 = lonXY(live[j2].lon, R); + ctx.beginPath(); ctx.moveTo(p1[0], p1[1]); ctx.lineTo(p2[0], p2[1]); + ctx.strokeStyle = KINDCOL[h.kind] + (0.15 + h.s * 0.6).toFixed(3) + ")"; + ctx.lineWidth = 0.8 + h.s * 2.2; ctx.stroke(); + } + // the natal chart on the inner ring + transit chords striking it + if (natalActive) { + const NG = { sun: "☉", moon: "☽", mercury: "☿", venus: "♀", mars: "♂", jupiter: "♃", saturn: "♄" }; + for (const p2 in NG) { + const nl = natalActive.pos[p2]; + for (const lv of live) { + const h = aspectHit(lv.lon, nl, p2 === "sun" || p2 === "moon" || /sun|moon/.test(lv.k)); + if (!h || h.s < 0.08) continue; + const q1 = lonXY(lv.lon, R), q2 = lonXY(nl, R * 0.55); + ctx.setLineDash([3, 4]); + ctx.beginPath(); ctx.moveTo(q1[0], q1[1]); ctx.lineTo(q2[0], q2[1]); + ctx.strokeStyle = KINDCOL[h.kind] + (0.15 + h.s * 0.55).toFixed(3) + ")"; + ctx.lineWidth = 0.8 + h.s * 1.8; ctx.stroke(); + ctx.setLineDash([]); + } + const gp2 = lonXY(nl, R * 0.55); + ctx.fillStyle = "rgba(255,214,130,0.6)"; + ctx.font = "11px 'Helvetica Neue', Arial, sans-serif"; + ctx.fillText(NG[p2], gp2[0], gp2[1]); + } + } + // the living planets on the rim + for (const lv of live) { + const pp = lonXY(lv.lon, R); + ctx.font = "13px 'Helvetica Neue', Arial, sans-serif"; + ctx.fillStyle = "rgba(150,190,255,0.35)"; + ctx.fillText(lv.glyph, pp[0], pp[1] + 0.5); + ctx.fillStyle = "rgba(224,234,255,0.95)"; + ctx.fillText(lv.glyph, pp[0], pp[1]); + } + ctx.restore(); + } + const SKINS = { chakra: { key: "chakra", label: "chakra", word: "chakra", @@ -2401,6 +2548,11 @@ key: "tarot", label: "the spread (tarot)", word: "position", layout: "map", stations: SPREAD, stationOf: spreadOf, scaleOf: (k) => SPREAD_SCALES[k], paths: SPREAD_PATHS, }, + zodiac: { + key: "zodiac", label: "the wheel (zodiac)", word: "sign", layout: "map", + stations: SIGNS, stationOf: zodiacOf, scaleOf: (k) => SIGN_SCALES[k], + overlay: drawWheelOverlay, dynamicStations: rezodiac, + }, }; for (const s in SKINS) { const byKey = {}; @@ -2435,6 +2587,7 @@ chakraSpin.rot += chakraSpin.vel * dt + dt * 0.12; } const g = chakraGeom(); + if (activeSkin.dynamicStations) activeSkin.dynamicStations(); // e.g. planets walking the signs for (const c of activeSkin.stations) { c.members = []; c.energy = 0; } for (const n of sources.values()) { if (!n.station) n.station = activeSkin.stationOf(n.key); @@ -2722,9 +2875,31 @@ ctx.fillStyle = rgba(rgbMix(c.col, 0.55), 0.8); ctx.fillText(c.label + (c.members.length ? " · " + c.members.length : ""), cx2, cy2 + r + 16); }); + if (activeSkin.overlay) activeSkin.overlay(t, g); // e.g. the zodiac wheel's chords & houses ctx.restore(); } + // aspects as cables — in every view, the planets' live aspects draw as lines + // between their own spheres: red the hard, green the soft, gold the meetings + function drawAspectCables(t) { + if (typeof aspectHit !== "function") return; + const pts = []; + for (const gp of GT_PLANETS) { + const n = sources.get(gp[0]); + if (n && typeof n.raw === "number" && n.x > -9000 && n.x !== 0) pts.push(n); + } + const KINDCOL = { hard: "rgba(255,95,95,", soft: "rgba(110,220,150,", conj: "rgba(255,214,110," }; + for (let i = 0; i < pts.length; i++) for (let j = i + 1; j < pts.length; j++) { + const h = aspectHit(((pts[i].raw % 360) + 360) % 360, ((pts[j].raw % 360) + 360) % 360, + /sun|moon/.test(pts[i].key + pts[j].key)); + if (!h || h.s < 0.08) continue; + ctx.beginPath(); ctx.moveTo(pts[i].x, pts[i].y); ctx.lineTo(pts[j].x, pts[j].y); + ctx.strokeStyle = KINDCOL[h.kind] + (0.10 + h.s * 0.45).toFixed(3) + ")"; + ctx.lineWidth = 0.8 + h.s * 2; + ctx.stroke(); + } + } + function drawStationPaths() { // the canonical connective lines of a map skin const draw = (pairs, dashed) => { if (!pairs) return; @@ -3856,14 +4031,26 @@ let natalActive = null; // {name, pos{7}, web[{a,b,kind,w}]} function computeNatal(c) { - const T = jCent(julianDay(+c.y, +c.mo, +c.d, +c.hh + (+c.mm) / 60 - (+c.utcoff || 0))); + const jd0 = julianDay(+c.y, +c.mo, +c.d, +c.hh + (+c.mm) / 60 - (+c.utcoff || 0)); + const T = jCent(jd0); const pos = skyAt(T); const web = []; // the chart's own tight aspects — what transits will ring for (let i = 0; i < 7; i++) for (let j = i + 1; j < 7; j++) { const h = aspectHit(pos[PLANET7[i]], pos[PLANET7[j]], false); if (h && h.s > 0.1) web.push({ a: PLANET7[i], b: PLANET7[j], kind: h.kind, w: h.s }); } - return { pos, web }; + // the angles — ascendant & midheaven, when a birthplace is given. Formula + // verified analytically (Monte-Carlo horizon residual ~1e-13°) and against + // sunrise: the Sun sits on the ASC at the geometric rise, refraction aside. + let asc = null, mc = null; + if (c.lat != null && c.lon != null && isFinite(+c.lat) && isFinite(+c.lon)) { + const eps = (23.4392911 - 0.0130042 * T) * D2R; + const th = norm360(280.46061837 + 360.98564736629 * (jd0 - 2451545.0) + (+c.lon)) * D2R; + const phi = (+c.lat) * D2R; + asc = norm360(Math.atan2(Math.cos(th), -(Math.sin(th) * Math.cos(eps) + Math.tan(phi) * Math.sin(eps))) / D2R); + mc = norm360(Math.atan2(Math.sin(th), Math.cos(th) * Math.cos(eps)) / D2R); + } + return { pos, web, asc, mc }; } function setNatalSource(key, label, norm) { @@ -3900,6 +4087,16 @@ setNatalSource("natal.moon", "the moon on " + who + "'s chart", moonS); setNatalSource("natal.saturn", "saturn on " + who + "'s chart", satS); setNatalSource("natal.birthday", who + " · solar return", Math.max(0, 1 - sepDeg(Tr.sun, natalActive.pos.sun) / 15)); + if (natalActive.asc != null) { // the angles receive; hard contacts carry the weight + let ang = 0; + for (const tp of PLANET7) { + const hA = aspectHit(Tr[tp], natalActive.asc, false); + const hM = aspectHit(Tr[tp], natalActive.mc, false); + if (hA && hA.kind !== "soft") ang = Math.max(ang, hA.s); + if (hM && hM.kind !== "soft") ang = Math.max(ang, hM.s); + } + setNatalSource("natal.angles", who + " · the angles struck", ang); + } } function activateChart(i) { charts.active = i; @@ -3907,9 +4104,10 @@ const c = charts.list[i]; if (!c) { natalActive = null; return; } const n = computeNatal(c); - natalActive = { name: c.name || "you", pos: n.pos, web: n.web }; + natalActive = { name: c.name || "you", pos: n.pos, web: n.web, asc: n.asc, mc: n.mc }; natalTick(); - eventTicker.unshift({ text: "✨ " + natalActive.name + "'s sky is in the choir · " + n.web.length + " natal aspects in the web", tAdded: now(), src: "sys" }); + const rising = n.asc != null ? " · " + SIGNS[Math.floor(n.asc / 30)].key + " rising" : ""; + eventTicker.unshift({ text: "✨ " + natalActive.name + "'s sky is in the choir · " + n.web.length + " natal aspects in the web" + rising, tAdded: now(), src: "sys" }); } setInterval(natalTick, 60000); // the moon moves ~0.5°/hour — a minute is plenty if (charts.list.length) setTimeout(() => activateChart(charts.active), 1500); @@ -3953,12 +4151,17 @@ return inp; }; const nameI = mk("name (you, a friend…)"), dateI = mk("", "date"), timeI = mk("", "time"), - utcI = mk("UTC offset at birth, e.g. +10 or -5.5", "number"); - utcI.step = "0.5"; + utcI = mk("UTC offset at birth, e.g. +10 or -5.5", "number"), + latI = mk("birth lat, e.g. -27.47", "number"), lonI = mk("birth lon (east +), e.g. 153.03", "number"); + utcI.step = "0.5"; latI.step = "any"; lonI.step = "any"; const r1 = document.createElement("div"); r1.className = "hrow"; r1.appendChild(nameI); const r2 = document.createElement("div"); r2.className = "hrow"; r2.appendChild(dateI); r2.appendChild(timeI); const r3 = document.createElement("div"); r3.className = "hrow"; r3.appendChild(utcI); - natalEl.appendChild(r1); natalEl.appendChild(r2); natalEl.appendChild(r3); + const r4 = document.createElement("div"); r4.className = "hrow"; r4.appendChild(latI); r4.appendChild(lonI); + natalEl.appendChild(r1); natalEl.appendChild(r2); natalEl.appendChild(r3); natalEl.appendChild(r4); + const hint = document.createElement("div"); hint.className = "sub"; + hint.textContent = "lat/lon unlock the ascendant, midheaven & whole-sign houses (the wheel skin draws them). the angles move 1° every 4 minutes — birth-time precision matters most here."; + natalEl.appendChild(hint); const save = document.createElement("button"); save.className = "midibtn"; save.style.marginTop = "8px"; save.textContent = "cast the chart"; save.onclick = () => { @@ -3966,7 +4169,9 @@ const [y, mo, d] = dateI.value.split("-").map(Number); const [hh, mm] = timeI.value.split(":").map(Number); if (y < 1800 || y > 2049) { sub.textContent = "the ephemeris holds 1800–2049 — outside that the sky drifts."; return; } - charts.list.push({ name: nameI.value.trim() || "you", y, mo, d, hh, mm, utcoff: parseFloat(utcI.value) || 0 }); + charts.list.push({ name: nameI.value.trim() || "you", y, mo, d, hh, mm, utcoff: parseFloat(utcI.value) || 0, + lat: latI.value === "" ? null : parseFloat(latI.value), + lon: lonI.value === "" ? null : parseFloat(lonI.value) }); try { localStorage.setItem("gs_charts", JSON.stringify(charts)); } catch (e) {} activateChart(charts.list.length - 1); openNatal();