sri-yantra 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/FUNDING.yml +1 -0
- package/LICENSE +21 -0
- package/README.md +38 -0
- package/about.html +175 -0
- package/css/about.css +132 -0
- package/css/common.css +305 -0
- package/css/core.css +124 -0
- package/css/densities.css +46 -0
- package/css/themes/dark/bg0.png +0 -0
- package/css/themes/dark/dark.css +279 -0
- package/css/themes/evol-blue/evol-blue.css +113 -0
- package/css/themes/evol-blue/spirals.png +0 -0
- package/css/themes/light/bg0.png +0 -0
- package/css/themes/light/light.css +94 -0
- package/css/yantra.css +40 -0
- package/favicon.png +0 -0
- package/icon-192.png +0 -0
- package/icon-512.png +0 -0
- package/index.html +254 -0
- package/js/omg.js +88 -0
- package/js/yantra-data.js +20 -0
- package/js/yantra.js +305 -0
- package/manifest.json +24 -0
- package/package.json +26 -0
- package/sri-yantra.png +0 -0
- package/sw.js +71 -0
package/index.html
ADDED
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<title>Sri Yantra</title>
|
|
7
|
+
<meta
|
|
8
|
+
name="description"
|
|
9
|
+
content="Sri Yantra generator: nine interlocking triangles with lotus petals, circles and gates, drawn to scale. Pick colors, build it up triangle by triangle, and download it as SVG or PNG. Free, works offline."
|
|
10
|
+
/>
|
|
11
|
+
<meta name="author" content="Olivier Giulieri" />
|
|
12
|
+
<link rel="canonical" href="https://evoluteur.github.io/sri-yantra/" />
|
|
13
|
+
<link rel="icon" type="image/png" href="favicon.png" />
|
|
14
|
+
<link rel="manifest" href="manifest.json" />
|
|
15
|
+
<link rel="apple-touch-icon" href="icon-192.png" />
|
|
16
|
+
<meta name="theme-color" content="#1a212d" />
|
|
17
|
+
|
|
18
|
+
<link
|
|
19
|
+
rel="stylesheet"
|
|
20
|
+
href="https://fonts.googleapis.com/css?family=Overpass"
|
|
21
|
+
/>
|
|
22
|
+
<link id="omg-core-css" rel="stylesheet" href="css/core.css" />
|
|
23
|
+
<link id="omg-theme-css" rel="stylesheet" href="css/themes/dark/dark.css" />
|
|
24
|
+
<link id="omg-density-css" rel="stylesheet" href="css/densities.css" />
|
|
25
|
+
<link rel="stylesheet" href="css/common.css" />
|
|
26
|
+
<link rel="stylesheet" href="css/yantra.css" />
|
|
27
|
+
|
|
28
|
+
<script src="js/omg.js"></script>
|
|
29
|
+
<script src="js/yantra-data.js"></script>
|
|
30
|
+
<script src="js/yantra.js"></script>
|
|
31
|
+
<script>
|
|
32
|
+
if ("serviceWorker" in navigator) {
|
|
33
|
+
addEventListener("load", () =>
|
|
34
|
+
navigator.serviceWorker.register("sw.js"),
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
</script>
|
|
38
|
+
</head>
|
|
39
|
+
|
|
40
|
+
<body
|
|
41
|
+
onload="
|
|
42
|
+
setupPage('home');
|
|
43
|
+
initYantra();
|
|
44
|
+
"
|
|
45
|
+
id="omg-body"
|
|
46
|
+
>
|
|
47
|
+
<div id="omg-header">
|
|
48
|
+
<h1>Sri Yantra</h1>
|
|
49
|
+
<div id="omg-header-tools">
|
|
50
|
+
<div id="omg-theme-picker"></div>
|
|
51
|
+
<a
|
|
52
|
+
id="omg-github"
|
|
53
|
+
href="https://github.com/evoluteur/sri-yantra"
|
|
54
|
+
target="_blank"
|
|
55
|
+
rel="noopener"
|
|
56
|
+
title="Sri Yantra on GitHub"
|
|
57
|
+
aria-label="Sri Yantra on GitHub"
|
|
58
|
+
><svg viewBox="0 0 16 16" aria-hidden="true">
|
|
59
|
+
<path
|
|
60
|
+
d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z"
|
|
61
|
+
/></svg
|
|
62
|
+
></a>
|
|
63
|
+
</div>
|
|
64
|
+
</div>
|
|
65
|
+
<noscript
|
|
66
|
+
><p class="tagline">
|
|
67
|
+
Sri Yantra draws as SVG, so it needs JavaScript. Enable it to draw one,
|
|
68
|
+
or read <a href="about.html">about the Sri Yantra</a>.
|
|
69
|
+
</p></noscript
|
|
70
|
+
>
|
|
71
|
+
<h2>Nine triangles around a point</h2>
|
|
72
|
+
<div class="content">
|
|
73
|
+
<p class="tagline">
|
|
74
|
+
The Sri Yantra is the best known of the sacred diagrams of the Indian
|
|
75
|
+
tantric traditions: four upward triangles and five downward ones,
|
|
76
|
+
interlaced around a central point and ringed by lotus petals, circles
|
|
77
|
+
and a gated square. This one is drawn to scale in your browser. Change
|
|
78
|
+
the colors, build it up one triangle at a time, and save it. New to the
|
|
79
|
+
Sri Yantra? Read <a href="about.html">about it</a>.
|
|
80
|
+
</p>
|
|
81
|
+
|
|
82
|
+
<div class="layout">
|
|
83
|
+
<div class="stage">
|
|
84
|
+
<div id="yantra" class="canvas-box"></div>
|
|
85
|
+
<p class="note" id="yantra-note"></p>
|
|
86
|
+
</div>
|
|
87
|
+
|
|
88
|
+
<div class="side">
|
|
89
|
+
<div class="panel">
|
|
90
|
+
<h3>Build it up</h3>
|
|
91
|
+
<div class="ctl">
|
|
92
|
+
<label for="tri-n">Triangles drawn, smallest first</label
|
|
93
|
+
><span class="val" id="tri-n-val"></span>
|
|
94
|
+
<input type="range" id="tri-n" min="0" max="9" step="1" />
|
|
95
|
+
</div>
|
|
96
|
+
<div
|
|
97
|
+
class="chips"
|
|
98
|
+
id="which-chips"
|
|
99
|
+
role="group"
|
|
100
|
+
aria-label="Which triangles"
|
|
101
|
+
></div>
|
|
102
|
+
<div class="btns">
|
|
103
|
+
<button type="button" id="play" class="ghost">Build it up</button>
|
|
104
|
+
</div>
|
|
105
|
+
</div>
|
|
106
|
+
|
|
107
|
+
<div class="panel">
|
|
108
|
+
<h3>Layers</h3>
|
|
109
|
+
<label class="opt"
|
|
110
|
+
><input type="checkbox" id="l-bindu" /> Bindu (the central
|
|
111
|
+
point)</label
|
|
112
|
+
>
|
|
113
|
+
<label class="opt"
|
|
114
|
+
><input type="checkbox" id="l-tri" /> The nine triangles</label
|
|
115
|
+
>
|
|
116
|
+
<label class="opt"
|
|
117
|
+
><input type="checkbox" id="l-lotus8" /> Eight-petal lotus</label
|
|
118
|
+
>
|
|
119
|
+
<label class="opt"
|
|
120
|
+
><input type="checkbox" id="l-lotus16" /> Sixteen-petal
|
|
121
|
+
lotus</label
|
|
122
|
+
>
|
|
123
|
+
<label class="opt"
|
|
124
|
+
><input type="checkbox" id="l-circles" /> Circles</label
|
|
125
|
+
>
|
|
126
|
+
<label class="opt"
|
|
127
|
+
><input type="checkbox" id="l-gates" /> Square with four
|
|
128
|
+
gates</label
|
|
129
|
+
>
|
|
130
|
+
</div>
|
|
131
|
+
|
|
132
|
+
<div class="panel">
|
|
133
|
+
<h3>Look</h3>
|
|
134
|
+
<div
|
|
135
|
+
class="chips"
|
|
136
|
+
id="pal-chips"
|
|
137
|
+
role="group"
|
|
138
|
+
aria-label="Colors"
|
|
139
|
+
></div>
|
|
140
|
+
<div
|
|
141
|
+
class="chips"
|
|
142
|
+
id="fill-chips"
|
|
143
|
+
role="group"
|
|
144
|
+
aria-label="Fill"
|
|
145
|
+
></div>
|
|
146
|
+
<div class="ctl">
|
|
147
|
+
<label for="weight">Line weight</label
|
|
148
|
+
><span class="val" id="weight-val"></span>
|
|
149
|
+
<input type="range" id="weight" min="4" max="30" step="1" />
|
|
150
|
+
</div>
|
|
151
|
+
</div>
|
|
152
|
+
|
|
153
|
+
<div class="panel">
|
|
154
|
+
<h3>Save</h3>
|
|
155
|
+
<div class="btns">
|
|
156
|
+
<button type="button" id="export-png">Download PNG</button>
|
|
157
|
+
<button type="button" id="export-svg" class="ghost">
|
|
158
|
+
Download SVG
|
|
159
|
+
</button>
|
|
160
|
+
</div>
|
|
161
|
+
</div>
|
|
162
|
+
</div>
|
|
163
|
+
</div>
|
|
164
|
+
|
|
165
|
+
<div class="layout info">
|
|
166
|
+
<div class="side wide">
|
|
167
|
+
<div class="panel">
|
|
168
|
+
<h3>The nine enclosures</h3>
|
|
169
|
+
<p class="note">
|
|
170
|
+
Tradition reads the yantra as nine enclosures, called
|
|
171
|
+
<em>avaranas</em>, from the outside in.
|
|
172
|
+
</p>
|
|
173
|
+
<ol class="enclosures" id="enclosures"></ol>
|
|
174
|
+
</div>
|
|
175
|
+
</div>
|
|
176
|
+
<div class="side wide">
|
|
177
|
+
<div class="panel">
|
|
178
|
+
<h3>Shiva and Shakti</h3>
|
|
179
|
+
<p class="note">
|
|
180
|
+
The four upward triangles are usually read as Shiva, the masculine
|
|
181
|
+
principle, and the five downward ones as Shakti, the feminine.
|
|
182
|
+
They meet at the bindu, where the two are said to be one. Meanings
|
|
183
|
+
vary between lineages, and there are several schools of drawing.
|
|
184
|
+
</p>
|
|
185
|
+
<p class="note">
|
|
186
|
+
This drawing takes its triangle coordinates from a published
|
|
187
|
+
construction. The two largest triangles have their corners on the
|
|
188
|
+
circle, and the figure is symmetric left to right. Practitioners
|
|
189
|
+
may prefer other proportions, and a hand-drawn yantra is a matter
|
|
190
|
+
of practice and not just geometry.
|
|
191
|
+
</p>
|
|
192
|
+
</div>
|
|
193
|
+
</div>
|
|
194
|
+
</div>
|
|
195
|
+
|
|
196
|
+
<div class="footer">
|
|
197
|
+
<p><a href="about.html">About the Sri Yantra</a></p>
|
|
198
|
+
<p>
|
|
199
|
+
Triangle coordinates from
|
|
200
|
+
<a href="https://texample.net/tikz/examples/sri-yantra/">TeXample</a>
|
|
201
|
+
(CC BY-SA 4.0).
|
|
202
|
+
</p>
|
|
203
|
+
<p>
|
|
204
|
+
The code of this page is
|
|
205
|
+
<a href="https://github.com/evoluteur/sri-yantra">Open source</a> with
|
|
206
|
+
MIT license.
|
|
207
|
+
</p>
|
|
208
|
+
<p>
|
|
209
|
+
Had fun browsing the app?
|
|
210
|
+
<a href="https://github.com/sponsors/evoluteur"
|
|
211
|
+
>Buy me a coffee by becoming a sponsor</a
|
|
212
|
+
>.
|
|
213
|
+
</p>
|
|
214
|
+
<p>
|
|
215
|
+
You may also like
|
|
216
|
+
<a
|
|
217
|
+
href="https://evoluteur.github.io/mandala-maker/"
|
|
218
|
+
target="_blank"
|
|
219
|
+
rel="noopener"
|
|
220
|
+
>Mandala Maker</a
|
|
221
|
+
>,
|
|
222
|
+
<a
|
|
223
|
+
href="https://evoluteur.github.io/sacred-geometry/"
|
|
224
|
+
target="_blank"
|
|
225
|
+
rel="noopener"
|
|
226
|
+
>Sacred Geometry</a
|
|
227
|
+
>,
|
|
228
|
+
<a
|
|
229
|
+
href="https://evoluteur.github.io/platonic-solids/"
|
|
230
|
+
target="_blank"
|
|
231
|
+
rel="noopener"
|
|
232
|
+
>Platonic Solids</a
|
|
233
|
+
>, and
|
|
234
|
+
<a
|
|
235
|
+
href="https://evoluteur.github.io/archimedean-solids/"
|
|
236
|
+
target="_blank"
|
|
237
|
+
rel="noopener"
|
|
238
|
+
>Archimedean Solids</a
|
|
239
|
+
>. See them all on
|
|
240
|
+
<a
|
|
241
|
+
href="https://evoluteur.github.io/esoterica.html"
|
|
242
|
+
target="_blank"
|
|
243
|
+
rel="noopener"
|
|
244
|
+
>Esoterica</a
|
|
245
|
+
>.
|
|
246
|
+
</p>
|
|
247
|
+
<p class="copyright">
|
|
248
|
+
© 2026
|
|
249
|
+
<a href="https://evoluteur.github.io/">Olivier Giulieri</a>
|
|
250
|
+
</p>
|
|
251
|
+
</div>
|
|
252
|
+
</div>
|
|
253
|
+
</body>
|
|
254
|
+
</html>
|
package/js/omg.js
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
const themes = ["dark", "light", "evol-blue"];
|
|
2
|
+
const themesColors = {
|
|
3
|
+
"evol-blue": "#0288d1",
|
|
4
|
+
dark: "#1a212d",
|
|
5
|
+
light: "#fdfdff",
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
// Same icon set as react-morph-charts' ThemeToggle (sun / moon / the
|
|
9
|
+
// squiggle used for its third "omg" option, reused here for evol-blue).
|
|
10
|
+
const themeIcons = {
|
|
11
|
+
light:
|
|
12
|
+
'<svg viewBox="0 0 24 24" width="18" height="18" fill="currentColor" aria-hidden="true"><path d="M12,7A5,5 0 0,1 17,12A5,5 0 0,1 12,17A5,5 0 0,1 7,12A5,5 0 0,1 12,7M12,9A3,3 0 0,0 9,12A3,3 0 0,0 12,15A3,3 0 0,0 15,12A3,3 0 0,0 12,9M12,2L14.39,5.42C13.65,5.15 12.84,5 12,5C11.16,5 10.35,5.15 9.61,5.42L12,2M3.34,7L7.5,6.65C6.9,7.16 6.36,7.78 5.94,8.5C5.5,9.24 5.25,10 5.11,10.79L3.34,7M3.36,17L5.12,13.23C5.26,14 5.53,14.78 5.95,15.5C6.37,16.24 6.91,16.86 7.5,17.37L3.36,17M20.65,7L18.88,10.79C18.74,10 18.47,9.23 18.05,8.5C17.63,7.78 17.1,7.15 16.5,6.64L20.65,7M20.64,17L16.5,17.36C17.09,16.85 17.62,16.22 18.04,15.5C18.46,14.77 18.73,14 18.87,13.21L20.64,17M12,22L9.59,18.56C10.33,18.83 11.14,19 12,19C12.82,19 13.63,18.83 14.37,18.56L12,22Z" /></svg>',
|
|
13
|
+
dark:
|
|
14
|
+
'<svg viewBox="0 0 24 24" width="18" height="18" fill="currentColor" aria-hidden="true"><path d="M18.97,15.95C19.8,15.87 20.69,17.05 20.16,17.8C19.84,18.25 19.5,18.67 19.08,19.07C15.17,23 8.84,23 4.94,19.07C1.03,15.17 1.03,8.83 4.94,4.93C5.34,4.53 5.76,4.17 6.21,3.85C6.96,3.32 8.14,4.21 8.06,5.04C7.79,7.9 8.75,10.87 10.95,13.06C13.14,15.26 16.1,16.22 18.97,15.95M17.33,17.97C14.5,17.81 11.7,16.64 9.53,14.5C7.36,12.31 6.2,9.5 6.04,6.68C3.23,9.82 3.34,14.64 6.35,17.66C9.37,20.67 14.19,20.78 17.33,17.97Z" /></svg>',
|
|
15
|
+
"evol-blue":
|
|
16
|
+
'<svg viewBox="0 0 20 20" width="18" height="18" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" aria-hidden="true"><path d="M10 10 10.18 10.07 10.28 10.25 10.26 10.5 10.09 10.74 9.78 10.91 9.37 10.93 8.93 10.75 8.55 10.37 8.32 9.81 8.33 9.15 8.61 8.47 9.17 7.91 9.96 7.56 10.89 7.53 11.83 7.86 12.63 8.55 13.16 9.55 13.29 10.74 12.98 11.96 12.2 13.03 11.04 13.8 9.61 14.11 8.1 13.87 6.72 13.08 5.67 11.79 5.13 10.15 5.21 8.36 5.95 6.65 7.31 5.28 9.12 4.44 11.18 4.31 13.21 4.93 14.95 6.28 16.12 8.22 16.54 10.51 16.11 12.87 14.83 14.98 12.83 16.54 10.34 17.3 7.68 17.13" /></svg>',
|
|
17
|
+
};
|
|
18
|
+
// const densities = ["huge", "big", "medium", "small", "tiny"];
|
|
19
|
+
const densities = ["big", "medium", "small", "tiny"];
|
|
20
|
+
|
|
21
|
+
const setupPage = (id) => {
|
|
22
|
+
// if (id === "home") {
|
|
23
|
+
// //
|
|
24
|
+
// alert("home");
|
|
25
|
+
// }
|
|
26
|
+
let elem;
|
|
27
|
+
if (typeof routes !== "undefined" && routes?.length) {
|
|
28
|
+
elem = document.getElementById("omg-nav");
|
|
29
|
+
if (elem) {
|
|
30
|
+
const h = routes.map((r) => `<a href="${r.url}">${r.name}</a>`).join("");
|
|
31
|
+
elem.innerHTML = h;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
const theme = localStorage.getItem("omg-theme") || "dark";
|
|
35
|
+
const density = localStorage.getItem("omg-density") || "medium";
|
|
36
|
+
setTheme(theme);
|
|
37
|
+
renderThemePicker(theme);
|
|
38
|
+
setDensity(density);
|
|
39
|
+
renderDensityPicker(density);
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
const renderThemePicker = (id) => {
|
|
44
|
+
const elem = document.getElementById("omg-theme-picker");
|
|
45
|
+
if (elem) {
|
|
46
|
+
elem.innerHTML = themes
|
|
47
|
+
.map(
|
|
48
|
+
(t) => `
|
|
49
|
+
<a class="${id === t ? "selected" : ""}" href="javascript:setTheme('${t}')" alt="${t}" title="${t}">${themeIcons[t]}</a>`
|
|
50
|
+
)
|
|
51
|
+
.join("");
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const renderDensityPicker = (id) => {
|
|
56
|
+
const elem = document.getElementById("omg-density-picker");
|
|
57
|
+
if (elem) {
|
|
58
|
+
elem.innerHTML =
|
|
59
|
+
"Density: " +
|
|
60
|
+
densities
|
|
61
|
+
.map(
|
|
62
|
+
(d) => `<a href="javascript:setDensity('${d}')" alt="${d}">${d}</a>`
|
|
63
|
+
)
|
|
64
|
+
.join("");
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const setTheme = (id) => {
|
|
69
|
+
if (!id) {
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
let elem = document.getElementById("omg-theme-css");
|
|
73
|
+
if (elem) {
|
|
74
|
+
elem.setAttribute("href", `css/themes/${id}/${id}.css`);
|
|
75
|
+
localStorage.setItem("omg-theme", id);
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
const setDensity = (id) => {
|
|
80
|
+
if (!id) {
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
let elem = document.getElementById("omg-body");
|
|
84
|
+
if (elem) {
|
|
85
|
+
elem.className = id;
|
|
86
|
+
localStorage.setItem("omg-density", id);
|
|
87
|
+
}
|
|
88
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/* The nine interlocking triangles of the Sri Yantra, in a 300 x 300 space
|
|
2
|
+
with the center at (150, 150) and the circle of radius 100 that passes
|
|
3
|
+
through every outer vertex. Each entry is [left x, base y, apex y, right x,
|
|
4
|
+
points up]. The base runs from (left, base y) to (right, base y) and the
|
|
5
|
+
apex is at (150, apex y). y grows downward, as on screen.
|
|
6
|
+
|
|
7
|
+
The numbers are from the Sri Yantra example on TeXample.net (CC BY-SA
|
|
8
|
+
4.0). The two largest triangles have all their vertices on the circle, and
|
|
9
|
+
every triangle is symmetric left to right. */
|
|
10
|
+
const YANTRA_TRIANGLES = [
|
|
11
|
+
{ id: "D1", data: [53.65669559977147, 123.20508075688774, 250, 246.34330440022853, 0] },
|
|
12
|
+
{ id: "U1", data: [52.984011026495736, 174.24660560764943, 50, 247.01598897350425, 1] },
|
|
13
|
+
{ id: "U3", data: [98.71823312733801, 220.03828947357886, 123.20508075688774, 201.281766872662, 1] },
|
|
14
|
+
{ id: "U2", data: [78.26467997914015, 197.92315674002487, 78.10499177949904, 221.73532002085986, 1] },
|
|
15
|
+
{ id: "D3", data: [90.4856922951427, 78.10499177949904, 160.66014976539617, 209.51430770485734, 0] },
|
|
16
|
+
{ id: "D2", data: [80.98384838952128, 103.12199145016105, 220.03828947357886, 219.0161516104787, 0] },
|
|
17
|
+
{ id: "U4", data: [114.9488500600036, 160.66014976539617, 103.12199145016105, 185.0511499399964, 1] },
|
|
18
|
+
{ id: "D4", data: [116.35142605010424, 134.30757626706648, 197.92315674002487, 183.64857394989576, 0] },
|
|
19
|
+
{ id: "D5", data: [124.61190803072795, 144.79777263138968, 174.24660560764943, 175.38809196927207, 0] },
|
|
20
|
+
];
|
package/js/yantra.js
ADDED
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
/* Sri Yantra generator: draws the nine interlocking triangles with their
|
|
2
|
+
lotus petals, circles and gated square as SVG, with color options and
|
|
3
|
+
PNG/SVG export. Plain JS, no dependencies. Triangle data: yantra-data.js. */
|
|
4
|
+
|
|
5
|
+
const $ = (id) => document.getElementById(id);
|
|
6
|
+
|
|
7
|
+
const PALETTES = [
|
|
8
|
+
{ id: "classic", label: "Gold", bg: "#1a212d", line: "#f6e3bf", shiva: "#f0a830", shakti: "#e0533d", lotus8: "#c47a3a", lotus16: "#e0a458", gate: "#b8893c", bindu: "#ff5a3c" },
|
|
9
|
+
{ id: "temple", label: "Temple", bg: "#fbf1dc", line: "#4a1f0c", shiva: "#e59a1a", shakti: "#b3261e", lotus8: "#e8892f", lotus16: "#f3c45a", gate: "#8c3b1e", bindu: "#b3261e" },
|
|
10
|
+
{ id: "night", label: "Night", bg: "#0f1220", line: "#d6dcff", shiva: "#6f8dff", shakti: "#c076ff", lotus8: "#4d68d8", lotus16: "#8b6ee8", gate: "#5b6fbf", bindu: "#ffd76a" },
|
|
11
|
+
{ id: "ink", label: "Ink", bg: "#ffffff", line: "#141414", shiva: "#9a9a9a", shakti: "#4a4a4a", lotus8: "#7a7a7a", lotus16: "#b5b5b5", gate: "#666666", bindu: "#141414" },
|
|
12
|
+
{ id: "saffron", label: "Saffron", bg: "#2b0f0f", line: "#f7dfb0", shiva: "#f0973a", shakti: "#c81d3f", lotus8: "#d9822b", lotus16: "#f2b545", gate: "#8c2f1f", bindu: "#ffce54" },
|
|
13
|
+
{ id: "emerald", label: "Emerald", bg: "#0b1f18", line: "#dff5e6", shiva: "#38b28a", shakti: "#1f7a4c", lotus8: "#3f9d6b", lotus16: "#7fd2a0", gate: "#2f6b52", bindu: "#ffe27a" },
|
|
14
|
+
{ id: "slate", label: "Slate", bg: "#14171c", line: "#eef0f3", shiva: "#9aa4b2", shakti: "#5f6b7a", lotus8: "#7c8794", lotus16: "#b4bcc6", gate: "#3f4750", bindu: "#ffffff" },
|
|
15
|
+
{ id: "parchment", label: "Parchment", bg: "#f2e6c9", line: "#4a3419", shiva: "#9a6a30", shakti: "#7a4420", lotus8: "#b5872f", lotus16: "#d4b26a", gate: "#5c3d1e", bindu: "#8a1f1f" },
|
|
16
|
+
{ id: "rose", label: "Rose", bg: "#22131f", line: "#ffe6ef", shiva: "#e8829e", shakti: "#b84f80", lotus8: "#d97398", lotus16: "#f0aac2", gate: "#8a3f66", bindu: "#fff2df" },
|
|
17
|
+
];
|
|
18
|
+
const FILLS = [
|
|
19
|
+
{ id: "filled", label: "Filled" },
|
|
20
|
+
{ id: "lines", label: "Outline" },
|
|
21
|
+
];
|
|
22
|
+
const WHICH = [
|
|
23
|
+
{ id: "both", label: "Both" },
|
|
24
|
+
{ id: "up", label: "Shiva (upward)" },
|
|
25
|
+
{ id: "down", label: "Shakti (downward)" },
|
|
26
|
+
];
|
|
27
|
+
const ENCLOSURES = [
|
|
28
|
+
["Trailokya Mohana", "the square with four gates: enchanter of the three worlds"],
|
|
29
|
+
["Sarva Asha Paripuraka", "the sixteen petals: fulfiller of all hopes"],
|
|
30
|
+
["Sarva Sankshobhana", "the eight petals: agitator of all"],
|
|
31
|
+
["Sarva Saubhagyadayaka", "the fourteen triangles: giver of all good fortune"],
|
|
32
|
+
["Sarvartha Sadhaka", "the outer ten triangles: accomplisher of all aims"],
|
|
33
|
+
["Sarva Rakshakara", "the inner ten triangles: protector of all"],
|
|
34
|
+
["Sarva Rogahara", "the eight triangles: remover of all disease"],
|
|
35
|
+
["Sarva Siddhiprada", "the central triangle: bestower of all powers"],
|
|
36
|
+
["Sarva Anandamaya", "the bindu: full of all bliss"],
|
|
37
|
+
];
|
|
38
|
+
|
|
39
|
+
const DEFAULTS = {
|
|
40
|
+
n: 9,
|
|
41
|
+
which: "both",
|
|
42
|
+
bindu: true,
|
|
43
|
+
tri: true,
|
|
44
|
+
lotus8: true,
|
|
45
|
+
lotus16: true,
|
|
46
|
+
circles: true,
|
|
47
|
+
gates: true,
|
|
48
|
+
palette: "classic",
|
|
49
|
+
fill: "filled",
|
|
50
|
+
weight: 10,
|
|
51
|
+
};
|
|
52
|
+
let S = { ...DEFAULTS };
|
|
53
|
+
try {
|
|
54
|
+
Object.assign(S, JSON.parse(localStorage.getItem("yantra-settings") || "{}"));
|
|
55
|
+
} catch (e) {}
|
|
56
|
+
const save = () => {
|
|
57
|
+
try {
|
|
58
|
+
localStorage.setItem("yantra-settings", JSON.stringify(S));
|
|
59
|
+
} catch (e) {}
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
// Triangles as drawn, in a space centered on (0, 0)
|
|
63
|
+
const TRIS = YANTRA_TRIANGLES.map(({ id, data: [l, b, a, r, up] }) => ({
|
|
64
|
+
id,
|
|
65
|
+
up: !!up,
|
|
66
|
+
width: r - l,
|
|
67
|
+
pts: [
|
|
68
|
+
[l - 150, b - 150],
|
|
69
|
+
[r - 150, b - 150],
|
|
70
|
+
[0, a - 150],
|
|
71
|
+
],
|
|
72
|
+
}));
|
|
73
|
+
const BY_SIZE = [...TRIS].sort((p, q) => p.width - q.width);
|
|
74
|
+
|
|
75
|
+
const pol = (deg, r) => {
|
|
76
|
+
const a = (deg * Math.PI) / 180;
|
|
77
|
+
return [r * Math.cos(a), -r * Math.sin(a)]; // y-up angles, y-down screen
|
|
78
|
+
};
|
|
79
|
+
const P = (p) => p[0].toFixed(2) + " " + p[1].toFixed(2);
|
|
80
|
+
|
|
81
|
+
// One lotus ring. petals: 8 or 16; r0 is the inner radius, r1 the petal tip.
|
|
82
|
+
const lotusPath = (count, r0, r1, controls) => {
|
|
83
|
+
const step = 360 / count;
|
|
84
|
+
let d = "";
|
|
85
|
+
for (let i = 0; i < count; i++) {
|
|
86
|
+
const start = step / 2 + step * i;
|
|
87
|
+
const [[a1, k1], [a2, k2]] = controls;
|
|
88
|
+
const at = (delta, r) => pol(start - delta, r);
|
|
89
|
+
d +=
|
|
90
|
+
`M${P(at(0, r0))}` +
|
|
91
|
+
`C${P(at(a1, k1))} ${P(at(a2, k2))} ${P(at(step / 2, r1))}` +
|
|
92
|
+
`C${P(at(step - a2, k2))} ${P(at(step - a1, k1))} ${P(at(step, r0))}` +
|
|
93
|
+
`A${r0} ${r0} 0 0 0 ${P(at(0, r0))}Z`;
|
|
94
|
+
}
|
|
95
|
+
return d;
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
// One outline of the gated square: half side h, gate half width g, gate depth d.
|
|
99
|
+
const gatePolygon = (h, g, d) => {
|
|
100
|
+
const pts = [];
|
|
101
|
+
for (let k = 0; k < 4; k++) {
|
|
102
|
+
// corner, gate in, gate out, ... for the top side, then rotate by 90 degrees each time
|
|
103
|
+
const side = [
|
|
104
|
+
[-h, -h], [-g, -h], [-g, -h - d], [g, -h - d], [g, -h],
|
|
105
|
+
];
|
|
106
|
+
side.forEach(([x, y]) => {
|
|
107
|
+
for (let i = 0; i < k; i++) [x, y] = [-y, x];
|
|
108
|
+
pts.push([x, y]);
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
return "M" + pts.map(P).join("L") + "Z";
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
const paletteOf = () => PALETTES.find((p) => p.id === S.palette) || PALETTES[0];
|
|
115
|
+
|
|
116
|
+
const triCount = () => Math.max(0, Math.min(9, S.n));
|
|
117
|
+
const shownTriangles = () => {
|
|
118
|
+
const list = BY_SIZE.slice(0, triCount());
|
|
119
|
+
if (S.which === "up") return list.filter((t) => t.up);
|
|
120
|
+
if (S.which === "down") return list.filter((t) => !t.up);
|
|
121
|
+
return list;
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
const yantraSVG = () => {
|
|
125
|
+
const c = paletteOf();
|
|
126
|
+
const w = S.weight / 10;
|
|
127
|
+
const filled = S.fill === "filled";
|
|
128
|
+
const V = 215;
|
|
129
|
+
const stroke = (sw = 1, op = 1) => `stroke="${c.line}" stroke-width="${(w * sw).toFixed(2)}" stroke-opacity="${op}" stroke-linejoin="round"`;
|
|
130
|
+
let out = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="${-V} ${-V} ${2 * V} ${2 * V}" class="plain" role="img" aria-label="Sri Yantra">`;
|
|
131
|
+
out += `<rect x="${-V}" y="${-V}" width="${2 * V}" height="${2 * V}" fill="${c.bg}"/>`;
|
|
132
|
+
|
|
133
|
+
if (S.gates) {
|
|
134
|
+
// three nested outlines; the band between the outer two is filled
|
|
135
|
+
const h = 168, g = 28, d = 26, gap = 7;
|
|
136
|
+
if (filled) {
|
|
137
|
+
out += `<path d="${gatePolygon(h + 2 * gap, g + 2 * gap, d)} ${gatePolygon(h, g, d)}" fill-rule="evenodd" fill="${c.gate}" fill-opacity="0.55"/>`;
|
|
138
|
+
}
|
|
139
|
+
for (let i = 0; i < 3; i++) {
|
|
140
|
+
out += `<path d="${gatePolygon(h + i * gap, g + i * gap, d)}" fill="none" ${stroke(i === 1 ? 0.6 : 1)}/>`;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
if (S.circles) {
|
|
144
|
+
[140, 147, 154].forEach((r, i) => {
|
|
145
|
+
out += `<circle r="${r}" fill="none" ${stroke(i === 0 ? 1 : 0.6)}/>`;
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
if (S.lotus16) {
|
|
149
|
+
out += `<path d="${lotusPath(16, 120, 140, [[2.5, 134], [8.75, 130]])}" fill="${filled ? c.lotus16 : "none"}" fill-opacity="0.6" ${stroke()}/>`;
|
|
150
|
+
}
|
|
151
|
+
if (S.lotus8) {
|
|
152
|
+
out += `<path d="${lotusPath(8, 100, 120, [[5, 114], [17.5, 110]])}" fill="${filled ? c.lotus8 : "none"}" fill-opacity="0.65" ${stroke()}/>`;
|
|
153
|
+
}
|
|
154
|
+
if (S.lotus8 || S.lotus16 || S.tri) {
|
|
155
|
+
out += `<circle r="100" fill="none" ${stroke()}/>`;
|
|
156
|
+
}
|
|
157
|
+
if (S.tri) {
|
|
158
|
+
// largest first so the small ones read on top
|
|
159
|
+
[...shownTriangles()].reverse().forEach((t) => {
|
|
160
|
+
const [a, b, top] = t.pts;
|
|
161
|
+
const col = t.up ? c.shiva : c.shakti;
|
|
162
|
+
out += `<path d="M${P(a)}L${P(b)}L${P(top)}Z" fill="${filled ? col : "none"}" fill-opacity="0.5" ${stroke(0.9)}/>`;
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
if (S.bindu) {
|
|
166
|
+
out += `<circle r="4.5" fill="${c.bindu}" ${stroke(0.5)}/>`;
|
|
167
|
+
}
|
|
168
|
+
return out + "</svg>";
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
const render = () => {
|
|
172
|
+
$("yantra").innerHTML = yantraSVG();
|
|
173
|
+
$("tri-n-val").textContent = triCount() + " of 9";
|
|
174
|
+
const shown = S.tri ? shownTriangles() : [];
|
|
175
|
+
const up = shown.filter((t) => t.up).length;
|
|
176
|
+
$("yantra-note").textContent = S.tri
|
|
177
|
+
? `${shown.length} triangle${shown.length === 1 ? "" : "s"} drawn: ${up} upward, ${shown.length - up} downward.`
|
|
178
|
+
: "Triangles hidden.";
|
|
179
|
+
document.querySelectorAll("#which-chips button").forEach((b, i) => {
|
|
180
|
+
const on = WHICH[i].id === S.which;
|
|
181
|
+
b.className = on ? "selected" : "";
|
|
182
|
+
b.setAttribute("aria-pressed", on);
|
|
183
|
+
});
|
|
184
|
+
document.querySelectorAll("#pal-chips button").forEach((b, i) => {
|
|
185
|
+
const on = PALETTES[i].id === S.palette;
|
|
186
|
+
b.className = on ? "selected" : "";
|
|
187
|
+
b.setAttribute("aria-pressed", on);
|
|
188
|
+
});
|
|
189
|
+
document.querySelectorAll("#fill-chips button").forEach((b, i) => {
|
|
190
|
+
const on = FILLS[i].id === S.fill;
|
|
191
|
+
b.className = on ? "selected" : "";
|
|
192
|
+
b.setAttribute("aria-pressed", on);
|
|
193
|
+
});
|
|
194
|
+
$("weight-val").textContent = (S.weight / 10).toFixed(1);
|
|
195
|
+
$("tri-n").value = S.n;
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
// ---------------------------------------------------------------- play
|
|
199
|
+
|
|
200
|
+
let playTimer = null;
|
|
201
|
+
const stopPlay = () => {
|
|
202
|
+
clearInterval(playTimer);
|
|
203
|
+
playTimer = null;
|
|
204
|
+
$("play").textContent = "Build it up";
|
|
205
|
+
};
|
|
206
|
+
const play = () => {
|
|
207
|
+
if (playTimer) return stopPlay();
|
|
208
|
+
S.n = 0;
|
|
209
|
+
S.tri = true;
|
|
210
|
+
$("l-tri").checked = true;
|
|
211
|
+
render();
|
|
212
|
+
$("play").textContent = "Stop";
|
|
213
|
+
playTimer = setInterval(() => {
|
|
214
|
+
S.n += 1;
|
|
215
|
+
render();
|
|
216
|
+
if (S.n >= 9) {
|
|
217
|
+
stopPlay();
|
|
218
|
+
save();
|
|
219
|
+
}
|
|
220
|
+
}, 700);
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
// ---------------------------------------------------------------- export
|
|
224
|
+
|
|
225
|
+
const download = (blob, name) => {
|
|
226
|
+
const a = document.createElement("a");
|
|
227
|
+
a.href = URL.createObjectURL(blob);
|
|
228
|
+
a.download = name;
|
|
229
|
+
document.body.appendChild(a);
|
|
230
|
+
a.click();
|
|
231
|
+
a.remove();
|
|
232
|
+
setTimeout(() => URL.revokeObjectURL(a.href), 2000);
|
|
233
|
+
};
|
|
234
|
+
const exportSVG = () => {
|
|
235
|
+
const svg = yantraSVG().replace(' class="plain"', "");
|
|
236
|
+
download(new Blob([svg], { type: "image/svg+xml" }), "sri-yantra.svg");
|
|
237
|
+
};
|
|
238
|
+
const exportPNG = () => {
|
|
239
|
+
const px = 2000;
|
|
240
|
+
const svg = yantraSVG()
|
|
241
|
+
.replace(' class="plain"', "")
|
|
242
|
+
.replace("<svg ", `<svg width="${px}" height="${px}" `);
|
|
243
|
+
const img = new Image();
|
|
244
|
+
img.onload = () => {
|
|
245
|
+
const c = document.createElement("canvas");
|
|
246
|
+
c.width = c.height = px;
|
|
247
|
+
c.getContext("2d").drawImage(img, 0, 0, px, px);
|
|
248
|
+
c.toBlob((b) => download(b, "sri-yantra.png"));
|
|
249
|
+
};
|
|
250
|
+
img.src = "data:image/svg+xml;charset=utf-8," + encodeURIComponent(svg);
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
// ---------------------------------------------------------------- UI
|
|
254
|
+
|
|
255
|
+
const chips = (id, items, pick) => {
|
|
256
|
+
const box = $(id);
|
|
257
|
+
box.innerHTML = "";
|
|
258
|
+
items.forEach((it) => {
|
|
259
|
+
const b = document.createElement("button");
|
|
260
|
+
b.type = "button";
|
|
261
|
+
b.textContent = it.label;
|
|
262
|
+
b.onclick = () => pick(it);
|
|
263
|
+
box.appendChild(b);
|
|
264
|
+
});
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
const initYantra = () => {
|
|
268
|
+
S.n = Math.max(0, Math.min(9, Math.round(S.n)));
|
|
269
|
+
if (!PALETTES.some((p) => p.id === S.palette)) S.palette = "classic";
|
|
270
|
+
|
|
271
|
+
$("tri-n").oninput = () => {
|
|
272
|
+
stopPlay();
|
|
273
|
+
S.n = +$("tri-n").value;
|
|
274
|
+
save();
|
|
275
|
+
render();
|
|
276
|
+
};
|
|
277
|
+
chips("which-chips", WHICH, (w) => { S.which = w.id; save(); render(); });
|
|
278
|
+
chips("pal-chips", PALETTES, (p) => { S.palette = p.id; save(); render(); });
|
|
279
|
+
chips("fill-chips", FILLS, (f) => { S.fill = f.id; save(); render(); });
|
|
280
|
+
|
|
281
|
+
["bindu", "tri", "lotus8", "lotus16", "circles", "gates"].forEach((k) => {
|
|
282
|
+
const el = $("l-" + k);
|
|
283
|
+
el.checked = !!S[k];
|
|
284
|
+
el.onchange = () => {
|
|
285
|
+
S[k] = el.checked;
|
|
286
|
+
save();
|
|
287
|
+
render();
|
|
288
|
+
};
|
|
289
|
+
});
|
|
290
|
+
const wt = $("weight");
|
|
291
|
+
wt.value = S.weight;
|
|
292
|
+
wt.oninput = () => {
|
|
293
|
+
S.weight = +wt.value;
|
|
294
|
+
save();
|
|
295
|
+
render();
|
|
296
|
+
};
|
|
297
|
+
$("play").onclick = play;
|
|
298
|
+
$("export-png").onclick = exportPNG;
|
|
299
|
+
$("export-svg").onclick = exportSVG;
|
|
300
|
+
|
|
301
|
+
$("enclosures").innerHTML = ENCLOSURES.map(
|
|
302
|
+
([sa, en]) => `<li><span class="sa">${sa}</span> <span class="en">– ${en}</span></li>`
|
|
303
|
+
).join("");
|
|
304
|
+
render();
|
|
305
|
+
};
|