{"spec_id":"star-chart-constellation","library":"highcharts","language":"javascript","code":"// anyplot.ai\n// star-chart-constellation: Star Chart with Constellations\n// Library: highcharts 12.6.0 | JavaScript 22.22.3\n// Quality: 90/100 | Created: 2026-06-17\n//# anyplot-orientation: square\n\n// A north-polar azimuthal-equidistant sky map. Stars are plotted by Right\n// Ascension / Declination, sized by apparent magnitude (brighter = larger), with\n// constellation stick-figure lines, an RA/Dec coordinate grid and the ecliptic.\n//\n// Imprint palette note: the spec asks for pale/white stars on a night sky, but\n// anyplot's brand contract is stronger — the page surface is cream (#FAF8F1) on\n// light and near-black (#1A1A17) on dark, and the first categorical series is\n// ALWAYS brand green #009E73. Green is the one star colour that stays legible on\n// BOTH surfaces, so the stars carry the brand green (identical across themes);\n// constellation lines, grid, ticks and labels use theme-adaptive chrome tokens.\n\nconst t = window.ANYPLOT_TOKENS;\nconst THEME = window.ANYPLOT_THEME === \"dark\" ? \"dark\" : \"light\";\nconst BRAND = t.palette[0]; // #009E73 — first/only star series\n\n// Soft theme-adaptive tone for the constellation stick-figures (structural).\nconst LINE_COL = THEME === \"dark\" ? \"rgba(184,183,176,0.55)\" : \"rgba(74,74,68,0.50)\";\n\n// --- Projection -------------------------------------------------------------\n// North-polar azimuthal equidistant: radius = co-latitude (90 - dec); the pole\n// sits at the centre and the celestial equator (dec 0) is the outer boundary.\nconst DEG = Math.PI / 180;\nfunction project(raH, dec) {\n  const ang = raH * 15 * DEG; // RA hours -> radians\n  const r = 90 - dec;\n  return [r * Math.sin(ang), r * Math.cos(ang)];\n}\n\n// --- Bright catalog ---------------------------------------------------------\n// [id, RA(hours), Dec(deg), apparent magnitude]. Real positions for 17 northern\n// constellations; lower magnitude = brighter.\nconst STARS = [\n  // Ursa Major\n  [\"Dubhe\", 11.062, 61.75, 1.79], [\"Merak\", 11.030, 56.38, 2.37],\n  [\"Phecda\", 11.897, 53.69, 2.44], [\"Megrez\", 12.257, 57.03, 3.31],\n  [\"Alioth\", 12.900, 55.96, 1.77], [\"Mizar\", 13.399, 54.93, 2.04],\n  [\"Alkaid\", 13.792, 49.31, 1.86],\n  // Ursa Minor\n  [\"Polaris\", 2.530, 89.26, 1.98], [\"Yildun\", 17.537, 86.59, 4.36],\n  [\"EpsUMi\", 16.766, 82.04, 4.21], [\"ZetUMi\", 15.734, 77.79, 4.32],\n  [\"Pherkad\", 15.345, 71.83, 3.05], [\"Kochab\", 14.845, 74.16, 2.08],\n  // Cassiopeia\n  [\"Caph\", 0.153, 59.15, 2.27], [\"Schedar\", 0.675, 56.54, 2.24],\n  [\"Cih\", 0.945, 60.72, 2.47], [\"Ruchbah\", 1.430, 60.24, 2.68],\n  [\"Segin\", 1.907, 63.67, 3.38],\n  // Cepheus\n  [\"Alderamin\", 21.310, 62.59, 2.45], [\"Alfirk\", 21.477, 70.56, 3.23],\n  [\"Errai\", 23.656, 77.63, 3.21], [\"ZetCep\", 22.181, 58.20, 3.35],\n  [\"IotCep\", 22.828, 66.20, 3.52], [\"DelCep\", 22.477, 58.42, 4.07],\n  // Cygnus\n  [\"Deneb\", 20.690, 45.28, 1.25], [\"Sadr\", 20.370, 40.26, 2.23],\n  [\"Gienah\", 20.770, 33.97, 2.48], [\"Fawaris\", 19.750, 45.13, 2.87],\n  [\"Albireo\", 19.512, 27.96, 3.18],\n  // Lyra\n  [\"Vega\", 18.615, 38.78, 0.03], [\"Sheliak\", 18.835, 33.36, 3.52],\n  [\"Sulafat\", 18.982, 32.69, 3.25], [\"DelLyr\", 18.908, 36.90, 4.30],\n  [\"ZetLyr\", 18.746, 37.60, 4.36],\n  // Draco\n  [\"Eltanin\", 17.943, 51.49, 2.23], [\"Rastaban\", 17.507, 52.30, 2.79],\n  [\"Grumium\", 17.892, 56.87, 3.75], [\"NuDra\", 17.538, 55.18, 4.88],\n  [\"Altais\", 19.209, 67.66, 3.07], [\"ZetDra\", 17.146, 65.71, 3.17],\n  [\"Edasich\", 15.415, 58.97, 3.29], [\"Thuban\", 14.073, 64.38, 3.65],\n  [\"KapDra\", 12.558, 69.79, 3.82], [\"LamDra\", 11.523, 69.33, 3.84],\n  // Bootes\n  [\"Arcturus\", 14.261, 19.18, -0.05], [\"Nekkar\", 15.032, 40.39, 3.49],\n  [\"Seginus\", 14.535, 38.31, 3.03], [\"Izar\", 14.749, 27.07, 2.37],\n  [\"DelBoo\", 15.258, 33.31, 3.46], [\"Muphrid\", 13.911, 18.40, 2.68],\n  // Corona Borealis\n  [\"Alphecca\", 15.578, 26.71, 2.22], [\"BetCrB\", 15.464, 29.10, 3.66],\n  [\"GamCrB\", 15.713, 26.30, 3.81], [\"TheCrB\", 15.553, 31.36, 4.14],\n  [\"DelCrB\", 15.827, 26.07, 4.59], [\"EpsCrB\", 15.958, 26.88, 4.13],\n  // Hercules\n  [\"ZetHer\", 16.688, 31.60, 2.81], [\"EtaHer\", 16.715, 38.92, 3.48],\n  [\"PiHer\", 17.251, 36.81, 3.16], [\"EpsHer\", 17.005, 30.93, 3.92],\n  [\"Korneph\", 16.504, 21.49, 2.78], [\"DelHer\", 17.250, 24.84, 3.12],\n  // Pegasus + Andromeda\n  [\"Alpheratz\", 0.140, 29.09, 2.06], [\"Scheat\", 23.063, 28.08, 2.42],\n  [\"Markab\", 23.079, 15.21, 2.48], [\"Algenib\", 0.221, 15.18, 2.83],\n  [\"Mirach\", 1.162, 35.62, 2.05], [\"Almach\", 2.065, 42.33, 2.10],\n  [\"DelAnd\", 0.655, 30.86, 3.27],\n  // Perseus\n  [\"Mirfak\", 3.405, 49.86, 1.79], [\"Algol\", 3.136, 40.96, 2.12],\n  [\"GamPer\", 3.080, 53.51, 2.91], [\"DelPer\", 3.715, 47.79, 3.01],\n  [\"EpsPer\", 3.964, 40.01, 2.90], [\"ZetPer\", 3.902, 31.88, 2.85],\n  // Auriga\n  [\"Capella\", 5.278, 45.998, 0.08], [\"Menkalinan\", 5.992, 44.95, 1.90],\n  [\"Mahasim\", 5.995, 37.21, 2.62], [\"Hassaleh\", 4.950, 33.17, 2.69],\n  [\"Almaaz\", 5.033, 43.82, 2.99],\n  // Gemini\n  [\"Castor\", 7.577, 31.89, 1.58], [\"Pollux\", 7.755, 28.03, 1.16],\n  [\"Alhena\", 6.629, 16.40, 1.92], [\"Wasat\", 7.335, 21.98, 3.53],\n  [\"Mebsuta\", 6.732, 25.13, 3.06], [\"Tejat\", 6.383, 22.51, 2.87],\n  // Leo\n  [\"Regulus\", 10.139, 11.97, 1.40], [\"Denebola\", 11.818, 14.57, 2.14],\n  [\"Algieba\", 10.333, 19.84, 2.08], [\"Zosma\", 11.235, 20.52, 2.56],\n  [\"EpsLeo\", 9.764, 23.77, 2.97], [\"EtaLeo\", 10.122, 16.76, 3.49],\n  [\"TheLeo\", 11.237, 15.43, 3.33],\n];\n\nconst byId = {};\nfor (const s of STARS) byId[s[0]] = s;\n\n// --- Constellation stick-figures (pairs of star ids) ------------------------\nconst EDGES = [\n  // Ursa Major\n  [\"Dubhe\", \"Merak\"], [\"Merak\", \"Phecda\"], [\"Phecda\", \"Megrez\"],\n  [\"Megrez\", \"Dubhe\"], [\"Megrez\", \"Alioth\"], [\"Alioth\", \"Mizar\"],\n  [\"Mizar\", \"Alkaid\"],\n  // Ursa Minor\n  [\"Polaris\", \"Yildun\"], [\"Yildun\", \"EpsUMi\"], [\"EpsUMi\", \"ZetUMi\"],\n  [\"ZetUMi\", \"Pherkad\"], [\"Pherkad\", \"Kochab\"], [\"Kochab\", \"ZetUMi\"],\n  // Cassiopeia\n  [\"Caph\", \"Schedar\"], [\"Schedar\", \"Cih\"], [\"Cih\", \"Ruchbah\"],\n  [\"Ruchbah\", \"Segin\"],\n  // Cepheus\n  [\"Alderamin\", \"Alfirk\"], [\"Alfirk\", \"IotCep\"], [\"IotCep\", \"Errai\"],\n  [\"Alderamin\", \"DelCep\"], [\"DelCep\", \"ZetCep\"], [\"ZetCep\", \"Alfirk\"],\n  // Cygnus\n  [\"Deneb\", \"Sadr\"], [\"Sadr\", \"Albireo\"], [\"Fawaris\", \"Sadr\"],\n  [\"Sadr\", \"Gienah\"],\n  // Lyra\n  [\"Vega\", \"ZetLyr\"], [\"ZetLyr\", \"DelLyr\"], [\"DelLyr\", \"Sulafat\"],\n  [\"Sulafat\", \"Sheliak\"], [\"Sheliak\", \"ZetLyr\"],\n  // Draco\n  [\"Eltanin\", \"Rastaban\"], [\"Rastaban\", \"NuDra\"], [\"NuDra\", \"Grumium\"],\n  [\"Grumium\", \"Eltanin\"], [\"Grumium\", \"Altais\"], [\"Altais\", \"ZetDra\"],\n  [\"ZetDra\", \"Edasich\"], [\"Edasich\", \"Thuban\"], [\"Thuban\", \"KapDra\"],\n  [\"KapDra\", \"LamDra\"],\n  // Bootes\n  [\"Arcturus\", \"Muphrid\"], [\"Arcturus\", \"Izar\"], [\"Izar\", \"DelBoo\"],\n  [\"DelBoo\", \"Nekkar\"], [\"Nekkar\", \"Seginus\"], [\"Seginus\", \"Arcturus\"],\n  // Corona Borealis\n  [\"TheCrB\", \"BetCrB\"], [\"BetCrB\", \"Alphecca\"], [\"Alphecca\", \"GamCrB\"],\n  [\"GamCrB\", \"DelCrB\"], [\"DelCrB\", \"EpsCrB\"],\n  // Hercules\n  [\"ZetHer\", \"EtaHer\"], [\"EtaHer\", \"PiHer\"], [\"PiHer\", \"EpsHer\"],\n  [\"EpsHer\", \"ZetHer\"], [\"ZetHer\", \"Korneph\"], [\"EpsHer\", \"DelHer\"],\n  // Pegasus + Andromeda\n  [\"Alpheratz\", \"Scheat\"], [\"Scheat\", \"Markab\"], [\"Markab\", \"Algenib\"],\n  [\"Algenib\", \"Alpheratz\"], [\"Alpheratz\", \"DelAnd\"], [\"DelAnd\", \"Mirach\"],\n  [\"Mirach\", \"Almach\"],\n  // Perseus\n  [\"GamPer\", \"Mirfak\"], [\"Mirfak\", \"DelPer\"], [\"DelPer\", \"EpsPer\"],\n  [\"EpsPer\", \"ZetPer\"], [\"Mirfak\", \"Algol\"],\n  // Auriga\n  [\"Capella\", \"Almaaz\"], [\"Almaaz\", \"Hassaleh\"], [\"Hassaleh\", \"Mahasim\"],\n  [\"Mahasim\", \"Menkalinan\"], [\"Menkalinan\", \"Capella\"],\n  // Gemini\n  [\"Castor\", \"Pollux\"], [\"Castor\", \"Mebsuta\"], [\"Mebsuta\", \"Tejat\"],\n  [\"Pollux\", \"Wasat\"], [\"Wasat\", \"Alhena\"],\n  // Leo\n  [\"EpsLeo\", \"Algieba\"], [\"Algieba\", \"EtaLeo\"], [\"EtaLeo\", \"Regulus\"],\n  [\"Regulus\", \"TheLeo\"], [\"TheLeo\", \"Denebola\"], [\"Denebola\", \"Zosma\"],\n  [\"Zosma\", \"Algieba\"],\n];\n\n// --- Magnitude -> marker radius (CSS px in the 1200px mount space) -----------\n// Brighter (lower magnitude) -> larger point.\nfunction radius(mag) {\n  const v = Math.max(0, Math.min(1, (6.5 - mag) / 6.6));\n  return 1.0 + 5.2 * Math.pow(v, 1.5);\n}\n\n// --- Bright stars + constellation lines -------------------------------------\nconst brightData = STARS.map(([id, ra, dec, mag]) => {\n  const [x, y] = project(ra, dec);\n  return { x, y, name: id, mag, marker: { radius: radius(mag) } };\n});\n\nconst constLines = [];\nfor (const [a, b] of EDGES) {\n  const sa = byId[a], sb = byId[b];\n  const pa = project(sa[1], sa[2]);\n  const pb = project(sb[1], sb[2]);\n  constLines.push(pa, pb, [null, null]);\n}\n\n// --- Deterministic faint background field (no RNG in the browser; tiny LCG) --\nlet seed = 20260617;\nfunction rnd() {\n  seed = (seed * 1103515245 + 12345) & 0x7fffffff;\n  return seed / 0x7fffffff;\n}\nconst fieldData = [];\nfor (let i = 0; i < 165; i++) {\n  const r = 90 * Math.sqrt(rnd()); // uniform spatial density across the disc\n  if (r > 88) continue; // keep clear of the boundary ring\n  const ang = rnd() * 2 * Math.PI;\n  const mag = 4.3 + rnd() * 2.1; // faint stars only\n  fieldData.push({ x: r * Math.sin(ang), y: r * Math.cos(ang), marker: { radius: radius(mag) } });\n}\n\n// --- Coordinate grid: Dec parallels + RA meridians --------------------------\nconst gridData = [];\nfor (const dec of [30, 60]) {\n  const r = 90 - dec;\n  for (let a = 0; a <= 360; a += 4) {\n    gridData.push([r * Math.sin(a * DEG), r * Math.cos(a * DEG)]);\n  }\n  gridData.push([null, null]);\n}\nfor (let h = 0; h < 24; h += 2) {\n  const ang = h * 15 * DEG;\n  gridData.push([0, 0], [90 * Math.sin(ang), 90 * Math.cos(ang)], [null, null]);\n}\n\n// Boundary ring (celestial equator, dec 0) drawn a touch stronger.\nconst boundary = [];\nfor (let a = 0; a <= 360; a += 3) boundary.push([90 * Math.sin(a * DEG), 90 * Math.cos(a * DEG)]);\n\n// --- Ecliptic (Sun's path), dashed amber ------------------------------------\nconst OBL = 23.4392811 * DEG;\nconst ecliptic = [];\nfor (let lon = 0; lon <= 360; lon += 2) {\n  const lam = lon * DEG;\n  const dec = Math.asin(Math.sin(OBL) * Math.sin(lam)) / DEG;\n  if (dec < 0) {\n    ecliptic.push([null, null]);\n    continue;\n  }\n  const ang = Math.atan2(Math.cos(OBL) * Math.sin(lam), Math.cos(lam));\n  const r = 90 - dec;\n  ecliptic.push([r * Math.sin(ang), r * Math.cos(ang)]);\n}\n\n// --- Labels (invisible scatter carrying dataLabels) -------------------------\nconst conLabels = [\n  [\"UMa\", 12.4, 56.5], [\"UMi\", 16.6, 80.0], [\"Cas\", 0.95, 60.5],\n  [\"Cep\", 22.3, 66.5], [\"Cyg\", 20.2, 38.5], [\"Lyr\", 18.55, 35.0],\n  [\"Dra\", 16.6, 60.5], [\"Boo\", 14.65, 30.5], [\"CrB\", 15.65, 23.5],\n  [\"Her\", 16.95, 33.5], [\"Peg\", 23.55, 21.5], [\"And\", 1.45, 38.5],\n  [\"Per\", 3.45, 45.5], [\"Aur\", 5.35, 40.5], [\"Gem\", 7.10, 24.5],\n  [\"Leo\", 10.6, 18.0],\n].map(([name, ra, dec]) => {\n  const [x, y] = project(ra, dec);\n  return { x, y, name };\n});\n\nconst raTicks = [];\nfor (let h = 0; h < 24; h += 2) {\n  const ang = h * 15 * DEG;\n  const r = 96;\n  raTicks.push({ x: r * Math.sin(ang), y: r * Math.cos(ang), name: h + \"ʰ\" });\n}\nconst decTicks = [30, 60].map((d) => ({ x: 3.5, y: 90 - d, name: \"+\" + d + \"°\" }));\n\n// --- Chart ------------------------------------------------------------------\nconst axis = {\n  min: -103, max: 103, gridLineWidth: 0, lineWidth: 0, tickWidth: 0,\n  minPadding: 0, maxPadding: 0, startOnTick: false, endOnTick: false,\n  labels: { enabled: false }, title: { text: null },\n};\n\nHighcharts.chart(\"container\", {\n  chart: {\n    backgroundColor: \"transparent\", animation: false, margin: [90, 70, 50, 70],\n    style: { fontFamily: \"inherit\" },\n  },\n  credits: { enabled: false },\n  colors: t.palette,\n  title: {\n    text: \"star-chart-constellation · javascript · highcharts · anyplot.ai\",\n    style: { color: t.ink, fontSize: \"22px\", fontWeight: \"600\" },\n  },\n  subtitle: {\n    text: \"Northern sky · azimuthal-equidistant projection · point size ∝ brightness (apparent magnitude)\",\n    style: { color: t.inkSoft, fontSize: \"14px\" },\n  },\n  legend: { enabled: false },\n  xAxis: axis,\n  yAxis: Object.assign({}, axis),\n  tooltip: {\n    backgroundColor: t.elevatedBg, borderColor: t.grid,\n    style: { color: t.ink, fontSize: \"13px\" },\n  },\n  plotOptions: {\n    series: { animation: false, enableMouseTracking: false, states: { inactive: { opacity: 1 } } },\n  },\n  series: [\n    // Bright stars first → the brand-green categorical series.\n    {\n      type: \"scatter\", name: \"Stars\", data: brightData, color: BRAND, zIndex: 6,\n      enableMouseTracking: true,\n      marker: { symbol: \"circle\", lineWidth: 0.5, lineColor: t.ink, fillColor: BRAND },\n      tooltip: { headerFormat: \"\", pointFormatter() { return \"<b>\" + this.name + \"</b><br/>mag \" + this.mag.toFixed(2); } },\n    },\n    {\n      type: \"scatter\", name: \"Field\", data: fieldData, color: BRAND, zIndex: 5,\n      opacity: 0.5, marker: { symbol: \"circle\", lineWidth: 0 },\n    },\n    {\n      type: \"line\", name: \"Constellations\", data: constLines, zIndex: 4,\n      color: LINE_COL, lineWidth: 1.2, marker: { enabled: false }, dashStyle: \"Solid\",\n    },\n    {\n      type: \"line\", name: \"Ecliptic\", data: ecliptic, zIndex: 3,\n      color: t.amber, lineWidth: 1.4, dashStyle: \"Dash\", marker: { enabled: false },\n    },\n    {\n      type: \"line\", name: \"Equator\", data: boundary, zIndex: 2,\n      color: t.inkSoft, lineWidth: 1.6, marker: { enabled: false },\n    },\n    {\n      type: \"line\", name: \"Grid\", data: gridData, zIndex: 1,\n      color: t.grid, lineWidth: 1, marker: { enabled: false },\n    },\n    // Label layers (markers off; only dataLabels render).\n    {\n      type: \"scatter\", name: \"conLabels\", data: conLabels, zIndex: 7,\n      marker: { enabled: false },\n      dataLabels: {\n        enabled: true, allowOverlap: true, format: \"{point.name}\",\n        style: { color: t.ink, fontSize: \"14px\", fontWeight: \"600\", fontStyle: \"italic\", textOutline: \"none\" },\n      },\n    },\n    {\n      type: \"scatter\", name: \"raTicks\", data: raTicks, zIndex: 7,\n      marker: { enabled: false },\n      dataLabels: {\n        enabled: true, allowOverlap: true, format: \"{point.name}\", align: \"center\", verticalAlign: \"middle\",\n        style: { color: t.inkSoft, fontSize: \"12px\", fontWeight: \"400\", textOutline: \"none\" },\n      },\n    },\n    {\n      type: \"scatter\", name: \"decTicks\", data: decTicks, zIndex: 7,\n      marker: { enabled: false },\n      dataLabels: {\n        enabled: true, allowOverlap: true, format: \"{point.name}\", align: \"left\", verticalAlign: \"middle\",\n        style: { color: t.inkSoft, fontSize: \"12px\", fontWeight: \"400\", textOutline: \"none\" },\n      },\n    },\n  ],\n});\n"}