The Moon in 10241 Dots — Re-creating a 1969 Masterpiece
Tags: css, javascript, webdev, showdev
Published: 2023-07-18 14:48:57

In 1969, German artist Max Ernst created a beautiful painting in celebration of the Moon-landing:

![Original Poster](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ubh1nd16fc3n5jj2taxq.jpg)

Entitled _Naissance d'une Galaxie_ (Birth of a Galaxy), I can imagine it must've taken **months** to complete, using stencils for all the dots. 

Luckily, we can code it much faster!

---

## Radial Gradient
First, we need a background-layer with a radial background:

```css
main {
  background: #06101D radial-gradient(
    #245898, 
    #2C67AB, 
    #244D7A 15%, 
    #475F66, 
    #93A7A2 65%, 
    #234E85 66%, 
    #06101D 70%
  ) no-repeat;
}
```

![Radial Gradient](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/d3dbbqf6jde1vakbichy.png)

---

## Fluffy Clouds Filter
We need some "fluffy clouds" to simulate the painted layers, so we'll use the "fluffy" filter from my CSS Filter Editor:

{% codepen https://codepen.io/stoumann/pen/MWeNmyb %}

We add an `::after`-pseudo-element to the same layer as the radial background, inheriting that same background, but also using the "fluffy"-filter:

```css
main {
  &::after {
    background: inherit;
    mix-blend-mode: hard-light;
    content: "";
    display: block;
    filter: url(#fluffy) blur(5px); 
    inset: 0;
    opacity: 0.33;
    position: absolute;
  }
}
```

And now we have:

![Fluffy Filter](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jmmm6vofuksnu1bqgzg2.png)

---

## Markup
Before we continue with the dots, let's add an SVG-element in the markup — we'll add the `innerHTML` later:

```html
<svg viewBox="0 0 1000 1000" class="moon"></svg>
```

---

## Dots
To hardcode the dots would probably take as long time as the original painting, so let's use JavaScript and some Math to help us out!

To start with, we need a function, `dots()`, with a configuration-object:

```js
function dots(config =
  {
    dotsring: 8,
    dotsize: 3,
    radius: 2,
    rings: 50,
    rotate: 6,
    spread: 10,
    x: 1000,
    y: 1000
  }) {
  ...
}
```

Explainer:

- **dotsring.** Dots per ring. This number is added to each new ring.
- **dotsize.** The relative dotsize (see SVG `viewBox`)
- **radius.** The start radius-index. In this case, we want to skip the first one, creating a larger "hole" in the center of the moon.
- **rings.** The total amount of rings
- **rotate.** Amount to rotate each ring
- **spread.** The distance between the dots in a ring
- **x.** The `viewBox` x-width of the SVG.
- **y.** The `viewBox` y-width of the SVG.

Next, we need arrays of coordinates for each ring. For that, we need a small helper-method:

```js
const coords = (number) => {
  const frags = 360 / number
  return Array.from({ length: number + 1 }, 
  (_, i) => (frags / 180) * i * Math.PI)
}
```
---

### Fill
If you inspect the original painting, you'll notice that most of the dots have the same, light-yellow color. A few, random dots are light blue or lavender. 

To help us fill-in random dots with these values, we need a random-method:

```js
const random = (min, max) =>
  Math.floor(Math.random() * (max - min) + min);
```

... and a `fill()`-method, where we parse-in the current dot-`index` as `number`:


```js
const fill = (number) =>
  ['#74BAA0', '#B3A0C1'][number % random(1, 100)] ||
  '#BCD2A9';
```

---

### Rendering the dots
To render the dots, we need **two** loops: 

- One for the rings. For each ring, we output a `<g>`-element, to `<g>`roup all the dots within a ring.

- One for the dots _within_ a single ring. In this inner loop, we calculate the `x` and `y` position of each dot from the coordinates we created using `coords()`. 

After that, we simply output a `<circle>` for each entry.

```js
let s = '';
for (let i = config.radius; i <= config.rings; i++) {
  const r = config.spread * i;
  const ring = coords(config.dotsring * i);
  s += `<g style="--r:${i * config.rotate}deg">${ring
    .map((value, index) => {
      const x =
        config.x / 2 - Math.round(r * Math.cos(value));
      const y =
        config.y / 2 - Math.round(r * Math.sin(value));
      return `<circle cx="${x}" cy="${y}" r="${
        config.dotsize
      }" fill="${fill(i + index)}" />`;
    })
    .join('')}</g>`;
}
return s;
```

Let's add that output to the SVG-element, we created earlier:

```js
document.querySelector('.moon').innerHTML = dots()
```

... and voilà:

![Dots](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/iq267u3n3uiazc8wlyri.png)

> **Could this be done in CSS instead of SVG?**. 
Yes, using `sin()` and `cos()` in CSS, this is definitely possible. I just find it easier to work with SVG and it's `viewBox` when working with such large "datasets" as this.

---

## Combining the Layers
Let's combine the dots with the radial, fluffy-clouds-background:

![Combining](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jyyk1lgwqgtqcfy3nr5q.png)

And we're done! ... but what about the lower part of the painting — below the Moon? 

I tried using the `drops`-filter (from the CSS Filter Editor above) and a bunch of overlaying gradients, but simply couldn't get the right "look and feel". 

---

## Adding Poster Texts
Let's turn the painting into a poster, and add some texts.

We'll use `cqi`-units for the texts, so they scale nicely to the width of our poster:

![Poster Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7ag52qo067nyq6cxr26i.png)

---

## Framing the Poster
Posters look better in frames, so let's add a nice wooden frame! 

CSS has a `border-style` called `ridge` which does the job — so with:

```css
body {
  border: 1cqi ridge #D39E85;
}
```

— we get: 

![Poster with frame](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xqsnt3az6u9ch88i5zmv.png)

---

## Wallpaper
Finally, it's time to hang our framed poster on a wall. 

Let's add a wallpaper from [CSS Patterns](https://css-pattern.com/) to the `<html>`-element — and a `box-shadow` to the `<body>`-element:

![Wallpaper](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1wcpxhngddmuog7w2wol.png)

Beautiful, isn't it? Hope your CPU could manage the 10241 dots!

## Demo
{% codepen https://codepen.io/stoumann/pen/rNQvezG %}