<div class="grid">
	<header>
		<h1>Paper Snowflake Maker</h1>
		<p class="intro">Drag the handles to generate a unique snowflake.</p>
	</header>
	<div class="container">
		<div class="handle-group">
			<div class="handle-group__inner">
				<div class="handle" data-index="0" data-x="--x1" data-y="--y1" data-x-drag="true"></div>
				<div class="handle" data-index="1" data-x="--x2" data-y="--y2" data-x-drag="true"></div>
				<div class="handle" data-index="2" data-x="--x3" data-y="--y3"></div>
				<div class="handle" data-index="3" data-x="--rx1a" data-y="--ry1a"></div>
				<div class="handle" data-index="4" data-x="--rx1b" data-y="--ry1b" data-x-drag="true"></div>
				<div class="handle" data-index="5" data-x="--rx1c" data-y="--ry1c"></div>
				<div class="handle" data-index="6" data-x="--centerX1" data-y="--centerY1"></div>
				<div class="handle" data-index="7" data-x="--centerX2" data-y="--centerY2"></div>
				<div class="handle" data-index="8" data-x="--lx1a" data-y="--ly1a"></div>
				<div class="handle" data-index="9" data-x="--lx1b" data-y="--ly1b" data-x-drag="true"></div>
				<div class="handle" data-index="10" data-x="--lx1c" data-y="--ly1c"></div>
				<div class="handle" data-index="11" data-x="--lx2a" data-y="--ly2a"></div>
				<div class="handle" data-index="12" data-x="--lx2b" data-y="--ly2b" data-x-drag="true"></div>
				<div class="handle" data-index="13" data-x="--lx2c" data-y="--ly2c"></div>
			</div>
		</div>
		<div class="snowflake">
			<div class="triangle" style="--i: 0"></div>
			<div class="triangle" style="--i: 1"></div>
			<div class="triangle" style="--i: 2"></div>
			<div class="triangle" style="--i: 3"></div>
			<div class="triangle" style="--i: 4"></div>
			<div class="triangle" style="--i: 5"></div>
			<div class="triangle" style="--i: 6"></div>
			<div class="triangle" style="--i: 7"></div>
			<div class="triangle" style="--i: 8"></div>
			<div class="triangle" style="--i: 9"></div>
			<div class="triangle" style="--i: 10"></div>
			<div class="triangle" style="--i: 11"></div>
		</div>
	</div>
	<div class="result-wrapper">
		<h2>Clip path for single segment:</h2>
		<div class="result">
			<code data-path-result></code>
		</div>
		<button data-btn>Hide guides</button>
	</div>
</div>

@import url("https://fonts.googleapis.com/css?family=Montserrat:400,700");

* {
	box-sizing: border-box;
}

:root {
	--c1: rgba(250, 250, 250, 1);
	--c2: rgba(227, 227, 230, 1);
}

body {
	font-family: Montserrat, sans-serif;
	margin: 0;
	padding: 1rem;
	max-width: 100%;
	overflow-x: hidden;
	display: flex;
	justify-content: center;
	background-color: rgba(21, 21, 31, 1);
	color: var(--c1);
	
	@media (min-width: 45rem) {
		font-size: 1.1rem;
	}
}

.intro {
	@media (min-width: 45rem) {
		font-size: 1.4rem;
	}
}

h1 {
	@media (min-width: 45rem) {
		font-size: 3.5rem;
		margin: 0 0 1rem;
	}
}

h2 {
	font-size: 1.5rem;
	margin: 0 0 1rem;
}

.result {
	line-height: 1.4;
}

.grid {
	display: grid;
	gap: 1rem;
	max-width: 80rem;
	
	@media (min-width: 60rem) {
		grid-template-columns: auto minmax(0, 1fr);
		gap: 2rem 3rem;
	}
}

header {
	grid-column: 1 / -1;
	text-align: center;
	
	@media (min-width: 60rem) {
		padding: 3rem 0;
	}
}

