What we’re building
A thin equilateral triangle outline in the background that rotates.
Three shapes placed at the triangle’s vertices: a regular 5-point star, a regular (equilateral) triangle, and a square.
All three shapes morph into the same perfect circle (a smiley face via a mask) and back, in sync with the rotation and mask timing.
All shape contours live in HTML (inside <path d=»…»> and <animate>), no JS, CSS only for styling and timing of visibility/spin.
Why it works (big ideas)
Unified topology for morphing
Browsers morph SVG paths by interpolating numbers in the d attribute. For a clean morph, each path must have the same “shape grammar”: same number/order of commands and numbers.
We enforce this by describing every state (star, triangle, square, circle) as a single path: M + 12 cubic C segments + Z.
Straight lines (square/triangle edges, star arms) are simply degenerate cubics (control points on the line), so they still fit the same “12 × C” pattern.
A perfect-looking circle
The target “one shape” is a circle centered at (50,50) with radius 30. We render it with 12 cubic Bézier arcs (30° each) so it’s smooth (no polygon facets).
Mask for the smiley face
Eyes and mouth are cut out with a <mask>. We don’t morph them — we fade the holes on only during the circle phase (so they move in sync with the morph).
Global sync
All SMIL morphs (<animate attributeName=»d»>) use the exact same duration and keyTimes.
The CSS animations (mask visibility and orbit spin) use the same total duration and linear timing.
Result: everything switches phases at the same instants.
Safe layout
The “stage” is 80vmin so it always fills 80% of the smaller viewport side.
The revolving centers sit on a radius that keeps shapes inside the viewBox while rotating.
Step-by-step (kid-friendly)
Make a stage
Create an SVG that fills a square area (viewBox=»0 0 100 100″) and put it in a centered container sized 80vmin. That keeps things proportional on any screen.
Add a rotating triangle frame
Draw a thin equilateral triangle outline around the center. Put the triangle and your shapes in a <g id=»orbit»> and rotate that group with CSS. Now the triangle and shapes spin together.
Place the 3 shapes at the triangle’s corners
We use three <path>s: star, triangle, square. Their centers are exactly at the frame’s three vertices. Sizes are chosen to look balanced and never clip.
Describe each shape with the same “grammar”
Each <path> starts with M, then 12 cubic C segments, then Z.
Even straight edges are drawn with cubic segments whose control points are on the line (so it stays straight). That keeps morphing stable and pretty.
Define the target circle
The circle (smiley body) is drawn as 12 cubic arcs (30° per arc). That gives a visually perfect circle.
Morph the shapes into the circle and back
On each path, add <animate attributeName=»d»> with:
dur=»11.2s» total;
hold 5s on the start shape → morph 0.6s → hold 5s on circle → morph 0.6s back;
implemented with keyTimes=»0;0.4464286;0.5;0.9464286;1″ (those are 5/11.2, 0.6/11.2, etc.).
All three use identical timings.
Cut eyes and mouth only during circle phase
Create a <mask> with two circles (eyes) and a curved path (mouth).
Use a CSS animation on the mask group’s opacity with the same 11.2s duration and the same phase splits so the holes show only in the circle phase.
Style once with CSS
Use currentColor to fill all shapes using a single —orange variable. Keep comments clear. Respect prefers-reduced-motion.
Test sync
On load, everything starts at t=0.
After ~5s, there’s a 0.6s morph into the circle, the holes appear, the orbit keeps rotating smoothly.
After another 5s, there’s a 0.6s morph back.
All events cross the same timestamps → no phase drift.
Troubleshooting checklist
One shape lags behind? Double-check all durations are 11.2s and keyTimes are identical across all <animate>and CSS @keyframes.
Edges look polygonal? Make sure the circle is the cubic-Bézier version (not polygon points).
Anything clips the edges? Keep the orbit centers radius and shape sizes as provided.
Motion sensitivity? The code respects prefers-reduced-motion: reduce to disable CSS animations.
✅ Production code for CodePen
Paste the next block into HTML and the second one into CSS.
Only comments, <title>, and <desc> are translated to English. Paths, numbers, timings remain the same.
🔸 HTML
<main class="stage"><main class="stage"> <svg viewBox="0 0 100 100" role="img" aria-labelledby="t d" focusable="false"> <title id="t">4 synced shapes → one smiley circle (morphing)</title> <desc id="d"> Background: a thin equilateral triangle outline rotates. At its vertices sit: a star, an equilateral triangle, and a square. All three morph in sync into a single circle with a smiley (mask), then back. All contours are defined in HTML. No JS. </desc><defs> <!-- Smiley mask: white = visible, black = holes (eyes/mouth) --> <mask id="smileMask"> <rect x="0" y="0" width="100" height="100" fill="white" /> <!-- Eyes + mouth: we show them only during the circle phase via CSS opacity animation --> <g id="smileHoles" opacity="0"> <!-- Eyes --> <circle cx="43" cy="45" r="3" fill="black" /> <circle cx="57" cy="45" r="3" fill="black" /> <!-- Mouth --> <path d="M40 58 Q50 68 60 58" fill="none" stroke="black" stroke-width="5" stroke-linecap="round" /> </g> </mask> </defs><!-- The rotating "orbit": background triangle + 3 morphing shapes at its vertices --> <g id="orbit"> <!-- Thin equilateral triangle outline (1px, non-scaling stroke), same group so it spins in sync --> <!-- Vertices: (50,24) (72.5,63) (27.5,63) — radius from center is 26 --> <path id="wireTri" d="M50 24 L72.5 63 L27.5 63 Z" fill="none" stroke="currentColor" stroke-width="1" vector-effect="non-scaling-stroke" /><!-- 1) STAR (bottom-left, center near 27.5,63) → CIRCLE(center 50,50; R=30) → STAR --> <path id="star" class="shape" mask="url(#smileMask)" d="M27.483 54 C27.82 55.036 28.157 56.073 28.494 57.109 C28.83 58.146 29.167 59.182 29.504 60.219 C31.684 60.219 33.863 60.219 36.043 60.219 C34.279 61.5 32.516 62.781 30.753 64.062 C31.426 66.135 32.1 68.208 32.773 70.281 C31.01 69 29.247 67.719 27.483 66.438 C26.602 67.078 25.72 67.719 24.838 68.359 C23.957 69 23.075 69.641 22.193 70.281 C22.867 68.208 23.54 66.135 24.214 64.062 C22.451 62.781 20.687 61.5 18.924 60.219 C21.103 60.219 23.283 60.219 25.463 60.219 C26.136 58.146 26.81 56.073 27.483 54 Z"> <!-- Morph timing: total 11.2s → hold 5s (0–44.64286%) → morph 0.6s (to 50%) → hold 5s → morph back 0.6s --> <animate attributeName="d" dur="11.2s" repeatCount="indefinite" calcMode="linear" keyTimes="0;0.4464286;0.5;0.9464286;1" values=" M27.483 54 C27.82 55.036 28.157 56.073 28.494 57.109 C28.83 58.146 29.167 59.182 29.504 60.219 C31.684 60.219 33.863 60.219 36.043 60.219 C34.279 61.5 32.516 62.781 30.753 64.062 C31.426 66.135 32.1 68.208 32.773 70.281 C31.01 69 29.247 67.719 27.483 66.438 C26.602 67.078 25.72 67.719 24.838 68.359 C23.957 69 23.075 69.641 22.193 70.281 C22.867 68.208 23.54 66.135 24.214 64.062 C22.451 62.781 20.687 61.5 18.924 60.219 C21.103 60.219 23.283 60.219 25.463 60.219 C26.136 58.146 26.81 56.073 27.483 54 Z; M27.483 54 C27.82 55.036 28.157 56.073 28.494 57.109 C28.83 58.146 29.167 59.182 29.504 60.219 C31.684 60.219 33.863 60.219 36.043 60.219 C34.279 61.5 32.516 62.781 30.753 64.062 C31.426 66.135 32.1 68.208 32.773 70.281 C31.01 69 29.247 67.719 27.483 66.438 C26.602 67.078 25.72 67.719 24.838 68.359 C23.957 69 23.075 69.641 22.193 70.281 C22.867 68.208 23.54 66.135 24.214 64.062 C22.451 62.781 20.687 61.5 18.924 60.219 C21.103 60.219 23.283 60.219 25.463 60.219 C26.136 58.146 26.81 56.073 27.483 54 Z; M50 20 C55.266 20 60.439 21.386 65 24.019 C69.561 26.652 73.348 30.439 75.981 35 C78.614 39.561 80 44.734 80 50 C80 55.266 78.614 60.439 75.981 65 C73.348 69.561 69.561 73.348 65 75.981 C60.439 78.614 55.266 80 50 80 C44.734 80 39.561 78.614 35 75.981 C30.439 73.348 26.652 69.561 24.019 65 C21.386 60.439 20 55.266 20 50 C20 44.734 21.386 39.561 24.019 35 C26.652 30.439 30.439 26.652 35 24.019 C39.561 21.386 44.734 20 50 20 Z; M50 20 C55.266 20 60.439 21.386 65 24.019 C69.561 26.652 73.348 30.439 75.981 35 C78.614 39.561 80 44.734 80 50 C80 55.266 78.614 60.439 75.981 65 C73.348 69.561 69.561 73.348 65 75.981 C60.439 78.614 55.266 80 50 80 C44.734 80 39.561 78.614 35 75.981 C30.439 73.348 26.652 69.561 24.019 65 C21.386 60.439 20 55.266 20 50 C20 44.734 21.386 39.561 24.019 35 C26.652 30.439 30.439 26.652 35 24.019 C39.561 21.386 44.734 20 50 20 Z; M27.483 54 C27.82 55.036 28.157 56.073 28.494 57.109 C28.83 58.146 29.167 59.182 29.504 60.219 C31.684 60.219 33.863 60.219 36.043 60.219 C34.279 61.5 32.516 62.781 30.753 64.062 C31.426 66.135 32.1 68.208 32.773 70.281 C31.01 69 29.247 67.719 27.483 66.438 C26.602 67.078 25.72 67.719 24.838 68.359 C23.957 69 23.075 69.641 22.193 70.281 C22.867 68.208 23.54 66.135 24.214 64.062 C22.451 62.781 20.687 61.5 18.924 60.219 C21.103 60.219 23.283 60.219 25.463 60.219 C26.136 58.146 26.81 56.073 27.483 54 Z" /> </path><!-- 2) EQUILATERAL TRIANGLE (bottom-right) → CIRCLE → TRIANGLE --> <path id="tri" class="shape" mask="url(#smileMask)" d="M72.517 54 C73.166 55.125 73.816 56.25 74.465 57.375 C75.115 58.5 75.764 59.625 76.414 60.75 C77.063 61.875 77.713 63 78.362 64.125 C79.012 65.25 79.661 66.375 80.311 67.5 C79.012 67.5 77.713 67.5 76.414 67.5 C75.115 67.5 73.816 67.5 72.517 67.5 C71.218 67.5 69.919 67.5 68.62 67.5 C67.321 67.5 66.021 67.5 64.722 67.5 C65.372 66.375 66.021 65.25 66.671 64.125 C67.321 63 67.97 61.875 68.62 60.75 C69.269 59.625 69.919 58.5 70.568 57.375 C71.218 56.25 71.867 55.125 72.517 54 Z"> <animate attributeName="d" dur="11.2s" repeatCount="indefinite" calcMode="linear" keyTimes="0;0.4464286;0.5;0.9464286;1" values=" M72.517 54 C73.166 55.125 73.816 56.25 74.465 57.375 C75.115 58.5 75.764 59.625 76.414 60.75 C77.063 61.875 77.713 63 78.362 64.125 C79.012 65.25 79.661 66.375 80.311 67.5 C79.012 67.5 77.713 67.5 76.414 67.5 C75.115 67.5 73.816 67.5 72.517 67.5 C71.218 67.5 69.919 67.5 68.62 67.5 C67.321 67.5 66.021 67.5 64.722 67.5 C65.372 66.375 66.021 65.25 66.671 64.125 C67.321 63 67.97 61.875 68.62 60.75 C69.269 59.625 69.919 58.5 70.568 57.375 C71.218 56.25 71.867 55.125 72.517 54 Z; M72.517 54 C73.166 55.125 73.816 56.25 74.465 57.375 C75.115 58.5 75.764 59.625 76.414 60.75 C77.063 61.875 77.713 63 78.362 64.125 C79.012 65.25 79.661 66.375 80.311 67.5 C79.012 67.5 77.713 67.5 76.414 67.5 C75.115 67.5 73.816 67.5 72.517 67.5 C71.218 67.5 69.919 67.5 68.62 67.5 C67.321 67.5 66.021 67.5 64.722 67.5 C65.372 66.375 66.021 65.25 66.671 64.125 C67.321 63 67.97 61.875 68.62 60.75 C69.269 59.625 69.919 58.5 70.568 57.375 C71.218 56.25 71.867 55.125 72.517 54 Z; M50 20 C55.266 20 60.439 21.386 65 24.019 C69.561 26.652 73.348 30.439 75.981 35 C78.614 39.561 80 44.734 80 50 C80 55.266 78.614 60.439 75.981 65 C73.348 69.561 69.561 73.348 65 75.981 C60.439 78.614 55.266 80 50 80 C44.734 80 39.561 78.614 35 75.981 C30.439 73.348 26.652 69.561 24.019 65 C21.386 60.439 20 55.266 20 50 C20 44.734 21.386 39.561 24.019 35 C26.652 30.439 30.439 26.652 35 24.019 C39.561 21.386 44.734 20 50 20 Z; M50 20 C55.266 20 60.439 21.386 65 24.019 C69.561 26.652 73.348 30.439 75.981 35 C78.614 39.561 80 44.734 80 50 C80 55.266 78.614 60.439 75.981 65 C73.348 69.561 69.561 73.348 65 75.981 C60.439 78.614 55.266 80 50 80 C44.734 80 39.561 78.614 35 75.981 C30.439 73.348 26.652 69.561 24.019 65 C21.386 60.439 20 55.266 20 50 C20 44.734 21.386 39.561 24.019 35 C26.652 30.439 30.439 26.652 35 24.019 C39.561 21.386 44.734 20 50 20 Z; M72.517 54 C73.166 55.125 73.816 56.25 74.465 57.375 C75.115 58.5 75.764 59.625 76.414 60.75 C77.063 61.875 77.713 63 78.362 64.125 C79.012 65.25 79.661 66.375 80.311 67.5 C79.012 67.5 77.713 67.5 76.414 67.5 C75.115 67.5 73.816 67.5 72.517 67.5 C71.218 67.5 69.919 67.5 68.62 67.5 C67.321 67.5 66.021 67.5 64.722 67.5 C65.372 66.375 66.021 65.25 66.671 64.125 C67.321 63 67.97 61.875 68.62 60.75 C69.269 59.625 69.919 58.5 70.568 57.375 C71.218 56.25 71.867 55.125 72.517 54 Z" /> </path><!-- 3) SQUARE (top-center) → CIRCLE → SQUARE --> <path id="square" class="shape" mask="url(#smileMask)" d="M43 17 C44.556 17 46.111 17 47.667 17 C49.222 17 50.778 17 52.333 17 C53.889 17 55.444 17 57 17 C57 18.556 57 20.111 57 21.667 C57 23.222 57 24.778 57 26.333 C57 27.889 57 29.444 57 31 C55.444 31 53.889 31 52.333 31 C50.778 31 49.222 31 47.667 31 C46.111 31 44.556 31 43 31 C43 29.444 43 27.889 43 26.333 C43 24.778 43 23.222 43 21.667 C43 20.111 43 18.556 43 17 Z"> <animate attributeName="d" dur="11.2s" repeatCount="indefinite" calcMode="linear" keyTimes="0;0.4464286;0.5;0.9464286;1" values=" M43 17 C44.556 17 46.111 17 47.667 17 C49.222 17 50.778 17 52.333 17 C53.889 17 55.444 17 57 17 C57 18.556 57 20.111 57 21.667 C57 23.222 57 24.778 57 26.333 C57 27.889 57 29.444 57 31 C55.444 31 53.889 31 52.333 31 C50.778 31 49.222 31 47.667 31 C46.111 31 44.556 31 43 31 C43 29.444 43 27.889 43 26.333 C43 24.778 43 23.222 43 21.667 C43 20.111 43 18.556 43 17 Z; M43 17 C44.556 17 46.111 17 47.667 17 C49.222 17 50.778 17 52.333 17 C53.889 17 55.444 17 57 17 C57 18.556 57 20.111 57 21.667 C57 23.222 57 24.778 57 26.333 C57 27.889 57 29.444 57 31 C55.444 31 53.889 31 52.333 31 C50.778 31 49.222 31 47.667 31 C46.111 31 44.556 31 43 31 C43 29.444 43 27.889 43 26.333 C43 24.778 43 23.222 43 21.667 C43 20.111 43 18.556 43 17 Z; M50 20 C55.266 20 60.439 21.386 65 24.019 C69.561 26.652 73.348 30.439 75.981 35 C78.614 39.561 80 44.734 80 50 C80 55.266 78.614 60.439 75.981 65 C73.348 69.561 69.561 73.348 65 75.981 C60.439 78.614 55.266 80 50 80 C44.734 80 39.561 78.614 35 75.981 C30.439 73.348 26.652 69.561 24.019 65 C21.386 60.439 20 55.266 20 50 C20 44.734 21.386 39.561 24.019 35 C26.652 30.439 30.439 26.652 35 24.019 C39.561 21.386 44.734 20 50 20 Z; M50 20 C55.266 20 60.439 21.386 65 24.019 C69.561 26.652 73.348 30.439 75.981 35 C78.614 39.561 80 44.734 80 50 C80 55.266 78.614 60.439 75.981 65 C73.348 69.561 69.561 73.348 65 75.981 C60.439 78.614 55.266 80 50 80 C44.734 80 39.561 78.614 35 75.981 C30.439 73.348 26.652 69.561 24.019 65 C21.386 60.439 20 55.266 20 50 C20 44.734 21.386 39.561 24.019 35 C26.652 30.439 30.439 26.652 35 24.019 C39.561 21.386 44.734 20 50 20 Z; M43 17 C44.556 17 46.111 17 47.667 17 C49.222 17 50.778 17 52.333 17 C53.889 17 55.444 17 57 17 C57 18.556 57 20.111 57 21.667 C57 23.222 57 24.778 57 26.333 C57 27.889 57 29.444 57 31 C55.444 31 53.889 31 52.333 31 C50.778 31 49.222 31 47.667 31 C46.111 31 44.556 31 43 31 C43 29.444 43 27.889 43 26.333 C43 24.778 43 23.222 43 21.667 C43 20.111 43 18.556 43 17 Z" /> </path> </g> </svg> </main><!-- 3) SQUARE (top-center) → CIRCLE → SQUARE --></main>
🔸 CSS
:root { --orange: #ff8a00; }
html, body {
height: 100%;
margin: 0;
background: #111;
color: #ddd;
font-family: system-ui, -apple-system, Segoe UI, Roboto, sans-serif;
display: grid;
place-items: center;
}
/* Stage = 80% of the smaller viewport side as requested */
.stage {
width: 80vmin;
height: 80vmin;
display: grid;
place-items: center;
}
/* Single color from CSS; paths fill with currentColor */
.stage svg { color: var(—orange); }
.shape { fill: currentColor; }
/* Spin the entire orbit (outline + shapes) in perfect sync */
#orbit {
transform-origin: 50% 50%;
animation: spin 11.2s linear infinite;
}
@keyframes spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
/* Smiley holes visible only during the circle phase — same phase split as morph */
@keyframes holesOnOff {
/* 0–44.64286%: hold original shapes — no holes */
0%, 44.64286% { opacity: 0; }
/* 50–94.64286%: hold the circle — show eyes/mouth */
50%, 94.64286% { opacity: 1; }
/* 94.64286–100%: morph back — hide again */
100% { opacity: 0; }
}
#smileHoles { animation: holesOnOff 11.2s linear infinite both; }
/* Respect reduced motion */
@media (prefers-reduced-motion: reduce) {
#orbit, #smileHoles { animation: none; }
}