1<!DOCTYPE html>
2<html lang="en">
3<head>
4<meta charset="UTF-8" />
5<meta name="viewport" content="width=device-width,initial-scale=1" />
6<title>Twisting Layered Prism</title>
7<style>
8 :root{
9 --bg:#f3f3fb;
10 --top:#8b9899;
11 --left:#9aa8ad;
12 --right:#8f9d9f;
13 --w:136px;
14 --h:340px;
15 --d:78px;
16 --layers:56;
17 --step:6px;
18 --dur:4.8s;
19 }
20
21 html,body{
22 height:100%;
23 margin:0;
24 overflow:hidden;
25 background:var(--bg);
26 font-family:sans-serif;
27 }
28
29 .stage{
30 width:100vw;
31 height:100vh;
32 display:grid;
33 place-items:center;
34 perspective:1100px;
35 perspective-origin:50% 42%;
36 }
37
38 .object{
39 position:relative;
40 width:var(--w);
41 height:var(--h);
42 transform-style:preserve-3d;
43 transform:
44 rotateX(-24deg)
45 rotateY(-45deg)
46 translateY(10px);
47 }
48
49 .stack{
50 position:absolute;
51 inset:0;
52 transform-style:preserve-3d;
53 animation: spin var(--dur) linear infinite;
54 }
55
56 .slice{
57 position:absolute;
58 left:0;
59 width:var(--w);
60 height:var(--step);
61 transform-style:preserve-3d;
62 transform-origin:50% 50%;
63 }
64
65 .face{
66 position:absolute;
67 inset:0;
68 backface-visibility:hidden;
69 }
70
71 .topFace{
72 background:var(--top);
73 transform:rotateX(90deg) translateZ(calc(var(--step) / 2));
74 transform-origin:50% 50%;
75 }
76
77 .leftFace{
78 width:var(--d);
79 height:var(--step);
80 background:var(--left);
81 left:0;
82 transform-origin:0 50%;
83 transform:
84 rotateY(-90deg)
85 translateZ(calc(var(--step) / 2));
86 }
87
88 .rightFace{
89 width:var(--d);
90 height:var(--step);
91 background:var(--right);
92 right:0;
93 left:auto;
94 transform-origin:100% 50%;
95 transform:
96 rotateY(90deg)
97 translateZ(calc(var(--step) / 2));
98 }
99
100 .cap{
101 position:absolute;
102 width:var(--w);
103 height:var(--d);
104 background:var(--top);
105 transform-style:preserve-3d;
106 }
107
108 .cap.top{
109 transform:
110 translateY(0)
111 rotateX(90deg)
112 translateZ(calc(var(--d) / 2));
113 }
114
115 .cap.bottom{
116 transform:
117 translateY(calc(var(--h) - 2px))
118 rotateX(90deg)
119 translateZ(calc(var(--d) / 2));
120 filter:brightness(.96);
121 }
122
123 @keyframes spin{
124 0%,18%{
125 transform:rotateY(0deg);
126 }
127 50%{
128 transform:rotateY(180deg);
129 }
130 82%,100%{
131 transform:rotateY(360deg);
132 }
133 }
134</style>
135</head>
136<body>
137<div class="stage">
138 <div class="object">
139 <div class="stack" id="stack">
140 <div class="cap top"></div>
141 <div class="cap bottom"></div>
142 </div>
143 </div>
144</div>
145
146<script>
147 const stack = document.getElementById('stack');
148
149 const layers = 56;
150 const step = 6;
151 const height = layers * step;
152 const amp = 34; // side in/out deformation
153 const twistMax = 34; // per-slice local twist
154 const phaseShift = Math.PI * 0.95;
155
156 for(let i=0;i<layers;i++){
157 const t = i/(layers-1); // 0..1 bottom->top
158 const y = i * step;
159
160 // Envelope keeps top/bottom nearly rectangular and middle most deformed
161 const env = Math.sin(Math.PI * t);
162
163 // Two opposing traveling waves around the height to mimic the frames
164 const waveA = Math.sin((t * 2 - 0.15) * Math.PI);
165 const waveB = Math.sin((t * 2 + 0.85) * Math.PI);
166
167 // Horizontal squeeze/expand and local twist
168 const x = amp * env * Math.sin(2 * Math.PI * t + phaseShift);
169 const rot = twistMax * env * Math.sin(2 * Math.PI * t - 0.35);
170
171 const slice = document.createElement('div');
172 slice.className = 'slice';
173 slice.style.top = `${y}px`;
174 slice.style.transform = `
175 translate3d(${x}px,0,0)
176 rotateZ(${rot}deg)
177 scaleX(${1 - 0.10 * env * Math.cos(2*Math.PI*t)} )
178 `;
179
180 const top = document.createElement('div');
181 top.className = 'face topFace';
182
183 const left = document.createElement('div');
184 left.className = 'face leftFace';
185
186 const right = document.createElement('div');
187 right.className = 'face rightFace';
188
189 // subtle tonal variation by height, like stacked sheets catching light
190 const l1 = 70 - env * 4 + waveA * 1.5;
191 const l2 = 74 - env * 3 + waveB * 1.2;
192 const l3 = 68 - env * 5 - waveA * 1.2;
193
194 top.style.background = `hsl(190 8% ${l1}%)`;
195 left.style.background = `hsl(195 11% ${l2}%)`;
196 right.style.background = `hsl(190 10% ${l3}%)`;
197
198 slice.append(top,left,right);
199 stack.appendChild(slice);
200 }
201</script>
202</body>
203</html>