.container {
	--height: min(90vw, 40rem);
	--s: 12;
	--angle: calc(360deg / var(--s));
	
	--y3: 10%; /* Clip y position */
	--a: calc(100% - var(--y3)); /* Adjacent side length */
	
	--x3: calc(var(--o) * (100% - var(--y3)));
	--x1: 15%;
	--y1: 0;
	--x2: 20%;
	--y2: 17%;
	--bg: repeating-conic-gradient(var(--c1), var(--c1), var(--angle), var(--c2) var(--angle), var(--c2) calc(var(--angle) * 2));
	
	/* Cutout sections */
	--ry1a: 20%;
	--ry1c: 62%;
	/* "free" values */
	--rx1b: 28%;
	--ry1b: 25%;
	/* Calculate x values for right side */
	--rx1a: calc(var(--o) * (100% - var(--ry1a)));
	--rx1c: calc(var(--o) * (100% - var(--ry1c)));
	--r1: var(--rx1a) var(--ry1a), var(--rx1b) var(--ry1b), var(--rx1c) var(--ry1c);
	
	--centerY1: 85%;
	--centerX1: calc(var(--o) * (100% - var(--centerY1)));
	--centerX2: 0;
	--centerY2: 90%;
	--center: var(--centerX1) var(--centerY1), var(--centerX2) var(--centerY2);
	
	/* Left side values should start at x = 0 */
	--ly1a: 70%;
	--ly1c: 50%;
	/* These are "free" values, can be anything */
	--lx1b: 13%;
	--ly1b: 45%;
	--l1: 0 var(--ly1a), var(--lx1b) var(--ly1b), 0 var(--ly1c);
	
	--ly2a: 40%;
	--ly2c: 15%;
	/* These are "free" values, can be anything */
	--lx2b: 9%;
	--ly2b: 30%;
	--l2: 0 var(--ly2a), var(--lx2b) var(--ly2b), 0 var(--ly2c);
	
	position: relative;
	width: var(--height);
	height: var(--height);
	opacity: 0;
}

.snowflake {
	&::before {
		--c1: rgba(205, 205, 255, 0.05);
		--c2: rgba(200, 200, 255, 0.12);
		
		content: '';
		position: absolute;
		inset: 0;
		background: repeating-conic-gradient(var(--c1), var(--c1), var(--angle), var(--c2) var(--angle), var(--c2) calc(var(--angle) * 2));
	}
}

.handle-group {
	width: 50%;
	height: 50%;
	position: absolute;
	left: 50%;
	top: 0;
}

.handle-group__inner {
	width: 100%;
	height: 100%;
	position: relative;
}

.handle {
	width: 20px;
	height: 20px;
	position: absolute;
	background-color: transparentize(darkorchid, 0.65);
	border-radius: 50%;
	border: 2px solid darkorchid;
	z-index: 1;
	transform: translate3d(-50%, -50%, 0);
	opacity: 0.9;
	
	&.is-active {
		background-color: deeppink;
		border: 2px solid deeppink;
		opacity: 0.8;
	}
}

.triangle {
	--clip: polygon(0 0, var(--x1) var(--y1), var(--x2) var(--y2), var(--x3) var(--y3), var(--r1), var(--center), var(--l1), var(--l2));
	--bg: repeating-conic-gradient(from 0deg at 0 100%, white, rgba(200, 200, 200, 1) var(--angle));
	
	width: 50%;
	height: 50%;
	position: absolute;
	top: 0;
	left: 50%;
	background: var(--bg);
	-webkit-clip-path: var(--clip);
	clip-path: var(--clip);
	transform: rotate(calc(var(--i) * var(--angle)));
	transform-origin: bottom left;
	
	&:nth-child(even) {
		transform: rotateY(180deg) rotate(calc((var(--i) - 1) * var(--angle)));
		background: repeating-conic-gradient(from 0deg at 0 100%, rgba(200, 200, 200, 1), white var(--angle));
	}
	
	&:first-child {
		background: rgba(180, 180, 205, 1);
	}
}

.result {
	display: grid;
	grid-template-columns: auto 1fr;
	gap: 0 0.25rem;
	padding: 1rem;
	background-color: rgba(12, 12, 20, 1);
	border-radius: 0.4rem;
	margin-bottom: 1rem;
	
	.current {
		color: deeppink;
	}
	
	@media (min-width: 60rem) {
		margin-bottom: 2rem;
	}
}

.handles-hidden {
	filter: drop-shadow(1rem 1rem 1rem rgba(0, 0, 0, 0.9));
	
	.snowflake::before {
		display: none;
	}
	
	.triangle:first-child {
		background: var(--bg);
	}
}

