Pen Settings



CSS Base

Vendor Prefixing

Add External Stylesheets/Pens

Any URL's added here will be added as <link>s in order, and before the CSS in the editor. If you link to another Pen, it will include the CSS from that Pen. If the preprocessor matches, it will attempt to combine them before processing.

+ add another resource


Babel includes JSX processing.

Add External Scripts/Pens

Any URL's added here will be added as <script>s in order, and run before the JavaScript in the editor. You can use the URL of any other Pen and it will include the JavaScript from that Pen.

+ add another resource


Add Packages

Search for and use JavaScript packages from npm here. By selecting a package, an import statement will be added to the top of the JavaScript editor for this package.


Save Automatically?

If active, Pens will autosave every 30 seconds after being saved once.

Auto-Updating Preview

If enabled, the preview panel updates automatically as you code. If disabled, use the "Run" button to update.

Format on Save

If enabled, your code will be formatted when you actively save your Pen. Note: your code becomes un-folded during formatting.

Editor Settings

Code Indentation

Want to change your Syntax Highlighting theme, Fonts and more?

Visit your global Editor Settings.


                   <div id="darken"></div>
   <div id="universe"></div>
   <img id="logoLeft" src="" />
   <img id="logoRight" src="" />
   <div id="controls">
     <div id="header"><label title="Regenerate Star Map" class="link" id="constellation"></label>
       <span class="showOrbits"><label for="showOrbits" style="color:gray">Show Orbits</label><input id="showOrbits"
           type="checkbox" checked></input></span>
     <div id="links"></div>


                body {
  width: 100vw;
  height: 100vh;
  background-color: black;
  background: url(;
  overflow: hidden;
  background-color: black;
  background-size: cover;
  display: flex;
  justify-content: center;
  font-family: Oxanium;

#universe {
  width: 100vw;
  height: 100vh;
  user-select: none;
#universe * {
  transform-style: preserve-3d;

.system {
  will-change: transform;
  position: absolute;
  transition: all 1s;

.star {
  position: absolute;
  display: flex;
  align-items: center;
  justify-content: center;

.planet, .starIcon {
  transition: all 1s;

.orbit {
  position: absolute;
  border: 1px solid rgba(177, 176, 176, 0.2);
  border-radius: 50%;

.position {
  position: absolute;

@keyframes orbit {
  100% {
    transform: rotateZ(-360deg);

@keyframes spin {
  100% {
    transform: rotateZ(360deg) rotateY(360deg) rotateX(360deg);

#darken {
  display: none;
  position: fixed;
  z-index: 1;
  left: 0;
  top: 0;
  width: 100vw;
  height: 100vh;
  overflow: auto;
  background-color: rgb(0, 0, 0);
  background-color: rgba(0, 0, 0, 0.85);

#controls {
  position: absolute;
  top: 0;
  z-index: 2;
  padding: 5px;
  color: white;
  width: 80%;
  text-align: center;

#header {

#links {
  width: 100%;
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  text-align: center;

#links div {
  padding: 5px;
  flex: 0 1 auto;

.disabled {
  display: none;

.link:hover {
  filter: brightness(60%);
  cursor: pointer;

.systemLabel {
  position: absolute;
  opacity: 0.6;
  color: rgb(157, 169, 207);

#logoRight {
  width: 100px;
  height: 100px;

#logoLeft {
  position: absolute;
  top: 0;
  left: 0;

#logoRight {
  position: absolute;
  left: calc(100% - 100px);
  top: 0;
.showOrbits {
  font-size: 80%;
@media only screen and (max-width: 1000px) {
  #logoRight {


                (function () {

    const planetSet = ["๐ŸŒ", "๐ŸŒ’", "๐ŸŒ“", "๐ŸŒ”", "๐ŸŒ•", "๐ŸŒ–", "๐ŸŒ—", "๐ŸŒ˜"]
    const moons = ["๐ŸŒ›", "๐ŸŒœ", "๐Ÿ›ฐ๏ธ"]
    const notSpaceObjects = ["โ›น", "โ›„", "๐ŸŽƒ", "๐ŸŽ…", "๐Ÿ‚", "๐Ÿƒ", "๐Ÿ„", "๐ŸŠ", "๐Ÿ‹", "๐ŸŒ", "๐Ÿ‘€", "๐Ÿ‘ค", "๐Ÿ‘ฅ", "๐Ÿ‘ฆ", "๐Ÿ‘ง", "๐Ÿ‘จ", "๐Ÿ‘ฉ", "๐Ÿ‘ช", "๐Ÿ‘ซ", "๐Ÿ‘ฌ", "๐Ÿ‘ญ", "๐Ÿ‘ฎ", "๐Ÿ‘ฏ", "๐Ÿ‘ฐ", "๐Ÿ‘ฑ", "๐Ÿ‘ฒ", "๐Ÿ‘ณ", "๐Ÿ‘ด", "๐Ÿ‘ต", "๐Ÿ‘ถ", "๐Ÿ‘ท", "๐Ÿ‘ธ", "๐Ÿ‘น", "๐Ÿ‘บ", "๐Ÿ‘ป", "๐Ÿ‘ผ", "๐Ÿ‘ฝ", "๐Ÿ‘พ", "๐Ÿ‘ฟ", "๐Ÿ’€", "๐Ÿ’", "๐Ÿ’‚", "๐Ÿ’ƒ", "๐Ÿ’†", "๐Ÿ’‡", "๐Ÿ’", "๐Ÿ’‘", "๐Ÿ’ฉ", "๐Ÿ•ด", "๐Ÿ•ต", "๐Ÿ—ฟ", "๐Ÿ˜€", "๐Ÿ˜", "๐Ÿ˜‚", "๐Ÿ˜ƒ", "๐Ÿ˜„", "๐Ÿ˜…", "๐Ÿ˜†", "๐Ÿ˜‡", "๐Ÿ˜ˆ", "๐Ÿ˜‰", "๐Ÿ˜Š", "๐Ÿ˜‹", "๐Ÿ˜Œ", "๐Ÿ˜", "๐Ÿ˜Ž", "๐Ÿ˜", "๐Ÿ˜", "๐Ÿ˜‘", "๐Ÿ˜’", "๐Ÿ˜“", "๐Ÿ˜”", "๐Ÿ˜•", "๐Ÿ˜–", "๐Ÿ˜—", "๐Ÿ˜˜", "๐Ÿ˜™", "๐Ÿ˜š", "๐Ÿ˜›", "๐Ÿ˜œ", "๐Ÿ˜", "๐Ÿ˜ž", "๐Ÿ˜Ÿ", "๐Ÿ˜ ", "๐Ÿ˜ก", "๐Ÿ˜ข", "๐Ÿ˜ฃ", "๐Ÿ˜ค", "๐Ÿ˜ฅ", "๐Ÿ˜ฆ", "๐Ÿ˜ง", "๐Ÿ˜จ", "๐Ÿ˜ฉ", "๐Ÿ˜ช", "๐Ÿ˜ซ", "๐Ÿ˜ฌ", "๐Ÿ˜ญ", "๐Ÿ˜ฎ", "๐Ÿ˜ฏ", "๐Ÿ˜ฐ", "๐Ÿ˜ฑ", "๐Ÿ˜ฒ", "๐Ÿ˜ณ", "๐Ÿ˜ด", "๐Ÿ˜ต", "๐Ÿ˜ถ", "๐Ÿ˜ท", "๐Ÿ˜ธ", "๐Ÿ˜น", "๐Ÿ˜บ", "๐Ÿ˜ป", "๐Ÿ˜ผ", "๐Ÿ˜ฝ", "๐Ÿ˜พ", "๐Ÿ˜ฟ", "๐Ÿ™€", "๐Ÿ™", "๐Ÿ™‚", "๐Ÿ™ƒ", "๐Ÿ™„", "๐Ÿ™…", "๐Ÿ™†", "๐Ÿ™‡", "๐Ÿ™ˆ", "๐Ÿ™‰", "๐Ÿ™Š", "๐Ÿ™‹", "๐Ÿ™Œ", "๐Ÿ™", "๐Ÿ™Ž", "๐Ÿ™", "๐Ÿšด", "๐Ÿšต", "๐Ÿšถ", "๐Ÿ›ธ", "๐Ÿค", "๐Ÿค‘", "๐Ÿค’", "๐Ÿค“", "๐Ÿค”", "๐Ÿค•", "๐Ÿค–", "๐Ÿค—", "๐Ÿค ", "๐Ÿคก", "๐Ÿคข", "๐Ÿคฃ", "๐Ÿคค", "๐Ÿคฅ", "๐Ÿคฆ", "๐Ÿคง", "๐Ÿคจ", "๐Ÿคฉ", "๐Ÿคช", "๐Ÿคซ", "๐Ÿคฌ", "๐Ÿคญ", "๐Ÿคฎ", "๐Ÿคฏ", "๐Ÿคฐ", "๐Ÿคฑ", "๐Ÿคฒ", "๐Ÿคณ", "๐Ÿคด", "๐Ÿคต", "๐Ÿคถ", "๐Ÿคท", "๐Ÿคธ", "๐Ÿคน", "๐Ÿคบ", "๐Ÿคป", "๐Ÿคผ", "๐Ÿคฝ", "๐Ÿคพ", "๐Ÿฅฐ", "๐Ÿฅณ", "๐Ÿฅด", "๐Ÿฅต", "๐Ÿฅถ", "๐Ÿฅบ", "๐Ÿฆธ", "๐Ÿฆน", "๐Ÿง", "๐Ÿง‘", "๐Ÿง’", "๐Ÿง“", "๐Ÿง”", "๐Ÿง•", "๐Ÿง–", "๐Ÿง—", "๐Ÿง˜", "๐Ÿง™", "๐Ÿงš", "๐Ÿง›", "๐Ÿงœ", "๐Ÿง", "๐Ÿงž", "๐ŸงŸ", "๐Ÿงธ", "๐Ÿ€", "๐Ÿ", "๐Ÿ‚", "๐Ÿƒ", "๐Ÿ„", "๐Ÿ…", "๐Ÿ†", "๐Ÿ‡", "๐Ÿˆ", "๐Ÿ‰", "๐ŸŠ", "๐Ÿ‹", "๐ŸŒ", "๐Ÿ", "๐ŸŽ", "๐Ÿ", "๐Ÿ", "๐Ÿ‘", "๐Ÿ’", "๐Ÿ“", "๐Ÿ”", "๐Ÿ•", "๐Ÿ–", "๐Ÿ—", "๐Ÿ˜", "๐Ÿ™", "๐Ÿš", "๐Ÿ›", "๐Ÿœ", "๐Ÿ", "๐Ÿž", "๐ŸŸ", "๐Ÿ ", "๐Ÿก", "๐Ÿข", "๐Ÿฃ", "๐Ÿค", "๐Ÿฅ", "๐Ÿฆ", "๐Ÿง", "๐Ÿจ", "๐Ÿฉ", "๐Ÿช", "๐Ÿซ", "๐Ÿฌ", "๐Ÿญ", "๐Ÿฎ", "๐Ÿฏ", "๐Ÿฐ", "๐Ÿฑ", "๐Ÿฒ", "๐Ÿณ", "๐Ÿด", "๐Ÿต", "๐Ÿถ", "๐Ÿท", "๐Ÿธ", "๐Ÿน", "๐Ÿบ", "๐Ÿป", "๐Ÿผ", "๐Ÿฆ€", "๐Ÿฆ", "๐Ÿฆ‚", "๐Ÿฆƒ", "๐Ÿฆ„", "๐Ÿฆ…", "๐Ÿฆ†", "๐Ÿฆ‡", "๐Ÿฆˆ", "๐Ÿฆ‰", "๐ŸฆŠ", "๐Ÿฆ‹", "๐ŸฆŒ", "๐Ÿฆ", "๐ŸฆŽ", "๐Ÿฆ", "๐Ÿฆ", "๐Ÿฆ‘", "๐Ÿฆ’", "๐Ÿฆ“", "๐Ÿฆ”", "๐Ÿฆ•", "๐Ÿฆ–", "๐Ÿฆ—", "๐Ÿฆ˜", "๐Ÿฆ™", "๐Ÿฆš", "๐Ÿฆ›", "๐Ÿฆœ", "๐Ÿฆ", "๐Ÿฆž", "๐ŸฆŸ", "๐Ÿฆ ", "๐Ÿฆก", "๐Ÿฆข"]
    const groupNames = ["Constellation", "Cluster", "Open Cluster", "Group", "Federation"]
    const CONSTELLATIONS = ["Andromeda", "Antlia", "Apus", "Aquarius", "Aquila", "Ara", "Aries", "Auriga", "Boรถtes", "Caelum", "Camelopardalis", "Cancer", "Canes Venatici", "Canis Major", "Canis Minor", "Capricornus", "Carina", "Cassiopeia", "Centaurus", "Cepheus", "Cetus", "Chamaeleon", "Circinus", "Columba", "Coma Berenices", "Corona Austrina", "Corona Borealis", "Corvus", "Crater", "Crux", "Cygnus", "Delphinus", "Dorado", "Draco", "Equuleus", "Eridanus", "Fornax", "Gemini", "Grus", "Hercules", "Horologium", "Hydra", "Hydrus", "Indus", "Lacerta", "Leo", "Leo Minor", "Lepus", "Libra", "Lupus", "Lynx", "Lyra", "Mensa", "Microscopium", "Monoceros", "Musca", "Norma", "Octans", "Ophiuchus", "Orion", "Pavo", "Pegasus", "Perseus", "Phoenix", "Pictor", "Pisces", "Piscis Austrinus", "Puppis", "Pyxis", "Reticulum", "Sagitta", "Sagittarius", "Scorpius", "Sculptor", "Scutum", "Serpens", "Sextans", "Taurus", "Telescopium", "Triangulum", "Triangulum Australe", "Tucana", "Ursa Major", "Ursa Minor", "Vela", "Virgo", "Volans", "Vulpecula"] /*88*/
    const STARS = ["Absolutno", "Achernar", "Achird", "Acrab", "Acrux", "Acubens", "Adhafera", "Adhara", "Adhil", "Ain", "Ainalrami", "Aladfar", "Alamak", "Alasia", "Alathfar", "Albaldah", "Albali", "Albireo", "Alchiba", "Alcor", "Alcyone", "Aldebaran", "Alderamin", "Aldhanab", "Aldhibah", "Aldulfin", "Alfirk", "Algedi", "Algenib", "Algieba", "Algol", "Algorab", "Alhena", "Alioth", "Aljanah", "Alkaid", "Al Kalb al Rai", "Alkalurops", "Alkaphrah", "Alkarab", "Alkes", "Almaaz", "Almach", "Al Minliar al Asad", "Alnair", "Alnasl", "Alnilam", "Alnitak", "Alniyat", "Alphard", "Alphecca", "Alpheratz", "Alpherg", "Alrakis", "Alrescha", "Alruba", "Alsafi", "Alsciaukat", "Alsephina", "Alshain", "Alshat", "Altair", "Altais", "Alterf", "Aludra", "Alula Australis", "Alula Borealis", "Alya", "Alzirr", "Amadioha", "Amansinaya", "Anadolu", "Ancha", "Angetenar", "Aniara", "Ankaa", "Anser", "Antares", "Arcalis", "Arcturus", "Arkab Posterior", "Arkab Prior", "Arneb", "Ascella", "Asellus Australis", "Asellus Borealis", "Ashlesha", "Asellus Primus", "Asellus Secundus", "Asellus Tertius", "Aspidiske", "Asterope/Sterope", "Atakoraka", "Athebyne", "Atik", "Atlas", "Atria", "Avior", "Axolotl", "Ayeyarwady", "Azelfafage", "Azha", "Azmidi", "Baekdu", "Barnard's Star", "Baten Kaitos", "Beemim", "Beid", "Belel", "Belenos", "Bellatrix", "Berehinya", "Betelgeuse", "Bharani", "Bibha", "Biham", "Bosona", "Botein", "Brachium", "Bubup", "Buna", "Bunda", "Canopus", "Capella", "Caph", "Castor", "Castula", "Cebalrai", "Ceibo", "Celaeno", "Cervantes", "Chalawan", "Chamukuy", "Chaophraya", "Chara", "Chason", "Chechia", "Chertan", "Citadelle", "Citala", "Cocibolca", "Copernicus", "Cor Caroli", "Cujam", "Cursa", "Dabih", "Dalim", "Deneb", "Deneb Algedi", "Denebola", "Diadem", "Dingolay", "Diphda", "Diwo", "Diya", "Dofida", "Dombay", "Dschubba", "Dubhe", "Dziban", "Ebla", "Edasich", "Electra", "Elgafar", "Elkurud", "Elnath", "Eltanin", "Emiw", "Enif", "Errai", "Fafnir", "Fang", "Fawaris", "Felis", "Felixvarela", "Flegetonte", "Fomalhaut", "Formosa", "Franz", "Fulu", "Funi", "Fumalsamakah", "Furud", "Fuyue", "Gacrux", "Gakyid", "Garnet Star", "Giausar", "Gienah", "Ginan", "Gloas", "Gomeisa", "Graffias", "Grumium", "Gudja", "Gumala", "Guniibuu", "Hadar", "Haedus", "Hamal", "Hassaleh", "Hatysa", "Helvetios", "Heze", "Hoggar", "Homam", "Horna", "Hunahpu", "Hunor", "Iklil", "Illyrian", "Imai", "Intercrus", "Inquill", "Intan", "Irena", "Itonda", "Izar", "Jabbah", "Jishui", "Kaffaljidhma", "Kakkab", "Kalausi", "Kamui", "Kang", "Karaka", "Kaus Australis", "Kaus Borealis", "Delta Sagittarii", "Kaveh", "Kekouan", "Keid", "Khambalia", "Kitalpha", "Kochab", "Koeia", "Koit", "Kornephoros", "Kraz", "Kuma", "Kurhah", "La Superba", "Larawag", "Lerna", "Lesath", "Libertas", "Lich", "Liesma", "Lilii Borea", "Lionrock", "Lucilinburhuc", "Lusitania", "Maasym", "Macondo", "Mago", "Mahasim", "Mahsati", "Maia", "Malmok", "Marfark", "Marfik", "Markab", "Markeb", "Marohu", "Marsic", "Matar", "Mebsuta", "Megrez", "Meissa", "Mekbuda", "Meleph", "Menkalinan", "Menkar", "Menkent", "Menkib", "Merak", "Merga", "Meridiana", "Merope", "Mesarthim", "Miaplacidus", "Mimosa", "Minchir", "Minelauva", "Mintaka", "Mira", "Mirach", "Miram", "Mirfak", "Mirzam", "Misam", "Mizar", "Moldoveanu", "Monch", "Montuno", "Morava", "Moriah", "Mothallah", "Mouhoun", "Mpingo", "Muliphein", "Muphrid", "Muscida", "Musica", "Muspelheim", "Nahn", "Naledi", "Naos", "Nash", "Nashira", "Nasti", "Natasha", "Navi", "Nekkar", "Nembus", "Nenque", "Nervia", "Nihal", "Nikawiy", "Nosaxa", "Nunki", "Nusakan", "Nushagak", "Nyamien", "Ogma", "Okab", "Paikauhale", "Parumleo", "Peacock", "Petra", "Phact", "Phecda", "Pherkad", "Phoenicia", "Piautos", "Pincoya", "Pipoltr", "Pipirima", "Pleione", "Poerava", "Polaris", "Polaris Australis", "Polis", "Pollux", "Porrima", "Praecipua", "Prima Hyadum", "Procyon", "Propus", "Proxima Centauri", "Ran", "Rapeto", "Rasalas", "Rasalgethi", "Rasalhague", "Rastaban", "Regor", "Regulus", "Revati", "Rigel", "Rigil Kentaurus", "Rosaliadecastro", "Rotanev", "Ruchbah", "Rukbat", "Sabik", "Saclateni", "Sadachbia", "Sadalbari", "Sadalmelik", "Sadalsuud", "Sadr", "Sagarmatha", "Saiph", "Salm", "Samaya", "Sansuna", "Sargas", "Sarin", "Sarir", "Sceptrum", "Scheat", "Schedar", "Secunda Hyadum", "Segin", "Seginus", "Sham", "Shama", "Sharjah", "Shaula", "Sheliak", "Sheratan", "Sika", "Sirius", "Situla", "Skat", "Solaris", "Spica", "Sterrennacht", "Stribor", "Sualocin", "Subra", "Suhail", "Sulafat", "Syrma", "Tabit", "Taika", "Taiyangshou", "Taiyi", "Talitha", "Tangra", "Tania Australis", "Tania Borealis", "Tapecue", "Tarazed", "Tarf", "Taygeta", "Tegmine", "Tejat", "Terebellum", "Tevel", "Thabit", "Theemin", "Thuban", "Tiaki", "Tianguan", "Tianyi", "Timir", "Tislit", "Titawin", "Tojil", "Toliman", "Tonatiuh", "Torcular", "Tuiren", "Tupa", "Tupi", "Tureis", "Ukdah", "Uklun", "Unukalhai", "Unurgunite", "Uruk", "Vega", "Veritate", "Vindemiatrix", "Wasat", "Wazn", "Wezen", "Wurren", "Xamidimura", "Xihe", "Xuange", "Yed Posterior", "Yed Prior", "Yildun", "Zaniah", "Zaurak", "Zavijava", "Zhang", "Zibal", "Zosma", "Zubenelgenubi", "Zubenelhakrabi", "Zubeneschamali"]
    var stars = [];
    var sortedLinks = []
    var WINDOW_MIN
    const MIN_THRESHOLD = 640
    var universe = document.getElementById("universe")
    var links = document.querySelector("#links")

    window.onload = attachHandlers

    function attachHandlers(ev) {

    // window.addEventListener("resize", attachHandlers)

    function initializeGalaxy() {
        WINDOW_MIN = Math.min(universe.clientHeight, universe.clientWidth)
        stars = STARS.slice()
        links.innerHTML = ""
        document.querySelector("#darken").style.display = "none"
        document.querySelector("#showOrbits").checked = true
        let numSystems = generateSystems();
        [...document.querySelectorAll("#constellation")].forEach(el => el.addEventListener("click",
        document.querySelector("#showOrbits").addEventListener("change", function (ev) {

            if (! {
                [...universe.querySelectorAll(".orbit")].forEach(o => = "none")
            } else {
                [...universe.querySelectorAll(".orbit")].forEach(o => = "")
        document.querySelector("#constellation").innerText = `${pick(CONSTELLATIONS)} ${pick(groupNames)} [${numSystems} known star systems]`

    function generateSystems() {

        universe.innerHTML = ""
        sortedLinks = [];
        const MAX_SYSTEMS = isMobile() ? 10 : 15
        let numSystems = getRandomInt(5, MAX_SYSTEMS)
        for (let i = 0; i < numSystems; i++) {

            let numPlanets = getRandomInt(1, 10)
            let starSize = getRandomInt(50, 100) //fonst size - percent
            let maxOrbit = getRandomInt(50, 500) //pixels
            let maxPlanetSize = starSize / 5

            let iconSet = Math.random() < .1 ? notSpaceObjects : planetSet
            let sun = Math.random() < .1 ? pick(notSpaceObjects) : "๐ŸŒž"
            let system = generateSystem("Star", sun, starSize, numPlanets, iconSet, maxOrbit, maxPlanetSize)

            //system position
   = `${getRandomInt(0,80)}%`
   = `${getRandomInt(10,80)}%`
        sortedLinks.sort((a, b) => {
            return a.systemID < b.systemID ? -1 : 1
        sortedLinks.forEach(link => links.appendChild(link))
        return numSystems

    function generateSystem(type, starIcon, starFont, numPlanets, planetSet, maxOrbit, maxPlanetSize) {

        let system = document.createElement("div");
        system.setAttribute("class", "system")

        //system label
        let labl = document.createElement("div")

        //system size and orientation = `${maxOrbit}px` = `${maxOrbit}px`
        let xrot = getRandomInt(0, 180) /*random orientation for system*/
        let yrot = getRandomInt(0, 180) /*note that going over 90deg for these values makes 3D orbits get painted in wrong order TODO: fix?!*/
        let scale = getRandomFloat(.2, 1)
        let labelScale = scale < .2 ? 3 : scale < .7 ? 2 : 1 = ` scale(${scale}) rotateX(${xrot}deg) rotateY(${yrot}deg)` = ` rotateY(${-yrot}deg) rotateX(${-xrot}deg) scale(${labelScale}) `

        //system center (can be a star or planet)
        let star = document.createElement("div")
        let starInner = document.createElement("div")
        starInner.setAttribute("class", "starInner")
        star.setAttribute("class", "star") = `100%` = `100%`

        starInner.innerHTML = `<div class="starIcon">${starIcon}</div>`
        let starSpan = starInner.querySelector(".starIcon") = `${starFont}%` = `hue-rotate(${getRandomInt(0,360)}deg)`
        star.querySelector(".starInner").style.animation = `spin ${getRandomFloat(15,30)}s infinite linear`


        const MIN_PLANET_SIZE = 5 /*percent of parent system*/
        let orbitsForThisSystem = [] /*keep track so they aren't too close together*/

        /*maxOrbital range is 50px-500px so 275 is middlish. note that maxOrbit (size of system) alone determines orbital speeds (and hence, "star mass")*/
        let massOfStar = 275 / maxOrbit
        system.starMassLabel = massOfStar > 1 ? massOfStar * 10 : massOfStar

        const PERCENT_MOONS = .3
        let moonProbability = type == "Star" ? PERCENT_MOONS : 0 /*at top level only, generate some planetary systems with moons*/
        for (let i = 0; i < numPlanets; i++) {

            //generate planet
            let fontSize = getRandomFloat(MIN_PLANET_SIZE, maxPlanetSize);
            let orbitExtent =  i == numPlanets - 1 ? 100 : getRandomFloat(30, 100) /*percentage of the system the orbit takes up*/
            let planet = generatePlanet(pick(planetSet), fontSize, maxOrbit, orbitExtent, moonProbability, orbitsForThisSystem)

            //orientation and spin 
            if (planet.systemType != "System") {
                planet.querySelector(".planet").style.animation = `spin ${getRandomFloat(1,30)}s infinite linear`
        if (type == "Star") {
            let systemID = generateInfo(system)
            labl.innerText = systemID

        return system

    function generateInfo(system) {

        let link = document.createElement("div")
        link.classList.add("link") = getRandomColor()
        system.querySelector(".systemLabel").style.color =
        let systemID = pickDistinct(stars)
        link.innerText = systemID
        link.systemID = systemID
        system.systemID = systemID
        link.onclick = (ev) => {
            let isStillOpen = system.getAttribute("data-isModal") == "true"
            if (!isStillOpen) deactivateLink(systemID)
        return systemID

    function generatePlanet(icon, fontSize, systemSize, orbitExtent, moonProbability, otherOrbits) {

        let template = `<div class="position"><div class="planet">${icon}</div></div>`
        let orbit = document.createElement("div")
        orbit.innerHTML = template
        let inner = orbit.querySelector(".planet")
        let outer = orbit.querySelector(".position")
        orbit.setAttribute("class", "orbit")

        const MIN_ORBIT_DISTANCE = 5 //percent of system
        while (isCloseToOtherOrbits(orbitExtent, otherOrbits)) {
            orbitExtent = getRandomFloat(30, 110)

        //orbital calculations:
        let physicalDistance = orbitExtent * systemSize
        let orbitalPeriod = (Math.pow(physicalDistance, 2) * .0000002) = `orbit ${orbitalPeriod}s infinite linear` = `${orbitExtent}%` = `${orbitExtent}%` = `calc(50% - ${orbitExtent/2}%)` /*position orbit in middle of system*/ = `calc(50% - ${orbitExtent/2}%)`

        //icon size and position = `${fontSize}%` = `-${fontSize/2}%` = `hue-rotate(${getRandomInt(0,360)}deg)` = `-.5em` = `calc(50% - .5em)`

        if (Math.random() < moonProbability) {

            orbit.systemType = "System"
            let numPlanets = getRandomInt(1, 3)
            let lowerBoundOrbit = fontSize > 20 ? 100 : 50
            let maxOrbit = getRandomInt(lowerBoundOrbit, 150) //percentage of the system width that this orbit will occupy

            let maxPlanetSize = fontSize / 3 //max moon size
            let planetWithMoons = generateSystem("Planetary", pick(planetSet), fontSize, numPlanets, moons, maxOrbit, maxPlanetSize)
   = `-${maxOrbit/2}px`
   = `calc(50% - ${maxOrbit/2}px)`

            orbit.querySelector(".planet").innerHTML = planetWithMoons.outerHTML

        return orbit

        function isCloseToOtherOrbits(val, arr) {
            return arr.filter(o => Math.abs(o - val) < MIN_ORBIT_DISTANCE).length > 0

    function attachZoomHandlers() {
        var systems = universe.querySelectorAll("#universe > .system");
        [].forEach(s => {

            const NUMPLANETS = s.querySelectorAll(":scope >.orbit").length
            s.addEventListener("click", function (ev) {

                let isModal = s.hasAttribute("data-isModal") ? s.getAttribute("data-isModal") == "true" : false
                let isFlat = s.hasAttribute("data-isFlat") ? s.getAttribute("data-isFlat") == "true" : false

                if (!isModal) {

                    //show darkening layer to obscure other systems, and store relevant style attributes to restore later
                    document.querySelector("#darken").style.display = "block"
                    s.querySelector(".systemLabel").style.display = "none"
                    s.setAttribute("data-isModal", true)

           = 2 /*the star system is above the darkening layer, which is z=1*/
           = "100vmin" /*fullscreen size*/
           = "100vmin"
           = `calc(50% - 50vmin)`
           = `calc(50% - 50vmin)`
           =\(.*?\)/, "") /*remove scale*/

                    activateLink(s.systemID, `  (Stellar Mass: ${s.starMassLabel.toFixed(2)} Mโ˜‰, ${NUMPLANETS} Planet${NUMPLANETS>1?"s":""})`) //link

                    const MODAL_ZOOM = 10 /*first modal mode adds a zoom effect */ ;
                    [...s.querySelectorAll(".planet, .starIcon")].forEach(sp => {
                        let num =\d+/)
               = `${num*MODAL_ZOOM}%`

                } else {
                    //it's currently modal, so...
                    if (!isFlat) { //make it flat view first, don't close it yet
               = "" /*removes the orientation transform, thus, flat*/
                        s.setAttribute("data-isFlat", true)
                    } else {
                        //it was in flat mode. exit fullscreen mode and restore original size/position
                        document.querySelector("#darken").style.display = "none"
                        s.querySelector(".systemLabel").style.display = "block"
               = s.getAttribute("data-width")
               = s.getAttribute("data-height")
               = s.getAttribute("data-top")
               = s.getAttribute("data-left")
               = s.getAttribute("data-transform");
                        [...s.querySelectorAll(".planet, .starIcon")].forEach(sp => {
                   = sp.getAttribute("data-fontSize")
               = 0
                        s.setAttribute("data-isModal", false)
                        s.setAttribute("data-isFlat", false)


    function activateLink(systemID, infoText) {
        let link = [...links.querySelectorAll("div")].filter(l => l.systemID == systemID)[0];
        link.innerText = link.innerText + " System" + infoText;
        [...links.querySelectorAll("div")].forEach(el => {

    function deactivateLink(systemID) {
        let link = [...links.querySelectorAll("div")].filter(l => l.systemID == systemID)[0]
        link.innerText = link.innerText.replace(/System.*/, "");
        [...links.querySelectorAll("div")].forEach(el => {

    function pick(arr) {
        return arr[Math.floor(Math.random() * arr.length)];

    function pickDistinct(arr) {
        let index = Math.floor(Math.random() * arr.length)
        let item = arr[index]
        arr.splice(index, 1) /*delete this entry*/
        return item

    function getRandomInt(min, max) {
        min = Math.ceil(min)
        max = Math.floor(max)
        return Math.floor(Math.random() * (max - min + 1)) + min

    function getRandomFloat(min, max) {
        return (Math.random() * (max - min)) + min

    function getRandomColor() {
        return `rgb(${getRandomInt(100,255)},${getRandomInt(100,255)},${getRandomInt(100,255)})`

    function isMobile() {
        let mobile = window.matchMedia(`only screen and (max-width: ${MIN_THRESHOLD}px), only screen and (max-height:${MIN_THRESHOLD}px)`).matches;
        return mobile || navigator.userAgent.indexOf("Firefox") != -1 || navigator.userAgent.indexOf("Silk") != -1;