UNPKG

kaabalah

Version:

The de-facto library for any esoteric calculations and tooling

855 lines (853 loc) 181 kB
import { TREE_TOPOLOGY_SPHERE_NAMES, TREE_TOPOLOGY_SPHERE_IDS, TREE_TOPOLOGY_PATH_IDS } from './chunk-6N5YC2WD.mjs'; import { id, createTree } from './chunk-5K4OQ4UB.mjs'; // src/visual/archeometer.ts var TAU = Math.PI * 2; var ARCHEOMETER_DEFAULT_VIEWBOX = { minX: 0, minY: 0, width: 912, height: 912 }; var ARCHEOMETER_OUTER_CROWN_BLEED = 22; var DEFAULT_LAYERS = { degreeCrown: true, zodiacUtterance: true, planetaryUtterance: true, cosmologicalMusic: true, astralZodiac: true, astralPlanetary: true, chromicRays: true, whiteRays: true, solarCenter: true }; var COLOR_RING_FILLS = { degreeOuter: "#fff8ef", degreeInner: "#fff2e1", zodiacUtterance: "#eba0be", planetaryUtterance: "#fffaf1", cosmologicalMusic: "#fffaf0", astralZodiac: "#efc0bc", astralPlanetary: "#d2e8df", chromicRays: "#fff3d4", whiteRays: "#eef1ec", solarCenter: "#f7d65b" }; var COLOR_PALETTE = { paper: "#fffaf1", ink: "#151515", subtleInk: "#605a54", ringStroke: "#171717", degreeTick: "#171717", degreeLabel: "#171717", whiteRay: "#25395a", ringFills: COLOR_RING_FILLS }; var DEFAULT_ARCHEOMETER_SECTOR_CORRESPONDENCES = [ { degree: 0, utterance: { id: "ph", degree: 0, letter: "P, Ph", number: 80, color: "#f2d54c", triangleId: "wordJesus", gloss: "Utterance / light" }, triangleLabel: { degree: 0, label: "S, Sh", number: 300 }, musicalNote: { degree: 0, note: "Si" }, zodiacSign: { degree: 0, name: "Capricorn", glyph: "\u2651" }, planetaryPoint: { degree: 0, name: "Saturn", glyph: "\u2644", phase: "diurnal" } }, { degree: 30, utterance: { id: "za", degree: 30, letter: "W, OU", number: 70, color: "#b7d56a", triangleId: "ether", gloss: "Ether" }, triangleLabel: { degree: 30, label: "D", number: 4 }, musicalNote: { degree: 30, note: "Do" }, zodiacSign: { degree: 30, name: "Sagittarius", glyph: "\u2650" }, planetaryPoint: { degree: 30, name: "Jupiter", glyph: "\u2643", phase: "diurnal" } }, { degree: 60, utterance: { id: "ma", degree: 60, letter: "M", number: 40, color: "#79bf6a", triangleId: "mary", gloss: "Mary" }, triangleLabel: { degree: 60, label: "C", number: 20 }, musicalNote: { degree: 60, note: "R\xE9" }, zodiacSign: { degree: 60, name: "Scorpio", glyph: "\u264F" }, planetaryPoint: { degree: 60, name: "Mars", glyph: "\u2642", phase: "diurnal" } }, { degree: 90, utterance: { id: "u", degree: 90, letter: "L", number: 30, color: "#69ad74", triangleId: "divineFire", gloss: "Fire" }, triangleLabel: { degree: 90, label: "" }, musicalNote: { degree: 90, note: "Mi" }, zodiacSign: { degree: 90, name: "Libra", glyph: "\u264E" }, planetaryPoint: { degree: 90, name: "Venus", glyph: "\u2640", phase: "diurnal" } }, { degree: 120, utterance: { id: "o", degree: 120, letter: "I, Y, J", number: 10, color: "#efb83e", triangleId: "wordJesus", gloss: "Voice / sound" }, triangleLabel: { degree: 120, label: "Ts", number: 90 }, musicalNote: { degree: 120, note: "Fa" }, zodiacSign: { degree: 120, name: "Virgo", glyph: "\u264D" }, planetaryPoint: { degree: 120, name: "Mercury", glyph: "\u263F", phase: "diurnal" } }, { degree: 150, utterance: { id: "la", degree: 150, letter: "T", number: 9, color: "#ec8d3f", triangleId: "ether", gloss: "Ether" }, triangleLabel: { degree: 150, label: "N", number: 50 }, musicalNote: { degree: 150, note: "Sol" }, zodiacSign: { degree: 150, name: "Leo", glyph: "\u264C" }, planetaryPoint: { degree: 150, name: "Sun", glyph: "\u2609", phase: "diurnal" } }, { degree: 180, utterance: { id: "ri", degree: 180, letter: "E, H", number: 8, color: "#df4d43", triangleId: "mary", gloss: "Descending R" }, triangleLabel: { degree: 180, label: "B", number: 2 }, musicalNote: { degree: 180, note: "La" }, zodiacSign: { degree: 180, name: "Cancer", glyph: "\u264B" }, planetaryPoint: { degree: 180, name: "Moon", glyph: "\u263E", phase: "nocturnal" } }, { degree: 210, utterance: { id: "t", degree: 210, letter: "Z", number: 7, color: "#8b54ad", triangleId: "divineFire", gloss: "Divine fire" }, triangleLabel: { degree: 210, label: "Ts", number: 90 }, musicalNote: { degree: 210, note: "Si" }, zodiacSign: { degree: 210, name: "Gemini", glyph: "\u264A" }, planetaryPoint: { degree: 210, name: "Mercury", glyph: "\u263F", phase: "nocturnal" } }, { degree: 240, utterance: { id: "y", degree: 240, letter: "V, OU", number: 6, color: "#d85c43", triangleId: "wordJesus", gloss: "Word / JeShU" }, triangleLabel: { degree: 240, label: "G", number: 3 }, musicalNote: { degree: 240, note: "Do" }, zodiacSign: { degree: 240, name: "Taurus", glyph: "\u2649" }, planetaryPoint: { degree: 240, name: "Venus", glyph: "\u2640", phase: "nocturnal" } }, { degree: 270, utterance: { id: "ka", degree: 270, letter: "H, E", number: 5, color: "#3c82c2", triangleId: "ether", gloss: "Etheric power" }, triangleLabel: { degree: 270, label: "C", number: 20 }, musicalNote: { degree: 270, note: "R\xE9" }, zodiacSign: { degree: 270, name: "Aries", glyph: "\u2648" }, planetaryPoint: { degree: 270, name: "Mars", glyph: "\u2642", phase: "nocturnal" } }, { degree: 300, utterance: { id: "h", degree: 300, letter: "R", number: 200, color: "#d4579c", triangleId: "mary", gloss: "Living waters" }, triangleLabel: { degree: 300, label: "D", number: 4 }, musicalNote: { degree: 300, note: "Mi" }, zodiacSign: { degree: 300, name: "Pisces", glyph: "\u2653" }, planetaryPoint: { degree: 300, name: "Jupiter", glyph: "\u2643", phase: "nocturnal" } }, { degree: 330, utterance: { id: "hou", degree: 330, letter: "K", number: 100, color: "#7963b8", triangleId: "divineFire", gloss: "Divine fire" }, triangleLabel: { degree: 330, label: "S, Sh", number: 300 }, musicalNote: { degree: 330, note: "Fa" }, zodiacSign: { degree: 330, name: "Aquarius", glyph: "\u2652" }, planetaryPoint: { degree: 330, name: "Saturn", glyph: "\u2644", phase: "nocturnal" } } ]; var DEFAULT_ARCHEOMETER_UTTERANCE = DEFAULT_ARCHEOMETER_SECTOR_CORRESPONDENCES.map((sector) => sector.utterance); var DEFAULT_ARCHEOMETER_TRIANGLE_LABELS = DEFAULT_ARCHEOMETER_SECTOR_CORRESPONDENCES.map((sector) => sector.triangleLabel); var DEFAULT_ARCHEOMETER_TRIANGLES = [ { id: "wordJesus", title: "Triangle of the Word / JeShU", phrase: "Y-PhO", apex: "north", vertices: [0, 120, 240], fill: "#f2cf45", vertexFills: ["#f2cf45", "#5470a5", "#dd3e38"], stroke: "#171717" }, { id: "mary", title: "Triangle of Mary / Living Waters", phrase: "Ma-Ri-H", apex: "south", vertices: [180, 300, 60], fill: "#e25b61", vertexFills: ["#cc58a1", "#f28a32", "#78bd79"], stroke: "#171717" }, { id: "ether", title: "Triangle of the Ether", phrase: "La-Ka-Za", apex: "west", vertices: [270, 30, 150], fill: "#78bd79", vertexFills: ["#e96836", "#b7bd58", "#8f69a3"], stroke: "#171717" }, { id: "divineFire", title: "Triangle of Divine Fire", phrase: "Hou-U-T", apex: "east", vertices: [90, 210, 330], fill: "#cc58a1", vertexFills: ["#63a890", "#d45375", "#f0b33f"], stroke: "#171717" } ]; var DEFAULT_ARCHEOMETER_MUSICAL_NOTES = DEFAULT_ARCHEOMETER_SECTOR_CORRESPONDENCES.map((sector) => sector.musicalNote); var DEFAULT_ARCHEOMETER_ZODIAC = DEFAULT_ARCHEOMETER_SECTOR_CORRESPONDENCES.map((sector) => sector.zodiacSign); var DEFAULT_ARCHEOMETER_PLANETS = DEFAULT_ARCHEOMETER_SECTOR_CORRESPONDENCES.map((sector) => sector.planetaryPoint); function getArcheometerRenderModel(options = {}) { const viewBox = normalizeViewBox(options.viewBox); const scale = Math.min(viewBox.width / ARCHEOMETER_DEFAULT_VIEWBOX.width, viewBox.height / ARCHEOMETER_DEFAULT_VIEWBOX.height); const padding = options.padding ?? 0; const center = { x: round(viewBox.minX + viewBox.width / 2), y: round(viewBox.minY + viewBox.height / 2) }; const frameRadius = round(Math.max(0, Math.min(viewBox.width, viewBox.height) / 2 - padding)); const outerRadius = round(Math.max(0, frameRadius - ARCHEOMETER_OUTER_CROWN_BLEED * scale)); return { viewBox, center, outerRadius, scale, rings: buildRings(outerRadius, frameRadius), palette: resolvePalette(options.palette), rotationDegrees: options.rotationDegrees ?? 0, layers: { ...DEFAULT_LAYERS, ...options.layers ?? {} }, degreeLabelEvery: options.degreeLabelEvery ?? 15, utterance: options.utterance ?? DEFAULT_ARCHEOMETER_UTTERANCE, triangles: options.triangles ?? DEFAULT_ARCHEOMETER_TRIANGLES, triangleLabels: options.triangleLabels ?? DEFAULT_ARCHEOMETER_TRIANGLE_LABELS, musicalNotes: options.musicalNotes ?? DEFAULT_ARCHEOMETER_MUSICAL_NOTES, zodiacSigns: options.zodiacSigns ?? DEFAULT_ARCHEOMETER_ZODIAC, planetaryPoints: options.planetaryPoints ?? DEFAULT_ARCHEOMETER_PLANETS }; } var DEFAULT_ARCHETYPE_UTTERANCE = DEFAULT_ARCHEOMETER_UTTERANCE; var DEFAULT_ARCHETYPE_TRIANGLES = DEFAULT_ARCHEOMETER_TRIANGLES; var DEFAULT_ARCHETYPE_MUSICAL_NOTES = DEFAULT_ARCHEOMETER_MUSICAL_NOTES; var DEFAULT_ARCHETYPE_ZODIAC = DEFAULT_ARCHEOMETER_ZODIAC; var DEFAULT_ARCHETYPE_PLANETS = DEFAULT_ARCHEOMETER_PLANETS; function generateArcheometerSvg(options = {}) { const model = getArcheometerRenderModel(options); const { viewBox, center, outerRadius, palette } = model; const background = options.background ?? "transparent"; const lines = []; const push = (line) => lines.push(line); const title = options.title ?? "The Cosmological Archeometer"; const attrs = [ `xmlns="http://www.w3.org/2000/svg"`, options.width !== void 0 ? `width="${escapeAttr(String(options.width))}"` : "", options.height !== void 0 ? `height="${escapeAttr(String(options.height))}"` : "", `viewBox="${fmt(viewBox.minX)} ${fmt(viewBox.minY)} ${fmt(viewBox.width)} ${fmt(viewBox.height)}"`, `preserveAspectRatio="xMidYMid meet"`, `role="img"`, `aria-label="${escapeAttr(title)}"` ].filter(Boolean); push(`<svg ${attrs.join(" ")}>`); push(`<title>${escapeText(title)}</title>`); if (background !== "transparent") { push(`<rect x="${fmt(viewBox.minX)}" y="${fmt(viewBox.minY)}" width="${fmt(viewBox.width)}" height="${fmt(viewBox.height)}" fill="${escapeAttr(background)}"/>`); } push(`<circle cx="${fmt(center.x)}" cy="${fmt(center.y)}" r="${fmt(outerRadius)}" fill="${escapeAttr(palette.paper)}"/>`); renderDefs(push, model); renderRingGrounds(push, model); if (model.layers.chromicRays) renderChromicRays(push, model); if (model.layers.planetaryUtterance) renderPlanetaryUtterance(push, model); if (model.layers.zodiacUtterance) renderZodiacUtterance(push, model); if (model.layers.cosmologicalMusic) renderCosmologicalMusic(push, model); if (model.layers.astralZodiac) renderAstralZodiac(push, model); if (model.layers.astralPlanetary) renderAstralPlanetary(push, model); if (model.layers.solarCenter) renderSolarCenter(push, model); if (model.layers.whiteRays) renderWhiteRays(push, model); if (model.layers.degreeCrown) renderDegreeCrown(push, model); renderFrame(push, model); push(`</svg>`); return lines.join("\n"); } function renderDefs(push, model) { const { rings, center } = model; const planetaryClipOuter = planetaryTriangleClipOuterRadius(rings); const planetaryClipInner = planetaryTriangleClipInnerRadius(rings); const chromicClipOuter = chromicCoreOuterRadius(rings); const chromicClipInner = chromicCoreInnerRadius(rings); push(`<defs>`); push( `<clipPath id="archeometer-planetary-clip"><path d="${annulusPath( center, planetaryClipInner, planetaryClipOuter )}" fill-rule="evenodd" clip-rule="evenodd"/></clipPath>` ); push( `<clipPath id="archeometer-chromic-clip"><path d="${annulusPath( center, chromicClipInner, chromicClipOuter )}" fill-rule="evenodd" clip-rule="evenodd"/></clipPath>` ); push(`</defs>`); } function renderRingGrounds(push, model) { const { rings, center, palette } = model; const order = [ "degreeOuter", "degreeInner", "zodiacUtterance", "planetaryUtterance", "cosmologicalMusic", "astralZodiac", "astralPlanetary", "chromicRays", "whiteRays" ]; push(`<g id="archeometer-ring-grounds">`); for (const id2 of order) { const ring = rings[id2]; push(`<path id="archeometer-ring-${id2}" d="${annulusPath(center, ring.r1, ring.r2)}" fill="${escapeAttr(palette.ringFills[id2])}" fill-rule="evenodd" stroke="none"/>`); } push(`</g>`); } function renderDegreeCrown(push, model) { const { center, rings, palette, scale } = model; const outer = rings.degreeOuter; const inner = rings.degreeInner; push(`<g id="archeometer-degree-crown" aria-label="dual 360 degree differential numerical protractor">`); for (let degree = 0; degree < 360; degree += 30) { const angle = angleOf(model, degree); const pOuter = polarToXY(center, (outer.r1 + outer.r2) / 2, angle); const pInner = polarToXY(center, (inner.r1 + inner.r2) / 2, angle); const rot = tangentRotation(angle); const outerText = String(normalizeDegrees(345 + degree) || 360); const innerText = String(normalizeDegrees(15 - degree) || 360); push(textSvg(outerText, pOuter, 8.4 * scale, palette.degreeLabel, rot, "middle")); push(textSvg(innerText, pInner, 7.6 * scale, palette.degreeLabel, rot, "middle")); } push(`</g>`); } function renderZodiacUtterance(push, model) { const { center, rings, palette, scale } = model; const ring = rings.zodiacUtterance; const sorted = sortedByDegree(model.utterance); push(`<g id="archeometer-zodiacal-utterance" aria-label="zodiacal crown of the utterance">`); for (let i = 0; i < 12; i++) { const degree = i * 30; const sector = annularSectorPath( center, ring.r1, ring.r2, angleOf(model, degree - 15), angleOf(model, degree + 15), 30 ); push( `<path d="${sector}" fill="${escapeAttr(palette.ringFills.zodiacUtterance)}" fill-opacity="1" stroke="${escapeAttr( palette.ringStroke )}" stroke-opacity="0.34" stroke-width="${fmt(0.7 * scale)}"/>` ); } for (const point of sorted) { const a = angleOf(model, point.degree); const p = polarToXY(center, ring.r1 + (ring.r2 - ring.r1) * 0.58, a); const shieldR = (ring.r2 - ring.r1) * 0.22; const letterParts = point.letter.split(",").map((part) => part.trim()).filter(Boolean); const isStacked = letterParts.length > 1; const letterFontSize = (isStacked ? letterParts.length > 2 ? 8.3 : 10.6 : 14) * scale; const letterLineHeight = letterFontSize * (isStacked ? 1.02 : 1); const numberFontSize = (isStacked ? 6.6 : 7.1) * scale; const numberGap = (isStacked ? 4.8 : 0) * scale; const stackedNumberY = p.y + (letterParts.length - 1) * letterLineHeight / 2 + numberGap + numberFontSize * 0.42; const singleLetterY = p.y - shieldR * 0.48; const firstLineY = isStacked ? p.y - (letterParts.length - 1) * letterLineHeight / 2 - numberFontSize * 0.42 : singleLetterY; const numberY = isStacked ? stackedNumberY : p.y + shieldR * 0.72; push( `<g class="archeometer-utterance-point" data-degree="${fmt(normalizeDegrees(point.degree))}" data-letter="${escapeAttr( point.letter )}">` ); for (const [index, part] of letterParts.entries()) { push(textSvg(part, { x: p.x, y: firstLineY + index * letterLineHeight }, letterFontSize, palette.ink, 0, "middle", 700)); } push(textSvg(String(point.number), { x: p.x, y: numberY }, numberFontSize, palette.ink, 0, "middle")); push(`</g>`); } push(`</g>`); } function renderPlanetaryUtterance(push, model) { const { center, rings, palette, scale } = model; const ring = rings.planetaryUtterance; const clipOuter = planetaryTriangleClipOuterRadius(rings); const vertexRadius = clipOuter; push(`<g id="archeometer-planetary-utterance" aria-label="four trigones of the planetary utterance">`); push(`<g clip-path="url(#archeometer-planetary-clip)">`); for (const triangle of model.triangles) { const vertices = triangle.vertices.map((degree) => polarToXY(center, vertexRadius, angleOf(model, degree))); if (triangle.vertexFills) { const centroid = polygonCentroid(vertices); const midpoints = vertices.map((vertex, index) => midpoint(vertex, vertices[(index + 1) % vertices.length])); for (const [index, vertex] of vertices.entries()) { const previousMidpoint = midpoints[(index + vertices.length - 1) % vertices.length]; const nextMidpoint = midpoints[index]; push(`<path class="archeometer-trigone-vertex-fill" data-triangle="${escapeAttr(triangle.id)}" data-degree="${fmt(normalizeDegrees(triangle.vertices[index]))}" d="${polygonPath([vertex, nextMidpoint, centroid, previousMidpoint])}" fill="${escapeAttr(triangle.vertexFills[index])}" fill-opacity="0.54" stroke="none"/>`); } push(`<path class="archeometer-trigone" data-triangle="${escapeAttr(triangle.id)}" d="${polygonPath(vertices)}" fill="none" stroke="${escapeAttr(palette.ink)}" stroke-opacity="0.82" stroke-width="${fmt(1.35 * scale)}"/>`); } else { push(`<path class="archeometer-trigone" data-triangle="${escapeAttr(triangle.id)}" d="${polygonPath(vertices)}" fill="${escapeAttr(triangle.fill)}" fill-opacity="0.52" stroke="${escapeAttr(palette.ink)}" stroke-opacity="0.82" stroke-width="${fmt(1.35 * scale)}"/>`); } } push(`</g>`); for (const label of sortedByDegree(model.triangleLabels)) { if (!label.label) continue; const a = angleOf(model, label.degree); const labelPoint = polarToXY(center, ring.r1 + (clipOuter - ring.r1) * 0.56, a); push(textSvg(label.label, labelPoint, 12 * scale, palette.ink, 0, "middle", 700)); if (label.number !== void 0) { const numberPoint = polarToXY(center, ring.r1 + (clipOuter - ring.r1) * 0.8, a); push(textSvg(String(label.number), numberPoint, 7.5 * scale, palette.ink, 0, "middle", 600)); } } push(`</g>`); } function renderCosmologicalMusic(push, model) { const { center, rings, palette, scale } = model; const ring = rings.cosmologicalMusic; const notes = sortedByDegree(model.musicalNotes); push(`<g id="archeometer-cosmological-music" aria-label="cosmological musical crown">`); push(`<path id="archeometer-music-backing" d="${annulusPath(center, ring.r1, ring.r2)}" fill="${escapeAttr(palette.paper)}" stroke="${escapeAttr(palette.ringStroke)}" stroke-opacity="0.58" stroke-width="${fmt(0.8 * scale)}" fill-rule="evenodd"/>`); for (const note of notes) { const point = nearestUtterance(note.degree, model.utterance); const start = angleOf(model, note.degree - 15); const end = angleOf(model, note.degree + 15); const sector = annularSectorPath(center, ring.r1, ring.r2, start, end, 30); push(`<path d="${sector}" fill="${escapeAttr(note.color ?? point?.color ?? palette.ringFills.cosmologicalMusic)}" fill-opacity="0.24" stroke="${escapeAttr(palette.ringStroke)}" stroke-opacity="0.3" stroke-width="${fmt(0.55 * scale)}"/>`); } for (const note of notes) { const start = angleOf(model, note.degree - 5.5); const end = angleOf(model, note.degree + 5.5); for (let i = 1; i <= 5; i++) { const r = ring.r1 + (ring.r2 - ring.r1) * i / 6; push(`<path class="archeometer-music-staff-line" data-degree="${fmt(normalizeDegrees(note.degree))}" d="${arcSegmentPath(center, r, start, end)}" fill="none" stroke="${escapeAttr(palette.ink)}" stroke-opacity="0.58" stroke-width="${fmt(0.48 * scale)}" stroke-linecap="round"/>`); } } for (const note of notes) { if (!note.note) continue; const p = polarToXY(center, (ring.r1 + ring.r2) / 2, angleOf(model, note.degree)); push(textSvg(note.note, p, 11.8 * scale, palette.ink, tangentRotation(angleOf(model, note.degree)), "middle", 700)); } push(`</g>`); } function renderAstralZodiac(push, model) { const { center, rings, palette, scale } = model; const ring = rings.astralZodiac; const ringWidth = ring.r2 - ring.r1; push(`<g id="archeometer-astral-zodiac" aria-label="astral zodiacal crown">`); for (const sign of sortedByDegree(model.zodiacSigns)) { const sector = annularSectorPath( center, ring.r1, ring.r2, angleOf(model, sign.degree - 15), angleOf(model, sign.degree + 15), 30 ); const pointColor = triangleVertexFillForDegree(model, sign.degree) ?? sign.color ?? nearestUtterance(sign.degree, model.utterance)?.color ?? palette.ringFills.astralZodiac; push( `<path d="${sector}" fill="${escapeAttr(pointColor)}" fill-opacity="0.40" stroke="${escapeAttr( palette.ringStroke )}" stroke-opacity="0.32" stroke-width="${fmt(0.6 * scale)}"/>` ); } const markerRadius = Math.min(ringWidth * 0.33, 13 * scale); const glyphSize = 17.5 * scale; for (const sign of sortedByDegree(model.zodiacSigns)) { const pointColor = triangleVertexFillForDegree(model, sign.degree) ?? sign.color ?? nearestUtterance(sign.degree, model.utterance)?.color ?? palette.ringFills.astralZodiac; const p = polarToXY(center, (ring.r1 + ring.r2) / 2, angleOf(model, sign.degree)); push( `<g class="archeometer-zodiac-sign" data-sign="${escapeAttr(sign.name)}" data-degree="${fmt( normalizeDegrees(sign.degree) )}">` ); push( `<circle cx="${fmt(p.x)}" cy="${fmt(p.y)}" r="${fmt(markerRadius)}" fill="${escapeAttr( pointColor )}" fill-opacity="0.48" stroke="${escapeAttr(palette.ink)}" stroke-width="${fmt(0.75 * scale)}"/>` ); push(textSvg(sign.glyph, p, glyphSize, palette.ink, 0, "middle")); push(`</g>`); } push(`</g>`); } function renderAstralPlanetary(push, model) { const { center, rings, palette, scale } = model; const ring = rings.astralPlanetary; const ringWidth = ring.r2 - ring.r1; push(`<g id="archeometer-astral-planetary" aria-label="astral planetary crown">`); for (const planet of sortedByDegree(model.planetaryPoints)) { const pointColor = triangleVertexFillForDegree(model, planet.degree) ?? planet.color ?? nearestUtterance(planet.degree, model.utterance)?.color ?? palette.ringFills.astralPlanetary; const sector = annularSectorPath( center, ring.r1, ring.r2, angleOf(model, planet.degree - 15), angleOf(model, planet.degree + 15), 30 ); push( `<path class="archeometer-astral-planetary-sector" data-degree="${fmt( normalizeDegrees(planet.degree) )}" d="${sector}" fill="${escapeAttr(pointColor)}" fill-opacity="0.36" stroke="none"/>` ); } for (let degree = 15; degree < 360; degree += 30) { const line = lineFromPolar(center, ring.r1, ring.r2, angleOf(model, degree)); push(lineSvg(line, palette.ringStroke, 0.6 * scale, 0.42, "archeometer-astral-planetary-divider", ` data-degree="${fmt(degree)}"`)); } const markerRadius = Math.min(ringWidth * 0.33, 13 * scale); for (const planet of sortedByDegree(model.planetaryPoints)) { const p = polarToXY(center, (ring.r1 + ring.r2) / 2, angleOf(model, planet.degree)); const color = triangleVertexFillForDegree(model, planet.degree) ?? planet.color ?? nearestUtterance(planet.degree, model.utterance)?.color ?? palette.ink; const glyphSize = (planet.name === "Moon" ? 17 : 18.5) * scale; push( `<g class="archeometer-planet" data-planet="${escapeAttr(planet.name)}" data-degree="${fmt( normalizeDegrees(planet.degree) )}">` ); push( `<circle cx="${fmt(p.x)}" cy="${fmt(p.y)}" r="${fmt(markerRadius)}" fill="${escapeAttr( color )}" fill-opacity="0.44" stroke="${escapeAttr(palette.ink)}" stroke-width="${fmt(0.8 * scale)}"/>` ); push(textSvg(planet.glyph, p, glyphSize, palette.ink, 0, "middle")); push(`</g>`); } push(`</g>`); } function renderChromicRays(push, model) { push(`<g id="archeometer-chromic-rays" aria-label="dodecagonal crown of chromic circum-solar rays">`); push(`<g clip-path="url(#archeometer-chromic-clip)">`); renderChromicTriangleCore(push, model); push(`</g>`); push(`</g>`); } function renderChromicTriangleCore(push, model) { const { center, rings, palette, scale } = model; const trianglesById = new Map(model.triangles.map((triangle) => [triangle.id, triangle])); const wordJesus = trianglesById.get("wordJesus"); const mary = trianglesById.get("mary"); const ether = trianglesById.get("ether"); const divineFire = trianglesById.get("divineFire"); const chromicOuter = chromicCoreOuterRadius(rings); const chromic = (x, y) => scaleArcheometerReferencePoint(center, chromicOuter, x, y); const path = (...commands) => commands.join(" "); const move = (x, y) => { const p = chromic(x, y); return `M ${fmt(p.x)} ${fmt(p.y)}`; }; const line = (x, y) => { const p = chromic(x, y); return `L ${fmt(p.x)} ${fmt(p.y)}`; }; const cubic = (x1, y1, x2, y2, x, y) => { const c1 = chromic(x1, y1); const c2 = chromic(x2, y2); const p = chromic(x, y); return `C ${fmt(c1.x)} ${fmt(c1.y)} ${fmt(c2.x)} ${fmt(c2.y)} ${fmt(p.x)} ${fmt(p.y)}`; }; const strokeWidth = fmt(1.4 * scale); const facetStrokeWidth = fmt(0.85 * scale); const primaryFacetStrokeWidth = fmt(0.95 * scale); const primaryStroke = wordJesus?.stroke ?? palette.ink; push(`<g id="archeometer-chromic-triangle-core" aria-label="inner chromic primary triangle core">`); push(`<path class="archeometer-chromic-foundation" data-triangle="ether" d="${path(move(1.3999, 137.047), line(204.875, 19.5703), line(204.875, 254.523), "Z")}" fill="${escapeAttr(ether?.fill ?? "#78BD79")}" stroke="${escapeAttr(palette.ink)}" stroke-width="${strokeWidth}"/>`); push(`<path class="archeometer-chromic-foundation" data-triangle="divineFire" d="${path(move(272.7, 137.047), line(69.2251, 254.523), line(69.2251, 19.5703), "Z")}" fill="${escapeAttr(divineFire?.fill ?? "#CC58A1")}" stroke="${escapeAttr(palette.ink)}" stroke-width="${strokeWidth}"/>`); push(`<path class="archeometer-chromic-foundation" data-triangle="mary" d="${path(move(137.05, 272.702), line(19.5737, 69.2266), line(254.527, 69.2266), "Z")}" fill="${escapeAttr(mary?.fill ?? "#E25B61")}" stroke="${escapeAttr(palette.ink)}" stroke-width="${strokeWidth}"/>`); for (const triangle of [ether, divineFire, mary]) { if (!triangle?.vertexFills) continue; const vertices = triangle.vertices.map((degree) => polarToXY(center, chromicOuter, angleOf(model, degree))); const centroid = polygonCentroid(vertices); const midpoints = vertices.map((vertex, index) => midpoint(vertex, vertices[(index + 1) % vertices.length])); for (const [index, vertex] of vertices.entries()) { const previousMidpoint = midpoints[(index + vertices.length - 1) % vertices.length]; const nextMidpoint = midpoints[index]; push(`<path class="archeometer-chromic-trigone-facet" data-triangle="${escapeAttr(triangle.id)}" data-degree="${fmt(normalizeDegrees(triangle.vertices[index]))}" d="${polygonPath([vertex, nextMidpoint, centroid, previousMidpoint])}" fill="${escapeAttr(triangle.vertexFills[index])}" stroke="${escapeAttr(palette.ink)}" stroke-opacity="0.72" stroke-width="${facetStrokeWidth}"/>`); } } push(`<path class="archeometer-chromic-primary-facet" data-triangle="wordJesus" data-degree="120" d="${path(move(137.05, 204.874), line(254.527, 204.874), line(195.789, 103.136), cubic(195.789, 103.136, 215.377, 136.525, 195.789, 170.699), cubic(176.2, 204.874, 137.05, 204.874, 137.05, 204.874), "Z")}" fill="${escapeAttr(wordJesus?.vertexFills?.[1] ?? "#5470A5")}" stroke="${escapeAttr(palette.ink)}" stroke-opacity="0.78" stroke-width="${primaryFacetStrokeWidth}"/>`); push(`<path class="archeometer-chromic-primary-facet" data-triangle="wordJesus" data-degree="240" d="${path(move(19.5737, 204.874), line(137.05, 204.874), cubic(137.05, 204.874, 94.7002, 203.927, 78.312, 170.699), cubic(61.9238, 137.472, 78.312, 103.136, 78.312, 103.136), line(19.5737, 204.874), "Z")}" fill="${escapeAttr(wordJesus?.vertexFills?.[2] ?? "#DD3E38")}" stroke="${escapeAttr(palette.ink)}" stroke-opacity="0.78" stroke-width="${primaryFacetStrokeWidth}"/>`); push(`<path class="archeometer-chromic-primary-facet" data-triangle="wordJesus" data-degree="0" fill-rule="evenodd" clip-rule="evenodd" d="${path(move(195.789, 103.136), line(137.05, 1.39844), line(78.312, 103.136), cubic(78.312, 103.136, 100.9, 71.1992, 137.05, 71.1992), cubic(173.2, 71.1992, 195.789, 103.136, 195.789, 103.136), "Z")}" fill="${escapeAttr(wordJesus?.vertexFills?.[0] ?? "#F2CF45")}" stroke="${escapeAttr(palette.ink)}" stroke-opacity="0.78" stroke-width="${primaryFacetStrokeWidth}"/>`); push(`<path class="archeometer-chromic-primary-outline" data-triangle="wordJesus" d="${path( move(137.05, 204.874), line(254.527, 204.874), line(195.789, 103.136), move(137.05, 204.874), line(19.5737, 204.874), line(78.312, 103.136), move(137.05, 204.874), cubic(137.05, 204.874, 94.7002, 203.927, 78.312, 170.699), cubic(61.9238, 137.472, 78.312, 103.136, 78.312, 103.136), move(137.05, 204.874), cubic(137.05, 204.874, 176.2, 204.874, 195.789, 170.699), cubic(215.377, 136.525, 195.789, 103.136, 195.789, 103.136), move(195.789, 103.136), line(137.05, 1.39844), line(78.312, 103.136), move(195.789, 103.136), cubic(195.789, 103.136, 173.2, 71.1992, 137.05, 71.1992), cubic(100.9, 71.1992, 78.312, 103.136, 78.312, 103.136) )}" fill="none" stroke="${escapeAttr(primaryStroke)}" stroke-width="${strokeWidth}"/>`); push(`</g>`); } function renderWhiteRays(push, model) { const { center, rings, palette, scale } = model; const solarRadius = rings.solarCenter.r2; const sunCenterCircleRadiusWithPadding = 3 + 0.3; push(`<g id="archeometer-white-rays" aria-label="crown of white rays and musical staff">`); for (let degree = 0; degree < 180; degree += 30) { if (degree === 90 || degree === 270) continue; push(lineSvg(lineFromPolar(center, solarRadius, sunCenterCircleRadiusWithPadding, angleOf(model, degree)), palette.whiteRay, 1.55 * scale, 0.92)); push(lineSvg(lineFromPolar(center, solarRadius, sunCenterCircleRadiusWithPadding, angleOf(model, degree + 180)), palette.whiteRay, 1.55 * scale, 0.92)); } push(`</g>`); } function renderSolarCenter(push, model) { const { center, rings, palette, scale } = model; const r = rings.solarCenter.r2; const innerR = r * 0.68; const arcStart = polarToXY(center, innerR * 0.72, deg2rad(240)); const arcEnd = polarToXY(center, innerR * 0.72, deg2rad(300)); const paddingTop = 22; const sunCenterCircleRadius = 3; push(`<g id="archeometer-solar-center" aria-label="solar center Mi">`); push(`<circle cx="${fmt(center.x)}" cy="${fmt(center.y)}" r="${fmt(r)}" fill="none" stroke="${escapeAttr(palette.ink)}" stroke-width="${fmt(1.2 * scale)}"/>`); push(lineSvg({ x1: center.x - innerR - 13, x2: center.x + innerR + 13, y1: center.y - 1.5, y2: center.y - 1.5 }, palette.whiteRay, 1.55 * scale, 0.92)); push(lineSvg({ x1: center.x - innerR - 13, x2: center.x + innerR + 13, y1: center.y, y2: center.y }, palette.whiteRay, 1.55 * scale, 0.92)); push(lineSvg({ x1: center.x - innerR - 13, x2: center.x + innerR + 13, y1: center.y + 1.5, y2: center.y + 1.5 }, palette.whiteRay, 1.55 * scale, 0.92)); push(`<line x1="${fmt(center.x - innerR - 2.5)}" y1="${fmt(center.y + 8 + paddingTop)}" x2="${fmt(center.x + innerR + 2.5)}" y2="${fmt(center.y + 8 + paddingTop)}" stroke="${escapeAttr(palette.ink)}" stroke-width="${fmt(1.1 * scale)}"/>`); push(`<line x1="${fmt(center.x - innerR - 7.5)}" y1="${fmt(center.y + paddingTop)}" x2="${fmt(center.x + innerR + 7.5)}" y2="${fmt(center.y + paddingTop)}" stroke="${escapeAttr(palette.ink)}" stroke-width="${fmt(1.1 * scale)}"/>`); push(`<line x1="${fmt(center.x - innerR - 11)}" y1="${fmt(center.y - 8 + paddingTop)}" x2="${fmt(center.x + innerR + 11)}" y2="${fmt(center.y - 8 + paddingTop)}" stroke="${escapeAttr(palette.ink)}" stroke-width="${fmt(1.1 * scale)}"/>`); push(`<path d="M ${fmt(arcStart.x)} ${fmt(arcStart.y)} A ${fmt(innerR * 0.72)} ${fmt(innerR * 0.72)} 0 0 1 ${fmt(arcEnd.x)} ${fmt(arcEnd.y)}" fill="none" stroke="${escapeAttr(palette.ink)}" stroke-width="${fmt(1.1 * scale)}"/>`); push(`<circle cx="${fmt(center.x)}" cy="${fmt(center.y)}" r="${fmt(sunCenterCircleRadius)}" fill="${escapeAttr(palette.paper)}" stroke="${escapeAttr(palette.ink)}" stroke-width="${fmt(0.5 * scale)}"/>`); push(`</g>`); } function renderFrame(push, model) { const { center, rings, palette, scale } = model; const radii = Array.from( new Map( [ rings.degreeOuter.r2, rings.degreeOuter.r1, rings.degreeInner.r1, rings.zodiacUtterance.r1, rings.planetaryUtterance.r1, rings.cosmologicalMusic.r1, rings.astralZodiac.r1, rings.astralPlanetary.r1, rings.chromicRays.r1, rings.solarCenter.r2 ].map((radius) => [fmt(radius), radius]) ).values() ); push(`<g id="archeometer-frame" aria-hidden="true">`); for (const radius of radii) { if (radius <= 0) continue; push( `<circle cx="${fmt(center.x)}" cy="${fmt(center.y)}" r="${fmt(radius)}" fill="none" stroke="${escapeAttr( palette.ringStroke )}" stroke-opacity="0.82" stroke-width="${fmt(0.85 * scale)}"/>` ); } push( `<circle cx="${fmt(center.x)}" cy="${fmt(center.y)}" r="${fmt( rings.degreeOuter.r2 )}" fill="none" stroke="${escapeAttr(palette.ringStroke)}" stroke-width="${fmt(2.2 * scale)}"/>` ); push(`</g>`); } function buildRings(contentRadius, frameRadius = contentRadius) { const ring = (id2, r1, r2, radius = contentRadius) => ({ id: id2, r1: round(radius * r1), r2: round(radius * r2) }); return { degreeOuter: ring("degreeOuter", 0.955, 1, frameRadius), degreeInner: { id: "degreeInner", r1: round(contentRadius * 0.955), r2: round(frameRadius * 0.955) }, zodiacUtterance: ring("zodiacUtterance", 0.8, 0.955), planetaryUtterance: ring("planetaryUtterance", 0.461, 0.8), cosmologicalMusic: ring("cosmologicalMusic", 0.395, 0.461), astralZodiac: ring("astralZodiac", 0.297, 0.395), astralPlanetary: ring("astralPlanetary", 0.198, 0.297), chromicRays: ring("chromicRays", 0.198, 0.395), whiteRays: ring("whiteRays", 0.12, 0.198), solarCenter: ring("solarCenter", 0, 0.099) }; } function planetaryTriangleClipInnerRadius(rings) { return rings.planetaryUtterance.r1; } function planetaryTriangleClipOuterRadius(rings) { return rings.planetaryUtterance.r2; } function chromicCoreInnerRadius(rings) { return rings.solarCenter.r2; } function chromicCoreOuterRadius(rings) { return rings.whiteRays.r2; } function resolvePalette(palette) { const base = COLOR_PALETTE; const overrides = typeof palette === "object" ? palette : {}; return { paper: overrides.paper ?? base.paper, ink: overrides.ink ?? base.ink, subtleInk: overrides.subtleInk ?? base.subtleInk, ringStroke: overrides.ringStroke ?? base.ringStroke, degreeTick: overrides.degreeTick ?? base.degreeTick, degreeLabel: overrides.degreeLabel ?? base.degreeLabel, whiteRay: overrides.whiteRay ?? base.whiteRay, ringFills: { ...base.ringFills, ...overrides.ringFills ?? {} } }; } function normalizeViewBox(viewBox) { return { minX: viewBox?.minX ?? ARCHEOMETER_DEFAULT_VIEWBOX.minX, minY: viewBox?.minY ?? ARCHEOMETER_DEFAULT_VIEWBOX.minY, width: viewBox?.width ?? ARCHEOMETER_DEFAULT_VIEWBOX.width, height: viewBox?.height ?? ARCHEOMETER_DEFAULT_VIEWBOX.height }; } function angleOf(model, degree) { return deg2rad(degree + model.rotationDegrees - 90); } function polarToXY(center, radius, angleRad) { return { x: round(center.x + Math.cos(angleRad) * radius), y: round(center.y + Math.sin(angleRad) * radius) }; } function scaleArcheometerReferencePoint(center, radius, x, y) { const referenceCenter = 137.05; const referenceRadius = 135.652; const scale = radius / referenceRadius; return { x: round(center.x + (x - referenceCenter) * scale), y: round(center.y + (y - referenceCenter) * scale) }; } function lineFromPolar(center, r1, r2, angleRad) { const p1 = polarToXY(center, r1, angleRad); const p2 = polarToXY(center, r2, angleRad); return { x1: p1.x, y1: p1.y, x2: p2.x, y2: p2.y }; } function lineSvg(line, color, strokeWidth, opacity = 1, className, attributes = "") { const classAttr = className ? ` class="${escapeAttr(className)}"` : ""; return `<line${classAttr}${attributes} x1="${fmt(line.x1)}" y1="${fmt(line.y1)}" x2="${fmt(line.x2)}" y2="${fmt(line.y2)}" stroke="${escapeAttr(color)}" stroke-opacity="${fmt(opacity)}" stroke-width="${fmt(strokeWidth)}" stroke-linecap="round"/>`; } function textSvg(value, p, fontSize, color, rotation = 0, anchor = "middle", weight, className, attributes = "") { const transform = rotation ? ` transform="rotate(${fmt(rotation)} ${fmt(p.x)} ${fmt(p.y)})"` : ""; const weightAttr = weight ? ` font-weight="${weight}"` : ""; const classAttr = ""; return `<text${classAttr}${attributes} x="${fmt(p.x)}" y="${fmt(p.y)}"${transform} font-family="${textFontFamily()}" font-size="${fmt(fontSize)}" text-anchor="${anchor}" dominant-baseline="middle" fill="${escapeAttr(color)}"${weightAttr}>${escapeText(value)}</text>`; } function annulusPath(center, r1, r2) { return [ `M ${fmt(center.x)} ${fmt(center.y - r2)}`, `A ${fmt(r2)} ${fmt(r2)} 0 1 1 ${fmt(center.x)} ${fmt(center.y + r2)}`, `A ${fmt(r2)} ${fmt(r2)} 0 1 1 ${fmt(center.x)} ${fmt(center.y - r2)}`, `M ${fmt(center.x)} ${fmt(center.y - r1)}`, `A ${fmt(r1)} ${fmt(r1)} 0 1 0 ${fmt(center.x)} ${fmt(center.y + r1)}`, `A ${fmt(r1)} ${fmt(r1)} 0 1 0 ${fmt(center.x)} ${fmt(center.y - r1)}`, `Z` ].join(" "); } function annularSectorPath(center, r1, r2, startAngle, endAngle, spanDegrees) { const outerStart = polarToXY(center, r2, startAngle); const outerEnd = polarToXY(center, r2, endAngle); const innerEnd = polarToXY(center, r1, endAngle); const innerStart = polarToXY(center, r1, startAngle); const largeArc = Math.abs(spanDegrees) > 180 ? 1 : 0; const sweep = 1 ; const inverseSweep = 0 ; return [ `M ${fmt(outerStart.x)} ${fmt(outerStart.y)}`, `A ${fmt(r2)} ${fmt(r2)} 0 ${largeArc} ${sweep} ${fmt(outerEnd.x)} ${fmt(outerEnd.y)}`, `L ${fmt(innerEnd.x)} ${fmt(innerEnd.y)}`, `A ${fmt(r1)} ${fmt(r1)} 0 ${largeArc} ${inverseSweep} ${fmt(innerStart.x)} ${fmt(innerStart.y)}`, `Z` ].join(" "); } function arcSegmentPath(center, radius, startAngle, endAngle) { const start = polarToXY(center, radius, startAngle); const end = polarToXY(center, radius, endAngle); return `M ${fmt(start.x)} ${fmt(start.y)} A ${fmt(radius)} ${fmt(radius)} 0 0 1 ${fmt(end.x)} ${fmt(end.y)}`; } function polygonPath(points) { if (points.length === 0) return ""; const [first, ...rest] = points; return [`M ${fmt(first.x)} ${fmt(first.y)}`, ...rest.map((p) => `L ${fmt(p.x)} ${fmt(p.y)}`), "Z"].join(" "); } function midpoint(a, b) { return { x: round((a.x + b.x) / 2), y: round((a.y + b.y) / 2) }; } function polygonCentroid(points) { const total = points.reduce( (sum, point) => ({ x: sum.x + point.x, y: sum.y + point.y }), { x: 0, y: 0 } ); return { x: round(total.x / points.length), y: round(total.y / points.length) }; } function sortedByDegree(items) { return [...items].sort((a, b) => normalizeDegrees(a.degree) - normalizeDegrees(b.degree)); } function nearestUtterance(degree, utterance) { let best; let bestDelta = Number.POSITIVE_INFINITY; for (const point of utterance) { const delta = Math.abs(shortestAngularDistance(degree, point.degree)); if (delta < bestDelta) { best = point; bestDelta = delta; } } return best; } function triangleVertexFillForDegree(model, degree) { for (const triangle of model.triangles) { if (!triangle.vertexFills) continue; for (const [index, vertexDegree] of triangle.vertices.entries()) { if (Math.abs(shortestAngularDistance(degree, vertexDegree)) < 1e-3) { return triangle.vertexFills[index]; } } } return void 0; } function tangentRotation(angleRad) { let degrees = rad2deg(angleRad) + 90; degrees = (degrees % 360 + 360) % 360; if (degrees > 90 && degrees < 270) degrees += 180; return (degrees % 360 + 360) % 360; } function shortestAngularDistance(a, b) { return (normalizeDegrees(a) - normalizeDegrees(b) + 540) % 360 - 180; } function normalizeDegrees(value) { return (value % 360 + 360) % 360; } function deg2rad(degrees) { return degrees * (TAU / 360); } function rad2deg(radians) { return radians * (360 / TAU); } function round(value) { return Math.round(value * 1e3) / 1e3; } function fmt(value) { if (!Number.isFinite(value)) return "0"; return Number.isInteger(value) ? String(value) : value.toFixed(3).replace(/0+$/, "").replace(/\.$/, ""); } function escapeAttr(value) { return escapeText(value).replace(/"/g, "&quot;"); } function escapeText(value) { return String(value).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;"); } function textFontFamily() { return "Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Noto Sans Symbols 2', 'Noto Sans Symbols', 'Segoe UI Symbol', 'Apple Symbols', sans-serif"; } // src/visual/astro-glyph-assets.ts var rawGlyph = (markup) => [ { kind: "raw", markup } ]; var raw100 = (markup) => rawGlyph(`<g transform="scale(0.01)">${markup}</g>`); var GLYPH_FILL = "var(--astro-glyph-fill, #fff)"; var rawPlanet = (markup) => rawGlyph( `<g transform="scale(0.01)" stroke="currentColor" stroke-width="7" stroke-linecap="round" stroke-linejoin="round">${markup}</g>` ); var SOURCE_POINT_TEXT_GLYPH = (text) => rawGlyph( `<g transform="scale(0.01)"><text x="0" y="0" text-anchor="middle" dominant-baseline="central" font-family="Arial Narrow, Liberation Sans Narrow, Arial, Helvetica, sans-serif" font-size="90" font-weight="500" fill="currentColor" stroke="none">${text}</text></g>` ); var SOURCE_ZODIAC_GLYPH_PRIMITIVES = { "Aries": [{ kind: "raw", markup: '<g transform="scale(0.0172864) translate(-66.624999 -71.429581)"><path d="M 64.28125,95.179581 L 64.28125,91.398331 C 64.281226,88.419171 63.635393,83.106676 62.34375,75.460831 C 61.760395,71.856687 60.802062,68.106691 59.46875,64.210831 C 58.114565,60.252532 56.6979,57.148369 55.21875,54.898331 C 54.072902,53.190039 52.70832,52.335874 51.125,52.335831 C 49.333324,52.335874 48.083325,53.023373 47.375,54.398331 C 46.72916,55.669204 46.406243,57.054619 46.40625,58.554581 C 46.406243,61.721281 47.541659,64.585861 49.8125,67.148331 L 44.5,67.148331 C 42.666664,64.335862 41.749998,61.367115 41.75,58.242081 C 41.749998,54.971288 42.624997,52.40879 44.375,50.554581 C 46.187494,48.637961 48.385408,47.679628 50.96875,47.679581 C 54.302069,47.679628 56.906233,49.054627 58.78125,51.804581 C 60.906229,54.908788 62.67706,58.679617 64.09375,63.117081 C 65.093725,66.346276 65.937474,69.919189 66.625,73.835831 C 67.312472,69.919189 68.156222,66.346276 69.15625,63.117081 C 70.489553,58.783784 72.260384,55.012954 74.46875,51.804581 C 76.343713,49.054627 78.947878,47.679628 82.28125,47.679581 C 84.864538,47.679628 87.062453,48.637961 88.875,50.554581 C 90.624949,52.40879 91.499948,54.971288 91.5,58.242081 C 91.499948,61.367115 90.583283,64.335862 88.75,67.148331 L 83.4375,67.148331 C 85.708287,64.585861 86.843703,61.721281 86.84375,58.554581 C 86.843703,57.054619 86.520787,55.669204 85.875,54.398331 C 85.166621,53.023373 83.916623,52.335874 82.125,52.335831 C 80.541626,52.335874 79.177044,53.190039 78.03125,54.898331 C 76.552047,57.148369 75.135381,60.252532 73.78125,64.210831 C 72.447884,68.106691 71.489552,71.856687 70.90625,75.460831 C 69.614554,83.106676 68.968721,88.419171 68.96875,91.398331 L 68.96875,95.179581 L 64.28125,95.179581" fill="currentColor" stroke="none" stroke-linecap="round" stroke-linejoin="round"/></g>' }], "Taurus": [{ kind: "raw", markup: '<g transform="scale(0.0175287) translate(-155.375000 -71.429581)"><path d="M 140.9375,59.023331 C 140.37499,57.690034 139.57291,56.502535 138.53125,55.460831 C 137.57291,54.502537 136.39583,53.710871 135,53.085831 C 133.79166,52.544206 132.40625,52.273373 130.84375,52.273331 L 130.84375,47.585831 C 132.98958,47.585877 134.96874,47.971294 136.78125,48.742081 C 138.67707,49.554626 140.36457,50.690041 141.84375,52.148331 C 143.34374,53.627538 144.48957,55.325453 145.28125,57.242081 C 145.84373,58.57545 146.64582,59.762949 147.6875,60.804581 C 148.64581,61.762947 149.8229,62.554613 151.21875,63.179581 C 152.42706,63.721278 153.81247,63.992111 155.375,63.992081 C 156.87497,63.992111 158.26039,63.721278 159.53125,63.179581 C 160.86455,62.617112 162.04163,61.825447 163.0625,60.804581 C 164.10413,59.762949 164.90621,58.57545 165.46875,57.242081 C 166.26038,55.325453 167.40621,53.627538 168.90625,52.148331 C 170.38537,50.690041 172.07287,49.554626 173.96875,48.742081 C 175.7812,47.971294 177.76037,47.585877 179.90625,47.585831 L 179.90625,52.273331 C 178.3437,52.273373 176.95828,52.544206 175.75,53.085831 C 174.35412,53.710871 173.17704,54.502537 172.21875,55.460831 C 171.17704,56.502535 170.37496,57.690034 169.8125,59.023331 C 169.02079,60.919197 167.87496,62.617112 166.375,64.117081 C 165.6458,64.846277 164.69788,65.585859 163.53125,66.335831 C 164.69788,67.085858 165.6458,67.825441 166.375,68.554581 C 167.87496,70.054605 169.02079,71.75252 169.8125,73.648331 C 170.58329,75.52335 170.96871,77.533764 170.96875,79.679581 C 170.96871,81.762927 170.58329,83.742091 169.8125,85.617081 C 168.99996,87.533754 167.85413,89.231669 166.375,90.710831 C 164.8958,92.169166 163.2083,93.304582 161.3125,94.117081 C 159.49997,94.887914 157.5208,95.27333 155.375,95.273331 C 153.12498,95.27333 151.14581,94.887914 149.4375,94.117081 C 147.45831,93.221249 145.77082,92.085833 144.375,90.710831 C 142.89582,89.231669 141.74999,87.533754 140.9375,85.617081 C 140.16666,83.742091 139.78124,81.762927 139.78125,79.679581 C 139.78124,77.533764 140.16666,75.52335 140.9375,73.648331 C 141.77082,71.62752 142.91665,69.929605 144.375,68.554581 C 145.27082,67.700441 146.22915,66.960858 147.25,66.335831 C 146.20832,65.710859 145.24998,64.971277 144.375,64.117081 C 142.91665,62.700446 141.77082,61.002531 140.9375,59.023331 M 159.53125,69.492081 C 158.26039,68.950439 156.87497,68.679606 155.375,68.679581 C 153.87497,68.679606 152.48956,68.950439 151.21875,69.492081 C 149.92706,70.075438 148.74998,70.867104 147.6875,71.867081 C 146.60415,72.992102 145.80207,74.179601 145.28125,75.429581 C 144.73957,76.742098 144.46873,78.158764 144.46875,79.679581 C 144.46873,81.137927 144.73957,82.523343 145.28125,83.835831 C 145.80207,85.08584 146.60415,86.273339 147.6875,87.398331 C 148.74998,88.398337 149.92706,89.190003 151.21875,89.773331 C 152.48956,90.315001 153.87497,90.585834 155.375,90.585831 C 156.87497,90.585834 158.26039,90.315001 159.53125,89.773331 C 160.82288,89.190003 161.99997,88.398337 163.0625,87.398331 C 164.1458,86.273339 164.94788,85.08584 165.46875,83.835831 C 166.01038,82.523343 166.28121,81.137927 166.28125,79.679581 C 166.28121,78.158764 166.01038,76.742098 165.46875,75.429581 C 164.94788,74.179601 164.1458,72.992102 163.0625,71.867081 C 161.99997,70.867104 160.82288,70.075438 159.53125,69.492081" fill="currentColor" stroke="none" stroke-linecap="round" stroke-linejoin="round"/></g>' }], "Gemini": [{ kind: "raw", markup: '<g transform="scale(0.0177663) translate(-244.125000 -71.429581)"><path d="M 257.25,89.007706 C 260.72912,89.486878 264.12495,90.143127 267.4375,90.976456 L 267.4375,95.632706 C 260.06246,93.77854 252.29163,92.851458 244.125,92.851456 C 235.95831,92.851458 228.18749,93.77854 220.8125,95.632706 L 220.8125,90.976456 C 224.12499,90.143127 227.52082,89.486878 231,89.007706 L 231,53.851456 C 227.52082,53.37233 224.12499,52.716081 220.8125,51.882706 L 220.8125,47.226456 C 228.18749,49.080668 235.95831,50.00775 244.125,50.007706 C 252.29163,50.00775 260.06246,49.080668 267.4375,47.226456 L 267.4375,51.882706 C 264.12495,52.716081 260.72912,53.37233 257.25,53.851456 L 257.25,89.007706 M 252.5625,54.351456 C 249.79163,54.580663 246.97914,54.695246 244.125,54.695206 C 241.27081,54.695246 238.45831,54.580663 235.6875,54.351456 L 235.6875,88.507706 C 238.45831,88.278545 241.27081,88.163962 244.125,88.163956 C 246.97914,88.163962 249.79163,88.278545 252.5625,88.507706 L 252.5625,54.351456" fill="currentColor" stroke="none" stroke-linecap="round" stroke-linejoin="round"/></g>' }], "Cancer": [{ kind: "raw", markup: '<g transform="scale(0.0190187) translate(-332.875000 -71.429584)"><path d="M 310.26562,78.913956 C 312.16145,80.122304 313.98437,81.101469 315.73438,81.851456 C 320.19269,83.788967 325.14061,84.757716 330.57812,84.757706 C 333.99476,84.757716 337.23434,84.372299 340.29688,83.601456 C 340.02601,83.3723 339.77601,83.143134 339.54688,82.913956 C 338.60934,81.955635 337.92184,80.934803 337.48438,79.851456 C 337.02601,78.726472 336.79684,77.528556 336.79688,76.257706 C 336.79684,75.007725 337.02601,73.820227 337.48438,72.695206 C 337.96351,71.507729 338.65101,70.486897 339.54688,69.632706 C 340.52601,68.695232 341.53642,68.007732 342.57812,67.570206 C 343.66142,67.1119 344.84892,66.882734 346.14062,66.882706 C 347.49475,66.882734 348.68225,67.1119 349.70312,67.570206 C 350.91141,68.111899 351.92183,68.799398 352.73438,69.632706 C 353.67183,70.591063 354.35933,71.611896 354.79688,72.695206 C 355.25516,73.820227 355.48433,75.007725 355.48438,76.257706 C 355.48433,77.507723 355.25516,78.705638 354.79688,79.851456 C 354.29683,81.101469 353.50516,82.174385 352.42188,83.070206 C 350.901,84.320216 3