<svg 
  viewBox="0 0 1000 1000" 
  width="1000" 
  height="1000" 
  role="img"
>
  <title>
    A geometric pattern composed of nested circles and squiggly lines.
    Inspired by Legends of Zelda: Tears of the Kingdom
  </title>
  
  <defs>
    <linearGradient id="gold-gradient" gradientTransform="rotate(-50)">
      <stop offset="5%" stop-color="hsl(44, 100%, 67%)" />
      <stop offset="95%" stop-color="hsl(44, 70%, 27%)" />
    </linearGradient>
  </defs>

  <g class="pattern">
    <!-- 
      Our graphics code goes here 
    -->
  </g>
</svg>

<form autocomplete="off">
  <fieldset>
    <legend>Color Schemes</legend>
    
    <label>
      <input type="radio" name="theme" value="" checked/>
      Base
    </label>
    <label>
      <input type="radio" name="theme" value="gold" />
      Gold
    </label>
    <label>
      <input type="radio" name="theme" value="faded" />
      Faded
    </label>
    <label>
      <input type="radio" name="theme" value="green" />
      Green
    </label>
  </fieldset>
  <button type="button">Randomize</button>
</form>
/* Define our base custom properties */
:root {
  --fill: hsl(30, 50%, 90%);
  --stroke: hsl(80, 80%, 28%);
}

/* Apply styles to our elements */
svg {
  background: var(--fill);
}
.fill {
  fill: var(--fill);
}
.stroke {
  fill: none;
  stroke: var(--stroke);
  stroke-width: 10;
}