button {
	padding: 0.75rem 1rem;
	border: none;
	border-radius: 0.5rem;
	background: rgba(204, 211, 219, 1);
	cursor: pointer;
	font-family: inherit;
	font-size: 1.1rem;
	font-weight: 700;
	min-width: 10rem;
	transition: background 200ms;
	
	&:hover,
	&:focus {
		background: rgba(155, 155, 170, 1);
	}
	
	&.is-active {
		background: deeppink;
		
		&:hover,
		&:focus {
			background: rgba(155, 155, 170, 1);
		}
	}
}
View Compiled
const el = document.querySelector('.container')
const snowflake = document.querySelector('.snowflake')
const segments = getComputedStyle(el).getPropertyValue('--s')
const switcher = document.querySelector('[data-btn]')
const angle = 360 / parseFloat(segments)
const handles = [...document.querySelectorAll('.handle')]
const pathResult = document.querySelector('[data-path-result]')
const triangles = [...document.querySelectorAll('.triangle')]
const button = document.querySelector('[data-btn]')

const getTanFromDegrees = (angle) => {
	const tanFromDegrees = Math.tan(angle * Math.PI / 180)
	return tanFromDegrees.toFixed(3)
}

const initialize = () => {
	el.style.setProperty('--o', getTanFromDegrees(angle))
	el.style.opacity = 1
}

/* Set initial positions for handles */
const positionHandles = () => {
	handles.forEach((handle) => {
		const propX = getComputedStyle(el).getPropertyValue(handle.dataset.x)
		const propY = getComputedStyle(el).getPropertyValue(handle.dataset.y)

		handle.style.left = propX
		handle.style.top = propY
	})
}

const a = el.clientHeight / 2
const boundWidth = getTanFromDegrees(angle) * a + 10

const calculateCustomProperty = (relativeValue, parentValue) => {
	const value = (relativeValue / parentValue) * 100
	const valueAsPercentage = Math.round(value)
	return `${valueAsPercentage}%`
}

const setActiveState = (target) => {
	handles.forEach((handle) => {
		handle.classList.remove('is-active')
	})
	
	target.classList.add('is-active')
}

const setClipPathValue = (activeIndex) => {
	const values = handles.map((handle, index) => {
		const triangle = triangles[0]
		const { x, y } = handle.dataset
		const valueX = getComputedStyle(triangle).getPropertyValue(x)
		const valueY = getComputedStyle(triangle).getPropertyValue(y)
		const className = index == parseInt(activeIndex) ? 'current' : ''
		
		if (valueX) {
			return `<span class="${className}">${valueX} ${valueY}</span>`
		} else {
			return `<span class="${className}">0 ${valueY}</span>`
		}
	}).join(', ')
	
	return pathResult.innerHTML = `clip-path: polygon(0 0, ${values});`
}

setClipPathValue()


const onDragHandle = (pointermove) => {
	const { target, x, y } = pointermove
	
	/* Prevent error if target is dragged out of bounds */
	if (!target.parentElement) return
	
	const parentPosX = target.parentElement.getBoundingClientRect().left
	const parentPosY = target.parentElement.getBoundingClientRect().top
	const parentWidth = target.parentElement.clientWidth
	const parentHeight = target.parentElement.clientHeight
	const relativeX = x - parentPosX
	const relativeY = y - parentPosY
	const propertyX = target.dataset.x
	const propertyY = target.dataset.y
	
	const x1 = calculateCustomProperty(relativeX, parentWidth)
	const y1 = calculateCustomProperty(relativeY, parentHeight)

	/* Set custom properties */
	el.style.setProperty(propertyY, y1)
	
	if (target.dataset.xDrag) {
		el.style.setProperty(propertyX, x1)
	}
	
	setClipPathValue(target.dataset.index)
}

Draggable.create('.handle', {
	bounds: { top: -12, left: -12, width: boundWidth + 24, height: a + 24 },
	onDrag: onDragHandle,
	edgeResistance: 1,
	onPress: ({ target }) => {
		setActiveState(target)
		setClipPathValue(target.dataset.index)
	}
})

const toggleHandles = () => {
	const handleGroup = document.querySelector('.handle-group')
	
	el.classList.toggle('handles-hidden')
	button.classList.toggle('is-active')
	
	if (handleGroup.hidden) {
		button.innerText = 'Hide guides'
	} else {
		button.innerText = 'Show guides'
	}
	
	handleGroup.hidden = !handleGroup.hidden
}

initialize()
positionHandles()
button.addEventListener('click', toggleHandles)

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

  1. https://unpkg.co/gsap@3/dist/gsap.min.js
  2. https://unpkg.com/gsap@3/dist/Draggable.min.js