- let ro = 200, n = 3, f = .18, p = .2*ro;
- let ri = ro*(1 - f);
- let h = 2*ro + p, w = 2*h; 
- let ox = -.25*w, oy = -.5*h;
- let ba = 2*Math.PI/n, fs = .5*p;
- let vo = [], vi = [];
- let rv = .02*ro; 
- let ra = ro + 3*rv, rc = Math.round(ri*Math.cos(.5*ba) - fs);
- let ms = 2*rv, mo = -.5*ms;

- for(let i = 0; i <= n; i++)
	- let ca = i*ba;
	- vo.push([ro*Math.cos(-ca), ro*Math.sin(-ca)]);
	- vi.push([ri*Math.cos(ca), ri*Math.sin(ca)]);

- vo = vo.concat(vi).map(c => c.map(k => Math.round(k))), n = vo.length;
- let flat = vo.flat().join(' ');
- let tx = ~(.9*ri*Math.cos(.5*ba))

svg(viewBox=[ox, oy, w, h].join())
	style * { font: #{fs}px/ 2 ubuntu mono, consolas, monaco, monospace }
	marker#arr(viewBox=[mo, mo, 2*ms, ms].join(' ') 
						 markerWidth=2*ms markerHeight=ms orient='auto-start-reverse')
		path(d=`M${mo}${mo} ${ms} 0${mo} ${-mo} 0 0`)
	path.poly#draw(d=`M${flat}z` pathLength='1')
	- for(let i = 0; i < n; i++)
		- let m = 1 + (i === .5*n - 1 ? .18 : (i < .5*n ? .1 : -.2));
		circle(cx=vo[i][0] cy=vo[i][1] r=.02*ro)
		- if(i && i !== n - 1)
			- if(i === .5*n - 1)
				text(x=m*vo[i][0] y=m*vo[i][1]) P<tspan>0</tspan>=P<tspan>#{i}</tspan>
			- else if(i === .5*n)
				text(x=m*vo[i][0] y=m*vo[i][1]) P<tspan>#{i}</tspan>=P<tspan>#{n - 1}</tspan>
			- else
				text(x=m*vo[i][0] y=m*vo[i][1]) P<tspan>#{i}</tspan>
	path.line#at(d=`M0${-ra}A${ra} ${ra} 0 0 1 ${ra} 0`)
	text(dy=-3)
		textPath(href='#at' startOffset='50%') anticlockwise
	path.line#ac(d=`M${-rc} 0A${rc} ${rc} 0 0 0 ${rc} 0`)
	text(dominant-baseline='hanging' dy=4)
		textPath(href='#ac' startOffset='50%') clockwise
	
	path.poly#open(d=`M${flat}z` transform=`translate(${h})`)
	path.line(d=`M${h + ri - p} 0h${-.25*ro}`)
	text(x=h + tx dominant-baseline='middle') tunnel opening
View Compiled
$c: #00908a #41024f #f60c61 #ff6a00 #ffb231;
$t: 4s;
$f: .8;
$p: $f*100%;

svg > path, circle { stroke: nth($c, 2) }

svg > path { stroke-width: 2 }

.poly {
	fill: nth($c, 5);
	stroke-linejoin: round
}

[r] { fill: nth($c, 3) }

[r] + [x] { dominant-baseline: middle; font-style: italic }

tspan { font-size: .5em; dominant-baseline: text-before-edge
 }

.line, text, marker { color: nth($c, 1) }

.line {
	fill: none;
	stroke: currentcolor;
	marker-start: url(#arr)
}

text, marker { fill: currentcolor }

textPath, [r] + [x] { text-anchor: middle }

#draw {
	stroke-dasharray: 1;
	animation: a $t infinite;
	animation-name: draw, fill;
	animation-timing-function: linear, ease-out
}

@keyframes draw {
	0% { stroke-dashoffset: 1 }
	#{$p}, 100% { stroke-dashoffset: 0 }
}

@keyframes fill {
	0%, #{$p} { fill-opacity: 0 }
	#{.5*($p + 100%)}, 100% { fill-opacity: 1 }
}
View Compiled
const _OPEN = document.getElementById('open'), 
			T = 240;
		
let v = _OPEN.getAttribute('d').replace(/M|z/gi, '').split(' ').map(c => +c);
v = v.reduce((a, c, i) => i%2 ? a.concat([[v[i - 1], c]]) : a, []);

let max = 0, off, 
		n = v.length, 
		cpy = v.map(c => c), 
		idx = [0, .5*n - 1, .5*n, n - 1];

v.forEach(c => { if(c[1] > max) max = c[1] });
off = Math.round(.15*max);

function ani(t = 0) {
	let k = t/T, 
			y = off*.5*(1 - Math.cos(k*2*Math.PI));

	v.forEach((c, i) => {
		if(idx.includes(i)) {
			cpy[i][1] = Math.round((1 - 2*(i === 0 || (i === n - 1)))*y);
		}
	});

	_OPEN.setAttribute('d', `M${cpy.flat().join(' ')}z`)
	requestAnimationFrame(ani.bind(this, ++t))
};

ani(0)

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.