/* Define new themes that can be added using a class */
.theme-faded {
  --fill: hsl(56, 100%, 95%);
  --stroke: hsl(51, 45%, 55%);
}
.theme-green {
  --fill: hsl(167, 42%, 24%);
  --stroke: hsl(169, 44%, 32%);
}
.theme-gold {
  /* For this theme we're referencing an SVG gradient element to apply a gradient to our strokes */
  --stroke: url(#gold-gradient);
  --fill: hsl(44, 81%, 17%);
}

* {
  font-family: sans-serif;
}

html, body {
  display: grid;
  place-items: center;
  min-height: 100%;
  background-color: #eee;
}

svg {
  max-width: 90vw;
  max-height:85vh;
  width: auto;
  height: auto;
}

form {
  display: flex;
  justify-content: center;
  align-items: center;
  column-gap: 3em;
  row-gap: 1em;
  flex-wrap: wrap;
}

fieldset {
  display: flex;
  gap: 1em;
}



:root {
  --hue: 200;
}

button {
  /* Text Colors */
  --text-saturation: 90%;
  --text-lightness: 40%;
  
  --text-saturation-hover: calc(var(--text-saturation) + 10%);
  --text-lightness-hover: calc(var(--text-lightness) - 5%); ;
  
  --text-saturation-active: var(--text-saturation-hover);
  --text-lightness-active: calc(var(--text-lightness) - 10%); ;
  
  --text-saturation-disabled: calc(var(--text-saturation) - 60%);
  --text-lightness-disabled: calc(var(--text-lightness) + 10%);
  
  /* Background Colors */
  --background-saturation: 0%;
  --background-lightness: 100%;
  
  --background-saturation-hover: calc(var(--background-saturation) + 80%);
  --background-lightness-hover: calc(var(--background-lightness) - 5%);
  
  --background-saturation-active: var(--background-saturation-hover);
  --background-lightness-active: calc(var(--background-lightness) - 10%);
  
  --background-saturation-disabled: calc(var(--background-saturation) + 30%);
  --background-lightness-disabled: calc(var(--background-lightness) - 10%);
  
  /* Border Colors */
  --border-saturation: 90%;
  --border-lightness: 60%;
  
  --border-saturation-hover: calc(var(--border-saturation) + 10%);
  --border-lightness-hover: calc(var(--border-lightness) - 10%);
  
  --border-saturation-active: var(--border-saturation-hover);
  --border-lightness-active: calc(var(--border-lightness) - 20%);
  
  --border-saturation-disabled: calc(var(--border-saturation) - 60%);
  --border-lightness-disabled: calc(var(--border-lightness) + 20%);
  
  /* Focus shadow styles */
  --shadow-saturation-focus: 100%;
  --shadow-lightness-focus: 85%;
  
  /* Color Styles */
  color: hsl(var(--hue), var(--text-saturation), var(--text-lightness));
  background-color: hsl(var(--hue), var(--background-saturation), var(--background-lightness)); 
  border:0.1em solid hsl(var(--hue), var(--border-saturation), var(--border-lightness)); 
  
  /* Misc. Styles */
  border-radius: 0.25em;
  cursor: pointer;
  display: inline-block;
  font-size: 1em;
  padding: 0.5em 1em;
  transition-property: box-shadow, background-color, border-color, color;
  transition-timing-function: ease-out;
  transition-duration: 0.2s;
}

button:hover {
  color: hsl(
    var(--hue), 
    var(--text-saturation-hover), 
    var(--text-lightness-hover)
  );
  
  background-color: hsl(
    var(--hue), 
    var(--background-saturation-hover), 
    var(--background-lightness-hover)
  );
  
  border-color: hsl(
    var(--hue), 
    var(--border-saturation-hover), 
    var(--border-lightness-hover)
  );
}

button:active {
  color: hsl(
    var(--hue), 
    var(--text-saturation-active), 
    var(--text-lightness-active)
  );
  
  background-color: hsl(
    var(--hue), 
    var(--background-saturation-active), 
    var(--background-lightness-active)
  );
  
  border-color: hsl(
    var(--hue), 
    var(--border-saturation-active), 
    var(--border-lightness-active)
  );
}

button:focus {
  outline: none;
  box-shadow: 0 0 0 0.25em hsl(
    var(--hue), 
    var(--shadow-saturation-focus),
    var(--shadow-lightness-focus)
  );
}
import { randomInt } from 'https://unpkg.com/randomness-helpers@0.0.1/dist/index.js';
import { spline } from 'https://unpkg.com/@georgedoescode/spline@1.0.1/spline.js';

const gridSize = 1000;

function circle({ x, y, r, className }) {
  return `
    <circle 
      cx="${x}" 
      cy="${y}" 
      r="${r}" 
      class="${className}"
    />
  `;
}

function circleGroup({x, y, r }) {
  // We'll store all our circles in an array.
  const circles = [];

  // First, draw a circle with a white background but no border.
  // This will hide any elements behind the circle.
  circles.push(circle({ x, y, r, className: 'fill' }));

  // Decide how much space to put between our circles.
  const gap = 20;

  // Draw a number of circles, making each one smaller than the last
  // until we hit a radius of 0.
  let circleSize = r;
  while(circleSize > 0) {
    circles.push(circle({ x, y, r: circleSize, className: 'stroke' }));
    
    circleSize -= gap;
  }

  // Return all our circles as a single string;
  return circles.join('');
}

function squigglyLine(x, squiggleWidth, squiggleHeight) {
  const points = [];
  
  // Define an offset for how far the points should
  // be placed from the center of our line.
  let xOffset = squiggleWidth / 2;
  
  for(
    // Start slightly above the edge of our canvas
    let y = -1 * squiggleHeight; 
    // End slightly below the bottom
    y <= gridSize + squiggleHeight; 
    // Iterate down, adding new points to our array
    y += squiggleHeight
  ) {
    // Add a new point.
    // Adjust our x value by our offset;
    points.push({y, x: x + xOffset});
    // Flip our x offset so the next point is 
    // placed on the other side of our center line.
    xOffset *= -1;
  }
  
  // Use our points to build a path
  return `
    <path d="${spline(points)}" class="stroke" />
  `;
}

const patternEl =  document.querySelector('.pattern');

function draw() {
  const circleGroups = [];
  for (let i = 0; i < randomInt(12, 24); i++) {
    circleGroups.push(circleGroup({
      x: randomInt(0, gridSize),
      y: randomInt(0, gridSize),
      r: randomInt(80, 240)
    }))
  }
  
  const lines = [];
  const lineGap = randomInt(15, 25);
  const squiggleWidth = randomInt(10, 15);
  const squiggleHeight = randomInt(60, 90);
  for(let x = lineGap * -1; x < gridSize + lineGap; x += lineGap) {
    lines.push(squigglyLine(x, squiggleWidth, squiggleHeight));
  }
  
  patternEl.innerHTML = lines.join('') + circleGroups.join('');
}

draw();

document.querySelector('button').addEventListener('click', draw);

let themeClass = 'theme-';

document.querySelectorAll('input').forEach(input => {
  input.addEventListener('change', ({target}) => { 
    document.body.classList.remove(themeClass);
    themeClass = `theme-${target.value}`;
    document.body.classList.add(themeClass);
  });
});

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.