<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);
});
});
This Pen doesn't use any external CSS resources.
This Pen doesn't use any external JavaScript resources.