1const tableLeftOffset = 1028;
  2const tableTopOffset = 128;
  3const baseElementSize = 80;
  4const initElementSize = 10;
  5const elementSpacing = 16;
  6const easeStrength = 4;
  7const mouseInfluenceDistance = 200;
  8const mouseInfluencePower = 0.5;
  9const influenceScale = 1.2;
 10
 11var currentlySelectedElement = -1;
 12
 13const elementPositions = [
 14  [0, 0],
 15  [17, 0],
 16  [0, 1],
 17  [1, 1],
 18  [12, 1],
 19  [13, 1],
 20  [14, 1],
 21  [15, 1],
 22  [16, 1],
 23  [17, 1],
 24  [0, 2],
 25  [1, 2],
 26  [12, 2],
 27  [13, 2],
 28  [14, 2],
 29  [15, 2],
 30  [16, 2],
 31  [17, 2],
 32  [0, 3],
 33  [1, 3],
 34  [2, 3],
 35  [3, 3],
 36  [4, 3],
 37  [5, 3],
 38  [6, 3],
 39  [7, 3],
 40  [8, 3],
 41  [9, 3],
 42  [10, 3],
 43  [11, 3],
 44  [12, 3],
 45  [13, 3],
 46  [14, 3],
 47  [15, 3],
 48  [16, 3],
 49  [17, 3],
 50  [0, 4],
 51  [1, 4],
 52  [2, 4],
 53  [3, 4],
 54  [4, 4],
 55  [5, 4],
 56  [6, 4],
 57  [7, 4],
 58  [8, 4],
 59  [9, 4],
 60  [10, 4],
 61  [11, 4],
 62  [12, 4],
 63  [13, 4],
 64  [14, 4],
 65  [15, 4],
 66  [16, 4],
 67  [17, 4],
 68  [0, 5],
 69  [1, 5],
 70  [3, 8],
 71  [4, 8],
 72  [5, 8],
 73  [6, 8],
 74  [7, 8],
 75  [8, 8],
 76  [9, 8],
 77  [10, 8],
 78  [11, 8],
 79  [12, 8],
 80  [13, 8],
 81  [14, 8],
 82  [15, 8],
 83  [16, 8],
 84  [17, 8],
 85  [3, 5],
 86  [4, 5],
 87  [5, 5],
 88  [6, 5],
 89  [7, 5],
 90  [8, 5],
 91  [9, 5],
 92  [10, 5],
 93  [11, 5],
 94  [12, 5],
 95  [13, 5],
 96  [14, 5],
 97  [15, 5],
 98  [16, 5],
 99  [17, 5],
100  [0, 6],
101  [1, 6],
102  [3, 9],
103  [4, 9],
104  [5, 9],
105  [6, 9],
106  [7, 9],
107  [8, 9],
108  [9, 9],
109  [10, 9],
110  [11, 9],
111  [12, 9],
112  [13, 9],
113  [14, 9],
114  [15, 9],
115  [16, 9],
116  [17, 9],
117  [3, 6],
118  [4, 6],
119  [5, 6],
120  [6, 6],
121  [7, 6],
122  [8, 6],
123  [9, 6],
124  [10, 6],
125  [11, 6],
126  [12, 6],
127  [13, 6],
128  [14, 6],
129  [15, 6],
130  [16, 6],
131  [17, 6],
132];
133var elementSizes = Array(elementPositions.length).fill(initElementSize);
134var elementLeftOffsets = Array(elementPositions.length).fill(0);
135var elementTopOffsets = Array(elementPositions.length).fill(0);
136
137var mouseX = 0;
138var mouseY = 0;
139
140function generateElementPropertyHTMLCode(name, value, unit) {
141  return `<div class="element-property-container">
142    <div class="element-property-name">${name}</div>
143    <div class="element-property-value">${value}${unit}</div>
144</div>`;
145}
146
147function formatUnit(unitString) {
148  if (unitString !== undefined) {
149    if (unitString.kelvin !== undefined) {
150      return "&nbsp;" + unitString.kelvin;
151    }
152    return "&nbsp;" + unitString;
153  }
154  return "";
155}
156
157function formatValue(valueString) {
158  if (valueString !== undefined) {
159    return valueString;
160  }
161  return "NaN";
162}
163
164function formattedCPKColor(hex_color) {
165  colorString = "#" + hex_color;
166  var r = parseInt(colorString.substr(1, 2), 16); // Grab the hex representation of red (chars 1-2) and convert to decimal (base 10).
167  var g = parseInt(colorString.substr(3, 2), 16);
168  var b = parseInt(colorString.substr(5, 2), 16);
169  (r /= 255), (g /= 255), (b /= 255);
170  var max = Math.max(r, g, b),
171    min = Math.min(r, g, b);
172  var h,
173    s,
174    l = (max + min) / 2;
175
176  if (max == min) {
177    h = s = 0; // achromatic
178  } else {
179    var d = max - min;
180    s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
181    switch (max) {
182      case r:
183        h = (g - b) / d + (g < b ? 6 : 0);
184        break;
185      case g:
186        h = (b - r) / d + 2;
187        break;
188      case b:
189        h = (r - g) / d + 4;
190        break;
191    }
192    h /= 6;
193  }
194
195  return `hsl(${h * 360}deg ${s * 100}% 78%)`;
196}
197
198function displayElementProperties(elementIndex) {
199  if (currentlySelectedElement != elementIndex) {
200    currentlySelectedElement = elementIndex;
201    properties = [
202      "abundance.universe",
203      "allotropes",
204      "atomic_mass",
205      "atomic_number",
206      "block",
207      "boiling-point",
208      "classifications.dot_numbers",
209      "conductivity.electric",
210      "conductivity.thermal",
211      "critical_pressure",
212      "critical_temperature",
213      "crystal_structure",
214      "density.liquid",
215      "density.stp",
216      "discovered",
217      "electron_configuration",
218      "electronegativity_pauling",
219      "gas_phase",
220      "group",
221      "hardness.mohs",
222      "heat.fusion",
223      "heat.molar",
224      "heat.vaporization",
225      "isotopes_known",
226      "isotopes_stable",
227      "isotopic_abundances",
228      "melting_point",
229      "molar_volume",
230      "quantum_numbers",
231      "radius.empirical",
232      "refractive_index",
233      "speed_of_sound",
234    ];
235    document.getElementById("element-data").innerHTML = "";
236    document.getElementById("element-data").innerHTML = `
237        <div id="element-name"></div>
238        <div id="element-appearance"></div>
239        <div id="element-properties">
240    </div>`;
241    document.body.style.backgroundColor = formattedCPKColor(
242      pTable[elementIndex].cpk_hex
243    );
244    document.getElementById("element-name").innerHTML =
245      pTable[elementIndex].name;
246    document.getElementById("element-appearance").innerHTML =
247      pTable[elementIndex].appearance;
248    document.getElementById("element-properties").innerHTML = "";
249    for (const [key, value] of Object.entries(properties)) {
250      if (value.includes(".")) {
251        propertySplit = value.split(".");
252        propertyName = propertySplit[0] + `(${propertySplit[1]})`;
253        try {
254          propertyValue =
255            pTable[elementIndex][propertySplit[0]][propertySplit[1]];
256          propertyUnit = formatUnit(
257            pTableUnits[propertySplit[0]][propertySplit[1]]
258          );
259        } catch {
260          propertyValue = undefined;
261          propertyUnit = "";
262        }
263        document.getElementById("element-properties").innerHTML +=
264          generateElementPropertyHTMLCode(
265            propertyName,
266            formatValue(propertyValue),
267            propertyUnit
268          );
269      } else {
270        if (value == "discovered") {
271          document.getElementById("element-properties").innerHTML +=
272            generateElementPropertyHTMLCode(
273              value,
274              "by " +
275                formatValue(pTable[elementIndex][value].by) +
276                " in " +
277                formatValue(pTable[elementIndex][value].year),
278              ""
279            );
280        } else {
281          document.getElementById("element-properties").innerHTML +=
282            generateElementPropertyHTMLCode(
283              value,
284              formatValue(pTable[elementIndex][value]),
285              formatUnit(pTableUnits[value])
286            );
287        }
288      }
289    }
290  }
291}
292
293function drawElement(elementIndex) {
294  elementLeftOffsets[elementIndex] =
295    tableLeftOffset +
296    Math.pow(
297      elementPositions[elementIndex][0] * (baseElementSize + elementSpacing) +
298        elementPositions[elementIndex][1] * 100,
299      1.7
300    ) /
301      100;
302  elementTopOffsets[elementIndex] =
303    tableTopOffset +
304    Math.pow(
305      elementPositions[elementIndex][1] * (baseElementSize + elementSpacing),
306      1.2
307    ) /
308      2;
309  elementSizes[elementIndex] =
310    4 *
311    (elementPositions[elementIndex][0] * 2 + elementPositions[elementIndex][1]);
312  document.body.innerHTML += `
313    <div class="table-element" id="element-index-${elementIndex}" style="left: ${elementLeftOffsets[elementIndex]}px; top: ${elementTopOffsets[elementIndex]}px;">${pTable[elementIndex].symbol}<div class="element-name">${pTable[elementIndex].name}</div><div class="element-z-number">${pTable[elementIndex].atomic_number}</div></div>
314    `;
315}
316var oldTime = new Date();
317function updateElements() {
318  var deltaTime = new Date() - oldTime;
319  for (elementIndex in elementPositions) {
320    refLeftOffset =
321      tableLeftOffset +
322      elementPositions[elementIndex][0] * (baseElementSize + elementSpacing);
323    refTopOffset =
324      tableTopOffset +
325      elementPositions[elementIndex][1] * (baseElementSize + elementSpacing);
326    elementLeftOffsets[elementIndex] =
327      elementLeftOffsets[elementIndex] +
328      (((refLeftOffset - elementLeftOffsets[elementIndex]) / easeStrength) *
329        deltaTime) /
330        100;
331    elementTopOffsets[elementIndex] =
332      elementTopOffsets[elementIndex] +
333      (((refTopOffset - elementTopOffsets[elementIndex]) / easeStrength) *
334        deltaTime) /
335        100;
336    distFromMouse = Math.sqrt(
337      (refLeftOffset + baseElementSize / 2 - mouseX) ** 2 +
338        (refTopOffset + baseElementSize / 2 - mouseY) ** 2
339    );
340    targetSize =
341      baseElementSize +
342      elementSpacing /
343        (influenceScale +
344          (distFromMouse / mouseInfluenceDistance) ** mouseInfluencePower);
345    elementSizes[elementIndex] =
346      elementSizes[elementIndex] +
347      (((targetSize - elementSizes[elementIndex]) / easeStrength) * deltaTime) /
348        100;
349    document.getElementById(`element-index-${elementIndex}`).style.width =
350      elementSizes[elementIndex] + "px";
351    document.getElementById(`element-index-${elementIndex}`).style.height =
352      elementSizes[elementIndex] + "px";
353    document.getElementById(`element-index-${elementIndex}`).style.left =
354      elementLeftOffsets[elementIndex] +
355      (baseElementSize - elementSizes[elementIndex]) / 2 +
356      "px";
357    document.getElementById(`element-index-${elementIndex}`).style.top =
358      elementTopOffsets[elementIndex] +
359      (baseElementSize - elementSizes[elementIndex]) / 2 +
360      "px";
361  }
362  oldTime = new Date();
363  window.requestAnimationFrame(updateElements);
364}
365
366for (elementIndex in elementPositions) {
367  drawElement(elementIndex);
368}
369
370document.body.addEventListener("mousemove", (event) => {
371  mouseX = event.clientX;
372  mouseY = event.clientY;
373  el = document.elementFromPoint(mouseX, mouseY);
374  if (el.parentNode.className == "table-element") {
375    displayElementProperties(parseInt(el.parentNode.id.split("-index-")[1]));
376  }
377  if (el.className == "table-element") {
378    displayElementProperties(parseInt(el.id.split("-index-")[1]));
379  }
380});
381
382displayElementProperties(1);
383
384window.requestAnimationFrame(updateElements);